Project import generated by Copybara.

NOKEYCHECK=True
GitOrigin-RevId: 3c46b17d1e60ae6d918268bc61dd3226dd70bcbb
diff --git a/linux/sound/Kconfig b/linux/sound/Kconfig
new file mode 100644
index 0000000..fcad760
--- /dev/null
+++ b/linux/sound/Kconfig
@@ -0,0 +1,135 @@
+menuconfig SOUND
+	tristate "Sound card support"
+	depends on HAS_IOMEM
+	help
+	  If you have a sound card in your computer, i.e. if it can say more
+	  than an occasional beep, say Y.  Be sure to have all the information
+	  about your sound card and its configuration down (I/O port,
+	  interrupt and DMA channel), because you will be asked for it.
+
+	  You want to read the Sound-HOWTO, available from
+	  <http://www.tldp.org/docs.html#howto>. General information about
+	  the modular sound system is contained in the files
+	  <file:Documentation/sound/oss/Introduction>.  The file
+	  <file:Documentation/sound/oss/README.OSS> contains some slightly
+	  outdated but still useful information as well.  Newer sound
+	  driver documentation is found in <file:Documentation/sound/alsa/*>.
+
+	  If you have a PnP sound card and you want to configure it at boot
+	  time using the ISA PnP tools (read
+	  <http://www.roestock.demon.co.uk/isapnptools/>), then you need to
+	  compile the sound card support as a module and load that module
+	  after the PnP configuration is finished.  To do this, choose M here
+	  and read <file:Documentation/sound/oss/README.modules>; the module
+	  will be called soundcore.
+
+if SOUND
+
+config SOUND_OSS_CORE
+	bool
+	default n
+
+config SOUND_OSS_CORE_PRECLAIM
+	bool "Preclaim OSS device numbers"
+	depends on SOUND_OSS_CORE
+	default y
+	help
+	  With this option enabled, the kernel will claim all OSS device
+	  numbers if any OSS support (native or emulation) is enabled
+	  whether the respective module is loaded or not and try to load the
+	  appropriate module using sound-slot/service-* and char-major-*
+	  module aliases when one of the device numbers is opened.  With
+	  this option disabled, kernel will only claim actually in-use
+	  device numbers and opening a missing device will generate only the
+	  standard char-major-* aliases.
+
+	  The only visible difference is use of additional module aliases
+	  and whether OSS sound devices appear multiple times in
+	  /proc/devices.  sound-slot/service-* module aliases are scheduled
+	  to be removed (ie. PRECLAIM won't be available) and this option is
+	  to make the transition easier.  This option can be overridden
+	  during boot using the kernel parameter soundcore.preclaim_oss.
+
+	  Disabling this allows alternative OSS implementations.
+
+	  Please read Documentation/feature-removal-schedule.txt for
+	  details.
+
+	  If unsure, say Y.
+
+source "sound/oss/dmasound/Kconfig"
+
+if !M68K
+
+menuconfig SND
+	tristate "Advanced Linux Sound Architecture"
+	help
+	  Say 'Y' or 'M' to enable ALSA (Advanced Linux Sound Architecture),
+	  the new base sound system.
+
+	  For more information, see <http://www.alsa-project.org/>
+
+if SND
+
+source "sound/core/Kconfig"
+
+source "sound/drivers/Kconfig"
+
+source "sound/isa/Kconfig"
+
+source "sound/pci/Kconfig"
+
+source "sound/ppc/Kconfig"
+
+source "sound/aoa/Kconfig"
+
+source "sound/arm/Kconfig"
+
+source "sound/atmel/Kconfig"
+
+source "sound/spi/Kconfig"
+
+source "sound/mips/Kconfig"
+
+source "sound/sh/Kconfig"
+
+# the following will depend on the order of config.
+# here assuming USB is defined before ALSA
+source "sound/usb/Kconfig"
+
+# the following will depend on the order of config.
+# here assuming PCMCIA is defined before ALSA
+source "sound/pcmcia/Kconfig"
+
+source "sound/sparc/Kconfig"
+
+source "sound/parisc/Kconfig"
+
+source "sound/soc/Kconfig"
+
+endif # SND
+
+menuconfig SOUND_PRIME
+	tristate "Open Sound System (DEPRECATED)"
+	select SOUND_OSS_CORE
+	help
+	  Say 'Y' or 'M' to enable Open Sound System drivers.
+
+if SOUND_PRIME
+
+source "sound/oss/Kconfig"
+
+endif # SOUND_PRIME
+
+endif # !M68K
+
+endif # SOUND
+
+# AC97_BUS is used from both sound and ucb1400
+config AC97_BUS
+	tristate
+	help
+	  This is used to avoid config and link hard dependencies between the
+	  sound subsystem and other function drivers completely unrelated to
+	  sound although they're sharing the AC97 bus. Concerned drivers
+	  should "select" this.
diff --git a/linux/sound/Makefile b/linux/sound/Makefile
new file mode 100644
index 0000000..ec467de
--- /dev/null
+++ b/linux/sound/Makefile
@@ -0,0 +1,19 @@
+# Makefile for the Linux sound card driver
+#
+
+obj-$(CONFIG_SOUND) += soundcore.o
+obj-$(CONFIG_SOUND_PRIME) += sound_firmware.o
+obj-$(CONFIG_SOUND_PRIME) += oss/
+obj-$(CONFIG_DMASOUND) += oss/
+obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
+	sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/
+obj-$(CONFIG_SND_AOA) += aoa/
+
+# This one must be compilable even if sound is configured out
+obj-$(CONFIG_AC97_BUS) += ac97_bus.o
+
+ifeq ($(CONFIG_SND),y)
+  obj-y += last.o
+endif
+
+soundcore-objs  := sound_core.o
diff --git a/linux/sound/ac97_bus.c b/linux/sound/ac97_bus.c
new file mode 100644
index 0000000..a351dd0
--- /dev/null
+++ b/linux/sound/ac97_bus.c
@@ -0,0 +1,77 @@
+/*
+ * Linux driver model AC97 bus interface
+ *
+ * Author:	Nicolas Pitre
+ * Created:	Jan 14, 2005
+ * Copyright:	(C) MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/string.h>
+#include <sound/ac97_codec.h>
+
+/*
+ * Let drivers decide whether they want to support given codec from their
+ * probe method.  Drivers have direct access to the struct snd_ac97 structure and may
+ * decide based on the id field amongst other things.
+ */
+static int ac97_bus_match(struct device *dev, struct device_driver *drv)
+{
+	return 1;
+}
+
+#ifdef CONFIG_PM
+static int ac97_bus_suspend(struct device *dev, pm_message_t state)
+{
+	int ret = 0;
+
+	if (dev->driver && dev->driver->suspend)
+		ret = dev->driver->suspend(dev, state);
+
+	return ret;
+}
+
+static int ac97_bus_resume(struct device *dev)
+{
+	int ret = 0;
+
+	if (dev->driver && dev->driver->resume)
+		ret = dev->driver->resume(dev);
+
+	return ret;
+}
+#endif /* CONFIG_PM */
+
+struct bus_type ac97_bus_type = {
+	.name		= "ac97",
+	.match		= ac97_bus_match,
+#ifdef CONFIG_PM
+	.suspend	= ac97_bus_suspend,
+	.resume		= ac97_bus_resume,
+#endif /* CONFIG_PM */
+};
+
+static int __init ac97_bus_init(void)
+{
+	return bus_register(&ac97_bus_type);
+}
+
+subsys_initcall(ac97_bus_init);
+
+static void __exit ac97_bus_exit(void)
+{
+	bus_unregister(&ac97_bus_type);
+}
+
+module_exit(ac97_bus_exit);
+
+EXPORT_SYMBOL(ac97_bus_type);
+
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/aoa/Kconfig b/linux/sound/aoa/Kconfig
new file mode 100644
index 0000000..c081e18
--- /dev/null
+++ b/linux/sound/aoa/Kconfig
@@ -0,0 +1,17 @@
+menuconfig SND_AOA
+	tristate "Apple Onboard Audio driver"
+	depends on PPC_PMAC
+	select SND_PCM
+	---help---
+	This option enables the new driver for the various
+	Apple Onboard Audio components.
+
+if SND_AOA
+
+source "sound/aoa/fabrics/Kconfig"
+
+source "sound/aoa/codecs/Kconfig"
+
+source "sound/aoa/soundbus/Kconfig"
+
+endif	# SND_AOA
diff --git a/linux/sound/aoa/Makefile b/linux/sound/aoa/Makefile
new file mode 100644
index 0000000..a8c037f
--- /dev/null
+++ b/linux/sound/aoa/Makefile
@@ -0,0 +1,4 @@
+obj-$(CONFIG_SND_AOA) += core/
+obj-$(CONFIG_SND_AOA_SOUNDBUS) += soundbus/
+obj-$(CONFIG_SND_AOA) += fabrics/
+obj-$(CONFIG_SND_AOA) += codecs/
diff --git a/linux/sound/aoa/aoa-gpio.h b/linux/sound/aoa/aoa-gpio.h
new file mode 100644
index 0000000..6065b03
--- /dev/null
+++ b/linux/sound/aoa/aoa-gpio.h
@@ -0,0 +1,83 @@
+/*
+ * Apple Onboard Audio GPIO definitions
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#ifndef __AOA_GPIO_H
+#define __AOA_GPIO_H
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <asm/prom.h>
+
+typedef void (*notify_func_t)(void *data);
+
+enum notify_type {
+	AOA_NOTIFY_HEADPHONE,
+	AOA_NOTIFY_LINE_IN,
+	AOA_NOTIFY_LINE_OUT,
+};
+
+struct gpio_runtime;
+struct gpio_methods {
+	/* for initialisation/de-initialisation of the GPIO layer */
+	void (*init)(struct gpio_runtime *rt);
+	void (*exit)(struct gpio_runtime *rt);
+
+	/* turn off headphone, speakers, lineout */
+	void (*all_amps_off)(struct gpio_runtime *rt);
+	/* turn headphone, speakers, lineout back to previous setting */
+	void (*all_amps_restore)(struct gpio_runtime *rt);
+
+	void (*set_headphone)(struct gpio_runtime *rt, int on);
+	void (*set_speakers)(struct gpio_runtime *rt, int on);
+	void (*set_lineout)(struct gpio_runtime *rt, int on);
+	void (*set_master)(struct gpio_runtime *rt, int on);
+
+	int (*get_headphone)(struct gpio_runtime *rt);
+	int (*get_speakers)(struct gpio_runtime *rt);
+	int (*get_lineout)(struct gpio_runtime *rt);
+	int (*get_master)(struct gpio_runtime *rt);
+
+	void (*set_hw_reset)(struct gpio_runtime *rt, int on);
+
+	/* use this to be notified of any events. The notification
+	 * function is passed the data, and is called in process
+	 * context by the use of schedule_work.
+	 * The interface for it is that setting a function to NULL
+	 * removes it, and they return 0 if the operation succeeded,
+	 * and -EBUSY if the notification is already assigned by
+	 * someone else. */
+	int (*set_notify)(struct gpio_runtime *rt,
+			  enum notify_type type,
+			  notify_func_t notify,
+			  void *data);
+	/* returns 0 if not plugged in, 1 if plugged in
+	 * or a negative error code */
+	int (*get_detect)(struct gpio_runtime *rt,
+			  enum notify_type type);
+};
+
+struct gpio_notification {
+	struct delayed_work work;
+	notify_func_t notify;
+	void *data;
+	void *gpio_private;
+	struct mutex mutex;
+};
+
+struct gpio_runtime {
+	/* to be assigned by fabric */
+	struct device_node *node;
+	/* since everyone needs this pointer anyway... */
+	struct gpio_methods *methods;
+	/* to be used by the gpio implementation */
+	int implementation_private;
+	struct gpio_notification headphone_notify;
+	struct gpio_notification line_in_notify;
+	struct gpio_notification line_out_notify;
+};
+
+#endif /* __AOA_GPIO_H */
diff --git a/linux/sound/aoa/aoa.h b/linux/sound/aoa/aoa.h
new file mode 100644
index 0000000..e087894
--- /dev/null
+++ b/linux/sound/aoa/aoa.h
@@ -0,0 +1,129 @@
+/*
+ * Apple Onboard Audio definitions
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#ifndef __AOA_H
+#define __AOA_H
+#include <asm/prom.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/asound.h>
+#include <sound/control.h>
+#include "aoa-gpio.h"
+#include "soundbus/soundbus.h"
+
+#define MAX_CODEC_NAME_LEN	32
+
+struct aoa_codec {
+	char	name[MAX_CODEC_NAME_LEN];
+
+	struct module *owner;
+
+	/* called when the fabric wants to init this codec.
+	 * Do alsa card manipulations from here. */
+	int (*init)(struct aoa_codec *codec);
+
+	/* called when the fabric is done with the codec.
+	 * The alsa card will be cleaned up so don't bother. */
+	void (*exit)(struct aoa_codec *codec);
+
+	/* May be NULL, but can be used by the fabric.
+	 * Refcounting is the codec driver's responsibility */
+	struct device_node *node;
+
+	/* assigned by fabric before init() is called, points
+	 * to the soundbus device. Cannot be NULL. */
+	struct soundbus_dev *soundbus_dev;
+
+	/* assigned by the fabric before init() is called, points
+	 * to the fabric's gpio runtime record for the relevant
+	 * device. */
+	struct gpio_runtime *gpio;
+
+	/* assigned by the fabric before init() is called, contains
+	 * a codec specific bitmask of what outputs and inputs are
+	 * actually connected */
+	u32 connected;
+
+	/* data the fabric can associate with this structure */
+	void *fabric_data;
+
+	/* private! */
+	struct list_head list;
+	struct aoa_fabric *fabric;
+};
+
+/* return 0 on success */
+extern int
+aoa_codec_register(struct aoa_codec *codec);
+extern void
+aoa_codec_unregister(struct aoa_codec *codec);
+
+#define MAX_LAYOUT_NAME_LEN	32
+
+struct aoa_fabric {
+	char	name[MAX_LAYOUT_NAME_LEN];
+
+	struct module *owner;
+
+	/* once codecs register, they are passed here after.
+	 * They are of course not initialised, since the
+	 * fabric is responsible for initialising some fields
+	 * in the codec structure! */
+	int (*found_codec)(struct aoa_codec *codec);
+	/* called for each codec when it is removed,
+	 * also in the case that aoa_fabric_unregister
+	 * is called and all codecs are removed
+	 * from this fabric.
+	 * Also called if found_codec returned 0 but
+	 * the codec couldn't initialise. */
+	void (*remove_codec)(struct aoa_codec *codec);
+	/* If found_codec returned 0, and the codec
+	 * could be initialised, this is called. */
+	void (*attached_codec)(struct aoa_codec *codec);
+};
+
+/* return 0 on success, -EEXIST if another fabric is
+ * registered, -EALREADY if the same fabric is registered.
+ * Passing NULL can be used to test for the presence
+ * of another fabric, if -EALREADY is returned there is
+ * no other fabric present.
+ * In the case that the function returns -EALREADY
+ * and the fabric passed is not NULL, all codecs
+ * that are not assigned yet are passed to the fabric
+ * again for reconsideration. */
+extern int
+aoa_fabric_register(struct aoa_fabric *fabric, struct device *dev);
+
+/* it is vital to call this when the fabric exits!
+ * When calling, the remove_codec will be called
+ * for all codecs, unless it is NULL. */
+extern void
+aoa_fabric_unregister(struct aoa_fabric *fabric);
+
+/* if for some reason you want to get rid of a codec
+ * before the fabric is removed, use this.
+ * Note that remove_codec is called for it! */
+extern void
+aoa_fabric_unlink_codec(struct aoa_codec *codec);
+
+/* alsa help methods */
+struct aoa_card {
+	struct snd_card *alsa_card;
+};
+        
+extern int aoa_snd_device_new(snd_device_type_t type,
+	void * device_data, struct snd_device_ops * ops);
+extern struct snd_card *aoa_get_card(void);
+extern int aoa_snd_ctl_add(struct snd_kcontrol* control);
+
+/* GPIO stuff */
+extern struct gpio_methods *pmf_gpio_methods;
+extern struct gpio_methods *ftr_gpio_methods;
+/* extern struct gpio_methods *map_gpio_methods; */
+
+#endif /* __AOA_H */
diff --git a/linux/sound/aoa/codecs/Kconfig b/linux/sound/aoa/codecs/Kconfig
new file mode 100644
index 0000000..808eb11
--- /dev/null
+++ b/linux/sound/aoa/codecs/Kconfig
@@ -0,0 +1,32 @@
+config SND_AOA_ONYX
+	tristate "support Onyx chip"
+	select I2C
+	select I2C_POWERMAC
+	---help---
+	This option enables support for the Onyx (pcm3052)
+	codec chip found in the latest Apple machines
+	(most of those with digital audio output).
+
+#config SND_AOA_TOPAZ
+#	tristate "support Topaz chips"
+#	---help---
+#	This option enables support for the Topaz (CS84xx)
+#	codec chips found in the latest Apple machines,
+#	these chips do the digital input and output on
+#	some PowerMacs.
+
+config SND_AOA_TAS
+	tristate "support TAS chips"
+	select I2C
+	select I2C_POWERMAC
+	---help---
+	This option enables support for the tas chips
+	found in a lot of Apple Machines, especially
+	iBooks and PowerBooks without digital.
+
+config SND_AOA_TOONIE
+	tristate "support Toonie chip"
+	---help---
+	This option enables support for the toonie codec
+	found in the Mac Mini. If you have a Mac Mini and
+	want to hear sound, select this option.
diff --git a/linux/sound/aoa/codecs/Makefile b/linux/sound/aoa/codecs/Makefile
new file mode 100644
index 0000000..c3ee77f
--- /dev/null
+++ b/linux/sound/aoa/codecs/Makefile
@@ -0,0 +1,7 @@
+snd-aoa-codec-onyx-objs := onyx.o
+snd-aoa-codec-tas-objs := tas.o
+snd-aoa-codec-toonie-objs := toonie.o
+
+obj-$(CONFIG_SND_AOA_ONYX) += snd-aoa-codec-onyx.o
+obj-$(CONFIG_SND_AOA_TAS) += snd-aoa-codec-tas.o
+obj-$(CONFIG_SND_AOA_TOONIE) += snd-aoa-codec-toonie.o
diff --git a/linux/sound/aoa/codecs/onyx.c b/linux/sound/aoa/codecs/onyx.c
new file mode 100644
index 0000000..91852e4
--- /dev/null
+++ b/linux/sound/aoa/codecs/onyx.c
@@ -0,0 +1,1149 @@
+/*
+ * Apple Onboard Audio driver for Onyx codec
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ *
+ *
+ * This is a driver for the pcm3052 codec chip (codenamed Onyx)
+ * that is present in newer Apple hardware (with digital output).
+ *
+ * The Onyx codec has the following connections (listed by the bit
+ * to be used in aoa_codec.connected):
+ *  0: analog output
+ *  1: digital output
+ *  2: line input
+ *  3: microphone input
+ * Note that even though I know of no machine that has for example
+ * the digital output connected but not the analog, I have handled
+ * all the different cases in the code so that this driver may serve
+ * as a good example of what to do.
+ *
+ * NOTE: This driver assumes that there's at most one chip to be
+ * 	 used with one alsa card, in form of creating all kinds
+ *	 of mixer elements without regard for their existence.
+ *	 But snd-aoa assumes that there's at most one card, so
+ *	 this means you can only have one onyx on a system. This
+ *	 should probably be fixed by changing the assumption of
+ *	 having just a single card on a system, and making the
+ *	 'card' pointer accessible to anyone who needs it instead
+ *	 of hiding it in the aoa_snd_* functions...
+ *
+ */
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa");
+
+#include "onyx.h"
+#include "../aoa.h"
+#include "../soundbus/soundbus.h"
+
+
+#define PFX "snd-aoa-codec-onyx: "
+
+struct onyx {
+	/* cache registers 65 to 80, they are write-only! */
+	u8			cache[16];
+	struct i2c_client	*i2c;
+	struct aoa_codec	codec;
+	u32			initialised:1,
+				spdif_locked:1,
+				analog_locked:1,
+				original_mute:2;
+	int			open_count;
+	struct codec_info	*codec_info;
+
+	/* mutex serializes concurrent access to the device
+	 * and this structure.
+	 */
+	struct mutex mutex;
+};
+#define codec_to_onyx(c) container_of(c, struct onyx, codec)
+
+/* both return 0 if all ok, else on error */
+static int onyx_read_register(struct onyx *onyx, u8 reg, u8 *value)
+{
+	s32 v;
+
+	if (reg != ONYX_REG_CONTROL) {
+		*value = onyx->cache[reg-FIRSTREGISTER];
+		return 0;
+	}
+	v = i2c_smbus_read_byte_data(onyx->i2c, reg);
+	if (v < 0)
+		return -1;
+	*value = (u8)v;
+	onyx->cache[ONYX_REG_CONTROL-FIRSTREGISTER] = *value;
+	return 0;
+}
+
+static int onyx_write_register(struct onyx *onyx, u8 reg, u8 value)
+{
+	int result;
+
+	result = i2c_smbus_write_byte_data(onyx->i2c, reg, value);
+	if (!result)
+		onyx->cache[reg-FIRSTREGISTER] = value;
+	return result;
+}
+
+/* alsa stuff */
+
+static int onyx_dev_register(struct snd_device *dev)
+{
+	return 0;
+}
+
+static struct snd_device_ops ops = {
+	.dev_register = onyx_dev_register,
+};
+
+/* this is necessary because most alsa mixer programs
+ * can't properly handle the negative range */
+#define VOLUME_RANGE_SHIFT	128
+
+static int onyx_snd_vol_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = -128 + VOLUME_RANGE_SHIFT;
+	uinfo->value.integer.max = -1 + VOLUME_RANGE_SHIFT;
+	return 0;
+}
+
+static int onyx_snd_vol_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+	s8 l, r;
+
+	mutex_lock(&onyx->mutex);
+	onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l);
+	onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r);
+	mutex_unlock(&onyx->mutex);
+
+	ucontrol->value.integer.value[0] = l + VOLUME_RANGE_SHIFT;
+	ucontrol->value.integer.value[1] = r + VOLUME_RANGE_SHIFT;
+
+	return 0;
+}
+
+static int onyx_snd_vol_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+	s8 l, r;
+
+	if (ucontrol->value.integer.value[0] < -128 + VOLUME_RANGE_SHIFT ||
+	    ucontrol->value.integer.value[0] > -1 + VOLUME_RANGE_SHIFT)
+		return -EINVAL;
+	if (ucontrol->value.integer.value[1] < -128 + VOLUME_RANGE_SHIFT ||
+	    ucontrol->value.integer.value[1] > -1 + VOLUME_RANGE_SHIFT)
+		return -EINVAL;
+
+	mutex_lock(&onyx->mutex);
+	onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l);
+	onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r);
+
+	if (l + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[0] &&
+	    r + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[1]) {
+		mutex_unlock(&onyx->mutex);
+		return 0;
+	}
+
+	onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_LEFT,
+			    ucontrol->value.integer.value[0]
+			     - VOLUME_RANGE_SHIFT);
+	onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT,
+			    ucontrol->value.integer.value[1]
+			     - VOLUME_RANGE_SHIFT);
+	mutex_unlock(&onyx->mutex);
+
+	return 1;
+}
+
+static struct snd_kcontrol_new volume_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Master Playback Volume",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = onyx_snd_vol_info,
+	.get = onyx_snd_vol_get,
+	.put = onyx_snd_vol_put,
+};
+
+/* like above, this is necessary because a lot
+ * of alsa mixer programs don't handle ranges
+ * that don't start at 0 properly.
+ * even alsamixer is one of them... */
+#define INPUTGAIN_RANGE_SHIFT	(-3)
+
+static int onyx_snd_inputgain_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 3 + INPUTGAIN_RANGE_SHIFT;
+	uinfo->value.integer.max = 28 + INPUTGAIN_RANGE_SHIFT;
+	return 0;
+}
+
+static int onyx_snd_inputgain_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+	u8 ig;
+
+	mutex_lock(&onyx->mutex);
+	onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &ig);
+	mutex_unlock(&onyx->mutex);
+
+	ucontrol->value.integer.value[0] =
+		(ig & ONYX_ADC_PGA_GAIN_MASK) + INPUTGAIN_RANGE_SHIFT;
+
+	return 0;
+}
+
+static int onyx_snd_inputgain_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+	u8 v, n;
+
+	if (ucontrol->value.integer.value[0] < 3 + INPUTGAIN_RANGE_SHIFT ||
+	    ucontrol->value.integer.value[0] > 28 + INPUTGAIN_RANGE_SHIFT)
+		return -EINVAL;
+	mutex_lock(&onyx->mutex);
+	onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
+	n = v;
+	n &= ~ONYX_ADC_PGA_GAIN_MASK;
+	n |= (ucontrol->value.integer.value[0] - INPUTGAIN_RANGE_SHIFT)
+		& ONYX_ADC_PGA_GAIN_MASK;
+	onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, n);
+	mutex_unlock(&onyx->mutex);
+
+	return n != v;
+}
+
+static struct snd_kcontrol_new inputgain_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Master Capture Volume",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = onyx_snd_inputgain_info,
+	.get = onyx_snd_inputgain_get,
+	.put = onyx_snd_inputgain_put,
+};
+
+static int onyx_snd_capture_source_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "Line-In", "Microphone" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int onyx_snd_capture_source_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+	s8 v;
+
+	mutex_lock(&onyx->mutex);
+	onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
+	mutex_unlock(&onyx->mutex);
+
+	ucontrol->value.enumerated.item[0] = !!(v&ONYX_ADC_INPUT_MIC);
+
+	return 0;
+}
+
+static void onyx_set_capture_source(struct onyx *onyx, int mic)
+{
+	s8 v;
+
+	mutex_lock(&onyx->mutex);
+	onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
+	v &= ~ONYX_ADC_INPUT_MIC;
+	if (mic)
+		v |= ONYX_ADC_INPUT_MIC;
+	onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, v);
+	mutex_unlock(&onyx->mutex);
+}
+
+static int onyx_snd_capture_source_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	if (ucontrol->value.enumerated.item[0] > 1)
+		return -EINVAL;
+	onyx_set_capture_source(snd_kcontrol_chip(kcontrol),
+				ucontrol->value.enumerated.item[0]);
+	return 1;
+}
+
+static struct snd_kcontrol_new capture_source_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	/* If we name this 'Input Source', it properly shows up in
+	 * alsamixer as a selection, * but it's shown under the
+	 * 'Playback' category.
+	 * If I name it 'Capture Source', it shows up in strange
+	 * ways (two bools of which one can be selected at a
+	 * time) but at least it's shown in the 'Capture'
+	 * category.
+	 * I was told that this was due to backward compatibility,
+	 * but I don't understand then why the mangling is *not*
+	 * done when I name it "Input Source".....
+	 */
+	.name = "Capture Source",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = onyx_snd_capture_source_info,
+	.get = onyx_snd_capture_source_get,
+	.put = onyx_snd_capture_source_put,
+};
+
+#define onyx_snd_mute_info	snd_ctl_boolean_stereo_info
+
+static int onyx_snd_mute_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+	u8 c;
+
+	mutex_lock(&onyx->mutex);
+	onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &c);
+	mutex_unlock(&onyx->mutex);
+
+	ucontrol->value.integer.value[0] = !(c & ONYX_MUTE_LEFT);
+	ucontrol->value.integer.value[1] = !(c & ONYX_MUTE_RIGHT);
+
+	return 0;
+}
+
+static int onyx_snd_mute_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+	u8 v = 0, c = 0;
+	int err = -EBUSY;
+
+	mutex_lock(&onyx->mutex);
+	if (onyx->analog_locked)
+		goto out_unlock;
+
+	onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
+	c = v;
+	c &= ~(ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT);
+	if (!ucontrol->value.integer.value[0])
+		c |= ONYX_MUTE_LEFT;
+	if (!ucontrol->value.integer.value[1])
+		c |= ONYX_MUTE_RIGHT;
+	err = onyx_write_register(onyx, ONYX_REG_DAC_CONTROL, c);
+
+ out_unlock:
+	mutex_unlock(&onyx->mutex);
+
+	return !err ? (v != c) : err;
+}
+
+static struct snd_kcontrol_new mute_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Master Playback Switch",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = onyx_snd_mute_info,
+	.get = onyx_snd_mute_get,
+	.put = onyx_snd_mute_put,
+};
+
+
+#define onyx_snd_single_bit_info	snd_ctl_boolean_mono_info
+
+#define FLAG_POLARITY_INVERT	1
+#define FLAG_SPDIFLOCK		2
+
+static int onyx_snd_single_bit_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+	u8 c;
+	long int pv = kcontrol->private_value;
+	u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT;
+	u8 address = (pv >> 8) & 0xff;
+	u8 mask = pv & 0xff;
+
+	mutex_lock(&onyx->mutex);
+	onyx_read_register(onyx, address, &c);
+	mutex_unlock(&onyx->mutex);
+
+	ucontrol->value.integer.value[0] = !!(c & mask) ^ polarity;
+
+	return 0;
+}
+
+static int onyx_snd_single_bit_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+	u8 v = 0, c = 0;
+	int err;
+	long int pv = kcontrol->private_value;
+	u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT;
+	u8 spdiflock = (pv >> 16) & FLAG_SPDIFLOCK;
+	u8 address = (pv >> 8) & 0xff;
+	u8 mask = pv & 0xff;
+
+	mutex_lock(&onyx->mutex);
+	if (spdiflock && onyx->spdif_locked) {
+		/* even if alsamixer doesn't care.. */
+		err = -EBUSY;
+		goto out_unlock;
+	}
+	onyx_read_register(onyx, address, &v);
+	c = v;
+	c &= ~(mask);
+	if (!!ucontrol->value.integer.value[0] ^ polarity)
+		c |= mask;
+	err = onyx_write_register(onyx, address, c);
+
+ out_unlock:
+	mutex_unlock(&onyx->mutex);
+
+	return !err ? (v != c) : err;
+}
+
+#define SINGLE_BIT(n, type, description, address, mask, flags)	 	\
+static struct snd_kcontrol_new n##_control = {				\
+	.iface = SNDRV_CTL_ELEM_IFACE_##type,				\
+	.name = description,						\
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,			\
+	.info = onyx_snd_single_bit_info,				\
+	.get = onyx_snd_single_bit_get,					\
+	.put = onyx_snd_single_bit_put,					\
+	.private_value = (flags << 16) | (address << 8) | mask		\
+}
+
+SINGLE_BIT(spdif,
+	   MIXER,
+	   SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+	   ONYX_REG_DIG_INFO4,
+	   ONYX_SPDIF_ENABLE,
+	   FLAG_SPDIFLOCK);
+SINGLE_BIT(ovr1,
+	   MIXER,
+	   "Oversampling Rate",
+	   ONYX_REG_DAC_CONTROL,
+	   ONYX_OVR1,
+	   0);
+SINGLE_BIT(flt0,
+	   MIXER,
+	   "Fast Digital Filter Rolloff",
+	   ONYX_REG_DAC_FILTER,
+	   ONYX_ROLLOFF_FAST,
+	   FLAG_POLARITY_INVERT);
+SINGLE_BIT(hpf,
+	   MIXER,
+	   "Highpass Filter",
+	   ONYX_REG_ADC_HPF_BYPASS,
+	   ONYX_HPF_DISABLE,
+	   FLAG_POLARITY_INVERT);
+SINGLE_BIT(dm12,
+	   MIXER,
+	   "Digital De-Emphasis",
+	   ONYX_REG_DAC_DEEMPH,
+	   ONYX_DIGDEEMPH_CTRL,
+	   0);
+
+static int onyx_spdif_info(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int onyx_spdif_mask_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	/* datasheet page 30, all others are 0 */
+	ucontrol->value.iec958.status[0] = 0x3e;
+	ucontrol->value.iec958.status[1] = 0xff;
+
+	ucontrol->value.iec958.status[3] = 0x3f;
+	ucontrol->value.iec958.status[4] = 0x0f;
+
+	return 0;
+}
+
+static struct snd_kcontrol_new onyx_spdif_mask = {
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
+	.info =		onyx_spdif_info,
+	.get =		onyx_spdif_mask_get,
+};
+
+static int onyx_spdif_get(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+	u8 v;
+
+	mutex_lock(&onyx->mutex);
+	onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v);
+	ucontrol->value.iec958.status[0] = v & 0x3e;
+
+	onyx_read_register(onyx, ONYX_REG_DIG_INFO2, &v);
+	ucontrol->value.iec958.status[1] = v;
+
+	onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v);
+	ucontrol->value.iec958.status[3] = v & 0x3f;
+
+	onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
+	ucontrol->value.iec958.status[4] = v & 0x0f;
+	mutex_unlock(&onyx->mutex);
+
+	return 0;
+}
+
+static int onyx_spdif_put(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	struct onyx *onyx = snd_kcontrol_chip(kcontrol);
+	u8 v;
+
+	mutex_lock(&onyx->mutex);
+	onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v);
+	v = (v & ~0x3e) | (ucontrol->value.iec958.status[0] & 0x3e);
+	onyx_write_register(onyx, ONYX_REG_DIG_INFO1, v);
+
+	v = ucontrol->value.iec958.status[1];
+	onyx_write_register(onyx, ONYX_REG_DIG_INFO2, v);
+
+	onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v);
+	v = (v & ~0x3f) | (ucontrol->value.iec958.status[3] & 0x3f);
+	onyx_write_register(onyx, ONYX_REG_DIG_INFO3, v);
+
+	onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
+	v = (v & ~0x0f) | (ucontrol->value.iec958.status[4] & 0x0f);
+	onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v);
+	mutex_unlock(&onyx->mutex);
+
+	return 1;
+}
+
+static struct snd_kcontrol_new onyx_spdif_ctrl = {
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =		onyx_spdif_info,
+	.get =		onyx_spdif_get,
+	.put =		onyx_spdif_put,
+};
+
+/* our registers */
+
+static u8 register_map[] = {
+	ONYX_REG_DAC_ATTEN_LEFT,
+	ONYX_REG_DAC_ATTEN_RIGHT,
+	ONYX_REG_CONTROL,
+	ONYX_REG_DAC_CONTROL,
+	ONYX_REG_DAC_DEEMPH,
+	ONYX_REG_DAC_FILTER,
+	ONYX_REG_DAC_OUTPHASE,
+	ONYX_REG_ADC_CONTROL,
+	ONYX_REG_ADC_HPF_BYPASS,
+	ONYX_REG_DIG_INFO1,
+	ONYX_REG_DIG_INFO2,
+	ONYX_REG_DIG_INFO3,
+	ONYX_REG_DIG_INFO4
+};
+
+static u8 initial_values[ARRAY_SIZE(register_map)] = {
+	0x80, 0x80, /* muted */
+	ONYX_MRST | ONYX_SRST, /* but handled specially! */
+	ONYX_MUTE_LEFT | ONYX_MUTE_RIGHT,
+	0, /* no deemphasis */
+	ONYX_DAC_FILTER_ALWAYS,
+	ONYX_OUTPHASE_INVERTED,
+	(-1 /*dB*/ + 8) & 0xF, /* line in selected, -1 dB gain*/
+	ONYX_ADC_HPF_ALWAYS,
+	(1<<2),	/* pcm audio */
+	2,	/* category: pcm coder */
+	0,	/* sampling frequency 44.1 kHz, clock accuracy level II */
+	1	/* 24 bit depth */
+};
+
+/* reset registers of chip, either to initial or to previous values */
+static int onyx_register_init(struct onyx *onyx)
+{
+	int i;
+	u8 val;
+	u8 regs[sizeof(initial_values)];
+
+	if (!onyx->initialised) {
+		memcpy(regs, initial_values, sizeof(initial_values));
+		if (onyx_read_register(onyx, ONYX_REG_CONTROL, &val))
+			return -1;
+		val &= ~ONYX_SILICONVERSION;
+		val |= initial_values[3];
+		regs[3] = val;
+	} else {
+		for (i=0; i<sizeof(register_map); i++)
+			regs[i] = onyx->cache[register_map[i]-FIRSTREGISTER];
+	}
+
+	for (i=0; i<sizeof(register_map); i++) {
+		if (onyx_write_register(onyx, register_map[i], regs[i]))
+			return -1;
+	}
+	onyx->initialised = 1;
+	return 0;
+}
+
+static struct transfer_info onyx_transfers[] = {
+	/* this is first so we can skip it if no input is present...
+	 * No hardware exists with that, but it's here as an example
+	 * of what to do :) */
+	{
+		/* analog input */
+		.formats = SNDRV_PCM_FMTBIT_S8 |
+			   SNDRV_PCM_FMTBIT_S16_BE |
+			   SNDRV_PCM_FMTBIT_S24_BE,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.transfer_in = 1,
+		.must_be_clock_source = 0,
+		.tag = 0,
+	},
+	{
+		/* if analog and digital are currently off, anything should go,
+		 * so this entry describes everything we can do... */
+		.formats = SNDRV_PCM_FMTBIT_S8 |
+			   SNDRV_PCM_FMTBIT_S16_BE |
+			   SNDRV_PCM_FMTBIT_S24_BE
+#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
+			   | SNDRV_PCM_FMTBIT_COMPRESSED_16BE
+#endif
+		,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.tag = 0,
+	},
+	{
+		/* analog output */
+		.formats = SNDRV_PCM_FMTBIT_S8 |
+			   SNDRV_PCM_FMTBIT_S16_BE |
+			   SNDRV_PCM_FMTBIT_S24_BE,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.transfer_in = 0,
+		.must_be_clock_source = 0,
+		.tag = 1,
+	},
+	{
+		/* digital pcm output, also possible for analog out */
+		.formats = SNDRV_PCM_FMTBIT_S8 |
+			   SNDRV_PCM_FMTBIT_S16_BE |
+			   SNDRV_PCM_FMTBIT_S24_BE,
+		.rates = SNDRV_PCM_RATE_32000 |
+			 SNDRV_PCM_RATE_44100 |
+			 SNDRV_PCM_RATE_48000,
+		.transfer_in = 0,
+		.must_be_clock_source = 0,
+		.tag = 2,
+	},
+#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
+	/* Once alsa gets supports for this kind of thing we can add it... */
+	{
+		/* digital compressed output */
+		.formats =  SNDRV_PCM_FMTBIT_COMPRESSED_16BE,
+		.rates = SNDRV_PCM_RATE_32000 |
+			 SNDRV_PCM_RATE_44100 |
+			 SNDRV_PCM_RATE_48000,
+		.tag = 2,
+	},
+#endif
+	{}
+};
+
+static int onyx_usable(struct codec_info_item *cii,
+		       struct transfer_info *ti,
+		       struct transfer_info *out)
+{
+	u8 v;
+	struct onyx *onyx = cii->codec_data;
+	int spdif_enabled, analog_enabled;
+
+	mutex_lock(&onyx->mutex);
+	onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
+	spdif_enabled = !!(v & ONYX_SPDIF_ENABLE);
+	onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
+	analog_enabled =
+		(v & (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT))
+		 != (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT);
+	mutex_unlock(&onyx->mutex);
+
+	switch (ti->tag) {
+	case 0: return 1;
+	case 1:	return analog_enabled;
+	case 2: return spdif_enabled;
+	}
+	return 1;
+}
+
+static int onyx_prepare(struct codec_info_item *cii,
+			struct bus_info *bi,
+			struct snd_pcm_substream *substream)
+{
+	u8 v;
+	struct onyx *onyx = cii->codec_data;
+	int err = -EBUSY;
+
+	mutex_lock(&onyx->mutex);
+
+#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
+	if (substream->runtime->format == SNDRV_PCM_FMTBIT_COMPRESSED_16BE) {
+		/* mute and lock analog output */
+		onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
+		if (onyx_write_register(onyx,
+					ONYX_REG_DAC_CONTROL,
+					v | ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT))
+			goto out_unlock;
+		onyx->analog_locked = 1;
+		err = 0;
+		goto out_unlock;
+	}
+#endif
+	switch (substream->runtime->rate) {
+	case 32000:
+	case 44100:
+	case 48000:
+		/* these rates are ok for all outputs */
+		/* FIXME: program spdif channel control bits here so that
+		 *	  userspace doesn't have to if it only plays pcm! */
+		err = 0;
+		goto out_unlock;
+	default:
+		/* got some rate that the digital output can't do,
+		 * so disable and lock it */
+		onyx_read_register(cii->codec_data, ONYX_REG_DIG_INFO4, &v);
+		if (onyx_write_register(onyx,
+					ONYX_REG_DIG_INFO4,
+					v & ~ONYX_SPDIF_ENABLE))
+			goto out_unlock;
+		onyx->spdif_locked = 1;
+		err = 0;
+		goto out_unlock;
+	}
+
+ out_unlock:
+	mutex_unlock(&onyx->mutex);
+
+	return err;
+}
+
+static int onyx_open(struct codec_info_item *cii,
+		     struct snd_pcm_substream *substream)
+{
+	struct onyx *onyx = cii->codec_data;
+
+	mutex_lock(&onyx->mutex);
+	onyx->open_count++;
+	mutex_unlock(&onyx->mutex);
+
+	return 0;
+}
+
+static int onyx_close(struct codec_info_item *cii,
+		      struct snd_pcm_substream *substream)
+{
+	struct onyx *onyx = cii->codec_data;
+
+	mutex_lock(&onyx->mutex);
+	onyx->open_count--;
+	if (!onyx->open_count)
+		onyx->spdif_locked = onyx->analog_locked = 0;
+	mutex_unlock(&onyx->mutex);
+
+	return 0;
+}
+
+static int onyx_switch_clock(struct codec_info_item *cii,
+			     enum clock_switch what)
+{
+	struct onyx *onyx = cii->codec_data;
+
+	mutex_lock(&onyx->mutex);
+	/* this *MUST* be more elaborate later... */
+	switch (what) {
+	case CLOCK_SWITCH_PREPARE_SLAVE:
+		onyx->codec.gpio->methods->all_amps_off(onyx->codec.gpio);
+		break;
+	case CLOCK_SWITCH_SLAVE:
+		onyx->codec.gpio->methods->all_amps_restore(onyx->codec.gpio);
+		break;
+	default: /* silence warning */
+		break;
+	}
+	mutex_unlock(&onyx->mutex);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int onyx_suspend(struct codec_info_item *cii, pm_message_t state)
+{
+	struct onyx *onyx = cii->codec_data;
+	u8 v;
+	int err = -ENXIO;
+
+	mutex_lock(&onyx->mutex);
+	if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v))
+		goto out_unlock;
+	onyx_write_register(onyx, ONYX_REG_CONTROL, v | ONYX_ADPSV | ONYX_DAPSV);
+	/* Apple does a sleep here but the datasheet says to do it on resume */
+	err = 0;
+ out_unlock:
+	mutex_unlock(&onyx->mutex);
+
+	return err;
+}
+
+static int onyx_resume(struct codec_info_item *cii)
+{
+	struct onyx *onyx = cii->codec_data;
+	u8 v;
+	int err = -ENXIO;
+
+	mutex_lock(&onyx->mutex);
+
+	/* reset codec */
+	onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
+	msleep(1);
+	onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1);
+	msleep(1);
+	onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
+	msleep(1);
+
+	/* take codec out of suspend (if it still is after reset) */
+	if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v))
+		goto out_unlock;
+	onyx_write_register(onyx, ONYX_REG_CONTROL, v & ~(ONYX_ADPSV | ONYX_DAPSV));
+	/* FIXME: should divide by sample rate, but 8k is the lowest we go */
+	msleep(2205000/8000);
+	/* reset all values */
+	onyx_register_init(onyx);
+	err = 0;
+ out_unlock:
+	mutex_unlock(&onyx->mutex);
+
+	return err;
+}
+
+#endif /* CONFIG_PM */
+
+static struct codec_info onyx_codec_info = {
+	.transfers = onyx_transfers,
+	.sysclock_factor = 256,
+	.bus_factor = 64,
+	.owner = THIS_MODULE,
+	.usable = onyx_usable,
+	.prepare = onyx_prepare,
+	.open = onyx_open,
+	.close = onyx_close,
+	.switch_clock = onyx_switch_clock,
+#ifdef CONFIG_PM
+	.suspend = onyx_suspend,
+	.resume = onyx_resume,
+#endif
+};
+
+static int onyx_init_codec(struct aoa_codec *codec)
+{
+	struct onyx *onyx = codec_to_onyx(codec);
+	struct snd_kcontrol *ctl;
+	struct codec_info *ci = &onyx_codec_info;
+	u8 v;
+	int err;
+
+	if (!onyx->codec.gpio || !onyx->codec.gpio->methods) {
+		printk(KERN_ERR PFX "gpios not assigned!!\n");
+		return -EINVAL;
+	}
+
+	onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
+	msleep(1);
+	onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1);
+	msleep(1);
+	onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
+	msleep(1);
+
+	if (onyx_register_init(onyx)) {
+		printk(KERN_ERR PFX "failed to initialise onyx registers\n");
+		return -ENODEV;
+	}
+
+	if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, onyx, &ops)) {
+		printk(KERN_ERR PFX "failed to create onyx snd device!\n");
+		return -ENODEV;
+	}
+
+	/* nothing connected? what a joke! */
+	if ((onyx->codec.connected & 0xF) == 0)
+		return -ENOTCONN;
+
+	/* if no inputs are present... */
+	if ((onyx->codec.connected & 0xC) == 0) {
+		if (!onyx->codec_info)
+			onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL);
+		if (!onyx->codec_info)
+			return -ENOMEM;
+		ci = onyx->codec_info;
+		*ci = onyx_codec_info;
+		ci->transfers++;
+	}
+
+	/* if no outputs are present... */
+	if ((onyx->codec.connected & 3) == 0) {
+		if (!onyx->codec_info)
+			onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL);
+		if (!onyx->codec_info)
+			return -ENOMEM;
+		ci = onyx->codec_info;
+		/* this is fine as there have to be inputs
+		 * if we end up in this part of the code */
+		*ci = onyx_codec_info;
+		ci->transfers[1].formats = 0;
+	}
+
+	if (onyx->codec.soundbus_dev->attach_codec(onyx->codec.soundbus_dev,
+						   aoa_get_card(),
+						   ci, onyx)) {
+		printk(KERN_ERR PFX "error creating onyx pcm\n");
+		return -ENODEV;
+	}
+#define ADDCTL(n)							\
+	do {								\
+		ctl = snd_ctl_new1(&n, onyx);				\
+		if (ctl) {						\
+			ctl->id.device =				\
+				onyx->codec.soundbus_dev->pcm->device;	\
+			err = aoa_snd_ctl_add(ctl);			\
+			if (err)					\
+				goto error;				\
+		}							\
+	} while (0)
+
+	if (onyx->codec.soundbus_dev->pcm) {
+		/* give the user appropriate controls
+		 * depending on what inputs are connected */
+		if ((onyx->codec.connected & 0xC) == 0xC)
+			ADDCTL(capture_source_control);
+		else if (onyx->codec.connected & 4)
+			onyx_set_capture_source(onyx, 0);
+		else
+			onyx_set_capture_source(onyx, 1);
+		if (onyx->codec.connected & 0xC)
+			ADDCTL(inputgain_control);
+
+		/* depending on what output is connected,
+		 * give the user appropriate controls */
+		if (onyx->codec.connected & 1) {
+			ADDCTL(volume_control);
+			ADDCTL(mute_control);
+			ADDCTL(ovr1_control);
+			ADDCTL(flt0_control);
+			ADDCTL(hpf_control);
+			ADDCTL(dm12_control);
+			/* spdif control defaults to off */
+		}
+		if (onyx->codec.connected & 2) {
+			ADDCTL(onyx_spdif_mask);
+			ADDCTL(onyx_spdif_ctrl);
+		}
+		if ((onyx->codec.connected & 3) == 3)
+			ADDCTL(spdif_control);
+		/* if only S/PDIF is connected, enable it unconditionally */
+		if ((onyx->codec.connected & 3) == 2) {
+			onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
+			v |= ONYX_SPDIF_ENABLE;
+			onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v);
+		}
+	}
+#undef ADDCTL
+	printk(KERN_INFO PFX "attached to onyx codec via i2c\n");
+
+	return 0;
+ error:
+	onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx);
+	snd_device_free(aoa_get_card(), onyx);
+	return err;
+}
+
+static void onyx_exit_codec(struct aoa_codec *codec)
+{
+	struct onyx *onyx = codec_to_onyx(codec);
+
+	if (!onyx->codec.soundbus_dev) {
+		printk(KERN_ERR PFX "onyx_exit_codec called without soundbus_dev!\n");
+		return;
+	}
+	onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx);
+}
+
+static int onyx_create(struct i2c_adapter *adapter,
+		       struct device_node *node,
+		       int addr)
+{
+	struct i2c_board_info info;
+	struct i2c_client *client;
+
+	memset(&info, 0, sizeof(struct i2c_board_info));
+	strlcpy(info.type, "aoa_codec_onyx", I2C_NAME_SIZE);
+	info.addr = addr;
+	info.platform_data = node;
+	client = i2c_new_device(adapter, &info);
+	if (!client)
+		return -ENODEV;
+
+	/*
+	 * We know the driver is already loaded, so the device should be
+	 * already bound. If not it means binding failed, which suggests
+	 * the device doesn't really exist and should be deleted.
+	 * Ideally this would be replaced by better checks _before_
+	 * instantiating the device.
+	 */
+	if (!client->driver) {
+		i2c_unregister_device(client);
+		return -ENODEV;
+	}
+
+	/*
+	 * Let i2c-core delete that device on driver removal.
+	 * This is safe because i2c-core holds the core_lock mutex for us.
+	 */
+	list_add_tail(&client->detected, &client->driver->clients);
+	return 0;
+}
+
+static int onyx_i2c_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct device_node *node = client->dev.platform_data;
+	struct onyx *onyx;
+	u8 dummy;
+
+	onyx = kzalloc(sizeof(struct onyx), GFP_KERNEL);
+
+	if (!onyx)
+		return -ENOMEM;
+
+	mutex_init(&onyx->mutex);
+	onyx->i2c = client;
+	i2c_set_clientdata(client, onyx);
+
+	/* we try to read from register ONYX_REG_CONTROL
+	 * to check if the codec is present */
+	if (onyx_read_register(onyx, ONYX_REG_CONTROL, &dummy) != 0) {
+		printk(KERN_ERR PFX "failed to read control register\n");
+		goto fail;
+	}
+
+	strlcpy(onyx->codec.name, "onyx", MAX_CODEC_NAME_LEN);
+	onyx->codec.owner = THIS_MODULE;
+	onyx->codec.init = onyx_init_codec;
+	onyx->codec.exit = onyx_exit_codec;
+	onyx->codec.node = of_node_get(node);
+
+	if (aoa_codec_register(&onyx->codec)) {
+		goto fail;
+	}
+	printk(KERN_DEBUG PFX "created and attached onyx instance\n");
+	return 0;
+ fail:
+	i2c_set_clientdata(client, NULL);
+	kfree(onyx);
+	return -ENODEV;
+}
+
+static int onyx_i2c_attach(struct i2c_adapter *adapter)
+{
+	struct device_node *busnode, *dev = NULL;
+	struct pmac_i2c_bus *bus;
+
+	bus = pmac_i2c_adapter_to_bus(adapter);
+	if (bus == NULL)
+		return -ENODEV;
+	busnode = pmac_i2c_get_bus_node(bus);
+
+	while ((dev = of_get_next_child(busnode, dev)) != NULL) {
+		if (of_device_is_compatible(dev, "pcm3052")) {
+			const u32 *addr;
+			printk(KERN_DEBUG PFX "found pcm3052\n");
+			addr = of_get_property(dev, "reg", NULL);
+			if (!addr)
+				return -ENODEV;
+			return onyx_create(adapter, dev, (*addr)>>1);
+		}
+	}
+
+	/* if that didn't work, try desperate mode for older
+	 * machines that have stuff missing from the device tree */
+
+	if (!of_device_is_compatible(busnode, "k2-i2c"))
+		return -ENODEV;
+
+	printk(KERN_DEBUG PFX "found k2-i2c, checking if onyx chip is on it\n");
+	/* probe both possible addresses for the onyx chip */
+	if (onyx_create(adapter, NULL, 0x46) == 0)
+		return 0;
+	return onyx_create(adapter, NULL, 0x47);
+}
+
+static int onyx_i2c_remove(struct i2c_client *client)
+{
+	struct onyx *onyx = i2c_get_clientdata(client);
+
+	aoa_codec_unregister(&onyx->codec);
+	of_node_put(onyx->codec.node);
+	if (onyx->codec_info)
+		kfree(onyx->codec_info);
+	i2c_set_clientdata(client, onyx);
+	kfree(onyx);
+	return 0;
+}
+
+static const struct i2c_device_id onyx_i2c_id[] = {
+	{ "aoa_codec_onyx", 0 },
+	{ }
+};
+
+static struct i2c_driver onyx_driver = {
+	.driver = {
+		.name = "aoa_codec_onyx",
+		.owner = THIS_MODULE,
+	},
+	.attach_adapter = onyx_i2c_attach,
+	.probe = onyx_i2c_probe,
+	.remove = onyx_i2c_remove,
+	.id_table = onyx_i2c_id,
+};
+
+static int __init onyx_init(void)
+{
+	return i2c_add_driver(&onyx_driver);
+}
+
+static void __exit onyx_exit(void)
+{
+	i2c_del_driver(&onyx_driver);
+}
+
+module_init(onyx_init);
+module_exit(onyx_exit);
diff --git a/linux/sound/aoa/codecs/onyx.h b/linux/sound/aoa/codecs/onyx.h
new file mode 100644
index 0000000..ffd2025
--- /dev/null
+++ b/linux/sound/aoa/codecs/onyx.h
@@ -0,0 +1,75 @@
+/*
+ * Apple Onboard Audio driver for Onyx codec (header)
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+#ifndef __SND_AOA_CODEC_ONYX_H
+#define __SND_AOA_CODEC_ONYX_H
+#include <stddef.h>
+#include <linux/i2c.h>
+#include <asm/pmac_low_i2c.h>
+#include <asm/prom.h>
+
+/* PCM3052 register definitions */
+
+/* the attenuation registers take values from
+ * -1 (0dB) to -127 (-63.0 dB) or others (muted) */
+#define ONYX_REG_DAC_ATTEN_LEFT		65
+#define FIRSTREGISTER			ONYX_REG_DAC_ATTEN_LEFT
+#define ONYX_REG_DAC_ATTEN_RIGHT	66
+
+#define ONYX_REG_CONTROL		67
+#	define ONYX_MRST		(1<<7)
+#	define ONYX_SRST		(1<<6)
+#	define ONYX_ADPSV		(1<<5)
+#	define ONYX_DAPSV		(1<<4)
+#	define ONYX_SILICONVERSION	(1<<0)
+/* all others reserved */
+
+#define ONYX_REG_DAC_CONTROL		68
+#	define ONYX_OVR1		(1<<6)
+#	define ONYX_MUTE_RIGHT		(1<<1)
+#	define ONYX_MUTE_LEFT		(1<<0)
+
+#define ONYX_REG_DAC_DEEMPH		69
+#	define ONYX_DIGDEEMPH_SHIFT	5
+#	define ONYX_DIGDEEMPH_MASK	(3<<ONYX_DIGDEEMPH_SHIFT)
+#	define ONYX_DIGDEEMPH_CTRL	(1<<4)
+
+#define ONYX_REG_DAC_FILTER		70
+#	define ONYX_ROLLOFF_FAST	(1<<5)
+#	define ONYX_DAC_FILTER_ALWAYS	(1<<2)
+
+#define	ONYX_REG_DAC_OUTPHASE		71
+#	define ONYX_OUTPHASE_INVERTED	(1<<0)
+
+#define ONYX_REG_ADC_CONTROL		72
+#	define ONYX_ADC_INPUT_MIC	(1<<5)
+/* 8 + input gain in dB, valid range for input gain is -4 .. 20 dB */
+#	define ONYX_ADC_PGA_GAIN_MASK	0x1f
+
+#define ONYX_REG_ADC_HPF_BYPASS		75
+#	define ONYX_HPF_DISABLE		(1<<3)
+#	define ONYX_ADC_HPF_ALWAYS	(1<<2)
+
+#define ONYX_REG_DIG_INFO1		77
+#	define ONYX_MASK_DIN_TO_BPZ	(1<<7)
+/* bits 1-5 control channel bits 1-5 */
+#	define ONYX_DIGOUT_DISABLE	(1<<0)
+
+#define ONYX_REG_DIG_INFO2		78
+/* controls channel bits 8-15 */
+
+#define ONYX_REG_DIG_INFO3		79
+/* control channel bits 24-29, high 2 bits reserved */
+
+#define ONYX_REG_DIG_INFO4		80
+#	define ONYX_VALIDL		(1<<7)
+#	define ONYX_VALIDR		(1<<6)
+#	define ONYX_SPDIF_ENABLE	(1<<5)
+/* lower 4 bits control bits 32-35 of channel control and word length */
+#	define ONYX_WORDLEN_MASK	(0xF)
+
+#endif /* __SND_AOA_CODEC_ONYX_H */
diff --git a/linux/sound/aoa/codecs/tas-basstreble.h b/linux/sound/aoa/codecs/tas-basstreble.h
new file mode 100644
index 0000000..69b6113
--- /dev/null
+++ b/linux/sound/aoa/codecs/tas-basstreble.h
@@ -0,0 +1,134 @@
+/*
+ * This file is only included exactly once!
+ *
+ * The tables here are derived from the tas3004 datasheet,
+ * modulo typo corrections and some smoothing...
+ */
+
+#define TAS3004_TREBLE_MIN	0
+#define TAS3004_TREBLE_MAX	72
+#define TAS3004_BASS_MIN	0
+#define TAS3004_BASS_MAX	72
+#define TAS3004_TREBLE_ZERO	36
+#define TAS3004_BASS_ZERO	36
+
+static u8 tas3004_treble_table[] = {
+	150, /* -18 dB */
+	149,
+	148,
+	147,
+	146,
+	145,
+	144,
+	143,
+	142,
+	141,
+	140,
+	139,
+	138,
+	137,
+	136,
+	135,
+	134,
+	133,
+	132,
+	131,
+	130,
+	129,
+	128,
+	127,
+	126,
+	125,
+	124,
+	123,
+	122,
+	121,
+	120,
+	119,
+	118,
+	117,
+	116,
+	115,
+	114, /* 0 dB */
+	113,
+	112,
+	111,
+	109,
+	108,
+	107,
+	105,
+	104,
+	103,
+	101,
+	99,
+	98,
+	96,
+	93,
+	91,
+	89,
+	86,
+	83,
+	81,
+	77,
+	74,
+	71,
+	67,
+	63,
+	59,
+	54,
+	49,
+	44,
+	38,
+	32,
+	26,
+	19,
+	10,
+	4,
+	2,
+	1, /* +18 dB */
+};
+
+static inline u8 tas3004_treble(int idx)
+{
+	return tas3004_treble_table[idx];
+}
+
+/* I only save the difference here to the treble table
+ * so that the binary is smaller...
+ * I have also ignored completely differences of
+ * +/- 1
+ */
+static s8 tas3004_bass_diff_to_treble[] = {
+	2, /* 7 dB, offset 50 */
+	2,
+	2,
+	2,
+	2,
+	1,
+	2,
+	2,
+	2,
+	3,
+	4,
+	4,
+	5,
+	6,
+	7,
+	8,
+	9,
+	10,
+	11,
+	14,
+	13,
+	8,
+	1, /* 18 dB */
+};
+
+static inline u8 tas3004_bass(int idx)
+{
+	u8 result = tas3004_treble_table[idx];
+
+	if (idx >= 50)
+		result += tas3004_bass_diff_to_treble[idx-50];
+	return result;
+}
diff --git a/linux/sound/aoa/codecs/tas-gain-table.h b/linux/sound/aoa/codecs/tas-gain-table.h
new file mode 100644
index 0000000..4cfa675
--- /dev/null
+++ b/linux/sound/aoa/codecs/tas-gain-table.h
@@ -0,0 +1,209 @@
+/*
+ This is the program used to generate below table.
+
+#include <stdio.h>
+#include <math.h>
+int main() {
+  int dB2;
+  printf("/" "* This file is only included exactly once!\n");
+  printf(" *\n");
+  printf(" * If they'd only tell us that generating this table was\n");
+  printf(" * as easy as calculating\n");
+  printf(" *      hwvalue = 1048576.0*exp(0.057564628*dB*2)\n");
+  printf(" * :) *" "/\n");
+  printf("static int tas_gaintable[] = {\n");
+  printf("	0x000000, /" "* -infinity dB *" "/\n");
+  for (dB2=-140;dB2<=36;dB2++)
+    printf("	0x%.6x, /" "* %-02.1f dB *" "/\n", (int)(1048576.0*exp(0.057564628*dB2)), dB2/2.0);
+  printf("};\n\n");
+}
+
+*/
+
+/* This file is only included exactly once!
+ *
+ * If they'd only tell us that generating this table was
+ * as easy as calculating
+ *      hwvalue = 1048576.0*exp(0.057564628*dB*2)
+ * :) */
+static int tas_gaintable[] = {
+	0x000000, /* -infinity dB */
+	0x00014b, /* -70.0 dB */
+	0x00015f, /* -69.5 dB */
+	0x000174, /* -69.0 dB */
+	0x00018a, /* -68.5 dB */
+	0x0001a1, /* -68.0 dB */
+	0x0001ba, /* -67.5 dB */
+	0x0001d4, /* -67.0 dB */
+	0x0001f0, /* -66.5 dB */
+	0x00020d, /* -66.0 dB */
+	0x00022c, /* -65.5 dB */
+	0x00024d, /* -65.0 dB */
+	0x000270, /* -64.5 dB */
+	0x000295, /* -64.0 dB */
+	0x0002bc, /* -63.5 dB */
+	0x0002e6, /* -63.0 dB */
+	0x000312, /* -62.5 dB */
+	0x000340, /* -62.0 dB */
+	0x000372, /* -61.5 dB */
+	0x0003a6, /* -61.0 dB */
+	0x0003dd, /* -60.5 dB */
+	0x000418, /* -60.0 dB */
+	0x000456, /* -59.5 dB */
+	0x000498, /* -59.0 dB */
+	0x0004de, /* -58.5 dB */
+	0x000528, /* -58.0 dB */
+	0x000576, /* -57.5 dB */
+	0x0005c9, /* -57.0 dB */
+	0x000620, /* -56.5 dB */
+	0x00067d, /* -56.0 dB */
+	0x0006e0, /* -55.5 dB */
+	0x000748, /* -55.0 dB */
+	0x0007b7, /* -54.5 dB */
+	0x00082c, /* -54.0 dB */
+	0x0008a8, /* -53.5 dB */
+	0x00092b, /* -53.0 dB */
+	0x0009b6, /* -52.5 dB */
+	0x000a49, /* -52.0 dB */
+	0x000ae5, /* -51.5 dB */
+	0x000b8b, /* -51.0 dB */
+	0x000c3a, /* -50.5 dB */
+	0x000cf3, /* -50.0 dB */
+	0x000db8, /* -49.5 dB */
+	0x000e88, /* -49.0 dB */
+	0x000f64, /* -48.5 dB */
+	0x00104e, /* -48.0 dB */
+	0x001145, /* -47.5 dB */
+	0x00124b, /* -47.0 dB */
+	0x001361, /* -46.5 dB */
+	0x001487, /* -46.0 dB */
+	0x0015be, /* -45.5 dB */
+	0x001708, /* -45.0 dB */
+	0x001865, /* -44.5 dB */
+	0x0019d8, /* -44.0 dB */
+	0x001b60, /* -43.5 dB */
+	0x001cff, /* -43.0 dB */
+	0x001eb7, /* -42.5 dB */
+	0x002089, /* -42.0 dB */
+	0x002276, /* -41.5 dB */
+	0x002481, /* -41.0 dB */
+	0x0026ab, /* -40.5 dB */
+	0x0028f5, /* -40.0 dB */
+	0x002b63, /* -39.5 dB */
+	0x002df5, /* -39.0 dB */
+	0x0030ae, /* -38.5 dB */
+	0x003390, /* -38.0 dB */
+	0x00369e, /* -37.5 dB */
+	0x0039db, /* -37.0 dB */
+	0x003d49, /* -36.5 dB */
+	0x0040ea, /* -36.0 dB */
+	0x0044c3, /* -35.5 dB */
+	0x0048d6, /* -35.0 dB */
+	0x004d27, /* -34.5 dB */
+	0x0051b9, /* -34.0 dB */
+	0x005691, /* -33.5 dB */
+	0x005bb2, /* -33.0 dB */
+	0x006121, /* -32.5 dB */
+	0x0066e3, /* -32.0 dB */
+	0x006cfb, /* -31.5 dB */
+	0x007370, /* -31.0 dB */
+	0x007a48, /* -30.5 dB */
+	0x008186, /* -30.0 dB */
+	0x008933, /* -29.5 dB */
+	0x009154, /* -29.0 dB */
+	0x0099f1, /* -28.5 dB */
+	0x00a310, /* -28.0 dB */
+	0x00acba, /* -27.5 dB */
+	0x00b6f6, /* -27.0 dB */
+	0x00c1cd, /* -26.5 dB */
+	0x00cd49, /* -26.0 dB */
+	0x00d973, /* -25.5 dB */
+	0x00e655, /* -25.0 dB */
+	0x00f3fb, /* -24.5 dB */
+	0x010270, /* -24.0 dB */
+	0x0111c0, /* -23.5 dB */
+	0x0121f9, /* -23.0 dB */
+	0x013328, /* -22.5 dB */
+	0x01455b, /* -22.0 dB */
+	0x0158a2, /* -21.5 dB */
+	0x016d0e, /* -21.0 dB */
+	0x0182af, /* -20.5 dB */
+	0x019999, /* -20.0 dB */
+	0x01b1de, /* -19.5 dB */
+	0x01cb94, /* -19.0 dB */
+	0x01e6cf, /* -18.5 dB */
+	0x0203a7, /* -18.0 dB */
+	0x022235, /* -17.5 dB */
+	0x024293, /* -17.0 dB */
+	0x0264db, /* -16.5 dB */
+	0x02892c, /* -16.0 dB */
+	0x02afa3, /* -15.5 dB */
+	0x02d862, /* -15.0 dB */
+	0x03038a, /* -14.5 dB */
+	0x033142, /* -14.0 dB */
+	0x0361af, /* -13.5 dB */
+	0x0394fa, /* -13.0 dB */
+	0x03cb50, /* -12.5 dB */
+	0x0404de, /* -12.0 dB */
+	0x0441d5, /* -11.5 dB */
+	0x048268, /* -11.0 dB */
+	0x04c6d0, /* -10.5 dB */
+	0x050f44, /* -10.0 dB */
+	0x055c04, /* -9.5 dB */
+	0x05ad50, /* -9.0 dB */
+	0x06036e, /* -8.5 dB */
+	0x065ea5, /* -8.0 dB */
+	0x06bf44, /* -7.5 dB */
+	0x07259d, /* -7.0 dB */
+	0x079207, /* -6.5 dB */
+	0x0804dc, /* -6.0 dB */
+	0x087e80, /* -5.5 dB */
+	0x08ff59, /* -5.0 dB */
+	0x0987d5, /* -4.5 dB */
+	0x0a1866, /* -4.0 dB */
+	0x0ab189, /* -3.5 dB */
+	0x0b53be, /* -3.0 dB */
+	0x0bff91, /* -2.5 dB */
+	0x0cb591, /* -2.0 dB */
+	0x0d765a, /* -1.5 dB */
+	0x0e4290, /* -1.0 dB */
+	0x0f1adf, /* -0.5 dB */
+	0x100000, /* 0.0 dB */
+	0x10f2b4, /* 0.5 dB */
+	0x11f3c9, /* 1.0 dB */
+	0x13041a, /* 1.5 dB */
+	0x14248e, /* 2.0 dB */
+	0x15561a, /* 2.5 dB */
+	0x1699c0, /* 3.0 dB */
+	0x17f094, /* 3.5 dB */
+	0x195bb8, /* 4.0 dB */
+	0x1adc61, /* 4.5 dB */
+	0x1c73d5, /* 5.0 dB */
+	0x1e236d, /* 5.5 dB */
+	0x1fec98, /* 6.0 dB */
+	0x21d0d9, /* 6.5 dB */
+	0x23d1cd, /* 7.0 dB */
+	0x25f125, /* 7.5 dB */
+	0x2830af, /* 8.0 dB */
+	0x2a9254, /* 8.5 dB */
+	0x2d1818, /* 9.0 dB */
+	0x2fc420, /* 9.5 dB */
+	0x3298b0, /* 10.0 dB */
+	0x35982f, /* 10.5 dB */
+	0x38c528, /* 11.0 dB */
+	0x3c224c, /* 11.5 dB */
+	0x3fb278, /* 12.0 dB */
+	0x4378b0, /* 12.5 dB */
+	0x477829, /* 13.0 dB */
+	0x4bb446, /* 13.5 dB */
+	0x5030a1, /* 14.0 dB */
+	0x54f106, /* 14.5 dB */
+	0x59f980, /* 15.0 dB */
+	0x5f4e52, /* 15.5 dB */
+	0x64f403, /* 16.0 dB */
+	0x6aef5e, /* 16.5 dB */
+	0x714575, /* 17.0 dB */
+	0x77fbaa, /* 17.5 dB */
+	0x7f17af, /* 18.0 dB */
+};
+
diff --git a/linux/sound/aoa/codecs/tas.c b/linux/sound/aoa/codecs/tas.c
new file mode 100644
index 0000000..fd2188c
--- /dev/null
+++ b/linux/sound/aoa/codecs/tas.c
@@ -0,0 +1,1040 @@
+/*
+ * Apple Onboard Audio driver for tas codec
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ *
+ * Open questions:
+ *  - How to distinguish between 3004 and versions?
+ *
+ * FIXMEs:
+ *  - This codec driver doesn't honour the 'connected'
+ *    property of the aoa_codec struct, hence if
+ *    it is used in machines where not everything is
+ *    connected it will display wrong mixer elements.
+ *  - Driver assumes that the microphone is always
+ *    monaureal and connected to the right channel of
+ *    the input. This should also be a codec-dependent
+ *    flag, maybe the codec should have 3 different
+ *    bits for the three different possibilities how
+ *    it can be hooked up...
+ *    But as long as I don't see any hardware hooked
+ *    up that way...
+ *  - As Apple notes in their code, the tas3004 seems
+ *    to delay the right channel by one sample. You can
+ *    see this when for example recording stereo in
+ *    audacity, or recording the tas output via cable
+ *    on another machine (use a sinus generator or so).
+ *    I tried programming the BiQuads but couldn't
+ *    make the delay work, maybe someone can read the
+ *    datasheet and fix it. The relevant Apple comment
+ *    is in AppleTAS3004Audio.cpp lines 1637 ff. Note
+ *    that their comment describing how they program
+ *    the filters sucks...
+ *
+ * Other things:
+ *  - this should actually register *two* aoa_codec
+ *    structs since it has two inputs. Then it must
+ *    use the prepare callback to forbid running the
+ *    secondary output on a different clock.
+ *    Also, whatever bus knows how to do this must
+ *    provide two soundbus_dev devices and the fabric
+ *    must be able to link them correctly.
+ *
+ *    I don't even know if Apple ever uses the second
+ *    port on the tas3004 though, I don't think their
+ *    i2s controllers can even do it. OTOH, they all
+ *    derive the clocks from common clocks, so it
+ *    might just be possible. The framework allows the
+ *    codec to refine the transfer_info items in the
+ *    usable callback, so we can simply remove the
+ *    rates the second instance is not using when it
+ *    actually is in use.
+ *    Maybe we'll need to make the sound busses have
+ *    a 'clock group id' value so the codec can
+ *    determine if the two outputs can be driven at
+ *    the same time. But that is likely overkill, up
+ *    to the fabric to not link them up incorrectly,
+ *    and up to the hardware designer to not wire
+ *    them up in some weird unusable way.
+ */
+#include <stddef.h>
+#include <linux/i2c.h>
+#include <asm/pmac_low_i2c.h>
+#include <asm/prom.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("tas codec driver for snd-aoa");
+
+#include "tas.h"
+#include "tas-gain-table.h"
+#include "tas-basstreble.h"
+#include "../aoa.h"
+#include "../soundbus/soundbus.h"
+
+#define PFX "snd-aoa-codec-tas: "
+
+
+struct tas {
+	struct aoa_codec	codec;
+	struct i2c_client	*i2c;
+	u32			mute_l:1, mute_r:1 ,
+				controls_created:1 ,
+				drc_enabled:1,
+				hw_enabled:1;
+	u8			cached_volume_l, cached_volume_r;
+	u8			mixer_l[3], mixer_r[3];
+	u8			bass, treble;
+	u8			acr;
+	int			drc_range;
+	/* protects hardware access against concurrency from
+	 * userspace when hitting controls and during
+	 * codec init/suspend/resume */
+	struct mutex		mtx;
+};
+
+static int tas_reset_init(struct tas *tas);
+
+static struct tas *codec_to_tas(struct aoa_codec *codec)
+{
+	return container_of(codec, struct tas, codec);
+}
+
+static inline int tas_write_reg(struct tas *tas, u8 reg, u8 len, u8 *data)
+{
+	if (len == 1)
+		return i2c_smbus_write_byte_data(tas->i2c, reg, *data);
+	else
+		return i2c_smbus_write_i2c_block_data(tas->i2c, reg, len, data);
+}
+
+static void tas3004_set_drc(struct tas *tas)
+{
+	unsigned char val[6];
+
+	if (tas->drc_enabled)
+		val[0] = 0x50; /* 3:1 above threshold */
+	else
+		val[0] = 0x51; /* disabled */
+	val[1] = 0x02; /* 1:1 below threshold */
+	if (tas->drc_range > 0xef)
+		val[2] = 0xef;
+	else if (tas->drc_range < 0)
+		val[2] = 0x00;
+	else
+		val[2] = tas->drc_range;
+	val[3] = 0xb0;
+	val[4] = 0x60;
+	val[5] = 0xa0;
+
+	tas_write_reg(tas, TAS_REG_DRC, 6, val);
+}
+
+static void tas_set_treble(struct tas *tas)
+{
+	u8 tmp;
+
+	tmp = tas3004_treble(tas->treble);
+	tas_write_reg(tas, TAS_REG_TREBLE, 1, &tmp);
+}
+
+static void tas_set_bass(struct tas *tas)
+{
+	u8 tmp;
+
+	tmp = tas3004_bass(tas->bass);
+	tas_write_reg(tas, TAS_REG_BASS, 1, &tmp);
+}
+
+static void tas_set_volume(struct tas *tas)
+{
+	u8 block[6];
+	int tmp;
+	u8 left, right;
+
+	left = tas->cached_volume_l;
+	right = tas->cached_volume_r;
+
+	if (left > 177) left = 177;
+	if (right > 177) right = 177;
+
+	if (tas->mute_l) left = 0;
+	if (tas->mute_r) right = 0;
+
+	/* analysing the volume and mixer tables shows
+	 * that they are similar enough when we shift
+	 * the mixer table down by 4 bits. The error
+	 * is miniscule, in just one item the error
+	 * is 1, at a value of 0x07f17b (mixer table
+	 * value is 0x07f17a) */
+	tmp = tas_gaintable[left];
+	block[0] = tmp>>20;
+	block[1] = tmp>>12;
+	block[2] = tmp>>4;
+	tmp = tas_gaintable[right];
+	block[3] = tmp>>20;
+	block[4] = tmp>>12;
+	block[5] = tmp>>4;
+	tas_write_reg(tas, TAS_REG_VOL, 6, block);
+}
+
+static void tas_set_mixer(struct tas *tas)
+{
+	u8 block[9];
+	int tmp, i;
+	u8 val;
+
+	for (i=0;i<3;i++) {
+		val = tas->mixer_l[i];
+		if (val > 177) val = 177;
+		tmp = tas_gaintable[val];
+		block[3*i+0] = tmp>>16;
+		block[3*i+1] = tmp>>8;
+		block[3*i+2] = tmp;
+	}
+	tas_write_reg(tas, TAS_REG_LMIX, 9, block);
+
+	for (i=0;i<3;i++) {
+		val = tas->mixer_r[i];
+		if (val > 177) val = 177;
+		tmp = tas_gaintable[val];
+		block[3*i+0] = tmp>>16;
+		block[3*i+1] = tmp>>8;
+		block[3*i+2] = tmp;
+	}
+	tas_write_reg(tas, TAS_REG_RMIX, 9, block);
+}
+
+/* alsa stuff */
+
+static int tas_dev_register(struct snd_device *dev)
+{
+	return 0;
+}
+
+static struct snd_device_ops ops = {
+	.dev_register = tas_dev_register,
+};
+
+static int tas_snd_vol_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 177;
+	return 0;
+}
+
+static int tas_snd_vol_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&tas->mtx);
+	ucontrol->value.integer.value[0] = tas->cached_volume_l;
+	ucontrol->value.integer.value[1] = tas->cached_volume_r;
+	mutex_unlock(&tas->mtx);
+	return 0;
+}
+
+static int tas_snd_vol_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	if (ucontrol->value.integer.value[0] < 0 ||
+	    ucontrol->value.integer.value[0] > 177)
+		return -EINVAL;
+	if (ucontrol->value.integer.value[1] < 0 ||
+	    ucontrol->value.integer.value[1] > 177)
+		return -EINVAL;
+
+	mutex_lock(&tas->mtx);
+	if (tas->cached_volume_l == ucontrol->value.integer.value[0]
+	 && tas->cached_volume_r == ucontrol->value.integer.value[1]) {
+		mutex_unlock(&tas->mtx);
+		return 0;
+	}
+
+	tas->cached_volume_l = ucontrol->value.integer.value[0];
+	tas->cached_volume_r = ucontrol->value.integer.value[1];
+	if (tas->hw_enabled)
+		tas_set_volume(tas);
+	mutex_unlock(&tas->mtx);
+	return 1;
+}
+
+static struct snd_kcontrol_new volume_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Master Playback Volume",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = tas_snd_vol_info,
+	.get = tas_snd_vol_get,
+	.put = tas_snd_vol_put,
+};
+
+#define tas_snd_mute_info	snd_ctl_boolean_stereo_info
+
+static int tas_snd_mute_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&tas->mtx);
+	ucontrol->value.integer.value[0] = !tas->mute_l;
+	ucontrol->value.integer.value[1] = !tas->mute_r;
+	mutex_unlock(&tas->mtx);
+	return 0;
+}
+
+static int tas_snd_mute_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&tas->mtx);
+	if (tas->mute_l == !ucontrol->value.integer.value[0]
+	 && tas->mute_r == !ucontrol->value.integer.value[1]) {
+		mutex_unlock(&tas->mtx);
+		return 0;
+	}
+
+	tas->mute_l = !ucontrol->value.integer.value[0];
+	tas->mute_r = !ucontrol->value.integer.value[1];
+	if (tas->hw_enabled)
+		tas_set_volume(tas);
+	mutex_unlock(&tas->mtx);
+	return 1;
+}
+
+static struct snd_kcontrol_new mute_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Master Playback Switch",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = tas_snd_mute_info,
+	.get = tas_snd_mute_get,
+	.put = tas_snd_mute_put,
+};
+
+static int tas_snd_mixer_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 177;
+	return 0;
+}
+
+static int tas_snd_mixer_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+	int idx = kcontrol->private_value;
+
+	mutex_lock(&tas->mtx);
+	ucontrol->value.integer.value[0] = tas->mixer_l[idx];
+	ucontrol->value.integer.value[1] = tas->mixer_r[idx];
+	mutex_unlock(&tas->mtx);
+
+	return 0;
+}
+
+static int tas_snd_mixer_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+	int idx = kcontrol->private_value;
+
+	mutex_lock(&tas->mtx);
+	if (tas->mixer_l[idx] == ucontrol->value.integer.value[0]
+	 && tas->mixer_r[idx] == ucontrol->value.integer.value[1]) {
+		mutex_unlock(&tas->mtx);
+		return 0;
+	}
+
+	tas->mixer_l[idx] = ucontrol->value.integer.value[0];
+	tas->mixer_r[idx] = ucontrol->value.integer.value[1];
+
+	if (tas->hw_enabled)
+		tas_set_mixer(tas);
+	mutex_unlock(&tas->mtx);
+	return 1;
+}
+
+#define MIXER_CONTROL(n,descr,idx)			\
+static struct snd_kcontrol_new n##_control = {		\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,		\
+	.name = descr " Playback Volume",		\
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,	\
+	.info = tas_snd_mixer_info,			\
+	.get = tas_snd_mixer_get,			\
+	.put = tas_snd_mixer_put,			\
+	.private_value = idx,				\
+}
+
+MIXER_CONTROL(pcm1, "PCM", 0);
+MIXER_CONTROL(monitor, "Monitor", 2);
+
+static int tas_snd_drc_range_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = TAS3004_DRC_MAX;
+	return 0;
+}
+
+static int tas_snd_drc_range_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&tas->mtx);
+	ucontrol->value.integer.value[0] = tas->drc_range;
+	mutex_unlock(&tas->mtx);
+	return 0;
+}
+
+static int tas_snd_drc_range_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	if (ucontrol->value.integer.value[0] < 0 ||
+	    ucontrol->value.integer.value[0] > TAS3004_DRC_MAX)
+		return -EINVAL;
+
+	mutex_lock(&tas->mtx);
+	if (tas->drc_range == ucontrol->value.integer.value[0]) {
+		mutex_unlock(&tas->mtx);
+		return 0;
+	}
+
+	tas->drc_range = ucontrol->value.integer.value[0];
+	if (tas->hw_enabled)
+		tas3004_set_drc(tas);
+	mutex_unlock(&tas->mtx);
+	return 1;
+}
+
+static struct snd_kcontrol_new drc_range_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DRC Range",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = tas_snd_drc_range_info,
+	.get = tas_snd_drc_range_get,
+	.put = tas_snd_drc_range_put,
+};
+
+#define tas_snd_drc_switch_info		snd_ctl_boolean_mono_info
+
+static int tas_snd_drc_switch_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&tas->mtx);
+	ucontrol->value.integer.value[0] = tas->drc_enabled;
+	mutex_unlock(&tas->mtx);
+	return 0;
+}
+
+static int tas_snd_drc_switch_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&tas->mtx);
+	if (tas->drc_enabled == ucontrol->value.integer.value[0]) {
+		mutex_unlock(&tas->mtx);
+		return 0;
+	}
+
+	tas->drc_enabled = !!ucontrol->value.integer.value[0];
+	if (tas->hw_enabled)
+		tas3004_set_drc(tas);
+	mutex_unlock(&tas->mtx);
+	return 1;
+}
+
+static struct snd_kcontrol_new drc_switch_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DRC Range Switch",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = tas_snd_drc_switch_info,
+	.get = tas_snd_drc_switch_get,
+	.put = tas_snd_drc_switch_put,
+};
+
+static int tas_snd_capture_source_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "Line-In", "Microphone" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int tas_snd_capture_source_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&tas->mtx);
+	ucontrol->value.enumerated.item[0] = !!(tas->acr & TAS_ACR_INPUT_B);
+	mutex_unlock(&tas->mtx);
+	return 0;
+}
+
+static int tas_snd_capture_source_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+	int oldacr;
+
+	if (ucontrol->value.enumerated.item[0] > 1)
+		return -EINVAL;
+	mutex_lock(&tas->mtx);
+	oldacr = tas->acr;
+
+	/*
+	 * Despite what the data sheet says in one place, the
+	 * TAS_ACR_B_MONAUREAL bit forces mono output even when
+	 * input A (line in) is selected.
+	 */
+	tas->acr &= ~(TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL);
+	if (ucontrol->value.enumerated.item[0])
+		tas->acr |= TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL |
+		      TAS_ACR_B_MON_SEL_RIGHT;
+	if (oldacr == tas->acr) {
+		mutex_unlock(&tas->mtx);
+		return 0;
+	}
+	if (tas->hw_enabled)
+		tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
+	mutex_unlock(&tas->mtx);
+	return 1;
+}
+
+static struct snd_kcontrol_new capture_source_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	/* If we name this 'Input Source', it properly shows up in
+	 * alsamixer as a selection, * but it's shown under the
+	 * 'Playback' category.
+	 * If I name it 'Capture Source', it shows up in strange
+	 * ways (two bools of which one can be selected at a
+	 * time) but at least it's shown in the 'Capture'
+	 * category.
+	 * I was told that this was due to backward compatibility,
+	 * but I don't understand then why the mangling is *not*
+	 * done when I name it "Input Source".....
+	 */
+	.name = "Capture Source",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = tas_snd_capture_source_info,
+	.get = tas_snd_capture_source_get,
+	.put = tas_snd_capture_source_put,
+};
+
+static int tas_snd_treble_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = TAS3004_TREBLE_MIN;
+	uinfo->value.integer.max = TAS3004_TREBLE_MAX;
+	return 0;
+}
+
+static int tas_snd_treble_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&tas->mtx);
+	ucontrol->value.integer.value[0] = tas->treble;
+	mutex_unlock(&tas->mtx);
+	return 0;
+}
+
+static int tas_snd_treble_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	if (ucontrol->value.integer.value[0] < TAS3004_TREBLE_MIN ||
+	    ucontrol->value.integer.value[0] > TAS3004_TREBLE_MAX)
+		return -EINVAL;
+	mutex_lock(&tas->mtx);
+	if (tas->treble == ucontrol->value.integer.value[0]) {
+		mutex_unlock(&tas->mtx);
+		return 0;
+	}
+
+	tas->treble = ucontrol->value.integer.value[0];
+	if (tas->hw_enabled)
+		tas_set_treble(tas);
+	mutex_unlock(&tas->mtx);
+	return 1;
+}
+
+static struct snd_kcontrol_new treble_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Treble",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = tas_snd_treble_info,
+	.get = tas_snd_treble_get,
+	.put = tas_snd_treble_put,
+};
+
+static int tas_snd_bass_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = TAS3004_BASS_MIN;
+	uinfo->value.integer.max = TAS3004_BASS_MAX;
+	return 0;
+}
+
+static int tas_snd_bass_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&tas->mtx);
+	ucontrol->value.integer.value[0] = tas->bass;
+	mutex_unlock(&tas->mtx);
+	return 0;
+}
+
+static int tas_snd_bass_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct tas *tas = snd_kcontrol_chip(kcontrol);
+
+	if (ucontrol->value.integer.value[0] < TAS3004_BASS_MIN ||
+	    ucontrol->value.integer.value[0] > TAS3004_BASS_MAX)
+		return -EINVAL;
+	mutex_lock(&tas->mtx);
+	if (tas->bass == ucontrol->value.integer.value[0]) {
+		mutex_unlock(&tas->mtx);
+		return 0;
+	}
+
+	tas->bass = ucontrol->value.integer.value[0];
+	if (tas->hw_enabled)
+		tas_set_bass(tas);
+	mutex_unlock(&tas->mtx);
+	return 1;
+}
+
+static struct snd_kcontrol_new bass_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Bass",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = tas_snd_bass_info,
+	.get = tas_snd_bass_get,
+	.put = tas_snd_bass_put,
+};
+
+static struct transfer_info tas_transfers[] = {
+	{
+		/* input */
+		.formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE,
+		.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+		.transfer_in = 1,
+	},
+	{
+		/* output */
+		.formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE,
+		.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+		.transfer_in = 0,
+	},
+	{}
+};
+
+static int tas_usable(struct codec_info_item *cii,
+		      struct transfer_info *ti,
+		      struct transfer_info *out)
+{
+	return 1;
+}
+
+static int tas_reset_init(struct tas *tas)
+{
+	u8 tmp;
+
+	tas->codec.gpio->methods->all_amps_off(tas->codec.gpio);
+	msleep(5);
+	tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0);
+	msleep(5);
+	tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 1);
+	msleep(20);
+	tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0);
+	msleep(10);
+	tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio);
+
+	tmp = TAS_MCS_SCLK64 | TAS_MCS_SPORT_MODE_I2S | TAS_MCS_SPORT_WL_24BIT;
+	if (tas_write_reg(tas, TAS_REG_MCS, 1, &tmp))
+		goto outerr;
+
+	tas->acr |= TAS_ACR_ANALOG_PDOWN;
+	if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
+		goto outerr;
+
+	tmp = 0;
+	if (tas_write_reg(tas, TAS_REG_MCS2, 1, &tmp))
+		goto outerr;
+
+	tas3004_set_drc(tas);
+
+	/* Set treble & bass to 0dB */
+	tas->treble = TAS3004_TREBLE_ZERO;
+	tas->bass = TAS3004_BASS_ZERO;
+	tas_set_treble(tas);
+	tas_set_bass(tas);
+
+	tas->acr &= ~TAS_ACR_ANALOG_PDOWN;
+	if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr))
+		goto outerr;
+
+	return 0;
+ outerr:
+	return -ENODEV;
+}
+
+static int tas_switch_clock(struct codec_info_item *cii, enum clock_switch clock)
+{
+	struct tas *tas = cii->codec_data;
+
+	switch(clock) {
+	case CLOCK_SWITCH_PREPARE_SLAVE:
+		/* Clocks are going away, mute mute mute */
+		tas->codec.gpio->methods->all_amps_off(tas->codec.gpio);
+		tas->hw_enabled = 0;
+		break;
+	case CLOCK_SWITCH_SLAVE:
+		/* Clocks are back, re-init the codec */
+		mutex_lock(&tas->mtx);
+		tas_reset_init(tas);
+		tas_set_volume(tas);
+		tas_set_mixer(tas);
+		tas->hw_enabled = 1;
+		tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio);
+		mutex_unlock(&tas->mtx);
+		break;
+	default:
+		/* doesn't happen as of now */
+		return -EINVAL;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/* we are controlled via i2c and assume that is always up
+ * If that wasn't the case, we'd have to suspend once
+ * our i2c device is suspended, and then take note of that! */
+static int tas_suspend(struct tas *tas)
+{
+	mutex_lock(&tas->mtx);
+	tas->hw_enabled = 0;
+	tas->acr |= TAS_ACR_ANALOG_PDOWN;
+	tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr);
+	mutex_unlock(&tas->mtx);
+	return 0;
+}
+
+static int tas_resume(struct tas *tas)
+{
+	/* reset codec */
+	mutex_lock(&tas->mtx);
+	tas_reset_init(tas);
+	tas_set_volume(tas);
+	tas_set_mixer(tas);
+	tas->hw_enabled = 1;
+	mutex_unlock(&tas->mtx);
+	return 0;
+}
+
+static int _tas_suspend(struct codec_info_item *cii, pm_message_t state)
+{
+	return tas_suspend(cii->codec_data);
+}
+
+static int _tas_resume(struct codec_info_item *cii)
+{
+	return tas_resume(cii->codec_data);
+}
+#else /* CONFIG_PM */
+#define _tas_suspend	NULL
+#define _tas_resume	NULL
+#endif /* CONFIG_PM */
+
+static struct codec_info tas_codec_info = {
+	.transfers = tas_transfers,
+	/* in theory, we can drive it at 512 too...
+	 * but so far the framework doesn't allow
+	 * for that and I don't see much point in it. */
+	.sysclock_factor = 256,
+	/* same here, could be 32 for just one 16 bit format */
+	.bus_factor = 64,
+	.owner = THIS_MODULE,
+	.usable = tas_usable,
+	.switch_clock = tas_switch_clock,
+	.suspend = _tas_suspend,
+	.resume = _tas_resume,
+};
+
+static int tas_init_codec(struct aoa_codec *codec)
+{
+	struct tas *tas = codec_to_tas(codec);
+	int err;
+
+	if (!tas->codec.gpio || !tas->codec.gpio->methods) {
+		printk(KERN_ERR PFX "gpios not assigned!!\n");
+		return -EINVAL;
+	}
+
+	mutex_lock(&tas->mtx);
+	if (tas_reset_init(tas)) {
+		printk(KERN_ERR PFX "tas failed to initialise\n");
+		mutex_unlock(&tas->mtx);
+		return -ENXIO;
+	}
+	tas->hw_enabled = 1;
+	mutex_unlock(&tas->mtx);
+
+	if (tas->codec.soundbus_dev->attach_codec(tas->codec.soundbus_dev,
+						   aoa_get_card(),
+						   &tas_codec_info, tas)) {
+		printk(KERN_ERR PFX "error attaching tas to soundbus\n");
+		return -ENODEV;
+	}
+
+	if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, tas, &ops)) {
+		printk(KERN_ERR PFX "failed to create tas snd device!\n");
+		return -ENODEV;
+	}
+	err = aoa_snd_ctl_add(snd_ctl_new1(&volume_control, tas));
+	if (err)
+		goto error;
+
+	err = aoa_snd_ctl_add(snd_ctl_new1(&mute_control, tas));
+	if (err)
+		goto error;
+
+	err = aoa_snd_ctl_add(snd_ctl_new1(&pcm1_control, tas));
+	if (err)
+		goto error;
+
+	err = aoa_snd_ctl_add(snd_ctl_new1(&monitor_control, tas));
+	if (err)
+		goto error;
+
+	err = aoa_snd_ctl_add(snd_ctl_new1(&capture_source_control, tas));
+	if (err)
+		goto error;
+
+	err = aoa_snd_ctl_add(snd_ctl_new1(&drc_range_control, tas));
+	if (err)
+		goto error;
+
+	err = aoa_snd_ctl_add(snd_ctl_new1(&drc_switch_control, tas));
+	if (err)
+		goto error;
+
+	err = aoa_snd_ctl_add(snd_ctl_new1(&treble_control, tas));
+	if (err)
+		goto error;
+
+	err = aoa_snd_ctl_add(snd_ctl_new1(&bass_control, tas));
+	if (err)
+		goto error;
+
+	return 0;
+ error:
+	tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas);
+	snd_device_free(aoa_get_card(), tas);
+	return err;
+}
+
+static void tas_exit_codec(struct aoa_codec *codec)
+{
+	struct tas *tas = codec_to_tas(codec);
+
+	if (!tas->codec.soundbus_dev)
+		return;
+	tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas);
+}
+
+
+static int tas_create(struct i2c_adapter *adapter,
+		       struct device_node *node,
+		       int addr)
+{
+	struct i2c_board_info info;
+	struct i2c_client *client;
+
+	memset(&info, 0, sizeof(struct i2c_board_info));
+	strlcpy(info.type, "aoa_codec_tas", I2C_NAME_SIZE);
+	info.addr = addr;
+	info.platform_data = node;
+
+	client = i2c_new_device(adapter, &info);
+	if (!client)
+		return -ENODEV;
+	/*
+	 * We know the driver is already loaded, so the device should be
+	 * already bound. If not it means binding failed, and then there
+	 * is no point in keeping the device instantiated.
+	 */
+	if (!client->driver) {
+		i2c_unregister_device(client);
+		return -ENODEV;
+	}
+
+	/*
+	 * Let i2c-core delete that device on driver removal.
+	 * This is safe because i2c-core holds the core_lock mutex for us.
+	 */
+	list_add_tail(&client->detected, &client->driver->clients);
+	return 0;
+}
+
+static int tas_i2c_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct device_node *node = client->dev.platform_data;
+	struct tas *tas;
+
+	tas = kzalloc(sizeof(struct tas), GFP_KERNEL);
+
+	if (!tas)
+		return -ENOMEM;
+
+	mutex_init(&tas->mtx);
+	tas->i2c = client;
+	i2c_set_clientdata(client, tas);
+
+	/* seems that half is a saner default */
+	tas->drc_range = TAS3004_DRC_MAX / 2;
+
+	strlcpy(tas->codec.name, "tas", MAX_CODEC_NAME_LEN);
+	tas->codec.owner = THIS_MODULE;
+	tas->codec.init = tas_init_codec;
+	tas->codec.exit = tas_exit_codec;
+	tas->codec.node = of_node_get(node);
+
+	if (aoa_codec_register(&tas->codec)) {
+		goto fail;
+	}
+	printk(KERN_DEBUG
+	       "snd-aoa-codec-tas: tas found, addr 0x%02x on %s\n",
+	       (unsigned int)client->addr, node->full_name);
+	return 0;
+ fail:
+	mutex_destroy(&tas->mtx);
+	kfree(tas);
+	return -EINVAL;
+}
+
+static int tas_i2c_attach(struct i2c_adapter *adapter)
+{
+	struct device_node *busnode, *dev = NULL;
+	struct pmac_i2c_bus *bus;
+
+	bus = pmac_i2c_adapter_to_bus(adapter);
+	if (bus == NULL)
+		return -ENODEV;
+	busnode = pmac_i2c_get_bus_node(bus);
+
+	while ((dev = of_get_next_child(busnode, dev)) != NULL) {
+		if (of_device_is_compatible(dev, "tas3004")) {
+			const u32 *addr;
+			printk(KERN_DEBUG PFX "found tas3004\n");
+			addr = of_get_property(dev, "reg", NULL);
+			if (!addr)
+				continue;
+			return tas_create(adapter, dev, ((*addr) >> 1) & 0x7f);
+		}
+		/* older machines have no 'codec' node with a 'compatible'
+		 * property that says 'tas3004', they just have a 'deq'
+		 * node without any such property... */
+		if (strcmp(dev->name, "deq") == 0) {
+			const u32 *_addr;
+			u32 addr;
+			printk(KERN_DEBUG PFX "found 'deq' node\n");
+			_addr = of_get_property(dev, "i2c-address", NULL);
+			if (!_addr)
+				continue;
+			addr = ((*_addr) >> 1) & 0x7f;
+			/* now, if the address doesn't match any of the two
+			 * that a tas3004 can have, we cannot handle this.
+			 * I doubt it ever happens but hey. */
+			if (addr != 0x34 && addr != 0x35)
+				continue;
+			return tas_create(adapter, dev, addr);
+		}
+	}
+	return -ENODEV;
+}
+
+static int tas_i2c_remove(struct i2c_client *client)
+{
+	struct tas *tas = i2c_get_clientdata(client);
+	u8 tmp = TAS_ACR_ANALOG_PDOWN;
+
+	aoa_codec_unregister(&tas->codec);
+	of_node_put(tas->codec.node);
+
+	/* power down codec chip */
+	tas_write_reg(tas, TAS_REG_ACR, 1, &tmp);
+
+	mutex_destroy(&tas->mtx);
+	kfree(tas);
+	return 0;
+}
+
+static const struct i2c_device_id tas_i2c_id[] = {
+	{ "aoa_codec_tas", 0 },
+	{ }
+};
+
+static struct i2c_driver tas_driver = {
+	.driver = {
+		.name = "aoa_codec_tas",
+		.owner = THIS_MODULE,
+	},
+	.attach_adapter = tas_i2c_attach,
+	.probe = tas_i2c_probe,
+	.remove = tas_i2c_remove,
+	.id_table = tas_i2c_id,
+};
+
+static int __init tas_init(void)
+{
+	return i2c_add_driver(&tas_driver);
+}
+
+static void __exit tas_exit(void)
+{
+	i2c_del_driver(&tas_driver);
+}
+
+module_init(tas_init);
+module_exit(tas_exit);
diff --git a/linux/sound/aoa/codecs/tas.h b/linux/sound/aoa/codecs/tas.h
new file mode 100644
index 0000000..ae177e3
--- /dev/null
+++ b/linux/sound/aoa/codecs/tas.h
@@ -0,0 +1,55 @@
+/*
+ * Apple Onboard Audio driver for tas codec (header)
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+#ifndef __SND_AOA_CODECTASH
+#define __SND_AOA_CODECTASH
+
+#define TAS_REG_MCS	0x01	/* main control */
+#	define TAS_MCS_FASTLOAD		(1<<7)
+#	define TAS_MCS_SCLK64		(1<<6)
+#	define TAS_MCS_SPORT_MODE_MASK	(3<<4)
+#	define TAS_MCS_SPORT_MODE_I2S	(2<<4)
+#	define TAS_MCS_SPORT_MODE_RJ	(1<<4)
+#	define TAS_MCS_SPORT_MODE_LJ	(0<<4)
+#	define TAS_MCS_SPORT_WL_MASK	(3<<0)
+#	define TAS_MCS_SPORT_WL_16BIT	(0<<0)
+#	define TAS_MCS_SPORT_WL_18BIT	(1<<0)
+#	define TAS_MCS_SPORT_WL_20BIT	(2<<0)
+#	define TAS_MCS_SPORT_WL_24BIT	(3<<0)
+
+#define TAS_REG_DRC	0x02
+#define TAS_REG_VOL	0x04
+#define TAS_REG_TREBLE	0x05
+#define TAS_REG_BASS	0x06
+#define TAS_REG_LMIX	0x07
+#define TAS_REG_RMIX	0x08
+
+#define TAS_REG_ACR	0x40	/* analog control */
+#	define TAS_ACR_B_MONAUREAL	(1<<7)
+#	define TAS_ACR_B_MON_SEL_RIGHT	(1<<6)
+#	define TAS_ACR_DEEMPH_MASK	(3<<2)
+#	define TAS_ACR_DEEMPH_OFF	(0<<2)
+#	define TAS_ACR_DEEMPH_48KHz	(1<<2)
+#	define TAS_ACR_DEEMPH_44KHz	(2<<2)
+#	define TAS_ACR_INPUT_B		(1<<1)
+#	define TAS_ACR_ANALOG_PDOWN	(1<<0)
+
+#define TAS_REG_MCS2	0x43	/* main control 2 */
+#	define TAS_MCS2_ALLPASS		(1<<1)
+
+#define TAS_REG_LEFT_BIQUAD6	0x10
+#define TAS_REG_RIGHT_BIQUAD6	0x19
+
+#define TAS_REG_LEFT_LOUDNESS		0x21
+#define TAS_REG_RIGHT_LOUDNESS		0x22
+#define TAS_REG_LEFT_LOUDNESS_GAIN	0x23
+#define TAS_REG_RIGHT_LOUDNESS_GAIN	0x24
+
+#define TAS3001_DRC_MAX		0x5f
+#define TAS3004_DRC_MAX		0xef
+
+#endif /* __SND_AOA_CODECTASH */
diff --git a/linux/sound/aoa/codecs/toonie.c b/linux/sound/aoa/codecs/toonie.c
new file mode 100644
index 0000000..69d2cb6
--- /dev/null
+++ b/linux/sound/aoa/codecs/toonie.c
@@ -0,0 +1,151 @@
+/*
+ * Apple Onboard Audio driver for Toonie codec
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ *
+ *
+ * This is a driver for the toonie codec chip. This chip is present
+ * on the Mac Mini and is nothing but a DAC.
+ */
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("toonie codec driver for snd-aoa");
+
+#include "../aoa.h"
+#include "../soundbus/soundbus.h"
+
+
+#define PFX "snd-aoa-codec-toonie: "
+
+struct toonie {
+	struct aoa_codec	codec;
+};
+#define codec_to_toonie(c) container_of(c, struct toonie, codec)
+
+static int toonie_dev_register(struct snd_device *dev)
+{
+	return 0;
+}
+
+static struct snd_device_ops ops = {
+	.dev_register = toonie_dev_register,
+};
+
+static struct transfer_info toonie_transfers[] = {
+	/* This thing *only* has analog output,
+	 * the rates are taken from Info.plist
+	 * from Darwin. */
+	{
+		.formats = SNDRV_PCM_FMTBIT_S16_BE |
+			   SNDRV_PCM_FMTBIT_S24_BE,
+		.rates = SNDRV_PCM_RATE_32000 |
+			 SNDRV_PCM_RATE_44100 |
+			 SNDRV_PCM_RATE_48000 |
+			 SNDRV_PCM_RATE_88200 |
+			 SNDRV_PCM_RATE_96000,
+	},
+	{}
+};
+
+static int toonie_usable(struct codec_info_item *cii,
+			 struct transfer_info *ti,
+			 struct transfer_info *out)
+{
+	return 1;
+}
+
+#ifdef CONFIG_PM
+static int toonie_suspend(struct codec_info_item *cii, pm_message_t state)
+{
+	/* can we turn it off somehow? */
+	return 0;
+}
+
+static int toonie_resume(struct codec_info_item *cii)
+{
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static struct codec_info toonie_codec_info = {
+	.transfers = toonie_transfers,
+	.sysclock_factor = 256,
+	.bus_factor = 64,
+	.owner = THIS_MODULE,
+	.usable = toonie_usable,
+#ifdef CONFIG_PM
+	.suspend = toonie_suspend,
+	.resume = toonie_resume,
+#endif
+};
+
+static int toonie_init_codec(struct aoa_codec *codec)
+{
+	struct toonie *toonie = codec_to_toonie(codec);
+
+	/* nothing connected? what a joke! */
+	if (toonie->codec.connected != 1)
+		return -ENOTCONN;
+
+	if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, toonie, &ops)) {
+		printk(KERN_ERR PFX "failed to create toonie snd device!\n");
+		return -ENODEV;
+	}
+
+	if (toonie->codec.soundbus_dev->attach_codec(toonie->codec.soundbus_dev,
+						     aoa_get_card(),
+						     &toonie_codec_info, toonie)) {
+		printk(KERN_ERR PFX "error creating toonie pcm\n");
+		snd_device_free(aoa_get_card(), toonie);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void toonie_exit_codec(struct aoa_codec *codec)
+{
+	struct toonie *toonie = codec_to_toonie(codec);
+
+	if (!toonie->codec.soundbus_dev) {
+		printk(KERN_ERR PFX "toonie_exit_codec called without soundbus_dev!\n");
+		return;
+	}
+	toonie->codec.soundbus_dev->detach_codec(toonie->codec.soundbus_dev, toonie);
+}
+
+static struct toonie *toonie;
+
+static int __init toonie_init(void)
+{
+	toonie = kzalloc(sizeof(struct toonie), GFP_KERNEL);
+
+	if (!toonie)
+		return -ENOMEM;
+
+	strlcpy(toonie->codec.name, "toonie", sizeof(toonie->codec.name));
+	toonie->codec.owner = THIS_MODULE;
+	toonie->codec.init = toonie_init_codec;
+	toonie->codec.exit = toonie_exit_codec;
+
+	if (aoa_codec_register(&toonie->codec)) {
+		kfree(toonie);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void __exit toonie_exit(void)
+{
+	aoa_codec_unregister(&toonie->codec);
+	kfree(toonie);
+}
+
+module_init(toonie_init);
+module_exit(toonie_exit);
diff --git a/linux/sound/aoa/core/Makefile b/linux/sound/aoa/core/Makefile
new file mode 100644
index 0000000..a1596e8
--- /dev/null
+++ b/linux/sound/aoa/core/Makefile
@@ -0,0 +1,5 @@
+obj-$(CONFIG_SND_AOA) += snd-aoa.o
+snd-aoa-objs := core.o \
+		alsa.o \
+		gpio-pmf.o \
+		gpio-feature.o
diff --git a/linux/sound/aoa/core/alsa.c b/linux/sound/aoa/core/alsa.c
new file mode 100644
index 0000000..0fa3855
--- /dev/null
+++ b/linux/sound/aoa/core/alsa.c
@@ -0,0 +1,100 @@
+/*
+ * Apple Onboard Audio Alsa helpers
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+#include <linux/module.h>
+#include "alsa.h"
+
+static int index = -1;
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "index for AOA sound card.");
+
+static struct aoa_card *aoa_card;
+
+int aoa_alsa_init(char *name, struct module *mod, struct device *dev)
+{
+	struct snd_card *alsa_card;
+	int err;
+
+	if (aoa_card)
+		/* cannot be EEXIST due to usage in aoa_fabric_register */
+		return -EBUSY;
+
+	err = snd_card_create(index, name, mod, sizeof(struct aoa_card),
+			      &alsa_card);
+	if (err < 0)
+		return err;
+	aoa_card = alsa_card->private_data;
+	aoa_card->alsa_card = alsa_card;
+	alsa_card->dev = dev;
+	strlcpy(alsa_card->driver, "AppleOnbdAudio", sizeof(alsa_card->driver));
+	strlcpy(alsa_card->shortname, name, sizeof(alsa_card->shortname));
+	strlcpy(alsa_card->longname, name, sizeof(alsa_card->longname));
+	strlcpy(alsa_card->mixername, name, sizeof(alsa_card->mixername));
+	err = snd_card_register(aoa_card->alsa_card);
+	if (err < 0) {
+		printk(KERN_ERR "snd-aoa: couldn't register alsa card\n");
+		snd_card_free(aoa_card->alsa_card);
+		aoa_card = NULL;
+		return err;
+	}
+	return 0;
+}
+
+struct snd_card *aoa_get_card(void)
+{
+	if (aoa_card)
+		return aoa_card->alsa_card;
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(aoa_get_card);
+
+void aoa_alsa_cleanup(void)
+{
+	if (aoa_card) {
+		snd_card_free(aoa_card->alsa_card);
+		aoa_card = NULL;
+	}
+}
+
+int aoa_snd_device_new(snd_device_type_t type,
+		       void * device_data, struct snd_device_ops * ops)
+{
+	struct snd_card *card = aoa_get_card();
+	int err;
+
+	if (!card) return -ENOMEM;
+
+	err = snd_device_new(card, type, device_data, ops);
+	if (err) {
+		printk(KERN_ERR "snd-aoa: failed to create snd device (%d)\n", err);
+		return err;
+	}
+	err = snd_device_register(card, device_data);
+	if (err) {
+		printk(KERN_ERR "snd-aoa: failed to register "
+				"snd device (%d)\n", err);
+		printk(KERN_ERR "snd-aoa: have you forgotten the "
+				"dev_register callback?\n");
+		snd_device_free(card, device_data);
+	}
+	return err;
+}
+EXPORT_SYMBOL_GPL(aoa_snd_device_new);
+
+int aoa_snd_ctl_add(struct snd_kcontrol* control)
+{
+	int err;
+
+	if (!aoa_card) return -ENODEV;
+
+	err = snd_ctl_add(aoa_card->alsa_card, control);
+	if (err)
+		printk(KERN_ERR "snd-aoa: failed to add alsa control (%d)\n",
+		       err);
+	return err;
+}
+EXPORT_SYMBOL_GPL(aoa_snd_ctl_add);
diff --git a/linux/sound/aoa/core/alsa.h b/linux/sound/aoa/core/alsa.h
new file mode 100644
index 0000000..9669e44
--- /dev/null
+++ b/linux/sound/aoa/core/alsa.h
@@ -0,0 +1,16 @@
+/*
+ * Apple Onboard Audio Alsa private helpers
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#ifndef __SND_AOA_ALSA_H
+#define __SND_AOA_ALSA_H
+#include "../aoa.h"
+
+extern int aoa_alsa_init(char *name, struct module *mod, struct device *dev);
+extern void aoa_alsa_cleanup(void);
+
+#endif /* __SND_AOA_ALSA_H */
diff --git a/linux/sound/aoa/core/core.c b/linux/sound/aoa/core/core.c
new file mode 100644
index 0000000..10bec6c
--- /dev/null
+++ b/linux/sound/aoa/core/core.c
@@ -0,0 +1,162 @@
+/*
+ * Apple Onboard Audio driver core
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include "../aoa.h"
+#include "alsa.h"
+
+MODULE_DESCRIPTION("Apple Onboard Audio Sound Driver");
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_LICENSE("GPL");
+
+/* We allow only one fabric. This simplifies things,
+ * and more don't really make that much sense */
+static struct aoa_fabric *fabric;
+static LIST_HEAD(codec_list);
+
+static int attach_codec_to_fabric(struct aoa_codec *c)
+{
+	int err;
+
+	if (!try_module_get(c->owner))
+		return -EBUSY;
+	/* found_codec has to be assigned */
+	err = -ENOENT;
+	if (fabric->found_codec)
+		err = fabric->found_codec(c);
+	if (err) {
+		module_put(c->owner);
+		printk(KERN_ERR "snd-aoa: fabric didn't like codec %s\n",
+				c->name);
+		return err;
+	}
+	c->fabric = fabric;
+
+	err = 0;
+	if (c->init)
+		err = c->init(c);
+	if (err) {
+		printk(KERN_ERR "snd-aoa: codec %s didn't init\n", c->name);
+		c->fabric = NULL;
+		if (fabric->remove_codec)
+			fabric->remove_codec(c);
+		module_put(c->owner);
+		return err;
+	}
+	if (fabric->attached_codec)
+		fabric->attached_codec(c);
+	return 0;
+}
+
+int aoa_codec_register(struct aoa_codec *codec)
+{
+	int err = 0;
+
+	/* if there's a fabric already, we can tell if we
+	 * will want to have this codec, so propagate error
+	 * through. Otherwise, this will happen later... */
+	if (fabric)
+		err = attach_codec_to_fabric(codec);
+	if (!err)
+		list_add(&codec->list, &codec_list);
+	return err;
+}
+EXPORT_SYMBOL_GPL(aoa_codec_register);
+
+void aoa_codec_unregister(struct aoa_codec *codec)
+{
+	list_del(&codec->list);
+	if (codec->fabric && codec->exit)
+		codec->exit(codec);
+	if (fabric && fabric->remove_codec)
+		fabric->remove_codec(codec);
+	codec->fabric = NULL;
+	module_put(codec->owner);
+}
+EXPORT_SYMBOL_GPL(aoa_codec_unregister);
+
+int aoa_fabric_register(struct aoa_fabric *new_fabric, struct device *dev)
+{
+	struct aoa_codec *c;
+	int err;
+
+	/* allow querying for presence of fabric
+	 * (i.e. do this test first!) */
+	if (new_fabric == fabric) {
+		err = -EALREADY;
+		goto attach;
+	}
+	if (fabric)
+		return -EEXIST;
+	if (!new_fabric)
+		return -EINVAL;
+
+	err = aoa_alsa_init(new_fabric->name, new_fabric->owner, dev);
+	if (err)
+		return err;
+
+	fabric = new_fabric;
+
+ attach:
+	list_for_each_entry(c, &codec_list, list) {
+		if (c->fabric != fabric)
+			attach_codec_to_fabric(c);
+	}
+	return err;
+}
+EXPORT_SYMBOL_GPL(aoa_fabric_register);
+
+void aoa_fabric_unregister(struct aoa_fabric *old_fabric)
+{
+	struct aoa_codec *c;
+
+	if (fabric != old_fabric)
+		return;
+
+	list_for_each_entry(c, &codec_list, list) {
+		if (c->fabric)
+			aoa_fabric_unlink_codec(c);
+	}
+
+	aoa_alsa_cleanup();
+
+	fabric = NULL;
+}
+EXPORT_SYMBOL_GPL(aoa_fabric_unregister);
+
+void aoa_fabric_unlink_codec(struct aoa_codec *codec)
+{
+	if (!codec->fabric) {
+		printk(KERN_ERR "snd-aoa: fabric unassigned "
+				"in aoa_fabric_unlink_codec\n");
+		dump_stack();
+		return;
+	}
+	if (codec->exit)
+		codec->exit(codec);
+	if (codec->fabric->remove_codec)
+		codec->fabric->remove_codec(codec);
+	codec->fabric = NULL;
+	module_put(codec->owner);
+}
+EXPORT_SYMBOL_GPL(aoa_fabric_unlink_codec);
+
+static int __init aoa_init(void)
+{
+	return 0;
+}
+
+static void __exit aoa_exit(void)
+{
+	aoa_alsa_cleanup();
+}
+
+module_init(aoa_init);
+module_exit(aoa_exit);
diff --git a/linux/sound/aoa/core/gpio-feature.c b/linux/sound/aoa/core/gpio-feature.c
new file mode 100644
index 0000000..de8e03a
--- /dev/null
+++ b/linux/sound/aoa/core/gpio-feature.c
@@ -0,0 +1,423 @@
+/*
+ * Apple Onboard Audio feature call GPIO control
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ *
+ * This file contains the GPIO control routines for
+ * direct (through feature calls) access to the GPIO
+ * registers.
+ */
+
+#include <asm/pmac_feature.h>
+#include <linux/interrupt.h>
+#include "../aoa.h"
+
+/* TODO: these are lots of global variables
+ * that aren't used on most machines...
+ * Move them into a dynamically allocated
+ * structure and use that.
+ */
+
+/* these are the GPIO numbers (register addresses as offsets into
+ * the GPIO space) */
+static int headphone_mute_gpio;
+static int master_mute_gpio;
+static int amp_mute_gpio;
+static int lineout_mute_gpio;
+static int hw_reset_gpio;
+static int lineout_detect_gpio;
+static int headphone_detect_gpio;
+static int linein_detect_gpio;
+
+/* see the SWITCH_GPIO macro */
+static int headphone_mute_gpio_activestate;
+static int master_mute_gpio_activestate;
+static int amp_mute_gpio_activestate;
+static int lineout_mute_gpio_activestate;
+static int hw_reset_gpio_activestate;
+static int lineout_detect_gpio_activestate;
+static int headphone_detect_gpio_activestate;
+static int linein_detect_gpio_activestate;
+
+/* node pointers that we save when getting the GPIO number
+ * to get the interrupt later */
+static struct device_node *lineout_detect_node;
+static struct device_node *linein_detect_node;
+static struct device_node *headphone_detect_node;
+
+static int lineout_detect_irq;
+static int linein_detect_irq;
+static int headphone_detect_irq;
+
+static struct device_node *get_gpio(char *name,
+				    char *altname,
+				    int *gpioptr,
+				    int *gpioactiveptr)
+{
+	struct device_node *np, *gpio;
+	const u32 *reg;
+	const char *audio_gpio;
+
+	*gpioptr = -1;
+
+	/* check if we can get it the easy way ... */
+	np = of_find_node_by_name(NULL, name);
+	if (!np) {
+		/* some machines have only gpioX/extint-gpioX nodes,
+		 * and an audio-gpio property saying what it is ...
+		 * So what we have to do is enumerate all children
+		 * of the gpio node and check them all. */
+		gpio = of_find_node_by_name(NULL, "gpio");
+		if (!gpio)
+			return NULL;
+		while ((np = of_get_next_child(gpio, np))) {
+			audio_gpio = of_get_property(np, "audio-gpio", NULL);
+			if (!audio_gpio)
+				continue;
+			if (strcmp(audio_gpio, name) == 0)
+				break;
+			if (altname && (strcmp(audio_gpio, altname) == 0))
+				break;
+		}
+		/* still not found, assume not there */
+		if (!np)
+			return NULL;
+	}
+
+	reg = of_get_property(np, "reg", NULL);
+	if (!reg)
+		return NULL;
+
+	*gpioptr = *reg;
+
+	/* this is a hack, usually the GPIOs 'reg' property
+	 * should have the offset based from the GPIO space
+	 * which is at 0x50, but apparently not always... */
+	if (*gpioptr < 0x50)
+		*gpioptr += 0x50;
+
+	reg = of_get_property(np, "audio-gpio-active-state", NULL);
+	if (!reg)
+		/* Apple seems to default to 1, but
+		 * that doesn't seem right at least on most
+		 * machines. So until proven that the opposite
+		 * is necessary, we default to 0
+		 * (which, incidentally, snd-powermac also does...) */
+		*gpioactiveptr = 0;
+	else
+		*gpioactiveptr = *reg;
+
+	return np;
+}
+
+static void get_irq(struct device_node * np, int *irqptr)
+{
+	if (np)
+		*irqptr = irq_of_parse_and_map(np, 0);
+	else
+		*irqptr = NO_IRQ;
+}
+
+/* 0x4 is outenable, 0x1 is out, thus 4 or 5 */
+#define SWITCH_GPIO(name, v, on)				\
+	(((v)&~1) | ((on)?					\
+			(name##_gpio_activestate==0?4:5):	\
+			(name##_gpio_activestate==0?5:4)))
+
+#define FTR_GPIO(name, bit)					\
+static void ftr_gpio_set_##name(struct gpio_runtime *rt, int on)\
+{								\
+	int v;							\
+								\
+	if (unlikely(!rt)) return;				\
+								\
+	if (name##_mute_gpio < 0)				\
+		return;						\
+								\
+	v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL,		\
+			      name##_mute_gpio,			\
+			      0);				\
+								\
+	/* muted = !on... */					\
+	v = SWITCH_GPIO(name##_mute, v, !on);			\
+								\
+	pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL,		\
+			  name##_mute_gpio, v);			\
+								\
+	rt->implementation_private &= ~(1<<bit);		\
+	rt->implementation_private |= (!!on << bit);		\
+}								\
+static int ftr_gpio_get_##name(struct gpio_runtime *rt)		\
+{								\
+	if (unlikely(!rt)) return 0;				\
+	return (rt->implementation_private>>bit)&1;		\
+}
+
+FTR_GPIO(headphone, 0);
+FTR_GPIO(amp, 1);
+FTR_GPIO(lineout, 2);
+FTR_GPIO(master, 3);
+
+static void ftr_gpio_set_hw_reset(struct gpio_runtime *rt, int on)
+{
+	int v;
+
+	if (unlikely(!rt)) return;
+	if (hw_reset_gpio < 0)
+		return;
+
+	v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL,
+			      hw_reset_gpio, 0);
+	v = SWITCH_GPIO(hw_reset, v, on);
+	pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL,
+			  hw_reset_gpio, v);
+}
+
+static struct gpio_methods methods;
+
+static void ftr_gpio_all_amps_off(struct gpio_runtime *rt)
+{
+	int saved;
+
+	if (unlikely(!rt)) return;
+	saved = rt->implementation_private;
+	ftr_gpio_set_headphone(rt, 0);
+	ftr_gpio_set_amp(rt, 0);
+	ftr_gpio_set_lineout(rt, 0);
+	if (methods.set_master)
+		ftr_gpio_set_master(rt, 0);
+	rt->implementation_private = saved;
+}
+
+static void ftr_gpio_all_amps_restore(struct gpio_runtime *rt)
+{
+	int s;
+
+	if (unlikely(!rt)) return;
+	s = rt->implementation_private;
+	ftr_gpio_set_headphone(rt, (s>>0)&1);
+	ftr_gpio_set_amp(rt, (s>>1)&1);
+	ftr_gpio_set_lineout(rt, (s>>2)&1);
+	if (methods.set_master)
+		ftr_gpio_set_master(rt, (s>>3)&1);
+}
+
+static void ftr_handle_notify(struct work_struct *work)
+{
+	struct gpio_notification *notif =
+		container_of(work, struct gpio_notification, work.work);
+
+	mutex_lock(&notif->mutex);
+	if (notif->notify)
+		notif->notify(notif->data);
+	mutex_unlock(&notif->mutex);
+}
+
+static void gpio_enable_dual_edge(int gpio)
+{
+	int v;
+
+	if (gpio == -1)
+		return;
+	v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0);
+	v |= 0x80; /* enable dual edge */
+	pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio, v);
+}
+
+static void ftr_gpio_init(struct gpio_runtime *rt)
+{
+	get_gpio("headphone-mute", NULL,
+		 &headphone_mute_gpio,
+		 &headphone_mute_gpio_activestate);
+	get_gpio("amp-mute", NULL,
+		 &amp_mute_gpio,
+		 &amp_mute_gpio_activestate);
+	get_gpio("lineout-mute", NULL,
+		 &lineout_mute_gpio,
+		 &lineout_mute_gpio_activestate);
+	get_gpio("hw-reset", "audio-hw-reset",
+		 &hw_reset_gpio,
+		 &hw_reset_gpio_activestate);
+	if (get_gpio("master-mute", NULL,
+		     &master_mute_gpio,
+		     &master_mute_gpio_activestate)) {
+		methods.set_master = ftr_gpio_set_master;
+		methods.get_master = ftr_gpio_get_master;
+	}
+
+	headphone_detect_node = get_gpio("headphone-detect", NULL,
+					 &headphone_detect_gpio,
+					 &headphone_detect_gpio_activestate);
+	/* go Apple, and thanks for giving these different names
+	 * across the board... */
+	lineout_detect_node = get_gpio("lineout-detect", "line-output-detect",
+				       &lineout_detect_gpio,
+				       &lineout_detect_gpio_activestate);
+	linein_detect_node = get_gpio("linein-detect", "line-input-detect",
+				      &linein_detect_gpio,
+				      &linein_detect_gpio_activestate);
+
+	gpio_enable_dual_edge(headphone_detect_gpio);
+	gpio_enable_dual_edge(lineout_detect_gpio);
+	gpio_enable_dual_edge(linein_detect_gpio);
+
+	get_irq(headphone_detect_node, &headphone_detect_irq);
+	get_irq(lineout_detect_node, &lineout_detect_irq);
+	get_irq(linein_detect_node, &linein_detect_irq);
+
+	ftr_gpio_all_amps_off(rt);
+	rt->implementation_private = 0;
+	INIT_DELAYED_WORK(&rt->headphone_notify.work, ftr_handle_notify);
+	INIT_DELAYED_WORK(&rt->line_in_notify.work, ftr_handle_notify);
+	INIT_DELAYED_WORK(&rt->line_out_notify.work, ftr_handle_notify);
+	mutex_init(&rt->headphone_notify.mutex);
+	mutex_init(&rt->line_in_notify.mutex);
+	mutex_init(&rt->line_out_notify.mutex);
+}
+
+static void ftr_gpio_exit(struct gpio_runtime *rt)
+{
+	ftr_gpio_all_amps_off(rt);
+	rt->implementation_private = 0;
+	if (rt->headphone_notify.notify)
+		free_irq(headphone_detect_irq, &rt->headphone_notify);
+	if (rt->line_in_notify.gpio_private)
+		free_irq(linein_detect_irq, &rt->line_in_notify);
+	if (rt->line_out_notify.gpio_private)
+		free_irq(lineout_detect_irq, &rt->line_out_notify);
+	cancel_delayed_work(&rt->headphone_notify.work);
+	cancel_delayed_work(&rt->line_in_notify.work);
+	cancel_delayed_work(&rt->line_out_notify.work);
+	flush_scheduled_work();
+	mutex_destroy(&rt->headphone_notify.mutex);
+	mutex_destroy(&rt->line_in_notify.mutex);
+	mutex_destroy(&rt->line_out_notify.mutex);
+}
+
+static irqreturn_t ftr_handle_notify_irq(int xx, void *data)
+{
+	struct gpio_notification *notif = data;
+
+	schedule_delayed_work(&notif->work, 0);
+
+	return IRQ_HANDLED;
+}
+
+static int ftr_set_notify(struct gpio_runtime *rt,
+			  enum notify_type type,
+			  notify_func_t notify,
+			  void *data)
+{
+	struct gpio_notification *notif;
+	notify_func_t old;
+	int irq;
+	char *name;
+	int err = -EBUSY;
+
+	switch (type) {
+	case AOA_NOTIFY_HEADPHONE:
+		notif = &rt->headphone_notify;
+		name = "headphone-detect";
+		irq = headphone_detect_irq;
+		break;
+	case AOA_NOTIFY_LINE_IN:
+		notif = &rt->line_in_notify;
+		name = "linein-detect";
+		irq = linein_detect_irq;
+		break;
+	case AOA_NOTIFY_LINE_OUT:
+		notif = &rt->line_out_notify;
+		name = "lineout-detect";
+		irq = lineout_detect_irq;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (irq == NO_IRQ)
+		return -ENODEV;
+
+	mutex_lock(&notif->mutex);
+
+	old = notif->notify;
+
+	if (!old && !notify) {
+		err = 0;
+		goto out_unlock;
+	}
+
+	if (old && notify) {
+		if (old == notify && notif->data == data)
+			err = 0;
+		goto out_unlock;
+	}
+
+	if (old && !notify)
+		free_irq(irq, notif);
+
+	if (!old && notify) {
+		err = request_irq(irq, ftr_handle_notify_irq, 0, name, notif);
+		if (err)
+			goto out_unlock;
+	}
+
+	notif->notify = notify;
+	notif->data = data;
+
+	err = 0;
+ out_unlock:
+	mutex_unlock(&notif->mutex);
+	return err;
+}
+
+static int ftr_get_detect(struct gpio_runtime *rt,
+			  enum notify_type type)
+{
+	int gpio, ret, active;
+
+	switch (type) {
+	case AOA_NOTIFY_HEADPHONE:
+		gpio = headphone_detect_gpio;
+		active = headphone_detect_gpio_activestate;
+		break;
+	case AOA_NOTIFY_LINE_IN:
+		gpio = linein_detect_gpio;
+		active = linein_detect_gpio_activestate;
+		break;
+	case AOA_NOTIFY_LINE_OUT:
+		gpio = lineout_detect_gpio;
+		active = lineout_detect_gpio_activestate;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (gpio == -1)
+		return -ENODEV;
+
+	ret = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0);
+	if (ret < 0)
+		return ret;
+	return ((ret >> 1) & 1) == active;
+}
+
+static struct gpio_methods methods = {
+	.init			= ftr_gpio_init,
+	.exit			= ftr_gpio_exit,
+	.all_amps_off		= ftr_gpio_all_amps_off,
+	.all_amps_restore	= ftr_gpio_all_amps_restore,
+	.set_headphone		= ftr_gpio_set_headphone,
+	.set_speakers		= ftr_gpio_set_amp,
+	.set_lineout		= ftr_gpio_set_lineout,
+	.set_hw_reset		= ftr_gpio_set_hw_reset,
+	.get_headphone		= ftr_gpio_get_headphone,
+	.get_speakers		= ftr_gpio_get_amp,
+	.get_lineout		= ftr_gpio_get_lineout,
+	.set_notify		= ftr_set_notify,
+	.get_detect		= ftr_get_detect,
+};
+
+struct gpio_methods *ftr_gpio_methods = &methods;
+EXPORT_SYMBOL_GPL(ftr_gpio_methods);
diff --git a/linux/sound/aoa/core/gpio-pmf.c b/linux/sound/aoa/core/gpio-pmf.c
new file mode 100644
index 0000000..7e267c9
--- /dev/null
+++ b/linux/sound/aoa/core/gpio-pmf.c
@@ -0,0 +1,254 @@
+/*
+ * Apple Onboard Audio pmf GPIOs
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <linux/slab.h>
+#include <asm/pmac_feature.h>
+#include <asm/pmac_pfunc.h>
+#include "../aoa.h"
+
+#define PMF_GPIO(name, bit)					\
+static void pmf_gpio_set_##name(struct gpio_runtime *rt, int on)\
+{								\
+	struct pmf_args args = { .count = 1, .u[0].v = !on };	\
+	int rc;							\
+							\
+	if (unlikely(!rt)) return;				\
+	rc = pmf_call_function(rt->node, #name "-mute", &args);	\
+	if (rc && rc != -ENODEV)				\
+		printk(KERN_WARNING "pmf_gpio_set_" #name	\
+		" failed, rc: %d\n", rc);			\
+	rt->implementation_private &= ~(1<<bit);		\
+	rt->implementation_private |= (!!on << bit);		\
+}								\
+static int pmf_gpio_get_##name(struct gpio_runtime *rt)		\
+{								\
+	if (unlikely(!rt)) return 0;				\
+	return (rt->implementation_private>>bit)&1;		\
+}
+
+PMF_GPIO(headphone, 0);
+PMF_GPIO(amp, 1);
+PMF_GPIO(lineout, 2);
+
+static void pmf_gpio_set_hw_reset(struct gpio_runtime *rt, int on)
+{
+	struct pmf_args args = { .count = 1, .u[0].v = !!on };
+	int rc;
+
+	if (unlikely(!rt)) return;
+	rc = pmf_call_function(rt->node, "hw-reset", &args);
+	if (rc)
+		printk(KERN_WARNING "pmf_gpio_set_hw_reset"
+		       " failed, rc: %d\n", rc);
+}
+
+static void pmf_gpio_all_amps_off(struct gpio_runtime *rt)
+{
+	int saved;
+
+	if (unlikely(!rt)) return;
+	saved = rt->implementation_private;
+	pmf_gpio_set_headphone(rt, 0);
+	pmf_gpio_set_amp(rt, 0);
+	pmf_gpio_set_lineout(rt, 0);
+	rt->implementation_private = saved;
+}
+
+static void pmf_gpio_all_amps_restore(struct gpio_runtime *rt)
+{
+	int s;
+
+	if (unlikely(!rt)) return;
+	s = rt->implementation_private;
+	pmf_gpio_set_headphone(rt, (s>>0)&1);
+	pmf_gpio_set_amp(rt, (s>>1)&1);
+	pmf_gpio_set_lineout(rt, (s>>2)&1);
+}
+
+static void pmf_handle_notify(struct work_struct *work)
+{
+	struct gpio_notification *notif =
+		container_of(work, struct gpio_notification, work.work);
+
+	mutex_lock(&notif->mutex);
+	if (notif->notify)
+		notif->notify(notif->data);
+	mutex_unlock(&notif->mutex);
+}
+
+static void pmf_gpio_init(struct gpio_runtime *rt)
+{
+	pmf_gpio_all_amps_off(rt);
+	rt->implementation_private = 0;
+	INIT_DELAYED_WORK(&rt->headphone_notify.work, pmf_handle_notify);
+	INIT_DELAYED_WORK(&rt->line_in_notify.work, pmf_handle_notify);
+	INIT_DELAYED_WORK(&rt->line_out_notify.work, pmf_handle_notify);
+	mutex_init(&rt->headphone_notify.mutex);
+	mutex_init(&rt->line_in_notify.mutex);
+	mutex_init(&rt->line_out_notify.mutex);
+}
+
+static void pmf_gpio_exit(struct gpio_runtime *rt)
+{
+	pmf_gpio_all_amps_off(rt);
+	rt->implementation_private = 0;
+
+	if (rt->headphone_notify.gpio_private)
+		pmf_unregister_irq_client(rt->headphone_notify.gpio_private);
+	if (rt->line_in_notify.gpio_private)
+		pmf_unregister_irq_client(rt->line_in_notify.gpio_private);
+	if (rt->line_out_notify.gpio_private)
+		pmf_unregister_irq_client(rt->line_out_notify.gpio_private);
+
+	/* make sure no work is pending before freeing
+	 * all things */
+	cancel_delayed_work(&rt->headphone_notify.work);
+	cancel_delayed_work(&rt->line_in_notify.work);
+	cancel_delayed_work(&rt->line_out_notify.work);
+	flush_scheduled_work();
+
+	mutex_destroy(&rt->headphone_notify.mutex);
+	mutex_destroy(&rt->line_in_notify.mutex);
+	mutex_destroy(&rt->line_out_notify.mutex);
+
+	kfree(rt->headphone_notify.gpio_private);
+	kfree(rt->line_in_notify.gpio_private);
+	kfree(rt->line_out_notify.gpio_private);
+}
+
+static void pmf_handle_notify_irq(void *data)
+{
+	struct gpio_notification *notif = data;
+
+	schedule_delayed_work(&notif->work, 0);
+}
+
+static int pmf_set_notify(struct gpio_runtime *rt,
+			  enum notify_type type,
+			  notify_func_t notify,
+			  void *data)
+{
+	struct gpio_notification *notif;
+	notify_func_t old;
+	struct pmf_irq_client *irq_client;
+	char *name;
+	int err = -EBUSY;
+
+	switch (type) {
+	case AOA_NOTIFY_HEADPHONE:
+		notif = &rt->headphone_notify;
+		name = "headphone-detect";
+		break;
+	case AOA_NOTIFY_LINE_IN:
+		notif = &rt->line_in_notify;
+		name = "linein-detect";
+		break;
+	case AOA_NOTIFY_LINE_OUT:
+		notif = &rt->line_out_notify;
+		name = "lineout-detect";
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&notif->mutex);
+
+	old = notif->notify;
+
+	if (!old && !notify) {
+		err = 0;
+		goto out_unlock;
+	}
+
+	if (old && notify) {
+		if (old == notify && notif->data == data)
+			err = 0;
+		goto out_unlock;
+	}
+
+	if (old && !notify) {
+		irq_client = notif->gpio_private;
+		pmf_unregister_irq_client(irq_client);
+		kfree(irq_client);
+		notif->gpio_private = NULL;
+	}
+	if (!old && notify) {
+		irq_client = kzalloc(sizeof(struct pmf_irq_client),
+				     GFP_KERNEL);
+		if (!irq_client) {
+			err = -ENOMEM;
+			goto out_unlock;
+		}
+		irq_client->data = notif;
+		irq_client->handler = pmf_handle_notify_irq;
+		irq_client->owner = THIS_MODULE;
+		err = pmf_register_irq_client(rt->node,
+					      name,
+					      irq_client);
+		if (err) {
+			printk(KERN_ERR "snd-aoa: gpio layer failed to"
+					" register %s irq (%d)\n", name, err);
+			kfree(irq_client);
+			goto out_unlock;
+		}
+		notif->gpio_private = irq_client;
+	}
+	notif->notify = notify;
+	notif->data = data;
+
+	err = 0;
+ out_unlock:
+	mutex_unlock(&notif->mutex);
+	return err;
+}
+
+static int pmf_get_detect(struct gpio_runtime *rt,
+			  enum notify_type type)
+{
+	char *name;
+	int err = -EBUSY, ret;
+	struct pmf_args args = { .count = 1, .u[0].p = &ret };
+
+	switch (type) {
+	case AOA_NOTIFY_HEADPHONE:
+		name = "headphone-detect";
+		break;
+	case AOA_NOTIFY_LINE_IN:
+		name = "linein-detect";
+		break;
+	case AOA_NOTIFY_LINE_OUT:
+		name = "lineout-detect";
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	err = pmf_call_function(rt->node, name, &args);
+	if (err)
+		return err;
+	return ret;
+}
+
+static struct gpio_methods methods = {
+	.init			= pmf_gpio_init,
+	.exit			= pmf_gpio_exit,
+	.all_amps_off		= pmf_gpio_all_amps_off,
+	.all_amps_restore	= pmf_gpio_all_amps_restore,
+	.set_headphone		= pmf_gpio_set_headphone,
+	.set_speakers		= pmf_gpio_set_amp,
+	.set_lineout		= pmf_gpio_set_lineout,
+	.set_hw_reset		= pmf_gpio_set_hw_reset,
+	.get_headphone		= pmf_gpio_get_headphone,
+	.get_speakers		= pmf_gpio_get_amp,
+	.get_lineout		= pmf_gpio_get_lineout,
+	.set_notify		= pmf_set_notify,
+	.get_detect		= pmf_get_detect,
+};
+
+struct gpio_methods *pmf_gpio_methods = &methods;
+EXPORT_SYMBOL_GPL(pmf_gpio_methods);
diff --git a/linux/sound/aoa/fabrics/Kconfig b/linux/sound/aoa/fabrics/Kconfig
new file mode 100644
index 0000000..3ca475a
--- /dev/null
+++ b/linux/sound/aoa/fabrics/Kconfig
@@ -0,0 +1,11 @@
+config SND_AOA_FABRIC_LAYOUT
+	tristate "layout-id fabric"
+	select SND_AOA_SOUNDBUS
+	select SND_AOA_SOUNDBUS_I2S
+	---help---
+	This enables the layout-id fabric for the Apple Onboard
+	Audio driver, the module holding it all together
+	based on the device-tree's layout-id property.
+	
+	If you are unsure and have a later Apple machine,
+	compile it as a module.
diff --git a/linux/sound/aoa/fabrics/Makefile b/linux/sound/aoa/fabrics/Makefile
new file mode 100644
index 0000000..da37c10
--- /dev/null
+++ b/linux/sound/aoa/fabrics/Makefile
@@ -0,0 +1,3 @@
+snd-aoa-fabric-layout-objs += layout.o
+
+obj-$(CONFIG_SND_AOA_FABRIC_LAYOUT) += snd-aoa-fabric-layout.o
diff --git a/linux/sound/aoa/fabrics/layout.c b/linux/sound/aoa/fabrics/layout.c
new file mode 100644
index 0000000..3fd1a7e
--- /dev/null
+++ b/linux/sound/aoa/fabrics/layout.c
@@ -0,0 +1,1168 @@
+/*
+ * Apple Onboard Audio driver -- layout/machine id fabric
+ *
+ * Copyright 2006-2008 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ *
+ *
+ * This fabric module looks for sound codecs based on the
+ * layout-id or device-id property in the device tree.
+ */
+#include <asm/prom.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include "../aoa.h"
+#include "../soundbus/soundbus.h"
+
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Layout-ID fabric for snd-aoa");
+
+#define MAX_CODECS_PER_BUS	2
+
+/* These are the connections the layout fabric
+ * knows about. It doesn't really care about the
+ * input ones, but I thought I'd separate them
+ * to give them proper names. The thing is that
+ * Apple usually will distinguish the active output
+ * by GPIOs, while the active input is set directly
+ * on the codec. Hence we here tell the codec what
+ * we think is connected. This information is hard-
+ * coded below ... */
+#define CC_SPEAKERS	(1<<0)
+#define CC_HEADPHONE	(1<<1)
+#define CC_LINEOUT	(1<<2)
+#define CC_DIGITALOUT	(1<<3)
+#define CC_LINEIN	(1<<4)
+#define CC_MICROPHONE	(1<<5)
+#define CC_DIGITALIN	(1<<6)
+/* pretty bogus but users complain...
+ * This is a flag saying that the LINEOUT
+ * should be renamed to HEADPHONE.
+ * be careful with input detection! */
+#define CC_LINEOUT_LABELLED_HEADPHONE	(1<<7)
+
+struct codec_connection {
+	/* CC_ flags from above */
+	int connected;
+	/* codec dependent bit to be set in the aoa_codec.connected field.
+	 * This intentionally doesn't have any generic flags because the
+	 * fabric has to know the codec anyway and all codecs might have
+	 * different connectors */
+	int codec_bit;
+};
+
+struct codec_connect_info {
+	char *name;
+	struct codec_connection *connections;
+};
+
+#define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF	(1<<0)
+
+struct layout {
+	unsigned int layout_id, device_id;
+	struct codec_connect_info codecs[MAX_CODECS_PER_BUS];
+	int flags;
+
+	/* if busname is not assigned, we use 'Master' below,
+	 * so that our layout table doesn't need to be filled
+	 * too much.
+	 * We only assign these two if we expect to find more
+	 * than one soundbus, i.e. on those machines with
+	 * multiple layout-ids */
+	char *busname;
+	int pcmid;
+};
+
+MODULE_ALIAS("sound-layout-36");
+MODULE_ALIAS("sound-layout-41");
+MODULE_ALIAS("sound-layout-45");
+MODULE_ALIAS("sound-layout-47");
+MODULE_ALIAS("sound-layout-48");
+MODULE_ALIAS("sound-layout-49");
+MODULE_ALIAS("sound-layout-50");
+MODULE_ALIAS("sound-layout-51");
+MODULE_ALIAS("sound-layout-56");
+MODULE_ALIAS("sound-layout-57");
+MODULE_ALIAS("sound-layout-58");
+MODULE_ALIAS("sound-layout-60");
+MODULE_ALIAS("sound-layout-61");
+MODULE_ALIAS("sound-layout-62");
+MODULE_ALIAS("sound-layout-64");
+MODULE_ALIAS("sound-layout-65");
+MODULE_ALIAS("sound-layout-66");
+MODULE_ALIAS("sound-layout-67");
+MODULE_ALIAS("sound-layout-68");
+MODULE_ALIAS("sound-layout-69");
+MODULE_ALIAS("sound-layout-70");
+MODULE_ALIAS("sound-layout-72");
+MODULE_ALIAS("sound-layout-76");
+MODULE_ALIAS("sound-layout-80");
+MODULE_ALIAS("sound-layout-82");
+MODULE_ALIAS("sound-layout-84");
+MODULE_ALIAS("sound-layout-86");
+MODULE_ALIAS("sound-layout-90");
+MODULE_ALIAS("sound-layout-92");
+MODULE_ALIAS("sound-layout-94");
+MODULE_ALIAS("sound-layout-96");
+MODULE_ALIAS("sound-layout-98");
+MODULE_ALIAS("sound-layout-100");
+
+MODULE_ALIAS("aoa-device-id-14");
+MODULE_ALIAS("aoa-device-id-22");
+MODULE_ALIAS("aoa-device-id-35");
+
+/* onyx with all but microphone connected */
+static struct codec_connection onyx_connections_nomic[] = {
+	{
+		.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
+		.codec_bit = 0,
+	},
+	{
+		.connected = CC_DIGITALOUT,
+		.codec_bit = 1,
+	},
+	{
+		.connected = CC_LINEIN,
+		.codec_bit = 2,
+	},
+	{} /* terminate array by .connected == 0 */
+};
+
+/* onyx on machines without headphone */
+static struct codec_connection onyx_connections_noheadphones[] = {
+	{
+		.connected = CC_SPEAKERS | CC_LINEOUT |
+			     CC_LINEOUT_LABELLED_HEADPHONE,
+		.codec_bit = 0,
+	},
+	{
+		.connected = CC_DIGITALOUT,
+		.codec_bit = 1,
+	},
+	/* FIXME: are these correct? probably not for all the machines
+	 * below ... If not this will need separating. */
+	{
+		.connected = CC_LINEIN,
+		.codec_bit = 2,
+	},
+	{
+		.connected = CC_MICROPHONE,
+		.codec_bit = 3,
+	},
+	{} /* terminate array by .connected == 0 */
+};
+
+/* onyx on machines with real line-out */
+static struct codec_connection onyx_connections_reallineout[] = {
+	{
+		.connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE,
+		.codec_bit = 0,
+	},
+	{
+		.connected = CC_DIGITALOUT,
+		.codec_bit = 1,
+	},
+	{
+		.connected = CC_LINEIN,
+		.codec_bit = 2,
+	},
+	{} /* terminate array by .connected == 0 */
+};
+
+/* tas on machines without line out */
+static struct codec_connection tas_connections_nolineout[] = {
+	{
+		.connected = CC_SPEAKERS | CC_HEADPHONE,
+		.codec_bit = 0,
+	},
+	{
+		.connected = CC_LINEIN,
+		.codec_bit = 2,
+	},
+	{
+		.connected = CC_MICROPHONE,
+		.codec_bit = 3,
+	},
+	{} /* terminate array by .connected == 0 */
+};
+
+/* tas on machines with neither line out nor line in */
+static struct codec_connection tas_connections_noline[] = {
+	{
+		.connected = CC_SPEAKERS | CC_HEADPHONE,
+		.codec_bit = 0,
+	},
+	{
+		.connected = CC_MICROPHONE,
+		.codec_bit = 3,
+	},
+	{} /* terminate array by .connected == 0 */
+};
+
+/* tas on machines without microphone */
+static struct codec_connection tas_connections_nomic[] = {
+	{
+		.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
+		.codec_bit = 0,
+	},
+	{
+		.connected = CC_LINEIN,
+		.codec_bit = 2,
+	},
+	{} /* terminate array by .connected == 0 */
+};
+
+/* tas on machines with everything connected */
+static struct codec_connection tas_connections_all[] = {
+	{
+		.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
+		.codec_bit = 0,
+	},
+	{
+		.connected = CC_LINEIN,
+		.codec_bit = 2,
+	},
+	{
+		.connected = CC_MICROPHONE,
+		.codec_bit = 3,
+	},
+	{} /* terminate array by .connected == 0 */
+};
+
+static struct codec_connection toonie_connections[] = {
+	{
+		.connected = CC_SPEAKERS | CC_HEADPHONE,
+		.codec_bit = 0,
+	},
+	{} /* terminate array by .connected == 0 */
+};
+
+static struct codec_connection topaz_input[] = {
+	{
+		.connected = CC_DIGITALIN,
+		.codec_bit = 0,
+	},
+	{} /* terminate array by .connected == 0 */
+};
+
+static struct codec_connection topaz_output[] = {
+	{
+		.connected = CC_DIGITALOUT,
+		.codec_bit = 1,
+	},
+	{} /* terminate array by .connected == 0 */
+};
+
+static struct codec_connection topaz_inout[] = {
+	{
+		.connected = CC_DIGITALIN,
+		.codec_bit = 0,
+	},
+	{
+		.connected = CC_DIGITALOUT,
+		.codec_bit = 1,
+	},
+	{} /* terminate array by .connected == 0 */
+};
+
+static struct layout layouts[] = {
+	/* last PowerBooks (15" Oct 2005) */
+	{ .layout_id = 82,
+	  .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_noheadphones,
+	  },
+	  .codecs[1] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	},
+	/* PowerMac9,1 */
+	{ .layout_id = 60,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_reallineout,
+	  },
+	},
+	/* PowerMac9,1 */
+	{ .layout_id = 61,
+	  .codecs[0] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	},
+	/* PowerBook5,7 */
+	{ .layout_id = 64,
+	  .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_noheadphones,
+	  },
+	},
+	/* PowerBook5,7 */
+	{ .layout_id = 65,
+	  .codecs[0] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	},
+	/* PowerBook5,9 [17" Oct 2005] */
+	{ .layout_id = 84,
+	  .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_noheadphones,
+	  },
+	  .codecs[1] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	},
+	/* PowerMac8,1 */
+	{ .layout_id = 45,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_noheadphones,
+	  },
+	  .codecs[1] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	},
+	/* Quad PowerMac (analog in, analog/digital out) */
+	{ .layout_id = 68,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_nomic,
+	  },
+	},
+	/* Quad PowerMac (digital in) */
+	{ .layout_id = 69,
+	  .codecs[0] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	  .busname = "digital in", .pcmid = 1 },
+	/* Early 2005 PowerBook (PowerBook 5,6) */
+	{ .layout_id = 70,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_nolineout,
+	  },
+	},
+	/* PowerBook 5,4 */
+	{ .layout_id = 51,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_nolineout,
+	  },
+	},
+	/* PowerBook6,7 */
+	{ .layout_id = 80,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_noline,
+	  },
+	},
+	/* PowerBook6,8 */
+	{ .layout_id = 72,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_nolineout,
+	  },
+	},
+	/* PowerMac8,2 */
+	{ .layout_id = 86,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_nomic,
+	  },
+	  .codecs[1] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	},
+	/* PowerBook6,7 */
+	{ .layout_id = 92,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_nolineout,
+	  },
+	},
+	/* PowerMac10,1 (Mac Mini) */
+	{ .layout_id = 58,
+	  .codecs[0] = {
+		.name = "toonie",
+		.connections = toonie_connections,
+	  },
+	},
+	{
+	  .layout_id = 96,
+	  .codecs[0] = {
+	  	.name = "onyx",
+	  	.connections = onyx_connections_noheadphones,
+	  },
+	},
+	/* unknown, untested, but this comes from Apple */
+	{ .layout_id = 41,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_all,
+	  },
+	},
+	{ .layout_id = 36,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_nomic,
+	  },
+	  .codecs[1] = {
+		.name = "topaz",
+		.connections = topaz_inout,
+	  },
+	},
+	{ .layout_id = 47,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_noheadphones,
+	  },
+	},
+	{ .layout_id = 48,
+	  .codecs[0] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	},
+	{ .layout_id = 49,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_nomic,
+	  },
+	},
+	{ .layout_id = 50,
+	  .codecs[0] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	},
+	{ .layout_id = 56,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_noheadphones,
+	  },
+	},
+	{ .layout_id = 57,
+	  .codecs[0] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	},
+	{ .layout_id = 62,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_noheadphones,
+	  },
+	  .codecs[1] = {
+		.name = "topaz",
+		.connections = topaz_output,
+	  },
+	},
+	{ .layout_id = 66,
+	  .codecs[0] = {
+		.name = "onyx",
+		.connections = onyx_connections_noheadphones,
+	  },
+	},
+	{ .layout_id = 67,
+	  .codecs[0] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	},
+	{ .layout_id = 76,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_nomic,
+	  },
+	  .codecs[1] = {
+		.name = "topaz",
+		.connections = topaz_inout,
+	  },
+	},
+	{ .layout_id = 90,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_noline,
+	  },
+	},
+	{ .layout_id = 94,
+	  .codecs[0] = {
+		.name = "onyx",
+		/* but it has an external mic?? how to select? */
+		.connections = onyx_connections_noheadphones,
+	  },
+	},
+	{ .layout_id = 98,
+	  .codecs[0] = {
+		.name = "toonie",
+		.connections = toonie_connections,
+	  },
+	},
+	{ .layout_id = 100,
+	  .codecs[0] = {
+		.name = "topaz",
+		.connections = topaz_input,
+	  },
+	  .codecs[1] = {
+		.name = "onyx",
+		.connections = onyx_connections_noheadphones,
+	  },
+	},
+	/* PowerMac3,4 */
+	{ .device_id = 14,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_noline,
+	  },
+	},
+	/* PowerMac3,6 */
+	{ .device_id = 22,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_all,
+	  },
+	},
+	/* PowerBook5,2 */
+	{ .device_id = 35,
+	  .codecs[0] = {
+		.name = "tas",
+		.connections = tas_connections_all,
+	  },
+	},
+	{}
+};
+
+static struct layout *find_layout_by_id(unsigned int id)
+{
+	struct layout *l;
+
+	l = layouts;
+	while (l->codecs[0].name) {
+		if (l->layout_id == id)
+			return l;
+		l++;
+	}
+	return NULL;
+}
+
+static struct layout *find_layout_by_device(unsigned int id)
+{
+	struct layout *l;
+
+	l = layouts;
+	while (l->codecs[0].name) {
+		if (l->device_id == id)
+			return l;
+		l++;
+	}
+	return NULL;
+}
+
+static void use_layout(struct layout *l)
+{
+	int i;
+
+	for (i=0; i<MAX_CODECS_PER_BUS; i++) {
+		if (l->codecs[i].name) {
+			request_module("snd-aoa-codec-%s", l->codecs[i].name);
+		}
+	}
+	/* now we wait for the codecs to call us back */
+}
+
+struct layout_dev;
+
+struct layout_dev_ptr {
+	struct layout_dev *ptr;
+};
+
+struct layout_dev {
+	struct list_head list;
+	struct soundbus_dev *sdev;
+	struct device_node *sound;
+	struct aoa_codec *codecs[MAX_CODECS_PER_BUS];
+	struct layout *layout;
+	struct gpio_runtime gpio;
+
+	/* we need these for headphone/lineout detection */
+	struct snd_kcontrol *headphone_ctrl;
+	struct snd_kcontrol *lineout_ctrl;
+	struct snd_kcontrol *speaker_ctrl;
+	struct snd_kcontrol *master_ctrl;
+	struct snd_kcontrol *headphone_detected_ctrl;
+	struct snd_kcontrol *lineout_detected_ctrl;
+
+	struct layout_dev_ptr selfptr_headphone;
+	struct layout_dev_ptr selfptr_lineout;
+
+	u32 have_lineout_detect:1,
+	    have_headphone_detect:1,
+	    switch_on_headphone:1,
+	    switch_on_lineout:1;
+};
+
+static LIST_HEAD(layouts_list);
+static int layouts_list_items;
+/* this can go away but only if we allow multiple cards,
+ * make the fabric handle all the card stuff, etc... */
+static struct layout_dev *layout_device;
+
+#define control_info	snd_ctl_boolean_mono_info
+
+#define AMP_CONTROL(n, description)					\
+static int n##_control_get(struct snd_kcontrol *kcontrol,		\
+			   struct snd_ctl_elem_value *ucontrol)		\
+{									\
+	struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);	\
+	if (gpio->methods && gpio->methods->get_##n)			\
+		ucontrol->value.integer.value[0] =			\
+			gpio->methods->get_##n(gpio);			\
+	return 0;							\
+}									\
+static int n##_control_put(struct snd_kcontrol *kcontrol,		\
+			   struct snd_ctl_elem_value *ucontrol)		\
+{									\
+	struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);	\
+	if (gpio->methods && gpio->methods->get_##n)			\
+		gpio->methods->set_##n(gpio,				\
+			!!ucontrol->value.integer.value[0]);		\
+	return 1;							\
+}									\
+static struct snd_kcontrol_new n##_ctl = {				\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,				\
+	.name = description,						\
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,                      \
+	.info = control_info,						\
+	.get = n##_control_get,						\
+	.put = n##_control_put,						\
+}
+
+AMP_CONTROL(headphone, "Headphone Switch");
+AMP_CONTROL(speakers, "Speakers Switch");
+AMP_CONTROL(lineout, "Line-Out Switch");
+AMP_CONTROL(master, "Master Switch");
+
+static int detect_choice_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
+
+	switch (kcontrol->private_value) {
+	case 0:
+		ucontrol->value.integer.value[0] = ldev->switch_on_headphone;
+		break;
+	case 1:
+		ucontrol->value.integer.value[0] = ldev->switch_on_lineout;
+		break;
+	default:
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int detect_choice_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
+
+	switch (kcontrol->private_value) {
+	case 0:
+		ldev->switch_on_headphone = !!ucontrol->value.integer.value[0];
+		break;
+	case 1:
+		ldev->switch_on_lineout = !!ucontrol->value.integer.value[0];
+		break;
+	default:
+		return -ENODEV;
+	}
+	return 1;
+}
+
+static struct snd_kcontrol_new headphone_detect_choice = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Headphone Detect Autoswitch",
+	.info = control_info,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.get = detect_choice_get,
+	.put = detect_choice_put,
+	.private_value = 0,
+};
+
+static struct snd_kcontrol_new lineout_detect_choice = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Line-Out Detect Autoswitch",
+	.info = control_info,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.get = detect_choice_get,
+	.put = detect_choice_put,
+	.private_value = 1,
+};
+
+static int detected_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
+	int v;
+
+	switch (kcontrol->private_value) {
+	case 0:
+		v = ldev->gpio.methods->get_detect(&ldev->gpio,
+						   AOA_NOTIFY_HEADPHONE);
+		break;
+	case 1:
+		v = ldev->gpio.methods->get_detect(&ldev->gpio,
+						   AOA_NOTIFY_LINE_OUT);
+		break;
+	default:
+		return -ENODEV;
+	}
+	ucontrol->value.integer.value[0] = v;
+	return 0;
+}
+
+static struct snd_kcontrol_new headphone_detected = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Headphone Detected",
+	.info = control_info,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.get = detected_get,
+	.private_value = 0,
+};
+
+static struct snd_kcontrol_new lineout_detected = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Line-Out Detected",
+	.info = control_info,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.get = detected_get,
+	.private_value = 1,
+};
+
+static int check_codec(struct aoa_codec *codec,
+		       struct layout_dev *ldev,
+		       struct codec_connect_info *cci)
+{
+	const u32 *ref;
+	char propname[32];
+	struct codec_connection *cc;
+
+	/* if the codec has a 'codec' node, we require a reference */
+	if (codec->node && (strcmp(codec->node->name, "codec") == 0)) {
+		snprintf(propname, sizeof(propname),
+			 "platform-%s-codec-ref", codec->name);
+		ref = of_get_property(ldev->sound, propname, NULL);
+		if (!ref) {
+			printk(KERN_INFO "snd-aoa-fabric-layout: "
+				"required property %s not present\n", propname);
+			return -ENODEV;
+		}
+		if (*ref != codec->node->phandle) {
+			printk(KERN_INFO "snd-aoa-fabric-layout: "
+				"%s doesn't match!\n", propname);
+			return -ENODEV;
+		}
+	} else {
+		if (layouts_list_items != 1) {
+			printk(KERN_INFO "snd-aoa-fabric-layout: "
+				"more than one soundbus, but no references.\n");
+			return -ENODEV;
+		}
+	}
+	codec->soundbus_dev = ldev->sdev;
+	codec->gpio = &ldev->gpio;
+
+	cc = cci->connections;
+	if (!cc)
+		return -EINVAL;
+
+	printk(KERN_INFO "snd-aoa-fabric-layout: can use this codec\n");
+
+	codec->connected = 0;
+	codec->fabric_data = cc;
+
+	while (cc->connected) {
+		codec->connected |= 1<<cc->codec_bit;
+		cc++;
+	}
+
+	return 0;
+}
+
+static int layout_found_codec(struct aoa_codec *codec)
+{
+	struct layout_dev *ldev;
+	int i;
+
+	list_for_each_entry(ldev, &layouts_list, list) {
+		for (i=0; i<MAX_CODECS_PER_BUS; i++) {
+			if (!ldev->layout->codecs[i].name)
+				continue;
+			if (strcmp(ldev->layout->codecs[i].name, codec->name) == 0) {
+				if (check_codec(codec,
+						ldev,
+						&ldev->layout->codecs[i]) == 0)
+					return 0;
+			}
+		}
+	}
+	return -ENODEV;
+}
+
+static void layout_remove_codec(struct aoa_codec *codec)
+{
+	int i;
+	/* here remove the codec from the layout dev's
+	 * codec reference */
+
+	codec->soundbus_dev = NULL;
+	codec->gpio = NULL;
+	for (i=0; i<MAX_CODECS_PER_BUS; i++) {
+	}
+}
+
+static void layout_notify(void *data)
+{
+	struct layout_dev_ptr *dptr = data;
+	struct layout_dev *ldev;
+	int v, update;
+	struct snd_kcontrol *detected, *c;
+	struct snd_card *card = aoa_get_card();
+
+	ldev = dptr->ptr;
+	if (data == &ldev->selfptr_headphone) {
+		v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_HEADPHONE);
+		detected = ldev->headphone_detected_ctrl;
+		update = ldev->switch_on_headphone;
+		if (update) {
+			ldev->gpio.methods->set_speakers(&ldev->gpio, !v);
+			ldev->gpio.methods->set_headphone(&ldev->gpio, v);
+			ldev->gpio.methods->set_lineout(&ldev->gpio, 0);
+		}
+	} else if (data == &ldev->selfptr_lineout) {
+		v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_LINE_OUT);
+		detected = ldev->lineout_detected_ctrl;
+		update = ldev->switch_on_lineout;
+		if (update) {
+			ldev->gpio.methods->set_speakers(&ldev->gpio, !v);
+			ldev->gpio.methods->set_headphone(&ldev->gpio, 0);
+			ldev->gpio.methods->set_lineout(&ldev->gpio, v);
+		}
+	} else
+		return;
+
+	if (detected)
+		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &detected->id);
+	if (update) {
+		c = ldev->headphone_ctrl;
+		if (c)
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
+		c = ldev->speaker_ctrl;
+		if (c)
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
+		c = ldev->lineout_ctrl;
+		if (c)
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
+	}
+}
+
+static void layout_attached_codec(struct aoa_codec *codec)
+{
+	struct codec_connection *cc;
+	struct snd_kcontrol *ctl;
+	int headphones, lineout;
+	struct layout_dev *ldev = layout_device;
+
+	/* need to add this codec to our codec array! */
+
+	cc = codec->fabric_data;
+
+	headphones = codec->gpio->methods->get_detect(codec->gpio,
+						      AOA_NOTIFY_HEADPHONE);
+ 	lineout = codec->gpio->methods->get_detect(codec->gpio,
+						   AOA_NOTIFY_LINE_OUT);
+
+	if (codec->gpio->methods->set_master) {
+		ctl = snd_ctl_new1(&master_ctl, codec->gpio);
+		ldev->master_ctrl = ctl;
+		aoa_snd_ctl_add(ctl);
+	}
+	while (cc->connected) {
+		if (cc->connected & CC_SPEAKERS) {
+			if (headphones <= 0 && lineout <= 0)
+				ldev->gpio.methods->set_speakers(codec->gpio, 1);
+			ctl = snd_ctl_new1(&speakers_ctl, codec->gpio);
+			ldev->speaker_ctrl = ctl;
+			aoa_snd_ctl_add(ctl);
+		}
+		if (cc->connected & CC_HEADPHONE) {
+			if (headphones == 1)
+				ldev->gpio.methods->set_headphone(codec->gpio, 1);
+			ctl = snd_ctl_new1(&headphone_ctl, codec->gpio);
+			ldev->headphone_ctrl = ctl;
+			aoa_snd_ctl_add(ctl);
+			ldev->have_headphone_detect =
+				!ldev->gpio.methods
+					->set_notify(&ldev->gpio,
+						     AOA_NOTIFY_HEADPHONE,
+						     layout_notify,
+						     &ldev->selfptr_headphone);
+			if (ldev->have_headphone_detect) {
+				ctl = snd_ctl_new1(&headphone_detect_choice,
+						   ldev);
+				aoa_snd_ctl_add(ctl);
+				ctl = snd_ctl_new1(&headphone_detected,
+						   ldev);
+				ldev->headphone_detected_ctrl = ctl;
+				aoa_snd_ctl_add(ctl);
+			}
+		}
+		if (cc->connected & CC_LINEOUT) {
+			if (lineout == 1)
+				ldev->gpio.methods->set_lineout(codec->gpio, 1);
+			ctl = snd_ctl_new1(&lineout_ctl, codec->gpio);
+			if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+				strlcpy(ctl->id.name,
+					"Headphone Switch", sizeof(ctl->id.name));
+			ldev->lineout_ctrl = ctl;
+			aoa_snd_ctl_add(ctl);
+			ldev->have_lineout_detect =
+				!ldev->gpio.methods
+					->set_notify(&ldev->gpio,
+						     AOA_NOTIFY_LINE_OUT,
+						     layout_notify,
+						     &ldev->selfptr_lineout);
+			if (ldev->have_lineout_detect) {
+				ctl = snd_ctl_new1(&lineout_detect_choice,
+						   ldev);
+				if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+					strlcpy(ctl->id.name,
+						"Headphone Detect Autoswitch",
+						sizeof(ctl->id.name));
+				aoa_snd_ctl_add(ctl);
+				ctl = snd_ctl_new1(&lineout_detected,
+						   ldev);
+				if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+					strlcpy(ctl->id.name,
+						"Headphone Detected",
+						sizeof(ctl->id.name));
+				ldev->lineout_detected_ctrl = ctl;
+				aoa_snd_ctl_add(ctl);
+			}
+		}
+		cc++;
+	}
+	/* now update initial state */
+	if (ldev->have_headphone_detect)
+		layout_notify(&ldev->selfptr_headphone);
+	if (ldev->have_lineout_detect)
+		layout_notify(&ldev->selfptr_lineout);
+}
+
+static struct aoa_fabric layout_fabric = {
+	.name = "SoundByLayout",
+	.owner = THIS_MODULE,
+	.found_codec = layout_found_codec,
+	.remove_codec = layout_remove_codec,
+	.attached_codec = layout_attached_codec,
+};
+
+static int aoa_fabric_layout_probe(struct soundbus_dev *sdev)
+{
+	struct device_node *sound = NULL;
+	const unsigned int *id;
+	struct layout *layout = NULL;
+	struct layout_dev *ldev = NULL;
+	int err;
+
+	/* hm, currently we can only have one ... */
+	if (layout_device)
+		return -ENODEV;
+
+	/* by breaking out we keep a reference */
+	while ((sound = of_get_next_child(sdev->ofdev.dev.of_node, sound))) {
+		if (sound->type && strcasecmp(sound->type, "soundchip") == 0)
+			break;
+	}
+	if (!sound)
+		return -ENODEV;
+
+	id = of_get_property(sound, "layout-id", NULL);
+	if (id) {
+		layout = find_layout_by_id(*id);
+	} else {
+		id = of_get_property(sound, "device-id", NULL);
+		if (id)
+			layout = find_layout_by_device(*id);
+	}
+
+	if (!layout) {
+		printk(KERN_ERR "snd-aoa-fabric-layout: unknown layout\n");
+		goto outnodev;
+	}
+
+	ldev = kzalloc(sizeof(struct layout_dev), GFP_KERNEL);
+	if (!ldev)
+		goto outnodev;
+
+	layout_device = ldev;
+	ldev->sdev = sdev;
+	ldev->sound = sound;
+	ldev->layout = layout;
+	ldev->gpio.node = sound->parent;
+	switch (layout->layout_id) {
+	case 0:  /* anything with device_id, not layout_id */
+	case 41: /* that unknown machine no one seems to have */
+	case 51: /* PowerBook5,4 */
+	case 58: /* Mac Mini */
+		ldev->gpio.methods = ftr_gpio_methods;
+		printk(KERN_DEBUG
+		       "snd-aoa-fabric-layout: Using direct GPIOs\n");
+		break;
+	default:
+		ldev->gpio.methods = pmf_gpio_methods;
+		printk(KERN_DEBUG
+		       "snd-aoa-fabric-layout: Using PMF GPIOs\n");
+	}
+	ldev->selfptr_headphone.ptr = ldev;
+	ldev->selfptr_lineout.ptr = ldev;
+	dev_set_drvdata(&sdev->ofdev.dev, ldev);
+	list_add(&ldev->list, &layouts_list);
+	layouts_list_items++;
+
+	/* assign these before registering ourselves, so
+	 * callbacks that are done during registration
+	 * already have the values */
+	sdev->pcmid = ldev->layout->pcmid;
+	if (ldev->layout->busname) {
+		sdev->pcmname = ldev->layout->busname;
+	} else {
+		sdev->pcmname = "Master";
+	}
+
+	ldev->gpio.methods->init(&ldev->gpio);
+
+	err = aoa_fabric_register(&layout_fabric, &sdev->ofdev.dev);
+	if (err && err != -EALREADY) {
+		printk(KERN_INFO "snd-aoa-fabric-layout: can't use,"
+				 " another fabric is active!\n");
+		goto outlistdel;
+	}
+
+	use_layout(layout);
+	ldev->switch_on_headphone = 1;
+	ldev->switch_on_lineout = 1;
+	return 0;
+ outlistdel:
+	/* we won't be using these then... */
+	ldev->gpio.methods->exit(&ldev->gpio);
+	/* reset if we didn't use it */
+	sdev->pcmname = NULL;
+	sdev->pcmid = -1;
+	list_del(&ldev->list);
+	layouts_list_items--;
+ outnodev:
+ 	of_node_put(sound);
+ 	layout_device = NULL;
+ 	kfree(ldev);
+	return -ENODEV;
+}
+
+static int aoa_fabric_layout_remove(struct soundbus_dev *sdev)
+{
+	struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);
+	int i;
+
+	for (i=0; i<MAX_CODECS_PER_BUS; i++) {
+		if (ldev->codecs[i]) {
+			aoa_fabric_unlink_codec(ldev->codecs[i]);
+		}
+		ldev->codecs[i] = NULL;
+	}
+	list_del(&ldev->list);
+	layouts_list_items--;
+	of_node_put(ldev->sound);
+
+	ldev->gpio.methods->set_notify(&ldev->gpio,
+				       AOA_NOTIFY_HEADPHONE,
+				       NULL,
+				       NULL);
+	ldev->gpio.methods->set_notify(&ldev->gpio,
+				       AOA_NOTIFY_LINE_OUT,
+				       NULL,
+				       NULL);
+
+	ldev->gpio.methods->exit(&ldev->gpio);
+	layout_device = NULL;
+	kfree(ldev);
+	sdev->pcmid = -1;
+	sdev->pcmname = NULL;
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t state)
+{
+	struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);
+
+	if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
+		ldev->gpio.methods->all_amps_off(&ldev->gpio);
+
+	return 0;
+}
+
+static int aoa_fabric_layout_resume(struct soundbus_dev *sdev)
+{
+	struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);
+
+	if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
+		ldev->gpio.methods->all_amps_restore(&ldev->gpio);
+
+	return 0;
+}
+#endif
+
+static struct soundbus_driver aoa_soundbus_driver = {
+	.name = "snd_aoa_soundbus_drv",
+	.owner = THIS_MODULE,
+	.probe = aoa_fabric_layout_probe,
+	.remove = aoa_fabric_layout_remove,
+#ifdef CONFIG_PM
+	.suspend = aoa_fabric_layout_suspend,
+	.resume = aoa_fabric_layout_resume,
+#endif
+	.driver = {
+		.owner = THIS_MODULE,
+	}
+};
+
+static int __init aoa_fabric_layout_init(void)
+{
+	int err;
+
+	err = soundbus_register_driver(&aoa_soundbus_driver);
+	if (err)
+		return err;
+	return 0;
+}
+
+static void __exit aoa_fabric_layout_exit(void)
+{
+	soundbus_unregister_driver(&aoa_soundbus_driver);
+	aoa_fabric_unregister(&layout_fabric);
+}
+
+module_init(aoa_fabric_layout_init);
+module_exit(aoa_fabric_layout_exit);
diff --git a/linux/sound/aoa/soundbus/Kconfig b/linux/sound/aoa/soundbus/Kconfig
new file mode 100644
index 0000000..839d113
--- /dev/null
+++ b/linux/sound/aoa/soundbus/Kconfig
@@ -0,0 +1,14 @@
+config SND_AOA_SOUNDBUS
+	tristate "Apple Soundbus support"
+	select SND_PCM
+	---help---
+	This option enables the generic driver for the soundbus
+	support on Apple machines.
+	
+	It is required for the sound bus implementations.
+
+config SND_AOA_SOUNDBUS_I2S
+	tristate "I2S bus support"
+	depends on SND_AOA_SOUNDBUS && PCI
+	---help---
+	This option enables support for Apple I2S busses.
diff --git a/linux/sound/aoa/soundbus/Makefile b/linux/sound/aoa/soundbus/Makefile
new file mode 100644
index 0000000..0e61f5a
--- /dev/null
+++ b/linux/sound/aoa/soundbus/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_SND_AOA_SOUNDBUS) += snd-aoa-soundbus.o
+snd-aoa-soundbus-objs := core.o sysfs.o
+obj-$(CONFIG_SND_AOA_SOUNDBUS_I2S) += i2sbus/
diff --git a/linux/sound/aoa/soundbus/core.c b/linux/sound/aoa/soundbus/core.c
new file mode 100644
index 0000000..7487eb7
--- /dev/null
+++ b/linux/sound/aoa/soundbus/core.c
@@ -0,0 +1,219 @@
+/*
+ * soundbus
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <linux/module.h>
+#include "soundbus.h"
+
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Apple Soundbus");
+
+struct soundbus_dev *soundbus_dev_get(struct soundbus_dev *dev)
+{
+	struct device *tmp;
+
+	if (!dev)
+		return NULL;
+	tmp = get_device(&dev->ofdev.dev);
+	if (tmp)
+		return to_soundbus_device(tmp);
+	else
+		return NULL;
+}
+EXPORT_SYMBOL_GPL(soundbus_dev_get);
+
+void soundbus_dev_put(struct soundbus_dev *dev)
+{
+	if (dev)
+		put_device(&dev->ofdev.dev);
+}
+EXPORT_SYMBOL_GPL(soundbus_dev_put);
+
+static int soundbus_probe(struct device *dev)
+{
+	int error = -ENODEV;
+	struct soundbus_driver *drv;
+	struct soundbus_dev *soundbus_dev;
+
+	drv = to_soundbus_driver(dev->driver);
+	soundbus_dev = to_soundbus_device(dev);
+
+	if (!drv->probe)
+		return error;
+
+	soundbus_dev_get(soundbus_dev);
+
+	error = drv->probe(soundbus_dev);
+	if (error)
+		soundbus_dev_put(soundbus_dev);
+
+	return error;
+}
+
+
+static int soundbus_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	struct soundbus_dev * soundbus_dev;
+	struct platform_device * of;
+	const char *compat;
+	int retval = 0;
+	int cplen, seen = 0;
+
+	if (!dev)
+		return -ENODEV;
+
+	soundbus_dev = to_soundbus_device(dev);
+	if (!soundbus_dev)
+		return -ENODEV;
+
+	of = &soundbus_dev->ofdev;
+
+	/* stuff we want to pass to /sbin/hotplug */
+	retval = add_uevent_var(env, "OF_NAME=%s", of->dev.of_node->name);
+	if (retval)
+		return retval;
+
+	retval = add_uevent_var(env, "OF_TYPE=%s", of->dev.of_node->type);
+	if (retval)
+		return retval;
+
+	/* Since the compatible field can contain pretty much anything
+	 * it's not really legal to split it out with commas. We split it
+	 * up using a number of environment variables instead. */
+
+	compat = of_get_property(of->dev.of_node, "compatible", &cplen);
+	while (compat && cplen > 0) {
+		int tmp = env->buflen;
+		retval = add_uevent_var(env, "OF_COMPATIBLE_%d=%s", seen, compat);
+		if (retval)
+			return retval;
+		compat += env->buflen - tmp;
+		cplen -= env->buflen - tmp;
+		seen += 1;
+	}
+
+	retval = add_uevent_var(env, "OF_COMPATIBLE_N=%d", seen);
+	if (retval)
+		return retval;
+	retval = add_uevent_var(env, "MODALIAS=%s", soundbus_dev->modalias);
+
+	return retval;
+}
+
+static int soundbus_device_remove(struct device *dev)
+{
+	struct soundbus_dev * soundbus_dev = to_soundbus_device(dev);
+	struct soundbus_driver * drv = to_soundbus_driver(dev->driver);
+
+	if (dev->driver && drv->remove)
+		drv->remove(soundbus_dev);
+	soundbus_dev_put(soundbus_dev);
+
+	return 0;
+}
+
+static void soundbus_device_shutdown(struct device *dev)
+{
+	struct soundbus_dev * soundbus_dev = to_soundbus_device(dev);
+	struct soundbus_driver * drv = to_soundbus_driver(dev->driver);
+
+	if (dev->driver && drv->shutdown)
+		drv->shutdown(soundbus_dev);
+}
+
+#ifdef CONFIG_PM
+
+static int soundbus_device_suspend(struct device *dev, pm_message_t state)
+{
+	struct soundbus_dev * soundbus_dev = to_soundbus_device(dev);
+	struct soundbus_driver * drv = to_soundbus_driver(dev->driver);
+
+	if (dev->driver && drv->suspend)
+		return drv->suspend(soundbus_dev, state);
+	return 0;
+}
+
+static int soundbus_device_resume(struct device * dev)
+{
+	struct soundbus_dev * soundbus_dev = to_soundbus_device(dev);
+	struct soundbus_driver * drv = to_soundbus_driver(dev->driver);
+
+	if (dev->driver && drv->resume)
+		return drv->resume(soundbus_dev);
+	return 0;
+}
+
+#endif /* CONFIG_PM */
+
+static struct bus_type soundbus_bus_type = {
+	.name		= "aoa-soundbus",
+	.probe		= soundbus_probe,
+	.uevent		= soundbus_uevent,
+	.remove		= soundbus_device_remove,
+	.shutdown	= soundbus_device_shutdown,
+#ifdef CONFIG_PM
+	.suspend	= soundbus_device_suspend,
+	.resume		= soundbus_device_resume,
+#endif
+	.dev_attrs	= soundbus_dev_attrs,
+};
+
+int soundbus_add_one(struct soundbus_dev *dev)
+{
+	static int devcount;
+
+	/* sanity checks */
+	if (!dev->attach_codec ||
+	    !dev->ofdev.dev.of_node ||
+	    dev->pcmname ||
+	    dev->pcmid != -1) {
+		printk(KERN_ERR "soundbus: adding device failed sanity check!\n");
+		return -EINVAL;
+	}
+
+	dev_set_name(&dev->ofdev.dev, "soundbus:%x", ++devcount);
+	dev->ofdev.dev.bus = &soundbus_bus_type;
+	return of_device_register(&dev->ofdev);
+}
+EXPORT_SYMBOL_GPL(soundbus_add_one);
+
+void soundbus_remove_one(struct soundbus_dev *dev)
+{
+	of_device_unregister(&dev->ofdev);
+}
+EXPORT_SYMBOL_GPL(soundbus_remove_one);
+
+int soundbus_register_driver(struct soundbus_driver *drv)
+{
+	/* initialize common driver fields */
+	drv->driver.name = drv->name;
+	drv->driver.bus = &soundbus_bus_type;
+
+	/* register with core */
+	return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(soundbus_register_driver);
+
+void soundbus_unregister_driver(struct soundbus_driver *drv)
+{
+	driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(soundbus_unregister_driver);
+
+static int __init soundbus_init(void)
+{
+	return bus_register(&soundbus_bus_type);
+}
+
+static void __exit soundbus_exit(void)
+{
+	bus_unregister(&soundbus_bus_type);
+}
+
+subsys_initcall(soundbus_init);
+module_exit(soundbus_exit);
diff --git a/linux/sound/aoa/soundbus/i2sbus/Makefile b/linux/sound/aoa/soundbus/i2sbus/Makefile
new file mode 100644
index 0000000..1b949b2
--- /dev/null
+++ b/linux/sound/aoa/soundbus/i2sbus/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_SND_AOA_SOUNDBUS_I2S) += snd-aoa-i2sbus.o
+snd-aoa-i2sbus-objs := core.o pcm.o control.o
diff --git a/linux/sound/aoa/soundbus/i2sbus/control.c b/linux/sound/aoa/soundbus/i2sbus/control.c
new file mode 100644
index 0000000..4dc9b49
--- /dev/null
+++ b/linux/sound/aoa/soundbus/i2sbus/control.c
@@ -0,0 +1,194 @@
+/*
+ * i2sbus driver -- bus control routines
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <asm/io.h>
+#include <asm/prom.h>
+#include <asm/macio.h>
+#include <asm/pmac_feature.h>
+#include <asm/pmac_pfunc.h>
+#include <asm/keylargo.h>
+
+#include "i2sbus.h"
+
+int i2sbus_control_init(struct macio_dev* dev, struct i2sbus_control **c)
+{
+	*c = kzalloc(sizeof(struct i2sbus_control), GFP_KERNEL);
+	if (!*c)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&(*c)->list);
+
+	(*c)->macio = dev->bus->chip;
+	return 0;
+}
+
+void i2sbus_control_destroy(struct i2sbus_control *c)
+{
+	kfree(c);
+}
+
+/* this is serialised externally */
+int i2sbus_control_add_dev(struct i2sbus_control *c,
+			   struct i2sbus_dev *i2sdev)
+{
+	struct device_node *np;
+
+	np = i2sdev->sound.ofdev.dev.of_node;
+	i2sdev->enable = pmf_find_function(np, "enable");
+	i2sdev->cell_enable = pmf_find_function(np, "cell-enable");
+	i2sdev->clock_enable = pmf_find_function(np, "clock-enable");
+	i2sdev->cell_disable = pmf_find_function(np, "cell-disable");
+	i2sdev->clock_disable = pmf_find_function(np, "clock-disable");
+
+	/* if the bus number is not 0 or 1 we absolutely need to use
+	 * the platform functions -- there's nothing in Darwin that
+	 * would allow seeing a system behind what the FCRs are then,
+	 * and I don't want to go parsing a bunch of platform functions
+	 * by hand to try finding a system... */
+	if (i2sdev->bus_number != 0 && i2sdev->bus_number != 1 &&
+	    (!i2sdev->enable ||
+	     !i2sdev->cell_enable || !i2sdev->clock_enable ||
+	     !i2sdev->cell_disable || !i2sdev->clock_disable)) {
+		pmf_put_function(i2sdev->enable);
+		pmf_put_function(i2sdev->cell_enable);
+		pmf_put_function(i2sdev->clock_enable);
+		pmf_put_function(i2sdev->cell_disable);
+		pmf_put_function(i2sdev->clock_disable);
+		return -ENODEV;
+	}
+
+	list_add(&i2sdev->item, &c->list);
+
+	return 0;
+}
+
+void i2sbus_control_remove_dev(struct i2sbus_control *c,
+			       struct i2sbus_dev *i2sdev)
+{
+	/* this is serialised externally */
+	list_del(&i2sdev->item);
+	if (list_empty(&c->list))
+		i2sbus_control_destroy(c);
+}
+
+int i2sbus_control_enable(struct i2sbus_control *c,
+			  struct i2sbus_dev *i2sdev)
+{
+	struct pmf_args args = { .count = 0 };
+	struct macio_chip *macio = c->macio;
+
+	if (i2sdev->enable)
+		return pmf_call_one(i2sdev->enable, &args);
+
+	if (macio == NULL || macio->base == NULL)
+		return -ENODEV;
+
+	switch (i2sdev->bus_number) {
+	case 0:
+		/* these need to be locked or done through
+		 * newly created feature calls! */
+		MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_ENABLE);
+		break;
+	case 1:
+		MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_ENABLE);
+		break;
+	default:
+		return -ENODEV;
+	}
+	return 0;
+}
+
+int i2sbus_control_cell(struct i2sbus_control *c,
+			struct i2sbus_dev *i2sdev,
+			int enable)
+{
+	struct pmf_args args = { .count = 0 };
+	struct macio_chip *macio = c->macio;
+
+	switch (enable) {
+	case 0:
+		if (i2sdev->cell_disable)
+			return pmf_call_one(i2sdev->cell_disable, &args);
+		break;
+	case 1:
+		if (i2sdev->cell_enable)
+			return pmf_call_one(i2sdev->cell_enable, &args);
+		break;
+	default:
+		printk(KERN_ERR "i2sbus: INVALID CELL ENABLE VALUE\n");
+		return -ENODEV;
+	}
+
+	if (macio == NULL || macio->base == NULL)
+		return -ENODEV;
+
+	switch (i2sdev->bus_number) {
+	case 0:
+		if (enable)
+			MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE);
+		else
+			MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE);
+		break;
+	case 1:
+		if (enable)
+			MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_CELL_ENABLE);
+		else
+			MACIO_BIC(KEYLARGO_FCR1, KL1_I2S1_CELL_ENABLE);
+		break;
+	default:
+		return -ENODEV;
+	}
+	return 0;
+}
+
+int i2sbus_control_clock(struct i2sbus_control *c,
+			 struct i2sbus_dev *i2sdev,
+			 int enable)
+{
+	struct pmf_args args = { .count = 0 };
+	struct macio_chip *macio = c->macio;
+
+	switch (enable) {
+	case 0:
+		if (i2sdev->clock_disable)
+			return pmf_call_one(i2sdev->clock_disable, &args);
+		break;
+	case 1:
+		if (i2sdev->clock_enable)
+			return pmf_call_one(i2sdev->clock_enable, &args);
+		break;
+	default:
+		printk(KERN_ERR "i2sbus: INVALID CLOCK ENABLE VALUE\n");
+		return -ENODEV;
+	}
+
+	if (macio == NULL || macio->base == NULL)
+		return -ENODEV;
+
+	switch (i2sdev->bus_number) {
+	case 0:
+		if (enable)
+			MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT);
+		else
+			MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT);
+		break;
+	case 1:
+		if (enable)
+			MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_CLK_ENABLE_BIT);
+		else
+			MACIO_BIC(KEYLARGO_FCR1, KL1_I2S1_CLK_ENABLE_BIT);
+		break;
+	default:
+		return -ENODEV;
+	}
+	return 0;
+}
diff --git a/linux/sound/aoa/soundbus/i2sbus/core.c b/linux/sound/aoa/soundbus/i2sbus/core.c
new file mode 100644
index 0000000..3ff8cc5
--- /dev/null
+++ b/linux/sound/aoa/soundbus/i2sbus/core.c
@@ -0,0 +1,465 @@
+/*
+ * i2sbus driver
+ *
+ * Copyright 2006-2008 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+
+#include <asm/macio.h>
+#include <asm/dbdma.h>
+
+#include "../soundbus.h"
+#include "i2sbus.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
+MODULE_DESCRIPTION("Apple Soundbus: I2S support");
+
+static int force;
+module_param(force, int, 0444);
+MODULE_PARM_DESC(force, "Force loading i2sbus even when"
+			" no layout-id property is present");
+
+static struct of_device_id i2sbus_match[] = {
+	{ .name = "i2s" },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(of, i2sbus_match);
+
+static int alloc_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev,
+				       struct dbdma_command_mem *r,
+				       int numcmds)
+{
+	/* one more for rounding, one for branch back, one for stop command */
+	r->size = (numcmds + 3) * sizeof(struct dbdma_cmd);
+	/* We use the PCI APIs for now until the generic one gets fixed
+	 * enough or until we get some macio-specific versions
+	 */
+	r->space = dma_alloc_coherent(
+			&macio_get_pci_dev(i2sdev->macio)->dev,
+			r->size,
+			&r->bus_addr,
+			GFP_KERNEL);
+
+	if (!r->space) return -ENOMEM;
+
+	memset(r->space, 0, r->size);
+	r->cmds = (void*)DBDMA_ALIGN(r->space);
+	r->bus_cmd_start = r->bus_addr +
+			   (dma_addr_t)((char*)r->cmds - (char*)r->space);
+
+	return 0;
+}
+
+static void free_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev,
+				       struct dbdma_command_mem *r)
+{
+	if (!r->space) return;
+
+	dma_free_coherent(&macio_get_pci_dev(i2sdev->macio)->dev,
+			    r->size, r->space, r->bus_addr);
+}
+
+static void i2sbus_release_dev(struct device *dev)
+{
+	struct i2sbus_dev *i2sdev;
+	int i;
+
+	i2sdev = container_of(dev, struct i2sbus_dev, sound.ofdev.dev);
+
+ 	if (i2sdev->intfregs) iounmap(i2sdev->intfregs);
+ 	if (i2sdev->out.dbdma) iounmap(i2sdev->out.dbdma);
+ 	if (i2sdev->in.dbdma) iounmap(i2sdev->in.dbdma);
+	for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++)
+		if (i2sdev->allocated_resource[i])
+			release_and_free_resource(i2sdev->allocated_resource[i]);
+	free_dbdma_descriptor_ring(i2sdev, &i2sdev->out.dbdma_ring);
+	free_dbdma_descriptor_ring(i2sdev, &i2sdev->in.dbdma_ring);
+	for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++)
+		free_irq(i2sdev->interrupts[i], i2sdev);
+	i2sbus_control_remove_dev(i2sdev->control, i2sdev);
+	mutex_destroy(&i2sdev->lock);
+	kfree(i2sdev);
+}
+
+static irqreturn_t i2sbus_bus_intr(int irq, void *devid)
+{
+	struct i2sbus_dev *dev = devid;
+	u32 intreg;
+
+	spin_lock(&dev->low_lock);
+	intreg = in_le32(&dev->intfregs->intr_ctl);
+
+	/* acknowledge interrupt reasons */
+	out_le32(&dev->intfregs->intr_ctl, intreg);
+
+	spin_unlock(&dev->low_lock);
+
+	return IRQ_HANDLED;
+}
+
+
+/*
+ * XXX FIXME: We test the layout_id's here to get the proper way of
+ * mapping in various registers, thanks to bugs in Apple device-trees.
+ * We could instead key off the machine model and the name of the i2s
+ * node (i2s-a). This we'll do when we move it all to macio_asic.c
+ * and have that export items for each sub-node too.
+ */
+static int i2sbus_get_and_fixup_rsrc(struct device_node *np, int index,
+				     int layout, struct resource *res)
+{
+	struct device_node *parent;
+	int pindex, rc = -ENXIO;
+	const u32 *reg;
+
+	/* Machines with layout 76 and 36 (K2 based) have a weird device
+	 * tree what we need to special case.
+	 * Normal machines just fetch the resource from the i2s-X node.
+	 * Darwin further divides normal machines into old and new layouts
+	 * with a subtely different code path but that doesn't seem necessary
+	 * in practice, they just bloated it. In addition, even on our K2
+	 * case the i2s-modem node, if we ever want to handle it, uses the
+	 * normal layout
+	 */
+	if (layout != 76 && layout != 36)
+		return of_address_to_resource(np, index, res);
+
+	parent = of_get_parent(np);
+	pindex = (index == aoa_resource_i2smmio) ? 0 : 1;
+	rc = of_address_to_resource(parent, pindex, res);
+	if (rc)
+		goto bail;
+	reg = of_get_property(np, "reg", NULL);
+	if (reg == NULL) {
+		rc = -ENXIO;
+		goto bail;
+	}
+	res->start += reg[index * 2];
+	res->end = res->start + reg[index * 2 + 1] - 1;
+ bail:
+	of_node_put(parent);
+	return rc;
+}
+
+/* FIXME: look at device node refcounting */
+static int i2sbus_add_dev(struct macio_dev *macio,
+			  struct i2sbus_control *control,
+			  struct device_node *np)
+{
+	struct i2sbus_dev *dev;
+	struct device_node *child = NULL, *sound = NULL;
+	struct resource *r;
+	int i, layout = 0, rlen, ok = force;
+	static const char *rnames[] = { "i2sbus: %s (control)",
+					"i2sbus: %s (tx)",
+					"i2sbus: %s (rx)" };
+	static irq_handler_t ints[] = {
+		i2sbus_bus_intr,
+		i2sbus_tx_intr,
+		i2sbus_rx_intr
+	};
+
+	if (strlen(np->name) != 5)
+		return 0;
+	if (strncmp(np->name, "i2s-", 4))
+		return 0;
+
+	dev = kzalloc(sizeof(struct i2sbus_dev), GFP_KERNEL);
+	if (!dev)
+		return 0;
+
+	i = 0;
+	while ((child = of_get_next_child(np, child))) {
+		if (strcmp(child->name, "sound") == 0) {
+			i++;
+			sound = child;
+		}
+	}
+	if (i == 1) {
+		const u32 *id = of_get_property(sound, "layout-id", NULL);
+
+		if (id) {
+			layout = *id;
+			snprintf(dev->sound.modalias, 32,
+				 "sound-layout-%d", layout);
+			ok = 1;
+		} else {
+			id = of_get_property(sound, "device-id", NULL);
+			/*
+			 * We probably cannot handle all device-id machines,
+			 * so restrict to those we do handle for now.
+			 */
+			if (id && (*id == 22 || *id == 14 || *id == 35)) {
+				snprintf(dev->sound.modalias, 32,
+					 "aoa-device-id-%d", *id);
+				ok = 1;
+				layout = -1;
+			}
+		}
+	}
+	/* for the time being, until we can handle non-layout-id
+	 * things in some fabric, refuse to attach if there is no
+	 * layout-id property or we haven't been forced to attach.
+	 * When there are two i2s busses and only one has a layout-id,
+	 * then this depends on the order, but that isn't important
+	 * either as the second one in that case is just a modem. */
+	if (!ok) {
+		kfree(dev);
+		return -ENODEV;
+	}
+
+	mutex_init(&dev->lock);
+	spin_lock_init(&dev->low_lock);
+	dev->sound.ofdev.archdata.dma_mask = macio->ofdev.archdata.dma_mask;
+	dev->sound.ofdev.dev.of_node = np;
+	dev->sound.ofdev.dev.dma_mask = &dev->sound.ofdev.archdata.dma_mask;
+	dev->sound.ofdev.dev.parent = &macio->ofdev.dev;
+	dev->sound.ofdev.dev.release = i2sbus_release_dev;
+	dev->sound.attach_codec = i2sbus_attach_codec;
+	dev->sound.detach_codec = i2sbus_detach_codec;
+	dev->sound.pcmid = -1;
+	dev->macio = macio;
+	dev->control = control;
+	dev->bus_number = np->name[4] - 'a';
+	INIT_LIST_HEAD(&dev->sound.codec_list);
+
+	for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
+		dev->interrupts[i] = -1;
+		snprintf(dev->rnames[i], sizeof(dev->rnames[i]),
+			 rnames[i], np->name);
+	}
+	for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
+		int irq = irq_of_parse_and_map(np, i);
+		if (request_irq(irq, ints[i], 0, dev->rnames[i], dev))
+			goto err;
+		dev->interrupts[i] = irq;
+	}
+
+
+	/* Resource handling is problematic as some device-trees contain
+	 * useless crap (ugh ugh ugh). We work around that here by calling
+	 * specific functions for calculating the appropriate resources.
+	 *
+	 * This will all be moved to macio_asic.c at one point
+	 */
+	for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) {
+		if (i2sbus_get_and_fixup_rsrc(np,i,layout,&dev->resources[i]))
+			goto err;
+		/* If only we could use our resource dev->resources[i]...
+		 * but request_resource doesn't know about parents and
+		 * contained resources...
+		 */
+		dev->allocated_resource[i] =
+			request_mem_region(dev->resources[i].start,
+					   dev->resources[i].end -
+					   dev->resources[i].start + 1,
+					   dev->rnames[i]);
+		if (!dev->allocated_resource[i]) {
+			printk(KERN_ERR "i2sbus: failed to claim resource %d!\n", i);
+			goto err;
+		}
+	}
+
+	r = &dev->resources[aoa_resource_i2smmio];
+	rlen = r->end - r->start + 1;
+	if (rlen < sizeof(struct i2s_interface_regs))
+		goto err;
+	dev->intfregs = ioremap(r->start, rlen);
+
+	r = &dev->resources[aoa_resource_txdbdma];
+	rlen = r->end - r->start + 1;
+	if (rlen < sizeof(struct dbdma_regs))
+		goto err;
+	dev->out.dbdma = ioremap(r->start, rlen);
+
+	r = &dev->resources[aoa_resource_rxdbdma];
+	rlen = r->end - r->start + 1;
+	if (rlen < sizeof(struct dbdma_regs))
+		goto err;
+	dev->in.dbdma = ioremap(r->start, rlen);
+
+	if (!dev->intfregs || !dev->out.dbdma || !dev->in.dbdma)
+		goto err;
+
+	if (alloc_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring,
+					MAX_DBDMA_COMMANDS))
+		goto err;
+	if (alloc_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring,
+					MAX_DBDMA_COMMANDS))
+		goto err;
+
+	if (i2sbus_control_add_dev(dev->control, dev)) {
+		printk(KERN_ERR "i2sbus: control layer didn't like bus\n");
+		goto err;
+	}
+
+	if (soundbus_add_one(&dev->sound)) {
+		printk(KERN_DEBUG "i2sbus: device registration error!\n");
+		goto err;
+	}
+
+	/* enable this cell */
+	i2sbus_control_cell(dev->control, dev, 1);
+	i2sbus_control_enable(dev->control, dev);
+	i2sbus_control_clock(dev->control, dev, 1);
+
+	return 1;
+ err:
+	for (i=0;i<3;i++)
+		if (dev->interrupts[i] != -1)
+			free_irq(dev->interrupts[i], dev);
+	free_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring);
+	free_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring);
+	if (dev->intfregs) iounmap(dev->intfregs);
+	if (dev->out.dbdma) iounmap(dev->out.dbdma);
+	if (dev->in.dbdma) iounmap(dev->in.dbdma);
+	for (i=0;i<3;i++)
+		if (dev->allocated_resource[i])
+			release_and_free_resource(dev->allocated_resource[i]);
+	mutex_destroy(&dev->lock);
+	kfree(dev);
+	return 0;
+}
+
+static int i2sbus_probe(struct macio_dev* dev, const struct of_device_id *match)
+{
+	struct device_node *np = NULL;
+	int got = 0, err;
+	struct i2sbus_control *control = NULL;
+
+	err = i2sbus_control_init(dev, &control);
+	if (err)
+		return err;
+	if (!control) {
+		printk(KERN_ERR "i2sbus_control_init API breakage\n");
+		return -ENODEV;
+	}
+
+	while ((np = of_get_next_child(dev->ofdev.dev.of_node, np))) {
+		if (of_device_is_compatible(np, "i2sbus") ||
+		    of_device_is_compatible(np, "i2s-modem")) {
+			got += i2sbus_add_dev(dev, control, np);
+		}
+	}
+
+	if (!got) {
+		/* found none, clean up */
+		i2sbus_control_destroy(control);
+		return -ENODEV;
+	}
+
+	dev_set_drvdata(&dev->ofdev.dev, control);
+
+	return 0;
+}
+
+static int i2sbus_remove(struct macio_dev* dev)
+{
+	struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
+	struct i2sbus_dev *i2sdev, *tmp;
+
+	list_for_each_entry_safe(i2sdev, tmp, &control->list, item)
+		soundbus_remove_one(&i2sdev->sound);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state)
+{
+	struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
+	struct codec_info_item *cii;
+	struct i2sbus_dev* i2sdev;
+	int err, ret = 0;
+
+	list_for_each_entry(i2sdev, &control->list, item) {
+		/* Notify Alsa */
+		if (i2sdev->sound.pcm) {
+			/* Suspend PCM streams */
+			snd_pcm_suspend_all(i2sdev->sound.pcm);
+		}
+
+		/* Notify codecs */
+		list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+			err = 0;
+			if (cii->codec->suspend)
+				err = cii->codec->suspend(cii, state);
+			if (err)
+				ret = err;
+		}
+
+		/* wait until streams are stopped */
+		i2sbus_wait_for_stop_both(i2sdev);
+	}
+
+	return ret;
+}
+
+static int i2sbus_resume(struct macio_dev* dev)
+{
+	struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev);
+	struct codec_info_item *cii;
+	struct i2sbus_dev* i2sdev;
+	int err, ret = 0;
+
+	list_for_each_entry(i2sdev, &control->list, item) {
+		/* reset i2s bus format etc. */
+		i2sbus_pcm_prepare_both(i2sdev);
+
+		/* Notify codecs so they can re-initialize */
+		list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+			err = 0;
+			if (cii->codec->resume)
+				err = cii->codec->resume(cii);
+			if (err)
+				ret = err;
+		}
+	}
+
+	return ret;
+}
+#endif /* CONFIG_PM */
+
+static int i2sbus_shutdown(struct macio_dev* dev)
+{
+	return 0;
+}
+
+static struct macio_driver i2sbus_drv = {
+	.driver = {
+		.name = "soundbus-i2s",
+		.owner = THIS_MODULE,
+		.of_match_table = i2sbus_match,
+	},
+	.probe = i2sbus_probe,
+	.remove = i2sbus_remove,
+#ifdef CONFIG_PM
+	.suspend = i2sbus_suspend,
+	.resume = i2sbus_resume,
+#endif
+	.shutdown = i2sbus_shutdown,
+};
+
+static int __init soundbus_i2sbus_init(void)
+{
+	return macio_register_driver(&i2sbus_drv);
+}
+
+static void __exit soundbus_i2sbus_exit(void)
+{
+	macio_unregister_driver(&i2sbus_drv);
+}
+
+module_init(soundbus_i2sbus_init);
+module_exit(soundbus_i2sbus_exit);
diff --git a/linux/sound/aoa/soundbus/i2sbus/i2sbus.h b/linux/sound/aoa/soundbus/i2sbus/i2sbus.h
new file mode 100644
index 0000000..befefd9
--- /dev/null
+++ b/linux/sound/aoa/soundbus/i2sbus/i2sbus.h
@@ -0,0 +1,126 @@
+/*
+ * i2sbus driver -- private definitions
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+#ifndef __I2SBUS_H
+#define __I2SBUS_H
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/completion.h>
+
+#include <sound/pcm.h>
+
+#include <asm/prom.h>
+#include <asm/pmac_feature.h>
+#include <asm/dbdma.h>
+
+#include "interface.h"
+#include "../soundbus.h"
+
+struct i2sbus_control {
+	struct list_head list;
+	struct macio_chip *macio;
+};
+
+#define MAX_DBDMA_COMMANDS	32
+
+struct dbdma_command_mem {
+	dma_addr_t bus_addr;
+	dma_addr_t bus_cmd_start;
+	struct dbdma_cmd *cmds;
+	void *space;
+	int size;
+	u32 running:1;
+	u32 stopping:1;
+};
+
+struct pcm_info {
+	u32 created:1, /* has this direction been created with alsa? */
+	    active:1;  /* is this stream active? */
+	/* runtime information */
+	struct snd_pcm_substream *substream;
+	int current_period;
+	u32 frame_count;
+	struct dbdma_command_mem dbdma_ring;
+	volatile struct dbdma_regs __iomem *dbdma;
+	struct completion *stop_completion;
+};
+
+enum {
+	aoa_resource_i2smmio = 0,
+	aoa_resource_txdbdma,
+	aoa_resource_rxdbdma,
+};
+
+struct i2sbus_dev {
+	struct soundbus_dev sound;
+	struct macio_dev *macio;
+	struct i2sbus_control *control;
+	volatile struct i2s_interface_regs __iomem *intfregs;
+
+	struct resource resources[3];
+	struct resource *allocated_resource[3];
+	int interrupts[3];
+	char rnames[3][32];
+
+	/* info about currently active substreams */
+	struct pcm_info out, in;
+	snd_pcm_format_t format;
+	unsigned int rate;
+
+	/* list for a single controller */
+	struct list_head item;
+	/* number of bus on controller */
+	int bus_number;
+	/* for use by control layer */
+	struct pmf_function *enable,
+			    *cell_enable,
+			    *cell_disable,
+			    *clock_enable,
+			    *clock_disable;
+
+	/* locks */
+	/* spinlock for low-level interrupt locking */
+	spinlock_t low_lock;
+	/* mutex for high-level consistency */
+	struct mutex lock;
+};
+
+#define soundbus_dev_to_i2sbus_dev(sdev) \
+		container_of(sdev, struct i2sbus_dev, sound)
+
+/* pcm specific functions */
+extern int
+i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card,
+		    struct codec_info *ci, void *data);
+extern void
+i2sbus_detach_codec(struct soundbus_dev *dev, void *data);
+extern irqreturn_t
+i2sbus_tx_intr(int irq, void *devid);
+extern irqreturn_t
+i2sbus_rx_intr(int irq, void *devid);
+
+extern void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev);
+extern void i2sbus_pcm_prepare_both(struct i2sbus_dev *i2sdev);
+
+/* control specific functions */
+extern int i2sbus_control_init(struct macio_dev* dev,
+			       struct i2sbus_control **c);
+extern void i2sbus_control_destroy(struct i2sbus_control *c);
+extern int i2sbus_control_add_dev(struct i2sbus_control *c,
+				  struct i2sbus_dev *i2sdev);
+extern void i2sbus_control_remove_dev(struct i2sbus_control *c,
+				      struct i2sbus_dev *i2sdev);
+extern int i2sbus_control_enable(struct i2sbus_control *c,
+				 struct i2sbus_dev *i2sdev);
+extern int i2sbus_control_cell(struct i2sbus_control *c,
+			       struct i2sbus_dev *i2sdev,
+			       int enable);
+extern int i2sbus_control_clock(struct i2sbus_control *c,
+				struct i2sbus_dev *i2sdev,
+				int enable);
+#endif /* __I2SBUS_H */
diff --git a/linux/sound/aoa/soundbus/i2sbus/interface.h b/linux/sound/aoa/soundbus/i2sbus/interface.h
new file mode 100644
index 0000000..c6b5f54
--- /dev/null
+++ b/linux/sound/aoa/soundbus/i2sbus/interface.h
@@ -0,0 +1,187 @@
+/*
+ * i2sbus driver -- interface register definitions
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+#ifndef __I2SBUS_INTERFACE_H
+#define __I2SBUS_INTERFACE_H
+
+/* i2s bus control registers, at least what we know about them */
+
+#define __PAD(m,n) u8 __pad##m[n]
+#define _PAD(line, n) __PAD(line, n)
+#define PAD(n) _PAD(__LINE__, (n))
+struct i2s_interface_regs {
+	__le32 intr_ctl;	/* 0x00 */
+	PAD(12);
+	__le32 serial_format;	/* 0x10 */
+	PAD(12);
+	__le32 codec_msg_out;	/* 0x20 */
+	PAD(12);
+	__le32 codec_msg_in;	/* 0x30 */
+	PAD(12);
+	__le32 frame_count;	/* 0x40 */
+	PAD(12);
+	__le32 frame_match;	/* 0x50 */
+	PAD(12);
+	__le32 data_word_sizes;	/* 0x60 */
+	PAD(12);
+	__le32 peak_level_sel;	/* 0x70 */
+	PAD(12);
+	__le32 peak_level_in0;	/* 0x80 */
+	PAD(12);
+	__le32 peak_level_in1;	/* 0x90 */
+	PAD(12);
+	/* total size: 0x100 bytes */
+}  __attribute__((__packed__));
+
+/* interrupt register is just a bitfield with
+ * interrupt enable and pending bits */
+#define I2S_REG_INTR_CTL		0x00
+#	define I2S_INT_FRAME_COUNT		(1<<31)
+#	define I2S_PENDING_FRAME_COUNT		(1<<30)
+#	define I2S_INT_MESSAGE_FLAG		(1<<29)
+#	define I2S_PENDING_MESSAGE_FLAG		(1<<28)
+#	define I2S_INT_NEW_PEAK			(1<<27)
+#	define I2S_PENDING_NEW_PEAK		(1<<26)
+#	define I2S_INT_CLOCKS_STOPPED		(1<<25)
+#	define I2S_PENDING_CLOCKS_STOPPED	(1<<24)
+#	define I2S_INT_EXTERNAL_SYNC_ERROR	(1<<23)
+#	define I2S_PENDING_EXTERNAL_SYNC_ERROR	(1<<22)
+#	define I2S_INT_EXTERNAL_SYNC_OK		(1<<21)
+#	define I2S_PENDING_EXTERNAL_SYNC_OK	(1<<20)
+#	define I2S_INT_NEW_SAMPLE_RATE		(1<<19)
+#	define I2S_PENDING_NEW_SAMPLE_RATE	(1<<18)
+#	define I2S_INT_STATUS_FLAG		(1<<17)
+#	define I2S_PENDING_STATUS_FLAG		(1<<16)
+
+/* serial format register is more interesting :)
+ * It contains:
+ *  - clock source
+ *  - MClk divisor
+ *  - SClk divisor
+ *  - SClk master flag
+ *  - serial format (sony, i2s 64x, i2s 32x, dav, silabs)
+ *  - external sample frequency interrupt (don't understand)
+ *  - external sample frequency
+ */
+#define I2S_REG_SERIAL_FORMAT		0x10
+/* clock source. You get either 18.432, 45.1584 or 49.1520 MHz */
+#	define I2S_SF_CLOCK_SOURCE_SHIFT	30
+#	define I2S_SF_CLOCK_SOURCE_MASK		(3<<I2S_SF_CLOCK_SOURCE_SHIFT)
+#	define I2S_SF_CLOCK_SOURCE_18MHz	(0<<I2S_SF_CLOCK_SOURCE_SHIFT)
+#	define I2S_SF_CLOCK_SOURCE_45MHz	(1<<I2S_SF_CLOCK_SOURCE_SHIFT)
+#	define I2S_SF_CLOCK_SOURCE_49MHz	(2<<I2S_SF_CLOCK_SOURCE_SHIFT)
+/* also, let's define the exact clock speeds here, in Hz */
+#define I2S_CLOCK_SPEED_18MHz	18432000
+#define I2S_CLOCK_SPEED_45MHz	45158400
+#define I2S_CLOCK_SPEED_49MHz	49152000
+/* MClk is the clock that drives the codec, usually called its 'system clock'.
+ * It is derived by taking only every 'divisor' tick of the clock.
+ */
+#	define I2S_SF_MCLKDIV_SHIFT		24
+#	define I2S_SF_MCLKDIV_MASK		(0x1F<<I2S_SF_MCLKDIV_SHIFT)
+#	define I2S_SF_MCLKDIV_1			(0x14<<I2S_SF_MCLKDIV_SHIFT)
+#	define I2S_SF_MCLKDIV_3			(0x13<<I2S_SF_MCLKDIV_SHIFT)
+#	define I2S_SF_MCLKDIV_5			(0x12<<I2S_SF_MCLKDIV_SHIFT)
+#	define I2S_SF_MCLKDIV_14		(0x0E<<I2S_SF_MCLKDIV_SHIFT)
+#	define I2S_SF_MCLKDIV_OTHER(div)	(((div/2-1)<<I2S_SF_MCLKDIV_SHIFT)&I2S_SF_MCLKDIV_MASK)
+static inline int i2s_sf_mclkdiv(int div, int *out)
+{
+	int d;
+
+	switch(div) {
+	case 1: *out |= I2S_SF_MCLKDIV_1; return 0;
+	case 3: *out |= I2S_SF_MCLKDIV_3; return 0;
+	case 5: *out |= I2S_SF_MCLKDIV_5; return 0;
+	case 14: *out |= I2S_SF_MCLKDIV_14; return 0;
+	default:
+		if (div%2) return -1;
+		d = div/2-1;
+		if (d == 0x14 || d == 0x13 || d == 0x12 || d == 0x0E)
+			return -1;
+		*out |= I2S_SF_MCLKDIV_OTHER(div);
+		return 0;
+	}
+}
+/* SClk is the clock that drives the i2s wire bus. Note that it is
+ * derived from the MClk above by taking only every 'divisor' tick
+ * of MClk.
+ */
+#	define I2S_SF_SCLKDIV_SHIFT		20
+#	define I2S_SF_SCLKDIV_MASK		(0xF<<I2S_SF_SCLKDIV_SHIFT)
+#	define I2S_SF_SCLKDIV_1			(8<<I2S_SF_SCLKDIV_SHIFT)
+#	define I2S_SF_SCLKDIV_3			(9<<I2S_SF_SCLKDIV_SHIFT)
+#	define I2S_SF_SCLKDIV_OTHER(div)	(((div/2-1)<<I2S_SF_SCLKDIV_SHIFT)&I2S_SF_SCLKDIV_MASK)
+static inline int i2s_sf_sclkdiv(int div, int *out)
+{
+	int d;
+
+	switch(div) {
+	case 1: *out |= I2S_SF_SCLKDIV_1; return 0;
+	case 3: *out |= I2S_SF_SCLKDIV_3; return 0;
+	default:
+		if (div%2) return -1;
+		d = div/2-1;
+		if (d == 8 || d == 9) return -1;
+		*out |= I2S_SF_SCLKDIV_OTHER(div);
+		return 0;
+	}
+}
+#	define I2S_SF_SCLK_MASTER		(1<<19)
+/* serial format is the way the data is put to the i2s wire bus */
+#	define I2S_SF_SERIAL_FORMAT_SHIFT	16
+#	define I2S_SF_SERIAL_FORMAT_MASK	(7<<I2S_SF_SERIAL_FORMAT_SHIFT)
+#	define I2S_SF_SERIAL_FORMAT_SONY	(0<<I2S_SF_SERIAL_FORMAT_SHIFT)
+#	define I2S_SF_SERIAL_FORMAT_I2S_64X	(1<<I2S_SF_SERIAL_FORMAT_SHIFT)
+#	define I2S_SF_SERIAL_FORMAT_I2S_32X	(2<<I2S_SF_SERIAL_FORMAT_SHIFT)
+#	define I2S_SF_SERIAL_FORMAT_I2S_DAV	(4<<I2S_SF_SERIAL_FORMAT_SHIFT)
+#	define I2S_SF_SERIAL_FORMAT_I2S_SILABS	(5<<I2S_SF_SERIAL_FORMAT_SHIFT)
+/* unknown */
+#	define I2S_SF_EXT_SAMPLE_FREQ_INT_SHIFT	12
+#	define I2S_SF_EXT_SAMPLE_FREQ_INT_MASK	(0xF<<I2S_SF_SAMPLE_FREQ_INT_SHIFT)
+/* probably gives external frequency? */
+#	define I2S_SF_EXT_SAMPLE_FREQ_MASK	0xFFF
+
+/* used to send codec messages, but how isn't clear */
+#define I2S_REG_CODEC_MSG_OUT		0x20
+
+/* used to receive codec messages, but how isn't clear */
+#define I2S_REG_CODEC_MSG_IN		0x30
+
+/* frame count reg isn't clear to me yet, but probably useful */
+#define I2S_REG_FRAME_COUNT		0x40
+
+/* program to some value, and get interrupt if frame count reaches it */
+#define I2S_REG_FRAME_MATCH		0x50
+
+/* this register describes how the bus transfers data */
+#define I2S_REG_DATA_WORD_SIZES		0x60
+/* number of interleaved input channels */
+#	define I2S_DWS_NUM_CHANNELS_IN_SHIFT	24
+#	define I2S_DWS_NUM_CHANNELS_IN_MASK	(0x1F<<I2S_DWS_NUM_CHANNELS_IN_SHIFT)
+/* word size of input data */
+#	define I2S_DWS_DATA_IN_SIZE_SHIFT	16
+#	define I2S_DWS_DATA_IN_16BIT		(0<<I2S_DWS_DATA_IN_SIZE_SHIFT)
+#	define I2S_DWS_DATA_IN_24BIT		(3<<I2S_DWS_DATA_IN_SIZE_SHIFT)
+/* number of interleaved output channels */
+#	define I2S_DWS_NUM_CHANNELS_OUT_SHIFT	8
+#	define I2S_DWS_NUM_CHANNELS_OUT_MASK	(0x1F<<I2S_DWS_NUM_CHANNELS_OUT_SHIFT)
+/* word size of output data */
+#	define I2S_DWS_DATA_OUT_SIZE_SHIFT	0
+#	define I2S_DWS_DATA_OUT_16BIT		(0<<I2S_DWS_DATA_OUT_SIZE_SHIFT)
+#	define I2S_DWS_DATA_OUT_24BIT		(3<<I2S_DWS_DATA_OUT_SIZE_SHIFT)
+
+
+/* unknown */
+#define I2S_REG_PEAK_LEVEL_SEL		0x70
+
+/* unknown */
+#define I2S_REG_PEAK_LEVEL_IN0		0x80
+
+/* unknown */
+#define I2S_REG_PEAK_LEVEL_IN1		0x90
+
+#endif /* __I2SBUS_INTERFACE_H */
diff --git a/linux/sound/aoa/soundbus/i2sbus/pcm.c b/linux/sound/aoa/soundbus/i2sbus/pcm.c
new file mode 100644
index 0000000..be83899
--- /dev/null
+++ b/linux/sound/aoa/soundbus/i2sbus/pcm.c
@@ -0,0 +1,1063 @@
+/*
+ * i2sbus driver -- pcm routines
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <asm/macio.h>
+#include <linux/pci.h>
+#include "../soundbus.h"
+#include "i2sbus.h"
+
+static inline void get_pcm_info(struct i2sbus_dev *i2sdev, int in,
+				struct pcm_info **pi, struct pcm_info **other)
+{
+	if (in) {
+		if (pi)
+			*pi = &i2sdev->in;
+		if (other)
+			*other = &i2sdev->out;
+	} else {
+		if (pi)
+			*pi = &i2sdev->out;
+		if (other)
+			*other = &i2sdev->in;
+	}
+}
+
+static int clock_and_divisors(int mclk, int sclk, int rate, int *out)
+{
+	/* sclk must be derived from mclk! */
+	if (mclk % sclk)
+		return -1;
+	/* derive sclk register value */
+	if (i2s_sf_sclkdiv(mclk / sclk, out))
+		return -1;
+
+	if (I2S_CLOCK_SPEED_18MHz % (rate * mclk) == 0) {
+		if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_18MHz / (rate * mclk), out)) {
+			*out |= I2S_SF_CLOCK_SOURCE_18MHz;
+			return 0;
+		}
+	}
+	if (I2S_CLOCK_SPEED_45MHz % (rate * mclk) == 0) {
+		if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_45MHz / (rate * mclk), out)) {
+			*out |= I2S_SF_CLOCK_SOURCE_45MHz;
+			return 0;
+		}
+	}
+	if (I2S_CLOCK_SPEED_49MHz % (rate * mclk) == 0) {
+		if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_49MHz / (rate * mclk), out)) {
+			*out |= I2S_SF_CLOCK_SOURCE_49MHz;
+			return 0;
+		}
+	}
+	return -1;
+}
+
+#define CHECK_RATE(rate)						\
+	do { if (rates & SNDRV_PCM_RATE_ ##rate) {			\
+		int dummy;						\
+		if (clock_and_divisors(sysclock_factor,			\
+				       bus_factor, rate, &dummy))	\
+			rates &= ~SNDRV_PCM_RATE_ ##rate;		\
+	} } while (0)
+
+static int i2sbus_pcm_open(struct i2sbus_dev *i2sdev, int in)
+{
+	struct pcm_info *pi, *other;
+	struct soundbus_dev *sdev;
+	int masks_inited = 0, err;
+	struct codec_info_item *cii, *rev;
+	struct snd_pcm_hardware *hw;
+	u64 formats = 0;
+	unsigned int rates = 0;
+	struct transfer_info v;
+	int result = 0;
+	int bus_factor = 0, sysclock_factor = 0;
+	int found_this;
+
+	mutex_lock(&i2sdev->lock);
+
+	get_pcm_info(i2sdev, in, &pi, &other);
+
+	hw = &pi->substream->runtime->hw;
+	sdev = &i2sdev->sound;
+
+	if (pi->active) {
+		/* alsa messed up */
+		result = -EBUSY;
+		goto out_unlock;
+	}
+
+	/* we now need to assign the hw */
+	list_for_each_entry(cii, &sdev->codec_list, list) {
+		struct transfer_info *ti = cii->codec->transfers;
+		bus_factor = cii->codec->bus_factor;
+		sysclock_factor = cii->codec->sysclock_factor;
+		while (ti->formats && ti->rates) {
+			v = *ti;
+			if (ti->transfer_in == in
+			    && cii->codec->usable(cii, ti, &v)) {
+				if (masks_inited) {
+					formats &= v.formats;
+					rates &= v.rates;
+				} else {
+					formats = v.formats;
+					rates = v.rates;
+					masks_inited = 1;
+				}
+			}
+			ti++;
+		}
+	}
+	if (!masks_inited || !bus_factor || !sysclock_factor) {
+		result = -ENODEV;
+		goto out_unlock;
+	}
+	/* bus dependent stuff */
+	hw->info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+		   SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME |
+		   SNDRV_PCM_INFO_JOINT_DUPLEX;
+
+	CHECK_RATE(5512);
+	CHECK_RATE(8000);
+	CHECK_RATE(11025);
+	CHECK_RATE(16000);
+	CHECK_RATE(22050);
+	CHECK_RATE(32000);
+	CHECK_RATE(44100);
+	CHECK_RATE(48000);
+	CHECK_RATE(64000);
+	CHECK_RATE(88200);
+	CHECK_RATE(96000);
+	CHECK_RATE(176400);
+	CHECK_RATE(192000);
+	hw->rates = rates;
+
+	/* well. the codec might want 24 bits only, and we'll
+	 * ever only transfer 24 bits, but they are top-aligned!
+	 * So for alsa, we claim that we're doing full 32 bit
+	 * while in reality we'll ignore the lower 8 bits of
+	 * that when doing playback (they're transferred as 0
+	 * as far as I know, no codecs we have are 32-bit capable
+	 * so I can't really test) and when doing recording we'll
+	 * always have those lower 8 bits recorded as 0 */
+	if (formats & SNDRV_PCM_FMTBIT_S24_BE)
+		formats |= SNDRV_PCM_FMTBIT_S32_BE;
+	if (formats & SNDRV_PCM_FMTBIT_U24_BE)
+		formats |= SNDRV_PCM_FMTBIT_U32_BE;
+	/* now mask off what we can support. I suppose we could
+	 * also support S24_3LE and some similar formats, but I
+	 * doubt there's a codec that would be able to use that,
+	 * so we don't support it here. */
+	hw->formats = formats & (SNDRV_PCM_FMTBIT_S16_BE |
+				 SNDRV_PCM_FMTBIT_U16_BE |
+				 SNDRV_PCM_FMTBIT_S32_BE |
+				 SNDRV_PCM_FMTBIT_U32_BE);
+
+	/* we need to set the highest and lowest rate possible.
+	 * These are the highest and lowest rates alsa can
+	 * support properly in its bitfield.
+	 * Below, we'll use that to restrict to the rate
+	 * currently in use (if any). */
+	hw->rate_min = 5512;
+	hw->rate_max = 192000;
+	/* if the other stream is active, then we can only
+	 * support what it is currently using.
+	 * FIXME: I lied. This comment is wrong. We can support
+	 * anything that works with the same serial format, ie.
+	 * when recording 24 bit sound we can well play 16 bit
+	 * sound at the same time iff using the same transfer mode.
+	 */
+	if (other->active) {
+		/* FIXME: is this guaranteed by the alsa api? */
+		hw->formats &= (1ULL << i2sdev->format);
+		/* see above, restrict rates to the one we already have */
+		hw->rate_min = i2sdev->rate;
+		hw->rate_max = i2sdev->rate;
+	}
+
+	hw->channels_min = 2;
+	hw->channels_max = 2;
+	/* these are somewhat arbitrary */
+	hw->buffer_bytes_max = 131072;
+	hw->period_bytes_min = 256;
+	hw->period_bytes_max = 16384;
+	hw->periods_min = 3;
+	hw->periods_max = MAX_DBDMA_COMMANDS;
+	err = snd_pcm_hw_constraint_integer(pi->substream->runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0) {
+		result = err;
+		goto out_unlock;
+	}
+	list_for_each_entry(cii, &sdev->codec_list, list) {
+		if (cii->codec->open) {
+			err = cii->codec->open(cii, pi->substream);
+			if (err) {
+				result = err;
+				/* unwind */
+				found_this = 0;
+				list_for_each_entry_reverse(rev,
+				    &sdev->codec_list, list) {
+					if (found_this && rev->codec->close) {
+						rev->codec->close(rev,
+								pi->substream);
+					}
+					if (rev == cii)
+						found_this = 1;
+				}
+				goto out_unlock;
+			}
+		}
+	}
+
+ out_unlock:
+	mutex_unlock(&i2sdev->lock);
+	return result;
+}
+
+#undef CHECK_RATE
+
+static int i2sbus_pcm_close(struct i2sbus_dev *i2sdev, int in)
+{
+	struct codec_info_item *cii;
+	struct pcm_info *pi;
+	int err = 0, tmp;
+
+	mutex_lock(&i2sdev->lock);
+
+	get_pcm_info(i2sdev, in, &pi, NULL);
+
+	list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+		if (cii->codec->close) {
+			tmp = cii->codec->close(cii, pi->substream);
+			if (tmp)
+				err = tmp;
+		}
+	}
+
+	pi->substream = NULL;
+	pi->active = 0;
+	mutex_unlock(&i2sdev->lock);
+	return err;
+}
+
+static void i2sbus_wait_for_stop(struct i2sbus_dev *i2sdev,
+				 struct pcm_info *pi)
+{
+	unsigned long flags;
+	struct completion done;
+	long timeout;
+
+	spin_lock_irqsave(&i2sdev->low_lock, flags);
+	if (pi->dbdma_ring.stopping) {
+		init_completion(&done);
+		pi->stop_completion = &done;
+		spin_unlock_irqrestore(&i2sdev->low_lock, flags);
+		timeout = wait_for_completion_timeout(&done, HZ);
+		spin_lock_irqsave(&i2sdev->low_lock, flags);
+		pi->stop_completion = NULL;
+		if (timeout == 0) {
+			/* timeout expired, stop dbdma forcefully */
+			printk(KERN_ERR "i2sbus_wait_for_stop: timed out\n");
+			/* make sure RUN, PAUSE and S0 bits are cleared */
+			out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
+			pi->dbdma_ring.stopping = 0;
+			timeout = 10;
+			while (in_le32(&pi->dbdma->status) & ACTIVE) {
+				if (--timeout <= 0)
+					break;
+				udelay(1);
+			}
+		}
+	}
+	spin_unlock_irqrestore(&i2sdev->low_lock, flags);
+}
+
+#ifdef CONFIG_PM
+void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev)
+{
+	struct pcm_info *pi;
+
+	get_pcm_info(i2sdev, 0, &pi, NULL);
+	i2sbus_wait_for_stop(i2sdev, pi);
+	get_pcm_info(i2sdev, 1, &pi, NULL);
+	i2sbus_wait_for_stop(i2sdev, pi);
+}
+#endif
+
+static int i2sbus_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+}
+
+static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+	struct pcm_info *pi;
+
+	get_pcm_info(i2sdev, in, &pi, NULL);
+	if (pi->dbdma_ring.stopping)
+		i2sbus_wait_for_stop(i2sdev, pi);
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int i2sbus_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	return i2sbus_hw_free(substream, 0);
+}
+
+static int i2sbus_record_hw_free(struct snd_pcm_substream *substream)
+{
+	return i2sbus_hw_free(substream, 1);
+}
+
+static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in)
+{
+	/* whee. Hard work now. The user has selected a bitrate
+	 * and bit format, so now we have to program our
+	 * I2S controller appropriately. */
+	struct snd_pcm_runtime *runtime;
+	struct dbdma_cmd *command;
+	int i, periodsize, nperiods;
+	dma_addr_t offset;
+	struct bus_info bi;
+	struct codec_info_item *cii;
+	int sfr = 0;		/* serial format register */
+	int dws = 0;		/* data word sizes reg */
+	int input_16bit;
+	struct pcm_info *pi, *other;
+	int cnt;
+	int result = 0;
+	unsigned int cmd, stopaddr;
+
+	mutex_lock(&i2sdev->lock);
+
+	get_pcm_info(i2sdev, in, &pi, &other);
+
+	if (pi->dbdma_ring.running) {
+		result = -EBUSY;
+		goto out_unlock;
+	}
+	if (pi->dbdma_ring.stopping)
+		i2sbus_wait_for_stop(i2sdev, pi);
+
+	if (!pi->substream || !pi->substream->runtime) {
+		result = -EINVAL;
+		goto out_unlock;
+	}
+
+	runtime = pi->substream->runtime;
+	pi->active = 1;
+	if (other->active &&
+	    ((i2sdev->format != runtime->format)
+	     || (i2sdev->rate != runtime->rate))) {
+		result = -EINVAL;
+		goto out_unlock;
+	}
+
+	i2sdev->format = runtime->format;
+	i2sdev->rate = runtime->rate;
+
+	periodsize = snd_pcm_lib_period_bytes(pi->substream);
+	nperiods = pi->substream->runtime->periods;
+	pi->current_period = 0;
+
+	/* generate dbdma command ring first */
+	command = pi->dbdma_ring.cmds;
+	memset(command, 0, (nperiods + 2) * sizeof(struct dbdma_cmd));
+
+	/* commands to DMA to/from the ring */
+	/*
+	 * For input, we need to do a graceful stop; if we abort
+	 * the DMA, we end up with leftover bytes that corrupt
+	 * the next recording.  To do this we set the S0 status
+	 * bit and wait for the DMA controller to stop.  Each
+	 * command has a branch condition to
+	 * make it branch to a stop command if S0 is set.
+	 * On input we also need to wait for the S7 bit to be
+	 * set before turning off the DMA controller.
+	 * In fact we do the graceful stop for output as well.
+	 */
+	offset = runtime->dma_addr;
+	cmd = (in? INPUT_MORE: OUTPUT_MORE) | BR_IFSET | INTR_ALWAYS;
+	stopaddr = pi->dbdma_ring.bus_cmd_start +
+		(nperiods + 1) * sizeof(struct dbdma_cmd);
+	for (i = 0; i < nperiods; i++, command++, offset += periodsize) {
+		command->command = cpu_to_le16(cmd);
+		command->cmd_dep = cpu_to_le32(stopaddr);
+		command->phy_addr = cpu_to_le32(offset);
+		command->req_count = cpu_to_le16(periodsize);
+	}
+
+	/* branch back to beginning of ring */
+	command->command = cpu_to_le16(DBDMA_NOP | BR_ALWAYS);
+	command->cmd_dep = cpu_to_le32(pi->dbdma_ring.bus_cmd_start);
+	command++;
+
+	/* set stop command */
+	command->command = cpu_to_le16(DBDMA_STOP);
+
+	/* ok, let's set the serial format and stuff */
+	switch (runtime->format) {
+	/* 16 bit formats */
+	case SNDRV_PCM_FORMAT_S16_BE:
+	case SNDRV_PCM_FORMAT_U16_BE:
+		/* FIXME: if we add different bus factors we need to
+		 * do more here!! */
+		bi.bus_factor = 0;
+		list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+			bi.bus_factor = cii->codec->bus_factor;
+			break;
+		}
+		if (!bi.bus_factor) {
+			result = -ENODEV;
+			goto out_unlock;
+		}
+		input_16bit = 1;
+		break;
+	case SNDRV_PCM_FORMAT_S32_BE:
+	case SNDRV_PCM_FORMAT_U32_BE:
+		/* force 64x bus speed, otherwise the data cannot be
+		 * transferred quickly enough! */
+		bi.bus_factor = 64;
+		input_16bit = 0;
+		break;
+	default:
+		result = -EINVAL;
+		goto out_unlock;
+	}
+	/* we assume all sysclocks are the same! */
+	list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+		bi.sysclock_factor = cii->codec->sysclock_factor;
+		break;
+	}
+
+	if (clock_and_divisors(bi.sysclock_factor,
+			       bi.bus_factor,
+			       runtime->rate,
+			       &sfr) < 0) {
+		result = -EINVAL;
+		goto out_unlock;
+	}
+	switch (bi.bus_factor) {
+	case 32:
+		sfr |= I2S_SF_SERIAL_FORMAT_I2S_32X;
+		break;
+	case 64:
+		sfr |= I2S_SF_SERIAL_FORMAT_I2S_64X;
+		break;
+	}
+	/* FIXME: THIS ASSUMES MASTER ALL THE TIME */
+	sfr |= I2S_SF_SCLK_MASTER;
+
+	list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
+		int err = 0;
+		if (cii->codec->prepare)
+			err = cii->codec->prepare(cii, &bi, pi->substream);
+		if (err) {
+			result = err;
+			goto out_unlock;
+		}
+	}
+	/* codecs are fine with it, so set our clocks */
+	if (input_16bit)
+		dws =	(2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) |
+			(2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) |
+			I2S_DWS_DATA_IN_16BIT | I2S_DWS_DATA_OUT_16BIT;
+	else
+		dws =	(2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) |
+			(2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) |
+			I2S_DWS_DATA_IN_24BIT | I2S_DWS_DATA_OUT_24BIT;
+
+	/* early exit if already programmed correctly */
+	/* not locking these is fine since we touch them only in this function */
+	if (in_le32(&i2sdev->intfregs->serial_format) == sfr
+	 && in_le32(&i2sdev->intfregs->data_word_sizes) == dws)
+		goto out_unlock;
+
+	/* let's notify the codecs about clocks going away.
+	 * For now we only do mastering on the i2s cell... */
+	list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+		if (cii->codec->switch_clock)
+			cii->codec->switch_clock(cii, CLOCK_SWITCH_PREPARE_SLAVE);
+
+	i2sbus_control_enable(i2sdev->control, i2sdev);
+	i2sbus_control_cell(i2sdev->control, i2sdev, 1);
+
+	out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED);
+
+	i2sbus_control_clock(i2sdev->control, i2sdev, 0);
+
+	msleep(1);
+
+	/* wait for clock stopped. This can apparently take a while... */
+	cnt = 100;
+	while (cnt-- &&
+	    !(in_le32(&i2sdev->intfregs->intr_ctl) & I2S_PENDING_CLOCKS_STOPPED)) {
+		msleep(5);
+	}
+	out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED);
+
+	/* not locking these is fine since we touch them only in this function */
+	out_le32(&i2sdev->intfregs->serial_format, sfr);
+	out_le32(&i2sdev->intfregs->data_word_sizes, dws);
+
+        i2sbus_control_enable(i2sdev->control, i2sdev);
+        i2sbus_control_cell(i2sdev->control, i2sdev, 1);
+        i2sbus_control_clock(i2sdev->control, i2sdev, 1);
+	msleep(1);
+
+	list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+		if (cii->codec->switch_clock)
+			cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE);
+
+ out_unlock:
+	mutex_unlock(&i2sdev->lock);
+	return result;
+}
+
+#ifdef CONFIG_PM
+void i2sbus_pcm_prepare_both(struct i2sbus_dev *i2sdev)
+{
+	i2sbus_pcm_prepare(i2sdev, 0);
+	i2sbus_pcm_prepare(i2sdev, 1);
+}
+#endif
+
+static int i2sbus_pcm_trigger(struct i2sbus_dev *i2sdev, int in, int cmd)
+{
+	struct codec_info_item *cii;
+	struct pcm_info *pi;
+	int result = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2sdev->low_lock, flags);
+
+	get_pcm_info(i2sdev, in, &pi, NULL);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (pi->dbdma_ring.running) {
+			result = -EALREADY;
+			goto out_unlock;
+		}
+		list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+			if (cii->codec->start)
+				cii->codec->start(cii, pi->substream);
+		pi->dbdma_ring.running = 1;
+
+		if (pi->dbdma_ring.stopping) {
+			/* Clear the S0 bit, then see if we stopped yet */
+			out_le32(&pi->dbdma->control, 1 << 16);
+			if (in_le32(&pi->dbdma->status) & ACTIVE) {
+				/* possible race here? */
+				udelay(10);
+				if (in_le32(&pi->dbdma->status) & ACTIVE) {
+					pi->dbdma_ring.stopping = 0;
+					goto out_unlock; /* keep running */
+				}
+			}
+		}
+
+		/* make sure RUN, PAUSE and S0 bits are cleared */
+		out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
+
+		/* set branch condition select register */
+		out_le32(&pi->dbdma->br_sel, (1 << 16) | 1);
+
+		/* write dma command buffer address to the dbdma chip */
+		out_le32(&pi->dbdma->cmdptr, pi->dbdma_ring.bus_cmd_start);
+
+		/* initialize the frame count and current period */
+		pi->current_period = 0;
+		pi->frame_count = in_le32(&i2sdev->intfregs->frame_count);
+
+		/* set the DMA controller running */
+		out_le32(&pi->dbdma->control, (RUN << 16) | RUN);
+
+		/* off you go! */
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (!pi->dbdma_ring.running) {
+			result = -EALREADY;
+			goto out_unlock;
+		}
+		pi->dbdma_ring.running = 0;
+
+		/* Set the S0 bit to make the DMA branch to the stop cmd */
+		out_le32(&pi->dbdma->control, (1 << 16) | 1);
+		pi->dbdma_ring.stopping = 1;
+
+		list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
+			if (cii->codec->stop)
+				cii->codec->stop(cii, pi->substream);
+		break;
+	default:
+		result = -EINVAL;
+		goto out_unlock;
+	}
+
+ out_unlock:
+	spin_unlock_irqrestore(&i2sdev->low_lock, flags);
+	return result;
+}
+
+static snd_pcm_uframes_t i2sbus_pcm_pointer(struct i2sbus_dev *i2sdev, int in)
+{
+	struct pcm_info *pi;
+	u32 fc;
+
+	get_pcm_info(i2sdev, in, &pi, NULL);
+
+	fc = in_le32(&i2sdev->intfregs->frame_count);
+	fc = fc - pi->frame_count;
+
+	if (fc >= pi->substream->runtime->buffer_size)
+		fc %= pi->substream->runtime->buffer_size;
+	return fc;
+}
+
+static inline void handle_interrupt(struct i2sbus_dev *i2sdev, int in)
+{
+	struct pcm_info *pi;
+	u32 fc, nframes;
+	u32 status;
+	int timeout, i;
+	int dma_stopped = 0;
+	struct snd_pcm_runtime *runtime;
+
+	spin_lock(&i2sdev->low_lock);
+	get_pcm_info(i2sdev, in, &pi, NULL);
+	if (!pi->dbdma_ring.running && !pi->dbdma_ring.stopping)
+		goto out_unlock;
+
+	i = pi->current_period;
+	runtime = pi->substream->runtime;
+	while (pi->dbdma_ring.cmds[i].xfer_status) {
+		if (le16_to_cpu(pi->dbdma_ring.cmds[i].xfer_status) & BT)
+			/*
+			 * BT is the branch taken bit.  If it took a branch
+			 * it is because we set the S0 bit to make it
+			 * branch to the stop command.
+			 */
+			dma_stopped = 1;
+		pi->dbdma_ring.cmds[i].xfer_status = 0;
+
+		if (++i >= runtime->periods) {
+			i = 0;
+			pi->frame_count += runtime->buffer_size;
+		}
+		pi->current_period = i;
+
+		/*
+		 * Check the frame count.  The DMA tends to get a bit
+		 * ahead of the frame counter, which confuses the core.
+		 */
+		fc = in_le32(&i2sdev->intfregs->frame_count);
+		nframes = i * runtime->period_size;
+		if (fc < pi->frame_count + nframes)
+			pi->frame_count = fc - nframes;
+	}
+
+	if (dma_stopped) {
+		timeout = 1000;
+		for (;;) {
+			status = in_le32(&pi->dbdma->status);
+			if (!(status & ACTIVE) && (!in || (status & 0x80)))
+				break;
+			if (--timeout <= 0) {
+				printk(KERN_ERR "i2sbus: timed out "
+				       "waiting for DMA to stop!\n");
+				break;
+			}
+			udelay(1);
+		}
+
+		/* Turn off DMA controller, clear S0 bit */
+		out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
+
+		pi->dbdma_ring.stopping = 0;
+		if (pi->stop_completion)
+			complete(pi->stop_completion);
+	}
+
+	if (!pi->dbdma_ring.running)
+		goto out_unlock;
+	spin_unlock(&i2sdev->low_lock);
+	/* may call _trigger again, hence needs to be unlocked */
+	snd_pcm_period_elapsed(pi->substream);
+	return;
+
+ out_unlock:
+	spin_unlock(&i2sdev->low_lock);
+}
+
+irqreturn_t i2sbus_tx_intr(int irq, void *devid)
+{
+	handle_interrupt((struct i2sbus_dev *)devid, 0);
+	return IRQ_HANDLED;
+}
+
+irqreturn_t i2sbus_rx_intr(int irq, void *devid)
+{
+	handle_interrupt((struct i2sbus_dev *)devid, 1);
+	return IRQ_HANDLED;
+}
+
+static int i2sbus_playback_open(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	i2sdev->out.substream = substream;
+	return i2sbus_pcm_open(i2sdev, 0);
+}
+
+static int i2sbus_playback_close(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+	int err;
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->out.substream != substream)
+		return -EINVAL;
+	err = i2sbus_pcm_close(i2sdev, 0);
+	if (!err)
+		i2sdev->out.substream = NULL;
+	return err;
+}
+
+static int i2sbus_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->out.substream != substream)
+		return -EINVAL;
+	return i2sbus_pcm_prepare(i2sdev, 0);
+}
+
+static int i2sbus_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->out.substream != substream)
+		return -EINVAL;
+	return i2sbus_pcm_trigger(i2sdev, 0, cmd);
+}
+
+static snd_pcm_uframes_t i2sbus_playback_pointer(struct snd_pcm_substream
+						 *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->out.substream != substream)
+		return 0;
+	return i2sbus_pcm_pointer(i2sdev, 0);
+}
+
+static struct snd_pcm_ops i2sbus_playback_ops = {
+	.open =		i2sbus_playback_open,
+	.close =	i2sbus_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	i2sbus_hw_params,
+	.hw_free =	i2sbus_playback_hw_free,
+	.prepare =	i2sbus_playback_prepare,
+	.trigger =	i2sbus_playback_trigger,
+	.pointer =	i2sbus_playback_pointer,
+};
+
+static int i2sbus_record_open(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	i2sdev->in.substream = substream;
+	return i2sbus_pcm_open(i2sdev, 1);
+}
+
+static int i2sbus_record_close(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+	int err;
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->in.substream != substream)
+		return -EINVAL;
+	err = i2sbus_pcm_close(i2sdev, 1);
+	if (!err)
+		i2sdev->in.substream = NULL;
+	return err;
+}
+
+static int i2sbus_record_prepare(struct snd_pcm_substream *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->in.substream != substream)
+		return -EINVAL;
+	return i2sbus_pcm_prepare(i2sdev, 1);
+}
+
+static int i2sbus_record_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->in.substream != substream)
+		return -EINVAL;
+	return i2sbus_pcm_trigger(i2sdev, 1, cmd);
+}
+
+static snd_pcm_uframes_t i2sbus_record_pointer(struct snd_pcm_substream
+					       *substream)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
+
+	if (!i2sdev)
+		return -EINVAL;
+	if (i2sdev->in.substream != substream)
+		return 0;
+	return i2sbus_pcm_pointer(i2sdev, 1);
+}
+
+static struct snd_pcm_ops i2sbus_record_ops = {
+	.open =		i2sbus_record_open,
+	.close =	i2sbus_record_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	i2sbus_hw_params,
+	.hw_free =	i2sbus_record_hw_free,
+	.prepare =	i2sbus_record_prepare,
+	.trigger =	i2sbus_record_trigger,
+	.pointer =	i2sbus_record_pointer,
+};
+
+static void i2sbus_private_free(struct snd_pcm *pcm)
+{
+	struct i2sbus_dev *i2sdev = snd_pcm_chip(pcm);
+	struct codec_info_item *p, *tmp;
+
+	i2sdev->sound.pcm = NULL;
+	i2sdev->out.created = 0;
+	i2sdev->in.created = 0;
+	list_for_each_entry_safe(p, tmp, &i2sdev->sound.codec_list, list) {
+		printk(KERN_ERR "i2sbus: a codec didn't unregister!\n");
+		list_del(&p->list);
+		module_put(p->codec->owner);
+		kfree(p);
+	}
+	soundbus_dev_put(&i2sdev->sound);
+	module_put(THIS_MODULE);
+}
+
+int
+i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card,
+		    struct codec_info *ci, void *data)
+{
+	int err, in = 0, out = 0;
+	struct transfer_info *tmp;
+	struct i2sbus_dev *i2sdev = soundbus_dev_to_i2sbus_dev(dev);
+	struct codec_info_item *cii;
+
+	if (!dev->pcmname || dev->pcmid == -1) {
+		printk(KERN_ERR "i2sbus: pcm name and id must be set!\n");
+		return -EINVAL;
+	}
+
+	list_for_each_entry(cii, &dev->codec_list, list) {
+		if (cii->codec_data == data)
+			return -EALREADY;
+	}
+
+	if (!ci->transfers || !ci->transfers->formats
+	    || !ci->transfers->rates || !ci->usable)
+		return -EINVAL;
+
+	/* we currently code the i2s transfer on the clock, and support only
+	 * 32 and 64 */
+	if (ci->bus_factor != 32 && ci->bus_factor != 64)
+		return -EINVAL;
+
+	/* If you want to fix this, you need to keep track of what transport infos
+	 * are to be used, which codecs they belong to, and then fix all the
+	 * sysclock/busclock stuff above to depend on which is usable */
+	list_for_each_entry(cii, &dev->codec_list, list) {
+		if (cii->codec->sysclock_factor != ci->sysclock_factor) {
+			printk(KERN_DEBUG
+			       "cannot yet handle multiple different sysclocks!\n");
+			return -EINVAL;
+		}
+		if (cii->codec->bus_factor != ci->bus_factor) {
+			printk(KERN_DEBUG
+			       "cannot yet handle multiple different bus clocks!\n");
+			return -EINVAL;
+		}
+	}
+
+	tmp = ci->transfers;
+	while (tmp->formats && tmp->rates) {
+		if (tmp->transfer_in)
+			in = 1;
+		else
+			out = 1;
+		tmp++;
+	}
+
+	cii = kzalloc(sizeof(struct codec_info_item), GFP_KERNEL);
+	if (!cii) {
+		printk(KERN_DEBUG "i2sbus: failed to allocate cii\n");
+		return -ENOMEM;
+	}
+
+	/* use the private data to point to the codec info */
+	cii->sdev = soundbus_dev_get(dev);
+	cii->codec = ci;
+	cii->codec_data = data;
+
+	if (!cii->sdev) {
+		printk(KERN_DEBUG
+		       "i2sbus: failed to get soundbus dev reference\n");
+		err = -ENODEV;
+		goto out_free_cii;
+	}
+
+	if (!try_module_get(THIS_MODULE)) {
+		printk(KERN_DEBUG "i2sbus: failed to get module reference!\n");
+		err = -EBUSY;
+		goto out_put_sdev;
+	}
+
+	if (!try_module_get(ci->owner)) {
+		printk(KERN_DEBUG
+		       "i2sbus: failed to get module reference to codec owner!\n");
+		err = -EBUSY;
+		goto out_put_this_module;
+	}
+
+	if (!dev->pcm) {
+		err = snd_pcm_new(card, dev->pcmname, dev->pcmid, 0, 0,
+				  &dev->pcm);
+		if (err) {
+			printk(KERN_DEBUG "i2sbus: failed to create pcm\n");
+			goto out_put_ci_module;
+		}
+		dev->pcm->dev = &dev->ofdev.dev;
+	}
+
+	/* ALSA yet again sucks.
+	 * If it is ever fixed, remove this line. See below. */
+	out = in = 1;
+
+	if (!i2sdev->out.created && out) {
+		if (dev->pcm->card != card) {
+			/* eh? */
+			printk(KERN_ERR
+			       "Can't attach same bus to different cards!\n");
+			err = -EINVAL;
+			goto out_put_ci_module;
+		}
+		err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, 1);
+		if (err)
+			goto out_put_ci_module;
+		snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				&i2sbus_playback_ops);
+		i2sdev->out.created = 1;
+	}
+
+	if (!i2sdev->in.created && in) {
+		if (dev->pcm->card != card) {
+			printk(KERN_ERR
+			       "Can't attach same bus to different cards!\n");
+			err = -EINVAL;
+			goto out_put_ci_module;
+		}
+		err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, 1);
+		if (err)
+			goto out_put_ci_module;
+		snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&i2sbus_record_ops);
+		i2sdev->in.created = 1;
+	}
+
+	/* so we have to register the pcm after adding any substream
+	 * to it because alsa doesn't create the devices for the
+	 * substreams when we add them later.
+	 * Therefore, force in and out on both busses (above) and
+	 * register the pcm now instead of just after creating it.
+	 */
+	err = snd_device_register(card, dev->pcm);
+	if (err) {
+		printk(KERN_ERR "i2sbus: error registering new pcm\n");
+		goto out_put_ci_module;
+	}
+	/* no errors any more, so let's add this to our list */
+	list_add(&cii->list, &dev->codec_list);
+
+	dev->pcm->private_data = i2sdev;
+	dev->pcm->private_free = i2sbus_private_free;
+
+	/* well, we really should support scatter/gather DMA */
+	snd_pcm_lib_preallocate_pages_for_all(
+		dev->pcm, SNDRV_DMA_TYPE_DEV,
+		snd_dma_pci_data(macio_get_pci_dev(i2sdev->macio)),
+		64 * 1024, 64 * 1024);
+
+	return 0;
+ out_put_ci_module:
+	module_put(ci->owner);
+ out_put_this_module:
+	module_put(THIS_MODULE);
+ out_put_sdev:
+	soundbus_dev_put(dev);
+ out_free_cii:
+	kfree(cii);
+	return err;
+}
+
+void i2sbus_detach_codec(struct soundbus_dev *dev, void *data)
+{
+	struct codec_info_item *cii = NULL, *i;
+
+	list_for_each_entry(i, &dev->codec_list, list) {
+		if (i->codec_data == data) {
+			cii = i;
+			break;
+		}
+	}
+	if (cii) {
+		list_del(&cii->list);
+		module_put(cii->codec->owner);
+		kfree(cii);
+	}
+	/* no more codecs, but still a pcm? */
+	if (list_empty(&dev->codec_list) && dev->pcm) {
+		/* the actual cleanup is done by the callback above! */
+		snd_device_free(dev->pcm->card, dev->pcm);
+	}
+}
diff --git a/linux/sound/aoa/soundbus/soundbus.h b/linux/sound/aoa/soundbus/soundbus.h
new file mode 100644
index 0000000..adecbf3
--- /dev/null
+++ b/linux/sound/aoa/soundbus/soundbus.h
@@ -0,0 +1,204 @@
+/*
+ * soundbus generic definitions
+ *
+ * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * GPL v2, can be found in COPYING.
+ */
+#ifndef __SOUNDBUS_H
+#define __SOUNDBUS_H
+
+#include <linux/of_device.h>
+#include <sound/pcm.h>
+#include <linux/list.h>
+
+
+/* When switching from master to slave or the other way around,
+ * you don't want to have the codec chip acting as clock source
+ * while the bus still is.
+ * More importantly, while switch from slave to master, you need
+ * to turn off the chip's master function first, but then there's
+ * no clock for a while and other chips might reset, so we notify
+ * their drivers after having switched.
+ * The constants here are codec-point of view, so when we switch
+ * the soundbus to master we tell the codec we're going to switch
+ * and give it CLOCK_SWITCH_PREPARE_SLAVE!
+ */
+enum clock_switch {
+	CLOCK_SWITCH_PREPARE_SLAVE,
+	CLOCK_SWITCH_PREPARE_MASTER,
+	CLOCK_SWITCH_SLAVE,
+	CLOCK_SWITCH_MASTER,
+	CLOCK_SWITCH_NOTIFY,
+};
+
+/* information on a transfer the codec can take */
+struct transfer_info {
+	u64 formats;		/* SNDRV_PCM_FMTBIT_* */
+	unsigned int rates;	/* SNDRV_PCM_RATE_* */
+	/* flags */
+	u32 transfer_in:1, /* input = 1, output = 0 */
+	    must_be_clock_source:1;
+	/* for codecs to distinguish among their TIs */
+	int tag;
+};
+
+struct codec_info_item {
+	struct codec_info *codec;
+	void *codec_data;
+	struct soundbus_dev *sdev;
+	/* internal, to be used by the soundbus provider */
+	struct list_head list;
+};
+
+/* for prepare, where the codecs need to know
+ * what we're going to drive the bus with */
+struct bus_info {
+	/* see below */
+	int sysclock_factor;
+	int bus_factor;
+};
+
+/* information on the codec itself, plus function pointers */
+struct codec_info {
+	/* the module this lives in */
+	struct module *owner;
+
+	/* supported transfer possibilities, array terminated by
+	 * formats or rates being 0. */
+	struct transfer_info *transfers;
+
+	/* Master clock speed factor
+	 * to be used (master clock speed = sysclock_factor * sampling freq)
+	 * Unused if the soundbus provider has no such notion.
+	 */
+	int sysclock_factor;
+
+	/* Bus factor, bus clock speed = bus_factor * sampling freq)
+	 * Unused if the soundbus provider has no such notion.
+	 */
+	int bus_factor;
+
+	/* operations */
+	/* clock switching, see above */
+	int (*switch_clock)(struct codec_info_item *cii,
+			    enum clock_switch clock);
+
+	/* called for each transfer_info when the user
+	 * opens the pcm device to determine what the
+	 * hardware can support at this point in time.
+	 * That can depend on other user-switchable controls.
+	 * Return 1 if usable, 0 if not.
+	 * out points to another instance of a transfer_info
+	 * which is initialised to the values in *ti, and
+	 * it's format and rate values can be modified by
+	 * the callback if it is necessary to further restrict
+	 * the formats that can be used at the moment, for
+	 * example when one codec has multiple logical codec
+	 * info structs for multiple inputs.
+	 */
+	int (*usable)(struct codec_info_item *cii,
+		      struct transfer_info *ti,
+		      struct transfer_info *out);
+
+	/* called when pcm stream is opened, probably not implemented
+	 * most of the time since it isn't too useful */
+	int (*open)(struct codec_info_item *cii,
+		    struct snd_pcm_substream *substream);
+
+	/* called when the pcm stream is closed, at this point
+	 * the user choices can all be unlocked (see below) */
+	int (*close)(struct codec_info_item *cii,
+		     struct snd_pcm_substream *substream);
+
+	/* if the codec must forbid some user choices because
+	 * they are not valid with the substream/transfer info,
+	 * it must do so here. Example: no digital output for
+	 * incompatible framerate, say 8KHz, on Onyx.
+	 * If the selected stuff in the substream is NOT
+	 * compatible, you have to reject this call! */
+	int (*prepare)(struct codec_info_item *cii,
+		       struct bus_info *bi,
+		       struct snd_pcm_substream *substream);
+
+	/* start() is called before data is pushed to the codec.
+	 * Note that start() must be atomic! */
+	int (*start)(struct codec_info_item *cii,
+		     struct snd_pcm_substream *substream);
+
+	/* stop() is called after data is no longer pushed to the codec.
+	 * Note that stop() must be atomic! */
+	int (*stop)(struct codec_info_item *cii,
+		    struct snd_pcm_substream *substream);
+
+	int (*suspend)(struct codec_info_item *cii, pm_message_t state);
+	int (*resume)(struct codec_info_item *cii);
+};
+
+/* information on a soundbus device */
+struct soundbus_dev {
+	/* the bus it belongs to */
+	struct list_head onbuslist;
+
+	/* the of device it represents */
+	struct platform_device ofdev;
+
+	/* what modules go by */
+	char modalias[32];
+
+	/* These fields must be before attach_codec can be called.
+	 * They should be set by the owner of the alsa card object
+	 * that is needed, and whoever sets them must make sure
+	 * that they are unique within that alsa card object. */
+	char *pcmname;
+	int pcmid;
+
+	/* this is assigned by the soundbus provider in attach_codec */
+	struct snd_pcm *pcm;
+
+	/* operations */
+	/* attach a codec to this soundbus, give the alsa
+	 * card object the PCMs for this soundbus should be in.
+	 * The 'data' pointer must be unique, it is used as the
+	 * key for detach_codec(). */
+	int (*attach_codec)(struct soundbus_dev *dev, struct snd_card *card,
+			    struct codec_info *ci, void *data);
+	void (*detach_codec)(struct soundbus_dev *dev, void *data);
+	/* TODO: suspend/resume */
+
+	/* private for the soundbus provider */
+	struct list_head codec_list;
+	u32 have_out:1, have_in:1;
+};
+#define to_soundbus_device(d) container_of(d, struct soundbus_dev, ofdev.dev)
+#define of_to_soundbus_device(d) container_of(d, struct soundbus_dev, ofdev)
+
+extern int soundbus_add_one(struct soundbus_dev *dev);
+extern void soundbus_remove_one(struct soundbus_dev *dev);
+
+extern struct soundbus_dev *soundbus_dev_get(struct soundbus_dev *dev);
+extern void soundbus_dev_put(struct soundbus_dev *dev);
+
+struct soundbus_driver {
+	char *name;
+	struct module *owner;
+
+	/* we don't implement any matching at all */
+
+	int	(*probe)(struct soundbus_dev* dev);
+	int	(*remove)(struct soundbus_dev* dev);
+
+	int	(*suspend)(struct soundbus_dev* dev, pm_message_t state);
+	int	(*resume)(struct soundbus_dev* dev);
+	int	(*shutdown)(struct soundbus_dev* dev);
+
+	struct device_driver driver;
+};
+#define to_soundbus_driver(drv) container_of(drv,struct soundbus_driver, driver)
+
+extern int soundbus_register_driver(struct soundbus_driver *drv);
+extern void soundbus_unregister_driver(struct soundbus_driver *drv);
+
+extern struct device_attribute soundbus_dev_attrs[];
+
+#endif /* __SOUNDBUS_H */
diff --git a/linux/sound/aoa/soundbus/sysfs.c b/linux/sound/aoa/soundbus/sysfs.c
new file mode 100644
index 0000000..e0980b5
--- /dev/null
+++ b/linux/sound/aoa/soundbus/sysfs.c
@@ -0,0 +1,42 @@
+#include <linux/kernel.h>
+#include <linux/stat.h>
+/* FIX UP */
+#include "soundbus.h"
+
+#define soundbus_config_of_attr(field, format_string)			\
+static ssize_t								\
+field##_show (struct device *dev, struct device_attribute *attr,	\
+              char *buf)						\
+{									\
+	struct soundbus_dev *mdev = to_soundbus_device (dev);		\
+	return sprintf (buf, format_string, mdev->ofdev.dev.of_node->field); \
+}
+
+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct soundbus_dev *sdev = to_soundbus_device(dev);
+	struct platform_device *of = &sdev->ofdev;
+	int length;
+
+	if (*sdev->modalias) {
+		strlcpy(buf, sdev->modalias, sizeof(sdev->modalias) + 1);
+		strcat(buf, "\n");
+		length = strlen(buf);
+	} else {
+		length = sprintf(buf, "of:N%sT%s\n",
+				 of->dev.of_node->name, of->dev.of_node->type);
+	}
+
+	return length;
+}
+
+soundbus_config_of_attr (name, "%s\n");
+soundbus_config_of_attr (type, "%s\n");
+
+struct device_attribute soundbus_dev_attrs[] = {
+	__ATTR_RO(name),
+	__ATTR_RO(type),
+	__ATTR_RO(modalias),
+	__ATTR_NULL
+};
diff --git a/linux/sound/arm/Kconfig b/linux/sound/arm/Kconfig
new file mode 100644
index 0000000..885683a
--- /dev/null
+++ b/linux/sound/arm/Kconfig
@@ -0,0 +1,43 @@
+# ALSA ARM drivers
+
+menuconfig SND_ARM
+	bool "ARM sound devices"
+	depends on ARM
+	default y
+	help
+	  Support for sound devices specific to ARM architectures.
+	  Drivers that are implemented on ASoC can be found in
+	  "ALSA for SoC audio support" section.
+
+if SND_ARM
+
+config SND_ARMAACI
+	tristate "ARM PrimeCell PL041 AC Link support"
+	depends on ARM_AMBA
+	select SND_PCM
+	select SND_AC97_CODEC
+
+config SND_PXA2XX_PCM
+	tristate
+	select SND_PCM
+
+config SND_PXA2XX_LIB
+	tristate
+	select SND_AC97_CODEC if SND_PXA2XX_LIB_AC97
+
+config SND_PXA2XX_LIB_AC97
+	bool
+
+config SND_PXA2XX_AC97
+	tristate "AC97 driver for the Intel PXA2xx chip"
+	depends on ARCH_PXA
+	select SND_PXA2XX_PCM
+	select SND_AC97_CODEC
+	select SND_PXA2XX_LIB
+	select SND_PXA2XX_LIB_AC97
+	help
+	  Say Y or M if you want to support any AC97 codec attached to
+	  the PXA2xx AC97 interface.
+
+endif	# SND_ARM
+
diff --git a/linux/sound/arm/Makefile b/linux/sound/arm/Makefile
new file mode 100644
index 0000000..8c0c851
--- /dev/null
+++ b/linux/sound/arm/Makefile
@@ -0,0 +1,16 @@
+#
+# Makefile for ALSA
+#
+
+obj-$(CONFIG_SND_ARMAACI)	+= snd-aaci.o
+snd-aaci-objs			:= aaci.o
+
+obj-$(CONFIG_SND_PXA2XX_PCM)	+= snd-pxa2xx-pcm.o
+snd-pxa2xx-pcm-objs		:= pxa2xx-pcm.o
+
+obj-$(CONFIG_SND_PXA2XX_LIB)	+= snd-pxa2xx-lib.o
+snd-pxa2xx-lib-y		:= pxa2xx-pcm-lib.o
+snd-pxa2xx-lib-$(CONFIG_SND_PXA2XX_LIB_AC97)	+= pxa2xx-ac97-lib.o
+
+obj-$(CONFIG_SND_PXA2XX_AC97)	+= snd-pxa2xx-ac97.o
+snd-pxa2xx-ac97-objs		:= pxa2xx-ac97.o
diff --git a/linux/sound/arm/aaci.c b/linux/sound/arm/aaci.c
new file mode 100644
index 0000000..91acc9a
--- /dev/null
+++ b/linux/sound/arm/aaci.c
@@ -0,0 +1,1140 @@
+/*
+ *  linux/sound/arm/aaci.c - ARM PrimeCell AACI PL041 driver
+ *
+ *  Copyright (C) 2003 Deep Blue Solutions Ltd, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  Documentation: ARM DDI 0173B
+ */
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/amba/bus.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/ac97_codec.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "aaci.h"
+
+#define DRIVER_NAME	"aaci-pl041"
+
+/*
+ * PM support is not complete.  Turn it off.
+ */
+#undef CONFIG_PM
+
+static void aaci_ac97_select_codec(struct aaci *aaci, struct snd_ac97 *ac97)
+{
+	u32 v, maincr = aaci->maincr | MAINCR_SCRA(ac97->num);
+
+	/*
+	 * Ensure that the slot 1/2 RX registers are empty.
+	 */
+	v = readl(aaci->base + AACI_SLFR);
+	if (v & SLFR_2RXV)
+		readl(aaci->base + AACI_SL2RX);
+	if (v & SLFR_1RXV)
+		readl(aaci->base + AACI_SL1RX);
+
+	writel(maincr, aaci->base + AACI_MAINCR);
+}
+
+/*
+ * P29:
+ *  The recommended use of programming the external codec through slot 1
+ *  and slot 2 data is to use the channels during setup routines and the
+ *  slot register at any other time.  The data written into slot 1, slot 2
+ *  and slot 12 registers is transmitted only when their corresponding
+ *  SI1TxEn, SI2TxEn and SI12TxEn bits are set in the AACI_MAINCR
+ *  register.
+ */
+static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+			    unsigned short val)
+{
+	struct aaci *aaci = ac97->private_data;
+	u32 v;
+	int timeout = 5000;
+
+	if (ac97->num >= 4)
+		return;
+
+	mutex_lock(&aaci->ac97_sem);
+
+	aaci_ac97_select_codec(aaci, ac97);
+
+	/*
+	 * P54: You must ensure that AACI_SL2TX is always written
+	 * to, if required, before data is written to AACI_SL1TX.
+	 */
+	writel(val << 4, aaci->base + AACI_SL2TX);
+	writel(reg << 12, aaci->base + AACI_SL1TX);
+
+	/*
+	 * Wait for the transmission of both slots to complete.
+	 */
+	do {
+		v = readl(aaci->base + AACI_SLFR);
+	} while ((v & (SLFR_1TXB|SLFR_2TXB)) && --timeout);
+
+	if (!timeout)
+		dev_err(&aaci->dev->dev,
+			"timeout waiting for write to complete\n");
+
+	mutex_unlock(&aaci->ac97_sem);
+}
+
+/*
+ * Read an AC'97 register.
+ */
+static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct aaci *aaci = ac97->private_data;
+	u32 v;
+	int timeout = 5000;
+	int retries = 10;
+
+	if (ac97->num >= 4)
+		return ~0;
+
+	mutex_lock(&aaci->ac97_sem);
+
+	aaci_ac97_select_codec(aaci, ac97);
+
+	/*
+	 * Write the register address to slot 1.
+	 */
+	writel((reg << 12) | (1 << 19), aaci->base + AACI_SL1TX);
+
+	/*
+	 * Wait for the transmission to complete.
+	 */
+	do {
+		v = readl(aaci->base + AACI_SLFR);
+	} while ((v & SLFR_1TXB) && --timeout);
+
+	if (!timeout) {
+		dev_err(&aaci->dev->dev, "timeout on slot 1 TX busy\n");
+		v = ~0;
+		goto out;
+	}
+
+	/*
+	 * Give the AC'97 codec more than enough time
+	 * to respond. (42us = ~2 frames at 48kHz.)
+	 */
+	udelay(42);
+
+	/*
+	 * Wait for slot 2 to indicate data.
+	 */
+	timeout = 5000;
+	do {
+		cond_resched();
+		v = readl(aaci->base + AACI_SLFR) & (SLFR_1RXV|SLFR_2RXV);
+	} while ((v != (SLFR_1RXV|SLFR_2RXV)) && --timeout);
+
+	if (!timeout) {
+		dev_err(&aaci->dev->dev, "timeout on RX valid\n");
+		v = ~0;
+		goto out;
+	}
+
+	do {
+		v = readl(aaci->base + AACI_SL1RX) >> 12;
+		if (v == reg) {
+			v = readl(aaci->base + AACI_SL2RX) >> 4;
+			break;
+		} else if (--retries) {
+			dev_warn(&aaci->dev->dev,
+				 "ac97 read back fail.  retry\n");
+			continue;
+		} else {
+			dev_warn(&aaci->dev->dev,
+				"wrong ac97 register read back (%x != %x)\n",
+				v, reg);
+			v = ~0;
+		}
+	} while (retries);
+ out:
+	mutex_unlock(&aaci->ac97_sem);
+	return v;
+}
+
+static inline void
+aaci_chan_wait_ready(struct aaci_runtime *aacirun, unsigned long mask)
+{
+	u32 val;
+	int timeout = 5000;
+
+	do {
+		val = readl(aacirun->base + AACI_SR);
+	} while (val & mask && timeout--);
+}
+
+
+
+/*
+ * Interrupt support.
+ */
+static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
+{
+	if (mask & ISR_ORINTR) {
+		dev_warn(&aaci->dev->dev, "RX overrun on chan %d\n", channel);
+		writel(ICLR_RXOEC1 << channel, aaci->base + AACI_INTCLR);
+	}
+
+	if (mask & ISR_RXTOINTR) {
+		dev_warn(&aaci->dev->dev, "RX timeout on chan %d\n", channel);
+		writel(ICLR_RXTOFEC1 << channel, aaci->base + AACI_INTCLR);
+	}
+
+	if (mask & ISR_RXINTR) {
+		struct aaci_runtime *aacirun = &aaci->capture;
+		void *ptr;
+
+		if (!aacirun->substream || !aacirun->start) {
+			dev_warn(&aaci->dev->dev, "RX interrupt???\n");
+			writel(0, aacirun->base + AACI_IE);
+			return;
+		}
+
+		spin_lock(&aacirun->lock);
+
+		ptr = aacirun->ptr;
+		do {
+			unsigned int len = aacirun->fifosz;
+			u32 val;
+
+			if (aacirun->bytes <= 0) {
+				aacirun->bytes += aacirun->period;
+				aacirun->ptr = ptr;
+				spin_unlock(&aacirun->lock);
+				snd_pcm_period_elapsed(aacirun->substream);
+				spin_lock(&aacirun->lock);
+			}
+			if (!(aacirun->cr & CR_EN))
+				break;
+
+			val = readl(aacirun->base + AACI_SR);
+			if (!(val & SR_RXHF))
+				break;
+			if (!(val & SR_RXFF))
+				len >>= 1;
+
+			aacirun->bytes -= len;
+
+			/* reading 16 bytes at a time */
+			for( ; len > 0; len -= 16) {
+				asm(
+					"ldmia	%1, {r0, r1, r2, r3}\n\t"
+					"stmia	%0!, {r0, r1, r2, r3}"
+					: "+r" (ptr)
+					: "r" (aacirun->fifo)
+					: "r0", "r1", "r2", "r3", "cc");
+
+				if (ptr >= aacirun->end)
+					ptr = aacirun->start;
+			}
+		} while(1);
+
+		aacirun->ptr = ptr;
+
+		spin_unlock(&aacirun->lock);
+	}
+
+	if (mask & ISR_URINTR) {
+		dev_dbg(&aaci->dev->dev, "TX underrun on chan %d\n", channel);
+		writel(ICLR_TXUEC1 << channel, aaci->base + AACI_INTCLR);
+	}
+
+	if (mask & ISR_TXINTR) {
+		struct aaci_runtime *aacirun = &aaci->playback;
+		void *ptr;
+
+		if (!aacirun->substream || !aacirun->start) {
+			dev_warn(&aaci->dev->dev, "TX interrupt???\n");
+			writel(0, aacirun->base + AACI_IE);
+			return;
+		}
+
+		spin_lock(&aacirun->lock);
+
+		ptr = aacirun->ptr;
+		do {
+			unsigned int len = aacirun->fifosz;
+			u32 val;
+
+			if (aacirun->bytes <= 0) {
+				aacirun->bytes += aacirun->period;
+				aacirun->ptr = ptr;
+				spin_unlock(&aacirun->lock);
+				snd_pcm_period_elapsed(aacirun->substream);
+				spin_lock(&aacirun->lock);
+			}
+			if (!(aacirun->cr & CR_EN))
+				break;
+
+			val = readl(aacirun->base + AACI_SR);
+			if (!(val & SR_TXHE))
+				break;
+			if (!(val & SR_TXFE))
+				len >>= 1;
+
+			aacirun->bytes -= len;
+
+			/* writing 16 bytes at a time */
+			for ( ; len > 0; len -= 16) {
+				asm(
+					"ldmia	%0!, {r0, r1, r2, r3}\n\t"
+					"stmia	%1, {r0, r1, r2, r3}"
+					: "+r" (ptr)
+					: "r" (aacirun->fifo)
+					: "r0", "r1", "r2", "r3", "cc");
+
+				if (ptr >= aacirun->end)
+					ptr = aacirun->start;
+			}
+		} while (1);
+
+		aacirun->ptr = ptr;
+
+		spin_unlock(&aacirun->lock);
+	}
+}
+
+static irqreturn_t aaci_irq(int irq, void *devid)
+{
+	struct aaci *aaci = devid;
+	u32 mask;
+	int i;
+
+	mask = readl(aaci->base + AACI_ALLINTS);
+	if (mask) {
+		u32 m = mask;
+		for (i = 0; i < 4; i++, m >>= 7) {
+			if (m & 0x7f) {
+				aaci_fifo_irq(aaci, i, m);
+			}
+		}
+	}
+
+	return mask ? IRQ_HANDLED : IRQ_NONE;
+}
+
+
+
+/*
+ * ALSA support.
+ */
+static struct snd_pcm_hardware aaci_hw_info = {
+	.info			= SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				  SNDRV_PCM_INFO_RESUME,
+
+	/*
+	 * ALSA doesn't support 18-bit or 20-bit packed into 32-bit
+	 * words.  It also doesn't support 12-bit at all.
+	 */
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE,
+
+	/* rates are setup from the AC'97 codec */
+	.channels_min		= 2,
+	.channels_max		= 6,
+	.buffer_bytes_max	= 64 * 1024,
+	.period_bytes_min	= 256,
+	.period_bytes_max	= PAGE_SIZE,
+	.periods_min		= 4,
+	.periods_max		= PAGE_SIZE / 16,
+};
+
+static int __aaci_pcm_open(struct aaci *aaci,
+			   struct snd_pcm_substream *substream,
+			   struct aaci_runtime *aacirun)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int ret;
+
+	aacirun->substream = substream;
+	runtime->private_data = aacirun;
+	runtime->hw = aaci_hw_info;
+	runtime->hw.rates = aacirun->pcm->rates;
+	snd_pcm_limit_hw_rates(runtime);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+	    aacirun->pcm->r[1].slots)
+		snd_ac97_pcm_double_rate_rules(runtime);
+
+	/*
+	 * FIXME: ALSA specifies fifo_size in bytes.  If we're in normal
+	 * mode, each 32-bit word contains one sample.  If we're in
+	 * compact mode, each 32-bit word contains two samples, effectively
+	 * halving the FIFO size.  However, we don't know for sure which
+	 * we'll be using at this point.  We set this to the lower limit.
+	 */
+	runtime->hw.fifo_size = aaci->fifosize * 2;
+
+	ret = request_irq(aaci->dev->irq[0], aaci_irq, IRQF_SHARED|IRQF_DISABLED,
+			  DRIVER_NAME, aaci);
+	if (ret)
+		goto out;
+
+	return 0;
+
+ out:
+	return ret;
+}
+
+
+/*
+ * Common ALSA stuff
+ */
+static int aaci_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct aaci *aaci = substream->private_data;
+	struct aaci_runtime *aacirun = substream->runtime->private_data;
+
+	WARN_ON(aacirun->cr & CR_EN);
+
+	aacirun->substream = NULL;
+	free_irq(aaci->dev->irq[0], aaci);
+
+	return 0;
+}
+
+static int aaci_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct aaci_runtime *aacirun = substream->runtime->private_data;
+
+	/*
+	 * This must not be called with the device enabled.
+	 */
+	WARN_ON(aacirun->cr & CR_EN);
+
+	if (aacirun->pcm_open)
+		snd_ac97_pcm_close(aacirun->pcm);
+	aacirun->pcm_open = 0;
+
+	/*
+	 * Clear out the DMA and any allocated buffers.
+	 */
+	snd_pcm_lib_free_pages(substream);
+
+	return 0;
+}
+
+static int aaci_pcm_hw_params(struct snd_pcm_substream *substream,
+			      struct aaci_runtime *aacirun,
+			      struct snd_pcm_hw_params *params)
+{
+	int err;
+	struct aaci *aaci = substream->private_data;
+
+	aaci_pcm_hw_free(substream);
+	if (aacirun->pcm_open) {
+		snd_ac97_pcm_close(aacirun->pcm);
+		aacirun->pcm_open = 0;
+	}
+
+	err = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(params));
+	if (err >= 0) {
+		unsigned int rate = params_rate(params);
+		int dbl = rate > 48000;
+
+		err = snd_ac97_pcm_open(aacirun->pcm, rate,
+					params_channels(params),
+					aacirun->pcm->r[dbl].slots);
+
+		aacirun->pcm_open = err == 0;
+		aacirun->cr = CR_FEN | CR_COMPACT | CR_SZ16;
+		aacirun->fifosz = aaci->fifosize * 4;
+
+		if (aacirun->cr & CR_COMPACT)
+			aacirun->fifosz >>= 1;
+	}
+
+	return err;
+}
+
+static int aaci_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct aaci_runtime *aacirun = runtime->private_data;
+
+	aacirun->start	= runtime->dma_area;
+	aacirun->end	= aacirun->start + snd_pcm_lib_buffer_bytes(substream);
+	aacirun->ptr	= aacirun->start;
+	aacirun->period	=
+	aacirun->bytes	= frames_to_bytes(runtime, runtime->period_size);
+
+	return 0;
+}
+
+static snd_pcm_uframes_t aaci_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct aaci_runtime *aacirun = runtime->private_data;
+	ssize_t bytes = aacirun->ptr - aacirun->start;
+
+	return bytes_to_frames(runtime, bytes);
+}
+
+
+/*
+ * Playback specific ALSA stuff
+ */
+static const u32 channels_to_txmask[] = {
+	[2] = CR_SL3 | CR_SL4,
+	[4] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8,
+	[6] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8 | CR_SL6 | CR_SL9,
+};
+
+/*
+ * We can support two and four channel audio.  Unfortunately
+ * six channel audio requires a non-standard channel ordering:
+ *   2 -> FL(3), FR(4)
+ *   4 -> FL(3), FR(4), SL(7), SR(8)
+ *   6 -> FL(3), FR(4), SL(7), SR(8), C(6), LFE(9) (required)
+ *        FL(3), FR(4), C(6), SL(7), SR(8), LFE(9) (actual)
+ * This requires an ALSA configuration file to correct.
+ */
+static unsigned int channel_list[] = { 2, 4, 6 };
+
+static int
+aaci_rule_channels(struct snd_pcm_hw_params *p, struct snd_pcm_hw_rule *rule)
+{
+	struct aaci *aaci = rule->private;
+	unsigned int chan_mask = 1 << 0, slots;
+
+	/*
+	 * pcms[0] is the our 5.1 PCM instance.
+	 */
+	slots = aaci->ac97_bus->pcms[0].r[0].slots;
+	if (slots & (1 << AC97_SLOT_PCM_SLEFT)) {
+		chan_mask |= 1 << 1;
+		if (slots & (1 << AC97_SLOT_LFE))
+			chan_mask |= 1 << 2;
+	}
+
+	return snd_interval_list(hw_param_interval(p, rule->var),
+				 ARRAY_SIZE(channel_list), channel_list,
+				 chan_mask);
+}
+
+static int aaci_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct aaci *aaci = substream->private_data;
+	int ret;
+
+	/*
+	 * Add rule describing channel dependency.
+	 */
+	ret = snd_pcm_hw_rule_add(substream->runtime, 0,
+				  SNDRV_PCM_HW_PARAM_CHANNELS,
+				  aaci_rule_channels, aaci,
+				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (ret)
+		return ret;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		ret = __aaci_pcm_open(aaci, substream, &aaci->playback);
+	} else {
+		ret = __aaci_pcm_open(aaci, substream, &aaci->capture);
+	}
+	return ret;
+}
+
+static int aaci_pcm_playback_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *params)
+{
+	struct aaci_runtime *aacirun = substream->runtime->private_data;
+	unsigned int channels = params_channels(params);
+	int ret;
+
+	WARN_ON(channels >= ARRAY_SIZE(channels_to_txmask) ||
+		!channels_to_txmask[channels]);
+
+	ret = aaci_pcm_hw_params(substream, aacirun, params);
+
+	/*
+	 * Enable FIFO, compact mode, 16 bits per sample.
+	 * FIXME: double rate slots?
+	 */
+	if (ret >= 0)
+		aacirun->cr |= channels_to_txmask[channels];
+
+	return ret;
+}
+
+static void aaci_pcm_playback_stop(struct aaci_runtime *aacirun)
+{
+	u32 ie;
+
+	ie = readl(aacirun->base + AACI_IE);
+	ie &= ~(IE_URIE|IE_TXIE);
+	writel(ie, aacirun->base + AACI_IE);
+	aacirun->cr &= ~CR_EN;
+	aaci_chan_wait_ready(aacirun, SR_TXB);
+	writel(aacirun->cr, aacirun->base + AACI_TXCR);
+}
+
+static void aaci_pcm_playback_start(struct aaci_runtime *aacirun)
+{
+	u32 ie;
+
+	aaci_chan_wait_ready(aacirun, SR_TXB);
+	aacirun->cr |= CR_EN;
+
+	ie = readl(aacirun->base + AACI_IE);
+	ie |= IE_URIE | IE_TXIE;
+	writel(ie, aacirun->base + AACI_IE);
+	writel(aacirun->cr, aacirun->base + AACI_TXCR);
+}
+
+static int aaci_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct aaci_runtime *aacirun = substream->runtime->private_data;
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&aacirun->lock, flags);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		aaci_pcm_playback_start(aacirun);
+		break;
+
+	case SNDRV_PCM_TRIGGER_RESUME:
+		aaci_pcm_playback_start(aacirun);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		aaci_pcm_playback_stop(aacirun);
+		break;
+
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		aaci_pcm_playback_stop(aacirun);
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	spin_unlock_irqrestore(&aacirun->lock, flags);
+
+	return ret;
+}
+
+static struct snd_pcm_ops aaci_playback_ops = {
+	.open		= aaci_pcm_open,
+	.close		= aaci_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= aaci_pcm_playback_hw_params,
+	.hw_free	= aaci_pcm_hw_free,
+	.prepare	= aaci_pcm_prepare,
+	.trigger	= aaci_pcm_playback_trigger,
+	.pointer	= aaci_pcm_pointer,
+};
+
+static int aaci_pcm_capture_hw_params(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *params)
+{
+	struct aaci_runtime *aacirun = substream->runtime->private_data;
+	int ret;
+
+	ret = aaci_pcm_hw_params(substream, aacirun, params);
+	if (ret >= 0)
+		/* Line in record: slot 3 and 4 */
+		aacirun->cr |= CR_SL3 | CR_SL4;
+
+	return ret;
+}
+
+static void aaci_pcm_capture_stop(struct aaci_runtime *aacirun)
+{
+	u32 ie;
+
+	aaci_chan_wait_ready(aacirun, SR_RXB);
+
+	ie = readl(aacirun->base + AACI_IE);
+	ie &= ~(IE_ORIE | IE_RXIE);
+	writel(ie, aacirun->base+AACI_IE);
+
+	aacirun->cr &= ~CR_EN;
+
+	writel(aacirun->cr, aacirun->base + AACI_RXCR);
+}
+
+static void aaci_pcm_capture_start(struct aaci_runtime *aacirun)
+{
+	u32 ie;
+
+	aaci_chan_wait_ready(aacirun, SR_RXB);
+
+#ifdef DEBUG
+	/* RX Timeout value: bits 28:17 in RXCR */
+	aacirun->cr |= 0xf << 17;
+#endif
+
+	aacirun->cr |= CR_EN;
+	writel(aacirun->cr, aacirun->base + AACI_RXCR);
+
+	ie = readl(aacirun->base + AACI_IE);
+	ie |= IE_ORIE |IE_RXIE; // overrun and rx interrupt -- half full
+	writel(ie, aacirun->base + AACI_IE);
+}
+
+static int aaci_pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct aaci_runtime *aacirun = substream->runtime->private_data;
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&aacirun->lock, flags);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		aaci_pcm_capture_start(aacirun);
+		break;
+
+	case SNDRV_PCM_TRIGGER_RESUME:
+		aaci_pcm_capture_start(aacirun);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		aaci_pcm_capture_stop(aacirun);
+		break;
+
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		aaci_pcm_capture_stop(aacirun);
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	spin_unlock_irqrestore(&aacirun->lock, flags);
+
+	return ret;
+}
+
+static int aaci_pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct aaci *aaci = substream->private_data;
+
+	aaci_pcm_prepare(substream);
+
+	/* allow changing of sample rate */
+	aaci_ac97_write(aaci->ac97, AC97_EXTENDED_STATUS, 0x0001); /* VRA */
+	aaci_ac97_write(aaci->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate);
+	aaci_ac97_write(aaci->ac97, AC97_PCM_MIC_ADC_RATE, runtime->rate);
+
+	/* Record select: Mic: 0, Aux: 3, Line: 4 */
+	aaci_ac97_write(aaci->ac97, AC97_REC_SEL, 0x0404);
+
+	return 0;
+}
+
+static struct snd_pcm_ops aaci_capture_ops = {
+	.open		= aaci_pcm_open,
+	.close		= aaci_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= aaci_pcm_capture_hw_params,
+	.hw_free	= aaci_pcm_hw_free,
+	.prepare	= aaci_pcm_capture_prepare,
+	.trigger	= aaci_pcm_capture_trigger,
+	.pointer	= aaci_pcm_pointer,
+};
+
+/*
+ * Power Management.
+ */
+#ifdef CONFIG_PM
+static int aaci_do_suspend(struct snd_card *card, unsigned int state)
+{
+	struct aaci *aaci = card->private_data;
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3cold);
+	snd_pcm_suspend_all(aaci->pcm);
+	return 0;
+}
+
+static int aaci_do_resume(struct snd_card *card, unsigned int state)
+{
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+
+static int aaci_suspend(struct amba_device *dev, pm_message_t state)
+{
+	struct snd_card *card = amba_get_drvdata(dev);
+	return card ? aaci_do_suspend(card) : 0;
+}
+
+static int aaci_resume(struct amba_device *dev)
+{
+	struct snd_card *card = amba_get_drvdata(dev);
+	return card ? aaci_do_resume(card) : 0;
+}
+#else
+#define aaci_do_suspend		NULL
+#define aaci_do_resume		NULL
+#define aaci_suspend		NULL
+#define aaci_resume		NULL
+#endif
+
+
+static struct ac97_pcm ac97_defs[] __devinitdata = {
+	[0] = {	/* Front PCM */
+		.exclusive = 1,
+		.r = {
+			[0] = {
+				.slots	= (1 << AC97_SLOT_PCM_LEFT) |
+					  (1 << AC97_SLOT_PCM_RIGHT) |
+					  (1 << AC97_SLOT_PCM_CENTER) |
+					  (1 << AC97_SLOT_PCM_SLEFT) |
+					  (1 << AC97_SLOT_PCM_SRIGHT) |
+					  (1 << AC97_SLOT_LFE),
+			},
+			[1] = {
+				.slots	= (1 << AC97_SLOT_PCM_LEFT) |
+					  (1 << AC97_SLOT_PCM_RIGHT) |
+					  (1 << AC97_SLOT_PCM_LEFT_0) |
+					  (1 << AC97_SLOT_PCM_RIGHT_0),
+			},
+		},
+	},
+	[1] = {	/* PCM in */
+		.stream = 1,
+		.exclusive = 1,
+		.r = {
+			[0] = {
+				.slots	= (1 << AC97_SLOT_PCM_LEFT) |
+					  (1 << AC97_SLOT_PCM_RIGHT),
+			},
+		},
+	},
+	[2] = {	/* Mic in */
+		.stream = 1,
+		.exclusive = 1,
+		.r = {
+			[0] = {
+				.slots	= (1 << AC97_SLOT_MIC),
+			},
+		},
+	}
+};
+
+static struct snd_ac97_bus_ops aaci_bus_ops = {
+	.write	= aaci_ac97_write,
+	.read	= aaci_ac97_read,
+};
+
+static int __devinit aaci_probe_ac97(struct aaci *aaci)
+{
+	struct snd_ac97_template ac97_template;
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97 *ac97;
+	int ret;
+
+	/*
+	 * Assert AACIRESET for 2us
+	 */
+	writel(0, aaci->base + AACI_RESET);
+	udelay(2);
+	writel(RESET_NRST, aaci->base + AACI_RESET);
+
+	/*
+	 * Give the AC'97 codec more than enough time
+	 * to wake up. (42us = ~2 frames at 48kHz.)
+	 */
+	udelay(42);
+
+	ret = snd_ac97_bus(aaci->card, 0, &aaci_bus_ops, aaci, &ac97_bus);
+	if (ret)
+		goto out;
+
+	ac97_bus->clock = 48000;
+	aaci->ac97_bus = ac97_bus;
+
+	memset(&ac97_template, 0, sizeof(struct snd_ac97_template));
+	ac97_template.private_data = aaci;
+	ac97_template.num = 0;
+	ac97_template.scaps = AC97_SCAP_SKIP_MODEM;
+
+	ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97);
+	if (ret)
+		goto out;
+	aaci->ac97 = ac97;
+
+	/*
+	 * Disable AC97 PC Beep input on audio codecs.
+	 */
+	if (ac97_is_audio(ac97))
+		snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x801e);
+
+	ret = snd_ac97_pcm_assign(ac97_bus, ARRAY_SIZE(ac97_defs), ac97_defs);
+	if (ret)
+		goto out;
+
+	aaci->playback.pcm = &ac97_bus->pcms[0];
+	aaci->capture.pcm  = &ac97_bus->pcms[1];
+
+ out:
+	return ret;
+}
+
+static void aaci_free_card(struct snd_card *card)
+{
+	struct aaci *aaci = card->private_data;
+	if (aaci->base)
+		iounmap(aaci->base);
+}
+
+static struct aaci * __devinit aaci_init_card(struct amba_device *dev)
+{
+	struct aaci *aaci;
+	struct snd_card *card;
+	int err;
+
+	err = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+			      THIS_MODULE, sizeof(struct aaci), &card);
+	if (err < 0)
+		return NULL;
+
+	card->private_free = aaci_free_card;
+
+	strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver));
+	strlcpy(card->shortname, "ARM AC'97 Interface", sizeof(card->shortname));
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s at 0x%016llx, irq %d",
+		 card->shortname, (unsigned long long)dev->res.start,
+		 dev->irq[0]);
+
+	aaci = card->private_data;
+	mutex_init(&aaci->ac97_sem);
+	aaci->card = card;
+	aaci->dev = dev;
+
+	/* Set MAINCR to allow slot 1 and 2 data IO */
+	aaci->maincr = MAINCR_IE | MAINCR_SL1RXEN | MAINCR_SL1TXEN |
+		       MAINCR_SL2RXEN | MAINCR_SL2TXEN;
+
+	return aaci;
+}
+
+static int __devinit aaci_init_pcm(struct aaci *aaci)
+{
+	struct snd_pcm *pcm;
+	int ret;
+
+	ret = snd_pcm_new(aaci->card, "AACI AC'97", 0, 1, 1, &pcm);
+	if (ret == 0) {
+		aaci->pcm = pcm;
+		pcm->private_data = aaci;
+		pcm->info_flags = 0;
+
+		strlcpy(pcm->name, DRIVER_NAME, sizeof(pcm->name));
+
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &aaci_playback_ops);
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &aaci_capture_ops);
+		snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						      NULL, 0, 64 * 1024);
+	}
+
+	return ret;
+}
+
+static unsigned int __devinit aaci_size_fifo(struct aaci *aaci)
+{
+	struct aaci_runtime *aacirun = &aaci->playback;
+	int i;
+
+	writel(CR_FEN | CR_SZ16 | CR_EN, aacirun->base + AACI_TXCR);
+
+	for (i = 0; !(readl(aacirun->base + AACI_SR) & SR_TXFF) && i < 4096; i++)
+		writel(0, aacirun->fifo);
+
+	writel(0, aacirun->base + AACI_TXCR);
+
+	/*
+	 * Re-initialise the AACI after the FIFO depth test, to
+	 * ensure that the FIFOs are empty.  Unfortunately, merely
+	 * disabling the channel doesn't clear the FIFO.
+	 */
+	writel(aaci->maincr & ~MAINCR_IE, aaci->base + AACI_MAINCR);
+	writel(aaci->maincr, aaci->base + AACI_MAINCR);
+
+	/*
+	 * If we hit 4096, we failed.  Go back to the specified
+	 * fifo depth.
+	 */
+	if (i == 4096)
+		i = 8;
+
+	return i;
+}
+
+static int __devinit aaci_probe(struct amba_device *dev, struct amba_id *id)
+{
+	struct aaci *aaci;
+	int ret, i;
+
+	ret = amba_request_regions(dev, NULL);
+	if (ret)
+		return ret;
+
+	aaci = aaci_init_card(dev);
+	if (!aaci) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	aaci->base = ioremap(dev->res.start, resource_size(&dev->res));
+	if (!aaci->base) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	/*
+	 * Playback uses AACI channel 0
+	 */
+	spin_lock_init(&aaci->playback.lock);
+	aaci->playback.base = aaci->base + AACI_CSCH1;
+	aaci->playback.fifo = aaci->base + AACI_DR1;
+
+	/*
+	 * Capture uses AACI channel 0
+	 */
+	spin_lock_init(&aaci->capture.lock);
+	aaci->capture.base = aaci->base + AACI_CSCH1;
+	aaci->capture.fifo = aaci->base + AACI_DR1;
+
+	for (i = 0; i < 4; i++) {
+		void __iomem *base = aaci->base + i * 0x14;
+
+		writel(0, base + AACI_IE);
+		writel(0, base + AACI_TXCR);
+		writel(0, base + AACI_RXCR);
+	}
+
+	writel(0x1fff, aaci->base + AACI_INTCLR);
+	writel(aaci->maincr, aaci->base + AACI_MAINCR);
+	/*
+	 * Fix: ac97 read back fail errors by reading
+	 * from any arbitrary aaci register.
+	 */
+	readl(aaci->base + AACI_CSCH1);
+	ret = aaci_probe_ac97(aaci);
+	if (ret)
+		goto out;
+
+	/*
+	 * Size the FIFOs (must be multiple of 16).
+	 */
+	aaci->fifosize = aaci_size_fifo(aaci);
+	if (aaci->fifosize & 15) {
+		printk(KERN_WARNING "AACI: fifosize = %d not supported\n",
+		       aaci->fifosize);
+		ret = -ENODEV;
+		goto out;
+	}
+
+	ret = aaci_init_pcm(aaci);
+	if (ret)
+		goto out;
+
+	snd_card_set_dev(aaci->card, &dev->dev);
+
+	ret = snd_card_register(aaci->card);
+	if (ret == 0) {
+		dev_info(&dev->dev, "%s, fifo %d\n", aaci->card->longname,
+			 aaci->fifosize);
+		amba_set_drvdata(dev, aaci->card);
+		return ret;
+	}
+
+ out:
+	if (aaci)
+		snd_card_free(aaci->card);
+	amba_release_regions(dev);
+	return ret;
+}
+
+static int __devexit aaci_remove(struct amba_device *dev)
+{
+	struct snd_card *card = amba_get_drvdata(dev);
+
+	amba_set_drvdata(dev, NULL);
+
+	if (card) {
+		struct aaci *aaci = card->private_data;
+		writel(0, aaci->base + AACI_MAINCR);
+
+		snd_card_free(card);
+		amba_release_regions(dev);
+	}
+
+	return 0;
+}
+
+static struct amba_id aaci_ids[] = {
+	{
+		.id	= 0x00041041,
+		.mask	= 0x000fffff,
+	},
+	{ 0, 0 },
+};
+
+static struct amba_driver aaci_driver = {
+	.drv		= {
+		.name	= DRIVER_NAME,
+	},
+	.probe		= aaci_probe,
+	.remove		= __devexit_p(aaci_remove),
+	.suspend	= aaci_suspend,
+	.resume		= aaci_resume,
+	.id_table	= aaci_ids,
+};
+
+static int __init aaci_init(void)
+{
+	return amba_driver_register(&aaci_driver);
+}
+
+static void __exit aaci_exit(void)
+{
+	amba_driver_unregister(&aaci_driver);
+}
+
+module_init(aaci_init);
+module_exit(aaci_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ARM PrimeCell PL041 Advanced Audio CODEC Interface driver");
diff --git a/linux/sound/arm/aaci.h b/linux/sound/arm/aaci.h
new file mode 100644
index 0000000..6a4a2ee
--- /dev/null
+++ b/linux/sound/arm/aaci.h
@@ -0,0 +1,247 @@
+/*
+ *  linux/sound/arm/aaci.c - ARM PrimeCell AACI PL041 driver
+ *
+ *  Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef AACI_H
+#define AACI_H
+
+/*
+ * Control and status register offsets
+ *  P39.
+ */
+#define AACI_CSCH1	0x000
+#define AACI_CSCH2	0x014
+#define AACI_CSCH3	0x028
+#define AACI_CSCH4	0x03c
+
+#define AACI_RXCR	0x000	/* 29 bits Control Rx FIFO */
+#define AACI_TXCR	0x004	/* 17 bits Control Tx FIFO */
+#define AACI_SR		0x008	/* 12 bits Status */
+#define AACI_ISR	0x00c	/* 7 bits  Int Status */
+#define AACI_IE 	0x010	/* 7 bits  Int Enable */
+
+/*
+ * Other registers
+ */
+#define AACI_SL1RX	0x050
+#define AACI_SL1TX	0x054
+#define AACI_SL2RX	0x058
+#define AACI_SL2TX	0x05c
+#define AACI_SL12RX	0x060
+#define AACI_SL12TX	0x064
+#define AACI_SLFR	0x068	/* slot flags */
+#define AACI_SLISTAT	0x06c	/* slot interrupt status */
+#define AACI_SLIEN	0x070	/* slot interrupt enable */
+#define AACI_INTCLR	0x074	/* interrupt clear */
+#define AACI_MAINCR	0x078	/* main control */
+#define AACI_RESET	0x07c	/* reset control */
+#define AACI_SYNC	0x080	/* sync control */
+#define AACI_ALLINTS	0x084	/* all fifo interrupt status */
+#define AACI_MAINFR	0x088	/* main flag register */
+#define AACI_DR1	0x090	/* data read/written fifo 1 */
+#define AACI_DR2	0x0b0	/* data read/written fifo 2 */
+#define AACI_DR3	0x0d0	/* data read/written fifo 3 */
+#define AACI_DR4	0x0f0	/* data read/written fifo 4 */
+
+/*
+ * TX/RX fifo control register (CR). P48
+ */
+#define CR_FEN		(1 << 16)	/* fifo enable */
+#define CR_COMPACT	(1 << 15)	/* compact mode */
+#define CR_SZ16		(0 << 13)	/* 16 bits */
+#define CR_SZ18		(1 << 13)	/* 18 bits */
+#define CR_SZ20		(2 << 13)	/* 20 bits */
+#define CR_SZ12		(3 << 13)	/* 12 bits */
+#define CR_SL12		(1 << 12)
+#define CR_SL11		(1 << 11)
+#define CR_SL10		(1 << 10)
+#define CR_SL9		(1 << 9)
+#define CR_SL8		(1 << 8)
+#define CR_SL7		(1 << 7)
+#define CR_SL6		(1 << 6)
+#define CR_SL5		(1 << 5)
+#define CR_SL4		(1 << 4)
+#define CR_SL3		(1 << 3)
+#define CR_SL2		(1 << 2)
+#define CR_SL1		(1 << 1)
+#define CR_EN		(1 << 0)	/* transmit enable */
+
+/*
+ * status register bits. P49
+ */
+#define SR_RXTOFE	(1 << 11)	/* rx timeout fifo empty */
+#define SR_TXTO		(1 << 10)	/* rx timeout fifo nonempty */
+#define SR_TXU		(1 << 9)	/* tx underrun */
+#define SR_RXO		(1 << 8)	/* rx overrun */
+#define SR_TXB		(1 << 7)	/* tx busy */
+#define SR_RXB		(1 << 6)	/* rx busy */
+#define SR_TXFF		(1 << 5)	/* tx fifo full */
+#define SR_RXFF		(1 << 4)	/* rx fifo full */
+#define SR_TXHE		(1 << 3)	/* tx fifo half empty */
+#define SR_RXHF		(1 << 2)	/* rx fifo half full */
+#define SR_TXFE		(1 << 1)	/* tx fifo empty */
+#define SR_RXFE		(1 << 0)	/* rx fifo empty */
+
+/*
+ * interrupt status register bits.
+ */
+#define ISR_RXTOFEINTR	(1 << 6)	/* rx fifo empty */
+#define ISR_URINTR	(1 << 5)	/* tx underflow */
+#define ISR_ORINTR	(1 << 4)	/* rx overflow */
+#define ISR_RXINTR	(1 << 3)	/* rx fifo */
+#define ISR_TXINTR	(1 << 2)	/* tx fifo intr */
+#define ISR_RXTOINTR	(1 << 1)	/* tx timeout */
+#define ISR_TXCINTR	(1 << 0)	/* tx complete */
+
+/*
+ * interrupt enable register bits.
+ */
+#define IE_RXTOIE	(1 << 6)
+#define IE_URIE		(1 << 5)
+#define IE_ORIE		(1 << 4)
+#define IE_RXIE		(1 << 3)
+#define IE_TXIE		(1 << 2)
+#define IE_RXTIE	(1 << 1)
+#define IE_TXCIE	(1 << 0)
+
+/*
+ * interrupt status. P51
+ */
+#define ISR_RXTOFE	(1 << 6)	/* rx timeout fifo empty */
+#define ISR_UR		(1 << 5)	/* tx fifo underrun */
+#define ISR_OR		(1 << 4)	/* rx fifo overrun */
+#define ISR_RX		(1 << 3)	/* rx interrupt status */
+#define ISR_TX		(1 << 2)	/* tx interrupt status */
+#define ISR_RXTO	(1 << 1)	/* rx timeout */
+#define ISR_TXC		(1 << 0)	/* tx complete */
+
+/*
+ * interrupt enable. P52
+ */
+#define IE_RXTOFE	(1 << 6)	/* rx timeout fifo empty */
+#define IE_UR		(1 << 5)	/* tx fifo underrun */
+#define IE_OR		(1 << 4)	/* rx fifo overrun */
+#define IE_RX		(1 << 3)	/* rx interrupt status */
+#define IE_TX		(1 << 2)	/* tx interrupt status */
+#define IE_RXTO		(1 << 1)	/* rx timeout */
+#define IE_TXC		(1 << 0)	/* tx complete */
+
+/*
+ * slot flag register bits. P56
+ */
+#define SLFR_RWIS	(1 << 13)	/* raw wake-up interrupt status */
+#define SLFR_RGPIOINTR	(1 << 12)	/* raw gpio interrupt */
+#define SLFR_12TXE	(1 << 11)	/* slot 12 tx empty */
+#define SLFR_12RXV	(1 << 10)	/* slot 12 rx valid */
+#define SLFR_2TXE	(1 << 9)	/* slot 2 tx empty */
+#define SLFR_2RXV	(1 << 8)	/* slot 2 rx valid */
+#define SLFR_1TXE	(1 << 7)	/* slot 1 tx empty */
+#define SLFR_1RXV	(1 << 6)	/* slot 1 rx valid */
+#define SLFR_12TXB	(1 << 5)	/* slot 12 tx busy */
+#define SLFR_12RXB	(1 << 4)	/* slot 12 rx busy */
+#define SLFR_2TXB	(1 << 3)	/* slot 2 tx busy */
+#define SLFR_2RXB	(1 << 2)	/* slot 2 rx busy */
+#define SLFR_1TXB	(1 << 1)	/* slot 1 tx busy */
+#define SLFR_1RXB	(1 << 0)	/* slot 1 rx busy */
+
+/*
+ * Interrupt clear register.
+ */
+#define ICLR_RXTOFEC4	(1 << 12)
+#define ICLR_RXTOFEC3	(1 << 11)
+#define ICLR_RXTOFEC2	(1 << 10)
+#define ICLR_RXTOFEC1	(1 << 9)
+#define ICLR_TXUEC4	(1 << 8)
+#define ICLR_TXUEC3	(1 << 7)
+#define ICLR_TXUEC2	(1 << 6)
+#define ICLR_TXUEC1	(1 << 5)
+#define ICLR_RXOEC4	(1 << 4)
+#define ICLR_RXOEC3	(1 << 3)
+#define ICLR_RXOEC2	(1 << 2)
+#define ICLR_RXOEC1	(1 << 1)
+#define ICLR_WISC	(1 << 0)
+
+/*
+ * Main control register bits. P62
+ */
+#define MAINCR_SCRA(x)	((x) << 10)	/* secondary codec reg access */
+#define MAINCR_DMAEN	(1 << 9)	/* dma enable */
+#define MAINCR_SL12TXEN	(1 << 8)	/* slot 12 transmit enable */
+#define MAINCR_SL12RXEN	(1 << 7)	/* slot 12 receive enable */
+#define MAINCR_SL2TXEN	(1 << 6)	/* slot 2 transmit enable */
+#define MAINCR_SL2RXEN	(1 << 5)	/* slot 2 receive enable */
+#define MAINCR_SL1TXEN	(1 << 4)	/* slot 1 transmit enable */
+#define MAINCR_SL1RXEN	(1 << 3)	/* slot 1 receive enable */
+#define MAINCR_LPM	(1 << 2)	/* low power mode */
+#define MAINCR_LOOPBK	(1 << 1)	/* loopback */
+#define MAINCR_IE	(1 << 0)	/* aaci interface enable */
+
+/*
+ * Reset register bits. P65
+ */
+#define RESET_NRST	(1 << 0)
+
+/*
+ * Sync register bits. P65
+ */
+#define SYNC_FORCE	(1 << 0)
+
+/*
+ * Main flag register bits. P66
+ */
+#define MAINFR_TXB	(1 << 1)	/* transmit busy */
+#define MAINFR_RXB	(1 << 0)	/* receive busy */
+
+
+
+struct aaci_runtime {
+	void			__iomem *base;
+	void			__iomem *fifo;
+	spinlock_t		lock;
+
+	struct ac97_pcm		*pcm;
+	int			pcm_open;
+
+	u32			cr;
+	struct snd_pcm_substream	*substream;
+
+	/*
+	 * PIO support
+	 */
+	void			*start;
+	void			*end;
+	void			*ptr;
+	int			bytes;
+	unsigned int		period;
+	unsigned int		fifosz;
+};
+
+struct aaci {
+	struct amba_device	*dev;
+	struct snd_card		*card;
+	void			__iomem *base;
+	unsigned int		fifosize;
+
+	/* AC'97 */
+	struct mutex		ac97_sem;
+	struct snd_ac97_bus	*ac97_bus;
+	struct snd_ac97		*ac97;
+
+	u32			maincr;
+
+	struct aaci_runtime	playback;
+	struct aaci_runtime	capture;
+
+	struct snd_pcm		*pcm;
+};
+
+#define ACSTREAM_FRONT		0
+#define ACSTREAM_SURROUND	1
+#define ACSTREAM_LFE		2
+
+#endif
diff --git a/linux/sound/arm/pxa2xx-ac97-lib.c b/linux/sound/arm/pxa2xx-ac97-lib.c
new file mode 100644
index 0000000..88eec38
--- /dev/null
+++ b/linux/sound/arm/pxa2xx-ac97-lib.c
@@ -0,0 +1,400 @@
+/*
+ * Based on sound/arm/pxa2xx-ac97.c and sound/soc/pxa/pxa2xx-ac97.c
+ * which contain:
+ *
+ * Author:	Nicolas Pitre
+ * Created:	Dec 02, 2004
+ * Copyright:	MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+
+#include <sound/ac97_codec.h>
+#include <sound/pxa2xx-lib.h>
+
+#include <asm/irq.h>
+#include <mach/regs-ac97.h>
+#include <mach/audio.h>
+
+static DEFINE_MUTEX(car_mutex);
+static DECLARE_WAIT_QUEUE_HEAD(gsr_wq);
+static volatile long gsr_bits;
+static struct clk *ac97_clk;
+static struct clk *ac97conf_clk;
+static int reset_gpio;
+
+extern void pxa27x_assert_ac97reset(int reset_gpio, int on);
+
+/*
+ * Beware PXA27x bugs:
+ *
+ *   o Slot 12 read from modem space will hang controller.
+ *   o CDONE, SDONE interrupt fails after any slot 12 IO.
+ *
+ * We therefore have an hybrid approach for waiting on SDONE (interrupt or
+ * 1 jiffy timeout if interrupt never comes).
+ */
+
+unsigned short pxa2xx_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	unsigned short val = -1;
+	volatile u32 *reg_addr;
+
+	mutex_lock(&car_mutex);
+
+	/* set up primary or secondary codec space */
+	if (cpu_is_pxa25x() && reg == AC97_GPIO_STATUS)
+		reg_addr = ac97->num ? &SMC_REG_BASE : &PMC_REG_BASE;
+	else
+		reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE;
+	reg_addr += (reg >> 1);
+
+	/* start read access across the ac97 link */
+	GSR = GSR_CDONE | GSR_SDONE;
+	gsr_bits = 0;
+	val = *reg_addr;
+	if (reg == AC97_GPIO_STATUS)
+		goto out;
+	if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_SDONE, 1) <= 0 &&
+	    !((GSR | gsr_bits) & GSR_SDONE)) {
+		printk(KERN_ERR "%s: read error (ac97_reg=%d GSR=%#lx)\n",
+				__func__, reg, GSR | gsr_bits);
+		val = -1;
+		goto out;
+	}
+
+	/* valid data now */
+	GSR = GSR_CDONE | GSR_SDONE;
+	gsr_bits = 0;
+	val = *reg_addr;
+	/* but we've just started another cycle... */
+	wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_SDONE, 1);
+
+out:	mutex_unlock(&car_mutex);
+	return val;
+}
+EXPORT_SYMBOL_GPL(pxa2xx_ac97_read);
+
+void pxa2xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+			unsigned short val)
+{
+	volatile u32 *reg_addr;
+
+	mutex_lock(&car_mutex);
+
+	/* set up primary or secondary codec space */
+	if (cpu_is_pxa25x() && reg == AC97_GPIO_STATUS)
+		reg_addr = ac97->num ? &SMC_REG_BASE : &PMC_REG_BASE;
+	else
+		reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE;
+	reg_addr += (reg >> 1);
+
+	GSR = GSR_CDONE | GSR_SDONE;
+	gsr_bits = 0;
+	*reg_addr = val;
+	if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_CDONE, 1) <= 0 &&
+	    !((GSR | gsr_bits) & GSR_CDONE))
+		printk(KERN_ERR "%s: write error (ac97_reg=%d GSR=%#lx)\n",
+				__func__, reg, GSR | gsr_bits);
+
+	mutex_unlock(&car_mutex);
+}
+EXPORT_SYMBOL_GPL(pxa2xx_ac97_write);
+
+#ifdef CONFIG_PXA25x
+static inline void pxa_ac97_warm_pxa25x(void)
+{
+	gsr_bits = 0;
+
+	GCR |= GCR_WARM_RST | GCR_PRIRDY_IEN | GCR_SECRDY_IEN;
+	wait_event_timeout(gsr_wq, gsr_bits & (GSR_PCR | GSR_SCR), 1);
+}
+
+static inline void pxa_ac97_cold_pxa25x(void)
+{
+	GCR &=  GCR_COLD_RST;  /* clear everything but nCRST */
+	GCR &= ~GCR_COLD_RST;  /* then assert nCRST */
+
+	gsr_bits = 0;
+
+	GCR = GCR_COLD_RST;
+	GCR |= GCR_CDONE_IE|GCR_SDONE_IE;
+	wait_event_timeout(gsr_wq, gsr_bits & (GSR_PCR | GSR_SCR), 1);
+}
+#endif
+
+#ifdef CONFIG_PXA27x
+static inline void pxa_ac97_warm_pxa27x(void)
+{
+	gsr_bits = 0;
+
+	/* warm reset broken on Bulverde, so manually keep AC97 reset high */
+	pxa27x_assert_ac97reset(reset_gpio, 1);
+	udelay(10);
+	GCR |= GCR_WARM_RST;
+	pxa27x_assert_ac97reset(reset_gpio, 0);
+	udelay(500);
+}
+
+static inline void pxa_ac97_cold_pxa27x(void)
+{
+	GCR &=  GCR_COLD_RST;  /* clear everything but nCRST */
+	GCR &= ~GCR_COLD_RST;  /* then assert nCRST */
+
+	gsr_bits = 0;
+
+	/* PXA27x Developers Manual section 13.5.2.2.1 */
+	clk_enable(ac97conf_clk);
+	udelay(5);
+	clk_disable(ac97conf_clk);
+	GCR = GCR_COLD_RST;
+	udelay(50);
+}
+#endif
+
+#ifdef CONFIG_PXA3xx
+static inline void pxa_ac97_warm_pxa3xx(void)
+{
+	int timeout = 100;
+
+	gsr_bits = 0;
+
+	/* Can't use interrupts */
+	GCR |= GCR_WARM_RST;
+	while (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR)) && timeout--)
+		mdelay(1);
+}
+
+static inline void pxa_ac97_cold_pxa3xx(void)
+{
+	int timeout = 1000;
+
+	/* Hold CLKBPB for 100us */
+	GCR = 0;
+	GCR = GCR_CLKBPB;
+	udelay(100);
+	GCR = 0;
+
+	GCR &=  GCR_COLD_RST;  /* clear everything but nCRST */
+	GCR &= ~GCR_COLD_RST;  /* then assert nCRST */
+
+	gsr_bits = 0;
+
+	/* Can't use interrupts on PXA3xx */
+	GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN);
+
+	GCR = GCR_WARM_RST | GCR_COLD_RST;
+	while (!(GSR & (GSR_PCR | GSR_SCR)) && timeout--)
+		mdelay(10);
+}
+#endif
+
+bool pxa2xx_ac97_try_warm_reset(struct snd_ac97 *ac97)
+{
+	unsigned long gsr;
+
+#ifdef CONFIG_PXA25x
+	if (cpu_is_pxa25x())
+		pxa_ac97_warm_pxa25x();
+	else
+#endif
+#ifdef CONFIG_PXA27x
+	if (cpu_is_pxa27x())
+		pxa_ac97_warm_pxa27x();
+	else
+#endif
+#ifdef CONFIG_PXA3xx
+	if (cpu_is_pxa3xx())
+		pxa_ac97_warm_pxa3xx();
+	else
+#endif
+		BUG();
+	gsr = GSR | gsr_bits;
+	if (!(gsr & (GSR_PCR | GSR_SCR))) {
+		printk(KERN_INFO "%s: warm reset timeout (GSR=%#lx)\n",
+				 __func__, gsr);
+
+		return false;
+	}
+
+	return true;
+}
+EXPORT_SYMBOL_GPL(pxa2xx_ac97_try_warm_reset);
+
+bool pxa2xx_ac97_try_cold_reset(struct snd_ac97 *ac97)
+{
+	unsigned long gsr;
+
+#ifdef CONFIG_PXA25x
+	if (cpu_is_pxa25x())
+		pxa_ac97_cold_pxa25x();
+	else
+#endif
+#ifdef CONFIG_PXA27x
+	if (cpu_is_pxa27x())
+		pxa_ac97_cold_pxa27x();
+	else
+#endif
+#ifdef CONFIG_PXA3xx
+	if (cpu_is_pxa3xx())
+		pxa_ac97_cold_pxa3xx();
+	else
+#endif
+		BUG();
+
+	gsr = GSR | gsr_bits;
+	if (!(gsr & (GSR_PCR | GSR_SCR))) {
+		printk(KERN_INFO "%s: cold reset timeout (GSR=%#lx)\n",
+				 __func__, gsr);
+
+		return false;
+	}
+
+	return true;
+}
+EXPORT_SYMBOL_GPL(pxa2xx_ac97_try_cold_reset);
+
+
+void pxa2xx_ac97_finish_reset(struct snd_ac97 *ac97)
+{
+	GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN);
+	GCR |= GCR_SDONE_IE|GCR_CDONE_IE;
+}
+EXPORT_SYMBOL_GPL(pxa2xx_ac97_finish_reset);
+
+static irqreturn_t pxa2xx_ac97_irq(int irq, void *dev_id)
+{
+	long status;
+
+	status = GSR;
+	if (status) {
+		GSR = status;
+		gsr_bits |= status;
+		wake_up(&gsr_wq);
+
+		/* Although we don't use those we still need to clear them
+		   since they tend to spuriously trigger when MMC is used
+		   (hardware bug? go figure)... */
+		if (cpu_is_pxa27x()) {
+			MISR = MISR_EOC;
+			PISR = PISR_EOC;
+			MCSR = MCSR_EOC;
+		}
+
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+#ifdef CONFIG_PM
+int pxa2xx_ac97_hw_suspend(void)
+{
+	GCR |= GCR_ACLINK_OFF;
+	clk_disable(ac97_clk);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_suspend);
+
+int pxa2xx_ac97_hw_resume(void)
+{
+	clk_enable(ac97_clk);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_resume);
+#endif
+
+int __devinit pxa2xx_ac97_hw_probe(struct platform_device *dev)
+{
+	int ret;
+	pxa2xx_audio_ops_t *pdata = dev->dev.platform_data;
+
+	if (pdata) {
+		switch (pdata->reset_gpio) {
+		case 95:
+		case 113:
+			reset_gpio = pdata->reset_gpio;
+			break;
+		case 0:
+			reset_gpio = 113;
+			break;
+		case -1:
+			break;
+		default:
+			dev_err(&dev->dev, "Invalid reset GPIO %d\n",
+				pdata->reset_gpio);
+		}
+	} else {
+		if (cpu_is_pxa27x())
+			reset_gpio = 113;
+	}
+
+	if (cpu_is_pxa27x()) {
+		/* Use GPIO 113 as AC97 Reset on Bulverde */
+		pxa27x_assert_ac97reset(reset_gpio, 0);
+		ac97conf_clk = clk_get(&dev->dev, "AC97CONFCLK");
+		if (IS_ERR(ac97conf_clk)) {
+			ret = PTR_ERR(ac97conf_clk);
+			ac97conf_clk = NULL;
+			goto err_conf;
+		}
+	}
+
+	ac97_clk = clk_get(&dev->dev, "AC97CLK");
+	if (IS_ERR(ac97_clk)) {
+		ret = PTR_ERR(ac97_clk);
+		ac97_clk = NULL;
+		goto err_clk;
+	}
+
+	ret = clk_enable(ac97_clk);
+	if (ret)
+		goto err_clk2;
+
+	ret = request_irq(IRQ_AC97, pxa2xx_ac97_irq, IRQF_DISABLED, "AC97", NULL);
+	if (ret < 0)
+		goto err_irq;
+
+	return 0;
+
+err_irq:
+	GCR |= GCR_ACLINK_OFF;
+err_clk2:
+	clk_put(ac97_clk);
+	ac97_clk = NULL;
+err_clk:
+	if (ac97conf_clk) {
+		clk_put(ac97conf_clk);
+		ac97conf_clk = NULL;
+	}
+err_conf:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_probe);
+
+void pxa2xx_ac97_hw_remove(struct platform_device *dev)
+{
+	GCR |= GCR_ACLINK_OFF;
+	free_irq(IRQ_AC97, NULL);
+	if (ac97conf_clk) {
+		clk_put(ac97conf_clk);
+		ac97conf_clk = NULL;
+	}
+	clk_disable(ac97_clk);
+	clk_put(ac97_clk);
+	ac97_clk = NULL;
+}
+EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_remove);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("Intel/Marvell PXA sound library");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/arm/pxa2xx-ac97.c b/linux/sound/arm/pxa2xx-ac97.c
new file mode 100644
index 0000000..5d94118
--- /dev/null
+++ b/linux/sound/arm/pxa2xx-ac97.c
@@ -0,0 +1,270 @@
+/*
+ * linux/sound/pxa2xx-ac97.c -- AC97 support for the Intel PXA2xx chip.
+ *
+ * Author:	Nicolas Pitre
+ * Created:	Dec 02, 2004
+ * Copyright:	MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/pxa2xx-lib.h>
+
+#include <mach/regs-ac97.h>
+#include <mach/audio.h>
+
+#include "pxa2xx-pcm.h"
+
+static void pxa2xx_ac97_reset(struct snd_ac97 *ac97)
+{
+	if (!pxa2xx_ac97_try_cold_reset(ac97)) {
+		pxa2xx_ac97_try_warm_reset(ac97);
+	}
+
+	pxa2xx_ac97_finish_reset(ac97);
+}
+
+static struct snd_ac97_bus_ops pxa2xx_ac97_ops = {
+	.read	= pxa2xx_ac97_read,
+	.write	= pxa2xx_ac97_write,
+	.reset	= pxa2xx_ac97_reset,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_out = {
+	.name			= "AC97 PCM out",
+	.dev_addr		= __PREG(PCDR),
+	.drcmr			= &DRCMR(12),
+	.dcmd			= DCMD_INCSRCADDR | DCMD_FLOWTRG |
+				  DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_in = {
+	.name			= "AC97 PCM in",
+	.dev_addr		= __PREG(PCDR),
+	.drcmr			= &DRCMR(11),
+	.dcmd			= DCMD_INCTRGADDR | DCMD_FLOWSRC |
+				  DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct snd_pcm *pxa2xx_ac97_pcm;
+static struct snd_ac97 *pxa2xx_ac97_ac97;
+
+static int pxa2xx_ac97_pcm_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	pxa2xx_audio_ops_t *platform_ops;
+	int r;
+
+	runtime->hw.channels_min = 2;
+	runtime->hw.channels_max = 2;
+
+	r = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+	    AC97_RATES_FRONT_DAC : AC97_RATES_ADC;
+	runtime->hw.rates = pxa2xx_ac97_ac97->rates[r];
+	snd_pcm_limit_hw_rates(runtime);
+
+       	platform_ops = substream->pcm->card->dev->platform_data;
+	if (platform_ops && platform_ops->startup)
+		return platform_ops->startup(substream, platform_ops->priv);
+	else
+		return 0;
+}
+
+static void pxa2xx_ac97_pcm_shutdown(struct snd_pcm_substream *substream)
+{
+	pxa2xx_audio_ops_t *platform_ops;
+
+       	platform_ops = substream->pcm->card->dev->platform_data;
+	if (platform_ops && platform_ops->shutdown)
+		platform_ops->shutdown(substream, platform_ops->priv);
+}
+
+static int pxa2xx_ac97_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		  AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE;
+	return snd_ac97_set_rate(pxa2xx_ac97_ac97, reg, runtime->rate);
+}
+
+static struct pxa2xx_pcm_client pxa2xx_ac97_pcm_client = {
+	.playback_params	= &pxa2xx_ac97_pcm_out,
+	.capture_params		= &pxa2xx_ac97_pcm_in,
+	.startup		= pxa2xx_ac97_pcm_startup,
+	.shutdown		= pxa2xx_ac97_pcm_shutdown,
+	.prepare		= pxa2xx_ac97_pcm_prepare,
+};
+
+#ifdef CONFIG_PM
+
+static int pxa2xx_ac97_do_suspend(struct snd_card *card, pm_message_t state)
+{
+	pxa2xx_audio_ops_t *platform_ops = card->dev->platform_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3cold);
+	snd_pcm_suspend_all(pxa2xx_ac97_pcm);
+	snd_ac97_suspend(pxa2xx_ac97_ac97);
+	if (platform_ops && platform_ops->suspend)
+		platform_ops->suspend(platform_ops->priv);
+
+	return pxa2xx_ac97_hw_suspend();
+}
+
+static int pxa2xx_ac97_do_resume(struct snd_card *card)
+{
+	pxa2xx_audio_ops_t *platform_ops = card->dev->platform_data;
+	int rc;
+
+	rc = pxa2xx_ac97_hw_resume();
+	if (rc)
+		return rc;
+
+	if (platform_ops && platform_ops->resume)
+		platform_ops->resume(platform_ops->priv);
+	snd_ac97_resume(pxa2xx_ac97_ac97);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+
+	return 0;
+}
+
+static int pxa2xx_ac97_suspend(struct device *dev)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	int ret = 0;
+
+	if (card)
+		ret = pxa2xx_ac97_do_suspend(card, PMSG_SUSPEND);
+
+	return ret;
+}
+
+static int pxa2xx_ac97_resume(struct device *dev)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	int ret = 0;
+
+	if (card)
+		ret = pxa2xx_ac97_do_resume(card);
+
+	return ret;
+}
+
+static const struct dev_pm_ops pxa2xx_ac97_pm_ops = {
+	.suspend	= pxa2xx_ac97_suspend,
+	.resume		= pxa2xx_ac97_resume,
+};
+#endif
+
+static int __devinit pxa2xx_ac97_probe(struct platform_device *dev)
+{
+	struct snd_card *card;
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97_template ac97_template;
+	int ret;
+	pxa2xx_audio_ops_t *pdata = dev->dev.platform_data;
+
+	if (dev->id >= 0) {
+		dev_err(&dev->dev, "PXA2xx has only one AC97 port.\n");
+		ret = -ENXIO;
+		goto err_dev;
+	}
+
+	ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+			      THIS_MODULE, 0, &card);
+	if (ret < 0)
+		goto err;
+
+	card->dev = &dev->dev;
+	strncpy(card->driver, dev->dev.driver->name, sizeof(card->driver));
+
+	ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm);
+	if (ret)
+		goto err;
+
+	ret = pxa2xx_ac97_hw_probe(dev);
+	if (ret)
+		goto err;
+
+	ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus);
+	if (ret)
+		goto err_remove;
+	memset(&ac97_template, 0, sizeof(ac97_template));
+	ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97);
+	if (ret)
+		goto err_remove;
+
+	snprintf(card->shortname, sizeof(card->shortname),
+		 "%s", snd_ac97_get_short_name(pxa2xx_ac97_ac97));
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s (%s)", dev->dev.driver->name, card->mixername);
+
+	if (pdata && pdata->codec_pdata[0])
+		snd_ac97_dev_add_pdata(ac97_bus->codec[0], pdata->codec_pdata[0]);
+	snd_card_set_dev(card, &dev->dev);
+	ret = snd_card_register(card);
+	if (ret == 0) {
+		platform_set_drvdata(dev, card);
+		return 0;
+	}
+
+err_remove:
+	pxa2xx_ac97_hw_remove(dev);
+err:
+	if (card)
+		snd_card_free(card);
+err_dev:
+	return ret;
+}
+
+static int __devexit pxa2xx_ac97_remove(struct platform_device *dev)
+{
+	struct snd_card *card = platform_get_drvdata(dev);
+
+	if (card) {
+		snd_card_free(card);
+		platform_set_drvdata(dev, NULL);
+		pxa2xx_ac97_hw_remove(dev);
+	}
+
+	return 0;
+}
+
+static struct platform_driver pxa2xx_ac97_driver = {
+	.probe		= pxa2xx_ac97_probe,
+	.remove		= __devexit_p(pxa2xx_ac97_remove),
+	.driver		= {
+		.name	= "pxa2xx-ac97",
+		.owner	= THIS_MODULE,
+#ifdef CONFIG_PM
+		.pm	= &pxa2xx_ac97_pm_ops,
+#endif
+	},
+};
+
+static int __init pxa2xx_ac97_init(void)
+{
+	return platform_driver_register(&pxa2xx_ac97_driver);
+}
+
+static void __exit pxa2xx_ac97_exit(void)
+{
+	platform_driver_unregister(&pxa2xx_ac97_driver);
+}
+
+module_init(pxa2xx_ac97_init);
+module_exit(pxa2xx_ac97_exit);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pxa2xx-ac97");
diff --git a/linux/sound/arm/pxa2xx-pcm-lib.c b/linux/sound/arm/pxa2xx-pcm-lib.c
new file mode 100644
index 0000000..8808b82
--- /dev/null
+++ b/linux/sound/arm/pxa2xx-pcm-lib.c
@@ -0,0 +1,282 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/pxa2xx-lib.h>
+
+#include <mach/dma.h>
+
+#include "pxa2xx-pcm.h"
+
+static const struct snd_pcm_hardware pxa2xx_pcm_hardware = {
+	.info			= SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_PAUSE |
+				  SNDRV_PCM_INFO_RESUME,
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
+					SNDRV_PCM_FMTBIT_S24_LE |
+					SNDRV_PCM_FMTBIT_S32_LE,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 8192 - 32,
+	.periods_min		= 1,
+	.periods_max		= PAGE_SIZE/sizeof(pxa_dma_desc),
+	.buffer_bytes_max	= 128 * 1024,
+	.fifo_size		= 32,
+};
+
+int __pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pxa2xx_runtime_data *rtd = runtime->private_data;
+	size_t totsize = params_buffer_bytes(params);
+	size_t period = params_period_bytes(params);
+	pxa_dma_desc *dma_desc;
+	dma_addr_t dma_buff_phys, next_desc_phys;
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+	runtime->dma_bytes = totsize;
+
+	dma_desc = rtd->dma_desc_array;
+	next_desc_phys = rtd->dma_desc_array_phys;
+	dma_buff_phys = runtime->dma_addr;
+	do {
+		next_desc_phys += sizeof(pxa_dma_desc);
+		dma_desc->ddadr = next_desc_phys;
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			dma_desc->dsadr = dma_buff_phys;
+			dma_desc->dtadr = rtd->params->dev_addr;
+		} else {
+			dma_desc->dsadr = rtd->params->dev_addr;
+			dma_desc->dtadr = dma_buff_phys;
+		}
+		if (period > totsize)
+			period = totsize;
+		dma_desc->dcmd = rtd->params->dcmd | period | DCMD_ENDIRQEN;
+		dma_desc++;
+		dma_buff_phys += period;
+	} while (totsize -= period);
+	dma_desc[-1].ddadr = rtd->dma_desc_array_phys;
+
+	return 0;
+}
+EXPORT_SYMBOL(__pxa2xx_pcm_hw_params);
+
+int __pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct pxa2xx_runtime_data *rtd = substream->runtime->private_data;
+
+	if (rtd && rtd->params && rtd->params->drcmr)
+		*rtd->params->drcmr = 0;
+
+	snd_pcm_set_runtime_buffer(substream, NULL);
+	return 0;
+}
+EXPORT_SYMBOL(__pxa2xx_pcm_hw_free);
+
+int pxa2xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct pxa2xx_runtime_data *prtd = substream->runtime->private_data;
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys;
+		DCSR(prtd->dma_ch) = DCSR_RUN;
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		DCSR(prtd->dma_ch) &= ~DCSR_RUN;
+		break;
+
+	case SNDRV_PCM_TRIGGER_RESUME:
+		DCSR(prtd->dma_ch) |= DCSR_RUN;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys;
+		DCSR(prtd->dma_ch) |= DCSR_RUN;
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(pxa2xx_pcm_trigger);
+
+snd_pcm_uframes_t
+pxa2xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pxa2xx_runtime_data *prtd = runtime->private_data;
+
+	dma_addr_t ptr = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+			 DSADR(prtd->dma_ch) : DTADR(prtd->dma_ch);
+	snd_pcm_uframes_t x = bytes_to_frames(runtime, ptr - runtime->dma_addr);
+
+	if (x == runtime->buffer_size)
+		x = 0;
+	return x;
+}
+EXPORT_SYMBOL(pxa2xx_pcm_pointer);
+
+int __pxa2xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct pxa2xx_runtime_data *prtd = substream->runtime->private_data;
+
+	if (!prtd || !prtd->params)
+		return 0;
+
+	DCSR(prtd->dma_ch) &= ~DCSR_RUN;
+	DCSR(prtd->dma_ch) = 0;
+	DCMD(prtd->dma_ch) = 0;
+	*prtd->params->drcmr = prtd->dma_ch | DRCMR_MAPVLD;
+
+	return 0;
+}
+EXPORT_SYMBOL(__pxa2xx_pcm_prepare);
+
+void pxa2xx_pcm_dma_irq(int dma_ch, void *dev_id)
+{
+	struct snd_pcm_substream *substream = dev_id;
+	struct pxa2xx_runtime_data *rtd = substream->runtime->private_data;
+	int dcsr;
+
+	dcsr = DCSR(dma_ch);
+	DCSR(dma_ch) = dcsr & ~DCSR_STOPIRQEN;
+
+	if (dcsr & DCSR_ENDINTR) {
+		snd_pcm_period_elapsed(substream);
+	} else {
+		printk(KERN_ERR "%s: DMA error on channel %d (DCSR=%#x)\n",
+			rtd->params->name, dma_ch, dcsr);
+		snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+	}
+}
+EXPORT_SYMBOL(pxa2xx_pcm_dma_irq);
+
+int __pxa2xx_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pxa2xx_runtime_data *rtd;
+	int ret;
+
+	runtime->hw = pxa2xx_pcm_hardware;
+
+	/*
+	 * For mysterious reasons (and despite what the manual says)
+	 * playback samples are lost if the DMA count is not a multiple
+	 * of the DMA burst size.  Let's add a rule to enforce that.
+	 */
+	ret = snd_pcm_hw_constraint_step(runtime, 0,
+		SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
+	if (ret)
+		goto out;
+
+	ret = snd_pcm_hw_constraint_step(runtime, 0,
+		SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
+	if (ret)
+		goto out;
+
+	ret = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0)
+		goto out;
+
+	ret = -ENOMEM;
+	rtd = kzalloc(sizeof(*rtd), GFP_KERNEL);
+	if (!rtd)
+		goto out;
+	rtd->dma_desc_array =
+		dma_alloc_writecombine(substream->pcm->card->dev, PAGE_SIZE,
+				       &rtd->dma_desc_array_phys, GFP_KERNEL);
+	if (!rtd->dma_desc_array)
+		goto err1;
+
+	rtd->dma_ch = -1;
+	runtime->private_data = rtd;
+	return 0;
+
+ err1:
+	kfree(rtd);
+ out:
+	return ret;
+}
+EXPORT_SYMBOL(__pxa2xx_pcm_open);
+
+int __pxa2xx_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pxa2xx_runtime_data *rtd = runtime->private_data;
+
+	dma_free_writecombine(substream->pcm->card->dev, PAGE_SIZE,
+			      rtd->dma_desc_array, rtd->dma_desc_array_phys);
+	kfree(rtd);
+	return 0;
+}
+EXPORT_SYMBOL(__pxa2xx_pcm_close);
+
+int pxa2xx_pcm_mmap(struct snd_pcm_substream *substream,
+	struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+				     runtime->dma_area,
+				     runtime->dma_addr,
+				     runtime->dma_bytes);
+}
+EXPORT_SYMBOL(pxa2xx_pcm_mmap);
+
+int pxa2xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = pxa2xx_pcm_hardware.buffer_bytes_max;
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+	buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+					   &buf->addr, GFP_KERNEL);
+	if (!buf->area)
+		return -ENOMEM;
+	buf->bytes = size;
+	return 0;
+}
+EXPORT_SYMBOL(pxa2xx_pcm_preallocate_dma_buffer);
+
+void pxa2xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+		dma_free_writecombine(pcm->card->dev, buf->bytes,
+				      buf->area, buf->addr);
+		buf->area = NULL;
+	}
+}
+EXPORT_SYMBOL(pxa2xx_pcm_free_dma_buffers);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("Intel PXA2xx sound library");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/arm/pxa2xx-pcm.c b/linux/sound/arm/pxa2xx-pcm.c
new file mode 100644
index 0000000..535704f
--- /dev/null
+++ b/linux/sound/arm/pxa2xx-pcm.c
@@ -0,0 +1,131 @@
+/*
+ * linux/sound/arm/pxa2xx-pcm.c -- ALSA PCM interface for the Intel PXA2xx chip
+ *
+ * Author:	Nicolas Pitre
+ * Created:	Nov 30, 2004
+ * Copyright:	(C) 2004 MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <sound/core.h>
+#include <sound/pxa2xx-lib.h>
+
+#include "pxa2xx-pcm.h"
+
+static int pxa2xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct pxa2xx_pcm_client *client = substream->private_data;
+
+	__pxa2xx_pcm_prepare(substream);
+
+	return client->prepare(substream);
+}
+
+static int pxa2xx_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct pxa2xx_pcm_client *client = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pxa2xx_runtime_data *rtd;
+	int ret;
+
+	ret = __pxa2xx_pcm_open(substream);
+	if (ret)
+		goto out;
+
+	rtd = runtime->private_data;
+
+	rtd->params = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		      client->playback_params : client->capture_params;
+	ret = pxa_request_dma(rtd->params->name, DMA_PRIO_LOW,
+			      pxa2xx_pcm_dma_irq, substream);
+	if (ret < 0)
+		goto err2;
+	rtd->dma_ch = ret;
+
+	ret = client->startup(substream);
+	if (!ret)
+		goto out;
+
+	pxa_free_dma(rtd->dma_ch);
+ err2:
+	__pxa2xx_pcm_close(substream);
+ out:
+	return ret;
+}
+
+static int pxa2xx_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct pxa2xx_pcm_client *client = substream->private_data;
+	struct pxa2xx_runtime_data *rtd = substream->runtime->private_data;
+
+	pxa_free_dma(rtd->dma_ch);
+	client->shutdown(substream);
+
+	return __pxa2xx_pcm_close(substream);
+}
+
+static struct snd_pcm_ops pxa2xx_pcm_ops = {
+	.open		= pxa2xx_pcm_open,
+	.close		= pxa2xx_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= __pxa2xx_pcm_hw_params,
+	.hw_free	= __pxa2xx_pcm_hw_free,
+	.prepare	= pxa2xx_pcm_prepare,
+	.trigger	= pxa2xx_pcm_trigger,
+	.pointer	= pxa2xx_pcm_pointer,
+	.mmap		= pxa2xx_pcm_mmap,
+};
+
+static u64 pxa2xx_pcm_dmamask = 0xffffffff;
+
+int pxa2xx_pcm_new(struct snd_card *card, struct pxa2xx_pcm_client *client,
+		   struct snd_pcm **rpcm)
+{
+	struct snd_pcm *pcm;
+	int play = client->playback_params ? 1 : 0;
+	int capt = client->capture_params ? 1 : 0;
+	int ret;
+
+	ret = snd_pcm_new(card, "PXA2xx-PCM", 0, play, capt, &pcm);
+	if (ret)
+		goto out;
+
+	pcm->private_data = client;
+	pcm->private_free = pxa2xx_pcm_free_dma_buffers;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &pxa2xx_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = 0xffffffff;
+
+	if (play) {
+		int stream = SNDRV_PCM_STREAM_PLAYBACK;
+		snd_pcm_set_ops(pcm, stream, &pxa2xx_pcm_ops);
+		ret = pxa2xx_pcm_preallocate_dma_buffer(pcm, stream);
+		if (ret)
+			goto out;
+	}
+	if (capt) {
+		int stream = SNDRV_PCM_STREAM_CAPTURE;
+		snd_pcm_set_ops(pcm, stream, &pxa2xx_pcm_ops);
+		ret = pxa2xx_pcm_preallocate_dma_buffer(pcm, stream);
+		if (ret)
+			goto out;
+	}
+
+	if (rpcm)
+		*rpcm = pcm;
+	ret = 0;
+
+ out:
+	return ret;
+}
+
+EXPORT_SYMBOL(pxa2xx_pcm_new);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/arm/pxa2xx-pcm.h b/linux/sound/arm/pxa2xx-pcm.h
new file mode 100644
index 0000000..65f86b5
--- /dev/null
+++ b/linux/sound/arm/pxa2xx-pcm.h
@@ -0,0 +1,30 @@
+/*
+ * linux/sound/arm/pxa2xx-pcm.h -- ALSA PCM interface for the Intel PXA2xx chip
+ *
+ * Author:	Nicolas Pitre
+ * Created:	Nov 30, 2004
+ * Copyright:	MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <mach/dma.h>
+
+struct pxa2xx_runtime_data {
+	int dma_ch;
+	struct pxa2xx_pcm_dma_params *params;
+	pxa_dma_desc *dma_desc_array;
+	dma_addr_t dma_desc_array_phys;
+};
+
+struct pxa2xx_pcm_client {
+	struct pxa2xx_pcm_dma_params *playback_params;
+	struct pxa2xx_pcm_dma_params *capture_params;
+	int (*startup)(struct snd_pcm_substream *);
+	void (*shutdown)(struct snd_pcm_substream *);
+	int (*prepare)(struct snd_pcm_substream *);
+};
+
+extern int pxa2xx_pcm_new(struct snd_card *, struct pxa2xx_pcm_client *, struct snd_pcm **);
+
diff --git a/linux/sound/atmel/Kconfig b/linux/sound/atmel/Kconfig
new file mode 100644
index 0000000..94de43a
--- /dev/null
+++ b/linux/sound/atmel/Kconfig
@@ -0,0 +1,19 @@
+menu "Atmel devices (AVR32 and AT91)"
+	depends on AVR32 || ARCH_AT91
+
+config SND_ATMEL_ABDAC
+	tristate "Atmel Audio Bitstream DAC (ABDAC) driver"
+	select SND_PCM
+	depends on DW_DMAC && AVR32
+	help
+	  ALSA sound driver for the Atmel Audio Bitstream DAC (ABDAC).
+
+config SND_ATMEL_AC97C
+	tristate "Atmel AC97 Controller (AC97C) driver"
+	select SND_PCM
+	select SND_AC97_CODEC
+	depends on (DW_DMAC && AVR32) || ARCH_AT91
+	help
+	  ALSA sound driver for the Atmel AC97 controller.
+
+endmenu
diff --git a/linux/sound/atmel/Makefile b/linux/sound/atmel/Makefile
new file mode 100644
index 0000000..219dcfa
--- /dev/null
+++ b/linux/sound/atmel/Makefile
@@ -0,0 +1,5 @@
+snd-atmel-abdac-objs		:= abdac.o
+snd-atmel-ac97c-objs		:= ac97c.o
+
+obj-$(CONFIG_SND_ATMEL_ABDAC)	+= snd-atmel-abdac.o
+obj-$(CONFIG_SND_ATMEL_AC97C)	+= snd-atmel-ac97c.o
diff --git a/linux/sound/atmel/abdac.c b/linux/sound/atmel/abdac.c
new file mode 100644
index 0000000..6e24091
--- /dev/null
+++ b/linux/sound/atmel/abdac.c
@@ -0,0 +1,602 @@
+/*
+ * Driver for the Atmel on-chip Audio Bitstream DAC (ABDAC)
+ *
+ * Copyright (C) 2006-2009 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <linux/bitmap.h>
+#include <linux/dw_dmac.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/atmel-abdac.h>
+
+/* DAC register offsets */
+#define DAC_DATA                                0x0000
+#define DAC_CTRL                                0x0008
+#define DAC_INT_MASK                            0x000c
+#define DAC_INT_EN                              0x0010
+#define DAC_INT_DIS                             0x0014
+#define DAC_INT_CLR                             0x0018
+#define DAC_INT_STATUS                          0x001c
+
+/* Bitfields in CTRL */
+#define DAC_SWAP_OFFSET                         30
+#define DAC_SWAP_SIZE                           1
+#define DAC_EN_OFFSET                           31
+#define DAC_EN_SIZE                             1
+
+/* Bitfields in INT_MASK/INT_EN/INT_DIS/INT_STATUS/INT_CLR */
+#define DAC_UNDERRUN_OFFSET                     28
+#define DAC_UNDERRUN_SIZE                       1
+#define DAC_TX_READY_OFFSET                     29
+#define DAC_TX_READY_SIZE                       1
+
+/* Bit manipulation macros */
+#define DAC_BIT(name)					\
+	(1 << DAC_##name##_OFFSET)
+#define DAC_BF(name, value)				\
+	(((value) & ((1 << DAC_##name##_SIZE) - 1))	\
+	 << DAC_##name##_OFFSET)
+#define DAC_BFEXT(name, value)				\
+	(((value) >> DAC_##name##_OFFSET)		\
+	 & ((1 << DAC_##name##_SIZE) - 1))
+#define DAC_BFINS(name, value, old)			\
+	(((old) & ~(((1 << DAC_##name##_SIZE) - 1)	\
+		    << DAC_##name##_OFFSET))		\
+	 | DAC_BF(name, value))
+
+/* Register access macros */
+#define dac_readl(port, reg)				\
+	__raw_readl((port)->regs + DAC_##reg)
+#define dac_writel(port, reg, value)			\
+	__raw_writel((value), (port)->regs + DAC_##reg)
+
+/*
+ * ABDAC supports a maximum of 6 different rates from a generic clock. The
+ * generic clock has a power of two divider, which gives 6 steps from 192 kHz
+ * to 5112 Hz.
+ */
+#define MAX_NUM_RATES	6
+/* ALSA seems to use rates between 192000 Hz and 5112 Hz. */
+#define RATE_MAX	192000
+#define RATE_MIN	5112
+
+enum {
+	DMA_READY = 0,
+};
+
+struct atmel_abdac_dma {
+	struct dma_chan		*chan;
+	struct dw_cyclic_desc	*cdesc;
+};
+
+struct atmel_abdac {
+	struct clk				*pclk;
+	struct clk				*sample_clk;
+	struct platform_device			*pdev;
+	struct atmel_abdac_dma			dma;
+
+	struct snd_pcm_hw_constraint_list	constraints_rates;
+	struct snd_pcm_substream		*substream;
+	struct snd_card				*card;
+	struct snd_pcm				*pcm;
+
+	void __iomem				*regs;
+	unsigned long				flags;
+	unsigned int				rates[MAX_NUM_RATES];
+	unsigned int				rates_num;
+	int					irq;
+};
+
+#define get_dac(card) ((struct atmel_abdac *)(card)->private_data)
+
+/* This function is called by the DMA driver. */
+static void atmel_abdac_dma_period_done(void *arg)
+{
+	struct atmel_abdac *dac = arg;
+	snd_pcm_period_elapsed(dac->substream);
+}
+
+static int atmel_abdac_prepare_dma(struct atmel_abdac *dac,
+		struct snd_pcm_substream *substream,
+		enum dma_data_direction direction)
+{
+	struct dma_chan			*chan = dac->dma.chan;
+	struct dw_cyclic_desc		*cdesc;
+	struct snd_pcm_runtime		*runtime = substream->runtime;
+	unsigned long			buffer_len, period_len;
+
+	/*
+	 * We don't do DMA on "complex" transfers, i.e. with
+	 * non-halfword-aligned buffers or lengths.
+	 */
+	if (runtime->dma_addr & 1 || runtime->buffer_size & 1) {
+		dev_dbg(&dac->pdev->dev, "too complex transfer\n");
+		return -EINVAL;
+	}
+
+	buffer_len = frames_to_bytes(runtime, runtime->buffer_size);
+	period_len = frames_to_bytes(runtime, runtime->period_size);
+
+	cdesc = dw_dma_cyclic_prep(chan, runtime->dma_addr, buffer_len,
+			period_len, DMA_TO_DEVICE);
+	if (IS_ERR(cdesc)) {
+		dev_dbg(&dac->pdev->dev, "could not prepare cyclic DMA\n");
+		return PTR_ERR(cdesc);
+	}
+
+	cdesc->period_callback = atmel_abdac_dma_period_done;
+	cdesc->period_callback_param = dac;
+
+	dac->dma.cdesc = cdesc;
+
+	set_bit(DMA_READY, &dac->flags);
+
+	return 0;
+}
+
+static struct snd_pcm_hardware atmel_abdac_hw = {
+	.info			= (SNDRV_PCM_INFO_MMAP
+				  | SNDRV_PCM_INFO_MMAP_VALID
+				  | SNDRV_PCM_INFO_INTERLEAVED
+				  | SNDRV_PCM_INFO_BLOCK_TRANSFER
+				  | SNDRV_PCM_INFO_RESUME
+				  | SNDRV_PCM_INFO_PAUSE),
+	.formats		= (SNDRV_PCM_FMTBIT_S16_BE),
+	.rates			= (SNDRV_PCM_RATE_KNOT),
+	.rate_min		= RATE_MIN,
+	.rate_max		= RATE_MAX,
+	.channels_min		= 2,
+	.channels_max		= 2,
+	.buffer_bytes_max	= 64 * 4096,
+	.period_bytes_min	= 4096,
+	.period_bytes_max	= 4096,
+	.periods_min		= 6,
+	.periods_max		= 64,
+};
+
+static int atmel_abdac_open(struct snd_pcm_substream *substream)
+{
+	struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
+
+	dac->substream = substream;
+	atmel_abdac_hw.rate_max = dac->rates[dac->rates_num - 1];
+	atmel_abdac_hw.rate_min = dac->rates[0];
+	substream->runtime->hw = atmel_abdac_hw;
+
+	return snd_pcm_hw_constraint_list(substream->runtime, 0,
+			SNDRV_PCM_HW_PARAM_RATE, &dac->constraints_rates);
+}
+
+static int atmel_abdac_close(struct snd_pcm_substream *substream)
+{
+	struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
+	dac->substream = NULL;
+	return 0;
+}
+
+static int atmel_abdac_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *hw_params)
+{
+	struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
+	int retval;
+
+	retval = snd_pcm_lib_malloc_pages(substream,
+			params_buffer_bytes(hw_params));
+	if (retval < 0)
+		return retval;
+	/* snd_pcm_lib_malloc_pages returns 1 if buffer is changed. */
+	if (retval == 1)
+		if (test_and_clear_bit(DMA_READY, &dac->flags))
+			dw_dma_cyclic_free(dac->dma.chan);
+
+	return retval;
+}
+
+static int atmel_abdac_hw_free(struct snd_pcm_substream *substream)
+{
+	struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
+	if (test_and_clear_bit(DMA_READY, &dac->flags))
+		dw_dma_cyclic_free(dac->dma.chan);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int atmel_abdac_prepare(struct snd_pcm_substream *substream)
+{
+	struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
+	int retval;
+
+	retval = clk_set_rate(dac->sample_clk, 256 * substream->runtime->rate);
+	if (retval)
+		return retval;
+
+	if (!test_bit(DMA_READY, &dac->flags))
+		retval = atmel_abdac_prepare_dma(dac, substream, DMA_TO_DEVICE);
+
+	return retval;
+}
+
+static int atmel_abdac_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct atmel_abdac *dac = snd_pcm_substream_chip(substream);
+	int retval = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */
+	case SNDRV_PCM_TRIGGER_RESUME: /* fall through */
+	case SNDRV_PCM_TRIGGER_START:
+		clk_enable(dac->sample_clk);
+		retval = dw_dma_cyclic_start(dac->dma.chan);
+		if (retval)
+			goto out;
+		dac_writel(dac, CTRL, DAC_BIT(EN));
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */
+	case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */
+	case SNDRV_PCM_TRIGGER_STOP:
+		dw_dma_cyclic_stop(dac->dma.chan);
+		dac_writel(dac, DATA, 0);
+		dac_writel(dac, CTRL, 0);
+		clk_disable(dac->sample_clk);
+		break;
+	default:
+		retval = -EINVAL;
+		break;
+	}
+out:
+	return retval;
+}
+
+static snd_pcm_uframes_t
+atmel_abdac_pointer(struct snd_pcm_substream *substream)
+{
+	struct atmel_abdac	*dac = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime	*runtime = substream->runtime;
+	snd_pcm_uframes_t	frames;
+	unsigned long		bytes;
+
+	bytes = dw_dma_get_src_addr(dac->dma.chan);
+	bytes -= runtime->dma_addr;
+
+	frames = bytes_to_frames(runtime, bytes);
+	if (frames >= runtime->buffer_size)
+		frames -= runtime->buffer_size;
+
+	return frames;
+}
+
+static irqreturn_t abdac_interrupt(int irq, void *dev_id)
+{
+	struct atmel_abdac *dac = dev_id;
+	u32 status;
+
+	status = dac_readl(dac, INT_STATUS);
+	if (status & DAC_BIT(UNDERRUN)) {
+		dev_err(&dac->pdev->dev, "underrun detected\n");
+		dac_writel(dac, INT_CLR, DAC_BIT(UNDERRUN));
+	} else {
+		dev_err(&dac->pdev->dev, "spurious interrupt (status=0x%x)\n",
+			status);
+		dac_writel(dac, INT_CLR, status);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static struct snd_pcm_ops atmel_abdac_ops = {
+	.open		= atmel_abdac_open,
+	.close		= atmel_abdac_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= atmel_abdac_hw_params,
+	.hw_free	= atmel_abdac_hw_free,
+	.prepare	= atmel_abdac_prepare,
+	.trigger	= atmel_abdac_trigger,
+	.pointer	= atmel_abdac_pointer,
+};
+
+static int __devinit atmel_abdac_pcm_new(struct atmel_abdac *dac)
+{
+	struct snd_pcm_hardware hw = atmel_abdac_hw;
+	struct snd_pcm *pcm;
+	int retval;
+
+	retval = snd_pcm_new(dac->card, dac->card->shortname,
+			dac->pdev->id, 1, 0, &pcm);
+	if (retval)
+		return retval;
+
+	strcpy(pcm->name, dac->card->shortname);
+	pcm->private_data = dac;
+	pcm->info_flags = 0;
+	dac->pcm = pcm;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &atmel_abdac_ops);
+
+	retval = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+			&dac->pdev->dev, hw.periods_min * hw.period_bytes_min,
+			hw.buffer_bytes_max);
+
+	return retval;
+}
+
+static bool filter(struct dma_chan *chan, void *slave)
+{
+	struct dw_dma_slave *dws = slave;
+
+	if (dws->dma_dev == chan->device->dev) {
+		chan->private = dws;
+		return true;
+	} else
+		return false;
+}
+
+static int set_sample_rates(struct atmel_abdac *dac)
+{
+	long new_rate = RATE_MAX;
+	int retval = -EINVAL;
+	int index = 0;
+
+	/* we start at 192 kHz and work our way down to 5112 Hz */
+	while (new_rate >= RATE_MIN && index < (MAX_NUM_RATES + 1)) {
+		new_rate = clk_round_rate(dac->sample_clk, 256 * new_rate);
+		if (new_rate < 0)
+			break;
+		/* make sure we are below the ABDAC clock */
+		if (new_rate <= clk_get_rate(dac->pclk)) {
+			dac->rates[index] = new_rate / 256;
+			index++;
+		}
+		/* divide by 256 and then by two to get next rate */
+		new_rate /= 256 * 2;
+	}
+
+	if (index) {
+		int i;
+
+		/* reverse array, smallest go first */
+		for (i = 0; i < (index / 2); i++) {
+			unsigned int tmp = dac->rates[index - 1 - i];
+			dac->rates[index - 1 - i] = dac->rates[i];
+			dac->rates[i] = tmp;
+		}
+
+		dac->constraints_rates.count = index;
+		dac->constraints_rates.list = dac->rates;
+		dac->constraints_rates.mask = 0;
+		dac->rates_num = index;
+
+		retval = 0;
+	}
+
+	return retval;
+}
+
+static int __devinit atmel_abdac_probe(struct platform_device *pdev)
+{
+	struct snd_card		*card;
+	struct atmel_abdac	*dac;
+	struct resource		*regs;
+	struct atmel_abdac_pdata	*pdata;
+	struct clk		*pclk;
+	struct clk		*sample_clk;
+	int			retval;
+	int			irq;
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!regs) {
+		dev_dbg(&pdev->dev, "no memory resource\n");
+		return -ENXIO;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_dbg(&pdev->dev, "could not get IRQ number\n");
+		return irq;
+	}
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_dbg(&pdev->dev, "no platform data\n");
+		return -ENXIO;
+	}
+
+	pclk = clk_get(&pdev->dev, "pclk");
+	if (IS_ERR(pclk)) {
+		dev_dbg(&pdev->dev, "no peripheral clock\n");
+		return PTR_ERR(pclk);
+	}
+	sample_clk = clk_get(&pdev->dev, "sample_clk");
+	if (IS_ERR(sample_clk)) {
+		dev_dbg(&pdev->dev, "no sample clock\n");
+		retval = PTR_ERR(sample_clk);
+		goto out_put_pclk;
+	}
+	clk_enable(pclk);
+
+	retval = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+			THIS_MODULE, sizeof(struct atmel_abdac), &card);
+	if (retval) {
+		dev_dbg(&pdev->dev, "could not create sound card device\n");
+		goto out_put_sample_clk;
+	}
+
+	dac = get_dac(card);
+
+	dac->irq = irq;
+	dac->card = card;
+	dac->pclk = pclk;
+	dac->sample_clk = sample_clk;
+	dac->pdev = pdev;
+
+	retval = set_sample_rates(dac);
+	if (retval < 0) {
+		dev_dbg(&pdev->dev, "could not set supported rates\n");
+		goto out_free_card;
+	}
+
+	dac->regs = ioremap(regs->start, regs->end - regs->start + 1);
+	if (!dac->regs) {
+		dev_dbg(&pdev->dev, "could not remap register memory\n");
+		goto out_free_card;
+	}
+
+	/* make sure the DAC is silent and disabled */
+	dac_writel(dac, DATA, 0);
+	dac_writel(dac, CTRL, 0);
+
+	retval = request_irq(irq, abdac_interrupt, 0, "abdac", dac);
+	if (retval) {
+		dev_dbg(&pdev->dev, "could not request irq\n");
+		goto out_unmap_regs;
+	}
+
+	snd_card_set_dev(card, &pdev->dev);
+
+	if (pdata->dws.dma_dev) {
+		struct dw_dma_slave *dws = &pdata->dws;
+		dma_cap_mask_t mask;
+
+		dws->tx_reg = regs->start + DAC_DATA;
+
+		dma_cap_zero(mask);
+		dma_cap_set(DMA_SLAVE, mask);
+
+		dac->dma.chan = dma_request_channel(mask, filter, dws);
+	}
+	if (!pdata->dws.dma_dev || !dac->dma.chan) {
+		dev_dbg(&pdev->dev, "DMA not available\n");
+		retval = -ENODEV;
+		goto out_unset_card_dev;
+	}
+
+	strcpy(card->driver, "Atmel ABDAC");
+	strcpy(card->shortname, "Atmel ABDAC");
+	sprintf(card->longname, "Atmel Audio Bitstream DAC");
+
+	retval = atmel_abdac_pcm_new(dac);
+	if (retval) {
+		dev_dbg(&pdev->dev, "could not register ABDAC pcm device\n");
+		goto out_release_dma;
+	}
+
+	retval = snd_card_register(card);
+	if (retval) {
+		dev_dbg(&pdev->dev, "could not register sound card\n");
+		goto out_release_dma;
+	}
+
+	platform_set_drvdata(pdev, card);
+
+	dev_info(&pdev->dev, "Atmel ABDAC at 0x%p using %s\n",
+			dac->regs, dev_name(&dac->dma.chan->dev->device));
+
+	return retval;
+
+out_release_dma:
+	dma_release_channel(dac->dma.chan);
+	dac->dma.chan = NULL;
+out_unset_card_dev:
+	snd_card_set_dev(card, NULL);
+	free_irq(irq, dac);
+out_unmap_regs:
+	iounmap(dac->regs);
+out_free_card:
+	snd_card_free(card);
+out_put_sample_clk:
+	clk_put(sample_clk);
+	clk_disable(pclk);
+out_put_pclk:
+	clk_put(pclk);
+	return retval;
+}
+
+#ifdef CONFIG_PM
+static int atmel_abdac_suspend(struct platform_device *pdev, pm_message_t msg)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+	struct atmel_abdac *dac = card->private_data;
+
+	dw_dma_cyclic_stop(dac->dma.chan);
+	clk_disable(dac->sample_clk);
+	clk_disable(dac->pclk);
+
+	return 0;
+}
+
+static int atmel_abdac_resume(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+	struct atmel_abdac *dac = card->private_data;
+
+	clk_enable(dac->pclk);
+	clk_enable(dac->sample_clk);
+	if (test_bit(DMA_READY, &dac->flags))
+		dw_dma_cyclic_start(dac->dma.chan);
+
+	return 0;
+}
+#else
+#define atmel_abdac_suspend NULL
+#define atmel_abdac_resume NULL
+#endif
+
+static int __devexit atmel_abdac_remove(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+	struct atmel_abdac *dac = get_dac(card);
+
+	clk_put(dac->sample_clk);
+	clk_disable(dac->pclk);
+	clk_put(dac->pclk);
+
+	dma_release_channel(dac->dma.chan);
+	dac->dma.chan = NULL;
+	snd_card_set_dev(card, NULL);
+	iounmap(dac->regs);
+	free_irq(dac->irq, dac);
+	snd_card_free(card);
+
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver atmel_abdac_driver = {
+	.remove		= __devexit_p(atmel_abdac_remove),
+	.driver		= {
+		.name	= "atmel_abdac",
+	},
+	.suspend	= atmel_abdac_suspend,
+	.resume		= atmel_abdac_resume,
+};
+
+static int __init atmel_abdac_init(void)
+{
+	return platform_driver_probe(&atmel_abdac_driver,
+			atmel_abdac_probe);
+}
+module_init(atmel_abdac_init);
+
+static void __exit atmel_abdac_exit(void)
+{
+	platform_driver_unregister(&atmel_abdac_driver);
+}
+module_exit(atmel_abdac_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Driver for Atmel Audio Bitstream DAC (ABDAC)");
+MODULE_AUTHOR("Hans-Christian Egtvedt <hans-christian.egtvedt@atmel.com>");
diff --git a/linux/sound/atmel/ac97c.c b/linux/sound/atmel/ac97c.c
new file mode 100644
index 0000000..10c3a87
--- /dev/null
+++ b/linux/sound/atmel/ac97c.c
@@ -0,0 +1,1199 @@
+/*
+ * Driver for Atmel AC97C
+ *
+ * Copyright (C) 2005-2009 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/bitmap.h>
+#include <linux/device.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/atmel_pdc.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/ac97_codec.h>
+#include <sound/atmel-ac97c.h>
+#include <sound/memalloc.h>
+
+#include <linux/dw_dmac.h>
+
+#include <mach/cpu.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+
+#include "ac97c.h"
+
+enum {
+	DMA_TX_READY = 0,
+	DMA_RX_READY,
+	DMA_TX_CHAN_PRESENT,
+	DMA_RX_CHAN_PRESENT,
+};
+
+/* Serialize access to opened variable */
+static DEFINE_MUTEX(opened_mutex);
+
+struct atmel_ac97c_dma {
+	struct dma_chan			*rx_chan;
+	struct dma_chan			*tx_chan;
+};
+
+struct atmel_ac97c {
+	struct clk			*pclk;
+	struct platform_device		*pdev;
+	struct atmel_ac97c_dma		dma;
+
+	struct snd_pcm_substream	*playback_substream;
+	struct snd_pcm_substream	*capture_substream;
+	struct snd_card			*card;
+	struct snd_pcm			*pcm;
+	struct snd_ac97			*ac97;
+	struct snd_ac97_bus		*ac97_bus;
+
+	u64				cur_format;
+	unsigned int			cur_rate;
+	unsigned long			flags;
+	int				playback_period, capture_period;
+	/* Serialize access to opened variable */
+	spinlock_t			lock;
+	void __iomem			*regs;
+	int				irq;
+	int				opened;
+	int				reset_pin;
+};
+
+#define get_chip(card) ((struct atmel_ac97c *)(card)->private_data)
+
+#define ac97c_writel(chip, reg, val)			\
+	__raw_writel((val), (chip)->regs + AC97C_##reg)
+#define ac97c_readl(chip, reg)				\
+	__raw_readl((chip)->regs + AC97C_##reg)
+
+/* This function is called by the DMA driver. */
+static void atmel_ac97c_dma_playback_period_done(void *arg)
+{
+	struct atmel_ac97c *chip = arg;
+	snd_pcm_period_elapsed(chip->playback_substream);
+}
+
+static void atmel_ac97c_dma_capture_period_done(void *arg)
+{
+	struct atmel_ac97c *chip = arg;
+	snd_pcm_period_elapsed(chip->capture_substream);
+}
+
+static int atmel_ac97c_prepare_dma(struct atmel_ac97c *chip,
+		struct snd_pcm_substream *substream,
+		enum dma_data_direction direction)
+{
+	struct dma_chan			*chan;
+	struct dw_cyclic_desc		*cdesc;
+	struct snd_pcm_runtime		*runtime = substream->runtime;
+	unsigned long			buffer_len, period_len;
+
+	/*
+	 * We don't do DMA on "complex" transfers, i.e. with
+	 * non-halfword-aligned buffers or lengths.
+	 */
+	if (runtime->dma_addr & 1 || runtime->buffer_size & 1) {
+		dev_dbg(&chip->pdev->dev, "too complex transfer\n");
+		return -EINVAL;
+	}
+
+	if (direction == DMA_TO_DEVICE)
+		chan = chip->dma.tx_chan;
+	else
+		chan = chip->dma.rx_chan;
+
+	buffer_len = frames_to_bytes(runtime, runtime->buffer_size);
+	period_len = frames_to_bytes(runtime, runtime->period_size);
+
+	cdesc = dw_dma_cyclic_prep(chan, runtime->dma_addr, buffer_len,
+			period_len, direction);
+	if (IS_ERR(cdesc)) {
+		dev_dbg(&chip->pdev->dev, "could not prepare cyclic DMA\n");
+		return PTR_ERR(cdesc);
+	}
+
+	if (direction == DMA_TO_DEVICE) {
+		cdesc->period_callback = atmel_ac97c_dma_playback_period_done;
+		set_bit(DMA_TX_READY, &chip->flags);
+	} else {
+		cdesc->period_callback = atmel_ac97c_dma_capture_period_done;
+		set_bit(DMA_RX_READY, &chip->flags);
+	}
+
+	cdesc->period_callback_param = chip;
+
+	return 0;
+}
+
+static struct snd_pcm_hardware atmel_ac97c_hw = {
+	.info			= (SNDRV_PCM_INFO_MMAP
+				  | SNDRV_PCM_INFO_MMAP_VALID
+				  | SNDRV_PCM_INFO_INTERLEAVED
+				  | SNDRV_PCM_INFO_BLOCK_TRANSFER
+				  | SNDRV_PCM_INFO_JOINT_DUPLEX
+				  | SNDRV_PCM_INFO_RESUME
+				  | SNDRV_PCM_INFO_PAUSE),
+	.formats		= (SNDRV_PCM_FMTBIT_S16_BE
+				  | SNDRV_PCM_FMTBIT_S16_LE),
+	.rates			= (SNDRV_PCM_RATE_CONTINUOUS),
+	.rate_min		= 4000,
+	.rate_max		= 48000,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= 2 * 2 * 64 * 2048,
+	.period_bytes_min	= 4096,
+	.period_bytes_max	= 4096,
+	.periods_min		= 6,
+	.periods_max		= 64,
+};
+
+static int atmel_ac97c_playback_open(struct snd_pcm_substream *substream)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	mutex_lock(&opened_mutex);
+	chip->opened++;
+	runtime->hw = atmel_ac97c_hw;
+	if (chip->cur_rate) {
+		runtime->hw.rate_min = chip->cur_rate;
+		runtime->hw.rate_max = chip->cur_rate;
+	}
+	if (chip->cur_format)
+		runtime->hw.formats = (1ULL << chip->cur_format);
+	mutex_unlock(&opened_mutex);
+	chip->playback_substream = substream;
+	return 0;
+}
+
+static int atmel_ac97c_capture_open(struct snd_pcm_substream *substream)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	mutex_lock(&opened_mutex);
+	chip->opened++;
+	runtime->hw = atmel_ac97c_hw;
+	if (chip->cur_rate) {
+		runtime->hw.rate_min = chip->cur_rate;
+		runtime->hw.rate_max = chip->cur_rate;
+	}
+	if (chip->cur_format)
+		runtime->hw.formats = (1ULL << chip->cur_format);
+	mutex_unlock(&opened_mutex);
+	chip->capture_substream = substream;
+	return 0;
+}
+
+static int atmel_ac97c_playback_close(struct snd_pcm_substream *substream)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+
+	mutex_lock(&opened_mutex);
+	chip->opened--;
+	if (!chip->opened) {
+		chip->cur_rate = 0;
+		chip->cur_format = 0;
+	}
+	mutex_unlock(&opened_mutex);
+
+	chip->playback_substream = NULL;
+
+	return 0;
+}
+
+static int atmel_ac97c_capture_close(struct snd_pcm_substream *substream)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+
+	mutex_lock(&opened_mutex);
+	chip->opened--;
+	if (!chip->opened) {
+		chip->cur_rate = 0;
+		chip->cur_format = 0;
+	}
+	mutex_unlock(&opened_mutex);
+
+	chip->capture_substream = NULL;
+
+	return 0;
+}
+
+static int atmel_ac97c_playback_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *hw_params)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+	int retval;
+
+	retval = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	if (retval < 0)
+		return retval;
+	/* snd_pcm_lib_malloc_pages returns 1 if buffer is changed. */
+	if (cpu_is_at32ap7000()) {
+		/* snd_pcm_lib_malloc_pages returns 1 if buffer is changed. */
+		if (retval == 1)
+			if (test_and_clear_bit(DMA_TX_READY, &chip->flags))
+				dw_dma_cyclic_free(chip->dma.tx_chan);
+	}
+	/* Set restrictions to params. */
+	mutex_lock(&opened_mutex);
+	chip->cur_rate = params_rate(hw_params);
+	chip->cur_format = params_format(hw_params);
+	mutex_unlock(&opened_mutex);
+
+	return retval;
+}
+
+static int atmel_ac97c_capture_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *hw_params)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+	int retval;
+
+	retval = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	if (retval < 0)
+		return retval;
+	/* snd_pcm_lib_malloc_pages returns 1 if buffer is changed. */
+	if (cpu_is_at32ap7000()) {
+		if (retval < 0)
+			return retval;
+		/* snd_pcm_lib_malloc_pages returns 1 if buffer is changed. */
+		if (retval == 1)
+			if (test_and_clear_bit(DMA_RX_READY, &chip->flags))
+				dw_dma_cyclic_free(chip->dma.rx_chan);
+	}
+
+	/* Set restrictions to params. */
+	mutex_lock(&opened_mutex);
+	chip->cur_rate = params_rate(hw_params);
+	chip->cur_format = params_format(hw_params);
+	mutex_unlock(&opened_mutex);
+
+	return retval;
+}
+
+static int atmel_ac97c_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+	if (cpu_is_at32ap7000()) {
+		if (test_and_clear_bit(DMA_TX_READY, &chip->flags))
+			dw_dma_cyclic_free(chip->dma.tx_chan);
+	}
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int atmel_ac97c_capture_hw_free(struct snd_pcm_substream *substream)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+	if (cpu_is_at32ap7000()) {
+		if (test_and_clear_bit(DMA_RX_READY, &chip->flags))
+			dw_dma_cyclic_free(chip->dma.rx_chan);
+	}
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int atmel_ac97c_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int block_size = frames_to_bytes(runtime, runtime->period_size);
+	unsigned long word = ac97c_readl(chip, OCA);
+	int retval;
+
+	chip->playback_period = 0;
+	word &= ~(AC97C_CH_MASK(PCM_LEFT) | AC97C_CH_MASK(PCM_RIGHT));
+
+	/* assign channels to AC97C channel A */
+	switch (runtime->channels) {
+	case 1:
+		word |= AC97C_CH_ASSIGN(PCM_LEFT, A);
+		break;
+	case 2:
+		word |= AC97C_CH_ASSIGN(PCM_LEFT, A)
+			| AC97C_CH_ASSIGN(PCM_RIGHT, A);
+		break;
+	default:
+		/* TODO: support more than two channels */
+		return -EINVAL;
+	}
+	ac97c_writel(chip, OCA, word);
+
+	/* configure sample format and size */
+	word = ac97c_readl(chip, CAMR);
+	if (chip->opened <= 1)
+		word = AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16;
+	else
+		word |= AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16;
+
+	switch (runtime->format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		if (cpu_is_at32ap7000())
+			word |= AC97C_CMR_CEM_LITTLE;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE: /* fall through */
+		word &= ~(AC97C_CMR_CEM_LITTLE);
+		break;
+	default:
+		word = ac97c_readl(chip, OCA);
+		word &= ~(AC97C_CH_MASK(PCM_LEFT) | AC97C_CH_MASK(PCM_RIGHT));
+		ac97c_writel(chip, OCA, word);
+		return -EINVAL;
+	}
+
+	/* Enable underrun interrupt on channel A */
+	word |= AC97C_CSR_UNRUN;
+
+	ac97c_writel(chip, CAMR, word);
+
+	/* Enable channel A event interrupt */
+	word = ac97c_readl(chip, IMR);
+	word |= AC97C_SR_CAEVT;
+	ac97c_writel(chip, IER, word);
+
+	/* set variable rate if needed */
+	if (runtime->rate != 48000) {
+		word = ac97c_readl(chip, MR);
+		word |= AC97C_MR_VRA;
+		ac97c_writel(chip, MR, word);
+	} else {
+		word = ac97c_readl(chip, MR);
+		word &= ~(AC97C_MR_VRA);
+		ac97c_writel(chip, MR, word);
+	}
+
+	retval = snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE,
+			runtime->rate);
+	if (retval)
+		dev_dbg(&chip->pdev->dev, "could not set rate %d Hz\n",
+				runtime->rate);
+
+	if (cpu_is_at32ap7000()) {
+		if (!test_bit(DMA_TX_READY, &chip->flags))
+			retval = atmel_ac97c_prepare_dma(chip, substream,
+					DMA_TO_DEVICE);
+	} else {
+		/* Initialize and start the PDC */
+		writel(runtime->dma_addr, chip->regs + ATMEL_PDC_TPR);
+		writel(block_size / 2, chip->regs + ATMEL_PDC_TCR);
+		writel(runtime->dma_addr + block_size,
+				chip->regs + ATMEL_PDC_TNPR);
+		writel(block_size / 2, chip->regs + ATMEL_PDC_TNCR);
+	}
+
+	return retval;
+}
+
+static int atmel_ac97c_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int block_size = frames_to_bytes(runtime, runtime->period_size);
+	unsigned long word = ac97c_readl(chip, ICA);
+	int retval;
+
+	chip->capture_period = 0;
+	word &= ~(AC97C_CH_MASK(PCM_LEFT) | AC97C_CH_MASK(PCM_RIGHT));
+
+	/* assign channels to AC97C channel A */
+	switch (runtime->channels) {
+	case 1:
+		word |= AC97C_CH_ASSIGN(PCM_LEFT, A);
+		break;
+	case 2:
+		word |= AC97C_CH_ASSIGN(PCM_LEFT, A)
+			| AC97C_CH_ASSIGN(PCM_RIGHT, A);
+		break;
+	default:
+		/* TODO: support more than two channels */
+		return -EINVAL;
+	}
+	ac97c_writel(chip, ICA, word);
+
+	/* configure sample format and size */
+	word = ac97c_readl(chip, CAMR);
+	if (chip->opened <= 1)
+		word = AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16;
+	else
+		word |= AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16;
+
+	switch (runtime->format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		if (cpu_is_at32ap7000())
+			word |= AC97C_CMR_CEM_LITTLE;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE: /* fall through */
+		word &= ~(AC97C_CMR_CEM_LITTLE);
+		break;
+	default:
+		word = ac97c_readl(chip, ICA);
+		word &= ~(AC97C_CH_MASK(PCM_LEFT) | AC97C_CH_MASK(PCM_RIGHT));
+		ac97c_writel(chip, ICA, word);
+		return -EINVAL;
+	}
+
+	/* Enable overrun interrupt on channel A */
+	word |= AC97C_CSR_OVRUN;
+
+	ac97c_writel(chip, CAMR, word);
+
+	/* Enable channel A event interrupt */
+	word = ac97c_readl(chip, IMR);
+	word |= AC97C_SR_CAEVT;
+	ac97c_writel(chip, IER, word);
+
+	/* set variable rate if needed */
+	if (runtime->rate != 48000) {
+		word = ac97c_readl(chip, MR);
+		word |= AC97C_MR_VRA;
+		ac97c_writel(chip, MR, word);
+	} else {
+		word = ac97c_readl(chip, MR);
+		word &= ~(AC97C_MR_VRA);
+		ac97c_writel(chip, MR, word);
+	}
+
+	retval = snd_ac97_set_rate(chip->ac97, AC97_PCM_LR_ADC_RATE,
+			runtime->rate);
+	if (retval)
+		dev_dbg(&chip->pdev->dev, "could not set rate %d Hz\n",
+				runtime->rate);
+
+	if (cpu_is_at32ap7000()) {
+		if (!test_bit(DMA_RX_READY, &chip->flags))
+			retval = atmel_ac97c_prepare_dma(chip, substream,
+					DMA_FROM_DEVICE);
+	} else {
+		/* Initialize and start the PDC */
+		writel(runtime->dma_addr, chip->regs + ATMEL_PDC_RPR);
+		writel(block_size / 2, chip->regs + ATMEL_PDC_RCR);
+		writel(runtime->dma_addr + block_size,
+				chip->regs + ATMEL_PDC_RNPR);
+		writel(block_size / 2, chip->regs + ATMEL_PDC_RNCR);
+	}
+
+	return retval;
+}
+
+static int
+atmel_ac97c_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+	unsigned long camr, ptcr = 0;
+	int retval = 0;
+
+	camr = ac97c_readl(chip, CAMR);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */
+	case SNDRV_PCM_TRIGGER_RESUME: /* fall through */
+	case SNDRV_PCM_TRIGGER_START:
+		if (cpu_is_at32ap7000()) {
+			retval = dw_dma_cyclic_start(chip->dma.tx_chan);
+			if (retval)
+				goto out;
+		} else {
+			ptcr = ATMEL_PDC_TXTEN;
+		}
+		camr |= AC97C_CMR_CENA | AC97C_CSR_ENDTX;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */
+	case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (cpu_is_at32ap7000())
+			dw_dma_cyclic_stop(chip->dma.tx_chan);
+		else
+			ptcr |= ATMEL_PDC_TXTDIS;
+		if (chip->opened <= 1)
+			camr &= ~AC97C_CMR_CENA;
+		break;
+	default:
+		retval = -EINVAL;
+		goto out;
+	}
+
+	ac97c_writel(chip, CAMR, camr);
+	if (!cpu_is_at32ap7000())
+		writel(ptcr, chip->regs + ATMEL_PDC_PTCR);
+out:
+	return retval;
+}
+
+static int
+atmel_ac97c_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct atmel_ac97c *chip = snd_pcm_substream_chip(substream);
+	unsigned long camr, ptcr = 0;
+	int retval = 0;
+
+	camr = ac97c_readl(chip, CAMR);
+	ptcr = readl(chip->regs + ATMEL_PDC_PTSR);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */
+	case SNDRV_PCM_TRIGGER_RESUME: /* fall through */
+	case SNDRV_PCM_TRIGGER_START:
+		if (cpu_is_at32ap7000()) {
+			retval = dw_dma_cyclic_start(chip->dma.rx_chan);
+			if (retval)
+				goto out;
+		} else {
+			ptcr = ATMEL_PDC_RXTEN;
+		}
+		camr |= AC97C_CMR_CENA | AC97C_CSR_ENDRX;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */
+	case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (cpu_is_at32ap7000())
+			dw_dma_cyclic_stop(chip->dma.rx_chan);
+		else
+			ptcr |= (ATMEL_PDC_RXTDIS);
+		if (chip->opened <= 1)
+			camr &= ~AC97C_CMR_CENA;
+		break;
+	default:
+		retval = -EINVAL;
+		break;
+	}
+
+	ac97c_writel(chip, CAMR, camr);
+	if (!cpu_is_at32ap7000())
+		writel(ptcr, chip->regs + ATMEL_PDC_PTCR);
+out:
+	return retval;
+}
+
+static snd_pcm_uframes_t
+atmel_ac97c_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct atmel_ac97c	*chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime	*runtime = substream->runtime;
+	snd_pcm_uframes_t	frames;
+	unsigned long		bytes;
+
+	if (cpu_is_at32ap7000())
+		bytes = dw_dma_get_src_addr(chip->dma.tx_chan);
+	else
+		bytes = readl(chip->regs + ATMEL_PDC_TPR);
+	bytes -= runtime->dma_addr;
+
+	frames = bytes_to_frames(runtime, bytes);
+	if (frames >= runtime->buffer_size)
+		frames -= runtime->buffer_size;
+	return frames;
+}
+
+static snd_pcm_uframes_t
+atmel_ac97c_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct atmel_ac97c	*chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime	*runtime = substream->runtime;
+	snd_pcm_uframes_t	frames;
+	unsigned long		bytes;
+
+	if (cpu_is_at32ap7000())
+		bytes = dw_dma_get_dst_addr(chip->dma.rx_chan);
+	else
+		bytes = readl(chip->regs + ATMEL_PDC_RPR);
+	bytes -= runtime->dma_addr;
+
+	frames = bytes_to_frames(runtime, bytes);
+	if (frames >= runtime->buffer_size)
+		frames -= runtime->buffer_size;
+	return frames;
+}
+
+static struct snd_pcm_ops atmel_ac97_playback_ops = {
+	.open		= atmel_ac97c_playback_open,
+	.close		= atmel_ac97c_playback_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= atmel_ac97c_playback_hw_params,
+	.hw_free	= atmel_ac97c_playback_hw_free,
+	.prepare	= atmel_ac97c_playback_prepare,
+	.trigger	= atmel_ac97c_playback_trigger,
+	.pointer	= atmel_ac97c_playback_pointer,
+};
+
+static struct snd_pcm_ops atmel_ac97_capture_ops = {
+	.open		= atmel_ac97c_capture_open,
+	.close		= atmel_ac97c_capture_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= atmel_ac97c_capture_hw_params,
+	.hw_free	= atmel_ac97c_capture_hw_free,
+	.prepare	= atmel_ac97c_capture_prepare,
+	.trigger	= atmel_ac97c_capture_trigger,
+	.pointer	= atmel_ac97c_capture_pointer,
+};
+
+static irqreturn_t atmel_ac97c_interrupt(int irq, void *dev)
+{
+	struct atmel_ac97c	*chip  = (struct atmel_ac97c *)dev;
+	irqreturn_t		retval = IRQ_NONE;
+	u32			sr     = ac97c_readl(chip, SR);
+	u32			casr   = ac97c_readl(chip, CASR);
+	u32			cosr   = ac97c_readl(chip, COSR);
+	u32			camr   = ac97c_readl(chip, CAMR);
+
+	if (sr & AC97C_SR_CAEVT) {
+		struct snd_pcm_runtime *runtime;
+		int offset, next_period, block_size;
+		dev_dbg(&chip->pdev->dev, "channel A event%s%s%s%s%s%s\n",
+				casr & AC97C_CSR_OVRUN   ? " OVRUN"   : "",
+				casr & AC97C_CSR_RXRDY   ? " RXRDY"   : "",
+				casr & AC97C_CSR_UNRUN   ? " UNRUN"   : "",
+				casr & AC97C_CSR_TXEMPTY ? " TXEMPTY" : "",
+				casr & AC97C_CSR_TXRDY   ? " TXRDY"   : "",
+				!casr                    ? " NONE"    : "");
+		if (!cpu_is_at32ap7000()) {
+			if ((casr & camr) & AC97C_CSR_ENDTX) {
+				runtime = chip->playback_substream->runtime;
+				block_size = frames_to_bytes(runtime,
+						runtime->period_size);
+				chip->playback_period++;
+
+				if (chip->playback_period == runtime->periods)
+					chip->playback_period = 0;
+				next_period = chip->playback_period + 1;
+				if (next_period == runtime->periods)
+					next_period = 0;
+
+				offset = block_size * next_period;
+
+				writel(runtime->dma_addr + offset,
+						chip->regs + ATMEL_PDC_TNPR);
+				writel(block_size / 2,
+						chip->regs + ATMEL_PDC_TNCR);
+
+				snd_pcm_period_elapsed(
+						chip->playback_substream);
+			}
+			if ((casr & camr) & AC97C_CSR_ENDRX) {
+				runtime = chip->capture_substream->runtime;
+				block_size = frames_to_bytes(runtime,
+						runtime->period_size);
+				chip->capture_period++;
+
+				if (chip->capture_period == runtime->periods)
+					chip->capture_period = 0;
+				next_period = chip->capture_period + 1;
+				if (next_period == runtime->periods)
+					next_period = 0;
+
+				offset = block_size * next_period;
+
+				writel(runtime->dma_addr + offset,
+						chip->regs + ATMEL_PDC_RNPR);
+				writel(block_size / 2,
+						chip->regs + ATMEL_PDC_RNCR);
+				snd_pcm_period_elapsed(chip->capture_substream);
+			}
+		}
+		retval = IRQ_HANDLED;
+	}
+
+	if (sr & AC97C_SR_COEVT) {
+		dev_info(&chip->pdev->dev, "codec channel event%s%s%s%s%s\n",
+				cosr & AC97C_CSR_OVRUN   ? " OVRUN"   : "",
+				cosr & AC97C_CSR_RXRDY   ? " RXRDY"   : "",
+				cosr & AC97C_CSR_TXEMPTY ? " TXEMPTY" : "",
+				cosr & AC97C_CSR_TXRDY   ? " TXRDY"   : "",
+				!cosr                    ? " NONE"    : "");
+		retval = IRQ_HANDLED;
+	}
+
+	if (retval == IRQ_NONE) {
+		dev_err(&chip->pdev->dev, "spurious interrupt sr 0x%08x "
+				"casr 0x%08x cosr 0x%08x\n", sr, casr, cosr);
+	}
+
+	return retval;
+}
+
+static struct ac97_pcm at91_ac97_pcm_defs[] __devinitdata = {
+	/* Playback */
+	{
+		.exclusive = 1,
+		.r = { {
+			.slots = ((1 << AC97_SLOT_PCM_LEFT)
+				  | (1 << AC97_SLOT_PCM_RIGHT)),
+		} },
+	},
+	/* PCM in */
+	{
+		.stream = 1,
+		.exclusive = 1,
+		.r = { {
+			.slots = ((1 << AC97_SLOT_PCM_LEFT)
+					| (1 << AC97_SLOT_PCM_RIGHT)),
+		} }
+	},
+	/* Mic in */
+	{
+		.stream = 1,
+		.exclusive = 1,
+		.r = { {
+			.slots = (1<<AC97_SLOT_MIC),
+		} }
+	},
+};
+
+static int __devinit atmel_ac97c_pcm_new(struct atmel_ac97c *chip)
+{
+	struct snd_pcm		*pcm;
+	struct snd_pcm_hardware	hw = atmel_ac97c_hw;
+	int			capture, playback, retval, err;
+
+	capture = test_bit(DMA_RX_CHAN_PRESENT, &chip->flags);
+	playback = test_bit(DMA_TX_CHAN_PRESENT, &chip->flags);
+
+	if (!cpu_is_at32ap7000()) {
+		err = snd_ac97_pcm_assign(chip->ac97_bus,
+				ARRAY_SIZE(at91_ac97_pcm_defs),
+				at91_ac97_pcm_defs);
+		if (err)
+			return err;
+	}
+	retval = snd_pcm_new(chip->card, chip->card->shortname,
+			chip->pdev->id, playback, capture, &pcm);
+	if (retval)
+		return retval;
+
+	if (capture)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&atmel_ac97_capture_ops);
+	if (playback)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				&atmel_ac97_playback_ops);
+
+	retval = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+			&chip->pdev->dev, hw.periods_min * hw.period_bytes_min,
+			hw.buffer_bytes_max);
+	if (retval)
+		return retval;
+
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, chip->card->shortname);
+	chip->pcm = pcm;
+
+	return 0;
+}
+
+static int atmel_ac97c_mixer_new(struct atmel_ac97c *chip)
+{
+	struct snd_ac97_template template;
+	memset(&template, 0, sizeof(template));
+	template.private_data = chip;
+	return snd_ac97_mixer(chip->ac97_bus, &template, &chip->ac97);
+}
+
+static void atmel_ac97c_write(struct snd_ac97 *ac97, unsigned short reg,
+		unsigned short val)
+{
+	struct atmel_ac97c *chip = get_chip(ac97);
+	unsigned long word;
+	int timeout = 40;
+
+	word = (reg & 0x7f) << 16 | val;
+
+	do {
+		if (ac97c_readl(chip, COSR) & AC97C_CSR_TXRDY) {
+			ac97c_writel(chip, COTHR, word);
+			return;
+		}
+		udelay(1);
+	} while (--timeout);
+
+	dev_dbg(&chip->pdev->dev, "codec write timeout\n");
+}
+
+static unsigned short atmel_ac97c_read(struct snd_ac97 *ac97,
+		unsigned short reg)
+{
+	struct atmel_ac97c *chip = get_chip(ac97);
+	unsigned long word;
+	int timeout = 40;
+	int write = 10;
+
+	word = (0x80 | (reg & 0x7f)) << 16;
+
+	if ((ac97c_readl(chip, COSR) & AC97C_CSR_RXRDY) != 0)
+		ac97c_readl(chip, CORHR);
+
+retry_write:
+	timeout = 40;
+
+	do {
+		if ((ac97c_readl(chip, COSR) & AC97C_CSR_TXRDY) != 0) {
+			ac97c_writel(chip, COTHR, word);
+			goto read_reg;
+		}
+		udelay(10);
+	} while (--timeout);
+
+	if (!--write)
+		goto timed_out;
+	goto retry_write;
+
+read_reg:
+	do {
+		if ((ac97c_readl(chip, COSR) & AC97C_CSR_RXRDY) != 0) {
+			unsigned short val = ac97c_readl(chip, CORHR);
+			return val;
+		}
+		udelay(10);
+	} while (--timeout);
+
+	if (!--write)
+		goto timed_out;
+	goto retry_write;
+
+timed_out:
+	dev_dbg(&chip->pdev->dev, "codec read timeout\n");
+	return 0xffff;
+}
+
+static bool filter(struct dma_chan *chan, void *slave)
+{
+	struct dw_dma_slave *dws = slave;
+
+	if (dws->dma_dev == chan->device->dev) {
+		chan->private = dws;
+		return true;
+	} else
+		return false;
+}
+
+static void atmel_ac97c_reset(struct atmel_ac97c *chip)
+{
+	ac97c_writel(chip, MR,   0);
+	ac97c_writel(chip, MR,   AC97C_MR_ENA);
+	ac97c_writel(chip, CAMR, 0);
+	ac97c_writel(chip, COMR, 0);
+
+	if (gpio_is_valid(chip->reset_pin)) {
+		gpio_set_value(chip->reset_pin, 0);
+		/* AC97 v2.2 specifications says minimum 1 us. */
+		udelay(2);
+		gpio_set_value(chip->reset_pin, 1);
+	}
+}
+
+static int __devinit atmel_ac97c_probe(struct platform_device *pdev)
+{
+	struct snd_card			*card;
+	struct atmel_ac97c		*chip;
+	struct resource			*regs;
+	struct ac97c_platform_data	*pdata;
+	struct clk			*pclk;
+	static struct snd_ac97_bus_ops	ops = {
+		.write	= atmel_ac97c_write,
+		.read	= atmel_ac97c_read,
+	};
+	int				retval;
+	int				irq;
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!regs) {
+		dev_dbg(&pdev->dev, "no memory resource\n");
+		return -ENXIO;
+	}
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_dbg(&pdev->dev, "no platform data\n");
+		return -ENXIO;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_dbg(&pdev->dev, "could not get irq\n");
+		return -ENXIO;
+	}
+
+	if (cpu_is_at32ap7000()) {
+		pclk = clk_get(&pdev->dev, "pclk");
+	} else {
+		pclk = clk_get(&pdev->dev, "ac97_clk");
+	}
+
+	if (IS_ERR(pclk)) {
+		dev_dbg(&pdev->dev, "no peripheral clock\n");
+		return PTR_ERR(pclk);
+	}
+	clk_enable(pclk);
+
+	retval = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+			THIS_MODULE, sizeof(struct atmel_ac97c), &card);
+	if (retval) {
+		dev_dbg(&pdev->dev, "could not create sound card device\n");
+		goto err_snd_card_new;
+	}
+
+	chip = get_chip(card);
+
+	retval = request_irq(irq, atmel_ac97c_interrupt, 0, "AC97C", chip);
+	if (retval) {
+		dev_dbg(&pdev->dev, "unable to request irq %d\n", irq);
+		goto err_request_irq;
+	}
+	chip->irq = irq;
+
+	spin_lock_init(&chip->lock);
+
+	strcpy(card->driver, "Atmel AC97C");
+	strcpy(card->shortname, "Atmel AC97C");
+	sprintf(card->longname, "Atmel AC97 controller");
+
+	chip->card = card;
+	chip->pclk = pclk;
+	chip->pdev = pdev;
+	chip->regs = ioremap(regs->start, regs->end - regs->start + 1);
+
+	if (!chip->regs) {
+		dev_dbg(&pdev->dev, "could not remap register memory\n");
+		goto err_ioremap;
+	}
+
+	if (gpio_is_valid(pdata->reset_pin)) {
+		if (gpio_request(pdata->reset_pin, "reset_pin")) {
+			dev_dbg(&pdev->dev, "reset pin not available\n");
+			chip->reset_pin = -ENODEV;
+		} else {
+			gpio_direction_output(pdata->reset_pin, 1);
+			chip->reset_pin = pdata->reset_pin;
+		}
+	}
+
+	snd_card_set_dev(card, &pdev->dev);
+
+	atmel_ac97c_reset(chip);
+
+	/* Enable overrun interrupt from codec channel */
+	ac97c_writel(chip, COMR, AC97C_CSR_OVRUN);
+	ac97c_writel(chip, IER, ac97c_readl(chip, IMR) | AC97C_SR_COEVT);
+
+	retval = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus);
+	if (retval) {
+		dev_dbg(&pdev->dev, "could not register on ac97 bus\n");
+		goto err_ac97_bus;
+	}
+
+	retval = atmel_ac97c_mixer_new(chip);
+	if (retval) {
+		dev_dbg(&pdev->dev, "could not register ac97 mixer\n");
+		goto err_ac97_bus;
+	}
+
+	if (cpu_is_at32ap7000()) {
+		if (pdata->rx_dws.dma_dev) {
+			struct dw_dma_slave *dws = &pdata->rx_dws;
+			dma_cap_mask_t mask;
+
+			dws->rx_reg = regs->start + AC97C_CARHR + 2;
+
+			dma_cap_zero(mask);
+			dma_cap_set(DMA_SLAVE, mask);
+
+			chip->dma.rx_chan = dma_request_channel(mask, filter,
+								dws);
+
+			dev_info(&chip->pdev->dev, "using %s for DMA RX\n",
+				dev_name(&chip->dma.rx_chan->dev->device));
+			set_bit(DMA_RX_CHAN_PRESENT, &chip->flags);
+		}
+
+		if (pdata->tx_dws.dma_dev) {
+			struct dw_dma_slave *dws = &pdata->tx_dws;
+			dma_cap_mask_t mask;
+
+			dws->tx_reg = regs->start + AC97C_CATHR + 2;
+
+			dma_cap_zero(mask);
+			dma_cap_set(DMA_SLAVE, mask);
+
+			chip->dma.tx_chan = dma_request_channel(mask, filter,
+								dws);
+
+			dev_info(&chip->pdev->dev, "using %s for DMA TX\n",
+				dev_name(&chip->dma.tx_chan->dev->device));
+			set_bit(DMA_TX_CHAN_PRESENT, &chip->flags);
+		}
+
+		if (!test_bit(DMA_RX_CHAN_PRESENT, &chip->flags) &&
+				!test_bit(DMA_TX_CHAN_PRESENT, &chip->flags)) {
+			dev_dbg(&pdev->dev, "DMA not available\n");
+			retval = -ENODEV;
+			goto err_dma;
+		}
+	} else {
+		/* Just pretend that we have DMA channel(for at91 i is actually
+		 * the PDC) */
+		set_bit(DMA_RX_CHAN_PRESENT, &chip->flags);
+		set_bit(DMA_TX_CHAN_PRESENT, &chip->flags);
+	}
+
+	retval = atmel_ac97c_pcm_new(chip);
+	if (retval) {
+		dev_dbg(&pdev->dev, "could not register ac97 pcm device\n");
+		goto err_dma;
+	}
+
+	retval = snd_card_register(card);
+	if (retval) {
+		dev_dbg(&pdev->dev, "could not register sound card\n");
+		goto err_dma;
+	}
+
+	platform_set_drvdata(pdev, card);
+
+	dev_info(&pdev->dev, "Atmel AC97 controller at 0x%p, irq = %d\n",
+			chip->regs, irq);
+
+	return 0;
+
+err_dma:
+	if (cpu_is_at32ap7000()) {
+		if (test_bit(DMA_RX_CHAN_PRESENT, &chip->flags))
+			dma_release_channel(chip->dma.rx_chan);
+		if (test_bit(DMA_TX_CHAN_PRESENT, &chip->flags))
+			dma_release_channel(chip->dma.tx_chan);
+		clear_bit(DMA_RX_CHAN_PRESENT, &chip->flags);
+		clear_bit(DMA_TX_CHAN_PRESENT, &chip->flags);
+		chip->dma.rx_chan = NULL;
+		chip->dma.tx_chan = NULL;
+	}
+err_ac97_bus:
+	snd_card_set_dev(card, NULL);
+
+	if (gpio_is_valid(chip->reset_pin))
+		gpio_free(chip->reset_pin);
+
+	iounmap(chip->regs);
+err_ioremap:
+	free_irq(irq, chip);
+err_request_irq:
+	snd_card_free(card);
+err_snd_card_new:
+	clk_disable(pclk);
+	clk_put(pclk);
+	return retval;
+}
+
+#ifdef CONFIG_PM
+static int atmel_ac97c_suspend(struct platform_device *pdev, pm_message_t msg)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+	struct atmel_ac97c *chip = card->private_data;
+
+	if (cpu_is_at32ap7000()) {
+		if (test_bit(DMA_RX_READY, &chip->flags))
+			dw_dma_cyclic_stop(chip->dma.rx_chan);
+		if (test_bit(DMA_TX_READY, &chip->flags))
+			dw_dma_cyclic_stop(chip->dma.tx_chan);
+	}
+	clk_disable(chip->pclk);
+
+	return 0;
+}
+
+static int atmel_ac97c_resume(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+	struct atmel_ac97c *chip = card->private_data;
+
+	clk_enable(chip->pclk);
+	if (cpu_is_at32ap7000()) {
+		if (test_bit(DMA_RX_READY, &chip->flags))
+			dw_dma_cyclic_start(chip->dma.rx_chan);
+		if (test_bit(DMA_TX_READY, &chip->flags))
+			dw_dma_cyclic_start(chip->dma.tx_chan);
+	}
+	return 0;
+}
+#else
+#define atmel_ac97c_suspend NULL
+#define atmel_ac97c_resume NULL
+#endif
+
+static int __devexit atmel_ac97c_remove(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+	struct atmel_ac97c *chip = get_chip(card);
+
+	if (gpio_is_valid(chip->reset_pin))
+		gpio_free(chip->reset_pin);
+
+	ac97c_writel(chip, CAMR, 0);
+	ac97c_writel(chip, COMR, 0);
+	ac97c_writel(chip, MR,   0);
+
+	clk_disable(chip->pclk);
+	clk_put(chip->pclk);
+	iounmap(chip->regs);
+	free_irq(chip->irq, chip);
+
+	if (cpu_is_at32ap7000()) {
+		if (test_bit(DMA_RX_CHAN_PRESENT, &chip->flags))
+			dma_release_channel(chip->dma.rx_chan);
+		if (test_bit(DMA_TX_CHAN_PRESENT, &chip->flags))
+			dma_release_channel(chip->dma.tx_chan);
+		clear_bit(DMA_RX_CHAN_PRESENT, &chip->flags);
+		clear_bit(DMA_TX_CHAN_PRESENT, &chip->flags);
+		chip->dma.rx_chan = NULL;
+		chip->dma.tx_chan = NULL;
+	}
+
+	snd_card_set_dev(card, NULL);
+	snd_card_free(card);
+
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver atmel_ac97c_driver = {
+	.remove		= __devexit_p(atmel_ac97c_remove),
+	.driver		= {
+		.name	= "atmel_ac97c",
+	},
+	.suspend	= atmel_ac97c_suspend,
+	.resume		= atmel_ac97c_resume,
+};
+
+static int __init atmel_ac97c_init(void)
+{
+	return platform_driver_probe(&atmel_ac97c_driver,
+			atmel_ac97c_probe);
+}
+module_init(atmel_ac97c_init);
+
+static void __exit atmel_ac97c_exit(void)
+{
+	platform_driver_unregister(&atmel_ac97c_driver);
+}
+module_exit(atmel_ac97c_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Driver for Atmel AC97 controller");
+MODULE_AUTHOR("Hans-Christian Egtvedt <hans-christian.egtvedt@atmel.com>");
diff --git a/linux/sound/atmel/ac97c.h b/linux/sound/atmel/ac97c.h
new file mode 100644
index 0000000..ecbba50
--- /dev/null
+++ b/linux/sound/atmel/ac97c.h
@@ -0,0 +1,73 @@
+/*
+ * Register definitions for Atmel AC97C
+ *
+ * Copyright (C) 2005-2009 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+#ifndef __SOUND_ATMEL_AC97C_H
+#define __SOUND_ATMEL_AC97C_H
+
+#define AC97C_MR		0x08
+#define AC97C_ICA		0x10
+#define AC97C_OCA		0x14
+#define AC97C_CARHR		0x20
+#define AC97C_CATHR		0x24
+#define AC97C_CASR		0x28
+#define AC97C_CAMR		0x2c
+#define AC97C_CORHR		0x40
+#define AC97C_COTHR		0x44
+#define AC97C_COSR		0x48
+#define AC97C_COMR		0x4c
+#define AC97C_SR		0x50
+#define AC97C_IER		0x54
+#define AC97C_IDR		0x58
+#define AC97C_IMR		0x5c
+#define AC97C_VERSION		0xfc
+
+#define AC97C_CATPR		PDC_TPR
+#define AC97C_CATCR		PDC_TCR
+#define AC97C_CATNPR		PDC_TNPR
+#define AC97C_CATNCR		PDC_TNCR
+#define AC97C_CARPR		PDC_RPR
+#define AC97C_CARCR		PDC_RCR
+#define AC97C_CARNPR		PDC_RNPR
+#define AC97C_CARNCR		PDC_RNCR
+#define AC97C_PTCR		PDC_PTCR
+
+#define AC97C_MR_ENA		(1 << 0)
+#define AC97C_MR_WRST		(1 << 1)
+#define AC97C_MR_VRA		(1 << 2)
+
+#define AC97C_CSR_TXRDY		(1 << 0)
+#define AC97C_CSR_TXEMPTY	(1 << 1)
+#define AC97C_CSR_UNRUN		(1 << 2)
+#define AC97C_CSR_RXRDY		(1 << 4)
+#define AC97C_CSR_OVRUN		(1 << 5)
+#define AC97C_CSR_ENDTX		(1 << 10)
+#define AC97C_CSR_ENDRX		(1 << 14)
+
+#define AC97C_CMR_SIZE_20	(0 << 16)
+#define AC97C_CMR_SIZE_18	(1 << 16)
+#define AC97C_CMR_SIZE_16	(2 << 16)
+#define AC97C_CMR_SIZE_10	(3 << 16)
+#define AC97C_CMR_CEM_LITTLE	(1 << 18)
+#define AC97C_CMR_CEM_BIG	(0 << 18)
+#define AC97C_CMR_CENA		(1 << 21)
+#define AC97C_CMR_DMAEN		(1 << 22)
+
+#define AC97C_SR_CAEVT		(1 << 3)
+#define AC97C_SR_COEVT		(1 << 2)
+#define AC97C_SR_WKUP		(1 << 1)
+#define AC97C_SR_SOF		(1 << 0)
+
+#define AC97C_CH_MASK(slot)						\
+	(0x7 << (3 * (AC97_SLOT_##slot - 3)))
+#define AC97C_CH_ASSIGN(slot, channel)					\
+	(AC97C_CHANNEL_##channel << (3 * (AC97_SLOT_##slot - 3)))
+#define AC97C_CHANNEL_NONE	0x0
+#define AC97C_CHANNEL_A		0x1
+
+#endif /* __SOUND_ATMEL_AC97C_H */
diff --git a/linux/sound/core/Kconfig b/linux/sound/core/Kconfig
new file mode 100644
index 0000000..475455c
--- /dev/null
+++ b/linux/sound/core/Kconfig
@@ -0,0 +1,214 @@
+# ALSA soundcard-configuration
+config SND_TIMER
+	tristate
+
+config SND_PCM
+	tristate
+	select SND_TIMER
+	select GCD
+
+config SND_HWDEP
+	tristate
+
+config SND_RAWMIDI
+	tristate
+
+# To be effective this also requires INPUT - users should say:
+#    select SND_JACK if INPUT=y || INPUT=SND
+# to avoid having to force INPUT on.
+config SND_JACK
+	bool
+
+config SND_SEQUENCER
+	tristate "Sequencer support"
+	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
+	select SOUND_OSS_CORE
+	bool
+
+config SND_MIXER_OSS
+	tristate "OSS Mixer API"
+	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"
+	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_PCM_OSS_PLUGINS
+	bool "OSS PCM (digital audio) API - Include plugin system"
+	depends on SND_PCM_OSS
+        default y
+	help
+          If you disable this option, the ALSA's OSS PCM API will not
+          support conversion of channels, formats and rates. It will
+          behave like most of new OSS/Free drivers in 2.4/2.6 kernels.
+
+config SND_SEQUENCER_OSS
+	bool "OSS Sequencer API"
+	depends on 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.
+
+	  If you choose M in "Sequencer support" (SND_SEQUENCER),
+	  this will be compiled as a module. The module will be called
+	  snd-seq-oss.
+
+config SND_HRTIMER
+	tristate "HR-timer backend support"
+	depends on HIGH_RES_TIMERS
+	select SND_TIMER
+	help
+	  Say Y here to enable HR-timer backend for ALSA timer.  ALSA uses
+	  the hrtimer as a precise timing source. 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-hrtimer.
+
+config SND_SEQ_HRTIMER_DEFAULT
+	bool "Use HR-timer as default sequencer timer"
+	depends on SND_HRTIMER && SND_SEQUENCER
+	default y
+	help
+	  Say Y here to use the HR-timer backend as the default sequencer
+	  timer.
+
+config SND_RTCTIMER
+	tristate "RTC Timer support"
+	depends on 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.
+
+	  Note that this option is exclusive with the new RTC drivers
+	  (CONFIG_RTC_CLASS) since this requires the old API.
+
+config SND_SEQ_RTCTIMER_DEFAULT
+	bool "Use RTC as default sequencer timer"
+	depends on SND_RTCTIMER && SND_SEQUENCER
+	depends on !SND_SEQ_HRTIMER_DEFAULT
+	default y
+	help
+	  Say Y here to use the RTC timer as the default sequencer
+	  timer.  This is strongly recommended because it ensures
+	  precise MIDI timing even when the system timer runs at less
+	  than 1000 Hz.
+
+	  If in doubt, say Y.
+
+config SND_DYNAMIC_MINORS
+	bool "Dynamic device file minor numbers"
+	help
+	  If you say Y here, the minor numbers of ALSA device files in
+	  /dev/snd/ are allocated dynamically.  This allows you to have
+	  more than 8 sound cards, but requires a dynamic device file
+	  system like udev.
+
+	  If you are unsure about this, say N here.
+
+config SND_SUPPORT_OLD_API
+	bool "Support old ALSA API"
+	default y
+	help
+	  Say Y here to support the obsolete ALSA PCM API (ver.0.9.0 rc3
+	  or older).
+
+config SND_VERBOSE_PROCFS
+	bool "Verbose procfs contents"
+	depends on PROC_FS
+	default y
+	help
+	  Say Y here to include code for verbose procfs contents (provides
+          useful information to developers when a problem occurs).  On the
+          other side, it makes the ALSA subsystem larger.
+
+config SND_VERBOSE_PRINTK
+	bool "Verbose printk"
+	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"
+	help
+	  Say Y here to enable ALSA debug code.
+
+config SND_DEBUG_VERBOSE
+	bool "More verbose debug"
+	depends on SND_DEBUG
+	help
+	  Say Y here to enable extra-verbose debugging messages.
+	  
+	  Let me repeat: it enables EXTRA-VERBOSE DEBUGGING messages.
+	  So, say Y only if you are ready to be annoyed.
+
+config SND_PCM_XRUN_DEBUG
+	bool "Enable PCM ring buffer overrun/underrun debugging"
+	default n
+	depends on SND_DEBUG && SND_VERBOSE_PROCFS
+	help
+	  Say Y to enable the PCM ring buffer overrun/underrun debugging.
+	  It is usually not required, but if you have trouble with
+	  sound clicking when system is loaded, it may help to determine
+	  the process or driver which causes the scheduling gaps.
+
+config SND_VMASTER
+	bool
+
+config SND_DMA_SGBUF
+	def_bool y
+	depends on X86
+
+source "sound/core/seq/Kconfig"
diff --git a/linux/sound/core/Makefile b/linux/sound/core/Makefile
new file mode 100644
index 0000000..350a08d
--- /dev/null
+++ b/linux/sound/core/Makefile
@@ -0,0 +1,33 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999,2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-y     := sound.o init.o memory.o info.o control.o misc.o device.o
+snd-$(CONFIG_ISA_DMA_API) += isadma.o
+snd-$(CONFIG_SND_OSSEMUL) += sound_oss.o info_oss.o
+snd-$(CONFIG_SND_VMASTER) += vmaster.o
+snd-$(CONFIG_SND_JACK)	  += jack.o
+
+snd-pcm-objs := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \
+		pcm_memory.o
+
+snd-page-alloc-y := memalloc.o
+snd-page-alloc-$(CONFIG_SND_DMA_SGBUF) += sgbuf.o
+
+snd-rawmidi-objs  := rawmidi.o
+snd-timer-objs    := timer.o
+snd-hrtimer-objs  := hrtimer.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_HRTIMER)	+= snd-hrtimer.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/linux/sound/core/control.c b/linux/sound/core/control.c
new file mode 100644
index 0000000..45a8180
--- /dev/null
+++ b/linux/sound/core/control.c
@@ -0,0 +1,1515 @@
+/*
+ *  Routines for driver control interface
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/threads.h>
+#include <linux/interrupt.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
+#define MAX_CONTROL_COUNT	1028
+
+struct snd_kctl_ioctl {
+	struct list_head list;		/* list of all ioctls */
+	snd_kctl_ioctl_func_t fioctl;
+};
+
+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)
+{
+	unsigned long flags;
+	struct snd_card *card;
+	struct snd_ctl_file *ctl;
+	int err;
+
+	err = nonseekable_open(inode, file);
+	if (err < 0)
+		return err;
+
+	card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL);
+	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 = kzalloc(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->prefer_pcm_subdevice = -1;
+	ctl->prefer_rawmidi_subdevice = -1;
+	ctl->pid = get_pid(task_pid(current));
+	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(struct snd_ctl_file * ctl)
+{
+	unsigned long flags;
+	struct snd_kctl_event *cread;
+	
+	spin_lock_irqsave(&ctl->read_lock, flags);
+	while (!list_empty(&ctl->events)) {
+		cread = snd_kctl_event(ctl->events.next);
+		list_del(&cread->list);
+		kfree(cread);
+	}
+	spin_unlock_irqrestore(&ctl->read_lock, flags);
+}
+
+static int snd_ctl_release(struct inode *inode, struct file *file)
+{
+	unsigned long flags;
+	struct snd_card *card;
+	struct snd_ctl_file *ctl;
+	struct snd_kcontrol *control;
+	unsigned int idx;
+
+	ctl = file->private_data;
+	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_entry(control, &card->controls, 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);
+	put_pid(ctl->pid);
+	kfree(ctl);
+	module_put(card->module);
+	snd_card_file_remove(card, file);
+	return 0;
+}
+
+void snd_ctl_notify(struct snd_card *card, unsigned int mask,
+		    struct snd_ctl_elem_id *id)
+{
+	unsigned long flags;
+	struct snd_ctl_file *ctl;
+	struct snd_kctl_event *ev;
+	
+	if (snd_BUG_ON(!card || !id))
+		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_entry(ctl, &card->ctl_files, list) {
+		if (!ctl->subscribed)
+			continue;
+		spin_lock_irqsave(&ctl->read_lock, flags);
+		list_for_each_entry(ev, &ctl->events, list) {
+			if (ev->id.numid == id->numid) {
+				ev->mask |= mask;
+				goto _found;
+			}
+		}
+		ev = kzalloc(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);
+}
+
+EXPORT_SYMBOL(snd_ctl_notify);
+
+/**
+ * snd_ctl_new - create a control instance from the template
+ * @control: the control template
+ * @access: the default control access
+ *
+ * Allocates a new struct snd_kcontrol 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.
+ */
+static struct snd_kcontrol *snd_ctl_new(struct snd_kcontrol *control,
+					unsigned int access)
+{
+	struct snd_kcontrol *kctl;
+	unsigned int idx;
+	
+	if (snd_BUG_ON(!control || !control->count))
+		return NULL;
+
+	if (control->count > MAX_CONTROL_COUNT)
+		return NULL;
+
+	kctl = kzalloc(sizeof(*kctl) + sizeof(struct snd_kcontrol_volatile) * control->count, GFP_KERNEL);
+	if (kctl == NULL) {
+		snd_printk(KERN_ERR "Cannot allocate control instance\n");
+		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 struct snd_kcontrol 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.
+ */
+struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
+				  void *private_data)
+{
+	struct snd_kcontrol kctl;
+	unsigned int access;
+	
+	if (snd_BUG_ON(!ncontrol || !ncontrol->info))
+		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));
+		if (strcmp(ncontrol->name, kctl.id.name) != 0)
+			snd_printk(KERN_WARNING
+				   "Control name '%s' truncated to '%s'\n",
+				   ncontrol->name, 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_TLV_READWRITE|
+				      SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND|
+				      SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK));
+	kctl.info = ncontrol->info;
+	kctl.get = ncontrol->get;
+	kctl.put = ncontrol->put;
+	kctl.tlv.p = ncontrol->tlv.p;
+	kctl.private_value = ncontrol->private_value;
+	kctl.private_data = private_data;
+	return snd_ctl_new(&kctl, access);
+}
+
+EXPORT_SYMBOL(snd_ctl_new1);
+
+/**
+ * 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(struct snd_kcontrol *kcontrol)
+{
+	if (kcontrol) {
+		if (kcontrol->private_free)
+			kcontrol->private_free(kcontrol);
+		kfree(kcontrol);
+	}
+}
+
+EXPORT_SYMBOL(snd_ctl_free_one);
+
+static unsigned int snd_ctl_hole_check(struct snd_card *card,
+				       unsigned int count)
+{
+	struct snd_kcontrol *kctl;
+
+	list_for_each_entry(kctl, &card->controls, 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(struct snd_card *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(struct snd_card *card, struct snd_kcontrol *kcontrol)
+{
+	struct snd_ctl_elem_id id;
+	unsigned int idx;
+	int err = -EINVAL;
+
+	if (! kcontrol)
+		return err;
+	if (snd_BUG_ON(!card || !kcontrol->info))
+		goto error;
+	id = kcontrol->id;
+	down_write(&card->controls_rwsem);
+	if (snd_ctl_find_id(card, &id)) {
+		up_write(&card->controls_rwsem);
+		snd_printd(KERN_ERR "control %i:%i:%i:%s:%i is already present\n",
+					id.iface,
+					id.device,
+					id.subdevice,
+					id.name,
+					id.index);
+		err = -EBUSY;
+		goto error;
+	}
+	if (snd_ctl_find_hole(card, kcontrol->count) < 0) {
+		up_write(&card->controls_rwsem);
+		err = -ENOMEM;
+		goto error;
+	}
+	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;
+
+ error:
+	snd_ctl_free_one(kcontrol);
+	return err;
+}
+
+EXPORT_SYMBOL(snd_ctl_add);
+
+/**
+ * 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(struct snd_card *card, struct snd_kcontrol *kcontrol)
+{
+	struct snd_ctl_elem_id id;
+	unsigned int idx;
+
+	if (snd_BUG_ON(!card || !kcontrol))
+		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;
+}
+
+EXPORT_SYMBOL(snd_ctl_remove);
+
+/**
+ * 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(struct snd_card *card, struct snd_ctl_elem_id *id)
+{
+	struct snd_kcontrol *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;
+}
+
+EXPORT_SYMBOL(snd_ctl_remove_id);
+
+/**
+ * snd_ctl_remove_user_ctl - remove and release the unlocked user control
+ * @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_user_ctl(struct snd_ctl_file * file,
+				   struct snd_ctl_elem_id *id)
+{
+	struct snd_card *card = file->card;
+	struct snd_kcontrol *kctl;
+	int idx, ret;
+
+	down_write(&card->controls_rwsem);
+	kctl = snd_ctl_find_id(card, id);
+	if (kctl == NULL) {
+		ret = -ENOENT;
+		goto error;
+	}
+	if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_USER)) {
+		ret = -EINVAL;
+		goto error;
+	}
+	for (idx = 0; idx < kctl->count; idx++)
+		if (kctl->vd[idx].owner != NULL && kctl->vd[idx].owner != file) {
+			ret = -EBUSY;
+			goto error;
+		}
+	ret = snd_ctl_remove(card, kctl);
+	if (ret < 0)
+		goto error;
+	card->user_ctl_count--;
+error:
+	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(struct snd_card *card, struct snd_ctl_elem_id *src_id,
+		      struct snd_ctl_elem_id *dst_id)
+{
+	struct snd_kcontrol *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;
+}
+
+EXPORT_SYMBOL(snd_ctl_rename_id);
+
+/**
+ * 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).
+ */
+struct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, unsigned int numid)
+{
+	struct snd_kcontrol *kctl;
+
+	if (snd_BUG_ON(!card || !numid))
+		return NULL;
+	list_for_each_entry(kctl, &card->controls, list) {
+		if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid)
+			return kctl;
+	}
+	return NULL;
+}
+
+EXPORT_SYMBOL(snd_ctl_find_numid);
+
+/**
+ * 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).
+ */
+struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card,
+				     struct snd_ctl_elem_id *id)
+{
+	struct snd_kcontrol *kctl;
+
+	if (snd_BUG_ON(!card || !id))
+		return NULL;
+	if (id->numid != 0)
+		return snd_ctl_find_numid(card, id->numid);
+	list_for_each_entry(kctl, &card->controls, 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;
+}
+
+EXPORT_SYMBOL(snd_ctl_find_id);
+
+static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl,
+			     unsigned int cmd, void __user *arg)
+{
+	struct snd_ctl_card_info *info;
+
+	info = kzalloc(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(struct snd_ctl_card_info))) {
+		kfree(info);
+		return -EFAULT;
+	}
+	kfree(info);
+	return 0;
+}
+
+static int snd_ctl_elem_list(struct snd_card *card,
+			     struct snd_ctl_elem_list __user *_list)
+{
+	struct list_head *plist;
+	struct snd_ctl_elem_list list;
+	struct snd_kcontrol *kctl;
+	struct snd_ctl_elem_id *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(struct snd_ctl_elem_id));
+		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(struct snd_ctl_elem_id))) {
+			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(struct snd_ctl_file *ctl,
+			     struct snd_ctl_elem_info *info)
+{
+	struct snd_card *card = ctl->card;
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_volatile *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_BUG_ON(info->access);
+		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 = pid_vnr(vd->owner->pid);
+		} else {
+			info->owner = -1;
+		}
+	}
+	up_read(&card->controls_rwsem);
+	return result;
+}
+
+static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl,
+				  struct snd_ctl_elem_info __user *_info)
+{
+	struct snd_ctl_elem_info info;
+	int result;
+
+	if (copy_from_user(&info, _info, sizeof(info)))
+		return -EFAULT;
+	snd_power_lock(ctl->card);
+	result = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0);
+	if (result >= 0)
+		result = snd_ctl_elem_info(ctl, &info);
+	snd_power_unlock(ctl->card);
+	if (result >= 0)
+		if (copy_to_user(_info, &info, sizeof(info)))
+			return -EFAULT;
+	return result;
+}
+
+static int snd_ctl_elem_read(struct snd_card *card,
+			     struct snd_ctl_elem_value *control)
+{
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_volatile *vd;
+	unsigned int index_offset;
+	int result;
+
+	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];
+		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(struct snd_card *card,
+				  struct snd_ctl_elem_value __user *_control)
+{
+	struct snd_ctl_elem_value *control;
+	int result;
+
+	control = memdup_user(_control, sizeof(*control));
+	if (IS_ERR(control))
+		return PTR_ERR(control);
+
+	snd_power_lock(card);
+	result = snd_power_wait(card, SNDRV_CTL_POWER_D0);
+	if (result >= 0)
+		result = snd_ctl_elem_read(card, control);
+	snd_power_unlock(card);
+	if (result >= 0)
+		if (copy_to_user(_control, control, sizeof(*control)))
+			result = -EFAULT;
+	kfree(control);
+	return result;
+}
+
+static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
+			      struct snd_ctl_elem_value *control)
+{
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_volatile *vd;
+	unsigned int index_offset;
+	int result;
+
+	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];
+		if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) ||
+		    kctl->put == NULL ||
+		    (file && vd->owner && 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(struct snd_ctl_file *file,
+				   struct snd_ctl_elem_value __user *_control)
+{
+	struct snd_ctl_elem_value *control;
+	struct snd_card *card;
+	int result;
+
+	control = memdup_user(_control, sizeof(*control));
+	if (IS_ERR(control))
+		return PTR_ERR(control);
+
+	card = file->card;
+	snd_power_lock(card);
+	result = snd_power_wait(card, SNDRV_CTL_POWER_D0);
+	if (result >= 0)
+		result = snd_ctl_elem_write(card, file, control);
+	snd_power_unlock(card);
+	if (result >= 0)
+		if (copy_to_user(_control, control, sizeof(*control)))
+			result = -EFAULT;
+	kfree(control);
+	return result;
+}
+
+static int snd_ctl_elem_lock(struct snd_ctl_file *file,
+			     struct snd_ctl_elem_id __user *_id)
+{
+	struct snd_card *card = file->card;
+	struct snd_ctl_elem_id id;
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_volatile *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;
+			result = 0;
+		}
+	}
+	up_write(&card->controls_rwsem);
+	return result;
+}
+
+static int snd_ctl_elem_unlock(struct snd_ctl_file *file,
+			       struct snd_ctl_elem_id __user *_id)
+{
+	struct snd_card *card = file->card;
+	struct snd_ctl_elem_id id;
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_volatile *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;
+			result = 0;
+		}
+	}
+	up_write(&card->controls_rwsem);
+	return result;
+}
+
+struct user_element {
+	struct snd_ctl_elem_info info;
+	void *elem_data;		/* element data */
+	unsigned long elem_data_size;	/* size of element data in bytes */
+	void *tlv_data;			/* TLV data */
+	unsigned long tlv_data_size;	/* TLV data size */
+	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(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct user_element *ue = kcontrol->private_data;
+
+	*uinfo = ue->info;
+	return 0;
+}
+
+static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *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(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *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 int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol,
+				 int op_flag,
+				 unsigned int size,
+				 unsigned int __user *tlv)
+{
+	struct user_element *ue = kcontrol->private_data;
+	int change = 0;
+	void *new_data;
+
+	if (op_flag > 0) {
+		if (size > 1024 * 128)	/* sane value */
+			return -EINVAL;
+
+		new_data = memdup_user(tlv, size);
+		if (IS_ERR(new_data))
+			return PTR_ERR(new_data);
+		change = ue->tlv_data_size != size;
+		if (!change)
+			change = memcmp(ue->tlv_data, new_data, size);
+		kfree(ue->tlv_data);
+		ue->tlv_data = new_data;
+		ue->tlv_data_size = size;
+	} else {
+		if (! ue->tlv_data_size || ! ue->tlv_data)
+			return -ENXIO;
+		if (size < ue->tlv_data_size)
+			return -ENOSPC;
+		if (copy_to_user(tlv, ue->tlv_data, ue->tlv_data_size))
+			return -EFAULT;
+	}
+	return change;
+}
+
+static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol)
+{
+	struct user_element *ue = kcontrol->private_data;
+	if (ue->tlv_data)
+		kfree(ue->tlv_data);
+	kfree(ue);
+}
+
+static int snd_ctl_elem_add(struct snd_ctl_file *file,
+			    struct snd_ctl_elem_info *info, int replace)
+{
+	struct snd_card *card = file->card;
+	struct snd_kcontrol 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 < 1)
+		return -EINVAL;
+	access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
+		(info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|
+				 SNDRV_CTL_ELEM_ACCESS_INACTIVE|
+				 SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE));
+	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;
+	if (access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) {
+		kctl.tlv.c = snd_ctl_elem_user_tlv;
+		access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+	}
+	switch (info->type) {
+	case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+	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 snd_aes_iec958);
+		if (info->count != 1)
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	private_size *= info->count;
+	ue = kzalloc(sizeof(struct user_element) + private_size, GFP_KERNEL);
+	if (ue == NULL)
+		return -ENOMEM;
+	ue->info = *info;
+	ue->info.access = 0;
+	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(ue);
+		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)
+		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(struct snd_ctl_file *file,
+				 struct snd_ctl_elem_info __user *_info, int replace)
+{
+	struct snd_ctl_elem_info 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(struct snd_ctl_file *file,
+			       struct snd_ctl_elem_id __user *_id)
+{
+	struct snd_ctl_elem_id id;
+
+	if (copy_from_user(&id, _id, sizeof(id)))
+		return -EFAULT;
+	return snd_ctl_remove_user_ctl(file, &id);
+}
+
+static int snd_ctl_subscribe_events(struct snd_ctl_file *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;
+}
+
+static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,
+                             struct snd_ctl_tlv __user *_tlv,
+                             int op_flag)
+{
+	struct snd_card *card = file->card;
+	struct snd_ctl_tlv tlv;
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_volatile *vd;
+	unsigned int len;
+	int err = 0;
+
+	if (copy_from_user(&tlv, _tlv, sizeof(tlv)))
+		return -EFAULT;
+	if (tlv.length < sizeof(unsigned int) * 2)
+		return -EINVAL;
+	down_read(&card->controls_rwsem);
+	kctl = snd_ctl_find_numid(card, tlv.numid);
+	if (kctl == NULL) {
+		err = -ENOENT;
+		goto __kctl_end;
+	}
+	if (kctl->tlv.p == NULL) {
+		err = -ENXIO;
+		goto __kctl_end;
+	}
+	vd = &kctl->vd[tlv.numid - kctl->id.numid];
+	if ((op_flag == 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) == 0) ||
+	    (op_flag > 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) == 0) ||
+	    (op_flag < 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND) == 0)) {
+	    	err = -ENXIO;
+	    	goto __kctl_end;
+	}
+	if (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
+		if (vd->owner != NULL && vd->owner != file) {
+			err = -EPERM;
+			goto __kctl_end;
+		}
+		err = kctl->tlv.c(kctl, op_flag, tlv.length, _tlv->tlv); 
+		if (err > 0) {
+			up_read(&card->controls_rwsem);
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &kctl->id);
+			return 0;
+		}
+	} else {
+		if (op_flag) {
+			err = -ENXIO;
+			goto __kctl_end;
+		}
+		len = kctl->tlv.p[1] + 2 * sizeof(unsigned int);
+		if (tlv.length < len) {
+			err = -ENOMEM;
+			goto __kctl_end;
+		}
+		if (copy_to_user(_tlv->tlv, kctl->tlv.p, len))
+			err = -EFAULT;
+	}
+      __kctl_end:
+	up_read(&card->controls_rwsem);
+	return err;
+}
+
+static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct snd_ctl_file *ctl;
+	struct snd_card *card;
+	struct snd_kctl_ioctl *p;
+	void __user *argp = (void __user *)arg;
+	int __user *ip = argp;
+	int err;
+
+	ctl = file->private_data;
+	card = ctl->card;
+	if (snd_BUG_ON(!card))
+		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(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(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_TLV_READ:
+		return snd_ctl_tlv_ioctl(ctl, argp, 0);
+	case SNDRV_CTL_IOCTL_TLV_WRITE:
+		return snd_ctl_tlv_ioctl(ctl, argp, 1);
+	case SNDRV_CTL_IOCTL_TLV_COMMAND:
+		return snd_ctl_tlv_ioctl(ctl, argp, -1);
+	case SNDRV_CTL_IOCTL_POWER:
+		return -ENOPROTOOPT;
+	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_entry(p, &snd_control_ioctls, list) {
+		err = p->fioctl(card, ctl, cmd, arg);
+		if (err != -ENOIOCTLCMD) {
+			up_read(&snd_ioctl_rwsem);
+			return err;
+		}
+	}
+	up_read(&snd_ioctl_rwsem);
+	snd_printdd("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)
+{
+	struct snd_ctl_file *ctl;
+	int err = 0;
+	ssize_t result = 0;
+
+	ctl = file->private_data;
+	if (snd_BUG_ON(!ctl || !ctl->card))
+		return -ENXIO;
+	if (!ctl->subscribed)
+		return -EBADFD;
+	if (count < sizeof(struct snd_ctl_event))
+		return -EINVAL;
+	spin_lock_irq(&ctl->read_lock);
+	while (count >= sizeof(struct snd_ctl_event)) {
+		struct snd_ctl_event ev;
+		struct snd_kctl_event *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 -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(struct snd_ctl_event))) {
+			err = -EFAULT;
+			goto __end;
+		}
+		spin_lock_irq(&ctl->read_lock);
+		buffer += sizeof(struct snd_ctl_event);
+		count -= sizeof(struct snd_ctl_event);
+		result += sizeof(struct snd_ctl_event);
+	}
+      __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;
+	struct snd_ctl_file *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)
+{
+	struct snd_kctl_ioctl *pn;
+
+	pn = kzalloc(sizeof(struct snd_kctl_ioctl), 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);
+}
+
+EXPORT_SYMBOL(snd_ctl_register_ioctl);
+
+#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);
+}
+
+EXPORT_SYMBOL(snd_ctl_register_ioctl_compat);
+#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 snd_kctl_ioctl *p;
+
+	if (snd_BUG_ON(!fcn))
+		return -EINVAL;
+	down_write(&snd_ioctl_rwsem);
+	list_for_each_entry(p, lists, 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);
+}
+
+EXPORT_SYMBOL(snd_ctl_unregister_ioctl);
+
+#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);
+}
+
+EXPORT_SYMBOL(snd_ctl_unregister_ioctl_compat);
+#endif
+
+static int snd_ctl_fasync(int fd, struct file * file, int on)
+{
+	struct snd_ctl_file *ctl;
+
+	ctl = file->private_data;
+	return fasync_helper(fd, file, on, &ctl->fasync);
+}
+
+/*
+ * ioctl32 compat
+ */
+#ifdef CONFIG_COMPAT
+#include "control_compat.c"
+#else
+#define snd_ctl_ioctl_compat	NULL
+#endif
+
+/*
+ *  INIT PART
+ */
+
+static const struct file_operations snd_ctl_f_ops =
+{
+	.owner =	THIS_MODULE,
+	.read =		snd_ctl_read,
+	.open =		snd_ctl_open,
+	.release =	snd_ctl_release,
+	.llseek =	no_llseek,
+	.poll =		snd_ctl_poll,
+	.unlocked_ioctl =	snd_ctl_ioctl,
+	.compat_ioctl =	snd_ctl_ioctl_compat,
+	.fasync =	snd_ctl_fasync,
+};
+
+/*
+ * registration of the control device
+ */
+static int snd_ctl_dev_register(struct snd_device *device)
+{
+	struct snd_card *card = device->device_data;
+	int err, cardnum;
+	char name[16];
+
+	if (snd_BUG_ON(!card))
+		return -ENXIO;
+	cardnum = card->number;
+	if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
+		return -ENXIO;
+	sprintf(name, "controlC%i", cardnum);
+	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
+				       &snd_ctl_f_ops, card, name)) < 0)
+		return err;
+	return 0;
+}
+
+/*
+ * disconnection of the control device
+ */
+static int snd_ctl_dev_disconnect(struct snd_device *device)
+{
+	struct snd_card *card = device->device_data;
+	struct snd_ctl_file *ctl;
+	int err, cardnum;
+
+	if (snd_BUG_ON(!card))
+		return -ENXIO;
+	cardnum = card->number;
+	if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
+		return -ENXIO;
+
+	read_lock(&card->ctl_files_rwlock);
+	list_for_each_entry(ctl, &card->ctl_files, list) {
+		wake_up(&ctl->change_sleep);
+		kill_fasync(&ctl->fasync, SIGIO, POLL_ERR);
+	}
+	read_unlock(&card->ctl_files_rwlock);
+
+	if ((err = snd_unregister_device(SNDRV_DEVICE_TYPE_CONTROL,
+					 card, -1)) < 0)
+		return err;
+	return 0;
+}
+
+/*
+ * free all controls
+ */
+static int snd_ctl_dev_free(struct snd_device *device)
+{
+	struct snd_card *card = device->device_data;
+	struct snd_kcontrol *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;
+}
+
+/*
+ * create control core:
+ * called from init.c
+ */
+int snd_ctl_create(struct snd_card *card)
+{
+	static struct snd_device_ops ops = {
+		.dev_free = snd_ctl_dev_free,
+		.dev_register =	snd_ctl_dev_register,
+		.dev_disconnect = snd_ctl_dev_disconnect,
+	};
+
+	if (snd_BUG_ON(!card))
+		return -ENXIO;
+	return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
+}
+
+/*
+ * Frequently used control callbacks
+ */
+int snd_ctl_boolean_mono_info(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ctl_boolean_mono_info);
+
+int snd_ctl_boolean_stereo_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ctl_boolean_stereo_info);
diff --git a/linux/sound/core/control_compat.c b/linux/sound/core/control_compat.c
new file mode 100644
index 0000000..4268744
--- /dev/null
+++ b/linux/sound/core/control_compat.c
@@ -0,0 +1,444 @@
+/*
+ * 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>
+#include <linux/slab.h>
+
+struct snd_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(struct snd_card *card,
+				    struct snd_ctl_elem_list32 __user *data32)
+{
+	struct snd_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 snd_ctl_elem_info32 {
+	struct snd_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(struct snd_ctl_file *ctl,
+				    struct snd_ctl_elem_info32 __user *data32)
+{
+	struct snd_ctl_elem_info *data;
+	int err;
+
+	data = kzalloc(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;
+
+	snd_power_lock(ctl->card);
+	err = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0);
+	if (err >= 0)
+		err = snd_ctl_elem_info(ctl, data);
+	snd_power_unlock(ctl->card);
+
+	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 snd_ctl_elem_value32 {
+	struct snd_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(struct snd_card *card, struct snd_ctl_elem_id *id,
+			int *countp)
+{
+	struct snd_kcontrol *kctl;
+	struct snd_ctl_elem_info *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 = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (info == NULL) {
+		up_read(&card->controls_rwsem);
+		return -ENOMEM;
+	}
+	info->id = *id;
+	err = kctl->info(kctl, info);
+	up_read(&card->controls_rwsem);
+	if (err >= 0) {
+		err = info->type;
+		*countp = info->count;
+	}
+	kfree(info);
+	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 snd_aes_iec958);
+	default:
+		return -1;
+	}
+}
+
+static int copy_ctl_value_from_user(struct snd_card *card,
+				    struct snd_ctl_elem_value *data,
+				    struct snd_ctl_elem_value32 __user *data32,
+				    int *typep, int *countp)
+{
+	int i, type, size;
+	int uninitialized_var(count);
+	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 snd_ctl_elem_value32 __user *data32,
+				  struct snd_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(struct snd_card *card, 
+					 struct snd_ctl_elem_value32 __user *data32)
+{
+	struct snd_ctl_elem_value *data;
+	int err, type, count;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0)
+		goto error;
+
+	snd_power_lock(card);
+	err = snd_power_wait(card, SNDRV_CTL_POWER_D0);
+	if (err >= 0)
+		err = snd_ctl_elem_read(card, data);
+	snd_power_unlock(card);
+	if (err >= 0)
+		err = copy_ctl_value_to_user(data32, data, type, count);
+ error:
+	kfree(data);
+	return err;
+}
+
+static int snd_ctl_elem_write_user_compat(struct snd_ctl_file *file,
+					  struct snd_ctl_elem_value32 __user *data32)
+{
+	struct snd_ctl_elem_value *data;
+	struct snd_card *card = file->card;
+	int err, type, count;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0)
+		goto error;
+
+	snd_power_lock(card);
+	err = snd_power_wait(card, SNDRV_CTL_POWER_D0);
+	if (err >= 0)
+		err = snd_ctl_elem_write(card, file, data);
+	snd_power_unlock(card);
+	if (err >= 0)
+		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(struct snd_ctl_file *file,
+				   struct snd_ctl_elem_info32 __user *data32,
+				   int replace)
+{
+	struct snd_ctl_elem_info *data;
+	int err;
+
+	data = kzalloc(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 snd_ctl_elem_list32),
+	SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct snd_ctl_elem_info32),
+	SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct snd_ctl_elem_value32),
+	SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct snd_ctl_elem_value32),
+	SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct snd_ctl_elem_info32),
+	SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct snd_ctl_elem_info32),
+};
+
+static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct snd_ctl_file *ctl;
+	struct snd_kctl_ioctl *p;
+	void __user *argp = compat_ptr(arg);
+	int err;
+
+	ctl = file->private_data;
+	if (snd_BUG_ON(!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:
+	case SNDRV_CTL_IOCTL_ELEM_REMOVE:
+	case SNDRV_CTL_IOCTL_TLV_READ:
+	case SNDRV_CTL_IOCTL_TLV_WRITE:
+	case SNDRV_CTL_IOCTL_TLV_COMMAND:
+		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_entry(p, &snd_control_compat_ioctls, 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/linux/sound/core/device.c b/linux/sound/core/device.c
new file mode 100644
index 0000000..a67dfac
--- /dev/null
+++ b/linux/sound/core/device.c
@@ -0,0 +1,243 @@
+/*
+ *  Device management routines
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/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_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(struct snd_card *card, snd_device_type_t type,
+		   void *device_data, struct snd_device_ops *ops)
+{
+	struct snd_device *dev;
+
+	if (snd_BUG_ON(!card || !device_data || !ops))
+		return -ENXIO;
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (dev == NULL) {
+		snd_printk(KERN_ERR "Cannot allocate device\n");
+		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;
+}
+
+EXPORT_SYMBOL(snd_device_new);
+
+/**
+ * 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
+ * callbacks, dev_disconnect and 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(struct snd_card *card, void *device_data)
+{
+	struct snd_device *dev;
+	
+	if (snd_BUG_ON(!card || !device_data))
+		return -ENXIO;
+	list_for_each_entry(dev, &card->devices, list) {
+		if (dev->device_data != device_data)
+			continue;
+		/* unlink */
+		list_del(&dev->list);
+		if (dev->state == SNDRV_DEV_REGISTERED &&
+		    dev->ops->dev_disconnect)
+			if (dev->ops->dev_disconnect(dev))
+				snd_printk(KERN_ERR
+					   "device disconnect failure\n");
+		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 %pF), not found\n", device_data,
+		   __builtin_return_address(0));
+	return -ENXIO;
+}
+
+EXPORT_SYMBOL(snd_device_free);
+
+/**
+ * snd_device_disconnect - 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(struct snd_card *card, void *device_data)
+{
+	struct snd_device *dev;
+
+	if (snd_BUG_ON(!card || !device_data))
+		return -ENXIO;
+	list_for_each_entry(dev, &card->devices, 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 %pF), 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(struct snd_card *card, void *device_data)
+{
+	struct snd_device *dev;
+	int err;
+
+	if (snd_BUG_ON(!card || !device_data))
+		return -ENXIO;
+	list_for_each_entry(dev, &card->devices, 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;
+		}
+		snd_printd("snd_device_register busy\n");
+		return -EBUSY;
+	}
+	snd_BUG();
+	return -ENXIO;
+}
+
+EXPORT_SYMBOL(snd_device_register);
+
+/*
+ * register all the devices on the card.
+ * called from init.c
+ */
+int snd_device_register_all(struct snd_card *card)
+{
+	struct snd_device *dev;
+	int err;
+	
+	if (snd_BUG_ON(!card))
+		return -ENXIO;
+	list_for_each_entry(dev, &card->devices, 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(struct snd_card *card)
+{
+	struct snd_device *dev;
+	int err = 0;
+
+	if (snd_BUG_ON(!card))
+		return -ENXIO;
+	list_for_each_entry(dev, &card->devices, 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(struct snd_card *card, snd_device_cmd_t cmd)
+{
+	struct snd_device *dev;
+	int err;
+	unsigned int range_low, range_high;
+
+	if (snd_BUG_ON(!card))
+		return -ENXIO;
+	range_low = cmd * SNDRV_DEV_TYPE_RANGE_SIZE;
+	range_high = range_low + SNDRV_DEV_TYPE_RANGE_SIZE - 1;
+      __again:
+	list_for_each_entry(dev, &card->devices, 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/linux/sound/core/hrtimer.c b/linux/sound/core/hrtimer.c
new file mode 100644
index 0000000..7730575
--- /dev/null
+++ b/linux/sound/core/hrtimer.c
@@ -0,0 +1,166 @@
+/*
+ * ALSA timer back-end using hrtimer
+ * Copyright (C) 2008 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
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/hrtimer.h>
+#include <sound/core.h>
+#include <sound/timer.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ALSA hrtimer backend");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_HRTIMER));
+
+#define NANO_SEC	1000000000UL	/* 10^9 in sec */
+static unsigned int resolution;
+
+struct snd_hrtimer {
+	struct snd_timer *timer;
+	struct hrtimer hrt;
+	atomic_t running;
+};
+
+static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
+{
+	struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt);
+	struct snd_timer *t = stime->timer;
+
+	if (!atomic_read(&stime->running))
+		return HRTIMER_NORESTART;
+
+	hrtimer_forward_now(hrt, ns_to_ktime(t->sticks * resolution));
+	snd_timer_interrupt(stime->timer, t->sticks);
+
+	if (!atomic_read(&stime->running))
+		return HRTIMER_NORESTART;
+	return HRTIMER_RESTART;
+}
+
+static int snd_hrtimer_open(struct snd_timer *t)
+{
+	struct snd_hrtimer *stime;
+
+	stime = kmalloc(sizeof(*stime), GFP_KERNEL);
+	if (!stime)
+		return -ENOMEM;
+	hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	stime->timer = t;
+	stime->hrt.function = snd_hrtimer_callback;
+	atomic_set(&stime->running, 0);
+	t->private_data = stime;
+	return 0;
+}
+
+static int snd_hrtimer_close(struct snd_timer *t)
+{
+	struct snd_hrtimer *stime = t->private_data;
+
+	if (stime) {
+		hrtimer_cancel(&stime->hrt);
+		kfree(stime);
+		t->private_data = NULL;
+	}
+	return 0;
+}
+
+static int snd_hrtimer_start(struct snd_timer *t)
+{
+	struct snd_hrtimer *stime = t->private_data;
+
+	atomic_set(&stime->running, 0);
+	hrtimer_cancel(&stime->hrt);
+	hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution),
+		      HRTIMER_MODE_REL);
+	atomic_set(&stime->running, 1);
+	return 0;
+}
+
+static int snd_hrtimer_stop(struct snd_timer *t)
+{
+	struct snd_hrtimer *stime = t->private_data;
+	atomic_set(&stime->running, 0);
+	return 0;
+}
+
+static struct snd_timer_hardware hrtimer_hw = {
+	.flags =	SNDRV_TIMER_HW_AUTO,
+	.open =		snd_hrtimer_open,
+	.close =	snd_hrtimer_close,
+	.start =	snd_hrtimer_start,
+	.stop =		snd_hrtimer_stop,
+};
+
+/*
+ * entry functions
+ */
+
+static struct snd_timer *mytimer;
+
+static int __init snd_hrtimer_init(void)
+{
+	struct snd_timer *timer;
+	struct timespec tp;
+	int err;
+
+	hrtimer_get_res(CLOCK_MONOTONIC, &tp);
+	if (tp.tv_sec > 0 || !tp.tv_nsec) {
+		snd_printk(KERN_ERR
+			   "snd-hrtimer: Invalid resolution %u.%09u",
+			   (unsigned)tp.tv_sec, (unsigned)tp.tv_nsec);
+		return -EINVAL;
+	}
+	resolution = tp.tv_nsec;
+
+	/* Create a new timer and set up the fields */
+	err = snd_timer_global_new("hrtimer", SNDRV_TIMER_GLOBAL_HRTIMER,
+				   &timer);
+	if (err < 0)
+		return err;
+
+	timer->module = THIS_MODULE;
+	strcpy(timer->name, "HR timer");
+	timer->hw = hrtimer_hw;
+	timer->hw.resolution = resolution;
+	timer->hw.ticks = NANO_SEC / resolution;
+
+	err = snd_timer_global_register(timer);
+	if (err < 0) {
+		snd_timer_global_free(timer);
+		return err;
+	}
+	mytimer = timer; /* remember this */
+
+	return 0;
+}
+
+static void __exit snd_hrtimer_exit(void)
+{
+	if (mytimer) {
+		snd_timer_global_free(mytimer);
+		mytimer = NULL;
+	}
+}
+
+module_init(snd_hrtimer_init);
+module_exit(snd_hrtimer_exit);
diff --git a/linux/sound/core/hwdep.c b/linux/sound/core/hwdep.c
new file mode 100644
index 0000000..a70ee7f
--- /dev/null
+++ b/linux/sound/core/hwdep.c
@@ -0,0 +1,529 @@
+/*
+ *  Hardware dependent layer
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/mutex.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@perex.cz>");
+MODULE_DESCRIPTION("Hardware dependent layer");
+MODULE_LICENSE("GPL");
+
+static LIST_HEAD(snd_hwdep_devices);
+static DEFINE_MUTEX(register_mutex);
+
+static int snd_hwdep_free(struct snd_hwdep *hwdep);
+static int snd_hwdep_dev_free(struct snd_device *device);
+static int snd_hwdep_dev_register(struct snd_device *device);
+static int snd_hwdep_dev_disconnect(struct snd_device *device);
+
+
+static struct snd_hwdep *snd_hwdep_search(struct snd_card *card, int device)
+{
+	struct snd_hwdep *hwdep;
+
+	list_for_each_entry(hwdep, &snd_hwdep_devices, list)
+		if (hwdep->card == card && hwdep->device == device)
+			return hwdep;
+	return NULL;
+}
+
+static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig)
+{
+	struct snd_hwdep *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)
+{
+	struct snd_hwdep *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)
+{
+	struct snd_hwdep *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);
+	struct snd_hwdep *hw;
+	int err;
+	wait_queue_t wait;
+
+	if (major == snd_major) {
+		hw = snd_lookup_minor_data(iminor(inode),
+					   SNDRV_DEVICE_TYPE_HWDEP);
+#ifdef CONFIG_SND_OSSEMUL
+	} else if (major == SOUND_MAJOR) {
+		hw = snd_lookup_oss_minor_data(iminor(inode),
+					       SNDRV_OSS_DEVICE_TYPE_DMFM);
+#endif
+	} else
+		return -ENXIO;
+	if (hw == NULL)
+		return -ENODEV;
+
+	if (!try_module_get(hw->card->module))
+		return -EFAULT;
+
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&hw->open_wait, &wait);
+	mutex_lock(&hw->open_mutex);
+	while (1) {
+		if (hw->exclusive && hw->used > 0) {
+			err = -EBUSY;
+			break;
+		}
+		if (!hw->ops.open) {
+			err = 0;
+			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);
+		mutex_unlock(&hw->open_mutex);
+		schedule();
+		mutex_lock(&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);
+		}
+	}
+	mutex_unlock(&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 = 0;
+	struct snd_hwdep *hw = file->private_data;
+	struct module *mod = hw->card->module;
+
+	mutex_lock(&hw->open_mutex);
+	if (hw->ops.release)
+		err = hw->ops.release(hw, file);
+	if (hw->used > 0)
+		hw->used--;
+	mutex_unlock(&hw->open_mutex);
+	wake_up(&hw->open_wait);
+
+	snd_card_file_remove(hw->card, file);
+	module_put(mod);
+	return err;
+}
+
+static unsigned int snd_hwdep_poll(struct file * file, poll_table * wait)
+{
+	struct snd_hwdep *hw = file->private_data;
+	if (hw->ops.poll)
+		return hw->ops.poll(hw, file, wait);
+	return 0;
+}
+
+static int snd_hwdep_info(struct snd_hwdep *hw,
+			  struct snd_hwdep_info __user *_info)
+{
+	struct snd_hwdep_info 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(struct snd_hwdep *hw,
+				struct snd_hwdep_dsp_status __user *_info)
+{
+	struct snd_hwdep_dsp_status 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(struct snd_hwdep *hw,
+			      struct snd_hwdep_dsp_image __user *_info)
+{
+	struct snd_hwdep_dsp_image 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)
+{
+	struct snd_hwdep *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)
+{
+	struct snd_hwdep *hw = file->private_data;
+	if (hw->ops.mmap)
+		return hw->ops.mmap(hw, file, vma);
+	return -ENXIO;
+}
+
+static int snd_hwdep_control_ioctl(struct snd_card *card,
+				   struct snd_ctl_file * control,
+				   unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE:
+		{
+			int device;
+
+			if (get_user(device, (int __user *)arg))
+				return -EFAULT;
+			mutex_lock(&register_mutex);
+			device = device < 0 ? 0 : device + 1;
+			while (device < SNDRV_MINOR_HWDEPS) {
+				if (snd_hwdep_search(card, device))
+					break;
+				device++;
+			}
+			if (device >= SNDRV_MINOR_HWDEPS)
+				device = -1;
+			mutex_unlock(&register_mutex);
+			if (put_user(device, (int __user *)arg))
+				return -EFAULT;
+			return 0;
+		}
+	case SNDRV_CTL_IOCTL_HWDEP_INFO:
+		{
+			struct snd_hwdep_info __user *info = (struct snd_hwdep_info __user *)arg;
+			int device, err;
+			struct snd_hwdep *hwdep;
+
+			if (get_user(device, &info->device))
+				return -EFAULT;
+			mutex_lock(&register_mutex);
+			hwdep = snd_hwdep_search(card, device);
+			if (hwdep)
+				err = snd_hwdep_info(hwdep, info);
+			else
+				err = -ENXIO;
+			mutex_unlock(&register_mutex);
+			return err;
+		}
+	}
+	return -ENOIOCTLCMD;
+}
+
+#ifdef CONFIG_COMPAT
+#include "hwdep_compat.c"
+#else
+#define snd_hwdep_ioctl_compat	NULL
+#endif
+
+/*
+
+ */
+
+static const 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,
+};
+
+/**
+ * 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(struct snd_card *card, char *id, int device,
+		  struct snd_hwdep **rhwdep)
+{
+	struct snd_hwdep *hwdep;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_hwdep_dev_free,
+		.dev_register = snd_hwdep_dev_register,
+		.dev_disconnect = snd_hwdep_dev_disconnect,
+	};
+
+	if (snd_BUG_ON(!card))
+		return -ENXIO;
+	if (rhwdep)
+		*rhwdep = NULL;
+	hwdep = kzalloc(sizeof(*hwdep), GFP_KERNEL);
+	if (hwdep == NULL) {
+		snd_printk(KERN_ERR "hwdep: cannot allocate\n");
+		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);
+	mutex_init(&hwdep->open_mutex);
+	if (rhwdep)
+		*rhwdep = hwdep;
+	return 0;
+}
+
+static int snd_hwdep_free(struct snd_hwdep *hwdep)
+{
+	if (!hwdep)
+		return 0;
+	if (hwdep->private_free)
+		hwdep->private_free(hwdep);
+	kfree(hwdep);
+	return 0;
+}
+
+static int snd_hwdep_dev_free(struct snd_device *device)
+{
+	struct snd_hwdep *hwdep = device->device_data;
+	return snd_hwdep_free(hwdep);
+}
+
+static int snd_hwdep_dev_register(struct snd_device *device)
+{
+	struct snd_hwdep *hwdep = device->device_data;
+	int err;
+	char name[32];
+
+	mutex_lock(&register_mutex);
+	if (snd_hwdep_search(hwdep->card, hwdep->device)) {
+		mutex_unlock(&register_mutex);
+		return -EBUSY;
+	}
+	list_add_tail(&hwdep->list, &snd_hwdep_devices);
+	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_f_ops, hwdep, name)) < 0) {
+		snd_printk(KERN_ERR "unable to register hardware dependent device %i:%i\n",
+			   hwdep->card->number, hwdep->device);
+		list_del(&hwdep->list);
+		mutex_unlock(&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_f_ops, hwdep,
+						    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
+	mutex_unlock(&register_mutex);
+	return 0;
+}
+
+static int snd_hwdep_dev_disconnect(struct snd_device *device)
+{
+	struct snd_hwdep *hwdep = device->device_data;
+
+	if (snd_BUG_ON(!hwdep))
+		return -ENXIO;
+	mutex_lock(&register_mutex);
+	if (snd_hwdep_search(hwdep->card, hwdep->device) != hwdep) {
+		mutex_unlock(&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);
+	list_del_init(&hwdep->list);
+	mutex_unlock(&register_mutex);
+	return 0;
+}
+
+#ifdef CONFIG_PROC_FS
+/*
+ *  Info interface
+ */
+
+static void snd_hwdep_proc_read(struct snd_info_entry *entry,
+				struct snd_info_buffer *buffer)
+{
+	struct snd_hwdep *hwdep;
+
+	mutex_lock(&register_mutex);
+	list_for_each_entry(hwdep, &snd_hwdep_devices, list)
+		snd_iprintf(buffer, "%02i-%02i: %s\n",
+			    hwdep->card->number, hwdep->device, hwdep->name);
+	mutex_unlock(&register_mutex);
+}
+
+static struct snd_info_entry *snd_hwdep_proc_entry;
+
+static void __init snd_hwdep_proc_init(void)
+{
+	struct snd_info_entry *entry;
+
+	if ((entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL)) != NULL) {
+		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;
+}
+
+static void __exit snd_hwdep_proc_done(void)
+{
+	snd_info_free_entry(snd_hwdep_proc_entry);
+}
+#else /* !CONFIG_PROC_FS */
+#define snd_hwdep_proc_init()
+#define snd_hwdep_proc_done()
+#endif /* CONFIG_PROC_FS */
+
+
+/*
+ *  ENTRY functions
+ */
+
+static int __init alsa_hwdep_init(void)
+{
+	snd_hwdep_proc_init();
+	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);
+	snd_hwdep_proc_done();
+}
+
+module_init(alsa_hwdep_init)
+module_exit(alsa_hwdep_exit)
+
+EXPORT_SYMBOL(snd_hwdep_new);
diff --git a/linux/sound/core/hwdep_compat.c b/linux/sound/core/hwdep_compat.c
new file mode 100644
index 0000000..3827c0c
--- /dev/null
+++ b/linux/sound/core/hwdep_compat.c
@@ -0,0 +1,78 @@
+/*
+ *   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 snd_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(struct snd_hwdep *hw,
+				     struct snd_hwdep_dsp_image32 __user *src)
+{
+	struct snd_hwdep_dsp_image __user *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 snd_hwdep_dsp_image32)
+};
+
+static long snd_hwdep_ioctl_compat(struct file * file, unsigned int cmd,
+				   unsigned long arg)
+{
+	struct snd_hwdep *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/linux/sound/core/info.c b/linux/sound/core/info.c
new file mode 100644
index 0000000..7077f60
--- /dev/null
+++ b/linux/sound/core/info.c
@@ -0,0 +1,1015 @@
+/*
+ *  Information interface for ALSA driver
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <sound/version.h>
+#include <linux/proc_fs.h>
+#include <linux/mutex.h>
+#include <stdarg.h>
+
+/*
+ *
+ */
+
+#ifdef CONFIG_PROC_FS
+
+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;
+}
+
+static DEFINE_MUTEX(info_mutex);
+
+struct snd_info_private_data {
+	struct snd_info_buffer *rbuffer;
+	struct snd_info_buffer *wbuffer;
+	struct snd_info_entry *entry;
+	void *file_private_data;
+};
+
+static int snd_info_version_init(void);
+static int snd_info_version_done(void);
+static void snd_info_disconnect(struct snd_info_entry *entry);
+
+
+/* resize the proc r/w buffer */
+static int resize_info_buffer(struct snd_info_buffer *buffer,
+			      unsigned int nsize)
+{
+	char *nbuf;
+
+	nsize = PAGE_ALIGN(nsize);
+	nbuf = krealloc(buffer->buffer, nsize, GFP_KERNEL);
+	if (! nbuf)
+		return -ENOMEM;
+
+	buffer->buffer = nbuf;
+	buffer->len = nsize;
+	return 0;
+}
+
+/**
+ * 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(struct snd_info_buffer *buffer, const char *fmt, ...)
+{
+	va_list args;
+	int len, res;
+	int err = 0;
+
+	might_sleep();
+	if (buffer->stop || buffer->error)
+		return 0;
+	len = buffer->len - buffer->size;
+	va_start(args, fmt);
+	for (;;) {
+		va_list ap;
+		va_copy(ap, args);
+		res = vsnprintf(buffer->buffer + buffer->curr, len, fmt, ap);
+		va_end(ap);
+		if (res < len)
+			break;
+		err = resize_info_buffer(buffer, buffer->len + PAGE_SIZE);
+		if (err < 0)
+			break;
+		len = buffer->len - buffer->size;
+	}
+	va_end(args);
+
+	if (err < 0)
+		return err;
+	buffer->curr += res;
+	buffer->size += res;
+	return res;
+}
+
+EXPORT_SYMBOL(snd_iprintf);
+
+/*
+
+ */
+
+static struct proc_dir_entry *snd_proc_root;
+struct snd_info_entry *snd_seq_root;
+EXPORT_SYMBOL(snd_seq_root);
+
+#ifdef CONFIG_SND_OSSEMUL
+struct snd_info_entry *snd_oss_root;
+#endif
+
+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)
+{
+	struct snd_info_private_data *data;
+	struct snd_info_entry *entry;
+	loff_t ret = -EINVAL, size;
+
+	data = file->private_data;
+	entry = data->entry;
+	mutex_lock(&entry->access);
+	if (entry->content == SNDRV_INFO_CONTENT_DATA &&
+	    entry->c.ops->llseek) {
+		offset = entry->c.ops->llseek(entry,
+					      data->file_private_data,
+					      file, offset, orig);
+		goto out;
+	}
+	if (entry->content == SNDRV_INFO_CONTENT_DATA)
+		size = entry->size;
+	else
+		size = 0;
+	switch (orig) {
+	case SEEK_SET:
+		break;
+	case SEEK_CUR:
+		offset += file->f_pos;
+		break;
+	case SEEK_END:
+		if (!size)
+			goto out;
+		offset += size;
+		break;
+	default:
+		goto out;
+	}
+	if (offset < 0)
+		goto out;
+	if (size && offset > size)
+		offset = size;
+	file->f_pos = offset;
+	ret = offset;
+ out:
+	mutex_unlock(&entry->access);
+	return ret;
+}
+
+static ssize_t snd_info_entry_read(struct file *file, char __user *buffer,
+				   size_t count, loff_t * offset)
+{
+	struct snd_info_private_data *data;
+	struct snd_info_entry *entry;
+	struct snd_info_buffer *buf;
+	size_t size = 0;
+	loff_t pos;
+
+	data = file->private_data;
+	if (snd_BUG_ON(!data))
+		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 (pos >= entry->size)
+			return 0;
+		if (entry->c.ops->read) {
+			size = entry->size - pos;
+			size = min(count, size);
+			size = entry->c.ops->read(entry,
+						  data->file_private_data,
+						  file, buffer, size, 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)
+{
+	struct snd_info_private_data *data;
+	struct snd_info_entry *entry;
+	struct snd_info_buffer *buf;
+	ssize_t size = 0;
+	loff_t pos;
+
+	data = file->private_data;
+	if (snd_BUG_ON(!data))
+		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;
+		mutex_lock(&entry->access);
+		if (pos + count >= buf->len) {
+			if (resize_info_buffer(buf, pos + count)) {
+				mutex_unlock(&entry->access);
+				return -ENOMEM;
+			}
+		}
+		if (copy_from_user(buf->buffer + pos, buffer, count)) {
+			mutex_unlock(&entry->access);
+			return -EFAULT;
+		}
+		buf->size = pos + count;
+		mutex_unlock(&entry->access);
+		size = count;
+		break;
+	case SNDRV_INFO_CONTENT_DATA:
+		if (entry->c.ops->write && count > 0) {
+			size_t maxsize = entry->size - pos;
+			count = min(count, maxsize);
+			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)
+{
+	struct snd_info_entry *entry;
+	struct snd_info_private_data *data;
+	struct snd_info_buffer *buffer;
+	struct proc_dir_entry *p;
+	int mode, err;
+
+	mutex_lock(&info_mutex);
+	p = PDE(inode);
+	entry = p == NULL ? NULL : (struct snd_info_entry *)p->data;
+	if (entry == NULL || ! entry->p) {
+		mutex_unlock(&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_DATA &&
+		     entry->c.ops->read == NULL)) {
+		    	err = -ENODEV;
+		    	goto __error;
+		}
+	}
+	if (mode == O_WRONLY || mode == O_RDWR) {
+		if ((entry->content == SNDRV_INFO_CONTENT_DATA &&
+		     entry->c.ops->write == NULL)) {
+		    	err = -ENODEV;
+		    	goto __error;
+		}
+	}
+	data = kzalloc(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 = kzalloc(sizeof(*buffer), GFP_KERNEL);
+			if (buffer == NULL)
+				goto __nomem;
+			data->rbuffer = buffer;
+			buffer->len = PAGE_SIZE;
+			buffer->buffer = kmalloc(buffer->len, GFP_KERNEL);
+			if (buffer->buffer == NULL)
+				goto __nomem;
+		}
+		if (mode == O_WRONLY || mode == O_RDWR) {
+			buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
+			if (buffer == NULL)
+				goto __nomem;
+			data->wbuffer = buffer;
+			buffer->len = PAGE_SIZE;
+			buffer->buffer = kmalloc(buffer->len, GFP_KERNEL);
+			if (buffer->buffer == NULL)
+				goto __nomem;
+		}
+		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;
+	mutex_unlock(&info_mutex);
+	if (entry->content == SNDRV_INFO_CONTENT_TEXT &&
+	    (mode == O_RDONLY || mode == O_RDWR)) {
+		if (entry->c.text.read) {
+			mutex_lock(&entry->access);
+			entry->c.text.read(entry, data->rbuffer);
+			mutex_unlock(&entry->access);
+		}
+	}
+	return 0;
+
+ __nomem:
+	if (data->rbuffer) {
+		kfree(data->rbuffer->buffer);
+		kfree(data->rbuffer);
+	}
+	if (data->wbuffer) {
+		kfree(data->wbuffer->buffer);
+		kfree(data->wbuffer);
+	}
+	kfree(data);
+	err = -ENOMEM;
+      __error:
+	module_put(entry->module);
+      __error1:
+	mutex_unlock(&info_mutex);
+	return err;
+}
+
+static int snd_info_entry_release(struct inode *inode, struct file *file)
+{
+	struct snd_info_entry *entry;
+	struct snd_info_private_data *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 (data->rbuffer) {
+			kfree(data->rbuffer->buffer);
+			kfree(data->rbuffer);
+		}
+		if (data->wbuffer) {
+			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);
+				}
+			}
+			kfree(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)
+{
+	struct snd_info_private_data *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 long snd_info_entry_ioctl(struct file *file, unsigned int cmd,
+				unsigned long arg)
+{
+	struct snd_info_private_data *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;
+}
+
+static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct inode *inode = file->f_path.dentry->d_inode;
+	struct snd_info_private_data *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 const 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,
+	.unlocked_ioctl =	snd_info_entry_ioctl,
+	.mmap =			snd_info_entry_mmap,
+	.open =			snd_info_entry_open,
+	.release =		snd_info_entry_release,
+};
+
+int __init snd_info_init(void)
+{
+	struct proc_dir_entry *p;
+
+	p = create_proc_entry("asound", S_IFDIR | S_IRUGO | S_IXUGO, NULL);
+	if (p == NULL)
+		return -ENOMEM;
+	snd_proc_root = p;
+#ifdef CONFIG_SND_OSSEMUL
+	{
+		struct snd_info_entry *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)
+	{
+		struct snd_info_entry *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_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_info_version_done();
+	if (snd_proc_root) {
+#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
+		snd_info_free_entry(snd_seq_root);
+#endif
+#ifdef CONFIG_SND_OSSEMUL
+		snd_info_free_entry(snd_oss_root);
+#endif
+		snd_remove_proc_entry(NULL, snd_proc_root);
+	}
+	return 0;
+}
+
+/*
+
+ */
+
+
+/*
+ * create a card proc file
+ * called from init.c
+ */
+int snd_info_card_create(struct snd_card *card)
+{
+	char str[8];
+	struct snd_info_entry *entry;
+
+	if (snd_BUG_ON(!card))
+		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(struct snd_card *card)
+{
+	struct proc_dir_entry *p;
+
+	if (snd_BUG_ON(!card))
+		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;
+}
+
+/*
+ * called on card->id change
+ */
+void snd_info_card_id_change(struct snd_card *card)
+{
+	mutex_lock(&info_mutex);
+	if (card->proc_root_link) {
+		snd_remove_proc_entry(snd_proc_root, card->proc_root_link);
+		card->proc_root_link = NULL;
+	}
+	if (strcmp(card->id, card->proc_root->name))
+		card->proc_root_link = proc_symlink(card->id,
+						    snd_proc_root,
+						    card->proc_root->name);
+	mutex_unlock(&info_mutex);
+}
+
+/*
+ * de-register the card proc file
+ * called from init.c
+ */
+void snd_info_card_disconnect(struct snd_card *card)
+{
+	if (!card)
+		return;
+	mutex_lock(&info_mutex);
+	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_disconnect(card->proc_root);
+	mutex_unlock(&info_mutex);
+}
+
+/*
+ * release the card proc file resources
+ * called from init.c
+ */
+int snd_info_card_free(struct snd_card *card)
+{
+	if (!card)
+		return 0;
+	snd_info_free_entry(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(struct snd_info_buffer *buffer, char *line, int len)
+{
+	int c = -1;
+
+	if (len <= 0 || buffer->stop || buffer->error)
+		return 1;
+	while (--len > 0) {
+		c = buffer->buffer[buffer->curr++];
+		if (c == '\n') {
+			if (buffer->curr >= buffer->size)
+				buffer->stop = 1;
+			break;
+		}
+		*line++ = c;
+		if (buffer->curr >= buffer->size) {
+			buffer->stop = 1;
+			break;
+		}
+	}
+	while (c != '\n' && !buffer->stop) {
+		c = buffer->buffer[buffer->curr++];
+		if (buffer->curr >= buffer->size)
+			buffer->stop = 1;
+	}
+	*line = '\0';
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_info_get_line);
+
+/**
+ * snd_info_get_str - 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.
+ */
+const char *snd_info_get_str(char *dest, const 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;
+}
+
+EXPORT_SYMBOL(snd_info_get_str);
+
+/**
+ * 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 struct snd_info_entry *snd_info_create_entry(const char *name)
+{
+	struct snd_info_entry *entry;
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (entry == NULL)
+		return NULL;
+	entry->name = kstrdup(name, GFP_KERNEL);
+	if (entry->name == NULL) {
+		kfree(entry);
+		return NULL;
+	}
+	entry->mode = S_IFREG | S_IRUGO;
+	entry->content = SNDRV_INFO_CONTENT_TEXT;
+	mutex_init(&entry->access);
+	INIT_LIST_HEAD(&entry->children);
+	INIT_LIST_HEAD(&entry->list);
+	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.
+ */
+struct snd_info_entry *snd_info_create_module_entry(struct module * module,
+					       const char *name,
+					       struct snd_info_entry *parent)
+{
+	struct snd_info_entry *entry = snd_info_create_entry(name);
+	if (entry) {
+		entry->module = module;
+		entry->parent = parent;
+	}
+	return entry;
+}
+
+EXPORT_SYMBOL(snd_info_create_module_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.
+ */
+struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card,
+					     const char *name,
+					     struct snd_info_entry * parent)
+{
+	struct snd_info_entry *entry = snd_info_create_entry(name);
+	if (entry) {
+		entry->module = card->module;
+		entry->card = card;
+		entry->parent = parent;
+	}
+	return entry;
+}
+
+EXPORT_SYMBOL(snd_info_create_card_entry);
+
+static void snd_info_disconnect(struct snd_info_entry *entry)
+{
+	struct list_head *p, *n;
+	struct proc_dir_entry *root;
+
+	list_for_each_safe(p, n, &entry->children) {
+		snd_info_disconnect(list_entry(p, struct snd_info_entry, list));
+	}
+
+	if (! entry->p)
+		return;
+	list_del_init(&entry->list);
+	root = entry->parent == NULL ? snd_proc_root : entry->parent->p;
+	snd_BUG_ON(!root);
+	snd_remove_proc_entry(root, entry->p);
+	entry->p = NULL;
+}
+
+static int snd_info_dev_free_entry(struct snd_device *device)
+{
+	struct snd_info_entry *entry = device->device_data;
+	snd_info_free_entry(entry);
+	return 0;
+}
+
+static int snd_info_dev_register_entry(struct snd_device *device)
+{
+	struct snd_info_entry *entry = device->device_data;
+	return snd_info_register(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(struct snd_card *card, const char *name,
+		      struct snd_info_entry **entryp)
+{
+	static struct snd_device_ops ops = {
+		.dev_free = snd_info_dev_free_entry,
+		.dev_register =	snd_info_dev_register_entry,
+		/* disconnect is done via snd_info_card_disconnect() */
+	};
+	struct snd_info_entry *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;
+}
+
+EXPORT_SYMBOL(snd_card_proc_new);
+
+/**
+ * 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(struct snd_info_entry * entry)
+{
+	if (entry == NULL)
+		return;
+	if (entry->p) {
+		mutex_lock(&info_mutex);
+		snd_info_disconnect(entry);
+		mutex_unlock(&info_mutex);
+	}
+	kfree(entry->name);
+	if (entry->private_free)
+		entry->private_free(entry);
+	kfree(entry);
+}
+
+EXPORT_SYMBOL(snd_info_free_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(struct snd_info_entry * entry)
+{
+	struct proc_dir_entry *root, *p = NULL;
+
+	if (snd_BUG_ON(!entry))
+		return -ENXIO;
+	root = entry->parent == NULL ? snd_proc_root : entry->parent->p;
+	mutex_lock(&info_mutex);
+	p = create_proc_entry(entry->name, entry->mode, root);
+	if (!p) {
+		mutex_unlock(&info_mutex);
+		return -ENOMEM;
+	}
+	if (!S_ISDIR(entry->mode))
+		p->proc_fops = &snd_info_entry_operations;
+	p->size = entry->size;
+	p->data = entry;
+	entry->p = p;
+	if (entry->parent)
+		list_add_tail(&entry->list, &entry->parent->children);
+	mutex_unlock(&info_mutex);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_info_register);
+
+/*
+
+ */
+
+static struct snd_info_entry *snd_info_version_entry;
+
+static void snd_info_version_read(struct snd_info_entry *entry, struct snd_info_buffer *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)
+{
+	struct snd_info_entry *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL);
+	if (entry == NULL)
+		return -ENOMEM;
+	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)
+{
+	snd_info_free_entry(snd_info_version_entry);
+	return 0;
+}
+
+#endif /* CONFIG_PROC_FS */
diff --git a/linux/sound/core/info_oss.c b/linux/sound/core/info_oss.c
new file mode 100644
index 0000000..e4af138
--- /dev/null
+++ b/linux/sound/core/info_oss.c
@@ -0,0 +1,138 @@
+/*
+ *  Information interface for ALSA driver
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/info.h>
+#include <sound/version.h>
+#include <linux/utsname.h>
+#include <linux/mutex.h>
+
+#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
+
+/*
+ *  OSS compatible part
+ */
+
+static DEFINE_MUTEX(strings);
+static char *snd_sndstat_strings[SNDRV_CARDS][SNDRV_OSS_INFO_DEV_COUNT];
+static struct snd_info_entry *snd_sndstat_proc_entry;
+
+int snd_oss_info_register(int dev, int num, char *string)
+{
+	char *x;
+
+	if (snd_BUG_ON(dev < 0 || dev >= SNDRV_OSS_INFO_DEV_COUNT))
+		return -ENXIO;
+	if (snd_BUG_ON(num < 0 || num >= SNDRV_CARDS))
+		return -ENXIO;
+	mutex_lock(&strings);
+	if (string == NULL) {
+		if ((x = snd_sndstat_strings[num][dev]) != NULL) {
+			kfree(x);
+			x = NULL;
+		}
+	} else {
+		x = kstrdup(string, GFP_KERNEL);
+		if (x == NULL) {
+			mutex_unlock(&strings);
+			return -ENOMEM;
+		}
+	}
+	snd_sndstat_strings[num][dev] = x;
+	mutex_unlock(&strings);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_oss_info_register);
+
+static int snd_sndstat_show_strings(struct snd_info_buffer *buf, char *id, int dev)
+{
+	int idx, ok = -1;
+	char *str;
+
+	snd_iprintf(buf, "\n%s:", id);
+	mutex_lock(&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);
+		}
+	}
+	mutex_unlock(&strings);
+	if (ok < 0)
+		snd_iprintf(buf, " NOT ENABLED IN CONFIG\n");
+	return ok;
+}
+
+static void snd_sndstat_proc_read(struct snd_info_entry *entry,
+				  struct snd_info_buffer *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",
+		    init_utsname()->sysname,
+		    init_utsname()->nodename,
+		    init_utsname()->release,
+		    init_utsname()->version,
+		    init_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)
+{
+	struct snd_info_entry *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 = 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)
+{
+	snd_info_free_entry(snd_sndstat_proc_entry);
+	snd_sndstat_proc_entry = NULL;
+	return 0;
+}
+
+#endif /* CONFIG_SND_OSSEMUL */
diff --git a/linux/sound/core/init.c b/linux/sound/core/init.c
new file mode 100644
index 0000000..57b792e
--- /dev/null
+++ b/linux/sound/core/init.c
@@ -0,0 +1,947 @@
+/*
+ *  Initialization routines
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/file.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/ctype.h>
+#include <linux/pm.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+
+/* monitor files for graceful shutdown (hotplug) */
+struct snd_monitor_file {
+	struct file *file;
+	const struct file_operations *disconnected_f_op;
+	struct list_head shutdown_list;	/* still need to shutdown */
+	struct list_head list;	/* link of monitor files */
+};
+
+static DEFINE_SPINLOCK(shutdown_lock);
+static LIST_HEAD(shutdown_files);
+
+static const struct file_operations snd_shutdown_f_ops;
+
+static unsigned int snd_cards_lock;	/* locked for registering/using */
+struct snd_card *snd_cards[SNDRV_CARDS];
+EXPORT_SYMBOL(snd_cards);
+
+static DEFINE_MUTEX(snd_card_mutex);
+
+static char *slots[SNDRV_CARDS];
+module_param_array(slots, charp, NULL, 0444);
+MODULE_PARM_DESC(slots, "Module names assigned to the slots.");
+
+/* return non-zero if the given index is reserved for the given
+ * module via slots option
+ */
+static int module_slot_match(struct module *module, int idx)
+{
+	int match = 1;
+#ifdef MODULE
+	const char *s1, *s2;
+
+	if (!module || !module->name || !slots[idx])
+		return 0;
+
+	s1 = module->name;
+	s2 = slots[idx];
+	if (*s2 == '!') {
+		match = 0; /* negative match */
+		s2++;
+	}
+	/* compare module name strings
+	 * hyphens are handled as equivalent with underscore
+	 */
+	for (;;) {
+		char c1 = *s1++;
+		char c2 = *s2++;
+		if (c1 == '-')
+			c1 = '_';
+		if (c2 == '-')
+			c2 = '_';
+		if (c1 != c2)
+			return !match;
+		if (!c1)
+			break;
+	}
+#endif /* MODULE */
+	return match;
+}
+
+#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
+int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int free_flag);
+EXPORT_SYMBOL(snd_mixer_oss_notify_callback);
+#endif
+
+#ifdef CONFIG_PROC_FS
+static void snd_card_id_read(struct snd_info_entry *entry,
+			     struct snd_info_buffer *buffer)
+{
+	snd_iprintf(buffer, "%s\n", entry->card->id);
+}
+
+static inline int init_info_for_card(struct snd_card *card)
+{
+	int err;
+	struct snd_info_entry *entry;
+
+	if ((err = snd_info_card_register(card)) < 0) {
+		snd_printd("unable to create card info\n");
+		return err;
+	}
+	if ((entry = snd_info_create_card_entry(card, "id", card->proc_root)) == NULL) {
+		snd_printd("unable to create card entry\n");
+		return err;
+	}
+	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;
+	return 0;
+}
+#else /* !CONFIG_PROC_FS */
+#define init_info_for_card(card)
+#endif
+
+/**
+ *  snd_card_create - 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
+ *  @card_ret: the pointer to store the created card instance
+ *
+ *  Creates and initializes a soundcard structure.
+ *
+ *  The function allocates snd_card instance via kzalloc with the given
+ *  space for the driver to use freely.  The allocated struct is stored
+ *  in the given card_ret pointer.
+ *
+ *  Returns zero if successful or a negative error code.
+ */
+int snd_card_create(int idx, const char *xid,
+		    struct module *module, int extra_size,
+		    struct snd_card **card_ret)
+{
+	struct snd_card *card;
+	int err, idx2;
+
+	if (snd_BUG_ON(!card_ret))
+		return -EINVAL;
+	*card_ret = NULL;
+
+	if (extra_size < 0)
+		extra_size = 0;
+	card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
+	if (!card)
+		return -ENOMEM;
+	if (xid)
+		strlcpy(card->id, xid, sizeof(card->id));
+	err = 0;
+	mutex_lock(&snd_card_mutex);
+	if (idx < 0) {
+		for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
+			/* idx == -1 == 0xffff means: take any free slot */
+			if (~snd_cards_lock & idx & 1<<idx2) {
+				if (module_slot_match(module, idx2)) {
+					idx = idx2;
+					break;
+				}
+			}
+	}
+	if (idx < 0) {
+		for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
+			/* idx == -1 == 0xffff means: take any free slot */
+			if (~snd_cards_lock & idx & 1<<idx2) {
+				if (!slots[idx2] || !*slots[idx2]) {
+					idx = idx2;
+					break;
+				}
+			}
+	}
+	if (idx < 0)
+		err = -ENODEV;
+	else if (idx < snd_ecards_limit) {
+		if (snd_cards_lock & (1 << idx))
+			err = -EBUSY;	/* invalid */
+	} else if (idx >= SNDRV_CARDS)
+		err = -ENODEV;
+	if (err < 0) {
+		mutex_unlock(&snd_card_mutex);
+		snd_printk(KERN_ERR "cannot find the slot for index %d (range 0-%i), error: %d\n",
+			 idx, snd_ecards_limit - 1, err);
+		goto __error;
+	}
+	snd_cards_lock |= 1 << idx;		/* lock it */
+	if (idx >= snd_ecards_limit)
+		snd_ecards_limit = idx + 1; /* increase the limit */
+	mutex_unlock(&snd_card_mutex);
+	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_LIST_HEAD(&card->files_list);
+	init_waitqueue_head(&card->shutdown_sleep);
+#ifdef CONFIG_PM
+	mutex_init(&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 */
+	err = snd_ctl_create(card);
+	if (err < 0) {
+		snd_printk(KERN_ERR "unable to register control minors\n");
+		goto __error;
+	}
+	err = snd_info_card_create(card);
+	if (err < 0) {
+		snd_printk(KERN_ERR "unable to create card info\n");
+		goto __error_ctl;
+	}
+	if (extra_size > 0)
+		card->private_data = (char *)card + sizeof(struct snd_card);
+	*card_ret = card;
+	return 0;
+
+      __error_ctl:
+	snd_device_free_all(card, SNDRV_DEV_CMD_PRE);
+      __error:
+	kfree(card);
+  	return err;
+}
+EXPORT_SYMBOL(snd_card_create);
+
+/* return non-zero if a card is already locked */
+int snd_card_locked(int card)
+{
+	int locked;
+
+	mutex_lock(&snd_card_mutex);
+	locked = snd_cards_lock & (1 << card);
+	mutex_unlock(&snd_card_mutex);
+	return locked;
+}
+
+static loff_t snd_disconnect_llseek(struct file *file, loff_t offset, int orig)
+{
+	return -ENODEV;
+}
+
+static ssize_t snd_disconnect_read(struct file *file, char __user *buf,
+				   size_t count, loff_t *offset)
+{
+	return -ENODEV;
+}
+
+static ssize_t snd_disconnect_write(struct file *file, const char __user *buf,
+				    size_t count, loff_t *offset)
+{
+	return -ENODEV;
+}
+
+static int snd_disconnect_release(struct inode *inode, struct file *file)
+{
+	struct snd_monitor_file *df = NULL, *_df;
+
+	spin_lock(&shutdown_lock);
+	list_for_each_entry(_df, &shutdown_files, shutdown_list) {
+		if (_df->file == file) {
+			df = _df;
+			list_del_init(&df->shutdown_list);
+			break;
+		}
+	}
+	spin_unlock(&shutdown_lock);
+
+	if (likely(df)) {
+		if ((file->f_flags & FASYNC) && df->disconnected_f_op->fasync)
+			df->disconnected_f_op->fasync(-1, file, 0);
+		return df->disconnected_f_op->release(inode, file);
+	}
+
+	panic("%s(%p, %p) failed!", __func__, inode, file);
+}
+
+static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait)
+{
+	return POLLERR | POLLNVAL;
+}
+
+static long snd_disconnect_ioctl(struct file *file,
+				 unsigned int cmd, unsigned long arg)
+{
+	return -ENODEV;
+}
+
+static int snd_disconnect_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	return -ENODEV;
+}
+
+static int snd_disconnect_fasync(int fd, struct file *file, int on)
+{
+	return -ENODEV;
+}
+
+static const struct file_operations snd_shutdown_f_ops =
+{
+	.owner = 	THIS_MODULE,
+	.llseek =	snd_disconnect_llseek,
+	.read = 	snd_disconnect_read,
+	.write =	snd_disconnect_write,
+	.release =	snd_disconnect_release,
+	.poll =		snd_disconnect_poll,
+	.unlocked_ioctl = snd_disconnect_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = snd_disconnect_ioctl,
+#endif
+	.mmap =		snd_disconnect_mmap,
+	.fasync =	snd_disconnect_fasync
+};
+
+/**
+ *  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(struct snd_card *card)
+{
+	struct snd_monitor_file *mfile;
+	struct file *file;
+	int err;
+
+	if (!card)
+		return -EINVAL;
+
+	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 */
+	mutex_lock(&snd_card_mutex);
+	snd_cards[card->number] = NULL;
+	snd_cards_lock &= ~(1 << card->number);
+	mutex_unlock(&snd_card_mutex);
+	
+	/* phase 2: replace file->f_op with special dummy operations */
+	
+	spin_lock(&card->files_lock);
+	list_for_each_entry(mfile, &card->files_list, list) {
+		file = mfile->file;
+
+		/* it's critical part, use endless loop */
+		/* we have no room to fail */
+		mfile->disconnected_f_op = mfile->file->f_op;
+
+		spin_lock(&shutdown_lock);
+		list_add(&mfile->shutdown_list, &shutdown_files);
+		spin_unlock(&shutdown_lock);
+
+		mfile->file->f_op = &snd_shutdown_f_ops;
+		fops_get(mfile->file->f_op);
+	}
+	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);
+
+	snd_info_card_disconnect(card);
+	if (card->card_dev) {
+		device_unregister(card->card_dev);
+		card->card_dev = NULL;
+	}
+#ifdef CONFIG_PM
+	wake_up(&card->power_sleep);
+#endif
+	return 0;	
+}
+
+EXPORT_SYMBOL(snd_card_disconnect);
+
+/**
+ *  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.
+ */
+static int snd_card_do_free(struct snd_card *card)
+{
+#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);
+	snd_info_free_entry(card->proc_id);
+	if (snd_info_card_free(card) < 0) {
+		snd_printk(KERN_WARNING "unable to free card info\n");
+		/* Not fatal error */
+	}
+	kfree(card);
+	return 0;
+}
+
+int snd_card_free_when_closed(struct snd_card *card)
+{
+	int free_now = 0;
+	int ret = snd_card_disconnect(card);
+	if (ret)
+		return ret;
+
+	spin_lock(&card->files_lock);
+	if (list_empty(&card->files_list))
+		free_now = 1;
+	else
+		card->free_on_last_close = 1;
+	spin_unlock(&card->files_lock);
+
+	if (free_now)
+		snd_card_do_free(card);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_card_free_when_closed);
+
+int snd_card_free(struct snd_card *card)
+{
+	int ret = snd_card_disconnect(card);
+	if (ret)
+		return ret;
+
+	/* wait, until all devices are ready for the free operation */
+	wait_event(card->shutdown_sleep, list_empty(&card->files_list));
+	snd_card_do_free(card);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_card_free);
+
+static void snd_card_set_id_no_lock(struct snd_card *card, const char *nid)
+{
+	int i, len, idx_flag = 0, loops = SNDRV_CARDS;
+	const char *spos, *src;
+	char *id;
+	
+	if (nid == NULL) {
+		id = card->shortname;
+		spos = src = id;
+		while (*id != '\0') {
+			if (*id == ' ')
+				spos = id + 1;
+			id++;
+		}
+	} else {
+		spos = src = nid;
+	}
+	id = card->id;
+	while (*spos != '\0' && !isalnum(*spos))
+		spos++;
+	if (isdigit(*spos))
+		*id++ = isalpha(src[0]) ? src[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 set 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) {
+			if (id[len-1] != '9')
+				id[len-1]++;
+			else
+				id[len-1] = 'A';
+		} 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++;
+			*(char *)spos++ = '_';
+			*(char *)spos++ = '1';
+			*(char *)spos++ = '\0';
+			idx_flag++;
+		}
+	}
+}
+
+/**
+ *  snd_card_set_id - set card identification name
+ *  @card: soundcard structure
+ *  @nid: new identification string
+ *
+ *  This function sets the card identification and checks for name
+ *  collisions.
+ */
+void snd_card_set_id(struct snd_card *card, const char *nid)
+{
+	/* check if user specified own card->id */
+	if (card->id[0] != '\0')
+		return;
+	mutex_lock(&snd_card_mutex);
+	snd_card_set_id_no_lock(card, nid);
+	mutex_unlock(&snd_card_mutex);
+}
+EXPORT_SYMBOL(snd_card_set_id);
+
+static ssize_t
+card_id_show_attr(struct device *dev,
+		  struct device_attribute *attr, char *buf)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	return snprintf(buf, PAGE_SIZE, "%s\n", card ? card->id : "(null)");
+}
+
+static ssize_t
+card_id_store_attr(struct device *dev, struct device_attribute *attr,
+		   const char *buf, size_t count)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	char buf1[sizeof(card->id)];
+	size_t copy = count > sizeof(card->id) - 1 ?
+					sizeof(card->id) - 1 : count;
+	size_t idx;
+	int c;
+
+	for (idx = 0; idx < copy; idx++) {
+		c = buf[idx];
+		if (!isalnum(c) && c != '_' && c != '-')
+			return -EINVAL;
+	}
+	memcpy(buf1, buf, copy);
+	buf1[copy] = '\0';
+	mutex_lock(&snd_card_mutex);
+	if (!snd_info_check_reserved_words(buf1)) {
+	     __exist:
+		mutex_unlock(&snd_card_mutex);
+		return -EEXIST;
+	}
+	for (idx = 0; idx < snd_ecards_limit; idx++) {
+		if (snd_cards[idx] && !strcmp(snd_cards[idx]->id, buf1)) {
+			if (card == snd_cards[idx])
+				goto __ok;
+			else
+				goto __exist;
+		}
+	}
+	strcpy(card->id, buf1);
+	snd_info_card_id_change(card);
+__ok:
+	mutex_unlock(&snd_card_mutex);
+
+	return count;
+}
+
+static struct device_attribute card_id_attrs =
+	__ATTR(id, S_IRUGO | S_IWUSR, card_id_show_attr, card_id_store_attr);
+
+static ssize_t
+card_number_show_attr(struct device *dev,
+		     struct device_attribute *attr, char *buf)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	return snprintf(buf, PAGE_SIZE, "%i\n", card ? card->number : -1);
+}
+
+static struct device_attribute card_number_attrs =
+	__ATTR(number, S_IRUGO, card_number_show_attr, NULL);
+
+/**
+ *  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(struct snd_card *card)
+{
+	int err;
+
+	if (snd_BUG_ON(!card))
+		return -EINVAL;
+
+	if (!card->card_dev) {
+		card->card_dev = device_create(sound_class, card->dev,
+					       MKDEV(0, 0), card,
+					       "card%i", card->number);
+		if (IS_ERR(card->card_dev))
+			card->card_dev = NULL;
+	}
+
+	if ((err = snd_device_register_all(card)) < 0)
+		return err;
+	mutex_lock(&snd_card_mutex);
+	if (snd_cards[card->number]) {
+		/* already registered */
+		mutex_unlock(&snd_card_mutex);
+		return 0;
+	}
+	snd_card_set_id_no_lock(card, card->id[0] == '\0' ? NULL : card->id);
+	snd_cards[card->number] = card;
+	mutex_unlock(&snd_card_mutex);
+	init_info_for_card(card);
+#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
+	if (card->card_dev) {
+		err = device_create_file(card->card_dev, &card_id_attrs);
+		if (err < 0)
+			return err;
+		err = device_create_file(card->card_dev, &card_number_attrs);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_card_register);
+
+#ifdef CONFIG_PROC_FS
+static struct snd_info_entry *snd_card_info_entry;
+
+static void snd_card_info_read(struct snd_info_entry *entry,
+			       struct snd_info_buffer *buffer)
+{
+	int idx, count;
+	struct snd_card *card;
+
+	for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
+		mutex_lock(&snd_card_mutex);
+		if ((card = snd_cards[idx]) != NULL) {
+			count++;
+			snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n",
+					idx,
+					card->id,
+					card->driver,
+					card->shortname);
+			snd_iprintf(buffer, "                      %s\n",
+					card->longname);
+		}
+		mutex_unlock(&snd_card_mutex);
+	}
+	if (!count)
+		snd_iprintf(buffer, "--- no soundcards ---\n");
+}
+
+#ifdef CONFIG_SND_OSSEMUL
+
+void snd_card_info_read_oss(struct snd_info_buffer *buffer)
+{
+	int idx, count;
+	struct snd_card *card;
+
+	for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
+		mutex_lock(&snd_card_mutex);
+		if ((card = snd_cards[idx]) != NULL) {
+			count++;
+			snd_iprintf(buffer, "%s\n", card->longname);
+		}
+		mutex_unlock(&snd_card_mutex);
+	}
+	if (!count) {
+		snd_iprintf(buffer, "--- no soundcards ---\n");
+	}
+}
+
+#endif
+
+#ifdef MODULE
+static struct snd_info_entry *snd_card_module_info_entry;
+static void snd_card_module_info_read(struct snd_info_entry *entry,
+				      struct snd_info_buffer *buffer)
+{
+	int idx;
+	struct snd_card *card;
+
+	for (idx = 0; idx < SNDRV_CARDS; idx++) {
+		mutex_lock(&snd_card_mutex);
+		if ((card = snd_cards[idx]) != NULL)
+			snd_iprintf(buffer, "%2i %s\n",
+				    idx, card->module->name);
+		mutex_unlock(&snd_card_mutex);
+	}
+}
+#endif
+
+int __init snd_card_info_init(void)
+{
+	struct snd_info_entry *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, "cards", NULL);
+	if (! entry)
+		return -ENOMEM;
+	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 = 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)
+{
+	snd_info_free_entry(snd_card_info_entry);
+#ifdef MODULE
+	snd_info_free_entry(snd_card_module_info_entry);
+#endif
+	return 0;
+}
+
+#endif /* CONFIG_PROC_FS */
+
+/**
+ *  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(struct snd_card *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;
+}
+
+EXPORT_SYMBOL(snd_component_add);
+
+/**
+ *  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(struct snd_card *card, struct file *file)
+{
+	struct snd_monitor_file *mfile;
+
+	mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
+	if (mfile == NULL)
+		return -ENOMEM;
+	mfile->file = file;
+	mfile->disconnected_f_op = NULL;
+	spin_lock(&card->files_lock);
+	if (card->shutdown) {
+		spin_unlock(&card->files_lock);
+		kfree(mfile);
+		return -ENODEV;
+	}
+	list_add(&mfile->list, &card->files_list);
+	spin_unlock(&card->files_lock);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_card_file_add);
+
+/**
+ *  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 snd_card_free_when_closed() was
+ *  called beforehand, it processes the pending release of
+ *  resources.
+ *
+ *  Returns zero or a negative error code.
+ */
+int snd_card_file_remove(struct snd_card *card, struct file *file)
+{
+	struct snd_monitor_file *mfile, *found = NULL;
+	int last_close = 0;
+
+	spin_lock(&card->files_lock);
+	list_for_each_entry(mfile, &card->files_list, list) {
+		if (mfile->file == file) {
+			list_del(&mfile->list);
+			if (mfile->disconnected_f_op)
+				fops_put(mfile->disconnected_f_op);
+			found = mfile;
+			break;
+		}
+	}
+	if (list_empty(&card->files_list))
+		last_close = 1;
+	spin_unlock(&card->files_lock);
+	if (last_close) {
+		wake_up(&card->shutdown_sleep);
+		if (card->free_on_last_close)
+			snd_card_do_free(card);
+	}
+	if (!found) {
+		snd_printk(KERN_ERR "ALSA card file remove problem (%p)\n", file);
+		return -ENOENT;
+	}
+	kfree(found);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_card_file_remove);
+
+#ifdef CONFIG_PM
+/**
+ *  snd_power_wait - wait until the power-state is changed.
+ *  @card: soundcard structure
+ *  @power_state: expected power state
+ *
+ *  Waits until the power-state is changed.
+ *
+ *  Note: the power lock must be active before call.
+ */
+int snd_power_wait(struct snd_card *card, unsigned int power_state)
+{
+	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;
+		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;
+}
+
+EXPORT_SYMBOL(snd_power_wait);
+#endif /* CONFIG_PM */
diff --git a/linux/sound/core/isadma.c b/linux/sound/core/isadma.c
new file mode 100644
index 0000000..950e19b
--- /dev/null
+++ b/linux/sound/core/isadma.c
@@ -0,0 +1,116 @@
+/*
+ *  ISA DMA support functions
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ * Defining following add some delay. Maybe this helps for some broken
+ * ISA DMA controllers.
+ */
+
+#undef HAVE_REALLY_SLOW_DMA_CONTROLLER
+
+#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);
+}
+
+EXPORT_SYMBOL(snd_dma_program);
+
+/**
+ * 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);
+}
+
+EXPORT_SYMBOL(snd_dma_disable);
+
+/**
+ * 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, result1;
+
+	flags = claim_dma_lock();
+	clear_dma_ff(dma);
+	if (!isa_dma_bridge_buggy)
+		disable_dma(dma);
+	result = get_dma_residue(dma);
+	/*
+	 * HACK - read the counter again and choose higher value in order to
+	 * avoid reading during counter lower byte roll over if the
+	 * isa_dma_bridge_buggy is set.
+	 */
+	result1 = get_dma_residue(dma);
+	if (!isa_dma_bridge_buggy)
+		enable_dma(dma);
+	release_dma_lock(flags);
+	if (unlikely(result < result1))
+		result = result1;
+#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;
+}
+
+EXPORT_SYMBOL(snd_dma_pointer);
diff --git a/linux/sound/core/jack.c b/linux/sound/core/jack.c
new file mode 100644
index 0000000..4902ae5
--- /dev/null
+++ b/linux/sound/core/jack.c
@@ -0,0 +1,239 @@
+/*
+ *  Jack abstraction layer
+ *
+ *  Copyright 2008 Wolfson Microelectronics
+ *
+ *   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/input.h>
+#include <linux/slab.h>
+#include <sound/jack.h>
+#include <sound/core.h>
+
+static int jack_switch_types[] = {
+	SW_HEADPHONE_INSERT,
+	SW_MICROPHONE_INSERT,
+	SW_LINEOUT_INSERT,
+	SW_JACK_PHYSICAL_INSERT,
+	SW_VIDEOOUT_INSERT,
+};
+
+static int snd_jack_dev_free(struct snd_device *device)
+{
+	struct snd_jack *jack = device->device_data;
+
+	if (jack->private_free)
+		jack->private_free(jack);
+
+	/* If the input device is registered with the input subsystem
+	 * then we need to use a different deallocator. */
+	if (jack->registered)
+		input_unregister_device(jack->input_dev);
+	else
+		input_free_device(jack->input_dev);
+
+	kfree(jack->id);
+	kfree(jack);
+
+	return 0;
+}
+
+static int snd_jack_dev_register(struct snd_device *device)
+{
+	struct snd_jack *jack = device->device_data;
+	struct snd_card *card = device->card;
+	int err, i;
+
+	snprintf(jack->name, sizeof(jack->name), "%s %s",
+		 card->shortname, jack->id);
+	jack->input_dev->name = jack->name;
+
+	/* Default to the sound card device. */
+	if (!jack->input_dev->dev.parent)
+		jack->input_dev->dev.parent = snd_card_get_device_link(card);
+
+	/* Add capabilities for any keys that are enabled */
+	for (i = 0; i < ARRAY_SIZE(jack->key); i++) {
+		int testbit = SND_JACK_BTN_0 >> i;
+
+		if (!(jack->type & testbit))
+			continue;
+
+		if (!jack->key[i])
+			jack->key[i] = BTN_0 + i;
+
+		input_set_capability(jack->input_dev, EV_KEY, jack->key[i]);
+	}
+
+	err = input_register_device(jack->input_dev);
+	if (err == 0)
+		jack->registered = 1;
+
+	return err;
+}
+
+/**
+ * snd_jack_new - Create a new jack
+ * @card:  the card instance
+ * @id:    an identifying string for this jack
+ * @type:  a bitmask of enum snd_jack_type values that can be detected by
+ *         this jack
+ * @jjack: Used to provide the allocated jack object to the caller.
+ *
+ * Creates a new jack object.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ * On success jjack will be initialised.
+ */
+int snd_jack_new(struct snd_card *card, const char *id, int type,
+		 struct snd_jack **jjack)
+{
+	struct snd_jack *jack;
+	int err;
+	int i;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_jack_dev_free,
+		.dev_register = snd_jack_dev_register,
+	};
+
+	jack = kzalloc(sizeof(struct snd_jack), GFP_KERNEL);
+	if (jack == NULL)
+		return -ENOMEM;
+
+	jack->id = kstrdup(id, GFP_KERNEL);
+
+	jack->input_dev = input_allocate_device();
+	if (jack->input_dev == NULL) {
+		err = -ENOMEM;
+		goto fail_input;
+	}
+
+	jack->input_dev->phys = "ALSA";
+
+	jack->type = type;
+
+	for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++)
+		if (type & (1 << i))
+			input_set_capability(jack->input_dev, EV_SW,
+					     jack_switch_types[i]);
+
+	err = snd_device_new(card, SNDRV_DEV_JACK, jack, &ops);
+	if (err < 0)
+		goto fail_input;
+
+	*jjack = jack;
+
+	return 0;
+
+fail_input:
+	input_free_device(jack->input_dev);
+	kfree(jack);
+	return err;
+}
+EXPORT_SYMBOL(snd_jack_new);
+
+/**
+ * snd_jack_set_parent - Set the parent device for a jack
+ *
+ * @jack:   The jack to configure
+ * @parent: The device to set as parent for the jack.
+ *
+ * Set the parent for the jack input device in the device tree.  This
+ * function is only valid prior to registration of the jack.  If no
+ * parent is configured then the parent device will be the sound card.
+ */
+void snd_jack_set_parent(struct snd_jack *jack, struct device *parent)
+{
+	WARN_ON(jack->registered);
+
+	jack->input_dev->dev.parent = parent;
+}
+EXPORT_SYMBOL(snd_jack_set_parent);
+
+/**
+ * snd_jack_set_key - Set a key mapping on a jack
+ *
+ * @jack:    The jack to configure
+ * @type:    Jack report type for this key
+ * @keytype: Input layer key type to be reported
+ *
+ * Map a SND_JACK_BTN_ button type to an input layer key, allowing
+ * reporting of keys on accessories via the jack abstraction.  If no
+ * mapping is provided but keys are enabled in the jack type then
+ * BTN_n numeric buttons will be reported.
+ *
+ * Note that this is intended to be use by simple devices with small
+ * numbers of keys that can be reported.  It is also possible to
+ * access the input device directly - devices with complex input
+ * capabilities on accessories should consider doing this rather than
+ * using this abstraction.
+ *
+ * This function may only be called prior to registration of the jack.
+ */
+int snd_jack_set_key(struct snd_jack *jack, enum snd_jack_types type,
+		     int keytype)
+{
+	int key = fls(SND_JACK_BTN_0) - fls(type);
+
+	WARN_ON(jack->registered);
+
+	if (!keytype || key >= ARRAY_SIZE(jack->key))
+		return -EINVAL;
+
+	jack->type |= type;
+	jack->key[key] = keytype;
+
+	return 0;
+}
+EXPORT_SYMBOL(snd_jack_set_key);
+
+/**
+ * snd_jack_report - Report the current status of a jack
+ *
+ * @jack:   The jack to report status for
+ * @status: The current status of the jack
+ */
+void snd_jack_report(struct snd_jack *jack, int status)
+{
+	int i;
+
+	if (!jack)
+		return;
+
+	for (i = 0; i < ARRAY_SIZE(jack->key); i++) {
+		int testbit = SND_JACK_BTN_0 >> i;
+
+		if (jack->type & testbit)
+			input_report_key(jack->input_dev, jack->key[i],
+					 status & testbit);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) {
+		int testbit = 1 << i;
+		if (jack->type & testbit)
+			input_report_switch(jack->input_dev,
+					    jack_switch_types[i],
+					    status & testbit);
+	}
+
+	input_sync(jack->input_dev);
+}
+EXPORT_SYMBOL(snd_jack_report);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("Jack detection support for ALSA");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/core/memalloc.c b/linux/sound/core/memalloc.c
new file mode 100644
index 0000000..9e92441
--- /dev/null
+++ b/linux/sound/core/memalloc.c
@@ -0,0 +1,546 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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/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/seq_file.h>
+#include <asm/uaccess.h>
+#include <linux/dma-mapping.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <sound/memalloc.h>
+
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Memory allocator for ALSA system.");
+MODULE_LICENSE("GPL");
+
+
+/*
+ */
+
+static DEFINE_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
+
+/*
+ *
+ *  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;
+}
+
+/**
+ * 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, gfp_t gfp_flags)
+{
+	int pg;
+	void *res;
+
+	if (WARN_ON(!size))
+		return NULL;
+	if (WARN_ON(!gfp_flags))
+		return NULL;
+	gfp_flags |= __GFP_COMP;	/* compound page lets parts be mapped */
+	pg = get_order(size);
+	if ((res = (void *) __get_free_pages(gfp_flags, pg)) != NULL)
+		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);
+	free_pages((unsigned long) ptr, pg);
+}
+
+/*
+ *
+ *  Bus-specific memory allocators
+ *
+ */
+
+#ifdef CONFIG_HAS_DMA
+/* 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;
+	gfp_t gfp_flags;
+
+	if (WARN_ON(!dma))
+		return NULL;
+	pg = get_order(size);
+	gfp_flags = GFP_KERNEL
+		| __GFP_COMP	/* compound page lets parts be mapped */
+		| __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)
+		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);
+	dma_free_coherent(dev, PAGE_SIZE << pg, ptr, dma);
+}
+#endif /* CONFIG_HAS_DMA */
+
+/*
+ *
+ *  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)
+{
+	if (WARN_ON(!size))
+		return -ENXIO;
+	if (WARN_ON(!dmab))
+		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_HAS_DMA
+	case SNDRV_DMA_TYPE_DEV:
+		dmab->area = snd_malloc_dev_pages(device, size, &dmab->addr);
+		break;
+#endif
+#ifdef CONFIG_SND_DMA_SGBUF
+	case SNDRV_DMA_TYPE_DEV_SG:
+		snd_malloc_sgbuf_pages(device, size, dmab, NULL);
+		break;
+#endif
+	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;
+
+	while ((err = snd_dma_alloc_pages(type, device, size, dmab)) < 0) {
+		size_t aligned_size;
+		if (err != -ENOMEM)
+			return err;
+		if (size <= PAGE_SIZE)
+			return -ENOMEM;
+		aligned_size = PAGE_SIZE << get_order(size);
+		if (size != aligned_size)
+			size = aligned_size;
+		else
+			size >>= 1;
+	}
+	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_HAS_DMA
+	case SNDRV_DMA_TYPE_DEV:
+		snd_free_dev_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr);
+		break;
+#endif
+#ifdef CONFIG_SND_DMA_SGBUF
+	case SNDRV_DMA_TYPE_DEV_SG:
+		snd_free_sgbuf_pages(dmab);
+		break;
+#endif
+	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 snd_mem_list *mem;
+
+	if (WARN_ON(!dmab))
+		return 0;
+
+	mutex_lock(&list_mutex);
+	list_for_each_entry(mem, &mem_list_head, list) {
+		if (mem->id == id &&
+		    (mem->buffer.dev.dev == NULL || dmab->dev.dev == NULL ||
+		     ! memcmp(&mem->buffer.dev, &dmab->dev, sizeof(dmab->dev)))) {
+			struct device *dev = dmab->dev.dev;
+			list_del(&mem->list);
+			*dmab = mem->buffer;
+			if (dmab->dev.dev == NULL)
+				dmab->dev.dev = dev;
+			kfree(mem);
+			mutex_unlock(&list_mutex);
+			return dmab->bytes;
+		}
+	}
+	mutex_unlock(&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;
+
+	if (WARN_ON(!dmab))
+		return -EINVAL;
+	mem = kmalloc(sizeof(*mem), GFP_KERNEL);
+	if (! mem)
+		return -ENOMEM;
+	mutex_lock(&list_mutex);
+	mem->buffer = *dmab;
+	mem->id = id;
+	list_add_tail(&mem->list, &mem_list_head);
+	mutex_unlock(&list_mutex);
+	return 0;
+}
+
+/*
+ * purge all reserved buffers
+ */
+static void free_all_reserved_pages(void)
+{
+	struct list_head *p;
+	struct snd_mem_list *mem;
+
+	mutex_lock(&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);
+	}
+	mutex_unlock(&list_mutex);
+}
+
+
+#ifdef CONFIG_PROC_FS
+/*
+ * proc file interface
+ */
+#define SND_MEM_PROC_FILE	"driver/snd-page-alloc"
+static struct proc_dir_entry *snd_mem_proc;
+
+static int snd_mem_proc_read(struct seq_file *seq, void *offset)
+{
+	long pages = snd_allocated_pages >> (PAGE_SHIFT-12);
+	struct snd_mem_list *mem;
+	int devno;
+	static char *types[] = { "UNKNOWN", "CONT", "DEV", "DEV-SG" };
+
+	mutex_lock(&list_mutex);
+	seq_printf(seq, "pages  : %li bytes (%li pages per %likB)\n",
+		   pages * PAGE_SIZE, pages, PAGE_SIZE / 1024);
+	devno = 0;
+	list_for_each_entry(mem, &mem_list_head, list) {
+		devno++;
+		seq_printf(seq, "buffer %d : ID %08x : type %s\n",
+			   devno, mem->id, types[mem->buffer.dev.type]);
+		seq_printf(seq, "  addr = 0x%lx, size = %d bytes\n",
+			   (unsigned long)mem->buffer.addr,
+			   (int)mem->buffer.bytes);
+	}
+	mutex_unlock(&list_mutex);
+	return 0;
+}
+
+static int snd_mem_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, snd_mem_proc_read, NULL);
+}
+
+/* FIXME: for pci only - other bus? */
+#ifdef CONFIG_PCI
+#define gettoken(bufp) strsep(bufp, " \t\n")
+
+static ssize_t snd_mem_proc_write(struct file *file, const char __user * buffer,
+				  size_t count, loff_t * ppos)
+{
+	char buf[128];
+	char *token, *p;
+
+	if (count > sizeof(buf) - 1)
+		return -EINVAL;
+	if (copy_from_user(buf, buffer, count))
+		return -EFAULT;
+	buf[count] = '\0';
+
+	p = buf;
+	token = gettoken(&p);
+	if (! token || *token == '#')
+		return count;
+	if (strcmp(token, "add") == 0) {
+		char *endp;
+		int vendor, device, size, buffers;
+		long mask;
+		int i, alloced;
+		struct pci_dev *pci;
+
+		if ((token = gettoken(&p)) == NULL ||
+		    (vendor = simple_strtol(token, NULL, 0)) <= 0 ||
+		    (token = gettoken(&p)) == NULL ||
+		    (device = simple_strtol(token, NULL, 0)) <= 0 ||
+		    (token = gettoken(&p)) == NULL ||
+		    (mask = simple_strtol(token, NULL, 0)) < 0 ||
+		    (token = gettoken(&p)) == NULL ||
+		    (size = memparse(token, &endp)) < 64*1024 ||
+		    size > 16*1024*1024 /* too big */ ||
+		    (token = gettoken(&p)) == NULL ||
+		    (buffers = simple_strtol(token, NULL, 0)) <= 0 ||
+		    buffers > 4) {
+			printk(KERN_ERR "snd-page-alloc: invalid proc write format\n");
+			return count;
+		}
+		vendor &= 0xffff;
+		device &= 0xffff;
+
+		alloced = 0;
+		pci = NULL;
+		while ((pci = pci_get_device(vendor, device, pci)) != NULL) {
+			if (mask > 0 && mask < 0xffffffff) {
+				if (pci_set_dma_mask(pci, mask) < 0 ||
+				    pci_set_consistent_dma_mask(pci, mask) < 0) {
+					printk(KERN_ERR "snd-page-alloc: cannot set DMA mask %lx for pci %04x:%04x\n", mask, vendor, device);
+					pci_dev_put(pci);
+					return count;
+				}
+			}
+			for (i = 0; i < 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),
+							size, &dmab) < 0) {
+					printk(KERN_ERR "snd-page-alloc: cannot allocate buffer pages (size = %d)\n", size);
+					pci_dev_put(pci);
+					return count;
+				}
+				snd_dma_reserve_buf(&dmab, snd_dma_pci_buf_id(pci));
+			}
+			alloced++;
+		}
+		if (! alloced) {
+			for (i = 0; i < buffers; i++) {
+				struct snd_dma_buffer dmab;
+				memset(&dmab, 0, sizeof(dmab));
+				/* FIXME: We can allocate only in ZONE_DMA
+				 * without a device pointer!
+				 */
+				if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, NULL,
+							size, &dmab) < 0) {
+					printk(KERN_ERR "snd-page-alloc: cannot allocate buffer pages (size = %d)\n", size);
+					break;
+				}
+				snd_dma_reserve_buf(&dmab, (unsigned int)((vendor << 16) | device));
+			}
+		}
+	} else if (strcmp(token, "erase") == 0)
+		/* FIXME: need for releasing each buffer chunk? */
+		free_all_reserved_pages();
+	else
+		printk(KERN_ERR "snd-page-alloc: invalid proc cmd\n");
+	return count;
+}
+#endif /* CONFIG_PCI */
+
+static const struct file_operations snd_mem_proc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= snd_mem_proc_open,
+	.read		= seq_read,
+#ifdef CONFIG_PCI
+	.write		= snd_mem_proc_write,
+#endif
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+#endif /* CONFIG_PROC_FS */
+
+/*
+ * module entry
+ */
+
+static int __init snd_mem_init(void)
+{
+#ifdef CONFIG_PROC_FS
+	snd_mem_proc = proc_create(SND_MEM_PROC_FILE, 0644, NULL,
+				   &snd_mem_proc_fops);
+#endif
+	return 0;
+}
+
+static void __exit snd_mem_exit(void)
+{
+	remove_proc_entry(SND_MEM_PROC_FILE, 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/linux/sound/core/memory.c b/linux/sound/core/memory.c
new file mode 100644
index 0000000..1161158
--- /dev/null
+++ b/linux/sound/core/memory.c
@@ -0,0 +1,91 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ * 
+ *  Misc memory accessors
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <sound/core.h>
+
+/**
+ * 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 __force*)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
+}
+
+EXPORT_SYMBOL(copy_to_user_fromio);
+
+/**
+ * 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 __force *)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
+}
+
+EXPORT_SYMBOL(copy_from_user_toio);
diff --git a/linux/sound/core/misc.c b/linux/sound/core/misc.c
new file mode 100644
index 0000000..2c41825
--- /dev/null
+++ b/linux/sound/core/misc.c
@@ -0,0 +1,152 @@
+/*
+ *  Misc and compatibility things
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+
+#ifdef CONFIG_SND_DEBUG
+
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+#define DEFAULT_DEBUG_LEVEL	2
+#else
+#define DEFAULT_DEBUG_LEVEL	1
+#endif
+
+static int debug = DEFAULT_DEBUG_LEVEL;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Debug level (0 = disable)");
+
+#endif /* CONFIG_SND_DEBUG */
+
+void release_and_free_resource(struct resource *res)
+{
+	if (res) {
+		release_resource(res);
+		kfree(res);
+	}
+}
+
+EXPORT_SYMBOL(release_and_free_resource);
+
+#ifdef CONFIG_SND_VERBOSE_PRINTK
+/* strip the leading path if the given path is absolute */
+static const char *sanity_file_name(const char *path)
+{
+	if (*path == '/')
+		return strrchr(path, '/') + 1;
+	else
+		return path;
+}
+
+/* print file and line with a certain printk prefix */
+static int print_snd_pfx(unsigned int level, const char *path, int line,
+			 const char *format)
+{
+	const char *file = sanity_file_name(path);
+	char tmp[] = "<0>";
+	const char *pfx = level ? KERN_DEBUG : KERN_DEFAULT;
+	int ret = 0;
+
+	if (format[0] == '<' && format[2] == '>') {
+		tmp[1] = format[1];
+		pfx = tmp;
+		ret = 1;
+	}
+	printk("%sALSA %s:%d: ", pfx, file, line);
+	return ret;
+}
+#else
+#define print_snd_pfx(level, path, line, format)	0
+#endif
+
+#if defined(CONFIG_SND_DEBUG) || defined(CONFIG_SND_VERBOSE_PRINTK)
+void __snd_printk(unsigned int level, const char *path, int line,
+		  const char *format, ...)
+{
+	va_list args;
+	
+#ifdef CONFIG_SND_DEBUG	
+	if (debug < level)
+		return;
+#endif
+	va_start(args, format);
+	if (print_snd_pfx(level, path, line, format))
+		format += 3; /* skip the printk level-prefix */
+	vprintk(format, args);
+	va_end(args);
+}
+EXPORT_SYMBOL_GPL(__snd_printk);
+#endif
+
+#ifdef CONFIG_PCI
+#include <linux/pci.h>
+/**
+ * snd_pci_quirk_lookup_id - look up a PCI SSID quirk list
+ * @vendor: PCI SSV id
+ * @device: PCI SSD id
+ * @list: quirk list, terminated by a null entry
+ *
+ * Look through the given quirk list and finds a matching entry
+ * with the same PCI SSID.  When subdevice is 0, all subdevice
+ * values may match.
+ *
+ * Returns the matched entry pointer, or NULL if nothing matched.
+ */
+const struct snd_pci_quirk *
+snd_pci_quirk_lookup_id(u16 vendor, u16 device,
+			const struct snd_pci_quirk *list)
+{
+	const struct snd_pci_quirk *q;
+
+	for (q = list; q->subvendor; q++) {
+		if (q->subvendor != vendor)
+			continue;
+		if (!q->subdevice ||
+		    (device & q->subdevice_mask) == q->subdevice)
+			return q;
+	}
+	return NULL;
+}
+EXPORT_SYMBOL(snd_pci_quirk_lookup_id);
+
+/**
+ * snd_pci_quirk_lookup - look up a PCI SSID quirk list
+ * @pci: pci_dev handle
+ * @list: quirk list, terminated by a null entry
+ *
+ * Look through the given quirk list and finds a matching entry
+ * with the same PCI SSID.  When subdevice is 0, all subdevice
+ * values may match.
+ *
+ * Returns the matched entry pointer, or NULL if nothing matched.
+ */
+const struct snd_pci_quirk *
+snd_pci_quirk_lookup(struct pci_dev *pci, const struct snd_pci_quirk *list)
+{
+	return snd_pci_quirk_lookup_id(pci->subsystem_vendor,
+				       pci->subsystem_device,
+				       list);
+}
+EXPORT_SYMBOL(snd_pci_quirk_lookup);
+#endif
diff --git a/linux/sound/core/oss/Makefile b/linux/sound/core/oss/Makefile
new file mode 100644
index 0000000..10a7945
--- /dev/null
+++ b/linux/sound/core/oss/Makefile
@@ -0,0 +1,13 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-mixer-oss-objs := mixer_oss.o
+
+snd-pcm-oss-y := pcm_oss.o
+snd-pcm-oss-$(CONFIG_SND_PCM_OSS_PLUGINS) += 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/linux/sound/core/oss/copy.c b/linux/sound/core/oss/copy.c
new file mode 100644
index 0000000..05b58d4
--- /dev/null
+++ b/linux/sound/core/oss/copy.c
@@ -0,0 +1,92 @@
+/*
+ *  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 <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+static snd_pcm_sframes_t copy_transfer(struct snd_pcm_plugin *plugin,
+			     const struct snd_pcm_plugin_channel *src_channels,
+			     struct snd_pcm_plugin_channel *dst_channels,
+			     snd_pcm_uframes_t frames)
+{
+	unsigned int channel;
+	unsigned int nchannels;
+
+	if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
+		return -ENXIO;
+	if (frames == 0)
+		return 0;
+	nchannels = plugin->src_format.channels;
+	for (channel = 0; channel < nchannels; channel++) {
+		if (snd_BUG_ON(src_channels->area.first % 8 ||
+			       src_channels->area.step % 8))
+			return -ENXIO;
+		if (snd_BUG_ON(dst_channels->area.first % 8 ||
+			       dst_channels->area.step % 8))
+			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(struct snd_pcm_substream *plug,
+			      struct snd_pcm_plugin_format *src_format,
+			      struct snd_pcm_plugin_format *dst_format,
+			      struct snd_pcm_plugin **r_plugin)
+{
+	int err;
+	struct snd_pcm_plugin *plugin;
+	int width;
+
+	if (snd_BUG_ON(!r_plugin))
+		return -ENXIO;
+	*r_plugin = NULL;
+
+	if (snd_BUG_ON(src_format->format != dst_format->format))
+		return -ENXIO;
+	if (snd_BUG_ON(src_format->rate != dst_format->rate))
+		return -ENXIO;
+	if (snd_BUG_ON(src_format->channels != dst_format->channels))
+		return -ENXIO;
+
+	width = snd_pcm_format_physical_width(src_format->format);
+	if (snd_BUG_ON(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/linux/sound/core/oss/io.c b/linux/sound/core/oss/io.c
new file mode 100644
index 0000000..6faa1d7
--- /dev/null
+++ b/linux/sound/core/oss/io.c
@@ -0,0 +1,141 @@
+/*
+ *  PCM I/O Plug-In Interface
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@perex.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 <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(struct snd_pcm_plugin *plugin,
+				    const struct snd_pcm_plugin_channel *src_channels,
+				    struct snd_pcm_plugin_channel *dst_channels,
+				    snd_pcm_uframes_t frames)
+{
+	if (snd_BUG_ON(!plugin))
+		return -ENXIO;
+	if (snd_BUG_ON(!src_channels))
+		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;
+		if (snd_BUG_ON(!bufs))
+			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(struct snd_pcm_plugin *plugin,
+				   const struct snd_pcm_plugin_channel *src_channels,
+				   struct snd_pcm_plugin_channel *dst_channels,
+				   snd_pcm_uframes_t frames)
+{
+	if (snd_BUG_ON(!plugin))
+		return -ENXIO;
+	if (snd_BUG_ON(!dst_channels))
+		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;
+		if (snd_BUG_ON(!bufs))
+			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(struct snd_pcm_plugin *plugin,
+			     snd_pcm_uframes_t frames,
+			     struct snd_pcm_plugin_channel **channels)
+{
+	int err;
+	unsigned int channel;
+	struct snd_pcm_plugin_channel *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(struct snd_pcm_substream *plug,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_pcm_plugin **r_plugin)
+{
+	int err;
+	struct snd_pcm_plugin_format format;
+	struct snd_pcm_plugin *plugin;
+
+	if (snd_BUG_ON(!r_plugin))
+		return -ENXIO;
+	*r_plugin = NULL;
+	if (snd_BUG_ON(!plug || !params))
+		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/linux/sound/core/oss/linear.c b/linux/sound/core/oss/linear.c
new file mode 100644
index 0000000..4c1d168
--- /dev/null
+++ b/linux/sound/core/oss/linear.c
@@ -0,0 +1,180 @@
+/*
+ *  Linear conversion Plug-In
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@perex.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 <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+/*
+ *  Basic linear conversion plugin
+ */
+ 
+struct linear_priv {
+	int cvt_endian;		/* need endian conversion? */
+	unsigned int src_ofs;	/* byte offset in source format */
+	unsigned int dst_ofs;	/* byte soffset in destination format */
+	unsigned int copy_ofs;	/* byte offset in temporary u32 data */
+	unsigned int dst_bytes;		/* byte size of destination format */
+	unsigned int copy_bytes;	/* bytes to copy per conversion */
+	unsigned int flip; /* MSB flip for signeness, done after endian conv */
+};
+
+static inline void do_convert(struct linear_priv *data,
+			      unsigned char *dst, unsigned char *src)
+{
+	unsigned int tmp = 0;
+	unsigned char *p = (unsigned char *)&tmp;
+
+	memcpy(p + data->copy_ofs, src + data->src_ofs, data->copy_bytes);
+	if (data->cvt_endian)
+		tmp = swab32(tmp);
+	tmp ^= data->flip;
+	memcpy(dst, p + data->dst_ofs, data->dst_bytes);
+}
+
+static void convert(struct snd_pcm_plugin *plugin,
+		    const struct snd_pcm_plugin_channel *src_channels,
+		    struct snd_pcm_plugin_channel *dst_channels,
+		    snd_pcm_uframes_t frames)
+{
+	struct linear_priv *data = (struct linear_priv *)plugin->extra_data;
+	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) {
+			do_convert(data, dst, src);
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+static snd_pcm_sframes_t linear_transfer(struct snd_pcm_plugin *plugin,
+			       const struct snd_pcm_plugin_channel *src_channels,
+			       struct snd_pcm_plugin_channel *dst_channels,
+			       snd_pcm_uframes_t frames)
+{
+	struct linear_priv *data;
+
+	if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
+		return -ENXIO;
+	data = (struct linear_priv *)plugin->extra_data;
+	if (frames == 0)
+		return 0;
+#ifdef CONFIG_SND_DEBUG
+	{
+		unsigned int channel;
+		for (channel = 0; channel < plugin->src_format.channels; channel++) {
+			if (snd_BUG_ON(src_channels[channel].area.first % 8 ||
+				       src_channels[channel].area.step % 8))
+				return -ENXIO;
+			if (snd_BUG_ON(dst_channels[channel].area.first % 8 ||
+				       dst_channels[channel].area.step % 8))
+				return -ENXIO;
+		}
+	}
+#endif
+	convert(plugin, src_channels, dst_channels, frames);
+	return frames;
+}
+
+static void init_data(struct linear_priv *data, int src_format, int dst_format)
+{
+	int src_le, dst_le, src_bytes, dst_bytes;
+
+	src_bytes = snd_pcm_format_width(src_format) / 8;
+	dst_bytes = snd_pcm_format_width(dst_format) / 8;
+	src_le = snd_pcm_format_little_endian(src_format) > 0;
+	dst_le = snd_pcm_format_little_endian(dst_format) > 0;
+
+	data->dst_bytes = dst_bytes;
+	data->cvt_endian = src_le != dst_le;
+	data->copy_bytes = src_bytes < dst_bytes ? src_bytes : dst_bytes;
+	if (src_le) {
+		data->copy_ofs = 4 - data->copy_bytes;
+		data->src_ofs = src_bytes - data->copy_bytes;
+	} else
+		data->src_ofs = snd_pcm_format_physical_width(src_format) / 8 -
+			src_bytes;
+	if (dst_le)
+		data->dst_ofs = 4 - data->dst_bytes;
+	else
+		data->dst_ofs = snd_pcm_format_physical_width(dst_format) / 8 -
+			dst_bytes;
+	if (snd_pcm_format_signed(src_format) !=
+	    snd_pcm_format_signed(dst_format)) {
+		if (dst_le)
+			data->flip = cpu_to_le32(0x80000000);
+		else
+			data->flip = cpu_to_be32(0x80000000);
+	}
+}
+
+int snd_pcm_plugin_build_linear(struct snd_pcm_substream *plug,
+				struct snd_pcm_plugin_format *src_format,
+				struct snd_pcm_plugin_format *dst_format,
+				struct snd_pcm_plugin **r_plugin)
+{
+	int err;
+	struct linear_priv *data;
+	struct snd_pcm_plugin *plugin;
+
+	if (snd_BUG_ON(!r_plugin))
+		return -ENXIO;
+	*r_plugin = NULL;
+
+	if (snd_BUG_ON(src_format->rate != dst_format->rate))
+		return -ENXIO;
+	if (snd_BUG_ON(src_format->channels != dst_format->channels))
+		return -ENXIO;
+	if (snd_BUG_ON(!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(struct linear_priv), &plugin);
+	if (err < 0)
+		return err;
+	data = (struct linear_priv *)plugin->extra_data;
+	init_data(data, src_format->format, dst_format->format);
+	plugin->transfer = linear_transfer;
+	*r_plugin = plugin;
+	return 0;
+}
diff --git a/linux/sound/core/oss/mixer_oss.c b/linux/sound/core/oss/mixer_oss.c
new file mode 100644
index 0000000..822dd56
--- /dev/null
+++ b/linux/sound/core/oss/mixer_oss.c
@@ -0,0 +1,1412 @@
+/*
+ *  OSS emulation layer for the mixer interface
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/string.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@perex.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)
+{
+	struct snd_card *card;
+	struct snd_mixer_oss_file *fmixer;
+	int err;
+
+	err = nonseekable_open(inode, file);
+	if (err < 0)
+		return err;
+
+	card = snd_lookup_oss_minor_data(iminor(inode),
+					 SNDRV_OSS_DEVICE_TYPE_MIXER);
+	if (card == NULL)
+		return -ENODEV;
+	if (card->mixer_oss == NULL)
+		return -ENODEV;
+	err = snd_card_file_add(card, file);
+	if (err < 0)
+		return err;
+	fmixer = kzalloc(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)
+{
+	struct snd_mixer_oss_file *fmixer;
+
+	if (file->private_data) {
+		fmixer = 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(struct snd_mixer_oss_file *fmixer,
+			      mixer_info __user *_info)
+{
+	struct snd_card *card = fmixer->card;
+	struct snd_mixer_oss *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(struct snd_mixer_oss_file *fmixer,
+				       _old_mixer_info __user *_info)
+{
+	struct snd_card *card = fmixer->card;
+	struct snd_mixer_oss *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(struct snd_mixer_oss_file *fmixer)
+{
+	struct snd_mixer_oss *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(struct snd_mixer_oss_file *fmixer)
+{
+	struct snd_mixer_oss *mixer = fmixer->mixer;
+	struct snd_mixer_oss_slot *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(struct snd_mixer_oss_file *fmixer)
+{
+	struct snd_mixer_oss *mixer = fmixer->mixer;
+	struct snd_mixer_oss_slot *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(struct snd_mixer_oss_file *fmixer)
+{
+	struct snd_mixer_oss *mixer = fmixer->mixer;
+	int result = 0;
+
+	if (mixer == NULL)
+		return -EIO;
+	if (mixer->put_recsrc && mixer->get_recsrc) {	/* exclusive */
+		result = mixer->mask_recsrc;
+	} else {
+		struct snd_mixer_oss_slot *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(struct snd_mixer_oss_file *fmixer)
+{
+	struct snd_mixer_oss *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 {
+		struct snd_mixer_oss_slot *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(struct snd_mixer_oss_file *fmixer, int recsrc)
+{
+	struct snd_mixer_oss *mixer = fmixer->mixer;
+	struct snd_mixer_oss_slot *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(struct snd_mixer_oss_file *fmixer, int slot)
+{
+	struct snd_mixer_oss *mixer = fmixer->mixer;
+	struct snd_mixer_oss_slot *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;
+	if (snd_BUG_ON(left < 0 || left > 100))
+		return -EIO;
+	if (snd_BUG_ON(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(struct snd_mixer_oss_file *fmixer,
+				    int slot, int volume)
+{
+	struct snd_mixer_oss *mixer = fmixer->mixer;
+	struct snd_mixer_oss_slot *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(struct snd_mixer_oss_file *fmixer, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	int tmp;
+
+	if (snd_BUG_ON(!fmixer))
+		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(file->private_data, cmd, arg);
+}
+
+int snd_mixer_oss_ioctl_card(struct snd_card *card, unsigned int cmd, unsigned long arg)
+{
+	struct snd_mixer_oss_file fmixer;
+	
+	if (snd_BUG_ON(!card))
+		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 const struct file_operations snd_mixer_oss_f_ops =
+{
+	.owner =	THIS_MODULE,
+	.open =		snd_mixer_oss_open,
+	.release =	snd_mixer_oss_release,
+	.llseek =	no_llseek,
+	.unlocked_ioctl =	snd_mixer_oss_ioctl,
+	.compat_ioctl =	snd_mixer_oss_ioctl_compat,
+};
+
+/*
+ *  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(struct snd_card *card, int slot)
+{
+	struct snd_mixer_oss *mixer = card->mixer_oss;
+	if (mixer)
+		mixer->mask_recsrc |= 1 << slot;
+}
+
+static int snd_mixer_oss_recsrce_get(struct snd_card *card, int slot)
+{
+	struct snd_mixer_oss *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 struct snd_kcontrol *snd_mixer_oss_test_id(struct snd_mixer_oss *mixer, const char *name, int index)
+{
+	struct snd_card *card = mixer->card;
+	struct snd_ctl_elem_id 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(struct snd_mixer_oss_file *fmixer,
+					  struct snd_mixer_oss_slot *pslot,
+					  unsigned int numid,
+					  int *left, int *right)
+{
+	struct snd_ctl_elem_info *uinfo;
+	struct snd_ctl_elem_value *uctl;
+	struct snd_kcontrol *kctl;
+	struct snd_card *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 = kzalloc(sizeof(*uinfo), GFP_KERNEL);
+	uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL)
+		goto __unalloc;
+	if (kctl->info(kctl, uinfo))
+		goto __unalloc;
+	if (kctl->get(kctl, uctl))
+		goto __unalloc;
+	if (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(struct snd_mixer_oss_file *fmixer,
+					 struct snd_mixer_oss_slot *pslot,
+					 unsigned int numid,
+					 int *left, int *right,
+					 int route)
+{
+	struct snd_ctl_elem_info *uinfo;
+	struct snd_ctl_elem_value *uctl;
+	struct snd_kcontrol *kctl;
+	struct snd_card *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 = kzalloc(sizeof(*uinfo), GFP_KERNEL);
+	uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL)
+		goto __unalloc;
+	if (kctl->info(kctl, uinfo))
+		goto __unalloc;
+	if (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(struct snd_mixer_oss_file *fmixer,
+				     struct snd_mixer_oss_slot *pslot,
+				     int *left, int *right)
+{
+	struct slot *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(struct snd_mixer_oss_file *fmixer,
+					  struct snd_mixer_oss_slot *pslot,
+					  unsigned int numid,
+					  int left, int right)
+{
+	struct snd_ctl_elem_info *uinfo;
+	struct snd_ctl_elem_value *uctl;
+	struct snd_kcontrol *kctl;
+	struct snd_card *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(&card->controls_rwsem);
+		return;
+	}
+	uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL);
+	uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL)
+		goto __unalloc;
+	if (kctl->info(kctl, uinfo))
+		goto __unalloc;
+	if (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);
+	if ((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(struct snd_mixer_oss_file *fmixer,
+					 struct snd_mixer_oss_slot *pslot,
+					 unsigned int numid,
+					 int left, int right,
+					 int route)
+{
+	struct snd_ctl_elem_info *uinfo;
+	struct snd_ctl_elem_value *uctl;
+	struct snd_kcontrol *kctl;
+	struct snd_card *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(&card->controls_rwsem);
+		return;
+	}
+	uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL);
+	uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL)
+		goto __unalloc;
+	if (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;
+	}
+	if ((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(struct snd_mixer_oss_file *fmixer,
+				     struct snd_mixer_oss_slot *pslot,
+				     int left, int right)
+{
+	struct slot *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_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_CSWITCH)
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], 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_CROUTE)
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], 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_CSWITCH) {
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], 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_CROUTE) {
+			snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], 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(struct snd_mixer_oss_file *fmixer,
+					struct snd_mixer_oss_slot *pslot,
+					int *active)
+{
+	struct slot *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(struct snd_mixer_oss_file *fmixer,
+					   struct snd_mixer_oss_slot *pslot,
+					   int *active)
+{
+	struct slot *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(struct snd_mixer_oss_file *fmixer,
+					struct snd_mixer_oss_slot *pslot,
+					int active)
+{
+	struct slot *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(struct snd_mixer_oss_file *fmixer,
+					   struct snd_mixer_oss_slot *pslot,
+					   int active)
+{
+	struct slot *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(struct snd_mixer_oss_file *fmixer, unsigned int *active_index)
+{
+	struct snd_card *card = fmixer->card;
+	struct snd_mixer_oss *mixer = fmixer->mixer;
+	struct snd_kcontrol *kctl;
+	struct snd_mixer_oss_slot *pslot;
+	struct slot *slot;
+	struct snd_ctl_elem_info *uinfo;
+	struct snd_ctl_elem_value *uctl;
+	int err, idx;
+	
+	uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL);
+	uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL) {
+		err = -ENOMEM;
+		goto __free_only;
+	}
+	down_read(&card->controls_rwsem);
+	kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0);
+	if (! kctl) {
+		err = -ENOENT;
+		goto __unlock;
+	}
+	if ((err = kctl->info(kctl, uinfo)) < 0)
+		goto __unlock;
+	if ((err = kctl->get(kctl, uctl)) < 0)
+		goto __unlock;
+	for (idx = 0; idx < 32; idx++) {
+		if (!(mixer->mask_recsrc & (1 << idx)))
+			continue;
+		pslot = &mixer->slots[idx];
+		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);
+      __free_only:
+      	kfree(uctl);
+      	kfree(uinfo);
+      	return err;
+}
+
+static int snd_mixer_oss_put_recsrc2(struct snd_mixer_oss_file *fmixer, unsigned int active_index)
+{
+	struct snd_card *card = fmixer->card;
+	struct snd_mixer_oss *mixer = fmixer->mixer;
+	struct snd_kcontrol *kctl;
+	struct snd_mixer_oss_slot *pslot;
+	struct slot *slot = NULL;
+	struct snd_ctl_elem_info *uinfo;
+	struct snd_ctl_elem_value *uctl;
+	int err;
+	unsigned int idx;
+
+	uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL);
+	uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
+	if (uinfo == NULL || uctl == NULL) {
+		err = -ENOMEM;
+		goto __free_only;
+	}
+	down_read(&card->controls_rwsem);
+	kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0);
+	if (! kctl) {
+		err = -ENOENT;
+		goto __unlock;
+	}
+	if ((err = kctl->info(kctl, uinfo)) < 0)
+		goto __unlock;
+	for (idx = 0; idx < 32; idx++) {
+		if (!(mixer->mask_recsrc & (1 << idx)))
+			continue;
+		pslot = &mixer->slots[idx];
+		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;
+	}
+	if (! slot)
+		goto __unlock;
+	for (idx = 0; idx < uinfo->count; idx++)
+		uctl->value.enumerated.item[idx] = slot->capture_item;
+	err = kctl->put(kctl, uctl);
+	if (err > 0)
+		snd_ctl_notify(fmixer->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
+	err = 0;
+      __unlock:
+	up_read(&card->controls_rwsem);
+      __free_only:
+	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(struct snd_mixer_oss *mixer, struct slot *slot, const char *name, int index, int item)
+{
+	struct snd_ctl_elem_info *info;
+	struct snd_kcontrol *kcontrol;
+	struct snd_card *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(struct snd_mixer_oss_slot *chn)
+{
+	struct slot *p = chn->private_data;
+	if (p) {
+		if (p->allocated && p->assigned) {
+			kfree(p->assigned->name);
+			kfree(p->assigned);
+		}
+		kfree(p);
+	}
+}
+
+static void mixer_slot_clear(struct snd_mixer_oss_slot *rslot)
+{
+	int idx = rslot->number; /* remember this */
+	if (rslot->private_free)
+		rslot->private_free(rslot);
+	memset(rslot, 0, sizeof(*rslot));
+	rslot->number = idx;
+}
+
+/* In a separate function to keep gcc 3.2 happy - do NOT merge this in
+   snd_mixer_oss_build_input! */
+static int snd_mixer_oss_build_test_all(struct snd_mixer_oss *mixer,
+					struct snd_mixer_oss_assign_table *ptr,
+					struct slot *slot)
+{
+	char str[64];
+	int err;
+
+	err = snd_mixer_oss_build_test(mixer, slot, ptr->name, ptr->index,
+				       SNDRV_MIXER_OSS_ITEM_GLOBAL);
+	if (err)
+		return err;
+	sprintf(str, "%s Switch", ptr->name);
+	err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index,
+				       SNDRV_MIXER_OSS_ITEM_GSWITCH);
+	if (err)
+		return err;
+	sprintf(str, "%s Route", ptr->name);
+	err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index,
+				       SNDRV_MIXER_OSS_ITEM_GROUTE);
+	if (err)
+		return err;
+	sprintf(str, "%s Volume", ptr->name);
+	err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index,
+				       SNDRV_MIXER_OSS_ITEM_GVOLUME);
+	if (err)
+		return err;
+	sprintf(str, "%s Playback Switch", ptr->name);
+	err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index,
+				       SNDRV_MIXER_OSS_ITEM_PSWITCH);
+	if (err)
+		return err;
+	sprintf(str, "%s Playback Route", ptr->name);
+	err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index,
+				       SNDRV_MIXER_OSS_ITEM_PROUTE);
+	if (err)
+		return err;
+	sprintf(str, "%s Playback Volume", ptr->name);
+	err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index,
+				       SNDRV_MIXER_OSS_ITEM_PVOLUME);
+	if (err)
+		return err;
+	sprintf(str, "%s Capture Switch", ptr->name);
+	err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index,
+				       SNDRV_MIXER_OSS_ITEM_CSWITCH);
+	if (err)
+		return err;
+	sprintf(str, "%s Capture Route", ptr->name);
+	err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index,
+				       SNDRV_MIXER_OSS_ITEM_CROUTE);
+	if (err)
+		return err;
+	sprintf(str, "%s Capture Volume", ptr->name);
+	err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index,
+				       SNDRV_MIXER_OSS_ITEM_CVOLUME);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+/*
+ * 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(struct snd_mixer_oss *mixer, struct snd_mixer_oss_assign_table *ptr, int ptr_allocated, int replace_old)
+{
+	struct slot slot;
+	struct slot *pslot;
+	struct snd_kcontrol *kctl;
+	struct snd_mixer_oss_slot *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_all(mixer, ptr, &slot))
+		return 0;
+	down_read(&mixer->card->controls_rwsem);
+	if (ptr->index == 0 && (kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0)) != NULL) {
+		struct snd_ctl_elem_info *uinfo;
+
+		uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL);
+		if (! uinfo) {
+			up_read(&mixer->card->controls_rwsem);
+			return -ENOMEM;
+		}
+			
+		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 = kmalloc(sizeof(slot), GFP_KERNEL);
+		if (! pslot)
+			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;
+}
+
+#ifdef CONFIG_PROC_FS
+/*
+ */
+#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(struct snd_info_entry *entry,
+				    struct snd_info_buffer *buffer)
+{
+	struct snd_mixer_oss *mixer = entry->private_data;
+	int i;
+
+	mutex_lock(&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");
+	}
+	mutex_unlock(&mixer->reg_mutex);
+}
+
+static void snd_mixer_oss_proc_write(struct snd_info_entry *entry,
+				     struct snd_info_buffer *buffer)
+{
+	struct snd_mixer_oss *mixer = entry->private_data;
+	char line[128], str[32], idxstr[16];
+	const char *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 */
+			mutex_lock(&mixer->reg_mutex);
+			mixer_slot_clear(&mixer->slots[ch]);
+			mutex_unlock(&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;
+		}
+		mutex_lock(&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 = kstrdup(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:
+		mutex_unlock(&mixer->reg_mutex);
+	}
+}
+
+static void snd_mixer_oss_proc_init(struct snd_mixer_oss *mixer)
+{
+	struct snd_info_entry *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 = snd_mixer_oss_proc_read;
+	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(struct snd_mixer_oss *mixer)
+{
+	snd_info_free_entry(mixer->proc_entry);
+	mixer->proc_entry = NULL;
+}
+#else /* !CONFIG_PROC_FS */
+#define snd_mixer_oss_proc_init(mix)
+#define snd_mixer_oss_proc_done(mix)
+#endif /* CONFIG_PROC_FS */
+
+static void snd_mixer_oss_build(struct snd_mixer_oss *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,	"Beep", 		0 },
+		{ SOUND_MIXER_SPEAKER,	"PC Speaker", 		0 }, /* fallback */
+		{ SOUND_MIXER_SPEAKER,	"Speaker", 		0 }, /* fallback */
+		{ 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,	"Speaker",		0 }, /*fallback*/
+		{ SOUND_MIXER_PHONEOUT,	"Mono",			0 }, /*fallback*/
+		{ 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)
+{
+	struct snd_mixer_oss *mixer = private;
+	struct snd_card *card;
+	int idx;
+ 
+	if (!mixer)
+		return 0;
+	card = mixer->card;
+	if (snd_BUG_ON(mixer != card->mixer_oss))
+		return -ENXIO;
+	card->mixer_oss = NULL;
+	for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++) {
+		struct snd_mixer_oss_slot *chn = &mixer->slots[idx];
+		if (chn->private_free)
+			chn->private_free(chn);
+	}
+	kfree(mixer);
+	return 0;
+}
+
+static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd)
+{
+	struct snd_mixer_oss *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;
+		mutex_init(&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_f_ops, card,
+						   name)) < 0) {
+			snd_printk(KERN_ERR "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 {
+		mixer = card->mixer_oss;
+		if (mixer == NULL)
+			return 0;
+		if (mixer->oss_dev_alloc) {
+#ifdef SNDRV_OSS_INFO_DEV_MIXERS
+			snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIXERS, mixer->card->number);
+#endif
+			snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, mixer->card, 0);
+			mixer->oss_dev_alloc = 0;
+		}
+		if (cmd == SND_MIXER_OSS_NOTIFY_DISCONNECT)
+			return 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/linux/sound/core/oss/mulaw.c b/linux/sound/core/oss/mulaw.c
new file mode 100644
index 0000000..f7649d4
--- /dev/null
+++ b/linux/sound/core/oss/mulaw.c
@@ -0,0 +1,344 @@
+/*
+ *  Mu-Law conversion Plug-In Interface
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@perex.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 <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)(struct snd_pcm_plugin *plugin,
+			const struct snd_pcm_plugin_channel *src_channels,
+			struct snd_pcm_plugin_channel *dst_channels,
+			snd_pcm_uframes_t frames);
+
+struct mulaw_priv {
+	mulaw_f func;
+	int cvt_endian;			/* need endian conversion? */
+	unsigned int native_ofs;	/* byte offset in native format */
+	unsigned int copy_ofs;		/* byte offset in s16 format */
+	unsigned int native_bytes;	/* byte size of the native format */
+	unsigned int copy_bytes;	/* bytes to copy per conversion */
+	u16 flip; /* MSB flip for signedness, done after endian conversion */
+};
+
+static inline void cvt_s16_to_native(struct mulaw_priv *data,
+				     unsigned char *dst, u16 sample)
+{
+	sample ^= data->flip;
+	if (data->cvt_endian)
+		sample = swab16(sample);
+	if (data->native_bytes > data->copy_bytes)
+		memset(dst, 0, data->native_bytes);
+	memcpy(dst + data->native_ofs, (char *)&sample + data->copy_ofs,
+	       data->copy_bytes);
+}
+
+static void mulaw_decode(struct snd_pcm_plugin *plugin,
+			const struct snd_pcm_plugin_channel *src_channels,
+			struct snd_pcm_plugin_channel *dst_channels,
+			snd_pcm_uframes_t frames)
+{
+	struct mulaw_priv *data = (struct mulaw_priv *)plugin->extra_data;
+	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);
+			cvt_s16_to_native(data, dst, sample);
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+static inline signed short cvt_native_to_s16(struct mulaw_priv *data,
+					     unsigned char *src)
+{
+	u16 sample = 0;
+	memcpy((char *)&sample + data->copy_ofs, src + data->native_ofs,
+	       data->copy_bytes);
+	if (data->cvt_endian)
+		sample = swab16(sample);
+	sample ^= data->flip;
+	return (signed short)sample;
+}
+
+static void mulaw_encode(struct snd_pcm_plugin *plugin,
+			const struct snd_pcm_plugin_channel *src_channels,
+			struct snd_pcm_plugin_channel *dst_channels,
+			snd_pcm_uframes_t frames)
+{
+	struct mulaw_priv *data = (struct mulaw_priv *)plugin->extra_data;
+	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 = cvt_native_to_s16(data, src);
+			*dst = linear2ulaw(sample);
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+static snd_pcm_sframes_t mulaw_transfer(struct snd_pcm_plugin *plugin,
+			      const struct snd_pcm_plugin_channel *src_channels,
+			      struct snd_pcm_plugin_channel *dst_channels,
+			      snd_pcm_uframes_t frames)
+{
+	struct mulaw_priv *data;
+
+	if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
+		return -ENXIO;
+	if (frames == 0)
+		return 0;
+#ifdef CONFIG_SND_DEBUG
+	{
+		unsigned int channel;
+		for (channel = 0; channel < plugin->src_format.channels; channel++) {
+			if (snd_BUG_ON(src_channels[channel].area.first % 8 ||
+				       src_channels[channel].area.step % 8))
+				return -ENXIO;
+			if (snd_BUG_ON(dst_channels[channel].area.first % 8 ||
+				       dst_channels[channel].area.step % 8))
+				return -ENXIO;
+		}
+	}
+#endif
+	data = (struct mulaw_priv *)plugin->extra_data;
+	data->func(plugin, src_channels, dst_channels, frames);
+	return frames;
+}
+
+static void init_data(struct mulaw_priv *data, int format)
+{
+#ifdef SNDRV_LITTLE_ENDIAN
+	data->cvt_endian = snd_pcm_format_big_endian(format) > 0;
+#else
+	data->cvt_endian = snd_pcm_format_little_endian(format) > 0;
+#endif
+	if (!snd_pcm_format_signed(format))
+		data->flip = 0x8000;
+	data->native_bytes = snd_pcm_format_physical_width(format) / 8;
+	data->copy_bytes = data->native_bytes < 2 ? 1 : 2;
+	if (snd_pcm_format_little_endian(format)) {
+		data->native_ofs = data->native_bytes - data->copy_bytes;
+		data->copy_ofs = 2 - data->copy_bytes;
+	} else {
+		/* S24 in 4bytes need an 1 byte offset */
+		data->native_ofs = data->native_bytes -
+			snd_pcm_format_width(format) / 8;
+	}
+}
+
+int snd_pcm_plugin_build_mulaw(struct snd_pcm_substream *plug,
+			       struct snd_pcm_plugin_format *src_format,
+			       struct snd_pcm_plugin_format *dst_format,
+			       struct snd_pcm_plugin **r_plugin)
+{
+	int err;
+	struct mulaw_priv *data;
+	struct snd_pcm_plugin *plugin;
+	struct snd_pcm_plugin_format *format;
+	mulaw_f func;
+
+	if (snd_BUG_ON(!r_plugin))
+		return -ENXIO;
+	*r_plugin = NULL;
+
+	if (snd_BUG_ON(src_format->rate != dst_format->rate))
+		return -ENXIO;
+	if (snd_BUG_ON(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;
+	}
+	if (snd_BUG_ON(!snd_pcm_format_linear(format->format)))
+		return -ENXIO;
+
+	err = snd_pcm_plugin_build(plug, "Mu-Law<->linear conversion",
+				   src_format, dst_format,
+				   sizeof(struct mulaw_priv), &plugin);
+	if (err < 0)
+		return err;
+	data = (struct mulaw_priv *)plugin->extra_data;
+	data->func = func;
+	init_data(data, format->format);
+	plugin->transfer = mulaw_transfer;
+	*r_plugin = plugin;
+	return 0;
+}
diff --git a/linux/sound/core/oss/pcm_oss.c b/linux/sound/core/oss/pcm_oss.c
new file mode 100644
index 0000000..b753ec6
--- /dev/null
+++ b/linux/sound/core/oss/pcm_oss.c
@@ -0,0 +1,3103 @@
+/*
+ *  Digital Audio (PCM) abstract layer / OSS compatible
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#if 0
+#define PLUGIN_DEBUG
+#endif
+#if 0
+#define OSS_DEBUG
+#endif
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
+#include <linux/moduleparam.h>
+#include <linux/math64.h>
+#include <linux/string.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];
+static int adsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1};
+static int nonblock_open = 1;
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.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(struct snd_card *card, unsigned int cmd, unsigned long arg);
+static int snd_pcm_oss_get_rate(struct snd_pcm_oss_file *pcm_oss_file);
+static int snd_pcm_oss_get_channels(struct snd_pcm_oss_file *pcm_oss_file);
+static int snd_pcm_oss_get_format(struct snd_pcm_oss_file *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);
+}
+
+/*
+ * helper functions to process hw_params
+ */
+static int snd_interval_refine_min(struct snd_interval *i, unsigned int min, int openmin)
+{
+	int changed = 0;
+	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(struct snd_interval *i, unsigned int max, int openmax)
+{
+	int changed = 0;
+	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;
+}
+
+static int snd_interval_refine_set(struct snd_interval *i, unsigned int val)
+{
+	struct snd_interval t;
+	t.empty = 0;
+	t.min = t.max = val;
+	t.openmin = t.openmax = 0;
+	t.integer = 1;
+	return snd_interval_refine(i, &t);
+}
+
+/**
+ * snd_pcm_hw_param_value_min
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * Return the minimum value for field PAR.
+ */
+static unsigned int
+snd_pcm_hw_param_value_min(const struct snd_pcm_hw_params *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 struct snd_interval *i = hw_param_interval_c(params, var);
+		if (dir)
+			*dir = i->openmin;
+		return snd_interval_min(i);
+	}
+	return -EINVAL;
+}
+
+/**
+ * snd_pcm_hw_param_value_max
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * Return the maximum value for field PAR.
+ */
+static unsigned int
+snd_pcm_hw_param_value_max(const struct snd_pcm_hw_params *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 struct snd_interval *i = hw_param_interval_c(params, var);
+		if (dir)
+			*dir = - (int) i->openmax;
+		return snd_interval_max(i);
+	}
+	return -EINVAL;
+}
+
+static int _snd_pcm_hw_param_mask(struct snd_pcm_hw_params *params,
+				  snd_pcm_hw_param_t var,
+				  const struct snd_mask *val)
+{
+	int changed;
+	changed = snd_mask_refine(hw_param_mask(params, var), val);
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+static int snd_pcm_hw_param_mask(struct snd_pcm_substream *pcm,
+				 struct snd_pcm_hw_params *params,
+				 snd_pcm_hw_param_t var,
+				 const struct snd_mask *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 _snd_pcm_hw_param_min(struct snd_pcm_hw_params *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
+		return -EINVAL;
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/**
+ * snd_pcm_hw_param_min
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @val: minimal value
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * 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
+ */
+static int snd_pcm_hw_param_min(struct snd_pcm_substream *pcm,
+				struct snd_pcm_hw_params *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);
+}
+
+static int _snd_pcm_hw_param_max(struct snd_pcm_hw_params *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
+		return -EINVAL;
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/**
+ * snd_pcm_hw_param_max
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @val: maximal value
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * 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
+ */
+static int snd_pcm_hw_param_max(struct snd_pcm_substream *pcm,
+				struct snd_pcm_hw_params *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);
+}
+
+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) {
+		(*c)--;
+	} else if (*cdir == 2) {
+		(*c)++;
+	}
+	return 0;
+}
+
+static int boundary_lt(unsigned int a, int adir,
+		       unsigned int b, int bdir)
+{
+	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
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @best: value to set
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * 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.
+  */
+static int snd_pcm_hw_param_near(struct snd_pcm_substream *pcm,
+				 struct snd_pcm_hw_params *params,
+				 snd_pcm_hw_param_t var, unsigned int best,
+				 int *dir)
+{
+	struct snd_pcm_hw_params *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) {
+		struct snd_pcm_hw_params *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);
+		if (max < 0)
+			return max;
+		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);
+	snd_BUG_ON(v < 0);
+	return v;
+}
+
+static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params,
+				 snd_pcm_hw_param_t var, unsigned int val,
+				 int dir)
+{
+	int changed;
+	if (hw_is_mask(var)) {
+		struct snd_mask *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)) {
+		struct snd_interval *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 {
+			struct snd_interval 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
+		return -EINVAL;
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/**
+ * snd_pcm_hw_param_set
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @val: value to set
+ * @dir: pointer to the direction (-1,0,1) or NULL
+ *
+ * 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
+ */
+static int snd_pcm_hw_param_set(struct snd_pcm_substream *pcm,
+				struct snd_pcm_hw_params *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);
+}
+
+static int _snd_pcm_hw_param_setinteger(struct snd_pcm_hw_params *params,
+					snd_pcm_hw_param_t var)
+{
+	int changed;
+	changed = snd_interval_setinteger(hw_param_interval(params, var));
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+	
+/*
+ * plugin
+ */
+
+#ifdef CONFIG_SND_PCM_OSS_PLUGINS
+static int snd_pcm_oss_plugin_clear(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_pcm_plugin *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(struct snd_pcm_plugin *plugin)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_plugin *plugin)
+{
+	struct snd_pcm_runtime *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;
+}
+#endif /* CONFIG_SND_PCM_OSS_PLUGINS */
+
+static long snd_pcm_oss_bytes(struct snd_pcm_substream *substream, long frames)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	long buffer_size = snd_pcm_lib_buffer_bytes(substream);
+	long bytes = frames_to_bytes(runtime, frames);
+	if (buffer_size == runtime->oss.buffer_bytes)
+		return bytes;
+#if BITS_PER_LONG >= 64
+	return runtime->oss.buffer_bytes * bytes / buffer_size;
+#else
+	{
+		u64 bsize = (u64)runtime->oss.buffer_bytes * (u64)bytes;
+		return div_u64(bsize, buffer_size);
+	}
+#endif
+}
+
+static long snd_pcm_alsa_frames(struct snd_pcm_substream *substream, long bytes)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	long 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 inline
+snd_pcm_uframes_t get_hw_ptr_period(struct snd_pcm_runtime *runtime)
+{
+	return runtime->hw_ptr_interrupt;
+}
+
+/* define extended formats in the recent OSS versions (if any) */
+/* linear formats */
+#define AFMT_S32_LE      0x00001000
+#define AFMT_S32_BE      0x00002000
+#define AFMT_S24_LE      0x00008000
+#define AFMT_S24_BE      0x00010000
+#define AFMT_S24_PACKED  0x00040000
+
+/* other supported formats */
+#define AFMT_FLOAT       0x00004000
+#define AFMT_SPDIF_RAW   0x00020000
+
+/* unsupported formats */
+#define AFMT_AC3         0x00000400
+#define AFMT_VORBIS      0x00000800
+
+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;
+	case AFMT_S32_LE:	return SNDRV_PCM_FORMAT_S32_LE;
+	case AFMT_S32_BE:	return SNDRV_PCM_FORMAT_S32_BE;
+	case AFMT_S24_LE:	return SNDRV_PCM_FORMAT_S24_LE;
+	case AFMT_S24_BE:	return SNDRV_PCM_FORMAT_S24_BE;
+	case AFMT_S24_PACKED:	return SNDRV_PCM_FORMAT_S24_3LE;
+	case AFMT_FLOAT:	return SNDRV_PCM_FORMAT_FLOAT;
+	case AFMT_SPDIF_RAW:	return SNDRV_PCM_FORMAT_IEC958_SUBFRAME;
+	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;
+	case SNDRV_PCM_FORMAT_S32_LE:	return AFMT_S32_LE;
+	case SNDRV_PCM_FORMAT_S32_BE:	return AFMT_S32_BE;
+	case SNDRV_PCM_FORMAT_S24_LE:	return AFMT_S24_LE;
+	case SNDRV_PCM_FORMAT_S24_BE:	return AFMT_S24_BE;
+	case SNDRV_PCM_FORMAT_S24_3LE:	return AFMT_S24_PACKED;
+	case SNDRV_PCM_FORMAT_FLOAT:	return AFMT_FLOAT;
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME: return AFMT_SPDIF_RAW;
+	default:			return -EINVAL;
+	}
+}
+
+static int snd_pcm_oss_period_size(struct snd_pcm_substream *substream, 
+				   struct snd_pcm_hw_params *oss_params,
+				   struct snd_pcm_hw_params *slave_params)
+{
+	size_t s;
+	size_t oss_buffer_size, oss_period_size, oss_periods;
+	size_t min_period_size, max_period_size;
+	struct snd_pcm_runtime *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(&substream->mmap_count)) {
+		if (oss_buffer_size > runtime->oss.mmap_bytes)
+			oss_buffer_size = runtime->oss.mmap_bytes;
+	}
+
+	if (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.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;
+
+	if (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(struct snd_pcm_substream *substream,
+		       struct snd_pcm_hw_params *params, unsigned int best_rate)
+{
+	struct snd_interval *it;
+	struct snd_pcm_hw_params *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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_pcm_hw_params *params, *sparams;
+	struct snd_pcm_sw_params *sw_params;
+	ssize_t oss_buffer_size, oss_period_size;
+	size_t oss_frame_size;
+	int err;
+	int direct;
+	int format, sformat, n;
+	struct snd_mask sformat_mask;
+	struct snd_mask mask;
+
+	if (mutex_lock_interruptible(&runtime->oss.params_lock))
+		return -EINTR;
+	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(&substream->mmap_count))
+		direct = 1;
+	else
+		direct = substream->oss.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(&substream->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);
+	if (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;
+
+#ifdef CONFIG_SND_PCM_OSS_PLUGINS
+	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) {
+			struct snd_pcm_plugin *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;
+			}
+		}
+	}
+#endif
+
+	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);
+	if (err < 0)
+		goto failure;
+
+	err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIODS,
+				     runtime->oss.periods, NULL);
+	if (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(&substream->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->avail_min = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+		1 : runtime->period_size;
+	if (atomic_read(&substream->mmap_count) ||
+	    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));
+	if (oss_period_size < 0) {
+		err = -EINVAL;
+		goto failure;
+	}
+#ifdef CONFIG_SND_PCM_OSS_PLUGINS
+	if (runtime->oss.plugin_first) {
+		err = snd_pcm_plug_alloc(substream, oss_period_size);
+		if (err < 0)
+			goto failure;
+	}
+#endif
+	oss_period_size *= oss_frame_size;
+
+	oss_buffer_size = oss_period_size * runtime->oss.periods;
+	if (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);
+
+	vfree(runtime->oss.buffer);
+	runtime->oss.buffer = vmalloc(runtime->oss.period_bytes);
+	if (!runtime->oss.buffer) {
+		err = -ENOMEM;
+		goto failure;
+	}
+
+	runtime->oss.params = 0;
+	runtime->oss.prepare = 1;
+	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);
+	mutex_unlock(&runtime->oss.params_lock);
+	return err;
+}
+
+static int snd_pcm_oss_get_active_substream(struct snd_pcm_oss_file *pcm_oss_file, struct snd_pcm_substream **r_substream)
+{
+	int idx, err;
+	struct snd_pcm_substream *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;
+		}
+	}
+	if (!asubstream)
+		return -EIO;
+	if (r_substream)
+		*r_substream = asubstream;
+	return 0;
+}
+
+static int snd_pcm_oss_prepare(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct snd_pcm_runtime *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_period = 0;
+	runtime->oss.period_ptr = 0;
+	runtime->oss.buffer_used = 0;
+
+	return 0;
+}
+
+static int snd_pcm_oss_make_ready(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream, snd_pcm_sframes_t *delay)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream, const char *ptr, snd_pcm_uframes_t frames, int in_kernel)
+{
+	struct snd_pcm_runtime *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(KERN_DEBUG "pcm_oss: write: "
+				       "recovering from XRUN\n");
+			else
+				printk(KERN_DEBUG "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(struct snd_pcm_substream *substream, char *ptr, snd_pcm_uframes_t frames, int in_kernel)
+{
+	struct snd_pcm_runtime *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(KERN_DEBUG "pcm_oss: read: "
+				       "recovering from XRUN\n");
+			else
+				printk(KERN_DEBUG "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(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel)
+{
+	struct snd_pcm_runtime *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(KERN_DEBUG "pcm_oss: writev: "
+				       "recovering from XRUN\n");
+			else
+				printk(KERN_DEBUG "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(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel)
+{
+	struct snd_pcm_runtime *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(KERN_DEBUG "pcm_oss: readv: "
+				       "recovering from XRUN\n");
+			else
+				printk(KERN_DEBUG "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(struct snd_pcm_substream *substream, const char *buf, size_t bytes, int in_kernel)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_sframes_t frames, frames1;
+#ifdef CONFIG_SND_PCM_OSS_PLUGINS
+	if (runtime->oss.plugin_first) {
+		struct snd_pcm_plugin_channel *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
+#endif
+	{
+		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(struct snd_pcm_substream *substream, const char __user *buf, size_t bytes)
+{
+	size_t xfer = 0;
+	ssize_t tmp;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (atomic_read(&substream->mmap_count))
+		return -ENXIO;
+
+	if ((tmp = snd_pcm_oss_make_ready(substream)) < 0)
+		return tmp;
+	mutex_lock(&runtime->oss.params_lock);
+	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)) {
+					tmp = -EFAULT;
+					goto err;
+				}
+			}
+			runtime->oss.buffer_used += tmp;
+			buf += tmp;
+			bytes -= tmp;
+			xfer += tmp;
+			if (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)
+					goto err;
+				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->f_flags & O_NONBLOCK) != 0) {
+					tmp = -EAGAIN;
+					goto err;
+				}
+			}
+		} else {
+			tmp = snd_pcm_oss_write2(substream,
+						 (const char __force *)buf,
+						 runtime->oss.period_bytes, 0);
+			if (tmp <= 0)
+				goto err;
+			runtime->oss.bytes += tmp;
+			buf += tmp;
+			bytes -= tmp;
+			xfer += tmp;
+			if ((substream->f_flags & O_NONBLOCK) != 0 &&
+			    tmp != runtime->oss.period_bytes)
+				break;
+		}
+	}
+	mutex_unlock(&runtime->oss.params_lock);
+	return xfer;
+
+ err:
+	mutex_unlock(&runtime->oss.params_lock);
+	return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+}
+
+static ssize_t snd_pcm_oss_read2(struct snd_pcm_substream *substream, char *buf, size_t bytes, int in_kernel)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_sframes_t frames, frames1;
+#ifdef CONFIG_SND_PCM_OSS_PLUGINS
+	char __user *final_dst = (char __user *)buf;
+	if (runtime->oss.plugin_first) {
+		struct snd_pcm_plugin_channel *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
+#endif
+	{
+		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(struct snd_pcm_substream *substream, char __user *buf, size_t bytes)
+{
+	size_t xfer = 0;
+	ssize_t tmp;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (atomic_read(&substream->mmap_count))
+		return -ENXIO;
+
+	if ((tmp = snd_pcm_oss_make_ready(substream)) < 0)
+		return tmp;
+	mutex_lock(&runtime->oss.params_lock);
+	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)
+					goto err;
+				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)) {
+				tmp = -EFAULT;
+				goto err;
+			}
+			buf += tmp;
+			bytes -= tmp;
+			xfer += tmp;
+			runtime->oss.buffer_used -= tmp;
+		} else {
+			tmp = snd_pcm_oss_read2(substream, (char __force *)buf,
+						runtime->oss.period_bytes, 0);
+			if (tmp <= 0)
+				goto err;
+			runtime->oss.bytes += tmp;
+			buf += tmp;
+			bytes -= tmp;
+			xfer += tmp;
+		}
+	}
+	mutex_unlock(&runtime->oss.params_lock);
+	return xfer;
+
+ err:
+	mutex_unlock(&runtime->oss.params_lock);
+	return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+}
+
+static int snd_pcm_oss_reset(struct snd_pcm_oss_file *pcm_oss_file)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+	int i;
+
+	for (i = 0; i < 2; i++) { 
+		substream = pcm_oss_file->streams[i];
+		if (!substream)
+			continue;
+		runtime = substream->runtime;
+		snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+		runtime->oss.prepare = 1;
+		runtime->oss.buffer_used = 0;
+		runtime->oss.prev_hw_ptr_period = 0;
+		runtime->oss.period_ptr = 0;
+	}
+	return 0;
+}
+
+static int snd_pcm_oss_post(struct snd_pcm_oss_file *pcm_oss_file)
+{
+	struct snd_pcm_substream *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_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(struct snd_pcm_substream *substream, size_t size)
+{
+	struct snd_pcm_runtime *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(KERN_DEBUG "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(struct snd_pcm_oss_file *pcm_oss_file)
+{
+	int err = 0;
+	unsigned int saved_f_flags;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *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(&substream->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);
+		mutex_lock(&runtime->oss.params_lock);
+		if (runtime->oss.buffer_used > 0) {
+#ifdef OSS_DEBUG
+			printk(KERN_DEBUG "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) {
+				mutex_unlock(&runtime->oss.params_lock);
+				return err;
+			}
+		} else if (runtime->oss.period_ptr > 0) {
+#ifdef OSS_DEBUG
+			printk(KERN_DEBUG "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) {
+				mutex_unlock(&runtime->oss.params_lock);
+				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);
+					size1 /= runtime->channels; /* frames */
+					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);
+			}
+		}
+		mutex_unlock(&runtime->oss.params_lock);
+		/*
+		 * finish sync: drain the buffer
+		 */
+	      __direct:
+		saved_f_flags = substream->f_flags;
+		substream->f_flags &= ~O_NONBLOCK;
+		err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
+		substream->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_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(struct snd_pcm_oss_file *pcm_oss_file, int rate)
+{
+	int idx;
+
+	for (idx = 1; idx >= 0; --idx) {
+		struct snd_pcm_substream *substream = pcm_oss_file->streams[idx];
+		struct snd_pcm_runtime *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(struct snd_pcm_oss_file *pcm_oss_file)
+{
+	struct snd_pcm_substream *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(struct snd_pcm_oss_file *pcm_oss_file, unsigned int channels)
+{
+	int idx;
+	if (channels < 1)
+		channels = 1;
+	if (channels > 128)
+		return -EINVAL;
+	for (idx = 1; idx >= 0; --idx) {
+		struct snd_pcm_substream *substream = pcm_oss_file->streams[idx];
+		struct snd_pcm_runtime *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(struct snd_pcm_oss_file *pcm_oss_file)
+{
+	struct snd_pcm_substream *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(struct snd_pcm_oss_file *pcm_oss_file)
+{
+	struct snd_pcm_substream *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(struct snd_pcm_oss_file *pcm_oss_file)
+{
+	struct snd_pcm_substream *substream;
+	int err;
+	int direct;
+	struct snd_pcm_hw_params *params;
+	unsigned int formats = 0;
+	struct snd_mask format_mask;
+	int fmt;
+
+	if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0)
+		return err;
+	if (atomic_read(&substream->mmap_count))
+		direct = 1;
+	else
+		direct = substream->oss.setup.direct;
+	if (!direct)
+		return AFMT_MU_LAW | AFMT_U8 |
+		       AFMT_S16_LE | AFMT_S16_BE |
+		       AFMT_S8 | AFMT_U16_LE |
+		       AFMT_U16_BE |
+			AFMT_S32_LE | AFMT_S32_BE |
+			AFMT_S24_LE | AFMT_S24_BE |
+			AFMT_S24_PACKED;
+	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);
+	if (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(struct snd_pcm_oss_file *pcm_oss_file, int format)
+{
+	int formats, idx;
+	
+	if (format != AFMT_QUERY) {
+		formats = snd_pcm_oss_get_formats(pcm_oss_file);
+		if (formats < 0)
+			return formats;
+		if (!(formats & format))
+			format = AFMT_U8;
+		for (idx = 1; idx >= 0; --idx) {
+			struct snd_pcm_substream *substream = pcm_oss_file->streams[idx];
+			struct snd_pcm_runtime *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(struct snd_pcm_oss_file *pcm_oss_file)
+{
+	struct snd_pcm_substream *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(struct snd_pcm_substream *substream, int subdivide)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_oss_file *pcm_oss_file, int subdivide)
+{
+	int err = -EINVAL, idx;
+
+	for (idx = 1; idx >= 0; --idx) {
+		struct snd_pcm_substream *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(struct snd_pcm_substream *substream, unsigned int val)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_oss_file *pcm_oss_file, unsigned int val)
+{
+	int err = -EINVAL, idx;
+
+	for (idx = 1; idx >= 0; --idx) {
+		struct snd_pcm_substream *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)
+{
+	spin_lock(&file->f_lock);
+	file->f_flags |= O_NONBLOCK;
+	spin_unlock(&file->f_lock);
+	return 0;
+}
+
+static int snd_pcm_oss_get_caps1(struct snd_pcm_substream *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
+	{
+		struct snd_pcm_runtime *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(struct snd_pcm_oss_file *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++) {
+		struct snd_pcm_substream *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(struct snd_pcm_substream *substream,
+				      snd_pcm_uframes_t hw_ptr)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_oss_file *pcm_oss_file, int trigger)
+{
+	struct snd_pcm_runtime *runtime;
+	struct snd_pcm_substream *psubstream = NULL, *csubstream = NULL;
+	int err, cmd;
+
+#ifdef OSS_DEBUG
+	printk(KERN_DEBUG "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->mmap_count))
+				snd_pcm_oss_simulate_fill(psubstream,
+						get_hw_ptr_period(runtime));
+			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_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_ioctl(csubstream, cmd, NULL);
+		if (err < 0)
+			return err;
+	}
+ _skip2:
+	return 0;
+}
+
+static int snd_pcm_oss_get_trigger(struct snd_pcm_oss_file *pcm_oss_file)
+{
+	struct snd_pcm_substream *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(struct snd_pcm_oss_file *pcm_oss_file)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *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_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(struct snd_pcm_oss_file *pcm_oss_file, int stream, struct count_info __user * _info)
+{	
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *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(&substream->mmap_count)) {
+		snd_pcm_sframes_t n;
+		delay = get_hw_ptr_period(runtime);
+		n = delay - runtime->oss.prev_hw_ptr_period;
+		if (n < 0)
+			n += runtime->boundary;
+		info.blocks = n / runtime->period_size;
+		runtime->oss.prev_hw_ptr_period = 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);
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			if (substream->oss.setup.buggyptr)
+				info.blocks = (runtime->oss.buffer_bytes - delay - fixup) / runtime->oss.period_bytes;
+			else
+				info.blocks = (delay + fixup) / runtime->oss.period_bytes;
+			info.bytes = (runtime->oss.bytes - delay) & INT_MAX;
+		} else {
+			delay += fixup;
+			info.blocks = delay / runtime->oss.period_bytes;
+			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(struct snd_pcm_oss_file *pcm_oss_file, int stream, struct audio_buf_info __user *_info)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *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(KERN_DEBUG "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(struct snd_pcm_oss_file *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 const char *strip_task_path(const char *path)
+{
+	const char *ptr, *ptrl = NULL;
+	for (ptr = path; *ptr; ptr++) {
+		if (*ptr == '/')
+			ptrl = ptr + 1;
+	}
+	return ptrl;
+}
+
+static void snd_pcm_oss_look_for_setup(struct snd_pcm *pcm, int stream,
+				      const char *task_name,
+				      struct snd_pcm_oss_setup *rsetup)
+{
+	struct snd_pcm_oss_setup *setup;
+
+	mutex_lock(&pcm->streams[stream].oss.setup_mutex);
+	do {
+		for (setup = pcm->streams[stream].oss.setup_list; setup;
+		     setup = setup->next) {
+			if (!strcmp(setup->task_name, task_name))
+				goto out;
+		}
+	} while ((task_name = strip_task_path(task_name)) != NULL);
+ out:
+	if (setup)
+		*rsetup = *setup;
+	mutex_unlock(&pcm->streams[stream].oss.setup_mutex);
+}
+
+static void snd_pcm_oss_release_substream(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime;
+	runtime = substream->runtime;
+	vfree(runtime->oss.buffer);
+	runtime->oss.buffer = NULL;
+#ifdef CONFIG_SND_PCM_OSS_PLUGINS
+	snd_pcm_oss_plugin_clear(substream);
+#endif
+	substream->oss.oss = 0;
+}
+
+static void snd_pcm_oss_init_substream(struct snd_pcm_substream *substream,
+				       struct snd_pcm_oss_setup *setup,
+				       int minor)
+{
+	struct snd_pcm_runtime *runtime;
+
+	substream->oss.oss = 1;
+	substream->oss.setup = *setup;
+	if (setup->nonblock)
+		substream->f_flags |= O_NONBLOCK;
+	else if (setup->block)
+		substream->f_flags &= ~O_NONBLOCK;
+	runtime = substream->runtime;
+	runtime->oss.params = 1;
+	runtime->oss.trigger = 1;
+	runtime->oss.rate = 8000;
+	mutex_init(&runtime->oss.params_lock);
+	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;
+	substream->pcm_release = snd_pcm_oss_release_substream;
+}
+
+static int snd_pcm_oss_release_file(struct snd_pcm_oss_file *pcm_oss_file)
+{
+	int cidx;
+	if (!pcm_oss_file)
+		return 0;
+	for (cidx = 0; cidx < 2; ++cidx) {
+		struct snd_pcm_substream *substream = pcm_oss_file->streams[cidx];
+		if (substream)
+			snd_pcm_release_substream(substream);
+	}
+	kfree(pcm_oss_file);
+	return 0;
+}
+
+static int snd_pcm_oss_open_file(struct file *file,
+				 struct snd_pcm *pcm,
+				 struct snd_pcm_oss_file **rpcm_oss_file,
+				 int minor,
+				 struct snd_pcm_oss_setup *setup)
+{
+	int idx, err;
+	struct snd_pcm_oss_file *pcm_oss_file;
+	struct snd_pcm_substream *substream;
+	fmode_t f_mode = file->f_mode;
+
+	if (rpcm_oss_file)
+		*rpcm_oss_file = NULL;
+
+	pcm_oss_file = kzalloc(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;
+
+	file->f_flags &= ~O_APPEND;
+	for (idx = 0; idx < 2; idx++) {
+		if (setup[idx].disable)
+			continue;
+		if (! pcm->streams[idx].substream_count)
+			continue; /* no matching substream */
+		if (idx == SNDRV_PCM_STREAM_PLAYBACK) {
+			if (! (f_mode & FMODE_WRITE))
+				continue;
+		} else {
+			if (! (f_mode & FMODE_READ))
+				continue;
+		}
+		err = snd_pcm_open_substream(pcm, idx, file, &substream);
+		if (err < 0) {
+			snd_pcm_oss_release_file(pcm_oss_file);
+			return err;
+		}
+
+		pcm_oss_file->streams[idx] = substream;
+		substream->file = pcm_oss_file;
+		snd_pcm_oss_init_substream(substream, &setup[idx], minor);
+	}
+	
+	if (!pcm_oss_file->streams[0] && !pcm_oss_file->streams[1]) {
+		snd_pcm_oss_release_file(pcm_oss_file);
+		return -EINVAL;
+	}
+
+	file->private_data = pcm_oss_file;
+	if (rpcm_oss_file)
+		*rpcm_oss_file = pcm_oss_file;
+	return 0;
+}
+
+
+static int snd_task_name(struct task_struct *task, char *name, size_t size)
+{
+	unsigned int idx;
+
+	if (snd_BUG_ON(!task || !name || 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;
+}
+
+static int snd_pcm_oss_open(struct inode *inode, struct file *file)
+{
+	int err;
+	char task_name[32];
+	struct snd_pcm *pcm;
+	struct snd_pcm_oss_file *pcm_oss_file;
+	struct snd_pcm_oss_setup setup[2];
+	int nonblock;
+	wait_queue_t wait;
+
+	err = nonseekable_open(inode, file);
+	if (err < 0)
+		return err;
+
+	pcm = snd_lookup_oss_minor_data(iminor(inode),
+					SNDRV_OSS_DEVICE_TYPE_PCM);
+	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;
+	}
+	memset(setup, 0, sizeof(setup));
+	if (file->f_mode & FMODE_WRITE)
+		snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					   task_name, &setup[0]);
+	if (file->f_mode & FMODE_READ)
+		snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE,
+					   task_name, &setup[1]);
+
+	nonblock = !!(file->f_flags & O_NONBLOCK);
+	if (!nonblock)
+		nonblock = nonblock_open;
+
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&pcm->open_wait, &wait);
+	mutex_lock(&pcm->open_mutex);
+	while (1) {
+		err = snd_pcm_oss_open_file(file, pcm, &pcm_oss_file,
+					    iminor(inode), setup);
+		if (err >= 0)
+			break;
+		if (err == -EAGAIN) {
+			if (nonblock) {
+				err = -EBUSY;
+				break;
+			}
+		} else
+			break;
+		set_current_state(TASK_INTERRUPTIBLE);
+		mutex_unlock(&pcm->open_mutex);
+		schedule();
+		mutex_lock(&pcm->open_mutex);
+		if (signal_pending(current)) {
+			err = -ERESTARTSYS;
+			break;
+		}
+	}
+	remove_wait_queue(&pcm->open_wait, &wait);
+	mutex_unlock(&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)
+{
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_oss_file *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];
+	if (snd_BUG_ON(!substream))
+		return -ENXIO;
+	pcm = substream->pcm;
+	if (!pcm->card->shutdown)
+		snd_pcm_oss_sync(pcm_oss_file);
+	mutex_lock(&pcm->open_mutex);
+	snd_pcm_oss_release_file(pcm_oss_file);
+	mutex_unlock(&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)
+{
+	struct snd_pcm_oss_file *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 */
+		struct snd_pcm_substream *substream;
+		int idx;
+		for (idx = 0; idx < 2; ++idx) {
+			substream = pcm_oss_file->streams[idx];
+			if (substream != NULL)
+				break;
+		}
+		if (snd_BUG_ON(idx >= 2))
+			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(KERN_DEBUG "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)
+{
+	struct snd_pcm_oss_file *pcm_oss_file;
+	struct snd_pcm_substream *substream;
+
+	pcm_oss_file = file->private_data;
+	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+	if (substream == NULL)
+		return -ENXIO;
+	substream->f_flags = file->f_flags & O_NONBLOCK;
+#ifndef OSS_DEBUG
+	return snd_pcm_oss_read1(substream, buf, count);
+#else
+	{
+		ssize_t res = snd_pcm_oss_read1(substream, buf, count);
+		printk(KERN_DEBUG "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)
+{
+	struct snd_pcm_oss_file *pcm_oss_file;
+	struct snd_pcm_substream *substream;
+	long result;
+
+	pcm_oss_file = file->private_data;
+	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	if (substream == NULL)
+		return -ENXIO;
+	substream->f_flags = file->f_flags & O_NONBLOCK;
+	result = snd_pcm_oss_write1(substream, buf, count);
+#ifdef OSS_DEBUG
+	printk(KERN_DEBUG "pcm_oss: write %li bytes (wrote %li bytes)\n",
+	       (long)count, (long)result);
+#endif
+	return result;
+}
+
+static int snd_pcm_oss_playback_ready(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	if (atomic_read(&substream->mmap_count))
+		return runtime->oss.prev_hw_ptr_period !=
+						get_hw_ptr_period(runtime);
+	else
+		return snd_pcm_playback_avail(runtime) >=
+						runtime->oss.period_frames;
+}
+
+static int snd_pcm_oss_capture_ready(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	if (atomic_read(&substream->mmap_count))
+		return runtime->oss.prev_hw_ptr_period !=
+						get_hw_ptr_period(runtime);
+	else
+		return snd_pcm_capture_avail(runtime) >=
+						runtime->oss.period_frames;
+}
+
+static unsigned int snd_pcm_oss_poll(struct file *file, poll_table * wait)
+{
+	struct snd_pcm_oss_file *pcm_oss_file;
+	unsigned int mask;
+	struct snd_pcm_substream *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) {
+		struct snd_pcm_runtime *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) {
+		struct snd_pcm_runtime *runtime = csubstream->runtime;
+		snd_pcm_state_t 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) {
+			struct snd_pcm_oss_file 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)
+{
+	struct snd_pcm_oss_file *pcm_oss_file;
+	struct snd_pcm_substream *substream = NULL;
+	struct snd_pcm_runtime *runtime;
+	int err;
+
+#ifdef OSS_DEBUG
+	printk(KERN_DEBUG "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;
+	}
+#ifdef CONFIG_SND_PCM_OSS_PLUGINS
+	if (runtime->oss.plugin_first != NULL)
+		return -EIO;
+#endif
+
+	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(KERN_DEBUG "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;
+}
+
+#ifdef CONFIG_SND_VERBOSE_PROCFS
+/*
+ *  /proc interface
+ */
+
+static void snd_pcm_oss_proc_read(struct snd_info_entry *entry,
+				  struct snd_info_buffer *buffer)
+{
+	struct snd_pcm_str *pstr = entry->private_data;
+	struct snd_pcm_oss_setup *setup = pstr->oss.setup_list;
+	mutex_lock(&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;
+	}
+	mutex_unlock(&pstr->oss.setup_mutex);
+}
+
+static void snd_pcm_oss_proc_free_setup_list(struct snd_pcm_str * pstr)
+{
+	struct snd_pcm_oss_setup *setup, *setupn;
+
+	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(struct snd_info_entry *entry,
+				   struct snd_info_buffer *buffer)
+{
+	struct snd_pcm_str *pstr = entry->private_data;
+	char line[128], str[32], task_name[32];
+	const char *ptr;
+	int idx1;
+	struct snd_pcm_oss_setup *setup, *setup1, template;
+
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		mutex_lock(&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);
+			mutex_unlock(&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;
+			} else if (!strcmp(str, "buggy-ptr")) {
+				template.buggyptr = 1;
+			}
+		} while (*str);
+		if (setup == NULL) {
+			setup = kmalloc(sizeof(*setup), GFP_KERNEL);
+			if (! setup) {
+				buffer->error = -ENOMEM;
+				mutex_unlock(&pstr->oss.setup_mutex);
+				return;
+			}
+			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 = kstrdup(task_name, GFP_KERNEL);
+			if (! template.task_name) {
+				kfree(setup);
+				buffer->error = -ENOMEM;
+				mutex_unlock(&pstr->oss.setup_mutex);
+				return;
+			}
+		}
+		*setup = template;
+		mutex_unlock(&pstr->oss.setup_mutex);
+	}
+}
+
+static void snd_pcm_oss_proc_init(struct snd_pcm *pcm)
+{
+	int stream;
+	for (stream = 0; stream < 2; ++stream) {
+		struct snd_info_entry *entry;
+		struct snd_pcm_str *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 = snd_pcm_oss_proc_read;
+			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(struct snd_pcm *pcm)
+{
+	int stream;
+	for (stream = 0; stream < 2; ++stream) {
+		struct snd_pcm_str *pstr = &pcm->streams[stream];
+		snd_info_free_entry(pstr->oss.proc_entry);
+		pstr->oss.proc_entry = NULL;
+		snd_pcm_oss_proc_free_setup_list(pstr);
+	}
+}
+#else /* !CONFIG_SND_VERBOSE_PROCFS */
+#define snd_pcm_oss_proc_init(pcm)
+#define snd_pcm_oss_proc_done(pcm)
+#endif /* CONFIG_SND_VERBOSE_PROCFS */
+
+/*
+ *  ENTRY functions
+ */
+
+static const 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,
+	.llseek =	no_llseek,
+	.poll =		snd_pcm_oss_poll,
+	.unlocked_ioctl =	snd_pcm_oss_ioctl,
+	.compat_ioctl =	snd_pcm_oss_ioctl_compat,
+	.mmap =		snd_pcm_oss_mmap,
+};
+
+static void register_oss_dsp(struct snd_pcm *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_f_reg,
+				    pcm, name) < 0) {
+		snd_printk(KERN_ERR "unable to register OSS PCM device %i:%i\n",
+			   pcm->card->number, pcm->device);
+	}
+}
+
+static int snd_pcm_oss_register_minor(struct snd_pcm *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(struct snd_pcm *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);
+		}
+		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;
+	}
+	return 0;
+}
+
+static int snd_pcm_oss_unregister_minor(struct snd_pcm *pcm)
+{
+	snd_pcm_oss_disconnect_minor(pcm);
+	snd_pcm_oss_proc_done(pcm);
+	return 0;
+}
+
+static struct snd_pcm_notify 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(KERN_ERR "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(KERN_ERR "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/linux/sound/core/oss/pcm_plugin.c b/linux/sound/core/oss/pcm_plugin.c
new file mode 100644
index 0000000..6751daa
--- /dev/null
+++ b/linux/sound/core/oss/pcm_plugin.c
@@ -0,0 +1,752 @@
+/*
+ *  PCM Plug-In shared (kernel/library) code
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@perex.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 <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)
+
+/*
+ *  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(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames)
+{
+	struct snd_pcm_plugin_format *format;
+	ssize_t width;
+	size_t size;
+	unsigned int channel;
+	struct snd_pcm_plugin_channel *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;
+	if (snd_BUG_ON(size % 8))
+		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) {
+		if (snd_BUG_ON(size % format->channels))
+			return -EINVAL;
+		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(struct snd_pcm_substream *plug, snd_pcm_uframes_t frames)
+{
+	int err;
+	if (snd_BUG_ON(!snd_pcm_plug_first(plug)))
+		return -ENXIO;
+	if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) {
+		struct snd_pcm_plugin *plugin = snd_pcm_plug_first(plug);
+		while (plugin->next) {
+			if (plugin->dst_frames)
+				frames = plugin->dst_frames(plugin, frames);
+			if (snd_BUG_ON(frames <= 0))
+				return -ENXIO;
+			plugin = plugin->next;
+			err = snd_pcm_plugin_alloc(plugin, frames);
+			if (err < 0)
+				return err;
+		}
+	} else {
+		struct snd_pcm_plugin *plugin = snd_pcm_plug_last(plug);
+		while (plugin->prev) {
+			if (plugin->src_frames)
+				frames = plugin->src_frames(plugin, frames);
+			if (snd_BUG_ON(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(struct snd_pcm_plugin *plugin,
+				       snd_pcm_uframes_t frames,
+				       struct snd_pcm_plugin_channel **channels)
+{
+	*channels = plugin->buf_channels;
+	return frames;
+}
+
+int snd_pcm_plugin_build(struct snd_pcm_substream *plug,
+			 const char *name,
+			 struct snd_pcm_plugin_format *src_format,
+			 struct snd_pcm_plugin_format *dst_format,
+			 size_t extra,
+			 struct snd_pcm_plugin **ret)
+{
+	struct snd_pcm_plugin *plugin;
+	unsigned int channels;
+	
+	if (snd_BUG_ON(!plug))
+		return -ENXIO;
+	if (snd_BUG_ON(!src_format || !dst_format))
+		return -ENXIO;
+	plugin = kzalloc(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_BUG_ON(plugin->src_width <= 0);
+	plugin->dst_format = *dst_format;
+	plugin->dst_width = snd_pcm_format_physical_width(dst_format->format);
+	snd_BUG_ON(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->client_channels = snd_pcm_plugin_client_channels;
+	*ret = plugin;
+	return 0;
+}
+
+int snd_pcm_plugin_free(struct snd_pcm_plugin *plugin)
+{
+	if (! plugin)
+		return 0;
+	if (plugin->private_free)
+		plugin->private_free(plugin);
+	kfree(plugin->buf_channels);
+	vfree(plugin->buf);
+	kfree(plugin);
+	return 0;
+}
+
+snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *plug, snd_pcm_uframes_t drv_frames)
+{
+	struct snd_pcm_plugin *plugin, *plugin_prev, *plugin_next;
+	int stream = snd_pcm_plug_stream(plug);
+
+	if (snd_BUG_ON(!plug))
+		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(struct snd_pcm_substream *plug, snd_pcm_uframes_t clt_frames)
+{
+	struct snd_pcm_plugin *plugin, *plugin_prev, *plugin_next;
+	snd_pcm_sframes_t frames;
+	int stream = snd_pcm_plug_stream(plug);
+	
+	if (snd_BUG_ON(!plug))
+		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(struct snd_mask *mask, int format)
+{
+	struct snd_mask 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_U24_3LE | SNDRV_PCM_FMTBIT_S24_3LE |
+		       SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE |
+		       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_3LE,
+	SNDRV_PCM_FORMAT_S24_3BE,
+	SNDRV_PCM_FORMAT_U24_3LE,
+	SNDRV_PCM_FORMAT_U24_3BE,
+	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, struct snd_mask *format_mask)
+{
+	int i;
+
+	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)) {
+		unsigned int width = snd_pcm_format_width(format);
+		int unsignd = snd_pcm_format_unsigned(format) > 0;
+		int big = snd_pcm_format_big_endian(format) > 0;
+		unsigned int badness, best = -1;
+		int best_format = -1;
+		for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) {
+			int f = preferred_formats[i];
+			unsigned int w;
+			if (!snd_mask_test(format_mask, f))
+				continue;
+			w = snd_pcm_format_width(f);
+			if (w >= width)
+				badness = w - width;
+			else
+				badness = width - w + 32;
+			badness += snd_pcm_format_unsigned(f) != unsignd;
+			badness += snd_pcm_format_big_endian(f) != big;
+			if (badness < best) {
+				best_format = f;
+				best = badness;
+			}
+		}
+		return best_format >= 0 ? best_format : -EINVAL;
+	} else {
+		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(struct snd_pcm_substream *plug,
+				struct snd_pcm_hw_params *params,
+				struct snd_pcm_hw_params *slave_params)
+{
+	struct snd_pcm_plugin_format tmpformat;
+	struct snd_pcm_plugin_format dstformat;
+	struct snd_pcm_plugin_format srcformat;
+	int src_access, dst_access;
+	struct snd_pcm_plugin *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 (! rate_match(srcformat.rate, dstformat.rate) &&
+	    ! snd_pcm_format_linear(srcformat.format)) {
+		if (srcformat.format != SNDRV_PCM_FORMAT_MU_LAW)
+			return -EINVAL;
+		tmpformat.format = SNDRV_PCM_FORMAT_S16;
+		err = snd_pcm_plugin_build_mulaw(plug,
+						 &srcformat, &tmpformat,
+						 &plugin);
+		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) {
+		tmpformat.channels = dstformat.channels;
+		err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin);
+		pdprintf("channels reduction: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, 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;
+	}
+
+	/* rate resampling */
+	if (!rate_match(srcformat.rate, dstformat.rate)) {
+		if (srcformat.format != SNDRV_PCM_FORMAT_S16) {
+			/* convert to S16 for resampling */
+			tmpformat.format = SNDRV_PCM_FORMAT_S16;
+			err = snd_pcm_plugin_build_linear(plug,
+							  &srcformat, &tmpformat,
+							  &plugin);
+			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;
+		}
+		tmpformat.rate = dstformat.rate;
+        	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)
+			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 (srcformat.format == SNDRV_PCM_FORMAT_MU_LAW ||
+		    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;
+	}
+
+	/* channels extension */
+	if (srcformat.channels < dstformat.channels) {
+		tmpformat.channels = dstformat.channels;
+		err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin);
+		pdprintf("channels extension: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, 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(struct snd_pcm_substream *plug,
+					 char *buf,
+					 snd_pcm_uframes_t count,
+					 struct snd_pcm_plugin_channel **channels)
+{
+	struct snd_pcm_plugin *plugin;
+	struct snd_pcm_plugin_channel *v;
+	struct snd_pcm_plugin_format *format;
+	int width, nchannels, channel;
+	int stream = snd_pcm_plug_stream(plug);
+
+	if (snd_BUG_ON(!buf))
+		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;
+	if (snd_BUG_ON(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;
+}
+
+snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *src_channels, snd_pcm_uframes_t size)
+{
+	struct snd_pcm_plugin *plugin, *next;
+	struct snd_pcm_plugin_channel *dst_channels;
+	int err;
+	snd_pcm_sframes_t frames = size;
+
+	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(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *dst_channels_final, snd_pcm_uframes_t size)
+{
+	struct snd_pcm_plugin *plugin, *next;
+	struct snd_pcm_plugin_channel *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;
+		} 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 struct snd_pcm_channel_area *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 struct snd_pcm_channel_area *src_area, size_t src_offset,
+		      const struct snd_pcm_channel_area *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/linux/sound/core/oss/pcm_plugin.h b/linux/sound/core/oss/pcm_plugin.h
new file mode 100644
index 0000000..b9afab6
--- /dev/null
+++ b/linux/sound/core/oss/pcm_plugin.h
@@ -0,0 +1,184 @@
+#ifndef __PCM_PLUGIN_H
+#define __PCM_PLUGIN_H
+
+/*
+ *  Digital Audio (Plugin interface) abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifdef CONFIG_SND_PCM_OSS_PLUGINS
+
+#define snd_pcm_plug_stream(plug) ((plug)->stream)
+
+enum snd_pcm_plugin_action {
+	INIT = 0,
+	PREPARE = 1,
+};
+
+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 */
+};
+
+struct snd_pcm_plugin_channel {
+	void *aptr;			/* pointer to the allocated area */
+	struct snd_pcm_channel_area area;
+	snd_pcm_uframes_t frames;	/* allocated frames */
+	unsigned int enabled:1;		/* channel need to be processed */
+	unsigned int wanted:1;		/* channel is wanted */
+};
+
+struct snd_pcm_plugin_format {
+	int format;
+	unsigned int rate;
+	unsigned int channels;
+};
+
+struct snd_pcm_plugin {
+	const char *name;		/* plug-in name */
+	int stream;
+	struct snd_pcm_plugin_format src_format;	/* source format */
+	struct snd_pcm_plugin_format 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)(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t dst_frames);
+	snd_pcm_sframes_t (*dst_frames)(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t src_frames);
+	snd_pcm_sframes_t (*client_channels)(struct snd_pcm_plugin *plugin,
+					     snd_pcm_uframes_t frames,
+					     struct snd_pcm_plugin_channel **channels);
+	snd_pcm_sframes_t (*transfer)(struct snd_pcm_plugin *plugin,
+				      const struct snd_pcm_plugin_channel *src_channels,
+				      struct snd_pcm_plugin_channel *dst_channels,
+				      snd_pcm_uframes_t frames);
+	int (*action)(struct snd_pcm_plugin *plugin,
+		      enum snd_pcm_plugin_action action,
+		      unsigned long data);
+	struct snd_pcm_plugin *prev;
+	struct snd_pcm_plugin *next;
+	struct snd_pcm_substream *plug;
+	void *private_data;
+	void (*private_free)(struct snd_pcm_plugin *plugin);
+	char *buf;
+	snd_pcm_uframes_t buf_frames;
+	struct snd_pcm_plugin_channel *buf_channels;
+	char extra_data[0];
+};
+
+int snd_pcm_plugin_build(struct snd_pcm_substream *handle,
+                         const char *name,
+                         struct snd_pcm_plugin_format *src_format,
+                         struct snd_pcm_plugin_format *dst_format,
+                         size_t extra,
+                         struct snd_pcm_plugin **ret);
+int snd_pcm_plugin_free(struct snd_pcm_plugin *plugin);
+int snd_pcm_plugin_clear(struct snd_pcm_plugin **first);
+int snd_pcm_plug_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t frames);
+snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t drv_size);
+snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t clt_size);
+
+#define FULL ROUTE_PLUGIN_RESOLUTION
+#define HALF ROUTE_PLUGIN_RESOLUTION / 2
+
+int snd_pcm_plugin_build_io(struct snd_pcm_substream *handle,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_pcm_plugin **r_plugin);
+int snd_pcm_plugin_build_linear(struct snd_pcm_substream *handle,
+				struct snd_pcm_plugin_format *src_format,
+				struct snd_pcm_plugin_format *dst_format,
+				struct snd_pcm_plugin **r_plugin);
+int snd_pcm_plugin_build_mulaw(struct snd_pcm_substream *handle,
+			       struct snd_pcm_plugin_format *src_format,
+			       struct snd_pcm_plugin_format *dst_format,
+			       struct snd_pcm_plugin **r_plugin);
+int snd_pcm_plugin_build_rate(struct snd_pcm_substream *handle,
+			      struct snd_pcm_plugin_format *src_format,
+			      struct snd_pcm_plugin_format *dst_format,
+			      struct snd_pcm_plugin **r_plugin);
+int snd_pcm_plugin_build_route(struct snd_pcm_substream *handle,
+			       struct snd_pcm_plugin_format *src_format,
+			       struct snd_pcm_plugin_format *dst_format,
+		               struct snd_pcm_plugin **r_plugin);
+int snd_pcm_plugin_build_copy(struct snd_pcm_substream *handle,
+			      struct snd_pcm_plugin_format *src_format,
+			      struct snd_pcm_plugin_format *dst_format,
+			      struct snd_pcm_plugin **r_plugin);
+
+int snd_pcm_plug_format_plugins(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_pcm_hw_params *slave_params);
+
+int snd_pcm_plug_slave_format(int format, struct snd_mask *format_mask);
+
+int snd_pcm_plugin_append(struct snd_pcm_plugin *plugin);
+
+snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *handle,
+					      struct snd_pcm_plugin_channel *src_channels,
+					      snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_plug_read_transfer(struct snd_pcm_substream *handle,
+					     struct snd_pcm_plugin_channel *dst_channels_final,
+					     snd_pcm_uframes_t size);
+
+snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(struct snd_pcm_substream *handle,
+						   char *buf, snd_pcm_uframes_t count,
+						   struct snd_pcm_plugin_channel **channels);
+
+snd_pcm_sframes_t snd_pcm_plugin_client_channels(struct snd_pcm_plugin *plugin,
+						 snd_pcm_uframes_t frames,
+						 struct snd_pcm_plugin_channel **channels);
+
+int snd_pcm_area_silence(const struct snd_pcm_channel_area *dst_channel,
+			 size_t dst_offset,
+			 size_t samples, int format);
+int snd_pcm_area_copy(const struct snd_pcm_channel_area *src_channel,
+		      size_t src_offset,
+		      const struct snd_pcm_channel_area *dst_channel,
+		      size_t dst_offset,
+		      size_t samples, int format);
+
+void *snd_pcm_plug_buf_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t size);
+void snd_pcm_plug_buf_unlock(struct snd_pcm_substream *plug, void *ptr);
+snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream,
+				     const char *ptr, snd_pcm_uframes_t size,
+				     int in_kernel);
+snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream,
+				    char *ptr, snd_pcm_uframes_t size, int in_kernel);
+snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream,
+				      void **bufs, snd_pcm_uframes_t frames,
+				      int in_kernel);
+snd_pcm_sframes_t snd_pcm_oss_readv3(struct snd_pcm_substream *substream,
+				     void **bufs, snd_pcm_uframes_t frames,
+				     int in_kernel);
+
+#else
+
+static inline snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t drv_size) { return drv_size; }
+static inline snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t clt_size) { return clt_size; }
+static inline int snd_pcm_plug_slave_format(int format, struct snd_mask *format_mask) { return format; }
+
+#endif
+
+#ifdef PLUGIN_DEBUG
+#define pdprintf(fmt, args...) printk(KERN_DEBUG "plugin: " fmt, ##args)
+#else
+#define pdprintf(fmt, args...)
+#endif
+
+#endif				/* __PCM_PLUGIN_H */
diff --git a/linux/sound/core/oss/rate.c b/linux/sound/core/oss/rate.c
new file mode 100644
index 0000000..2fa9299
--- /dev/null
+++ b/linux/sound/core/oss/rate.c
@@ -0,0 +1,348 @@
+/*
+ *  Rate conversion Plug-In
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@perex.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 <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
+ */
+
+struct rate_channel {
+	signed short last_S1;
+	signed short last_S2;
+};
+ 
+typedef void (*rate_f)(struct snd_pcm_plugin *plugin,
+		       const struct snd_pcm_plugin_channel *src_channels,
+		       struct snd_pcm_plugin_channel *dst_channels,
+		       int src_frames, int dst_frames);
+
+struct rate_priv {
+	unsigned int pitch;
+	unsigned int pos;
+	rate_f func;
+	snd_pcm_sframes_t old_src_frames, old_dst_frames;
+	struct rate_channel channels[0];
+};
+
+static void rate_init(struct snd_pcm_plugin *plugin)
+{
+	unsigned int channel;
+	struct rate_priv *data = (struct rate_priv *)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(struct snd_pcm_plugin *plugin,
+			    const struct snd_pcm_plugin_channel *src_channels,
+			    struct snd_pcm_plugin_channel *dst_channels,
+			    int src_frames, int dst_frames)
+{
+	unsigned int pos = 0;
+	signed int val;
+	signed short S1, S2;
+	signed short *src, *dst;
+	unsigned int channel;
+	int src_step, dst_step;
+	int src_frames1, dst_frames1;
+	struct rate_priv *data = (struct rate_priv *)plugin->extra_data;
+	struct rate_channel *rchannels = data->channels;
+	
+	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 = (signed short *)src_channels[channel].area.addr +
+			src_channels[channel].area.first / 8 / 2;
+		dst = (signed short *)dst_channels[channel].area.addr +
+			dst_channels[channel].area.first / 8 / 2;
+		src_step = src_channels[channel].area.step / 8 / 2;
+		dst_step = dst_channels[channel].area.step / 8 / 2;
+		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) {
+					S2 = *src;
+					src += src_step;
+				}
+			}
+			val = S1 + ((S2 - S1) * (signed int)pos) / BITS;
+			if (val < -32768)
+				val = -32768;
+			else if (val > 32767)
+				val = 32767;
+			*dst = val;
+			dst += dst_step;
+			pos += data->pitch;
+		}
+		rchannels->last_S1 = S1;
+		rchannels->last_S2 = S2;
+		rchannels++;
+	}
+	data->pos = pos;
+}
+
+static void resample_shrink(struct snd_pcm_plugin *plugin,
+			    const struct snd_pcm_plugin_channel *src_channels,
+			    struct snd_pcm_plugin_channel *dst_channels,
+			    int src_frames, int dst_frames)
+{
+	unsigned int pos = 0;
+	signed int val;
+	signed short S1, S2;
+	signed short *src, *dst;
+	unsigned int channel;
+	int src_step, dst_step;
+	int src_frames1, dst_frames1;
+	struct rate_priv *data = (struct rate_priv *)plugin->extra_data;
+	struct rate_channel *rchannels = data->channels;
+
+	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 = (signed short *)src_channels[channel].area.addr +
+			src_channels[channel].area.first / 8 / 2;
+		dst = (signed short *)dst_channels[channel].area.addr +
+			dst_channels[channel].area.first / 8 / 2;
+		src_step = src_channels[channel].area.step / 8 / 2;
+		dst_step = dst_channels[channel].area.step / 8 / 2;
+		src_frames1 = src_frames;
+		dst_frames1 = dst_frames;
+		while (dst_frames1 > 0) {
+			S1 = S2;
+			if (src_frames1-- > 0) {
+				S2 = *src;
+				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;
+				*dst = val;
+				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(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames)
+{
+	struct rate_priv *data;
+	snd_pcm_sframes_t res;
+
+	if (snd_BUG_ON(!plugin))
+		return -ENXIO;
+	if (frames == 0)
+		return 0;
+	data = (struct rate_priv *)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(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames)
+{
+	struct rate_priv *data;
+	snd_pcm_sframes_t res;
+
+	if (snd_BUG_ON(!plugin))
+		return -ENXIO;
+	if (frames == 0)
+		return 0;
+	data = (struct rate_priv *)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(struct snd_pcm_plugin *plugin,
+			     const struct snd_pcm_plugin_channel *src_channels,
+			     struct snd_pcm_plugin_channel *dst_channels,
+			     snd_pcm_uframes_t frames)
+{
+	snd_pcm_uframes_t dst_frames;
+	struct rate_priv *data;
+
+	if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
+		return -ENXIO;
+	if (frames == 0)
+		return 0;
+#ifdef CONFIG_SND_DEBUG
+	{
+		unsigned int channel;
+		for (channel = 0; channel < plugin->src_format.channels; channel++) {
+			if (snd_BUG_ON(src_channels[channel].area.first % 8 ||
+				       src_channels[channel].area.step % 8))
+				return -ENXIO;
+			if (snd_BUG_ON(dst_channels[channel].area.first % 8 ||
+				       dst_channels[channel].area.step % 8))
+				return -ENXIO;
+		}
+	}
+#endif
+
+	dst_frames = rate_dst_frames(plugin, frames);
+	if (dst_frames > dst_channels[0].frames)
+		dst_frames = dst_channels[0].frames;
+	data = (struct rate_priv *)plugin->extra_data;
+	data->func(plugin, src_channels, dst_channels, frames, dst_frames);
+	return dst_frames;
+}
+
+static int rate_action(struct snd_pcm_plugin *plugin,
+		       enum snd_pcm_plugin_action action,
+		       unsigned long udata)
+{
+	if (snd_BUG_ON(!plugin))
+		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(struct snd_pcm_substream *plug,
+			      struct snd_pcm_plugin_format *src_format,
+			      struct snd_pcm_plugin_format *dst_format,
+			      struct snd_pcm_plugin **r_plugin)
+{
+	int err;
+	struct rate_priv *data;
+	struct snd_pcm_plugin *plugin;
+
+	if (snd_BUG_ON(!r_plugin))
+		return -ENXIO;
+	*r_plugin = NULL;
+
+	if (snd_BUG_ON(src_format->channels != dst_format->channels))
+		return -ENXIO;
+	if (snd_BUG_ON(src_format->channels <= 0))
+		return -ENXIO;
+	if (snd_BUG_ON(src_format->format != SNDRV_PCM_FORMAT_S16))
+		return -ENXIO;
+	if (snd_BUG_ON(dst_format->format != SNDRV_PCM_FORMAT_S16))
+		return -ENXIO;
+	if (snd_BUG_ON(src_format->rate == dst_format->rate))
+		return -ENXIO;
+
+	err = snd_pcm_plugin_build(plug, "rate conversion",
+				   src_format, dst_format,
+				   sizeof(struct rate_priv) +
+				   src_format->channels * sizeof(struct rate_channel),
+				   &plugin);
+	if (err < 0)
+		return err;
+	data = (struct rate_priv *)plugin->extra_data;
+	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/linux/sound/core/oss/route.c b/linux/sound/core/oss/route.c
new file mode 100644
index 0000000..bbe25d8
--- /dev/null
+++ b/linux/sound/core/oss/route.c
@@ -0,0 +1,109 @@
+/*
+ *  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 <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "pcm_plugin.h"
+
+static void zero_areas(struct snd_pcm_plugin_channel *dvp, int ndsts,
+		       snd_pcm_uframes_t frames, int format)
+{
+	int dst = 0;
+	for (; dst < ndsts; ++dst) {
+		if (dvp->wanted)
+			snd_pcm_area_silence(&dvp->area, 0, frames, format);
+		dvp->enabled = 0;
+		dvp++;
+	}
+}
+
+static inline void copy_area(const struct snd_pcm_plugin_channel *src_channel,
+			     struct snd_pcm_plugin_channel *dst_channel,
+			     snd_pcm_uframes_t frames, int format)
+{
+	dst_channel->enabled = 1;
+	snd_pcm_area_copy(&src_channel->area, 0, &dst_channel->area, 0, frames, format);
+}
+
+static snd_pcm_sframes_t route_transfer(struct snd_pcm_plugin *plugin,
+					const struct snd_pcm_plugin_channel *src_channels,
+					struct snd_pcm_plugin_channel *dst_channels,
+					snd_pcm_uframes_t frames)
+{
+	int nsrcs, ndsts, dst;
+	struct snd_pcm_plugin_channel *dvp;
+	int format;
+
+	if (snd_BUG_ON(!plugin || !src_channels || !dst_channels))
+		return -ENXIO;
+	if (frames == 0)
+		return 0;
+
+	nsrcs = plugin->src_format.channels;
+	ndsts = plugin->dst_format.channels;
+
+	format = plugin->dst_format.format;
+	dvp = dst_channels;
+	if (nsrcs <= 1) {
+		/* expand to all channels */
+		for (dst = 0; dst < ndsts; ++dst) {
+			copy_area(src_channels, dvp, frames, format);
+			dvp++;
+		}
+		return frames;
+	}
+
+	for (dst = 0; dst < ndsts && dst < nsrcs; ++dst) {
+		copy_area(src_channels, dvp, frames, format);
+		dvp++;
+		src_channels++;
+	}
+	if (dst < ndsts)
+		zero_areas(dvp, ndsts - dst, frames, format);
+	return frames;
+}
+
+int snd_pcm_plugin_build_route(struct snd_pcm_substream *plug,
+			       struct snd_pcm_plugin_format *src_format,
+			       struct snd_pcm_plugin_format *dst_format,
+			       struct snd_pcm_plugin **r_plugin)
+{
+	struct snd_pcm_plugin *plugin;
+	int err;
+
+	if (snd_BUG_ON(!r_plugin))
+		return -ENXIO;
+	*r_plugin = NULL;
+	if (snd_BUG_ON(src_format->rate != dst_format->rate))
+		return -ENXIO;
+	if (snd_BUG_ON(src_format->format != dst_format->format))
+		return -ENXIO;
+
+	err = snd_pcm_plugin_build(plug, "route conversion",
+				   src_format, dst_format, 0, &plugin);
+	if (err < 0)
+		return err;
+
+	plugin->transfer = route_transfer;
+	*r_plugin = plugin;
+	return 0;
+}
diff --git a/linux/sound/core/pcm.c b/linux/sound/core/pcm.c
new file mode 100644
index 0000000..6b4b128
--- /dev/null
+++ b/linux/sound/core/pcm.c
@@ -0,0 +1,1170 @@
+/*
+ *  Digital Audio (PCM) abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/mutex.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@perex.cz>, Abramo Bagnara <abramo@alsa-project.org>");
+MODULE_DESCRIPTION("Midlevel PCM code for ALSA.");
+MODULE_LICENSE("GPL");
+
+static LIST_HEAD(snd_pcm_devices);
+static LIST_HEAD(snd_pcm_notify_list);
+static DEFINE_MUTEX(register_mutex);
+
+static int snd_pcm_free(struct snd_pcm *pcm);
+static int snd_pcm_dev_free(struct snd_device *device);
+static int snd_pcm_dev_register(struct snd_device *device);
+static int snd_pcm_dev_disconnect(struct snd_device *device);
+
+static struct snd_pcm *snd_pcm_get(struct snd_card *card, int device)
+{
+	struct snd_pcm *pcm;
+
+	list_for_each_entry(pcm, &snd_pcm_devices, list) {
+		if (pcm->card == card && pcm->device == device)
+			return pcm;
+	}
+	return NULL;
+}
+
+static int snd_pcm_next(struct snd_card *card, int device)
+{
+	struct snd_pcm *pcm;
+
+	list_for_each_entry(pcm, &snd_pcm_devices, list) {
+		if (pcm->card == card && pcm->device > device)
+			return pcm->device;
+		else if (pcm->card->number > card->number)
+			return -1;
+	}
+	return -1;
+}
+
+static int snd_pcm_add(struct snd_pcm *newpcm)
+{
+	struct snd_pcm *pcm;
+
+	list_for_each_entry(pcm, &snd_pcm_devices, list) {
+		if (pcm->card == newpcm->card && pcm->device == newpcm->device)
+			return -EBUSY;
+		if (pcm->card->number > newpcm->card->number ||
+				(pcm->card == newpcm->card &&
+				pcm->device > newpcm->device)) {
+			list_add(&newpcm->list, pcm->list.prev);
+			return 0;
+		}
+	}
+	list_add_tail(&newpcm->list, &snd_pcm_devices);
+	return 0;
+}
+
+static int snd_pcm_control_ioctl(struct snd_card *card,
+				 struct snd_ctl_file *control,
+				 unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	case SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE:
+		{
+			int device;
+
+			if (get_user(device, (int __user *)arg))
+				return -EFAULT;
+			mutex_lock(&register_mutex);
+			device = snd_pcm_next(card, device);
+			mutex_unlock(&register_mutex);
+			if (put_user(device, (int __user *)arg))
+				return -EFAULT;
+			return 0;
+		}
+	case SNDRV_CTL_IOCTL_PCM_INFO:
+		{
+			struct snd_pcm_info __user *info;
+			unsigned int device, subdevice;
+			int stream;
+			struct snd_pcm *pcm;
+			struct snd_pcm_str *pstr;
+			struct snd_pcm_substream *substream;
+			int err;
+
+			info = (struct snd_pcm_info __user *)arg;
+			if (get_user(device, &info->device))
+				return -EFAULT;
+			if (get_user(stream, &info->stream))
+				return -EFAULT;
+			if (stream < 0 || stream > 1)
+				return -EINVAL;
+			if (get_user(subdevice, &info->subdevice))
+				return -EFAULT;
+			mutex_lock(&register_mutex);
+			pcm = snd_pcm_get(card, device);
+			if (pcm == NULL) {
+				err = -ENXIO;
+				goto _error;
+			}
+			pstr = &pcm->streams[stream];
+			if (pstr->substream_count == 0) {
+				err = -ENOENT;
+				goto _error;
+			}
+			if (subdevice >= pstr->substream_count) {
+				err = -ENXIO;
+				goto _error;
+			}
+			for (substream = pstr->substream; substream;
+			     substream = substream->next)
+				if (substream->number == (int)subdevice)
+					break;
+			if (substream == NULL) {
+				err = -ENXIO;
+				goto _error;
+			}
+			err = snd_pcm_info_user(substream, info);
+		_error:
+			mutex_unlock(&register_mutex);
+			return err;
+		}
+	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 FORMAT(v) [SNDRV_PCM_FORMAT_##v] = #v
+
+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),
+	FORMAT(G723_24),
+	FORMAT(G723_24_1B),
+	FORMAT(G723_40),
+	FORMAT(G723_40_1B),
+};
+
+const char *snd_pcm_format_name(snd_pcm_format_t format)
+{
+	if (format >= ARRAY_SIZE(snd_pcm_format_names))
+		return "Unknown";
+	return snd_pcm_format_names[format];
+}
+EXPORT_SYMBOL_GPL(snd_pcm_format_name);
+
+#ifdef CONFIG_SND_VERBOSE_PROCFS
+
+#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 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_subformat_names[] = {
+	SUBFORMAT(STD), 
+};
+
+static char *snd_pcm_tstamp_mode_names[] = {
+	TSTAMP(NONE),
+	TSTAMP(ENABLE),
+};
+
+static const char *snd_pcm_stream_name(int stream)
+{
+	return snd_pcm_stream_names[stream];
+}
+
+static const char *snd_pcm_access_name(snd_pcm_access_t access)
+{
+	return snd_pcm_access_names[access];
+}
+
+static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat)
+{
+	return snd_pcm_subformat_names[subformat];
+}
+
+static const char *snd_pcm_tstamp_mode_name(int mode)
+{
+	return snd_pcm_tstamp_mode_names[mode];
+}
+
+static const char *snd_pcm_state_name(snd_pcm_state_t state)
+{
+	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
+
+static void snd_pcm_proc_info_read(struct snd_pcm_substream *substream,
+				   struct snd_info_buffer *buffer)
+{
+	struct snd_pcm_info *info;
+	int err;
+
+	if (! 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(struct snd_info_entry *entry,
+					  struct snd_info_buffer *buffer)
+{
+	snd_pcm_proc_info_read(((struct snd_pcm_str *)entry->private_data)->substream,
+			       buffer);
+}
+
+static void snd_pcm_substream_proc_info_read(struct snd_info_entry *entry,
+					     struct snd_info_buffer *buffer)
+{
+	snd_pcm_proc_info_read(entry->private_data, buffer);
+}
+
+static void snd_pcm_substream_proc_hw_params_read(struct snd_info_entry *entry,
+						  struct snd_info_buffer *buffer)
+{
+	struct snd_pcm_substream *substream = entry->private_data;
+	struct snd_pcm_runtime *runtime;
+
+	mutex_lock(&substream->pcm->open_mutex);
+	runtime = substream->runtime;
+	if (!runtime) {
+		snd_iprintf(buffer, "closed\n");
+		goto unlock;
+	}
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		snd_iprintf(buffer, "no setup\n");
+		goto unlock;
+	}
+	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);	
+#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
+ unlock:
+	mutex_unlock(&substream->pcm->open_mutex);
+}
+
+static void snd_pcm_substream_proc_sw_params_read(struct snd_info_entry *entry,
+						  struct snd_info_buffer *buffer)
+{
+	struct snd_pcm_substream *substream = entry->private_data;
+	struct snd_pcm_runtime *runtime;
+
+	mutex_lock(&substream->pcm->open_mutex);
+	runtime = substream->runtime;
+	if (!runtime) {
+		snd_iprintf(buffer, "closed\n");
+		goto unlock;
+	}
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		snd_iprintf(buffer, "no setup\n");
+		goto unlock;
+	}
+	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, "avail_min: %lu\n", runtime->control->avail_min);
+	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);
+ unlock:
+	mutex_unlock(&substream->pcm->open_mutex);
+}
+
+static void snd_pcm_substream_proc_status_read(struct snd_info_entry *entry,
+					       struct snd_info_buffer *buffer)
+{
+	struct snd_pcm_substream *substream = entry->private_data;
+	struct snd_pcm_runtime *runtime;
+	struct snd_pcm_status status;
+	int err;
+
+	mutex_lock(&substream->pcm->open_mutex);
+	runtime = substream->runtime;
+	if (!runtime) {
+		snd_iprintf(buffer, "closed\n");
+		goto unlock;
+	}
+	memset(&status, 0, sizeof(status));
+	err = snd_pcm_status(substream, &status);
+	if (err < 0) {
+		snd_iprintf(buffer, "error %d\n", err);
+		goto unlock;
+	}
+	snd_iprintf(buffer, "state: %s\n", snd_pcm_state_name(status.state));
+	snd_iprintf(buffer, "owner_pid   : %d\n", pid_vnr(substream->pid));
+	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);
+ unlock:
+	mutex_unlock(&substream->pcm->open_mutex);
+}
+
+#ifdef CONFIG_SND_PCM_XRUN_DEBUG
+static void snd_pcm_xrun_debug_read(struct snd_info_entry *entry,
+				    struct snd_info_buffer *buffer)
+{
+	struct snd_pcm_str *pstr = entry->private_data;
+	snd_iprintf(buffer, "%d\n", pstr->xrun_debug);
+}
+
+static void snd_pcm_xrun_debug_write(struct snd_info_entry *entry,
+				     struct snd_info_buffer *buffer)
+{
+	struct snd_pcm_str *pstr = 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(struct snd_pcm_str *pstr)
+{
+	struct snd_pcm *pcm = pstr->pcm;
+	struct snd_info_entry *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, 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_PCM_XRUN_DEBUG
+	if ((entry = snd_info_create_card_entry(pcm->card, "xrun_debug",
+						pstr->proc_root)) != NULL) {
+		entry->c.text.read = snd_pcm_xrun_debug_read;
+		entry->c.text.write = snd_pcm_xrun_debug_write;
+		entry->mode |= S_IWUSR;
+		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(struct snd_pcm_str *pstr)
+{
+#ifdef CONFIG_SND_PCM_XRUN_DEBUG
+	snd_info_free_entry(pstr->proc_xrun_debug_entry);
+	pstr->proc_xrun_debug_entry = NULL;
+#endif
+	snd_info_free_entry(pstr->proc_info_entry);
+	pstr->proc_info_entry = NULL;
+	snd_info_free_entry(pstr->proc_root);
+	pstr->proc_root = NULL;
+	return 0;
+}
+
+static int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream)
+{
+	struct snd_info_entry *entry;
+	struct snd_card *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,
+				      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,
+				      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,
+				      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,
+				      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(struct snd_pcm_substream *substream)
+{
+	snd_info_free_entry(substream->proc_info_entry);
+	substream->proc_info_entry = NULL;
+	snd_info_free_entry(substream->proc_hw_params_entry);
+	substream->proc_hw_params_entry = NULL;
+	snd_info_free_entry(substream->proc_sw_params_entry);
+	substream->proc_sw_params_entry = NULL;
+	snd_info_free_entry(substream->proc_status_entry);
+	substream->proc_status_entry = NULL;
+	snd_info_free_entry(substream->proc_root);
+	substream->proc_root = NULL;
+	return 0;
+}
+#else /* !CONFIG_SND_VERBOSE_PROCFS */
+static inline int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) { return 0; }
+static inline int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr) { return 0; }
+static inline int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream) { return 0; }
+static inline int snd_pcm_substream_proc_done(struct snd_pcm_substream *substream) { return 0; }
+#endif /* CONFIG_SND_VERBOSE_PROCFS */
+
+/**
+ * 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(struct snd_pcm *pcm, int stream, int substream_count)
+{
+	int idx, err;
+	struct snd_pcm_str *pstr = &pcm->streams[stream];
+	struct snd_pcm_substream *substream, *prev;
+
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+	mutex_init(&pstr->oss.setup_mutex);
+#endif
+	pstr->stream = stream;
+	pstr->pcm = pcm;
+	pstr->substream_count = substream_count;
+	if (substream_count > 0) {
+		err = snd_pcm_stream_proc_init(pstr);
+		if (err < 0) {
+			snd_printk(KERN_ERR "Error in snd_pcm_stream_proc_init\n");
+			return err;
+		}
+	}
+	prev = NULL;
+	for (idx = 0, prev = NULL; idx < substream_count; idx++) {
+		substream = kzalloc(sizeof(*substream), GFP_KERNEL);
+		if (substream == NULL) {
+			snd_printk(KERN_ERR "Cannot allocate PCM substream\n");
+			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) {
+			snd_printk(KERN_ERR "Error in snd_pcm_stream_proc_init\n");
+			if (prev == NULL)
+				pstr->substream = NULL;
+			else
+				prev->next = NULL;
+			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);
+		atomic_set(&substream->mmap_count, 0);
+		prev = substream;
+	}
+	return 0;
+}				
+
+EXPORT_SYMBOL(snd_pcm_new_stream);
+
+/**
+ * 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(struct snd_card *card, const char *id, int device,
+		int playback_count, int capture_count,
+	        struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_pcm_dev_free,
+		.dev_register =	snd_pcm_dev_register,
+		.dev_disconnect = snd_pcm_dev_disconnect,
+	};
+
+	if (snd_BUG_ON(!card))
+		return -ENXIO;
+	if (rpcm)
+		*rpcm = NULL;
+	pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
+	if (pcm == NULL) {
+		snd_printk(KERN_ERR "Cannot allocate PCM\n");
+		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;
+	}
+	mutex_init(&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;
+	}
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_pcm_new);
+
+static void snd_pcm_free_stream(struct snd_pcm_str * pstr)
+{
+	struct snd_pcm_substream *substream, *substream_next;
+#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
+	struct snd_pcm_oss_setup *setup, *setupn;
+#endif
+	substream = pstr->substream;
+	while (substream) {
+		substream_next = substream->next;
+		snd_pcm_timer_done(substream);
+		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(struct snd_pcm *pcm)
+{
+	struct snd_pcm_notify *notify;
+
+	if (!pcm)
+		return 0;
+	list_for_each_entry(notify, &snd_pcm_notify_list, list) {
+		notify->n_unregister(pcm);
+	}
+	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(struct snd_device *device)
+{
+	struct snd_pcm *pcm = device->device_data;
+	return snd_pcm_free(pcm);
+}
+
+int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
+			     struct file *file,
+			     struct snd_pcm_substream **rsubstream)
+{
+	struct snd_pcm_str * pstr;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+	struct snd_ctl_file *kctl;
+	struct snd_card *card;
+	int prefer_subdevice = -1;
+	size_t size;
+
+	if (snd_BUG_ON(!pcm || !rsubstream))
+		return -ENXIO;
+	*rsubstream = NULL;
+	pstr = &pcm->streams[stream];
+	if (pstr->substream == NULL || pstr->substream_count == 0)
+		return -ENODEV;
+
+	card = pcm->card;
+	read_lock(&card->ctl_files_rwlock);
+	list_for_each_entry(kctl, &card->ctl_files, list) {
+		if (kctl->pid == task_pid(current)) {
+			prefer_subdevice = kctl->prefer_pcm_subdevice;
+			if (prefer_subdevice != -1)
+				break;
+		}
+	}
+	read_unlock(&card->ctl_files_rwlock);
+
+	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 (file->f_flags & O_APPEND) {
+		if (prefer_subdevice < 0) {
+			if (pstr->substream_count > 1)
+				return -EINVAL; /* must be unique */
+			substream = pstr->substream;
+		} else {
+			for (substream = pstr->substream; substream;
+			     substream = substream->next)
+				if (substream->number == prefer_subdevice)
+					break;
+		}
+		if (! substream)
+			return -ENODEV;
+		if (! SUBSTREAM_BUSY(substream))
+			return -EBADFD;
+		substream->ref_count++;
+		*rsubstream = substream;
+		return 0;
+	}
+
+	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 = kzalloc(sizeof(*runtime), GFP_KERNEL);
+	if (runtime == NULL)
+		return -ENOMEM;
+
+	size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));
+	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(struct snd_pcm_mmap_control));
+	runtime->control = snd_malloc_pages(size, GFP_KERNEL);
+	if (runtime->control == NULL) {
+		snd_free_pages((void*)runtime->status,
+			       PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)));
+		kfree(runtime);
+		return -ENOMEM;
+	}
+	memset((void*)runtime->control, 0, size);
+
+	init_waitqueue_head(&runtime->sleep);
+	init_waitqueue_head(&runtime->tsleep);
+
+	runtime->status->state = SNDRV_PCM_STATE_OPEN;
+
+	substream->runtime = runtime;
+	substream->private_data = pcm->private_data;
+	substream->ref_count = 1;
+	substream->f_flags = file->f_flags;
+	substream->pid = get_pid(task_pid(current));
+	pstr->substream_opened++;
+	*rsubstream = substream;
+	return 0;
+}
+
+void snd_pcm_detach_substream(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime;
+
+	if (PCM_RUNTIME_CHECK(substream))
+		return;
+	runtime = substream->runtime;
+	if (runtime->private_free != NULL)
+		runtime->private_free(runtime);
+	snd_free_pages((void*)runtime->status,
+		       PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)));
+	snd_free_pages((void*)runtime->control,
+		       PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)));
+	kfree(runtime->hw_constraints.rules);
+#ifdef CONFIG_SND_PCM_XRUN_DEBUG
+	if (runtime->hwptr_log)
+		kfree(runtime->hwptr_log);
+#endif
+	kfree(runtime);
+	substream->runtime = NULL;
+	put_pid(substream->pid);
+	substream->pid = NULL;
+	substream->pstr->substream_opened--;
+}
+
+static ssize_t show_pcm_class(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct snd_pcm *pcm;
+	const char *str;
+	static const char *strs[SNDRV_PCM_CLASS_LAST + 1] = {
+		[SNDRV_PCM_CLASS_GENERIC] = "generic",
+		[SNDRV_PCM_CLASS_MULTI] = "multi",
+		[SNDRV_PCM_CLASS_MODEM] = "modem",
+		[SNDRV_PCM_CLASS_DIGITIZER] = "digitizer",
+	};
+
+	if (! (pcm = dev_get_drvdata(dev)) ||
+	    pcm->dev_class > SNDRV_PCM_CLASS_LAST)
+		str = "none";
+	else
+		str = strs[pcm->dev_class];
+        return snprintf(buf, PAGE_SIZE, "%s\n", str);
+}
+
+static struct device_attribute pcm_attrs =
+	__ATTR(pcm_class, S_IRUGO, show_pcm_class, NULL);
+
+static int snd_pcm_dev_register(struct snd_device *device)
+{
+	int cidx, err;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_notify *notify;
+	char str[16];
+	struct snd_pcm *pcm;
+	struct device *dev;
+
+	if (snd_BUG_ON(!device || !device->device_data))
+		return -ENXIO;
+	pcm = device->device_data;
+	mutex_lock(&register_mutex);
+	err = snd_pcm_add(pcm);
+	if (err) {
+		mutex_unlock(&register_mutex);
+		return err;
+	}
+	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);
+			devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
+			break;
+		case SNDRV_PCM_STREAM_CAPTURE:
+			sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
+			devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
+			break;
+		}
+		/* device pointer to use, pcm->dev takes precedence if
+		 * it is assigned, otherwise fall back to card's device
+		 * if possible */
+		dev = pcm->dev;
+		if (!dev)
+			dev = snd_card_get_device_link(pcm->card);
+		/* register pcm */
+		err = snd_register_device_for_dev(devtype, pcm->card,
+						  pcm->device,
+						  &snd_pcm_f_ops[cidx],
+						  pcm, str, dev);
+		if (err < 0) {
+			list_del(&pcm->list);
+			mutex_unlock(&register_mutex);
+			return err;
+		}
+		snd_add_device_sysfs_file(devtype, pcm->card, pcm->device,
+					  &pcm_attrs);
+		for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
+			snd_pcm_timer_init(substream);
+	}
+
+	list_for_each_entry(notify, &snd_pcm_notify_list, list)
+		notify->n_register(pcm);
+
+	mutex_unlock(&register_mutex);
+	return 0;
+}
+
+static int snd_pcm_dev_disconnect(struct snd_device *device)
+{
+	struct snd_pcm *pcm = device->device_data;
+	struct snd_pcm_notify *notify;
+	struct snd_pcm_substream *substream;
+	int cidx, devtype;
+
+	mutex_lock(&register_mutex);
+	if (list_empty(&pcm->list))
+		goto unlock;
+
+	list_del_init(&pcm->list);
+	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_entry(notify, &snd_pcm_notify_list, list) {
+		notify->n_disconnect(pcm);
+	}
+	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);
+	}
+ unlock:
+	mutex_unlock(&register_mutex);
+	return 0;
+}
+
+int snd_pcm_notify(struct snd_pcm_notify *notify, int nfree)
+{
+	struct snd_pcm *pcm;
+
+	if (snd_BUG_ON(!notify ||
+		       !notify->n_register ||
+		       !notify->n_unregister ||
+		       !notify->n_disconnect))
+		return -EINVAL;
+	mutex_lock(&register_mutex);
+	if (nfree) {
+		list_del(&notify->list);
+		list_for_each_entry(pcm, &snd_pcm_devices, list)
+			notify->n_unregister(pcm);
+	} else {
+		list_add_tail(&notify->list, &snd_pcm_notify_list);
+		list_for_each_entry(pcm, &snd_pcm_devices, list)
+			notify->n_register(pcm);
+	}
+	mutex_unlock(&register_mutex);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_pcm_notify);
+
+#ifdef CONFIG_PROC_FS
+/*
+ *  Info interface
+ */
+
+static void snd_pcm_proc_read(struct snd_info_entry *entry,
+			      struct snd_info_buffer *buffer)
+{
+	struct snd_pcm *pcm;
+
+	mutex_lock(&register_mutex);
+	list_for_each_entry(pcm, &snd_pcm_devices, list) {
+		snd_iprintf(buffer, "%02i-%02i: %s : %s",
+			    pcm->card->number, pcm->device, 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");
+	}
+	mutex_unlock(&register_mutex);
+}
+
+static struct snd_info_entry *snd_pcm_proc_entry;
+
+static void snd_pcm_proc_init(void)
+{
+	struct snd_info_entry *entry;
+
+	if ((entry = snd_info_create_module_entry(THIS_MODULE, "pcm", NULL)) != NULL) {
+		snd_info_set_text_ops(entry, NULL, snd_pcm_proc_read);
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	snd_pcm_proc_entry = entry;
+}
+
+static void snd_pcm_proc_done(void)
+{
+	snd_info_free_entry(snd_pcm_proc_entry);
+}
+
+#else /* !CONFIG_PROC_FS */
+#define snd_pcm_proc_init()
+#define snd_pcm_proc_done()
+#endif /* CONFIG_PROC_FS */
+
+
+/*
+ *  ENTRY functions
+ */
+
+static int __init alsa_pcm_init(void)
+{
+	snd_ctl_register_ioctl(snd_pcm_control_ioctl);
+	snd_ctl_register_ioctl_compat(snd_pcm_control_ioctl);
+	snd_pcm_proc_init();
+	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);
+	snd_pcm_proc_done();
+}
+
+module_init(alsa_pcm_init)
+module_exit(alsa_pcm_exit)
diff --git a/linux/sound/core/pcm_compat.c b/linux/sound/core/pcm_compat.c
new file mode 100644
index 0000000..5fb2e28
--- /dev/null
+++ b/linux/sound/core/pcm_compat.c
@@ -0,0 +1,532 @@
+/*
+ *   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>
+#include <linux/slab.h>
+
+static int snd_pcm_ioctl_delay_compat(struct snd_pcm_substream *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(struct snd_pcm_substream *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(struct snd_pcm_substream *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 snd_pcm_hw_params32 {
+	u32 flags;
+	struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]; /* this must be identical */
+	struct snd_mask mres[5];	/* reserved masks */
+	struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
+	struct snd_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 snd_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];
+};
+
+/* recalcuate the boundary within 32bit */
+static snd_pcm_uframes_t recalculate_boundary(struct snd_pcm_runtime *runtime)
+{
+	snd_pcm_uframes_t boundary;
+
+	if (! runtime->buffer_size)
+		return 0;
+	boundary = runtime->buffer_size;
+	while (boundary * 2 <= 0x7fffffffUL - runtime->buffer_size)
+		boundary *= 2;
+	return boundary;
+}
+
+static int snd_pcm_ioctl_sw_params_compat(struct snd_pcm_substream *substream,
+					  struct snd_pcm_sw_params32 __user *src)
+{
+	struct snd_pcm_sw_params params;
+	snd_pcm_uframes_t boundary;
+	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;
+	/*
+	 * Check silent_size parameter.  Since we have 64bit boundary,
+	 * silence_size must be compared with the 32bit boundary.
+	 */
+	boundary = recalculate_boundary(substream->runtime);
+	if (boundary && params.silence_size >= boundary)
+		params.silence_size = substream->runtime->boundary;
+	err = snd_pcm_sw_params(substream, &params);
+	if (err < 0)
+		return err;
+	if (boundary && put_user(boundary, &src->boundary))
+		return -EFAULT;
+	return err;
+}
+
+struct snd_pcm_channel_info32 {
+	u32 channel;
+	u32 offset;
+	u32 first;
+	u32 step;
+};
+
+static int snd_pcm_ioctl_channel_info_compat(struct snd_pcm_substream *substream,
+					     struct snd_pcm_channel_info32 __user *src)
+{
+	struct snd_pcm_channel_info 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 snd_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(struct snd_pcm_substream *substream,
+				      struct snd_pcm_status32 __user *src)
+{
+	struct snd_pcm_status 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;
+}
+
+/* both for HW_PARAMS and HW_REFINE */
+static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream,
+					  int refine, 
+					  struct snd_pcm_hw_params32 __user *data32)
+{
+	struct snd_pcm_hw_params *data;
+	struct snd_pcm_runtime *runtime;
+	int err;
+
+	if (! (runtime = substream->runtime))
+		return -ENOTTY;
+
+	/* only fifo_size is different, so just copy all */
+	data = memdup_user(data32, sizeof(*data32));
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	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) {
+		unsigned int new_boundary = recalculate_boundary(runtime);
+		if (new_boundary)
+			runtime->boundary = new_boundary;
+	}
+ error:
+	kfree(data);
+	return err;
+}
+
+
+/*
+ */
+struct snd_xferi32 {
+	s32 result;
+	u32 buf;
+	u32 frames;
+};
+
+static int snd_pcm_ioctl_xferi_compat(struct snd_pcm_substream *substream,
+				      int dir, struct snd_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 snd_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(struct snd_pcm_substream *substream,
+				      int dir, struct snd_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 snd_pcm_mmap_status32 {
+	s32 state;
+	s32 pad1;
+	u32 hw_ptr;
+	struct compat_timespec tstamp;
+	s32 suspended_state;
+} __attribute__((packed));
+
+struct snd_pcm_mmap_control32 {
+	u32 appl_ptr;
+	u32 avail_min;
+};
+
+struct snd_pcm_sync_ptr32 {
+	u32 flags;
+	union {
+		struct snd_pcm_mmap_status32 status;
+		unsigned char reserved[64];
+	} s;
+	union {
+		struct snd_pcm_mmap_control32 control;
+		unsigned char reserved[64];
+	} c;
+} __attribute__((packed));
+
+static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream,
+					 struct snd_pcm_sync_ptr32 __user *src)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	volatile struct snd_pcm_mmap_status *status;
+	volatile struct snd_pcm_mmap_control *control;
+	u32 sflags;
+	struct snd_pcm_mmap_control scontrol;
+	struct snd_pcm_mmap_status sstatus;
+	snd_pcm_uframes_t boundary;
+	int err;
+
+	if (snd_BUG_ON(!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;
+	boundary = recalculate_boundary(runtime);
+	if (! boundary)
+		boundary = 0x7fffffff;
+	snd_pcm_stream_lock_irq(substream);
+	/* FIXME: we should consider the boundary for the sync from app */
+	if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL))
+		control->appl_ptr = scontrol.appl_ptr;
+	else
+		scontrol.appl_ptr = control->appl_ptr % boundary;
+	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 % boundary;
+	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 snd_pcm_hw_params32),
+	SNDRV_PCM_IOCTL_HW_PARAMS32 = _IOWR('A', 0x11, struct snd_pcm_hw_params32),
+	SNDRV_PCM_IOCTL_SW_PARAMS32 = _IOWR('A', 0x13, struct snd_pcm_sw_params32),
+	SNDRV_PCM_IOCTL_STATUS32 = _IOR('A', 0x20, struct snd_pcm_status32),
+	SNDRV_PCM_IOCTL_DELAY32 = _IOR('A', 0x21, s32),
+	SNDRV_PCM_IOCTL_CHANNEL_INFO32 = _IOR('A', 0x32, struct snd_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 snd_xferi32),
+	SNDRV_PCM_IOCTL_READI_FRAMES32 = _IOR('A', 0x51, struct snd_xferi32),
+	SNDRV_PCM_IOCTL_WRITEN_FRAMES32 = _IOW('A', 0x52, struct snd_xfern32),
+	SNDRV_PCM_IOCTL_READN_FRAMES32 = _IOR('A', 0x53, struct snd_xfern32),
+	SNDRV_PCM_IOCTL_SYNC_PTR32 = _IOWR('A', 0x23, struct snd_pcm_sync_ptr32),
+
+};
+
+static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct snd_pcm_file *pcm_file;
+	struct snd_pcm_substream *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.
+	 */
+	pcm_file->no_compat_mmap = 1;
+
+	switch (cmd) {
+	case SNDRV_PCM_IOCTL_PVERSION:
+	case SNDRV_PCM_IOCTL_INFO:
+	case SNDRV_PCM_IOCTL_TSTAMP:
+	case SNDRV_PCM_IOCTL_TTSTAMP:
+	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(file, substream, cmd, argp);
+		else
+			return snd_pcm_capture_ioctl1(file, 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/linux/sound/core/pcm_lib.c b/linux/sound/core/pcm_lib.c
new file mode 100644
index 0000000..11446a1
--- /dev/null
+++ b/linux/sound/core/pcm_lib.c
@@ -0,0 +1,2206 @@
+/*
+ *  Digital Audio (PCM) abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.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 <linux/slab.h>
+#include <linux/time.h>
+#include <linux/math64.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(struct snd_pcm_substream *substream, snd_pcm_uframes_t new_hw_ptr)
+{
+	struct snd_pcm_runtime *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;
+		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);
+			if (avail > runtime->buffer_size)
+				avail = runtime->buffer_size;
+			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 = new_hw_ptr;
+			} else {
+				runtime->silence_start = ofs;
+			}
+		}
+		frames = runtime->buffer_size - runtime->silence_filled;
+	}
+	if (snd_BUG_ON(frames > runtime->buffer_size))
+		return;
+	if (frames == 0)
+		return;
+	ofs = runtime->silence_start % 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_BUG_ON(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_BUG_ON(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 pcm_debug_name(struct snd_pcm_substream *substream,
+			   char *name, size_t len)
+{
+	snprintf(name, len, "pcmC%dD%d%c:%d",
+		 substream->pcm->card->number,
+		 substream->pcm->device,
+		 substream->stream ? 'c' : 'p',
+		 substream->number);
+}
+
+#define XRUN_DEBUG_BASIC	(1<<0)
+#define XRUN_DEBUG_STACK	(1<<1)	/* dump also stack */
+#define XRUN_DEBUG_JIFFIESCHECK	(1<<2)	/* do jiffies check */
+#define XRUN_DEBUG_PERIODUPDATE	(1<<3)	/* full period update info */
+#define XRUN_DEBUG_HWPTRUPDATE	(1<<4)	/* full hwptr update info */
+#define XRUN_DEBUG_LOG		(1<<5)	/* show last 10 positions on err */
+#define XRUN_DEBUG_LOGONCE	(1<<6)	/* do above only once */
+
+#ifdef CONFIG_SND_PCM_XRUN_DEBUG
+
+#define xrun_debug(substream, mask) \
+			((substream)->pstr->xrun_debug & (mask))
+#else
+#define xrun_debug(substream, mask)	0
+#endif
+
+#define dump_stack_on_xrun(substream) do {			\
+		if (xrun_debug(substream, XRUN_DEBUG_STACK))	\
+			dump_stack();				\
+	} while (0)
+
+static void xrun(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE)
+		snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp);
+	snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+	if (xrun_debug(substream, XRUN_DEBUG_BASIC)) {
+		char name[16];
+		pcm_debug_name(substream, name, sizeof(name));
+		snd_printd(KERN_DEBUG "XRUN: %s\n", name);
+		dump_stack_on_xrun(substream);
+	}
+}
+
+#ifdef CONFIG_SND_PCM_XRUN_DEBUG
+#define hw_ptr_error(substream, fmt, args...)				\
+	do {								\
+		if (xrun_debug(substream, XRUN_DEBUG_BASIC)) {		\
+			xrun_log_show(substream);			\
+			if (printk_ratelimit()) {			\
+				snd_printd("PCM: " fmt, ##args);	\
+			}						\
+			dump_stack_on_xrun(substream);			\
+		}							\
+	} while (0)
+
+#define XRUN_LOG_CNT	10
+
+struct hwptr_log_entry {
+	unsigned long jiffies;
+	snd_pcm_uframes_t pos;
+	snd_pcm_uframes_t period_size;
+	snd_pcm_uframes_t buffer_size;
+	snd_pcm_uframes_t old_hw_ptr;
+	snd_pcm_uframes_t hw_ptr_base;
+};
+
+struct snd_pcm_hwptr_log {
+	unsigned int idx;
+	unsigned int hit: 1;
+	struct hwptr_log_entry entries[XRUN_LOG_CNT];
+};
+
+static void xrun_log(struct snd_pcm_substream *substream,
+		     snd_pcm_uframes_t pos)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_pcm_hwptr_log *log = runtime->hwptr_log;
+	struct hwptr_log_entry *entry;
+
+	if (log == NULL) {
+		log = kzalloc(sizeof(*log), GFP_ATOMIC);
+		if (log == NULL)
+			return;
+		runtime->hwptr_log = log;
+	} else {
+		if (xrun_debug(substream, XRUN_DEBUG_LOGONCE) && log->hit)
+			return;
+	}
+	entry = &log->entries[log->idx];
+	entry->jiffies = jiffies;
+	entry->pos = pos;
+	entry->period_size = runtime->period_size;
+	entry->buffer_size = runtime->buffer_size;
+	entry->old_hw_ptr = runtime->status->hw_ptr;
+	entry->hw_ptr_base = runtime->hw_ptr_base;
+	log->idx = (log->idx + 1) % XRUN_LOG_CNT;
+}
+
+static void xrun_log_show(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_hwptr_log *log = substream->runtime->hwptr_log;
+	struct hwptr_log_entry *entry;
+	char name[16];
+	unsigned int idx;
+	int cnt;
+
+	if (log == NULL)
+		return;
+	if (xrun_debug(substream, XRUN_DEBUG_LOGONCE) && log->hit)
+		return;
+	pcm_debug_name(substream, name, sizeof(name));
+	for (cnt = 0, idx = log->idx; cnt < XRUN_LOG_CNT; cnt++) {
+		entry = &log->entries[idx];
+		if (entry->period_size == 0)
+			break;
+		snd_printd("hwptr log: %s: j=%lu, pos=%ld/%ld/%ld, "
+			   "hwptr=%ld/%ld\n",
+			   name, entry->jiffies, (unsigned long)entry->pos,
+			   (unsigned long)entry->period_size,
+			   (unsigned long)entry->buffer_size,
+			   (unsigned long)entry->old_hw_ptr,
+			   (unsigned long)entry->hw_ptr_base);
+		idx++;
+		idx %= XRUN_LOG_CNT;
+	}
+	log->hit = 1;
+}
+
+#else /* ! CONFIG_SND_PCM_XRUN_DEBUG */
+
+#define hw_ptr_error(substream, fmt, args...) do { } while (0)
+#define xrun_log(substream, pos)	do { } while (0)
+#define xrun_log_show(substream)	do { } while (0)
+
+#endif
+
+int snd_pcm_update_state(struct snd_pcm_substream *substream,
+			 struct snd_pcm_runtime *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 (runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
+		if (avail >= runtime->buffer_size) {
+			snd_pcm_drain_done(substream);
+			return -EPIPE;
+		}
+	} else {
+		if (avail >= runtime->stop_threshold) {
+			xrun(substream);
+			return -EPIPE;
+		}
+	}
+	if (runtime->twake) {
+		if (avail >= runtime->twake)
+			wake_up(&runtime->tsleep);
+	} else if (avail >= runtime->control->avail_min)
+		wake_up(&runtime->sleep);
+	return 0;
+}
+
+static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
+				  unsigned int in_interrupt)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_uframes_t pos;
+	snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base;
+	snd_pcm_sframes_t hdelta, delta;
+	unsigned long jdelta;
+
+	old_hw_ptr = runtime->status->hw_ptr;
+	pos = substream->ops->pointer(substream);
+	if (pos == SNDRV_PCM_POS_XRUN) {
+		xrun(substream);
+		return -EPIPE;
+	}
+	if (pos >= runtime->buffer_size) {
+		if (printk_ratelimit()) {
+			char name[16];
+			pcm_debug_name(substream, name, sizeof(name));
+			xrun_log_show(substream);
+			snd_printd(KERN_ERR  "BUG: %s, pos = %ld, "
+				   "buffer size = %ld, period size = %ld\n",
+				   name, pos, runtime->buffer_size,
+				   runtime->period_size);
+		}
+		pos = 0;
+	}
+	pos -= pos % runtime->min_align;
+	if (xrun_debug(substream, XRUN_DEBUG_LOG))
+		xrun_log(substream, pos);
+	hw_base = runtime->hw_ptr_base;
+	new_hw_ptr = hw_base + pos;
+	if (in_interrupt) {
+		/* we know that one period was processed */
+		/* delta = "expected next hw_ptr" for in_interrupt != 0 */
+		delta = runtime->hw_ptr_interrupt + runtime->period_size;
+		if (delta > new_hw_ptr) {
+			/* check for double acknowledged interrupts */
+			hdelta = jiffies - runtime->hw_ptr_jiffies;
+			if (hdelta > runtime->hw_ptr_buffer_jiffies/2) {
+				hw_base += runtime->buffer_size;
+				if (hw_base >= runtime->boundary)
+					hw_base = 0;
+				new_hw_ptr = hw_base + pos;
+				goto __delta;
+			}
+		}
+	}
+	/* new_hw_ptr might be lower than old_hw_ptr in case when */
+	/* pointer crosses the end of the ring buffer */
+	if (new_hw_ptr < old_hw_ptr) {
+		hw_base += runtime->buffer_size;
+		if (hw_base >= runtime->boundary)
+			hw_base = 0;
+		new_hw_ptr = hw_base + pos;
+	}
+      __delta:
+	delta = new_hw_ptr - old_hw_ptr;
+	if (delta < 0)
+		delta += runtime->boundary;
+	if (xrun_debug(substream, in_interrupt ?
+			XRUN_DEBUG_PERIODUPDATE : XRUN_DEBUG_HWPTRUPDATE)) {
+		char name[16];
+		pcm_debug_name(substream, name, sizeof(name));
+		snd_printd("%s_update: %s: pos=%u/%u/%u, "
+			   "hwptr=%ld/%ld/%ld/%ld\n",
+			   in_interrupt ? "period" : "hwptr",
+			   name,
+			   (unsigned int)pos,
+			   (unsigned int)runtime->period_size,
+			   (unsigned int)runtime->buffer_size,
+			   (unsigned long)delta,
+			   (unsigned long)old_hw_ptr,
+			   (unsigned long)new_hw_ptr,
+			   (unsigned long)runtime->hw_ptr_base);
+	}
+	/* something must be really wrong */
+	if (delta >= runtime->buffer_size + runtime->period_size) {
+		hw_ptr_error(substream,
+			       "Unexpected hw_pointer value %s"
+			       "(stream=%i, pos=%ld, new_hw_ptr=%ld, "
+			       "old_hw_ptr=%ld)\n",
+				     in_interrupt ? "[Q] " : "[P]",
+				     substream->stream, (long)pos,
+				     (long)new_hw_ptr, (long)old_hw_ptr);
+		return 0;
+	}
+
+	/* Do jiffies check only in xrun_debug mode */
+	if (!xrun_debug(substream, XRUN_DEBUG_JIFFIESCHECK))
+		goto no_jiffies_check;
+
+	/* Skip the jiffies check for hardwares with BATCH flag.
+	 * Such hardware usually just increases the position at each IRQ,
+	 * thus it can't give any strange position.
+	 */
+	if (runtime->hw.info & SNDRV_PCM_INFO_BATCH)
+		goto no_jiffies_check;
+	hdelta = delta;
+	if (hdelta < runtime->delay)
+		goto no_jiffies_check;
+	hdelta -= runtime->delay;
+	jdelta = jiffies - runtime->hw_ptr_jiffies;
+	if (((hdelta * HZ) / runtime->rate) > jdelta + HZ/100) {
+		delta = jdelta /
+			(((runtime->period_size * HZ) / runtime->rate)
+								+ HZ/100);
+		/* move new_hw_ptr according jiffies not pos variable */
+		new_hw_ptr = old_hw_ptr;
+		hw_base = delta;
+		/* use loop to avoid checks for delta overflows */
+		/* the delta value is small or zero in most cases */
+		while (delta > 0) {
+			new_hw_ptr += runtime->period_size;
+			if (new_hw_ptr >= runtime->boundary)
+				new_hw_ptr -= runtime->boundary;
+			delta--;
+		}
+		/* align hw_base to buffer_size */
+		hw_ptr_error(substream,
+			     "hw_ptr skipping! %s"
+			     "(pos=%ld, delta=%ld, period=%ld, "
+			     "jdelta=%lu/%lu/%lu, hw_ptr=%ld/%ld)\n",
+			     in_interrupt ? "[Q] " : "",
+			     (long)pos, (long)hdelta,
+			     (long)runtime->period_size, jdelta,
+			     ((hdelta * HZ) / runtime->rate), hw_base,
+			     (unsigned long)old_hw_ptr,
+			     (unsigned long)new_hw_ptr);
+		/* reset values to proper state */
+		delta = 0;
+		hw_base = new_hw_ptr - (new_hw_ptr % runtime->buffer_size);
+	}
+ no_jiffies_check:
+	if (delta > runtime->period_size + runtime->period_size / 2) {
+		hw_ptr_error(substream,
+			     "Lost interrupts? %s"
+			     "(stream=%i, delta=%ld, new_hw_ptr=%ld, "
+			     "old_hw_ptr=%ld)\n",
+			     in_interrupt ? "[Q] " : "",
+			     substream->stream, (long)delta,
+			     (long)new_hw_ptr,
+			     (long)old_hw_ptr);
+	}
+
+	if (runtime->status->hw_ptr == new_hw_ptr)
+		return 0;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+	    runtime->silence_size > 0)
+		snd_pcm_playback_silence(substream, new_hw_ptr);
+
+	if (in_interrupt) {
+		delta = new_hw_ptr - runtime->hw_ptr_interrupt;
+		if (delta < 0)
+			delta += runtime->boundary;
+		delta -= (snd_pcm_uframes_t)delta % runtime->period_size;
+		runtime->hw_ptr_interrupt += delta;
+		if (runtime->hw_ptr_interrupt >= runtime->boundary)
+			runtime->hw_ptr_interrupt -= runtime->boundary;
+	}
+	runtime->hw_ptr_base = hw_base;
+	runtime->status->hw_ptr = new_hw_ptr;
+	runtime->hw_ptr_jiffies = jiffies;
+	if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE)
+		snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp);
+
+	return snd_pcm_update_state(substream, runtime);
+}
+
+/* CAUTION: call it with irq disabled */
+int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_update_hw_ptr0(substream, 0);
+}
+
+/**
+ * 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(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops)
+{
+	struct snd_pcm_str *stream = &pcm->streams[direction];
+	struct snd_pcm_substream *substream;
+	
+	for (substream = stream->substream; substream != NULL; substream = substream->next)
+		substream->ops = ops;
+}
+
+EXPORT_SYMBOL(snd_pcm_set_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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *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;
+}
+
+EXPORT_SYMBOL(snd_pcm_set_sync);
+
+/*
+ *  Standard ioctl routine
+ */
+
+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_BUG_ON(!n);
+		*r = 0;
+		return UINT_MAX;
+	}
+	n = div_u64_rem(n, c, r);
+	if (n >= UINT_MAX) {
+		*r = 0;
+		return UINT_MAX;
+	}
+	return n;
+}
+
+/**
+ * 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(struct snd_interval *i, const struct snd_interval *v)
+{
+	int changed = 0;
+	if (snd_BUG_ON(snd_interval_empty(i)))
+		return -EINVAL;
+	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;
+}
+
+EXPORT_SYMBOL(snd_interval_refine);
+
+static int snd_interval_refine_first(struct snd_interval *i)
+{
+	if (snd_BUG_ON(snd_interval_empty(i)))
+		return -EINVAL;
+	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(struct snd_interval *i)
+{
+	if (snd_BUG_ON(snd_interval_empty(i)))
+		return -EINVAL;
+	if (snd_interval_single(i))
+		return 0;
+	i->min = i->max;
+	i->openmin = i->openmax;
+	if (i->openmin)
+		i->min--;
+	return 1;
+}
+
+void snd_interval_mul(const struct snd_interval *a, const struct snd_interval *b, struct snd_interval *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
+ * @a: dividend
+ * @b: divisor
+ * @c: quotient
+ *
+ * c = a / b
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+void snd_interval_div(const struct snd_interval *a, const struct snd_interval *b, struct snd_interval *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
+ * @a: dividend 1
+ * @b: dividend 2
+ * @k: divisor (as integer)
+ * @c: result
+  *
+ * c = a * b / k
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+void snd_interval_muldivk(const struct snd_interval *a, const struct snd_interval *b,
+		      unsigned int k, struct snd_interval *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
+ * @a: dividend 1
+ * @k: dividend 2 (as integer)
+ * @b: divisor
+ * @c: result
+ *
+ * c = a * k / b
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+void snd_interval_mulkdiv(const struct snd_interval *a, unsigned int k,
+		      const struct snd_interval *b, struct snd_interval *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;
+}
+
+/* ---- */
+
+
+/**
+ * snd_interval_ratnum - refine the interval value
+ * @i: interval to refine
+ * @rats_count: number of ratnum_t 
+ * @rats: ratnum_t array
+ * @nump: pointer to store the resultant numerator
+ * @denp: pointer to store the resultant denominator
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+int snd_interval_ratnum(struct snd_interval *i,
+			unsigned int rats_count, struct snd_ratnum *rats,
+			unsigned int *nump, unsigned int *denp)
+{
+	unsigned int best_num, best_den;
+	int best_diff;
+	unsigned int k;
+	struct snd_interval t;
+	int err;
+	unsigned int result_num, result_den;
+	int result_diff;
+
+	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_up(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 (diff < 0)
+			diff = -diff;
+		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);
+	
+	result_num = best_num;
+	result_diff = best_diff;
+	result_den = 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_down(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 (diff < 0)
+			diff = -diff;
+		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 (best_diff * result_den < result_diff * best_den) {
+			result_num = best_num;
+			result_den = best_den;
+		}
+		if (nump)
+			*nump = result_num;
+		if (denp)
+			*denp = result_den;
+	}
+	return err;
+}
+
+EXPORT_SYMBOL(snd_interval_ratnum);
+
+/**
+ * snd_interval_ratden - refine the interval value
+ * @i: interval to refine
+ * @rats_count: number of struct ratden
+ * @rats: struct ratden array
+ * @nump: pointer to store the resultant numerator
+ * @denp: pointer to store the resultant denominator
+ *
+ * Returns non-zero if the value is changed, zero if not changed.
+ */
+static int snd_interval_ratden(struct snd_interval *i,
+			       unsigned int rats_count, struct snd_ratden *rats,
+			       unsigned int *nump, unsigned int *denp)
+{
+	unsigned int best_num, best_diff, best_den;
+	unsigned int k;
+	struct snd_interval 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(struct snd_interval *i, unsigned int count, unsigned int *list, unsigned int mask)
+{
+        unsigned int k;
+	struct snd_interval list_range;
+
+	if (!count) {
+		i->empty = 1;
+		return -EINVAL;
+	}
+	snd_interval_any(&list_range);
+	list_range.min = UINT_MAX;
+	list_range.max = 0;
+        for (k = 0; k < count; k++) {
+		if (mask && !(mask & (1 << k)))
+			continue;
+		if (!snd_interval_test(i, list[k]))
+			continue;
+		list_range.min = min(list_range.min, list[k]);
+		list_range.max = max(list_range.max, list[k]);
+        }
+	return snd_interval_refine(i, &list_range);
+}
+
+EXPORT_SYMBOL(snd_interval_list);
+
+static int snd_interval_step(struct snd_interval *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(struct snd_pcm_runtime *runtime, unsigned int cond,
+			int var,
+			snd_pcm_hw_rule_func_t func, void *private,
+			int dep, ...)
+{
+	struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
+	struct snd_pcm_hw_rule *c;
+	unsigned int k;
+	va_list args;
+	va_start(args, dep);
+	if (constrs->rules_num >= constrs->rules_all) {
+		struct snd_pcm_hw_rule *new;
+		unsigned int new_rules = constrs->rules_all + 16;
+		new = kcalloc(new_rules, sizeof(*c), GFP_KERNEL);
+		if (!new) {
+			va_end(args);
+			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) {
+		if (snd_BUG_ON(k >= ARRAY_SIZE(c->deps))) {
+			va_end(args);
+			return -EINVAL;
+		}
+		c->deps[k++] = dep;
+		if (dep < 0)
+			break;
+		dep = va_arg(args, int);
+	}
+	constrs->rules_num++;
+	va_end(args);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_rule_add);
+
+/**
+ * snd_pcm_hw_constraint_mask - apply the given bitmap mask constraint
+ * @runtime: PCM runtime instance
+ * @var: hw_params variable to apply the mask
+ * @mask: the bitmap mask
+ *
+ * Apply the constraint of the given bitmap mask to a 32-bit mask parameter.
+ */
+int snd_pcm_hw_constraint_mask(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,
+			       u_int32_t mask)
+{
+	struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
+	struct snd_mask *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 - apply the given bitmap mask constraint
+ * @runtime: PCM runtime instance
+ * @var: hw_params variable to apply the mask
+ * @mask: the 64bit bitmap mask
+ *
+ * Apply the constraint of the given bitmap mask to a 64-bit mask parameter.
+ */
+int snd_pcm_hw_constraint_mask64(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,
+				 u_int64_t mask)
+{
+	struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
+	struct snd_mask *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 - apply an integer constraint to an interval
+ * @runtime: PCM runtime instance
+ * @var: hw_params variable to apply the integer constraint
+ *
+ * Apply the constraint of integer to an interval parameter.
+ */
+int snd_pcm_hw_constraint_integer(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var)
+{
+	struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
+	return snd_interval_setinteger(constrs_interval(constrs, var));
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_constraint_integer);
+
+/**
+ * snd_pcm_hw_constraint_minmax - apply a min/max range constraint to an interval
+ * @runtime: PCM runtime instance
+ * @var: hw_params variable to apply the range
+ * @min: the minimal value
+ * @max: the maximal value
+ * 
+ * Apply the min/max range constraint to an interval parameter.
+ */
+int snd_pcm_hw_constraint_minmax(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,
+				 unsigned int min, unsigned int max)
+{
+	struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
+	struct snd_interval t;
+	t.min = min;
+	t.max = max;
+	t.openmin = t.openmax = 0;
+	t.integer = 0;
+	return snd_interval_refine(constrs_interval(constrs, var), &t);
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_constraint_minmax);
+
+static int snd_pcm_hw_rule_list(struct snd_pcm_hw_params *params,
+				struct snd_pcm_hw_rule *rule)
+{
+	struct snd_pcm_hw_constraint_list *list = rule->private;
+	return snd_interval_list(hw_param_interval(params, rule->var), list->count, list->list, list->mask);
+}		
+
+
+/**
+ * snd_pcm_hw_constraint_list - apply a list of constraints to a parameter
+ * @runtime: PCM runtime instance
+ * @cond: condition bits
+ * @var: hw_params variable to apply the list constraint
+ * @l: list
+ * 
+ * Apply the list of constraints to an interval parameter.
+ */
+int snd_pcm_hw_constraint_list(struct snd_pcm_runtime *runtime,
+			       unsigned int cond,
+			       snd_pcm_hw_param_t var,
+			       struct snd_pcm_hw_constraint_list *l)
+{
+	return snd_pcm_hw_rule_add(runtime, cond, var,
+				   snd_pcm_hw_rule_list, l,
+				   var, -1);
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_constraint_list);
+
+static int snd_pcm_hw_rule_ratnums(struct snd_pcm_hw_params *params,
+				   struct snd_pcm_hw_rule *rule)
+{
+	struct snd_pcm_hw_constraint_ratnums *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 - apply ratnums constraint to a parameter
+ * @runtime: PCM runtime instance
+ * @cond: condition bits
+ * @var: hw_params variable to apply the ratnums constraint
+ * @r: struct snd_ratnums constriants
+ */
+int snd_pcm_hw_constraint_ratnums(struct snd_pcm_runtime *runtime, 
+				  unsigned int cond,
+				  snd_pcm_hw_param_t var,
+				  struct snd_pcm_hw_constraint_ratnums *r)
+{
+	return snd_pcm_hw_rule_add(runtime, cond, var,
+				   snd_pcm_hw_rule_ratnums, r,
+				   var, -1);
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_constraint_ratnums);
+
+static int snd_pcm_hw_rule_ratdens(struct snd_pcm_hw_params *params,
+				   struct snd_pcm_hw_rule *rule)
+{
+	struct snd_pcm_hw_constraint_ratdens *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 - apply ratdens constraint to a parameter
+ * @runtime: PCM runtime instance
+ * @cond: condition bits
+ * @var: hw_params variable to apply the ratdens constraint
+ * @r: struct snd_ratdens constriants
+ */
+int snd_pcm_hw_constraint_ratdens(struct snd_pcm_runtime *runtime, 
+				  unsigned int cond,
+				  snd_pcm_hw_param_t var,
+				  struct snd_pcm_hw_constraint_ratdens *r)
+{
+	return snd_pcm_hw_rule_add(runtime, cond, var,
+				   snd_pcm_hw_rule_ratdens, r,
+				   var, -1);
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_constraint_ratdens);
+
+static int snd_pcm_hw_rule_msbits(struct snd_pcm_hw_params *params,
+				  struct snd_pcm_hw_rule *rule)
+{
+	unsigned int l = (unsigned long) rule->private;
+	int width = l & 0xffff;
+	unsigned int msbits = l >> 16;
+	struct snd_interval *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 - add a hw constraint msbits rule
+ * @runtime: PCM runtime instance
+ * @cond: condition bits
+ * @width: sample bits width
+ * @msbits: msbits width
+ */
+int snd_pcm_hw_constraint_msbits(struct snd_pcm_runtime *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);
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_constraint_msbits);
+
+static int snd_pcm_hw_rule_step(struct snd_pcm_hw_params *params,
+				struct snd_pcm_hw_rule *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 - add a hw constraint step rule
+ * @runtime: PCM runtime instance
+ * @cond: condition bits
+ * @var: hw_params variable to apply the step constraint
+ * @step: step size
+ */
+int snd_pcm_hw_constraint_step(struct snd_pcm_runtime *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);
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_constraint_step);
+
+static int snd_pcm_hw_rule_pow2(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
+{
+	static unsigned 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 - add a hw constraint power-of-2 rule
+ * @runtime: PCM runtime instance
+ * @cond: condition bits
+ * @var: hw_params variable to apply the power-of-2 constraint
+ */
+int snd_pcm_hw_constraint_pow2(struct snd_pcm_runtime *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);
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_constraint_pow2);
+
+static void _snd_pcm_hw_param_any(struct snd_pcm_hw_params *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();
+}
+
+void _snd_pcm_hw_params_any(struct snd_pcm_hw_params *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;
+}
+
+EXPORT_SYMBOL(_snd_pcm_hw_params_any);
+
+/**
+ * snd_pcm_hw_param_value - return @params field @var value
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @dir: pointer to the direction (-1,0,1) or %NULL
+ *
+ * Return the value for field @var if it's fixed in configuration space
+ * defined by @params. Return -%EINVAL otherwise.
+ */
+int snd_pcm_hw_param_value(const struct snd_pcm_hw_params *params,
+			   snd_pcm_hw_param_t var, int *dir)
+{
+	if (hw_is_mask(var)) {
+		const struct snd_mask *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 struct snd_interval *i = hw_param_interval_c(params, var);
+		if (!snd_interval_single(i))
+			return -EINVAL;
+		if (dir)
+			*dir = i->openmin;
+		return snd_interval_value(i);
+	}
+	return -EINVAL;
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_param_value);
+
+void _snd_pcm_hw_param_setempty(struct snd_pcm_hw_params *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();
+	}
+}
+
+EXPORT_SYMBOL(_snd_pcm_hw_param_setempty);
+
+static int _snd_pcm_hw_param_first(struct snd_pcm_hw_params *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
+		return -EINVAL;
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+
+/**
+ * snd_pcm_hw_param_first - refine config space and return minimum value
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @dir: pointer to the direction (-1,0,1) or %NULL
+ *
+ * Inside configuration space defined by @params remove from @var all
+ * values > minimum. Reduce configuration space accordingly.
+ * Return the minimum.
+ */
+int snd_pcm_hw_param_first(struct snd_pcm_substream *pcm, 
+			   struct snd_pcm_hw_params *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);
+		if (snd_BUG_ON(err < 0))
+			return err;
+	}
+	return snd_pcm_hw_param_value(params, var, dir);
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_param_first);
+
+static int _snd_pcm_hw_param_last(struct snd_pcm_hw_params *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
+		return -EINVAL;
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+
+/**
+ * snd_pcm_hw_param_last - refine config space and return maximum value
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ * @var: parameter to retrieve
+ * @dir: pointer to the direction (-1,0,1) or %NULL
+ *
+ * Inside configuration space defined by @params remove from @var all
+ * values < maximum. Reduce configuration space accordingly.
+ * Return the maximum.
+ */
+int snd_pcm_hw_param_last(struct snd_pcm_substream *pcm, 
+			  struct snd_pcm_hw_params *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);
+		if (snd_BUG_ON(err < 0))
+			return err;
+	}
+	return snd_pcm_hw_param_value(params, var, dir);
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_param_last);
+
+/**
+ * snd_pcm_hw_param_choose - choose a configuration defined by @params
+ * @pcm: PCM instance
+ * @params: the hw_params instance
+ *
+ * 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(struct snd_pcm_substream *pcm,
+			     struct snd_pcm_hw_params *params)
+{
+	static int vars[] = {
+		SNDRV_PCM_HW_PARAM_ACCESS,
+		SNDRV_PCM_HW_PARAM_FORMAT,
+		SNDRV_PCM_HW_PARAM_SUBFORMAT,
+		SNDRV_PCM_HW_PARAM_CHANNELS,
+		SNDRV_PCM_HW_PARAM_RATE,
+		SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+		SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+		SNDRV_PCM_HW_PARAM_TICK_TIME,
+		-1
+	};
+	int err, *v;
+
+	for (v = vars; *v != -1; v++) {
+		if (*v != SNDRV_PCM_HW_PARAM_BUFFER_SIZE)
+			err = snd_pcm_hw_param_first(pcm, params, *v, NULL);
+		else
+			err = snd_pcm_hw_param_last(pcm, params, *v, NULL);
+		if (snd_BUG_ON(err < 0))
+			return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_lib_ioctl_reset(struct snd_pcm_substream *substream,
+				   void *arg)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream,
+					  void *arg)
+{
+	struct snd_pcm_channel_info *info = arg;
+	struct snd_pcm_runtime *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;
+}
+
+static int snd_pcm_lib_ioctl_fifo_size(struct snd_pcm_substream *substream,
+				       void *arg)
+{
+	struct snd_pcm_hw_params *params = arg;
+	snd_pcm_format_t format;
+	int channels, width;
+
+	params->fifo_size = substream->runtime->hw.fifo_size;
+	if (!(substream->runtime->hw.info & SNDRV_PCM_INFO_FIFO_IN_FRAMES)) {
+		format = params_format(params);
+		channels = params_channels(params);
+		width = snd_pcm_format_physical_width(format);
+		params->fifo_size /= width * channels;
+	}
+	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(struct snd_pcm_substream *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);
+	case SNDRV_PCM_IOCTL1_FIFO_SIZE:
+		return snd_pcm_lib_ioctl_fifo_size(substream, arg);
+	}
+	return -ENXIO;
+}
+
+EXPORT_SYMBOL(snd_pcm_lib_ioctl);
+
+/**
+ * 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, 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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime;
+	unsigned long flags;
+
+	if (PCM_RUNTIME_CHECK(substream))
+		return;
+	runtime = substream->runtime;
+
+	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_ptr0(substream, 1) < 0)
+		goto _end;
+
+	if (substream->timer_running)
+		snd_timer_interrupt(substream->timer, 1);
+ _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);
+}
+
+EXPORT_SYMBOL(snd_pcm_period_elapsed);
+
+/*
+ * Wait until avail_min data becomes available
+ * Returns a negative error code if any error occurs during operation.
+ * The available space is stored on availp.  When err = 0 and avail = 0
+ * on the capture stream, it indicates the stream is in DRAINING state.
+ */
+static int wait_for_avail(struct snd_pcm_substream *substream,
+			      snd_pcm_uframes_t *availp)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	wait_queue_t wait;
+	int err = 0;
+	snd_pcm_uframes_t avail = 0;
+	long tout;
+
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&runtime->tsleep, &wait);
+	for (;;) {
+		if (signal_pending(current)) {
+			err = -ERESTARTSYS;
+			break;
+		}
+		set_current_state(TASK_INTERRUPTIBLE);
+		snd_pcm_stream_unlock_irq(substream);
+		tout = schedule_timeout(msecs_to_jiffies(10000));
+		snd_pcm_stream_lock_irq(substream);
+		switch (runtime->status->state) {
+		case SNDRV_PCM_STATE_SUSPENDED:
+			err = -ESTRPIPE;
+			goto _endloop;
+		case SNDRV_PCM_STATE_XRUN:
+			err = -EPIPE;
+			goto _endloop;
+		case SNDRV_PCM_STATE_DRAINING:
+			if (is_playback)
+				err = -EPIPE;
+			else 
+				avail = 0; /* indicate draining */
+			goto _endloop;
+		case SNDRV_PCM_STATE_OPEN:
+		case SNDRV_PCM_STATE_SETUP:
+		case SNDRV_PCM_STATE_DISCONNECTED:
+			err = -EBADFD;
+			goto _endloop;
+		}
+		if (!tout) {
+			snd_printd("%s write error (DMA or IRQ trouble?)\n",
+				   is_playback ? "playback" : "capture");
+			err = -EIO;
+			break;
+		}
+		if (is_playback)
+			avail = snd_pcm_playback_avail(runtime);
+		else
+			avail = snd_pcm_capture_avail(runtime);
+		if (avail >= runtime->twake)
+			break;
+	}
+ _endloop:
+	remove_wait_queue(&runtime->tsleep, &wait);
+	*availp = avail;
+	return err;
+}
+	
+static int snd_pcm_lib_write_transfer(struct snd_pcm_substream *substream,
+				      unsigned int hwoff,
+				      unsigned long data, unsigned int off,
+				      snd_pcm_uframes_t frames)
+{
+	struct snd_pcm_runtime *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);
+		if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)))
+			return -EFAULT;
+	}
+	return 0;
+}
+ 
+typedef int (*transfer_f)(struct snd_pcm_substream *substream, unsigned int hwoff,
+			  unsigned long data, unsigned int off,
+			  snd_pcm_uframes_t size);
+
+static snd_pcm_sframes_t snd_pcm_lib_write1(struct snd_pcm_substream *substream, 
+					    unsigned long data,
+					    snd_pcm_uframes_t size,
+					    int nonblock,
+					    transfer_f transfer)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_uframes_t xfer = 0;
+	snd_pcm_uframes_t offset = 0;
+	int err = 0;
+
+	if (size == 0)
+		return 0;
+
+	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;
+	}
+
+	runtime->twake = runtime->control->avail_min ? : 1;
+	while (size > 0) {
+		snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
+		snd_pcm_uframes_t avail;
+		snd_pcm_uframes_t cont;
+		if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+			snd_pcm_update_hw_ptr(substream);
+		avail = snd_pcm_playback_avail(runtime);
+		if (!avail) {
+			if (nonblock) {
+				err = -EAGAIN;
+				goto _end_unlock;
+			}
+			runtime->twake = min_t(snd_pcm_uframes_t, size,
+					runtime->control->avail_min ? : 1);
+			err = wait_for_avail(substream, &avail);
+			if (err < 0)
+				goto _end_unlock;
+		}
+		frames = size > avail ? avail : size;
+		cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
+		if (frames > cont)
+			frames = cont;
+		if (snd_BUG_ON(!frames)) {
+			runtime->twake = 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);
+		err = transfer(substream, appl_ofs, data, offset, frames);
+		snd_pcm_stream_lock_irq(substream);
+		if (err < 0)
+			goto _end_unlock;
+		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;
+		}
+	}
+ _end_unlock:
+	runtime->twake = 0;
+	if (xfer > 0 && err >= 0)
+		snd_pcm_update_state(substream, runtime);
+	snd_pcm_stream_unlock_irq(substream);
+	return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
+}
+
+/* sanity-check for read/write methods */
+static int pcm_sanity_check(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime;
+	if (PCM_RUNTIME_CHECK(substream))
+		return -ENXIO;
+	runtime = substream->runtime;
+	if (snd_BUG_ON(!substream->ops->copy && !runtime->dma_area))
+		return -EINVAL;
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+	return 0;
+}
+
+snd_pcm_sframes_t snd_pcm_lib_write(struct snd_pcm_substream *substream, const void __user *buf, snd_pcm_uframes_t size)
+{
+	struct snd_pcm_runtime *runtime;
+	int nonblock;
+	int err;
+
+	err = pcm_sanity_check(substream);
+	if (err < 0)
+		return err;
+	runtime = substream->runtime;
+	nonblock = !!(substream->f_flags & O_NONBLOCK);
+
+	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);
+}
+
+EXPORT_SYMBOL(snd_pcm_lib_write);
+
+static int snd_pcm_lib_writev_transfer(struct snd_pcm_substream *substream,
+				       unsigned int hwoff,
+				       unsigned long data, unsigned int off,
+				       snd_pcm_uframes_t frames)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+	void __user **bufs = (void __user **)data;
+	int channels = runtime->channels;
+	int c;
+	if (substream->ops->copy) {
+		if (snd_BUG_ON(!substream->ops->silence))
+			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;
+		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(struct snd_pcm_substream *substream,
+				     void __user **bufs,
+				     snd_pcm_uframes_t frames)
+{
+	struct snd_pcm_runtime *runtime;
+	int nonblock;
+	int err;
+
+	err = pcm_sanity_check(substream);
+	if (err < 0)
+		return err;
+	runtime = substream->runtime;
+	nonblock = !!(substream->f_flags & O_NONBLOCK);
+
+	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);
+}
+
+EXPORT_SYMBOL(snd_pcm_lib_writev);
+
+static int snd_pcm_lib_read_transfer(struct snd_pcm_substream *substream, 
+				     unsigned int hwoff,
+				     unsigned long data, unsigned int off,
+				     snd_pcm_uframes_t frames)
+{
+	struct snd_pcm_runtime *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);
+		if (copy_to_user(buf, hwbuf, frames_to_bytes(runtime, frames)))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_lib_read1(struct snd_pcm_substream *substream,
+					   unsigned long data,
+					   snd_pcm_uframes_t size,
+					   int nonblock,
+					   transfer_f transfer)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_uframes_t xfer = 0;
+	snd_pcm_uframes_t offset = 0;
+	int err = 0;
+
+	if (size == 0)
+		return 0;
+
+	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;
+	}
+
+	runtime->twake = runtime->control->avail_min ? : 1;
+	while (size > 0) {
+		snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
+		snd_pcm_uframes_t avail;
+		snd_pcm_uframes_t cont;
+		if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+			snd_pcm_update_hw_ptr(substream);
+		avail = snd_pcm_capture_avail(runtime);
+		if (!avail) {
+			if (runtime->status->state ==
+			    SNDRV_PCM_STATE_DRAINING) {
+				snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+				goto _end_unlock;
+			}
+			if (nonblock) {
+				err = -EAGAIN;
+				goto _end_unlock;
+			}
+			runtime->twake = min_t(snd_pcm_uframes_t, size,
+					runtime->control->avail_min ? : 1);
+			err = wait_for_avail(substream, &avail);
+			if (err < 0)
+				goto _end_unlock;
+			if (!avail)
+				continue; /* draining */
+		}
+		frames = size > avail ? avail : size;
+		cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
+		if (frames > cont)
+			frames = cont;
+		if (snd_BUG_ON(!frames)) {
+			runtime->twake = 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);
+		err = transfer(substream, appl_ofs, data, offset, frames);
+		snd_pcm_stream_lock_irq(substream);
+		if (err < 0)
+			goto _end_unlock;
+		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;
+	}
+ _end_unlock:
+	runtime->twake = 0;
+	if (xfer > 0 && err >= 0)
+		snd_pcm_update_state(substream, runtime);
+	snd_pcm_stream_unlock_irq(substream);
+	return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
+}
+
+snd_pcm_sframes_t snd_pcm_lib_read(struct snd_pcm_substream *substream, void __user *buf, snd_pcm_uframes_t size)
+{
+	struct snd_pcm_runtime *runtime;
+	int nonblock;
+	int err;
+	
+	err = pcm_sanity_check(substream);
+	if (err < 0)
+		return err;
+	runtime = substream->runtime;
+	nonblock = !!(substream->f_flags & O_NONBLOCK);
+	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);
+}
+
+EXPORT_SYMBOL(snd_pcm_lib_read);
+
+static int snd_pcm_lib_readv_transfer(struct snd_pcm_substream *substream,
+				      unsigned int hwoff,
+				      unsigned long data, unsigned int off,
+				      snd_pcm_uframes_t frames)
+{
+	struct snd_pcm_runtime *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;
+		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(struct snd_pcm_substream *substream,
+				    void __user **bufs,
+				    snd_pcm_uframes_t frames)
+{
+	struct snd_pcm_runtime *runtime;
+	int nonblock;
+	int err;
+
+	err = pcm_sanity_check(substream);
+	if (err < 0)
+		return err;
+	runtime = substream->runtime;
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+
+	nonblock = !!(substream->f_flags & O_NONBLOCK);
+	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);
+}
+
+EXPORT_SYMBOL(snd_pcm_lib_readv);
diff --git a/linux/sound/core/pcm_memory.c b/linux/sound/core/pcm_memory.c
new file mode 100644
index 0000000..917e405
--- /dev/null
+++ b/linux/sound/core/pcm_memory.c
@@ -0,0 +1,492 @@
+/*
+ *  Digital Audio (PCM) abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/vmalloc.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(struct snd_pcm_substream *substream, size_t size)
+{
+	struct snd_dma_buffer *dmab = &substream->dma_buffer;
+	int err;
+
+	/* 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(struct snd_pcm_substream *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(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_preallocate_dma_free(substream);
+#ifdef CONFIG_SND_VERBOSE_PROCFS
+	snd_info_free_entry(substream->proc_prealloc_max_entry);
+	substream->proc_prealloc_max_entry = NULL;
+	snd_info_free_entry(substream->proc_prealloc_entry);
+	substream->proc_prealloc_entry = NULL;
+#endif
+	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(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *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;
+}
+
+EXPORT_SYMBOL(snd_pcm_lib_preallocate_free_for_all);
+
+#ifdef CONFIG_SND_VERBOSE_PROCFS
+/*
+ * read callback for prealloc proc file
+ *
+ * prints the current allocated size in kB.
+ */
+static void snd_pcm_lib_preallocate_proc_read(struct snd_info_entry *entry,
+					      struct snd_info_buffer *buffer)
+{
+	struct snd_pcm_substream *substream = entry->private_data;
+	snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_buffer.bytes / 1024);
+}
+
+/*
+ * read callback for prealloc_max proc file
+ *
+ * prints the maximum allowed size in kB.
+ */
+static void snd_pcm_lib_preallocate_max_proc_read(struct snd_info_entry *entry,
+						  struct snd_info_buffer *buffer)
+{
+	struct snd_pcm_substream *substream = entry->private_data;
+	snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_max / 1024);
+}
+
+/*
+ * write callback for prealloc proc file
+ *
+ * accepts the preallocation size in kB.
+ */
+static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry,
+					       struct snd_info_buffer *buffer)
+{
+	struct snd_pcm_substream *substream = 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;
+	}
+}
+
+static inline void preallocate_info_init(struct snd_pcm_substream *substream)
+{
+	struct snd_info_entry *entry;
+
+	if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) {
+		entry->c.text.read = snd_pcm_lib_preallocate_proc_read;
+		entry->c.text.write = snd_pcm_lib_preallocate_proc_write;
+		entry->mode |= S_IWUSR;
+		entry->private_data = substream;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	substream->proc_prealloc_entry = entry;
+	if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc_max", substream->proc_root)) != NULL) {
+		entry->c.text.read = snd_pcm_lib_preallocate_max_proc_read;
+		entry->private_data = substream;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	substream->proc_prealloc_max_entry = entry;
+}
+
+#else /* !CONFIG_SND_VERBOSE_PROCFS */
+#define preallocate_info_init(s)
+#endif /* CONFIG_SND_VERBOSE_PROCFS */
+
+/*
+ * pre-allocate the buffer and create a proc file for the substream
+ */
+static int snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
+					  size_t size, size_t max)
+{
+
+	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;
+	preallocate_info_init(substream);
+	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(struct snd_pcm_substream *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);
+}
+
+EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages);
+
+/**
+ * snd_pcm_lib_preallocate_pages_for_all - pre-allocation for continous memory type (all substreams)
+ * @pcm: the pcm 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(struct snd_pcm *pcm,
+					  int type, void *data,
+					  size_t size, size_t max)
+{
+	struct snd_pcm_substream *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;
+}
+
+EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all);
+
+#ifdef CONFIG_SND_DMA_SGBUF
+/**
+ * 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(struct snd_pcm_substream *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];
+}
+
+EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page);
+
+/*
+ * compute the max chunk size with continuous pages on sg-buffer
+ */
+unsigned int snd_pcm_sgbuf_get_chunk_size(struct snd_pcm_substream *substream,
+					  unsigned int ofs, unsigned int size)
+{
+	struct snd_sg_buf *sg = snd_pcm_substream_sgbuf(substream);
+	unsigned int start, end, pg;
+
+	start = ofs >> PAGE_SHIFT;
+	end = (ofs + size - 1) >> PAGE_SHIFT;
+	/* check page continuity */
+	pg = sg->table[start].addr >> PAGE_SHIFT;
+	for (;;) {
+		start++;
+		if (start > end)
+			break;
+		pg++;
+		if ((sg->table[start].addr >> PAGE_SHIFT) != pg)
+			return (start << PAGE_SHIFT) - ofs;
+	}
+	/* ok, all on continuous pages */
+	return size;
+}
+EXPORT_SYMBOL(snd_pcm_sgbuf_get_chunk_size);
+#endif /* CONFIG_SND_DMA_SGBUF */
+
+/**
+ * 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(struct snd_pcm_substream *substream, size_t size)
+{
+	struct snd_pcm_runtime *runtime;
+	struct snd_dma_buffer *dmab = NULL;
+
+	if (PCM_RUNTIME_CHECK(substream))
+		return -EINVAL;
+	if (snd_BUG_ON(substream->dma_buffer.dev.type ==
+		       SNDRV_DMA_TYPE_UNKNOWN))
+		return -EINVAL;
+	runtime = substream->runtime;
+
+	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 = kzalloc(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 */
+}
+
+EXPORT_SYMBOL(snd_pcm_lib_malloc_pages);
+
+/**
+ * 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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime;
+
+	if (PCM_RUNTIME_CHECK(substream))
+		return -EINVAL;
+	runtime = substream->runtime;
+	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;
+}
+
+EXPORT_SYMBOL(snd_pcm_lib_free_pages);
+
+int _snd_pcm_lib_alloc_vmalloc_buffer(struct snd_pcm_substream *substream,
+				      size_t size, gfp_t gfp_flags)
+{
+	struct snd_pcm_runtime *runtime;
+
+	if (PCM_RUNTIME_CHECK(substream))
+		return -EINVAL;
+	runtime = substream->runtime;
+	if (runtime->dma_area) {
+		if (runtime->dma_bytes >= size)
+			return 0; /* already large enough */
+		vfree(runtime->dma_area);
+	}
+	runtime->dma_area = __vmalloc(size, gfp_flags, PAGE_KERNEL);
+	if (!runtime->dma_area)
+		return -ENOMEM;
+	runtime->dma_bytes = size;
+	return 1;
+}
+EXPORT_SYMBOL(_snd_pcm_lib_alloc_vmalloc_buffer);
+
+/**
+ * snd_pcm_lib_free_vmalloc_buffer - free vmalloc buffer
+ * @substream: the substream with a buffer allocated by
+ *	snd_pcm_lib_alloc_vmalloc_buffer()
+ */
+int snd_pcm_lib_free_vmalloc_buffer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime;
+
+	if (PCM_RUNTIME_CHECK(substream))
+		return -EINVAL;
+	runtime = substream->runtime;
+	vfree(runtime->dma_area);
+	runtime->dma_area = NULL;
+	return 0;
+}
+EXPORT_SYMBOL(snd_pcm_lib_free_vmalloc_buffer);
+
+/**
+ * snd_pcm_lib_get_vmalloc_page - map vmalloc buffer offset to page struct
+ * @substream: the substream with a buffer allocated by
+ *	snd_pcm_lib_alloc_vmalloc_buffer()
+ * @offset: offset in the buffer
+ *
+ * This function is to be used as the page callback in the PCM ops.
+ */
+struct page *snd_pcm_lib_get_vmalloc_page(struct snd_pcm_substream *substream,
+					  unsigned long offset)
+{
+	return vmalloc_to_page(substream->runtime->dma_area + offset);
+}
+EXPORT_SYMBOL(snd_pcm_lib_get_vmalloc_page);
diff --git a/linux/sound/core/pcm_misc.c b/linux/sound/core/pcm_misc.c
new file mode 100644
index 0000000..434af3c
--- /dev/null
+++ b/linux/sound/core/pcm_misc.c
@@ -0,0 +1,486 @@
+/*
+ *  PCM Interface - misc routines
+ *  Copyright (c) 1998 by Jaroslav Kysela <perex@perex.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 <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 = { 0x00, 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 = {},
+	},
+	[SNDRV_PCM_FORMAT_G723_24] = {
+		.width = 3, .phys = 3, .le = -1, .signd = -1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_G723_40] = {
+		.width = 5, .phys = 5, .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 },
+	},
+	[SNDRV_PCM_FORMAT_G723_24_1B] = {
+		.width = 3, .phys = 8, .le = -1, .signd = -1,
+		.silence = {},
+	},
+	[SNDRV_PCM_FORMAT_G723_40_1B] = {
+		.width = 5, .phys = 8, .le = -1, .signd = -1,
+		.silence = {},
+	},
+};
+
+
+/**
+ * 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;
+}
+
+EXPORT_SYMBOL(snd_pcm_format_signed);
+
+/**
+ * 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;
+}
+
+EXPORT_SYMBOL(snd_pcm_format_unsigned);
+
+/**
+ * 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;
+}
+
+EXPORT_SYMBOL(snd_pcm_format_linear);
+
+/**
+ * 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;
+}
+
+EXPORT_SYMBOL(snd_pcm_format_little_endian);
+
+/**
+ * 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;
+}
+
+EXPORT_SYMBOL(snd_pcm_format_big_endian);
+
+/**
+ * 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;
+}
+
+EXPORT_SYMBOL(snd_pcm_format_width);
+
+/**
+ * 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;
+}
+
+EXPORT_SYMBOL(snd_pcm_format_physical_width);
+
+/**
+ * snd_pcm_format_size - return the byte size of samples on the given format
+ * @format: the format to check
+ * @samples: sampling rate
+ *
+ * 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;
+}
+
+EXPORT_SYMBOL(snd_pcm_format_size);
+
+/**
+ * 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;
+}
+
+EXPORT_SYMBOL(snd_pcm_format_silence_64);
+
+/**
+ * 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;
+}
+
+EXPORT_SYMBOL(snd_pcm_format_set_silence);
+
+/**
+ * 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(struct snd_pcm_runtime *runtime)
+{
+	int i;
+	for (i = 0; i < (int)snd_pcm_known_rates.count; i++) {
+		if (runtime->hw.rates & (1 << i)) {
+			runtime->hw.rate_min = snd_pcm_known_rates.list[i];
+			break;
+		}
+	}
+	for (i = (int)snd_pcm_known_rates.count - 1; i >= 0; i--) {
+		if (runtime->hw.rates & (1 << i)) {
+			runtime->hw.rate_max = snd_pcm_known_rates.list[i];
+			break;
+		}
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_pcm_limit_hw_rates);
+
+/**
+ * snd_pcm_rate_to_rate_bit - converts sample rate to SNDRV_PCM_RATE_xxx bit
+ * @rate: the sample rate to convert
+ *
+ * Returns the SNDRV_PCM_RATE_xxx flag that corresponds to the given rate, or
+ * SNDRV_PCM_RATE_KNOT for an unknown rate.
+ */
+unsigned int snd_pcm_rate_to_rate_bit(unsigned int rate)
+{
+	unsigned int i;
+
+	for (i = 0; i < snd_pcm_known_rates.count; i++)
+		if (snd_pcm_known_rates.list[i] == rate)
+			return 1u << i;
+	return SNDRV_PCM_RATE_KNOT;
+}
+EXPORT_SYMBOL(snd_pcm_rate_to_rate_bit);
diff --git a/linux/sound/core/pcm_native.c b/linux/sound/core/pcm_native.c
new file mode 100644
index 0000000..e82c1f9
--- /dev/null
+++ b/linux/sound/core/pcm_native.c
@@ -0,0 +1,3473 @@
+/*
+ *  Digital Audio (PCM) abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/mm.h>
+#include <linux/file.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/pm_qos_params.h>
+#include <linux/uio.h>
+#include <linux/dma-mapping.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>
+#if defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT)
+#include <dma-coherence.h>
+#endif
+
+/*
+ *  Compatibility
+ */
+
+struct snd_pcm_hw_params_old {
+	unsigned int flags;
+	unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT -
+			   SNDRV_PCM_HW_PARAM_ACCESS + 1];
+	struct snd_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;
+	snd_pcm_uframes_t fifo_size;
+	unsigned char reserved[64];
+};
+
+#ifdef CONFIG_SND_SUPPORT_OLD_API
+#define SNDRV_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct snd_pcm_hw_params_old)
+#define SNDRV_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, struct snd_pcm_hw_params_old)
+
+static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params_old __user * _oparams);
+static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params_old __user * _oparams);
+#endif
+static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream);
+
+/*
+ *
+ */
+
+DEFINE_RWLOCK(snd_pcm_link_rwlock);
+EXPORT_SYMBOL(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(struct snd_pcm_substream *substream, struct snd_pcm_info *info)
+{
+	struct snd_pcm_runtime *runtime;
+	struct snd_pcm *pcm = substream->pcm;
+	struct snd_pcm_str *pstr = substream->pstr;
+
+	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(struct snd_pcm_substream *substream,
+		      struct snd_pcm_info __user * _info)
+{
+	struct snd_pcm_info *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
+static const char * const 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(struct snd_pcm_substream *substream, 
+		      struct snd_pcm_hw_params *params)
+{
+	unsigned int k;
+	struct snd_pcm_hardware *hw;
+	struct snd_interval *i = NULL;
+	struct snd_mask *m = NULL;
+	struct snd_pcm_hw_constraints *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(KERN_DEBUG "%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(KERN_DEBUG "%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++) {
+			struct snd_pcm_hw_rule *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(KERN_DEBUG "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 & ~SNDRV_PCM_INFO_FIFO_IN_FRAMES;
+	if (!params->fifo_size) {
+		m = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+		i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+		if (snd_mask_min(m) == snd_mask_max(m) &&
+                    snd_interval_min(i) == snd_interval_max(i)) {
+			changed = substream->ops->ioctl(substream,
+					SNDRV_PCM_IOCTL1_FIFO_SIZE, params);
+			if (changed < 0)
+				return changed;
+		}
+	}
+	params->rmask = 0;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_pcm_hw_refine);
+
+static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params __user * _params)
+{
+	struct snd_pcm_hw_params *params;
+	int err;
+
+	params = memdup_user(_params, sizeof(*params));
+	if (IS_ERR(params))
+		return PTR_ERR(params);
+
+	err = snd_pcm_hw_refine(substream, params);
+	if (copy_to_user(_params, params, sizeof(*params))) {
+		if (!err)
+			err = -EFAULT;
+	}
+
+	kfree(params);
+	return err;
+}
+
+static int period_to_usecs(struct snd_pcm_runtime *runtime)
+{
+	int usecs;
+
+	if (! runtime->rate)
+		return -1; /* invalid */
+
+	/* take 75% of period time as the deadline */
+	usecs = (750000 / runtime->rate) * runtime->period_size;
+	usecs += ((750000 % runtime->rate) * runtime->period_size) /
+		runtime->rate;
+
+	return usecs;
+}
+
+static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime;
+	int err, usecs;
+	unsigned int bits;
+	snd_pcm_uframes_t frames;
+
+	if (PCM_RUNTIME_CHECK(substream))
+		return -ENXIO;
+	runtime = substream->runtime;
+	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(&substream->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->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->control->avail_min = 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;
+
+	if (pm_qos_request_active(&substream->latency_pm_qos_req))
+		pm_qos_remove_request(&substream->latency_pm_qos_req);
+	if ((usecs = period_to_usecs(runtime)) >= 0)
+		pm_qos_add_request(&substream->latency_pm_qos_req,
+				   PM_QOS_CPU_DMA_LATENCY, usecs);
+	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(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params __user * _params)
+{
+	struct snd_pcm_hw_params *params;
+	int err;
+
+	params = memdup_user(_params, sizeof(*params));
+	if (IS_ERR(params))
+		return PTR_ERR(params);
+
+	err = snd_pcm_hw_params(substream, params);
+	if (copy_to_user(_params, params, sizeof(*params))) {
+		if (!err)
+			err = -EFAULT;
+	}
+
+	kfree(params);
+	return err;
+}
+
+static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime;
+	int result = 0;
+
+	if (PCM_RUNTIME_CHECK(substream))
+		return -ENXIO;
+	runtime = substream->runtime;
+	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(&substream->mmap_count))
+		return -EBADFD;
+	if (substream->ops->hw_free)
+		result = substream->ops->hw_free(substream);
+	runtime->status->state = SNDRV_PCM_STATE_OPEN;
+	pm_qos_remove_request(&substream->latency_pm_qos_req);
+	return result;
+}
+
+static int snd_pcm_sw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_sw_params *params)
+{
+	struct snd_pcm_runtime *runtime;
+	int err;
+
+	if (PCM_RUNTIME_CHECK(substream))
+		return -ENXIO;
+	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 (params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST)
+		return -EINVAL;
+	if (params->avail_min == 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;
+	}
+	err = 0;
+	snd_pcm_stream_lock_irq(substream);
+	runtime->tstamp_mode = params->tstamp_mode;
+	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;
+        params->boundary = runtime->boundary;
+	if (snd_pcm_running(substream)) {
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+		    runtime->silence_size > 0)
+			snd_pcm_playback_silence(substream, ULONG_MAX);
+		err = snd_pcm_update_state(substream, runtime);
+	}
+	snd_pcm_stream_unlock_irq(substream);
+	return err;
+}
+
+static int snd_pcm_sw_params_user(struct snd_pcm_substream *substream,
+				  struct snd_pcm_sw_params __user * _params)
+{
+	struct snd_pcm_sw_params 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(struct snd_pcm_substream *substream,
+		   struct snd_pcm_status *status)
+{
+	struct snd_pcm_runtime *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_ENABLE) {
+			status->tstamp = runtime->status->tstamp;
+			goto _tstamp_end;
+		}
+	}
+	snd_pcm_gettime(runtime, &status->tstamp);
+ _tstamp_end:
+	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;
+			status->delay += runtime->delay;
+		} else
+			status->delay = 0;
+	} else {
+		status->avail = snd_pcm_capture_avail(runtime);
+		if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+			status->delay = status->avail + runtime->delay;
+		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(struct snd_pcm_substream *substream,
+			       struct snd_pcm_status __user * _status)
+{
+	struct snd_pcm_status status;
+	int res;
+	
+	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(struct snd_pcm_substream *substream,
+				struct snd_pcm_channel_info * info)
+{
+	struct snd_pcm_runtime *runtime;
+	unsigned int channel;
+	
+	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(struct snd_pcm_substream *substream,
+				     struct snd_pcm_channel_info __user * _info)
+{
+	struct snd_pcm_channel_info 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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	if (runtime->trigger_master == NULL)
+		return;
+	if (runtime->trigger_master == substream) {
+		snd_pcm_gettime(runtime, &runtime->trigger_tstamp);
+	} 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)(struct snd_pcm_substream *substream, int state);
+	int (*do_action)(struct snd_pcm_substream *substream, int state);
+	void (*undo_action)(struct snd_pcm_substream *substream, int state);
+	void (*post_action)(struct snd_pcm_substream *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,
+				struct snd_pcm_substream *substream,
+				int state, int do_lock)
+{
+	struct snd_pcm_substream *s = NULL;
+	struct snd_pcm_substream *s1;
+	int res = 0;
+
+	snd_pcm_group_for_each_entry(s, substream) {
+		if (do_lock && s != substream)
+			spin_lock_nested(&s->self_group.lock,
+					 SINGLE_DEPTH_NESTING);
+		res = ops->pre_action(s, state);
+		if (res < 0)
+			goto _unlock;
+	}
+	snd_pcm_group_for_each_entry(s, substream) {
+		res = ops->do_action(s, state);
+		if (res < 0) {
+			if (ops->undo_action) {
+				snd_pcm_group_for_each_entry(s1, substream) {
+					if (s1 == s) /* failed stream */
+						break;
+					ops->undo_action(s1, state);
+				}
+			}
+			s = NULL; /* unlock all */
+			goto _unlock;
+		}
+	}
+	snd_pcm_group_for_each_entry(s, substream) {
+		ops->post_action(s, state);
+	}
+ _unlock:
+	if (do_lock) {
+		/* unlock streams */
+		snd_pcm_group_for_each_entry(s1, substream) {
+			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,
+				 struct snd_pcm_substream *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,
+			  struct snd_pcm_substream *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,
+				   struct snd_pcm_substream *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,
+				    struct snd_pcm_substream *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(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *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(struct snd_pcm_substream *substream, int state)
+{
+	if (substream->runtime->trigger_master == substream)
+		substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
+}
+
+static void snd_pcm_post_start(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_trigger_tstamp(substream);
+	runtime->hw_ptr_jiffies = jiffies;
+	runtime->hw_ptr_buffer_jiffies = (runtime->buffer_size * HZ) / 
+							    runtime->rate;
+	runtime->status->state = state;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+	    runtime->silence_size > 0)
+		snd_pcm_playback_silence(substream, ULONG_MAX);
+	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
+ * @substream: the PCM substream instance
+ */
+int snd_pcm_start(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_action(&snd_pcm_action_start, substream,
+			      SNDRV_PCM_STATE_RUNNING);
+}
+
+/*
+ * stop callbacks
+ */
+static int snd_pcm_pre_stop(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *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(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *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;
+	}
+	wake_up(&runtime->sleep);
+	wake_up(&runtime->tsleep);
+}
+
+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
+ * @substream: the PCM substream instance
+ * @state: PCM state after stopping the stream
+ *
+ * The state of each stream is then changed to the given state unconditionally.
+ */
+int snd_pcm_stop(struct snd_pcm_substream *substream, int state)
+{
+	return snd_pcm_action(&snd_pcm_action_stop, substream, state);
+}
+
+EXPORT_SYMBOL(snd_pcm_stop);
+
+/**
+ * snd_pcm_drain_done - stop the DMA only when the given stream is playback
+ * @substream: the PCM substream
+ *
+ * After stopping, the state is changed to SETUP.
+ * Unlike snd_pcm_stop(), this affects only the given stream.
+ */
+int snd_pcm_drain_done(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_action_single(&snd_pcm_action_stop, substream,
+				     SNDRV_PCM_STATE_SETUP);
+}
+
+/*
+ * pause callbacks
+ */
+static int snd_pcm_pre_pause(struct snd_pcm_substream *substream, int push)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream, int push)
+{
+	if (substream->runtime->trigger_master != substream)
+		return 0;
+	/* some drivers might use hw_ptr to recover from the pause -
+	   update the hw_ptr now */
+	if (push)
+		snd_pcm_update_hw_ptr(substream);
+	/* The jiffies check in snd_pcm_update_hw_ptr*() is done by
+	 * a delta betwen the current jiffies, this gives a large enough
+	 * delta, effectively to skip the check once.
+	 */
+	substream->runtime->hw_ptr_jiffies = jiffies - HZ * 1000;
+	return substream->ops->trigger(substream,
+				       push ? SNDRV_PCM_TRIGGER_PAUSE_PUSH :
+					      SNDRV_PCM_TRIGGER_PAUSE_RELEASE);
+}
+
+static void snd_pcm_undo_pause(struct snd_pcm_substream *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(struct snd_pcm_substream *substream, int push)
+{
+	struct snd_pcm_runtime *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);
+		wake_up(&runtime->sleep);
+		wake_up(&runtime->tsleep);
+	} else {
+		runtime->status->state = SNDRV_PCM_STATE_RUNNING;
+		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(struct snd_pcm_substream *substream, int push)
+{
+	return snd_pcm_action(&snd_pcm_action_pause, substream, push);
+}
+
+#ifdef CONFIG_PM
+/* suspend */
+
+static int snd_pcm_pre_suspend(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_trigger_tstamp(substream);
+	if (substream->timer)
+		snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSUSPEND,
+				 &runtime->trigger_tstamp);
+	runtime->status->suspended_state = runtime->status->state;
+	runtime->status->state = SNDRV_PCM_STATE_SUSPENDED;
+	wake_up(&runtime->sleep);
+	wake_up(&runtime->tsleep);
+}
+
+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
+ * @substream: the PCM substream
+ *
+ * After this call, all streams are changed to SUSPENDED state.
+ */
+int snd_pcm_suspend(struct snd_pcm_substream *substream)
+{
+	int err;
+	unsigned long flags;
+
+	if (! substream)
+		return 0;
+
+	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;
+}
+
+EXPORT_SYMBOL(snd_pcm_suspend);
+
+/**
+ * snd_pcm_suspend_all - trigger SUSPEND to all substreams in the given pcm
+ * @pcm: the PCM instance
+ *
+ * After this call, all streams are changed to SUSPENDED state.
+ */
+int snd_pcm_suspend_all(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	int stream, err = 0;
+
+	if (! pcm)
+		return 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;
+}
+
+EXPORT_SYMBOL(snd_pcm_suspend_all);
+
+/* resume */
+
+static int snd_pcm_pre_resume(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	if (!(runtime->info & SNDRV_PCM_INFO_RESUME))
+		return -ENOSYS;
+	runtime->trigger_master = substream;
+	return 0;
+}
+
+static int snd_pcm_do_resume(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *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(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_trigger_tstamp(substream);
+	if (substream->timer)
+		snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MRESUME,
+				 &runtime->trigger_tstamp);
+	runtime->status->state = runtime->status->suspended_state;
+}
+
+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(struct snd_pcm_substream *substream)
+{
+	struct snd_card *card = substream->pcm->card;
+	int res;
+
+	snd_power_lock(card);
+	if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0)) >= 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(struct snd_pcm_substream *substream)
+{
+	return -ENOSYS;
+}
+
+#endif /* CONFIG_PM */
+
+/*
+ * xrun ioctl
+ *
+ * Change the RUNNING stream(s) to XRUN state.
+ */
+static int snd_pcm_xrun(struct snd_pcm_substream *substream)
+{
+	struct snd_card *card = substream->pcm->card;
+	struct snd_pcm_runtime *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);
+		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(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
+	if (err < 0)
+		return err;
+	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(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_action_nonatomic(&snd_pcm_action_reset, substream, 0);
+}
+
+/*
+ * prepare ioctl
+ */
+/* we use the second argument for updating f_flags */
+static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream,
+			       int f_flags)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN ||
+	    runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED)
+		return -EBADFD;
+	if (snd_pcm_running(substream))
+		return -EBUSY;
+	substream->f_flags = f_flags;
+	return 0;
+}
+
+static int snd_pcm_do_prepare(struct snd_pcm_substream *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(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *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 - prepare the PCM substream to be triggerable
+ * @substream: the PCM substream instance
+ * @file: file to refer f_flags
+ */
+static int snd_pcm_prepare(struct snd_pcm_substream *substream,
+			   struct file *file)
+{
+	int res;
+	struct snd_card *card = substream->pcm->card;
+	int f_flags;
+
+	if (file)
+		f_flags = file->f_flags;
+	else
+		f_flags = substream->f_flags;
+
+	snd_power_lock(card);
+	if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0)) >= 0)
+		res = snd_pcm_action_nonatomic(&snd_pcm_action_prepare,
+					       substream, f_flags);
+	snd_power_unlock(card);
+	return res;
+}
+
+/*
+ * drain ioctl
+ */
+
+static int snd_pcm_pre_drain_init(struct snd_pcm_substream *substream, int state)
+{
+	substream->runtime->trigger_master = substream;
+	return 0;
+}
+
+static int snd_pcm_do_drain_init(struct snd_pcm_substream *substream, int state)
+{
+	struct snd_pcm_runtime *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 new_state = snd_pcm_capture_avail(runtime) > 0 ?
+				SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP;
+			snd_pcm_do_stop(substream, new_state);
+			snd_pcm_post_stop(substream, new_state);
+		}
+	}
+	return 0;
+}
+
+static void snd_pcm_post_drain_init(struct snd_pcm_substream *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
+};
+
+static int snd_pcm_drop(struct snd_pcm_substream *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(struct snd_pcm_substream *substream,
+			 struct file *file)
+{
+	struct snd_card *card;
+	struct snd_pcm_runtime *runtime;
+	struct snd_pcm_substream *s;
+	wait_queue_t wait;
+	int result = 0;
+	int nonblock = 0;
+
+	card = substream->pcm->card;
+	runtime = substream->runtime;
+
+	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);
+		if (result < 0) {
+			snd_power_unlock(card);
+			return result;
+		}
+	}
+
+	if (file) {
+		if (file->f_flags & O_NONBLOCK)
+			nonblock = 1;
+	} else if (substream->f_flags & O_NONBLOCK)
+		nonblock = 1;
+
+	down_read(&snd_pcm_link_rwsem);
+	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 unlock;
+	/* in non-blocking, we don't wait in ioctl but let caller poll */
+	if (nonblock) {
+		result = -EAGAIN;
+		goto unlock;
+	}
+
+	for (;;) {
+		long tout;
+		struct snd_pcm_runtime *to_check;
+		if (signal_pending(current)) {
+			result = -ERESTARTSYS;
+			break;
+		}
+		/* find a substream to drain */
+		to_check = NULL;
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
+				continue;
+			runtime = s->runtime;
+			if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
+				to_check = runtime;
+				break;
+			}
+		}
+		if (!to_check)
+			break; /* all drained */
+		init_waitqueue_entry(&wait, current);
+		add_wait_queue(&to_check->sleep, &wait);
+		set_current_state(TASK_INTERRUPTIBLE);
+		snd_pcm_stream_unlock_irq(substream);
+		up_read(&snd_pcm_link_rwsem);
+		snd_power_unlock(card);
+		tout = schedule_timeout(10 * HZ);
+		snd_power_lock(card);
+		down_read(&snd_pcm_link_rwsem);
+		snd_pcm_stream_lock_irq(substream);
+		remove_wait_queue(&to_check->sleep, &wait);
+		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;
+		}
+	}
+
+ unlock:
+	snd_pcm_stream_unlock_irq(substream);
+	up_read(&snd_pcm_link_rwsem);
+	snd_power_unlock(card);
+
+	return result;
+}
+
+/*
+ * drop ioctl
+ *
+ * Immediately put all linked substreams into SETUP state.
+ */
+static int snd_pcm_drop(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime;
+	struct snd_card *card;
+	int result = 0;
+	
+	if (PCM_RUNTIME_CHECK(substream))
+		return -ENXIO;
+	runtime = substream->runtime;
+	card = substream->pcm->card;
+
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN ||
+	    runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED ||
+	    runtime->status->state == SNDRV_PCM_STATE_SUSPENDED)
+		return -EBADFD;
+
+	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);
+
+	return result;
+}
+
+
+/* WARNING: Don't forget to fput back the file */
+static struct file *snd_pcm_file_fd(int fd)
+{
+	struct file *file;
+	struct inode *inode;
+	unsigned int minor;
+
+	file = fget(fd);
+	if (!file)
+		return NULL;
+	inode = file->f_path.dentry->d_inode;
+	if (!S_ISCHR(inode->i_mode) ||
+	    imajor(inode) != snd_major) {
+		fput(file);
+		return NULL;
+	}
+	minor = iminor(inode);
+	if (!snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_PLAYBACK) &&
+	    !snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_CAPTURE)) {
+		fput(file);
+		return NULL;
+	}
+	return file;
+}
+
+/*
+ * PCM link handling
+ */
+static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
+{
+	int res = 0;
+	struct file *file;
+	struct snd_pcm_file *pcm_file;
+	struct snd_pcm_substream *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(struct snd_pcm_group), 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(struct snd_pcm_substream *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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_substream *s;
+	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_entry(s, substream) {
+			relink_to_local(s);
+			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(struct snd_pcm_hw_params *params,
+			       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval 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(struct snd_pcm_hw_params *params,
+			       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval 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(struct snd_pcm_hw_params *params,
+				   struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval 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(struct snd_pcm_hw_params *params,
+				   struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval 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(struct snd_pcm_hw_params *params,
+				  struct snd_pcm_hw_rule *rule)
+{
+	unsigned int k;
+	struct snd_interval *i = hw_param_interval(params, rule->deps[0]);
+	struct snd_mask m;
+	struct snd_mask *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(struct snd_pcm_hw_params *params,
+				       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval 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 };
+
+const struct snd_pcm_hw_constraint_list snd_pcm_known_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+};
+
+static int snd_pcm_hw_rule_rate(struct snd_pcm_hw_params *params,
+				struct snd_pcm_hw_rule *rule)
+{
+	struct snd_pcm_hardware *hw = rule->private;
+	return snd_interval_list(hw_param_interval(params, rule->var),
+				 snd_pcm_known_rates.count,
+				 snd_pcm_known_rates.list, hw->rates);
+}		
+
+static int snd_pcm_hw_rule_buffer_bytes_max(struct snd_pcm_hw_params *params,
+					    struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval t;
+	struct snd_pcm_substream *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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_pcm_hw_constraints *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(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_pcm_hardware *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);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, hw->formats);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_SUBFORMAT, 1 << SNDRV_PCM_SUBFORMAT_STD);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
+					   hw->channels_min, hw->channels_max);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE,
+					   hw->rate_min, hw->rate_max);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+					   hw->period_bytes_min, hw->period_bytes_max);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIODS,
+					   hw->periods_min, hw->periods_max);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+					   hw->period_bytes_min, hw->buffer_bytes_max);
+	if (err < 0)
+		return err;
+
+	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);
+		if (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_integer(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+
+	return 0;
+}
+
+static void pcm_release_private(struct snd_pcm_substream *substream)
+{
+	snd_pcm_unlink(substream);
+}
+
+void snd_pcm_release_substream(struct snd_pcm_substream *substream)
+{
+	substream->ref_count--;
+	if (substream->ref_count > 0)
+		return;
+
+	snd_pcm_drop(substream);
+	if (substream->hw_opened) {
+		if (substream->ops->hw_free != NULL)
+			substream->ops->hw_free(substream);
+		substream->ops->close(substream);
+		substream->hw_opened = 0;
+	}
+	if (pm_qos_request_active(&substream->latency_pm_qos_req))
+		pm_qos_remove_request(&substream->latency_pm_qos_req);
+	if (substream->pcm_release) {
+		substream->pcm_release(substream);
+		substream->pcm_release = NULL;
+	}
+	snd_pcm_detach_substream(substream);
+}
+
+EXPORT_SYMBOL(snd_pcm_release_substream);
+
+int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
+			   struct file *file,
+			   struct snd_pcm_substream **rsubstream)
+{
+	struct snd_pcm_substream *substream;
+	int err;
+
+	err = snd_pcm_attach_substream(pcm, stream, file, &substream);
+	if (err < 0)
+		return err;
+	if (substream->ref_count > 1) {
+		*rsubstream = substream;
+		return 0;
+	}
+
+	err = snd_pcm_hw_constraints_init(substream);
+	if (err < 0) {
+		snd_printd("snd_pcm_hw_constraints_init failed\n");
+		goto error;
+	}
+
+	if ((err = substream->ops->open(substream)) < 0)
+		goto error;
+
+	substream->hw_opened = 1;
+
+	err = snd_pcm_hw_constraints_complete(substream);
+	if (err < 0) {
+		snd_printd("snd_pcm_hw_constraints_complete failed\n");
+		goto error;
+	}
+
+	*rsubstream = substream;
+	return 0;
+
+ error:
+	snd_pcm_release_substream(substream);
+	return err;
+}
+
+EXPORT_SYMBOL(snd_pcm_open_substream);
+
+static int snd_pcm_open_file(struct file *file,
+			     struct snd_pcm *pcm,
+			     int stream,
+			     struct snd_pcm_file **rpcm_file)
+{
+	struct snd_pcm_file *pcm_file;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_str *str;
+	int err;
+
+	if (rpcm_file)
+		*rpcm_file = NULL;
+
+	err = snd_pcm_open_substream(pcm, stream, file, &substream);
+	if (err < 0)
+		return err;
+
+	pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL);
+	if (pcm_file == NULL) {
+		snd_pcm_release_substream(substream);
+		return -ENOMEM;
+	}
+	pcm_file->substream = substream;
+	if (substream->ref_count == 1) {
+		str = substream->pstr;
+		substream->file = pcm_file;
+		substream->pcm_release = pcm_release_private;
+	}
+	file->private_data = pcm_file;
+	if (rpcm_file)
+		*rpcm_file = pcm_file;
+	return 0;
+}
+
+static int snd_pcm_playback_open(struct inode *inode, struct file *file)
+{
+	struct snd_pcm *pcm;
+	int err = nonseekable_open(inode, file);
+	if (err < 0)
+		return err;
+	pcm = snd_lookup_minor_data(iminor(inode),
+				    SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
+	return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+static int snd_pcm_capture_open(struct inode *inode, struct file *file)
+{
+	struct snd_pcm *pcm;
+	int err = nonseekable_open(inode, file);
+	if (err < 0)
+		return err;
+	pcm = snd_lookup_minor_data(iminor(inode),
+				    SNDRV_DEVICE_TYPE_PCM_CAPTURE);
+	return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)
+{
+	int err;
+	struct snd_pcm_file *pcm_file;
+	wait_queue_t wait;
+
+	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);
+	mutex_lock(&pcm->open_mutex);
+	while (1) {
+		err = snd_pcm_open_file(file, pcm, stream, &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);
+		mutex_unlock(&pcm->open_mutex);
+		schedule();
+		mutex_lock(&pcm->open_mutex);
+		if (signal_pending(current)) {
+			err = -ERESTARTSYS;
+			break;
+		}
+	}
+	remove_wait_queue(&pcm->open_wait, &wait);
+	mutex_unlock(&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)
+{
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_file *pcm_file;
+
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	if (snd_BUG_ON(!substream))
+		return -ENXIO;
+	pcm = substream->pcm;
+	mutex_lock(&pcm->open_mutex);
+	snd_pcm_release_substream(substream);
+	kfree(pcm_file);
+	mutex_unlock(&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(struct snd_pcm_substream *substream,
+						 snd_pcm_uframes_t frames)
+{
+	struct snd_pcm_runtime *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;
+	case SNDRV_PCM_STATE_SUSPENDED:
+		ret = -ESTRPIPE;
+		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;
+	appl_ptr = runtime->control->appl_ptr - frames;
+	if (appl_ptr < 0)
+		appl_ptr += runtime->boundary;
+	runtime->control->appl_ptr = appl_ptr;
+	ret = frames;
+ __end:
+	snd_pcm_stream_unlock_irq(substream);
+	return ret;
+}
+
+static snd_pcm_sframes_t snd_pcm_capture_rewind(struct snd_pcm_substream *substream,
+						snd_pcm_uframes_t frames)
+{
+	struct snd_pcm_runtime *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;
+	case SNDRV_PCM_STATE_SUSPENDED:
+		ret = -ESTRPIPE;
+		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;
+	appl_ptr = runtime->control->appl_ptr - frames;
+	if (appl_ptr < 0)
+		appl_ptr += runtime->boundary;
+	runtime->control->appl_ptr = appl_ptr;
+	ret = frames;
+ __end:
+	snd_pcm_stream_unlock_irq(substream);
+	return ret;
+}
+
+static snd_pcm_sframes_t snd_pcm_playback_forward(struct snd_pcm_substream *substream,
+						  snd_pcm_uframes_t frames)
+{
+	struct snd_pcm_runtime *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;
+	case SNDRV_PCM_STATE_SUSPENDED:
+		ret = -ESTRPIPE;
+		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;
+	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;
+	ret = frames;
+ __end:
+	snd_pcm_stream_unlock_irq(substream);
+	return ret;
+}
+
+static snd_pcm_sframes_t snd_pcm_capture_forward(struct snd_pcm_substream *substream,
+						 snd_pcm_uframes_t frames)
+{
+	struct snd_pcm_runtime *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;
+	case SNDRV_PCM_STATE_SUSPENDED:
+		ret = -ESTRPIPE;
+		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;
+	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;
+	ret = frames;
+ __end:
+	snd_pcm_stream_unlock_irq(substream);
+	return ret;
+}
+
+static int snd_pcm_hwsync(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *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(struct snd_pcm_substream *substream,
+			 snd_pcm_sframes_t __user *res)
+{
+	struct snd_pcm_runtime *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);
+		n += runtime->delay;
+		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(struct snd_pcm_substream *substream,
+			    struct snd_pcm_sync_ptr __user *_sync_ptr)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_pcm_sync_ptr sync_ptr;
+	volatile struct snd_pcm_mmap_status *status;
+	volatile struct snd_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 snd_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_tstamp(struct snd_pcm_substream *substream, int __user *_arg)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int arg;
+	
+	if (get_user(arg, _arg))
+		return -EFAULT;
+	if (arg < 0 || arg > SNDRV_PCM_TSTAMP_TYPE_LAST)
+		return -EINVAL;
+	runtime->tstamp_type = SNDRV_PCM_TSTAMP_TYPE_GETTIMEOFDAY;
+	if (arg == SNDRV_PCM_TSTAMP_TYPE_MONOTONIC)
+		runtime->tstamp_type = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
+	return 0;
+}
+		
+static int snd_pcm_common_ioctl1(struct file *file,
+				 struct snd_pcm_substream *substream,
+				 unsigned int cmd, void __user *arg)
+{
+	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:	/* just for compatibility */
+		return 0;
+	case SNDRV_PCM_IOCTL_TTSTAMP:
+		return snd_pcm_tstamp(substream, arg);
+	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, file);
+	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);
+#ifdef CONFIG_SND_SUPPORT_OLD_API
+	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);
+#endif
+	case SNDRV_PCM_IOCTL_DRAIN:
+		return snd_pcm_drain(substream, file);
+	case SNDRV_PCM_IOCTL_DROP:
+		return snd_pcm_drop(substream);
+	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;
+	}
+	}
+	snd_printd("unknown ioctl = 0x%x\n", cmd);
+	return -ENOTTY;
+}
+
+static int snd_pcm_playback_ioctl1(struct file *file,
+				   struct snd_pcm_substream *substream,
+				   unsigned int cmd, void __user *arg)
+{
+	if (snd_BUG_ON(!substream))
+		return -ENXIO;
+	if (snd_BUG_ON(substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
+		return -EINVAL;
+	switch (cmd) {
+	case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
+	{
+		struct snd_xferi xferi;
+		struct snd_xferi __user *_xferi = arg;
+		struct snd_pcm_runtime *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:
+	{
+		struct snd_xfern xfern;
+		struct snd_xfern __user *_xfern = arg;
+		struct snd_pcm_runtime *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 = memdup_user(xfern.bufs,
+				   sizeof(void *) * runtime->channels);
+		if (IS_ERR(bufs))
+			return PTR_ERR(bufs);
+		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;
+	}
+	}
+	return snd_pcm_common_ioctl1(file, substream, cmd, arg);
+}
+
+static int snd_pcm_capture_ioctl1(struct file *file,
+				  struct snd_pcm_substream *substream,
+				  unsigned int cmd, void __user *arg)
+{
+	if (snd_BUG_ON(!substream))
+		return -ENXIO;
+	if (snd_BUG_ON(substream->stream != SNDRV_PCM_STREAM_CAPTURE))
+		return -EINVAL;
+	switch (cmd) {
+	case SNDRV_PCM_IOCTL_READI_FRAMES:
+	{
+		struct snd_xferi xferi;
+		struct snd_xferi __user *_xferi = arg;
+		struct snd_pcm_runtime *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:
+	{
+		struct snd_xfern xfern;
+		struct snd_xfern __user *_xfern = arg;
+		struct snd_pcm_runtime *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 = memdup_user(xfern.bufs,
+				   sizeof(void *) * runtime->channels);
+		if (IS_ERR(bufs))
+			return PTR_ERR(bufs);
+		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(file, substream, cmd, arg);
+}
+
+static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd,
+				   unsigned long arg)
+{
+	struct snd_pcm_file *pcm_file;
+
+	pcm_file = file->private_data;
+
+	if (((cmd >> 8) & 0xff) != 'A')
+		return -ENOTTY;
+
+	return snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd,
+				       (void __user *)arg);
+}
+
+static long snd_pcm_capture_ioctl(struct file *file, unsigned int cmd,
+				  unsigned long arg)
+{
+	struct snd_pcm_file *pcm_file;
+
+	pcm_file = file->private_data;
+
+	if (((cmd >> 8) & 0xff) != 'A')
+		return -ENOTTY;
+
+	return snd_pcm_capture_ioctl1(file, pcm_file->substream, cmd,
+				      (void __user *)arg);
+}
+
+int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream,
+			 unsigned int cmd, void *arg)
+{
+	mm_segment_t fs;
+	int result;
+	
+	fs = snd_enter_user();
+	switch (substream->stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		result = snd_pcm_playback_ioctl1(NULL, substream, cmd,
+						 (void __user *)arg);
+		break;
+	case SNDRV_PCM_STREAM_CAPTURE:
+		result = snd_pcm_capture_ioctl1(NULL, substream, cmd,
+						(void __user *)arg);
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	snd_leave_user(fs);
+	return result;
+}
+
+EXPORT_SYMBOL(snd_pcm_kernel_ioctl);
+
+static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count,
+			    loff_t * offset)
+{
+	struct snd_pcm_file *pcm_file;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+	snd_pcm_sframes_t result;
+
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	if (PCM_RUNTIME_CHECK(substream))
+		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)
+{
+	struct snd_pcm_file *pcm_file;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+	snd_pcm_sframes_t result;
+
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	if (PCM_RUNTIME_CHECK(substream))
+		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_write(substream, buf, count);
+	if (result > 0)
+		result = frames_to_bytes(runtime, result);
+	return result;
+}
+
+static ssize_t snd_pcm_aio_read(struct kiocb *iocb, const struct iovec *iov,
+			     unsigned long nr_segs, loff_t pos)
+
+{
+	struct snd_pcm_file *pcm_file;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+	snd_pcm_sframes_t result;
+	unsigned long i;
+	void __user **bufs;
+	snd_pcm_uframes_t frames;
+
+	pcm_file = iocb->ki_filp->private_data;
+	substream = pcm_file->substream;
+	if (PCM_RUNTIME_CHECK(substream))
+		return -ENXIO;
+	runtime = substream->runtime;
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+	if (nr_segs > 1024 || nr_segs != runtime->channels)
+		return -EINVAL;
+	if (!frame_aligned(runtime, iov->iov_len))
+		return -EINVAL;
+	frames = bytes_to_samples(runtime, iov->iov_len);
+	bufs = kmalloc(sizeof(void *) * nr_segs, GFP_KERNEL);
+	if (bufs == NULL)
+		return -ENOMEM;
+	for (i = 0; i < nr_segs; ++i)
+		bufs[i] = iov[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_aio_write(struct kiocb *iocb, const struct iovec *iov,
+			      unsigned long nr_segs, loff_t pos)
+{
+	struct snd_pcm_file *pcm_file;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+	snd_pcm_sframes_t result;
+	unsigned long i;
+	void __user **bufs;
+	snd_pcm_uframes_t frames;
+
+	pcm_file = iocb->ki_filp->private_data;
+	substream = pcm_file->substream;
+	if (PCM_RUNTIME_CHECK(substream))
+		return -ENXIO;
+	runtime = substream->runtime;
+	if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		return -EBADFD;
+	if (nr_segs > 128 || nr_segs != runtime->channels ||
+	    !frame_aligned(runtime, iov->iov_len))
+		return -EINVAL;
+	frames = bytes_to_samples(runtime, iov->iov_len);
+	bufs = kmalloc(sizeof(void *) * nr_segs, GFP_KERNEL);
+	if (bufs == NULL)
+		return -ENOMEM;
+	for (i = 0; i < nr_segs; ++i)
+		bufs[i] = iov[i].iov_base;
+	result = snd_pcm_lib_writev(substream, bufs, frames);
+	if (result > 0)
+		result = frames_to_bytes(runtime, result);
+	kfree(bufs);
+	return result;
+}
+
+static unsigned int snd_pcm_playback_poll(struct file *file, poll_table * wait)
+{
+	struct snd_pcm_file *pcm_file;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+        unsigned int mask;
+	snd_pcm_uframes_t avail;
+
+	pcm_file = file->private_data;
+
+	substream = pcm_file->substream;
+	if (PCM_RUNTIME_CHECK(substream))
+		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)
+{
+	struct snd_pcm_file *pcm_file;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+        unsigned int mask;
+	snd_pcm_uframes_t avail;
+
+	pcm_file = file->private_data;
+
+	substream = pcm_file->substream;
+	if (PCM_RUNTIME_CHECK(substream))
+		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 int snd_pcm_mmap_status_fault(struct vm_area_struct *area,
+						struct vm_fault *vmf)
+{
+	struct snd_pcm_substream *substream = area->vm_private_data;
+	struct snd_pcm_runtime *runtime;
+	
+	if (substream == NULL)
+		return VM_FAULT_SIGBUS;
+	runtime = substream->runtime;
+	vmf->page = virt_to_page(runtime->status);
+	get_page(vmf->page);
+	return 0;
+}
+
+static const struct vm_operations_struct snd_pcm_vm_ops_status =
+{
+	.fault =	snd_pcm_mmap_status_fault,
+};
+
+static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file,
+			       struct vm_area_struct *area)
+{
+	struct snd_pcm_runtime *runtime;
+	long size;
+	if (!(area->vm_flags & VM_READ))
+		return -EINVAL;
+	runtime = substream->runtime;
+	size = area->vm_end - area->vm_start;
+	if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)))
+		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 int snd_pcm_mmap_control_fault(struct vm_area_struct *area,
+						struct vm_fault *vmf)
+{
+	struct snd_pcm_substream *substream = area->vm_private_data;
+	struct snd_pcm_runtime *runtime;
+	
+	if (substream == NULL)
+		return VM_FAULT_SIGBUS;
+	runtime = substream->runtime;
+	vmf->page = virt_to_page(runtime->control);
+	get_page(vmf->page);
+	return 0;
+}
+
+static const struct vm_operations_struct snd_pcm_vm_ops_control =
+{
+	.fault =	snd_pcm_mmap_control_fault,
+};
+
+static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file *file,
+				struct vm_area_struct *area)
+{
+	struct snd_pcm_runtime *runtime;
+	long size;
+	if (!(area->vm_flags & VM_READ))
+		return -EINVAL;
+	runtime = substream->runtime;
+	size = area->vm_end - area->vm_start;
+	if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)))
+		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(struct snd_pcm_substream *substream, struct file *file,
+			       struct vm_area_struct *area)
+{
+	return -ENXIO;
+}
+static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file *file,
+				struct vm_area_struct *area)
+{
+	return -ENXIO;
+}
+#endif /* coherent mmap */
+
+static inline struct page *
+snd_pcm_default_page_ops(struct snd_pcm_substream *substream, unsigned long ofs)
+{
+	void *vaddr = substream->runtime->dma_area + ofs;
+#if defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT)
+	if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV)
+		return virt_to_page(CAC_ADDR(vaddr));
+#endif
+#if defined(CONFIG_PPC32) && defined(CONFIG_NOT_COHERENT_CACHE)
+	if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV) {
+		dma_addr_t addr = substream->runtime->dma_addr + ofs;
+		addr -= get_dma_offset(substream->dma_buffer.dev.dev);
+		/* assume dma_handle set via pfn_to_phys() in
+		 * mm/dma-noncoherent.c
+		 */
+		return pfn_to_page(addr >> PAGE_SHIFT);
+	}
+#endif
+	return virt_to_page(vaddr);
+}
+
+/*
+ * fault callback for mmapping a RAM page
+ */
+static int snd_pcm_mmap_data_fault(struct vm_area_struct *area,
+						struct vm_fault *vmf)
+{
+	struct snd_pcm_substream *substream = area->vm_private_data;
+	struct snd_pcm_runtime *runtime;
+	unsigned long offset;
+	struct page * page;
+	size_t dma_bytes;
+	
+	if (substream == NULL)
+		return VM_FAULT_SIGBUS;
+	runtime = substream->runtime;
+	offset = vmf->pgoff << PAGE_SHIFT;
+	dma_bytes = PAGE_ALIGN(runtime->dma_bytes);
+	if (offset > dma_bytes - PAGE_SIZE)
+		return VM_FAULT_SIGBUS;
+	if (substream->ops->page)
+		page = substream->ops->page(substream, offset);
+	else
+		page = snd_pcm_default_page_ops(substream, offset);
+	if (!page)
+		return VM_FAULT_SIGBUS;
+	get_page(page);
+	vmf->page = page;
+	return 0;
+}
+
+static const struct vm_operations_struct snd_pcm_vm_ops_data = {
+	.open =		snd_pcm_mmap_data_open,
+	.close =	snd_pcm_mmap_data_close,
+};
+
+static const struct vm_operations_struct snd_pcm_vm_ops_data_fault = {
+	.open =		snd_pcm_mmap_data_open,
+	.close =	snd_pcm_mmap_data_close,
+	.fault =	snd_pcm_mmap_data_fault,
+};
+
+#ifndef ARCH_HAS_DMA_MMAP_COHERENT
+/* This should be defined / handled globally! */
+#ifdef CONFIG_ARM
+#define ARCH_HAS_DMA_MMAP_COHERENT
+#endif
+#endif
+
+/*
+ * mmap the DMA buffer on RAM
+ */
+static int snd_pcm_default_mmap(struct snd_pcm_substream *substream,
+				struct vm_area_struct *area)
+{
+	area->vm_flags |= VM_RESERVED;
+#ifdef ARCH_HAS_DMA_MMAP_COHERENT
+	if (!substream->ops->page &&
+	    substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV)
+		return dma_mmap_coherent(substream->dma_buffer.dev.dev,
+					 area,
+					 substream->runtime->dma_area,
+					 substream->runtime->dma_addr,
+					 area->vm_end - area->vm_start);
+#elif defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT)
+	if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV &&
+	    !plat_device_is_coherent(substream->dma_buffer.dev.dev))
+		area->vm_page_prot = pgprot_noncached(area->vm_page_prot);
+#endif /* ARCH_HAS_DMA_MMAP_COHERENT */
+	/* mmap with fault handler */
+	area->vm_ops = &snd_pcm_vm_ops_data_fault;
+	return 0;
+}
+
+/*
+ * mmap the DMA buffer on I/O memory area
+ */
+#if SNDRV_PCM_INFO_MMAP_IOMEM
+int snd_pcm_lib_mmap_iomem(struct snd_pcm_substream *substream,
+			   struct vm_area_struct *area)
+{
+	long size;
+	unsigned long offset;
+
+	area->vm_page_prot = pgprot_noncached(area->vm_page_prot);
+	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;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_pcm_lib_mmap_iomem);
+#endif /* SNDRV_PCM_INFO_MMAP */
+
+/* mmap callback with pgprot_noncached */
+int snd_pcm_lib_mmap_noncached(struct snd_pcm_substream *substream,
+			       struct vm_area_struct *area)
+{
+	area->vm_page_prot = pgprot_noncached(area->vm_page_prot);
+	return snd_pcm_default_mmap(substream, area);
+}
+EXPORT_SYMBOL(snd_pcm_lib_mmap_noncached);
+
+/*
+ * mmap DMA buffer
+ */
+int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file,
+		      struct vm_area_struct *area)
+{
+	struct snd_pcm_runtime *runtime;
+	long size;
+	unsigned long offset;
+	size_t dma_bytes;
+	int err;
+
+	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;
+	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;
+
+	area->vm_ops = &snd_pcm_vm_ops_data;
+	area->vm_private_data = substream;
+	if (substream->ops->mmap)
+		err = substream->ops->mmap(substream, area);
+	else
+		err = snd_pcm_default_mmap(substream, area);
+	if (!err)
+		atomic_inc(&substream->mmap_count);
+	return err;
+}
+
+EXPORT_SYMBOL(snd_pcm_mmap_data);
+
+static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
+{
+	struct snd_pcm_file * pcm_file;
+	struct snd_pcm_substream *substream;	
+	unsigned long offset;
+	
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	if (PCM_RUNTIME_CHECK(substream))
+		return -ENXIO;
+
+	offset = area->vm_pgoff << PAGE_SHIFT;
+	switch (offset) {
+	case SNDRV_PCM_MMAP_OFFSET_STATUS:
+		if (pcm_file->no_compat_mmap)
+			return -ENXIO;
+		return snd_pcm_mmap_status(substream, file, area);
+	case SNDRV_PCM_MMAP_OFFSET_CONTROL:
+		if (pcm_file->no_compat_mmap)
+			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)
+{
+	struct snd_pcm_file * pcm_file;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+
+	pcm_file = file->private_data;
+	substream = pcm_file->substream;
+	if (PCM_RUNTIME_CHECK(substream))
+		return -ENXIO;
+	runtime = substream->runtime;
+	return fasync_helper(fd, file, on, &runtime->fasync);
+}
+
+/*
+ * 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
+ */
+
+#ifdef CONFIG_SND_SUPPORT_OLD_API
+#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(struct snd_pcm_hw_params *params,
+					       struct snd_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 snd_pcm_hw_params_old *oparams,
+					     struct snd_pcm_hw_params *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(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params_old __user * _oparams)
+{
+	struct snd_pcm_hw_params *params;
+	struct snd_pcm_hw_params_old *oparams = NULL;
+	int err;
+
+	params = kmalloc(sizeof(*params), GFP_KERNEL);
+	if (!params)
+		return -ENOMEM;
+
+	oparams = memdup_user(_oparams, sizeof(*oparams));
+	if (IS_ERR(oparams)) {
+		err = PTR_ERR(oparams);
+		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;
+	}
+
+	kfree(oparams);
+out:
+	kfree(params);
+	return err;
+}
+
+static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params_old __user * _oparams)
+{
+	struct snd_pcm_hw_params *params;
+	struct snd_pcm_hw_params_old *oparams = NULL;
+	int err;
+
+	params = kmalloc(sizeof(*params), GFP_KERNEL);
+	if (!params)
+		return -ENOMEM;
+
+	oparams = memdup_user(_oparams, sizeof(*oparams));
+	if (IS_ERR(oparams)) {
+		err = PTR_ERR(oparams);
+		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;
+	}
+
+	kfree(oparams);
+out:
+	kfree(params);
+	return err;
+}
+#endif /* CONFIG_SND_SUPPORT_OLD_API */
+
+#ifndef CONFIG_MMU
+static unsigned long snd_pcm_get_unmapped_area(struct file *file,
+					       unsigned long addr,
+					       unsigned long len,
+					       unsigned long pgoff,
+					       unsigned long flags)
+{
+	struct snd_pcm_file *pcm_file = file->private_data;
+	struct snd_pcm_substream *substream = pcm_file->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long offset = pgoff << PAGE_SHIFT;
+
+	switch (offset) {
+	case SNDRV_PCM_MMAP_OFFSET_STATUS:
+		return (unsigned long)runtime->status;
+	case SNDRV_PCM_MMAP_OFFSET_CONTROL:
+		return (unsigned long)runtime->control;
+	default:
+		return (unsigned long)runtime->dma_area + offset;
+	}
+}
+#else
+# define snd_pcm_get_unmapped_area NULL
+#endif
+
+/*
+ *  Register section
+ */
+
+const struct file_operations snd_pcm_f_ops[2] = {
+	{
+		.owner =		THIS_MODULE,
+		.write =		snd_pcm_write,
+		.aio_write =		snd_pcm_aio_write,
+		.open =			snd_pcm_playback_open,
+		.release =		snd_pcm_release,
+		.llseek =		no_llseek,
+		.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,
+		.get_unmapped_area =	snd_pcm_get_unmapped_area,
+	},
+	{
+		.owner =		THIS_MODULE,
+		.read =			snd_pcm_read,
+		.aio_read =		snd_pcm_aio_read,
+		.open =			snd_pcm_capture_open,
+		.release =		snd_pcm_release,
+		.llseek =		no_llseek,
+		.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,
+		.get_unmapped_area =	snd_pcm_get_unmapped_area,
+	}
+};
diff --git a/linux/sound/core/pcm_timer.c b/linux/sound/core/pcm_timer.c
new file mode 100644
index 0000000..b01d948
--- /dev/null
+++ b/linux/sound/core/pcm_timer.c
@@ -0,0 +1,141 @@
+/*
+ *  Digital Audio (PCM) abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <linux/gcd.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/timer.h>
+
+/*
+ *  Timer functions
+ */
+
+void snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream)
+{
+	unsigned long rate, mult, fsize, l, post;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	
+        mult = 1000000000;
+	rate = runtime->rate;
+	if (snd_BUG_ON(!rate))
+		return;
+	l = gcd(mult, rate);
+	mult /= l;
+	rate /= l;
+	fsize = runtime->period_size;
+	if (snd_BUG_ON(!fsize))
+		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(struct snd_timer * timer)
+{
+	struct snd_pcm_substream *substream;
+	
+	substream = timer->private_data;
+	return substream->runtime ? substream->runtime->timer_resolution : 0;
+}
+
+static int snd_pcm_timer_start(struct snd_timer * timer)
+{
+	struct snd_pcm_substream *substream;
+	
+	substream = snd_timer_chip(timer);
+	substream->timer_running = 1;
+	return 0;
+}
+
+static int snd_pcm_timer_stop(struct snd_timer * timer)
+{
+	struct snd_pcm_substream *substream;
+	
+	substream = snd_timer_chip(timer);
+	substream->timer_running = 0;
+	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(struct snd_timer *timer)
+{
+	struct snd_pcm_substream *substream = timer->private_data;
+	substream->timer = NULL;
+}
+
+void snd_pcm_timer_init(struct snd_pcm_substream *substream)
+{
+	struct snd_timer_id tid;
+	struct snd_timer *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(struct snd_pcm_substream *substream)
+{
+	if (substream->timer) {
+		snd_device_free(substream->pcm->card, substream->timer);
+		substream->timer = NULL;
+	}
+}
diff --git a/linux/sound/core/rawmidi.c b/linux/sound/core/rawmidi.c
new file mode 100644
index 0000000..cbbed0d
--- /dev/null
+++ b/linux/sound/core/rawmidi.c
@@ -0,0 +1,1717 @@
+/*
+ *  Abstract layer for MIDI v1.0 stream
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/core.h>
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/mutex.h>
+#include <linux/moduleparam.h>
+#include <linux/delay.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@perex.cz>");
+MODULE_DESCRIPTION("Midlevel RawMidi code for ALSA.");
+MODULE_LICENSE("GPL");
+
+#ifdef CONFIG_SND_OSSEMUL
+static int midi_map[SNDRV_CARDS];
+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(struct snd_rawmidi *rawmidi);
+static int snd_rawmidi_dev_free(struct snd_device *device);
+static int snd_rawmidi_dev_register(struct snd_device *device);
+static int snd_rawmidi_dev_disconnect(struct snd_device *device);
+
+static LIST_HEAD(snd_rawmidi_devices);
+static DEFINE_MUTEX(register_mutex);
+
+static struct snd_rawmidi *snd_rawmidi_search(struct snd_card *card, int device)
+{
+	struct snd_rawmidi *rawmidi;
+
+	list_for_each_entry(rawmidi, &snd_rawmidi_devices, list)
+		if (rawmidi->card == card && rawmidi->device == device)
+			return rawmidi;
+	return NULL;
+}
+
+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(struct snd_rawmidi_substream *substream)
+{
+	struct snd_rawmidi_runtime *runtime = substream->runtime;
+	return runtime->avail >= runtime->avail_min;
+}
+
+static inline int snd_rawmidi_ready_append(struct snd_rawmidi_substream *substream,
+					   size_t count)
+{
+	struct snd_rawmidi_runtime *runtime = substream->runtime;
+	return runtime->avail >= runtime->avail_min &&
+	       (!substream->append || runtime->avail >= count);
+}
+
+static void snd_rawmidi_input_event_tasklet(unsigned long data)
+{
+	struct snd_rawmidi_substream *substream = (struct snd_rawmidi_substream *)data;
+	substream->runtime->event(substream);
+}
+
+static void snd_rawmidi_output_trigger_tasklet(unsigned long data)
+{
+	struct snd_rawmidi_substream *substream = (struct snd_rawmidi_substream *)data;
+	substream->ops->trigger(substream, 1);
+}
+
+static int snd_rawmidi_runtime_create(struct snd_rawmidi_substream *substream)
+{
+	struct snd_rawmidi_runtime *runtime;
+
+	if ((runtime = kzalloc(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(struct snd_rawmidi_substream *substream)
+{
+	struct snd_rawmidi_runtime *runtime = substream->runtime;
+
+	kfree(runtime->buffer);
+	kfree(runtime);
+	substream->runtime = NULL;
+	return 0;
+}
+
+static inline void snd_rawmidi_output_trigger(struct snd_rawmidi_substream *substream,int up)
+{
+	if (!substream->opened)
+		return;
+	if (up) {
+		tasklet_schedule(&substream->runtime->tasklet);
+	} else {
+		tasklet_kill(&substream->runtime->tasklet);
+		substream->ops->trigger(substream, 0);
+	}
+}
+
+static void snd_rawmidi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	if (!substream->opened)
+		return;
+	substream->ops->trigger(substream, up);
+	if (!up && substream->runtime->event)
+		tasklet_kill(&substream->runtime->tasklet);
+}
+
+int snd_rawmidi_drop_output(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_rawmidi_runtime *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(struct snd_rawmidi_substream *substream)
+{
+	int err;
+	long timeout;
+	struct snd_rawmidi_runtime *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(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_rawmidi_runtime *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;
+}
+
+/* look for an available substream for the given stream direction;
+ * if a specific subdevice is given, try to assign it
+ */
+static int assign_substream(struct snd_rawmidi *rmidi, int subdevice,
+			    int stream, int mode,
+			    struct snd_rawmidi_substream **sub_ret)
+{
+	struct snd_rawmidi_substream *substream;
+	struct snd_rawmidi_str *s = &rmidi->streams[stream];
+	static unsigned int info_flags[2] = {
+		[SNDRV_RAWMIDI_STREAM_OUTPUT] = SNDRV_RAWMIDI_INFO_OUTPUT,
+		[SNDRV_RAWMIDI_STREAM_INPUT] = SNDRV_RAWMIDI_INFO_INPUT,
+	};
+
+	if (!(rmidi->info_flags & info_flags[stream]))
+		return -ENXIO;
+	if (subdevice >= 0 && subdevice >= s->substream_count)
+		return -ENODEV;
+
+	list_for_each_entry(substream, &s->substreams, list) {
+		if (substream->opened) {
+			if (stream == SNDRV_RAWMIDI_STREAM_INPUT ||
+			    !(mode & SNDRV_RAWMIDI_LFLG_APPEND) ||
+			    !substream->append)
+				continue;
+		}
+		if (subdevice < 0 || subdevice == substream->number) {
+			*sub_ret = substream;
+			return 0;
+		}
+	}
+	return -EAGAIN;
+}
+
+/* open and do ref-counting for the given substream */
+static int open_substream(struct snd_rawmidi *rmidi,
+			  struct snd_rawmidi_substream *substream,
+			  int mode)
+{
+	int err;
+
+	if (substream->use_count == 0) {
+		err = snd_rawmidi_runtime_create(substream);
+		if (err < 0)
+			return err;
+		err = substream->ops->open(substream);
+		if (err < 0) {
+			snd_rawmidi_runtime_free(substream);
+			return err;
+		}
+		substream->opened = 1;
+		substream->active_sensing = 0;
+		if (mode & SNDRV_RAWMIDI_LFLG_APPEND)
+			substream->append = 1;
+		substream->pid = get_pid(task_pid(current));
+		rmidi->streams[substream->stream].substream_opened++;
+	}
+	substream->use_count++;
+	return 0;
+}
+
+static void close_substream(struct snd_rawmidi *rmidi,
+			    struct snd_rawmidi_substream *substream,
+			    int cleanup);
+
+static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode,
+			     struct snd_rawmidi_file *rfile)
+{
+	struct snd_rawmidi_substream *sinput = NULL, *soutput = NULL;
+	int err;
+
+	rfile->input = rfile->output = NULL;
+	if (mode & SNDRV_RAWMIDI_LFLG_INPUT) {
+		err = assign_substream(rmidi, subdevice,
+				       SNDRV_RAWMIDI_STREAM_INPUT,
+				       mode, &sinput);
+		if (err < 0)
+			return err;
+	}
+	if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) {
+		err = assign_substream(rmidi, subdevice,
+				       SNDRV_RAWMIDI_STREAM_OUTPUT,
+				       mode, &soutput);
+		if (err < 0)
+			return err;
+	}
+
+	if (sinput) {
+		err = open_substream(rmidi, sinput, mode);
+		if (err < 0)
+			return err;
+	}
+	if (soutput) {
+		err = open_substream(rmidi, soutput, mode);
+		if (err < 0) {
+			if (sinput)
+				close_substream(rmidi, sinput, 0);
+			return err;
+		}
+	}
+
+	rfile->rmidi = rmidi;
+	rfile->input = sinput;
+	rfile->output = soutput;
+	return 0;
+}
+
+/* called from sound/core/seq/seq_midi.c */
+int snd_rawmidi_kernel_open(struct snd_card *card, int device, int subdevice,
+			    int mode, struct snd_rawmidi_file * rfile)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	if (snd_BUG_ON(!rfile))
+		return -EINVAL;
+
+	mutex_lock(&register_mutex);
+	rmidi = snd_rawmidi_search(card, device);
+	if (rmidi == NULL) {
+		mutex_unlock(&register_mutex);
+		return -ENODEV;
+	}
+	if (!try_module_get(rmidi->card->module)) {
+		mutex_unlock(&register_mutex);
+		return -ENXIO;
+	}
+	mutex_unlock(&register_mutex);
+
+	mutex_lock(&rmidi->open_mutex);
+	err = rawmidi_open_priv(rmidi, subdevice, mode, rfile);
+	mutex_unlock(&rmidi->open_mutex);
+	if (err < 0)
+		module_put(rmidi->card->module);
+	return err;
+}
+
+static int snd_rawmidi_open(struct inode *inode, struct file *file)
+{
+	int maj = imajor(inode);
+	struct snd_card *card;
+	int subdevice;
+	unsigned short fflags;
+	int err;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_file *rawmidi_file = NULL;
+	wait_queue_t wait;
+	struct snd_ctl_file *kctl;
+
+	if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK)) 
+		return -EINVAL;		/* invalid combination */
+
+	err = nonseekable_open(inode, file);
+	if (err < 0)
+		return err;
+
+	if (maj == snd_major) {
+		rmidi = snd_lookup_minor_data(iminor(inode),
+					      SNDRV_DEVICE_TYPE_RAWMIDI);
+#ifdef CONFIG_SND_OSSEMUL
+	} else if (maj == SOUND_MAJOR) {
+		rmidi = snd_lookup_oss_minor_data(iminor(inode),
+						  SNDRV_OSS_DEVICE_TYPE_MIDI);
+#endif
+	} else
+		return -ENXIO;
+
+	if (rmidi == NULL)
+		return -ENODEV;
+
+	if (!try_module_get(rmidi->card->module))
+		return -ENXIO;
+
+	mutex_lock(&rmidi->open_mutex);
+	card = rmidi->card;
+	err = snd_card_file_add(card, file);
+	if (err < 0)
+		goto __error_card;
+	fflags = snd_rawmidi_file_flags(file);
+	if ((file->f_flags & O_APPEND) || maj == SOUND_MAJOR) /* OSS emul? */
+		fflags |= SNDRV_RAWMIDI_LFLG_APPEND;
+	rawmidi_file = kmalloc(sizeof(*rawmidi_file), GFP_KERNEL);
+	if (rawmidi_file == NULL) {
+		err = -ENOMEM;
+		goto __error;
+	}
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&rmidi->open_wait, &wait);
+	while (1) {
+		subdevice = -1;
+		read_lock(&card->ctl_files_rwlock);
+		list_for_each_entry(kctl, &card->ctl_files, list) {
+			if (kctl->pid == task_pid(current)) {
+				subdevice = kctl->prefer_rawmidi_subdevice;
+				if (subdevice != -1)
+					break;
+			}
+		}
+		read_unlock(&card->ctl_files_rwlock);
+		err = rawmidi_open_priv(rmidi, 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);
+		mutex_unlock(&rmidi->open_mutex);
+		schedule();
+		mutex_lock(&rmidi->open_mutex);
+		if (signal_pending(current)) {
+			err = -ERESTARTSYS;
+			break;
+		}
+	}
+	remove_wait_queue(&rmidi->open_wait, &wait);
+	if (err < 0) {
+		kfree(rawmidi_file);
+		goto __error;
+	}
+#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
+	file->private_data = rawmidi_file;
+	mutex_unlock(&rmidi->open_mutex);
+	return 0;
+
+ __error:
+	snd_card_file_remove(card, file);
+ __error_card:
+	mutex_unlock(&rmidi->open_mutex);
+	module_put(rmidi->card->module);
+	return err;
+}
+
+static void close_substream(struct snd_rawmidi *rmidi,
+			    struct snd_rawmidi_substream *substream,
+			    int cleanup)
+{
+	if (--substream->use_count)
+		return;
+
+	if (cleanup) {
+		if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT)
+			snd_rawmidi_input_trigger(substream, 0);
+		else {
+			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 (substream->runtime->private_free)
+		substream->runtime->private_free(substream);
+	snd_rawmidi_runtime_free(substream);
+	substream->opened = 0;
+	substream->append = 0;
+	put_pid(substream->pid);
+	substream->pid = NULL;
+	rmidi->streams[substream->stream].substream_opened--;
+}
+
+static void rawmidi_release_priv(struct snd_rawmidi_file *rfile)
+{
+	struct snd_rawmidi *rmidi;
+
+	rmidi = rfile->rmidi;
+	mutex_lock(&rmidi->open_mutex);
+	if (rfile->input) {
+		close_substream(rmidi, rfile->input, 1);
+		rfile->input = NULL;
+	}
+	if (rfile->output) {
+		close_substream(rmidi, rfile->output, 1);
+		rfile->output = NULL;
+	}
+	rfile->rmidi = NULL;
+	mutex_unlock(&rmidi->open_mutex);
+	wake_up(&rmidi->open_wait);
+}
+
+/* called from sound/core/seq/seq_midi.c */
+int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile)
+{
+	struct snd_rawmidi *rmidi;
+
+	if (snd_BUG_ON(!rfile))
+		return -ENXIO;
+	
+	rmidi = rfile->rmidi;
+	rawmidi_release_priv(rfile);
+	module_put(rmidi->card->module);
+	return 0;
+}
+
+static int snd_rawmidi_release(struct inode *inode, struct file *file)
+{
+	struct snd_rawmidi_file *rfile;
+	struct snd_rawmidi *rmidi;
+	struct module *module;
+
+	rfile = file->private_data;
+	rmidi = rfile->rmidi;
+	rawmidi_release_priv(rfile);
+	kfree(rfile);
+	module = rmidi->card->module;
+	snd_card_file_remove(rmidi->card, file);
+	module_put(module);
+	return 0;
+}
+
+static int snd_rawmidi_info(struct snd_rawmidi_substream *substream,
+			    struct snd_rawmidi_info *info)
+{
+	struct snd_rawmidi *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(struct snd_rawmidi_substream *substream,
+				 struct snd_rawmidi_info __user * _info)
+{
+	struct snd_rawmidi_info info;
+	int err;
+	if ((err = snd_rawmidi_info(substream, &info)) < 0)
+		return err;
+	if (copy_to_user(_info, &info, sizeof(struct snd_rawmidi_info)))
+		return -EFAULT;
+	return 0;
+}
+
+int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info)
+{
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_str *pstr;
+	struct snd_rawmidi_substream *substream;
+
+	mutex_lock(&register_mutex);
+	rmidi = snd_rawmidi_search(card, info->device);
+	mutex_unlock(&register_mutex);
+	if (!rmidi)
+		return -ENXIO;
+	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_entry(substream, &pstr->substreams, list) {
+		if ((unsigned int)substream->number == info->subdevice)
+			return snd_rawmidi_info(substream, info);
+	}
+	return -ENXIO;
+}
+
+static int snd_rawmidi_info_select_user(struct snd_card *card,
+					struct snd_rawmidi_info __user *_info)
+{
+	int err;
+	struct snd_rawmidi_info 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(struct snd_rawmidi_info)))
+		return -EFAULT;
+	return 0;
+}
+
+int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream,
+			      struct snd_rawmidi_params * params)
+{
+	char *newbuf;
+	struct snd_rawmidi_runtime *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) {
+		newbuf = kmalloc(params->buffer_size, GFP_KERNEL);
+		if (!newbuf)
+			return -ENOMEM;
+		kfree(runtime->buffer);
+		runtime->buffer = newbuf;
+		runtime->buffer_size = params->buffer_size;
+		runtime->avail = runtime->buffer_size;
+	}
+	runtime->avail_min = params->avail_min;
+	substream->active_sensing = !params->no_active_sensing;
+	return 0;
+}
+
+int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream,
+			     struct snd_rawmidi_params * params)
+{
+	char *newbuf;
+	struct snd_rawmidi_runtime *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) {
+		newbuf = kmalloc(params->buffer_size, GFP_KERNEL);
+		if (!newbuf)
+			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(struct snd_rawmidi_substream *substream,
+				     struct snd_rawmidi_status * status)
+{
+	struct snd_rawmidi_runtime *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(struct snd_rawmidi_substream *substream,
+				    struct snd_rawmidi_status * status)
+{
+	struct snd_rawmidi_runtime *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)
+{
+	struct snd_rawmidi_file *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:
+	{
+		int stream;
+		struct snd_rawmidi_info __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:
+	{
+		struct snd_rawmidi_params params;
+		if (copy_from_user(&params, argp, sizeof(struct snd_rawmidi_params)))
+			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;
+		struct snd_rawmidi_status status;
+		if (copy_from_user(&status, argp, sizeof(struct snd_rawmidi_status)))
+			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(struct snd_rawmidi_status)))
+			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(struct snd_card *card,
+				     struct snd_ctl_file *control,
+				     unsigned int cmd,
+				     unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+
+	switch (cmd) {
+	case SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE:
+	{
+		int device;
+		
+		if (get_user(device, (int __user *)argp))
+			return -EFAULT;
+		if (device >= SNDRV_RAWMIDI_DEVICES) /* next device is -1 */
+			device = SNDRV_RAWMIDI_DEVICES - 1;
+		mutex_lock(&register_mutex);
+		device = device < 0 ? 0 : device + 1;
+		while (device < SNDRV_RAWMIDI_DEVICES) {
+			if (snd_rawmidi_search(card, device))
+				break;
+			device++;
+		}
+		if (device == SNDRV_RAWMIDI_DEVICES)
+			device = -1;
+		mutex_unlock(&register_mutex);
+		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(struct snd_rawmidi_substream *substream,
+			const unsigned char *buffer, int count)
+{
+	unsigned long flags;
+	int result = 0, count1;
+	struct snd_rawmidi_runtime *runtime = substream->runtime;
+
+	if (!substream->opened)
+		return -EBADFD;
+	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_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(struct snd_rawmidi_substream *substream,
+				     unsigned char __user *userbuf,
+				     unsigned char *kernelbuf, long count)
+{
+	unsigned long flags;
+	long result = 0, count1;
+	struct snd_rawmidi_runtime *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 (kernelbuf)
+			memcpy(kernelbuf + result, runtime->buffer + runtime->appl_ptr, count1);
+		if (userbuf) {
+			spin_unlock_irqrestore(&runtime->lock, flags);
+			if (copy_to_user(userbuf + 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(struct snd_rawmidi_substream *substream,
+			     unsigned char *buf, long count)
+{
+	snd_rawmidi_input_trigger(substream, 1);
+	return snd_rawmidi_kernel_read1(substream, NULL/*userbuf*/, buf, count);
+}
+
+static ssize_t snd_rawmidi_read(struct file *file, char __user *buf, size_t count,
+				loff_t *offset)
+{
+	long result;
+	int count1;
+	struct snd_rawmidi_file *rfile;
+	struct snd_rawmidi_substream *substream;
+	struct snd_rawmidi_runtime *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 __user *)buf,
+						  NULL/*kernelbuf*/,
+						  count);
+		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(struct snd_rawmidi_substream *substream)
+{
+	struct snd_rawmidi_runtime *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(struct snd_rawmidi_substream *substream,
+			      unsigned char *buffer, int count)
+{
+	unsigned long flags;
+	int result, count1;
+	struct snd_rawmidi_runtime *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(struct snd_rawmidi_substream *substream, int count)
+{
+	unsigned long flags;
+	struct snd_rawmidi_runtime *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_BUG_ON(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
+ * @buffer: 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(struct snd_rawmidi_substream *substream,
+			 unsigned char *buffer, int count)
+{
+	if (!substream->opened)
+		return -EBADFD;
+	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(struct snd_rawmidi_substream *substream,
+				      const unsigned char __user *userbuf,
+				      const unsigned char *kernelbuf,
+				      long count)
+{
+	unsigned long flags;
+	long count1, result;
+	struct snd_rawmidi_runtime *runtime = substream->runtime;
+
+	if (snd_BUG_ON(!kernelbuf && !userbuf))
+		return -EINVAL;
+	if (snd_BUG_ON(!runtime->buffer))
+		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 (kernelbuf)
+			memcpy(runtime->buffer + runtime->appl_ptr,
+			       kernelbuf + result, count1);
+		else if (userbuf) {
+			spin_unlock_irqrestore(&runtime->lock, flags);
+			if (copy_from_user(runtime->buffer + runtime->appl_ptr,
+					   userbuf + result, 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;
+		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(struct snd_rawmidi_substream *substream,
+			      const unsigned char *buf, long count)
+{
+	return snd_rawmidi_kernel_write1(substream, NULL, buf, count);
+}
+
+static ssize_t snd_rawmidi_write(struct file *file, const char __user *buf,
+				 size_t count, loff_t *offset)
+{
+	long result, timeout;
+	int count1;
+	struct snd_rawmidi_file *rfile;
+	struct snd_rawmidi_runtime *runtime;
+	struct snd_rawmidi_substream *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, buf, NULL, count);
+		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_DSYNC) {
+		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)
+{
+	struct snd_rawmidi_file *rfile;
+	struct snd_rawmidi_runtime *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(struct snd_info_entry *entry,
+				       struct snd_info_buffer *buffer)
+{
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *substream;
+	struct snd_rawmidi_runtime *runtime;
+
+	rmidi = entry->private_data;
+	snd_iprintf(buffer, "%s\n\n", rmidi->name);
+	mutex_lock(&rmidi->open_mutex);
+	if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT) {
+		list_for_each_entry(substream,
+				    &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams,
+				    list) {
+			snd_iprintf(buffer,
+				    "Output %d\n"
+				    "  Tx bytes     : %lu\n",
+				    substream->number,
+				    (unsigned long) substream->bytes);
+			if (substream->opened) {
+				snd_iprintf(buffer,
+				    "  Owner PID    : %d\n",
+				    pid_vnr(substream->pid));
+				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_entry(substream,
+				    &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams,
+				    list) {
+			snd_iprintf(buffer,
+				    "Input %d\n"
+				    "  Rx bytes     : %lu\n",
+				    substream->number,
+				    (unsigned long) substream->bytes);
+			if (substream->opened) {
+				snd_iprintf(buffer,
+					    "  Owner PID    : %d\n",
+					    pid_vnr(substream->pid));
+				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);
+			}
+		}
+	}
+	mutex_unlock(&rmidi->open_mutex);
+}
+
+/*
+ *  Register functions
+ */
+
+static const 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,
+	.llseek =	no_llseek,
+	.poll =		snd_rawmidi_poll,
+	.unlocked_ioctl =	snd_rawmidi_ioctl,
+	.compat_ioctl =	snd_rawmidi_ioctl_compat,
+};
+
+static int snd_rawmidi_alloc_substreams(struct snd_rawmidi *rmidi,
+					struct snd_rawmidi_str *stream,
+					int direction,
+					int count)
+{
+	struct snd_rawmidi_substream *substream;
+	int idx;
+
+	for (idx = 0; idx < count; idx++) {
+		substream = kzalloc(sizeof(*substream), GFP_KERNEL);
+		if (substream == NULL) {
+			snd_printk(KERN_ERR "rawmidi: cannot allocate substream\n");
+			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(struct snd_card *card, char *id, int device,
+		    int output_count, int input_count,
+		    struct snd_rawmidi ** rrawmidi)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_rawmidi_dev_free,
+		.dev_register = snd_rawmidi_dev_register,
+		.dev_disconnect = snd_rawmidi_dev_disconnect,
+	};
+
+	if (snd_BUG_ON(!card))
+		return -ENXIO;
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL);
+	if (rmidi == NULL) {
+		snd_printk(KERN_ERR "rawmidi: cannot allocate\n");
+		return -ENOMEM;
+	}
+	rmidi->card = card;
+	rmidi->device = device;
+	mutex_init(&rmidi->open_mutex);
+	init_waitqueue_head(&rmidi->open_wait);
+	INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams);
+	INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams);
+
+	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;
+	}
+	if (rrawmidi)
+		*rrawmidi = rmidi;
+	return 0;
+}
+
+static void snd_rawmidi_free_substreams(struct snd_rawmidi_str *stream)
+{
+	struct snd_rawmidi_substream *substream;
+
+	while (!list_empty(&stream->substreams)) {
+		substream = list_entry(stream->substreams.next, struct snd_rawmidi_substream, list);
+		list_del(&substream->list);
+		kfree(substream);
+	}
+}
+
+static int snd_rawmidi_free(struct snd_rawmidi *rmidi)
+{
+	if (!rmidi)
+		return 0;
+
+	snd_info_free_entry(rmidi->proc_entry);
+	rmidi->proc_entry = NULL;
+	mutex_lock(&register_mutex);
+	if (rmidi->ops && rmidi->ops->dev_unregister)
+		rmidi->ops->dev_unregister(rmidi);
+	mutex_unlock(&register_mutex);
+
+	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(struct snd_device *device)
+{
+	struct snd_rawmidi *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(struct snd_seq_device *device)
+{
+	struct snd_rawmidi *rmidi = device->private_data;
+	rmidi->seq_dev = NULL;
+}
+#endif
+
+static int snd_rawmidi_dev_register(struct snd_device *device)
+{
+	int err;
+	struct snd_info_entry *entry;
+	char name[16];
+	struct snd_rawmidi *rmidi = device->device_data;
+
+	if (rmidi->device >= SNDRV_RAWMIDI_DEVICES)
+		return -ENOMEM;
+	mutex_lock(&register_mutex);
+	if (snd_rawmidi_search(rmidi->card, rmidi->device)) {
+		mutex_unlock(&register_mutex);
+		return -EBUSY;
+	}
+	list_add_tail(&rmidi->list, &snd_rawmidi_devices);
+	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_f_ops, rmidi, name)) < 0) {
+		snd_printk(KERN_ERR "unable to register rawmidi device %i:%i\n", rmidi->card->number, rmidi->device);
+		list_del(&rmidi->list);
+		mutex_unlock(&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);
+		list_del(&rmidi->list);
+		mutex_unlock(&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_f_ops,
+					    rmidi, 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_f_ops,
+					    rmidi, 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 */
+	mutex_unlock(&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 = 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(struct snd_device *device)
+{
+	struct snd_rawmidi *rmidi = device->device_data;
+
+	mutex_lock(&register_mutex);
+	list_del_init(&rmidi->list);
+#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 */
+	snd_unregister_device(SNDRV_DEVICE_TYPE_RAWMIDI, rmidi->card, rmidi->device);
+	mutex_unlock(&register_mutex);
+	return 0;
+}
+
+/**
+ * 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(struct snd_rawmidi *rmidi, int stream,
+			 struct snd_rawmidi_ops *ops)
+{
+	struct snd_rawmidi_substream *substream;
+	
+	list_for_each_entry(substream, &rmidi->streams[stream].substreams, 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_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/linux/sound/core/rawmidi_compat.c b/linux/sound/core/rawmidi_compat.c
new file mode 100644
index 0000000..5268c1f
--- /dev/null
+++ b/linux/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 snd_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(struct snd_rawmidi_file *rfile,
+					   struct snd_rawmidi_params32 __user *src)
+{
+	struct snd_rawmidi_params 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 snd_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(struct snd_rawmidi_file *rfile,
+					   struct snd_rawmidi_status32 __user *src)
+{
+	int err;
+	struct snd_rawmidi_status 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 snd_rawmidi_params32),
+	SNDRV_RAWMIDI_IOCTL_STATUS32 = _IOWR('W', 0x20, struct snd_rawmidi_status32),
+};
+
+static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct snd_rawmidi_file *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/linux/sound/core/rtctimer.c b/linux/sound/core/rtctimer.c
new file mode 100644
index 0000000..0851cd1
--- /dev/null
+++ b/linux/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 <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <linux/log2.h>
+#include <sound/core.h>
+#include <sound/timer.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(struct snd_timer *t);
+static int rtctimer_close(struct snd_timer *t);
+static int rtctimer_start(struct snd_timer *t);
+static int rtctimer_stop(struct snd_timer *t);
+
+
+/*
+ * The hardware dependent description for this timer.
+ */
+static struct snd_timer_hardware rtc_hw = {
+	.flags =	SNDRV_TIMER_HW_AUTO |
+			SNDRV_TIMER_HW_FIRST |
+			SNDRV_TIMER_HW_TASKLET,
+	.ticks =	100000000L,		/* FIXME: XXX */
+	.open =		rtctimer_open,
+	.close =	rtctimer_close,
+	.start =	rtctimer_start,
+	.stop =		rtctimer_stop,
+};
+
+static int rtctimer_freq = RTC_FREQ;		/* frequency */
+static struct snd_timer *rtctimer;
+static struct tasklet_struct rtc_tasklet;
+static rtc_task_t rtc_task;
+
+
+static int
+rtctimer_open(struct snd_timer *t)
+{
+	int err;
+
+	err = rtc_register(&rtc_task);
+	if (err < 0)
+		return err;
+	t->private_data = &rtc_task;
+	return 0;
+}
+
+static int
+rtctimer_close(struct snd_timer *t)
+{
+	rtc_task_t *rtc = t->private_data;
+	if (rtc) {
+		rtc_unregister(rtc);
+		tasklet_kill(&rtc_tasklet);
+		t->private_data = NULL;
+	}
+	return 0;
+}
+
+static int
+rtctimer_start(struct snd_timer *timer)
+{
+	rtc_task_t *rtc = timer->private_data;
+	if (snd_BUG_ON(!rtc))
+		return -EINVAL;
+	rtc_control(rtc, RTC_IRQP_SET, rtctimer_freq);
+	rtc_control(rtc, RTC_PIE_ON, 0);
+	return 0;
+}
+
+static int
+rtctimer_stop(struct snd_timer *timer)
+{
+	rtc_task_t *rtc = timer->private_data;
+	if (snd_BUG_ON(!rtc))
+		return -EINVAL;
+	rtc_control(rtc, RTC_PIE_OFF, 0);
+	return 0;
+}
+
+static void rtctimer_tasklet(unsigned long data)
+{
+	snd_timer_interrupt((struct snd_timer *)data, 1);
+}
+
+/*
+ * interrupt
+ */
+static void rtctimer_interrupt(void *private_data)
+{
+	tasklet_schedule(private_data);
+}
+
+
+/*
+ *  ENTRY functions
+ */
+static int __init rtctimer_init(void)
+{
+	int err;
+	struct snd_timer *timer;
+
+	if (rtctimer_freq < 2 || rtctimer_freq > 8192 ||
+	    !is_power_of_2(rtctimer_freq)) {
+		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;
+
+	timer->module = THIS_MODULE;
+	strcpy(timer->name, "RTC timer");
+	timer->hw = rtc_hw;
+	timer->hw.resolution = NANO_SEC / rtctimer_freq;
+
+	tasklet_init(&rtc_tasklet, rtctimer_tasklet, (unsigned long)timer);
+
+	/* set up RTC callback */
+	rtc_task.func = rtctimer_interrupt;
+	rtc_task.private_data = &rtc_tasklet;
+
+	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_free(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/linux/sound/core/seq/Kconfig b/linux/sound/core/seq/Kconfig
new file mode 100644
index 0000000..b851fd8
--- /dev/null
+++ b/linux/sound/core/seq/Kconfig
@@ -0,0 +1,16 @@
+# define SND_XXX_SEQ to min(SND_SEQUENCER,SND_XXX)
+
+config SND_RAWMIDI_SEQ
+	def_tristate SND_SEQUENCER && SND_RAWMIDI
+
+config SND_OPL3_LIB_SEQ
+	def_tristate SND_SEQUENCER && SND_OPL3_LIB
+
+config SND_OPL4_LIB_SEQ
+	def_tristate SND_SEQUENCER && SND_OPL4_LIB
+
+config SND_SBAWE_SEQ
+	def_tristate SND_SEQUENCER && SND_SBAWE
+
+config SND_EMU10K1_SEQ
+	def_tristate SND_SEQUENCER && SND_EMU10K1
diff --git a/linux/sound/core/seq/Makefile b/linux/sound/core/seq/Makefile
new file mode 100644
index 0000000..941f64a
--- /dev/null
+++ b/linux/sound/core/seq/Makefile
@@ -0,0 +1,29 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
+#
+
+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-dummy-objs := seq_dummy.o
+snd-seq-virmidi-objs := seq_virmidi.o
+
+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
+  obj-$(CONFIG_SND_SEQUENCER) += oss/
+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-$(CONFIG_SND_RAWMIDI_SEQ) += snd-seq-midi.o snd-seq-midi-event.o
+obj-$(CONFIG_SND_OPL3_LIB_SEQ) += snd-seq-midi-event.o snd-seq-midi-emul.o
+obj-$(CONFIG_SND_OPL4_LIB_SEQ) += snd-seq-midi-event.o snd-seq-midi-emul.o
+obj-$(CONFIG_SND_SBAWE_SEQ) += snd-seq-midi-emul.o snd-seq-virmidi.o
+obj-$(CONFIG_SND_EMU10K1_SEQ) += snd-seq-midi-emul.o snd-seq-virmidi.o
diff --git a/linux/sound/core/seq/oss/Makefile b/linux/sound/core/seq/oss/Makefile
new file mode 100644
index 0000000..b38406b
--- /dev/null
+++ b/linux/sound/core/seq/oss/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.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/linux/sound/core/seq/oss/seq_oss.c b/linux/sound/core/seq/oss/seq_oss.c
new file mode 100644
index 0000000..a1f1a2f
--- /dev/null
+++ b/linux/sound/core/seq/oss/seq_oss.c
@@ -0,0 +1,312 @@
+/*
+ * 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 <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.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);
+#ifdef CONFIG_PROC_FS
+static int register_proc(void);
+static void unregister_proc(void);
+#else
+static inline int register_proc(void) { return 0; }
+static inline void unregister_proc(void) {}
+#endif
+
+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);
+
+
+/*
+ * module interface
+ */
+
+static int __init alsa_seq_oss_init(void)
+{
+	int rc;
+	static struct snd_seq_dev_ops 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(struct snd_seq_oss_reg))) < 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 DEFINE_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;
+
+	mutex_lock(&register_mutex);
+	rc = snd_seq_oss_open(file, level);
+	mutex_unlock(&register_mutex);
+
+	return rc;
+}
+
+static int
+odev_release(struct inode *inode, struct file *file)
+{
+	struct seq_oss_devinfo *dp;
+
+	if ((dp = file->private_data) == NULL)
+		return 0;
+
+	snd_seq_oss_drain_write(dp);
+
+	mutex_lock(&register_mutex);
+	snd_seq_oss_release(dp);
+	mutex_unlock(&register_mutex);
+
+	return 0;
+}
+
+static ssize_t
+odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+	struct seq_oss_devinfo *dp;
+	dp = file->private_data;
+	if (snd_BUG_ON(!dp))
+		return -ENXIO;
+	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)
+{
+	struct seq_oss_devinfo *dp;
+	dp = file->private_data;
+	if (snd_BUG_ON(!dp))
+		return -ENXIO;
+	return snd_seq_oss_write(dp, buf, count, file);
+}
+
+static long
+odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct seq_oss_devinfo *dp;
+	dp = file->private_data;
+	if (snd_BUG_ON(!dp))
+		return -ENXIO;
+	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)
+{
+	struct seq_oss_devinfo *dp;
+	dp = file->private_data;
+	if (snd_BUG_ON(!dp))
+		return -ENXIO;
+	return snd_seq_oss_poll(dp, file, wait);
+}
+
+/*
+ * registration of sequencer minor device
+ */
+
+static const 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,
+	.llseek =	noop_llseek,
+};
+
+static int __init
+register_device(void)
+{
+	int rc;
+
+	mutex_lock(&register_mutex);
+	if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER,
+					  NULL, 0,
+					  &seq_oss_f_ops, NULL,
+					  SNDRV_SEQ_OSS_DEVNAME)) < 0) {
+		snd_printk(KERN_ERR "can't register device seq\n");
+		mutex_unlock(&register_mutex);
+		return rc;
+	}
+	if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC,
+					  NULL, 0,
+					  &seq_oss_f_ops, NULL,
+					  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);
+		mutex_unlock(&register_mutex);
+		return rc;
+	}
+	debug_printk(("device registered\n"));
+	mutex_unlock(&register_mutex);
+	return 0;
+}
+
+static void
+unregister_device(void)
+{
+	mutex_lock(&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");
+	mutex_unlock(&register_mutex);
+}
+
+/*
+ * /proc interface
+ */
+
+#ifdef CONFIG_PROC_FS
+
+static struct snd_info_entry *info_entry;
+
+static void
+info_read(struct snd_info_entry *entry, struct snd_info_buffer *buf)
+{
+	mutex_lock(&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);
+	mutex_unlock(&register_mutex);
+}
+
+
+static int __init
+register_proc(void)
+{
+	struct snd_info_entry *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 = info_read;
+	if (snd_info_register(entry) < 0) {
+		snd_info_free_entry(entry);
+		return -ENOMEM;
+	}
+	info_entry = entry;
+	return 0;
+}
+
+static void
+unregister_proc(void)
+{
+	snd_info_free_entry(info_entry);
+	info_entry = NULL;
+}
+#endif /* CONFIG_PROC_FS */
diff --git a/linux/sound/core/seq/oss/seq_oss_device.h b/linux/sound/core/seq/oss/seq_oss_device.h
new file mode 100644
index 0000000..c0154a9
--- /dev/null
+++ b/linux/sound/core/seq/oss/seq_oss_device.h
@@ -0,0 +1,189 @@
+/*
+ * 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 <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 unsigned int reltime_t;
+typedef unsigned int abstime_t;
+
+
+/*
+ * synthesizer channel information
+ */
+struct seq_oss_chinfo {
+	int note, vel;
+};
+
+/*
+ * synthesizer information
+ */
+struct seq_oss_synthinfo {
+	struct snd_seq_oss_arg arg;
+	struct seq_oss_chinfo *ch;
+	struct seq_oss_synth_sysex *sysex;
+	int nr_voices;
+	int opened;
+	int is_midi;
+	int midi_mapped;
+};
+
+
+/*
+ * sequencer client information
+ */
+
+struct seq_oss_devinfo {
+
+	int index;	/* application index */
+	int cseq;	/* sequencer client number */
+	int port;	/* sequencer port number */
+	int queue;	/* sequencer queue number */
+
+	struct snd_seq_addr 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;
+	struct seq_oss_synthinfo synths[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
+	int synth_opened;
+
+	/* output queue */
+	struct seq_oss_writeq *writeq;
+
+	/* midi input queue */
+	struct seq_oss_readq *readq;
+
+	/* timer */
+	struct seq_oss_timer *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(struct seq_oss_devinfo *dp);
+int snd_seq_oss_ioctl(struct seq_oss_devinfo *dp, unsigned int cmd, unsigned long arg);
+int snd_seq_oss_read(struct seq_oss_devinfo *dev, char __user *buf, int count);
+int snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt);
+unsigned int snd_seq_oss_poll(struct seq_oss_devinfo *dp, struct file *file, poll_table * wait);
+
+void snd_seq_oss_reset(struct seq_oss_devinfo *dp);
+void snd_seq_oss_drain_write(struct seq_oss_devinfo *dp);
+
+/* */
+void snd_seq_oss_process_queue(struct seq_oss_devinfo *dp, abstime_t time);
+
+
+/* proc interface */
+void snd_seq_oss_system_info_read(struct snd_info_buffer *buf);
+void snd_seq_oss_midi_info_read(struct snd_info_buffer *buf);
+void snd_seq_oss_synth_info_read(struct snd_info_buffer *buf);
+void snd_seq_oss_readq_info_read(struct seq_oss_readq *q, struct snd_info_buffer *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 */
+static inline int
+snd_seq_oss_dispatch(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int atomic, int hop)
+{
+	return snd_seq_kernel_client_dispatch(dp->cseq, ev, atomic, hop);
+}
+
+/* ioctl */
+static inline int
+snd_seq_oss_control(struct seq_oss_devinfo *dp, unsigned int type, void *arg)
+{
+	return snd_seq_kernel_client_ctl(dp->cseq, type, arg);
+}
+
+/* fill the addresses in header */
+static inline void
+snd_seq_oss_fill_addr(struct seq_oss_devinfo *dp, struct snd_seq_event *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_printd x; } while (0)
+#else
+#define debug_printk(x)	/**/
+#endif
+
+#endif /* __SEQ_OSS_DEVICE_H */
diff --git a/linux/sound/core/seq/oss/seq_oss_event.c b/linux/sound/core/seq/oss/seq_oss_event.c
new file mode 100644
index 0000000..066f5f3
--- /dev/null
+++ b/linux/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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
+static int chn_voice_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
+static int chn_common_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
+static int timing_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
+static int local_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
+static int old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
+static int note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev);
+static int note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev);
+static int set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev);
+static int set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev);
+static int set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev);
+
+
+/*
+ * convert an OSS event to ALSA event
+ * return 0 : enqueued
+ *        non-zero : invalid - ignored
+ */
+
+int
+snd_seq_oss_process_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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 {
+			union evrec 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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *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(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev)
+{
+	struct seq_oss_synthinfo *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(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev)
+{
+	struct seq_oss_synthinfo *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(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *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(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *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(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *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(struct snd_seq_event *ev, int direct, void *private_data,
+			int atomic, int hop)
+{
+	struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data;
+	union evrec *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 = (union evrec*)&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/linux/sound/core/seq/oss/seq_oss_event.h b/linux/sound/core/seq/oss/seq_oss_event.h
new file mode 100644
index 0000000..9a4d9ad
--- /dev/null
+++ b/linux/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) */
+struct evrec_short {
+	unsigned char code;
+	unsigned char parm1;
+	unsigned char dev;
+	unsigned char parm2;
+};
+	
+/* short note events (4bytes) */
+struct evrec_note {
+	unsigned char code;
+	unsigned char chn;
+	unsigned char note;
+	unsigned char vel;
+};
+	
+/* long timer events (8bytes) */
+struct evrec_timer {
+	unsigned char code;
+	unsigned char cmd;
+	unsigned char dummy1, dummy2;
+	unsigned int time;
+};
+
+/* long extended events (8bytes) */
+struct evrec_extended {
+	unsigned char code;
+	unsigned char cmd;
+	unsigned char dev;
+	unsigned char chn;
+	unsigned char p1, p2, p3, p4;
+};
+
+/* long channel events (8bytes) */
+struct evrec_long {
+	unsigned char code;
+	unsigned char dev;
+	unsigned char cmd;
+	unsigned char chn;
+	unsigned char p1, p2;
+	unsigned short val;
+};
+	
+/* channel voice events (8bytes) */
+struct evrec_voice {
+	unsigned char code;
+	unsigned char dev;
+	unsigned char cmd;
+	unsigned char chn;
+	unsigned char note, parm;
+	unsigned short dummy;
+};
+
+/* sysex events (8bytes) */
+struct evrec_sysex {
+	unsigned char code;
+	unsigned char dev;
+	unsigned char buf[6];
+};
+
+/* event record */
+union evrec {
+	struct evrec_short s;
+	struct evrec_note n;
+	struct evrec_long l;
+	struct evrec_voice v;
+	struct evrec_timer t;
+	struct evrec_extended e;
+	struct evrec_sysex 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(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
+int snd_seq_oss_process_timer_event(struct seq_oss_timer *rec, union evrec *q);
+int snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data, int atomic, int hop);
+
+
+#endif /* __SEQ_OSS_EVENT_H */
diff --git a/linux/sound/core/seq/oss/seq_oss_init.c b/linux/sound/core/seq/oss/seq_oss_init.c
new file mode 100644
index 0000000..69cd7b3
--- /dev/null
+++ b/linux/sound/core/seq/oss/seq_oss_init.c
@@ -0,0 +1,545 @@
+/*
+ * 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>
+#include <linux/slab.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 struct seq_oss_devinfo *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS];
+
+
+/*
+ * prototypes
+ */
+static int receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop);
+static int translate_mode(struct file *file);
+static int create_port(struct seq_oss_devinfo *dp);
+static int delete_port(struct seq_oss_devinfo *dp);
+static int alloc_seq_queue(struct seq_oss_devinfo *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;
+	struct snd_seq_port_info *port;
+	struct snd_seq_port_callback port_callback;
+
+	port = kmalloc(sizeof(*port), GFP_KERNEL);
+	if (!port) {
+		rc = -ENOMEM;
+		goto __error;
+	}
+
+	/* create ALSA client */
+	rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS,
+					  "OSS sequencer");
+	if (rc < 0)
+		goto __error;
+
+	system_client = rc;
+	debug_printk(("new client = %d\n", rc));
+
+	/* 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) {
+		struct snd_seq_port_subscribe 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);
+	return rc;
+}
+
+
+/*
+ * receive annoucement from system port, and check the midi device
+ */
+static int
+receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop)
+{
+	struct snd_seq_port_info 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;
+	struct seq_oss_devinfo *dp;
+
+	dp = kzalloc(sizeof(*dp), GFP_KERNEL);
+	if (!dp) {
+		snd_printk(KERN_ERR "can't malloc device info\n");
+		return -ENOMEM;
+	}
+	debug_printk(("oss_open: dp = %p\n", dp));
+
+	dp->cseq = system_client;
+	dp->port = -1;
+	dp->queue = -1;
+
+	for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) {
+		if (client_table[i] == NULL)
+			break;
+	}
+
+	dp->index = i;
+	if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) {
+		snd_printk(KERN_ERR "too many applications\n");
+		rc = -ENOMEM;
+		goto _error;
+	}
+
+	/* 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"));
+	rc = create_port(dp);
+	if (rc < 0) {
+		snd_printk(KERN_ERR "can't create port\n");
+		goto _error;
+	}
+
+	/* allocate queue */
+	debug_printk(("allocate queue\n"));
+	rc = alloc_seq_queue(dp);
+	if (rc < 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)) {
+		dp->readq = snd_seq_oss_readq_new(dp, maxqlen);
+		if (!dp->readq) {
+			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) {
+			rc = -ENOMEM;
+			goto _error;
+		}
+	}
+
+	/* initialize timer */
+	debug_printk(("initialize timer\n"));
+	dp->timer = snd_seq_oss_timer_new(dp);
+	if (!dp->timer) {
+		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);
+	delete_seq_queue(dp->queue);
+	delete_port(dp);
+
+	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(struct seq_oss_devinfo *dp)
+{
+	int rc;
+	struct snd_seq_port_info port;
+	struct snd_seq_port_callback 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(struct seq_oss_devinfo *dp)
+{
+	if (dp->port < 0) {
+		kfree(dp);
+		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(struct seq_oss_devinfo *dp)
+{
+	struct snd_seq_queue_info 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)
+{
+	struct snd_seq_queue_info 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)
+{
+	struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)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(struct seq_oss_devinfo *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(struct seq_oss_devinfo *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(struct seq_oss_devinfo *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);
+}
+
+
+#ifdef CONFIG_PROC_FS
+/*
+ * 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(struct snd_info_buffer *buf)
+{
+	int i;
+	struct seq_oss_devinfo *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);
+	}
+}
+#endif /* CONFIG_PROC_FS */
diff --git a/linux/sound/core/seq/oss/seq_oss_ioctl.c b/linux/sound/core/seq/oss/seq_oss_ioctl.c
new file mode 100644
index 0000000..5ac701c
--- /dev/null
+++ b/linux/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(struct seq_oss_devinfo *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(struct seq_oss_devinfo *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(struct seq_oss_devinfo *dp, void __user *arg)
+{
+	unsigned char ev[8];
+	struct snd_seq_event 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, (union evrec *)ev, &tmpev)) {
+		snd_seq_oss_dispatch(dp, &tmpev, 0, 0);
+	}
+	return 0;
+}
+
+int
+snd_seq_oss_ioctl(struct seq_oss_devinfo *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/linux/sound/core/seq/oss/seq_oss_midi.c b/linux/sound/core/seq/oss/seq_oss_midi.c
new file mode 100644
index 0000000..677dc84
--- /dev/null
+++ b/linux/sound/core/seq/oss/seq_oss_midi.c
@@ -0,0 +1,714 @@
+/*
+ * 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 <sound/asoundef.h>
+#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>
+#include <linux/slab.h>
+
+
+/*
+ * constants
+ */
+#define SNDRV_SEQ_OSS_MAX_MIDI_NAME	30
+
+/*
+ * definition of midi device record
+ */
+struct seq_oss_midi {
+	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];
+	struct snd_midi_event *coder;	/* MIDI event coder */
+	struct seq_oss_devinfo *devinfo;	/* assigned OSSseq device */
+	snd_use_lock_t use_lock;
+};
+
+
+/*
+ * midi device table
+ */
+static int max_midi_devs;
+static struct seq_oss_midi *midi_devs[SNDRV_SEQ_OSS_MAX_MIDI_DEVS];
+
+static DEFINE_SPINLOCK(register_lock);
+
+/*
+ * prototypes
+ */
+static struct seq_oss_midi *get_mdev(int dev);
+static struct seq_oss_midi *get_mididev(struct seq_oss_devinfo *dp, int dev);
+static int send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev);
+static int send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev);
+
+/*
+ * look up the existing ports
+ * this looks a very exhausting job.
+ */
+int __init
+snd_seq_oss_midi_lookup_ports(int client)
+{
+	struct snd_seq_client_info *clinfo;
+	struct snd_seq_port_info *pinfo;
+
+	clinfo = kzalloc(sizeof(*clinfo), GFP_KERNEL);
+	pinfo = kzalloc(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 struct seq_oss_midi *
+get_mdev(int dev)
+{
+	struct seq_oss_midi *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 struct seq_oss_midi *
+find_slot(int client, int port)
+{
+	int i;
+	struct seq_oss_midi *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(struct snd_seq_port_info *pinfo)
+{
+	int i;
+	struct seq_oss_midi *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 = kzalloc(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)
+{
+	struct seq_oss_midi *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;
+	struct seq_oss_midi *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(struct seq_oss_devinfo *dp)
+{
+	dp->max_mididev = max_midi_devs;
+}
+
+/*
+ * clean up midi tables
+ */
+void
+snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *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(struct seq_oss_devinfo *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 struct seq_oss_midi *
+get_mididev(struct seq_oss_devinfo *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(struct seq_oss_devinfo *dp, int dev, int fmode)
+{
+	int perm;
+	struct seq_oss_midi *mdev;
+	struct snd_seq_port_subscribe 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(struct seq_oss_devinfo *dp, int dev)
+{
+	struct seq_oss_midi *mdev;
+	struct snd_seq_port_subscribe 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(struct seq_oss_devinfo *dp, int dev)
+{
+	struct seq_oss_midi *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(struct seq_oss_devinfo *dp, int dev)
+{
+	struct seq_oss_midi *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) {
+		struct snd_seq_event 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);
+		}
+		for (c = 0; c < 16; c++) {
+			ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
+			ev.data.control.channel = c;
+			ev.data.control.param = MIDI_CTL_ALL_NOTES_OFF;
+			snd_seq_oss_dispatch(dp, &ev, 0, 0);
+			if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
+				ev.data.control.param =
+					MIDI_CTL_RESET_CONTROLLERS;
+				snd_seq_oss_dispatch(dp, &ev, 0, 0);
+				ev.type = SNDRV_SEQ_EVENT_PITCHBEND;
+				ev.data.control.value = 0;
+				snd_seq_oss_dispatch(dp, &ev, 0, 0);
+			}
+		}
+	}
+	// 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(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr)
+{
+	struct seq_oss_midi *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(struct snd_seq_event *ev, int direct, void *private_data)
+{
+	struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data;
+	struct seq_oss_midi *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(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev)
+{
+	union evrec 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(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *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(struct seq_oss_devinfo *dp, int dev, unsigned char c, struct snd_seq_event *ev)
+{
+	struct seq_oss_midi *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(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf)
+{
+	struct seq_oss_midi *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;
+}
+
+
+#ifdef CONFIG_PROC_FS
+/*
+ * 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(struct snd_info_buffer *buf)
+{
+	int i;
+	struct seq_oss_midi *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);
+	}
+}
+#endif /* CONFIG_PROC_FS */
diff --git a/linux/sound/core/seq/oss/seq_oss_midi.h b/linux/sound/core/seq/oss/seq_oss_midi.h
new file mode 100644
index 0000000..84eb866
--- /dev/null
+++ b/linux/sound/core/seq/oss/seq_oss_midi.h
@@ -0,0 +1,48 @@
+/*
+ * 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>
+
+int snd_seq_oss_midi_lookup_ports(int client);
+int snd_seq_oss_midi_check_new_port(struct snd_seq_port_info *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(struct seq_oss_devinfo *dp);
+void snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *dp);
+
+int snd_seq_oss_midi_open(struct seq_oss_devinfo *dp, int dev, int file_mode);
+void snd_seq_oss_midi_open_all(struct seq_oss_devinfo *dp, int file_mode);
+int snd_seq_oss_midi_close(struct seq_oss_devinfo *dp, int dev);
+void snd_seq_oss_midi_reset(struct seq_oss_devinfo *dp, int dev);
+int snd_seq_oss_midi_putc(struct seq_oss_devinfo *dp, int dev, unsigned char c,
+			  struct snd_seq_event *ev);
+int snd_seq_oss_midi_input(struct snd_seq_event *ev, int direct, void *private);
+int snd_seq_oss_midi_filemode(struct seq_oss_devinfo *dp, int dev);
+int snd_seq_oss_midi_make_info(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf);
+void snd_seq_oss_midi_get_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr);
+
+#endif
diff --git a/linux/sound/core/seq/oss/seq_oss_readq.c b/linux/sound/core/seq/oss/seq_oss_readq.c
new file mode 100644
index 0000000..73661c4
--- /dev/null
+++ b/linux/sound/core/seq/oss/seq_oss_readq.c
@@ -0,0 +1,237 @@
+/*
+ * 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>
+#include <linux/slab.h>
+
+/*
+ * constants
+ */
+//#define SNDRV_SEQ_OSS_MAX_TIMEOUT	(unsigned long)(-1)
+#define SNDRV_SEQ_OSS_MAX_TIMEOUT	(HZ * 3600)
+
+
+/*
+ * prototypes
+ */
+
+
+/*
+ * create a read queue
+ */
+struct seq_oss_readq *
+snd_seq_oss_readq_new(struct seq_oss_devinfo *dp, int maxlen)
+{
+	struct seq_oss_readq *q;
+
+	if ((q = kzalloc(sizeof(*q), GFP_KERNEL)) == NULL) {
+		snd_printk(KERN_ERR "can't malloc read queue\n");
+		return NULL;
+	}
+
+	if ((q->q = kcalloc(maxlen, sizeof(union evrec), 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(struct seq_oss_readq *q)
+{
+	if (q) {
+		kfree(q->q);
+		kfree(q);
+	}
+}
+
+/*
+ * reset the read queue
+ */
+void
+snd_seq_oss_readq_clear(struct seq_oss_readq *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(struct seq_oss_readq *q, int dev, unsigned char *data, int len)
+{
+	union evrec 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(struct seq_oss_readq *q, union evrec *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(struct seq_oss_readq *q, union evrec *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(struct seq_oss_readq *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(struct seq_oss_readq *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(struct seq_oss_readq *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(struct seq_oss_readq *q, unsigned long curt, int seq_mode)
+{
+	if (curt != q->input_time) {
+		union evrec 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;
+}
+
+
+#ifdef CONFIG_PROC_FS
+/*
+ * proc interface
+ */
+void
+snd_seq_oss_readq_info_read(struct seq_oss_readq *q, struct snd_info_buffer *buf)
+{
+	snd_iprintf(buf, "  read queue [%s] length = %d : tick = %ld\n",
+		    (waitqueue_active(&q->midi_sleep) ? "sleeping":"running"),
+		    q->qlen, q->input_time);
+}
+#endif /* CONFIG_PROC_FS */
diff --git a/linux/sound/core/seq/oss/seq_oss_readq.h b/linux/sound/core/seq/oss/seq_oss_readq.h
new file mode 100644
index 0000000..f1463f1
--- /dev/null
+++ b/linux/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 {
+	union evrec *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;
+};
+
+struct seq_oss_readq *snd_seq_oss_readq_new(struct seq_oss_devinfo *dp, int maxlen);
+void snd_seq_oss_readq_delete(struct seq_oss_readq *q);
+void snd_seq_oss_readq_clear(struct seq_oss_readq *readq);
+unsigned int snd_seq_oss_readq_poll(struct seq_oss_readq *readq, struct file *file, poll_table *wait);
+int snd_seq_oss_readq_puts(struct seq_oss_readq *readq, int dev, unsigned char *data, int len);
+int snd_seq_oss_readq_put_event(struct seq_oss_readq *readq, union evrec *ev);
+int snd_seq_oss_readq_put_timestamp(struct seq_oss_readq *readq, unsigned long curt, int seq_mode);
+int snd_seq_oss_readq_pick(struct seq_oss_readq *q, union evrec *rec);
+void snd_seq_oss_readq_wait(struct seq_oss_readq *q);
+void snd_seq_oss_readq_free(struct seq_oss_readq *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/linux/sound/core/seq/oss/seq_oss_rw.c b/linux/sound/core/seq/oss/seq_oss_rw.c
new file mode 100644
index 0000000..6a7b6ac
--- /dev/null
+++ b/linux/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(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt);
+
+
+/*
+ * read interface
+ */
+
+int
+snd_seq_oss_read(struct seq_oss_devinfo *dp, char __user *buf, int count)
+{
+	struct seq_oss_readq *readq = dp->readq;
+	int result = 0, err = 0;
+	int ev_len;
+	union evrec 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(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt)
+{
+	int result = 0, err = 0;
+	int ev_size, fmt;
+	union evrec 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(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt)
+{
+	int rc = 0;
+	struct snd_seq_event 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(struct seq_oss_devinfo *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/linux/sound/core/seq/oss/seq_oss_synth.c b/linux/sound/core/seq/oss/seq_oss_synth.c
new file mode 100644
index 0000000..ee44ab9
--- /dev/null
+++ b/linux/sound/core/seq/oss/seq_oss_synth.c
@@ -0,0 +1,663 @@
+/*
+ * 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>
+#include <linux/slab.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 {
+	int len;
+	int skip;
+	unsigned char buf[MAX_SYSEX_BUFLEN];
+};
+
+/* synth info */
+struct seq_oss_synth {
+	int seq_device;
+
+	/* for synth_info */
+	int synth_type;
+	int synth_subtype;
+	int nr_voices;
+
+	char name[SNDRV_SEQ_OSS_MAX_SYNTH_NAME];
+	struct snd_seq_oss_callback oper;
+
+	int opened;
+
+	void *private_data;
+	snd_use_lock_t use_lock;
+};
+
+
+/*
+ * device table
+ */
+static int max_synth_devs;
+static struct seq_oss_synth *synth_devs[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
+static struct seq_oss_synth 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 struct seq_oss_synth *get_synthdev(struct seq_oss_devinfo *dp, int dev);
+static void reset_channels(struct seq_oss_synthinfo *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(struct snd_seq_device *dev)
+{
+	int i;
+	struct seq_oss_synth *rec;
+	struct snd_seq_oss_reg *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	unsigned long flags;
+
+	if ((rec = kzalloc(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(struct snd_seq_device *dev)
+{
+	int index;
+	struct seq_oss_synth *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 struct seq_oss_synth *
+get_sdev(int dev)
+{
+	struct seq_oss_synth *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(struct seq_oss_devinfo *dp)
+{
+	int i;
+	struct seq_oss_synth *rec;
+	struct seq_oss_synthinfo *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(struct seq_oss_chinfo), GFP_KERNEL);
+			if (!info->ch) {
+				snd_printk(KERN_ERR "Cannot malloc\n");
+				rec->oper.close(&info->arg);
+				module_put(rec->oper.owner);
+				snd_use_lock_free(&rec->use_lock);
+				continue;
+			}
+			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(struct seq_oss_devinfo *dp)
+{
+	int i;
+
+	if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)
+		return;
+
+	for (i = 0; i < dp->max_mididev; i++) {
+		struct seq_oss_synthinfo *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(struct seq_oss_devinfo *dp)
+{
+	int i;
+	struct seq_oss_synth *rec;
+	struct seq_oss_synthinfo *info;
+
+	if (snd_BUG_ON(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);
+		}
+		kfree(info->sysex);
+		info->sysex = NULL;
+		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(struct seq_oss_devinfo *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 struct seq_oss_synth *
+get_synthdev(struct seq_oss_devinfo *dp, int dev)
+{
+	struct seq_oss_synth *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(struct seq_oss_synthinfo *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(struct seq_oss_devinfo *dp, int dev)
+{
+	struct seq_oss_synth *rec;
+	struct seq_oss_synthinfo *info;
+
+	if (snd_BUG_ON(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;
+			kfree(info->sysex);
+			info->sysex = NULL;
+			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 {
+		struct snd_seq_event 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(struct seq_oss_devinfo *dp, int dev, int fmt,
+			    const char __user *buf, int p, int c)
+{
+	struct seq_oss_synth *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(struct seq_oss_devinfo *dp, int dev)
+{
+	struct seq_oss_synth *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(struct seq_oss_devinfo *dp, int dev, unsigned char *buf, struct snd_seq_event *ev)
+{
+	int i, send;
+	unsigned char *dest;
+	struct seq_oss_synth_sysex *sysex;
+
+	if (! snd_seq_oss_synth_is_valid(dp, dev))
+		return -ENXIO;
+
+	sysex = dp->synths[dev].sysex;
+	if (sysex == NULL) {
+		sysex = kzalloc(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(struct seq_oss_devinfo *dp, int dev, struct snd_seq_event *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(struct seq_oss_devinfo *dp, int dev, unsigned int cmd, unsigned long addr)
+{
+	struct seq_oss_synth *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(struct seq_oss_devinfo *dp, int dev, unsigned char *data, struct snd_seq_event *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(struct seq_oss_devinfo *dp, int dev, struct synth_info *inf)
+{
+	struct seq_oss_synth *rec;
+
+	if (dev < 0 || dev >= dp->max_synthdev)
+		return -ENXIO;
+
+	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;
+}
+
+
+#ifdef CONFIG_PROC_FS
+/*
+ * proc interface
+ */
+void
+snd_seq_oss_synth_info_read(struct snd_info_buffer *buf)
+{
+	int i;
+	struct seq_oss_synth *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);
+	}
+}
+#endif /* CONFIG_PROC_FS */
diff --git a/linux/sound/core/seq/oss/seq_oss_synth.h b/linux/sound/core/seq/oss/seq_oss_synth.h
new file mode 100644
index 0000000..dbdfcbb
--- /dev/null
+++ b/linux/sound/core/seq/oss/seq_oss_synth.h
@@ -0,0 +1,51 @@
+/*
+ * 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>
+
+void snd_seq_oss_synth_init(void);
+int snd_seq_oss_synth_register(struct snd_seq_device *dev);
+int snd_seq_oss_synth_unregister(struct snd_seq_device *dev);
+void snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp);
+void snd_seq_oss_synth_setup_midi(struct seq_oss_devinfo *dp);
+void snd_seq_oss_synth_cleanup(struct seq_oss_devinfo *dp);
+
+void snd_seq_oss_synth_reset(struct seq_oss_devinfo *dp, int dev);
+int snd_seq_oss_synth_load_patch(struct seq_oss_devinfo *dp, int dev, int fmt,
+				 const char __user *buf, int p, int c);
+int snd_seq_oss_synth_is_valid(struct seq_oss_devinfo *dp, int dev);
+int snd_seq_oss_synth_sysex(struct seq_oss_devinfo *dp, int dev, unsigned char *buf,
+			    struct snd_seq_event *ev);
+int snd_seq_oss_synth_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_event *ev);
+int snd_seq_oss_synth_ioctl(struct seq_oss_devinfo *dp, int dev, unsigned int cmd,
+			    unsigned long addr);
+int snd_seq_oss_synth_raw_event(struct seq_oss_devinfo *dp, int dev,
+				unsigned char *data, struct snd_seq_event *ev);
+
+int snd_seq_oss_synth_make_info(struct seq_oss_devinfo *dp, int dev, struct synth_info *inf);
+
+#endif
diff --git a/linux/sound/core/seq/oss/seq_oss_timer.c b/linux/sound/core/seq/oss/seq_oss_timer.c
new file mode 100644
index 0000000..ab59cbf
--- /dev/null
+++ b/linux/sound/core/seq/oss/seq_oss_timer.c
@@ -0,0 +1,284 @@
+/*
+ * 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>
+#include <linux/slab.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(struct seq_oss_timer *timer);
+static int send_timer_event(struct seq_oss_devinfo *dp, int type, int value);
+
+
+/*
+ * create and register a new timer.
+ * if queue is not started yet, start it.
+ */
+struct seq_oss_timer *
+snd_seq_oss_timer_new(struct seq_oss_devinfo *dp)
+{
+	struct seq_oss_timer *rec;
+
+	rec = kzalloc(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(struct seq_oss_timer *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(struct seq_oss_timer *rec, union evrec *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(struct seq_oss_timer *timer)
+{
+	timer->tempo = (60 * 1000000) / timer->oss_tempo;
+	timer->ppq = timer->oss_timebase;
+}
+
+
+/*
+ * dispatch a timer event
+ */
+static int
+send_timer_event(struct seq_oss_devinfo *dp, int type, int value)
+{
+	struct snd_seq_event 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(struct seq_oss_timer *timer)
+{
+	struct seq_oss_devinfo *dp = timer->dp;
+	struct snd_seq_queue_tempo 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(struct seq_oss_timer *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(struct seq_oss_timer *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(struct seq_oss_timer *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(struct seq_oss_timer *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/linux/sound/core/seq/oss/seq_oss_timer.h b/linux/sound/core/seq/oss/seq_oss_timer.h
new file mode 100644
index 0000000..b995bd6
--- /dev/null
+++ b/linux/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 {
+	struct seq_oss_devinfo *dp;
+	reltime_t cur_tick;
+	int realtime;
+	int running;
+	int tempo, ppq;	/* ALSA queue */
+	int oss_tempo, oss_timebase;
+};	
+
+
+struct seq_oss_timer *snd_seq_oss_timer_new(struct seq_oss_devinfo *dp);
+void snd_seq_oss_timer_delete(struct seq_oss_timer *dp);
+
+int snd_seq_oss_timer_start(struct seq_oss_timer *timer);
+int snd_seq_oss_timer_stop(struct seq_oss_timer *timer);
+int snd_seq_oss_timer_continue(struct seq_oss_timer *timer);
+int snd_seq_oss_timer_tempo(struct seq_oss_timer *timer, int value);
+#define snd_seq_oss_timer_reset  snd_seq_oss_timer_start
+
+int snd_seq_oss_timer_ioctl(struct seq_oss_timer *timer, unsigned int cmd, int __user *arg);
+
+/*
+ * get current processed time
+ */
+static inline abstime_t
+snd_seq_oss_timer_cur_tick(struct seq_oss_timer *timer)
+{
+	return timer->cur_tick;
+}
+
+
+/*
+ * is realtime event?
+ */
+static inline int
+snd_seq_oss_timer_is_realtime(struct seq_oss_timer *timer)
+{
+	return timer->realtime;
+}
+
+#endif
diff --git a/linux/sound/core/seq/oss/seq_oss_writeq.c b/linux/sound/core/seq/oss/seq_oss_writeq.c
new file mode 100644
index 0000000..d50338b
--- /dev/null
+++ b/linux/sound/core/seq/oss/seq_oss_writeq.c
@@ -0,0 +1,173 @@
+/*
+ * 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>
+#include <linux/slab.h>
+
+
+/*
+ * create a write queue record
+ */
+struct seq_oss_writeq *
+snd_seq_oss_writeq_new(struct seq_oss_devinfo *dp, int maxlen)
+{
+	struct seq_oss_writeq *q;
+	struct snd_seq_client_pool pool;
+
+	if ((q = kzalloc(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(struct seq_oss_writeq *q)
+{
+	if (q) {
+		snd_seq_oss_writeq_clear(q);	/* to be sure */
+		kfree(q);
+	}
+}
+
+
+/*
+ * reset the write queue
+ */
+void
+snd_seq_oss_writeq_clear(struct seq_oss_writeq *q)
+{
+	struct snd_seq_remove_events 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(struct seq_oss_writeq *q)
+{
+	struct seq_oss_devinfo *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) {
+		struct snd_seq_event ev;
+		union evrec *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 = (union evrec *)&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(struct seq_oss_writeq *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(struct seq_oss_writeq *q)
+{
+	struct snd_seq_client_pool 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(struct seq_oss_writeq *q, int val)
+{
+	struct snd_seq_client_pool 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/linux/sound/core/seq/oss/seq_oss_writeq.h b/linux/sound/core/seq/oss/seq_oss_writeq.h
new file mode 100644
index 0000000..c469d29
--- /dev/null
+++ b/linux/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 {
+	struct seq_oss_devinfo *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
+ */
+struct seq_oss_writeq *snd_seq_oss_writeq_new(struct seq_oss_devinfo *dp, int maxlen);
+void snd_seq_oss_writeq_delete(struct seq_oss_writeq *q);
+void snd_seq_oss_writeq_clear(struct seq_oss_writeq *q);
+int snd_seq_oss_writeq_sync(struct seq_oss_writeq *q);
+void snd_seq_oss_writeq_wakeup(struct seq_oss_writeq *q, abstime_t time);
+int snd_seq_oss_writeq_get_free_size(struct seq_oss_writeq *q);
+void snd_seq_oss_writeq_set_output(struct seq_oss_writeq *q, int size);
+
+
+#endif
diff --git a/linux/sound/core/seq/seq.c b/linux/sound/core/seq/seq.c
new file mode 100644
index 0000000..bf09a5a
--- /dev/null
+++ b/linux/sound/core/seq/seq.c
@@ -0,0 +1,132 @@
+/*
+ *  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 <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[15] = {[0] = SNDRV_SEQ_CLIENT_DUMMY, [1 ... 14] = -1};
+#else
+int seq_client_load[15] = {[0 ... 14] = -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 =
+#ifdef CONFIG_SND_SEQ_HRTIMER_DEFAULT
+	SNDRV_TIMER_GLOBAL_HRTIMER
+#elif defined(CONFIG_SND_SEQ_RTCTIMER_DEFAULT)
+	SNDRV_TIMER_GLOBAL_RTC
+#else
+	SNDRV_TIMER_GLOBAL_SYSTEM
+#endif
+	;
+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@perex.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)
diff --git a/linux/sound/core/seq/seq_clientmgr.c b/linux/sound/core/seq/seq_clientmgr.c
new file mode 100644
index 0000000..99a485f
--- /dev/null
+++ b/linux/sound/core/seq/seq_clientmgr.c
@@ -0,0 +1,2593 @@
+/*
+ *  ALSA sequencer Client Manager
+ *  Copyright (c) 1998-2001 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *                             Jaroslav Kysela <perex@perex.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 <linux/init.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
+ * 
+ */
+
+/*
+ * There are four ranges of client numbers (last two shared):
+ * 0..15: global clients
+ * 16..127: statically allocated client numbers for cards 0..27
+ * 128..191: dynamically allocated client numbers for cards 28..31
+ * 128..191: dynamically allocated client numbers for applications
+ */
+
+/* number of kernel non-card clients */
+#define SNDRV_SEQ_GLOBAL_CLIENTS	16
+/* clients per cards, for static clients */
+#define SNDRV_SEQ_CLIENTS_PER_CARD	4
+/* dynamically allocated client numbers (both kernel drivers and user space) */
+#define SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN	128
+
+#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 DEFINE_MUTEX(register_mutex);
+
+/*
+ * client table
+ */
+static char clienttablock[SNDRV_SEQ_MAX_CLIENTS];
+static struct snd_seq_client *clienttab[SNDRV_SEQ_MAX_CLIENTS];
+static struct snd_seq_usage client_usage;
+
+/*
+ * prototypes
+ */
+static int bounce_error_event(struct snd_seq_client *client,
+			      struct snd_seq_event *event,
+			      int err, int atomic, int hop);
+static int snd_seq_deliver_single_event(struct snd_seq_client *client,
+					struct snd_seq_event *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(struct snd_seq_client *client)
+{
+	return snd_seq_total_cells(client->pool) > 0;
+}
+
+/* return pointer to client structure for specified id */
+static struct snd_seq_client *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];
+}
+
+struct snd_seq_client *snd_seq_client_use_ptr(int clientid)
+{
+	unsigned long flags;
+	struct snd_seq_client *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_MODULES
+	if (!in_interrupt()) {
+		static char client_requested[SNDRV_SEQ_GLOBAL_CLIENTS];
+		static char card_requested[SNDRV_CARDS];
+		if (clientid < SNDRV_SEQ_GLOBAL_CLIENTS) {
+			int idx;
+			
+			if (!client_requested[clientid]) {
+				client_requested[clientid] = 1;
+				for (idx = 0; idx < 15; 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 < SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN) {
+			int card = (clientid - SNDRV_SEQ_GLOBAL_CLIENTS) /
+				SNDRV_SEQ_CLIENTS_PER_CARD;
+			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(struct snd_seq_usage *res, int num)
+{
+	res->cur += num;
+	if (res->cur > res->peak)
+		res->peak = res->cur;
+}
+
+static void usage_free(struct snd_seq_usage *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 struct snd_seq_client *seq_create_client1(int client_index, int poolsize)
+{
+	unsigned long flags;
+	int c;
+	struct snd_seq_client *client;
+
+	/* init client data */
+	client = kzalloc(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);
+	mutex_init(&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 = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN;
+		     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(struct snd_seq_client *client)
+{
+	unsigned long flags;
+
+	if (!client)
+		return 0;
+	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(struct snd_seq_client * client)
+{
+	mutex_lock(&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);
+	}
+	mutex_unlock(&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 */
+	struct snd_seq_client *client;
+	struct snd_seq_user_client *user;
+	int err;
+
+	err = nonseekable_open(inode, file);
+	if (err < 0)
+		return err;
+
+	if (mutex_lock_interruptible(&register_mutex))
+		return -ERESTARTSYS;
+	client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS);
+	if (client == NULL) {
+		mutex_unlock(&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);
+			mutex_unlock(&register_mutex);
+			return -ENOMEM;
+		}
+	}
+
+	usage_alloc(&client_usage, 1);
+	client->type = USER_CLIENT;
+	mutex_unlock(&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)
+{
+	struct snd_seq_client *client = 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)
+{
+	struct snd_seq_client *client = file->private_data;
+	struct snd_seq_fifo *fifo;
+	int err;
+	long result = 0;
+	struct snd_seq_event_cell *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 */
+	if (snd_BUG_ON(!client))
+		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(struct snd_seq_event)) {
+		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)) {
+			struct snd_seq_event tmpev;
+			tmpev = cell->event;
+			tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK;
+			if (copy_to_user(buf, &tmpev, sizeof(struct snd_seq_event))) {
+				err = -EFAULT;
+				break;
+			}
+			count -= sizeof(struct snd_seq_event);
+			buf += sizeof(struct snd_seq_event);
+			err = snd_seq_expand_var_event(&cell->event, count,
+						       (char __force *)buf, 0,
+						       sizeof(struct snd_seq_event));
+			if (err < 0)
+				break;
+			result += err;
+			count -= err;
+			buf += err;
+		} else {
+			if (copy_to_user(buf, &cell->event, sizeof(struct snd_seq_event))) {
+				err = -EFAULT;
+				break;
+			}
+			count -= sizeof(struct snd_seq_event);
+			buf += sizeof(struct snd_seq_event);
+		}
+		snd_seq_cell_free(cell);
+		cell = NULL; /* to be sure */
+		result += sizeof(struct snd_seq_event);
+	}
+
+	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(struct snd_seq_client_port *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 struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event,
+						    int filter)
+{
+	struct snd_seq_client *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(struct snd_seq_client *client,
+			      struct snd_seq_event *event,
+			      int err, int atomic, int hop)
+{
+	struct snd_seq_event 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(struct snd_seq_event *event,
+				     int queue, int real_time)
+{
+	struct snd_seq_queue *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(struct snd_seq_client *client,
+					struct snd_seq_event *event,
+					int filter, int atomic, int hop)
+{
+	struct snd_seq_client *dest = NULL;
+	struct snd_seq_client_port *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(struct snd_seq_client *client,
+				  struct snd_seq_event *event,
+				  int atomic, int hop)
+{
+	struct snd_seq_subscribers *subs;
+	int err = 0, num_ev = 0;
+	struct snd_seq_event event_saved;
+	struct snd_seq_client_port *src_port;
+	struct snd_seq_port_subs_info *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_entry(subs, &grp->list_head, 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(struct snd_seq_client *client,
+				struct snd_seq_event *event,
+				int atomic, int hop)
+{
+	int num_ev = 0, err = 0;
+	struct snd_seq_client *dest_client;
+	struct snd_seq_client_port *port;
+
+	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_entry(port, &dest_client->ports_list_head, 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(struct snd_seq_client *client,
+			   struct snd_seq_event *event, int atomic, int hop)
+{
+	int err = 0, num_ev = 0;
+	int dest;
+	struct snd_seq_addr 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(struct snd_seq_client *client, struct snd_seq_event *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(struct snd_seq_client *client, struct snd_seq_event *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(struct snd_seq_event_cell *cell, int atomic, int hop)
+{
+	struct snd_seq_client *client;
+	int result;
+
+	if (snd_BUG_ON(!cell))
+		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.
+		 */
+		struct snd_seq_event 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(struct snd_seq_client *client,
+					struct snd_seq_event *event,
+					struct file *file, int blocking,
+					int atomic, int hop)
+{
+	struct snd_seq_event_cell *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 */
+		struct snd_seq_client_port *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(struct snd_seq_event *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_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)
+{
+	struct snd_seq_client *client = file->private_data;
+	int written = 0, len;
+	int err = -EINVAL;
+	struct snd_seq_event event;
+
+	if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT))
+		return -ENXIO;
+
+	/* check client structures are in place */
+	if (snd_BUG_ON(!client))
+		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(struct snd_seq_event)) {
+		/* 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 __force *)buf
+						+ sizeof(struct snd_seq_event);
+			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)
+{
+	struct snd_seq_client *client = file->private_data;
+	unsigned int mask = 0;
+
+	/* check client structures are in place */
+	if (snd_BUG_ON(!client))
+		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(struct snd_seq_client *client, void __user *arg)
+{
+	struct snd_seq_system_info 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(struct snd_seq_client *client, void __user *arg)
+{
+	struct snd_seq_running_info info;
+	struct snd_seq_client *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(struct snd_seq_client *cptr,
+			    struct snd_seq_client_info *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(struct snd_seq_client *client,
+					 void __user *arg)
+{
+	struct snd_seq_client *cptr;
+	struct snd_seq_client_info 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(struct snd_seq_client *client,
+					 void __user *arg)
+{
+	struct snd_seq_client_info 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(struct snd_seq_client *client,
+				     void __user *arg)
+{
+	struct snd_seq_client_port *port;
+	struct snd_seq_port_info info;
+	struct snd_seq_port_callback *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(struct snd_seq_client *client,
+				     void __user *arg)
+{
+	struct snd_seq_port_info 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(struct snd_seq_client *client,
+				       void __user *arg)
+{
+	struct snd_seq_client *cptr;
+	struct snd_seq_client_port *port;
+	struct snd_seq_port_info 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(struct snd_seq_client *client,
+				       void __user *arg)
+{
+	struct snd_seq_client_port *port;
+	struct snd_seq_port_info 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(struct snd_seq_client *client,
+					 struct snd_seq_client_port *sport,
+					 struct snd_seq_client_port *dport,
+					 struct snd_seq_port_subscribe *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,
+				       struct snd_seq_port_subscribe *info,
+				       int evtype)
+{
+	struct snd_seq_event 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(struct snd_seq_client *client,
+					void __user *arg)
+{
+	int result = -EINVAL;
+	struct snd_seq_client *receiver = NULL, *sender = NULL;
+	struct snd_seq_client_port *sport = NULL, *dport = NULL;
+	struct snd_seq_port_subscribe 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(struct snd_seq_client *client,
+					  void __user *arg)
+{
+	int result = -ENXIO;
+	struct snd_seq_client *receiver = NULL, *sender = NULL;
+	struct snd_seq_client_port *sport = NULL, *dport = NULL;
+	struct snd_seq_port_subscribe 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(struct snd_seq_client *client,
+				      void __user *arg)
+{
+	struct snd_seq_queue_info info;
+	int result;
+	struct snd_seq_queue *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(struct snd_seq_client *client,
+				      void __user *arg)
+{
+	struct snd_seq_queue_info 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(struct snd_seq_client *client,
+					void __user *arg)
+{
+	struct snd_seq_queue_info info;
+	struct snd_seq_queue *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(struct snd_seq_client *client,
+					void __user *arg)
+{
+	struct snd_seq_queue_info info;
+	struct snd_seq_queue *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(struct snd_seq_client *client, void __user *arg)
+{
+	struct snd_seq_queue_info info;
+	struct snd_seq_queue *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(struct snd_seq_client *client,
+					  void __user *arg)
+{
+	struct snd_seq_queue_status status;
+	struct snd_seq_queue *queue;
+	struct snd_seq_timer *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(struct snd_seq_client *client,
+					 void __user *arg)
+{
+	struct snd_seq_queue_tempo tempo;
+	struct snd_seq_queue *queue;
+	struct snd_seq_timer *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, struct snd_seq_queue_tempo *tempo)
+{
+	if (!snd_seq_queue_check_access(tempo->queue, client))
+		return -EPERM;
+	return snd_seq_queue_timer_set_tempo(tempo->queue, client, tempo);
+}
+
+EXPORT_SYMBOL(snd_seq_set_queue_tempo);
+
+static int snd_seq_ioctl_set_queue_tempo(struct snd_seq_client *client,
+					 void __user *arg)
+{
+	int result;
+	struct snd_seq_queue_tempo 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(struct snd_seq_client *client,
+					 void __user *arg)
+{
+	struct snd_seq_queue_timer timer;
+	struct snd_seq_queue *queue;
+	struct snd_seq_timer *tmr;
+
+	if (copy_from_user(&timer, arg, sizeof(timer)))
+		return -EFAULT;
+
+	queue = queueptr(timer.queue);
+	if (queue == NULL)
+		return -EINVAL;
+
+	if (mutex_lock_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;
+	}
+	mutex_unlock(&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(struct snd_seq_client *client,
+					 void __user *arg)
+{
+	int result = 0;
+	struct snd_seq_queue_timer 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)) {
+		struct snd_seq_queue *q;
+		struct snd_seq_timer *tmr;
+
+		q = queueptr(timer.queue);
+		if (q == NULL)
+			return -ENXIO;
+		if (mutex_lock_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);
+		mutex_unlock(&q->timer_mutex);
+		queuefree(q);
+	} else {
+		return -EPERM;
+	}	
+
+	return result;
+}
+
+
+/* GET_QUEUE_CLIENT ioctl() */
+static int snd_seq_ioctl_get_queue_client(struct snd_seq_client *client,
+					  void __user *arg)
+{
+	struct snd_seq_queue_client 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(struct snd_seq_client *client,
+					  void __user *arg)
+{
+	int err;
+	struct snd_seq_queue_client 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(struct snd_seq_client *client,
+					 void __user *arg)
+{
+	struct snd_seq_client_pool info;
+	struct snd_seq_client *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;
+	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(struct snd_seq_client *client,
+					 void __user *arg)
+{
+	struct snd_seq_client_pool 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(struct snd_seq_client *client,
+				       void __user *arg)
+{
+	struct snd_seq_remove_events 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(struct snd_seq_client *client,
+					  void __user *arg)
+{
+	int result;
+	struct snd_seq_client *sender = NULL;
+	struct snd_seq_client_port *sport = NULL;
+	struct snd_seq_port_subscribe subs;
+	struct snd_seq_subscribers *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(struct snd_seq_client *client,
+				    void __user *arg)
+{
+	int result = -ENXIO;
+	struct snd_seq_client *cptr = NULL;
+	struct snd_seq_client_port *port = NULL;
+	struct snd_seq_query_subs subs;
+	struct snd_seq_port_subs_info *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! */
+			struct snd_seq_subscribers *s;
+			if (subs.type == SNDRV_SEQ_QUERY_SUBS_READ) {
+				s = list_entry(p, struct snd_seq_subscribers, src_list);
+				subs.addr = s->info.dest;
+			} else {
+				s = list_entry(p, struct snd_seq_subscribers, 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(struct snd_seq_client *client,
+					   void __user *arg)
+{
+	struct snd_seq_client *cptr = NULL;
+	struct snd_seq_client_info 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(struct snd_seq_client *client,
+					 void __user *arg)
+{
+	struct snd_seq_client *cptr;
+	struct snd_seq_client_port *port = NULL;
+	struct snd_seq_port_info 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)(struct snd_seq_client *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(struct snd_seq_client *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%02x)\n",
+		   cmd, _IOC_TYPE(cmd), _IOC_NR(cmd));
+	return -ENOTTY;
+}
+
+
+static long snd_seq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct snd_seq_client *client = file->private_data;
+
+	if (snd_BUG_ON(!client))
+		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(struct snd_card *card, int client_index,
+				 const char *name_fmt, ...)
+{
+	struct snd_seq_client *client;
+	va_list args;
+
+	if (snd_BUG_ON(in_interrupt()))
+		return -EBUSY;
+
+	if (card && client_index >= SNDRV_SEQ_CLIENTS_PER_CARD)
+		return -EINVAL;
+	if (card == NULL && client_index >= SNDRV_SEQ_GLOBAL_CLIENTS)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&register_mutex))
+		return -ERESTARTSYS;
+
+	if (card) {
+		client_index += SNDRV_SEQ_GLOBAL_CLIENTS
+			+ card->number * SNDRV_SEQ_CLIENTS_PER_CARD;
+		if (client_index >= SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN)
+			client_index = -1;
+	}
+
+	/* empty write queue as default */
+	client = seq_create_client1(client_index, 0);
+	if (client == NULL) {
+		mutex_unlock(&register_mutex);
+		return -EBUSY;	/* failure code */
+	}
+	usage_alloc(&client_usage, 1);
+
+	client->accept_input = 1;
+	client->accept_output = 1;
+		
+	va_start(args, name_fmt);
+	vsnprintf(client->name, sizeof(client->name), name_fmt, args);
+	va_end(args);
+
+	client->type = KERNEL_CLIENT;
+	mutex_unlock(&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;
+}
+
+EXPORT_SYMBOL(snd_seq_create_kernel_client);
+
+/* exported to kernel modules */
+int snd_seq_delete_kernel_client(int client)
+{
+	struct snd_seq_client *ptr;
+
+	if (snd_BUG_ON(in_interrupt()))
+		return -EBUSY;
+
+	ptr = clientptr(client);
+	if (ptr == NULL)
+		return -EINVAL;
+
+	seq_free_client(ptr);
+	kfree(ptr);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_seq_delete_kernel_client);
+
+/* 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, struct snd_seq_event *ev,
+				 struct file *file, int blocking,
+				 int atomic, int hop)
+{
+	struct snd_seq_client *cptr;
+	int result;
+
+	if (snd_BUG_ON(!ev))
+		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, struct snd_seq_event * ev,
+				  int atomic, int hop)
+{
+	return kernel_client_enqueue(client, ev, NULL, 0, atomic, hop);
+}
+
+EXPORT_SYMBOL(snd_seq_kernel_client_enqueue);
+
+/*
+ * 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, struct snd_seq_event * ev,
+					   struct file *file,
+					   int atomic, int hop)
+{
+	return kernel_client_enqueue(client, ev, file, 1, atomic, hop);
+}
+
+EXPORT_SYMBOL(snd_seq_kernel_client_enqueue_blocking);
+
+/* 
+ * 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, struct snd_seq_event * ev,
+				   int atomic, int hop)
+{
+	struct snd_seq_client *cptr;
+	int result;
+
+	if (snd_BUG_ON(!ev))
+		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;
+}
+
+EXPORT_SYMBOL(snd_seq_kernel_client_dispatch);
+
+/*
+ * 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)
+{
+	struct snd_seq_client *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;
+}
+
+EXPORT_SYMBOL(snd_seq_kernel_client_ctl);
+
+/* exported (for OSS emulator) */
+int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait)
+{
+	struct snd_seq_client *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;
+}
+
+EXPORT_SYMBOL(snd_seq_kernel_client_write_poll);
+
+/*---------------------------------------------------------------------------*/
+
+#ifdef CONFIG_PROC_FS
+/*
+ *  /proc interface
+ */
+static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer,
+					  struct snd_seq_port_subs_info *group,
+					  int is_src, char *msg)
+{
+	struct list_head *p;
+	struct snd_seq_subscribers *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, struct snd_seq_subscribers, src_list);
+		else
+			s = list_entry(p, struct snd_seq_subscribers, 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(struct snd_info_buffer *buffer,
+				    struct snd_seq_client *client)
+{
+	struct snd_seq_client_port *p;
+
+	mutex_lock(&client->ports_mutex);
+	list_for_each_entry(p, &client->ports_list_head, 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: ");
+	}
+	mutex_unlock(&client->ports_mutex);
+}
+
+
+void snd_seq_info_pool(struct snd_info_buffer *buffer,
+		       struct snd_seq_pool *pool, char *space);
+
+/* exported to seq_info.c */
+void snd_seq_info_clients_read(struct snd_info_entry *entry, 
+			       struct snd_info_buffer *buffer)
+{
+	int c;
+	struct snd_seq_client *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);
+	}
+}
+#endif /* CONFIG_PROC_FS */
+
+/*---------------------------------------------------------------------------*/
+
+
+/*
+ *  REGISTRATION PART
+ */
+
+static const 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,
+	.llseek =	no_llseek,
+	.poll =		snd_seq_poll,
+	.unlocked_ioctl =	snd_seq_ioctl,
+	.compat_ioctl =	snd_seq_ioctl_compat,
+};
+
+/* 
+ * register sequencer device 
+ */
+int __init snd_sequencer_device_init(void)
+{
+	int err;
+
+	if (mutex_lock_interruptible(&register_mutex))
+		return -ERESTARTSYS;
+
+	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0,
+				       &snd_seq_f_ops, NULL, "seq")) < 0) {
+		mutex_unlock(&register_mutex);
+		return err;
+	}
+	
+	mutex_unlock(&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/linux/sound/core/seq/seq_clientmgr.h b/linux/sound/core/seq/seq_clientmgr.h
new file mode 100644
index 0000000..20f0a72
--- /dev/null
+++ b/linux/sound/core/seq/seq_clientmgr.h
@@ -0,0 +1,103 @@
+/*
+ *   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 */
+	struct snd_seq_fifo *fifo;	/* queue for incoming events */
+	int fifo_pool_size;
+};
+
+struct snd_seq_kernel_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 mutex ports_mutex;
+	int convert32;		/* convert 32->64bit */
+
+	/* output pool */
+	struct snd_seq_pool *pool;		/* memory pool for this client */
+
+	union {
+		struct snd_seq_user_client user;
+		struct snd_seq_kernel_client kernel;
+	} data;
+};
+
+/* usage statistics */
+struct snd_seq_usage {
+	int cur;
+	int peak;
+};
+
+
+int client_init_data(void);
+int snd_sequencer_device_init(void);
+void snd_sequencer_device_done(void);
+
+/* get locked pointer to client */
+struct snd_seq_client *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) */
+int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop);
+
+/* exported to other modules */
+int snd_seq_kernel_client_enqueue(int client, struct snd_seq_event *ev, int atomic, int hop);
+int snd_seq_kernel_client_enqueue_blocking(int client, struct snd_seq_event * 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,
+				       struct snd_seq_port_subscribe *info, int evtype);
+
+extern int seq_client_load[15];
+
+#endif
diff --git a/linux/sound/core/seq/seq_compat.c b/linux/sound/core/seq/seq_compat.c
new file mode 100644
index 0000000..81f7c10
--- /dev/null
+++ b/linux/sound/core/seq/seq_compat.c
@@ -0,0 +1,138 @@
+/*
+ *   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>
+#include <linux/slab.h>
+
+struct snd_seq_port_info32 {
+	struct snd_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(struct snd_seq_client *client, unsigned int cmd,
+					struct snd_seq_port_info32 __user *data32)
+{
+	int err = -EFAULT;
+	struct snd_seq_port_info *data;
+	mm_segment_t fs;
+
+	data = memdup_user(data32, sizeof(*data32));
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	if (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 snd_seq_port_info32),
+	SNDRV_SEQ_IOCTL_DELETE_PORT32 = _IOW ('S', 0x21, struct snd_seq_port_info32),
+	SNDRV_SEQ_IOCTL_GET_PORT_INFO32 = _IOWR('S', 0x22, struct snd_seq_port_info32),
+	SNDRV_SEQ_IOCTL_SET_PORT_INFO32 = _IOW ('S', 0x23, struct snd_seq_port_info32),
+	SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32 = _IOWR('S', 0x52, struct snd_seq_port_info32),
+};
+
+static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct snd_seq_client *client = file->private_data;
+	void __user *argp = compat_ptr(arg);
+
+	if (snd_BUG_ON(!client))
+		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/linux/sound/core/seq/seq_device.c b/linux/sound/core/seq/seq_device.c
new file mode 100644
index 0000000..1f99767
--- /dev/null
+++ b/linux/sound/core/seq/seq_device.c
@@ -0,0 +1,572 @@
+/*
+ *  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 <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>
+#include <linux/mutex.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ALSA sequencer device management");
+MODULE_LICENSE("GPL");
+
+/* 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 */
+	struct snd_seq_dev_ops 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 mutex reg_mutex;
+
+	struct list_head list;	/* next driver */
+};
+
+
+static LIST_HEAD(opslist);
+static int num_ops;
+static DEFINE_MUTEX(ops_mutex);
+#ifdef CONFIG_PROC_FS
+static struct snd_info_entry *info_entry;
+#endif
+
+/*
+ * prototypes
+ */
+static int snd_seq_device_free(struct snd_seq_device *dev);
+static int snd_seq_device_dev_free(struct snd_device *device);
+static int snd_seq_device_dev_register(struct snd_device *device);
+static int snd_seq_device_dev_disconnect(struct snd_device *device);
+
+static int init_device(struct snd_seq_device *dev, struct ops_list *ops);
+static int free_device(struct snd_seq_device *dev, struct ops_list *ops);
+static struct ops_list *find_driver(char *id, int create_if_empty);
+static struct ops_list *create_driver(char *id);
+static void unlock_driver(struct ops_list *ops);
+static void remove_drivers(void);
+
+/*
+ * show all drivers and their status
+ */
+
+#ifdef CONFIG_PROC_FS
+static void snd_seq_device_info(struct snd_info_entry *entry,
+				struct snd_info_buffer *buffer)
+{
+	struct ops_list *ops;
+
+	mutex_lock(&ops_mutex);
+	list_for_each_entry(ops, &opslist, 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);
+	}
+	mutex_unlock(&ops_mutex);
+}
+#endif
+ 
+/*
+ * load all registered drivers (called from seq_clientmgr.c)
+ */
+
+#ifdef CONFIG_MODULES
+/* 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_MODULES
+	struct ops_list *ops;
+
+	/* Calling request_module during module_init()
+	 * may cause blocking.
+	 */
+	if (snd_seq_in_init)
+		return;
+
+	mutex_lock(&ops_mutex);
+	list_for_each_entry(ops, &opslist, list) {
+		if (! (ops->driver & DRIVER_LOADED) &&
+		    ! (ops->driver & DRIVER_REQUESTED)) {
+			ops->used++;
+			mutex_unlock(&ops_mutex);
+			ops->driver |= DRIVER_REQUESTED;
+			request_module("snd-%s", ops->id);
+			mutex_lock(&ops_mutex);
+			ops->used--;
+		}
+	}
+	mutex_unlock(&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(struct snd_card *card, int device, char *id, int argsize,
+		       struct snd_seq_device **result)
+{
+	struct snd_seq_device *dev;
+	struct ops_list *ops;
+	int err;
+	static struct snd_device_ops dops = {
+		.dev_free = snd_seq_device_dev_free,
+		.dev_register = snd_seq_device_dev_register,
+		.dev_disconnect = snd_seq_device_dev_disconnect,
+	};
+
+	if (result)
+		*result = NULL;
+
+	if (snd_BUG_ON(!id))
+		return -EINVAL;
+
+	ops = find_driver(id, 1);
+	if (ops == NULL)
+		return -ENOMEM;
+
+	dev = kzalloc(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 */
+	mutex_lock(&ops->reg_mutex);
+	list_add_tail(&dev->list, &ops->dev_list);
+	ops->num_devices++;
+	mutex_unlock(&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(struct snd_seq_device *dev)
+{
+	struct ops_list *ops;
+
+	if (snd_BUG_ON(!dev))
+		return -EINVAL;
+
+	ops = find_driver(dev->id, 0);
+	if (ops == NULL)
+		return -ENXIO;
+
+	/* remove the device from the list */
+	mutex_lock(&ops->reg_mutex);
+	list_del(&dev->list);
+	ops->num_devices--;
+	mutex_unlock(&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(struct snd_device *device)
+{
+	struct snd_seq_device *dev = device->device_data;
+	return snd_seq_device_free(dev);
+}
+
+/*
+ * register the device
+ */
+static int snd_seq_device_dev_register(struct snd_device *device)
+{
+	struct snd_seq_device *dev = device->device_data;
+	struct ops_list *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(struct snd_device *device)
+{
+	struct snd_seq_device *dev = device->device_data;
+	struct ops_list *ops;
+
+	ops = find_driver(dev->id, 0);
+	if (ops == NULL)
+		return -ENOENT;
+
+	free_device(dev, ops);
+
+	unlock_driver(ops);
+	return 0;
+}
+
+/*
+ * register device driver
+ * id = driver id
+ * entry = driver operators - duplicated to each instance
+ */
+int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry,
+				   int argsize)
+{
+	struct ops_list *ops;
+	struct snd_seq_device *dev;
+
+	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;
+	}
+
+	mutex_lock(&ops->reg_mutex);
+	/* copy driver operators */
+	ops->ops = *entry;
+	ops->driver |= DRIVER_LOADED;
+	ops->argsize = argsize;
+
+	/* initialize existing devices if necessary */
+	list_for_each_entry(dev, &ops->dev_list, list) {
+		init_device(dev, ops);
+	}
+	mutex_unlock(&ops->reg_mutex);
+
+	unlock_driver(ops);
+	snd_seq_autoload_unlock();
+
+	return 0;
+}
+
+
+/*
+ * create driver record
+ */
+static struct ops_list * create_driver(char *id)
+{
+	struct ops_list *ops;
+
+	ops = kzalloc(sizeof(*ops), GFP_KERNEL);
+	if (ops == NULL)
+		return ops;
+
+	/* set up driver entry */
+	strlcpy(ops->id, id, sizeof(ops->id));
+	mutex_init(&ops->reg_mutex);
+	/*
+	 * The ->reg_mutex locking rules are per-driver, so we create
+	 * separate per-driver lock classes:
+	 */
+	lockdep_set_class(&ops->reg_mutex, (struct lock_class_key *)id);
+
+	ops->driver = DRIVER_EMPTY;
+	INIT_LIST_HEAD(&ops->dev_list);
+	/* lock this instance */
+	ops->used = 1;
+
+	/* register driver entry */
+	mutex_lock(&ops_mutex);
+	list_add_tail(&ops->list, &opslist);
+	num_ops++;
+	mutex_unlock(&ops_mutex);
+
+	return ops;
+}
+
+
+/*
+ * unregister the specified driver
+ */
+int snd_seq_device_unregister_driver(char *id)
+{
+	struct ops_list *ops;
+	struct snd_seq_device *dev;
+
+	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 */
+	mutex_lock(&ops->reg_mutex);
+	ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */
+	list_for_each_entry(dev, &ops->dev_list, 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);
+	mutex_unlock(&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;
+
+	mutex_lock(&ops_mutex);
+	head = opslist.next;
+	while (head != &opslist) {
+		struct ops_list *ops = list_entry(head, struct ops_list, 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;
+	}
+	mutex_unlock(&ops_mutex);
+}
+
+/*
+ * initialize the device - call init_device operator
+ */
+static int init_device(struct snd_seq_device *dev, struct ops_list *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(struct snd_seq_device *dev, struct ops_list *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 struct ops_list * find_driver(char *id, int create_if_empty)
+{
+	struct ops_list *ops;
+
+	mutex_lock(&ops_mutex);
+	list_for_each_entry(ops, &opslist, list) {
+		if (strcmp(ops->id, id) == 0) {
+			ops->used++;
+			mutex_unlock(&ops_mutex);
+			return ops;
+		}
+	}
+	mutex_unlock(&ops_mutex);
+	if (create_if_empty)
+		return create_driver(id);
+	return NULL;
+}
+
+static void unlock_driver(struct ops_list *ops)
+{
+	mutex_lock(&ops_mutex);
+	ops->used--;
+	mutex_unlock(&ops_mutex);
+}
+
+
+/*
+ * module part
+ */
+
+static int __init alsa_seq_device_init(void)
+{
+#ifdef CONFIG_PROC_FS
+	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 = snd_seq_device_info;
+	if (snd_info_register(info_entry) < 0) {
+		snd_info_free_entry(info_entry);
+		return -ENOMEM;
+	}
+#endif
+	return 0;
+}
+
+static void __exit alsa_seq_device_exit(void)
+{
+	remove_drivers();
+#ifdef CONFIG_PROC_FS
+	snd_info_free_entry(info_entry);
+#endif
+	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);
+EXPORT_SYMBOL(snd_seq_autoload_lock);
+EXPORT_SYMBOL(snd_seq_autoload_unlock);
diff --git a/linux/sound/core/seq/seq_dummy.c b/linux/sound/core/seq/seq_dummy.c
new file mode 100644
index 0000000..f3bdc54
--- /dev/null
+++ b/linux/sound/core/seq/seq_dummy.c
@@ -0,0 +1,261 @@
+/*
+ * 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 <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;
+
+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");
+
+struct snd_seq_dummy_port {
+	int client;
+	int port;
+	int duplex;
+	int connect;
+};
+
+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, struct snd_seq_port_subscribe *info)
+{
+	struct snd_seq_dummy_port *p;
+	int i;
+	struct snd_seq_event 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(struct snd_seq_event *ev, int direct, void *private_data,
+	    int atomic, int hop)
+{
+	struct snd_seq_dummy_port *p;
+	struct snd_seq_event 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)
+{
+	kfree(private_data);
+}
+
+/*
+ * create a port
+ */
+static struct snd_seq_dummy_port __init *
+create_port(int idx, int type)
+{
+	struct snd_seq_port_info pinfo;
+	struct snd_seq_port_callback pcb;
+	struct snd_seq_dummy_port *rec;
+
+	if ((rec = kzalloc(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
+		| SNDRV_SEQ_PORT_TYPE_SOFTWARE
+		| SNDRV_SEQ_PORT_TYPE_PORT;
+	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)
+{
+	struct snd_seq_dummy_port *rec1, *rec2;
+	int i;
+
+	if (ports < 1) {
+		snd_printk(KERN_ERR "invalid number of ports %d\n", ports);
+		return -EINVAL;
+	}
+
+	/* create client */
+	my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY,
+						 "Midi Through");
+	if (my_client < 0)
+		return my_client;
+
+	/* 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/linux/sound/core/seq/seq_fifo.c b/linux/sound/core/seq/seq_fifo.c
new file mode 100644
index 0000000..0d75afa
--- /dev/null
+++ b/linux/sound/core/seq/seq_fifo.c
@@ -0,0 +1,272 @@
+/*
+ *   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/core.h>
+#include <linux/slab.h>
+#include "seq_fifo.h"
+#include "seq_lock.h"
+
+
+/* FIFO */
+
+/* create new fifo */
+struct snd_seq_fifo *snd_seq_fifo_new(int poolsize)
+{
+	struct snd_seq_fifo *f;
+
+	f = kzalloc(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(struct snd_seq_fifo **fifo)
+{
+	struct snd_seq_fifo *f;
+
+	if (snd_BUG_ON(!fifo))
+		return;
+	f = *fifo;
+	if (snd_BUG_ON(!f))
+		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 struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f);
+
+/* clear queue */
+void snd_seq_fifo_clear(struct snd_seq_fifo *f)
+{
+	struct snd_seq_event_cell *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(struct snd_seq_fifo *f,
+			  struct snd_seq_event *event)
+{
+	struct snd_seq_event_cell *cell;
+	unsigned long flags;
+	int err;
+
+	if (snd_BUG_ON(!f))
+		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 struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f)
+{
+	struct snd_seq_event_cell *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(struct snd_seq_fifo *f,
+			  struct snd_seq_event_cell **cellp, int nonblock)
+{
+	struct snd_seq_event_cell *cell;
+	unsigned long flags;
+	wait_queue_t wait;
+
+	if (snd_BUG_ON(!f))
+		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(struct snd_seq_fifo *f,
+			       struct snd_seq_event_cell *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(struct snd_seq_fifo *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(struct snd_seq_fifo *f, int poolsize)
+{
+	unsigned long flags;
+	struct snd_seq_pool *newpool, *oldpool;
+	struct snd_seq_event_cell *cell, *next, *oldhead;
+
+	if (snd_BUG_ON(!f || !f->pool))
+		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/linux/sound/core/seq/seq_fifo.h b/linux/sound/core/seq/seq_fifo.h
new file mode 100644
index 0000000..062c446
--- /dev/null
+++ b/linux/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 === */
+
+struct snd_seq_fifo {
+	struct snd_seq_pool *pool;		/* FIFO pool */
+	struct snd_seq_event_cell *head;    	/* pointer to head of fifo */
+	struct snd_seq_event_cell *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;
+
+};
+
+/* create new fifo (constructor) */
+struct snd_seq_fifo *snd_seq_fifo_new(int poolsize);
+
+/* delete fifo (destructor) */
+void snd_seq_fifo_delete(struct snd_seq_fifo **f);
+
+
+/* enqueue event to fifo */
+int snd_seq_fifo_event_in(struct snd_seq_fifo *f, struct snd_seq_event *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(struct snd_seq_fifo *f, struct snd_seq_event_cell **cellp, int nonblock);
+
+/* free dequeued cell - fifo should be locked */
+void snd_seq_fifo_cell_putback(struct snd_seq_fifo *f, struct snd_seq_event_cell *cell);
+
+/* clean up queue */
+void snd_seq_fifo_clear(struct snd_seq_fifo *f);
+
+/* polling */
+int snd_seq_fifo_poll_wait(struct snd_seq_fifo *f, struct file *file, poll_table *wait);
+
+/* resize pool in fifo */
+int snd_seq_fifo_resize(struct snd_seq_fifo *f, int poolsize);
+
+
+#endif
diff --git a/linux/sound/core/seq/seq_info.c b/linux/sound/core/seq/seq_info.c
new file mode 100644
index 0000000..201f810
--- /dev/null
+++ b/linux/sound/core/seq/seq_info.c
@@ -0,0 +1,71 @@
+/*
+ *   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 <linux/init.h>
+#include <sound/core.h>
+
+#include "seq_info.h"
+#include "seq_clientmgr.h"
+#include "seq_timer.h"
+
+#ifdef CONFIG_PROC_FS
+static struct snd_info_entry *queues_entry;
+static struct snd_info_entry *clients_entry;
+static struct snd_info_entry *timer_entry;
+
+
+static struct snd_info_entry * __init
+create_info_entry(char *name, void (*read)(struct snd_info_entry *,
+					   struct snd_info_buffer *))
+{
+	struct snd_info_entry *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 = 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",
+					 snd_seq_info_queues_read);
+	clients_entry = create_info_entry("clients",
+					  snd_seq_info_clients_read);
+	timer_entry = create_info_entry("timer", snd_seq_info_timer_read);
+	return 0;
+}
+
+int __exit snd_seq_info_done(void)
+{
+	snd_info_free_entry(queues_entry);
+	snd_info_free_entry(clients_entry);
+	snd_info_free_entry(timer_entry);
+	return 0;
+}
+#endif
diff --git a/linux/sound/core/seq/seq_info.h b/linux/sound/core/seq/seq_info.h
new file mode 100644
index 0000000..4892a7f
--- /dev/null
+++ b/linux/sound/core/seq/seq_info.h
@@ -0,0 +1,40 @@
+/*
+ *   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(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
+void snd_seq_info_timer_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
+void snd_seq_info_queues_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
+
+
+#ifdef CONFIG_PROC_FS
+int snd_seq_info_init( void );
+int snd_seq_info_done( void );
+#else
+static inline int snd_seq_info_init(void) { return 0; }
+static inline int snd_seq_info_done(void) { return 0; }
+#endif
+
+#endif
diff --git a/linux/sound/core/seq/seq_lock.c b/linux/sound/core/seq/seq_lock.c
new file mode 100644
index 0000000..54f921e
--- /dev/null
+++ b/linux/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/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;
+		}
+		schedule_timeout_uninterruptible(1);
+		max_count--;
+	}
+}
+
+EXPORT_SYMBOL(snd_use_lock_sync_helper);
+
+#endif
diff --git a/linux/sound/core/seq/seq_lock.h b/linux/sound/core/seq/seq_lock.h
new file mode 100644
index 0000000..54044bc
--- /dev/null
+++ b/linux/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/linux/sound/core/seq/seq_memory.c b/linux/sound/core/seq/seq_memory.c
new file mode 100644
index 0000000..7fb5543
--- /dev/null
+++ b/linux/sound/core/seq/seq_memory.c
@@ -0,0 +1,520 @@
+/*
+ *  ALSA sequencer Memory Manager
+ *  Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *                        Jaroslav Kysela <perex@perex.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 <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"
+
+static inline int snd_seq_pool_available(struct snd_seq_pool *pool)
+{
+	return pool->total_elements - atomic_read(&pool->counter);
+}
+
+static inline int snd_seq_output_ok(struct snd_seq_pool *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 struct snd_seq_event *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 struct snd_seq_event *event,
+			   snd_seq_dump_func_t func, void *private_data)
+{
+	int len, err;
+	struct snd_seq_event_cell *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 = (struct snd_seq_event_cell *)event->data.ext.ptr;
+	for (; len > 0 && cell; cell = cell->next) {
+		int size = sizeof(struct snd_seq_event);
+		if (len < size)
+			size = len;
+		err = func(private_data, &cell->event, size);
+		if (err < 0)
+			return err;
+		len -= size;
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_seq_dump_var_event);
+
+
+/*
+ * 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 struct snd_seq_event *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 = roundup(len, 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;
+}
+
+EXPORT_SYMBOL(snd_seq_expand_var_event);
+
+/*
+ * release this cell, free extended data if available
+ */
+
+static inline void free_cell(struct snd_seq_pool *pool,
+			     struct snd_seq_event_cell *cell)
+{
+	cell->next = pool->free;
+	pool->free = cell;
+	atomic_dec(&pool->counter);
+}
+
+void snd_seq_cell_free(struct snd_seq_event_cell * cell)
+{
+	unsigned long flags;
+	struct snd_seq_pool *pool;
+
+	if (snd_BUG_ON(!cell))
+		return;
+	pool = cell->pool;
+	if (snd_BUG_ON(!pool))
+		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) {
+			struct snd_seq_event_cell *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(struct snd_seq_pool *pool,
+			      struct snd_seq_event_cell **cellp,
+			      int nonblock, struct file *file)
+{
+	struct snd_seq_event_cell *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(struct snd_seq_pool *pool, struct snd_seq_event *event,
+		      struct snd_seq_event_cell **cellp, int nonblock,
+		      struct file *file)
+{
+	int ncells, err;
+	unsigned int extlen;
+	struct snd_seq_event_cell *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(struct snd_seq_event) - 1) / sizeof(struct snd_seq_event);
+	}
+	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;
+		struct snd_seq_event_cell *src, *tmp, *tail;
+		char *buf;
+
+		cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED;
+		cell->event.data.ext.ptr = NULL;
+
+		src = (struct snd_seq_event_cell *)event->data.ext.ptr;
+		buf = (char *)event->data.ext.ptr;
+		tail = NULL;
+
+		while (ncells-- > 0) {
+			int size = sizeof(struct snd_seq_event);
+			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(struct snd_seq_pool *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(struct snd_seq_pool *pool)
+{
+	int cell;
+	struct snd_seq_event_cell *cellptr;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!pool))
+		return -EINVAL;
+	if (pool->ptr)			/* should be atomic? */
+		return 0;
+
+	pool->ptr = vmalloc(sizeof(struct snd_seq_event_cell) * 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(struct snd_seq_pool *pool)
+{
+	unsigned long flags;
+	struct snd_seq_event_cell *ptr;
+	int max_count = 5 * HZ;
+
+	if (snd_BUG_ON(!pool))
+		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;
+		}
+		schedule_timeout_uninterruptible(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 */
+struct snd_seq_pool *snd_seq_pool_new(int poolsize)
+{
+	struct snd_seq_pool *pool;
+
+	/* create pool block */
+	pool = kzalloc(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(struct snd_seq_pool **ppool)
+{
+	struct snd_seq_pool *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(struct snd_info_buffer *buffer,
+		       struct snd_seq_pool *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/linux/sound/core/seq/seq_memory.h b/linux/sound/core/seq/seq_memory.h
new file mode 100644
index 0000000..63e9143
--- /dev/null
+++ b/linux/sound/core/seq/seq_memory.h
@@ -0,0 +1,103 @@
+/*
+ *  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>
+
+/* container for sequencer event (internal use) */
+struct snd_seq_event_cell {
+	struct snd_seq_event event;
+	struct snd_seq_pool *pool;				/* used pool */
+	struct snd_seq_event_cell *next;	/* next cell */
+};
+
+/* design note: the pool is a contiguous 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 snd_seq_pool {
+	struct snd_seq_event_cell *ptr;	/* pointer to first event chunk */
+	struct snd_seq_event_cell *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;
+};
+
+void snd_seq_cell_free(struct snd_seq_event_cell *cell);
+
+int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event,
+		      struct snd_seq_event_cell **cellp, int nonblock, struct file *file);
+
+/* return number of unused (free) cells */
+static inline int snd_seq_unused_cells(struct snd_seq_pool *pool)
+{
+	return pool ? pool->total_elements - atomic_read(&pool->counter) : 0;
+}
+
+/* return total number of allocated cells */
+static inline int snd_seq_total_cells(struct snd_seq_pool *pool)
+{
+	return pool ? pool->total_elements : 0;
+}
+
+/* init pool - allocate events */
+int snd_seq_pool_init(struct snd_seq_pool *pool);
+
+/* done pool - free events */
+int snd_seq_pool_done(struct snd_seq_pool *pool);
+
+/* create pool */
+struct snd_seq_pool *snd_seq_pool_new(int poolsize);
+
+/* remove pool */
+int snd_seq_pool_delete(struct snd_seq_pool **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(struct snd_seq_pool *pool, struct file *file, poll_table *wait);
+
+
+#endif
diff --git a/linux/sound/core/seq/seq_midi.c b/linux/sound/core/seq/seq_midi.c
new file mode 100644
index 0000000..ebaf1b5
--- /dev/null
+++ b/linux/sound/core/seq/seq_midi.c
@@ -0,0 +1,481 @@
+/*
+ *   Generic MIDI synth driver for ALSA sequencer
+ *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *                         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
+ *
+ */
+ 
+/* 
+Possible options for midisynth module:
+	- automatic opening of midi ports on first received event or subscription
+	  (close will be performed when client leaves)
+*/
+
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.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@perex.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 */
+struct seq_midisynth {
+	struct snd_card *card;
+	int device;
+	int subdevice;
+	struct snd_rawmidi_file input_rfile;
+	struct snd_rawmidi_file output_rfile;
+	int seq_client;
+	int seq_port;
+	struct snd_midi_event *parser;
+};
+
+struct seq_midisynth_client {
+	int seq_client;
+	int num_ports;
+	int ports_per_device[SNDRV_RAWMIDI_DEVICES];
+ 	struct seq_midisynth *ports[SNDRV_RAWMIDI_DEVICES];
+};
+
+static struct seq_midisynth_client *synths[SNDRV_CARDS];
+static DEFINE_MUTEX(register_mutex);
+
+/* handle rawmidi input event (MIDI v1.0 stream) */
+static void snd_midi_input_event(struct snd_rawmidi_substream *substream)
+{
+	struct snd_rawmidi_runtime *runtime;
+	struct seq_midisynth *msynth;
+	struct snd_seq_event ev;
+	char buf[16], *pbuf;
+	long res, count;
+
+	if (substream == NULL)
+		return;
+	runtime = substream->runtime;
+	msynth = 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(struct snd_rawmidi_substream *substream, const char *buf, int count)
+{
+	struct snd_rawmidi_runtime *runtime;
+	int tmp;
+
+	if (snd_BUG_ON(!substream || !buf))
+		return -EINVAL;
+	runtime = substream->runtime;
+	if ((tmp = runtime->avail) < count) {
+		if (printk_ratelimit())
+			snd_printk(KERN_ERR "MIDI output buffer overrun\n");
+		return -ENOMEM;
+	}
+	if (snd_rawmidi_kernel_write(substream, buf, count) < count)
+		return -EINVAL;
+	return 0;
+}
+
+static int event_process_midi(struct snd_seq_event *ev, int direct,
+			      void *private_data, int atomic, int hop)
+{
+	struct seq_midisynth *msynth = private_data;
+	unsigned char msg[10];	/* buffer for constructing midi messages */
+	struct snd_rawmidi_substream *substream;
+	int len;
+
+	if (snd_BUG_ON(!msynth))
+		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;
+		}
+		snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)dump_midi, substream);
+		snd_midi_event_reset_decode(msynth->parser);
+	} else {
+		if (msynth->parser == NULL)
+			return -EIO;
+		len = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev);
+		if (len < 0)
+			return 0;
+		if (dump_midi(substream, msg, len) < 0)
+			snd_midi_event_reset_decode(msynth->parser);
+	}
+	return 0;
+}
+
+
+static int snd_seq_midisynth_new(struct seq_midisynth *msynth,
+				 struct snd_card *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, struct snd_seq_port_subscribe *info)
+{
+	int err;
+	struct seq_midisynth *msynth = private_data;
+	struct snd_rawmidi_runtime *runtime;
+	struct snd_rawmidi_params params;
+
+	/* open midi port */
+	if ((err = snd_rawmidi_kernel_open(msynth->card, 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, struct snd_seq_port_subscribe *info)
+{
+	int err;
+	struct seq_midisynth *msynth = private_data;
+
+	if (snd_BUG_ON(!msynth->input_rfile.input))
+		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, struct snd_seq_port_subscribe *info)
+{
+	int err;
+	struct seq_midisynth *msynth = private_data;
+	struct snd_rawmidi_params params;
+
+	/* open midi port */
+	if ((err = snd_rawmidi_kernel_open(msynth->card, 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;
+	params.no_active_sensing = 1;
+	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, struct snd_seq_port_subscribe *info)
+{
+	struct seq_midisynth *msynth = private_data;
+
+	if (snd_BUG_ON(!msynth->output_rfile.output))
+		return -EINVAL;
+	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(struct seq_midisynth *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);
+}
+
+/* register new midi synth port */
+static int
+snd_seq_midisynth_register_port(struct snd_seq_device *dev)
+{
+	struct seq_midisynth_client *client;
+	struct seq_midisynth *msynth, *ms;
+	struct snd_seq_port_info *port;
+	struct snd_rawmidi_info *info;
+	struct snd_rawmidi *rmidi = dev->private_data;
+	int newclient = 0;
+	unsigned int p, ports;
+	struct snd_seq_port_callback pcallbacks;
+	struct snd_card *card = dev->card;
+	int device = dev->device;
+	unsigned int input_count = 0, output_count = 0;
+
+	if (snd_BUG_ON(!card || 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;
+
+	mutex_lock(&register_mutex);
+	client = synths[card->number];
+	if (client == NULL) {
+		newclient = 1;
+		client = kzalloc(sizeof(*client), GFP_KERNEL);
+		if (client == NULL) {
+			mutex_unlock(&register_mutex);
+			kfree(info);
+			return -ENOMEM;
+		}
+		client->seq_client =
+			snd_seq_create_kernel_client(
+				card, 0, "%s", card->shortname[0] ?
+				(const char *)card->shortname : "External MIDI");
+		if (client->seq_client < 0) {
+			kfree(client);
+			mutex_unlock(&register_mutex);
+			kfree(info);
+			return -ENOMEM;
+		}
+	}
+
+	msynth = kcalloc(ports, sizeof(struct seq_midisynth), 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
+			| SNDRV_SEQ_PORT_TYPE_HARDWARE
+			| SNDRV_SEQ_PORT_TYPE_PORT;
+		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 (rmidi->ops && rmidi->ops->get_port_info)
+			rmidi->ops->get_port_info(rmidi, p, port);
+		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;
+	mutex_unlock(&register_mutex);
+	kfree(info);
+	kfree(port);
+	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);
+	mutex_unlock(&register_mutex);
+	return -ENOMEM;
+}
+
+/* release midi synth port */
+static int
+snd_seq_midisynth_unregister_port(struct snd_seq_device *dev)
+{
+	struct seq_midisynth_client *client;
+	struct seq_midisynth *msynth;
+	struct snd_card *card = dev->card;
+	int device = dev->device, p, ports;
+	
+	mutex_lock(&register_mutex);
+	client = synths[card->number];
+	if (client == NULL || client->ports[device] == NULL) {
+		mutex_unlock(&register_mutex);
+		return -ENODEV;
+	}
+	ports = client->ports_per_device[device];
+	client->ports_per_device[device] = 0;
+	msynth = client->ports[device];
+	client->ports[device] = NULL;
+	for (p = 0; p < ports; p++)
+		snd_seq_midisynth_delete(&msynth[p]);
+	kfree(msynth);
+	client->num_ports--;
+	if (client->num_ports <= 0) {
+		snd_seq_delete_kernel_client(client->seq_client);
+		synths[card->number] = NULL;
+		kfree(client);
+	}
+	mutex_unlock(&register_mutex);
+	return 0;
+}
+
+
+static int __init alsa_seq_midi_init(void)
+{
+	static struct snd_seq_dev_ops 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/linux/sound/core/seq/seq_midi_emul.c b/linux/sound/core/seq/seq_midi_emul.c
new file mode 100644
index 0000000..07c6631
--- /dev/null
+++ b/linux/sound/core/seq/seq_midi_emul.c
@@ -0,0 +1,739 @@
+/*
+ *  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 <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(struct snd_midi_op *ops, void *drv,
+		     struct snd_midi_channel *chan,
+		     int note, int vel);
+static void do_control(struct snd_midi_op *ops, void *private,
+		       struct snd_midi_channel_set *chset,
+		       struct snd_midi_channel *chan,
+		       int control, int value);
+static void rpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
+		struct snd_midi_channel_set *chset);
+static void nrpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
+		 struct snd_midi_channel_set *chset);
+static void sysex(struct snd_midi_op *ops, void *private, unsigned char *sysex,
+		  int len, struct snd_midi_channel_set *chset);
+static void all_sounds_off(struct snd_midi_op *ops, void *private,
+			   struct snd_midi_channel *chan);
+static void all_notes_off(struct snd_midi_op *ops, void *private,
+			  struct snd_midi_channel *chan);
+static void snd_midi_reset_controllers(struct snd_midi_channel *chan);
+static void reset_all_channels(struct snd_midi_channel_set *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 controllers 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(struct snd_midi_op *ops,
+		       struct snd_seq_event *ev,
+		       struct snd_midi_channel_set *chanset)
+{
+	struct snd_midi_channel *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 controller 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 controller 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_ECHO:
+	not_yet:
+	default:
+		/*snd_printd("Unimplemented event %d\n", ev->type);*/
+		break;
+	}
+}
+
+
+/*
+ * release note
+ */
+static void
+note_off(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *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 controller and pass
+ * events that need to take place immediately to the driver.
+ */
+static void
+do_control(struct snd_midi_op *ops, void *drv, struct snd_midi_channel_set *chset,
+	   struct snd_midi_channel *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(struct snd_midi_channel_set *chset)
+{
+	int i;
+
+	chset->midi_mode = SNDRV_MIDI_MODE_GM;
+	chset->gs_master_volume = 127;
+
+	for (i = 0; i < chset->max_channels; i++) {
+		struct snd_midi_channel *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(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
+    struct snd_midi_channel_set *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(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
+     struct snd_midi_channel_set *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(struct snd_midi_op *ops, void *private, unsigned char *buf, int len,
+      struct snd_midi_channel_set *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(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *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(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *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(struct snd_midi_channel *p, int n)
+{
+	if (p == NULL)
+		return;
+
+	memset(p, 0, sizeof(struct snd_midi_channel));
+	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 struct snd_midi_channel *snd_midi_channel_init_set(int n)
+{
+	struct snd_midi_channel *chan;
+	int  i;
+
+	chan = kmalloc(n * sizeof(struct snd_midi_channel), 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(struct snd_midi_channel_set *chset)
+{
+	int ch;
+	for (ch = 0; ch < chset->max_channels; ch++) {
+		struct snd_midi_channel *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.
+ */
+struct snd_midi_channel_set *snd_midi_channel_alloc_set(int n)
+{
+	struct snd_midi_channel_set *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(struct snd_midi_channel *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(struct snd_midi_channel_set *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/linux/sound/core/seq/seq_midi_event.c b/linux/sound/core/seq/seq_midi_event.c
new file mode 100644
index 0000000..b5d6ea4
--- /dev/null
+++ b/linux/sound/core/seq/seq_midi_event.c
@@ -0,0 +1,549 @@
+/*
+ *  MIDI byte <-> sequencer event coder
+ *
+ *  Copyright (C) 1998,99 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
+ */
+
+#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@perex.cz>");
+MODULE_DESCRIPTION("MIDI byte <-> sequencer event coder");
+MODULE_LICENSE("GPL");
+
+/* event type, index into status_event[] */
+/* from 0 to 6 are normal commands (note off, on, etc.) for 0x9?-0xe? */
+#define ST_INVALID	7
+#define ST_SPECIAL	8
+#define ST_SYSEX	ST_SPECIAL
+/* from 8 to 15 are events for 0xf0-0xf7 */
+
+
+/*
+ * prototypes
+ */
+static void note_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
+static void one_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
+static void pitchbend_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
+static void two_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
+static void one_param_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
+static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
+static void note_decode(struct snd_seq_event *ev, unsigned char *buf);
+static void one_param_decode(struct snd_seq_event *ev, unsigned char *buf);
+static void pitchbend_decode(struct snd_seq_event *ev, unsigned char *buf);
+static void two_param_decode(struct snd_seq_event *ev, unsigned char *buf);
+static void songpos_decode(struct snd_seq_event *ev, unsigned char *buf);
+
+/*
+ * event list
+ */
+static struct status_event_list {
+	int event;
+	int qlen;
+	void (*encode)(struct snd_midi_event *dev, struct snd_seq_event *ev);
+	void (*decode)(struct snd_seq_event *ev, unsigned char *buf);
+} status_event[] = {
+	/* 0x80 - 0xef */
+	{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},
+	/* invalid */
+	{SNDRV_SEQ_EVENT_NONE,		-1, NULL, NULL},
+	/* 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,		-1, NULL, NULL}, /* 0xf4 */
+	{SNDRV_SEQ_EVENT_NONE,		-1, NULL, NULL}, /* 0xf5 */
+	{SNDRV_SEQ_EVENT_TUNE_REQUEST,	 0, NULL, NULL}, /* 0xf6 */
+	{SNDRV_SEQ_EVENT_NONE,		-1, NULL, NULL}, /* 0xf7 */
+	{SNDRV_SEQ_EVENT_CLOCK,		 0, NULL, NULL}, /* 0xf8 */
+	{SNDRV_SEQ_EVENT_NONE,		-1, 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, 		-1, NULL, NULL}, /* 0xfd */
+	{SNDRV_SEQ_EVENT_SENSING, 	 0, NULL, NULL}, /* 0xfe */
+	{SNDRV_SEQ_EVENT_RESET, 	 0, NULL, NULL}, /* 0xff */
+};
+
+static int extra_decode_ctrl14(struct snd_midi_event *dev, unsigned char *buf, int len,
+			       struct snd_seq_event *ev);
+static int extra_decode_xrpn(struct snd_midi_event *dev, unsigned char *buf, int count,
+			     struct snd_seq_event *ev);
+
+static struct extra_event_list {
+	int event;
+	int (*decode)(struct snd_midi_event *dev, unsigned char *buf, int len,
+		      struct snd_seq_event *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, struct snd_midi_event **rdev)
+{
+	struct snd_midi_event *dev;
+
+	*rdev = NULL;
+	dev = kzalloc(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;
+	dev->type = ST_INVALID;
+	spin_lock_init(&dev->lock);
+	*rdev = dev;
+	return 0;
+}
+
+void snd_midi_event_free(struct snd_midi_event *dev)
+{
+	if (dev != NULL) {
+		kfree(dev->buf);
+		kfree(dev);
+	}
+}
+
+/*
+ * initialize record
+ */
+static inline void reset_encode(struct snd_midi_event *dev)
+{
+	dev->read = 0;
+	dev->qlen = 0;
+	dev->type = ST_INVALID;
+}
+
+void snd_midi_event_reset_encode(struct snd_midi_event *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(struct snd_midi_event *dev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	dev->lastcmd = 0xff;
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+#if 0
+void snd_midi_event_init(struct snd_midi_event *dev)
+{
+	snd_midi_event_reset_encode(dev);
+	snd_midi_event_reset_decode(dev);
+}
+#endif  /*  0  */
+
+void snd_midi_event_no_status(struct snd_midi_event *dev, int on)
+{
+	dev->nostat = on ? 1 : 0;
+}
+
+/*
+ * resize buffer
+ */
+#if 0
+int snd_midi_event_resize_buffer(struct snd_midi_event *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;
+}
+#endif  /*  0  */
+
+/*
+ *  read bytes and encode to sequencer event if finished
+ *  return the size of encoded bytes
+ */
+long snd_midi_event_encode(struct snd_midi_event *dev, unsigned char *buf, long count,
+			   struct snd_seq_event *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(struct snd_midi_event *dev, int c,
+			       struct snd_seq_event *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 ev->type != SNDRV_SEQ_EVENT_NONE;
+	}
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if ((c & 0x80) &&
+	    (c != MIDI_CMD_COMMON_SYSEX_END || dev->type != ST_SYSEX)) {
+		/* new command */
+		dev->buf[0] = c;
+		if ((c & 0xf0) == 0xf0) /* system messages */
+			dev->type = (c & 0x0f) + ST_SPECIAL;
+		else
+			dev->type = (c >> 4) & 0x07;
+		dev->read = 1;
+		dev->qlen = status_event[dev->type].qlen;
+	} else {
+		if (dev->qlen > 0) {
+			/* rest of command */
+			dev->buf[dev->read++] = c;
+			if (dev->type != ST_SYSEX)
+				dev->qlen--;
+		} else {
+			/* running status */
+			dev->buf[1] = c;
+			dev->qlen = status_event[dev->type].qlen - 1;
+			dev->read = 2;
+		}
+	}
+	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);
+		if (dev->type >= ST_SPECIAL)
+			dev->type = ST_INVALID;
+		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(struct snd_midi_event *dev, struct snd_seq_event *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(struct snd_midi_event *dev, struct snd_seq_event *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(struct snd_midi_event *dev, struct snd_seq_event *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(struct snd_midi_event *dev, struct snd_seq_event *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(struct snd_midi_event *dev, struct snd_seq_event *ev)
+{
+	ev->data.control.value = dev->buf[1];
+}
+
+/* encode song position */
+static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *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(struct snd_midi_event *dev, unsigned char *buf, long count,
+			   struct snd_seq_event *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(struct snd_seq_event *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(struct snd_seq_event *ev, unsigned char *buf)
+{
+	buf[0] = ev->data.control.value & 0x7f;
+}
+
+/* decode pitch wheel change */
+static void pitchbend_decode(struct snd_seq_event *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(struct snd_seq_event *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(struct snd_seq_event *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(struct snd_midi_event *dev, unsigned char *buf,
+			       int count, struct snd_seq_event *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(struct snd_midi_event *dev, unsigned char *buf,
+			     int count, struct snd_seq_event *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 & 0x3f80) >> 7;
+	bytes[1] = ev->data.control.param & 0x007f;
+	bytes[2] = (ev->data.control.value & 0x3f80) >> 7;
+	bytes[3] = ev->data.control.value & 0x007f;
+	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_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/linux/sound/core/seq/seq_ports.c b/linux/sound/core/seq/seq_ports.c
new file mode 100644
index 0000000..3bf7d73
--- /dev/null
+++ b/linux/sound/core/seq/seq_ports.c
@@ -0,0 +1,684 @@
+/*
+ *   ALSA sequencer Ports
+ *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *                         Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/core.h>
+#include <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 */
+struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client,
+						 int num)
+{
+	struct snd_seq_client_port *port;
+
+	if (client == NULL)
+		return NULL;
+	read_lock(&client->ports_lock);
+	list_for_each_entry(port, &client->ports_list_head, 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 */
+struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client,
+						       struct snd_seq_port_info *pinfo)
+{
+	int num;
+	struct snd_seq_client_port *port, *found;
+
+	num = pinfo->addr.port;
+	found = NULL;
+	read_lock(&client->ports_lock);
+	list_for_each_entry(port, &client->ports_list_head, 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 snd_seq_port_subs_info */
+static void port_subs_info_init(struct snd_seq_port_subs_info *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) */
+struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client,
+						int port)
+{
+	unsigned long flags;
+	struct snd_seq_client_port *new_port, *p;
+	int num = -1;
+	
+	/* sanity check */
+	if (snd_BUG_ON(!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 = kzalloc(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;
+	mutex_lock(&client->ports_mutex);
+	write_lock_irqsave(&client->ports_lock, flags);
+	list_for_each_entry(p, &client->ports_list_head, 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, &p->list);
+	client->num_ports++;
+	new_port->addr.port = num;	/* store the port number in the port */
+	write_unlock_irqrestore(&client->ports_lock, flags);
+	mutex_unlock(&client->ports_mutex);
+	sprintf(new_port->name, "port-%d", num);
+
+	return new_port;
+}
+
+/* */
+enum group_type {
+	SRC_LIST, DEST_LIST
+};
+
+static int subscribe_port(struct snd_seq_client *client,
+			  struct snd_seq_client_port *port,
+			  struct snd_seq_port_subs_info *grp,
+			  struct snd_seq_port_subscribe *info, int send_ack);
+static int unsubscribe_port(struct snd_seq_client *client,
+			    struct snd_seq_client_port *port,
+			    struct snd_seq_port_subs_info *grp,
+			    struct snd_seq_port_subscribe *info, int send_ack);
+
+
+static struct snd_seq_client_port *get_client_port(struct snd_seq_addr *addr,
+						   struct snd_seq_client **cp)
+{
+	struct snd_seq_client_port *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(struct snd_seq_client *client,
+				  struct snd_seq_client_port *port,
+				  struct snd_seq_port_subs_info *grp,
+				  int grptype)
+{
+	struct list_head *p, *n;
+
+	list_for_each_safe(p, n, &grp->list_head) {
+		struct snd_seq_subscribers *subs;
+		struct snd_seq_client *c;
+		struct snd_seq_client_port *aport;
+
+		if (grptype == SRC_LIST) {
+			subs = list_entry(p, struct snd_seq_subscribers, src_list);
+			aport = get_client_port(&subs->info.dest, &c);
+		} else {
+			subs = list_entry(p, struct snd_seq_subscribers, 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 */
+			struct snd_seq_port_subs_info *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);
+			up_write(&agrp->list_mutex);
+			unsubscribe_port(c, aport, agrp, &subs->info, 1);
+			kfree(subs);
+			snd_seq_port_unlock(aport);
+			snd_seq_client_unlock(c);
+		}
+	}
+}
+
+/* delete port data */
+static int port_delete(struct snd_seq_client *client,
+		       struct snd_seq_client_port *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_BUG_ON(port->c_src.count != 0);
+	snd_BUG_ON(port->c_dest.count != 0);
+
+	kfree(port);
+	return 0;
+}
+
+
+/* delete a port with the given port id */
+int snd_seq_delete_port(struct snd_seq_client *client, int port)
+{
+	unsigned long flags;
+	struct snd_seq_client_port *found = NULL, *p;
+
+	mutex_lock(&client->ports_mutex);
+	write_lock_irqsave(&client->ports_lock, flags);
+	list_for_each_entry(p, &client->ports_list_head, list) {
+		if (p->addr.port == port) {
+			/* ok found.  delete from the list at first */
+			list_del(&p->list);
+			client->num_ports--;
+			found = p;
+			break;
+		}
+	}
+	write_unlock_irqrestore(&client->ports_lock, flags);
+	mutex_unlock(&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(struct snd_seq_client *client)
+{
+	unsigned long flags;
+	struct list_head deleted_list;
+	struct snd_seq_client_port *port, *tmp;
+	
+	/* move the port list to deleted_list, and
+	 * clear the port list in the client data.
+	 */
+	mutex_lock(&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);
+		list_del_init(&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_entry_safe(port, tmp, &deleted_list, list) {
+		list_del(&port->list);
+		snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port);
+		port_delete(client, port);
+	}
+	mutex_unlock(&client->ports_mutex);
+	return 0;
+}
+
+/* set port info fields */
+int snd_seq_set_port_info(struct snd_seq_client_port * port,
+			  struct snd_seq_port_info * info)
+{
+	if (snd_BUG_ON(!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(struct snd_seq_client_port * port,
+			  struct snd_seq_port_info * info)
+{
+	if (snd_BUG_ON(!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(struct snd_seq_client *client,
+			  struct snd_seq_client_port *port,
+			  struct snd_seq_port_subs_info *grp,
+			  struct snd_seq_port_subscribe *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(struct snd_seq_client *client,
+			    struct snd_seq_client_port *port,
+			    struct snd_seq_port_subs_info *grp,
+			    struct snd_seq_port_subscribe *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(struct snd_seq_addr *r, struct snd_seq_addr *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(struct snd_seq_port_subscribe *r,
+			   struct snd_seq_port_subscribe *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(struct snd_seq_client *connector,
+			 struct snd_seq_client *src_client,
+			 struct snd_seq_client_port *src_port,
+			 struct snd_seq_client *dest_client,
+			 struct snd_seq_client_port *dest_port,
+			 struct snd_seq_port_subscribe *info)
+{
+	struct snd_seq_port_subs_info *src = &src_port->c_src;
+	struct snd_seq_port_subs_info *dest = &dest_port->c_dest;
+	struct snd_seq_subscribers *subs, *s;
+	int err, src_called = 0;
+	unsigned long flags;
+	int exclusive;
+
+	subs = kzalloc(sizeof(*subs), GFP_KERNEL);
+	if (! subs)
+		return -ENOMEM;
+
+	subs->info = *info;
+	atomic_set(&subs->ref_count, 2);
+
+	down_write(&src->list_mutex);
+	down_write_nested(&dest->list_mutex, SINGLE_DEPTH_NESTING);
+
+	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_entry(s, &src->list_head, src_list) {
+			if (match_subs_info(info, &s->info))
+				goto __error;
+		}
+		list_for_each_entry(s, &dest->list_head, 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(struct snd_seq_client *connector,
+			    struct snd_seq_client *src_client,
+			    struct snd_seq_client_port *src_port,
+			    struct snd_seq_client *dest_client,
+			    struct snd_seq_client_port *dest_port,
+			    struct snd_seq_port_subscribe *info)
+{
+	struct snd_seq_port_subs_info *src = &src_port->c_src;
+	struct snd_seq_port_subs_info *dest = &dest_port->c_dest;
+	struct snd_seq_subscribers *subs;
+	int err = -ENOENT;
+	unsigned long flags;
+
+	down_write(&src->list_mutex);
+	down_write_nested(&dest->list_mutex, SINGLE_DEPTH_NESTING);
+
+	/* look for the connection */
+	list_for_each_entry(subs, &src->list_head, 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 */
+struct snd_seq_subscribers *snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp,
+							  struct snd_seq_addr *dest_addr)
+{
+	struct snd_seq_subscribers *s, *found = NULL;
+
+	down_read(&src_grp->list_mutex);
+	list_for_each_entry(s, &src_grp->list_head, 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,
+			      struct snd_seq_port_callback *pcbp,
+			      int cap, int type, int midi_channels,
+			      int midi_voices, char *portname)
+{
+	struct snd_seq_port_info 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;
+}
+
+EXPORT_SYMBOL(snd_seq_event_port_attach);
+
+/*
+ * Detach the driver from a port.
+ */
+/* exported */
+int snd_seq_event_port_detach(int client, int port)
+{
+	struct snd_seq_port_info 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;
+}
+
+EXPORT_SYMBOL(snd_seq_event_port_detach);
diff --git a/linux/sound/core/seq/seq_ports.h b/linux/sound/core/seq/seq_ports.h
new file mode 100644
index 0000000..9d71171
--- /dev/null
+++ b/linux/sound/core/seq/seq_ports.h
@@ -0,0 +1,142 @@
+/*
+ *   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.
+ 
+*/
+
+struct snd_seq_subscribers {
+	struct snd_seq_port_subscribe info;	/* additional info */
+	struct list_head src_list;	/* link of sources */
+	struct list_head dest_list;	/* link of destinations */
+	atomic_t ref_count;
+};
+
+struct snd_seq_port_subs_info {
+	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;
+	int (*open)(void *private_data, struct snd_seq_port_subscribe *info);
+	int (*close)(void *private_data, struct snd_seq_port_subscribe *info);
+};
+
+struct snd_seq_client_port {
+
+	struct snd_seq_addr 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 */
+	struct snd_seq_port_subs_info c_src;	/* read (sender) list */
+	struct snd_seq_port_subs_info c_dest;	/* write (dest) list */
+
+	int (*event_input)(struct snd_seq_event *ev, int direct, void *private_data,
+			   int atomic, int hop);
+	void (*private_free)(void *private_data);
+	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;
+		
+};
+
+struct snd_seq_client;
+
+/* return pointer to port structure and lock port */
+struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, int num);
+
+/* search for next port - port is locked if found */
+struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client,
+						       struct snd_seq_port_info *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) */
+struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, int port_index);
+
+/* delete a port */
+int snd_seq_delete_port(struct snd_seq_client *client, int port);
+
+/* delete all ports */
+int snd_seq_delete_all_ports(struct snd_seq_client *client);
+
+/* set port info fields */
+int snd_seq_set_port_info(struct snd_seq_client_port *port,
+			  struct snd_seq_port_info *info);
+
+/* get port info fields */
+int snd_seq_get_port_info(struct snd_seq_client_port *port,
+			  struct snd_seq_port_info *info);
+
+/* add subscriber to subscription list */
+int snd_seq_port_connect(struct snd_seq_client *caller,
+			 struct snd_seq_client *s, struct snd_seq_client_port *sp,
+			 struct snd_seq_client *d, struct snd_seq_client_port *dp,
+			 struct snd_seq_port_subscribe *info);
+
+/* remove subscriber from subscription list */ 
+int snd_seq_port_disconnect(struct snd_seq_client *caller,
+			    struct snd_seq_client *s, struct snd_seq_client_port *sp,
+			    struct snd_seq_client *d, struct snd_seq_client_port *dp,
+			    struct snd_seq_port_subscribe *info);
+
+/* subscribe port */
+int snd_seq_port_subscribe(struct snd_seq_client_port *port,
+			   struct snd_seq_port_subscribe *info);
+
+/* get matched subscriber */
+struct snd_seq_subscribers *snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp,
+							  struct snd_seq_addr *dest_addr);
+
+#endif
diff --git a/linux/sound/core/seq/seq_prioq.c b/linux/sound/core/seq/seq_prioq.c
new file mode 100644
index 0000000..29896ab
--- /dev/null
+++ b/linux/sound/core/seq/seq_prioq.c
@@ -0,0 +1,453 @@
+/*
+ *   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 <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) */
+struct snd_seq_prioq *snd_seq_prioq_new(void)
+{
+	struct snd_seq_prioq *f;
+
+	f = kzalloc(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(struct snd_seq_prioq **fifo)
+{
+	struct snd_seq_prioq *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(struct snd_seq_event *a,
+				    struct snd_seq_event *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(struct snd_seq_event *a,
+					struct snd_seq_event *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(struct snd_seq_prioq * f,
+			  struct snd_seq_event_cell * cell)
+{
+	struct snd_seq_event_cell *cur, *prev;
+	unsigned long flags;
+	int count;
+	int prior;
+
+	if (snd_BUG_ON(!f || !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 */
+struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f)
+{
+	struct snd_seq_event_cell *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(struct snd_seq_prioq * 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 */
+struct snd_seq_event_cell *snd_seq_prioq_cell_peek(struct snd_seq_prioq * 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(struct snd_seq_event_cell *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(struct snd_seq_prioq * f, int client, int timestamp)
+{
+	register struct snd_seq_event_cell *cell, *next;
+	unsigned long flags;
+	struct snd_seq_event_cell *prev = NULL;
+	struct snd_seq_event_cell *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(KERN_DEBUG "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(struct snd_seq_remove_events *info,
+			      struct snd_seq_event *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(struct snd_seq_prioq * f, int client,
+				 struct snd_seq_remove_events *info)
+{
+	struct snd_seq_event_cell *cell, *next;
+	unsigned long flags;
+	struct snd_seq_event_cell *prev = NULL;
+	struct snd_seq_event_cell *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/linux/sound/core/seq/seq_prioq.h b/linux/sound/core/seq/seq_prioq.h
new file mode 100644
index 0000000..d38bb78
--- /dev/null
+++ b/linux/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 === */
+
+struct snd_seq_prioq {
+	struct snd_seq_event_cell *head;      /* pointer to head of prioq */
+	struct snd_seq_event_cell *tail;      /* pointer to tail of prioq */
+	int cells;
+	spinlock_t lock;
+};
+
+
+/* create new prioq (constructor) */
+struct snd_seq_prioq *snd_seq_prioq_new(void);
+
+/* delete prioq (destructor) */
+void snd_seq_prioq_delete(struct snd_seq_prioq **fifo);
+
+/* enqueue cell to prioq */
+int snd_seq_prioq_cell_in(struct snd_seq_prioq *f, struct snd_seq_event_cell *cell);
+
+/* dequeue cell from prioq */ 
+struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f);
+
+/* return number of events available in prioq */
+int snd_seq_prioq_avail(struct snd_seq_prioq *f);
+
+/* peek at cell at the head of the prioq */
+struct snd_seq_event_cell *snd_seq_prioq_cell_peek(struct snd_seq_prioq *f);
+
+/* client left queue */
+void snd_seq_prioq_leave(struct snd_seq_prioq *f, int client, int timestamp);        
+
+/* Remove events */
+void snd_seq_prioq_remove_events(struct snd_seq_prioq *f, int client,
+				 struct snd_seq_remove_events *info);
+
+#endif
diff --git a/linux/sound/core/seq/seq_queue.c b/linux/sound/core/seq/seq_queue.c
new file mode 100644
index 0000000..e7a8e9e
--- /dev/null
+++ b/linux/sound/core/seq/seq_queue.c
@@ -0,0 +1,795 @@
+/*
+ *   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 struct snd_seq_queue pointer is returned via
+ *       queueptr().  This pointer *MUST* be released afterward by
+ *       queuefree(ptr).
+ *     - Addition of experimental sync support.
+ */
+
+#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 struct snd_seq_queue *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(struct snd_seq_queue *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 struct snd_seq_queue *queue_list_remove(int id, int client)
+{
+	struct snd_seq_queue *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 struct snd_seq_queue *queue_new(int owner, int locked)
+{
+	struct snd_seq_queue *q;
+
+	q = kzalloc(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);
+	mutex_init(&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(struct snd_seq_queue *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)
+{
+	struct snd_seq_queue *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)
+{
+	struct snd_seq_queue *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 */
+struct snd_seq_queue *queueptr(int queueid)
+{
+	struct snd_seq_queue *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 */
+struct snd_seq_queue *snd_seq_queue_find_name(char *name)
+{
+	int i;
+	struct snd_seq_queue *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(struct snd_seq_queue *q, int atomic, int hop)
+{
+	unsigned long flags;
+	struct snd_seq_event_cell *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(struct snd_seq_event_cell *cell, int atomic, int hop)
+{
+	int dest, err;
+	struct snd_seq_queue *q;
+
+	if (snd_BUG_ON(!cell))
+		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(struct snd_seq_queue *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(struct snd_seq_queue *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(struct snd_seq_queue *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)
+{
+	struct snd_seq_queue *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)
+{
+	struct snd_seq_queue *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;
+	struct snd_seq_queue *queue;
+	struct snd_seq_timer *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)
+{
+	struct snd_seq_queue *queue;
+	struct snd_seq_timer *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,
+				  struct snd_seq_queue_tempo *info)
+{
+	struct snd_seq_queue *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)
+{
+	struct snd_seq_queue *queue;
+
+	queue = queueptr(queueid);
+	if (queue == NULL)
+		return -EINVAL;
+	mutex_lock(&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);
+	}
+	mutex_unlock(&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)
+{
+	struct snd_seq_queue *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;
+	struct snd_seq_queue *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;
+	struct snd_seq_queue *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;
+	struct snd_seq_queue *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, struct snd_seq_remove_events *info)
+{
+	int i;
+	struct snd_seq_queue *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(struct snd_seq_queue *q, struct snd_seq_event *ev,
+				  int atomic, int hop)
+{
+	struct snd_seq_event 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.
+ */
+static void snd_seq_queue_process_event(struct snd_seq_queue *q,
+					struct snd_seq_event *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(struct snd_seq_event *ev, int atomic, int hop)
+{
+	struct snd_seq_queue *q;
+
+	if (snd_BUG_ON(!ev))
+		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;
+}
+
+
+/*----------------------------------------------------------------*/
+
+#ifdef CONFIG_PROC_FS
+/* exported to seq_info.c */
+void snd_seq_info_queues_read(struct snd_info_entry *entry, 
+			      struct snd_info_buffer *buffer)
+{
+	int i, bpm;
+	struct snd_seq_queue *q;
+	struct snd_seq_timer *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);
+	}
+}
+#endif /* CONFIG_PROC_FS */
+
diff --git a/linux/sound/core/seq/seq_queue.h b/linux/sound/core/seq/seq_queue.h
new file mode 100644
index 0000000..30c8111
--- /dev/null
+++ b/linux/sound/core/seq/seq_queue.h
@@ -0,0 +1,139 @@
+/*
+ *   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 */
+
+	struct snd_seq_prioq	*tickq;		/* midi tick event queue */
+	struct snd_seq_prioq	*timeq;		/* real-time event queue */	
+	
+	struct snd_seq_timer *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 mutex 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(struct snd_seq_event_cell *cell, int atomic, int hop);
+
+/* Remove events */
+void snd_seq_queue_client_leave_cells(int client);
+void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info);
+
+/* return pointer to queue structure for specified id */
+struct snd_seq_queue *queueptr(int queueid);
+/* unlock */
+#define queuefree(q) snd_use_lock_free(&(q)->use_lock)
+
+/* return the (first) queue matching with the specified name */
+struct snd_seq_queue *snd_seq_queue_find_name(char *name);
+
+/* check single queue and dispatch events */
+void snd_seq_check_queue(struct snd_seq_queue *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, struct snd_seq_queue_tempo *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(struct snd_seq_event *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/linux/sound/core/seq/seq_system.c b/linux/sound/core/seq/seq_system.c
new file mode 100644
index 0000000..c38b90c
--- /dev/null
+++ b/linux/sound/core/seq/seq_system.c
@@ -0,0 +1,174 @@
+/*
+ *   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 <linux/init.h>
+#include <linux/slab.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(struct snd_seq_event * ev, int client, int port)
+{
+	if (announce_port < 0)
+		return -ENODEV;
+
+	memset(ev, 0, sizeof(struct snd_seq_event));
+
+	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)
+{
+	struct snd_seq_event 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, struct snd_seq_event *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(struct snd_seq_event * 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)
+{
+	struct snd_seq_port_callback pcallbacks;
+	struct snd_seq_port_info *port;
+
+	port = kzalloc(sizeof(*port), GFP_KERNEL);
+	if (!port)
+		return -ENOMEM;
+
+	memset(&pcallbacks, 0, sizeof(pcallbacks));
+	pcallbacks.owner = THIS_MODULE;
+	pcallbacks.event_input = event_input_timer;
+
+	/* register client */
+	sysclient = snd_seq_create_kernel_client(NULL, 0, "System");
+
+	/* 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(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/linux/sound/core/seq/seq_system.h b/linux/sound/core/seq/seq_system.h
new file mode 100644
index 0000000..cf2cfa2
--- /dev/null
+++ b/linux/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, struct snd_seq_event *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/linux/sound/core/seq/seq_timer.c b/linux/sound/core/seq/seq_timer.c
new file mode 100644
index 0000000..160b1bd
--- /dev/null
+++ b/linux/sound/core/seq/seq_timer.c
@@ -0,0 +1,455 @@
+/*
+ *   ALSA sequencer Timer
+ *   Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *                              Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/core.h>
+#include <linux/slab.h>
+#include "seq_timer.h"
+#include "seq_queue.h"
+#include "seq_info.h"
+
+/* allowed sequencer timer frequencies, in Hz */
+#define MIN_FREQUENCY		10
+#define MAX_FREQUENCY		6250
+#define DEFAULT_FREQUENCY	1000
+
+#define SKEW_BASE	0x10000	/* 16bit shift */
+
+static void snd_seq_timer_set_tick_resolution(struct snd_seq_timer *tmr)
+{
+	if (tmr->tempo < 1000000)
+		tmr->tick.resolution = (tmr->tempo * 1000) / tmr->ppq;
+	else {
+		/* might overflow.. */
+		unsigned int s;
+		s = tmr->tempo % tmr->ppq;
+		s = (s * 1000) / tmr->ppq;
+		tmr->tick.resolution = (tmr->tempo / tmr->ppq) * 1000;
+		tmr->tick.resolution += s;
+	}
+	if (tmr->tick.resolution <= 0)
+		tmr->tick.resolution = 1;
+	snd_seq_timer_update_tick(&tmr->tick, 0);
+}
+
+/* create new timer (constructor) */
+struct snd_seq_timer *snd_seq_timer_new(void)
+{
+	struct snd_seq_timer *tmr;
+	
+	tmr = kzalloc(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(struct snd_seq_timer **tmr)
+{
+	struct snd_seq_timer *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(struct snd_seq_timer * tmr)
+{
+	/* setup defaults */
+	tmr->ppq = 96;		/* 96 PPQ */
+	tmr->tempo = 500000;	/* 120 BPM */
+	snd_seq_timer_set_tick_resolution(tmr);
+	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(struct snd_seq_timer * 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(struct snd_timer_instance *timeri,
+				    unsigned long resolution,
+				    unsigned long ticks)
+{
+	unsigned long flags;
+	struct snd_seq_queue *q = timeri->callback_data;
+	struct snd_seq_timer *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(struct snd_seq_timer * tmr, int tempo)
+{
+	unsigned long flags;
+
+	if (snd_BUG_ON(!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);
+	}
+	spin_unlock_irqrestore(&tmr->lock, flags);
+	return 0;
+}
+
+/* set current ppq */
+int snd_seq_timer_set_ppq(struct snd_seq_timer * tmr, int ppq)
+{
+	unsigned long flags;
+
+	if (snd_BUG_ON(!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);
+	spin_unlock_irqrestore(&tmr->lock, flags);
+	return 0;
+}
+
+/* set current tick position */
+int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr,
+				    snd_seq_tick_time_t position)
+{
+	unsigned long flags;
+
+	if (snd_BUG_ON(!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(struct snd_seq_timer *tmr,
+				    snd_seq_real_time_t position)
+{
+	unsigned long flags;
+
+	if (snd_BUG_ON(!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(struct snd_seq_timer *tmr, unsigned int skew,
+			   unsigned int base)
+{
+	unsigned long flags;
+
+	if (snd_BUG_ON(!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(struct snd_seq_queue *q)
+{
+	struct snd_timer_instance *t;
+	struct snd_seq_timer *tmr;
+	char str[32];
+	int err;
+
+	tmr = q->timer;
+	if (snd_BUG_ON(!tmr))
+		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) {
+			struct snd_timer_id 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(struct snd_seq_queue *q)
+{
+	struct snd_seq_timer *tmr;
+	
+	tmr = q->timer;
+	if (snd_BUG_ON(!tmr))
+		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(struct snd_seq_timer * 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(struct snd_seq_timer *tmr)
+{
+	struct snd_timer *t;
+	unsigned long freq;
+
+	t = tmr->timeri->timer;
+	if (snd_BUG_ON(!t))
+		return -EINVAL;
+
+	freq = tmr->preferred_resolution;
+	if (!freq)
+		freq = DEFAULT_FREQUENCY;
+	else if (freq < MIN_FREQUENCY)
+		freq = MIN_FREQUENCY;
+	else if (freq > MAX_FREQUENCY)
+		freq = MAX_FREQUENCY;
+
+	tmr->ticks = 1;
+	if (!(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 * freq));
+			if (! tmr->ticks)
+				tmr->ticks = 1;
+		}
+	}
+	tmr->initialized = 1;
+	return 0;
+}
+
+int snd_seq_timer_start(struct snd_seq_timer * 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(struct snd_seq_timer * 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(struct snd_seq_timer *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(struct snd_seq_timer *tmr)
+{
+	return tmr->tick.cur_tick;
+}
+
+
+#ifdef CONFIG_PROC_FS
+/* exported to seq_info.c */
+void snd_seq_info_timer_read(struct snd_info_entry *entry,
+			     struct snd_info_buffer *buffer)
+{
+	int idx;
+	struct snd_seq_queue *q;
+	struct snd_seq_timer *tmr;
+	struct snd_timer_instance *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);
+ 	}
+}
+#endif /* CONFIG_PROC_FS */
+
diff --git a/linux/sound/core/seq/seq_timer.h b/linux/sound/core/seq/seq_timer.h
new file mode 100644
index 0000000..88dfb71
--- /dev/null
+++ b/linux/sound/core/seq/seq_timer.h
@@ -0,0 +1,148 @@
+/*
+ *  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>
+
+struct snd_seq_timer_tick {
+	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 */
+};
+
+struct snd_seq_timer {
+	/* ... 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 */
+	struct snd_seq_timer_tick	tick;	/* current tick */
+	int tick_updated;
+	
+	int			type;		/* timer type */
+	struct snd_timer_id	alsa_id;	/* ALSA's timer ID */
+	struct snd_timer_instance	*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;
+};
+
+
+/* create new timer (constructor) */
+struct snd_seq_timer *snd_seq_timer_new(void);
+
+/* delete timer (destructor) */
+void snd_seq_timer_delete(struct snd_seq_timer **tmr);
+
+/* */
+static inline void snd_seq_timer_update_tick(struct snd_seq_timer_tick *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 */
+struct snd_seq_queue;
+int snd_seq_timer_open(struct snd_seq_queue *q);
+int snd_seq_timer_close(struct snd_seq_queue *q);
+int snd_seq_timer_midi_open(struct snd_seq_queue *q);
+int snd_seq_timer_midi_close(struct snd_seq_queue *q);
+void snd_seq_timer_defaults(struct snd_seq_timer *tmr);
+void snd_seq_timer_reset(struct snd_seq_timer *tmr);
+int snd_seq_timer_stop(struct snd_seq_timer *tmr);
+int snd_seq_timer_start(struct snd_seq_timer *tmr);
+int snd_seq_timer_continue(struct snd_seq_timer *tmr);
+int snd_seq_timer_set_tempo(struct snd_seq_timer *tmr, int tempo);
+int snd_seq_timer_set_ppq(struct snd_seq_timer *tmr, int ppq);
+int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr, snd_seq_tick_time_t position);
+int snd_seq_timer_set_position_time(struct snd_seq_timer *tmr, snd_seq_real_time_t position);
+int snd_seq_timer_set_skew(struct snd_seq_timer *tmr, unsigned int skew, unsigned int base);
+snd_seq_real_time_t snd_seq_timer_get_cur_time(struct snd_seq_timer *tmr);
+snd_seq_tick_time_t snd_seq_timer_get_cur_tick(struct snd_seq_timer *tmr);
+
+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;
+
+#endif
diff --git a/linux/sound/core/seq/seq_virmidi.c b/linux/sound/core/seq/seq_virmidi.c
new file mode 100644
index 0000000..86e7739
--- /dev/null
+++ b/linux/sound/core/seq/seq_virmidi.c
@@ -0,0 +1,542 @@
+/*
+ *  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 <linux/init.h>
+#include <linux/wait.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(struct snd_virmidi *vmidi,
+				   struct snd_seq_event *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(struct snd_virmidi_dev *rdev,
+					 struct snd_seq_event *ev)
+{
+	struct snd_virmidi *vmidi;
+	unsigned char msg[4];
+	int len;
+
+	read_lock(&rdev->filelist_lock);
+	list_for_each_entry(vmidi, &rdev->filelist, 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.
+ */
+#if 0
+int snd_virmidi_receive(struct snd_rawmidi *rmidi, struct snd_seq_event *ev)
+{
+	struct snd_virmidi_dev *rdev;
+
+	rdev = rmidi->private_data;
+	return snd_virmidi_dev_receive_event(rdev, ev);
+}
+#endif  /*  0  */
+
+/*
+ * event handler of virmidi port
+ */
+static int snd_virmidi_event_input(struct snd_seq_event *ev, int direct,
+				   void *private_data, int atomic, int hop)
+{
+	struct snd_virmidi_dev *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(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_virmidi *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(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_virmidi *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, in_atomic(), 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, in_atomic(), 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(struct snd_rawmidi_substream *substream)
+{
+	struct snd_virmidi_dev *rdev = substream->rmidi->private_data;
+	struct snd_rawmidi_runtime *runtime = substream->runtime;
+	struct snd_virmidi *vmidi;
+	unsigned long flags;
+
+	vmidi = kzalloc(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(struct snd_rawmidi_substream *substream)
+{
+	struct snd_virmidi_dev *rdev = substream->rmidi->private_data;
+	struct snd_rawmidi_runtime *runtime = substream->runtime;
+	struct snd_virmidi *vmidi;
+
+	vmidi = kzalloc(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(struct snd_rawmidi_substream *substream)
+{
+	struct snd_virmidi *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(struct snd_rawmidi_substream *substream)
+{
+	struct snd_virmidi *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,
+				 struct snd_seq_port_subscribe *info)
+{
+	struct snd_virmidi_dev *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,
+				   struct snd_seq_port_subscribe *info)
+{
+	struct snd_virmidi_dev *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,
+			   struct snd_seq_port_subscribe *info)
+{
+	struct snd_virmidi_dev *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,
+			     struct snd_seq_port_subscribe *info)
+{
+	struct snd_virmidi_dev *rdev;
+
+	rdev = private_data;
+	rdev->flags &= ~SNDRV_VIRMIDI_USE;
+	module_put(rdev->card->module);
+	return 0;
+}
+
+
+/*
+ *  Register functions
+ */
+
+static struct snd_rawmidi_ops snd_virmidi_input_ops = {
+	.open = snd_virmidi_input_open,
+	.close = snd_virmidi_input_close,
+	.trigger = snd_virmidi_input_trigger,
+};
+
+static struct snd_rawmidi_ops 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(struct snd_virmidi_dev *rdev)
+{
+	int client;
+	struct snd_seq_port_callback pcallbacks;
+	struct snd_seq_port_info *pinfo;
+	int err;
+
+	if (rdev->client >= 0)
+		return 0;
+
+	pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL);
+	if (!pinfo) {
+		err = -ENOMEM;
+		goto __error;
+	}
+
+	client = snd_seq_create_kernel_client(rdev->card, rdev->device,
+					      "%s %d-%d", rdev->rmidi->name,
+					      rdev->card->number,
+					      rdev->device);
+	if (client < 0) {
+		err = client;
+		goto __error;
+	}
+	rdev->client = client;
+
+	/* create a port */
+	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
+		| SNDRV_SEQ_PORT_TYPE_SOFTWARE
+		| SNDRV_SEQ_PORT_TYPE_PORT;
+	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(pinfo);
+	return err;
+}
+
+
+/*
+ * release the sequencer client
+ */
+static void snd_virmidi_dev_detach_seq(struct snd_virmidi_dev *rdev)
+{
+	if (rdev->client >= 0) {
+		snd_seq_delete_kernel_client(rdev->client);
+		rdev->client = -1;
+	}
+}
+
+/*
+ * register the device
+ */
+static int snd_virmidi_dev_register(struct snd_rawmidi *rmidi)
+{
+	struct snd_virmidi_dev *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(struct snd_rawmidi *rmidi)
+{
+	struct snd_virmidi_dev *rdev = rmidi->private_data;
+
+	if (rdev->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH)
+		snd_virmidi_dev_detach_seq(rdev);
+	return 0;
+}
+
+/*
+ *
+ */
+static struct snd_rawmidi_global_ops snd_virmidi_global_ops = {
+	.dev_register = snd_virmidi_dev_register,
+	.dev_unregister = snd_virmidi_dev_unregister,
+};
+
+/*
+ * free device
+ */
+static void snd_virmidi_free(struct snd_rawmidi *rmidi)
+{
+	struct snd_virmidi_dev *rdev = rmidi->private_data;
+	kfree(rdev);
+}
+
+/*
+ * create a new device
+ *
+ */
+/* exported */
+int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmidi)
+{
+	struct snd_rawmidi *rmidi;
+	struct snd_virmidi_dev *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 = kzalloc(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);
diff --git a/linux/sound/core/sgbuf.c b/linux/sound/core/sgbuf.c
new file mode 100644
index 0000000..4e7ec2b
--- /dev/null
+++ b/linux/sound/core/sgbuf.c
@@ -0,0 +1,138 @@
+/*
+ * 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/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)	ALIGN((tbl), 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;
+
+	if (dmab->area)
+		vunmap(dmab->area);
+	dmab->area = NULL;
+
+	tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
+	tmpb.dev.dev = sgbuf->dev;
+	for (i = 0; i < sgbuf->pages; i++) {
+		if (!(sgbuf->table[i].addr & ~PAGE_MASK))
+			continue; /* continuous pages */
+		tmpb.area = sgbuf->table[i].buf;
+		tmpb.addr = sgbuf->table[i].addr & PAGE_MASK;
+		tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT;
+		snd_dma_free_pages(&tmpb);
+	}
+
+	kfree(sgbuf->table);
+	kfree(sgbuf->page_table);
+	kfree(sgbuf);
+	dmab->private_data = NULL;
+	
+	return 0;
+}
+
+#define MAX_ALLOC_PAGES		32
+
+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, chunk, maxpages;
+	struct snd_dma_buffer tmpb;
+	struct snd_sg_page *table;
+	struct page **pgtable;
+
+	dmab->area = NULL;
+	dmab->addr = 0;
+	dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL);
+	if (! sgbuf)
+		return NULL;
+	sgbuf->dev = device;
+	pages = snd_sgbuf_aligned_pages(size);
+	sgbuf->tblsize = sgbuf_align_table(pages);
+	table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL);
+	if (!table)
+		goto _failed;
+	sgbuf->table = table;
+	pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL);
+	if (!pgtable)
+		goto _failed;
+	sgbuf->page_table = pgtable;
+
+	/* allocate pages */
+	maxpages = MAX_ALLOC_PAGES;
+	while (pages > 0) {
+		chunk = pages;
+		/* don't be too eager to take a huge chunk */
+		if (chunk > maxpages)
+			chunk = maxpages;
+		chunk <<= PAGE_SHIFT;
+		if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV, device,
+						 chunk, &tmpb) < 0) {
+			if (!sgbuf->pages)
+				return NULL;
+			if (!res_size)
+				goto _failed;
+			size = sgbuf->pages * PAGE_SIZE;
+			break;
+		}
+		chunk = tmpb.bytes >> PAGE_SHIFT;
+		for (i = 0; i < chunk; i++) {
+			table->buf = tmpb.area;
+			table->addr = tmpb.addr;
+			if (!i)
+				table->addr |= chunk; /* mark head */
+			table++;
+			*pgtable++ = virt_to_page(tmpb.area);
+			tmpb.area += PAGE_SIZE;
+			tmpb.addr += PAGE_SIZE;
+		}
+		sgbuf->pages += chunk;
+		pages -= chunk;
+		if (chunk < maxpages)
+			maxpages = chunk;
+	}
+
+	sgbuf->size = size;
+	dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL);
+	if (! dmab->area)
+		goto _failed;
+	if (res_size)
+		*res_size = sgbuf->size;
+	return dmab->area;
+
+ _failed:
+	snd_free_sgbuf_pages(dmab); /* free the table */
+	return NULL;
+}
diff --git a/linux/sound/core/sound.c b/linux/sound/core/sound.c
new file mode 100644
index 0000000..66691fe
--- /dev/null
+++ b/linux/sound/core/sound.c
@@ -0,0 +1,475 @@
+/*
+ *  Advanced Linux Sound Architecture
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/device.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/mutex.h>
+
+static int major = CONFIG_SND_MAJOR;
+int snd_major;
+EXPORT_SYMBOL(snd_major);
+
+static int cards_limit = 1;
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.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.");
+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;
+EXPORT_SYMBOL(snd_ecards_limit);
+
+static struct snd_minor *snd_minors[SNDRV_OS_MINORS];
+static DEFINE_MUTEX(sound_mutex);
+
+#ifdef CONFIG_MODULES
+
+/**
+ * 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 request_module.  Returns immediately if already loaded.
+ */
+void snd_request_card(int card)
+{
+	if (snd_card_locked(card))
+		return;
+	if (card < 0 || card >= cards_limit)
+		return;
+	request_module("snd-card-%i", card);
+}
+
+EXPORT_SYMBOL(snd_request_card);
+
+static void snd_request_other(int minor)
+{
+	char *str;
+
+	switch (minor) {
+	case SNDRV_MINOR_SEQUENCER:	str = "snd-seq";	break;
+	case SNDRV_MINOR_TIMER:		str = "snd-timer";	break;
+	default:			return;
+	}
+	request_module(str);
+}
+
+#endif	/* modular kernel */
+
+/**
+ * snd_lookup_minor_data - get user data of a registered device
+ * @minor: the minor number
+ * @type: device type (SNDRV_DEVICE_TYPE_XXX)
+ *
+ * Checks that a minor device with the specified type is registered, and returns
+ * its user data pointer.
+ */
+void *snd_lookup_minor_data(unsigned int minor, int type)
+{
+	struct snd_minor *mreg;
+	void *private_data;
+
+	if (minor >= ARRAY_SIZE(snd_minors))
+		return NULL;
+	mutex_lock(&sound_mutex);
+	mreg = snd_minors[minor];
+	if (mreg && mreg->type == type)
+		private_data = mreg->private_data;
+	else
+		private_data = NULL;
+	mutex_unlock(&sound_mutex);
+	return private_data;
+}
+
+EXPORT_SYMBOL(snd_lookup_minor_data);
+
+#ifdef CONFIG_MODULES
+static struct snd_minor *autoload_device(unsigned int minor)
+{
+	int dev;
+	mutex_unlock(&sound_mutex); /* release lock temporarily */
+	dev = SNDRV_MINOR_DEVICE(minor);
+	if (dev == SNDRV_MINOR_CONTROL) {
+		/* /dev/aloadC? */
+		int card = SNDRV_MINOR_CARD(minor);
+		if (snd_cards[card] == NULL)
+			snd_request_card(card);
+	} else if (dev == SNDRV_MINOR_GLOBAL) {
+		/* /dev/aloadSEQ */
+		snd_request_other(minor);
+	}
+	mutex_lock(&sound_mutex); /* reacuire lock */
+	return snd_minors[minor];
+}
+#else /* !CONFIG_MODULES */
+#define autoload_device(minor)	NULL
+#endif /* CONFIG_MODULES */
+
+static int snd_open(struct inode *inode, struct file *file)
+{
+	unsigned int minor = iminor(inode);
+	struct snd_minor *mptr = NULL;
+	const struct file_operations *old_fops;
+	int err = 0;
+
+	if (minor >= ARRAY_SIZE(snd_minors))
+		return -ENODEV;
+	mutex_lock(&sound_mutex);
+	mptr = snd_minors[minor];
+	if (mptr == NULL) {
+		mptr = autoload_device(minor);
+		if (!mptr) {
+			mutex_unlock(&sound_mutex);
+			return -ENODEV;
+		}
+	}
+	old_fops = file->f_op;
+	file->f_op = fops_get(mptr->f_ops);
+	if (file->f_op == NULL) {
+		file->f_op = old_fops;
+		err = -ENODEV;
+	}
+	mutex_unlock(&sound_mutex);
+	if (err < 0)
+		return err;
+
+	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 const struct file_operations snd_fops =
+{
+	.owner =	THIS_MODULE,
+	.open =		snd_open,
+	.llseek =	noop_llseek,
+};
+
+#ifdef CONFIG_SND_DYNAMIC_MINORS
+static int snd_find_free_minor(void)
+{
+	int minor;
+
+	for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) {
+		/* skip minors still used statically for autoloading devices */
+		if (SNDRV_MINOR_DEVICE(minor) == SNDRV_MINOR_CONTROL ||
+		    minor == SNDRV_MINOR_SEQUENCER)
+			continue;
+		if (!snd_minors[minor])
+			return minor;
+	}
+	return -EBUSY;
+}
+#else
+static int snd_kernel_minor(int type, struct snd_card *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:
+		if (snd_BUG_ON(!card))
+			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:
+		if (snd_BUG_ON(!card))
+			return -EINVAL;
+		minor = SNDRV_MINOR(card->number, type + dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (snd_BUG_ON(minor < 0 || minor >= SNDRV_OS_MINORS))
+		return -EINVAL;
+	return minor;
+}
+#endif
+
+/**
+ * snd_register_device_for_dev - Register the ALSA device file for the card
+ * @type: the device type, SNDRV_DEVICE_TYPE_XXX
+ * @card: the card instance
+ * @dev: the device index
+ * @f_ops: the file operations
+ * @private_data: user pointer for f_ops->open()
+ * @name: the device file name
+ * @device: the &struct device to link this new device to
+ *
+ * Registers an ALSA device file for the given card.
+ * The operators have to be set in reg parameter.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
+				const struct file_operations *f_ops,
+				void *private_data,
+				const char *name, struct device *device)
+{
+	int minor;
+	struct snd_minor *preg;
+
+	if (snd_BUG_ON(!name))
+		return -EINVAL;
+	preg = kmalloc(sizeof *preg, GFP_KERNEL);
+	if (preg == NULL)
+		return -ENOMEM;
+	preg->type = type;
+	preg->card = card ? card->number : -1;
+	preg->device = dev;
+	preg->f_ops = f_ops;
+	preg->private_data = private_data;
+	mutex_lock(&sound_mutex);
+#ifdef CONFIG_SND_DYNAMIC_MINORS
+	minor = snd_find_free_minor();
+#else
+	minor = snd_kernel_minor(type, card, dev);
+	if (minor >= 0 && snd_minors[minor])
+		minor = -EBUSY;
+#endif
+	if (minor < 0) {
+		mutex_unlock(&sound_mutex);
+		kfree(preg);
+		return minor;
+	}
+	snd_minors[minor] = preg;
+	preg->dev = device_create(sound_class, device, MKDEV(major, minor),
+				  private_data, "%s", name);
+	if (IS_ERR(preg->dev)) {
+		snd_minors[minor] = NULL;
+		mutex_unlock(&sound_mutex);
+		minor = PTR_ERR(preg->dev);
+		kfree(preg);
+		return minor;
+	}
+
+	mutex_unlock(&sound_mutex);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_register_device_for_dev);
+
+/* find the matching minor record
+ * return the index of snd_minor, or -1 if not found
+ */
+static int find_snd_minor(int type, struct snd_card *card, int dev)
+{
+	int cardnum, minor;
+	struct snd_minor *mptr;
+
+	cardnum = card ? card->number : -1;
+	for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor)
+		if ((mptr = snd_minors[minor]) != NULL &&
+		    mptr->type == type &&
+		    mptr->card == cardnum &&
+		    mptr->device == dev)
+			return minor;
+	return -1;
+}
+
+/**
+ * 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, struct snd_card *card, int dev)
+{
+	int minor;
+
+	mutex_lock(&sound_mutex);
+	minor = find_snd_minor(type, card, dev);
+	if (minor < 0) {
+		mutex_unlock(&sound_mutex);
+		return -EINVAL;
+	}
+
+	device_destroy(sound_class, MKDEV(major, minor));
+
+	kfree(snd_minors[minor]);
+	snd_minors[minor] = NULL;
+	mutex_unlock(&sound_mutex);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_unregister_device);
+
+int snd_add_device_sysfs_file(int type, struct snd_card *card, int dev,
+			      struct device_attribute *attr)
+{
+	int minor, ret = -EINVAL;
+	struct device *d;
+
+	mutex_lock(&sound_mutex);
+	minor = find_snd_minor(type, card, dev);
+	if (minor >= 0 && (d = snd_minors[minor]->dev) != NULL)
+		ret = device_create_file(d, attr);
+	mutex_unlock(&sound_mutex);
+	return ret;
+
+}
+
+EXPORT_SYMBOL(snd_add_device_sysfs_file);
+
+#ifdef CONFIG_PROC_FS
+/*
+ *  INFO PART
+ */
+
+static struct snd_info_entry *snd_minor_info_entry;
+
+static const char *snd_device_type_name(int type)
+{
+	switch (type) {
+	case SNDRV_DEVICE_TYPE_CONTROL:
+		return "control";
+	case SNDRV_DEVICE_TYPE_HWDEP:
+		return "hardware dependent";
+	case SNDRV_DEVICE_TYPE_RAWMIDI:
+		return "raw midi";
+	case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:
+		return "digital audio playback";
+	case SNDRV_DEVICE_TYPE_PCM_CAPTURE:
+		return "digital audio capture";
+	case SNDRV_DEVICE_TYPE_SEQUENCER:
+		return "sequencer";
+	case SNDRV_DEVICE_TYPE_TIMER:
+		return "timer";
+	default:
+		return "?";
+	}
+}
+
+static void snd_minor_info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	int minor;
+	struct snd_minor *mptr;
+
+	mutex_lock(&sound_mutex);
+	for (minor = 0; minor < SNDRV_OS_MINORS; ++minor) {
+		if (!(mptr = snd_minors[minor]))
+			continue;
+		if (mptr->card >= 0) {
+			if (mptr->device >= 0)
+				snd_iprintf(buffer, "%3i: [%2i-%2i]: %s\n",
+					    minor, mptr->card, mptr->device,
+					    snd_device_type_name(mptr->type));
+			else
+				snd_iprintf(buffer, "%3i: [%2i]   : %s\n",
+					    minor, mptr->card,
+					    snd_device_type_name(mptr->type));
+		} else
+			snd_iprintf(buffer, "%3i:        : %s\n", minor,
+				    snd_device_type_name(mptr->type));
+	}
+	mutex_unlock(&sound_mutex);
+}
+
+int __init snd_minor_info_init(void)
+{
+	struct snd_info_entry *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, "devices", NULL);
+	if (entry) {
+		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)
+{
+	snd_info_free_entry(snd_minor_info_entry);
+	return 0;
+}
+#endif /* CONFIG_PROC_FS */
+
+/*
+ *  INIT PART
+ */
+
+static int __init alsa_sound_init(void)
+{
+	snd_major = major;
+	snd_ecards_limit = cards_limit;
+	if (register_chrdev(major, "alsa", &snd_fops)) {
+		snd_printk(KERN_ERR "unable to register native major device number %d\n", major);
+		return -EIO;
+	}
+	if (snd_info_init() < 0) {
+		unregister_chrdev(major, "alsa");
+		return -ENOMEM;
+	}
+	snd_info_minor_register();
+#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)
+{
+	snd_info_minor_unregister();
+	snd_info_done();
+	unregister_chrdev(major, "alsa");
+}
+
+subsys_initcall(alsa_sound_init);
+module_exit(alsa_sound_exit);
diff --git a/linux/sound/core/sound_oss.c b/linux/sound/core/sound_oss.c
new file mode 100644
index 0000000..0c164e5
--- /dev/null
+++ b/linux/sound/core/sound_oss.c
@@ -0,0 +1,278 @@
+/*
+ *  Advanced Linux Sound Architecture
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#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>
+#include <linux/mutex.h>
+
+#define SNDRV_OSS_MINORS 128
+
+static struct snd_minor *snd_oss_minors[SNDRV_OSS_MINORS];
+static DEFINE_MUTEX(sound_oss_mutex);
+
+void *snd_lookup_oss_minor_data(unsigned int minor, int type)
+{
+	struct snd_minor *mreg;
+	void *private_data;
+
+	if (minor >= ARRAY_SIZE(snd_oss_minors))
+		return NULL;
+	mutex_lock(&sound_oss_mutex);
+	mreg = snd_oss_minors[minor];
+	if (mreg && mreg->type == type)
+		private_data = mreg->private_data;
+	else
+		private_data = NULL;
+	mutex_unlock(&sound_oss_mutex);
+	return private_data;
+}
+
+EXPORT_SYMBOL(snd_lookup_oss_minor_data);
+
+static int snd_oss_kernel_minor(int type, struct snd_card *card, int dev)
+{
+	int minor;
+
+	switch (type) {
+	case SNDRV_OSS_DEVICE_TYPE_MIXER:
+		if (snd_BUG_ON(!card || dev < 0 || 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:
+		if (snd_BUG_ON(!card || dev < 0 || 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:
+		if (snd_BUG_ON(!card || dev < 0 || 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;
+	}
+	if (minor < 0 || minor >= SNDRV_OSS_MINORS)
+		return -EINVAL;
+	return minor;
+}
+
+int snd_register_oss_device(int type, struct snd_card *card, int dev,
+			    const struct file_operations *f_ops, void *private_data,
+			    const char *name)
+{
+	int minor = snd_oss_kernel_minor(type, card, dev);
+	int minor_unit;
+	struct snd_minor *preg;
+	int cidx = SNDRV_MINOR_OSS_CARD(minor);
+	int track2 = -1;
+	int register1 = -1, register2 = -1;
+	struct device *carddev = snd_card_get_device_link(card);
+
+	if (card && card->number >= 8)
+		return 0; /* ignore silently */
+	if (minor < 0)
+		return minor;
+	preg = kmalloc(sizeof(struct snd_minor), GFP_KERNEL);
+	if (preg == NULL)
+		return -ENOMEM;
+	preg->type = type;
+	preg->card = card ? card->number : -1;
+	preg->device = dev;
+	preg->f_ops = f_ops;
+	preg->private_data = private_data;
+	mutex_lock(&sound_oss_mutex);
+	snd_oss_minors[minor] = preg;
+	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_device(f_ops, minor, carddev);
+	if (register1 != minor)
+		goto __end;
+	if (track2 >= 0) {
+		register2 = register_sound_special_device(f_ops, track2,
+							  carddev);
+		if (register2 != track2)
+			goto __end;
+		snd_oss_minors[track2] = preg;
+	}
+	mutex_unlock(&sound_oss_mutex);
+	return 0;
+
+      __end:
+      	if (register2 >= 0)
+      		unregister_sound_special(register2);
+      	if (register1 >= 0)
+      		unregister_sound_special(register1);
+	snd_oss_minors[minor] = NULL;
+	mutex_unlock(&sound_oss_mutex);
+	kfree(preg);
+      	return -EBUSY;
+}
+
+EXPORT_SYMBOL(snd_register_oss_device);
+
+int snd_unregister_oss_device(int type, struct snd_card *card, int dev)
+{
+	int minor = snd_oss_kernel_minor(type, card, dev);
+	int cidx = SNDRV_MINOR_OSS_CARD(minor);
+	int track2 = -1;
+	struct snd_minor *mptr;
+
+	if (card && card->number >= 8)
+		return 0;
+	if (minor < 0)
+		return minor;
+	mutex_lock(&sound_oss_mutex);
+	mptr = snd_oss_minors[minor];
+	if (mptr == NULL) {
+		mutex_unlock(&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);
+		snd_oss_minors[track2] = NULL;
+	}
+	snd_oss_minors[minor] = NULL;
+	mutex_unlock(&sound_oss_mutex);
+	kfree(mptr);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_unregister_oss_device);
+
+/*
+ *  INFO PART
+ */
+
+#ifdef CONFIG_PROC_FS
+
+static struct snd_info_entry *snd_minor_info_oss_entry;
+
+static const char *snd_oss_device_type_name(int type)
+{
+	switch (type) {
+	case SNDRV_OSS_DEVICE_TYPE_MIXER:
+		return "mixer";
+	case SNDRV_OSS_DEVICE_TYPE_SEQUENCER:
+	case SNDRV_OSS_DEVICE_TYPE_MUSIC:
+		return "sequencer";
+	case SNDRV_OSS_DEVICE_TYPE_PCM:
+		return "digital audio";
+	case SNDRV_OSS_DEVICE_TYPE_MIDI:
+		return "raw midi";
+	case SNDRV_OSS_DEVICE_TYPE_DMFM:
+		return "hardware dependent";
+	default:
+		return "?";
+	}
+}
+
+static void snd_minor_info_oss_read(struct snd_info_entry *entry,
+				    struct snd_info_buffer *buffer)
+{
+	int minor;
+	struct snd_minor *mptr;
+
+	mutex_lock(&sound_oss_mutex);
+	for (minor = 0; minor < SNDRV_OSS_MINORS; ++minor) {
+		if (!(mptr = snd_oss_minors[minor]))
+			continue;
+		if (mptr->card >= 0)
+			snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", minor,
+				    mptr->card, mptr->device,
+				    snd_oss_device_type_name(mptr->type));
+		else
+			snd_iprintf(buffer, "%3i:       : %s\n", minor,
+				    snd_oss_device_type_name(mptr->type));
+	}
+	mutex_unlock(&sound_oss_mutex);
+}
+
+
+int __init snd_minor_info_oss_init(void)
+{
+	struct snd_info_entry *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, "devices", snd_oss_root);
+	if (entry) {
+		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;
+	return 0;
+}
+
+int __exit snd_minor_info_oss_done(void)
+{
+	snd_info_free_entry(snd_minor_info_oss_entry);
+	return 0;
+}
+#endif /* CONFIG_PROC_FS */
+
+#endif /* CONFIG_SND_OSSEMUL */
diff --git a/linux/sound/core/timer.c b/linux/sound/core/timer.c
new file mode 100644
index 0000000..13afb60
--- /dev/null
+++ b/linux/sound/core/timer.c
@@ -0,0 +1,1992 @@
+/*
+ *  Timers abstract layer
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/mutex.h>
+#include <linux/moduleparam.h>
+#include <linux/string.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>
+
+#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;
+static int timer_tstamp_monotonic = 1;
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.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.");
+module_param(timer_tstamp_monotonic, int, 0444);
+MODULE_PARM_DESC(timer_tstamp_monotonic, "Use posix monotonic clock source for timestamps (default).");
+
+struct snd_timer_user {
+	struct snd_timer_instance *timeri;
+	int tread;		/* enhanced read with timestamps and events */
+	unsigned long ticks;
+	unsigned long overrun;
+	int qhead;
+	int qtail;
+	int qused;
+	int queue_size;
+	struct snd_timer_read *queue;
+	struct snd_timer_tread *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;
+	struct mutex tread_sem;
+};
+
+/* 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 DEFINE_MUTEX(register_mutex);
+
+static int snd_timer_free(struct snd_timer *timer);
+static int snd_timer_dev_free(struct snd_device *device);
+static int snd_timer_dev_register(struct snd_device *device);
+static int snd_timer_dev_disconnect(struct snd_device *device);
+
+static void snd_timer_reschedule(struct snd_timer * timer, unsigned long ticks_left);
+
+/*
+ * create a timer instance with the given owner string.
+ * when timer is not NULL, increments the module counter
+ */
+static struct snd_timer_instance *snd_timer_instance_new(char *owner,
+							 struct snd_timer *timer)
+{
+	struct snd_timer_instance *timeri;
+	timeri = kzalloc(sizeof(*timeri), GFP_KERNEL);
+	if (timeri == NULL)
+		return NULL;
+	timeri->owner = kstrdup(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 && !try_module_get(timer->module)) {
+		kfree(timeri->owner);
+		kfree(timeri);
+		return NULL;
+	}
+
+	return timeri;
+}
+
+/*
+ * find a timer instance from the given timer id
+ */
+static struct snd_timer *snd_timer_find(struct snd_timer_id *tid)
+{
+	struct snd_timer *timer = NULL;
+
+	list_for_each_entry(timer, &snd_timer_list, 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_MODULES
+
+static void snd_timer_request(struct snd_timer_id *tid)
+{
+	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(struct snd_timer_instance *slave)
+{
+	struct snd_timer *timer;
+	struct snd_timer_instance *master;
+
+	/* FIXME: it's really dumb to look up all entries.. */
+	list_for_each_entry(timer, &snd_timer_list, device_list) {
+		list_for_each_entry(master, &timer->open_list_head, 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(struct snd_timer_instance *master)
+{
+	struct snd_timer_instance *slave, *tmp;
+
+	/* check all pending slaves */
+	list_for_each_entry_safe(slave, tmp, &snd_timer_slave_list, open_list) {
+		if (slave->slave_class == master->slave_class &&
+		    slave->slave_id == master->slave_id) {
+			list_move_tail(&slave->open_list, &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(struct snd_timer_instance **ti,
+		   char *owner, struct snd_timer_id *tid,
+		   unsigned int slave_id)
+{
+	struct snd_timer *timer;
+	struct snd_timer_instance *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;
+		}
+		mutex_lock(&register_mutex);
+		timeri = snd_timer_instance_new(owner, NULL);
+		if (!timeri) {
+			mutex_unlock(&register_mutex);
+			return -ENOMEM;
+		}
+		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);
+		mutex_unlock(&register_mutex);
+		*ti = timeri;
+		return 0;
+	}
+
+	/* open a master instance */
+	mutex_lock(&register_mutex);
+	timer = snd_timer_find(tid);
+#ifdef CONFIG_MODULES
+	if (!timer) {
+		mutex_unlock(&register_mutex);
+		snd_timer_request(tid);
+		mutex_lock(&register_mutex);
+		timer = snd_timer_find(tid);
+	}
+#endif
+	if (!timer) {
+		mutex_unlock(&register_mutex);
+		return -ENODEV;
+	}
+	if (!list_empty(&timer->open_list_head)) {
+		timeri = list_entry(timer->open_list_head.next,
+				    struct snd_timer_instance, open_list);
+		if (timeri->flags & SNDRV_TIMER_IFLG_EXCLUSIVE) {
+			mutex_unlock(&register_mutex);
+			return -EBUSY;
+		}
+	}
+	timeri = snd_timer_instance_new(owner, timer);
+	if (!timeri) {
+		mutex_unlock(&register_mutex);
+		return -ENOMEM;
+	}
+	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);
+	mutex_unlock(&register_mutex);
+	*ti = timeri;
+	return 0;
+}
+
+static int _snd_timer_stop(struct snd_timer_instance *timeri,
+			   int keep_flag, int event);
+
+/*
+ * close a timer instance
+ */
+int snd_timer_close(struct snd_timer_instance *timeri)
+{
+	struct snd_timer *timer = NULL;
+	struct snd_timer_instance *slave, *tmp;
+
+	if (snd_BUG_ON(!timeri))
+		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);
+		mutex_lock(&register_mutex);
+		list_del(&timeri->open_list);
+		mutex_unlock(&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);
+		mutex_lock(&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_entry_safe(slave, tmp, &timeri->slave_list_head,
+					 open_list) {
+			spin_lock_irq(&slave_active_lock);
+			_snd_timer_stop(slave, 1, SNDRV_TIMER_EVENT_RESOLUTION);
+			list_move_tail(&slave->open_list, &snd_timer_slave_list);
+			slave->master = NULL;
+			slave->timer = NULL;
+			spin_unlock_irq(&slave_active_lock);
+		}
+		mutex_unlock(&register_mutex);
+	}
+	if (timeri->private_free)
+		timeri->private_free(timeri);
+	kfree(timeri->owner);
+	kfree(timeri);
+	if (timer)
+		module_put(timer->module);
+	return 0;
+}
+
+unsigned long snd_timer_resolution(struct snd_timer_instance *timeri)
+{
+	struct snd_timer * 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(struct snd_timer_instance *ti, int event)
+{
+	struct snd_timer *timer;
+	unsigned long flags;
+	unsigned long resolution = 0;
+	struct snd_timer_instance *ts;
+	struct timespec tstamp;
+
+	if (timer_tstamp_monotonic)
+		do_posix_clock_monotonic_gettime(&tstamp);
+	else
+		getnstimeofday(&tstamp);
+	if (snd_BUG_ON(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, event, &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_entry(ts, &ti->slave_active_head, active_list)
+		if (ts->ccallback)
+			ts->ccallback(ti, event + 100, &tstamp, resolution);
+	spin_unlock_irqrestore(&timer->lock, flags);
+}
+
+static int snd_timer_start1(struct snd_timer *timer, struct snd_timer_instance *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(struct snd_timer_instance *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(struct snd_timer_instance *timeri, unsigned int ticks)
+{
+	struct snd_timer *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(struct snd_timer_instance * timeri,
+			   int keep_flag, int event)
+{
+	struct snd_timer *timer;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!timeri))
+		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(struct snd_timer_instance *timeri)
+{
+	struct snd_timer *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(struct snd_timer_instance *timeri)
+{
+	struct snd_timer *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(struct snd_timer_instance * 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(struct snd_timer * timer, unsigned long ticks_left)
+{
+	struct snd_timer_instance *ti;
+	unsigned long ticks = ~0UL;
+
+	list_for_each_entry(ti, &timer->active_list_head, 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)
+{
+	struct snd_timer *timer = (struct snd_timer *) arg;
+	struct snd_timer_instance *ti;
+	struct list_head *p;
+	unsigned long resolution, ticks;
+	unsigned long flags;
+
+	spin_lock_irqsave(&timer->lock, flags);
+	/* now process all callbacks */
+	while (!list_empty(&timer->sack_list_head)) {
+		p = timer->sack_list_head.next;		/* get first item */
+		ti = list_entry(p, struct snd_timer_instance, 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_irqrestore(&timer->lock, flags);
+}
+
+/*
+ * timer interrupt
+ *
+ * ticks_left is usually equal to timer->sticks.
+ *
+ */
+void snd_timer_interrupt(struct snd_timer * timer, unsigned long ticks_left)
+{
+	struct snd_timer_instance *ti, *ts, *tmp;
+	unsigned long resolution, ticks;
+	struct list_head *p, *ack_list_head;
+	unsigned long flags;
+	int use_tasklet = 0;
+
+	if (timer == NULL)
+		return;
+
+	spin_lock_irqsave(&timer->lock, flags);
+
+	/* 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_entry because the active_list of a
+	 * processed instance is relinked to done_list_head before the callback
+	 * is called.
+	 */
+	list_for_each_entry_safe(ti, tmp, &timer->active_list_head,
+				 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(&ti->active_list);
+		}
+		if ((timer->hw.flags & SNDRV_TIMER_HW_TASKLET) ||
+		    (ti->flags & SNDRV_TIMER_IFLG_FAST))
+			ack_list_head = &timer->ack_list_head;
+		else
+			ack_list_head = &timer->sack_list_head;
+		if (list_empty(&ti->ack_list))
+			list_add_tail(&ti->ack_list, ack_list_head);
+		list_for_each_entry(ts, &ti->slave_active_head, active_list) {
+			ts->pticks = ti->pticks;
+			ts->resolution = resolution;
+			if (list_empty(&ts->ack_list))
+				list_add_tail(&ts->ack_list, ack_list_head);
+		}
+	}
+	if (timer->flags & SNDRV_TIMER_FLG_RESCHED)
+		snd_timer_reschedule(timer, timer->sticks);
+	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 = list_entry(p, struct snd_timer_instance, 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_irqrestore(&timer->lock, flags);
+
+	if (use_tasklet)
+		tasklet_schedule(&timer->task_queue);
+}
+
+/*
+
+ */
+
+int snd_timer_new(struct snd_card *card, char *id, struct snd_timer_id *tid,
+		  struct snd_timer **rtimer)
+{
+	struct snd_timer *timer;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_timer_dev_free,
+		.dev_register = snd_timer_dev_register,
+		.dev_disconnect = snd_timer_dev_disconnect,
+	};
+
+	if (snd_BUG_ON(!tid))
+		return -EINVAL;
+	if (rtimer)
+		*rtimer = NULL;
+	timer = kzalloc(sizeof(*timer), GFP_KERNEL);
+	if (timer == NULL) {
+		snd_printk(KERN_ERR "timer: cannot allocate\n");
+		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) {
+		timer->module = card->module;
+		err = snd_device_new(card, SNDRV_DEV_TIMER, timer, &ops);
+		if (err < 0) {
+			snd_timer_free(timer);
+			return err;
+		}
+	}
+	if (rtimer)
+		*rtimer = timer;
+	return 0;
+}
+
+static int snd_timer_free(struct snd_timer *timer)
+{
+	if (!timer)
+		return 0;
+
+	mutex_lock(&register_mutex);
+	if (! list_empty(&timer->open_list_head)) {
+		struct list_head *p, *n;
+		struct snd_timer_instance *ti;
+		snd_printk(KERN_WARNING "timer %p is busy?\n", timer);
+		list_for_each_safe(p, n, &timer->open_list_head) {
+			list_del_init(p);
+			ti = list_entry(p, struct snd_timer_instance, open_list);
+			ti->timer = NULL;
+		}
+	}
+	list_del(&timer->device_list);
+	mutex_unlock(&register_mutex);
+
+	if (timer->private_free)
+		timer->private_free(timer);
+	kfree(timer);
+	return 0;
+}
+
+static int snd_timer_dev_free(struct snd_device *device)
+{
+	struct snd_timer *timer = device->device_data;
+	return snd_timer_free(timer);
+}
+
+static int snd_timer_dev_register(struct snd_device *dev)
+{
+	struct snd_timer *timer = dev->device_data;
+	struct snd_timer *timer1;
+
+	if (snd_BUG_ON(!timer || !timer->hw.start || !timer->hw.stop))
+		return -ENXIO;
+	if (!(timer->hw.flags & SNDRV_TIMER_HW_SLAVE) &&
+	    !timer->hw.resolution && timer->hw.c_resolution == NULL)
+	    	return -EINVAL;
+
+	mutex_lock(&register_mutex);
+	list_for_each_entry(timer1, &snd_timer_list, 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.. */
+		mutex_unlock(&register_mutex);
+		return -EBUSY;
+	}
+	list_add_tail(&timer->device_list, &timer1->device_list);
+	mutex_unlock(&register_mutex);
+	return 0;
+}
+
+static int snd_timer_dev_disconnect(struct snd_device *device)
+{
+	struct snd_timer *timer = device->device_data;
+	mutex_lock(&register_mutex);
+	list_del_init(&timer->device_list);
+	mutex_unlock(&register_mutex);
+	return 0;
+}
+
+void snd_timer_notify(struct snd_timer *timer, int event, struct timespec *tstamp)
+{
+	unsigned long flags;
+	unsigned long resolution = 0;
+	struct snd_timer_instance *ti, *ts;
+
+	if (! (timer->hw.flags & SNDRV_TIMER_HW_SLAVE))
+		return;
+	if (snd_BUG_ON(event < SNDRV_TIMER_EVENT_MSTART ||
+		       event > SNDRV_TIMER_EVENT_MRESUME))
+		return;
+	spin_lock_irqsave(&timer->lock, flags);
+	if (event == SNDRV_TIMER_EVENT_MSTART ||
+	    event == SNDRV_TIMER_EVENT_MCONTINUE ||
+	    event == SNDRV_TIMER_EVENT_MRESUME) {
+		if (timer->hw.c_resolution)
+			resolution = timer->hw.c_resolution(timer);
+		else
+			resolution = timer->hw.resolution;
+	}
+	list_for_each_entry(ti, &timer->active_list_head, active_list) {
+		if (ti->ccallback)
+			ti->ccallback(ti, event, tstamp, resolution);
+		list_for_each_entry(ts, &ti->slave_active_head, 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, struct snd_timer **rtimer)
+{
+	struct snd_timer_id 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(struct snd_timer *timer)
+{
+	return snd_timer_free(timer);
+}
+
+int snd_timer_global_register(struct snd_timer *timer)
+{
+	struct snd_device dev;
+
+	memset(&dev, 0, sizeof(dev));
+	dev.device_data = timer;
+	return snd_timer_dev_register(&dev);
+}
+
+/*
+ *  System timer
+ */
+
+struct snd_timer_system_private {
+	struct timer_list tlist;
+	unsigned long last_expires;
+	unsigned long last_jiffies;
+	unsigned long correction;
+};
+
+static void snd_timer_s_function(unsigned long data)
+{
+	struct snd_timer *timer = (struct snd_timer *)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(struct snd_timer * 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 = 0;
+	}
+	priv->last_expires = priv->tlist.expires = njiff;
+	add_timer(&priv->tlist);
+	return 0;
+}
+
+static int snd_timer_s_stop(struct snd_timer * 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;
+	priv->correction = 0;
+	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(struct snd_timer *timer)
+{
+	kfree(timer->private_data);
+}
+
+static int snd_timer_register_system(void)
+{
+	struct snd_timer *timer;
+	struct snd_timer_system_private *priv;
+	int err;
+
+	err = snd_timer_global_new("system", SNDRV_TIMER_GLOBAL_SYSTEM, &timer);
+	if (err < 0)
+		return err;
+	strcpy(timer->name, "system timer");
+	timer->hw = snd_timer_system;
+	priv = kzalloc(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);
+}
+
+#ifdef CONFIG_PROC_FS
+/*
+ *  Info interface
+ */
+
+static void snd_timer_proc_read(struct snd_info_entry *entry,
+				struct snd_info_buffer *buffer)
+{
+	struct snd_timer *timer;
+	struct snd_timer_instance *ti;
+
+	mutex_lock(&register_mutex);
+	list_for_each_entry(timer, &snd_timer_list, 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");
+		list_for_each_entry(ti, &timer->open_list_head, open_list)
+			snd_iprintf(buffer, "  Client %s : %s\n",
+				    ti->owner ? ti->owner : "unknown",
+				    ti->flags & (SNDRV_TIMER_IFLG_START |
+						 SNDRV_TIMER_IFLG_RUNNING)
+				    ? "running" : "stopped");
+	}
+	mutex_unlock(&register_mutex);
+}
+
+static struct snd_info_entry *snd_timer_proc_entry;
+
+static void __init snd_timer_proc_init(void)
+{
+	struct snd_info_entry *entry;
+
+	entry = snd_info_create_module_entry(THIS_MODULE, "timers", NULL);
+	if (entry != NULL) {
+		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;
+}
+
+static void __exit snd_timer_proc_done(void)
+{
+	snd_info_free_entry(snd_timer_proc_entry);
+}
+#else /* !CONFIG_PROC_FS */
+#define snd_timer_proc_init()
+#define snd_timer_proc_done()
+#endif
+
+/*
+ *  USER SPACE interface
+ */
+
+static void snd_timer_user_interrupt(struct snd_timer_instance *timeri,
+				     unsigned long resolution,
+				     unsigned long ticks)
+{
+	struct snd_timer_user *tu = timeri->callback_data;
+	struct snd_timer_read *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(struct snd_timer_user *tu,
+					    struct snd_timer_tread *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(struct snd_timer_instance *timeri,
+				     int event,
+				     struct timespec *tstamp,
+				     unsigned long resolution)
+{
+	struct snd_timer_user *tu = timeri->callback_data;
+	struct snd_timer_tread r1;
+	unsigned long flags;
+
+	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_irqsave(&tu->qlock, flags);
+	snd_timer_user_append_to_tqueue(tu, &r1);
+	spin_unlock_irqrestore(&tu->qlock, flags);
+	kill_fasync(&tu->fasync, SIGIO, POLL_IN);
+	wake_up(&tu->qchange_sleep);
+}
+
+static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri,
+				      unsigned long resolution,
+				      unsigned long ticks)
+{
+	struct snd_timer_user *tu = timeri->callback_data;
+	struct snd_timer_tread *r, r1;
+	struct timespec tstamp;
+	int prev, append = 0;
+
+	memset(&tstamp, 0, sizeof(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) {
+		if (timer_tstamp_monotonic)
+			do_posix_clock_monotonic_gettime(&tstamp);
+		else
+			getnstimeofday(&tstamp);
+	}
+	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)
+{
+	struct snd_timer_user *tu;
+	int err;
+
+	err = nonseekable_open(inode, file);
+	if (err < 0)
+		return err;
+
+	tu = kzalloc(sizeof(*tu), GFP_KERNEL);
+	if (tu == NULL)
+		return -ENOMEM;
+	spin_lock_init(&tu->qlock);
+	init_waitqueue_head(&tu->qchange_sleep);
+	mutex_init(&tu->tread_sem);
+	tu->ticks = 1;
+	tu->queue_size = 128;
+	tu->queue = kmalloc(tu->queue_size * sizeof(struct snd_timer_read),
+			    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)
+{
+	struct snd_timer_user *tu;
+
+	if (file->private_data) {
+		tu = file->private_data;
+		file->private_data = NULL;
+		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(struct snd_timer_id *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(struct snd_timer_id *id, struct snd_timer *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(struct snd_timer_id __user *_tid)
+{
+	struct snd_timer_id id;
+	struct snd_timer *timer;
+	struct list_head *p;
+
+	if (copy_from_user(&id, _tid, sizeof(id)))
+		return -EFAULT;
+	mutex_lock(&register_mutex);
+	if (id.dev_class < 0) {		/* first item */
+		if (list_empty(&snd_timer_list))
+			snd_timer_user_zero_id(&id);
+		else {
+			timer = list_entry(snd_timer_list.next,
+					   struct snd_timer, 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 = list_entry(p, struct snd_timer, 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 {
+						if (id.subdevice < 0) {
+							id.subdevice = 0;
+						} else {
+							id.subdevice++;
+						}
+					}
+				}
+			}
+			list_for_each(p, &snd_timer_list) {
+				timer = list_entry(p, struct snd_timer, 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);
+		}
+	}
+	mutex_unlock(&register_mutex);
+	if (copy_to_user(_tid, &id, sizeof(*_tid)))
+		return -EFAULT;
+	return 0;
+}
+
+static int snd_timer_user_ginfo(struct file *file,
+				struct snd_timer_ginfo __user *_ginfo)
+{
+	struct snd_timer_ginfo *ginfo;
+	struct snd_timer_id tid;
+	struct snd_timer *t;
+	struct list_head *p;
+	int err = 0;
+
+	ginfo = memdup_user(_ginfo, sizeof(*ginfo));
+	if (IS_ERR(ginfo))
+		return PTR_ERR(ginfo);
+
+	tid = ginfo->tid;
+	memset(ginfo, 0, sizeof(*ginfo));
+	ginfo->tid = tid;
+	mutex_lock(&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;
+	}
+	mutex_unlock(&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,
+				  struct snd_timer_gparams __user *_gparams)
+{
+	struct snd_timer_gparams gparams;
+	struct snd_timer *t;
+	int err;
+
+	if (copy_from_user(&gparams, _gparams, sizeof(gparams)))
+		return -EFAULT;
+	mutex_lock(&register_mutex);
+	t = snd_timer_find(&gparams.tid);
+	if (!t) {
+		err = -ENODEV;
+		goto _error;
+	}
+	if (!list_empty(&t->open_list_head)) {
+		err = -EBUSY;
+		goto _error;
+	}
+	if (!t->hw.set_period) {
+		err = -ENOSYS;
+		goto _error;
+	}
+	err = t->hw.set_period(t, gparams.period_num, gparams.period_den);
+_error:
+	mutex_unlock(&register_mutex);
+	return err;
+}
+
+static int snd_timer_user_gstatus(struct file *file,
+				  struct snd_timer_gstatus __user *_gstatus)
+{
+	struct snd_timer_gstatus gstatus;
+	struct snd_timer_id tid;
+	struct snd_timer *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;
+	mutex_lock(&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;
+	}
+	mutex_unlock(&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,
+				  struct snd_timer_select __user *_tselect)
+{
+	struct snd_timer_user *tu;
+	struct snd_timer_select tselect;
+	char str[32];
+	int err = 0;
+
+	tu = file->private_data;
+	mutex_lock(&tu->tread_sem);
+	if (tu->timeri) {
+		snd_timer_close(tu->timeri);
+		tu->timeri = NULL;
+	}
+	if (copy_from_user(&tselect, _tselect, sizeof(tselect))) {
+		err = -EFAULT;
+		goto __err;
+	}
+	sprintf(str, "application %i", current->pid);
+	if (tselect.id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
+		tselect.id.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION;
+	err = snd_timer_open(&tu->timeri, str, &tselect.id, current->pid);
+	if (err < 0)
+		goto __err;
+
+	kfree(tu->queue);
+	tu->queue = NULL;
+	kfree(tu->tqueue);
+	tu->tqueue = NULL;
+	if (tu->tread) {
+		tu->tqueue = kmalloc(tu->queue_size * sizeof(struct snd_timer_tread),
+				     GFP_KERNEL);
+		if (tu->tqueue == NULL)
+			err = -ENOMEM;
+	} else {
+		tu->queue = kmalloc(tu->queue_size * sizeof(struct snd_timer_read),
+				    GFP_KERNEL);
+		if (tu->queue == NULL)
+			err = -ENOMEM;
+	}
+
+      	if (err < 0) {
+		snd_timer_close(tu->timeri);
+      		tu->timeri = NULL;
+      	} else {
+		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;
+	}
+
+      __err:
+      	mutex_unlock(&tu->tread_sem);
+	return err;
+}
+
+static int snd_timer_user_info(struct file *file,
+			       struct snd_timer_info __user *_info)
+{
+	struct snd_timer_user *tu;
+	struct snd_timer_info *info;
+	struct snd_timer *t;
+	int err = 0;
+
+	tu = file->private_data;
+	if (!tu->timeri)
+		return -EBADFD;
+	t = tu->timeri->timer;
+	if (!t)
+		return -EBADFD;
+
+	info = kzalloc(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,
+				 struct snd_timer_params __user *_params)
+{
+	struct snd_timer_user *tu;
+	struct snd_timer_params params;
+	struct snd_timer *t;
+	struct snd_timer_read *tr;
+	struct snd_timer_tread *ttr;
+	int err;
+
+	tu = file->private_data;
+	if (!tu->timeri)
+		return -EBADFD;
+	t = tu->timeri->timer;
+	if (!t)
+		return -EBADFD;
+	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_SUSPEND)|
+			      (1<<SNDRV_TIMER_EVENT_RESUME)|
+			      (1<<SNDRV_TIMER_EVENT_MSTART)|
+			      (1<<SNDRV_TIMER_EVENT_MSTOP)|
+			      (1<<SNDRV_TIMER_EVENT_MCONTINUE)|
+			      (1<<SNDRV_TIMER_EVENT_MPAUSE)|
+			      (1<<SNDRV_TIMER_EVENT_MSUSPEND)|
+			      (1<<SNDRV_TIMER_EVENT_MRESUME))) {
+		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 = kmalloc(params.queue_size * sizeof(*ttr),
+				      GFP_KERNEL);
+			if (ttr) {
+				kfree(tu->tqueue);
+				tu->queue_size = params.queue_size;
+				tu->tqueue = ttr;
+			}
+		} else {
+			tr = kmalloc(params.queue_size * sizeof(*tr),
+				     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) {
+			struct snd_timer_tread 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 {
+			struct snd_timer_read *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,
+				 struct snd_timer_status __user *_status)
+{
+	struct snd_timer_user *tu;
+	struct snd_timer_status status;
+
+	tu = file->private_data;
+	if (!tu->timeri)
+		return -EBADFD;
+	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;
+	struct snd_timer_user *tu;
+
+	tu = file->private_data;
+	if (!tu->timeri)
+		return -EBADFD;
+	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;
+	struct snd_timer_user *tu;
+
+	tu = file->private_data;
+	if (!tu->timeri)
+		return -EBADFD;
+	return (err = snd_timer_stop(tu->timeri)) < 0 ? err : 0;
+}
+
+static int snd_timer_user_continue(struct file *file)
+{
+	int err;
+	struct snd_timer_user *tu;
+
+	tu = file->private_data;
+	if (!tu->timeri)
+		return -EBADFD;
+	tu->timeri->lost = 0;
+	return (err = snd_timer_continue(tu->timeri)) < 0 ? err : 0;
+}
+
+static int snd_timer_user_pause(struct file *file)
+{
+	int err;
+	struct snd_timer_user *tu;
+
+	tu = file->private_data;
+	if (!tu->timeri)
+		return -EBADFD;
+	return (err = snd_timer_pause(tu->timeri)) < 0 ? err : 0;
+}
+
+enum {
+	SNDRV_TIMER_IOCTL_START_OLD = _IO('T', 0x20),
+	SNDRV_TIMER_IOCTL_STOP_OLD = _IO('T', 0x21),
+	SNDRV_TIMER_IOCTL_CONTINUE_OLD = _IO('T', 0x22),
+	SNDRV_TIMER_IOCTL_PAUSE_OLD = _IO('T', 0x23),
+};
+
+static long snd_timer_user_ioctl(struct file *file, unsigned int cmd,
+				 unsigned long arg)
+{
+	struct snd_timer_user *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;
+
+		mutex_lock(&tu->tread_sem);
+		if (tu->timeri)	{	/* too late */
+			mutex_unlock(&tu->tread_sem);
+			return -EBUSY;
+		}
+		if (get_user(xarg, p)) {
+			mutex_unlock(&tu->tread_sem);
+			return -EFAULT;
+		}
+		tu->tread = xarg ? 1 : 0;
+		mutex_unlock(&tu->tread_sem);
+		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:
+	case SNDRV_TIMER_IOCTL_START_OLD:
+		return snd_timer_user_start(file);
+	case SNDRV_TIMER_IOCTL_STOP:
+	case SNDRV_TIMER_IOCTL_STOP_OLD:
+		return snd_timer_user_stop(file);
+	case SNDRV_TIMER_IOCTL_CONTINUE:
+	case SNDRV_TIMER_IOCTL_CONTINUE_OLD:
+		return snd_timer_user_continue(file);
+	case SNDRV_TIMER_IOCTL_PAUSE:
+	case SNDRV_TIMER_IOCTL_PAUSE_OLD:
+		return snd_timer_user_pause(file);
+	}
+	return -ENOTTY;
+}
+
+static int snd_timer_user_fasync(int fd, struct file * file, int on)
+{
+	struct snd_timer_user *tu;
+
+	tu = file->private_data;
+	return fasync_helper(fd, file, on, &tu->fasync);
+}
+
+static ssize_t snd_timer_user_read(struct file *file, char __user *buffer,
+				   size_t count, loff_t *offset)
+{
+	struct snd_timer_user *tu;
+	long result = 0, unit;
+	int err = 0;
+
+	tu = file->private_data;
+	unit = tu->tread ? sizeof(struct snd_timer_tread) : sizeof(struct snd_timer_read);
+	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(struct snd_timer_tread))) {
+				err = -EFAULT;
+				goto _error;
+			}
+		} else {
+			if (copy_to_user(buffer, &tu->queue[tu->qhead++],
+					 sizeof(struct snd_timer_read))) {
+				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;
+        struct snd_timer_user *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 const 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,
+	.llseek =	no_llseek,
+	.poll =		snd_timer_user_poll,
+	.unlocked_ioctl =	snd_timer_user_ioctl,
+	.compat_ioctl =	snd_timer_user_ioctl_compat,
+	.fasync = 	snd_timer_user_fasync,
+};
+
+/*
+ *  ENTRY functions
+ */
+
+static int __init alsa_timer_init(void)
+{
+	int err;
+
+#ifdef SNDRV_OSS_INFO_DEV_TIMERS
+	snd_oss_info_register(SNDRV_OSS_INFO_DEV_TIMERS, SNDRV_CARDS - 1,
+			      "system timer");
+#endif
+
+	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_f_ops, NULL, "timer")) < 0)
+		snd_printk(KERN_ERR "unable to register timer device (%i)\n",
+			   err);
+	snd_timer_proc_init();
+	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) {
+		struct snd_timer *timer = list_entry(p, struct snd_timer, device_list);
+		snd_timer_free(timer);
+	}
+	snd_timer_proc_done();
+#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_interrupt);
diff --git a/linux/sound/core/timer_compat.c b/linux/sound/core/timer_compat.c
new file mode 100644
index 0000000..e05802a
--- /dev/null
+++ b/linux/sound/core/timer_compat.c
@@ -0,0 +1,127 @@
+/*
+ *   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 snd_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 snd_timer_info32 __user *_info)
+{
+	struct snd_timer_user *tu;
+	struct snd_timer_info32 info;
+	struct snd_timer *t;
+
+	tu = file->private_data;
+	if (snd_BUG_ON(!tu->timeri))
+		return -ENXIO;
+	t = tu->timeri->timer;
+	if (snd_BUG_ON(!t))
+		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 snd_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 snd_timer_status32 __user *_status)
+{
+	struct snd_timer_user *tu;
+	struct snd_timer_status status;
+	
+	tu = file->private_data;
+	if (snd_BUG_ON(!tu->timeri))
+		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 snd_timer_info32),
+	SNDRV_TIMER_IOCTL_STATUS32 = _IOW('T', 0x14, struct snd_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_START_OLD:
+	case SNDRV_TIMER_IOCTL_STOP:
+	case SNDRV_TIMER_IOCTL_STOP_OLD:
+	case SNDRV_TIMER_IOCTL_CONTINUE:
+	case SNDRV_TIMER_IOCTL_CONTINUE_OLD:
+	case SNDRV_TIMER_IOCTL_PAUSE:
+	case SNDRV_TIMER_IOCTL_PAUSE_OLD:
+	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/linux/sound/core/vmaster.c b/linux/sound/core/vmaster.c
new file mode 100644
index 0000000..3b9b550
--- /dev/null
+++ b/linux/sound/core/vmaster.c
@@ -0,0 +1,399 @@
+/*
+ * Virtual master and slave controls
+ *
+ *  Copyright (c) 2008 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, version 2.
+ *
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+
+/*
+ * a subset of information returned via ctl info callback
+ */
+struct link_ctl_info {
+	int type;		/* value type */
+	int count;		/* item count */
+	int min_val, max_val;	/* min, max values */
+};
+
+/*
+ * link master - this contains a list of slave controls that are
+ * identical types, i.e. info returns the same value type and value
+ * ranges, but may have different number of counts.
+ *
+ * The master control is so far only mono volume/switch for simplicity.
+ * The same value will be applied to all slaves.
+ */
+struct link_master {
+	struct list_head slaves;
+	struct link_ctl_info info;
+	int val;		/* the master value */
+	unsigned int tlv[4];
+};
+
+/*
+ * link slave - this contains a slave control element
+ *
+ * It fakes the control callbacsk with additional attenuation by the
+ * master control.  A slave may have either one or two channels.
+ */
+
+struct link_slave {
+	struct list_head list;
+	struct link_master *master;
+	struct link_ctl_info info;
+	int vals[2];		/* current values */
+	unsigned int flags;
+	struct snd_kcontrol slave; /* the copy of original control entry */
+};
+
+static int slave_update(struct link_slave *slave)
+{
+	struct snd_ctl_elem_value *uctl;
+	int err, ch;
+
+	uctl = kmalloc(sizeof(*uctl), GFP_KERNEL);
+	if (!uctl)
+		return -ENOMEM;
+	uctl->id = slave->slave.id;
+	err = slave->slave.get(&slave->slave, uctl);
+	for (ch = 0; ch < slave->info.count; ch++)
+		slave->vals[ch] = uctl->value.integer.value[ch];
+	kfree(uctl);
+	return 0;
+}
+
+/* get the slave ctl info and save the initial values */
+static int slave_init(struct link_slave *slave)
+{
+	struct snd_ctl_elem_info *uinfo;
+	int err;
+
+	if (slave->info.count) {
+		/* already initialized */
+		if (slave->flags & SND_CTL_SLAVE_NEED_UPDATE)
+			return slave_update(slave);
+		return 0;
+	}
+
+	uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL);
+	if (!uinfo)
+		return -ENOMEM;
+	uinfo->id = slave->slave.id;
+	err = slave->slave.info(&slave->slave, uinfo);
+	if (err < 0) {
+		kfree(uinfo);
+		return err;
+	}
+	slave->info.type = uinfo->type;
+	slave->info.count = uinfo->count;
+	if (slave->info.count > 2  ||
+	    (slave->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER &&
+	     slave->info.type != SNDRV_CTL_ELEM_TYPE_BOOLEAN)) {
+		snd_printk(KERN_ERR "invalid slave element\n");
+		kfree(uinfo);
+		return -EINVAL;
+	}
+	slave->info.min_val = uinfo->value.integer.min;
+	slave->info.max_val = uinfo->value.integer.max;
+	kfree(uinfo);
+
+	return slave_update(slave);
+}
+
+/* initialize master volume */
+static int master_init(struct link_master *master)
+{
+	struct link_slave *slave;
+
+	if (master->info.count)
+		return 0; /* already initialized */
+
+	list_for_each_entry(slave, &master->slaves, list) {
+		int err = slave_init(slave);
+		if (err < 0)
+			return err;
+		master->info = slave->info;
+		master->info.count = 1; /* always mono */
+		/* set full volume as default (= no attenuation) */
+		master->val = master->info.max_val;
+		return 0;
+	}
+	return -ENOENT;
+}
+
+static int slave_get_val(struct link_slave *slave,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	int err, ch;
+
+	err = slave_init(slave);
+	if (err < 0)
+		return err;
+	for (ch = 0; ch < slave->info.count; ch++)
+		ucontrol->value.integer.value[ch] = slave->vals[ch];
+	return 0;
+}
+
+static int slave_put_val(struct link_slave *slave,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	int err, ch, vol;
+
+	err = master_init(slave->master);
+	if (err < 0)
+		return err;
+
+	switch (slave->info.type) {
+	case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+		for (ch = 0; ch < slave->info.count; ch++)
+			ucontrol->value.integer.value[ch] &=
+				!!slave->master->val;
+		break;
+	case SNDRV_CTL_ELEM_TYPE_INTEGER:
+		for (ch = 0; ch < slave->info.count; ch++) {
+			/* max master volume is supposed to be 0 dB */
+			vol = ucontrol->value.integer.value[ch];
+			vol += slave->master->val - slave->master->info.max_val;
+			if (vol < slave->info.min_val)
+				vol = slave->info.min_val;
+			else if (vol > slave->info.max_val)
+				vol = slave->info.max_val;
+			ucontrol->value.integer.value[ch] = vol;
+		}
+		break;
+	}
+	return slave->slave.put(&slave->slave, ucontrol);
+}
+
+/*
+ * ctl callbacks for slaves
+ */
+static int slave_info(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_info *uinfo)
+{
+	struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+	return slave->slave.info(&slave->slave, uinfo);
+}
+
+static int slave_get(struct snd_kcontrol *kcontrol,
+		     struct snd_ctl_elem_value *ucontrol)
+{
+	struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+	return slave_get_val(slave, ucontrol);
+}
+
+static int slave_put(struct snd_kcontrol *kcontrol,
+		     struct snd_ctl_elem_value *ucontrol)
+{
+	struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+	int err, ch, changed = 0;
+
+	err = slave_init(slave);
+	if (err < 0)
+		return err;
+	for (ch = 0; ch < slave->info.count; ch++) {
+		if (slave->vals[ch] != ucontrol->value.integer.value[ch]) {
+			changed = 1;
+			slave->vals[ch] = ucontrol->value.integer.value[ch];
+		}
+	}
+	if (!changed)
+		return 0;
+	return slave_put_val(slave, ucontrol);
+}
+
+static int slave_tlv_cmd(struct snd_kcontrol *kcontrol,
+			 int op_flag, unsigned int size,
+			 unsigned int __user *tlv)
+{
+	struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+	/* FIXME: this assumes that the max volume is 0 dB */
+	return slave->slave.tlv.c(&slave->slave, op_flag, size, tlv);
+}
+
+static void slave_free(struct snd_kcontrol *kcontrol)
+{
+	struct link_slave *slave = snd_kcontrol_chip(kcontrol);
+	if (slave->slave.private_free)
+		slave->slave.private_free(&slave->slave);
+	if (slave->master)
+		list_del(&slave->list);
+	kfree(slave);
+}
+
+/*
+ * Add a slave control to the group with the given master control
+ *
+ * All slaves must be the same type (returning the same information
+ * via info callback).  The fucntion doesn't check it, so it's your
+ * responsibility.
+ *
+ * Also, some additional limitations:
+ * - at most two channels
+ * - logarithmic volume control (dB level), no linear volume
+ * - master can only attenuate the volume, no gain
+ */
+int _snd_ctl_add_slave(struct snd_kcontrol *master, struct snd_kcontrol *slave,
+		       unsigned int flags)
+{
+	struct link_master *master_link = snd_kcontrol_chip(master);
+	struct link_slave *srec;
+
+	srec = kzalloc(sizeof(*srec) +
+		       slave->count * sizeof(*slave->vd), GFP_KERNEL);
+	if (!srec)
+		return -ENOMEM;
+	srec->slave = *slave;
+	memcpy(srec->slave.vd, slave->vd, slave->count * sizeof(*slave->vd));
+	srec->master = master_link;
+	srec->flags = flags;
+
+	/* override callbacks */
+	slave->info = slave_info;
+	slave->get = slave_get;
+	slave->put = slave_put;
+	if (slave->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)
+		slave->tlv.c = slave_tlv_cmd;
+	slave->private_data = srec;
+	slave->private_free = slave_free;
+
+	list_add_tail(&srec->list, &master_link->slaves);
+	return 0;
+}
+EXPORT_SYMBOL(_snd_ctl_add_slave);
+
+/*
+ * ctl callbacks for master controls
+ */
+static int master_info(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_info *uinfo)
+{
+	struct link_master *master = snd_kcontrol_chip(kcontrol);
+	int ret;
+
+	ret = master_init(master);
+	if (ret < 0)
+		return ret;
+	uinfo->type = master->info.type;
+	uinfo->count = master->info.count;
+	uinfo->value.integer.min = master->info.min_val;
+	uinfo->value.integer.max = master->info.max_val;
+	return 0;
+}
+
+static int master_get(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_value *ucontrol)
+{
+	struct link_master *master = snd_kcontrol_chip(kcontrol);
+	int err = master_init(master);
+	if (err < 0)
+		return err;
+	ucontrol->value.integer.value[0] = master->val;
+	return 0;
+}
+
+static int master_put(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_value *ucontrol)
+{
+	struct link_master *master = snd_kcontrol_chip(kcontrol);
+	struct link_slave *slave;
+	struct snd_ctl_elem_value *uval;
+	int err, old_val;
+
+	err = master_init(master);
+	if (err < 0)
+		return err;
+	old_val = master->val;
+	if (ucontrol->value.integer.value[0] == old_val)
+		return 0;
+
+	uval = kmalloc(sizeof(*uval), GFP_KERNEL);
+	if (!uval)
+		return -ENOMEM;
+	list_for_each_entry(slave, &master->slaves, list) {
+		master->val = old_val;
+		uval->id = slave->slave.id;
+		slave_get_val(slave, uval);
+		master->val = ucontrol->value.integer.value[0];
+		slave_put_val(slave, uval);
+	}
+	kfree(uval);
+	return 1;
+}
+
+static void master_free(struct snd_kcontrol *kcontrol)
+{
+	struct link_master *master = snd_kcontrol_chip(kcontrol);
+	struct link_slave *slave;
+
+	list_for_each_entry(slave, &master->slaves, list)
+		slave->master = NULL;
+	kfree(master);
+}
+
+
+/**
+ * snd_ctl_make_virtual_master - Create a virtual master control
+ * @name: name string of the control element to create
+ * @tlv: optional TLV int array for dB information
+ *
+ * Creates a virtual matster control with the given name string.
+ * Returns the created control element, or NULL for errors (ENOMEM).
+ *
+ * After creating a vmaster element, you can add the slave controls
+ * via snd_ctl_add_slave() or snd_ctl_add_slave_uncached().
+ *
+ * The optional argument @tlv can be used to specify the TLV information
+ * for dB scale of the master control.  It should be a single element
+ * with #SNDRV_CTL_TLVT_DB_SCALE, #SNDRV_CTL_TLV_DB_MINMAX or
+ * #SNDRV_CTL_TLVT_DB_MINMAX_MUTE type, and should be the max 0dB.
+ */
+struct snd_kcontrol *snd_ctl_make_virtual_master(char *name,
+						 const unsigned int *tlv)
+{
+	struct link_master *master;
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_new knew;
+
+	memset(&knew, 0, sizeof(knew));
+	knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	knew.name = name;
+	knew.info = master_info;
+
+	master = kzalloc(sizeof(*master), GFP_KERNEL);
+	if (!master)
+		return NULL;
+	INIT_LIST_HEAD(&master->slaves);
+
+	kctl = snd_ctl_new1(&knew, master);
+	if (!kctl) {
+		kfree(master);
+		return NULL;
+	}
+	/* override some callbacks */
+	kctl->info = master_info;
+	kctl->get = master_get;
+	kctl->put = master_put;
+	kctl->private_free = master_free;
+
+	/* additional (constant) TLV read */
+	if (tlv &&
+	    (tlv[0] == SNDRV_CTL_TLVT_DB_SCALE ||
+	     tlv[0] == SNDRV_CTL_TLVT_DB_MINMAX ||
+	     tlv[0] == SNDRV_CTL_TLVT_DB_MINMAX_MUTE)) {
+		kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+		memcpy(master->tlv, tlv, sizeof(master->tlv));
+		kctl->tlv.p = master->tlv;
+	}
+
+	return kctl;
+}
+EXPORT_SYMBOL(snd_ctl_make_virtual_master);
diff --git a/linux/sound/drivers/Kconfig b/linux/sound/drivers/Kconfig
new file mode 100644
index 0000000..c896116
--- /dev/null
+++ b/linux/sound/drivers/Kconfig
@@ -0,0 +1,222 @@
+config SND_MPU401_UART
+        tristate
+        select SND_RAWMIDI
+
+config SND_OPL3_LIB
+	tristate
+	select SND_TIMER
+	select SND_HWDEP
+
+config SND_OPL4_LIB
+	tristate
+	select SND_TIMER
+	select SND_HWDEP
+
+config SND_VX_LIB
+	tristate
+	select SND_HWDEP
+	select SND_PCM
+
+config SND_AC97_CODEC
+	tristate
+	select SND_PCM
+	select AC97_BUS
+	select SND_VMASTER
+
+menuconfig SND_DRIVERS
+	bool "Generic sound devices"
+	default y
+	help
+	  Support for generic sound devices.
+  
+if SND_DRIVERS
+
+config SND_PCSP
+	tristate "PC-Speaker support (READ HELP!)"
+	depends on PCSPKR_PLATFORM && X86 && HIGH_RES_TIMERS
+	depends on INPUT
+	depends on EXPERIMENTAL
+	select SND_PCM
+	help
+	  If you don't have a sound card in your computer, you can include a
+	  driver for the PC speaker which allows it to act like a primitive
+	  sound card.
+	  This driver also replaces the pcspkr driver for beeps.
+
+	  You can compile this as a module which will be called snd-pcsp.
+
+	  WARNING: if you already have a soundcard, enabling this
+	  driver may lead to a problem. Namely, it may get loaded
+	  before the other sound driver of yours, making the
+	  pc-speaker a default sound device. Which is likely not
+	  what you want. To make this driver play nicely with other
+	  sound driver, you can add this into your /etc/modprobe.conf:
+	  options snd-pcsp index=2
+
+	  You don't need this driver if you only want your pc-speaker to beep.
+	  You don't need this driver if you have a tablet piezo beeper
+	  in your PC instead of the real speaker.
+
+	  Say N if you have a sound card.
+	  Say M if you don't.
+	  Say Y only if you really know what you do.
+
+config SND_DUMMY
+	tristate "Dummy (/dev/null) soundcard"
+	select SND_PCM
+	help
+	  Say Y here to include the dummy driver.  This driver does
+	  nothing, but emulates various mixer controls and PCM devices.
+
+	  You don't need this unless you're testing the hardware support
+	  of programs using the ALSA API.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-dummy.
+
+config SND_ALOOP
+        tristate "Generic loopback driver (PCM)"
+        select SND_PCM
+        help
+          Say 'Y' or 'M' to include support for the PCM loopback device.
+	  This module returns played samples back to the user space using
+	  the standard ALSA PCM device. The devices are routed 0->1 and
+          1->0, where first number is the playback PCM device and second
+	  number is the capture device. Module creates two PCM devices and
+	  configured number of substreams (see the pcm_substreams module
+          parameter).
+
+	  The looback device allow time sychronization with an external
+	  timing source using the time shift universal control (+-20%
+	  of system time).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-aloop.
+
+config SND_VIRMIDI
+	tristate "Virtual MIDI soundcard"
+	depends on SND_SEQUENCER
+	select SND_TIMER
+	select SND_RAWMIDI
+	help
+	  Say Y here to include the virtual MIDI driver.  This driver
+	  allows to connect applications using raw MIDI devices to
+	  sequencer clients.
+
+	  If you don't know what MIDI is, say N here.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-virmidi.
+
+config SND_MTPAV
+	tristate "MOTU MidiTimePiece AV multiport MIDI"
+	select SND_RAWMIDI
+	help
+	  To use a MOTU MidiTimePiece AV multiport MIDI adapter
+	  connected to the parallel port, say Y here and make sure that
+	  the standard parallel port driver isn't used for the port.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-mtpav.
+
+config SND_MTS64
+	tristate "ESI Miditerminal 4140 driver"
+	depends on PARPORT
+	select SND_RAWMIDI
+	help
+	  The ESI Miditerminal 4140 is a 4 In 4 Out MIDI Interface with 
+          additional SMPTE Timecode capabilities for the parallel port.
+
+	  Say 'Y' to include support for this device.
+
+	  To compile this driver as a module, chose 'M' here: the module 
+          will be called snd-mts64.
+
+config SND_SERIAL_U16550
+	tristate "UART16550 serial MIDI driver"
+	select SND_RAWMIDI
+	help
+	  To include support for MIDI serial port interfaces, say Y here
+	  and read <file:Documentation/sound/alsa/serial-u16550.txt>.
+	  This driver works with serial UARTs 16550 and better.
+
+	  This driver accesses the serial port hardware directly, so
+	  make sure that the standard serial driver isn't used or
+	  deactivated with setserial before loading this driver.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-serial-u16550.
+
+config SND_MPU401
+	tristate "Generic MPU-401 UART driver"
+	select SND_MPU401_UART
+	help
+	  Say Y here to include support for MIDI ports compatible with
+	  the Roland MPU-401 interface in UART mode.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-mpu401.
+
+config SND_PORTMAN2X4
+	tristate "Portman 2x4 driver"
+	depends on PARPORT
+	select SND_RAWMIDI
+	help
+	  Say Y here to include support for Midiman Portman 2x4 parallel
+	  port MIDI device.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-portman2x4.
+
+config SND_ML403_AC97CR
+	tristate "Xilinx ML403 AC97 Controller Reference"
+	depends on XILINX_VIRTEX
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for the
+	  opb_ac97_controller_ref_v1_00_a ip core found in Xilinx's ML403
+	  reference design.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ml403_ac97cr.
+
+config SND_AC97_POWER_SAVE
+	bool "AC97 Power-Saving Mode"
+	depends on SND_AC97_CODEC
+	default n
+	help
+	  Say Y here to enable the aggressive power-saving support of
+	  AC97 codecs.  In this mode, the power-mode is dynamically
+	  controlled at each open/close.
+
+	  The mode is activated by passing 'power_save=X' to the
+	  snd-ac97-codec driver module, where 'X' is the time-out
+	  value, a nonnegative integer that specifies how many
+	  seconds of idle time the driver must count before it may
+	  put the AC97 into power-save mode;  a value of 0 (zero)
+	  disables the use of this power-save mode.
+
+	  After the snd-ac97-codec driver module has been loaded,
+	  the 'power_save' parameter can be set via sysfs as follows:
+
+	    echo 10 > /sys/module/snd_ac97_codec/parameters/power_save
+
+	  In this case, the time-out is set to 10 seconds; setting
+	  the time-out to 1 second (the minimum activation value)
+	  isn't recommended because many applications try to reopen
+	  the device frequently.  A value of 10 seconds would be a
+	  good choice for normal operations.
+
+	  See Documentation/sound/alsa/powersave.txt for more details.
+
+config SND_AC97_POWER_SAVE_DEFAULT
+	int "Default time-out for AC97 power-save mode"
+	depends on SND_AC97_POWER_SAVE
+	default 0
+	help
+	  The default time-out value in seconds for AC97 automatic
+	  power-save mode.  0 means to disable the power-save mode.
+
+	  See SND_AC97_POWER_SAVE for more details.
+
+endif	# SND_DRIVERS
diff --git a/linux/sound/drivers/Makefile b/linux/sound/drivers/Makefile
new file mode 100644
index 0000000..1a8440c
--- /dev/null
+++ b/linux/sound/drivers/Makefile
@@ -0,0 +1,25 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-dummy-objs := dummy.o
+snd-aloop-objs := aloop.o
+snd-mtpav-objs := mtpav.o
+snd-mts64-objs := mts64.o
+snd-portman2x4-objs := portman2x4.o
+snd-serial-u16550-objs := serial-u16550.o
+snd-virmidi-objs := virmidi.o
+snd-ml403-ac97cr-objs := ml403-ac97cr.o pcm-indirect2.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_DUMMY) += snd-dummy.o
+obj-$(CONFIG_SND_ALOOP) += snd-aloop.o
+obj-$(CONFIG_SND_VIRMIDI) += snd-virmidi.o
+obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o
+obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o
+obj-$(CONFIG_SND_MTS64) += snd-mts64.o
+obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o
+obj-$(CONFIG_SND_ML403_AC97CR) += snd-ml403-ac97cr.o
+
+obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/
diff --git a/linux/sound/drivers/aloop.c b/linux/sound/drivers/aloop.c
new file mode 100644
index 0000000..12b44b0
--- /dev/null
+++ b/linux/sound/drivers/aloop.c
@@ -0,0 +1,1258 @@
+/*
+ *  Loopback soundcard
+ *
+ *  Original code:
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *  More accurate positioning and full-duplex support:
+ *  Copyright (c) Ahmet İnan <ainan at mathematik.uni-freiburg.de>
+ *
+ *  Major (almost complete) rewrite:
+ *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ *  A next major update in 2010 (separate timers for playback and capture):
+ *  Copyright (c) Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("A loopback soundcard");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ALSA,Loopback soundcard}}");
+
+#define MAX_PCM_SUBSTREAMS	8
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
+static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
+static int pcm_notify[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for loopback soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for loopback soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable this loopback soundcard.");
+module_param_array(pcm_substreams, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver.");
+module_param_array(pcm_notify, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes.");
+
+#define NO_PITCH 100000
+
+struct loopback_pcm;
+
+struct loopback_cable {
+	spinlock_t lock;
+	struct loopback_pcm *streams[2];
+	struct snd_pcm_hardware hw;
+	/* flags */
+	unsigned int valid;
+	unsigned int running;
+	unsigned int pause;
+};
+
+struct loopback_setup {
+	unsigned int notify: 1;
+	unsigned int rate_shift;
+	unsigned int format;
+	unsigned int rate;
+	unsigned int channels;
+	struct snd_ctl_elem_id active_id;
+	struct snd_ctl_elem_id format_id;
+	struct snd_ctl_elem_id rate_id;
+	struct snd_ctl_elem_id channels_id;
+};
+
+struct loopback {
+	struct snd_card *card;
+	struct mutex cable_lock;
+	struct loopback_cable *cables[MAX_PCM_SUBSTREAMS][2];
+	struct snd_pcm *pcm[2];
+	struct loopback_setup setup[MAX_PCM_SUBSTREAMS][2];
+};
+
+struct loopback_pcm {
+	struct loopback *loopback;
+	struct snd_pcm_substream *substream;
+	struct loopback_cable *cable;
+	unsigned int pcm_buffer_size;
+	unsigned int buf_pos;	/* position in buffer */
+	unsigned int silent_size;
+	/* PCM parameters */
+	unsigned int pcm_period_size;
+	unsigned int pcm_bps;		/* bytes per second */
+	unsigned int pcm_salign;	/* bytes per sample * channels */
+	unsigned int pcm_rate_shift;	/* rate shift value */
+	/* flags */
+	unsigned int period_update_pending :1;
+	/* timer stuff */
+	unsigned int irq_pos;		/* fractional IRQ position */
+	unsigned int period_size_frac;
+	unsigned long last_jiffies;
+	struct timer_list timer;
+};
+
+static struct platform_device *devices[SNDRV_CARDS];
+
+static inline unsigned int byte_pos(struct loopback_pcm *dpcm, unsigned int x)
+{
+	if (dpcm->pcm_rate_shift == NO_PITCH) {
+		x /= HZ;
+	} else {
+		x = div_u64(NO_PITCH * (unsigned long long)x,
+			    HZ * (unsigned long long)dpcm->pcm_rate_shift);
+	}
+	return x - (x % dpcm->pcm_salign);
+}
+
+static inline unsigned int frac_pos(struct loopback_pcm *dpcm, unsigned int x)
+{
+	if (dpcm->pcm_rate_shift == NO_PITCH) {	/* no pitch */
+		return x * HZ;
+	} else {
+		x = div_u64(dpcm->pcm_rate_shift * (unsigned long long)x * HZ,
+			    NO_PITCH);
+	}
+	return x;
+}
+
+static inline struct loopback_setup *get_setup(struct loopback_pcm *dpcm)
+{
+	int device = dpcm->substream->pstr->pcm->device;
+	
+	if (dpcm->substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		device ^= 1;
+	return &dpcm->loopback->setup[dpcm->substream->number][device];
+}
+
+static inline unsigned int get_notify(struct loopback_pcm *dpcm)
+{
+	return get_setup(dpcm)->notify;
+}
+
+static inline unsigned int get_rate_shift(struct loopback_pcm *dpcm)
+{
+	return get_setup(dpcm)->rate_shift;
+}
+
+static void loopback_timer_start(struct loopback_pcm *dpcm)
+{
+	unsigned long tick;
+	unsigned int rate_shift = get_rate_shift(dpcm);
+
+	if (rate_shift != dpcm->pcm_rate_shift) {
+		dpcm->pcm_rate_shift = rate_shift;
+		dpcm->period_size_frac = frac_pos(dpcm, dpcm->pcm_period_size);
+	}
+	if (dpcm->period_size_frac <= dpcm->irq_pos) {
+		dpcm->irq_pos %= dpcm->period_size_frac;
+		dpcm->period_update_pending = 1;
+	}
+	tick = dpcm->period_size_frac - dpcm->irq_pos;
+	tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps;
+	dpcm->timer.expires = jiffies + tick;
+	add_timer(&dpcm->timer);
+}
+
+static inline void loopback_timer_stop(struct loopback_pcm *dpcm)
+{
+	del_timer(&dpcm->timer);
+	dpcm->timer.expires = 0;
+}
+
+#define CABLE_VALID_PLAYBACK	(1 << SNDRV_PCM_STREAM_PLAYBACK)
+#define CABLE_VALID_CAPTURE	(1 << SNDRV_PCM_STREAM_CAPTURE)
+#define CABLE_VALID_BOTH	(CABLE_VALID_PLAYBACK|CABLE_VALID_CAPTURE)
+
+static int loopback_check_format(struct loopback_cable *cable, int stream)
+{
+	struct snd_pcm_runtime *runtime, *cruntime;
+	struct loopback_setup *setup;
+	struct snd_card *card;
+	int check;
+
+	if (cable->valid != CABLE_VALID_BOTH) {
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+			goto __notify;
+		return 0;
+	}
+	runtime = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]->
+							substream->runtime;
+	cruntime = cable->streams[SNDRV_PCM_STREAM_CAPTURE]->
+							substream->runtime;
+	check = runtime->format != cruntime->format ||
+		runtime->rate != cruntime->rate ||
+		runtime->channels != cruntime->channels;
+	if (!check)
+		return 0;
+	if (stream == SNDRV_PCM_STREAM_CAPTURE) {
+		return -EIO;
+	} else {
+		snd_pcm_stop(cable->streams[SNDRV_PCM_STREAM_CAPTURE]->
+					substream, SNDRV_PCM_STATE_DRAINING);
+	      __notify:
+		runtime = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]->
+							substream->runtime;
+		setup = get_setup(cable->streams[SNDRV_PCM_STREAM_PLAYBACK]);
+		card = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]->loopback->card;
+		if (setup->format != runtime->format) {
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+							&setup->format_id);
+			setup->format = runtime->format;
+		}
+		if (setup->rate != runtime->rate) {
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+							&setup->rate_id);
+			setup->rate = runtime->rate;
+		}
+		if (setup->channels != runtime->channels) {
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+							&setup->channels_id);
+			setup->channels = runtime->channels;
+		}
+	}
+	return 0;
+}
+
+static void loopback_active_notify(struct loopback_pcm *dpcm)
+{
+	snd_ctl_notify(dpcm->loopback->card,
+		       SNDRV_CTL_EVENT_MASK_VALUE,
+		       &get_setup(dpcm)->active_id);
+}
+
+static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct loopback_pcm *dpcm = runtime->private_data;
+	struct loopback_cable *cable = dpcm->cable;
+	int err, stream = 1 << substream->stream;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		err = loopback_check_format(cable, substream->stream);
+		if (err < 0)
+			return err;
+		dpcm->last_jiffies = jiffies;
+		dpcm->pcm_rate_shift = 0;
+		spin_lock(&cable->lock);	
+		cable->running |= stream;
+		cable->pause &= ~stream;
+		spin_unlock(&cable->lock);
+		loopback_timer_start(dpcm);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			loopback_active_notify(dpcm);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		spin_lock(&cable->lock);	
+		cable->running &= ~stream;
+		cable->pause &= ~stream;
+		spin_unlock(&cable->lock);
+		loopback_timer_stop(dpcm);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			loopback_active_notify(dpcm);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		spin_lock(&cable->lock);	
+		cable->pause |= stream;
+		spin_unlock(&cable->lock);
+		loopback_timer_stop(dpcm);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		spin_lock(&cable->lock);
+		dpcm->last_jiffies = jiffies;
+		cable->pause &= ~stream;
+		spin_unlock(&cable->lock);
+		loopback_timer_start(dpcm);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void params_change_substream(struct loopback_pcm *dpcm,
+				    struct snd_pcm_runtime *runtime)
+{
+	struct snd_pcm_runtime *dst_runtime;
+
+	if (dpcm == NULL || dpcm->substream == NULL)
+		return;
+	dst_runtime = dpcm->substream->runtime;
+	if (dst_runtime == NULL)
+		return;
+	dst_runtime->hw = dpcm->cable->hw;
+}
+
+static void params_change(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct loopback_pcm *dpcm = runtime->private_data;
+	struct loopback_cable *cable = dpcm->cable;
+
+	cable->hw.formats = (1ULL << runtime->format);
+	cable->hw.rate_min = runtime->rate;
+	cable->hw.rate_max = runtime->rate;
+	cable->hw.channels_min = runtime->channels;
+	cable->hw.channels_max = runtime->channels;
+	params_change_substream(cable->streams[SNDRV_PCM_STREAM_PLAYBACK],
+				runtime);
+	params_change_substream(cable->streams[SNDRV_PCM_STREAM_CAPTURE],
+				runtime);
+}
+
+static int loopback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct loopback_pcm *dpcm = runtime->private_data;
+	struct loopback_cable *cable = dpcm->cable;
+	int bps, salign;
+
+	salign = (snd_pcm_format_width(runtime->format) *
+						runtime->channels) / 8;
+	bps = salign * runtime->rate;
+	if (bps <= 0 || salign <= 0)
+		return -EINVAL;
+
+	dpcm->buf_pos = 0;
+	dpcm->pcm_buffer_size = frames_to_bytes(runtime, runtime->buffer_size);
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		/* clear capture buffer */
+		dpcm->silent_size = dpcm->pcm_buffer_size;
+		snd_pcm_format_set_silence(runtime->format, runtime->dma_area,
+					   runtime->buffer_size * runtime->channels);
+	}
+
+	dpcm->irq_pos = 0;
+	dpcm->period_update_pending = 0;
+	dpcm->pcm_bps = bps;
+	dpcm->pcm_salign = salign;
+	dpcm->pcm_period_size = frames_to_bytes(runtime, runtime->period_size);
+
+	mutex_lock(&dpcm->loopback->cable_lock);
+	if (!(cable->valid & ~(1 << substream->stream)) ||
+            (get_setup(dpcm)->notify &&
+	     substream->stream == SNDRV_PCM_STREAM_PLAYBACK))
+		params_change(substream);
+	cable->valid |= 1 << substream->stream;
+	mutex_unlock(&dpcm->loopback->cable_lock);
+
+	return 0;
+}
+
+static void clear_capture_buf(struct loopback_pcm *dpcm, unsigned int bytes)
+{
+	struct snd_pcm_runtime *runtime = dpcm->substream->runtime;
+	char *dst = runtime->dma_area;
+	unsigned int dst_off = dpcm->buf_pos;
+
+	if (dpcm->silent_size >= dpcm->pcm_buffer_size)
+		return;
+	if (dpcm->silent_size + bytes > dpcm->pcm_buffer_size)
+		bytes = dpcm->pcm_buffer_size - dpcm->silent_size;
+
+	for (;;) {
+		unsigned int size = bytes;
+		if (dst_off + size > dpcm->pcm_buffer_size)
+			size = dpcm->pcm_buffer_size - dst_off;
+		snd_pcm_format_set_silence(runtime->format, dst + dst_off,
+					   bytes_to_frames(runtime, size) *
+					   	runtime->channels);
+		dpcm->silent_size += size;
+		bytes -= size;
+		if (!bytes)
+			break;
+		dst_off = 0;
+	}
+}
+
+static void copy_play_buf(struct loopback_pcm *play,
+			  struct loopback_pcm *capt,
+			  unsigned int bytes)
+{
+	struct snd_pcm_runtime *runtime = play->substream->runtime;
+	char *src = runtime->dma_area;
+	char *dst = capt->substream->runtime->dma_area;
+	unsigned int src_off = play->buf_pos;
+	unsigned int dst_off = capt->buf_pos;
+	unsigned int clear_bytes = 0;
+
+	/* check if playback is draining, trim the capture copy size
+	 * when our pointer is at the end of playback ring buffer */
+	if (runtime->status->state == SNDRV_PCM_STATE_DRAINING &&
+	    snd_pcm_playback_hw_avail(runtime) < runtime->buffer_size) { 
+	    	snd_pcm_uframes_t appl_ptr, appl_ptr1, diff;
+		appl_ptr = appl_ptr1 = runtime->control->appl_ptr;
+		appl_ptr1 -= appl_ptr1 % runtime->buffer_size;
+		appl_ptr1 += play->buf_pos / play->pcm_salign;
+		if (appl_ptr < appl_ptr1)
+			appl_ptr1 -= runtime->buffer_size;
+		diff = (appl_ptr - appl_ptr1) * play->pcm_salign;
+		if (diff < bytes) {
+			clear_bytes = bytes - diff;
+			bytes = diff;
+		}
+	}
+
+	for (;;) {
+		unsigned int size = bytes;
+		if (src_off + size > play->pcm_buffer_size)
+			size = play->pcm_buffer_size - src_off;
+		if (dst_off + size > capt->pcm_buffer_size)
+			size = capt->pcm_buffer_size - dst_off;
+		memcpy(dst + dst_off, src + src_off, size);
+		capt->silent_size = 0;
+		bytes -= size;
+		if (!bytes)
+			break;
+		src_off = (src_off + size) % play->pcm_buffer_size;
+		dst_off = (dst_off + size) % capt->pcm_buffer_size;
+	}
+
+	if (clear_bytes > 0) {
+		clear_capture_buf(capt, clear_bytes);
+		capt->silent_size = 0;
+	}
+}
+
+#define BYTEPOS_UPDATE_POSONLY	0
+#define BYTEPOS_UPDATE_CLEAR	1
+#define BYTEPOS_UPDATE_COPY	2
+
+static void loopback_bytepos_update(struct loopback_pcm *dpcm,
+				    unsigned int delta,
+				    unsigned int cmd)
+{
+	unsigned int count;
+	unsigned long last_pos;
+
+	last_pos = byte_pos(dpcm, dpcm->irq_pos);
+	dpcm->irq_pos += delta * dpcm->pcm_bps;
+	count = byte_pos(dpcm, dpcm->irq_pos) - last_pos;
+	if (!count)
+		return;
+	if (cmd == BYTEPOS_UPDATE_CLEAR)
+		clear_capture_buf(dpcm, count);
+	else if (cmd == BYTEPOS_UPDATE_COPY)
+		copy_play_buf(dpcm->cable->streams[SNDRV_PCM_STREAM_PLAYBACK],
+			      dpcm->cable->streams[SNDRV_PCM_STREAM_CAPTURE],
+			      count);
+	dpcm->buf_pos += count;
+	dpcm->buf_pos %= dpcm->pcm_buffer_size;
+	if (dpcm->irq_pos >= dpcm->period_size_frac) {
+		dpcm->irq_pos %= dpcm->period_size_frac;
+		dpcm->period_update_pending = 1;
+	}
+}
+
+static unsigned int loopback_pos_update(struct loopback_cable *cable)
+{
+	struct loopback_pcm *dpcm_play =
+			cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	struct loopback_pcm *dpcm_capt =
+			cable->streams[SNDRV_PCM_STREAM_CAPTURE];
+	unsigned long delta_play = 0, delta_capt = 0;
+	unsigned int running;
+
+	spin_lock(&cable->lock);	
+	running = cable->running ^ cable->pause;
+	if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) {
+		delta_play = jiffies - dpcm_play->last_jiffies;
+		dpcm_play->last_jiffies += delta_play;
+	}
+
+	if (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) {
+		delta_capt = jiffies - dpcm_capt->last_jiffies;
+		dpcm_capt->last_jiffies += delta_capt;
+	}
+
+	if (delta_play == 0 && delta_capt == 0) {
+		spin_unlock(&cable->lock);
+		return running;
+	}
+		
+	if (delta_play > delta_capt) {
+		loopback_bytepos_update(dpcm_play, delta_play - delta_capt,
+					BYTEPOS_UPDATE_POSONLY);
+		delta_play = delta_capt;
+	} else if (delta_play < delta_capt) {
+		loopback_bytepos_update(dpcm_capt, delta_capt - delta_play,
+					BYTEPOS_UPDATE_CLEAR);
+		delta_capt = delta_play;
+	}
+
+	if (delta_play == 0 && delta_capt == 0) {
+		spin_unlock(&cable->lock);
+		return running;
+	}
+	/* note delta_capt == delta_play at this moment */
+	loopback_bytepos_update(dpcm_capt, delta_capt, BYTEPOS_UPDATE_COPY);
+	loopback_bytepos_update(dpcm_play, delta_play, BYTEPOS_UPDATE_POSONLY);
+	spin_unlock(&cable->lock);
+	return running;
+}
+
+static void loopback_timer_function(unsigned long data)
+{
+	struct loopback_pcm *dpcm = (struct loopback_pcm *)data;
+	unsigned int running;
+
+	running = loopback_pos_update(dpcm->cable);
+	if (running & (1 << dpcm->substream->stream)) {
+		loopback_timer_start(dpcm);
+		if (dpcm->period_update_pending) {
+			dpcm->period_update_pending = 0;
+			snd_pcm_period_elapsed(dpcm->substream);
+		}
+	}
+}
+
+static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct loopback_pcm *dpcm = runtime->private_data;
+
+	loopback_pos_update(dpcm->cable);
+	return bytes_to_frames(runtime, dpcm->buf_pos);
+}
+
+static struct snd_pcm_hardware loopback_pcm_hardware =
+{
+	.info =		(SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
+			 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE),
+	.formats =	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
+			 SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE |
+			 SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE),
+	.rates =	SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_192000,
+	.rate_min =		8000,
+	.rate_max =		192000,
+	.channels_min =		1,
+	.channels_max =		32,
+	.buffer_bytes_max =	2 * 1024 * 1024,
+	.period_bytes_min =	64,
+	/* note check overflow in frac_pos() using pcm_rate_shift before
+	   changing period_bytes_max value */
+	.period_bytes_max =	1024 * 1024,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static void loopback_runtime_free(struct snd_pcm_runtime *runtime)
+{
+	struct loopback_pcm *dpcm = runtime->private_data;
+	kfree(dpcm);
+}
+
+static int loopback_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+}
+
+static int loopback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct loopback_pcm *dpcm = runtime->private_data;
+	struct loopback_cable *cable = dpcm->cable;
+
+	mutex_lock(&dpcm->loopback->cable_lock);
+	cable->valid &= ~(1 << substream->stream);
+	mutex_unlock(&dpcm->loopback->cable_lock);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static unsigned int get_cable_index(struct snd_pcm_substream *substream)
+{
+	if (!substream->pcm->device)
+		return substream->stream;
+	else
+		return !substream->stream;
+}
+
+static int rule_format(struct snd_pcm_hw_params *params,
+		       struct snd_pcm_hw_rule *rule)
+{
+
+	struct snd_pcm_hardware *hw = rule->private;
+	struct snd_mask *maskp = hw_param_mask(params, rule->var);
+
+	maskp->bits[0] &= (u_int32_t)hw->formats;
+	maskp->bits[1] &= (u_int32_t)(hw->formats >> 32);
+	memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX-64) / 8); /* clear rest */
+	if (! maskp->bits[0] && ! maskp->bits[1])
+		return -EINVAL;
+	return 0;
+}
+
+static int rule_rate(struct snd_pcm_hw_params *params,
+		     struct snd_pcm_hw_rule *rule)
+{
+	struct snd_pcm_hardware *hw = rule->private;
+	struct snd_interval t;
+
+        t.min = hw->rate_min;
+        t.max = hw->rate_max;
+        t.openmin = t.openmax = 0;
+        t.integer = 0;
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int rule_channels(struct snd_pcm_hw_params *params,
+			 struct snd_pcm_hw_rule *rule)
+{
+	struct snd_pcm_hardware *hw = rule->private;
+	struct snd_interval t;
+
+        t.min = hw->channels_min;
+        t.max = hw->channels_max;
+        t.openmin = t.openmax = 0;
+        t.integer = 0;
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int loopback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct loopback *loopback = substream->private_data;
+	struct loopback_pcm *dpcm;
+	struct loopback_cable *cable;
+	int err = 0;
+	int dev = get_cable_index(substream);
+
+	mutex_lock(&loopback->cable_lock);
+	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
+	if (!dpcm) {
+		err = -ENOMEM;
+		goto unlock;
+	}
+	dpcm->loopback = loopback;
+	dpcm->substream = substream;
+	setup_timer(&dpcm->timer, loopback_timer_function,
+		    (unsigned long)dpcm);
+
+	cable = loopback->cables[substream->number][dev];
+	if (!cable) {
+		cable = kzalloc(sizeof(*cable), GFP_KERNEL);
+		if (!cable) {
+			kfree(dpcm);
+			err = -ENOMEM;
+			goto unlock;
+		}
+		spin_lock_init(&cable->lock);
+		cable->hw = loopback_pcm_hardware;
+		loopback->cables[substream->number][dev] = cable;
+	}
+	dpcm->cable = cable;
+	cable->streams[substream->stream] = dpcm;
+
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+	/* use dynamic rules based on actual runtime->hw values */
+	/* note that the default rules created in the PCM midlevel code */
+	/* are cached -> they do not reflect the actual state */
+	err = snd_pcm_hw_rule_add(runtime, 0,
+				  SNDRV_PCM_HW_PARAM_FORMAT,
+				  rule_format, &runtime->hw,
+				  SNDRV_PCM_HW_PARAM_FORMAT, -1);
+	if (err < 0)
+		goto unlock;
+	err = snd_pcm_hw_rule_add(runtime, 0,
+				  SNDRV_PCM_HW_PARAM_RATE,
+				  rule_rate, &runtime->hw,
+				  SNDRV_PCM_HW_PARAM_RATE, -1);
+	if (err < 0)
+		goto unlock;
+	err = snd_pcm_hw_rule_add(runtime, 0,
+				  SNDRV_PCM_HW_PARAM_CHANNELS,
+				  rule_channels, &runtime->hw,
+				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (err < 0)
+		goto unlock;
+
+	runtime->private_data = dpcm;
+	runtime->private_free = loopback_runtime_free;
+	if (get_notify(dpcm))
+		runtime->hw = loopback_pcm_hardware;
+	else
+		runtime->hw = cable->hw;
+ unlock:
+	mutex_unlock(&loopback->cable_lock);
+	return err;
+}
+
+static int loopback_close(struct snd_pcm_substream *substream)
+{
+	struct loopback *loopback = substream->private_data;
+	struct loopback_pcm *dpcm = substream->runtime->private_data;
+	struct loopback_cable *cable;
+	int dev = get_cable_index(substream);
+
+	loopback_timer_stop(dpcm);
+	mutex_lock(&loopback->cable_lock);
+	cable = loopback->cables[substream->number][dev];
+	if (cable->streams[!substream->stream]) {
+		/* other stream is still alive */
+		cable->streams[substream->stream] = NULL;
+	} else {
+		/* free the cable */
+		loopback->cables[substream->number][dev] = NULL;
+		kfree(cable);
+	}
+	mutex_unlock(&loopback->cable_lock);
+	return 0;
+}
+
+static struct snd_pcm_ops loopback_playback_ops = {
+	.open =		loopback_open,
+	.close =	loopback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	loopback_hw_params,
+	.hw_free =	loopback_hw_free,
+	.prepare =	loopback_prepare,
+	.trigger =	loopback_trigger,
+	.pointer =	loopback_pointer,
+};
+
+static struct snd_pcm_ops loopback_capture_ops = {
+	.open =		loopback_open,
+	.close =	loopback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	loopback_hw_params,
+	.hw_free =	loopback_hw_free,
+	.prepare =	loopback_prepare,
+	.trigger =	loopback_trigger,
+	.pointer =	loopback_pointer,
+};
+
+static int __devinit loopback_pcm_new(struct loopback *loopback,
+				      int device, int substreams)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(loopback->card, "Loopback PCM", device,
+			  substreams, substreams, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &loopback_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &loopback_capture_ops);
+
+	pcm->private_data = loopback;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "Loopback PCM");
+
+	loopback->pcm[device] = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+			snd_dma_continuous_data(GFP_KERNEL),
+			0, 2 * 1024 * 1024);
+	return 0;
+}
+
+static int loopback_rate_shift_info(struct snd_kcontrol *kcontrol,   
+				    struct snd_ctl_elem_info *uinfo) 
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 80000;
+	uinfo->value.integer.max = 120000;
+	uinfo->value.integer.step = 1;
+	return 0;
+}                                  
+
+static int loopback_rate_shift_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct loopback *loopback = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] =
+		loopback->setup[kcontrol->id.subdevice]
+			       [kcontrol->id.device].rate_shift;
+	return 0;
+}
+
+static int loopback_rate_shift_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct loopback *loopback = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change = 0;
+
+	val = ucontrol->value.integer.value[0];
+	if (val < 80000)
+		val = 80000;
+	if (val > 120000)
+		val = 120000;	
+	mutex_lock(&loopback->cable_lock);
+	if (val != loopback->setup[kcontrol->id.subdevice]
+				  [kcontrol->id.device].rate_shift) {
+		loopback->setup[kcontrol->id.subdevice]
+			       [kcontrol->id.device].rate_shift = val;
+		change = 1;
+	}
+	mutex_unlock(&loopback->cable_lock);
+	return change;
+}
+
+static int loopback_notify_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct loopback *loopback = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] =
+		loopback->setup[kcontrol->id.subdevice]
+			       [kcontrol->id.device].notify;
+	return 0;
+}
+
+static int loopback_notify_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct loopback *loopback = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change = 0;
+
+	val = ucontrol->value.integer.value[0] ? 1 : 0;
+	if (val != loopback->setup[kcontrol->id.subdevice]
+				[kcontrol->id.device].notify) {
+		loopback->setup[kcontrol->id.subdevice]
+			[kcontrol->id.device].notify = val;
+		change = 1;
+	}
+	return change;
+}
+
+static int loopback_active_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct loopback *loopback = snd_kcontrol_chip(kcontrol);
+	struct loopback_cable *cable = loopback->cables
+			[kcontrol->id.subdevice][kcontrol->id.device ^ 1];
+	unsigned int val = 0;
+
+	if (cable != NULL)
+		val = (cable->running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ?
+									1 : 0;
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int loopback_format_info(struct snd_kcontrol *kcontrol,   
+				struct snd_ctl_elem_info *uinfo) 
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = SNDRV_PCM_FORMAT_LAST;
+	uinfo->value.integer.step = 1;
+	return 0;
+}                                  
+
+static int loopback_format_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct loopback *loopback = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] =
+		loopback->setup[kcontrol->id.subdevice]
+			       [kcontrol->id.device].format;
+	return 0;
+}
+
+static int loopback_rate_info(struct snd_kcontrol *kcontrol,   
+			      struct snd_ctl_elem_info *uinfo) 
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 192000;
+	uinfo->value.integer.step = 1;
+	return 0;
+}                                  
+
+static int loopback_rate_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct loopback *loopback = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] =
+		loopback->setup[kcontrol->id.subdevice]
+			       [kcontrol->id.device].rate;
+	return 0;
+}
+
+static int loopback_channels_info(struct snd_kcontrol *kcontrol,   
+				  struct snd_ctl_elem_info *uinfo) 
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 1;
+	uinfo->value.integer.max = 1024;
+	uinfo->value.integer.step = 1;
+	return 0;
+}                                  
+
+static int loopback_channels_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct loopback *loopback = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] =
+		loopback->setup[kcontrol->id.subdevice]
+			       [kcontrol->id.device].channels;
+	return 0;
+}
+
+static struct snd_kcontrol_new loopback_controls[]  __devinitdata = {
+{
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "PCM Rate Shift 100000",
+	.info =         loopback_rate_shift_info,
+	.get =          loopback_rate_shift_get,
+	.put =          loopback_rate_shift_put,
+},
+{
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "PCM Notify",
+	.info =         snd_ctl_boolean_mono_info,
+	.get =          loopback_notify_get,
+	.put =          loopback_notify_put,
+},
+#define ACTIVE_IDX 2
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "PCM Slave Active",
+	.info =         snd_ctl_boolean_mono_info,
+	.get =          loopback_active_get,
+},
+#define FORMAT_IDX 3
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "PCM Slave Format",
+	.info =         loopback_format_info,
+	.get =          loopback_format_get
+},
+#define RATE_IDX 4
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "PCM Slave Rate",
+	.info =         loopback_rate_info,
+	.get =          loopback_rate_get
+},
+#define CHANNELS_IDX 5
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "PCM Slave Channels",
+	.info =         loopback_channels_info,
+	.get =          loopback_channels_get
+}
+};
+
+static int __devinit loopback_mixer_new(struct loopback *loopback, int notify)
+{
+	struct snd_card *card = loopback->card;
+	struct snd_pcm *pcm;
+	struct snd_kcontrol *kctl;
+	struct loopback_setup *setup;
+	int err, dev, substr, substr_count, idx;
+
+	strcpy(card->mixername, "Loopback Mixer");
+	for (dev = 0; dev < 2; dev++) {
+		pcm = loopback->pcm[dev];
+		substr_count =
+		    pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count;
+		for (substr = 0; substr < substr_count; substr++) {
+			setup = &loopback->setup[substr][dev];
+			setup->notify = notify;
+			setup->rate_shift = NO_PITCH;
+			setup->format = SNDRV_PCM_FORMAT_S16_LE;
+			setup->rate = 48000;
+			setup->channels = 2;
+			for (idx = 0; idx < ARRAY_SIZE(loopback_controls);
+									idx++) {
+				kctl = snd_ctl_new1(&loopback_controls[idx],
+						    loopback);
+				if (!kctl)
+					return -ENOMEM;
+				kctl->id.device = dev;
+				kctl->id.subdevice = substr;
+				switch (idx) {
+				case ACTIVE_IDX:
+					setup->active_id = kctl->id;
+					break;
+				case FORMAT_IDX:
+					setup->format_id = kctl->id;
+					break;
+				case RATE_IDX:
+					setup->rate_id = kctl->id;
+					break;
+				case CHANNELS_IDX:
+					setup->channels_id = kctl->id;
+					break;
+				default:
+					break;
+				}
+				err = snd_ctl_add(card, kctl);
+				if (err < 0)
+					return err;
+			}
+		}
+	}
+	return 0;
+}
+
+#ifdef CONFIG_PROC_FS
+
+static void print_dpcm_info(struct snd_info_buffer *buffer,
+			    struct loopback_pcm *dpcm,
+			    const char *id)
+{
+	snd_iprintf(buffer, "  %s\n", id);
+	if (dpcm == NULL) {
+		snd_iprintf(buffer, "    inactive\n");
+		return;
+	}
+	snd_iprintf(buffer, "    buffer_size:\t%u\n", dpcm->pcm_buffer_size);
+	snd_iprintf(buffer, "    buffer_pos:\t\t%u\n", dpcm->buf_pos);
+	snd_iprintf(buffer, "    silent_size:\t%u\n", dpcm->silent_size);
+	snd_iprintf(buffer, "    period_size:\t%u\n", dpcm->pcm_period_size);
+	snd_iprintf(buffer, "    bytes_per_sec:\t%u\n", dpcm->pcm_bps);
+	snd_iprintf(buffer, "    sample_align:\t%u\n", dpcm->pcm_salign);
+	snd_iprintf(buffer, "    rate_shift:\t\t%u\n", dpcm->pcm_rate_shift);
+	snd_iprintf(buffer, "    update_pending:\t%u\n",
+						dpcm->period_update_pending);
+	snd_iprintf(buffer, "    irq_pos:\t\t%u\n", dpcm->irq_pos);
+	snd_iprintf(buffer, "    period_frac:\t%u\n", dpcm->period_size_frac);
+	snd_iprintf(buffer, "    last_jiffies:\t%lu (%lu)\n",
+					dpcm->last_jiffies, jiffies);
+	snd_iprintf(buffer, "    timer_expires:\t%lu\n", dpcm->timer.expires);
+}
+
+static void print_substream_info(struct snd_info_buffer *buffer,
+				 struct loopback *loopback,
+				 int sub,
+				 int num)
+{
+	struct loopback_cable *cable = loopback->cables[sub][num];
+
+	snd_iprintf(buffer, "Cable %i substream %i:\n", num, sub);
+	if (cable == NULL) {
+		snd_iprintf(buffer, "  inactive\n");
+		return;
+	}
+	snd_iprintf(buffer, "  valid: %u\n", cable->valid);
+	snd_iprintf(buffer, "  running: %u\n", cable->running);
+	snd_iprintf(buffer, "  pause: %u\n", cable->pause);
+	print_dpcm_info(buffer, cable->streams[0], "Playback");
+	print_dpcm_info(buffer, cable->streams[1], "Capture");
+}
+
+static void print_cable_info(struct snd_info_entry *entry,
+			     struct snd_info_buffer *buffer)
+{
+	struct loopback *loopback = entry->private_data;
+	int sub, num;
+
+	mutex_lock(&loopback->cable_lock);
+	num = entry->name[strlen(entry->name)-1];
+	num = num == '0' ? 0 : 1;
+	for (sub = 0; sub < MAX_PCM_SUBSTREAMS; sub++)
+		print_substream_info(buffer, loopback, sub, num);
+	mutex_unlock(&loopback->cable_lock);
+}
+
+static int __devinit loopback_proc_new(struct loopback *loopback, int cidx)
+{
+	char name[32];
+	struct snd_info_entry *entry;
+	int err;
+
+	snprintf(name, sizeof(name), "cable#%d", cidx);
+	err = snd_card_proc_new(loopback->card, name, &entry);
+	if (err < 0)
+		return err;
+
+	snd_info_set_text_ops(entry, loopback, print_cable_info);
+	return 0;
+}
+
+#else /* !CONFIG_PROC_FS */
+
+#define loopback_proc_new(loopback, cidx) do { } while (0)
+
+#endif
+
+static int __devinit loopback_probe(struct platform_device *devptr)
+{
+	struct snd_card *card;
+	struct loopback *loopback;
+	int dev = devptr->id;
+	int err;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct loopback), &card);
+	if (err < 0)
+		return err;
+	loopback = card->private_data;
+
+	if (pcm_substreams[dev] < 1)
+		pcm_substreams[dev] = 1;
+	if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS)
+		pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
+	
+	loopback->card = card;
+	mutex_init(&loopback->cable_lock);
+
+	err = loopback_pcm_new(loopback, 0, pcm_substreams[dev]);
+	if (err < 0)
+		goto __nodev;
+	err = loopback_pcm_new(loopback, 1, pcm_substreams[dev]);
+	if (err < 0)
+		goto __nodev;
+	err = loopback_mixer_new(loopback, pcm_notify[dev] ? 1 : 0);
+	if (err < 0)
+		goto __nodev;
+	loopback_proc_new(loopback, 0);
+	loopback_proc_new(loopback, 1);
+	strcpy(card->driver, "Loopback");
+	strcpy(card->shortname, "Loopback");
+	sprintf(card->longname, "Loopback %i", dev + 1);
+	err = snd_card_register(card);
+	if (!err) {
+		platform_set_drvdata(devptr, card);
+		return 0;
+	}
+      __nodev:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit loopback_remove(struct platform_device *devptr)
+{
+	snd_card_free(platform_get_drvdata(devptr));
+	platform_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int loopback_suspend(struct platform_device *pdev,
+				pm_message_t state)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+	struct loopback *loopback = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+	snd_pcm_suspend_all(loopback->pcm[0]);
+	snd_pcm_suspend_all(loopback->pcm[1]);
+	return 0;
+}
+	
+static int loopback_resume(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+#define SND_LOOPBACK_DRIVER	"snd_aloop"
+
+static struct platform_driver loopback_driver = {
+	.probe		= loopback_probe,
+	.remove		= __devexit_p(loopback_remove),
+#ifdef CONFIG_PM
+	.suspend	= loopback_suspend,
+	.resume		= loopback_resume,
+#endif
+	.driver		= {
+		.name	= SND_LOOPBACK_DRIVER
+	},
+};
+
+static void loopback_unregister_all(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(devices); ++i)
+		platform_device_unregister(devices[i]);
+	platform_driver_unregister(&loopback_driver);
+}
+
+static int __init alsa_card_loopback_init(void)
+{
+	int i, err, cards;
+
+	err = platform_driver_register(&loopback_driver);
+	if (err < 0)
+		return err;
+
+
+	cards = 0;
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		struct platform_device *device;
+		if (!enable[i])
+			continue;
+		device = platform_device_register_simple(SND_LOOPBACK_DRIVER,
+							 i, NULL, 0);
+		if (IS_ERR(device))
+			continue;
+		if (!platform_get_drvdata(device)) {
+			platform_device_unregister(device);
+			continue;
+		}
+		devices[i] = device;
+		cards++;
+	}
+	if (!cards) {
+#ifdef MODULE
+		printk(KERN_ERR "aloop: No loopback enabled\n");
+#endif
+		loopback_unregister_all();
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_loopback_exit(void)
+{
+	loopback_unregister_all();
+}
+
+module_init(alsa_card_loopback_init)
+module_exit(alsa_card_loopback_exit)
diff --git a/linux/sound/drivers/dummy.c b/linux/sound/drivers/dummy.c
new file mode 100644
index 0000000..7f41990
--- /dev/null
+++ b/linux/sound/drivers/dummy.c
@@ -0,0 +1,1157 @@
+/*
+ *  Dummy soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/hrtimer.h>
+#include <linux/math64.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Dummy soundcard (/dev/null)");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ALSA,Dummy soundcard}}");
+
+#define MAX_PCM_DEVICES		4
+#define MAX_PCM_SUBSTREAMS	128
+#define MAX_MIDI_DEVICES	2
+
+/* defaults */
+#define MAX_BUFFER_SIZE		(64*1024)
+#define MIN_PERIOD_SIZE		64
+#define MAX_PERIOD_SIZE		MAX_BUFFER_SIZE
+#define USE_FORMATS 		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE)
+#define USE_RATE		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000
+#define USE_RATE_MIN		5500
+#define USE_RATE_MAX		48000
+#define USE_CHANNELS_MIN 	1
+#define USE_CHANNELS_MAX 	2
+#define USE_PERIODS_MIN 	1
+#define USE_PERIODS_MAX 	1024
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
+static char *model[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = NULL};
+static int pcm_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
+//static int midi_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+#ifdef CONFIG_HIGH_RES_TIMERS
+static int hrtimer = 1;
+#endif
+static int fake_buffer = 1;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for dummy soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for dummy soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable this dummy soundcard.");
+module_param_array(model, charp, NULL, 0444);
+MODULE_PARM_DESC(model, "Soundcard model.");
+module_param_array(pcm_devs, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_devs, "PCM devices # (0-4) for dummy driver.");
+module_param_array(pcm_substreams, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-128) for dummy driver.");
+//module_param_array(midi_devs, int, NULL, 0444);
+//MODULE_PARM_DESC(midi_devs, "MIDI devices # (0-2) for dummy driver.");
+module_param(fake_buffer, bool, 0444);
+MODULE_PARM_DESC(fake_buffer, "Fake buffer allocations.");
+#ifdef CONFIG_HIGH_RES_TIMERS
+module_param(hrtimer, bool, 0644);
+MODULE_PARM_DESC(hrtimer, "Use hrtimer as the timer source.");
+#endif
+
+static struct platform_device *devices[SNDRV_CARDS];
+
+#define MIXER_ADDR_MASTER	0
+#define MIXER_ADDR_LINE		1
+#define MIXER_ADDR_MIC		2
+#define MIXER_ADDR_SYNTH	3
+#define MIXER_ADDR_CD		4
+#define MIXER_ADDR_LAST		4
+
+struct dummy_timer_ops {
+	int (*create)(struct snd_pcm_substream *);
+	void (*free)(struct snd_pcm_substream *);
+	int (*prepare)(struct snd_pcm_substream *);
+	int (*start)(struct snd_pcm_substream *);
+	int (*stop)(struct snd_pcm_substream *);
+	snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *);
+};
+
+struct dummy_model {
+	const char *name;
+	int (*playback_constraints)(struct snd_pcm_runtime *runtime);
+	int (*capture_constraints)(struct snd_pcm_runtime *runtime);
+	u64 formats;
+	size_t buffer_bytes_max;
+	size_t period_bytes_min;
+	size_t period_bytes_max;
+	unsigned int periods_min;
+	unsigned int periods_max;
+	unsigned int rates;
+	unsigned int rate_min;
+	unsigned int rate_max;
+	unsigned int channels_min;
+	unsigned int channels_max;
+};
+
+struct snd_dummy {
+	struct snd_card *card;
+	struct dummy_model *model;
+	struct snd_pcm *pcm;
+	struct snd_pcm_hardware pcm_hw;
+	spinlock_t mixer_lock;
+	int mixer_volume[MIXER_ADDR_LAST+1][2];
+	int capture_source[MIXER_ADDR_LAST+1][2];
+	const struct dummy_timer_ops *timer_ops;
+};
+
+/*
+ * card models
+ */
+
+static int emu10k1_playback_constraints(struct snd_pcm_runtime *runtime)
+{
+	int err;
+	err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+struct dummy_model model_emu10k1 = {
+	.name = "emu10k1",
+	.playback_constraints = emu10k1_playback_constraints,
+	.buffer_bytes_max = 128 * 1024,
+};
+
+struct dummy_model model_rme9652 = {
+	.name = "rme9652",
+	.buffer_bytes_max = 26 * 64 * 1024,
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+	.channels_min = 26,
+	.channels_max = 26,
+	.periods_min = 2,
+	.periods_max = 2,
+};
+
+struct dummy_model model_ice1712 = {
+	.name = "ice1712",
+	.buffer_bytes_max = 256 * 1024,
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+	.channels_min = 10,
+	.channels_max = 10,
+	.periods_min = 1,
+	.periods_max = 1024,
+};
+
+struct dummy_model model_uda1341 = {
+	.name = "uda1341",
+	.buffer_bytes_max = 16380,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.channels_min = 2,
+	.channels_max = 2,
+	.periods_min = 2,
+	.periods_max = 255,
+};
+
+struct dummy_model model_ac97 = {
+	.name = "ac97",
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.channels_min = 2,
+	.channels_max = 2,
+	.rates = SNDRV_PCM_RATE_48000,
+	.rate_min = 48000,
+	.rate_max = 48000,
+};
+
+struct dummy_model model_ca0106 = {
+	.name = "ca0106",
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.buffer_bytes_max = ((65536-64)*8),
+	.period_bytes_max = (65536-64),
+	.periods_min = 2,
+	.periods_max = 8,
+	.channels_min = 2,
+	.channels_max = 2,
+	.rates = SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_96000|SNDRV_PCM_RATE_192000,
+	.rate_min = 48000,
+	.rate_max = 192000,
+};
+
+struct dummy_model *dummy_models[] = {
+	&model_emu10k1,
+	&model_rme9652,
+	&model_ice1712,
+	&model_uda1341,
+	&model_ac97,
+	&model_ca0106,
+	NULL
+};
+
+/*
+ * system timer interface
+ */
+
+struct dummy_systimer_pcm {
+	spinlock_t lock;
+	struct timer_list timer;
+	unsigned long base_time;
+	unsigned int frac_pos;	/* fractional sample position (based HZ) */
+	unsigned int frac_period_rest;
+	unsigned int frac_buffer_size;	/* buffer_size * HZ */
+	unsigned int frac_period_size;	/* period_size * HZ */
+	unsigned int rate;
+	int elapsed;
+	struct snd_pcm_substream *substream;
+};
+
+static void dummy_systimer_rearm(struct dummy_systimer_pcm *dpcm)
+{
+	dpcm->timer.expires = jiffies +
+		(dpcm->frac_period_rest + dpcm->rate - 1) / dpcm->rate;
+	add_timer(&dpcm->timer);
+}
+
+static void dummy_systimer_update(struct dummy_systimer_pcm *dpcm)
+{
+	unsigned long delta;
+
+	delta = jiffies - dpcm->base_time;
+	if (!delta)
+		return;
+	dpcm->base_time += delta;
+	delta *= dpcm->rate;
+	dpcm->frac_pos += delta;
+	while (dpcm->frac_pos >= dpcm->frac_buffer_size)
+		dpcm->frac_pos -= dpcm->frac_buffer_size;
+	while (dpcm->frac_period_rest <= delta) {
+		dpcm->elapsed++;
+		dpcm->frac_period_rest += dpcm->frac_period_size;
+	}
+	dpcm->frac_period_rest -= delta;
+}
+
+static int dummy_systimer_start(struct snd_pcm_substream *substream)
+{
+	struct dummy_systimer_pcm *dpcm = substream->runtime->private_data;
+	spin_lock(&dpcm->lock);
+	dpcm->base_time = jiffies;
+	dummy_systimer_rearm(dpcm);
+	spin_unlock(&dpcm->lock);
+	return 0;
+}
+
+static int dummy_systimer_stop(struct snd_pcm_substream *substream)
+{
+	struct dummy_systimer_pcm *dpcm = substream->runtime->private_data;
+	spin_lock(&dpcm->lock);
+	del_timer(&dpcm->timer);
+	spin_unlock(&dpcm->lock);
+	return 0;
+}
+
+static int dummy_systimer_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct dummy_systimer_pcm *dpcm = runtime->private_data;
+
+	dpcm->frac_pos = 0;
+	dpcm->rate = runtime->rate;
+	dpcm->frac_buffer_size = runtime->buffer_size * HZ;
+	dpcm->frac_period_size = runtime->period_size * HZ;
+	dpcm->frac_period_rest = dpcm->frac_period_size;
+	dpcm->elapsed = 0;
+
+	return 0;
+}
+
+static void dummy_systimer_callback(unsigned long data)
+{
+	struct dummy_systimer_pcm *dpcm = (struct dummy_systimer_pcm *)data;
+	unsigned long flags;
+	int elapsed = 0;
+	
+	spin_lock_irqsave(&dpcm->lock, flags);
+	dummy_systimer_update(dpcm);
+	dummy_systimer_rearm(dpcm);
+	elapsed = dpcm->elapsed;
+	dpcm->elapsed = 0;
+	spin_unlock_irqrestore(&dpcm->lock, flags);
+	if (elapsed)
+		snd_pcm_period_elapsed(dpcm->substream);
+}
+
+static snd_pcm_uframes_t
+dummy_systimer_pointer(struct snd_pcm_substream *substream)
+{
+	struct dummy_systimer_pcm *dpcm = substream->runtime->private_data;
+	snd_pcm_uframes_t pos;
+
+	spin_lock(&dpcm->lock);
+	dummy_systimer_update(dpcm);
+	pos = dpcm->frac_pos / HZ;
+	spin_unlock(&dpcm->lock);
+	return pos;
+}
+
+static int dummy_systimer_create(struct snd_pcm_substream *substream)
+{
+	struct dummy_systimer_pcm *dpcm;
+
+	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
+	if (!dpcm)
+		return -ENOMEM;
+	substream->runtime->private_data = dpcm;
+	init_timer(&dpcm->timer);
+	dpcm->timer.data = (unsigned long) dpcm;
+	dpcm->timer.function = dummy_systimer_callback;
+	spin_lock_init(&dpcm->lock);
+	dpcm->substream = substream;
+	return 0;
+}
+
+static void dummy_systimer_free(struct snd_pcm_substream *substream)
+{
+	kfree(substream->runtime->private_data);
+}
+
+static struct dummy_timer_ops dummy_systimer_ops = {
+	.create =	dummy_systimer_create,
+	.free =		dummy_systimer_free,
+	.prepare =	dummy_systimer_prepare,
+	.start =	dummy_systimer_start,
+	.stop =		dummy_systimer_stop,
+	.pointer =	dummy_systimer_pointer,
+};
+
+#ifdef CONFIG_HIGH_RES_TIMERS
+/*
+ * hrtimer interface
+ */
+
+struct dummy_hrtimer_pcm {
+	ktime_t base_time;
+	ktime_t period_time;
+	atomic_t running;
+	struct hrtimer timer;
+	struct tasklet_struct tasklet;
+	struct snd_pcm_substream *substream;
+};
+
+static void dummy_hrtimer_pcm_elapsed(unsigned long priv)
+{
+	struct dummy_hrtimer_pcm *dpcm = (struct dummy_hrtimer_pcm *)priv;
+	if (atomic_read(&dpcm->running))
+		snd_pcm_period_elapsed(dpcm->substream);
+}
+
+static enum hrtimer_restart dummy_hrtimer_callback(struct hrtimer *timer)
+{
+	struct dummy_hrtimer_pcm *dpcm;
+
+	dpcm = container_of(timer, struct dummy_hrtimer_pcm, timer);
+	if (!atomic_read(&dpcm->running))
+		return HRTIMER_NORESTART;
+	tasklet_schedule(&dpcm->tasklet);
+	hrtimer_forward_now(timer, dpcm->period_time);
+	return HRTIMER_RESTART;
+}
+
+static int dummy_hrtimer_start(struct snd_pcm_substream *substream)
+{
+	struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data;
+
+	dpcm->base_time = hrtimer_cb_get_time(&dpcm->timer);
+	hrtimer_start(&dpcm->timer, dpcm->period_time, HRTIMER_MODE_REL);
+	atomic_set(&dpcm->running, 1);
+	return 0;
+}
+
+static int dummy_hrtimer_stop(struct snd_pcm_substream *substream)
+{
+	struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data;
+
+	atomic_set(&dpcm->running, 0);
+	hrtimer_cancel(&dpcm->timer);
+	return 0;
+}
+
+static inline void dummy_hrtimer_sync(struct dummy_hrtimer_pcm *dpcm)
+{
+	tasklet_kill(&dpcm->tasklet);
+}
+
+static snd_pcm_uframes_t
+dummy_hrtimer_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct dummy_hrtimer_pcm *dpcm = runtime->private_data;
+	u64 delta;
+	u32 pos;
+
+	delta = ktime_us_delta(hrtimer_cb_get_time(&dpcm->timer),
+			       dpcm->base_time);
+	delta = div_u64(delta * runtime->rate + 999999, 1000000);
+	div_u64_rem(delta, runtime->buffer_size, &pos);
+	return pos;
+}
+
+static int dummy_hrtimer_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct dummy_hrtimer_pcm *dpcm = runtime->private_data;
+	unsigned int period, rate;
+	long sec;
+	unsigned long nsecs;
+
+	dummy_hrtimer_sync(dpcm);
+	period = runtime->period_size;
+	rate = runtime->rate;
+	sec = period / rate;
+	period %= rate;
+	nsecs = div_u64((u64)period * 1000000000UL + rate - 1, rate);
+	dpcm->period_time = ktime_set(sec, nsecs);
+
+	return 0;
+}
+
+static int dummy_hrtimer_create(struct snd_pcm_substream *substream)
+{
+	struct dummy_hrtimer_pcm *dpcm;
+
+	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
+	if (!dpcm)
+		return -ENOMEM;
+	substream->runtime->private_data = dpcm;
+	hrtimer_init(&dpcm->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	dpcm->timer.function = dummy_hrtimer_callback;
+	dpcm->substream = substream;
+	atomic_set(&dpcm->running, 0);
+	tasklet_init(&dpcm->tasklet, dummy_hrtimer_pcm_elapsed,
+		     (unsigned long)dpcm);
+	return 0;
+}
+
+static void dummy_hrtimer_free(struct snd_pcm_substream *substream)
+{
+	struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data;
+	dummy_hrtimer_sync(dpcm);
+	kfree(dpcm);
+}
+
+static struct dummy_timer_ops dummy_hrtimer_ops = {
+	.create =	dummy_hrtimer_create,
+	.free =		dummy_hrtimer_free,
+	.prepare =	dummy_hrtimer_prepare,
+	.start =	dummy_hrtimer_start,
+	.stop =		dummy_hrtimer_stop,
+	.pointer =	dummy_hrtimer_pointer,
+};
+
+#endif /* CONFIG_HIGH_RES_TIMERS */
+
+/*
+ * PCM interface
+ */
+
+static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_dummy *dummy = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		return dummy->timer_ops->start(substream);
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		return dummy->timer_ops->stop(substream);
+	}
+	return -EINVAL;
+}
+
+static int dummy_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_dummy *dummy = snd_pcm_substream_chip(substream);
+
+	return dummy->timer_ops->prepare(substream);
+}
+
+static snd_pcm_uframes_t dummy_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_dummy *dummy = snd_pcm_substream_chip(substream);
+
+	return dummy->timer_ops->pointer(substream);
+}
+
+static struct snd_pcm_hardware dummy_pcm_hardware = {
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		USE_FORMATS,
+	.rates =		USE_RATE,
+	.rate_min =		USE_RATE_MIN,
+	.rate_max =		USE_RATE_MAX,
+	.channels_min =		USE_CHANNELS_MIN,
+	.channels_max =		USE_CHANNELS_MAX,
+	.buffer_bytes_max =	MAX_BUFFER_SIZE,
+	.period_bytes_min =	MIN_PERIOD_SIZE,
+	.period_bytes_max =	MAX_PERIOD_SIZE,
+	.periods_min =		USE_PERIODS_MIN,
+	.periods_max =		USE_PERIODS_MAX,
+	.fifo_size =		0,
+};
+
+static int dummy_pcm_hw_params(struct snd_pcm_substream *substream,
+			       struct snd_pcm_hw_params *hw_params)
+{
+	if (fake_buffer) {
+		/* runtime->dma_bytes has to be set manually to allow mmap */
+		substream->runtime->dma_bytes = params_buffer_bytes(hw_params);
+		return 0;
+	}
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int dummy_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	if (fake_buffer)
+		return 0;
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int dummy_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_dummy *dummy = snd_pcm_substream_chip(substream);
+	struct dummy_model *model = dummy->model;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	dummy->timer_ops = &dummy_systimer_ops;
+#ifdef CONFIG_HIGH_RES_TIMERS
+	if (hrtimer)
+		dummy->timer_ops = &dummy_hrtimer_ops;
+#endif
+
+	err = dummy->timer_ops->create(substream);
+	if (err < 0)
+		return err;
+
+	runtime->hw = dummy->pcm_hw;
+	if (substream->pcm->device & 1) {
+		runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED;
+		runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;
+	}
+	if (substream->pcm->device & 2)
+		runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP |
+				      SNDRV_PCM_INFO_MMAP_VALID);
+
+	if (model == NULL)
+		return 0;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (model->playback_constraints)
+			err = model->playback_constraints(substream->runtime);
+	} else {
+		if (model->capture_constraints)
+			err = model->capture_constraints(substream->runtime);
+	}
+	if (err < 0) {
+		dummy->timer_ops->free(substream);
+		return err;
+	}
+	return 0;
+}
+
+static int dummy_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_dummy *dummy = snd_pcm_substream_chip(substream);
+	dummy->timer_ops->free(substream);
+	return 0;
+}
+
+/*
+ * dummy buffer handling
+ */
+
+static void *dummy_page[2];
+
+static void free_fake_buffer(void)
+{
+	if (fake_buffer) {
+		int i;
+		for (i = 0; i < 2; i++)
+			if (dummy_page[i]) {
+				free_page((unsigned long)dummy_page[i]);
+				dummy_page[i] = NULL;
+			}
+	}
+}
+
+static int alloc_fake_buffer(void)
+{
+	int i;
+
+	if (!fake_buffer)
+		return 0;
+	for (i = 0; i < 2; i++) {
+		dummy_page[i] = (void *)get_zeroed_page(GFP_KERNEL);
+		if (!dummy_page[i]) {
+			free_fake_buffer();
+			return -ENOMEM;
+		}
+	}
+	return 0;
+}
+
+static int dummy_pcm_copy(struct snd_pcm_substream *substream,
+			  int channel, snd_pcm_uframes_t pos,
+			  void __user *dst, snd_pcm_uframes_t count)
+{
+	return 0; /* do nothing */
+}
+
+static int dummy_pcm_silence(struct snd_pcm_substream *substream,
+			     int channel, snd_pcm_uframes_t pos,
+			     snd_pcm_uframes_t count)
+{
+	return 0; /* do nothing */
+}
+
+static struct page *dummy_pcm_page(struct snd_pcm_substream *substream,
+				   unsigned long offset)
+{
+	return virt_to_page(dummy_page[substream->stream]); /* the same page */
+}
+
+static struct snd_pcm_ops dummy_pcm_ops = {
+	.open =		dummy_pcm_open,
+	.close =	dummy_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	dummy_pcm_hw_params,
+	.hw_free =	dummy_pcm_hw_free,
+	.prepare =	dummy_pcm_prepare,
+	.trigger =	dummy_pcm_trigger,
+	.pointer =	dummy_pcm_pointer,
+};
+
+static struct snd_pcm_ops dummy_pcm_ops_no_buf = {
+	.open =		dummy_pcm_open,
+	.close =	dummy_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	dummy_pcm_hw_params,
+	.hw_free =	dummy_pcm_hw_free,
+	.prepare =	dummy_pcm_prepare,
+	.trigger =	dummy_pcm_trigger,
+	.pointer =	dummy_pcm_pointer,
+	.copy =		dummy_pcm_copy,
+	.silence =	dummy_pcm_silence,
+	.page =		dummy_pcm_page,
+};
+
+static int __devinit snd_card_dummy_pcm(struct snd_dummy *dummy, int device,
+					int substreams)
+{
+	struct snd_pcm *pcm;
+	struct snd_pcm_ops *ops;
+	int err;
+
+	err = snd_pcm_new(dummy->card, "Dummy PCM", device,
+			       substreams, substreams, &pcm);
+	if (err < 0)
+		return err;
+	dummy->pcm = pcm;
+	if (fake_buffer)
+		ops = &dummy_pcm_ops_no_buf;
+	else
+		ops = &dummy_pcm_ops;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, ops);
+	pcm->private_data = dummy;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "Dummy PCM");
+	if (!fake_buffer) {
+		snd_pcm_lib_preallocate_pages_for_all(pcm,
+			SNDRV_DMA_TYPE_CONTINUOUS,
+			snd_dma_continuous_data(GFP_KERNEL),
+			0, 64*1024);
+	}
+	return 0;
+}
+
+/*
+ * mixer interface
+ */
+
+#define DUMMY_VOLUME(xname, xindex, addr) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .name = xname, .index = xindex, \
+  .info = snd_dummy_volume_info, \
+  .get = snd_dummy_volume_get, .put = snd_dummy_volume_put, \
+  .private_value = addr, \
+  .tlv = { .p = db_scale_dummy } }
+
+static int snd_dummy_volume_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = -50;
+	uinfo->value.integer.max = 100;
+	return 0;
+}
+ 
+static int snd_dummy_volume_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol);
+	int addr = kcontrol->private_value;
+
+	spin_lock_irq(&dummy->mixer_lock);
+	ucontrol->value.integer.value[0] = dummy->mixer_volume[addr][0];
+	ucontrol->value.integer.value[1] = dummy->mixer_volume[addr][1];
+	spin_unlock_irq(&dummy->mixer_lock);
+	return 0;
+}
+
+static int snd_dummy_volume_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol);
+	int change, addr = kcontrol->private_value;
+	int left, right;
+
+	left = ucontrol->value.integer.value[0];
+	if (left < -50)
+		left = -50;
+	if (left > 100)
+		left = 100;
+	right = ucontrol->value.integer.value[1];
+	if (right < -50)
+		right = -50;
+	if (right > 100)
+		right = 100;
+	spin_lock_irq(&dummy->mixer_lock);
+	change = dummy->mixer_volume[addr][0] != left ||
+	         dummy->mixer_volume[addr][1] != right;
+	dummy->mixer_volume[addr][0] = left;
+	dummy->mixer_volume[addr][1] = right;
+	spin_unlock_irq(&dummy->mixer_lock);
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_dummy, -4500, 30, 0);
+
+#define DUMMY_CAPSRC(xname, xindex, addr) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_dummy_capsrc_info, \
+  .get = snd_dummy_capsrc_get, .put = snd_dummy_capsrc_put, \
+  .private_value = addr }
+
+#define snd_dummy_capsrc_info	snd_ctl_boolean_stereo_info
+ 
+static int snd_dummy_capsrc_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol);
+	int addr = kcontrol->private_value;
+
+	spin_lock_irq(&dummy->mixer_lock);
+	ucontrol->value.integer.value[0] = dummy->capture_source[addr][0];
+	ucontrol->value.integer.value[1] = dummy->capture_source[addr][1];
+	spin_unlock_irq(&dummy->mixer_lock);
+	return 0;
+}
+
+static int snd_dummy_capsrc_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol);
+	int change, addr = kcontrol->private_value;
+	int left, right;
+
+	left = ucontrol->value.integer.value[0] & 1;
+	right = ucontrol->value.integer.value[1] & 1;
+	spin_lock_irq(&dummy->mixer_lock);
+	change = dummy->capture_source[addr][0] != left &&
+	         dummy->capture_source[addr][1] != right;
+	dummy->capture_source[addr][0] = left;
+	dummy->capture_source[addr][1] = right;
+	spin_unlock_irq(&dummy->mixer_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_dummy_controls[] = {
+DUMMY_VOLUME("Master Volume", 0, MIXER_ADDR_MASTER),
+DUMMY_CAPSRC("Master Capture Switch", 0, MIXER_ADDR_MASTER),
+DUMMY_VOLUME("Synth Volume", 0, MIXER_ADDR_SYNTH),
+DUMMY_CAPSRC("Synth Capture Switch", 0, MIXER_ADDR_SYNTH),
+DUMMY_VOLUME("Line Volume", 0, MIXER_ADDR_LINE),
+DUMMY_CAPSRC("Line Capture Switch", 0, MIXER_ADDR_LINE),
+DUMMY_VOLUME("Mic Volume", 0, MIXER_ADDR_MIC),
+DUMMY_CAPSRC("Mic Capture Switch", 0, MIXER_ADDR_MIC),
+DUMMY_VOLUME("CD Volume", 0, MIXER_ADDR_CD),
+DUMMY_CAPSRC("CD Capture Switch", 0, MIXER_ADDR_CD)
+};
+
+static int __devinit snd_card_dummy_new_mixer(struct snd_dummy *dummy)
+{
+	struct snd_card *card = dummy->card;
+	unsigned int idx;
+	int err;
+
+	spin_lock_init(&dummy->mixer_lock);
+	strcpy(card->mixername, "Dummy Mixer");
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_dummy_controls); idx++) {
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_dummy_controls[idx], dummy));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_PROC_FS)
+/*
+ * proc interface
+ */
+static void print_formats(struct snd_dummy *dummy,
+			  struct snd_info_buffer *buffer)
+{
+	int i;
+
+	for (i = 0; i < SNDRV_PCM_FORMAT_LAST; i++) {
+		if (dummy->pcm_hw.formats & (1ULL << i))
+			snd_iprintf(buffer, " %s", snd_pcm_format_name(i));
+	}
+}
+
+static void print_rates(struct snd_dummy *dummy,
+			struct snd_info_buffer *buffer)
+{
+	static int rates[] = {
+		5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000,
+		64000, 88200, 96000, 176400, 192000,
+	};
+	int i;
+
+	if (dummy->pcm_hw.rates & SNDRV_PCM_RATE_CONTINUOUS)
+		snd_iprintf(buffer, " continuous");
+	if (dummy->pcm_hw.rates & SNDRV_PCM_RATE_KNOT)
+		snd_iprintf(buffer, " knot");
+	for (i = 0; i < ARRAY_SIZE(rates); i++)
+		if (dummy->pcm_hw.rates & (1 << i))
+			snd_iprintf(buffer, " %d", rates[i]);
+}
+
+#define get_dummy_int_ptr(dummy, ofs) \
+	(unsigned int *)((char *)&((dummy)->pcm_hw) + (ofs))
+#define get_dummy_ll_ptr(dummy, ofs) \
+	(unsigned long long *)((char *)&((dummy)->pcm_hw) + (ofs))
+
+struct dummy_hw_field {
+	const char *name;
+	const char *format;
+	unsigned int offset;
+	unsigned int size;
+};
+#define FIELD_ENTRY(item, fmt) {		   \
+	.name = #item,				   \
+	.format = fmt,				   \
+	.offset = offsetof(struct snd_pcm_hardware, item), \
+	.size = sizeof(dummy_pcm_hardware.item) }
+
+static struct dummy_hw_field fields[] = {
+	FIELD_ENTRY(formats, "%#llx"),
+	FIELD_ENTRY(rates, "%#x"),
+	FIELD_ENTRY(rate_min, "%d"),
+	FIELD_ENTRY(rate_max, "%d"),
+	FIELD_ENTRY(channels_min, "%d"),
+	FIELD_ENTRY(channels_max, "%d"),
+	FIELD_ENTRY(buffer_bytes_max, "%ld"),
+	FIELD_ENTRY(period_bytes_min, "%ld"),
+	FIELD_ENTRY(period_bytes_max, "%ld"),
+	FIELD_ENTRY(periods_min, "%d"),
+	FIELD_ENTRY(periods_max, "%d"),
+};
+
+static void dummy_proc_read(struct snd_info_entry *entry,
+			    struct snd_info_buffer *buffer)
+{
+	struct snd_dummy *dummy = entry->private_data;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(fields); i++) {
+		snd_iprintf(buffer, "%s ", fields[i].name);
+		if (fields[i].size == sizeof(int))
+			snd_iprintf(buffer, fields[i].format,
+				*get_dummy_int_ptr(dummy, fields[i].offset));
+		else
+			snd_iprintf(buffer, fields[i].format,
+				*get_dummy_ll_ptr(dummy, fields[i].offset));
+		if (!strcmp(fields[i].name, "formats"))
+			print_formats(dummy, buffer);
+		else if (!strcmp(fields[i].name, "rates"))
+			print_rates(dummy, buffer);
+		snd_iprintf(buffer, "\n");
+	}
+}
+
+static void dummy_proc_write(struct snd_info_entry *entry,
+			     struct snd_info_buffer *buffer)
+{
+	struct snd_dummy *dummy = entry->private_data;
+	char line[64];
+
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		char item[20];
+		const char *ptr;
+		unsigned long long val;
+		int i;
+
+		ptr = snd_info_get_str(item, line, sizeof(item));
+		for (i = 0; i < ARRAY_SIZE(fields); i++) {
+			if (!strcmp(item, fields[i].name))
+				break;
+		}
+		if (i >= ARRAY_SIZE(fields))
+			continue;
+		snd_info_get_str(item, ptr, sizeof(item));
+		if (strict_strtoull(item, 0, &val))
+			continue;
+		if (fields[i].size == sizeof(int))
+			*get_dummy_int_ptr(dummy, fields[i].offset) = val;
+		else
+			*get_dummy_ll_ptr(dummy, fields[i].offset) = val;
+	}
+}
+
+static void __devinit dummy_proc_init(struct snd_dummy *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(chip->card, "dummy_pcm", &entry)) {
+		snd_info_set_text_ops(entry, chip, dummy_proc_read);
+		entry->c.text.write = dummy_proc_write;
+		entry->mode |= S_IWUSR;
+		entry->private_data = chip;
+	}
+}
+#else
+#define dummy_proc_init(x)
+#endif /* CONFIG_SND_DEBUG && CONFIG_PROC_FS */
+
+static int __devinit snd_dummy_probe(struct platform_device *devptr)
+{
+	struct snd_card *card;
+	struct snd_dummy *dummy;
+	struct dummy_model *m = NULL, **mdl;
+	int idx, err;
+	int dev = devptr->id;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_dummy), &card);
+	if (err < 0)
+		return err;
+	dummy = card->private_data;
+	dummy->card = card;
+	for (mdl = dummy_models; *mdl && model[dev]; mdl++) {
+		if (strcmp(model[dev], (*mdl)->name) == 0) {
+			printk(KERN_INFO
+				"snd-dummy: Using model '%s' for card %i\n",
+				(*mdl)->name, card->number);
+			m = dummy->model = *mdl;
+			break;
+		}
+	}
+	for (idx = 0; idx < MAX_PCM_DEVICES && idx < pcm_devs[dev]; idx++) {
+		if (pcm_substreams[dev] < 1)
+			pcm_substreams[dev] = 1;
+		if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS)
+			pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
+		err = snd_card_dummy_pcm(dummy, idx, pcm_substreams[dev]);
+		if (err < 0)
+			goto __nodev;
+	}
+
+	dummy->pcm_hw = dummy_pcm_hardware;
+	if (m) {
+		if (m->formats)
+			dummy->pcm_hw.formats = m->formats;
+		if (m->buffer_bytes_max)
+			dummy->pcm_hw.buffer_bytes_max = m->buffer_bytes_max;
+		if (m->period_bytes_min)
+			dummy->pcm_hw.period_bytes_min = m->period_bytes_min;
+		if (m->period_bytes_max)
+			dummy->pcm_hw.period_bytes_max = m->period_bytes_max;
+		if (m->periods_min)
+			dummy->pcm_hw.periods_min = m->periods_min;
+		if (m->periods_max)
+			dummy->pcm_hw.periods_max = m->periods_max;
+		if (m->rates)
+			dummy->pcm_hw.rates = m->rates;
+		if (m->rate_min)
+			dummy->pcm_hw.rate_min = m->rate_min;
+		if (m->rate_max)
+			dummy->pcm_hw.rate_max = m->rate_max;
+		if (m->channels_min)
+			dummy->pcm_hw.channels_min = m->channels_min;
+		if (m->channels_max)
+			dummy->pcm_hw.channels_max = m->channels_max;
+	}
+
+	err = snd_card_dummy_new_mixer(dummy);
+	if (err < 0)
+		goto __nodev;
+	strcpy(card->driver, "Dummy");
+	strcpy(card->shortname, "Dummy");
+	sprintf(card->longname, "Dummy %i", dev + 1);
+
+	dummy_proc_init(dummy);
+
+	snd_card_set_dev(card, &devptr->dev);
+
+	err = snd_card_register(card);
+	if (err == 0) {
+		platform_set_drvdata(devptr, card);
+		return 0;
+	}
+      __nodev:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit snd_dummy_remove(struct platform_device *devptr)
+{
+	snd_card_free(platform_get_drvdata(devptr));
+	platform_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_dummy_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+	struct snd_dummy *dummy = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(dummy->pcm);
+	return 0;
+}
+	
+static int snd_dummy_resume(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+#define SND_DUMMY_DRIVER	"snd_dummy"
+
+static struct platform_driver snd_dummy_driver = {
+	.probe		= snd_dummy_probe,
+	.remove		= __devexit_p(snd_dummy_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_dummy_suspend,
+	.resume		= snd_dummy_resume,
+#endif
+	.driver		= {
+		.name	= SND_DUMMY_DRIVER
+	},
+};
+
+static void snd_dummy_unregister_all(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(devices); ++i)
+		platform_device_unregister(devices[i]);
+	platform_driver_unregister(&snd_dummy_driver);
+	free_fake_buffer();
+}
+
+static int __init alsa_card_dummy_init(void)
+{
+	int i, cards, err;
+
+	err = platform_driver_register(&snd_dummy_driver);
+	if (err < 0)
+		return err;
+
+	err = alloc_fake_buffer();
+	if (err < 0) {
+		platform_driver_unregister(&snd_dummy_driver);
+		return err;
+	}
+
+	cards = 0;
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		struct platform_device *device;
+		if (! enable[i])
+			continue;
+		device = platform_device_register_simple(SND_DUMMY_DRIVER,
+							 i, NULL, 0);
+		if (IS_ERR(device))
+			continue;
+		if (!platform_get_drvdata(device)) {
+			platform_device_unregister(device);
+			continue;
+		}
+		devices[i] = device;
+		cards++;
+	}
+	if (!cards) {
+#ifdef MODULE
+		printk(KERN_ERR "Dummy soundcard not found or device busy\n");
+#endif
+		snd_dummy_unregister_all();
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_dummy_exit(void)
+{
+	snd_dummy_unregister_all();
+}
+
+module_init(alsa_card_dummy_init)
+module_exit(alsa_card_dummy_exit)
diff --git a/linux/sound/drivers/ml403-ac97cr.c b/linux/sound/drivers/ml403-ac97cr.c
new file mode 100644
index 0000000..a1282c1
--- /dev/null
+++ b/linux/sound/drivers/ml403-ac97cr.c
@@ -0,0 +1,1355 @@
+/*
+ * ALSA driver for Xilinx ML403 AC97 Controller Reference
+ *   IP: opb_ac97_controller_ref_v1_00_a (EDK 8.1i)
+ *   IP: opb_ac97_controller_ref_v1_00_a (EDK 9.1i)
+ *
+ *  Copyright (c) by 2007  Joachim Foerster <JOFT@gmx.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
+ *
+ */
+
+/* Some notes / status of this driver:
+ *
+ * - Don't wonder about some strange implementations of things - especially the
+ * (heavy) shadowing of codec registers, with which I tried to reduce read
+ * accesses to a minimum, because after a variable amount of accesses, the AC97
+ * controller doesn't raise the register access finished bit anymore ...
+ *
+ * - Playback support seems to be pretty stable - no issues here.
+ * - Capture support "works" now, too. Overruns don't happen any longer so often.
+ *   But there might still be some ...
+ */
+
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+
+#include <linux/platform_device.h>
+
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+
+/* HZ */
+#include <linux/param.h>
+/* jiffies, time_*() */
+#include <linux/jiffies.h>
+/* schedule_timeout*() */
+#include <linux/sched.h>
+/* spin_lock*() */
+#include <linux/spinlock.h>
+/* struct mutex, mutex_init(), mutex_*lock() */
+#include <linux/mutex.h>
+
+/* snd_printk(), snd_printd() */
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/ac97_codec.h>
+
+#include "pcm-indirect2.h"
+
+
+#define SND_ML403_AC97CR_DRIVER "ml403-ac97cr"
+
+MODULE_AUTHOR("Joachim Foerster <JOFT@gmx.de>");
+MODULE_DESCRIPTION("Xilinx ML403 AC97 Controller Reference");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Xilinx,ML403 AC97 Controller Reference}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ML403 AC97 Controller Reference.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ML403 AC97 Controller Reference.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable this ML403 AC97 Controller Reference.");
+
+/* Special feature options */
+/*#define CODEC_WRITE_CHECK_RAF*/ /* don't return after a write to a codec
+				   * register, while RAF bit is not set
+				   */
+/* Debug options for code which may be removed completely in a final version */
+#ifdef CONFIG_SND_DEBUG
+/*#define CODEC_STAT*/            /* turn on some minimal "statistics"
+				   * about codec register usage
+				   */
+#define SND_PCM_INDIRECT2_STAT    /* turn on some "statistics" about the
+				   * process of copying bytes from the
+				   * intermediate buffer to the hardware
+				   * fifo and the other way round
+				   */
+#endif
+
+/* Definition of a "level/facility dependent" printk(); may be removed
+ * completely in a final version
+ */
+#undef PDEBUG
+#ifdef CONFIG_SND_DEBUG
+/* "facilities" for PDEBUG */
+#define UNKNOWN       (1<<0)
+#define CODEC_SUCCESS (1<<1)
+#define CODEC_FAKE    (1<<2)
+#define INIT_INFO     (1<<3)
+#define INIT_FAILURE  (1<<4)
+#define WORK_INFO     (1<<5)
+#define WORK_FAILURE  (1<<6)
+
+#define PDEBUG_FACILITIES (UNKNOWN | INIT_FAILURE | WORK_FAILURE)
+
+#define PDEBUG(fac, fmt, args...) do { \
+		if (fac & PDEBUG_FACILITIES) \
+			snd_printd(KERN_DEBUG SND_ML403_AC97CR_DRIVER ": " \
+				   fmt, ##args); \
+	} while (0)
+#else
+#define PDEBUG(fac, fmt, args...) /* nothing */
+#endif
+
+
+
+/* Defines for "waits"/timeouts (portions of HZ=250 on arch/ppc by default) */
+#define CODEC_TIMEOUT_ON_INIT       5	/* timeout for checking for codec
+					 * readiness (after insmod)
+					 */
+#ifndef CODEC_WRITE_CHECK_RAF
+#define CODEC_WAIT_AFTER_WRITE    100	/* general, static wait after a write
+					 * access to a codec register, may be
+					 * 0 to completely remove wait
+					 */
+#else
+#define CODEC_TIMEOUT_AFTER_WRITE   5	/* timeout after a write access to a
+					 * codec register, if RAF bit is used
+					 */
+#endif
+#define CODEC_TIMEOUT_AFTER_READ    5	/* timeout after a read access to a
+					 * codec register (checking RAF bit)
+					 */
+
+/* Infrastructure for codec register shadowing */
+#define LM4550_REG_OK        (1<<0)   /* register exists */
+#define LM4550_REG_DONEREAD  (1<<1)   /* read register once, value should be
+				       * the same currently in the register
+				       */
+#define LM4550_REG_NOSAVE    (1<<2)   /* values written to this register will
+				       * not be saved in the register
+				       */
+#define LM4550_REG_NOSHADOW  (1<<3)   /* don't do register shadowing, use plain
+				       * hardware access
+				       */
+#define LM4550_REG_READONLY  (1<<4)   /* register is read only */
+#define LM4550_REG_FAKEPROBE (1<<5)   /* fake write _and_ read actions during
+				       * probe() correctly
+				       */
+#define LM4550_REG_FAKEREAD  (1<<6)   /* fake read access, always return
+				       * default value
+				       */
+#define LM4550_REG_ALLFAKE   (LM4550_REG_FAKEREAD | LM4550_REG_FAKEPROBE)
+
+struct lm4550_reg {
+	u16 value;
+	u16 flag;
+	u16 wmask;
+	u16 def;
+};
+
+struct lm4550_reg lm4550_regfile[64] = {
+	[AC97_RESET / 2]              = {.flag = LM4550_REG_OK \
+						| LM4550_REG_NOSAVE \
+						| LM4550_REG_FAKEREAD,
+					 .def = 0x0D50},
+	[AC97_MASTER / 2]             = {.flag = LM4550_REG_OK
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x9F1F,
+					 .def = 0x8000},
+	[AC97_HEADPHONE / 2]          = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x9F1F,
+					 .def = 0x8000},
+	[AC97_MASTER_MONO / 2]        = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x801F,
+					 .def = 0x8000},
+	[AC97_PC_BEEP / 2]            = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x801E,
+					 .def = 0x0},
+	[AC97_PHONE / 2]              = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x801F,
+					 .def = 0x8008},
+	[AC97_MIC / 2]                = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x805F,
+					 .def = 0x8008},
+	[AC97_LINE / 2]               = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x9F1F,
+					 .def = 0x8808},
+	[AC97_CD / 2]                 = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x9F1F,
+					 .def = 0x8808},
+	[AC97_VIDEO / 2]              = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x9F1F,
+					 .def = 0x8808},
+	[AC97_AUX / 2]                = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x9F1F,
+					 .def = 0x8808},
+	[AC97_PCM / 2]                = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x9F1F,
+					 .def = 0x8008},
+	[AC97_REC_SEL / 2]            = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x707,
+					 .def = 0x0},
+	[AC97_REC_GAIN / 2]           = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .wmask = 0x8F0F,
+					 .def = 0x8000},
+	[AC97_GENERAL_PURPOSE / 2]    = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .def = 0x0,
+					 .wmask = 0xA380},
+	[AC97_3D_CONTROL / 2]         = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEREAD \
+						| LM4550_REG_READONLY,
+					 .def = 0x0101},
+	[AC97_POWERDOWN / 2]          = {.flag = LM4550_REG_OK \
+						| LM4550_REG_NOSHADOW \
+						| LM4550_REG_NOSAVE,
+					 .wmask = 0xFF00},
+					/* may not write ones to
+					 * REF/ANL/DAC/ADC bits
+					 * FIXME: Is this ok?
+					 */
+	[AC97_EXTENDED_ID / 2]        = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEREAD \
+						| LM4550_REG_READONLY,
+					 .def = 0x0201}, /* primary codec */
+	[AC97_EXTENDED_STATUS / 2]    = {.flag = LM4550_REG_OK \
+						| LM4550_REG_NOSHADOW \
+						| LM4550_REG_NOSAVE,
+					 .wmask = 0x1},
+	[AC97_PCM_FRONT_DAC_RATE / 2] = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .def = 0xBB80,
+					 .wmask = 0xFFFF},
+	[AC97_PCM_LR_ADC_RATE / 2]    = {.flag = LM4550_REG_OK \
+						| LM4550_REG_FAKEPROBE,
+					 .def = 0xBB80,
+					 .wmask = 0xFFFF},
+	[AC97_VENDOR_ID1 / 2]         = {.flag = LM4550_REG_OK \
+						| LM4550_REG_READONLY \
+						| LM4550_REG_FAKEREAD,
+					 .def = 0x4E53},
+	[AC97_VENDOR_ID2 / 2]         = {.flag = LM4550_REG_OK \
+						| LM4550_REG_READONLY \
+						| LM4550_REG_FAKEREAD,
+					 .def = 0x4350}
+};
+
+#define LM4550_RF_OK(reg)    (lm4550_regfile[reg / 2].flag & LM4550_REG_OK)
+
+static void lm4550_regfile_init(void)
+{
+	int i;
+	for (i = 0; i < 64; i++)
+		if (lm4550_regfile[i].flag & LM4550_REG_FAKEPROBE)
+			lm4550_regfile[i].value = lm4550_regfile[i].def;
+}
+
+static void lm4550_regfile_write_values_after_init(struct snd_ac97 *ac97)
+{
+	int i;
+	for (i = 0; i < 64; i++)
+		if ((lm4550_regfile[i].flag & LM4550_REG_FAKEPROBE) &&
+		    (lm4550_regfile[i].value != lm4550_regfile[i].def)) {
+			PDEBUG(CODEC_FAKE, "lm4550_regfile_write_values_after_"
+			       "init(): reg=0x%x value=0x%x / %d is different "
+			       "from def=0x%x / %d\n",
+			       i, lm4550_regfile[i].value,
+			       lm4550_regfile[i].value, lm4550_regfile[i].def,
+			       lm4550_regfile[i].def);
+			snd_ac97_write(ac97, i * 2, lm4550_regfile[i].value);
+			lm4550_regfile[i].flag |= LM4550_REG_DONEREAD;
+		}
+}
+
+
+/* direct registers */
+#define CR_REG(ml403_ac97cr, x) ((ml403_ac97cr)->port + CR_REG_##x)
+
+#define CR_REG_PLAYFIFO         0x00
+#define   CR_PLAYDATA(a)        ((a) & 0xFFFF)
+
+#define CR_REG_RECFIFO          0x04
+#define   CR_RECDATA(a)         ((a) & 0xFFFF)
+
+#define CR_REG_STATUS           0x08
+#define   CR_RECOVER            (1<<7)
+#define   CR_PLAYUNDER          (1<<6)
+#define   CR_CODECREADY         (1<<5)
+#define   CR_RAF                (1<<4)
+#define   CR_RECEMPTY           (1<<3)
+#define   CR_RECFULL            (1<<2)
+#define   CR_PLAYHALF           (1<<1)
+#define   CR_PLAYFULL           (1<<0)
+
+#define CR_REG_RESETFIFO        0x0C
+#define   CR_RECRESET           (1<<1)
+#define   CR_PLAYRESET          (1<<0)
+
+#define CR_REG_CODEC_ADDR       0x10
+/* UG082 says:
+ * #define   CR_CODEC_ADDR(a)  ((a) << 1)
+ * #define   CR_CODEC_READ     (1<<0)
+ * #define   CR_CODEC_WRITE    (0<<0)
+ */
+/* RefDesign example says: */
+#define   CR_CODEC_ADDR(a)      ((a) << 0)
+#define   CR_CODEC_READ         (1<<7)
+#define   CR_CODEC_WRITE        (0<<7)
+
+#define CR_REG_CODEC_DATAREAD   0x14
+#define   CR_CODEC_DATAREAD(v)  ((v) & 0xFFFF)
+
+#define CR_REG_CODEC_DATAWRITE  0x18
+#define   CR_CODEC_DATAWRITE(v) ((v) & 0xFFFF)
+
+#define CR_FIFO_SIZE            32
+
+struct snd_ml403_ac97cr {
+	/* lock for access to (controller) registers */
+	spinlock_t reg_lock;
+	/* mutex for the whole sequence of accesses to (controller) registers
+	 * which affect codec registers
+	 */
+	struct mutex cdc_mutex;
+
+	int irq; /* for playback */
+	int enable_irq;	/* for playback */
+
+	int capture_irq;
+	int enable_capture_irq;
+
+	struct resource *res_port;
+	void *port;
+
+	struct snd_ac97 *ac97;
+	int ac97_fake;
+#ifdef CODEC_STAT
+	int ac97_read;
+	int ac97_write;
+#endif
+
+	struct platform_device *pfdev;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *playback_substream;
+	struct snd_pcm_substream *capture_substream;
+
+	struct snd_pcm_indirect2 ind_rec; /* for playback */
+	struct snd_pcm_indirect2 capture_ind2_rec;
+};
+
+static struct snd_pcm_hardware snd_ml403_ac97cr_playback = {
+	.info =	            (SNDRV_PCM_INFO_MMAP |
+			     SNDRV_PCM_INFO_INTERLEAVED |
+			     SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =          SNDRV_PCM_FMTBIT_S16_BE,
+	.rates =	    (SNDRV_PCM_RATE_CONTINUOUS |
+			     SNDRV_PCM_RATE_8000_48000),
+	.rate_min =	    4000,
+	.rate_max =	    48000,
+	.channels_min =     2,
+	.channels_max =     2,
+	.buffer_bytes_max = (128*1024),
+	.period_bytes_min = CR_FIFO_SIZE/2,
+	.period_bytes_max = (64*1024),
+	.periods_min =      2,
+	.periods_max =      (128*1024)/(CR_FIFO_SIZE/2),
+	.fifo_size =	    0,
+};
+
+static struct snd_pcm_hardware snd_ml403_ac97cr_capture = {
+	.info =	            (SNDRV_PCM_INFO_MMAP |
+			     SNDRV_PCM_INFO_INTERLEAVED |
+			     SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =          SNDRV_PCM_FMTBIT_S16_BE,
+	.rates =            (SNDRV_PCM_RATE_CONTINUOUS |
+			     SNDRV_PCM_RATE_8000_48000),
+	.rate_min =         4000,
+	.rate_max =         48000,
+	.channels_min =     2,
+	.channels_max =     2,
+	.buffer_bytes_max = (128*1024),
+	.period_bytes_min = CR_FIFO_SIZE/2,
+	.period_bytes_max = (64*1024),
+	.periods_min =      2,
+	.periods_max =      (128*1024)/(CR_FIFO_SIZE/2),
+	.fifo_size =	    0,
+};
+
+static size_t
+snd_ml403_ac97cr_playback_ind2_zero(struct snd_pcm_substream *substream,
+				    struct snd_pcm_indirect2 *rec)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	int copied_words = 0;
+	u32 full = 0;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+
+	spin_lock(&ml403_ac97cr->reg_lock);
+	while ((full = (in_be32(CR_REG(ml403_ac97cr, STATUS)) &
+			CR_PLAYFULL)) != CR_PLAYFULL) {
+		out_be32(CR_REG(ml403_ac97cr, PLAYFIFO), 0);
+		copied_words++;
+	}
+	rec->hw_ready = 0;
+	spin_unlock(&ml403_ac97cr->reg_lock);
+
+	return (size_t) (copied_words * 2);
+}
+
+static size_t
+snd_ml403_ac97cr_playback_ind2_copy(struct snd_pcm_substream *substream,
+				    struct snd_pcm_indirect2 *rec,
+				    size_t bytes)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	u16 *src;
+	int copied_words = 0;
+	u32 full = 0;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+	src = (u16 *)(substream->runtime->dma_area + rec->sw_data);
+
+	spin_lock(&ml403_ac97cr->reg_lock);
+	while (((full = (in_be32(CR_REG(ml403_ac97cr, STATUS)) &
+			 CR_PLAYFULL)) != CR_PLAYFULL) && (bytes > 1)) {
+		out_be32(CR_REG(ml403_ac97cr, PLAYFIFO),
+			 CR_PLAYDATA(src[copied_words]));
+		copied_words++;
+		bytes = bytes - 2;
+	}
+	if (full != CR_PLAYFULL)
+		rec->hw_ready = 1;
+	else
+		rec->hw_ready = 0;
+	spin_unlock(&ml403_ac97cr->reg_lock);
+
+	return (size_t) (copied_words * 2);
+}
+
+static size_t
+snd_ml403_ac97cr_capture_ind2_null(struct snd_pcm_substream *substream,
+				   struct snd_pcm_indirect2 *rec)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	int copied_words = 0;
+	u32 empty = 0;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+
+	spin_lock(&ml403_ac97cr->reg_lock);
+	while ((empty = (in_be32(CR_REG(ml403_ac97cr, STATUS)) &
+			 CR_RECEMPTY)) != CR_RECEMPTY) {
+		volatile u32 trash;
+
+		trash = CR_RECDATA(in_be32(CR_REG(ml403_ac97cr, RECFIFO)));
+		/* Hmmmm, really necessary? Don't want call to in_be32()
+		 * to be optimised away!
+		 */
+		trash++;
+		copied_words++;
+	}
+	rec->hw_ready = 0;
+	spin_unlock(&ml403_ac97cr->reg_lock);
+
+	return (size_t) (copied_words * 2);
+}
+
+static size_t
+snd_ml403_ac97cr_capture_ind2_copy(struct snd_pcm_substream *substream,
+				   struct snd_pcm_indirect2 *rec, size_t bytes)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	u16 *dst;
+	int copied_words = 0;
+	u32 empty = 0;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+	dst = (u16 *)(substream->runtime->dma_area + rec->sw_data);
+
+	spin_lock(&ml403_ac97cr->reg_lock);
+	while (((empty = (in_be32(CR_REG(ml403_ac97cr, STATUS)) &
+			  CR_RECEMPTY)) != CR_RECEMPTY) && (bytes > 1)) {
+		dst[copied_words] = CR_RECDATA(in_be32(CR_REG(ml403_ac97cr,
+							      RECFIFO)));
+		copied_words++;
+		bytes = bytes - 2;
+	}
+	if (empty != CR_RECEMPTY)
+		rec->hw_ready = 1;
+	else
+		rec->hw_ready = 0;
+	spin_unlock(&ml403_ac97cr->reg_lock);
+
+	return (size_t) (copied_words * 2);
+}
+
+static snd_pcm_uframes_t
+snd_ml403_ac97cr_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	struct snd_pcm_indirect2 *ind2_rec = NULL;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+
+	if (substream == ml403_ac97cr->playback_substream)
+		ind2_rec = &ml403_ac97cr->ind_rec;
+	if (substream == ml403_ac97cr->capture_substream)
+		ind2_rec = &ml403_ac97cr->capture_ind2_rec;
+
+	if (ind2_rec != NULL)
+		return snd_pcm_indirect2_pointer(substream, ind2_rec);
+	return (snd_pcm_uframes_t) 0;
+}
+
+static int
+snd_ml403_ac97cr_pcm_playback_trigger(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	int err = 0;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		PDEBUG(WORK_INFO, "trigger(playback): START\n");
+		ml403_ac97cr->ind_rec.hw_ready = 1;
+
+		/* clear play FIFO */
+		out_be32(CR_REG(ml403_ac97cr, RESETFIFO), CR_PLAYRESET);
+
+		/* enable play irq */
+		ml403_ac97cr->enable_irq = 1;
+		enable_irq(ml403_ac97cr->irq);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		PDEBUG(WORK_INFO, "trigger(playback): STOP\n");
+		ml403_ac97cr->ind_rec.hw_ready = 0;
+#ifdef SND_PCM_INDIRECT2_STAT
+		snd_pcm_indirect2_stat(substream, &ml403_ac97cr->ind_rec);
+#endif
+		/* disable play irq */
+		disable_irq_nosync(ml403_ac97cr->irq);
+		ml403_ac97cr->enable_irq = 0;
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	PDEBUG(WORK_INFO, "trigger(playback): (done)\n");
+	return err;
+}
+
+static int
+snd_ml403_ac97cr_pcm_capture_trigger(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	int err = 0;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		PDEBUG(WORK_INFO, "trigger(capture): START\n");
+		ml403_ac97cr->capture_ind2_rec.hw_ready = 0;
+
+		/* clear record FIFO */
+		out_be32(CR_REG(ml403_ac97cr, RESETFIFO), CR_RECRESET);
+
+		/* enable record irq */
+		ml403_ac97cr->enable_capture_irq = 1;
+		enable_irq(ml403_ac97cr->capture_irq);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		PDEBUG(WORK_INFO, "trigger(capture): STOP\n");
+		ml403_ac97cr->capture_ind2_rec.hw_ready = 0;
+#ifdef SND_PCM_INDIRECT2_STAT
+		snd_pcm_indirect2_stat(substream,
+				       &ml403_ac97cr->capture_ind2_rec);
+#endif
+		/* disable capture irq */
+		disable_irq_nosync(ml403_ac97cr->capture_irq);
+		ml403_ac97cr->enable_capture_irq = 0;
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	PDEBUG(WORK_INFO, "trigger(capture): (done)\n");
+	return err;
+}
+
+static int
+snd_ml403_ac97cr_pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	struct snd_pcm_runtime *runtime;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+	runtime = substream->runtime;
+
+	PDEBUG(WORK_INFO,
+	       "prepare(): period_bytes=%d, minperiod_bytes=%d\n",
+	       snd_pcm_lib_period_bytes(substream), CR_FIFO_SIZE / 2);
+
+	/* set sampling rate */
+	snd_ac97_set_rate(ml403_ac97cr->ac97, AC97_PCM_FRONT_DAC_RATE,
+			  runtime->rate);
+	PDEBUG(WORK_INFO, "prepare(): rate=%d\n", runtime->rate);
+
+	/* init struct for intermediate buffer */
+	memset(&ml403_ac97cr->ind_rec, 0,
+	       sizeof(struct snd_pcm_indirect2));
+	ml403_ac97cr->ind_rec.hw_buffer_size = CR_FIFO_SIZE;
+	ml403_ac97cr->ind_rec.sw_buffer_size =
+		snd_pcm_lib_buffer_bytes(substream);
+	ml403_ac97cr->ind_rec.min_periods = -1;
+	ml403_ac97cr->ind_rec.min_multiple =
+		snd_pcm_lib_period_bytes(substream) / (CR_FIFO_SIZE / 2);
+	PDEBUG(WORK_INFO, "prepare(): hw_buffer_size=%d, "
+	       "sw_buffer_size=%d, min_multiple=%d\n",
+	       CR_FIFO_SIZE, ml403_ac97cr->ind_rec.sw_buffer_size,
+	       ml403_ac97cr->ind_rec.min_multiple);
+	return 0;
+}
+
+static int
+snd_ml403_ac97cr_pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	struct snd_pcm_runtime *runtime;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+	runtime = substream->runtime;
+
+	PDEBUG(WORK_INFO,
+	       "prepare(capture): period_bytes=%d, minperiod_bytes=%d\n",
+	       snd_pcm_lib_period_bytes(substream), CR_FIFO_SIZE / 2);
+
+	/* set sampling rate */
+	snd_ac97_set_rate(ml403_ac97cr->ac97, AC97_PCM_LR_ADC_RATE,
+			  runtime->rate);
+	PDEBUG(WORK_INFO, "prepare(capture): rate=%d\n", runtime->rate);
+
+	/* init struct for intermediate buffer */
+	memset(&ml403_ac97cr->capture_ind2_rec, 0,
+	       sizeof(struct snd_pcm_indirect2));
+	ml403_ac97cr->capture_ind2_rec.hw_buffer_size = CR_FIFO_SIZE;
+	ml403_ac97cr->capture_ind2_rec.sw_buffer_size =
+		snd_pcm_lib_buffer_bytes(substream);
+	ml403_ac97cr->capture_ind2_rec.min_multiple =
+		snd_pcm_lib_period_bytes(substream) / (CR_FIFO_SIZE / 2);
+	PDEBUG(WORK_INFO, "prepare(capture): hw_buffer_size=%d, "
+	       "sw_buffer_size=%d, min_multiple=%d\n", CR_FIFO_SIZE,
+	       ml403_ac97cr->capture_ind2_rec.sw_buffer_size,
+	       ml403_ac97cr->capture_ind2_rec.min_multiple);
+	return 0;
+}
+
+static int snd_ml403_ac97cr_hw_free(struct snd_pcm_substream *substream)
+{
+	PDEBUG(WORK_INFO, "hw_free()\n");
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int
+snd_ml403_ac97cr_hw_params(struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *hw_params)
+{
+	PDEBUG(WORK_INFO, "hw_params(): desired buffer bytes=%d, desired "
+	       "period bytes=%d\n",
+	       params_buffer_bytes(hw_params), params_period_bytes(hw_params));
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int snd_ml403_ac97cr_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	struct snd_pcm_runtime *runtime;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+	runtime = substream->runtime;
+
+	PDEBUG(WORK_INFO, "open(playback)\n");
+	ml403_ac97cr->playback_substream = substream;
+	runtime->hw = snd_ml403_ac97cr_playback;
+
+	snd_pcm_hw_constraint_step(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				   CR_FIFO_SIZE / 2);
+	return 0;
+}
+
+static int snd_ml403_ac97cr_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	struct snd_pcm_runtime *runtime;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+	runtime = substream->runtime;
+
+	PDEBUG(WORK_INFO, "open(capture)\n");
+	ml403_ac97cr->capture_substream = substream;
+	runtime->hw = snd_ml403_ac97cr_capture;
+
+	snd_pcm_hw_constraint_step(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				   CR_FIFO_SIZE / 2);
+	return 0;
+}
+
+static int snd_ml403_ac97cr_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+
+	PDEBUG(WORK_INFO, "close(playback)\n");
+	ml403_ac97cr->playback_substream = NULL;
+	return 0;
+}
+
+static int snd_ml403_ac97cr_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+
+	ml403_ac97cr = snd_pcm_substream_chip(substream);
+
+	PDEBUG(WORK_INFO, "close(capture)\n");
+	ml403_ac97cr->capture_substream = NULL;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_ml403_ac97cr_playback_ops = {
+	.open = snd_ml403_ac97cr_playback_open,
+	.close = snd_ml403_ac97cr_playback_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_ml403_ac97cr_hw_params,
+	.hw_free = snd_ml403_ac97cr_hw_free,
+	.prepare = snd_ml403_ac97cr_pcm_playback_prepare,
+	.trigger = snd_ml403_ac97cr_pcm_playback_trigger,
+	.pointer = snd_ml403_ac97cr_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_ml403_ac97cr_capture_ops = {
+	.open = snd_ml403_ac97cr_capture_open,
+	.close = snd_ml403_ac97cr_capture_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_ml403_ac97cr_hw_params,
+	.hw_free = snd_ml403_ac97cr_hw_free,
+	.prepare = snd_ml403_ac97cr_pcm_capture_prepare,
+	.trigger = snd_ml403_ac97cr_pcm_capture_trigger,
+	.pointer = snd_ml403_ac97cr_pcm_pointer,
+};
+
+static irqreturn_t snd_ml403_ac97cr_irq(int irq, void *dev_id)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	struct platform_device *pfdev;
+	int cmp_irq;
+
+	ml403_ac97cr = (struct snd_ml403_ac97cr *)dev_id;
+	if (ml403_ac97cr == NULL)
+		return IRQ_NONE;
+
+	pfdev = ml403_ac97cr->pfdev;
+
+	/* playback interrupt */
+	cmp_irq = platform_get_irq(pfdev, 0);
+	if (irq == cmp_irq) {
+		if (ml403_ac97cr->enable_irq)
+			snd_pcm_indirect2_playback_interrupt(
+				ml403_ac97cr->playback_substream,
+				&ml403_ac97cr->ind_rec,
+				snd_ml403_ac97cr_playback_ind2_copy,
+				snd_ml403_ac97cr_playback_ind2_zero);
+		else
+			goto __disable_irq;
+	} else {
+		/* record interrupt */
+		cmp_irq = platform_get_irq(pfdev, 1);
+		if (irq == cmp_irq) {
+			if (ml403_ac97cr->enable_capture_irq)
+				snd_pcm_indirect2_capture_interrupt(
+					ml403_ac97cr->capture_substream,
+					&ml403_ac97cr->capture_ind2_rec,
+					snd_ml403_ac97cr_capture_ind2_copy,
+					snd_ml403_ac97cr_capture_ind2_null);
+			else
+				goto __disable_irq;
+		} else
+			return IRQ_NONE;
+	}
+	return IRQ_HANDLED;
+
+__disable_irq:
+	PDEBUG(INIT_INFO, "irq(): irq %d is meant to be disabled! So, now try "
+	       "to disable it _really_!\n", irq);
+	disable_irq_nosync(irq);
+	return IRQ_HANDLED;
+}
+
+static unsigned short
+snd_ml403_ac97cr_codec_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data;
+#ifdef CODEC_STAT
+	u32 stat;
+	u32 rafaccess = 0;
+#endif
+	unsigned long end_time;
+	u16 value = 0;
+
+	if (!LM4550_RF_OK(reg)) {
+		snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
+			   "access to unknown/unused codec register 0x%x "
+			   "ignored!\n", reg);
+		return 0;
+	}
+	/* check if we can fake/answer this access from our shadow register */
+	if ((lm4550_regfile[reg / 2].flag &
+	     (LM4550_REG_DONEREAD | LM4550_REG_ALLFAKE)) &&
+	    !(lm4550_regfile[reg / 2].flag & LM4550_REG_NOSHADOW)) {
+		if (lm4550_regfile[reg / 2].flag & LM4550_REG_FAKEREAD) {
+			PDEBUG(CODEC_FAKE, "codec_read(): faking read from "
+			       "reg=0x%x, val=0x%x / %d\n",
+			       reg, lm4550_regfile[reg / 2].def,
+			       lm4550_regfile[reg / 2].def);
+			return lm4550_regfile[reg / 2].def;
+		} else if ((lm4550_regfile[reg / 2].flag &
+			    LM4550_REG_FAKEPROBE) &&
+			   ml403_ac97cr->ac97_fake) {
+			PDEBUG(CODEC_FAKE, "codec_read(): faking read from "
+			       "reg=0x%x, val=0x%x / %d (probe)\n",
+			       reg, lm4550_regfile[reg / 2].value,
+			       lm4550_regfile[reg / 2].value);
+			return lm4550_regfile[reg / 2].value;
+		} else {
+#ifdef CODEC_STAT
+			PDEBUG(CODEC_FAKE, "codec_read(): read access "
+			       "answered by shadow register 0x%x (value=0x%x "
+			       "/ %d) (cw=%d cr=%d)\n",
+			       reg, lm4550_regfile[reg / 2].value,
+			       lm4550_regfile[reg / 2].value,
+			       ml403_ac97cr->ac97_write,
+			       ml403_ac97cr->ac97_read);
+#else
+			PDEBUG(CODEC_FAKE, "codec_read(): read access "
+			       "answered by shadow register 0x%x (value=0x%x "
+			       "/ %d)\n",
+			       reg, lm4550_regfile[reg / 2].value,
+			       lm4550_regfile[reg / 2].value);
+#endif
+			return lm4550_regfile[reg / 2].value;
+		}
+	}
+	/* if we are here, we _have_ to access the codec really, no faking */
+	if (mutex_lock_interruptible(&ml403_ac97cr->cdc_mutex) != 0)
+		return 0;
+#ifdef CODEC_STAT
+	ml403_ac97cr->ac97_read++;
+#endif
+	spin_lock(&ml403_ac97cr->reg_lock);
+	out_be32(CR_REG(ml403_ac97cr, CODEC_ADDR),
+		 CR_CODEC_ADDR(reg) | CR_CODEC_READ);
+	spin_unlock(&ml403_ac97cr->reg_lock);
+	end_time = jiffies + (HZ / CODEC_TIMEOUT_AFTER_READ);
+	do {
+		spin_lock(&ml403_ac97cr->reg_lock);
+#ifdef CODEC_STAT
+		rafaccess++;
+		stat = in_be32(CR_REG(ml403_ac97cr, STATUS));
+		if ((stat & CR_RAF) == CR_RAF) {
+			value = CR_CODEC_DATAREAD(
+				in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD)));
+			PDEBUG(CODEC_SUCCESS, "codec_read(): (done) reg=0x%x, "
+			       "value=0x%x / %d (STATUS=0x%x)\n",
+			       reg, value, value, stat);
+#else
+		if ((in_be32(CR_REG(ml403_ac97cr, STATUS)) &
+		     CR_RAF) == CR_RAF) {
+			value = CR_CODEC_DATAREAD(
+				in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD)));
+			PDEBUG(CODEC_SUCCESS, "codec_read(): (done) "
+			       "reg=0x%x, value=0x%x / %d\n",
+			       reg, value, value);
+#endif
+			lm4550_regfile[reg / 2].value = value;
+			lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD;
+			spin_unlock(&ml403_ac97cr->reg_lock);
+			mutex_unlock(&ml403_ac97cr->cdc_mutex);
+			return value;
+		}
+		spin_unlock(&ml403_ac97cr->reg_lock);
+		schedule_timeout_uninterruptible(1);
+	} while (time_after(end_time, jiffies));
+	/* read the DATAREAD register anyway, see comment below */
+	spin_lock(&ml403_ac97cr->reg_lock);
+	value =
+	    CR_CODEC_DATAREAD(in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD)));
+	spin_unlock(&ml403_ac97cr->reg_lock);
+#ifdef CODEC_STAT
+	snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
+		   "timeout while codec read! "
+		   "(reg=0x%x, last STATUS=0x%x, DATAREAD=0x%x / %d, %d) "
+		   "(cw=%d, cr=%d)\n",
+		   reg, stat, value, value, rafaccess,
+		   ml403_ac97cr->ac97_write, ml403_ac97cr->ac97_read);
+#else
+	snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
+		   "timeout while codec read! "
+		   "(reg=0x%x, DATAREAD=0x%x / %d)\n",
+		   reg, value, value);
+#endif
+	/* BUG: This is PURE speculation! But after _most_ read timeouts the
+	 * value in the register is ok!
+	 */
+	lm4550_regfile[reg / 2].value = value;
+	lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD;
+	mutex_unlock(&ml403_ac97cr->cdc_mutex);
+	return value;
+}
+
+static void
+snd_ml403_ac97cr_codec_write(struct snd_ac97 *ac97, unsigned short reg,
+			     unsigned short val)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data;
+
+#ifdef CODEC_STAT
+	u32 stat;
+	u32 rafaccess = 0;
+#endif
+#ifdef CODEC_WRITE_CHECK_RAF
+	unsigned long end_time;
+#endif
+
+	if (!LM4550_RF_OK(reg)) {
+		snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
+			   "access to unknown/unused codec register 0x%x "
+			   "ignored!\n", reg);
+		return;
+	}
+	if (lm4550_regfile[reg / 2].flag & LM4550_REG_READONLY) {
+		snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
+			   "write access to read only codec register 0x%x "
+			   "ignored!\n", reg);
+		return;
+	}
+	if ((val & lm4550_regfile[reg / 2].wmask) != val) {
+		snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
+			   "write access to codec register 0x%x "
+			   "with bad value 0x%x / %d!\n",
+			   reg, val, val);
+		val = val & lm4550_regfile[reg / 2].wmask;
+	}
+	if (((lm4550_regfile[reg / 2].flag & LM4550_REG_FAKEPROBE) &&
+	     ml403_ac97cr->ac97_fake) &&
+	    !(lm4550_regfile[reg / 2].flag & LM4550_REG_NOSHADOW)) {
+		PDEBUG(CODEC_FAKE, "codec_write(): faking write to reg=0x%x, "
+		       "val=0x%x / %d\n", reg, val, val);
+		lm4550_regfile[reg / 2].value = (val &
+						lm4550_regfile[reg / 2].wmask);
+		return;
+	}
+	if (mutex_lock_interruptible(&ml403_ac97cr->cdc_mutex) != 0)
+		return;
+#ifdef CODEC_STAT
+	ml403_ac97cr->ac97_write++;
+#endif
+	spin_lock(&ml403_ac97cr->reg_lock);
+	out_be32(CR_REG(ml403_ac97cr, CODEC_DATAWRITE),
+		 CR_CODEC_DATAWRITE(val));
+	out_be32(CR_REG(ml403_ac97cr, CODEC_ADDR),
+		 CR_CODEC_ADDR(reg) | CR_CODEC_WRITE);
+	spin_unlock(&ml403_ac97cr->reg_lock);
+#ifdef CODEC_WRITE_CHECK_RAF
+	/* check CR_CODEC_RAF bit to see if write access to register is done;
+	 * loop until bit is set or timeout happens
+	 */
+	end_time = jiffies + HZ / CODEC_TIMEOUT_AFTER_WRITE;
+	do {
+		spin_lock(&ml403_ac97cr->reg_lock);
+#ifdef CODEC_STAT
+		rafaccess++;
+		stat = in_be32(CR_REG(ml403_ac97cr, STATUS))
+		if ((stat & CR_RAF) == CR_RAF) {
+#else
+		if ((in_be32(CR_REG(ml403_ac97cr, STATUS)) &
+		     CR_RAF) == CR_RAF) {
+#endif
+			PDEBUG(CODEC_SUCCESS, "codec_write(): (done) "
+			       "reg=0x%x, value=%d / 0x%x\n",
+			       reg, val, val);
+			if (!(lm4550_regfile[reg / 2].flag &
+			      LM4550_REG_NOSHADOW) &&
+			    !(lm4550_regfile[reg / 2].flag &
+			      LM4550_REG_NOSAVE))
+				lm4550_regfile[reg / 2].value = val;
+			lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD;
+			spin_unlock(&ml403_ac97cr->reg_lock);
+			mutex_unlock(&ml403_ac97cr->cdc_mutex);
+			return;
+		}
+		spin_unlock(&ml403_ac97cr->reg_lock);
+		schedule_timeout_uninterruptible(1);
+	} while (time_after(end_time, jiffies));
+#ifdef CODEC_STAT
+	snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
+		   "timeout while codec write "
+		   "(reg=0x%x, val=0x%x / %d, last STATUS=0x%x, %d) "
+		   "(cw=%d, cr=%d)\n",
+		   reg, val, val, stat, rafaccess, ml403_ac97cr->ac97_write,
+		   ml403_ac97cr->ac97_read);
+#else
+	snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
+		   "timeout while codec write (reg=0x%x, val=0x%x / %d)\n",
+		   reg, val, val);
+#endif
+#else /* CODEC_WRITE_CHECK_RAF */
+#if CODEC_WAIT_AFTER_WRITE > 0
+	/* officially, in AC97 spec there is no possibility for a AC97
+	 * controller to determine, if write access is done or not - so: How
+	 * is Xilinx able to provide a RAF bit for write access?
+	 * => very strange, thus just don't check RAF bit (compare with
+	 * Xilinx's example app in EDK 8.1i) and wait
+	 */
+	schedule_timeout_uninterruptible(HZ / CODEC_WAIT_AFTER_WRITE);
+#endif
+	PDEBUG(CODEC_SUCCESS, "codec_write(): (done) "
+	       "reg=0x%x, value=%d / 0x%x (no RAF check)\n",
+	       reg, val, val);
+#endif
+	mutex_unlock(&ml403_ac97cr->cdc_mutex);
+	return;
+}
+
+static int __devinit
+snd_ml403_ac97cr_chip_init(struct snd_ml403_ac97cr *ml403_ac97cr)
+{
+	unsigned long end_time;
+	PDEBUG(INIT_INFO, "chip_init():\n");
+	end_time = jiffies + HZ / CODEC_TIMEOUT_ON_INIT;
+	do {
+		if (in_be32(CR_REG(ml403_ac97cr, STATUS)) & CR_CODECREADY) {
+			/* clear both hardware FIFOs */
+			out_be32(CR_REG(ml403_ac97cr, RESETFIFO),
+				 CR_RECRESET | CR_PLAYRESET);
+			PDEBUG(INIT_INFO, "chip_init(): (done)\n");
+			return 0;
+		}
+		schedule_timeout_uninterruptible(1);
+	} while (time_after(end_time, jiffies));
+	snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": "
+		   "timeout while waiting for codec, "
+		   "not ready!\n");
+	return -EBUSY;
+}
+
+static int snd_ml403_ac97cr_free(struct snd_ml403_ac97cr *ml403_ac97cr)
+{
+	PDEBUG(INIT_INFO, "free():\n");
+	/* irq release */
+	if (ml403_ac97cr->irq >= 0)
+		free_irq(ml403_ac97cr->irq, ml403_ac97cr);
+	if (ml403_ac97cr->capture_irq >= 0)
+		free_irq(ml403_ac97cr->capture_irq, ml403_ac97cr);
+	/* give back "port" */
+	if (ml403_ac97cr->port != NULL)
+		iounmap(ml403_ac97cr->port);
+	kfree(ml403_ac97cr);
+	PDEBUG(INIT_INFO, "free(): (done)\n");
+	return 0;
+}
+
+static int snd_ml403_ac97cr_dev_free(struct snd_device *snddev)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr = snddev->device_data;
+	PDEBUG(INIT_INFO, "dev_free():\n");
+	return snd_ml403_ac97cr_free(ml403_ac97cr);
+}
+
+static int __devinit
+snd_ml403_ac97cr_create(struct snd_card *card, struct platform_device *pfdev,
+			struct snd_ml403_ac97cr **rml403_ac97cr)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_ml403_ac97cr_dev_free,
+	};
+	struct resource *resource;
+	int irq;
+
+	*rml403_ac97cr = NULL;
+	ml403_ac97cr = kzalloc(sizeof(*ml403_ac97cr), GFP_KERNEL);
+	if (ml403_ac97cr == NULL)
+		return -ENOMEM;
+	spin_lock_init(&ml403_ac97cr->reg_lock);
+	mutex_init(&ml403_ac97cr->cdc_mutex);
+	ml403_ac97cr->card = card;
+	ml403_ac97cr->pfdev = pfdev;
+	ml403_ac97cr->irq = -1;
+	ml403_ac97cr->enable_irq = 0;
+	ml403_ac97cr->capture_irq = -1;
+	ml403_ac97cr->enable_capture_irq = 0;
+	ml403_ac97cr->port = NULL;
+	ml403_ac97cr->res_port = NULL;
+
+	PDEBUG(INIT_INFO, "Trying to reserve resources now ...\n");
+	resource = platform_get_resource(pfdev, IORESOURCE_MEM, 0);
+	/* get "port" */
+	ml403_ac97cr->port = ioremap_nocache(resource->start,
+					     (resource->end) -
+					     (resource->start) + 1);
+	if (ml403_ac97cr->port == NULL) {
+		snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": "
+			   "unable to remap memory region (%x to %x)\n",
+			   resource->start, resource->end);
+		snd_ml403_ac97cr_free(ml403_ac97cr);
+		return -EBUSY;
+	}
+	snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": "
+		   "remap controller memory region to "
+		   "0x%x done\n", (unsigned int)ml403_ac97cr->port);
+	/* get irq */
+	irq = platform_get_irq(pfdev, 0);
+	if (request_irq(irq, snd_ml403_ac97cr_irq, IRQF_DISABLED,
+			dev_name(&pfdev->dev), (void *)ml403_ac97cr)) {
+		snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": "
+			   "unable to grab IRQ %d\n",
+			   irq);
+		snd_ml403_ac97cr_free(ml403_ac97cr);
+		return -EBUSY;
+	}
+	ml403_ac97cr->irq = irq;
+	snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": "
+		   "request (playback) irq %d done\n",
+		   ml403_ac97cr->irq);
+	irq = platform_get_irq(pfdev, 1);
+	if (request_irq(irq, snd_ml403_ac97cr_irq, IRQF_DISABLED,
+			dev_name(&pfdev->dev), (void *)ml403_ac97cr)) {
+		snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": "
+			   "unable to grab IRQ %d\n",
+			   irq);
+		snd_ml403_ac97cr_free(ml403_ac97cr);
+		return -EBUSY;
+	}
+	ml403_ac97cr->capture_irq = irq;
+	snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": "
+		   "request (capture) irq %d done\n",
+		   ml403_ac97cr->capture_irq);
+
+	err = snd_ml403_ac97cr_chip_init(ml403_ac97cr);
+	if (err < 0) {
+		snd_ml403_ac97cr_free(ml403_ac97cr);
+		return err;
+	}
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ml403_ac97cr, &ops);
+	if (err < 0) {
+		PDEBUG(INIT_FAILURE, "probe(): snd_device_new() failed!\n");
+		snd_ml403_ac97cr_free(ml403_ac97cr);
+		return err;
+	}
+
+	*rml403_ac97cr = ml403_ac97cr;
+	return 0;
+}
+
+static void snd_ml403_ac97cr_mixer_free(struct snd_ac97 *ac97)
+{
+	struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data;
+	PDEBUG(INIT_INFO, "mixer_free():\n");
+	ml403_ac97cr->ac97 = NULL;
+	PDEBUG(INIT_INFO, "mixer_free(): (done)\n");
+}
+
+static int __devinit
+snd_ml403_ac97cr_mixer(struct snd_ml403_ac97cr *ml403_ac97cr)
+{
+	struct snd_ac97_bus *bus;
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_ml403_ac97cr_codec_write,
+		.read = snd_ml403_ac97cr_codec_read,
+	};
+	PDEBUG(INIT_INFO, "mixer():\n");
+	err = snd_ac97_bus(ml403_ac97cr->card, 0, &ops, NULL, &bus);
+	if (err < 0)
+		return err;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ml403_ac97cr->ac97_fake = 1;
+	lm4550_regfile_init();
+#ifdef CODEC_STAT
+	ml403_ac97cr->ac97_read = 0;
+	ml403_ac97cr->ac97_write = 0;
+#endif
+	ac97.private_data = ml403_ac97cr;
+	ac97.private_free = snd_ml403_ac97cr_mixer_free;
+	ac97.scaps = AC97_SCAP_AUDIO | AC97_SCAP_SKIP_MODEM |
+	    AC97_SCAP_NO_SPDIF;
+	err = snd_ac97_mixer(bus, &ac97, &ml403_ac97cr->ac97);
+	ml403_ac97cr->ac97_fake = 0;
+	lm4550_regfile_write_values_after_init(ml403_ac97cr->ac97);
+	PDEBUG(INIT_INFO, "mixer(): (done) snd_ac97_mixer()=%d\n", err);
+	return err;
+}
+
+static int __devinit
+snd_ml403_ac97cr_pcm(struct snd_ml403_ac97cr *ml403_ac97cr, int device,
+		     struct snd_pcm **rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	err = snd_pcm_new(ml403_ac97cr->card, "ML403AC97CR/1", device, 1, 1,
+			  &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_ml403_ac97cr_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&snd_ml403_ac97cr_capture_ops);
+	pcm->private_data = ml403_ac97cr;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "ML403AC97CR DAC/ADC");
+	ml403_ac97cr->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+					  snd_dma_continuous_data(GFP_KERNEL),
+					  64 * 1024,
+					  128 * 1024);
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+static int __devinit snd_ml403_ac97cr_probe(struct platform_device *pfdev)
+{
+	struct snd_card *card;
+	struct snd_ml403_ac97cr *ml403_ac97cr = NULL;
+	int err;
+	int dev = pfdev->id;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev])
+		return -ENOENT;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+	err = snd_ml403_ac97cr_create(card, pfdev, &ml403_ac97cr);
+	if (err < 0) {
+		PDEBUG(INIT_FAILURE, "probe(): create failed!\n");
+		snd_card_free(card);
+		return err;
+	}
+	PDEBUG(INIT_INFO, "probe(): create done\n");
+	card->private_data = ml403_ac97cr;
+	err = snd_ml403_ac97cr_mixer(ml403_ac97cr);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	PDEBUG(INIT_INFO, "probe(): mixer done\n");
+	err = snd_ml403_ac97cr_pcm(ml403_ac97cr, 0, NULL);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	PDEBUG(INIT_INFO, "probe(): PCM done\n");
+	strcpy(card->driver, SND_ML403_AC97CR_DRIVER);
+	strcpy(card->shortname, "ML403 AC97 Controller Reference");
+	sprintf(card->longname, "%s %s at 0x%lx, irq %i & %i, device %i",
+		card->shortname, card->driver,
+		(unsigned long)ml403_ac97cr->port, ml403_ac97cr->irq,
+		ml403_ac97cr->capture_irq, dev + 1);
+
+	snd_card_set_dev(card, &pfdev->dev);
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	platform_set_drvdata(pfdev, card);
+	PDEBUG(INIT_INFO, "probe(): (done)\n");
+	return 0;
+}
+
+static int snd_ml403_ac97cr_remove(struct platform_device *pfdev)
+{
+	snd_card_free(platform_get_drvdata(pfdev));
+	platform_set_drvdata(pfdev, NULL);
+	return 0;
+}
+
+/* work with hotplug and coldplug */
+MODULE_ALIAS("platform:" SND_ML403_AC97CR_DRIVER);
+
+static struct platform_driver snd_ml403_ac97cr_driver = {
+	.probe = snd_ml403_ac97cr_probe,
+	.remove = snd_ml403_ac97cr_remove,
+	.driver = {
+		.name = SND_ML403_AC97CR_DRIVER,
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init alsa_card_ml403_ac97cr_init(void)
+{
+	return platform_driver_register(&snd_ml403_ac97cr_driver);
+}
+
+static void __exit alsa_card_ml403_ac97cr_exit(void)
+{
+	platform_driver_unregister(&snd_ml403_ac97cr_driver);
+}
+
+module_init(alsa_card_ml403_ac97cr_init)
+module_exit(alsa_card_ml403_ac97cr_exit)
diff --git a/linux/sound/drivers/mpu401/Makefile b/linux/sound/drivers/mpu401/Makefile
new file mode 100644
index 0000000..918f83f
--- /dev/null
+++ b/linux/sound/drivers/mpu401/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-mpu401-objs := mpu401.o
+snd-mpu401-uart-objs := mpu401_uart.o
+
+obj-$(CONFIG_SND_MPU401_UART) += snd-mpu401-uart.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_MPU401) += snd-mpu401.o
diff --git a/linux/sound/drivers/mpu401/mpu401.c b/linux/sound/drivers/mpu401/mpu401.c
new file mode 100644
index 0000000..149d05a
--- /dev/null
+++ b/linux/sound/drivers/mpu401/mpu401.c
@@ -0,0 +1,289 @@
+/*
+ *  Driver for generic MPU-401 boards (UART mode only)
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) 2004 by Castet Matthieu <castet.matthieu@free.fr>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/pnp.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("MPU-401 UART");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -2}; /* exclude the first card */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+#ifdef CONFIG_PNP
+static int pnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* MPU-401 port number */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* MPU-401 IRQ */
+static int uart_enter[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for MPU-401 device.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for MPU-401 device.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable MPU-401 device.");
+#ifdef CONFIG_PNP
+module_param_array(pnp, bool, NULL, 0444);
+MODULE_PARM_DESC(pnp, "PnP detection for MPU-401 device.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for MPU-401 device.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for MPU-401 device.");
+module_param_array(uart_enter, bool, NULL, 0444);
+MODULE_PARM_DESC(uart_enter, "Issue UART_ENTER command at open.");
+
+static struct platform_device *platform_devices[SNDRV_CARDS];
+static int pnp_registered;
+static unsigned int snd_mpu401_devices;
+
+static int snd_mpu401_create(int dev, struct snd_card **rcard)
+{
+	struct snd_card *card;
+	int err;
+
+	if (!uart_enter[dev])
+		snd_printk(KERN_ERR "the uart_enter option is obsolete; remove it\n");
+
+	*rcard = NULL;
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+	strcpy(card->driver, "MPU-401 UART");
+	strcpy(card->shortname, card->driver);
+	sprintf(card->longname, "%s at %#lx, ", card->shortname, port[dev]);
+	if (irq[dev] >= 0) {
+		sprintf(card->longname + strlen(card->longname), "irq %d", irq[dev]);
+	} else {
+		strcat(card->longname, "polled");
+	}
+
+	err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, port[dev], 0,
+				  irq[dev], irq[dev] >= 0 ? IRQF_DISABLED : 0,
+				  NULL);
+	if (err < 0) {
+		printk(KERN_ERR "MPU401 not detected at 0x%lx\n", port[dev]);
+		goto _err;
+	}
+
+	*rcard = card;
+	return 0;
+
+ _err:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devinit snd_mpu401_probe(struct platform_device *devptr)
+{
+	int dev = devptr->id;
+	int err;
+	struct snd_card *card;
+
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR "specify port\n");
+		return -EINVAL;
+	}
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		snd_printk(KERN_ERR "specify or disable IRQ\n");
+		return -EINVAL;
+	}
+	err = snd_mpu401_create(dev, &card);
+	if (err < 0)
+		return err;
+	snd_card_set_dev(card, &devptr->dev);
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	platform_set_drvdata(devptr, card);
+	return 0;
+}
+
+static int __devexit snd_mpu401_remove(struct platform_device *devptr)
+{
+	snd_card_free(platform_get_drvdata(devptr));
+	platform_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#define SND_MPU401_DRIVER	"snd_mpu401"
+
+static struct platform_driver snd_mpu401_driver = {
+	.probe		= snd_mpu401_probe,
+	.remove		= __devexit_p(snd_mpu401_remove),
+	.driver		= {
+		.name	= SND_MPU401_DRIVER
+	},
+};
+
+
+#ifdef CONFIG_PNP
+
+#define IO_EXTENT 2
+
+static struct pnp_device_id snd_mpu401_pnpids[] = {
+	{ .id = "PNPb006" },
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp, snd_mpu401_pnpids);
+
+static int __devinit snd_mpu401_pnp(int dev, struct pnp_dev *device,
+				 const struct pnp_device_id *id)
+{
+	if (!pnp_port_valid(device, 0) ||
+	    pnp_port_flags(device, 0) & IORESOURCE_DISABLED) {
+		snd_printk(KERN_ERR "no PnP port\n");
+		return -ENODEV;
+	}
+	if (pnp_port_len(device, 0) < IO_EXTENT) {
+		snd_printk(KERN_ERR "PnP port length is %llu, expected %d\n",
+			   (unsigned long long)pnp_port_len(device, 0),
+			   IO_EXTENT);
+		return -ENODEV;
+	}
+	port[dev] = pnp_port_start(device, 0);
+
+	if (!pnp_irq_valid(device, 0) ||
+	    pnp_irq_flags(device, 0) & IORESOURCE_DISABLED) {
+		snd_printk(KERN_WARNING "no PnP irq, using polling\n");
+		irq[dev] = -1;
+	} else {
+		irq[dev] = pnp_irq(device, 0);
+	}
+	return 0;
+}
+
+static int __devinit snd_mpu401_pnp_probe(struct pnp_dev *pnp_dev,
+					  const struct pnp_device_id *id)
+{
+	static int dev;
+	struct snd_card *card;
+	int err;
+
+	for ( ; dev < SNDRV_CARDS; ++dev) {
+		if (!enable[dev] || !pnp[dev])
+			continue;
+		err = snd_mpu401_pnp(dev, pnp_dev, id);
+		if (err < 0)
+			return err;
+		err = snd_mpu401_create(dev, &card);
+		if (err < 0)
+			return err;
+		if ((err = snd_card_register(card)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+		snd_card_set_dev(card, &pnp_dev->dev);
+		pnp_set_drvdata(pnp_dev, card);
+		snd_mpu401_devices++;
+		++dev;
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static void __devexit snd_mpu401_pnp_remove(struct pnp_dev *dev)
+{
+	struct snd_card *card = (struct snd_card *) pnp_get_drvdata(dev);
+
+	snd_card_disconnect(card);
+	snd_card_free_when_closed(card);
+}
+
+static struct pnp_driver snd_mpu401_pnp_driver = {
+	.name = "mpu401",
+	.id_table = snd_mpu401_pnpids,
+	.probe = snd_mpu401_pnp_probe,
+	.remove = __devexit_p(snd_mpu401_pnp_remove),
+};
+#else
+static struct pnp_driver snd_mpu401_pnp_driver;
+#endif
+
+static void snd_mpu401_unregister_all(void)
+{
+	int i;
+
+	if (pnp_registered)
+		pnp_unregister_driver(&snd_mpu401_pnp_driver);
+	for (i = 0; i < ARRAY_SIZE(platform_devices); ++i)
+		platform_device_unregister(platform_devices[i]);
+	platform_driver_unregister(&snd_mpu401_driver);
+}
+
+static int __init alsa_card_mpu401_init(void)
+{
+	int i, err;
+
+	if ((err = platform_driver_register(&snd_mpu401_driver)) < 0)
+		return err;
+
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		struct platform_device *device;
+		if (! enable[i])
+			continue;
+#ifdef CONFIG_PNP
+		if (pnp[i])
+			continue;
+#endif
+		device = platform_device_register_simple(SND_MPU401_DRIVER,
+							 i, NULL, 0);
+		if (IS_ERR(device))
+			continue;
+		if (!platform_get_drvdata(device)) {
+			platform_device_unregister(device);
+			continue;
+		}
+		platform_devices[i] = device;
+		snd_mpu401_devices++;
+	}
+	err = pnp_register_driver(&snd_mpu401_pnp_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (!snd_mpu401_devices) {
+#ifdef MODULE
+		printk(KERN_ERR "MPU-401 device not found or device busy\n");
+#endif
+		snd_mpu401_unregister_all();
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_mpu401_exit(void)
+{
+	snd_mpu401_unregister_all();
+}
+
+module_init(alsa_card_mpu401_init)
+module_exit(alsa_card_mpu401_exit)
diff --git a/linux/sound/drivers/mpu401/mpu401_uart.c b/linux/sound/drivers/mpu401/mpu401_uart.c
new file mode 100644
index 0000000..2af0999
--- /dev/null
+++ b/linux/sound/drivers/mpu401/mpu401_uart.c
@@ -0,0 +1,631 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for control of MPU-401 in UART mode
+ *
+ *  MPU-401 supports UART mode which is not capable generate transmit
+ *  interrupts thus output is done via polling. Also, if irq < 0, then
+ *  input is done also via polling. Do not expect good performance.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *   13-03-2003:
+ *      Added support for different kind of hardware I/O. Build in choices
+ *      are port and mmio. For other kind of I/O, set mpu->read and
+ *      mpu->write to your own I/O functions.
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <sound/core.h>
+#include <sound/mpu401.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Routines for control of MPU-401 in UART mode");
+MODULE_LICENSE("GPL");
+
+static void snd_mpu401_uart_input_read(struct snd_mpu401 * mpu);
+static void snd_mpu401_uart_output_write(struct snd_mpu401 * mpu);
+
+/*
+
+ */
+
+#define snd_mpu401_input_avail(mpu) \
+	(!(mpu->read(mpu, MPU401C(mpu)) & MPU401_RX_EMPTY))
+#define snd_mpu401_output_ready(mpu) \
+	(!(mpu->read(mpu, MPU401C(mpu)) & MPU401_TX_FULL))
+
+/* Build in lowlevel io */
+static void mpu401_write_port(struct snd_mpu401 *mpu, unsigned char data,
+			      unsigned long addr)
+{
+	outb(data, addr);
+}
+
+static unsigned char mpu401_read_port(struct snd_mpu401 *mpu,
+				      unsigned long addr)
+{
+	return inb(addr);
+}
+
+static void mpu401_write_mmio(struct snd_mpu401 *mpu, unsigned char data,
+			      unsigned long addr)
+{
+	writeb(data, (void __iomem *)addr);
+}
+
+static unsigned char mpu401_read_mmio(struct snd_mpu401 *mpu,
+				      unsigned long addr)
+{
+	return readb((void __iomem *)addr);
+}
+/*  */
+
+static void snd_mpu401_uart_clear_rx(struct snd_mpu401 *mpu)
+{
+	int timeout = 100000;
+	for (; timeout > 0 && snd_mpu401_input_avail(mpu); timeout--)
+		mpu->read(mpu, MPU401D(mpu));
+#ifdef CONFIG_SND_DEBUG
+	if (timeout <= 0)
+		snd_printk(KERN_ERR "cmd: clear rx timeout (status = 0x%x)\n",
+			   mpu->read(mpu, MPU401C(mpu)));
+#endif
+}
+
+static void uart_interrupt_tx(struct snd_mpu401 *mpu)
+{
+	unsigned long flags;
+
+	if (test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode) &&
+	    test_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode)) {
+		spin_lock_irqsave(&mpu->output_lock, flags);
+		snd_mpu401_uart_output_write(mpu);
+		spin_unlock_irqrestore(&mpu->output_lock, flags);
+	}
+}
+
+static void _snd_mpu401_uart_interrupt(struct snd_mpu401 *mpu)
+{
+	unsigned long flags;
+
+	if (mpu->info_flags & MPU401_INFO_INPUT) {
+		spin_lock_irqsave(&mpu->input_lock, flags);
+		if (test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode))
+			snd_mpu401_uart_input_read(mpu);
+		else
+			snd_mpu401_uart_clear_rx(mpu);
+		spin_unlock_irqrestore(&mpu->input_lock, flags);
+	}
+	if (! (mpu->info_flags & MPU401_INFO_TX_IRQ))
+		/* ok. for better Tx performance try do some output
+		   when input is done */
+		uart_interrupt_tx(mpu);
+}
+
+/**
+ * snd_mpu401_uart_interrupt - generic MPU401-UART interrupt handler
+ * @irq: the irq number
+ * @dev_id: mpu401 instance
+ *
+ * Processes the interrupt for MPU401-UART i/o.
+ */
+irqreturn_t snd_mpu401_uart_interrupt(int irq, void *dev_id)
+{
+	struct snd_mpu401 *mpu = dev_id;
+	
+	if (mpu == NULL)
+		return IRQ_NONE;
+	_snd_mpu401_uart_interrupt(mpu);
+	return IRQ_HANDLED;
+}
+
+EXPORT_SYMBOL(snd_mpu401_uart_interrupt);
+
+/**
+ * snd_mpu401_uart_interrupt_tx - generic MPU401-UART transmit irq handler
+ * @irq: the irq number
+ * @dev_id: mpu401 instance
+ *
+ * Processes the interrupt for MPU401-UART output.
+ */
+irqreturn_t snd_mpu401_uart_interrupt_tx(int irq, void *dev_id)
+{
+	struct snd_mpu401 *mpu = dev_id;
+	
+	if (mpu == NULL)
+		return IRQ_NONE;
+	uart_interrupt_tx(mpu);
+	return IRQ_HANDLED;
+}
+
+EXPORT_SYMBOL(snd_mpu401_uart_interrupt_tx);
+
+/*
+ * timer callback
+ * reprogram the timer and call the interrupt job
+ */
+static void snd_mpu401_uart_timer(unsigned long data)
+{
+	struct snd_mpu401 *mpu = (struct snd_mpu401 *)data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&mpu->timer_lock, flags);
+	/*mpu->mode |= MPU401_MODE_TIMER;*/
+	mpu->timer.expires = 1 + jiffies;
+	add_timer(&mpu->timer);
+	spin_unlock_irqrestore(&mpu->timer_lock, flags);
+	if (mpu->rmidi)
+		_snd_mpu401_uart_interrupt(mpu);
+}
+
+/*
+ * initialize the timer callback if not programmed yet
+ */
+static void snd_mpu401_uart_add_timer (struct snd_mpu401 *mpu, int input)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave (&mpu->timer_lock, flags);
+	if (mpu->timer_invoked == 0) {
+		init_timer(&mpu->timer);
+		mpu->timer.data = (unsigned long)mpu;
+		mpu->timer.function = snd_mpu401_uart_timer;
+		mpu->timer.expires = 1 + jiffies;
+		add_timer(&mpu->timer);
+	} 
+	mpu->timer_invoked |= input ? MPU401_MODE_INPUT_TIMER :
+		MPU401_MODE_OUTPUT_TIMER;
+	spin_unlock_irqrestore (&mpu->timer_lock, flags);
+}
+
+/*
+ * remove the timer callback if still active
+ */
+static void snd_mpu401_uart_remove_timer (struct snd_mpu401 *mpu, int input)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave (&mpu->timer_lock, flags);
+	if (mpu->timer_invoked) {
+		mpu->timer_invoked &= input ? ~MPU401_MODE_INPUT_TIMER :
+			~MPU401_MODE_OUTPUT_TIMER;
+		if (! mpu->timer_invoked)
+			del_timer(&mpu->timer);
+	}
+	spin_unlock_irqrestore (&mpu->timer_lock, flags);
+}
+
+/*
+ * send a UART command
+ * return zero if successful, non-zero for some errors
+ */
+
+static int snd_mpu401_uart_cmd(struct snd_mpu401 * mpu, unsigned char cmd,
+			       int ack)
+{
+	unsigned long flags;
+	int timeout, ok;
+
+	spin_lock_irqsave(&mpu->input_lock, flags);
+	if (mpu->hardware != MPU401_HW_TRID4DWAVE) {
+		mpu->write(mpu, 0x00, MPU401D(mpu));
+		/*snd_mpu401_uart_clear_rx(mpu);*/
+	}
+	/* ok. standard MPU-401 initialization */
+	if (mpu->hardware != MPU401_HW_SB) {
+		for (timeout = 1000; timeout > 0 &&
+			     !snd_mpu401_output_ready(mpu); timeout--)
+			udelay(10);
+#ifdef CONFIG_SND_DEBUG
+		if (!timeout)
+			snd_printk(KERN_ERR "cmd: tx timeout (status = 0x%x)\n",
+				   mpu->read(mpu, MPU401C(mpu)));
+#endif
+	}
+	mpu->write(mpu, cmd, MPU401C(mpu));
+	if (ack && !(mpu->info_flags & MPU401_INFO_NO_ACK)) {
+		ok = 0;
+		timeout = 10000;
+		while (!ok && timeout-- > 0) {
+			if (snd_mpu401_input_avail(mpu)) {
+				if (mpu->read(mpu, MPU401D(mpu)) == MPU401_ACK)
+					ok = 1;
+			}
+		}
+		if (!ok && mpu->read(mpu, MPU401D(mpu)) == MPU401_ACK)
+			ok = 1;
+	} else
+		ok = 1;
+	spin_unlock_irqrestore(&mpu->input_lock, flags);
+	if (!ok) {
+		snd_printk(KERN_ERR "cmd: 0x%x failed at 0x%lx "
+			   "(status = 0x%x, data = 0x%x)\n", cmd, mpu->port,
+			   mpu->read(mpu, MPU401C(mpu)),
+			   mpu->read(mpu, MPU401D(mpu)));
+		return 1;
+	}
+	return 0;
+}
+
+static int snd_mpu401_do_reset(struct snd_mpu401 *mpu)
+{
+	if (snd_mpu401_uart_cmd(mpu, MPU401_RESET, 1))
+		return -EIO;
+	if (snd_mpu401_uart_cmd(mpu, MPU401_ENTER_UART, 0))
+		return -EIO;
+	return 0;
+}
+
+/*
+ * input/output open/close - protected by open_mutex in rawmidi.c
+ */
+static int snd_mpu401_uart_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_mpu401 *mpu;
+	int err;
+
+	mpu = substream->rmidi->private_data;
+	if (mpu->open_input && (err = mpu->open_input(mpu)) < 0)
+		return err;
+	if (! test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode)) {
+		if (snd_mpu401_do_reset(mpu) < 0)
+			goto error_out;
+	}
+	mpu->substream_input = substream;
+	set_bit(MPU401_MODE_BIT_INPUT, &mpu->mode);
+	return 0;
+
+error_out:
+	if (mpu->open_input && mpu->close_input)
+		mpu->close_input(mpu);
+	return -EIO;
+}
+
+static int snd_mpu401_uart_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_mpu401 *mpu;
+	int err;
+
+	mpu = substream->rmidi->private_data;
+	if (mpu->open_output && (err = mpu->open_output(mpu)) < 0)
+		return err;
+	if (! test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode)) {
+		if (snd_mpu401_do_reset(mpu) < 0)
+			goto error_out;
+	}
+	mpu->substream_output = substream;
+	set_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode);
+	return 0;
+
+error_out:
+	if (mpu->open_output && mpu->close_output)
+		mpu->close_output(mpu);
+	return -EIO;
+}
+
+static int snd_mpu401_uart_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_mpu401 *mpu;
+	int err = 0;
+
+	mpu = substream->rmidi->private_data;
+	clear_bit(MPU401_MODE_BIT_INPUT, &mpu->mode);
+	mpu->substream_input = NULL;
+	if (! test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode))
+		err = snd_mpu401_uart_cmd(mpu, MPU401_RESET, 0);
+	if (mpu->close_input)
+		mpu->close_input(mpu);
+	if (err)
+		return -EIO;
+	return 0;
+}
+
+static int snd_mpu401_uart_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_mpu401 *mpu;
+	int err = 0;
+
+	mpu = substream->rmidi->private_data;
+	clear_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode);
+	mpu->substream_output = NULL;
+	if (! test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode))
+		err = snd_mpu401_uart_cmd(mpu, MPU401_RESET, 0);
+	if (mpu->close_output)
+		mpu->close_output(mpu);
+	if (err)
+		return -EIO;
+	return 0;
+}
+
+/*
+ * trigger input callback
+ */
+static void
+snd_mpu401_uart_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct snd_mpu401 *mpu;
+	int max = 64;
+
+	mpu = substream->rmidi->private_data;
+	if (up) {
+		if (! test_and_set_bit(MPU401_MODE_BIT_INPUT_TRIGGER,
+				       &mpu->mode)) {
+			/* first time - flush FIFO */
+			while (max-- > 0)
+				mpu->read(mpu, MPU401D(mpu));
+			if (mpu->irq < 0)
+				snd_mpu401_uart_add_timer(mpu, 1);
+		}
+		
+		/* read data in advance */
+		spin_lock_irqsave(&mpu->input_lock, flags);
+		snd_mpu401_uart_input_read(mpu);
+		spin_unlock_irqrestore(&mpu->input_lock, flags);
+	} else {
+		if (mpu->irq < 0)
+			snd_mpu401_uart_remove_timer(mpu, 1);
+		clear_bit(MPU401_MODE_BIT_INPUT_TRIGGER, &mpu->mode);
+	}
+
+}
+
+/*
+ * transfer input pending data
+ * call with input_lock spinlock held
+ */
+static void snd_mpu401_uart_input_read(struct snd_mpu401 * mpu)
+{
+	int max = 128;
+	unsigned char byte;
+
+	while (max-- > 0) {
+		if (! snd_mpu401_input_avail(mpu))
+			break; /* input not available */
+		byte = mpu->read(mpu, MPU401D(mpu));
+		if (test_bit(MPU401_MODE_BIT_INPUT_TRIGGER, &mpu->mode))
+			snd_rawmidi_receive(mpu->substream_input, &byte, 1);
+	}
+}
+
+/*
+ *  Tx FIFO sizes:
+ *    CS4237B			- 16 bytes
+ *    AudioDrive ES1688         - 12 bytes
+ *    S3 SonicVibes             -  8 bytes
+ *    SoundBlaster AWE 64       -  2 bytes (ugly hardware)
+ */
+
+/*
+ * write output pending bytes
+ * call with output_lock spinlock held
+ */
+static void snd_mpu401_uart_output_write(struct snd_mpu401 * mpu)
+{
+	unsigned char byte;
+	int max = 256;
+
+	do {
+		if (snd_rawmidi_transmit_peek(mpu->substream_output,
+					      &byte, 1) == 1) {
+			/*
+			 * Try twice because there is hardware that insists on
+			 * setting the output busy bit after each write.
+			 */
+			if (!snd_mpu401_output_ready(mpu) &&
+			    !snd_mpu401_output_ready(mpu))
+				break;	/* Tx FIFO full - try again later */
+			mpu->write(mpu, byte, MPU401D(mpu));
+			snd_rawmidi_transmit_ack(mpu->substream_output, 1);
+		} else {
+			snd_mpu401_uart_remove_timer (mpu, 0);
+			break;	/* no other data - leave the tx loop */
+		}
+	} while (--max > 0);
+}
+
+/*
+ * output trigger callback
+ */
+static void
+snd_mpu401_uart_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct snd_mpu401 *mpu;
+
+	mpu = substream->rmidi->private_data;
+	if (up) {
+		set_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode);
+
+		/* try to add the timer at each output trigger,
+		 * since the output timer might have been removed in
+		 * snd_mpu401_uart_output_write().
+		 */
+		if (! (mpu->info_flags & MPU401_INFO_TX_IRQ))
+			snd_mpu401_uart_add_timer(mpu, 0);
+
+		/* output pending data */
+		spin_lock_irqsave(&mpu->output_lock, flags);
+		snd_mpu401_uart_output_write(mpu);
+		spin_unlock_irqrestore(&mpu->output_lock, flags);
+	} else {
+		if (! (mpu->info_flags & MPU401_INFO_TX_IRQ))
+			snd_mpu401_uart_remove_timer(mpu, 0);
+		clear_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode);
+	}
+}
+
+/*
+
+ */
+
+static struct snd_rawmidi_ops snd_mpu401_uart_output =
+{
+	.open =		snd_mpu401_uart_output_open,
+	.close =	snd_mpu401_uart_output_close,
+	.trigger =	snd_mpu401_uart_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_mpu401_uart_input =
+{
+	.open =		snd_mpu401_uart_input_open,
+	.close =	snd_mpu401_uart_input_close,
+	.trigger =	snd_mpu401_uart_input_trigger,
+};
+
+static void snd_mpu401_uart_free(struct snd_rawmidi *rmidi)
+{
+	struct snd_mpu401 *mpu = rmidi->private_data;
+	if (mpu->irq_flags && mpu->irq >= 0)
+		free_irq(mpu->irq, (void *) mpu);
+	release_and_free_resource(mpu->res);
+	kfree(mpu);
+}
+
+/**
+ * snd_mpu401_uart_new - create an MPU401-UART instance
+ * @card: the card instance
+ * @device: the device index, zero-based
+ * @hardware: the hardware type, MPU401_HW_XXXX
+ * @port: the base address of MPU401 port
+ * @info_flags: bitflags MPU401_INFO_XXX
+ * @irq: the irq number, -1 if no interrupt for mpu
+ * @irq_flags: the irq request flags (SA_XXX), 0 if irq was already reserved.
+ * @rrawmidi: the pointer to store the new rawmidi instance
+ *
+ * Creates a new MPU-401 instance.
+ *
+ * Note that the rawmidi instance is returned on the rrawmidi argument,
+ * not the mpu401 instance itself.  To access to the mpu401 instance,
+ * cast from rawmidi->private_data (with struct snd_mpu401 magic-cast).
+ *
+ * Returns zero if successful, or a negative error code.
+ */
+int snd_mpu401_uart_new(struct snd_card *card, int device,
+			unsigned short hardware,
+			unsigned long port,
+			unsigned int info_flags,
+			int irq, int irq_flags,
+			struct snd_rawmidi ** rrawmidi)
+{
+	struct snd_mpu401 *mpu;
+	struct snd_rawmidi *rmidi;
+	int in_enable, out_enable;
+	int err;
+
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	if (! (info_flags & (MPU401_INFO_INPUT | MPU401_INFO_OUTPUT)))
+		info_flags |= MPU401_INFO_INPUT | MPU401_INFO_OUTPUT;
+	in_enable = (info_flags & MPU401_INFO_INPUT) ? 1 : 0;
+	out_enable = (info_flags & MPU401_INFO_OUTPUT) ? 1 : 0;
+	if ((err = snd_rawmidi_new(card, "MPU-401U", device,
+				   out_enable, in_enable, &rmidi)) < 0)
+		return err;
+	mpu = kzalloc(sizeof(*mpu), GFP_KERNEL);
+	if (mpu == NULL) {
+		snd_printk(KERN_ERR "mpu401_uart: cannot allocate\n");
+		snd_device_free(card, rmidi);
+		return -ENOMEM;
+	}
+	rmidi->private_data = mpu;
+	rmidi->private_free = snd_mpu401_uart_free;
+	spin_lock_init(&mpu->input_lock);
+	spin_lock_init(&mpu->output_lock);
+	spin_lock_init(&mpu->timer_lock);
+	mpu->hardware = hardware;
+	if (! (info_flags & MPU401_INFO_INTEGRATED)) {
+		int res_size = hardware == MPU401_HW_PC98II ? 4 : 2;
+		mpu->res = request_region(port, res_size, "MPU401 UART");
+		if (mpu->res == NULL) {
+			snd_printk(KERN_ERR "mpu401_uart: "
+				   "unable to grab port 0x%lx size %d\n",
+				   port, res_size);
+			snd_device_free(card, rmidi);
+			return -EBUSY;
+		}
+	}
+	if (info_flags & MPU401_INFO_MMIO) {
+		mpu->write = mpu401_write_mmio;
+		mpu->read = mpu401_read_mmio;
+	} else {
+		mpu->write = mpu401_write_port;
+		mpu->read = mpu401_read_port;
+	}
+	mpu->port = port;
+	if (hardware == MPU401_HW_PC98II)
+		mpu->cport = port + 2;
+	else
+		mpu->cport = port + 1;
+	if (irq >= 0 && irq_flags) {
+		if (request_irq(irq, snd_mpu401_uart_interrupt, irq_flags,
+				"MPU401 UART", (void *) mpu)) {
+			snd_printk(KERN_ERR "mpu401_uart: "
+				   "unable to grab IRQ %d\n", irq);
+			snd_device_free(card, rmidi);
+			return -EBUSY;
+		}
+	}
+	mpu->info_flags = info_flags;
+	mpu->irq = irq;
+	mpu->irq_flags = irq_flags;
+	if (card->shortname[0])
+		snprintf(rmidi->name, sizeof(rmidi->name), "%s MIDI",
+			 card->shortname);
+	else
+		sprintf(rmidi->name, "MPU-401 MIDI %d-%d",card->number, device);
+	if (out_enable) {
+		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+				    &snd_mpu401_uart_output);
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+	}
+	if (in_enable) {
+		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+				    &snd_mpu401_uart_input);
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+		if (out_enable)
+			rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+	}
+	mpu->rmidi = rmidi;
+	if (rrawmidi)
+		*rrawmidi = rmidi;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_mpu401_uart_new);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_mpu401_uart_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_mpu401_uart_exit(void)
+{
+}
+
+module_init(alsa_mpu401_uart_init)
+module_exit(alsa_mpu401_uart_exit)
diff --git a/linux/sound/drivers/mtpav.c b/linux/sound/drivers/mtpav.c
new file mode 100644
index 0000000..da03597
--- /dev/null
+++ b/linux/sound/drivers/mtpav.c
@@ -0,0 +1,792 @@
+/*
+ *      MOTU Midi Timepiece ALSA Main routines
+ *      Copyright by Michael T. Mayers (c) Jan 09, 2000
+ *      mail: michael@tweakoz.com
+ *      Thanks to John Galbraith
+ *
+ *      This program is free software; you can redistribute it and/or modify
+ *      it under the terms of the GNU General Public License as published by
+ *      the Free Software Foundation; either version 2 of the License, or
+ *      (at your option) any later version.
+ *
+ *      This program is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *      GNU General Public License for more details.
+ *
+ *      You should have received a copy of the GNU General Public License
+ *      along with this program; if not, write to the Free Software
+ *      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *
+ *      This driver is for the 'Mark Of The Unicorn' (MOTU)
+ *      MidiTimePiece AV multiport MIDI interface 
+ *
+ *      IOPORTS
+ *      -------
+ *      8 MIDI Ins and 8 MIDI outs
+ *      Video Sync In (BNC), Word Sync Out (BNC), 
+ *      ADAT Sync Out (DB9)
+ *      SMPTE in/out (1/4")
+ *      2 programmable pedal/footswitch inputs and 4 programmable MIDI controller knobs.
+ *      Macintosh RS422 serial port
+ *      RS422 "network" port for ganging multiple MTP's
+ *      PC Parallel Port ( which this driver currently uses )
+ *
+ *      MISC FEATURES
+ *      -------------
+ *      Hardware MIDI routing, merging, and filtering   
+ *      MIDI Synchronization to Video, ADAT, SMPTE and other Clock sources
+ *      128 'scene' memories, recallable from MIDI program change
+ *
+ *
+ * ChangeLog
+ * Jun 11 2001	Takashi Iwai <tiwai@suse.de>
+ *      - Recoded & debugged
+ *      - Added timer interrupt for midi outputs
+ *      - hwports is between 1 and 8, which specifies the number of hardware ports.
+ *        The three global ports, computer, adat and broadcast ports, are created
+ *        always after h/w and remote ports.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <linux/delay.h>
+
+#include <asm/io.h>
+
+/*
+ *      globals
+ */
+MODULE_AUTHOR("Michael T. Mayers");
+MODULE_DESCRIPTION("MOTU MidiTimePiece AV multiport MIDI");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{MOTU,MidiTimePiece AV multiport MIDI}}");
+
+// io resources
+#define MTPAV_IOBASE		0x378
+#define MTPAV_IRQ		7
+#define MTPAV_MAX_PORTS		8
+
+static int index = SNDRV_DEFAULT_IDX1;
+static char *id = SNDRV_DEFAULT_STR1;
+static long port = MTPAV_IOBASE;	/* 0x378, 0x278 */
+static int irq = MTPAV_IRQ;		/* 7, 5 */
+static int hwports = MTPAV_MAX_PORTS;	/* use hardware ports 1-8 */
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for MotuMTPAV MIDI.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for MotuMTPAV MIDI.");
+module_param(port, long, 0444);
+MODULE_PARM_DESC(port, "Parallel port # for MotuMTPAV MIDI.");
+module_param(irq, int, 0444);
+MODULE_PARM_DESC(irq, "Parallel IRQ # for MotuMTPAV MIDI.");
+module_param(hwports, int, 0444);
+MODULE_PARM_DESC(hwports, "Hardware ports # for MotuMTPAV MIDI.");
+
+static struct platform_device *device;
+
+/*
+ *      defines
+ */
+//#define USE_FAKE_MTP //       don't actually read/write to MTP device (for debugging without an actual unit) (does not work yet)
+
+// parallel port usage masks
+#define SIGS_BYTE 0x08
+#define SIGS_RFD 0x80
+#define SIGS_IRQ 0x40
+#define SIGS_IN0 0x10
+#define SIGS_IN1 0x20
+
+#define SIGC_WRITE 0x04
+#define SIGC_READ 0x08
+#define SIGC_INTEN 0x10
+
+#define DREG 0
+#define SREG 1
+#define CREG 2
+
+//
+#define MTPAV_MODE_INPUT_OPENED		0x01
+#define MTPAV_MODE_OUTPUT_OPENED	0x02
+#define MTPAV_MODE_INPUT_TRIGGERED	0x04
+#define MTPAV_MODE_OUTPUT_TRIGGERED	0x08
+
+#define NUMPORTS (0x12+1)
+
+
+/*
+ */
+
+struct mtpav_port {
+	u8 number;
+	u8 hwport;
+	u8 mode;
+	u8 running_status;
+	struct snd_rawmidi_substream *input;
+	struct snd_rawmidi_substream *output;
+};
+
+struct mtpav {
+	struct snd_card *card;
+	unsigned long port;
+	struct resource *res_port;
+	int irq;			/* interrupt (for inputs) */
+	spinlock_t spinlock;
+	int share_irq;			/* number of accesses to input interrupts */
+	int istimer;			/* number of accesses to timer interrupts */
+	struct timer_list timer;	/* timer interrupts for outputs */
+	struct snd_rawmidi *rmidi;
+	int num_ports;		/* number of hw ports (1-8) */
+	struct mtpav_port ports[NUMPORTS];	/* all ports including computer, adat and bc */
+
+	u32 inmidiport;		/* selected input midi port */
+	u32 inmidistate;	/* during midi command 0xf5 */
+
+	u32 outmidihwport;	/* selected output midi hw port */
+};
+
+
+/*
+ * possible hardware ports (selected by 0xf5 port message)
+ *      0x00		all ports
+ *      0x01 .. 0x08    this MTP's ports 1..8
+ *      0x09 .. 0x10    networked MTP's ports (9..16)
+ *      0x11            networked MTP's computer port
+ *      0x63            to ADAT
+ *
+ * mappig:
+ *  subdevice 0 - (X-1)    ports
+ *            X - (2*X-1)  networked ports
+ *            X            computer
+ *            X+1          ADAT
+ *            X+2          all ports
+ *
+ *  where X = chip->num_ports
+ */
+
+#define MTPAV_PIDX_COMPUTER	0
+#define MTPAV_PIDX_ADAT		1
+#define MTPAV_PIDX_BROADCAST	2
+
+
+static int translate_subdevice_to_hwport(struct mtpav *chip, int subdev)
+{
+	if (subdev < 0)
+		return 0x01; /* invalid - use port 0 as default */
+	else if (subdev < chip->num_ports)
+		return subdev + 1; /* single mtp port */
+	else if (subdev < chip->num_ports * 2)
+		return subdev - chip->num_ports + 0x09; /* remote port */
+	else if (subdev == chip->num_ports * 2 + MTPAV_PIDX_COMPUTER)
+		return 0x11; /* computer port */
+	else if (subdev == chip->num_ports + MTPAV_PIDX_ADAT)
+		return 0x63;		/* ADAT */
+	return 0; /* all ports */
+}
+
+static int translate_hwport_to_subdevice(struct mtpav *chip, int hwport)
+{
+	int p;
+	if (hwport <= 0x00) /* all ports */
+		return chip->num_ports + MTPAV_PIDX_BROADCAST;
+	else if (hwport <= 0x08) { /* single port */
+		p = hwport - 1;
+		if (p >= chip->num_ports)
+			p = 0;
+		return p;
+	} else if (hwport <= 0x10) { /* remote port */
+		p = hwport - 0x09 + chip->num_ports;
+		if (p >= chip->num_ports * 2)
+			p = chip->num_ports;
+		return p;
+	} else if (hwport == 0x11)  /* computer port */
+		return chip->num_ports + MTPAV_PIDX_COMPUTER;
+	else  /* ADAT */
+		return chip->num_ports + MTPAV_PIDX_ADAT;
+}
+
+
+/*
+ */
+
+static u8 snd_mtpav_getreg(struct mtpav *chip, u16 reg)
+{
+	u8 rval = 0;
+
+	if (reg == SREG) {
+		rval = inb(chip->port + SREG);
+		rval = (rval & 0xf8);
+	} else if (reg == CREG) {
+		rval = inb(chip->port + CREG);
+		rval = (rval & 0x1c);
+	}
+
+	return rval;
+}
+
+/*
+ */
+
+static inline void snd_mtpav_mputreg(struct mtpav *chip, u16 reg, u8 val)
+{
+	if (reg == DREG || reg == CREG)
+		outb(val, chip->port + reg);
+}
+
+/*
+ */
+
+static void snd_mtpav_wait_rfdhi(struct mtpav *chip)
+{
+	int counts = 10000;
+	u8 sbyte;
+
+	sbyte = snd_mtpav_getreg(chip, SREG);
+	while (!(sbyte & SIGS_RFD) && counts--) {
+		sbyte = snd_mtpav_getreg(chip, SREG);
+		udelay(10);
+	}
+}
+
+static void snd_mtpav_send_byte(struct mtpav *chip, u8 byte)
+{
+	u8 tcbyt;
+	u8 clrwrite;
+	u8 setwrite;
+
+	snd_mtpav_wait_rfdhi(chip);
+
+	/////////////////
+
+	tcbyt = snd_mtpav_getreg(chip, CREG);
+	clrwrite = tcbyt & (SIGC_WRITE ^ 0xff);
+	setwrite = tcbyt | SIGC_WRITE;
+
+	snd_mtpav_mputreg(chip, DREG, byte);
+	snd_mtpav_mputreg(chip, CREG, clrwrite);	// clear write bit
+
+	snd_mtpav_mputreg(chip, CREG, setwrite);	// set write bit
+
+}
+
+
+/*
+ */
+
+/* call this with spin lock held */
+static void snd_mtpav_output_port_write(struct mtpav *mtp_card,
+					struct mtpav_port *portp,
+					struct snd_rawmidi_substream *substream)
+{
+	u8 outbyte;
+
+	// Get the outbyte first, so we can emulate running status if
+	// necessary
+	if (snd_rawmidi_transmit(substream, &outbyte, 1) != 1)
+		return;
+
+	// send port change command if necessary
+
+	if (portp->hwport != mtp_card->outmidihwport) {
+		mtp_card->outmidihwport = portp->hwport;
+
+		snd_mtpav_send_byte(mtp_card, 0xf5);
+		snd_mtpav_send_byte(mtp_card, portp->hwport);
+		/*
+		snd_printk(KERN_DEBUG "new outport: 0x%x\n",
+			   (unsigned int) portp->hwport);
+		*/
+		if (!(outbyte & 0x80) && portp->running_status)
+			snd_mtpav_send_byte(mtp_card, portp->running_status);
+	}
+
+	// send data
+
+	do {
+		if (outbyte & 0x80)
+			portp->running_status = outbyte;
+		
+		snd_mtpav_send_byte(mtp_card, outbyte);
+	} while (snd_rawmidi_transmit(substream, &outbyte, 1) == 1);
+}
+
+static void snd_mtpav_output_write(struct snd_rawmidi_substream *substream)
+{
+	struct mtpav *mtp_card = substream->rmidi->private_data;
+	struct mtpav_port *portp = &mtp_card->ports[substream->number];
+	unsigned long flags;
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	snd_mtpav_output_port_write(mtp_card, portp, substream);
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+}
+
+
+/*
+ *      mtpav control
+ */
+
+static void snd_mtpav_portscan(struct mtpav *chip)	// put mtp into smart routing mode
+{
+	u8 p;
+
+	for (p = 0; p < 8; p++) {
+		snd_mtpav_send_byte(chip, 0xf5);
+		snd_mtpav_send_byte(chip, p);
+		snd_mtpav_send_byte(chip, 0xfe);
+	}
+}
+
+/*
+ */
+
+static int snd_mtpav_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct mtpav *mtp_card = substream->rmidi->private_data;
+	struct mtpav_port *portp = &mtp_card->ports[substream->number];
+	unsigned long flags;
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	portp->mode |= MTPAV_MODE_INPUT_OPENED;
+	portp->input = substream;
+	if (mtp_card->share_irq++ == 0)
+		snd_mtpav_mputreg(mtp_card, CREG, (SIGC_INTEN | SIGC_WRITE));	// enable pport interrupts
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+	return 0;
+}
+
+/*
+ */
+
+static int snd_mtpav_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct mtpav *mtp_card = substream->rmidi->private_data;
+	struct mtpav_port *portp = &mtp_card->ports[substream->number];
+	unsigned long flags;
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	portp->mode &= ~MTPAV_MODE_INPUT_OPENED;
+	portp->input = NULL;
+	if (--mtp_card->share_irq == 0)
+		snd_mtpav_mputreg(mtp_card, CREG, 0);	// disable pport interrupts
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+	return 0;
+}
+
+/*
+ */
+
+static void snd_mtpav_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct mtpav *mtp_card = substream->rmidi->private_data;
+	struct mtpav_port *portp = &mtp_card->ports[substream->number];
+	unsigned long flags;
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	if (up)
+		portp->mode |= MTPAV_MODE_INPUT_TRIGGERED;
+	else
+		portp->mode &= ~MTPAV_MODE_INPUT_TRIGGERED;
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+
+}
+
+
+/*
+ * timer interrupt for outputs
+ */
+
+static void snd_mtpav_output_timer(unsigned long data)
+{
+	unsigned long flags;
+	struct mtpav *chip = (struct mtpav *)data;
+	int p;
+
+	spin_lock_irqsave(&chip->spinlock, flags);
+	/* reprogram timer */
+	chip->timer.expires = 1 + jiffies;
+	add_timer(&chip->timer);
+	/* process each port */
+	for (p = 0; p <= chip->num_ports * 2 + MTPAV_PIDX_BROADCAST; p++) {
+		struct mtpav_port *portp = &chip->ports[p];
+		if ((portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED) && portp->output)
+			snd_mtpav_output_port_write(chip, portp, portp->output);
+	}
+	spin_unlock_irqrestore(&chip->spinlock, flags);
+}
+
+/* spinlock held! */
+static void snd_mtpav_add_output_timer(struct mtpav *chip)
+{
+	chip->timer.expires = 1 + jiffies;
+	add_timer(&chip->timer);
+}
+
+/* spinlock held! */
+static void snd_mtpav_remove_output_timer(struct mtpav *chip)
+{
+	del_timer(&chip->timer);
+}
+
+/*
+ */
+
+static int snd_mtpav_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct mtpav *mtp_card = substream->rmidi->private_data;
+	struct mtpav_port *portp = &mtp_card->ports[substream->number];
+	unsigned long flags;
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	portp->mode |= MTPAV_MODE_OUTPUT_OPENED;
+	portp->output = substream;
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+	return 0;
+};
+
+/*
+ */
+
+static int snd_mtpav_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct mtpav *mtp_card = substream->rmidi->private_data;
+	struct mtpav_port *portp = &mtp_card->ports[substream->number];
+	unsigned long flags;
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	portp->mode &= ~MTPAV_MODE_OUTPUT_OPENED;
+	portp->output = NULL;
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+	return 0;
+};
+
+/*
+ */
+
+static void snd_mtpav_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct mtpav *mtp_card = substream->rmidi->private_data;
+	struct mtpav_port *portp = &mtp_card->ports[substream->number];
+	unsigned long flags;
+
+	spin_lock_irqsave(&mtp_card->spinlock, flags);
+	if (up) {
+		if (! (portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED)) {
+			if (mtp_card->istimer++ == 0)
+				snd_mtpav_add_output_timer(mtp_card);
+			portp->mode |= MTPAV_MODE_OUTPUT_TRIGGERED;
+		}
+	} else {
+		portp->mode &= ~MTPAV_MODE_OUTPUT_TRIGGERED;
+		if (--mtp_card->istimer == 0)
+			snd_mtpav_remove_output_timer(mtp_card);
+	}
+	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
+
+	if (up)
+		snd_mtpav_output_write(substream);
+}
+
+/*
+ * midi interrupt for inputs
+ */
+
+static void snd_mtpav_inmidi_process(struct mtpav *mcrd, u8 inbyte)
+{
+	struct mtpav_port *portp;
+
+	if ((int)mcrd->inmidiport > mcrd->num_ports * 2 + MTPAV_PIDX_BROADCAST)
+		return;
+
+	portp = &mcrd->ports[mcrd->inmidiport];
+	if (portp->mode & MTPAV_MODE_INPUT_TRIGGERED)
+		snd_rawmidi_receive(portp->input, &inbyte, 1);
+}
+
+static void snd_mtpav_inmidi_h(struct mtpav *mcrd, u8 inbyte)
+{
+	if (inbyte >= 0xf8) {
+		/* real-time midi code */
+		snd_mtpav_inmidi_process(mcrd, inbyte);
+		return;
+	}
+
+	if (mcrd->inmidistate == 0) {	// awaiting command
+		if (inbyte == 0xf5)	// MTP port #
+			mcrd->inmidistate = 1;
+		else
+			snd_mtpav_inmidi_process(mcrd, inbyte);
+	} else if (mcrd->inmidistate) {
+		mcrd->inmidiport = translate_hwport_to_subdevice(mcrd, inbyte);
+		mcrd->inmidistate = 0;
+	}
+}
+
+static void snd_mtpav_read_bytes(struct mtpav *mcrd)
+{
+	u8 clrread, setread;
+	u8 mtp_read_byte;
+	u8 sr, cbyt;
+	int i;
+
+	u8 sbyt = snd_mtpav_getreg(mcrd, SREG);
+
+	/* printk(KERN_DEBUG "snd_mtpav_read_bytes() sbyt: 0x%x\n", sbyt); */
+
+	if (!(sbyt & SIGS_BYTE))
+		return;
+
+	cbyt = snd_mtpav_getreg(mcrd, CREG);
+	clrread = cbyt & (SIGC_READ ^ 0xff);
+	setread = cbyt | SIGC_READ;
+
+	do {
+
+		mtp_read_byte = 0;
+		for (i = 0; i < 4; i++) {
+			snd_mtpav_mputreg(mcrd, CREG, setread);
+			sr = snd_mtpav_getreg(mcrd, SREG);
+			snd_mtpav_mputreg(mcrd, CREG, clrread);
+
+			sr &= SIGS_IN0 | SIGS_IN1;
+			sr >>= 4;
+			mtp_read_byte |= sr << (i * 2);
+		}
+
+		snd_mtpav_inmidi_h(mcrd, mtp_read_byte);
+
+		sbyt = snd_mtpav_getreg(mcrd, SREG);
+
+	} while (sbyt & SIGS_BYTE);
+}
+
+static irqreturn_t snd_mtpav_irqh(int irq, void *dev_id)
+{
+	struct mtpav *mcard = dev_id;
+
+	spin_lock(&mcard->spinlock);
+	snd_mtpav_read_bytes(mcard);
+	spin_unlock(&mcard->spinlock);
+	return IRQ_HANDLED;
+}
+
+/*
+ * get ISA resources
+ */
+static int __devinit snd_mtpav_get_ISA(struct mtpav * mcard)
+{
+	if ((mcard->res_port = request_region(port, 3, "MotuMTPAV MIDI")) == NULL) {
+		snd_printk(KERN_ERR "MTVAP port 0x%lx is busy\n", port);
+		return -EBUSY;
+	}
+	mcard->port = port;
+	if (request_irq(irq, snd_mtpav_irqh, IRQF_DISABLED, "MOTU MTPAV", mcard)) {
+		snd_printk(KERN_ERR "MTVAP IRQ %d busy\n", irq);
+		return -EBUSY;
+	}
+	mcard->irq = irq;
+	return 0;
+}
+
+
+/*
+ */
+
+static struct snd_rawmidi_ops snd_mtpav_output = {
+	.open =		snd_mtpav_output_open,
+	.close =	snd_mtpav_output_close,
+	.trigger =	snd_mtpav_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_mtpav_input = {
+	.open =		snd_mtpav_input_open,
+	.close =	snd_mtpav_input_close,
+	.trigger =	snd_mtpav_input_trigger,
+};
+
+
+/*
+ * get RAWMIDI resources
+ */
+
+static void __devinit snd_mtpav_set_name(struct mtpav *chip,
+				      struct snd_rawmidi_substream *substream)
+{
+	if (substream->number >= 0 && substream->number < chip->num_ports)
+		sprintf(substream->name, "MTP direct %d", (substream->number % chip->num_ports) + 1);
+	else if (substream->number >= 8 && substream->number < chip->num_ports * 2)
+		sprintf(substream->name, "MTP remote %d", (substream->number % chip->num_ports) + 1);
+	else if (substream->number == chip->num_ports * 2)
+		strcpy(substream->name, "MTP computer");
+	else if (substream->number == chip->num_ports * 2 + 1)
+		strcpy(substream->name, "MTP ADAT");
+	else
+		strcpy(substream->name, "MTP broadcast");
+}
+
+static int __devinit snd_mtpav_get_RAWMIDI(struct mtpav *mcard)
+{
+	int rval;
+	struct snd_rawmidi *rawmidi;
+	struct snd_rawmidi_substream *substream;
+	struct list_head *list;
+
+	if (hwports < 1)
+		hwports = 1;
+	else if (hwports > 8)
+		hwports = 8;
+	mcard->num_ports = hwports;
+
+	if ((rval = snd_rawmidi_new(mcard->card, "MotuMIDI", 0,
+				    mcard->num_ports * 2 + MTPAV_PIDX_BROADCAST + 1,
+				    mcard->num_ports * 2 + MTPAV_PIDX_BROADCAST + 1,
+				    &mcard->rmidi)) < 0)
+		return rval;
+	rawmidi = mcard->rmidi;
+	rawmidi->private_data = mcard;
+
+	list_for_each(list, &rawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) {
+		substream = list_entry(list, struct snd_rawmidi_substream, list);
+		snd_mtpav_set_name(mcard, substream);
+		substream->ops = &snd_mtpav_input;
+	}
+	list_for_each(list, &rawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) {
+		substream = list_entry(list, struct snd_rawmidi_substream, list);
+		snd_mtpav_set_name(mcard, substream);
+		substream->ops = &snd_mtpav_output;
+		mcard->ports[substream->number].hwport = translate_subdevice_to_hwport(mcard, substream->number);
+	}
+	rawmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT |
+			       SNDRV_RAWMIDI_INFO_DUPLEX;
+	sprintf(rawmidi->name, "MTP AV MIDI");
+	return 0;
+}
+
+/*
+ */
+
+static void snd_mtpav_free(struct snd_card *card)
+{
+	struct mtpav *crd = card->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&crd->spinlock, flags);
+	if (crd->istimer > 0)
+		snd_mtpav_remove_output_timer(crd);
+	spin_unlock_irqrestore(&crd->spinlock, flags);
+	if (crd->irq >= 0)
+		free_irq(crd->irq, (void *)crd);
+	release_and_free_resource(crd->res_port);
+}
+
+/*
+ */
+static int __devinit snd_mtpav_probe(struct platform_device *dev)
+{
+	struct snd_card *card;
+	int err;
+	struct mtpav *mtp_card;
+
+	err = snd_card_create(index, id, THIS_MODULE, sizeof(*mtp_card), &card);
+	if (err < 0)
+		return err;
+
+	mtp_card = card->private_data;
+	spin_lock_init(&mtp_card->spinlock);
+	init_timer(&mtp_card->timer);
+	mtp_card->card = card;
+	mtp_card->irq = -1;
+	mtp_card->share_irq = 0;
+	mtp_card->inmidistate = 0;
+	mtp_card->outmidihwport = 0xffffffff;
+	init_timer(&mtp_card->timer);
+	mtp_card->timer.function = snd_mtpav_output_timer;
+	mtp_card->timer.data = (unsigned long) mtp_card;
+
+	card->private_free = snd_mtpav_free;
+
+	err = snd_mtpav_get_RAWMIDI(mtp_card);
+	if (err < 0)
+		goto __error;
+
+	mtp_card->inmidiport = mtp_card->num_ports + MTPAV_PIDX_BROADCAST;
+
+	err = snd_mtpav_get_ISA(mtp_card);
+	if (err < 0)
+		goto __error;
+
+	strcpy(card->driver, "MTPAV");
+	strcpy(card->shortname, "MTPAV on parallel port");
+	snprintf(card->longname, sizeof(card->longname),
+		 "MTPAV on parallel port at 0x%lx", port);
+
+	snd_mtpav_portscan(mtp_card);
+
+	snd_card_set_dev(card, &dev->dev);
+	err = snd_card_register(mtp_card->card);
+	if (err < 0)
+		goto __error;
+
+	platform_set_drvdata(dev, card);
+	printk(KERN_INFO "Motu MidiTimePiece on parallel port irq: %d ioport: 0x%lx\n", irq, port);
+	return 0;
+
+ __error:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit snd_mtpav_remove(struct platform_device *devptr)
+{
+	snd_card_free(platform_get_drvdata(devptr));
+	platform_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#define SND_MTPAV_DRIVER	"snd_mtpav"
+
+static struct platform_driver snd_mtpav_driver = {
+	.probe		= snd_mtpav_probe,
+	.remove		= __devexit_p(snd_mtpav_remove),
+	.driver		= {
+		.name	= SND_MTPAV_DRIVER
+	},
+};
+
+static int __init alsa_card_mtpav_init(void)
+{
+	int err;
+
+	if ((err = platform_driver_register(&snd_mtpav_driver)) < 0)
+		return err;
+
+	device = platform_device_register_simple(SND_MTPAV_DRIVER, -1, NULL, 0);
+	if (!IS_ERR(device)) {
+		if (platform_get_drvdata(device))
+			return 0;
+		platform_device_unregister(device);
+		err = -ENODEV;
+	} else
+		err = PTR_ERR(device);
+	platform_driver_unregister(&snd_mtpav_driver);
+	return err;
+}
+
+static void __exit alsa_card_mtpav_exit(void)
+{
+	platform_device_unregister(device);
+	platform_driver_unregister(&snd_mtpav_driver);
+}
+
+module_init(alsa_card_mtpav_init)
+module_exit(alsa_card_mtpav_exit)
diff --git a/linux/sound/drivers/mts64.c b/linux/sound/drivers/mts64.c
new file mode 100644
index 0000000..8539ab0
--- /dev/null
+++ b/linux/sound/drivers/mts64.c
@@ -0,0 +1,1089 @@
+/*     
+ *   ALSA Driver for Ego Systems Inc. (ESI) Miditerminal 4140
+ *   Copyright (c) 2006 by Matthias König <mk@phasorlab.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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/parport.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <sound/control.h>
+
+#define CARD_NAME "Miditerminal 4140"
+#define DRIVER_NAME "MTS64"
+#define PLATFORM_DRIVER "snd_mts64"
+
+static int index[SNDRV_CARDS]  = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS]   = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+static struct platform_device *platform_devices[SNDRV_CARDS]; 
+static int device_count;
+
+module_param_array(index, int, NULL, S_IRUGO);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, S_IRUGO);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, S_IRUGO);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+
+MODULE_AUTHOR("Matthias Koenig <mk@phasorlab.de>");
+MODULE_DESCRIPTION("ESI Miditerminal 4140");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESI,Miditerminal 4140}}");
+
+/*********************************************************************
+ * Chip specific
+ *********************************************************************/
+#define MTS64_NUM_INPUT_PORTS 5
+#define MTS64_NUM_OUTPUT_PORTS 4
+#define MTS64_SMPTE_SUBSTREAM 4
+
+struct mts64 {
+	spinlock_t lock;
+	struct snd_card *card;
+	struct snd_rawmidi *rmidi;
+	struct pardevice *pardev;
+	int pardev_claimed;
+
+	int open_count;
+	int current_midi_output_port;
+	int current_midi_input_port;
+	u8 mode[MTS64_NUM_INPUT_PORTS];
+	struct snd_rawmidi_substream *midi_input_substream[MTS64_NUM_INPUT_PORTS];
+	int smpte_switch;
+	u8 time[4]; /* [0]=hh, [1]=mm, [2]=ss, [3]=ff */
+	u8 fps;
+};
+
+static int snd_mts64_free(struct mts64 *mts)
+{
+	kfree(mts);
+	return 0;
+}
+
+static int __devinit snd_mts64_create(struct snd_card *card, 
+				      struct pardevice *pardev, 
+				      struct mts64 **rchip)
+{
+	struct mts64 *mts;
+
+	*rchip = NULL;
+
+	mts = kzalloc(sizeof(struct mts64), GFP_KERNEL);
+	if (mts == NULL) 
+		return -ENOMEM;
+
+	/* Init chip specific data */
+	spin_lock_init(&mts->lock);
+	mts->card = card;
+	mts->pardev = pardev;
+	mts->current_midi_output_port = -1;
+	mts->current_midi_input_port = -1;
+
+	*rchip = mts;
+
+	return 0;
+}
+
+/*********************************************************************
+ * HW register related constants
+ *********************************************************************/
+
+/* Status Bits */
+#define MTS64_STAT_BSY             0x80
+#define MTS64_STAT_BIT_SET         0x20  /* readout process, bit is set */
+#define MTS64_STAT_PORT            0x10  /* read byte is a port number */
+
+/* Control Bits */
+#define MTS64_CTL_READOUT          0x08  /* enable readout */
+#define MTS64_CTL_WRITE_CMD        0x06  
+#define MTS64_CTL_WRITE_DATA       0x02  
+#define MTS64_CTL_STROBE           0x01  
+
+/* Command */
+#define MTS64_CMD_RESET            0xfe
+#define MTS64_CMD_PROBE            0x8f  /* Used in probing procedure */
+#define MTS64_CMD_SMPTE_SET_TIME   0xe8
+#define MTS64_CMD_SMPTE_SET_FPS    0xee
+#define MTS64_CMD_SMPTE_STOP       0xef
+#define MTS64_CMD_SMPTE_FPS_24     0xe3
+#define MTS64_CMD_SMPTE_FPS_25     0xe2
+#define MTS64_CMD_SMPTE_FPS_2997   0xe4 
+#define MTS64_CMD_SMPTE_FPS_30D    0xe1
+#define MTS64_CMD_SMPTE_FPS_30     0xe0
+#define MTS64_CMD_COM_OPEN         0xf8  /* setting the communication mode */
+#define MTS64_CMD_COM_CLOSE1       0xff  /* clearing communication mode */
+#define MTS64_CMD_COM_CLOSE2       0xf5
+
+/*********************************************************************
+ * Hardware specific functions
+ *********************************************************************/
+static void mts64_enable_readout(struct parport *p);
+static void mts64_disable_readout(struct parport *p);
+static int mts64_device_ready(struct parport *p);
+static int mts64_device_init(struct parport *p);
+static int mts64_device_open(struct mts64 *mts);
+static int mts64_device_close(struct mts64 *mts);
+static u8 mts64_map_midi_input(u8 c);
+static int mts64_probe(struct parport *p);
+static u16 mts64_read(struct parport *p);
+static u8 mts64_read_char(struct parport *p);
+static void mts64_smpte_start(struct parport *p,
+			      u8 hours, u8 minutes,
+			      u8 seconds, u8 frames,
+			      u8 idx);
+static void mts64_smpte_stop(struct parport *p);
+static void mts64_write_command(struct parport *p, u8 c);
+static void mts64_write_data(struct parport *p, u8 c);
+static void mts64_write_midi(struct mts64 *mts, u8 c, int midiport);
+
+
+/*  Enables the readout procedure
+ *
+ *  Before we can read a midi byte from the device, we have to set
+ *  bit 3 of control port.
+ */
+static void mts64_enable_readout(struct parport *p)
+{
+	u8 c;
+
+	c = parport_read_control(p);
+	c |= MTS64_CTL_READOUT;
+	parport_write_control(p, c); 
+}
+
+/*  Disables readout 
+ *
+ *  Readout is disabled by clearing bit 3 of control
+ */
+static void mts64_disable_readout(struct parport *p)
+{
+	u8 c;
+
+	c = parport_read_control(p);
+	c &= ~MTS64_CTL_READOUT;
+	parport_write_control(p, c);
+}
+
+/*  waits for device ready
+ *
+ *  Checks if BUSY (Bit 7 of status) is clear
+ *  1 device ready
+ *  0 failure
+ */
+static int mts64_device_ready(struct parport *p)
+{
+	int i;
+	u8 c;
+
+	for (i = 0; i < 0xffff; ++i) {
+		c = parport_read_status(p);
+		c &= MTS64_STAT_BSY;
+		if (c != 0) 
+			return 1;
+	} 
+
+	return 0;
+}
+
+/*  Init device (LED blinking startup magic)
+ *
+ *  Returns:
+ *  0 init ok
+ *  -EIO failure
+ */
+static int __devinit mts64_device_init(struct parport *p)
+{
+	int i;
+
+	mts64_write_command(p, MTS64_CMD_RESET);
+
+	for (i = 0; i < 64; ++i) {
+		msleep(100);
+
+		if (mts64_probe(p) == 0) {
+			/* success */
+			mts64_disable_readout(p);
+			return 0;
+		}
+	}
+	mts64_disable_readout(p);
+
+	return -EIO;
+}
+
+/* 
+ *  Opens the device (set communication mode)
+ */
+static int mts64_device_open(struct mts64 *mts)
+{
+	int i;
+	struct parport *p = mts->pardev->port;
+
+	for (i = 0; i < 5; ++i)
+		mts64_write_command(p, MTS64_CMD_COM_OPEN);
+
+	return 0;
+}
+
+/*  
+ *  Close device (clear communication mode)
+ */
+static int mts64_device_close(struct mts64 *mts)
+{
+	int i;
+	struct parport *p = mts->pardev->port;
+
+	for (i = 0; i < 5; ++i) {
+		mts64_write_command(p, MTS64_CMD_COM_CLOSE1);
+		mts64_write_command(p, MTS64_CMD_COM_CLOSE2);
+	}
+
+	return 0;
+}
+
+/*  map hardware port to substream number
+ * 
+ *  When reading a byte from the device, the device tells us
+ *  on what port the byte is. This HW port has to be mapped to
+ *  the midiport (substream number).
+ *  substream 0-3 are Midiports 1-4
+ *  substream 4 is SMPTE Timecode
+ *  The mapping is done by the table:
+ *  HW | 0 | 1 | 2 | 3 | 4 
+ *  SW | 0 | 1 | 4 | 2 | 3
+ */
+static u8 mts64_map_midi_input(u8 c)
+{
+	static u8 map[] = { 0, 1, 4, 2, 3 };
+
+	return map[c];
+}
+
+
+/*  Probe parport for device
+ *
+ *  Do we have a Miditerminal 4140 on parport? 
+ *  Returns:
+ *  0       device found
+ *  -ENODEV no device
+ */
+static int __devinit mts64_probe(struct parport *p)
+{
+	u8 c;
+
+	mts64_smpte_stop(p);
+	mts64_write_command(p, MTS64_CMD_PROBE);
+
+	msleep(50);
+	
+	c = mts64_read(p);
+
+	c &= 0x00ff;
+	if (c != MTS64_CMD_PROBE) 
+		return -ENODEV;
+	else 
+		return 0;
+
+}
+
+/*  Read byte incl. status from device
+ *
+ *  Returns:
+ *  data in lower 8 bits and status in upper 8 bits
+ */
+static u16 mts64_read(struct parport *p)
+{
+	u8 data, status;
+
+	mts64_device_ready(p);
+	mts64_enable_readout(p);
+	status = parport_read_status(p);
+	data = mts64_read_char(p);
+	mts64_disable_readout(p);
+
+	return (status << 8) | data;
+}
+
+/*  Read a byte from device
+ *
+ *  Note, that readout mode has to be enabled.
+ *  readout procedure is as follows: 
+ *  - Write number of the Bit to read to DATA
+ *  - Read STATUS
+ *  - Bit 5 of STATUS indicates if Bit is set
+ *
+ *  Returns:
+ *  Byte read from device
+ */
+static u8 mts64_read_char(struct parport *p)
+{
+	u8 c = 0;
+	u8 status;
+	u8 i;
+
+	for (i = 0; i < 8; ++i) {
+		parport_write_data(p, i);
+		c >>= 1;
+		status = parport_read_status(p);
+		if (status & MTS64_STAT_BIT_SET) 
+			c |= 0x80;
+	}
+	
+	return c;
+}
+
+/*  Starts SMPTE Timecode generation
+ *
+ *  The device creates SMPTE Timecode by hardware.
+ *  0 24 fps
+ *  1 25 fps
+ *  2 29.97 fps
+ *  3 30 fps (Drop-frame)
+ *  4 30 fps
+ */
+static void mts64_smpte_start(struct parport *p,
+			      u8 hours, u8 minutes,
+			      u8 seconds, u8 frames,
+			      u8 idx)
+{
+	static u8 fps[5] = { MTS64_CMD_SMPTE_FPS_24, 
+			     MTS64_CMD_SMPTE_FPS_25,
+			     MTS64_CMD_SMPTE_FPS_2997, 
+			     MTS64_CMD_SMPTE_FPS_30D,
+			     MTS64_CMD_SMPTE_FPS_30    };
+
+	mts64_write_command(p, MTS64_CMD_SMPTE_SET_TIME);
+	mts64_write_command(p, frames);
+	mts64_write_command(p, seconds);
+	mts64_write_command(p, minutes);
+	mts64_write_command(p, hours);
+
+	mts64_write_command(p, MTS64_CMD_SMPTE_SET_FPS);
+	mts64_write_command(p, fps[idx]);
+}
+
+/*  Stops SMPTE Timecode generation
+ */
+static void mts64_smpte_stop(struct parport *p)
+{
+	mts64_write_command(p, MTS64_CMD_SMPTE_STOP);
+}
+
+/*  Write a command byte to device
+ */
+static void mts64_write_command(struct parport *p, u8 c)
+{
+	mts64_device_ready(p);
+
+	parport_write_data(p, c);
+
+	parport_write_control(p, MTS64_CTL_WRITE_CMD);
+	parport_write_control(p, MTS64_CTL_WRITE_CMD | MTS64_CTL_STROBE);
+	parport_write_control(p, MTS64_CTL_WRITE_CMD);
+}
+
+/*  Write a data byte to device 
+ */
+static void mts64_write_data(struct parport *p, u8 c)
+{
+	mts64_device_ready(p);
+
+	parport_write_data(p, c);
+
+	parport_write_control(p, MTS64_CTL_WRITE_DATA);
+	parport_write_control(p, MTS64_CTL_WRITE_DATA | MTS64_CTL_STROBE);
+	parport_write_control(p, MTS64_CTL_WRITE_DATA);
+}
+
+/*  Write a MIDI byte to midiport
+ *
+ *  midiport ranges from 0-3 and maps to Ports 1-4
+ *  assumptions: communication mode is on
+ */
+static void mts64_write_midi(struct mts64 *mts, u8 c,
+			     int midiport)
+{
+	struct parport *p = mts->pardev->port;
+
+	/* check current midiport */
+	if (mts->current_midi_output_port != midiport)
+		mts64_write_command(p, midiport);
+
+	/* write midi byte */
+	mts64_write_data(p, c);
+}
+
+/*********************************************************************
+ * Control elements
+ *********************************************************************/
+
+/* SMPTE Switch */
+#define snd_mts64_ctl_smpte_switch_info		snd_ctl_boolean_mono_info
+
+static int snd_mts64_ctl_smpte_switch_get(struct snd_kcontrol* kctl,
+					  struct snd_ctl_elem_value *uctl)
+{
+	struct mts64 *mts = snd_kcontrol_chip(kctl);
+
+	spin_lock_irq(&mts->lock);
+	uctl->value.integer.value[0] = mts->smpte_switch;
+	spin_unlock_irq(&mts->lock);
+
+	return 0;
+}
+
+/* smpte_switch is not accessed from IRQ handler, so we just need
+   to protect the HW access */
+static int snd_mts64_ctl_smpte_switch_put(struct snd_kcontrol* kctl,
+					  struct snd_ctl_elem_value *uctl)
+{
+	struct mts64 *mts = snd_kcontrol_chip(kctl);
+	int changed = 0;
+	int val = !!uctl->value.integer.value[0];
+
+	spin_lock_irq(&mts->lock);
+	if (mts->smpte_switch == val)
+		goto __out;
+
+	changed = 1;
+	mts->smpte_switch = val;
+	if (mts->smpte_switch) {
+		mts64_smpte_start(mts->pardev->port,
+				  mts->time[0], mts->time[1],
+				  mts->time[2], mts->time[3],
+				  mts->fps);
+	} else {
+		mts64_smpte_stop(mts->pardev->port);
+	}
+__out:
+	spin_unlock_irq(&mts->lock);
+	return changed;
+}
+
+static struct snd_kcontrol_new mts64_ctl_smpte_switch __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
+	.name  = "SMPTE Playback Switch",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value = 0,
+	.info = snd_mts64_ctl_smpte_switch_info,
+	.get  = snd_mts64_ctl_smpte_switch_get,
+	.put  = snd_mts64_ctl_smpte_switch_put
+};
+
+/* Time */
+static int snd_mts64_ctl_smpte_time_h_info(struct snd_kcontrol *kctl,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 23;
+	return 0;
+}
+
+static int snd_mts64_ctl_smpte_time_f_info(struct snd_kcontrol *kctl,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 99;
+	return 0;
+}
+
+static int snd_mts64_ctl_smpte_time_info(struct snd_kcontrol *kctl,
+					 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 59;
+	return 0;
+}
+
+static int snd_mts64_ctl_smpte_time_get(struct snd_kcontrol *kctl,
+					struct snd_ctl_elem_value *uctl)
+{
+	struct mts64 *mts = snd_kcontrol_chip(kctl);
+	int idx = kctl->private_value;
+
+	spin_lock_irq(&mts->lock);
+	uctl->value.integer.value[0] = mts->time[idx];
+	spin_unlock_irq(&mts->lock);
+
+	return 0;
+}
+
+static int snd_mts64_ctl_smpte_time_put(struct snd_kcontrol *kctl,
+					struct snd_ctl_elem_value *uctl)
+{
+	struct mts64 *mts = snd_kcontrol_chip(kctl);
+	int idx = kctl->private_value;
+	unsigned int time = uctl->value.integer.value[0] % 60;
+	int changed = 0;
+
+	spin_lock_irq(&mts->lock);
+	if (mts->time[idx] != time) {
+		changed = 1;
+		mts->time[idx] = time;
+	}
+	spin_unlock_irq(&mts->lock);
+
+	return changed;
+}
+
+static struct snd_kcontrol_new mts64_ctl_smpte_time_hours __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
+	.name  = "SMPTE Time Hours",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value = 0,
+	.info = snd_mts64_ctl_smpte_time_h_info,
+	.get  = snd_mts64_ctl_smpte_time_get,
+	.put  = snd_mts64_ctl_smpte_time_put
+};
+
+static struct snd_kcontrol_new mts64_ctl_smpte_time_minutes __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
+	.name  = "SMPTE Time Minutes",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value = 1,
+	.info = snd_mts64_ctl_smpte_time_info,
+	.get  = snd_mts64_ctl_smpte_time_get,
+	.put  = snd_mts64_ctl_smpte_time_put
+};
+
+static struct snd_kcontrol_new mts64_ctl_smpte_time_seconds __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
+	.name  = "SMPTE Time Seconds",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value = 2,
+	.info = snd_mts64_ctl_smpte_time_info,
+	.get  = snd_mts64_ctl_smpte_time_get,
+	.put  = snd_mts64_ctl_smpte_time_put
+};
+
+static struct snd_kcontrol_new mts64_ctl_smpte_time_frames __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
+	.name  = "SMPTE Time Frames",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value = 3,
+	.info = snd_mts64_ctl_smpte_time_f_info,
+	.get  = snd_mts64_ctl_smpte_time_get,
+	.put  = snd_mts64_ctl_smpte_time_put
+};
+
+/* FPS */
+static int snd_mts64_ctl_smpte_fps_info(struct snd_kcontrol *kctl,
+					struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[5] = { "24",
+				  "25",
+				  "29.97",
+				  "30D",
+				  "30"    };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 5;
+	if (uinfo->value.enumerated.item > 4)
+		uinfo->value.enumerated.item = 4;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	
+	return 0;
+}
+
+static int snd_mts64_ctl_smpte_fps_get(struct snd_kcontrol *kctl,
+				       struct snd_ctl_elem_value *uctl)
+{
+	struct mts64 *mts = snd_kcontrol_chip(kctl);
+
+	spin_lock_irq(&mts->lock);
+	uctl->value.enumerated.item[0] = mts->fps;
+	spin_unlock_irq(&mts->lock);
+
+	return 0;
+}
+
+static int snd_mts64_ctl_smpte_fps_put(struct snd_kcontrol *kctl,
+				       struct snd_ctl_elem_value *uctl)
+{
+	struct mts64 *mts = snd_kcontrol_chip(kctl);
+	int changed = 0;
+
+	if (uctl->value.enumerated.item[0] >= 5)
+		return -EINVAL;
+	spin_lock_irq(&mts->lock);
+	if (mts->fps != uctl->value.enumerated.item[0]) {
+		changed = 1;
+		mts->fps = uctl->value.enumerated.item[0];
+	}
+	spin_unlock_irq(&mts->lock);
+
+	return changed;
+}
+
+static struct snd_kcontrol_new mts64_ctl_smpte_fps __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
+	.name  = "SMPTE Fps",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value = 0,
+	.info  = snd_mts64_ctl_smpte_fps_info,
+	.get   = snd_mts64_ctl_smpte_fps_get,
+	.put   = snd_mts64_ctl_smpte_fps_put
+};
+
+
+static int __devinit snd_mts64_ctl_create(struct snd_card *card, 
+					  struct mts64 *mts) 
+{
+	int err, i;
+	static struct snd_kcontrol_new *control[] __devinitdata = {
+		&mts64_ctl_smpte_switch,
+		&mts64_ctl_smpte_time_hours,
+		&mts64_ctl_smpte_time_minutes,
+		&mts64_ctl_smpte_time_seconds,
+		&mts64_ctl_smpte_time_frames,
+		&mts64_ctl_smpte_fps,
+	        NULL  };
+
+	for (i = 0; control[i]; ++i) {
+		err = snd_ctl_add(card, snd_ctl_new1(control[i], mts));
+		if (err < 0) {
+			snd_printd("Cannot create control: %s\n", 
+				   control[i]->name);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+/*********************************************************************
+ * Rawmidi
+ *********************************************************************/
+#define MTS64_MODE_INPUT_TRIGGERED 0x01
+
+static int snd_mts64_rawmidi_open(struct snd_rawmidi_substream *substream)
+{
+	struct mts64 *mts = substream->rmidi->private_data;
+
+	if (mts->open_count == 0) {
+		/* We don't need a spinlock here, because this is just called 
+		   if the device has not been opened before. 
+		   So there aren't any IRQs from the device */
+		mts64_device_open(mts);
+
+		msleep(50);
+	}
+	++(mts->open_count);
+
+	return 0;
+}
+
+static int snd_mts64_rawmidi_close(struct snd_rawmidi_substream *substream)
+{
+	struct mts64 *mts = substream->rmidi->private_data;
+	unsigned long flags;
+
+	--(mts->open_count);
+	if (mts->open_count == 0) {
+		/* We need the spinlock_irqsave here because we can still
+		   have IRQs at this point */
+		spin_lock_irqsave(&mts->lock, flags);
+		mts64_device_close(mts);
+		spin_unlock_irqrestore(&mts->lock, flags);
+
+		msleep(500);
+
+	} else if (mts->open_count < 0)
+		mts->open_count = 0;
+
+	return 0;
+}
+
+static void snd_mts64_rawmidi_output_trigger(struct snd_rawmidi_substream *substream,
+					     int up)
+{
+	struct mts64 *mts = substream->rmidi->private_data;
+	u8 data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&mts->lock, flags);
+	while (snd_rawmidi_transmit_peek(substream, &data, 1) == 1) {
+		mts64_write_midi(mts, data, substream->number+1);
+		snd_rawmidi_transmit_ack(substream, 1);
+	}
+	spin_unlock_irqrestore(&mts->lock, flags);
+}
+
+static void snd_mts64_rawmidi_input_trigger(struct snd_rawmidi_substream *substream,
+					    int up)
+{
+	struct mts64 *mts = substream->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&mts->lock, flags);
+	if (up)
+		mts->mode[substream->number] |= MTS64_MODE_INPUT_TRIGGERED;
+	else
+ 		mts->mode[substream->number] &= ~MTS64_MODE_INPUT_TRIGGERED;
+	
+	spin_unlock_irqrestore(&mts->lock, flags);
+}
+
+static struct snd_rawmidi_ops snd_mts64_rawmidi_output_ops = {
+	.open    = snd_mts64_rawmidi_open,
+	.close   = snd_mts64_rawmidi_close,
+	.trigger = snd_mts64_rawmidi_output_trigger
+};
+
+static struct snd_rawmidi_ops snd_mts64_rawmidi_input_ops = {
+	.open    = snd_mts64_rawmidi_open,
+	.close   = snd_mts64_rawmidi_close,
+	.trigger = snd_mts64_rawmidi_input_trigger
+};
+
+/* Create and initialize the rawmidi component */
+static int __devinit snd_mts64_rawmidi_create(struct snd_card *card)
+{
+	struct mts64 *mts = card->private_data;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *substream;
+	struct list_head *list;
+	int err;
+	
+	err = snd_rawmidi_new(card, CARD_NAME, 0, 
+			      MTS64_NUM_OUTPUT_PORTS, 
+			      MTS64_NUM_INPUT_PORTS, 
+			      &rmidi);
+	if (err < 0) 
+		return err;
+
+	rmidi->private_data = mts;
+	strcpy(rmidi->name, CARD_NAME);
+	rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+		            SNDRV_RAWMIDI_INFO_INPUT |
+                            SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	mts->rmidi = rmidi;
+
+	/* register rawmidi ops */
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 
+			    &snd_mts64_rawmidi_output_ops);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, 
+			    &snd_mts64_rawmidi_input_ops);
+
+	/* name substreams */
+	/* output */
+	list_for_each(list, 
+		      &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) {
+		substream = list_entry(list, struct snd_rawmidi_substream, list);
+		sprintf(substream->name,
+			"Miditerminal %d", substream->number+1);
+	}
+	/* input */
+	list_for_each(list, 
+		      &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) {
+		substream = list_entry(list, struct snd_rawmidi_substream, list);
+		mts->midi_input_substream[substream->number] = substream;
+		switch(substream->number) {
+		case MTS64_SMPTE_SUBSTREAM:
+			strcpy(substream->name, "Miditerminal SMPTE");
+			break;
+		default:
+			sprintf(substream->name,
+				"Miditerminal %d", substream->number+1);
+		}
+	}
+
+	/* controls */
+	err = snd_mts64_ctl_create(card, mts);
+
+	return err;
+}
+
+/*********************************************************************
+ * parport stuff
+ *********************************************************************/
+static void snd_mts64_interrupt(void *private)
+{
+	struct mts64 *mts = ((struct snd_card*)private)->private_data;
+	u16 ret;
+	u8 status, data;
+	struct snd_rawmidi_substream *substream;
+
+	spin_lock(&mts->lock);
+	ret = mts64_read(mts->pardev->port);
+	data = ret & 0x00ff;
+	status = ret >> 8;
+
+	if (status & MTS64_STAT_PORT) {
+		mts->current_midi_input_port = mts64_map_midi_input(data);
+	} else {
+		if (mts->current_midi_input_port == -1) 
+			goto __out;
+		substream = mts->midi_input_substream[mts->current_midi_input_port];
+		if (mts->mode[substream->number] & MTS64_MODE_INPUT_TRIGGERED)
+			snd_rawmidi_receive(substream, &data, 1);
+	}
+__out:
+	spin_unlock(&mts->lock);
+}
+
+static int __devinit snd_mts64_probe_port(struct parport *p)
+{
+	struct pardevice *pardev;
+	int res;
+
+	pardev = parport_register_device(p, DRIVER_NAME,
+					 NULL, NULL, NULL,
+					 0, NULL);
+	if (!pardev)
+		return -EIO;
+	
+	if (parport_claim(pardev)) {
+		parport_unregister_device(pardev);
+		return -EIO;
+	}
+
+	res = mts64_probe(p);
+
+	parport_release(pardev);
+	parport_unregister_device(pardev);
+
+	return res;
+}
+
+static void __devinit snd_mts64_attach(struct parport *p)
+{
+	struct platform_device *device;
+
+	device = platform_device_alloc(PLATFORM_DRIVER, device_count);
+	if (!device)
+		return;
+
+	/* Temporary assignment to forward the parport */
+	platform_set_drvdata(device, p);
+
+	if (platform_device_add(device) < 0) {
+		platform_device_put(device);
+		return;
+	}
+
+	/* Since we dont get the return value of probe
+	 * We need to check if device probing succeeded or not */
+	if (!platform_get_drvdata(device)) {
+		platform_device_unregister(device);
+		return;
+	}
+
+	/* register device in global table */
+	platform_devices[device_count] = device;
+	device_count++;
+}
+
+static void snd_mts64_detach(struct parport *p)
+{
+	/* nothing to do here */
+}
+
+static struct parport_driver mts64_parport_driver = {
+	.name   = "mts64",
+	.attach = snd_mts64_attach,
+	.detach = snd_mts64_detach
+};
+
+/*********************************************************************
+ * platform stuff
+ *********************************************************************/
+static void snd_mts64_card_private_free(struct snd_card *card)
+{
+	struct mts64 *mts = card->private_data;
+	struct pardevice *pardev = mts->pardev;
+
+	if (pardev) {
+		if (mts->pardev_claimed)
+			parport_release(pardev);
+		parport_unregister_device(pardev);
+	}
+
+	snd_mts64_free(mts);
+}
+
+static int __devinit snd_mts64_probe(struct platform_device *pdev)
+{
+	struct pardevice *pardev;
+	struct parport *p;
+	int dev = pdev->id;
+	struct snd_card *card = NULL;
+	struct mts64 *mts = NULL;
+	int err;
+
+	p = platform_get_drvdata(pdev);
+	platform_set_drvdata(pdev, NULL);
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) 
+		return -ENOENT;
+	if ((err = snd_mts64_probe_port(p)) < 0)
+		return err;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0) {
+		snd_printd("Cannot create card\n");
+		return err;
+	}
+	strcpy(card->driver, DRIVER_NAME);
+	strcpy(card->shortname, "ESI " CARD_NAME);
+	sprintf(card->longname,  "%s at 0x%lx, irq %i", 
+		card->shortname, p->base, p->irq);
+
+	pardev = parport_register_device(p,                   /* port */
+					 DRIVER_NAME,         /* name */
+					 NULL,                /* preempt */
+					 NULL,                /* wakeup */
+					 snd_mts64_interrupt, /* ISR */
+					 PARPORT_DEV_EXCL,    /* flags */
+					 (void *)card);       /* private */
+	if (pardev == NULL) {
+		snd_printd("Cannot register pardevice\n");
+		err = -EIO;
+		goto __err;
+	}
+
+	if ((err = snd_mts64_create(card, pardev, &mts)) < 0) {
+		snd_printd("Cannot create main component\n");
+		parport_unregister_device(pardev);
+		goto __err;
+	}
+	card->private_data = mts;
+	card->private_free = snd_mts64_card_private_free;
+	
+	if ((err = snd_mts64_rawmidi_create(card)) < 0) {
+		snd_printd("Creating Rawmidi component failed\n");
+		goto __err;
+	}
+
+	/* claim parport */
+	if (parport_claim(pardev)) {
+		snd_printd("Cannot claim parport 0x%lx\n", pardev->port->base);
+		err = -EIO;
+		goto __err;
+	}
+	mts->pardev_claimed = 1;
+
+	/* init device */
+	if ((err = mts64_device_init(p)) < 0)
+		goto __err;
+
+	platform_set_drvdata(pdev, card);
+
+	snd_card_set_dev(card, &pdev->dev);
+
+	/* At this point card will be usable */
+	if ((err = snd_card_register(card)) < 0) {
+		snd_printd("Cannot register card\n");
+		goto __err;
+	}
+
+	snd_printk(KERN_INFO "ESI Miditerminal 4140 on 0x%lx\n", p->base);
+	return 0;
+
+__err:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit snd_mts64_remove(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+
+	if (card)
+		snd_card_free(card);
+
+	return 0;
+}
+
+
+static struct platform_driver snd_mts64_driver = {
+	.probe  = snd_mts64_probe,
+	.remove = __devexit_p(snd_mts64_remove),
+	.driver = {
+		.name = PLATFORM_DRIVER
+	}
+};
+
+/*********************************************************************
+ * module init stuff
+ *********************************************************************/
+static void snd_mts64_unregister_all(void)
+{
+	int i;
+
+	for (i = 0; i < SNDRV_CARDS; ++i) {
+		if (platform_devices[i]) {
+			platform_device_unregister(platform_devices[i]);
+			platform_devices[i] = NULL;
+		}
+	}		
+	platform_driver_unregister(&snd_mts64_driver);
+	parport_unregister_driver(&mts64_parport_driver);
+}
+
+static int __init snd_mts64_module_init(void)
+{
+	int err;
+
+	if ((err = platform_driver_register(&snd_mts64_driver)) < 0)
+		return err;
+
+	if (parport_register_driver(&mts64_parport_driver) != 0) {
+		platform_driver_unregister(&snd_mts64_driver);
+		return -EIO;
+	}
+
+	if (device_count == 0) {
+		snd_mts64_unregister_all();
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void __exit snd_mts64_module_exit(void)
+{
+	snd_mts64_unregister_all();
+}
+
+module_init(snd_mts64_module_init);
+module_exit(snd_mts64_module_exit);
diff --git a/linux/sound/drivers/opl3/Makefile b/linux/sound/drivers/opl3/Makefile
new file mode 100644
index 0000000..7f2c2a1
--- /dev/null
+++ b/linux/sound/drivers/opl3/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-opl3-lib-objs := opl3_lib.o opl3_synth.o
+snd-opl3-synth-y := opl3_seq.o opl3_midi.o opl3_drums.o
+snd-opl3-synth-$(CONFIG_SND_SEQUENCER_OSS) += opl3_oss.o
+
+obj-$(CONFIG_SND_OPL3_LIB) += snd-opl3-lib.o
+obj-$(CONFIG_SND_OPL4_LIB) += snd-opl3-lib.o
+obj-$(CONFIG_SND_OPL3_LIB_SEQ) += snd-opl3-synth.o
diff --git a/linux/sound/drivers/opl3/opl3_drums.c b/linux/sound/drivers/opl3/opl3_drums.c
new file mode 100644
index 0000000..7369438
--- /dev/null
+++ b/linux/sound/drivers/opl3/opl3_drums.c
@@ -0,0 +1,226 @@
+/*
+ *  Copyright (c) by Uros Bizjak <uros@kss-loka.si>
+ *
+ *   OPL2/OPL3/OPL4 FM routines for internal percussion channels
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "opl3_voice.h"
+
+extern char snd_opl3_regmap[MAX_OPL2_VOICES][4];
+
+static char snd_opl3_drum_table[47] =
+{
+	OPL3_BASSDRUM_ON,  OPL3_BASSDRUM_ON,  OPL3_HIHAT_ON,	/* 35 - 37 */
+	OPL3_SNAREDRUM_ON, OPL3_HIHAT_ON,     OPL3_SNAREDRUM_ON, /* 38 - 40 */
+	OPL3_BASSDRUM_ON,  OPL3_HIHAT_ON,     OPL3_BASSDRUM_ON,	/* 41 - 43 */
+	OPL3_HIHAT_ON,     OPL3_TOMTOM_ON,    OPL3_HIHAT_ON,	/* 44 - 46 */
+	OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,    OPL3_CYMBAL_ON,	/* 47 - 49 */
+
+	OPL3_TOMTOM_ON,    OPL3_CYMBAL_ON,    OPL3_CYMBAL_ON,	/* 50 - 52 */
+	OPL3_CYMBAL_ON,    OPL3_CYMBAL_ON,    OPL3_CYMBAL_ON,	/* 53 - 55 */
+	OPL3_HIHAT_ON,     OPL3_CYMBAL_ON,    OPL3_TOMTOM_ON,	/* 56 - 58 */
+	OPL3_CYMBAL_ON,    OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,	/* 59 - 61 */
+	OPL3_HIHAT_ON,     OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,	/* 62 - 64 */
+
+	OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,	/* 65 - 67 */
+	OPL3_TOMTOM_ON,    OPL3_HIHAT_ON,     OPL3_HIHAT_ON,	/* 68 - 70 */
+	OPL3_HIHAT_ON,     OPL3_HIHAT_ON,     OPL3_TOMTOM_ON,	/* 71 - 73 */
+	OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,	/* 74 - 76 */
+	OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,    OPL3_TOMTOM_ON,	/* 77 - 79 */
+	OPL3_CYMBAL_ON,    OPL3_CYMBAL_ON			/* 80 - 81 */
+};
+
+struct snd_opl3_drum_voice {
+	int voice;
+	int op;
+	unsigned char am_vib;
+	unsigned char ksl_level;
+	unsigned char attack_decay;
+	unsigned char sustain_release;
+	unsigned char feedback_connection;
+	unsigned char wave_select;
+};
+
+struct snd_opl3_drum_note {
+	int voice;
+	unsigned char fnum;
+	unsigned char octave_f;
+	unsigned char feedback_connection;
+};
+
+static struct snd_opl3_drum_voice bass_op0 = {6, 0, 0x00, 0x32, 0xf8, 0x66, 0x30, 0x00};
+static struct snd_opl3_drum_voice bass_op1 = {6, 1, 0x00, 0x03, 0xf6, 0x57, 0x30, 0x00};
+static struct snd_opl3_drum_note bass_note = {6, 0x90, 0x09};
+
+static struct snd_opl3_drum_voice hihat = {7, 0, 0x00, 0x03, 0xf0, 0x06, 0x20, 0x00};
+
+static struct snd_opl3_drum_voice snare = {7, 1, 0x00, 0x03, 0xf0, 0x07, 0x20, 0x02};
+static struct snd_opl3_drum_note snare_note = {7, 0xf4, 0x0d};
+
+static struct snd_opl3_drum_voice tomtom = {8, 0, 0x02, 0x03, 0xf0, 0x06, 0x10, 0x00};
+static struct snd_opl3_drum_note tomtom_note = {8, 0xf4, 0x09};
+
+static struct snd_opl3_drum_voice cymbal = {8, 1, 0x04, 0x03, 0xf0, 0x06, 0x10, 0x00};
+
+/*
+ * set drum voice characteristics
+ */
+static void snd_opl3_drum_voice_set(struct snd_opl3 *opl3,
+				    struct snd_opl3_drum_voice *data)
+{
+	unsigned char op_offset = snd_opl3_regmap[data->voice][data->op];
+	unsigned char voice_offset = data->voice;
+	unsigned short opl3_reg;
+	
+	/* Set OPL3 AM_VIB register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_AM_VIB + op_offset);
+	opl3->command(opl3, opl3_reg, data->am_vib);
+
+	/* Set OPL3 KSL_LEVEL register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_KSL_LEVEL + op_offset);
+	opl3->command(opl3, opl3_reg, data->ksl_level);
+
+	/* Set OPL3 ATTACK_DECAY register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_ATTACK_DECAY + op_offset);
+	opl3->command(opl3, opl3_reg, data->attack_decay);
+
+	/* Set OPL3 SUSTAIN_RELEASE register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_SUSTAIN_RELEASE + op_offset);
+	opl3->command(opl3, opl3_reg, data->sustain_release);
+
+	/* Set OPL3 FEEDBACK_CONNECTION register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
+	opl3->command(opl3, opl3_reg, data->feedback_connection);
+
+	/* Select waveform */
+	opl3_reg = OPL3_LEFT | (OPL3_REG_WAVE_SELECT + op_offset);
+	opl3->command(opl3, opl3_reg, data->wave_select);
+}
+
+/*
+ * Set drum voice pitch
+ */
+static void snd_opl3_drum_note_set(struct snd_opl3 *opl3,
+				   struct snd_opl3_drum_note *data)
+{
+	unsigned char voice_offset = data->voice;
+	unsigned short opl3_reg;
+
+	/* Set OPL3 FNUM_LOW register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_FNUM_LOW + voice_offset);
+	opl3->command(opl3, opl3_reg, data->fnum);
+
+	/* Set OPL3 KEYON_BLOCK register */ 
+	opl3_reg = OPL3_LEFT | (OPL3_REG_KEYON_BLOCK + voice_offset);
+	opl3->command(opl3, opl3_reg, data->octave_f);
+}
+
+/*
+ * Set drum voice volume and position
+ */
+static void snd_opl3_drum_vol_set(struct snd_opl3 *opl3,
+				  struct snd_opl3_drum_voice *data,
+				  int vel, struct snd_midi_channel *chan)
+{
+	unsigned char op_offset = snd_opl3_regmap[data->voice][data->op];
+	unsigned char voice_offset = data->voice;
+	unsigned char reg_val;
+	unsigned short opl3_reg;
+
+	/* Set OPL3 KSL_LEVEL register */ 
+	reg_val = data->ksl_level;
+	snd_opl3_calc_volume(&reg_val, vel, chan);
+	opl3_reg = OPL3_LEFT | (OPL3_REG_KSL_LEVEL + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Set OPL3 FEEDBACK_CONNECTION register */ 
+	/* Set output voice connection */
+	reg_val = data->feedback_connection | OPL3_STEREO_BITS;
+	if (chan->gm_pan < 43)
+		reg_val &= ~OPL3_VOICE_TO_RIGHT;
+	if (chan->gm_pan > 85)
+		reg_val &= ~OPL3_VOICE_TO_LEFT;
+	opl3_reg = OPL3_LEFT | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+}
+
+/*
+ * Loads drum voices at init time
+ */
+void snd_opl3_load_drums(struct snd_opl3 *opl3)
+{
+	snd_opl3_drum_voice_set(opl3, &bass_op0);
+	snd_opl3_drum_voice_set(opl3, &bass_op1);
+	snd_opl3_drum_note_set(opl3, &bass_note);
+
+	snd_opl3_drum_voice_set(opl3, &hihat);
+
+	snd_opl3_drum_voice_set(opl3, &snare);
+	snd_opl3_drum_note_set(opl3, &snare_note);
+
+	snd_opl3_drum_voice_set(opl3, &tomtom);
+	snd_opl3_drum_note_set(opl3, &tomtom_note);
+
+	snd_opl3_drum_voice_set(opl3, &cymbal);
+}
+
+/*
+ * Switch drum voice on or off
+ */
+void snd_opl3_drum_switch(struct snd_opl3 *opl3, int note, int vel, int on_off,
+			  struct snd_midi_channel *chan)
+{
+	unsigned char drum_mask;
+	struct snd_opl3_drum_voice *drum_voice;
+
+	if (!(opl3->drum_reg & OPL3_PERCUSSION_ENABLE))
+		return;
+
+	if ((note < 35) || (note > 81))
+		return;
+	drum_mask = snd_opl3_drum_table[note - 35];
+
+	if (on_off) {
+		switch (drum_mask) {
+		case OPL3_BASSDRUM_ON:
+			drum_voice = &bass_op1;
+			break;
+		case OPL3_HIHAT_ON:
+			drum_voice = &hihat;
+			break;
+		case OPL3_SNAREDRUM_ON:
+			drum_voice = &snare;
+			break;
+		case OPL3_TOMTOM_ON:
+			drum_voice = &tomtom;
+			break;
+		case OPL3_CYMBAL_ON:
+			drum_voice = &cymbal;
+			break;
+		default:
+			drum_voice = &tomtom;
+		}
+
+		snd_opl3_drum_vol_set(opl3, drum_voice, vel, chan);
+		opl3->drum_reg |= drum_mask;
+	} else {
+		opl3->drum_reg &= ~drum_mask;
+	}
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION,
+			 opl3->drum_reg);
+}
diff --git a/linux/sound/drivers/opl3/opl3_lib.c b/linux/sound/drivers/opl3/opl3_lib.c
new file mode 100644
index 0000000..6e31e46
--- /dev/null
+++ b/linux/sound/drivers/opl3/opl3_lib.c
@@ -0,0 +1,560 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ *                   Hannu Savolainen 1993-1996,
+ *                   Rob Hooft
+ *                   
+ *  Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)
+ *
+ *  Most if code is ported from OSS/Lite.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/opl3.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/minors.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Hannu Savolainen 1993-1996, Rob Hooft");
+MODULE_DESCRIPTION("Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)");
+MODULE_LICENSE("GPL");
+
+extern char snd_opl3_regmap[MAX_OPL2_VOICES][4];
+
+static void snd_opl2_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val)
+{
+	unsigned long flags;
+	unsigned long port;
+
+	/*
+	 * The original 2-OP synth requires a quite long delay
+	 * after writing to a register.
+	 */
+
+	port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port;
+
+	spin_lock_irqsave(&opl3->reg_lock, flags);
+
+	outb((unsigned char) cmd, port);
+	udelay(10);
+
+	outb((unsigned char) val, port + 1);
+	udelay(30);
+
+	spin_unlock_irqrestore(&opl3->reg_lock, flags);
+}
+
+static void snd_opl3_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val)
+{
+	unsigned long flags;
+	unsigned long port;
+
+	/*
+	 * The OPL-3 survives with just two INBs
+	 * after writing to a register.
+	 */
+
+	port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port;
+
+	spin_lock_irqsave(&opl3->reg_lock, flags);
+
+	outb((unsigned char) cmd, port);
+	inb(opl3->l_port);
+	inb(opl3->l_port);
+
+	outb((unsigned char) val, port + 1);
+	inb(opl3->l_port);
+	inb(opl3->l_port);
+
+	spin_unlock_irqrestore(&opl3->reg_lock, flags);
+}
+
+static int snd_opl3_detect(struct snd_opl3 * opl3)
+{
+	/*
+	 * This function returns 1 if the FM chip is present at the given I/O port
+	 * The detection algorithm plays with the timer built in the FM chip and
+	 * looks for a change in the status register.
+	 *
+	 * Note! The timers of the FM chip are not connected to AdLib (and compatible)
+	 * boards.
+	 *
+	 * Note2! The chip is initialized if detected.
+	 */
+
+	unsigned char stat1, stat2, signature;
+
+	/* Reset timers 1 and 2 */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
+	/* Reset the IRQ of the FM chip */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
+	signature = stat1 = inb(opl3->l_port);	/* Status register */
+	if ((stat1 & 0xe0) != 0x00) {	/* Should be 0x00 */
+		snd_printd("OPL3: stat1 = 0x%x\n", stat1);
+		return -ENODEV;
+	}
+	/* Set timer1 to 0xff */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 0xff);
+	/* Unmask and start timer 1 */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER2_MASK | OPL3_TIMER1_START);
+	/* Now we have to delay at least 80us */
+	udelay(200);
+	/* Read status after timers have expired */
+	stat2 = inb(opl3->l_port);
+	/* Stop the timers */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
+	/* Reset the IRQ of the FM chip */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
+	if ((stat2 & 0xe0) != 0xc0) {	/* There is no YM3812 */
+		snd_printd("OPL3: stat2 = 0x%x\n", stat2);
+		return -ENODEV;
+	}
+
+	/* If the toplevel code knows exactly the type of chip, don't try
+	   to detect it. */
+	if (opl3->hardware != OPL3_HW_AUTO)
+		return 0;
+
+	/* There is a FM chip on this address. Detect the type (OPL2 to OPL4) */
+	if (signature == 0x06) {	/* OPL2 */
+		opl3->hardware = OPL3_HW_OPL2;
+	} else {
+		/*
+		 * If we had an OPL4 chip, opl3->hardware would have been set
+		 * by the OPL4 driver; so we can assume OPL3 here.
+		 */
+		if (snd_BUG_ON(!opl3->r_port))
+			return -ENODEV;
+		opl3->hardware = OPL3_HW_OPL3;
+	}
+	return 0;
+}
+
+/*
+ *  AdLib timers
+ */
+
+/*
+ *  Timer 1 - 80us
+ */
+
+static int snd_opl3_timer1_start(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	unsigned int ticks;
+	struct snd_opl3 *opl3;
+
+	opl3 = snd_timer_chip(timer);
+	spin_lock_irqsave(&opl3->timer_lock, flags);
+	ticks = timer->sticks;
+	tmp = (opl3->timer_enable | OPL3_TIMER1_START) & ~OPL3_TIMER1_MASK;
+	opl3->timer_enable = tmp;
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 256 - ticks);	/* timer 1 count */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* enable timer 1 IRQ */
+	spin_unlock_irqrestore(&opl3->timer_lock, flags);
+	return 0;
+}
+
+static int snd_opl3_timer1_stop(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	struct snd_opl3 *opl3;
+
+	opl3 = snd_timer_chip(timer);
+	spin_lock_irqsave(&opl3->timer_lock, flags);
+	tmp = (opl3->timer_enable | OPL3_TIMER1_MASK) & ~OPL3_TIMER1_START;
+	opl3->timer_enable = tmp;
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* disable timer #1 */
+	spin_unlock_irqrestore(&opl3->timer_lock, flags);
+	return 0;
+}
+
+/*
+ *  Timer 2 - 320us
+ */
+
+static int snd_opl3_timer2_start(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	unsigned int ticks;
+	struct snd_opl3 *opl3;
+
+	opl3 = snd_timer_chip(timer);
+	spin_lock_irqsave(&opl3->timer_lock, flags);
+	ticks = timer->sticks;
+	tmp = (opl3->timer_enable | OPL3_TIMER2_START) & ~OPL3_TIMER2_MASK;
+	opl3->timer_enable = tmp;
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER2, 256 - ticks);	/* timer 1 count */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* enable timer 1 IRQ */
+	spin_unlock_irqrestore(&opl3->timer_lock, flags);
+	return 0;
+}
+
+static int snd_opl3_timer2_stop(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	struct snd_opl3 *opl3;
+
+	opl3 = snd_timer_chip(timer);
+	spin_lock_irqsave(&opl3->timer_lock, flags);
+	tmp = (opl3->timer_enable | OPL3_TIMER2_MASK) & ~OPL3_TIMER2_START;
+	opl3->timer_enable = tmp;
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* disable timer #1 */
+	spin_unlock_irqrestore(&opl3->timer_lock, flags);
+	return 0;
+}
+
+/*
+
+ */
+
+static struct snd_timer_hardware snd_opl3_timer1 =
+{
+	.flags =	SNDRV_TIMER_HW_STOP,
+	.resolution =	80000,
+	.ticks =	256,
+	.start =	snd_opl3_timer1_start,
+	.stop =		snd_opl3_timer1_stop,
+};
+
+static struct snd_timer_hardware snd_opl3_timer2 =
+{
+	.flags =	SNDRV_TIMER_HW_STOP,
+	.resolution =	320000,
+	.ticks =	256,
+	.start =	snd_opl3_timer2_start,
+	.stop =		snd_opl3_timer2_stop,
+};
+
+static int snd_opl3_timer1_init(struct snd_opl3 * opl3, int timer_no)
+{
+	struct snd_timer *timer = NULL;
+	struct snd_timer_id tid;
+	int err;
+
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = opl3->card->number;
+	tid.device = timer_no;
+	tid.subdevice = 0;
+	if ((err = snd_timer_new(opl3->card, "AdLib timer #1", &tid, &timer)) >= 0) {
+		strcpy(timer->name, "AdLib timer #1");
+		timer->private_data = opl3;
+		timer->hw = snd_opl3_timer1;
+	}
+	opl3->timer1 = timer;
+	return err;
+}
+
+static int snd_opl3_timer2_init(struct snd_opl3 * opl3, int timer_no)
+{
+	struct snd_timer *timer = NULL;
+	struct snd_timer_id tid;
+	int err;
+
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = opl3->card->number;
+	tid.device = timer_no;
+	tid.subdevice = 0;
+	if ((err = snd_timer_new(opl3->card, "AdLib timer #2", &tid, &timer)) >= 0) {
+		strcpy(timer->name, "AdLib timer #2");
+		timer->private_data = opl3;
+		timer->hw = snd_opl3_timer2;
+	}
+	opl3->timer2 = timer;
+	return err;
+}
+
+/*
+
+ */
+
+void snd_opl3_interrupt(struct snd_hwdep * hw)
+{
+	unsigned char status;
+	struct snd_opl3 *opl3;
+	struct snd_timer *timer;
+
+	if (hw == NULL)
+		return;
+
+	opl3 = hw->private_data;
+	status = inb(opl3->l_port);
+#if 0
+	snd_printk(KERN_DEBUG "AdLib IRQ status = 0x%x\n", status);
+#endif
+	if (!(status & 0x80))
+		return;
+
+	if (status & 0x40) {
+		timer = opl3->timer1;
+		snd_timer_interrupt(timer, timer->sticks);
+	}
+	if (status & 0x20) {
+		timer = opl3->timer2;
+		snd_timer_interrupt(timer, timer->sticks);
+	}
+}
+
+EXPORT_SYMBOL(snd_opl3_interrupt);
+
+/*
+
+ */
+
+static int snd_opl3_free(struct snd_opl3 *opl3)
+{
+	if (snd_BUG_ON(!opl3))
+		return -ENXIO;
+	if (opl3->private_free)
+		opl3->private_free(opl3);
+	snd_opl3_clear_patches(opl3);
+	release_and_free_resource(opl3->res_l_port);
+	release_and_free_resource(opl3->res_r_port);
+	kfree(opl3);
+	return 0;
+}
+
+static int snd_opl3_dev_free(struct snd_device *device)
+{
+	struct snd_opl3 *opl3 = device->device_data;
+	return snd_opl3_free(opl3);
+}
+
+int snd_opl3_new(struct snd_card *card,
+		 unsigned short hardware,
+		 struct snd_opl3 **ropl3)
+{
+	static struct snd_device_ops ops = {
+		.dev_free = snd_opl3_dev_free,
+	};
+	struct snd_opl3 *opl3;
+	int err;
+
+	*ropl3 = NULL;
+	opl3 = kzalloc(sizeof(*opl3), GFP_KERNEL);
+	if (opl3 == NULL) {
+		snd_printk(KERN_ERR "opl3: cannot allocate\n");
+		return -ENOMEM;
+	}
+
+	opl3->card = card;
+	opl3->hardware = hardware;
+	spin_lock_init(&opl3->reg_lock);
+	spin_lock_init(&opl3->timer_lock);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_CODEC, opl3, &ops)) < 0) {
+		snd_opl3_free(opl3);
+		return err;
+	}
+
+	*ropl3 = opl3;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_opl3_new);
+
+int snd_opl3_init(struct snd_opl3 *opl3)
+{
+	if (! opl3->command) {
+		printk(KERN_ERR "snd_opl3_init: command not defined!\n");
+		return -EINVAL;
+	}
+
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TEST, OPL3_ENABLE_WAVE_SELECT);
+	/* Melodic mode */
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, 0x00);
+
+	switch (opl3->hardware & OPL3_HW_MASK) {
+	case OPL3_HW_OPL2:
+		opl3->max_voices = MAX_OPL2_VOICES;
+		break;
+	case OPL3_HW_OPL3:
+	case OPL3_HW_OPL4:
+		opl3->max_voices = MAX_OPL3_VOICES;
+		/* Enter OPL3 mode */
+		opl3->command(opl3, OPL3_RIGHT | OPL3_REG_MODE, OPL3_OPL3_ENABLE);
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_opl3_init);
+
+int snd_opl3_create(struct snd_card *card,
+		    unsigned long l_port,
+		    unsigned long r_port,
+		    unsigned short hardware,
+		    int integrated,
+		    struct snd_opl3 ** ropl3)
+{
+	struct snd_opl3 *opl3;
+	int err;
+
+	*ropl3 = NULL;
+	if ((err = snd_opl3_new(card, hardware, &opl3)) < 0)
+		return err;
+	if (! integrated) {
+		if ((opl3->res_l_port = request_region(l_port, 2, "OPL2/3 (left)")) == NULL) {
+			snd_printk(KERN_ERR "opl3: can't grab left port 0x%lx\n", l_port);
+			snd_device_free(card, opl3);
+			return -EBUSY;
+		}
+		if (r_port != 0 &&
+		    (opl3->res_r_port = request_region(r_port, 2, "OPL2/3 (right)")) == NULL) {
+			snd_printk(KERN_ERR "opl3: can't grab right port 0x%lx\n", r_port);
+			snd_device_free(card, opl3);
+			return -EBUSY;
+		}
+	}
+	opl3->l_port = l_port;
+	opl3->r_port = r_port;
+
+	switch (opl3->hardware) {
+	/* some hardware doesn't support timers */
+	case OPL3_HW_OPL3_SV:
+	case OPL3_HW_OPL3_CS:
+	case OPL3_HW_OPL3_FM801:
+		opl3->command = &snd_opl3_command;
+		break;
+	default:
+		opl3->command = &snd_opl2_command;
+		if ((err = snd_opl3_detect(opl3)) < 0) {
+			snd_printd("OPL2/3 chip not detected at 0x%lx/0x%lx\n",
+				   opl3->l_port, opl3->r_port);
+			snd_device_free(card, opl3);
+			return err;
+		}
+		/* detect routine returns correct hardware type */
+		switch (opl3->hardware & OPL3_HW_MASK) {
+		case OPL3_HW_OPL3:
+		case OPL3_HW_OPL4:
+			opl3->command = &snd_opl3_command;
+		}
+	}
+
+	snd_opl3_init(opl3);
+
+	*ropl3 = opl3;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_opl3_create);
+
+int snd_opl3_timer_new(struct snd_opl3 * opl3, int timer1_dev, int timer2_dev)
+{
+	int err;
+
+	if (timer1_dev >= 0)
+		if ((err = snd_opl3_timer1_init(opl3, timer1_dev)) < 0)
+			return err;
+	if (timer2_dev >= 0) {
+		if ((err = snd_opl3_timer2_init(opl3, timer2_dev)) < 0) {
+			snd_device_free(opl3->card, opl3->timer1);
+			opl3->timer1 = NULL;
+			return err;
+		}
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_opl3_timer_new);
+
+int snd_opl3_hwdep_new(struct snd_opl3 * opl3,
+		       int device, int seq_device,
+		       struct snd_hwdep ** rhwdep)
+{
+	struct snd_hwdep *hw;
+	struct snd_card *card = opl3->card;
+	int err;
+
+	if (rhwdep)
+		*rhwdep = NULL;
+
+	/* create hardware dependent device (direct FM) */
+
+	if ((err = snd_hwdep_new(card, "OPL2/OPL3", device, &hw)) < 0) {
+		snd_device_free(card, opl3);
+		return err;
+	}
+	hw->private_data = opl3;
+	hw->exclusive = 1;
+#ifdef CONFIG_SND_OSSEMUL
+	if (device == 0) {
+		hw->oss_type = SNDRV_OSS_DEVICE_TYPE_DMFM;
+		sprintf(hw->oss_dev, "dmfm%i", card->number);
+	}
+#endif
+	strcpy(hw->name, hw->id);
+	switch (opl3->hardware & OPL3_HW_MASK) {
+	case OPL3_HW_OPL2:
+		strcpy(hw->name, "OPL2 FM");
+		hw->iface = SNDRV_HWDEP_IFACE_OPL2;
+		break;
+	case OPL3_HW_OPL3:
+		strcpy(hw->name, "OPL3 FM");
+		hw->iface = SNDRV_HWDEP_IFACE_OPL3;
+		break;
+	case OPL3_HW_OPL4:
+		strcpy(hw->name, "OPL4 FM");
+		hw->iface = SNDRV_HWDEP_IFACE_OPL4;
+		break;
+	}
+
+	/* operators - only ioctl */
+	hw->ops.open = snd_opl3_open;
+	hw->ops.ioctl = snd_opl3_ioctl;
+	hw->ops.write = snd_opl3_write;
+	hw->ops.release = snd_opl3_release;
+
+	opl3->hwdep = hw;
+	opl3->seq_dev_num = seq_device;
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	if (snd_seq_device_new(card, seq_device, SNDRV_SEQ_DEV_ID_OPL3,
+			       sizeof(struct snd_opl3 *), &opl3->seq_dev) >= 0) {
+		strcpy(opl3->seq_dev->name, hw->name);
+		*(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(opl3->seq_dev) = opl3;
+	}
+#endif
+	if (rhwdep)
+		*rhwdep = hw;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_opl3_hwdep_new);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_opl3_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_opl3_exit(void)
+{
+}
+
+module_init(alsa_opl3_init)
+module_exit(alsa_opl3_exit)
diff --git a/linux/sound/drivers/opl3/opl3_midi.c b/linux/sound/drivers/opl3/opl3_midi.c
new file mode 100644
index 0000000..7d722a0
--- /dev/null
+++ b/linux/sound/drivers/opl3/opl3_midi.c
@@ -0,0 +1,881 @@
+/*
+ *  Copyright (c) by Uros Bizjak <uros@kss-loka.si>
+ *
+ *  Midi synth routines for OPL2/OPL3/OPL4 FM
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#undef DEBUG_ALLOC
+#undef DEBUG_MIDI
+
+#include "opl3_voice.h"
+#include <sound/asoundef.h>
+
+extern char snd_opl3_regmap[MAX_OPL2_VOICES][4];
+
+extern int use_internal_drums;
+
+static void snd_opl3_note_off_unsafe(void *p, int note, int vel,
+				     struct snd_midi_channel *chan);
+/*
+ * The next table looks magical, but it certainly is not. Its values have
+ * been calculated as table[i]=8*log(i/64)/log(2) with an obvious exception
+ * for i=0. This log-table converts a linear volume-scaling (0..127) to a
+ * logarithmic scaling as present in the FM-synthesizer chips. so :    Volume
+ * 64 =  0 db = relative volume  0 and:    Volume 32 = -6 db = relative
+ * volume -8 it was implemented as a table because it is only 128 bytes and
+ * it saves a lot of log() calculations. (Rob Hooft <hooft@chem.ruu.nl>)
+ */
+
+static char opl3_volume_table[128] =
+{
+	-63, -48, -40, -35, -32, -29, -27, -26,
+	-24, -23, -21, -20, -19, -18, -18, -17,
+	-16, -15, -15, -14, -13, -13, -12, -12,
+	-11, -11, -10, -10, -10, -9, -9, -8,
+	-8, -8, -7, -7, -7, -6, -6, -6,
+	-5, -5, -5, -5, -4, -4, -4, -4,
+	-3, -3, -3, -3, -2, -2, -2, -2,
+	-2, -1, -1, -1, -1, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1,
+	1, 2, 2, 2, 2, 2, 2, 2,
+	3, 3, 3, 3, 3, 3, 3, 4,
+	4, 4, 4, 4, 4, 4, 4, 5,
+	5, 5, 5, 5, 5, 5, 5, 5,
+	6, 6, 6, 6, 6, 6, 6, 6,
+	6, 7, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 8, 8, 8, 8, 8
+};
+
+void snd_opl3_calc_volume(unsigned char *volbyte, int vel,
+			  struct snd_midi_channel *chan)
+{
+	int oldvol, newvol, n;
+	int volume;
+
+	volume = (vel * chan->gm_volume * chan->gm_expression) / (127*127);
+	if (volume > 127)
+		volume = 127;
+
+	oldvol = OPL3_TOTAL_LEVEL_MASK - (*volbyte & OPL3_TOTAL_LEVEL_MASK);
+
+	newvol = opl3_volume_table[volume] + oldvol;
+	if (newvol > OPL3_TOTAL_LEVEL_MASK)
+		newvol = OPL3_TOTAL_LEVEL_MASK;
+	else if (newvol < 0)
+		newvol = 0;
+
+	n = OPL3_TOTAL_LEVEL_MASK - (newvol & OPL3_TOTAL_LEVEL_MASK);
+
+	*volbyte = (*volbyte & OPL3_KSL_MASK) | (n & OPL3_TOTAL_LEVEL_MASK);
+}
+
+/*
+ * Converts the note frequency to block and fnum values for the FM chip
+ */
+static short opl3_note_table[16] =
+{
+	305, 323,	/* for pitch bending, -2 semitones */
+	343, 363, 385, 408, 432, 458, 485, 514, 544, 577, 611, 647,
+	686, 726	/* for pitch bending, +2 semitones */
+};
+
+static void snd_opl3_calc_pitch(unsigned char *fnum, unsigned char *blocknum,
+				int note, struct snd_midi_channel *chan)
+{
+	int block = ((note / 12) & 0x07) - 1;
+	int idx = (note % 12) + 2;
+	int freq;
+
+	if (chan->midi_pitchbend) {
+		int pitchbend = chan->midi_pitchbend;
+		int segment;
+
+		if (pitchbend > 0x1FFF)
+			pitchbend = 0x1FFF;
+
+		segment = pitchbend / 0x1000;
+		freq = opl3_note_table[idx+segment];
+		freq += ((opl3_note_table[idx+segment+1] - freq) *
+			 (pitchbend % 0x1000)) / 0x1000;
+	} else {
+		freq = opl3_note_table[idx];
+	}
+
+	*fnum = (unsigned char) freq;
+	*blocknum = ((freq >> 8) & OPL3_FNUM_HIGH_MASK) |
+		((block << 2) & OPL3_BLOCKNUM_MASK);
+}
+
+
+#ifdef DEBUG_ALLOC
+static void debug_alloc(struct snd_opl3 *opl3, char *s, int voice) {
+	int i;
+	char *str = "x.24";
+
+	printk(KERN_DEBUG "time %.5i: %s [%.2i]: ", opl3->use_time, s, voice);
+	for (i = 0; i < opl3->max_voices; i++)
+		printk("%c", *(str + opl3->voices[i].state + 1));
+	printk("\n");
+}
+#endif
+
+/*
+ * Get a FM voice (channel) to play a note on.
+ */
+static int opl3_get_voice(struct snd_opl3 *opl3, int instr_4op,
+			  struct snd_midi_channel *chan) {
+	int chan_4op_1;		/* first voice for 4op instrument */
+	int chan_4op_2;		/* second voice for 4op instrument */
+
+	struct snd_opl3_voice *vp, *vp2;
+	unsigned int voice_time;
+	int i;
+
+#ifdef DEBUG_ALLOC
+	char *alloc_type[3] = { "FREE     ", "CHEAP    ", "EXPENSIVE" };
+#endif
+
+	/* This is our "allocation cost" table */
+	enum {
+		FREE = 0, CHEAP, EXPENSIVE, END
+	};
+
+	/* Keeps track of what we are finding */
+	struct best {
+		unsigned int time;
+		int voice;
+	} best[END];
+	struct best *bp;
+
+	for (i = 0; i < END; i++) {
+		best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */;
+		best[i].voice = -1;
+	}
+
+	/* Look through all the channels for the most suitable. */
+	for (i = 0; i < opl3->max_voices; i++) {
+		vp = &opl3->voices[i];
+
+		if (vp->state == SNDRV_OPL3_ST_NOT_AVAIL)
+		  /* skip unavailable channels, allocated by
+		     drum voices or by bounded 4op voices) */
+			continue;
+
+		voice_time = vp->time;
+		bp = best;
+
+		chan_4op_1 = ((i < 3) || (i > 8 && i < 12));
+		chan_4op_2 = ((i > 2 && i < 6) || (i > 11 && i < 15));
+		if (instr_4op) {
+			/* allocate 4op voice */
+			/* skip channels unavailable to 4op instrument */
+			if (!chan_4op_1)
+				continue;
+
+			if (vp->state)
+				/* kill one voice, CHEAP */
+				bp++;
+			/* get state of bounded 2op channel
+			   to be allocated for 4op instrument */
+			vp2 = &opl3->voices[i + 3];
+			if (vp2->state == SNDRV_OPL3_ST_ON_2OP) {
+				/* kill two voices, EXPENSIVE */
+				bp++;
+				voice_time = (voice_time > vp->time) ?
+					voice_time : vp->time;
+			}
+		} else {
+			/* allocate 2op voice */
+			if ((chan_4op_1) || (chan_4op_2))
+				/* use bounded channels for 2op, CHEAP */
+				bp++;
+			else if (vp->state)
+				/* kill one voice on 2op channel, CHEAP */
+				bp++;
+			/* raise kill cost to EXPENSIVE for all channels */
+			if (vp->state)
+				bp++;
+		}
+		if (voice_time < bp->time) {
+			bp->time = voice_time;
+			bp->voice = i;
+		}
+	}
+
+	for (i = 0; i < END; i++) {
+		if (best[i].voice >= 0) {
+#ifdef DEBUG_ALLOC
+			printk(KERN_DEBUG "%s %iop allocation on voice %i\n",
+			       alloc_type[i], instr_4op ? 4 : 2,
+			       best[i].voice);
+#endif
+			return best[i].voice;
+		}
+	}
+	/* not found */
+	return -1;
+}
+
+/* ------------------------------ */
+
+/*
+ * System timer interrupt function
+ */
+void snd_opl3_timer_func(unsigned long data)
+{
+
+	struct snd_opl3 *opl3 = (struct snd_opl3 *)data;
+	unsigned long flags;
+	int again = 0;
+	int i;
+
+	spin_lock_irqsave(&opl3->voice_lock, flags);
+	for (i = 0; i < opl3->max_voices; i++) {
+		struct snd_opl3_voice *vp = &opl3->voices[i];
+		if (vp->state > 0 && vp->note_off_check) {
+			if (vp->note_off == jiffies)
+				snd_opl3_note_off_unsafe(opl3, vp->note, 0,
+							 vp->chan);
+			else
+				again++;
+		}
+	}
+	spin_unlock_irqrestore(&opl3->voice_lock, flags);
+
+	spin_lock_irqsave(&opl3->sys_timer_lock, flags);
+	if (again) {
+		opl3->tlist.expires = jiffies + 1;	/* invoke again */
+		add_timer(&opl3->tlist);
+	} else {
+		opl3->sys_timer_status = 0;
+	}
+	spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);
+}
+
+/*
+ * Start system timer
+ */
+static void snd_opl3_start_timer(struct snd_opl3 *opl3)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&opl3->sys_timer_lock, flags);
+	if (! opl3->sys_timer_status) {
+		opl3->tlist.expires = jiffies + 1;
+		add_timer(&opl3->tlist);
+		opl3->sys_timer_status = 1;
+	}
+	spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);
+}
+
+/* ------------------------------ */
+
+
+static int snd_opl3_oss_map[MAX_OPL3_VOICES] = {
+	0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17, 3, 4 ,5, 12, 13, 14
+};
+
+/*
+ * Start a note.
+ */
+void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
+{
+	struct snd_opl3 *opl3;
+	int instr_4op;
+
+	int voice;
+	struct snd_opl3_voice *vp, *vp2;
+	unsigned short connect_mask;
+	unsigned char connection;
+	unsigned char vol_op[4];
+
+	int extra_prg = 0;
+
+	unsigned short reg_side;
+	unsigned char op_offset;
+	unsigned char voice_offset;
+	unsigned short opl3_reg;
+	unsigned char reg_val;
+	unsigned char prg, bank;
+
+	int key = note;
+	unsigned char fnum, blocknum;
+	int i;
+
+	struct fm_patch *patch;
+	struct fm_instrument *fm;
+	unsigned long flags;
+
+	opl3 = p;
+
+#ifdef DEBUG_MIDI
+	snd_printk(KERN_DEBUG "Note on, ch %i, inst %i, note %i, vel %i\n",
+		   chan->number, chan->midi_program, note, vel);
+#endif
+
+	/* in SYNTH mode, application takes care of voices */
+	/* in SEQ mode, drum voice numbers are notes on drum channel */
+	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
+		if (chan->drum_channel) {
+			/* percussion instruments are located in bank 128 */
+			bank = 128;
+			prg = note;
+		} else {
+			bank = chan->gm_bank_select;
+			prg = chan->midi_program;
+		}
+	} else {
+		/* Prepare for OSS mode */
+		if (chan->number >= MAX_OPL3_VOICES)
+			return;
+
+		/* OSS instruments are located in bank 127 */
+		bank = 127;
+		prg = chan->midi_program;
+	}
+
+	spin_lock_irqsave(&opl3->voice_lock, flags);
+
+	if (use_internal_drums) {
+		snd_opl3_drum_switch(opl3, note, vel, 1, chan);
+		spin_unlock_irqrestore(&opl3->voice_lock, flags);
+		return;
+	}
+
+ __extra_prg:
+	patch = snd_opl3_find_patch(opl3, prg, bank, 0);
+	if (!patch) {
+		spin_unlock_irqrestore(&opl3->voice_lock, flags);
+		return;
+	}
+
+	fm = &patch->inst;
+	switch (patch->type) {
+	case FM_PATCH_OPL2:
+		instr_4op = 0;
+		break;
+	case FM_PATCH_OPL3:
+		if (opl3->hardware >= OPL3_HW_OPL3) {
+			instr_4op = 1;
+			break;
+		}
+	default:
+		spin_unlock_irqrestore(&opl3->voice_lock, flags);
+		return;
+	}
+#ifdef DEBUG_MIDI
+	snd_printk(KERN_DEBUG "  --> OPL%i instrument: %s\n",
+		   instr_4op ? 3 : 2, patch->name);
+#endif
+	/* in SYNTH mode, application takes care of voices */
+	/* in SEQ mode, allocate voice on free OPL3 channel */
+	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
+		voice = opl3_get_voice(opl3, instr_4op, chan);
+	} else {
+		/* remap OSS voice */
+		voice = snd_opl3_oss_map[chan->number];		
+	}
+
+	if (voice < MAX_OPL2_VOICES) {
+		/* Left register block for voices 0 .. 8 */
+		reg_side = OPL3_LEFT;
+		voice_offset = voice;
+		connect_mask = (OPL3_LEFT_4OP_0 << voice_offset) & 0x07;
+	} else {
+		/* Right register block for voices 9 .. 17 */
+		reg_side = OPL3_RIGHT;
+		voice_offset = voice - MAX_OPL2_VOICES;
+		connect_mask = (OPL3_RIGHT_4OP_0 << voice_offset) & 0x38;
+	}
+
+	/* kill voice on channel */
+	vp = &opl3->voices[voice];
+	if (vp->state > 0) {
+		opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+		reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT;
+		opl3->command(opl3, opl3_reg, reg_val);
+	}
+	if (instr_4op) {
+		vp2 = &opl3->voices[voice + 3];
+		if (vp->state > 0) {
+			opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK +
+					       voice_offset + 3);
+			reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT;
+			opl3->command(opl3, opl3_reg, reg_val);
+		}
+	}
+
+	/* set connection register */
+	if (instr_4op) {
+		if ((opl3->connection_reg ^ connect_mask) & connect_mask) {
+			opl3->connection_reg |= connect_mask;
+			/* set connection bit */
+			opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT;
+			opl3->command(opl3, opl3_reg, opl3->connection_reg);
+		}
+	} else {
+		if ((opl3->connection_reg ^ ~connect_mask) & connect_mask) {
+			opl3->connection_reg &= ~connect_mask;
+			/* clear connection bit */
+			opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT;
+			opl3->command(opl3, opl3_reg, opl3->connection_reg);
+		}
+	}
+
+#ifdef DEBUG_MIDI
+	snd_printk(KERN_DEBUG "  --> setting OPL3 connection: 0x%x\n",
+		   opl3->connection_reg);
+#endif
+	/*
+	 * calculate volume depending on connection
+	 * between FM operators (see include/opl3.h)
+	 */
+	for (i = 0; i < (instr_4op ? 4 : 2); i++)
+		vol_op[i] = fm->op[i].ksl_level;
+
+	connection = fm->feedback_connection[0] & 0x01;
+	if (instr_4op) {
+		connection <<= 1;
+		connection |= fm->feedback_connection[1] & 0x01;
+
+		snd_opl3_calc_volume(&vol_op[3], vel, chan);
+		switch (connection) {
+		case 0x03:
+			snd_opl3_calc_volume(&vol_op[2], vel, chan);
+			/* fallthru */
+		case 0x02:
+			snd_opl3_calc_volume(&vol_op[0], vel, chan);
+			break;
+		case 0x01:
+			snd_opl3_calc_volume(&vol_op[1], vel, chan);
+		}
+	} else {
+		snd_opl3_calc_volume(&vol_op[1], vel, chan);
+		if (connection)
+			snd_opl3_calc_volume(&vol_op[0], vel, chan);
+	}
+
+	/* Program the FM voice characteristics */
+	for (i = 0; i < (instr_4op ? 4 : 2); i++) {
+#ifdef DEBUG_MIDI
+		snd_printk(KERN_DEBUG "  --> programming operator %i\n", i);
+#endif
+		op_offset = snd_opl3_regmap[voice_offset][i];
+
+		/* Set OPL3 AM_VIB register of requested voice/operator */ 
+		reg_val = fm->op[i].am_vib;
+		opl3_reg = reg_side | (OPL3_REG_AM_VIB + op_offset);
+		opl3->command(opl3, opl3_reg, reg_val);
+
+		/* Set OPL3 KSL_LEVEL register of requested voice/operator */ 
+		reg_val = vol_op[i];
+		opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + op_offset);
+		opl3->command(opl3, opl3_reg, reg_val);
+
+		/* Set OPL3 ATTACK_DECAY register of requested voice/operator */ 
+		reg_val = fm->op[i].attack_decay;
+		opl3_reg = reg_side | (OPL3_REG_ATTACK_DECAY + op_offset);
+		opl3->command(opl3, opl3_reg, reg_val);
+
+		/* Set OPL3 SUSTAIN_RELEASE register of requested voice/operator */ 
+		reg_val = fm->op[i].sustain_release;
+		opl3_reg = reg_side | (OPL3_REG_SUSTAIN_RELEASE + op_offset);
+		opl3->command(opl3, opl3_reg, reg_val);
+
+		/* Select waveform */
+		reg_val = fm->op[i].wave_select;
+		opl3_reg = reg_side | (OPL3_REG_WAVE_SELECT + op_offset);
+		opl3->command(opl3, opl3_reg, reg_val);
+	}
+
+	/* Set operator feedback and 2op inter-operator connection */
+	reg_val = fm->feedback_connection[0];
+	/* Set output voice connection */
+	reg_val |= OPL3_STEREO_BITS;
+	if (chan->gm_pan < 43)
+		reg_val &= ~OPL3_VOICE_TO_RIGHT;
+	if (chan->gm_pan > 85)
+		reg_val &= ~OPL3_VOICE_TO_LEFT;
+	opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	if (instr_4op) {
+		/* Set 4op inter-operator connection */
+		reg_val = fm->feedback_connection[1] & OPL3_CONNECTION_BIT;
+		/* Set output voice connection */
+		reg_val |= OPL3_STEREO_BITS;
+		if (chan->gm_pan < 43)
+			reg_val &= ~OPL3_VOICE_TO_RIGHT;
+		if (chan->gm_pan > 85)
+			reg_val &= ~OPL3_VOICE_TO_LEFT;
+		opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION +
+				       voice_offset + 3);
+		opl3->command(opl3, opl3_reg, reg_val);
+	}
+
+	/*
+	 * Special treatment of percussion notes for fm:
+	 * Requested pitch is really program, and pitch for
+	 * device is whatever was specified in the patch library.
+	 */
+	if (fm->fix_key)
+		note = fm->fix_key;
+	/*
+	 * use transpose if defined in patch library
+	 */
+	if (fm->trnsps)
+		note += (fm->trnsps - 64);
+
+	snd_opl3_calc_pitch(&fnum, &blocknum, note, chan);
+
+	/* Set OPL3 FNUM_LOW register of requested voice */
+	opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset);
+	opl3->command(opl3, opl3_reg, fnum);
+
+	opl3->voices[voice].keyon_reg = blocknum;
+
+	/* Set output sound flag */
+	blocknum |= OPL3_KEYON_BIT;
+
+#ifdef DEBUG_MIDI
+	snd_printk(KERN_DEBUG "  --> trigger voice %i\n", voice);
+#endif
+	/* Set OPL3 KEYON_BLOCK register of requested voice */ 
+	opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+	opl3->command(opl3, opl3_reg, blocknum);
+
+	/* kill note after fixed duration (in centiseconds) */
+	if (fm->fix_dur) {
+		opl3->voices[voice].note_off = jiffies +
+			(fm->fix_dur * HZ) / 100;
+		snd_opl3_start_timer(opl3);
+		opl3->voices[voice].note_off_check = 1;
+	} else
+		opl3->voices[voice].note_off_check = 0;
+
+	/* get extra pgm, but avoid possible loops */
+	extra_prg = (extra_prg) ? 0 : fm->modes;
+
+	/* do the bookkeeping */
+	vp->time = opl3->use_time++;
+	vp->note = key;
+	vp->chan = chan;
+
+	if (instr_4op) {
+		vp->state = SNDRV_OPL3_ST_ON_4OP;
+
+		vp2 = &opl3->voices[voice + 3];
+		vp2->time = opl3->use_time++;
+		vp2->note = key;
+		vp2->chan = chan;
+		vp2->state = SNDRV_OPL3_ST_NOT_AVAIL;
+	} else {
+		if (vp->state == SNDRV_OPL3_ST_ON_4OP) {
+			/* 4op killed by 2op, release bounded voice */
+			vp2 = &opl3->voices[voice + 3];
+			vp2->time = opl3->use_time++;
+			vp2->state = SNDRV_OPL3_ST_OFF;
+		}
+		vp->state = SNDRV_OPL3_ST_ON_2OP;
+	}
+
+#ifdef DEBUG_ALLOC
+	debug_alloc(opl3, "note on ", voice);
+#endif
+
+	/* allocate extra program if specified in patch library */
+	if (extra_prg) {
+		if (extra_prg > 128) {
+			bank = 128;
+			/* percussions start at 35 */
+			prg = extra_prg - 128 + 35 - 1;
+		} else {
+			bank = 0;
+			prg = extra_prg - 1;
+		}
+#ifdef DEBUG_MIDI
+		snd_printk(KERN_DEBUG " *** allocating extra program\n");
+#endif
+		goto __extra_prg;
+	}
+	spin_unlock_irqrestore(&opl3->voice_lock, flags);
+}
+
+static void snd_opl3_kill_voice(struct snd_opl3 *opl3, int voice)
+{
+	unsigned short reg_side;
+	unsigned char voice_offset;
+	unsigned short opl3_reg;
+
+	struct snd_opl3_voice *vp, *vp2;
+
+	if (snd_BUG_ON(voice >= MAX_OPL3_VOICES))
+		return;
+
+	vp = &opl3->voices[voice];
+	if (voice < MAX_OPL2_VOICES) {
+		/* Left register block for voices 0 .. 8 */
+		reg_side = OPL3_LEFT;
+		voice_offset = voice;
+	} else {
+		/* Right register block for voices 9 .. 17 */
+		reg_side = OPL3_RIGHT;
+		voice_offset = voice - MAX_OPL2_VOICES;
+	}
+
+	/* kill voice */
+#ifdef DEBUG_MIDI
+	snd_printk(KERN_DEBUG "  --> kill voice %i\n", voice);
+#endif
+	opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+	/* clear Key ON bit */
+	opl3->command(opl3, opl3_reg, vp->keyon_reg);
+
+	/* do the bookkeeping */
+	vp->time = opl3->use_time++;
+
+	if (vp->state == SNDRV_OPL3_ST_ON_4OP) {
+		vp2 = &opl3->voices[voice + 3];
+
+		vp2->time = opl3->use_time++;
+		vp2->state = SNDRV_OPL3_ST_OFF;
+	}
+	vp->state = SNDRV_OPL3_ST_OFF;
+#ifdef DEBUG_ALLOC
+	debug_alloc(opl3, "note off", voice);
+#endif
+
+}
+
+/*
+ * Release a note in response to a midi note off.
+ */
+static void snd_opl3_note_off_unsafe(void *p, int note, int vel,
+				     struct snd_midi_channel *chan)
+{
+  	struct snd_opl3 *opl3;
+
+	int voice;
+	struct snd_opl3_voice *vp;
+
+	opl3 = p;
+
+#ifdef DEBUG_MIDI
+	snd_printk(KERN_DEBUG "Note off, ch %i, inst %i, note %i\n",
+		   chan->number, chan->midi_program, note);
+#endif
+
+	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
+		if (chan->drum_channel && use_internal_drums) {
+			snd_opl3_drum_switch(opl3, note, vel, 0, chan);
+			return;
+		}
+		/* this loop will hopefully kill all extra voices, because
+		   they are grouped by the same channel and note values */
+		for (voice = 0; voice < opl3->max_voices; voice++) {
+			vp = &opl3->voices[voice];
+			if (vp->state > 0 && vp->chan == chan && vp->note == note) {
+				snd_opl3_kill_voice(opl3, voice);
+			}
+		}
+	} else {
+		/* remap OSS voices */
+		if (chan->number < MAX_OPL3_VOICES) {
+			voice = snd_opl3_oss_map[chan->number];		
+			snd_opl3_kill_voice(opl3, voice);
+		}
+	}
+}
+
+void snd_opl3_note_off(void *p, int note, int vel,
+		       struct snd_midi_channel *chan)
+{
+	struct snd_opl3 *opl3 = p;
+	unsigned long flags;
+
+	spin_lock_irqsave(&opl3->voice_lock, flags);
+	snd_opl3_note_off_unsafe(p, note, vel, chan);
+	spin_unlock_irqrestore(&opl3->voice_lock, flags);
+}
+
+/*
+ * key pressure change
+ */
+void snd_opl3_key_press(void *p, int note, int vel, struct snd_midi_channel *chan)
+{
+  	struct snd_opl3 *opl3;
+
+	opl3 = p;
+#ifdef DEBUG_MIDI
+	snd_printk(KERN_DEBUG "Key pressure, ch#: %i, inst#: %i\n",
+		   chan->number, chan->midi_program);
+#endif
+}
+
+/*
+ * terminate note
+ */
+void snd_opl3_terminate_note(void *p, int note, struct snd_midi_channel *chan)
+{
+  	struct snd_opl3 *opl3;
+
+	opl3 = p;
+#ifdef DEBUG_MIDI
+	snd_printk(KERN_DEBUG "Terminate note, ch#: %i, inst#: %i\n",
+		   chan->number, chan->midi_program);
+#endif
+}
+
+static void snd_opl3_update_pitch(struct snd_opl3 *opl3, int voice)
+{
+	unsigned short reg_side;
+	unsigned char voice_offset;
+	unsigned short opl3_reg;
+
+	unsigned char fnum, blocknum;
+
+	struct snd_opl3_voice *vp;
+
+	if (snd_BUG_ON(voice >= MAX_OPL3_VOICES))
+		return;
+
+	vp = &opl3->voices[voice];
+	if (vp->chan == NULL)
+		return; /* not allocated? */
+
+	if (voice < MAX_OPL2_VOICES) {
+		/* Left register block for voices 0 .. 8 */
+		reg_side = OPL3_LEFT;
+		voice_offset = voice;
+	} else {
+		/* Right register block for voices 9 .. 17 */
+		reg_side = OPL3_RIGHT;
+		voice_offset = voice - MAX_OPL2_VOICES;
+	}
+
+	snd_opl3_calc_pitch(&fnum, &blocknum, vp->note, vp->chan);
+
+	/* Set OPL3 FNUM_LOW register of requested voice */
+	opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset);
+	opl3->command(opl3, opl3_reg, fnum);
+
+	vp->keyon_reg = blocknum;
+
+	/* Set output sound flag */
+	blocknum |= OPL3_KEYON_BIT;
+
+	/* Set OPL3 KEYON_BLOCK register of requested voice */ 
+	opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+	opl3->command(opl3, opl3_reg, blocknum);
+
+	vp->time = opl3->use_time++;
+}
+
+/*
+ * Update voice pitch controller
+ */
+static void snd_opl3_pitch_ctrl(struct snd_opl3 *opl3, struct snd_midi_channel *chan)
+{
+	int voice;
+	struct snd_opl3_voice *vp;
+
+	unsigned long flags;
+
+	spin_lock_irqsave(&opl3->voice_lock, flags);
+
+	if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
+		for (voice = 0; voice < opl3->max_voices; voice++) {
+			vp = &opl3->voices[voice];
+			if (vp->state > 0 && vp->chan == chan) {
+				snd_opl3_update_pitch(opl3, voice);
+			}
+		}
+	} else {
+		/* remap OSS voices */
+		if (chan->number < MAX_OPL3_VOICES) {
+			voice = snd_opl3_oss_map[chan->number];		
+			snd_opl3_update_pitch(opl3, voice);
+		}
+	}
+	spin_unlock_irqrestore(&opl3->voice_lock, flags);
+}
+
+/*
+ * Deal with a controller type event.  This includes all types of
+ * control events, not just the midi controllers
+ */
+void snd_opl3_control(void *p, int type, struct snd_midi_channel *chan)
+{
+  	struct snd_opl3 *opl3;
+
+	opl3 = p;
+#ifdef DEBUG_MIDI
+	snd_printk(KERN_DEBUG "Controller, TYPE = %i, ch#: %i, inst#: %i\n",
+		   type, chan->number, chan->midi_program);
+#endif
+
+	switch (type) {
+	case MIDI_CTL_MSB_MODWHEEL:
+		if (chan->control[MIDI_CTL_MSB_MODWHEEL] > 63)
+			opl3->drum_reg |= OPL3_VIBRATO_DEPTH;
+		else 
+			opl3->drum_reg &= ~OPL3_VIBRATO_DEPTH;
+		opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION,
+				 opl3->drum_reg);
+		break;
+	case MIDI_CTL_E2_TREMOLO_DEPTH:
+		if (chan->control[MIDI_CTL_E2_TREMOLO_DEPTH] > 63)
+			opl3->drum_reg |= OPL3_TREMOLO_DEPTH;
+		else 
+			opl3->drum_reg &= ~OPL3_TREMOLO_DEPTH;
+		opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION,
+				 opl3->drum_reg);
+		break;
+	case MIDI_CTL_PITCHBEND:
+		snd_opl3_pitch_ctrl(opl3, chan);
+		break;
+	}
+}
+
+/*
+ * NRPN events
+ */
+void snd_opl3_nrpn(void *p, struct snd_midi_channel *chan,
+		   struct snd_midi_channel_set *chset)
+{
+  	struct snd_opl3 *opl3;
+
+	opl3 = p;
+#ifdef DEBUG_MIDI
+	snd_printk(KERN_DEBUG "NRPN, ch#: %i, inst#: %i\n",
+		   chan->number, chan->midi_program);
+#endif
+}
+
+/*
+ * receive sysex
+ */
+void snd_opl3_sysex(void *p, unsigned char *buf, int len,
+		    int parsed, struct snd_midi_channel_set *chset)
+{
+  	struct snd_opl3 *opl3;
+
+	opl3 = p;
+#ifdef DEBUG_MIDI
+	snd_printk(KERN_DEBUG "SYSEX\n");
+#endif
+}
diff --git a/linux/sound/drivers/opl3/opl3_oss.c b/linux/sound/drivers/opl3/opl3_oss.c
new file mode 100644
index 0000000..ade3ca5
--- /dev/null
+++ b/linux/sound/drivers/opl3/opl3_oss.c
@@ -0,0 +1,284 @@
+/*
+ *  Interface for OSS sequencer emulation
+ *
+ *  Copyright (C) 2000 Uros Bizjak <uros@kss-loka.si>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "opl3_voice.h"
+
+static int snd_opl3_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure);
+static int snd_opl3_close_seq_oss(struct snd_seq_oss_arg *arg);
+static int snd_opl3_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, unsigned long ioarg);
+static int snd_opl3_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format, const char __user *buf, int offs, int count);
+static int snd_opl3_reset_seq_oss(struct snd_seq_oss_arg *arg);
+
+/* */
+
+static inline mm_segment_t snd_enter_user(void)
+{
+	mm_segment_t fs = get_fs();
+	set_fs(get_ds());
+	return fs;
+}
+
+static inline void snd_leave_user(mm_segment_t fs)
+{
+	set_fs(fs);
+}
+
+/* operators */
+
+extern struct snd_midi_op opl3_ops;
+
+static struct snd_seq_oss_callback oss_callback = {
+	.owner = 	THIS_MODULE,
+	.open =		snd_opl3_open_seq_oss,
+	.close =	snd_opl3_close_seq_oss,
+	.ioctl =	snd_opl3_ioctl_seq_oss,
+	.load_patch =	snd_opl3_load_patch_seq_oss,
+	.reset =	snd_opl3_reset_seq_oss,
+};
+
+static int snd_opl3_oss_event_input(struct snd_seq_event *ev, int direct,
+				    void *private_data, int atomic, int hop)
+{
+	struct snd_opl3 *opl3 = private_data;
+
+	if (ev->type != SNDRV_SEQ_EVENT_OSS)
+		snd_midi_process_event(&opl3_ops, ev, opl3->oss_chset);
+	return 0;
+}
+
+/* ------------------------------ */
+
+static void snd_opl3_oss_free_port(void *private_data)
+{
+	struct snd_opl3 *opl3 = private_data;
+
+	snd_midi_channel_free_set(opl3->oss_chset);
+}
+
+static int snd_opl3_oss_create_port(struct snd_opl3 * opl3)
+{
+	struct snd_seq_port_callback callbacks;
+	char name[32];
+	int voices, opl_ver;
+
+	voices = (opl3->hardware < OPL3_HW_OPL3) ?
+		MAX_OPL2_VOICES : MAX_OPL3_VOICES;
+	opl3->oss_chset = snd_midi_channel_alloc_set(voices);
+	if (opl3->oss_chset == NULL)
+		return -ENOMEM;
+	opl3->oss_chset->private_data = opl3;
+
+	memset(&callbacks, 0, sizeof(callbacks));
+	callbacks.owner = THIS_MODULE;
+	callbacks.event_input = snd_opl3_oss_event_input;
+	callbacks.private_free = snd_opl3_oss_free_port;
+	callbacks.private_data = opl3;
+
+	opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8;
+	sprintf(name, "OPL%i OSS Port", opl_ver);
+
+	opl3->oss_chset->client = opl3->seq_client;
+	opl3->oss_chset->port = snd_seq_event_port_attach(opl3->seq_client, &callbacks,
+							  SNDRV_SEQ_PORT_CAP_WRITE,
+							  SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+							  SNDRV_SEQ_PORT_TYPE_MIDI_GM |
+							  SNDRV_SEQ_PORT_TYPE_HARDWARE |
+							  SNDRV_SEQ_PORT_TYPE_SYNTHESIZER,
+							  voices, voices,
+							  name);
+	if (opl3->oss_chset->port < 0) {
+		int port;
+		port = opl3->oss_chset->port;
+		snd_midi_channel_free_set(opl3->oss_chset);
+		return port;
+	}
+	return 0;
+}
+
+/* ------------------------------ */
+
+/* register OSS synth */
+void snd_opl3_init_seq_oss(struct snd_opl3 *opl3, char *name)
+{
+	struct snd_seq_oss_reg *arg;
+	struct snd_seq_device *dev;
+
+	if (snd_seq_device_new(opl3->card, 0, SNDRV_SEQ_DEV_ID_OSS,
+			       sizeof(struct snd_seq_oss_reg), &dev) < 0)
+		return;
+
+	opl3->oss_seq_dev = dev;
+	strlcpy(dev->name, name, sizeof(dev->name));
+	arg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	arg->type = SYNTH_TYPE_FM;
+	if (opl3->hardware < OPL3_HW_OPL3) {
+		arg->subtype = FM_TYPE_ADLIB;
+		arg->nvoices = MAX_OPL2_VOICES;
+	} else {
+		arg->subtype = FM_TYPE_OPL3;
+		arg->nvoices = MAX_OPL3_VOICES;
+	}
+	arg->oper = oss_callback;
+	arg->private_data = opl3;
+
+	if (snd_opl3_oss_create_port(opl3)) {
+		/* register to OSS synth table */
+		snd_device_register(opl3->card, dev);
+	}
+}
+
+/* unregister */
+void snd_opl3_free_seq_oss(struct snd_opl3 *opl3)
+{
+	if (opl3->oss_seq_dev) {
+		/* The instance should have been released in prior */
+		opl3->oss_seq_dev = NULL;
+	}
+}
+
+/* ------------------------------ */
+
+/* open OSS sequencer */
+static int snd_opl3_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure)
+{
+	struct snd_opl3 *opl3 = closure;
+	int err;
+
+	if (snd_BUG_ON(!arg))
+		return -ENXIO;
+
+	if ((err = snd_opl3_synth_setup(opl3)) < 0)
+		return err;
+
+	/* fill the argument data */
+	arg->private_data = opl3;
+	arg->addr.client = opl3->oss_chset->client;
+	arg->addr.port = opl3->oss_chset->port;
+
+	if ((err = snd_opl3_synth_use_inc(opl3)) < 0)
+		return err;
+
+	opl3->synth_mode = SNDRV_OPL3_MODE_SYNTH;
+	return 0;
+}
+
+/* close OSS sequencer */
+static int snd_opl3_close_seq_oss(struct snd_seq_oss_arg *arg)
+{
+	struct snd_opl3 *opl3;
+
+	if (snd_BUG_ON(!arg))
+		return -ENXIO;
+	opl3 = arg->private_data;
+
+	snd_opl3_synth_cleanup(opl3);
+
+	snd_opl3_synth_use_dec(opl3);
+	return 0;
+}
+
+/* load patch */
+
+/* from sound_config.h */
+#define SBFM_MAXINSTR	256
+
+static int snd_opl3_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format,
+				       const char __user *buf, int offs, int count)
+{
+	struct snd_opl3 *opl3;
+	struct sbi_instrument sbi;
+	char name[32];
+	int err, type;
+
+	if (snd_BUG_ON(!arg))
+		return -ENXIO;
+	opl3 = arg->private_data;
+
+	if (format == FM_PATCH)
+		type = FM_PATCH_OPL2;
+	else if (format == OPL3_PATCH)
+		type = FM_PATCH_OPL3;
+	else
+		return -EINVAL;
+
+	if (count < (int)sizeof(sbi)) {
+		snd_printk(KERN_ERR "FM Error: Patch record too short\n");
+		return -EINVAL;
+	}
+	if (copy_from_user(&sbi, buf, sizeof(sbi)))
+		return -EFAULT;
+
+	if (sbi.channel < 0 || sbi.channel >= SBFM_MAXINSTR) {
+		snd_printk(KERN_ERR "FM Error: Invalid instrument number %d\n",
+			   sbi.channel);
+		return -EINVAL;
+	}
+
+	memset(name, 0, sizeof(name));
+	sprintf(name, "Chan%d", sbi.channel);
+
+	err = snd_opl3_load_patch(opl3, sbi.channel, 127, type, name, NULL,
+				  sbi.operators);
+	if (err < 0)
+		return err;
+
+	return sizeof(sbi);
+}
+
+/* ioctl */
+static int snd_opl3_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd,
+				  unsigned long ioarg)
+{
+	struct snd_opl3 *opl3;
+
+	if (snd_BUG_ON(!arg))
+		return -ENXIO;
+	opl3 = arg->private_data;
+	switch (cmd) {
+		case SNDCTL_FM_LOAD_INSTR:
+			snd_printk(KERN_ERR "OPL3: "
+				   "Obsolete ioctl(SNDCTL_FM_LOAD_INSTR) used. "
+				   "Fix the program.\n");
+			return -EINVAL;
+
+		case SNDCTL_SYNTH_MEMAVL:
+			return 0x7fffffff;
+
+		case SNDCTL_FM_4OP_ENABLE:
+			// handled automatically by OPL instrument type
+			return 0;
+
+		default:
+			return -EINVAL;
+	}
+	return 0;
+}
+
+/* reset device */
+static int snd_opl3_reset_seq_oss(struct snd_seq_oss_arg *arg)
+{
+	struct snd_opl3 *opl3;
+
+	if (snd_BUG_ON(!arg))
+		return -ENXIO;
+	opl3 = arg->private_data;
+
+	return 0;
+}
diff --git a/linux/sound/drivers/opl3/opl3_seq.c b/linux/sound/drivers/opl3/opl3_seq.c
new file mode 100644
index 0000000..2d33f53
--- /dev/null
+++ b/linux/sound/drivers/opl3/opl3_seq.c
@@ -0,0 +1,297 @@
+/*
+ *  Copyright (c) by Uros Bizjak <uros@kss-loka.si>
+ *
+ *  Midi Sequencer interface routines for OPL2/OPL3/OPL4 FM
+ *
+ *  OPL2/3 FM instrument loader:
+ *   alsa-tools/seq/sbiload/
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "opl3_voice.h"
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ALSA driver for OPL3 FM synth");
+
+int use_internal_drums = 0;
+module_param(use_internal_drums, bool, 0444);
+MODULE_PARM_DESC(use_internal_drums, "Enable internal OPL2/3 drums.");
+
+int snd_opl3_synth_use_inc(struct snd_opl3 * opl3)
+{
+	if (!try_module_get(opl3->card->module))
+		return -EFAULT;
+	return 0;
+
+}
+
+void snd_opl3_synth_use_dec(struct snd_opl3 * opl3)
+{
+	module_put(opl3->card->module);
+}
+
+int snd_opl3_synth_setup(struct snd_opl3 * opl3)
+{
+	int idx;
+	struct snd_hwdep *hwdep = opl3->hwdep;
+
+	mutex_lock(&hwdep->open_mutex);
+	if (hwdep->used) {
+		mutex_unlock(&hwdep->open_mutex);
+		return -EBUSY;
+	}
+	hwdep->used++;
+	mutex_unlock(&hwdep->open_mutex);
+
+	snd_opl3_reset(opl3);
+
+	for (idx = 0; idx < MAX_OPL3_VOICES; idx++) {
+		opl3->voices[idx].state = SNDRV_OPL3_ST_OFF;
+		opl3->voices[idx].time = 0;
+		opl3->voices[idx].keyon_reg = 0x00;
+	}
+	opl3->use_time = 0;
+	opl3->connection_reg = 0x00;
+	if (opl3->hardware >= OPL3_HW_OPL3) {
+		/* Clear 4-op connections */
+		opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT,
+				 opl3->connection_reg);
+		opl3->max_voices = MAX_OPL3_VOICES;
+	}
+	return 0;
+}
+
+void snd_opl3_synth_cleanup(struct snd_opl3 * opl3)
+{
+	unsigned long flags;
+	struct snd_hwdep *hwdep;
+
+	/* Stop system timer */
+	spin_lock_irqsave(&opl3->sys_timer_lock, flags);
+	if (opl3->sys_timer_status) {
+		del_timer(&opl3->tlist);
+		opl3->sys_timer_status = 0;
+	}
+	spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);
+
+	snd_opl3_reset(opl3);
+	hwdep = opl3->hwdep;
+	mutex_lock(&hwdep->open_mutex);
+	hwdep->used--;
+	mutex_unlock(&hwdep->open_mutex);
+	wake_up(&hwdep->open_wait);
+}
+
+static int snd_opl3_synth_use(void *private_data, struct snd_seq_port_subscribe * info)
+{
+	struct snd_opl3 *opl3 = private_data;
+	int err;
+
+	if ((err = snd_opl3_synth_setup(opl3)) < 0)
+		return err;
+
+	if (use_internal_drums) {
+		/* Percussion mode */
+		opl3->voices[6].state = opl3->voices[7].state = 
+			opl3->voices[8].state = SNDRV_OPL3_ST_NOT_AVAIL;
+		snd_opl3_load_drums(opl3);
+		opl3->drum_reg = OPL3_PERCUSSION_ENABLE;
+		opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, opl3->drum_reg);
+	} else {
+		opl3->drum_reg = 0x00;
+	}
+
+	if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) {
+		if ((err = snd_opl3_synth_use_inc(opl3)) < 0)
+			return err;
+	}
+	opl3->synth_mode = SNDRV_OPL3_MODE_SEQ;
+	return 0;
+}
+
+static int snd_opl3_synth_unuse(void *private_data, struct snd_seq_port_subscribe * info)
+{
+	struct snd_opl3 *opl3 = private_data;
+
+	snd_opl3_synth_cleanup(opl3);
+
+	if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM)
+		snd_opl3_synth_use_dec(opl3);
+	return 0;
+}
+
+/*
+ * MIDI emulation operators
+ */
+struct snd_midi_op opl3_ops = {
+	.note_on =		snd_opl3_note_on,
+	.note_off =		snd_opl3_note_off,
+	.key_press =		snd_opl3_key_press,
+	.note_terminate =	snd_opl3_terminate_note,
+	.control =		snd_opl3_control,
+	.nrpn =			snd_opl3_nrpn,
+	.sysex =		snd_opl3_sysex,
+};
+
+static int snd_opl3_synth_event_input(struct snd_seq_event * ev, int direct,
+				      void *private_data, int atomic, int hop)
+{
+	struct snd_opl3 *opl3 = private_data;
+
+	snd_midi_process_event(&opl3_ops, ev, opl3->chset);
+	return 0;
+}
+
+/* ------------------------------ */
+
+static void snd_opl3_synth_free_port(void *private_data)
+{
+	struct snd_opl3 *opl3 = private_data;
+
+	snd_midi_channel_free_set(opl3->chset);
+}
+
+static int snd_opl3_synth_create_port(struct snd_opl3 * opl3)
+{
+	struct snd_seq_port_callback callbacks;
+	char name[32];
+	int voices, opl_ver;
+
+	voices = (opl3->hardware < OPL3_HW_OPL3) ?
+		MAX_OPL2_VOICES : MAX_OPL3_VOICES;
+	opl3->chset = snd_midi_channel_alloc_set(16);
+	if (opl3->chset == NULL)
+		return -ENOMEM;
+	opl3->chset->private_data = opl3;
+
+	memset(&callbacks, 0, sizeof(callbacks));
+	callbacks.owner = THIS_MODULE;
+	callbacks.use = snd_opl3_synth_use;
+	callbacks.unuse = snd_opl3_synth_unuse;
+	callbacks.event_input = snd_opl3_synth_event_input;
+	callbacks.private_free = snd_opl3_synth_free_port;
+	callbacks.private_data = opl3;
+
+	opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8;
+	sprintf(name, "OPL%i FM Port", opl_ver);
+
+	opl3->chset->client = opl3->seq_client;
+	opl3->chset->port = snd_seq_event_port_attach(opl3->seq_client, &callbacks,
+						      SNDRV_SEQ_PORT_CAP_WRITE |
+						      SNDRV_SEQ_PORT_CAP_SUBS_WRITE,
+						      SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+						      SNDRV_SEQ_PORT_TYPE_MIDI_GM |
+						      SNDRV_SEQ_PORT_TYPE_DIRECT_SAMPLE |
+						      SNDRV_SEQ_PORT_TYPE_HARDWARE |
+						      SNDRV_SEQ_PORT_TYPE_SYNTHESIZER,
+						      16, voices,
+						      name);
+	if (opl3->chset->port < 0) {
+		int port;
+		port = opl3->chset->port;
+		snd_midi_channel_free_set(opl3->chset);
+		return port;
+	}
+	return 0;
+}
+
+/* ------------------------------ */
+
+static int snd_opl3_seq_new_device(struct snd_seq_device *dev)
+{
+	struct snd_opl3 *opl3;
+	int client, err;
+	char name[32];
+	int opl_ver;
+
+	opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (opl3 == NULL)
+		return -EINVAL;
+
+	spin_lock_init(&opl3->voice_lock);
+
+	opl3->seq_client = -1;
+
+	/* allocate new client */
+	opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8;
+	sprintf(name, "OPL%i FM synth", opl_ver);
+	client = opl3->seq_client =
+		snd_seq_create_kernel_client(opl3->card, opl3->seq_dev_num,
+					     name);
+	if (client < 0)
+		return client;
+
+	if ((err = snd_opl3_synth_create_port(opl3)) < 0) {
+		snd_seq_delete_kernel_client(client);
+		opl3->seq_client = -1;
+		return err;
+	}
+
+	/* setup system timer */
+	init_timer(&opl3->tlist);
+	opl3->tlist.function = snd_opl3_timer_func;
+	opl3->tlist.data = (unsigned long) opl3;
+	spin_lock_init(&opl3->sys_timer_lock);
+	opl3->sys_timer_status = 0;
+
+#ifdef CONFIG_SND_SEQUENCER_OSS
+	snd_opl3_init_seq_oss(opl3, name);
+#endif
+	return 0;
+}
+
+static int snd_opl3_seq_delete_device(struct snd_seq_device *dev)
+{
+	struct snd_opl3 *opl3;
+
+	opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (opl3 == NULL)
+		return -EINVAL;
+
+#ifdef CONFIG_SND_SEQUENCER_OSS
+	snd_opl3_free_seq_oss(opl3);
+#endif
+	if (opl3->seq_client >= 0) {
+		snd_seq_delete_kernel_client(opl3->seq_client);
+		opl3->seq_client = -1;
+	}
+	return 0;
+}
+
+static int __init alsa_opl3_seq_init(void)
+{
+	static struct snd_seq_dev_ops ops =
+	{
+		snd_opl3_seq_new_device,
+		snd_opl3_seq_delete_device
+	};
+
+	return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OPL3, &ops,
+					      sizeof(struct snd_opl3 *));
+}
+
+static void __exit alsa_opl3_seq_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OPL3);
+}
+
+module_init(alsa_opl3_seq_init)
+module_exit(alsa_opl3_seq_exit)
diff --git a/linux/sound/drivers/opl3/opl3_synth.c b/linux/sound/drivers/opl3/opl3_synth.c
new file mode 100644
index 0000000..301acb6
--- /dev/null
+++ b/linux/sound/drivers/opl3/opl3_synth.c
@@ -0,0 +1,615 @@
+/*
+ *  Copyright (c) by Uros Bizjak <uros@kss-loka.si>
+ *                   
+ *  Routines for OPL2/OPL3/OPL4 control
+ *
+ *   This program is free software; you can redistribute it and/or modify 
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/slab.h>
+#include <sound/opl3.h>
+#include <sound/asound_fm.h>
+
+#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
+#define OPL3_SUPPORT_SYNTH
+#endif
+
+/*
+ *    There is 18 possible 2 OP voices
+ *      (9 in the left and 9 in the right).
+ *      The first OP is the modulator and 2nd is the carrier.
+ *
+ *      The first three voices in the both sides may be connected
+ *      with another voice to a 4 OP voice. For example voice 0
+ *      can be connected with voice 3. The operators of voice 3 are
+ *      used as operators 3 and 4 of the new 4 OP voice.
+ *      In this case the 2 OP voice number 0 is the 'first half' and
+ *      voice 3 is the second.
+ */
+
+
+/*
+ *    Register offset table for OPL2/3 voices,
+ *    OPL2 / one OPL3 register array side only
+ */
+
+char snd_opl3_regmap[MAX_OPL2_VOICES][4] =
+{
+/*	  OP1   OP2   OP3   OP4		*/
+/*	 ------------------------	*/
+	{ 0x00, 0x03, 0x08, 0x0b },
+	{ 0x01, 0x04, 0x09, 0x0c },
+	{ 0x02, 0x05, 0x0a, 0x0d },
+
+	{ 0x08, 0x0b, 0x00, 0x00 },
+	{ 0x09, 0x0c, 0x00, 0x00 },
+	{ 0x0a, 0x0d, 0x00, 0x00 },
+
+	{ 0x10, 0x13, 0x00, 0x00 },	/* used by percussive voices */
+	{ 0x11, 0x14, 0x00, 0x00 },	/* if the percussive mode */
+	{ 0x12, 0x15, 0x00, 0x00 }	/* is selected (only left reg block) */
+};
+
+EXPORT_SYMBOL(snd_opl3_regmap);
+
+/*
+ * prototypes
+ */
+static int snd_opl3_play_note(struct snd_opl3 * opl3, struct snd_dm_fm_note * note);
+static int snd_opl3_set_voice(struct snd_opl3 * opl3, struct snd_dm_fm_voice * voice);
+static int snd_opl3_set_params(struct snd_opl3 * opl3, struct snd_dm_fm_params * params);
+static int snd_opl3_set_mode(struct snd_opl3 * opl3, int mode);
+static int snd_opl3_set_connection(struct snd_opl3 * opl3, int connection);
+
+/* ------------------------------ */
+
+/*
+ * open the device exclusively
+ */
+int snd_opl3_open(struct snd_hwdep * hw, struct file *file)
+{
+	return 0;
+}
+
+/*
+ * ioctl for hwdep device:
+ */
+int snd_opl3_ioctl(struct snd_hwdep * hw, struct file *file,
+		   unsigned int cmd, unsigned long arg)
+{
+	struct snd_opl3 *opl3 = hw->private_data;
+	void __user *argp = (void __user *)arg;
+
+	if (snd_BUG_ON(!opl3))
+		return -EINVAL;
+
+	switch (cmd) {
+		/* get information */
+	case SNDRV_DM_FM_IOCTL_INFO:
+		{
+			struct snd_dm_fm_info info;
+
+			info.fm_mode = opl3->fm_mode;
+			info.rhythm = opl3->rhythm;
+			if (copy_to_user(argp, &info, sizeof(struct snd_dm_fm_info)))
+				return -EFAULT;
+			return 0;
+		}
+
+	case SNDRV_DM_FM_IOCTL_RESET:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_RESET:
+#endif
+		snd_opl3_reset(opl3);
+		return 0;
+
+	case SNDRV_DM_FM_IOCTL_PLAY_NOTE:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_PLAY_NOTE:
+#endif
+		{
+			struct snd_dm_fm_note note;
+			if (copy_from_user(&note, argp, sizeof(struct snd_dm_fm_note)))
+				return -EFAULT;
+			return snd_opl3_play_note(opl3, &note);
+		}
+
+	case SNDRV_DM_FM_IOCTL_SET_VOICE:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_SET_VOICE:
+#endif
+		{
+			struct snd_dm_fm_voice voice;
+			if (copy_from_user(&voice, argp, sizeof(struct snd_dm_fm_voice)))
+				return -EFAULT;
+			return snd_opl3_set_voice(opl3, &voice);
+		}
+
+	case SNDRV_DM_FM_IOCTL_SET_PARAMS:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_SET_PARAMS:
+#endif
+		{
+			struct snd_dm_fm_params params;
+			if (copy_from_user(&params, argp, sizeof(struct snd_dm_fm_params)))
+				return -EFAULT;
+			return snd_opl3_set_params(opl3, &params);
+		}
+
+	case SNDRV_DM_FM_IOCTL_SET_MODE:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_SET_MODE:
+#endif
+		return snd_opl3_set_mode(opl3, (int) arg);
+
+	case SNDRV_DM_FM_IOCTL_SET_CONNECTION:
+#ifdef CONFIG_SND_OSSEMUL
+	case SNDRV_DM_FM_OSS_IOCTL_SET_OPL:
+#endif
+		return snd_opl3_set_connection(opl3, (int) arg);
+
+#ifdef OPL3_SUPPORT_SYNTH
+	case SNDRV_DM_FM_IOCTL_CLEAR_PATCHES:
+		snd_opl3_clear_patches(opl3);
+		return 0;
+#endif
+
+#ifdef CONFIG_SND_DEBUG
+	default:
+		snd_printk(KERN_WARNING "unknown IOCTL: 0x%x\n", cmd);
+#endif
+	}
+	return -ENOTTY;
+}
+
+/*
+ * close the device
+ */
+int snd_opl3_release(struct snd_hwdep * hw, struct file *file)
+{
+	struct snd_opl3 *opl3 = hw->private_data;
+
+	snd_opl3_reset(opl3);
+	return 0;
+}
+
+#ifdef OPL3_SUPPORT_SYNTH
+/*
+ * write the device - load patches
+ */
+long snd_opl3_write(struct snd_hwdep *hw, const char __user *buf, long count,
+		    loff_t *offset)
+{
+	struct snd_opl3 *opl3 = hw->private_data;
+	long result = 0;
+	int err = 0;
+	struct sbi_patch inst;
+
+	while (count >= sizeof(inst)) {
+		unsigned char type;
+		if (copy_from_user(&inst, buf, sizeof(inst)))
+			return -EFAULT;
+		if (!memcmp(inst.key, FM_KEY_SBI, 4) ||
+		    !memcmp(inst.key, FM_KEY_2OP, 4))
+			type = FM_PATCH_OPL2;
+		else if (!memcmp(inst.key, FM_KEY_4OP, 4))
+			type = FM_PATCH_OPL3;
+		else /* invalid type */
+			break;
+		err = snd_opl3_load_patch(opl3, inst.prog, inst.bank, type,
+					  inst.name, inst.extension,
+					  inst.data);
+		if (err < 0)
+			break;
+		result += sizeof(inst);
+		count -= sizeof(inst);
+	}
+	return result > 0 ? result : err;
+}
+
+
+/*
+ * Patch management
+ */
+
+/* offsets for SBI params */
+#define AM_VIB		0
+#define KSL_LEVEL	2
+#define ATTACK_DECAY	4
+#define SUSTAIN_RELEASE	6
+#define WAVE_SELECT	8
+
+/* offset for SBI instrument */
+#define CONNECTION	10
+#define OFFSET_4OP	11
+
+/*
+ * load a patch, obviously.
+ *
+ * loaded on the given program and bank numbers with the given type
+ * (FM_PATCH_OPLx).
+ * data is the pointer of SBI record _without_ header (key and name).
+ * name is the name string of the patch.
+ * ext is the extension data of 7 bytes long (stored in name of SBI
+ * data up to offset 25), or NULL to skip.
+ * return 0 if successful or a negative error code.
+ */
+int snd_opl3_load_patch(struct snd_opl3 *opl3,
+			int prog, int bank, int type,
+			const char *name,
+			const unsigned char *ext,
+			const unsigned char *data)
+{
+	struct fm_patch *patch;
+	int i;
+
+	patch = snd_opl3_find_patch(opl3, prog, bank, 1);
+	if (!patch)
+		return -ENOMEM;
+
+	patch->type = type;
+
+	for (i = 0; i < 2; i++) {
+		patch->inst.op[i].am_vib = data[AM_VIB + i];
+		patch->inst.op[i].ksl_level = data[KSL_LEVEL + i];
+		patch->inst.op[i].attack_decay = data[ATTACK_DECAY + i];
+		patch->inst.op[i].sustain_release = data[SUSTAIN_RELEASE + i];
+		patch->inst.op[i].wave_select = data[WAVE_SELECT + i];
+	}
+	patch->inst.feedback_connection[0] = data[CONNECTION];
+
+	if (type == FM_PATCH_OPL3) {
+		for (i = 0; i < 2; i++) {
+			patch->inst.op[i+2].am_vib =
+				data[OFFSET_4OP + AM_VIB + i];
+			patch->inst.op[i+2].ksl_level =
+				data[OFFSET_4OP + KSL_LEVEL + i];
+			patch->inst.op[i+2].attack_decay =
+				data[OFFSET_4OP + ATTACK_DECAY + i];
+			patch->inst.op[i+2].sustain_release =
+				data[OFFSET_4OP + SUSTAIN_RELEASE + i];
+			patch->inst.op[i+2].wave_select =
+				data[OFFSET_4OP + WAVE_SELECT + i];
+		}
+		patch->inst.feedback_connection[1] =
+			data[OFFSET_4OP + CONNECTION];
+	}
+
+	if (ext) {
+		patch->inst.echo_delay = ext[0];
+		patch->inst.echo_atten = ext[1];
+		patch->inst.chorus_spread = ext[2];
+		patch->inst.trnsps = ext[3];
+		patch->inst.fix_dur = ext[4];
+		patch->inst.modes = ext[5];
+		patch->inst.fix_key = ext[6];
+	}
+
+	if (name)
+		strlcpy(patch->name, name, sizeof(patch->name));
+
+	return 0;
+}
+EXPORT_SYMBOL(snd_opl3_load_patch);
+
+/*
+ * find a patch with the given program and bank numbers, returns its pointer
+ * if no matching patch is found and create_patch is set, it creates a
+ * new patch object.
+ */
+struct fm_patch *snd_opl3_find_patch(struct snd_opl3 *opl3, int prog, int bank,
+				     int create_patch)
+{
+	/* pretty dumb hash key */
+	unsigned int key = (prog + bank) % OPL3_PATCH_HASH_SIZE;
+	struct fm_patch *patch;
+
+	for (patch = opl3->patch_table[key]; patch; patch = patch->next) {
+		if (patch->prog == prog && patch->bank == bank)
+			return patch;
+	}
+	if (!create_patch)
+		return NULL;
+
+	patch = kzalloc(sizeof(*patch), GFP_KERNEL);
+	if (!patch)
+		return NULL;
+	patch->prog = prog;
+	patch->bank = bank;
+	patch->next = opl3->patch_table[key];
+	opl3->patch_table[key] = patch;
+	return patch;
+}
+EXPORT_SYMBOL(snd_opl3_find_patch);
+
+/*
+ * Clear all patches of the given OPL3 instance
+ */
+void snd_opl3_clear_patches(struct snd_opl3 *opl3)
+{
+	int i;
+	for (i = 0; i <  OPL3_PATCH_HASH_SIZE; i++) {
+		struct fm_patch *patch, *next;
+		for (patch = opl3->patch_table[i]; patch; patch = next) {
+			next = patch->next;
+			kfree(patch);
+		}
+	}
+	memset(opl3->patch_table, 0, sizeof(opl3->patch_table));
+}
+#endif /* OPL3_SUPPORT_SYNTH */
+
+/* ------------------------------ */
+
+void snd_opl3_reset(struct snd_opl3 * opl3)
+{
+	unsigned short opl3_reg;
+
+	unsigned short reg_side;
+	unsigned char voice_offset;
+
+	int max_voices, i;
+
+	max_voices = (opl3->hardware < OPL3_HW_OPL3) ?
+		MAX_OPL2_VOICES : MAX_OPL3_VOICES;
+
+	for (i = 0; i < max_voices; i++) {
+		/* Get register array side and offset of voice */
+		if (i < MAX_OPL2_VOICES) {
+			/* Left register block for voices 0 .. 8 */
+			reg_side = OPL3_LEFT;
+			voice_offset = i;
+		} else {
+			/* Right register block for voices 9 .. 17 */
+			reg_side = OPL3_RIGHT;
+			voice_offset = i - MAX_OPL2_VOICES;
+		}
+		opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + snd_opl3_regmap[voice_offset][0]);
+		opl3->command(opl3, opl3_reg, OPL3_TOTAL_LEVEL_MASK); /* Operator 1 volume */
+		opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + snd_opl3_regmap[voice_offset][1]);
+		opl3->command(opl3, opl3_reg, OPL3_TOTAL_LEVEL_MASK); /* Operator 2 volume */
+
+		opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+		opl3->command(opl3, opl3_reg, 0x00);	/* Note off */
+	}
+
+	opl3->max_voices = MAX_OPL2_VOICES;
+	opl3->fm_mode = SNDRV_DM_FM_MODE_OPL2;
+
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TEST, OPL3_ENABLE_WAVE_SELECT);
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, 0x00);	/* Melodic mode */
+	opl3->rhythm = 0;
+}
+
+EXPORT_SYMBOL(snd_opl3_reset);
+
+static int snd_opl3_play_note(struct snd_opl3 * opl3, struct snd_dm_fm_note * note)
+{
+	unsigned short reg_side;
+	unsigned char voice_offset;
+
+	unsigned short opl3_reg;
+	unsigned char reg_val;
+
+	/* Voices 0 -  8 in OPL2 mode */
+	/* Voices 0 - 17 in OPL3 mode */
+	if (note->voice >= ((opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) ?
+			    MAX_OPL3_VOICES : MAX_OPL2_VOICES))
+		return -EINVAL;
+
+	/* Get register array side and offset of voice */
+	if (note->voice < MAX_OPL2_VOICES) {
+		/* Left register block for voices 0 .. 8 */
+		reg_side = OPL3_LEFT;
+		voice_offset = note->voice;
+	} else {
+		/* Right register block for voices 9 .. 17 */
+		reg_side = OPL3_RIGHT;
+		voice_offset = note->voice - MAX_OPL2_VOICES;
+	}
+
+	/* Set lower 8 bits of note frequency */
+	reg_val = (unsigned char) note->fnum;
+	opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+	
+	reg_val = 0x00;
+	/* Set output sound flag */
+	if (note->key_on)
+		reg_val |= OPL3_KEYON_BIT;
+	/* Set octave */
+	reg_val |= (note->octave << 2) & OPL3_BLOCKNUM_MASK;
+	/* Set higher 2 bits of note frequency */
+	reg_val |= (unsigned char) (note->fnum >> 8) & OPL3_FNUM_HIGH_MASK;
+
+	/* Set OPL3 KEYON_BLOCK register of requested voice */ 
+	opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	return 0;
+}
+
+
+static int snd_opl3_set_voice(struct snd_opl3 * opl3, struct snd_dm_fm_voice * voice)
+{
+	unsigned short reg_side;
+	unsigned char op_offset;
+	unsigned char voice_offset;
+
+	unsigned short opl3_reg;
+	unsigned char reg_val;
+
+	/* Only operators 1 and 2 */
+	if (voice->op > 1)
+		return -EINVAL;
+	/* Voices 0 -  8 in OPL2 mode */
+	/* Voices 0 - 17 in OPL3 mode */
+	if (voice->voice >= ((opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) ?
+			     MAX_OPL3_VOICES : MAX_OPL2_VOICES))
+		return -EINVAL;
+
+	/* Get register array side and offset of voice */
+	if (voice->voice < MAX_OPL2_VOICES) {
+		/* Left register block for voices 0 .. 8 */
+		reg_side = OPL3_LEFT;
+		voice_offset = voice->voice;
+	} else {
+		/* Right register block for voices 9 .. 17 */
+		reg_side = OPL3_RIGHT;
+		voice_offset = voice->voice - MAX_OPL2_VOICES;
+	}
+	/* Get register offset of operator */
+	op_offset = snd_opl3_regmap[voice_offset][voice->op];
+
+	reg_val = 0x00;
+	/* Set amplitude modulation (tremolo) effect */
+	if (voice->am)
+		reg_val |= OPL3_TREMOLO_ON;
+	/* Set vibrato effect */
+	if (voice->vibrato)
+		reg_val |= OPL3_VIBRATO_ON;
+	/* Set sustaining sound phase */
+	if (voice->do_sustain)
+		reg_val |= OPL3_SUSTAIN_ON;
+	/* Set keyboard scaling bit */ 
+	if (voice->kbd_scale)
+		reg_val |= OPL3_KSR;
+	/* Set harmonic or frequency multiplier */
+	reg_val |= voice->harmonic & OPL3_MULTIPLE_MASK;
+
+	/* Set OPL3 AM_VIB register of requested voice/operator */ 
+	opl3_reg = reg_side | (OPL3_REG_AM_VIB + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Set decreasing volume of higher notes */
+	reg_val = (voice->scale_level << 6) & OPL3_KSL_MASK;
+	/* Set output volume */
+	reg_val |= ~voice->volume & OPL3_TOTAL_LEVEL_MASK;
+
+	/* Set OPL3 KSL_LEVEL register of requested voice/operator */ 
+	opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Set attack phase level */
+	reg_val = (voice->attack << 4) & OPL3_ATTACK_MASK;
+	/* Set decay phase level */
+	reg_val |= voice->decay & OPL3_DECAY_MASK;
+
+	/* Set OPL3 ATTACK_DECAY register of requested voice/operator */ 
+	opl3_reg = reg_side | (OPL3_REG_ATTACK_DECAY + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Set sustain phase level */
+	reg_val = (voice->sustain << 4) & OPL3_SUSTAIN_MASK;
+	/* Set release phase level */
+	reg_val |= voice->release & OPL3_RELEASE_MASK;
+
+	/* Set OPL3 SUSTAIN_RELEASE register of requested voice/operator */ 
+	opl3_reg = reg_side | (OPL3_REG_SUSTAIN_RELEASE + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Set inter-operator feedback */
+	reg_val = (voice->feedback << 1) & OPL3_FEEDBACK_MASK;
+	/* Set inter-operator connection */
+	if (voice->connection)
+		reg_val |= OPL3_CONNECTION_BIT;
+	/* OPL-3 only */
+	if (opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) {
+		if (voice->left)
+			reg_val |= OPL3_VOICE_TO_LEFT;
+		if (voice->right)
+			reg_val |= OPL3_VOICE_TO_RIGHT;
+	}
+	/* Feedback/connection bits are applicable to voice */
+	opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	/* Select waveform */
+	reg_val = voice->waveform & OPL3_WAVE_SELECT_MASK;
+	opl3_reg = reg_side | (OPL3_REG_WAVE_SELECT + op_offset);
+	opl3->command(opl3, opl3_reg, reg_val);
+
+	return 0;
+}
+
+static int snd_opl3_set_params(struct snd_opl3 * opl3, struct snd_dm_fm_params * params)
+{
+	unsigned char reg_val;
+
+	reg_val = 0x00;
+	/* Set keyboard split method */
+	if (params->kbd_split)
+		reg_val |= OPL3_KEYBOARD_SPLIT;
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_KBD_SPLIT, reg_val);
+
+	reg_val = 0x00;
+	/* Set amplitude modulation (tremolo) depth */
+	if (params->am_depth)
+		reg_val |= OPL3_TREMOLO_DEPTH;
+	/* Set vibrato depth */
+	if (params->vib_depth)
+		reg_val |= OPL3_VIBRATO_DEPTH;
+	/* Set percussion mode */
+	if (params->rhythm) {
+		reg_val |= OPL3_PERCUSSION_ENABLE;
+		opl3->rhythm = 1;
+	} else {
+		opl3->rhythm = 0;
+	}
+	/* Play percussion instruments */
+	if (params->bass)
+		reg_val |= OPL3_BASSDRUM_ON;
+	if (params->snare)
+		reg_val |= OPL3_SNAREDRUM_ON;
+	if (params->tomtom)
+		reg_val |= OPL3_TOMTOM_ON;
+	if (params->cymbal)
+		reg_val |= OPL3_CYMBAL_ON;
+	if (params->hihat)
+		reg_val |= OPL3_HIHAT_ON;
+
+	opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, reg_val);
+	return 0;
+}
+
+static int snd_opl3_set_mode(struct snd_opl3 * opl3, int mode)
+{
+	if ((mode == SNDRV_DM_FM_MODE_OPL3) && (opl3->hardware < OPL3_HW_OPL3))
+		return -EINVAL;
+
+	opl3->fm_mode = mode;
+	if (opl3->hardware >= OPL3_HW_OPL3)
+		opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT, 0x00);	/* Clear 4-op connections */
+
+	return 0;
+}
+
+static int snd_opl3_set_connection(struct snd_opl3 * opl3, int connection)
+{
+	unsigned char reg_val;
+
+	/* OPL-3 only */
+	if (opl3->fm_mode != SNDRV_DM_FM_MODE_OPL3)
+		return -EINVAL;
+
+	reg_val = connection & (OPL3_RIGHT_4OP_0 | OPL3_RIGHT_4OP_1 | OPL3_RIGHT_4OP_2 |
+				OPL3_LEFT_4OP_0 | OPL3_LEFT_4OP_1 | OPL3_LEFT_4OP_2);
+	/* Set 4-op connections */
+	opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT, reg_val);
+
+	return 0;
+}
+
diff --git a/linux/sound/drivers/opl3/opl3_voice.h b/linux/sound/drivers/opl3/opl3_voice.h
new file mode 100644
index 0000000..a371c07
--- /dev/null
+++ b/linux/sound/drivers/opl3/opl3_voice.h
@@ -0,0 +1,52 @@
+#ifndef __OPL3_VOICE_H
+#define __OPL3_VOICE_H
+
+/*
+ *  Copyright (c) 2000 Uros Bizjak <uros@kss-loka.si>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/opl3.h>
+
+/* Prototypes for opl3_seq.c */
+int snd_opl3_synth_use_inc(struct snd_opl3 * opl3);
+void snd_opl3_synth_use_dec(struct snd_opl3 * opl3);
+int snd_opl3_synth_setup(struct snd_opl3 * opl3);
+void snd_opl3_synth_cleanup(struct snd_opl3 * opl3);
+
+/* Prototypes for opl3_midi.c */
+void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan);
+void snd_opl3_note_off(void *p, int note, int vel, struct snd_midi_channel *chan);
+void snd_opl3_key_press(void *p, int note, int vel, struct snd_midi_channel *chan);
+void snd_opl3_terminate_note(void *p, int note, struct snd_midi_channel *chan);
+void snd_opl3_control(void *p, int type, struct snd_midi_channel *chan);
+void snd_opl3_nrpn(void *p, struct snd_midi_channel *chan, struct snd_midi_channel_set *chset);
+void snd_opl3_sysex(void *p, unsigned char *buf, int len, int parsed, struct snd_midi_channel_set *chset);
+
+void snd_opl3_calc_volume(unsigned char *reg, int vel, struct snd_midi_channel *chan);
+void snd_opl3_timer_func(unsigned long data);
+
+/* Prototypes for opl3_drums.c */
+void snd_opl3_load_drums(struct snd_opl3 *opl3);
+void snd_opl3_drum_switch(struct snd_opl3 *opl3, int note, int on_off, int vel, struct snd_midi_channel *chan);
+
+/* Prototypes for opl3_oss.c */
+#ifdef CONFIG_SND_SEQUENCER_OSS
+void snd_opl3_init_seq_oss(struct snd_opl3 *opl3, char *name);
+void snd_opl3_free_seq_oss(struct snd_opl3 *opl3);
+#endif
+
+#endif
diff --git a/linux/sound/drivers/opl4/Makefile b/linux/sound/drivers/opl4/Makefile
new file mode 100644
index 0000000..b94009b
--- /dev/null
+++ b/linux/sound/drivers/opl4/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-opl4-lib-objs := opl4_lib.o opl4_mixer.o opl4_proc.o
+snd-opl4-synth-objs := opl4_seq.o opl4_synth.o yrw801.o
+
+obj-$(CONFIG_SND_OPL4_LIB) += snd-opl4-lib.o
+obj-$(CONFIG_SND_OPL4_LIB_SEQ) += snd-opl4-synth.o
diff --git a/linux/sound/drivers/opl4/opl4_lib.c b/linux/sound/drivers/opl4/opl4_lib.c
new file mode 100644
index 0000000..f07e38d
--- /dev/null
+++ b/linux/sound/drivers/opl4/opl4_lib.c
@@ -0,0 +1,280 @@
+/*
+ * Functions for accessing OPL4 devices
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "opl4_local.h"
+#include <sound/initval.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <asm/io.h>
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("OPL4 driver");
+MODULE_LICENSE("GPL");
+
+static void inline snd_opl4_wait(struct snd_opl4 *opl4)
+{
+	int timeout = 10;
+	while ((inb(opl4->fm_port) & OPL4_STATUS_BUSY) && --timeout > 0)
+		;
+}
+
+void snd_opl4_write(struct snd_opl4 *opl4, u8 reg, u8 value)
+{
+	snd_opl4_wait(opl4);
+	outb(reg, opl4->pcm_port);
+
+	snd_opl4_wait(opl4);
+	outb(value, opl4->pcm_port + 1);
+}
+
+EXPORT_SYMBOL(snd_opl4_write);
+
+u8 snd_opl4_read(struct snd_opl4 *opl4, u8 reg)
+{
+	snd_opl4_wait(opl4);
+	outb(reg, opl4->pcm_port);
+
+	snd_opl4_wait(opl4);
+	return inb(opl4->pcm_port + 1);
+}
+
+EXPORT_SYMBOL(snd_opl4_read);
+
+void snd_opl4_read_memory(struct snd_opl4 *opl4, char *buf, int offset, int size)
+{
+	unsigned long flags;
+	u8 memcfg;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+
+	memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
+
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
+
+	snd_opl4_wait(opl4);
+	outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
+	snd_opl4_wait(opl4);
+	insb(opl4->pcm_port + 1, buf, size);
+
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
+
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+EXPORT_SYMBOL(snd_opl4_read_memory);
+
+void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size)
+{
+	unsigned long flags;
+	u8 memcfg;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+
+	memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
+
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
+
+	snd_opl4_wait(opl4);
+	outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
+	snd_opl4_wait(opl4);
+	outsb(opl4->pcm_port + 1, buf, size);
+
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
+
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+EXPORT_SYMBOL(snd_opl4_write_memory);
+
+static void snd_opl4_enable_opl4(struct snd_opl4 *opl4)
+{
+	outb(OPL3_REG_MODE, opl4->fm_port + 2);
+	inb(opl4->fm_port);
+	inb(opl4->fm_port);
+	outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, opl4->fm_port + 3);
+	inb(opl4->fm_port);
+	inb(opl4->fm_port);
+}
+
+static int snd_opl4_detect(struct snd_opl4 *opl4)
+{
+	u8 id1, id2;
+
+	snd_opl4_enable_opl4(opl4);
+
+	id1 = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
+	snd_printdd("OPL4[02]=%02x\n", id1);
+	switch (id1 & OPL4_DEVICE_ID_MASK) {
+	case 0x20:
+		opl4->hardware = OPL3_HW_OPL4;
+		break;
+	case 0x40:
+		opl4->hardware = OPL3_HW_OPL4_ML;
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x00);
+	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0xff);
+	id1 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_FM);
+	id2 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_PCM);
+	snd_printdd("OPL4 id1=%02x id2=%02x\n", id1, id2);
+       	if (id1 != 0x00 || id2 != 0xff)
+		return -ENODEV;
+
+	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x3f);
+	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0x3f);
+	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, 0x00);
+	return 0;
+}
+
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+static void snd_opl4_seq_dev_free(struct snd_seq_device *seq_dev)
+{
+	struct snd_opl4 *opl4 = seq_dev->private_data;
+	opl4->seq_dev = NULL;
+}
+
+static int snd_opl4_create_seq_dev(struct snd_opl4 *opl4, int seq_device)
+{
+	opl4->seq_dev_num = seq_device;
+	if (snd_seq_device_new(opl4->card, seq_device, SNDRV_SEQ_DEV_ID_OPL4,
+			       sizeof(struct snd_opl4 *), &opl4->seq_dev) >= 0) {
+		strcpy(opl4->seq_dev->name, "OPL4 Wavetable");
+		*(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(opl4->seq_dev) = opl4;
+		opl4->seq_dev->private_data = opl4;
+		opl4->seq_dev->private_free = snd_opl4_seq_dev_free;
+	}
+	return 0;
+}
+#endif
+
+static void snd_opl4_free(struct snd_opl4 *opl4)
+{
+#ifdef CONFIG_PROC_FS
+	snd_opl4_free_proc(opl4);
+#endif
+	release_and_free_resource(opl4->res_fm_port);
+	release_and_free_resource(opl4->res_pcm_port);
+	kfree(opl4);
+}
+
+static int snd_opl4_dev_free(struct snd_device *device)
+{
+	struct snd_opl4 *opl4 = device->device_data;
+	snd_opl4_free(opl4);
+	return 0;
+}
+
+int snd_opl4_create(struct snd_card *card,
+		    unsigned long fm_port, unsigned long pcm_port,
+		    int seq_device,
+		    struct snd_opl3 **ropl3, struct snd_opl4 **ropl4)
+{
+	struct snd_opl4 *opl4;
+	struct snd_opl3 *opl3;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_opl4_dev_free
+	};
+
+	if (ropl3)
+		*ropl3 = NULL;
+	if (ropl4)
+		*ropl4 = NULL;
+
+	opl4 = kzalloc(sizeof(*opl4), GFP_KERNEL);
+	if (!opl4)
+		return -ENOMEM;
+
+	opl4->res_fm_port = request_region(fm_port, 8, "OPL4 FM");
+	opl4->res_pcm_port = request_region(pcm_port, 8, "OPL4 PCM/MIX");
+	if (!opl4->res_fm_port || !opl4->res_pcm_port) {
+		snd_printk(KERN_ERR "opl4: can't grab ports 0x%lx, 0x%lx\n", fm_port, pcm_port);
+		snd_opl4_free(opl4);
+		return -EBUSY;
+	}
+
+	opl4->card = card;
+	opl4->fm_port = fm_port;
+	opl4->pcm_port = pcm_port;
+	spin_lock_init(&opl4->reg_lock);
+	mutex_init(&opl4->access_mutex);
+
+	err = snd_opl4_detect(opl4);
+	if (err < 0) {
+		snd_opl4_free(opl4);
+		snd_printd("OPL4 chip not detected at %#lx/%#lx\n", fm_port, pcm_port);
+		return err;
+	}
+
+	err = snd_device_new(card, SNDRV_DEV_CODEC, opl4, &ops);
+	if (err < 0) {
+		snd_opl4_free(opl4);
+		return err;
+	}
+
+	err = snd_opl3_create(card, fm_port, fm_port + 2, opl4->hardware, 1, &opl3);
+	if (err < 0) {
+		snd_device_free(card, opl4);
+		return err;
+	}
+
+	/* opl3 initialization disabled opl4, so reenable */
+	snd_opl4_enable_opl4(opl4);
+
+	snd_opl4_create_mixer(opl4);
+#ifdef CONFIG_PROC_FS
+	snd_opl4_create_proc(opl4);
+#endif
+
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	opl4->seq_client = -1;
+	if (opl4->hardware < OPL3_HW_OPL4_ML)
+		snd_opl4_create_seq_dev(opl4, seq_device);
+#endif
+
+	if (ropl3)
+		*ropl3 = opl3;
+	if (ropl4)
+		*ropl4 = opl4;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_opl4_create);
+
+static int __init alsa_opl4_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_opl4_exit(void)
+{
+}
+
+module_init(alsa_opl4_init)
+module_exit(alsa_opl4_exit)
diff --git a/linux/sound/drivers/opl4/opl4_local.h b/linux/sound/drivers/opl4/opl4_local.h
new file mode 100644
index 0000000..470e5a7
--- /dev/null
+++ b/linux/sound/drivers/opl4/opl4_local.h
@@ -0,0 +1,232 @@
+/*
+ * Local definitions for the OPL4 driver
+ *
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed and/or modified under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef __OPL4_LOCAL_H
+#define __OPL4_LOCAL_H
+
+#include <sound/opl4.h>
+
+/*
+ * Register numbers
+ */
+
+#define OPL4_REG_TEST0			0x00
+#define OPL4_REG_TEST1			0x01
+
+#define OPL4_REG_MEMORY_CONFIGURATION	0x02
+#define   OPL4_MODE_BIT			0x01
+#define   OPL4_MTYPE_BIT		0x02
+#define   OPL4_TONE_HEADER_MASK		0x1c
+#define   OPL4_DEVICE_ID_MASK		0xe0
+
+#define OPL4_REG_MEMORY_ADDRESS_HIGH	0x03
+#define OPL4_REG_MEMORY_ADDRESS_MID	0x04
+#define OPL4_REG_MEMORY_ADDRESS_LOW	0x05
+#define OPL4_REG_MEMORY_DATA		0x06
+
+/*
+ * Offsets to the register banks for voices. To get the
+ * register number just add the voice number to the bank offset.
+ *
+ * Wave Table Number low bits (0x08 to 0x1F)
+ */
+#define OPL4_REG_TONE_NUMBER		0x08
+
+/* Wave Table Number high bit, F-Number low bits (0x20 to 0x37) */
+#define OPL4_REG_F_NUMBER		0x20
+#define   OPL4_TONE_NUMBER_BIT8		0x01
+#define   OPL4_F_NUMBER_LOW_MASK	0xfe
+
+/* F-Number high bits, Octave, Pseudo-Reverb (0x38 to 0x4F) */
+#define OPL4_REG_OCTAVE			0x38
+#define   OPL4_F_NUMBER_HIGH_MASK	0x07
+#define   OPL4_BLOCK_MASK		0xf0
+#define   OPL4_PSEUDO_REVERB_BIT	0x08
+
+/* Total Level, Level Direct (0x50 to 0x67) */
+#define OPL4_REG_LEVEL			0x50
+#define   OPL4_TOTAL_LEVEL_MASK		0xfe
+#define   OPL4_LEVEL_DIRECT_BIT		0x01
+
+/* Key On, Damp, LFO RST, CH, Panpot (0x68 to 0x7F) */
+#define OPL4_REG_MISC			0x68
+#define   OPL4_KEY_ON_BIT		0x80
+#define   OPL4_DAMP_BIT			0x40
+#define   OPL4_LFO_RESET_BIT		0x20
+#define   OPL4_OUTPUT_CHANNEL_BIT	0x10
+#define   OPL4_PAN_POT_MASK		0x0f
+
+/* LFO, VIB (0x80 to 0x97) */
+#define OPL4_REG_LFO_VIBRATO		0x80
+#define   OPL4_LFO_FREQUENCY_MASK	0x38
+#define   OPL4_VIBRATO_DEPTH_MASK	0x07
+#define   OPL4_CHORUS_SEND_MASK		0xc0 /* ML only */
+
+/* Attack / Decay 1 rate (0x98 to 0xAF) */
+#define OPL4_REG_ATTACK_DECAY1		0x98
+#define   OPL4_ATTACK_RATE_MASK		0xf0
+#define   OPL4_DECAY1_RATE_MASK		0x0f
+
+/* Decay level / 2 rate (0xB0 to 0xC7) */
+#define OPL4_REG_LEVEL_DECAY2		0xb0
+#define   OPL4_DECAY_LEVEL_MASK		0xf0
+#define   OPL4_DECAY2_RATE_MASK		0x0f
+
+/* Release rate / Rate correction (0xC8 to 0xDF) */
+#define OPL4_REG_RELEASE_CORRECTION	0xc8
+#define   OPL4_RELEASE_RATE_MASK	0x0f
+#define   OPL4_RATE_INTERPOLATION_MASK	0xf0
+
+/* AM (0xE0 to 0xF7) */
+#define OPL4_REG_TREMOLO		0xe0
+#define   OPL4_TREMOLO_DEPTH_MASK	0x07
+#define   OPL4_REVERB_SEND_MASK		0xe0 /* ML only */
+
+/* Mixer */
+#define OPL4_REG_MIX_CONTROL_FM		0xf8
+#define OPL4_REG_MIX_CONTROL_PCM	0xf9
+#define   OPL4_MIX_LEFT_MASK		0x07
+#define   OPL4_MIX_RIGHT_MASK		0x38
+
+#define OPL4_REG_ATC			0xfa
+#define   OPL4_ATC_BIT			0x01 /* ???, ML only */
+
+/* bits in the OPL3 Status register */
+#define OPL4_STATUS_BUSY		0x01
+#define OPL4_STATUS_LOAD		0x02
+
+
+#define OPL4_MAX_VOICES 24
+
+#define SNDRV_SEQ_DEV_ID_OPL4 "opl4-synth"
+
+
+struct opl4_sound {
+	u16 tone;
+	s16 pitch_offset;
+	u8 key_scaling;
+	s8 panpot;
+	u8 vibrato;
+	u8 tone_attenuate;
+	u8 volume_factor;
+	u8 reg_lfo_vibrato;
+	u8 reg_attack_decay1;
+	u8 reg_level_decay2;
+	u8 reg_release_correction;
+	u8 reg_tremolo;
+};
+
+struct opl4_region {
+	u8 key_min, key_max;
+	struct opl4_sound sound;
+};
+
+struct opl4_region_ptr {
+	int count;
+	const struct opl4_region *regions;
+};
+
+struct opl4_voice {
+	struct list_head list;
+	int number;
+	struct snd_midi_channel *chan;
+	int note;
+	int velocity;
+	const struct opl4_sound *sound;
+	u8 level_direct;
+	u8 reg_f_number;
+	u8 reg_misc;
+	u8 reg_lfo_vibrato;
+};
+
+struct snd_opl4 {
+	unsigned long fm_port;
+	unsigned long pcm_port;
+	struct resource *res_fm_port;
+	struct resource *res_pcm_port;
+	unsigned short hardware;
+	spinlock_t reg_lock;
+	struct snd_card *card;
+
+#ifdef CONFIG_PROC_FS
+	struct snd_info_entry *proc_entry;
+	int memory_access;
+#endif
+	struct mutex access_mutex;
+
+#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
+	int used;
+
+	int seq_dev_num;
+	int seq_client;
+	struct snd_seq_device *seq_dev;
+
+	struct snd_midi_channel_set *chset;
+	struct opl4_voice voices[OPL4_MAX_VOICES];
+	struct list_head off_voices;
+	struct list_head on_voices;
+#endif
+};
+
+/* opl4_lib.c */
+void snd_opl4_write(struct snd_opl4 *opl4, u8 reg, u8 value);
+u8 snd_opl4_read(struct snd_opl4 *opl4, u8 reg);
+void snd_opl4_read_memory(struct snd_opl4 *opl4, char *buf, int offset, int size);
+void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size);
+
+/* opl4_mixer.c */
+int snd_opl4_create_mixer(struct snd_opl4 *opl4);
+
+#ifdef CONFIG_PROC_FS
+/* opl4_proc.c */
+int snd_opl4_create_proc(struct snd_opl4 *opl4);
+void snd_opl4_free_proc(struct snd_opl4 *opl4);
+#endif
+
+/* opl4_seq.c */
+extern int volume_boost;
+
+/* opl4_synth.c */
+void snd_opl4_synth_reset(struct snd_opl4 *opl4);
+void snd_opl4_synth_shutdown(struct snd_opl4 *opl4);
+void snd_opl4_note_on(void *p, int note, int vel, struct snd_midi_channel *chan);
+void snd_opl4_note_off(void *p, int note, int vel, struct snd_midi_channel *chan);
+void snd_opl4_terminate_note(void *p, int note, struct snd_midi_channel *chan);
+void snd_opl4_control(void *p, int type, struct snd_midi_channel *chan);
+void snd_opl4_sysex(void *p, unsigned char *buf, int len, int parsed, struct snd_midi_channel_set *chset);
+
+/* yrw801.c */
+int snd_yrw801_detect(struct snd_opl4 *opl4);
+extern const struct opl4_region_ptr snd_yrw801_regions[];
+
+#endif /* __OPL4_LOCAL_H */
diff --git a/linux/sound/drivers/opl4/opl4_mixer.c b/linux/sound/drivers/opl4/opl4_mixer.c
new file mode 100644
index 0000000..04079de
--- /dev/null
+++ b/linux/sound/drivers/opl4/opl4_mixer.c
@@ -0,0 +1,95 @@
+/*
+ * OPL4 mixer functions
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "opl4_local.h"
+#include <sound/control.h>
+
+static int snd_opl4_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 7;
+	return 0;
+}
+
+static int snd_opl4_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_opl4 *opl4 = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	u8 reg = kcontrol->private_value;
+	u8 value;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	value = snd_opl4_read(opl4, reg);
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+	ucontrol->value.integer.value[0] = 7 - (value & 7);
+	ucontrol->value.integer.value[1] = 7 - ((value >> 3) & 7);
+	return 0;
+}
+
+static int snd_opl4_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_opl4 *opl4 = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	u8 reg = kcontrol->private_value;
+	u8 value, old_value;
+
+	value = (7 - (ucontrol->value.integer.value[0] & 7)) |
+		((7 - (ucontrol->value.integer.value[1] & 7)) << 3);
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	old_value = snd_opl4_read(opl4, reg);
+	snd_opl4_write(opl4, reg, value);
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+	return value != old_value;
+}
+
+static struct snd_kcontrol_new snd_opl4_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "FM Playback Volume",
+		.info = snd_opl4_ctl_info,
+		.get = snd_opl4_ctl_get,
+		.put = snd_opl4_ctl_put,
+		.private_value = OPL4_REG_MIX_CONTROL_FM
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Wavetable Playback Volume",
+		.info = snd_opl4_ctl_info,
+		.get = snd_opl4_ctl_get,
+		.put = snd_opl4_ctl_put,
+		.private_value = OPL4_REG_MIX_CONTROL_PCM
+	}
+};
+
+int snd_opl4_create_mixer(struct snd_opl4 *opl4)
+{
+	struct snd_card *card = opl4->card;
+	int i, err;
+
+	strcat(card->mixername, ",OPL4");
+
+	for (i = 0; i < 2; ++i) {
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_opl4_controls[i], opl4));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
diff --git a/linux/sound/drivers/opl4/opl4_proc.c b/linux/sound/drivers/opl4/opl4_proc.c
new file mode 100644
index 0000000..df850b8
--- /dev/null
+++ b/linux/sound/drivers/opl4/opl4_proc.c
@@ -0,0 +1,132 @@
+/*
+ * Functions for the OPL4 proc file
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "opl4_local.h"
+#include <linux/vmalloc.h>
+#include <sound/info.h>
+
+#ifdef CONFIG_PROC_FS
+
+static int snd_opl4_mem_proc_open(struct snd_info_entry *entry,
+				  unsigned short mode, void **file_private_data)
+{
+	struct snd_opl4 *opl4 = entry->private_data;
+
+	mutex_lock(&opl4->access_mutex);
+	if (opl4->memory_access) {
+		mutex_unlock(&opl4->access_mutex);
+		return -EBUSY;
+	}
+	opl4->memory_access++;
+	mutex_unlock(&opl4->access_mutex);
+	return 0;
+}
+
+static int snd_opl4_mem_proc_release(struct snd_info_entry *entry,
+				     unsigned short mode, void *file_private_data)
+{
+	struct snd_opl4 *opl4 = entry->private_data;
+
+	mutex_lock(&opl4->access_mutex);
+	opl4->memory_access--;
+	mutex_unlock(&opl4->access_mutex);
+	return 0;
+}
+
+static ssize_t snd_opl4_mem_proc_read(struct snd_info_entry *entry,
+				      void *file_private_data,
+				      struct file *file, char __user *_buf,
+				      size_t count, loff_t pos)
+{
+	struct snd_opl4 *opl4 = entry->private_data;
+	char* buf;
+
+	buf = vmalloc(count);
+	if (!buf)
+		return -ENOMEM;
+	snd_opl4_read_memory(opl4, buf, pos, count);
+	if (copy_to_user(_buf, buf, count)) {
+		vfree(buf);
+		return -EFAULT;
+	}
+	vfree(buf);
+	return count;
+}
+
+static ssize_t snd_opl4_mem_proc_write(struct snd_info_entry *entry,
+				       void *file_private_data,
+				       struct file *file,
+				       const char __user *_buf,
+				       size_t count, loff_t pos)
+{
+	struct snd_opl4 *opl4 = entry->private_data;
+	char *buf;
+
+	buf = vmalloc(count);
+	if (!buf)
+		return -ENOMEM;
+	if (copy_from_user(buf, _buf, count)) {
+		vfree(buf);
+		return -EFAULT;
+	}
+	snd_opl4_write_memory(opl4, buf, pos, count);
+	vfree(buf);
+	return count;
+}
+
+static struct snd_info_entry_ops snd_opl4_mem_proc_ops = {
+	.open = snd_opl4_mem_proc_open,
+	.release = snd_opl4_mem_proc_release,
+	.read = snd_opl4_mem_proc_read,
+	.write = snd_opl4_mem_proc_write,
+};
+
+int snd_opl4_create_proc(struct snd_opl4 *opl4)
+{
+	struct snd_info_entry *entry;
+
+	entry = snd_info_create_card_entry(opl4->card, "opl4-mem", opl4->card->proc_root);
+	if (entry) {
+		if (opl4->hardware < OPL3_HW_OPL4_ML) {
+			/* OPL4 can access 4 MB external ROM/SRAM */
+			entry->mode |= S_IWUSR;
+			entry->size = 4 * 1024 * 1024;
+		} else {
+			/* OPL4-ML has 1 MB internal ROM */
+			entry->size = 1 * 1024 * 1024;
+		}
+		entry->content = SNDRV_INFO_CONTENT_DATA;
+		entry->c.ops = &snd_opl4_mem_proc_ops;
+		entry->module = THIS_MODULE;
+		entry->private_data = opl4;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	opl4->proc_entry = entry;
+	return 0;
+}
+
+void snd_opl4_free_proc(struct snd_opl4 *opl4)
+{
+	snd_info_free_entry(opl4->proc_entry);
+}
+
+#endif /* CONFIG_PROC_FS */
diff --git a/linux/sound/drivers/opl4/opl4_seq.c b/linux/sound/drivers/opl4/opl4_seq.c
new file mode 100644
index 0000000..43d8a2b
--- /dev/null
+++ b/linux/sound/drivers/opl4/opl4_seq.c
@@ -0,0 +1,214 @@
+/*
+ * OPL4 sequencer functions
+ *
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed and/or modified under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opl4_local.h"
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("OPL4 wavetable synth driver");
+MODULE_LICENSE("Dual BSD/GPL");
+
+int volume_boost = 8;
+
+module_param(volume_boost, int, 0644);
+MODULE_PARM_DESC(volume_boost, "Additional volume for OPL4 wavetable sounds.");
+
+static int snd_opl4_seq_use_inc(struct snd_opl4 *opl4)
+{
+	if (!try_module_get(opl4->card->module))
+		return -EFAULT;
+	return 0;
+}
+
+static void snd_opl4_seq_use_dec(struct snd_opl4 *opl4)
+{
+	module_put(opl4->card->module);
+}
+
+static int snd_opl4_seq_use(void *private_data, struct snd_seq_port_subscribe *info)
+{
+	struct snd_opl4 *opl4 = private_data;
+	int err;
+
+	mutex_lock(&opl4->access_mutex);
+
+	if (opl4->used) {
+		mutex_unlock(&opl4->access_mutex);
+		return -EBUSY;
+	}
+	opl4->used++;
+
+	if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) {
+		err = snd_opl4_seq_use_inc(opl4);
+		if (err < 0) {
+			mutex_unlock(&opl4->access_mutex);
+			return err;
+		}
+	}
+
+	mutex_unlock(&opl4->access_mutex);
+
+	snd_opl4_synth_reset(opl4);
+	return 0;
+}
+
+static int snd_opl4_seq_unuse(void *private_data, struct snd_seq_port_subscribe *info)
+{
+	struct snd_opl4 *opl4 = private_data;
+
+	snd_opl4_synth_shutdown(opl4);
+
+	mutex_lock(&opl4->access_mutex);
+	opl4->used--;
+	mutex_unlock(&opl4->access_mutex);
+
+	if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM)
+		snd_opl4_seq_use_dec(opl4);
+	return 0;
+}
+
+static struct snd_midi_op opl4_ops = {
+	.note_on =		snd_opl4_note_on,
+	.note_off =		snd_opl4_note_off,
+	.note_terminate =	snd_opl4_terminate_note,
+	.control =		snd_opl4_control,
+	.sysex =		snd_opl4_sysex,
+};
+
+static int snd_opl4_seq_event_input(struct snd_seq_event *ev, int direct,
+				    void *private_data, int atomic, int hop)
+{
+	struct snd_opl4 *opl4 = private_data;
+
+	snd_midi_process_event(&opl4_ops, ev, opl4->chset);
+	return 0;
+}
+
+static void snd_opl4_seq_free_port(void *private_data)
+{
+	struct snd_opl4 *opl4 = private_data;
+
+	snd_midi_channel_free_set(opl4->chset);
+}
+
+static int snd_opl4_seq_new_device(struct snd_seq_device *dev)
+{
+	struct snd_opl4 *opl4;
+	int client;
+	struct snd_seq_port_callback pcallbacks;
+
+	opl4 = *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (!opl4)
+		return -EINVAL;
+
+	if (snd_yrw801_detect(opl4) < 0)
+		return -ENODEV;
+
+	opl4->chset = snd_midi_channel_alloc_set(16);
+	if (!opl4->chset)
+		return -ENOMEM;
+	opl4->chset->private_data = opl4;
+
+	/* allocate new client */
+	client = snd_seq_create_kernel_client(opl4->card, opl4->seq_dev_num,
+					      "OPL4 Wavetable");
+	if (client < 0) {
+		snd_midi_channel_free_set(opl4->chset);
+		return client;
+	}
+	opl4->seq_client = client;
+	opl4->chset->client = client;
+
+	/* create new port */
+	memset(&pcallbacks, 0, sizeof(pcallbacks));
+	pcallbacks.owner = THIS_MODULE;
+	pcallbacks.use = snd_opl4_seq_use;
+	pcallbacks.unuse = snd_opl4_seq_unuse;
+	pcallbacks.event_input = snd_opl4_seq_event_input;
+	pcallbacks.private_free = snd_opl4_seq_free_port;
+	pcallbacks.private_data = opl4;
+
+	opl4->chset->port = snd_seq_event_port_attach(client, &pcallbacks,
+						      SNDRV_SEQ_PORT_CAP_WRITE |
+						      SNDRV_SEQ_PORT_CAP_SUBS_WRITE,
+						      SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+						      SNDRV_SEQ_PORT_TYPE_MIDI_GM |
+						      SNDRV_SEQ_PORT_TYPE_HARDWARE |
+						      SNDRV_SEQ_PORT_TYPE_SYNTHESIZER,
+						      16, 24,
+						      "OPL4 Wavetable Port");
+	if (opl4->chset->port < 0) {
+		int err = opl4->chset->port;
+		snd_midi_channel_free_set(opl4->chset);
+		snd_seq_delete_kernel_client(client);
+		opl4->seq_client = -1;
+		return err;
+	}
+	return 0;
+}
+
+static int snd_opl4_seq_delete_device(struct snd_seq_device *dev)
+{
+	struct snd_opl4 *opl4;
+
+	opl4 = *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (!opl4)
+		return -EINVAL;
+
+	if (opl4->seq_client >= 0) {
+		snd_seq_delete_kernel_client(opl4->seq_client);
+		opl4->seq_client = -1;
+	}
+	return 0;
+}
+
+static int __init alsa_opl4_synth_init(void)
+{
+	static struct snd_seq_dev_ops ops = {
+		snd_opl4_seq_new_device,
+		snd_opl4_seq_delete_device
+	};
+
+	return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OPL4, &ops,
+					      sizeof(struct snd_opl4 *));
+}
+
+static void __exit alsa_opl4_synth_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OPL4);
+}
+
+module_init(alsa_opl4_synth_init)
+module_exit(alsa_opl4_synth_exit)
diff --git a/linux/sound/drivers/opl4/opl4_synth.c b/linux/sound/drivers/opl4/opl4_synth.c
new file mode 100644
index 0000000..49b9e24
--- /dev/null
+++ b/linux/sound/drivers/opl4/opl4_synth.c
@@ -0,0 +1,634 @@
+/*
+ * OPL4 MIDI synthesizer functions
+ *
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed and/or modified under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opl4_local.h"
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <sound/asoundef.h>
+
+/* GM2 controllers */
+#ifndef MIDI_CTL_RELEASE_TIME
+#define MIDI_CTL_RELEASE_TIME	0x48
+#define MIDI_CTL_ATTACK_TIME	0x49
+#define MIDI_CTL_DECAY_TIME	0x4b
+#define MIDI_CTL_VIBRATO_RATE	0x4c
+#define MIDI_CTL_VIBRATO_DEPTH	0x4d
+#define MIDI_CTL_VIBRATO_DELAY	0x4e
+#endif
+
+/*
+ * This table maps 100/128 cents to F_NUMBER.
+ */
+static const s16 snd_opl4_pitch_map[0x600] = {
+	0x000,0x000,0x001,0x001,0x002,0x002,0x003,0x003,
+	0x004,0x004,0x005,0x005,0x006,0x006,0x006,0x007,
+	0x007,0x008,0x008,0x009,0x009,0x00a,0x00a,0x00b,
+	0x00b,0x00c,0x00c,0x00d,0x00d,0x00d,0x00e,0x00e,
+	0x00f,0x00f,0x010,0x010,0x011,0x011,0x012,0x012,
+	0x013,0x013,0x014,0x014,0x015,0x015,0x015,0x016,
+	0x016,0x017,0x017,0x018,0x018,0x019,0x019,0x01a,
+	0x01a,0x01b,0x01b,0x01c,0x01c,0x01d,0x01d,0x01e,
+	0x01e,0x01e,0x01f,0x01f,0x020,0x020,0x021,0x021,
+	0x022,0x022,0x023,0x023,0x024,0x024,0x025,0x025,
+	0x026,0x026,0x027,0x027,0x028,0x028,0x029,0x029,
+	0x029,0x02a,0x02a,0x02b,0x02b,0x02c,0x02c,0x02d,
+	0x02d,0x02e,0x02e,0x02f,0x02f,0x030,0x030,0x031,
+	0x031,0x032,0x032,0x033,0x033,0x034,0x034,0x035,
+	0x035,0x036,0x036,0x037,0x037,0x038,0x038,0x038,
+	0x039,0x039,0x03a,0x03a,0x03b,0x03b,0x03c,0x03c,
+	0x03d,0x03d,0x03e,0x03e,0x03f,0x03f,0x040,0x040,
+	0x041,0x041,0x042,0x042,0x043,0x043,0x044,0x044,
+	0x045,0x045,0x046,0x046,0x047,0x047,0x048,0x048,
+	0x049,0x049,0x04a,0x04a,0x04b,0x04b,0x04c,0x04c,
+	0x04d,0x04d,0x04e,0x04e,0x04f,0x04f,0x050,0x050,
+	0x051,0x051,0x052,0x052,0x053,0x053,0x054,0x054,
+	0x055,0x055,0x056,0x056,0x057,0x057,0x058,0x058,
+	0x059,0x059,0x05a,0x05a,0x05b,0x05b,0x05c,0x05c,
+	0x05d,0x05d,0x05e,0x05e,0x05f,0x05f,0x060,0x060,
+	0x061,0x061,0x062,0x062,0x063,0x063,0x064,0x064,
+	0x065,0x065,0x066,0x066,0x067,0x067,0x068,0x068,
+	0x069,0x069,0x06a,0x06a,0x06b,0x06b,0x06c,0x06c,
+	0x06d,0x06d,0x06e,0x06e,0x06f,0x06f,0x070,0x071,
+	0x071,0x072,0x072,0x073,0x073,0x074,0x074,0x075,
+	0x075,0x076,0x076,0x077,0x077,0x078,0x078,0x079,
+	0x079,0x07a,0x07a,0x07b,0x07b,0x07c,0x07c,0x07d,
+	0x07d,0x07e,0x07e,0x07f,0x07f,0x080,0x081,0x081,
+	0x082,0x082,0x083,0x083,0x084,0x084,0x085,0x085,
+	0x086,0x086,0x087,0x087,0x088,0x088,0x089,0x089,
+	0x08a,0x08a,0x08b,0x08b,0x08c,0x08d,0x08d,0x08e,
+	0x08e,0x08f,0x08f,0x090,0x090,0x091,0x091,0x092,
+	0x092,0x093,0x093,0x094,0x094,0x095,0x096,0x096,
+	0x097,0x097,0x098,0x098,0x099,0x099,0x09a,0x09a,
+	0x09b,0x09b,0x09c,0x09c,0x09d,0x09d,0x09e,0x09f,
+	0x09f,0x0a0,0x0a0,0x0a1,0x0a1,0x0a2,0x0a2,0x0a3,
+	0x0a3,0x0a4,0x0a4,0x0a5,0x0a6,0x0a6,0x0a7,0x0a7,
+	0x0a8,0x0a8,0x0a9,0x0a9,0x0aa,0x0aa,0x0ab,0x0ab,
+	0x0ac,0x0ad,0x0ad,0x0ae,0x0ae,0x0af,0x0af,0x0b0,
+	0x0b0,0x0b1,0x0b1,0x0b2,0x0b2,0x0b3,0x0b4,0x0b4,
+	0x0b5,0x0b5,0x0b6,0x0b6,0x0b7,0x0b7,0x0b8,0x0b8,
+	0x0b9,0x0ba,0x0ba,0x0bb,0x0bb,0x0bc,0x0bc,0x0bd,
+	0x0bd,0x0be,0x0be,0x0bf,0x0c0,0x0c0,0x0c1,0x0c1,
+	0x0c2,0x0c2,0x0c3,0x0c3,0x0c4,0x0c4,0x0c5,0x0c6,
+	0x0c6,0x0c7,0x0c7,0x0c8,0x0c8,0x0c9,0x0c9,0x0ca,
+	0x0cb,0x0cb,0x0cc,0x0cc,0x0cd,0x0cd,0x0ce,0x0ce,
+	0x0cf,0x0d0,0x0d0,0x0d1,0x0d1,0x0d2,0x0d2,0x0d3,
+	0x0d3,0x0d4,0x0d5,0x0d5,0x0d6,0x0d6,0x0d7,0x0d7,
+	0x0d8,0x0d8,0x0d9,0x0da,0x0da,0x0db,0x0db,0x0dc,
+	0x0dc,0x0dd,0x0de,0x0de,0x0df,0x0df,0x0e0,0x0e0,
+	0x0e1,0x0e1,0x0e2,0x0e3,0x0e3,0x0e4,0x0e4,0x0e5,
+	0x0e5,0x0e6,0x0e7,0x0e7,0x0e8,0x0e8,0x0e9,0x0e9,
+	0x0ea,0x0eb,0x0eb,0x0ec,0x0ec,0x0ed,0x0ed,0x0ee,
+	0x0ef,0x0ef,0x0f0,0x0f0,0x0f1,0x0f1,0x0f2,0x0f3,
+	0x0f3,0x0f4,0x0f4,0x0f5,0x0f5,0x0f6,0x0f7,0x0f7,
+	0x0f8,0x0f8,0x0f9,0x0f9,0x0fa,0x0fb,0x0fb,0x0fc,
+	0x0fc,0x0fd,0x0fd,0x0fe,0x0ff,0x0ff,0x100,0x100,
+	0x101,0x101,0x102,0x103,0x103,0x104,0x104,0x105,
+	0x106,0x106,0x107,0x107,0x108,0x108,0x109,0x10a,
+	0x10a,0x10b,0x10b,0x10c,0x10c,0x10d,0x10e,0x10e,
+	0x10f,0x10f,0x110,0x111,0x111,0x112,0x112,0x113,
+	0x114,0x114,0x115,0x115,0x116,0x116,0x117,0x118,
+	0x118,0x119,0x119,0x11a,0x11b,0x11b,0x11c,0x11c,
+	0x11d,0x11e,0x11e,0x11f,0x11f,0x120,0x120,0x121,
+	0x122,0x122,0x123,0x123,0x124,0x125,0x125,0x126,
+	0x126,0x127,0x128,0x128,0x129,0x129,0x12a,0x12b,
+	0x12b,0x12c,0x12c,0x12d,0x12e,0x12e,0x12f,0x12f,
+	0x130,0x131,0x131,0x132,0x132,0x133,0x134,0x134,
+	0x135,0x135,0x136,0x137,0x137,0x138,0x138,0x139,
+	0x13a,0x13a,0x13b,0x13b,0x13c,0x13d,0x13d,0x13e,
+	0x13e,0x13f,0x140,0x140,0x141,0x141,0x142,0x143,
+	0x143,0x144,0x144,0x145,0x146,0x146,0x147,0x148,
+	0x148,0x149,0x149,0x14a,0x14b,0x14b,0x14c,0x14c,
+	0x14d,0x14e,0x14e,0x14f,0x14f,0x150,0x151,0x151,
+	0x152,0x153,0x153,0x154,0x154,0x155,0x156,0x156,
+	0x157,0x157,0x158,0x159,0x159,0x15a,0x15b,0x15b,
+	0x15c,0x15c,0x15d,0x15e,0x15e,0x15f,0x160,0x160,
+	0x161,0x161,0x162,0x163,0x163,0x164,0x165,0x165,
+	0x166,0x166,0x167,0x168,0x168,0x169,0x16a,0x16a,
+	0x16b,0x16b,0x16c,0x16d,0x16d,0x16e,0x16f,0x16f,
+	0x170,0x170,0x171,0x172,0x172,0x173,0x174,0x174,
+	0x175,0x175,0x176,0x177,0x177,0x178,0x179,0x179,
+	0x17a,0x17a,0x17b,0x17c,0x17c,0x17d,0x17e,0x17e,
+	0x17f,0x180,0x180,0x181,0x181,0x182,0x183,0x183,
+	0x184,0x185,0x185,0x186,0x187,0x187,0x188,0x188,
+	0x189,0x18a,0x18a,0x18b,0x18c,0x18c,0x18d,0x18e,
+	0x18e,0x18f,0x190,0x190,0x191,0x191,0x192,0x193,
+	0x193,0x194,0x195,0x195,0x196,0x197,0x197,0x198,
+	0x199,0x199,0x19a,0x19a,0x19b,0x19c,0x19c,0x19d,
+	0x19e,0x19e,0x19f,0x1a0,0x1a0,0x1a1,0x1a2,0x1a2,
+	0x1a3,0x1a4,0x1a4,0x1a5,0x1a6,0x1a6,0x1a7,0x1a8,
+	0x1a8,0x1a9,0x1a9,0x1aa,0x1ab,0x1ab,0x1ac,0x1ad,
+	0x1ad,0x1ae,0x1af,0x1af,0x1b0,0x1b1,0x1b1,0x1b2,
+	0x1b3,0x1b3,0x1b4,0x1b5,0x1b5,0x1b6,0x1b7,0x1b7,
+	0x1b8,0x1b9,0x1b9,0x1ba,0x1bb,0x1bb,0x1bc,0x1bd,
+	0x1bd,0x1be,0x1bf,0x1bf,0x1c0,0x1c1,0x1c1,0x1c2,
+	0x1c3,0x1c3,0x1c4,0x1c5,0x1c5,0x1c6,0x1c7,0x1c7,
+	0x1c8,0x1c9,0x1c9,0x1ca,0x1cb,0x1cb,0x1cc,0x1cd,
+	0x1cd,0x1ce,0x1cf,0x1cf,0x1d0,0x1d1,0x1d1,0x1d2,
+	0x1d3,0x1d3,0x1d4,0x1d5,0x1d5,0x1d6,0x1d7,0x1d7,
+	0x1d8,0x1d9,0x1d9,0x1da,0x1db,0x1db,0x1dc,0x1dd,
+	0x1dd,0x1de,0x1df,0x1df,0x1e0,0x1e1,0x1e1,0x1e2,
+	0x1e3,0x1e4,0x1e4,0x1e5,0x1e6,0x1e6,0x1e7,0x1e8,
+	0x1e8,0x1e9,0x1ea,0x1ea,0x1eb,0x1ec,0x1ec,0x1ed,
+	0x1ee,0x1ee,0x1ef,0x1f0,0x1f0,0x1f1,0x1f2,0x1f3,
+	0x1f3,0x1f4,0x1f5,0x1f5,0x1f6,0x1f7,0x1f7,0x1f8,
+	0x1f9,0x1f9,0x1fa,0x1fb,0x1fb,0x1fc,0x1fd,0x1fe,
+	0x1fe,0x1ff,0x200,0x200,0x201,0x202,0x202,0x203,
+	0x204,0x205,0x205,0x206,0x207,0x207,0x208,0x209,
+	0x209,0x20a,0x20b,0x20b,0x20c,0x20d,0x20e,0x20e,
+	0x20f,0x210,0x210,0x211,0x212,0x212,0x213,0x214,
+	0x215,0x215,0x216,0x217,0x217,0x218,0x219,0x21a,
+	0x21a,0x21b,0x21c,0x21c,0x21d,0x21e,0x21e,0x21f,
+	0x220,0x221,0x221,0x222,0x223,0x223,0x224,0x225,
+	0x226,0x226,0x227,0x228,0x228,0x229,0x22a,0x22b,
+	0x22b,0x22c,0x22d,0x22d,0x22e,0x22f,0x230,0x230,
+	0x231,0x232,0x232,0x233,0x234,0x235,0x235,0x236,
+	0x237,0x237,0x238,0x239,0x23a,0x23a,0x23b,0x23c,
+	0x23c,0x23d,0x23e,0x23f,0x23f,0x240,0x241,0x241,
+	0x242,0x243,0x244,0x244,0x245,0x246,0x247,0x247,
+	0x248,0x249,0x249,0x24a,0x24b,0x24c,0x24c,0x24d,
+	0x24e,0x24f,0x24f,0x250,0x251,0x251,0x252,0x253,
+	0x254,0x254,0x255,0x256,0x257,0x257,0x258,0x259,
+	0x259,0x25a,0x25b,0x25c,0x25c,0x25d,0x25e,0x25f,
+	0x25f,0x260,0x261,0x262,0x262,0x263,0x264,0x265,
+	0x265,0x266,0x267,0x267,0x268,0x269,0x26a,0x26a,
+	0x26b,0x26c,0x26d,0x26d,0x26e,0x26f,0x270,0x270,
+	0x271,0x272,0x273,0x273,0x274,0x275,0x276,0x276,
+	0x277,0x278,0x279,0x279,0x27a,0x27b,0x27c,0x27c,
+	0x27d,0x27e,0x27f,0x27f,0x280,0x281,0x282,0x282,
+	0x283,0x284,0x285,0x285,0x286,0x287,0x288,0x288,
+	0x289,0x28a,0x28b,0x28b,0x28c,0x28d,0x28e,0x28e,
+	0x28f,0x290,0x291,0x291,0x292,0x293,0x294,0x294,
+	0x295,0x296,0x297,0x298,0x298,0x299,0x29a,0x29b,
+	0x29b,0x29c,0x29d,0x29e,0x29e,0x29f,0x2a0,0x2a1,
+	0x2a1,0x2a2,0x2a3,0x2a4,0x2a5,0x2a5,0x2a6,0x2a7,
+	0x2a8,0x2a8,0x2a9,0x2aa,0x2ab,0x2ab,0x2ac,0x2ad,
+	0x2ae,0x2af,0x2af,0x2b0,0x2b1,0x2b2,0x2b2,0x2b3,
+	0x2b4,0x2b5,0x2b5,0x2b6,0x2b7,0x2b8,0x2b9,0x2b9,
+	0x2ba,0x2bb,0x2bc,0x2bc,0x2bd,0x2be,0x2bf,0x2c0,
+	0x2c0,0x2c1,0x2c2,0x2c3,0x2c4,0x2c4,0x2c5,0x2c6,
+	0x2c7,0x2c7,0x2c8,0x2c9,0x2ca,0x2cb,0x2cb,0x2cc,
+	0x2cd,0x2ce,0x2ce,0x2cf,0x2d0,0x2d1,0x2d2,0x2d2,
+	0x2d3,0x2d4,0x2d5,0x2d6,0x2d6,0x2d7,0x2d8,0x2d9,
+	0x2da,0x2da,0x2db,0x2dc,0x2dd,0x2dd,0x2de,0x2df,
+	0x2e0,0x2e1,0x2e1,0x2e2,0x2e3,0x2e4,0x2e5,0x2e5,
+	0x2e6,0x2e7,0x2e8,0x2e9,0x2e9,0x2ea,0x2eb,0x2ec,
+	0x2ed,0x2ed,0x2ee,0x2ef,0x2f0,0x2f1,0x2f1,0x2f2,
+	0x2f3,0x2f4,0x2f5,0x2f5,0x2f6,0x2f7,0x2f8,0x2f9,
+	0x2f9,0x2fa,0x2fb,0x2fc,0x2fd,0x2fd,0x2fe,0x2ff,
+	0x300,0x301,0x302,0x302,0x303,0x304,0x305,0x306,
+	0x306,0x307,0x308,0x309,0x30a,0x30a,0x30b,0x30c,
+	0x30d,0x30e,0x30f,0x30f,0x310,0x311,0x312,0x313,
+	0x313,0x314,0x315,0x316,0x317,0x318,0x318,0x319,
+	0x31a,0x31b,0x31c,0x31c,0x31d,0x31e,0x31f,0x320,
+	0x321,0x321,0x322,0x323,0x324,0x325,0x326,0x326,
+	0x327,0x328,0x329,0x32a,0x32a,0x32b,0x32c,0x32d,
+	0x32e,0x32f,0x32f,0x330,0x331,0x332,0x333,0x334,
+	0x334,0x335,0x336,0x337,0x338,0x339,0x339,0x33a,
+	0x33b,0x33c,0x33d,0x33e,0x33e,0x33f,0x340,0x341,
+	0x342,0x343,0x343,0x344,0x345,0x346,0x347,0x348,
+	0x349,0x349,0x34a,0x34b,0x34c,0x34d,0x34e,0x34e,
+	0x34f,0x350,0x351,0x352,0x353,0x353,0x354,0x355,
+	0x356,0x357,0x358,0x359,0x359,0x35a,0x35b,0x35c,
+	0x35d,0x35e,0x35f,0x35f,0x360,0x361,0x362,0x363,
+	0x364,0x364,0x365,0x366,0x367,0x368,0x369,0x36a,
+	0x36a,0x36b,0x36c,0x36d,0x36e,0x36f,0x370,0x370,
+	0x371,0x372,0x373,0x374,0x375,0x376,0x377,0x377,
+	0x378,0x379,0x37a,0x37b,0x37c,0x37d,0x37d,0x37e,
+	0x37f,0x380,0x381,0x382,0x383,0x383,0x384,0x385,
+	0x386,0x387,0x388,0x389,0x38a,0x38a,0x38b,0x38c,
+	0x38d,0x38e,0x38f,0x390,0x391,0x391,0x392,0x393,
+	0x394,0x395,0x396,0x397,0x398,0x398,0x399,0x39a,
+	0x39b,0x39c,0x39d,0x39e,0x39f,0x39f,0x3a0,0x3a1,
+	0x3a2,0x3a3,0x3a4,0x3a5,0x3a6,0x3a7,0x3a7,0x3a8,
+	0x3a9,0x3aa,0x3ab,0x3ac,0x3ad,0x3ae,0x3ae,0x3af,
+	0x3b0,0x3b1,0x3b2,0x3b3,0x3b4,0x3b5,0x3b6,0x3b6,
+	0x3b7,0x3b8,0x3b9,0x3ba,0x3bb,0x3bc,0x3bd,0x3be,
+	0x3bf,0x3bf,0x3c0,0x3c1,0x3c2,0x3c3,0x3c4,0x3c5,
+	0x3c6,0x3c7,0x3c7,0x3c8,0x3c9,0x3ca,0x3cb,0x3cc,
+	0x3cd,0x3ce,0x3cf,0x3d0,0x3d1,0x3d1,0x3d2,0x3d3,
+	0x3d4,0x3d5,0x3d6,0x3d7,0x3d8,0x3d9,0x3da,0x3da,
+	0x3db,0x3dc,0x3dd,0x3de,0x3df,0x3e0,0x3e1,0x3e2,
+	0x3e3,0x3e4,0x3e4,0x3e5,0x3e6,0x3e7,0x3e8,0x3e9,
+	0x3ea,0x3eb,0x3ec,0x3ed,0x3ee,0x3ef,0x3ef,0x3f0,
+	0x3f1,0x3f2,0x3f3,0x3f4,0x3f5,0x3f6,0x3f7,0x3f8,
+	0x3f9,0x3fa,0x3fa,0x3fb,0x3fc,0x3fd,0x3fe,0x3ff
+};
+
+/*
+ * Attenuation according to GM recommendations, in -0.375 dB units.
+ * table[v] = 40 * log(v / 127) / -0.375
+ */
+static unsigned char snd_opl4_volume_table[128] = {
+	255,224,192,173,160,150,141,134,
+	128,122,117,113,109,105,102, 99,
+	 96, 93, 90, 88, 85, 83, 81, 79,
+	 77, 75, 73, 71, 70, 68, 67, 65,
+	 64, 62, 61, 59, 58, 57, 56, 54,
+	 53, 52, 51, 50, 49, 48, 47, 46,
+	 45, 44, 43, 42, 41, 40, 39, 39,
+	 38, 37, 36, 35, 34, 34, 33, 32,
+	 31, 31, 30, 29, 29, 28, 27, 27,
+	 26, 25, 25, 24, 24, 23, 22, 22,
+	 21, 21, 20, 19, 19, 18, 18, 17,
+	 17, 16, 16, 15, 15, 14, 14, 13,
+	 13, 12, 12, 11, 11, 10, 10,  9,
+	  9,  9,  8,  8,  7,  7,  6,  6,
+	  6,  5,  5,  4,  4,  4,  3,  3,
+	  2,  2,  2,  1,  1,  0,  0,  0
+};
+
+/*
+ * Initializes all voices.
+ */
+void snd_opl4_synth_reset(struct snd_opl4 *opl4)
+{
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < OPL4_MAX_VOICES; i++)
+		snd_opl4_write(opl4, OPL4_REG_MISC + i, OPL4_DAMP_BIT);
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+
+	INIT_LIST_HEAD(&opl4->off_voices);
+	INIT_LIST_HEAD(&opl4->on_voices);
+	memset(opl4->voices, 0, sizeof(opl4->voices));
+	for (i = 0; i < OPL4_MAX_VOICES; i++) {
+		opl4->voices[i].number = i;
+		list_add_tail(&opl4->voices[i].list, &opl4->off_voices);
+	}
+
+	snd_midi_channel_set_clear(opl4->chset);
+}
+
+/*
+ * Shuts down all voices.
+ */
+void snd_opl4_synth_shutdown(struct snd_opl4 *opl4)
+{
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < OPL4_MAX_VOICES; i++)
+		snd_opl4_write(opl4, OPL4_REG_MISC + i,
+			       opl4->voices[i].reg_misc & ~OPL4_KEY_ON_BIT);
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+/*
+ * Executes the callback for all voices playing the specified note.
+ */
+static void snd_opl4_do_for_note(struct snd_opl4 *opl4, int note, struct snd_midi_channel *chan,
+				 void (*func)(struct snd_opl4 *opl4, struct opl4_voice *voice))
+{
+	int i;
+	unsigned long flags;
+	struct opl4_voice *voice;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < OPL4_MAX_VOICES; i++) {
+		voice = &opl4->voices[i];
+		if (voice->chan == chan && voice->note == note) {
+			func(opl4, voice);
+		}
+	}
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+/*
+ * Executes the callback for all voices of to the specified channel.
+ */
+static void snd_opl4_do_for_channel(struct snd_opl4 *opl4,
+				    struct snd_midi_channel *chan,
+				    void (*func)(struct snd_opl4 *opl4, struct opl4_voice *voice))
+{
+	int i;
+	unsigned long flags;
+	struct opl4_voice *voice;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < OPL4_MAX_VOICES; i++) {
+		voice = &opl4->voices[i];
+		if (voice->chan == chan) {
+			func(opl4, voice);
+		}
+	}
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+/*
+ * Executes the callback for all active voices.
+ */
+static void snd_opl4_do_for_all(struct snd_opl4 *opl4,
+				void (*func)(struct snd_opl4 *opl4, struct opl4_voice *voice))
+{
+	int i;
+	unsigned long flags;
+	struct opl4_voice *voice;
+
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < OPL4_MAX_VOICES; i++) {
+		voice = &opl4->voices[i];
+		if (voice->chan)
+			func(opl4, voice);
+	}
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+static void snd_opl4_update_volume(struct snd_opl4 *opl4, struct opl4_voice *voice)
+{
+	int att;
+
+	att = voice->sound->tone_attenuate;
+	att += snd_opl4_volume_table[opl4->chset->gs_master_volume & 0x7f];
+	att += snd_opl4_volume_table[voice->chan->gm_volume & 0x7f];
+	att += snd_opl4_volume_table[voice->chan->gm_expression & 0x7f];
+	att += snd_opl4_volume_table[voice->velocity];
+	att = 0x7f - (0x7f - att) * (voice->sound->volume_factor) / 0xfe - volume_boost;
+	if (att < 0)
+		att = 0;
+	else if (att > 0x7e)
+		att = 0x7e;
+	snd_opl4_write(opl4, OPL4_REG_LEVEL + voice->number,
+		       (att << 1) | voice->level_direct);
+	voice->level_direct = 0;
+}
+
+static void snd_opl4_update_pan(struct snd_opl4 *opl4, struct opl4_voice *voice)
+{
+	int pan = voice->sound->panpot;
+
+	if (!voice->chan->drum_channel)
+		pan += (voice->chan->control[MIDI_CTL_MSB_PAN] - 0x40) >> 3;
+	if (pan < -7)
+		pan = -7;
+	else if (pan > 7)
+		pan = 7;
+	voice->reg_misc = (voice->reg_misc & ~OPL4_PAN_POT_MASK)
+		| (pan & OPL4_PAN_POT_MASK);
+	snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc);
+}
+
+static void snd_opl4_update_vibrato_depth(struct snd_opl4 *opl4,
+					  struct opl4_voice *voice)
+{
+	int depth;
+
+	if (voice->chan->drum_channel)
+		return;
+	depth = (7 - voice->sound->vibrato)
+		* (voice->chan->control[MIDI_CTL_VIBRATO_DEPTH] & 0x7f);
+	depth = (depth >> 7) + voice->sound->vibrato;
+	voice->reg_lfo_vibrato &= ~OPL4_VIBRATO_DEPTH_MASK;
+	voice->reg_lfo_vibrato |= depth & OPL4_VIBRATO_DEPTH_MASK;
+	snd_opl4_write(opl4, OPL4_REG_LFO_VIBRATO + voice->number,
+		       voice->reg_lfo_vibrato);
+}
+
+static void snd_opl4_update_pitch(struct snd_opl4 *opl4,
+				  struct opl4_voice *voice)
+{
+	struct snd_midi_channel *chan = voice->chan;
+	int note, pitch, octave;
+
+	note = chan->drum_channel ? 60 : voice->note;
+	/*
+	 * pitch is in 100/128 cents, so 0x80 is one semitone and
+	 * 0x600 is one octave.
+	 */
+	pitch = ((note - 60) << 7) * voice->sound->key_scaling / 100 + (60 << 7);
+	pitch += voice->sound->pitch_offset;
+	if (!chan->drum_channel)
+		pitch += chan->gm_rpn_coarse_tuning;
+	pitch += chan->gm_rpn_fine_tuning >> 7;
+	pitch += chan->midi_pitchbend * chan->gm_rpn_pitch_bend_range / 0x2000;
+	if (pitch < 0)
+		pitch = 0;
+	else if (pitch >= 0x6000)
+		pitch = 0x5fff;
+	octave = pitch / 0x600 - 8;
+	pitch = snd_opl4_pitch_map[pitch % 0x600];
+
+	snd_opl4_write(opl4, OPL4_REG_OCTAVE + voice->number,
+		       (octave << 4) | ((pitch >> 7) & OPL4_F_NUMBER_HIGH_MASK));
+	voice->reg_f_number = (voice->reg_f_number & OPL4_TONE_NUMBER_BIT8)
+		| ((pitch << 1) & OPL4_F_NUMBER_LOW_MASK);
+	snd_opl4_write(opl4, OPL4_REG_F_NUMBER + voice->number, voice->reg_f_number);
+}
+
+static void snd_opl4_update_tone_parameters(struct snd_opl4 *opl4,
+					    struct opl4_voice *voice)
+{
+	snd_opl4_write(opl4, OPL4_REG_ATTACK_DECAY1 + voice->number,
+		       voice->sound->reg_attack_decay1);
+	snd_opl4_write(opl4, OPL4_REG_LEVEL_DECAY2 + voice->number,
+		       voice->sound->reg_level_decay2);
+	snd_opl4_write(opl4, OPL4_REG_RELEASE_CORRECTION + voice->number,
+		       voice->sound->reg_release_correction);
+	snd_opl4_write(opl4, OPL4_REG_TREMOLO + voice->number,
+		       voice->sound->reg_tremolo);
+}
+
+/* allocate one voice */
+static struct opl4_voice *snd_opl4_get_voice(struct snd_opl4 *opl4)
+{
+	/* first, try to get the oldest key-off voice */
+	if (!list_empty(&opl4->off_voices))
+		return list_entry(opl4->off_voices.next, struct opl4_voice, list);
+	/* then get the oldest key-on voice */
+	snd_BUG_ON(list_empty(&opl4->on_voices));
+	return list_entry(opl4->on_voices.next, struct opl4_voice, list);
+}
+
+static void snd_opl4_wait_for_wave_headers(struct snd_opl4 *opl4)
+{
+	int timeout = 200;
+
+	while ((inb(opl4->fm_port) & OPL4_STATUS_LOAD) && --timeout > 0)
+		udelay(10);
+}
+
+void snd_opl4_note_on(void *private_data, int note, int vel, struct snd_midi_channel *chan)
+{
+	struct snd_opl4 *opl4 = private_data;
+	const struct opl4_region_ptr *regions;
+	struct opl4_voice *voice[2];
+	const struct opl4_sound *sound[2];
+	int voices = 0, i;
+	unsigned long flags;
+
+	/* determine the number of voices and voice parameters */
+	i = chan->drum_channel ? 0x80 : (chan->midi_program & 0x7f);
+	regions = &snd_yrw801_regions[i];
+	for (i = 0; i < regions->count; i++) {
+		if (note >= regions->regions[i].key_min &&
+		    note <= regions->regions[i].key_max) {
+			sound[voices] = &regions->regions[i].sound;
+			if (++voices >= 2)
+				break;
+		}
+	}
+
+	/* allocate and initialize the needed voices */
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < voices; i++) {
+		voice[i] = snd_opl4_get_voice(opl4);
+		list_del(&voice[i]->list);
+		list_add_tail(&voice[i]->list, &opl4->on_voices);
+		voice[i]->chan = chan;
+		voice[i]->note = note;
+		voice[i]->velocity = vel & 0x7f;
+		voice[i]->sound = sound[i];
+	}
+
+	/* set tone number (triggers header loading) */
+	for (i = 0; i < voices; i++) {
+		voice[i]->reg_f_number =
+			(sound[i]->tone >> 8) & OPL4_TONE_NUMBER_BIT8;
+		snd_opl4_write(opl4, OPL4_REG_F_NUMBER + voice[i]->number,
+			       voice[i]->reg_f_number);
+		snd_opl4_write(opl4, OPL4_REG_TONE_NUMBER + voice[i]->number,
+			       sound[i]->tone & 0xff);
+	}
+
+	/* set parameters which can be set while loading */
+	for (i = 0; i < voices; i++) {
+		voice[i]->reg_misc = OPL4_LFO_RESET_BIT;
+		snd_opl4_update_pan(opl4, voice[i]);
+		snd_opl4_update_pitch(opl4, voice[i]);
+		voice[i]->level_direct = OPL4_LEVEL_DIRECT_BIT;
+		snd_opl4_update_volume(opl4, voice[i]);
+	}
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+
+	/* wait for completion of loading */
+	snd_opl4_wait_for_wave_headers(opl4);
+
+	/* set remaining parameters */
+	spin_lock_irqsave(&opl4->reg_lock, flags);
+	for (i = 0; i < voices; i++) {
+		snd_opl4_update_tone_parameters(opl4, voice[i]);
+		voice[i]->reg_lfo_vibrato = voice[i]->sound->reg_lfo_vibrato;
+		snd_opl4_update_vibrato_depth(opl4, voice[i]);
+	}
+
+	/* finally, switch on all voices */
+	for (i = 0; i < voices; i++) {
+		voice[i]->reg_misc =
+			(voice[i]->reg_misc & 0x1f) | OPL4_KEY_ON_BIT;
+		snd_opl4_write(opl4, OPL4_REG_MISC + voice[i]->number,
+			       voice[i]->reg_misc);
+	}
+	spin_unlock_irqrestore(&opl4->reg_lock, flags);
+}
+
+static void snd_opl4_voice_off(struct snd_opl4 *opl4, struct opl4_voice *voice)
+{
+	list_del(&voice->list);
+	list_add_tail(&voice->list, &opl4->off_voices);
+
+	voice->reg_misc &= ~OPL4_KEY_ON_BIT;
+	snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc);
+}
+
+void snd_opl4_note_off(void *private_data, int note, int vel, struct snd_midi_channel *chan)
+{
+	struct snd_opl4 *opl4 = private_data;
+
+	snd_opl4_do_for_note(opl4, note, chan, snd_opl4_voice_off);
+}
+
+static void snd_opl4_terminate_voice(struct snd_opl4 *opl4, struct opl4_voice *voice)
+{
+	list_del(&voice->list);
+	list_add_tail(&voice->list, &opl4->off_voices);
+
+	voice->reg_misc = (voice->reg_misc & ~OPL4_KEY_ON_BIT) | OPL4_DAMP_BIT;
+	snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc);
+}
+
+void snd_opl4_terminate_note(void *private_data, int note, struct snd_midi_channel *chan)
+{
+	struct snd_opl4 *opl4 = private_data;
+
+	snd_opl4_do_for_note(opl4, note, chan, snd_opl4_terminate_voice);
+}
+
+void snd_opl4_control(void *private_data, int type, struct snd_midi_channel *chan)
+{
+	struct snd_opl4 *opl4 = private_data;
+
+	switch (type) {
+	case MIDI_CTL_MSB_MODWHEEL:
+		chan->control[MIDI_CTL_VIBRATO_DEPTH] = chan->control[MIDI_CTL_MSB_MODWHEEL];
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_vibrato_depth);
+		break;
+	case MIDI_CTL_MSB_MAIN_VOLUME:
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_volume);
+		break;
+	case MIDI_CTL_MSB_PAN:
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_pan);
+		break;
+	case MIDI_CTL_MSB_EXPRESSION:
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_volume);
+		break;
+	case MIDI_CTL_VIBRATO_RATE:
+		/* not yet supported */
+		break;
+	case MIDI_CTL_VIBRATO_DEPTH:
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_vibrato_depth);
+		break;
+	case MIDI_CTL_VIBRATO_DELAY:
+		/* not yet supported */
+		break;
+	case MIDI_CTL_E1_REVERB_DEPTH:
+		/*
+		 * Each OPL4 voice has a bit called "Pseudo-Reverb", but
+		 * IMHO _not_ using it enhances the listening experience.
+		 */
+		break;
+	case MIDI_CTL_PITCHBEND:
+		snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_pitch);
+		break;
+	}
+}
+
+void snd_opl4_sysex(void *private_data, unsigned char *buf, int len,
+		    int parsed, struct snd_midi_channel_set *chset)
+{
+	struct snd_opl4 *opl4 = private_data;
+
+	if (parsed == SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME)
+		snd_opl4_do_for_all(opl4, snd_opl4_update_volume);
+}
diff --git a/linux/sound/drivers/opl4/yrw801.c b/linux/sound/drivers/opl4/yrw801.c
new file mode 100644
index 0000000..6c33549
--- /dev/null
+++ b/linux/sound/drivers/opl4/yrw801.c
@@ -0,0 +1,961 @@
+/*
+ * Information about the Yamaha YRW801 wavetable ROM chip
+ *
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed and/or modified under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opl4_local.h"
+
+int snd_yrw801_detect(struct snd_opl4 *opl4)
+{
+	char buf[15];
+
+	snd_opl4_read_memory(opl4, buf, 0x001200, 15);
+	if (memcmp(buf, "CopyrightYAMAHA", 15))
+		return -ENODEV;
+	snd_opl4_read_memory(opl4, buf, 0x1ffffe, 2);
+	if (buf[0] != 0x01)
+		return -ENODEV;
+	snd_printdd("YRW801 ROM version %02x.%02x\n", buf[0], buf[1]);
+	return 0;
+}
+
+/*
+ * The instrument definitions are stored statically because, in practice, the
+ * OPL4 is always coupled with a YRW801. Dynamic instrument loading would be
+ * required if downloading sample data to external SRAM was actually supported
+ * by this driver.
+ */
+
+static const struct opl4_region regions_00[] = { /* Acoustic Grand Piano */
+	{0x14, 0x27, {0x12c,7474,100, 0,0,0x00,0xc8,0x20,0xf2,0x13,0x08,0x0}},
+	{0x28, 0x2d, {0x12d,6816,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x2e, 0x33, {0x12e,5899,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12f,5290,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x130,4260,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x131,3625,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x132,3116,100, 0,0,0x04,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x133,2081,100, 0,0,0x03,0xc8,0x20,0xf2,0x14,0x18,0x0}},
+	{0x53, 0x58, {0x134,1444,100, 0,0,0x07,0xc8,0x20,0xf3,0x14,0x18,0x0}},
+	{0x59, 0x6d, {0x135,1915,100, 0,0,0x00,0xc8,0x20,0xf4,0x15,0x08,0x0}}
+};
+static const struct opl4_region regions_01[] = { /* Bright Acoustic Piano */
+	{0x14, 0x2d, {0x12c,7474,100, 0,0,0x00,0xc8,0x20,0xf2,0x13,0x08,0x0}},
+	{0x2e, 0x33, {0x12d,6816,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12e,5899,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x12f,5290,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x130,4260,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x131,3625,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x132,3116,100, 0,0,0x04,0xc8,0x20,0xf2,0x14,0x08,0x0}},
+	{0x53, 0x58, {0x133,2081,100, 0,0,0x07,0xc8,0x20,0xf2,0x14,0x18,0x0}},
+	{0x59, 0x5e, {0x134,1444,100, 0,0,0x0a,0xc8,0x20,0xf3,0x14,0x18,0x0}},
+	{0x5f, 0x6d, {0x135,1915,100, 0,0,0x00,0xc8,0x20,0xf4,0x15,0x08,0x0}}
+};
+static const struct opl4_region regions_02[] = { /* Electric Grand Piano */
+	{0x14, 0x2d, {0x12c,7476,100, 1,0,0x00,0xae,0x20,0xf2,0x13,0x07,0x0}},
+	{0x2e, 0x33, {0x12d,6818,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x34, 0x39, {0x12e,5901,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x3a, 0x3f, {0x12f,5292,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x40, 0x45, {0x130,4262,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x46, 0x4b, {0x131,3627,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x4c, 0x52, {0x132,3118,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
+	{0x53, 0x58, {0x133,2083,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x17,0x0}},
+	{0x59, 0x5e, {0x134,1446,100, 1,0,0x00,0xae,0x20,0xf3,0x14,0x17,0x0}},
+	{0x5f, 0x6d, {0x135,1917,100, 1,0,0x00,0xae,0x20,0xf4,0x15,0x07,0x0}},
+	{0x00, 0x7f, {0x06c,6375,100,-1,0,0x00,0xc2,0x28,0xf4,0x23,0x18,0x0}}
+};
+static const struct opl4_region regions_03[] = { /* Honky-Tonk Piano */
+	{0x14, 0x27, {0x12c,7474,100, 0,0,0x00,0xb4,0x20,0xf2,0x13,0x08,0x0}},
+	{0x28, 0x2d, {0x12d,6816,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x2e, 0x33, {0x12e,5899,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12f,5290,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x130,4260,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x131,3625,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x132,3116,100, 0,0,0x04,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x133,2081,100, 0,0,0x03,0xb4,0x20,0xf2,0x14,0x18,0x0}},
+	{0x53, 0x58, {0x134,1444,100, 0,0,0x07,0xb4,0x20,0xf3,0x14,0x18,0x0}},
+	{0x59, 0x6d, {0x135,1915,100, 0,0,0x00,0xb4,0x20,0xf4,0x15,0x08,0x0}},
+	{0x14, 0x27, {0x12c,7486,100, 0,0,0x00,0xb4,0x20,0xf2,0x13,0x08,0x0}},
+	{0x28, 0x2d, {0x12d,6803,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x2e, 0x33, {0x12e,5912,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12f,5275,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x130,4274,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x131,3611,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x132,3129,100, 0,0,0x04,0xb4,0x20,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x133,2074,100, 0,0,0x07,0xb4,0x20,0xf2,0x14,0x18,0x0}},
+	{0x53, 0x58, {0x134,1457,100, 0,0,0x01,0xb4,0x20,0xf3,0x14,0x18,0x0}},
+	{0x59, 0x6d, {0x135,1903,100, 0,0,0x00,0xb4,0x20,0xf4,0x15,0x08,0x0}}
+};
+static const struct opl4_region regions_04[] = { /* Electric Piano 1 */
+	{0x15, 0x6c, {0x00b,6570,100, 0,0,0x00,0x28,0x38,0xf0,0x00,0x0c,0x0}},
+	{0x00, 0x7f, {0x06c,6375,100, 0,2,0x00,0xb0,0x22,0xf4,0x23,0x19,0x0}}
+};
+static const struct opl4_region regions_05[] = { /* Electric Piano 2 */
+	{0x14, 0x27, {0x12c,7476,100, 0,3,0x00,0xa2,0x1b,0xf2,0x13,0x08,0x0}},
+	{0x28, 0x2d, {0x12d,6818,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x2e, 0x33, {0x12e,5901,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12f,5292,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x130,4262,100, 0,3,0x0a,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x131,3627,100, 0,3,0x0a,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x132,3118,100, 0,3,0x04,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x133,2083,100, 0,3,0x03,0xa2,0x1b,0xf2,0x14,0x18,0x0}},
+	{0x53, 0x58, {0x134,1446,100, 0,3,0x07,0xa2,0x1b,0xf3,0x14,0x18,0x0}},
+	{0x59, 0x6d, {0x135,1917,100, 0,3,0x00,0xa2,0x1b,0xf4,0x15,0x08,0x0}},
+	{0x14, 0x2d, {0x12c,7472,100, 0,0,0x00,0xa2,0x18,0xf2,0x13,0x08,0x0}},
+	{0x2e, 0x33, {0x12d,6814,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x34, 0x39, {0x12e,5897,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x3a, 0x3f, {0x12f,5288,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x40, 0x45, {0x130,4258,100, 0,0,0x0a,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x46, 0x4b, {0x131,3623,100, 0,0,0x0a,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x4c, 0x52, {0x132,3114,100, 0,0,0x04,0xa2,0x18,0xf2,0x14,0x08,0x0}},
+	{0x53, 0x58, {0x133,2079,100, 0,0,0x07,0xa2,0x18,0xf2,0x14,0x18,0x0}},
+	{0x59, 0x5e, {0x134,1442,100, 0,0,0x0a,0xa2,0x18,0xf3,0x14,0x18,0x0}},
+	{0x5f, 0x6d, {0x135,1913,100, 0,0,0x00,0xa2,0x18,0xf4,0x15,0x08,0x0}}
+};
+static const struct opl4_region regions_06[] = { /* Harpsichord */
+	{0x15, 0x39, {0x080,5158,100, 0,0,0x00,0xb2,0x20,0xf5,0x24,0x19,0x0}},
+	{0x3a, 0x3f, {0x081,4408,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x09,0x0}},
+	{0x40, 0x45, {0x082,3622,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x09,0x0}},
+	{0x46, 0x4d, {0x083,2843,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x19,0x0}},
+	{0x4e, 0x6c, {0x084,1307,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x29,0x0}}
+};
+static const struct opl4_region regions_07[] = { /* Clavinet */
+	{0x15, 0x51, {0x027,5009,100, 0,0,0x00,0xd2,0x28,0xf5,0x13,0x2b,0x0}},
+	{0x52, 0x6c, {0x028,3495,100, 0,0,0x00,0xd2,0x28,0xf5,0x13,0x3b,0x0}}
+};
+static const struct opl4_region regions_08[] = { /* Celesta */
+	{0x15, 0x6c, {0x02b,3267,100, 0,0,0x00,0xdc,0x20,0xf4,0x15,0x07,0x3}}
+};
+static const struct opl4_region regions_09[] = { /* Glockenspiel */
+	{0x15, 0x78, {0x0f3, 285,100, 0,0,0x00,0xc2,0x28,0xf6,0x25,0x25,0x0}}
+};
+static const struct opl4_region regions_0a[] = { /* Music Box */
+	{0x15, 0x6c, {0x0f3,3362,100, 0,0,0x00,0xb6,0x20,0xa6,0x25,0x25,0x0}},
+	{0x15, 0x6c, {0x101,4773,100, 0,0,0x00,0xaa,0x20,0xd4,0x14,0x16,0x0}}
+};
+static const struct opl4_region regions_0b[] = { /* Vibraphone */
+	{0x15, 0x6c, {0x101,4778,100, 0,0,0x00,0xc0,0x28,0xf4,0x14,0x16,0x4}}
+};
+static const struct opl4_region regions_0c[] = { /* Marimba */
+	{0x15, 0x3f, {0x0f4,4778,100, 0,0,0x00,0xc4,0x38,0xf7,0x47,0x08,0x0}},
+	{0x40, 0x4c, {0x0f5,3217,100, 0,0,0x00,0xc4,0x38,0xf7,0x47,0x08,0x0}},
+	{0x4d, 0x5a, {0x0f5,3217,100, 0,0,0x00,0xc4,0x38,0xf7,0x48,0x08,0x0}},
+	{0x5b, 0x7f, {0x0f5,3218,100, 0,0,0x00,0xc4,0x38,0xf7,0x48,0x18,0x0}}
+};
+static const struct opl4_region regions_0d[] = { /* Xylophone */
+	{0x00, 0x7f, {0x136,1729,100, 0,0,0x00,0xd2,0x38,0xf0,0x06,0x36,0x0}}
+};
+static const struct opl4_region regions_0e[] = { /* Tubular Bell */
+	{0x01, 0x7f, {0x0ff,3999,100, 0,1,0x00,0x90,0x21,0xf4,0xa3,0x25,0x1}}
+};
+static const struct opl4_region regions_0f[] = { /* Dulcimer */
+	{0x00, 0x7f, {0x03f,4236,100, 0,1,0x00,0xbc,0x29,0xf5,0x16,0x07,0x0}},
+	{0x00, 0x7f, {0x040,4236,100, 0,2,0x0e,0x94,0x2a,0xf5,0x16,0x07,0x0}}
+};
+static const struct opl4_region regions_10[] = { /* Drawbar Organ */
+	{0x01, 0x7f, {0x08e,4394,100, 0,2,0x14,0xc2,0x3a,0xf0,0x00,0x0a,0x0}}
+};
+static const struct opl4_region regions_11[] = { /* Percussive Organ */
+	{0x15, 0x3b, {0x08c,6062,100, 0,3,0x00,0xbe,0x3b,0xf0,0x00,0x09,0x0}},
+	{0x3c, 0x6c, {0x08d,2984,100, 0,3,0x00,0xbe,0x3b,0xf0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_12[] = { /* Rock Organ */
+	{0x15, 0x30, {0x128,6574,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
+	{0x31, 0x3c, {0x129,5040,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
+	{0x3d, 0x48, {0x12a,3498,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
+	{0x49, 0x54, {0x12b,1957,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
+	{0x55, 0x6c, {0x127, 423,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}}
+};
+static const struct opl4_region regions_13[] = { /* Church Organ */
+	{0x15, 0x29, {0x087,7466,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
+	{0x2a, 0x30, {0x088,6456,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
+	{0x31, 0x38, {0x089,5428,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
+	{0x39, 0x41, {0x08a,4408,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
+	{0x42, 0x6c, {0x08b,3406,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_14[] = { /* Reed Organ */
+	{0x00, 0x53, {0x0ac,5570,100, 0,0,0x06,0xc0,0x38,0xf0,0x00,0x09,0x1}},
+	{0x54, 0x7f, {0x0ad,2497,100, 0,0,0x00,0xc0,0x38,0xf0,0x00,0x09,0x1}}
+};
+static const struct opl4_region regions_15[] = { /* Accordion */
+	{0x15, 0x4c, {0x006,4261,100, 0,2,0x00,0xa4,0x22,0x90,0x00,0x09,0x0}},
+	{0x4d, 0x6c, {0x007,1530,100, 0,2,0x00,0xa4,0x22,0x90,0x00,0x09,0x0}},
+	{0x15, 0x6c, {0x070,4391,100, 0,3,0x00,0x8a,0x23,0xa0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_16[] = { /* Harmonica */
+	{0x15, 0x6c, {0x070,4408,100, 0,0,0x00,0xae,0x30,0xa0,0x00,0x09,0x2}}
+};
+static const struct opl4_region regions_17[] = { /* Tango Accordion */
+	{0x00, 0x53, {0x0ac,5573,100, 0,0,0x00,0xae,0x38,0xf0,0x00,0x09,0x0}},
+	{0x54, 0x7f, {0x0ad,2500,100, 0,0,0x00,0xae,0x38,0xf0,0x00,0x09,0x0}},
+	{0x15, 0x6c, {0x041,8479,100, 0,2,0x00,0x6a,0x3a,0x75,0x20,0x0a,0x0}}
+};
+static const struct opl4_region regions_18[] = { /* Nylon Guitar */
+	{0x15, 0x2f, {0x0b3,6964,100, 0,0,0x05,0xca,0x28,0xf5,0x34,0x09,0x0}},
+	{0x30, 0x36, {0x0b7,5567,100, 0,0,0x0c,0xca,0x28,0xf5,0x34,0x09,0x0}},
+	{0x37, 0x3c, {0x0b5,4653,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}},
+	{0x3d, 0x43, {0x0b4,3892,100, 0,0,0x00,0xca,0x28,0xf6,0x35,0x09,0x0}},
+	{0x44, 0x60, {0x0b6,2723,100, 0,0,0x00,0xca,0x28,0xf6,0x35,0x19,0x0}}
+};
+static const struct opl4_region regions_19[] = { /* Steel Guitar */
+	{0x15, 0x31, {0x00c,6937,100, 0,0,0x00,0xbc,0x28,0xf0,0x04,0x19,0x0}},
+	{0x32, 0x38, {0x00d,5410,100, 0,0,0x00,0xbc,0x28,0xf0,0x05,0x09,0x0}},
+	{0x39, 0x47, {0x00e,4379,100, 0,0,0x00,0xbc,0x28,0xf5,0x94,0x09,0x0}},
+	{0x48, 0x6c, {0x00f,2843,100, 0,0,0x00,0xbc,0x28,0xf6,0x95,0x09,0x0}}
+};
+static const struct opl4_region regions_1a[] = { /* Jazz Guitar */
+	{0x15, 0x31, {0x05a,6832,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}},
+	{0x32, 0x3f, {0x05b,4897,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}},
+	{0x40, 0x6c, {0x05c,3218,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}}
+};
+static const struct opl4_region regions_1b[] = { /* Clean Guitar */
+	{0x15, 0x2c, {0x061,7053,100, 0,1,0x00,0xb4,0x29,0xf5,0x54,0x0a,0x0}},
+	{0x2d, 0x31, {0x060,6434,100, 0,1,0x00,0xb4,0x29,0xf5,0x54,0x0a,0x0}},
+	{0x32, 0x38, {0x063,5764,100, 0,1,0x00,0xbe,0x29,0xf5,0x55,0x0a,0x0}},
+	{0x39, 0x3f, {0x062,4627,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x0a,0x0}},
+	{0x40, 0x44, {0x065,3963,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x1a,0x0}},
+	{0x45, 0x4b, {0x064,3313,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x1a,0x0}},
+	{0x4c, 0x54, {0x066,2462,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x2a,0x0}},
+	{0x55, 0x6c, {0x067,1307,100, 0,1,0x00,0xb4,0x29,0xf6,0x56,0x0a,0x0}}
+};
+static const struct opl4_region regions_1c[] = { /* Muted Guitar */
+	{0x01, 0x7f, {0x068,4408,100, 0,0,0x00,0xcc,0x28,0xf6,0x15,0x09,0x0}}
+};
+static const struct opl4_region regions_1d[] = { /* Overdriven Guitar */
+	{0x00, 0x40, {0x0a5,6589,100, 0,1,0x00,0xc0,0x29,0xf2,0x11,0x09,0x0}},
+	{0x41, 0x7f, {0x0a6,5428,100, 0,1,0x00,0xc0,0x29,0xf2,0x11,0x09,0x0}}
+};
+static const struct opl4_region regions_1e[] = { /* Distortion Guitar */
+	{0x15, 0x2a, {0x051,6928,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x2b, 0x2e, {0x052,6433,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x2f, 0x32, {0x053,5944,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x33, 0x36, {0x054,5391,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x37, 0x3a, {0x055,4897,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x3b, 0x3e, {0x056,4408,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x3f, 0x42, {0x057,3892,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x43, 0x46, {0x058,3361,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
+	{0x47, 0x6c, {0x059,2784,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}}
+};
+static const struct opl4_region regions_1f[] = { /* Guitar Harmonics */
+	{0x15, 0x44, {0x05e,5499,100, 0,0,0x00,0xce,0x28,0xf4,0x24,0x09,0x0}},
+	{0x45, 0x49, {0x05d,4850,100, 0,0,0x00,0xe2,0x28,0xf4,0x24,0x09,0x0}},
+	{0x4a, 0x6c, {0x05f,4259,100, 0,0,0x00,0xce,0x28,0xf4,0x24,0x09,0x0}}
+};
+static const struct opl4_region regions_20[] = { /* Acoustic Bass */
+	{0x15, 0x30, {0x004,8053,100, 0,0,0x00,0xe2,0x18,0xf5,0x15,0x09,0x0}},
+	{0x31, 0x6c, {0x005,4754,100, 0,0,0x00,0xe2,0x18,0xf5,0x15,0x09,0x0}}
+};
+static const struct opl4_region regions_21[] = { /* Fingered Bass */
+	{0x01, 0x20, {0x04a,8762,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}},
+	{0x21, 0x25, {0x04b,8114,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}},
+	{0x26, 0x2a, {0x04c,7475,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}},
+	{0x2b, 0x7f, {0x04d,6841,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}}
+};
+static const struct opl4_region regions_22[] = { /* Picked Bass */
+	{0x15, 0x23, {0x04f,7954,100, 0,0,0x00,0xcc,0x18,0xf3,0x90,0x0a,0x0}},
+	{0x24, 0x2a, {0x050,7318,100, 0,0,0x05,0xcc,0x18,0xf3,0x90,0x1a,0x0}},
+	{0x2b, 0x2f, {0x06b,6654,100, 0,0,0x00,0xcc,0x18,0xf3,0x90,0x2a,0x0}},
+	{0x30, 0x47, {0x069,6031,100, 0,0,0x00,0xcc,0x18,0xf5,0xb0,0x0a,0x0}},
+	{0x48, 0x6c, {0x06a,5393,100, 0,0,0x00,0xcc,0x18,0xf5,0xb0,0x0a,0x0}}
+};
+static const struct opl4_region regions_23[] = { /* Fretless Bass */
+	{0x01, 0x7f, {0x04e,5297,100, 0,0,0x00,0xd2,0x10,0xf3,0x63,0x19,0x0}}
+};
+static const struct opl4_region regions_24[] = { /* Slap Bass 1 */
+	{0x15, 0x6c, {0x0a3,7606,100, 0,1,0x00,0xde,0x19,0xf5,0x32,0x1a,0x0}}
+};
+static const struct opl4_region regions_25[] = { /* Slap Bass 2 */
+	{0x01, 0x7f, {0x0a2,6694,100, 0,0,0x00,0xda,0x20,0xb0,0x02,0x09,0x0}}
+};
+static const struct opl4_region regions_26[] = { /* Synth Bass 1 */
+	{0x15, 0x6c, {0x0be,7466,100, 0,1,0x00,0xb8,0x39,0xf4,0x14,0x09,0x0}}
+};
+static const struct opl4_region regions_27[] = { /* Synth Bass 2 */
+	{0x00, 0x7f, {0x117,8103,100, 0,1,0x00,0xca,0x39,0xf3,0x50,0x08,0x0}}
+};
+static const struct opl4_region regions_28[] = { /* Violin */
+	{0x15, 0x3a, {0x105,5158,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x3b, 0x3f, {0x102,4754,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x40, 0x41, {0x106,4132,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x42, 0x44, {0x107,4033,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x45, 0x47, {0x108,3580,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x48, 0x4a, {0x10a,2957,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x4b, 0x4c, {0x10b,2724,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x4d, 0x4e, {0x10c,2530,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x4f, 0x51, {0x10d,2166,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
+	{0x52, 0x6c, {0x109,1825,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}
+};
+static const struct opl4_region regions_29[] = { /* Viola */
+	{0x15, 0x32, {0x103,5780,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x33, 0x35, {0x104,5534,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x36, 0x38, {0x105,5158,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x39, 0x3d, {0x102,4754,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x3e, 0x3f, {0x106,4132,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x40, 0x42, {0x107,4033,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x43, 0x45, {0x108,3580,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x46, 0x48, {0x10a,2957,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x49, 0x4a, {0x10b,2724,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x4b, 0x4c, {0x10c,2530,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x4d, 0x4f, {0x10d,2166,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}},
+	{0x50, 0x6c, {0x109,1825,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}}
+};
+static const struct opl4_region regions_2a[] = { /* Cello */
+	{0x15, 0x2d, {0x112,6545,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x08,0x0}},
+	{0x2e, 0x37, {0x113,5764,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x08,0x0}},
+	{0x38, 0x3e, {0x115,4378,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}},
+	{0x3f, 0x44, {0x116,3998,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}},
+	{0x45, 0x6c, {0x114,3218,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}}
+};
+static const struct opl4_region regions_2b[] = { /* Contrabass */
+	{0x15, 0x29, {0x110,7713,100, 0,1,0x00,0xc2,0x19,0x90,0x00,0x09,0x0}},
+	{0x2a, 0x6c, {0x111,6162,100, 0,1,0x00,0xc2,0x19,0x90,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_2c[] = { /* Tremolo Strings */
+	{0x15, 0x3b, {0x0b0,4810,100, 0,0,0x0a,0xde,0x38,0xf0,0x00,0x07,0x6}},
+	{0x3c, 0x41, {0x035,4035,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}},
+	{0x42, 0x47, {0x033,3129,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}},
+	{0x48, 0x52, {0x034,2625,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}},
+	{0x53, 0x6c, {0x0af, 936,100, 0,0,0x00,0xde,0x38,0xf0,0x00,0x07,0x6}}
+};
+static const struct opl4_region regions_2d[] = { /* Pizzicato Strings */
+	{0x15, 0x32, {0x0b8,6186,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}},
+	{0x33, 0x3b, {0x0b9,5031,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}},
+	{0x3c, 0x42, {0x0bb,4146,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}},
+	{0x43, 0x48, {0x0ba,3245,100, 0,0,0x00,0xc2,0x28,0xf0,0x00,0x05,0x0}},
+	{0x49, 0x6c, {0x0bc,2352,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}}
+};
+static const struct opl4_region regions_2e[] = { /* Harp */
+	{0x15, 0x46, {0x07e,3740,100, 0,1,0x00,0xd2,0x29,0xf5,0x25,0x07,0x0}},
+	{0x47, 0x6c, {0x07f,2319,100, 0,1,0x00,0xd2,0x29,0xf5,0x25,0x07,0x0}}
+};
+static const struct opl4_region regions_2f[] = { /* Timpani */
+	{0x15, 0x6c, {0x100,6570,100, 0,0,0x00,0xf8,0x28,0xf0,0x05,0x16,0x0}}
+};
+static const struct opl4_region regions_30[] = { /* Strings */
+	{0x15, 0x3b, {0x13c,4806,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}},
+	{0x3c, 0x41, {0x13e,4035,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}},
+	{0x42, 0x47, {0x13d,3122,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}},
+	{0x48, 0x52, {0x13f,2629,100, 0,0,0x00,0xbe,0x20,0x80,0x00,0x07,0x0}},
+	{0x53, 0x6c, {0x140, 950,100, 0,0,0x00,0xbe,0x20,0x80,0x00,0x07,0x0}}
+};
+static const struct opl4_region regions_31[] = { /* Slow Strings */
+	{0x15, 0x3b, {0x0b0,4810,100, 0,1,0x0a,0xbe,0x19,0xf0,0x00,0x07,0x0}},
+	{0x3c, 0x41, {0x035,4035,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}},
+	{0x42, 0x47, {0x033,3129,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}},
+	{0x48, 0x52, {0x034,2625,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}},
+	{0x53, 0x6c, {0x0af, 936,100, 0,1,0x00,0xbe,0x19,0xf0,0x00,0x07,0x0}}
+};
+static const struct opl4_region regions_32[] = { /* Synth Strings 1 */
+	{0x05, 0x71, {0x002,6045,100,-2,0,0x00,0xa6,0x20,0x93,0x22,0x06,0x0}},
+	{0x15, 0x6c, {0x0ae,3261,100, 2,0,0x00,0xc6,0x20,0x70,0x01,0x06,0x0}}
+};
+static const struct opl4_region regions_33[] = { /* Synth Strings 2 */
+	{0x15, 0x6c, {0x002,4513,100, 5,1,0x00,0xb4,0x19,0x70,0x00,0x06,0x0}},
+	{0x15, 0x6c, {0x002,4501,100,-5,1,0x00,0xb4,0x19,0x70,0x00,0x06,0x0}}
+};
+static const struct opl4_region regions_34[] = { /* Choir Aahs */
+	{0x15, 0x3a, {0x018,5010,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}},
+	{0x3b, 0x40, {0x019,4370,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}},
+	{0x41, 0x47, {0x01a,3478,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}},
+	{0x48, 0x6c, {0x01b,2197,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}}
+};
+static const struct opl4_region regions_35[] = { /* Voice Oohs */
+	{0x15, 0x6c, {0x029,3596,100, 0,0,0x00,0xe6,0x20,0xf7,0x20,0x08,0x0}}
+};
+static const struct opl4_region regions_36[] = { /* Synth Voice */
+	{0x15, 0x6c, {0x02a,3482,100, 0,1,0x00,0xc2,0x19,0x85,0x21,0x07,0x0}}
+};
+static const struct opl4_region regions_37[] = { /* Orchestra Hit */
+	{0x15, 0x6c, {0x049,4394,100, 0,0,0x00,0xfe,0x30,0x80,0x05,0x05,0x0}}
+};
+static const struct opl4_region regions_38[] = { /* Trumpet */
+	{0x15, 0x3c, {0x0f6,4706,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
+	{0x3d, 0x43, {0x0f8,3894,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
+	{0x44, 0x48, {0x0f7,3118,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
+	{0x49, 0x4e, {0x0fa,2322,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
+	{0x4f, 0x55, {0x0f9,1634,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
+	{0x56, 0x6c, {0x0fb, 786,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}}
+};
+static const struct opl4_region regions_39[] = { /* Trombone */
+	{0x15, 0x3a, {0x0f0,5053,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}},
+	{0x3b, 0x3f, {0x0f1,4290,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}},
+	{0x40, 0x6c, {0x0f2,3580,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_3a[] = { /* Tuba */
+	{0x15, 0x2d, {0x085,7096,100, 0,1,0x00,0xde,0x21,0xf5,0x10,0x09,0x0}},
+	{0x2e, 0x6c, {0x086,6014,100, 0,1,0x00,0xde,0x21,0xf5,0x10,0x09,0x0}}
+};
+static const struct opl4_region regions_3b[] = { /* Muted Trumpet */
+	{0x15, 0x45, {0x0b1,4135,100, 0,0,0x00,0xcc,0x28,0xf3,0x10,0x0a,0x1}},
+	{0x46, 0x6c, {0x0b2,2599,100, 0,0,0x00,0xcc,0x28,0x83,0x10,0x0a,0x1}}
+};
+static const struct opl4_region regions_3c[] = { /* French Horns */
+	{0x15, 0x49, {0x07c,3624,100, 0,2,0x00,0xd0,0x1a,0xf0,0x00,0x09,0x0}},
+	{0x4a, 0x6c, {0x07d,2664,100, 0,2,0x00,0xd0,0x1a,0xf0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_3d[] = { /* Brass Section */
+	{0x15, 0x42, {0x0fc,4375,100, 0,0,0x00,0xd6,0x28,0xf0,0x00,0x0a,0x0}},
+	{0x43, 0x6c, {0x0fd,2854,100, 0,0,0x00,0xd6,0x28,0xf0,0x00,0x0a,0x0}}
+};
+static const struct opl4_region regions_3e[] = { /* Synth Brass 1 */
+	{0x01, 0x27, {0x0d3,9094,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x28, 0x2d, {0x0da,8335,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x2e, 0x33, {0x0d4,7558,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x34, 0x39, {0x0db,6785,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x3a, 0x3f, {0x0d5,6042,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x40, 0x45, {0x0dc,5257,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x46, 0x4b, {0x0d6,4493,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x4c, 0x51, {0x0dd,3741,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x52, 0x57, {0x0d7,3012,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x58, 0x5d, {0x0de,2167,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x5e, 0x63, {0x0d8,1421,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x64, 0x7f, {0x0d9,-115,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
+	{0x01, 0x27, {0x118,9103,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x28, 0x2d, {0x119,8340,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x2e, 0x33, {0x11a,7565,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x34, 0x39, {0x11b,6804,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x3a, 0x3f, {0x11c,6042,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x40, 0x45, {0x11d,5277,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x46, 0x4b, {0x11e,4520,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x4c, 0x51, {0x11f,3741,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x52, 0x57, {0x120,3012,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x58, 0x5d, {0x121,2166,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x5e, 0x64, {0x122,1421,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
+	{0x65, 0x7f, {0x123,-115,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}
+};
+static const struct opl4_region regions_3f[] = { /* Synth Brass 2 */
+	{0x01, 0x27, {0x118,9113,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x28, 0x2d, {0x119,8350,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x2e, 0x33, {0x11a,7575,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x34, 0x39, {0x11b,6814,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x3a, 0x3f, {0x11c,6052,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x40, 0x45, {0x11d,5287,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x46, 0x4b, {0x11e,4530,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x4c, 0x51, {0x11f,3751,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x52, 0x57, {0x120,3022,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x58, 0x5d, {0x121,2176,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x5e, 0x64, {0x122,1431,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x65, 0x7f, {0x123,-105,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
+	{0x00, 0x7f, {0x124,4034,100,-3,2,0x00,0xea,0x22,0x85,0x23,0x08,0x0}}
+};
+static const struct opl4_region regions_40[] = { /* Soprano Sax */
+	{0x15, 0x3f, {0x0e3,4228,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}},
+	{0x40, 0x45, {0x0e4,3495,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}},
+	{0x46, 0x4b, {0x0e5,2660,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}},
+	{0x4c, 0x51, {0x0e6,2002,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}},
+	{0x52, 0x59, {0x0e7,1186,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}},
+	{0x59, 0x6c, {0x0e8,1730,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}}
+};
+static const struct opl4_region regions_41[] = { /* Alto Sax */
+	{0x15, 0x32, {0x092,6204,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x33, 0x35, {0x096,5812,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x36, 0x3a, {0x099,5318,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x3b, 0x3b, {0x08f,5076,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x3c, 0x3e, {0x093,4706,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x3f, 0x41, {0x097,4321,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x42, 0x44, {0x09a,3893,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x45, 0x47, {0x090,3497,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x48, 0x4a, {0x094,3119,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x4b, 0x4d, {0x098,2726,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x4e, 0x50, {0x09b,2393,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x51, 0x53, {0x091,2088,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
+	{0x54, 0x6c, {0x095,1732,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}
+};
+static const struct opl4_region regions_42[] = { /* Tenor Sax */
+	{0x24, 0x30, {0x0e9,6301,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x31, 0x34, {0x0ea,5781,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x35, 0x3a, {0x0eb,5053,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x3b, 0x41, {0x0ed,4165,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x42, 0x47, {0x0ec,3218,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x48, 0x51, {0x0ee,2462,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
+	{0x52, 0x6c, {0x0ef,1421,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}}
+};
+static const struct opl4_region regions_43[] = { /* Baritone Sax */
+	{0x15, 0x2d, {0x0df,6714,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}},
+	{0x2e, 0x34, {0x0e1,5552,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}},
+	{0x35, 0x39, {0x0e2,5178,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}},
+	{0x3a, 0x6c, {0x0e0,4437,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}}
+};
+static const struct opl4_region regions_44[] = { /* Oboe */
+	{0x15, 0x3c, {0x042,4493,100, 0,1,0x00,0xe6,0x39,0xf4,0x10,0x0a,0x0}},
+	{0x3d, 0x43, {0x044,3702,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
+	{0x44, 0x49, {0x043,2956,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
+	{0x4a, 0x4f, {0x046,2166,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
+	{0x50, 0x55, {0x045,1420,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
+	{0x56, 0x6c, {0x047, 630,100, 0,1,0x00,0xe6,0x39,0xf4,0x10,0x0a,0x0}}
+};
+static const struct opl4_region regions_45[] = { /* English Horn */
+	{0x15, 0x38, {0x03c,5098,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}},
+	{0x39, 0x3e, {0x03b,4291,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}},
+	{0x3f, 0x6c, {0x03d,3540,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_46[] = { /* Bassoon */
+	{0x15, 0x22, {0x038,7833,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}},
+	{0x23, 0x2e, {0x03a,7070,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}},
+	{0x2f, 0x6c, {0x039,6302,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}}
+};
+static const struct opl4_region regions_47[] = { /* Clarinet */
+	{0x15, 0x3b, {0x09e,5900,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}},
+	{0x3c, 0x41, {0x0a0,5158,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}},
+	{0x42, 0x4a, {0x09f,4260,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}},
+	{0x4b, 0x6c, {0x0a1,2957,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}}
+};
+static const struct opl4_region regions_48[] = { /* Piccolo */
+	{0x15, 0x40, {0x071,4803,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x41, 0x4d, {0x072,3314,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x4e, 0x53, {0x073,1731,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x54, 0x5f, {0x074,2085,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x60, 0x6c, {0x075,1421,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}}
+};
+static const struct opl4_region regions_49[] = { /* Flute */
+	{0x15, 0x40, {0x071,4803,100, 0,0,0x00,0xdc,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x41, 0x4d, {0x072,3314,100, 0,0,0x00,0xdc,0x38,0xf0,0x00,0x0a,0x2}},
+	{0x4e, 0x6c, {0x073,1731,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}}
+};
+static const struct opl4_region regions_4a[] = { /* Recorder */
+	{0x15, 0x6f, {0x0bd,4897,100, 0,0,0x00,0xec,0x30,0x70,0x00,0x09,0x1}}
+};
+static const struct opl4_region regions_4b[] = { /* Pan Flute */
+	{0x15, 0x6c, {0x077,2359,100, 0,0,0x00,0xde,0x38,0xf0,0x00,0x09,0x3}}
+};
+static const struct opl4_region regions_4c[] = { /* Bottle Blow */
+	{0x15, 0x6c, {0x077,2359,100, 0,0,0x00,0xc8,0x38,0xf0,0x00,0x09,0x1}},
+	{0x01, 0x7f, {0x125,7372,100, 0,0,0x1e,0x80,0x00,0xf0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_4d[] = { /* Shakuhachi */
+	{0x00, 0x7f, {0x0ab,4548,100, 0,0,0x00,0xd6,0x30,0xf0,0x00,0x0a,0x3}},
+	{0x15, 0x6c, {0x076,3716,100, 0,0,0x00,0xa2,0x28,0x70,0x00,0x09,0x2}}
+};
+static const struct opl4_region regions_4e[] = { /* Whistle */
+	{0x00, 0x7f, {0x0aa,1731,100, 0,4,0x00,0xd2,0x2c,0x70,0x00,0x0a,0x0}}
+};
+static const struct opl4_region regions_4f[] = { /* Ocarina */
+	{0x00, 0x7f, {0x0aa,1731,100, 0,1,0x00,0xce,0x29,0x90,0x00,0x0a,0x1}}
+};
+static const struct opl4_region regions_50[] = { /* Square Lead */
+	{0x01, 0x2a, {0x0cc,9853,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x2b, 0x36, {0x0cd,6785,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x37, 0x42, {0x0ca,5248,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x43, 0x4e, {0x0cf,3713,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x4f, 0x5a, {0x0ce,2176,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x5b, 0x7f, {0x0cb, 640,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
+	{0x01, 0x2a, {0x0cc,9844,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
+	{0x2b, 0x36, {0x0cd,6776,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
+	{0x37, 0x42, {0x0ca,5239,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
+	{0x43, 0x4e, {0x0cf,3704,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
+	{0x4f, 0x5a, {0x0ce,2167,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
+	{0x5b, 0x7f, {0x0cb, 631,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}}
+};
+static const struct opl4_region regions_51[] = { /* Sawtooth Lead */
+	{0x01, 0x27, {0x118,9108,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x28, 0x2d, {0x119,8345,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x2e, 0x33, {0x11a,7570,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x34, 0x39, {0x11b,6809,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x3a, 0x3f, {0x11c,6047,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x40, 0x45, {0x11d,5282,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x46, 0x4b, {0x11e,4525,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x4c, 0x51, {0x11f,3746,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x52, 0x57, {0x120,3017,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x58, 0x5d, {0x121,2171,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x5e, 0x66, {0x122,1426,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x67, 0x7f, {0x123,-110,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x01, 0x27, {0x118,9098,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x28, 0x2d, {0x119,8335,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x2e, 0x33, {0x11a,7560,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x34, 0x39, {0x11b,6799,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x3a, 0x3f, {0x11c,6037,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x40, 0x45, {0x11d,5272,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x46, 0x4b, {0x11e,4515,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x4c, 0x51, {0x11f,3736,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x52, 0x57, {0x120,3007,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x58, 0x5d, {0x121,2161,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x5e, 0x66, {0x122,1416,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
+	{0x67, 0x7f, {0x123,-120,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}
+};
+static const struct opl4_region regions_52[] = { /* Calliope Lead */
+	{0x00, 0x7f, {0x0aa,1731,100, 0,0,0x00,0xc2,0x28,0x90,0x00,0x0a,0x2}},
+	{0x15, 0x6c, {0x076,3716,100, 0,0,0x00,0xb6,0x28,0xb0,0x00,0x09,0x2}}
+};
+static const struct opl4_region regions_53[] = { /* Chiffer Lead */
+	{0x00, 0x7f, {0x13a,3665,100, 0,2,0x00,0xcc,0x2a,0xf0,0x10,0x09,0x1}},
+	{0x01, 0x7f, {0x0fe,3660,100, 0,0,0x00,0xbe,0x28,0xf3,0x10,0x17,0x0}}
+};
+static const struct opl4_region regions_54[] = { /* Charang Lead */
+	{0x00, 0x40, {0x0a5,6594,100, 0,3,0x00,0xba,0x33,0xf2,0x11,0x09,0x0}},
+	{0x41, 0x7f, {0x0a6,5433,100, 0,3,0x00,0xba,0x33,0xf2,0x11,0x09,0x0}},
+	{0x01, 0x27, {0x118,9098,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x28, 0x2d, {0x119,8335,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x2e, 0x33, {0x11a,7560,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x34, 0x39, {0x11b,6799,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x3a, 0x3f, {0x11c,6037,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x40, 0x45, {0x11d,5272,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x46, 0x4b, {0x11e,4515,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x4c, 0x51, {0x11f,3736,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x52, 0x57, {0x120,3007,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x58, 0x5d, {0x121,2161,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x5e, 0x66, {0x122,1416,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
+	{0x67, 0x7f, {0x123,-120,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}
+};
+static const struct opl4_region regions_55[] = { /* Voice Lead */
+	{0x00, 0x7f, {0x0aa,1739,100, 0,6,0x00,0x8c,0x2e,0x90,0x00,0x0a,0x0}},
+	{0x15, 0x6c, {0x02a,3474,100, 0,1,0x00,0xd8,0x29,0xf0,0x05,0x0a,0x0}}
+};
+static const struct opl4_region regions_56[] = { /* 5ths Lead */
+	{0x01, 0x27, {0x118,8468,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x28, 0x2d, {0x119,7705,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x2e, 0x33, {0x11a,6930,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x34, 0x39, {0x11b,6169,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x3a, 0x3f, {0x11c,5407,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x40, 0x45, {0x11d,4642,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x46, 0x4b, {0x11e,3885,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x4c, 0x51, {0x11f,3106,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x52, 0x57, {0x120,2377,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x58, 0x5d, {0x121,1531,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x5e, 0x64, {0x122, 786,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x65, 0x7f, {0x123,-750,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
+	{0x05, 0x71, {0x002,4503,100, 0,1,0x00,0xb8,0x31,0xb3,0x20,0x0b,0x0}}
+};
+static const struct opl4_region regions_57[] = { /* Bass & Lead */
+	{0x00, 0x7f, {0x117,8109,100, 0,1,0x00,0xbc,0x29,0xf3,0x50,0x08,0x0}},
+	{0x01, 0x27, {0x118,9097,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x28, 0x2d, {0x119,8334,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x2e, 0x33, {0x11a,7559,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x34, 0x39, {0x11b,6798,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x3a, 0x3f, {0x11c,6036,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x40, 0x45, {0x11d,5271,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x46, 0x4b, {0x11e,4514,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x4c, 0x51, {0x11f,3735,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x52, 0x57, {0x120,3006,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x58, 0x5d, {0x121,2160,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x5e, 0x66, {0x122,1415,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
+	{0x67, 0x7f, {0x123,-121,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}
+};
+static const struct opl4_region regions_58[] = { /* New Age Pad */
+	{0x15, 0x6c, {0x002,4501,100, 0,4,0x00,0xa4,0x24,0x80,0x01,0x05,0x0}},
+	{0x15, 0x6c, {0x0f3,4253,100, 0,3,0x00,0x8c,0x23,0xa2,0x14,0x06,0x1}}
+};
+static const struct opl4_region regions_59[] = { /* Warm Pad */
+	{0x15, 0x6c, {0x04e,5306,100, 2,2,0x00,0x92,0x2a,0x34,0x23,0x05,0x2}},
+	{0x15, 0x6c, {0x029,3575,100,-2,2,0x00,0xbe,0x22,0x31,0x23,0x06,0x0}}
+};
+static const struct opl4_region regions_5a[] = { /* Polysynth Pad */
+	{0x01, 0x27, {0x118,9111,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x28, 0x2d, {0x119,8348,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x2e, 0x33, {0x11a,7573,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x34, 0x39, {0x11b,6812,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x3a, 0x3f, {0x11c,6050,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x40, 0x45, {0x11d,5285,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x46, 0x4b, {0x11e,4528,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x4c, 0x51, {0x11f,3749,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x52, 0x57, {0x120,3020,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x58, 0x5d, {0x121,2174,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x5e, 0x66, {0x122,1429,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x67, 0x7f, {0x123,-107,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
+	{0x00, 0x7f, {0x124,4024,100, 0,2,0x00,0xae,0x22,0xe5,0x20,0x08,0x0}}
+};
+static const struct opl4_region regions_5b[] = { /* Choir Pad */
+	{0x15, 0x3a, {0x018,5010,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
+	{0x3b, 0x40, {0x019,4370,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
+	{0x41, 0x47, {0x01a,3478,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
+	{0x48, 0x6c, {0x01b,2197,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
+	{0x15, 0x6c, {0x02a,3482,100, 0,4,0x00,0x98,0x24,0x65,0x21,0x06,0x0}}
+};
+static const struct opl4_region regions_5c[] = { /* Bowed Pad */
+	{0x15, 0x6c, {0x101,4790,100,-1,1,0x00,0xbe,0x19,0x44,0x14,0x16,0x0}},
+	{0x00, 0x7f, {0x0aa,1720,100, 1,1,0x00,0x94,0x19,0x40,0x00,0x06,0x0}}
+};
+static const struct opl4_region regions_5d[] = { /* Metallic Pad */
+	{0x15, 0x31, {0x00c,6943,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
+	{0x32, 0x38, {0x00d,5416,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
+	{0x39, 0x47, {0x00e,4385,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
+	{0x48, 0x6c, {0x00f,2849,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
+	{0x00, 0x7f, {0x03f,4224,100, 0,1,0x00,0x9c,0x31,0x65,0x16,0x07,0x0}}
+};
+static const struct opl4_region regions_5e[] = { /* Halo Pad */
+	{0x00, 0x7f, {0x124,4038,100, 0,2,0x00,0xa6,0x1a,0x85,0x23,0x08,0x0}},
+	{0x15, 0x6c, {0x02a,3471,100, 0,3,0x00,0xc0,0x1b,0xc0,0x05,0x06,0x0}}
+};
+static const struct opl4_region regions_5f[] = { /* Sweep Pad */
+	{0x01, 0x27, {0x0d3,9100,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x28, 0x2d, {0x0da,8341,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x2e, 0x33, {0x0d4,7564,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x34, 0x39, {0x0db,6791,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x3a, 0x3f, {0x0d5,6048,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x40, 0x45, {0x0dc,5263,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x46, 0x4b, {0x0d6,4499,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x4c, 0x51, {0x0dd,3747,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x52, 0x57, {0x0d7,3018,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x58, 0x5d, {0x0de,2173,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x5e, 0x63, {0x0d8,1427,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x64, 0x7f, {0x0d9,-109,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
+	{0x01, 0x27, {0x0d3,9088,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x28, 0x2d, {0x0da,8329,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x2e, 0x33, {0x0d4,7552,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x34, 0x39, {0x0db,6779,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x3a, 0x3f, {0x0d5,6036,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x40, 0x45, {0x0dc,5251,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x46, 0x4b, {0x0d6,4487,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x4c, 0x51, {0x0dd,3735,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x52, 0x57, {0x0d7,3006,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x58, 0x5d, {0x0de,2161,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x5e, 0x63, {0x0d8,1415,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
+	{0x64, 0x7f, {0x0d9,-121,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}
+};
+static const struct opl4_region regions_60[] = { /* Ice Rain */
+	{0x01, 0x7f, {0x04e,9345,100, 0,2,0x00,0xcc,0x22,0xa3,0x63,0x17,0x0}},
+	{0x00, 0x7f, {0x143,5586, 20, 0,2,0x00,0x6e,0x2a,0xf0,0x05,0x05,0x0}}
+};
+static const struct opl4_region regions_61[] = { /* Soundtrack */
+	{0x15, 0x6c, {0x002,4501,100, 0,2,0x00,0xb6,0x2a,0x60,0x01,0x05,0x0}},
+	{0x15, 0x6c, {0x0f3,1160,100, 0,5,0x00,0xa8,0x2d,0x52,0x14,0x06,0x2}}
+};
+static const struct opl4_region regions_62[] = { /* Crystal */
+	{0x15, 0x6c, {0x0f3,1826,100, 0,3,0x00,0xb8,0x33,0xf6,0x25,0x25,0x0}},
+	{0x15, 0x2c, {0x06d,7454,100, 0,3,0x00,0xac,0x3b,0x85,0x24,0x06,0x0}},
+	{0x2d, 0x36, {0x06e,5925,100, 0,3,0x00,0xac,0x3b,0x85,0x24,0x06,0x0}},
+	{0x37, 0x6c, {0x06f,4403,100, 0,3,0x09,0xac,0x3b,0x85,0x24,0x06,0x0}}
+};
+static const struct opl4_region regions_63[] = { /* Atmosphere */
+	{0x05, 0x71, {0x002,4509,100, 0,2,0x00,0xc8,0x32,0x73,0x22,0x06,0x1}},
+	{0x15, 0x2f, {0x0b3,6964,100, 0,2,0x05,0xc2,0x32,0xf5,0x34,0x07,0x2}},
+	{0x30, 0x36, {0x0b7,5567,100, 0,2,0x0c,0xc2,0x32,0xf5,0x34,0x07,0x2}},
+	{0x37, 0x3c, {0x0b5,4653,100, 0,2,0x00,0xc2,0x32,0xf6,0x34,0x07,0x2}},
+	{0x3d, 0x43, {0x0b4,3892,100, 0,2,0x00,0xc2,0x32,0xf6,0x35,0x07,0x2}},
+	{0x44, 0x60, {0x0b6,2723,100, 0,2,0x00,0xc2,0x32,0xf6,0x35,0x17,0x2}}
+};
+static const struct opl4_region regions_64[] = { /* Brightness */
+	{0x00, 0x7f, {0x137,5285,100, 0,2,0x00,0xbe,0x2a,0xa5,0x18,0x08,0x0}},
+	{0x15, 0x6c, {0x02a,3481,100, 0,1,0x00,0xc8,0x29,0x80,0x05,0x05,0x0}}
+};
+static const struct opl4_region regions_65[] = { /* Goblins */
+	{0x15, 0x6c, {0x002,4501,100,-1,2,0x00,0xca,0x2a,0x40,0x01,0x05,0x0}},
+	{0x15, 0x6c, {0x009,9679, 20, 1,4,0x00,0x3c,0x0c,0x22,0x11,0x06,0x0}}
+};
+static const struct opl4_region regions_66[] = { /* Echoes */
+	{0x15, 0x6c, {0x02a,3487,100, 0,3,0x00,0xae,0x2b,0xf5,0x21,0x06,0x0}},
+	{0x00, 0x7f, {0x124,4027,100, 0,3,0x00,0xae,0x2b,0x85,0x23,0x07,0x0}}
+};
+static const struct opl4_region regions_67[] = { /* Sci-Fi */
+	{0x15, 0x31, {0x00c,6940,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
+	{0x32, 0x38, {0x00d,5413,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
+	{0x39, 0x47, {0x00e,4382,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
+	{0x48, 0x6c, {0x00f,2846,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
+	{0x15, 0x6c, {0x002,4498,100, 0,2,0x00,0xd4,0x22,0x80,0x01,0x05,0x0}}
+};
+static const struct opl4_region regions_68[] = { /* Sitar */
+	{0x00, 0x7f, {0x10f,4408,100, 0,2,0x00,0xc4,0x32,0xf4,0x15,0x16,0x1}}
+};
+static const struct opl4_region regions_69[] = { /* Banjo */
+	{0x15, 0x34, {0x013,5685,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
+	{0x35, 0x38, {0x014,5009,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
+	{0x39, 0x3c, {0x012,4520,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
+	{0x3d, 0x44, {0x015,3622,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
+	{0x45, 0x4c, {0x017,2661,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
+	{0x4d, 0x6d, {0x016,1632,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}}
+};
+static const struct opl4_region regions_6a[] = { /* Shamisen */
+	{0x15, 0x6c, {0x10e,3273,100, 0,0,0x00,0xc0,0x28,0xf7,0x76,0x08,0x0}}
+};
+static const struct opl4_region regions_6b[] = { /* Koto */
+	{0x00, 0x7f, {0x0a9,4033,100, 0,0,0x00,0xc6,0x20,0xf0,0x06,0x07,0x0}}
+};
+static const struct opl4_region regions_6c[] = { /* Kalimba */
+	{0x00, 0x7f, {0x137,3749,100, 0,0,0x00,0xce,0x38,0xf5,0x18,0x08,0x0}}
+};
+static const struct opl4_region regions_6d[] = { /* Bagpipe */
+	{0x15, 0x39, {0x0a4,7683,100, 0,4,0x00,0xc0,0x1c,0xf0,0x00,0x09,0x0}},
+	{0x15, 0x39, {0x0a7,7680,100, 0,1,0x00,0xaa,0x19,0xf0,0x00,0x09,0x0}},
+	{0x3a, 0x6c, {0x0a8,3697,100, 0,1,0x00,0xaa,0x19,0xf0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_6e[] = { /* Fiddle */
+	{0x15, 0x3a, {0x105,5158,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x3b, 0x3f, {0x102,4754,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x40, 0x41, {0x106,4132,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x42, 0x44, {0x107,4033,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x45, 0x47, {0x108,3580,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x48, 0x4a, {0x10a,2957,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x4b, 0x4c, {0x10b,2724,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x4d, 0x4e, {0x10c,2530,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x4f, 0x51, {0x10d,2166,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
+	{0x52, 0x6c, {0x109,1825,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}
+};
+static const struct opl4_region regions_6f[] = { /* Shanai */
+	{0x15, 0x6c, {0x041,6946,100, 0,1,0x00,0xc4,0x31,0x95,0x20,0x09,0x0}}
+};
+static const struct opl4_region regions_70[] = { /* Tinkle Bell */
+	{0x15, 0x73, {0x0f3,1821,100, 0,3,0x00,0xc8,0x3b,0xd6,0x25,0x25,0x0}},
+	{0x00, 0x7f, {0x137,5669,100, 0,3,0x00,0x66,0x3b,0xf5,0x18,0x08,0x0}}
+};
+static const struct opl4_region regions_71[] = { /* Agogo */
+	{0x15, 0x74, {0x00b,2474,100, 0,0,0x00,0xd2,0x38,0xf0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_72[] = { /* Steel Drums */
+	{0x01, 0x7f, {0x0fe,3670,100, 0,0,0x00,0xca,0x38,0xf3,0x06,0x17,0x1}},
+	{0x15, 0x6c, {0x100,9602,100, 0,0,0x00,0x54,0x38,0xb0,0x05,0x16,0x1}}
+};
+static const struct opl4_region regions_73[] = { /* Woodblock */
+	{0x15, 0x6c, {0x02c,2963, 50, 0,0,0x07,0xd4,0x00,0xf0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_74[] = { /* Taiko Drum */
+	{0x13, 0x6c, {0x03e,1194, 50, 0,0,0x00,0xaa,0x38,0xf0,0x04,0x04,0x0}}
+};
+static const struct opl4_region regions_75[] = { /* Melodic Tom */
+	{0x15, 0x6c, {0x0c7,6418, 50, 0,0,0x00,0xe4,0x38,0xf0,0x05,0x01,0x0}}
+};
+static const struct opl4_region regions_76[] = { /* Synth Drum */
+	{0x15, 0x6c, {0x026,3898, 50, 0,0,0x00,0xd0,0x38,0xf0,0x04,0x04,0x0}}
+};
+static const struct opl4_region regions_77[] = { /* Reverse Cymbal */
+	{0x15, 0x6c, {0x031,4138, 50, 0,0,0x00,0xfe,0x38,0x3a,0xf0,0x09,0x0}}
+};
+static const struct opl4_region regions_78[] = { /* Guitar Fret Noise */
+	{0x15, 0x6c, {0x138,5266,100, 0,0,0x00,0xa0,0x38,0xf0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_79[] = { /* Breath Noise */
+	{0x01, 0x7f, {0x125,4269,100, 0,0,0x1e,0xd0,0x38,0xf0,0x00,0x09,0x0}}
+};
+static const struct opl4_region regions_7a[] = { /* Seashore */
+	{0x15, 0x6c, {0x008,2965, 20,-2,0,0x00,0xfe,0x00,0x20,0x03,0x04,0x0}},
+	{0x01, 0x7f, {0x037,4394, 20, 2,0,0x14,0xfe,0x00,0x20,0x04,0x05,0x0}}
+};
+static const struct opl4_region regions_7b[] = { /* Bird Tweet */
+	{0x15, 0x6c, {0x009,8078,  5,-4,7,0x00,0xc2,0x0f,0x22,0x12,0x07,0x0}},
+	{0x15, 0x6c, {0x009,3583,  5, 4,5,0x00,0xae,0x15,0x72,0x12,0x07,0x0}}
+};
+static const struct opl4_region regions_7c[] = { /* Telephone Ring */
+	{0x15, 0x6c, {0x003,3602, 10, 0,0,0x00,0xce,0x00,0xf0,0x00,0x0f,0x0}}
+};
+static const struct opl4_region regions_7d[] = { /* Helicopter */
+	{0x0c, 0x7f, {0x001,2965, 10,-2,0,0x00,0xe0,0x08,0x30,0x01,0x07,0x0}},
+	{0x01, 0x7f, {0x037,4394, 10, 2,0,0x44,0x76,0x00,0x30,0x01,0x07,0x0}}
+};
+static const struct opl4_region regions_7e[] = { /* Applause */
+	{0x15, 0x6c, {0x036,8273, 20,-6,7,0x00,0xc4,0x0f,0x70,0x01,0x05,0x0}},
+	{0x15, 0x6c, {0x036,8115,  5, 6,7,0x00,0xc6,0x07,0x70,0x01,0x05,0x0}}
+};
+static const struct opl4_region regions_7f[] = { /* Gun Shot */
+	{0x15, 0x6c, {0x139,2858, 20, 0,0,0x00,0xbe,0x38,0xf0,0x03,0x00,0x0}}
+};
+static const struct opl4_region regions_drums[] = {
+	{0x18, 0x18, {0x0cb,6397,100, 3,0,0x00,0xf4,0x38,0xc9,0x1c,0x0c,0x0}},
+	{0x19, 0x19, {0x0c4,3714,100, 0,0,0x00,0xe0,0x00,0x97,0x19,0x09,0x0}},
+	{0x1a, 0x1a, {0x0c4,3519,100, 0,0,0x00,0xea,0x00,0x61,0x01,0x07,0x0}},
+	{0x1b, 0x1b, {0x0c4,3586,100, 0,0,0x00,0xea,0x00,0xf7,0x19,0x09,0x0}},
+	{0x1c, 0x1c, {0x0c4,3586,100, 0,0,0x00,0xea,0x00,0x81,0x01,0x07,0x0}},
+	{0x1e, 0x1e, {0x0c3,4783,100, 0,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x1f, 0x1f, {0x0d1,4042,100, 0,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}},
+	{0x20, 0x20, {0x0d2,5943,100, 0,0,0x00,0xcc,0x00,0xf0,0x00,0x09,0x0}},
+	{0x21, 0x21, {0x011,3842,100, 0,0,0x00,0xea,0x00,0xf0,0x16,0x06,0x0}},
+	{0x23, 0x23, {0x011,4098,100, 0,0,0x00,0xea,0x00,0xf0,0x16,0x06,0x0}},
+	{0x24, 0x24, {0x011,4370,100, 0,0,0x00,0xea,0x00,0xf0,0x00,0x06,0x0}},
+	{0x25, 0x25, {0x0d2,4404,100, 0,0,0x00,0xd6,0x00,0xf0,0x00,0x06,0x0}},
+	{0x26, 0x26, {0x0d1,4298,100, 0,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}},
+	{0x27, 0x27, {0x00a,4403,100,-1,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
+	{0x28, 0x28, {0x0d1,4554,100, 0,0,0x00,0xdc,0x00,0xf0,0x07,0x07,0x0}},
+	{0x29, 0x29, {0x0c8,4242,100,-4,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}},
+	{0x2a, 0x2a, {0x079,6160,100, 2,0,0x00,0xe0,0x00,0xf5,0x19,0x09,0x0}},
+	{0x2b, 0x2b, {0x0c8,4626,100,-3,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}},
+	{0x2c, 0x2c, {0x07b,6039,100, 2,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
+	{0x2d, 0x2d, {0x0c8,5394,100,-2,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}},
+	{0x2e, 0x2e, {0x07a,5690,100, 2,0,0x00,0xd6,0x00,0xf0,0x00,0x05,0x0}},
+	{0x2f, 0x2f, {0x0c7,5185,100, 2,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}},
+	{0x30, 0x30, {0x0c7,5650,100, 3,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}},
+	{0x31, 0x31, {0x031,4395,100, 2,0,0x00,0xea,0x00,0xf0,0x05,0x05,0x0}},
+	{0x32, 0x32, {0x0c7,6162,100, 4,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}},
+	{0x33, 0x33, {0x02e,4391,100,-2,0,0x00,0xea,0x00,0xf0,0x05,0x05,0x0}},
+	{0x34, 0x34, {0x07a,3009,100,-2,0,0x00,0xea,0x00,0xf2,0x15,0x05,0x0}},
+	{0x35, 0x35, {0x021,4522,100,-3,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}},
+	{0x36, 0x36, {0x025,5163,100, 1,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
+	{0x37, 0x37, {0x031,5287,100,-1,0,0x00,0xea,0x00,0xf5,0x16,0x06,0x0}},
+	{0x38, 0x38, {0x01d,4395,100, 2,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
+	{0x39, 0x39, {0x031,4647,100,-2,0,0x00,0xea,0x00,0xf4,0x16,0x06,0x0}},
+	{0x3a, 0x3a, {0x09d,4426,100,-4,0,0x00,0xe0,0x00,0xf4,0x17,0x07,0x0}},
+	{0x3b, 0x3b, {0x02e,4659,100,-2,0,0x00,0xea,0x00,0xf0,0x06,0x06,0x0}},
+	{0x3c, 0x3c, {0x01c,4769,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x3d, 0x3d, {0x01c,4611,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x3e, 0x3e, {0x01e,4402,100,-3,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x3f, 0x3f, {0x01f,4387,100,-3,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x40, 0x40, {0x01f,3983,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x41, 0x41, {0x09c,4526,100, 2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x42, 0x42, {0x09c,4016,100, 2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x43, 0x43, {0x00b,4739,100,-4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x44, 0x44, {0x00b,4179,100,-4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x45, 0x45, {0x02f,4787,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
+	{0x46, 0x46, {0x030,4665,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
+	{0x47, 0x47, {0x144,4519,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x0b,0x0}},
+	{0x48, 0x48, {0x144,4111,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x0b,0x0}},
+	{0x49, 0x49, {0x024,6408,100, 3,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4a, 0x4a, {0x024,4144,100, 3,0,0x00,0xcc,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4b, 0x4b, {0x020,4001,100, 2,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4c, 0x4c, {0x02c,4402,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4d, 0x4d, {0x02c,3612,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4e, 0x4e, {0x022,4129,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x4f, 0x4f, {0x023,4147,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
+	{0x50, 0x50, {0x032,4412,100,-4,0,0x00,0xd6,0x00,0xf0,0x08,0x09,0x0}},
+	{0x51, 0x51, {0x032,4385,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
+	{0x52, 0x52, {0x02f,5935,100,-1,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}}
+};
+
+#define REGION(num) { ARRAY_SIZE(regions ## num), regions ## num }
+const struct opl4_region_ptr snd_yrw801_regions[0x81] = {
+	REGION(_00), REGION(_01), REGION(_02), REGION(_03),
+	REGION(_04), REGION(_05), REGION(_06), REGION(_07),
+	REGION(_08), REGION(_09), REGION(_0a), REGION(_0b),
+	REGION(_0c), REGION(_0d), REGION(_0e), REGION(_0f),
+	REGION(_10), REGION(_11), REGION(_12), REGION(_13),
+	REGION(_14), REGION(_15), REGION(_16), REGION(_17),
+	REGION(_18), REGION(_19), REGION(_1a), REGION(_1b),
+	REGION(_1c), REGION(_1d), REGION(_1e), REGION(_1f),
+	REGION(_20), REGION(_21), REGION(_22), REGION(_23),
+	REGION(_24), REGION(_25), REGION(_26), REGION(_27),
+	REGION(_28), REGION(_29), REGION(_2a), REGION(_2b),
+	REGION(_2c), REGION(_2d), REGION(_2e), REGION(_2f),
+	REGION(_30), REGION(_31), REGION(_32), REGION(_33),
+	REGION(_34), REGION(_35), REGION(_36), REGION(_37),
+	REGION(_38), REGION(_39), REGION(_3a), REGION(_3b),
+	REGION(_3c), REGION(_3d), REGION(_3e), REGION(_3f),
+	REGION(_40), REGION(_41), REGION(_42), REGION(_43),
+	REGION(_44), REGION(_45), REGION(_46), REGION(_47),
+	REGION(_48), REGION(_49), REGION(_4a), REGION(_4b),
+	REGION(_4c), REGION(_4d), REGION(_4e), REGION(_4f),
+	REGION(_50), REGION(_51), REGION(_52), REGION(_53),
+	REGION(_54), REGION(_55), REGION(_56), REGION(_57),
+	REGION(_58), REGION(_59), REGION(_5a), REGION(_5b),
+	REGION(_5c), REGION(_5d), REGION(_5e), REGION(_5f),
+	REGION(_60), REGION(_61), REGION(_62), REGION(_63),
+	REGION(_64), REGION(_65), REGION(_66), REGION(_67),
+	REGION(_68), REGION(_69), REGION(_6a), REGION(_6b),
+	REGION(_6c), REGION(_6d), REGION(_6e), REGION(_6f),
+	REGION(_70), REGION(_71), REGION(_72), REGION(_73),
+	REGION(_74), REGION(_75), REGION(_76), REGION(_77),
+	REGION(_78), REGION(_79), REGION(_7a), REGION(_7b),
+	REGION(_7c), REGION(_7d), REGION(_7e), REGION(_7f),
+	REGION(_drums)
+};
diff --git a/linux/sound/drivers/pcm-indirect2.c b/linux/sound/drivers/pcm-indirect2.c
new file mode 100644
index 0000000..3c93c23
--- /dev/null
+++ b/linux/sound/drivers/pcm-indirect2.c
@@ -0,0 +1,573 @@
+/*
+ * Helper functions for indirect PCM data transfer to a simple FIFO in
+ * hardware (small, no possibility to read "hardware io position",
+ * updating position done by interrupt, ...)
+ *
+ *  Copyright (c) by 2007  Joachim Foerster <JOFT@gmx.de>
+ *
+ *  Based on "pcm-indirect.h" (alsa-driver-1.0.13) by
+ *
+ *  Copyright (c) by 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* snd_printk/d() */
+#include <sound/core.h>
+/* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t
+ * snd_pcm_period_elapsed() */
+#include <sound/pcm.h>
+
+#include "pcm-indirect2.h"
+
+#ifdef SND_PCM_INDIRECT2_STAT
+/* jiffies */
+#include <linux/jiffies.h>
+
+void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream,
+			    struct snd_pcm_indirect2 *rec)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int i;
+	int j;
+	int k;
+	int seconds = (rec->lastbytetime - rec->firstbytetime) / HZ;
+
+	snd_printk(KERN_DEBUG "STAT: mul_elapsed: %u, mul_elapsed_real: %d, "
+		   "irq_occured: %d\n",
+		   rec->mul_elapsed, rec->mul_elapsed_real, rec->irq_occured);
+	snd_printk(KERN_DEBUG "STAT: min_multiple: %d (irqs/period)\n",
+		   rec->min_multiple);
+	snd_printk(KERN_DEBUG "STAT: firstbytetime: %lu, lastbytetime: %lu, "
+		   "firstzerotime: %lu\n",
+		 rec->firstbytetime, rec->lastbytetime, rec->firstzerotime);
+	snd_printk(KERN_DEBUG "STAT: bytes2hw: %u Bytes => (by runtime->rate) "
+		   "length: %d s\n",
+		 rec->bytes2hw, rec->bytes2hw / 2 / 2 / runtime->rate);
+	snd_printk(KERN_DEBUG "STAT: (by measurement) length: %d => "
+		   "rate: %d Bytes/s = %d Frames/s|Hz\n",
+		   seconds, rec->bytes2hw / seconds,
+		   rec->bytes2hw / 2 / 2 / seconds);
+	snd_printk(KERN_DEBUG
+		   "STAT: zeros2hw: %u = %d ms ~ %d * %d zero copies\n",
+		   rec->zeros2hw, ((rec->zeros2hw / 2 / 2) * 1000) /
+		   runtime->rate,
+		   rec->zeros2hw / (rec->hw_buffer_size / 2),
+		   (rec->hw_buffer_size / 2));
+	snd_printk(KERN_DEBUG "STAT: pointer_calls: %u, lastdifftime: %u\n",
+		   rec->pointer_calls, rec->lastdifftime);
+	snd_printk(KERN_DEBUG "STAT: sw_io: %d, sw_data: %d\n", rec->sw_io,
+		   rec->sw_data);
+	snd_printk(KERN_DEBUG "STAT: byte_sizes[]:\n");
+	k = 0;
+	for (j = 0; j < 8; j++) {
+		for (i = j * 8; i < (j + 1) * 8; i++)
+			if (rec->byte_sizes[i] != 0) {
+				snd_printk(KERN_DEBUG "%u: %u",
+					   i, rec->byte_sizes[i]);
+				k++;
+			}
+		if (((k % 8) == 0) && (k != 0)) {
+			snd_printk(KERN_DEBUG "\n");
+			k = 0;
+		}
+	}
+	snd_printk(KERN_DEBUG "\n");
+	snd_printk(KERN_DEBUG "STAT: zero_sizes[]:\n");
+	for (j = 0; j < 8; j++) {
+		k = 0;
+		for (i = j * 8; i < (j + 1) * 8; i++)
+			if (rec->zero_sizes[i] != 0)
+				snd_printk(KERN_DEBUG "%u: %u",
+					   i, rec->zero_sizes[i]);
+			else
+				k++;
+		if (!k)
+			snd_printk(KERN_DEBUG "\n");
+	}
+	snd_printk(KERN_DEBUG "\n");
+	snd_printk(KERN_DEBUG "STAT: min_adds[]:\n");
+	for (j = 0; j < 8; j++) {
+		if (rec->min_adds[j] != 0)
+			snd_printk(KERN_DEBUG "%u: %u", j, rec->min_adds[j]);
+	}
+	snd_printk(KERN_DEBUG "\n");
+	snd_printk(KERN_DEBUG "STAT: mul_adds[]:\n");
+	for (j = 0; j < 8; j++) {
+		if (rec->mul_adds[j] != 0)
+			snd_printk(KERN_DEBUG "%u: %u", j, rec->mul_adds[j]);
+	}
+	snd_printk(KERN_DEBUG "\n");
+	snd_printk(KERN_DEBUG
+		   "STAT: zero_times_saved: %d, zero_times_notsaved: %d\n",
+		   rec->zero_times_saved, rec->zero_times_notsaved);
+	/* snd_printk(KERN_DEBUG "STAT: zero_times[]\n");
+	i = 0;
+	for (j = 0; j < 3750; j++) {
+		if (rec->zero_times[j] != 0) {
+			snd_printk(KERN_DEBUG "%u: %u", j, rec->zero_times[j]);
+			i++;
+		}
+		if (((i % 8) == 0) && (i != 0))
+			snd_printk(KERN_DEBUG "\n");
+	}
+	snd_printk(KERN_DEBUG "\n"); */
+	return;
+}
+#endif
+
+/*
+ * _internal_ helper function for playback/capture transfer function
+ */
+static void
+snd_pcm_indirect2_increase_min_periods(struct snd_pcm_substream *substream,
+				       struct snd_pcm_indirect2 *rec,
+				       int isplay, int iscopy,
+				       unsigned int bytes)
+{
+	if (rec->min_periods >= 0) {
+		if (iscopy) {
+			rec->sw_io += bytes;
+			if (rec->sw_io >= rec->sw_buffer_size)
+				rec->sw_io -= rec->sw_buffer_size;
+		} else if (isplay) {
+			/* If application does not write data in multiples of
+			 * a period, move sw_data to the next correctly aligned
+			 * position, so that sw_io can converge to it (in the
+			 * next step).
+			 */
+			if (!rec->check_alignment) {
+				if (rec->bytes2hw %
+				    snd_pcm_lib_period_bytes(substream)) {
+					unsigned bytes2hw_aligned =
+					    (1 +
+					     (rec->bytes2hw /
+					      snd_pcm_lib_period_bytes
+					      (substream))) *
+					    snd_pcm_lib_period_bytes
+					    (substream);
+					rec->sw_data =
+					    bytes2hw_aligned %
+					    rec->sw_buffer_size;
+#ifdef SND_PCM_INDIRECT2_STAT
+					snd_printk(KERN_DEBUG
+						   "STAT: @re-align: aligned "
+						   "bytes2hw to next period "
+						   "size boundary: %d "
+						   "(instead of %d)\n",
+						   bytes2hw_aligned,
+						   rec->bytes2hw);
+					snd_printk(KERN_DEBUG
+						   "STAT: @re-align: sw_data "
+						   "moves to: %d\n",
+						   rec->sw_data);
+#endif
+				}
+				rec->check_alignment = 1;
+			}
+			/* We are at the end and are copying zeros into the
+			 * fifo.
+			 * Now, we have to make sure that sw_io is increased
+			 * until the position of sw_data: Filling the fifo with
+			 * the first zeros means, the last bytes were played.
+			 */
+			if (rec->sw_io != rec->sw_data) {
+				unsigned int diff;
+				if (rec->sw_data > rec->sw_io)
+					diff = rec->sw_data - rec->sw_io;
+				else
+					diff = (rec->sw_buffer_size -
+						rec->sw_io) +
+						rec->sw_data;
+				if (bytes >= diff)
+					rec->sw_io = rec->sw_data;
+				else {
+					rec->sw_io += bytes;
+					if (rec->sw_io >= rec->sw_buffer_size)
+						rec->sw_io -=
+						    rec->sw_buffer_size;
+				}
+			}
+		}
+		rec->min_period_count += bytes;
+		if (rec->min_period_count >= (rec->hw_buffer_size / 2)) {
+			rec->min_periods += (rec->min_period_count /
+					     (rec->hw_buffer_size / 2));
+#ifdef SND_PCM_INDIRECT2_STAT
+			if ((rec->min_period_count /
+			     (rec->hw_buffer_size / 2)) > 7)
+				snd_printk(KERN_DEBUG
+					   "STAT: more than 7 (%d) min_adds "
+					   "at once - too big to save!\n",
+					   (rec->min_period_count /
+					    (rec->hw_buffer_size / 2)));
+			else
+				rec->min_adds[(rec->min_period_count /
+					       (rec->hw_buffer_size / 2))]++;
+#endif
+			rec->min_period_count = (rec->min_period_count %
+						 (rec->hw_buffer_size / 2));
+		}
+	} else if (isplay && iscopy)
+		rec->min_periods = 0;
+}
+
+/*
+ * helper function for playback/capture pointer callback
+ */
+snd_pcm_uframes_t
+snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream,
+			  struct snd_pcm_indirect2 *rec)
+{
+#ifdef SND_PCM_INDIRECT2_STAT
+	rec->pointer_calls++;
+#endif
+	return bytes_to_frames(substream->runtime, rec->sw_io);
+}
+
+/*
+ * _internal_ helper function for playback interrupt callback
+ */
+static void
+snd_pcm_indirect2_playback_transfer(struct snd_pcm_substream *substream,
+				    struct snd_pcm_indirect2 *rec,
+				    snd_pcm_indirect2_copy_t copy,
+				    snd_pcm_indirect2_zero_t zero)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
+
+	/* runtime->control->appl_ptr: position where ALSA will write next time
+	 * rec->appl_ptr: position where ALSA was last time
+	 * diff: obviously ALSA wrote that much bytes into the intermediate
+	 * buffer since we checked last time
+	 */
+	snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;
+
+	if (diff) {
+#ifdef SND_PCM_INDIRECT2_STAT
+		rec->lastdifftime = jiffies;
+#endif
+		if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
+			diff += runtime->boundary;
+		/* number of bytes "added" by ALSA increases the number of
+		 * bytes which are ready to "be transfered to HW"/"played"
+		 * Then, set rec->appl_ptr to not count bytes twice next time.
+		 */
+		rec->sw_ready += (int)frames_to_bytes(runtime, diff);
+		rec->appl_ptr = appl_ptr;
+	}
+	if (rec->hw_ready && (rec->sw_ready <= 0)) {
+		unsigned int bytes;
+
+#ifdef SND_PCM_INDIRECT2_STAT
+		if (rec->firstzerotime == 0) {
+			rec->firstzerotime = jiffies;
+			snd_printk(KERN_DEBUG
+				   "STAT: @firstzerotime: mul_elapsed: %d, "
+				   "min_period_count: %d\n",
+				   rec->mul_elapsed, rec->min_period_count);
+			snd_printk(KERN_DEBUG
+				   "STAT: @firstzerotime: sw_io: %d, "
+				   "sw_data: %d, appl_ptr: %u\n",
+				   rec->sw_io, rec->sw_data,
+				   (unsigned int)appl_ptr);
+		}
+		if ((jiffies - rec->firstzerotime) < 3750) {
+			rec->zero_times[(jiffies - rec->firstzerotime)]++;
+			rec->zero_times_saved++;
+		} else
+			rec->zero_times_notsaved++;
+#endif
+		bytes = zero(substream, rec);
+
+#ifdef SND_PCM_INDIRECT2_STAT
+		rec->zeros2hw += bytes;
+		if (bytes < 64)
+			rec->zero_sizes[bytes]++;
+		else
+			snd_printk(KERN_DEBUG
+				   "STAT: %d zero Bytes copied to hardware at "
+				   "once - too big to save!\n",
+				   bytes);
+#endif
+		snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 0,
+						       bytes);
+		return;
+	}
+	while (rec->hw_ready && (rec->sw_ready > 0)) {
+		/* sw_to_end: max. number of bytes that can be read/take from
+		 * the current position (sw_data) in _one_ step
+		 */
+		unsigned int sw_to_end = rec->sw_buffer_size - rec->sw_data;
+
+		/* bytes: number of bytes we have available (for reading) */
+		unsigned int bytes = rec->sw_ready;
+
+		if (sw_to_end < bytes)
+			bytes = sw_to_end;
+		if (!bytes)
+			break;
+
+#ifdef SND_PCM_INDIRECT2_STAT
+		if (rec->firstbytetime == 0)
+			rec->firstbytetime = jiffies;
+		rec->lastbytetime = jiffies;
+#endif
+		/* copy bytes from intermediate buffer position sw_data to the
+		 * HW and return number of bytes actually written
+		 * Furthermore, set hw_ready to 0, if the fifo isn't empty
+		 * now => more could be transfered to fifo
+		 */
+		bytes = copy(substream, rec, bytes);
+		rec->bytes2hw += bytes;
+
+#ifdef SND_PCM_INDIRECT2_STAT
+		if (bytes < 64)
+			rec->byte_sizes[bytes]++;
+		else
+			snd_printk(KERN_DEBUG
+				   "STAT: %d Bytes copied to hardware at once "
+				   "- too big to save!\n",
+				   bytes);
+#endif
+		/* increase sw_data by the number of actually written bytes
+		 * (= number of taken bytes from intermediate buffer)
+		 */
+		rec->sw_data += bytes;
+		if (rec->sw_data == rec->sw_buffer_size)
+			rec->sw_data = 0;
+		/* now sw_data is the position where ALSA is going to write
+		 * in the intermediate buffer next time = position we are going
+		 * to read from next time
+		 */
+
+		snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 1,
+						       bytes);
+
+		/* we read bytes from intermediate buffer, so we need to say
+		 * that the number of bytes ready for transfer are decreased
+		 * now
+		 */
+		rec->sw_ready -= bytes;
+	}
+	return;
+}
+
+/*
+ * helper function for playback interrupt routine
+ */
+void
+snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream,
+				     struct snd_pcm_indirect2 *rec,
+				     snd_pcm_indirect2_copy_t copy,
+				     snd_pcm_indirect2_zero_t zero)
+{
+#ifdef SND_PCM_INDIRECT2_STAT
+	rec->irq_occured++;
+#endif
+	/* hardware played some bytes, so there is room again (in fifo) */
+	rec->hw_ready = 1;
+
+	/* don't call ack() now, instead call transfer() function directly
+	 * (normally called by ack() )
+	 */
+	snd_pcm_indirect2_playback_transfer(substream, rec, copy, zero);
+
+	if (rec->min_periods >= rec->min_multiple) {
+#ifdef SND_PCM_INDIRECT2_STAT
+		if ((rec->min_periods / rec->min_multiple) > 7)
+			snd_printk(KERN_DEBUG
+				   "STAT: more than 7 (%d) mul_adds - too big "
+				   "to save!\n",
+				   (rec->min_periods / rec->min_multiple));
+		else
+			rec->mul_adds[(rec->min_periods /
+				       rec->min_multiple)]++;
+		rec->mul_elapsed_real += (rec->min_periods /
+					  rec->min_multiple);
+		rec->mul_elapsed++;
+#endif
+		rec->min_periods = (rec->min_periods % rec->min_multiple);
+		snd_pcm_period_elapsed(substream);
+	}
+}
+
+/*
+ * _internal_ helper function for capture interrupt callback
+ */
+static void
+snd_pcm_indirect2_capture_transfer(struct snd_pcm_substream *substream,
+				   struct snd_pcm_indirect2 *rec,
+				   snd_pcm_indirect2_copy_t copy,
+				   snd_pcm_indirect2_zero_t null)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
+	snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;
+
+	if (diff) {
+#ifdef SND_PCM_INDIRECT2_STAT
+		rec->lastdifftime = jiffies;
+#endif
+		if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
+			diff += runtime->boundary;
+		rec->sw_ready -= frames_to_bytes(runtime, diff);
+		rec->appl_ptr = appl_ptr;
+	}
+	/* if hardware has something, but the intermediate buffer is full
+	 * => skip contents of buffer
+	 */
+	if (rec->hw_ready && (rec->sw_ready >= (int)rec->sw_buffer_size)) {
+		unsigned int bytes;
+
+#ifdef SND_PCM_INDIRECT2_STAT
+		if (rec->firstzerotime == 0) {
+			rec->firstzerotime = jiffies;
+			snd_printk(KERN_DEBUG "STAT: (capture) "
+				   "@firstzerotime: mul_elapsed: %d, "
+				   "min_period_count: %d\n",
+				   rec->mul_elapsed, rec->min_period_count);
+			snd_printk(KERN_DEBUG "STAT: (capture) "
+				   "@firstzerotime: sw_io: %d, sw_data: %d, "
+				   "appl_ptr: %u\n",
+				   rec->sw_io, rec->sw_data,
+				   (unsigned int)appl_ptr);
+		}
+		if ((jiffies - rec->firstzerotime) < 3750) {
+			rec->zero_times[(jiffies - rec->firstzerotime)]++;
+			rec->zero_times_saved++;
+		} else
+			rec->zero_times_notsaved++;
+#endif
+		bytes = null(substream, rec);
+
+#ifdef SND_PCM_INDIRECT2_STAT
+		rec->zeros2hw += bytes;
+		if (bytes < 64)
+			rec->zero_sizes[bytes]++;
+		else
+			snd_printk(KERN_DEBUG
+				   "STAT: (capture) %d zero Bytes copied to "
+				   "hardware at once - too big to save!\n",
+				   bytes);
+#endif
+		snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 0,
+						       bytes);
+		/* report an overrun */
+		rec->sw_io = SNDRV_PCM_POS_XRUN;
+		return;
+	}
+	while (rec->hw_ready && (rec->sw_ready < (int)rec->sw_buffer_size)) {
+		/* sw_to_end: max. number of bytes that we can write to the
+		 *  intermediate buffer (until it's end)
+		 */
+		size_t sw_to_end = rec->sw_buffer_size - rec->sw_data;
+
+		/* bytes: max. number of bytes, which may be copied to the
+		 *  intermediate buffer without overflow (in _one_ step)
+		 */
+		size_t bytes = rec->sw_buffer_size - rec->sw_ready;
+
+		/* limit number of bytes (for transfer) by available room in
+		 * the intermediate buffer
+		 */
+		if (sw_to_end < bytes)
+			bytes = sw_to_end;
+		if (!bytes)
+			break;
+
+#ifdef SND_PCM_INDIRECT2_STAT
+		if (rec->firstbytetime == 0)
+			rec->firstbytetime = jiffies;
+		rec->lastbytetime = jiffies;
+#endif
+		/* copy bytes from the intermediate buffer (position sw_data)
+		 * to the HW at most and return number of bytes actually copied
+		 * from HW
+		 * Furthermore, set hw_ready to 0, if the fifo is empty now.
+		 */
+		bytes = copy(substream, rec, bytes);
+		rec->bytes2hw += bytes;
+
+#ifdef SND_PCM_INDIRECT2_STAT
+		if (bytes < 64)
+			rec->byte_sizes[bytes]++;
+		else
+			snd_printk(KERN_DEBUG
+				   "STAT: (capture) %d Bytes copied to "
+				   "hardware at once - too big to save!\n",
+				   bytes);
+#endif
+		/* increase sw_data by the number of actually copied bytes from
+		 * HW
+		 */
+		rec->sw_data += bytes;
+		if (rec->sw_data == rec->sw_buffer_size)
+			rec->sw_data = 0;
+
+		snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 1,
+						       bytes);
+
+		/* number of bytes in the intermediate buffer, which haven't
+		 * been fetched by ALSA yet.
+		 */
+		rec->sw_ready += bytes;
+	}
+	return;
+}
+
+/*
+ * helper function for capture interrupt routine
+ */
+void
+snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream,
+				    struct snd_pcm_indirect2 *rec,
+				    snd_pcm_indirect2_copy_t copy,
+				    snd_pcm_indirect2_zero_t null)
+{
+#ifdef SND_PCM_INDIRECT2_STAT
+	rec->irq_occured++;
+#endif
+	/* hardware recorded some bytes, so there is something to read from the
+	 * record fifo:
+	 */
+	rec->hw_ready = 1;
+
+	/* don't call ack() now, instead call transfer() function directly
+	 * (normally called by ack() )
+	 */
+	snd_pcm_indirect2_capture_transfer(substream, rec, copy, null);
+
+	if (rec->min_periods >= rec->min_multiple) {
+
+#ifdef SND_PCM_INDIRECT2_STAT
+		if ((rec->min_periods / rec->min_multiple) > 7)
+			snd_printk(KERN_DEBUG
+				   "STAT: more than 7 (%d) mul_adds - "
+				   "too big to save!\n",
+				   (rec->min_periods / rec->min_multiple));
+		else
+			rec->mul_adds[(rec->min_periods /
+				       rec->min_multiple)]++;
+		rec->mul_elapsed_real += (rec->min_periods /
+					  rec->min_multiple);
+		rec->mul_elapsed++;
+#endif
+		rec->min_periods = (rec->min_periods % rec->min_multiple);
+		snd_pcm_period_elapsed(substream);
+	}
+}
diff --git a/linux/sound/drivers/pcm-indirect2.h b/linux/sound/drivers/pcm-indirect2.h
new file mode 100644
index 0000000..2ea6e46
--- /dev/null
+++ b/linux/sound/drivers/pcm-indirect2.h
@@ -0,0 +1,140 @@
+/*
+ * Helper functions for indirect PCM data transfer to a simple FIFO in
+ * hardware (small, no possibility to read "hardware io position",
+ * updating position done by interrupt, ...)
+ *
+ *  Copyright (c) by 2007  Joachim Foerster <JOFT@gmx.de>
+ *
+ *  Based on "pcm-indirect.h" (alsa-driver-1.0.13) by
+ *
+ *  Copyright (c) by 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __SOUND_PCM_INDIRECT2_H
+#define __SOUND_PCM_INDIRECT2_H
+
+/* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t */
+#include <sound/pcm.h>
+
+/* Debug options for code which may be removed completely in a final version */
+#ifdef CONFIG_SND_DEBUG
+#define SND_PCM_INDIRECT2_STAT    /* turn on some "statistics" about the
+				   * process of copying bytes from the
+				   * intermediate buffer to the hardware
+				   * fifo and the other way round
+				   */
+#endif
+
+struct snd_pcm_indirect2 {
+	unsigned int hw_buffer_size;  /* Byte size of hardware buffer */
+	int hw_ready;		      /* playback: 1 = hw fifo has room left,
+				       * 0 = hw fifo is full
+				       */
+	unsigned int min_multiple;
+	int min_periods;	      /* counts number of min. periods until
+				       * min_multiple is reached
+				       */
+	int min_period_count;	      /* counts bytes to count number of
+				       * min. periods
+				       */
+
+	unsigned int sw_buffer_size;  /* Byte size of software buffer */
+
+	/* sw_data: position in intermediate buffer, where we will read (or
+	 *          write) from/to next time (to transfer data to/from HW)
+	 */
+	unsigned int sw_data;         /* Offset to next dst (or src) in sw
+				       * ring buffer
+				       */
+	/* easiest case (playback):
+	 * sw_data is nearly the same as ~ runtime->control->appl_ptr, with the
+	 * exception that sw_data is "behind" by the number if bytes ALSA wrote
+	 * to the intermediate buffer last time.
+	 * A call to ack() callback synchronizes both indirectly.
+	 */
+
+	/* We have no real sw_io pointer here. Usually sw_io is pointing to the
+	 * current playback/capture position _inside_ the hardware. Devices
+	 * with plain FIFOs often have no possibility to publish this position.
+	 * So we say: if sw_data is updated, that means bytes were copied to
+	 * the hardware, we increase sw_io by that amount, because there have
+	 * to be as much bytes which were played. So sw_io will stay behind
+	 * sw_data all the time and has to converge to sw_data at the end of
+	 * playback.
+	 */
+	unsigned int sw_io;           /* Current software pointer in bytes */
+
+	/* sw_ready: number of bytes ALSA copied to the intermediate buffer, so
+	 * it represents the number of bytes which wait for transfer to the HW
+	 */
+	int sw_ready;		  /* Bytes ready to be transferred to/from hw */
+
+	/* appl_ptr: last known position of ALSA (where ALSA is going to write
+	 * next time into the intermediate buffer
+	 */
+	snd_pcm_uframes_t appl_ptr;   /* Last seen appl_ptr */
+
+	unsigned int bytes2hw;
+	int check_alignment;
+
+#ifdef SND_PCM_INDIRECT2_STAT
+	unsigned int zeros2hw;
+	unsigned int mul_elapsed;
+	unsigned int mul_elapsed_real;
+	unsigned long firstbytetime;
+	unsigned long lastbytetime;
+	unsigned long firstzerotime;
+	unsigned int byte_sizes[64];
+	unsigned int zero_sizes[64];
+	unsigned int min_adds[8];
+	unsigned int mul_adds[8];
+	unsigned int zero_times[3750];	/* = 15s */
+	unsigned int zero_times_saved;
+	unsigned int zero_times_notsaved;
+	unsigned int irq_occured;
+	unsigned int pointer_calls;
+	unsigned int lastdifftime;
+#endif
+};
+
+typedef size_t (*snd_pcm_indirect2_copy_t) (struct snd_pcm_substream *substream,
+					   struct snd_pcm_indirect2 *rec,
+					   size_t bytes);
+typedef size_t (*snd_pcm_indirect2_zero_t) (struct snd_pcm_substream *substream,
+					   struct snd_pcm_indirect2 *rec);
+
+#ifdef SND_PCM_INDIRECT2_STAT
+void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream,
+				   struct snd_pcm_indirect2 *rec);
+#endif
+
+snd_pcm_uframes_t
+snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream,
+			  struct snd_pcm_indirect2 *rec);
+void
+snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream,
+				     struct snd_pcm_indirect2 *rec,
+				     snd_pcm_indirect2_copy_t copy,
+				     snd_pcm_indirect2_zero_t zero);
+void
+snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream,
+				    struct snd_pcm_indirect2 *rec,
+				    snd_pcm_indirect2_copy_t copy,
+				    snd_pcm_indirect2_zero_t null);
+
+#endif /* __SOUND_PCM_INDIRECT2_H */
diff --git a/linux/sound/drivers/pcsp/Makefile b/linux/sound/drivers/pcsp/Makefile
new file mode 100644
index 0000000..b19555b
--- /dev/null
+++ b/linux/sound/drivers/pcsp/Makefile
@@ -0,0 +1,2 @@
+snd-pcsp-objs := pcsp.o pcsp_lib.o pcsp_mixer.o pcsp_input.o
+obj-$(CONFIG_SND_PCSP) += snd-pcsp.o
diff --git a/linux/sound/drivers/pcsp/pcsp.c b/linux/sound/drivers/pcsp/pcsp.c
new file mode 100644
index 0000000..f165c77
--- /dev/null
+++ b/linux/sound/drivers/pcsp/pcsp.c
@@ -0,0 +1,244 @@
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Copyright (C) 1997-2001  David Woodhouse
+ * Copyright (C) 2001-2008  Stas Sergeev
+ */
+
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <asm/bitops.h>
+#include "pcsp_input.h"
+#include "pcsp.h"
+
+MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>");
+MODULE_DESCRIPTION("PC-Speaker driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}");
+MODULE_ALIAS("platform:pcspkr");
+
+static int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+static int enable = SNDRV_DEFAULT_ENABLE1;	/* Enable this card */
+static int nopcm;	/* Disable PCM capability of the driver */
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for pcsp soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for pcsp soundcard.");
+module_param(enable, bool, 0444);
+MODULE_PARM_DESC(enable, "Enable PC-Speaker sound.");
+module_param(nopcm, bool, 0444);
+MODULE_PARM_DESC(nopcm, "Disable PC-Speaker PCM sound. Only beeps remain.");
+
+struct snd_pcsp pcsp_chip;
+
+static int __devinit snd_pcsp_create(struct snd_card *card)
+{
+	static struct snd_device_ops ops = { };
+	struct timespec tp;
+	int err;
+	int div, min_div, order;
+
+	if (!nopcm) {
+		hrtimer_get_res(CLOCK_MONOTONIC, &tp);
+		if (tp.tv_sec || tp.tv_nsec > PCSP_MAX_PERIOD_NS) {
+			printk(KERN_ERR "PCSP: Timer resolution is not sufficient "
+				"(%linS)\n", tp.tv_nsec);
+			printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI "
+				"enabled.\n");
+			printk(KERN_ERR "PCSP: Turned into nopcm mode.\n");
+			nopcm = 1;
+		}
+	}
+
+	if (loops_per_jiffy >= PCSP_MIN_LPJ && tp.tv_nsec <= PCSP_MIN_PERIOD_NS)
+		min_div = MIN_DIV;
+	else
+		min_div = MAX_DIV;
+#if PCSP_DEBUG
+	printk(KERN_DEBUG "PCSP: lpj=%li, min_div=%i, res=%li\n",
+	       loops_per_jiffy, min_div, tp.tv_nsec);
+#endif
+
+	div = MAX_DIV / min_div;
+	order = fls(div) - 1;
+
+	pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE);
+	pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE);
+	pcsp_chip.playback_ptr = 0;
+	pcsp_chip.period_ptr = 0;
+	atomic_set(&pcsp_chip.timer_active, 0);
+	pcsp_chip.enable = 1;
+	pcsp_chip.pcspkr = 1;
+
+	spin_lock_init(&pcsp_chip.substream_lock);
+
+	pcsp_chip.card = card;
+	pcsp_chip.port = 0x61;
+	pcsp_chip.irq = -1;
+	pcsp_chip.dma = -1;
+
+	/* Register device */
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, &pcsp_chip, &ops);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int __devinit snd_card_pcsp_probe(int devnum, struct device *dev)
+{
+	struct snd_card *card;
+	int err;
+
+	if (devnum != 0)
+		return -EINVAL;
+
+	hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	pcsp_chip.timer.function = pcsp_do_timer;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	err = snd_pcsp_create(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (!nopcm) {
+		err = snd_pcsp_new_pcm(&pcsp_chip);
+		if (err < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+	err = snd_pcsp_new_mixer(&pcsp_chip, nopcm);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	snd_card_set_dev(pcsp_chip.card, dev);
+
+	strcpy(card->driver, "PC-Speaker");
+	strcpy(card->shortname, "pcsp");
+	sprintf(card->longname, "Internal PC-Speaker at port 0x%x",
+		pcsp_chip.port);
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	return 0;
+}
+
+static int __devinit alsa_card_pcsp_init(struct device *dev)
+{
+	int err;
+
+	err = snd_card_pcsp_probe(0, dev);
+	if (err) {
+		printk(KERN_ERR "PC-Speaker initialization failed.\n");
+		return err;
+	}
+
+#ifdef CONFIG_DEBUG_PAGEALLOC
+	/* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */
+	printk(KERN_WARNING "PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, "
+	       "which may make the sound noisy.\n");
+#endif
+
+	return 0;
+}
+
+static void __devexit alsa_card_pcsp_exit(struct snd_pcsp *chip)
+{
+	snd_card_free(chip->card);
+}
+
+static int __devinit pcsp_probe(struct platform_device *dev)
+{
+	int err;
+
+	err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev);
+	if (err < 0)
+		return err;
+
+	err = alsa_card_pcsp_init(&dev->dev);
+	if (err < 0) {
+		pcspkr_input_remove(pcsp_chip.input_dev);
+		return err;
+	}
+
+	platform_set_drvdata(dev, &pcsp_chip);
+	return 0;
+}
+
+static int __devexit pcsp_remove(struct platform_device *dev)
+{
+	struct snd_pcsp *chip = platform_get_drvdata(dev);
+	alsa_card_pcsp_exit(chip);
+	pcspkr_input_remove(chip->input_dev);
+	platform_set_drvdata(dev, NULL);
+	return 0;
+}
+
+static void pcsp_stop_beep(struct snd_pcsp *chip)
+{
+	pcsp_sync_stop(chip);
+	pcspkr_stop_sound();
+}
+
+#ifdef CONFIG_PM
+static int pcsp_suspend(struct platform_device *dev, pm_message_t state)
+{
+	struct snd_pcsp *chip = platform_get_drvdata(dev);
+	pcsp_stop_beep(chip);
+	snd_pcm_suspend_all(chip->pcm);
+	return 0;
+}
+#else
+#define pcsp_suspend NULL
+#endif	/* CONFIG_PM */
+
+static void pcsp_shutdown(struct platform_device *dev)
+{
+	struct snd_pcsp *chip = platform_get_drvdata(dev);
+	pcsp_stop_beep(chip);
+}
+
+static struct platform_driver pcsp_platform_driver = {
+	.driver		= {
+		.name	= "pcspkr",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= pcsp_probe,
+	.remove		= __devexit_p(pcsp_remove),
+	.suspend	= pcsp_suspend,
+	.shutdown	= pcsp_shutdown,
+};
+
+static int __init pcsp_init(void)
+{
+	if (!enable)
+		return -ENODEV;
+	return platform_driver_register(&pcsp_platform_driver);
+}
+
+static void __exit pcsp_exit(void)
+{
+	platform_driver_unregister(&pcsp_platform_driver);
+}
+
+module_init(pcsp_init);
+module_exit(pcsp_exit);
diff --git a/linux/sound/drivers/pcsp/pcsp.h b/linux/sound/drivers/pcsp/pcsp.h
new file mode 100644
index 0000000..4ff6c8c
--- /dev/null
+++ b/linux/sound/drivers/pcsp/pcsp.h
@@ -0,0 +1,88 @@
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Copyright (C) 1993-1997  Michael Beck
+ * Copyright (C) 1997-2001  David Woodhouse
+ * Copyright (C) 2001-2008  Stas Sergeev
+ */
+
+#ifndef __PCSP_H__
+#define __PCSP_H__
+
+#include <linux/hrtimer.h>
+#include <linux/timex.h>
+#if defined(CONFIG_MIPS) || defined(CONFIG_X86)
+/* Use the global PIT lock ! */
+#include <asm/i8253.h>
+#else
+#include <asm/8253pit.h>
+static DEFINE_RAW_SPINLOCK(i8253_lock);
+#endif
+
+#define PCSP_SOUND_VERSION 0x400	/* read 4.00 */
+#define PCSP_DEBUG 0
+
+/* default timer freq for PC-Speaker: 18643 Hz */
+#define DIV_18KHZ 64
+#define MAX_DIV DIV_18KHZ
+#define CALC_DIV(d) (MAX_DIV >> (d))
+#define CUR_DIV() CALC_DIV(chip->treble)
+#define PCSP_MAX_TREBLE 1
+
+/* unfortunately, with hrtimers 37KHz does not work very well :( */
+#define PCSP_DEFAULT_TREBLE 0
+#define MIN_DIV (MAX_DIV >> PCSP_MAX_TREBLE)
+
+/* wild guess */
+#define PCSP_MIN_LPJ 1000000
+#define PCSP_DEFAULT_SDIV (DIV_18KHZ >> 1)
+#define PCSP_DEFAULT_SRATE (PIT_TICK_RATE / PCSP_DEFAULT_SDIV)
+#define PCSP_INDEX_INC() (1 << (PCSP_MAX_TREBLE - chip->treble))
+#define PCSP_CALC_RATE(i) (PIT_TICK_RATE / CALC_DIV(i))
+#define PCSP_RATE() PCSP_CALC_RATE(chip->treble)
+#define PCSP_MIN_RATE__1 MAX_DIV/PIT_TICK_RATE
+#define PCSP_MAX_RATE__1 MIN_DIV/PIT_TICK_RATE
+#define PCSP_MAX_PERIOD_NS (1000000000ULL * PCSP_MIN_RATE__1)
+#define PCSP_MIN_PERIOD_NS (1000000000ULL * PCSP_MAX_RATE__1)
+#define PCSP_CALC_NS(div) ({ \
+	u64 __val = 1000000000ULL * (div); \
+	do_div(__val, PIT_TICK_RATE); \
+	__val; \
+})
+#define PCSP_PERIOD_NS() PCSP_CALC_NS(CUR_DIV())
+
+#define PCSP_MAX_PERIOD_SIZE	(64*1024)
+#define PCSP_MAX_PERIODS	512
+#define PCSP_BUFFER_SIZE	(128*1024)
+
+struct snd_pcsp {
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct input_dev *input_dev;
+	struct hrtimer timer;
+	unsigned short port, irq, dma;
+	spinlock_t substream_lock;
+	struct snd_pcm_substream *playback_substream;
+	unsigned int fmt_size;
+	unsigned int is_signed;
+	size_t playback_ptr;
+	size_t period_ptr;
+	atomic_t timer_active;
+	int thalf;
+	u64 ns_rem;
+	unsigned char val61;
+	int enable;
+	int max_treble;
+	int treble;
+	int pcspkr;
+};
+
+extern struct snd_pcsp pcsp_chip;
+
+extern enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle);
+extern void pcsp_sync_stop(struct snd_pcsp *chip);
+
+extern int snd_pcsp_new_pcm(struct snd_pcsp *chip);
+extern int snd_pcsp_new_mixer(struct snd_pcsp *chip, int nopcm);
+
+#endif
diff --git a/linux/sound/drivers/pcsp/pcsp_input.c b/linux/sound/drivers/pcsp/pcsp_input.c
new file mode 100644
index 0000000..b5e2b54
--- /dev/null
+++ b/linux/sound/drivers/pcsp/pcsp_input.c
@@ -0,0 +1,116 @@
+/*
+ *  PC Speaker beeper driver for Linux
+ *
+ *  Copyright (c) 2002 Vojtech Pavlik
+ *  Copyright (c) 1992 Orest Zborowski
+ *
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation
+ */
+
+#include <linux/init.h>
+#include <linux/input.h>
+#include <asm/io.h>
+#include "pcsp.h"
+
+static void pcspkr_do_sound(unsigned int count)
+{
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&i8253_lock, flags);
+
+	if (count) {
+		/* set command for counter 2, 2 byte write */
+		outb_p(0xB6, 0x43);
+		/* select desired HZ */
+		outb_p(count & 0xff, 0x42);
+		outb((count >> 8) & 0xff, 0x42);
+		/* enable counter 2 */
+		outb_p(inb_p(0x61) | 3, 0x61);
+	} else {
+		/* disable counter 2 */
+		outb(inb_p(0x61) & 0xFC, 0x61);
+	}
+
+	raw_spin_unlock_irqrestore(&i8253_lock, flags);
+}
+
+void pcspkr_stop_sound(void)
+{
+	pcspkr_do_sound(0);
+}
+
+static int pcspkr_input_event(struct input_dev *dev, unsigned int type,
+			      unsigned int code, int value)
+{
+	unsigned int count = 0;
+
+	if (atomic_read(&pcsp_chip.timer_active) || !pcsp_chip.pcspkr)
+		return 0;
+
+	switch (type) {
+	case EV_SND:
+		switch (code) {
+		case SND_BELL:
+			if (value)
+				value = 1000;
+		case SND_TONE:
+			break;
+		default:
+			return -1;
+		}
+		break;
+
+	default:
+		return -1;
+	}
+
+	if (value > 20 && value < 32767)
+		count = PIT_TICK_RATE / value;
+
+	pcspkr_do_sound(count);
+
+	return 0;
+}
+
+int __devinit pcspkr_input_init(struct input_dev **rdev, struct device *dev)
+{
+	int err;
+
+	struct input_dev *input_dev = input_allocate_device();
+	if (!input_dev)
+		return -ENOMEM;
+
+	input_dev->name = "PC Speaker";
+	input_dev->phys = "isa0061/input0";
+	input_dev->id.bustype = BUS_ISA;
+	input_dev->id.vendor = 0x001f;
+	input_dev->id.product = 0x0001;
+	input_dev->id.version = 0x0100;
+	input_dev->dev.parent = dev;
+
+	input_dev->evbit[0] = BIT(EV_SND);
+	input_dev->sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE);
+	input_dev->event = pcspkr_input_event;
+
+	err = input_register_device(input_dev);
+	if (err) {
+		input_free_device(input_dev);
+		return err;
+	}
+
+	*rdev = input_dev;
+	return 0;
+}
+
+int pcspkr_input_remove(struct input_dev *dev)
+{
+	pcspkr_stop_sound();
+	input_unregister_device(dev);	/* this also does kfree() */
+
+	return 0;
+}
diff --git a/linux/sound/drivers/pcsp/pcsp_input.h b/linux/sound/drivers/pcsp/pcsp_input.h
new file mode 100644
index 0000000..e66738c
--- /dev/null
+++ b/linux/sound/drivers/pcsp/pcsp_input.h
@@ -0,0 +1,14 @@
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Copyright (C) 2001-2008  Stas Sergeev
+ */
+
+#ifndef __PCSP_INPUT_H__
+#define __PCSP_INPUT_H__
+
+int __devinit pcspkr_input_init(struct input_dev **rdev, struct device *dev);
+int pcspkr_input_remove(struct input_dev *dev);
+void pcspkr_stop_sound(void);
+
+#endif
diff --git a/linux/sound/drivers/pcsp/pcsp_lib.c b/linux/sound/drivers/pcsp/pcsp_lib.c
new file mode 100644
index 0000000..ce9e7d1
--- /dev/null
+++ b/linux/sound/drivers/pcsp/pcsp_lib.c
@@ -0,0 +1,359 @@
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Copyright (C) 1993-1997  Michael Beck
+ * Copyright (C) 1997-2001  David Woodhouse
+ * Copyright (C) 2001-2008  Stas Sergeev
+ */
+
+#include <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <sound/pcm.h>
+#include <asm/io.h>
+#include "pcsp.h"
+
+static int nforce_wa;
+module_param(nforce_wa, bool, 0444);
+MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround "
+		"(expect bad sound)");
+
+#define DMIX_WANTS_S16	1
+
+/*
+ * Call snd_pcm_period_elapsed in a tasklet
+ * This avoids spinlock messes and long-running irq contexts
+ */
+static void pcsp_call_pcm_elapsed(unsigned long priv)
+{
+	if (atomic_read(&pcsp_chip.timer_active)) {
+		struct snd_pcm_substream *substream;
+		substream = pcsp_chip.playback_substream;
+		if (substream)
+			snd_pcm_period_elapsed(substream);
+	}
+}
+
+static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0);
+
+/* write the port and returns the next expire time in ns;
+ * called at the trigger-start and in hrtimer callback
+ */
+static u64 pcsp_timer_update(struct snd_pcsp *chip)
+{
+	unsigned char timer_cnt, val;
+	u64 ns;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+	unsigned long flags;
+
+	if (chip->thalf) {
+		outb(chip->val61, 0x61);
+		chip->thalf = 0;
+		return chip->ns_rem;
+	}
+
+	substream = chip->playback_substream;
+	if (!substream)
+		return 0;
+
+	runtime = substream->runtime;
+	/* assume it is mono! */
+	val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1];
+	if (chip->is_signed)
+		val ^= 0x80;
+	timer_cnt = val * CUR_DIV() / 256;
+
+	if (timer_cnt && chip->enable) {
+		raw_spin_lock_irqsave(&i8253_lock, flags);
+		if (!nforce_wa) {
+			outb_p(chip->val61, 0x61);
+			outb_p(timer_cnt, 0x42);
+			outb(chip->val61 ^ 1, 0x61);
+		} else {
+			outb(chip->val61 ^ 2, 0x61);
+			chip->thalf = 1;
+		}
+		raw_spin_unlock_irqrestore(&i8253_lock, flags);
+	}
+
+	chip->ns_rem = PCSP_PERIOD_NS();
+	ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
+	chip->ns_rem -= ns;
+	return ns;
+}
+
+static void pcsp_pointer_update(struct snd_pcsp *chip)
+{
+	struct snd_pcm_substream *substream;
+	size_t period_bytes, buffer_bytes;
+	int periods_elapsed;
+	unsigned long flags;
+
+	/* update the playback position */
+	substream = chip->playback_substream;
+	if (!substream)
+		return;
+
+	period_bytes = snd_pcm_lib_period_bytes(substream);
+	buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
+
+	spin_lock_irqsave(&chip->substream_lock, flags);
+	chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size;
+	periods_elapsed = chip->playback_ptr - chip->period_ptr;
+	if (periods_elapsed < 0) {
+#if PCSP_DEBUG
+		printk(KERN_INFO "PCSP: buffer_bytes mod period_bytes != 0 ? "
+			"(%zi %zi %zi)\n",
+			chip->playback_ptr, period_bytes, buffer_bytes);
+#endif
+		periods_elapsed += buffer_bytes;
+	}
+	periods_elapsed /= period_bytes;
+	/* wrap the pointer _before_ calling snd_pcm_period_elapsed(),
+	 * or ALSA will BUG on us. */
+	chip->playback_ptr %= buffer_bytes;
+
+	if (periods_elapsed) {
+		chip->period_ptr += periods_elapsed * period_bytes;
+		chip->period_ptr %= buffer_bytes;
+	}
+	spin_unlock_irqrestore(&chip->substream_lock, flags);
+
+	if (periods_elapsed)
+		tasklet_schedule(&pcsp_pcm_tasklet);
+}
+
+enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
+{
+	struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
+	int pointer_update;
+	u64 ns;
+
+	if (!atomic_read(&chip->timer_active) || !chip->playback_substream)
+		return HRTIMER_NORESTART;
+
+	pointer_update = !chip->thalf;
+	ns = pcsp_timer_update(chip);
+	if (!ns) {
+		printk(KERN_WARNING "PCSP: unexpected stop\n");
+		return HRTIMER_NORESTART;
+	}
+
+	if (pointer_update)
+		pcsp_pointer_update(chip);
+
+	hrtimer_forward(handle, hrtimer_get_expires(handle), ns_to_ktime(ns));
+
+	return HRTIMER_RESTART;
+}
+
+static int pcsp_start_playing(struct snd_pcsp *chip)
+{
+#if PCSP_DEBUG
+	printk(KERN_INFO "PCSP: start_playing called\n");
+#endif
+	if (atomic_read(&chip->timer_active)) {
+		printk(KERN_ERR "PCSP: Timer already active\n");
+		return -EIO;
+	}
+
+	raw_spin_lock(&i8253_lock);
+	chip->val61 = inb(0x61) | 0x03;
+	outb_p(0x92, 0x43);	/* binary, mode 1, LSB only, ch 2 */
+	raw_spin_unlock(&i8253_lock);
+	atomic_set(&chip->timer_active, 1);
+	chip->thalf = 0;
+
+	hrtimer_start(&pcsp_chip.timer, ktime_set(0, 0), HRTIMER_MODE_REL);
+	return 0;
+}
+
+static void pcsp_stop_playing(struct snd_pcsp *chip)
+{
+#if PCSP_DEBUG
+	printk(KERN_INFO "PCSP: stop_playing called\n");
+#endif
+	if (!atomic_read(&chip->timer_active))
+		return;
+
+	atomic_set(&chip->timer_active, 0);
+	raw_spin_lock(&i8253_lock);
+	/* restore the timer */
+	outb_p(0xb6, 0x43);	/* binary, mode 3, LSB/MSB, ch 2 */
+	outb(chip->val61 & 0xFC, 0x61);
+	raw_spin_unlock(&i8253_lock);
+}
+
+/*
+ * Force to stop and sync the stream
+ */
+void pcsp_sync_stop(struct snd_pcsp *chip)
+{
+	local_irq_disable();
+	pcsp_stop_playing(chip);
+	local_irq_enable();
+	hrtimer_cancel(&chip->timer);
+	tasklet_kill(&pcsp_pcm_tasklet);
+}
+
+static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+#if PCSP_DEBUG
+	printk(KERN_INFO "PCSP: close called\n");
+#endif
+	pcsp_sync_stop(chip);
+	chip->playback_substream = NULL;
+	return 0;
+}
+
+static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+	int err;
+	pcsp_sync_stop(chip);
+	err = snd_pcm_lib_malloc_pages(substream,
+				      params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+#if PCSP_DEBUG
+	printk(KERN_INFO "PCSP: hw_free called\n");
+#endif
+	pcsp_sync_stop(chip);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+	pcsp_sync_stop(chip);
+	chip->playback_ptr = 0;
+	chip->period_ptr = 0;
+	chip->fmt_size =
+		snd_pcm_format_physical_width(substream->runtime->format) >> 3;
+	chip->is_signed = snd_pcm_format_signed(substream->runtime->format);
+#if PCSP_DEBUG
+	printk(KERN_INFO "PCSP: prepare called, "
+			"size=%zi psize=%zi f=%zi f1=%i fsize=%i\n",
+			snd_pcm_lib_buffer_bytes(substream),
+			snd_pcm_lib_period_bytes(substream),
+			snd_pcm_lib_buffer_bytes(substream) /
+			snd_pcm_lib_period_bytes(substream),
+			substream->runtime->periods,
+			chip->fmt_size);
+#endif
+	return 0;
+}
+
+static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+#if PCSP_DEBUG
+	printk(KERN_INFO "PCSP: trigger called\n");
+#endif
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		return pcsp_start_playing(chip);
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		pcsp_stop_playing(chip);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
+						   *substream)
+{
+	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+	unsigned int pos;
+	spin_lock(&chip->substream_lock);
+	pos = chip->playback_ptr;
+	spin_unlock(&chip->substream_lock);
+	return bytes_to_frames(substream->runtime, pos);
+}
+
+static struct snd_pcm_hardware snd_pcsp_playback = {
+	.info = (SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_HALF_DUPLEX |
+		 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats = (SNDRV_PCM_FMTBIT_U8
+#if DMIX_WANTS_S16
+		    | SNDRV_PCM_FMTBIT_S16_LE
+#endif
+	    ),
+	.rates = SNDRV_PCM_RATE_KNOT,
+	.rate_min = PCSP_DEFAULT_SRATE,
+	.rate_max = PCSP_DEFAULT_SRATE,
+	.channels_min = 1,
+	.channels_max = 1,
+	.buffer_bytes_max = PCSP_BUFFER_SIZE,
+	.period_bytes_min = 64,
+	.period_bytes_max = PCSP_MAX_PERIOD_SIZE,
+	.periods_min = 2,
+	.periods_max = PCSP_MAX_PERIODS,
+	.fifo_size = 0,
+};
+
+static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+#if PCSP_DEBUG
+	printk(KERN_INFO "PCSP: open called\n");
+#endif
+	if (atomic_read(&chip->timer_active)) {
+		printk(KERN_ERR "PCSP: still active!!\n");
+		return -EBUSY;
+	}
+	runtime->hw = snd_pcsp_playback;
+	chip->playback_substream = substream;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_pcsp_playback_ops = {
+	.open = snd_pcsp_playback_open,
+	.close = snd_pcsp_playback_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_pcsp_playback_hw_params,
+	.hw_free = snd_pcsp_playback_hw_free,
+	.prepare = snd_pcsp_playback_prepare,
+	.trigger = snd_pcsp_trigger,
+	.pointer = snd_pcsp_playback_pointer,
+};
+
+int __devinit snd_pcsp_new_pcm(struct snd_pcsp *chip)
+{
+	int err;
+
+	err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_pcsp_playback_ops);
+
+	chip->pcm->private_data = chip;
+	chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+	strcpy(chip->pcm->name, "pcsp");
+
+	snd_pcm_lib_preallocate_pages_for_all(chip->pcm,
+					      SNDRV_DMA_TYPE_CONTINUOUS,
+					      snd_dma_continuous_data
+					      (GFP_KERNEL), PCSP_BUFFER_SIZE,
+					      PCSP_BUFFER_SIZE);
+
+	return 0;
+}
diff --git a/linux/sound/drivers/pcsp/pcsp_mixer.c b/linux/sound/drivers/pcsp/pcsp_mixer.c
new file mode 100644
index 0000000..6f633f4
--- /dev/null
+++ b/linux/sound/drivers/pcsp/pcsp_mixer.c
@@ -0,0 +1,163 @@
+/*
+ * PC-Speaker driver for Linux
+ *
+ * Mixer implementation.
+ * Copyright (C) 2001-2008  Stas Sergeev
+ */
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include "pcsp.h"
+
+
+static int pcsp_enable_info(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int pcsp_enable_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = chip->enable;
+	return 0;
+}
+
+static int pcsp_enable_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int enab = ucontrol->value.integer.value[0];
+	if (enab != chip->enable) {
+		chip->enable = enab;
+		changed = 1;
+	}
+	return changed;
+}
+
+static int pcsp_treble_info(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = chip->max_treble + 1;
+	if (uinfo->value.enumerated.item > chip->max_treble)
+		uinfo->value.enumerated.item = chip->max_treble;
+	sprintf(uinfo->value.enumerated.name, "%lu",
+		(unsigned long)PCSP_CALC_RATE(uinfo->value.enumerated.item));
+	return 0;
+}
+
+static int pcsp_treble_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = chip->treble;
+	return 0;
+}
+
+static int pcsp_treble_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int treble = ucontrol->value.enumerated.item[0];
+	if (treble != chip->treble) {
+		chip->treble = treble;
+#if PCSP_DEBUG
+		printk(KERN_INFO "PCSP: rate set to %li\n", PCSP_RATE());
+#endif
+		changed = 1;
+	}
+	return changed;
+}
+
+static int pcsp_pcspkr_info(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int pcsp_pcspkr_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = chip->pcspkr;
+	return 0;
+}
+
+static int pcsp_pcspkr_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int spkr = ucontrol->value.integer.value[0];
+	if (spkr != chip->pcspkr) {
+		chip->pcspkr = spkr;
+		changed = 1;
+	}
+	return changed;
+}
+
+#define PCSP_MIXER_CONTROL(ctl_type, ctl_name) \
+{ \
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name =		ctl_name, \
+	.info =		pcsp_##ctl_type##_info, \
+	.get =		pcsp_##ctl_type##_get, \
+	.put =		pcsp_##ctl_type##_put, \
+}
+
+static struct snd_kcontrol_new __devinitdata snd_pcsp_controls_pcm[] = {
+	PCSP_MIXER_CONTROL(enable, "Master Playback Switch"),
+	PCSP_MIXER_CONTROL(treble, "BaseFRQ Playback Volume"),
+};
+
+static struct snd_kcontrol_new __devinitdata snd_pcsp_controls_spkr[] = {
+	PCSP_MIXER_CONTROL(pcspkr, "Beep Playback Switch"),
+};
+
+static int __devinit snd_pcsp_ctls_add(struct snd_pcsp *chip,
+	struct snd_kcontrol_new *ctls, int num)
+{
+	int i, err;
+	struct snd_card *card = chip->card;
+	for (i = 0; i < num; i++) {
+		err = snd_ctl_add(card, snd_ctl_new1(ctls + i, chip));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+int __devinit snd_pcsp_new_mixer(struct snd_pcsp *chip, int nopcm)
+{
+	int err;
+	struct snd_card *card = chip->card;
+
+	if (!nopcm) {
+		err = snd_pcsp_ctls_add(chip, snd_pcsp_controls_pcm,
+			ARRAY_SIZE(snd_pcsp_controls_pcm));
+		if (err < 0)
+			return err;
+	}
+	err = snd_pcsp_ctls_add(chip, snd_pcsp_controls_spkr,
+		ARRAY_SIZE(snd_pcsp_controls_spkr));
+	if (err < 0)
+		return err;
+
+	strcpy(card->mixername, "PC-Speaker");
+
+	return 0;
+}
diff --git a/linux/sound/drivers/portman2x4.c b/linux/sound/drivers/portman2x4.c
new file mode 100644
index 0000000..f2b0ba2
--- /dev/null
+++ b/linux/sound/drivers/portman2x4.c
@@ -0,0 +1,878 @@
+/*
+ *   Driver for Midiman Portman2x4 parallel port midi interface
+ *
+ *   Copyright (c) by Levent Guendogdu <levon@feature-it.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * ChangeLog
+ * Jan 24 2007 Matthias Koenig <mkoenig@suse.de>
+ *      - cleanup and rewrite
+ * Sep 30 2004 Tobias Gehrig <tobias@gehrig.tk>
+ *      - source code cleanup
+ * Sep 03 2004 Tobias Gehrig <tobias@gehrig.tk>
+ *      - fixed compilation problem with alsa 1.0.6a (removed MODULE_CLASSES,
+ *        MODULE_PARM_SYNTAX and changed MODULE_DEVICES to
+ *        MODULE_SUPPORTED_DEVICE)
+ * Mar 24 2004 Tobias Gehrig <tobias@gehrig.tk>
+ *      - added 2.6 kernel support
+ * Mar 18 2004 Tobias Gehrig <tobias@gehrig.tk>
+ *      - added parport_unregister_driver to the startup routine if the driver fails to detect a portman
+ *      - added support for all 4 output ports in portman_putmidi
+ * Mar 17 2004 Tobias Gehrig <tobias@gehrig.tk>
+ *      - added checks for opened input device in interrupt handler
+ * Feb 20 2004 Tobias Gehrig <tobias@gehrig.tk>
+ *      - ported from alsa 0.5 to 1.0
+ */
+
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/parport.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <sound/control.h>
+
+#define CARD_NAME "Portman 2x4"
+#define DRIVER_NAME "portman"
+#define PLATFORM_DRIVER "snd_portman2x4"
+
+static int index[SNDRV_CARDS]  = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS]   = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+static struct platform_device *platform_devices[SNDRV_CARDS]; 
+static int device_count;
+
+module_param_array(index, int, NULL, S_IRUGO);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, S_IRUGO);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, S_IRUGO);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+
+MODULE_AUTHOR("Levent Guendogdu, Tobias Gehrig, Matthias Koenig");
+MODULE_DESCRIPTION("Midiman Portman2x4");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Midiman,Portman2x4}}");
+
+/*********************************************************************
+ * Chip specific
+ *********************************************************************/
+#define PORTMAN_NUM_INPUT_PORTS 2
+#define PORTMAN_NUM_OUTPUT_PORTS 4
+
+struct portman {
+	spinlock_t reg_lock;
+	struct snd_card *card;
+	struct snd_rawmidi *rmidi;
+	struct pardevice *pardev;
+	int pardev_claimed;
+
+	int open_count;
+	int mode[PORTMAN_NUM_INPUT_PORTS];
+	struct snd_rawmidi_substream *midi_input[PORTMAN_NUM_INPUT_PORTS];
+};
+
+static int portman_free(struct portman *pm)
+{
+	kfree(pm);
+	return 0;
+}
+
+static int __devinit portman_create(struct snd_card *card, 
+				    struct pardevice *pardev, 
+				    struct portman **rchip)
+{
+	struct portman *pm;
+
+	*rchip = NULL;
+
+	pm = kzalloc(sizeof(struct portman), GFP_KERNEL);
+	if (pm == NULL) 
+		return -ENOMEM;
+
+	/* Init chip specific data */
+	spin_lock_init(&pm->reg_lock);
+	pm->card = card;
+	pm->pardev = pardev;
+
+	*rchip = pm;
+
+	return 0;
+}
+
+/*********************************************************************
+ * HW related constants
+ *********************************************************************/
+
+/* Standard PC parallel port status register equates. */
+#define	PP_STAT_BSY   	0x80	/* Busy status.  Inverted. */
+#define	PP_STAT_ACK   	0x40	/* Acknowledge.  Non-Inverted. */
+#define	PP_STAT_POUT  	0x20	/* Paper Out.    Non-Inverted. */
+#define	PP_STAT_SEL   	0x10	/* Select.       Non-Inverted. */
+#define	PP_STAT_ERR   	0x08	/* Error.        Non-Inverted. */
+
+/* Standard PC parallel port command register equates. */
+#define	PP_CMD_IEN  	0x10	/* IRQ Enable.   Non-Inverted. */
+#define	PP_CMD_SELI 	0x08	/* Select Input. Inverted. */
+#define	PP_CMD_INIT 	0x04	/* Init Printer. Non-Inverted. */
+#define	PP_CMD_FEED 	0x02	/* Auto Feed.    Inverted. */
+#define	PP_CMD_STB      0x01	/* Strobe.       Inverted. */
+
+/* Parallel Port Command Register as implemented by PCP2x4. */
+#define	INT_EN	 	PP_CMD_IEN	/* Interrupt enable. */
+#define	STROBE	        PP_CMD_STB	/* Command strobe. */
+
+/* The parallel port command register field (b1..b3) selects the 
+ * various "registers" within the PC/P 2x4.  These are the internal
+ * address of these "registers" that must be written to the parallel
+ * port command register.
+ */
+#define	RXDATA0		(0 << 1)	/* PCP RxData channel 0. */
+#define	RXDATA1		(1 << 1)	/* PCP RxData channel 1. */
+#define	GEN_CTL		(2 << 1)	/* PCP General Control Register. */
+#define	SYNC_CTL 	(3 << 1)	/* PCP Sync Control Register. */
+#define	TXDATA0		(4 << 1)	/* PCP TxData channel 0. */
+#define	TXDATA1		(5 << 1)	/* PCP TxData channel 1. */
+#define	TXDATA2		(6 << 1)	/* PCP TxData channel 2. */
+#define	TXDATA3		(7 << 1)	/* PCP TxData channel 3. */
+
+/* Parallel Port Status Register as implemented by PCP2x4. */
+#define	ESTB		PP_STAT_POUT	/* Echoed strobe. */
+#define	INT_REQ         PP_STAT_ACK	/* Input data int request. */
+#define	BUSY            PP_STAT_ERR	/* Interface Busy. */
+
+/* Parallel Port Status Register BUSY and SELECT lines are multiplexed
+ * between several functions.  Depending on which 2x4 "register" is
+ * currently selected (b1..b3), the BUSY and SELECT lines are
+ * assigned as follows:
+ *
+ *   SELECT LINE:                                                    A3 A2 A1
+ *                                                                   --------
+ */
+#define	RXAVAIL		PP_STAT_SEL	/* Rx Available, channel 0.   0 0 0 */
+//  RXAVAIL1    PP_STAT_SEL             /* Rx Available, channel 1.   0 0 1 */
+#define	SYNC_STAT	PP_STAT_SEL	/* Reserved - Sync Status.    0 1 0 */
+//                                      /* Reserved.                  0 1 1 */
+#define	TXEMPTY		PP_STAT_SEL	/* Tx Empty, channel 0.       1 0 0 */
+//      TXEMPTY1        PP_STAT_SEL     /* Tx Empty, channel 1.       1 0 1 */
+//  TXEMPTY2    PP_STAT_SEL             /* Tx Empty, channel 2.       1 1 0 */
+//  TXEMPTY3    PP_STAT_SEL             /* Tx Empty, channel 3.       1 1 1 */
+
+/*   BUSY LINE:                                                      A3 A2 A1
+ *                                                                   --------
+ */
+#define	RXDATA		PP_STAT_BSY	/* Rx Input Data, channel 0.  0 0 0 */
+//      RXDATA1         PP_STAT_BSY     /* Rx Input Data, channel 1.  0 0 1 */
+#define	SYNC_DATA       PP_STAT_BSY	/* Reserved - Sync Data.      0 1 0 */
+					/* Reserved.                  0 1 1 */
+#define	DATA_ECHO       PP_STAT_BSY	/* Parallel Port Data Echo.   1 0 0 */
+#define	A0_ECHO         PP_STAT_BSY	/* Address 0 Echo.            1 0 1 */
+#define	A1_ECHO         PP_STAT_BSY	/* Address 1 Echo.            1 1 0 */
+#define	A2_ECHO         PP_STAT_BSY	/* Address 2 Echo.            1 1 1 */
+
+#define PORTMAN2X4_MODE_INPUT_TRIGGERED	 0x01
+
+/*********************************************************************
+ * Hardware specific functions
+ *********************************************************************/
+static inline void portman_write_command(struct portman *pm, u8 value)
+{
+	parport_write_control(pm->pardev->port, value);
+}
+
+static inline u8 portman_read_command(struct portman *pm)
+{
+	return parport_read_control(pm->pardev->port);
+}
+
+static inline u8 portman_read_status(struct portman *pm)
+{
+	return parport_read_status(pm->pardev->port);
+}
+
+static inline u8 portman_read_data(struct portman *pm)
+{
+	return parport_read_data(pm->pardev->port);
+}
+
+static inline void portman_write_data(struct portman *pm, u8 value)
+{
+	parport_write_data(pm->pardev->port, value);
+}
+
+static void portman_write_midi(struct portman *pm, 
+			       int port, u8 mididata)
+{
+	int command = ((port + 4) << 1);
+
+	/* Get entering data byte and port number in BL and BH respectively.
+	 * Set up Tx Channel address field for use with PP Cmd Register.
+	 * Store address field in BH register.
+	 * Inputs:      AH = Output port number (0..3).
+	 *              AL = Data byte.
+	 *    command = TXDATA0 | INT_EN;
+	 * Align port num with address field (b1...b3),
+	 * set address for TXDatax, Strobe=0
+	 */
+	command |= INT_EN;
+
+	/* Disable interrupts so that the process is not interrupted, then 
+	 * write the address associated with the current Tx channel to the 
+	 * PP Command Reg.  Do not set the Strobe signal yet.
+	 */
+
+	do {
+		portman_write_command(pm, command);
+
+		/* While the address lines settle, write parallel output data to 
+		 * PP Data Reg.  This has no effect until Strobe signal is asserted.
+		 */
+
+		portman_write_data(pm, mididata);
+		
+		/* If PCP channel's TxEmpty is set (TxEmpty is read through the PP
+		 * Status Register), then go write data.  Else go back and wait.
+		 */
+	} while ((portman_read_status(pm) & TXEMPTY) != TXEMPTY);
+
+	/* TxEmpty is set.  Maintain PC/P destination address and assert
+	 * Strobe through the PP Command Reg.  This will Strobe data into
+	 * the PC/P transmitter and set the PC/P BUSY signal.
+	 */
+
+	portman_write_command(pm, command | STROBE);
+
+	/* Wait for strobe line to settle and echo back through hardware.
+	 * Once it has echoed back, assume that the address and data lines
+	 * have settled!
+	 */
+
+	while ((portman_read_status(pm) & ESTB) == 0)
+		cpu_relax();
+
+	/* Release strobe and immediately re-allow interrupts. */
+	portman_write_command(pm, command);
+
+	while ((portman_read_status(pm) & ESTB) == ESTB)
+		cpu_relax();
+
+	/* PC/P BUSY is now set.  We must wait until BUSY resets itself.
+	 * We'll reenable ints while we're waiting.
+	 */
+
+	while ((portman_read_status(pm) & BUSY) == BUSY)
+		cpu_relax();
+
+	/* Data sent. */
+}
+
+
+/*
+ *  Read MIDI byte from port
+ *  Attempt to read input byte from specified hardware input port (0..).
+ *  Return -1 if no data
+ */
+static int portman_read_midi(struct portman *pm, int port)
+{
+	unsigned char midi_data = 0;
+	unsigned char cmdout;	/* Saved address+IE bit. */
+
+	/* Make sure clocking edge is down before starting... */
+	portman_write_data(pm, 0);	/* Make sure edge is down. */
+
+	/* Set destination address to PCP. */
+	cmdout = (port << 1) | INT_EN;	/* Address + IE + No Strobe. */
+	portman_write_command(pm, cmdout);
+
+	while ((portman_read_status(pm) & ESTB) == ESTB)
+		cpu_relax();	/* Wait for strobe echo. */
+
+	/* After the address lines settle, check multiplexed RxAvail signal.
+	 * If data is available, read it.
+	 */
+	if ((portman_read_status(pm) & RXAVAIL) == 0)
+		return -1;	/* No data. */
+
+	/* Set the Strobe signal to enable the Rx clocking circuitry. */
+	portman_write_command(pm, cmdout | STROBE);	/* Write address+IE+Strobe. */
+
+	while ((portman_read_status(pm) & ESTB) == 0)
+		cpu_relax(); /* Wait for strobe echo. */
+
+	/* The first data bit (msb) is already sitting on the input line. */
+	midi_data = (portman_read_status(pm) & 128);
+	portman_write_data(pm, 1);	/* Cause rising edge, which shifts data. */
+
+	/* Data bit 6. */
+	portman_write_data(pm, 0);	/* Cause falling edge while data settles. */
+	midi_data |= (portman_read_status(pm) >> 1) & 64;
+	portman_write_data(pm, 1);	/* Cause rising edge, which shifts data. */
+
+	/* Data bit 5. */
+	portman_write_data(pm, 0);	/* Cause falling edge while data settles. */
+	midi_data |= (portman_read_status(pm) >> 2) & 32;
+	portman_write_data(pm, 1);	/* Cause rising edge, which shifts data. */
+
+	/* Data bit 4. */
+	portman_write_data(pm, 0);	/* Cause falling edge while data settles. */
+	midi_data |= (portman_read_status(pm) >> 3) & 16;
+	portman_write_data(pm, 1);	/* Cause rising edge, which shifts data. */
+
+	/* Data bit 3. */
+	portman_write_data(pm, 0);	/* Cause falling edge while data settles. */
+	midi_data |= (portman_read_status(pm) >> 4) & 8;
+	portman_write_data(pm, 1);	/* Cause rising edge, which shifts data. */
+
+	/* Data bit 2. */
+	portman_write_data(pm, 0);	/* Cause falling edge while data settles. */
+	midi_data |= (portman_read_status(pm) >> 5) & 4;
+	portman_write_data(pm, 1);	/* Cause rising edge, which shifts data. */
+
+	/* Data bit 1. */
+	portman_write_data(pm, 0);	/* Cause falling edge while data settles. */
+	midi_data |= (portman_read_status(pm) >> 6) & 2;
+	portman_write_data(pm, 1);	/* Cause rising edge, which shifts data. */
+
+	/* Data bit 0. */
+	portman_write_data(pm, 0);	/* Cause falling edge while data settles. */
+	midi_data |= (portman_read_status(pm) >> 7) & 1;
+	portman_write_data(pm, 1);	/* Cause rising edge, which shifts data. */
+	portman_write_data(pm, 0);	/* Return data clock low. */
+
+
+	/* De-assert Strobe and return data. */
+	portman_write_command(pm, cmdout);	/* Output saved address+IE. */
+
+	/* Wait for strobe echo. */
+	while ((portman_read_status(pm) & ESTB) == ESTB)
+		cpu_relax();
+
+	return (midi_data & 255);	/* Shift back and return value. */
+}
+
+/*
+ *  Checks if any input data on the given channel is available
+ *  Checks RxAvail 
+ */
+static int portman_data_avail(struct portman *pm, int channel)
+{
+	int command = INT_EN;
+	switch (channel) {
+	case 0:
+		command |= RXDATA0;
+		break;
+	case 1:
+		command |= RXDATA1;
+		break;
+	}
+	/* Write hardware (assumme STROBE=0) */
+	portman_write_command(pm, command);
+	/* Check multiplexed RxAvail signal */
+	if ((portman_read_status(pm) & RXAVAIL) == RXAVAIL)
+		return 1;	/* Data available */
+
+	/* No Data available */
+	return 0;
+}
+
+
+/*
+ *  Flushes any input
+ */
+static void portman_flush_input(struct portman *pm, unsigned char port)
+{
+	/* Local variable for counting things */
+	unsigned int i = 0;
+	unsigned char command = 0;
+
+	switch (port) {
+	case 0:
+		command = RXDATA0;
+		break;
+	case 1:
+		command = RXDATA1;
+		break;
+	default:
+		snd_printk(KERN_WARNING
+			   "portman_flush_input() Won't flush port %i\n",
+			   port);
+		return;
+	}
+
+	/* Set address for specified channel in port and allow to settle. */
+	portman_write_command(pm, command);
+
+	/* Assert the Strobe and wait for echo back. */
+	portman_write_command(pm, command | STROBE);
+
+	/* Wait for ESTB */
+	while ((portman_read_status(pm) & ESTB) == 0)
+		cpu_relax();
+
+	/* Output clock cycles to the Rx circuitry. */
+	portman_write_data(pm, 0);
+
+	/* Flush 250 bits... */
+	for (i = 0; i < 250; i++) {
+		portman_write_data(pm, 1);
+		portman_write_data(pm, 0);
+	}
+
+	/* Deassert the Strobe signal of the port and wait for it to settle. */
+	portman_write_command(pm, command | INT_EN);
+
+	/* Wait for settling */
+	while ((portman_read_status(pm) & ESTB) == ESTB)
+		cpu_relax();
+}
+
+static int portman_probe(struct parport *p)
+{
+	/* Initialize the parallel port data register.  Will set Rx clocks
+	 * low in case we happen to be addressing the Rx ports at this time.
+	 */
+	/* 1 */
+	parport_write_data(p, 0);
+
+	/* Initialize the parallel port command register, thus initializing
+	 * hardware handshake lines to midi box:
+	 *
+	 *                                  Strobe = 0
+	 *                                  Interrupt Enable = 0            
+	 */
+	/* 2 */
+	parport_write_control(p, 0);
+
+	/* Check if Portman PC/P 2x4 is out there. */
+	/* 3 */
+	parport_write_control(p, RXDATA0);	/* Write Strobe=0 to command reg. */
+
+	/* Check for ESTB to be clear */
+	/* 4 */
+	if ((parport_read_status(p) & ESTB) == ESTB)
+		return 1;	/* CODE 1 - Strobe Failure. */
+
+	/* Set for RXDATA0 where no damage will be done. */
+	/* 5 */
+	parport_write_control(p, RXDATA0 + STROBE);	/* Write Strobe=1 to command reg. */
+
+	/* 6 */
+	if ((parport_read_status(p) & ESTB) != ESTB)
+		return 1;	/* CODE 1 - Strobe Failure. */
+
+	/* 7 */
+	parport_write_control(p, 0);	/* Reset Strobe=0. */
+
+	/* Check if Tx circuitry is functioning properly.  If initialized 
+	 * unit TxEmpty is false, send out char and see if if goes true.
+	 */
+	/* 8 */
+	parport_write_control(p, TXDATA0);	/* Tx channel 0, strobe off. */
+
+	/* If PCP channel's TxEmpty is set (TxEmpty is read through the PP
+	 * Status Register), then go write data.  Else go back and wait.
+	 */
+	/* 9 */
+	if ((parport_read_status(p) & TXEMPTY) == 0)
+		return 2;
+
+	/* Return OK status. */
+	return 0;
+}
+
+static int portman_device_init(struct portman *pm)
+{
+	portman_flush_input(pm, 0);
+	portman_flush_input(pm, 1);
+
+	return 0;
+}
+
+/*********************************************************************
+ * Rawmidi
+ *********************************************************************/
+static int snd_portman_midi_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int snd_portman_midi_close(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static void snd_portman_midi_input_trigger(struct snd_rawmidi_substream *substream,
+					   int up)
+{
+	struct portman *pm = substream->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&pm->reg_lock, flags);
+	if (up)
+		pm->mode[substream->number] |= PORTMAN2X4_MODE_INPUT_TRIGGERED;
+	else
+		pm->mode[substream->number] &= ~PORTMAN2X4_MODE_INPUT_TRIGGERED;
+	spin_unlock_irqrestore(&pm->reg_lock, flags);
+}
+
+static void snd_portman_midi_output_trigger(struct snd_rawmidi_substream *substream,
+					    int up)
+{
+	struct portman *pm = substream->rmidi->private_data;
+	unsigned long flags;
+	unsigned char byte;
+
+	spin_lock_irqsave(&pm->reg_lock, flags);
+	if (up) {
+		while ((snd_rawmidi_transmit(substream, &byte, 1) == 1))
+			portman_write_midi(pm, substream->number, byte);
+	}
+	spin_unlock_irqrestore(&pm->reg_lock, flags);
+}
+
+static struct snd_rawmidi_ops snd_portman_midi_output = {
+	.open =		snd_portman_midi_open,
+	.close =	snd_portman_midi_close,
+	.trigger =	snd_portman_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_portman_midi_input = {
+	.open =		snd_portman_midi_open,
+	.close =	snd_portman_midi_close,
+	.trigger =	snd_portman_midi_input_trigger,
+};
+
+/* Create and initialize the rawmidi component */
+static int __devinit snd_portman_rawmidi_create(struct snd_card *card)
+{
+	struct portman *pm = card->private_data;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *substream;
+	int err;
+	
+	err = snd_rawmidi_new(card, CARD_NAME, 0, 
+			      PORTMAN_NUM_OUTPUT_PORTS, 
+			      PORTMAN_NUM_INPUT_PORTS, 
+			      &rmidi);
+	if (err < 0) 
+		return err;
+
+	rmidi->private_data = pm;
+	strcpy(rmidi->name, CARD_NAME);
+	rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+		            SNDRV_RAWMIDI_INFO_INPUT |
+                            SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	pm->rmidi = rmidi;
+
+	/* register rawmidi ops */
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 
+			    &snd_portman_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, 
+			    &snd_portman_midi_input);
+
+	/* name substreams */
+	/* output */
+	list_for_each_entry(substream,
+			    &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams,
+			    list) {
+		sprintf(substream->name,
+			"Portman2x4 %d", substream->number+1);
+	}
+	/* input */
+	list_for_each_entry(substream,
+			    &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams,
+			    list) {
+		pm->midi_input[substream->number] = substream;
+		sprintf(substream->name,
+			"Portman2x4 %d", substream->number+1);
+	}
+
+	return err;
+}
+
+/*********************************************************************
+ * parport stuff
+ *********************************************************************/
+static void snd_portman_interrupt(void *userdata)
+{
+	unsigned char midivalue = 0;
+	struct portman *pm = ((struct snd_card*)userdata)->private_data;
+
+	spin_lock(&pm->reg_lock);
+
+	/* While any input data is waiting */
+	while ((portman_read_status(pm) & INT_REQ) == INT_REQ) {
+		/* If data available on channel 0, 
+		   read it and stuff it into the queue. */
+		if (portman_data_avail(pm, 0)) {
+			/* Read Midi */
+			midivalue = portman_read_midi(pm, 0);
+			/* put midi into queue... */
+			if (pm->mode[0] & PORTMAN2X4_MODE_INPUT_TRIGGERED)
+				snd_rawmidi_receive(pm->midi_input[0],
+						    &midivalue, 1);
+
+		}
+		/* If data available on channel 1, 
+		   read it and stuff it into the queue. */
+		if (portman_data_avail(pm, 1)) {
+			/* Read Midi */
+			midivalue = portman_read_midi(pm, 1);
+			/* put midi into queue... */
+			if (pm->mode[1] & PORTMAN2X4_MODE_INPUT_TRIGGERED)
+				snd_rawmidi_receive(pm->midi_input[1],
+						    &midivalue, 1);
+		}
+
+	}
+
+	spin_unlock(&pm->reg_lock);
+}
+
+static int __devinit snd_portman_probe_port(struct parport *p)
+{
+	struct pardevice *pardev;
+	int res;
+
+	pardev = parport_register_device(p, DRIVER_NAME,
+					 NULL, NULL, NULL,
+					 0, NULL);
+	if (!pardev)
+		return -EIO;
+	
+	if (parport_claim(pardev)) {
+		parport_unregister_device(pardev);
+		return -EIO;
+	}
+
+	res = portman_probe(p);
+
+	parport_release(pardev);
+	parport_unregister_device(pardev);
+
+	return res ? -EIO : 0;
+}
+
+static void __devinit snd_portman_attach(struct parport *p)
+{
+	struct platform_device *device;
+
+	device = platform_device_alloc(PLATFORM_DRIVER, device_count);
+	if (!device)
+		return;
+
+	/* Temporary assignment to forward the parport */
+	platform_set_drvdata(device, p);
+
+	if (platform_device_add(device) < 0) {
+		platform_device_put(device);
+		return;
+	}
+
+	/* Since we dont get the return value of probe
+	 * We need to check if device probing succeeded or not */
+	if (!platform_get_drvdata(device)) {
+		platform_device_unregister(device);
+		return;
+	}
+
+	/* register device in global table */
+	platform_devices[device_count] = device;
+	device_count++;
+}
+
+static void snd_portman_detach(struct parport *p)
+{
+	/* nothing to do here */
+}
+
+static struct parport_driver portman_parport_driver = {
+	.name   = "portman2x4",
+	.attach = snd_portman_attach,
+	.detach = snd_portman_detach
+};
+
+/*********************************************************************
+ * platform stuff
+ *********************************************************************/
+static void snd_portman_card_private_free(struct snd_card *card)
+{
+	struct portman *pm = card->private_data;
+	struct pardevice *pardev = pm->pardev;
+
+	if (pardev) {
+		if (pm->pardev_claimed)
+			parport_release(pardev);
+		parport_unregister_device(pardev);
+	}
+
+	portman_free(pm);
+}
+
+static int __devinit snd_portman_probe(struct platform_device *pdev)
+{
+	struct pardevice *pardev;
+	struct parport *p;
+	int dev = pdev->id;
+	struct snd_card *card = NULL;
+	struct portman *pm = NULL;
+	int err;
+
+	p = platform_get_drvdata(pdev);
+	platform_set_drvdata(pdev, NULL);
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) 
+		return -ENOENT;
+
+	if ((err = snd_portman_probe_port(p)) < 0)
+		return err;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0) {
+		snd_printd("Cannot create card\n");
+		return err;
+	}
+	strcpy(card->driver, DRIVER_NAME);
+	strcpy(card->shortname, CARD_NAME);
+	sprintf(card->longname,  "%s at 0x%lx, irq %i", 
+		card->shortname, p->base, p->irq);
+
+	pardev = parport_register_device(p,                     /* port */
+					 DRIVER_NAME,           /* name */
+					 NULL,                  /* preempt */
+					 NULL,                  /* wakeup */
+					 snd_portman_interrupt, /* ISR */
+					 PARPORT_DEV_EXCL,      /* flags */
+					 (void *)card);         /* private */
+	if (pardev == NULL) {
+		snd_printd("Cannot register pardevice\n");
+		err = -EIO;
+		goto __err;
+	}
+
+	if ((err = portman_create(card, pardev, &pm)) < 0) {
+		snd_printd("Cannot create main component\n");
+		parport_unregister_device(pardev);
+		goto __err;
+	}
+	card->private_data = pm;
+	card->private_free = snd_portman_card_private_free;
+	
+	if ((err = snd_portman_rawmidi_create(card)) < 0) {
+		snd_printd("Creating Rawmidi component failed\n");
+		goto __err;
+	}
+
+	/* claim parport */
+	if (parport_claim(pardev)) {
+		snd_printd("Cannot claim parport 0x%lx\n", pardev->port->base);
+		err = -EIO;
+		goto __err;
+	}
+	pm->pardev_claimed = 1;
+
+	/* init device */
+	if ((err = portman_device_init(pm)) < 0)
+		goto __err;
+
+	platform_set_drvdata(pdev, card);
+
+	snd_card_set_dev(card, &pdev->dev);
+
+	/* At this point card will be usable */
+	if ((err = snd_card_register(card)) < 0) {
+		snd_printd("Cannot register card\n");
+		goto __err;
+	}
+
+	snd_printk(KERN_INFO "Portman 2x4 on 0x%lx\n", p->base);
+	return 0;
+
+__err:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit snd_portman_remove(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+
+	if (card)
+		snd_card_free(card);
+
+	return 0;
+}
+
+
+static struct platform_driver snd_portman_driver = {
+	.probe  = snd_portman_probe,
+	.remove = __devexit_p(snd_portman_remove),
+	.driver = {
+		.name = PLATFORM_DRIVER
+	}
+};
+
+/*********************************************************************
+ * module init stuff
+ *********************************************************************/
+static void snd_portman_unregister_all(void)
+{
+	int i;
+
+	for (i = 0; i < SNDRV_CARDS; ++i) {
+		if (platform_devices[i]) {
+			platform_device_unregister(platform_devices[i]);
+			platform_devices[i] = NULL;
+		}
+	}		
+	platform_driver_unregister(&snd_portman_driver);
+	parport_unregister_driver(&portman_parport_driver);
+}
+
+static int __init snd_portman_module_init(void)
+{
+	int err;
+
+	if ((err = platform_driver_register(&snd_portman_driver)) < 0)
+		return err;
+
+	if (parport_register_driver(&portman_parport_driver) != 0) {
+		platform_driver_unregister(&snd_portman_driver);
+		return -EIO;
+	}
+
+	if (device_count == 0) {
+		snd_portman_unregister_all();
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void __exit snd_portman_module_exit(void)
+{
+	snd_portman_unregister_all();
+}
+
+module_init(snd_portman_module_init);
+module_exit(snd_portman_module_exit);
diff --git a/linux/sound/drivers/serial-u16550.c b/linux/sound/drivers/serial-u16550.c
new file mode 100644
index 0000000..a25fb7b
--- /dev/null
+++ b/linux/sound/drivers/serial-u16550.c
@@ -0,0 +1,1050 @@
+/*
+ *   serial.c
+ *   Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ *                    Isaku Yamahata <yamahata@private.email.ne.jp>,
+ *		      George Hansper <ghansper@apana.org.au>,
+ *		      Hannu Savolainen
+ *
+ *   This code is based on the code from ALSA 0.5.9, but heavily rewritten.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * Sat Mar 31 17:27:57 PST 2001 tim.mann@compaq.com 
+ *      Added support for the Midiator MS-124T and for the MS-124W in
+ *      Single Addressed (S/A) or Multiple Burst (M/B) mode, with
+ *      power derived either parasitically from the serial port or
+ *      from a separate power supply.
+ *
+ *      More documentation can be found in serial-u16550.txt.
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+#include <sound/initval.h>
+
+#include <linux/serial_reg.h>
+#include <linux/jiffies.h>
+
+#include <asm/io.h>
+
+MODULE_DESCRIPTION("MIDI serial u16550");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ALSA, MIDI serial u16550}}");
+
+#define SNDRV_SERIAL_SOUNDCANVAS 0 /* Roland Soundcanvas; F5 NN selects part */
+#define SNDRV_SERIAL_MS124T 1      /* Midiator MS-124T */
+#define SNDRV_SERIAL_MS124W_SA 2   /* Midiator MS-124W in S/A mode */
+#define SNDRV_SERIAL_MS124W_MB 3   /* Midiator MS-124W in M/B mode */
+#define SNDRV_SERIAL_GENERIC 4     /* Generic Interface */
+#define SNDRV_SERIAL_MAX_ADAPTOR SNDRV_SERIAL_GENERIC
+static char *adaptor_names[] = {
+	"Soundcanvas",
+        "MS-124T",
+	"MS-124W S/A",
+	"MS-124W M/B",
+	"Generic"
+};
+
+#define SNDRV_SERIAL_NORMALBUFF 0 /* Normal blocking buffer operation */
+#define SNDRV_SERIAL_DROPBUFF   1 /* Non-blocking discard operation */
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x3f8,0x2f8,0x3e8,0x2e8 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; 	/* 3,4,5,7,9,10,11,14,15 */
+static int speed[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 38400}; /* 9600,19200,38400,57600,115200 */
+static int base[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 115200}; /* baud base */
+static int outs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};	 /* 1 to 16 */
+static int ins[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};	/* 1 to 16 */
+static int adaptor[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = SNDRV_SERIAL_SOUNDCANVAS};
+static int droponfull[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS -1)] = SNDRV_SERIAL_NORMALBUFF };
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Serial MIDI.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Serial MIDI.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable UART16550A chip.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for UART16550A chip.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for UART16550A chip.");
+module_param_array(speed, int, NULL, 0444);
+MODULE_PARM_DESC(speed, "Speed in bauds.");
+module_param_array(base, int, NULL, 0444);
+MODULE_PARM_DESC(base, "Base for divisor in bauds.");
+module_param_array(outs, int, NULL, 0444);
+MODULE_PARM_DESC(outs, "Number of MIDI outputs.");
+module_param_array(ins, int, NULL, 0444);
+MODULE_PARM_DESC(ins, "Number of MIDI inputs.");
+module_param_array(droponfull, bool, NULL, 0444);
+MODULE_PARM_DESC(droponfull, "Flag to enable drop-on-full buffer mode");
+
+module_param_array(adaptor, int, NULL, 0444);
+MODULE_PARM_DESC(adaptor, "Type of adaptor.");
+
+/*#define SNDRV_SERIAL_MS124W_MB_NOCOMBO 1*/  /* Address outs as 0-3 instead of bitmap */
+
+#define SNDRV_SERIAL_MAX_OUTS	16		/* max 64, min 16 */
+#define SNDRV_SERIAL_MAX_INS	16		/* max 64, min 16 */
+
+#define TX_BUFF_SIZE		(1<<15)		/* Must be 2^n */
+#define TX_BUFF_MASK		(TX_BUFF_SIZE - 1)
+
+#define SERIAL_MODE_NOT_OPENED 		(0)
+#define SERIAL_MODE_INPUT_OPEN		(1 << 0)
+#define SERIAL_MODE_OUTPUT_OPEN		(1 << 1)
+#define SERIAL_MODE_INPUT_TRIGGERED	(1 << 2)
+#define SERIAL_MODE_OUTPUT_TRIGGERED	(1 << 3)
+
+struct snd_uart16550 {
+	struct snd_card *card;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *midi_output[SNDRV_SERIAL_MAX_OUTS];
+	struct snd_rawmidi_substream *midi_input[SNDRV_SERIAL_MAX_INS];
+
+	int filemode;		/* open status of file */
+
+	spinlock_t open_lock;
+
+	int irq;
+
+	unsigned long base;
+	struct resource *res_base;
+
+	unsigned int speed;
+	unsigned int speed_base;
+	unsigned char divisor;
+
+	unsigned char old_divisor_lsb;
+	unsigned char old_divisor_msb;
+	unsigned char old_line_ctrl_reg;
+
+	/* parameter for using of write loop */
+	short int fifo_limit;	/* used in uart16550 */
+        short int fifo_count;	/* used in uart16550 */
+
+	/* type of adaptor */
+	int adaptor;
+
+	/* inputs */
+	int prev_in;
+	unsigned char rstatus;
+
+	/* outputs */
+	int prev_out;
+	unsigned char prev_status[SNDRV_SERIAL_MAX_OUTS];
+
+	/* write buffer and its writing/reading position */
+	unsigned char tx_buff[TX_BUFF_SIZE];
+	int buff_in_count;
+        int buff_in;
+        int buff_out;
+        int drop_on_full;
+
+	/* wait timer */
+	unsigned int timer_running:1;
+	struct timer_list buffer_timer;
+
+};
+
+static struct platform_device *devices[SNDRV_CARDS];
+
+static inline void snd_uart16550_add_timer(struct snd_uart16550 *uart)
+{
+	if (!uart->timer_running) {
+		/* timer 38600bps * 10bit * 16byte */
+		uart->buffer_timer.expires = jiffies + (HZ+255)/256;
+		uart->timer_running = 1;
+		add_timer(&uart->buffer_timer);
+	}
+}
+
+static inline void snd_uart16550_del_timer(struct snd_uart16550 *uart)
+{
+	if (uart->timer_running) {
+		del_timer(&uart->buffer_timer);
+		uart->timer_running = 0;
+	}
+}
+
+/* This macro is only used in snd_uart16550_io_loop */
+static inline void snd_uart16550_buffer_output(struct snd_uart16550 *uart)
+{
+	unsigned short buff_out = uart->buff_out;
+	if (uart->buff_in_count > 0) {
+		outb(uart->tx_buff[buff_out], uart->base + UART_TX);
+		uart->fifo_count++;
+		buff_out++;
+		buff_out &= TX_BUFF_MASK;
+		uart->buff_out = buff_out;
+		uart->buff_in_count--;
+	}
+}
+
+/* This loop should be called with interrupts disabled
+ * We don't want to interrupt this, 
+ * as we're already handling an interrupt 
+ */
+static void snd_uart16550_io_loop(struct snd_uart16550 * uart)
+{
+	unsigned char c, status;
+	int substream;
+
+	/* recall previous stream */
+	substream = uart->prev_in;
+
+	/* Read Loop */
+	while ((status = inb(uart->base + UART_LSR)) & UART_LSR_DR) {
+		/* while receive data ready */
+		c = inb(uart->base + UART_RX);
+
+		/* keep track of last status byte */
+		if (c & 0x80)
+			uart->rstatus = c;
+
+		/* handle stream switch */
+		if (uart->adaptor == SNDRV_SERIAL_GENERIC) {
+			if (uart->rstatus == 0xf5) {
+				if (c <= SNDRV_SERIAL_MAX_INS && c > 0)
+					substream = c - 1;
+				if (c != 0xf5)
+					/* prevent future bytes from being
+					   interpreted as streams */
+					uart->rstatus = 0;
+			} else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN)
+				   && uart->midi_input[substream])
+				snd_rawmidi_receive(uart->midi_input[substream],
+						    &c, 1);
+		} else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN) &&
+			   uart->midi_input[substream])
+			snd_rawmidi_receive(uart->midi_input[substream], &c, 1);
+
+		if (status & UART_LSR_OE)
+			snd_printk(KERN_WARNING
+				   "%s: Overrun on device at 0x%lx\n",
+			       uart->rmidi->name, uart->base);
+	}
+
+	/* remember the last stream */
+	uart->prev_in = substream;
+
+	/* no need of check SERIAL_MODE_OUTPUT_OPEN because if not,
+	   buffer is never filled. */
+	/* Check write status */
+	if (status & UART_LSR_THRE)
+		uart->fifo_count = 0;
+	if (uart->adaptor == SNDRV_SERIAL_MS124W_SA
+	   || uart->adaptor == SNDRV_SERIAL_GENERIC) {
+		/* Can't use FIFO, must send only when CTS is true */
+		status = inb(uart->base + UART_MSR);
+		while (uart->fifo_count == 0 && (status & UART_MSR_CTS) &&
+		       uart->buff_in_count > 0) {
+		       snd_uart16550_buffer_output(uart);
+		       status = inb(uart->base + UART_MSR);
+		}
+	} else {
+		/* Write loop */
+		while (uart->fifo_count < uart->fifo_limit /* Can we write ? */
+		       && uart->buff_in_count > 0)	/* Do we want to? */
+			snd_uart16550_buffer_output(uart);
+	}
+	if (uart->irq < 0 && uart->buff_in_count > 0)
+		snd_uart16550_add_timer(uart);
+}
+
+/* NOTES ON SERVICING INTERUPTS
+ * ---------------------------
+ * After receiving a interrupt, it is important to indicate to the UART that
+ * this has been done. 
+ * For a Rx interrupt, this is done by reading the received byte.
+ * For a Tx interrupt this is done by either:
+ * a) Writing a byte
+ * b) Reading the IIR
+ * It is particularly important to read the IIR if a Tx interrupt is received
+ * when there is no data in tx_buff[], as in this case there no other
+ * indication that the interrupt has been serviced, and it remains outstanding
+ * indefinitely. This has the curious side effect that and no further interrupts
+ * will be generated from this device AT ALL!!.
+ * It is also desirable to clear outstanding interrupts when the device is
+ * opened/closed.
+ *
+ *
+ * Note that some devices need OUT2 to be set before they will generate
+ * interrupts at all. (Possibly tied to an internal pull-up on CTS?)
+ */
+static irqreturn_t snd_uart16550_interrupt(int irq, void *dev_id)
+{
+	struct snd_uart16550 *uart;
+
+	uart = dev_id;
+	spin_lock(&uart->open_lock);
+	if (uart->filemode == SERIAL_MODE_NOT_OPENED) {
+		spin_unlock(&uart->open_lock);
+		return IRQ_NONE;
+	}
+	/* indicate to the UART that the interrupt has been serviced */
+	inb(uart->base + UART_IIR);
+	snd_uart16550_io_loop(uart);
+	spin_unlock(&uart->open_lock);
+	return IRQ_HANDLED;
+}
+
+/* When the polling mode, this function calls snd_uart16550_io_loop. */
+static void snd_uart16550_buffer_timer(unsigned long data)
+{
+	unsigned long flags;
+	struct snd_uart16550 *uart;
+
+	uart = (struct snd_uart16550 *)data;
+	spin_lock_irqsave(&uart->open_lock, flags);
+	snd_uart16550_del_timer(uart);
+	snd_uart16550_io_loop(uart);
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+}
+
+/*
+ *  this method probes, if an uart sits on given port
+ *  return 0 if found
+ *  return negative error if not found
+ */
+static int __devinit snd_uart16550_detect(struct snd_uart16550 *uart)
+{
+	unsigned long io_base = uart->base;
+	int ok;
+	unsigned char c;
+
+	/* Do some vague tests for the presence of the uart */
+	if (io_base == 0 || io_base == SNDRV_AUTO_PORT) {
+		return -ENODEV;	/* Not configured */
+	}
+
+	uart->res_base = request_region(io_base, 8, "Serial MIDI");
+	if (uart->res_base == NULL) {
+		snd_printk(KERN_ERR "u16550: can't grab port 0x%lx\n", io_base);
+		return -EBUSY;
+	}
+
+	/* uart detected unless one of the following tests should fail */
+	ok = 1;
+	/* 8 data-bits, 1 stop-bit, parity off, DLAB = 0 */
+	outb(UART_LCR_WLEN8, io_base + UART_LCR); /* Line Control Register */
+	c = inb(io_base + UART_IER);
+	/* The top four bits of the IER should always == 0 */
+	if ((c & 0xf0) != 0)
+		ok = 0;		/* failed */
+
+	outb(0xaa, io_base + UART_SCR);
+	/* Write arbitrary data into the scratch reg */
+	c = inb(io_base + UART_SCR);
+	/* If it comes back, it's OK */
+	if (c != 0xaa)
+		ok = 0;		/* failed */
+
+	outb(0x55, io_base + UART_SCR);
+	/* Write arbitrary data into the scratch reg */
+	c = inb(io_base + UART_SCR);
+	/* If it comes back, it's OK */
+	if (c != 0x55)
+		ok = 0;		/* failed */
+
+	return ok;
+}
+
+static void snd_uart16550_do_open(struct snd_uart16550 * uart)
+{
+	char byte;
+
+	/* Initialize basic variables */
+	uart->buff_in_count = 0;
+	uart->buff_in = 0;
+	uart->buff_out = 0;
+	uart->fifo_limit = 1;
+	uart->fifo_count = 0;
+	uart->timer_running = 0;
+
+	outb(UART_FCR_ENABLE_FIFO	/* Enable FIFO's (if available) */
+	     | UART_FCR_CLEAR_RCVR	/* Clear receiver FIFO */
+	     | UART_FCR_CLEAR_XMIT	/* Clear transmitter FIFO */
+	     | UART_FCR_TRIGGER_4	/* Set FIFO trigger at 4-bytes */
+	/* NOTE: interrupt generated after T=(time)4-bytes
+	 * if less than UART_FCR_TRIGGER bytes received
+	 */
+	     ,uart->base + UART_FCR);	/* FIFO Control Register */
+
+	if ((inb(uart->base + UART_IIR) & 0xf0) == 0xc0)
+		uart->fifo_limit = 16;
+	if (uart->divisor != 0) {
+		uart->old_line_ctrl_reg = inb(uart->base + UART_LCR);
+		outb(UART_LCR_DLAB	/* Divisor latch access bit */
+		     ,uart->base + UART_LCR);	/* Line Control Register */
+		uart->old_divisor_lsb = inb(uart->base + UART_DLL);
+		uart->old_divisor_msb = inb(uart->base + UART_DLM);
+
+		outb(uart->divisor
+		     ,uart->base + UART_DLL);	/* Divisor Latch Low */
+		outb(0
+		     ,uart->base + UART_DLM);	/* Divisor Latch High */
+		/* DLAB is reset to 0 in next outb() */
+	}
+	/* Set serial parameters (parity off, etc) */
+	outb(UART_LCR_WLEN8	/* 8 data-bits */
+	     | 0		/* 1 stop-bit */
+	     | 0		/* parity off */
+	     | 0		/* DLAB = 0 */
+	     ,uart->base + UART_LCR);	/* Line Control Register */
+
+	switch (uart->adaptor) {
+	default:
+		outb(UART_MCR_RTS	/* Set Request-To-Send line active */
+		     | UART_MCR_DTR	/* Set Data-Terminal-Ready line active */
+		     | UART_MCR_OUT2	/* Set OUT2 - not always required, but when
+					 * it is, it is ESSENTIAL for enabling interrupts
+				 */
+		     ,uart->base + UART_MCR);	/* Modem Control Register */
+		break;
+	case SNDRV_SERIAL_MS124W_SA:
+	case SNDRV_SERIAL_MS124W_MB:
+		/* MS-124W can draw power from RTS and DTR if they
+		   are in opposite states. */ 
+		outb(UART_MCR_RTS | (0&UART_MCR_DTR) | UART_MCR_OUT2,
+		     uart->base + UART_MCR);
+		break;
+	case SNDRV_SERIAL_MS124T:
+		/* MS-124T can draw power from RTS and/or DTR (preferably
+		   both) if they are both asserted. */
+		outb(UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2,
+		     uart->base + UART_MCR);
+		break;
+	}
+
+	if (uart->irq < 0) {
+		byte = (0 & UART_IER_RDI)	/* Disable Receiver data interrupt */
+		    |(0 & UART_IER_THRI)	/* Disable Transmitter holding register empty interrupt */
+		    ;
+	} else if (uart->adaptor == SNDRV_SERIAL_MS124W_SA) {
+		byte = UART_IER_RDI	/* Enable Receiver data interrupt */
+		    | UART_IER_MSI	/* Enable Modem status interrupt */
+		    ;
+	} else if (uart->adaptor == SNDRV_SERIAL_GENERIC) {
+		byte = UART_IER_RDI	/* Enable Receiver data interrupt */
+		    | UART_IER_MSI	/* Enable Modem status interrupt */
+		    | UART_IER_THRI	/* Enable Transmitter holding register empty interrupt */
+		    ;
+	} else {
+		byte = UART_IER_RDI	/* Enable Receiver data interrupt */
+		    | UART_IER_THRI	/* Enable Transmitter holding register empty interrupt */
+		    ;
+	}
+	outb(byte, uart->base + UART_IER);	/* Interrupt enable Register */
+
+	inb(uart->base + UART_LSR);	/* Clear any pre-existing overrun indication */
+	inb(uart->base + UART_IIR);	/* Clear any pre-existing transmit interrupt */
+	inb(uart->base + UART_RX);	/* Clear any pre-existing receive interrupt */
+}
+
+static void snd_uart16550_do_close(struct snd_uart16550 * uart)
+{
+	if (uart->irq < 0)
+		snd_uart16550_del_timer(uart);
+
+	/* NOTE: may need to disable interrupts before de-registering out handler.
+	 * For now, the consequences are harmless.
+	 */
+
+	outb((0 & UART_IER_RDI)		/* Disable Receiver data interrupt */
+	     |(0 & UART_IER_THRI)	/* Disable Transmitter holding register empty interrupt */
+	     ,uart->base + UART_IER);	/* Interrupt enable Register */
+
+	switch (uart->adaptor) {
+	default:
+		outb((0 & UART_MCR_RTS)		/* Deactivate Request-To-Send line  */
+		     |(0 & UART_MCR_DTR)	/* Deactivate Data-Terminal-Ready line */
+		     |(0 & UART_MCR_OUT2)	/* Deactivate OUT2 */
+		     ,uart->base + UART_MCR);	/* Modem Control Register */
+	  break;
+	case SNDRV_SERIAL_MS124W_SA:
+	case SNDRV_SERIAL_MS124W_MB:
+		/* MS-124W can draw power from RTS and DTR if they
+		   are in opposite states; leave it powered. */ 
+		outb(UART_MCR_RTS | (0&UART_MCR_DTR) | (0&UART_MCR_OUT2),
+		     uart->base + UART_MCR);
+		break;
+	case SNDRV_SERIAL_MS124T:
+		/* MS-124T can draw power from RTS and/or DTR (preferably
+		   both) if they are both asserted; leave it powered. */
+		outb(UART_MCR_RTS | UART_MCR_DTR | (0&UART_MCR_OUT2),
+		     uart->base + UART_MCR);
+		break;
+	}
+
+	inb(uart->base + UART_IIR);	/* Clear any outstanding interrupts */
+
+	/* Restore old divisor */
+	if (uart->divisor != 0) {
+		outb(UART_LCR_DLAB		/* Divisor latch access bit */
+		     ,uart->base + UART_LCR);	/* Line Control Register */
+		outb(uart->old_divisor_lsb
+		     ,uart->base + UART_DLL);	/* Divisor Latch Low */
+		outb(uart->old_divisor_msb
+		     ,uart->base + UART_DLM);	/* Divisor Latch High */
+		/* Restore old LCR (data bits, stop bits, parity, DLAB) */
+		outb(uart->old_line_ctrl_reg
+		     ,uart->base + UART_LCR);	/* Line Control Register */
+	}
+}
+
+static int snd_uart16550_input_open(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_uart16550 *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	if (uart->filemode == SERIAL_MODE_NOT_OPENED)
+		snd_uart16550_do_open(uart);
+	uart->filemode |= SERIAL_MODE_INPUT_OPEN;
+	uart->midi_input[substream->number] = substream;
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+	return 0;
+}
+
+static int snd_uart16550_input_close(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_uart16550 *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	uart->filemode &= ~SERIAL_MODE_INPUT_OPEN;
+	uart->midi_input[substream->number] = NULL;
+	if (uart->filemode == SERIAL_MODE_NOT_OPENED)
+		snd_uart16550_do_close(uart);
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+	return 0;
+}
+
+static void snd_uart16550_input_trigger(struct snd_rawmidi_substream *substream,
+					int up)
+{
+	unsigned long flags;
+	struct snd_uart16550 *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	if (up)
+		uart->filemode |= SERIAL_MODE_INPUT_TRIGGERED;
+	else
+		uart->filemode &= ~SERIAL_MODE_INPUT_TRIGGERED;
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+}
+
+static int snd_uart16550_output_open(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_uart16550 *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	if (uart->filemode == SERIAL_MODE_NOT_OPENED)
+		snd_uart16550_do_open(uart);
+	uart->filemode |= SERIAL_MODE_OUTPUT_OPEN;
+	uart->midi_output[substream->number] = substream;
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+	return 0;
+};
+
+static int snd_uart16550_output_close(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_uart16550 *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	uart->filemode &= ~SERIAL_MODE_OUTPUT_OPEN;
+	uart->midi_output[substream->number] = NULL;
+	if (uart->filemode == SERIAL_MODE_NOT_OPENED)
+		snd_uart16550_do_close(uart);
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+	return 0;
+};
+
+static inline int snd_uart16550_buffer_can_write(struct snd_uart16550 *uart,
+						 int Num)
+{
+	if (uart->buff_in_count + Num < TX_BUFF_SIZE)
+		return 1;
+	else
+		return 0;
+}
+
+static inline int snd_uart16550_write_buffer(struct snd_uart16550 *uart,
+					     unsigned char byte)
+{
+	unsigned short buff_in = uart->buff_in;
+	if (uart->buff_in_count < TX_BUFF_SIZE) {
+		uart->tx_buff[buff_in] = byte;
+		buff_in++;
+		buff_in &= TX_BUFF_MASK;
+		uart->buff_in = buff_in;
+		uart->buff_in_count++;
+		if (uart->irq < 0) /* polling mode */
+			snd_uart16550_add_timer(uart);
+		return 1;
+	} else
+		return 0;
+}
+
+static int snd_uart16550_output_byte(struct snd_uart16550 *uart,
+				     struct snd_rawmidi_substream *substream,
+				     unsigned char midi_byte)
+{
+	if (uart->buff_in_count == 0                    /* Buffer empty? */
+	    && ((uart->adaptor != SNDRV_SERIAL_MS124W_SA &&
+	    uart->adaptor != SNDRV_SERIAL_GENERIC) ||
+		(uart->fifo_count == 0                  /* FIFO empty? */
+		 && (inb(uart->base + UART_MSR) & UART_MSR_CTS)))) { /* CTS? */
+
+	        /* Tx Buffer Empty - try to write immediately */
+		if ((inb(uart->base + UART_LSR) & UART_LSR_THRE) != 0) {
+		        /* Transmitter holding register (and Tx FIFO) empty */
+		        uart->fifo_count = 1;
+			outb(midi_byte, uart->base + UART_TX);
+		} else {
+		        if (uart->fifo_count < uart->fifo_limit) {
+			        uart->fifo_count++;
+				outb(midi_byte, uart->base + UART_TX);
+			} else {
+			        /* Cannot write (buffer empty) -
+				 * put char in buffer */
+				snd_uart16550_write_buffer(uart, midi_byte);
+			}
+		}
+	} else {
+		if (!snd_uart16550_write_buffer(uart, midi_byte)) {
+			snd_printk(KERN_WARNING
+				   "%s: Buffer overrun on device at 0x%lx\n",
+				   uart->rmidi->name, uart->base);
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+static void snd_uart16550_output_write(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	unsigned char midi_byte, addr_byte;
+	struct snd_uart16550 *uart = substream->rmidi->private_data;
+	char first;
+	static unsigned long lasttime = 0;
+	
+	/* Interrupts are disabled during the updating of the tx_buff,
+	 * since it is 'bad' to have two processes updating the same
+	 * variables (ie buff_in & buff_out)
+	 */
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+
+	if (uart->irq < 0)	/* polling */
+		snd_uart16550_io_loop(uart);
+
+	if (uart->adaptor == SNDRV_SERIAL_MS124W_MB) {
+		while (1) {
+			/* buffer full? */
+			/* in this mode we need two bytes of space */
+			if (uart->buff_in_count > TX_BUFF_SIZE - 2)
+				break;
+			if (snd_rawmidi_transmit(substream, &midi_byte, 1) != 1)
+				break;
+#ifdef SNDRV_SERIAL_MS124W_MB_NOCOMBO
+			/* select exactly one of the four ports */
+			addr_byte = (1 << (substream->number + 4)) | 0x08;
+#else
+			/* select any combination of the four ports */
+			addr_byte = (substream->number << 4) | 0x08;
+			/* ...except none */
+			if (addr_byte == 0x08)
+				addr_byte = 0xf8;
+#endif
+			snd_uart16550_output_byte(uart, substream, addr_byte);
+			/* send midi byte */
+			snd_uart16550_output_byte(uart, substream, midi_byte);
+		}
+	} else {
+		first = 0;
+		while (snd_rawmidi_transmit_peek(substream, &midi_byte, 1) == 1) {
+			/* Also send F5 after 3 seconds with no data
+			 * to handle device disconnect */
+			if (first == 0 &&
+			    (uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS ||
+			     uart->adaptor == SNDRV_SERIAL_GENERIC) &&
+			    (uart->prev_out != substream->number ||
+			     time_after(jiffies, lasttime + 3*HZ))) {
+
+				if (snd_uart16550_buffer_can_write(uart, 3)) {
+					/* Roland Soundcanvas part selection */
+					/* If this substream of the data is
+					 * different previous substream
+					 * in this uart, send the change part
+					 * event
+					 */
+					uart->prev_out = substream->number;
+					/* change part */
+					snd_uart16550_output_byte(uart, substream,
+								  0xf5);
+					/* data */
+					snd_uart16550_output_byte(uart, substream,
+								  uart->prev_out + 1);
+					/* If midi_byte is a data byte,
+					 * send the previous status byte */
+					if (midi_byte < 0x80 &&
+					    uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS)
+						snd_uart16550_output_byte(uart, substream, uart->prev_status[uart->prev_out]);
+				} else if (!uart->drop_on_full)
+					break;
+
+			}
+
+			/* send midi byte */
+			if (!snd_uart16550_output_byte(uart, substream, midi_byte) &&
+			    !uart->drop_on_full )
+				break;
+
+			if (midi_byte >= 0x80 && midi_byte < 0xf0)
+				uart->prev_status[uart->prev_out] = midi_byte;
+			first = 1;
+
+			snd_rawmidi_transmit_ack( substream, 1 );
+		}
+		lasttime = jiffies;
+	}
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+}
+
+static void snd_uart16550_output_trigger(struct snd_rawmidi_substream *substream,
+					 int up)
+{
+	unsigned long flags;
+	struct snd_uart16550 *uart = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&uart->open_lock, flags);
+	if (up)
+		uart->filemode |= SERIAL_MODE_OUTPUT_TRIGGERED;
+	else
+		uart->filemode &= ~SERIAL_MODE_OUTPUT_TRIGGERED;
+	spin_unlock_irqrestore(&uart->open_lock, flags);
+	if (up)
+		snd_uart16550_output_write(substream);
+}
+
+static struct snd_rawmidi_ops snd_uart16550_output =
+{
+	.open =		snd_uart16550_output_open,
+	.close =	snd_uart16550_output_close,
+	.trigger =	snd_uart16550_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_uart16550_input =
+{
+	.open =		snd_uart16550_input_open,
+	.close =	snd_uart16550_input_close,
+	.trigger =	snd_uart16550_input_trigger,
+};
+
+static int snd_uart16550_free(struct snd_uart16550 *uart)
+{
+	if (uart->irq >= 0)
+		free_irq(uart->irq, uart);
+	release_and_free_resource(uart->res_base);
+	kfree(uart);
+	return 0;
+};
+
+static int snd_uart16550_dev_free(struct snd_device *device)
+{
+	struct snd_uart16550 *uart = device->device_data;
+	return snd_uart16550_free(uart);
+}
+
+static int __devinit snd_uart16550_create(struct snd_card *card,
+				       unsigned long iobase,
+				       int irq,
+				       unsigned int speed,
+				       unsigned int base,
+				       int adaptor,
+				       int droponfull,
+				       struct snd_uart16550 **ruart)
+{
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_uart16550_dev_free,
+	};
+	struct snd_uart16550 *uart;
+	int err;
+
+
+	if ((uart = kzalloc(sizeof(*uart), GFP_KERNEL)) == NULL)
+		return -ENOMEM;
+	uart->adaptor = adaptor;
+	uart->card = card;
+	spin_lock_init(&uart->open_lock);
+	uart->irq = -1;
+	uart->base = iobase;
+	uart->drop_on_full = droponfull;
+
+	if ((err = snd_uart16550_detect(uart)) <= 0) {
+		printk(KERN_ERR "no UART detected at 0x%lx\n", iobase);
+		snd_uart16550_free(uart);
+		return -ENODEV;
+	}
+
+	if (irq >= 0 && irq != SNDRV_AUTO_IRQ) {
+		if (request_irq(irq, snd_uart16550_interrupt,
+				IRQF_DISABLED, "Serial MIDI", uart)) {
+			snd_printk(KERN_WARNING
+				   "irq %d busy. Using Polling.\n", irq);
+		} else {
+			uart->irq = irq;
+		}
+	}
+	uart->divisor = base / speed;
+	uart->speed = base / (unsigned int)uart->divisor;
+	uart->speed_base = base;
+	uart->prev_out = -1;
+	uart->prev_in = 0;
+	uart->rstatus = 0;
+	memset(uart->prev_status, 0x80, sizeof(unsigned char) * SNDRV_SERIAL_MAX_OUTS);
+	init_timer(&uart->buffer_timer);
+	uart->buffer_timer.function = snd_uart16550_buffer_timer;
+	uart->buffer_timer.data = (unsigned long)uart;
+	uart->timer_running = 0;
+
+	/* Register device */
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, uart, &ops)) < 0) {
+		snd_uart16550_free(uart);
+		return err;
+	}
+
+	switch (uart->adaptor) {
+	case SNDRV_SERIAL_MS124W_SA:
+	case SNDRV_SERIAL_MS124W_MB:
+		/* MS-124W can draw power from RTS and DTR if they
+		   are in opposite states. */ 
+		outb(UART_MCR_RTS | (0&UART_MCR_DTR), uart->base + UART_MCR);
+		break;
+	case SNDRV_SERIAL_MS124T:
+		/* MS-124T can draw power from RTS and/or DTR (preferably
+		   both) if they are asserted. */
+		outb(UART_MCR_RTS | UART_MCR_DTR, uart->base + UART_MCR);
+		break;
+	default:
+		break;
+	}
+
+	if (ruart)
+		*ruart = uart;
+
+	return 0;
+}
+
+static void __devinit snd_uart16550_substreams(struct snd_rawmidi_str *stream)
+{
+	struct snd_rawmidi_substream *substream;
+
+	list_for_each_entry(substream, &stream->substreams, list) {
+		sprintf(substream->name, "Serial MIDI %d", substream->number + 1);
+	}
+}
+
+static int __devinit snd_uart16550_rmidi(struct snd_uart16550 *uart, int device,
+				      int outs, int ins,
+				      struct snd_rawmidi **rmidi)
+{
+	struct snd_rawmidi *rrawmidi;
+	int err;
+
+	err = snd_rawmidi_new(uart->card, "UART Serial MIDI", device,
+			      outs, ins, &rrawmidi);
+	if (err < 0)
+		return err;
+	snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+			    &snd_uart16550_input);
+	snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+			    &snd_uart16550_output);
+	strcpy(rrawmidi->name, "Serial MIDI");
+	snd_uart16550_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]);
+	snd_uart16550_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]);
+	rrawmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+			       SNDRV_RAWMIDI_INFO_INPUT |
+			       SNDRV_RAWMIDI_INFO_DUPLEX;
+	rrawmidi->private_data = uart;
+	if (rmidi)
+		*rmidi = rrawmidi;
+	return 0;
+}
+
+static int __devinit snd_serial_probe(struct platform_device *devptr)
+{
+	struct snd_card *card;
+	struct snd_uart16550 *uart;
+	int err;
+	int dev = devptr->id;
+
+	switch (adaptor[dev]) {
+	case SNDRV_SERIAL_SOUNDCANVAS:
+		ins[dev] = 1;
+		break;
+	case SNDRV_SERIAL_MS124T:
+	case SNDRV_SERIAL_MS124W_SA:
+		outs[dev] = 1;
+		ins[dev] = 1;
+		break;
+	case SNDRV_SERIAL_MS124W_MB:
+		outs[dev] = 16;
+		ins[dev] = 1;
+		break;
+	case SNDRV_SERIAL_GENERIC:
+		break;
+	default:
+		snd_printk(KERN_ERR
+			   "Adaptor type is out of range 0-%d (%d)\n",
+			   SNDRV_SERIAL_MAX_ADAPTOR, adaptor[dev]);
+		return -ENODEV;
+	}
+
+	if (outs[dev] < 1 || outs[dev] > SNDRV_SERIAL_MAX_OUTS) {
+		snd_printk(KERN_ERR
+			   "Count of outputs is out of range 1-%d (%d)\n",
+			   SNDRV_SERIAL_MAX_OUTS, outs[dev]);
+		return -ENODEV;
+	}
+
+	if (ins[dev] < 1 || ins[dev] > SNDRV_SERIAL_MAX_INS) {
+		snd_printk(KERN_ERR
+			   "Count of inputs is out of range 1-%d (%d)\n",
+			   SNDRV_SERIAL_MAX_INS, ins[dev]);
+		return -ENODEV;
+	}
+
+	err  = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	strcpy(card->driver, "Serial");
+	strcpy(card->shortname, "Serial MIDI (UART16550A)");
+
+	if ((err = snd_uart16550_create(card,
+					port[dev],
+					irq[dev],
+					speed[dev],
+					base[dev],
+					adaptor[dev],
+					droponfull[dev],
+					&uart)) < 0)
+		goto _err;
+
+	err = snd_uart16550_rmidi(uart, 0, outs[dev], ins[dev], &uart->rmidi);
+	if (err < 0)
+		goto _err;
+
+	sprintf(card->longname, "%s [%s] at %#lx, irq %d",
+		card->shortname,
+		adaptor_names[uart->adaptor],
+		uart->base,
+		uart->irq);
+
+	snd_card_set_dev(card, &devptr->dev);
+
+	if ((err = snd_card_register(card)) < 0)
+		goto _err;
+
+	platform_set_drvdata(devptr, card);
+	return 0;
+
+ _err:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit snd_serial_remove(struct platform_device *devptr)
+{
+	snd_card_free(platform_get_drvdata(devptr));
+	platform_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#define SND_SERIAL_DRIVER	"snd_serial_u16550"
+
+static struct platform_driver snd_serial_driver = {
+	.probe		= snd_serial_probe,
+	.remove		= __devexit_p( snd_serial_remove),
+	.driver		= {
+		.name	= SND_SERIAL_DRIVER
+	},
+};
+
+static void snd_serial_unregister_all(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(devices); ++i)
+		platform_device_unregister(devices[i]);
+	platform_driver_unregister(&snd_serial_driver);
+}
+
+static int __init alsa_card_serial_init(void)
+{
+	int i, cards, err;
+
+	if ((err = platform_driver_register(&snd_serial_driver)) < 0)
+		return err;
+
+	cards = 0;
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		struct platform_device *device;
+		if (! enable[i])
+			continue;
+		device = platform_device_register_simple(SND_SERIAL_DRIVER,
+							 i, NULL, 0);
+		if (IS_ERR(device))
+			continue;
+		if (!platform_get_drvdata(device)) {
+			platform_device_unregister(device);
+			continue;
+		}
+		devices[i] = device;
+		cards++;
+	}
+	if (! cards) {
+#ifdef MODULE
+		printk(KERN_ERR "serial midi soundcard not found or device busy\n");
+#endif
+		snd_serial_unregister_all();
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_serial_exit(void)
+{
+	snd_serial_unregister_all();
+}
+
+module_init(alsa_card_serial_init)
+module_exit(alsa_card_serial_exit)
diff --git a/linux/sound/drivers/virmidi.c b/linux/sound/drivers/virmidi.c
new file mode 100644
index 0000000..f4cd493
--- /dev/null
+++ b/linux/sound/drivers/virmidi.c
@@ -0,0 +1,197 @@
+/*
+ *  Dummy soundcard for virtual rawmidi devices
+ *
+ *  Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ * VIRTUAL RAW MIDI DEVICE CARDS
+ *
+ * This dummy card contains up to 4 virtual rawmidi devices.
+ * They are not real rawmidi devices but just associated with sequencer
+ * clients, so that any input/output sources can be connected as a raw
+ * MIDI device arbitrary.
+ * Also, multiple access is allowed to a single rawmidi device.
+ *
+ * Typical usage is like following:
+ * - Load snd-virmidi module.
+ *	# modprobe snd-virmidi index=2
+ *   Then, sequencer clients 72:0 to 75:0 will be created, which are
+ *   mapped from /dev/snd/midiC1D0 to /dev/snd/midiC1D3, respectively.
+ *
+ * - Connect input/output via aconnect.
+ *	% aconnect 64:0 72:0	# keyboard input redirection 64:0 -> 72:0
+ *	% aconnect 72:0 65:0	# output device redirection 72:0 -> 65:0
+ *
+ * - Run application using a midi device (eg. /dev/snd/midiC1D0)
+ */
+
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/seq_kernel.h>
+#include <sound/seq_virmidi.h>
+#include <sound/initval.h>
+
+/* hack: OSS defines midi_devs, so undefine it (versioned symbols) */
+#undef midi_devs
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("Dummy soundcard for virtual rawmidi devices");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ALSA,Virtual rawmidi device}}");
+
+#define MAX_MIDI_DEVICES	4
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
+static int midi_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for virmidi soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for virmidi soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable this soundcard.");
+module_param_array(midi_devs, int, NULL, 0444);
+MODULE_PARM_DESC(midi_devs, "MIDI devices # (1-4)");
+
+struct snd_card_virmidi {
+	struct snd_card *card;
+	struct snd_rawmidi *midi[MAX_MIDI_DEVICES];
+};
+
+static struct platform_device *devices[SNDRV_CARDS];
+
+
+static int __devinit snd_virmidi_probe(struct platform_device *devptr)
+{
+	struct snd_card *card;
+	struct snd_card_virmidi *vmidi;
+	int idx, err;
+	int dev = devptr->id;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_card_virmidi), &card);
+	if (err < 0)
+		return err;
+	vmidi = card->private_data;
+	vmidi->card = card;
+
+	if (midi_devs[dev] > MAX_MIDI_DEVICES) {
+		snd_printk(KERN_WARNING
+			   "too much midi devices for virmidi %d: "
+			   "force to use %d\n", dev, MAX_MIDI_DEVICES);
+		midi_devs[dev] = MAX_MIDI_DEVICES;
+	}
+	for (idx = 0; idx < midi_devs[dev]; idx++) {
+		struct snd_rawmidi *rmidi;
+		struct snd_virmidi_dev *rdev;
+		if ((err = snd_virmidi_new(card, idx, &rmidi)) < 0)
+			goto __nodev;
+		rdev = rmidi->private_data;
+		vmidi->midi[idx] = rmidi;
+		strcpy(rmidi->name, "Virtual Raw MIDI");
+		rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH;
+	}
+	
+	strcpy(card->driver, "VirMIDI");
+	strcpy(card->shortname, "VirMIDI");
+	sprintf(card->longname, "Virtual MIDI Card %i", dev + 1);
+
+	snd_card_set_dev(card, &devptr->dev);
+
+	if ((err = snd_card_register(card)) == 0) {
+		platform_set_drvdata(devptr, card);
+		return 0;
+	}
+      __nodev:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit snd_virmidi_remove(struct platform_device *devptr)
+{
+	snd_card_free(platform_get_drvdata(devptr));
+	platform_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#define SND_VIRMIDI_DRIVER	"snd_virmidi"
+
+static struct platform_driver snd_virmidi_driver = {
+	.probe		= snd_virmidi_probe,
+	.remove		= __devexit_p(snd_virmidi_remove),
+	.driver		= {
+		.name	= SND_VIRMIDI_DRIVER
+	},
+};
+
+static void snd_virmidi_unregister_all(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(devices); ++i)
+		platform_device_unregister(devices[i]);
+	platform_driver_unregister(&snd_virmidi_driver);
+}
+
+static int __init alsa_card_virmidi_init(void)
+{
+	int i, cards, err;
+
+	if ((err = platform_driver_register(&snd_virmidi_driver)) < 0)
+		return err;
+
+	cards = 0;
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		struct platform_device *device;
+		if (! enable[i])
+			continue;
+		device = platform_device_register_simple(SND_VIRMIDI_DRIVER,
+							 i, NULL, 0);
+		if (IS_ERR(device))
+			continue;
+		if (!platform_get_drvdata(device)) {
+			platform_device_unregister(device);
+			continue;
+		}
+		devices[i] = device;
+		cards++;
+	}
+	if (!cards) {
+#ifdef MODULE
+		printk(KERN_ERR "Card-VirMIDI soundcard not found or device busy\n");
+#endif
+		snd_virmidi_unregister_all();
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_virmidi_exit(void)
+{
+	snd_virmidi_unregister_all();
+}
+
+module_init(alsa_card_virmidi_init)
+module_exit(alsa_card_virmidi_exit)
diff --git a/linux/sound/drivers/vx/Makefile b/linux/sound/drivers/vx/Makefile
new file mode 100644
index 0000000..9a168a3
--- /dev/null
+++ b/linux/sound/drivers/vx/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-vx-lib-objs := vx_core.o vx_hwdep.o vx_pcm.o vx_mixer.o vx_cmd.o vx_uer.o
+
+obj-$(CONFIG_SND_VX_LIB) += snd-vx-lib.o
diff --git a/linux/sound/drivers/vx/vx_cmd.c b/linux/sound/drivers/vx/vx_cmd.c
new file mode 100644
index 0000000..23f4857
--- /dev/null
+++ b/linux/sound/drivers/vx/vx_cmd.c
@@ -0,0 +1,109 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * DSP commands
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/vx_core.h>
+#include "vx_cmd.h"
+
+/*
+ * Array of DSP commands
+ */
+static struct vx_cmd_info vx_dsp_cmds[] = {
+[CMD_VERSION] =			{ 0x010000, 2, RMH_SSIZE_FIXED, 1 },
+[CMD_SUPPORTED] =		{ 0x020000, 1, RMH_SSIZE_FIXED, 2 },
+[CMD_TEST_IT] =			{ 0x040000, 1, RMH_SSIZE_FIXED, 1 },
+[CMD_SEND_IRQA] =		{ 0x070001, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_IBL] =			{ 0x080000, 1, RMH_SSIZE_FIXED, 4 },
+[CMD_ASYNC] =			{ 0x0A0000, 1, RMH_SSIZE_ARG, 0 },
+[CMD_RES_PIPE] =		{ 0x400000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_FREE_PIPE] =		{ 0x410000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_CONF_PIPE] =		{ 0x42A101, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_ABORT_CONF_PIPE] =		{ 0x42A100, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_PARAM_OUTPUT_PIPE] =	{ 0x43A000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_STOP_PIPE] =		{ 0x470004, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_PIPE_STATE] =		{ 0x480000, 1, RMH_SSIZE_FIXED, 1 },
+[CMD_PIPE_SPL_COUNT] =		{ 0x49A000, 2, RMH_SSIZE_FIXED, 2 },
+[CMD_CAN_START_PIPE] =		{ 0x4b0000, 1, RMH_SSIZE_FIXED, 1 },
+[CMD_SIZE_HBUFFER] =		{ 0x4C0000, 1, RMH_SSIZE_FIXED, 1 },
+[CMD_START_STREAM] =		{ 0x80A000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_START_ONE_STREAM] =	{ 0x800000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_PAUSE_STREAM] =		{ 0x81A000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_PAUSE_ONE_STREAM] =	{ 0x810000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_STREAM_OUT_LEVEL_ADJUST] =	{ 0x828000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_STOP_STREAM] =		{ 0x830000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_FORMAT_STREAM_OUT] =	{ 0x868000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_FORMAT_STREAM_IN] =	{ 0x878800, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_GET_STREAM_STATE] =	{ 0x890001, 2, RMH_SSIZE_FIXED, 1 },
+[CMD_DROP_BYTES_AWAY] =		{ 0x8A8000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_GET_REMAINING_BYTES] =	{ 0x8D0800, 1, RMH_SSIZE_FIXED, 2 },
+[CMD_CONNECT_AUDIO] =		{ 0xC10000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_AUDIO_LEVEL_ADJUST] =	{ 0xC2A000, 3, RMH_SSIZE_FIXED, 0 },
+[CMD_AUDIO_VU_PIC_METER] =	{ 0xC3A003, 2, RMH_SSIZE_FIXED, 1 },
+[CMD_GET_AUDIO_LEVELS] =	{ 0xC4A000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_GET_NOTIFY_EVENT] =	{ 0x4D0000, 1, RMH_SSIZE_ARG, 0 },
+[CMD_INFO_NOTIFIED] =		{ 0x0B0000, 1, RMH_SSIZE_FIXED, 2 },
+[CMD_ACCESS_IO_FCT] =		{ 0x098000, 1, RMH_SSIZE_ARG, 0 },
+[CMD_STATUS_R_BUFFERS] =	{ 0x440000, 1, RMH_SSIZE_ARG, 0 },
+[CMD_UPDATE_R_BUFFERS] =	{ 0x848000, 4, RMH_SSIZE_FIXED, 0 },
+[CMD_LOAD_EFFECT_CONTEXT] =	{ 0x0c8000, 3, RMH_SSIZE_FIXED, 1 },
+[CMD_EFFECT_ONE_PIPE] =		{ 0x458000, 0, RMH_SSIZE_FIXED, 0 },
+[CMD_MODIFY_CLOCK] =		{ 0x0d0000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_STREAM1_OUT_SET_N_LEVELS] ={ 0x858000, 3, RMH_SSIZE_FIXED, 0 },
+[CMD_PURGE_STREAM_DCMDS] =	{ 0x8b8000, 3, RMH_SSIZE_FIXED, 0 },
+[CMD_NOTIFY_PIPE_TIME] =	{ 0x4e0000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_LOAD_EFFECT_CONTEXT_PACKET] = { 0x0c8000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_RELIC_R_BUFFER] =		{ 0x8e0800, 1, RMH_SSIZE_FIXED, 1 },
+[CMD_RESYNC_AUDIO_INPUTS] =	{ 0x0e0000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_NOTIFY_STREAM_TIME] =	{ 0x8f0000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_STREAM_SAMPLE_COUNT] =	{ 0x900000, 1, RMH_SSIZE_FIXED, 2 },
+[CMD_CONFIG_TIME_CODE] =	{ 0x050000, 2, RMH_SSIZE_FIXED, 0 },
+[CMD_GET_TIME_CODE] =		{ 0x060000, 1, RMH_SSIZE_FIXED, 5 },
+[CMD_MANAGE_SIGNAL] =		{ 0x0f0000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_PARAMETER_STREAM_OUT] =	{ 0x91A000, 3, RMH_SSIZE_FIXED, 0 },
+[CMD_READ_BOARD_FREQ] =		{ 0x030000, 1, RMH_SSIZE_FIXED, 2 },
+[CMD_GET_STREAM_LEVELS] =	{ 0x8c0000, 1, RMH_SSIZE_FIXED, 3 },
+[CMD_PURGE_PIPE_DCMDS] =	{ 0x4f8000, 3, RMH_SSIZE_FIXED, 0 },
+// [CMD_SET_STREAM_OUT_EFFECTS] =	{ 0x888000, 34, RMH_SSIZE_FIXED, 0 },
+// [CMD_GET_STREAM_OUT_EFFECTS] =	{ 0x928000, 2, RMH_SSIZE_FIXED, 32 },
+[CMD_CONNECT_MONITORING] =	{ 0xC00000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_STREAM2_OUT_SET_N_LEVELS] = { 0x938000, 3, RMH_SSIZE_FIXED, 0 },
+[CMD_CANCEL_R_BUFFERS] =	{ 0x948000, 4, RMH_SSIZE_FIXED, 0 },
+[CMD_NOTIFY_END_OF_BUFFER] =	{ 0x950000, 1, RMH_SSIZE_FIXED, 0 },
+[CMD_GET_STREAM_VU_METER] =	{ 0x95A000, 2, RMH_SSIZE_ARG, 0 },
+};
+
+/**
+ * vx_init_rmh - initialize the RMH instance
+ * @rmh: the rmh pointer to be initialized
+ * @cmd: the rmh command to be set
+ */
+void vx_init_rmh(struct vx_rmh *rmh, unsigned int cmd)
+{
+	if (snd_BUG_ON(cmd >= CMD_LAST_INDEX))
+		return;
+	rmh->LgCmd = vx_dsp_cmds[cmd].length;
+	rmh->LgStat = vx_dsp_cmds[cmd].st_length;
+	rmh->DspStat = vx_dsp_cmds[cmd].st_type;
+	rmh->Cmd[0] = vx_dsp_cmds[cmd].opcode;
+}
+
diff --git a/linux/sound/drivers/vx/vx_cmd.h b/linux/sound/drivers/vx/vx_cmd.h
new file mode 100644
index 0000000..a85248b
--- /dev/null
+++ b/linux/sound/drivers/vx/vx_cmd.h
@@ -0,0 +1,246 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * Definitions of DSP commands
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#ifndef __VX_CMD_H
+#define __VX_CMD_H
+
+enum {
+	CMD_VERSION,
+	CMD_SUPPORTED,
+	CMD_TEST_IT,
+	CMD_SEND_IRQA,
+	CMD_IBL,
+	CMD_ASYNC,
+	CMD_RES_PIPE,
+	CMD_FREE_PIPE,
+	CMD_CONF_PIPE,
+	CMD_ABORT_CONF_PIPE,
+	CMD_PARAM_OUTPUT_PIPE,
+	CMD_STOP_PIPE,
+	CMD_PIPE_STATE,
+	CMD_PIPE_SPL_COUNT,
+	CMD_CAN_START_PIPE,
+	CMD_SIZE_HBUFFER,
+	CMD_START_STREAM,
+	CMD_START_ONE_STREAM,
+	CMD_PAUSE_STREAM,
+	CMD_PAUSE_ONE_STREAM,
+	CMD_STREAM_OUT_LEVEL_ADJUST,
+	CMD_STOP_STREAM,
+	CMD_FORMAT_STREAM_OUT,
+	CMD_FORMAT_STREAM_IN,
+	CMD_GET_STREAM_STATE,
+	CMD_DROP_BYTES_AWAY,
+	CMD_GET_REMAINING_BYTES,
+	CMD_CONNECT_AUDIO,
+	CMD_AUDIO_LEVEL_ADJUST,
+	CMD_AUDIO_VU_PIC_METER,
+	CMD_GET_AUDIO_LEVELS,
+	CMD_GET_NOTIFY_EVENT,
+	CMD_INFO_NOTIFIED,
+	CMD_ACCESS_IO_FCT,
+	CMD_STATUS_R_BUFFERS,
+	CMD_UPDATE_R_BUFFERS,
+	CMD_LOAD_EFFECT_CONTEXT,
+	CMD_EFFECT_ONE_PIPE,
+	CMD_MODIFY_CLOCK,
+	CMD_STREAM1_OUT_SET_N_LEVELS,
+	CMD_PURGE_STREAM_DCMDS,
+	CMD_NOTIFY_PIPE_TIME,
+	CMD_LOAD_EFFECT_CONTEXT_PACKET,
+	CMD_RELIC_R_BUFFER,
+	CMD_RESYNC_AUDIO_INPUTS,
+	CMD_NOTIFY_STREAM_TIME,
+	CMD_STREAM_SAMPLE_COUNT,
+	CMD_CONFIG_TIME_CODE,
+	CMD_GET_TIME_CODE,
+	CMD_MANAGE_SIGNAL,
+	CMD_PARAMETER_STREAM_OUT,
+	CMD_READ_BOARD_FREQ,
+	CMD_GET_STREAM_LEVELS,
+	CMD_PURGE_PIPE_DCMDS,
+	// CMD_SET_STREAM_OUT_EFFECTS,
+	// CMD_GET_STREAM_OUT_EFFECTS,
+	CMD_CONNECT_MONITORING,
+	CMD_STREAM2_OUT_SET_N_LEVELS,
+	CMD_CANCEL_R_BUFFERS,
+	CMD_NOTIFY_END_OF_BUFFER,
+	CMD_GET_STREAM_VU_METER,
+	CMD_LAST_INDEX
+};
+
+struct vx_cmd_info {
+	unsigned int opcode;	/* command word */
+	int length;		/* command length (in words) */
+	int st_type;		/* status type (RMH_SSIZE_XXX) */
+	int st_length;		/* fixed length */
+};
+
+/* Family and code op of some DSP requests. */
+#define CODE_OP_PIPE_TIME                       0x004e0000
+#define CODE_OP_START_STREAM                    0x00800000
+#define CODE_OP_PAUSE_STREAM                    0x00810000
+#define CODE_OP_OUT_STREAM_LEVEL                0x00820000
+#define CODE_OP_UPDATE_R_BUFFERS                0x00840000
+#define CODE_OP_OUT_STREAM1_LEVEL_CURVE         0x00850000
+#define CODE_OP_OUT_STREAM2_LEVEL_CURVE         0x00930000
+#define CODE_OP_OUT_STREAM_FORMAT               0x00860000
+#define CODE_OP_STREAM_TIME                     0x008f0000
+#define CODE_OP_OUT_STREAM_EXTRAPARAMETER       0x00910000
+#define CODE_OP_OUT_AUDIO_LEVEL                 0x00c20000
+
+#define NOTIFY_LAST_COMMAND     0x00400000
+
+/* Values for a user delay */
+#define DC_DIFFERED_DELAY       (1<<BIT_DIFFERED_COMMAND)
+#define DC_NOTIFY_DELAY         (1<<BIT_NOTIFIED_COMMAND)
+#define DC_HBUFFER_DELAY        (1<<BIT_TIME_RELATIVE_TO_BUFFER)
+#define DC_MULTIPLE_DELAY       (1<<BIT_RESERVED)
+#define DC_STREAM_TIME_DELAY    (1<<BIT_STREAM_TIME)
+#define DC_CANCELLED_DELAY      (1<<BIT_CANCELLED_COMMAND)
+
+/* Values for tiDelayed field in TIME_INFO structure,
+ * and for pbPause field in PLAY_BUFFER_INFO structure
+ */
+#define BIT_DIFFERED_COMMAND                0
+#define BIT_NOTIFIED_COMMAND                1
+#define BIT_TIME_RELATIVE_TO_BUFFER         2
+#define BIT_RESERVED                        3
+#define BIT_STREAM_TIME                     4
+#define BIT_CANCELLED_COMMAND               5
+
+/* Access to the "Size" field of the response of the CMD_GET_NOTIFY_EVENT request. */
+#define GET_NOTIFY_EVENT_SIZE_FIELD_MASK    0x000000ff
+
+/* DSP commands general masks */
+#define OPCODE_MASK                 0x00ff0000
+#define DSP_DIFFERED_COMMAND_MASK   0x0000C000
+
+/* Notifications (NOTIFY_INFO) */
+#define ALL_CMDS_NOTIFIED                   0x0000  // reserved
+#define START_STREAM_NOTIFIED               0x0001
+#define PAUSE_STREAM_NOTIFIED               0x0002
+#define OUT_STREAM_LEVEL_NOTIFIED           0x0003
+#define OUT_STREAM_PARAMETER_NOTIFIED       0x0004  // left for backward compatibility
+#define OUT_STREAM_FORMAT_NOTIFIED          0x0004
+#define PIPE_TIME_NOTIFIED                  0x0005
+#define OUT_AUDIO_LEVEL_NOTIFIED            0x0006
+#define OUT_STREAM_LEVEL_CURVE_NOTIFIED     0x0007
+#define STREAM_TIME_NOTIFIED                0x0008
+#define OUT_STREAM_EXTRAPARAMETER_NOTIFIED  0x0009
+#define UNKNOWN_COMMAND_NOTIFIED            0xffff
+
+/* Output pipe parameters setting */
+#define MASK_VALID_PIPE_MPEG_PARAM      0x000040
+#define MASK_VALID_PIPE_BACKWARD_PARAM  0x000020
+#define MASK_SET_PIPE_MPEG_PARAM        0x000002
+#define MASK_SET_PIPE_BACKWARD_PARAM    0x000001
+
+#define MASK_DSP_WORD           0x00FFFFFF
+#define MASK_ALL_STREAM         0x00FFFFFF
+#define MASK_DSP_WORD_LEVEL     0x000001FF
+#define MASK_FIRST_FIELD        0x0000001F
+#define FIELD_SIZE              5
+
+#define COMMAND_RECORD_MASK     0x000800
+
+/* PipeManagement definition bits (PIPE_DECL_INFO) */
+#define P_UNDERRUN_SKIP_SOUND_MASK				0x01
+#define P_PREPARE_FOR_MPEG3_MASK				0x02
+#define P_DO_NOT_RESET_ANALOG_LEVELS			0x04
+#define P_ALLOW_UNDER_ALLOCATION_MASK			0x08
+#define P_DATA_MODE_MASK				0x10
+#define P_ASIO_BUFFER_MANAGEMENT_MASK			0x20
+
+#define BIT_SKIP_SOUND					0x08	// bit 3
+#define BIT_DATA_MODE					0x10	// bit 4
+    
+/* Bits in the CMD_MODIFY_CLOCK request. */
+#define CMD_MODIFY_CLOCK_FD_BIT     0x00000001
+#define CMD_MODIFY_CLOCK_T_BIT      0x00000002
+#define CMD_MODIFY_CLOCK_S_BIT      0x00000004
+
+/* Access to the results of the CMD_GET_TIME_CODE RMH. */
+#define TIME_CODE_V_MASK            0x00800000
+#define TIME_CODE_N_MASK            0x00400000
+#define TIME_CODE_B_MASK            0x00200000
+#define TIME_CODE_W_MASK            0x00100000
+
+/* Values for the CMD_MANAGE_SIGNAL RMH. */
+#define MANAGE_SIGNAL_TIME_CODE     0x01
+#define MANAGE_SIGNAL_MIDI          0x02
+
+/* Values for the CMD_CONFIG_TIME_CODE RMH. */
+#define CONFIG_TIME_CODE_CANCEL     0x00001000
+    
+/* Mask to get only the effective time from the
+ * high word out of the 2 returned by the DSP
+ */
+#define PCX_TIME_HI_MASK        0x000fffff
+
+/* Values for setting a H-Buffer time */
+#define HBUFFER_TIME_HIGH       0x00200000
+#define HBUFFER_TIME_LOW        0x00000000
+
+#define NOTIFY_MASK_TIME_HIGH   0x00400000
+#define MULTIPLE_MASK_TIME_HIGH 0x00100000
+#define STREAM_MASK_TIME_HIGH   0x00800000
+
+
+/*
+ *
+ */
+void vx_init_rmh(struct vx_rmh *rmh, unsigned int cmd);
+
+/**
+ * vx_send_pipe_cmd_params - fill first command word for pipe commands
+ * @rmh: the rmh to be modified
+ * @is_capture: 0 = playback, 1 = capture operation
+ * @param1: first pipe-parameter
+ * @param2: second pipe-parameter
+ */
+static inline void vx_set_pipe_cmd_params(struct vx_rmh *rmh, int is_capture,
+					  int param1, int param2)
+{
+	if (is_capture)
+		rmh->Cmd[0] |= COMMAND_RECORD_MASK;
+	rmh->Cmd[0] |= (((u32)param1 & MASK_FIRST_FIELD) << FIELD_SIZE) & MASK_DSP_WORD;
+		
+	if (param2)
+		rmh->Cmd[0] |= ((u32)param2 & MASK_FIRST_FIELD) & MASK_DSP_WORD;
+	
+}
+
+/**
+ * vx_set_stream_cmd_params - fill first command word for stream commands
+ * @rmh: the rmh to be modified
+ * @is_capture: 0 = playback, 1 = capture operation
+ * @pipe: the pipe index (zero-based)
+ */
+static inline void vx_set_stream_cmd_params(struct vx_rmh *rmh, int is_capture, int pipe)
+{
+	if (is_capture)
+		rmh->Cmd[0] |= COMMAND_RECORD_MASK;
+	rmh->Cmd[0] |= (((u32)pipe & MASK_FIRST_FIELD) << FIELD_SIZE) & MASK_DSP_WORD;
+}
+
+#endif /* __VX_CMD_H */
diff --git a/linux/sound/drivers/vx/vx_core.c b/linux/sound/drivers/vx/vx_core.c
new file mode 100644
index 0000000..19c6e37
--- /dev/null
+++ b/linux/sound/drivers/vx/vx_core.c
@@ -0,0 +1,827 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * Hardware core part
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/asoundef.h>
+#include <sound/info.h>
+#include <asm/io.h>
+#include <sound/vx_core.h>
+#include "vx_cmd.h"
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("Common routines for Digigram VX drivers");
+MODULE_LICENSE("GPL");
+
+
+/*
+ * vx_check_reg_bit - wait for the specified bit is set/reset on a register
+ * @reg: register to check
+ * @mask: bit mask
+ * @bit: resultant bit to be checked
+ * @time: time-out of loop in msec
+ *
+ * returns zero if a bit matches, or a negative error code.
+ */
+int snd_vx_check_reg_bit(struct vx_core *chip, int reg, int mask, int bit, int time)
+{
+	unsigned long end_time = jiffies + (time * HZ + 999) / 1000;
+#ifdef CONFIG_SND_DEBUG
+	static char *reg_names[VX_REG_MAX] = {
+		"ICR", "CVR", "ISR", "IVR", "RXH", "RXM", "RXL",
+		"DMA", "CDSP", "RFREQ", "RUER/V2", "DATA", "MEMIRQ",
+		"ACQ", "BIT0", "BIT1", "MIC0", "MIC1", "MIC2",
+		"MIC3", "INTCSR", "CNTRL", "GPIOC",
+		"LOFREQ", "HIFREQ", "CSUER", "RUER"
+	};
+#endif
+	do {
+		if ((snd_vx_inb(chip, reg) & mask) == bit)
+			return 0;
+		//msleep(10);
+	} while (time_after_eq(end_time, jiffies));
+	snd_printd(KERN_DEBUG "vx_check_reg_bit: timeout, reg=%s, mask=0x%x, val=0x%x\n", reg_names[reg], mask, snd_vx_inb(chip, reg));
+	return -EIO;
+}
+
+EXPORT_SYMBOL(snd_vx_check_reg_bit);
+
+/*
+ * vx_send_irq_dsp - set command irq bit
+ * @num: the requested IRQ type, IRQ_XXX
+ *
+ * this triggers the specified IRQ request
+ * returns 0 if successful, or a negative error code.
+ * 
+ */
+static int vx_send_irq_dsp(struct vx_core *chip, int num)
+{
+	int nirq;
+
+	/* wait for Hc = 0 */
+	if (snd_vx_check_reg_bit(chip, VX_CVR, CVR_HC, 0, 200) < 0)
+		return -EIO;
+
+	nirq = num;
+	if (vx_has_new_dsp(chip))
+		nirq += VXP_IRQ_OFFSET;
+	vx_outb(chip, CVR, (nirq >> 1) | CVR_HC);
+	return 0;
+}
+
+
+/*
+ * vx_reset_chk - reset CHK bit on ISR
+ *
+ * returns 0 if successful, or a negative error code.
+ */
+static int vx_reset_chk(struct vx_core *chip)
+{
+	/* Reset irq CHK */
+	if (vx_send_irq_dsp(chip, IRQ_RESET_CHK) < 0)
+		return -EIO;
+	/* Wait until CHK = 0 */
+	if (vx_check_isr(chip, ISR_CHK, 0, 200) < 0)
+		return -EIO;
+	return 0;
+}
+
+/*
+ * vx_transfer_end - terminate message transfer
+ * @cmd: IRQ message to send (IRQ_MESS_XXX_END)
+ *
+ * returns 0 if successful, or a negative error code.
+ * the error code can be VX-specific, retrieved via vx_get_error().
+ * NB: call with spinlock held!
+ */
+static int vx_transfer_end(struct vx_core *chip, int cmd)
+{
+	int err;
+
+	if ((err = vx_reset_chk(chip)) < 0)
+		return err;
+
+	/* irq MESS_READ/WRITE_END */
+	if ((err = vx_send_irq_dsp(chip, cmd)) < 0)
+		return err;
+
+	/* Wait CHK = 1 */
+	if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0)
+		return err;
+
+	/* If error, Read RX */
+	if ((err = vx_inb(chip, ISR)) & ISR_ERR) {
+		if ((err = vx_wait_for_rx_full(chip)) < 0) {
+			snd_printd(KERN_DEBUG "transfer_end: error in rx_full\n");
+			return err;
+		}
+		err = vx_inb(chip, RXH) << 16;
+		err |= vx_inb(chip, RXM) << 8;
+		err |= vx_inb(chip, RXL);
+		snd_printd(KERN_DEBUG "transfer_end: error = 0x%x\n", err);
+		return -(VX_ERR_MASK | err);
+	}
+	return 0;
+}
+
+/*
+ * vx_read_status - return the status rmh
+ * @rmh: rmh record to store the status
+ *
+ * returns 0 if successful, or a negative error code.
+ * the error code can be VX-specific, retrieved via vx_get_error().
+ * NB: call with spinlock held!
+ */
+static int vx_read_status(struct vx_core *chip, struct vx_rmh *rmh)
+{
+	int i, err, val, size;
+
+	/* no read necessary? */
+	if (rmh->DspStat == RMH_SSIZE_FIXED && rmh->LgStat == 0)
+		return 0;
+
+	/* Wait for RX full (with timeout protection)
+	 * The first word of status is in RX
+	 */
+	err = vx_wait_for_rx_full(chip);
+	if (err < 0)
+		return err;
+
+	/* Read RX */
+	val = vx_inb(chip, RXH) << 16;
+	val |= vx_inb(chip, RXM) << 8;
+	val |= vx_inb(chip, RXL);
+
+	/* If status given by DSP, let's decode its size */
+	switch (rmh->DspStat) {
+	case RMH_SSIZE_ARG:
+		size = val & 0xff;
+		rmh->Stat[0] = val & 0xffff00;
+		rmh->LgStat = size + 1;
+		break;
+	case RMH_SSIZE_MASK:
+		/* Let's count the arg numbers from a mask */
+		rmh->Stat[0] = val;
+		size = 0;
+		while (val) {
+			if (val & 0x01)
+				size++;
+			val >>= 1;
+		}
+		rmh->LgStat = size + 1;
+		break;
+	default:
+		/* else retrieve the status length given by the driver */
+		size = rmh->LgStat;
+		rmh->Stat[0] = val;  /* Val is the status 1st word */
+		size--;              /* hence adjust remaining length */
+		break;
+        }
+
+	if (size < 1)
+		return 0;
+	if (snd_BUG_ON(size > SIZE_MAX_STATUS))
+		return -EINVAL;
+
+	for (i = 1; i <= size; i++) {
+		/* trigger an irq MESS_WRITE_NEXT */
+		err = vx_send_irq_dsp(chip, IRQ_MESS_WRITE_NEXT);
+		if (err < 0)
+			return err;
+		/* Wait for RX full (with timeout protection) */
+		err = vx_wait_for_rx_full(chip);
+		if (err < 0)
+			return err;
+		rmh->Stat[i] = vx_inb(chip, RXH) << 16;
+		rmh->Stat[i] |= vx_inb(chip, RXM) <<  8;
+		rmh->Stat[i] |= vx_inb(chip, RXL);
+	}
+
+	return vx_transfer_end(chip, IRQ_MESS_WRITE_END);
+}
+
+
+#define MASK_MORE_THAN_1_WORD_COMMAND   0x00008000
+#define MASK_1_WORD_COMMAND             0x00ff7fff
+
+/*
+ * vx_send_msg_nolock - send a DSP message and read back the status
+ * @rmh: the rmh record to send and receive
+ *
+ * returns 0 if successful, or a negative error code.
+ * the error code can be VX-specific, retrieved via vx_get_error().
+ * 
+ * this function doesn't call spinlock at all.
+ */
+int vx_send_msg_nolock(struct vx_core *chip, struct vx_rmh *rmh)
+{
+	int i, err;
+	
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+	if ((err = vx_reset_chk(chip)) < 0) {
+		snd_printd(KERN_DEBUG "vx_send_msg: vx_reset_chk error\n");
+		return err;
+	}
+
+#if 0
+	printk(KERN_DEBUG "rmh: cmd = 0x%06x, length = %d, stype = %d\n",
+	       rmh->Cmd[0], rmh->LgCmd, rmh->DspStat);
+	if (rmh->LgCmd > 1) {
+		printk(KERN_DEBUG "  ");
+		for (i = 1; i < rmh->LgCmd; i++)
+			printk("0x%06x ", rmh->Cmd[i]);
+		printk("\n");
+	}
+#endif
+	/* Check bit M is set according to length of the command */
+	if (rmh->LgCmd > 1)
+		rmh->Cmd[0] |= MASK_MORE_THAN_1_WORD_COMMAND;
+	else
+		rmh->Cmd[0] &= MASK_1_WORD_COMMAND;
+
+	/* Wait for TX empty */
+	if ((err = vx_wait_isr_bit(chip, ISR_TX_EMPTY)) < 0) {
+		snd_printd(KERN_DEBUG "vx_send_msg: wait tx empty error\n");
+		return err;
+	}
+
+	/* Write Cmd[0] */
+	vx_outb(chip, TXH, (rmh->Cmd[0] >> 16) & 0xff);
+	vx_outb(chip, TXM, (rmh->Cmd[0] >> 8) & 0xff);
+	vx_outb(chip, TXL, rmh->Cmd[0] & 0xff);
+
+	/* Trigger irq MESSAGE */
+	if ((err = vx_send_irq_dsp(chip, IRQ_MESSAGE)) < 0) {
+		snd_printd(KERN_DEBUG "vx_send_msg: send IRQ_MESSAGE error\n");
+		return err;
+	}
+
+	/* Wait for CHK = 1 */
+	if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0)
+		return err;
+
+	/* If error, get error value from RX */
+	if (vx_inb(chip, ISR) & ISR_ERR) {
+		if ((err = vx_wait_for_rx_full(chip)) < 0) {
+			snd_printd(KERN_DEBUG "vx_send_msg: rx_full read error\n");
+			return err;
+		}
+		err = vx_inb(chip, RXH) << 16;
+		err |= vx_inb(chip, RXM) << 8;
+		err |= vx_inb(chip, RXL);
+		snd_printd(KERN_DEBUG "msg got error = 0x%x at cmd[0]\n", err);
+		err = -(VX_ERR_MASK | err);
+		return err;
+	}
+
+	/* Send the other words */
+	if (rmh->LgCmd > 1) {
+		for (i = 1; i < rmh->LgCmd; i++) {
+			/* Wait for TX ready */
+			if ((err = vx_wait_isr_bit(chip, ISR_TX_READY)) < 0) {
+				snd_printd(KERN_DEBUG "vx_send_msg: tx_ready error\n");
+				return err;
+			}
+
+			/* Write Cmd[i] */
+			vx_outb(chip, TXH, (rmh->Cmd[i] >> 16) & 0xff);
+			vx_outb(chip, TXM, (rmh->Cmd[i] >> 8) & 0xff);
+			vx_outb(chip, TXL, rmh->Cmd[i] & 0xff);
+
+			/* Trigger irq MESS_READ_NEXT */
+			if ((err = vx_send_irq_dsp(chip, IRQ_MESS_READ_NEXT)) < 0) {
+				snd_printd(KERN_DEBUG "vx_send_msg: IRQ_READ_NEXT error\n");
+				return err;
+			}
+		}
+		/* Wait for TX empty */
+		if ((err = vx_wait_isr_bit(chip, ISR_TX_READY)) < 0) {
+			snd_printd(KERN_DEBUG "vx_send_msg: TX_READY error\n");
+			return err;
+		}
+		/* End of transfer */
+		err = vx_transfer_end(chip, IRQ_MESS_READ_END);
+		if (err < 0)
+			return err;
+	}
+
+	return vx_read_status(chip, rmh);
+}
+
+
+/*
+ * vx_send_msg - send a DSP message with spinlock
+ * @rmh: the rmh record to send and receive
+ *
+ * returns 0 if successful, or a negative error code.
+ * see vx_send_msg_nolock().
+ */
+int vx_send_msg(struct vx_core *chip, struct vx_rmh *rmh)
+{
+	unsigned long flags;
+	int err;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	err = vx_send_msg_nolock(chip, rmh);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return err;
+}
+
+
+/*
+ * vx_send_rih_nolock - send an RIH to xilinx
+ * @cmd: the command to send
+ *
+ * returns 0 if successful, or a negative error code.
+ * the error code can be VX-specific, retrieved via vx_get_error().
+ *
+ * this function doesn't call spinlock at all.
+ *
+ * unlike RMH, no command is sent to DSP.
+ */
+int vx_send_rih_nolock(struct vx_core *chip, int cmd)
+{
+	int err;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+#if 0
+	printk(KERN_DEBUG "send_rih: cmd = 0x%x\n", cmd);
+#endif
+	if ((err = vx_reset_chk(chip)) < 0)
+		return err;
+	/* send the IRQ */
+	if ((err = vx_send_irq_dsp(chip, cmd)) < 0)
+		return err;
+	/* Wait CHK = 1 */
+	if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0)
+		return err;
+	/* If error, read RX */
+	if (vx_inb(chip, ISR) & ISR_ERR) {
+		if ((err = vx_wait_for_rx_full(chip)) < 0)
+			return err;
+		err = vx_inb(chip, RXH) << 16;
+		err |= vx_inb(chip, RXM) << 8;
+		err |= vx_inb(chip, RXL);
+		return -(VX_ERR_MASK | err);
+	}
+	return 0;
+}
+
+
+/*
+ * vx_send_rih - send an RIH with spinlock
+ * @cmd: the command to send
+ *
+ * see vx_send_rih_nolock().
+ */
+int vx_send_rih(struct vx_core *chip, int cmd)
+{
+	unsigned long flags;
+	int err;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	err = vx_send_rih_nolock(chip, cmd);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return err;
+}
+
+#define END_OF_RESET_WAIT_TIME		500	/* us */
+
+/**
+ * snd_vx_boot_xilinx - boot up the xilinx interface
+ * @boot: the boot record to load
+ */
+int snd_vx_load_boot_image(struct vx_core *chip, const struct firmware *boot)
+{
+	unsigned int i;
+	int no_fillup = vx_has_new_dsp(chip);
+
+	/* check the length of boot image */
+	if (boot->size <= 0)
+		return -EINVAL;
+	if (boot->size % 3)
+		return -EINVAL;
+#if 0
+	{
+		/* more strict check */
+		unsigned int c = ((u32)boot->data[0] << 16) | ((u32)boot->data[1] << 8) | boot->data[2];
+		if (boot->size != (c + 2) * 3)
+			return -EINVAL;
+	}
+#endif
+
+	/* reset dsp */
+	vx_reset_dsp(chip);
+	
+	udelay(END_OF_RESET_WAIT_TIME); /* another wait? */
+
+	/* download boot strap */
+	for (i = 0; i < 0x600; i += 3) {
+		if (i >= boot->size) {
+			if (no_fillup)
+				break;
+			if (vx_wait_isr_bit(chip, ISR_TX_EMPTY) < 0) {
+				snd_printk(KERN_ERR "dsp boot failed at %d\n", i);
+				return -EIO;
+			}
+			vx_outb(chip, TXH, 0);
+			vx_outb(chip, TXM, 0);
+			vx_outb(chip, TXL, 0);
+		} else {
+			const unsigned char *image = boot->data + i;
+			if (vx_wait_isr_bit(chip, ISR_TX_EMPTY) < 0) {
+				snd_printk(KERN_ERR "dsp boot failed at %d\n", i);
+				return -EIO;
+			}
+			vx_outb(chip, TXH, image[0]);
+			vx_outb(chip, TXM, image[1]);
+			vx_outb(chip, TXL, image[2]);
+		}
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_vx_load_boot_image);
+
+/*
+ * vx_test_irq_src - query the source of interrupts
+ *
+ * called from irq handler only
+ */
+static int vx_test_irq_src(struct vx_core *chip, unsigned int *ret)
+{
+	int err;
+
+	vx_init_rmh(&chip->irq_rmh, CMD_TEST_IT);
+	spin_lock(&chip->lock);
+	err = vx_send_msg_nolock(chip, &chip->irq_rmh);
+	if (err < 0)
+		*ret = 0;
+	else
+		*ret = chip->irq_rmh.Stat[0];
+	spin_unlock(&chip->lock);
+	return err;
+}
+
+
+/*
+ * vx_interrupt - soft irq handler
+ */
+static void vx_interrupt(unsigned long private_data)
+{
+	struct vx_core *chip = (struct vx_core *) private_data;
+	unsigned int events;
+		
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return;
+
+	if (vx_test_irq_src(chip, &events) < 0)
+		return;
+    
+#if 0
+	if (events & 0x000800)
+		printk(KERN_ERR "DSP Stream underrun ! IRQ events = 0x%x\n", events);
+#endif
+	// printk(KERN_DEBUG "IRQ events = 0x%x\n", events);
+
+	/* We must prevent any application using this DSP
+	 * and block any further request until the application
+	 * either unregisters or reloads the DSP
+	 */
+	if (events & FATAL_DSP_ERROR) {
+		snd_printk(KERN_ERR "vx_core: fatal DSP error!!\n");
+		return;
+	}
+
+	/* The start on time code conditions are filled (ie the time code
+	 * received by the board is equal to one of those given to it).
+	 */
+	if (events & TIME_CODE_EVENT_PENDING)
+		; /* so far, nothing to do yet */
+
+	/* The frequency has changed on the board (UER mode). */
+	if (events & FREQUENCY_CHANGE_EVENT_PENDING)
+		vx_change_frequency(chip);
+
+	/* update the pcm streams */
+	vx_pcm_update_intr(chip, events);
+}
+
+
+/**
+ * snd_vx_irq_handler - interrupt handler
+ */
+irqreturn_t snd_vx_irq_handler(int irq, void *dev)
+{
+	struct vx_core *chip = dev;
+
+	if (! (chip->chip_status & VX_STAT_CHIP_INIT) ||
+	    (chip->chip_status & VX_STAT_IS_STALE))
+		return IRQ_NONE;
+	if (! vx_test_and_ack(chip))
+		tasklet_schedule(&chip->tq);
+	return IRQ_HANDLED;
+}
+
+EXPORT_SYMBOL(snd_vx_irq_handler);
+
+/*
+ */
+static void vx_reset_board(struct vx_core *chip, int cold_reset)
+{
+	if (snd_BUG_ON(!chip->ops->reset_board))
+		return;
+
+	/* current source, later sync'ed with target */
+	chip->audio_source = VX_AUDIO_SRC_LINE;
+	if (cold_reset) {
+		chip->audio_source_target = chip->audio_source;
+		chip->clock_source = INTERNAL_QUARTZ;
+		chip->clock_mode = VX_CLOCK_MODE_AUTO;
+		chip->freq = 48000;
+		chip->uer_detected = VX_UER_MODE_NOT_PRESENT;
+		chip->uer_bits = SNDRV_PCM_DEFAULT_CON_SPDIF;
+	}
+
+	chip->ops->reset_board(chip, cold_reset);
+
+	vx_reset_codec(chip, cold_reset);
+
+	vx_set_internal_clock(chip, chip->freq);
+
+	/* Reset the DSP */
+	vx_reset_dsp(chip);
+
+	if (vx_is_pcmcia(chip)) {
+		/* Acknowledge any pending IRQ and reset the MEMIRQ flag. */
+		vx_test_and_ack(chip);
+		vx_validate_irq(chip, 1);
+	}
+
+	/* init CBits */
+	vx_set_iec958_status(chip, chip->uer_bits);
+}
+
+
+/*
+ * proc interface
+ */
+
+static void vx_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct vx_core *chip = entry->private_data;
+	static char *audio_src_vxp[] = { "Line", "Mic", "Digital" };
+	static char *audio_src_vx2[] = { "Analog", "Analog", "Digital" };
+	static char *clock_mode[] = { "Auto", "Internal", "External" };
+	static char *clock_src[] = { "Internal", "External" };
+	static char *uer_type[] = { "Consumer", "Professional", "Not Present" };
+	
+	snd_iprintf(buffer, "%s\n", chip->card->longname);
+	snd_iprintf(buffer, "Xilinx Firmware: %s\n",
+		    chip->chip_status & VX_STAT_XILINX_LOADED ? "Loaded" : "No");
+	snd_iprintf(buffer, "Device Initialized: %s\n",
+		    chip->chip_status & VX_STAT_DEVICE_INIT ? "Yes" : "No");
+	snd_iprintf(buffer, "DSP audio info:");
+	if (chip->audio_info & VX_AUDIO_INFO_REAL_TIME)
+		snd_iprintf(buffer, " realtime");
+	if (chip->audio_info & VX_AUDIO_INFO_OFFLINE)
+		snd_iprintf(buffer, " offline");
+	if (chip->audio_info & VX_AUDIO_INFO_MPEG1)
+		snd_iprintf(buffer, " mpeg1");
+	if (chip->audio_info & VX_AUDIO_INFO_MPEG2)
+		snd_iprintf(buffer, " mpeg2");
+	if (chip->audio_info & VX_AUDIO_INFO_LINEAR_8)
+		snd_iprintf(buffer, " linear8");
+	if (chip->audio_info & VX_AUDIO_INFO_LINEAR_16)
+		snd_iprintf(buffer, " linear16");
+	if (chip->audio_info & VX_AUDIO_INFO_LINEAR_24)
+		snd_iprintf(buffer, " linear24");
+	snd_iprintf(buffer, "\n");
+	snd_iprintf(buffer, "Input Source: %s\n", vx_is_pcmcia(chip) ?
+		    audio_src_vxp[chip->audio_source] :
+		    audio_src_vx2[chip->audio_source]);
+	snd_iprintf(buffer, "Clock Mode: %s\n", clock_mode[chip->clock_mode]);
+	snd_iprintf(buffer, "Clock Source: %s\n", clock_src[chip->clock_source]);
+	snd_iprintf(buffer, "Frequency: %d\n", chip->freq);
+	snd_iprintf(buffer, "Detected Frequency: %d\n", chip->freq_detected);
+	snd_iprintf(buffer, "Detected UER type: %s\n", uer_type[chip->uer_detected]);
+	snd_iprintf(buffer, "Min/Max/Cur IBL: %d/%d/%d (granularity=%d)\n",
+		    chip->ibl.min_size, chip->ibl.max_size, chip->ibl.size,
+		    chip->ibl.granularity);
+}
+
+static void vx_proc_init(struct vx_core *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(chip->card, "vx-status", &entry))
+		snd_info_set_text_ops(entry, chip, vx_proc_read);
+}
+
+
+/**
+ * snd_vx_dsp_boot - load the DSP boot
+ */
+int snd_vx_dsp_boot(struct vx_core *chip, const struct firmware *boot)
+{
+	int err;
+	int cold_reset = !(chip->chip_status & VX_STAT_DEVICE_INIT);
+
+	vx_reset_board(chip, cold_reset);
+	vx_validate_irq(chip, 0);
+
+	if ((err = snd_vx_load_boot_image(chip, boot)) < 0)
+		return err;
+	msleep(10);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_vx_dsp_boot);
+
+/**
+ * snd_vx_dsp_load - load the DSP image
+ */
+int snd_vx_dsp_load(struct vx_core *chip, const struct firmware *dsp)
+{
+	unsigned int i;
+	int err;
+	unsigned int csum = 0;
+	const unsigned char *image, *cptr;
+
+	if (dsp->size % 3)
+		return -EINVAL;
+
+	vx_toggle_dac_mute(chip, 1);
+
+	/* Transfert data buffer from PC to DSP */
+	for (i = 0; i < dsp->size; i += 3) {
+		image = dsp->data + i;
+		/* Wait DSP ready for a new read */
+		if ((err = vx_wait_isr_bit(chip, ISR_TX_EMPTY)) < 0) {
+			printk(KERN_ERR
+			       "dsp loading error at position %d\n", i);
+			return err;
+		}
+		cptr = image;
+		csum ^= *cptr;
+		csum = (csum >> 24) | (csum << 8);
+		vx_outb(chip, TXH, *cptr++);
+		csum ^= *cptr;
+		csum = (csum >> 24) | (csum << 8);
+		vx_outb(chip, TXM, *cptr++);
+		csum ^= *cptr;
+		csum = (csum >> 24) | (csum << 8);
+		vx_outb(chip, TXL, *cptr++);
+	}
+	snd_printdd(KERN_DEBUG "checksum = 0x%08x\n", csum);
+
+	msleep(200);
+
+	if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0)
+		return err;
+
+	vx_toggle_dac_mute(chip, 0);
+
+	vx_test_and_ack(chip);
+	vx_validate_irq(chip, 1);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_vx_dsp_load);
+
+#ifdef CONFIG_PM
+/*
+ * suspend
+ */
+int snd_vx_suspend(struct vx_core *chip, pm_message_t state)
+{
+	unsigned int i;
+
+	snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
+	chip->chip_status |= VX_STAT_IN_SUSPEND;
+	for (i = 0; i < chip->hw->num_codecs; i++)
+		snd_pcm_suspend_all(chip->pcm[i]);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_vx_suspend);
+
+/*
+ * resume
+ */
+int snd_vx_resume(struct vx_core *chip)
+{
+	int i, err;
+
+	chip->chip_status &= ~VX_STAT_CHIP_INIT;
+
+	for (i = 0; i < 4; i++) {
+		if (! chip->firmware[i])
+			continue;
+		err = chip->ops->load_dsp(chip, i, chip->firmware[i]);
+		if (err < 0) {
+			snd_printk(KERN_ERR "vx: firmware resume error at DSP %d\n", i);
+			return -EIO;
+		}
+	}
+
+	chip->chip_status |= VX_STAT_CHIP_INIT;
+	chip->chip_status &= ~VX_STAT_IN_SUSPEND;
+
+	snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_vx_resume);
+#endif
+
+/**
+ * snd_vx_create - constructor for struct vx_core
+ * @hw: hardware specific record
+ *
+ * this function allocates the instance and prepare for the hardware
+ * initialization.
+ *
+ * return the instance pointer if successful, NULL in error.
+ */
+struct vx_core *snd_vx_create(struct snd_card *card, struct snd_vx_hardware *hw,
+			      struct snd_vx_ops *ops,
+			      int extra_size)
+{
+	struct vx_core *chip;
+
+	if (snd_BUG_ON(!card || !hw || !ops))
+		return NULL;
+
+	chip = kzalloc(sizeof(*chip) + extra_size, GFP_KERNEL);
+	if (! chip) {
+		snd_printk(KERN_ERR "vx_core: no memory\n");
+		return NULL;
+	}
+	spin_lock_init(&chip->lock);
+	spin_lock_init(&chip->irq_lock);
+	chip->irq = -1;
+	chip->hw = hw;
+	chip->type = hw->type;
+	chip->ops = ops;
+	tasklet_init(&chip->tq, vx_interrupt, (unsigned long)chip);
+	mutex_init(&chip->mixer_mutex);
+
+	chip->card = card;
+	card->private_data = chip;
+	strcpy(card->driver, hw->name);
+	sprintf(card->shortname, "Digigram %s", hw->name);
+
+	vx_proc_init(chip);
+
+	return chip;
+}
+
+EXPORT_SYMBOL(snd_vx_create);
+
+/*
+ * module entries
+ */
+static int __init alsa_vx_core_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_vx_core_exit(void)
+{
+}
+
+module_init(alsa_vx_core_init)
+module_exit(alsa_vx_core_exit)
diff --git a/linux/sound/drivers/vx/vx_hwdep.c b/linux/sound/drivers/vx/vx_hwdep.c
new file mode 100644
index 0000000..f7a6fbd
--- /dev/null
+++ b/linux/sound/drivers/vx/vx_hwdep.c
@@ -0,0 +1,259 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * DSP firmware management
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <sound/core.h>
+#include <sound/hwdep.h>
+#include <sound/vx_core.h>
+
+#ifdef SND_VX_FW_LOADER
+
+MODULE_FIRMWARE("vx/bx_1_vxp.b56");
+MODULE_FIRMWARE("vx/bx_1_vp4.b56");
+MODULE_FIRMWARE("vx/x1_1_vx2.xlx");
+MODULE_FIRMWARE("vx/x1_2_v22.xlx");
+MODULE_FIRMWARE("vx/x1_1_vxp.xlx");
+MODULE_FIRMWARE("vx/x1_1_vp4.xlx");
+MODULE_FIRMWARE("vx/bd56002.boot");
+MODULE_FIRMWARE("vx/bd563v2.boot");
+MODULE_FIRMWARE("vx/bd563s3.boot");
+MODULE_FIRMWARE("vx/l_1_vx2.d56");
+MODULE_FIRMWARE("vx/l_1_v22.d56");
+MODULE_FIRMWARE("vx/l_1_vxp.d56");
+MODULE_FIRMWARE("vx/l_1_vp4.d56");
+
+int snd_vx_setup_firmware(struct vx_core *chip)
+{
+	static char *fw_files[VX_TYPE_NUMS][4] = {
+		[VX_TYPE_BOARD] = {
+			NULL, "x1_1_vx2.xlx", "bd56002.boot", "l_1_vx2.d56",
+		},
+		[VX_TYPE_V2] = {
+			NULL, "x1_2_v22.xlx", "bd563v2.boot", "l_1_v22.d56",
+		},
+		[VX_TYPE_MIC] = {
+			NULL, "x1_2_v22.xlx", "bd563v2.boot", "l_1_v22.d56",
+		},
+		[VX_TYPE_VXPOCKET] = {
+			"bx_1_vxp.b56", "x1_1_vxp.xlx", "bd563s3.boot", "l_1_vxp.d56"
+		},
+		[VX_TYPE_VXP440] = {
+			"bx_1_vp4.b56", "x1_1_vp4.xlx", "bd563s3.boot", "l_1_vp4.d56"
+		},
+	};
+
+	int i, err;
+
+	for (i = 0; i < 4; i++) {
+		char path[32];
+		const struct firmware *fw;
+		if (! fw_files[chip->type][i])
+			continue;
+		sprintf(path, "vx/%s", fw_files[chip->type][i]);
+		if (request_firmware(&fw, path, chip->dev)) {
+			snd_printk(KERN_ERR "vx: can't load firmware %s\n", path);
+			return -ENOENT;
+		}
+		err = chip->ops->load_dsp(chip, i, fw);
+		if (err < 0) {
+			release_firmware(fw);
+			return err;
+		}
+		if (i == 1)
+			chip->chip_status |= VX_STAT_XILINX_LOADED;
+#ifdef CONFIG_PM
+		chip->firmware[i] = fw;
+#else
+		release_firmware(fw);
+#endif
+	}
+
+	/* ok, we reached to the last one */
+	/* create the devices if not built yet */
+	if ((err = snd_vx_pcm_new(chip)) < 0)
+		return err;
+
+	if ((err = snd_vx_mixer_new(chip)) < 0)
+		return err;
+
+	if (chip->ops->add_controls)
+		if ((err = chip->ops->add_controls(chip)) < 0)
+			return err;
+
+	chip->chip_status |= VX_STAT_DEVICE_INIT;
+	chip->chip_status |= VX_STAT_CHIP_INIT;
+
+	return snd_card_register(chip->card);
+}
+
+/* exported */
+void snd_vx_free_firmware(struct vx_core *chip)
+{
+#ifdef CONFIG_PM
+	int i;
+	for (i = 0; i < 4; i++)
+		release_firmware(chip->firmware[i]);
+#endif
+}
+
+#else /* old style firmware loading */
+
+static int vx_hwdep_dsp_status(struct snd_hwdep *hw,
+			       struct snd_hwdep_dsp_status *info)
+{
+	static char *type_ids[VX_TYPE_NUMS] = {
+		[VX_TYPE_BOARD] = "vxboard",
+		[VX_TYPE_V2] = "vx222",
+		[VX_TYPE_MIC] = "vx222",
+		[VX_TYPE_VXPOCKET] = "vxpocket",
+		[VX_TYPE_VXP440] = "vxp440",
+	};
+	struct vx_core *vx = hw->private_data;
+
+	if (snd_BUG_ON(!type_ids[vx->type]))
+		return -EINVAL;
+	strcpy(info->id, type_ids[vx->type]);
+	if (vx_is_pcmcia(vx))
+		info->num_dsps = 4;
+	else
+		info->num_dsps = 3;
+	if (vx->chip_status & VX_STAT_CHIP_INIT)
+		info->chip_ready = 1;
+	info->version = VX_DRIVER_VERSION;
+	return 0;
+}
+
+static void free_fw(const struct firmware *fw)
+{
+	if (fw) {
+		vfree(fw->data);
+		kfree(fw);
+	}
+}
+
+static int vx_hwdep_dsp_load(struct snd_hwdep *hw,
+			     struct snd_hwdep_dsp_image *dsp)
+{
+	struct vx_core *vx = hw->private_data;
+	int index, err;
+	struct firmware *fw;
+
+	if (snd_BUG_ON(!vx->ops->load_dsp))
+		return -ENXIO;
+
+	fw = kmalloc(sizeof(*fw), GFP_KERNEL);
+	if (! fw) {
+		snd_printk(KERN_ERR "cannot allocate firmware\n");
+		return -ENOMEM;
+	}
+	fw->size = dsp->length;
+	fw->data = vmalloc(fw->size);
+	if (! fw->data) {
+		snd_printk(KERN_ERR "cannot allocate firmware image (length=%d)\n",
+			   (int)fw->size);
+		kfree(fw);
+		return -ENOMEM;
+	}
+	if (copy_from_user((void *)fw->data, dsp->image, dsp->length)) {
+		free_fw(fw);
+		return -EFAULT;
+	}
+
+	index = dsp->index;
+	if (! vx_is_pcmcia(vx))
+		index++;
+	err = vx->ops->load_dsp(vx, index, fw);
+	if (err < 0) {
+		free_fw(fw);
+		return err;
+	}
+#ifdef CONFIG_PM
+	vx->firmware[index] = fw;
+#else
+	free_fw(fw);
+#endif
+
+	if (index == 1)
+		vx->chip_status |= VX_STAT_XILINX_LOADED;
+	if (index < 3)
+		return 0;
+
+	/* ok, we reached to the last one */
+	/* create the devices if not built yet */
+	if (! (vx->chip_status & VX_STAT_DEVICE_INIT)) {
+		if ((err = snd_vx_pcm_new(vx)) < 0)
+			return err;
+
+		if ((err = snd_vx_mixer_new(vx)) < 0)
+			return err;
+
+		if (vx->ops->add_controls)
+			if ((err = vx->ops->add_controls(vx)) < 0)
+				return err;
+
+		if ((err = snd_card_register(vx->card)) < 0)
+			return err;
+
+		vx->chip_status |= VX_STAT_DEVICE_INIT;
+	}
+	vx->chip_status |= VX_STAT_CHIP_INIT;
+	return 0;
+}
+
+
+/* exported */
+int snd_vx_setup_firmware(struct vx_core *chip)
+{
+	int err;
+	struct snd_hwdep *hw;
+
+	if ((err = snd_hwdep_new(chip->card, SND_VX_HWDEP_ID, 0, &hw)) < 0)
+		return err;
+
+	hw->iface = SNDRV_HWDEP_IFACE_VX;
+	hw->private_data = chip;
+	hw->ops.dsp_status = vx_hwdep_dsp_status;
+	hw->ops.dsp_load = vx_hwdep_dsp_load;
+	hw->exclusive = 1;
+	sprintf(hw->name, "VX Loader (%s)", chip->card->driver);
+	chip->hwdep = hw;
+
+	return snd_card_register(chip->card);
+}
+
+/* exported */
+void snd_vx_free_firmware(struct vx_core *chip)
+{
+#ifdef CONFIG_PM
+	int i;
+	for (i = 0; i < 4; i++)
+		free_fw(chip->firmware[i]);
+#endif
+}
+
+#endif /* SND_VX_FW_LOADER */
+
+EXPORT_SYMBOL(snd_vx_setup_firmware);
+EXPORT_SYMBOL(snd_vx_free_firmware);
diff --git a/linux/sound/drivers/vx/vx_mixer.c b/linux/sound/drivers/vx/vx_mixer.c
new file mode 100644
index 0000000..c71b8d1
--- /dev/null
+++ b/linux/sound/drivers/vx/vx_mixer.c
@@ -0,0 +1,1028 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * Common mixer part
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/vx_core.h>
+#include "vx_cmd.h"
+
+
+/*
+ * write a codec data (24bit)
+ */
+static void vx_write_codec_reg(struct vx_core *chip, int codec, unsigned int data)
+{
+	unsigned long flags;
+
+	if (snd_BUG_ON(!chip->ops->write_codec))
+		return;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->ops->write_codec(chip, codec, data);
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+/*
+ * Data type used to access the Codec
+ */
+union vx_codec_data {
+	u32 l;
+#ifdef SNDRV_BIG_ENDIAN
+	struct w {
+		u16 h;
+		u16 l;
+	} w;
+	struct b {
+		u8 hh;
+		u8 mh;
+		u8 ml;
+		u8 ll;
+	} b;
+#else /* LITTLE_ENDIAN */
+	struct w {
+		u16 l;
+		u16 h;
+	} w;
+	struct b {
+		u8 ll;
+		u8 ml;
+		u8 mh;
+		u8 hh;
+	} b;
+#endif
+};
+
+#define SET_CDC_DATA_SEL(di,s)          ((di).b.mh = (u8) (s))
+#define SET_CDC_DATA_REG(di,r)          ((di).b.ml = (u8) (r))
+#define SET_CDC_DATA_VAL(di,d)          ((di).b.ll = (u8) (d))
+#define SET_CDC_DATA_INIT(di)           ((di).l = 0L, SET_CDC_DATA_SEL(di,XX_CODEC_SELECTOR))
+
+/*
+ * set up codec register and write the value
+ * @codec: the codec id, 0 or 1
+ * @reg: register index
+ * @val: data value
+ */
+static void vx_set_codec_reg(struct vx_core *chip, int codec, int reg, int val)
+{
+	union vx_codec_data data;
+	/* DAC control register */
+	SET_CDC_DATA_INIT(data);
+	SET_CDC_DATA_REG(data, reg);
+	SET_CDC_DATA_VAL(data, val);
+	vx_write_codec_reg(chip, codec, data.l);
+}
+
+
+/*
+ * vx_set_analog_output_level - set the output attenuation level
+ * @codec: the output codec, 0 or 1.  (1 for VXP440 only)
+ * @left: left output level, 0 = mute
+ * @right: right output level
+ */
+static void vx_set_analog_output_level(struct vx_core *chip, int codec, int left, int right)
+{
+	left  = chip->hw->output_level_max - left;
+	right = chip->hw->output_level_max - right;
+
+	if (chip->ops->akm_write) {
+		chip->ops->akm_write(chip, XX_CODEC_LEVEL_LEFT_REGISTER, left);
+		chip->ops->akm_write(chip, XX_CODEC_LEVEL_RIGHT_REGISTER, right);
+	} else {
+		/* convert to attenuation level: 0 = 0dB (max), 0xe3 = -113.5 dB (min) */
+		vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_LEFT_REGISTER, left);
+		vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_RIGHT_REGISTER, right);
+	}
+}
+
+
+/*
+ * vx_toggle_dac_mute -  mute/unmute DAC
+ * @mute: 0 = unmute, 1 = mute
+ */
+
+#define DAC_ATTEN_MIN	0x08
+#define DAC_ATTEN_MAX	0x38
+
+void vx_toggle_dac_mute(struct vx_core *chip, int mute)
+{
+	unsigned int i;
+	for (i = 0; i < chip->hw->num_codecs; i++) {
+		if (chip->ops->akm_write)
+			chip->ops->akm_write(chip, XX_CODEC_DAC_CONTROL_REGISTER, mute); /* XXX */
+		else
+			vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER,
+					 mute ? DAC_ATTEN_MAX : DAC_ATTEN_MIN);
+	}
+}
+
+/*
+ * vx_reset_codec - reset and initialize the codecs
+ */
+void vx_reset_codec(struct vx_core *chip, int cold_reset)
+{
+	unsigned int i;
+	int port = chip->type >= VX_TYPE_VXPOCKET ? 0x75 : 0x65;
+
+	chip->ops->reset_codec(chip);
+
+	/* AKM codecs should be initialized in reset_codec callback */
+	if (! chip->ops->akm_write) {
+		/* initialize old codecs */
+		for (i = 0; i < chip->hw->num_codecs; i++) {
+			/* DAC control register (change level when zero crossing + mute) */
+			vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER, DAC_ATTEN_MAX);
+			/* ADC control register */
+			vx_set_codec_reg(chip, i, XX_CODEC_ADC_CONTROL_REGISTER, 0x00);
+			/* Port mode register */
+			vx_set_codec_reg(chip, i, XX_CODEC_PORT_MODE_REGISTER, port);
+			/* Clock control register */
+			vx_set_codec_reg(chip, i, XX_CODEC_CLOCK_CONTROL_REGISTER, 0x00);
+		}
+	}
+
+	/* mute analog output */
+	for (i = 0; i < chip->hw->num_codecs; i++) {
+		chip->output_level[i][0] = 0;
+		chip->output_level[i][1] = 0;
+		vx_set_analog_output_level(chip, i, 0, 0);
+	}
+}
+
+/*
+ * change the audio input source
+ * @src: the target source (VX_AUDIO_SRC_XXX)
+ */
+static void vx_change_audio_source(struct vx_core *chip, int src)
+{
+	unsigned long flags;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->ops->change_audio_source(chip, src);
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+/*
+ * change the audio source if necessary and possible
+ * returns 1 if the source is actually changed.
+ */
+int vx_sync_audio_source(struct vx_core *chip)
+{
+	if (chip->audio_source_target == chip->audio_source ||
+	    chip->pcm_running)
+		return 0;
+	vx_change_audio_source(chip, chip->audio_source_target);
+	chip->audio_source = chip->audio_source_target;
+	return 1;
+}
+
+
+/*
+ * audio level, mute, monitoring
+ */
+struct vx_audio_level {
+	unsigned int has_level: 1;
+	unsigned int has_monitor_level: 1;
+	unsigned int has_mute: 1;
+	unsigned int has_monitor_mute: 1;
+	unsigned int mute;
+	unsigned int monitor_mute;
+	short level;
+	short monitor_level;
+};
+
+static int vx_adjust_audio_level(struct vx_core *chip, int audio, int capture,
+				 struct vx_audio_level *info)
+{
+	struct vx_rmh rmh;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+        vx_init_rmh(&rmh, CMD_AUDIO_LEVEL_ADJUST);
+	if (capture)
+		rmh.Cmd[0] |= COMMAND_RECORD_MASK;
+	/* Add Audio IO mask */
+	rmh.Cmd[1] = 1 << audio;
+	rmh.Cmd[2] = 0;
+	if (info->has_level) {
+		rmh.Cmd[0] |=  VALID_AUDIO_IO_DIGITAL_LEVEL;
+		rmh.Cmd[2] |= info->level;
+        }
+	if (info->has_monitor_level) {
+		rmh.Cmd[0] |=  VALID_AUDIO_IO_MONITORING_LEVEL;
+		rmh.Cmd[2] |= ((unsigned int)info->monitor_level << 10);
+        }
+	if (info->has_mute) { 
+		rmh.Cmd[0] |= VALID_AUDIO_IO_MUTE_LEVEL;
+		if (info->mute)
+			rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_LEVEL;
+	}
+	if (info->has_monitor_mute) {
+		/* validate flag for M2 at least to unmute it */ 
+		rmh.Cmd[0] |=  VALID_AUDIO_IO_MUTE_MONITORING_1 | VALID_AUDIO_IO_MUTE_MONITORING_2;
+		if (info->monitor_mute)
+			rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_MONITORING_1;
+	}
+
+	return vx_send_msg(chip, &rmh);
+}
+
+    
+#if 0 // not used
+static int vx_read_audio_level(struct vx_core *chip, int audio, int capture,
+			       struct vx_audio_level *info)
+{
+	int err;
+	struct vx_rmh rmh;
+
+	memset(info, 0, sizeof(*info));
+        vx_init_rmh(&rmh, CMD_GET_AUDIO_LEVELS);
+	if (capture)
+		rmh.Cmd[0] |= COMMAND_RECORD_MASK;
+	/* Add Audio IO mask */
+	rmh.Cmd[1] = 1 << audio;
+	err = vx_send_msg(chip, &rmh);
+	if (err < 0)
+		return err;
+	info.level = rmh.Stat[0] & MASK_DSP_WORD_LEVEL;
+	info.monitor_level = (rmh.Stat[0] >> 10) & MASK_DSP_WORD_LEVEL;
+	info.mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_LEVEL) ? 1 : 0;
+	info.monitor_mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_MONITORING_1) ? 1 : 0;
+	return 0;
+}
+#endif // not used
+
+/*
+ * set the monitoring level and mute state of the given audio
+ * no more static, because must be called from vx_pcm to demute monitoring
+ */
+int vx_set_monitor_level(struct vx_core *chip, int audio, int level, int active)
+{
+	struct vx_audio_level info;
+
+	memset(&info, 0, sizeof(info));
+	info.has_monitor_level = 1;
+	info.monitor_level = level;
+	info.has_monitor_mute = 1;
+	info.monitor_mute = !active;
+	chip->audio_monitor[audio] = level;
+	chip->audio_monitor_active[audio] = active;
+	return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */
+}
+
+
+/*
+ * set the mute status of the given audio
+ */
+static int vx_set_audio_switch(struct vx_core *chip, int audio, int active)
+{
+	struct vx_audio_level info;
+
+	memset(&info, 0, sizeof(info));
+	info.has_mute = 1;
+	info.mute = !active;
+	chip->audio_active[audio] = active;
+	return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */
+}
+
+/*
+ * set the mute status of the given audio
+ */
+static int vx_set_audio_gain(struct vx_core *chip, int audio, int capture, int level)
+{
+	struct vx_audio_level info;
+
+	memset(&info, 0, sizeof(info));
+	info.has_level = 1;
+	info.level = level;
+	chip->audio_gain[capture][audio] = level;
+	return vx_adjust_audio_level(chip, audio, capture, &info);
+}
+
+/*
+ * reset all audio levels
+ */
+static void vx_reset_audio_levels(struct vx_core *chip)
+{
+	unsigned int i, c;
+	struct vx_audio_level info;
+
+	memset(chip->audio_gain, 0, sizeof(chip->audio_gain));
+	memset(chip->audio_active, 0, sizeof(chip->audio_active));
+	memset(chip->audio_monitor, 0, sizeof(chip->audio_monitor));
+	memset(chip->audio_monitor_active, 0, sizeof(chip->audio_monitor_active));
+
+	for (c = 0; c < 2; c++) {
+		for (i = 0; i < chip->hw->num_ins * 2; i++) {
+			memset(&info, 0, sizeof(info));
+			if (c == 0) {
+				info.has_monitor_level = 1;
+				info.has_mute = 1;
+				info.has_monitor_mute = 1;
+			}
+			info.has_level = 1;
+			info.level = CVAL_0DB; /* default: 0dB */
+			vx_adjust_audio_level(chip, i, c, &info);
+			chip->audio_gain[c][i] = CVAL_0DB;
+			chip->audio_monitor[i] = CVAL_0DB;
+		}
+	}
+}
+
+
+/*
+ * VU, peak meter record
+ */
+
+#define VU_METER_CHANNELS	2
+
+struct vx_vu_meter {
+	int saturated;
+	int vu_level;
+	int peak_level;
+};
+
+/*
+ * get the VU and peak meter values
+ * @audio: the audio index
+ * @capture: 0 = playback, 1 = capture operation
+ * @info: the array of vx_vu_meter records (size = 2).
+ */
+static int vx_get_audio_vu_meter(struct vx_core *chip, int audio, int capture, struct vx_vu_meter *info)
+{
+	struct vx_rmh rmh;
+	int i, err;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+	vx_init_rmh(&rmh, CMD_AUDIO_VU_PIC_METER);
+	rmh.LgStat += 2 * VU_METER_CHANNELS;
+	if (capture)
+		rmh.Cmd[0] |= COMMAND_RECORD_MASK;
+    
+        /* Add Audio IO mask */
+	rmh.Cmd[1] = 0;
+	for (i = 0; i < VU_METER_CHANNELS; i++)
+		rmh.Cmd[1] |= 1 << (audio + i);
+	err = vx_send_msg(chip, &rmh);
+	if (err < 0)
+		return err;
+	/* Read response */
+	for (i = 0; i < 2 * VU_METER_CHANNELS; i +=2) {
+		info->saturated = (rmh.Stat[0] & (1 << (audio + i))) ? 1 : 0;
+		info->vu_level = rmh.Stat[i + 1];
+		info->peak_level = rmh.Stat[i + 2];
+		info++;
+	}
+	return 0;
+}
+   
+
+/*
+ * control API entries
+ */
+
+/*
+ * output level control
+ */
+static int vx_output_level_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = chip->hw->output_level_max;
+	return 0;
+}
+
+static int vx_output_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	int codec = kcontrol->id.index;
+	mutex_lock(&chip->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->output_level[codec][0];
+	ucontrol->value.integer.value[1] = chip->output_level[codec][1];
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_output_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	int codec = kcontrol->id.index;
+	unsigned int val[2], vmax;
+
+	vmax = chip->hw->output_level_max;
+	val[0] = ucontrol->value.integer.value[0];
+	val[1] = ucontrol->value.integer.value[1];
+	if (val[0] > vmax || val[1] > vmax)
+		return -EINVAL;
+	mutex_lock(&chip->mixer_mutex);
+	if (val[0] != chip->output_level[codec][0] ||
+	    val[1] != chip->output_level[codec][1]) {
+		vx_set_analog_output_level(chip, codec, val[0], val[1]);
+		chip->output_level[codec][0] = val[0];
+		chip->output_level[codec][1] = val[1];
+		mutex_unlock(&chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static struct snd_kcontrol_new vx_control_output_level = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.name =		"Master Playback Volume",
+	.info =		vx_output_level_info,
+	.get =		vx_output_level_get,
+	.put =		vx_output_level_put,
+	/* tlv will be filled later */
+};
+
+/*
+ * audio source select
+ */
+static int vx_audio_src_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts_mic[3] = {
+		"Digital", "Line", "Mic"
+	};
+	static char *texts_vx2[2] = {
+		"Digital", "Analog"
+	};
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	if (chip->type >= VX_TYPE_VXPOCKET) {
+		uinfo->value.enumerated.items = 3;
+		if (uinfo->value.enumerated.item > 2)
+			uinfo->value.enumerated.item = 2;
+		strcpy(uinfo->value.enumerated.name,
+		       texts_mic[uinfo->value.enumerated.item]);
+	} else {
+		uinfo->value.enumerated.items = 2;
+		if (uinfo->value.enumerated.item > 1)
+			uinfo->value.enumerated.item = 1;
+		strcpy(uinfo->value.enumerated.name,
+		       texts_vx2[uinfo->value.enumerated.item]);
+	}
+	return 0;
+}
+
+static int vx_audio_src_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = chip->audio_source_target;
+	return 0;
+}
+
+static int vx_audio_src_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+
+	if (chip->type >= VX_TYPE_VXPOCKET) {
+		if (ucontrol->value.enumerated.item[0] > 2)
+			return -EINVAL;
+	} else {
+		if (ucontrol->value.enumerated.item[0] > 1)
+			return -EINVAL;
+	}
+	mutex_lock(&chip->mixer_mutex);
+	if (chip->audio_source_target != ucontrol->value.enumerated.item[0]) {
+		chip->audio_source_target = ucontrol->value.enumerated.item[0];
+		vx_sync_audio_source(chip);
+		mutex_unlock(&chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static struct snd_kcontrol_new vx_control_audio_src = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Capture Source",
+	.info =		vx_audio_src_info,
+	.get =		vx_audio_src_get,
+	.put =		vx_audio_src_put,
+};
+
+/*
+ * clock mode selection
+ */
+static int vx_clock_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[3] = {
+		"Auto", "Internal", "External"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int vx_clock_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = chip->clock_mode;
+	return 0;
+}
+
+static int vx_clock_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+
+	if (ucontrol->value.enumerated.item[0] > 2)
+		return -EINVAL;
+	mutex_lock(&chip->mixer_mutex);
+	if (chip->clock_mode != ucontrol->value.enumerated.item[0]) {
+		chip->clock_mode = ucontrol->value.enumerated.item[0];
+		vx_set_clock(chip, chip->freq);
+		mutex_unlock(&chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static struct snd_kcontrol_new vx_control_clock_mode = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Clock Mode",
+	.info =		vx_clock_mode_info,
+	.get =		vx_clock_mode_get,
+	.put =		vx_clock_mode_put,
+};
+
+/*
+ * Audio Gain
+ */
+static int vx_audio_gain_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = CVAL_MAX;
+	return 0;
+}
+
+static int vx_audio_gain_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+	int capture = (kcontrol->private_value >> 8) & 1;
+
+	mutex_lock(&chip->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->audio_gain[capture][audio];
+	ucontrol->value.integer.value[1] = chip->audio_gain[capture][audio+1];
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_audio_gain_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+	int capture = (kcontrol->private_value >> 8) & 1;
+	unsigned int val[2];
+
+	val[0] = ucontrol->value.integer.value[0];
+	val[1] = ucontrol->value.integer.value[1];
+	if (val[0] > CVAL_MAX || val[1] > CVAL_MAX)
+		return -EINVAL;
+	mutex_lock(&chip->mixer_mutex);
+	if (val[0] != chip->audio_gain[capture][audio] ||
+	    val[1] != chip->audio_gain[capture][audio+1]) {
+		vx_set_audio_gain(chip, audio, capture, val[0]);
+		vx_set_audio_gain(chip, audio+1, capture, val[1]);
+		mutex_unlock(&chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_audio_monitor_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+
+	mutex_lock(&chip->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->audio_monitor[audio];
+	ucontrol->value.integer.value[1] = chip->audio_monitor[audio+1];
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_audio_monitor_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+	unsigned int val[2];
+
+	val[0] = ucontrol->value.integer.value[0];
+	val[1] = ucontrol->value.integer.value[1];
+	if (val[0] > CVAL_MAX || val[1] > CVAL_MAX)
+		return -EINVAL;
+
+	mutex_lock(&chip->mixer_mutex);
+	if (val[0] != chip->audio_monitor[audio] ||
+	    val[1] != chip->audio_monitor[audio+1]) {
+		vx_set_monitor_level(chip, audio, val[0],
+				     chip->audio_monitor_active[audio]);
+		vx_set_monitor_level(chip, audio+1, val[1],
+				     chip->audio_monitor_active[audio+1]);
+		mutex_unlock(&chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+#define vx_audio_sw_info	snd_ctl_boolean_stereo_info
+
+static int vx_audio_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+
+	mutex_lock(&chip->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->audio_active[audio];
+	ucontrol->value.integer.value[1] = chip->audio_active[audio+1];
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+
+	mutex_lock(&chip->mixer_mutex);
+	if (ucontrol->value.integer.value[0] != chip->audio_active[audio] ||
+	    ucontrol->value.integer.value[1] != chip->audio_active[audio+1]) {
+		vx_set_audio_switch(chip, audio,
+				    !!ucontrol->value.integer.value[0]);
+		vx_set_audio_switch(chip, audio+1,
+				    !!ucontrol->value.integer.value[1]);
+		mutex_unlock(&chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_monitor_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+
+	mutex_lock(&chip->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->audio_monitor_active[audio];
+	ucontrol->value.integer.value[1] = chip->audio_monitor_active[audio+1];
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	int audio = kcontrol->private_value & 0xff;
+
+	mutex_lock(&chip->mixer_mutex);
+	if (ucontrol->value.integer.value[0] != chip->audio_monitor_active[audio] ||
+	    ucontrol->value.integer.value[1] != chip->audio_monitor_active[audio+1]) {
+		vx_set_monitor_level(chip, audio, chip->audio_monitor[audio],
+				     !!ucontrol->value.integer.value[0]);
+		vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1],
+				     !!ucontrol->value.integer.value[1]);
+		mutex_unlock(&chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_audio_gain, -10975, 25, 0);
+
+static struct snd_kcontrol_new vx_control_audio_gain = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	/* name will be filled later */
+	.info =         vx_audio_gain_info,
+	.get =          vx_audio_gain_get,
+	.put =          vx_audio_gain_put,
+	.tlv = { .p = db_scale_audio_gain },
+};
+static struct snd_kcontrol_new vx_control_output_switch = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "PCM Playback Switch",
+	.info =         vx_audio_sw_info,
+	.get =          vx_audio_sw_get,
+	.put =          vx_audio_sw_put
+};
+static struct snd_kcontrol_new vx_control_monitor_gain = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Monitoring Volume",
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.info =         vx_audio_gain_info,	/* shared */
+	.get =          vx_audio_monitor_get,
+	.put =          vx_audio_monitor_put,
+	.tlv = { .p = db_scale_audio_gain },
+};
+static struct snd_kcontrol_new vx_control_monitor_switch = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Monitoring Switch",
+	.info =         vx_audio_sw_info,	/* shared */
+	.get =          vx_monitor_sw_get,
+	.put =          vx_monitor_sw_put
+};
+
+
+/*
+ * IEC958 status bits
+ */
+static int vx_iec958_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int vx_iec958_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&chip->mixer_mutex);
+	ucontrol->value.iec958.status[0] = (chip->uer_bits >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (chip->uer_bits >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (chip->uer_bits >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (chip->uer_bits >> 24) & 0xff;
+	mutex_unlock(&chip->mixer_mutex);
+        return 0;
+}
+
+static int vx_iec958_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0xff;
+        return 0;
+}
+
+static int vx_iec958_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+
+	val = (ucontrol->value.iec958.status[0] << 0) |
+	      (ucontrol->value.iec958.status[1] << 8) |
+	      (ucontrol->value.iec958.status[2] << 16) |
+	      (ucontrol->value.iec958.status[3] << 24);
+	mutex_lock(&chip->mixer_mutex);
+	if (chip->uer_bits != val) {
+		chip->uer_bits = val;
+		vx_set_iec958_status(chip, val);
+		mutex_unlock(&chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&chip->mixer_mutex);
+	return 0;
+}
+
+static struct snd_kcontrol_new vx_control_iec958_mask = {
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+	.info =		vx_iec958_info,	/* shared */
+	.get =		vx_iec958_mask_get,
+};
+
+static struct snd_kcontrol_new vx_control_iec958 = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =         vx_iec958_info,
+	.get =          vx_iec958_get,
+	.put =          vx_iec958_put
+};
+
+
+/*
+ * VU meter
+ */
+
+#define METER_MAX	0xff
+#define METER_SHIFT	16
+
+static int vx_vu_meter_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = METER_MAX;
+	return 0;
+}
+
+static int vx_vu_meter_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	struct vx_vu_meter meter[2];
+	int audio = kcontrol->private_value & 0xff;
+	int capture = (kcontrol->private_value >> 8) & 1;
+
+	vx_get_audio_vu_meter(chip, audio, capture, meter);
+	ucontrol->value.integer.value[0] = meter[0].vu_level >> METER_SHIFT;
+	ucontrol->value.integer.value[1] = meter[1].vu_level >> METER_SHIFT;
+	return 0;
+}
+
+static int vx_peak_meter_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	struct vx_vu_meter meter[2];
+	int audio = kcontrol->private_value & 0xff;
+	int capture = (kcontrol->private_value >> 8) & 1;
+
+	vx_get_audio_vu_meter(chip, audio, capture, meter);
+	ucontrol->value.integer.value[0] = meter[0].peak_level >> METER_SHIFT;
+	ucontrol->value.integer.value[1] = meter[1].peak_level >> METER_SHIFT;
+	return 0;
+}
+
+#define vx_saturation_info	snd_ctl_boolean_stereo_info
+
+static int vx_saturation_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *chip = snd_kcontrol_chip(kcontrol);
+	struct vx_vu_meter meter[2];
+	int audio = kcontrol->private_value & 0xff;
+
+	vx_get_audio_vu_meter(chip, audio, 1, meter); /* capture only */
+	ucontrol->value.integer.value[0] = meter[0].saturated;
+	ucontrol->value.integer.value[1] = meter[1].saturated;
+	return 0;
+}
+
+static struct snd_kcontrol_new vx_control_vu_meter = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	/* name will be filled later */
+	.info =		vx_vu_meter_info,
+	.get =		vx_vu_meter_get,
+};
+
+static struct snd_kcontrol_new vx_control_peak_meter = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	/* name will be filled later */
+	.info =		vx_vu_meter_info,	/* shared */
+	.get =		vx_peak_meter_get,
+};
+
+static struct snd_kcontrol_new vx_control_saturation = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Input Saturation",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		vx_saturation_info,
+	.get =		vx_saturation_get,
+};
+
+
+
+/*
+ *
+ */
+
+int snd_vx_mixer_new(struct vx_core *chip)
+{
+	unsigned int i, c;
+	int err;
+	struct snd_kcontrol_new temp;
+	struct snd_card *card = chip->card;
+	char name[32];
+
+	strcpy(card->mixername, card->driver);
+
+	/* output level controls */
+	for (i = 0; i < chip->hw->num_outs; i++) {
+		temp = vx_control_output_level;
+		temp.index = i;
+		temp.tlv.p = chip->hw->output_level_db_scale;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+	}
+
+	/* PCM volumes, switches, monitoring */
+	for (i = 0; i < chip->hw->num_outs; i++) {
+		int val = i * 2;
+		temp = vx_control_audio_gain;
+		temp.index = i;
+		temp.name = "PCM Playback Volume";
+		temp.private_value = val;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+		temp = vx_control_output_switch;
+		temp.index = i;
+		temp.private_value = val;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+		temp = vx_control_monitor_gain;
+		temp.index = i;
+		temp.private_value = val;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+		temp = vx_control_monitor_switch;
+		temp.index = i;
+		temp.private_value = val;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+	}
+	for (i = 0; i < chip->hw->num_outs; i++) {
+		temp = vx_control_audio_gain;
+		temp.index = i;
+		temp.name = "PCM Capture Volume";
+		temp.private_value = (i * 2) | (1 << 8);
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+	}
+
+	/* Audio source */
+	if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_audio_src, chip))) < 0)
+		return err;
+	/* clock mode */
+	if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_clock_mode, chip))) < 0)
+		return err;
+	/* IEC958 controls */
+	if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958_mask, chip))) < 0)
+		return err;
+	if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958, chip))) < 0)
+		return err;
+	/* VU, peak, saturation meters */
+	for (c = 0; c < 2; c++) {
+		static char *dir[2] = { "Output", "Input" };
+		for (i = 0; i < chip->hw->num_ins; i++) {
+			int val = (i * 2) | (c << 8);
+			if (c == 1) {
+				temp = vx_control_saturation;
+				temp.index = i;
+				temp.private_value = val;
+				if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+					return err;
+			}
+			sprintf(name, "%s VU Meter", dir[c]);
+			temp = vx_control_vu_meter;
+			temp.index = i;
+			temp.name = name;
+			temp.private_value = val;
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+				return err;
+			sprintf(name, "%s Peak Meter", dir[c]);
+			temp = vx_control_peak_meter;
+			temp.index = i;
+			temp.name = name;
+			temp.private_value = val;
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0)
+				return err;
+		}
+	}
+	vx_reset_audio_levels(chip);
+	return 0;
+}
diff --git a/linux/sound/drivers/vx/vx_pcm.c b/linux/sound/drivers/vx/vx_pcm.c
new file mode 100644
index 0000000..35a2f71
--- /dev/null
+++ b/linux/sound/drivers/vx/vx_pcm.c
@@ -0,0 +1,1283 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * PCM part
+ *
+ * Copyright (c) 2002,2003 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *
+ * STRATEGY
+ *  for playback, we send series of "chunks", which size is equal with the
+ *  IBL size, typically 126 samples.  at each end of chunk, the end-of-buffer
+ *  interrupt is notified, and the interrupt handler will feed the next chunk.
+ *
+ *  the current position is calculated from the sample count RMH.
+ *  pipe->transferred is the counter of data which has been already transferred.
+ *  if this counter reaches to the period size, snd_pcm_period_elapsed() will
+ *  be issued.
+ *
+ *  for capture, the situation is much easier.
+ *  to get a low latency response, we'll check the capture streams at each
+ *  interrupt (capture stream has no EOB notification).  if the pending
+ *  data is accumulated to the period size, snd_pcm_period_elapsed() is
+ *  called and the pointer is updated.
+ *
+ *  the current point of read buffer is kept in pipe->hw_ptr.  note that
+ *  this is in bytes.
+ *
+ *
+ * TODO
+ *  - linked trigger for full-duplex mode.
+ *  - scheduled action on the stream.
+ */
+
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/asoundef.h>
+#include <sound/pcm.h>
+#include <sound/vx_core.h>
+#include "vx_cmd.h"
+
+
+/*
+ * read three pending pcm bytes via inb()
+ */
+static void vx_pcm_read_per_bytes(struct vx_core *chip, struct snd_pcm_runtime *runtime,
+				  struct vx_pipe *pipe)
+{
+	int offset = pipe->hw_ptr;
+	unsigned char *buf = (unsigned char *)(runtime->dma_area + offset);
+	*buf++ = vx_inb(chip, RXH);
+	if (++offset >= pipe->buffer_bytes) {
+		offset = 0;
+		buf = (unsigned char *)runtime->dma_area;
+	}
+	*buf++ = vx_inb(chip, RXM);
+	if (++offset >= pipe->buffer_bytes) {
+		offset = 0;
+		buf = (unsigned char *)runtime->dma_area;
+	}
+	*buf++ = vx_inb(chip, RXL);
+	if (++offset >= pipe->buffer_bytes) {
+		offset = 0;
+		buf = (unsigned char *)runtime->dma_area;
+	}
+	pipe->hw_ptr = offset;
+}
+
+/*
+ * vx_set_pcx_time - convert from the PC time to the RMH status time.
+ * @pc_time: the pointer for the PC-time to set
+ * @dsp_time: the pointer for RMH status time array
+ */
+static void vx_set_pcx_time(struct vx_core *chip, pcx_time_t *pc_time,
+			    unsigned int *dsp_time)
+{
+	dsp_time[0] = (unsigned int)((*pc_time) >> 24) & PCX_TIME_HI_MASK;
+	dsp_time[1] = (unsigned int)(*pc_time) &  MASK_DSP_WORD;
+}
+
+/*
+ * vx_set_differed_time - set the differed time if specified
+ * @rmh: the rmh record to modify
+ * @pipe: the pipe to be checked
+ *
+ * if the pipe is programmed with the differed time, set the DSP time
+ * on the rmh and changes its command length.
+ *
+ * returns the increase of the command length.
+ */
+static int vx_set_differed_time(struct vx_core *chip, struct vx_rmh *rmh,
+				struct vx_pipe *pipe)
+{
+	/* Update The length added to the RMH command by the timestamp */
+	if (! (pipe->differed_type & DC_DIFFERED_DELAY))
+		return 0;
+		
+	/* Set the T bit */
+	rmh->Cmd[0] |= DSP_DIFFERED_COMMAND_MASK;
+
+	/* Time stamp is the 1st following parameter */
+	vx_set_pcx_time(chip, &pipe->pcx_time, &rmh->Cmd[1]);
+
+	/* Add the flags to a notified differed command */
+	if (pipe->differed_type & DC_NOTIFY_DELAY)
+		rmh->Cmd[1] |= NOTIFY_MASK_TIME_HIGH ;
+
+	/* Add the flags to a multiple differed command */
+	if (pipe->differed_type & DC_MULTIPLE_DELAY)
+		rmh->Cmd[1] |= MULTIPLE_MASK_TIME_HIGH;
+
+	/* Add the flags to a stream-time differed command */
+	if (pipe->differed_type & DC_STREAM_TIME_DELAY)
+		rmh->Cmd[1] |= STREAM_MASK_TIME_HIGH;
+		
+	rmh->LgCmd += 2;
+	return 2;
+}
+
+/*
+ * vx_set_stream_format - send the stream format command
+ * @pipe: the affected pipe
+ * @data: format bitmask
+ */
+static int vx_set_stream_format(struct vx_core *chip, struct vx_pipe *pipe,
+				unsigned int data)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, pipe->is_capture ?
+		    CMD_FORMAT_STREAM_IN : CMD_FORMAT_STREAM_OUT);
+	rmh.Cmd[0] |= pipe->number << FIELD_SIZE;
+
+        /* Command might be longer since we may have to add a timestamp */
+	vx_set_differed_time(chip, &rmh, pipe);
+
+	rmh.Cmd[rmh.LgCmd] = (data & 0xFFFFFF00) >> 8;
+	rmh.Cmd[rmh.LgCmd + 1] = (data & 0xFF) << 16 /*| (datal & 0xFFFF00) >> 8*/;
+	rmh.LgCmd += 2;
+    
+	return vx_send_msg(chip, &rmh);
+}
+
+
+/*
+ * vx_set_format - set the format of a pipe
+ * @pipe: the affected pipe
+ * @runtime: pcm runtime instance to be referred
+ *
+ * returns 0 if successful, or a negative error code.
+ */
+static int vx_set_format(struct vx_core *chip, struct vx_pipe *pipe,
+			 struct snd_pcm_runtime *runtime)
+{
+	unsigned int header = HEADER_FMT_BASE;
+
+	if (runtime->channels == 1)
+		header |= HEADER_FMT_MONO;
+	if (snd_pcm_format_little_endian(runtime->format))
+		header |= HEADER_FMT_INTEL;
+	if (runtime->rate < 32000 && runtime->rate > 11025)
+		header |= HEADER_FMT_UPTO32;
+	else if (runtime->rate <= 11025)
+		header |= HEADER_FMT_UPTO11;
+
+	switch (snd_pcm_format_physical_width(runtime->format)) {
+	// case 8: break;
+	case 16: header |= HEADER_FMT_16BITS; break;
+	case 24: header |= HEADER_FMT_24BITS; break;
+	default : 
+		snd_BUG();
+		return -EINVAL;
+        };
+
+	return vx_set_stream_format(chip, pipe, header);
+}
+
+/*
+ * set / query the IBL size
+ */
+static int vx_set_ibl(struct vx_core *chip, struct vx_ibl_info *info)
+{
+	int err;
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_IBL);
+	rmh.Cmd[0] |= info->size & 0x03ffff;
+	err = vx_send_msg(chip, &rmh);
+	if (err < 0)
+		return err;
+	info->size = rmh.Stat[0];
+	info->max_size = rmh.Stat[1];
+	info->min_size = rmh.Stat[2];
+	info->granularity = rmh.Stat[3];
+	snd_printdd(KERN_DEBUG "vx_set_ibl: size = %d, max = %d, min = %d, gran = %d\n",
+		   info->size, info->max_size, info->min_size, info->granularity);
+	return 0;
+}
+
+
+/*
+ * vx_get_pipe_state - get the state of a pipe
+ * @pipe: the pipe to be checked
+ * @state: the pointer for the returned state
+ *
+ * checks the state of a given pipe, and stores the state (1 = running,
+ * 0 = paused) on the given pointer.
+ *
+ * called from trigger callback only
+ */
+static int vx_get_pipe_state(struct vx_core *chip, struct vx_pipe *pipe, int *state)
+{
+	int err;
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_PIPE_STATE);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	err = vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+	if (! err)
+		*state = (rmh.Stat[0] & (1 << pipe->number)) ? 1 : 0;
+	return err;
+}
+
+
+/*
+ * vx_query_hbuffer_size - query available h-buffer size in bytes
+ * @pipe: the pipe to be checked
+ *
+ * return the available size on h-buffer in bytes,
+ * or a negative error code.
+ *
+ * NOTE: calling this function always switches to the stream mode.
+ *       you'll need to disconnect the host to get back to the
+ *       normal mode.
+ */
+static int vx_query_hbuffer_size(struct vx_core *chip, struct vx_pipe *pipe)
+{
+	int result;
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_SIZE_HBUFFER);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	if (pipe->is_capture)
+		rmh.Cmd[0] |= 0x00000001;
+	result = vx_send_msg(chip, &rmh);
+	if (! result)
+		result = rmh.Stat[0] & 0xffff;
+	return result;
+}
+
+
+/*
+ * vx_pipe_can_start - query whether a pipe is ready for start
+ * @pipe: the pipe to be checked
+ *
+ * return 1 if ready, 0 if not ready, and negative value on error.
+ *
+ * called from trigger callback only
+ */
+static int vx_pipe_can_start(struct vx_core *chip, struct vx_pipe *pipe)
+{
+	int err;
+	struct vx_rmh rmh;
+        
+	vx_init_rmh(&rmh, CMD_CAN_START_PIPE);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	rmh.Cmd[0] |= 1;
+
+	err = vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+	if (! err) {
+		if (rmh.Stat[0])
+			err = 1;
+	}
+	return err;
+}
+
+/*
+ * vx_conf_pipe - tell the pipe to stand by and wait for IRQA.
+ * @pipe: the pipe to be configured
+ */
+static int vx_conf_pipe(struct vx_core *chip, struct vx_pipe *pipe)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_CONF_PIPE);
+	if (pipe->is_capture)
+		rmh.Cmd[0] |= COMMAND_RECORD_MASK;
+	rmh.Cmd[1] = 1 << pipe->number;
+	return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */
+}
+
+/*
+ * vx_send_irqa - trigger IRQA
+ */
+static int vx_send_irqa(struct vx_core *chip)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_SEND_IRQA);
+	return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+}
+
+
+#define MAX_WAIT_FOR_DSP        250
+/*
+ * vx boards do not support inter-card sync, besides
+ * only 126 samples require to be prepared before a pipe can start
+ */
+#define CAN_START_DELAY         2	/* wait 2ms only before asking if the pipe is ready*/
+#define WAIT_STATE_DELAY        2	/* wait 2ms after irqA was requested and check if the pipe state toggled*/
+
+/*
+ * vx_toggle_pipe - start / pause a pipe
+ * @pipe: the pipe to be triggered
+ * @state: start = 1, pause = 0
+ *
+ * called from trigger callback only
+ *
+ */
+static int vx_toggle_pipe(struct vx_core *chip, struct vx_pipe *pipe, int state)
+{
+	int err, i, cur_state;
+
+	/* Check the pipe is not already in the requested state */
+	if (vx_get_pipe_state(chip, pipe, &cur_state) < 0)
+		return -EBADFD;
+	if (state == cur_state)
+		return 0;
+
+	/* If a start is requested, ask the DSP to get prepared
+	 * and wait for a positive acknowledge (when there are
+	 * enough sound buffer for this pipe)
+	 */
+	if (state) {
+		for (i = 0 ; i < MAX_WAIT_FOR_DSP; i++) {
+			err = vx_pipe_can_start(chip, pipe);
+			if (err > 0)
+				break;
+			/* Wait for a few, before asking again
+			 * to avoid flooding the DSP with our requests
+			 */
+			mdelay(1);
+		}
+	}
+    
+	if ((err = vx_conf_pipe(chip, pipe)) < 0)
+		return err;
+
+	if ((err = vx_send_irqa(chip)) < 0)
+		return err;
+    
+	/* If it completes successfully, wait for the pipes
+	 * reaching the expected state before returning
+	 * Check one pipe only (since they are synchronous)
+	 */
+	for (i = 0; i < MAX_WAIT_FOR_DSP; i++) {
+		err = vx_get_pipe_state(chip, pipe, &cur_state);
+		if (err < 0 || cur_state == state)
+			break;
+		err = -EIO;
+		mdelay(1);
+	}
+	return err < 0 ? -EIO : 0;
+}
+
+    
+/*
+ * vx_stop_pipe - stop a pipe
+ * @pipe: the pipe to be stopped
+ *
+ * called from trigger callback only
+ */
+static int vx_stop_pipe(struct vx_core *chip, struct vx_pipe *pipe)
+{
+	struct vx_rmh rmh;
+	vx_init_rmh(&rmh, CMD_STOP_PIPE);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+}
+
+
+/*
+ * vx_alloc_pipe - allocate a pipe and initialize the pipe instance
+ * @capture: 0 = playback, 1 = capture operation
+ * @audioid: the audio id to be assigned
+ * @num_audio: number of audio channels
+ * @pipep: the returned pipe instance
+ *
+ * return 0 on success, or a negative error code.
+ */
+static int vx_alloc_pipe(struct vx_core *chip, int capture,
+			 int audioid, int num_audio,
+			 struct vx_pipe **pipep)
+{
+	int err;
+	struct vx_pipe *pipe;
+	struct vx_rmh rmh;
+	int data_mode;
+
+	*pipep = NULL;
+	vx_init_rmh(&rmh, CMD_RES_PIPE);
+	vx_set_pipe_cmd_params(&rmh, capture, audioid, num_audio);
+#if 0	// NYI
+	if (underrun_skip_sound)
+		rmh.Cmd[0] |= BIT_SKIP_SOUND;
+#endif	// NYI
+	data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0;
+	if (! capture && data_mode)
+		rmh.Cmd[0] |= BIT_DATA_MODE;
+	err = vx_send_msg(chip, &rmh);
+	if (err < 0)
+		return err;
+
+	/* initialize the pipe record */
+	pipe = kzalloc(sizeof(*pipe), GFP_KERNEL);
+	if (! pipe) {
+		/* release the pipe */
+		vx_init_rmh(&rmh, CMD_FREE_PIPE);
+		vx_set_pipe_cmd_params(&rmh, capture, audioid, 0);
+		vx_send_msg(chip, &rmh);
+		return -ENOMEM;
+	}
+
+	/* the pipe index should be identical with the audio index */
+	pipe->number = audioid;
+	pipe->is_capture = capture;
+	pipe->channels = num_audio;
+	pipe->differed_type = 0;
+	pipe->pcx_time = 0;
+	pipe->data_mode = data_mode;
+	*pipep = pipe;
+
+	return 0;
+}
+
+
+/*
+ * vx_free_pipe - release a pipe
+ * @pipe: pipe to be released
+ */
+static int vx_free_pipe(struct vx_core *chip, struct vx_pipe *pipe)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_FREE_PIPE);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	vx_send_msg(chip, &rmh);
+
+	kfree(pipe);
+	return 0;
+}
+
+
+/*
+ * vx_start_stream - start the stream
+ *
+ * called from trigger callback only
+ */
+static int vx_start_stream(struct vx_core *chip, struct vx_pipe *pipe)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_START_ONE_STREAM);
+	vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number);
+	vx_set_differed_time(chip, &rmh, pipe);
+	return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+}
+
+
+/*
+ * vx_stop_stream - stop the stream
+ *
+ * called from trigger callback only
+ */
+static int vx_stop_stream(struct vx_core *chip, struct vx_pipe *pipe)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_STOP_STREAM);
+	vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number);
+	return vx_send_msg_nolock(chip, &rmh); /* no lock needed for trigger */ 
+}
+
+
+/*
+ * playback hw information
+ */
+
+static struct snd_pcm_hardware vx_pcm_playback_hw = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID /*|*/
+				 /*SNDRV_PCM_INFO_RESUME*/),
+	.formats =		(/*SNDRV_PCM_FMTBIT_U8 |*/
+				 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	126,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		VX_MAX_PERIODS,
+	.fifo_size =		126,
+};
+
+
+static void vx_pcm_delayed_start(unsigned long arg);
+
+/*
+ * vx_pcm_playback_open - open callback for playback
+ */
+static int vx_pcm_playback_open(struct snd_pcm_substream *subs)
+{
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct vx_core *chip = snd_pcm_substream_chip(subs);
+	struct vx_pipe *pipe = NULL;
+	unsigned int audio;
+	int err;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+	audio = subs->pcm->device * 2;
+	if (snd_BUG_ON(audio >= chip->audio_outs))
+		return -EINVAL;
+	
+	/* playback pipe may have been already allocated for monitoring */
+	pipe = chip->playback_pipes[audio];
+	if (! pipe) {
+		/* not allocated yet */
+		err = vx_alloc_pipe(chip, 0, audio, 2, &pipe); /* stereo playback */
+		if (err < 0)
+			return err;
+		chip->playback_pipes[audio] = pipe;
+	}
+	/* open for playback */
+	pipe->references++;
+
+	pipe->substream = subs;
+	tasklet_init(&pipe->start_tq, vx_pcm_delayed_start, (unsigned long)subs);
+	chip->playback_pipes[audio] = pipe;
+
+	runtime->hw = vx_pcm_playback_hw;
+	runtime->hw.period_bytes_min = chip->ibl.size;
+	runtime->private_data = pipe;
+
+	/* align to 4 bytes (otherwise will be problematic when 24bit is used) */ 
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4);
+
+	return 0;
+}
+
+/*
+ * vx_pcm_playback_close - close callback for playback
+ */
+static int vx_pcm_playback_close(struct snd_pcm_substream *subs)
+{
+	struct vx_core *chip = snd_pcm_substream_chip(subs);
+	struct vx_pipe *pipe;
+
+	if (! subs->runtime->private_data)
+		return -EINVAL;
+
+	pipe = subs->runtime->private_data;
+
+	if (--pipe->references == 0) {
+		chip->playback_pipes[pipe->number] = NULL;
+		vx_free_pipe(chip, pipe);
+	}
+
+	return 0;
+
+}
+
+
+/*
+ * vx_notify_end_of_buffer - send "end-of-buffer" notifier at the given pipe
+ * @pipe: the pipe to notify
+ *
+ * NB: call with a certain lock.
+ */
+static int vx_notify_end_of_buffer(struct vx_core *chip, struct vx_pipe *pipe)
+{
+	int err;
+	struct vx_rmh rmh;  /* use a temporary rmh here */
+
+	/* Toggle Dsp Host Interface into Message mode */
+	vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT);
+	vx_init_rmh(&rmh, CMD_NOTIFY_END_OF_BUFFER);
+	vx_set_stream_cmd_params(&rmh, 0, pipe->number);
+	err = vx_send_msg_nolock(chip, &rmh);
+	if (err < 0)
+		return err;
+	/* Toggle Dsp Host Interface back to sound transfer mode */
+	vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT);
+	return 0;
+}
+
+/*
+ * vx_pcm_playback_transfer_chunk - transfer a single chunk
+ * @subs: substream
+ * @pipe: the pipe to transfer
+ * @size: chunk size in bytes
+ *
+ * transfer a single buffer chunk.  EOB notificaton is added after that.
+ * called from the interrupt handler, too.
+ *
+ * return 0 if ok.
+ */
+static int vx_pcm_playback_transfer_chunk(struct vx_core *chip,
+					  struct snd_pcm_runtime *runtime,
+					  struct vx_pipe *pipe, int size)
+{
+	int space, err = 0;
+
+	space = vx_query_hbuffer_size(chip, pipe);
+	if (space < 0) {
+		/* disconnect the host, SIZE_HBUF command always switches to the stream mode */
+		vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT);
+		snd_printd("error hbuffer\n");
+		return space;
+	}
+	if (space < size) {
+		vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT);
+		snd_printd("no enough hbuffer space %d\n", space);
+		return -EIO; /* XRUN */
+	}
+		
+	/* we don't need irqsave here, because this function
+	 * is called from either trigger callback or irq handler
+	 */
+	spin_lock(&chip->lock); 
+	vx_pseudo_dma_write(chip, runtime, pipe, size);
+	err = vx_notify_end_of_buffer(chip, pipe);
+	/* disconnect the host, SIZE_HBUF command always switches to the stream mode */
+	vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT);
+	spin_unlock(&chip->lock);
+	return err;
+}
+
+/*
+ * update the position of the given pipe.
+ * pipe->position is updated and wrapped within the buffer size.
+ * pipe->transferred is updated, too, but the size is not wrapped,
+ * so that the caller can check the total transferred size later
+ * (to call snd_pcm_period_elapsed).
+ */
+static int vx_update_pipe_position(struct vx_core *chip,
+				   struct snd_pcm_runtime *runtime,
+				   struct vx_pipe *pipe)
+{
+	struct vx_rmh rmh;
+	int err, update;
+	u64 count;
+
+	vx_init_rmh(&rmh, CMD_STREAM_SAMPLE_COUNT);
+	vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0);
+	err = vx_send_msg(chip, &rmh);
+	if (err < 0)
+		return err;
+
+	count = ((u64)(rmh.Stat[0] & 0xfffff) << 24) | (u64)rmh.Stat[1];
+	update = (int)(count - pipe->cur_count);
+	pipe->cur_count = count;
+	pipe->position += update;
+	if (pipe->position >= (int)runtime->buffer_size)
+		pipe->position %= runtime->buffer_size;
+	pipe->transferred += update;
+	return 0;
+}
+
+/*
+ * transfer the pending playback buffer data to DSP
+ * called from interrupt handler
+ */
+static void vx_pcm_playback_transfer(struct vx_core *chip,
+				     struct snd_pcm_substream *subs,
+				     struct vx_pipe *pipe, int nchunks)
+{
+	int i, err;
+	struct snd_pcm_runtime *runtime = subs->runtime;
+
+	if (! pipe->prepared || (chip->chip_status & VX_STAT_IS_STALE))
+		return;
+	for (i = 0; i < nchunks; i++) {
+		if ((err = vx_pcm_playback_transfer_chunk(chip, runtime, pipe,
+							  chip->ibl.size)) < 0)
+			return;
+	}
+}
+
+/*
+ * update the playback position and call snd_pcm_period_elapsed() if necessary
+ * called from interrupt handler
+ */
+static void vx_pcm_playback_update(struct vx_core *chip,
+				   struct snd_pcm_substream *subs,
+				   struct vx_pipe *pipe)
+{
+	int err;
+	struct snd_pcm_runtime *runtime = subs->runtime;
+
+	if (pipe->running && ! (chip->chip_status & VX_STAT_IS_STALE)) {
+		if ((err = vx_update_pipe_position(chip, runtime, pipe)) < 0)
+			return;
+		if (pipe->transferred >= (int)runtime->period_size) {
+			pipe->transferred %= runtime->period_size;
+			snd_pcm_period_elapsed(subs);
+		}
+	}
+}
+
+/*
+ * start the stream and pipe.
+ * this function is called from tasklet, which is invoked by the trigger
+ * START callback.
+ */
+static void vx_pcm_delayed_start(unsigned long arg)
+{
+	struct snd_pcm_substream *subs = (struct snd_pcm_substream *)arg;
+	struct vx_core *chip = subs->pcm->private_data;
+	struct vx_pipe *pipe = subs->runtime->private_data;
+	int err;
+
+	/*  printk( KERN_DEBUG "DDDD tasklet delayed start jiffies = %ld\n", jiffies);*/
+
+	if ((err = vx_start_stream(chip, pipe)) < 0) {
+		snd_printk(KERN_ERR "vx: cannot start stream\n");
+		return;
+	}
+	if ((err = vx_toggle_pipe(chip, pipe, 1)) < 0) {
+		snd_printk(KERN_ERR "vx: cannot start pipe\n");
+		return;
+	}
+	/*   printk( KERN_DEBUG "dddd tasklet delayed start jiffies = %ld \n", jiffies);*/
+}
+
+/*
+ * vx_pcm_playback_trigger - trigger callback for playback
+ */
+static int vx_pcm_trigger(struct snd_pcm_substream *subs, int cmd)
+{
+	struct vx_core *chip = snd_pcm_substream_chip(subs);
+	struct vx_pipe *pipe = subs->runtime->private_data;
+	int err;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+		
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (! pipe->is_capture)
+			vx_pcm_playback_transfer(chip, subs, pipe, 2);
+		/* FIXME:
+		 * we trigger the pipe using tasklet, so that the interrupts are
+		 * issued surely after the trigger is completed.
+		 */ 
+		tasklet_schedule(&pipe->start_tq);
+		chip->pcm_running++;
+		pipe->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		vx_toggle_pipe(chip, pipe, 0);
+		vx_stop_pipe(chip, pipe);
+		vx_stop_stream(chip, pipe);
+		chip->pcm_running--;
+		pipe->running = 0;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if ((err = vx_toggle_pipe(chip, pipe, 0)) < 0)
+			return err;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if ((err = vx_toggle_pipe(chip, pipe, 1)) < 0)
+			return err;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * vx_pcm_playback_pointer - pointer callback for playback
+ */
+static snd_pcm_uframes_t vx_pcm_playback_pointer(struct snd_pcm_substream *subs)
+{
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct vx_pipe *pipe = runtime->private_data;
+	return pipe->position;
+}
+
+/*
+ * vx_pcm_hw_params - hw_params callback for playback and capture
+ */
+static int vx_pcm_hw_params(struct snd_pcm_substream *subs,
+				     struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_alloc_vmalloc_32_buffer
+					(subs, params_buffer_bytes(hw_params));
+}
+
+/*
+ * vx_pcm_hw_free - hw_free callback for playback and capture
+ */
+static int vx_pcm_hw_free(struct snd_pcm_substream *subs)
+{
+	return snd_pcm_lib_free_vmalloc_buffer(subs);
+}
+
+/*
+ * vx_pcm_prepare - prepare callback for playback and capture
+ */
+static int vx_pcm_prepare(struct snd_pcm_substream *subs)
+{
+	struct vx_core *chip = snd_pcm_substream_chip(subs);
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct vx_pipe *pipe = runtime->private_data;
+	int err, data_mode;
+	// int max_size, nchunks;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+	data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0;
+	if (data_mode != pipe->data_mode && ! pipe->is_capture) {
+		/* IEC958 status (raw-mode) was changed */
+		/* we reopen the pipe */
+		struct vx_rmh rmh;
+		snd_printdd(KERN_DEBUG "reopen the pipe with data_mode = %d\n", data_mode);
+		vx_init_rmh(&rmh, CMD_FREE_PIPE);
+		vx_set_pipe_cmd_params(&rmh, 0, pipe->number, 0);
+		if ((err = vx_send_msg(chip, &rmh)) < 0)
+			return err;
+		vx_init_rmh(&rmh, CMD_RES_PIPE);
+		vx_set_pipe_cmd_params(&rmh, 0, pipe->number, pipe->channels);
+		if (data_mode)
+			rmh.Cmd[0] |= BIT_DATA_MODE;
+		if ((err = vx_send_msg(chip, &rmh)) < 0)
+			return err;
+		pipe->data_mode = data_mode;
+	}
+
+	if (chip->pcm_running && chip->freq != runtime->rate) {
+		snd_printk(KERN_ERR "vx: cannot set different clock %d "
+			   "from the current %d\n", runtime->rate, chip->freq);
+		return -EINVAL;
+	}
+	vx_set_clock(chip, runtime->rate);
+
+	if ((err = vx_set_format(chip, pipe, runtime)) < 0)
+		return err;
+
+	if (vx_is_pcmcia(chip)) {
+		pipe->align = 2; /* 16bit word */
+	} else {
+		pipe->align = 4; /* 32bit word */
+	}
+
+	pipe->buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size);
+	pipe->period_bytes = frames_to_bytes(runtime, runtime->period_size);
+	pipe->hw_ptr = 0;
+
+	/* set the timestamp */
+	vx_update_pipe_position(chip, runtime, pipe);
+	/* clear again */
+	pipe->transferred = 0;
+	pipe->position = 0;
+
+	pipe->prepared = 1;
+
+	return 0;
+}
+
+
+/*
+ * operators for PCM playback
+ */
+static struct snd_pcm_ops vx_pcm_playback_ops = {
+	.open =		vx_pcm_playback_open,
+	.close =	vx_pcm_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	vx_pcm_hw_params,
+	.hw_free =	vx_pcm_hw_free,
+	.prepare =	vx_pcm_prepare,
+	.trigger =	vx_pcm_trigger,
+	.pointer =	vx_pcm_playback_pointer,
+	.page =		snd_pcm_lib_get_vmalloc_page,
+	.mmap =		snd_pcm_lib_mmap_vmalloc,
+};
+
+
+/*
+ * playback hw information
+ */
+
+static struct snd_pcm_hardware vx_pcm_capture_hw = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID /*|*/
+				 /*SNDRV_PCM_INFO_RESUME*/),
+	.formats =		(/*SNDRV_PCM_FMTBIT_U8 |*/
+				 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	126,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		VX_MAX_PERIODS,
+	.fifo_size =		126,
+};
+
+
+/*
+ * vx_pcm_capture_open - open callback for capture
+ */
+static int vx_pcm_capture_open(struct snd_pcm_substream *subs)
+{
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct vx_core *chip = snd_pcm_substream_chip(subs);
+	struct vx_pipe *pipe;
+	struct vx_pipe *pipe_out_monitoring = NULL;
+	unsigned int audio;
+	int err;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return -EBUSY;
+
+	audio = subs->pcm->device * 2;
+	if (snd_BUG_ON(audio >= chip->audio_ins))
+		return -EINVAL;
+	err = vx_alloc_pipe(chip, 1, audio, 2, &pipe);
+	if (err < 0)
+		return err;
+	pipe->substream = subs;
+	tasklet_init(&pipe->start_tq, vx_pcm_delayed_start, (unsigned long)subs);
+	chip->capture_pipes[audio] = pipe;
+
+	/* check if monitoring is needed */
+	if (chip->audio_monitor_active[audio]) {
+		pipe_out_monitoring = chip->playback_pipes[audio];
+		if (! pipe_out_monitoring) {
+			/* allocate a pipe */
+			err = vx_alloc_pipe(chip, 0, audio, 2, &pipe_out_monitoring);
+			if (err < 0)
+				return err;
+			chip->playback_pipes[audio] = pipe_out_monitoring;
+		}
+		pipe_out_monitoring->references++;
+		/* 
+		   if an output pipe is available, it's audios still may need to be 
+		   unmuted. hence we'll have to call a mixer entry point.
+		*/
+		vx_set_monitor_level(chip, audio, chip->audio_monitor[audio],
+				     chip->audio_monitor_active[audio]);
+		/* assuming stereo */
+		vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1],
+				     chip->audio_monitor_active[audio+1]); 
+	}
+
+	pipe->monitoring_pipe = pipe_out_monitoring; /* default value NULL */
+
+	runtime->hw = vx_pcm_capture_hw;
+	runtime->hw.period_bytes_min = chip->ibl.size;
+	runtime->private_data = pipe;
+
+	/* align to 4 bytes (otherwise will be problematic when 24bit is used) */ 
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4);
+
+	return 0;
+}
+
+/*
+ * vx_pcm_capture_close - close callback for capture
+ */
+static int vx_pcm_capture_close(struct snd_pcm_substream *subs)
+{
+	struct vx_core *chip = snd_pcm_substream_chip(subs);
+	struct vx_pipe *pipe;
+	struct vx_pipe *pipe_out_monitoring;
+	
+	if (! subs->runtime->private_data)
+		return -EINVAL;
+	pipe = subs->runtime->private_data;
+	chip->capture_pipes[pipe->number] = NULL;
+
+	pipe_out_monitoring = pipe->monitoring_pipe;
+
+	/*
+	  if an output pipe is attached to this input, 
+	  check if it needs to be released.
+	*/
+	if (pipe_out_monitoring) {
+		if (--pipe_out_monitoring->references == 0) {
+			vx_free_pipe(chip, pipe_out_monitoring);
+			chip->playback_pipes[pipe->number] = NULL;
+			pipe->monitoring_pipe = NULL;
+		}
+	}
+	
+	vx_free_pipe(chip, pipe);
+	return 0;
+}
+
+
+
+#define DMA_READ_ALIGN	6	/* hardware alignment for read */
+
+/*
+ * vx_pcm_capture_update - update the capture buffer
+ */
+static void vx_pcm_capture_update(struct vx_core *chip, struct snd_pcm_substream *subs,
+				  struct vx_pipe *pipe)
+{
+	int size, space, count;
+	struct snd_pcm_runtime *runtime = subs->runtime;
+
+	if (! pipe->prepared || (chip->chip_status & VX_STAT_IS_STALE))
+		return;
+
+	size = runtime->buffer_size - snd_pcm_capture_avail(runtime);
+	if (! size)
+		return;
+	size = frames_to_bytes(runtime, size);
+	space = vx_query_hbuffer_size(chip, pipe);
+	if (space < 0)
+		goto _error;
+	if (size > space)
+		size = space;
+	size = (size / 3) * 3; /* align to 3 bytes */
+	if (size < DMA_READ_ALIGN)
+		goto _error;
+
+	/* keep the last 6 bytes, they will be read after disconnection */
+	count = size - DMA_READ_ALIGN;
+	/* read bytes until the current pointer reaches to the aligned position
+	 * for word-transfer
+	 */
+	while (count > 0) {
+		if ((pipe->hw_ptr % pipe->align) == 0)
+			break;
+		if (vx_wait_for_rx_full(chip) < 0)
+			goto _error;
+		vx_pcm_read_per_bytes(chip, runtime, pipe);
+		count -= 3;
+	}
+	if (count > 0) {
+		/* ok, let's accelerate! */
+		int align = pipe->align * 3;
+		space = (count / align) * align;
+		vx_pseudo_dma_read(chip, runtime, pipe, space);
+		count -= space;
+	}
+	/* read the rest of bytes */
+	while (count > 0) {
+		if (vx_wait_for_rx_full(chip) < 0)
+			goto _error;
+		vx_pcm_read_per_bytes(chip, runtime, pipe);
+		count -= 3;
+	}
+	/* disconnect the host, SIZE_HBUF command always switches to the stream mode */
+	vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT);
+	/* read the last pending 6 bytes */
+	count = DMA_READ_ALIGN;
+	while (count > 0) {
+		vx_pcm_read_per_bytes(chip, runtime, pipe);
+		count -= 3;
+	}
+	/* update the position */
+	pipe->transferred += size;
+	if (pipe->transferred >= pipe->period_bytes) {
+		pipe->transferred %= pipe->period_bytes;
+		snd_pcm_period_elapsed(subs);
+	}
+	return;
+
+ _error:
+	/* disconnect the host, SIZE_HBUF command always switches to the stream mode */
+	vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT);
+	return;
+}
+
+/*
+ * vx_pcm_capture_pointer - pointer callback for capture
+ */
+static snd_pcm_uframes_t vx_pcm_capture_pointer(struct snd_pcm_substream *subs)
+{
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct vx_pipe *pipe = runtime->private_data;
+	return bytes_to_frames(runtime, pipe->hw_ptr);
+}
+
+/*
+ * operators for PCM capture
+ */
+static struct snd_pcm_ops vx_pcm_capture_ops = {
+	.open =		vx_pcm_capture_open,
+	.close =	vx_pcm_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	vx_pcm_hw_params,
+	.hw_free =	vx_pcm_hw_free,
+	.prepare =	vx_pcm_prepare,
+	.trigger =	vx_pcm_trigger,
+	.pointer =	vx_pcm_capture_pointer,
+	.page =		snd_pcm_lib_get_vmalloc_page,
+	.mmap =		snd_pcm_lib_mmap_vmalloc,
+};
+
+
+/*
+ * interrupt handler for pcm streams
+ */
+void vx_pcm_update_intr(struct vx_core *chip, unsigned int events)
+{
+	unsigned int i;
+	struct vx_pipe *pipe;
+
+#define EVENT_MASK	(END_OF_BUFFER_EVENTS_PENDING|ASYNC_EVENTS_PENDING)
+
+	if (events & EVENT_MASK) {
+		vx_init_rmh(&chip->irq_rmh, CMD_ASYNC);
+		if (events & ASYNC_EVENTS_PENDING)
+			chip->irq_rmh.Cmd[0] |= 0x00000001;	/* SEL_ASYNC_EVENTS */
+		if (events & END_OF_BUFFER_EVENTS_PENDING)
+			chip->irq_rmh.Cmd[0] |= 0x00000002;	/* SEL_END_OF_BUF_EVENTS */
+
+		if (vx_send_msg(chip, &chip->irq_rmh) < 0) {
+			snd_printdd(KERN_ERR "msg send error!!\n");
+			return;
+		}
+
+		i = 1;
+		while (i < chip->irq_rmh.LgStat) {
+			int p, buf, capture, eob;
+			p = chip->irq_rmh.Stat[i] & MASK_FIRST_FIELD;
+			capture = (chip->irq_rmh.Stat[i] & 0x400000) ? 1 : 0;
+			eob = (chip->irq_rmh.Stat[i] & 0x800000) ? 1 : 0;
+			i++;
+			if (events & ASYNC_EVENTS_PENDING)
+				i++;
+			buf = 1; /* force to transfer */
+			if (events & END_OF_BUFFER_EVENTS_PENDING) {
+				if (eob)
+					buf = chip->irq_rmh.Stat[i];
+				i++;
+			}
+			if (capture)
+				continue;
+			if (snd_BUG_ON(p < 0 || p >= chip->audio_outs))
+				continue;
+			pipe = chip->playback_pipes[p];
+			if (pipe && pipe->substream) {
+				vx_pcm_playback_update(chip, pipe->substream, pipe);
+				vx_pcm_playback_transfer(chip, pipe->substream, pipe, buf);
+			}
+		}
+	}
+
+	/* update the capture pcm pointers as frequently as possible */
+	for (i = 0; i < chip->audio_ins; i++) {
+		pipe = chip->capture_pipes[i];
+		if (pipe && pipe->substream)
+			vx_pcm_capture_update(chip, pipe->substream, pipe);
+	}
+}
+
+
+/*
+ * vx_init_audio_io - check the availabe audio i/o and allocate pipe arrays
+ */
+static int vx_init_audio_io(struct vx_core *chip)
+{
+	struct vx_rmh rmh;
+	int preferred;
+
+	vx_init_rmh(&rmh, CMD_SUPPORTED);
+	if (vx_send_msg(chip, &rmh) < 0) {
+		snd_printk(KERN_ERR "vx: cannot get the supported audio data\n");
+		return -ENXIO;
+	}
+
+	chip->audio_outs = rmh.Stat[0] & MASK_FIRST_FIELD;
+	chip->audio_ins = (rmh.Stat[0] >> (FIELD_SIZE*2)) & MASK_FIRST_FIELD;
+	chip->audio_info = rmh.Stat[1];
+
+	/* allocate pipes */
+	chip->playback_pipes = kcalloc(chip->audio_outs, sizeof(struct vx_pipe *), GFP_KERNEL);
+	if (!chip->playback_pipes)
+		return -ENOMEM;
+	chip->capture_pipes = kcalloc(chip->audio_ins, sizeof(struct vx_pipe *), GFP_KERNEL);
+	if (!chip->capture_pipes) {
+		kfree(chip->playback_pipes);
+		return -ENOMEM;
+	}
+
+	preferred = chip->ibl.size;
+	chip->ibl.size = 0;
+	vx_set_ibl(chip, &chip->ibl); /* query the info */
+	if (preferred > 0) {
+		chip->ibl.size = ((preferred + chip->ibl.granularity - 1) /
+				  chip->ibl.granularity) * chip->ibl.granularity;
+		if (chip->ibl.size > chip->ibl.max_size)
+			chip->ibl.size = chip->ibl.max_size;
+	} else
+		chip->ibl.size = chip->ibl.min_size; /* set to the minimum */
+	vx_set_ibl(chip, &chip->ibl);
+
+	return 0;
+}
+
+
+/*
+ * free callback for pcm
+ */
+static void snd_vx_pcm_free(struct snd_pcm *pcm)
+{
+	struct vx_core *chip = pcm->private_data;
+	chip->pcm[pcm->device] = NULL;
+	kfree(chip->playback_pipes);
+	chip->playback_pipes = NULL;
+	kfree(chip->capture_pipes);
+	chip->capture_pipes = NULL;
+}
+
+/*
+ * snd_vx_pcm_new - create and initialize a pcm
+ */
+int snd_vx_pcm_new(struct vx_core *chip)
+{
+	struct snd_pcm *pcm;
+	unsigned int i;
+	int err;
+
+	if ((err = vx_init_audio_io(chip)) < 0)
+		return err;
+
+	for (i = 0; i < chip->hw->num_codecs; i++) {
+		unsigned int outs, ins;
+		outs = chip->audio_outs > i * 2 ? 1 : 0;
+		ins = chip->audio_ins > i * 2 ? 1 : 0;
+		if (! outs && ! ins)
+			break;
+		err = snd_pcm_new(chip->card, "VX PCM", i,
+				  outs, ins, &pcm);
+		if (err < 0)
+			return err;
+		if (outs)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &vx_pcm_playback_ops);
+		if (ins)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &vx_pcm_capture_ops);
+
+		pcm->private_data = chip;
+		pcm->private_free = snd_vx_pcm_free;
+		pcm->info_flags = 0;
+		strcpy(pcm->name, chip->card->shortname);
+		chip->pcm[i] = pcm;
+	}
+
+	return 0;
+}
diff --git a/linux/sound/drivers/vx/vx_uer.c b/linux/sound/drivers/vx/vx_uer.c
new file mode 100644
index 0000000..b0560fe
--- /dev/null
+++ b/linux/sound/drivers/vx/vx_uer.c
@@ -0,0 +1,312 @@
+/*
+ * Driver for Digigram VX soundcards
+ *
+ * IEC958 stuff
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/vx_core.h>
+#include "vx_cmd.h"
+
+
+/*
+ * vx_modify_board_clock - tell the board that its clock has been modified
+ * @sync: DSP needs to resynchronize its FIFO
+ */
+static int vx_modify_board_clock(struct vx_core *chip, int sync)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_MODIFY_CLOCK);
+	/* Ask the DSP to resynchronize its FIFO. */
+	if (sync)
+		rmh.Cmd[0] |= CMD_MODIFY_CLOCK_S_BIT;
+	return vx_send_msg(chip, &rmh);
+}
+
+/*
+ * vx_modify_board_inputs - resync audio inputs
+ */
+static int vx_modify_board_inputs(struct vx_core *chip)
+{
+	struct vx_rmh rmh;
+
+	vx_init_rmh(&rmh, CMD_RESYNC_AUDIO_INPUTS);
+        rmh.Cmd[0] |= 1 << 0; /* reference: AUDIO 0 */
+	return vx_send_msg(chip, &rmh);
+}
+
+/*
+ * vx_read_one_cbit - read one bit from UER config
+ * @index: the bit index
+ * returns 0 or 1.
+ */
+static int vx_read_one_cbit(struct vx_core *chip, int index)
+{
+	unsigned long flags;
+	int val;
+	spin_lock_irqsave(&chip->lock, flags);
+	if (chip->type >= VX_TYPE_VXPOCKET) {
+		vx_outb(chip, CSUER, 1); /* read */
+		vx_outb(chip, RUER, index & XX_UER_CBITS_OFFSET_MASK);
+		val = (vx_inb(chip, RUER) >> 7) & 0x01;
+	} else {
+		vx_outl(chip, CSUER, 1); /* read */
+		vx_outl(chip, RUER, index & XX_UER_CBITS_OFFSET_MASK);
+		val = (vx_inl(chip, RUER) >> 7) & 0x01;
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return val;
+}
+
+/*
+ * vx_write_one_cbit - write one bit to UER config
+ * @index: the bit index
+ * @val: bit value, 0 or 1
+ */
+static void vx_write_one_cbit(struct vx_core *chip, int index, int val)
+{
+	unsigned long flags;
+	val = !!val;	/* 0 or 1 */
+	spin_lock_irqsave(&chip->lock, flags);
+	if (vx_is_pcmcia(chip)) {
+		vx_outb(chip, CSUER, 0); /* write */
+		vx_outb(chip, RUER, (val << 7) | (index & XX_UER_CBITS_OFFSET_MASK));
+	} else {
+		vx_outl(chip, CSUER, 0); /* write */
+		vx_outl(chip, RUER, (val << 7) | (index & XX_UER_CBITS_OFFSET_MASK));
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+/*
+ * vx_read_uer_status - read the current UER status
+ * @mode: pointer to store the UER mode, VX_UER_MODE_XXX
+ *
+ * returns the frequency of UER, or 0 if not sync,
+ * or a negative error code.
+ */
+static int vx_read_uer_status(struct vx_core *chip, unsigned int *mode)
+{
+	int val, freq;
+
+	/* Default values */
+	freq = 0;
+
+	/* Read UER status */
+	if (vx_is_pcmcia(chip))
+	    val = vx_inb(chip, CSUER);
+	else
+	    val = vx_inl(chip, CSUER);
+	if (val < 0)
+		return val;
+	/* If clock is present, read frequency */
+	if (val & VX_SUER_CLOCK_PRESENT_MASK) {
+		switch (val & VX_SUER_FREQ_MASK) {
+		case VX_SUER_FREQ_32KHz_MASK:
+			freq = 32000;
+			break;
+		case VX_SUER_FREQ_44KHz_MASK:
+			freq = 44100;
+			break;
+		case VX_SUER_FREQ_48KHz_MASK:
+			freq = 48000;
+			break;
+		}
+        }
+	if (val & VX_SUER_DATA_PRESENT_MASK)
+		/* bit 0 corresponds to consumer/professional bit */
+		*mode = vx_read_one_cbit(chip, 0) ?
+			VX_UER_MODE_PROFESSIONAL : VX_UER_MODE_CONSUMER;
+	else
+		*mode = VX_UER_MODE_NOT_PRESENT;
+
+	return freq;
+}
+
+
+/*
+ * compute the sample clock value from frequency
+ *
+ * The formula is as follows:
+ *
+ *    HexFreq = (dword) ((double) ((double) 28224000 / (double) Frequency))
+ *    switch ( HexFreq & 0x00000F00 )
+ *    case 0x00000100: ;
+ *    case 0x00000200:
+ *    case 0x00000300: HexFreq -= 0x00000201 ;
+ *    case 0x00000400:
+ *    case 0x00000500:
+ *    case 0x00000600:
+ *    case 0x00000700: HexFreq = (dword) (((double) 28224000 / (double) (Frequency*2)) - 1)
+ *    default        : HexFreq = (dword) ((double) 28224000 / (double) (Frequency*4)) - 0x000001FF
+ */
+
+static int vx_calc_clock_from_freq(struct vx_core *chip, int freq)
+{
+	int hexfreq;
+
+	if (snd_BUG_ON(freq <= 0))
+		return 0;
+
+	hexfreq = (28224000 * 10) / freq;
+	hexfreq = (hexfreq + 5) / 10;
+
+	/* max freq = 55125 Hz */
+	if (snd_BUG_ON(hexfreq <= 0x00000200))
+		return 0;
+
+	if (hexfreq <= 0x03ff)
+		return hexfreq - 0x00000201;
+	if (hexfreq <= 0x07ff) 
+		return (hexfreq / 2) - 1;
+	if (hexfreq <= 0x0fff)
+		return (hexfreq / 4) + 0x000001ff;
+
+	return 0x5fe; 	/* min freq = 6893 Hz */
+}
+
+
+/*
+ * vx_change_clock_source - change the clock source
+ * @source: the new source
+ */
+static void vx_change_clock_source(struct vx_core *chip, int source)
+{
+	unsigned long flags;
+
+	/* we mute DAC to prevent clicks */
+	vx_toggle_dac_mute(chip, 1);
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->ops->set_clock_source(chip, source);
+	chip->clock_source = source;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	/* unmute */
+	vx_toggle_dac_mute(chip, 0);
+}
+
+
+/*
+ * set the internal clock
+ */
+void vx_set_internal_clock(struct vx_core *chip, unsigned int freq)
+{
+	int clock;
+	unsigned long flags;
+	/* Get real clock value */
+	clock = vx_calc_clock_from_freq(chip, freq);
+	snd_printdd(KERN_DEBUG "set internal clock to 0x%x from freq %d\n", clock, freq);
+	spin_lock_irqsave(&chip->lock, flags);
+	if (vx_is_pcmcia(chip)) {
+		vx_outb(chip, HIFREQ, (clock >> 8) & 0x0f);
+		vx_outb(chip, LOFREQ, clock & 0xff);
+	} else {
+		vx_outl(chip, HIFREQ, (clock >> 8) & 0x0f);
+		vx_outl(chip, LOFREQ, clock & 0xff);
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+/*
+ * set the iec958 status bits
+ * @bits: 32-bit status bits
+ */
+void vx_set_iec958_status(struct vx_core *chip, unsigned int bits)
+{
+	int i;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return;
+
+	for (i = 0; i < 32; i++)
+		vx_write_one_cbit(chip, i, bits & (1 << i));
+}
+
+
+/*
+ * vx_set_clock - change the clock and audio source if necessary
+ */
+int vx_set_clock(struct vx_core *chip, unsigned int freq)
+{
+	int src_changed = 0;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return 0;
+
+	/* change the audio source if possible */
+	vx_sync_audio_source(chip);
+
+	if (chip->clock_mode == VX_CLOCK_MODE_EXTERNAL ||
+	    (chip->clock_mode == VX_CLOCK_MODE_AUTO &&
+	     chip->audio_source == VX_AUDIO_SRC_DIGITAL)) {
+		if (chip->clock_source != UER_SYNC) {
+			vx_change_clock_source(chip, UER_SYNC);
+			mdelay(6);
+			src_changed = 1;
+		}
+	} else if (chip->clock_mode == VX_CLOCK_MODE_INTERNAL ||
+		   (chip->clock_mode == VX_CLOCK_MODE_AUTO &&
+		    chip->audio_source != VX_AUDIO_SRC_DIGITAL)) {
+		if (chip->clock_source != INTERNAL_QUARTZ) {
+			vx_change_clock_source(chip, INTERNAL_QUARTZ);
+			src_changed = 1;
+		}
+		if (chip->freq == freq)
+			return 0;
+		vx_set_internal_clock(chip, freq);
+		if (src_changed)
+			vx_modify_board_inputs(chip);
+	}
+	if (chip->freq == freq)
+		return 0;
+	chip->freq = freq;
+	vx_modify_board_clock(chip, 1);
+	return 0;
+}
+
+
+/*
+ * vx_change_frequency - called from interrupt handler
+ */
+int vx_change_frequency(struct vx_core *chip)
+{
+	int freq;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return 0;
+
+	if (chip->clock_source == INTERNAL_QUARTZ)
+		return 0;
+	/*
+	 * Read the real UER board frequency
+	 */
+	freq = vx_read_uer_status(chip, &chip->uer_detected);
+	if (freq < 0)
+		return freq;
+	/*
+	 * The frequency computed by the DSP is good and
+	 * is different from the previous computed.
+	 */
+	if (freq == 48000 || freq == 44100 || freq == 32000)
+		chip->freq_detected = freq;
+
+	return 0;
+}
diff --git a/linux/sound/i2c/Makefile b/linux/sound/i2c/Makefile
new file mode 100644
index 0000000..36879bf
--- /dev/null
+++ b/linux/sound/i2c/Makefile
@@ -0,0 +1,15 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-i2c-objs := i2c.o
+snd-cs8427-objs := cs8427.o
+snd-tea6330t-objs := tea6330t.o
+
+obj-$(CONFIG_SND) += other/
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_INTERWAVE_STB) += snd-tea6330t.o snd-i2c.o
+obj-$(CONFIG_SND_ICE1712) += snd-cs8427.o snd-i2c.o
+obj-$(CONFIG_SND_ICE1724) += snd-i2c.o
diff --git a/linux/sound/i2c/cs8427.c b/linux/sound/i2c/cs8427.c
new file mode 100644
index 0000000..04ae870
--- /dev/null
+++ b/linux/sound/i2c/cs8427.c
@@ -0,0 +1,615 @@
+/*
+ *  Routines for control of the CS8427 via i2c bus
+ *  IEC958 (S/PDIF) receiver & transmitter by Cirrus Logic
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/bitrev.h>
+#include <asm/unaligned.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/cs8427.h>
+#include <sound/asoundef.h>
+
+static void snd_cs8427_reset(struct snd_i2c_device *cs8427);
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("IEC958 (S/PDIF) receiver & transmitter by Cirrus Logic");
+MODULE_LICENSE("GPL");
+
+#define CS8427_ADDR			(0x20>>1) /* fixed address */
+
+struct cs8427_stream {
+	struct snd_pcm_substream *substream;
+	char hw_status[24];		/* hardware status */
+	char def_status[24];		/* default status */
+	char pcm_status[24];		/* PCM private status */
+	char hw_udata[32];
+	struct snd_kcontrol *pcm_ctl;
+};
+
+struct cs8427 {
+	unsigned char regmap[0x14];	/* map of first 1 + 13 registers */
+	unsigned int rate;
+	unsigned int reset_timeout;
+	struct cs8427_stream playback;
+	struct cs8427_stream capture;
+};
+
+int snd_cs8427_reg_write(struct snd_i2c_device *device, unsigned char reg,
+			 unsigned char val)
+{
+	int err;
+	unsigned char buf[2];
+
+	buf[0] = reg & 0x7f;
+	buf[1] = val;
+	if ((err = snd_i2c_sendbytes(device, buf, 2)) != 2) {
+		snd_printk(KERN_ERR "unable to send bytes 0x%02x:0x%02x "
+			   "to CS8427 (%i)\n", buf[0], buf[1], err);
+		return err < 0 ? err : -EIO;
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_cs8427_reg_write);
+
+static int snd_cs8427_reg_read(struct snd_i2c_device *device, unsigned char reg)
+{
+	int err;
+	unsigned char buf;
+
+	if ((err = snd_i2c_sendbytes(device, &reg, 1)) != 1) {
+		snd_printk(KERN_ERR "unable to send register 0x%x byte "
+			   "to CS8427\n", reg);
+		return err < 0 ? err : -EIO;
+	}
+	if ((err = snd_i2c_readbytes(device, &buf, 1)) != 1) {
+		snd_printk(KERN_ERR "unable to read register 0x%x byte "
+			   "from CS8427\n", reg);
+		return err < 0 ? err : -EIO;
+	}
+	return buf;
+}
+
+static int snd_cs8427_select_corudata(struct snd_i2c_device *device, int udata)
+{
+	struct cs8427 *chip = device->private_data;
+	int err;
+
+	udata = udata ? CS8427_BSEL : 0;
+	if (udata != (chip->regmap[CS8427_REG_CSDATABUF] & udata)) {
+		chip->regmap[CS8427_REG_CSDATABUF] &= ~CS8427_BSEL;
+		chip->regmap[CS8427_REG_CSDATABUF] |= udata;
+		err = snd_cs8427_reg_write(device, CS8427_REG_CSDATABUF,
+					   chip->regmap[CS8427_REG_CSDATABUF]);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_cs8427_send_corudata(struct snd_i2c_device *device,
+				    int udata,
+				    unsigned char *ndata,
+				    int count)
+{
+	struct cs8427 *chip = device->private_data;
+	char *hw_data = udata ?
+		chip->playback.hw_udata : chip->playback.hw_status;
+	char data[32];
+	int err, idx;
+
+	if (!memcmp(hw_data, ndata, count))
+		return 0;
+	if ((err = snd_cs8427_select_corudata(device, udata)) < 0)
+		return err;
+	memcpy(hw_data, ndata, count);
+	if (udata) {
+		memset(data, 0, sizeof(data));
+		if (memcmp(hw_data, data, count) == 0) {
+			chip->regmap[CS8427_REG_UDATABUF] &= ~CS8427_UBMMASK;
+			chip->regmap[CS8427_REG_UDATABUF] |= CS8427_UBMZEROS |
+				CS8427_EFTUI;
+			err = snd_cs8427_reg_write(device, CS8427_REG_UDATABUF,
+						   chip->regmap[CS8427_REG_UDATABUF]);
+			return err < 0 ? err : 0;
+		}
+	}
+	data[0] = CS8427_REG_AUTOINC | CS8427_REG_CORU_DATABUF;
+	for (idx = 0; idx < count; idx++)
+		data[idx + 1] = bitrev8(ndata[idx]);
+	if (snd_i2c_sendbytes(device, data, count + 1) != count + 1)
+		return -EIO;
+	return 1;
+}
+
+static void snd_cs8427_free(struct snd_i2c_device *device)
+{
+	kfree(device->private_data);
+}
+
+int snd_cs8427_create(struct snd_i2c_bus *bus,
+		      unsigned char addr,
+		      unsigned int reset_timeout,
+		      struct snd_i2c_device **r_cs8427)
+{
+	static unsigned char initvals1[] = {
+	  CS8427_REG_CONTROL1 | CS8427_REG_AUTOINC,
+	  /* CS8427_REG_CONTROL1: RMCK to OMCK, valid PCM audio, disable mutes,
+	     TCBL=output */
+	  CS8427_SWCLK | CS8427_TCBLDIR,
+	  /* CS8427_REG_CONTROL2: hold last valid audio sample, RMCK=256*Fs,
+	     normal stereo operation */
+	  0x00,
+	  /* CS8427_REG_DATAFLOW: output drivers normal operation, Tx<=serial,
+	     Rx=>serial */
+	  CS8427_TXDSERIAL | CS8427_SPDAES3RECEIVER,
+	  /* CS8427_REG_CLOCKSOURCE: Run off, CMCK=256*Fs,
+	     output time base = OMCK, input time base = recovered input clock,
+	     recovered input clock source is ILRCK changed to AES3INPUT
+	     (workaround, see snd_cs8427_reset) */
+	  CS8427_RXDILRCK,
+	  /* CS8427_REG_SERIALINPUT: Serial audio input port data format = I2S,
+	     24-bit, 64*Fsi */
+	  CS8427_SIDEL | CS8427_SILRPOL,
+	  /* CS8427_REG_SERIALOUTPUT: Serial audio output port data format
+	     = I2S, 24-bit, 64*Fsi */
+	  CS8427_SODEL | CS8427_SOLRPOL,
+	};
+	static unsigned char initvals2[] = {
+	  CS8427_REG_RECVERRMASK | CS8427_REG_AUTOINC,
+	  /* CS8427_REG_RECVERRMASK: unmask the input PLL clock, V, confidence,
+	     biphase, parity status bits */
+	  /* CS8427_UNLOCK | CS8427_V | CS8427_CONF | CS8427_BIP | CS8427_PAR,*/
+	  0xff, /* set everything */
+	  /* CS8427_REG_CSDATABUF:
+	     Registers 32-55 window to CS buffer
+	     Inhibit D->E transfers from overwriting first 5 bytes of CS data.
+	     Inhibit D->E transfers (all) of CS data.
+	     Allow E->F transfer of CS data.
+	     One byte mode; both A/B channels get same written CB data.
+	     A channel info is output to chip's EMPH* pin. */
+	  CS8427_CBMR | CS8427_DETCI,
+	  /* CS8427_REG_UDATABUF:
+	     Use internal buffer to transmit User (U) data.
+	     Chip's U pin is an output.
+	     Transmit all O's for user data.
+	     Inhibit D->E transfers.
+	     Inhibit E->F transfers. */
+	  CS8427_UD | CS8427_EFTUI | CS8427_DETUI,
+	};
+	int err;
+	struct cs8427 *chip;
+	struct snd_i2c_device *device;
+	unsigned char buf[24];
+
+	if ((err = snd_i2c_device_create(bus, "CS8427",
+					 CS8427_ADDR | (addr & 7),
+					 &device)) < 0)
+		return err;
+	chip = device->private_data = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+	      	snd_i2c_device_free(device);
+		return -ENOMEM;
+	}
+	device->private_free = snd_cs8427_free;
+	
+	snd_i2c_lock(bus);
+	err = snd_cs8427_reg_read(device, CS8427_REG_ID_AND_VER);
+	if (err != CS8427_VER8427A) {
+		/* give second chance */
+		snd_printk(KERN_WARNING "invalid CS8427 signature 0x%x: "
+			   "let me try again...\n", err);
+		err = snd_cs8427_reg_read(device, CS8427_REG_ID_AND_VER);
+	}
+	if (err != CS8427_VER8427A) {
+		snd_i2c_unlock(bus);
+		snd_printk(KERN_ERR "unable to find CS8427 signature "
+			   "(expected 0x%x, read 0x%x),\n",
+			   CS8427_VER8427A, err);
+		snd_printk(KERN_ERR "   initialization is not completed\n");
+		return -EFAULT;
+	}
+	/* turn off run bit while making changes to configuration */
+	err = snd_cs8427_reg_write(device, CS8427_REG_CLOCKSOURCE, 0x00);
+	if (err < 0)
+		goto __fail;
+	/* send initial values */
+	memcpy(chip->regmap + (initvals1[0] & 0x7f), initvals1 + 1, 6);
+	if ((err = snd_i2c_sendbytes(device, initvals1, 7)) != 7) {
+		err = err < 0 ? err : -EIO;
+		goto __fail;
+	}
+	/* Turn off CS8427 interrupt stuff that is not used in hardware */
+	memset(buf, 0, 7);
+	/* from address 9 to 15 */
+	buf[0] = 9;	/* register */
+	if ((err = snd_i2c_sendbytes(device, buf, 7)) != 7)
+		goto __fail;
+	/* send transfer initialization sequence */
+	memcpy(chip->regmap + (initvals2[0] & 0x7f), initvals2 + 1, 3);
+	if ((err = snd_i2c_sendbytes(device, initvals2, 4)) != 4) {
+		err = err < 0 ? err : -EIO;
+		goto __fail;
+	}
+	/* write default channel status bytes */
+	put_unaligned_le32(SNDRV_PCM_DEFAULT_CON_SPDIF, buf);
+	memset(buf + 4, 0, 24 - 4);
+	if (snd_cs8427_send_corudata(device, 0, buf, 24) < 0)
+		goto __fail;
+	memcpy(chip->playback.def_status, buf, 24);
+	memcpy(chip->playback.pcm_status, buf, 24);
+	snd_i2c_unlock(bus);
+
+	/* turn on run bit and rock'n'roll */
+	if (reset_timeout < 1)
+		reset_timeout = 1;
+	chip->reset_timeout = reset_timeout;
+	snd_cs8427_reset(device);
+
+#if 0	// it's nice for read tests
+	{
+	char buf[128];
+	int xx;
+	buf[0] = 0x81;
+	snd_i2c_sendbytes(device, buf, 1);
+	snd_i2c_readbytes(device, buf, 127);
+	for (xx = 0; xx < 127; xx++)
+		printk(KERN_DEBUG "reg[0x%x] = 0x%x\n", xx+1, buf[xx]);
+	}
+#endif
+	
+	if (r_cs8427)
+		*r_cs8427 = device;
+	return 0;
+
+      __fail:
+      	snd_i2c_unlock(bus);
+      	snd_i2c_device_free(device);
+      	return err < 0 ? err : -EIO;
+}
+
+EXPORT_SYMBOL(snd_cs8427_create);
+
+/*
+ * Reset the chip using run bit, also lock PLL using ILRCK and
+ * put back AES3INPUT. This workaround is described in latest
+ * CS8427 datasheet, otherwise TXDSERIAL will not work.
+ */
+static void snd_cs8427_reset(struct snd_i2c_device *cs8427)
+{
+	struct cs8427 *chip;
+	unsigned long end_time;
+	int data, aes3input = 0;
+
+	if (snd_BUG_ON(!cs8427))
+		return;
+	chip = cs8427->private_data;
+	snd_i2c_lock(cs8427->bus);
+	if ((chip->regmap[CS8427_REG_CLOCKSOURCE] & CS8427_RXDAES3INPUT) ==
+	    CS8427_RXDAES3INPUT)  /* AES3 bit is set */
+		aes3input = 1;
+	chip->regmap[CS8427_REG_CLOCKSOURCE] &= ~(CS8427_RUN | CS8427_RXDMASK);
+	snd_cs8427_reg_write(cs8427, CS8427_REG_CLOCKSOURCE,
+			     chip->regmap[CS8427_REG_CLOCKSOURCE]);
+	udelay(200);
+	chip->regmap[CS8427_REG_CLOCKSOURCE] |= CS8427_RUN | CS8427_RXDILRCK;
+	snd_cs8427_reg_write(cs8427, CS8427_REG_CLOCKSOURCE,
+			     chip->regmap[CS8427_REG_CLOCKSOURCE]);
+	udelay(200);
+	snd_i2c_unlock(cs8427->bus);
+	end_time = jiffies + chip->reset_timeout;
+	while (time_after_eq(end_time, jiffies)) {
+		snd_i2c_lock(cs8427->bus);
+		data = snd_cs8427_reg_read(cs8427, CS8427_REG_RECVERRORS);
+		snd_i2c_unlock(cs8427->bus);
+		if (!(data & CS8427_UNLOCK))
+			break;
+		schedule_timeout_uninterruptible(1);
+	}
+	snd_i2c_lock(cs8427->bus);
+	chip->regmap[CS8427_REG_CLOCKSOURCE] &= ~CS8427_RXDMASK;
+	if (aes3input)
+		chip->regmap[CS8427_REG_CLOCKSOURCE] |= CS8427_RXDAES3INPUT;
+	snd_cs8427_reg_write(cs8427, CS8427_REG_CLOCKSOURCE,
+			     chip->regmap[CS8427_REG_CLOCKSOURCE]);
+	snd_i2c_unlock(cs8427->bus);
+}
+
+static int snd_cs8427_in_status_info(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	return 0;
+}
+
+static int snd_cs8427_in_status_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_i2c_device *device = snd_kcontrol_chip(kcontrol);
+	int data;
+
+	snd_i2c_lock(device->bus);
+	data = snd_cs8427_reg_read(device, kcontrol->private_value);
+	snd_i2c_unlock(device->bus);
+	if (data < 0)
+		return data;
+	ucontrol->value.integer.value[0] = data;
+	return 0;
+}
+
+static int snd_cs8427_qsubcode_info(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+	uinfo->count = 10;
+	return 0;
+}
+
+static int snd_cs8427_qsubcode_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_i2c_device *device = snd_kcontrol_chip(kcontrol);
+	unsigned char reg = CS8427_REG_QSUBCODE;
+	int err;
+
+	snd_i2c_lock(device->bus);
+	if ((err = snd_i2c_sendbytes(device, &reg, 1)) != 1) {
+		snd_printk(KERN_ERR "unable to send register 0x%x byte "
+			   "to CS8427\n", reg);
+		snd_i2c_unlock(device->bus);
+		return err < 0 ? err : -EIO;
+	}
+	err = snd_i2c_readbytes(device, ucontrol->value.bytes.data, 10);
+	if (err != 10) {
+		snd_printk(KERN_ERR "unable to read Q-subcode bytes "
+			   "from CS8427\n");
+		snd_i2c_unlock(device->bus);
+		return err < 0 ? err : -EIO;
+	}
+	snd_i2c_unlock(device->bus);
+	return 0;
+}
+
+static int snd_cs8427_spdif_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_cs8427_spdif_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_i2c_device *device = snd_kcontrol_chip(kcontrol);
+	struct cs8427 *chip = device->private_data;
+	
+	snd_i2c_lock(device->bus);
+	memcpy(ucontrol->value.iec958.status, chip->playback.def_status, 24);
+	snd_i2c_unlock(device->bus);
+	return 0;
+}
+
+static int snd_cs8427_spdif_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_i2c_device *device = snd_kcontrol_chip(kcontrol);
+	struct cs8427 *chip = device->private_data;
+	unsigned char *status = kcontrol->private_value ?
+		chip->playback.pcm_status : chip->playback.def_status;
+	struct snd_pcm_runtime *runtime = chip->playback.substream ?
+		chip->playback.substream->runtime : NULL;
+	int err, change;
+
+	snd_i2c_lock(device->bus);
+	change = memcmp(ucontrol->value.iec958.status, status, 24) != 0;
+	memcpy(status, ucontrol->value.iec958.status, 24);
+	if (change && (kcontrol->private_value ?
+		       runtime != NULL : runtime == NULL)) {
+		err = snd_cs8427_send_corudata(device, 0, status, 24);
+		if (err < 0)
+			change = err;
+	}
+	snd_i2c_unlock(device->bus);
+	return change;
+}
+
+static int snd_cs8427_spdif_mask_info(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_cs8427_spdif_mask_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	memset(ucontrol->value.iec958.status, 0xff, 24);
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_cs8427_iec958_controls[] = {
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.info =		snd_cs8427_in_status_info,
+	.name =		"IEC958 CS8427 Input Status",
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READ |
+			 SNDRV_CTL_ELEM_ACCESS_VOLATILE),
+	.get =		snd_cs8427_in_status_get,
+	.private_value = 15,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.info =		snd_cs8427_in_status_info,
+	.name =		"IEC958 CS8427 Error Status",
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READ |
+			 SNDRV_CTL_ELEM_ACCESS_VOLATILE),
+	.get =		snd_cs8427_in_status_get,
+	.private_value = 16,
+},
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+	.info =		snd_cs8427_spdif_mask_info,
+	.get =		snd_cs8427_spdif_mask_get,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =		snd_cs8427_spdif_info,
+	.get =		snd_cs8427_spdif_get,
+	.put =		snd_cs8427_spdif_put,
+	.private_value = 0
+},
+{
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_INACTIVE),
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+	.info =		snd_cs8427_spdif_info,
+	.get =		snd_cs8427_spdif_get,
+	.put =		snd_cs8427_spdif_put,
+	.private_value = 1
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.info =		snd_cs8427_qsubcode_info,
+	.name =		"IEC958 Q-subcode Capture Default",
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READ |
+			 SNDRV_CTL_ELEM_ACCESS_VOLATILE),
+	.get =		snd_cs8427_qsubcode_get
+}};
+
+int snd_cs8427_iec958_build(struct snd_i2c_device *cs8427,
+			    struct snd_pcm_substream *play_substream,
+			    struct snd_pcm_substream *cap_substream)
+{
+	struct cs8427 *chip = cs8427->private_data;
+	struct snd_kcontrol *kctl;
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!play_substream || !cap_substream))
+		return -EINVAL;
+	for (idx = 0; idx < ARRAY_SIZE(snd_cs8427_iec958_controls); idx++) {
+		kctl = snd_ctl_new1(&snd_cs8427_iec958_controls[idx], cs8427);
+		if (kctl == NULL)
+			return -ENOMEM;
+		kctl->id.device = play_substream->pcm->device;
+		kctl->id.subdevice = play_substream->number;
+		err = snd_ctl_add(cs8427->bus->card, kctl);
+		if (err < 0)
+			return err;
+		if (! strcmp(kctl->id.name,
+			     SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM)))
+			chip->playback.pcm_ctl = kctl;
+	}
+
+	chip->playback.substream = play_substream;
+	chip->capture.substream = cap_substream;
+	if (snd_BUG_ON(!chip->playback.pcm_ctl))
+		return -EIO;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_cs8427_iec958_build);
+
+int snd_cs8427_iec958_active(struct snd_i2c_device *cs8427, int active)
+{
+	struct cs8427 *chip;
+
+	if (snd_BUG_ON(!cs8427))
+		return -ENXIO;
+	chip = cs8427->private_data;
+	if (active)
+		memcpy(chip->playback.pcm_status,
+		       chip->playback.def_status, 24);
+	chip->playback.pcm_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(cs8427->bus->card,
+		       SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO,
+		       &chip->playback.pcm_ctl->id);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_cs8427_iec958_active);
+
+int snd_cs8427_iec958_pcm(struct snd_i2c_device *cs8427, unsigned int rate)
+{
+	struct cs8427 *chip;
+	char *status;
+	int err, reset;
+
+	if (snd_BUG_ON(!cs8427))
+		return -ENXIO;
+	chip = cs8427->private_data;
+	status = chip->playback.pcm_status;
+	snd_i2c_lock(cs8427->bus);
+	if (status[0] & IEC958_AES0_PROFESSIONAL) {
+		status[0] &= ~IEC958_AES0_PRO_FS;
+		switch (rate) {
+		case 32000: status[0] |= IEC958_AES0_PRO_FS_32000; break;
+		case 44100: status[0] |= IEC958_AES0_PRO_FS_44100; break;
+		case 48000: status[0] |= IEC958_AES0_PRO_FS_48000; break;
+		default: status[0] |= IEC958_AES0_PRO_FS_NOTID; break;
+		}
+	} else {
+		status[3] &= ~IEC958_AES3_CON_FS;
+		switch (rate) {
+		case 32000: status[3] |= IEC958_AES3_CON_FS_32000; break;
+		case 44100: status[3] |= IEC958_AES3_CON_FS_44100; break;
+		case 48000: status[3] |= IEC958_AES3_CON_FS_48000; break;
+		}
+	}
+	err = snd_cs8427_send_corudata(cs8427, 0, status, 24);
+	if (err > 0)
+		snd_ctl_notify(cs8427->bus->card,
+			       SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->playback.pcm_ctl->id);
+	reset = chip->rate != rate;
+	chip->rate = rate;
+	snd_i2c_unlock(cs8427->bus);
+	if (reset)
+		snd_cs8427_reset(cs8427);
+	return err < 0 ? err : 0;
+}
+
+EXPORT_SYMBOL(snd_cs8427_iec958_pcm);
+
+static int __init alsa_cs8427_module_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_cs8427_module_exit(void)
+{
+}
+
+module_init(alsa_cs8427_module_init)
+module_exit(alsa_cs8427_module_exit)
diff --git a/linux/sound/i2c/i2c.c b/linux/sound/i2c/i2c.c
new file mode 100644
index 0000000..eb7c7d0
--- /dev/null
+++ b/linux/sound/i2c/i2c.c
@@ -0,0 +1,352 @@
+/*
+ *   Generic i2c interface for ALSA
+ *
+ *   (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de>
+ *   Modified for the ALSA driver by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <sound/core.h>
+#include <sound/i2c.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Generic i2c interface for ALSA");
+MODULE_LICENSE("GPL");
+
+static int snd_i2c_bit_sendbytes(struct snd_i2c_device *device,
+				 unsigned char *bytes, int count);
+static int snd_i2c_bit_readbytes(struct snd_i2c_device *device,
+				 unsigned char *bytes, int count);
+static int snd_i2c_bit_probeaddr(struct snd_i2c_bus *bus,
+				 unsigned short addr);
+
+static struct snd_i2c_ops snd_i2c_bit_ops = {
+	.sendbytes = snd_i2c_bit_sendbytes,
+	.readbytes = snd_i2c_bit_readbytes,
+	.probeaddr = snd_i2c_bit_probeaddr,
+};
+
+static int snd_i2c_bus_free(struct snd_i2c_bus *bus)
+{
+	struct snd_i2c_bus *slave;
+	struct snd_i2c_device *device;
+
+	if (snd_BUG_ON(!bus))
+		return -EINVAL;
+	while (!list_empty(&bus->devices)) {
+		device = snd_i2c_device(bus->devices.next);
+		snd_i2c_device_free(device);
+	}
+	if (bus->master)
+		list_del(&bus->buses);
+	else {
+		while (!list_empty(&bus->buses)) {
+			slave = snd_i2c_slave_bus(bus->buses.next);
+			snd_device_free(bus->card, slave);
+		}
+	}
+	if (bus->private_free)
+		bus->private_free(bus);
+	kfree(bus);
+	return 0;
+}
+
+static int snd_i2c_bus_dev_free(struct snd_device *device)
+{
+	struct snd_i2c_bus *bus = device->device_data;
+	return snd_i2c_bus_free(bus);
+}
+
+int snd_i2c_bus_create(struct snd_card *card, const char *name,
+		       struct snd_i2c_bus *master, struct snd_i2c_bus **ri2c)
+{
+	struct snd_i2c_bus *bus;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_i2c_bus_dev_free,
+	};
+
+	*ri2c = NULL;
+	bus = kzalloc(sizeof(*bus), GFP_KERNEL);
+	if (bus == NULL)
+		return -ENOMEM;
+	mutex_init(&bus->lock_mutex);
+	INIT_LIST_HEAD(&bus->devices);
+	INIT_LIST_HEAD(&bus->buses);
+	bus->card = card;
+	bus->ops = &snd_i2c_bit_ops;
+	if (master) {
+		list_add_tail(&bus->buses, &master->buses);
+		bus->master = master;
+	}
+	strlcpy(bus->name, name, sizeof(bus->name));
+	err = snd_device_new(card, SNDRV_DEV_BUS, bus, &ops);
+	if (err < 0) {
+		snd_i2c_bus_free(bus);
+		return err;
+	}
+	*ri2c = bus;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_i2c_bus_create);
+
+int snd_i2c_device_create(struct snd_i2c_bus *bus, const char *name,
+			  unsigned char addr, struct snd_i2c_device **rdevice)
+{
+	struct snd_i2c_device *device;
+
+	*rdevice = NULL;
+	if (snd_BUG_ON(!bus))
+		return -EINVAL;
+	device = kzalloc(sizeof(*device), GFP_KERNEL);
+	if (device == NULL)
+		return -ENOMEM;
+	device->addr = addr;
+	strlcpy(device->name, name, sizeof(device->name));
+	list_add_tail(&device->list, &bus->devices);
+	device->bus = bus;
+	*rdevice = device;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_i2c_device_create);
+
+int snd_i2c_device_free(struct snd_i2c_device *device)
+{
+	if (device->bus)
+		list_del(&device->list);
+	if (device->private_free)
+		device->private_free(device);
+	kfree(device);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_i2c_device_free);
+
+int snd_i2c_sendbytes(struct snd_i2c_device *device, unsigned char *bytes, int count)
+{
+	return device->bus->ops->sendbytes(device, bytes, count);
+}
+
+EXPORT_SYMBOL(snd_i2c_sendbytes);
+
+int snd_i2c_readbytes(struct snd_i2c_device *device, unsigned char *bytes, int count)
+{
+	return device->bus->ops->readbytes(device, bytes, count);
+}
+
+EXPORT_SYMBOL(snd_i2c_readbytes);
+
+int snd_i2c_probeaddr(struct snd_i2c_bus *bus, unsigned short addr)
+{
+	return bus->ops->probeaddr(bus, addr);
+}
+
+EXPORT_SYMBOL(snd_i2c_probeaddr);
+
+/*
+ *  bit-operations
+ */
+
+static inline void snd_i2c_bit_hw_start(struct snd_i2c_bus *bus)
+{
+	if (bus->hw_ops.bit->start)
+		bus->hw_ops.bit->start(bus);
+}
+
+static inline void snd_i2c_bit_hw_stop(struct snd_i2c_bus *bus)
+{
+	if (bus->hw_ops.bit->stop)
+		bus->hw_ops.bit->stop(bus);
+}
+
+static void snd_i2c_bit_direction(struct snd_i2c_bus *bus, int clock, int data)
+{
+	if (bus->hw_ops.bit->direction)
+		bus->hw_ops.bit->direction(bus, clock, data);
+}
+
+static void snd_i2c_bit_set(struct snd_i2c_bus *bus, int clock, int data)
+{
+	bus->hw_ops.bit->setlines(bus, clock, data);
+}
+
+#if 0
+static int snd_i2c_bit_clock(struct snd_i2c_bus *bus)
+{
+	if (bus->hw_ops.bit->getclock)
+		return bus->hw_ops.bit->getclock(bus);
+	return -ENXIO;
+}
+#endif
+
+static int snd_i2c_bit_data(struct snd_i2c_bus *bus, int ack)
+{
+	return bus->hw_ops.bit->getdata(bus, ack);
+}
+
+static void snd_i2c_bit_start(struct snd_i2c_bus *bus)
+{
+	snd_i2c_bit_hw_start(bus);
+	snd_i2c_bit_direction(bus, 1, 1);	/* SCL - wr, SDA - wr */
+	snd_i2c_bit_set(bus, 1, 1);
+	snd_i2c_bit_set(bus, 1, 0);
+	snd_i2c_bit_set(bus, 0, 0);
+}
+
+static void snd_i2c_bit_stop(struct snd_i2c_bus *bus)
+{
+	snd_i2c_bit_set(bus, 0, 0);
+	snd_i2c_bit_set(bus, 1, 0);
+	snd_i2c_bit_set(bus, 1, 1);
+	snd_i2c_bit_hw_stop(bus);
+}
+
+static void snd_i2c_bit_send(struct snd_i2c_bus *bus, int data)
+{
+	snd_i2c_bit_set(bus, 0, data);
+	snd_i2c_bit_set(bus, 1, data);
+	snd_i2c_bit_set(bus, 0, data);
+}
+
+static int snd_i2c_bit_ack(struct snd_i2c_bus *bus)
+{
+	int ack;
+
+	snd_i2c_bit_set(bus, 0, 1);
+	snd_i2c_bit_set(bus, 1, 1);
+	snd_i2c_bit_direction(bus, 1, 0);	/* SCL - wr, SDA - rd */
+	ack = snd_i2c_bit_data(bus, 1);
+	snd_i2c_bit_direction(bus, 1, 1);	/* SCL - wr, SDA - wr */
+	snd_i2c_bit_set(bus, 0, 1);
+	return ack ? -EIO : 0;
+}
+
+static int snd_i2c_bit_sendbyte(struct snd_i2c_bus *bus, unsigned char data)
+{
+	int i, err;
+
+	for (i = 7; i >= 0; i--)
+		snd_i2c_bit_send(bus, !!(data & (1 << i)));
+	err = snd_i2c_bit_ack(bus);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_i2c_bit_readbyte(struct snd_i2c_bus *bus, int last)
+{
+	int i;
+	unsigned char data = 0;
+
+	snd_i2c_bit_set(bus, 0, 1);
+	snd_i2c_bit_direction(bus, 1, 0);	/* SCL - wr, SDA - rd */
+	for (i = 7; i >= 0; i--) {
+		snd_i2c_bit_set(bus, 1, 1);
+		if (snd_i2c_bit_data(bus, 0))
+			data |= (1 << i);
+		snd_i2c_bit_set(bus, 0, 1);
+	}
+	snd_i2c_bit_direction(bus, 1, 1);	/* SCL - wr, SDA - wr */
+	snd_i2c_bit_send(bus, !!last);
+	return data;
+}
+
+static int snd_i2c_bit_sendbytes(struct snd_i2c_device *device,
+				 unsigned char *bytes, int count)
+{
+	struct snd_i2c_bus *bus = device->bus;
+	int err, res = 0;
+
+	if (device->flags & SND_I2C_DEVICE_ADDRTEN)
+		return -EIO;		/* not yet implemented */
+	snd_i2c_bit_start(bus);
+	err = snd_i2c_bit_sendbyte(bus, device->addr << 1);
+	if (err < 0) {
+		snd_i2c_bit_hw_stop(bus);
+		return err;
+	}
+	while (count-- > 0) {
+		err = snd_i2c_bit_sendbyte(bus, *bytes++);
+		if (err < 0) {
+			snd_i2c_bit_hw_stop(bus);
+			return err;
+		}
+		res++;
+	}
+	snd_i2c_bit_stop(bus);
+	return res;
+}
+
+static int snd_i2c_bit_readbytes(struct snd_i2c_device *device,
+				 unsigned char *bytes, int count)
+{
+	struct snd_i2c_bus *bus = device->bus;
+	int err, res = 0;
+
+	if (device->flags & SND_I2C_DEVICE_ADDRTEN)
+		return -EIO;		/* not yet implemented */
+	snd_i2c_bit_start(bus);
+	err = snd_i2c_bit_sendbyte(bus, (device->addr << 1) | 1);
+	if (err < 0) {
+		snd_i2c_bit_hw_stop(bus);
+		return err;
+	}
+	while (count-- > 0) {
+		err = snd_i2c_bit_readbyte(bus, count == 0);
+		if (err < 0) {
+			snd_i2c_bit_hw_stop(bus);
+			return err;
+		}
+		*bytes++ = (unsigned char)err;
+		res++;
+	}
+	snd_i2c_bit_stop(bus);
+	return res;
+}
+
+static int snd_i2c_bit_probeaddr(struct snd_i2c_bus *bus, unsigned short addr)
+{
+	int err;
+
+	if (addr & 0x8000)	/* 10-bit address */
+		return -EIO;	/* not yet implemented */
+	if (addr & 0x7f80)	/* invalid address */
+		return -EINVAL;
+	snd_i2c_bit_start(bus);
+	err = snd_i2c_bit_sendbyte(bus, addr << 1);
+	snd_i2c_bit_stop(bus);
+	return err;
+}
+
+
+static int __init alsa_i2c_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_i2c_exit(void)
+{
+}
+
+module_init(alsa_i2c_init)
+module_exit(alsa_i2c_exit)
diff --git a/linux/sound/i2c/other/Makefile b/linux/sound/i2c/other/Makefile
new file mode 100644
index 0000000..2dad40f
--- /dev/null
+++ b/linux/sound/i2c/other/Makefile
@@ -0,0 +1,17 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ak4114-objs := ak4114.o
+snd-ak4117-objs := ak4117.o
+snd-ak4113-objs := ak4113.o
+snd-ak4xxx-adda-objs := ak4xxx-adda.o
+snd-pt2258-objs := pt2258.o
+snd-tea575x-tuner-objs := tea575x-tuner.o
+
+# Module Dependency
+obj-$(CONFIG_SND_PDAUDIOCF) += snd-ak4117.o
+obj-$(CONFIG_SND_ICE1712) += snd-ak4xxx-adda.o
+obj-$(CONFIG_SND_ICE1724) += snd-ak4114.o snd-ak4113.o snd-ak4xxx-adda.o snd-pt2258.o
+obj-$(CONFIG_SND_FM801_TEA575X) += snd-tea575x-tuner.o
diff --git a/linux/sound/i2c/other/ak4113.c b/linux/sound/i2c/other/ak4113.c
new file mode 100644
index 0000000..971a84a
--- /dev/null
+++ b/linux/sound/i2c/other/ak4113.c
@@ -0,0 +1,639 @@
+/*
+ *  Routines for control of the AK4113 via I2C/4-wire serial interface
+ *  IEC958 (S/PDIF) receiver by Asahi Kasei
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) by Pavel Hofman <pavel.hofman@ivitera.com>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/ak4113.h>
+#include <sound/asoundef.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("Pavel Hofman <pavel.hofman@ivitera.com>");
+MODULE_DESCRIPTION("AK4113 IEC958 (S/PDIF) receiver by Asahi Kasei");
+MODULE_LICENSE("GPL");
+
+#define AK4113_ADDR			0x00 /* fixed address */
+
+static void ak4113_stats(struct work_struct *work);
+static void ak4113_init_regs(struct ak4113 *chip);
+
+
+static void reg_write(struct ak4113 *ak4113, unsigned char reg,
+		unsigned char val)
+{
+	ak4113->write(ak4113->private_data, reg, val);
+	if (reg < sizeof(ak4113->regmap))
+		ak4113->regmap[reg] = val;
+}
+
+static inline unsigned char reg_read(struct ak4113 *ak4113, unsigned char reg)
+{
+	return ak4113->read(ak4113->private_data, reg);
+}
+
+static void snd_ak4113_free(struct ak4113 *chip)
+{
+	chip->init = 1;	/* don't schedule new work */
+	mb();
+	cancel_delayed_work(&chip->work);
+	flush_scheduled_work();
+	kfree(chip);
+}
+
+static int snd_ak4113_dev_free(struct snd_device *device)
+{
+	struct ak4113 *chip = device->device_data;
+	snd_ak4113_free(chip);
+	return 0;
+}
+
+int snd_ak4113_create(struct snd_card *card, ak4113_read_t *read,
+		ak4113_write_t *write, const unsigned char *pgm,
+		void *private_data, struct ak4113 **r_ak4113)
+{
+	struct ak4113 *chip;
+	int err = 0;
+	unsigned char reg;
+	static struct snd_device_ops ops = {
+		.dev_free =     snd_ak4113_dev_free,
+	};
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	spin_lock_init(&chip->lock);
+	chip->card = card;
+	chip->read = read;
+	chip->write = write;
+	chip->private_data = private_data;
+	INIT_DELAYED_WORK(&chip->work, ak4113_stats);
+
+	for (reg = 0; reg < AK4113_WRITABLE_REGS ; reg++)
+		chip->regmap[reg] = pgm[reg];
+	ak4113_init_regs(chip);
+
+	chip->rcs0 = reg_read(chip, AK4113_REG_RCS0) & ~(AK4113_QINT |
+			AK4113_CINT | AK4113_STC);
+	chip->rcs1 = reg_read(chip, AK4113_REG_RCS1);
+	chip->rcs2 = reg_read(chip, AK4113_REG_RCS2);
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0)
+		goto __fail;
+
+	if (r_ak4113)
+		*r_ak4113 = chip;
+	return 0;
+
+__fail:
+	snd_ak4113_free(chip);
+	return err < 0 ? err : -EIO;
+}
+EXPORT_SYMBOL_GPL(snd_ak4113_create);
+
+void snd_ak4113_reg_write(struct ak4113 *chip, unsigned char reg,
+		unsigned char mask, unsigned char val)
+{
+	if (reg >= AK4113_WRITABLE_REGS)
+		return;
+	reg_write(chip, reg, (chip->regmap[reg] & ~mask) | val);
+}
+EXPORT_SYMBOL_GPL(snd_ak4113_reg_write);
+
+static void ak4113_init_regs(struct ak4113 *chip)
+{
+	unsigned char old = chip->regmap[AK4113_REG_PWRDN], reg;
+
+	/* bring the chip to reset state and powerdown state */
+	reg_write(chip, AK4113_REG_PWRDN, old & ~(AK4113_RST|AK4113_PWN));
+	udelay(200);
+	/* release reset, but leave powerdown */
+	reg_write(chip, AK4113_REG_PWRDN, (old | AK4113_RST) & ~AK4113_PWN);
+	udelay(200);
+	for (reg = 1; reg < AK4113_WRITABLE_REGS; reg++)
+		reg_write(chip, reg, chip->regmap[reg]);
+	/* release powerdown, everything is initialized now */
+	reg_write(chip, AK4113_REG_PWRDN, old | AK4113_RST | AK4113_PWN);
+}
+
+void snd_ak4113_reinit(struct ak4113 *chip)
+{
+	chip->init = 1;
+	mb();
+	flush_scheduled_work();
+	ak4113_init_regs(chip);
+	/* bring up statistics / event queing */
+	chip->init = 0;
+	if (chip->kctls[0])
+		schedule_delayed_work(&chip->work, HZ / 10);
+}
+EXPORT_SYMBOL_GPL(snd_ak4113_reinit);
+
+static unsigned int external_rate(unsigned char rcs1)
+{
+	switch (rcs1 & (AK4113_FS0|AK4113_FS1|AK4113_FS2|AK4113_FS3)) {
+	case AK4113_FS_8000HZ:
+		return 8000;
+	case AK4113_FS_11025HZ:
+		return 11025;
+	case AK4113_FS_16000HZ:
+		return 16000;
+	case AK4113_FS_22050HZ:
+		return 22050;
+	case AK4113_FS_24000HZ:
+		return 24000;
+	case AK4113_FS_32000HZ:
+		return 32000;
+	case AK4113_FS_44100HZ:
+		return 44100;
+	case AK4113_FS_48000HZ:
+		return 48000;
+	case AK4113_FS_64000HZ:
+		return 64000;
+	case AK4113_FS_88200HZ:
+		return 88200;
+	case AK4113_FS_96000HZ:
+		return 96000;
+	case AK4113_FS_176400HZ:
+		return 176400;
+	case AK4113_FS_192000HZ:
+		return 192000;
+	default:
+		return 0;
+	}
+}
+
+static int snd_ak4113_in_error_info(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = LONG_MAX;
+	return 0;
+}
+
+static int snd_ak4113_in_error_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4113 *chip = snd_kcontrol_chip(kcontrol);
+	long *ptr;
+
+	spin_lock_irq(&chip->lock);
+	ptr = (long *)(((char *)chip) + kcontrol->private_value);
+	ucontrol->value.integer.value[0] = *ptr;
+	*ptr = 0;
+	spin_unlock_irq(&chip->lock);
+	return 0;
+}
+
+#define snd_ak4113_in_bit_info		snd_ctl_boolean_mono_info
+
+static int snd_ak4113_in_bit_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4113 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char reg = kcontrol->private_value & 0xff;
+	unsigned char bit = (kcontrol->private_value >> 8) & 0xff;
+	unsigned char inv = (kcontrol->private_value >> 31) & 1;
+
+	ucontrol->value.integer.value[0] =
+		((reg_read(chip, reg) & (1 << bit)) ? 1 : 0) ^ inv;
+	return 0;
+}
+
+static int snd_ak4113_rx_info(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 5;
+	return 0;
+}
+
+static int snd_ak4113_rx_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4113 *chip = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] =
+		(AK4113_IPS(chip->regmap[AK4113_REG_IO1]));
+	return 0;
+}
+
+static int snd_ak4113_rx_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4113 *chip = snd_kcontrol_chip(kcontrol);
+	int change;
+	u8 old_val;
+
+	spin_lock_irq(&chip->lock);
+	old_val = chip->regmap[AK4113_REG_IO1];
+	change = ucontrol->value.integer.value[0] != AK4113_IPS(old_val);
+	if (change)
+		reg_write(chip, AK4113_REG_IO1,
+				(old_val & (~AK4113_IPS(0xff))) |
+				(AK4113_IPS(ucontrol->value.integer.value[0])));
+	spin_unlock_irq(&chip->lock);
+	return change;
+}
+
+static int snd_ak4113_rate_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 192000;
+	return 0;
+}
+
+static int snd_ak4113_rate_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4113 *chip = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = external_rate(reg_read(chip,
+				AK4113_REG_RCS1));
+	return 0;
+}
+
+static int snd_ak4113_spdif_info(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_ak4113_spdif_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4113 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned i;
+
+	for (i = 0; i < AK4113_REG_RXCSB_SIZE; i++)
+		ucontrol->value.iec958.status[i] = reg_read(chip,
+				AK4113_REG_RXCSB0 + i);
+	return 0;
+}
+
+static int snd_ak4113_spdif_mask_info(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_ak4113_spdif_mask_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	memset(ucontrol->value.iec958.status, 0xff, AK4113_REG_RXCSB_SIZE);
+	return 0;
+}
+
+static int snd_ak4113_spdif_pinfo(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0xffff;
+	uinfo->count = 4;
+	return 0;
+}
+
+static int snd_ak4113_spdif_pget(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4113 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned short tmp;
+
+	ucontrol->value.integer.value[0] = 0xf8f2;
+	ucontrol->value.integer.value[1] = 0x4e1f;
+	tmp = reg_read(chip, AK4113_REG_Pc0) |
+		(reg_read(chip, AK4113_REG_Pc1) << 8);
+	ucontrol->value.integer.value[2] = tmp;
+	tmp = reg_read(chip, AK4113_REG_Pd0) |
+		(reg_read(chip, AK4113_REG_Pd1) << 8);
+	ucontrol->value.integer.value[3] = tmp;
+	return 0;
+}
+
+static int snd_ak4113_spdif_qinfo(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+	uinfo->count = AK4113_REG_QSUB_SIZE;
+	return 0;
+}
+
+static int snd_ak4113_spdif_qget(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4113 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned i;
+
+	for (i = 0; i < AK4113_REG_QSUB_SIZE; i++)
+		ucontrol->value.bytes.data[i] = reg_read(chip,
+				AK4113_REG_QSUB_ADDR + i);
+	return 0;
+}
+
+/* Don't forget to change AK4113_CONTROLS define!!! */
+static struct snd_kcontrol_new snd_ak4113_iec958_controls[] = {
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Parity Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4113_in_error_info,
+	.get =		snd_ak4113_in_error_get,
+	.private_value = offsetof(struct ak4113, parity_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 V-Bit Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4113_in_error_info,
+	.get =		snd_ak4113_in_error_get,
+	.private_value = offsetof(struct ak4113, v_bit_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 C-CRC Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4113_in_error_info,
+	.get =		snd_ak4113_in_error_get,
+	.private_value = offsetof(struct ak4113, ccrc_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Q-CRC Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4113_in_error_info,
+	.get =		snd_ak4113_in_error_get,
+	.private_value = offsetof(struct ak4113, qcrc_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 External Rate",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4113_rate_info,
+	.get =		snd_ak4113_rate_get,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK),
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.info =		snd_ak4113_spdif_mask_info,
+	.get =		snd_ak4113_spdif_mask_get,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4113_spdif_info,
+	.get =		snd_ak4113_spdif_get,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Preample Capture Default",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4113_spdif_pinfo,
+	.get =		snd_ak4113_spdif_pget,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Q-subcode Capture Default",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4113_spdif_qinfo,
+	.get =		snd_ak4113_spdif_qget,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Audio",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4113_in_bit_info,
+	.get =		snd_ak4113_in_bit_get,
+	.private_value = (1<<31) | (1<<8) | AK4113_REG_RCS0,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Non-PCM Bitstream",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4113_in_bit_info,
+	.get =		snd_ak4113_in_bit_get,
+	.private_value = (0<<8) | AK4113_REG_RCS1,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 DTS Bitstream",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4113_in_bit_info,
+	.get =		snd_ak4113_in_bit_get,
+	.private_value = (1<<8) | AK4113_REG_RCS1,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"AK4113 Input Select",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ |
+		SNDRV_CTL_ELEM_ACCESS_WRITE,
+	.info =		snd_ak4113_rx_info,
+	.get =		snd_ak4113_rx_get,
+	.put =		snd_ak4113_rx_put,
+}
+};
+
+static void snd_ak4113_proc_regs_read(struct snd_info_entry *entry,
+		struct snd_info_buffer *buffer)
+{
+	struct ak4113 *ak4113 = entry->private_data;
+	int reg, val;
+	/* all ak4113 registers 0x00 - 0x1c */
+	for (reg = 0; reg < 0x1d; reg++) {
+		val = reg_read(ak4113, reg);
+		snd_iprintf(buffer, "0x%02x = 0x%02x\n", reg, val);
+	}
+}
+
+static void snd_ak4113_proc_init(struct ak4113 *ak4113)
+{
+	struct snd_info_entry *entry;
+	if (!snd_card_proc_new(ak4113->card, "ak4113", &entry))
+		snd_info_set_text_ops(entry, ak4113, snd_ak4113_proc_regs_read);
+}
+
+int snd_ak4113_build(struct ak4113 *ak4113,
+		struct snd_pcm_substream *cap_substream)
+{
+	struct snd_kcontrol *kctl;
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!cap_substream))
+		return -EINVAL;
+	ak4113->substream = cap_substream;
+	for (idx = 0; idx < AK4113_CONTROLS; idx++) {
+		kctl = snd_ctl_new1(&snd_ak4113_iec958_controls[idx], ak4113);
+		if (kctl == NULL)
+			return -ENOMEM;
+		kctl->id.device = cap_substream->pcm->device;
+		kctl->id.subdevice = cap_substream->number;
+		err = snd_ctl_add(ak4113->card, kctl);
+		if (err < 0)
+			return err;
+		ak4113->kctls[idx] = kctl;
+	}
+	snd_ak4113_proc_init(ak4113);
+	/* trigger workq */
+	schedule_delayed_work(&ak4113->work, HZ / 10);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_ak4113_build);
+
+int snd_ak4113_external_rate(struct ak4113 *ak4113)
+{
+	unsigned char rcs1;
+
+	rcs1 = reg_read(ak4113, AK4113_REG_RCS1);
+	return external_rate(rcs1);
+}
+EXPORT_SYMBOL_GPL(snd_ak4113_external_rate);
+
+int snd_ak4113_check_rate_and_errors(struct ak4113 *ak4113, unsigned int flags)
+{
+	struct snd_pcm_runtime *runtime =
+		ak4113->substream ? ak4113->substream->runtime : NULL;
+	unsigned long _flags;
+	int res = 0;
+	unsigned char rcs0, rcs1, rcs2;
+	unsigned char c0, c1;
+
+	rcs1 = reg_read(ak4113, AK4113_REG_RCS1);
+	if (flags & AK4113_CHECK_NO_STAT)
+		goto __rate;
+	rcs0 = reg_read(ak4113, AK4113_REG_RCS0);
+	rcs2 = reg_read(ak4113, AK4113_REG_RCS2);
+	spin_lock_irqsave(&ak4113->lock, _flags);
+	if (rcs0 & AK4113_PAR)
+		ak4113->parity_errors++;
+	if (rcs0 & AK4113_V)
+		ak4113->v_bit_errors++;
+	if (rcs2 & AK4113_CCRC)
+		ak4113->ccrc_errors++;
+	if (rcs2 & AK4113_QCRC)
+		ak4113->qcrc_errors++;
+	c0 = (ak4113->rcs0 & (AK4113_QINT | AK4113_CINT | AK4113_STC |
+				AK4113_AUDION | AK4113_AUTO | AK4113_UNLCK)) ^
+		(rcs0 & (AK4113_QINT | AK4113_CINT | AK4113_STC |
+			 AK4113_AUDION | AK4113_AUTO | AK4113_UNLCK));
+	c1 = (ak4113->rcs1 & (AK4113_DTSCD | AK4113_NPCM | AK4113_PEM |
+				AK4113_DAT | 0xf0)) ^
+		(rcs1 & (AK4113_DTSCD | AK4113_NPCM | AK4113_PEM |
+			 AK4113_DAT | 0xf0));
+	ak4113->rcs0 = rcs0 & ~(AK4113_QINT | AK4113_CINT | AK4113_STC);
+	ak4113->rcs1 = rcs1;
+	ak4113->rcs2 = rcs2;
+	spin_unlock_irqrestore(&ak4113->lock, _flags);
+
+	if (rcs0 & AK4113_PAR)
+		snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				&ak4113->kctls[0]->id);
+	if (rcs0 & AK4113_V)
+		snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				&ak4113->kctls[1]->id);
+	if (rcs2 & AK4113_CCRC)
+		snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				&ak4113->kctls[2]->id);
+	if (rcs2 & AK4113_QCRC)
+		snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				&ak4113->kctls[3]->id);
+
+	/* rate change */
+	if (c1 & 0xf0)
+		snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				&ak4113->kctls[4]->id);
+
+	if ((c1 & AK4113_PEM) | (c0 & AK4113_CINT))
+		snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				&ak4113->kctls[6]->id);
+	if (c0 & AK4113_QINT)
+		snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				&ak4113->kctls[8]->id);
+
+	if (c0 & AK4113_AUDION)
+		snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				&ak4113->kctls[9]->id);
+	if (c1 & AK4113_NPCM)
+		snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				&ak4113->kctls[10]->id);
+	if (c1 & AK4113_DTSCD)
+		snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				&ak4113->kctls[11]->id);
+
+	if (ak4113->change_callback && (c0 | c1) != 0)
+		ak4113->change_callback(ak4113, c0, c1);
+
+__rate:
+	/* compare rate */
+	res = external_rate(rcs1);
+	if (!(flags & AK4113_CHECK_NO_RATE) && runtime &&
+			(runtime->rate != res)) {
+		snd_pcm_stream_lock_irqsave(ak4113->substream, _flags);
+		if (snd_pcm_running(ak4113->substream)) {
+			/*printk(KERN_DEBUG "rate changed (%i <- %i)\n",
+			 * runtime->rate, res); */
+			snd_pcm_stop(ak4113->substream,
+					SNDRV_PCM_STATE_DRAINING);
+			wake_up(&runtime->sleep);
+			res = 1;
+		}
+		snd_pcm_stream_unlock_irqrestore(ak4113->substream, _flags);
+	}
+	return res;
+}
+EXPORT_SYMBOL_GPL(snd_ak4113_check_rate_and_errors);
+
+static void ak4113_stats(struct work_struct *work)
+{
+	struct ak4113 *chip = container_of(work, struct ak4113, work.work);
+
+	if (!chip->init)
+		snd_ak4113_check_rate_and_errors(chip, chip->check_flags);
+
+	schedule_delayed_work(&chip->work, HZ / 10);
+}
diff --git a/linux/sound/i2c/other/ak4114.c b/linux/sound/i2c/other/ak4114.c
new file mode 100644
index 0000000..0341451
--- /dev/null
+++ b/linux/sound/i2c/other/ak4114.c
@@ -0,0 +1,626 @@
+/*
+ *  Routines for control of the AK4114 via I2C and 4-wire serial interface
+ *  IEC958 (S/PDIF) receiver by Asahi Kasei
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/ak4114.h>
+#include <sound/asoundef.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("AK4114 IEC958 (S/PDIF) receiver by Asahi Kasei");
+MODULE_LICENSE("GPL");
+
+#define AK4114_ADDR			0x00 /* fixed address */
+
+static void ak4114_stats(struct work_struct *work);
+static void ak4114_init_regs(struct ak4114 *chip);
+
+static void reg_write(struct ak4114 *ak4114, unsigned char reg, unsigned char val)
+{
+	ak4114->write(ak4114->private_data, reg, val);
+	if (reg <= AK4114_REG_INT1_MASK)
+		ak4114->regmap[reg] = val;
+	else if (reg >= AK4114_REG_TXCSB0 && reg <= AK4114_REG_TXCSB4)
+		ak4114->txcsb[reg-AK4114_REG_TXCSB0] = val;
+}
+
+static inline unsigned char reg_read(struct ak4114 *ak4114, unsigned char reg)
+{
+	return ak4114->read(ak4114->private_data, reg);
+}
+
+#if 0
+static void reg_dump(struct ak4114 *ak4114)
+{
+	int i;
+
+	printk(KERN_DEBUG "AK4114 REG DUMP:\n");
+	for (i = 0; i < 0x20; i++)
+		printk(KERN_DEBUG "reg[%02x] = %02x (%02x)\n", i, reg_read(ak4114, i), i < sizeof(ak4114->regmap) ? ak4114->regmap[i] : 0);
+}
+#endif
+
+static void snd_ak4114_free(struct ak4114 *chip)
+{
+	chip->init = 1;	/* don't schedule new work */
+	mb();
+	cancel_delayed_work(&chip->work);
+	flush_scheduled_work();
+	kfree(chip);
+}
+
+static int snd_ak4114_dev_free(struct snd_device *device)
+{
+	struct ak4114 *chip = device->device_data;
+	snd_ak4114_free(chip);
+	return 0;
+}
+
+int snd_ak4114_create(struct snd_card *card,
+		      ak4114_read_t *read, ak4114_write_t *write,
+		      const unsigned char pgm[7], const unsigned char txcsb[5],
+		      void *private_data, struct ak4114 **r_ak4114)
+{
+	struct ak4114 *chip;
+	int err = 0;
+	unsigned char reg;
+	static struct snd_device_ops ops = {
+		.dev_free =     snd_ak4114_dev_free,
+	};
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	spin_lock_init(&chip->lock);
+	chip->card = card;
+	chip->read = read;
+	chip->write = write;
+	chip->private_data = private_data;
+	INIT_DELAYED_WORK(&chip->work, ak4114_stats);
+
+	for (reg = 0; reg < 7; reg++)
+		chip->regmap[reg] = pgm[reg];
+	for (reg = 0; reg < 5; reg++)
+		chip->txcsb[reg] = txcsb[reg];
+
+	ak4114_init_regs(chip);
+
+	chip->rcs0 = reg_read(chip, AK4114_REG_RCS0) & ~(AK4114_QINT | AK4114_CINT);
+	chip->rcs1 = reg_read(chip, AK4114_REG_RCS1);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0)
+		goto __fail;
+
+	if (r_ak4114)
+		*r_ak4114 = chip;
+	return 0;
+
+      __fail:
+	snd_ak4114_free(chip);
+	return err < 0 ? err : -EIO;
+}
+
+void snd_ak4114_reg_write(struct ak4114 *chip, unsigned char reg, unsigned char mask, unsigned char val)
+{
+	if (reg <= AK4114_REG_INT1_MASK)
+		reg_write(chip, reg, (chip->regmap[reg] & ~mask) | val);
+	else if (reg >= AK4114_REG_TXCSB0 && reg <= AK4114_REG_TXCSB4)
+		reg_write(chip, reg,
+			  (chip->txcsb[reg-AK4114_REG_TXCSB0] & ~mask) | val);
+}
+
+static void ak4114_init_regs(struct ak4114 *chip)
+{
+	unsigned char old = chip->regmap[AK4114_REG_PWRDN], reg;
+
+	/* bring the chip to reset state and powerdown state */
+	reg_write(chip, AK4114_REG_PWRDN, old & ~(AK4114_RST|AK4114_PWN));
+	udelay(200);
+	/* release reset, but leave powerdown */
+	reg_write(chip, AK4114_REG_PWRDN, (old | AK4114_RST) & ~AK4114_PWN);
+	udelay(200);
+	for (reg = 1; reg < 7; reg++)
+		reg_write(chip, reg, chip->regmap[reg]);
+	for (reg = 0; reg < 5; reg++)
+		reg_write(chip, reg + AK4114_REG_TXCSB0, chip->txcsb[reg]);
+	/* release powerdown, everything is initialized now */
+	reg_write(chip, AK4114_REG_PWRDN, old | AK4114_RST | AK4114_PWN);
+}
+
+void snd_ak4114_reinit(struct ak4114 *chip)
+{
+	chip->init = 1;
+	mb();
+	flush_scheduled_work();
+	ak4114_init_regs(chip);
+	/* bring up statistics / event queing */
+	chip->init = 0;
+	if (chip->kctls[0])
+		schedule_delayed_work(&chip->work, HZ / 10);
+}
+
+static unsigned int external_rate(unsigned char rcs1)
+{
+	switch (rcs1 & (AK4114_FS0|AK4114_FS1|AK4114_FS2|AK4114_FS3)) {
+	case AK4114_FS_32000HZ: return 32000;
+	case AK4114_FS_44100HZ: return 44100;
+	case AK4114_FS_48000HZ: return 48000;
+	case AK4114_FS_88200HZ: return 88200;
+	case AK4114_FS_96000HZ: return 96000;
+	case AK4114_FS_176400HZ: return 176400;
+	case AK4114_FS_192000HZ: return 192000;
+	default:		return 0;
+	}
+}
+
+static int snd_ak4114_in_error_info(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = LONG_MAX;
+	return 0;
+}
+
+static int snd_ak4114_in_error_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4114 *chip = snd_kcontrol_chip(kcontrol);
+	long *ptr;
+
+	spin_lock_irq(&chip->lock);
+	ptr = (long *)(((char *)chip) + kcontrol->private_value);
+	ucontrol->value.integer.value[0] = *ptr;
+	*ptr = 0;
+	spin_unlock_irq(&chip->lock);
+	return 0;
+}
+
+#define snd_ak4114_in_bit_info		snd_ctl_boolean_mono_info
+
+static int snd_ak4114_in_bit_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4114 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char reg = kcontrol->private_value & 0xff;
+	unsigned char bit = (kcontrol->private_value >> 8) & 0xff;
+	unsigned char inv = (kcontrol->private_value >> 31) & 1;
+
+	ucontrol->value.integer.value[0] = ((reg_read(chip, reg) & (1 << bit)) ? 1 : 0) ^ inv;
+	return 0;
+}
+
+static int snd_ak4114_rate_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 192000;
+	return 0;
+}
+
+static int snd_ak4114_rate_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4114 *chip = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = external_rate(reg_read(chip, AK4114_REG_RCS1));
+	return 0;
+}
+
+static int snd_ak4114_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_ak4114_spdif_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4114 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned i;
+
+	for (i = 0; i < AK4114_REG_RXCSB_SIZE; i++)
+		ucontrol->value.iec958.status[i] = reg_read(chip, AK4114_REG_RXCSB0 + i);
+	return 0;
+}
+
+static int snd_ak4114_spdif_playback_get(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4114 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned i;
+
+	for (i = 0; i < AK4114_REG_TXCSB_SIZE; i++)
+		ucontrol->value.iec958.status[i] = chip->txcsb[i];
+	return 0;
+}
+
+static int snd_ak4114_spdif_playback_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4114 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned i;
+
+	for (i = 0; i < AK4114_REG_TXCSB_SIZE; i++)
+		reg_write(chip, AK4114_REG_TXCSB0 + i, ucontrol->value.iec958.status[i]);
+	return 0;
+}
+
+static int snd_ak4114_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_ak4114_spdif_mask_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	memset(ucontrol->value.iec958.status, 0xff, AK4114_REG_RXCSB_SIZE);
+	return 0;
+}
+
+static int snd_ak4114_spdif_pinfo(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0xffff;
+	uinfo->count = 4;
+	return 0;
+}
+
+static int snd_ak4114_spdif_pget(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4114 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned short tmp;
+
+	ucontrol->value.integer.value[0] = 0xf8f2;
+	ucontrol->value.integer.value[1] = 0x4e1f;
+	tmp = reg_read(chip, AK4114_REG_Pc0) | (reg_read(chip, AK4114_REG_Pc1) << 8);
+	ucontrol->value.integer.value[2] = tmp;
+	tmp = reg_read(chip, AK4114_REG_Pd0) | (reg_read(chip, AK4114_REG_Pd1) << 8);
+	ucontrol->value.integer.value[3] = tmp;
+	return 0;
+}
+
+static int snd_ak4114_spdif_qinfo(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+	uinfo->count = AK4114_REG_QSUB_SIZE;
+	return 0;
+}
+
+static int snd_ak4114_spdif_qget(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4114 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned i;
+
+	for (i = 0; i < AK4114_REG_QSUB_SIZE; i++)
+		ucontrol->value.bytes.data[i] = reg_read(chip, AK4114_REG_QSUB_ADDR + i);
+	return 0;
+}
+
+/* Don't forget to change AK4114_CONTROLS define!!! */
+static struct snd_kcontrol_new snd_ak4114_iec958_controls[] = {
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Parity Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_in_error_info,
+	.get =		snd_ak4114_in_error_get,
+	.private_value = offsetof(struct ak4114, parity_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 V-Bit Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_in_error_info,
+	.get =		snd_ak4114_in_error_get,
+	.private_value = offsetof(struct ak4114, v_bit_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 C-CRC Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_in_error_info,
+	.get =		snd_ak4114_in_error_get,
+	.private_value = offsetof(struct ak4114, ccrc_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Q-CRC Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_in_error_info,
+	.get =		snd_ak4114_in_error_get,
+	.private_value = offsetof(struct ak4114, qcrc_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 External Rate",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_rate_info,
+	.get =		snd_ak4114_rate_get,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.info =		snd_ak4114_spdif_mask_info,
+	.get =		snd_ak4114_spdif_mask_get,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_spdif_info,
+	.get =		snd_ak4114_spdif_playback_get,
+	.put =		snd_ak4114_spdif_playback_put,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK),
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.info =		snd_ak4114_spdif_mask_info,
+	.get =		snd_ak4114_spdif_mask_get,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT),
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_spdif_info,
+	.get =		snd_ak4114_spdif_get,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Preample Capture Default",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_spdif_pinfo,
+	.get =		snd_ak4114_spdif_pget,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Q-subcode Capture Default",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_spdif_qinfo,
+	.get =		snd_ak4114_spdif_qget,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Audio",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_in_bit_info,
+	.get =		snd_ak4114_in_bit_get,
+	.private_value = (1<<31) | (1<<8) | AK4114_REG_RCS0,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Non-PCM Bitstream",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_in_bit_info,
+	.get =		snd_ak4114_in_bit_get,
+	.private_value = (6<<8) | AK4114_REG_RCS0,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 DTS Bitstream",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_in_bit_info,
+	.get =		snd_ak4114_in_bit_get,
+	.private_value = (3<<8) | AK4114_REG_RCS0,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 PPL Lock Status",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4114_in_bit_info,
+	.get =		snd_ak4114_in_bit_get,
+	.private_value = (1<<31) | (4<<8) | AK4114_REG_RCS0,
+}
+};
+
+
+static void snd_ak4114_proc_regs_read(struct snd_info_entry *entry,
+		struct snd_info_buffer *buffer)
+{
+	struct ak4114 *ak4114 = entry->private_data;
+	int reg, val;
+	/* all ak4114 registers 0x00 - 0x1f */
+	for (reg = 0; reg < 0x20; reg++) {
+		val = reg_read(ak4114, reg);
+		snd_iprintf(buffer, "0x%02x = 0x%02x\n", reg, val);
+	}
+}
+
+static void snd_ak4114_proc_init(struct ak4114 *ak4114)
+{
+	struct snd_info_entry *entry;
+	if (!snd_card_proc_new(ak4114->card, "ak4114", &entry))
+		snd_info_set_text_ops(entry, ak4114, snd_ak4114_proc_regs_read);
+}
+
+int snd_ak4114_build(struct ak4114 *ak4114,
+		     struct snd_pcm_substream *ply_substream,
+		     struct snd_pcm_substream *cap_substream)
+{
+	struct snd_kcontrol *kctl;
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!cap_substream))
+		return -EINVAL;
+	ak4114->playback_substream = ply_substream;
+	ak4114->capture_substream = cap_substream;
+	for (idx = 0; idx < AK4114_CONTROLS; idx++) {
+		kctl = snd_ctl_new1(&snd_ak4114_iec958_controls[idx], ak4114);
+		if (kctl == NULL)
+			return -ENOMEM;
+		if (strstr(kctl->id.name, "Playback")) {
+			if (ply_substream == NULL) {
+				snd_ctl_free_one(kctl);
+				ak4114->kctls[idx] = NULL;
+				continue;
+			}
+			kctl->id.device = ply_substream->pcm->device;
+			kctl->id.subdevice = ply_substream->number;
+		} else {
+			kctl->id.device = cap_substream->pcm->device;
+			kctl->id.subdevice = cap_substream->number;
+		}
+		err = snd_ctl_add(ak4114->card, kctl);
+		if (err < 0)
+			return err;
+		ak4114->kctls[idx] = kctl;
+	}
+	snd_ak4114_proc_init(ak4114);
+	/* trigger workq */
+	schedule_delayed_work(&ak4114->work, HZ / 10);
+	return 0;
+}
+
+/* notify kcontrols if any parameters are changed */
+static void ak4114_notify(struct ak4114 *ak4114,
+			  unsigned char rcs0, unsigned char rcs1,
+			  unsigned char c0, unsigned char c1)
+{
+	if (!ak4114->kctls[0])
+		return;
+
+	if (rcs0 & AK4114_PAR)
+		snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ak4114->kctls[0]->id);
+	if (rcs0 & AK4114_V)
+		snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ak4114->kctls[1]->id);
+	if (rcs1 & AK4114_CCRC)
+		snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ak4114->kctls[2]->id);
+	if (rcs1 & AK4114_QCRC)
+		snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ak4114->kctls[3]->id);
+
+	/* rate change */
+	if (c1 & 0xf0)
+		snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ak4114->kctls[4]->id);
+
+	if ((c0 & AK4114_PEM) | (c0 & AK4114_CINT))
+		snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ak4114->kctls[9]->id);
+	if (c0 & AK4114_QINT)
+		snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ak4114->kctls[10]->id);
+
+	if (c0 & AK4114_AUDION)
+		snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ak4114->kctls[11]->id);
+	if (c0 & AK4114_AUTO)
+		snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ak4114->kctls[12]->id);
+	if (c0 & AK4114_DTSCD)
+		snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ak4114->kctls[13]->id);
+	if (c0 & AK4114_UNLCK)
+		snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ak4114->kctls[14]->id);
+}
+
+int snd_ak4114_external_rate(struct ak4114 *ak4114)
+{
+	unsigned char rcs1;
+
+	rcs1 = reg_read(ak4114, AK4114_REG_RCS1);
+	return external_rate(rcs1);
+}
+
+int snd_ak4114_check_rate_and_errors(struct ak4114 *ak4114, unsigned int flags)
+{
+	struct snd_pcm_runtime *runtime = ak4114->capture_substream ? ak4114->capture_substream->runtime : NULL;
+	unsigned long _flags;
+	int res = 0;
+	unsigned char rcs0, rcs1;
+	unsigned char c0, c1;
+
+	rcs1 = reg_read(ak4114, AK4114_REG_RCS1);
+	if (flags & AK4114_CHECK_NO_STAT)
+		goto __rate;
+	rcs0 = reg_read(ak4114, AK4114_REG_RCS0);
+	spin_lock_irqsave(&ak4114->lock, _flags);
+	if (rcs0 & AK4114_PAR)
+		ak4114->parity_errors++;
+	if (rcs1 & AK4114_V)
+		ak4114->v_bit_errors++;
+	if (rcs1 & AK4114_CCRC)
+		ak4114->ccrc_errors++;
+	if (rcs1 & AK4114_QCRC)
+		ak4114->qcrc_errors++;
+	c0 = (ak4114->rcs0 & (AK4114_QINT | AK4114_CINT | AK4114_PEM | AK4114_AUDION | AK4114_AUTO | AK4114_UNLCK)) ^
+                     (rcs0 & (AK4114_QINT | AK4114_CINT | AK4114_PEM | AK4114_AUDION | AK4114_AUTO | AK4114_UNLCK));
+	c1 = (ak4114->rcs1 & 0xf0) ^ (rcs1 & 0xf0);
+	ak4114->rcs0 = rcs0 & ~(AK4114_QINT | AK4114_CINT);
+	ak4114->rcs1 = rcs1;
+	spin_unlock_irqrestore(&ak4114->lock, _flags);
+
+	ak4114_notify(ak4114, rcs0, rcs1, c0, c1);
+	if (ak4114->change_callback && (c0 | c1) != 0)
+		ak4114->change_callback(ak4114, c0, c1);
+
+      __rate:
+	/* compare rate */
+	res = external_rate(rcs1);
+	if (!(flags & AK4114_CHECK_NO_RATE) && runtime && runtime->rate != res) {
+		snd_pcm_stream_lock_irqsave(ak4114->capture_substream, _flags);
+		if (snd_pcm_running(ak4114->capture_substream)) {
+			// printk(KERN_DEBUG "rate changed (%i <- %i)\n", runtime->rate, res);
+			snd_pcm_stop(ak4114->capture_substream, SNDRV_PCM_STATE_DRAINING);
+			res = 1;
+		}
+		snd_pcm_stream_unlock_irqrestore(ak4114->capture_substream, _flags);
+	}
+	return res;
+}
+
+static void ak4114_stats(struct work_struct *work)
+{
+	struct ak4114 *chip = container_of(work, struct ak4114, work.work);
+
+	if (!chip->init)
+		snd_ak4114_check_rate_and_errors(chip, chip->check_flags);
+
+	schedule_delayed_work(&chip->work, HZ / 10);
+}
+
+EXPORT_SYMBOL(snd_ak4114_create);
+EXPORT_SYMBOL(snd_ak4114_reg_write);
+EXPORT_SYMBOL(snd_ak4114_reinit);
+EXPORT_SYMBOL(snd_ak4114_build);
+EXPORT_SYMBOL(snd_ak4114_external_rate);
+EXPORT_SYMBOL(snd_ak4114_check_rate_and_errors);
diff --git a/linux/sound/i2c/other/ak4117.c b/linux/sound/i2c/other/ak4117.c
new file mode 100644
index 0000000..2cad2d6
--- /dev/null
+++ b/linux/sound/i2c/other/ak4117.c
@@ -0,0 +1,551 @@
+/*
+ *  Routines for control of the AK4117 via 4-wire serial interface
+ *  IEC958 (S/PDIF) receiver by Asahi Kasei
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/ak4117.h>
+#include <sound/asoundef.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("AK4117 IEC958 (S/PDIF) receiver by Asahi Kasei");
+MODULE_LICENSE("GPL");
+
+#define AK4117_ADDR			0x00 /* fixed address */
+
+static void snd_ak4117_timer(unsigned long data);
+
+static void reg_write(struct ak4117 *ak4117, unsigned char reg, unsigned char val)
+{
+	ak4117->write(ak4117->private_data, reg, val);
+	if (reg < sizeof(ak4117->regmap))
+		ak4117->regmap[reg] = val;
+}
+
+static inline unsigned char reg_read(struct ak4117 *ak4117, unsigned char reg)
+{
+	return ak4117->read(ak4117->private_data, reg);
+}
+
+#if 0
+static void reg_dump(struct ak4117 *ak4117)
+{
+	int i;
+
+	printk(KERN_DEBUG "AK4117 REG DUMP:\n");
+	for (i = 0; i < 0x1b; i++)
+		printk(KERN_DEBUG "reg[%02x] = %02x (%02x)\n", i, reg_read(ak4117, i), i < sizeof(ak4117->regmap) ? ak4117->regmap[i] : 0);
+}
+#endif
+
+static void snd_ak4117_free(struct ak4117 *chip)
+{
+	del_timer(&chip->timer);
+	kfree(chip);
+}
+
+static int snd_ak4117_dev_free(struct snd_device *device)
+{
+	struct ak4117 *chip = device->device_data;
+	snd_ak4117_free(chip);
+	return 0;
+}
+
+int snd_ak4117_create(struct snd_card *card, ak4117_read_t *read, ak4117_write_t *write,
+		      const unsigned char pgm[5], void *private_data, struct ak4117 **r_ak4117)
+{
+	struct ak4117 *chip;
+	int err = 0;
+	unsigned char reg;
+	static struct snd_device_ops ops = {
+		.dev_free =     snd_ak4117_dev_free,
+	};
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	spin_lock_init(&chip->lock);
+	chip->card = card;
+	chip->read = read;
+	chip->write = write;
+	chip->private_data = private_data;
+	init_timer(&chip->timer);
+	chip->timer.data = (unsigned long)chip;
+	chip->timer.function = snd_ak4117_timer;
+
+	for (reg = 0; reg < 5; reg++)
+		chip->regmap[reg] = pgm[reg];
+	snd_ak4117_reinit(chip);
+
+	chip->rcs0 = reg_read(chip, AK4117_REG_RCS0) & ~(AK4117_QINT | AK4117_CINT | AK4117_STC);
+	chip->rcs1 = reg_read(chip, AK4117_REG_RCS1);
+	chip->rcs2 = reg_read(chip, AK4117_REG_RCS2);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_CODEC, chip, &ops)) < 0)
+		goto __fail;
+
+	if (r_ak4117)
+		*r_ak4117 = chip;
+	return 0;
+
+      __fail:
+	snd_ak4117_free(chip);
+	return err < 0 ? err : -EIO;
+}
+
+void snd_ak4117_reg_write(struct ak4117 *chip, unsigned char reg, unsigned char mask, unsigned char val)
+{
+	if (reg >= 5)
+		return;
+	reg_write(chip, reg, (chip->regmap[reg] & ~mask) | val);
+}
+
+void snd_ak4117_reinit(struct ak4117 *chip)
+{
+	unsigned char old = chip->regmap[AK4117_REG_PWRDN], reg;
+
+	del_timer(&chip->timer);
+	chip->init = 1;
+	/* bring the chip to reset state and powerdown state */
+	reg_write(chip, AK4117_REG_PWRDN, 0);
+	udelay(200);
+	/* release reset, but leave powerdown */
+	reg_write(chip, AK4117_REG_PWRDN, (old | AK4117_RST) & ~AK4117_PWN);
+	udelay(200);
+	for (reg = 1; reg < 5; reg++)
+		reg_write(chip, reg, chip->regmap[reg]);
+	/* release powerdown, everything is initialized now */
+	reg_write(chip, AK4117_REG_PWRDN, old | AK4117_RST | AK4117_PWN);
+	chip->init = 0;
+	chip->timer.expires = 1 + jiffies;
+	add_timer(&chip->timer);
+}
+
+static unsigned int external_rate(unsigned char rcs1)
+{
+	switch (rcs1 & (AK4117_FS0|AK4117_FS1|AK4117_FS2|AK4117_FS3)) {
+	case AK4117_FS_32000HZ: return 32000;
+	case AK4117_FS_44100HZ: return 44100;
+	case AK4117_FS_48000HZ: return 48000;
+	case AK4117_FS_88200HZ: return 88200;
+	case AK4117_FS_96000HZ: return 96000;
+	case AK4117_FS_176400HZ: return 176400;
+	case AK4117_FS_192000HZ: return 192000;
+	default:		return 0;
+	}
+}
+
+static int snd_ak4117_in_error_info(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = LONG_MAX;
+	return 0;
+}
+
+static int snd_ak4117_in_error_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4117 *chip = snd_kcontrol_chip(kcontrol);
+	long *ptr;
+
+	spin_lock_irq(&chip->lock);
+	ptr = (long *)(((char *)chip) + kcontrol->private_value);
+	ucontrol->value.integer.value[0] = *ptr;
+	*ptr = 0;
+	spin_unlock_irq(&chip->lock);
+	return 0;
+}
+
+#define snd_ak4117_in_bit_info		snd_ctl_boolean_mono_info
+
+static int snd_ak4117_in_bit_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4117 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char reg = kcontrol->private_value & 0xff;
+	unsigned char bit = (kcontrol->private_value >> 8) & 0xff;
+	unsigned char inv = (kcontrol->private_value >> 31) & 1;
+
+	ucontrol->value.integer.value[0] = ((reg_read(chip, reg) & (1 << bit)) ? 1 : 0) ^ inv;
+	return 0;
+}
+
+static int snd_ak4117_rx_info(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_ak4117_rx_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4117 *chip = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = (chip->regmap[AK4117_REG_IO] & AK4117_IPS) ? 1 : 0;
+	return 0;
+}
+
+static int snd_ak4117_rx_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4117 *chip = snd_kcontrol_chip(kcontrol);
+	int change;
+	u8 old_val;
+	
+	spin_lock_irq(&chip->lock);
+	old_val = chip->regmap[AK4117_REG_IO];
+	change = !!ucontrol->value.integer.value[0] != ((old_val & AK4117_IPS) ? 1 : 0);
+	if (change)
+		reg_write(chip, AK4117_REG_IO, (old_val & ~AK4117_IPS) | (ucontrol->value.integer.value[0] ? AK4117_IPS : 0));
+	spin_unlock_irq(&chip->lock);
+	return change;
+}
+
+static int snd_ak4117_rate_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 192000;
+	return 0;
+}
+
+static int snd_ak4117_rate_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4117 *chip = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = external_rate(reg_read(chip, AK4117_REG_RCS1));
+	return 0;
+}
+
+static int snd_ak4117_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_ak4117_spdif_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4117 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned i;
+
+	for (i = 0; i < AK4117_REG_RXCSB_SIZE; i++)
+		ucontrol->value.iec958.status[i] = reg_read(chip, AK4117_REG_RXCSB0 + i);
+	return 0;
+}
+
+static int snd_ak4117_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_ak4117_spdif_mask_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	memset(ucontrol->value.iec958.status, 0xff, AK4117_REG_RXCSB_SIZE);
+	return 0;
+}
+
+static int snd_ak4117_spdif_pinfo(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0xffff;
+	uinfo->count = 4;
+	return 0;
+}
+
+static int snd_ak4117_spdif_pget(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4117 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned short tmp;
+
+	ucontrol->value.integer.value[0] = 0xf8f2;
+	ucontrol->value.integer.value[1] = 0x4e1f;
+	tmp = reg_read(chip, AK4117_REG_Pc0) | (reg_read(chip, AK4117_REG_Pc1) << 8);
+	ucontrol->value.integer.value[2] = tmp;
+	tmp = reg_read(chip, AK4117_REG_Pd0) | (reg_read(chip, AK4117_REG_Pd1) << 8);
+	ucontrol->value.integer.value[3] = tmp;
+	return 0;
+}
+
+static int snd_ak4117_spdif_qinfo(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+	uinfo->count = AK4117_REG_QSUB_SIZE;
+	return 0;
+}
+
+static int snd_ak4117_spdif_qget(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct ak4117 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned i;
+
+	for (i = 0; i < AK4117_REG_QSUB_SIZE; i++)
+		ucontrol->value.bytes.data[i] = reg_read(chip, AK4117_REG_QSUB_ADDR + i);
+	return 0;
+}
+
+/* Don't forget to change AK4117_CONTROLS define!!! */
+static struct snd_kcontrol_new snd_ak4117_iec958_controls[] = {
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Parity Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4117_in_error_info,
+	.get =		snd_ak4117_in_error_get,
+	.private_value = offsetof(struct ak4117, parity_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 V-Bit Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4117_in_error_info,
+	.get =		snd_ak4117_in_error_get,
+	.private_value = offsetof(struct ak4117, v_bit_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 C-CRC Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4117_in_error_info,
+	.get =		snd_ak4117_in_error_get,
+	.private_value = offsetof(struct ak4117, ccrc_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Q-CRC Errors",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4117_in_error_info,
+	.get =		snd_ak4117_in_error_get,
+	.private_value = offsetof(struct ak4117, qcrc_errors),
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 External Rate",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4117_rate_info,
+	.get =		snd_ak4117_rate_get,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK),
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.info =		snd_ak4117_spdif_mask_info,
+	.get =		snd_ak4117_spdif_mask_get,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT),
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4117_spdif_info,
+	.get =		snd_ak4117_spdif_get,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Preample Capture Default",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4117_spdif_pinfo,
+	.get =		snd_ak4117_spdif_pget,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Q-subcode Capture Default",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4117_spdif_qinfo,
+	.get =		snd_ak4117_spdif_qget,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Audio",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4117_in_bit_info,
+	.get =		snd_ak4117_in_bit_get,
+	.private_value = (1<<31) | (3<<8) | AK4117_REG_RCS0,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 Non-PCM Bitstream",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4117_in_bit_info,
+	.get =		snd_ak4117_in_bit_get,
+	.private_value = (5<<8) | AK4117_REG_RCS1,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"IEC958 DTS Bitstream",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info =		snd_ak4117_in_bit_info,
+	.get =		snd_ak4117_in_bit_get,
+	.private_value = (6<<8) | AK4117_REG_RCS1,
+},
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		"AK4117 Input Select",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE,
+	.info =		snd_ak4117_rx_info,
+	.get =		snd_ak4117_rx_get,
+	.put =		snd_ak4117_rx_put,
+}
+};
+
+int snd_ak4117_build(struct ak4117 *ak4117, struct snd_pcm_substream *cap_substream)
+{
+	struct snd_kcontrol *kctl;
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!cap_substream))
+		return -EINVAL;
+	ak4117->substream = cap_substream;
+	for (idx = 0; idx < AK4117_CONTROLS; idx++) {
+		kctl = snd_ctl_new1(&snd_ak4117_iec958_controls[idx], ak4117);
+		if (kctl == NULL)
+			return -ENOMEM;
+		kctl->id.device = cap_substream->pcm->device;
+		kctl->id.subdevice = cap_substream->number;
+		err = snd_ctl_add(ak4117->card, kctl);
+		if (err < 0)
+			return err;
+		ak4117->kctls[idx] = kctl;
+	}
+	return 0;
+}
+
+int snd_ak4117_external_rate(struct ak4117 *ak4117)
+{
+	unsigned char rcs1;
+
+	rcs1 = reg_read(ak4117, AK4117_REG_RCS1);
+	return external_rate(rcs1);
+}
+
+int snd_ak4117_check_rate_and_errors(struct ak4117 *ak4117, unsigned int flags)
+{
+	struct snd_pcm_runtime *runtime = ak4117->substream ? ak4117->substream->runtime : NULL;
+	unsigned long _flags;
+	int res = 0;
+	unsigned char rcs0, rcs1, rcs2;
+	unsigned char c0, c1;
+
+	rcs1 = reg_read(ak4117, AK4117_REG_RCS1);
+	if (flags & AK4117_CHECK_NO_STAT)
+		goto __rate;
+	rcs0 = reg_read(ak4117, AK4117_REG_RCS0);
+	rcs2 = reg_read(ak4117, AK4117_REG_RCS2);
+	// printk(KERN_DEBUG "AK IRQ: rcs0 = 0x%x, rcs1 = 0x%x, rcs2 = 0x%x\n", rcs0, rcs1, rcs2);
+	spin_lock_irqsave(&ak4117->lock, _flags);
+	if (rcs0 & AK4117_PAR)
+		ak4117->parity_errors++;
+	if (rcs0 & AK4117_V)
+		ak4117->v_bit_errors++;
+	if (rcs2 & AK4117_CCRC)
+		ak4117->ccrc_errors++;
+	if (rcs2 & AK4117_QCRC)
+		ak4117->qcrc_errors++;
+	c0 = (ak4117->rcs0 & (AK4117_QINT | AK4117_CINT | AK4117_STC | AK4117_AUDION | AK4117_AUTO | AK4117_UNLCK)) ^
+                     (rcs0 & (AK4117_QINT | AK4117_CINT | AK4117_STC | AK4117_AUDION | AK4117_AUTO | AK4117_UNLCK));
+	c1 = (ak4117->rcs1 & (AK4117_DTSCD | AK4117_NPCM | AK4117_PEM | 0x0f)) ^
+	             (rcs1 & (AK4117_DTSCD | AK4117_NPCM | AK4117_PEM | 0x0f));
+	ak4117->rcs0 = rcs0 & ~(AK4117_QINT | AK4117_CINT | AK4117_STC);
+	ak4117->rcs1 = rcs1;
+	ak4117->rcs2 = rcs2;
+	spin_unlock_irqrestore(&ak4117->lock, _flags);
+
+	if (rcs0 & AK4117_PAR)
+		snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[0]->id);
+	if (rcs0 & AK4117_V)
+		snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[1]->id);
+	if (rcs2 & AK4117_CCRC)
+		snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[2]->id);
+	if (rcs2 & AK4117_QCRC)
+		snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[3]->id);
+
+	/* rate change */
+	if (c1 & 0x0f)
+		snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[4]->id);
+
+	if ((c1 & AK4117_PEM) | (c0 & AK4117_CINT))
+		snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[6]->id);
+	if (c0 & AK4117_QINT)
+		snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[8]->id);
+
+	if (c0 & AK4117_AUDION)
+		snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[9]->id);
+	if (c1 & AK4117_NPCM)
+		snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[10]->id);
+	if (c1 & AK4117_DTSCD)
+		snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[11]->id);
+		
+	if (ak4117->change_callback && (c0 | c1) != 0)
+		ak4117->change_callback(ak4117, c0, c1);
+
+      __rate:
+	/* compare rate */
+	res = external_rate(rcs1);
+	if (!(flags & AK4117_CHECK_NO_RATE) && runtime && runtime->rate != res) {
+		snd_pcm_stream_lock_irqsave(ak4117->substream, _flags);
+		if (snd_pcm_running(ak4117->substream)) {
+			// printk(KERN_DEBUG "rate changed (%i <- %i)\n", runtime->rate, res);
+			snd_pcm_stop(ak4117->substream, SNDRV_PCM_STATE_DRAINING);
+			wake_up(&runtime->sleep);
+			res = 1;
+		}
+		snd_pcm_stream_unlock_irqrestore(ak4117->substream, _flags);
+	}
+	return res;
+}
+
+static void snd_ak4117_timer(unsigned long data)
+{
+	struct ak4117 *chip = (struct ak4117 *)data;
+
+	if (chip->init)
+		return;
+	snd_ak4117_check_rate_and_errors(chip, 0);
+	chip->timer.expires = 1 + jiffies;
+	add_timer(&chip->timer);
+}
+
+EXPORT_SYMBOL(snd_ak4117_create);
+EXPORT_SYMBOL(snd_ak4117_reg_write);
+EXPORT_SYMBOL(snd_ak4117_reinit);
+EXPORT_SYMBOL(snd_ak4117_build);
+EXPORT_SYMBOL(snd_ak4117_external_rate);
+EXPORT_SYMBOL(snd_ak4117_check_rate_and_errors);
diff --git a/linux/sound/i2c/other/ak4xxx-adda.c b/linux/sound/i2c/other/ak4xxx-adda.c
new file mode 100644
index 0000000..57ccba8
--- /dev/null
+++ b/linux/sound/i2c/other/ak4xxx-adda.c
@@ -0,0 +1,944 @@
+/*
+ *   ALSA driver for AK4524 / AK4528 / AK4529 / AK4355 / AK4358 / AK4381
+ *   AD and DA converters
+ *
+ *	Copyright (c) 2000-2004 Jaroslav Kysela <perex@perex.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 <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/ak4xxx-adda.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("Routines for control of AK452x / AK43xx  AD/DA converters");
+MODULE_LICENSE("GPL");
+
+/* write the given register and save the data to the cache */
+void snd_akm4xxx_write(struct snd_akm4xxx *ak, int chip, unsigned char reg,
+		       unsigned char val)
+{
+	ak->ops.lock(ak, chip);
+	ak->ops.write(ak, chip, reg, val);
+
+	/* save the data */
+	snd_akm4xxx_set(ak, chip, reg, val);
+	ak->ops.unlock(ak, chip);
+}
+
+EXPORT_SYMBOL(snd_akm4xxx_write);
+
+/* reset procedure for AK4524 and AK4528 */
+static void ak4524_reset(struct snd_akm4xxx *ak, int state)
+{
+	unsigned int chip;
+	unsigned char reg;
+
+	for (chip = 0; chip < ak->num_dacs/2; chip++) {
+		snd_akm4xxx_write(ak, chip, 0x01, state ? 0x00 : 0x03);
+		if (state)
+			continue;
+		/* DAC volumes */
+		for (reg = 0x04; reg < ak->total_regs; reg++)
+			snd_akm4xxx_write(ak, chip, reg,
+					  snd_akm4xxx_get(ak, chip, reg));
+	}
+}
+
+/* reset procedure for AK4355 and AK4358 */
+static void ak435X_reset(struct snd_akm4xxx *ak, int state)
+{
+	unsigned char reg;
+
+	if (state) {
+		snd_akm4xxx_write(ak, 0, 0x01, 0x02); /* reset and soft-mute */
+		return;
+	}
+	for (reg = 0x00; reg < ak->total_regs; reg++)
+		if (reg != 0x01)
+			snd_akm4xxx_write(ak, 0, reg,
+					  snd_akm4xxx_get(ak, 0, reg));
+	snd_akm4xxx_write(ak, 0, 0x01, 0x01); /* un-reset, unmute */
+}
+
+/* reset procedure for AK4381 */
+static void ak4381_reset(struct snd_akm4xxx *ak, int state)
+{
+	unsigned int chip;
+	unsigned char reg;
+	for (chip = 0; chip < ak->num_dacs/2; chip++) {
+		snd_akm4xxx_write(ak, chip, 0x00, state ? 0x0c : 0x0f);
+		if (state)
+			continue;
+		for (reg = 0x01; reg < ak->total_regs; reg++)
+			snd_akm4xxx_write(ak, chip, reg,
+					  snd_akm4xxx_get(ak, chip, reg));
+	}
+}
+
+/*
+ * reset the AKM codecs
+ * @state: 1 = reset codec, 0 = restore the registers
+ *
+ * assert the reset operation and restores the register values to the chips.
+ */
+void snd_akm4xxx_reset(struct snd_akm4xxx *ak, int state)
+{
+	switch (ak->type) {
+	case SND_AK4524:
+	case SND_AK4528:
+	case SND_AK4620:
+		ak4524_reset(ak, state);
+		break;
+	case SND_AK4529:
+		/* FIXME: needed for ak4529? */
+		break;
+	case SND_AK4355:
+		ak435X_reset(ak, state);
+		break;
+	case SND_AK4358:
+		ak435X_reset(ak, state);
+		break;
+	case SND_AK4381:
+		ak4381_reset(ak, state);
+		break;
+	default:
+		break;
+	}
+}
+
+EXPORT_SYMBOL(snd_akm4xxx_reset);
+
+
+/*
+ * Volume conversion table for non-linear volumes
+ * from -63.5dB (mute) to 0dB step 0.5dB
+ *
+ * Used for AK4524/AK4620 input/ouput attenuation, AK4528, and
+ * AK5365 input attenuation
+ */
+static const unsigned char vol_cvt_datt[128] = {
+	0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04,
+	0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 0x06,
+	0x06, 0x07, 0x07, 0x08, 0x08, 0x08, 0x09, 0x0a,
+	0x0a, 0x0b, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0f,
+	0x10, 0x10, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14,
+	0x15, 0x16, 0x17, 0x17, 0x18, 0x19, 0x1a, 0x1c,
+	0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x23,
+	0x24, 0x25, 0x26, 0x28, 0x29, 0x2a, 0x2b, 0x2d,
+	0x2e, 0x30, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+	0x37, 0x38, 0x39, 0x3b, 0x3c, 0x3e, 0x3f, 0x40,
+	0x41, 0x42, 0x43, 0x44, 0x46, 0x47, 0x48, 0x4a,
+	0x4b, 0x4d, 0x4e, 0x50, 0x51, 0x52, 0x53, 0x54,
+	0x55, 0x56, 0x58, 0x59, 0x5b, 0x5c, 0x5e, 0x5f,
+	0x60, 0x61, 0x62, 0x64, 0x65, 0x66, 0x67, 0x69,
+	0x6a, 0x6c, 0x6d, 0x6f, 0x70, 0x71, 0x72, 0x73,
+	0x75, 0x76, 0x77, 0x79, 0x7a, 0x7c, 0x7d, 0x7f,
+};
+
+/*
+ * dB tables
+ */
+static const DECLARE_TLV_DB_SCALE(db_scale_vol_datt, -6350, 50, 1);
+static const DECLARE_TLV_DB_SCALE(db_scale_8bit, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(db_scale_7bit, -6350, 50, 1);
+static const DECLARE_TLV_DB_LINEAR(db_scale_linear, TLV_DB_GAIN_MUTE, 0);
+
+/*
+ * initialize all the ak4xxx chips
+ */
+void snd_akm4xxx_init(struct snd_akm4xxx *ak)
+{
+	static const unsigned char inits_ak4524[] = {
+		0x00, 0x07, /* 0: all power up */
+		0x01, 0x00, /* 1: ADC/DAC reset */
+		0x02, 0x60, /* 2: 24bit I2S */
+		0x03, 0x19, /* 3: deemphasis off */
+		0x01, 0x03, /* 1: ADC/DAC enable */
+		0x04, 0x00, /* 4: ADC left muted */
+		0x05, 0x00, /* 5: ADC right muted */
+		0x06, 0x00, /* 6: DAC left muted */
+		0x07, 0x00, /* 7: DAC right muted */
+		0xff, 0xff
+	};
+	static const unsigned char inits_ak4528[] = {
+		0x00, 0x07, /* 0: all power up */
+		0x01, 0x00, /* 1: ADC/DAC reset */
+		0x02, 0x60, /* 2: 24bit I2S */
+		0x03, 0x0d, /* 3: deemphasis off, turn LR highpass filters on */
+		0x01, 0x03, /* 1: ADC/DAC enable */
+		0x04, 0x00, /* 4: ADC left muted */
+		0x05, 0x00, /* 5: ADC right muted */
+		0xff, 0xff
+	};
+	static const unsigned char inits_ak4529[] = {
+		0x09, 0x01, /* 9: ATS=0, RSTN=1 */
+		0x0a, 0x3f, /* A: all power up, no zero/overflow detection */
+		0x00, 0x0c, /* 0: TDM=0, 24bit I2S, SMUTE=0 */
+		0x01, 0x00, /* 1: ACKS=0, ADC, loop off */
+		0x02, 0xff, /* 2: LOUT1 muted */
+		0x03, 0xff, /* 3: ROUT1 muted */
+		0x04, 0xff, /* 4: LOUT2 muted */
+		0x05, 0xff, /* 5: ROUT2 muted */
+		0x06, 0xff, /* 6: LOUT3 muted */
+		0x07, 0xff, /* 7: ROUT3 muted */
+		0x0b, 0xff, /* B: LOUT4 muted */
+		0x0c, 0xff, /* C: ROUT4 muted */
+		0x08, 0x55, /* 8: deemphasis all off */
+		0xff, 0xff
+	};
+	static const unsigned char inits_ak4355[] = {
+		0x01, 0x02, /* 1: reset and soft-mute */
+		0x00, 0x06, /* 0: mode3(i2s), disable auto-clock detect,
+			     * disable DZF, sharp roll-off, RSTN#=0 */
+		0x02, 0x0e, /* 2: DA's power up, normal speed, RSTN#=0 */
+		// 0x02, 0x2e, /* quad speed */
+		0x03, 0x01, /* 3: de-emphasis off */
+		0x04, 0x00, /* 4: LOUT1 volume muted */
+		0x05, 0x00, /* 5: ROUT1 volume muted */
+		0x06, 0x00, /* 6: LOUT2 volume muted */
+		0x07, 0x00, /* 7: ROUT2 volume muted */
+		0x08, 0x00, /* 8: LOUT3 volume muted */
+		0x09, 0x00, /* 9: ROUT3 volume muted */
+		0x0a, 0x00, /* a: DATT speed=0, ignore DZF */
+		0x01, 0x01, /* 1: un-reset, unmute */
+		0xff, 0xff
+	};
+	static const unsigned char inits_ak4358[] = {
+		0x01, 0x02, /* 1: reset and soft-mute */
+		0x00, 0x06, /* 0: mode3(i2s), disable auto-clock detect,
+			     * disable DZF, sharp roll-off, RSTN#=0 */
+		0x02, 0x4e, /* 2: DA's power up, normal speed, RSTN#=0 */
+		/* 0x02, 0x6e,*/ /* quad speed */
+		0x03, 0x01, /* 3: de-emphasis off */
+		0x04, 0x00, /* 4: LOUT1 volume muted */
+		0x05, 0x00, /* 5: ROUT1 volume muted */
+		0x06, 0x00, /* 6: LOUT2 volume muted */
+		0x07, 0x00, /* 7: ROUT2 volume muted */
+		0x08, 0x00, /* 8: LOUT3 volume muted */
+		0x09, 0x00, /* 9: ROUT3 volume muted */
+		0x0b, 0x00, /* b: LOUT4 volume muted */
+		0x0c, 0x00, /* c: ROUT4 volume muted */
+		0x0a, 0x00, /* a: DATT speed=0, ignore DZF */
+		0x01, 0x01, /* 1: un-reset, unmute */
+		0xff, 0xff
+	};
+	static const unsigned char inits_ak4381[] = {
+		0x00, 0x0c, /* 0: mode3(i2s), disable auto-clock detect */
+		0x01, 0x02, /* 1: de-emphasis off, normal speed,
+			     * sharp roll-off, DZF off */
+		// 0x01, 0x12, /* quad speed */
+		0x02, 0x00, /* 2: DZF disabled */
+		0x03, 0x00, /* 3: LATT 0 */
+		0x04, 0x00, /* 4: RATT 0 */
+		0x00, 0x0f, /* 0: power-up, un-reset */
+		0xff, 0xff
+	};
+	static const unsigned char inits_ak4620[] = {
+		0x00, 0x07, /* 0: normal */
+		0x01, 0x00, /* 0: reset */
+		0x01, 0x02, /* 1: RSTAD */
+		0x01, 0x03, /* 1: RSTDA */
+		0x01, 0x0f, /* 1: normal */
+		0x02, 0x60, /* 2: 24bit I2S */
+		0x03, 0x01, /* 3: deemphasis off */
+		0x04, 0x00, /* 4: LIN muted */
+		0x05, 0x00, /* 5: RIN muted */
+		0x06, 0x00, /* 6: LOUT muted */
+		0x07, 0x00, /* 7: ROUT muted */
+		0xff, 0xff
+	};
+
+	int chip;
+	const unsigned char *ptr, *inits;
+	unsigned char reg, data;
+
+	memset(ak->images, 0, sizeof(ak->images));
+	memset(ak->volumes, 0, sizeof(ak->volumes));
+
+	switch (ak->type) {
+	case SND_AK4524:
+		inits = inits_ak4524;
+		ak->num_chips = ak->num_dacs / 2;
+		ak->name = "ak4524";
+		ak->total_regs = 0x08;
+		break;
+	case SND_AK4528:
+		inits = inits_ak4528;
+		ak->num_chips = ak->num_dacs / 2;
+		ak->name = "ak4528";
+		ak->total_regs = 0x06;
+		break;
+	case SND_AK4529:
+		inits = inits_ak4529;
+		ak->num_chips = 1;
+		ak->name = "ak4529";
+		ak->total_regs = 0x0d;
+		break;
+	case SND_AK4355:
+		inits = inits_ak4355;
+		ak->num_chips = 1;
+		ak->name = "ak4355";
+		ak->total_regs = 0x0b;
+		break;
+	case SND_AK4358:
+		inits = inits_ak4358;
+		ak->num_chips = 1;
+		ak->name = "ak4358";
+		ak->total_regs = 0x10;
+		break;
+	case SND_AK4381:
+		inits = inits_ak4381;
+		ak->num_chips = ak->num_dacs / 2;
+		ak->name = "ak4381";
+		ak->total_regs = 0x05;
+		break;
+	case SND_AK5365:
+		/* FIXME: any init sequence? */
+		ak->num_chips = 1;
+		ak->name = "ak5365";
+		ak->total_regs = 0x08;
+		return;
+	case SND_AK4620:
+		inits = inits_ak4620;
+		ak->num_chips = ak->num_dacs / 2;
+		ak->name = "ak4620";
+		ak->total_regs = 0x08;
+		break;
+	default:
+		snd_BUG();
+		return;
+	}
+
+	for (chip = 0; chip < ak->num_chips; chip++) {
+		ptr = inits;
+		while (*ptr != 0xff) {
+			reg = *ptr++;
+			data = *ptr++;
+			snd_akm4xxx_write(ak, chip, reg, data);
+			udelay(10);
+		}
+	}
+}
+
+EXPORT_SYMBOL(snd_akm4xxx_init);
+
+/*
+ * Mixer callbacks
+ */
+#define AK_IPGA 			(1<<20)	/* including IPGA */
+#define AK_VOL_CVT 			(1<<21)	/* need dB conversion */
+#define AK_NEEDSMSB 			(1<<22)	/* need MSB update bit */
+#define AK_INVERT 			(1<<23)	/* data is inverted */
+#define AK_GET_CHIP(val)		(((val) >> 8) & 0xff)
+#define AK_GET_ADDR(val)		((val) & 0xff)
+#define AK_GET_SHIFT(val)		(((val) >> 16) & 0x0f)
+#define AK_GET_VOL_CVT(val)		(((val) >> 21) & 1)
+#define AK_GET_IPGA(val)		(((val) >> 20) & 1)
+#define AK_GET_NEEDSMSB(val)		(((val) >> 22) & 1)
+#define AK_GET_INVERT(val)		(((val) >> 23) & 1)
+#define AK_GET_MASK(val)		(((val) >> 24) & 0xff)
+#define AK_COMPOSE(chip,addr,shift,mask) \
+	(((chip) << 8) | (addr) | ((shift) << 16) | ((mask) << 24))
+
+static int snd_akm4xxx_volume_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	unsigned int mask = AK_GET_MASK(kcontrol->private_value);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_akm4xxx_volume_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol);
+	int chip = AK_GET_CHIP(kcontrol->private_value);
+	int addr = AK_GET_ADDR(kcontrol->private_value);
+
+	ucontrol->value.integer.value[0] = snd_akm4xxx_get_vol(ak, chip, addr);
+	return 0;
+}
+
+static int put_ak_reg(struct snd_kcontrol *kcontrol, int addr,
+		      unsigned char nval)
+{
+	struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol);
+	unsigned int mask = AK_GET_MASK(kcontrol->private_value);
+	int chip = AK_GET_CHIP(kcontrol->private_value);
+
+	if (snd_akm4xxx_get_vol(ak, chip, addr) == nval)
+		return 0;
+
+	snd_akm4xxx_set_vol(ak, chip, addr, nval);
+	if (AK_GET_VOL_CVT(kcontrol->private_value) && nval < 128)
+		nval = vol_cvt_datt[nval];
+	if (AK_GET_IPGA(kcontrol->private_value) && nval >= 128)
+		nval++; /* need to correct + 1 since both 127 and 128 are 0dB */
+	if (AK_GET_INVERT(kcontrol->private_value))
+		nval = mask - nval;
+	if (AK_GET_NEEDSMSB(kcontrol->private_value))
+		nval |= 0x80;
+	/* printk(KERN_DEBUG "DEBUG - AK writing reg: chip %x addr %x,
+	   nval %x\n", chip, addr, nval); */
+	snd_akm4xxx_write(ak, chip, addr, nval);
+	return 1;
+}
+
+static int snd_akm4xxx_volume_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned int mask = AK_GET_MASK(kcontrol->private_value);
+	unsigned int val = ucontrol->value.integer.value[0];
+	if (val > mask)
+		return -EINVAL;
+	return put_ak_reg(kcontrol, AK_GET_ADDR(kcontrol->private_value), val);
+}
+
+static int snd_akm4xxx_stereo_volume_info(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_info *uinfo)
+{
+	unsigned int mask = AK_GET_MASK(kcontrol->private_value);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_akm4xxx_stereo_volume_get(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol);
+	int chip = AK_GET_CHIP(kcontrol->private_value);
+	int addr = AK_GET_ADDR(kcontrol->private_value);
+
+	ucontrol->value.integer.value[0] = snd_akm4xxx_get_vol(ak, chip, addr);
+	ucontrol->value.integer.value[1] = snd_akm4xxx_get_vol(ak, chip, addr+1);
+	return 0;
+}
+
+static int snd_akm4xxx_stereo_volume_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	int addr = AK_GET_ADDR(kcontrol->private_value);
+	unsigned int mask = AK_GET_MASK(kcontrol->private_value);
+	unsigned int val[2];
+	int change;
+
+	val[0] = ucontrol->value.integer.value[0];
+	val[1] = ucontrol->value.integer.value[1];
+	if (val[0] > mask || val[1] > mask)
+		return -EINVAL;
+	change = put_ak_reg(kcontrol, addr, val[0]);
+	change |= put_ak_reg(kcontrol, addr + 1, val[1]);
+	return change;
+}
+
+static int snd_akm4xxx_deemphasis_info(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = {
+		"44.1kHz", "Off", "48kHz", "32kHz",
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item >= 4)
+		uinfo->value.enumerated.item = 3;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_akm4xxx_deemphasis_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol);
+	int chip = AK_GET_CHIP(kcontrol->private_value);
+	int addr = AK_GET_ADDR(kcontrol->private_value);
+	int shift = AK_GET_SHIFT(kcontrol->private_value);
+	ucontrol->value.enumerated.item[0] =
+		(snd_akm4xxx_get(ak, chip, addr) >> shift) & 3;
+	return 0;
+}
+
+static int snd_akm4xxx_deemphasis_put(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol);
+	int chip = AK_GET_CHIP(kcontrol->private_value);
+	int addr = AK_GET_ADDR(kcontrol->private_value);
+	int shift = AK_GET_SHIFT(kcontrol->private_value);
+	unsigned char nval = ucontrol->value.enumerated.item[0] & 3;
+	int change;
+	
+	nval = (nval << shift) |
+		(snd_akm4xxx_get(ak, chip, addr) & ~(3 << shift));
+	change = snd_akm4xxx_get(ak, chip, addr) != nval;
+	if (change)
+		snd_akm4xxx_write(ak, chip, addr, nval);
+	return change;
+}
+
+#define ak4xxx_switch_info	snd_ctl_boolean_mono_info
+
+static int ak4xxx_switch_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol);
+	int chip = AK_GET_CHIP(kcontrol->private_value);
+	int addr = AK_GET_ADDR(kcontrol->private_value);
+	int shift = AK_GET_SHIFT(kcontrol->private_value);
+	int invert = AK_GET_INVERT(kcontrol->private_value);
+	/* we observe the (1<<shift) bit only */
+	unsigned char val = snd_akm4xxx_get(ak, chip, addr) & (1<<shift);
+	if (invert)
+		val = ! val;
+	ucontrol->value.integer.value[0] = (val & (1<<shift)) != 0;
+	return 0;
+}
+
+static int ak4xxx_switch_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol);
+	int chip = AK_GET_CHIP(kcontrol->private_value);
+	int addr = AK_GET_ADDR(kcontrol->private_value);
+	int shift = AK_GET_SHIFT(kcontrol->private_value);
+	int invert = AK_GET_INVERT(kcontrol->private_value);
+	long flag = ucontrol->value.integer.value[0];
+	unsigned char val, oval;
+	int change;
+
+	if (invert)
+		flag = ! flag;
+	oval = snd_akm4xxx_get(ak, chip, addr);
+	if (flag)
+		val = oval | (1<<shift);
+	else
+		val = oval & ~(1<<shift);
+	change = (oval != val);
+	if (change)
+		snd_akm4xxx_write(ak, chip, addr, val);
+	return change;
+}
+
+#define AK5365_NUM_INPUTS 5
+
+static int ak4xxx_capture_num_inputs(struct snd_akm4xxx *ak, int mixer_ch)
+{
+	int num_names;
+	const char **input_names;
+
+	input_names = ak->adc_info[mixer_ch].input_names;
+	num_names = 0;
+	while (num_names < AK5365_NUM_INPUTS && input_names[num_names])
+		++num_names;
+	return num_names;
+}
+
+static int ak4xxx_capture_source_info(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol);
+	int mixer_ch = AK_GET_SHIFT(kcontrol->private_value);
+	const char **input_names;
+	int  num_names, idx;
+
+	num_names = ak4xxx_capture_num_inputs(ak, mixer_ch);
+	if (!num_names)
+		return -EINVAL;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = num_names;
+	idx = uinfo->value.enumerated.item;
+	if (idx >= num_names)
+		return -EINVAL;
+	input_names = ak->adc_info[mixer_ch].input_names;
+	strncpy(uinfo->value.enumerated.name, input_names[idx],
+		sizeof(uinfo->value.enumerated.name));
+	return 0;
+}
+
+static int ak4xxx_capture_source_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol);
+	int chip = AK_GET_CHIP(kcontrol->private_value);
+	int addr = AK_GET_ADDR(kcontrol->private_value);
+	int mask = AK_GET_MASK(kcontrol->private_value);
+	unsigned char val;
+
+	val = snd_akm4xxx_get(ak, chip, addr) & mask;
+	ucontrol->value.enumerated.item[0] = val;
+	return 0;
+}
+
+static int ak4xxx_capture_source_put(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol);
+	int mixer_ch = AK_GET_SHIFT(kcontrol->private_value);
+	int chip = AK_GET_CHIP(kcontrol->private_value);
+	int addr = AK_GET_ADDR(kcontrol->private_value);
+	int mask = AK_GET_MASK(kcontrol->private_value);
+	unsigned char oval, val;
+	int num_names = ak4xxx_capture_num_inputs(ak, mixer_ch);
+
+	if (ucontrol->value.enumerated.item[0] >= num_names)
+		return -EINVAL;
+
+	oval = snd_akm4xxx_get(ak, chip, addr);
+	val = oval & ~mask;
+	val |= ucontrol->value.enumerated.item[0] & mask;
+	if (val != oval) {
+		snd_akm4xxx_write(ak, chip, addr, val);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * build AK4xxx controls
+ */
+
+static int build_dac_controls(struct snd_akm4xxx *ak)
+{
+	int idx, err, mixer_ch, num_stereo;
+	struct snd_kcontrol_new knew;
+
+	mixer_ch = 0;
+	for (idx = 0; idx < ak->num_dacs; ) {
+		/* mute control for Revolution 7.1 - AK4381 */
+		if (ak->type == SND_AK4381 
+				&&  ak->dac_info[mixer_ch].switch_name) {
+			memset(&knew, 0, sizeof(knew));
+			knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+			knew.count = 1;
+			knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+			knew.name = ak->dac_info[mixer_ch].switch_name;
+			knew.info = ak4xxx_switch_info;
+			knew.get = ak4xxx_switch_get;
+			knew.put = ak4xxx_switch_put;
+			knew.access = 0;
+			/* register 1, bit 0 (SMUTE): 0 = normal operation,
+			   1 = mute */
+			knew.private_value =
+				AK_COMPOSE(idx/2, 1, 0, 0) | AK_INVERT;
+			err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak));
+			if (err < 0)
+				return err;
+		}
+		memset(&knew, 0, sizeof(knew));
+		if (! ak->dac_info || ! ak->dac_info[mixer_ch].name) {
+			knew.name = "DAC Volume";
+			knew.index = mixer_ch + ak->idx_offset * 2;
+			num_stereo = 1;
+		} else {
+			knew.name = ak->dac_info[mixer_ch].name;
+			num_stereo = ak->dac_info[mixer_ch].num_channels;
+		}
+		knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+		knew.count = 1;
+		knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+		if (num_stereo == 2) {
+			knew.info = snd_akm4xxx_stereo_volume_info;
+			knew.get = snd_akm4xxx_stereo_volume_get;
+			knew.put = snd_akm4xxx_stereo_volume_put;
+		} else {
+			knew.info = snd_akm4xxx_volume_info;
+			knew.get = snd_akm4xxx_volume_get;
+			knew.put = snd_akm4xxx_volume_put;
+		}
+		switch (ak->type) {
+		case SND_AK4524:
+			/* register 6 & 7 */
+			knew.private_value =
+				AK_COMPOSE(idx/2, (idx%2) + 6, 0, 127) |
+				AK_VOL_CVT;
+			knew.tlv.p = db_scale_vol_datt;
+			break;
+		case SND_AK4528:
+			/* register 4 & 5 */
+			knew.private_value =
+				AK_COMPOSE(idx/2, (idx%2) + 4, 0, 127) |
+				AK_VOL_CVT;
+			knew.tlv.p = db_scale_vol_datt;
+			break;
+		case SND_AK4529: {
+			/* registers 2-7 and b,c */
+			int val = idx < 6 ? idx + 2 : (idx - 6) + 0xb;
+			knew.private_value =
+				AK_COMPOSE(0, val, 0, 255) | AK_INVERT;
+			knew.tlv.p = db_scale_8bit;
+			break;
+		}
+		case SND_AK4355:
+			/* register 4-9, chip #0 only */
+			knew.private_value = AK_COMPOSE(0, idx + 4, 0, 255);
+			knew.tlv.p = db_scale_8bit;
+			break;
+		case SND_AK4358: {
+			/* register 4-9 and 11-12, chip #0 only */
+			int  addr = idx < 6 ? idx + 4 : idx + 5;
+			knew.private_value =
+				AK_COMPOSE(0, addr, 0, 127) | AK_NEEDSMSB;
+			knew.tlv.p = db_scale_7bit;
+			break;
+		}
+		case SND_AK4381:
+			/* register 3 & 4 */
+			knew.private_value =
+				AK_COMPOSE(idx/2, (idx%2) + 3, 0, 255);
+			knew.tlv.p = db_scale_linear;
+			break;
+		case SND_AK4620:
+			/* register 6 & 7 */
+			knew.private_value =
+				AK_COMPOSE(idx/2, (idx%2) + 6, 0, 255);
+			knew.tlv.p = db_scale_linear;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak));
+		if (err < 0)
+			return err;
+
+		idx += num_stereo;
+		mixer_ch++;
+	}
+	return 0;
+}
+
+static int build_adc_controls(struct snd_akm4xxx *ak)
+{
+	int idx, err, mixer_ch, num_stereo, max_steps;
+	struct snd_kcontrol_new knew;
+
+	mixer_ch = 0;
+	if (ak->type == SND_AK4528)
+		return 0;	/* no controls */
+	for (idx = 0; idx < ak->num_adcs;) {
+		memset(&knew, 0, sizeof(knew));
+		if (! ak->adc_info || ! ak->adc_info[mixer_ch].name) {
+			knew.name = "ADC Volume";
+			knew.index = mixer_ch + ak->idx_offset * 2;
+			num_stereo = 1;
+		} else {
+			knew.name = ak->adc_info[mixer_ch].name;
+			num_stereo = ak->adc_info[mixer_ch].num_channels;
+		}
+		knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+		knew.count = 1;
+		knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+		if (num_stereo == 2) {
+			knew.info = snd_akm4xxx_stereo_volume_info;
+			knew.get = snd_akm4xxx_stereo_volume_get;
+			knew.put = snd_akm4xxx_stereo_volume_put;
+		} else {
+			knew.info = snd_akm4xxx_volume_info;
+			knew.get = snd_akm4xxx_volume_get;
+			knew.put = snd_akm4xxx_volume_put;
+		}
+		/* register 4 & 5 */
+		if (ak->type == SND_AK5365)
+			max_steps = 152;
+		else
+			max_steps = 164;
+		knew.private_value =
+			AK_COMPOSE(idx/2, (idx%2) + 4, 0, max_steps) |
+			AK_VOL_CVT | AK_IPGA;
+		knew.tlv.p = db_scale_vol_datt;
+		err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak));
+		if (err < 0)
+			return err;
+
+		if (ak->type == SND_AK5365 && (idx % 2) == 0) {
+			if (! ak->adc_info || 
+			    ! ak->adc_info[mixer_ch].switch_name) {
+				knew.name = "Capture Switch";
+				knew.index = mixer_ch + ak->idx_offset * 2;
+			} else
+				knew.name = ak->adc_info[mixer_ch].switch_name;
+			knew.info = ak4xxx_switch_info;
+			knew.get = ak4xxx_switch_get;
+			knew.put = ak4xxx_switch_put;
+			knew.access = 0;
+			/* register 2, bit 0 (SMUTE): 0 = normal operation,
+			   1 = mute */
+			knew.private_value =
+				AK_COMPOSE(idx/2, 2, 0, 0) | AK_INVERT;
+			err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak));
+			if (err < 0)
+				return err;
+
+			memset(&knew, 0, sizeof(knew));
+			knew.name = ak->adc_info[mixer_ch].selector_name;
+			if (!knew.name) {
+				knew.name = "Capture Channel";
+				knew.index = mixer_ch + ak->idx_offset * 2;
+			}
+
+			knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+			knew.info = ak4xxx_capture_source_info;
+			knew.get = ak4xxx_capture_source_get;
+			knew.put = ak4xxx_capture_source_put;
+			knew.access = 0;
+			/* input selector control: reg. 1, bits 0-2.
+			 * mis-use 'shift' to pass mixer_ch */
+			knew.private_value
+				= AK_COMPOSE(idx/2, 1, mixer_ch, 0x07);
+			err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak));
+			if (err < 0)
+				return err;
+		}
+
+		idx += num_stereo;
+		mixer_ch++;
+	}
+	return 0;
+}
+
+static int build_deemphasis(struct snd_akm4xxx *ak, int num_emphs)
+{
+	int idx, err;
+	struct snd_kcontrol_new knew;
+
+	for (idx = 0; idx < num_emphs; idx++) {
+		memset(&knew, 0, sizeof(knew));
+		knew.name = "Deemphasis";
+		knew.index = idx + ak->idx_offset;
+		knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+		knew.count = 1;
+		knew.info = snd_akm4xxx_deemphasis_info;
+		knew.get = snd_akm4xxx_deemphasis_get;
+		knew.put = snd_akm4xxx_deemphasis_put;
+		switch (ak->type) {
+		case SND_AK4524:
+		case SND_AK4528:
+		case SND_AK4620:
+			/* register 3 */
+			knew.private_value = AK_COMPOSE(idx, 3, 0, 0);
+			break;
+		case SND_AK4529: {
+			int shift = idx == 3 ? 6 : (2 - idx) * 2;
+			/* register 8 with shift */
+			knew.private_value = AK_COMPOSE(0, 8, shift, 0);
+			break;
+		}
+		case SND_AK4355:
+		case SND_AK4358:
+			knew.private_value = AK_COMPOSE(idx, 3, 0, 0);
+			break;
+		case SND_AK4381:
+			knew.private_value = AK_COMPOSE(idx, 1, 1, 0);
+			break;
+		default:
+			return -EINVAL;
+		}
+		err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_PROC_FS
+static void proc_regs_read(struct snd_info_entry *entry,
+		struct snd_info_buffer *buffer)
+{
+	struct snd_akm4xxx *ak = entry->private_data;
+	int reg, val, chip;
+	for (chip = 0; chip < ak->num_chips; chip++) {
+		for (reg = 0; reg < ak->total_regs; reg++) {
+			val =  snd_akm4xxx_get(ak, chip, reg);
+			snd_iprintf(buffer, "chip %d: 0x%02x = 0x%02x\n", chip,
+					reg, val);
+		}
+	}
+}
+
+static int proc_init(struct snd_akm4xxx *ak)
+{
+	struct snd_info_entry *entry;
+	int err;
+	err = snd_card_proc_new(ak->card, ak->name, &entry);
+	if (err < 0)
+		return err;
+	snd_info_set_text_ops(entry, ak, proc_regs_read);
+	return 0;
+}
+#else /* !CONFIG_PROC_FS */
+static int proc_init(struct snd_akm4xxx *ak) { return 0; }
+#endif
+
+int snd_akm4xxx_build_controls(struct snd_akm4xxx *ak)
+{
+	int err, num_emphs;
+
+	err = build_dac_controls(ak);
+	if (err < 0)
+		return err;
+
+	err = build_adc_controls(ak);
+	if (err < 0)
+		return err;
+	if (ak->type == SND_AK4355 || ak->type == SND_AK4358)
+		num_emphs = 1;
+	else if (ak->type == SND_AK4620)
+		num_emphs = 0;
+	else
+		num_emphs = ak->num_dacs / 2;
+	err = build_deemphasis(ak, num_emphs);
+	if (err < 0)
+		return err;
+	err = proc_init(ak);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+EXPORT_SYMBOL(snd_akm4xxx_build_controls);
+
+static int __init alsa_akm4xxx_module_init(void)
+{
+	return 0;
+}
+        
+static void __exit alsa_akm4xxx_module_exit(void)
+{
+}
+        
+module_init(alsa_akm4xxx_module_init)
+module_exit(alsa_akm4xxx_module_exit)
diff --git a/linux/sound/i2c/other/pt2258.c b/linux/sound/i2c/other/pt2258.c
new file mode 100644
index 0000000..797d3a6
--- /dev/null
+++ b/linux/sound/i2c/other/pt2258.c
@@ -0,0 +1,226 @@
+/*
+ *   ALSA Driver for the PT2258 volume controller.
+ *
+ *	Copyright (c) 2006  Jochen Voss <voss@seehuhn.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/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/i2c.h>
+#include <sound/pt2258.h>
+
+MODULE_AUTHOR("Jochen Voss <voss@seehuhn.de>");
+MODULE_DESCRIPTION("PT2258 volume controller (Princeton Technology Corp.)");
+MODULE_LICENSE("GPL");
+
+#define PT2258_CMD_RESET 0xc0
+#define PT2258_CMD_UNMUTE 0xf8
+#define PT2258_CMD_MUTE 0xf9
+
+static const unsigned char pt2258_channel_code[12] = {
+	0x80, 0x90,		/* channel 1: -10dB, -1dB */
+	0x40, 0x50,		/* channel 2: -10dB, -1dB */
+	0x00, 0x10,		/* channel 3: -10dB, -1dB */
+	0x20, 0x30,		/* channel 4: -10dB, -1dB */
+	0x60, 0x70,		/* channel 5: -10dB, -1dB */
+	0xa0, 0xb0		/* channel 6: -10dB, -1dB */
+};
+
+int snd_pt2258_reset(struct snd_pt2258 *pt)
+{
+	unsigned char bytes[2];
+	int i;
+
+	/* reset chip */
+	bytes[0] = PT2258_CMD_RESET;
+	snd_i2c_lock(pt->i2c_bus);
+	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1)
+		goto __error;
+	snd_i2c_unlock(pt->i2c_bus);
+
+	/* mute all channels */
+	pt->mute = 1;
+	bytes[0] = PT2258_CMD_MUTE;
+	snd_i2c_lock(pt->i2c_bus);
+	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1)
+		goto __error;
+	snd_i2c_unlock(pt->i2c_bus);
+
+	/* set all channels to 0dB */
+	for (i = 0; i < 6; ++i)
+		pt->volume[i] = 0;
+	bytes[0] = 0xd0;
+	bytes[1] = 0xe0;
+	snd_i2c_lock(pt->i2c_bus);
+	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2)
+		goto __error;
+	snd_i2c_unlock(pt->i2c_bus);
+
+	return 0;
+
+      __error:
+	snd_i2c_unlock(pt->i2c_bus);
+	snd_printk(KERN_ERR "PT2258 reset failed\n");
+	return -EIO;
+}
+
+static int pt2258_stereo_volume_info(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 79;
+	return 0;
+}
+
+static int pt2258_stereo_volume_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pt2258 *pt = kcontrol->private_data;
+	int base = kcontrol->private_value;
+
+	/* chip does not support register reads */
+	ucontrol->value.integer.value[0] = 79 - pt->volume[base];
+	ucontrol->value.integer.value[1] = 79 - pt->volume[base + 1];
+	return 0;
+}
+
+static int pt2258_stereo_volume_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pt2258 *pt = kcontrol->private_data;
+	int base = kcontrol->private_value;
+	unsigned char bytes[2];
+	int val0, val1;
+
+	val0 = 79 - ucontrol->value.integer.value[0];
+	val1 = 79 - ucontrol->value.integer.value[1];
+	if (val0 < 0 || val0 > 79 || val1 < 0 || val1 > 79)
+		return -EINVAL;
+	if (val0 == pt->volume[base] && val1 == pt->volume[base + 1])
+		return 0;
+
+	pt->volume[base] = val0;
+	bytes[0] = pt2258_channel_code[2 * base] | (val0 / 10);
+	bytes[1] = pt2258_channel_code[2 * base + 1] | (val0 % 10);
+	snd_i2c_lock(pt->i2c_bus);
+	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2)
+		goto __error;
+	snd_i2c_unlock(pt->i2c_bus);
+
+	pt->volume[base + 1] = val1;
+	bytes[0] = pt2258_channel_code[2 * base + 2] | (val1 / 10);
+	bytes[1] = pt2258_channel_code[2 * base + 3] | (val1 % 10);
+	snd_i2c_lock(pt->i2c_bus);
+	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2)
+		goto __error;
+	snd_i2c_unlock(pt->i2c_bus);
+
+	return 1;
+
+      __error:
+	snd_i2c_unlock(pt->i2c_bus);
+	snd_printk(KERN_ERR "PT2258 access failed\n");
+	return -EIO;
+}
+
+#define pt2258_switch_info	snd_ctl_boolean_mono_info
+
+static int pt2258_switch_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pt2258 *pt = kcontrol->private_data;
+
+	ucontrol->value.integer.value[0] = !pt->mute;
+	return 0;
+}
+
+static int pt2258_switch_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pt2258 *pt = kcontrol->private_data;
+	unsigned char bytes[2];
+	int val;
+
+	val = !ucontrol->value.integer.value[0];
+	if (pt->mute == val)
+		return 0;
+
+	pt->mute = val;
+	bytes[0] = val ? PT2258_CMD_MUTE : PT2258_CMD_UNMUTE;
+	snd_i2c_lock(pt->i2c_bus);
+	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1)
+		goto __error;
+	snd_i2c_unlock(pt->i2c_bus);
+
+	return 1;
+
+      __error:
+	snd_i2c_unlock(pt->i2c_bus);
+	snd_printk(KERN_ERR "PT2258 access failed 2\n");
+	return -EIO;
+}
+
+static const DECLARE_TLV_DB_SCALE(pt2258_db_scale, -7900, 100, 0);
+
+int snd_pt2258_build_controls(struct snd_pt2258 *pt)
+{
+	struct snd_kcontrol_new knew;
+	char *names[3] = {
+		"Mic Loopback Playback Volume",
+		"Line Loopback Playback Volume",
+		"CD Loopback Playback Volume"
+	};
+	int i, err;
+
+	for (i = 0; i < 3; ++i) {
+		memset(&knew, 0, sizeof(knew));
+		knew.name = names[i];
+		knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+		knew.count = 1;
+		knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		    SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+		knew.private_value = 2 * i;
+		knew.info = pt2258_stereo_volume_info;
+		knew.get = pt2258_stereo_volume_get;
+		knew.put = pt2258_stereo_volume_put;
+		knew.tlv.p = pt2258_db_scale;
+
+		err = snd_ctl_add(pt->card, snd_ctl_new1(&knew, pt));
+		if (err < 0)
+			return err;
+	}
+
+	memset(&knew, 0, sizeof(knew));
+	knew.name = "Loopback Switch";
+	knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	knew.info = pt2258_switch_info;
+	knew.get = pt2258_switch_get;
+	knew.put = pt2258_switch_put;
+	knew.access = 0;
+	err = snd_ctl_add(pt->card, snd_ctl_new1(&knew, pt));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_pt2258_reset);
+EXPORT_SYMBOL(snd_pt2258_build_controls);
diff --git a/linux/sound/i2c/other/tea575x-tuner.c b/linux/sound/i2c/other/tea575x-tuner.c
new file mode 100644
index 0000000..ee538f1
--- /dev/null
+++ b/linux/sound/i2c/other/tea575x-tuner.c
@@ -0,0 +1,365 @@
+/*
+ *   ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips
+ *
+ *	Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/version.h>
+#include <sound/core.h>
+#include <sound/tea575x-tuner.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips");
+MODULE_LICENSE("GPL");
+
+static int radio_nr = -1;
+module_param(radio_nr, int, 0);
+
+#define RADIO_VERSION KERNEL_VERSION(0, 0, 2)
+#define FREQ_LO		 (87 * 16000)
+#define FREQ_HI		(108 * 16000)
+
+/*
+ * definitions
+ */
+
+#define TEA575X_BIT_SEARCH	(1<<24)		/* 1 = search action, 0 = tuned */
+#define TEA575X_BIT_UPDOWN	(1<<23)		/* 0 = search down, 1 = search up */
+#define TEA575X_BIT_MONO	(1<<22)		/* 0 = stereo, 1 = mono */
+#define TEA575X_BIT_BAND_MASK	(3<<20)
+#define TEA575X_BIT_BAND_FM	(0<<20)
+#define TEA575X_BIT_BAND_MW	(1<<20)
+#define TEA575X_BIT_BAND_LW	(1<<21)
+#define TEA575X_BIT_BAND_SW	(1<<22)
+#define TEA575X_BIT_PORT_0	(1<<19)		/* user bit */
+#define TEA575X_BIT_PORT_1	(1<<18)		/* user bit */
+#define TEA575X_BIT_SEARCH_MASK	(3<<16)		/* search level */
+#define TEA575X_BIT_SEARCH_5_28	     (0<<16)	/* FM >5uV, AM >28uV */
+#define TEA575X_BIT_SEARCH_10_40     (1<<16)	/* FM >10uV, AM > 40uV */
+#define TEA575X_BIT_SEARCH_30_63     (2<<16)	/* FM >30uV, AM > 63uV */
+#define TEA575X_BIT_SEARCH_150_1000  (3<<16)	/* FM > 150uV, AM > 1000uV */
+#define TEA575X_BIT_DUMMY	(1<<15)		/* buffer */
+#define TEA575X_BIT_FREQ_MASK	0x7fff
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+	{
+		.id            = V4L2_CID_AUDIO_MUTE,
+		.name          = "Mute",
+		.minimum       = 0,
+		.maximum       = 1,
+		.default_value = 1,
+		.type          = V4L2_CTRL_TYPE_BOOLEAN,
+	}
+};
+
+/*
+ * lowlevel part
+ */
+
+static void snd_tea575x_set_freq(struct snd_tea575x *tea)
+{
+	unsigned long freq;
+
+	freq = tea->freq / 16;		/* to kHz */
+	if (freq > 108000)
+		freq = 108000;
+	if (freq < 87000)
+		freq = 87000;
+	/* crystal fixup */
+	if (tea->tea5759)
+		freq -= tea->freq_fixup;
+	else
+		freq += tea->freq_fixup;
+	/* freq /= 12.5 */
+	freq *= 10;
+	freq /= 125;
+
+	tea->val &= ~TEA575X_BIT_FREQ_MASK;
+	tea->val |= freq & TEA575X_BIT_FREQ_MASK;
+	tea->ops->write(tea, tea->val);
+}
+
+/*
+ * Linux Video interface
+ */
+
+static int vidioc_querycap(struct file *file, void  *priv,
+					struct v4l2_capability *v)
+{
+	struct snd_tea575x *tea = video_drvdata(file);
+
+	strcpy(v->card, tea->tea5759 ? "TEA5759" : "TEA5757");
+	strlcpy(v->driver, "tea575x-tuner", sizeof(v->driver));
+	strlcpy(v->card, "Maestro Radio", sizeof(v->card));
+	sprintf(v->bus_info, "PCI");
+	v->version = RADIO_VERSION;
+	v->capabilities = V4L2_CAP_TUNER;
+	return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+					struct v4l2_tuner *v)
+{
+	if (v->index > 0)
+		return -EINVAL;
+
+	strcpy(v->name, "FM");
+	v->type = V4L2_TUNER_RADIO;
+	v->rangelow = FREQ_LO;
+	v->rangehigh = FREQ_HI;
+	v->rxsubchans = V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO;
+	v->capability = V4L2_TUNER_CAP_LOW;
+	v->audmode = V4L2_TUNER_MODE_MONO;
+	v->signal = 0xffff;
+	return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+					struct v4l2_tuner *v)
+{
+	if (v->index > 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+					struct v4l2_frequency *f)
+{
+	struct snd_tea575x *tea = video_drvdata(file);
+
+	f->type = V4L2_TUNER_RADIO;
+	f->frequency = tea->freq;
+	return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+					struct v4l2_frequency *f)
+{
+	struct snd_tea575x *tea = video_drvdata(file);
+
+	if (f->frequency < FREQ_LO || f->frequency > FREQ_HI)
+		return -EINVAL;
+
+	tea->freq = f->frequency;
+
+	snd_tea575x_set_freq(tea);
+
+	return 0;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+					struct v4l2_audio *a)
+{
+	if (a->index > 1)
+		return -EINVAL;
+
+	strcpy(a->name, "Radio");
+	a->capability = V4L2_AUDCAP_STEREO;
+	return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+					struct v4l2_audio *a)
+{
+	if (a->index != 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+					struct v4l2_queryctrl *qc)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+		if (qc->id && qc->id == radio_qctrl[i].id) {
+			memcpy(qc, &(radio_qctrl[i]),
+						sizeof(*qc));
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+					struct v4l2_control *ctrl)
+{
+	struct snd_tea575x *tea = video_drvdata(file);
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		if (tea->ops->mute) {
+			ctrl->value = tea->mute;
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+					struct v4l2_control *ctrl)
+{
+	struct snd_tea575x *tea = video_drvdata(file);
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		if (tea->ops->mute) {
+			tea->ops->mute(tea, ctrl->value);
+			tea->mute = ctrl->value;
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+	*i = 0;
+	return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+	if (i != 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int snd_tea575x_exclusive_open(struct file *file)
+{
+	struct snd_tea575x *tea = video_drvdata(file);
+
+	return test_and_set_bit(0, &tea->in_use) ? -EBUSY : 0;
+}
+
+static int snd_tea575x_exclusive_release(struct file *file)
+{
+	struct snd_tea575x *tea = video_drvdata(file);
+
+	clear_bit(0, &tea->in_use);
+	return 0;
+}
+
+static const struct v4l2_file_operations tea575x_fops = {
+	.owner		= THIS_MODULE,
+	.open           = snd_tea575x_exclusive_open,
+	.release        = snd_tea575x_exclusive_release,
+	.ioctl		= video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops tea575x_ioctl_ops = {
+	.vidioc_querycap    = vidioc_querycap,
+	.vidioc_g_tuner     = vidioc_g_tuner,
+	.vidioc_s_tuner     = vidioc_s_tuner,
+	.vidioc_g_audio     = vidioc_g_audio,
+	.vidioc_s_audio     = vidioc_s_audio,
+	.vidioc_g_input     = vidioc_g_input,
+	.vidioc_s_input     = vidioc_s_input,
+	.vidioc_g_frequency = vidioc_g_frequency,
+	.vidioc_s_frequency = vidioc_s_frequency,
+	.vidioc_queryctrl   = vidioc_queryctrl,
+	.vidioc_g_ctrl      = vidioc_g_ctrl,
+	.vidioc_s_ctrl      = vidioc_s_ctrl,
+};
+
+static struct video_device tea575x_radio = {
+	.name           = "tea575x-tuner",
+	.fops           = &tea575x_fops,
+	.ioctl_ops 	= &tea575x_ioctl_ops,
+	.release	= video_device_release,
+};
+
+/*
+ * initialize all the tea575x chips
+ */
+void snd_tea575x_init(struct snd_tea575x *tea)
+{
+	int retval;
+	unsigned int val;
+	struct video_device *tea575x_radio_inst;
+
+	val = tea->ops->read(tea);
+	if (val == 0x1ffffff || val == 0) {
+		snd_printk(KERN_ERR
+			   "tea575x-tuner: Cannot find TEA575x chip\n");
+		return;
+	}
+
+	tea->in_use = 0;
+	tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_10_40;
+	tea->freq = 90500 * 16;		/* 90.5Mhz default */
+
+	tea575x_radio_inst = video_device_alloc();
+	if (tea575x_radio_inst == NULL) {
+		printk(KERN_ERR "tea575x-tuner: not enough memory\n");
+		return;
+	}
+
+	memcpy(tea575x_radio_inst, &tea575x_radio, sizeof(tea575x_radio));
+
+	strcpy(tea575x_radio.name, tea->tea5759 ?
+				   "TEA5759 radio" : "TEA5757 radio");
+
+	video_set_drvdata(tea575x_radio_inst, tea);
+
+	retval = video_register_device(tea575x_radio_inst,
+				       VFL_TYPE_RADIO, radio_nr);
+	if (retval) {
+		printk(KERN_ERR "tea575x-tuner: can't register video device!\n");
+		kfree(tea575x_radio_inst);
+		return;
+	}
+
+	snd_tea575x_set_freq(tea);
+
+	/* mute on init */
+	if (tea->ops->mute) {
+		tea->ops->mute(tea, 1);
+		tea->mute = 1;
+	}
+	tea->vd = tea575x_radio_inst;
+}
+
+void snd_tea575x_exit(struct snd_tea575x *tea)
+{
+	if (tea->vd) {
+		video_unregister_device(tea->vd);
+		tea->vd = NULL;
+	}
+}
+
+static int __init alsa_tea575x_module_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_tea575x_module_exit(void)
+{
+}
+
+module_init(alsa_tea575x_module_init)
+module_exit(alsa_tea575x_module_exit)
+
+EXPORT_SYMBOL(snd_tea575x_init);
+EXPORT_SYMBOL(snd_tea575x_exit);
diff --git a/linux/sound/i2c/tea6330t.c b/linux/sound/i2c/tea6330t.c
new file mode 100644
index 0000000..0e3a9f2
--- /dev/null
+++ b/linux/sound/i2c/tea6330t.c
@@ -0,0 +1,385 @@
+/*
+ *  Routines for control of the TEA6330T circuit via i2c bus
+ *  Sound fader control circuit for car radios by Philips Semiconductors
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tea6330t.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Routines for control of the TEA6330T circuit via i2c bus");
+MODULE_LICENSE("GPL");
+
+#define TEA6330T_ADDR			(0x80>>1) /* fixed address */
+
+#define TEA6330T_SADDR_VOLUME_LEFT	0x00	/* volume left */
+#define TEA6330T_SADDR_VOLUME_RIGHT	0x01	/* volume right */
+#define TEA6330T_SADDR_BASS		0x02	/* bass control */
+#define TEA6330T_SADDR_TREBLE		0x03	/* treble control */
+#define TEA6330T_SADDR_FADER		0x04	/* fader control */
+#define   TEA6330T_MFN			0x20	/* mute control for selected channels */
+#define   TEA6330T_FCH			0x10	/* select fader channels - front or rear */
+#define TEA6330T_SADDR_AUDIO_SWITCH	0x05	/* audio switch */
+#define   TEA6330T_GMU			0x80	/* mute control, general mute */
+#define   TEA6330T_EQN			0x40	/* equalizer switchover (0=equalizer-on) */
+
+
+struct tea6330t {
+	struct snd_i2c_device *device;
+	struct snd_i2c_bus *bus;
+	int equalizer;
+	int fader;
+	unsigned char regs[8];
+	unsigned char mleft, mright;
+	unsigned char bass, treble;
+	unsigned char max_bass, max_treble;
+};
+
+
+int snd_tea6330t_detect(struct snd_i2c_bus *bus, int equalizer)
+{
+	int res;
+
+	snd_i2c_lock(bus);
+	res = snd_i2c_probeaddr(bus, TEA6330T_ADDR);
+	snd_i2c_unlock(bus);
+	return res;
+}
+
+#if 0
+static void snd_tea6330t_set(struct tea6330t *tea,
+			     unsigned char addr, unsigned char value)
+{
+#if 0
+	printk(KERN_DEBUG "set - 0x%x/0x%x\n", addr, value);
+#endif
+	snd_i2c_write(tea->bus, TEA6330T_ADDR, addr, value, 1);
+}
+#endif
+
+#define TEA6330T_MASTER_VOLUME(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_tea6330t_info_master_volume, \
+  .get = snd_tea6330t_get_master_volume, .put = snd_tea6330t_put_master_volume }
+
+static int snd_tea6330t_info_master_volume(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 43;
+	return 0;
+}
+
+static int snd_tea6330t_get_master_volume(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct tea6330t *tea = snd_kcontrol_chip(kcontrol);
+	
+	snd_i2c_lock(tea->bus);
+	ucontrol->value.integer.value[0] = tea->mleft - 0x14;
+	ucontrol->value.integer.value[1] = tea->mright - 0x14;
+	snd_i2c_unlock(tea->bus);
+	return 0;
+}
+
+static int snd_tea6330t_put_master_volume(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct tea6330t *tea = snd_kcontrol_chip(kcontrol);
+	int change, count, err;
+	unsigned char bytes[3];
+	unsigned char val1, val2;
+	
+	val1 = (ucontrol->value.integer.value[0] % 44) + 0x14;
+	val2 = (ucontrol->value.integer.value[1] % 44) + 0x14;
+	snd_i2c_lock(tea->bus);
+	change = val1 != tea->mleft || val2 != tea->mright;
+	tea->mleft = val1;
+	tea->mright = val2;
+	count = 0;
+	if (tea->regs[TEA6330T_SADDR_VOLUME_LEFT] != 0) {
+		bytes[count++] = TEA6330T_SADDR_VOLUME_LEFT;
+		bytes[count++] = tea->regs[TEA6330T_SADDR_VOLUME_LEFT] = tea->mleft;
+	}
+	if (tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] != 0) {
+		if (count == 0)
+			bytes[count++] = TEA6330T_SADDR_VOLUME_RIGHT;
+		bytes[count++] = tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] = tea->mright;
+	}
+	if (count > 0) {
+		if ((err = snd_i2c_sendbytes(tea->device, bytes, count)) < 0)
+			change = err;
+	}
+	snd_i2c_unlock(tea->bus);
+	return change;
+}
+
+#define TEA6330T_MASTER_SWITCH(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_tea6330t_info_master_switch, \
+  .get = snd_tea6330t_get_master_switch, .put = snd_tea6330t_put_master_switch }
+
+#define snd_tea6330t_info_master_switch		snd_ctl_boolean_stereo_info
+
+static int snd_tea6330t_get_master_switch(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct tea6330t *tea = snd_kcontrol_chip(kcontrol);
+	
+	snd_i2c_lock(tea->bus);
+	ucontrol->value.integer.value[0] = tea->regs[TEA6330T_SADDR_VOLUME_LEFT] == 0 ? 0 : 1;
+	ucontrol->value.integer.value[1] = tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] == 0 ? 0 : 1;
+	snd_i2c_unlock(tea->bus);
+	return 0;
+}
+
+static int snd_tea6330t_put_master_switch(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct tea6330t *tea = snd_kcontrol_chip(kcontrol);
+	int change, err;
+	unsigned char bytes[3];
+	unsigned char oval1, oval2, val1, val2;
+	
+	val1 = ucontrol->value.integer.value[0] & 1;
+	val2 = ucontrol->value.integer.value[1] & 1;
+	snd_i2c_lock(tea->bus);
+	oval1 = tea->regs[TEA6330T_SADDR_VOLUME_LEFT] == 0 ? 0 : 1;
+	oval2 = tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] == 0 ? 0 : 1;
+	change = val1 != oval1 || val2 != oval2;
+	tea->regs[TEA6330T_SADDR_VOLUME_LEFT] = val1 ? tea->mleft : 0;
+	tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] = val2 ? tea->mright : 0;
+	bytes[0] = TEA6330T_SADDR_VOLUME_LEFT;
+	bytes[1] = tea->regs[TEA6330T_SADDR_VOLUME_LEFT];
+	bytes[2] = tea->regs[TEA6330T_SADDR_VOLUME_RIGHT];
+	if ((err = snd_i2c_sendbytes(tea->device, bytes, 3)) < 0)
+		change = err;
+	snd_i2c_unlock(tea->bus);
+	return change;
+}
+
+#define TEA6330T_BASS(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_tea6330t_info_bass, \
+  .get = snd_tea6330t_get_bass, .put = snd_tea6330t_put_bass }
+
+static int snd_tea6330t_info_bass(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct tea6330t *tea = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = tea->max_bass;
+	return 0;
+}
+
+static int snd_tea6330t_get_bass(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct tea6330t *tea = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = tea->bass;
+	return 0;
+}
+
+static int snd_tea6330t_put_bass(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct tea6330t *tea = snd_kcontrol_chip(kcontrol);
+	int change, err;
+	unsigned char bytes[2];
+	unsigned char val1;
+	
+	val1 = ucontrol->value.integer.value[0] % (tea->max_bass + 1);
+	snd_i2c_lock(tea->bus);
+	tea->bass = val1;
+	val1 += tea->equalizer ? 7 : 3;
+	change = tea->regs[TEA6330T_SADDR_BASS] != val1;
+	bytes[0] = TEA6330T_SADDR_BASS;
+	bytes[1] = tea->regs[TEA6330T_SADDR_BASS] = val1;
+	if ((err = snd_i2c_sendbytes(tea->device, bytes, 2)) < 0)
+		change = err;
+	snd_i2c_unlock(tea->bus);
+	return change;
+}
+
+#define TEA6330T_TREBLE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_tea6330t_info_treble, \
+  .get = snd_tea6330t_get_treble, .put = snd_tea6330t_put_treble }
+
+static int snd_tea6330t_info_treble(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	struct tea6330t *tea = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = tea->max_treble;
+	return 0;
+}
+
+static int snd_tea6330t_get_treble(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct tea6330t *tea = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = tea->treble;
+	return 0;
+}
+
+static int snd_tea6330t_put_treble(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct tea6330t *tea = snd_kcontrol_chip(kcontrol);
+	int change, err;
+	unsigned char bytes[2];
+	unsigned char val1;
+	
+	val1 = ucontrol->value.integer.value[0] % (tea->max_treble + 1);
+	snd_i2c_lock(tea->bus);
+	tea->treble = val1;
+	val1 += 3;
+	change = tea->regs[TEA6330T_SADDR_TREBLE] != val1;
+	bytes[0] = TEA6330T_SADDR_TREBLE;
+	bytes[1] = tea->regs[TEA6330T_SADDR_TREBLE] = val1;
+	if ((err = snd_i2c_sendbytes(tea->device, bytes, 2)) < 0)
+		change = err;
+	snd_i2c_unlock(tea->bus);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_tea6330t_controls[] = {
+TEA6330T_MASTER_SWITCH("Master Playback Switch", 0),
+TEA6330T_MASTER_VOLUME("Master Playback Volume", 0),
+TEA6330T_BASS("Tone Control - Bass", 0),
+TEA6330T_TREBLE("Tone Control - Treble", 0)
+};
+
+static void snd_tea6330_free(struct snd_i2c_device *device)
+{
+	kfree(device->private_data);
+}
+                                        
+int snd_tea6330t_update_mixer(struct snd_card *card,
+			      struct snd_i2c_bus *bus,
+			      int equalizer, int fader)
+{
+	struct snd_i2c_device *device;
+	struct tea6330t *tea;
+	struct snd_kcontrol_new *knew;
+	unsigned int idx;
+	int err = -ENOMEM;
+	u8 default_treble, default_bass;
+	unsigned char bytes[7];
+
+	tea = kzalloc(sizeof(*tea), GFP_KERNEL);
+	if (tea == NULL)
+		return -ENOMEM;
+	if ((err = snd_i2c_device_create(bus, "TEA6330T", TEA6330T_ADDR, &device)) < 0) {
+		kfree(tea);
+		return err;
+	}
+	tea->device = device;
+	tea->bus = bus;
+	tea->equalizer = equalizer;
+	tea->fader = fader;
+	device->private_data = tea;
+	device->private_free = snd_tea6330_free;
+
+	snd_i2c_lock(bus);
+
+	/* turn fader off and handle equalizer */
+	tea->regs[TEA6330T_SADDR_FADER] = 0x3f;
+	tea->regs[TEA6330T_SADDR_AUDIO_SWITCH] = equalizer ? 0 : TEA6330T_EQN;
+	/* initialize mixer */
+	if (!tea->equalizer) {
+		tea->max_bass = 9;
+		tea->max_treble = 8;
+		default_bass = 3 + 4;
+		tea->bass = 4;
+		default_treble = 3 + 4;
+		tea->treble = 4;
+	} else {
+		tea->max_bass = 5;
+		tea->max_treble = 0;
+		default_bass = 7 + 4;
+		tea->bass = 4;
+		default_treble = 3;
+		tea->treble = 0;
+	}
+	tea->mleft = tea->mright = 0x14;
+	tea->regs[TEA6330T_SADDR_BASS] = default_bass;
+	tea->regs[TEA6330T_SADDR_TREBLE] = default_treble;
+
+	/* compose I2C message and put the hardware to initial state */
+	bytes[0] = TEA6330T_SADDR_VOLUME_LEFT;
+	for (idx = 0; idx < 6; idx++)
+		bytes[idx+1] = tea->regs[idx];
+	if ((err = snd_i2c_sendbytes(device, bytes, 7)) < 0)
+		goto __error;
+
+	strcat(card->mixername, ",TEA6330T");
+	if ((err = snd_component_add(card, "TEA6330T")) < 0)
+		goto __error;
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_tea6330t_controls); idx++) {
+		knew = &snd_tea6330t_controls[idx];
+		if (tea->treble == 0 && !strcmp(knew->name, "Tone Control - Treble"))
+			continue;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(knew, tea))) < 0)
+			goto __error;
+	}
+
+	snd_i2c_unlock(bus);
+	return 0;
+	
+      __error:
+      	snd_i2c_unlock(bus);
+      	snd_i2c_device_free(device);
+      	return err;
+}
+
+EXPORT_SYMBOL(snd_tea6330t_detect);
+EXPORT_SYMBOL(snd_tea6330t_update_mixer);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_tea6330t_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_tea6330t_exit(void)
+{
+}
+
+module_init(alsa_tea6330t_init)
+module_exit(alsa_tea6330t_exit)
diff --git a/linux/sound/isa/Kconfig b/linux/sound/isa/Kconfig
new file mode 100644
index 0000000..52064cf
--- /dev/null
+++ b/linux/sound/isa/Kconfig
@@ -0,0 +1,446 @@
+# ALSA ISA drivers
+
+config SND_WSS_LIB
+        tristate
+        select SND_PCM
+
+config SND_SB_COMMON
+        tristate
+
+config SND_SB8_DSP
+        tristate
+        select SND_PCM
+        select SND_SB_COMMON
+
+config SND_SB16_DSP
+        tristate
+        select SND_PCM
+        select SND_SB_COMMON
+
+menuconfig SND_ISA
+	bool "ISA sound devices"
+	depends on ISA && ISA_DMA_API
+	default y
+	help
+	  Support for sound devices connected via the ISA bus.
+
+if SND_ISA
+
+config SND_ADLIB
+	tristate "AdLib FM card"
+	select SND_OPL3_LIB
+	help
+	  Say Y here to include support for AdLib FM cards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-adlib.
+
+config SND_AD1816A
+	tristate "Analog Devices SoundPort AD1816A"
+	depends on PNP
+	select ISAPNP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for Analog Devices SoundPort
+	  AD1816A or compatible sound chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ad1816a.
+
+config SND_AD1848
+	tristate "Generic AD1848/CS4248 driver"
+	select SND_WSS_LIB
+	help
+	  Say Y here to include support for AD1848 (Analog Devices) or
+	  CS4248 (Cirrus Logic - Crystal Semiconductors) chips.
+	  
+	  For newer chips from Cirrus Logic, use the CS4231 or CS4232+
+	  drivers.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ad1848.
+
+config SND_ALS100
+	tristate "Diamond Tech. DT-019x and Avance Logic ALSxxx"
+	depends on PNP
+	select ISAPNP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_SB16_DSP
+	help
+	  Say Y here to include support for soundcards based on the
+	  Diamond Technologies DT-019X or Avance Logic chips: ALS007,
+	  ALS100, ALS110, ALS120 and ALS200 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-als100.
+
+config SND_AZT1605
+	tristate "Aztech AZT1605 Driver"
+	depends on SND
+	select SND_WSS_LIB
+	select SND_MPU401_UART
+	select SND_OPL3_LIB
+	help
+	  Say Y here to include support for Aztech Sound Galaxy cards
+	  based on the AZT1605 chipset.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-azt1605.
+
+config SND_AZT2316
+	tristate "Aztech AZT2316 Driver"
+	depends on SND
+	select SND_WSS_LIB
+	select SND_MPU401_UART
+	select SND_OPL3_LIB
+	help
+	  Say Y here to include support for Aztech Sound Galaxy cards
+	  based on the AZT2316 chipset.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-azt2316.
+
+config SND_AZT2320
+	tristate "Aztech Systems AZT2320"
+	depends on PNP
+	select ISAPNP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	help
+	  Say Y here to include support for soundcards based on the
+	  Aztech Systems AZT2320 chip.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-azt2320.
+
+config SND_CMI8330
+	tristate "C-Media CMI8330"
+	select SND_WSS_LIB
+	select SND_SB16_DSP
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	help
+	  Say Y here to include support for soundcards based on the
+	  C-Media CMI8330 chip.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cmi8330.
+
+config SND_CS4231
+	tristate "Generic Cirrus Logic CS4231 driver"
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	help
+	  Say Y here to include support for CS4231 chips from Cirrus
+	  Logic - Crystal Semiconductors.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cs4231.
+
+config SND_CS4236
+	tristate "Generic Cirrus Logic CS4232/CS4236+ driver"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	help
+	  Say Y to include support for CS4232,CS4235,CS4236,CS4237B,
+	  CS4238B,CS4239 chips from Cirrus Logic - Crystal
+	  Semiconductors.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cs4236.
+
+config SND_ES1688
+	tristate "Generic ESS ES688/ES1688 and ES968 PnP driver"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for ESS AudioDrive ES688 or
+	  ES1688 chips. Also, this module support cards with ES968 PnP chip.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-es1688.
+
+config SND_ES18XX
+	tristate "Generic ESS ES18xx driver"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for ESS AudioDrive ES18xx chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-es18xx.
+
+config SND_SC6000
+	tristate "Gallant SC-6000/6600/7000 and Audio Excel DSP 16"
+	depends on HAS_IOPORT
+	select SND_WSS_LIB
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	help
+	  Say Y here to include support for Gallant SC-6000, SC-6600, SC-7000
+	  cards and clones:
+	  Audio Excel DSP 16 and Zoltrix AV302.
+
+	  These cards are based on CompuMedia ASC-9308 or ASC-9408 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sc6000.
+
+config SND_GUSCLASSIC
+	tristate "Gravis UltraSound Classic"
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say Y here to include support for Gravis UltraSound Classic
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-gusclassic.
+
+config SND_GUSEXTREME
+	tristate "Gravis UltraSound Extreme"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y here to include support for Gravis UltraSound Extreme
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-gusextreme.
+
+config SND_GUSMAX
+	tristate "Gravis UltraSound MAX"
+	select SND_RAWMIDI
+	select SND_WSS_LIB
+	help
+	  Say Y here to include support for Gravis UltraSound MAX
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-gusmax.
+
+config SND_INTERWAVE
+	tristate "AMD InterWave, Gravis UltraSound PnP"
+	depends on PNP
+	select SND_RAWMIDI
+	select SND_WSS_LIB
+	help
+	  Say Y here to include support for AMD InterWave based
+	  soundcards (Gravis UltraSound Plug & Play, STB SoundRage32,
+	  MED3210, Dynasonic Pro, Panasonic PCA761AW).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-interwave.
+
+config SND_INTERWAVE_STB
+	tristate "AMD InterWave + TEA6330T (UltraSound 32-Pro)"
+	depends on PNP
+	select SND_RAWMIDI
+	select SND_WSS_LIB
+	help
+	  Say Y here to include support for AMD InterWave based
+	  soundcards with a TEA6330T bass and treble regulator
+	  (UltraSound 32-Pro).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-interwave-stb.
+
+config SND_JAZZ16
+	tristate "Media Vision Jazz16 card and compatibles"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_SB8_DSP
+	help
+	  Say Y here to include support for soundcards based on the
+	  Media Vision Jazz16 chipset: digital chip MVD1216 (Jazz16),
+	  codec MVA416 (CS4216) and mixer MVA514 (ICS2514).
+	  Media Vision's Jazz16 cards were sold under names Pro Sonic 16,
+	  Premium 3-D and Pro 3-D. There were also OEMs cards with the
+	  Jazz16 chipset.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-jazz16.
+
+config SND_OPL3SA2
+	tristate "Yamaha OPL3-SA2/SA3"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	help
+	  Say Y here to include support for Yamaha OPL3-SA2 and OPL3-SA3
+	  chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-opl3sa2.
+
+config SND_OPTI92X_AD1848
+	tristate "OPTi 82C92x - AD1848"
+	select SND_OPL3_LIB
+	select SND_OPL4_LIB
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	help
+	  Say Y here to include support for soundcards based on Opti
+	  82C92x or OTI-601 chips and using an AD1848 codec.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-opti92x-ad1848.
+
+config SND_OPTI92X_CS4231
+	tristate "OPTi 82C92x - CS4231"
+	select SND_OPL3_LIB
+	select SND_OPL4_LIB
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	help
+	  Say Y here to include support for soundcards based on Opti
+	  82C92x chips and using a CS4231 codec.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-opti92x-cs4231.
+
+config SND_OPTI93X
+	tristate "OPTi 82C93x"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	help
+	  Say Y here to include support for soundcards based on Opti
+	  82C93x chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-opti93x.
+
+config SND_MIRO
+	tristate "Miro miroSOUND PCM1pro/PCM12/PCM20radio driver"
+	select SND_OPL4_LIB
+	select SND_WSS_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Miro miroSOUND PCM1 pro, 
+	  miroSOUND PCM12 and miroSOUND PCM20 Radio soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-miro.
+
+config SND_SB8
+	tristate "Sound Blaster 1.0/2.0/Pro (8-bit)"
+	select SND_OPL3_LIB
+	select SND_RAWMIDI
+	select SND_SB8_DSP
+	help
+	  Say Y here to include support for Creative Sound Blaster 1.0/
+	  2.0/Pro (8-bit) or 100% compatible soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sb8.
+
+config SND_SB16
+	tristate "Sound Blaster 16 (PnP)"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_SB16_DSP
+	help
+	  Say Y here to include support for Sound Blaster 16 soundcards
+	  (including the Plug and Play version).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sb16.
+
+config SND_SBAWE
+	tristate "Sound Blaster AWE (32,64) (PnP)"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_SB16_DSP
+	help
+	  Say Y here to include support for Sound Blaster AWE soundcards
+	  (including the Plug and Play version).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sbawe.
+
+config SND_SB16_CSP
+	bool "Sound Blaster 16/AWE CSP support"
+	depends on (SND_SB16 || SND_SBAWE) && (BROKEN || !PPC)
+	select FW_LOADER
+	help
+	  Say Y here to include support for the CSP core.  This special
+	  coprocessor can do variable tasks like various compression and
+	  decompression algorithms.
+
+config SND_SSCAPE
+	tristate "Ensoniq SoundScape driver"
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	select FW_LOADER
+	help
+	  Say Y here to include support for Ensoniq SoundScape 
+	  and Ensoniq OEM soundcards.
+
+	  The PCM audio is supported on SoundScape Classic, Elite, PnP
+	  and VIVO cards. The supported OEM cards are SPEA Media FX and
+	  Reveal SC-600.
+	  The MIDI support is very experimental and requires binary
+	  firmware files called "scope.cod" and "sndscape.co?" where the
+	  ? is digit 0, 1, 2, 3 or 4. The firmware files can be found
+	  in DOS or Windows driver packages. One has to put the firmware
+	  files into the /lib/firmware directory.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sscape.
+
+config SND_WAVEFRONT
+	tristate "Turtle Beach Maui,Tropez,Tropez+ (Wavefront)"
+	select FW_LOADER
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_WSS_LIB
+	help
+	  Say Y here to include support for Turtle Beach Maui, Tropez
+	  and Tropez+ soundcards based on the Wavefront chip.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-wavefront.
+
+config SND_MSND_PINNACLE
+	tristate "Turtle Beach MultiSound Pinnacle/Fiji driver"
+	depends on X86 && EXPERIMENTAL
+	select FW_LOADER
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say Y to include support for Turtle Beach MultiSound Pinnacle/
+	  Fiji soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-msnd-pinnacle.
+
+config SND_MSND_CLASSIC
+	tristate "Support for Turtle Beach MultiSound Classic, Tahiti, Monterey"
+	depends on X86 && EXPERIMENTAL
+	select FW_LOADER
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  Say M here if you have a Turtle Beach MultiSound Classic, Tahiti or
+	  Monterey (not for the Pinnacle or Fiji).
+
+	  See <file:Documentation/sound/oss/MultiSound> for important information
+	  about this driver.  Note that it has been discontinued, but the
+	  Voyetra Turtle Beach knowledge base entry for it is still available
+	  at <http://www.turtlebeach.com/site/kb_ftp/790.asp>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-msnd-classic.
+
+endif	# SND_ISA
+
diff --git a/linux/sound/isa/Makefile b/linux/sound/isa/Makefile
new file mode 100644
index 0000000..8d781e4
--- /dev/null
+++ b/linux/sound/isa/Makefile
@@ -0,0 +1,26 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-adlib-objs := adlib.o
+snd-als100-objs := als100.o
+snd-azt2320-objs := azt2320.o
+snd-cmi8330-objs := cmi8330.o
+snd-es18xx-objs := es18xx.o
+snd-opl3sa2-objs := opl3sa2.o
+snd-sc6000-objs := sc6000.o
+snd-sscape-objs := sscape.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_ADLIB) += snd-adlib.o
+obj-$(CONFIG_SND_ALS100) += snd-als100.o
+obj-$(CONFIG_SND_AZT2320) += snd-azt2320.o
+obj-$(CONFIG_SND_CMI8330) += snd-cmi8330.o
+obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o
+obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o
+obj-$(CONFIG_SND_SC6000) += snd-sc6000.o
+obj-$(CONFIG_SND_SSCAPE) += snd-sscape.o
+
+obj-$(CONFIG_SND) += ad1816a/ ad1848/ cs423x/ es1688/ galaxy/ gus/ msnd/ opti9xx/ \
+		     sb/ wavefront/ wss/
diff --git a/linux/sound/isa/ad1816a/Makefile b/linux/sound/isa/ad1816a/Makefile
new file mode 100644
index 0000000..487ab23
--- /dev/null
+++ b/linux/sound/isa/ad1816a/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ad1816a-objs := ad1816a.o ad1816a_lib.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AD1816A) += snd-ad1816a.o
diff --git a/linux/sound/isa/ad1816a/ad1816a.c b/linux/sound/isa/ad1816a/ad1816a.c
new file mode 100644
index 0000000..3cb75bc
--- /dev/null
+++ b/linux/sound/isa/ad1816a/ad1816a.c
@@ -0,0 +1,294 @@
+
+/*
+    card-ad1816a.c - driver for ADI SoundPort AD1816A based soundcards.
+    Copyright (C) 2000 by Massimo Piccioni <dafastidio@libero.it>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+*/
+
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/ad1816a.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+
+#define PFX "ad1816a: "
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_DESCRIPTION("AD1816A, AD1815");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Highscreen,Sound-Boostar 16 3D},"
+		"{Analog Devices,AD1815},"
+		"{Analog Devices,AD1816A},"
+		"{TerraTec,Base 64},"
+		"{TerraTec,AudioSystem EWS64S},"
+		"{Aztech/Newcom SC-16 3D},"
+		"{Shark Predator ISA}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 1-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* Pnp setup */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* Pnp setup */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+static int clockfreq[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ad1816a based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ad1816a based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ad1816a based soundcard.");
+module_param_array(clockfreq, int, NULL, 0444);
+MODULE_PARM_DESC(clockfreq, "Clock frequency for ad1816a driver (default = 0).");
+
+struct snd_card_ad1816a {
+	struct pnp_dev *dev;
+	struct pnp_dev *devmpu;
+};
+
+static struct pnp_card_device_id snd_ad1816a_pnpids[] = {
+	/* Analog Devices AD1815 */
+	{ .id = "ADS7150", .devs = { { .id = "ADS7150" }, { .id = "ADS7151" } } },
+	/* Analog Device AD1816? */
+	{ .id = "ADS7180", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Analog Devices AD1816A - added by Kenneth Platz <kxp@atl.hp.com> */
+	{ .id = "ADS7181", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Analog Devices AD1816A - Aztech/Newcom SC-16 3D */
+	{ .id = "AZT1022", .devs = { { .id = "AZT1018" }, { .id = "AZT2002" } } },
+	/* Highscreen Sound-Boostar 16 3D - added by Stefan Behnel */
+	{ .id = "LWC1061", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Highscreen Sound-Boostar 16 3D */
+	{ .id = "MDK1605", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Shark Predator ISA - added by Ken Arromdee */
+	{ .id = "SMM7180", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Analog Devices AD1816A - Terratec AudioSystem EWS64 S */
+	{ .id = "TER1112", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* Analog Devices AD1816A - Terratec AudioSystem EWS64 S */
+	{ .id = "TER1112", .devs = { { .id = "TER1100" }, { .id = "TER1101" } } },
+	/* Analog Devices AD1816A - Terratec Base 64 */
+	{ .id = "TER1411", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } },
+	/* end */
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_ad1816a_pnpids);
+
+
+#define	DRIVER_NAME	"snd-card-ad1816a"
+
+
+static int __devinit snd_card_ad1816a_pnp(int dev, struct snd_card_ad1816a *acard,
+					  struct pnp_card_link *card,
+					  const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	int err;
+
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL)
+		return -EBUSY;
+
+	acard->devmpu = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (acard->devmpu == NULL) {
+		mpu_port[dev] = -1;
+		snd_printk(KERN_WARNING PFX "MPU401 device busy, skipping.\n");
+	}
+
+	pdev = acard->dev;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		printk(KERN_ERR PFX "AUDIO PnP configure failure\n");
+		return -EBUSY;
+	}
+
+	port[dev] = pnp_port_start(pdev, 2);
+	fm_port[dev] = pnp_port_start(pdev, 1);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+
+	if (acard->devmpu == NULL)
+		return 0;
+
+	pdev = acard->devmpu;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		printk(KERN_ERR PFX "MPU401 PnP configure failure\n");
+		mpu_port[dev] = -1;
+		acard->devmpu = NULL;
+	} else {
+		mpu_port[dev] = pnp_port_start(pdev, 0);
+		mpu_irq[dev] = pnp_irq(pdev, 0);
+	}
+
+	return 0;
+}
+
+static int __devinit snd_card_ad1816a_probe(int dev, struct pnp_card_link *pcard,
+					    const struct pnp_card_device_id *pid)
+{
+	int error;
+	struct snd_card *card;
+	struct snd_card_ad1816a *acard;
+	struct snd_ad1816a *chip;
+	struct snd_opl3 *opl3;
+	struct snd_timer *timer;
+
+	error = snd_card_create(index[dev], id[dev], THIS_MODULE,
+				sizeof(struct snd_card_ad1816a), &card);
+	if (error < 0)
+		return error;
+	acard = card->private_data;
+
+	if ((error = snd_card_ad1816a_pnp(dev, acard, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+
+	if ((error = snd_ad1816a_create(card, port[dev],
+					irq[dev],
+					dma1[dev],
+					dma2[dev],
+					&chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	if (clockfreq[dev] >= 5000 && clockfreq[dev] <= 100000)
+		chip->clock_freq = clockfreq[dev];
+
+	strcpy(card->driver, "AD1816A");
+	strcpy(card->shortname, "ADI SoundPort AD1816A");
+	sprintf(card->longname, "%s, SS at 0x%lx, irq %d, dma %d&%d",
+		card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]);
+
+	if ((error = snd_ad1816a_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_ad1816a_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	error = snd_ad1816a_timer(chip, 0, &timer);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if (mpu_port[dev] > 0) {
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+					mpu_port[dev], 0, mpu_irq[dev], IRQF_DISABLED,
+					NULL) < 0)
+			printk(KERN_ERR PFX "no MPU-401 device at 0x%lx.\n", mpu_port[dev]);
+	}
+
+	if (fm_port[dev] > 0) {
+		if (snd_opl3_create(card,
+				    fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_AUTO, 0, &opl3) < 0) {
+			printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx.\n", fm_port[dev], fm_port[dev] + 2);
+		} else {
+			error = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+			if (error < 0) {
+				snd_card_free(card);
+				return error;
+			}
+		}
+	}
+
+	if ((error = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	return 0;
+}
+
+static unsigned int __devinitdata ad1816a_devices;
+
+static int __devinit snd_ad1816a_pnp_detect(struct pnp_card_link *card,
+					    const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+		res = snd_card_ad1816a_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		ad1816a_devices++;
+		return 0;
+	}
+        return -ENODEV;
+}
+
+static void __devexit snd_ad1816a_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+static struct pnp_card_driver ad1816a_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= "ad1816a",
+	.id_table	= snd_ad1816a_pnpids,
+	.probe		= snd_ad1816a_pnp_detect,
+	.remove		= __devexit_p(snd_ad1816a_pnp_remove),
+	/* FIXME: suspend/resume */
+};
+
+static int __init alsa_card_ad1816a_init(void)
+{
+	int err;
+
+	err = pnp_register_card_driver(&ad1816a_pnpc_driver);
+	if (err)
+		return err;
+
+	if (!ad1816a_devices) {
+		pnp_unregister_card_driver(&ad1816a_pnpc_driver);
+#ifdef MODULE
+		printk(KERN_ERR "no AD1816A based soundcards found.\n");
+#endif	/* MODULE */
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_ad1816a_exit(void)
+{
+	pnp_unregister_card_driver(&ad1816a_pnpc_driver);
+}
+
+module_init(alsa_card_ad1816a_init)
+module_exit(alsa_card_ad1816a_exit)
diff --git a/linux/sound/isa/ad1816a/ad1816a_lib.c b/linux/sound/isa/ad1816a/ad1816a_lib.c
new file mode 100644
index 0000000..05aef8b
--- /dev/null
+++ b/linux/sound/isa/ad1816a/ad1816a_lib.c
@@ -0,0 +1,972 @@
+/*
+    ad1816a.c - lowlevel code for Analog Devices AD1816A chip.
+    Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+*/
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/ad1816a.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+static inline int snd_ad1816a_busy_wait(struct snd_ad1816a *chip)
+{
+	int timeout;
+
+	for (timeout = 1000; timeout-- > 0; udelay(10))
+		if (inb(AD1816A_REG(AD1816A_CHIP_STATUS)) & AD1816A_READY)
+			return 0;
+
+	snd_printk(KERN_WARNING "chip busy.\n");
+	return -EBUSY;
+}
+
+static inline unsigned char snd_ad1816a_in(struct snd_ad1816a *chip, unsigned char reg)
+{
+	snd_ad1816a_busy_wait(chip);
+	return inb(AD1816A_REG(reg));
+}
+
+static inline void snd_ad1816a_out(struct snd_ad1816a *chip, unsigned char reg,
+			    unsigned char value)
+{
+	snd_ad1816a_busy_wait(chip);
+	outb(value, AD1816A_REG(reg));
+}
+
+static inline void snd_ad1816a_out_mask(struct snd_ad1816a *chip, unsigned char reg,
+				 unsigned char mask, unsigned char value)
+{
+	snd_ad1816a_out(chip, reg,
+		(value & mask) | (snd_ad1816a_in(chip, reg) & ~mask));
+}
+
+static unsigned short snd_ad1816a_read(struct snd_ad1816a *chip, unsigned char reg)
+{
+	snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f);
+	return snd_ad1816a_in(chip, AD1816A_INDIR_DATA_LOW) |
+		(snd_ad1816a_in(chip, AD1816A_INDIR_DATA_HIGH) << 8);
+}
+
+static void snd_ad1816a_write(struct snd_ad1816a *chip, unsigned char reg,
+			      unsigned short value)
+{
+	snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f);
+	snd_ad1816a_out(chip, AD1816A_INDIR_DATA_LOW, value & 0xff);
+	snd_ad1816a_out(chip, AD1816A_INDIR_DATA_HIGH, (value >> 8) & 0xff);
+}
+
+static void snd_ad1816a_write_mask(struct snd_ad1816a *chip, unsigned char reg,
+				   unsigned short mask, unsigned short value)
+{
+	snd_ad1816a_write(chip, reg,
+		(value & mask) | (snd_ad1816a_read(chip, reg) & ~mask));
+}
+
+
+static unsigned char snd_ad1816a_get_format(struct snd_ad1816a *chip,
+					    unsigned int format, int channels)
+{
+	unsigned char retval = AD1816A_FMT_LINEAR_8;
+
+	switch (format) {
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		retval = AD1816A_FMT_ULAW_8;
+		break;
+	case SNDRV_PCM_FORMAT_A_LAW:
+		retval = AD1816A_FMT_ALAW_8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		retval = AD1816A_FMT_LINEAR_16_LIT;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE:
+		retval = AD1816A_FMT_LINEAR_16_BIG;
+	}
+	return (channels > 1) ? (retval | AD1816A_FMT_STEREO) : retval;
+}
+
+static int snd_ad1816a_open(struct snd_ad1816a *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	if (chip->mode & mode) {
+		spin_unlock_irqrestore(&chip->lock, flags);
+		return -EAGAIN;
+	}
+
+	switch ((mode &= AD1816A_MODE_OPEN)) {
+	case AD1816A_MODE_PLAYBACK:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_PLAYBACK_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_PLAYBACK_IRQ_ENABLE, 0xffff);
+		break;
+	case AD1816A_MODE_CAPTURE:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_CAPTURE_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_CAPTURE_IRQ_ENABLE, 0xffff);
+		break;
+	case AD1816A_MODE_TIMER:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_TIMER_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_TIMER_IRQ_ENABLE, 0xffff);
+	}
+	chip->mode |= mode;
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static void snd_ad1816a_close(struct snd_ad1816a *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	switch ((mode &= AD1816A_MODE_OPEN)) {
+	case AD1816A_MODE_PLAYBACK:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_PLAYBACK_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_PLAYBACK_IRQ_ENABLE, 0x0000);
+		break;
+	case AD1816A_MODE_CAPTURE:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_CAPTURE_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_CAPTURE_IRQ_ENABLE, 0x0000);
+		break;
+	case AD1816A_MODE_TIMER:
+		snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS,
+			AD1816A_TIMER_IRQ_PENDING, 0x00);
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_TIMER_IRQ_ENABLE, 0x0000);
+	}
+	if (!((chip->mode &= ~mode) & AD1816A_MODE_OPEN))
+		chip->mode = 0;
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+static int snd_ad1816a_trigger(struct snd_ad1816a *chip, unsigned char what,
+			       int channel, int cmd, int iscapture)
+{
+	int error = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_STOP:
+		spin_lock(&chip->lock);
+		cmd = (cmd == SNDRV_PCM_TRIGGER_START) ? 0xff: 0x00;
+		/* if (what & AD1816A_PLAYBACK_ENABLE) */
+		/* That is not valid, because playback and capture enable
+		 * are the same bit pattern, just to different addresses
+		 */
+		if (! iscapture)
+			snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+				AD1816A_PLAYBACK_ENABLE, cmd);
+		else
+			snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+				AD1816A_CAPTURE_ENABLE, cmd);
+		spin_unlock(&chip->lock);
+		break;
+	default:
+		snd_printk(KERN_WARNING "invalid trigger mode 0x%x.\n", what);
+		error = -EINVAL;
+	}
+
+	return error;
+}
+
+static int snd_ad1816a_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	return snd_ad1816a_trigger(chip, AD1816A_PLAYBACK_ENABLE,
+				   SNDRV_PCM_STREAM_PLAYBACK, cmd, 0);
+}
+
+static int snd_ad1816a_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	return snd_ad1816a_trigger(chip, AD1816A_CAPTURE_ENABLE,
+				   SNDRV_PCM_STREAM_CAPTURE, cmd, 1);
+}
+
+static int snd_ad1816a_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_ad1816a_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_ad1816a_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size, rate;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	chip->p_dma_size = size = snd_pcm_lib_buffer_bytes(substream);
+	snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+		AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00);
+
+	snd_dma_program(chip->dma1, runtime->dma_addr, size,
+			DMA_MODE_WRITE | DMA_AUTOINIT);
+
+	rate = runtime->rate;
+	if (chip->clock_freq)
+		rate = (rate * 33000) / chip->clock_freq;
+	snd_ad1816a_write(chip, AD1816A_PLAYBACK_SAMPLE_RATE, rate);
+	snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+		AD1816A_FMT_ALL | AD1816A_FMT_STEREO,
+		snd_ad1816a_get_format(chip, runtime->format,
+			runtime->channels));
+
+	snd_ad1816a_write(chip, AD1816A_PLAYBACK_BASE_COUNT,
+		snd_pcm_lib_period_bytes(substream) / 4 - 1);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static int snd_ad1816a_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size, rate;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	chip->c_dma_size = size = snd_pcm_lib_buffer_bytes(substream);
+	snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+		AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00);
+
+	snd_dma_program(chip->dma2, runtime->dma_addr, size,
+			DMA_MODE_READ | DMA_AUTOINIT);
+
+	rate = runtime->rate;
+	if (chip->clock_freq)
+		rate = (rate * 33000) / chip->clock_freq;
+	snd_ad1816a_write(chip, AD1816A_CAPTURE_SAMPLE_RATE, rate);
+	snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+		AD1816A_FMT_ALL | AD1816A_FMT_STEREO,
+		snd_ad1816a_get_format(chip, runtime->format,
+			runtime->channels));
+
+	snd_ad1816a_write(chip, AD1816A_CAPTURE_BASE_COUNT,
+		snd_pcm_lib_period_bytes(substream) / 4 - 1);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+
+static snd_pcm_uframes_t snd_ad1816a_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	if (!(chip->mode & AD1816A_MODE_PLAYBACK))
+		return 0;
+	ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_ad1816a_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	if (!(chip->mode & AD1816A_MODE_CAPTURE))
+		return 0;
+	ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+
+static irqreturn_t snd_ad1816a_interrupt(int irq, void *dev_id)
+{
+	struct snd_ad1816a *chip = dev_id;
+	unsigned char status;
+
+	spin_lock(&chip->lock);
+	status = snd_ad1816a_in(chip, AD1816A_INTERRUPT_STATUS);
+	spin_unlock(&chip->lock);
+
+	if ((status & AD1816A_PLAYBACK_IRQ_PENDING) && chip->playback_substream)
+		snd_pcm_period_elapsed(chip->playback_substream);
+
+	if ((status & AD1816A_CAPTURE_IRQ_PENDING) && chip->capture_substream)
+		snd_pcm_period_elapsed(chip->capture_substream);
+
+	if ((status & AD1816A_TIMER_IRQ_PENDING) && chip->timer)
+		snd_timer_interrupt(chip->timer, chip->timer->sticks);
+
+	spin_lock(&chip->lock);
+	snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00);
+	spin_unlock(&chip->lock);
+	return IRQ_HANDLED;
+}
+
+
+static struct snd_pcm_hardware snd_ad1816a_playback = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+				 SNDRV_PCM_FMTBIT_S16_BE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		55200,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_ad1816a_capture = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+				 SNDRV_PCM_FMTBIT_S16_BE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		55200,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static int snd_ad1816a_timer_close(struct snd_timer *timer)
+{
+	struct snd_ad1816a *chip = snd_timer_chip(timer);
+	snd_ad1816a_close(chip, AD1816A_MODE_TIMER);
+	return 0;
+}
+
+static int snd_ad1816a_timer_open(struct snd_timer *timer)
+{
+	struct snd_ad1816a *chip = snd_timer_chip(timer);
+	snd_ad1816a_open(chip, AD1816A_MODE_TIMER);
+	return 0;
+}
+
+static unsigned long snd_ad1816a_timer_resolution(struct snd_timer *timer)
+{
+	if (snd_BUG_ON(!timer))
+		return 0;
+
+	return 10000;
+}
+
+static int snd_ad1816a_timer_start(struct snd_timer *timer)
+{
+	unsigned short bits;
+	unsigned long flags;
+	struct snd_ad1816a *chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->lock, flags);
+	bits = snd_ad1816a_read(chip, AD1816A_INTERRUPT_ENABLE);
+
+	if (!(bits & AD1816A_TIMER_ENABLE)) {
+		snd_ad1816a_write(chip, AD1816A_TIMER_BASE_COUNT,
+			timer->sticks & 0xffff);
+
+		snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+			AD1816A_TIMER_ENABLE, 0xffff);
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static int snd_ad1816a_timer_stop(struct snd_timer *timer)
+{
+	unsigned long flags;
+	struct snd_ad1816a *chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->lock, flags);
+
+	snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE,
+		AD1816A_TIMER_ENABLE, 0x0000);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static struct snd_timer_hardware snd_ad1816a_timer_table = {
+	.flags =	SNDRV_TIMER_HW_AUTO,
+	.resolution =	10000,
+	.ticks =	65535,
+	.open =		snd_ad1816a_timer_open,
+	.close =	snd_ad1816a_timer_close,
+	.c_resolution =	snd_ad1816a_timer_resolution,
+	.start =	snd_ad1816a_timer_start,
+	.stop =		snd_ad1816a_timer_stop,
+};
+
+static int snd_ad1816a_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int error;
+
+	if ((error = snd_ad1816a_open(chip, AD1816A_MODE_PLAYBACK)) < 0)
+		return error;
+	runtime->hw = snd_ad1816a_playback;
+	snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.period_bytes_max);
+	chip->playback_substream = substream;
+	return 0;
+}
+
+static int snd_ad1816a_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int error;
+
+	if ((error = snd_ad1816a_open(chip, AD1816A_MODE_CAPTURE)) < 0)
+		return error;
+	runtime->hw = snd_ad1816a_capture;
+	snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max);
+	chip->capture_substream = substream;
+	return 0;
+}
+
+static int snd_ad1816a_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	snd_ad1816a_close(chip, AD1816A_MODE_PLAYBACK);
+	return 0;
+}
+
+static int snd_ad1816a_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ad1816a *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	snd_ad1816a_close(chip, AD1816A_MODE_CAPTURE);
+	return 0;
+}
+
+
+static void __devinit snd_ad1816a_init(struct snd_ad1816a *chip)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00);
+	snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG,
+		AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00);
+	snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG,
+		AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00);
+	snd_ad1816a_write(chip, AD1816A_INTERRUPT_ENABLE, 0x0000);
+	snd_ad1816a_write_mask(chip, AD1816A_CHIP_CONFIG,
+		AD1816A_CAPTURE_NOT_EQUAL | AD1816A_WSS_ENABLE, 0xffff);
+	snd_ad1816a_write(chip, AD1816A_DSP_CONFIG, 0x0000);
+	snd_ad1816a_write(chip, AD1816A_POWERDOWN_CTRL, 0x0000);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static int __devinit snd_ad1816a_probe(struct snd_ad1816a *chip)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	switch (chip->version = snd_ad1816a_read(chip, AD1816A_VERSION_ID)) {
+	case 0:
+		chip->hardware = AD1816A_HW_AD1815;
+		break;
+	case 1:
+		chip->hardware = AD1816A_HW_AD18MAX10;
+		break;
+	case 3:
+		chip->hardware = AD1816A_HW_AD1816A;
+		break;
+	default:
+		chip->hardware = AD1816A_HW_AUTO;
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static int snd_ad1816a_free(struct snd_ad1816a *chip)
+{
+	release_and_free_resource(chip->res_port);
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *) chip);
+	if (chip->dma1 >= 0) {
+		snd_dma_disable(chip->dma1);
+		free_dma(chip->dma1);
+	}
+	if (chip->dma2 >= 0) {
+		snd_dma_disable(chip->dma2);
+		free_dma(chip->dma2);
+	}
+	kfree(chip);
+	return 0;
+}
+
+static int snd_ad1816a_dev_free(struct snd_device *device)
+{
+	struct snd_ad1816a *chip = device->device_data;
+	return snd_ad1816a_free(chip);
+}
+
+static const char __devinit *snd_ad1816a_chip_id(struct snd_ad1816a *chip)
+{
+	switch (chip->hardware) {
+	case AD1816A_HW_AD1816A: return "AD1816A";
+	case AD1816A_HW_AD1815:	return "AD1815";
+	case AD1816A_HW_AD18MAX10: return "AD18max10";
+	default:
+		snd_printk(KERN_WARNING "Unknown chip version %d:%d.\n",
+			chip->version, chip->hardware);
+		return "AD1816A - unknown";
+	}
+}
+
+int __devinit snd_ad1816a_create(struct snd_card *card,
+				 unsigned long port, int irq, int dma1, int dma2,
+				 struct snd_ad1816a **rchip)
+{
+        static struct snd_device_ops ops = {
+		.dev_free =	snd_ad1816a_dev_free,
+	};
+	int error;
+	struct snd_ad1816a *chip;
+
+	*rchip = NULL;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	chip->irq = -1;
+	chip->dma1 = -1;
+	chip->dma2 = -1;
+
+	if ((chip->res_port = request_region(port, 16, "AD1816A")) == NULL) {
+		snd_printk(KERN_ERR "ad1816a: can't grab port 0x%lx\n", port);
+		snd_ad1816a_free(chip);
+		return -EBUSY;
+	}
+	if (request_irq(irq, snd_ad1816a_interrupt, IRQF_DISABLED, "AD1816A", (void *) chip)) {
+		snd_printk(KERN_ERR "ad1816a: can't grab IRQ %d\n", irq);
+		snd_ad1816a_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = irq;
+	if (request_dma(dma1, "AD1816A - 1")) {
+		snd_printk(KERN_ERR "ad1816a: can't grab DMA1 %d\n", dma1);
+		snd_ad1816a_free(chip);
+		return -EBUSY;
+	}
+	chip->dma1 = dma1;
+	if (request_dma(dma2, "AD1816A - 2")) {
+		snd_printk(KERN_ERR "ad1816a: can't grab DMA2 %d\n", dma2);
+		snd_ad1816a_free(chip);
+		return -EBUSY;
+	}
+	chip->dma2 = dma2;
+
+	chip->card = card;
+	chip->port = port;
+	spin_lock_init(&chip->lock);
+
+	if ((error = snd_ad1816a_probe(chip))) {
+		snd_ad1816a_free(chip);
+		return error;
+	}
+
+	snd_ad1816a_init(chip);
+
+	/* Register device */
+	if ((error = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_ad1816a_free(chip);
+		return error;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_ad1816a_playback_ops = {
+	.open =		snd_ad1816a_playback_open,
+	.close =	snd_ad1816a_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ad1816a_hw_params,
+	.hw_free =	snd_ad1816a_hw_free,
+	.prepare =	snd_ad1816a_playback_prepare,
+	.trigger =	snd_ad1816a_playback_trigger,
+	.pointer =	snd_ad1816a_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_ad1816a_capture_ops = {
+	.open =		snd_ad1816a_capture_open,
+	.close =	snd_ad1816a_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ad1816a_hw_params,
+	.hw_free =	snd_ad1816a_hw_free,
+	.prepare =	snd_ad1816a_capture_prepare,
+	.trigger =	snd_ad1816a_capture_trigger,
+	.pointer =	snd_ad1816a_capture_pointer,
+};
+
+int __devinit snd_ad1816a_pcm(struct snd_ad1816a *chip, int device, struct snd_pcm **rpcm)
+{
+	int error;
+	struct snd_pcm *pcm;
+
+	if ((error = snd_pcm_new(chip->card, "AD1816A", device, 1, 1, &pcm)))
+		return error;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ad1816a_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ad1816a_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = (chip->dma1 == chip->dma2 ) ? SNDRV_PCM_INFO_JOINT_DUPLEX : 0;
+
+	strcpy(pcm->name, snd_ad1816a_chip_id(chip));
+	snd_ad1816a_init(chip);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);
+
+	chip->pcm = pcm;
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+int __devinit snd_ad1816a_timer(struct snd_ad1816a *chip, int device, struct snd_timer **rtimer)
+{
+	struct snd_timer *timer;
+	struct snd_timer_id tid;
+	int error;
+
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = chip->card->number;
+	tid.device = device;
+	tid.subdevice = 0;
+	if ((error = snd_timer_new(chip->card, "AD1816A", &tid, &timer)) < 0)
+		return error;
+	strcpy(timer->name, snd_ad1816a_chip_id(chip));
+	timer->private_data = chip;
+	chip->timer = timer;
+	timer->hw = snd_ad1816a_timer_table;
+	if (rtimer)
+		*rtimer = timer;
+	return 0;
+}
+
+/*
+ *
+ */
+
+static int snd_ad1816a_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[8] = {
+		"Line", "Mix", "CD", "Synth", "Video",
+		"Mic", "Phone",
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 2;
+	uinfo->value.enumerated.items = 7;
+	if (uinfo->value.enumerated.item > 6)
+		uinfo->value.enumerated.item = 6;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ad1816a_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned short val;
+	
+	spin_lock_irqsave(&chip->lock, flags);
+	val = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	ucontrol->value.enumerated.item[0] = (val >> 12) & 7;
+	ucontrol->value.enumerated.item[1] = (val >> 4) & 7;
+	return 0;
+}
+
+static int snd_ad1816a_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned short val;
+	int change;
+	
+	if (ucontrol->value.enumerated.item[0] > 6 ||
+	    ucontrol->value.enumerated.item[1] > 6)
+		return -EINVAL;
+	val = (ucontrol->value.enumerated.item[0] << 12) |
+	      (ucontrol->value.enumerated.item[1] << 4);
+	spin_lock_irqsave(&chip->lock, flags);
+	change = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL) != val;
+	snd_ad1816a_write(chip, AD1816A_ADC_SOURCE_SEL, val);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return change;
+}
+
+#define AD1816A_SINGLE_TLV(xname, reg, shift, mask, invert, xtlv)	\
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .name = xname, .info = snd_ad1816a_info_single, \
+  .get = snd_ad1816a_get_single, .put = snd_ad1816a_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \
+  .tlv = { .p = (xtlv) } }
+#define AD1816A_SINGLE(xname, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_single, \
+  .get = snd_ad1816a_get_single, .put = snd_ad1816a_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_ad1816a_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_ad1816a_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irqsave(&chip->lock, flags);
+	ucontrol->value.integer.value[0] = (snd_ad1816a_read(chip, reg) >> shift) & mask;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_ad1816a_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short old_val, val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->lock, flags);
+	old_val = snd_ad1816a_read(chip, reg);
+	val = (old_val & ~(mask << shift)) | val;
+	change = val != old_val;
+	snd_ad1816a_write(chip, reg, val);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return change;
+}
+
+#define AD1816A_DOUBLE_TLV(xname, reg, shift_left, shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .name = xname, .info = snd_ad1816a_info_double,		\
+  .get = snd_ad1816a_get_double, .put = snd_ad1816a_put_double, \
+  .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24), \
+  .tlv = { .p = (xtlv) } }
+
+#define AD1816A_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_double, \
+  .get = snd_ad1816a_get_double, .put = snd_ad1816a_put_double, \
+  .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24) }
+
+static int snd_ad1816a_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_ad1816a_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift_left = (kcontrol->private_value >> 8) & 0x0f;
+	int shift_right = (kcontrol->private_value >> 12) & 0x0f;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	unsigned short val;
+	
+	spin_lock_irqsave(&chip->lock, flags);
+	val = snd_ad1816a_read(chip, reg);
+	ucontrol->value.integer.value[0] = (val >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (val >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_ad1816a_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift_left = (kcontrol->private_value >> 8) & 0x0f;
+	int shift_right = (kcontrol->private_value >> 12) & 0x0f;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short old_val, val1, val2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->lock, flags);
+	old_val = snd_ad1816a_read(chip, reg);
+	val1 = (old_val & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+	change = val1 != old_val;
+	snd_ad1816a_write(chip, reg, val1);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
+
+static struct snd_kcontrol_new snd_ad1816a_controls[] __devinitdata = {
+AD1816A_DOUBLE("Master Playback Switch", AD1816A_MASTER_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Master Playback Volume", AD1816A_MASTER_ATT, 8, 0, 31, 1,
+		   db_scale_5bit),
+AD1816A_DOUBLE("PCM Playback Switch", AD1816A_VOICE_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("PCM Playback Volume", AD1816A_VOICE_ATT, 8, 0, 63, 1,
+		   db_scale_6bit),
+AD1816A_DOUBLE("Line Playback Switch", AD1816A_LINE_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Line Playback Volume", AD1816A_LINE_GAIN_ATT, 8, 0, 31, 1,
+		   db_scale_5bit_12db_max),
+AD1816A_DOUBLE("CD Playback Switch", AD1816A_CD_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("CD Playback Volume", AD1816A_CD_GAIN_ATT, 8, 0, 31, 1,
+		   db_scale_5bit_12db_max),
+AD1816A_DOUBLE("Synth Playback Switch", AD1816A_SYNTH_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Synth Playback Volume", AD1816A_SYNTH_GAIN_ATT, 8, 0, 31, 1,
+		   db_scale_5bit_12db_max),
+AD1816A_DOUBLE("FM Playback Switch", AD1816A_FM_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("FM Playback Volume", AD1816A_FM_ATT, 8, 0, 63, 1,
+		   db_scale_6bit),
+AD1816A_SINGLE("Mic Playback Switch", AD1816A_MIC_GAIN_ATT, 15, 1, 1),
+AD1816A_SINGLE_TLV("Mic Playback Volume", AD1816A_MIC_GAIN_ATT, 8, 31, 1,
+		   db_scale_5bit_12db_max),
+AD1816A_SINGLE("Mic Boost", AD1816A_MIC_GAIN_ATT, 14, 1, 0),
+AD1816A_DOUBLE("Video Playback Switch", AD1816A_VID_GAIN_ATT, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Video Playback Volume", AD1816A_VID_GAIN_ATT, 8, 0, 31, 1,
+		   db_scale_5bit_12db_max),
+AD1816A_SINGLE("Phone Capture Switch", AD1816A_PHONE_IN_GAIN_ATT, 15, 1, 1),
+AD1816A_SINGLE_TLV("Phone Capture Volume", AD1816A_PHONE_IN_GAIN_ATT, 0, 15, 1,
+		   db_scale_4bit),
+AD1816A_SINGLE("Phone Playback Switch", AD1816A_PHONE_OUT_ATT, 7, 1, 1),
+AD1816A_SINGLE_TLV("Phone Playback Volume", AD1816A_PHONE_OUT_ATT, 0, 31, 1,
+		   db_scale_5bit),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_ad1816a_info_mux,
+	.get = snd_ad1816a_get_mux,
+	.put = snd_ad1816a_put_mux,
+},
+AD1816A_DOUBLE("Capture Switch", AD1816A_ADC_PGA, 15, 7, 1, 1),
+AD1816A_DOUBLE_TLV("Capture Volume", AD1816A_ADC_PGA, 8, 0, 15, 0,
+		   db_scale_rec_gain),
+AD1816A_SINGLE("3D Control - Switch", AD1816A_3D_PHAT_CTRL, 15, 1, 1),
+AD1816A_SINGLE("3D Control - Level", AD1816A_3D_PHAT_CTRL, 0, 15, 0),
+};
+                                        
+int __devinit snd_ad1816a_mixer(struct snd_ad1816a *chip)
+{
+	struct snd_card *card;
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!chip || !chip->card))
+		return -EINVAL;
+
+	card = chip->card;
+
+	strcpy(card->mixername, snd_ad1816a_chip_id(chip));
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_ad1816a_controls); idx++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ad1816a_controls[idx], chip))) < 0)
+			return err;
+	}
+	return 0;
+}
diff --git a/linux/sound/isa/ad1848/Makefile b/linux/sound/isa/ad1848/Makefile
new file mode 100644
index 0000000..3d6dea3
--- /dev/null
+++ b/linux/sound/isa/ad1848/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ad1848-objs := ad1848.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AD1848) += snd-ad1848.o
+
diff --git a/linux/sound/isa/ad1848/ad1848.c b/linux/sound/isa/ad1848/ad1848.c
new file mode 100644
index 0000000..4beeb6f
--- /dev/null
+++ b/linux/sound/isa/ad1848/ad1848.c
@@ -0,0 +1,188 @@
+/*
+ *  Generic driver for AD1848/AD1847/CS4248 chips (0.1 Alpha)
+ *  Copyright (c) by Tugrul Galatali <galatalt@stuy.edu>,
+ *                   Jaroslav Kysela <perex@perex.cz>
+ *  Based on card-4232.c by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/initval.h>
+
+#define CRD_NAME "Generic AD1848/AD1847/CS4248"
+#define DEV_NAME "ad1848"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Tugrul Galatali <galatalt@stuy.edu>, Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Analog Devices,AD1848},"
+	        "{Analog Devices,AD1847},"
+		"{Crystal Semiconductors,CS4248}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+static int thinkpad[SNDRV_CARDS];			/* Thinkpad special case */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver.");
+module_param_array(thinkpad, bool, NULL, 0444);
+MODULE_PARM_DESC(thinkpad, "Enable only for the onboard CS4248 of IBM Thinkpad 360/750/755 series.");
+
+static int __devinit snd_ad1848_match(struct device *dev, unsigned int n)
+{
+	if (!enable[n])
+		return 0;
+
+	if (port[n] == SNDRV_AUTO_PORT) {
+		dev_err(dev, "please specify port\n");
+		return 0;
+	}
+	if (irq[n] == SNDRV_AUTO_IRQ) {
+		dev_err(dev, "please specify irq\n");
+		return 0;	
+	}
+	if (dma1[n] == SNDRV_AUTO_DMA) {
+		dev_err(dev, "please specify dma1\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int __devinit snd_ad1848_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	struct snd_wss *chip;
+	struct snd_pcm *pcm;
+	int error;
+
+	error = snd_card_create(index[n], id[n], THIS_MODULE, 0, &card);
+	if (error < 0)
+		return error;
+
+	error = snd_wss_create(card, port[n], -1, irq[n], dma1[n], -1,
+			thinkpad[n] ? WSS_HW_THINKPAD : WSS_HW_DETECT,
+			0, &chip);
+	if (error < 0)
+		goto out;
+
+	card->private_data = chip;
+
+	error = snd_wss_pcm(chip, 0, &pcm);
+	if (error < 0)
+		goto out;
+
+	error = snd_wss_mixer(chip);
+	if (error < 0)
+		goto out;
+
+	strcpy(card->driver, "AD1848");
+	strcpy(card->shortname, pcm->name);
+
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		pcm->name, chip->port, irq[n], dma1[n]);
+	if (thinkpad[n])
+		strcat(card->longname, " [Thinkpad]");
+
+	snd_card_set_dev(card, dev);
+
+	error = snd_card_register(card);
+	if (error < 0)
+		goto out;
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+out:	snd_card_free(card);
+	return error;
+}
+
+static int __devexit snd_ad1848_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	dev_set_drvdata(dev, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_ad1848_suspend(struct device *dev, unsigned int n, pm_message_t state)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_wss *chip = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	chip->suspend(chip);
+	return 0;
+}
+
+static int snd_ad1848_resume(struct device *dev, unsigned int n)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_wss *chip = card->private_data;
+
+	chip->resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct isa_driver snd_ad1848_driver = {
+	.match		= snd_ad1848_match,
+	.probe		= snd_ad1848_probe,
+	.remove		= __devexit_p(snd_ad1848_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_ad1848_suspend,
+	.resume		= snd_ad1848_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+static int __init alsa_card_ad1848_init(void)
+{
+	return isa_register_driver(&snd_ad1848_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_ad1848_exit(void)
+{
+	isa_unregister_driver(&snd_ad1848_driver);
+}
+
+module_init(alsa_card_ad1848_init);
+module_exit(alsa_card_ad1848_exit);
diff --git a/linux/sound/isa/adlib.c b/linux/sound/isa/adlib.c
new file mode 100644
index 0000000..7465ae0
--- /dev/null
+++ b/linux/sound/isa/adlib.c
@@ -0,0 +1,129 @@
+/*
+ * AdLib FM card driver.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/isa.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/opl3.h>
+
+#define CRD_NAME "AdLib FM"
+#define DEV_NAME "adlib"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Rene Herman");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+
+static int __devinit snd_adlib_match(struct device *dev, unsigned int n)
+{
+	if (!enable[n])
+		return 0;
+
+	if (port[n] == SNDRV_AUTO_PORT) {
+		dev_err(dev, "please specify port\n");
+		return 0;
+	}
+	return 1;
+}
+
+static void snd_adlib_free(struct snd_card *card)
+{
+	release_and_free_resource(card->private_data);
+}
+
+static int __devinit snd_adlib_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	struct snd_opl3 *opl3;
+	int error;
+
+	error = snd_card_create(index[n], id[n], THIS_MODULE, 0, &card);
+	if (error < 0) {
+		dev_err(dev, "could not create card\n");
+		return error;
+	}
+
+	card->private_data = request_region(port[n], 4, CRD_NAME);
+	if (!card->private_data) {
+		dev_err(dev, "could not grab ports\n");
+		error = -EBUSY;
+		goto out;
+	}
+	card->private_free = snd_adlib_free;
+
+	strcpy(card->driver, DEV_NAME);
+	strcpy(card->shortname, CRD_NAME);
+	sprintf(card->longname, CRD_NAME " at %#lx", port[n]);
+
+	error = snd_opl3_create(card, port[n], port[n] + 2, OPL3_HW_AUTO, 1, &opl3);
+	if (error < 0) {
+		dev_err(dev, "could not create OPL\n");
+		goto out;
+	}
+
+	error = snd_opl3_hwdep_new(opl3, 0, 0, NULL);
+	if (error < 0) {
+		dev_err(dev, "could not create FM\n");
+		goto out;
+	}
+
+	snd_card_set_dev(card, dev);
+
+	error = snd_card_register(card);
+	if (error < 0) {
+		dev_err(dev, "could not register card\n");
+		goto out;
+	}
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+out:	snd_card_free(card);
+	return error;
+}
+
+static int __devexit snd_adlib_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	dev_set_drvdata(dev, NULL);
+	return 0;
+}
+
+static struct isa_driver snd_adlib_driver = {
+	.match		= snd_adlib_match,
+	.probe		= snd_adlib_probe,
+	.remove		= __devexit_p(snd_adlib_remove),
+
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+static int __init alsa_card_adlib_init(void)
+{
+	return isa_register_driver(&snd_adlib_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_adlib_exit(void)
+{
+	isa_unregister_driver(&snd_adlib_driver);
+}
+
+module_init(alsa_card_adlib_init);
+module_exit(alsa_card_adlib_exit);
diff --git a/linux/sound/isa/als100.c b/linux/sound/isa/als100.c
new file mode 100644
index 0000000..20becc8
--- /dev/null
+++ b/linux/sound/isa/als100.c
@@ -0,0 +1,379 @@
+
+/*
+    card-als100.c - driver for Avance Logic ALS100 based soundcards.
+    Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>
+    Copyright (C) 1999-2002 by Massimo Piccioni <dafastidio@libero.it>
+
+    Thanks to Pierfrancesco 'qM2' Passerini.
+
+    Generalised for soundcards based on DT-0196 and ALS-007 chips
+    by Jonathan Woithe <jwoithe@physics.adelaide.edu.au>: June 2002.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+*/
+
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/time.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/sb.h>
+
+#define PFX "als100: "
+
+MODULE_DESCRIPTION("Avance Logic ALS007/ALS1X0");
+MODULE_SUPPORTED_DEVICE("{{Diamond Technologies DT-019X},"
+		"{Avance Logic ALS-007}}"
+		"{{Avance Logic,ALS100 - PRO16PNP},"
+	        "{Avance Logic,ALS110},"
+	        "{Avance Logic,ALS120},"
+	        "{Avance Logic,ALS200},"
+	        "{3D Melody,MF1000},"
+	        "{Digimate,3D Sound},"
+	        "{Avance Logic,ALS120},"
+	        "{RTL,RTL3000}}");
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* PnP setup */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* PnP setup */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Avance Logic based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Avance Logic based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Avance Logic based soundcard.");
+
+MODULE_ALIAS("snd-dt019x");
+
+struct snd_card_als100 {
+	struct pnp_dev *dev;
+	struct pnp_dev *devmpu;
+	struct pnp_dev *devopl;
+	struct snd_sb *chip;
+};
+
+static struct pnp_card_device_id snd_als100_pnpids[] = {
+	/* DT197A30 */
+	{ .id = "RWB1688",
+	  .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } },
+	  .driver_data = SB_HW_DT019X },
+	/* DT0196 / ALS-007 */
+	{ .id = "ALS0007",
+	  .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } },
+	  .driver_data = SB_HW_DT019X },
+	/* ALS100 - PRO16PNP */
+	{ .id = "ALS0001",
+	  .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } },
+	  .driver_data = SB_HW_ALS100 },
+	/* ALS110 - MF1000 - Digimate 3D Sound */
+	{ .id = "ALS0110",
+	  .devs = { { "@@@1001" }, { "@X@1001" }, { "@H@1001" } },
+	  .driver_data = SB_HW_ALS100 },
+	/* ALS120 */
+	{ .id = "ALS0120",
+	  .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } },
+	  .driver_data = SB_HW_ALS100 },
+	/* ALS200 */
+	{ .id = "ALS0200",
+	  .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0001" } },
+	  .driver_data = SB_HW_ALS100 },
+	/* ALS200 OEM */
+	{ .id = "ALS0200",
+	  .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0020" } },
+	  .driver_data = SB_HW_ALS100 },
+	/* RTL3000 */
+	{ .id = "RTL3000",
+	  .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } },
+	  .driver_data = SB_HW_ALS100 },
+	{ .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_als100_pnpids);
+
+static int __devinit snd_card_als100_pnp(int dev, struct snd_card_als100 *acard,
+					 struct pnp_card_link *card,
+					 const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	int err;
+
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL)
+		return -ENODEV;
+
+	acard->devmpu = pnp_request_card_device(card, id->devs[1].id, acard->dev);
+	acard->devopl = pnp_request_card_device(card, id->devs[2].id, acard->dev);
+
+	pdev = acard->dev;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
+		return err;
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	if (id->driver_data == SB_HW_DT019X)
+		dma8[dev] = pnp_dma(pdev, 0);
+	else {
+		dma8[dev] = pnp_dma(pdev, 1);
+		dma16[dev] = pnp_dma(pdev, 0);
+	}
+	irq[dev] = pnp_irq(pdev, 0);
+
+	pdev = acard->devmpu;
+	if (pdev != NULL) {
+		err = pnp_activate_dev(pdev);
+		if (err < 0)
+			goto __mpu_error;
+		mpu_port[dev] = pnp_port_start(pdev, 0);
+		mpu_irq[dev] = pnp_irq(pdev, 0);
+	} else {
+	     __mpu_error:
+	     	if (pdev) {
+		     	pnp_release_card_device(pdev);
+	     		snd_printk(KERN_ERR PFX "MPU401 pnp configure failure, skipping\n");
+	     	}
+	     	acard->devmpu = NULL;
+	     	mpu_port[dev] = -1;
+	}
+
+	pdev = acard->devopl;
+	if (pdev != NULL) {
+		err = pnp_activate_dev(pdev);
+		if (err < 0)
+			goto __fm_error;
+		fm_port[dev] = pnp_port_start(pdev, 0);
+	} else {
+	      __fm_error:
+	     	if (pdev) {
+		     	pnp_release_card_device(pdev);
+	     		snd_printk(KERN_ERR PFX "OPL3 pnp configure failure, skipping\n");
+	     	}
+	     	acard->devopl = NULL;
+	     	fm_port[dev] = -1;
+	}
+
+	return 0;
+}
+
+static int __devinit snd_card_als100_probe(int dev,
+					struct pnp_card_link *pcard,
+					const struct pnp_card_device_id *pid)
+{
+	int error;
+	struct snd_sb *chip;
+	struct snd_card *card;
+	struct snd_card_als100 *acard;
+	struct snd_opl3 *opl3;
+
+	error = snd_card_create(index[dev], id[dev], THIS_MODULE,
+				sizeof(struct snd_card_als100), &card);
+	if (error < 0)
+		return error;
+	acard = card->private_data;
+
+	if ((error = snd_card_als100_pnp(dev, acard, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+
+	if (pid->driver_data == SB_HW_DT019X)
+		dma16[dev] = -1;
+
+	error = snd_sbdsp_create(card, port[dev], irq[dev],
+				  snd_sb16dsp_interrupt,
+				  dma8[dev], dma16[dev],
+				  pid->driver_data,
+				  &chip);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	acard->chip = chip;
+
+	if (pid->driver_data == SB_HW_DT019X) {
+		strcpy(card->driver, "DT-019X");
+		strcpy(card->shortname, "Diamond Tech. DT-019X");
+		sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d",
+			card->shortname, chip->name, chip->port,
+			irq[dev], dma8[dev]);
+	} else {
+		strcpy(card->driver, "ALS100");
+		strcpy(card->shortname, "Avance Logic ALS100");
+		sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d&%d",
+			card->shortname, chip->name, chip->port,
+			irq[dev], dma8[dev], dma16[dev]);
+	}
+
+	if ((error = snd_sb16dsp_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if ((error = snd_sbmixer_new(chip)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		int mpu_type = MPU401_HW_ALS100;
+
+		if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+			mpu_irq[dev] = -1;
+
+		if (pid->driver_data == SB_HW_DT019X)
+			mpu_type = MPU401_HW_MPU401;
+
+		if (snd_mpu401_uart_new(card, 0,
+					mpu_type,
+					mpu_port[dev], 0, 
+					mpu_irq[dev],
+					mpu_irq[dev] >= 0 ? IRQF_DISABLED : 0,
+					NULL) < 0)
+			snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx\n", mpu_port[dev]);
+	}
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card,
+				    fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_AUTO, 0, &opl3) < 0) {
+			snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n",
+				   fm_port[dev], fm_port[dev] + 2);
+		} else {
+			if ((error = snd_opl3_timer_new(opl3, 0, 1)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+			if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+		}
+	}
+
+	if ((error = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	return 0;
+}
+
+static unsigned int __devinitdata als100_devices;
+
+static int __devinit snd_als100_pnp_detect(struct pnp_card_link *card,
+					   const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+		res = snd_card_als100_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		als100_devices++;
+		return 0;
+	}
+	return -ENODEV;
+}
+
+static void __devexit snd_als100_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_als100_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_card_als100 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_sbmixer_suspend(chip);
+	return 0;
+}
+
+static int snd_als100_pnp_resume(struct pnp_card_link *pcard)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_card_als100 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_sbdsp_reset(chip);
+	snd_sbmixer_resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct pnp_card_driver als100_pnpc_driver = {
+	.flags          = PNP_DRIVER_RES_DISABLE,
+	.name		= "als100",
+        .id_table       = snd_als100_pnpids,
+        .probe          = snd_als100_pnp_detect,
+        .remove         = __devexit_p(snd_als100_pnp_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_als100_pnp_suspend,
+	.resume		= snd_als100_pnp_resume,
+#endif
+};
+
+static int __init alsa_card_als100_init(void)
+{
+	int err;
+
+	err = pnp_register_card_driver(&als100_pnpc_driver);
+	if (err)
+		return err;
+
+	if (!als100_devices) {
+		pnp_unregister_card_driver(&als100_pnpc_driver);
+#ifdef MODULE
+		snd_printk(KERN_ERR "no Avance Logic based soundcards found\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_als100_exit(void)
+{
+	pnp_unregister_card_driver(&als100_pnpc_driver);
+}
+
+module_init(alsa_card_als100_init)
+module_exit(alsa_card_als100_exit)
diff --git a/linux/sound/isa/azt2320.c b/linux/sound/isa/azt2320.c
new file mode 100644
index 0000000..aac8dc1
--- /dev/null
+++ b/linux/sound/isa/azt2320.c
@@ -0,0 +1,355 @@
+/*
+    card-azt2320.c - driver for Aztech Systems AZT2320 based soundcards.
+    Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+*/
+
+/*
+    This driver should provide support for most Aztech AZT2320 based cards.
+    Several AZT2316 chips are also supported/tested, but autoprobe doesn't
+    work: all module option have to be set.
+
+    No docs available for us at Aztech headquarters !!!   Unbelievable ...
+    No other help obtained.
+
+    Thanks to Rainer Wiesner <rainer.wiesner@01019freenet.de> for the WSS
+    activation method (full-duplex audio!).
+*/
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+
+#define PFX "azt2320: "
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_DESCRIPTION("Aztech Systems AZT2320");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Aztech Systems,PRO16V},"
+		"{Aztech Systems,AZT2320},"
+		"{Aztech Systems,AZT3300},"
+		"{Aztech Systems,AZT2320},"
+		"{Aztech Systems,AZT3000}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* Pnp setup */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* Pnp setup */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* PnP setup */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for azt2320 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for azt2320 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable azt2320 based soundcard.");
+
+struct snd_card_azt2320 {
+	int dev_no;
+	struct pnp_dev *dev;
+	struct pnp_dev *devmpu;
+	struct snd_wss *chip;
+};
+
+static struct pnp_card_device_id snd_azt2320_pnpids[] = {
+	/* PRO16V */
+	{ .id = "AZT1008", .devs = { { "AZT1008" }, { "AZT2001" }, } },
+	/* Aztech Sound Galaxy 16 */
+	{ .id = "AZT2320", .devs = { { "AZT0001" }, { "AZT0002" }, } },
+	/* Packard Bell Sound III 336 AM/SP */
+	{ .id = "AZT3000", .devs = { { "AZT1003" }, { "AZT2001" }, } },
+	/* AT3300 */
+	{ .id = "AZT3002", .devs = { { "AZT1004" }, { "AZT2001" }, } },
+	/* --- */
+	{ .id = "AZT3005", .devs = { { "AZT1003" }, { "AZT2001" }, } },
+	/* --- */
+	{ .id = "AZT3011", .devs = { { "AZT1003" }, { "AZT2001" }, } },
+	{ .id = "" }	/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_azt2320_pnpids);
+
+#define	DRIVER_NAME	"snd-card-azt2320"
+
+static int __devinit snd_card_azt2320_pnp(int dev, struct snd_card_azt2320 *acard,
+					  struct pnp_card_link *card,
+					  const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	int err;
+
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL)
+		return -ENODEV;
+
+	acard->devmpu = pnp_request_card_device(card, id->devs[1].id, NULL);
+
+	pdev = acard->dev;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n");
+		return err;
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	fm_port[dev] = pnp_port_start(pdev, 1);
+	wss_port[dev] = pnp_port_start(pdev, 2);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+
+	pdev = acard->devmpu;
+	if (pdev != NULL) {
+		err = pnp_activate_dev(pdev);
+		if (err < 0)
+			goto __mpu_error;
+		mpu_port[dev] = pnp_port_start(pdev, 0);
+		mpu_irq[dev] = pnp_irq(pdev, 0);
+	} else {
+	     __mpu_error:
+	     	if (pdev) {
+		     	pnp_release_card_device(pdev);
+	     		snd_printk(KERN_ERR PFX "MPU401 pnp configure failure, skipping\n");
+	     	}
+	     	acard->devmpu = NULL;
+	     	mpu_port[dev] = -1;
+	}
+
+	return 0;
+}
+
+/* same of snd_sbdsp_command by Jaroslav Kysela */
+static int __devinit snd_card_azt2320_command(unsigned long port, unsigned char val)
+{
+	int i;
+	unsigned long limit;
+
+	limit = jiffies + HZ / 10;
+	for (i = 50000; i && time_after(limit, jiffies); i--)
+		if (!(inb(port + 0x0c) & 0x80)) {
+			outb(val, port + 0x0c);
+			return 0;
+		}
+	return -EBUSY;
+}
+
+static int __devinit snd_card_azt2320_enable_wss(unsigned long port)
+{
+	int error;
+
+	if ((error = snd_card_azt2320_command(port, 0x09)))
+		return error;
+	if ((error = snd_card_azt2320_command(port, 0x00)))
+		return error;
+
+	mdelay(5);
+	return 0;
+}
+
+static int __devinit snd_card_azt2320_probe(int dev,
+					    struct pnp_card_link *pcard,
+					    const struct pnp_card_device_id *pid)
+{
+	int error;
+	struct snd_card *card;
+	struct snd_card_azt2320 *acard;
+	struct snd_wss *chip;
+	struct snd_opl3 *opl3;
+
+	error = snd_card_create(index[dev], id[dev], THIS_MODULE,
+				sizeof(struct snd_card_azt2320), &card);
+	if (error < 0)
+		return error;
+	acard = card->private_data;
+
+	if ((error = snd_card_azt2320_pnp(dev, acard, pcard, pid))) {
+		snd_card_free(card);
+		return error;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+
+	if ((error = snd_card_azt2320_enable_wss(port[dev]))) {
+		snd_card_free(card);
+		return error;
+	}
+
+	error = snd_wss_create(card, wss_port[dev], -1,
+			       irq[dev],
+			       dma1[dev], dma2[dev],
+			       WSS_HW_DETECT, 0, &chip);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	strcpy(card->driver, "AZT2320");
+	strcpy(card->shortname, "Aztech AZT2320");
+	sprintf(card->longname, "%s, WSS at 0x%lx, irq %i, dma %i&%i",
+		card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]);
+
+	error = snd_wss_pcm(chip, 0, NULL);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	error = snd_wss_mixer(chip);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	error = snd_wss_timer(chip, 0, NULL);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_AZT2320,
+				mpu_port[dev], 0,
+				mpu_irq[dev], IRQF_DISABLED,
+				NULL) < 0)
+			snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx\n", mpu_port[dev]);
+	}
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card,
+				    fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_AUTO, 0, &opl3) < 0) {
+			snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n",
+				   fm_port[dev], fm_port[dev] + 2);
+		} else {
+			if ((error = snd_opl3_timer_new(opl3, 1, 2)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+			if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+				snd_card_free(card);
+				return error;
+			}
+		}
+	}
+
+	if ((error = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	return 0;
+}
+
+static unsigned int __devinitdata azt2320_devices;
+
+static int __devinit snd_azt2320_pnp_detect(struct pnp_card_link *card,
+					    const struct pnp_card_device_id *id)
+{
+	static int dev;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev])
+			continue;
+		res = snd_card_azt2320_probe(dev, card, id);
+		if (res < 0)
+			return res;
+		dev++;
+		azt2320_devices++;
+		return 0;
+	}
+        return -ENODEV;
+}
+
+static void __devexit snd_azt2320_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_azt2320_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_card_azt2320 *acard = card->private_data;
+	struct snd_wss *chip = acard->chip;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	chip->suspend(chip);
+	return 0;
+}
+
+static int snd_azt2320_pnp_resume(struct pnp_card_link *pcard)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_card_azt2320 *acard = card->private_data;
+	struct snd_wss *chip = acard->chip;
+
+	chip->resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct pnp_card_driver azt2320_pnpc_driver = {
+	.flags          = PNP_DRIVER_RES_DISABLE,
+	.name           = "azt2320",
+	.id_table       = snd_azt2320_pnpids,
+	.probe          = snd_azt2320_pnp_detect,
+	.remove         = __devexit_p(snd_azt2320_pnp_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_azt2320_pnp_suspend,
+	.resume		= snd_azt2320_pnp_resume,
+#endif
+};
+
+static int __init alsa_card_azt2320_init(void)
+{
+	int err;
+
+	err = pnp_register_card_driver(&azt2320_pnpc_driver);
+	if (err)
+		return err;
+
+	if (!azt2320_devices) {
+		pnp_unregister_card_driver(&azt2320_pnpc_driver);
+#ifdef MODULE
+		snd_printk(KERN_ERR "no AZT2320 based soundcards found\n");
+#endif
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit alsa_card_azt2320_exit(void)
+{
+	pnp_unregister_card_driver(&azt2320_pnpc_driver);
+}
+
+module_init(alsa_card_azt2320_init)
+module_exit(alsa_card_azt2320_exit)
diff --git a/linux/sound/isa/cmi8330.c b/linux/sound/isa/cmi8330.c
new file mode 100644
index 0000000..fe79a16
--- /dev/null
+++ b/linux/sound/isa/cmi8330.c
@@ -0,0 +1,782 @@
+/*
+ *  Driver for C-Media's CMI8330 and CMI8329 soundcards.
+ *  Copyright (c) by George Talusan <gstalusan@uwaterloo.ca>
+ *    http://www.undergrad.math.uwaterloo.ca/~gstalusa
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ * NOTES
+ *
+ *  The extended registers contain mixer settings which are largely
+ *  untapped for the time being.
+ *
+ *  MPU401 and SPDIF are not supported yet.  I don't have the hardware
+ *  to aid in coding and testing, so I won't bother.
+ *
+ *  To quickly load the module,
+ *
+ *  modprobe -a snd-cmi8330 sbport=0x220 sbirq=5 sbdma8=1
+ *    sbdma16=5 wssport=0x530 wssirq=11 wssdma=0 fmport=0x388
+ *
+ *  This card has two mixers and two PCM devices.  I've cheesed it such
+ *  that recording and playback can be done through the same device.
+ *  The driver "magically" routes the capturing to the AD1848 codec,
+ *  and playback to the SB16 codec.  This allows for full-duplex mode
+ *  to some extent.
+ *  The utilities in alsa-utils are aware of both devices, so passing
+ *  the appropriate parameters to amixer and alsactl will give you
+ *  full control over both mixers.
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/opl3.h>
+#include <sound/mpu401.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+/*
+ */
+/* #define ENABLE_SB_MIXER */
+#define PLAYBACK_ON_SB
+
+/*
+ */
+MODULE_AUTHOR("George Talusan <gstalusan@uwaterloo.ca>");
+MODULE_DESCRIPTION("C-Media CMI8330/CMI8329");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{C-Media,CMI8330,isapnp:{CMI0001,@@@0001,@X@0001}}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long sbport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int sbirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int sbdma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int sbdma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static long wssport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int wssirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int wssdma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static long fmport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long mpuport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int mpuirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for CMI8330/CMI8329 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string  for CMI8330/CMI8329 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable CMI8330/CMI8329 soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+
+module_param_array(sbport, long, NULL, 0444);
+MODULE_PARM_DESC(sbport, "Port # for CMI8330/CMI8329 SB driver.");
+module_param_array(sbirq, int, NULL, 0444);
+MODULE_PARM_DESC(sbirq, "IRQ # for CMI8330/CMI8329 SB driver.");
+module_param_array(sbdma8, int, NULL, 0444);
+MODULE_PARM_DESC(sbdma8, "DMA8 for CMI8330/CMI8329 SB driver.");
+module_param_array(sbdma16, int, NULL, 0444);
+MODULE_PARM_DESC(sbdma16, "DMA16 for CMI8330/CMI8329 SB driver.");
+
+module_param_array(wssport, long, NULL, 0444);
+MODULE_PARM_DESC(wssport, "Port # for CMI8330/CMI8329 WSS driver.");
+module_param_array(wssirq, int, NULL, 0444);
+MODULE_PARM_DESC(wssirq, "IRQ # for CMI8330/CMI8329 WSS driver.");
+module_param_array(wssdma, int, NULL, 0444);
+MODULE_PARM_DESC(wssdma, "DMA for CMI8330/CMI8329 WSS driver.");
+
+module_param_array(fmport, long, NULL, 0444);
+MODULE_PARM_DESC(fmport, "FM port # for CMI8330/CMI8329 driver.");
+module_param_array(mpuport, long, NULL, 0444);
+MODULE_PARM_DESC(mpuport, "MPU-401 port # for CMI8330/CMI8329 driver.");
+module_param_array(mpuirq, int, NULL, 0444);
+MODULE_PARM_DESC(mpuirq, "IRQ # for CMI8330/CMI8329 MPU-401 port.");
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+#endif
+
+#define CMI8330_RMUX3D    16
+#define CMI8330_MUTEMUX   17
+#define CMI8330_OUTPUTVOL 18
+#define CMI8330_MASTVOL   19
+#define CMI8330_LINVOL    20
+#define CMI8330_CDINVOL   21
+#define CMI8330_WAVVOL    22
+#define CMI8330_RECMUX    23
+#define CMI8330_WAVGAIN   24
+#define CMI8330_LINGAIN   25
+#define CMI8330_CDINGAIN  26
+
+static unsigned char snd_cmi8330_image[((CMI8330_CDINGAIN)-16) + 1] =
+{
+	0x40,			/* 16 - recording mux (SB-mixer-enabled) */
+#ifdef ENABLE_SB_MIXER
+	0x40,			/* 17 - mute mux (Mode2) */
+#else
+	0x0,			/* 17 - mute mux */
+#endif
+	0x0,			/* 18 - vol */
+	0x0,			/* 19 - master volume */
+	0x0,			/* 20 - line-in volume */
+	0x0,			/* 21 - cd-in volume */
+	0x0,			/* 22 - wave volume */
+	0x0,			/* 23 - mute/rec mux */
+	0x0,			/* 24 - wave rec gain */
+	0x0,			/* 25 - line-in rec gain */
+	0x0			/* 26 - cd-in rec gain */
+};
+
+typedef int (*snd_pcm_open_callback_t)(struct snd_pcm_substream *);
+
+enum card_type {
+	CMI8330,
+	CMI8329
+};
+
+struct snd_cmi8330 {
+#ifdef CONFIG_PNP
+	struct pnp_dev *cap;
+	struct pnp_dev *play;
+	struct pnp_dev *mpu;
+#endif
+	struct snd_card *card;
+	struct snd_wss *wss;
+	struct snd_sb *sb;
+
+	struct snd_pcm *pcm;
+	struct snd_cmi8330_stream {
+		struct snd_pcm_ops ops;
+		snd_pcm_open_callback_t open;
+		void *private_data; /* sb or wss */
+	} streams[2];
+
+	enum card_type type;
+};
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_cmi8330_pnpids[] = {
+	{ .id = "CMI0001", .devs = { { "@X@0001" }, { "@@@0001" }, { "@H@0001" }, { "A@@0001" } } },
+	{ .id = "CMI0001", .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } } },
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_cmi8330_pnpids);
+
+#endif
+
+
+static struct snd_kcontrol_new snd_cmi8330_controls[] __devinitdata = {
+WSS_DOUBLE("Master Playback Volume", 0,
+		CMI8330_MASTVOL, CMI8330_MASTVOL, 4, 0, 15, 0),
+WSS_SINGLE("Loud Playback Switch", 0,
+		CMI8330_MUTEMUX, 6, 1, 1),
+WSS_DOUBLE("PCM Playback Switch", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE("PCM Playback Volume", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1),
+WSS_DOUBLE("Line Playback Switch", 0,
+		CMI8330_MUTEMUX, CMI8330_MUTEMUX, 4, 3, 1, 0),
+WSS_DOUBLE("Line Playback Volume", 0,
+		CMI8330_LINVOL, CMI8330_LINVOL, 4, 0, 15, 0),
+WSS_DOUBLE("Line Capture Switch", 0,
+		CMI8330_RMUX3D, CMI8330_RMUX3D, 2, 1, 1, 0),
+WSS_DOUBLE("Line Capture Volume", 0,
+		CMI8330_LINGAIN, CMI8330_LINGAIN, 4, 0, 15, 0),
+WSS_DOUBLE("CD Playback Switch", 0,
+		CMI8330_MUTEMUX, CMI8330_MUTEMUX, 2, 1, 1, 0),
+WSS_DOUBLE("CD Capture Switch", 0,
+		CMI8330_RMUX3D, CMI8330_RMUX3D, 4, 3, 1, 0),
+WSS_DOUBLE("CD Playback Volume", 0,
+		CMI8330_CDINVOL, CMI8330_CDINVOL, 4, 0, 15, 0),
+WSS_DOUBLE("CD Capture Volume", 0,
+		CMI8330_CDINGAIN, CMI8330_CDINGAIN, 4, 0, 15, 0),
+WSS_SINGLE("Mic Playback Switch", 0,
+		CMI8330_MUTEMUX, 0, 1, 0),
+WSS_SINGLE("Mic Playback Volume", 0,
+		CMI8330_OUTPUTVOL, 0, 7, 0),
+WSS_SINGLE("Mic Capture Switch", 0,
+		CMI8330_RMUX3D, 0, 1, 0),
+WSS_SINGLE("Mic Capture Volume", 0,
+		CMI8330_OUTPUTVOL, 5, 7, 0),
+WSS_DOUBLE("Wavetable Playback Switch", 0,
+		CMI8330_RECMUX, CMI8330_RECMUX, 1, 0, 1, 0),
+WSS_DOUBLE("Wavetable Playback Volume", 0,
+		CMI8330_WAVVOL, CMI8330_WAVVOL, 4, 0, 15, 0),
+WSS_DOUBLE("Wavetable Capture Switch", 0,
+		CMI8330_RECMUX, CMI8330_RECMUX, 5, 4, 1, 0),
+WSS_DOUBLE("Wavetable Capture Volume", 0,
+		CMI8330_WAVGAIN, CMI8330_WAVGAIN, 4, 0, 15, 0),
+WSS_SINGLE("3D Control - Switch", 0,
+		CMI8330_RMUX3D, 5, 1, 1),
+WSS_SINGLE("Beep Playback Volume", 0,
+		CMI8330_OUTPUTVOL, 3, 3, 0),
+WSS_DOUBLE("FM Playback Switch", 0,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("FM Playback Volume", 0,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1),
+WSS_SINGLE(SNDRV_CTL_NAME_IEC958("Input ", CAPTURE, SWITCH), 0,
+		CMI8330_RMUX3D, 7, 1, 1),
+WSS_SINGLE(SNDRV_CTL_NAME_IEC958("Input ", PLAYBACK, SWITCH), 0,
+		CMI8330_MUTEMUX, 7, 1, 1),
+};
+
+#ifdef ENABLE_SB_MIXER
+static struct sbmix_elem cmi8330_sb_mixers[] __devinitdata = {
+SB_DOUBLE("SB Master Playback Volume", SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31),
+SB_DOUBLE("Tone Control - Bass", SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15),
+SB_DOUBLE("Tone Control - Treble", SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15),
+SB_DOUBLE("SB PCM Playback Volume", SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31),
+SB_DOUBLE("SB Synth Playback Volume", SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31),
+SB_DOUBLE("SB CD Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1),
+SB_DOUBLE("SB CD Playback Volume", SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31),
+SB_DOUBLE("SB Line Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1),
+SB_DOUBLE("SB Line Playback Volume", SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31),
+SB_SINGLE("SB Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1),
+SB_SINGLE("SB Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31),
+SB_SINGLE("SB Beep Volume", SB_DSP4_SPEAKER_DEV, 6, 3),
+SB_DOUBLE("SB Capture Volume", SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3),
+SB_DOUBLE("SB Playback Volume", SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3),
+SB_SINGLE("SB Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1),
+};
+
+static unsigned char cmi8330_sb_init_values[][2] __devinitdata = {
+	{ SB_DSP4_MASTER_DEV + 0, 0 },
+	{ SB_DSP4_MASTER_DEV + 1, 0 },
+	{ SB_DSP4_PCM_DEV + 0, 0 },
+	{ SB_DSP4_PCM_DEV + 1, 0 },
+	{ SB_DSP4_SYNTH_DEV + 0, 0 },
+	{ SB_DSP4_SYNTH_DEV + 1, 0 },
+	{ SB_DSP4_INPUT_LEFT, 0 },
+	{ SB_DSP4_INPUT_RIGHT, 0 },
+	{ SB_DSP4_OUTPUT_SW, 0 },
+	{ SB_DSP4_SPEAKER_DEV, 0 },
+};
+
+
+static int __devinit cmi8330_add_sb_mixers(struct snd_sb *chip)
+{
+	int idx, err;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	snd_sbmixer_write(chip, 0x00, 0x00);		/* mixer reset */
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+	/* mute and zero volume channels */
+	for (idx = 0; idx < ARRAY_SIZE(cmi8330_sb_init_values); idx++) {
+		spin_lock_irqsave(&chip->mixer_lock, flags);
+		snd_sbmixer_write(chip, cmi8330_sb_init_values[idx][0],
+				  cmi8330_sb_init_values[idx][1]);
+		spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	}
+
+	for (idx = 0; idx < ARRAY_SIZE(cmi8330_sb_mixers); idx++) {
+		if ((err = snd_sbmixer_add_ctl_elem(chip, &cmi8330_sb_mixers[idx])) < 0)
+			return err;
+	}
+	return 0;
+}
+#endif
+
+static int __devinit snd_cmi8330_mixer(struct snd_card *card, struct snd_cmi8330 *acard)
+{
+	unsigned int idx;
+	int err;
+
+	strcpy(card->mixername, (acard->type == CMI8329) ? "CMI8329" : "CMI8330/C3D");
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_cmi8330_controls); idx++) {
+		err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_cmi8330_controls[idx],
+					     acard->wss));
+		if (err < 0)
+			return err;
+	}
+
+#ifdef ENABLE_SB_MIXER
+	if ((err = cmi8330_add_sb_mixers(acard->sb)) < 0)
+		return err;
+#endif
+	return 0;
+}
+
+#ifdef CONFIG_PNP
+static int __devinit snd_cmi8330_pnp(int dev, struct snd_cmi8330 *acard,
+				     struct pnp_card_link *card,
+				     const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	int err;
+
+	/* CMI8329 has a device with ID A@@0001, CMI8330 does not */
+	acard->type = (id->devs[3].id[0]) ? CMI8329 : CMI8330;
+
+	acard->cap = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->cap == NULL)
+		return -EBUSY;
+
+	acard->play = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (acard->play == NULL)
+		return -EBUSY;
+
+	acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+	if (acard->mpu == NULL)
+		return -EBUSY;
+
+	pdev = acard->cap;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "AD1848 PnP configure failure\n");
+		return -EBUSY;
+	}
+	wssport[dev] = pnp_port_start(pdev, 0);
+	wssdma[dev] = pnp_dma(pdev, 0);
+	wssirq[dev] = pnp_irq(pdev, 0);
+	if (pnp_port_start(pdev, 1))
+		fmport[dev] = pnp_port_start(pdev, 1);
+
+	/* allocate SB16 resources */
+	pdev = acard->play;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "SB16 PnP configure failure\n");
+		return -EBUSY;
+	}
+	sbport[dev] = pnp_port_start(pdev, 0);
+	sbdma8[dev] = pnp_dma(pdev, 0);
+	sbdma16[dev] = pnp_dma(pdev, 1);
+	sbirq[dev] = pnp_irq(pdev, 0);
+	/* On CMI8239, the OPL3 port might be present in SB16 PnP resources */
+	if (fmport[dev] == SNDRV_AUTO_PORT) {
+		if (pnp_port_start(pdev, 1))
+			fmport[dev] = pnp_port_start(pdev, 1);
+		else
+			fmport[dev] = 0x388;	/* Or hardwired */
+	}
+
+	/* allocate MPU-401 resources */
+	pdev = acard->mpu;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0)
+		snd_printk(KERN_ERR "MPU-401 PnP configure failure: will be disabled\n");
+	else {
+		mpuport[dev] = pnp_port_start(pdev, 0);
+		mpuirq[dev] = pnp_irq(pdev, 0);
+	}
+	return 0;
+}
+#endif
+
+/*
+ * PCM interface
+ *
+ * since we call the different chip interfaces for playback and capture
+ * directions, we need a trick.
+ *
+ * - copy the ops for each direction into a local record.
+ * - replace the open callback with the new one, which replaces the
+ *   substream->private_data with the corresponding chip instance
+ *   and calls again the original open callback of the chip.
+ *
+ */
+
+#ifdef PLAYBACK_ON_SB
+#define CMI_SB_STREAM	SNDRV_PCM_STREAM_PLAYBACK
+#define CMI_AD_STREAM	SNDRV_PCM_STREAM_CAPTURE
+#else
+#define CMI_SB_STREAM	SNDRV_PCM_STREAM_CAPTURE
+#define CMI_AD_STREAM	SNDRV_PCM_STREAM_PLAYBACK
+#endif
+
+static int snd_cmi8330_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_cmi8330 *chip = snd_pcm_substream_chip(substream);
+
+	/* replace the private_data and call the original open callback */
+	substream->private_data = chip->streams[SNDRV_PCM_STREAM_PLAYBACK].private_data;
+	return chip->streams[SNDRV_PCM_STREAM_PLAYBACK].open(substream);
+}
+
+static int snd_cmi8330_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_cmi8330 *chip = snd_pcm_substream_chip(substream);
+
+	/* replace the private_data and call the original open callback */
+	substream->private_data = chip->streams[SNDRV_PCM_STREAM_CAPTURE].private_data;
+	return chip->streams[SNDRV_PCM_STREAM_CAPTURE].open(substream);
+}
+
+static int __devinit snd_cmi8330_pcm(struct snd_card *card, struct snd_cmi8330 *chip)
+{
+	struct snd_pcm *pcm;
+	const struct snd_pcm_ops *ops;
+	int err;
+	static snd_pcm_open_callback_t cmi_open_callbacks[2] = {
+		snd_cmi8330_playback_open,
+		snd_cmi8330_capture_open
+	};
+
+	if ((err = snd_pcm_new(card, (chip->type == CMI8329) ? "CMI8329" : "CMI8330", 0, 1, 1, &pcm)) < 0)
+		return err;
+	strcpy(pcm->name, (chip->type == CMI8329) ? "CMI8329" : "CMI8330");
+	pcm->private_data = chip;
+	
+	/* SB16 */
+	ops = snd_sb16dsp_get_pcm_ops(CMI_SB_STREAM);
+	chip->streams[CMI_SB_STREAM].ops = *ops;
+	chip->streams[CMI_SB_STREAM].open = ops->open;
+	chip->streams[CMI_SB_STREAM].ops.open = cmi_open_callbacks[CMI_SB_STREAM];
+	chip->streams[CMI_SB_STREAM].private_data = chip->sb;
+
+	/* AD1848 */
+	ops = snd_wss_get_pcm_ops(CMI_AD_STREAM);
+	chip->streams[CMI_AD_STREAM].ops = *ops;
+	chip->streams[CMI_AD_STREAM].open = ops->open;
+	chip->streams[CMI_AD_STREAM].ops.open = cmi_open_callbacks[CMI_AD_STREAM];
+	chip->streams[CMI_AD_STREAM].private_data = chip->wss;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &chip->streams[SNDRV_PCM_STREAM_PLAYBACK].ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &chip->streams[SNDRV_PCM_STREAM_CAPTURE].ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, 128*1024);
+	chip->pcm = pcm;
+
+	return 0;
+}
+
+
+#ifdef CONFIG_PM
+static int snd_cmi8330_suspend(struct snd_card *card)
+{
+	struct snd_cmi8330 *acard = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(acard->pcm);
+	acard->wss->suspend(acard->wss);
+	snd_sbmixer_suspend(acard->sb);
+	return 0;
+}
+
+static int snd_cmi8330_resume(struct snd_card *card)
+{
+	struct snd_cmi8330 *acard = card->private_data;
+
+	snd_sbdsp_reset(acard->sb);
+	snd_sbmixer_suspend(acard->sb);
+	acard->wss->resume(acard->wss);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+
+/*
+ */
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev)		isapnp[dev]
+#else
+#define is_isapnp_selected(dev)		0
+#endif
+
+#define PFX	"cmi8330: "
+
+static int snd_cmi8330_card_new(int dev, struct snd_card **cardp)
+{
+	struct snd_card *card;
+	struct snd_cmi8330 *acard;
+	int err;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_cmi8330), &card);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "could not get a new card\n");
+		return err;
+	}
+	acard = card->private_data;
+	acard->card = card;
+	*cardp = card;
+	return 0;
+}
+
+static int __devinit snd_cmi8330_probe(struct snd_card *card, int dev)
+{
+	struct snd_cmi8330 *acard;
+	int i, err;
+	struct snd_opl3 *opl3;
+
+	acard = card->private_data;
+	err = snd_wss_create(card, wssport[dev] + 4, -1,
+			     wssirq[dev],
+			     wssdma[dev], -1,
+			     WSS_HW_DETECT, 0, &acard->wss);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "AD1848 device busy??\n");
+		return err;
+	}
+	if (acard->wss->hardware != WSS_HW_CMI8330) {
+		snd_printk(KERN_ERR PFX "AD1848 not found during probe\n");
+		return -ENODEV;
+	}
+
+	if ((err = snd_sbdsp_create(card, sbport[dev],
+				    sbirq[dev],
+				    snd_sb16dsp_interrupt,
+				    sbdma8[dev],
+				    sbdma16[dev],
+				    SB_HW_AUTO, &acard->sb)) < 0) {
+		snd_printk(KERN_ERR PFX "SB16 device busy??\n");
+		return err;
+	}
+	if (acard->sb->hardware != SB_HW_16) {
+		snd_printk(KERN_ERR PFX "SB16 not found during probe\n");
+		return err;
+	}
+
+	snd_wss_out(acard->wss, CS4231_MISC_INFO, 0x40); /* switch on MODE2 */
+	for (i = CMI8330_RMUX3D; i <= CMI8330_CDINGAIN; i++)
+		snd_wss_out(acard->wss, i,
+			    snd_cmi8330_image[i - CMI8330_RMUX3D]);
+
+	if ((err = snd_cmi8330_mixer(card, acard)) < 0) {
+		snd_printk(KERN_ERR PFX "failed to create mixers\n");
+		return err;
+	}
+
+	if ((err = snd_cmi8330_pcm(card, acard)) < 0) {
+		snd_printk(KERN_ERR PFX "failed to create pcms\n");
+		return err;
+	}
+	if (fmport[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card,
+				    fmport[dev], fmport[dev] + 2,
+				    OPL3_HW_AUTO, 0, &opl3) < 0) {
+			snd_printk(KERN_ERR PFX
+				   "no OPL device at 0x%lx-0x%lx ?\n",
+				   fmport[dev], fmport[dev] + 2);
+		} else {
+			err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	if (mpuport[dev] != SNDRV_AUTO_PORT) {
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+					mpuport[dev], 0, mpuirq[dev],
+					IRQF_DISABLED, NULL) < 0)
+			printk(KERN_ERR PFX "no MPU-401 device at 0x%lx.\n",
+				mpuport[dev]);
+	}
+
+	strcpy(card->driver, (acard->type == CMI8329) ? "CMI8329" : "CMI8330/C3D");
+	strcpy(card->shortname, (acard->type == CMI8329) ? "C-Media CMI8329" : "C-Media CMI8330/C3D");
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		card->shortname,
+		acard->wss->port,
+		wssirq[dev],
+		wssdma[dev]);
+
+	return snd_card_register(card);
+}
+
+static int __devinit snd_cmi8330_isa_match(struct device *pdev,
+					   unsigned int dev)
+{
+	if (!enable[dev] || is_isapnp_selected(dev))
+		return 0;
+	if (wssport[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify wssport\n");
+		return 0;
+	}
+	if (sbport[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify sbport\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int __devinit snd_cmi8330_isa_probe(struct device *pdev,
+					   unsigned int dev)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_cmi8330_card_new(dev, &card);
+	if (err < 0)
+		return err;
+	snd_card_set_dev(card, pdev);
+	if ((err = snd_cmi8330_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(pdev, card);
+	return 0;
+}
+
+static int __devexit snd_cmi8330_isa_remove(struct device *devptr,
+					    unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	dev_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cmi8330_isa_suspend(struct device *dev, unsigned int n,
+				   pm_message_t state)
+{
+	return snd_cmi8330_suspend(dev_get_drvdata(dev));
+}
+
+static int snd_cmi8330_isa_resume(struct device *dev, unsigned int n)
+{
+	return snd_cmi8330_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#define DEV_NAME	"cmi8330"
+
+static struct isa_driver snd_cmi8330_driver = {
+	.match		= snd_cmi8330_isa_match,
+	.probe		= snd_cmi8330_isa_probe,
+	.remove		= __devexit_p(snd_cmi8330_isa_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_cmi8330_isa_suspend,
+	.resume		= snd_cmi8330_isa_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+
+#ifdef CONFIG_PNP
+static int __devinit snd_cmi8330_pnp_detect(struct pnp_card_link *pcard,
+					    const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+			       
+	res = snd_cmi8330_card_new(dev, &card);
+	if (res < 0)
+		return res;
+	if ((res = snd_cmi8330_pnp(dev, card->private_data, pcard, pid)) < 0) {
+		snd_printk(KERN_ERR PFX "PnP detection failed\n");
+		snd_card_free(card);
+		return res;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+	if ((res = snd_cmi8330_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_cmi8330_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_cmi8330_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	return snd_cmi8330_suspend(pnp_get_card_drvdata(pcard));
+}
+
+static int snd_cmi8330_pnp_resume(struct pnp_card_link *pcard)
+{
+	return snd_cmi8330_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver cmi8330_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "cmi8330",
+	.id_table = snd_cmi8330_pnpids,
+	.probe = snd_cmi8330_pnp_detect,
+	.remove = __devexit_p(snd_cmi8330_pnp_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_cmi8330_pnp_suspend,
+	.resume		= snd_cmi8330_pnp_resume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_cmi8330_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_cmi8330_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&cmi8330_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_cmi8330_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&cmi8330_pnpc_driver);
+
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_cmi8330_driver);
+}
+
+module_init(alsa_card_cmi8330_init)
+module_exit(alsa_card_cmi8330_exit)
diff --git a/linux/sound/isa/cs423x/Makefile b/linux/sound/isa/cs423x/Makefile
new file mode 100644
index 0000000..6d397e8
--- /dev/null
+++ b/linux/sound/isa/cs423x/Makefile
@@ -0,0 +1,13 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-cs4231-objs := cs4231.o
+snd-cs4236-objs := cs4236.o cs4236_lib.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_CS4231) += snd-cs4231.o
+obj-$(CONFIG_SND_CS4236) += snd-cs4236.o
+
+
diff --git a/linux/sound/isa/cs423x/cs4231.c b/linux/sound/isa/cs423x/cs4231.c
new file mode 100644
index 0000000..cb9153e
--- /dev/null
+++ b/linux/sound/isa/cs423x/cs4231.c
@@ -0,0 +1,205 @@
+/*
+ *  Generic driver for CS4231 chips
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Originally the CS4232/CS4232A driver, modified for use on CS4231 by
+ *  Tugrul Galatali <galatalt@stuy.edu>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+#define CRD_NAME "Generic CS4231"
+#define DEV_NAME "cs4231"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Crystal Semiconductors,CS4231}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,11,12,15 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for " CRD_NAME " driver.");
+
+static int __devinit snd_cs4231_match(struct device *dev, unsigned int n)
+{
+	if (!enable[n])
+		return 0;
+
+	if (port[n] == SNDRV_AUTO_PORT) {
+		dev_err(dev, "please specify port\n");
+		return 0;
+	}
+	if (irq[n] == SNDRV_AUTO_IRQ) {
+		dev_err(dev, "please specify irq\n");
+		return 0;
+	}
+	if (dma1[n] == SNDRV_AUTO_DMA) {
+		dev_err(dev, "please specify dma1\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int __devinit snd_cs4231_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	struct snd_wss *chip;
+	struct snd_pcm *pcm;
+	int error;
+
+	error = snd_card_create(index[n], id[n], THIS_MODULE, 0, &card);
+	if (error < 0)
+		return error;
+
+	error = snd_wss_create(card, port[n], -1, irq[n], dma1[n], dma2[n],
+			WSS_HW_DETECT, 0, &chip);
+	if (error < 0)
+		goto out;
+
+	card->private_data = chip;
+
+	error = snd_wss_pcm(chip, 0, &pcm);
+	if (error < 0)
+		goto out;
+
+	strcpy(card->driver, "CS4231");
+	strcpy(card->shortname, pcm->name);
+
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		pcm->name, chip->port, irq[n], dma1[n]);
+	if (dma2[n] >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%d", dma2[n]);
+
+	error = snd_wss_mixer(chip);
+	if (error < 0)
+		goto out;
+
+	error = snd_wss_timer(chip, 0, NULL);
+	if (error < 0)
+		goto out;
+
+	if (mpu_port[n] > 0 && mpu_port[n] != SNDRV_AUTO_PORT) {
+		if (mpu_irq[n] == SNDRV_AUTO_IRQ)
+			mpu_irq[n] = -1;
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_CS4232,
+					mpu_port[n], 0, mpu_irq[n],
+					mpu_irq[n] >= 0 ? IRQF_DISABLED : 0,
+					NULL) < 0)
+			dev_warn(dev, "MPU401 not detected\n");
+	}
+
+	snd_card_set_dev(card, dev);
+
+	error = snd_card_register(card);
+	if (error < 0)
+		goto out;
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+out:	snd_card_free(card);
+	return error;
+}
+
+static int __devexit snd_cs4231_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	dev_set_drvdata(dev, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cs4231_suspend(struct device *dev, unsigned int n, pm_message_t state)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_wss *chip = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	chip->suspend(chip);
+	return 0;
+}
+
+static int snd_cs4231_resume(struct device *dev, unsigned int n)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_wss *chip = card->private_data;
+
+	chip->resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct isa_driver snd_cs4231_driver = {
+	.match		= snd_cs4231_match,
+	.probe		= snd_cs4231_probe,
+	.remove		= __devexit_p(snd_cs4231_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_cs4231_suspend,
+	.resume		= snd_cs4231_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+static int __init alsa_card_cs4231_init(void)
+{
+	return isa_register_driver(&snd_cs4231_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_cs4231_exit(void)
+{
+	isa_unregister_driver(&snd_cs4231_driver);
+}
+
+module_init(alsa_card_cs4231_init);
+module_exit(alsa_card_cs4231_exit);
diff --git a/linux/sound/isa/cs423x/cs4236.c b/linux/sound/isa/cs423x/cs4236.c
new file mode 100644
index 0000000..999dc1e
--- /dev/null
+++ b/linux/sound/isa/cs423x/cs4236.c
@@ -0,0 +1,729 @@
+/*
+ *  Driver for generic CS4232/CS4235/CS4236/CS4236B/CS4237B/CS4238B/CS4239 chips
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Cirrus Logic CS4232-9");
+MODULE_SUPPORTED_DEVICE("{{Turtle Beach,TBS-2000},"
+		"{Turtle Beach,Tropez Plus},"
+		"{SIC CrystalWave 32},"
+		"{Hewlett Packard,Omnibook 5500},"
+		"{TerraTec,Maestro 32/96},"
+		"{Philips,PCA70PS}},"
+		"{{Crystal Semiconductors,CS4235},"
+		"{Crystal Semiconductors,CS4236},"
+		"{Crystal Semiconductors,CS4237},"
+		"{Crystal Semiconductors,CS4238},"
+		"{Crystal Semiconductors,CS4239},"
+		"{Acer,AW37},"
+		"{Acer,AW35/Pro},"
+		"{Crystal,3D},"
+		"{Crystal Computer,TidalWave128},"
+		"{Dell,Optiplex GX1},"
+		"{Dell,Workstation 400 sound},"
+		"{EliteGroup,P5TX-LA sound},"
+		"{Gallant,SC-70P},"
+		"{Gateway,E1000 Onboard CS4236B},"
+		"{Genius,Sound Maker 3DJ},"
+		"{Hewlett Packard,HP6330 sound},"
+		"{IBM,PC 300PL sound},"
+		"{IBM,Aptiva 2137 E24},"
+		"{IBM,IntelliStation M Pro},"
+		"{Intel,Marlin Spike Mobo CS4235},"
+		"{Intel PR440FX Onboard},"
+		"{Guillemot,MaxiSound 16 PnP},"
+		"{NewClear,3D},"
+		"{TerraTec,AudioSystem EWS64L/XL},"
+		"{Typhoon Soundsystem,CS4236B},"
+		"{Turtle Beach,Malibu},"
+		"{Unknown,Digital PC 5000 Onboard}}");
+
+MODULE_ALIAS("snd_cs4232");
+
+#define IDENT "CS4232+"
+#define DEV_NAME "cs4232+"
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long cport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* PnP setup */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static long sb_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,11,12,15 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " IDENT " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " IDENT " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " IDENT " soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " IDENT " driver.");
+module_param_array(cport, long, NULL, 0444);
+MODULE_PARM_DESC(cport, "Control port # for " IDENT " driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " IDENT " driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for " IDENT " driver.");
+module_param_array(sb_port, long, NULL, 0444);
+MODULE_PARM_DESC(sb_port, "SB port # for " IDENT " driver (optional).");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " IDENT " driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " IDENT " driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " IDENT " driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for " IDENT " driver.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnpc_registered;
+static int pnp_registered;
+#endif /* CONFIG_PNP */
+
+struct snd_card_cs4236 {
+	struct snd_wss *chip;
+	struct resource *res_sb_port;
+#ifdef CONFIG_PNP
+	struct pnp_dev *wss;
+	struct pnp_dev *ctrl;
+	struct pnp_dev *mpu;
+#endif
+};
+
+#ifdef CONFIG_PNP
+
+/*
+ * PNP BIOS
+ */
+static const struct pnp_device_id snd_cs423x_pnpbiosids[] = {
+	{ .id = "CSC0100" },
+	{ .id = "CSC0000" },
+	/* Guillemot Turtlebeach something appears to be cs4232 compatible
+	 * (untested) */
+	{ .id = "GIM0100" },
+	{ .id = "" }
+};
+MODULE_DEVICE_TABLE(pnp, snd_cs423x_pnpbiosids);
+
+#define CS423X_ISAPNP_DRIVER	"cs4232_isapnp"
+static struct pnp_card_device_id snd_cs423x_pnpids[] = {
+	/* Philips PCA70PS */
+	{ .id = "CSC0d32", .devs = { { "CSC0000" }, { "CSC0010" }, { "PNPb006" } } },
+	/* TerraTec Maestro 32/96 (CS4232) */
+	{ .id = "CSC1a32", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* HP Omnibook 5500 onboard */
+	{ .id = "CSC4232", .devs = { { "CSC0000" }, { "CSC0002" }, { "CSC0003" } } },
+	/* Unnamed CS4236 card (Made in Taiwan) */
+	{ .id = "CSC4236", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Turtle Beach TBS-2000 (CS4232) */
+	{ .id = "CSC7532", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSCb006" } } },
+	/* Turtle Beach Tropez Plus (CS4232) */
+	{ .id = "CSC7632", .devs = { { "CSC0000" }, { "CSC0010" }, { "PNPb006" } } },
+	/* SIC CrystalWave 32 (CS4232) */
+	{ .id = "CSCf032", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Netfinity 3000 on-board soundcard */
+	{ .id = "CSCe825", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC010f" } } },
+	/* Intel Marlin Spike Motherboard - CS4235 */
+	{ .id = "CSC0225", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Intel Marlin Spike Motherboard (#2) - CS4235 */
+	{ .id = "CSC0225", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+	/* Unknown Intel mainboard - CS4235 */
+	{ .id = "CSC0225", .devs = { { "CSC0100" }, { "CSC0110" } } },
+	/* Genius Sound Maker 3DJ - CS4237B */
+	{ .id = "CSC0437", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Digital PC 5000 Onboard - CS4236B */
+	{ .id = "CSC0735", .devs = { { "CSC0000" }, { "CSC0010" } } },
+	/* some unknown CS4236B */
+	{ .id = "CSC0b35", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Intel PR440FX Onboard sound */
+	{ .id = "CSC0b36", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* CS4235 on mainboard without MPU */
+	{ .id = "CSC1425", .devs = { { "CSC0100" }, { "CSC0110" } } },
+	/* Gateway E1000 Onboard CS4236B */
+	{ .id = "CSC1335", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* HP 6330 Onboard sound */
+	{ .id = "CSC1525", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+	/* Crystal Computer TidalWave128 */
+	{ .id = "CSC1e37", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* ACER AW37 - CS4235 */
+	{ .id = "CSC4236", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* build-in soundcard in EliteGroup P5TX-LA motherboard - CS4237B */
+	{ .id = "CSC4237", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Crystal 3D - CS4237B */
+	{ .id = "CSC4336", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Typhoon Soundsystem PnP - CS4236B */
+	{ .id = "CSC4536", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Crystal CX4235-XQ3 EP - CS4235 */
+	{ .id = "CSC4625", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+	/* Crystal Semiconductors CS4237B */
+	{ .id = "CSC4637", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* NewClear 3D - CX4237B-XQ3 */
+	{ .id = "CSC4837", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Dell Optiplex GX1 - CS4236B */
+	{ .id = "CSC6835", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Dell P410 motherboard - CS4236B */
+	{ .id = "CSC6835", .devs = { { "CSC0000" }, { "CSC0010" } } },
+	/* Dell Workstation 400 Onboard - CS4236B */
+	{ .id = "CSC6836", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Turtle Beach Malibu - CS4237B */
+	{ .id = "CSC7537", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* CS4235 - onboard */
+	{ .id = "CSC8025", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } },
+	/* IBM Aptiva 2137 E24 Onboard - CS4237B */
+	{ .id = "CSC8037", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* IBM IntelliStation M Pro motherboard */
+	{ .id = "CSCc835", .devs = { { "CSC0000" }, { "CSC0010" } } },
+	/* Guillemot MaxiSound 16 PnP - CS4236B */
+	{ .id = "CSC9836", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Gallant SC-70P */
+	{ .id = "CSC9837", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* Techmakers MF-4236PW */
+	{ .id = "CSCa736", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* TerraTec AudioSystem EWS64XL - CS4236B */
+	{ .id = "CSCa836", .devs = { { "CSCa800" }, { "CSCa810" }, { "CSCa803" } } },
+	/* TerraTec AudioSystem EWS64XL - CS4236B */
+	{ .id = "CSCa836", .devs = { { "CSCa800" }, { "CSCa810" } } },
+	/* ACER AW37/Pro - CS4235 */
+	{ .id = "CSCd925", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* ACER AW35/Pro - CS4237B */
+	{ .id = "CSCd937", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* CS4235 without MPU401 */
+	{ .id = "CSCe825", .devs = { { "CSC0100" }, { "CSC0110" } } },
+	/* Unknown SiS530 - CS4235 */
+	{ .id = "CSC4825", .devs = { { "CSC0100" }, { "CSC0110" } } },
+	/* IBM IntelliStation M Pro 6898 11U - CS4236B */
+	{ .id = "CSCe835", .devs = { { "CSC0000" }, { "CSC0010" } } },
+	/* IBM PC 300PL Onboard - CS4236B */
+	{ .id = "CSCe836", .devs = { { "CSC0000" }, { "CSC0010" } } },
+	/* Some noname CS4236 based card */
+	{ .id = "CSCe936", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* CS4236B */
+	{ .id = "CSCf235", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* CS4236B */
+	{ .id = "CSCf238", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } },
+	/* --- */
+	{ .id = "" }	/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_cs423x_pnpids);
+
+/* WSS initialization */
+static int __devinit snd_cs423x_pnp_init_wss(int dev, struct pnp_dev *pdev)
+{
+	if (pnp_activate_dev(pdev) < 0) {
+		printk(KERN_ERR IDENT " WSS PnP configure failed for WSS (out of resources?)\n");
+		return -EBUSY;
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	if (fm_port[dev] > 0)
+		fm_port[dev] = pnp_port_start(pdev, 1);
+	sb_port[dev] = pnp_port_start(pdev, 2);
+	irq[dev] = pnp_irq(pdev, 0);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1) == 4 ? -1 : (int)pnp_dma(pdev, 1);
+	snd_printdd("isapnp WSS: wss port=0x%lx, fm port=0x%lx, sb port=0x%lx\n",
+			port[dev], fm_port[dev], sb_port[dev]);
+	snd_printdd("isapnp WSS: irq=%i, dma1=%i, dma2=%i\n",
+			irq[dev], dma1[dev], dma2[dev]);
+	return 0;
+}
+
+/* CTRL initialization */
+static int __devinit snd_cs423x_pnp_init_ctrl(int dev, struct pnp_dev *pdev)
+{
+	if (pnp_activate_dev(pdev) < 0) {
+		printk(KERN_ERR IDENT " CTRL PnP configure failed for WSS (out of resources?)\n");
+		return -EBUSY;
+	}
+	cport[dev] = pnp_port_start(pdev, 0);
+	snd_printdd("isapnp CTRL: control port=0x%lx\n", cport[dev]);
+	return 0;
+}
+
+/* MPU initialization */
+static int __devinit snd_cs423x_pnp_init_mpu(int dev, struct pnp_dev *pdev)
+{
+	if (pnp_activate_dev(pdev) < 0) {
+		printk(KERN_ERR IDENT " MPU401 PnP configure failed for WSS (out of resources?)\n");
+		mpu_port[dev] = SNDRV_AUTO_PORT;
+		mpu_irq[dev] = SNDRV_AUTO_IRQ;
+	} else {
+		mpu_port[dev] = pnp_port_start(pdev, 0);
+		if (mpu_irq[dev] >= 0 &&
+		    pnp_irq_valid(pdev, 0) && pnp_irq(pdev, 0) >= 0) {
+			mpu_irq[dev] = pnp_irq(pdev, 0);
+		} else {
+			mpu_irq[dev] = -1;	/* disable interrupt */
+		}
+	}
+	snd_printdd("isapnp MPU: port=0x%lx, irq=%i\n", mpu_port[dev], mpu_irq[dev]);
+	return 0;
+}
+
+static int __devinit snd_card_cs423x_pnp(int dev, struct snd_card_cs4236 *acard,
+					 struct pnp_dev *pdev,
+					 struct pnp_dev *cdev)
+{
+	acard->wss = pdev;
+	if (snd_cs423x_pnp_init_wss(dev, acard->wss) < 0)
+		return -EBUSY;
+	if (cdev)
+		cport[dev] = pnp_port_start(cdev, 0);
+	else
+		cport[dev] = -1;
+	return 0;
+}
+
+static int __devinit snd_card_cs423x_pnpc(int dev, struct snd_card_cs4236 *acard,
+					  struct pnp_card_link *card,
+					  const struct pnp_card_device_id *id)
+{
+	acard->wss = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->wss == NULL)
+		return -EBUSY;
+	acard->ctrl = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (acard->ctrl == NULL)
+		return -EBUSY;
+	if (id->devs[2].id[0]) {
+		acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+		if (acard->mpu == NULL)
+			return -EBUSY;
+	}
+
+	/* WSS initialization */
+	if (snd_cs423x_pnp_init_wss(dev, acard->wss) < 0)
+		return -EBUSY;
+
+	/* CTRL initialization */
+	if (acard->ctrl && cport[dev] > 0) {
+		if (snd_cs423x_pnp_init_ctrl(dev, acard->ctrl) < 0)
+			return -EBUSY;
+	}
+	/* MPU initialization */
+	if (acard->mpu && mpu_port[dev] > 0) {
+		if (snd_cs423x_pnp_init_mpu(dev, acard->mpu) < 0)
+			return -EBUSY;
+	}
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev)		isapnp[dev]
+#else
+#define is_isapnp_selected(dev)		0
+#endif
+
+static void snd_card_cs4236_free(struct snd_card *card)
+{
+	struct snd_card_cs4236 *acard = card->private_data;
+
+	release_and_free_resource(acard->res_sb_port);
+}
+
+static int snd_cs423x_card_new(int dev, struct snd_card **cardp)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_card_cs4236), &card);
+	if (err < 0)
+		return err;
+	card->private_free = snd_card_cs4236_free;
+	*cardp = card;
+	return 0;
+}
+
+static int __devinit snd_cs423x_probe(struct snd_card *card, int dev)
+{
+	struct snd_card_cs4236 *acard;
+	struct snd_pcm *pcm;
+	struct snd_wss *chip;
+	struct snd_opl3 *opl3;
+	int err;
+
+	acard = card->private_data;
+	if (sb_port[dev] > 0 && sb_port[dev] != SNDRV_AUTO_PORT)
+		if ((acard->res_sb_port = request_region(sb_port[dev], 16, IDENT " SB")) == NULL) {
+			printk(KERN_ERR IDENT ": unable to register SB port at 0x%lx\n", sb_port[dev]);
+			return -EBUSY;
+		}
+
+	err = snd_cs4236_create(card, port[dev], cport[dev],
+			     irq[dev],
+			     dma1[dev], dma2[dev],
+			     WSS_HW_DETECT3, 0, &chip);
+	if (err < 0)
+		return err;
+
+	acard->chip = chip;
+	if (chip->hardware & WSS_HW_CS4236B_MASK) {
+
+		err = snd_cs4236_pcm(chip, 0, &pcm);
+		if (err < 0)
+			return err;
+
+		err = snd_cs4236_mixer(chip);
+		if (err < 0)
+			return err;
+	} else {
+		err = snd_wss_pcm(chip, 0, &pcm);
+		if (err < 0)
+			return err;
+
+		err = snd_wss_mixer(chip);
+		if (err < 0)
+			return err;
+	}
+	strcpy(card->driver, pcm->name);
+	strcpy(card->shortname, pcm->name);
+	sprintf(card->longname, "%s at 0x%lx, irq %i, dma %i",
+		pcm->name,
+		chip->port,
+		irq[dev],
+		dma1[dev]);
+	if (dma2[dev] >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%d", dma2[dev]);
+
+	err = snd_wss_timer(chip, 0, NULL);
+	if (err < 0)
+		return err;
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card,
+				    fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_OPL3_CS, 0, &opl3) < 0) {
+			printk(KERN_WARNING IDENT ": OPL3 not detected\n");
+		} else {
+			if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0)
+				return err;
+		}
+	}
+
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+			mpu_irq[dev] = -1;
+		if (snd_mpu401_uart_new(card, 0, MPU401_HW_CS4232,
+					mpu_port[dev], 0,
+					mpu_irq[dev],
+					mpu_irq[dev] >= 0 ? IRQF_DISABLED : 0, NULL) < 0)
+			printk(KERN_WARNING IDENT ": MPU401 not detected\n");
+	}
+
+	return snd_card_register(card);
+}
+
+static int __devinit snd_cs423x_isa_match(struct device *pdev,
+					  unsigned int dev)
+{
+	if (!enable[dev] || is_isapnp_selected(dev))
+		return 0;
+
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		dev_err(pdev, "please specify port\n");
+		return 0;
+	}
+	if (cport[dev] == SNDRV_AUTO_PORT) {
+		dev_err(pdev, "please specify cport\n");
+		return 0;
+	}
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		dev_err(pdev, "please specify irq\n");
+		return 0;
+	}
+	if (dma1[dev] == SNDRV_AUTO_DMA) {
+		dev_err(pdev, "please specify dma1\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int __devinit snd_cs423x_isa_probe(struct device *pdev,
+					  unsigned int dev)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_cs423x_card_new(dev, &card);
+	if (err < 0)
+		return err;
+	snd_card_set_dev(card, pdev);
+	if ((err = snd_cs423x_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	dev_set_drvdata(pdev, card);
+	return 0;
+}
+
+static int __devexit snd_cs423x_isa_remove(struct device *pdev,
+					   unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(pdev));
+	dev_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_cs423x_suspend(struct snd_card *card)
+{
+	struct snd_card_cs4236 *acard = card->private_data;
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	acard->chip->suspend(acard->chip);
+	return 0;
+}
+
+static int snd_cs423x_resume(struct snd_card *card)
+{
+	struct snd_card_cs4236 *acard = card->private_data;
+	acard->chip->resume(acard->chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+
+static int snd_cs423x_isa_suspend(struct device *dev, unsigned int n,
+				  pm_message_t state)
+{
+	return snd_cs423x_suspend(dev_get_drvdata(dev));
+}
+
+static int snd_cs423x_isa_resume(struct device *dev, unsigned int n)
+{
+	return snd_cs423x_resume(dev_get_drvdata(dev));
+}
+#endif
+
+static struct isa_driver cs423x_isa_driver = {
+	.match		= snd_cs423x_isa_match,
+	.probe		= snd_cs423x_isa_probe,
+	.remove		= __devexit_p(snd_cs423x_isa_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_cs423x_isa_suspend,
+	.resume		= snd_cs423x_isa_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+
+#ifdef CONFIG_PNP
+static int __devinit snd_cs423x_pnpbios_detect(struct pnp_dev *pdev,
+					       const struct pnp_device_id *id)
+{
+	static int dev;
+	int err;
+	struct snd_card *card;
+	struct pnp_dev *cdev;
+	char cid[PNP_ID_LEN];
+
+	if (pnp_device_is_isapnp(pdev))
+		return -ENOENT;	/* we have another procedure - card */
+	for (; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	/* prepare second id */
+	strcpy(cid, pdev->id[0].id);
+	cid[5] = '1';
+	cdev = NULL;
+	list_for_each_entry(cdev, &(pdev->protocol->devices), protocol_list) {
+		if (!strcmp(cdev->id[0].id, cid))
+			break;
+	}
+	err = snd_cs423x_card_new(dev, &card);
+	if (err < 0)
+		return err;
+	err = snd_card_cs423x_pnp(dev, card->private_data, pdev, cdev);
+	if (err < 0) {
+		printk(KERN_ERR "PnP BIOS detection failed for " IDENT "\n");
+		snd_card_free(card);
+		return err;
+	}
+	snd_card_set_dev(card, &pdev->dev);
+	if ((err = snd_cs423x_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pnp_set_drvdata(pdev, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_cs423x_pnp_remove(struct pnp_dev *pdev)
+{
+	snd_card_free(pnp_get_drvdata(pdev));
+	pnp_set_drvdata(pdev, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_cs423x_pnp_suspend(struct pnp_dev *pdev, pm_message_t state)
+{
+	return snd_cs423x_suspend(pnp_get_drvdata(pdev));
+}
+
+static int snd_cs423x_pnp_resume(struct pnp_dev *pdev)
+{
+	return snd_cs423x_resume(pnp_get_drvdata(pdev));
+}
+#endif
+
+static struct pnp_driver cs423x_pnp_driver = {
+	.name = "cs423x-pnpbios",
+	.id_table = snd_cs423x_pnpbiosids,
+	.probe = snd_cs423x_pnpbios_detect,
+	.remove = __devexit_p(snd_cs423x_pnp_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_cs423x_pnp_suspend,
+	.resume		= snd_cs423x_pnp_resume,
+#endif
+};
+
+static int __devinit snd_cs423x_pnpc_detect(struct pnp_card_link *pcard,
+					    const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	res = snd_cs423x_card_new(dev, &card);
+	if (res < 0)
+		return res;
+	if ((res = snd_card_cs423x_pnpc(dev, card->private_data, pcard, pid)) < 0) {
+		printk(KERN_ERR "isapnp detection failed and probing for " IDENT
+		       " is not supported\n");
+		snd_card_free(card);
+		return res;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+	if ((res = snd_cs423x_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_cs423x_pnpc_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_cs423x_pnpc_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	return snd_cs423x_suspend(pnp_get_card_drvdata(pcard));
+}
+
+static int snd_cs423x_pnpc_resume(struct pnp_card_link *pcard)
+{
+	return snd_cs423x_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver cs423x_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = CS423X_ISAPNP_DRIVER,
+	.id_table = snd_cs423x_pnpids,
+	.probe = snd_cs423x_pnpc_detect,
+	.remove = __devexit_p(snd_cs423x_pnpc_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_cs423x_pnpc_suspend,
+	.resume		= snd_cs423x_pnpc_resume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_cs423x_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&cs423x_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+	err = pnp_register_driver(&cs423x_pnp_driver);
+	if (!err)
+		pnp_registered = 1;
+	err = pnp_register_card_driver(&cs423x_pnpc_driver);
+	if (!err)
+		pnpc_registered = 1;
+	if (pnp_registered)
+		err = 0;
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_cs423x_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnpc_registered)
+		pnp_unregister_card_driver(&cs423x_pnpc_driver);
+	if (pnp_registered)
+		pnp_unregister_driver(&cs423x_pnp_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&cs423x_isa_driver);
+}
+
+module_init(alsa_card_cs423x_init)
+module_exit(alsa_card_cs423x_exit)
diff --git a/linux/sound/isa/cs423x/cs4236_lib.c b/linux/sound/isa/cs423x/cs4236_lib.c
new file mode 100644
index 0000000..c5adca3
--- /dev/null
+++ b/linux/sound/isa/cs423x/cs4236_lib.c
@@ -0,0 +1,1089 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for control of CS4235/4236B/4237B/4238B/4239 chips
+ *
+ *  Note:
+ *     -----
+ *
+ *  Bugs:
+ *     -----
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ *  Indirect control registers (CS4236B+)
+ * 
+ *  C0
+ *     D8: WSS reset (all chips)
+ *
+ *  C1 (all chips except CS4236)
+ *     D7-D5: version 
+ *     D4-D0: chip id
+ *             11101 - CS4235
+ *             01011 - CS4236B
+ *             01000 - CS4237B
+ *             01001 - CS4238B
+ *             11110 - CS4239
+ *
+ *  C2
+ *     D7-D4: 3D Space (CS4235,CS4237B,CS4238B,CS4239)
+ *     D3-D0: 3D Center (CS4237B); 3D Volume (CS4238B)
+ * 
+ *  C3
+ *     D7: 3D Enable (CS4237B)
+ *     D6: 3D Mono Enable (CS4237B)
+ *     D5: 3D Serial Output (CS4237B,CS4238B)
+ *     D4: 3D Enable (CS4235,CS4238B,CS4239)
+ *
+ *  C4
+ *     D7: consumer serial port enable (CS4237B,CS4238B)
+ *     D6: channels status block reset (CS4237B,CS4238B)
+ *     D5: user bit in sub-frame of digital audio data (CS4237B,CS4238B)
+ *     D4: validity bit bit in sub-frame of digital audio data (CS4237B,CS4238B)
+ * 
+ *  C5  lower channel status (digital serial data description) (CS4237B,CS4238B)
+ *     D7-D6: first two bits of category code
+ *     D5: lock
+ *     D4-D3: pre-emphasis (0 = none, 1 = 50/15us)
+ *     D2: copy/copyright (0 = copy inhibited)
+ *     D1: 0 = digital audio / 1 = non-digital audio
+ *     
+ *  C6  upper channel status (digital serial data description) (CS4237B,CS4238B)
+ *     D7-D6: sample frequency (0 = 44.1kHz)
+ *     D5: generation status (0 = no indication, 1 = original/commercially precaptureed data)
+ *     D4-D0: category code (upper bits)
+ *
+ *  C7  reserved (must write 0)
+ *
+ *  C8  wavetable control
+ *     D7: volume control interrupt enable (CS4235,CS4239)
+ *     D6: hardware volume control format (CS4235,CS4239)
+ *     D3: wavetable serial port enable (all chips)
+ *     D2: DSP serial port switch (all chips)
+ *     D1: disable MCLK (all chips)
+ *     D0: force BRESET low (all chips)
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+/*
+ *
+ */
+
+static unsigned char snd_cs4236_ext_map[18] = {
+	/* CS4236_LEFT_LINE */		0xff,
+	/* CS4236_RIGHT_LINE */		0xff,
+	/* CS4236_LEFT_MIC */		0xdf,
+	/* CS4236_RIGHT_MIC */		0xdf,
+	/* CS4236_LEFT_MIX_CTRL */	0xe0 | 0x18,
+	/* CS4236_RIGHT_MIX_CTRL */	0xe0,
+	/* CS4236_LEFT_FM */		0xbf,
+	/* CS4236_RIGHT_FM */		0xbf,
+	/* CS4236_LEFT_DSP */		0xbf,
+	/* CS4236_RIGHT_DSP */		0xbf,
+	/* CS4236_RIGHT_LOOPBACK */	0xbf,
+	/* CS4236_DAC_MUTE */		0xe0,
+	/* CS4236_ADC_RATE */		0x01,	/* 48kHz */
+	/* CS4236_DAC_RATE */		0x01,	/* 48kHz */
+	/* CS4236_LEFT_MASTER */	0xbf,
+	/* CS4236_RIGHT_MASTER */	0xbf,
+	/* CS4236_LEFT_WAVE */		0xbf,
+	/* CS4236_RIGHT_WAVE */		0xbf
+};
+
+/*
+ *
+ */
+
+static void snd_cs4236_ctrl_out(struct snd_wss *chip,
+				unsigned char reg, unsigned char val)
+{
+	outb(reg, chip->cport + 3);
+	outb(chip->cimage[reg] = val, chip->cport + 4);
+}
+
+static unsigned char snd_cs4236_ctrl_in(struct snd_wss *chip, unsigned char reg)
+{
+	outb(reg, chip->cport + 3);
+	return inb(chip->cport + 4);
+}
+
+/*
+ *  PCM
+ */
+
+#define CLOCKS 8
+
+static struct snd_ratnum clocks[CLOCKS] = {
+	{ .num = 16934400, .den_min = 353, .den_max = 353, .den_step = 1 },
+	{ .num = 16934400, .den_min = 529, .den_max = 529, .den_step = 1 },
+	{ .num = 16934400, .den_min = 617, .den_max = 617, .den_step = 1 },
+	{ .num = 16934400, .den_min = 1058, .den_max = 1058, .den_step = 1 },
+	{ .num = 16934400, .den_min = 1764, .den_max = 1764, .den_step = 1 },
+	{ .num = 16934400, .den_min = 2117, .den_max = 2117, .den_step = 1 },
+	{ .num = 16934400, .den_min = 2558, .den_max = 2558, .den_step = 1 },
+	{ .num = 16934400/16, .den_min = 21, .den_max = 192, .den_step = 1 }
+};
+
+static struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = {
+	.nrats = CLOCKS,
+	.rats = clocks,
+};
+
+static int snd_cs4236_xrate(struct snd_pcm_runtime *runtime)
+{
+	return snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+					     &hw_constraints_clocks);
+}
+
+static unsigned char divisor_to_rate_register(unsigned int divisor)
+{
+	switch (divisor) {
+	case 353:	return 1;
+	case 529:	return 2;
+	case 617:	return 3;
+	case 1058:	return 4;
+	case 1764:	return 5;
+	case 2117:	return 6;
+	case 2558:	return 7;
+	default:
+		if (divisor < 21 || divisor > 192) {
+			snd_BUG();
+			return 192;
+		}
+		return divisor;
+	}
+}
+
+static void snd_cs4236_playback_format(struct snd_wss *chip,
+				       struct snd_pcm_hw_params *params,
+				       unsigned char pdfr)
+{
+	unsigned long flags;
+	unsigned char rate = divisor_to_rate_register(params->rate_den);
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	/* set fast playback format change and clean playback FIFO */
+	snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+		    chip->image[CS4231_ALT_FEATURE_1] | 0x10);
+	snd_wss_out(chip, CS4231_PLAYBK_FORMAT, pdfr & 0xf0);
+	snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+		    chip->image[CS4231_ALT_FEATURE_1] & ~0x10);
+	snd_cs4236_ext_out(chip, CS4236_DAC_RATE, rate);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs4236_capture_format(struct snd_wss *chip,
+				      struct snd_pcm_hw_params *params,
+				      unsigned char cdfr)
+{
+	unsigned long flags;
+	unsigned char rate = divisor_to_rate_register(params->rate_den);
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	/* set fast capture format change and clean capture FIFO */
+	snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+		    chip->image[CS4231_ALT_FEATURE_1] | 0x20);
+	snd_wss_out(chip, CS4231_REC_FORMAT, cdfr & 0xf0);
+	snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+		    chip->image[CS4231_ALT_FEATURE_1] & ~0x20);
+	snd_cs4236_ext_out(chip, CS4236_ADC_RATE, rate);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+#ifdef CONFIG_PM
+
+static void snd_cs4236_suspend(struct snd_wss *chip)
+{
+	int reg;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (reg = 0; reg < 32; reg++)
+		chip->image[reg] = snd_wss_in(chip, reg);
+	for (reg = 0; reg < 18; reg++)
+		chip->eimage[reg] = snd_cs4236_ext_in(chip, CS4236_I23VAL(reg));
+	for (reg = 2; reg < 9; reg++)
+		chip->cimage[reg] = snd_cs4236_ctrl_in(chip, reg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs4236_resume(struct snd_wss *chip)
+{
+	int reg;
+	unsigned long flags;
+	
+	snd_wss_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (reg = 0; reg < 32; reg++) {
+		switch (reg) {
+		case CS4236_EXT_REG:
+		case CS4231_VERSION:
+		case 27:	/* why? CS4235 - master left */
+		case 29:	/* why? CS4235 - master right */
+			break;
+		default:
+			snd_wss_out(chip, reg, chip->image[reg]);
+			break;
+		}
+	}
+	for (reg = 0; reg < 18; reg++)
+		snd_cs4236_ext_out(chip, CS4236_I23VAL(reg), chip->eimage[reg]);
+	for (reg = 2; reg < 9; reg++) {
+		switch (reg) {
+		case 7:
+			break;
+		default:
+			snd_cs4236_ctrl_out(chip, reg, chip->cimage[reg]);
+		}
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_down(chip);
+}
+
+#endif /* CONFIG_PM */
+/*
+ * This function does no fail if the chip is not CS4236B or compatible.
+ * It just an equivalent to the snd_wss_create() then.
+ */
+int snd_cs4236_create(struct snd_card *card,
+		      unsigned long port,
+		      unsigned long cport,
+		      int irq, int dma1, int dma2,
+		      unsigned short hardware,
+		      unsigned short hwshare,
+		      struct snd_wss **rchip)
+{
+	struct snd_wss *chip;
+	unsigned char ver1, ver2;
+	unsigned int reg;
+	int err;
+
+	*rchip = NULL;
+	if (hardware == WSS_HW_DETECT)
+		hardware = WSS_HW_DETECT3;
+
+	err = snd_wss_create(card, port, cport,
+			     irq, dma1, dma2, hardware, hwshare, &chip);
+	if (err < 0)
+		return err;
+
+	if ((chip->hardware & WSS_HW_CS4236B_MASK) == 0) {
+		snd_printd("chip is not CS4236+, hardware=0x%x\n",
+			   chip->hardware);
+		*rchip = chip;
+		return 0;
+	}
+#if 0
+	{
+		int idx;
+		for (idx = 0; idx < 8; idx++)
+			snd_printk(KERN_DEBUG "CD%i = 0x%x\n",
+				   idx, inb(chip->cport + idx));
+		for (idx = 0; idx < 9; idx++)
+			snd_printk(KERN_DEBUG "C%i = 0x%x\n",
+				   idx, snd_cs4236_ctrl_in(chip, idx));
+	}
+#endif
+	if (cport < 0x100 || cport == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR "please, specify control port "
+			   "for CS4236+ chips\n");
+		snd_device_free(card, chip);
+		return -ENODEV;
+	}
+	ver1 = snd_cs4236_ctrl_in(chip, 1);
+	ver2 = snd_cs4236_ext_in(chip, CS4236_VERSION);
+	snd_printdd("CS4236: [0x%lx] C1 (version) = 0x%x, ext = 0x%x\n",
+			cport, ver1, ver2);
+	if (ver1 != ver2) {
+		snd_printk(KERN_ERR "CS4236+ chip detected, but "
+			   "control port 0x%lx is not valid\n", cport);
+		snd_device_free(card, chip);
+		return -ENODEV;
+	}
+	snd_cs4236_ctrl_out(chip, 0, 0x00);
+	snd_cs4236_ctrl_out(chip, 2, 0xff);
+	snd_cs4236_ctrl_out(chip, 3, 0x00);
+	snd_cs4236_ctrl_out(chip, 4, 0x80);
+	reg = ((IEC958_AES1_CON_PCM_CODER & 3) << 6) |
+	      IEC958_AES0_CON_EMPHASIS_NONE;
+	snd_cs4236_ctrl_out(chip, 5, reg);
+	snd_cs4236_ctrl_out(chip, 6, IEC958_AES1_CON_PCM_CODER >> 2);
+	snd_cs4236_ctrl_out(chip, 7, 0x00);
+	/*
+	 * 0x8c for C8 is valid for Turtle Beach Malibu - the IEC-958
+	 * output is working with this setup, other hardware should
+	 * have different signal paths and this value should be
+	 * selectable in the future
+	 */
+	snd_cs4236_ctrl_out(chip, 8, 0x8c);
+	chip->rate_constraint = snd_cs4236_xrate;
+	chip->set_playback_format = snd_cs4236_playback_format;
+	chip->set_capture_format = snd_cs4236_capture_format;
+#ifdef CONFIG_PM
+	chip->suspend = snd_cs4236_suspend;
+	chip->resume = snd_cs4236_resume;
+#endif
+
+	/* initialize extended registers */
+	for (reg = 0; reg < sizeof(snd_cs4236_ext_map); reg++)
+		snd_cs4236_ext_out(chip, CS4236_I23VAL(reg),
+				   snd_cs4236_ext_map[reg]);
+
+	/* initialize compatible but more featured registers */
+	snd_wss_out(chip, CS4231_LEFT_INPUT, 0x40);
+	snd_wss_out(chip, CS4231_RIGHT_INPUT, 0x40);
+	snd_wss_out(chip, CS4231_AUX1_LEFT_INPUT, 0xff);
+	snd_wss_out(chip, CS4231_AUX1_RIGHT_INPUT, 0xff);
+	snd_wss_out(chip, CS4231_AUX2_LEFT_INPUT, 0xdf);
+	snd_wss_out(chip, CS4231_AUX2_RIGHT_INPUT, 0xdf);
+	snd_wss_out(chip, CS4231_RIGHT_LINE_IN, 0xff);
+	snd_wss_out(chip, CS4231_LEFT_LINE_IN, 0xff);
+	snd_wss_out(chip, CS4231_RIGHT_LINE_IN, 0xff);
+	switch (chip->hardware) {
+	case WSS_HW_CS4235:
+	case WSS_HW_CS4239:
+		snd_wss_out(chip, CS4235_LEFT_MASTER, 0xff);
+		snd_wss_out(chip, CS4235_RIGHT_MASTER, 0xff);
+		break;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+int snd_cs4236_pcm(struct snd_wss *chip, int device, struct snd_pcm **rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+	
+	err = snd_wss_pcm(chip, device, &pcm);
+	if (err < 0)
+		return err;
+	pcm->info_flags &= ~SNDRV_PCM_INFO_JOINT_DUPLEX;
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+/*
+ *  MIXER
+ */
+
+#define CS4236_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_single, \
+  .get = snd_cs4236_get_single, .put = snd_cs4236_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+#define CS4236_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .info = snd_cs4236_info_single, \
+  .get = snd_cs4236_get_single, .put = snd_cs4236_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_cs4236_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_cs4236_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->eimage[CS4236_REG(reg)] >> shift) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_cs4236_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = (chip->eimage[CS4236_REG(reg)] & ~(mask << shift)) | val;
+	change = val != chip->eimage[CS4236_REG(reg)];
+	snd_cs4236_ext_out(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define CS4236_SINGLEC(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_single, \
+  .get = snd_cs4236_get_singlec, .put = snd_cs4236_put_singlec, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_cs4236_get_singlec(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->cimage[reg] >> shift) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_cs4236_put_singlec(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = (chip->cimage[reg] & ~(mask << shift)) | val;
+	change = val != chip->cimage[reg];
+	snd_cs4236_ctrl_out(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define CS4236_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4236_get_double, .put = snd_cs4236_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+#define CS4236_DOUBLE_TLV(xname, xindex, left_reg, right_reg, shift_left, \
+			  shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4236_get_double, .put = snd_cs4236_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | \
+		   (shift_right << 19) | (mask << 24) | (invert << 22), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_cs4236_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_cs4236_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->eimage[CS4236_REG(left_reg)] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (chip->eimage[CS4236_REG(right_reg)] >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_cs4236_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (left_reg != right_reg) {
+		val1 = (chip->eimage[CS4236_REG(left_reg)] & ~(mask << shift_left)) | val1;
+		val2 = (chip->eimage[CS4236_REG(right_reg)] & ~(mask << shift_right)) | val2;
+		change = val1 != chip->eimage[CS4236_REG(left_reg)] || val2 != chip->eimage[CS4236_REG(right_reg)];
+		snd_cs4236_ext_out(chip, left_reg, val1);
+		snd_cs4236_ext_out(chip, right_reg, val2);
+	} else {
+		val1 = (chip->eimage[CS4236_REG(left_reg)] & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+		change = val1 != chip->eimage[CS4236_REG(left_reg)];
+		snd_cs4236_ext_out(chip, left_reg, val1);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define CS4236_DOUBLE1(xname, xindex, left_reg, right_reg, shift_left, \
+			shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4236_get_double1, .put = snd_cs4236_put_double1, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+#define CS4236_DOUBLE1_TLV(xname, xindex, left_reg, right_reg, shift_left, \
+			   shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4236_get_double1, .put = snd_cs4236_put_double1, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | \
+		   (shift_right << 19) | (mask << 24) | (invert << 22), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_cs4236_get_double1(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (chip->eimage[CS4236_REG(right_reg)] >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_cs4236_put_double1(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
+	val2 = (chip->eimage[CS4236_REG(right_reg)] & ~(mask << shift_right)) | val2;
+	change = val1 != chip->image[left_reg] || val2 != chip->eimage[CS4236_REG(right_reg)];
+	snd_wss_out(chip, left_reg, val1);
+	snd_cs4236_ext_out(chip, right_reg, val2);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define CS4236_MASTER_DIGITAL(xname, xindex, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4236_get_master_digital, .put = snd_cs4236_put_master_digital, \
+  .private_value = 71 << 24, \
+  .tlv = { .p = (xtlv) } }
+
+static inline int snd_cs4236_mixer_master_digital_invert_volume(int vol)
+{
+	return (vol < 64) ? 63 - vol : 64 + (71 - vol);
+}
+
+static int snd_cs4236_get_master_digital(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = snd_cs4236_mixer_master_digital_invert_volume(chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] & 0x7f);
+	ucontrol->value.integer.value[1] = snd_cs4236_mixer_master_digital_invert_volume(chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)] & 0x7f);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_cs4236_put_master_digital(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1, val2;
+	
+	val1 = snd_cs4236_mixer_master_digital_invert_volume(ucontrol->value.integer.value[0] & 0x7f);
+	val2 = snd_cs4236_mixer_master_digital_invert_volume(ucontrol->value.integer.value[1] & 0x7f);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val1 = (chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] & ~0x7f) | val1;
+	val2 = (chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)] & ~0x7f) | val2;
+	change = val1 != chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] || val2 != chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)];
+	snd_cs4236_ext_out(chip, CS4236_LEFT_MASTER, val1);
+	snd_cs4236_ext_out(chip, CS4236_RIGHT_MASTER, val2);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define CS4235_OUTPUT_ACCU(xname, xindex, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .info = snd_cs4236_info_double, \
+  .get = snd_cs4235_get_output_accu, .put = snd_cs4235_put_output_accu, \
+  .private_value = 3 << 24, \
+  .tlv = { .p = (xtlv) } }
+
+static inline int snd_cs4235_mixer_output_accu_get_volume(int vol)
+{
+	switch ((vol >> 5) & 3) {
+	case 0: return 1;
+	case 1: return 3;
+	case 2: return 2;
+	case 3: return 0;
+ 	}
+	return 3;
+}
+
+static inline int snd_cs4235_mixer_output_accu_set_volume(int vol)
+{
+	switch (vol & 3) {
+	case 0: return 3 << 5;
+	case 1: return 0 << 5;
+	case 2: return 2 << 5;
+	case 3: return 1 << 5;
+	}
+	return 1 << 5;
+}
+
+static int snd_cs4235_get_output_accu(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = snd_cs4235_mixer_output_accu_get_volume(chip->image[CS4235_LEFT_MASTER]);
+	ucontrol->value.integer.value[1] = snd_cs4235_mixer_output_accu_get_volume(chip->image[CS4235_RIGHT_MASTER]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_cs4235_put_output_accu(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1, val2;
+	
+	val1 = snd_cs4235_mixer_output_accu_set_volume(ucontrol->value.integer.value[0]);
+	val2 = snd_cs4235_mixer_output_accu_set_volume(ucontrol->value.integer.value[1]);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val1 = (chip->image[CS4235_LEFT_MASTER] & ~(3 << 5)) | val1;
+	val2 = (chip->image[CS4235_RIGHT_MASTER] & ~(3 << 5)) | val2;
+	change = val1 != chip->image[CS4235_LEFT_MASTER] || val2 != chip->image[CS4235_RIGHT_MASTER];
+	snd_wss_out(chip, CS4235_LEFT_MASTER, val1);
+	snd_wss_out(chip, CS4235_RIGHT_MASTER, val2);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_7bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit_12db_max, -8250, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_22db_max, -2400, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_2bit, -1800, 600, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
+
+static struct snd_kcontrol_new snd_cs4236_controls[] = {
+
+CS4236_DOUBLE("Master Digital Playback Switch", 0,
+		CS4236_LEFT_MASTER, CS4236_RIGHT_MASTER, 7, 7, 1, 1),
+CS4236_DOUBLE("Master Digital Capture Switch", 0,
+		CS4236_DAC_MUTE, CS4236_DAC_MUTE, 7, 6, 1, 1),
+CS4236_MASTER_DIGITAL("Master Digital Volume", 0, db_scale_7bit),
+
+CS4236_DOUBLE_TLV("Capture Boost Volume", 0,
+		  CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1,
+		  db_scale_2bit),
+
+WSS_DOUBLE("PCM Playback Switch", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("PCM Playback Volume", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1,
+		db_scale_6bit),
+
+CS4236_DOUBLE("DSP Playback Switch", 0,
+		CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 7, 7, 1, 1),
+CS4236_DOUBLE_TLV("DSP Playback Volume", 0,
+		  CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 0, 0, 63, 1,
+		  db_scale_6bit),
+
+CS4236_DOUBLE("FM Playback Switch", 0,
+		CS4236_LEFT_FM, CS4236_RIGHT_FM, 7, 7, 1, 1),
+CS4236_DOUBLE_TLV("FM Playback Volume", 0,
+		  CS4236_LEFT_FM, CS4236_RIGHT_FM, 0, 0, 63, 1,
+		  db_scale_6bit),
+
+CS4236_DOUBLE("Wavetable Playback Switch", 0,
+		CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 7, 7, 1, 1),
+CS4236_DOUBLE_TLV("Wavetable Playback Volume", 0,
+		  CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 0, 0, 63, 1,
+		  db_scale_6bit_12db_max),
+
+WSS_DOUBLE("Synth Playback Switch", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Synth Volume", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_DOUBLE("Synth Capture Switch", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1),
+WSS_DOUBLE("Synth Capture Bypass", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 5, 5, 1, 1),
+
+CS4236_DOUBLE("Mic Playback Switch", 0,
+		CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 6, 6, 1, 1),
+CS4236_DOUBLE("Mic Capture Switch", 0,
+		CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 7, 7, 1, 1),
+CS4236_DOUBLE_TLV("Mic Volume", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC,
+		  0, 0, 31, 1, db_scale_5bit_22db_max),
+CS4236_DOUBLE("Mic Playback Boost (+20dB)", 0,
+		CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 5, 5, 1, 0),
+
+WSS_DOUBLE("Line Playback Switch", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Line Volume", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_DOUBLE("Line Capture Switch", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1),
+WSS_DOUBLE("Line Capture Bypass", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 5, 5, 1, 1),
+
+WSS_DOUBLE("CD Playback Switch", 0,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("CD Volume", 0,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_DOUBLE("CD Capture Switch", 0,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 6, 6, 1, 1),
+
+CS4236_DOUBLE1("Mono Output Playback Switch", 0,
+		CS4231_MONO_CTRL, CS4236_RIGHT_MIX_CTRL, 6, 7, 1, 1),
+CS4236_DOUBLE1("Beep Playback Switch", 0,
+		CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1),
+WSS_SINGLE_TLV("Beep Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1,
+		db_scale_4bit),
+WSS_SINGLE("Beep Bypass Playback Switch", 0, CS4231_MONO_CTRL, 5, 1, 0),
+
+WSS_DOUBLE_TLV("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT,
+		0, 0, 15, 0, db_scale_rec_gain),
+WSS_DOUBLE("Analog Loopback Capture Switch", 0,
+		CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 7, 7, 1, 0),
+
+WSS_SINGLE("Loopback Digital Playback Switch", 0, CS4231_LOOPBACK, 0, 1, 0),
+CS4236_DOUBLE1_TLV("Loopback Digital Playback Volume", 0,
+		   CS4231_LOOPBACK, CS4236_RIGHT_LOOPBACK, 2, 0, 63, 1,
+		   db_scale_6bit),
+};
+
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_6db_max, -5600, 200, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_2bit_16db_max, -2400, 800, 0);
+
+static struct snd_kcontrol_new snd_cs4235_controls[] = {
+
+WSS_DOUBLE("Master Playback Switch", 0,
+		CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Master Playback Volume", 0,
+		CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 0, 0, 31, 1,
+		db_scale_5bit_6db_max),
+
+CS4235_OUTPUT_ACCU("Playback Volume", 0, db_scale_2bit_16db_max),
+
+WSS_DOUBLE("Synth Playback Switch", 1,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE("Synth Capture Switch", 1,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1),
+WSS_DOUBLE_TLV("Synth Volume", 1,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+
+CS4236_DOUBLE_TLV("Capture Volume", 0,
+		  CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1,
+		  db_scale_2bit),
+
+WSS_DOUBLE("PCM Playback Switch", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE("PCM Capture Switch", 0,
+		CS4236_DAC_MUTE, CS4236_DAC_MUTE, 7, 6, 1, 1),
+WSS_DOUBLE_TLV("PCM Volume", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1,
+		db_scale_6bit),
+
+CS4236_DOUBLE("DSP Switch", 0, CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 7, 7, 1, 1),
+
+CS4236_DOUBLE("FM Switch", 0, CS4236_LEFT_FM, CS4236_RIGHT_FM, 7, 7, 1, 1),
+
+CS4236_DOUBLE("Wavetable Switch", 0,
+		CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 7, 7, 1, 1),
+
+CS4236_DOUBLE("Mic Capture Switch", 0,
+		CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 7, 7, 1, 1),
+CS4236_DOUBLE("Mic Playback Switch", 0,
+		CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 6, 6, 1, 1),
+CS4236_SINGLE_TLV("Mic Volume", 0, CS4236_LEFT_MIC, 0, 31, 1,
+		  db_scale_5bit_22db_max),
+CS4236_SINGLE("Mic Boost (+20dB)", 0, CS4236_LEFT_MIC, 5, 1, 0),
+
+WSS_DOUBLE("Line Playback Switch", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("Line Capture Switch", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1),
+WSS_DOUBLE_TLV("Line Volume", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+
+WSS_DOUBLE("CD Playback Switch", 1,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("CD Capture Switch", 1,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 6, 6, 1, 1),
+WSS_DOUBLE_TLV("CD Volume", 1,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+
+CS4236_DOUBLE1("Beep Playback Switch", 0,
+		CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1),
+WSS_SINGLE("Beep Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1),
+
+WSS_DOUBLE("Analog Loopback Switch", 0,
+		CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 7, 7, 1, 0),
+};
+
+#define CS4236_IEC958_ENABLE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_cs4236_info_single, \
+  .get = snd_cs4236_get_iec958_switch, .put = snd_cs4236_put_iec958_switch, \
+  .private_value = 1 << 16 }
+
+static int snd_cs4236_get_iec958_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = chip->image[CS4231_ALT_FEATURE_1] & 0x02 ? 1 : 0;
+#if 0
+	printk(KERN_DEBUG "get valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, "
+	       "C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n",
+			snd_wss_in(chip, CS4231_ALT_FEATURE_1),
+			snd_cs4236_ctrl_in(chip, 3),
+			snd_cs4236_ctrl_in(chip, 4),
+			snd_cs4236_ctrl_in(chip, 5),
+			snd_cs4236_ctrl_in(chip, 6),
+			snd_cs4236_ctrl_in(chip, 8));
+#endif
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_cs4236_put_iec958_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short enable, val;
+	
+	enable = ucontrol->value.integer.value[0] & 1;
+
+	mutex_lock(&chip->mce_mutex);
+	snd_wss_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = (chip->image[CS4231_ALT_FEATURE_1] & ~0x0e) | (0<<2) | (enable << 1);
+	change = val != chip->image[CS4231_ALT_FEATURE_1];
+	snd_wss_out(chip, CS4231_ALT_FEATURE_1, val);
+	val = snd_cs4236_ctrl_in(chip, 4) | 0xc0;
+	snd_cs4236_ctrl_out(chip, 4, val);
+	udelay(100);
+	val &= ~0x40;
+	snd_cs4236_ctrl_out(chip, 4, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_down(chip);
+	mutex_unlock(&chip->mce_mutex);
+
+#if 0
+	printk(KERN_DEBUG "set valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, "
+	       "C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n",
+			snd_wss_in(chip, CS4231_ALT_FEATURE_1),
+			snd_cs4236_ctrl_in(chip, 3),
+			snd_cs4236_ctrl_in(chip, 4),
+			snd_cs4236_ctrl_in(chip, 5),
+			snd_cs4236_ctrl_in(chip, 6),
+			snd_cs4236_ctrl_in(chip, 8));
+#endif
+	return change;
+}
+
+static struct snd_kcontrol_new snd_cs4236_iec958_controls[] = {
+CS4236_IEC958_ENABLE("IEC958 Output Enable", 0),
+CS4236_SINGLEC("IEC958 Output Validity", 0, 4, 4, 1, 0),
+CS4236_SINGLEC("IEC958 Output User", 0, 4, 5, 1, 0),
+CS4236_SINGLEC("IEC958 Output CSBR", 0, 4, 6, 1, 0),
+CS4236_SINGLEC("IEC958 Output Channel Status Low", 0, 5, 1, 127, 0),
+CS4236_SINGLEC("IEC958 Output Channel Status High", 0, 6, 0, 255, 0)
+};
+
+static struct snd_kcontrol_new snd_cs4236_3d_controls_cs4235[] = {
+CS4236_SINGLEC("3D Control - Switch", 0, 3, 4, 1, 0),
+CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1)
+};
+
+static struct snd_kcontrol_new snd_cs4236_3d_controls_cs4237[] = {
+CS4236_SINGLEC("3D Control - Switch", 0, 3, 7, 1, 0),
+CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1),
+CS4236_SINGLEC("3D Control - Center", 0, 2, 0, 15, 1),
+CS4236_SINGLEC("3D Control - Mono", 0, 3, 6, 1, 0),
+CS4236_SINGLEC("3D Control - IEC958", 0, 3, 5, 1, 0)
+};
+
+static struct snd_kcontrol_new snd_cs4236_3d_controls_cs4238[] = {
+CS4236_SINGLEC("3D Control - Switch", 0, 3, 4, 1, 0),
+CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1),
+CS4236_SINGLEC("3D Control - Volume", 0, 2, 0, 15, 1),
+CS4236_SINGLEC("3D Control - IEC958", 0, 3, 5, 1, 0)
+};
+
+int snd_cs4236_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card;
+	unsigned int idx, count;
+	int err;
+	struct snd_kcontrol_new *kcontrol;
+
+	if (snd_BUG_ON(!chip || !chip->card))
+		return -EINVAL;
+	card = chip->card;
+	strcpy(card->mixername, snd_wss_chip_id(chip));
+
+	if (chip->hardware == WSS_HW_CS4235 ||
+	    chip->hardware == WSS_HW_CS4239) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_cs4235_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4235_controls[idx], chip))) < 0)
+				return err;
+		}
+	} else {
+		for (idx = 0; idx < ARRAY_SIZE(snd_cs4236_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4236_controls[idx], chip))) < 0)
+				return err;
+		}
+	}
+	switch (chip->hardware) {
+	case WSS_HW_CS4235:
+	case WSS_HW_CS4239:
+		count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4235);
+		kcontrol = snd_cs4236_3d_controls_cs4235;
+		break;
+	case WSS_HW_CS4237B:
+		count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4237);
+		kcontrol = snd_cs4236_3d_controls_cs4237;
+		break;
+	case WSS_HW_CS4238B:
+		count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4238);
+		kcontrol = snd_cs4236_3d_controls_cs4238;
+		break;
+	default:
+		count = 0;
+		kcontrol = NULL;
+	}
+	for (idx = 0; idx < count; idx++, kcontrol++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(kcontrol, chip))) < 0)
+			return err;
+	}
+	if (chip->hardware == WSS_HW_CS4237B ||
+	    chip->hardware == WSS_HW_CS4238B) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_cs4236_iec958_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4236_iec958_controls[idx], chip))) < 0)
+				return err;
+		}
+	}
+	return 0;
+}
diff --git a/linux/sound/isa/es1688/Makefile b/linux/sound/isa/es1688/Makefile
new file mode 100644
index 0000000..aee1e4d
--- /dev/null
+++ b/linux/sound/isa/es1688/Makefile
@@ -0,0 +1,11 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-es1688-lib-objs := es1688_lib.o
+snd-es1688-objs := es1688.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_ES1688) += snd-es1688.o snd-es1688-lib.o
+obj-$(CONFIG_SND_GUSEXTREME) += snd-es1688-lib.o
diff --git a/linux/sound/isa/es1688/es1688.c b/linux/sound/isa/es1688/es1688.c
new file mode 100644
index 0000000..0cde813
--- /dev/null
+++ b/linux/sound/isa/es1688/es1688.c
@@ -0,0 +1,372 @@
+/*
+ *  Driver for generic ESS AudioDrive ESx688 soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/isapnp.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/es1688.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define CRD_NAME "Generic ESS ES1688/ES688 AudioDrive"
+#define DEV_NAME "es1688"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,ES688 PnP AudioDrive,pnp:ESS0100},"
+	        "{ESS,ES1688 PnP AudioDrive,pnp:ESS0102},"
+	        "{ESS,ES688 AudioDrive,pnp:ESS6881},"
+	        "{ESS,ES1688 AudioDrive,pnp:ESS1681}}");
+
+MODULE_ALIAS("snd_es968");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#endif
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* Usually 0x388 */
+static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1};
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_array(irq, int, NULL, 0444);
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for ES1688 driver.");
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for " CRD_NAME " driver.");
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev)		isapnp[dev]
+#else
+#define is_isapnp_selected(dev)		0
+#endif
+
+static int __devinit snd_es1688_match(struct device *dev, unsigned int n)
+{
+	return enable[n] && !is_isapnp_selected(n);
+}
+
+static int __devinit snd_es1688_legacy_create(struct snd_card *card,
+					struct device *dev, unsigned int n)
+{
+	struct snd_es1688 *chip = card->private_data;
+	static long possible_ports[] = {0x220, 0x240, 0x260};
+	static int possible_irqs[] = {5, 9, 10, 7, -1};
+	static int possible_dmas[] = {1, 3, 0, -1};
+
+	int i, error;
+
+	if (irq[n] == SNDRV_AUTO_IRQ) {
+		irq[n] = snd_legacy_find_free_irq(possible_irqs);
+		if (irq[n] < 0) {
+			dev_err(dev, "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma8[n] == SNDRV_AUTO_DMA) {
+		dma8[n] = snd_legacy_find_free_dma(possible_dmas);
+		if (dma8[n] < 0) {
+			dev_err(dev, "unable to find a free DMA\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[n] != SNDRV_AUTO_PORT)
+		return snd_es1688_create(card, chip, port[n], mpu_port[n],
+				irq[n], mpu_irq[n], dma8[n], ES1688_HW_AUTO);
+
+	i = 0;
+	do {
+		port[n] = possible_ports[i];
+		error = snd_es1688_create(card, chip, port[n], mpu_port[n],
+				irq[n], mpu_irq[n], dma8[n], ES1688_HW_AUTO);
+	} while (error < 0 && ++i < ARRAY_SIZE(possible_ports));
+
+	return error;
+}
+
+static int __devinit snd_es1688_probe(struct snd_card *card, unsigned int n)
+{
+	struct snd_es1688 *chip = card->private_data;
+	struct snd_opl3 *opl3;
+	struct snd_pcm *pcm;
+	int error;
+
+	error = snd_es1688_pcm(card, chip, 0, &pcm);
+	if (error < 0)
+		return error;
+
+	error = snd_es1688_mixer(card, chip);
+	if (error < 0)
+		return error;
+
+	strlcpy(card->driver, "ES1688", sizeof(card->driver));
+	strlcpy(card->shortname, pcm->name, sizeof(card->shortname));
+	snprintf(card->longname, sizeof(card->longname),
+		"%s at 0x%lx, irq %i, dma %i", pcm->name, chip->port,
+		 chip->irq, chip->dma8);
+
+	if (fm_port[n] == SNDRV_AUTO_PORT)
+		fm_port[n] = port[n];	/* share the same port */
+
+	if (fm_port[n] > 0) {
+		if (snd_opl3_create(card, fm_port[n], fm_port[n] + 2,
+				OPL3_HW_OPL3, 0, &opl3) < 0)
+			dev_warn(card->dev,
+				 "opl3 not detected at 0x%lx\n", fm_port[n]);
+		else {
+			error =	snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+			if (error < 0)
+				return error;
+		}
+	}
+
+	if (mpu_irq[n] >= 0 && mpu_irq[n] != SNDRV_AUTO_IRQ &&
+			chip->mpu_port > 0) {
+		error = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688,
+				chip->mpu_port, 0,
+				mpu_irq[n], IRQF_DISABLED, NULL);
+		if (error < 0)
+			return error;
+	}
+
+	return snd_card_register(card);
+}
+
+static int __devinit snd_es1688_isa_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	int error;
+
+	error = snd_card_create(index[n], id[n], THIS_MODULE,
+				sizeof(struct snd_es1688), &card);
+	if (error < 0)
+		return error;
+
+	error = snd_es1688_legacy_create(card, dev, n);
+	if (error < 0)
+		goto out;
+
+	snd_card_set_dev(card, dev);
+
+	error = snd_es1688_probe(card, n);
+	if (error < 0)
+		goto out;
+
+	dev_set_drvdata(dev, card);
+
+	return 0;
+out:
+	snd_card_free(card);
+	return error;
+}
+
+static int __devexit snd_es1688_isa_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	dev_set_drvdata(dev, NULL);
+	return 0;
+}
+
+static struct isa_driver snd_es1688_driver = {
+	.match		= snd_es1688_match,
+	.probe		= snd_es1688_isa_probe,
+	.remove		= __devexit_p(snd_es1688_isa_remove),
+#if 0	/* FIXME */
+	.suspend	= snd_es1688_suspend,
+	.resume		= snd_es1688_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+static int snd_es968_pnp_is_probed;
+
+#ifdef CONFIG_PNP
+static int __devinit snd_card_es968_pnp(struct snd_card *card, unsigned int n,
+					struct pnp_card_link *pcard,
+					const struct pnp_card_device_id *pid)
+{
+	struct snd_es1688 *chip = card->private_data;
+	struct pnp_dev *pdev;
+	int error;
+
+	pdev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
+	if (pdev == NULL)
+		return -ENODEV;
+
+	error = pnp_activate_dev(pdev);
+	if (error < 0) {
+		snd_printk(KERN_ERR "ES968 pnp configure failure\n");
+		return error;
+	}
+	port[n] = pnp_port_start(pdev, 0);
+	dma8[n] = pnp_dma(pdev, 0);
+	irq[n] = pnp_irq(pdev, 0);
+
+	return snd_es1688_create(card, chip, port[n], mpu_port[n], irq[n],
+				 mpu_irq[n], dma8[n], ES1688_HW_AUTO);
+}
+
+static int __devinit snd_es968_pnp_detect(struct pnp_card_link *pcard,
+					  const struct pnp_card_device_id *pid)
+{
+	struct snd_card *card;
+	static unsigned int dev;
+	int error;
+	struct snd_es1688 *chip;
+
+	if (snd_es968_pnp_is_probed)
+		return -EBUSY;
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev == SNDRV_CARDS)
+		return -ENODEV;
+
+	error = snd_card_create(index[dev], id[dev], THIS_MODULE,
+				sizeof(struct snd_es1688), &card);
+	if (error < 0)
+		return error;
+	chip = card->private_data;
+
+	error = snd_card_es968_pnp(card, dev, pcard, pid);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+	error = snd_es1688_probe(card, dev);
+	if (error < 0)
+		return error;
+	pnp_set_card_drvdata(pcard, card);
+	snd_es968_pnp_is_probed = 1;
+	return 0;
+}
+
+static void __devexit snd_es968_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+	snd_es968_pnp_is_probed = 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_es968_pnp_suspend(struct pnp_card_link *pcard,
+				 pm_message_t state)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_es1688 *chip = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	return 0;
+}
+
+static int snd_es968_pnp_resume(struct pnp_card_link *pcard)
+{
+	struct snd_card *card = pnp_get_card_drvdata(pcard);
+	struct snd_es1688 *chip = card->private_data;
+
+	snd_es1688_reset(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct pnp_card_device_id snd_es968_pnpids[] = {
+	{ .id = "ESS0968", .devs = { { "@@@0968" }, } },
+	{ .id = "ESS0968", .devs = { { "ESS0968" }, } },
+	{ .id = "", } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_es968_pnpids);
+
+static struct pnp_card_driver es968_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= DEV_NAME " PnP",
+	.id_table	= snd_es968_pnpids,
+	.probe		= snd_es968_pnp_detect,
+	.remove		= __devexit_p(snd_es968_pnp_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_es968_pnp_suspend,
+	.resume		= snd_es968_pnp_resume,
+#endif
+};
+#endif
+
+static int __init alsa_card_es1688_init(void)
+{
+#ifdef CONFIG_PNP
+	pnp_register_card_driver(&es968_pnpc_driver);
+	if (snd_es968_pnp_is_probed)
+		return 0;
+	pnp_unregister_card_driver(&es968_pnpc_driver);
+#endif
+	return isa_register_driver(&snd_es1688_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_es1688_exit(void)
+{
+	if (!snd_es968_pnp_is_probed) {
+		isa_unregister_driver(&snd_es1688_driver);
+		return;
+	}
+#ifdef CONFIG_PNP
+	pnp_unregister_card_driver(&es968_pnpc_driver);
+#endif
+}
+
+module_init(alsa_card_es1688_init);
+module_exit(alsa_card_es1688_exit);
diff --git a/linux/sound/isa/es1688/es1688_lib.c b/linux/sound/isa/es1688/es1688_lib.c
new file mode 100644
index 0000000..0767620
--- /dev/null
+++ b/linux/sound/isa/es1688/es1688_lib.c
@@ -0,0 +1,1045 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for control of ESS ES1688/688/488 chip
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/es1688.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ESS ESx688 lowlevel module");
+MODULE_LICENSE("GPL");
+
+static int snd_es1688_dsp_command(struct snd_es1688 *chip, unsigned char val)
+{
+	int i;
+
+	for (i = 10000; i; i--)
+		if ((inb(ES1688P(chip, STATUS)) & 0x80) == 0) {
+			outb(val, ES1688P(chip, COMMAND));
+			return 1;
+		}
+#ifdef CONFIG_SND_DEBUG
+	printk(KERN_DEBUG "snd_es1688_dsp_command: timeout (0x%x)\n", val);
+#endif
+	return 0;
+}
+
+static int snd_es1688_dsp_get_byte(struct snd_es1688 *chip)
+{
+	int i;
+
+	for (i = 1000; i; i--)
+		if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80)
+			return inb(ES1688P(chip, READ));
+	snd_printd("es1688 get byte failed: 0x%lx = 0x%x!!!\n", ES1688P(chip, DATA_AVAIL), inb(ES1688P(chip, DATA_AVAIL)));
+	return -ENODEV;
+}
+
+static int snd_es1688_write(struct snd_es1688 *chip,
+			    unsigned char reg, unsigned char data)
+{
+	if (!snd_es1688_dsp_command(chip, reg))
+		return 0;
+	return snd_es1688_dsp_command(chip, data);
+}
+
+static int snd_es1688_read(struct snd_es1688 *chip, unsigned char reg)
+{
+	/* Read a byte from an extended mode register of ES1688 */
+	if (!snd_es1688_dsp_command(chip, 0xc0))
+		return -1;
+	if (!snd_es1688_dsp_command(chip, reg))
+		return -1;
+	return snd_es1688_dsp_get_byte(chip);
+}
+
+void snd_es1688_mixer_write(struct snd_es1688 *chip,
+			    unsigned char reg, unsigned char data)
+{
+	outb(reg, ES1688P(chip, MIXER_ADDR));
+	udelay(10);
+	outb(data, ES1688P(chip, MIXER_DATA));
+	udelay(10);
+}
+
+static unsigned char snd_es1688_mixer_read(struct snd_es1688 *chip, unsigned char reg)
+{
+	unsigned char result;
+
+	outb(reg, ES1688P(chip, MIXER_ADDR));
+	udelay(10);
+	result = inb(ES1688P(chip, MIXER_DATA));
+	udelay(10);
+	return result;
+}
+
+int snd_es1688_reset(struct snd_es1688 *chip)
+{
+	int i;
+
+	outb(3, ES1688P(chip, RESET));		/* valid only for ESS chips, SB -> 1 */
+	udelay(10);
+	outb(0, ES1688P(chip, RESET));
+	udelay(30);
+	for (i = 0; i < 1000 && !(inb(ES1688P(chip, DATA_AVAIL)) & 0x80); i++);
+	if (inb(ES1688P(chip, READ)) != 0xaa) {
+		snd_printd("ess_reset at 0x%lx: failed!!!\n", chip->port);
+		return -ENODEV;
+	}
+	snd_es1688_dsp_command(chip, 0xc6);	/* enable extended mode */
+	return 0;
+}
+EXPORT_SYMBOL(snd_es1688_reset);
+
+static int snd_es1688_probe(struct snd_es1688 *chip)
+{
+	unsigned long flags;
+	unsigned short major, minor, hw;
+	int i;
+
+	/*
+	 *  initialization sequence
+	 */
+
+	spin_lock_irqsave(&chip->reg_lock, flags);	/* Some ESS1688 cards need this */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE2));	/* ENABLE2 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE2));	/* ENABLE2 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE2));	/* ENABLE2 */
+	inb(ES1688P(chip, ENABLE1));	/* ENABLE1 */
+	inb(ES1688P(chip, ENABLE0));	/* ENABLE0 */
+
+	if (snd_es1688_reset(chip) < 0) {
+		snd_printdd("ESS: [0x%lx] reset failed... 0x%x\n", chip->port, inb(ES1688P(chip, READ)));
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return -ENODEV;
+	}
+	snd_es1688_dsp_command(chip, 0xe7);	/* return identification */
+
+	for (i = 1000, major = minor = 0; i; i--) {
+		if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80) {
+			if (major == 0) {
+				major = inb(ES1688P(chip, READ));
+			} else {
+				minor = inb(ES1688P(chip, READ));
+			}
+		}
+	}
+
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	snd_printdd("ESS: [0x%lx] found.. major = 0x%x, minor = 0x%x\n", chip->port, major, minor);
+
+	chip->version = (major << 8) | minor;
+	if (!chip->version)
+		return -ENODEV;	/* probably SB */
+
+	hw = ES1688_HW_AUTO;
+	switch (chip->version & 0xfff0) {
+	case 0x4880:
+		snd_printk(KERN_ERR "[0x%lx] ESS: AudioDrive ES488 detected, "
+			   "but driver is in another place\n", chip->port);
+		return -ENODEV;
+	case 0x6880:
+		hw = (chip->version & 0x0f) >= 8 ? ES1688_HW_1688 : ES1688_HW_688;
+		break;
+	default:
+		snd_printk(KERN_ERR "[0x%lx] ESS: unknown AudioDrive chip "
+			   "with version 0x%x (Jazz16 soundcard?)\n",
+			   chip->port, chip->version);
+		return -ENODEV;
+	}
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_write(chip, 0xb1, 0x10);	/* disable IRQ */
+	snd_es1688_write(chip, 0xb2, 0x00);	/* disable DMA */
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	/* enable joystick, but disable OPL3 */
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	snd_es1688_mixer_write(chip, 0x40, 0x01);
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+	return 0;
+}
+
+static int snd_es1688_init(struct snd_es1688 * chip, int enable)
+{
+	static int irqs[16] = {-1, -1, 0, -1, -1, 1, -1, 2, -1, 0, 3, -1, -1, -1, -1, -1};
+	unsigned long flags;
+	int cfg, irq_bits, dma, dma_bits, tmp, tmp1;
+
+	/* ok.. setup MPU-401 port and joystick and OPL3 */
+	cfg = 0x01;		/* enable joystick, but disable OPL3 */
+	if (enable && chip->mpu_port >= 0x300 && chip->mpu_irq > 0 && chip->hardware != ES1688_HW_688) {
+		tmp = (chip->mpu_port & 0x0f0) >> 4;
+		if (tmp <= 3) {
+			switch (chip->mpu_irq) {
+			case 9:
+				tmp1 = 4;
+				break;
+			case 5:
+				tmp1 = 5;
+				break;
+			case 7:
+				tmp1 = 6;
+				break;
+			case 10:
+				tmp1 = 7;
+				break;
+			default:
+				tmp1 = 0;
+			}
+			if (tmp1) {
+				cfg |= (tmp << 3) | (tmp1 << 5);
+			}
+		}
+	}
+#if 0
+	snd_printk(KERN_DEBUG "mpu cfg = 0x%x\n", cfg);
+#endif
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_mixer_write(chip, 0x40, cfg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	/* --- */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_read(chip, 0xb1);
+	snd_es1688_read(chip, 0xb2);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (enable) {
+		cfg = 0xf0;	/* enable only DMA counter interrupt */
+		irq_bits = irqs[chip->irq & 0x0f];
+		if (irq_bits < 0) {
+			snd_printk(KERN_ERR "[0x%lx] ESS: bad IRQ %d "
+				   "for ES1688 chip!!\n",
+				   chip->port, chip->irq);
+#if 0
+			irq_bits = 0;
+			cfg = 0x10;
+#endif
+			return -EINVAL;
+		}
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_es1688_write(chip, 0xb1, cfg | (irq_bits << 2));
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		cfg = 0xf0;	/* extended mode DMA enable */
+		dma = chip->dma8;
+		if (dma > 3 || dma == 2) {
+			snd_printk(KERN_ERR "[0x%lx] ESS: bad DMA channel %d "
+				   "for ES1688 chip!!\n", chip->port, dma);
+#if 0
+			dma_bits = 0;
+			cfg = 0x00;	/* disable all DMA */
+#endif
+			return -EINVAL;
+		} else {
+			dma_bits = dma;
+			if (dma != 3)
+				dma_bits++;
+		}
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_es1688_write(chip, 0xb2, cfg | (dma_bits << 2));
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	} else {
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_es1688_write(chip, 0xb1, 0x10);	/* disable IRQ */
+		snd_es1688_write(chip, 0xb2, 0x00);	/* disable DMA */
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	}
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_read(chip, 0xb1);
+	snd_es1688_read(chip, 0xb2);
+	snd_es1688_reset(chip);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+/*
+
+ */
+
+static struct snd_ratnum clocks[2] = {
+	{
+		.num = 795444,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	},
+	{
+		.num = 397722,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	}
+};
+
+static struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks  = {
+	.nrats = 2,
+	.rats = clocks,
+};
+
+static void snd_es1688_set_rate(struct snd_es1688 *chip, struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int bits, divider;
+
+	if (runtime->rate_num == clocks[0].num)
+		bits = 256 - runtime->rate_den;
+	else
+		bits = 128 - runtime->rate_den;
+	/* set filter register */
+	divider = 256 - 7160000*20/(8*82*runtime->rate);
+	/* write result to hardware */
+	snd_es1688_write(chip, 0xa1, bits);
+	snd_es1688_write(chip, 0xa2, divider);
+}
+
+static int snd_es1688_ioctl(struct snd_pcm_substream *substream,
+			    unsigned int cmd, void *arg)
+{
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static int snd_es1688_trigger(struct snd_es1688 *chip, int cmd, unsigned char value)
+{
+	int val;
+
+	if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		value = 0x00;
+	} else if (cmd != SNDRV_PCM_TRIGGER_START) {
+		return -EINVAL;
+	}
+	spin_lock(&chip->reg_lock);
+	chip->trigger_value = value;
+	val = snd_es1688_read(chip, 0xb8);
+	if ((val < 0) || (val & 0x0f) == value) {
+		spin_unlock(&chip->reg_lock);
+		return -EINVAL;	/* something is wrong */
+	}
+#if 0
+	printk(KERN_DEBUG "trigger: val = 0x%x, value = 0x%x\n", val, value);
+	printk(KERN_DEBUG "trigger: pointer = 0x%x\n",
+	       snd_dma_pointer(chip->dma8, chip->dma_size));
+#endif
+	snd_es1688_write(chip, 0xb8, (val & 0xf0) | value);
+	spin_unlock(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_es1688_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_es1688_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_es1688_playback_prepare(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma_size = size;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_reset(chip);
+	snd_es1688_set_rate(chip, substream);
+	snd_es1688_write(chip, 0xb8, 4);	/* auto init DMA mode */
+	snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels));
+	snd_es1688_write(chip, 0xb9, 2);	/* demand mode (4 bytes/request) */
+	if (runtime->channels == 1) {
+		if (snd_pcm_format_width(runtime->format) == 8) {
+			/* 8. bit mono */
+			snd_es1688_write(chip, 0xb6, 0x80);
+			snd_es1688_write(chip, 0xb7, 0x51);
+			snd_es1688_write(chip, 0xb7, 0xd0);
+		} else {
+			/* 16. bit mono */
+			snd_es1688_write(chip, 0xb6, 0x00);
+			snd_es1688_write(chip, 0xb7, 0x71);
+			snd_es1688_write(chip, 0xb7, 0xf4);
+		}
+	} else {
+		if (snd_pcm_format_width(runtime->format) == 8) {
+			/* 8. bit stereo */
+			snd_es1688_write(chip, 0xb6, 0x80);
+			snd_es1688_write(chip, 0xb7, 0x51);
+			snd_es1688_write(chip, 0xb7, 0x98);
+		} else {
+			/* 16. bit stereo */
+			snd_es1688_write(chip, 0xb6, 0x00);
+			snd_es1688_write(chip, 0xb7, 0x71);
+			snd_es1688_write(chip, 0xb7, 0xbc);
+		}
+	}
+	snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50);
+	snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50);
+	snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKON);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	/* --- */
+	count = -count;
+	snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_write(chip, 0xa4, (unsigned char) count);
+	snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_es1688_playback_trigger(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	return snd_es1688_trigger(chip, cmd, 0x05);
+}
+
+static int snd_es1688_capture_prepare(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma_size = size;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_reset(chip);
+	snd_es1688_set_rate(chip, substream);
+	snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKOFF);
+	snd_es1688_write(chip, 0xb8, 0x0e);	/* auto init DMA mode */
+	snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels));
+	snd_es1688_write(chip, 0xb9, 2);	/* demand mode (4 bytes/request) */
+	if (runtime->channels == 1) {
+		if (snd_pcm_format_width(runtime->format) == 8) {
+			/* 8. bit mono */
+			snd_es1688_write(chip, 0xb7, 0x51);
+			snd_es1688_write(chip, 0xb7, 0xd0);
+		} else {
+			/* 16. bit mono */
+			snd_es1688_write(chip, 0xb7, 0x71);
+			snd_es1688_write(chip, 0xb7, 0xf4);
+		}
+	} else {
+		if (snd_pcm_format_width(runtime->format) == 8) {
+			/* 8. bit stereo */
+			snd_es1688_write(chip, 0xb7, 0x51);
+			snd_es1688_write(chip, 0xb7, 0x98);
+		} else {
+			/* 16. bit stereo */
+			snd_es1688_write(chip, 0xb7, 0x71);
+			snd_es1688_write(chip, 0xb7, 0xbc);
+		}
+	}
+	snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50);
+	snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	/* --- */
+	count = -count;
+	snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1688_write(chip, 0xa4, (unsigned char) count);
+	snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_es1688_capture_trigger(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	return snd_es1688_trigger(chip, cmd, 0x0f);
+}
+
+static irqreturn_t snd_es1688_interrupt(int irq, void *dev_id)
+{
+	struct snd_es1688 *chip = dev_id;
+
+	if (chip->trigger_value == 0x05)	/* ok.. playback is active */
+		snd_pcm_period_elapsed(chip->playback_substream);
+	if (chip->trigger_value == 0x0f)	/* ok.. capture is active */
+		snd_pcm_period_elapsed(chip->capture_substream);
+
+	inb(ES1688P(chip, DATA_AVAIL));	/* ack interrupt */
+	return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_es1688_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	
+	if (chip->trigger_value != 0x05)
+		return 0;
+	ptr = snd_dma_pointer(chip->dma8, chip->dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_es1688_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	
+	if (chip->trigger_value != 0x0f)
+		return 0;
+	ptr = snd_dma_pointer(chip->dma8, chip->dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static struct snd_pcm_hardware snd_es1688_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_es1688_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+
+ */
+
+static int snd_es1688_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (chip->capture_substream != NULL)
+		return -EAGAIN;
+	chip->playback_substream = substream;
+	runtime->hw = snd_es1688_playback;
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &hw_constraints_clocks);
+	return 0;
+}
+
+static int snd_es1688_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (chip->playback_substream != NULL)
+		return -EAGAIN;
+	chip->capture_substream = substream;
+	runtime->hw = snd_es1688_capture;
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &hw_constraints_clocks);
+	return 0;
+}
+
+static int snd_es1688_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	return 0;
+}
+
+static int snd_es1688_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	return 0;
+}
+
+static int snd_es1688_free(struct snd_es1688 *chip)
+{
+	if (chip->res_port) {
+		snd_es1688_init(chip, 0);
+		release_and_free_resource(chip->res_port);
+	}
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *) chip);
+	if (chip->dma8 >= 0) {
+		disable_dma(chip->dma8);
+		free_dma(chip->dma8);
+	}
+	return 0;
+}
+
+static int snd_es1688_dev_free(struct snd_device *device)
+{
+	struct snd_es1688 *chip = device->device_data;
+	return snd_es1688_free(chip);
+}
+
+static const char *snd_es1688_chip_id(struct snd_es1688 *chip)
+{
+	static char tmp[16];
+	sprintf(tmp, "ES%s688 rev %i", chip->hardware == ES1688_HW_688 ? "" : "1", chip->version & 0x0f);
+	return tmp;
+}
+
+int snd_es1688_create(struct snd_card *card,
+		      struct snd_es1688 *chip,
+		      unsigned long port,
+		      unsigned long mpu_port,
+		      int irq,
+		      int mpu_irq,
+		      int dma8,
+		      unsigned short hardware)
+{
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_es1688_dev_free,
+	};
+                                
+	int err;
+
+	if (chip == NULL)
+		return -ENOMEM;
+	chip->irq = -1;
+	chip->dma8 = -1;
+	
+	if ((chip->res_port = request_region(port + 4, 12, "ES1688")) == NULL) {
+		snd_printk(KERN_ERR "es1688: can't grab port 0x%lx\n", port + 4);
+		return -EBUSY;
+	}
+	if (request_irq(irq, snd_es1688_interrupt, IRQF_DISABLED, "ES1688", (void *) chip)) {
+		snd_printk(KERN_ERR "es1688: can't grab IRQ %d\n", irq);
+		return -EBUSY;
+	}
+	chip->irq = irq;
+	if (request_dma(dma8, "ES1688")) {
+		snd_printk(KERN_ERR "es1688: can't grab DMA8 %d\n", dma8);
+		return -EBUSY;
+	}
+	chip->dma8 = dma8;
+
+	spin_lock_init(&chip->reg_lock);
+	spin_lock_init(&chip->mixer_lock);
+	chip->port = port;
+	mpu_port &= ~0x000f;
+	if (mpu_port < 0x300 || mpu_port > 0x330)
+		mpu_port = 0;
+	chip->mpu_port = mpu_port;
+	chip->mpu_irq = mpu_irq;
+	chip->hardware = hardware;
+
+	err = snd_es1688_probe(chip);
+	if (err < 0)
+		return err;
+
+	err = snd_es1688_init(chip, 1);
+	if (err < 0)
+		return err;
+
+	/* Register device */
+	return snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+}
+
+static struct snd_pcm_ops snd_es1688_playback_ops = {
+	.open =			snd_es1688_playback_open,
+	.close =		snd_es1688_playback_close,
+	.ioctl =		snd_es1688_ioctl,
+	.hw_params =		snd_es1688_hw_params,
+	.hw_free =		snd_es1688_hw_free,
+	.prepare =		snd_es1688_playback_prepare,
+	.trigger =		snd_es1688_playback_trigger,
+	.pointer =		snd_es1688_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_es1688_capture_ops = {
+	.open =			snd_es1688_capture_open,
+	.close =		snd_es1688_capture_close,
+	.ioctl =		snd_es1688_ioctl,
+	.hw_params =		snd_es1688_hw_params,
+	.hw_free =		snd_es1688_hw_free,
+	.prepare =		snd_es1688_capture_prepare,
+	.trigger =		snd_es1688_capture_trigger,
+	.pointer =		snd_es1688_capture_pointer,
+};
+
+int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip,
+		   int device, struct snd_pcm **rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(card, "ESx688", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es1688_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es1688_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+	sprintf(pcm->name, snd_es1688_chip_id(chip));
+	chip->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, 64*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+/*
+ *  MIXER part
+ */
+
+static int snd_es1688_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[9] = {
+		"Mic", "Mic Master", "CD", "AOUT",
+		"Mic1", "Mix", "Line", "Master"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 8;
+	if (uinfo->value.enumerated.item > 7)
+		uinfo->value.enumerated.item = 7;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_es1688_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = snd_es1688_mixer_read(chip, ES1688_REC_DEV) & 7;
+	return 0;
+}
+
+static int snd_es1688_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char oval, nval;
+	int change;
+	
+	if (ucontrol->value.enumerated.item[0] > 8)
+		return -EINVAL;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	oval = snd_es1688_mixer_read(chip, ES1688_REC_DEV);
+	nval = (ucontrol->value.enumerated.item[0] & 7) | (oval & ~15);
+	change = nval != oval;
+	if (change)
+		snd_es1688_mixer_write(chip, ES1688_REC_DEV, nval);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define ES1688_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_es1688_info_single, \
+  .get = snd_es1688_get_single, .put = snd_es1688_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_es1688_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_es1688_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (snd_es1688_mixer_read(chip, reg) >> shift) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_es1688_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned char oval, nval;
+	
+	nval = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		nval = mask - nval;
+	nval <<= shift;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	oval = snd_es1688_mixer_read(chip, reg);
+	nval = (oval & ~(mask << shift)) | nval;
+	change = nval != oval;
+	if (change)
+		snd_es1688_mixer_write(chip, reg, nval);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define ES1688_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_es1688_info_double, \
+  .get = snd_es1688_get_double, .put = snd_es1688_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+static int snd_es1688_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_es1688_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	unsigned char left, right;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (left_reg < 0xa0)
+		left = snd_es1688_mixer_read(chip, left_reg);
+	else
+		left = snd_es1688_read(chip, left_reg);
+	if (left_reg != right_reg) {
+		if (right_reg < 0xa0) 
+			right = snd_es1688_mixer_read(chip, right_reg);
+		else
+			right = snd_es1688_read(chip, right_reg);
+	} else
+		right = left;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (left >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (right >> shift_right) & mask;
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_es1688_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned char val1, val2, oval1, oval2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (left_reg != right_reg) {
+		if (left_reg < 0xa0)
+			oval1 = snd_es1688_mixer_read(chip, left_reg);
+		else
+			oval1 = snd_es1688_read(chip, left_reg);
+		if (right_reg < 0xa0)
+			oval2 = snd_es1688_mixer_read(chip, right_reg);
+		else
+			oval2 = snd_es1688_read(chip, right_reg);
+		val1 = (oval1 & ~(mask << shift_left)) | val1;
+		val2 = (oval2 & ~(mask << shift_right)) | val2;
+		change = val1 != oval1 || val2 != oval2;
+		if (change) {
+			if (left_reg < 0xa0)
+				snd_es1688_mixer_write(chip, left_reg, val1);
+			else
+				snd_es1688_write(chip, left_reg, val1);
+			if (right_reg < 0xa0)
+				snd_es1688_mixer_write(chip, right_reg, val1);
+			else
+				snd_es1688_write(chip, right_reg, val1);
+		}
+	} else {
+		if (left_reg < 0xa0)
+			oval1 = snd_es1688_mixer_read(chip, left_reg);
+		else
+			oval1 = snd_es1688_read(chip, left_reg);
+		val1 = (oval1 & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+		change = val1 != oval1;
+		if (change) {
+			if (left_reg < 0xa0)
+				snd_es1688_mixer_write(chip, left_reg, val1);
+			else
+				snd_es1688_write(chip, left_reg, val1);
+		}
+			
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_es1688_controls[] = {
+ES1688_DOUBLE("Master Playback Volume", 0, ES1688_MASTER_DEV, ES1688_MASTER_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("PCM Playback Volume", 0, ES1688_PCM_DEV, ES1688_PCM_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("Line Playback Volume", 0, ES1688_LINE_DEV, ES1688_LINE_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("CD Playback Volume", 0, ES1688_CD_DEV, ES1688_CD_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("FM Playback Volume", 0, ES1688_FM_DEV, ES1688_FM_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("Mic Playback Volume", 0, ES1688_MIC_DEV, ES1688_MIC_DEV, 4, 0, 15, 0),
+ES1688_DOUBLE("Aux Playback Volume", 0, ES1688_AUX_DEV, ES1688_AUX_DEV, 4, 0, 15, 0),
+ES1688_SINGLE("Beep Playback Volume", 0, ES1688_SPEAKER_DEV, 0, 7, 0),
+ES1688_DOUBLE("Capture Volume", 0, ES1688_RECLEV_DEV, ES1688_RECLEV_DEV, 4, 0, 15, 0),
+ES1688_SINGLE("Capture Switch", 0, ES1688_REC_DEV, 4, 1, 1),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_es1688_info_mux,
+	.get = snd_es1688_get_mux,
+	.put = snd_es1688_put_mux,
+},
+};
+
+#define ES1688_INIT_TABLE_SIZE (sizeof(snd_es1688_init_table)/2)
+
+static unsigned char snd_es1688_init_table[][2] = {
+	{ ES1688_MASTER_DEV, 0 },
+	{ ES1688_PCM_DEV, 0 },
+	{ ES1688_LINE_DEV, 0 },
+	{ ES1688_CD_DEV, 0 },
+	{ ES1688_FM_DEV, 0 },
+	{ ES1688_MIC_DEV, 0 },
+	{ ES1688_AUX_DEV, 0 },
+	{ ES1688_SPEAKER_DEV, 0 },
+	{ ES1688_RECLEV_DEV, 0 },
+	{ ES1688_REC_DEV, 0x17 }
+};
+                                        
+int snd_es1688_mixer(struct snd_card *card, struct snd_es1688 *chip)
+{
+	unsigned int idx;
+	int err;
+	unsigned char reg, val;
+
+	if (snd_BUG_ON(!chip || !card))
+		return -EINVAL;
+
+	strcpy(card->mixername, snd_es1688_chip_id(chip));
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_es1688_controls); idx++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es1688_controls[idx], chip))) < 0)
+			return err;
+	}
+	for (idx = 0; idx < ES1688_INIT_TABLE_SIZE; idx++) {
+		reg = snd_es1688_init_table[idx][0];
+		val = snd_es1688_init_table[idx][1];
+		if (reg < 0xa0)
+			snd_es1688_mixer_write(chip, reg, val);
+		else
+			snd_es1688_write(chip, reg, val);
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_es1688_mixer_write);
+EXPORT_SYMBOL(snd_es1688_create);
+EXPORT_SYMBOL(snd_es1688_pcm);
+EXPORT_SYMBOL(snd_es1688_mixer);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_es1688_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_es1688_exit(void)
+{
+}
+
+module_init(alsa_es1688_init)
+module_exit(alsa_es1688_exit)
diff --git a/linux/sound/isa/es18xx.c b/linux/sound/isa/es18xx.c
new file mode 100644
index 0000000..fb4d6b3
--- /dev/null
+++ b/linux/sound/isa/es18xx.c
@@ -0,0 +1,2434 @@
+/*
+ *  Driver for generic ESS AudioDrive ES18xx soundcards
+ *  Copyright (c) by Christian Fischbach <fishbach@pool.informatik.rwth-aachen.de>
+ *  Copyright (c) by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+/* GENERAL NOTES:
+ *
+ * BUGS:
+ * - There are pops (we can't delay in trigger function, cause midlevel 
+ *   often need to trigger down and then up very quickly).
+ *   Any ideas?
+ * - Support for 16 bit DMA seems to be broken. I've no hardware to tune it.
+ */
+
+/*
+ * ES1868  NOTES:
+ * - The chip has one half duplex pcm (with very limited full duplex support).
+ *
+ * - Duplex stereophonic sound is impossible.
+ * - Record and playback must share the same frequency rate.
+ *
+ * - The driver use dma2 for playback and dma1 for capture.
+ */
+
+/*
+ * ES1869 NOTES:
+ *
+ * - there are a first full duplex pcm and a second playback only pcm
+ *   (incompatible with first pcm capture)
+ * 
+ * - there is support for the capture volume and ESS Spatializer 3D effect.
+ *
+ * - contrarily to some pages in DS_1869.PDF the rates can be set
+ *   independently.
+ *
+ * - Zoom Video is implemented by sharing the FM DAC, thus the user can
+ *   have either FM playback or Video playback but not both simultaneously.
+ *   The Video Playback Switch mixer control toggles this choice.
+ *
+ * BUGS:
+ *
+ * - There is a major trouble I noted:
+ *
+ *   using both channel for playback stereo 16 bit samples at 44100 Hz
+ *   the second pcm (Audio1) DMA slows down irregularly and sound is garbled.
+ *   
+ *   The same happens using Audio1 for captureing.
+ *
+ *   The Windows driver does not suffer of this (although it use Audio1
+ *   only for captureing). I'm unable to discover why.
+ *
+ */
+
+/*
+ * ES1879 NOTES:
+ * - When Zoom Video is enabled (reg 0x71 bit 6 toggled on) the PCM playback
+ *   seems to be effected (speaker_test plays a lower frequency). Can't find
+ *   anything in the datasheet to account for this, so a Video Playback Switch
+ *   control has been included to allow ZV to be enabled only when necessary.
+ *   Then again on at least one test system the 0x71 bit 6 enable bit is not 
+ *   needed for ZV, so maybe the datasheet is entirely wrong here.
+ */
+ 
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/isapnp.h>
+#include <linux/moduleparam.h>
+#include <linux/delay.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define PFX "es18xx: "
+
+struct snd_es18xx {
+	unsigned long port;		/* port of ESS chip */
+	unsigned long ctrl_port;	/* Control port of ESS chip */
+	struct resource *res_port;
+	struct resource *res_mpu_port;
+	struct resource *res_ctrl_port;
+	int irq;			/* IRQ number of ESS chip */
+	int dma1;			/* DMA1 */
+	int dma2;			/* DMA2 */
+	unsigned short version;		/* version of ESS chip */
+	int caps;			/* Chip capabilities */
+	unsigned short audio2_vol;	/* volume level of audio2 */
+
+	unsigned short active;		/* active channel mask */
+	unsigned int dma1_shift;
+	unsigned int dma2_shift;
+
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *playback_a_substream;
+	struct snd_pcm_substream *capture_a_substream;
+	struct snd_pcm_substream *playback_b_substream;
+
+	struct snd_rawmidi *rmidi;
+
+	struct snd_kcontrol *hw_volume;
+	struct snd_kcontrol *hw_switch;
+	struct snd_kcontrol *master_volume;
+	struct snd_kcontrol *master_switch;
+
+	spinlock_t reg_lock;
+	spinlock_t mixer_lock;
+#ifdef CONFIG_PM
+	unsigned char pm_reg;
+#endif
+#ifdef CONFIG_PNP
+	struct pnp_dev *dev;
+	struct pnp_dev *devc;
+#endif
+};
+
+#define AUDIO1_IRQ	0x01
+#define AUDIO2_IRQ	0x02
+#define HWV_IRQ		0x04
+#define MPU_IRQ		0x08
+
+#define ES18XX_PCM2	0x0001	/* Has two useable PCM */
+#define ES18XX_SPATIALIZER 0x0002	/* Has 3D Spatializer */
+#define ES18XX_RECMIX	0x0004	/* Has record mixer */
+#define ES18XX_DUPLEX_MONO 0x0008	/* Has mono duplex only */
+#define ES18XX_DUPLEX_SAME 0x0010	/* Playback and record must share the same rate */
+#define ES18XX_NEW_RATE	0x0020	/* More precise rate setting */
+#define ES18XX_AUXB	0x0040	/* AuxB mixer control */
+#define ES18XX_HWV	0x0080	/* Has separate hardware volume mixer controls*/
+#define ES18XX_MONO	0x0100	/* Mono_in mixer control */
+#define ES18XX_I2S	0x0200	/* I2S mixer control */
+#define ES18XX_MUTEREC	0x0400	/* Record source can be muted */
+#define ES18XX_CONTROL	0x0800	/* Has control ports */
+
+/* Power Management */
+#define ES18XX_PM	0x07
+#define ES18XX_PM_GPO0	0x01
+#define ES18XX_PM_GPO1	0x02
+#define ES18XX_PM_PDR	0x04
+#define ES18XX_PM_ANA	0x08
+#define ES18XX_PM_FM	0x020
+#define ES18XX_PM_SUS	0x080
+
+/* Lowlevel */
+
+#define DAC1 0x01
+#define ADC1 0x02
+#define DAC2 0x04
+#define MILLISECOND 10000
+
+static int snd_es18xx_dsp_command(struct snd_es18xx *chip, unsigned char val)
+{
+        int i;
+
+        for(i = MILLISECOND; i; i--)
+                if ((inb(chip->port + 0x0C) & 0x80) == 0) {
+                        outb(val, chip->port + 0x0C);
+                        return 0;
+                }
+	snd_printk(KERN_ERR "dsp_command: timeout (0x%x)\n", val);
+        return -EINVAL;
+}
+
+static int snd_es18xx_dsp_get_byte(struct snd_es18xx *chip)
+{
+        int i;
+
+        for(i = MILLISECOND/10; i; i--)
+                if (inb(chip->port + 0x0C) & 0x40)
+                        return inb(chip->port + 0x0A);
+	snd_printk(KERN_ERR "dsp_get_byte failed: 0x%lx = 0x%x!!!\n",
+		   chip->port + 0x0A, inb(chip->port + 0x0A));
+        return -ENODEV;
+}
+
+#undef REG_DEBUG
+
+static int snd_es18xx_write(struct snd_es18xx *chip,
+			    unsigned char reg, unsigned char data)
+{
+	unsigned long flags;
+	int ret;
+	
+        spin_lock_irqsave(&chip->reg_lock, flags);
+	ret = snd_es18xx_dsp_command(chip, reg);
+	if (ret < 0)
+		goto end;
+        ret = snd_es18xx_dsp_command(chip, data);
+ end:
+        spin_unlock_irqrestore(&chip->reg_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk(KERN_DEBUG "Reg %02x set to %02x\n", reg, data);
+#endif
+	return ret;
+}
+
+static int snd_es18xx_read(struct snd_es18xx *chip, unsigned char reg)
+{
+	unsigned long flags;
+	int ret, data;
+        spin_lock_irqsave(&chip->reg_lock, flags);
+	ret = snd_es18xx_dsp_command(chip, 0xC0);
+	if (ret < 0)
+		goto end;
+        ret = snd_es18xx_dsp_command(chip, reg);
+	if (ret < 0)
+		goto end;
+	data = snd_es18xx_dsp_get_byte(chip);
+	ret = data;
+#ifdef REG_DEBUG
+	snd_printk(KERN_DEBUG "Reg %02x now is %02x (%d)\n", reg, data, ret);
+#endif
+ end:
+        spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return ret;
+}
+
+/* Return old value */
+static int snd_es18xx_bits(struct snd_es18xx *chip, unsigned char reg,
+			   unsigned char mask, unsigned char val)
+{
+        int ret;
+	unsigned char old, new, oval;
+	unsigned long flags;
+        spin_lock_irqsave(&chip->reg_lock, flags);
+        ret = snd_es18xx_dsp_command(chip, 0xC0);
+	if (ret < 0)
+		goto end;
+        ret = snd_es18xx_dsp_command(chip, reg);
+	if (ret < 0)
+		goto end;
+	ret = snd_es18xx_dsp_get_byte(chip);
+	if (ret < 0) {
+		goto end;
+	}
+	old = ret;
+	oval = old & mask;
+	if (val != oval) {
+		ret = snd_es18xx_dsp_command(chip, reg);
+		if (ret < 0)
+			goto end;
+		new = (old & ~mask) | (val & mask);
+		ret = snd_es18xx_dsp_command(chip, new);
+		if (ret < 0)
+			goto end;
+#ifdef REG_DEBUG
+		snd_printk(KERN_DEBUG "Reg %02x was %02x, set to %02x (%d)\n",
+			   reg, old, new, ret);
+#endif
+	}
+	ret = oval;
+ end:
+        spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return ret;
+}
+
+static inline void snd_es18xx_mixer_write(struct snd_es18xx *chip,
+			    unsigned char reg, unsigned char data)
+{
+	unsigned long flags;
+        spin_lock_irqsave(&chip->mixer_lock, flags);
+        outb(reg, chip->port + 0x04);
+        outb(data, chip->port + 0x05);
+        spin_unlock_irqrestore(&chip->mixer_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk(KERN_DEBUG "Mixer reg %02x set to %02x\n", reg, data);
+#endif
+}
+
+static inline int snd_es18xx_mixer_read(struct snd_es18xx *chip, unsigned char reg)
+{
+	unsigned long flags;
+	int data;
+        spin_lock_irqsave(&chip->mixer_lock, flags);
+        outb(reg, chip->port + 0x04);
+	data = inb(chip->port + 0x05);
+        spin_unlock_irqrestore(&chip->mixer_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk(KERN_DEBUG "Mixer reg %02x now is %02x\n", reg, data);
+#endif
+        return data;
+}
+
+/* Return old value */
+static inline int snd_es18xx_mixer_bits(struct snd_es18xx *chip, unsigned char reg,
+					unsigned char mask, unsigned char val)
+{
+	unsigned char old, new, oval;
+	unsigned long flags;
+        spin_lock_irqsave(&chip->mixer_lock, flags);
+        outb(reg, chip->port + 0x04);
+	old = inb(chip->port + 0x05);
+	oval = old & mask;
+	if (val != oval) {
+		new = (old & ~mask) | (val & mask);
+		outb(new, chip->port + 0x05);
+#ifdef REG_DEBUG
+		snd_printk(KERN_DEBUG "Mixer reg %02x was %02x, set to %02x\n",
+			   reg, old, new);
+#endif
+	}
+        spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	return oval;
+}
+
+static inline int snd_es18xx_mixer_writable(struct snd_es18xx *chip, unsigned char reg,
+					    unsigned char mask)
+{
+	int old, expected, new;
+	unsigned long flags;
+        spin_lock_irqsave(&chip->mixer_lock, flags);
+        outb(reg, chip->port + 0x04);
+	old = inb(chip->port + 0x05);
+	expected = old ^ mask;
+	outb(expected, chip->port + 0x05);
+	new = inb(chip->port + 0x05);
+        spin_unlock_irqrestore(&chip->mixer_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk(KERN_DEBUG "Mixer reg %02x was %02x, set to %02x, now is %02x\n",
+		   reg, old, expected, new);
+#endif
+	return expected == new;
+}
+
+
+static int __devinit snd_es18xx_reset(struct snd_es18xx *chip)
+{
+	int i;
+        outb(0x03, chip->port + 0x06);
+        inb(chip->port + 0x06);
+        outb(0x00, chip->port + 0x06);
+        for(i = 0; i < MILLISECOND && !(inb(chip->port + 0x0E) & 0x80); i++);
+        if (inb(chip->port + 0x0A) != 0xAA)
+                return -1;
+	return 0;
+}
+
+static int snd_es18xx_reset_fifo(struct snd_es18xx *chip)
+{
+        outb(0x02, chip->port + 0x06);
+        inb(chip->port + 0x06);
+        outb(0x00, chip->port + 0x06);
+	return 0;
+}
+
+static struct snd_ratnum new_clocks[2] = {
+	{
+		.num = 793800,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	},
+	{
+		.num = 768000,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	}
+};
+
+static struct snd_pcm_hw_constraint_ratnums new_hw_constraints_clocks = {
+	.nrats = 2,
+	.rats = new_clocks,
+};
+
+static struct snd_ratnum old_clocks[2] = {
+	{
+		.num = 795444,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	},
+	{
+		.num = 397722,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	}
+};
+
+static struct snd_pcm_hw_constraint_ratnums old_hw_constraints_clocks  = {
+	.nrats = 2,
+	.rats = old_clocks,
+};
+
+
+static void snd_es18xx_rate_set(struct snd_es18xx *chip, 
+				struct snd_pcm_substream *substream,
+				int mode)
+{
+	unsigned int bits, div0;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	if (chip->caps & ES18XX_NEW_RATE) {
+		if (runtime->rate_num == new_clocks[0].num)
+			bits = 128 - runtime->rate_den;
+		else
+			bits = 256 - runtime->rate_den;
+	} else {
+		if (runtime->rate_num == old_clocks[0].num)
+			bits = 256 - runtime->rate_den;
+		else
+			bits = 128 - runtime->rate_den;
+	}
+
+	/* set filter register */
+	div0 = 256 - 7160000*20/(8*82*runtime->rate);
+		
+	if ((chip->caps & ES18XX_PCM2) && mode == DAC2) {
+		snd_es18xx_mixer_write(chip, 0x70, bits);
+		/*
+		 * Comment from kernel oss driver:
+		 * FKS: fascinating: 0x72 doesn't seem to work.
+		 */
+		snd_es18xx_write(chip, 0xA2, div0);
+		snd_es18xx_mixer_write(chip, 0x72, div0);
+	} else {
+		snd_es18xx_write(chip, 0xA1, bits);
+		snd_es18xx_write(chip, 0xA2, div0);
+	}
+}
+
+static int snd_es18xx_playback_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+	int shift, err;
+
+	shift = 0;
+	if (params_channels(hw_params) == 2)
+		shift++;
+	if (snd_pcm_format_width(params_format(hw_params)) == 16)
+		shift++;
+
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) {
+		if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+		    (chip->capture_a_substream) &&
+		    params_channels(hw_params) != 1) {
+			_snd_pcm_hw_param_setempty(hw_params, SNDRV_PCM_HW_PARAM_CHANNELS);
+			return -EBUSY;
+		}
+		chip->dma2_shift = shift;
+	} else {
+		chip->dma1_shift = shift;
+	}
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	return 0;
+}
+
+static int snd_es18xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_es18xx_playback1_prepare(struct snd_es18xx *chip,
+					struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+        snd_es18xx_rate_set(chip, substream, DAC2);
+
+        /* Transfer Count Reload */
+        count = 0x10000 - count;
+        snd_es18xx_mixer_write(chip, 0x74, count & 0xff);
+        snd_es18xx_mixer_write(chip, 0x76, count >> 8);
+
+	/* Set format */
+        snd_es18xx_mixer_bits(chip, 0x7A, 0x07,
+			      ((runtime->channels == 1) ? 0x00 : 0x02) |
+			      (snd_pcm_format_width(runtime->format) == 16 ? 0x01 : 0x00) |
+			      (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x04));
+
+        /* Set DMA controller */
+        snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+
+	return 0;
+}
+
+static int snd_es18xx_playback1_trigger(struct snd_es18xx *chip,
+					struct snd_pcm_substream *substream,
+					int cmd)
+{
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (chip->active & DAC2)
+			return 0;
+		chip->active |= DAC2;
+                /* Start DMA */
+		if (chip->dma2 >= 4)
+			snd_es18xx_mixer_write(chip, 0x78, 0xb3);
+		else
+			snd_es18xx_mixer_write(chip, 0x78, 0x93);
+#ifdef AVOID_POPS
+		/* Avoid pops */
+                udelay(100000);
+		if (chip->caps & ES18XX_PCM2)
+			/* Restore Audio 2 volume */
+			snd_es18xx_mixer_write(chip, 0x7C, chip->audio2_vol);
+		else
+			/* Enable PCM output */
+			snd_es18xx_dsp_command(chip, 0xD1);
+#endif
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (!(chip->active & DAC2))
+			return 0;
+		chip->active &= ~DAC2;
+                /* Stop DMA */
+                snd_es18xx_mixer_write(chip, 0x78, 0x00);
+#ifdef AVOID_POPS
+                udelay(25000);
+		if (chip->caps & ES18XX_PCM2)
+			/* Set Audio 2 volume to 0 */
+			snd_es18xx_mixer_write(chip, 0x7C, 0);
+		else
+			/* Disable PCM output */
+			snd_es18xx_dsp_command(chip, 0xD3);
+#endif
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int snd_es18xx_capture_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+	int shift, err;
+
+	shift = 0;
+	if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+	    chip->playback_a_substream &&
+	    params_channels(hw_params) != 1) {
+		_snd_pcm_hw_param_setempty(hw_params, SNDRV_PCM_HW_PARAM_CHANNELS);
+		return -EBUSY;
+	}
+	if (params_channels(hw_params) == 2)
+		shift++;
+	if (snd_pcm_format_width(params_format(hw_params)) == 16)
+		shift++;
+	chip->dma1_shift = shift;
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	return 0;
+}
+
+static int snd_es18xx_capture_prepare(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	snd_es18xx_reset_fifo(chip);
+
+        /* Set stereo/mono */
+        snd_es18xx_bits(chip, 0xA8, 0x03, runtime->channels == 1 ? 0x02 : 0x01);
+
+        snd_es18xx_rate_set(chip, substream, ADC1);
+
+        /* Transfer Count Reload */
+	count = 0x10000 - count;
+	snd_es18xx_write(chip, 0xA4, count & 0xff);
+	snd_es18xx_write(chip, 0xA5, count >> 8);
+
+#ifdef AVOID_POPS
+	udelay(100000);
+#endif
+
+        /* Set format */
+        snd_es18xx_write(chip, 0xB7, 
+                         snd_pcm_format_unsigned(runtime->format) ? 0x51 : 0x71);
+        snd_es18xx_write(chip, 0xB7, 0x90 |
+                         ((runtime->channels == 1) ? 0x40 : 0x08) |
+                         (snd_pcm_format_width(runtime->format) == 16 ? 0x04 : 0x00) |
+                         (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x20));
+
+        /* Set DMA controller */
+        snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+
+	return 0;
+}
+
+static int snd_es18xx_capture_trigger(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (chip->active & ADC1)
+			return 0;
+		chip->active |= ADC1;
+                /* Start DMA */
+                snd_es18xx_write(chip, 0xB8, 0x0f);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (!(chip->active & ADC1))
+			return 0;
+		chip->active &= ~ADC1;
+                /* Stop DMA */
+                snd_es18xx_write(chip, 0xB8, 0x00);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int snd_es18xx_playback2_prepare(struct snd_es18xx *chip,
+					struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	snd_es18xx_reset_fifo(chip);
+
+        /* Set stereo/mono */
+        snd_es18xx_bits(chip, 0xA8, 0x03, runtime->channels == 1 ? 0x02 : 0x01);
+
+        snd_es18xx_rate_set(chip, substream, DAC1);
+
+        /* Transfer Count Reload */
+	count = 0x10000 - count;
+	snd_es18xx_write(chip, 0xA4, count & 0xff);
+	snd_es18xx_write(chip, 0xA5, count >> 8);
+
+        /* Set format */
+        snd_es18xx_write(chip, 0xB6,
+                         snd_pcm_format_unsigned(runtime->format) ? 0x80 : 0x00);
+        snd_es18xx_write(chip, 0xB7, 
+                         snd_pcm_format_unsigned(runtime->format) ? 0x51 : 0x71);
+        snd_es18xx_write(chip, 0xB7, 0x90 |
+                         (runtime->channels == 1 ? 0x40 : 0x08) |
+                         (snd_pcm_format_width(runtime->format) == 16 ? 0x04 : 0x00) |
+                         (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x20));
+
+        /* Set DMA controller */
+        snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+
+	return 0;
+}
+
+static int snd_es18xx_playback2_trigger(struct snd_es18xx *chip,
+					struct snd_pcm_substream *substream,
+					int cmd)
+{
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (chip->active & DAC1)
+			return 0;
+		chip->active |= DAC1;
+                /* Start DMA */
+                snd_es18xx_write(chip, 0xB8, 0x05);
+#ifdef AVOID_POPS
+		/* Avoid pops */
+                udelay(100000);
+                /* Enable Audio 1 */
+                snd_es18xx_dsp_command(chip, 0xD1);
+#endif
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (!(chip->active & DAC1))
+			return 0;
+		chip->active &= ~DAC1;
+                /* Stop DMA */
+                snd_es18xx_write(chip, 0xB8, 0x00);
+#ifdef AVOID_POPS
+		/* Avoid pops */
+                udelay(25000);
+                /* Disable Audio 1 */
+                snd_es18xx_dsp_command(chip, 0xD3);
+#endif
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int snd_es18xx_playback_prepare(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2))
+		return snd_es18xx_playback1_prepare(chip, substream);
+	else
+		return snd_es18xx_playback2_prepare(chip, substream);
+}
+
+static int snd_es18xx_playback_trigger(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2))
+		return snd_es18xx_playback1_trigger(chip, substream, cmd);
+	else
+		return snd_es18xx_playback2_trigger(chip, substream, cmd);
+}
+
+static irqreturn_t snd_es18xx_interrupt(int irq, void *dev_id)
+{
+	struct snd_card *card = dev_id;
+	struct snd_es18xx *chip = card->private_data;
+	unsigned char status;
+
+	if (chip->caps & ES18XX_CONTROL) {
+		/* Read Interrupt status */
+		status = inb(chip->ctrl_port + 6);
+	} else {
+		/* Read Interrupt status */
+		status = snd_es18xx_mixer_read(chip, 0x7f) >> 4;
+	}
+#if 0
+	else {
+		status = 0;
+		if (inb(chip->port + 0x0C) & 0x01)
+			status |= AUDIO1_IRQ;
+		if (snd_es18xx_mixer_read(chip, 0x7A) & 0x80)
+			status |= AUDIO2_IRQ;
+		if ((chip->caps & ES18XX_HWV) &&
+		    snd_es18xx_mixer_read(chip, 0x64) & 0x10)
+			status |= HWV_IRQ;
+	}
+#endif
+
+	/* Audio 1 & Audio 2 */
+        if (status & AUDIO2_IRQ) {
+                if (chip->active & DAC2)
+                	snd_pcm_period_elapsed(chip->playback_a_substream);
+		/* ack interrupt */
+                snd_es18xx_mixer_bits(chip, 0x7A, 0x80, 0x00);
+        }
+        if (status & AUDIO1_IRQ) {
+                /* ok.. capture is active */
+                if (chip->active & ADC1)
+                	snd_pcm_period_elapsed(chip->capture_a_substream);
+                /* ok.. playback2 is active */
+                else if (chip->active & DAC1)
+                	snd_pcm_period_elapsed(chip->playback_b_substream);
+		/* ack interrupt */
+		inb(chip->port + 0x0E);
+        }
+
+	/* MPU */
+	if ((status & MPU_IRQ) && chip->rmidi)
+		snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+
+	/* Hardware volume */
+	if (status & HWV_IRQ) {
+		int split = 0;
+		if (chip->caps & ES18XX_HWV) {
+			split = snd_es18xx_mixer_read(chip, 0x64) & 0x80;
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+					&chip->hw_switch->id);
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+					&chip->hw_volume->id);
+		}
+		if (!split) {
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+					&chip->master_switch->id);
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+					&chip->master_volume->id);
+		}
+		/* ack interrupt */
+		snd_es18xx_mixer_write(chip, 0x66, 0x00);
+	}
+	return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_es18xx_playback_pointer(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	int pos;
+
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) {
+		if (!(chip->active & DAC2))
+			return 0;
+		pos = snd_dma_pointer(chip->dma2, size);
+		return pos >> chip->dma2_shift;
+	} else {
+		if (!(chip->active & DAC1))
+			return 0;
+		pos = snd_dma_pointer(chip->dma1, size);
+		return pos >> chip->dma1_shift;
+	}
+}
+
+static snd_pcm_uframes_t snd_es18xx_capture_pointer(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	int pos;
+
+        if (!(chip->active & ADC1))
+                return 0;
+	pos = snd_dma_pointer(chip->dma1, size);
+	return pos >> chip->dma1_shift;
+}
+
+static struct snd_pcm_hardware snd_es18xx_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | 
+				 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_es18xx_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | 
+				 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static int snd_es18xx_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) {
+		if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+		    chip->capture_a_substream && 
+		    chip->capture_a_substream->runtime->channels != 1)
+			return -EAGAIN;
+		chip->playback_a_substream = substream;
+	} else if (substream->number <= 1) {
+		if (chip->capture_a_substream)
+			return -EAGAIN;
+		chip->playback_b_substream = substream;
+	} else {
+		snd_BUG();
+		return -EINVAL;
+	}
+	substream->runtime->hw = snd_es18xx_playback;
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      (chip->caps & ES18XX_NEW_RATE) ? &new_hw_constraints_clocks : &old_hw_constraints_clocks);
+        return 0;
+}
+
+static int snd_es18xx_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+
+        if (chip->playback_b_substream)
+                return -EAGAIN;
+	if ((chip->caps & ES18XX_DUPLEX_MONO) &&
+	    chip->playback_a_substream &&
+	    chip->playback_a_substream->runtime->channels != 1)
+		return -EAGAIN;
+        chip->capture_a_substream = substream;
+	substream->runtime->hw = snd_es18xx_capture;
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      (chip->caps & ES18XX_NEW_RATE) ? &new_hw_constraints_clocks : &old_hw_constraints_clocks);
+        return 0;
+}
+
+static int snd_es18xx_playback_close(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+
+	if (substream->number == 0 && (chip->caps & ES18XX_PCM2))
+		chip->playback_a_substream = NULL;
+	else
+		chip->playback_b_substream = NULL;
+	
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_es18xx_capture_close(struct snd_pcm_substream *substream)
+{
+        struct snd_es18xx *chip = snd_pcm_substream_chip(substream);
+
+        chip->capture_a_substream = NULL;
+	snd_pcm_lib_free_pages(substream);
+        return 0;
+}
+
+/*
+ *  MIXER part
+ */
+
+/* Record source mux routines:
+ * Depending on the chipset this mux switches between 4, 5, or 8 possible inputs.
+ * bit table for the 4/5 source mux:
+ * reg 1C:
+ *  b2 b1 b0   muxSource
+ *   x  0  x   microphone
+ *   0  1  x   CD
+ *   1  1  0   line
+ *   1  1  1   mixer
+ * if it's "mixer" and it's a 5 source mux chipset then reg 7A bit 3 determines
+ * either the play mixer or the capture mixer.
+ *
+ * "map4Source" translates from source number to reg bit pattern
+ * "invMap4Source" translates from reg bit pattern to source number
+ */
+
+static int snd_es18xx_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts5Source[5] = {
+		"Mic", "CD", "Line", "Master", "Mix"
+	};
+	static char *texts8Source[8] = {
+		"Mic", "Mic Master", "CD", "AOUT",
+		"Mic1", "Mix", "Line", "Master"
+	};
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	switch (chip->version) {
+	case 0x1868:
+	case 0x1878:
+		uinfo->value.enumerated.items = 4;
+		if (uinfo->value.enumerated.item > 3)
+			uinfo->value.enumerated.item = 3;
+		strcpy(uinfo->value.enumerated.name,
+			texts5Source[uinfo->value.enumerated.item]);
+		break;
+	case 0x1887:
+	case 0x1888:
+		uinfo->value.enumerated.items = 5;
+		if (uinfo->value.enumerated.item > 4)
+			uinfo->value.enumerated.item = 4;
+		strcpy(uinfo->value.enumerated.name, texts5Source[uinfo->value.enumerated.item]);
+		break;
+	case 0x1869: /* DS somewhat contradictory for 1869: could be be 5 or 8 */
+	case 0x1879:
+		uinfo->value.enumerated.items = 8;
+		if (uinfo->value.enumerated.item > 7)
+			uinfo->value.enumerated.item = 7;
+		strcpy(uinfo->value.enumerated.name, texts8Source[uinfo->value.enumerated.item]);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int snd_es18xx_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	static unsigned char invMap4Source[8] = {0, 0, 1, 1, 0, 0, 2, 3};
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	int muxSource = snd_es18xx_mixer_read(chip, 0x1c) & 0x07;
+	if (!(chip->version == 0x1869 || chip->version == 0x1879)) {
+		muxSource = invMap4Source[muxSource];
+		if (muxSource==3 && 
+		    (chip->version == 0x1887 || chip->version == 0x1888) &&
+		    (snd_es18xx_mixer_read(chip, 0x7a) & 0x08)
+		) 
+			muxSource = 4;
+	}
+	ucontrol->value.enumerated.item[0] = muxSource;
+	return 0;
+}
+
+static int snd_es18xx_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	static unsigned char map4Source[4] = {0, 2, 6, 7};
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char val = ucontrol->value.enumerated.item[0];
+	unsigned char retVal = 0;
+
+	switch (chip->version) {
+ /* 5 source chips */
+	case 0x1887:
+	case 0x1888:
+		if (val > 4)
+			return -EINVAL;
+		if (val == 4) {
+			retVal = snd_es18xx_mixer_bits(chip, 0x7a, 0x08, 0x08) != 0x08;
+			val = 3;
+		} else
+			retVal = snd_es18xx_mixer_bits(chip, 0x7a, 0x08, 0x00) != 0x00;
+ /* 4 source chips */
+	case 0x1868:
+	case 0x1878:
+		if (val > 3)
+			return -EINVAL;
+		val = map4Source[val];
+		break;
+ /* 8 source chips */
+	case 0x1869:
+	case 0x1879:
+		if (val > 7)
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return (snd_es18xx_mixer_bits(chip, 0x1c, 0x07, val) != val) || retVal;
+}
+
+#define snd_es18xx_info_spatializer_enable	snd_ctl_boolean_mono_info
+
+static int snd_es18xx_get_spatializer_enable(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char val = snd_es18xx_mixer_read(chip, 0x50);
+	ucontrol->value.integer.value[0] = !!(val & 8);
+	return 0;
+}
+
+static int snd_es18xx_put_spatializer_enable(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char oval, nval;
+	int change;
+	nval = ucontrol->value.integer.value[0] ? 0x0c : 0x04;
+	oval = snd_es18xx_mixer_read(chip, 0x50) & 0x0c;
+	change = nval != oval;
+	if (change) {
+		snd_es18xx_mixer_write(chip, 0x50, nval & ~0x04);
+		snd_es18xx_mixer_write(chip, 0x50, nval);
+	}
+	return change;
+}
+
+static int snd_es18xx_info_hw_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 63;
+	return 0;
+}
+
+static int snd_es18xx_get_hw_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = snd_es18xx_mixer_read(chip, 0x61) & 0x3f;
+	ucontrol->value.integer.value[1] = snd_es18xx_mixer_read(chip, 0x63) & 0x3f;
+	return 0;
+}
+
+#define snd_es18xx_info_hw_switch	snd_ctl_boolean_stereo_info
+
+static int snd_es18xx_get_hw_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = !(snd_es18xx_mixer_read(chip, 0x61) & 0x40);
+	ucontrol->value.integer.value[1] = !(snd_es18xx_mixer_read(chip, 0x63) & 0x40);
+	return 0;
+}
+
+static void snd_es18xx_hwv_free(struct snd_kcontrol *kcontrol)
+{
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	chip->master_volume = NULL;
+	chip->master_switch = NULL;
+	chip->hw_volume = NULL;
+	chip->hw_switch = NULL;
+}
+
+static int snd_es18xx_reg_bits(struct snd_es18xx *chip, unsigned char reg,
+			       unsigned char mask, unsigned char val)
+{
+	if (reg < 0xa0)
+		return snd_es18xx_mixer_bits(chip, reg, mask, val);
+	else
+		return snd_es18xx_bits(chip, reg, mask, val);
+}
+
+static int snd_es18xx_reg_read(struct snd_es18xx *chip, unsigned char reg)
+{
+	if (reg < 0xa0)
+		return snd_es18xx_mixer_read(chip, reg);
+	else
+		return snd_es18xx_read(chip, reg);
+}
+
+#define ES18XX_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_es18xx_info_single, \
+  .get = snd_es18xx_get_single, .put = snd_es18xx_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_es18xx_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_es18xx_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int val;
+	
+	val = snd_es18xx_reg_read(chip, reg);
+	ucontrol->value.integer.value[0] = (val >> shift) & mask;
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_es18xx_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	unsigned char val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	mask <<= shift;
+	val <<= shift;
+	return snd_es18xx_reg_bits(chip, reg, mask, val) != val;
+}
+
+#define ES18XX_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_es18xx_info_double, \
+  .get = snd_es18xx_get_double, .put = snd_es18xx_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+static int snd_es18xx_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_es18xx_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	unsigned char left, right;
+	
+	left = snd_es18xx_reg_read(chip, left_reg);
+	if (left_reg != right_reg)
+		right = snd_es18xx_reg_read(chip, right_reg);
+	else
+		right = left;
+	ucontrol->value.integer.value[0] = (left >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (right >> shift_right) & mask;
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_es18xx_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned char val1, val2, mask1, mask2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	mask1 = mask << shift_left;
+	mask2 = mask << shift_right;
+	if (left_reg != right_reg) {
+		change = 0;
+		if (snd_es18xx_reg_bits(chip, left_reg, mask1, val1) != val1)
+			change = 1;
+		if (snd_es18xx_reg_bits(chip, right_reg, mask2, val2) != val2)
+			change = 1;
+	} else {
+		change = (snd_es18xx_reg_bits(chip, left_reg, mask1 | mask2, 
+					      val1 | val2) != (val1 | val2));
+	}
+	return change;
+}
+
+/* Mixer controls
+ * These arrays contain setup data for mixer controls.
+ * 
+ * The controls that are universal to all chipsets are fully initialized
+ * here.
+ */
+static struct snd_kcontrol_new snd_es18xx_base_controls[] = {
+ES18XX_DOUBLE("Master Playback Volume", 0, 0x60, 0x62, 0, 0, 63, 0),
+ES18XX_DOUBLE("Master Playback Switch", 0, 0x60, 0x62, 6, 6, 1, 1),
+ES18XX_DOUBLE("Line Playback Volume", 0, 0x3e, 0x3e, 4, 0, 15, 0),
+ES18XX_DOUBLE("CD Playback Volume", 0, 0x38, 0x38, 4, 0, 15, 0),
+ES18XX_DOUBLE("FM Playback Volume", 0, 0x36, 0x36, 4, 0, 15, 0),
+ES18XX_DOUBLE("Mic Playback Volume", 0, 0x1a, 0x1a, 4, 0, 15, 0),
+ES18XX_DOUBLE("Aux Playback Volume", 0, 0x3a, 0x3a, 4, 0, 15, 0),
+ES18XX_SINGLE("Record Monitor", 0, 0xa8, 3, 1, 0),
+ES18XX_DOUBLE("Capture Volume", 0, 0xb4, 0xb4, 4, 0, 15, 0),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_es18xx_info_mux,
+	.get = snd_es18xx_get_mux,
+	.put = snd_es18xx_put_mux,
+}
+};
+
+static struct snd_kcontrol_new snd_es18xx_recmix_controls[] = {
+ES18XX_DOUBLE("PCM Capture Volume", 0, 0x69, 0x69, 4, 0, 15, 0),
+ES18XX_DOUBLE("Mic Capture Volume", 0, 0x68, 0x68, 4, 0, 15, 0),
+ES18XX_DOUBLE("Line Capture Volume", 0, 0x6e, 0x6e, 4, 0, 15, 0),
+ES18XX_DOUBLE("FM Capture Volume", 0, 0x6b, 0x6b, 4, 0, 15, 0),
+ES18XX_DOUBLE("CD Capture Volume", 0, 0x6a, 0x6a, 4, 0, 15, 0),
+ES18XX_DOUBLE("Aux Capture Volume", 0, 0x6c, 0x6c, 4, 0, 15, 0)
+};
+
+/*
+ * The chipset specific mixer controls
+ */
+static struct snd_kcontrol_new snd_es18xx_opt_speaker =
+	ES18XX_SINGLE("Beep Playback Volume", 0, 0x3c, 0, 7, 0);
+
+static struct snd_kcontrol_new snd_es18xx_opt_1869[] = {
+ES18XX_SINGLE("Capture Switch", 0, 0x1c, 4, 1, 1),
+ES18XX_SINGLE("Video Playback Switch", 0, 0x7f, 0, 1, 0),
+ES18XX_DOUBLE("Mono Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0),
+ES18XX_DOUBLE("Mono Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0)
+};
+
+static struct snd_kcontrol_new snd_es18xx_opt_1878 =
+	ES18XX_DOUBLE("Video Playback Volume", 0, 0x68, 0x68, 4, 0, 15, 0);
+
+static struct snd_kcontrol_new snd_es18xx_opt_1879[] = {
+ES18XX_SINGLE("Video Playback Switch", 0, 0x71, 6, 1, 0),
+ES18XX_DOUBLE("Video Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0),
+ES18XX_DOUBLE("Video Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0)
+};
+
+static struct snd_kcontrol_new snd_es18xx_pcm1_controls[] = {
+ES18XX_DOUBLE("PCM Playback Volume", 0, 0x14, 0x14, 4, 0, 15, 0),
+};
+
+static struct snd_kcontrol_new snd_es18xx_pcm2_controls[] = {
+ES18XX_DOUBLE("PCM Playback Volume", 0, 0x7c, 0x7c, 4, 0, 15, 0),
+ES18XX_DOUBLE("PCM Playback Volume", 1, 0x14, 0x14, 4, 0, 15, 0)
+};
+
+static struct snd_kcontrol_new snd_es18xx_spatializer_controls[] = {
+ES18XX_SINGLE("3D Control - Level", 0, 0x52, 0, 63, 0),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "3D Control - Switch",
+	.info = snd_es18xx_info_spatializer_enable,
+	.get = snd_es18xx_get_spatializer_enable,
+	.put = snd_es18xx_put_spatializer_enable,
+}
+};
+
+static struct snd_kcontrol_new snd_es18xx_micpre1_control = 
+ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0xa9, 2, 1, 0);
+
+static struct snd_kcontrol_new snd_es18xx_micpre2_control =
+ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0x7d, 3, 1, 0);
+
+static struct snd_kcontrol_new snd_es18xx_hw_volume_controls[] = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Hardware Master Playback Volume",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = snd_es18xx_info_hw_volume,
+	.get = snd_es18xx_get_hw_volume,
+},
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Hardware Master Playback Switch",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = snd_es18xx_info_hw_switch,
+	.get = snd_es18xx_get_hw_switch,
+},
+ES18XX_SINGLE("Hardware Master Volume Split", 0, 0x64, 7, 1, 0),
+};
+
+static int __devinit snd_es18xx_config_read(struct snd_es18xx *chip, unsigned char reg)
+{
+	int data;
+
+	outb(reg, chip->ctrl_port);
+	data = inb(chip->ctrl_port + 1);
+	return data;
+}
+
+static void __devinit snd_es18xx_config_write(struct snd_es18xx *chip, 
+					      unsigned char reg, unsigned char data)
+{
+	/* No need for spinlocks, this function is used only in
+	   otherwise protected init code */
+	outb(reg, chip->ctrl_port);
+	outb(data, chip->ctrl_port + 1);
+#ifdef REG_DEBUG
+	snd_printk(KERN_DEBUG "Config reg %02x set to %02x\n", reg, data);
+#endif
+}
+
+static int __devinit snd_es18xx_initialize(struct snd_es18xx *chip,
+					   unsigned long mpu_port,
+					   unsigned long fm_port)
+{
+	int mask = 0;
+
+        /* enable extended mode */
+        snd_es18xx_dsp_command(chip, 0xC6);
+	/* Reset mixer registers */
+	snd_es18xx_mixer_write(chip, 0x00, 0x00);
+
+        /* Audio 1 DMA demand mode (4 bytes/request) */
+        snd_es18xx_write(chip, 0xB9, 2);
+	if (chip->caps & ES18XX_CONTROL) {
+		/* Hardware volume IRQ */
+		snd_es18xx_config_write(chip, 0x27, chip->irq);
+		if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) {
+			/* FM I/O */
+			snd_es18xx_config_write(chip, 0x62, fm_port >> 8);
+			snd_es18xx_config_write(chip, 0x63, fm_port & 0xff);
+		}
+		if (mpu_port > 0 && mpu_port != SNDRV_AUTO_PORT) {
+			/* MPU-401 I/O */
+			snd_es18xx_config_write(chip, 0x64, mpu_port >> 8);
+			snd_es18xx_config_write(chip, 0x65, mpu_port & 0xff);
+			/* MPU-401 IRQ */
+			snd_es18xx_config_write(chip, 0x28, chip->irq);
+		}
+		/* Audio1 IRQ */
+		snd_es18xx_config_write(chip, 0x70, chip->irq);
+		/* Audio2 IRQ */
+		snd_es18xx_config_write(chip, 0x72, chip->irq);
+		/* Audio1 DMA */
+		snd_es18xx_config_write(chip, 0x74, chip->dma1);
+		/* Audio2 DMA */
+		snd_es18xx_config_write(chip, 0x75, chip->dma2);
+
+		/* Enable Audio 1 IRQ */
+		snd_es18xx_write(chip, 0xB1, 0x50);
+		/* Enable Audio 2 IRQ */
+		snd_es18xx_mixer_write(chip, 0x7A, 0x40);
+		/* Enable Audio 1 DMA */
+		snd_es18xx_write(chip, 0xB2, 0x50);
+		/* Enable MPU and hardware volume interrupt */
+		snd_es18xx_mixer_write(chip, 0x64, 0x42);
+		/* Enable ESS wavetable input */
+		snd_es18xx_mixer_bits(chip, 0x48, 0x10, 0x10);
+	}
+	else {
+		int irqmask, dma1mask, dma2mask;
+		switch (chip->irq) {
+		case 2:
+		case 9:
+			irqmask = 0;
+			break;
+		case 5:
+			irqmask = 1;
+			break;
+		case 7:
+			irqmask = 2;
+			break;
+		case 10:
+			irqmask = 3;
+			break;
+		default:
+			snd_printk(KERN_ERR "invalid irq %d\n", chip->irq);
+			return -ENODEV;
+		}
+		switch (chip->dma1) {
+		case 0:
+			dma1mask = 1;
+			break;
+		case 1:
+			dma1mask = 2;
+			break;
+		case 3:
+			dma1mask = 3;
+			break;
+		default:
+			snd_printk(KERN_ERR "invalid dma1 %d\n", chip->dma1);
+			return -ENODEV;
+		}
+		switch (chip->dma2) {
+		case 0:
+			dma2mask = 0;
+			break;
+		case 1:
+			dma2mask = 1;
+			break;
+		case 3:
+			dma2mask = 2;
+			break;
+		case 5:
+			dma2mask = 3;
+			break;
+		default:
+			snd_printk(KERN_ERR "invalid dma2 %d\n", chip->dma2);
+			return -ENODEV;
+		}
+
+		/* Enable and set Audio 1 IRQ */
+		snd_es18xx_write(chip, 0xB1, 0x50 | (irqmask << 2));
+		/* Enable and set Audio 1 DMA */
+		snd_es18xx_write(chip, 0xB2, 0x50 | (dma1mask << 2));
+		/* Set Audio 2 DMA */
+		snd_es18xx_mixer_bits(chip, 0x7d, 0x07, 0x04 | dma2mask);
+		/* Enable Audio 2 IRQ and DMA
+		   Set capture mixer input */
+		snd_es18xx_mixer_write(chip, 0x7A, 0x68);
+		/* Enable and set hardware volume interrupt */
+		snd_es18xx_mixer_write(chip, 0x64, 0x06);
+		if (mpu_port > 0 && mpu_port != SNDRV_AUTO_PORT) {
+			/* MPU401 share irq with audio
+			   Joystick enabled
+			   FM enabled */
+			snd_es18xx_mixer_write(chip, 0x40,
+					       0x43 | (mpu_port & 0xf0) >> 1);
+		}
+		snd_es18xx_mixer_write(chip, 0x7f, ((irqmask + 1) << 1) | 0x01);
+	}
+	if (chip->caps & ES18XX_NEW_RATE) {
+		/* Change behaviour of register A1
+		   4x oversampling
+		   2nd channel DAC asynchronous */
+		snd_es18xx_mixer_write(chip, 0x71, 0x32);
+	}
+	if (!(chip->caps & ES18XX_PCM2)) {
+		/* Enable DMA FIFO */
+		snd_es18xx_write(chip, 0xB7, 0x80);
+	}
+	if (chip->caps & ES18XX_SPATIALIZER) {
+		/* Set spatializer parameters to recommended values */
+		snd_es18xx_mixer_write(chip, 0x54, 0x8f);
+		snd_es18xx_mixer_write(chip, 0x56, 0x95);
+		snd_es18xx_mixer_write(chip, 0x58, 0x94);
+		snd_es18xx_mixer_write(chip, 0x5a, 0x80);
+	}
+	/* Flip the "enable I2S" bits for those chipsets that need it */
+	switch (chip->version) {
+	case 0x1879:
+		//Leaving I2S enabled on the 1879 screws up the PCM playback (rate effected somehow)
+		//so a Switch control has been added to toggle this 0x71 bit on/off:
+		//snd_es18xx_mixer_bits(chip, 0x71, 0x40, 0x40);
+		/* Note: we fall through on purpose here. */
+	case 0x1878:
+		snd_es18xx_config_write(chip, 0x29, snd_es18xx_config_read(chip, 0x29) | 0x40);
+		break;
+	}
+	/* Mute input source */
+	if (chip->caps & ES18XX_MUTEREC)
+		mask = 0x10;
+	if (chip->caps & ES18XX_RECMIX)
+		snd_es18xx_mixer_write(chip, 0x1c, 0x05 | mask);
+	else {
+		snd_es18xx_mixer_write(chip, 0x1c, 0x00 | mask);
+		snd_es18xx_write(chip, 0xb4, 0x00);
+	}
+#ifndef AVOID_POPS
+	/* Enable PCM output */
+	snd_es18xx_dsp_command(chip, 0xD1);
+#endif
+
+        return 0;
+}
+
+static int __devinit snd_es18xx_identify(struct snd_es18xx *chip)
+{
+	int hi,lo;
+
+	/* reset */
+	if (snd_es18xx_reset(chip) < 0) {
+		snd_printk(KERN_ERR "reset at 0x%lx failed!!!\n", chip->port);
+		return -ENODEV;
+	}
+
+	snd_es18xx_dsp_command(chip, 0xe7);
+	hi = snd_es18xx_dsp_get_byte(chip);
+	if (hi < 0) {
+		return hi;
+	}
+	lo = snd_es18xx_dsp_get_byte(chip);
+	if ((lo & 0xf0) != 0x80) {
+		return -ENODEV;
+	}
+	if (hi == 0x48) {
+		chip->version = 0x488;
+		return 0;
+	}
+	if (hi != 0x68) {
+		return -ENODEV;
+	}
+	if ((lo & 0x0f) < 8) {
+		chip->version = 0x688;
+		return 0;
+	}
+			
+        outb(0x40, chip->port + 0x04);
+	udelay(10);
+	hi = inb(chip->port + 0x05);
+	udelay(10);
+	lo = inb(chip->port + 0x05);
+	if (hi != lo) {
+		chip->version = hi << 8 | lo;
+		chip->ctrl_port = inb(chip->port + 0x05) << 8;
+		udelay(10);
+		chip->ctrl_port += inb(chip->port + 0x05);
+
+		if ((chip->res_ctrl_port = request_region(chip->ctrl_port, 8, "ES18xx - CTRL")) == NULL) {
+			snd_printk(KERN_ERR PFX "unable go grab port 0x%lx\n", chip->ctrl_port);
+			return -EBUSY;
+		}
+
+		return 0;
+	}
+
+	/* If has Hardware volume */
+	if (snd_es18xx_mixer_writable(chip, 0x64, 0x04)) {
+		/* If has Audio2 */
+		if (snd_es18xx_mixer_writable(chip, 0x70, 0x7f)) {
+			/* If has volume count */
+			if (snd_es18xx_mixer_writable(chip, 0x64, 0x20)) {
+				chip->version = 0x1887;
+			} else {
+				chip->version = 0x1888;
+			}
+		} else {
+			chip->version = 0x1788;
+		}
+	}
+	else
+		chip->version = 0x1688;
+	return 0;
+}
+
+static int __devinit snd_es18xx_probe(struct snd_es18xx *chip,
+					unsigned long mpu_port,
+					unsigned long fm_port)
+{
+	if (snd_es18xx_identify(chip) < 0) {
+		snd_printk(KERN_ERR PFX "[0x%lx] ESS chip not found\n", chip->port);
+                return -ENODEV;
+	}
+
+	switch (chip->version) {
+	case 0x1868:
+		chip->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_CONTROL;
+		break;
+	case 0x1869:
+		chip->caps = ES18XX_PCM2 | ES18XX_SPATIALIZER | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_MONO | ES18XX_MUTEREC | ES18XX_CONTROL | ES18XX_HWV;
+		break;
+	case 0x1878:
+		chip->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_I2S | ES18XX_CONTROL;
+		break;
+	case 0x1879:
+		chip->caps = ES18XX_PCM2 | ES18XX_SPATIALIZER | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_I2S | ES18XX_CONTROL | ES18XX_HWV;
+		break;
+	case 0x1887:
+	case 0x1888:
+		chip->caps = ES18XX_PCM2 | ES18XX_RECMIX | ES18XX_AUXB | ES18XX_DUPLEX_SAME;
+		break;
+	default:
+		snd_printk(KERN_ERR "[0x%lx] unsupported chip ES%x\n",
+                           chip->port, chip->version);
+                return -ENODEV;
+        }
+
+        snd_printd("[0x%lx] ESS%x chip found\n", chip->port, chip->version);
+
+	if (chip->dma1 == chip->dma2)
+		chip->caps &= ~(ES18XX_PCM2 | ES18XX_DUPLEX_SAME);
+
+	return snd_es18xx_initialize(chip, mpu_port, fm_port);
+}
+
+static struct snd_pcm_ops snd_es18xx_playback_ops = {
+	.open =		snd_es18xx_playback_open,
+	.close =	snd_es18xx_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_es18xx_playback_hw_params,
+	.hw_free =	snd_es18xx_pcm_hw_free,
+	.prepare =	snd_es18xx_playback_prepare,
+	.trigger =	snd_es18xx_playback_trigger,
+	.pointer =	snd_es18xx_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_es18xx_capture_ops = {
+	.open =		snd_es18xx_capture_open,
+	.close =	snd_es18xx_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_es18xx_capture_hw_params,
+	.hw_free =	snd_es18xx_pcm_hw_free,
+	.prepare =	snd_es18xx_capture_prepare,
+	.trigger =	snd_es18xx_capture_trigger,
+	.pointer =	snd_es18xx_capture_pointer,
+};
+
+static int __devinit snd_es18xx_pcm(struct snd_card *card, int device,
+				    struct snd_pcm **rpcm)
+{
+	struct snd_es18xx *chip = card->private_data;
+        struct snd_pcm *pcm;
+	char str[16];
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	sprintf(str, "ES%x", chip->version);
+	if (chip->caps & ES18XX_PCM2)
+		err = snd_pcm_new(card, str, device, 2, 1, &pcm);
+	else
+		err = snd_pcm_new(card, str, device, 1, 1, &pcm);
+        if (err < 0)
+                return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es18xx_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es18xx_capture_ops);
+
+	/* global setup */
+        pcm->private_data = chip;
+        pcm->info_flags = 0;
+	if (chip->caps & ES18XX_DUPLEX_SAME)
+		pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX;
+	if (! (chip->caps & ES18XX_PCM2))
+		pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+	sprintf(pcm->name, "ESS AudioDrive ES%x", chip->version);
+        chip->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024,
+					      chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);
+
+        if (rpcm)
+        	*rpcm = pcm;
+	return 0;
+}
+
+/* Power Management support functions */
+#ifdef CONFIG_PM
+static int snd_es18xx_suspend(struct snd_card *card, pm_message_t state)
+{
+	struct snd_es18xx *chip = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+	snd_pcm_suspend_all(chip->pcm);
+
+	/* power down */
+	chip->pm_reg = (unsigned char)snd_es18xx_read(chip, ES18XX_PM);
+	chip->pm_reg |= (ES18XX_PM_FM | ES18XX_PM_SUS);
+	snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg);
+	snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_SUS);
+
+	return 0;
+}
+
+static int snd_es18xx_resume(struct snd_card *card)
+{
+	struct snd_es18xx *chip = card->private_data;
+
+	/* restore PM register, we won't wake till (not 0x07) i/o activity though */
+	snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_FM);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static int snd_es18xx_free(struct snd_card *card)
+{
+	struct snd_es18xx *chip = card->private_data;
+
+	release_and_free_resource(chip->res_port);
+	release_and_free_resource(chip->res_ctrl_port);
+	release_and_free_resource(chip->res_mpu_port);
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *) card);
+	if (chip->dma1 >= 0) {
+		disable_dma(chip->dma1);
+		free_dma(chip->dma1);
+	}
+	if (chip->dma2 >= 0 && chip->dma1 != chip->dma2) {
+		disable_dma(chip->dma2);
+		free_dma(chip->dma2);
+	}
+	return 0;
+}
+
+static int snd_es18xx_dev_free(struct snd_device *device)
+{
+	return snd_es18xx_free(device->card);
+}
+
+static int __devinit snd_es18xx_new_device(struct snd_card *card,
+					   unsigned long port,
+					   unsigned long mpu_port,
+					   unsigned long fm_port,
+					   int irq, int dma1, int dma2)
+{
+	struct snd_es18xx *chip = card->private_data;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_es18xx_dev_free,
+        };
+	int err;
+
+	spin_lock_init(&chip->reg_lock);
+ 	spin_lock_init(&chip->mixer_lock);
+        chip->port = port;
+        chip->irq = -1;
+        chip->dma1 = -1;
+        chip->dma2 = -1;
+        chip->audio2_vol = 0x00;
+	chip->active = 0;
+
+	chip->res_port = request_region(port, 16, "ES18xx");
+	if (chip->res_port == NULL) {
+		snd_es18xx_free(card);
+		snd_printk(KERN_ERR PFX "unable to grap ports 0x%lx-0x%lx\n", port, port + 16 - 1);
+		return -EBUSY;
+	}
+
+	if (request_irq(irq, snd_es18xx_interrupt, IRQF_DISABLED, "ES18xx",
+			(void *) card)) {
+		snd_es18xx_free(card);
+		snd_printk(KERN_ERR PFX "unable to grap IRQ %d\n", irq);
+		return -EBUSY;
+	}
+	chip->irq = irq;
+
+	if (request_dma(dma1, "ES18xx DMA 1")) {
+		snd_es18xx_free(card);
+		snd_printk(KERN_ERR PFX "unable to grap DMA1 %d\n", dma1);
+		return -EBUSY;
+	}
+	chip->dma1 = dma1;
+
+	if (dma2 != dma1 && request_dma(dma2, "ES18xx DMA 2")) {
+		snd_es18xx_free(card);
+		snd_printk(KERN_ERR PFX "unable to grap DMA2 %d\n", dma2);
+		return -EBUSY;
+	}
+	chip->dma2 = dma2;
+
+	if (snd_es18xx_probe(chip, mpu_port, fm_port) < 0) {
+		snd_es18xx_free(card);
+		return -ENODEV;
+	}
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		snd_es18xx_free(card);
+		return err;
+	}
+        return 0;
+}
+
+static int __devinit snd_es18xx_mixer(struct snd_card *card)
+{
+	struct snd_es18xx *chip = card->private_data;
+	int err;
+	unsigned int idx;
+
+	strcpy(card->mixername, chip->pcm->name);
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_base_controls); idx++) {
+		struct snd_kcontrol *kctl;
+		kctl = snd_ctl_new1(&snd_es18xx_base_controls[idx], chip);
+		if (chip->caps & ES18XX_HWV) {
+			switch (idx) {
+			case 0:
+				chip->master_volume = kctl;
+				kctl->private_free = snd_es18xx_hwv_free;
+				break;
+			case 1:
+				chip->master_switch = kctl;
+				kctl->private_free = snd_es18xx_hwv_free;
+				break;
+			}
+		}
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			return err;
+	}
+	if (chip->caps & ES18XX_PCM2) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_pcm2_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_pcm2_controls[idx], chip))) < 0)
+				return err;
+		} 
+	} else {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_pcm1_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_pcm1_controls[idx], chip))) < 0)
+				return err;
+		}
+	}
+
+	if (chip->caps & ES18XX_RECMIX) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_recmix_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_recmix_controls[idx], chip))) < 0)
+				return err;
+		}
+	}
+	switch (chip->version) {
+	default:
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_micpre1_control, chip))) < 0)
+			return err;
+		break;
+	case 0x1869:
+	case 0x1879:
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_micpre2_control, chip))) < 0)
+			return err;
+		break;
+	}
+	if (chip->caps & ES18XX_SPATIALIZER) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_spatializer_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_spatializer_controls[idx], chip))) < 0)
+				return err;
+		}
+	}
+	if (chip->caps & ES18XX_HWV) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_hw_volume_controls); idx++) {
+			struct snd_kcontrol *kctl;
+			kctl = snd_ctl_new1(&snd_es18xx_hw_volume_controls[idx], chip);
+			if (idx == 0)
+				chip->hw_volume = kctl;
+			else
+				chip->hw_switch = kctl;
+			kctl->private_free = snd_es18xx_hwv_free;
+			if ((err = snd_ctl_add(card, kctl)) < 0)
+				return err;
+			
+		}
+	}
+	/* finish initializing other chipset specific controls
+	 */
+	if (chip->version != 0x1868) {
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_opt_speaker,
+						     chip));
+		if (err < 0)
+			return err;
+	}
+	if (chip->version == 0x1869) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_opt_1869); idx++) {
+			err = snd_ctl_add(card,
+					  snd_ctl_new1(&snd_es18xx_opt_1869[idx],
+						       chip));
+			if (err < 0)
+				return err;
+		}
+	} else if (chip->version == 0x1878) {
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_opt_1878,
+						     chip));
+		if (err < 0)
+			return err;
+	} else if (chip->version == 0x1879) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_opt_1879); idx++) {
+			err = snd_ctl_add(card,
+					  snd_ctl_new1(&snd_es18xx_opt_1879[idx],
+						       chip));
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+       
+
+/* Card level */
+
+MODULE_AUTHOR("Christian Fischbach <fishbach@pool.informatik.rwth-aachen.de>, Abramo Bagnara <abramo@alsa-project.org>");  
+MODULE_DESCRIPTION("ESS ES18xx AudioDrive");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,ES1868 PnP AudioDrive},"
+		"{ESS,ES1869 PnP AudioDrive},"
+		"{ESS,ES1878 PnP AudioDrive},"
+		"{ESS,ES1879 PnP AudioDrive},"
+		"{ESS,ES1887 PnP AudioDrive},"
+		"{ESS,ES1888 PnP AudioDrive},"
+		"{ESS,ES1887 AudioDrive},"
+		"{ESS,ES1888 AudioDrive}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260,0x280 */
+#ifndef CONFIG_PNP
+static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1};
+#else
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+#endif
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ES18xx soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ES18xx soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ES18xx soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for ES18xx driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for ES18xx driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for ES18xx driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for ES18xx driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA 1 # for ES18xx driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA 2 # for ES18xx driver.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+static int pnpc_registered;
+
+static struct pnp_device_id snd_audiodrive_pnpbiosids[] = {
+	{ .id = "ESS1869" },
+	{ .id = "ESS1879" },
+	{ .id = "" }		/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp, snd_audiodrive_pnpbiosids);
+
+/* PnP main device initialization */
+static int __devinit snd_audiodrive_pnp_init_main(int dev, struct pnp_dev *pdev)
+{
+	if (pnp_activate_dev(pdev) < 0) {
+		snd_printk(KERN_ERR PFX "PnP configure failure (out of resources?)\n");
+		return -EBUSY;
+	}
+	/* ok. hack using Vendor-Defined Card-Level registers */
+	/* skip csn and logdev initialization - already done in isapnp_configure */
+	if (pnp_device_is_isapnp(pdev)) {
+		isapnp_cfg_begin(isapnp_card_number(pdev), isapnp_csn_number(pdev));
+		isapnp_write_byte(0x27, pnp_irq(pdev, 0));	/* Hardware Volume IRQ Number */
+		if (mpu_port[dev] != SNDRV_AUTO_PORT)
+			isapnp_write_byte(0x28, pnp_irq(pdev, 0)); /* MPU-401 IRQ Number */
+		isapnp_write_byte(0x72, pnp_irq(pdev, 0));	/* second IRQ */
+		isapnp_cfg_end();
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	fm_port[dev] = pnp_port_start(pdev, 1);
+	mpu_port[dev] = pnp_port_start(pdev, 2);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+	snd_printdd("PnP ES18xx: port=0x%lx, fm port=0x%lx, mpu port=0x%lx\n", port[dev], fm_port[dev], mpu_port[dev]);
+	snd_printdd("PnP ES18xx: dma1=%i, dma2=%i, irq=%i\n", dma1[dev], dma2[dev], irq[dev]);
+	return 0;
+}
+
+static int __devinit snd_audiodrive_pnp(int dev, struct snd_es18xx *chip,
+					struct pnp_dev *pdev)
+{
+	chip->dev = pdev;
+	if (snd_audiodrive_pnp_init_main(dev, chip->dev) < 0)
+		return -EBUSY;
+	return 0;
+}
+
+static struct pnp_card_device_id snd_audiodrive_pnpids[] = {
+	/* ESS 1868 (integrated on Compaq dual P-Pro motherboard and Genius 18PnP 3D) */
+	{ .id = "ESS1868", .devs = { { "ESS1868" }, { "ESS0000" } } },
+	/* ESS 1868 (integrated on Maxisound Cards) */
+	{ .id = "ESS1868", .devs = { { "ESS8601" }, { "ESS8600" } } },
+	/* ESS 1868 (integrated on Maxisound Cards) */
+	{ .id = "ESS1868", .devs = { { "ESS8611" }, { "ESS8610" } } },
+	/* ESS ES1869 Plug and Play AudioDrive */
+	{ .id = "ESS0003", .devs = { { "ESS1869" }, { "ESS0006" } } },
+	/* ESS 1869 */
+	{ .id = "ESS1869", .devs = { { "ESS1869" }, { "ESS0006" } } },
+	/* ESS 1878 */
+	{ .id = "ESS1878", .devs = { { "ESS1878" }, { "ESS0004" } } },
+	/* ESS 1879 */
+	{ .id = "ESS1879", .devs = { { "ESS1879" }, { "ESS0009" } } },
+	/* --- */
+	{ .id = "" } /* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_audiodrive_pnpids);
+
+static int __devinit snd_audiodrive_pnpc(int dev, struct snd_es18xx *chip,
+					struct pnp_card_link *card,
+					const struct pnp_card_device_id *id)
+{
+	chip->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (chip->dev == NULL)
+		return -EBUSY;
+
+	chip->devc = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (chip->devc == NULL)
+		return -EBUSY;
+
+	/* Control port initialization */
+	if (pnp_activate_dev(chip->devc) < 0) {
+		snd_printk(KERN_ERR PFX "PnP control configure failure (out of resources?)\n");
+		return -EAGAIN;
+	}
+	snd_printdd("pnp: port=0x%llx\n",
+			(unsigned long long)pnp_port_start(chip->devc, 0));
+	if (snd_audiodrive_pnp_init_main(dev, chip->dev) < 0)
+		return -EBUSY;
+
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev)		isapnp[dev]
+#else
+#define is_isapnp_selected(dev)		0
+#endif
+
+static int snd_es18xx_card_new(int dev, struct snd_card **cardp)
+{
+	return snd_card_create(index[dev], id[dev], THIS_MODULE,
+			       sizeof(struct snd_es18xx), cardp);
+}
+
+static int __devinit snd_audiodrive_probe(struct snd_card *card, int dev)
+{
+	struct snd_es18xx *chip = card->private_data;
+	struct snd_opl3 *opl3;
+	int err;
+
+	err = snd_es18xx_new_device(card,
+				    port[dev], mpu_port[dev], fm_port[dev],
+				    irq[dev], dma1[dev], dma2[dev]);
+	if (err < 0)
+		return err;
+
+	sprintf(card->driver, "ES%x", chip->version);
+	
+	sprintf(card->shortname, "ESS AudioDrive ES%x", chip->version);
+	if (dma1[dev] != dma2[dev])
+		sprintf(card->longname, "%s at 0x%lx, irq %d, dma1 %d, dma2 %d",
+			card->shortname,
+			chip->port,
+			irq[dev], dma1[dev], dma2[dev]);
+	else
+		sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+			card->shortname,
+			chip->port,
+			irq[dev], dma1[dev]);
+
+	err = snd_es18xx_pcm(card, 0, NULL);
+	if (err < 0)
+		return err;
+
+	err = snd_es18xx_mixer(card);
+	if (err < 0)
+		return err;
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_OPL3, 0, &opl3) < 0) {
+			snd_printk(KERN_WARNING PFX
+				   "opl3 not detected at 0x%lx\n",
+				   fm_port[dev]);
+		} else {
+			err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		err = snd_mpu401_uart_new(card, 0, MPU401_HW_ES18XX,
+					  mpu_port[dev], 0,
+					  irq[dev], 0, &chip->rmidi);
+		if (err < 0)
+			return err;
+	}
+
+	return snd_card_register(card);
+}
+
+static int __devinit snd_es18xx_isa_match(struct device *pdev, unsigned int dev)
+{
+	return enable[dev] && !is_isapnp_selected(dev);
+}
+
+static int __devinit snd_es18xx_isa_probe1(int dev, struct device *devptr)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_es18xx_card_new(dev, &card);
+	if (err < 0)
+		return err;
+	snd_card_set_dev(card, devptr);
+	if ((err = snd_audiodrive_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(devptr, card);
+	return 0;
+}
+
+static int __devinit snd_es18xx_isa_probe(struct device *pdev, unsigned int dev)
+{
+	int err;
+	static int possible_irqs[] = {5, 9, 10, 7, 11, 12, -1};
+	static int possible_dmas[] = {1, 0, 3, 5, -1};
+
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		if ((irq[dev] = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1[dev] == SNDRV_AUTO_DMA) {
+		if ((dma1[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	if (dma2[dev] == SNDRV_AUTO_DMA) {
+		if ((dma2[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[dev] != SNDRV_AUTO_PORT) {
+		return snd_es18xx_isa_probe1(dev, pdev);
+	} else {
+		static unsigned long possible_ports[] = {0x220, 0x240, 0x260, 0x280};
+		int i;
+		for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+			port[dev] = possible_ports[i];
+			err = snd_es18xx_isa_probe1(dev, pdev);
+			if (! err)
+				return 0;
+		}
+		return err;
+	}
+}
+
+static int __devexit snd_es18xx_isa_remove(struct device *devptr,
+					   unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	dev_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_es18xx_isa_suspend(struct device *dev, unsigned int n,
+				  pm_message_t state)
+{
+	return snd_es18xx_suspend(dev_get_drvdata(dev), state);
+}
+
+static int snd_es18xx_isa_resume(struct device *dev, unsigned int n)
+{
+	return snd_es18xx_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#define DEV_NAME "es18xx"
+
+static struct isa_driver snd_es18xx_isa_driver = {
+	.match		= snd_es18xx_isa_match,
+	.probe		= snd_es18xx_isa_probe,
+	.remove		= __devexit_p(snd_es18xx_isa_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_es18xx_isa_suspend,
+	.resume		= snd_es18xx_isa_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+
+#ifdef CONFIG_PNP
+static int __devinit snd_audiodrive_pnp_detect(struct pnp_dev *pdev,
+					    const struct pnp_device_id *id)
+{
+	static int dev;
+	int err;
+	struct snd_card *card;
+
+	if (pnp_device_is_isapnp(pdev))
+		return -ENOENT;	/* we have another procedure - card */
+	for (; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	err = snd_es18xx_card_new(dev, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_audiodrive_pnp(dev, card->private_data, pdev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_card_set_dev(card, &pdev->dev);
+	if ((err = snd_audiodrive_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pnp_set_drvdata(pdev, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_audiodrive_pnp_remove(struct pnp_dev * pdev)
+{
+	snd_card_free(pnp_get_drvdata(pdev));
+	pnp_set_drvdata(pdev, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_audiodrive_pnp_suspend(struct pnp_dev *pdev, pm_message_t state)
+{
+	return snd_es18xx_suspend(pnp_get_drvdata(pdev), state);
+}
+static int snd_audiodrive_pnp_resume(struct pnp_dev *pdev)
+{
+	return snd_es18xx_resume(pnp_get_drvdata(pdev));
+}
+#endif
+
+static struct pnp_driver es18xx_pnp_driver = {
+	.name = "es18xx-pnpbios",
+	.id_table = snd_audiodrive_pnpbiosids,
+	.probe = snd_audiodrive_pnp_detect,
+	.remove = __devexit_p(snd_audiodrive_pnp_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_audiodrive_pnp_suspend,
+	.resume = snd_audiodrive_pnp_resume,
+#endif
+};
+
+static int __devinit snd_audiodrive_pnpc_detect(struct pnp_card_link *pcard,
+					       const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	res = snd_es18xx_card_new(dev, &card);
+	if (res < 0)
+		return res;
+
+	if ((res = snd_audiodrive_pnpc(dev, card->private_data, pcard, pid)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+	if ((res = snd_audiodrive_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_audiodrive_pnpc_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_audiodrive_pnpc_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	return snd_es18xx_suspend(pnp_get_card_drvdata(pcard), state);
+}
+
+static int snd_audiodrive_pnpc_resume(struct pnp_card_link *pcard)
+{
+	return snd_es18xx_resume(pnp_get_card_drvdata(pcard));
+}
+
+#endif
+
+static struct pnp_card_driver es18xx_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "es18xx",
+	.id_table = snd_audiodrive_pnpids,
+	.probe = snd_audiodrive_pnpc_detect,
+	.remove = __devexit_p(snd_audiodrive_pnpc_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_audiodrive_pnpc_suspend,
+	.resume		= snd_audiodrive_pnpc_resume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_es18xx_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_es18xx_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_driver(&es18xx_pnp_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	err = pnp_register_card_driver(&es18xx_pnpc_driver);
+	if (!err)
+		pnpc_registered = 1;
+
+	if (isa_registered || pnp_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_es18xx_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnpc_registered)
+		pnp_unregister_card_driver(&es18xx_pnpc_driver);
+	if (pnp_registered)
+		pnp_unregister_driver(&es18xx_pnp_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_es18xx_isa_driver);
+}
+
+module_init(alsa_card_es18xx_init)
+module_exit(alsa_card_es18xx_exit)
diff --git a/linux/sound/isa/galaxy/Makefile b/linux/sound/isa/galaxy/Makefile
new file mode 100644
index 0000000..e307066
--- /dev/null
+++ b/linux/sound/isa/galaxy/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
+#
+
+snd-azt1605-objs := azt1605.o
+snd-azt2316-objs := azt2316.o
+
+obj-$(CONFIG_SND_AZT1605) += snd-azt1605.o
+obj-$(CONFIG_SND_AZT2316) += snd-azt2316.o
diff --git a/linux/sound/isa/galaxy/azt1605.c b/linux/sound/isa/galaxy/azt1605.c
new file mode 100644
index 0000000..9a97643
--- /dev/null
+++ b/linux/sound/isa/galaxy/azt1605.c
@@ -0,0 +1,91 @@
+/*
+ * Aztech AZT1605 Driver
+ * Copyright (C) 2007,2010  Rene Herman
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#define AZT1605
+
+#define CRD_NAME "Aztech AZT1605"
+#define DRV_NAME "AZT1605"
+#define DEV_NAME "azt1605"
+
+#define GALAXY_DSP_MAJOR		2
+#define GALAXY_DSP_MINOR		1
+
+#define GALAXY_CONFIG_SIZE		3
+
+/*
+ * 24-bit config register
+ */
+
+#define GALAXY_CONFIG_SBA_220		(0 << 0)
+#define GALAXY_CONFIG_SBA_240		(1 << 0)
+#define GALAXY_CONFIG_SBA_260		(2 << 0)
+#define GALAXY_CONFIG_SBA_280		(3 << 0)
+#define GALAXY_CONFIG_SBA_MASK		GALAXY_CONFIG_SBA_280
+
+#define GALAXY_CONFIG_MPUA_300		(0 << 2)
+#define GALAXY_CONFIG_MPUA_330		(1 << 2)
+
+#define GALAXY_CONFIG_MPU_ENABLE	(1 << 3)
+
+#define GALAXY_CONFIG_GAME_ENABLE	(1 << 4)
+
+#define GALAXY_CONFIG_CD_PANASONIC	(1 << 5)
+#define GALAXY_CONFIG_CD_MITSUMI	(1 << 6)
+#define GALAXY_CONFIG_CD_MASK		(\
+	GALAXY_CONFIG_CD_PANASONIC | GALAXY_CONFIG_CD_MITSUMI)
+
+#define GALAXY_CONFIG_UNUSED		(1 << 7)
+#define GALAXY_CONFIG_UNUSED_MASK	GALAXY_CONFIG_UNUSED
+
+#define GALAXY_CONFIG_SBIRQ_2		(1 << 8)
+#define GALAXY_CONFIG_SBIRQ_3		(1 << 9)
+#define GALAXY_CONFIG_SBIRQ_5		(1 << 10)
+#define GALAXY_CONFIG_SBIRQ_7		(1 << 11)
+
+#define GALAXY_CONFIG_MPUIRQ_2		(1 << 12)
+#define GALAXY_CONFIG_MPUIRQ_3		(1 << 13)
+#define GALAXY_CONFIG_MPUIRQ_5		(1 << 14)
+#define GALAXY_CONFIG_MPUIRQ_7		(1 << 15)
+
+#define GALAXY_CONFIG_WSSA_530		(0 << 16)
+#define GALAXY_CONFIG_WSSA_604		(1 << 16)
+#define GALAXY_CONFIG_WSSA_E80		(2 << 16)
+#define GALAXY_CONFIG_WSSA_F40		(3 << 16)
+
+#define GALAXY_CONFIG_WSS_ENABLE	(1 << 18)
+
+#define GALAXY_CONFIG_CDIRQ_11		(1 << 19)
+#define GALAXY_CONFIG_CDIRQ_12		(1 << 20)
+#define GALAXY_CONFIG_CDIRQ_15		(1 << 21)
+#define GALAXY_CONFIG_CDIRQ_MASK	(\
+	GALAXY_CONFIG_CDIRQ_11 | GALAXY_CONFIG_CDIRQ_12 |\
+	GALAXY_CONFIG_CDIRQ_15)
+
+#define GALAXY_CONFIG_CDDMA_DISABLE	(0 << 22)
+#define GALAXY_CONFIG_CDDMA_0		(1 << 22)
+#define GALAXY_CONFIG_CDDMA_1		(2 << 22)
+#define GALAXY_CONFIG_CDDMA_3		(3 << 22)
+#define GALAXY_CONFIG_CDDMA_MASK	GALAXY_CONFIG_CDDMA_3
+
+#define GALAXY_CONFIG_MASK		(\
+	GALAXY_CONFIG_SBA_MASK | GALAXY_CONFIG_CD_MASK |\
+	GALAXY_CONFIG_UNUSED_MASK | GALAXY_CONFIG_CDIRQ_MASK |\
+	GALAXY_CONFIG_CDDMA_MASK)
+
+#include "galaxy.c"
diff --git a/linux/sound/isa/galaxy/azt2316.c b/linux/sound/isa/galaxy/azt2316.c
new file mode 100644
index 0000000..1894411
--- /dev/null
+++ b/linux/sound/isa/galaxy/azt2316.c
@@ -0,0 +1,111 @@
+/*
+ * Aztech AZT2316 Driver
+ * Copyright (C) 2007,2010  Rene Herman
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#define AZT2316
+
+#define CRD_NAME "Aztech AZT2316"
+#define DRV_NAME "AZT2316"
+#define DEV_NAME "azt2316"
+
+#define GALAXY_DSP_MAJOR		3
+#define GALAXY_DSP_MINOR		1
+
+#define GALAXY_CONFIG_SIZE		4
+
+/*
+ * 32-bit config register
+ */
+
+#define GALAXY_CONFIG_SBA_220		(0 << 0)
+#define GALAXY_CONFIG_SBA_240		(1 << 0)
+#define GALAXY_CONFIG_SBA_260		(2 << 0)
+#define GALAXY_CONFIG_SBA_280		(3 << 0)
+#define GALAXY_CONFIG_SBA_MASK		GALAXY_CONFIG_SBA_280
+
+#define GALAXY_CONFIG_SBIRQ_2		(1 << 2)
+#define GALAXY_CONFIG_SBIRQ_5		(1 << 3)
+#define GALAXY_CONFIG_SBIRQ_7		(1 << 4)
+#define GALAXY_CONFIG_SBIRQ_10		(1 << 5)
+
+#define GALAXY_CONFIG_SBDMA_DISABLE	(0 << 6)
+#define GALAXY_CONFIG_SBDMA_0		(1 << 6)
+#define GALAXY_CONFIG_SBDMA_1		(2 << 6)
+#define GALAXY_CONFIG_SBDMA_3		(3 << 6)
+
+#define GALAXY_CONFIG_WSSA_530		(0 << 8)
+#define GALAXY_CONFIG_WSSA_604		(1 << 8)
+#define GALAXY_CONFIG_WSSA_E80		(2 << 8)
+#define GALAXY_CONFIG_WSSA_F40		(3 << 8)
+
+#define GALAXY_CONFIG_WSS_ENABLE	(1 << 10)
+
+#define GALAXY_CONFIG_GAME_ENABLE	(1 << 11)
+
+#define GALAXY_CONFIG_MPUA_300		(0 << 12)
+#define GALAXY_CONFIG_MPUA_330		(1 << 12)
+
+#define GALAXY_CONFIG_MPU_ENABLE	(1 << 13)
+
+#define GALAXY_CONFIG_CDA_310		(0 << 14)
+#define GALAXY_CONFIG_CDA_320		(1 << 14)
+#define GALAXY_CONFIG_CDA_340		(2 << 14)
+#define GALAXY_CONFIG_CDA_350		(3 << 14)
+#define GALAXY_CONFIG_CDA_MASK		GALAXY_CONFIG_CDA_350
+
+#define GALAXY_CONFIG_CD_DISABLE	(0 << 16)
+#define GALAXY_CONFIG_CD_PANASONIC	(1 << 16)
+#define GALAXY_CONFIG_CD_SONY		(2 << 16)
+#define GALAXY_CONFIG_CD_MITSUMI	(3 << 16)
+#define GALAXY_CONFIG_CD_AZTECH		(4 << 16)
+#define GALAXY_CONFIG_CD_UNUSED_5	(5 << 16)
+#define GALAXY_CONFIG_CD_UNUSED_6	(6 << 16)
+#define GALAXY_CONFIG_CD_UNUSED_7	(7 << 16)
+#define GALAXY_CONFIG_CD_MASK		GALAXY_CONFIG_CD_UNUSED_7
+
+#define GALAXY_CONFIG_CDDMA8_DISABLE	(0 << 20)
+#define GALAXY_CONFIG_CDDMA8_0		(1 << 20)
+#define GALAXY_CONFIG_CDDMA8_1		(2 << 20)
+#define GALAXY_CONFIG_CDDMA8_3		(3 << 20)
+#define GALAXY_CONFIG_CDDMA8_MASK	GALAXY_CONFIG_CDDMA8_3
+
+#define GALAXY_CONFIG_CDDMA16_DISABLE	(0 << 22)
+#define GALAXY_CONFIG_CDDMA16_5		(1 << 22)
+#define GALAXY_CONFIG_CDDMA16_6		(2 << 22)
+#define GALAXY_CONFIG_CDDMA16_7		(3 << 22)
+#define GALAXY_CONFIG_CDDMA16_MASK	GALAXY_CONFIG_CDDMA16_7
+
+#define GALAXY_CONFIG_MPUIRQ_2		(1 << 24)
+#define GALAXY_CONFIG_MPUIRQ_5		(1 << 25)
+#define GALAXY_CONFIG_MPUIRQ_7		(1 << 26)
+#define GALAXY_CONFIG_MPUIRQ_10		(1 << 27)
+
+#define GALAXY_CONFIG_CDIRQ_5		(1 << 28)
+#define GALAXY_CONFIG_CDIRQ_11		(1 << 29)
+#define GALAXY_CONFIG_CDIRQ_12		(1 << 30)
+#define GALAXY_CONFIG_CDIRQ_15		(1 << 31)
+#define GALAXY_CONFIG_CDIRQ_MASK	(\
+	GALAXY_CONFIG_CDIRQ_5 | GALAXY_CONFIG_CDIRQ_11 |\
+	GALAXY_CONFIG_CDIRQ_12 | GALAXY_CONFIG_CDIRQ_15)
+
+#define GALAXY_CONFIG_MASK		(\
+	GALAXY_CONFIG_SBA_MASK | GALAXY_CONFIG_CDA_MASK |\
+	GALAXY_CONFIG_CD_MASK | GALAXY_CONFIG_CDDMA16_MASK |\
+	GALAXY_CONFIG_CDDMA8_MASK | GALAXY_CONFIG_CDIRQ_MASK)
+
+#include "galaxy.c"
diff --git a/linux/sound/isa/galaxy/galaxy.c b/linux/sound/isa/galaxy/galaxy.c
new file mode 100644
index 0000000..ee54df0
--- /dev/null
+++ b/linux/sound/isa/galaxy/galaxy.c
@@ -0,0 +1,652 @@
+/*
+ * Aztech AZT1605/AZT2316 Driver
+ * Copyright (C) 2007,2010  Rene Herman
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <asm/processor.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Rene Herman");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_array(wss_port, long, NULL, 0444);
+MODULE_PARM_DESC(wss_port, "WSS port # for " CRD_NAME " driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for " CRD_NAME " driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "Playback DMA # for " CRD_NAME " driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "Capture DMA # for " CRD_NAME " driver.");
+
+/*
+ * Generic SB DSP support routines
+ */
+
+#define DSP_PORT_RESET		0x6
+#define DSP_PORT_READ		0xa
+#define DSP_PORT_COMMAND	0xc
+#define DSP_PORT_STATUS		0xc
+#define DSP_PORT_DATA_AVAIL	0xe
+
+#define DSP_SIGNATURE		0xaa
+
+#define DSP_COMMAND_GET_VERSION	0xe1
+
+static int __devinit dsp_get_byte(void __iomem *port, u8 *val)
+{
+	int loops = 1000;
+
+	while (!(ioread8(port + DSP_PORT_DATA_AVAIL) & 0x80)) {
+		if (!loops--)
+			return -EIO;
+		cpu_relax();
+	}
+	*val = ioread8(port + DSP_PORT_READ);
+	return 0;
+}
+
+static int __devinit dsp_reset(void __iomem *port)
+{
+	u8 val;
+
+	iowrite8(1, port + DSP_PORT_RESET);
+	udelay(10);
+	iowrite8(0, port + DSP_PORT_RESET);
+
+	if (dsp_get_byte(port, &val) < 0 || val != DSP_SIGNATURE)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int __devinit dsp_command(void __iomem *port, u8 cmd)
+{
+	int loops = 1000;
+
+	while (ioread8(port + DSP_PORT_STATUS) & 0x80) {
+		if (!loops--)
+			return -EIO;
+		cpu_relax();
+	}
+	iowrite8(cmd, port + DSP_PORT_COMMAND);
+	return 0;
+}
+
+static int __devinit dsp_get_version(void __iomem *port, u8 *major, u8 *minor)
+{
+	int err;
+
+	err = dsp_command(port, DSP_COMMAND_GET_VERSION);
+	if (err < 0)
+		return err;
+
+	err = dsp_get_byte(port, major);
+	if (err < 0)
+		return err;
+
+	err = dsp_get_byte(port, minor);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/*
+ * Generic WSS support routines
+ */
+
+#define WSS_CONFIG_DMA_0	(1 << 0)
+#define WSS_CONFIG_DMA_1	(2 << 0)
+#define WSS_CONFIG_DMA_3	(3 << 0)
+#define WSS_CONFIG_DUPLEX	(1 << 2)
+#define WSS_CONFIG_IRQ_7	(1 << 3)
+#define WSS_CONFIG_IRQ_9	(2 << 3)
+#define WSS_CONFIG_IRQ_10	(3 << 3)
+#define WSS_CONFIG_IRQ_11	(4 << 3)
+
+#define WSS_PORT_CONFIG		0
+#define WSS_PORT_SIGNATURE	3
+
+#define WSS_SIGNATURE		4
+
+static int __devinit wss_detect(void __iomem *wss_port)
+{
+	if ((ioread8(wss_port + WSS_PORT_SIGNATURE) & 0x3f) != WSS_SIGNATURE)
+		return -ENODEV;
+
+	return 0;
+}
+
+static void wss_set_config(void __iomem *wss_port, u8 wss_config)
+{
+	iowrite8(wss_config, wss_port + WSS_PORT_CONFIG);
+}
+
+/*
+ * Aztech Sound Galaxy specifics
+ */
+
+#define GALAXY_PORT_CONFIG	1024
+#define CONFIG_PORT_SET		4
+
+#define DSP_COMMAND_GALAXY_8	8
+#define GALAXY_COMMAND_GET_TYPE	5
+
+#define DSP_COMMAND_GALAXY_9	9
+#define GALAXY_COMMAND_WSSMODE	0
+#define GALAXY_COMMAND_SB8MODE	1
+
+#define GALAXY_MODE_WSS		GALAXY_COMMAND_WSSMODE
+#define GALAXY_MODE_SB8		GALAXY_COMMAND_SB8MODE
+
+struct snd_galaxy {
+	void __iomem *port;
+	void __iomem *config_port;
+	void __iomem *wss_port;
+	u32 config;
+	struct resource *res_port;
+	struct resource *res_config_port;
+	struct resource *res_wss_port;
+};
+
+static u32 config[SNDRV_CARDS];
+static u8 wss_config[SNDRV_CARDS];
+
+static int __devinit snd_galaxy_match(struct device *dev, unsigned int n)
+{
+	if (!enable[n])
+		return 0;
+
+	switch (port[n]) {
+	case SNDRV_AUTO_PORT:
+		dev_err(dev, "please specify port\n");
+		return 0;
+	case 0x220:
+		config[n] |= GALAXY_CONFIG_SBA_220;
+		break;
+	case 0x240:
+		config[n] |= GALAXY_CONFIG_SBA_240;
+		break;
+	case 0x260:
+		config[n] |= GALAXY_CONFIG_SBA_260;
+		break;
+	case 0x280:
+		config[n] |= GALAXY_CONFIG_SBA_280;
+		break;
+	default:
+		dev_err(dev, "invalid port %#lx\n", port[n]);
+		return 0;
+	}
+
+	switch (wss_port[n]) {
+	case SNDRV_AUTO_PORT:
+		dev_err(dev,  "please specify wss_port\n");
+		return 0;
+	case 0x530:
+		config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_530;
+		break;
+	case 0x604:
+		config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_604;
+		break;
+	case 0xe80:
+		config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_E80;
+		break;
+	case 0xf40:
+		config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_F40;
+		break;
+	default:
+		dev_err(dev, "invalid WSS port %#lx\n", wss_port[n]);
+		return 0;
+	}
+
+	switch (irq[n]) {
+	case SNDRV_AUTO_IRQ:
+		dev_err(dev,  "please specify irq\n");
+		return 0;
+	case 7:
+		wss_config[n] |= WSS_CONFIG_IRQ_7;
+		break;
+	case 2:
+		irq[n] = 9;
+	case 9:
+		wss_config[n] |= WSS_CONFIG_IRQ_9;
+		break;
+	case 10:
+		wss_config[n] |= WSS_CONFIG_IRQ_10;
+		break;
+	case 11:
+		wss_config[n] |= WSS_CONFIG_IRQ_11;
+		break;
+	default:
+		dev_err(dev, "invalid IRQ %d\n", irq[n]);
+		return 0;
+	}
+
+	switch (dma1[n]) {
+	case SNDRV_AUTO_DMA:
+		dev_err(dev,  "please specify dma1\n");
+		return 0;
+	case 0:
+		wss_config[n] |= WSS_CONFIG_DMA_0;
+		break;
+	case 1:
+		wss_config[n] |= WSS_CONFIG_DMA_1;
+		break;
+	case 3:
+		wss_config[n] |= WSS_CONFIG_DMA_3;
+		break;
+	default:
+		dev_err(dev, "invalid playback DMA %d\n", dma1[n]);
+		return 0;
+	}
+
+	if (dma2[n] == SNDRV_AUTO_DMA || dma2[n] == dma1[n]) {
+		dma2[n] = -1;
+		goto mpu;
+	}
+
+	wss_config[n] |= WSS_CONFIG_DUPLEX;
+	switch (dma2[n]) {
+	case 0:
+		break;
+	case 1:
+		if (dma1[n] == 0)
+			break;
+	default:
+		dev_err(dev, "invalid capture DMA %d\n", dma2[n]);
+		return 0;
+	}
+
+mpu:
+	switch (mpu_port[n]) {
+	case SNDRV_AUTO_PORT:
+		dev_warn(dev, "mpu_port not specified; not using MPU-401\n");
+		mpu_port[n] = -1;
+		goto fm;
+	case 0x300:
+		config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_300;
+		break;
+	case 0x330:
+		config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_330;
+		break;
+	default:
+		dev_err(dev, "invalid MPU port %#lx\n", mpu_port[n]);
+		return 0;
+	}
+
+	switch (mpu_irq[n]) {
+	case SNDRV_AUTO_IRQ:
+		dev_warn(dev, "mpu_irq not specified: using polling mode\n");
+		mpu_irq[n] = -1;
+		break;
+	case 2:
+		mpu_irq[n] = 9;
+	case 9:
+		config[n] |= GALAXY_CONFIG_MPUIRQ_2;
+		break;
+#ifdef AZT1605
+	case 3:
+		config[n] |= GALAXY_CONFIG_MPUIRQ_3;
+		break;
+#endif
+	case 5:
+		config[n] |= GALAXY_CONFIG_MPUIRQ_5;
+		break;
+	case 7:
+		config[n] |= GALAXY_CONFIG_MPUIRQ_7;
+		break;
+#ifdef AZT2316
+	case 10:
+		config[n] |= GALAXY_CONFIG_MPUIRQ_10;
+		break;
+#endif
+	default:
+		dev_err(dev, "invalid MPU IRQ %d\n", mpu_irq[n]);
+		return 0;
+	}
+
+	if (mpu_irq[n] == irq[n]) {
+		dev_err(dev, "cannot share IRQ between WSS and MPU-401\n");
+		return 0;
+	}
+
+fm:
+	switch (fm_port[n]) {
+	case SNDRV_AUTO_PORT:
+		dev_warn(dev, "fm_port not specified: not using OPL3\n");
+		fm_port[n] = -1;
+		break;
+	case 0x388:
+		break;
+	default:
+		dev_err(dev, "illegal FM port %#lx\n", fm_port[n]);
+		return 0;
+	}
+
+	config[n] |= GALAXY_CONFIG_GAME_ENABLE;
+	return 1;
+}
+
+static int __devinit galaxy_init(struct snd_galaxy *galaxy, u8 *type)
+{
+	u8 major;
+	u8 minor;
+	int err;
+
+	err = dsp_reset(galaxy->port);
+	if (err < 0)
+		return err;
+
+	err = dsp_get_version(galaxy->port, &major, &minor);
+	if (err < 0)
+		return err;
+
+	if (major != GALAXY_DSP_MAJOR || minor != GALAXY_DSP_MINOR)
+		return -ENODEV;
+
+	err = dsp_command(galaxy->port, DSP_COMMAND_GALAXY_8);
+	if (err < 0)
+		return err;
+
+	err = dsp_command(galaxy->port, GALAXY_COMMAND_GET_TYPE);
+	if (err < 0)
+		return err;
+
+	err = dsp_get_byte(galaxy->port, type);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int __devinit galaxy_set_mode(struct snd_galaxy *galaxy, u8 mode)
+{
+	int err;
+
+	err = dsp_command(galaxy->port, DSP_COMMAND_GALAXY_9);
+	if (err < 0)
+		return err;
+
+	err = dsp_command(galaxy->port, mode);
+	if (err < 0)
+		return err;
+
+#ifdef AZT1605
+	/*
+	 * Needed for MPU IRQ on AZT1605, but AZT2316 loses WSS again
+	 */
+	err = dsp_reset(galaxy->port);
+	if (err < 0)
+		return err;
+#endif
+
+	return 0;
+}
+
+static void galaxy_set_config(struct snd_galaxy *galaxy, u32 config)
+{
+	u8 tmp = ioread8(galaxy->config_port + CONFIG_PORT_SET);
+	int i;
+
+	iowrite8(tmp | 0x80, galaxy->config_port + CONFIG_PORT_SET);
+	for (i = 0; i < GALAXY_CONFIG_SIZE; i++) {
+		iowrite8(config, galaxy->config_port + i);
+		config >>= 8;
+	}
+	iowrite8(tmp & 0x7f, galaxy->config_port + CONFIG_PORT_SET);
+	msleep(10);
+}
+
+static void __devinit galaxy_config(struct snd_galaxy *galaxy, u32 config)
+{
+	int i;
+
+	for (i = GALAXY_CONFIG_SIZE; i; i--) {
+		u8 tmp = ioread8(galaxy->config_port + i - 1);
+		galaxy->config = (galaxy->config << 8) | tmp;
+	}
+	config |= galaxy->config & GALAXY_CONFIG_MASK;
+	galaxy_set_config(galaxy, config);
+}
+
+static int __devinit galaxy_wss_config(struct snd_galaxy *galaxy, u8 wss_config)
+{
+	int err;
+
+	err = wss_detect(galaxy->wss_port);
+	if (err < 0)
+		return err;
+
+	wss_set_config(galaxy->wss_port, wss_config);
+
+	err = galaxy_set_mode(galaxy, GALAXY_MODE_WSS);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static void snd_galaxy_free(struct snd_card *card)
+{
+	struct snd_galaxy *galaxy = card->private_data;
+
+	if (galaxy->wss_port) {
+		wss_set_config(galaxy->wss_port, 0);
+		ioport_unmap(galaxy->wss_port);
+		release_and_free_resource(galaxy->res_wss_port);
+	}
+	if (galaxy->config_port) {
+		galaxy_set_config(galaxy, galaxy->config);
+		ioport_unmap(galaxy->config_port);
+		release_and_free_resource(galaxy->res_config_port);
+	}
+	if (galaxy->port) {
+		ioport_unmap(galaxy->port);
+		release_and_free_resource(galaxy->res_port);
+	}
+}
+
+static int __devinit snd_galaxy_probe(struct device *dev, unsigned int n)
+{
+	struct snd_galaxy *galaxy;
+	struct snd_wss *chip;
+	struct snd_card *card;
+	u8 type;
+	int err;
+
+	err = snd_card_create(index[n], id[n], THIS_MODULE, sizeof *galaxy,
+			      &card);
+	if (err < 0)
+		return err;
+
+	snd_card_set_dev(card, dev);
+
+	card->private_free = snd_galaxy_free;
+	galaxy = card->private_data;
+
+	galaxy->res_port = request_region(port[n], 16, DRV_NAME);
+	if (!galaxy->res_port) {
+		dev_err(dev, "could not grab ports %#lx-%#lx\n", port[n],
+			port[n] + 15);
+		err = -EBUSY;
+		goto error;
+	}
+	galaxy->port = ioport_map(port[n], 16);
+
+	err = galaxy_init(galaxy, &type);
+	if (err < 0) {
+		dev_err(dev, "did not find a Sound Galaxy at %#lx\n", port[n]);
+		goto error;
+	}
+	dev_info(dev, "Sound Galaxy (type %d) found at %#lx\n", type, port[n]);
+
+	galaxy->res_config_port = request_region(port[n] + GALAXY_PORT_CONFIG,
+						 16, DRV_NAME);
+	if (!galaxy->res_config_port) {
+		dev_err(dev, "could not grab ports %#lx-%#lx\n",
+			port[n] + GALAXY_PORT_CONFIG,
+			port[n] + GALAXY_PORT_CONFIG + 15);
+		err = -EBUSY;
+		goto error;
+	}
+	galaxy->config_port = ioport_map(port[n] + GALAXY_PORT_CONFIG, 16);
+
+	galaxy_config(galaxy, config[n]);
+
+	galaxy->res_wss_port = request_region(wss_port[n], 4, DRV_NAME);
+	if (!galaxy->res_wss_port)  {
+		dev_err(dev, "could not grab ports %#lx-%#lx\n", wss_port[n],
+			wss_port[n] + 3);
+		err = -EBUSY;
+		goto error;
+	}
+	galaxy->wss_port = ioport_map(wss_port[n], 4);
+
+	err = galaxy_wss_config(galaxy, wss_config[n]);
+	if (err < 0) {
+		dev_err(dev, "could not configure WSS\n");
+		goto error;
+	}
+
+	strcpy(card->driver, DRV_NAME);
+	strcpy(card->shortname, DRV_NAME);
+	sprintf(card->longname, "%s at %#lx/%#lx, irq %d, dma %d/%d",
+		card->shortname, port[n], wss_port[n], irq[n], dma1[n],
+		dma2[n]);
+
+	err = snd_wss_create(card, wss_port[n] + 4, -1, irq[n], dma1[n],
+			     dma2[n], WSS_HW_DETECT, 0, &chip);
+	if (err < 0)
+		goto error;
+
+	err = snd_wss_pcm(chip, 0, NULL);
+	if (err < 0)
+		goto error;
+
+	err = snd_wss_mixer(chip);
+	if (err < 0)
+		goto error;
+
+	err = snd_wss_timer(chip, 0, NULL);
+	if (err < 0)
+		goto error;
+
+	if (mpu_port[n] >= 0) {
+		err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+					  mpu_port[n], 0, mpu_irq[n],
+					  IRQF_DISABLED, NULL);
+		if (err < 0)
+			goto error;
+	}
+
+	if (fm_port[n] >= 0) {
+		struct snd_opl3 *opl3;
+
+		err = snd_opl3_create(card, fm_port[n], fm_port[n] + 2,
+				      OPL3_HW_AUTO, 0, &opl3);
+		if (err < 0) {
+			dev_err(dev, "no OPL device at %#lx\n", fm_port[n]);
+			goto error;
+		}
+		err = snd_opl3_timer_new(opl3, 1, 2);
+		if (err < 0)
+			goto error;
+
+		err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+		if (err < 0)
+			goto error;
+	}
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto error;
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+error:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit snd_galaxy_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	dev_set_drvdata(dev, NULL);
+	return 0;
+}
+
+static struct isa_driver snd_galaxy_driver = {
+	.match		= snd_galaxy_match,
+	.probe		= snd_galaxy_probe,
+	.remove		= __devexit_p(snd_galaxy_remove),
+
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+static int __init alsa_card_galaxy_init(void)
+{
+	return isa_register_driver(&snd_galaxy_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_galaxy_exit(void)
+{
+	isa_unregister_driver(&snd_galaxy_driver);
+}
+
+module_init(alsa_card_galaxy_init);
+module_exit(alsa_card_galaxy_exit);
diff --git a/linux/sound/isa/gus/Makefile b/linux/sound/isa/gus/Makefile
new file mode 100644
index 0000000..6cd4ee0
--- /dev/null
+++ b/linux/sound/isa/gus/Makefile
@@ -0,0 +1,24 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-gus-lib-objs := gus_main.o \
+		    gus_io.o gus_irq.o gus_timer.o \
+		    gus_mem.o gus_mem_proc.o gus_dram.o gus_dma.o gus_volume.o \
+		    gus_pcm.o gus_mixer.o \
+		    gus_uart.o \
+		    gus_reset.o
+
+snd-gusclassic-objs := gusclassic.o
+snd-gusextreme-objs := gusextreme.o
+snd-gusmax-objs := gusmax.o
+snd-interwave-objs := interwave.o
+snd-interwave-stb-objs := interwave-stb.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_GUSCLASSIC) += snd-gusclassic.o snd-gus-lib.o
+obj-$(CONFIG_SND_GUSMAX) += snd-gusmax.o snd-gus-lib.o
+obj-$(CONFIG_SND_GUSEXTREME) += snd-gusextreme.o snd-gus-lib.o
+obj-$(CONFIG_SND_INTERWAVE) += snd-interwave.o snd-gus-lib.o
+obj-$(CONFIG_SND_INTERWAVE_STB) += snd-interwave-stb.o snd-gus-lib.o
diff --git a/linux/sound/isa/gus/gus_dma.c b/linux/sound/isa/gus/gus_dma.c
new file mode 100644
index 0000000..36c27c8
--- /dev/null
+++ b/linux/sound/isa/gus/gus_dma.c
@@ -0,0 +1,250 @@
+/*
+ *  Routines for GF1 DMA control
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/dma.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+static void snd_gf1_dma_ack(struct snd_gus_card * gus)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, 0x00);
+	snd_gf1_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static void snd_gf1_dma_program(struct snd_gus_card * gus,
+				unsigned int addr,
+				unsigned long buf_addr,
+				unsigned int count,
+				unsigned int cmd)
+{
+	unsigned long flags;
+	unsigned int address;
+	unsigned char dma_cmd;
+	unsigned int address_high;
+
+	snd_printdd("dma_transfer: addr=0x%x, buf=0x%lx, count=0x%x\n",
+		    addr, buf_addr, count);
+
+	if (gus->gf1.dma1 > 3) {
+		if (gus->gf1.enh_mode) {
+			address = addr >> 1;
+		} else {
+			if (addr & 0x1f) {
+				snd_printd("snd_gf1_dma_transfer: unaligned address (0x%x)?\n", addr);
+				return;
+			}
+			address = (addr & 0x000c0000) | ((addr & 0x0003ffff) >> 1);
+		}
+	} else {
+		address = addr;
+	}
+
+	dma_cmd = SNDRV_GF1_DMA_ENABLE | (unsigned short) cmd;
+#if 0
+	dma_cmd |= 0x08;
+#endif
+	if (dma_cmd & SNDRV_GF1_DMA_16BIT) {
+		count++;
+		count &= ~1;	/* align */
+	}
+	if (gus->gf1.dma1 > 3) {
+		dma_cmd |= SNDRV_GF1_DMA_WIDTH16;
+		count++;
+		count &= ~1;	/* align */
+	}
+	snd_gf1_dma_ack(gus);
+	snd_dma_program(gus->gf1.dma1, buf_addr, count, dma_cmd & SNDRV_GF1_DMA_READ ? DMA_MODE_READ : DMA_MODE_WRITE);
+#if 0
+	snd_printk(KERN_DEBUG "address = 0x%x, count = 0x%x, dma_cmd = 0x%x\n",
+		   address << 1, count, dma_cmd);
+#endif
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	if (gus->gf1.enh_mode) {
+		address_high = ((address >> 16) & 0x000000f0) | (address & 0x0000000f);
+		snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4));
+		snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH, (unsigned char) address_high);
+	} else
+		snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4));
+	snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, dma_cmd);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static struct snd_gf1_dma_block *snd_gf1_dma_next_block(struct snd_gus_card * gus)
+{
+	struct snd_gf1_dma_block *block;
+
+	/* PCM block have bigger priority than synthesizer one */
+	if (gus->gf1.dma_data_pcm) {
+		block = gus->gf1.dma_data_pcm;
+		if (gus->gf1.dma_data_pcm_last == block) {
+			gus->gf1.dma_data_pcm =
+			gus->gf1.dma_data_pcm_last = NULL;
+		} else {
+			gus->gf1.dma_data_pcm = block->next;
+		}
+	} else if (gus->gf1.dma_data_synth) {
+		block = gus->gf1.dma_data_synth;
+		if (gus->gf1.dma_data_synth_last == block) {
+			gus->gf1.dma_data_synth =
+			gus->gf1.dma_data_synth_last = NULL;
+		} else {
+			gus->gf1.dma_data_synth = block->next;
+		}
+	} else {
+		block = NULL;
+	}
+	if (block) {
+		gus->gf1.dma_ack = block->ack;
+		gus->gf1.dma_private_data = block->private_data;
+	}
+	return block;
+}
+
+
+static void snd_gf1_dma_interrupt(struct snd_gus_card * gus)
+{
+	struct snd_gf1_dma_block *block;
+
+	snd_gf1_dma_ack(gus);
+	if (gus->gf1.dma_ack)
+		gus->gf1.dma_ack(gus, gus->gf1.dma_private_data);
+	spin_lock(&gus->dma_lock);
+	if (gus->gf1.dma_data_pcm == NULL &&
+	    gus->gf1.dma_data_synth == NULL) {
+	    	gus->gf1.dma_ack = NULL;
+		gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER;
+		spin_unlock(&gus->dma_lock);
+		return;
+	}
+	block = snd_gf1_dma_next_block(gus);
+	spin_unlock(&gus->dma_lock);
+	snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd);
+	kfree(block);
+#if 0
+	snd_printd(KERN_DEBUG "program dma (IRQ) - "
+		   "addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n",
+		   block->addr, block->buf_addr, block->count, block->cmd);
+#endif
+}
+
+int snd_gf1_dma_init(struct snd_gus_card * gus)
+{
+	mutex_lock(&gus->dma_mutex);
+	gus->gf1.dma_shared++;
+	if (gus->gf1.dma_shared > 1) {
+		mutex_unlock(&gus->dma_mutex);
+		return 0;
+	}
+	gus->gf1.interrupt_handler_dma_write = snd_gf1_dma_interrupt;
+	gus->gf1.dma_data_pcm = 
+	gus->gf1.dma_data_pcm_last =
+	gus->gf1.dma_data_synth = 
+	gus->gf1.dma_data_synth_last = NULL;
+	mutex_unlock(&gus->dma_mutex);
+	return 0;
+}
+
+int snd_gf1_dma_done(struct snd_gus_card * gus)
+{
+	struct snd_gf1_dma_block *block;
+
+	mutex_lock(&gus->dma_mutex);
+	gus->gf1.dma_shared--;
+	if (!gus->gf1.dma_shared) {
+		snd_dma_disable(gus->gf1.dma1);
+		snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_WRITE);
+		snd_gf1_dma_ack(gus);
+		while ((block = gus->gf1.dma_data_pcm)) {
+			gus->gf1.dma_data_pcm = block->next;
+			kfree(block);
+		}
+		while ((block = gus->gf1.dma_data_synth)) {
+			gus->gf1.dma_data_synth = block->next;
+			kfree(block);
+		}
+		gus->gf1.dma_data_pcm_last =
+		gus->gf1.dma_data_synth_last = NULL;
+	}
+	mutex_unlock(&gus->dma_mutex);
+	return 0;
+}
+
+int snd_gf1_dma_transfer_block(struct snd_gus_card * gus,
+			       struct snd_gf1_dma_block * __block,
+			       int atomic,
+			       int synth)
+{
+	unsigned long flags;
+	struct snd_gf1_dma_block *block;
+
+	block = kmalloc(sizeof(*block), atomic ? GFP_ATOMIC : GFP_KERNEL);
+	if (block == NULL) {
+		snd_printk(KERN_ERR "gf1: DMA transfer failure; not enough memory\n");
+		return -ENOMEM;
+	}
+	*block = *__block;
+	block->next = NULL;
+
+	snd_printdd("addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n",
+		    block->addr, (long) block->buffer, block->count,
+		    block->cmd);
+
+	snd_printdd("gus->gf1.dma_data_pcm_last = 0x%lx\n",
+		    (long)gus->gf1.dma_data_pcm_last);
+	snd_printdd("gus->gf1.dma_data_pcm = 0x%lx\n",
+		    (long)gus->gf1.dma_data_pcm);
+
+	spin_lock_irqsave(&gus->dma_lock, flags);
+	if (synth) {
+		if (gus->gf1.dma_data_synth_last) {
+			gus->gf1.dma_data_synth_last->next = block;
+			gus->gf1.dma_data_synth_last = block;
+		} else {
+			gus->gf1.dma_data_synth = 
+			gus->gf1.dma_data_synth_last = block;
+		}
+	} else {
+		if (gus->gf1.dma_data_pcm_last) {
+			gus->gf1.dma_data_pcm_last->next = block;
+			gus->gf1.dma_data_pcm_last = block;
+		} else {
+			gus->gf1.dma_data_pcm = 
+			gus->gf1.dma_data_pcm_last = block;
+		}
+	}
+	if (!(gus->gf1.dma_flags & SNDRV_GF1_DMA_TRIGGER)) {
+		gus->gf1.dma_flags |= SNDRV_GF1_DMA_TRIGGER;
+		block = snd_gf1_dma_next_block(gus);
+		spin_unlock_irqrestore(&gus->dma_lock, flags);
+		if (block == NULL)
+			return 0;
+		snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd);
+		kfree(block);
+		return 0;
+	}
+	spin_unlock_irqrestore(&gus->dma_lock, flags);
+	return 0;
+}
diff --git a/linux/sound/isa/gus/gus_dram.c b/linux/sound/isa/gus/gus_dram.c
new file mode 100644
index 0000000..fd2e2e2
--- /dev/null
+++ b/linux/sound/isa/gus/gus_dram.c
@@ -0,0 +1,102 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  DRAM access routines
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+
+static int snd_gus_dram_poke(struct snd_gus_card *gus, char __user *_buffer,
+			     unsigned int address, unsigned int size)
+{
+	unsigned long flags;
+	unsigned int size1, size2;
+	char buffer[256], *pbuffer;
+
+	while (size > 0) {
+		size1 = size > sizeof(buffer) ? sizeof(buffer) : size;
+		if (copy_from_user(buffer, _buffer, size1))
+			return -EFAULT;
+		if (gus->interwave) {
+			spin_lock_irqsave(&gus->reg_lock, flags);
+			snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+			snd_gf1_dram_addr(gus, address);
+			outsb(GUSP(gus, DRAM), buffer, size1);
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+			address += size1;
+		} else {
+			pbuffer = buffer;
+			size2 = size1;
+			while (size2--)
+				snd_gf1_poke(gus, address++, *pbuffer++);
+		}
+		size -= size1;
+		_buffer += size1;
+	}
+	return 0;
+}
+
+
+int snd_gus_dram_write(struct snd_gus_card *gus, char __user *buffer,
+		       unsigned int address, unsigned int size)
+{
+	return snd_gus_dram_poke(gus, buffer, address, size);
+}
+
+static int snd_gus_dram_peek(struct snd_gus_card *gus, char __user *_buffer,
+			     unsigned int address, unsigned int size,
+			     int rom)
+{
+	unsigned long flags;
+	unsigned int size1, size2;
+	char buffer[256], *pbuffer;
+
+	while (size > 0) {
+		size1 = size > sizeof(buffer) ? sizeof(buffer) : size;
+		if (gus->interwave) {
+			spin_lock_irqsave(&gus->reg_lock, flags);
+			snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, rom ? 0x03 : 0x01);
+			snd_gf1_dram_addr(gus, address);
+			insb(GUSP(gus, DRAM), buffer, size1);
+			snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+			address += size1;
+		} else {
+			pbuffer = buffer;
+			size2 = size1;
+			while (size2--)
+				*pbuffer++ = snd_gf1_peek(gus, address++);
+		}
+		if (copy_to_user(_buffer, buffer, size1))
+			return -EFAULT;
+		size -= size1;
+		_buffer += size1;
+	}
+	return 0;
+}
+
+int snd_gus_dram_read(struct snd_gus_card *gus, char __user *buffer,
+		      unsigned int address, unsigned int size,
+		      int rom)
+{
+	return snd_gus_dram_peek(gus, buffer, address, size, rom);
+}
diff --git a/linux/sound/isa/gus/gus_instr.c b/linux/sound/isa/gus/gus_instr.c
new file mode 100644
index 0000000..4dc9caf
--- /dev/null
+++ b/linux/sound/isa/gus/gus_instr.c
@@ -0,0 +1,172 @@
+/*
+ *  Routines for Gravis UltraSound soundcards - Synthesizer
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+/*
+ *
+ */
+
+int snd_gus_iwffff_put_sample(void *private_data, struct iwffff_wave *wave,
+			      char __user *data, long len, int atomic)
+{
+	struct snd_gus_card *gus = private_data;
+	struct snd_gf1_mem_block *block;
+	int err;
+
+	if (wave->format & IWFFFF_WAVE_ROM)
+		return 0;	/* it's probably ok - verify the address? */
+	if (wave->format & IWFFFF_WAVE_STEREO)
+		return -EINVAL;	/* not supported */
+	block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc,
+				  SNDRV_GF1_MEM_OWNER_WAVE_IWFFFF,
+				  NULL, wave->size,
+				  wave->format & IWFFFF_WAVE_16BIT, 1,
+				  wave->share_id);
+	if (block == NULL)
+		return -ENOMEM;
+	err = snd_gus_dram_write(gus, data,
+				 block->ptr, wave->size);
+	if (err < 0) {
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 0);
+		snd_gf1_mem_xfree(&gus->gf1.mem_alloc, block);
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 1);
+		return err;
+	}
+	wave->address.memory = block->ptr;
+	return 0;
+}
+
+int snd_gus_iwffff_get_sample(void *private_data, struct iwffff_wave *wave,
+			      char __user *data, long len, int atomic)
+{
+	struct snd_gus_card *gus = private_data;
+
+	return snd_gus_dram_read(gus, data, wave->address.memory, wave->size,
+				 wave->format & IWFFFF_WAVE_ROM ? 1 : 0);
+}
+
+int snd_gus_iwffff_remove_sample(void *private_data, struct iwffff_wave *wave,
+				 int atomic)
+{
+	struct snd_gus_card *gus = private_data;
+
+	if (wave->format & IWFFFF_WAVE_ROM)
+		return 0;	/* it's probably ok - verify the address? */	
+	return snd_gf1_mem_free(&gus->gf1.mem_alloc, wave->address.memory);
+}
+
+/*
+ *
+ */
+
+int snd_gus_gf1_put_sample(void *private_data, struct gf1_wave *wave,
+			   char __user *data, long len, int atomic)
+{
+	struct snd_gus_card *gus = private_data;
+	struct snd_gf1_mem_block *block;
+	int err;
+
+	if (wave->format & GF1_WAVE_STEREO)
+		return -EINVAL;	/* not supported */
+	block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc,
+				  SNDRV_GF1_MEM_OWNER_WAVE_GF1,
+				  NULL, wave->size,
+				  wave->format & GF1_WAVE_16BIT, 1,
+				  wave->share_id);
+	if (block == NULL)
+		return -ENOMEM;
+	err = snd_gus_dram_write(gus, data,
+				 block->ptr, wave->size);
+	if (err < 0) {
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 0);
+		snd_gf1_mem_xfree(&gus->gf1.mem_alloc, block);
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 1);
+		return err;
+	}
+	wave->address.memory = block->ptr;
+	return 0;
+}
+
+int snd_gus_gf1_get_sample(void *private_data, struct gf1_wave *wave,
+			   char __user *data, long len, int atomic)
+{
+	struct snd_gus_card *gus = private_data;
+
+	return snd_gus_dram_read(gus, data, wave->address.memory, wave->size, 0);
+}
+
+int snd_gus_gf1_remove_sample(void *private_data, struct gf1_wave *wave,
+			      int atomic)
+{
+	struct snd_gus_card *gus = private_data;
+
+	return snd_gf1_mem_free(&gus->gf1.mem_alloc, wave->address.memory);
+}
+
+/*
+ *
+ */
+
+int snd_gus_simple_put_sample(void *private_data, struct simple_instrument *instr,
+			      char __user *data, long len, int atomic)
+{
+	struct snd_gus_card *gus = private_data;
+	struct snd_gf1_mem_block *block;
+	int err;
+
+	if (instr->format & SIMPLE_WAVE_STEREO)
+		return -EINVAL;	/* not supported */
+	block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc,
+				  SNDRV_GF1_MEM_OWNER_WAVE_SIMPLE,
+				  NULL, instr->size,
+				  instr->format & SIMPLE_WAVE_16BIT, 1,
+				  instr->share_id);
+	if (block == NULL)
+		return -ENOMEM;
+	err = snd_gus_dram_write(gus, data, block->ptr, instr->size);
+	if (err < 0) {
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 0);
+		snd_gf1_mem_xfree(&gus->gf1.mem_alloc, block);
+		snd_gf1_mem_lock(&gus->gf1.mem_alloc, 1);
+		return err;
+	}
+	instr->address.memory = block->ptr;
+	return 0;
+}
+
+int snd_gus_simple_get_sample(void *private_data, struct simple_instrument *instr,
+			      char __user *data, long len, int atomic)
+{
+	struct snd_gus_card *gus = private_data;
+
+	return snd_gus_dram_read(gus, data, instr->address.memory, instr->size, 0);
+}
+
+int snd_gus_simple_remove_sample(void *private_data, struct simple_instrument *instr,
+			         int atomic)
+{
+	struct snd_gus_card *gus = private_data;
+
+	return snd_gf1_mem_free(&gus->gf1.mem_alloc, instr->address.memory);
+}
diff --git a/linux/sound/isa/gus/gus_io.c b/linux/sound/isa/gus/gus_io.c
new file mode 100644
index 0000000..ca79878
--- /dev/null
+++ b/linux/sound/isa/gus/gus_io.c
@@ -0,0 +1,540 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  I/O routines for GF1/InterWave synthesizer chips
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+void snd_gf1_delay(struct snd_gus_card * gus)
+{
+	int i;
+
+	for (i = 0; i < 6; i++) {
+		mb();
+		inb(GUSP(gus, DRAM));
+	}
+}
+
+/*
+ *  =======================================================================
+ */
+
+/*
+ *  ok.. stop of control registers (wave & ramp) need some special things..
+ *       big UltraClick (tm) elimination...
+ */
+
+static inline void __snd_gf1_ctrl_stop(struct snd_gus_card * gus, unsigned char reg)
+{
+	unsigned char value;
+
+	outb(reg | 0x80, gus->gf1.reg_regsel);
+	mb();
+	value = inb(gus->gf1.reg_data8);
+	mb();
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	outb((value | 0x03) & ~(0x80 | 0x20), gus->gf1.reg_data8);
+	mb();
+}
+
+static inline void __snd_gf1_write8(struct snd_gus_card * gus,
+				    unsigned char reg,
+				    unsigned char data)
+{
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	outb(data, gus->gf1.reg_data8);
+	mb();
+}
+
+static inline unsigned char __snd_gf1_look8(struct snd_gus_card * gus,
+					    unsigned char reg)
+{
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	return inb(gus->gf1.reg_data8);
+}
+
+static inline void __snd_gf1_write16(struct snd_gus_card * gus,
+				     unsigned char reg, unsigned int data)
+{
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) data, gus->gf1.reg_data16);
+	mb();
+}
+
+static inline unsigned short __snd_gf1_look16(struct snd_gus_card * gus,
+					      unsigned char reg)
+{
+	outb(reg, gus->gf1.reg_regsel);
+	mb();
+	return inw(gus->gf1.reg_data16);
+}
+
+static inline void __snd_gf1_adlib_write(struct snd_gus_card * gus,
+					 unsigned char reg, unsigned char data)
+{
+	outb(reg, gus->gf1.reg_timerctrl);
+	inb(gus->gf1.reg_timerctrl);
+	inb(gus->gf1.reg_timerctrl);
+	outb(data, gus->gf1.reg_timerdata);
+	inb(gus->gf1.reg_timerctrl);
+	inb(gus->gf1.reg_timerctrl);
+}
+
+static inline void __snd_gf1_write_addr(struct snd_gus_card * gus, unsigned char reg,
+                                        unsigned int addr, int w_16bit)
+{
+	if (gus->gf1.enh_mode) {
+		if (w_16bit)
+			addr = ((addr >> 1) & ~0x0000000f) | (addr & 0x0000000f);
+		__snd_gf1_write8(gus, SNDRV_GF1_VB_UPPER_ADDRESS, (unsigned char) ((addr >> 26) & 0x03));
+	} else if (w_16bit)
+		addr = (addr & 0x00c0000f) | ((addr & 0x003ffff0) >> 1);
+	__snd_gf1_write16(gus, reg, (unsigned short) (addr >> 11));
+	__snd_gf1_write16(gus, reg + 1, (unsigned short) (addr << 5));
+}
+
+static inline unsigned int __snd_gf1_read_addr(struct snd_gus_card * gus,
+					       unsigned char reg, short w_16bit)
+{
+	unsigned int res;
+
+	res = ((unsigned int) __snd_gf1_look16(gus, reg | 0x80) << 11) & 0xfff800;
+	res |= ((unsigned int) __snd_gf1_look16(gus, (reg + 1) | 0x80) >> 5) & 0x0007ff;
+	if (gus->gf1.enh_mode) {
+		res |= (unsigned int) __snd_gf1_look8(gus, SNDRV_GF1_VB_UPPER_ADDRESS | 0x80) << 26;
+		if (w_16bit)
+			res = ((res << 1) & 0xffffffe0) | (res & 0x0000000f);
+	} else if (w_16bit)
+		res = ((res & 0x001ffff0) << 1) | (res & 0x00c0000f);
+	return res;
+}
+
+
+/*
+ *  =======================================================================
+ */
+
+void snd_gf1_ctrl_stop(struct snd_gus_card * gus, unsigned char reg)
+{
+	__snd_gf1_ctrl_stop(gus, reg);
+}
+
+void snd_gf1_write8(struct snd_gus_card * gus,
+		    unsigned char reg,
+		    unsigned char data)
+{
+	__snd_gf1_write8(gus, reg, data);
+}
+
+unsigned char snd_gf1_look8(struct snd_gus_card * gus, unsigned char reg)
+{
+	return __snd_gf1_look8(gus, reg);
+}
+
+void snd_gf1_write16(struct snd_gus_card * gus,
+		     unsigned char reg,
+		     unsigned int data)
+{
+	__snd_gf1_write16(gus, reg, data);
+}
+
+unsigned short snd_gf1_look16(struct snd_gus_card * gus, unsigned char reg)
+{
+	return __snd_gf1_look16(gus, reg);
+}
+
+void snd_gf1_adlib_write(struct snd_gus_card * gus,
+                         unsigned char reg,
+                         unsigned char data)
+{
+	__snd_gf1_adlib_write(gus, reg, data);
+}
+
+void snd_gf1_write_addr(struct snd_gus_card * gus, unsigned char reg,
+                        unsigned int addr, short w_16bit)
+{
+	__snd_gf1_write_addr(gus, reg, addr, w_16bit);
+}
+
+unsigned int snd_gf1_read_addr(struct snd_gus_card * gus,
+                               unsigned char reg,
+                               short w_16bit)
+{
+	return __snd_gf1_read_addr(gus, reg, w_16bit);
+}
+
+/*
+
+ */
+
+void snd_gf1_i_ctrl_stop(struct snd_gus_card * gus, unsigned char reg)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	__snd_gf1_ctrl_stop(gus, reg);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+void snd_gf1_i_write8(struct snd_gus_card * gus,
+		      unsigned char reg,
+                      unsigned char data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	__snd_gf1_write8(gus, reg, data);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned char snd_gf1_i_look8(struct snd_gus_card * gus, unsigned char reg)
+{
+	unsigned long flags;
+	unsigned char res;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	res = __snd_gf1_look8(gus, reg);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return res;
+}
+
+void snd_gf1_i_write16(struct snd_gus_card * gus,
+		       unsigned char reg,
+		       unsigned int data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	__snd_gf1_write16(gus, reg, data);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned short snd_gf1_i_look16(struct snd_gus_card * gus, unsigned char reg)
+{
+	unsigned long flags;
+	unsigned short res;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	res = __snd_gf1_look16(gus, reg);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return res;
+}
+
+#if 0
+
+void snd_gf1_i_adlib_write(struct snd_gus_card * gus,
+		           unsigned char reg,
+		           unsigned char data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	__snd_gf1_adlib_write(gus, reg, data);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+void snd_gf1_i_write_addr(struct snd_gus_card * gus, unsigned char reg,
+			  unsigned int addr, short w_16bit)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	__snd_gf1_write_addr(gus, reg, addr, w_16bit);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+#endif  /*  0  */
+
+#ifdef CONFIG_SND_DEBUG
+static unsigned int snd_gf1_i_read_addr(struct snd_gus_card * gus,
+					unsigned char reg, short w_16bit)
+{
+	unsigned int res;
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	res = __snd_gf1_read_addr(gus, reg, w_16bit);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return res;
+}
+#endif
+
+/*
+
+ */
+
+void snd_gf1_dram_addr(struct snd_gus_card * gus, unsigned int addr)
+{
+	outb(0x43, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(0x44, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+}
+
+void snd_gf1_poke(struct snd_gus_card * gus, unsigned int addr, unsigned char data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+	outb(data, gus->gf1.reg_dram);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned char snd_gf1_peek(struct snd_gus_card * gus, unsigned int addr)
+{
+	unsigned long flags;
+	unsigned char res;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+	res = inb(gus->gf1.reg_dram);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return res;
+}
+
+#if 0
+
+void snd_gf1_pokew(struct snd_gus_card * gus, unsigned int addr, unsigned short data)
+{
+	unsigned long flags;
+
+#ifdef CONFIG_SND_DEBUG
+	if (!gus->interwave)
+		snd_printk(KERN_DEBUG "snd_gf1_pokew - GF1!!!\n");
+#endif
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+	outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel);
+	mb();
+	outw(data, gus->gf1.reg_data16);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+unsigned short snd_gf1_peekw(struct snd_gus_card * gus, unsigned int addr)
+{
+	unsigned long flags;
+	unsigned short res;
+
+#ifdef CONFIG_SND_DEBUG
+	if (!gus->interwave)
+		snd_printk(KERN_DEBUG "snd_gf1_peekw - GF1!!!\n");
+#endif
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+	outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel);
+	mb();
+	res = inw(gus->gf1.reg_data16);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return res;
+}
+
+void snd_gf1_dram_setmem(struct snd_gus_card * gus, unsigned int addr,
+			 unsigned short value, unsigned int count)
+{
+	unsigned long port;
+	unsigned long flags;
+
+#ifdef CONFIG_SND_DEBUG
+	if (!gus->interwave)
+		snd_printk(KERN_DEBUG "snd_gf1_dram_setmem - GF1!!!\n");
+#endif
+	addr &= ~1;
+	count >>= 1;
+	port = GUSP(gus, GF1DATALOW);
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel);
+	mb();
+	outw((unsigned short) addr, gus->gf1.reg_data16);
+	mb();
+	outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel);
+	mb();
+	outb((unsigned char) (addr >> 16), gus->gf1.reg_data8);
+	mb();
+	outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel);
+	while (count--)
+		outw(value, port);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+#endif  /*  0  */
+
+void snd_gf1_select_active_voices(struct snd_gus_card * gus)
+{
+	unsigned short voices;
+
+	static unsigned short voices_tbl[32 - 14 + 1] =
+	{
+	    44100, 41160, 38587, 36317, 34300, 32494, 30870, 29400, 28063, 26843,
+	    25725, 24696, 23746, 22866, 22050, 21289, 20580, 19916, 19293
+	};
+
+	voices = gus->gf1.active_voices;
+	if (voices > 32)
+		voices = 32;
+	if (voices < 14)
+		voices = 14;
+	if (gus->gf1.enh_mode)
+		voices = 32;
+	gus->gf1.active_voices = voices;
+	gus->gf1.playback_freq =
+	    gus->gf1.enh_mode ? 44100 : voices_tbl[voices - 14];
+	if (!gus->gf1.enh_mode) {
+		snd_gf1_i_write8(gus, SNDRV_GF1_GB_ACTIVE_VOICES, 0xc0 | (voices - 1));
+		udelay(100);
+	}
+}
+
+#ifdef CONFIG_SND_DEBUG
+
+void snd_gf1_print_voice_registers(struct snd_gus_card * gus)
+{
+	unsigned char mode;
+	int voice, ctrl;
+
+	voice = gus->gf1.active_voice;
+	printk(KERN_INFO " -%i- GF1  voice ctrl, ramp ctrl  = 0x%x, 0x%x\n", voice, ctrl = snd_gf1_i_read8(gus, 0), snd_gf1_i_read8(gus, 0x0d));
+	printk(KERN_INFO " -%i- GF1  frequency              = 0x%x\n", voice, snd_gf1_i_read16(gus, 1));
+	printk(KERN_INFO " -%i- GF1  loop start, end        = 0x%x (0x%x), 0x%x (0x%x)\n", voice, snd_gf1_i_read_addr(gus, 2, ctrl & 4), snd_gf1_i_read_addr(gus, 2, (ctrl & 4) ^ 4), snd_gf1_i_read_addr(gus, 4, ctrl & 4), snd_gf1_i_read_addr(gus, 4, (ctrl & 4) ^ 4));
+	printk(KERN_INFO " -%i- GF1  ramp start, end, rate  = 0x%x, 0x%x, 0x%x\n", voice, snd_gf1_i_read8(gus, 7), snd_gf1_i_read8(gus, 8), snd_gf1_i_read8(gus, 6));
+	printk(KERN_INFO" -%i- GF1  volume                 = 0x%x\n", voice, snd_gf1_i_read16(gus, 9));
+	printk(KERN_INFO " -%i- GF1  position               = 0x%x (0x%x)\n", voice, snd_gf1_i_read_addr(gus, 0x0a, ctrl & 4), snd_gf1_i_read_addr(gus, 0x0a, (ctrl & 4) ^ 4));
+	if (gus->interwave && snd_gf1_i_read8(gus, 0x19) & 0x01) {	/* enhanced mode */
+		mode = snd_gf1_i_read8(gus, 0x15);
+		printk(KERN_INFO " -%i- GFA1 mode                   = 0x%x\n", voice, mode);
+		if (mode & 0x01) {	/* Effect processor */
+			printk(KERN_INFO " -%i- GFA1 effect address         = 0x%x\n", voice, snd_gf1_i_read_addr(gus, 0x11, ctrl & 4));
+			printk(KERN_INFO " -%i- GFA1 effect volume          = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x16));
+			printk(KERN_INFO " -%i- GFA1 effect volume final    = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x1d));
+			printk(KERN_INFO " -%i- GFA1 effect acumulator      = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x14));
+		}
+		if (mode & 0x20) {
+			printk(KERN_INFO " -%i- GFA1 left offset            = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x13), snd_gf1_i_read16(gus, 0x13) >> 4);
+			printk(KERN_INFO " -%i- GFA1 left offset final      = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x1c), snd_gf1_i_read16(gus, 0x1c) >> 4);
+			printk(KERN_INFO " -%i- GFA1 right offset           = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x0c), snd_gf1_i_read16(gus, 0x0c) >> 4);
+			printk(KERN_INFO " -%i- GFA1 right offset final     = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x1b), snd_gf1_i_read16(gus, 0x1b) >> 4);
+		} else
+			printk(KERN_INFO " -%i- GF1  pan                    = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c));
+	} else
+		printk(KERN_INFO " -%i- GF1  pan                    = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c));
+}
+
+#if 0
+
+void snd_gf1_print_global_registers(struct snd_gus_card * gus)
+{
+	unsigned char global_mode = 0x00;
+
+	printk(KERN_INFO " -G- GF1 active voices            = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_ACTIVE_VOICES));
+	if (gus->interwave) {
+		global_mode = snd_gf1_i_read8(gus, SNDRV_GF1_GB_GLOBAL_MODE);
+		printk(KERN_INFO " -G- GF1 global mode              = 0x%x\n", global_mode);
+	}
+	if (global_mode & 0x02)	/* LFO enabled? */
+		printk(KERN_INFO " -G- GF1 LFO base                 = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_LFO_BASE));
+	printk(KERN_INFO " -G- GF1 voices IRQ read          = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VOICES_IRQ_READ));
+	printk(KERN_INFO " -G- GF1 DRAM DMA control         = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL));
+	printk(KERN_INFO " -G- GF1 DRAM DMA high/low        = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH), snd_gf1_i_read16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW));
+	printk(KERN_INFO " -G- GF1 DRAM IO high/low         = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_IO_HIGH), snd_gf1_i_read16(gus, SNDRV_GF1_GW_DRAM_IO_LOW));
+	if (!gus->interwave)
+		printk(KERN_INFO " -G- GF1 record DMA control       = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL));
+	printk(KERN_INFO " -G- GF1 DRAM IO 16               = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_DRAM_IO16));
+	if (gus->gf1.enh_mode) {
+		printk(KERN_INFO " -G- GFA1 memory config           = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG));
+		printk(KERN_INFO " -G- GFA1 memory control          = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_MEMORY_CONTROL));
+		printk(KERN_INFO " -G- GFA1 FIFO record base        = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR));
+		printk(KERN_INFO " -G- GFA1 FIFO playback base      = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR));
+		printk(KERN_INFO " -G- GFA1 interleave control      = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_INTERLEAVE));
+	}
+}
+
+void snd_gf1_print_setup_registers(struct snd_gus_card * gus)
+{
+	printk(KERN_INFO " -S- mix control                  = 0x%x\n", inb(GUSP(gus, MIXCNTRLREG)));
+	printk(KERN_INFO " -S- IRQ status                   = 0x%x\n", inb(GUSP(gus, IRQSTAT)));
+	printk(KERN_INFO " -S- timer control                = 0x%x\n", inb(GUSP(gus, TIMERCNTRL)));
+	printk(KERN_INFO " -S- timer data                   = 0x%x\n", inb(GUSP(gus, TIMERDATA)));
+	printk(KERN_INFO " -S- status read                  = 0x%x\n", inb(GUSP(gus, REGCNTRLS)));
+	printk(KERN_INFO " -S- Sound Blaster control        = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL));
+	printk(KERN_INFO " -S- AdLib timer 1/2              = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_ADLIB_TIMER_1), snd_gf1_i_look8(gus, SNDRV_GF1_GB_ADLIB_TIMER_2));
+	printk(KERN_INFO " -S- reset                        = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET));
+	if (gus->interwave) {
+		printk(KERN_INFO " -S- compatibility                = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_COMPATIBILITY));
+		printk(KERN_INFO " -S- decode control               = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DECODE_CONTROL));
+		printk(KERN_INFO " -S- version number               = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER));
+		printk(KERN_INFO " -S- MPU-401 emul. control A/B    = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A), snd_gf1_i_look8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B));
+		printk(KERN_INFO " -S- emulation IRQ                = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_EMULATION_IRQ));
+	}
+}
+
+void snd_gf1_peek_print_block(struct snd_gus_card * gus, unsigned int addr, int count, int w_16bit)
+{
+	if (!w_16bit) {
+		while (count-- > 0)
+			printk(count > 0 ? "%02x:" : "%02x", snd_gf1_peek(gus, addr++));
+	} else {
+		while (count-- > 0) {
+			printk(count > 0 ? "%04x:" : "%04x", snd_gf1_peek(gus, addr) | (snd_gf1_peek(gus, addr + 1) << 8));
+			addr += 2;
+		}
+	}
+}
+
+#endif  /*  0  */
+
+#endif
diff --git a/linux/sound/isa/gus/gus_irq.c b/linux/sound/isa/gus/gus_irq.c
new file mode 100644
index 0000000..2055aff
--- /dev/null
+++ b/linux/sound/isa/gus/gus_irq.c
@@ -0,0 +1,149 @@
+/*
+ *  Routine for IRQ handling from GF1/InterWave chip
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/gus.h>
+
+#ifdef CONFIG_SND_DEBUG
+#define STAT_ADD(x)	((x)++)
+#else
+#define STAT_ADD(x)	while (0) { ; }
+#endif
+
+irqreturn_t snd_gus_interrupt(int irq, void *dev_id)
+{
+	struct snd_gus_card * gus = dev_id;
+	unsigned char status;
+	int loop = 100;
+	int handled = 0;
+
+__again:
+	status = inb(gus->gf1.reg_irqstat);
+	if (status == 0)
+		return IRQ_RETVAL(handled);
+	handled = 1;
+	/* snd_printk(KERN_DEBUG "IRQ: status = 0x%x\n", status); */
+	if (status & 0x02) {
+		STAT_ADD(gus->gf1.interrupt_stat_midi_in);
+		if (gus->gf1.interrupt_handler_midi_in)
+			gus->gf1.interrupt_handler_midi_in(gus);
+	}
+	if (status & 0x01) {
+		STAT_ADD(gus->gf1.interrupt_stat_midi_out);
+		if (gus->gf1.interrupt_handler_midi_out)
+			gus->gf1.interrupt_handler_midi_out(gus);
+	}
+	if (status & (0x20 | 0x40)) {
+		unsigned int already, _current_;
+		unsigned char voice_status, voice;
+		struct snd_gus_voice *pvoice;
+
+		already = 0;
+		while (((voice_status = snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ)) & 0xc0) != 0xc0) {
+			voice = voice_status & 0x1f;
+			_current_ = 1 << voice;
+			if (already & _current_)
+				continue;	/* multi request */
+			already |= _current_;	/* mark request */
+#if 0
+			printk(KERN_DEBUG "voice = %i, voice_status = 0x%x, "
+			       "voice_verify = %i\n",
+			       voice, voice_status, inb(GUSP(gus, GF1PAGE)));
+#endif
+			pvoice = &gus->gf1.voices[voice]; 
+			if (pvoice->use) {
+				if (!(voice_status & 0x80)) {	/* voice position IRQ */
+					STAT_ADD(pvoice->interrupt_stat_wave);
+					pvoice->handler_wave(gus, pvoice);
+				}
+				if (!(voice_status & 0x40)) {	/* volume ramp IRQ */
+					STAT_ADD(pvoice->interrupt_stat_volume);
+					pvoice->handler_volume(gus, pvoice);
+				}
+			} else {
+				STAT_ADD(gus->gf1.interrupt_stat_voice_lost);
+				snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+				snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+			}
+		}
+	}
+	if (status & 0x04) {
+		STAT_ADD(gus->gf1.interrupt_stat_timer1);
+		if (gus->gf1.interrupt_handler_timer1)
+			gus->gf1.interrupt_handler_timer1(gus);
+	}
+	if (status & 0x08) {
+		STAT_ADD(gus->gf1.interrupt_stat_timer2);
+		if (gus->gf1.interrupt_handler_timer2)
+			gus->gf1.interrupt_handler_timer2(gus);
+	}
+	if (status & 0x80) {
+		if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL) & 0x40) {
+			STAT_ADD(gus->gf1.interrupt_stat_dma_write);
+			if (gus->gf1.interrupt_handler_dma_write)
+				gus->gf1.interrupt_handler_dma_write(gus);
+		}
+		if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL) & 0x40) {
+			STAT_ADD(gus->gf1.interrupt_stat_dma_read);
+			if (gus->gf1.interrupt_handler_dma_read)
+				gus->gf1.interrupt_handler_dma_read(gus);
+		}
+	}
+	if (--loop > 0)
+		goto __again;
+	return IRQ_NONE;
+}
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_gus_irq_info_read(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer)
+{
+	struct snd_gus_card *gus;
+	struct snd_gus_voice *pvoice;
+	int idx;
+
+	gus = entry->private_data;
+	snd_iprintf(buffer, "midi out = %u\n", gus->gf1.interrupt_stat_midi_out);
+	snd_iprintf(buffer, "midi in = %u\n", gus->gf1.interrupt_stat_midi_in);
+	snd_iprintf(buffer, "timer1 = %u\n", gus->gf1.interrupt_stat_timer1);
+	snd_iprintf(buffer, "timer2 = %u\n", gus->gf1.interrupt_stat_timer2);
+	snd_iprintf(buffer, "dma write = %u\n", gus->gf1.interrupt_stat_dma_write);
+	snd_iprintf(buffer, "dma read = %u\n", gus->gf1.interrupt_stat_dma_read);
+	snd_iprintf(buffer, "voice lost = %u\n", gus->gf1.interrupt_stat_voice_lost);
+	for (idx = 0; idx < 32; idx++) {
+		pvoice = &gus->gf1.voices[idx];
+		snd_iprintf(buffer, "voice %i: wave = %u, volume = %u\n",
+					idx,
+					pvoice->interrupt_stat_wave,
+					pvoice->interrupt_stat_volume);
+	}
+}
+
+void snd_gus_irq_profile_init(struct snd_gus_card *gus)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(gus->card, "gusirq", &entry))
+		snd_info_set_text_ops(entry, gus, snd_gus_irq_info_read);
+}
+
+#endif
diff --git a/linux/sound/isa/gus/gus_main.c b/linux/sound/isa/gus/gus_main.c
new file mode 100644
index 0000000..12eb98f
--- /dev/null
+++ b/linux/sound/isa/gus/gus_main.c
@@ -0,0 +1,482 @@
+/*
+ *  Routines for Gravis UltraSound soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/control.h>
+
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Routines for Gravis UltraSound soundcards");
+MODULE_LICENSE("GPL");
+
+static int snd_gus_init_dma_irq(struct snd_gus_card * gus, int latches);
+
+int snd_gus_use_inc(struct snd_gus_card * gus)
+{
+	if (!try_module_get(gus->card->module))
+		return 0;
+	return 1;
+}
+
+void snd_gus_use_dec(struct snd_gus_card * gus)
+{
+	module_put(gus->card->module);
+}
+
+static int snd_gus_joystick_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 31;
+	return 0;
+}
+
+static int snd_gus_joystick_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = gus->joystick_dac & 31;
+	return 0;
+}
+
+static int snd_gus_joystick_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval;
+	
+	nval = ucontrol->value.integer.value[0] & 31;
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	change = gus->joystick_dac != nval;
+	gus->joystick_dac = nval;
+	snd_gf1_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_gus_joystick_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.name = "Joystick Speed",
+	.info = snd_gus_joystick_info,
+	.get = snd_gus_joystick_get,
+	.put = snd_gus_joystick_put
+};
+
+static void snd_gus_init_control(struct snd_gus_card *gus)
+{
+	if (!gus->ace_flag)
+		snd_ctl_add(gus->card, snd_ctl_new1(&snd_gus_joystick_control, gus));
+}
+
+/*
+ *
+ */
+
+static int snd_gus_free(struct snd_gus_card *gus)
+{
+	if (gus->gf1.res_port2 == NULL)
+		goto __hw_end;
+	snd_gf1_stop(gus);
+	snd_gus_init_dma_irq(gus, 0);
+      __hw_end:
+	release_and_free_resource(gus->gf1.res_port1);
+	release_and_free_resource(gus->gf1.res_port2);
+	if (gus->gf1.irq >= 0)
+		free_irq(gus->gf1.irq, (void *) gus);
+	if (gus->gf1.dma1 >= 0) {
+		disable_dma(gus->gf1.dma1);
+		free_dma(gus->gf1.dma1);
+	}
+	if (!gus->equal_dma && gus->gf1.dma2 >= 0) {
+		disable_dma(gus->gf1.dma2);
+		free_dma(gus->gf1.dma2);
+	}
+	kfree(gus);
+	return 0;
+}
+
+static int snd_gus_dev_free(struct snd_device *device)
+{
+	struct snd_gus_card *gus = device->device_data;
+	return snd_gus_free(gus);
+}
+
+int snd_gus_create(struct snd_card *card,
+		   unsigned long port,
+		   int irq, int dma1, int dma2,
+		   int timer_dev,
+		   int voices,
+		   int pcm_channels,
+		   int effect,
+		   struct snd_gus_card **rgus)
+{
+	struct snd_gus_card *gus;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_gus_dev_free,
+	};
+
+	*rgus = NULL;
+	gus = kzalloc(sizeof(*gus), GFP_KERNEL);
+	if (gus == NULL)
+		return -ENOMEM;
+	spin_lock_init(&gus->reg_lock);
+	spin_lock_init(&gus->voice_alloc);
+	spin_lock_init(&gus->active_voice_lock);
+	spin_lock_init(&gus->event_lock);
+	spin_lock_init(&gus->dma_lock);
+	spin_lock_init(&gus->pcm_volume_level_lock);
+	spin_lock_init(&gus->uart_cmd_lock);
+	mutex_init(&gus->dma_mutex);
+	gus->gf1.irq = -1;
+	gus->gf1.dma1 = -1;
+	gus->gf1.dma2 = -1;
+	gus->card = card;
+	gus->gf1.port = port;
+	/* fill register variables for speedup */
+	gus->gf1.reg_page = GUSP(gus, GF1PAGE);
+	gus->gf1.reg_regsel = GUSP(gus, GF1REGSEL);
+	gus->gf1.reg_data8 = GUSP(gus, GF1DATAHIGH);
+	gus->gf1.reg_data16 = GUSP(gus, GF1DATALOW);
+	gus->gf1.reg_irqstat = GUSP(gus, IRQSTAT);
+	gus->gf1.reg_dram = GUSP(gus, DRAM);
+	gus->gf1.reg_timerctrl = GUSP(gus, TIMERCNTRL);
+	gus->gf1.reg_timerdata = GUSP(gus, TIMERDATA);
+	/* allocate resources */
+	if ((gus->gf1.res_port1 = request_region(port, 16, "GUS GF1 (Adlib/SB)")) == NULL) {
+		snd_printk(KERN_ERR "gus: can't grab SB port 0x%lx\n", port);
+		snd_gus_free(gus);
+		return -EBUSY;
+	}
+	if ((gus->gf1.res_port2 = request_region(port + 0x100, 12, "GUS GF1 (Synth)")) == NULL) {
+		snd_printk(KERN_ERR "gus: can't grab synth port 0x%lx\n", port + 0x100);
+		snd_gus_free(gus);
+		return -EBUSY;
+	}
+	if (irq >= 0 && request_irq(irq, snd_gus_interrupt, IRQF_DISABLED, "GUS GF1", (void *) gus)) {
+		snd_printk(KERN_ERR "gus: can't grab irq %d\n", irq);
+		snd_gus_free(gus);
+		return -EBUSY;
+	}
+	gus->gf1.irq = irq;
+	if (request_dma(dma1, "GUS - 1")) {
+		snd_printk(KERN_ERR "gus: can't grab DMA1 %d\n", dma1);
+		snd_gus_free(gus);
+		return -EBUSY;
+	}
+	gus->gf1.dma1 = dma1;
+	if (dma2 >= 0 && dma1 != dma2) {
+		if (request_dma(dma2, "GUS - 2")) {
+			snd_printk(KERN_ERR "gus: can't grab DMA2 %d\n", dma2);
+			snd_gus_free(gus);
+			return -EBUSY;
+		}
+		gus->gf1.dma2 = dma2;
+	} else {
+		gus->gf1.dma2 = gus->gf1.dma1;
+		gus->equal_dma = 1;
+	}
+	gus->timer_dev = timer_dev;
+	if (voices < 14)
+		voices = 14;
+	if (voices > 32)
+		voices = 32;
+	if (pcm_channels < 0)
+		pcm_channels = 0;
+	if (pcm_channels > 8)
+		pcm_channels = 8;
+	pcm_channels++;
+	pcm_channels &= ~1;
+	gus->gf1.effect = effect ? 1 : 0;
+	gus->gf1.active_voices = voices;
+	gus->gf1.pcm_channels = pcm_channels;
+	gus->gf1.volume_ramp = 25;
+	gus->gf1.smooth_pan = 1;
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, gus, &ops)) < 0) {
+		snd_gus_free(gus);
+		return err;
+	}
+	*rgus = gus;
+	return 0;
+}
+
+/*
+ *  Memory detection routine for plain GF1 soundcards
+ */
+
+static int snd_gus_detect_memory(struct snd_gus_card * gus)
+{
+	int l, idx, local;
+	unsigned char d;
+
+	snd_gf1_poke(gus, 0L, 0xaa);
+	snd_gf1_poke(gus, 1L, 0x55);
+	if (snd_gf1_peek(gus, 0L) != 0xaa || snd_gf1_peek(gus, 1L) != 0x55) {
+		snd_printk(KERN_ERR "plain GF1 card at 0x%lx without onboard DRAM?\n", gus->gf1.port);
+		return -ENOMEM;
+	}
+	for (idx = 1, d = 0xab; idx < 4; idx++, d++) {
+		local = idx << 18;
+		snd_gf1_poke(gus, local, d);
+		snd_gf1_poke(gus, local + 1, d + 1);
+		if (snd_gf1_peek(gus, local) != d ||
+		    snd_gf1_peek(gus, local + 1) != d + 1 ||
+		    snd_gf1_peek(gus, 0L) != 0xaa)
+			break;
+	}
+#if 1
+	gus->gf1.memory = idx << 18;
+#else
+	gus->gf1.memory = 256 * 1024;
+#endif
+	for (l = 0, local = gus->gf1.memory; l < 4; l++, local -= 256 * 1024) {
+		gus->gf1.mem_alloc.banks_8[l].address =
+		    gus->gf1.mem_alloc.banks_8[l].size = 0;
+		gus->gf1.mem_alloc.banks_16[l].address = l << 18;
+		gus->gf1.mem_alloc.banks_16[l].size = local > 0 ? 256 * 1024 : 0;
+	}
+	gus->gf1.mem_alloc.banks_8[0].size = gus->gf1.memory;
+	return 0;		/* some memory were detected */
+}
+
+static int snd_gus_init_dma_irq(struct snd_gus_card * gus, int latches)
+{
+	struct snd_card *card;
+	unsigned long flags;
+	int irq, dma1, dma2;
+	static unsigned char irqs[16] =
+		{0, 0, 1, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7};
+	static unsigned char dmas[8] =
+		{6, 1, 0, 2, 0, 3, 4, 5};
+
+	if (snd_BUG_ON(!gus))
+		return -EINVAL;
+	card = gus->card;
+	if (snd_BUG_ON(!card))
+		return -EINVAL;
+
+	gus->mix_cntrl_reg &= 0xf8;
+	gus->mix_cntrl_reg |= 0x01;	/* disable MIC, LINE IN, enable LINE OUT */
+	if (gus->codec_flag || gus->ess_flag) {
+		gus->mix_cntrl_reg &= ~1;	/* enable LINE IN */
+		gus->mix_cntrl_reg |= 4;	/* enable MIC */
+	}
+	dma1 = gus->gf1.dma1;
+	dma1 = abs(dma1);
+	dma1 = dmas[dma1 & 7];
+	dma2 = gus->gf1.dma2;
+	dma2 = abs(dma2);
+	dma2 = dmas[dma2 & 7];
+	dma1 |= gus->equal_dma ? 0x40 : (dma2 << 3);
+
+	if ((dma1 & 7) == 0 || (dma2 & 7) == 0) {
+		snd_printk(KERN_ERR "Error! DMA isn't defined.\n");
+		return -EINVAL;
+	}
+	irq = gus->gf1.irq;
+	irq = abs(irq);
+	irq = irqs[irq & 0x0f];
+	if (irq == 0) {
+		snd_printk(KERN_ERR "Error! IRQ isn't defined.\n");
+		return -EINVAL;
+	}
+	irq |= 0x40;
+#if 0
+	card->mixer.mix_ctrl_reg |= 0x10;
+#endif
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(5, GUSP(gus, REGCNTRLS));
+	outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+	outb(0x00, GUSP(gus, IRQDMACNTRLREG));
+	outb(0, GUSP(gus, REGCNTRLS));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+	udelay(100);
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+	outb(dma1, GUSP(gus, IRQDMACNTRLREG));
+	if (latches) {
+		outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+		outb(irq, GUSP(gus, IRQDMACNTRLREG));
+	}
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+	udelay(100);
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+	outb(dma1, GUSP(gus, IRQDMACNTRLREG));
+	if (latches) {
+		outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+		outb(irq, GUSP(gus, IRQDMACNTRLREG));
+	}
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+	snd_gf1_delay(gus);
+
+	if (latches)
+		gus->mix_cntrl_reg |= 0x08;	/* enable latches */
+	else
+		gus->mix_cntrl_reg &= ~0x08;	/* disable latches */
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+	outb(0, GUSP(gus, GF1PAGE));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+	return 0;
+}
+
+static int snd_gus_check_version(struct snd_gus_card * gus)
+{
+	unsigned long flags;
+	unsigned char val, rev;
+	struct snd_card *card;
+
+	card = gus->card;
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(0x20, GUSP(gus, REGCNTRLS));
+	val = inb(GUSP(gus, REGCNTRLS));
+	rev = inb(GUSP(gus, BOARDVERSION));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	snd_printdd("GF1 [0x%lx] init - val = 0x%x, rev = 0x%x\n", gus->gf1.port, val, rev);
+	strcpy(card->driver, "GUS");
+	strcpy(card->longname, "Gravis UltraSound Classic (2.4)");
+	if ((val != 255 && (val & 0x06)) || (rev >= 5 && rev != 255)) {
+		if (rev >= 5 && rev <= 9) {
+			gus->ics_flag = 1;
+			if (rev == 5)
+				gus->ics_flipped = 1;
+			card->longname[27] = '3';
+			card->longname[29] = rev == 5 ? '5' : '7';
+		}
+		if (rev >= 10 && rev != 255) {
+			if (rev >= 10 && rev <= 11) {
+				strcpy(card->driver, "GUS MAX");
+				strcpy(card->longname, "Gravis UltraSound MAX");
+				gus->max_flag = 1;
+			} else if (rev == 0x30) {
+				strcpy(card->driver, "GUS ACE");
+				strcpy(card->longname, "Gravis UltraSound Ace");
+				gus->ace_flag = 1;
+			} else if (rev == 0x50) {
+				strcpy(card->driver, "GUS Extreme");
+				strcpy(card->longname, "Gravis UltraSound Extreme");
+				gus->ess_flag = 1;
+			} else {
+				snd_printk(KERN_ERR "unknown GF1 revision number at 0x%lx - 0x%x (0x%x)\n", gus->gf1.port, rev, val);
+				snd_printk(KERN_ERR "  please - report to <perex@perex.cz>\n");
+			}
+		}
+	}
+	strcpy(card->shortname, card->longname);
+	gus->uart_enable = 1;	/* standard GUSes doesn't have midi uart trouble */
+	snd_gus_init_control(gus);
+	return 0;
+}
+
+int snd_gus_initialize(struct snd_gus_card *gus)
+{
+	int err;
+
+	if (!gus->interwave) {
+		if ((err = snd_gus_check_version(gus)) < 0) {
+			snd_printk(KERN_ERR "version check failed\n");
+			return err;
+		}
+		if ((err = snd_gus_detect_memory(gus)) < 0)
+			return err;
+	}
+	if ((err = snd_gus_init_dma_irq(gus, 1)) < 0)
+		return err;
+	snd_gf1_start(gus);
+	gus->initialized = 1;
+	return 0;
+}
+
+  /* gus_io.c */
+EXPORT_SYMBOL(snd_gf1_delay);
+EXPORT_SYMBOL(snd_gf1_write8);
+EXPORT_SYMBOL(snd_gf1_look8);
+EXPORT_SYMBOL(snd_gf1_write16);
+EXPORT_SYMBOL(snd_gf1_look16);
+EXPORT_SYMBOL(snd_gf1_i_write8);
+EXPORT_SYMBOL(snd_gf1_i_look8);
+EXPORT_SYMBOL(snd_gf1_i_look16);
+EXPORT_SYMBOL(snd_gf1_dram_addr);
+EXPORT_SYMBOL(snd_gf1_write_addr);
+EXPORT_SYMBOL(snd_gf1_poke);
+EXPORT_SYMBOL(snd_gf1_peek);
+  /* gus_reset.c */
+EXPORT_SYMBOL(snd_gf1_alloc_voice);
+EXPORT_SYMBOL(snd_gf1_free_voice);
+EXPORT_SYMBOL(snd_gf1_ctrl_stop);
+EXPORT_SYMBOL(snd_gf1_stop_voice);
+  /* gus_mixer.c */
+EXPORT_SYMBOL(snd_gf1_new_mixer);
+  /* gus_pcm.c */
+EXPORT_SYMBOL(snd_gf1_pcm_new);
+  /* gus.c */
+EXPORT_SYMBOL(snd_gus_use_inc);
+EXPORT_SYMBOL(snd_gus_use_dec);
+EXPORT_SYMBOL(snd_gus_create);
+EXPORT_SYMBOL(snd_gus_initialize);
+  /* gus_irq.c */
+EXPORT_SYMBOL(snd_gus_interrupt);
+  /* gus_uart.c */
+EXPORT_SYMBOL(snd_gf1_rawmidi_new);
+  /* gus_dram.c */
+EXPORT_SYMBOL(snd_gus_dram_write);
+EXPORT_SYMBOL(snd_gus_dram_read);
+  /* gus_volume.c */
+EXPORT_SYMBOL(snd_gf1_lvol_to_gvol_raw);
+EXPORT_SYMBOL(snd_gf1_translate_freq);
+  /* gus_mem.c */
+EXPORT_SYMBOL(snd_gf1_mem_alloc);
+EXPORT_SYMBOL(snd_gf1_mem_xfree);
+EXPORT_SYMBOL(snd_gf1_mem_free);
+EXPORT_SYMBOL(snd_gf1_mem_lock);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_gus_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_gus_exit(void)
+{
+}
+
+module_init(alsa_gus_init)
+module_exit(alsa_gus_exit)
diff --git a/linux/sound/isa/gus/gus_mem.c b/linux/sound/isa/gus/gus_mem.c
new file mode 100644
index 0000000..af888a0
--- /dev/null
+++ b/linux/sound/isa/gus/gus_mem.c
@@ -0,0 +1,351 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  GUS's memory allocation routines / bottom layer
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_gf1_mem_info_read(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer);
+#endif
+
+void snd_gf1_mem_lock(struct snd_gf1_mem * alloc, int xup)
+{
+	if (!xup) {
+		mutex_lock(&alloc->memory_mutex);
+	} else {
+		mutex_unlock(&alloc->memory_mutex);
+	}
+}
+
+static struct snd_gf1_mem_block *snd_gf1_mem_xalloc(struct snd_gf1_mem * alloc,
+					       struct snd_gf1_mem_block * block)
+{
+	struct snd_gf1_mem_block *pblock, *nblock;
+
+	nblock = kmalloc(sizeof(struct snd_gf1_mem_block), GFP_KERNEL);
+	if (nblock == NULL)
+		return NULL;
+	*nblock = *block;
+	pblock = alloc->first;
+	while (pblock) {
+		if (pblock->ptr > nblock->ptr) {
+			nblock->prev = pblock->prev;
+			nblock->next = pblock;
+			pblock->prev = nblock;
+			if (pblock == alloc->first)
+				alloc->first = nblock;
+			else
+				nblock->prev->next = nblock;
+			mutex_unlock(&alloc->memory_mutex);
+			return NULL;
+		}
+		pblock = pblock->next;
+	}
+	nblock->next = NULL;
+	if (alloc->last == NULL) {
+		nblock->prev = NULL;
+		alloc->first = alloc->last = nblock;
+	} else {
+		nblock->prev = alloc->last;
+		alloc->last->next = nblock;
+		alloc->last = nblock;
+	}
+	return nblock;
+}
+
+int snd_gf1_mem_xfree(struct snd_gf1_mem * alloc, struct snd_gf1_mem_block * block)
+{
+	if (block->share) {	/* ok.. shared block */
+		block->share--;
+		mutex_unlock(&alloc->memory_mutex);
+		return 0;
+	}
+	if (alloc->first == block) {
+		alloc->first = block->next;
+		if (block->next)
+			block->next->prev = NULL;
+	} else {
+		block->prev->next = block->next;
+		if (block->next)
+			block->next->prev = block->prev;
+	}
+	if (alloc->last == block) {
+		alloc->last = block->prev;
+		if (block->prev)
+			block->prev->next = NULL;
+	} else {
+		block->next->prev = block->prev;
+		if (block->prev)
+			block->prev->next = block->next;
+	}
+	kfree(block->name);
+	kfree(block);
+	return 0;
+}
+
+static struct snd_gf1_mem_block *snd_gf1_mem_look(struct snd_gf1_mem * alloc,
+					     unsigned int address)
+{
+	struct snd_gf1_mem_block *block;
+
+	for (block = alloc->first; block; block = block->next) {
+		if (block->ptr == address) {
+			return block;
+		}
+	}
+	return NULL;
+}
+
+static struct snd_gf1_mem_block *snd_gf1_mem_share(struct snd_gf1_mem * alloc,
+					      unsigned int *share_id)
+{
+	struct snd_gf1_mem_block *block;
+
+	if (!share_id[0] && !share_id[1] &&
+	    !share_id[2] && !share_id[3])
+		return NULL;
+	for (block = alloc->first; block; block = block->next)
+		if (!memcmp(share_id, block->share_id,
+				sizeof(block->share_id)))
+			return block;
+	return NULL;
+}
+
+static int snd_gf1_mem_find(struct snd_gf1_mem * alloc,
+			    struct snd_gf1_mem_block * block,
+			    unsigned int size, int w_16, int align)
+{
+	struct snd_gf1_bank_info *info = w_16 ? alloc->banks_16 : alloc->banks_8;
+	unsigned int idx, boundary;
+	int size1;
+	struct snd_gf1_mem_block *pblock;
+	unsigned int ptr1, ptr2;
+
+	if (w_16 && align < 2)
+		align = 2;
+	block->flags = w_16 ? SNDRV_GF1_MEM_BLOCK_16BIT : 0;
+	block->owner = SNDRV_GF1_MEM_OWNER_DRIVER;
+	block->share = 0;
+	block->share_id[0] = block->share_id[1] =
+	block->share_id[2] = block->share_id[3] = 0;
+	block->name = NULL;
+	block->prev = block->next = NULL;
+	for (pblock = alloc->first, idx = 0; pblock; pblock = pblock->next) {
+		while (pblock->ptr >= (boundary = info[idx].address + info[idx].size))
+			idx++;
+		while (pblock->ptr + pblock->size >= (boundary = info[idx].address + info[idx].size))
+			idx++;
+		ptr2 = boundary;
+		if (pblock->next) {
+			if (pblock->ptr + pblock->size == pblock->next->ptr)
+				continue;
+			if (pblock->next->ptr < boundary)
+				ptr2 = pblock->next->ptr;
+		}
+		ptr1 = ALIGN(pblock->ptr + pblock->size, align);
+		if (ptr1 >= ptr2)
+			continue;
+		size1 = ptr2 - ptr1;
+		if ((int)size <= size1) {
+			block->ptr = ptr1;
+			block->size = size;
+			return 0;
+		}
+	}
+	while (++idx < 4) {
+		if (size <= info[idx].size) {
+			/* I assume that bank address is already aligned.. */
+			block->ptr = info[idx].address;
+			block->size = size;
+			return 0;
+		}
+	}
+	return -ENOMEM;
+}
+
+struct snd_gf1_mem_block *snd_gf1_mem_alloc(struct snd_gf1_mem * alloc, int owner,
+				       char *name, int size, int w_16, int align,
+				       unsigned int *share_id)
+{
+	struct snd_gf1_mem_block block, *nblock;
+
+	snd_gf1_mem_lock(alloc, 0);
+	if (share_id != NULL) {
+		nblock = snd_gf1_mem_share(alloc, share_id);
+		if (nblock != NULL) {
+			if (size != (int)nblock->size) {
+				/* TODO: remove in the future */
+				snd_printk(KERN_ERR "snd_gf1_mem_alloc - share: sizes differ\n");
+				goto __std;
+			}
+			nblock->share++;
+			snd_gf1_mem_lock(alloc, 1);
+			return NULL;
+		}
+	}
+      __std:
+	if (snd_gf1_mem_find(alloc, &block, size, w_16, align) < 0) {
+		snd_gf1_mem_lock(alloc, 1);
+		return NULL;
+	}
+	if (share_id != NULL)
+		memcpy(&block.share_id, share_id, sizeof(block.share_id));
+	block.owner = owner;
+	block.name = kstrdup(name, GFP_KERNEL);
+	nblock = snd_gf1_mem_xalloc(alloc, &block);
+	snd_gf1_mem_lock(alloc, 1);
+	return nblock;
+}
+
+int snd_gf1_mem_free(struct snd_gf1_mem * alloc, unsigned int address)
+{
+	int result;
+	struct snd_gf1_mem_block *block;
+
+	snd_gf1_mem_lock(alloc, 0);
+	if ((block = snd_gf1_mem_look(alloc, address)) != NULL) {
+		result = snd_gf1_mem_xfree(alloc, block);
+		snd_gf1_mem_lock(alloc, 1);
+		return result;
+	}
+	snd_gf1_mem_lock(alloc, 1);
+	return -EINVAL;
+}
+
+int snd_gf1_mem_init(struct snd_gus_card * gus)
+{
+	struct snd_gf1_mem *alloc;
+	struct snd_gf1_mem_block block;
+#ifdef CONFIG_SND_DEBUG
+	struct snd_info_entry *entry;
+#endif
+
+	alloc = &gus->gf1.mem_alloc;
+	mutex_init(&alloc->memory_mutex);
+	alloc->first = alloc->last = NULL;
+	if (!gus->gf1.memory)
+		return 0;
+
+	memset(&block, 0, sizeof(block));
+	block.owner = SNDRV_GF1_MEM_OWNER_DRIVER;
+	if (gus->gf1.enh_mode) {
+		block.ptr = 0;
+		block.size = 1024;
+		block.name = kstrdup("InterWave LFOs", GFP_KERNEL);
+		if (snd_gf1_mem_xalloc(alloc, &block) == NULL)
+			return -ENOMEM;
+	}
+	block.ptr = gus->gf1.default_voice_address;
+	block.size = 4;
+	block.name = kstrdup("Voice default (NULL's)", GFP_KERNEL);
+	if (snd_gf1_mem_xalloc(alloc, &block) == NULL)
+		return -ENOMEM;
+#ifdef CONFIG_SND_DEBUG
+	if (! snd_card_proc_new(gus->card, "gusmem", &entry))
+		snd_info_set_text_ops(entry, gus, snd_gf1_mem_info_read);
+#endif
+	return 0;
+}
+
+int snd_gf1_mem_done(struct snd_gus_card * gus)
+{
+	struct snd_gf1_mem *alloc;
+	struct snd_gf1_mem_block *block, *nblock;
+
+	alloc = &gus->gf1.mem_alloc;
+	block = alloc->first;
+	while (block) {
+		nblock = block->next;
+		snd_gf1_mem_xfree(alloc, block);
+		block = nblock;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_gf1_mem_info_read(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer)
+{
+	struct snd_gus_card *gus;
+	struct snd_gf1_mem *alloc;
+	struct snd_gf1_mem_block *block;
+	unsigned int total, used;
+	int i;
+
+	gus = entry->private_data;
+	alloc = &gus->gf1.mem_alloc;
+	mutex_lock(&alloc->memory_mutex);
+	snd_iprintf(buffer, "8-bit banks       : \n    ");
+	for (i = 0; i < 4; i++)
+		snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_8[i].address, alloc->banks_8[i].size >> 10, i + 1 < 4 ? "," : "");
+	snd_iprintf(buffer, "\n"
+		    "16-bit banks      : \n    ");
+	for (i = total = 0; i < 4; i++) {
+		snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_16[i].address, alloc->banks_16[i].size >> 10, i + 1 < 4 ? "," : "");
+		total += alloc->banks_16[i].size;
+	}
+	snd_iprintf(buffer, "\n");
+	used = 0;
+	for (block = alloc->first, i = 0; block; block = block->next, i++) {
+		used += block->size;
+		snd_iprintf(buffer, "Block %i at 0x%lx onboard 0x%x size %i (0x%x):\n", i, (long) block, block->ptr, block->size, block->size);
+		if (block->share ||
+		    block->share_id[0] || block->share_id[1] ||
+		    block->share_id[2] || block->share_id[3])
+			snd_iprintf(buffer, "  Share           : %i [id0 0x%x] [id1 0x%x] [id2 0x%x] [id3 0x%x]\n",
+				block->share,
+				block->share_id[0], block->share_id[1],
+				block->share_id[2], block->share_id[3]);
+		snd_iprintf(buffer, "  Flags           :%s\n",
+		block->flags & SNDRV_GF1_MEM_BLOCK_16BIT ? " 16-bit" : "");
+		snd_iprintf(buffer, "  Owner           : ");
+		switch (block->owner) {
+		case SNDRV_GF1_MEM_OWNER_DRIVER:
+			snd_iprintf(buffer, "driver - %s\n", block->name);
+			break;
+		case SNDRV_GF1_MEM_OWNER_WAVE_SIMPLE:
+			snd_iprintf(buffer, "SIMPLE wave\n");
+			break;
+		case SNDRV_GF1_MEM_OWNER_WAVE_GF1:
+			snd_iprintf(buffer, "GF1 wave\n");
+			break;
+		case SNDRV_GF1_MEM_OWNER_WAVE_IWFFFF:
+			snd_iprintf(buffer, "IWFFFF wave\n");
+			break;
+		default:
+			snd_iprintf(buffer, "unknown\n");
+		}
+	}
+	snd_iprintf(buffer, "  Total: memory = %i, used = %i, free = %i\n",
+		    total, used, total - used);
+	mutex_unlock(&alloc->memory_mutex);
+#if 0
+	ultra_iprintf(buffer, "  Verify: free = %i, max 8-bit block = %i, max 16-bit block = %i\n",
+		      ultra_memory_free_size(card, &card->gf1.mem_alloc),
+		  ultra_memory_free_block(card, &card->gf1.mem_alloc, 0),
+		 ultra_memory_free_block(card, &card->gf1.mem_alloc, 1));
+#endif
+}
+#endif
diff --git a/linux/sound/isa/gus/gus_mem_proc.c b/linux/sound/isa/gus/gus_mem_proc.c
new file mode 100644
index 0000000..2ccb3fa
--- /dev/null
+++ b/linux/sound/isa/gus/gus_mem_proc.c
@@ -0,0 +1,102 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  GUS's memory access via proc filesystem
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/info.h>
+
+struct gus_proc_private {
+	int rom;		/* data are in ROM */
+	unsigned int address;
+	unsigned int size;
+	struct snd_gus_card * gus;
+};
+
+static ssize_t snd_gf1_mem_proc_dump(struct snd_info_entry *entry,
+				     void *file_private_data,
+				     struct file *file, char __user *buf,
+				     size_t count, loff_t pos)
+{
+	struct gus_proc_private *priv = entry->private_data;
+	struct snd_gus_card *gus = priv->gus;
+	int err;
+
+	err = snd_gus_dram_read(gus, buf, pos, count, priv->rom);
+	if (err < 0)
+		return err;
+	return count;
+}			
+
+static void snd_gf1_mem_proc_free(struct snd_info_entry *entry)
+{
+	struct gus_proc_private *priv = entry->private_data;
+	kfree(priv);
+}
+
+static struct snd_info_entry_ops snd_gf1_mem_proc_ops = {
+	.read = snd_gf1_mem_proc_dump,
+};
+
+int snd_gf1_mem_proc_init(struct snd_gus_card * gus)
+{
+	int idx;
+	char name[16];
+	struct gus_proc_private *priv;
+	struct snd_info_entry *entry;
+
+	for (idx = 0; idx < 4; idx++) {
+		if (gus->gf1.mem_alloc.banks_8[idx].size > 0) {
+			priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+			if (priv == NULL)
+				return -ENOMEM;
+			priv->gus = gus;
+			sprintf(name, "gus-ram-%i", idx);
+			if (! snd_card_proc_new(gus->card, name, &entry)) {
+				entry->content = SNDRV_INFO_CONTENT_DATA;
+				entry->private_data = priv;
+				entry->private_free = snd_gf1_mem_proc_free;
+				entry->c.ops = &snd_gf1_mem_proc_ops;
+				priv->address = gus->gf1.mem_alloc.banks_8[idx].address;
+				priv->size = entry->size = gus->gf1.mem_alloc.banks_8[idx].size;
+			}
+		}
+	}
+	for (idx = 0; idx < 4; idx++) {
+		if (gus->gf1.rom_present & (1 << idx)) {
+			priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+			if (priv == NULL)
+				return -ENOMEM;
+			priv->rom = 1;
+			priv->gus = gus;
+			sprintf(name, "gus-rom-%i", idx);
+			if (! snd_card_proc_new(gus->card, name, &entry)) {
+				entry->content = SNDRV_INFO_CONTENT_DATA;
+				entry->private_data = priv;
+				entry->private_free = snd_gf1_mem_proc_free;
+				entry->c.ops = &snd_gf1_mem_proc_ops;
+				priv->address = idx * 4096 * 1024;
+				priv->size = entry->size = gus->gf1.rom_memory;
+			}
+		}
+	}
+	return 0;
+}
diff --git a/linux/sound/isa/gus/gus_mixer.c b/linux/sound/isa/gus/gus_mixer.c
new file mode 100644
index 0000000..0dd4341
--- /dev/null
+++ b/linux/sound/isa/gus/gus_mixer.c
@@ -0,0 +1,193 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for control of ICS 2101 chip and "mixer" in GF1 chip
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/gus.h>
+
+/*
+ *
+ */
+
+#define GF1_SINGLE(xname, xindex, shift, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_gf1_info_single, \
+  .get = snd_gf1_get_single, .put = snd_gf1_put_single, \
+  .private_value = shift | (invert << 8) }
+
+#define snd_gf1_info_single	snd_ctl_boolean_mono_info
+
+static int snd_gf1_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+	int shift = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value >> 8) & 1;
+	
+	ucontrol->value.integer.value[0] = (gus->mix_cntrl_reg >> shift) & 1;
+	if (invert)
+		ucontrol->value.integer.value[0] ^= 1;
+	return 0;
+}
+
+static int snd_gf1_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int shift = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value >> 8) & 1;
+	int change;
+	unsigned char oval, nval;
+	
+	nval = ucontrol->value.integer.value[0] & 1;
+	if (invert)
+		nval ^= 1;
+	nval <<= shift;
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	oval = gus->mix_cntrl_reg;
+	nval = (oval & ~(1 << shift)) | nval;
+	change = nval != oval;
+	outb(gus->mix_cntrl_reg = nval, GUSP(gus, MIXCNTRLREG));
+	outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return change;
+}
+
+#define ICS_DOUBLE(xname, xindex, addr) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_ics_info_double, \
+  .get = snd_ics_get_double, .put = snd_ics_put_double, \
+  .private_value = addr }
+
+static int snd_ics_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 127;
+	return 0;
+}
+
+static int snd_ics_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int addr = kcontrol->private_value & 0xff;
+	unsigned char left, right;
+	
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	left = gus->gf1.ics_regs[addr][0];
+	right = gus->gf1.ics_regs[addr][1];
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	ucontrol->value.integer.value[0] = left & 127;
+	ucontrol->value.integer.value[1] = right & 127;
+	return 0;
+}
+
+static int snd_ics_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int addr = kcontrol->private_value & 0xff;
+	int change;
+	unsigned char val1, val2, oval1, oval2, tmp;
+	
+	val1 = ucontrol->value.integer.value[0] & 127;
+	val2 = ucontrol->value.integer.value[1] & 127;
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	oval1 = gus->gf1.ics_regs[addr][0];
+	oval2 = gus->gf1.ics_regs[addr][1];
+	change = val1 != oval1 || val2 != oval2;
+	gus->gf1.ics_regs[addr][0] = val1;
+	gus->gf1.ics_regs[addr][1] = val2;
+	if (gus->ics_flag && gus->ics_flipped &&
+	    (addr == SNDRV_ICS_GF1_DEV || addr == SNDRV_ICS_MASTER_DEV)) {
+		tmp = val1;
+		val1 = val2;
+		val2 = tmp;
+	}
+	addr <<= 3;
+	outb(addr | 0, GUSP(gus, MIXCNTRLPORT));
+	outb(1, GUSP(gus, MIXDATAPORT));
+	outb(addr | 2, GUSP(gus, MIXCNTRLPORT));
+	outb((unsigned char) val1, GUSP(gus, MIXDATAPORT));
+	outb(addr | 1, GUSP(gus, MIXCNTRLPORT));
+	outb(2, GUSP(gus, MIXDATAPORT));
+	outb(addr | 3, GUSP(gus, MIXCNTRLPORT));
+	outb((unsigned char) val2, GUSP(gus, MIXDATAPORT));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_gf1_controls[] = {
+GF1_SINGLE("Master Playback Switch", 0, 1, 1),
+GF1_SINGLE("Line Switch", 0, 0, 1),
+GF1_SINGLE("Mic Switch", 0, 2, 0)
+};
+
+static struct snd_kcontrol_new snd_ics_controls[] = {
+GF1_SINGLE("Master Playback Switch", 0, 1, 1),
+ICS_DOUBLE("Master Playback Volume", 0, SNDRV_ICS_MASTER_DEV),
+ICS_DOUBLE("Synth Playback Volume", 0, SNDRV_ICS_GF1_DEV),
+GF1_SINGLE("Line Switch", 0, 0, 1),
+ICS_DOUBLE("Line Playback Volume", 0, SNDRV_ICS_LINE_DEV),
+GF1_SINGLE("Mic Switch", 0, 2, 0),
+ICS_DOUBLE("Mic Playback Volume", 0, SNDRV_ICS_MIC_DEV),
+ICS_DOUBLE("CD Playback Volume", 0, SNDRV_ICS_CD_DEV)
+};
+
+int snd_gf1_new_mixer(struct snd_gus_card * gus)
+{
+	struct snd_card *card;
+	unsigned int idx, max;
+	int err;
+
+	if (snd_BUG_ON(!gus))
+		return -EINVAL;
+	card = gus->card;
+	if (snd_BUG_ON(!card))
+		return -EINVAL;
+
+	if (gus->ics_flag)
+		snd_component_add(card, "ICS2101");
+	if (card->mixername[0] == '\0') {
+		strcpy(card->mixername, gus->ics_flag ? "GF1,ICS2101" : "GF1");
+	} else {
+		if (gus->ics_flag)
+			strcat(card->mixername, ",ICS2101");
+		strcat(card->mixername, ",GF1");
+	}
+
+	if (!gus->ics_flag) {
+		max = gus->ess_flag ? 1 : ARRAY_SIZE(snd_gf1_controls);
+		for (idx = 0; idx < max; idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_gf1_controls[idx], gus))) < 0)
+				return err;
+		}
+	} else {
+		for (idx = 0; idx < ARRAY_SIZE(snd_ics_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ics_controls[idx], gus))) < 0)
+				return err;
+		}
+	}
+	return 0;
+}
diff --git a/linux/sound/isa/gus/gus_pcm.c b/linux/sound/isa/gus/gus_pcm.c
new file mode 100644
index 0000000..2dcf45b
--- /dev/null
+++ b/linux/sound/isa/gus/gus_pcm.c
@@ -0,0 +1,910 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for control of GF1 chip (PCM things)
+ *
+ *  InterWave chips supports interleaved DMA, but this feature isn't used in
+ *  this code.
+ *  
+ *  This code emulates autoinit DMA transfer for playback, recording by GF1
+ *  chip doesn't support autoinit DMA.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/dma.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/gus.h>
+#include <sound/pcm_params.h>
+#include "gus_tables.h"
+
+/* maximum rate */
+
+#define SNDRV_GF1_PCM_RATE		48000
+
+#define SNDRV_GF1_PCM_PFLG_NONE		0
+#define SNDRV_GF1_PCM_PFLG_ACTIVE	(1<<0)
+#define SNDRV_GF1_PCM_PFLG_NEUTRAL	(2<<0)
+
+struct gus_pcm_private {
+	struct snd_gus_card * gus;
+	struct snd_pcm_substream *substream;
+	spinlock_t lock;
+	unsigned int voices;
+	struct snd_gus_voice *pvoices[2];
+	unsigned int memory;
+	unsigned short flags;
+	unsigned char voice_ctrl, ramp_ctrl;
+	unsigned int bpos;
+	unsigned int blocks;
+	unsigned int block_size;
+	unsigned int dma_size;
+	wait_queue_head_t sleep;
+	atomic_t dma_count;
+	int final_volume;
+};
+
+static int snd_gf1_pcm_use_dma = 1;
+
+static void snd_gf1_pcm_block_change_ack(struct snd_gus_card * gus, void *private_data)
+{
+	struct gus_pcm_private *pcmp = private_data;
+
+	if (pcmp) {
+		atomic_dec(&pcmp->dma_count);
+		wake_up(&pcmp->sleep);
+	}
+}
+
+static int snd_gf1_pcm_block_change(struct snd_pcm_substream *substream,
+				    unsigned int offset,
+				    unsigned int addr,
+				    unsigned int count)
+{
+	struct snd_gf1_dma_block block;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+
+	count += offset & 31;
+	offset &= ~31;
+	/*
+	snd_printk(KERN_DEBUG "block change - offset = 0x%x, count = 0x%x\n",
+		   offset, count);
+	*/
+	memset(&block, 0, sizeof(block));
+	block.cmd = SNDRV_GF1_DMA_IRQ;
+	if (snd_pcm_format_unsigned(runtime->format))
+		block.cmd |= SNDRV_GF1_DMA_UNSIGNED;
+	if (snd_pcm_format_width(runtime->format) == 16)
+		block.cmd |= SNDRV_GF1_DMA_16BIT;
+	block.addr = addr & ~31;
+	block.buffer = runtime->dma_area + offset;
+	block.buf_addr = runtime->dma_addr + offset;
+	block.count = count;
+	block.private_data = pcmp;
+	block.ack = snd_gf1_pcm_block_change_ack;
+	if (!snd_gf1_dma_transfer_block(pcmp->gus, &block, 0, 0))
+		atomic_inc(&pcmp->dma_count);
+	return 0;
+}
+
+static void snd_gf1_pcm_trigger_up(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	struct snd_gus_card * gus = pcmp->gus;
+	unsigned long flags;
+	unsigned char voice_ctrl, ramp_ctrl;
+	unsigned short rate;
+	unsigned int curr, begin, end;
+	unsigned short vol;
+	unsigned char pan;
+	unsigned int voice;
+
+	spin_lock_irqsave(&pcmp->lock, flags);
+	if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) {
+		spin_unlock_irqrestore(&pcmp->lock, flags);
+		return;
+	}
+	pcmp->flags |= SNDRV_GF1_PCM_PFLG_ACTIVE;
+	pcmp->final_volume = 0;
+	spin_unlock_irqrestore(&pcmp->lock, flags);
+	rate = snd_gf1_translate_freq(gus, runtime->rate << 4);
+	/* enable WAVE IRQ */
+	voice_ctrl = snd_pcm_format_width(runtime->format) == 16 ? 0x24 : 0x20;
+	/* enable RAMP IRQ + rollover */
+	ramp_ctrl = 0x24;
+	if (pcmp->blocks == 1) {
+		voice_ctrl |= 0x08;	/* loop enable */
+		ramp_ctrl &= ~0x04;	/* disable rollover */
+	}
+	for (voice = 0; voice < pcmp->voices; voice++) {
+		begin = pcmp->memory + voice * (pcmp->dma_size / runtime->channels);
+		curr = begin + (pcmp->bpos * pcmp->block_size) / runtime->channels;
+		end = curr + (pcmp->block_size / runtime->channels);
+		end -= snd_pcm_format_width(runtime->format) == 16 ? 2 : 1;
+		/*
+		snd_printk(KERN_DEBUG "init: curr=0x%x, begin=0x%x, end=0x%x, "
+			   "ctrl=0x%x, ramp=0x%x, rate=0x%x\n",
+			   curr, begin, end, voice_ctrl, ramp_ctrl, rate);
+		*/
+		pan = runtime->channels == 2 ? (!voice ? 1 : 14) : 8;
+		vol = !voice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
+		spin_lock_irqsave(&gus->reg_lock, flags);
+		snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, pan);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, rate);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, begin << 4, voice_ctrl & 4);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, curr << 4, voice_ctrl & 4);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME << 4);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0x2f);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, vol >> 8);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+		if (!gus->gf1.enh_mode) {
+			snd_gf1_delay(gus);
+			snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+		}
+		spin_unlock_irqrestore(&gus->reg_lock, flags);
+	}
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	for (voice = 0; voice < pcmp->voices; voice++) {
+		snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
+		if (gus->gf1.enh_mode)
+			snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, 0x00);	/* deactivate voice */
+		snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+		voice_ctrl &= ~0x20;
+	}
+	voice_ctrl |= 0x20;
+	if (!gus->gf1.enh_mode) {
+		snd_gf1_delay(gus);
+		for (voice = 0; voice < pcmp->voices; voice++) {
+			snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number);
+			snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+			voice_ctrl &= ~0x20;	/* disable IRQ for next voice */
+		}
+	}
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static void snd_gf1_pcm_interrupt_wave(struct snd_gus_card * gus,
+				       struct snd_gus_voice *pvoice)
+{
+	struct gus_pcm_private * pcmp;
+	struct snd_pcm_runtime *runtime;
+	unsigned char voice_ctrl, ramp_ctrl;
+	unsigned int idx;
+	unsigned int end, step;
+
+	if (!pvoice->private_data) {
+		snd_printd("snd_gf1_pcm: unknown wave irq?\n");
+		snd_gf1_smart_stop_voice(gus, pvoice->number);
+		return;
+	}
+	pcmp = pvoice->private_data;
+	if (pcmp == NULL) {
+		snd_printd("snd_gf1_pcm: unknown wave irq?\n");
+		snd_gf1_smart_stop_voice(gus, pvoice->number);
+		return;
+	}		
+	gus = pcmp->gus;
+	runtime = pcmp->substream->runtime;
+
+	spin_lock(&gus->reg_lock);
+	snd_gf1_select_voice(gus, pvoice->number);
+	voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & ~0x8b;
+	ramp_ctrl = (snd_gf1_read8(gus, SNDRV_GF1_VB_VOLUME_CONTROL) & ~0xa4) | 0x03;
+#if 0
+	snd_gf1_select_voice(gus, pvoice->number);
+	printk(KERN_DEBUG "position = 0x%x\n",
+	       (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4));
+	snd_gf1_select_voice(gus, pcmp->pvoices[1]->number);
+	printk(KERN_DEBUG "position = 0x%x\n",
+	       (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4));
+	snd_gf1_select_voice(gus, pvoice->number);
+#endif
+	pcmp->bpos++;
+	pcmp->bpos %= pcmp->blocks;
+	if (pcmp->bpos + 1 >= pcmp->blocks) {	/* last block? */
+		voice_ctrl |= 0x08;	/* enable loop */
+	} else {
+		ramp_ctrl |= 0x04;	/* enable rollover */
+	}
+	end = pcmp->memory + (((pcmp->bpos + 1) * pcmp->block_size) / runtime->channels);
+	end -= voice_ctrl & 4 ? 2 : 1;
+	step = pcmp->dma_size / runtime->channels;
+	voice_ctrl |= 0x20;
+	if (!pcmp->final_volume) {
+		ramp_ctrl |= 0x20;
+		ramp_ctrl &= ~0x03;
+	}
+	for (idx = 0; idx < pcmp->voices; idx++, end += step) {
+		snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+		voice_ctrl &= ~0x20;
+	}
+	if (!gus->gf1.enh_mode) {
+		snd_gf1_delay(gus);
+		voice_ctrl |= 0x20;
+		for (idx = 0; idx < pcmp->voices; idx++) {
+			snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number);
+			snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl);
+			snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl);
+			voice_ctrl &= ~0x20;
+		}
+	}
+	spin_unlock(&gus->reg_lock);
+
+	snd_pcm_period_elapsed(pcmp->substream);
+#if 0
+	if ((runtime->flags & SNDRV_PCM_FLG_MMAP) &&
+	    *runtime->state == SNDRV_PCM_STATE_RUNNING) {
+		end = pcmp->bpos * pcmp->block_size;
+		if (runtime->channels > 1) {
+			snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + (end / 2), pcmp->block_size / 2);
+			snd_gf1_pcm_block_change(pcmp->substream, end + (pcmp->block_size / 2), pcmp->memory + (pcmp->dma_size / 2) + (end / 2), pcmp->block_size / 2);
+		} else {
+			snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + end, pcmp->block_size);
+		}
+	}
+#endif
+}
+
+static void snd_gf1_pcm_interrupt_volume(struct snd_gus_card * gus,
+					 struct snd_gus_voice * pvoice)
+{
+	unsigned short vol;
+	int cvoice;
+	struct gus_pcm_private *pcmp = pvoice->private_data;
+
+	/* stop ramp, but leave rollover bit untouched */
+	spin_lock(&gus->reg_lock);
+	snd_gf1_select_voice(gus, pvoice->number);
+	snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+	spin_unlock(&gus->reg_lock);
+	if (pcmp == NULL)
+		return;
+	/* are we active? */
+	if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE))
+		return;
+	/* load real volume - better precision */
+	cvoice = pcmp->pvoices[0] == pvoice ? 0 : 1;
+	if (pcmp->substream == NULL)
+		return;
+	vol = !cvoice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
+	spin_lock(&gus->reg_lock);
+	snd_gf1_select_voice(gus, pvoice->number);
+	snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol);
+	pcmp->final_volume = 1;
+	spin_unlock(&gus->reg_lock);
+}
+
+static void snd_gf1_pcm_volume_change(struct snd_gus_card * gus)
+{
+}
+
+static int snd_gf1_pcm_poke_block(struct snd_gus_card *gus, unsigned char *buf,
+				  unsigned int pos, unsigned int count,
+				  int w16, int invert)
+{
+	unsigned int len;
+	unsigned long flags;
+
+	/*
+	printk(KERN_DEBUG
+	       "poke block; buf = 0x%x, pos = %i, count = %i, port = 0x%x\n",
+	       (int)buf, pos, count, gus->gf1.port);
+	*/
+	while (count > 0) {
+		len = count;
+		if (len > 512)		/* limit, to allow IRQ */
+			len = 512;
+		count -= len;
+		if (gus->interwave) {
+			spin_lock_irqsave(&gus->reg_lock, flags);
+			snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01 | (invert ? 0x08 : 0x00));
+			snd_gf1_dram_addr(gus, pos);
+			if (w16) {
+				outb(SNDRV_GF1_GW_DRAM_IO16, GUSP(gus, GF1REGSEL));
+				outsw(GUSP(gus, GF1DATALOW), buf, len >> 1);
+			} else {
+				outsb(GUSP(gus, DRAM), buf, len);
+			}
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+			buf += 512;
+			pos += 512;
+		} else {
+			invert = invert ? 0x80 : 0x00;
+			if (w16) {
+				len >>= 1;
+				while (len--) {
+					snd_gf1_poke(gus, pos++, *buf++);
+					snd_gf1_poke(gus, pos++, *buf++ ^ invert);
+				}
+			} else {
+				while (len--)
+					snd_gf1_poke(gus, pos++, *buf++ ^ invert);
+			}
+		}
+		if (count > 0 && !in_interrupt()) {
+			schedule_timeout_interruptible(1);
+			if (signal_pending(current))
+				return -EAGAIN;
+		}
+	}
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_copy(struct snd_pcm_substream *substream,
+				     int voice,
+				     snd_pcm_uframes_t pos,
+				     void __user *src,
+				     snd_pcm_uframes_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	unsigned int bpos, len;
+	
+	bpos = samples_to_bytes(runtime, pos) + (voice * (pcmp->dma_size / 2));
+	len = samples_to_bytes(runtime, count);
+	if (snd_BUG_ON(bpos > pcmp->dma_size))
+		return -EIO;
+	if (snd_BUG_ON(bpos + len > pcmp->dma_size))
+		return -EIO;
+	if (copy_from_user(runtime->dma_area + bpos, src, len))
+		return -EFAULT;
+	if (snd_gf1_pcm_use_dma && len > 32) {
+		return snd_gf1_pcm_block_change(substream, bpos, pcmp->memory + bpos, len);
+	} else {
+		struct snd_gus_card *gus = pcmp->gus;
+		int err, w16, invert;
+
+		w16 = (snd_pcm_format_width(runtime->format) == 16);
+		invert = snd_pcm_format_unsigned(runtime->format);
+		if ((err = snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos, pcmp->memory + bpos, len, w16, invert)) < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_silence(struct snd_pcm_substream *substream,
+					int voice,
+					snd_pcm_uframes_t pos,
+					snd_pcm_uframes_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	unsigned int bpos, len;
+	
+	bpos = samples_to_bytes(runtime, pos) + (voice * (pcmp->dma_size / 2));
+	len = samples_to_bytes(runtime, count);
+	if (snd_BUG_ON(bpos > pcmp->dma_size))
+		return -EIO;
+	if (snd_BUG_ON(bpos + len > pcmp->dma_size))
+		return -EIO;
+	snd_pcm_format_set_silence(runtime->format, runtime->dma_area + bpos, count);
+	if (snd_gf1_pcm_use_dma && len > 32) {
+		return snd_gf1_pcm_block_change(substream, bpos, pcmp->memory + bpos, len);
+	} else {
+		struct snd_gus_card *gus = pcmp->gus;
+		int err, w16, invert;
+
+		w16 = (snd_pcm_format_width(runtime->format) == 16);
+		invert = snd_pcm_format_unsigned(runtime->format);
+		if ((err = snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos, pcmp->memory + bpos, len, w16, invert)) < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_hw_params(struct snd_pcm_substream *substream,
+					  struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	int err;
+	
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	if (err > 0) {	/* change */
+		struct snd_gf1_mem_block *block;
+		if (pcmp->memory > 0) {
+			snd_gf1_mem_free(&gus->gf1.mem_alloc, pcmp->memory);
+			pcmp->memory = 0;
+		}
+		if ((block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc,
+		                               SNDRV_GF1_MEM_OWNER_DRIVER,
+					       "GF1 PCM",
+		                               runtime->dma_bytes, 1, 32,
+		                               NULL)) == NULL)
+			return -ENOMEM;
+		pcmp->memory = block->ptr;
+	}
+	pcmp->voices = params_channels(hw_params);
+	if (pcmp->pvoices[0] == NULL) {
+		if ((pcmp->pvoices[0] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL)
+			return -ENOMEM;
+		pcmp->pvoices[0]->handler_wave = snd_gf1_pcm_interrupt_wave;
+		pcmp->pvoices[0]->handler_volume = snd_gf1_pcm_interrupt_volume;
+		pcmp->pvoices[0]->volume_change = snd_gf1_pcm_volume_change;
+		pcmp->pvoices[0]->private_data = pcmp;
+	}
+	if (pcmp->voices > 1 && pcmp->pvoices[1] == NULL) {
+		if ((pcmp->pvoices[1] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL)
+			return -ENOMEM;
+		pcmp->pvoices[1]->handler_wave = snd_gf1_pcm_interrupt_wave;
+		pcmp->pvoices[1]->handler_volume = snd_gf1_pcm_interrupt_volume;
+		pcmp->pvoices[1]->volume_change = snd_gf1_pcm_volume_change;
+		pcmp->pvoices[1]->private_data = pcmp;
+	} else if (pcmp->voices == 1) {
+		if (pcmp->pvoices[1]) {
+			snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]);
+			pcmp->pvoices[1] = NULL;
+		}
+	}
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+
+	snd_pcm_lib_free_pages(substream);
+	if (pcmp->pvoices[0]) {
+		snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[0]);
+		pcmp->pvoices[0] = NULL;
+	}
+	if (pcmp->pvoices[1]) {
+		snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]);
+		pcmp->pvoices[1] = NULL;
+	}
+	if (pcmp->memory > 0) {
+		snd_gf1_mem_free(&pcmp->gus->gf1.mem_alloc, pcmp->memory);
+		pcmp->memory = 0;
+	}
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+
+	pcmp->bpos = 0;
+	pcmp->dma_size = snd_pcm_lib_buffer_bytes(substream);
+	pcmp->block_size = snd_pcm_lib_period_bytes(substream);
+	pcmp->blocks = pcmp->dma_size / pcmp->block_size;
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_trigger(struct snd_pcm_substream *substream,
+					int cmd)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	int voice;
+
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		snd_gf1_pcm_trigger_up(substream);
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		spin_lock(&pcmp->lock);
+		pcmp->flags &= ~SNDRV_GF1_PCM_PFLG_ACTIVE;
+		spin_unlock(&pcmp->lock);
+		voice = pcmp->pvoices[0]->number;
+		snd_gf1_stop_voices(gus, voice, voice);
+		if (pcmp->pvoices[1]) {
+			voice = pcmp->pvoices[1]->number;
+			snd_gf1_stop_voices(gus, voice, voice);
+		}
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_gf1_pcm_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	unsigned int pos;
+	unsigned char voice_ctrl;
+
+	pos = 0;
+	spin_lock(&gus->reg_lock);
+	if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) {
+		snd_gf1_select_voice(gus, pcmp->pvoices[0]->number);
+		voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+		pos = (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4) - pcmp->memory;
+		if (substream->runtime->channels > 1)
+			pos <<= 1;
+		pos = bytes_to_frames(runtime, pos);
+	}
+	spin_unlock(&gus->reg_lock);
+	return pos;
+}
+
+static struct snd_ratnum clock = {
+	.num = 9878400/16,
+	.den_min = 2,
+	.den_max = 257,
+	.den_step = 1,
+};
+
+static struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks  = {
+	.nrats = 1,
+	.rats = &clock,
+};
+
+static int snd_gf1_pcm_capture_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+
+	gus->c_dma_size = params_buffer_bytes(hw_params);
+	gus->c_period_size = params_period_bytes(hw_params);
+	gus->c_pos = 0;
+	gus->gf1.pcm_rcntrl_reg = 0x21;		/* IRQ at end, enable & start */
+	if (params_channels(hw_params) > 1)
+		gus->gf1.pcm_rcntrl_reg |= 2;
+	if (gus->gf1.dma2 > 3)
+		gus->gf1.pcm_rcntrl_reg |= 4;
+	if (snd_pcm_format_unsigned(params_format(hw_params)))
+		gus->gf1.pcm_rcntrl_reg |= 0x80;
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_gf1_pcm_capture_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_gf1_pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RECORD_RATE, runtime->rate_den - 2);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0);	/* disable sampling */
+	snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL);	/* Sampling Control Register */
+	snd_dma_program(gus->gf1.dma2, runtime->dma_addr, gus->c_period_size, DMA_MODE_READ);
+	return 0;
+}
+
+static int snd_gf1_pcm_capture_trigger(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	int val;
+	
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		val = gus->gf1.pcm_rcntrl_reg;
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		val = 0;
+	} else {
+		return -EINVAL;
+	}
+
+	spin_lock(&gus->reg_lock);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, val);
+	snd_gf1_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL);
+	spin_unlock(&gus->reg_lock);
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_gf1_pcm_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	int pos = snd_dma_pointer(gus->gf1.dma2, gus->c_period_size);
+	pos = bytes_to_frames(substream->runtime, (gus->c_pos + pos) % gus->c_dma_size);
+	return pos;
+}
+
+static void snd_gf1_pcm_interrupt_dma_read(struct snd_gus_card * gus)
+{
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0);	/* disable sampling */
+	snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL);	/* Sampling Control Register */
+	if (gus->pcm_cap_substream != NULL) {
+		snd_gf1_pcm_capture_prepare(gus->pcm_cap_substream); 
+		snd_gf1_pcm_capture_trigger(gus->pcm_cap_substream, SNDRV_PCM_TRIGGER_START);
+		gus->c_pos += gus->c_period_size;
+		snd_pcm_period_elapsed(gus->pcm_cap_substream);
+	}
+}
+
+static struct snd_pcm_hardware snd_gf1_pcm_playback =
+{
+	.info =			SNDRV_PCM_INFO_NONINTERLEAVED,
+	.formats		= (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+				 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5510,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_gf1_pcm_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		5510,
+	.rate_max =		44100,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static void snd_gf1_pcm_playback_free(struct snd_pcm_runtime *runtime)
+{
+	kfree(runtime->private_data);
+}
+
+static int snd_gf1_pcm_playback_open(struct snd_pcm_substream *substream)
+{
+	struct gus_pcm_private *pcmp;
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	pcmp = kzalloc(sizeof(*pcmp), GFP_KERNEL);
+	if (pcmp == NULL)
+		return -ENOMEM;
+	pcmp->gus = gus;
+	spin_lock_init(&pcmp->lock);
+	init_waitqueue_head(&pcmp->sleep);
+	atomic_set(&pcmp->dma_count, 0);
+
+	runtime->private_data = pcmp;
+	runtime->private_free = snd_gf1_pcm_playback_free;
+
+#if 0
+	printk(KERN_DEBUG "playback.buffer = 0x%lx, gf1.pcm_buffer = 0x%lx\n",
+	       (long) pcm->playback.buffer, (long) gus->gf1.pcm_buffer);
+#endif
+	if ((err = snd_gf1_dma_init(gus)) < 0)
+		return err;
+	pcmp->flags = SNDRV_GF1_PCM_PFLG_NONE;
+	pcmp->substream = substream;
+	runtime->hw = snd_gf1_pcm_playback;
+	snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.period_bytes_max);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64);
+	return 0;
+}
+
+static int snd_gf1_pcm_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct gus_pcm_private *pcmp = runtime->private_data;
+	
+	if (!wait_event_timeout(pcmp->sleep, (atomic_read(&pcmp->dma_count) <= 0), 2*HZ))
+		snd_printk(KERN_ERR "gf1 pcm - serious DMA problem\n");
+
+	snd_gf1_dma_done(gus);	
+	return 0;
+}
+
+static int snd_gf1_pcm_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+
+	gus->gf1.interrupt_handler_dma_read = snd_gf1_pcm_interrupt_dma_read;
+	gus->pcm_cap_substream = substream;
+	substream->runtime->hw = snd_gf1_pcm_capture;
+	snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.period_bytes_max);
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &hw_constraints_clocks);
+	return 0;
+}
+
+static int snd_gf1_pcm_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_gus_card *gus = snd_pcm_substream_chip(substream);
+
+	gus->pcm_cap_substream = NULL;
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_READ);
+	return 0;
+}
+
+static int snd_gf1_pcm_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 127;
+	return 0;
+}
+
+static int snd_gf1_pcm_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&gus->pcm_volume_level_lock, flags);
+	ucontrol->value.integer.value[0] = gus->gf1.pcm_volume_level_left1;
+	ucontrol->value.integer.value[1] = gus->gf1.pcm_volume_level_right1;
+	spin_unlock_irqrestore(&gus->pcm_volume_level_lock, flags);
+	return 0;
+}
+
+static int snd_gf1_pcm_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned int idx;
+	unsigned short val1, val2, vol;
+	struct gus_pcm_private *pcmp;
+	struct snd_gus_voice *pvoice;
+	
+	val1 = ucontrol->value.integer.value[0] & 127;
+	val2 = ucontrol->value.integer.value[1] & 127;
+	spin_lock_irqsave(&gus->pcm_volume_level_lock, flags);
+	change = val1 != gus->gf1.pcm_volume_level_left1 ||
+	         val2 != gus->gf1.pcm_volume_level_right1;
+	gus->gf1.pcm_volume_level_left1 = val1;
+	gus->gf1.pcm_volume_level_right1 = val2;
+	gus->gf1.pcm_volume_level_left = snd_gf1_lvol_to_gvol_raw(val1 << 9) << 4;
+	gus->gf1.pcm_volume_level_right = snd_gf1_lvol_to_gvol_raw(val2 << 9) << 4;
+	spin_unlock_irqrestore(&gus->pcm_volume_level_lock, flags);
+	/* are we active? */
+	spin_lock_irqsave(&gus->voice_alloc, flags);
+	for (idx = 0; idx < 32; idx++) {
+		pvoice = &gus->gf1.voices[idx];
+		if (!pvoice->pcm)
+			continue;
+		pcmp = pvoice->private_data;
+		if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE))
+			continue;
+		/* load real volume - better precision */
+		spin_lock(&gus->reg_lock);
+		snd_gf1_select_voice(gus, pvoice->number);
+		snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+		vol = pvoice == pcmp->pvoices[0] ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right;
+		snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol);
+		pcmp->final_volume = 1;
+		spin_unlock(&gus->reg_lock);
+	}
+	spin_unlock_irqrestore(&gus->voice_alloc, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_gf1_pcm_volume_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PCM Playback Volume",
+	.info = snd_gf1_pcm_volume_info,
+	.get = snd_gf1_pcm_volume_get,
+	.put = snd_gf1_pcm_volume_put
+};
+
+static struct snd_kcontrol_new snd_gf1_pcm_volume_control1 =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "GPCM Playback Volume",
+	.info = snd_gf1_pcm_volume_info,
+	.get = snd_gf1_pcm_volume_get,
+	.put = snd_gf1_pcm_volume_put
+};
+
+static struct snd_pcm_ops snd_gf1_pcm_playback_ops = {
+	.open =		snd_gf1_pcm_playback_open,
+	.close =	snd_gf1_pcm_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_gf1_pcm_playback_hw_params,
+	.hw_free =	snd_gf1_pcm_playback_hw_free,
+	.prepare =	snd_gf1_pcm_playback_prepare,
+	.trigger =	snd_gf1_pcm_playback_trigger,
+	.pointer =	snd_gf1_pcm_playback_pointer,
+	.copy =		snd_gf1_pcm_playback_copy,
+	.silence =	snd_gf1_pcm_playback_silence,
+};
+
+static struct snd_pcm_ops snd_gf1_pcm_capture_ops = {
+	.open =		snd_gf1_pcm_capture_open,
+	.close =	snd_gf1_pcm_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_gf1_pcm_capture_hw_params,
+	.hw_free =	snd_gf1_pcm_capture_hw_free,
+	.prepare =	snd_gf1_pcm_capture_prepare,
+	.trigger =	snd_gf1_pcm_capture_trigger,
+	.pointer =	snd_gf1_pcm_capture_pointer,
+};
+
+int snd_gf1_pcm_new(struct snd_gus_card * gus, int pcm_dev, int control_index, struct snd_pcm ** rpcm)
+{
+	struct snd_card *card;
+	struct snd_kcontrol *kctl;
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+	int capture, err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	card = gus->card;
+	capture = !gus->interwave && !gus->ess_flag && !gus->ace_flag ? 1 : 0;
+	err = snd_pcm_new(card,
+			  gus->interwave ? "AMD InterWave" : "GF1",
+			  pcm_dev,
+			  gus->gf1.pcm_channels / 2,
+			  capture,
+			  &pcm);
+	if (err < 0)
+		return err;
+	pcm->private_data = gus;
+	/* playback setup */
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_gf1_pcm_playback_ops);
+
+	for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
+		snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, gus->gf1.dma1 > 3 ? 128*1024 : 64*1024);
+	
+	pcm->info_flags = 0;
+	pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+	if (capture) {
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_gf1_pcm_capture_ops);
+		if (gus->gf1.dma2 == gus->gf1.dma1)
+			pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+		snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+					      SNDRV_DMA_TYPE_DEV, snd_dma_isa_data(),
+					      64*1024, gus->gf1.dma2 > 3 ? 128*1024 : 64*1024);
+	}
+	strcpy(pcm->name, pcm->id);
+	if (gus->interwave) {
+		sprintf(pcm->name + strlen(pcm->name), " rev %c", gus->revision + 'A');
+	}
+	strcat(pcm->name, " (synth)");
+	gus->pcm = pcm;
+
+	if (gus->codec_flag)
+		kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control1, gus);
+	else
+		kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control, gus);
+	if ((err = snd_ctl_add(card, kctl)) < 0)
+		return err;
+	kctl->id.index = control_index;
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
diff --git a/linux/sound/isa/gus/gus_reset.c b/linux/sound/isa/gus/gus_reset.c
new file mode 100644
index 0000000..3d1fed0
--- /dev/null
+++ b/linux/sound/isa/gus/gus_reset.c
@@ -0,0 +1,413 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+extern void snd_gf1_timers_init(struct snd_gus_card * gus);
+extern void snd_gf1_timers_done(struct snd_gus_card * gus);
+extern int snd_gf1_synth_init(struct snd_gus_card * gus);
+extern void snd_gf1_synth_done(struct snd_gus_card * gus);
+
+/*
+ *  ok.. default interrupt handlers...
+ */
+
+static void snd_gf1_default_interrupt_handler_midi_out(struct snd_gus_card * gus)
+{
+	snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x20);
+}
+
+static void snd_gf1_default_interrupt_handler_midi_in(struct snd_gus_card * gus)
+{
+	snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x80);
+}
+
+static void snd_gf1_default_interrupt_handler_timer1(struct snd_gus_card * gus)
+{
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~4);
+}
+
+static void snd_gf1_default_interrupt_handler_timer2(struct snd_gus_card * gus)
+{
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~8);
+}
+
+static void snd_gf1_default_interrupt_handler_wave_and_volume(struct snd_gus_card * gus, struct snd_gus_voice * voice)
+{
+	snd_gf1_i_ctrl_stop(gus, 0x00);
+	snd_gf1_i_ctrl_stop(gus, 0x0d);
+}
+
+static void snd_gf1_default_interrupt_handler_dma_write(struct snd_gus_card * gus)
+{
+	snd_gf1_i_write8(gus, 0x41, 0x00);
+}
+
+static void snd_gf1_default_interrupt_handler_dma_read(struct snd_gus_card * gus)
+{
+	snd_gf1_i_write8(gus, 0x49, 0x00);
+}
+
+void snd_gf1_set_default_handlers(struct snd_gus_card * gus, unsigned int what)
+{
+	if (what & SNDRV_GF1_HANDLER_MIDI_OUT)
+		gus->gf1.interrupt_handler_midi_out = snd_gf1_default_interrupt_handler_midi_out;
+	if (what & SNDRV_GF1_HANDLER_MIDI_IN)
+		gus->gf1.interrupt_handler_midi_in = snd_gf1_default_interrupt_handler_midi_in;
+	if (what & SNDRV_GF1_HANDLER_TIMER1)
+		gus->gf1.interrupt_handler_timer1 = snd_gf1_default_interrupt_handler_timer1;
+	if (what & SNDRV_GF1_HANDLER_TIMER2)
+		gus->gf1.interrupt_handler_timer2 = snd_gf1_default_interrupt_handler_timer2;
+	if (what & SNDRV_GF1_HANDLER_VOICE) {
+		struct snd_gus_voice *voice;
+		
+		voice = &gus->gf1.voices[what & 0xffff];
+		voice->handler_wave =
+		voice->handler_volume = snd_gf1_default_interrupt_handler_wave_and_volume;
+		voice->handler_effect = NULL;
+		voice->volume_change = NULL;
+	}
+	if (what & SNDRV_GF1_HANDLER_DMA_WRITE)
+		gus->gf1.interrupt_handler_dma_write = snd_gf1_default_interrupt_handler_dma_write;
+	if (what & SNDRV_GF1_HANDLER_DMA_READ)
+		gus->gf1.interrupt_handler_dma_read = snd_gf1_default_interrupt_handler_dma_read;
+}
+
+/*
+
+ */
+
+static void snd_gf1_clear_regs(struct snd_gus_card * gus)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	inb(GUSP(gus, IRQSTAT));
+	snd_gf1_write8(gus, 0x41, 0);	/* DRAM DMA Control Register */
+	snd_gf1_write8(gus, 0x45, 0);	/* Timer Control */
+	snd_gf1_write8(gus, 0x49, 0);	/* Sampling Control Register */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static void snd_gf1_look_regs(struct snd_gus_card * gus)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_look8(gus, 0x41);	/* DRAM DMA Control Register */
+	snd_gf1_look8(gus, 0x49);	/* Sampling Control Register */
+	inb(GUSP(gus, IRQSTAT));
+	snd_gf1_read8(gus, 0x0f);	/* IRQ Source Register */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+/*
+ *  put selected GF1 voices to initial stage...
+ */
+
+void snd_gf1_smart_stop_voice(struct snd_gus_card * gus, unsigned short voice)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_select_voice(gus, voice);
+#if 0
+	printk(KERN_DEBUG " -%i- smart stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME));
+#endif
+	snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+	snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+void snd_gf1_stop_voice(struct snd_gus_card * gus, unsigned short voice)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_select_voice(gus, voice);
+#if 0
+	printk(KERN_DEBUG " -%i- stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME));
+#endif
+	snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);
+	snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);
+	if (gus->gf1.enh_mode)
+		snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+#if 0
+	snd_gf1_lfo_shutdown(gus, voice, ULTRA_LFO_VIBRATO);
+	snd_gf1_lfo_shutdown(gus, voice, ULTRA_LFO_TREMOLO);
+#endif
+}
+
+static void snd_gf1_clear_voices(struct snd_gus_card * gus, unsigned short v_min,
+				 unsigned short v_max)
+{
+	unsigned long flags;
+	unsigned int daddr;
+	unsigned short i, w_16;
+
+	daddr = gus->gf1.default_voice_address << 4;
+	for (i = v_min; i <= v_max; i++) {
+#if 0
+		if (gus->gf1.syn_voices)
+			gus->gf1.syn_voices[i].flags = ~VFLG_DYNAMIC;
+#endif
+		spin_lock_irqsave(&gus->reg_lock, flags);
+		snd_gf1_select_voice(gus, i);
+		snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL);	/* Voice Control Register = voice stop */
+		snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL);	/* Volume Ramp Control Register = ramp off */
+		if (gus->gf1.enh_mode)
+			snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, gus->gf1.memory ? 0x02 : 0x82);	/* Deactivate voice */
+		w_16 = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & 0x04;
+		snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, 0x400);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, daddr, w_16);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, daddr, w_16);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, 0);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, 0);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0);
+		snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, 0);
+		snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, daddr, w_16);
+		snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, 7);
+		if (gus->gf1.enh_mode) {
+			snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0);
+			snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME, 0);
+			snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME_FINAL, 0);
+		}
+		spin_unlock_irqrestore(&gus->reg_lock, flags);
+#if 0
+		snd_gf1_lfo_shutdown(gus, i, ULTRA_LFO_VIBRATO);
+		snd_gf1_lfo_shutdown(gus, i, ULTRA_LFO_TREMOLO);
+#endif
+	}
+}
+
+void snd_gf1_stop_voices(struct snd_gus_card * gus, unsigned short v_min, unsigned short v_max)
+{
+	unsigned long flags;
+	short i, ramp_ok;
+	unsigned short ramp_end;
+
+	if (!in_interrupt()) {	/* this can't be done in interrupt */
+		for (i = v_min, ramp_ok = 0; i <= v_max; i++) {
+			spin_lock_irqsave(&gus->reg_lock, flags);
+			snd_gf1_select_voice(gus, i);
+			ramp_end = snd_gf1_read16(gus, 9) >> 8;
+			if (ramp_end > SNDRV_GF1_MIN_OFFSET) {
+				ramp_ok++;
+				snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 20);	/* ramp rate */
+				snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET);	/* ramp start */
+				snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, ramp_end);	/* ramp end */
+				snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40);	/* ramp down */
+				if (gus->gf1.enh_mode) {
+					snd_gf1_delay(gus);
+					snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40);
+				}
+			}
+			spin_unlock_irqrestore(&gus->reg_lock, flags);
+		}
+		msleep_interruptible(50);
+	}
+	snd_gf1_clear_voices(gus, v_min, v_max);
+}
+
+static void snd_gf1_alloc_voice_use(struct snd_gus_card * gus, 
+				    struct snd_gus_voice * pvoice,
+				    int type, int client, int port)
+{
+	pvoice->use = 1;
+	switch (type) {
+	case SNDRV_GF1_VOICE_TYPE_PCM:
+		gus->gf1.pcm_alloc_voices++;
+		pvoice->pcm = 1;
+		break;
+	case SNDRV_GF1_VOICE_TYPE_SYNTH:
+		pvoice->synth = 1;
+		pvoice->client = client;
+		pvoice->port = port;
+		break;
+	case SNDRV_GF1_VOICE_TYPE_MIDI:
+		pvoice->midi = 1;
+		pvoice->client = client;
+		pvoice->port = port;
+		break;
+	}
+}
+
+struct snd_gus_voice *snd_gf1_alloc_voice(struct snd_gus_card * gus, int type, int client, int port)
+{
+	struct snd_gus_voice *pvoice;
+	unsigned long flags;
+	int idx;
+
+	spin_lock_irqsave(&gus->voice_alloc, flags);
+	if (type == SNDRV_GF1_VOICE_TYPE_PCM) {
+		if (gus->gf1.pcm_alloc_voices >= gus->gf1.pcm_channels) {
+			spin_unlock_irqrestore(&gus->voice_alloc, flags);
+			return NULL;
+		}
+	}
+	for (idx = 0; idx < 32; idx++) {
+		pvoice = &gus->gf1.voices[idx];
+		if (!pvoice->use) {
+			snd_gf1_alloc_voice_use(gus, pvoice, type, client, port);
+			spin_unlock_irqrestore(&gus->voice_alloc, flags);
+			return pvoice;
+		}
+	} 
+	for (idx = 0; idx < 32; idx++) {
+		pvoice = &gus->gf1.voices[idx];
+		if (pvoice->midi && !pvoice->client) {
+			snd_gf1_clear_voices(gus, pvoice->number, pvoice->number);
+			snd_gf1_alloc_voice_use(gus, pvoice, type, client, port);
+			spin_unlock_irqrestore(&gus->voice_alloc, flags);
+			return pvoice;
+		}
+	} 
+	spin_unlock_irqrestore(&gus->voice_alloc, flags);
+	return NULL;
+}
+
+void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice)
+{
+	unsigned long flags;
+	void (*private_free)(struct snd_gus_voice *voice);
+	void *private_data;
+
+	if (voice == NULL || !voice->use)
+		return;
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | voice->number);
+	snd_gf1_clear_voices(gus, voice->number, voice->number);
+	spin_lock_irqsave(&gus->voice_alloc, flags);
+	private_free = voice->private_free;
+	private_data = voice->private_data;
+	voice->private_free = NULL;
+	voice->private_data = NULL;
+	if (voice->pcm)
+		gus->gf1.pcm_alloc_voices--;
+	voice->use = voice->pcm = 0;
+	voice->sample_ops = NULL;
+	spin_unlock_irqrestore(&gus->voice_alloc, flags);
+	if (private_free)
+		private_free(voice);
+}
+
+/*
+ *  call this function only by start of driver
+ */
+
+int snd_gf1_start(struct snd_gus_card * gus)
+{
+	unsigned long flags;
+	unsigned int i;
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* disable IRQ & DAC */
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac);
+
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL);
+	for (i = 0; i < 32; i++) {
+		gus->gf1.voices[i].number = i;
+		snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i);
+	}
+
+	snd_gf1_uart_cmd(gus, 0x03);	/* huh.. this cleanup took me some time... */
+
+	if (gus->gf1.enh_mode) {	/* enhanced mode !!!! */
+		snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01);
+		snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+	}
+	snd_gf1_clear_regs(gus);
+	snd_gf1_select_active_voices(gus);
+	snd_gf1_delay(gus);
+	gus->gf1.default_voice_address = gus->gf1.memory > 0 ? 0 : 512 - 8;
+	/* initialize LFOs & clear LFOs memory */
+	if (gus->gf1.enh_mode && gus->gf1.memory) {
+		gus->gf1.hw_lfo = 1;
+		gus->gf1.default_voice_address += 1024;
+	} else {
+		gus->gf1.sw_lfo = 1;
+	}
+#if 0
+	snd_gf1_lfo_init(gus);
+#endif
+	if (gus->gf1.memory > 0)
+		for (i = 0; i < 4; i++)
+			snd_gf1_poke(gus, gus->gf1.default_voice_address + i, 0);
+	snd_gf1_clear_regs(gus);
+	snd_gf1_clear_voices(gus, 0, 31);
+	snd_gf1_look_regs(gus);
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7);	/* Reset Register = IRQ enable, DAC enable */
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7);	/* Reset Register = IRQ enable, DAC enable */
+	if (gus->gf1.enh_mode) {	/* enhanced mode !!!! */
+		snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01);
+		snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
+	}
+	while ((snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ) & 0xc0) != 0xc0);
+
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE));
+	outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+
+	snd_gf1_timers_init(gus);
+	snd_gf1_look_regs(gus);
+	snd_gf1_mem_init(gus);
+	snd_gf1_mem_proc_init(gus);
+#ifdef CONFIG_SND_DEBUG
+	snd_gus_irq_profile_init(gus);
+#endif
+
+#if 0
+	if (gus->pnp_flag) {
+		if (gus->chip.playback_fifo_size > 0)
+			snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR, gus->chip.playback_fifo_block->ptr >> 8);
+		if (gus->chip.record_fifo_size > 0)
+			snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR, gus->chip.record_fifo_block->ptr >> 8);
+		snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_SIZE, gus->chip.interwave_fifo_reg);
+	}
+#endif
+
+	return 0;
+}
+
+/*
+ *  call this function only by shutdown of driver
+ */
+
+int snd_gf1_stop(struct snd_gus_card * gus)
+{
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0); /* stop all timers */
+	snd_gf1_stop_voices(gus, 0, 31);		/* stop all voices */
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* disable IRQ & DAC */
+	snd_gf1_timers_done(gus);
+	snd_gf1_mem_done(gus);
+#if 0
+	snd_gf1_lfo_done(gus);
+#endif
+	return 0;
+}
diff --git a/linux/sound/isa/gus/gus_tables.h b/linux/sound/isa/gus/gus_tables.h
new file mode 100644
index 0000000..42a4ca0
--- /dev/null
+++ b/linux/sound/isa/gus/gus_tables.h
@@ -0,0 +1,90 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#define SNDRV_GF1_SCALE_TABLE_SIZE	128
+#define SNDRV_GF1_ATTEN_TABLE_SIZE	128
+
+#ifdef __GUS_TABLES_ALLOC__
+
+#if 0
+
+unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE] =
+{
+      8372,      8870,      9397,      9956,     10548,     11175,
+     11840,     12544,     13290,     14080,     14917,     15804,
+     16744,     17740,     18795,     19912,     21096,     22351,
+     23680,     25088,     26580,     28160,     29834,     31609,
+     33488,     35479,     37589,     39824,     42192,     44701,
+     47359,     50175,     53159,     56320,     59669,     63217,
+     66976,     70959,     75178,     79649,     84385,     89402,
+     94719,    100351,    106318,    112640,    119338,    126434,
+    133952,    141918,    150356,    159297,    168769,    178805,
+    189437,    200702,    212636,    225280,    238676,    252868,
+    267905,    283835,    300713,    318594,    337539,    357610,
+    378874,    401403,    425272,    450560,    477352,    505737,
+    535809,    567670,    601425,    637188,    675077,    715219,
+    757749,    802807,    850544,    901120,    954703,   1011473,
+   1071618,   1135340,   1202851,   1274376,   1350154,   1430439,
+   1515497,   1605613,   1701088,   1802240,   1909407,   2022946,
+   2143237,   2270680,   2405702,   2548752,   2700309,   2860878,
+   3030994,   3211227,   3402176,   3604480,   3818814,   4045892,
+   4286473,   4541360,   4811404,   5097505,   5400618,   5721755,
+   6061989,   6422453,   6804352,   7208960,   7637627,   8091784,
+   8572947,   9082720,   9622807,  10195009,  10801236,  11443511,
+  12123977,  12844906
+};
+
+#endif  /*  0  */
+
+unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE] = {
+  4095 /* 0   */,1789 /* 1   */,1533 /* 2   */,1383 /* 3   */,1277 /* 4   */,
+  1195 /* 5   */,1127 /* 6   */,1070 /* 7   */,1021 /* 8   */,978  /* 9   */,
+  939  /* 10  */,903  /* 11  */,871  /* 12  */,842  /* 13  */,814  /* 14  */,
+  789  /* 15  */,765  /* 16  */,743  /* 17  */,722  /* 18  */,702  /* 19  */,
+  683  /* 20  */,665  /* 21  */,647  /* 22  */,631  /* 23  */,615  /* 24  */,
+  600  /* 25  */,586  /* 26  */,572  /* 27  */,558  /* 28  */,545  /* 29  */,
+  533  /* 30  */,521  /* 31  */,509  /* 32  */,498  /* 33  */,487  /* 34  */,
+  476  /* 35  */,466  /* 36  */,455  /* 37  */,446  /* 38  */,436  /* 39  */,
+  427  /* 40  */,418  /* 41  */,409  /* 42  */,400  /* 43  */,391  /* 44  */,
+  383  /* 45  */,375  /* 46  */,367  /* 47  */,359  /* 48  */,352  /* 49  */,
+  344  /* 50  */,337  /* 51  */,330  /* 52  */,323  /* 53  */,316  /* 54  */,
+  309  /* 55  */,302  /* 56  */,296  /* 57  */,289  /* 58  */,283  /* 59  */,
+  277  /* 60  */,271  /* 61  */,265  /* 62  */,259  /* 63  */,253  /* 64  */,
+  247  /* 65  */,242  /* 66  */,236  /* 67  */,231  /* 68  */,225  /* 69  */,
+  220  /* 70  */,215  /* 71  */,210  /* 72  */,205  /* 73  */,199  /* 74  */,
+  195  /* 75  */,190  /* 76  */,185  /* 77  */,180  /* 78  */,175  /* 79  */,
+  171  /* 80  */,166  /* 81  */,162  /* 82  */,157  /* 83  */,153  /* 84  */,
+  148  /* 85  */,144  /* 86  */,140  /* 87  */,135  /* 88  */,131  /* 89  */,
+  127  /* 90  */,123  /* 91  */,119  /* 92  */,115  /* 93  */,111  /* 94  */,
+  107  /* 95  */,103  /* 96  */,100  /* 97  */,96   /* 98  */,92   /* 99  */,
+  88   /* 100 */,85   /* 101 */,81   /* 102 */,77   /* 103 */,74   /* 104 */,
+  70   /* 105 */,67   /* 106 */,63   /* 107 */,60   /* 108 */,56   /* 109 */,
+  53   /* 110 */,50   /* 111 */,46   /* 112 */,43   /* 113 */,40   /* 114 */,
+  37   /* 115 */,33   /* 116 */,30   /* 117 */,27   /* 118 */,24   /* 119 */,
+  21   /* 120 */,18   /* 121 */,15   /* 122 */,12   /* 123 */,9    /* 124 */,
+  6    /* 125 */,3    /* 126 */,0    /* 127 */,
+};
+
+#else
+
+extern unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE];
+extern unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE];
+
+#endif
diff --git a/linux/sound/isa/gus/gus_timer.c b/linux/sound/isa/gus/gus_timer.c
new file mode 100644
index 0000000..c537271
--- /dev/null
+++ b/linux/sound/isa/gus/gus_timer.c
@@ -0,0 +1,203 @@
+/*
+ *  Routines for Gravis UltraSound soundcards - Timers
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *  GUS have similar timers as AdLib (OPL2/OPL3 chips).
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+/*
+ *  Timer 1 - 80us
+ */
+
+static int snd_gf1_timer1_start(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	unsigned int ticks;
+	struct snd_gus_card *gus;
+
+	gus = snd_timer_chip(timer);
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	ticks = timer->sticks;
+	tmp = (gus->gf1.timer_enabled |= 4);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_1, 256 - ticks);	/* timer 1 count */
+	snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp);	/* enable timer 1 IRQ */
+	snd_gf1_adlib_write(gus, 0x04, tmp >> 2);	/* timer 2 start */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return 0;
+}
+
+static int snd_gf1_timer1_stop(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	struct snd_gus_card *gus;
+
+	gus = snd_timer_chip(timer);
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	tmp = (gus->gf1.timer_enabled &= ~4);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp);	/* disable timer #1 */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return 0;
+}
+
+/*
+ *  Timer 2 - 320us
+ */
+
+static int snd_gf1_timer2_start(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	unsigned int ticks;
+	struct snd_gus_card *gus;
+
+	gus = snd_timer_chip(timer);
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	ticks = timer->sticks;
+	tmp = (gus->gf1.timer_enabled |= 8);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_2, 256 - ticks);	/* timer 2 count */
+	snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp);	/* enable timer 2 IRQ */
+	snd_gf1_adlib_write(gus, 0x04, tmp >> 2);	/* timer 2 start */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return 0;
+}
+
+static int snd_gf1_timer2_stop(struct snd_timer * timer)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	struct snd_gus_card *gus;
+
+	gus = snd_timer_chip(timer);
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	tmp = (gus->gf1.timer_enabled &= ~8);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp);	/* disable timer #1 */
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	return 0;
+}
+
+/*
+
+ */
+
+static void snd_gf1_interrupt_timer1(struct snd_gus_card * gus)
+{
+	struct snd_timer *timer = gus->gf1.timer1;
+
+	if (timer == NULL)
+		return;
+	snd_timer_interrupt(timer, timer->sticks);
+}
+
+static void snd_gf1_interrupt_timer2(struct snd_gus_card * gus)
+{
+	struct snd_timer *timer = gus->gf1.timer2;
+
+	if (timer == NULL)
+		return;
+	snd_timer_interrupt(timer, timer->sticks);
+}
+
+/*
+
+ */
+
+static struct snd_timer_hardware snd_gf1_timer1 =
+{
+	.flags =	SNDRV_TIMER_HW_STOP,
+	.resolution =	80000,
+	.ticks =	256,
+	.start =	snd_gf1_timer1_start,
+	.stop =		snd_gf1_timer1_stop,
+};
+
+static struct snd_timer_hardware snd_gf1_timer2 =
+{
+	.flags =	SNDRV_TIMER_HW_STOP,
+	.resolution =	320000,
+	.ticks =	256,
+	.start =	snd_gf1_timer2_start,
+	.stop =		snd_gf1_timer2_stop,
+};
+
+static void snd_gf1_timer1_free(struct snd_timer *timer)
+{
+	struct snd_gus_card *gus = timer->private_data;
+	gus->gf1.timer1 = NULL;
+}
+
+static void snd_gf1_timer2_free(struct snd_timer *timer)
+{
+	struct snd_gus_card *gus = timer->private_data;
+	gus->gf1.timer2 = NULL;
+}
+
+void snd_gf1_timers_init(struct snd_gus_card * gus)
+{
+	struct snd_timer *timer;
+	struct snd_timer_id tid;
+
+	if (gus->gf1.timer1 != NULL || gus->gf1.timer2 != NULL)
+		return;
+
+	gus->gf1.interrupt_handler_timer1 = snd_gf1_interrupt_timer1;
+	gus->gf1.interrupt_handler_timer2 = snd_gf1_interrupt_timer2;
+
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = gus->card->number;
+	tid.device = gus->timer_dev;
+	tid.subdevice = 0;
+
+	if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) {
+		strcpy(timer->name, "GF1 timer #1");
+		timer->private_data = gus;
+		timer->private_free = snd_gf1_timer1_free;
+		timer->hw = snd_gf1_timer1;
+	}
+	gus->gf1.timer1 = timer;
+
+	tid.device++;
+
+	if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) {
+		strcpy(timer->name, "GF1 timer #2");
+		timer->private_data = gus;
+		timer->private_free = snd_gf1_timer2_free;
+		timer->hw = snd_gf1_timer2;
+	}
+	gus->gf1.timer2 = timer;
+}
+
+void snd_gf1_timers_done(struct snd_gus_card * gus)
+{
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_TIMER1 | SNDRV_GF1_HANDLER_TIMER2);
+	if (gus->gf1.timer1) {
+		snd_device_free(gus->card, gus->gf1.timer1);
+		gus->gf1.timer1 = NULL;
+	}
+	if (gus->gf1.timer2) {
+		snd_device_free(gus->card, gus->gf1.timer2);
+		gus->gf1.timer2 = NULL;
+	}
+}
diff --git a/linux/sound/isa/gus/gus_uart.c b/linux/sound/isa/gus/gus_uart.c
new file mode 100644
index 0000000..21cc42e
--- /dev/null
+++ b/linux/sound/isa/gus/gus_uart.c
@@ -0,0 +1,262 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for the GF1 MIDI interface - like UART 6850
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+static void snd_gf1_interrupt_midi_in(struct snd_gus_card * gus)
+{
+	int count;
+	unsigned char stat, data, byte;
+	unsigned long flags;
+
+	count = 10;
+	while (count) {
+		spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+		stat = snd_gf1_uart_stat(gus);
+		if (!(stat & 0x01)) {	/* data in Rx FIFO? */
+			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+			count--;
+			continue;
+		}
+		count = 100;	/* arm counter to new value */
+		data = snd_gf1_uart_get(gus);
+		if (!(gus->gf1.uart_cmd & 0x80)) {
+			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+			continue;
+		}			
+		if (stat & 0x10) {	/* framing error */
+			gus->gf1.uart_framing++;
+			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+			continue;
+		}
+		byte = snd_gf1_uart_get(gus);
+		spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+		snd_rawmidi_receive(gus->midi_substream_input, &byte, 1);
+		if (stat & 0x20) {
+			gus->gf1.uart_overrun++;
+		}
+	}
+}
+
+static void snd_gf1_interrupt_midi_out(struct snd_gus_card * gus)
+{
+	char byte;
+	unsigned long flags;
+
+	/* try unlock output */
+	if (snd_gf1_uart_stat(gus) & 0x01)
+		snd_gf1_interrupt_midi_in(gus);
+
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (snd_gf1_uart_stat(gus) & 0x02) {	/* Tx FIFO free? */
+		if (snd_rawmidi_transmit(gus->midi_substream_output, &byte, 1) != 1) {	/* no other bytes or error */
+			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); /* disable Tx interrupt */
+		} else {
+			snd_gf1_uart_put(gus, byte);
+		}
+	}
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+}
+
+static void snd_gf1_uart_reset(struct snd_gus_card * gus, int close)
+{
+	snd_gf1_uart_cmd(gus, 0x03);	/* reset */
+	if (!close && gus->uart_enable) {
+		udelay(160);
+		snd_gf1_uart_cmd(gus, 0x00);	/* normal operations */
+	}
+}
+
+static int snd_gf1_uart_output_open(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_gus_card *gus;
+
+	gus = substream->rmidi->private_data;
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (!(gus->gf1.uart_cmd & 0x80)) {	/* input active? */
+		snd_gf1_uart_reset(gus, 0);
+	}
+	gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out;
+	gus->midi_substream_output = substream;
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+#if 0
+	snd_printk(KERN_DEBUG "write init - cmd = 0x%x, stat = 0x%x\n", gus->gf1.uart_cmd, snd_gf1_uart_stat(gus));
+#endif
+	return 0;
+}
+
+static int snd_gf1_uart_input_open(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_gus_card *gus;
+	int i;
+
+	gus = substream->rmidi->private_data;
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) {
+		snd_gf1_uart_reset(gus, 0);
+	}
+	gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in;
+	gus->midi_substream_input = substream;
+	if (gus->uart_enable) {
+		for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++)
+			snd_gf1_uart_get(gus);	/* clean Rx */
+		if (i >= 1000)
+			snd_printk(KERN_ERR "gus midi uart init read - cleanup error\n");
+	}
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+#if 0
+	snd_printk(KERN_DEBUG
+		   "read init - enable = %i, cmd = 0x%x, stat = 0x%x\n",
+		   gus->uart_enable, gus->gf1.uart_cmd, snd_gf1_uart_stat(gus));
+	snd_printk(KERN_DEBUG
+		   "[0x%x] reg (ctrl/status) = 0x%x, reg (data) = 0x%x "
+		   "(page = 0x%x)\n",
+		   gus->gf1.port + 0x100, inb(gus->gf1.port + 0x100),
+		   inb(gus->gf1.port + 0x101), inb(gus->gf1.port + 0x102));
+#endif
+	return 0;
+}
+
+static int snd_gf1_uart_output_close(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_gus_card *gus;
+
+	gus = substream->rmidi->private_data;
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (gus->gf1.interrupt_handler_midi_in != snd_gf1_interrupt_midi_in)
+		snd_gf1_uart_reset(gus, 1);
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_OUT);
+	gus->midi_substream_output = NULL;
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+	return 0;
+}
+
+static int snd_gf1_uart_input_close(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_gus_card *gus;
+
+	gus = substream->rmidi->private_data;
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out)
+		snd_gf1_uart_reset(gus, 1);
+	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_IN);
+	gus->midi_substream_input = NULL;
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+	return 0;
+}
+
+static void snd_gf1_uart_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_gus_card *gus;
+	unsigned long flags;
+
+	gus = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (up) {
+		if ((gus->gf1.uart_cmd & 0x80) == 0)
+			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x80); /* enable Rx interrupts */
+	} else {
+		if (gus->gf1.uart_cmd & 0x80)
+			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x80); /* disable Rx interrupts */
+	}
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+}
+
+static void snd_gf1_uart_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct snd_gus_card *gus;
+	char byte;
+	int timeout;
+
+	gus = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+	if (up) {
+		if ((gus->gf1.uart_cmd & 0x20) == 0) {
+			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+			/* wait for empty Rx - Tx is probably unlocked */
+			timeout = 10000;
+			while (timeout-- > 0 && snd_gf1_uart_stat(gus) & 0x01);
+			/* Tx FIFO free? */
+			spin_lock_irqsave(&gus->uart_cmd_lock, flags);
+			if (gus->gf1.uart_cmd & 0x20) {
+				spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+				return;
+			}
+			if (snd_gf1_uart_stat(gus) & 0x02) {
+				if (snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+					spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+					return;
+				}
+				snd_gf1_uart_put(gus, byte);
+			}
+			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x20);	/* enable Tx interrupt */
+		}
+	} else {
+		if (gus->gf1.uart_cmd & 0x20)
+			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20);
+	}
+	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
+}
+
+static struct snd_rawmidi_ops snd_gf1_uart_output =
+{
+	.open =		snd_gf1_uart_output_open,
+	.close =	snd_gf1_uart_output_close,
+	.trigger =	snd_gf1_uart_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_gf1_uart_input =
+{
+	.open =		snd_gf1_uart_input_open,
+	.close =	snd_gf1_uart_input_close,
+	.trigger =	snd_gf1_uart_input_trigger,
+};
+
+int snd_gf1_rawmidi_new(struct snd_gus_card * gus, int device, struct snd_rawmidi ** rrawmidi)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	if ((err = snd_rawmidi_new(gus->card, "GF1", device, 1, 1, &rmidi)) < 0)
+		return err;
+	strcpy(rmidi->name, gus->interwave ? "AMD InterWave" : "GF1");
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_gf1_uart_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_gf1_uart_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = gus;
+	gus->midi_uart = rmidi;
+	if (rrawmidi)
+		*rrawmidi = rmidi;
+	return err;
+}
diff --git a/linux/sound/isa/gus/gus_volume.c b/linux/sound/isa/gus/gus_volume.c
new file mode 100644
index 0000000..c3c028a
--- /dev/null
+++ b/linux/sound/isa/gus/gus_volume.c
@@ -0,0 +1,217 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#define __GUS_TABLES_ALLOC__
+#include "gus_tables.h"
+
+EXPORT_SYMBOL(snd_gf1_atten_table); /* for snd-gus-synth module */
+
+unsigned short snd_gf1_lvol_to_gvol_raw(unsigned int vol)
+{
+	unsigned short e, m, tmp;
+
+	if (vol > 65535)
+		vol = 65535;
+	tmp = vol;
+	e = 7;
+	if (tmp < 128) {
+		while (e > 0 && tmp < (1 << e))
+			e--;
+	} else {
+		while (tmp > 255) {
+			tmp >>= 1;
+			e++;
+		}
+	}
+	m = vol - (1 << e);
+	if (m > 0) {
+		if (e > 8)
+			m >>= e - 8;
+		else if (e < 8)
+			m <<= 8 - e;
+		m &= 255;
+	}
+	return (e << 8) | m;
+}
+
+#if 0
+
+unsigned int snd_gf1_gvol_to_lvol_raw(unsigned short gf1_vol)
+{
+	unsigned int rvol;
+	unsigned short e, m;
+
+	if (!gf1_vol)
+		return 0;
+	e = gf1_vol >> 8;
+	m = (unsigned char) gf1_vol;
+	rvol = 1 << e;
+	if (e > 8)
+		return rvol | (m << (e - 8));
+	return rvol | (m >> (8 - e));
+}
+
+unsigned int snd_gf1_calc_ramp_rate(struct snd_gus_card * gus,
+				    unsigned short start,
+				    unsigned short end,
+				    unsigned int us)
+{
+	static unsigned char vol_rates[19] =
+	{
+		23, 24, 26, 28, 29, 31, 32, 34,
+		36, 37, 39, 40, 42, 44, 45, 47,
+		49, 50, 52
+	};
+	unsigned short range, increment, value, i;
+
+	start >>= 4;
+	end >>= 4;
+	if (start < end)
+		us /= end - start;
+	else
+		us /= start - end;
+	range = 4;
+	value = gus->gf1.enh_mode ?
+	    vol_rates[0] :
+	    vol_rates[gus->gf1.active_voices - 14];
+	for (i = 0; i < 3; i++) {
+		if (us < value) {
+			range = i;
+			break;
+		} else
+			value <<= 3;
+	}
+	if (range == 4) {
+		range = 3;
+		increment = 1;
+	} else
+		increment = (value + (value >> 1)) / us;
+	return (range << 6) | (increment & 0x3f);
+}
+
+#endif  /*  0  */
+
+unsigned short snd_gf1_translate_freq(struct snd_gus_card * gus, unsigned int freq16)
+{
+	freq16 >>= 3;
+	if (freq16 < 50)
+		freq16 = 50;
+	if (freq16 & 0xf8000000) {
+		freq16 = ~0xf8000000;
+		snd_printk(KERN_ERR "snd_gf1_translate_freq: overflow - freq = 0x%x\n", freq16);
+	}
+	return ((freq16 << 9) + (gus->gf1.playback_freq >> 1)) / gus->gf1.playback_freq;
+}
+
+#if 0
+
+short snd_gf1_compute_vibrato(short cents, unsigned short fc_register)
+{
+	static short vibrato_table[] =
+	{
+		0, 0, 32, 592, 61, 1175, 93, 1808,
+		124, 2433, 152, 3007, 182, 3632, 213, 4290,
+		241, 4834, 255, 5200
+	};
+
+	long depth;
+	short *vi1, *vi2, pcents, v1;
+
+	pcents = cents < 0 ? -cents : cents;
+	for (vi1 = vibrato_table, vi2 = vi1 + 2; pcents > *vi2; vi1 = vi2, vi2 += 2);
+	v1 = *(vi1 + 1);
+	/* The FC table above is a list of pairs. The first number in the pair     */
+	/* is the cents index from 0-255 cents, and the second number in the       */
+	/* pair is the FC adjustment needed to change the pitch by the indexed     */
+	/* number of cents. The table was created for an FC of 32768.              */
+	/* The following expression does a linear interpolation against the        */
+	/* approximated log curve in the table above, and then scales the number   */
+	/* by the FC before the LFO. This calculation also adjusts the output      */
+	/* value to produce the appropriate depth for the hardware. The depth      */
+	/* is 2 * desired FC + 1.                                                  */
+	depth = (((int) (*(vi2 + 1) - *vi1) * (pcents - *vi1) / (*vi2 - *vi1)) + v1) * fc_register >> 14;
+	if (depth)
+		depth++;
+	if (depth > 255)
+		depth = 255;
+	return cents < 0 ? -(short) depth : (short) depth;
+}
+
+unsigned short snd_gf1_compute_pitchbend(unsigned short pitchbend, unsigned short sens)
+{
+	static long log_table[] = {1024, 1085, 1149, 1218, 1290, 1367, 1448, 1534, 1625, 1722, 1825, 1933};
+	int wheel, sensitivity;
+	unsigned int mantissa, f1, f2;
+	unsigned short semitones, f1_index, f2_index, f1_power, f2_power;
+	char bend_down = 0;
+	int bend;
+
+	if (!sens)
+		return 1024;
+	wheel = (int) pitchbend - 8192;
+	sensitivity = ((int) sens * wheel) / 128;
+	if (sensitivity < 0) {
+		bend_down = 1;
+		sensitivity = -sensitivity;
+	}
+	semitones = (unsigned int) (sensitivity >> 13);
+	mantissa = sensitivity % 8192;
+	f1_index = semitones % 12;
+	f2_index = (semitones + 1) % 12;
+	f1_power = semitones / 12;
+	f2_power = (semitones + 1) / 12;
+	f1 = log_table[f1_index] << f1_power;
+	f2 = log_table[f2_index] << f2_power;
+	bend = (int) ((((f2 - f1) * mantissa) >> 13) + f1);
+	if (bend_down)
+		bend = 1048576L / bend;
+	return bend;
+}
+
+unsigned short snd_gf1_compute_freq(unsigned int freq,
+				    unsigned int rate,
+				    unsigned short mix_rate)
+{
+	unsigned int fc;
+	int scale = 0;
+
+	while (freq >= 4194304L) {
+		scale++;
+		freq >>= 1;
+	}
+	fc = (freq << 10) / rate;
+	if (fc > 97391L) {
+		fc = 97391;
+		snd_printk(KERN_ERR "patch: (1) fc frequency overflow - %u\n", fc);
+	}
+	fc = (fc * 44100UL) / mix_rate;
+	while (scale--)
+		fc <<= 1;
+	if (fc > 65535L) {
+		fc = 65535;
+		snd_printk(KERN_ERR "patch: (2) fc frequency overflow - %u\n", fc);
+	}
+	return (unsigned short) fc;
+}
+
+#endif  /*  0  */
diff --git a/linux/sound/isa/gus/gusclassic.c b/linux/sound/isa/gus/gusclassic.c
new file mode 100644
index 0000000..086b8f0
--- /dev/null
+++ b/linux/sound/isa/gus/gusclassic.c
@@ -0,0 +1,245 @@
+/*
+ *  Driver for Gravis UltraSound Classic soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define CRD_NAME "Gravis UltraSound Classic"
+#define DEV_NAME "gusclassic"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Classic}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x230,0x240,0x250,0x260 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for " CRD_NAME " driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for " CRD_NAME " driver.");
+module_param_array(channels, int, NULL, 0444);
+MODULE_PARM_DESC(channels, "GF1 channels for " CRD_NAME " driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for " CRD_NAME " driver.");
+
+static int __devinit snd_gusclassic_match(struct device *dev, unsigned int n)
+{
+	return enable[n];
+}
+
+static int __devinit snd_gusclassic_create(struct snd_card *card,
+		struct device *dev, unsigned int n, struct snd_gus_card **rgus)
+{
+	static long possible_ports[] = {0x220, 0x230, 0x240, 0x250, 0x260};
+	static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, 4, -1};
+	static int possible_dmas[] = {5, 6, 7, 1, 3, -1};
+
+	int i, error;
+
+	if (irq[n] == SNDRV_AUTO_IRQ) {
+		irq[n] = snd_legacy_find_free_irq(possible_irqs);
+		if (irq[n] < 0) {
+			dev_err(dev, "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1[n] == SNDRV_AUTO_DMA) {
+		dma1[n] = snd_legacy_find_free_dma(possible_dmas);
+		if (dma1[n] < 0) {
+			dev_err(dev, "unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	if (dma2[n] == SNDRV_AUTO_DMA) {
+		dma2[n] = snd_legacy_find_free_dma(possible_dmas);
+		if (dma2[n] < 0) {
+			dev_err(dev, "unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[n] != SNDRV_AUTO_PORT)
+		return snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n],
+				0, channels[n], pcm_channels[n], 0, rgus);
+
+	i = 0;
+	do {
+		port[n] = possible_ports[i];
+		error = snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n],
+				0, channels[n], pcm_channels[n], 0, rgus);
+	} while (error < 0 && ++i < ARRAY_SIZE(possible_ports));
+
+	return error;
+}
+
+static int __devinit snd_gusclassic_detect(struct snd_gus_card *gus)
+{
+	unsigned char d;
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+		snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+		snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int __devinit snd_gusclassic_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	struct snd_gus_card *gus;
+	int error;
+
+	error = snd_card_create(index[n], id[n], THIS_MODULE, 0, &card);
+	if (error < 0)
+		return error;
+
+	if (pcm_channels[n] < 2)
+		pcm_channels[n] = 2;
+
+	error = snd_gusclassic_create(card, dev, n, &gus);
+	if (error < 0)
+		goto out;
+
+	error = snd_gusclassic_detect(gus);
+	if (error < 0)
+		goto out;
+
+	gus->joystick_dac = joystick_dac[n];
+
+	error = snd_gus_initialize(gus);
+	if (error < 0)
+		goto out;
+
+	error = -ENODEV;
+	if (gus->max_flag || gus->ess_flag) {
+		dev_err(dev, "GUS Classic or ACE soundcard was "
+			"not detected at 0x%lx\n", gus->gf1.port);
+		goto out;
+	}
+
+	error = snd_gf1_new_mixer(gus);
+	if (error < 0)
+		goto out;
+
+	error = snd_gf1_pcm_new(gus, 0, 0, NULL);
+	if (error < 0)
+		goto out;
+
+	if (!gus->ace_flag) {
+		error = snd_gf1_rawmidi_new(gus, 0, NULL);
+		if (error < 0)
+			goto out;
+	}
+
+	sprintf(card->longname + strlen(card->longname),
+		" at 0x%lx, irq %d, dma %d",
+		gus->gf1.port, gus->gf1.irq, gus->gf1.dma1);
+
+	if (gus->gf1.dma2 >= 0)
+		sprintf(card->longname + strlen(card->longname),
+			"&%d", gus->gf1.dma2);
+
+	snd_card_set_dev(card, dev);
+
+	error = snd_card_register(card);
+	if (error < 0)
+		goto out;
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+out:	snd_card_free(card);
+	return error;
+}
+
+static int __devexit snd_gusclassic_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	dev_set_drvdata(dev, NULL);
+	return 0;
+}
+
+static struct isa_driver snd_gusclassic_driver = {
+	.match		= snd_gusclassic_match,
+	.probe		= snd_gusclassic_probe,
+	.remove		= __devexit_p(snd_gusclassic_remove),
+#if 0	/* FIXME */
+	.suspend	= snd_gusclassic_suspend,
+	.remove		= snd_gusclassic_remove,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+static int __init alsa_card_gusclassic_init(void)
+{
+	return isa_register_driver(&snd_gusclassic_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_gusclassic_exit(void)
+{
+	isa_unregister_driver(&snd_gusclassic_driver);
+}
+
+module_init(alsa_card_gusclassic_init);
+module_exit(alsa_card_gusclassic_exit);
diff --git a/linux/sound/isa/gus/gusextreme.c b/linux/sound/isa/gus/gusextreme.c
new file mode 100644
index 0000000..008e8e5
--- /dev/null
+++ b/linux/sound/isa/gus/gusextreme.c
@@ -0,0 +1,374 @@
+/*
+ *  Driver for Gravis UltraSound Extreme soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/es1688.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#define SNDRV_LEGACY_AUTO_PROBE
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define CRD_NAME "Gravis UltraSound Extreme"
+#define DEV_NAME "gusextreme"
+
+MODULE_DESCRIPTION(CRD_NAME);
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Extreme}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
+static long gf1_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x210,0x220,0x230,0x240,0x250,0x260,0x270 */
+static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x300,0x310,0x320 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int gf1_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 2,3,5,9,11,12,15 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
+module_param_array(gf1_port, long, NULL, 0444);
+MODULE_PARM_DESC(gf1_port, "GF1 port # for " CRD_NAME " driver (optional).");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver.");
+module_param_array(gf1_irq, int, NULL, 0444);
+MODULE_PARM_DESC(gf1_irq, "GF1 IRQ # for " CRD_NAME " driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for " CRD_NAME " driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "GF1 DMA # for " CRD_NAME " driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for " CRD_NAME " driver.");
+module_param_array(channels, int, NULL, 0444);
+MODULE_PARM_DESC(channels, "GF1 channels for " CRD_NAME " driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for " CRD_NAME " driver.");
+
+static int __devinit snd_gusextreme_match(struct device *dev, unsigned int n)
+{
+	return enable[n];
+}
+
+static int __devinit snd_gusextreme_es1688_create(struct snd_card *card,
+		struct snd_es1688 *chip, struct device *dev, unsigned int n)
+{
+	static long possible_ports[] = {0x220, 0x240, 0x260};
+	static int possible_irqs[] = {5, 9, 10, 7, -1};
+	static int possible_dmas[] = {1, 3, 0, -1};
+
+	int i, error;
+
+	if (irq[n] == SNDRV_AUTO_IRQ) {
+		irq[n] = snd_legacy_find_free_irq(possible_irqs);
+		if (irq[n] < 0) {
+			dev_err(dev, "unable to find a free IRQ for ES1688\n");
+			return -EBUSY;
+		}
+	}
+	if (dma8[n] == SNDRV_AUTO_DMA) {
+		dma8[n] = snd_legacy_find_free_dma(possible_dmas);
+		if (dma8[n] < 0) {
+			dev_err(dev, "unable to find a free DMA for ES1688\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[n] != SNDRV_AUTO_PORT)
+		return snd_es1688_create(card, chip, port[n], mpu_port[n],
+				irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688);
+
+	i = 0;
+	do {
+		port[n] = possible_ports[i];
+		error = snd_es1688_create(card, chip, port[n], mpu_port[n],
+				irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688);
+	} while (error < 0 && ++i < ARRAY_SIZE(possible_ports));
+
+	return error;
+}
+
+static int __devinit snd_gusextreme_gus_card_create(struct snd_card *card,
+		struct device *dev, unsigned int n, struct snd_gus_card **rgus)
+{
+	static int possible_irqs[] = {11, 12, 15, 9, 5, 7, 3, -1};
+	static int possible_dmas[] = {5, 6, 7, 3, 1, -1};
+
+	if (gf1_irq[n] == SNDRV_AUTO_IRQ) {
+		gf1_irq[n] = snd_legacy_find_free_irq(possible_irqs);
+		if (gf1_irq[n] < 0) {
+			dev_err(dev, "unable to find a free IRQ for GF1\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1[n] == SNDRV_AUTO_DMA) {
+		dma1[n] = snd_legacy_find_free_dma(possible_dmas);
+		if (dma1[n] < 0) {
+			dev_err(dev, "unable to find a free DMA for GF1\n");
+			return -EBUSY;
+		}
+	}
+	return snd_gus_create(card, gf1_port[n], gf1_irq[n], dma1[n], -1,
+			0, channels[n], pcm_channels[n], 0, rgus);
+}
+
+static int __devinit snd_gusextreme_detect(struct snd_gus_card *gus,
+	struct snd_es1688 *es1688)
+{
+	unsigned long flags;
+	unsigned char d;
+
+	/*
+	 * This is main stuff - enable access to GF1 chip...
+	 * I'm not sure, if this will work for card which have
+	 * ES1688 chip in another place than 0x220.
+         *
+         * I used reverse-engineering in DOSEMU. [--jk]
+	 *
+	 * ULTRINIT.EXE:
+	 * 0x230 = 0,2,3
+	 * 0x240 = 2,0,1
+	 * 0x250 = 2,0,3
+	 * 0x260 = 2,2,1
+	 */
+
+	spin_lock_irqsave(&es1688->mixer_lock, flags);
+	snd_es1688_mixer_write(es1688, 0x40, 0x0b);	/* don't change!!! */
+	spin_unlock_irqrestore(&es1688->mixer_lock, flags);
+
+	spin_lock_irqsave(&es1688->reg_lock, flags);
+	outb(gus->gf1.port & 0x040 ? 2 : 0, ES1688P(es1688, INIT1));
+	outb(0, 0x201);
+	outb(gus->gf1.port & 0x020 ? 2 : 0, ES1688P(es1688, INIT1));
+	outb(0, 0x201);
+	outb(gus->gf1.port & 0x010 ? 3 : 1, ES1688P(es1688, INIT1));
+	spin_unlock_irqrestore(&es1688->reg_lock, flags);
+
+	udelay(100);
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+		snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+		return -EIO;
+	}
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+		snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int __devinit snd_gusextreme_mixer(struct snd_card *card)
+{
+	struct snd_ctl_elem_id id1, id2;
+	int error;
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+
+	/* reassign AUX to SYNTHESIZER */
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "Synth Playback Volume");
+	error = snd_ctl_rename_id(card, &id1, &id2);
+	if (error < 0)
+		return error;
+
+	/* reassign Master Playback Switch to Synth Playback Switch */
+	strcpy(id1.name, "Master Playback Switch");
+	strcpy(id2.name, "Synth Playback Switch");
+	error = snd_ctl_rename_id(card, &id1, &id2);
+	if (error < 0)
+		return error;
+
+	return 0;
+}
+
+static int __devinit snd_gusextreme_probe(struct device *dev, unsigned int n)
+{
+	struct snd_card *card;
+	struct snd_gus_card *gus;
+	struct snd_es1688 *es1688;
+	struct snd_opl3 *opl3;
+	int error;
+
+	error = snd_card_create(index[n], id[n], THIS_MODULE,
+				sizeof(struct snd_es1688), &card);
+	if (error < 0)
+		return error;
+
+	es1688 = card->private_data;
+
+	if (mpu_port[n] == SNDRV_AUTO_PORT)
+		mpu_port[n] = 0;
+
+	if (mpu_irq[n] > 15)
+		mpu_irq[n] = -1;
+
+	error = snd_gusextreme_es1688_create(card, es1688, dev, n);
+	if (error < 0)
+		goto out;
+
+	if (gf1_port[n] < 0)
+		gf1_port[n] = es1688->port + 0x20;
+
+	error = snd_gusextreme_gus_card_create(card, dev, n, &gus);
+	if (error < 0)
+		goto out;
+
+	error = snd_gusextreme_detect(gus, es1688);
+	if (error < 0)
+		goto out;
+
+	gus->joystick_dac = joystick_dac[n];
+
+	error = snd_gus_initialize(gus);
+	if (error < 0)
+		goto out;
+
+	error = -ENODEV;
+	if (!gus->ess_flag) {
+		dev_err(dev, "GUS Extreme soundcard was not "
+			"detected at 0x%lx\n", gus->gf1.port);
+		goto out;
+	}
+	gus->codec_flag = 1;
+
+	error = snd_es1688_pcm(card, es1688, 0, NULL);
+	if (error < 0)
+		goto out;
+
+	error = snd_es1688_mixer(card, es1688);
+	if (error < 0)
+		goto out;
+
+	snd_component_add(card, "ES1688");
+
+	if (pcm_channels[n] > 0) {
+		error = snd_gf1_pcm_new(gus, 1, 1, NULL);
+		if (error < 0)
+			goto out;
+	}
+
+	error = snd_gf1_new_mixer(gus);
+	if (error < 0)
+		goto out;
+
+	error = snd_gusextreme_mixer(card);
+	if (error < 0)
+		goto out;
+
+	if (snd_opl3_create(card, es1688->port, es1688->port + 2,
+			OPL3_HW_OPL3, 0, &opl3) < 0)
+		dev_warn(dev, "opl3 not detected at 0x%lx\n", es1688->port);
+	else {
+		error = snd_opl3_hwdep_new(opl3, 0, 2, NULL);
+		if (error < 0)
+			goto out;
+	}
+
+	if (es1688->mpu_port >= 0x300) {
+		error = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688,
+				es1688->mpu_port, 0,
+				mpu_irq[n], IRQF_DISABLED, NULL);
+		if (error < 0)
+			goto out;
+	}
+
+	sprintf(card->longname, "Gravis UltraSound Extreme at 0x%lx, "
+		"irq %i&%i, dma %i&%i", es1688->port,
+		gus->gf1.irq, es1688->irq, gus->gf1.dma1, es1688->dma8);
+
+	snd_card_set_dev(card, dev);
+
+	error = snd_card_register(card);
+	if (error < 0)
+		goto out;
+
+	dev_set_drvdata(dev, card);
+	return 0;
+
+out:	snd_card_free(card);
+	return error;
+}
+
+static int __devexit snd_gusextreme_remove(struct device *dev, unsigned int n)
+{
+	snd_card_free(dev_get_drvdata(dev));
+	dev_set_drvdata(dev, NULL);
+	return 0;
+}
+
+static struct isa_driver snd_gusextreme_driver = {
+	.match		= snd_gusextreme_match,
+	.probe		= snd_gusextreme_probe,
+	.remove		= __devexit_p(snd_gusextreme_remove),
+#if 0	/* FIXME */
+	.suspend	= snd_gusextreme_suspend,
+	.resume		= snd_gusextreme_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	}
+};
+
+static int __init alsa_card_gusextreme_init(void)
+{
+	return isa_register_driver(&snd_gusextreme_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_gusextreme_exit(void)
+{
+	isa_unregister_driver(&snd_gusextreme_driver);
+}
+
+module_init(alsa_card_gusextreme_init);
+module_exit(alsa_card_gusextreme_exit);
diff --git a/linux/sound/isa/gus/gusmax.c b/linux/sound/isa/gus/gusmax.c
new file mode 100644
index 0000000..3e4a58b
--- /dev/null
+++ b/linux/sound/isa/gus/gusmax.c
@@ -0,0 +1,387 @@
+/*
+ *  Driver for Gravis UltraSound MAX soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/wss.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Gravis UltraSound MAX");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound MAX}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x230,0x240,0x250,0x260 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 2,3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for GUS MAX soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for GUS MAX soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable GUS MAX soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for GUS MAX driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for GUS MAX driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for GUS MAX driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for GUS MAX driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for GUS MAX driver.");
+module_param_array(channels, int, NULL, 0444);
+MODULE_PARM_DESC(channels, "Used GF1 channels for GUS MAX driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for GUS MAX driver.");
+
+struct snd_gusmax {
+	int irq;
+	struct snd_card *card;
+	struct snd_gus_card *gus;
+	struct snd_wss *wss;
+	unsigned short gus_status_reg;
+	unsigned short pcm_status_reg;
+};
+
+#define PFX	"gusmax: "
+
+static int __devinit snd_gusmax_detect(struct snd_gus_card * gus)
+{
+	unsigned char d;
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+		snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+		snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static irqreturn_t snd_gusmax_interrupt(int irq, void *dev_id)
+{
+	struct snd_gusmax *maxcard = dev_id;
+	int loop, max = 5;
+	int handled = 0;
+
+	do {
+		loop = 0;
+		if (inb(maxcard->gus_status_reg)) {
+			handled = 1;
+			snd_gus_interrupt(irq, maxcard->gus);
+			loop++;
+		}
+		if (inb(maxcard->pcm_status_reg) & 0x01) { /* IRQ bit is set? */
+			handled = 1;
+			snd_wss_interrupt(irq, maxcard->wss);
+			loop++;
+		}
+	} while (loop && --max > 0);
+	return IRQ_RETVAL(handled);
+}
+
+static void __devinit snd_gusmax_init(int dev, struct snd_card *card,
+				      struct snd_gus_card * gus)
+{
+	gus->equal_irq = 1;
+	gus->codec_flag = 1;
+	gus->joystick_dac = joystick_dac[dev];
+	/* init control register */
+	gus->max_cntrl_val = (gus->gf1.port >> 4) & 0x0f;
+	if (gus->gf1.dma1 > 3)
+		gus->max_cntrl_val |= 0x10;
+	if (gus->gf1.dma2 > 3)
+		gus->max_cntrl_val |= 0x20;
+	gus->max_cntrl_val |= 0x40;
+	outb(gus->max_cntrl_val, GUSP(gus, MAXCNTRLPORT));
+}
+
+static int __devinit snd_gusmax_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card = chip->card;
+	struct snd_ctl_elem_id id1, id2;
+	int err;
+	
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	/* reassign AUXA to SYNTHESIZER */
+	strcpy(id1.name, "Aux Playback Switch");
+	strcpy(id2.name, "Synth Playback Switch");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "Synth Playback Volume");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	/* reassign AUXB to CD */
+	strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+	strcpy(id2.name, "CD Playback Switch");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "CD Playback Volume");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+#if 0
+	/* reassign Mono Input to MIC */
+	if (snd_mixer_group_rename(mixer,
+				SNDRV_MIXER_IN_MONO, 0,
+				SNDRV_MIXER_IN_MIC, 0) < 0)
+		goto __error;
+	if (snd_mixer_elem_rename(mixer,
+				SNDRV_MIXER_IN_MONO, 0, SNDRV_MIXER_ETYPE_INPUT,
+				SNDRV_MIXER_IN_MIC, 0) < 0)
+		goto __error;
+	if (snd_mixer_elem_rename(mixer,
+				"Mono Capture Volume", 0, SNDRV_MIXER_ETYPE_VOLUME1,
+				"Mic Capture Volume", 0) < 0)
+		goto __error;
+	if (snd_mixer_elem_rename(mixer,
+				"Mono Capture Switch", 0, SNDRV_MIXER_ETYPE_SWITCH1,
+				"Mic Capture Switch", 0) < 0)
+		goto __error;
+#endif
+	return 0;
+}
+
+static void snd_gusmax_free(struct snd_card *card)
+{
+	struct snd_gusmax *maxcard = card->private_data;
+	
+	if (maxcard == NULL)
+		return;
+	if (maxcard->irq >= 0)
+		free_irq(maxcard->irq, (void *)maxcard);
+}
+
+static int __devinit snd_gusmax_match(struct device *pdev, unsigned int dev)
+{
+	return enable[dev];
+}
+
+static int __devinit snd_gusmax_probe(struct device *pdev, unsigned int dev)
+{
+	static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1};
+	static int possible_dmas[] = {5, 6, 7, 1, 3, -1};
+	int xirq, xdma1, xdma2, err;
+	struct snd_card *card;
+	struct snd_gus_card *gus = NULL;
+	struct snd_wss *wss;
+	struct snd_gusmax *maxcard;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_gusmax), &card);
+	if (err < 0)
+		return err;
+	card->private_free = snd_gusmax_free;
+	maxcard = card->private_data;
+	maxcard->card = card;
+	maxcard->irq = -1;
+	
+	xirq = irq[dev];
+	if (xirq == SNDRV_AUTO_IRQ) {
+		if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			err = -EBUSY;
+			goto _err;
+		}
+	}
+	xdma1 = dma1[dev];
+	if (xdma1 == SNDRV_AUTO_DMA) {
+		if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA1\n");
+			err = -EBUSY;
+			goto _err;
+		}
+	}
+	xdma2 = dma2[dev];
+	if (xdma2 == SNDRV_AUTO_DMA) {
+		if ((xdma2 = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA2\n");
+			err = -EBUSY;
+			goto _err;
+		}
+	}
+
+	if (port[dev] != SNDRV_AUTO_PORT) {
+		err = snd_gus_create(card,
+				     port[dev],
+				     -xirq, xdma1, xdma2,
+				     0, channels[dev],
+				     pcm_channels[dev],
+				     0, &gus);
+	} else {
+		static unsigned long possible_ports[] = {
+			0x220, 0x230, 0x240, 0x250, 0x260
+		};
+		int i;
+		for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+			err = snd_gus_create(card,
+					     possible_ports[i],
+					     -xirq, xdma1, xdma2,
+					     0, channels[dev],
+					     pcm_channels[dev],
+					     0, &gus);
+			if (err >= 0) {
+				port[dev] = possible_ports[i];
+				break;
+			}
+		}
+	}
+	if (err < 0)
+		goto _err;
+
+	if ((err = snd_gusmax_detect(gus)) < 0)
+		goto _err;
+
+	maxcard->gus_status_reg = gus->gf1.reg_irqstat;
+	maxcard->pcm_status_reg = gus->gf1.port + 0x10c + 2;
+	snd_gusmax_init(dev, card, gus);
+	if ((err = snd_gus_initialize(gus)) < 0)
+		goto _err;
+
+	if (!gus->max_flag) {
+		snd_printk(KERN_ERR PFX "GUS MAX soundcard was not detected at 0x%lx\n", gus->gf1.port);
+		err = -ENODEV;
+		goto _err;
+	}
+
+	if (request_irq(xirq, snd_gusmax_interrupt, IRQF_DISABLED, "GUS MAX", (void *)maxcard)) {
+		snd_printk(KERN_ERR PFX "unable to grab IRQ %d\n", xirq);
+		err = -EBUSY;
+		goto _err;
+	}
+	maxcard->irq = xirq;
+	
+	err = snd_wss_create(card,
+			     gus->gf1.port + 0x10c, -1, xirq,
+			     xdma2 < 0 ? xdma1 : xdma2, xdma1,
+			     WSS_HW_DETECT,
+			     WSS_HWSHARE_IRQ |
+			     WSS_HWSHARE_DMA1 |
+			     WSS_HWSHARE_DMA2,
+			     &wss);
+	if (err < 0)
+		goto _err;
+
+	err = snd_wss_pcm(wss, 0, NULL);
+	if (err < 0)
+		goto _err;
+
+	err = snd_wss_mixer(wss);
+	if (err < 0)
+		goto _err;
+
+	err = snd_wss_timer(wss, 2, NULL);
+	if (err < 0)
+		goto _err;
+
+	if (pcm_channels[dev] > 0) {
+		if ((err = snd_gf1_pcm_new(gus, 1, 1, NULL)) < 0)
+			goto _err;
+	}
+	err = snd_gusmax_mixer(wss);
+	if (err < 0)
+		goto _err;
+
+	err = snd_gf1_rawmidi_new(gus, 0, NULL);
+	if (err < 0)
+		goto _err;
+
+	sprintf(card->longname + strlen(card->longname), " at 0x%lx, irq %i, dma %i", gus->gf1.port, xirq, xdma1);
+	if (xdma2 >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%i", xdma2);
+
+	snd_card_set_dev(card, pdev);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto _err;
+		
+	maxcard->gus = gus;
+	maxcard->wss = wss;
+
+	dev_set_drvdata(pdev, card);
+	return 0;
+
+ _err:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit snd_gusmax_remove(struct device *devptr, unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	dev_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#define DEV_NAME "gusmax"
+
+static struct isa_driver snd_gusmax_driver = {
+	.match		= snd_gusmax_match,
+	.probe		= snd_gusmax_probe,
+	.remove		= __devexit_p(snd_gusmax_remove),
+	/* FIXME: suspend/resume */
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+static int __init alsa_card_gusmax_init(void)
+{
+	return isa_register_driver(&snd_gusmax_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_gusmax_exit(void)
+{
+	isa_unregister_driver(&snd_gusmax_driver);
+}
+
+module_init(alsa_card_gusmax_init)
+module_exit(alsa_card_gusmax_exit)
diff --git a/linux/sound/isa/gus/interwave-stb.c b/linux/sound/isa/gus/interwave-stb.c
new file mode 100644
index 0000000..dbe4f48
--- /dev/null
+++ b/linux/sound/isa/gus/interwave-stb.c
@@ -0,0 +1,2 @@
+#define SNDRV_STB
+#include "interwave.c"
diff --git a/linux/sound/isa/gus/interwave.c b/linux/sound/isa/gus/interwave.c
new file mode 100644
index 0000000..c7b80e4
--- /dev/null
+++ b/linux/sound/isa/gus/interwave.c
@@ -0,0 +1,947 @@
+/*
+ *  Driver for AMD InterWave soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *   1999/07/22		Erik Inge Bolso <knan@mo.himolde.no>
+ *			* mixer group handlers
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+#include <sound/wss.h>
+#ifdef SNDRV_STB
+#include <sound/tea6330t.h>
+#endif
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+#ifndef SNDRV_STB
+MODULE_DESCRIPTION("AMD InterWave");
+MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Plug & Play},"
+		"{STB,SoundRage32},"
+		"{MED,MED3210},"
+		"{Dynasonix,Dynasonix Pro},"
+		"{Panasonic,PCA761AW}}");
+#else
+MODULE_DESCRIPTION("AMD InterWave STB with TEA6330T");
+MODULE_SUPPORTED_DEVICE("{{AMD,InterWave STB with TEA6330T}}");
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x210,0x220,0x230,0x240,0x250,0x260 */
+#ifdef SNDRV_STB
+static long port_tc[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x350,0x360,0x370,0x380 */
+#endif
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 2,3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
+static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
+				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
+static int midi[SNDRV_CARDS];
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+static int effect[SNDRV_CARDS];
+
+#ifdef SNDRV_STB
+#define PFX "interwave-stb: "
+#define INTERWAVE_DRIVER	"snd_interwave_stb"
+#define INTERWAVE_PNP_DRIVER	"interwave-stb"
+#else
+#define PFX "interwave: "
+#define INTERWAVE_DRIVER	"snd_interwave"
+#define INTERWAVE_PNP_DRIVER	"interwave"
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for InterWave soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for InterWave soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable InterWave soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for InterWave driver.");
+#ifdef SNDRV_STB
+module_param_array(port_tc, long, NULL, 0444);
+MODULE_PARM_DESC(port_tc, "Tone control (TEA6330T - i2c bus) port # for InterWave driver.");
+#endif
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for InterWave driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for InterWave driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for InterWave driver.");
+module_param_array(joystick_dac, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for InterWave driver.");
+module_param_array(midi, int, NULL, 0444);
+MODULE_PARM_DESC(midi, "MIDI UART enable for InterWave driver.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for InterWave driver.");
+module_param_array(effect, int, NULL, 0444);
+MODULE_PARM_DESC(effect, "Effects enable for InterWave driver.");
+
+struct snd_interwave {
+	int irq;
+	struct snd_card *card;
+	struct snd_gus_card *gus;
+	struct snd_wss *wss;
+#ifdef SNDRV_STB
+	struct resource *i2c_res;
+#endif
+	unsigned short gus_status_reg;
+	unsigned short pcm_status_reg;
+#ifdef CONFIG_PNP
+	struct pnp_dev *dev;
+#ifdef SNDRV_STB
+	struct pnp_dev *devtc;
+#endif
+#endif
+};
+
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+
+static struct pnp_card_device_id snd_interwave_pnpids[] = {
+#ifndef SNDRV_STB
+	/* Gravis UltraSound Plug & Play */
+	{ .id = "GRV0001", .devs = { { .id = "GRV0000" } } },
+	/* STB SoundRage32 */
+	{ .id = "STB011a", .devs = { { .id = "STB0010" } } },
+	/* MED3210 */
+	{ .id = "DXP3201", .devs = { { .id = "DXP0010" } } },
+	/* Dynasonic Pro */
+	/* This device also have CDC1117:DynaSonix Pro Audio Effects Processor */
+	{ .id = "CDC1111", .devs = { { .id = "CDC1112" } } },
+	/* Panasonic PCA761AW Audio Card */
+	{ .id = "ADV55ff", .devs = { { .id = "ADV0010" } } },
+	/* InterWave STB without TEA6330T */
+	{ .id = "ADV550a", .devs = { { .id = "ADV0010" } } },
+#else
+	/* InterWave STB with TEA6330T */
+	{ .id = "ADV550a", .devs = { { .id = "ADV0010" }, { .id = "ADV0015" } } },
+#endif
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_interwave_pnpids);
+
+#endif /* CONFIG_PNP */
+
+
+#ifdef SNDRV_STB
+static void snd_interwave_i2c_setlines(struct snd_i2c_bus *bus, int ctrl, int data)
+{
+	unsigned long port = bus->private_value;
+
+#if 0
+	printk(KERN_DEBUG "i2c_setlines - 0x%lx <- %i,%i\n", port, ctrl, data);
+#endif
+	outb((data << 1) | ctrl, port);
+	udelay(10);
+}
+
+static int snd_interwave_i2c_getclockline(struct snd_i2c_bus *bus)
+{
+	unsigned long port = bus->private_value;
+	unsigned char res;
+
+	res = inb(port) & 1;
+#if 0
+	printk(KERN_DEBUG "i2c_getclockline - 0x%lx -> %i\n", port, res);
+#endif
+	return res;
+}
+
+static int snd_interwave_i2c_getdataline(struct snd_i2c_bus *bus, int ack)
+{
+	unsigned long port = bus->private_value;
+	unsigned char res;
+
+	if (ack)
+		udelay(10);
+	res = (inb(port) & 2) >> 1;
+#if 0
+	printk(KERN_DEBUG "i2c_getdataline - 0x%lx -> %i\n", port, res);
+#endif
+	return res;
+}
+
+static struct snd_i2c_bit_ops snd_interwave_i2c_bit_ops = {
+	.setlines = snd_interwave_i2c_setlines,
+	.getclock = snd_interwave_i2c_getclockline,
+	.getdata  = snd_interwave_i2c_getdataline,
+};
+
+static int __devinit snd_interwave_detect_stb(struct snd_interwave *iwcard,
+					      struct snd_gus_card * gus, int dev,
+					      struct snd_i2c_bus **rbus)
+{
+	unsigned long port;
+	struct snd_i2c_bus *bus;
+	struct snd_card *card = iwcard->card;
+	char name[32];
+	int err;
+
+	*rbus = NULL;
+	port = port_tc[dev];
+	if (port == SNDRV_AUTO_PORT) {
+		port = 0x350;
+		if (gus->gf1.port == 0x250) {
+			port = 0x360;
+		}
+		while (port <= 0x380) {
+			if ((iwcard->i2c_res = request_region(port, 1, "InterWave (I2C bus)")) != NULL)
+				break;
+			port += 0x10;
+		}
+	} else {
+		iwcard->i2c_res = request_region(port, 1, "InterWave (I2C bus)");
+	}
+	if (iwcard->i2c_res == NULL) {
+		snd_printk(KERN_ERR "interwave: can't grab i2c bus port\n");
+		return -ENODEV;
+	}
+
+	sprintf(name, "InterWave-%i", card->number);
+	if ((err = snd_i2c_bus_create(card, name, NULL, &bus)) < 0)
+		return err;
+	bus->private_value = port;
+	bus->hw_ops.bit = &snd_interwave_i2c_bit_ops;
+	if ((err = snd_tea6330t_detect(bus, 0)) < 0)
+		return err;
+	*rbus = bus;
+	return 0;
+}
+#endif
+
+static int __devinit snd_interwave_detect(struct snd_interwave *iwcard,
+				          struct snd_gus_card * gus,
+				          int dev
+#ifdef SNDRV_STB
+				          , struct snd_i2c_bus **rbus
+#endif
+				          )
+{
+	unsigned long flags;
+	unsigned char rev1, rev2;
+	int d;
+
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
+		snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+	udelay(160);
+	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
+	udelay(160);
+	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
+		snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
+		return -ENODEV;
+	}
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	rev1 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, ~rev1);
+	rev2 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, rev1);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	snd_printdd("[0x%lx] InterWave check - rev1=0x%x, rev2=0x%x\n", gus->gf1.port, rev1, rev2);
+	if ((rev1 & 0xf0) == (rev2 & 0xf0) &&
+	    (rev1 & 0x0f) != (rev2 & 0x0f)) {
+		snd_printdd("[0x%lx] InterWave check - passed\n", gus->gf1.port);
+		gus->interwave = 1;
+		strcpy(gus->card->shortname, "AMD InterWave");
+		gus->revision = rev1 >> 4;
+#ifndef SNDRV_STB
+		return 0;	/* ok.. We have an InterWave board */
+#else
+		return snd_interwave_detect_stb(iwcard, gus, dev, rbus);
+#endif
+	}
+	snd_printdd("[0x%lx] InterWave check - failed\n", gus->gf1.port);
+	return -ENODEV;
+}
+
+static irqreturn_t snd_interwave_interrupt(int irq, void *dev_id)
+{
+	struct snd_interwave *iwcard = dev_id;
+	int loop, max = 5;
+	int handled = 0;
+
+	do {
+		loop = 0;
+		if (inb(iwcard->gus_status_reg)) {
+			handled = 1;
+			snd_gus_interrupt(irq, iwcard->gus);
+			loop++;
+		}
+		if (inb(iwcard->pcm_status_reg) & 0x01) {	/* IRQ bit is set? */
+			handled = 1;
+			snd_wss_interrupt(irq, iwcard->wss);
+			loop++;
+		}
+	} while (loop && --max > 0);
+	return IRQ_RETVAL(handled);
+}
+
+static void __devinit snd_interwave_reset(struct snd_gus_card * gus)
+{
+	snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x00);
+	udelay(160);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x01);
+	udelay(160);
+}
+
+static void __devinit snd_interwave_bank_sizes(struct snd_gus_card * gus, int *sizes)
+{
+	unsigned int idx;
+	unsigned int local;
+	unsigned char d;
+
+	for (idx = 0; idx < 4; idx++) {
+		sizes[idx] = 0;
+		d = 0x55;
+		for (local = idx << 22;
+		     local < (idx << 22) + 0x400000;
+		     local += 0x40000, d++) {
+			snd_gf1_poke(gus, local, d);
+			snd_gf1_poke(gus, local + 1, d + 1);
+#if 0
+			printk(KERN_DEBUG "d = 0x%x, local = 0x%x, "
+			       "local + 1 = 0x%x, idx << 22 = 0x%x\n",
+			       d,
+			       snd_gf1_peek(gus, local),
+			       snd_gf1_peek(gus, local + 1),
+			       snd_gf1_peek(gus, idx << 22));
+#endif
+			if (snd_gf1_peek(gus, local) != d ||
+			    snd_gf1_peek(gus, local + 1) != d + 1 ||
+			    snd_gf1_peek(gus, idx << 22) != 0x55)
+				break;
+			sizes[idx]++;
+		}
+	}
+#if 0
+	printk(KERN_DEBUG "sizes: %i %i %i %i\n",
+	       sizes[0], sizes[1], sizes[2], sizes[3]);
+#endif
+}
+
+struct rom_hdr {
+	/* 000 */ unsigned char iwave[8];
+	/* 008 */ unsigned char rom_hdr_revision;
+	/* 009 */ unsigned char series_number;
+	/* 010 */ unsigned char series_name[16];
+	/* 026 */ unsigned char date[10];
+	/* 036 */ unsigned short vendor_revision_major;
+	/* 038 */ unsigned short vendor_revision_minor;
+	/* 040 */ unsigned int rom_size;
+	/* 044 */ unsigned char copyright[128];
+	/* 172 */ unsigned char vendor_name[64];
+	/* 236 */ unsigned char rom_description[128];
+	/* 364 */ unsigned char pad[147];
+	/* 511 */ unsigned char csum;
+};
+
+static void __devinit snd_interwave_detect_memory(struct snd_gus_card * gus)
+{
+	static unsigned int lmc[13] =
+	{
+		0x00000001, 0x00000101, 0x01010101, 0x00000401,
+		0x04040401, 0x00040101, 0x04040101, 0x00000004,
+		0x00000404, 0x04040404, 0x00000010, 0x00001010,
+		0x10101010
+	};
+
+	int bank_pos, pages;
+	unsigned int i, lmct;
+	int psizes[4];
+	unsigned char iwave[8];
+	unsigned char csum;
+
+	snd_interwave_reset(gus);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_read8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01);		/* enhanced mode */
+	snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);	/* DRAM I/O cycles selected */
+	snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff10) | 0x004c);
+	/* ok.. simple test of memory size */
+	pages = 0;
+	snd_gf1_poke(gus, 0, 0x55);
+	snd_gf1_poke(gus, 1, 0xaa);
+#if 1
+	if (snd_gf1_peek(gus, 0) == 0x55 && snd_gf1_peek(gus, 1) == 0xaa)
+#else
+	if (0)			/* ok.. for testing of 0k RAM */
+#endif
+	{
+		snd_interwave_bank_sizes(gus, psizes);
+		lmct = (psizes[3] << 24) | (psizes[2] << 16) |
+		    (psizes[1] << 8) | psizes[0];
+#if 0
+		printk(KERN_DEBUG "lmct = 0x%08x\n", lmct);
+#endif
+		for (i = 0; i < ARRAY_SIZE(lmc); i++)
+			if (lmct == lmc[i]) {
+#if 0
+				printk(KERN_DEBUG "found !!! %i\n", i);
+#endif
+				snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | i);
+				snd_interwave_bank_sizes(gus, psizes);
+				break;
+			}
+		if (i >= ARRAY_SIZE(lmc) && !gus->gf1.enh_mode)
+			 snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | 2);
+		for (i = 0; i < 4; i++) {
+			gus->gf1.mem_alloc.banks_8[i].address =
+			    gus->gf1.mem_alloc.banks_16[i].address = i << 22;
+			gus->gf1.mem_alloc.banks_8[i].size =
+			    gus->gf1.mem_alloc.banks_16[i].size = psizes[i] << 18;
+			pages += psizes[i];
+		}
+	}
+	pages <<= 18;
+	gus->gf1.memory = pages;
+
+	snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x03);	/* select ROM */
+	snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff1f) | (4 << 5));
+	gus->gf1.rom_banks = 0;
+	gus->gf1.rom_memory = 0;
+	for (bank_pos = 0; bank_pos < 16L * 1024L * 1024L; bank_pos += 4L * 1024L * 1024L) {
+		for (i = 0; i < 8; ++i)
+			iwave[i] = snd_gf1_peek(gus, bank_pos + i);
+#ifdef CONFIG_SND_DEBUG_ROM
+		printk(KERN_DEBUG "ROM at 0x%06x = %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", bank_pos,
+		       iwave[0], iwave[1], iwave[2], iwave[3],
+		       iwave[4], iwave[5], iwave[6], iwave[7]);
+#endif
+		if (strncmp(iwave, "INTRWAVE", 8))
+			continue;	/* first check */
+		csum = 0;
+		for (i = 0; i < sizeof(struct rom_hdr); i++)
+			csum += snd_gf1_peek(gus, bank_pos + i);
+#ifdef CONFIG_SND_DEBUG_ROM
+		printk(KERN_DEBUG "ROM checksum = 0x%x (computed)\n", csum);
+#endif
+		if (csum != 0)
+			continue;	/* not valid rom */
+		gus->gf1.rom_banks++;
+		gus->gf1.rom_present |= 1 << (bank_pos >> 22);
+		gus->gf1.rom_memory = snd_gf1_peek(gus, bank_pos + 40) |
+				     (snd_gf1_peek(gus, bank_pos + 41) << 8) |
+				     (snd_gf1_peek(gus, bank_pos + 42) << 16) |
+				     (snd_gf1_peek(gus, bank_pos + 43) << 24);
+	}
+#if 0
+	if (gus->gf1.rom_memory > 0) {
+		if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8)
+			gus->card->type = SNDRV_CARD_TYPE_IW_DYNASONIC;
+	}
+#endif
+	snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x00);	/* select RAM */
+
+	if (!gus->gf1.enh_mode)
+		snd_interwave_reset(gus);
+}
+
+static void __devinit snd_interwave_init(int dev, struct snd_gus_card * gus)
+{
+	unsigned long flags;
+
+	/* ok.. some InterWave specific initialization */
+	spin_lock_irqsave(&gus->reg_lock, flags);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0x00);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30);
+	snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00);
+	spin_unlock_irqrestore(&gus->reg_lock, flags);
+	gus->equal_irq = 1;
+	gus->codec_flag = 1;
+	gus->interwave = 1;
+	gus->max_flag = 1;
+	gus->joystick_dac = joystick_dac[dev];
+
+}
+
+static struct snd_kcontrol_new snd_interwave_controls[] = {
+WSS_DOUBLE("Master Playback Switch", 0,
+		CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE("Master Playback Volume", 0,
+		CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 0, 0, 31, 1),
+WSS_DOUBLE("Mic Playback Switch", 0,
+		CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE("Mic Playback Volume", 0,
+		CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 0, 0, 31, 1)
+};
+
+static int __devinit snd_interwave_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card = chip->card;
+	struct snd_ctl_elem_id id1, id2;
+	unsigned int idx;
+	int err;
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+#if 0
+	/* remove mono microphone controls */
+	strcpy(id1.name, "Mic Playback Switch");
+	if ((err = snd_ctl_remove_id(card, &id1)) < 0)
+		return err;
+	strcpy(id1.name, "Mic Playback Volume");
+	if ((err = snd_ctl_remove_id(card, &id1)) < 0)
+		return err;
+#endif
+	/* add new master and mic controls */
+	for (idx = 0; idx < ARRAY_SIZE(snd_interwave_controls); idx++)
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_interwave_controls[idx], chip))) < 0)
+			return err;
+	snd_wss_out(chip, CS4231_LINE_LEFT_OUTPUT, 0x9f);
+	snd_wss_out(chip, CS4231_LINE_RIGHT_OUTPUT, 0x9f);
+	snd_wss_out(chip, CS4231_LEFT_MIC_INPUT, 0x9f);
+	snd_wss_out(chip, CS4231_RIGHT_MIC_INPUT, 0x9f);
+	/* reassign AUXA to SYNTHESIZER */
+	strcpy(id1.name, "Aux Playback Switch");
+	strcpy(id2.name, "Synth Playback Switch");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "Synth Playback Volume");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	/* reassign AUXB to CD */
+	strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+	strcpy(id2.name, "CD Playback Switch");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "CD Playback Volume");
+	if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+		return err;
+	return 0;
+}
+
+#ifdef CONFIG_PNP
+
+static int __devinit snd_interwave_pnp(int dev, struct snd_interwave *iwcard,
+				       struct pnp_card_link *card,
+				       const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	int err;
+
+	iwcard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (iwcard->dev == NULL)
+		return -EBUSY;
+
+#ifdef SNDRV_STB
+	iwcard->devtc = pnp_request_card_device(card, id->devs[1].id, NULL);
+	if (iwcard->devtc == NULL)
+		return -EBUSY;
+#endif
+	/* Synth & Codec initialization */
+	pdev = iwcard->dev;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "InterWave PnP configure failure (out of resources?)\n");
+		return err;
+	}
+	if (pnp_port_start(pdev, 0) + 0x100 != pnp_port_start(pdev, 1) ||
+	    pnp_port_start(pdev, 0) + 0x10c != pnp_port_start(pdev, 2)) {
+		snd_printk(KERN_ERR "PnP configure failure (wrong ports)\n");
+		return -ENOENT;
+	}
+	port[dev] = pnp_port_start(pdev, 0);
+	dma1[dev] = pnp_dma(pdev, 0);
+	if (dma2[dev] >= 0)
+		dma2[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+	snd_printdd("isapnp IW: sb port=0x%llx, gf1 port=0x%llx, codec port=0x%llx\n",
+			(unsigned long long)pnp_port_start(pdev, 0),
+			(unsigned long long)pnp_port_start(pdev, 1),
+			(unsigned long long)pnp_port_start(pdev, 2));
+	snd_printdd("isapnp IW: dma1=%i, dma2=%i, irq=%i\n", dma1[dev], dma2[dev], irq[dev]);
+#ifdef SNDRV_STB
+	/* Tone Control initialization */
+	pdev = iwcard->devtc;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "InterWave ToneControl PnP configure failure (out of resources?)\n");
+		return err;
+	}
+	port_tc[dev] = pnp_port_start(pdev, 0);
+	snd_printdd("isapnp IW: tone control port=0x%lx\n", port_tc[dev]);
+#endif
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+static void snd_interwave_free(struct snd_card *card)
+{
+	struct snd_interwave *iwcard = card->private_data;
+
+	if (iwcard == NULL)
+		return;
+#ifdef SNDRV_STB
+	release_and_free_resource(iwcard->i2c_res);
+#endif
+	if (iwcard->irq >= 0)
+		free_irq(iwcard->irq, (void *)iwcard);
+}
+
+static int snd_interwave_card_new(int dev, struct snd_card **cardp)
+{
+	struct snd_card *card;
+	struct snd_interwave *iwcard;
+	int err;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_interwave), &card);
+	if (err < 0)
+		return err;
+	iwcard = card->private_data;
+	iwcard->card = card;
+	iwcard->irq = -1;
+	card->private_free = snd_interwave_free;
+	*cardp = card;
+	return 0;
+}
+
+static int __devinit snd_interwave_probe(struct snd_card *card, int dev)
+{
+	int xirq, xdma1, xdma2;
+	struct snd_interwave *iwcard = card->private_data;
+	struct snd_wss *wss;
+	struct snd_gus_card *gus;
+#ifdef SNDRV_STB
+	struct snd_i2c_bus *i2c_bus;
+#endif
+	struct snd_pcm *pcm;
+	char *str;
+	int err;
+
+	xirq = irq[dev];
+	xdma1 = dma1[dev];
+	xdma2 = dma2[dev];
+
+	if ((err = snd_gus_create(card,
+				  port[dev],
+				  -xirq, xdma1, xdma2,
+				  0, 32,
+				  pcm_channels[dev], effect[dev], &gus)) < 0)
+		return err;
+
+	if ((err = snd_interwave_detect(iwcard, gus, dev
+#ifdef SNDRV_STB
+            , &i2c_bus
+#endif
+	    )) < 0)
+		return err;
+
+	iwcard->gus_status_reg = gus->gf1.reg_irqstat;
+	iwcard->pcm_status_reg = gus->gf1.port + 0x10c + 2;
+
+	snd_interwave_init(dev, gus);
+	snd_interwave_detect_memory(gus);
+	if ((err = snd_gus_initialize(gus)) < 0)
+		return err;
+
+	if (request_irq(xirq, snd_interwave_interrupt, IRQF_DISABLED,
+			"InterWave", iwcard)) {
+		snd_printk(KERN_ERR PFX "unable to grab IRQ %d\n", xirq);
+		return -EBUSY;
+	}
+	iwcard->irq = xirq;
+
+	err = snd_wss_create(card,
+			     gus->gf1.port + 0x10c, -1, xirq,
+			     xdma2 < 0 ? xdma1 : xdma2, xdma1,
+			     WSS_HW_INTERWAVE,
+			     WSS_HWSHARE_IRQ |
+			     WSS_HWSHARE_DMA1 |
+			     WSS_HWSHARE_DMA2,
+			     &wss);
+	if (err < 0)
+		return err;
+
+	err = snd_wss_pcm(wss, 0, &pcm);
+	if (err < 0)
+		return err;
+
+	sprintf(pcm->name + strlen(pcm->name), " rev %c", gus->revision + 'A');
+	strcat(pcm->name, " (codec)");
+
+	err = snd_wss_timer(wss, 2, NULL);
+	if (err < 0)
+		return err;
+
+	err = snd_wss_mixer(wss);
+	if (err < 0)
+		return err;
+
+	if (pcm_channels[dev] > 0) {
+		err = snd_gf1_pcm_new(gus, 1, 1, NULL);
+		if (err < 0)
+			return err;
+	}
+	err = snd_interwave_mixer(wss);
+	if (err < 0)
+		return err;
+
+#ifdef SNDRV_STB
+	{
+		struct snd_ctl_elem_id id1, id2;
+		memset(&id1, 0, sizeof(id1));
+		memset(&id2, 0, sizeof(id2));
+		id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+		strcpy(id1.name, "Master Playback Switch");
+		strcpy(id2.name, id1.name);
+		id2.index = 1;
+		if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+			return err;
+		strcpy(id1.name, "Master Playback Volume");
+		strcpy(id2.name, id1.name);
+		if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0)
+			return err;
+		if ((err = snd_tea6330t_update_mixer(card, i2c_bus, 0, 1)) < 0)
+			return err;
+	}
+#endif
+
+	gus->uart_enable = midi[dev];
+	if ((err = snd_gf1_rawmidi_new(gus, 0, NULL)) < 0)
+		return err;
+
+#ifndef SNDRV_STB
+	str = "AMD InterWave";
+	if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8)
+		str = "Dynasonic 3-D";
+#else
+	str = "InterWave STB";
+#endif
+	strcpy(card->driver, str);
+	strcpy(card->shortname, str);
+	sprintf(card->longname, "%s at 0x%lx, irq %i, dma %d",
+		str,
+		gus->gf1.port,
+		xirq,
+		xdma1);
+	if (xdma2 >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%d", xdma2);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		return err;
+	
+	iwcard->wss = wss;
+	iwcard->gus = gus;
+	return 0;
+}
+
+static int __devinit snd_interwave_isa_probe1(int dev, struct device *devptr)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_interwave_card_new(dev, &card);
+	if (err < 0)
+		return err;
+
+	snd_card_set_dev(card, devptr);
+	if ((err = snd_interwave_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(devptr, card);
+	return 0;
+}
+
+static int __devinit snd_interwave_isa_match(struct device *pdev,
+					     unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+#ifdef CONFIG_PNP
+	if (isapnp[dev])
+		return 0;
+#endif
+	return 1;
+}
+
+static int __devinit snd_interwave_isa_probe(struct device *pdev,
+					     unsigned int dev)
+{
+	int err;
+	static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1};
+	static int possible_dmas[] = {0, 1, 3, 5, 6, 7, -1};
+
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		if ((irq[dev] = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1[dev] == SNDRV_AUTO_DMA) {
+		if ((dma1[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	if (dma2[dev] == SNDRV_AUTO_DMA) {
+		if ((dma2[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[dev] != SNDRV_AUTO_PORT)
+		return snd_interwave_isa_probe1(dev, pdev);
+	else {
+		static long possible_ports[] = {0x210, 0x220, 0x230, 0x240, 0x250, 0x260};
+		int i;
+		for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+			port[dev] = possible_ports[i];
+			err = snd_interwave_isa_probe1(dev, pdev);
+			if (! err)
+				return 0;
+		}
+		return err;
+	}
+}
+
+static int __devexit snd_interwave_isa_remove(struct device *devptr, unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	dev_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+static struct isa_driver snd_interwave_driver = {
+	.match		= snd_interwave_isa_match,
+	.probe		= snd_interwave_isa_probe,
+	.remove		= __devexit_p(snd_interwave_isa_remove),
+	/* FIXME: suspend,resume */
+	.driver		= {
+		.name	= INTERWAVE_DRIVER
+	},
+};
+
+#ifdef CONFIG_PNP
+static int __devinit snd_interwave_pnp_detect(struct pnp_card_link *pcard,
+					      const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+				
+	res = snd_interwave_card_new(dev, &card);
+	if (res < 0)
+		return res;
+
+	if ((res = snd_interwave_pnp(dev, card->private_data, pcard, pid)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+	if ((res = snd_interwave_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return res;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_interwave_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+static struct pnp_card_driver interwave_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = INTERWAVE_PNP_DRIVER,
+	.id_table = snd_interwave_pnpids,
+	.probe = snd_interwave_pnp_detect,
+	.remove = __devexit_p(snd_interwave_pnp_remove),
+	/* FIXME: suspend,resume */
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_interwave_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_interwave_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&interwave_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_interwave_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&interwave_pnpc_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_interwave_driver);
+}
+
+module_init(alsa_card_interwave_init)
+module_exit(alsa_card_interwave_exit)
diff --git a/linux/sound/isa/msnd/Makefile b/linux/sound/isa/msnd/Makefile
new file mode 100644
index 0000000..2171c0a
--- /dev/null
+++ b/linux/sound/isa/msnd/Makefile
@@ -0,0 +1,9 @@
+
+snd-msnd-lib-objs := msnd.o msnd_midi.o msnd_pinnacle_mixer.o
+snd-msnd-pinnacle-objs := msnd_pinnacle.o
+snd-msnd-classic-objs := msnd_classic.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_MSND_PINNACLE) += snd-msnd-pinnacle.o snd-msnd-lib.o
+obj-$(CONFIG_SND_MSND_CLASSIC) += snd-msnd-classic.o snd-msnd-lib.o
+
diff --git a/linux/sound/isa/msnd/msnd.c b/linux/sound/isa/msnd/msnd.c
new file mode 100644
index 0000000..3a1526a
--- /dev/null
+++ b/linux/sound/isa/msnd/msnd.c
@@ -0,0 +1,707 @@
+/*********************************************************************
+ *
+ * 2002/06/30 Karsten Wiese:
+ *	removed kernel-version dependencies.
+ *	ripped from linux kernel 2.4.18 (OSS Implementation) by me.
+ *	In the OSS Version, this file is compiled to a separate MODULE,
+ *	that is used by the pinnacle and the classic driver.
+ *	since there is no classic driver for alsa yet (i dont have a classic
+ *	& writing one blindfold is difficult) this file's object is statically
+ *	linked into the pinnacle-driver-module for now.	look for the string
+ *		"uncomment this to make this a module again"
+ *	to do guess what.
+ *
+ * the following is a copy of the 2.4.18 OSS FREE file-heading comment:
+ *
+ * msnd.c - Driver Base
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "msnd.h"
+
+#define LOGNAME			"msnd"
+
+
+void snd_msnd_init_queue(void *base, int start, int size)
+{
+	writew(PCTODSP_BASED(start), base + JQS_wStart);
+	writew(PCTODSP_OFFSET(size) - 1, base + JQS_wSize);
+	writew(0, base + JQS_wHead);
+	writew(0, base + JQS_wTail);
+}
+EXPORT_SYMBOL(snd_msnd_init_queue);
+
+static int snd_msnd_wait_TXDE(struct snd_msnd *dev)
+{
+	unsigned int io = dev->io;
+	int timeout = 1000;
+
+	while (timeout-- > 0)
+		if (inb(io + HP_ISR) & HPISR_TXDE)
+			return 0;
+
+	return -EIO;
+}
+
+static int snd_msnd_wait_HC0(struct snd_msnd *dev)
+{
+	unsigned int io = dev->io;
+	int timeout = 1000;
+
+	while (timeout-- > 0)
+		if (!(inb(io + HP_CVR) & HPCVR_HC))
+			return 0;
+
+	return -EIO;
+}
+
+int snd_msnd_send_dsp_cmd(struct snd_msnd *dev, u8 cmd)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (snd_msnd_wait_HC0(dev) == 0) {
+		outb(cmd, dev->io + HP_CVR);
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return 0;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	snd_printd(KERN_ERR LOGNAME ": Send DSP command timeout\n");
+
+	return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_send_dsp_cmd);
+
+int snd_msnd_send_word(struct snd_msnd *dev, unsigned char high,
+		   unsigned char mid, unsigned char low)
+{
+	unsigned int io = dev->io;
+
+	if (snd_msnd_wait_TXDE(dev) == 0) {
+		outb(high, io + HP_TXH);
+		outb(mid, io + HP_TXM);
+		outb(low, io + HP_TXL);
+		return 0;
+	}
+
+	snd_printd(KERN_ERR LOGNAME ": Send host word timeout\n");
+
+	return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_send_word);
+
+int snd_msnd_upload_host(struct snd_msnd *dev, const u8 *bin, int len)
+{
+	int i;
+
+	if (len % 3 != 0) {
+		snd_printk(KERN_ERR LOGNAME
+			   ": Upload host data not multiple of 3!\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < len; i += 3)
+		if (snd_msnd_send_word(dev, bin[i], bin[i + 1], bin[i + 2]))
+			return -EIO;
+
+	inb(dev->io + HP_RXL);
+	inb(dev->io + HP_CVR);
+
+	return 0;
+}
+EXPORT_SYMBOL(snd_msnd_upload_host);
+
+int snd_msnd_enable_irq(struct snd_msnd *dev)
+{
+	unsigned long flags;
+
+	if (dev->irq_ref++)
+		return 0;
+
+	snd_printdd(LOGNAME ": Enabling IRQ\n");
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (snd_msnd_wait_TXDE(dev) == 0) {
+		outb(inb(dev->io + HP_ICR) | HPICR_TREQ, dev->io + HP_ICR);
+		if (dev->type == msndClassic)
+			outb(dev->irqid, dev->io + HP_IRQM);
+
+		outb(inb(dev->io + HP_ICR) & ~HPICR_TREQ, dev->io + HP_ICR);
+		outb(inb(dev->io + HP_ICR) | HPICR_RREQ, dev->io + HP_ICR);
+		enable_irq(dev->irq);
+		snd_msnd_init_queue(dev->DSPQ, dev->dspq_data_buff,
+				    dev->dspq_buff_size);
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return 0;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	snd_printd(KERN_ERR LOGNAME ": Enable IRQ failed\n");
+
+	return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_enable_irq);
+
+int snd_msnd_disable_irq(struct snd_msnd *dev)
+{
+	unsigned long flags;
+
+	if (--dev->irq_ref > 0)
+		return 0;
+
+	if (dev->irq_ref < 0)
+		snd_printd(KERN_WARNING LOGNAME ": IRQ ref count is %d\n",
+			   dev->irq_ref);
+
+	snd_printdd(LOGNAME ": Disabling IRQ\n");
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (snd_msnd_wait_TXDE(dev) == 0) {
+		outb(inb(dev->io + HP_ICR) & ~HPICR_RREQ, dev->io + HP_ICR);
+		if (dev->type == msndClassic)
+			outb(HPIRQ_NONE, dev->io + HP_IRQM);
+		disable_irq(dev->irq);
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return 0;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	snd_printd(KERN_ERR LOGNAME ": Disable IRQ failed\n");
+
+	return -EIO;
+}
+EXPORT_SYMBOL(snd_msnd_disable_irq);
+
+static inline long get_play_delay_jiffies(struct snd_msnd *chip, long size)
+{
+	long tmp = (size * HZ * chip->play_sample_size) / 8;
+	return tmp / (chip->play_sample_rate * chip->play_channels);
+}
+
+static void snd_msnd_dsp_write_flush(struct snd_msnd *chip)
+{
+	if (!(chip->mode & FMODE_WRITE) || !test_bit(F_WRITING, &chip->flags))
+		return;
+	set_bit(F_WRITEFLUSH, &chip->flags);
+/*	interruptible_sleep_on_timeout(
+		&chip->writeflush,
+		get_play_delay_jiffies(&chip, chip->DAPF.len));*/
+	clear_bit(F_WRITEFLUSH, &chip->flags);
+	if (!signal_pending(current))
+		schedule_timeout_interruptible(
+			get_play_delay_jiffies(chip, chip->play_period_bytes));
+	clear_bit(F_WRITING, &chip->flags);
+}
+
+void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file)
+{
+	if ((file ? file->f_mode : chip->mode) & FMODE_READ) {
+		clear_bit(F_READING, &chip->flags);
+		snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP);
+		snd_msnd_disable_irq(chip);
+		if (file) {
+			snd_printd(KERN_INFO LOGNAME
+				   ": Stopping read for %p\n", file);
+			chip->mode &= ~FMODE_READ;
+		}
+		clear_bit(F_AUDIO_READ_INUSE, &chip->flags);
+	}
+	if ((file ? file->f_mode : chip->mode) & FMODE_WRITE) {
+		if (test_bit(F_WRITING, &chip->flags)) {
+			snd_msnd_dsp_write_flush(chip);
+			snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP);
+		}
+		snd_msnd_disable_irq(chip);
+		if (file) {
+			snd_printd(KERN_INFO
+				   LOGNAME ": Stopping write for %p\n", file);
+			chip->mode &= ~FMODE_WRITE;
+		}
+		clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+	}
+}
+EXPORT_SYMBOL(snd_msnd_dsp_halt);
+
+
+int snd_msnd_DARQ(struct snd_msnd *chip, int bank)
+{
+	int /*size, n,*/ timeout = 3;
+	u16 wTmp;
+	/* void *DAQD; */
+
+	/* Increment the tail and check for queue wrap */
+	wTmp = readw(chip->DARQ + JQS_wTail) + PCTODSP_OFFSET(DAQDS__size);
+	if (wTmp > readw(chip->DARQ + JQS_wSize))
+		wTmp = 0;
+	while (wTmp == readw(chip->DARQ + JQS_wHead) && timeout--)
+		udelay(1);
+
+	if (chip->capturePeriods == 2) {
+		void *pDAQ = chip->mappedbase + DARQ_DATA_BUFF +
+			     bank * DAQDS__size + DAQDS_wStart;
+		unsigned short offset = 0x3000 + chip->capturePeriodBytes;
+
+		if (readw(pDAQ) != PCTODSP_BASED(0x3000))
+			offset = 0x3000;
+		writew(PCTODSP_BASED(offset), pDAQ);
+	}
+
+	writew(wTmp, chip->DARQ + JQS_wTail);
+
+#if 0
+	/* Get our digital audio queue struct */
+	DAQD = bank * DAQDS__size + chip->mappedbase + DARQ_DATA_BUFF;
+
+	/* Get length of data */
+	size = readw(DAQD + DAQDS_wSize);
+
+	/* Read data from the head (unprotected bank 1 access okay
+	   since this is only called inside an interrupt) */
+	outb(HPBLKSEL_1, chip->io + HP_BLKS);
+	n = msnd_fifo_write(&chip->DARF,
+			    (char *)(chip->base + bank * DAR_BUFF_SIZE),
+			    size, 0);
+	if (n <= 0) {
+		outb(HPBLKSEL_0, chip->io + HP_BLKS);
+		return n;
+	}
+	outb(HPBLKSEL_0, chip->io + HP_BLKS);
+#endif
+
+	return 1;
+}
+EXPORT_SYMBOL(snd_msnd_DARQ);
+
+int snd_msnd_DAPQ(struct snd_msnd *chip, int start)
+{
+	u16	DAPQ_tail;
+	int	protect = start, nbanks = 0;
+	void	*DAQD;
+	static int play_banks_submitted;
+	/* unsigned long flags;
+	spin_lock_irqsave(&chip->lock, flags); not necessary */
+
+	DAPQ_tail = readw(chip->DAPQ + JQS_wTail);
+	while (DAPQ_tail != readw(chip->DAPQ + JQS_wHead) || start) {
+		int bank_num = DAPQ_tail / PCTODSP_OFFSET(DAQDS__size);
+
+		if (start) {
+			start = 0;
+			play_banks_submitted = 0;
+		}
+
+		/* Get our digital audio queue struct */
+		DAQD = bank_num * DAQDS__size + chip->mappedbase +
+			DAPQ_DATA_BUFF;
+
+		/* Write size of this bank */
+		writew(chip->play_period_bytes, DAQD + DAQDS_wSize);
+		if (play_banks_submitted < 3)
+			++play_banks_submitted;
+		else if (chip->playPeriods == 2) {
+			unsigned short offset = chip->play_period_bytes;
+
+			if (readw(DAQD + DAQDS_wStart) != PCTODSP_BASED(0x0))
+				offset = 0;
+
+			writew(PCTODSP_BASED(offset), DAQD + DAQDS_wStart);
+		}
+		++nbanks;
+
+		/* Then advance the tail */
+		/*
+		if (protect)
+			snd_printd(KERN_INFO "B %X %lX\n",
+				   bank_num, xtime.tv_usec);
+		*/
+
+		DAPQ_tail = (++bank_num % 3) * PCTODSP_OFFSET(DAQDS__size);
+		writew(DAPQ_tail, chip->DAPQ + JQS_wTail);
+		/* Tell the DSP to play the bank */
+		snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_START);
+		if (protect)
+			if (2 == bank_num)
+				break;
+	}
+	/*
+	if (protect)
+		snd_printd(KERN_INFO "%lX\n", xtime.tv_usec);
+	*/
+	/* spin_unlock_irqrestore(&chip->lock, flags); not necessary */
+	return nbanks;
+}
+EXPORT_SYMBOL(snd_msnd_DAPQ);
+
+static void snd_msnd_play_reset_queue(struct snd_msnd *chip,
+				      unsigned int pcm_periods,
+				      unsigned int pcm_count)
+{
+	int	n;
+	void	*pDAQ = chip->mappedbase + DAPQ_DATA_BUFF;
+
+	chip->last_playbank = -1;
+	chip->playLimit = pcm_count * (pcm_periods - 1);
+	chip->playPeriods = pcm_periods;
+	writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wHead);
+	writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wTail);
+
+	chip->play_period_bytes = pcm_count;
+
+	for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) {
+		writew(PCTODSP_BASED((u32)(pcm_count * n)),
+			pDAQ + DAQDS_wStart);
+		writew(0, pDAQ + DAQDS_wSize);
+		writew(1, pDAQ + DAQDS_wFormat);
+		writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize);
+		writew(chip->play_channels, pDAQ + DAQDS_wChannels);
+		writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate);
+		writew(HIMT_PLAY_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg);
+		writew(n, pDAQ + DAQDS_wFlags);
+	}
+}
+
+static void snd_msnd_capture_reset_queue(struct snd_msnd *chip,
+					 unsigned int pcm_periods,
+					 unsigned int pcm_count)
+{
+	int		n;
+	void		*pDAQ;
+	/* unsigned long	flags; */
+
+	/* snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE); */
+
+	chip->last_recbank = 2;
+	chip->captureLimit = pcm_count * (pcm_periods - 1);
+	chip->capturePeriods = pcm_periods;
+	writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DARQ + JQS_wHead);
+	writew(PCTODSP_OFFSET(chip->last_recbank * DAQDS__size),
+		chip->DARQ + JQS_wTail);
+
+#if 0 /* Critical section: bank 1 access. this is how the OSS driver does it:*/
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(HPBLKSEL_1, chip->io + HP_BLKS);
+	memset_io(chip->mappedbase, 0, DAR_BUFF_SIZE * 3);
+	outb(HPBLKSEL_0, chip->io + HP_BLKS);
+	spin_unlock_irqrestore(&chip->lock, flags);
+#endif
+
+	chip->capturePeriodBytes = pcm_count;
+	snd_printdd("snd_msnd_capture_reset_queue() %i\n", pcm_count);
+
+	pDAQ = chip->mappedbase + DARQ_DATA_BUFF;
+
+	for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) {
+		u32 tmp = pcm_count * n;
+
+		writew(PCTODSP_BASED(tmp + 0x3000), pDAQ + DAQDS_wStart);
+		writew(pcm_count, pDAQ + DAQDS_wSize);
+		writew(1, pDAQ + DAQDS_wFormat);
+		writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize);
+		writew(chip->capture_channels, pDAQ + DAQDS_wChannels);
+		writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate);
+		writew(HIMT_RECORD_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg);
+		writew(n, pDAQ + DAQDS_wFlags);
+	}
+}
+
+static struct snd_pcm_hardware snd_msnd_playback = {
+	.info =			SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_BATCH,
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		8000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	0x3000,
+	.period_bytes_min =	0x40,
+	.period_bytes_max =	0x1800,
+	.periods_min =		2,
+	.periods_max =		3,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_msnd_capture = {
+	.info =			SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_BATCH,
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		8000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	0x3000,
+	.period_bytes_min =	0x40,
+	.period_bytes_max =	0x1800,
+	.periods_min =		2,
+	.periods_max =		3,
+	.fifo_size =		0,
+};
+
+
+static int snd_msnd_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	set_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+	clear_bit(F_WRITING, &chip->flags);
+	snd_msnd_enable_irq(chip);
+
+	runtime->dma_area = chip->mappedbase;
+	runtime->dma_bytes = 0x3000;
+
+	chip->playback_substream = substream;
+	runtime->hw = snd_msnd_playback;
+	return 0;
+}
+
+static int snd_msnd_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	snd_msnd_disable_irq(chip);
+	clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags);
+	return 0;
+}
+
+
+static int snd_msnd_playback_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *params)
+{
+	int	i;
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+	void	*pDAQ =	chip->mappedbase + DAPQ_DATA_BUFF;
+
+	chip->play_sample_size = snd_pcm_format_width(params_format(params));
+	chip->play_channels = params_channels(params);
+	chip->play_sample_rate = params_rate(params);
+
+	for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) {
+		writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize);
+		writew(chip->play_channels, pDAQ + DAQDS_wChannels);
+		writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate);
+	}
+	/* dont do this here:
+	 * snd_msnd_calibrate_adc(chip->play_sample_rate);
+	 */
+
+	return 0;
+}
+
+static int snd_msnd_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+	unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int pcm_count = snd_pcm_lib_period_bytes(substream);
+	unsigned int pcm_periods = pcm_size / pcm_count;
+
+	snd_msnd_play_reset_queue(chip, pcm_periods, pcm_count);
+	chip->playDMAPos = 0;
+	return 0;
+}
+
+static int snd_msnd_playback_trigger(struct snd_pcm_substream *substream,
+				     int cmd)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+	int	result = 0;
+
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		snd_printdd("snd_msnd_playback_trigger(START)\n");
+		chip->banksPlayed = 0;
+		set_bit(F_WRITING, &chip->flags);
+		snd_msnd_DAPQ(chip, 1);
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		snd_printdd("snd_msnd_playback_trigger(STop)\n");
+		/* interrupt diagnostic, comment this out later */
+		clear_bit(F_WRITING, &chip->flags);
+		snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP);
+	} else {
+		snd_printd(KERN_ERR "snd_msnd_playback_trigger(?????)\n");
+		result = -EINVAL;
+	}
+
+	snd_printdd("snd_msnd_playback_trigger() ENDE\n");
+	return result;
+}
+
+static snd_pcm_uframes_t
+snd_msnd_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	return bytes_to_frames(substream->runtime, chip->playDMAPos);
+}
+
+
+static struct snd_pcm_ops snd_msnd_playback_ops = {
+	.open =		snd_msnd_playback_open,
+	.close =	snd_msnd_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_msnd_playback_hw_params,
+	.prepare =	snd_msnd_playback_prepare,
+	.trigger =	snd_msnd_playback_trigger,
+	.pointer =	snd_msnd_playback_pointer,
+};
+
+static int snd_msnd_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	set_bit(F_AUDIO_READ_INUSE, &chip->flags);
+	snd_msnd_enable_irq(chip);
+	runtime->dma_area = chip->mappedbase + 0x3000;
+	runtime->dma_bytes = 0x3000;
+	memset(runtime->dma_area, 0, runtime->dma_bytes);
+	chip->capture_substream = substream;
+	runtime->hw = snd_msnd_capture;
+	return 0;
+}
+
+static int snd_msnd_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	snd_msnd_disable_irq(chip);
+	clear_bit(F_AUDIO_READ_INUSE, &chip->flags);
+	return 0;
+}
+
+static int snd_msnd_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+	unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int pcm_count = snd_pcm_lib_period_bytes(substream);
+	unsigned int pcm_periods = pcm_size / pcm_count;
+
+	snd_msnd_capture_reset_queue(chip, pcm_periods, pcm_count);
+	chip->captureDMAPos = 0;
+	return 0;
+}
+
+static int snd_msnd_capture_trigger(struct snd_pcm_substream *substream,
+				    int cmd)
+{
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		chip->last_recbank = -1;
+		set_bit(F_READING, &chip->flags);
+		if (snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_START) == 0)
+			return 0;
+
+		clear_bit(F_READING, &chip->flags);
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		clear_bit(F_READING, &chip->flags);
+		snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP);
+		return 0;
+	}
+	return -EINVAL;
+}
+
+
+static snd_pcm_uframes_t
+snd_msnd_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+
+	return bytes_to_frames(runtime, chip->captureDMAPos);
+}
+
+
+static int snd_msnd_capture_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *params)
+{
+	int		i;
+	struct snd_msnd *chip = snd_pcm_substream_chip(substream);
+	void		*pDAQ = chip->mappedbase + DARQ_DATA_BUFF;
+
+	chip->capture_sample_size = snd_pcm_format_width(params_format(params));
+	chip->capture_channels = params_channels(params);
+	chip->capture_sample_rate = params_rate(params);
+
+	for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) {
+		writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize);
+		writew(chip->capture_channels, pDAQ + DAQDS_wChannels);
+		writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate);
+	}
+	return 0;
+}
+
+
+static struct snd_pcm_ops snd_msnd_capture_ops = {
+	.open =		snd_msnd_capture_open,
+	.close =	snd_msnd_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_msnd_capture_hw_params,
+	.prepare =	snd_msnd_capture_prepare,
+	.trigger =	snd_msnd_capture_trigger,
+	.pointer =	snd_msnd_capture_pointer,
+};
+
+
+int snd_msnd_pcm(struct snd_card *card, int device,
+			struct snd_pcm **rpcm)
+{
+	struct snd_msnd *chip = card->private_data;
+	struct snd_pcm	*pcm;
+	int err;
+
+	err = snd_pcm_new(card, "MSNDPINNACLE", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_msnd_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_msnd_capture_ops);
+
+	pcm->private_data = chip;
+	strcpy(pcm->name, "Hurricane");
+
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+EXPORT_SYMBOL(snd_msnd_pcm);
+
+MODULE_DESCRIPTION("Common routines for Turtle Beach Multisound drivers");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/isa/msnd/msnd.h b/linux/sound/isa/msnd/msnd.h
new file mode 100644
index 0000000..3773e24
--- /dev/null
+++ b/linux/sound/isa/msnd/msnd.h
@@ -0,0 +1,308 @@
+/*********************************************************************
+ *
+ * msnd.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+#ifndef __MSND_H
+#define __MSND_H
+
+#define DEFSAMPLERATE		44100
+#define DEFSAMPLESIZE		SNDRV_PCM_FORMAT_S16
+#define DEFCHANNELS		1
+
+#define SRAM_BANK_SIZE		0x8000
+#define SRAM_CNTL_START		0x7F00
+#define SMA_STRUCT_START	0x7F40
+
+#define DSP_BASE_ADDR		0x4000
+#define DSP_BANK_BASE		0x4000
+
+#define AGND			0x01
+#define SIGNAL			0x02
+
+#define EXT_DSP_BIT_DCAL	0x0001
+#define EXT_DSP_BIT_MIDI_CON	0x0002
+
+#define BUFFSIZE		0x8000
+#define HOSTQ_SIZE		0x40
+
+#define DAP_BUFF_SIZE		0x2400
+
+#define DAPQ_STRUCT_SIZE	0x10
+#define DARQ_STRUCT_SIZE	0x10
+#define DAPQ_BUFF_SIZE		(3 * 0x10)
+#define DARQ_BUFF_SIZE		(3 * 0x10)
+#define MODQ_BUFF_SIZE		0x400
+
+#define DAPQ_DATA_BUFF		0x6C00
+#define DARQ_DATA_BUFF		0x6C30
+#define MODQ_DATA_BUFF		0x6C60
+#define MIDQ_DATA_BUFF		0x7060
+
+#define DAPQ_OFFSET		SRAM_CNTL_START
+#define DARQ_OFFSET		(SRAM_CNTL_START + 0x08)
+#define MODQ_OFFSET		(SRAM_CNTL_START + 0x10)
+#define MIDQ_OFFSET		(SRAM_CNTL_START + 0x18)
+#define DSPQ_OFFSET		(SRAM_CNTL_START + 0x20)
+
+#define	HP_ICR			0x00
+#define	HP_CVR			0x01
+#define	HP_ISR			0x02
+#define	HP_IVR			0x03
+#define HP_NU			0x04
+#define HP_INFO			0x04
+#define	HP_TXH			0x05
+#define	HP_RXH			0x05
+#define	HP_TXM			0x06
+#define	HP_RXM			0x06
+#define	HP_TXL			0x07
+#define	HP_RXL			0x07
+
+#define HP_ICR_DEF		0x00
+#define HP_CVR_DEF		0x12
+#define HP_ISR_DEF		0x06
+#define HP_IVR_DEF		0x0f
+#define HP_NU_DEF		0x00
+
+#define	HP_IRQM			0x09
+
+#define	HPR_BLRC		0x08
+#define	HPR_SPR1		0x09
+#define	HPR_SPR2		0x0A
+#define	HPR_TCL0		0x0B
+#define	HPR_TCL1		0x0C
+#define	HPR_TCL2		0x0D
+#define	HPR_TCL3		0x0E
+#define	HPR_TCL4		0x0F
+
+#define	HPICR_INIT		0x80
+#define HPICR_HM1		0x40
+#define HPICR_HM0		0x20
+#define HPICR_HF1		0x10
+#define HPICR_HF0		0x08
+#define	HPICR_TREQ		0x02
+#define	HPICR_RREQ		0x01
+
+#define HPCVR_HC		0x80
+
+#define	HPISR_HREQ		0x80
+#define HPISR_DMA		0x40
+#define HPISR_HF3		0x10
+#define HPISR_HF2		0x08
+#define	HPISR_TRDY		0x04
+#define	HPISR_TXDE		0x02
+#define	HPISR_RXDF		0x01
+
+#define	HPIO_290		0
+#define	HPIO_260		1
+#define	HPIO_250		2
+#define	HPIO_240		3
+#define	HPIO_230		4
+#define	HPIO_220		5
+#define	HPIO_210		6
+#define	HPIO_3E0		7
+
+#define	HPMEM_NONE		0
+#define	HPMEM_B000		1
+#define	HPMEM_C800		2
+#define	HPMEM_D000		3
+#define	HPMEM_D400		4
+#define	HPMEM_D800		5
+#define	HPMEM_E000		6
+#define	HPMEM_E800		7
+
+#define	HPIRQ_NONE		0
+#define HPIRQ_5			1
+#define HPIRQ_7			2
+#define HPIRQ_9			3
+#define HPIRQ_10		4
+#define HPIRQ_11		5
+#define HPIRQ_12		6
+#define HPIRQ_15		7
+
+#define	HIMT_PLAY_DONE		0x00
+#define	HIMT_RECORD_DONE	0x01
+#define	HIMT_MIDI_EOS		0x02
+#define	HIMT_MIDI_OUT		0x03
+
+#define	HIMT_MIDI_IN_UCHAR	0x0E
+#define	HIMT_DSP		0x0F
+
+#define	HDEX_BASE	       	0x92
+#define	HDEX_PLAY_START		(0 + HDEX_BASE)
+#define	HDEX_PLAY_STOP		(1 + HDEX_BASE)
+#define	HDEX_PLAY_PAUSE		(2 + HDEX_BASE)
+#define	HDEX_PLAY_RESUME	(3 + HDEX_BASE)
+#define	HDEX_RECORD_START	(4 + HDEX_BASE)
+#define	HDEX_RECORD_STOP	(5 + HDEX_BASE)
+#define	HDEX_MIDI_IN_START 	(6 + HDEX_BASE)
+#define	HDEX_MIDI_IN_STOP	(7 + HDEX_BASE)
+#define	HDEX_MIDI_OUT_START	(8 + HDEX_BASE)
+#define	HDEX_MIDI_OUT_STOP	(9 + HDEX_BASE)
+#define	HDEX_AUX_REQ		(10 + HDEX_BASE)
+
+#define	HDEXAR_CLEAR_PEAKS	1
+#define	HDEXAR_IN_SET_POTS	2
+#define	HDEXAR_AUX_SET_POTS	3
+#define	HDEXAR_CAL_A_TO_D	4
+#define	HDEXAR_RD_EXT_DSP_BITS	5
+
+/* Pinnacle only HDEXAR defs */
+#define	HDEXAR_SET_ANA_IN	0
+#define	HDEXAR_SET_SYNTH_IN	4
+#define	HDEXAR_READ_DAT_IN	5
+#define	HDEXAR_MIC_SET_POTS	6
+#define	HDEXAR_SET_DAT_IN	7
+
+#define HDEXAR_SET_SYNTH_48	8
+#define HDEXAR_SET_SYNTH_44	9
+
+#define HIWORD(l)		((u16)((((u32)(l)) >> 16) & 0xFFFF))
+#define LOWORD(l)		((u16)(u32)(l))
+#define HIBYTE(w)		((u8)(((u16)(w) >> 8) & 0xFF))
+#define LOBYTE(w)		((u8)(w))
+#define MAKELONG(low, hi)	((long)(((u16)(low))|(((u32)((u16)(hi)))<<16)))
+#define MAKEWORD(low, hi)	((u16)(((u8)(low))|(((u16)((u8)(hi)))<<8)))
+
+#define PCTODSP_OFFSET(w)	(u16)((w)/2)
+#define PCTODSP_BASED(w)	(u16)(((w)/2) + DSP_BASE_ADDR)
+#define DSPTOPC_BASED(w)	(((w) - DSP_BASE_ADDR) * 2)
+
+#ifdef SLOWIO
+#  undef outb
+#  undef inb
+#  define outb			outb_p
+#  define inb			inb_p
+#endif
+
+/* JobQueueStruct */
+#define JQS_wStart		0x00
+#define JQS_wSize		0x02
+#define JQS_wHead		0x04
+#define JQS_wTail		0x06
+#define JQS__size		0x08
+
+/* DAQueueDataStruct */
+#define DAQDS_wStart		0x00
+#define DAQDS_wSize		0x02
+#define DAQDS_wFormat		0x04
+#define DAQDS_wSampleSize	0x06
+#define DAQDS_wChannels		0x08
+#define DAQDS_wSampleRate	0x0A
+#define DAQDS_wIntMsg		0x0C
+#define DAQDS_wFlags		0x0E
+#define DAQDS__size		0x10
+
+#include <sound/pcm.h>
+
+struct snd_msnd {
+	void __iomem		*mappedbase;
+	int			play_period_bytes;
+	int			playLimit;
+	int			playPeriods;
+	int 			playDMAPos;
+	int			banksPlayed;
+	int 			captureDMAPos;
+	int			capturePeriodBytes;
+	int			captureLimit;
+	int			capturePeriods;
+	struct snd_card		*card;
+	void			*msndmidi_mpu;
+	struct snd_rawmidi	*rmidi;
+
+	/* Hardware resources */
+	long io;
+	int memid, irqid;
+	int irq, irq_ref;
+	unsigned long base;
+
+	/* Motorola 56k DSP SMA */
+	void __iomem	*SMA;
+	void __iomem	*DAPQ;
+	void __iomem	*DARQ;
+	void __iomem	*MODQ;
+	void __iomem	*MIDQ;
+	void __iomem	*DSPQ;
+	int dspq_data_buff, dspq_buff_size;
+
+	/* State variables */
+	enum { msndClassic, msndPinnacle } type;
+	mode_t mode;
+	unsigned long flags;
+#define F_RESETTING			0
+#define F_HAVEDIGITAL			1
+#define F_AUDIO_WRITE_INUSE		2
+#define F_WRITING			3
+#define F_WRITEBLOCK			4
+#define F_WRITEFLUSH			5
+#define F_AUDIO_READ_INUSE		6
+#define F_READING			7
+#define F_READBLOCK			8
+#define F_EXT_MIDI_INUSE		9
+#define F_HDR_MIDI_INUSE		10
+#define F_DISABLE_WRITE_NDELAY		11
+	spinlock_t lock;
+	spinlock_t mixer_lock;
+	int nresets;
+	unsigned recsrc;
+#define LEVEL_ENTRIES 32
+	int left_levels[LEVEL_ENTRIES];
+	int right_levels[LEVEL_ENTRIES];
+	int calibrate_signal;
+	int play_sample_size, play_sample_rate, play_channels;
+	int play_ndelay;
+	int capture_sample_size, capture_sample_rate, capture_channels;
+	int capture_ndelay;
+	u8 bCurrentMidiPatch;
+
+	int last_playbank, last_recbank;
+	struct snd_pcm_substream *playback_substream;
+	struct snd_pcm_substream *capture_substream;
+
+};
+
+void snd_msnd_init_queue(void *base, int start, int size);
+
+int snd_msnd_send_dsp_cmd(struct snd_msnd *chip, u8 cmd);
+int snd_msnd_send_word(struct snd_msnd *chip,
+			   unsigned char high,
+			   unsigned char mid,
+			   unsigned char low);
+int snd_msnd_upload_host(struct snd_msnd *chip,
+			     const u8 *bin, int len);
+int snd_msnd_enable_irq(struct snd_msnd *chip);
+int snd_msnd_disable_irq(struct snd_msnd *chip);
+void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file);
+int snd_msnd_DAPQ(struct snd_msnd *chip, int start);
+int snd_msnd_DARQ(struct snd_msnd *chip, int start);
+int snd_msnd_pcm(struct snd_card *card, int device, struct snd_pcm **rpcm);
+
+int snd_msndmidi_new(struct snd_card *card, int device);
+void snd_msndmidi_input_read(void *mpu);
+
+void snd_msndmix_setup(struct snd_msnd *chip);
+int __devinit snd_msndmix_new(struct snd_card *card);
+int snd_msndmix_force_recsrc(struct snd_msnd *chip, int recsrc);
+#endif /* __MSND_H */
diff --git a/linux/sound/isa/msnd/msnd_classic.c b/linux/sound/isa/msnd/msnd_classic.c
new file mode 100644
index 0000000..3b23a09
--- /dev/null
+++ b/linux/sound/isa/msnd/msnd_classic.c
@@ -0,0 +1,3 @@
+/* The work is in msnd_pinnacle.c, just define MSND_CLASSIC before it. */
+#define MSND_CLASSIC
+#include "msnd_pinnacle.c"
diff --git a/linux/sound/isa/msnd/msnd_classic.h b/linux/sound/isa/msnd/msnd_classic.h
new file mode 100644
index 0000000..f18d5fa
--- /dev/null
+++ b/linux/sound/isa/msnd/msnd_classic.h
@@ -0,0 +1,129 @@
+/*********************************************************************
+ *
+ * msnd_classic.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+#ifndef __MSND_CLASSIC_H
+#define __MSND_CLASSIC_H
+
+#define DSP_NUMIO				0x10
+
+#define	HP_MEMM					0x08
+
+#define	HP_BITM					0x0E
+#define	HP_WAIT					0x0D
+#define	HP_DSPR					0x0A
+#define	HP_PROR					0x0B
+#define	HP_BLKS					0x0C
+
+#define	HPPRORESET_OFF				0
+#define HPPRORESET_ON				1
+
+#define HPDSPRESET_OFF				0
+#define HPDSPRESET_ON				1
+
+#define HPBLKSEL_0				0
+#define HPBLKSEL_1				1
+
+#define HPWAITSTATE_0				0
+#define HPWAITSTATE_1				1
+
+#define HPBITMODE_16				0
+#define HPBITMODE_8				1
+
+#define	HIDSP_INT_PLAY_UNDER			0x00
+#define	HIDSP_INT_RECORD_OVER			0x01
+#define	HIDSP_INPUT_CLIPPING			0x02
+#define	HIDSP_MIDI_IN_OVER			0x10
+#define	HIDSP_MIDI_OVERRUN_ERR  0x13
+
+#define TIME_PRO_RESET_DONE			0x028A
+#define TIME_PRO_SYSEX				0x0040
+#define TIME_PRO_RESET				0x0032
+
+#define DAR_BUFF_SIZE				0x2000
+
+#define MIDQ_BUFF_SIZE				0x200
+#define DSPQ_BUFF_SIZE				0x40
+
+#define DSPQ_DATA_BUFF				0x7260
+
+#define MOP_SYNTH				0x10
+#define MOP_EXTOUT				0x32
+#define MOP_EXTTHRU				0x02
+#define MOP_OUTMASK				0x01
+
+#define MIP_EXTIN				0x01
+#define MIP_SYNTH				0x00
+#define MIP_INMASK				0x32
+
+/* Classic SMA Common Data */
+#define SMA_wCurrPlayBytes			0x0000
+#define SMA_wCurrRecordBytes			0x0002
+#define SMA_wCurrPlayVolLeft			0x0004
+#define SMA_wCurrPlayVolRight			0x0006
+#define SMA_wCurrInVolLeft			0x0008
+#define SMA_wCurrInVolRight			0x000a
+#define SMA_wUser_3				0x000c
+#define SMA_wUser_4				0x000e
+#define SMA_dwUser_5				0x0010
+#define SMA_dwUser_6				0x0014
+#define SMA_wUser_7				0x0018
+#define SMA_wReserved_A				0x001a
+#define SMA_wReserved_B				0x001c
+#define SMA_wReserved_C				0x001e
+#define SMA_wReserved_D				0x0020
+#define SMA_wReserved_E				0x0022
+#define SMA_wReserved_F				0x0024
+#define SMA_wReserved_G				0x0026
+#define SMA_wReserved_H				0x0028
+#define SMA_wCurrDSPStatusFlags			0x002a
+#define SMA_wCurrHostStatusFlags		0x002c
+#define SMA_wCurrInputTagBits			0x002e
+#define SMA_wCurrLeftPeak			0x0030
+#define SMA_wCurrRightPeak			0x0032
+#define SMA_wExtDSPbits				0x0034
+#define SMA_bExtHostbits			0x0036
+#define SMA_bBoardLevel				0x0037
+#define SMA_bInPotPosRight			0x0038
+#define SMA_bInPotPosLeft			0x0039
+#define SMA_bAuxPotPosRight			0x003a
+#define SMA_bAuxPotPosLeft			0x003b
+#define SMA_wCurrMastVolLeft			0x003c
+#define SMA_wCurrMastVolRight			0x003e
+#define SMA_bUser_12				0x0040
+#define SMA_bUser_13				0x0041
+#define SMA_wUser_14				0x0042
+#define SMA_wUser_15				0x0044
+#define SMA_wCalFreqAtoD			0x0046
+#define SMA_wUser_16				0x0048
+#define SMA_wUser_17				0x004a
+#define SMA__size				0x004c
+
+#define INITCODEFILE		"turtlebeach/msndinit.bin"
+#define PERMCODEFILE		"turtlebeach/msndperm.bin"
+#define LONGNAME		"MultiSound (Classic/Monterey/Tahiti)"
+
+#endif /* __MSND_CLASSIC_H */
diff --git a/linux/sound/isa/msnd/msnd_midi.c b/linux/sound/isa/msnd/msnd_midi.c
new file mode 100644
index 0000000..7874956
--- /dev/null
+++ b/linux/sound/isa/msnd/msnd_midi.c
@@ -0,0 +1,181 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) 2009 by Krzysztof Helt
+ *  Routines for control of MPU-401 in UART mode
+ *
+ *  MPU-401 supports UART mode which is not capable generate transmit
+ *  interrupts thus output is done via polling. Also, if irq < 0, then
+ *  input is done also via polling. Do not expect good performance.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+
+#include "msnd.h"
+
+#define MSNDMIDI_MODE_BIT_INPUT		0
+#define MSNDMIDI_MODE_BIT_OUTPUT		1
+#define MSNDMIDI_MODE_BIT_INPUT_TRIGGER	2
+#define MSNDMIDI_MODE_BIT_OUTPUT_TRIGGER	3
+
+struct snd_msndmidi {
+	struct snd_msnd *dev;
+
+	unsigned long mode;		/* MSNDMIDI_MODE_XXXX */
+
+	struct snd_rawmidi_substream *substream_input;
+
+	spinlock_t input_lock;
+};
+
+/*
+ * input/output open/close - protected by open_mutex in rawmidi.c
+ */
+static int snd_msndmidi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_msndmidi *mpu;
+
+	snd_printdd("snd_msndmidi_input_open()\n");
+
+	mpu = substream->rmidi->private_data;
+
+	mpu->substream_input = substream;
+
+	snd_msnd_enable_irq(mpu->dev);
+
+	snd_msnd_send_dsp_cmd(mpu->dev, HDEX_MIDI_IN_START);
+	set_bit(MSNDMIDI_MODE_BIT_INPUT, &mpu->mode);
+	return 0;
+}
+
+static int snd_msndmidi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_msndmidi *mpu;
+
+	mpu = substream->rmidi->private_data;
+	snd_msnd_send_dsp_cmd(mpu->dev, HDEX_MIDI_IN_STOP);
+	clear_bit(MSNDMIDI_MODE_BIT_INPUT, &mpu->mode);
+	mpu->substream_input = NULL;
+	snd_msnd_disable_irq(mpu->dev);
+	return 0;
+}
+
+static void snd_msndmidi_input_drop(struct snd_msndmidi *mpu)
+{
+	u16 tail;
+
+	tail = readw(mpu->dev->MIDQ + JQS_wTail);
+	writew(tail, mpu->dev->MIDQ + JQS_wHead);
+}
+
+/*
+ * trigger input
+ */
+static void snd_msndmidi_input_trigger(struct snd_rawmidi_substream *substream,
+					int up)
+{
+	unsigned long flags;
+	struct snd_msndmidi *mpu;
+
+	snd_printdd("snd_msndmidi_input_trigger(, %i)\n", up);
+
+	mpu = substream->rmidi->private_data;
+	spin_lock_irqsave(&mpu->input_lock, flags);
+	if (up) {
+		if (!test_and_set_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER,
+				      &mpu->mode))
+			snd_msndmidi_input_drop(mpu);
+	} else {
+		clear_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER, &mpu->mode);
+	}
+	spin_unlock_irqrestore(&mpu->input_lock, flags);
+	if (up)
+		snd_msndmidi_input_read(mpu);
+}
+
+void snd_msndmidi_input_read(void *mpuv)
+{
+	unsigned long flags;
+	struct snd_msndmidi *mpu = mpuv;
+	void *pwMIDQData = mpu->dev->mappedbase + MIDQ_DATA_BUFF;
+
+	spin_lock_irqsave(&mpu->input_lock, flags);
+	while (readw(mpu->dev->MIDQ + JQS_wTail) !=
+	       readw(mpu->dev->MIDQ + JQS_wHead)) {
+		u16 wTmp, val;
+		val = readw(pwMIDQData + 2 * readw(mpu->dev->MIDQ + JQS_wHead));
+
+			if (test_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER,
+				     &mpu->mode))
+				snd_rawmidi_receive(mpu->substream_input,
+						    (unsigned char *)&val, 1);
+
+		wTmp = readw(mpu->dev->MIDQ + JQS_wHead) + 1;
+		if (wTmp > readw(mpu->dev->MIDQ + JQS_wSize))
+			writew(0,  mpu->dev->MIDQ + JQS_wHead);
+		else
+			writew(wTmp,  mpu->dev->MIDQ + JQS_wHead);
+	}
+	spin_unlock_irqrestore(&mpu->input_lock, flags);
+}
+EXPORT_SYMBOL(snd_msndmidi_input_read);
+
+static struct snd_rawmidi_ops snd_msndmidi_input = {
+	.open =		snd_msndmidi_input_open,
+	.close =	snd_msndmidi_input_close,
+	.trigger =	snd_msndmidi_input_trigger,
+};
+
+static void snd_msndmidi_free(struct snd_rawmidi *rmidi)
+{
+	struct snd_msndmidi *mpu = rmidi->private_data;
+	kfree(mpu);
+}
+
+int snd_msndmidi_new(struct snd_card *card, int device)
+{
+	struct snd_msnd *chip = card->private_data;
+	struct snd_msndmidi *mpu;
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	err = snd_rawmidi_new(card, "MSND-MIDI", device, 1, 1, &rmidi);
+	if (err < 0)
+		return err;
+	mpu = kzalloc(sizeof(*mpu), GFP_KERNEL);
+	if (mpu == NULL) {
+		snd_device_free(card, rmidi);
+		return -ENOMEM;
+	}
+	mpu->dev = chip;
+	chip->msndmidi_mpu = mpu;
+	rmidi->private_data = mpu;
+	rmidi->private_free = snd_msndmidi_free;
+	spin_lock_init(&mpu->input_lock);
+	strcpy(rmidi->name, "MSND MIDI");
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+			    &snd_msndmidi_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+	return 0;
+}
diff --git a/linux/sound/isa/msnd/msnd_pinnacle.c b/linux/sound/isa/msnd/msnd_pinnacle.c
new file mode 100644
index 0000000..91d6023
--- /dev/null
+++ b/linux/sound/isa/msnd/msnd_pinnacle.c
@@ -0,0 +1,1245 @@
+/*********************************************************************
+ *
+ * Linux multisound pinnacle/fiji driver for ALSA.
+ *
+ * 2002/06/30 Karsten Wiese:
+ *	for now this is only used to build a pinnacle / fiji driver.
+ *	the OSS parent of this code is designed to also support
+ *	the multisound classic via the file msnd_classic.c.
+ *	to make it easier for some brave heart to implemt classic
+ *	support in alsa, i left all the MSND_CLASSIC tokens in this file.
+ *	but for now this untested & undone.
+ *
+ *
+ * ripped from linux kernel 2.4.18 by Karsten Wiese.
+ *
+ * the following is a copy of the 2.4.18 OSS FREE file-heading comment:
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ * msnd_pinnacle.c / msnd_classic.c
+ *
+ * -- If MSND_CLASSIC is defined:
+ *
+ *     -> driver for Turtle Beach Classic/Monterey/Tahiti
+ *
+ * -- Else
+ *
+ *     -> driver for Turtle Beach Pinnacle/Fiji
+ *
+ * 12-3-2000  Modified IO port validation  Steve Sycamore
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/firmware.h>
+#include <linux/isa.h>
+#include <linux/isapnp.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/asound.h>
+#include <sound/pcm.h>
+#include <sound/mpu401.h>
+
+#ifdef MSND_CLASSIC
+# ifndef __alpha__
+#  define SLOWIO
+# endif
+#endif
+#include "msnd.h"
+#ifdef MSND_CLASSIC
+#  include "msnd_classic.h"
+#  define LOGNAME			"msnd_classic"
+#else
+#  include "msnd_pinnacle.h"
+#  define LOGNAME			"snd_msnd_pinnacle"
+#endif
+
+static void __devinit set_default_audio_parameters(struct snd_msnd *chip)
+{
+	chip->play_sample_size = DEFSAMPLESIZE;
+	chip->play_sample_rate = DEFSAMPLERATE;
+	chip->play_channels = DEFCHANNELS;
+	chip->capture_sample_size = DEFSAMPLESIZE;
+	chip->capture_sample_rate = DEFSAMPLERATE;
+	chip->capture_channels = DEFCHANNELS;
+}
+
+static void snd_msnd_eval_dsp_msg(struct snd_msnd *chip, u16 wMessage)
+{
+	switch (HIBYTE(wMessage)) {
+	case HIMT_PLAY_DONE: {
+		if (chip->banksPlayed < 3)
+			snd_printdd("%08X: HIMT_PLAY_DONE: %i\n",
+				(unsigned)jiffies, LOBYTE(wMessage));
+
+		if (chip->last_playbank == LOBYTE(wMessage)) {
+			snd_printdd("chip.last_playbank == LOBYTE(wMessage)\n");
+			break;
+		}
+		chip->banksPlayed++;
+
+		if (test_bit(F_WRITING, &chip->flags))
+			snd_msnd_DAPQ(chip, 0);
+
+		chip->last_playbank = LOBYTE(wMessage);
+		chip->playDMAPos += chip->play_period_bytes;
+		if (chip->playDMAPos > chip->playLimit)
+			chip->playDMAPos = 0;
+		snd_pcm_period_elapsed(chip->playback_substream);
+
+		break;
+	}
+	case HIMT_RECORD_DONE:
+		if (chip->last_recbank == LOBYTE(wMessage))
+			break;
+		chip->last_recbank = LOBYTE(wMessage);
+		chip->captureDMAPos += chip->capturePeriodBytes;
+		if (chip->captureDMAPos > (chip->captureLimit))
+			chip->captureDMAPos = 0;
+
+		if (test_bit(F_READING, &chip->flags))
+			snd_msnd_DARQ(chip, chip->last_recbank);
+
+		snd_pcm_period_elapsed(chip->capture_substream);
+		break;
+
+	case HIMT_DSP:
+		switch (LOBYTE(wMessage)) {
+#ifndef MSND_CLASSIC
+		case HIDSP_PLAY_UNDER:
+#endif
+		case HIDSP_INT_PLAY_UNDER:
+			snd_printd(KERN_WARNING LOGNAME ": Play underflow %i\n",
+				chip->banksPlayed);
+			if (chip->banksPlayed > 2)
+				clear_bit(F_WRITING, &chip->flags);
+			break;
+
+		case HIDSP_INT_RECORD_OVER:
+			snd_printd(KERN_WARNING LOGNAME ": Record overflow\n");
+			clear_bit(F_READING, &chip->flags);
+			break;
+
+		default:
+			snd_printd(KERN_WARNING LOGNAME
+				   ": DSP message %d 0x%02x\n",
+				   LOBYTE(wMessage), LOBYTE(wMessage));
+			break;
+		}
+		break;
+
+	case HIMT_MIDI_IN_UCHAR:
+		if (chip->msndmidi_mpu)
+			snd_msndmidi_input_read(chip->msndmidi_mpu);
+		break;
+
+	default:
+		snd_printd(KERN_WARNING LOGNAME ": HIMT message %d 0x%02x\n",
+			   HIBYTE(wMessage), HIBYTE(wMessage));
+		break;
+	}
+}
+
+static irqreturn_t snd_msnd_interrupt(int irq, void *dev_id)
+{
+	struct snd_msnd *chip = dev_id;
+	void *pwDSPQData = chip->mappedbase + DSPQ_DATA_BUFF;
+
+	/* Send ack to DSP */
+	/* inb(chip->io + HP_RXL); */
+
+	/* Evaluate queued DSP messages */
+	while (readw(chip->DSPQ + JQS_wTail) != readw(chip->DSPQ + JQS_wHead)) {
+		u16 wTmp;
+
+		snd_msnd_eval_dsp_msg(chip,
+			readw(pwDSPQData + 2 * readw(chip->DSPQ + JQS_wHead)));
+
+		wTmp = readw(chip->DSPQ + JQS_wHead) + 1;
+		if (wTmp > readw(chip->DSPQ + JQS_wSize))
+			writew(0, chip->DSPQ + JQS_wHead);
+		else
+			writew(wTmp, chip->DSPQ + JQS_wHead);
+	}
+	/* Send ack to DSP */
+	inb(chip->io + HP_RXL);
+	return IRQ_HANDLED;
+}
+
+
+static int snd_msnd_reset_dsp(long io, unsigned char *info)
+{
+	int timeout = 100;
+
+	outb(HPDSPRESET_ON, io + HP_DSPR);
+	msleep(1);
+#ifndef MSND_CLASSIC
+	if (info)
+		*info = inb(io + HP_INFO);
+#endif
+	outb(HPDSPRESET_OFF, io + HP_DSPR);
+	msleep(1);
+	while (timeout-- > 0) {
+		if (inb(io + HP_CVR) == HP_CVR_DEF)
+			return 0;
+		msleep(1);
+	}
+	snd_printk(KERN_ERR LOGNAME ": Cannot reset DSP\n");
+
+	return -EIO;
+}
+
+static int __devinit snd_msnd_probe(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	unsigned char info;
+#ifndef MSND_CLASSIC
+	char *xv, *rev = NULL;
+	char *pin = "TB Pinnacle", *fiji = "TB Fiji";
+	char *pinfiji = "TB Pinnacle/Fiji";
+#endif
+
+	if (!request_region(chip->io, DSP_NUMIO, "probing")) {
+		snd_printk(KERN_ERR LOGNAME ": I/O port conflict\n");
+		return -ENODEV;
+	}
+
+	if (snd_msnd_reset_dsp(chip->io, &info) < 0) {
+		release_region(chip->io, DSP_NUMIO);
+		return -ENODEV;
+	}
+
+#ifdef MSND_CLASSIC
+	strcpy(card->shortname, "Classic/Tahiti/Monterey");
+	strcpy(card->longname, "Turtle Beach Multisound");
+	printk(KERN_INFO LOGNAME ": %s, "
+	       "I/O 0x%lx-0x%lx, IRQ %d, memory mapped to 0x%lX-0x%lX\n",
+	       card->shortname,
+	       chip->io, chip->io + DSP_NUMIO - 1,
+	       chip->irq,
+	       chip->base, chip->base + 0x7fff);
+#else
+	switch (info >> 4) {
+	case 0xf:
+		xv = "<= 1.15";
+		break;
+	case 0x1:
+		xv = "1.18/1.2";
+		break;
+	case 0x2:
+		xv = "1.3";
+		break;
+	case 0x3:
+		xv = "1.4";
+		break;
+	default:
+		xv = "unknown";
+		break;
+	}
+
+	switch (info & 0x7) {
+	case 0x0:
+		rev = "I";
+		strcpy(card->shortname, pin);
+		break;
+	case 0x1:
+		rev = "F";
+		strcpy(card->shortname, pin);
+		break;
+	case 0x2:
+		rev = "G";
+		strcpy(card->shortname, pin);
+		break;
+	case 0x3:
+		rev = "H";
+		strcpy(card->shortname, pin);
+		break;
+	case 0x4:
+		rev = "E";
+		strcpy(card->shortname, fiji);
+		break;
+	case 0x5:
+		rev = "C";
+		strcpy(card->shortname, fiji);
+		break;
+	case 0x6:
+		rev = "D";
+		strcpy(card->shortname, fiji);
+		break;
+	case 0x7:
+		rev = "A-B (Fiji) or A-E (Pinnacle)";
+		strcpy(card->shortname, pinfiji);
+		break;
+	}
+	strcpy(card->longname, "Turtle Beach Multisound Pinnacle");
+	printk(KERN_INFO LOGNAME ": %s revision %s, Xilinx version %s, "
+	       "I/O 0x%lx-0x%lx, IRQ %d, memory mapped to 0x%lX-0x%lX\n",
+	       card->shortname,
+	       rev, xv,
+	       chip->io, chip->io + DSP_NUMIO - 1,
+	       chip->irq,
+	       chip->base, chip->base + 0x7fff);
+#endif
+
+	release_region(chip->io, DSP_NUMIO);
+	return 0;
+}
+
+static int snd_msnd_init_sma(struct snd_msnd *chip)
+{
+	static int initted;
+	u16 mastVolLeft, mastVolRight;
+	unsigned long flags;
+
+#ifdef MSND_CLASSIC
+	outb(chip->memid, chip->io + HP_MEMM);
+#endif
+	outb(HPBLKSEL_0, chip->io + HP_BLKS);
+	/* Motorola 56k shared memory base */
+	chip->SMA = chip->mappedbase + SMA_STRUCT_START;
+
+	if (initted) {
+		mastVolLeft = readw(chip->SMA + SMA_wCurrMastVolLeft);
+		mastVolRight = readw(chip->SMA + SMA_wCurrMastVolRight);
+	} else
+		mastVolLeft = mastVolRight = 0;
+	memset_io(chip->mappedbase, 0, 0x8000);
+
+	/* Critical section: bank 1 access */
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(HPBLKSEL_1, chip->io + HP_BLKS);
+	memset_io(chip->mappedbase, 0, 0x8000);
+	outb(HPBLKSEL_0, chip->io + HP_BLKS);
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	/* Digital audio play queue */
+	chip->DAPQ = chip->mappedbase + DAPQ_OFFSET;
+	snd_msnd_init_queue(chip->DAPQ, DAPQ_DATA_BUFF, DAPQ_BUFF_SIZE);
+
+	/* Digital audio record queue */
+	chip->DARQ = chip->mappedbase + DARQ_OFFSET;
+	snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE);
+
+	/* MIDI out queue */
+	chip->MODQ = chip->mappedbase + MODQ_OFFSET;
+	snd_msnd_init_queue(chip->MODQ, MODQ_DATA_BUFF, MODQ_BUFF_SIZE);
+
+	/* MIDI in queue */
+	chip->MIDQ = chip->mappedbase + MIDQ_OFFSET;
+	snd_msnd_init_queue(chip->MIDQ, MIDQ_DATA_BUFF, MIDQ_BUFF_SIZE);
+
+	/* DSP -> host message queue */
+	chip->DSPQ = chip->mappedbase + DSPQ_OFFSET;
+	snd_msnd_init_queue(chip->DSPQ, DSPQ_DATA_BUFF, DSPQ_BUFF_SIZE);
+
+	/* Setup some DSP values */
+#ifndef MSND_CLASSIC
+	writew(1, chip->SMA + SMA_wCurrPlayFormat);
+	writew(chip->play_sample_size, chip->SMA + SMA_wCurrPlaySampleSize);
+	writew(chip->play_channels, chip->SMA + SMA_wCurrPlayChannels);
+	writew(chip->play_sample_rate, chip->SMA + SMA_wCurrPlaySampleRate);
+#endif
+	writew(chip->play_sample_rate, chip->SMA + SMA_wCalFreqAtoD);
+	writew(mastVolLeft, chip->SMA + SMA_wCurrMastVolLeft);
+	writew(mastVolRight, chip->SMA + SMA_wCurrMastVolRight);
+#ifndef MSND_CLASSIC
+	writel(0x00010000, chip->SMA + SMA_dwCurrPlayPitch);
+	writel(0x00000001, chip->SMA + SMA_dwCurrPlayRate);
+#endif
+	writew(0x303, chip->SMA + SMA_wCurrInputTagBits);
+
+	initted = 1;
+
+	return 0;
+}
+
+
+static int upload_dsp_code(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	const struct firmware *init_fw = NULL, *perm_fw = NULL;
+	int err;
+
+	outb(HPBLKSEL_0, chip->io + HP_BLKS);
+
+	err = request_firmware(&init_fw, INITCODEFILE, card->dev);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": Error loading " INITCODEFILE);
+		goto cleanup1;
+	}
+	err = request_firmware(&perm_fw, PERMCODEFILE, card->dev);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": Error loading " PERMCODEFILE);
+		goto cleanup;
+	}
+
+	memcpy_toio(chip->mappedbase, perm_fw->data, perm_fw->size);
+	if (snd_msnd_upload_host(chip, init_fw->data, init_fw->size) < 0) {
+		printk(KERN_WARNING LOGNAME ": Error uploading to DSP\n");
+		err = -ENODEV;
+		goto cleanup;
+	}
+	printk(KERN_INFO LOGNAME ": DSP firmware uploaded\n");
+	err = 0;
+
+cleanup:
+	release_firmware(perm_fw);
+cleanup1:
+	release_firmware(init_fw);
+	return err;
+}
+
+#ifdef MSND_CLASSIC
+static void reset_proteus(struct snd_msnd *chip)
+{
+	outb(HPPRORESET_ON, chip->io + HP_PROR);
+	msleep(TIME_PRO_RESET);
+	outb(HPPRORESET_OFF, chip->io + HP_PROR);
+	msleep(TIME_PRO_RESET_DONE);
+}
+#endif
+
+static int snd_msnd_initialize(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	int err, timeout;
+
+#ifdef MSND_CLASSIC
+	outb(HPWAITSTATE_0, chip->io + HP_WAIT);
+	outb(HPBITMODE_16, chip->io + HP_BITM);
+
+	reset_proteus(chip);
+#endif
+	err = snd_msnd_init_sma(chip);
+	if (err < 0) {
+		printk(KERN_WARNING LOGNAME ": Cannot initialize SMA\n");
+		return err;
+	}
+
+	err = snd_msnd_reset_dsp(chip->io, NULL);
+	if (err < 0)
+		return err;
+
+	err = upload_dsp_code(card);
+	if (err < 0) {
+		printk(KERN_WARNING LOGNAME ": Cannot upload DSP code\n");
+		return err;
+	}
+
+	timeout = 200;
+
+	while (readw(chip->mappedbase)) {
+		msleep(1);
+		if (!timeout--) {
+			snd_printd(KERN_ERR LOGNAME ": DSP reset timeout\n");
+			return -EIO;
+		}
+	}
+
+	snd_msndmix_setup(chip);
+	return 0;
+}
+
+static int snd_msnd_dsp_full_reset(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	int rv;
+
+	if (test_bit(F_RESETTING, &chip->flags) || ++chip->nresets > 10)
+		return 0;
+
+	set_bit(F_RESETTING, &chip->flags);
+	snd_msnd_dsp_halt(chip, NULL);	/* Unconditionally halt */
+
+	rv = snd_msnd_initialize(card);
+	if (rv)
+		printk(KERN_WARNING LOGNAME ": DSP reset failed\n");
+	snd_msndmix_force_recsrc(chip, 0);
+	clear_bit(F_RESETTING, &chip->flags);
+	return rv;
+}
+
+static int snd_msnd_dev_free(struct snd_device *device)
+{
+	snd_printdd("snd_msnd_chip_free()\n");
+	return 0;
+}
+
+static int snd_msnd_send_dsp_cmd_chk(struct snd_msnd *chip, u8 cmd)
+{
+	if (snd_msnd_send_dsp_cmd(chip, cmd) == 0)
+		return 0;
+	snd_msnd_dsp_full_reset(chip->card);
+	return snd_msnd_send_dsp_cmd(chip, cmd);
+}
+
+static int __devinit snd_msnd_calibrate_adc(struct snd_msnd *chip, u16 srate)
+{
+	snd_printdd("snd_msnd_calibrate_adc(%i)\n", srate);
+	writew(srate, chip->SMA + SMA_wCalFreqAtoD);
+	if (chip->calibrate_signal == 0)
+		writew(readw(chip->SMA + SMA_wCurrHostStatusFlags)
+		       | 0x0001, chip->SMA + SMA_wCurrHostStatusFlags);
+	else
+		writew(readw(chip->SMA + SMA_wCurrHostStatusFlags)
+		       & ~0x0001, chip->SMA + SMA_wCurrHostStatusFlags);
+	if (snd_msnd_send_word(chip, 0, 0, HDEXAR_CAL_A_TO_D) == 0 &&
+	    snd_msnd_send_dsp_cmd_chk(chip, HDEX_AUX_REQ) == 0) {
+		schedule_timeout_interruptible(msecs_to_jiffies(333));
+		return 0;
+	}
+	printk(KERN_WARNING LOGNAME ": ADC calibration failed\n");
+	return -EIO;
+}
+
+/*
+ * ALSA callback function, called when attempting to open the MIDI device.
+ */
+static int snd_msnd_mpu401_open(struct snd_mpu401 *mpu)
+{
+	snd_msnd_enable_irq(mpu->private_data);
+	snd_msnd_send_dsp_cmd(mpu->private_data, HDEX_MIDI_IN_START);
+	return 0;
+}
+
+static void snd_msnd_mpu401_close(struct snd_mpu401 *mpu)
+{
+	snd_msnd_send_dsp_cmd(mpu->private_data, HDEX_MIDI_IN_STOP);
+	snd_msnd_disable_irq(mpu->private_data);
+}
+
+static long mpu_io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+
+static int __devinit snd_msnd_attach(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =      snd_msnd_dev_free,
+		};
+
+	err = request_irq(chip->irq, snd_msnd_interrupt, 0, card->shortname,
+			  chip);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": Couldn't grab IRQ %d\n", chip->irq);
+		return err;
+	}
+	if (request_region(chip->io, DSP_NUMIO, card->shortname) == NULL) {
+		free_irq(chip->irq, chip);
+		return -EBUSY;
+	}
+
+	if (!request_mem_region(chip->base, BUFFSIZE, card->shortname)) {
+		printk(KERN_ERR LOGNAME
+			": unable to grab memory region 0x%lx-0x%lx\n",
+			chip->base, chip->base + BUFFSIZE - 1);
+		release_region(chip->io, DSP_NUMIO);
+		free_irq(chip->irq, chip);
+		return -EBUSY;
+	}
+	chip->mappedbase = ioremap_nocache(chip->base, 0x8000);
+	if (!chip->mappedbase) {
+		printk(KERN_ERR LOGNAME
+			": unable to map memory region 0x%lx-0x%lx\n",
+			chip->base, chip->base + BUFFSIZE - 1);
+		err = -EIO;
+		goto err_release_region;
+	}
+
+	err = snd_msnd_dsp_full_reset(card);
+	if (err < 0)
+		goto err_release_region;
+
+	/* Register device */
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0)
+		goto err_release_region;
+
+	err = snd_msnd_pcm(card, 0, NULL);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": error creating new PCM device\n");
+		goto err_release_region;
+	}
+
+	err = snd_msndmix_new(card);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": error creating new Mixer device\n");
+		goto err_release_region;
+	}
+
+
+	if (mpu_io[0] != SNDRV_AUTO_PORT) {
+		struct snd_mpu401 *mpu;
+
+		err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+					  mpu_io[0],
+					  MPU401_MODE_INPUT |
+					  MPU401_MODE_OUTPUT,
+					  mpu_irq[0], IRQF_DISABLED,
+					  &chip->rmidi);
+		if (err < 0) {
+			printk(KERN_ERR LOGNAME
+				": error creating new Midi device\n");
+			goto err_release_region;
+		}
+		mpu = chip->rmidi->private_data;
+
+		mpu->open_input = snd_msnd_mpu401_open;
+		mpu->close_input = snd_msnd_mpu401_close;
+		mpu->private_data = chip;
+	}
+
+	disable_irq(chip->irq);
+	snd_msnd_calibrate_adc(chip, chip->play_sample_rate);
+	snd_msndmix_force_recsrc(chip, 0);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto err_release_region;
+
+	return 0;
+
+err_release_region:
+	if (chip->mappedbase)
+		iounmap(chip->mappedbase);
+	release_mem_region(chip->base, BUFFSIZE);
+	release_region(chip->io, DSP_NUMIO);
+	free_irq(chip->irq, chip);
+	return err;
+}
+
+
+static void __devexit snd_msnd_unload(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+
+	iounmap(chip->mappedbase);
+	release_mem_region(chip->base, BUFFSIZE);
+	release_region(chip->io, DSP_NUMIO);
+	free_irq(chip->irq, chip);
+	snd_card_free(card);
+}
+
+#ifndef MSND_CLASSIC
+
+/* Pinnacle/Fiji Logical Device Configuration */
+
+static int __devinit snd_msnd_write_cfg(int cfg, int reg, int value)
+{
+	outb(reg, cfg);
+	outb(value, cfg + 1);
+	if (value != inb(cfg + 1)) {
+		printk(KERN_ERR LOGNAME ": snd_msnd_write_cfg: I/O error\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+static int __devinit snd_msnd_write_cfg_io0(int cfg, int num, u16 io)
+{
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IO0_BASEHI, HIBYTE(io)))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IO0_BASELO, LOBYTE(io)))
+		return -EIO;
+	return 0;
+}
+
+static int __devinit snd_msnd_write_cfg_io1(int cfg, int num, u16 io)
+{
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IO1_BASEHI, HIBYTE(io)))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IO1_BASELO, LOBYTE(io)))
+		return -EIO;
+	return 0;
+}
+
+static int __devinit snd_msnd_write_cfg_irq(int cfg, int num, u16 irq)
+{
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IRQ_NUMBER, LOBYTE(irq)))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_IRQ_TYPE, IRQTYPE_EDGE))
+		return -EIO;
+	return 0;
+}
+
+static int __devinit snd_msnd_write_cfg_mem(int cfg, int num, int mem)
+{
+	u16 wmem;
+
+	mem >>= 8;
+	wmem = (u16)(mem & 0xfff);
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_MEMBASEHI, HIBYTE(wmem)))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_MEMBASELO, LOBYTE(wmem)))
+		return -EIO;
+	if (wmem && snd_msnd_write_cfg(cfg, IREG_MEMCONTROL,
+				       MEMTYPE_HIADDR | MEMTYPE_16BIT))
+		return -EIO;
+	return 0;
+}
+
+static int __devinit snd_msnd_activate_logical(int cfg, int num)
+{
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg(cfg, IREG_ACTIVATE, LD_ACTIVATE))
+		return -EIO;
+	return 0;
+}
+
+static int __devinit snd_msnd_write_cfg_logical(int cfg, int num, u16 io0,
+						u16 io1, u16 irq, int mem)
+{
+	if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (snd_msnd_write_cfg_io0(cfg, num, io0))
+		return -EIO;
+	if (snd_msnd_write_cfg_io1(cfg, num, io1))
+		return -EIO;
+	if (snd_msnd_write_cfg_irq(cfg, num, irq))
+		return -EIO;
+	if (snd_msnd_write_cfg_mem(cfg, num, mem))
+		return -EIO;
+	if (snd_msnd_activate_logical(cfg, num))
+		return -EIO;
+	return 0;
+}
+
+static int __devinit snd_msnd_pinnacle_cfg_reset(int cfg)
+{
+	int i;
+
+	/* Reset devices if told to */
+	printk(KERN_INFO LOGNAME ": Resetting all devices\n");
+	for (i = 0; i < 4; ++i)
+		if (snd_msnd_write_cfg_logical(cfg, i, 0, 0, 0, 0))
+			return -EIO;
+
+	return 0;
+}
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+
+module_param_array(index, int, NULL, S_IRUGO);
+MODULE_PARM_DESC(index, "Index value for msnd_pinnacle soundcard.");
+module_param_array(id, charp, NULL, S_IRUGO);
+MODULE_PARM_DESC(id, "ID string for msnd_pinnacle soundcard.");
+
+static long io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static long mem[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+
+#ifndef MSND_CLASSIC
+static long cfg[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+
+/* Extra Peripheral Configuration (Default: Disable) */
+static long ide_io0[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long ide_io1[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int ide_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+
+static long joystick_io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+/* If we have the digital daugherboard... */
+static int digital[SNDRV_CARDS];
+
+/* Extra Peripheral Configuration */
+static int reset[SNDRV_CARDS];
+#endif
+
+static int write_ndelay[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = 1 };
+
+static int calibrate_signal;
+
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard.");
+#define has_isapnp(x) isapnp[x]
+#else
+#define has_isapnp(x) 0
+#endif
+
+MODULE_AUTHOR("Karsten Wiese <annabellesgarden@yahoo.de>");
+MODULE_DESCRIPTION("Turtle Beach " LONGNAME " Linux Driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(INITCODEFILE);
+MODULE_FIRMWARE(PERMCODEFILE);
+
+module_param_array(io, long, NULL, S_IRUGO);
+MODULE_PARM_DESC(io, "IO port #");
+module_param_array(irq, int, NULL, S_IRUGO);
+module_param_array(mem, long, NULL, S_IRUGO);
+module_param_array(write_ndelay, int, NULL, S_IRUGO);
+module_param(calibrate_signal, int, S_IRUGO);
+#ifndef MSND_CLASSIC
+module_param_array(digital, int, NULL, S_IRUGO);
+module_param_array(cfg, long, NULL, S_IRUGO);
+module_param_array(reset, int, 0, S_IRUGO);
+module_param_array(mpu_io, long, NULL, S_IRUGO);
+module_param_array(mpu_irq, int, NULL, S_IRUGO);
+module_param_array(ide_io0, long, NULL, S_IRUGO);
+module_param_array(ide_io1, long, NULL, S_IRUGO);
+module_param_array(ide_irq, int, NULL, S_IRUGO);
+module_param_array(joystick_io, long, NULL, S_IRUGO);
+#endif
+
+
+static int __devinit snd_msnd_isa_match(struct device *pdev, unsigned int i)
+{
+	if (io[i] == SNDRV_AUTO_PORT)
+		return 0;
+
+	if (irq[i] == SNDRV_AUTO_PORT || mem[i] == SNDRV_AUTO_PORT) {
+		printk(KERN_WARNING LOGNAME ": io, irq and mem must be set\n");
+		return 0;
+	}
+
+#ifdef MSND_CLASSIC
+	if (!(io[i] == 0x290 ||
+	      io[i] == 0x260 ||
+	      io[i] == 0x250 ||
+	      io[i] == 0x240 ||
+	      io[i] == 0x230 ||
+	      io[i] == 0x220 ||
+	      io[i] == 0x210 ||
+	      io[i] == 0x3e0)) {
+		printk(KERN_ERR LOGNAME ": \"io\" - DSP I/O base must be set "
+			" to 0x210, 0x220, 0x230, 0x240, 0x250, 0x260, 0x290, "
+			"or 0x3E0\n");
+		return 0;
+	}
+#else
+	if (io[i] < 0x100 || io[i] > 0x3e0 || (io[i] % 0x10) != 0) {
+		printk(KERN_ERR LOGNAME
+			": \"io\" - DSP I/O base must within the range 0x100 "
+			"to 0x3E0 and must be evenly divisible by 0x10\n");
+		return 0;
+	}
+#endif /* MSND_CLASSIC */
+
+	if (!(irq[i] == 5 ||
+	      irq[i] == 7 ||
+	      irq[i] == 9 ||
+	      irq[i] == 10 ||
+	      irq[i] == 11 ||
+	      irq[i] == 12)) {
+		printk(KERN_ERR LOGNAME
+			": \"irq\" - must be set to 5, 7, 9, 10, 11 or 12\n");
+		return 0;
+	}
+
+	if (!(mem[i] == 0xb0000 ||
+	      mem[i] == 0xc8000 ||
+	      mem[i] == 0xd0000 ||
+	      mem[i] == 0xd8000 ||
+	      mem[i] == 0xe0000 ||
+	      mem[i] == 0xe8000)) {
+		printk(KERN_ERR LOGNAME ": \"mem\" - must be set to "
+		       "0xb0000, 0xc8000, 0xd0000, 0xd8000, 0xe0000 or "
+		       "0xe8000\n");
+		return 0;
+	}
+
+#ifndef MSND_CLASSIC
+	if (cfg[i] == SNDRV_AUTO_PORT) {
+		printk(KERN_INFO LOGNAME ": Assuming PnP mode\n");
+	} else if (cfg[i] != 0x250 && cfg[i] != 0x260 && cfg[i] != 0x270) {
+		printk(KERN_INFO LOGNAME
+			": Config port must be 0x250, 0x260 or 0x270 "
+			"(or unspecified for PnP mode)\n");
+		return 0;
+	}
+#endif /* MSND_CLASSIC */
+
+	return 1;
+}
+
+static int __devinit snd_msnd_isa_probe(struct device *pdev, unsigned int idx)
+{
+	int err;
+	struct snd_card *card;
+	struct snd_msnd *chip;
+
+	if (has_isapnp(idx)
+#ifndef MSND_CLASSIC
+	    || cfg[idx] == SNDRV_AUTO_PORT
+#endif
+	    ) {
+		printk(KERN_INFO LOGNAME ": Assuming PnP mode\n");
+		return -ENODEV;
+	}
+
+	err = snd_card_create(index[idx], id[idx], THIS_MODULE,
+			      sizeof(struct snd_msnd), &card);
+	if (err < 0)
+		return err;
+
+	snd_card_set_dev(card, pdev);
+	chip = card->private_data;
+	chip->card = card;
+
+#ifdef MSND_CLASSIC
+	switch (irq[idx]) {
+	case 5:
+		chip->irqid = HPIRQ_5; break;
+	case 7:
+		chip->irqid = HPIRQ_7; break;
+	case 9:
+		chip->irqid = HPIRQ_9; break;
+	case 10:
+		chip->irqid = HPIRQ_10; break;
+	case 11:
+		chip->irqid = HPIRQ_11; break;
+	case 12:
+		chip->irqid = HPIRQ_12; break;
+	}
+
+	switch (mem[idx]) {
+	case 0xb0000:
+		chip->memid = HPMEM_B000; break;
+	case 0xc8000:
+		chip->memid = HPMEM_C800; break;
+	case 0xd0000:
+		chip->memid = HPMEM_D000; break;
+	case 0xd8000:
+		chip->memid = HPMEM_D800; break;
+	case 0xe0000:
+		chip->memid = HPMEM_E000; break;
+	case 0xe8000:
+		chip->memid = HPMEM_E800; break;
+	}
+#else
+	printk(KERN_INFO LOGNAME ": Non-PnP mode: configuring at port 0x%lx\n",
+			cfg[idx]);
+
+	if (!request_region(cfg[idx], 2, "Pinnacle/Fiji Config")) {
+		printk(KERN_ERR LOGNAME ": Config port 0x%lx conflict\n",
+			   cfg[idx]);
+		snd_card_free(card);
+		return -EIO;
+	}
+	if (reset[idx])
+		if (snd_msnd_pinnacle_cfg_reset(cfg[idx])) {
+			err = -EIO;
+			goto cfg_error;
+		}
+
+	/* DSP */
+	err = snd_msnd_write_cfg_logical(cfg[idx], 0,
+					 io[idx], 0,
+					 irq[idx], mem[idx]);
+
+	if (err)
+		goto cfg_error;
+
+	/* The following are Pinnacle specific */
+
+	/* MPU */
+	if (mpu_io[idx] != SNDRV_AUTO_PORT
+	    && mpu_irq[idx] != SNDRV_AUTO_IRQ) {
+		printk(KERN_INFO LOGNAME
+		       ": Configuring MPU to I/O 0x%lx IRQ %d\n",
+		       mpu_io[idx], mpu_irq[idx]);
+		err = snd_msnd_write_cfg_logical(cfg[idx], 1,
+						 mpu_io[idx], 0,
+						 mpu_irq[idx], 0);
+
+		if (err)
+			goto cfg_error;
+	}
+
+	/* IDE */
+	if (ide_io0[idx] != SNDRV_AUTO_PORT
+	    && ide_io1[idx] != SNDRV_AUTO_PORT
+	    && ide_irq[idx] != SNDRV_AUTO_IRQ) {
+		printk(KERN_INFO LOGNAME
+		       ": Configuring IDE to I/O 0x%lx, 0x%lx IRQ %d\n",
+		       ide_io0[idx], ide_io1[idx], ide_irq[idx]);
+		err = snd_msnd_write_cfg_logical(cfg[idx], 2,
+						 ide_io0[idx], ide_io1[idx],
+						 ide_irq[idx], 0);
+
+		if (err)
+			goto cfg_error;
+	}
+
+	/* Joystick */
+	if (joystick_io[idx] != SNDRV_AUTO_PORT) {
+		printk(KERN_INFO LOGNAME
+		       ": Configuring joystick to I/O 0x%lx\n",
+		       joystick_io[idx]);
+		err = snd_msnd_write_cfg_logical(cfg[idx], 3,
+						 joystick_io[idx], 0,
+						 0, 0);
+
+		if (err)
+			goto cfg_error;
+	}
+	release_region(cfg[idx], 2);
+
+#endif /* MSND_CLASSIC */
+
+	set_default_audio_parameters(chip);
+#ifdef MSND_CLASSIC
+	chip->type = msndClassic;
+#else
+	chip->type = msndPinnacle;
+#endif
+	chip->io = io[idx];
+	chip->irq = irq[idx];
+	chip->base = mem[idx];
+
+	chip->calibrate_signal = calibrate_signal ? 1 : 0;
+	chip->recsrc = 0;
+	chip->dspq_data_buff = DSPQ_DATA_BUFF;
+	chip->dspq_buff_size = DSPQ_BUFF_SIZE;
+	if (write_ndelay[idx])
+		clear_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+	else
+		set_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+#ifndef MSND_CLASSIC
+	if (digital[idx])
+		set_bit(F_HAVEDIGITAL, &chip->flags);
+#endif
+	spin_lock_init(&chip->lock);
+	err = snd_msnd_probe(card);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": Probe failed\n");
+		snd_card_free(card);
+		return err;
+	}
+
+	err = snd_msnd_attach(card);
+	if (err < 0) {
+		printk(KERN_ERR LOGNAME ": Attach failed\n");
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(pdev, card);
+
+	return 0;
+
+#ifndef MSND_CLASSIC
+cfg_error:
+	release_region(cfg[idx], 2);
+	snd_card_free(card);
+	return err;
+#endif
+}
+
+static int __devexit snd_msnd_isa_remove(struct device *pdev, unsigned int dev)
+{
+	snd_msnd_unload(dev_get_drvdata(pdev));
+	dev_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+#define DEV_NAME "msnd-pinnacle"
+
+static struct isa_driver snd_msnd_driver = {
+	.match		= snd_msnd_isa_match,
+	.probe		= snd_msnd_isa_probe,
+	.remove		= __devexit_p(snd_msnd_isa_remove),
+	/* FIXME: suspend, resume */
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+#ifdef CONFIG_PNP
+static int __devinit snd_msnd_pnp_detect(struct pnp_card_link *pcard,
+					 const struct pnp_card_device_id *pid)
+{
+	static int idx;
+	struct pnp_dev *pnp_dev;
+	struct pnp_dev *mpu_dev;
+	struct snd_card *card;
+	struct snd_msnd *chip;
+	int ret;
+
+	for ( ; idx < SNDRV_CARDS; idx++) {
+		if (has_isapnp(idx))
+			break;
+	}
+	if (idx >= SNDRV_CARDS)
+		return -ENODEV;
+
+	/*
+	 * Check that we still have room for another sound card ...
+	 */
+	pnp_dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
+	if (!pnp_dev)
+		return -ENODEV;
+
+	mpu_dev = pnp_request_card_device(pcard, pid->devs[1].id, NULL);
+	if (!mpu_dev)
+		return -ENODEV;
+
+	if (!pnp_is_active(pnp_dev) && pnp_activate_dev(pnp_dev) < 0) {
+		printk(KERN_INFO "msnd_pinnacle: device is inactive\n");
+		return -EBUSY;
+	}
+
+	if (!pnp_is_active(mpu_dev) && pnp_activate_dev(mpu_dev) < 0) {
+		printk(KERN_INFO "msnd_pinnacle: MPU device is inactive\n");
+		return -EBUSY;
+	}
+
+	/*
+	 * Create a new ALSA sound card entry, in anticipation
+	 * of detecting our hardware ...
+	 */
+	ret = snd_card_create(index[idx], id[idx], THIS_MODULE,
+			      sizeof(struct snd_msnd), &card);
+	if (ret < 0)
+		return ret;
+
+	chip = card->private_data;
+	chip->card = card;
+	snd_card_set_dev(card, &pcard->card->dev);
+
+	/*
+	 * Read the correct parameters off the ISA PnP bus ...
+	 */
+	io[idx] = pnp_port_start(pnp_dev, 0);
+	irq[idx] = pnp_irq(pnp_dev, 0);
+	mem[idx] = pnp_mem_start(pnp_dev, 0);
+	mpu_io[idx] = pnp_port_start(mpu_dev, 0);
+	mpu_irq[idx] = pnp_irq(mpu_dev, 0);
+
+	set_default_audio_parameters(chip);
+#ifdef MSND_CLASSIC
+	chip->type = msndClassic;
+#else
+	chip->type = msndPinnacle;
+#endif
+	chip->io = io[idx];
+	chip->irq = irq[idx];
+	chip->base = mem[idx];
+
+	chip->calibrate_signal = calibrate_signal ? 1 : 0;
+	chip->recsrc = 0;
+	chip->dspq_data_buff = DSPQ_DATA_BUFF;
+	chip->dspq_buff_size = DSPQ_BUFF_SIZE;
+	if (write_ndelay[idx])
+		clear_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+	else
+		set_bit(F_DISABLE_WRITE_NDELAY, &chip->flags);
+#ifndef MSND_CLASSIC
+	if (digital[idx])
+		set_bit(F_HAVEDIGITAL, &chip->flags);
+#endif
+	spin_lock_init(&chip->lock);
+	ret = snd_msnd_probe(card);
+	if (ret < 0) {
+		printk(KERN_ERR LOGNAME ": Probe failed\n");
+		goto _release_card;
+	}
+
+	ret = snd_msnd_attach(card);
+	if (ret < 0) {
+		printk(KERN_ERR LOGNAME ": Attach failed\n");
+		goto _release_card;
+	}
+
+	pnp_set_card_drvdata(pcard, card);
+	++idx;
+	return 0;
+
+_release_card:
+	snd_card_free(card);
+	return ret;
+}
+
+static void __devexit snd_msnd_pnp_remove(struct pnp_card_link *pcard)
+{
+	snd_msnd_unload(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+static int isa_registered;
+static int pnp_registered;
+
+static struct pnp_card_device_id msnd_pnpids[] = {
+	/* Pinnacle PnP */
+	{ .id = "BVJ0440", .devs = { { "TBS0000" }, { "TBS0001" } } },
+	{ .id = "" }	/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, msnd_pnpids);
+
+static struct pnp_card_driver msnd_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
+	.name = "msnd_pinnacle",
+	.id_table = msnd_pnpids,
+	.probe = snd_msnd_pnp_detect,
+	.remove = __devexit_p(snd_msnd_pnp_remove),
+};
+#endif /* CONFIG_PNP */
+
+static int __init snd_msnd_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_msnd_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&msnd_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit snd_msnd_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&msnd_pnpc_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_msnd_driver);
+}
+
+module_init(snd_msnd_init);
+module_exit(snd_msnd_exit);
+
diff --git a/linux/sound/isa/msnd/msnd_pinnacle.h b/linux/sound/isa/msnd/msnd_pinnacle.h
new file mode 100644
index 0000000..48318d1
--- /dev/null
+++ b/linux/sound/isa/msnd/msnd_pinnacle.h
@@ -0,0 +1,181 @@
+/*********************************************************************
+ *
+ * msnd_pinnacle.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+#ifndef __MSND_PINNACLE_H
+#define __MSND_PINNACLE_H
+
+#define DSP_NUMIO				0x08
+
+#define IREG_LOGDEVICE				0x07
+#define IREG_ACTIVATE				0x30
+#define LD_ACTIVATE				0x01
+#define LD_DISACTIVATE				0x00
+#define IREG_EECONTROL				0x3F
+#define IREG_MEMBASEHI				0x40
+#define IREG_MEMBASELO				0x41
+#define IREG_MEMCONTROL				0x42
+#define IREG_MEMRANGEHI				0x43
+#define IREG_MEMRANGELO				0x44
+#define MEMTYPE_8BIT				0x00
+#define MEMTYPE_16BIT				0x02
+#define MEMTYPE_RANGE				0x00
+#define MEMTYPE_HIADDR				0x01
+#define IREG_IO0_BASEHI				0x60
+#define IREG_IO0_BASELO				0x61
+#define IREG_IO1_BASEHI				0x62
+#define IREG_IO1_BASELO				0x63
+#define IREG_IRQ_NUMBER				0x70
+#define IREG_IRQ_TYPE				0x71
+#define IRQTYPE_HIGH				0x02
+#define IRQTYPE_LOW				0x00
+#define IRQTYPE_LEVEL				0x01
+#define IRQTYPE_EDGE				0x00
+
+#define	HP_DSPR					0x04
+#define	HP_BLKS					0x04
+
+#define HPDSPRESET_OFF				2
+#define HPDSPRESET_ON				0
+
+#define HPBLKSEL_0				2
+#define HPBLKSEL_1				3
+
+#define	HIMT_DAT_OFF				0x03
+
+#define	HIDSP_PLAY_UNDER			0x00
+#define	HIDSP_INT_PLAY_UNDER			0x01
+#define	HIDSP_SSI_TX_UNDER  			0x02
+#define HIDSP_RECQ_OVERFLOW			0x08
+#define HIDSP_INT_RECORD_OVER			0x09
+#define HIDSP_SSI_RX_OVERFLOW			0x0a
+
+#define	HIDSP_MIDI_IN_OVER			0x10
+
+#define	HIDSP_MIDI_FRAME_ERR			0x11
+#define	HIDSP_MIDI_PARITY_ERR			0x12
+#define	HIDSP_MIDI_OVERRUN_ERR			0x13
+
+#define HIDSP_INPUT_CLIPPING			0x20
+#define	HIDSP_MIX_CLIPPING			0x30
+#define HIDSP_DAT_IN_OFF			0x21
+
+#define TIME_PRO_RESET_DONE			0x028A
+#define TIME_PRO_SYSEX				0x001E
+#define TIME_PRO_RESET				0x0032
+
+#define DAR_BUFF_SIZE				0x1000
+
+#define MIDQ_BUFF_SIZE				0x800
+#define DSPQ_BUFF_SIZE				0x5A0
+
+#define DSPQ_DATA_BUFF				0x7860
+
+#define MOP_WAVEHDR				0
+#define MOP_EXTOUT				1
+#define MOP_HWINIT				0xfe
+#define MOP_NONE				0xff
+#define MOP_MAX					1
+
+#define MIP_EXTIN				0
+#define MIP_WAVEHDR				1
+#define MIP_HWINIT				0xfe
+#define MIP_MAX					1
+
+/* Pinnacle/Fiji SMA Common Data */
+#define SMA_wCurrPlayBytes			0x0000
+#define SMA_wCurrRecordBytes			0x0002
+#define SMA_wCurrPlayVolLeft			0x0004
+#define SMA_wCurrPlayVolRight			0x0006
+#define SMA_wCurrInVolLeft			0x0008
+#define SMA_wCurrInVolRight			0x000a
+#define SMA_wCurrMHdrVolLeft			0x000c
+#define SMA_wCurrMHdrVolRight			0x000e
+#define SMA_dwCurrPlayPitch			0x0010
+#define SMA_dwCurrPlayRate			0x0014
+#define SMA_wCurrMIDIIOPatch			0x0018
+#define SMA_wCurrPlayFormat			0x001a
+#define SMA_wCurrPlaySampleSize			0x001c
+#define SMA_wCurrPlayChannels			0x001e
+#define SMA_wCurrPlaySampleRate			0x0020
+#define SMA_wCurrRecordFormat			0x0022
+#define SMA_wCurrRecordSampleSize		0x0024
+#define SMA_wCurrRecordChannels			0x0026
+#define SMA_wCurrRecordSampleRate		0x0028
+#define SMA_wCurrDSPStatusFlags			0x002a
+#define SMA_wCurrHostStatusFlags		0x002c
+#define SMA_wCurrInputTagBits			0x002e
+#define SMA_wCurrLeftPeak			0x0030
+#define SMA_wCurrRightPeak			0x0032
+#define SMA_bMicPotPosLeft			0x0034
+#define SMA_bMicPotPosRight			0x0035
+#define SMA_bMicPotMaxLeft			0x0036
+#define SMA_bMicPotMaxRight			0x0037
+#define SMA_bInPotPosLeft			0x0038
+#define SMA_bInPotPosRight			0x0039
+#define SMA_bAuxPotPosLeft			0x003a
+#define SMA_bAuxPotPosRight			0x003b
+#define SMA_bInPotMaxLeft			0x003c
+#define SMA_bInPotMaxRight			0x003d
+#define SMA_bAuxPotMaxLeft			0x003e
+#define SMA_bAuxPotMaxRight			0x003f
+#define SMA_bInPotMaxMethod			0x0040
+#define SMA_bAuxPotMaxMethod			0x0041
+#define SMA_wCurrMastVolLeft			0x0042
+#define SMA_wCurrMastVolRight			0x0044
+#define SMA_wCalFreqAtoD			0x0046
+#define SMA_wCurrAuxVolLeft			0x0048
+#define SMA_wCurrAuxVolRight			0x004a
+#define SMA_wCurrPlay1VolLeft			0x004c
+#define SMA_wCurrPlay1VolRight			0x004e
+#define SMA_wCurrPlay2VolLeft			0x0050
+#define SMA_wCurrPlay2VolRight			0x0052
+#define SMA_wCurrPlay3VolLeft			0x0054
+#define SMA_wCurrPlay3VolRight			0x0056
+#define SMA_wCurrPlay4VolLeft			0x0058
+#define SMA_wCurrPlay4VolRight			0x005a
+#define SMA_wCurrPlay1PeakLeft			0x005c
+#define SMA_wCurrPlay1PeakRight			0x005e
+#define SMA_wCurrPlay2PeakLeft			0x0060
+#define SMA_wCurrPlay2PeakRight			0x0062
+#define SMA_wCurrPlay3PeakLeft			0x0064
+#define SMA_wCurrPlay3PeakRight			0x0066
+#define SMA_wCurrPlay4PeakLeft			0x0068
+#define SMA_wCurrPlay4PeakRight			0x006a
+#define SMA_wCurrPlayPeakLeft			0x006c
+#define SMA_wCurrPlayPeakRight			0x006e
+#define SMA_wCurrDATSR				0x0070
+#define SMA_wCurrDATRXCHNL			0x0072
+#define SMA_wCurrDATTXCHNL			0x0074
+#define SMA_wCurrDATRXRate			0x0076
+#define SMA_dwDSPPlayCount			0x0078
+#define SMA__size				0x007c
+
+#define INITCODEFILE		"turtlebeach/pndspini.bin"
+#define PERMCODEFILE		"turtlebeach/pndsperm.bin"
+#define LONGNAME		"MultiSound (Pinnacle/Fiji)"
+
+#endif /* __MSND_PINNACLE_H */
diff --git a/linux/sound/isa/msnd/msnd_pinnacle_mixer.c b/linux/sound/isa/msnd/msnd_pinnacle_mixer.c
new file mode 100644
index 0000000..494058a
--- /dev/null
+++ b/linux/sound/isa/msnd/msnd_pinnacle_mixer.c
@@ -0,0 +1,343 @@
+/***************************************************************************
+			  msnd_pinnacle_mixer.c  -  description
+			     -------------------
+    begin		: Fre Jun 7 2002
+    copyright 		: (C) 2002 by karsten wiese
+    email		: annabellesgarden@yahoo.de
+ ***************************************************************************/
+
+/***************************************************************************
+ *							      		   *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.				   *
+ *									   *
+ ***************************************************************************/
+
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include "msnd.h"
+#include "msnd_pinnacle.h"
+
+
+#define MSND_MIXER_VOLUME	0
+#define MSND_MIXER_PCM		1
+#define MSND_MIXER_AUX		2	/* Input source 1  (aux1) */
+#define MSND_MIXER_IMIX		3	/*  Recording monitor  */
+#define MSND_MIXER_SYNTH	4
+#define MSND_MIXER_SPEAKER	5
+#define MSND_MIXER_LINE		6
+#define MSND_MIXER_MIC		7
+#define MSND_MIXER_RECLEV	11	/* Recording level */
+#define MSND_MIXER_IGAIN	12	/* Input gain */
+#define MSND_MIXER_OGAIN	13	/* Output gain */
+#define MSND_MIXER_DIGITAL	17	/* Digital (input) 1 */
+
+/*	Device mask bits	*/
+
+#define MSND_MASK_VOLUME	(1 << MSND_MIXER_VOLUME)
+#define MSND_MASK_SYNTH		(1 << MSND_MIXER_SYNTH)
+#define MSND_MASK_PCM		(1 << MSND_MIXER_PCM)
+#define MSND_MASK_SPEAKER	(1 << MSND_MIXER_SPEAKER)
+#define MSND_MASK_LINE		(1 << MSND_MIXER_LINE)
+#define MSND_MASK_MIC		(1 << MSND_MIXER_MIC)
+#define MSND_MASK_IMIX		(1 << MSND_MIXER_IMIX)
+#define MSND_MASK_RECLEV	(1 << MSND_MIXER_RECLEV)
+#define MSND_MASK_IGAIN		(1 << MSND_MIXER_IGAIN)
+#define MSND_MASK_OGAIN		(1 << MSND_MIXER_OGAIN)
+#define MSND_MASK_AUX		(1 << MSND_MIXER_AUX)
+#define MSND_MASK_DIGITAL	(1 << MSND_MIXER_DIGITAL)
+
+static int snd_msndmix_info_mux(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[3] = {
+		"Analog", "MASS", "SPDIF",
+	};
+	struct snd_msnd *chip = snd_kcontrol_chip(kcontrol);
+	unsigned items = test_bit(F_HAVEDIGITAL, &chip->flags) ? 3 : 2;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = items;
+	if (uinfo->value.enumerated.item >= items)
+		uinfo->value.enumerated.item = items - 1;
+	strcpy(uinfo->value.enumerated.name,
+		texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_msndmix_get_mux(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_msnd *chip = snd_kcontrol_chip(kcontrol);
+	/* MSND_MASK_IMIX is the default */
+	ucontrol->value.enumerated.item[0] = 0;
+
+	if (chip->recsrc & MSND_MASK_SYNTH) {
+		ucontrol->value.enumerated.item[0] = 1;
+	} else if ((chip->recsrc & MSND_MASK_DIGITAL) &&
+		 test_bit(F_HAVEDIGITAL, &chip->flags)) {
+		ucontrol->value.enumerated.item[0] = 2;
+	}
+
+
+	return 0;
+}
+
+static int snd_msndmix_set_mux(struct snd_msnd *chip, int val)
+{
+	unsigned newrecsrc;
+	int change;
+	unsigned char msndbyte;
+
+	switch (val) {
+	case 0:
+		newrecsrc = MSND_MASK_IMIX;
+		msndbyte = HDEXAR_SET_ANA_IN;
+		break;
+	case 1:
+		newrecsrc = MSND_MASK_SYNTH;
+		msndbyte = HDEXAR_SET_SYNTH_IN;
+		break;
+	case 2:
+		newrecsrc = MSND_MASK_DIGITAL;
+		msndbyte = HDEXAR_SET_DAT_IN;
+		break;
+	default:
+		return -EINVAL;
+	}
+	change  = newrecsrc != chip->recsrc;
+	if (change) {
+		change = 0;
+		if (!snd_msnd_send_word(chip, 0, 0, msndbyte))
+			if (!snd_msnd_send_dsp_cmd(chip, HDEX_AUX_REQ)) {
+				chip->recsrc = newrecsrc;
+				change = 1;
+			}
+	}
+	return change;
+}
+
+static int snd_msndmix_put_mux(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol);
+	return snd_msndmix_set_mux(msnd, ucontrol->value.enumerated.item[0]);
+}
+
+
+static int snd_msndmix_volume_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 100;
+	return 0;
+}
+
+static int snd_msndmix_volume_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol);
+	int addr = kcontrol->private_value;
+	unsigned long flags;
+
+	spin_lock_irqsave(&msnd->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = msnd->left_levels[addr] * 100;
+	ucontrol->value.integer.value[0] /= 0xFFFF;
+	ucontrol->value.integer.value[1] = msnd->right_levels[addr] * 100;
+	ucontrol->value.integer.value[1] /= 0xFFFF;
+	spin_unlock_irqrestore(&msnd->mixer_lock, flags);
+	return 0;
+}
+
+#define update_volm(a, b)						\
+	do {								\
+		writew((dev->left_levels[a] >> 1) *			\
+		       readw(dev->SMA + SMA_wCurrMastVolLeft) / 0xffff,	\
+		       dev->SMA + SMA_##b##Left);			\
+		writew((dev->right_levels[a] >> 1)  *			\
+		       readw(dev->SMA + SMA_wCurrMastVolRight) / 0xffff, \
+		       dev->SMA + SMA_##b##Right);			\
+	} while (0);
+
+#define update_potm(d, s, ar)						\
+	do {								\
+		writeb((dev->left_levels[d] >> 8) *			\
+		       readw(dev->SMA + SMA_wCurrMastVolLeft) / 0xffff, \
+		       dev->SMA + SMA_##s##Left);			\
+		writeb((dev->right_levels[d] >> 8) *			\
+		       readw(dev->SMA + SMA_wCurrMastVolRight) / 0xffff, \
+		       dev->SMA + SMA_##s##Right);			\
+		if (snd_msnd_send_word(dev, 0, 0, ar) == 0)		\
+			snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ);	\
+	} while (0);
+
+#define update_pot(d, s, ar)						\
+	do {								\
+		writeb(dev->left_levels[d] >> 8,			\
+		       dev->SMA + SMA_##s##Left);			\
+		writeb(dev->right_levels[d] >> 8,			\
+		       dev->SMA + SMA_##s##Right);			\
+		if (snd_msnd_send_word(dev, 0, 0, ar) == 0)		\
+			snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ);	\
+	} while (0);
+
+
+static int snd_msndmix_set(struct snd_msnd *dev, int d, int left, int right)
+{
+	int bLeft, bRight;
+	int wLeft, wRight;
+	int updatemaster = 0;
+
+	if (d >= LEVEL_ENTRIES)
+		return -EINVAL;
+
+	bLeft = left * 0xff / 100;
+	wLeft = left * 0xffff / 100;
+
+	bRight = right * 0xff / 100;
+	wRight = right * 0xffff / 100;
+
+	dev->left_levels[d] = wLeft;
+	dev->right_levels[d] = wRight;
+
+	switch (d) {
+		/* master volume unscaled controls */
+	case MSND_MIXER_LINE:			/* line pot control */
+		/* scaled by IMIX in digital mix */
+		writeb(bLeft, dev->SMA + SMA_bInPotPosLeft);
+		writeb(bRight, dev->SMA + SMA_bInPotPosRight);
+		if (snd_msnd_send_word(dev, 0, 0, HDEXAR_IN_SET_POTS) == 0)
+			snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ);
+		break;
+	case MSND_MIXER_MIC:			/* mic pot control */
+		if (dev->type == msndClassic)
+			return -EINVAL;
+		/* scaled by IMIX in digital mix */
+		writeb(bLeft, dev->SMA + SMA_bMicPotPosLeft);
+		writeb(bRight, dev->SMA + SMA_bMicPotPosRight);
+		if (snd_msnd_send_word(dev, 0, 0, HDEXAR_MIC_SET_POTS) == 0)
+			snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ);
+		break;
+	case MSND_MIXER_VOLUME:		/* master volume */
+		writew(wLeft, dev->SMA + SMA_wCurrMastVolLeft);
+		writew(wRight, dev->SMA + SMA_wCurrMastVolRight);
+		/* fall through */
+
+	case MSND_MIXER_AUX:			/* aux pot control */
+		/* scaled by master volume */
+		/* fall through */
+
+		/* digital controls */
+	case MSND_MIXER_SYNTH:			/* synth vol (dsp mix) */
+	case MSND_MIXER_PCM:			/* pcm vol (dsp mix) */
+	case MSND_MIXER_IMIX:			/* input monitor (dsp mix) */
+		/* scaled by master volume */
+		updatemaster = 1;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (updatemaster) {
+		/* update master volume scaled controls */
+		update_volm(MSND_MIXER_PCM, wCurrPlayVol);
+		update_volm(MSND_MIXER_IMIX, wCurrInVol);
+		if (dev->type == msndPinnacle)
+			update_volm(MSND_MIXER_SYNTH, wCurrMHdrVol);
+		update_potm(MSND_MIXER_AUX, bAuxPotPos, HDEXAR_AUX_SET_POTS);
+	}
+
+	return 0;
+}
+
+static int snd_msndmix_volume_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol);
+	int change, addr = kcontrol->private_value;
+	int left, right;
+	unsigned long flags;
+
+	left = ucontrol->value.integer.value[0] % 101;
+	right = ucontrol->value.integer.value[1] % 101;
+	spin_lock_irqsave(&msnd->mixer_lock, flags);
+	change = msnd->left_levels[addr] != left
+		|| msnd->right_levels[addr] != right;
+	snd_msndmix_set(msnd, addr, left, right);
+	spin_unlock_irqrestore(&msnd->mixer_lock, flags);
+	return change;
+}
+
+
+#define DUMMY_VOLUME(xname, xindex, addr) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_msndmix_volume_info, \
+  .get = snd_msndmix_volume_get, .put = snd_msndmix_volume_put, \
+  .private_value = addr }
+
+
+static struct snd_kcontrol_new snd_msnd_controls[] = {
+DUMMY_VOLUME("Master Volume", 0, MSND_MIXER_VOLUME),
+DUMMY_VOLUME("PCM Volume", 0, MSND_MIXER_PCM),
+DUMMY_VOLUME("Aux Volume", 0, MSND_MIXER_AUX),
+DUMMY_VOLUME("Line Volume", 0, MSND_MIXER_LINE),
+DUMMY_VOLUME("Mic Volume", 0, MSND_MIXER_MIC),
+DUMMY_VOLUME("Monitor",	0, MSND_MIXER_IMIX),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_msndmix_info_mux,
+	.get = snd_msndmix_get_mux,
+	.put = snd_msndmix_put_mux,
+}
+};
+
+
+int __devinit snd_msndmix_new(struct snd_card *card)
+{
+	struct snd_msnd *chip = card->private_data;
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!chip))
+		return -EINVAL;
+	spin_lock_init(&chip->mixer_lock);
+	strcpy(card->mixername, "MSND Pinnacle Mixer");
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_msnd_controls); idx++)
+		err = snd_ctl_add(card,
+				  snd_ctl_new1(snd_msnd_controls + idx, chip));
+		if (err < 0)
+			return err;
+
+	return 0;
+}
+EXPORT_SYMBOL(snd_msndmix_new);
+
+void snd_msndmix_setup(struct snd_msnd *dev)
+{
+	update_pot(MSND_MIXER_LINE, bInPotPos, HDEXAR_IN_SET_POTS);
+	update_potm(MSND_MIXER_AUX, bAuxPotPos, HDEXAR_AUX_SET_POTS);
+	update_volm(MSND_MIXER_PCM, wCurrPlayVol);
+	update_volm(MSND_MIXER_IMIX, wCurrInVol);
+	if (dev->type == msndPinnacle) {
+		update_pot(MSND_MIXER_MIC, bMicPotPos, HDEXAR_MIC_SET_POTS);
+		update_volm(MSND_MIXER_SYNTH, wCurrMHdrVol);
+	}
+}
+EXPORT_SYMBOL(snd_msndmix_setup);
+
+int snd_msndmix_force_recsrc(struct snd_msnd *dev, int recsrc)
+{
+	dev->recsrc = -1;
+	return snd_msndmix_set_mux(dev, recsrc);
+}
+EXPORT_SYMBOL(snd_msndmix_force_recsrc);
diff --git a/linux/sound/isa/opl3sa2.c b/linux/sound/isa/opl3sa2.c
new file mode 100644
index 0000000..265abcc
--- /dev/null
+++ b/linux/sound/isa/opl3sa2.c
@@ -0,0 +1,970 @@
+/*
+ *  Driver for Yamaha OPL3-SA[2,3] soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/interrupt.h>
+#include <linux/pm.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <asm/io.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Yamaha OPL3SA2+");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Yamaha,YMF719E-S},"
+		"{Genius,Sound Maker 3DX},"
+		"{Yamaha,OPL3SA3},"
+		"{Intel,AL440LX sound},"
+	        "{NeoMagic,MagicWave 3DX}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0xf86,0x370,0x100 */
+static long sb_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x530,0xe80,0xf40,0x604 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x388 */
+static long midi_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x330,0x300 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 0,1,3,5,9,11,12,15 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
+static int opl3sa3_ymode[SNDRV_CARDS];   /* 0,1,2,3 */ /*SL Added*/
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for OPL3-SA soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for OPL3-SA soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable OPL3-SA soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for OPL3-SA driver.");
+module_param_array(sb_port, long, NULL, 0444);
+MODULE_PARM_DESC(sb_port, "SB port # for OPL3-SA driver.");
+module_param_array(wss_port, long, NULL, 0444);
+MODULE_PARM_DESC(wss_port, "WSS port # for OPL3-SA driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for OPL3-SA driver.");
+module_param_array(midi_port, long, NULL, 0444);
+MODULE_PARM_DESC(midi_port, "MIDI port # for OPL3-SA driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for OPL3-SA driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for OPL3-SA driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for OPL3-SA driver.");
+module_param_array(opl3sa3_ymode, int, NULL, 0444);
+MODULE_PARM_DESC(opl3sa3_ymode, "Speaker size selection for 3D Enhancement mode: Desktop/Large Notebook/Small Notebook/HiFi.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+static int pnpc_registered;
+#endif
+
+/* control ports */
+#define OPL3SA2_PM_CTRL		0x01
+#define OPL3SA2_SYS_CTRL		0x02
+#define OPL3SA2_IRQ_CONFIG	0x03
+#define OPL3SA2_IRQ_STATUS	0x04
+#define OPL3SA2_DMA_CONFIG	0x06
+#define OPL3SA2_MASTER_LEFT	0x07
+#define OPL3SA2_MASTER_RIGHT	0x08
+#define OPL3SA2_MIC		0x09
+#define OPL3SA2_MISC		0x0A
+
+/* opl3sa3 only */
+#define OPL3SA3_DGTL_DOWN	0x12
+#define OPL3SA3_ANLG_DOWN	0x13
+#define OPL3SA3_WIDE		0x14
+#define OPL3SA3_BASS		0x15
+#define OPL3SA3_TREBLE		0x16
+
+/* power management bits */
+#define OPL3SA2_PM_ADOWN		0x20
+#define OPL3SA2_PM_PSV		0x04		
+#define OPL3SA2_PM_PDN		0x02
+#define OPL3SA2_PM_PDX		0x01
+
+#define OPL3SA2_PM_D0	0x00
+#define OPL3SA2_PM_D3	(OPL3SA2_PM_ADOWN|OPL3SA2_PM_PSV|OPL3SA2_PM_PDN|OPL3SA2_PM_PDX)
+
+struct snd_opl3sa2 {
+	int version;		/* 2 or 3 */
+	unsigned long port;	/* control port */
+	struct resource *res_port; /* control port resource */
+	int irq;
+	int single_dma;
+	spinlock_t reg_lock;
+	struct snd_hwdep *synth;
+	struct snd_rawmidi *rmidi;
+	struct snd_wss *wss;
+	unsigned char ctlregs[0x20];
+	int ymode;		/* SL added */
+	struct snd_kcontrol *master_switch;
+	struct snd_kcontrol *master_volume;
+};
+
+#define PFX	"opl3sa2: "
+
+#ifdef CONFIG_PNP
+
+static struct pnp_device_id snd_opl3sa2_pnpbiosids[] = {
+	{ .id = "YMH0021" },
+	{ .id = "NMX2210" },	/* Gateway Solo 2500 */
+	{ .id = "" }		/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp, snd_opl3sa2_pnpbiosids);
+
+static struct pnp_card_device_id snd_opl3sa2_pnpids[] = {
+	/* Yamaha YMF719E-S (Genius Sound Maker 3DX) */
+	{ .id = "YMH0020", .devs = { { "YMH0021" } } },
+	/* Yamaha OPL3-SA3 (integrated on Intel's Pentium II AL440LX motherboard) */
+	{ .id = "YMH0030", .devs = { { "YMH0021" } } },
+	/* Yamaha OPL3-SA2 */
+	{ .id = "YMH0800", .devs = { { "YMH0021" } } },
+	/* Yamaha OPL3-SA2 */
+	{ .id = "YMH0801", .devs = { { "YMH0021" } } },
+	/* NeoMagic MagicWave 3DX */
+	{ .id = "NMX2200", .devs = { { "YMH2210" } } },
+	/* NeoMagic MagicWave 3D */
+	{ .id = "NMX2200", .devs = { { "NMX2210" } } },
+	/* --- */
+	{ .id = "" }	/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_opl3sa2_pnpids);
+
+#endif /* CONFIG_PNP */
+
+
+/* read control port (w/o spinlock) */
+static unsigned char __snd_opl3sa2_read(struct snd_opl3sa2 *chip, unsigned char reg)
+{
+	unsigned char result;
+#if 0
+	outb(0x1d, port);	/* password */
+	printk(KERN_DEBUG "read [0x%lx] = 0x%x\n", port, inb(port));
+#endif
+	outb(reg, chip->port);	/* register */
+	result = inb(chip->port + 1);
+#if 0
+	printk(KERN_DEBUG "read [0x%lx] = 0x%x [0x%x]\n",
+	       port, result, inb(port));
+#endif
+	return result;
+}
+
+/* read control port (with spinlock) */
+static unsigned char snd_opl3sa2_read(struct snd_opl3sa2 *chip, unsigned char reg)
+{
+	unsigned long flags;
+	unsigned char result;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	result = __snd_opl3sa2_read(chip, reg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return result;
+}
+
+/* write control port (w/o spinlock) */
+static void __snd_opl3sa2_write(struct snd_opl3sa2 *chip, unsigned char reg, unsigned char value)
+{
+#if 0
+	outb(0x1d, port);	/* password */
+#endif
+	outb(reg, chip->port);	/* register */
+	outb(value, chip->port + 1);
+	chip->ctlregs[reg] = value;
+}
+
+/* write control port (with spinlock) */
+static void snd_opl3sa2_write(struct snd_opl3sa2 *chip, unsigned char reg, unsigned char value)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	__snd_opl3sa2_write(chip, reg, value);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static int __devinit snd_opl3sa2_detect(struct snd_card *card)
+{
+	struct snd_opl3sa2 *chip = card->private_data;
+	unsigned long port;
+	unsigned char tmp, tmp1;
+	char str[2];
+
+	port = chip->port;
+	if ((chip->res_port = request_region(port, 2, "OPL3-SA control")) == NULL) {
+		snd_printk(KERN_ERR PFX "can't grab port 0x%lx\n", port);
+		return -EBUSY;
+	}
+	/*
+	snd_printk(KERN_DEBUG "REG 0A = 0x%x\n",
+		   snd_opl3sa2_read(chip, 0x0a));
+	*/
+	chip->version = 0;
+	tmp = snd_opl3sa2_read(chip, OPL3SA2_MISC);
+	if (tmp == 0xff) {
+		snd_printd("OPL3-SA [0x%lx] detect = 0x%x\n", port, tmp);
+		return -ENODEV;
+	}
+	switch (tmp & 0x07) {
+	case 0x01:
+		chip->version = 2; /* YMF711 */
+		break;
+	default:
+		chip->version = 3;
+		/* 0x02 - standard */
+		/* 0x03 - YM715B */
+		/* 0x04 - YM719 - OPL-SA4? */
+		/* 0x05 - OPL3-SA3 - Libretto 100 */
+		/* 0x07 - unknown - Neomagic MagicWave 3D */
+		break;
+	}
+	str[0] = chip->version + '0';
+	str[1] = 0;
+	strcat(card->shortname, str);
+	snd_opl3sa2_write(chip, OPL3SA2_MISC, tmp ^ 7);
+	if ((tmp1 = snd_opl3sa2_read(chip, OPL3SA2_MISC)) != tmp) {
+		snd_printd("OPL3-SA [0x%lx] detect (1) = 0x%x (0x%x)\n", port, tmp, tmp1);
+		return -ENODEV;
+	}
+	/* try if the MIC register is accesible */
+	tmp = snd_opl3sa2_read(chip, OPL3SA2_MIC);
+	snd_opl3sa2_write(chip, OPL3SA2_MIC, 0x8a);
+	if (((tmp1 = snd_opl3sa2_read(chip, OPL3SA2_MIC)) & 0x9f) != 0x8a) {
+		snd_printd("OPL3-SA [0x%lx] detect (2) = 0x%x (0x%x)\n", port, tmp, tmp1);
+		return -ENODEV;
+	}
+	snd_opl3sa2_write(chip, OPL3SA2_MIC, 0x9f);
+	/* initialization */
+	/* Power Management - full on */
+	snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D0);
+	if (chip->version > 2) {
+		/* ymode is bits 4&5 (of 0 to 7) on all but opl3sa2 versions */
+		snd_opl3sa2_write(chip, OPL3SA2_SYS_CTRL, (chip->ymode << 4));
+	} else {
+		/* default for opl3sa2 versions */
+		snd_opl3sa2_write(chip, OPL3SA2_SYS_CTRL, 0x00);
+	}
+	snd_opl3sa2_write(chip, OPL3SA2_IRQ_CONFIG, 0x0d);	/* Interrupt Channel Configuration - IRQ A = OPL3 + MPU + WSS */
+	if (chip->single_dma) {
+		snd_opl3sa2_write(chip, OPL3SA2_DMA_CONFIG, 0x03);	/* DMA Configuration - DMA A = WSS-R + WSS-P */
+	} else {
+		snd_opl3sa2_write(chip, OPL3SA2_DMA_CONFIG, 0x21);	/* DMA Configuration - DMA B = WSS-R, DMA A = WSS-P */
+	}
+	snd_opl3sa2_write(chip, OPL3SA2_MISC, 0x80 | (tmp & 7));	/* Miscellaneous - default */
+	if (chip->version > 2) {
+		snd_opl3sa2_write(chip, OPL3SA3_DGTL_DOWN, 0x00);	/* Digital Block Partial Power Down - default */
+		snd_opl3sa2_write(chip, OPL3SA3_ANLG_DOWN, 0x00);	/* Analog Block Partial Power Down - default */
+	}
+	return 0;
+}
+
+static irqreturn_t snd_opl3sa2_interrupt(int irq, void *dev_id)
+{
+	unsigned short status;
+	struct snd_card *card = dev_id;
+	struct snd_opl3sa2 *chip;
+	int handled = 0;
+
+	if (card == NULL)
+		return IRQ_NONE;
+
+	chip = card->private_data;
+	status = snd_opl3sa2_read(chip, OPL3SA2_IRQ_STATUS);
+
+	if (status & 0x20) {
+		handled = 1;
+		snd_opl3_interrupt(chip->synth);
+	}
+
+	if ((status & 0x10) && chip->rmidi != NULL) {
+		handled = 1;
+		snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+	}
+
+	if (status & 0x07) {	/* TI,CI,PI */
+		handled = 1;
+		snd_wss_interrupt(irq, chip->wss);
+	}
+
+	if (status & 0x40) { /* hardware volume change */
+		handled = 1;
+		/* reading from Master Lch register at 0x07 clears this bit */
+		snd_opl3sa2_read(chip, OPL3SA2_MASTER_RIGHT);
+		snd_opl3sa2_read(chip, OPL3SA2_MASTER_LEFT);
+		if (chip->master_switch && chip->master_volume) {
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+					&chip->master_switch->id);
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
+					&chip->master_volume->id);
+		}
+	}
+	return IRQ_RETVAL(handled);
+}
+
+#define OPL3SA2_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_wss_info_single, \
+  .get = snd_opl3sa2_get_single, .put = snd_opl3sa2_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+#define OPL3SA2_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .name = xname, .index = xindex, \
+  .info = snd_wss_info_single, \
+  .get = snd_opl3sa2_get_single, .put = snd_opl3sa2_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_opl3sa2_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->ctlregs[reg] >> shift) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_opl3sa2_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val, oval;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	oval = chip->ctlregs[reg];
+	val = (oval & ~(mask << shift)) | val;
+	change = val != oval;
+	__snd_opl3sa2_write(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+#define OPL3SA2_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_wss_info_double, \
+  .get = snd_opl3sa2_get_double, .put = snd_opl3sa2_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+#define OPL3SA2_DOUBLE_TLV(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .name = xname, .index = xindex, \
+  .info = snd_wss_info_double, \
+  .get = snd_opl3sa2_get_double, .put = snd_opl3sa2_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_opl3sa2_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->ctlregs[left_reg] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (chip->ctlregs[right_reg] >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_opl3sa2_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2, oval1, oval2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (left_reg != right_reg) {
+		oval1 = chip->ctlregs[left_reg];
+		oval2 = chip->ctlregs[right_reg];
+		val1 = (oval1 & ~(mask << shift_left)) | val1;
+		val2 = (oval2 & ~(mask << shift_right)) | val2;
+		change = val1 != oval1 || val2 != oval2;
+		__snd_opl3sa2_write(chip, left_reg, val1);
+		__snd_opl3sa2_write(chip, right_reg, val2);
+	} else {
+		oval1 = chip->ctlregs[left_reg];
+		val1 = (oval1 & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+		change = val1 != oval1;
+		__snd_opl3sa2_write(chip, left_reg, val1);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_master, -3000, 200, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+
+static struct snd_kcontrol_new snd_opl3sa2_controls[] = {
+OPL3SA2_DOUBLE("Master Playback Switch", 0, 0x07, 0x08, 7, 7, 1, 1),
+OPL3SA2_DOUBLE_TLV("Master Playback Volume", 0, 0x07, 0x08, 0, 0, 15, 1,
+		   db_scale_master),
+OPL3SA2_SINGLE("Mic Playback Switch", 0, 0x09, 7, 1, 1),
+OPL3SA2_SINGLE_TLV("Mic Playback Volume", 0, 0x09, 0, 31, 1,
+		   db_scale_5bit_12db_max),
+OPL3SA2_SINGLE("ZV Port Switch", 0, 0x02, 0, 1, 0),
+};
+
+static struct snd_kcontrol_new snd_opl3sa2_tone_controls[] = {
+OPL3SA2_DOUBLE("3D Control - Wide", 0, 0x14, 0x14, 4, 0, 7, 0),
+OPL3SA2_DOUBLE("Tone Control - Bass", 0, 0x15, 0x15, 4, 0, 7, 0),
+OPL3SA2_DOUBLE("Tone Control - Treble", 0, 0x16, 0x16, 4, 0, 7, 0)
+};
+
+static void snd_opl3sa2_master_free(struct snd_kcontrol *kcontrol)
+{
+	struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol);
+	chip->master_switch = NULL;
+	chip->master_volume = NULL;
+}
+
+static int __devinit snd_opl3sa2_mixer(struct snd_card *card)
+{
+	struct snd_opl3sa2 *chip = card->private_data;
+	struct snd_ctl_elem_id id1, id2;
+	struct snd_kcontrol *kctl;
+	unsigned int idx;
+	int err;
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	/* reassign AUX0 to CD */
+        strcpy(id1.name, "Aux Playback Switch");
+        strcpy(id2.name, "CD Playback Switch");
+        if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) {
+		snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+                return err;
+	}
+        strcpy(id1.name, "Aux Playback Volume");
+        strcpy(id2.name, "CD Playback Volume");
+        if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) {
+		snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+                return err;
+	}
+	/* reassign AUX1 to FM */
+        strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+        strcpy(id2.name, "FM Playback Switch");
+        if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) {
+		snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+                return err;
+	}
+        strcpy(id1.name, "Aux Playback Volume");
+        strcpy(id2.name, "FM Playback Volume");
+        if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) {
+		snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n");
+                return err;
+	}
+	/* add OPL3SA2 controls */
+	for (idx = 0; idx < ARRAY_SIZE(snd_opl3sa2_controls); idx++) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_opl3sa2_controls[idx], chip))) < 0)
+			return err;
+		switch (idx) {
+		case 0: chip->master_switch = kctl; kctl->private_free = snd_opl3sa2_master_free; break;
+		case 1: chip->master_volume = kctl; kctl->private_free = snd_opl3sa2_master_free; break;
+		}
+	}
+	if (chip->version > 2) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_opl3sa2_tone_controls); idx++)
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_opl3sa2_tone_controls[idx], chip))) < 0)
+				return err;
+	}
+	return 0;
+}
+
+/* Power Management support functions */
+#ifdef CONFIG_PM
+static int snd_opl3sa2_suspend(struct snd_card *card, pm_message_t state)
+{
+	if (card) {
+		struct snd_opl3sa2 *chip = card->private_data;
+
+		snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+		chip->wss->suspend(chip->wss);
+		/* power down */
+		snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D3);
+	}
+
+	return 0;
+}
+
+static int snd_opl3sa2_resume(struct snd_card *card)
+{
+	struct snd_opl3sa2 *chip;
+	int i;
+
+	if (!card)
+		return 0;
+
+	chip = card->private_data;
+	/* power up */
+	snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D0);
+
+	/* restore registers */
+	for (i = 2; i <= 0x0a; i++) {
+		if (i != OPL3SA2_IRQ_STATUS)
+			snd_opl3sa2_write(chip, i, chip->ctlregs[i]);
+	}
+	if (chip->version > 2) {
+		for (i = 0x12; i <= 0x16; i++)
+			snd_opl3sa2_write(chip, i, chip->ctlregs[i]);
+	}
+	/* restore wss */
+	chip->wss->resume(chip->wss);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_PNP
+static int __devinit snd_opl3sa2_pnp(int dev, struct snd_opl3sa2 *chip,
+				     struct pnp_dev *pdev)
+{
+	if (pnp_activate_dev(pdev) < 0) {
+		snd_printk(KERN_ERR "PnP configure failure (out of resources?)\n");
+		return -EBUSY;
+	}
+	sb_port[dev] = pnp_port_start(pdev, 0);
+	wss_port[dev] = pnp_port_start(pdev, 1);
+	fm_port[dev] = pnp_port_start(pdev, 2);
+	midi_port[dev] = pnp_port_start(pdev, 3);
+	port[dev] = pnp_port_start(pdev, 4);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+	snd_printdd("%sPnP OPL3-SA: sb port=0x%lx, wss port=0x%lx, fm port=0x%lx, midi port=0x%lx\n",
+		pnp_device_is_pnpbios(pdev) ? "BIOS" : "ISA", sb_port[dev], wss_port[dev], fm_port[dev], midi_port[dev]);
+	snd_printdd("%sPnP OPL3-SA: control port=0x%lx, dma1=%i, dma2=%i, irq=%i\n",
+		pnp_device_is_pnpbios(pdev) ? "BIOS" : "ISA", port[dev], dma1[dev], dma2[dev], irq[dev]);
+	return 0;
+}
+#endif /* CONFIG_PNP */
+
+static void snd_opl3sa2_free(struct snd_card *card)
+{
+	struct snd_opl3sa2 *chip = card->private_data;
+	if (chip->irq >= 0)
+		free_irq(chip->irq, card);
+	release_and_free_resource(chip->res_port);
+}
+
+static int snd_opl3sa2_card_new(int dev, struct snd_card **cardp)
+{
+	struct snd_card *card;
+	struct snd_opl3sa2 *chip;
+	int err;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_opl3sa2), &card);
+	if (err < 0)
+		return err;
+	strcpy(card->driver, "OPL3SA2");
+	strcpy(card->shortname, "Yamaha OPL3-SA");
+	chip = card->private_data;
+	spin_lock_init(&chip->reg_lock);
+	chip->irq = -1;
+	card->private_free = snd_opl3sa2_free;
+	*cardp = card;
+	return 0;
+}
+
+static int __devinit snd_opl3sa2_probe(struct snd_card *card, int dev)
+{
+	int xirq, xdma1, xdma2;
+	struct snd_opl3sa2 *chip;
+	struct snd_wss *wss;
+	struct snd_opl3 *opl3;
+	int err;
+
+	/* initialise this card from supplied (or default) parameter*/ 
+	chip = card->private_data;
+	chip->ymode = opl3sa3_ymode[dev] & 0x03 ;
+	chip->port = port[dev];
+	xirq = irq[dev];
+	xdma1 = dma1[dev];
+	xdma2 = dma2[dev];
+	if (xdma2 < 0)
+		chip->single_dma = 1;
+	err = snd_opl3sa2_detect(card);
+	if (err < 0)
+		return err;
+	err = request_irq(xirq, snd_opl3sa2_interrupt, IRQF_DISABLED,
+			  "OPL3-SA2", card);
+	if (err) {
+		snd_printk(KERN_ERR PFX "can't grab IRQ %d\n", xirq);
+		return -ENODEV;
+	}
+	chip->irq = xirq;
+	err = snd_wss_create(card,
+			     wss_port[dev] + 4, -1,
+			     xirq, xdma1, xdma2,
+			     WSS_HW_OPL3SA2, WSS_HWSHARE_IRQ, &wss);
+	if (err < 0) {
+		snd_printd("Oops, WSS not detected at 0x%lx\n", wss_port[dev] + 4);
+		return err;
+	}
+	chip->wss = wss;
+	err = snd_wss_pcm(wss, 0, NULL);
+	if (err < 0)
+		return err;
+	err = snd_wss_mixer(wss);
+	if (err < 0)
+		return err;
+	err = snd_opl3sa2_mixer(card);
+	if (err < 0)
+		return err;
+	err = snd_wss_timer(wss, 0, NULL);
+	if (err < 0)
+		return err;
+	if (fm_port[dev] >= 0x340 && fm_port[dev] < 0x400) {
+		if ((err = snd_opl3_create(card, fm_port[dev],
+					   fm_port[dev] + 2,
+					   OPL3_HW_OPL3, 0, &opl3)) < 0)
+			return err;
+		if ((err = snd_opl3_timer_new(opl3, 1, 2)) < 0)
+			return err;
+		if ((err = snd_opl3_hwdep_new(opl3, 0, 1, &chip->synth)) < 0)
+			return err;
+	}
+	if (midi_port[dev] >= 0x300 && midi_port[dev] < 0x340) {
+		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_OPL3SA2,
+					       midi_port[dev], 0,
+					       xirq, 0, &chip->rmidi)) < 0)
+			return err;
+	}
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		card->shortname, chip->port, xirq, xdma1);
+	if (xdma2 >= 0)
+		sprintf(card->longname + strlen(card->longname), "&%d", xdma2);
+
+	return snd_card_register(card);
+}
+
+#ifdef CONFIG_PNP
+static int __devinit snd_opl3sa2_pnp_detect(struct pnp_dev *pdev,
+					    const struct pnp_device_id *id)
+{
+	static int dev;
+	int err;
+	struct snd_card *card;
+
+	if (pnp_device_is_isapnp(pdev))
+		return -ENOENT;	/* we have another procedure - card */
+	for (; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	err = snd_opl3sa2_card_new(dev, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_opl3sa2_pnp(dev, card->private_data, pdev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_card_set_dev(card, &pdev->dev);
+	if ((err = snd_opl3sa2_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pnp_set_drvdata(pdev, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_opl3sa2_pnp_remove(struct pnp_dev * pdev)
+{
+	snd_card_free(pnp_get_drvdata(pdev));
+	pnp_set_drvdata(pdev, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_opl3sa2_pnp_suspend(struct pnp_dev *pdev, pm_message_t state)
+{
+	return snd_opl3sa2_suspend(pnp_get_drvdata(pdev), state);
+}
+static int snd_opl3sa2_pnp_resume(struct pnp_dev *pdev)
+{
+	return snd_opl3sa2_resume(pnp_get_drvdata(pdev));
+}
+#endif
+
+static struct pnp_driver opl3sa2_pnp_driver = {
+	.name = "snd-opl3sa2-pnpbios",
+	.id_table = snd_opl3sa2_pnpbiosids,
+	.probe = snd_opl3sa2_pnp_detect,
+	.remove = __devexit_p(snd_opl3sa2_pnp_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_opl3sa2_pnp_suspend,
+	.resume = snd_opl3sa2_pnp_resume,
+#endif
+};
+
+static int __devinit snd_opl3sa2_pnp_cdetect(struct pnp_card_link *pcard,
+					     const struct pnp_card_device_id *id)
+{
+	static int dev;
+	struct pnp_dev *pdev;
+	int err;
+	struct snd_card *card;
+
+	pdev = pnp_request_card_device(pcard, id->devs[0].id, NULL);
+	if (pdev == NULL) {
+		snd_printk(KERN_ERR PFX "can't get pnp device from id '%s'\n",
+			   id->devs[0].id);
+		return -EBUSY;
+	}
+	for (; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	err = snd_opl3sa2_card_new(dev, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_opl3sa2_pnp(dev, card->private_data, pdev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_card_set_dev(card, &pdev->dev);
+	if ((err = snd_opl3sa2_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_opl3sa2_pnp_cremove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_opl3sa2_pnp_csuspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	return snd_opl3sa2_suspend(pnp_get_card_drvdata(pcard), state);
+}
+static int snd_opl3sa2_pnp_cresume(struct pnp_card_link *pcard)
+{
+	return snd_opl3sa2_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver opl3sa2_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+	.name = "snd-opl3sa2-cpnp",
+	.id_table = snd_opl3sa2_pnpids,
+	.probe = snd_opl3sa2_pnp_cdetect,
+	.remove = __devexit_p(snd_opl3sa2_pnp_cremove),
+#ifdef CONFIG_PM
+	.suspend = snd_opl3sa2_pnp_csuspend,
+	.resume = snd_opl3sa2_pnp_cresume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __devinit snd_opl3sa2_isa_match(struct device *pdev,
+					   unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+#ifdef CONFIG_PNP
+	if (isapnp[dev])
+		return 0;
+#endif
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify port\n");
+		return 0;
+	}
+	if (wss_port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify wss_port\n");
+		return 0;
+	}
+	if (fm_port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify fm_port\n");
+		return 0;
+	}
+	if (midi_port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR PFX "specify midi_port\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int __devinit snd_opl3sa2_isa_probe(struct device *pdev,
+					   unsigned int dev)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_opl3sa2_card_new(dev, &card);
+	if (err < 0)
+		return err;
+	snd_card_set_dev(card, pdev);
+	if ((err = snd_opl3sa2_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(pdev, card);
+	return 0;
+}
+
+static int __devexit snd_opl3sa2_isa_remove(struct device *devptr,
+					    unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	dev_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_opl3sa2_isa_suspend(struct device *dev, unsigned int n,
+				   pm_message_t state)
+{
+	return snd_opl3sa2_suspend(dev_get_drvdata(dev), state);
+}
+
+static int snd_opl3sa2_isa_resume(struct device *dev, unsigned int n)
+{
+	return snd_opl3sa2_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#define DEV_NAME "opl3sa2"
+
+static struct isa_driver snd_opl3sa2_isa_driver = {
+	.match		= snd_opl3sa2_isa_match,
+	.probe		= snd_opl3sa2_isa_probe,
+	.remove		= __devexit_p(snd_opl3sa2_isa_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_opl3sa2_isa_suspend,
+	.resume		= snd_opl3sa2_isa_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+static int __init alsa_card_opl3sa2_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_opl3sa2_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_driver(&opl3sa2_pnp_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	err = pnp_register_card_driver(&opl3sa2_pnpc_driver);
+	if (!err)
+		pnpc_registered = 1;
+
+	if (isa_registered || pnp_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_opl3sa2_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnpc_registered)
+		pnp_unregister_card_driver(&opl3sa2_pnpc_driver);
+	if (pnp_registered)
+		pnp_unregister_driver(&opl3sa2_pnp_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_opl3sa2_isa_driver);
+}
+
+module_init(alsa_card_opl3sa2_init)
+module_exit(alsa_card_opl3sa2_exit)
diff --git a/linux/sound/isa/opti9xx/Makefile b/linux/sound/isa/opti9xx/Makefile
new file mode 100644
index 0000000..b4d894d
--- /dev/null
+++ b/linux/sound/isa/opti9xx/Makefile
@@ -0,0 +1,15 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-opti92x-ad1848-objs := opti92x-ad1848.o
+snd-opti92x-cs4231-objs := opti92x-cs4231.o
+snd-opti93x-objs := opti93x.o
+snd-miro-objs := miro.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_OPTI92X_AD1848) += snd-opti92x-ad1848.o
+obj-$(CONFIG_SND_OPTI92X_CS4231) += snd-opti92x-cs4231.o
+obj-$(CONFIG_SND_OPTI93X) += snd-opti93x.o
+obj-$(CONFIG_SND_MIRO) += snd-miro.o
diff --git a/linux/sound/isa/opti9xx/miro.c b/linux/sound/isa/opti9xx/miro.c
new file mode 100644
index 0000000..8c24102
--- /dev/null
+++ b/linux/sound/isa/opti9xx/miro.c
@@ -0,0 +1,1681 @@
+/*
+ *   ALSA soundcard driver for Miro miroSOUND PCM1 pro
+ *                                  miroSOUND PCM12
+ *                                  miroSOUND PCM20 Radio
+ *
+ *   Copyright (C) 2004-2005 Martin Langer <martin-langer@gmx.de>
+ *
+ *   Based on OSS ACI and ALSA OPTi9xx drivers
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl4.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+#include <sound/aci.h>
+
+MODULE_AUTHOR("Martin Langer <martin-langer@gmx.de>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Miro miroSOUND PCM1 pro, PCM12, PCM20 Radio");
+MODULE_SUPPORTED_DEVICE("{{Miro,miroSOUND PCM1 pro}, "
+			"{Miro,miroSOUND PCM12}, "
+			"{Miro,miroSOUND PCM20 Radio}}");
+
+static int index = SNDRV_DEFAULT_IDX1;		/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;		/* ID for this card */
+static long port = SNDRV_DEFAULT_PORT1; 	/* 0x530,0xe80,0xf40,0x604 */
+static long mpu_port = SNDRV_DEFAULT_PORT1;	/* 0x300,0x310,0x320,0x330 */
+static long fm_port = SNDRV_DEFAULT_PORT1;	/* 0x388 */
+static int irq = SNDRV_DEFAULT_IRQ1;		/* 5,7,9,10,11 */
+static int mpu_irq = SNDRV_DEFAULT_IRQ1;	/* 5,7,9,10 */
+static int dma1 = SNDRV_DEFAULT_DMA1;		/* 0,1,3 */
+static int dma2 = SNDRV_DEFAULT_DMA1;		/* 0,1,3 */
+static int wss;
+static int ide;
+#ifdef CONFIG_PNP
+static int isapnp = 1;				/* Enable ISA PnP detection */
+#endif
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for miro soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for miro soundcard.");
+module_param(port, long, 0444);
+MODULE_PARM_DESC(port, "WSS port # for miro driver.");
+module_param(mpu_port, long, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for miro driver.");
+module_param(fm_port, long, 0444);
+MODULE_PARM_DESC(fm_port, "FM Port # for miro driver.");
+module_param(irq, int, 0444);
+MODULE_PARM_DESC(irq, "WSS irq # for miro driver.");
+module_param(mpu_irq, int, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for miro driver.");
+module_param(dma1, int, 0444);
+MODULE_PARM_DESC(dma1, "1st dma # for miro driver.");
+module_param(dma2, int, 0444);
+MODULE_PARM_DESC(dma2, "2nd dma # for miro driver.");
+module_param(wss, int, 0444);
+MODULE_PARM_DESC(wss, "wss mode");
+module_param(ide, int, 0444);
+MODULE_PARM_DESC(ide, "enable ide port");
+#ifdef CONFIG_PNP
+module_param(isapnp, bool, 0444);
+MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard.");
+#endif
+
+#define OPTi9XX_HW_DETECT	0
+#define OPTi9XX_HW_82C928	1
+#define OPTi9XX_HW_82C929	2
+#define OPTi9XX_HW_82C924	3
+#define OPTi9XX_HW_82C925	4
+#define OPTi9XX_HW_82C930	5
+#define OPTi9XX_HW_82C931	6
+#define OPTi9XX_HW_82C933	7
+#define OPTi9XX_HW_LAST		OPTi9XX_HW_82C933
+
+#define OPTi9XX_MC_REG(n)	n
+
+struct snd_miro {
+	unsigned short hardware;
+	unsigned char password;
+	char name[7];
+
+	struct resource *res_mc_base;
+	struct resource *res_aci_port;
+
+	unsigned long mc_base;
+	unsigned long mc_base_size;
+	unsigned long pwd_reg;
+
+	spinlock_t lock;
+	struct snd_pcm *pcm;
+
+	long wss_base;
+	int irq;
+	int dma1;
+	int dma2;
+
+	long mpu_port;
+	int mpu_irq;
+
+	struct snd_miro_aci *aci;
+};
+
+static struct snd_miro_aci aci_device;
+
+static char * snd_opti9xx_names[] = {
+	"unknown",
+	"82C928", "82C929",
+	"82C924", "82C925",
+	"82C930", "82C931", "82C933"
+};
+
+static int snd_miro_pnp_is_probed;
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_miro_pnpids[] = {
+	/* PCM20 and PCM12 in PnP mode */
+	{ .id = "MIR0924",
+	  .devs = { { "MIR0000" }, { "MIR0002" }, { "MIR0005" } }, },
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_miro_pnpids);
+
+#endif	/* CONFIG_PNP */
+
+/* 
+ *  ACI control
+ */
+
+static int aci_busy_wait(struct snd_miro_aci *aci)
+{
+	long timeout;
+	unsigned char byte;
+
+	for (timeout = 1; timeout <= ACI_MINTIME + 30; timeout++) {
+		byte = inb(aci->aci_port + ACI_REG_BUSY);
+		if ((byte & 1) == 0) {
+			if (timeout >= ACI_MINTIME)
+				snd_printd("aci ready in round %ld.\n",
+					   timeout-ACI_MINTIME);
+			return byte;
+		}
+		if (timeout >= ACI_MINTIME) {
+			long out=10*HZ;
+			switch (timeout-ACI_MINTIME) {
+			case 0 ... 9:
+				out /= 10;
+			case 10 ... 19:
+				out /= 10;
+			case 20 ... 30:
+				out /= 10;
+			default:
+				set_current_state(TASK_UNINTERRUPTIBLE);
+				schedule_timeout(out);
+				break;
+			}
+		}
+	}
+	snd_printk(KERN_ERR "aci_busy_wait() time out\n");
+	return -EBUSY;
+}
+
+static inline int aci_write(struct snd_miro_aci *aci, unsigned char byte)
+{
+	if (aci_busy_wait(aci) >= 0) {
+		outb(byte, aci->aci_port + ACI_REG_COMMAND);
+		return 0;
+	} else {
+		snd_printk(KERN_ERR "aci busy, aci_write(0x%x) stopped.\n", byte);
+		return -EBUSY;
+	}
+}
+
+static inline int aci_read(struct snd_miro_aci *aci)
+{
+	unsigned char byte;
+
+	if (aci_busy_wait(aci) >= 0) {
+		byte = inb(aci->aci_port + ACI_REG_STATUS);
+		return byte;
+	} else {
+		snd_printk(KERN_ERR "aci busy, aci_read() stopped.\n");
+		return -EBUSY;
+	}
+}
+
+int snd_aci_cmd(struct snd_miro_aci *aci, int write1, int write2, int write3)
+{
+	int write[] = {write1, write2, write3};
+	int value, i;
+
+	if (mutex_lock_interruptible(&aci->aci_mutex))
+		return -EINTR;
+
+	for (i=0; i<3; i++) {
+		if (write[i]< 0 || write[i] > 255)
+			break;
+		else {
+			value = aci_write(aci, write[i]);
+			if (value < 0)
+				goto out;
+		}
+	}
+
+	value = aci_read(aci);
+
+out:	mutex_unlock(&aci->aci_mutex);
+	return value;
+}
+EXPORT_SYMBOL(snd_aci_cmd);
+
+static int aci_getvalue(struct snd_miro_aci *aci, unsigned char index)
+{
+	return snd_aci_cmd(aci, ACI_STATUS, index, -1);
+}
+
+static int aci_setvalue(struct snd_miro_aci *aci, unsigned char index,
+			int value)
+{
+	return snd_aci_cmd(aci, index, value, -1);
+}
+
+struct snd_miro_aci *snd_aci_get_aci(void)
+{
+	if (aci_device.aci_port == 0)
+		return NULL;
+	return &aci_device;
+}
+EXPORT_SYMBOL(snd_aci_get_aci);
+
+/*
+ *  MIXER part
+ */
+
+#define snd_miro_info_capture	snd_ctl_boolean_mono_info
+
+static int snd_miro_get_capture(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int value;
+
+	value = aci_getvalue(miro->aci, ACI_S_GENERAL);
+	if (value < 0) {
+		snd_printk(KERN_ERR "snd_miro_get_capture() failed: %d\n",
+			   value);
+		return value;
+	}
+
+	ucontrol->value.integer.value[0] = value & 0x20;
+
+	return 0;
+}
+
+static int snd_miro_put_capture(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int change, value, error;
+
+	value = !(ucontrol->value.integer.value[0]);
+
+	error = aci_setvalue(miro->aci, ACI_SET_SOLOMODE, value);
+	if (error < 0) {
+		snd_printk(KERN_ERR "snd_miro_put_capture() failed: %d\n",
+			   error);
+		return error;
+	}
+
+	change = (value != miro->aci->aci_solomode);
+	miro->aci->aci_solomode = value;
+	
+	return change;
+}
+
+static int snd_miro_info_preamp(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 3;
+
+	return 0;
+}
+
+static int snd_miro_get_preamp(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int value;
+
+	if (miro->aci->aci_version <= 176) {
+
+		/* 
+		   OSS says it's not readable with versions < 176.
+		   But it doesn't work on my card,
+		   which is a PCM12 with aci_version = 176.
+		*/
+
+		ucontrol->value.integer.value[0] = miro->aci->aci_preamp;
+		return 0;
+	}
+
+	value = aci_getvalue(miro->aci, ACI_GET_PREAMP);
+	if (value < 0) {
+		snd_printk(KERN_ERR "snd_miro_get_preamp() failed: %d\n",
+			   value);
+		return value;
+	}
+	
+	ucontrol->value.integer.value[0] = value;
+
+	return 0;
+}
+
+static int snd_miro_put_preamp(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int error, value, change;
+
+	value = ucontrol->value.integer.value[0];
+
+	error = aci_setvalue(miro->aci, ACI_SET_PREAMP, value);
+	if (error < 0) {
+		snd_printk(KERN_ERR "snd_miro_put_preamp() failed: %d\n",
+			   error);
+		return error;
+	}
+
+	change = (value != miro->aci->aci_preamp);
+	miro->aci->aci_preamp = value;
+
+	return change;
+}
+
+#define snd_miro_info_amp	snd_ctl_boolean_mono_info
+
+static int snd_miro_get_amp(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = miro->aci->aci_amp;
+
+	return 0;
+}
+
+static int snd_miro_put_amp(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int error, value, change;
+
+	value = ucontrol->value.integer.value[0];
+
+	error = aci_setvalue(miro->aci, ACI_SET_POWERAMP, value);
+	if (error < 0) {
+		snd_printk(KERN_ERR "snd_miro_put_amp() to %d failed: %d\n", value, error);
+		return error;
+	}
+
+	change = (value != miro->aci->aci_amp);
+	miro->aci->aci_amp = value;
+
+	return change;
+}
+
+#define MIRO_DOUBLE(ctl_name, ctl_index, get_right_reg, set_right_reg) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = ctl_name, \
+  .index = ctl_index, \
+  .info = snd_miro_info_double, \
+  .get = snd_miro_get_double, \
+  .put = snd_miro_put_double, \
+  .private_value = get_right_reg | (set_right_reg << 8) \
+}
+
+static int snd_miro_info_double(struct snd_kcontrol *kcontrol, 
+				struct snd_ctl_elem_info *uinfo)
+{
+	int reg = kcontrol->private_value & 0xff;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+
+	if ((reg >= ACI_GET_EQ1) && (reg <= ACI_GET_EQ7)) {
+
+		/* equalizer elements */
+
+		uinfo->value.integer.min = - 0x7f;
+		uinfo->value.integer.max = 0x7f;
+	} else {
+
+		/* non-equalizer elements */
+
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = 0x20;
+	}
+
+	return 0;
+}
+
+static int snd_miro_get_double(struct snd_kcontrol *kcontrol, 
+			       struct snd_ctl_elem_value *uinfo)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	int left_val, right_val;
+
+	int right_reg = kcontrol->private_value & 0xff;
+	int left_reg = right_reg + 1;
+
+	right_val = aci_getvalue(miro->aci, right_reg);
+	if (right_val < 0) {
+		snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", right_reg, right_val);
+		return right_val;
+	}
+
+	left_val = aci_getvalue(miro->aci, left_reg);
+	if (left_val < 0) {
+		snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", left_reg, left_val);
+		return left_val;
+	}
+
+	if ((right_reg >= ACI_GET_EQ1) && (right_reg <= ACI_GET_EQ7)) {
+
+		/* equalizer elements */
+
+		if (left_val < 0x80) {
+			uinfo->value.integer.value[0] = left_val;
+		} else {
+			uinfo->value.integer.value[0] = 0x80 - left_val;
+		}
+
+		if (right_val < 0x80) {
+			uinfo->value.integer.value[1] = right_val;
+		} else {
+			uinfo->value.integer.value[1] = 0x80 - right_val;
+		}
+
+	} else {
+
+		/* non-equalizer elements */
+
+		uinfo->value.integer.value[0] = 0x20 - left_val;
+		uinfo->value.integer.value[1] = 0x20 - right_val;
+	}
+
+	return 0;
+}
+
+static int snd_miro_put_double(struct snd_kcontrol *kcontrol, 
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_miro *miro = snd_kcontrol_chip(kcontrol);
+	struct snd_miro_aci *aci = miro->aci;
+	int left, right, left_old, right_old;
+	int setreg_left, setreg_right, getreg_left, getreg_right;
+	int change, error;
+
+	left = ucontrol->value.integer.value[0];
+	right = ucontrol->value.integer.value[1];
+
+	setreg_right = (kcontrol->private_value >> 8) & 0xff;
+	setreg_left = setreg_right + 8;
+	if (setreg_right == ACI_SET_MASTER)
+		setreg_left -= 7;
+
+	getreg_right = kcontrol->private_value & 0xff;
+	getreg_left = getreg_right + 1;
+
+	left_old = aci_getvalue(aci, getreg_left);
+	if (left_old < 0) {
+		snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_left, left_old);
+		return left_old;
+	}
+
+	right_old = aci_getvalue(aci, getreg_right);
+	if (right_old < 0) {
+		snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_right, right_old);
+		return right_old;
+	}
+
+	if ((getreg_right >= ACI_GET_EQ1) && (getreg_right <= ACI_GET_EQ7)) {
+
+		/* equalizer elements */
+
+		if (left < -0x7f || left > 0x7f ||
+		    right < -0x7f || right > 0x7f)
+			return -EINVAL;
+
+		if (left_old > 0x80) 
+			left_old = 0x80 - left_old;
+		if (right_old > 0x80) 
+			right_old = 0x80 - right_old;
+
+		if (left >= 0) {
+			error = aci_setvalue(aci, setreg_left, left);
+			if (error < 0) {
+				snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+					   left, error);
+				return error;
+			}
+		} else {
+			error = aci_setvalue(aci, setreg_left, 0x80 - left);
+			if (error < 0) {
+				snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+					   0x80 - left, error);
+				return error;
+			}
+		}
+
+		if (right >= 0) {
+			error = aci_setvalue(aci, setreg_right, right);
+			if (error < 0) {
+				snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+					   right, error);
+				return error;
+			}
+		} else {
+			error = aci_setvalue(aci, setreg_right, 0x80 - right);
+			if (error < 0) {
+				snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+					   0x80 - right, error);
+				return error;
+			}
+		}
+
+	} else {
+
+		/* non-equalizer elements */
+
+		if (left < 0 || left > 0x20 ||
+		    right < 0 || right > 0x20)
+			return -EINVAL;
+
+		left_old = 0x20 - left_old;
+		right_old = 0x20 - right_old;
+
+		error = aci_setvalue(aci, setreg_left, 0x20 - left);
+		if (error < 0) {
+			snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+				   0x20 - left, error);
+			return error;
+		}
+		error = aci_setvalue(aci, setreg_right, 0x20 - right);
+		if (error < 0) {
+			snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n",
+				   0x20 - right, error);
+			return error;
+		}
+	}
+
+	change = (left != left_old) || (right != right_old);
+
+	return change;
+}
+
+static struct snd_kcontrol_new snd_miro_controls[] __devinitdata = {
+MIRO_DOUBLE("Master Playback Volume", 0, ACI_GET_MASTER, ACI_SET_MASTER),
+MIRO_DOUBLE("Mic Playback Volume", 1, ACI_GET_MIC, ACI_SET_MIC),
+MIRO_DOUBLE("Line Playback Volume", 1, ACI_GET_LINE, ACI_SET_LINE),
+MIRO_DOUBLE("CD Playback Volume", 0, ACI_GET_CD, ACI_SET_CD),
+MIRO_DOUBLE("Synth Playback Volume", 0, ACI_GET_SYNTH, ACI_SET_SYNTH),
+MIRO_DOUBLE("PCM Playback Volume", 1, ACI_GET_PCM, ACI_SET_PCM),
+MIRO_DOUBLE("Aux Playback Volume", 2, ACI_GET_LINE2, ACI_SET_LINE2),
+};
+
+/* Equalizer with seven bands (only PCM20) 
+   from -12dB up to +12dB on each band */
+static struct snd_kcontrol_new snd_miro_eq_controls[] __devinitdata = {
+MIRO_DOUBLE("Tone Control - 28 Hz", 0, ACI_GET_EQ1, ACI_SET_EQ1),
+MIRO_DOUBLE("Tone Control - 160 Hz", 0, ACI_GET_EQ2, ACI_SET_EQ2),
+MIRO_DOUBLE("Tone Control - 400 Hz", 0, ACI_GET_EQ3, ACI_SET_EQ3),
+MIRO_DOUBLE("Tone Control - 1 kHz", 0, ACI_GET_EQ4, ACI_SET_EQ4),
+MIRO_DOUBLE("Tone Control - 2.5 kHz", 0, ACI_GET_EQ5, ACI_SET_EQ5),
+MIRO_DOUBLE("Tone Control - 6.3 kHz", 0, ACI_GET_EQ6, ACI_SET_EQ6),
+MIRO_DOUBLE("Tone Control - 16 kHz", 0, ACI_GET_EQ7, ACI_SET_EQ7),
+};
+
+static struct snd_kcontrol_new snd_miro_radio_control[] __devinitdata = {
+MIRO_DOUBLE("Radio Playback Volume", 0, ACI_GET_LINE1, ACI_SET_LINE1),
+};
+
+static struct snd_kcontrol_new snd_miro_line_control[] __devinitdata = {
+MIRO_DOUBLE("Line Playback Volume", 2, ACI_GET_LINE1, ACI_SET_LINE1),
+};
+
+static struct snd_kcontrol_new snd_miro_preamp_control[] __devinitdata = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Mic Boost",
+	.index = 1,
+	.info = snd_miro_info_preamp,
+	.get = snd_miro_get_preamp,
+	.put = snd_miro_put_preamp,
+}};
+
+static struct snd_kcontrol_new snd_miro_amp_control[] __devinitdata = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Line Boost",
+	.index = 0,
+	.info = snd_miro_info_amp,
+	.get = snd_miro_get_amp,
+	.put = snd_miro_put_amp,
+}};
+
+static struct snd_kcontrol_new snd_miro_capture_control[] __devinitdata = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PCM Capture Switch",
+	.index = 0,
+	.info = snd_miro_info_capture,
+	.get = snd_miro_get_capture,
+	.put = snd_miro_put_capture,
+}};
+
+static unsigned char aci_init_values[][2] __devinitdata = {
+	{ ACI_SET_MUTE, 0x00 },
+	{ ACI_SET_POWERAMP, 0x00 },
+	{ ACI_SET_PREAMP, 0x00 },
+	{ ACI_SET_SOLOMODE, 0x00 },
+	{ ACI_SET_MIC + 0, 0x20 },
+	{ ACI_SET_MIC + 8, 0x20 },
+	{ ACI_SET_LINE + 0, 0x20 },
+	{ ACI_SET_LINE + 8, 0x20 },
+	{ ACI_SET_CD + 0, 0x20 },
+	{ ACI_SET_CD + 8, 0x20 },
+	{ ACI_SET_PCM + 0, 0x20 },
+	{ ACI_SET_PCM + 8, 0x20 },
+	{ ACI_SET_LINE1 + 0, 0x20 },
+	{ ACI_SET_LINE1 + 8, 0x20 },
+	{ ACI_SET_LINE2 + 0, 0x20 },
+	{ ACI_SET_LINE2 + 8, 0x20 },
+	{ ACI_SET_SYNTH + 0, 0x20 },
+	{ ACI_SET_SYNTH + 8, 0x20 },
+	{ ACI_SET_MASTER + 0, 0x20 },
+	{ ACI_SET_MASTER + 1, 0x20 },
+};
+
+static int __devinit snd_set_aci_init_values(struct snd_miro *miro)
+{
+	int idx, error;
+	struct snd_miro_aci *aci = miro->aci;
+
+	/* enable WSS on PCM1 */
+
+	if ((aci->aci_product == 'A') && wss) {
+		error = aci_setvalue(aci, ACI_SET_WSS, wss);
+		if (error < 0) {
+			snd_printk(KERN_ERR "enabling WSS mode failed\n");
+			return error;
+		}
+	}
+
+	/* enable IDE port */
+
+	if (ide) {
+		error = aci_setvalue(aci, ACI_SET_IDE, ide);
+		if (error < 0) {
+			snd_printk(KERN_ERR "enabling IDE port failed\n");
+			return error;
+		}
+	}
+
+	/* set common aci values */
+
+	for (idx = 0; idx < ARRAY_SIZE(aci_init_values); idx++) {
+		error = aci_setvalue(aci, aci_init_values[idx][0],
+				     aci_init_values[idx][1]);
+		if (error < 0) {
+			snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", 
+				   aci_init_values[idx][0], error);
+                        return error;
+                }
+	}
+	aci->aci_amp = 0;
+	aci->aci_preamp = 0;
+	aci->aci_solomode = 1;
+
+	return 0;
+}
+
+static int __devinit snd_miro_mixer(struct snd_card *card,
+				    struct snd_miro *miro)
+{
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!miro || !card))
+		return -EINVAL;
+
+	switch (miro->hardware) {
+	case OPTi9XX_HW_82C924:
+		strcpy(card->mixername, "ACI & OPTi924");
+		break;
+	case OPTi9XX_HW_82C929:
+		strcpy(card->mixername, "ACI & OPTi929");
+		break;
+	default:
+		snd_BUG();
+		break;
+	}
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_miro_controls); idx++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_controls[idx], miro))) < 0)
+			return err;
+	}
+
+	if ((miro->aci->aci_product == 'A') ||
+	    (miro->aci->aci_product == 'B')) {
+		/* PCM1/PCM12 with power-amp and Line 2 */
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_line_control[0], miro))) < 0)
+			return err;
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_amp_control[0], miro))) < 0)
+			return err;
+	}
+
+	if ((miro->aci->aci_product == 'B') ||
+	    (miro->aci->aci_product == 'C')) {
+		/* PCM12/PCM20 with mic-preamp */
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_preamp_control[0], miro))) < 0)
+			return err;
+		if (miro->aci->aci_version >= 176)
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_capture_control[0], miro))) < 0)
+				return err;
+	}
+
+	if (miro->aci->aci_product == 'C') {
+		/* PCM20 with radio and 7 band equalizer */
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_radio_control[0], miro))) < 0)
+			return err;
+		for (idx = 0; idx < ARRAY_SIZE(snd_miro_eq_controls); idx++) {
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_eq_controls[idx], miro))) < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+static long snd_legacy_find_free_ioport(long *port_table, long size)
+{
+	while (*port_table != -1) {
+		struct resource *res;
+		if ((res = request_region(*port_table, size, 
+					  "ALSA test")) != NULL) {
+			release_and_free_resource(res);
+			return *port_table;
+		}
+		port_table++;
+	}
+	return -1;
+}
+
+static int __devinit snd_miro_init(struct snd_miro *chip,
+				   unsigned short hardware)
+{
+	static int opti9xx_mc_size[] = {7, 7, 10, 10, 2, 2, 2};
+
+	chip->hardware = hardware;
+	strcpy(chip->name, snd_opti9xx_names[hardware]);
+
+	chip->mc_base_size = opti9xx_mc_size[hardware];  
+
+	spin_lock_init(&chip->lock);
+
+	chip->wss_base = -1;
+	chip->irq = -1;
+	chip->dma1 = -1;
+	chip->dma2 = -1;
+	chip->mpu_port = -1;
+	chip->mpu_irq = -1;
+
+	chip->pwd_reg = 3;
+
+#ifdef CONFIG_PNP
+	if (isapnp && chip->mc_base)
+		/* PnP resource gives the least 10 bits */
+		chip->mc_base |= 0xc00;
+	else
+#endif
+		chip->mc_base = 0xf8c;
+
+	switch (hardware) {
+	case OPTi9XX_HW_82C929:
+		chip->password = 0xe3;
+		break;
+
+	case OPTi9XX_HW_82C924:
+		chip->password = 0xe5;
+		break;
+
+	default:
+		snd_printk(KERN_ERR "sorry, no support for %d\n", hardware);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static unsigned char snd_miro_read(struct snd_miro *chip,
+				   unsigned char reg)
+{
+	unsigned long flags;
+	unsigned char retval = 0xff;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(chip->password, chip->mc_base + chip->pwd_reg);
+
+	switch (chip->hardware) {
+	case OPTi9XX_HW_82C924:
+		if (reg > 7) {
+			outb(reg, chip->mc_base + 8);
+			outb(chip->password, chip->mc_base + chip->pwd_reg);
+			retval = inb(chip->mc_base + 9);
+			break;
+		}
+
+	case OPTi9XX_HW_82C929:
+		retval = inb(chip->mc_base + reg);
+		break;
+
+	default:
+		snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return retval;
+}
+
+static void snd_miro_write(struct snd_miro *chip, unsigned char reg,
+			   unsigned char value)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(chip->password, chip->mc_base + chip->pwd_reg);
+
+	switch (chip->hardware) {
+	case OPTi9XX_HW_82C924:
+		if (reg > 7) {
+			outb(reg, chip->mc_base + 8);
+			outb(chip->password, chip->mc_base + chip->pwd_reg);
+			outb(value, chip->mc_base + 9);
+			break;
+		}
+
+	case OPTi9XX_HW_82C929:
+		outb(value, chip->mc_base + reg);
+		break;
+
+	default:
+		snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+#define snd_miro_write_mask(chip, reg, value, mask)	\
+	snd_miro_write(chip, reg,			\
+		(snd_miro_read(chip, reg) & ~(mask)) | ((value) & (mask)))
+
+/*
+ *  Proc Interface
+ */
+
+static void snd_miro_proc_read(struct snd_info_entry * entry, 
+			       struct snd_info_buffer *buffer)
+{
+	struct snd_miro *miro = (struct snd_miro *) entry->private_data;
+	struct snd_miro_aci *aci = miro->aci;
+	char* model = "unknown";
+
+	/* miroSOUND PCM1 pro, early PCM12 */
+
+	if ((miro->hardware == OPTi9XX_HW_82C929) &&
+	    (aci->aci_vendor == 'm') &&
+	    (aci->aci_product == 'A')) {
+		switch (aci->aci_version) {
+		case 3:
+			model = "miroSOUND PCM1 pro";
+			break;
+		default:
+			model = "miroSOUND PCM1 pro / (early) PCM12";
+			break;
+		}
+	}
+
+	/* miroSOUND PCM12, PCM12 (Rev. E), PCM12 pnp */
+
+	if ((miro->hardware == OPTi9XX_HW_82C924) &&
+	    (aci->aci_vendor == 'm') &&
+	    (aci->aci_product == 'B')) {
+		switch (aci->aci_version) {
+		case 4:
+			model = "miroSOUND PCM12";
+			break;
+		case 176:
+			model = "miroSOUND PCM12 (Rev. E)";
+			break;
+		default:
+			model = "miroSOUND PCM12 / PCM12 pnp";
+			break;
+		}
+	}
+
+	/* miroSOUND PCM20 radio */
+
+	if ((miro->hardware == OPTi9XX_HW_82C924) &&
+	    (aci->aci_vendor == 'm') &&
+	    (aci->aci_product == 'C')) {
+		switch (aci->aci_version) {
+		case 7:
+			model = "miroSOUND PCM20 radio (Rev. E)";
+			break;
+		default:
+			model = "miroSOUND PCM20 radio";
+			break;
+		}
+	}
+
+	snd_iprintf(buffer, "\nGeneral information:\n");
+	snd_iprintf(buffer, "  model   : %s\n", model);
+	snd_iprintf(buffer, "  opti    : %s\n", miro->name);
+	snd_iprintf(buffer, "  codec   : %s\n", miro->pcm->name);
+	snd_iprintf(buffer, "  port    : 0x%lx\n", miro->wss_base);
+	snd_iprintf(buffer, "  irq     : %d\n", miro->irq);
+	snd_iprintf(buffer, "  dma     : %d,%d\n\n", miro->dma1, miro->dma2);
+
+	snd_iprintf(buffer, "MPU-401:\n");
+	snd_iprintf(buffer, "  port    : 0x%lx\n", miro->mpu_port);
+	snd_iprintf(buffer, "  irq     : %d\n\n", miro->mpu_irq);
+
+	snd_iprintf(buffer, "ACI information:\n");
+	snd_iprintf(buffer, "  vendor  : ");
+	switch (aci->aci_vendor) {
+	case 'm':
+		snd_iprintf(buffer, "Miro\n");
+		break;
+	default:
+		snd_iprintf(buffer, "unknown (0x%x)\n", aci->aci_vendor);
+		break;
+	}
+
+	snd_iprintf(buffer, "  product : ");
+	switch (aci->aci_product) {
+	case 'A':
+		snd_iprintf(buffer, "miroSOUND PCM1 pro / (early) PCM12\n");
+		break;
+	case 'B':
+		snd_iprintf(buffer, "miroSOUND PCM12\n");
+		break;
+	case 'C':
+		snd_iprintf(buffer, "miroSOUND PCM20 radio\n");
+		break;
+	default:
+		snd_iprintf(buffer, "unknown (0x%x)\n", aci->aci_product);
+		break;
+	}
+
+	snd_iprintf(buffer, "  firmware: %d (0x%x)\n",
+		    aci->aci_version, aci->aci_version);
+	snd_iprintf(buffer, "  port    : 0x%lx-0x%lx\n", 
+		    aci->aci_port, aci->aci_port+2);
+	snd_iprintf(buffer, "  wss     : 0x%x\n", wss);
+	snd_iprintf(buffer, "  ide     : 0x%x\n", ide);
+	snd_iprintf(buffer, "  solomode: 0x%x\n", aci->aci_solomode);
+	snd_iprintf(buffer, "  amp     : 0x%x\n", aci->aci_amp);
+	snd_iprintf(buffer, "  preamp  : 0x%x\n", aci->aci_preamp);
+}
+
+static void __devinit snd_miro_proc_init(struct snd_card *card,
+					 struct snd_miro *miro)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(card, "miro", &entry))
+		snd_info_set_text_ops(entry, miro, snd_miro_proc_read);
+}
+
+/*
+ *  Init
+ */
+
+static int __devinit snd_miro_configure(struct snd_miro *chip)
+{
+	unsigned char wss_base_bits;
+	unsigned char irq_bits;
+	unsigned char dma_bits;
+	unsigned char mpu_port_bits = 0;
+	unsigned char mpu_irq_bits;
+	unsigned long flags;
+
+	snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80);
+	snd_miro_write_mask(chip, OPTi9XX_MC_REG(2), 0x20, 0x20); /* OPL4 */
+	snd_miro_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02);
+
+	switch (chip->hardware) {
+	case OPTi9XX_HW_82C924:
+		snd_miro_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02);
+		snd_miro_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff);
+		break;
+	case OPTi9XX_HW_82C929:
+		/* untested init commands for OPTi929 */
+		snd_miro_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c);
+		break;
+	default:
+		snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware);
+		return -EINVAL;
+	}
+
+	/* PnP resource says it decodes only 10 bits of address */
+	switch (chip->wss_base & 0x3ff) {
+	case 0x130:
+		chip->wss_base = 0x530;
+		wss_base_bits = 0x00;
+		break;
+	case 0x204:
+		chip->wss_base = 0x604;
+		wss_base_bits = 0x03;
+		break;
+	case 0x280:
+		chip->wss_base = 0xe80;
+		wss_base_bits = 0x01;
+		break;
+	case 0x340:
+		chip->wss_base = 0xf40;
+		wss_base_bits = 0x02;
+		break;
+	default:
+		snd_printk(KERN_ERR "WSS port 0x%lx not valid\n", chip->wss_base);
+		goto __skip_base;
+	}
+	snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30);
+
+__skip_base:
+	switch (chip->irq) {
+	case 5:
+		irq_bits = 0x05;
+		break;
+	case 7:
+		irq_bits = 0x01;
+		break;
+	case 9:
+		irq_bits = 0x02;
+		break;
+	case 10:
+		irq_bits = 0x03;
+		break;
+	case 11:
+		irq_bits = 0x04;
+		break;
+	default:
+		snd_printk(KERN_ERR "WSS irq # %d not valid\n", chip->irq);
+		goto __skip_resources;
+	}
+
+	switch (chip->dma1) {
+	case 0:
+		dma_bits = 0x01;
+		break;
+	case 1:
+		dma_bits = 0x02;
+		break;
+	case 3:
+		dma_bits = 0x03;
+		break;
+	default:
+		snd_printk(KERN_ERR "WSS dma1 # %d not valid\n", chip->dma1);
+		goto __skip_resources;
+	}
+
+	if (chip->dma1 == chip->dma2) {
+		snd_printk(KERN_ERR "don't want to share dmas\n");
+		return -EBUSY;
+	}
+
+	switch (chip->dma2) {
+	case 0:
+	case 1:
+		break;
+	default:
+		snd_printk(KERN_ERR "WSS dma2 # %d not valid\n", chip->dma2);
+		goto __skip_resources;
+	}
+	dma_bits |= 0x04;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(irq_bits << 3 | dma_bits, chip->wss_base);
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+__skip_resources:
+	if (chip->hardware > OPTi9XX_HW_82C928) {
+		switch (chip->mpu_port) {
+		case 0:
+		case -1:
+			break;
+		case 0x300:
+			mpu_port_bits = 0x03;
+			break;
+		case 0x310:
+			mpu_port_bits = 0x02;
+			break;
+		case 0x320:
+			mpu_port_bits = 0x01;
+			break;
+		case 0x330:
+			mpu_port_bits = 0x00;
+			break;
+		default:
+			snd_printk(KERN_ERR "MPU-401 port 0x%lx not valid\n",
+				   chip->mpu_port);
+			goto __skip_mpu;
+		}
+
+		switch (chip->mpu_irq) {
+		case 5:
+			mpu_irq_bits = 0x02;
+			break;
+		case 7:
+			mpu_irq_bits = 0x03;
+			break;
+		case 9:
+			mpu_irq_bits = 0x00;
+			break;
+		case 10:
+			mpu_irq_bits = 0x01;
+			break;
+		default:
+			snd_printk(KERN_ERR "MPU-401 irq # %d not valid\n",
+				   chip->mpu_irq);
+			goto __skip_mpu;
+		}
+
+		snd_miro_write_mask(chip, OPTi9XX_MC_REG(6),
+			(chip->mpu_port <= 0) ? 0x00 :
+				0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3,
+			0xf8);
+	}
+__skip_mpu:
+
+	return 0;
+}
+
+static int __devinit snd_miro_opti_check(struct snd_miro *chip)
+{
+	unsigned char value;
+
+	chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size,
+					   "OPTi9xx MC");
+	if (chip->res_mc_base == NULL)
+		return -ENOMEM;
+
+	value = snd_miro_read(chip, OPTi9XX_MC_REG(1));
+	if (value != 0xff && value != inb(chip->mc_base + OPTi9XX_MC_REG(1)))
+		if (value == snd_miro_read(chip, OPTi9XX_MC_REG(1)))
+			return 0;
+
+	release_and_free_resource(chip->res_mc_base);
+	chip->res_mc_base = NULL;
+
+	return -ENODEV;
+}
+
+static int __devinit snd_card_miro_detect(struct snd_card *card,
+					  struct snd_miro *chip)
+{
+	int i, err;
+
+	for (i = OPTi9XX_HW_82C929; i <= OPTi9XX_HW_82C924; i++) {
+
+		if ((err = snd_miro_init(chip, i)) < 0)
+			return err;
+
+		err = snd_miro_opti_check(chip);
+		if (err == 0)
+			return 1;
+	}
+
+	return -ENODEV;
+}
+
+static int __devinit snd_card_miro_aci_detect(struct snd_card *card,
+					      struct snd_miro *miro)
+{
+	unsigned char regval;
+	int i;
+	struct snd_miro_aci *aci = &aci_device;
+
+	miro->aci = aci;
+
+	mutex_init(&aci->aci_mutex);
+
+	/* get ACI port from OPTi9xx MC 4 */
+
+	regval=inb(miro->mc_base + 4);
+	aci->aci_port = (regval & 0x10) ? 0x344 : 0x354;
+
+	miro->res_aci_port = request_region(aci->aci_port, 3, "miro aci");
+	if (miro->res_aci_port == NULL) {
+		snd_printk(KERN_ERR "aci i/o area 0x%lx-0x%lx already used.\n", 
+			   aci->aci_port, aci->aci_port+2);
+		return -ENOMEM;
+	}
+
+        /* force ACI into a known state */
+	for (i = 0; i < 3; i++)
+		if (snd_aci_cmd(aci, ACI_ERROR_OP, -1, -1) < 0) {
+			snd_printk(KERN_ERR "can't force aci into known state.\n");
+			return -ENXIO;
+		}
+
+	aci->aci_vendor = snd_aci_cmd(aci, ACI_READ_IDCODE, -1, -1);
+	aci->aci_product = snd_aci_cmd(aci, ACI_READ_IDCODE, -1, -1);
+	if (aci->aci_vendor < 0 || aci->aci_product < 0) {
+		snd_printk(KERN_ERR "can't read aci id on 0x%lx.\n",
+			   aci->aci_port);
+		return -ENXIO;
+	}
+
+	aci->aci_version = snd_aci_cmd(aci, ACI_READ_VERSION, -1, -1);
+	if (aci->aci_version < 0) {
+		snd_printk(KERN_ERR "can't read aci version on 0x%lx.\n", 
+			   aci->aci_port);
+		return -ENXIO;
+	}
+
+	if (snd_aci_cmd(aci, ACI_INIT, -1, -1) < 0 ||
+	    snd_aci_cmd(aci, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0 ||
+	    snd_aci_cmd(aci, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0) {
+		snd_printk(KERN_ERR "can't initialize aci.\n"); 
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static void snd_card_miro_free(struct snd_card *card)
+{
+	struct snd_miro *miro = card->private_data;
+
+	release_and_free_resource(miro->res_aci_port);
+	if (miro->aci)
+		miro->aci->aci_port = 0;
+	release_and_free_resource(miro->res_mc_base);
+}
+
+static int __devinit snd_miro_probe(struct snd_card *card)
+{
+	int error;
+	struct snd_miro *miro = card->private_data;
+	struct snd_wss *codec;
+	struct snd_timer *timer;
+	struct snd_pcm *pcm;
+	struct snd_rawmidi *rmidi;
+
+	if (!miro->res_mc_base) {
+		miro->res_mc_base = request_region(miro->mc_base,
+						miro->mc_base_size,
+						"miro (OPTi9xx MC)");
+		if (miro->res_mc_base == NULL) {
+			snd_printk(KERN_ERR "request for OPTI9xx MC failed\n");
+			return -ENOMEM;
+		}
+	}
+
+	error = snd_card_miro_aci_detect(card, miro);
+	if (error < 0) {
+		snd_card_free(card);
+		snd_printk(KERN_ERR "unable to detect aci chip\n");
+		return -ENODEV;
+	}
+
+	miro->wss_base = port;
+	miro->mpu_port = mpu_port;
+	miro->irq = irq;
+	miro->mpu_irq = mpu_irq;
+	miro->dma1 = dma1;
+	miro->dma2 = dma2;
+
+	/* init proc interface */
+	snd_miro_proc_init(card, miro);
+
+	error = snd_miro_configure(miro);
+	if (error)
+		return error;
+
+	error = snd_wss_create(card, miro->wss_base + 4, -1,
+			       miro->irq, miro->dma1, miro->dma2,
+			       WSS_HW_DETECT, 0, &codec);
+	if (error < 0)
+		return error;
+
+	error = snd_wss_pcm(codec, 0, &pcm);
+	if (error < 0)
+		return error;
+
+	error = snd_wss_mixer(codec);
+	if (error < 0)
+		return error;
+
+	error = snd_wss_timer(codec, 0, &timer);
+	if (error < 0)
+		return error;
+
+	miro->pcm = pcm;
+
+	error = snd_miro_mixer(card, miro);
+	if (error < 0)
+		return error;
+
+	if (miro->aci->aci_vendor == 'm') {
+		/* It looks like a miro sound card. */
+		switch (miro->aci->aci_product) {
+		case 'A':
+			sprintf(card->shortname, 
+				"miroSOUND PCM1 pro / PCM12");
+			break;
+		case 'B':
+			sprintf(card->shortname, 
+				"miroSOUND PCM12");
+			break;
+		case 'C':
+			sprintf(card->shortname, 
+				"miroSOUND PCM20 radio");
+			break;
+		default:
+			sprintf(card->shortname, 
+				"unknown miro");
+			snd_printk(KERN_INFO "unknown miro aci id\n");
+			break;
+		}
+	} else {
+		snd_printk(KERN_INFO "found unsupported aci card\n");
+		sprintf(card->shortname, "unknown Cardinal Technologies");
+	}
+
+	strcpy(card->driver, "miro");
+	sprintf(card->longname, "%s: OPTi%s, %s at 0x%lx, irq %d, dma %d&%d",
+		card->shortname, miro->name, pcm->name, miro->wss_base + 4,
+		miro->irq, miro->dma1, miro->dma2);
+
+	if (mpu_port <= 0 || mpu_port == SNDRV_AUTO_PORT)
+		rmidi = NULL;
+	else {
+		error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+				mpu_port, 0, miro->mpu_irq, IRQF_DISABLED,
+				&rmidi);
+		if (error < 0)
+			snd_printk(KERN_WARNING "no MPU-401 device at 0x%lx?\n",
+				   mpu_port);
+	}
+
+	if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) {
+		struct snd_opl3 *opl3 = NULL;
+		struct snd_opl4 *opl4;
+
+		if (snd_opl4_create(card, fm_port, fm_port - 8,
+				    2, &opl3, &opl4) < 0)
+			snd_printk(KERN_WARNING "no OPL4 device at 0x%lx\n",
+				   fm_port);
+	}
+
+	error = snd_set_aci_init_values(miro);
+	if (error < 0)
+                return error;
+
+	return snd_card_register(card);
+}
+
+static int __devinit snd_miro_isa_match(struct device *devptr, unsigned int n)
+{
+#ifdef CONFIG_PNP
+	if (snd_miro_pnp_is_probed)
+		return 0;
+	if (isapnp)
+		return 0;
+#endif
+	return 1;
+}
+
+static int __devinit snd_miro_isa_probe(struct device *devptr, unsigned int n)
+{
+	static long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1};
+	static long possible_mpu_ports[] = {0x330, 0x300, 0x310, 0x320, -1};
+	static int possible_irqs[] = {11, 9, 10, 7, -1};
+	static int possible_mpu_irqs[] = {10, 5, 9, 7, -1};
+	static int possible_dma1s[] = {3, 1, 0, -1};
+	static int possible_dma2s[][2] = { {1, -1}, {0, -1}, {-1, -1},
+					   {0, -1} };
+
+	int error;
+	struct snd_miro *miro;
+	struct snd_card *card;
+
+	error = snd_card_create(index, id, THIS_MODULE,
+				sizeof(struct snd_miro), &card);
+	if (error < 0)
+		return error;
+
+	card->private_free = snd_card_miro_free;
+	miro = card->private_data;
+
+	error = snd_card_miro_detect(card, miro);
+	if (error < 0) {
+		snd_card_free(card);
+		snd_printk(KERN_ERR "unable to detect OPTi9xx chip\n");
+		return -ENODEV;
+	}
+
+	if (port == SNDRV_AUTO_PORT) {
+		port = snd_legacy_find_free_ioport(possible_ports, 4);
+		if (port < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR "unable to find a free WSS port\n");
+			return -EBUSY;
+		}
+	}
+
+	if (mpu_port == SNDRV_AUTO_PORT) {
+		mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2);
+		if (mpu_port < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR
+				   "unable to find a free MPU401 port\n");
+			return -EBUSY;
+		}
+	}
+
+	if (irq == SNDRV_AUTO_IRQ) {
+		irq = snd_legacy_find_free_irq(possible_irqs);
+		if (irq < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (mpu_irq == SNDRV_AUTO_IRQ) {
+		mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs);
+		if (mpu_irq < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR
+				   "unable to find a free MPU401 IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1 == SNDRV_AUTO_DMA) {
+		dma1 = snd_legacy_find_free_dma(possible_dma1s);
+		if (dma1 < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR "unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+	if (dma2 == SNDRV_AUTO_DMA) {
+		dma2 = snd_legacy_find_free_dma(possible_dma2s[dma1 % 4]);
+		if (dma2 < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR "unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+
+	snd_card_set_dev(card, devptr);
+
+	error = snd_miro_probe(card);
+	if (error < 0) {
+		snd_card_free(card);
+		return error;
+	}
+
+	dev_set_drvdata(devptr, card);
+	return 0;
+}
+
+static int __devexit snd_miro_isa_remove(struct device *devptr,
+					 unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	dev_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#define DEV_NAME "miro"
+
+static struct isa_driver snd_miro_driver = {
+	.match		= snd_miro_isa_match,
+	.probe		= snd_miro_isa_probe,
+	.remove		= __devexit_p(snd_miro_isa_remove),
+	/* FIXME: suspend/resume */
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+#ifdef CONFIG_PNP
+
+static int __devinit snd_card_miro_pnp(struct snd_miro *chip,
+					struct pnp_card_link *card,
+					const struct pnp_card_device_id *pid)
+{
+	struct pnp_dev *pdev;
+	int err;
+	struct pnp_dev *devmpu;
+	struct pnp_dev *devmc;
+
+	pdev = pnp_request_card_device(card, pid->devs[0].id, NULL);
+	if (pdev == NULL)
+		return -EBUSY;
+
+	devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL);
+	if (devmpu == NULL)
+		return -EBUSY;
+
+	devmc = pnp_request_card_device(card, pid->devs[2].id, NULL);
+	if (devmc == NULL)
+		return -EBUSY;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err);
+		return err;
+	}
+
+	err = pnp_activate_dev(devmc);
+	if (err < 0) {
+		snd_printk(KERN_ERR "MC pnp configure failure: %d\n",
+				    err);
+		return err;
+	}
+
+	port = pnp_port_start(pdev, 1);
+	fm_port = pnp_port_start(pdev, 2) + 8;
+
+	/*
+	 * The MC(0) is never accessed and the miroSOUND PCM20 card does not
+	 * include it in the PnP resource range. OPTI93x include it.
+	 */
+	chip->mc_base = pnp_port_start(devmc, 0) - 1;
+	chip->mc_base_size = pnp_port_len(devmc, 0) + 1;
+
+	irq = pnp_irq(pdev, 0);
+	dma1 = pnp_dma(pdev, 0);
+	dma2 = pnp_dma(pdev, 1);
+
+	if (mpu_port > 0) {
+		err = pnp_activate_dev(devmpu);
+		if (err < 0) {
+			snd_printk(KERN_ERR "MPU401 pnp configure failure\n");
+			mpu_port = -1;
+			return err;
+		}
+		mpu_port = pnp_port_start(devmpu, 0);
+		mpu_irq = pnp_irq(devmpu, 0);
+	}
+	return 0;
+}
+
+static int __devinit snd_miro_pnp_probe(struct pnp_card_link *pcard,
+					const struct pnp_card_device_id *pid)
+{
+	struct snd_card *card;
+	int err;
+	struct snd_miro *miro;
+
+	if (snd_miro_pnp_is_probed)
+		return -EBUSY;
+	if (!isapnp)
+		return -ENODEV;
+	err = snd_card_create(index, id, THIS_MODULE,
+				sizeof(struct snd_miro), &card);
+	if (err < 0)
+		return err;
+
+	card->private_free = snd_card_miro_free;
+	miro = card->private_data;
+
+	err = snd_card_miro_pnp(miro, pcard, pid);
+	if (err) {
+		snd_card_free(card);
+		return err;
+	}
+
+	/* only miroSOUND PCM20 and PCM12 == OPTi924 */
+	err = snd_miro_init(miro, OPTi9XX_HW_82C924);
+	if (err) {
+		snd_card_free(card);
+		return err;
+	}
+
+	err = snd_miro_opti_check(miro);
+	if (err) {
+		snd_printk(KERN_ERR "OPTI chip not found\n");
+		snd_card_free(card);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pcard->card->dev);
+	err = snd_miro_probe(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	snd_miro_pnp_is_probed = 1;
+	return 0;
+}
+
+static void __devexit snd_miro_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+	snd_miro_pnp_is_probed = 0;
+}
+
+static struct pnp_card_driver miro_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= "miro",
+	.id_table	= snd_miro_pnpids,
+	.probe		= snd_miro_pnp_probe,
+	.remove		= __devexit_p(snd_miro_pnp_remove),
+};
+#endif
+
+static int __init alsa_card_miro_init(void)
+{
+#ifdef CONFIG_PNP
+	pnp_register_card_driver(&miro_pnpc_driver);
+	if (snd_miro_pnp_is_probed)
+		return 0;
+	pnp_unregister_card_driver(&miro_pnpc_driver);
+#endif
+	return isa_register_driver(&snd_miro_driver, 1);
+}
+
+static void __exit alsa_card_miro_exit(void)
+{
+	if (!snd_miro_pnp_is_probed) {
+		isa_unregister_driver(&snd_miro_driver);
+		return;
+	}
+#ifdef CONFIG_PNP
+	pnp_unregister_card_driver(&miro_pnpc_driver);
+#endif
+}
+
+module_init(alsa_card_miro_init)
+module_exit(alsa_card_miro_exit)
diff --git a/linux/sound/isa/opti9xx/opti92x-ad1848.c b/linux/sound/isa/opti9xx/opti92x-ad1848.c
new file mode 100644
index 0000000..c35dc68
--- /dev/null
+++ b/linux/sound/isa/opti9xx/opti92x-ad1848.c
@@ -0,0 +1,1165 @@
+/*
+    card-opti92x-ad1848.c - driver for OPTi 82c92x based soundcards.
+    Copyright (C) 1998-2000 by Massimo Piccioni <dafastidio@libero.it>
+
+    Part of this code was developed at the Italian Ministry of Air Defence,
+    Sixth Division (oh, che pace ...), Rome.
+
+    Thanks to Maria Grazia Pollarini, Salvatore Vassallo.
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+*/
+
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#ifndef OPTi93X
+#include <sound/opl4.h>
+#endif
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>");
+MODULE_LICENSE("GPL");
+#ifdef OPTi93X
+MODULE_DESCRIPTION("OPTi93X");
+MODULE_SUPPORTED_DEVICE("{{OPTi,82C931/3}}");
+#else	/* OPTi93X */
+#ifdef CS4231
+MODULE_DESCRIPTION("OPTi92X - CS4231");
+MODULE_SUPPORTED_DEVICE("{{OPTi,82C924 (CS4231)},"
+		"{OPTi,82C925 (CS4231)}}");
+#else	/* CS4231 */
+MODULE_DESCRIPTION("OPTi92X - AD1848");
+MODULE_SUPPORTED_DEVICE("{{OPTi,82C924 (AD1848)},"
+		"{OPTi,82C925 (AD1848)},"
+	        "{OAK,Mozart}}");
+#endif	/* CS4231 */
+#endif	/* OPTi93X */
+
+static int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;		/* ID for this card */
+//static int enable = SNDRV_DEFAULT_ENABLE1;	/* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp = 1;			/* Enable ISA PnP detection */
+#endif
+static long port = SNDRV_DEFAULT_PORT1; 	/* 0x530,0xe80,0xf40,0x604 */
+static long mpu_port = SNDRV_DEFAULT_PORT1;	/* 0x300,0x310,0x320,0x330 */
+static long fm_port = SNDRV_DEFAULT_PORT1;	/* 0x388 */
+static int irq = SNDRV_DEFAULT_IRQ1;		/* 5,7,9,10,11 */
+static int mpu_irq = SNDRV_DEFAULT_IRQ1;	/* 5,7,9,10 */
+static int dma1 = SNDRV_DEFAULT_DMA1;		/* 0,1,3 */
+#if defined(CS4231) || defined(OPTi93X)
+static int dma2 = SNDRV_DEFAULT_DMA1;		/* 0,1,3 */
+#endif	/* CS4231 || OPTi93X */
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for opti9xx based soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for opti9xx based soundcard.");
+//module_param(enable, bool, 0444);
+//MODULE_PARM_DESC(enable, "Enable opti9xx soundcard.");
+#ifdef CONFIG_PNP
+module_param(isapnp, bool, 0444);
+MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard.");
+#endif
+module_param(port, long, 0444);
+MODULE_PARM_DESC(port, "WSS port # for opti9xx driver.");
+module_param(mpu_port, long, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for opti9xx driver.");
+module_param(fm_port, long, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for opti9xx driver.");
+module_param(irq, int, 0444);
+MODULE_PARM_DESC(irq, "WSS irq # for opti9xx driver.");
+module_param(mpu_irq, int, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for opti9xx driver.");
+module_param(dma1, int, 0444);
+MODULE_PARM_DESC(dma1, "1st dma # for opti9xx driver.");
+#if defined(CS4231) || defined(OPTi93X)
+module_param(dma2, int, 0444);
+MODULE_PARM_DESC(dma2, "2nd dma # for opti9xx driver.");
+#endif	/* CS4231 || OPTi93X */
+
+#define OPTi9XX_HW_82C928	1
+#define OPTi9XX_HW_82C929	2
+#define OPTi9XX_HW_82C924	3
+#define OPTi9XX_HW_82C925	4
+#define OPTi9XX_HW_82C930	5
+#define OPTi9XX_HW_82C931	6
+#define OPTi9XX_HW_82C933	7
+#define OPTi9XX_HW_LAST		OPTi9XX_HW_82C933
+
+#define OPTi9XX_MC_REG(n)	n
+
+#ifdef OPTi93X
+
+#define OPTi93X_STATUS			0x02
+#define OPTi93X_PORT(chip, r)		((chip)->port + OPTi93X_##r)
+
+#define OPTi93X_IRQ_PLAYBACK		0x04
+#define OPTi93X_IRQ_CAPTURE		0x08
+
+#endif /* OPTi93X */
+
+struct snd_opti9xx {
+	unsigned short hardware;
+	unsigned char password;
+	char name[7];
+
+	unsigned long mc_base;
+	struct resource *res_mc_base;
+	unsigned long mc_base_size;
+#ifdef OPTi93X
+	unsigned long mc_indir_index;
+	unsigned long mc_indir_size;
+	struct resource *res_mc_indir;
+	struct snd_wss *codec;
+#endif	/* OPTi93X */
+	unsigned long pwd_reg;
+
+	spinlock_t lock;
+
+	long wss_base;
+	int irq;
+};
+
+static int snd_opti9xx_pnp_is_probed;
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_opti9xx_pnpids[] = {
+#ifndef OPTi93X
+	/* OPTi 82C924 */
+	{ .id = "OPT0924",
+	  .devs = { { "OPT0000" }, { "OPT0002" }, { "OPT0005" } },
+	  .driver_data = 0x0924 },
+	/* OPTi 82C925 */
+	{ .id = "OPT0925",
+	  .devs = { { "OPT9250" }, { "OPT0002" }, { "OPT0005" } },
+	  .driver_data = 0x0925 },
+#else
+	/* OPTi 82C931/3 */
+	{ .id = "OPT0931", .devs = { { "OPT9310" }, { "OPT0002" } },
+	  .driver_data = 0x0931 },
+#endif	/* OPTi93X */
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_opti9xx_pnpids);
+
+#endif	/* CONFIG_PNP */
+
+#ifdef OPTi93X
+#define DEV_NAME "opti93x"
+#else
+#define DEV_NAME "opti92x"
+#endif
+
+static char * snd_opti9xx_names[] = {
+	"unknown",
+	"82C928",	"82C929",
+	"82C924",	"82C925",
+	"82C930",	"82C931",	"82C933"
+};
+
+
+static long __devinit snd_legacy_find_free_ioport(long *port_table, long size)
+{
+	while (*port_table != -1) {
+		if (request_region(*port_table, size, "ALSA test")) {
+			release_region(*port_table, size);
+			return *port_table;
+		}
+		port_table++;
+	}
+	return -1;
+}
+
+static int __devinit snd_opti9xx_init(struct snd_opti9xx *chip,
+				      unsigned short hardware)
+{
+	static int opti9xx_mc_size[] = {7, 7, 10, 10, 2, 2, 2};
+
+	chip->hardware = hardware;
+	strcpy(chip->name, snd_opti9xx_names[hardware]);
+
+	spin_lock_init(&chip->lock);
+
+	chip->irq = -1;
+
+#ifndef OPTi93X
+#ifdef CONFIG_PNP
+	if (isapnp && chip->mc_base)
+		/* PnP resource gives the least 10 bits */
+		chip->mc_base |= 0xc00;
+	else
+#endif	/* CONFIG_PNP */
+	{
+		chip->mc_base = 0xf8c;
+		chip->mc_base_size = opti9xx_mc_size[hardware];
+	}
+#else
+		chip->mc_base_size = opti9xx_mc_size[hardware];
+#endif
+
+	switch (hardware) {
+#ifndef OPTi93X
+	case OPTi9XX_HW_82C928:
+	case OPTi9XX_HW_82C929:
+		chip->password = (hardware == OPTi9XX_HW_82C928) ? 0xe2 : 0xe3;
+		chip->pwd_reg = 3;
+		break;
+
+	case OPTi9XX_HW_82C924:
+	case OPTi9XX_HW_82C925:
+		chip->password = 0xe5;
+		chip->pwd_reg = 3;
+		break;
+#else	/* OPTi93X */
+
+	case OPTi9XX_HW_82C930:
+	case OPTi9XX_HW_82C931:
+	case OPTi9XX_HW_82C933:
+		chip->mc_base = (hardware == OPTi9XX_HW_82C930) ? 0xf8f : 0xf8d;
+		if (!chip->mc_indir_index) {
+			chip->mc_indir_index = 0xe0e;
+			chip->mc_indir_size = 2;
+		}
+		chip->password = 0xe4;
+		chip->pwd_reg = 0;
+		break;
+#endif	/* OPTi93X */
+
+	default:
+		snd_printk(KERN_ERR "chip %d not supported\n", hardware);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static unsigned char snd_opti9xx_read(struct snd_opti9xx *chip,
+				      unsigned char reg)
+{
+	unsigned long flags;
+	unsigned char retval = 0xff;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(chip->password, chip->mc_base + chip->pwd_reg);
+
+	switch (chip->hardware) {
+#ifndef OPTi93X
+	case OPTi9XX_HW_82C924:
+	case OPTi9XX_HW_82C925:
+		if (reg > 7) {
+			outb(reg, chip->mc_base + 8);
+			outb(chip->password, chip->mc_base + chip->pwd_reg);
+			retval = inb(chip->mc_base + 9);
+			break;
+		}
+
+	case OPTi9XX_HW_82C928:
+	case OPTi9XX_HW_82C929:
+		retval = inb(chip->mc_base + reg);
+		break;
+#else	/* OPTi93X */
+
+	case OPTi9XX_HW_82C930:
+	case OPTi9XX_HW_82C931:
+	case OPTi9XX_HW_82C933:
+		outb(reg, chip->mc_indir_index);
+		outb(chip->password, chip->mc_base + chip->pwd_reg);
+		retval = inb(chip->mc_indir_index + 1);
+		break;
+#endif	/* OPTi93X */
+
+	default:
+		snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return retval;
+}
+
+static void snd_opti9xx_write(struct snd_opti9xx *chip, unsigned char reg,
+			      unsigned char value)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(chip->password, chip->mc_base + chip->pwd_reg);
+
+	switch (chip->hardware) {
+#ifndef OPTi93X
+	case OPTi9XX_HW_82C924:
+	case OPTi9XX_HW_82C925:
+		if (reg > 7) {
+			outb(reg, chip->mc_base + 8);
+			outb(chip->password, chip->mc_base + chip->pwd_reg);
+			outb(value, chip->mc_base + 9);
+			break;
+		}
+
+	case OPTi9XX_HW_82C928:
+	case OPTi9XX_HW_82C929:
+		outb(value, chip->mc_base + reg);
+		break;
+#else	/* OPTi93X */
+
+	case OPTi9XX_HW_82C930:
+	case OPTi9XX_HW_82C931:
+	case OPTi9XX_HW_82C933:
+		outb(reg, chip->mc_indir_index);
+		outb(chip->password, chip->mc_base + chip->pwd_reg);
+		outb(value, chip->mc_indir_index + 1);
+		break;
+#endif	/* OPTi93X */
+
+	default:
+		snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+#define snd_opti9xx_write_mask(chip, reg, value, mask)	\
+	snd_opti9xx_write(chip, reg,			\
+		(snd_opti9xx_read(chip, reg) & ~(mask)) | ((value) & (mask)))
+
+
+static int __devinit snd_opti9xx_configure(struct snd_opti9xx *chip,
+					   long port,
+					   int irq, int dma1, int dma2,
+					   long mpu_port, int mpu_irq)
+{
+	unsigned char wss_base_bits;
+	unsigned char irq_bits;
+	unsigned char dma_bits;
+	unsigned char mpu_port_bits = 0;
+	unsigned char mpu_irq_bits;
+
+	switch (chip->hardware) {
+#ifndef OPTi93X
+	case OPTi9XX_HW_82C924:
+		/* opti 929 mode (?), OPL3 clock output, audio enable */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0xf0, 0xfc);
+		/* enable wave audio */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02);
+
+	case OPTi9XX_HW_82C925:
+		/* enable WSS mode */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80);
+		/* OPL3 FM synthesis */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20);
+		/* disable Sound Blaster IRQ and DMA */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff);
+#ifdef CS4231
+		/* cs4231/4248 fix enabled */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02);
+#else
+		/* cs4231/4248 fix disabled */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02);
+#endif	/* CS4231 */
+		break;
+
+	case OPTi9XX_HW_82C928:
+	case OPTi9XX_HW_82C929:
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80);
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20);
+		/*
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xa2, 0xae);
+		*/
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c);
+#ifdef CS4231
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02);
+#else
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02);
+#endif	/* CS4231 */
+		break;
+
+#else	/* OPTi93X */
+	case OPTi9XX_HW_82C931:
+	case OPTi9XX_HW_82C933:
+		/*
+		 * The BTC 1817DW has QS1000 wavetable which is connected
+		 * to the serial digital input of the OPTI931.
+		 */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(21), 0x82, 0xff);
+		/* 
+		 * This bit sets OPTI931 to automaticaly select FM
+		 * or digital input signal.
+		 */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(26), 0x01, 0x01);
+	case OPTi9XX_HW_82C930: /* FALL THROUGH */
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x03);
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0x00, 0xff);
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x10 |
+			(chip->hardware == OPTi9XX_HW_82C930 ? 0x00 : 0x04),
+			0x34);
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x20, 0xbf);
+		break;
+#endif	/* OPTi93X */
+
+	default:
+		snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware);
+		return -EINVAL;
+	}
+
+	/* PnP resource says it decodes only 10 bits of address */
+	switch (port & 0x3ff) {
+	case 0x130:
+		chip->wss_base = 0x530;
+		wss_base_bits = 0x00;
+		break;
+	case 0x204:
+		chip->wss_base = 0x604;
+		wss_base_bits = 0x03;
+		break;
+	case 0x280:
+		chip->wss_base = 0xe80;
+		wss_base_bits = 0x01;
+		break;
+	case 0x340:
+		chip->wss_base = 0xf40;
+		wss_base_bits = 0x02;
+		break;
+	default:
+		snd_printk(KERN_WARNING "WSS port 0x%lx not valid\n", port);
+		goto __skip_base;
+	}
+	snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30);
+
+__skip_base:
+	switch (irq) {
+//#ifdef OPTi93X
+	case 5:
+		irq_bits = 0x05;
+		break;
+//#endif	/* OPTi93X */
+	case 7:
+		irq_bits = 0x01;
+		break;
+	case 9:
+		irq_bits = 0x02;
+		break;
+	case 10:
+		irq_bits = 0x03;
+		break;
+	case 11:
+		irq_bits = 0x04;
+		break;
+	default:
+		snd_printk(KERN_WARNING "WSS irq # %d not valid\n", irq);
+		goto __skip_resources;
+	}
+
+	switch (dma1) {
+	case 0:
+		dma_bits = 0x01;
+		break;
+	case 1:
+		dma_bits = 0x02;
+		break;
+	case 3:
+		dma_bits = 0x03;
+		break;
+	default:
+		snd_printk(KERN_WARNING "WSS dma1 # %d not valid\n", dma1);
+		goto __skip_resources;
+	}
+
+#if defined(CS4231) || defined(OPTi93X)
+	if (dma1 == dma2) {
+		snd_printk(KERN_ERR "don't want to share dmas\n");
+		return -EBUSY;
+	}
+
+	switch (dma2) {
+	case 0:
+	case 1:
+		break;
+	default:
+		snd_printk(KERN_WARNING "WSS dma2 # %d not valid\n", dma2);
+		goto __skip_resources;
+	}
+	dma_bits |= 0x04;
+#endif	/* CS4231 || OPTi93X */
+
+#ifndef OPTi93X
+	 outb(irq_bits << 3 | dma_bits, chip->wss_base);
+#else /* OPTi93X */
+	snd_opti9xx_write(chip, OPTi9XX_MC_REG(3), (irq_bits << 3 | dma_bits));
+#endif /* OPTi93X */
+
+__skip_resources:
+	if (chip->hardware > OPTi9XX_HW_82C928) {
+		switch (mpu_port) {
+		case 0:
+		case -1:
+			break;
+		case 0x300:
+			mpu_port_bits = 0x03;
+			break;
+		case 0x310:
+			mpu_port_bits = 0x02;
+			break;
+		case 0x320:
+			mpu_port_bits = 0x01;
+			break;
+		case 0x330:
+			mpu_port_bits = 0x00;
+			break;
+		default:
+			snd_printk(KERN_WARNING
+				   "MPU-401 port 0x%lx not valid\n", mpu_port);
+			goto __skip_mpu;
+		}
+
+		switch (mpu_irq) {
+		case 5:
+			mpu_irq_bits = 0x02;
+			break;
+		case 7:
+			mpu_irq_bits = 0x03;
+			break;
+		case 9:
+			mpu_irq_bits = 0x00;
+			break;
+		case 10:
+			mpu_irq_bits = 0x01;
+			break;
+		default:
+			snd_printk(KERN_WARNING "MPU-401 irq # %d not valid\n",
+				mpu_irq);
+			goto __skip_mpu;
+		}
+
+		snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6),
+			(mpu_port <= 0) ? 0x00 :
+				0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3,
+			0xf8);
+	}
+__skip_mpu:
+
+	return 0;
+}
+
+#ifdef OPTi93X
+
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_3db_step, -9300, 300, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit_12db_max, -3300, 300, 0);
+
+static struct snd_kcontrol_new snd_opti93x_controls[] = {
+WSS_DOUBLE("Master Playback Switch", 0,
+		OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Master Playback Volume", 0,
+		OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 1, 1, 31, 1,
+		db_scale_5bit_3db_step),
+WSS_DOUBLE_TLV("PCM Playback Volume", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 31, 1,
+		db_scale_5bit),
+WSS_DOUBLE_TLV("FM Playback Volume", 0,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 1, 1, 15, 1,
+		db_scale_4bit_12db_max),
+WSS_DOUBLE("Line Playback Switch", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Line Playback Volume", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 15, 1,
+		db_scale_4bit_12db_max),
+WSS_DOUBLE("Mic Playback Switch", 0,
+		OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Mic Playback Volume", 0,
+		OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 1, 1, 15, 1,
+		db_scale_4bit_12db_max),
+WSS_DOUBLE_TLV("CD Playback Volume", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 1, 1, 15, 1,
+		db_scale_4bit_12db_max),
+WSS_DOUBLE("Aux Playback Switch", 0,
+		OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Aux Playback Volume", 0,
+		OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 1, 1, 15, 1,
+		db_scale_4bit_12db_max),
+};
+
+static int __devinit snd_opti93x_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card;
+	unsigned int idx;
+	struct snd_ctl_elem_id id1, id2;
+	int err;
+
+	if (snd_BUG_ON(!chip || !chip->pcm))
+		return -EINVAL;
+
+	card = chip->card;
+
+	strcpy(card->mixername, chip->pcm->name);
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	/* reassign AUX0 switch to CD */
+	strcpy(id1.name, "Aux Playback Switch");
+	strcpy(id2.name, "CD Playback Switch");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0) {
+		snd_printk(KERN_ERR "Cannot rename opti93x control\n");
+		return err;
+	}
+	/* reassign AUX1 switch to FM */
+	strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+	strcpy(id2.name, "FM Playback Switch");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0) {
+		snd_printk(KERN_ERR "Cannot rename opti93x control\n");
+		return err;
+	}
+	/* remove AUX1 volume */
+	strcpy(id1.name, "Aux Playback Volume"); id1.index = 1;
+	snd_ctl_remove_id(card, &id1);
+
+	/* Replace WSS volume controls with OPTi93x volume controls */
+	id1.index = 0;
+	for (idx = 0; idx < ARRAY_SIZE(snd_opti93x_controls); idx++) {
+		strcpy(id1.name, snd_opti93x_controls[idx].name);
+		snd_ctl_remove_id(card, &id1);
+
+		err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_opti93x_controls[idx], chip));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static irqreturn_t snd_opti93x_interrupt(int irq, void *dev_id)
+{
+	struct snd_opti9xx *chip = dev_id;
+	struct snd_wss *codec = chip->codec;
+	unsigned char status;
+
+	if (!codec)
+		return IRQ_HANDLED;
+
+	status = snd_opti9xx_read(chip, OPTi9XX_MC_REG(11));
+	if ((status & OPTi93X_IRQ_PLAYBACK) && codec->playback_substream)
+		snd_pcm_period_elapsed(codec->playback_substream);
+	if ((status & OPTi93X_IRQ_CAPTURE) && codec->capture_substream) {
+		snd_wss_overrange(codec);
+		snd_pcm_period_elapsed(codec->capture_substream);
+	}
+	outb(0x00, OPTi93X_PORT(codec, STATUS));
+	return IRQ_HANDLED;
+}
+
+#endif /* OPTi93X */
+
+static int __devinit snd_opti9xx_read_check(struct snd_opti9xx *chip)
+{
+	unsigned char value;
+#ifdef OPTi93X
+	unsigned long flags;
+#endif
+
+	chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size,
+					   "OPTi9xx MC");
+	if (chip->res_mc_base == NULL)
+		return -EBUSY;
+#ifndef OPTi93X
+	value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(1));
+	if (value != 0xff && value != inb(chip->mc_base + OPTi9XX_MC_REG(1)))
+		if (value == snd_opti9xx_read(chip, OPTi9XX_MC_REG(1)))
+			return 0;
+#else	/* OPTi93X */
+	chip->res_mc_indir = request_region(chip->mc_indir_index,
+					    chip->mc_indir_size,
+					    "OPTi93x MC");
+	if (chip->res_mc_indir == NULL)
+		return -EBUSY;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	outb(chip->password, chip->mc_base + chip->pwd_reg);
+	outb(((chip->mc_indir_index & 0x1f0) >> 4), chip->mc_base);
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(7));
+	snd_opti9xx_write(chip, OPTi9XX_MC_REG(7), 0xff - value);
+	if (snd_opti9xx_read(chip, OPTi9XX_MC_REG(7)) == 0xff - value)
+		return 0;
+
+	release_and_free_resource(chip->res_mc_indir);
+	chip->res_mc_indir = NULL;
+#endif	/* OPTi93X */
+	release_and_free_resource(chip->res_mc_base);
+	chip->res_mc_base = NULL;
+
+	return -ENODEV;
+}
+
+static int __devinit snd_card_opti9xx_detect(struct snd_card *card,
+					     struct snd_opti9xx *chip)
+{
+	int i, err;
+
+#ifndef OPTi93X
+	for (i = OPTi9XX_HW_82C928; i < OPTi9XX_HW_82C930; i++) {
+#else
+	for (i = OPTi9XX_HW_82C931; i >= OPTi9XX_HW_82C930; i--) {
+#endif
+		err = snd_opti9xx_init(chip, i);
+		if (err < 0)
+			return err;
+
+		err = snd_opti9xx_read_check(chip);
+		if (err == 0)
+			return 1;
+#ifdef OPTi93X
+		chip->mc_indir_index = 0;
+#endif
+	}
+	return -ENODEV;
+}
+
+#ifdef CONFIG_PNP
+static int __devinit snd_card_opti9xx_pnp(struct snd_opti9xx *chip,
+					  struct pnp_card_link *card,
+					  const struct pnp_card_device_id *pid)
+{
+	struct pnp_dev *pdev;
+	int err;
+	struct pnp_dev *devmpu;
+#ifndef OPTi93X
+	struct pnp_dev *devmc;
+#endif
+
+	pdev = pnp_request_card_device(card, pid->devs[0].id, NULL);
+	if (pdev == NULL)
+		return -EBUSY;
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err);
+		return err;
+	}
+
+#ifdef OPTi93X
+	port = pnp_port_start(pdev, 0) - 4;
+	fm_port = pnp_port_start(pdev, 1) + 8;
+	chip->mc_indir_index = pnp_port_start(pdev, 3) + 2;
+	chip->mc_indir_size = pnp_port_len(pdev, 3) - 2;
+#else
+	devmc = pnp_request_card_device(card, pid->devs[2].id, NULL);
+	if (devmc == NULL)
+		return -EBUSY;
+
+	err = pnp_activate_dev(devmc);
+	if (err < 0) {
+		snd_printk(KERN_ERR "MC pnp configure failure: %d\n", err);
+		return err;
+	}
+
+	port = pnp_port_start(pdev, 1);
+	fm_port = pnp_port_start(pdev, 2) + 8;
+	/*
+	 * The MC(0) is never accessed and card does not
+	 * include it in the PnP resource range. OPTI93x include it.
+	 */
+	chip->mc_base = pnp_port_start(devmc, 0) - 1;
+	chip->mc_base_size = pnp_port_len(devmc, 0) + 1;
+#endif	/* OPTi93X */
+	irq = pnp_irq(pdev, 0);
+	dma1 = pnp_dma(pdev, 0);
+#if defined(CS4231) || defined(OPTi93X)
+	dma2 = pnp_dma(pdev, 1);
+#endif	/* CS4231 || OPTi93X */
+
+	devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL);
+
+	if (devmpu && mpu_port > 0) {
+		err = pnp_activate_dev(devmpu);
+		if (err < 0) {
+			snd_printk(KERN_ERR "MPU401 pnp configure failure\n");
+			mpu_port = -1;
+		} else {
+			mpu_port = pnp_port_start(devmpu, 0);
+			mpu_irq = pnp_irq(devmpu, 0);
+		}
+	}
+	return pid->driver_data;
+}
+#endif	/* CONFIG_PNP */
+
+static void snd_card_opti9xx_free(struct snd_card *card)
+{
+	struct snd_opti9xx *chip = card->private_data;
+
+	if (chip) {
+#ifdef OPTi93X
+		if (chip->irq > 0) {
+			disable_irq(chip->irq);
+			free_irq(chip->irq, chip);
+		}
+		release_and_free_resource(chip->res_mc_indir);
+#endif
+		release_and_free_resource(chip->res_mc_base);
+	}
+}
+
+static int __devinit snd_opti9xx_probe(struct snd_card *card)
+{
+	static long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1};
+	int error;
+	int xdma2;
+	struct snd_opti9xx *chip = card->private_data;
+	struct snd_wss *codec;
+#ifdef CS4231
+	struct snd_timer *timer;
+#endif
+	struct snd_pcm *pcm;
+	struct snd_rawmidi *rmidi;
+	struct snd_hwdep *synth;
+
+#if defined(CS4231) || defined(OPTi93X)
+	xdma2 = dma2;
+#else
+	xdma2 = -1;
+#endif
+
+	if (port == SNDRV_AUTO_PORT) {
+		port = snd_legacy_find_free_ioport(possible_ports, 4);
+		if (port < 0) {
+			snd_printk(KERN_ERR "unable to find a free WSS port\n");
+			return -EBUSY;
+		}
+	}
+	error = snd_opti9xx_configure(chip, port, irq, dma1, xdma2,
+				      mpu_port, mpu_irq);
+	if (error)
+		return error;
+
+	error = snd_wss_create(card, chip->wss_base + 4, -1, irq, dma1, xdma2,
+#ifdef OPTi93X
+			       WSS_HW_OPTI93X, WSS_HWSHARE_IRQ,
+#else
+			       WSS_HW_DETECT, 0,
+#endif
+			       &codec);
+	if (error < 0)
+		return error;
+#ifdef OPTi93X
+	chip->codec = codec;
+#endif
+	error = snd_wss_pcm(codec, 0, &pcm);
+	if (error < 0)
+		return error;
+	error = snd_wss_mixer(codec);
+	if (error < 0)
+		return error;
+#ifdef OPTi93X
+	error = snd_opti93x_mixer(codec);
+	if (error < 0)
+		return error;
+#endif
+#ifdef CS4231
+	error = snd_wss_timer(codec, 0, &timer);
+	if (error < 0)
+		return error;
+#endif
+#ifdef OPTi93X
+	error = request_irq(irq, snd_opti93x_interrupt,
+			    IRQF_DISABLED, DEV_NAME" - WSS", chip);
+	if (error < 0) {
+		snd_printk(KERN_ERR "opti9xx: can't grab IRQ %d\n", irq);
+		return error;
+	}
+#endif
+	chip->irq = irq;
+	strcpy(card->driver, chip->name);
+	sprintf(card->shortname, "OPTi %s", card->driver);
+#if defined(CS4231) || defined(OPTi93X)
+	sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d&%d",
+		card->shortname, pcm->name,
+		chip->wss_base + 4, irq, dma1, xdma2);
+#else
+	sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d",
+		card->shortname, pcm->name, chip->wss_base + 4, irq, dma1);
+#endif	/* CS4231 || OPTi93X */
+
+	if (mpu_port <= 0 || mpu_port == SNDRV_AUTO_PORT)
+		rmidi = NULL;
+	else {
+		error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+				mpu_port, 0, mpu_irq, IRQF_DISABLED, &rmidi);
+		if (error)
+			snd_printk(KERN_WARNING "no MPU-401 device at 0x%lx?\n",
+				   mpu_port);
+	}
+
+	if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) {
+		struct snd_opl3 *opl3 = NULL;
+#ifndef OPTi93X
+		if (chip->hardware == OPTi9XX_HW_82C928 ||
+		    chip->hardware == OPTi9XX_HW_82C929 ||
+		    chip->hardware == OPTi9XX_HW_82C924) {
+			struct snd_opl4 *opl4;
+			/* assume we have an OPL4 */
+			snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2),
+					       0x20, 0x20);
+			if (snd_opl4_create(card, fm_port, fm_port - 8,
+					    2, &opl3, &opl4) < 0) {
+				/* no luck, use OPL3 instead */
+				snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2),
+						       0x00, 0x20);
+			}
+		}
+#endif	/* !OPTi93X */
+		if (!opl3 && snd_opl3_create(card, fm_port, fm_port + 2,
+					     OPL3_HW_AUTO, 0, &opl3) < 0) {
+			snd_printk(KERN_WARNING "no OPL device at 0x%lx-0x%lx\n",
+				   fm_port, fm_port + 4 - 1);
+		}
+		if (opl3) {
+			error = snd_opl3_hwdep_new(opl3, 0, 1, &synth);
+			if (error < 0)
+				return error;
+		}
+	}
+
+	return snd_card_register(card);
+}
+
+static int snd_opti9xx_card_new(struct snd_card **cardp)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_card_create(index, id, THIS_MODULE,
+			      sizeof(struct snd_opti9xx), &card);
+	if (err < 0)
+		return err;
+	card->private_free = snd_card_opti9xx_free;
+	*cardp = card;
+	return 0;
+}
+
+static int __devinit snd_opti9xx_isa_match(struct device *devptr,
+					   unsigned int dev)
+{
+#ifdef CONFIG_PNP
+	if (snd_opti9xx_pnp_is_probed)
+		return 0;
+	if (isapnp)
+		return 0;
+#endif
+	return 1;
+}
+
+static int __devinit snd_opti9xx_isa_probe(struct device *devptr,
+					   unsigned int dev)
+{
+	struct snd_card *card;
+	int error;
+	static long possible_mpu_ports[] = {0x300, 0x310, 0x320, 0x330, -1};
+#ifdef OPTi93X
+	static int possible_irqs[] = {5, 9, 10, 11, 7, -1};
+#else
+	static int possible_irqs[] = {9, 10, 11, 7, -1};
+#endif	/* OPTi93X */
+	static int possible_mpu_irqs[] = {5, 9, 10, 7, -1};
+	static int possible_dma1s[] = {3, 1, 0, -1};
+#if defined(CS4231) || defined(OPTi93X)
+	static int possible_dma2s[][2] = {{1,-1}, {0,-1}, {-1,-1}, {0,-1}};
+#endif	/* CS4231 || OPTi93X */
+
+	if (mpu_port == SNDRV_AUTO_PORT) {
+		if ((mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2)) < 0) {
+			snd_printk(KERN_ERR "unable to find a free MPU401 port\n");
+			return -EBUSY;
+		}
+	}
+	if (irq == SNDRV_AUTO_IRQ) {
+		if ((irq = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_printk(KERN_ERR "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (mpu_irq == SNDRV_AUTO_IRQ) {
+		if ((mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs)) < 0) {
+			snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma1 == SNDRV_AUTO_DMA) {
+		if ((dma1 = snd_legacy_find_free_dma(possible_dma1s)) < 0) {
+			snd_printk(KERN_ERR "unable to find a free DMA1\n");
+			return -EBUSY;
+		}
+	}
+#if defined(CS4231) || defined(OPTi93X)
+	if (dma2 == SNDRV_AUTO_DMA) {
+		if ((dma2 = snd_legacy_find_free_dma(possible_dma2s[dma1 % 4])) < 0) {
+			snd_printk(KERN_ERR "unable to find a free DMA2\n");
+			return -EBUSY;
+		}
+	}
+#endif
+
+	error = snd_opti9xx_card_new(&card);
+	if (error < 0)
+		return error;
+
+	if ((error = snd_card_opti9xx_detect(card, card->private_data)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	snd_card_set_dev(card, devptr);
+	if ((error = snd_opti9xx_probe(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	dev_set_drvdata(devptr, card);
+	return 0;
+}
+
+static int __devexit snd_opti9xx_isa_remove(struct device *devptr,
+					    unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	dev_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+static struct isa_driver snd_opti9xx_driver = {
+	.match		= snd_opti9xx_isa_match,
+	.probe		= snd_opti9xx_isa_probe,
+	.remove		= __devexit_p(snd_opti9xx_isa_remove),
+	/* FIXME: suspend/resume */
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+#ifdef CONFIG_PNP
+static int __devinit snd_opti9xx_pnp_probe(struct pnp_card_link *pcard,
+					   const struct pnp_card_device_id *pid)
+{
+	struct snd_card *card;
+	int error, hw;
+	struct snd_opti9xx *chip;
+
+	if (snd_opti9xx_pnp_is_probed)
+		return -EBUSY;
+	if (! isapnp)
+		return -ENODEV;
+	error = snd_opti9xx_card_new(&card);
+	if (error < 0)
+		return error;
+	chip = card->private_data;
+
+	hw = snd_card_opti9xx_pnp(chip, pcard, pid);
+	switch (hw) {
+	case 0x0924:
+		hw = OPTi9XX_HW_82C924;
+		break;
+	case 0x0925:
+		hw = OPTi9XX_HW_82C925;
+		break;
+	case 0x0931:
+		hw = OPTi9XX_HW_82C931;
+		break;
+	default:
+		snd_card_free(card);
+		return -ENODEV;
+	}
+
+	if ((error = snd_opti9xx_init(chip, hw))) {
+		snd_card_free(card);
+		return error;
+	}
+	error = snd_opti9xx_read_check(chip);
+	if (error) {
+		snd_printk(KERN_ERR "OPTI chip not found\n");
+		snd_card_free(card);
+		return error;
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+	if ((error = snd_opti9xx_probe(card)) < 0) {
+		snd_card_free(card);
+		return error;
+	}
+	pnp_set_card_drvdata(pcard, card);
+	snd_opti9xx_pnp_is_probed = 1;
+	return 0;
+}
+
+static void __devexit snd_opti9xx_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+	snd_opti9xx_pnp_is_probed = 0;
+}
+
+static struct pnp_card_driver opti9xx_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= "opti9xx",
+	.id_table	= snd_opti9xx_pnpids,
+	.probe		= snd_opti9xx_pnp_probe,
+	.remove		= __devexit_p(snd_opti9xx_pnp_remove),
+};
+#endif
+
+#ifdef OPTi93X
+#define CHIP_NAME	"82C93x"
+#else
+#define CHIP_NAME	"82C92x"
+#endif
+
+static int __init alsa_card_opti9xx_init(void)
+{
+#ifdef CONFIG_PNP
+	pnp_register_card_driver(&opti9xx_pnpc_driver);
+	if (snd_opti9xx_pnp_is_probed)
+		return 0;
+	pnp_unregister_card_driver(&opti9xx_pnpc_driver);
+#endif
+	return isa_register_driver(&snd_opti9xx_driver, 1);
+}
+
+static void __exit alsa_card_opti9xx_exit(void)
+{
+	if (!snd_opti9xx_pnp_is_probed) {
+		isa_unregister_driver(&snd_opti9xx_driver);
+		return;
+	}
+#ifdef CONFIG_PNP
+	pnp_unregister_card_driver(&opti9xx_pnpc_driver);
+#endif
+}
+
+module_init(alsa_card_opti9xx_init)
+module_exit(alsa_card_opti9xx_exit)
diff --git a/linux/sound/isa/opti9xx/opti92x-cs4231.c b/linux/sound/isa/opti9xx/opti92x-cs4231.c
new file mode 100644
index 0000000..b17ab19
--- /dev/null
+++ b/linux/sound/isa/opti9xx/opti92x-cs4231.c
@@ -0,0 +1,2 @@
+#define CS4231
+#include "opti92x-ad1848.c"
diff --git a/linux/sound/isa/opti9xx/opti93x.c b/linux/sound/isa/opti9xx/opti93x.c
new file mode 100644
index 0000000..bad9da5
--- /dev/null
+++ b/linux/sound/isa/opti9xx/opti93x.c
@@ -0,0 +1,3 @@
+#define OPTi93X
+#include "opti92x-ad1848.c"
+
diff --git a/linux/sound/isa/sb/Makefile b/linux/sound/isa/sb/Makefile
new file mode 100644
index 0000000..08b9fb9
--- /dev/null
+++ b/linux/sound/isa/sb/Makefile
@@ -0,0 +1,28 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-sb-common-objs := sb_common.o sb_mixer.o
+snd-sb8-dsp-objs := sb8_main.o sb8_midi.o
+snd-sb16-dsp-objs := sb16_main.o
+snd-sb16-csp-objs := sb16_csp.o
+snd-sb8-objs := sb8.o
+snd-sb16-objs := sb16.o
+snd-sbawe-objs := sbawe.o emu8000.o
+snd-emu8000-synth-objs := emu8000_synth.o emu8000_callback.o emu8000_patch.o emu8000_pcm.o
+snd-jazz16-objs := jazz16.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_SB_COMMON) += snd-sb-common.o
+obj-$(CONFIG_SND_SB16_DSP) += snd-sb16-dsp.o
+obj-$(CONFIG_SND_SB8_DSP) += snd-sb8-dsp.o
+obj-$(CONFIG_SND_SB8) += snd-sb8.o
+obj-$(CONFIG_SND_SB16) += snd-sb16.o
+obj-$(CONFIG_SND_SBAWE) += snd-sbawe.o
+obj-$(CONFIG_SND_JAZZ16) += snd-jazz16.o
+ifeq ($(CONFIG_SND_SB16_CSP),y)
+  obj-$(CONFIG_SND_SB16) += snd-sb16-csp.o
+  obj-$(CONFIG_SND_SBAWE) += snd-sb16-csp.o
+endif
+obj-$(CONFIG_SND_SBAWE_SEQ) += snd-emu8000-synth.o
diff --git a/linux/sound/isa/sb/emu8000.c b/linux/sound/isa/sb/emu8000.c
new file mode 100644
index 0000000..0c40951
--- /dev/null
+++ b/linux/sound/isa/sb/emu8000.c
@@ -0,0 +1,1162 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *     and (c) 1999 Steve Ratcliffe <steve@parabola.demon.co.uk>
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Routines for control of EMU8000 chip
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/emu8000.h>
+#include <sound/emu8000_reg.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
+/*
+ * emu8000 register controls
+ */
+
+/*
+ * The following routines read and write registers on the emu8000.  They
+ * should always be called via the EMU8000*READ/WRITE macros and never
+ * directly.  The macros handle the port number and command word.
+ */
+/* Write a word */
+void snd_emu8000_poke(struct snd_emu8000 *emu, unsigned int port, unsigned int reg, unsigned int val)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	outw((unsigned short)val, port); /* Send data */
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+}
+
+/* Read a word */
+unsigned short snd_emu8000_peek(struct snd_emu8000 *emu, unsigned int port, unsigned int reg)
+{
+	unsigned short res;
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	res = inw(port);	/* Read data */
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return res;
+}
+
+/* Write a double word */
+void snd_emu8000_poke_dw(struct snd_emu8000 *emu, unsigned int port, unsigned int reg, unsigned int val)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	outw((unsigned short)val, port); /* Send low word of data */
+	outw((unsigned short)(val>>16), port+2); /* Send high word of data */
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+}
+
+/* Read a double word */
+unsigned int snd_emu8000_peek_dw(struct snd_emu8000 *emu, unsigned int port, unsigned int reg)
+{
+	unsigned short low;
+	unsigned int res;
+	unsigned long flags;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if (reg != emu->last_reg) {
+		outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */
+		emu->last_reg = reg;
+	}
+	low = inw(port);	/* Read low word of data */
+	res = low + (inw(port+2) << 16);
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return res;
+}
+
+/*
+ * Set up / close a channel to be used for DMA.
+ */
+/*exported*/ void
+snd_emu8000_dma_chan(struct snd_emu8000 *emu, int ch, int mode)
+{
+	unsigned right_bit = (mode & EMU8000_RAM_RIGHT) ? 0x01000000 : 0;
+	mode &= EMU8000_RAM_MODE_MASK;
+	if (mode == EMU8000_RAM_CLOSE) {
+		EMU8000_CCCA_WRITE(emu, ch, 0);
+		EMU8000_DCYSUSV_WRITE(emu, ch, 0x807F);
+		return;
+	}
+	EMU8000_DCYSUSV_WRITE(emu, ch, 0x80);
+	EMU8000_VTFT_WRITE(emu, ch, 0);
+	EMU8000_CVCF_WRITE(emu, ch, 0);
+	EMU8000_PTRX_WRITE(emu, ch, 0x40000000);
+	EMU8000_CPF_WRITE(emu, ch, 0x40000000);
+	EMU8000_PSST_WRITE(emu, ch, 0);
+	EMU8000_CSL_WRITE(emu, ch, 0);
+	if (mode == EMU8000_RAM_WRITE) /* DMA write */
+		EMU8000_CCCA_WRITE(emu, ch, 0x06000000 | right_bit);
+	else	   /* DMA read */
+		EMU8000_CCCA_WRITE(emu, ch, 0x04000000 | right_bit);
+}
+
+/*
+ */
+static void __devinit
+snd_emu8000_read_wait(struct snd_emu8000 *emu)
+{
+	while ((EMU8000_SMALR_READ(emu) & 0x80000000) != 0) {
+		schedule_timeout_interruptible(1);
+		if (signal_pending(current))
+			break;
+	}
+}
+
+/*
+ */
+static void __devinit
+snd_emu8000_write_wait(struct snd_emu8000 *emu)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		schedule_timeout_interruptible(1);
+		if (signal_pending(current))
+			break;
+	}
+}
+
+/*
+ * detect a card at the given port
+ */
+static int __devinit
+snd_emu8000_detect(struct snd_emu8000 *emu)
+{
+	/* Initialise */
+	EMU8000_HWCF1_WRITE(emu, 0x0059);
+	EMU8000_HWCF2_WRITE(emu, 0x0020);
+	EMU8000_HWCF3_WRITE(emu, 0x0000);
+	/* Check for a recognisable emu8000 */
+	/*
+	if ((EMU8000_U1_READ(emu) & 0x000f) != 0x000c)
+		return -ENODEV;
+		*/
+	if ((EMU8000_HWCF1_READ(emu) & 0x007e) != 0x0058)
+		return -ENODEV;
+	if ((EMU8000_HWCF2_READ(emu) & 0x0003) != 0x0003)
+		return -ENODEV;
+
+	snd_printdd("EMU8000 [0x%lx]: Synth chip found\n",
+                    emu->port1);
+	return 0;
+}
+
+
+/*
+ * intiailize audio channels
+ */
+static void __devinit
+init_audio(struct snd_emu8000 *emu)
+{
+	int ch;
+
+	/* turn off envelope engines */
+	for (ch = 0; ch < EMU8000_CHANNELS; ch++)
+		EMU8000_DCYSUSV_WRITE(emu, ch, 0x80);
+  
+	/* reset all other parameters to zero */
+	for (ch = 0; ch < EMU8000_CHANNELS; ch++) {
+		EMU8000_ENVVOL_WRITE(emu, ch, 0);
+		EMU8000_ENVVAL_WRITE(emu, ch, 0);
+		EMU8000_DCYSUS_WRITE(emu, ch, 0);
+		EMU8000_ATKHLDV_WRITE(emu, ch, 0);
+		EMU8000_LFO1VAL_WRITE(emu, ch, 0);
+		EMU8000_ATKHLD_WRITE(emu, ch, 0);
+		EMU8000_LFO2VAL_WRITE(emu, ch, 0);
+		EMU8000_IP_WRITE(emu, ch, 0);
+		EMU8000_IFATN_WRITE(emu, ch, 0);
+		EMU8000_PEFE_WRITE(emu, ch, 0);
+		EMU8000_FMMOD_WRITE(emu, ch, 0);
+		EMU8000_TREMFRQ_WRITE(emu, ch, 0);
+		EMU8000_FM2FRQ2_WRITE(emu, ch, 0);
+		EMU8000_PTRX_WRITE(emu, ch, 0);
+		EMU8000_VTFT_WRITE(emu, ch, 0);
+		EMU8000_PSST_WRITE(emu, ch, 0);
+		EMU8000_CSL_WRITE(emu, ch, 0);
+		EMU8000_CCCA_WRITE(emu, ch, 0);
+	}
+
+	for (ch = 0; ch < EMU8000_CHANNELS; ch++) {
+		EMU8000_CPF_WRITE(emu, ch, 0);
+		EMU8000_CVCF_WRITE(emu, ch, 0);
+	}
+}
+
+
+/*
+ * initialize DMA address
+ */
+static void __devinit
+init_dma(struct snd_emu8000 *emu)
+{
+	EMU8000_SMALR_WRITE(emu, 0);
+	EMU8000_SMARR_WRITE(emu, 0);
+	EMU8000_SMALW_WRITE(emu, 0);
+	EMU8000_SMARW_WRITE(emu, 0);
+}
+
+/*
+ * initialization arrays; from ADIP
+ */
+static unsigned short init1[128] /*__devinitdata*/ = {
+	0x03ff, 0x0030,  0x07ff, 0x0130, 0x0bff, 0x0230,  0x0fff, 0x0330,
+	0x13ff, 0x0430,  0x17ff, 0x0530, 0x1bff, 0x0630,  0x1fff, 0x0730,
+	0x23ff, 0x0830,  0x27ff, 0x0930, 0x2bff, 0x0a30,  0x2fff, 0x0b30,
+	0x33ff, 0x0c30,  0x37ff, 0x0d30, 0x3bff, 0x0e30,  0x3fff, 0x0f30,
+
+	0x43ff, 0x0030,  0x47ff, 0x0130, 0x4bff, 0x0230,  0x4fff, 0x0330,
+	0x53ff, 0x0430,  0x57ff, 0x0530, 0x5bff, 0x0630,  0x5fff, 0x0730,
+	0x63ff, 0x0830,  0x67ff, 0x0930, 0x6bff, 0x0a30,  0x6fff, 0x0b30,
+	0x73ff, 0x0c30,  0x77ff, 0x0d30, 0x7bff, 0x0e30,  0x7fff, 0x0f30,
+
+	0x83ff, 0x0030,  0x87ff, 0x0130, 0x8bff, 0x0230,  0x8fff, 0x0330,
+	0x93ff, 0x0430,  0x97ff, 0x0530, 0x9bff, 0x0630,  0x9fff, 0x0730,
+	0xa3ff, 0x0830,  0xa7ff, 0x0930, 0xabff, 0x0a30,  0xafff, 0x0b30,
+	0xb3ff, 0x0c30,  0xb7ff, 0x0d30, 0xbbff, 0x0e30,  0xbfff, 0x0f30,
+
+	0xc3ff, 0x0030,  0xc7ff, 0x0130, 0xcbff, 0x0230,  0xcfff, 0x0330,
+	0xd3ff, 0x0430,  0xd7ff, 0x0530, 0xdbff, 0x0630,  0xdfff, 0x0730,
+	0xe3ff, 0x0830,  0xe7ff, 0x0930, 0xebff, 0x0a30,  0xefff, 0x0b30,
+	0xf3ff, 0x0c30,  0xf7ff, 0x0d30, 0xfbff, 0x0e30,  0xffff, 0x0f30,
+};
+
+static unsigned short init2[128] /*__devinitdata*/ = {
+	0x03ff, 0x8030, 0x07ff, 0x8130, 0x0bff, 0x8230, 0x0fff, 0x8330,
+	0x13ff, 0x8430, 0x17ff, 0x8530, 0x1bff, 0x8630, 0x1fff, 0x8730,
+	0x23ff, 0x8830, 0x27ff, 0x8930, 0x2bff, 0x8a30, 0x2fff, 0x8b30,
+	0x33ff, 0x8c30, 0x37ff, 0x8d30, 0x3bff, 0x8e30, 0x3fff, 0x8f30,
+
+	0x43ff, 0x8030, 0x47ff, 0x8130, 0x4bff, 0x8230, 0x4fff, 0x8330,
+	0x53ff, 0x8430, 0x57ff, 0x8530, 0x5bff, 0x8630, 0x5fff, 0x8730,
+	0x63ff, 0x8830, 0x67ff, 0x8930, 0x6bff, 0x8a30, 0x6fff, 0x8b30,
+	0x73ff, 0x8c30, 0x77ff, 0x8d30, 0x7bff, 0x8e30, 0x7fff, 0x8f30,
+
+	0x83ff, 0x8030, 0x87ff, 0x8130, 0x8bff, 0x8230, 0x8fff, 0x8330,
+	0x93ff, 0x8430, 0x97ff, 0x8530, 0x9bff, 0x8630, 0x9fff, 0x8730,
+	0xa3ff, 0x8830, 0xa7ff, 0x8930, 0xabff, 0x8a30, 0xafff, 0x8b30,
+	0xb3ff, 0x8c30, 0xb7ff, 0x8d30, 0xbbff, 0x8e30, 0xbfff, 0x8f30,
+
+	0xc3ff, 0x8030, 0xc7ff, 0x8130, 0xcbff, 0x8230, 0xcfff, 0x8330,
+	0xd3ff, 0x8430, 0xd7ff, 0x8530, 0xdbff, 0x8630, 0xdfff, 0x8730,
+	0xe3ff, 0x8830, 0xe7ff, 0x8930, 0xebff, 0x8a30, 0xefff, 0x8b30,
+	0xf3ff, 0x8c30, 0xf7ff, 0x8d30, 0xfbff, 0x8e30, 0xffff, 0x8f30,
+};
+
+static unsigned short init3[128] /*__devinitdata*/ = {
+	0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5,
+	0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x8F7C, 0x167E, 0xF254,
+	0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x8BAA, 0x1B6D, 0xF234,
+	0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x86E7, 0x229E, 0xF224,
+
+	0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x87F6, 0x2C28, 0xF254,
+	0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x8F02, 0x1341, 0xF264,
+	0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x8FA9, 0x3EB5, 0xF294,
+	0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0xC4C3, 0x3EBB, 0xC5C3,
+
+	0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x8671, 0x14FD, 0x8287,
+	0x3EBC, 0xE610, 0x3EC8, 0x8C7B, 0x031A, 0x87E6, 0x3EC8, 0x86F7,
+	0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x821F, 0x3ECA, 0x8386,
+	0x3EC1, 0x8C03, 0x3EC9, 0x831E, 0x3ECA, 0x8C4C, 0x3EBF, 0x8C55,
+
+	0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x8EAD, 0x3EC8, 0xD308,
+	0x3EC2, 0x8F7E, 0x3ECB, 0x8219, 0x3ECB, 0xD26E, 0x3EC5, 0x831F,
+	0x3EC6, 0xC308, 0x3EC3, 0xB2FF, 0x3EC9, 0x8265, 0x3EC9, 0x8319,
+	0x1342, 0xD36E, 0x3EC7, 0xB3FF, 0x0000, 0x8365, 0x1420, 0x9570,
+};
+
+static unsigned short init4[128] /*__devinitdata*/ = {
+	0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5,
+	0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x0F7C, 0x167E, 0x7254,
+	0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x0BAA, 0x1B6D, 0x7234,
+	0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x06E7, 0x229E, 0x7224,
+
+	0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x07F6, 0x2C28, 0x7254,
+	0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x0F02, 0x1341, 0x7264,
+	0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x0FA9, 0x3EB5, 0x7294,
+	0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0x44C3, 0x3EBB, 0x45C3,
+
+	0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x0671, 0x14FD, 0x0287,
+	0x3EBC, 0xE610, 0x3EC8, 0x0C7B, 0x031A, 0x07E6, 0x3EC8, 0x86F7,
+	0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x021F, 0x3ECA, 0x0386,
+	0x3EC1, 0x0C03, 0x3EC9, 0x031E, 0x3ECA, 0x8C4C, 0x3EBF, 0x0C55,
+
+	0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x0EAD, 0x3EC8, 0xD308,
+	0x3EC2, 0x8F7E, 0x3ECB, 0x0219, 0x3ECB, 0xD26E, 0x3EC5, 0x031F,
+	0x3EC6, 0xC308, 0x3EC3, 0x32FF, 0x3EC9, 0x0265, 0x3EC9, 0x8319,
+	0x1342, 0xD36E, 0x3EC7, 0x33FF, 0x0000, 0x8365, 0x1420, 0x9570,
+};
+
+/* send an initialization array
+ * Taken from the oss driver, not obvious from the doc how this
+ * is meant to work
+ */
+static void __devinit
+send_array(struct snd_emu8000 *emu, unsigned short *data, int size)
+{
+	int i;
+	unsigned short *p;
+
+	p = data;
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT1_WRITE(emu, i, *p);
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT2_WRITE(emu, i, *p);
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT3_WRITE(emu, i, *p);
+	for (i = 0; i < size; i++, p++)
+		EMU8000_INIT4_WRITE(emu, i, *p);
+}
+
+
+/*
+ * Send initialization arrays to start up, this just follows the
+ * initialisation sequence in the adip.
+ */
+static void __devinit
+init_arrays(struct snd_emu8000 *emu)
+{
+	send_array(emu, init1, ARRAY_SIZE(init1)/4);
+
+	msleep((1024 * 1000) / 44100); /* wait for 1024 clocks */
+	send_array(emu, init2, ARRAY_SIZE(init2)/4);
+	send_array(emu, init3, ARRAY_SIZE(init3)/4);
+
+	EMU8000_HWCF4_WRITE(emu, 0);
+	EMU8000_HWCF5_WRITE(emu, 0x83);
+	EMU8000_HWCF6_WRITE(emu, 0x8000);
+
+	send_array(emu, init4, ARRAY_SIZE(init4)/4);
+}
+
+
+#define UNIQUE_ID1	0xa5b9
+#define UNIQUE_ID2	0x9d53
+
+/*
+ * Size the onboard memory.
+ * This is written so as not to need arbitary delays after the write. It
+ * seems that the only way to do this is to use the one channel and keep
+ * reallocating between read and write.
+ */
+static void __devinit
+size_dram(struct snd_emu8000 *emu)
+{
+	int i, size, detected_size;
+
+	if (emu->dram_checked)
+		return;
+
+	size = 0;
+	detected_size = 0;
+
+	/* write out a magic number */
+	snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);
+	snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_READ);
+	EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET);
+	EMU8000_SMLD_WRITE(emu, UNIQUE_ID1);
+	snd_emu8000_init_fm(emu); /* This must really be here and not 2 lines back even */
+
+	while (size < EMU8000_MAX_DRAM) {
+
+		size += 512 * 1024;  /* increment 512kbytes */
+
+		/* Write a unique data on the test address.
+		 * if the address is out of range, the data is written on
+		 * 0x200000(=EMU8000_DRAM_OFFSET).  Then the id word is
+		 * changed by this data.
+		 */
+		/*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);*/
+		EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1));
+		EMU8000_SMLD_WRITE(emu, UNIQUE_ID2);
+		snd_emu8000_write_wait(emu);
+
+		/*
+		 * read the data on the just written DRAM address
+		 * if not the same then we have reached the end of ram.
+		 */
+		/*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_READ);*/
+		EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1));
+		/*snd_emu8000_read_wait(emu);*/
+		EMU8000_SMLD_READ(emu); /* discard stale data  */
+		if (EMU8000_SMLD_READ(emu) != UNIQUE_ID2)
+			break; /* no memory at this address */
+
+		detected_size = size;
+
+		snd_emu8000_read_wait(emu);
+
+		/*
+		 * If it is the same it could be that the address just
+		 * wraps back to the beginning; so check to see if the
+		 * initial value has been overwritten.
+		 */
+		EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET);
+		EMU8000_SMLD_READ(emu); /* discard stale data  */
+		if (EMU8000_SMLD_READ(emu) != UNIQUE_ID1)
+			break; /* we must have wrapped around */
+		snd_emu8000_read_wait(emu);
+	}
+
+	/* wait until FULL bit in SMAxW register is false */
+	for (i = 0; i < 10000; i++) {
+		if ((EMU8000_SMALW_READ(emu) & 0x80000000) == 0)
+			break;
+		schedule_timeout_interruptible(1);
+		if (signal_pending(current))
+			break;
+	}
+	snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_CLOSE);
+	snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_CLOSE);
+
+	snd_printdd("EMU8000 [0x%lx]: %d Kb on-board memory detected\n",
+		    emu->port1, detected_size/1024);
+
+	emu->mem_size = detected_size;
+	emu->dram_checked = 1;
+}
+
+
+/*
+ * Initiailise the FM section.  You have to do this to use sample RAM
+ * and therefore lose 2 voices.
+ */
+/*exported*/ void
+snd_emu8000_init_fm(struct snd_emu8000 *emu)
+{
+	unsigned long flags;
+
+	/* Initialize the last two channels for DRAM refresh and producing
+	   the reverb and chorus effects for Yamaha OPL-3 synthesizer */
+
+	/* 31: FM left channel, 0xffffe0-0xffffe8 */
+	EMU8000_DCYSUSV_WRITE(emu, 30, 0x80);
+	EMU8000_PSST_WRITE(emu, 30, 0xFFFFFFE0); /* full left */
+	EMU8000_CSL_WRITE(emu, 30, 0x00FFFFE8 | (emu->fm_chorus_depth << 24));
+	EMU8000_PTRX_WRITE(emu, 30, (emu->fm_reverb_depth << 8));
+	EMU8000_CPF_WRITE(emu, 30, 0);
+	EMU8000_CCCA_WRITE(emu, 30, 0x00FFFFE3);
+
+	/* 32: FM right channel, 0xfffff0-0xfffff8 */
+	EMU8000_DCYSUSV_WRITE(emu, 31, 0x80);
+	EMU8000_PSST_WRITE(emu, 31, 0x00FFFFF0); /* full right */
+	EMU8000_CSL_WRITE(emu, 31, 0x00FFFFF8 | (emu->fm_chorus_depth << 24));
+	EMU8000_PTRX_WRITE(emu, 31, (emu->fm_reverb_depth << 8));
+	EMU8000_CPF_WRITE(emu, 31, 0x8000);
+	EMU8000_CCCA_WRITE(emu, 31, 0x00FFFFF3);
+
+	snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0);
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	while (!(inw(EMU8000_PTR(emu)) & 0x1000))
+		;
+	while ((inw(EMU8000_PTR(emu)) & 0x1000))
+		;
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0x4828);
+	/* this is really odd part.. */
+	outb(0x3C, EMU8000_PTR(emu));
+	outb(0, EMU8000_DATA1(emu));
+
+	/* skew volume & cutoff */
+	EMU8000_VTFT_WRITE(emu, 30, 0x8000FFFF);
+	EMU8000_VTFT_WRITE(emu, 31, 0x8000FFFF);
+}
+
+
+/*
+ * The main initialization routine.
+ */
+static void __devinit
+snd_emu8000_init_hw(struct snd_emu8000 *emu)
+{
+	int i;
+
+	emu->last_reg = 0xffff; /* reset the last register index */
+
+	/* initialize hardware configuration */
+	EMU8000_HWCF1_WRITE(emu, 0x0059);
+	EMU8000_HWCF2_WRITE(emu, 0x0020);
+
+	/* disable audio; this seems to reduce a clicking noise a bit.. */
+	EMU8000_HWCF3_WRITE(emu, 0);
+
+	/* initialize audio channels */
+	init_audio(emu);
+
+	/* initialize DMA */
+	init_dma(emu);
+
+	/* initialize init arrays */
+	init_arrays(emu);
+
+	/*
+	 * Initialize the FM section of the AWE32, this is needed
+	 * for DRAM refresh as well
+	 */
+	snd_emu8000_init_fm(emu);
+
+	/* terminate all voices */
+	for (i = 0; i < EMU8000_DRAM_VOICES; i++)
+		EMU8000_DCYSUSV_WRITE(emu, 0, 0x807F);
+	
+	/* check DRAM memory size */
+	size_dram(emu);
+
+	/* enable audio */
+	EMU8000_HWCF3_WRITE(emu, 0x4);
+
+	/* set equzlier, chorus and reverb modes */
+	snd_emu8000_update_equalizer(emu);
+	snd_emu8000_update_chorus_mode(emu);
+	snd_emu8000_update_reverb_mode(emu);
+}
+
+
+/*----------------------------------------------------------------
+ * Bass/Treble Equalizer
+ *----------------------------------------------------------------*/
+
+static unsigned short bass_parm[12][3] = {
+	{0xD26A, 0xD36A, 0x0000}, /* -12 dB */
+	{0xD25B, 0xD35B, 0x0000}, /*  -8 */
+	{0xD24C, 0xD34C, 0x0000}, /*  -6 */
+	{0xD23D, 0xD33D, 0x0000}, /*  -4 */
+	{0xD21F, 0xD31F, 0x0000}, /*  -2 */
+	{0xC208, 0xC308, 0x0001}, /*   0 (HW default) */
+	{0xC219, 0xC319, 0x0001}, /*  +2 */
+	{0xC22A, 0xC32A, 0x0001}, /*  +4 */
+	{0xC24C, 0xC34C, 0x0001}, /*  +6 */
+	{0xC26E, 0xC36E, 0x0001}, /*  +8 */
+	{0xC248, 0xC384, 0x0002}, /* +10 */
+	{0xC26A, 0xC36A, 0x0002}, /* +12 dB */
+};
+
+static unsigned short treble_parm[12][9] = {
+	{0x821E, 0xC26A, 0x031E, 0xC36A, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, /* -12 dB */
+	{0x821E, 0xC25B, 0x031E, 0xC35B, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xC24C, 0x031E, 0xC34C, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xC23D, 0x031E, 0xC33D, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xC21F, 0x031E, 0xC31F, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021E, 0xD208, 0x831E, 0xD308, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021D, 0xD219, 0x831D, 0xD319, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021C, 0xD22A, 0x831C, 0xD32A, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x021A, 0xD24C, 0x831A, 0xD34C, 0x0002},
+	{0x821E, 0xD208, 0x031E, 0xD308, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}, /* +8 (HW default) */
+	{0x821D, 0xD219, 0x031D, 0xD319, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002},
+	{0x821C, 0xD22A, 0x031C, 0xD32A, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}  /* +12 dB */
+};
+
+
+/*
+ * set Emu8000 digital equalizer; from 0 to 11 [-12dB - 12dB]
+ */
+/*exported*/ void
+snd_emu8000_update_equalizer(struct snd_emu8000 *emu)
+{
+	unsigned short w;
+	int bass = emu->bass_level;
+	int treble = emu->treble_level;
+
+	if (bass < 0 || bass > 11 || treble < 0 || treble > 11)
+		return;
+	EMU8000_INIT4_WRITE(emu, 0x01, bass_parm[bass][0]);
+	EMU8000_INIT4_WRITE(emu, 0x11, bass_parm[bass][1]);
+	EMU8000_INIT3_WRITE(emu, 0x11, treble_parm[treble][0]);
+	EMU8000_INIT3_WRITE(emu, 0x13, treble_parm[treble][1]);
+	EMU8000_INIT3_WRITE(emu, 0x1b, treble_parm[treble][2]);
+	EMU8000_INIT4_WRITE(emu, 0x07, treble_parm[treble][3]);
+	EMU8000_INIT4_WRITE(emu, 0x0b, treble_parm[treble][4]);
+	EMU8000_INIT4_WRITE(emu, 0x0d, treble_parm[treble][5]);
+	EMU8000_INIT4_WRITE(emu, 0x17, treble_parm[treble][6]);
+	EMU8000_INIT4_WRITE(emu, 0x19, treble_parm[treble][7]);
+	w = bass_parm[bass][2] + treble_parm[treble][8];
+	EMU8000_INIT4_WRITE(emu, 0x15, (unsigned short)(w + 0x0262));
+	EMU8000_INIT4_WRITE(emu, 0x1d, (unsigned short)(w + 0x8362));
+}
+
+
+/*----------------------------------------------------------------
+ * Chorus mode control
+ *----------------------------------------------------------------*/
+
+/*
+ * chorus mode parameters
+ */
+#define SNDRV_EMU8000_CHORUS_1		0
+#define	SNDRV_EMU8000_CHORUS_2		1
+#define	SNDRV_EMU8000_CHORUS_3		2
+#define	SNDRV_EMU8000_CHORUS_4		3
+#define	SNDRV_EMU8000_CHORUS_FEEDBACK	4
+#define	SNDRV_EMU8000_CHORUS_FLANGER	5
+#define	SNDRV_EMU8000_CHORUS_SHORTDELAY	6
+#define	SNDRV_EMU8000_CHORUS_SHORTDELAY2	7
+#define SNDRV_EMU8000_CHORUS_PREDEFINED	8
+/* user can define chorus modes up to 32 */
+#define SNDRV_EMU8000_CHORUS_NUMBERS	32
+
+struct soundfont_chorus_fx {
+	unsigned short feedback;	/* feedback level (0xE600-0xE6FF) */
+	unsigned short delay_offset;	/* delay (0-0x0DA3) [1/44100 sec] */
+	unsigned short lfo_depth;	/* LFO depth (0xBC00-0xBCFF) */
+	unsigned int delay;	/* right delay (0-0xFFFFFFFF) [1/256/44100 sec] */
+	unsigned int lfo_freq;		/* LFO freq LFO freq (0-0xFFFFFFFF) */
+};
+
+/* 5 parameters for each chorus mode; 3 x 16bit, 2 x 32bit */
+static char chorus_defined[SNDRV_EMU8000_CHORUS_NUMBERS];
+static struct soundfont_chorus_fx chorus_parm[SNDRV_EMU8000_CHORUS_NUMBERS] = {
+	{0xE600, 0x03F6, 0xBC2C ,0x00000000, 0x0000006D}, /* chorus 1 */
+	{0xE608, 0x031A, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 2 */
+	{0xE610, 0x031A, 0xBC84, 0x00000000, 0x00000083}, /* chorus 3 */
+	{0xE620, 0x0269, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 4 */
+	{0xE680, 0x04D3, 0xBCA6, 0x00000000, 0x0000005B}, /* feedback */
+	{0xE6E0, 0x044E, 0xBC37, 0x00000000, 0x00000026}, /* flanger */
+	{0xE600, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay */
+	{0xE6C0, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay + feedback */
+};
+
+/*exported*/ int
+snd_emu8000_load_chorus_fx(struct snd_emu8000 *emu, int mode, const void __user *buf, long len)
+{
+	struct soundfont_chorus_fx rec;
+	if (mode < SNDRV_EMU8000_CHORUS_PREDEFINED || mode >= SNDRV_EMU8000_CHORUS_NUMBERS) {
+		snd_printk(KERN_WARNING "invalid chorus mode %d for uploading\n", mode);
+		return -EINVAL;
+	}
+	if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec)))
+		return -EFAULT;
+	chorus_parm[mode] = rec;
+	chorus_defined[mode] = 1;
+	return 0;
+}
+
+/*exported*/ void
+snd_emu8000_update_chorus_mode(struct snd_emu8000 *emu)
+{
+	int effect = emu->chorus_mode;
+	if (effect < 0 || effect >= SNDRV_EMU8000_CHORUS_NUMBERS ||
+	    (effect >= SNDRV_EMU8000_CHORUS_PREDEFINED && !chorus_defined[effect]))
+		return;
+	EMU8000_INIT3_WRITE(emu, 0x09, chorus_parm[effect].feedback);
+	EMU8000_INIT3_WRITE(emu, 0x0c, chorus_parm[effect].delay_offset);
+	EMU8000_INIT4_WRITE(emu, 0x03, chorus_parm[effect].lfo_depth);
+	EMU8000_HWCF4_WRITE(emu, chorus_parm[effect].delay);
+	EMU8000_HWCF5_WRITE(emu, chorus_parm[effect].lfo_freq);
+	EMU8000_HWCF6_WRITE(emu, 0x8000);
+	EMU8000_HWCF7_WRITE(emu, 0x0000);
+}
+
+/*----------------------------------------------------------------
+ * Reverb mode control
+ *----------------------------------------------------------------*/
+
+/*
+ * reverb mode parameters
+ */
+#define	SNDRV_EMU8000_REVERB_ROOM1	0
+#define SNDRV_EMU8000_REVERB_ROOM2	1
+#define	SNDRV_EMU8000_REVERB_ROOM3	2
+#define	SNDRV_EMU8000_REVERB_HALL1	3
+#define	SNDRV_EMU8000_REVERB_HALL2	4
+#define	SNDRV_EMU8000_REVERB_PLATE	5
+#define	SNDRV_EMU8000_REVERB_DELAY	6
+#define	SNDRV_EMU8000_REVERB_PANNINGDELAY 7
+#define SNDRV_EMU8000_REVERB_PREDEFINED	8
+/* user can define reverb modes up to 32 */
+#define SNDRV_EMU8000_REVERB_NUMBERS	32
+
+struct soundfont_reverb_fx {
+	unsigned short parms[28];
+};
+
+/* reverb mode settings; write the following 28 data of 16 bit length
+ *   on the corresponding ports in the reverb_cmds array
+ */
+static char reverb_defined[SNDRV_EMU8000_CHORUS_NUMBERS];
+static struct soundfont_reverb_fx reverb_parm[SNDRV_EMU8000_REVERB_NUMBERS] = {
+{{  /* room 1 */
+	0xB488, 0xA450, 0x9550, 0x84B5, 0x383A, 0x3EB5, 0x72F4,
+	0x72A4, 0x7254, 0x7204, 0x7204, 0x7204, 0x4416, 0x4516,
+	0xA490, 0xA590, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* room 2 */
+	0xB488, 0xA458, 0x9558, 0x84B5, 0x383A, 0x3EB5, 0x7284,
+	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548,
+	0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* room 3 */
+	0xB488, 0xA460, 0x9560, 0x84B5, 0x383A, 0x3EB5, 0x7284,
+	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4416, 0x4516,
+	0xA490, 0xA590, 0x842C, 0x852C, 0x842C, 0x852C, 0x842B,
+	0x852B, 0x842B, 0x852B, 0x842A, 0x852A, 0x842A, 0x852A,
+}},
+{{  /* hall 1 */
+	0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7284,
+	0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548,
+	0xA440, 0xA540, 0x842B, 0x852B, 0x842B, 0x852B, 0x842A,
+	0x852A, 0x842A, 0x852A, 0x8429, 0x8529, 0x8429, 0x8529,
+}},
+{{  /* hall 2 */
+	0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7254,
+	0x7234, 0x7224, 0x7254, 0x7264, 0x7294, 0x44C3, 0x45C3,
+	0xA404, 0xA504, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* plate */
+	0xB4FF, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7234,
+	0x7234, 0x7234, 0x7234, 0x7234, 0x7234, 0x4448, 0x4548,
+	0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429,
+	0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528,
+}},
+{{  /* delay */
+	0xB4FF, 0xA470, 0x9500, 0x84B5, 0x333A, 0x39B5, 0x7204,
+	0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500,
+	0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420,
+	0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520,
+}},
+{{  /* panning delay */
+	0xB4FF, 0xA490, 0x9590, 0x8474, 0x333A, 0x39B5, 0x7204,
+	0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500,
+	0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420,
+	0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520,
+}},
+};
+
+enum { DATA1, DATA2 };
+#define AWE_INIT1(c)	EMU8000_CMD(2,c), DATA1
+#define AWE_INIT2(c)	EMU8000_CMD(2,c), DATA2
+#define AWE_INIT3(c)	EMU8000_CMD(3,c), DATA1
+#define AWE_INIT4(c)	EMU8000_CMD(3,c), DATA2
+
+static struct reverb_cmd_pair {
+	unsigned short cmd, port;
+} reverb_cmds[28] = {
+  {AWE_INIT1(0x03)}, {AWE_INIT1(0x05)}, {AWE_INIT4(0x1F)}, {AWE_INIT1(0x07)},
+  {AWE_INIT2(0x14)}, {AWE_INIT2(0x16)}, {AWE_INIT1(0x0F)}, {AWE_INIT1(0x17)},
+  {AWE_INIT1(0x1F)}, {AWE_INIT2(0x07)}, {AWE_INIT2(0x0F)}, {AWE_INIT2(0x17)},
+  {AWE_INIT2(0x1D)}, {AWE_INIT2(0x1F)}, {AWE_INIT3(0x01)}, {AWE_INIT3(0x03)},
+  {AWE_INIT1(0x09)}, {AWE_INIT1(0x0B)}, {AWE_INIT1(0x11)}, {AWE_INIT1(0x13)},
+  {AWE_INIT1(0x19)}, {AWE_INIT1(0x1B)}, {AWE_INIT2(0x01)}, {AWE_INIT2(0x03)},
+  {AWE_INIT2(0x09)}, {AWE_INIT2(0x0B)}, {AWE_INIT2(0x11)}, {AWE_INIT2(0x13)},
+};
+
+/*exported*/ int
+snd_emu8000_load_reverb_fx(struct snd_emu8000 *emu, int mode, const void __user *buf, long len)
+{
+	struct soundfont_reverb_fx rec;
+
+	if (mode < SNDRV_EMU8000_REVERB_PREDEFINED || mode >= SNDRV_EMU8000_REVERB_NUMBERS) {
+		snd_printk(KERN_WARNING "invalid reverb mode %d for uploading\n", mode);
+		return -EINVAL;
+	}
+	if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec)))
+		return -EFAULT;
+	reverb_parm[mode] = rec;
+	reverb_defined[mode] = 1;
+	return 0;
+}
+
+/*exported*/ void
+snd_emu8000_update_reverb_mode(struct snd_emu8000 *emu)
+{
+	int effect = emu->reverb_mode;
+	int i;
+
+	if (effect < 0 || effect >= SNDRV_EMU8000_REVERB_NUMBERS ||
+	    (effect >= SNDRV_EMU8000_REVERB_PREDEFINED && !reverb_defined[effect]))
+		return;
+	for (i = 0; i < 28; i++) {
+		int port;
+		if (reverb_cmds[i].port == DATA1)
+			port = EMU8000_DATA1(emu);
+		else
+			port = EMU8000_DATA2(emu);
+		snd_emu8000_poke(emu, port, reverb_cmds[i].cmd, reverb_parm[effect].parms[i]);
+	}
+}
+
+
+/*----------------------------------------------------------------
+ * mixer interface
+ *----------------------------------------------------------------*/
+
+/*
+ * bass/treble
+ */
+static int mixer_bass_treble_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 11;
+	return 0;
+}
+
+static int mixer_bass_treble_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->treble_level : emu->bass_level;
+	return 0;
+}
+
+static int mixer_bass_treble_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1;
+	
+	val1 = ucontrol->value.integer.value[0] % 12;
+	spin_lock_irqsave(&emu->control_lock, flags);
+	if (kcontrol->private_value) {
+		change = val1 != emu->treble_level;
+		emu->treble_level = val1;
+	} else {
+		change = val1 != emu->bass_level;
+		emu->bass_level = val1;
+	}
+	spin_unlock_irqrestore(&emu->control_lock, flags);
+	snd_emu8000_update_equalizer(emu);
+	return change;
+}
+
+static struct snd_kcontrol_new mixer_bass_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Synth Tone Control - Bass",
+	.info = mixer_bass_treble_info,
+	.get = mixer_bass_treble_get,
+	.put = mixer_bass_treble_put,
+	.private_value = 0,
+};
+
+static struct snd_kcontrol_new mixer_treble_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Synth Tone Control - Treble",
+	.info = mixer_bass_treble_info,
+	.get = mixer_bass_treble_get,
+	.put = mixer_bass_treble_put,
+	.private_value = 1,
+};
+
+/*
+ * chorus/reverb mode
+ */
+static int mixer_chorus_reverb_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = kcontrol->private_value ? (SNDRV_EMU8000_CHORUS_NUMBERS-1) : (SNDRV_EMU8000_REVERB_NUMBERS-1);
+	return 0;
+}
+
+static int mixer_chorus_reverb_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->chorus_mode : emu->reverb_mode;
+	return 0;
+}
+
+static int mixer_chorus_reverb_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1;
+	
+	spin_lock_irqsave(&emu->control_lock, flags);
+	if (kcontrol->private_value) {
+		val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_CHORUS_NUMBERS;
+		change = val1 != emu->chorus_mode;
+		emu->chorus_mode = val1;
+	} else {
+		val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_REVERB_NUMBERS;
+		change = val1 != emu->reverb_mode;
+		emu->reverb_mode = val1;
+	}
+	spin_unlock_irqrestore(&emu->control_lock, flags);
+	if (change) {
+		if (kcontrol->private_value)
+			snd_emu8000_update_chorus_mode(emu);
+		else
+			snd_emu8000_update_reverb_mode(emu);
+	}
+	return change;
+}
+
+static struct snd_kcontrol_new mixer_chorus_mode_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Chorus Mode",
+	.info = mixer_chorus_reverb_info,
+	.get = mixer_chorus_reverb_get,
+	.put = mixer_chorus_reverb_put,
+	.private_value = 1,
+};
+
+static struct snd_kcontrol_new mixer_reverb_mode_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Reverb Mode",
+	.info = mixer_chorus_reverb_info,
+	.get = mixer_chorus_reverb_get,
+	.put = mixer_chorus_reverb_put,
+	.private_value = 0,
+};
+
+/*
+ * FM OPL3 chorus/reverb depth
+ */
+static int mixer_fm_depth_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	return 0;
+}
+
+static int mixer_fm_depth_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->fm_chorus_depth : emu->fm_reverb_depth;
+	return 0;
+}
+
+static int mixer_fm_depth_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned short val1;
+	
+	val1 = ucontrol->value.integer.value[0] % 256;
+	spin_lock_irqsave(&emu->control_lock, flags);
+	if (kcontrol->private_value) {
+		change = val1 != emu->fm_chorus_depth;
+		emu->fm_chorus_depth = val1;
+	} else {
+		change = val1 != emu->fm_reverb_depth;
+		emu->fm_reverb_depth = val1;
+	}
+	spin_unlock_irqrestore(&emu->control_lock, flags);
+	if (change)
+		snd_emu8000_init_fm(emu);
+	return change;
+}
+
+static struct snd_kcontrol_new mixer_fm_chorus_depth_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "FM Chorus Depth",
+	.info = mixer_fm_depth_info,
+	.get = mixer_fm_depth_get,
+	.put = mixer_fm_depth_put,
+	.private_value = 1,
+};
+
+static struct snd_kcontrol_new mixer_fm_reverb_depth_control =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "FM Reverb Depth",
+	.info = mixer_fm_depth_info,
+	.get = mixer_fm_depth_get,
+	.put = mixer_fm_depth_put,
+	.private_value = 0,
+};
+
+
+static struct snd_kcontrol_new *mixer_defs[EMU8000_NUM_CONTROLS] = {
+	&mixer_bass_control,
+	&mixer_treble_control,
+	&mixer_chorus_mode_control,
+	&mixer_reverb_mode_control,
+	&mixer_fm_chorus_depth_control,
+	&mixer_fm_reverb_depth_control,
+};
+
+/*
+ * create and attach mixer elements for WaveTable treble/bass controls
+ */
+static int __devinit
+snd_emu8000_create_mixer(struct snd_card *card, struct snd_emu8000 *emu)
+{
+	int i, err = 0;
+
+	if (snd_BUG_ON(!emu || !card))
+		return -EINVAL;
+
+	spin_lock_init(&emu->control_lock);
+
+	memset(emu->controls, 0, sizeof(emu->controls));
+	for (i = 0; i < EMU8000_NUM_CONTROLS; i++) {
+		if ((err = snd_ctl_add(card, emu->controls[i] = snd_ctl_new1(mixer_defs[i], emu))) < 0)
+			goto __error;
+	}
+	return 0;
+
+__error:
+	for (i = 0; i < EMU8000_NUM_CONTROLS; i++) {
+		down_write(&card->controls_rwsem);
+		if (emu->controls[i])
+			snd_ctl_remove(card, emu->controls[i]);
+		up_write(&card->controls_rwsem);
+	}
+	return err;
+}
+
+
+/*
+ * free resources
+ */
+static int snd_emu8000_free(struct snd_emu8000 *hw)
+{
+	release_and_free_resource(hw->res_port1);
+	release_and_free_resource(hw->res_port2);
+	release_and_free_resource(hw->res_port3);
+	kfree(hw);
+	return 0;
+}
+
+/*
+ */
+static int snd_emu8000_dev_free(struct snd_device *device)
+{
+	struct snd_emu8000 *hw = device->device_data;
+	return snd_emu8000_free(hw);
+}
+
+/*
+ * initialize and register emu8000 synth device.
+ */
+int __devinit
+snd_emu8000_new(struct snd_card *card, int index, long port, int seq_ports,
+		struct snd_seq_device **awe_ret)
+{
+	struct snd_seq_device *awe;
+	struct snd_emu8000 *hw;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_emu8000_dev_free,
+	};
+
+	if (awe_ret)
+		*awe_ret = NULL;
+
+	if (seq_ports <= 0)
+		return 0;
+
+	hw = kzalloc(sizeof(*hw), GFP_KERNEL);
+	if (hw == NULL)
+		return -ENOMEM;
+	spin_lock_init(&hw->reg_lock);
+	hw->index = index;
+	hw->port1 = port;
+	hw->port2 = port + 0x400;
+	hw->port3 = port + 0x800;
+	if (!(hw->res_port1 = request_region(hw->port1, 4, "Emu8000-1")) ||
+	    !(hw->res_port2 = request_region(hw->port2, 4, "Emu8000-2")) ||
+	    !(hw->res_port3 = request_region(hw->port3, 4, "Emu8000-3"))) {
+		snd_printk(KERN_ERR "sbawe: can't grab ports 0x%lx, 0x%lx, 0x%lx\n", hw->port1, hw->port2, hw->port3);
+		snd_emu8000_free(hw);
+		return -EBUSY;
+	}
+	hw->mem_size = 0;
+	hw->card = card;
+	hw->seq_ports = seq_ports;
+	hw->bass_level = 5;
+	hw->treble_level = 9;
+	hw->chorus_mode = 2;
+	hw->reverb_mode = 4;
+	hw->fm_chorus_depth = 0;
+	hw->fm_reverb_depth = 0;
+
+	if (snd_emu8000_detect(hw) < 0) {
+		snd_emu8000_free(hw);
+		return -ENODEV;
+	}
+
+	snd_emu8000_init_hw(hw);
+	if ((err = snd_emu8000_create_mixer(card, hw)) < 0) {
+		snd_emu8000_free(hw);
+		return err;
+	}
+	
+	if ((err = snd_device_new(card, SNDRV_DEV_CODEC, hw, &ops)) < 0) {
+		snd_emu8000_free(hw);
+		return err;
+	}
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+	if (snd_seq_device_new(card, index, SNDRV_SEQ_DEV_ID_EMU8000,
+			       sizeof(struct snd_emu8000*), &awe) >= 0) {
+		strcpy(awe->name, "EMU-8000");
+		*(struct snd_emu8000 **)SNDRV_SEQ_DEVICE_ARGPTR(awe) = hw;
+	}
+#else
+	awe = NULL;
+#endif
+	if (awe_ret)
+		*awe_ret = awe;
+
+	return 0;
+}
+
+
+/*
+ * exported stuff
+ */
+
+EXPORT_SYMBOL(snd_emu8000_poke);
+EXPORT_SYMBOL(snd_emu8000_peek);
+EXPORT_SYMBOL(snd_emu8000_poke_dw);
+EXPORT_SYMBOL(snd_emu8000_peek_dw);
+EXPORT_SYMBOL(snd_emu8000_dma_chan);
+EXPORT_SYMBOL(snd_emu8000_init_fm);
+EXPORT_SYMBOL(snd_emu8000_load_chorus_fx);
+EXPORT_SYMBOL(snd_emu8000_load_reverb_fx);
+EXPORT_SYMBOL(snd_emu8000_update_chorus_mode);
+EXPORT_SYMBOL(snd_emu8000_update_reverb_mode);
+EXPORT_SYMBOL(snd_emu8000_update_equalizer);
diff --git a/linux/sound/isa/sb/emu8000_callback.c b/linux/sound/isa/sb/emu8000_callback.c
new file mode 100644
index 0000000..9a3c71c
--- /dev/null
+++ b/linux/sound/isa/sb/emu8000_callback.c
@@ -0,0 +1,546 @@
+/*
+ *  synth callback routines for the emu8000 (AWE32/64)
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "emu8000_local.h"
+#include <sound/asoundef.h>
+
+/*
+ * prototypes
+ */
+static struct snd_emux_voice *get_voice(struct snd_emux *emu,
+					struct snd_emux_port *port);
+static int start_voice(struct snd_emux_voice *vp);
+static void trigger_voice(struct snd_emux_voice *vp);
+static void release_voice(struct snd_emux_voice *vp);
+static void update_voice(struct snd_emux_voice *vp, int update);
+static void reset_voice(struct snd_emux *emu, int ch);
+static void terminate_voice(struct snd_emux_voice *vp);
+static void sysex(struct snd_emux *emu, char *buf, int len, int parsed,
+		  struct snd_midi_channel_set *chset);
+#ifdef CONFIG_SND_SEQUENCER_OSS
+static int oss_ioctl(struct snd_emux *emu, int cmd, int p1, int p2);
+#endif
+static int load_fx(struct snd_emux *emu, int type, int mode,
+		   const void __user *buf, long len);
+
+static void set_pitch(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_volume(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_pan(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_fmmod(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_tremfreq(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_fm2frq2(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void set_filterQ(struct snd_emu8000 *hw, struct snd_emux_voice *vp);
+static void snd_emu8000_tweak_voice(struct snd_emu8000 *emu, int ch);
+
+/*
+ * Ensure a value is between two points
+ * macro evaluates its args more than once, so changed to upper-case.
+ */
+#define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0)
+#define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0)
+
+
+/*
+ * set up operators
+ */
+static struct snd_emux_operators emu8000_ops = {
+	.owner =	THIS_MODULE,
+	.get_voice =	get_voice,
+	.prepare =	start_voice,
+	.trigger =	trigger_voice,
+	.release =	release_voice,
+	.update =	update_voice,
+	.terminate =	terminate_voice,
+	.reset =	reset_voice,
+	.sample_new =	snd_emu8000_sample_new,
+	.sample_free =	snd_emu8000_sample_free,
+	.sample_reset = snd_emu8000_sample_reset,
+	.load_fx =	load_fx,
+	.sysex =	sysex,
+#ifdef CONFIG_SND_SEQUENCER_OSS
+	.oss_ioctl =	oss_ioctl,
+#endif
+};
+
+void
+snd_emu8000_ops_setup(struct snd_emu8000 *hw)
+{
+	hw->emu->ops = emu8000_ops;
+}
+
+
+
+/*
+ * Terminate a voice
+ */
+static void
+release_voice(struct snd_emux_voice *vp)
+{
+	int dcysusv;
+	struct snd_emu8000 *hw;
+
+	hw = vp->hw;
+	dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease;
+	EMU8000_DCYSUS_WRITE(hw, vp->ch, dcysusv);
+	dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease;
+	EMU8000_DCYSUSV_WRITE(hw, vp->ch, dcysusv);
+}
+
+
+/*
+ */
+static void
+terminate_voice(struct snd_emux_voice *vp)
+{
+	struct snd_emu8000 *hw; 
+
+	hw = vp->hw;
+	EMU8000_DCYSUSV_WRITE(hw, vp->ch, 0x807F);
+}
+
+
+/*
+ */
+static void
+update_voice(struct snd_emux_voice *vp, int update)
+{
+	struct snd_emu8000 *hw;
+
+	hw = vp->hw;
+	if (update & SNDRV_EMUX_UPDATE_VOLUME)
+		set_volume(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_PITCH)
+		set_pitch(hw, vp);
+	if ((update & SNDRV_EMUX_UPDATE_PAN) &&
+	    vp->port->ctrls[EMUX_MD_REALTIME_PAN])
+		set_pan(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_FMMOD)
+		set_fmmod(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_TREMFREQ)
+		set_tremfreq(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_FM2FRQ2)
+		set_fm2frq2(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_Q)
+		set_filterQ(hw, vp);
+}
+
+
+/*
+ * Find a channel (voice) within the EMU that is not in use or at least
+ * less in use than other channels.  Always returns a valid pointer
+ * no matter what.  If there is a real shortage of voices then one
+ * will be cut. Such is life.
+ *
+ * The channel index (vp->ch) must be initialized in this routine.
+ * In Emu8k, it is identical with the array index.
+ */
+static struct snd_emux_voice *
+get_voice(struct snd_emux *emu, struct snd_emux_port *port)
+{
+	int  i;
+	struct snd_emux_voice *vp;
+	struct snd_emu8000 *hw;
+
+	/* what we are looking for, in order of preference */
+	enum {
+		OFF=0, RELEASED, PLAYING, END
+	};
+
+	/* Keeps track of what we are finding */
+	struct best {
+		unsigned int  time;
+		int voice;
+	} best[END];
+	struct best *bp;
+
+	hw = emu->hw;
+
+	for (i = 0; i < END; i++) {
+		best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */;
+		best[i].voice = -1;
+	}
+
+	/*
+	 * Go through them all and get a best one to use.
+	 */
+	for (i = 0; i < emu->max_voices; i++) {
+		int state, val;
+
+		vp = &emu->voices[i];
+		state = vp->state;
+
+		if (state == SNDRV_EMUX_ST_OFF)
+			bp = best + OFF;
+		else if (state == SNDRV_EMUX_ST_RELEASED ||
+			 state == SNDRV_EMUX_ST_PENDING) {
+			bp = best + RELEASED;
+			val = (EMU8000_CVCF_READ(hw, vp->ch) >> 16) & 0xffff;
+			if (! val)
+				bp = best + OFF;
+		}
+		else if (state & SNDRV_EMUX_ST_ON)
+			bp = best + PLAYING;
+		else
+			continue;
+
+		/* check if sample is finished playing (non-looping only) */
+		if (state != SNDRV_EMUX_ST_OFF &&
+		    (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) {
+			val = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff;
+			if (val >= vp->reg.loopstart)
+				bp = best + OFF;
+		}
+
+		if (vp->time < bp->time) {
+			bp->time = vp->time;
+			bp->voice = i;
+		}
+	}
+
+	for (i = 0; i < END; i++) {
+		if (best[i].voice >= 0) {
+			vp = &emu->voices[best[i].voice];
+			vp->ch = best[i].voice;
+			return vp;
+		}
+	}
+
+	/* not found */
+	return NULL;
+}
+
+/*
+ */
+static int
+start_voice(struct snd_emux_voice *vp)
+{
+	unsigned int temp;
+	int ch;
+	int addr;
+	struct snd_midi_channel *chan;
+	struct snd_emu8000 *hw;
+
+	hw = vp->hw;
+	ch = vp->ch;
+	chan = vp->chan;
+
+	/* channel to be silent and idle */
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);
+	EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_PTRX_WRITE(hw, ch, 0);
+	EMU8000_CPF_WRITE(hw, ch, 0);
+
+	/* set pitch offset */
+	set_pitch(hw, vp);
+
+	/* set envelope parameters */
+	EMU8000_ENVVAL_WRITE(hw, ch, vp->reg.parm.moddelay);
+	EMU8000_ATKHLD_WRITE(hw, ch, vp->reg.parm.modatkhld);
+	EMU8000_DCYSUS_WRITE(hw, ch, vp->reg.parm.moddcysus);
+	EMU8000_ENVVOL_WRITE(hw, ch, vp->reg.parm.voldelay);
+	EMU8000_ATKHLDV_WRITE(hw, ch, vp->reg.parm.volatkhld);
+	/* decay/sustain parameter for volume envelope is used
+	   for triggerg the voice */
+
+	/* cutoff and volume */
+	set_volume(hw, vp);
+
+	/* modulation envelope heights */
+	EMU8000_PEFE_WRITE(hw, ch, vp->reg.parm.pefe);
+
+	/* lfo1/2 delay */
+	EMU8000_LFO1VAL_WRITE(hw, ch, vp->reg.parm.lfo1delay);
+	EMU8000_LFO2VAL_WRITE(hw, ch, vp->reg.parm.lfo2delay);
+
+	/* lfo1 pitch & cutoff shift */
+	set_fmmod(hw, vp);
+	/* lfo1 volume & freq */
+	set_tremfreq(hw, vp);
+	/* lfo2 pitch & freq */
+	set_fm2frq2(hw, vp);
+	/* pan & loop start */
+	set_pan(hw, vp);
+
+	/* chorus & loop end (chorus 8bit, MSB) */
+	addr = vp->reg.loopend - 1;
+	temp = vp->reg.parm.chorus;
+	temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10;
+	LIMITMAX(temp, 255);
+	temp = (temp <<24) | (unsigned int)addr;
+	EMU8000_CSL_WRITE(hw, ch, temp);
+
+	/* Q & current address (Q 4bit value, MSB) */
+	addr = vp->reg.start - 1;
+	temp = vp->reg.parm.filterQ;
+	temp = (temp<<28) | (unsigned int)addr;
+	EMU8000_CCCA_WRITE(hw, ch, temp);
+
+	/* clear unknown registers */
+	EMU8000_00A0_WRITE(hw, ch, 0);
+	EMU8000_0080_WRITE(hw, ch, 0);
+
+	/* reset volume */
+	temp = vp->vtarget << 16;
+	EMU8000_VTFT_WRITE(hw, ch, temp | vp->ftarget);
+	EMU8000_CVCF_WRITE(hw, ch, temp | 0xff00);
+
+	return 0;
+}
+
+/*
+ * Start envelope
+ */
+static void
+trigger_voice(struct snd_emux_voice *vp)
+{
+	int ch = vp->ch;
+	unsigned int temp;
+	struct snd_emu8000 *hw;
+
+	hw = vp->hw;
+
+	/* set reverb and pitch target */
+	temp = vp->reg.parm.reverb;
+	temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10;
+	LIMITMAX(temp, 255);
+	temp = (temp << 8) | (vp->ptarget << 16) | vp->aaux;
+	EMU8000_PTRX_WRITE(hw, ch, temp);
+	EMU8000_CPF_WRITE(hw, ch, vp->ptarget << 16);
+	EMU8000_DCYSUSV_WRITE(hw, ch, vp->reg.parm.voldcysus);
+}
+
+/*
+ * reset voice parameters
+ */
+static void
+reset_voice(struct snd_emux *emu, int ch)
+{
+	struct snd_emu8000 *hw;
+
+	hw = emu->hw;
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);
+	snd_emu8000_tweak_voice(hw, ch);
+}
+
+/*
+ * Set the pitch of a possibly playing note.
+ */
+static void
+set_pitch(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+	EMU8000_IP_WRITE(hw, vp->ch, vp->apitch);
+}
+
+/*
+ * Set the volume of a possibly already playing note
+ */
+static void
+set_volume(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+	int  ifatn;
+
+	ifatn = (unsigned char)vp->acutoff;
+	ifatn = (ifatn << 8);
+	ifatn |= (unsigned char)vp->avol;
+	EMU8000_IFATN_WRITE(hw, vp->ch, ifatn);
+}
+
+/*
+ * Set pan and loop start address.
+ */
+static void
+set_pan(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+	unsigned int temp;
+
+	temp = ((unsigned int)vp->apan<<24) | ((unsigned int)vp->reg.loopstart - 1);
+	EMU8000_PSST_WRITE(hw, vp->ch, temp);
+}
+
+#define MOD_SENSE 18
+
+static void
+set_fmmod(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+	unsigned short fmmod;
+	short pitch;
+	unsigned char cutoff;
+	int modulation;
+
+	pitch = (char)(vp->reg.parm.fmmod>>8);
+	cutoff = (vp->reg.parm.fmmod & 0xff);
+	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+	pitch += (MOD_SENSE * modulation) / 1200;
+	LIMITVALUE(pitch, -128, 127);
+	fmmod = ((unsigned char)pitch<<8) | cutoff;
+	EMU8000_FMMOD_WRITE(hw, vp->ch, fmmod);
+}
+
+/* set tremolo (lfo1) volume & frequency */
+static void
+set_tremfreq(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+	EMU8000_TREMFRQ_WRITE(hw, vp->ch, vp->reg.parm.tremfrq);
+}
+
+/* set lfo2 pitch & frequency */
+static void
+set_fm2frq2(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+	unsigned short fm2frq2;
+	short pitch;
+	unsigned char freq;
+	int modulation;
+
+	pitch = (char)(vp->reg.parm.fm2frq2>>8);
+	freq = vp->reg.parm.fm2frq2 & 0xff;
+	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+	pitch += (MOD_SENSE * modulation) / 1200;
+	LIMITVALUE(pitch, -128, 127);
+	fm2frq2 = ((unsigned char)pitch<<8) | freq;
+	EMU8000_FM2FRQ2_WRITE(hw, vp->ch, fm2frq2);
+}
+
+/* set filterQ */
+static void
+set_filterQ(struct snd_emu8000 *hw, struct snd_emux_voice *vp)
+{
+	unsigned int addr;
+	addr = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff;
+	addr |= (vp->reg.parm.filterQ << 28);
+	EMU8000_CCCA_WRITE(hw, vp->ch, addr);
+}
+
+/*
+ * set the envelope & LFO parameters to the default values
+ */
+static void
+snd_emu8000_tweak_voice(struct snd_emu8000 *emu, int i)
+{
+	/* set all mod/vol envelope shape to minimum */
+	EMU8000_ENVVOL_WRITE(emu, i, 0x8000);
+	EMU8000_ENVVAL_WRITE(emu, i, 0x8000);
+	EMU8000_DCYSUS_WRITE(emu, i, 0x7F7F);
+	EMU8000_ATKHLDV_WRITE(emu, i, 0x7F7F);
+	EMU8000_ATKHLD_WRITE(emu, i, 0x7F7F);
+	EMU8000_PEFE_WRITE(emu, i, 0);  /* mod envelope height to zero */
+	EMU8000_LFO1VAL_WRITE(emu, i, 0x8000); /* no delay for LFO1 */
+	EMU8000_LFO2VAL_WRITE(emu, i, 0x8000);
+	EMU8000_IP_WRITE(emu, i, 0xE000);	/* no pitch shift */
+	EMU8000_IFATN_WRITE(emu, i, 0xFF00);	/* volume to minimum */
+	EMU8000_FMMOD_WRITE(emu, i, 0);
+	EMU8000_TREMFRQ_WRITE(emu, i, 0);
+	EMU8000_FM2FRQ2_WRITE(emu, i, 0);
+}
+
+/*
+ * sysex callback
+ */
+static void
+sysex(struct snd_emux *emu, char *buf, int len, int parsed, struct snd_midi_channel_set *chset)
+{
+	struct snd_emu8000 *hw;
+
+	hw = emu->hw;
+
+	switch (parsed) {
+	case SNDRV_MIDI_SYSEX_GS_CHORUS_MODE:
+		hw->chorus_mode = chset->gs_chorus_mode;
+		snd_emu8000_update_chorus_mode(hw);
+		break;
+
+	case SNDRV_MIDI_SYSEX_GS_REVERB_MODE:
+		hw->reverb_mode = chset->gs_reverb_mode;
+		snd_emu8000_update_reverb_mode(hw);
+		break;
+	}
+}
+
+
+#ifdef CONFIG_SND_SEQUENCER_OSS
+/*
+ * OSS ioctl callback
+ */
+static int
+oss_ioctl(struct snd_emux *emu, int cmd, int p1, int p2)
+{
+	struct snd_emu8000 *hw;
+
+	hw = emu->hw;
+
+	switch (cmd) {
+	case _EMUX_OSS_REVERB_MODE:
+		hw->reverb_mode = p1;
+		snd_emu8000_update_reverb_mode(hw);
+		break;
+
+	case _EMUX_OSS_CHORUS_MODE:
+		hw->chorus_mode = p1;
+		snd_emu8000_update_chorus_mode(hw);
+		break;
+
+	case _EMUX_OSS_INITIALIZE_CHIP:
+		/* snd_emu8000_init(hw); */ /*ignored*/
+		break;
+
+	case _EMUX_OSS_EQUALIZER:
+		hw->bass_level = p1;
+		hw->treble_level = p2;
+		snd_emu8000_update_equalizer(hw);
+		break;
+	}
+	return 0;
+}
+#endif
+
+
+/*
+ * additional patch keys
+ */
+
+#define SNDRV_EMU8000_LOAD_CHORUS_FX	0x10	/* optarg=mode */
+#define SNDRV_EMU8000_LOAD_REVERB_FX	0x11	/* optarg=mode */
+
+
+/*
+ * callback routine
+ */
+
+static int
+load_fx(struct snd_emux *emu, int type, int mode, const void __user *buf, long len)
+{
+	struct snd_emu8000 *hw;
+	hw = emu->hw;
+
+	/* skip header */
+	buf += 16;
+	len -= 16;
+
+	switch (type) {
+	case SNDRV_EMU8000_LOAD_CHORUS_FX:
+		return snd_emu8000_load_chorus_fx(hw, mode, buf, len);
+	case SNDRV_EMU8000_LOAD_REVERB_FX:
+		return snd_emu8000_load_reverb_fx(hw, mode, buf, len);
+	}
+	return -EINVAL;
+}
+
diff --git a/linux/sound/isa/sb/emu8000_local.h b/linux/sound/isa/sb/emu8000_local.h
new file mode 100644
index 0000000..7e87c34
--- /dev/null
+++ b/linux/sound/isa/sb/emu8000_local.h
@@ -0,0 +1,45 @@
+#ifndef __EMU8000_LOCAL_H
+#define __EMU8000_LOCAL_H
+/*
+ *  Local defininitons for the emu8000 (AWE32/64)
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/emu8000.h>
+#include <sound/emu8000_reg.h>
+
+/* emu8000_patch.c */
+int snd_emu8000_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp,
+			   struct snd_util_memhdr *hdr,
+			   const void __user *data, long count);
+int snd_emu8000_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
+			    struct snd_util_memhdr *hdr);
+void snd_emu8000_sample_reset(struct snd_emux *rec);
+
+/* emu8000_callback.c */
+void snd_emu8000_ops_setup(struct snd_emu8000 *emu);
+
+/* emu8000_pcm.c */
+int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index);
+
+#endif	/* __EMU8000_LOCAL_H */
diff --git a/linux/sound/isa/sb/emu8000_patch.c b/linux/sound/isa/sb/emu8000_patch.c
new file mode 100644
index 0000000..c99c607
--- /dev/null
+++ b/linux/sound/isa/sb/emu8000_patch.c
@@ -0,0 +1,305 @@
+/*
+ *  Patch routines for the emu8000 (AWE32/64)
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "emu8000_local.h"
+#include <asm/uaccess.h>
+#include <linux/moduleparam.h>
+
+static int emu8000_reset_addr;
+module_param(emu8000_reset_addr, int, 0444);
+MODULE_PARM_DESC(emu8000_reset_addr, "reset write address at each time (makes slowdown)");
+
+
+/*
+ * Open up channels.
+ */
+static int
+snd_emu8000_open_dma(struct snd_emu8000 *emu, int write)
+{
+	int i;
+
+	/* reserve all 30 voices for loading */
+	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
+		snd_emux_lock_voice(emu->emu, i);
+		snd_emu8000_dma_chan(emu, i, write);
+	}
+
+	/* assign voice 31 and 32 to ROM */
+	EMU8000_VTFT_WRITE(emu, 30, 0);
+	EMU8000_PSST_WRITE(emu, 30, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 30, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
+	EMU8000_VTFT_WRITE(emu, 31, 0);
+	EMU8000_PSST_WRITE(emu, 31, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 31, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
+
+	return 0;
+}
+
+/*
+ * Close all dram channels.
+ */
+static void
+snd_emu8000_close_dma(struct snd_emu8000 *emu)
+{
+	int i;
+
+	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
+		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
+		snd_emux_unlock_voice(emu->emu, i);
+	}
+}
+
+/*
+ */
+
+#define BLANK_LOOP_START	4
+#define BLANK_LOOP_END		8
+#define BLANK_LOOP_SIZE		12
+#define BLANK_HEAD_SIZE		48
+
+/*
+ * Read a word from userland, taking care of conversions from
+ * 8bit samples etc.
+ */
+static unsigned short
+read_word(const void __user *buf, int offset, int mode)
+{
+	unsigned short c;
+	if (mode & SNDRV_SFNT_SAMPLE_8BITS) {
+		unsigned char cc;
+		get_user(cc, (unsigned char __user *)buf + offset);
+		c = cc << 8; /* convert 8bit -> 16bit */
+	} else {
+#ifdef SNDRV_LITTLE_ENDIAN
+		get_user(c, (unsigned short __user *)buf + offset);
+#else
+		unsigned short cc;
+		get_user(cc, (unsigned short __user *)buf + offset);
+		c = swab16(cc);
+#endif
+	}
+	if (mode & SNDRV_SFNT_SAMPLE_UNSIGNED)
+		c ^= 0x8000; /* unsigned -> signed */
+	return c;
+}
+
+/*
+ */
+static void
+snd_emu8000_write_wait(struct snd_emu8000 *emu)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		schedule_timeout_interruptible(1);
+		if (signal_pending(current))
+			break;
+	}
+}
+
+/*
+ * write sample word data
+ *
+ * You should not have to keep resetting the address each time
+ * as the chip is supposed to step on the next address automatically.
+ * It mostly does, but during writes of some samples at random it
+ * completely loses words (every one in 16 roughly but with no
+ * obvious pattern).
+ *
+ * This is therefore much slower than need be, but is at least
+ * working.
+ */
+static inline void
+write_word(struct snd_emu8000 *emu, int *offset, unsigned short data)
+{
+	if (emu8000_reset_addr) {
+		if (emu8000_reset_addr > 1)
+			snd_emu8000_write_wait(emu);
+		EMU8000_SMALW_WRITE(emu, *offset);
+	}
+	EMU8000_SMLD_WRITE(emu, data);
+	*offset += 1;
+}
+
+/*
+ * Write the sample to EMU800 memory.  This routine is invoked out of
+ * the generic soundfont routines as a callback.
+ */
+int
+snd_emu8000_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp,
+		       struct snd_util_memhdr *hdr,
+		       const void __user *data, long count)
+{
+	int  i;
+	int  rc;
+	int  offset;
+	int  truesize;
+	int  dram_offset, dram_start;
+	struct snd_emu8000 *emu;
+
+	emu = rec->hw;
+	if (snd_BUG_ON(!sp))
+		return -EINVAL;
+
+	if (sp->v.size == 0)
+		return 0;
+
+	/* be sure loop points start < end */
+	if (sp->v.loopstart > sp->v.loopend) {
+		int tmp = sp->v.loopstart;
+		sp->v.loopstart = sp->v.loopend;
+		sp->v.loopend = tmp;
+	}
+
+	/* compute true data size to be loaded */
+	truesize = sp->v.size;
+	if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
+		truesize += sp->v.loopend - sp->v.loopstart;
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
+		truesize += BLANK_LOOP_SIZE;
+
+	sp->block = snd_util_mem_alloc(hdr, truesize * 2);
+	if (sp->block == NULL) {
+		/*snd_printd("EMU8000: out of memory\n");*/
+		/* not ENOMEM (for compatibility) */
+		return -ENOSPC;
+	}
+
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS) {
+		if (!access_ok(VERIFY_READ, data, sp->v.size))
+			return -EFAULT;
+	} else {
+		if (!access_ok(VERIFY_READ, data, sp->v.size * 2))
+			return -EFAULT;
+	}
+
+	/* recalculate address offset */
+	sp->v.end -= sp->v.start;
+	sp->v.loopstart -= sp->v.start;
+	sp->v.loopend -= sp->v.start;
+	sp->v.start = 0;
+
+	/* dram position (in word) -- mem_offset is byte */
+	dram_offset = EMU8000_DRAM_OFFSET + (sp->block->offset >> 1);
+	dram_start = dram_offset;
+
+	/* set the total size (store onto obsolete checksum value) */
+	sp->v.truesize = truesize * 2; /* in bytes */
+
+	snd_emux_terminate_all(emu->emu);
+	if ((rc = snd_emu8000_open_dma(emu, EMU8000_RAM_WRITE)) != 0)
+		return rc;
+
+	/* Set the address to start writing at */
+	snd_emu8000_write_wait(emu);
+	EMU8000_SMALW_WRITE(emu, dram_offset);
+
+	/*snd_emu8000_init_fm(emu);*/
+
+#if 0
+	/* first block - write 48 samples for silence */
+	if (! sp->block->offset) {
+		for (i = 0; i < BLANK_HEAD_SIZE; i++) {
+			write_word(emu, &dram_offset, 0);
+		}
+	}
+#endif
+
+	offset = 0;
+	for (i = 0; i < sp->v.size; i++) {
+		unsigned short s;
+
+		s = read_word(data, offset, sp->v.mode_flags);
+		offset++;
+		write_word(emu, &dram_offset, s);
+
+		/* we may take too long time in this loop.
+		 * so give controls back to kernel if needed.
+		 */
+		cond_resched();
+
+		if (i == sp->v.loopend &&
+		    (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)))
+		{
+			int looplen = sp->v.loopend - sp->v.loopstart;
+			int k;
+
+			/* copy reverse loop */
+			for (k = 1; k <= looplen; k++) {
+				s = read_word(data, offset - k, sp->v.mode_flags);
+				write_word(emu, &dram_offset, s);
+			}
+			if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
+				sp->v.loopend += looplen;
+			} else {
+				sp->v.loopstart += looplen;
+				sp->v.loopend += looplen;
+			}
+			sp->v.end += looplen;
+		}
+	}
+
+	/* if no blank loop is attached in the sample, add it */
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
+		for (i = 0; i < BLANK_LOOP_SIZE; i++) {
+			write_word(emu, &dram_offset, 0);
+		}
+		if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) {
+			sp->v.loopstart = sp->v.end + BLANK_LOOP_START;
+			sp->v.loopend = sp->v.end + BLANK_LOOP_END;
+		}
+	}
+
+	/* add dram offset */
+	sp->v.start += dram_start;
+	sp->v.end += dram_start;
+	sp->v.loopstart += dram_start;
+	sp->v.loopend += dram_start;
+
+	snd_emu8000_close_dma(emu);
+	snd_emu8000_init_fm(emu);
+
+	return 0;
+}
+
+/*
+ * free a sample block
+ */
+int
+snd_emu8000_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
+			struct snd_util_memhdr *hdr)
+{
+	if (sp->block) {
+		snd_util_mem_free(hdr, sp->block);
+		sp->block = NULL;
+	}
+	return 0;
+}
+
+
+/*
+ * sample_reset callback - terminate voices
+ */
+void
+snd_emu8000_sample_reset(struct snd_emux *rec)
+{
+	snd_emux_terminate_all(rec);
+}
diff --git a/linux/sound/isa/sb/emu8000_pcm.c b/linux/sound/isa/sb/emu8000_pcm.c
new file mode 100644
index 0000000..2f85c66
--- /dev/null
+++ b/linux/sound/isa/sb/emu8000_pcm.c
@@ -0,0 +1,705 @@
+/*
+ * pcm emulation on emu8000 wavetable
+ *
+ *  Copyright (C) 2002 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "emu8000_local.h"
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+/*
+ * define the following if you want to use this pcm with non-interleaved mode
+ */
+/* #define USE_NONINTERLEAVE */
+
+/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set
+ * mmap_emulation flag to 1 in your .asoundrc, such like
+ *
+ *	pcm.emu8k {
+ *		type plug
+ *		slave.pcm {
+ *			type hw
+ *			card 0
+ *			device 1
+ *			mmap_emulation 1
+ *		}
+ *	}
+ *
+ * besides, for the time being, the non-interleaved mode doesn't work well on
+ * alsa-lib...
+ */
+
+
+struct snd_emu8k_pcm {
+	struct snd_emu8000 *emu;
+	struct snd_pcm_substream *substream;
+
+	unsigned int allocated_bytes;
+	struct snd_util_memblk *block;
+	unsigned int offset;
+	unsigned int buf_size;
+	unsigned int period_size;
+	unsigned int loop_start[2];
+	unsigned int pitch;
+	int panning[2];
+	int last_ptr;
+	int period_pos;
+	int voices;
+	unsigned int dram_opened: 1;
+	unsigned int running: 1;
+	unsigned int timer_running: 1;
+	struct timer_list timer;
+	spinlock_t timer_lock;
+};
+
+#define LOOP_BLANK_SIZE		8
+
+
+/*
+ * open up channels for the simultaneous data transfer and playback
+ */
+static int
+emu8k_open_dram_for_pcm(struct snd_emu8000 *emu, int channels)
+{
+	int i;
+
+	/* reserve up to 2 voices for playback */
+	snd_emux_lock_voice(emu->emu, 0);
+	if (channels > 1)
+		snd_emux_lock_voice(emu->emu, 1);
+
+	/* reserve 28 voices for loading */
+	for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) {
+		unsigned int mode = EMU8000_RAM_WRITE;
+		snd_emux_lock_voice(emu->emu, i);
+#ifndef USE_NONINTERLEAVE
+		if (channels > 1 && (i & 1) != 0)
+			mode |= EMU8000_RAM_RIGHT;
+#endif
+		snd_emu8000_dma_chan(emu, i, mode);
+	}
+
+	/* assign voice 31 and 32 to ROM */
+	EMU8000_VTFT_WRITE(emu, 30, 0);
+	EMU8000_PSST_WRITE(emu, 30, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 30, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
+	EMU8000_VTFT_WRITE(emu, 31, 0);
+	EMU8000_PSST_WRITE(emu, 31, 0x1d8);
+	EMU8000_CSL_WRITE(emu, 31, 0x1e0);
+	EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
+
+	return 0;
+}
+
+/*
+ */
+static void
+snd_emu8000_write_wait(struct snd_emu8000 *emu, int can_schedule)
+{
+	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
+		if (can_schedule) {
+			schedule_timeout_interruptible(1);
+			if (signal_pending(current))
+				break;
+		}
+	}
+}
+
+/*
+ * close all channels
+ */
+static void
+emu8k_close_dram(struct snd_emu8000 *emu)
+{
+	int i;
+
+	for (i = 0; i < 2; i++)
+		snd_emux_unlock_voice(emu->emu, i);
+	for (; i < EMU8000_DRAM_VOICES; i++) {
+		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
+		snd_emux_unlock_voice(emu->emu, i);
+	}
+}
+
+/*
+ * convert Hz to AWE32 rate offset (see emux/soundfont.c)
+ */
+
+#define OFFSET_SAMPLERATE	1011119		/* base = 44100 */
+#define SAMPLERATE_RATIO	4096
+
+static int calc_rate_offset(int hz)
+{
+	return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO);
+}
+
+
+/*
+ */
+
+static struct snd_pcm_hardware emu8k_pcm_hw = {
+#ifdef USE_NONINTERLEAVE
+	.info =			SNDRV_PCM_INFO_NONINTERLEAVED,
+#else
+	.info =			SNDRV_PCM_INFO_INTERLEAVED,
+#endif
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	1024,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =		0,
+
+};
+
+/*
+ * get the current position at the given channel from CCCA register
+ */
+static inline int emu8k_get_curpos(struct snd_emu8k_pcm *rec, int ch)
+{
+	int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff;
+	val -= rec->loop_start[ch] - 1;
+	return val;
+}
+
+
+/*
+ * timer interrupt handler
+ * check the current position and update the period if necessary.
+ */
+static void emu8k_pcm_timer_func(unsigned long data)
+{
+	struct snd_emu8k_pcm *rec = (struct snd_emu8k_pcm *)data;
+	int ptr, delta;
+
+	spin_lock(&rec->timer_lock);
+	/* update the current pointer */
+	ptr = emu8k_get_curpos(rec, 0);
+	if (ptr < rec->last_ptr)
+		delta = ptr + rec->buf_size - rec->last_ptr;
+	else
+		delta = ptr - rec->last_ptr;
+	rec->period_pos += delta;
+	rec->last_ptr = ptr;
+
+	/* reprogram timer */
+	rec->timer.expires = jiffies + 1;
+	add_timer(&rec->timer);
+
+	/* update period */
+	if (rec->period_pos >= (int)rec->period_size) {
+		rec->period_pos %= rec->period_size;
+		spin_unlock(&rec->timer_lock);
+		snd_pcm_period_elapsed(rec->substream);
+		return;
+	}
+	spin_unlock(&rec->timer_lock);
+}
+
+
+/*
+ * open pcm
+ * creating an instance here
+ */
+static int emu8k_pcm_open(struct snd_pcm_substream *subs)
+{
+	struct snd_emu8000 *emu = snd_pcm_substream_chip(subs);
+	struct snd_emu8k_pcm *rec;
+	struct snd_pcm_runtime *runtime = subs->runtime;
+
+	rec = kzalloc(sizeof(*rec), GFP_KERNEL);
+	if (! rec)
+		return -ENOMEM;
+
+	rec->emu = emu;
+	rec->substream = subs;
+	runtime->private_data = rec;
+
+	spin_lock_init(&rec->timer_lock);
+	init_timer(&rec->timer);
+	rec->timer.function = emu8k_pcm_timer_func;
+	rec->timer.data = (unsigned long)rec;
+
+	runtime->hw = emu8k_pcm_hw;
+	runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3;
+	runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2;
+
+	/* use timer to update periods.. (specified in msec) */
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+				     (1000000 + HZ - 1) / HZ, UINT_MAX);
+
+	return 0;
+}
+
+static int emu8k_pcm_close(struct snd_pcm_substream *subs)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+	kfree(rec);
+	subs->runtime->private_data = NULL;
+	return 0;
+}
+
+/*
+ * calculate pitch target
+ */
+static int calc_pitch_target(int pitch)
+{
+	int ptarget = 1 << (pitch >> 12);
+	if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710;
+	if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710;
+	if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710;
+	ptarget += (ptarget >> 1);
+	if (ptarget > 0xffff) ptarget = 0xffff;
+	return ptarget;
+}
+
+/*
+ * set up the voice
+ */
+static void setup_voice(struct snd_emu8k_pcm *rec, int ch)
+{
+	struct snd_emu8000 *hw = rec->emu;
+	unsigned int temp;
+
+	/* channel to be silent and idle */
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);
+	EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);
+	EMU8000_PTRX_WRITE(hw, ch, 0);
+	EMU8000_CPF_WRITE(hw, ch, 0);
+
+	/* pitch offset */
+	EMU8000_IP_WRITE(hw, ch, rec->pitch);
+	/* set envelope parameters */
+	EMU8000_ENVVAL_WRITE(hw, ch, 0x8000);
+	EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f);
+	EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f);
+	EMU8000_ENVVOL_WRITE(hw, ch, 0x8000);
+	EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f);
+	/* decay/sustain parameter for volume envelope is used
+	   for triggerg the voice */
+	/* modulation envelope heights */
+	EMU8000_PEFE_WRITE(hw, ch, 0x0);
+	/* lfo1/2 delay */
+	EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000);
+	EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000);
+	/* lfo1 pitch & cutoff shift */
+	EMU8000_FMMOD_WRITE(hw, ch, 0);
+	/* lfo1 volume & freq */
+	EMU8000_TREMFRQ_WRITE(hw, ch, 0);
+	/* lfo2 pitch & freq */
+	EMU8000_FM2FRQ2_WRITE(hw, ch, 0);
+	/* pan & loop start */
+	temp = rec->panning[ch];
+	temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1);
+	EMU8000_PSST_WRITE(hw, ch, temp);
+	/* chorus & loop end (chorus 8bit, MSB) */
+	temp = 0; // chorus
+	temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1);
+	EMU8000_CSL_WRITE(hw, ch, temp);
+	/* Q & current address (Q 4bit value, MSB) */
+	temp = 0; // filterQ
+	temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1);
+	EMU8000_CCCA_WRITE(hw, ch, temp);
+	/* clear unknown registers */
+	EMU8000_00A0_WRITE(hw, ch, 0);
+	EMU8000_0080_WRITE(hw, ch, 0);
+}
+
+/*
+ * trigger the voice
+ */
+static void start_voice(struct snd_emu8k_pcm *rec, int ch)
+{
+	unsigned long flags;
+	struct snd_emu8000 *hw = rec->emu;
+	unsigned int temp, aux;
+	int pt = calc_pitch_target(rec->pitch);
+
+	/* cutoff and volume */
+	EMU8000_IFATN_WRITE(hw, ch, 0xff00);
+	EMU8000_VTFT_WRITE(hw, ch, 0xffff);
+	EMU8000_CVCF_WRITE(hw, ch, 0xffff);
+	/* trigger envelope */
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f);
+	/* set reverb and pitch target */
+	temp = 0; // reverb
+	if (rec->panning[ch] == 0)
+		aux = 0xff;
+	else
+		aux = (-rec->panning[ch]) & 0xff;
+	temp = (temp << 8) | (pt << 16) | aux;
+	EMU8000_PTRX_WRITE(hw, ch, temp);
+	EMU8000_CPF_WRITE(hw, ch, pt << 16);
+
+	/* start timer */
+	spin_lock_irqsave(&rec->timer_lock, flags);
+	if (! rec->timer_running) {
+		rec->timer.expires = jiffies + 1;
+		add_timer(&rec->timer);
+		rec->timer_running = 1;
+	}
+	spin_unlock_irqrestore(&rec->timer_lock, flags);
+}
+
+/*
+ * stop the voice immediately
+ */
+static void stop_voice(struct snd_emu8k_pcm *rec, int ch)
+{
+	unsigned long flags;
+	struct snd_emu8000 *hw = rec->emu;
+
+	EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);
+
+	/* stop timer */
+	spin_lock_irqsave(&rec->timer_lock, flags);
+	if (rec->timer_running) {
+		del_timer(&rec->timer);
+		rec->timer_running = 0;
+	}
+	spin_unlock_irqrestore(&rec->timer_lock, flags);
+}
+
+static int emu8k_pcm_trigger(struct snd_pcm_substream *subs, int cmd)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+	int ch;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		for (ch = 0; ch < rec->voices; ch++)
+			start_voice(rec, ch);
+		rec->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		rec->running = 0;
+		for (ch = 0; ch < rec->voices; ch++)
+			stop_voice(rec, ch);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/*
+ * copy / silence ops
+ */
+
+/*
+ * this macro should be inserted in the copy/silence loops
+ * to reduce the latency.  without this, the system will hang up
+ * during the whole loop.
+ */
+#define CHECK_SCHEDULER() \
+do { \
+	cond_resched();\
+	if (signal_pending(current))\
+		return -EAGAIN;\
+} while (0)
+
+
+#ifdef USE_NONINTERLEAVE
+/* copy one channel block */
+static int emu8k_transfer_block(struct snd_emu8000 *emu, int offset, unsigned short *buf, int count)
+{
+	EMU8000_SMALW_WRITE(emu, offset);
+	while (count > 0) {
+		unsigned short sval;
+		CHECK_SCHEDULER();
+		if (get_user(sval, buf))
+			return -EFAULT;
+		EMU8000_SMLD_WRITE(emu, sval);
+		buf++;
+		count--;
+	}
+	return 0;
+}
+
+static int emu8k_pcm_copy(struct snd_pcm_substream *subs,
+			  int voice,
+			  snd_pcm_uframes_t pos,
+			  void *src,
+			  snd_pcm_uframes_t count)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+	struct snd_emu8000 *emu = rec->emu;
+
+	snd_emu8000_write_wait(emu, 1);
+	if (voice == -1) {
+		unsigned short *buf = src;
+		int i, err;
+		count /= rec->voices;
+		for (i = 0; i < rec->voices; i++) {
+			err = emu8k_transfer_block(emu, pos + rec->loop_start[i], buf, count);
+			if (err < 0)
+				return err;
+			buf += count;
+		}
+		return 0;
+	} else {
+		return emu8k_transfer_block(emu, pos + rec->loop_start[voice], src, count);
+	}
+}
+
+/* make a channel block silence */
+static int emu8k_silence_block(struct snd_emu8000 *emu, int offset, int count)
+{
+	EMU8000_SMALW_WRITE(emu, offset);
+	while (count > 0) {
+		CHECK_SCHEDULER();
+		EMU8000_SMLD_WRITE(emu, 0);
+		count--;
+	}
+	return 0;
+}
+
+static int emu8k_pcm_silence(struct snd_pcm_substream *subs,
+			     int voice,
+			     snd_pcm_uframes_t pos,
+			     snd_pcm_uframes_t count)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+	struct snd_emu8000 *emu = rec->emu;
+
+	snd_emu8000_write_wait(emu, 1);
+	if (voice == -1 && rec->voices == 1)
+		voice = 0;
+	if (voice == -1) {
+		int err;
+		err = emu8k_silence_block(emu, pos + rec->loop_start[0], count / 2);
+		if (err < 0)
+			return err;
+		return emu8k_silence_block(emu, pos + rec->loop_start[1], count / 2);
+	} else {
+		return emu8k_silence_block(emu, pos + rec->loop_start[voice], count);
+	}
+}
+
+#else /* interleave */
+
+/*
+ * copy the interleaved data can be done easily by using
+ * DMA "left" and "right" channels on emu8k engine.
+ */
+static int emu8k_pcm_copy(struct snd_pcm_substream *subs,
+			  int voice,
+			  snd_pcm_uframes_t pos,
+			  void __user *src,
+			  snd_pcm_uframes_t count)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+	struct snd_emu8000 *emu = rec->emu;
+	unsigned short __user *buf = src;
+
+	snd_emu8000_write_wait(emu, 1);
+	EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]);
+	if (rec->voices > 1)
+		EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]);
+
+	while (count-- > 0) {
+		unsigned short sval;
+		CHECK_SCHEDULER();
+		if (get_user(sval, buf))
+			return -EFAULT;
+		EMU8000_SMLD_WRITE(emu, sval);
+		buf++;
+		if (rec->voices > 1) {
+			CHECK_SCHEDULER();
+			if (get_user(sval, buf))
+				return -EFAULT;
+			EMU8000_SMRD_WRITE(emu, sval);
+			buf++;
+		}
+	}
+	return 0;
+}
+
+static int emu8k_pcm_silence(struct snd_pcm_substream *subs,
+			     int voice,
+			     snd_pcm_uframes_t pos,
+			     snd_pcm_uframes_t count)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+	struct snd_emu8000 *emu = rec->emu;
+
+	snd_emu8000_write_wait(emu, 1);
+	EMU8000_SMALW_WRITE(emu, rec->loop_start[0] + pos);
+	if (rec->voices > 1)
+		EMU8000_SMARW_WRITE(emu, rec->loop_start[1] + pos);
+	while (count-- > 0) {
+		CHECK_SCHEDULER();
+		EMU8000_SMLD_WRITE(emu, 0);
+		if (rec->voices > 1) {
+			CHECK_SCHEDULER();
+			EMU8000_SMRD_WRITE(emu, 0);
+		}
+	}
+	return 0;
+}
+#endif
+
+
+/*
+ * allocate a memory block
+ */
+static int emu8k_pcm_hw_params(struct snd_pcm_substream *subs,
+			       struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+	if (rec->block) {
+		/* reallocation - release the old block */
+		snd_util_mem_free(rec->emu->memhdr, rec->block);
+		rec->block = NULL;
+	}
+
+	rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4;
+	rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes);
+	if (! rec->block)
+		return -ENOMEM;
+	rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */
+	/* at least dma_bytes must be set for non-interleaved mode */
+	subs->dma_buffer.bytes = params_buffer_bytes(hw_params);
+
+	return 0;
+}
+
+/*
+ * free the memory block
+ */
+static int emu8k_pcm_hw_free(struct snd_pcm_substream *subs)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+	if (rec->block) {
+		int ch;
+		for (ch = 0; ch < rec->voices; ch++)
+			stop_voice(rec, ch); // to be sure
+		if (rec->dram_opened)
+			emu8k_close_dram(rec->emu);
+		snd_util_mem_free(rec->emu->memhdr, rec->block);
+		rec->block = NULL;
+	}
+	return 0;
+}
+
+/*
+ */
+static int emu8k_pcm_prepare(struct snd_pcm_substream *subs)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+
+	rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate);
+	rec->last_ptr = 0;
+	rec->period_pos = 0;
+
+	rec->buf_size = subs->runtime->buffer_size;
+	rec->period_size = subs->runtime->period_size;
+	rec->voices = subs->runtime->channels;
+	rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE;
+	if (rec->voices > 1)
+		rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE;
+	if (rec->voices > 1) {
+		rec->panning[0] = 0xff;
+		rec->panning[1] = 0x00;
+	} else
+		rec->panning[0] = 0x80;
+
+	if (! rec->dram_opened) {
+		int err, i, ch;
+
+		snd_emux_terminate_all(rec->emu->emu);
+		if ((err = emu8k_open_dram_for_pcm(rec->emu, rec->voices)) != 0)
+			return err;
+		rec->dram_opened = 1;
+
+		/* clear loop blanks */
+		snd_emu8000_write_wait(rec->emu, 0);
+		EMU8000_SMALW_WRITE(rec->emu, rec->offset);
+		for (i = 0; i < LOOP_BLANK_SIZE; i++)
+			EMU8000_SMLD_WRITE(rec->emu, 0);
+		for (ch = 0; ch < rec->voices; ch++) {
+			EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size);
+			for (i = 0; i < LOOP_BLANK_SIZE; i++)
+				EMU8000_SMLD_WRITE(rec->emu, 0);
+		}
+	}
+
+	setup_voice(rec, 0);
+	if (rec->voices > 1)
+		setup_voice(rec, 1);
+	return 0;
+}
+
+static snd_pcm_uframes_t emu8k_pcm_pointer(struct snd_pcm_substream *subs)
+{
+	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
+	if (rec->running)
+		return emu8k_get_curpos(rec, 0);
+	return 0;
+}
+
+
+static struct snd_pcm_ops emu8k_pcm_ops = {
+	.open =		emu8k_pcm_open,
+	.close =	emu8k_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	emu8k_pcm_hw_params,
+	.hw_free =	emu8k_pcm_hw_free,
+	.prepare =	emu8k_pcm_prepare,
+	.trigger =	emu8k_pcm_trigger,
+	.pointer =	emu8k_pcm_pointer,
+	.copy =		emu8k_pcm_copy,
+	.silence =	emu8k_pcm_silence,
+};
+
+
+static void snd_emu8000_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_emu8000 *emu = pcm->private_data;
+	emu->pcm = NULL;
+}
+
+int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm)) < 0)
+		return err;
+	pcm->private_data = emu;
+	pcm->private_free = snd_emu8000_pcm_free;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops);
+	emu->pcm = pcm;
+
+	snd_device_register(card, pcm);
+
+	return 0;
+}
diff --git a/linux/sound/isa/sb/emu8000_synth.c b/linux/sound/isa/sb/emu8000_synth.c
new file mode 100644
index 0000000..0c7905c
--- /dev/null
+++ b/linux/sound/isa/sb/emu8000_synth.c
@@ -0,0 +1,135 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *     and (c) 1999 Steve Ratcliffe <steve@parabola.demon.co.uk>
+ *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Emu8000 synth plug-in routine
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "emu8000_local.h"
+#include <linux/init.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Takashi Iwai, Steve Ratcliffe");
+MODULE_DESCRIPTION("Emu8000 synth plug-in routine");
+MODULE_LICENSE("GPL");
+
+/*----------------------------------------------------------------*/
+
+/*
+ * create a new hardware dependent device for Emu8000
+ */
+static int snd_emu8000_new_device(struct snd_seq_device *dev)
+{
+	struct snd_emu8000 *hw;
+	struct snd_emux *emu;
+
+	hw = *(struct snd_emu8000**)SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (hw == NULL)
+		return -EINVAL;
+
+	if (hw->emu)
+		return -EBUSY; /* already exists..? */
+
+	if (snd_emux_new(&emu) < 0)
+		return -ENOMEM;
+
+	hw->emu = emu;
+	snd_emu8000_ops_setup(hw);
+
+	emu->hw = hw;
+	emu->max_voices = EMU8000_DRAM_VOICES;
+	emu->num_ports = hw->seq_ports;
+
+	if (hw->memhdr) {
+		snd_printk(KERN_ERR "memhdr is already initialized!?\n");
+		snd_util_memhdr_free(hw->memhdr);
+	}
+	hw->memhdr = snd_util_memhdr_new(hw->mem_size);
+	if (hw->memhdr == NULL) {
+		snd_emux_free(emu);
+		hw->emu = NULL;
+		return -ENOMEM;
+	}
+
+	emu->memhdr = hw->memhdr;
+	emu->midi_ports = hw->seq_ports < 2 ? hw->seq_ports : 2; /* number of virmidi ports */
+	emu->midi_devidx = 1;
+	emu->linear_panning = 1;
+	emu->hwdep_idx = 2; /* FIXED */
+
+	if (snd_emux_register(emu, dev->card, hw->index, "Emu8000") < 0) {
+		snd_emux_free(emu);
+		snd_util_memhdr_free(hw->memhdr);
+		hw->emu = NULL;
+		hw->memhdr = NULL;
+		return -ENOMEM;
+	}
+
+	if (hw->mem_size > 0)
+		snd_emu8000_pcm_new(dev->card, hw, 1);
+
+	dev->driver_data = hw;
+
+	return 0;
+}
+
+
+/*
+ * free all resources
+ */
+static int snd_emu8000_delete_device(struct snd_seq_device *dev)
+{
+	struct snd_emu8000 *hw;
+
+	if (dev->driver_data == NULL)
+		return 0; /* no synth was allocated actually */
+
+	hw = dev->driver_data;
+	if (hw->pcm)
+		snd_device_free(dev->card, hw->pcm);
+	if (hw->emu)
+		snd_emux_free(hw->emu);
+	if (hw->memhdr)
+		snd_util_memhdr_free(hw->memhdr);
+	hw->emu = NULL;
+	hw->memhdr = NULL;
+	return 0;
+}
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_emu8000_init(void)
+{
+	
+	static struct snd_seq_dev_ops ops = {
+		snd_emu8000_new_device,
+		snd_emu8000_delete_device,
+	};
+	return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_EMU8000, &ops,
+					      sizeof(struct snd_emu8000*));
+}
+
+static void __exit alsa_emu8000_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_EMU8000);
+}
+
+module_init(alsa_emu8000_init)
+module_exit(alsa_emu8000_exit)
diff --git a/linux/sound/isa/sb/jazz16.c b/linux/sound/isa/sb/jazz16.c
new file mode 100644
index 0000000..8ccbcdd
--- /dev/null
+++ b/linux/sound/isa/sb/jazz16.c
@@ -0,0 +1,405 @@
+
+/*
+ * jazz16.c - driver for Media Vision Jazz16 based soundcards.
+ * Copyright (C) 2009 Krzysztof Helt <krzysztof.h1@wp.pl>
+ * Based on patches posted by Rask Ingemann Lambertsen and Rene Herman.
+ * Based on OSS Sound Blaster driver.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <asm/dma.h>
+#include <linux/isa.h>
+#include <sound/core.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/sb.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#define PFX "jazz16: "
+
+MODULE_DESCRIPTION("Media Vision Jazz16");
+MODULE_SUPPORTED_DEVICE("{{Media Vision ??? },"
+		"{RTL,RTL3000}}");
+
+MODULE_AUTHOR("Krzysztof Helt <krzysztof.h1@wp.pl>");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static unsigned long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static unsigned long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Media Vision Jazz16 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Media Vision Jazz16 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Media Vision Jazz16 based soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for jazz16 driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for jazz16 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for jazz16 driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for jazz16 driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "DMA8 # for jazz16 driver.");
+module_param_array(dma16, int, NULL, 0444);
+MODULE_PARM_DESC(dma16, "DMA16 # for jazz16 driver.");
+
+#define SB_JAZZ16_WAKEUP	0xaf
+#define SB_JAZZ16_SET_PORTS	0x50
+#define SB_DSP_GET_JAZZ_BRD_REV	0xfa
+#define SB_JAZZ16_SET_DMAINTR	0xfb
+#define SB_DSP_GET_JAZZ_MODEL	0xfe
+
+struct snd_card_jazz16 {
+	struct snd_sb *chip;
+};
+
+static irqreturn_t jazz16_interrupt(int irq, void *chip)
+{
+	return snd_sb8dsp_interrupt(chip);
+}
+
+static int __devinit jazz16_configure_ports(unsigned long port,
+					    unsigned long mpu_port, int idx)
+{
+	unsigned char val;
+
+	if (!request_region(0x201, 1, "jazz16 config")) {
+		snd_printk(KERN_ERR "config port region is already in use.\n");
+		return -EBUSY;
+	}
+	outb(SB_JAZZ16_WAKEUP - idx, 0x201);
+	udelay(100);
+	outb(SB_JAZZ16_SET_PORTS + idx, 0x201);
+	udelay(100);
+	val = port & 0x70;
+	val |= (mpu_port & 0x30) >> 4;
+	outb(val, 0x201);
+
+	release_region(0x201, 1);
+	return 0;
+}
+
+static int __devinit jazz16_detect_board(unsigned long port,
+					 unsigned long mpu_port)
+{
+	int err;
+	int val;
+	struct snd_sb chip;
+
+	if (!request_region(port, 0x10, "jazz16")) {
+		snd_printk(KERN_ERR "I/O port region is already in use.\n");
+		return -EBUSY;
+	}
+	/* just to call snd_sbdsp_command/reset/get_byte() */
+	chip.port = port;
+
+	err = snd_sbdsp_reset(&chip);
+	if (err < 0)
+		for (val = 0; val < 4; val++) {
+			err = jazz16_configure_ports(port, mpu_port, val);
+			if (err < 0)
+				break;
+
+			err = snd_sbdsp_reset(&chip);
+			if (!err)
+				break;
+		}
+	if (err < 0) {
+		err = -ENODEV;
+		goto err_unmap;
+	}
+	if (!snd_sbdsp_command(&chip, SB_DSP_GET_JAZZ_BRD_REV)) {
+		err = -EBUSY;
+		goto err_unmap;
+	}
+	val = snd_sbdsp_get_byte(&chip);
+	if (val >= 0x30)
+		snd_sbdsp_get_byte(&chip);
+
+	if ((val & 0xf0) != 0x10) {
+		err = -ENODEV;
+		goto err_unmap;
+	}
+	if (!snd_sbdsp_command(&chip, SB_DSP_GET_JAZZ_MODEL)) {
+		err = -EBUSY;
+		goto err_unmap;
+	}
+	snd_sbdsp_get_byte(&chip);
+	err = snd_sbdsp_get_byte(&chip);
+	snd_printd("Media Vision Jazz16 board detected: rev 0x%x, model 0x%x\n",
+		   val, err);
+
+	err = 0;
+
+err_unmap:
+	release_region(port, 0x10);
+	return err;
+}
+
+static int __devinit jazz16_configure_board(struct snd_sb *chip, int mpu_irq)
+{
+	static unsigned char jazz_irq_bits[] = { 0, 0, 2, 3, 0, 1, 0, 4,
+						 0, 2, 5, 0, 0, 0, 0, 6 };
+	static unsigned char jazz_dma_bits[] = { 0, 1, 0, 2, 0, 3, 0, 4 };
+
+	if (jazz_dma_bits[chip->dma8] == 0 ||
+	    jazz_dma_bits[chip->dma16] == 0 ||
+	    jazz_irq_bits[chip->irq] == 0)
+		return -EINVAL;
+
+	if (!snd_sbdsp_command(chip, SB_JAZZ16_SET_DMAINTR))
+		return -EBUSY;
+
+	if (!snd_sbdsp_command(chip,
+			       jazz_dma_bits[chip->dma8] |
+			       (jazz_dma_bits[chip->dma16] << 4)))
+		return -EBUSY;
+
+	if (!snd_sbdsp_command(chip,
+			       jazz_irq_bits[chip->irq] |
+			       (jazz_irq_bits[mpu_irq] << 4)))
+		return -EBUSY;
+
+	return 0;
+}
+
+static int __devinit snd_jazz16_match(struct device *devptr, unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR "please specify port\n");
+		return 0;
+	} else if (port[dev] == 0x200 || (port[dev] & ~0x270)) {
+		snd_printk(KERN_ERR "incorrect port specified\n");
+		return 0;
+	}
+	if (dma8[dev] != SNDRV_AUTO_DMA &&
+	    dma8[dev] != 1 && dma8[dev] != 3) {
+		snd_printk(KERN_ERR "dma8 must be 1 or 3\n");
+		return 0;
+	}
+	if (dma16[dev] != SNDRV_AUTO_DMA &&
+	    dma16[dev] != 5 && dma16[dev] != 7) {
+		snd_printk(KERN_ERR "dma16 must be 5 or 7\n");
+		return 0;
+	}
+	if (mpu_port[dev] != SNDRV_AUTO_PORT &&
+	    (mpu_port[dev] & ~0x030) != 0x300) {
+		snd_printk(KERN_ERR "incorrect mpu_port specified\n");
+		return 0;
+	}
+	if (mpu_irq[dev] != SNDRV_AUTO_DMA &&
+	    mpu_irq[dev] != 2 && mpu_irq[dev] != 3 &&
+	    mpu_irq[dev] != 5 && mpu_irq[dev] != 7) {
+		snd_printk(KERN_ERR "mpu_irq must be 2, 3, 5 or 7\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int __devinit snd_jazz16_probe(struct device *devptr, unsigned int dev)
+{
+	struct snd_card *card;
+	struct snd_card_jazz16 *jazz16;
+	struct snd_sb *chip;
+	struct snd_opl3 *opl3;
+	static int possible_irqs[] = {2, 3, 5, 7, 9, 10, 15, -1};
+	static int possible_dmas8[] = {1, 3, -1};
+	static int possible_dmas16[] = {5, 7, -1};
+	int err, xirq, xdma8, xdma16, xmpu_port, xmpu_irq;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_card_jazz16), &card);
+	if (err < 0)
+		return err;
+
+	jazz16 = card->private_data;
+
+	xirq = irq[dev];
+	if (xirq == SNDRV_AUTO_IRQ) {
+		xirq = snd_legacy_find_free_irq(possible_irqs);
+		if (xirq < 0) {
+			snd_printk(KERN_ERR "unable to find a free IRQ\n");
+			err = -EBUSY;
+			goto err_free;
+		}
+	}
+	xdma8 = dma8[dev];
+	if (xdma8 == SNDRV_AUTO_DMA) {
+		xdma8 = snd_legacy_find_free_dma(possible_dmas8);
+		if (xdma8 < 0) {
+			snd_printk(KERN_ERR "unable to find a free DMA8\n");
+			err = -EBUSY;
+			goto err_free;
+		}
+	}
+	xdma16 = dma16[dev];
+	if (xdma16 == SNDRV_AUTO_DMA) {
+		xdma16 = snd_legacy_find_free_dma(possible_dmas16);
+		if (xdma16 < 0) {
+			snd_printk(KERN_ERR "unable to find a free DMA16\n");
+			err = -EBUSY;
+			goto err_free;
+		}
+	}
+
+	xmpu_port = mpu_port[dev];
+	if (xmpu_port == SNDRV_AUTO_PORT)
+		xmpu_port = 0;
+	err = jazz16_detect_board(port[dev], xmpu_port);
+	if (err < 0) {
+		printk(KERN_ERR "Media Vision Jazz16 board not detected\n");
+		goto err_free;
+	}
+	err = snd_sbdsp_create(card, port[dev], irq[dev],
+			       jazz16_interrupt,
+			       dma8[dev], dma16[dev],
+			       SB_HW_JAZZ16,
+			       &chip);
+	if (err < 0)
+		goto err_free;
+
+	xmpu_irq = mpu_irq[dev];
+	if (xmpu_irq == SNDRV_AUTO_IRQ || mpu_port[dev] == SNDRV_AUTO_PORT)
+		xmpu_irq = 0;
+	err = jazz16_configure_board(chip, xmpu_irq);
+	if (err < 0) {
+		printk(KERN_ERR "Media Vision Jazz16 configuration failed\n");
+		goto err_free;
+	}
+
+	jazz16->chip = chip;
+
+	strcpy(card->driver, "jazz16");
+	strcpy(card->shortname, "Media Vision Jazz16");
+	sprintf(card->longname,
+		"Media Vision Jazz16 at 0x%lx, irq %d, dma8 %d, dma16 %d",
+		port[dev], xirq, xdma8, xdma16);
+
+	err = snd_sb8dsp_pcm(chip, 0, NULL);
+	if (err < 0)
+		goto err_free;
+	err = snd_sbmixer_new(chip);
+	if (err < 0)
+		goto err_free;
+
+	err = snd_opl3_create(card, chip->port, chip->port + 2,
+			      OPL3_HW_AUTO, 1, &opl3);
+	if (err < 0)
+		snd_printk(KERN_WARNING "no OPL device at 0x%lx-0x%lx\n",
+			   chip->port, chip->port + 2);
+	else {
+		err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+		if (err < 0)
+			goto err_free;
+	}
+	if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) {
+		if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+			mpu_irq[dev] = -1;
+
+		if (snd_mpu401_uart_new(card, 0,
+					MPU401_HW_MPU401,
+					mpu_port[dev], 0,
+					mpu_irq[dev],
+					mpu_irq[dev] >= 0 ? IRQF_DISABLED : 0,
+					NULL) < 0)
+			snd_printk(KERN_ERR "no MPU-401 device at 0x%lx\n",
+					mpu_port[dev]);
+	}
+
+	snd_card_set_dev(card, devptr);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto err_free;
+
+	dev_set_drvdata(devptr, card);
+	return 0;
+
+err_free:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit snd_jazz16_remove(struct device *devptr, unsigned int dev)
+{
+	struct snd_card *card = dev_get_drvdata(devptr);
+
+	dev_set_drvdata(devptr, NULL);
+	snd_card_free(card);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_jazz16_suspend(struct device *pdev, unsigned int n,
+			       pm_message_t state)
+{
+	struct snd_card *card = dev_get_drvdata(pdev);
+	struct snd_card_jazz16 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_sbmixer_suspend(chip);
+	return 0;
+}
+
+static int snd_jazz16_resume(struct device *pdev, unsigned int n)
+{
+	struct snd_card *card = dev_get_drvdata(pdev);
+	struct snd_card_jazz16 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_sbdsp_reset(chip);
+	snd_sbmixer_resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct isa_driver snd_jazz16_driver = {
+	.match		= snd_jazz16_match,
+	.probe		= snd_jazz16_probe,
+	.remove		= __devexit_p(snd_jazz16_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_jazz16_suspend,
+	.resume		= snd_jazz16_resume,
+#endif
+	.driver		= {
+		.name	= "jazz16"
+	},
+};
+
+static int __init alsa_card_jazz16_init(void)
+{
+	return isa_register_driver(&snd_jazz16_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_jazz16_exit(void)
+{
+	isa_unregister_driver(&snd_jazz16_driver);
+}
+
+module_init(alsa_card_jazz16_init)
+module_exit(alsa_card_jazz16_exit)
diff --git a/linux/sound/isa/sb/sb16.c b/linux/sound/isa/sb/sb16.c
new file mode 100644
index 0000000..4d1c5a3
--- /dev/null
+++ b/linux/sound/isa/sb/sb16.c
@@ -0,0 +1,698 @@
+/*
+ *  Driver for SoundBlaster 16/AWE32/AWE64 soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/pnp.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/sb16_csp.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/emu8000.h>
+#include <sound/seq_device.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+#ifdef SNDRV_SBAWE
+#define PFX "sbawe: "
+#else
+#define PFX "sb16: "
+#endif
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_LICENSE("GPL");
+#ifndef SNDRV_SBAWE
+MODULE_DESCRIPTION("Sound Blaster 16");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 16},"
+		"{Creative Labs,SB Vibra16S},"
+		"{Creative Labs,SB Vibra16C},"
+		"{Creative Labs,SB Vibra16CL},"
+		"{Creative Labs,SB Vibra16X}}");
+#else
+MODULE_DESCRIPTION("Sound Blaster AWE");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB AWE 32},"
+		"{Creative Labs,SB AWE 64},"
+		"{Creative Labs,SB AWE 64 Gold}}");
+#endif
+
+#if 0
+#define SNDRV_DEBUG_IRQ
+#endif
+
+#if defined(SNDRV_SBAWE) && (defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)))
+#define SNDRV_SBAWE_EMU8000
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260,0x280 */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x330,0x300 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+#ifdef SNDRV_SBAWE_EMU8000
+static long awe_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+#endif
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0,1,3 */
+static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 5,6,7 */
+static int mic_agc[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#ifdef CONFIG_SND_SB16_CSP
+static int csp[SNDRV_CARDS];
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for SoundBlaster 16 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for SoundBlaster 16 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable SoundBlaster 16 soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SB16 driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for SB16 driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for SB16 PnP driver.");
+#ifdef SNDRV_SBAWE_EMU8000
+module_param_array(awe_port, long, NULL, 0444);
+MODULE_PARM_DESC(awe_port, "AWE port # for SB16 PnP driver.");
+#endif
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SB16 driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for SB16 driver.");
+module_param_array(dma16, int, NULL, 0444);
+MODULE_PARM_DESC(dma16, "16-bit DMA # for SB16 driver.");
+module_param_array(mic_agc, int, NULL, 0444);
+MODULE_PARM_DESC(mic_agc, "Mic Auto-Gain-Control switch.");
+#ifdef CONFIG_SND_SB16_CSP
+module_param_array(csp, int, NULL, 0444);
+MODULE_PARM_DESC(csp, "ASP/CSP chip support.");
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+module_param_array(seq_ports, int, NULL, 0444);
+MODULE_PARM_DESC(seq_ports, "Number of sequencer ports for WaveTable synth.");
+#endif
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+#endif
+
+struct snd_card_sb16 {
+	struct resource *fm_res;	/* used to block FM i/o region for legacy cards */
+	struct snd_sb *chip;
+#ifdef CONFIG_PNP
+	int dev_no;
+	struct pnp_dev *dev;
+#ifdef SNDRV_SBAWE_EMU8000
+	struct pnp_dev *devwt;
+#endif
+#endif
+};
+
+#ifdef CONFIG_PNP
+
+static struct pnp_card_device_id snd_sb16_pnpids[] = {
+#ifndef SNDRV_SBAWE
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0024", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0025", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0026", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0027", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0028", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL0029", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL002a", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	/* Note: This card has also a CTL0051:StereoEnhance device!!! */
+	{ .id = "CTL002b", .devs = { { "CTL0031" } } },
+	/* Sound Blaster 16 PnP */
+	{ .id = "CTL002c", .devs = { { "CTL0031" } } },
+	/* Sound Blaster Vibra16S */
+	{ .id = "CTL0051", .devs = { { "CTL0001" } } },
+	/* Sound Blaster Vibra16C */
+	{ .id = "CTL0070", .devs = { { "CTL0001" } } },
+	/* Sound Blaster Vibra16CL - added by ctm@ardi.com */
+	{ .id = "CTL0080", .devs = { { "CTL0041" } } },
+	/* Sound Blaster 16 'value' PnP. It says model ct4130 on the pcb, */
+	/* but ct4131 on a sticker on the board.. */
+	{ .id = "CTL0086", .devs = { { "CTL0041" } } },
+	/* Sound Blaster Vibra16X */
+	{ .id = "CTL00f0", .devs = { { "CTL0043" } } },
+	/* Sound Blaster 16 (Virtual PC 2004) */
+	{ .id = "tBA03b0", .devs = { {.id="PNPb003" } } },
+#else  /* SNDRV_SBAWE defined */
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0035", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0039", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0042", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0043", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	/* Note: This card has also a CTL0051:StereoEnhance device!!! */
+	{ .id = "CTL0044", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	/* Note: This card has also a CTL0051:StereoEnhance device!!! */
+	{ .id = "CTL0045", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0046", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0047", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0048", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL0054", .devs = { { "CTL0031" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL009a", .devs = { { "CTL0041" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 32 PnP */
+	{ .id = "CTL009c", .devs = { { "CTL0041" }, { "CTL0021" } } },
+	/* Sound Blaster 32 PnP */
+	{ .id = "CTL009f", .devs = { { "CTL0041" }, { "CTL0021" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL009d", .devs = { { "CTL0042" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP Gold */
+	{ .id = "CTL009e", .devs = { { "CTL0044" }, { "CTL0023" } } },
+	/* Sound Blaster AWE 64 PnP Gold */
+	{ .id = "CTL00b2", .devs = { { "CTL0044" }, { "CTL0023" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c1", .devs = { { "CTL0042" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c3", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c5", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00c7", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00e4", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster AWE 64 PnP */
+	{ .id = "CTL00e9", .devs = { { "CTL0045" }, { "CTL0022" } } },
+	/* Sound Blaster 16 PnP (AWE) */
+	{ .id = "CTL00ed", .devs = { { "CTL0041" }, { "CTL0070" } } },
+	/* Generic entries */
+	{ .id = "CTLXXXX" , .devs = { { "CTL0031" }, { "CTL0021" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0041" }, { "CTL0021" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0042" }, { "CTL0022" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0044" }, { "CTL0023" } } },
+	{ .id = "CTLXXXX" , .devs = { { "CTL0045" }, { "CTL0022" } } },
+#endif /* SNDRV_SBAWE */
+	{ .id = "", }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_sb16_pnpids);
+
+#endif /* CONFIG_PNP */
+
+#ifdef SNDRV_SBAWE_EMU8000
+#define DRIVER_NAME	"snd-card-sbawe"
+#else
+#define DRIVER_NAME	"snd-card-sb16"
+#endif
+
+#ifdef CONFIG_PNP
+
+static int __devinit snd_card_sb16_pnp(int dev, struct snd_card_sb16 *acard,
+				       struct pnp_card_link *card,
+				       const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	int err;
+
+	acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->dev == NULL)
+		return -ENODEV; 
+
+#ifdef SNDRV_SBAWE_EMU8000
+	acard->devwt = pnp_request_card_device(card, id->devs[1].id, acard->dev);
+#endif
+	/* Audio initialization */
+	pdev = acard->dev;
+
+	err = pnp_activate_dev(pdev); 
+	if (err < 0) { 
+		snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); 
+		return err; 
+	} 
+	port[dev] = pnp_port_start(pdev, 0);
+	mpu_port[dev] = pnp_port_start(pdev, 1);
+	fm_port[dev] = pnp_port_start(pdev, 2);
+	dma8[dev] = pnp_dma(pdev, 0);
+	dma16[dev] = pnp_dma(pdev, 1);
+	irq[dev] = pnp_irq(pdev, 0);
+	snd_printdd("pnp SB16: port=0x%lx, mpu port=0x%lx, fm port=0x%lx\n",
+			port[dev], mpu_port[dev], fm_port[dev]);
+	snd_printdd("pnp SB16: dma1=%i, dma2=%i, irq=%i\n",
+			dma8[dev], dma16[dev], irq[dev]);
+#ifdef SNDRV_SBAWE_EMU8000
+	/* WaveTable initialization */
+	pdev = acard->devwt;
+	if (pdev != NULL) {
+		err = pnp_activate_dev(pdev); 
+		if (err < 0) { 
+			goto __wt_error; 
+		} 
+		awe_port[dev] = pnp_port_start(pdev, 0);
+		snd_printdd("pnp SB16: wavetable port=0x%llx\n",
+				(unsigned long long)pnp_port_start(pdev, 0));
+	} else {
+__wt_error:
+		if (pdev) {
+			pnp_release_card_device(pdev);
+			snd_printk(KERN_ERR PFX "WaveTable pnp configure failure\n");
+		}
+		acard->devwt = NULL;
+		awe_port[dev] = -1;
+	}
+#endif
+	return 0;
+}
+
+#endif /* CONFIG_PNP */
+
+static void snd_sb16_free(struct snd_card *card)
+{
+	struct snd_card_sb16 *acard = card->private_data;
+        
+	if (acard == NULL)
+		return;
+	release_and_free_resource(acard->fm_res);
+}
+
+#ifdef CONFIG_PNP
+#define is_isapnp_selected(dev)		isapnp[dev]
+#else
+#define is_isapnp_selected(dev)		0
+#endif
+
+static int snd_sb16_card_new(int dev, struct snd_card **cardp)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_card_sb16), &card);
+	if (err < 0)
+		return err;
+	card->private_free = snd_sb16_free;
+	*cardp = card;
+	return 0;
+}
+
+static int __devinit snd_sb16_probe(struct snd_card *card, int dev)
+{
+	int xirq, xdma8, xdma16;
+	struct snd_sb *chip;
+	struct snd_card_sb16 *acard = card->private_data;
+	struct snd_opl3 *opl3;
+	struct snd_hwdep *synth = NULL;
+#ifdef CONFIG_SND_SB16_CSP
+	struct snd_hwdep *xcsp = NULL;
+#endif
+	unsigned long flags;
+	int err;
+
+	xirq = irq[dev];
+	xdma8 = dma8[dev];
+	xdma16 = dma16[dev];
+
+	if ((err = snd_sbdsp_create(card,
+				    port[dev],
+				    xirq,
+				    snd_sb16dsp_interrupt,
+				    xdma8,
+				    xdma16,
+				    SB_HW_AUTO,
+				    &chip)) < 0)
+		return err;
+
+	acard->chip = chip;
+	if (chip->hardware != SB_HW_16) {
+		snd_printk(KERN_ERR PFX "SB 16 chip was not detected at 0x%lx\n", port[dev]);
+		return -ENODEV;
+	}
+	chip->mpu_port = mpu_port[dev];
+	if (! is_isapnp_selected(dev) && (err = snd_sb16dsp_configure(chip)) < 0)
+		return err;
+
+	if ((err = snd_sb16dsp_pcm(chip, 0, &chip->pcm)) < 0)
+		return err;
+
+	strcpy(card->driver,
+#ifdef SNDRV_SBAWE_EMU8000
+			awe_port[dev] > 0 ? "SB AWE" :
+#endif
+			"SB16");
+	strcpy(card->shortname, chip->name);
+	sprintf(card->longname, "%s at 0x%lx, irq %i, dma ",
+		chip->name,
+		chip->port,
+		xirq);
+	if (xdma8 >= 0)
+		sprintf(card->longname + strlen(card->longname), "%d", xdma8);
+	if (xdma16 >= 0)
+		sprintf(card->longname + strlen(card->longname), "%s%d",
+			xdma8 >= 0 ? "&" : "", xdma16);
+
+	if (chip->mpu_port > 0 && chip->mpu_port != SNDRV_AUTO_PORT) {
+		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_SB,
+					       chip->mpu_port, 0,
+					       xirq, 0, &chip->rmidi)) < 0)
+			return err;
+		chip->rmidi_callback = snd_mpu401_uart_interrupt;
+	}
+
+#ifdef SNDRV_SBAWE_EMU8000
+	if (awe_port[dev] == SNDRV_AUTO_PORT)
+		awe_port[dev] = 0; /* disable */
+#endif
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		if (snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
+				    OPL3_HW_OPL3,
+				    acard->fm_res != NULL || fm_port[dev] == port[dev],
+				    &opl3) < 0) {
+			snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n",
+				   fm_port[dev], fm_port[dev] + 2);
+		} else {
+#ifdef SNDRV_SBAWE_EMU8000
+			int seqdev = awe_port[dev] > 0 ? 2 : 1;
+#else
+			int seqdev = 1;
+#endif
+			if ((err = snd_opl3_hwdep_new(opl3, 0, seqdev, &synth)) < 0)
+				return err;
+		}
+	}
+
+	if ((err = snd_sbmixer_new(chip)) < 0)
+		return err;
+
+#ifdef CONFIG_SND_SB16_CSP
+	/* CSP chip on SB16ASP/AWE32 */
+	if ((chip->hardware == SB_HW_16) && csp[dev]) {
+		snd_sb_csp_new(chip, synth != NULL ? 1 : 0, &xcsp);
+		if (xcsp) {
+			chip->csp = xcsp->private_data;
+			chip->hardware = SB_HW_16CSP;
+		} else {
+			snd_printk(KERN_INFO PFX "warning - CSP chip not detected on soundcard #%i\n", dev + 1);
+		}
+	}
+#endif
+#ifdef SNDRV_SBAWE_EMU8000
+	if (awe_port[dev] > 0) {
+		if ((err = snd_emu8000_new(card, 1, awe_port[dev],
+					   seq_ports[dev], NULL)) < 0) {
+			snd_printk(KERN_ERR PFX "fatal error - EMU-8000 synthesizer not detected at 0x%lx\n", awe_port[dev]);
+
+			return err;
+		}
+	}
+#endif
+
+	/* setup Mic AGC */
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	snd_sbmixer_write(chip, SB_DSP4_MIC_AGC,
+		(snd_sbmixer_read(chip, SB_DSP4_MIC_AGC) & 0x01) |
+		(mic_agc[dev] ? 0x00 : 0x01));
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+	if ((err = snd_card_register(card)) < 0)
+		return err;
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_sb16_suspend(struct snd_card *card, pm_message_t state)
+{
+	struct snd_card_sb16 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_sbmixer_suspend(chip);
+	return 0;
+}
+
+static int snd_sb16_resume(struct snd_card *card)
+{
+	struct snd_card_sb16 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_sbdsp_reset(chip);
+	snd_sbmixer_resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static int __devinit snd_sb16_isa_probe1(int dev, struct device *pdev)
+{
+	struct snd_card_sb16 *acard;
+	struct snd_card *card;
+	int err;
+
+	err = snd_sb16_card_new(dev, &card);
+	if (err < 0)
+		return err;
+
+	acard = card->private_data;
+	/* non-PnP FM port address is hardwired with base port address */
+	fm_port[dev] = port[dev];
+	/* block the 0x388 port to avoid PnP conflicts */
+	acard->fm_res = request_region(0x388, 4, "SoundBlaster FM");
+#ifdef SNDRV_SBAWE_EMU8000
+	/* non-PnP AWE port address is hardwired with base port address */
+	awe_port[dev] = port[dev] + 0x400;
+#endif
+
+	snd_card_set_dev(card, pdev);
+	if ((err = snd_sb16_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	dev_set_drvdata(pdev, card);
+	return 0;
+}
+
+
+static int __devinit snd_sb16_isa_match(struct device *pdev, unsigned int dev)
+{
+	return enable[dev] && !is_isapnp_selected(dev);
+}
+
+static int __devinit snd_sb16_isa_probe(struct device *pdev, unsigned int dev)
+{
+	int err;
+	static int possible_irqs[] = {5, 9, 10, 7, -1};
+	static int possible_dmas8[] = {1, 3, 0, -1};
+	static int possible_dmas16[] = {5, 6, 7, -1};
+
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		if ((irq[dev] = snd_legacy_find_free_irq(possible_irqs)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			return -EBUSY;
+		}
+	}
+	if (dma8[dev] == SNDRV_AUTO_DMA) {
+		if ((dma8[dev] = snd_legacy_find_free_dma(possible_dmas8)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free 8-bit DMA\n");
+			return -EBUSY;
+		}
+	}
+	if (dma16[dev] == SNDRV_AUTO_DMA) {
+		if ((dma16[dev] = snd_legacy_find_free_dma(possible_dmas16)) < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free 16-bit DMA\n");
+			return -EBUSY;
+		}
+	}
+
+	if (port[dev] != SNDRV_AUTO_PORT)
+		return snd_sb16_isa_probe1(dev, pdev);
+	else {
+		static int possible_ports[] = {0x220, 0x240, 0x260, 0x280};
+		int i;
+		for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+			port[dev] = possible_ports[i];
+			err = snd_sb16_isa_probe1(dev, pdev);
+			if (! err)
+				return 0;
+		}
+		return err;
+	}
+}
+
+static int __devexit snd_sb16_isa_remove(struct device *pdev, unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(pdev));
+	dev_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_sb16_isa_suspend(struct device *dev, unsigned int n,
+				pm_message_t state)
+{
+	return snd_sb16_suspend(dev_get_drvdata(dev), state);
+}
+
+static int snd_sb16_isa_resume(struct device *dev, unsigned int n)
+{
+	return snd_sb16_resume(dev_get_drvdata(dev));
+}
+#endif
+
+#ifdef SNDRV_SBAWE
+#define DEV_NAME "sbawe"
+#else
+#define DEV_NAME "sb16"
+#endif
+
+static struct isa_driver snd_sb16_isa_driver = {
+	.match		= snd_sb16_isa_match,
+	.probe		= snd_sb16_isa_probe,
+	.remove		= __devexit_p(snd_sb16_isa_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_sb16_isa_suspend,
+	.resume		= snd_sb16_isa_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+
+#ifdef CONFIG_PNP
+static int __devinit snd_sb16_pnp_detect(struct pnp_card_link *pcard,
+					 const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (!enable[dev] || !isapnp[dev])
+			continue;
+		res = snd_sb16_card_new(dev, &card);
+		if (res < 0)
+			return res;
+		snd_card_set_dev(card, &pcard->card->dev);
+		if ((res = snd_card_sb16_pnp(dev, card->private_data, pcard, pid)) < 0 ||
+		    (res = snd_sb16_probe(card, dev)) < 0) {
+			snd_card_free(card);
+			return res;
+		}
+		pnp_set_card_drvdata(pcard, card);
+		dev++;
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+static void __devexit snd_sb16_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_sb16_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+	return snd_sb16_suspend(pnp_get_card_drvdata(pcard), state);
+}
+static int snd_sb16_pnp_resume(struct pnp_card_link *pcard)
+{
+	return snd_sb16_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver sb16_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DISABLE,
+#ifdef SNDRV_SBAWE
+	.name = "sbawe",
+#else
+	.name = "sb16",
+#endif
+	.id_table = snd_sb16_pnpids,
+	.probe = snd_sb16_pnp_detect,
+	.remove = __devexit_p(snd_sb16_pnp_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_sb16_pnp_suspend,
+	.resume = snd_sb16_pnp_resume,
+#endif
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_sb16_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_sb16_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&sb16_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_sb16_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&sb16_pnpc_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_sb16_isa_driver);
+}
+
+module_init(alsa_card_sb16_init)
+module_exit(alsa_card_sb16_exit)
diff --git a/linux/sound/isa/sb/sb16_csp.c b/linux/sound/isa/sb/sb16_csp.c
new file mode 100644
index 0000000..bdc8dde
--- /dev/null
+++ b/linux/sound/isa/sb/sb16_csp.c
@@ -0,0 +1,1201 @@
+/*
+ *  Copyright (c) 1999 by Uros Bizjak <uros@kss-loka.si>
+ *                        Takashi Iwai <tiwai@suse.de>
+ *
+ *  SB16ASP/AWE32 CSP control
+ *
+ *  CSP microcode loader:
+ *   alsa-tools/sb16_csp/ 
+ *
+ *   This program is free software; you can redistribute it and/or modify 
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/sb16_csp.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");
+MODULE_DESCRIPTION("ALSA driver for SB16 Creative Signal Processor");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("sb16/mulaw_main.csp");
+MODULE_FIRMWARE("sb16/alaw_main.csp");
+MODULE_FIRMWARE("sb16/ima_adpcm_init.csp");
+MODULE_FIRMWARE("sb16/ima_adpcm_playback.csp");
+MODULE_FIRMWARE("sb16/ima_adpcm_capture.csp");
+
+#ifdef SNDRV_LITTLE_ENDIAN
+#define CSP_HDR_VALUE(a,b,c,d)	((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))
+#else
+#define CSP_HDR_VALUE(a,b,c,d)	((d) | ((c)<<8) | ((b)<<16) | ((a)<<24))
+#endif
+
+#define RIFF_HEADER	CSP_HDR_VALUE('R', 'I', 'F', 'F')
+#define CSP__HEADER	CSP_HDR_VALUE('C', 'S', 'P', ' ')
+#define LIST_HEADER	CSP_HDR_VALUE('L', 'I', 'S', 'T')
+#define FUNC_HEADER	CSP_HDR_VALUE('f', 'u', 'n', 'c')
+#define CODE_HEADER	CSP_HDR_VALUE('c', 'o', 'd', 'e')
+#define INIT_HEADER	CSP_HDR_VALUE('i', 'n', 'i', 't')
+#define MAIN_HEADER	CSP_HDR_VALUE('m', 'a', 'i', 'n')
+
+/*
+ * RIFF data format
+ */
+struct riff_header {
+	__u32 name;
+	__u32 len;
+};
+
+struct desc_header {
+	struct riff_header info;
+	__u16 func_nr;
+	__u16 VOC_type;
+	__u16 flags_play_rec;
+	__u16 flags_16bit_8bit;
+	__u16 flags_stereo_mono;
+	__u16 flags_rates;
+};
+
+/*
+ * prototypes
+ */
+static void snd_sb_csp_free(struct snd_hwdep *hw);
+static int snd_sb_csp_open(struct snd_hwdep * hw, struct file *file);
+static int snd_sb_csp_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg);
+static int snd_sb_csp_release(struct snd_hwdep * hw, struct file *file);
+
+static int csp_detect(struct snd_sb *chip, int *version);
+static int set_codec_parameter(struct snd_sb *chip, unsigned char par, unsigned char val);
+static int set_register(struct snd_sb *chip, unsigned char reg, unsigned char val);
+static int read_register(struct snd_sb *chip, unsigned char reg);
+static int set_mode_register(struct snd_sb *chip, unsigned char mode);
+static int get_version(struct snd_sb *chip);
+
+static int snd_sb_csp_riff_load(struct snd_sb_csp * p,
+				struct snd_sb_csp_microcode __user * code);
+static int snd_sb_csp_unload(struct snd_sb_csp * p);
+static int snd_sb_csp_load_user(struct snd_sb_csp * p, const unsigned char __user *buf, int size, int load_flags);
+static int snd_sb_csp_autoload(struct snd_sb_csp * p, int pcm_sfmt, int play_rec_mode);
+static int snd_sb_csp_check_version(struct snd_sb_csp * p);
+
+static int snd_sb_csp_use(struct snd_sb_csp * p);
+static int snd_sb_csp_unuse(struct snd_sb_csp * p);
+static int snd_sb_csp_start(struct snd_sb_csp * p, int sample_width, int channels);
+static int snd_sb_csp_stop(struct snd_sb_csp * p);
+static int snd_sb_csp_pause(struct snd_sb_csp * p);
+static int snd_sb_csp_restart(struct snd_sb_csp * p);
+
+static int snd_sb_qsound_build(struct snd_sb_csp * p);
+static void snd_sb_qsound_destroy(struct snd_sb_csp * p);
+static int snd_sb_csp_qsound_transfer(struct snd_sb_csp * p);
+
+static int init_proc_entry(struct snd_sb_csp * p, int device);
+static void info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
+
+/*
+ * Detect CSP chip and create a new instance
+ */
+int snd_sb_csp_new(struct snd_sb *chip, int device, struct snd_hwdep ** rhwdep)
+{
+	struct snd_sb_csp *p;
+	int uninitialized_var(version);
+	int err;
+	struct snd_hwdep *hw;
+
+	if (rhwdep)
+		*rhwdep = NULL;
+
+	if (csp_detect(chip, &version))
+		return -ENODEV;
+
+	if ((err = snd_hwdep_new(chip->card, "SB16-CSP", device, &hw)) < 0)
+		return err;
+
+	if ((p = kzalloc(sizeof(*p), GFP_KERNEL)) == NULL) {
+		snd_device_free(chip->card, hw);
+		return -ENOMEM;
+	}
+	p->chip = chip;
+	p->version = version;
+
+	/* CSP operators */
+	p->ops.csp_use = snd_sb_csp_use;
+	p->ops.csp_unuse = snd_sb_csp_unuse;
+	p->ops.csp_autoload = snd_sb_csp_autoload;
+	p->ops.csp_start = snd_sb_csp_start;
+	p->ops.csp_stop = snd_sb_csp_stop;
+	p->ops.csp_qsound_transfer = snd_sb_csp_qsound_transfer;
+
+	mutex_init(&p->access_mutex);
+	sprintf(hw->name, "CSP v%d.%d", (version >> 4), (version & 0x0f));
+	hw->iface = SNDRV_HWDEP_IFACE_SB16CSP;
+	hw->private_data = p;
+	hw->private_free = snd_sb_csp_free;
+
+	/* operators - only write/ioctl */
+	hw->ops.open = snd_sb_csp_open;
+	hw->ops.ioctl = snd_sb_csp_ioctl;
+	hw->ops.release = snd_sb_csp_release;
+
+	/* create a proc entry */
+	init_proc_entry(p, device);
+	if (rhwdep)
+		*rhwdep = hw;
+	return 0;
+}
+
+/*
+ * free_private for hwdep instance
+ */
+static void snd_sb_csp_free(struct snd_hwdep *hwdep)
+{
+	int i;
+	struct snd_sb_csp *p = hwdep->private_data;
+	if (p) {
+		if (p->running & SNDRV_SB_CSP_ST_RUNNING)
+			snd_sb_csp_stop(p);
+		for (i = 0; i < ARRAY_SIZE(p->csp_programs); ++i)
+			release_firmware(p->csp_programs[i]);
+		kfree(p);
+	}
+}
+
+/* ------------------------------ */
+
+/*
+ * open the device exclusively
+ */
+static int snd_sb_csp_open(struct snd_hwdep * hw, struct file *file)
+{
+	struct snd_sb_csp *p = hw->private_data;
+	return (snd_sb_csp_use(p));
+}
+
+/*
+ * ioctl for hwdep device:
+ */
+static int snd_sb_csp_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct snd_sb_csp *p = hw->private_data;
+	struct snd_sb_csp_info info;
+	struct snd_sb_csp_start start_info;
+	int err;
+
+	if (snd_BUG_ON(!p))
+		return -EINVAL;
+
+	if (snd_sb_csp_check_version(p))
+		return -ENODEV;
+
+	switch (cmd) {
+		/* get information */
+	case SNDRV_SB_CSP_IOCTL_INFO:
+		*info.codec_name = *p->codec_name;
+		info.func_nr = p->func_nr;
+		info.acc_format = p->acc_format;
+		info.acc_channels = p->acc_channels;
+		info.acc_width = p->acc_width;
+		info.acc_rates = p->acc_rates;
+		info.csp_mode = p->mode;
+		info.run_channels = p->run_channels;
+		info.run_width = p->run_width;
+		info.version = p->version;
+		info.state = p->running;
+		if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+			err = -EFAULT;
+		else
+			err = 0;
+		break;
+
+		/* load CSP microcode */
+	case SNDRV_SB_CSP_IOCTL_LOAD_CODE:
+		err = (p->running & SNDRV_SB_CSP_ST_RUNNING ?
+		       -EBUSY : snd_sb_csp_riff_load(p, (struct snd_sb_csp_microcode __user *) arg));
+		break;
+	case SNDRV_SB_CSP_IOCTL_UNLOAD_CODE:
+		err = (p->running & SNDRV_SB_CSP_ST_RUNNING ?
+		       -EBUSY : snd_sb_csp_unload(p));
+		break;
+
+		/* change CSP running state */
+	case SNDRV_SB_CSP_IOCTL_START:
+		if (copy_from_user(&start_info, (void __user *) arg, sizeof(start_info)))
+			err = -EFAULT;
+		else
+			err = snd_sb_csp_start(p, start_info.sample_width, start_info.channels);
+		break;
+	case SNDRV_SB_CSP_IOCTL_STOP:
+		err = snd_sb_csp_stop(p);
+		break;
+	case SNDRV_SB_CSP_IOCTL_PAUSE:
+		err = snd_sb_csp_pause(p);
+		break;
+	case SNDRV_SB_CSP_IOCTL_RESTART:
+		err = snd_sb_csp_restart(p);
+		break;
+	default:
+		err = -ENOTTY;
+		break;
+	}
+
+	return err;
+}
+
+/*
+ * close the device
+ */
+static int snd_sb_csp_release(struct snd_hwdep * hw, struct file *file)
+{
+	struct snd_sb_csp *p = hw->private_data;
+	return (snd_sb_csp_unuse(p));
+}
+
+/* ------------------------------ */
+
+/*
+ * acquire device
+ */
+static int snd_sb_csp_use(struct snd_sb_csp * p)
+{
+	mutex_lock(&p->access_mutex);
+	if (p->used) {
+		mutex_unlock(&p->access_mutex);
+		return -EAGAIN;
+	}
+	p->used++;
+	mutex_unlock(&p->access_mutex);
+
+	return 0;
+
+}
+
+/*
+ * release device
+ */
+static int snd_sb_csp_unuse(struct snd_sb_csp * p)
+{
+	mutex_lock(&p->access_mutex);
+	p->used--;
+	mutex_unlock(&p->access_mutex);
+
+	return 0;
+}
+
+/*
+ * load microcode via ioctl: 
+ * code is user-space pointer
+ */
+static int snd_sb_csp_riff_load(struct snd_sb_csp * p,
+				struct snd_sb_csp_microcode __user * mcode)
+{
+	struct snd_sb_csp_mc_header info;
+
+	unsigned char __user *data_ptr;
+	unsigned char __user *data_end;
+	unsigned short func_nr = 0;
+
+	struct riff_header file_h, item_h, code_h;
+	__u32 item_type;
+	struct desc_header funcdesc_h;
+
+	unsigned long flags;
+	int err;
+
+	if (copy_from_user(&info, mcode, sizeof(info)))
+		return -EFAULT;
+	data_ptr = mcode->data;
+
+	if (copy_from_user(&file_h, data_ptr, sizeof(file_h)))
+		return -EFAULT;
+	if ((file_h.name != RIFF_HEADER) ||
+	    (le32_to_cpu(file_h.len) >= SNDRV_SB_CSP_MAX_MICROCODE_FILE_SIZE - sizeof(file_h))) {
+		snd_printd("%s: Invalid RIFF header\n", __func__);
+		return -EINVAL;
+	}
+	data_ptr += sizeof(file_h);
+	data_end = data_ptr + le32_to_cpu(file_h.len);
+
+	if (copy_from_user(&item_type, data_ptr, sizeof(item_type)))
+		return -EFAULT;
+	if (item_type != CSP__HEADER) {
+		snd_printd("%s: Invalid RIFF file type\n", __func__);
+		return -EINVAL;
+	}
+	data_ptr += sizeof (item_type);
+
+	for (; data_ptr < data_end; data_ptr += le32_to_cpu(item_h.len)) {
+		if (copy_from_user(&item_h, data_ptr, sizeof(item_h)))
+			return -EFAULT;
+		data_ptr += sizeof(item_h);
+		if (item_h.name != LIST_HEADER)
+			continue;
+
+		if (copy_from_user(&item_type, data_ptr, sizeof(item_type)))
+			 return -EFAULT;
+		switch (item_type) {
+		case FUNC_HEADER:
+			if (copy_from_user(&funcdesc_h, data_ptr + sizeof(item_type), sizeof(funcdesc_h)))
+				return -EFAULT;
+			func_nr = le16_to_cpu(funcdesc_h.func_nr);
+			break;
+		case CODE_HEADER:
+			if (func_nr != info.func_req)
+				break;	/* not required function, try next */
+			data_ptr += sizeof(item_type);
+
+			/* destroy QSound mixer element */
+			if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+				snd_sb_qsound_destroy(p);
+			}
+			/* Clear all flags */
+			p->running = 0;
+			p->mode = 0;
+
+			/* load microcode blocks */
+			for (;;) {
+				if (data_ptr >= data_end)
+					return -EINVAL;
+				if (copy_from_user(&code_h, data_ptr, sizeof(code_h)))
+					return -EFAULT;
+
+				/* init microcode blocks */
+				if (code_h.name != INIT_HEADER)
+					break;
+				data_ptr += sizeof(code_h);
+				err = snd_sb_csp_load_user(p, data_ptr, le32_to_cpu(code_h.len),
+						      SNDRV_SB_CSP_LOAD_INITBLOCK);
+				if (err)
+					return err;
+				data_ptr += le32_to_cpu(code_h.len);
+			}
+			/* main microcode block */
+			if (copy_from_user(&code_h, data_ptr, sizeof(code_h)))
+				return -EFAULT;
+
+			if (code_h.name != MAIN_HEADER) {
+				snd_printd("%s: Missing 'main' microcode\n", __func__);
+				return -EINVAL;
+			}
+			data_ptr += sizeof(code_h);
+			err = snd_sb_csp_load_user(p, data_ptr,
+						   le32_to_cpu(code_h.len), 0);
+			if (err)
+				return err;
+
+			/* fill in codec header */
+			strlcpy(p->codec_name, info.codec_name, sizeof(p->codec_name));
+			p->func_nr = func_nr;
+			p->mode = le16_to_cpu(funcdesc_h.flags_play_rec);
+			switch (le16_to_cpu(funcdesc_h.VOC_type)) {
+			case 0x0001:	/* QSound decoder */
+				if (le16_to_cpu(funcdesc_h.flags_play_rec) == SNDRV_SB_CSP_MODE_DSP_WRITE) {
+					if (snd_sb_qsound_build(p) == 0)
+						/* set QSound flag and clear all other mode flags */
+						p->mode = SNDRV_SB_CSP_MODE_QSOUND;
+				}
+				p->acc_format = 0;
+				break;
+			case 0x0006:	/* A Law codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_A_LAW;
+				break;
+			case 0x0007:	/* Mu Law codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW;
+				break;
+			case 0x0011:	/* what Creative thinks is IMA ADPCM codec */
+			case 0x0200:	/* Creative ADPCM codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM;
+				break;
+			case    201:	/* Text 2 Speech decoder */
+				/* TODO: Text2Speech handling routines */
+				p->acc_format = 0;
+				break;
+			case 0x0202:	/* Fast Speech 8 codec */
+			case 0x0203:	/* Fast Speech 10 codec */
+				p->acc_format = SNDRV_PCM_FMTBIT_SPECIAL;
+				break;
+			default:	/* other codecs are unsupported */
+				p->acc_format = p->acc_width = p->acc_rates = 0;
+				p->mode = 0;
+				snd_printd("%s: Unsupported CSP codec type: 0x%04x\n",
+					   __func__,
+					   le16_to_cpu(funcdesc_h.VOC_type));
+				return -EINVAL;
+			}
+			p->acc_channels = le16_to_cpu(funcdesc_h.flags_stereo_mono);
+			p->acc_width = le16_to_cpu(funcdesc_h.flags_16bit_8bit);
+			p->acc_rates = le16_to_cpu(funcdesc_h.flags_rates);
+
+			/* Decouple CSP from IRQ and DMAREQ lines */
+			spin_lock_irqsave(&p->chip->reg_lock, flags);
+			set_mode_register(p->chip, 0xfc);
+			set_mode_register(p->chip, 0x00);
+			spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+
+			/* finished loading successfully */
+			p->running = SNDRV_SB_CSP_ST_LOADED;	/* set LOADED flag */
+			return 0;
+		}
+	}
+	snd_printd("%s: Function #%d not found\n", __func__, info.func_req);
+	return -EINVAL;
+}
+
+/*
+ * unload CSP microcode
+ */
+static int snd_sb_csp_unload(struct snd_sb_csp * p)
+{
+	if (p->running & SNDRV_SB_CSP_ST_RUNNING)
+		return -EBUSY;
+	if (!(p->running & SNDRV_SB_CSP_ST_LOADED))
+		return -ENXIO;
+
+	/* clear supported formats */
+	p->acc_format = 0;
+	p->acc_channels = p->acc_width = p->acc_rates = 0;
+	/* destroy QSound mixer element */
+	if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+		snd_sb_qsound_destroy(p);
+	}
+	/* clear all flags */
+	p->running = 0;
+	p->mode = 0;
+	return 0;
+}
+
+/*
+ * send command sequence to DSP
+ */
+static inline int command_seq(struct snd_sb *chip, const unsigned char *seq, int size)
+{
+	int i;
+	for (i = 0; i < size; i++) {
+		if (!snd_sbdsp_command(chip, seq[i]))
+			return -EIO;
+	}
+	return 0;
+}
+
+/*
+ * set CSP codec parameter
+ */
+static int set_codec_parameter(struct snd_sb *chip, unsigned char par, unsigned char val)
+{
+	unsigned char dsp_cmd[3];
+
+	dsp_cmd[0] = 0x05;	/* CSP set codec parameter */
+	dsp_cmd[1] = val;	/* Parameter value */
+	dsp_cmd[2] = par;	/* Parameter */
+	command_seq(chip, dsp_cmd, 3);
+	snd_sbdsp_command(chip, 0x03);	/* DSP read? */
+	if (snd_sbdsp_get_byte(chip) != par)
+		return -EIO;
+	return 0;
+}
+
+/*
+ * set CSP register
+ */
+static int set_register(struct snd_sb *chip, unsigned char reg, unsigned char val)
+{
+	unsigned char dsp_cmd[3];
+
+	dsp_cmd[0] = 0x0e;	/* CSP set register */
+	dsp_cmd[1] = reg;	/* CSP Register */
+	dsp_cmd[2] = val;	/* value */
+	return command_seq(chip, dsp_cmd, 3);
+}
+
+/*
+ * read CSP register
+ * return < 0 -> error
+ */
+static int read_register(struct snd_sb *chip, unsigned char reg)
+{
+	unsigned char dsp_cmd[2];
+
+	dsp_cmd[0] = 0x0f;	/* CSP read register */
+	dsp_cmd[1] = reg;	/* CSP Register */
+	command_seq(chip, dsp_cmd, 2);
+	return snd_sbdsp_get_byte(chip);	/* Read DSP value */
+}
+
+/*
+ * set CSP mode register
+ */
+static int set_mode_register(struct snd_sb *chip, unsigned char mode)
+{
+	unsigned char dsp_cmd[2];
+
+	dsp_cmd[0] = 0x04;	/* CSP set mode register */
+	dsp_cmd[1] = mode;	/* mode */
+	return command_seq(chip, dsp_cmd, 2);
+}
+
+/*
+ * Detect CSP
+ * return 0 if CSP exists.
+ */
+static int csp_detect(struct snd_sb *chip, int *version)
+{
+	unsigned char csp_test1, csp_test2;
+	unsigned long flags;
+	int result = -ENODEV;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	set_codec_parameter(chip, 0x00, 0x00);
+	set_mode_register(chip, 0xfc);		/* 0xfc = ?? */
+
+	csp_test1 = read_register(chip, 0x83);
+	set_register(chip, 0x83, ~csp_test1);
+	csp_test2 = read_register(chip, 0x83);
+	if (csp_test2 != (csp_test1 ^ 0xff))
+		goto __fail;
+
+	set_register(chip, 0x83, csp_test1);
+	csp_test2 = read_register(chip, 0x83);
+	if (csp_test2 != csp_test1)
+		goto __fail;
+
+	set_mode_register(chip, 0x00);		/* 0x00 = ? */
+
+	*version = get_version(chip);
+	snd_sbdsp_reset(chip);	/* reset DSP after getversion! */
+	if (*version >= 0x10 && *version <= 0x1f)
+		result = 0;		/* valid version id */
+
+      __fail:
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return result;
+}
+
+/*
+ * get CSP version number
+ */
+static int get_version(struct snd_sb *chip)
+{
+	unsigned char dsp_cmd[2];
+
+	dsp_cmd[0] = 0x08;	/* SB_DSP_!something! */
+	dsp_cmd[1] = 0x03;	/* get chip version id? */
+	command_seq(chip, dsp_cmd, 2);
+
+	return (snd_sbdsp_get_byte(chip));
+}
+
+/*
+ * check if the CSP version is valid
+ */
+static int snd_sb_csp_check_version(struct snd_sb_csp * p)
+{
+	if (p->version < 0x10 || p->version > 0x1f) {
+		snd_printd("%s: Invalid CSP version: 0x%x\n", __func__, p->version);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * download microcode to CSP (microcode should have one "main" block).
+ */
+static int snd_sb_csp_load(struct snd_sb_csp * p, const unsigned char *buf, int size, int load_flags)
+{
+	int status, i;
+	int err;
+	int result = -EIO;
+	unsigned long flags;
+
+	spin_lock_irqsave(&p->chip->reg_lock, flags);
+	snd_sbdsp_command(p->chip, 0x01);	/* CSP download command */
+	if (snd_sbdsp_get_byte(p->chip)) {
+		snd_printd("%s: Download command failed\n", __func__);
+		goto __fail;
+	}
+	/* Send CSP low byte (size - 1) */
+	snd_sbdsp_command(p->chip, (unsigned char)(size - 1));
+	/* Send high byte */
+	snd_sbdsp_command(p->chip, (unsigned char)((size - 1) >> 8));
+	/* send microcode sequence */
+	/* load from kernel space */
+	while (size--) {
+		if (!snd_sbdsp_command(p->chip, *buf++))
+			goto __fail;
+	}
+	if (snd_sbdsp_get_byte(p->chip))
+		goto __fail;
+
+	if (load_flags & SNDRV_SB_CSP_LOAD_INITBLOCK) {
+		i = 0;
+		/* some codecs (FastSpeech) take some time to initialize */
+		while (1) {
+			snd_sbdsp_command(p->chip, 0x03);
+			status = snd_sbdsp_get_byte(p->chip);
+			if (status == 0x55 || ++i >= 10)
+				break;
+			udelay (10);
+		}
+		if (status != 0x55) {
+			snd_printd("%s: Microcode initialization failed\n", __func__);
+			goto __fail;
+		}
+	} else {
+		/*
+		 * Read mixer register SB_DSP4_DMASETUP after loading 'main' code.
+		 * Start CSP chip if no 16bit DMA channel is set - some kind
+		 * of autorun or perhaps a bugfix?
+		 */
+		spin_lock(&p->chip->mixer_lock);
+		status = snd_sbmixer_read(p->chip, SB_DSP4_DMASETUP);
+		spin_unlock(&p->chip->mixer_lock);
+		if (!(status & (SB_DMASETUP_DMA7 | SB_DMASETUP_DMA6 | SB_DMASETUP_DMA5))) {
+			err = (set_codec_parameter(p->chip, 0xaa, 0x00) ||
+			       set_codec_parameter(p->chip, 0xff, 0x00));
+			snd_sbdsp_reset(p->chip);		/* really! */
+			if (err)
+				goto __fail;
+			set_mode_register(p->chip, 0xc0);	/* c0 = STOP */
+			set_mode_register(p->chip, 0x70);	/* 70 = RUN */
+		}
+	}
+	result = 0;
+
+      __fail:
+	spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+	return result;
+}
+ 
+static int snd_sb_csp_load_user(struct snd_sb_csp * p, const unsigned char __user *buf, int size, int load_flags)
+{
+	int err;
+	unsigned char *kbuf;
+
+	kbuf = memdup_user(buf, size);
+	if (IS_ERR(kbuf))
+		return PTR_ERR(kbuf);
+
+	err = snd_sb_csp_load(p, kbuf, size, load_flags);
+
+	kfree(kbuf);
+	return err;
+}
+
+static int snd_sb_csp_firmware_load(struct snd_sb_csp *p, int index, int flags)
+{
+	static const char *const names[] = {
+		"sb16/mulaw_main.csp",
+		"sb16/alaw_main.csp",
+		"sb16/ima_adpcm_init.csp",
+		"sb16/ima_adpcm_playback.csp",
+		"sb16/ima_adpcm_capture.csp",
+	};
+	const struct firmware *program;
+
+	BUILD_BUG_ON(ARRAY_SIZE(names) != CSP_PROGRAM_COUNT);
+	program = p->csp_programs[index];
+	if (!program) {
+		int err = request_firmware(&program, names[index],
+				       p->chip->card->dev);
+		if (err < 0)
+			return err;
+		p->csp_programs[index] = program;
+	}
+	return snd_sb_csp_load(p, program->data, program->size, flags);
+}
+
+/*
+ * autoload hardware codec if necessary
+ * return 0 if CSP is loaded and ready to run (p->running != 0)
+ */
+static int snd_sb_csp_autoload(struct snd_sb_csp * p, int pcm_sfmt, int play_rec_mode)
+{
+	unsigned long flags;
+	int err = 0;
+
+	/* if CSP is running or manually loaded then exit */
+	if (p->running & (SNDRV_SB_CSP_ST_RUNNING | SNDRV_SB_CSP_ST_LOADED)) 
+		return -EBUSY;
+
+	/* autoload microcode only if requested hardware codec is not already loaded */
+	if (((1 << pcm_sfmt) & p->acc_format) && (play_rec_mode & p->mode)) {
+		p->running = SNDRV_SB_CSP_ST_AUTO;
+	} else {
+		switch (pcm_sfmt) {
+		case SNDRV_PCM_FORMAT_MU_LAW:
+			err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_MULAW, 0);
+			p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW;
+			p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE;
+			break;
+		case SNDRV_PCM_FORMAT_A_LAW:
+			err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_ALAW, 0);
+			p->acc_format = SNDRV_PCM_FMTBIT_A_LAW;
+			p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE;
+			break;
+		case SNDRV_PCM_FORMAT_IMA_ADPCM:
+			err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_ADPCM_INIT,
+						       SNDRV_SB_CSP_LOAD_INITBLOCK);
+			if (err)
+				break;
+			if (play_rec_mode == SNDRV_SB_CSP_MODE_DSP_WRITE) {
+				err = snd_sb_csp_firmware_load
+					(p, CSP_PROGRAM_ADPCM_PLAYBACK, 0);
+				p->mode = SNDRV_SB_CSP_MODE_DSP_WRITE;
+			} else {
+				err = snd_sb_csp_firmware_load
+					(p, CSP_PROGRAM_ADPCM_CAPTURE, 0);
+				p->mode = SNDRV_SB_CSP_MODE_DSP_READ;
+			}
+			p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM;
+			break;				  
+		default:
+			/* Decouple CSP from IRQ and DMAREQ lines */
+			if (p->running & SNDRV_SB_CSP_ST_AUTO) {
+				spin_lock_irqsave(&p->chip->reg_lock, flags);
+				set_mode_register(p->chip, 0xfc);
+				set_mode_register(p->chip, 0x00);
+				spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+				p->running = 0;			/* clear autoloaded flag */
+			}
+			return -EINVAL;
+		}
+		if (err) {
+			p->acc_format = 0;
+			p->acc_channels = p->acc_width = p->acc_rates = 0;
+
+			p->running = 0;				/* clear autoloaded flag */
+			p->mode = 0;
+			return (err);
+		} else {
+			p->running = SNDRV_SB_CSP_ST_AUTO;	/* set autoloaded flag */
+			p->acc_width = SNDRV_SB_CSP_SAMPLE_16BIT;	/* only 16 bit data */
+			p->acc_channels = SNDRV_SB_CSP_MONO | SNDRV_SB_CSP_STEREO;
+			p->acc_rates = SNDRV_SB_CSP_RATE_ALL;	/* HW codecs accept all rates */
+		}   
+
+	}
+	return (p->running & SNDRV_SB_CSP_ST_AUTO) ? 0 : -ENXIO;
+}
+
+/*
+ * start CSP
+ */
+static int snd_sb_csp_start(struct snd_sb_csp * p, int sample_width, int channels)
+{
+	unsigned char s_type;	/* sample type */
+	unsigned char mixL, mixR;
+	int result = -EIO;
+	unsigned long flags;
+
+	if (!(p->running & (SNDRV_SB_CSP_ST_LOADED | SNDRV_SB_CSP_ST_AUTO))) {
+		snd_printd("%s: Microcode not loaded\n", __func__);
+		return -ENXIO;
+	}
+	if (p->running & SNDRV_SB_CSP_ST_RUNNING) {
+		snd_printd("%s: CSP already running\n", __func__);
+		return -EBUSY;
+	}
+	if (!(sample_width & p->acc_width)) {
+		snd_printd("%s: Unsupported PCM sample width\n", __func__);
+		return -EINVAL;
+	}
+	if (!(channels & p->acc_channels)) {
+		snd_printd("%s: Invalid number of channels\n", __func__);
+		return -EINVAL;
+	}
+
+	/* Mute PCM volume */
+	spin_lock_irqsave(&p->chip->mixer_lock, flags);
+	mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV);
+	mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7);
+
+	spin_lock(&p->chip->reg_lock);
+	set_mode_register(p->chip, 0xc0);	/* c0 = STOP */
+	set_mode_register(p->chip, 0x70);	/* 70 = RUN */
+
+	s_type = 0x00;
+	if (channels == SNDRV_SB_CSP_MONO)
+		s_type = 0x11;	/* 000n 000n    (n = 1 if mono) */
+	if (sample_width == SNDRV_SB_CSP_SAMPLE_8BIT)
+		s_type |= 0x22;	/* 00dX 00dX    (d = 1 if 8 bit samples) */
+
+	if (set_codec_parameter(p->chip, 0x81, s_type)) {
+		snd_printd("%s: Set sample type command failed\n", __func__);
+		goto __fail;
+	}
+	if (set_codec_parameter(p->chip, 0x80, 0x00)) {
+		snd_printd("%s: Codec start command failed\n", __func__);
+		goto __fail;
+	}
+	p->run_width = sample_width;
+	p->run_channels = channels;
+
+	p->running |= SNDRV_SB_CSP_ST_RUNNING;
+
+	if (p->mode & SNDRV_SB_CSP_MODE_QSOUND) {
+		set_codec_parameter(p->chip, 0xe0, 0x01);
+		/* enable QSound decoder */
+		set_codec_parameter(p->chip, 0x00, 0xff);
+		set_codec_parameter(p->chip, 0x01, 0xff);
+		p->running |= SNDRV_SB_CSP_ST_QSOUND;
+		/* set QSound startup value */
+		snd_sb_csp_qsound_transfer(p);
+	}
+	result = 0;
+
+      __fail:
+	spin_unlock(&p->chip->reg_lock);
+
+	/* restore PCM volume */
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR);
+	spin_unlock_irqrestore(&p->chip->mixer_lock, flags);
+
+	return result;
+}
+
+/*
+ * stop CSP
+ */
+static int snd_sb_csp_stop(struct snd_sb_csp * p)
+{
+	int result;
+	unsigned char mixL, mixR;
+	unsigned long flags;
+
+	if (!(p->running & SNDRV_SB_CSP_ST_RUNNING))
+		return 0;
+
+	/* Mute PCM volume */
+	spin_lock_irqsave(&p->chip->mixer_lock, flags);
+	mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV);
+	mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7);
+
+	spin_lock(&p->chip->reg_lock);
+	if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+		set_codec_parameter(p->chip, 0xe0, 0x01);
+		/* disable QSound decoder */
+		set_codec_parameter(p->chip, 0x00, 0x00);
+		set_codec_parameter(p->chip, 0x01, 0x00);
+
+		p->running &= ~SNDRV_SB_CSP_ST_QSOUND;
+	}
+	result = set_mode_register(p->chip, 0xc0);	/* c0 = STOP */
+	spin_unlock(&p->chip->reg_lock);
+
+	/* restore PCM volume */
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL);
+	snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR);
+	spin_unlock_irqrestore(&p->chip->mixer_lock, flags);
+
+	if (!(result))
+		p->running &= ~(SNDRV_SB_CSP_ST_PAUSED | SNDRV_SB_CSP_ST_RUNNING);
+	return result;
+}
+
+/*
+ * pause CSP codec and hold DMA transfer
+ */
+static int snd_sb_csp_pause(struct snd_sb_csp * p)
+{
+	int result;
+	unsigned long flags;
+
+	if (!(p->running & SNDRV_SB_CSP_ST_RUNNING))
+		return -EBUSY;
+
+	spin_lock_irqsave(&p->chip->reg_lock, flags);
+	result = set_codec_parameter(p->chip, 0x80, 0xff);
+	spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+	if (!(result))
+		p->running |= SNDRV_SB_CSP_ST_PAUSED;
+
+	return result;
+}
+
+/*
+ * restart CSP codec and resume DMA transfer
+ */
+static int snd_sb_csp_restart(struct snd_sb_csp * p)
+{
+	int result;
+	unsigned long flags;
+
+	if (!(p->running & SNDRV_SB_CSP_ST_PAUSED))
+		return -EBUSY;
+
+	spin_lock_irqsave(&p->chip->reg_lock, flags);
+	result = set_codec_parameter(p->chip, 0x80, 0x00);
+	spin_unlock_irqrestore(&p->chip->reg_lock, flags);
+	if (!(result))
+		p->running &= ~SNDRV_SB_CSP_ST_PAUSED;
+
+	return result;
+}
+
+/* ------------------------------ */
+
+/*
+ * QSound mixer control for PCM
+ */
+
+#define snd_sb_qsound_switch_info	snd_ctl_boolean_mono_info
+
+static int snd_sb_qsound_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = p->q_enabled ? 1 : 0;
+	return 0;
+}
+
+static int snd_sb_qsound_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval;
+	
+	nval = ucontrol->value.integer.value[0] & 0x01;
+	spin_lock_irqsave(&p->q_lock, flags);
+	change = p->q_enabled != nval;
+	p->q_enabled = nval;
+	spin_unlock_irqrestore(&p->q_lock, flags);
+	return change;
+}
+
+static int snd_sb_qsound_space_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+	return 0;
+}
+
+static int snd_sb_qsound_space_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&p->q_lock, flags);
+	ucontrol->value.integer.value[0] = p->qpos_left;
+	ucontrol->value.integer.value[1] = p->qpos_right;
+	spin_unlock_irqrestore(&p->q_lock, flags);
+	return 0;
+}
+
+static int snd_sb_qsound_space_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval1, nval2;
+	
+	nval1 = ucontrol->value.integer.value[0];
+	if (nval1 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT)
+		nval1 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+	nval2 = ucontrol->value.integer.value[1];
+	if (nval2 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT)
+		nval2 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT;
+	spin_lock_irqsave(&p->q_lock, flags);
+	change = p->qpos_left != nval1 || p->qpos_right != nval2;
+	p->qpos_left = nval1;
+	p->qpos_right = nval2;
+	p->qpos_changed = change;
+	spin_unlock_irqrestore(&p->q_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_sb_qsound_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "3D Control - Switch",
+	.info = snd_sb_qsound_switch_info,
+	.get = snd_sb_qsound_switch_get,
+	.put = snd_sb_qsound_switch_put
+};
+
+static struct snd_kcontrol_new snd_sb_qsound_space = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "3D Control - Space",
+	.info = snd_sb_qsound_space_info,
+	.get = snd_sb_qsound_space_get,
+	.put = snd_sb_qsound_space_put
+};
+
+static int snd_sb_qsound_build(struct snd_sb_csp * p)
+{
+	struct snd_card *card;
+	int err;
+
+	if (snd_BUG_ON(!p))
+		return -EINVAL;
+
+	card = p->chip->card;
+	p->qpos_left = p->qpos_right = SNDRV_SB_CSP_QSOUND_MAX_RIGHT / 2;
+	p->qpos_changed = 0;
+
+	spin_lock_init(&p->q_lock);
+
+	if ((err = snd_ctl_add(card, p->qsound_switch = snd_ctl_new1(&snd_sb_qsound_switch, p))) < 0)
+		goto __error;
+	if ((err = snd_ctl_add(card, p->qsound_space = snd_ctl_new1(&snd_sb_qsound_space, p))) < 0)
+		goto __error;
+
+	return 0;
+
+     __error:
+	snd_sb_qsound_destroy(p);
+	return err;
+}
+
+static void snd_sb_qsound_destroy(struct snd_sb_csp * p)
+{
+	struct snd_card *card;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!p))
+		return;
+
+	card = p->chip->card;	
+	
+	down_write(&card->controls_rwsem);
+	if (p->qsound_switch)
+		snd_ctl_remove(card, p->qsound_switch);
+	if (p->qsound_space)
+		snd_ctl_remove(card, p->qsound_space);
+	up_write(&card->controls_rwsem);
+
+	/* cancel pending transfer of QSound parameters */
+	spin_lock_irqsave (&p->q_lock, flags);
+	p->qpos_changed = 0;
+	spin_unlock_irqrestore (&p->q_lock, flags);
+}
+
+/*
+ * Transfer qsound parameters to CSP,
+ * function should be called from interrupt routine
+ */
+static int snd_sb_csp_qsound_transfer(struct snd_sb_csp * p)
+{
+	int err = -ENXIO;
+
+	spin_lock(&p->q_lock);
+	if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+		set_codec_parameter(p->chip, 0xe0, 0x01);
+		/* left channel */
+		set_codec_parameter(p->chip, 0x00, p->qpos_left);
+		set_codec_parameter(p->chip, 0x02, 0x00);
+		/* right channel */
+		set_codec_parameter(p->chip, 0x00, p->qpos_right);
+		set_codec_parameter(p->chip, 0x03, 0x00);
+		err = 0;
+	}
+	p->qpos_changed = 0;
+	spin_unlock(&p->q_lock);
+	return err;
+}
+
+/* ------------------------------ */
+
+/*
+ * proc interface
+ */
+static int init_proc_entry(struct snd_sb_csp * p, int device)
+{
+	char name[16];
+	struct snd_info_entry *entry;
+	sprintf(name, "cspD%d", device);
+	if (! snd_card_proc_new(p->chip->card, name, &entry))
+		snd_info_set_text_ops(entry, p, info_read);
+	return 0;
+}
+
+static void info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_sb_csp *p = entry->private_data;
+
+	snd_iprintf(buffer, "Creative Signal Processor [v%d.%d]\n", (p->version >> 4), (p->version & 0x0f));
+	snd_iprintf(buffer, "State: %cx%c%c%c\n", ((p->running & SNDRV_SB_CSP_ST_QSOUND) ? 'Q' : '-'),
+		    ((p->running & SNDRV_SB_CSP_ST_PAUSED) ? 'P' : '-'),
+		    ((p->running & SNDRV_SB_CSP_ST_RUNNING) ? 'R' : '-'),
+		    ((p->running & SNDRV_SB_CSP_ST_LOADED) ? 'L' : '-'));
+	if (p->running & SNDRV_SB_CSP_ST_LOADED) {
+		snd_iprintf(buffer, "Codec: %s [func #%d]\n", p->codec_name, p->func_nr);
+		snd_iprintf(buffer, "Sample rates: ");
+		if (p->acc_rates == SNDRV_SB_CSP_RATE_ALL) {
+			snd_iprintf(buffer, "All\n");
+		} else {
+			snd_iprintf(buffer, "%s%s%s%s\n",
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_8000) ? "8000Hz " : ""),
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_11025) ? "11025Hz " : ""),
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_22050) ? "22050Hz " : ""),
+				    ((p->acc_rates & SNDRV_SB_CSP_RATE_44100) ? "44100Hz" : ""));
+		}
+		if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
+			snd_iprintf(buffer, "QSound decoder %sabled\n",
+				    p->q_enabled ? "en" : "dis");
+		} else {
+			snd_iprintf(buffer, "PCM format ID: 0x%x (%s/%s) [%s/%s] [%s/%s]\n",
+				    p->acc_format,
+				    ((p->acc_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? "16bit" : "-"),
+				    ((p->acc_width & SNDRV_SB_CSP_SAMPLE_8BIT) ? "8bit" : "-"),
+				    ((p->acc_channels & SNDRV_SB_CSP_MONO) ? "mono" : "-"),
+				    ((p->acc_channels & SNDRV_SB_CSP_STEREO) ? "stereo" : "-"),
+				    ((p->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) ? "playback" : "-"),
+				    ((p->mode & SNDRV_SB_CSP_MODE_DSP_READ) ? "capture" : "-"));
+		}
+	}
+	if (p->running & SNDRV_SB_CSP_ST_AUTO) {
+		snd_iprintf(buffer, "Autoloaded Mu-Law, A-Law or Ima-ADPCM hardware codec\n");
+	}
+	if (p->running & SNDRV_SB_CSP_ST_RUNNING) {
+		snd_iprintf(buffer, "Processing %dbit %s PCM samples\n",
+			    ((p->run_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? 16 : 8),
+			    ((p->run_channels & SNDRV_SB_CSP_MONO) ? "mono" : "stereo"));
+	}
+	if (p->running & SNDRV_SB_CSP_ST_QSOUND) {
+		snd_iprintf(buffer, "Qsound position: left = 0x%x, right = 0x%x\n",
+			    p->qpos_left, p->qpos_right);
+	}
+}
+
+/* */
+
+EXPORT_SYMBOL(snd_sb_csp_new);
+
+/*
+ * INIT part
+ */
+
+static int __init alsa_sb_csp_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb_csp_exit(void)
+{
+}
+
+module_init(alsa_sb_csp_init)
+module_exit(alsa_sb_csp_exit)
diff --git a/linux/sound/isa/sb/sb16_main.c b/linux/sound/isa/sb/sb16_main.c
new file mode 100644
index 0000000..2a6cc1c
--- /dev/null
+++ b/linux/sound/isa/sb/sb16_main.c
@@ -0,0 +1,924 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for control of 16-bit SoundBlaster cards and clones
+ *  Note: This is very ugly hardware which uses one 8-bit DMA channel and
+ *        second 16-bit DMA channel. Unfortunately 8-bit DMA channel can't
+ *        transfer 16-bit samples and 16-bit DMA channels can't transfer
+ *        8-bit samples. This make full duplex more complicated than
+ *        can be... People, don't buy these soundcards for full 16-bit
+ *        duplex!!!
+ *  Note: 16-bit wide is assigned to first direction which made request.
+ *        With full duplex - playback is preferred with abstract layer.
+ *
+ *  Note: Some chip revisions have hardware bug. Changing capture
+ *        channel from full-duplex 8bit DMA to 16bit DMA will block
+ *        16bit DMA transfers from DSP chip (capture) until 8bit transfer
+ *        to DSP chip (playback) starts. This bug can be avoided with
+ *        "16bit DMA Allocation" setting set to Playback or Capture.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/sb16_csp.h>
+#include <sound/mpu401.h>
+#include <sound/control.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Routines for control of 16-bit SoundBlaster cards and clones");
+MODULE_LICENSE("GPL");
+
+#ifdef CONFIG_SND_SB16_CSP
+static void snd_sb16_csp_playback_prepare(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		struct snd_sb_csp *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) &&
+			    ((1U << runtime->format) == csp->acc_format)) {
+				/* Supported runtime PCM format for playback */
+				if (csp->ops.csp_use(csp) == 0) {
+					/* If CSP was successfully acquired */
+					goto __start_CSP;
+				}
+			} else if ((csp->mode & SNDRV_SB_CSP_MODE_QSOUND) && (csp->q_enabled)) {
+				/* QSound decoder is loaded and enabled */
+				if ((1 << runtime->format) & (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+							      SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE)) {
+					/* Only for simple PCM formats */
+					if (csp->ops.csp_use(csp) == 0) {
+						/* If CSP was successfully acquired */
+						goto __start_CSP;
+					}
+				}
+			}
+		} else if (csp->ops.csp_use(csp) == 0) {
+			/* Acquire CSP and try to autoload hardware codec */
+			if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_WRITE)) {
+				/* Unsupported format, release CSP */
+				csp->ops.csp_unuse(csp);
+			} else {
+		      __start_CSP:
+				/* Try to start CSP */
+				if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_PLAYBACK_16) ?
+						       SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT,
+						       (runtime->channels > 1) ?
+						       SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) {
+					/* Failed, release CSP */
+					csp->ops.csp_unuse(csp);
+				} else {
+					/* Success, CSP acquired and running */
+					chip->open = SNDRV_SB_CSP_MODE_DSP_WRITE;
+				}
+			}
+		}
+	}
+}
+
+static void snd_sb16_csp_capture_prepare(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		struct snd_sb_csp *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) &&
+			    ((1U << runtime->format) == csp->acc_format)) {
+				/* Supported runtime PCM format for capture */
+				if (csp->ops.csp_use(csp) == 0) {
+					/* If CSP was successfully acquired */
+					goto __start_CSP;
+				}
+			}
+		} else if (csp->ops.csp_use(csp) == 0) {
+			/* Acquire CSP and try to autoload hardware codec */
+			if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_READ)) {
+				/* Unsupported format, release CSP */
+				csp->ops.csp_unuse(csp);
+			} else {
+		      __start_CSP:
+				/* Try to start CSP */
+				if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_CAPTURE_16) ?
+						       SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT,
+						       (runtime->channels > 1) ?
+						       SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) {
+					/* Failed, release CSP */
+					csp->ops.csp_unuse(csp);
+				} else {
+					/* Success, CSP acquired and running */
+					chip->open = SNDRV_SB_CSP_MODE_DSP_READ;
+				}
+			}
+		}
+	}
+}
+
+static void snd_sb16_csp_update(struct snd_sb *chip)
+{
+	if (chip->hardware == SB_HW_16CSP) {
+		struct snd_sb_csp *csp = chip->csp;
+
+		if (csp->qpos_changed) {
+			spin_lock(&chip->reg_lock);
+			csp->ops.csp_qsound_transfer (csp);
+			spin_unlock(&chip->reg_lock);
+		}
+	}
+}
+
+static void snd_sb16_csp_playback_open(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+	/* CSP decoders (QSound excluded) support only 16bit transfers */
+	if (chip->hardware == SB_HW_16CSP) {
+		struct snd_sb_csp *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if (csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) {
+				runtime->hw.formats |= csp->acc_format;
+			}
+		} else {
+			/* autoloaded codecs */
+			runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+					       SNDRV_PCM_FMTBIT_IMA_ADPCM;
+		}
+	}
+}
+
+static void snd_sb16_csp_playback_close(struct snd_sb *chip)
+{
+	if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_WRITE)) {
+		struct snd_sb_csp *csp = chip->csp;
+
+		if (csp->ops.csp_stop(csp) == 0) {
+			csp->ops.csp_unuse(csp);
+			chip->open = 0;
+		}
+	}
+}
+
+static void snd_sb16_csp_capture_open(struct snd_sb *chip, struct snd_pcm_runtime *runtime)
+{
+	/* CSP coders support only 16bit transfers */
+	if (chip->hardware == SB_HW_16CSP) {
+		struct snd_sb_csp *csp = chip->csp;
+
+		if (csp->running & SNDRV_SB_CSP_ST_LOADED) {
+			/* manually loaded codec */
+			if (csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) {
+				runtime->hw.formats |= csp->acc_format;
+			}
+		} else {
+			/* autoloaded codecs */
+			runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW |
+					       SNDRV_PCM_FMTBIT_IMA_ADPCM;
+		}
+	}
+}
+
+static void snd_sb16_csp_capture_close(struct snd_sb *chip)
+{
+	if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_READ)) {
+		struct snd_sb_csp *csp = chip->csp;
+
+		if (csp->ops.csp_stop(csp) == 0) {
+			csp->ops.csp_unuse(csp);
+			chip->open = 0;
+		}
+	}
+}
+#else
+#define snd_sb16_csp_playback_prepare(chip, runtime)	/*nop*/
+#define snd_sb16_csp_capture_prepare(chip, runtime)	/*nop*/
+#define snd_sb16_csp_update(chip)			/*nop*/
+#define snd_sb16_csp_playback_open(chip, runtime)	/*nop*/
+#define snd_sb16_csp_playback_close(chip)		/*nop*/
+#define snd_sb16_csp_capture_open(chip, runtime)	/*nop*/
+#define snd_sb16_csp_capture_close(chip)      	 	/*nop*/
+#endif
+
+
+static void snd_sb16_setup_rate(struct snd_sb *chip,
+				unsigned short rate,
+				int channel)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->mode & (channel == SNDRV_PCM_STREAM_PLAYBACK ? SB_MODE_PLAYBACK_16 : SB_MODE_CAPTURE_16))
+		snd_sb_ack_16bit(chip);
+	else
+		snd_sb_ack_8bit(chip);
+	if (!(chip->mode & SB_RATE_LOCK)) {
+		chip->locked_rate = rate;
+		snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_IN);
+		snd_sbdsp_command(chip, rate >> 8);
+		snd_sbdsp_command(chip, rate & 0xff);
+		snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_OUT);
+		snd_sbdsp_command(chip, rate >> 8);
+		snd_sbdsp_command(chip, rate & 0xff);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static int snd_sb16_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_sb16_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_sb16_playback_prepare(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned char format;
+	unsigned int size, count, dma;
+
+	snd_sb16_csp_playback_prepare(chip, runtime);
+	if (snd_pcm_format_unsigned(runtime->format) > 0) {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
+	} else {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
+	}
+
+	snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_PLAYBACK);
+	size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16;
+	snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+
+	count = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->mode & SB_MODE_PLAYBACK_16) {
+		count >>= 1;
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_OUT16_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+	} else {
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_OUT8_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_playback_trigger(struct snd_pcm_substream *substream,
+				     int cmd)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		chip->mode |= SB_RATE_LOCK_PLAYBACK;
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
+		/* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
+		if (chip->mode & SB_RATE_LOCK_CAPTURE)
+			snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		chip->mode &= ~SB_RATE_LOCK_PLAYBACK;
+		break;
+	default:
+		result = -EINVAL;
+	}
+	spin_unlock(&chip->reg_lock);
+	return result;
+}
+
+static int snd_sb16_capture_prepare(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned char format;
+	unsigned int size, count, dma;
+
+	snd_sb16_csp_capture_prepare(chip, runtime);
+	if (snd_pcm_format_unsigned(runtime->format) > 0) {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO;
+	} else {
+		format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO;
+	}
+	snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_CAPTURE);
+	size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16;
+	snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+
+	count = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->mode & SB_MODE_CAPTURE_16) {
+		count >>= 1;
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_IN16_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+	} else {
+		count--;
+		snd_sbdsp_command(chip, SB_DSP4_IN8_AI);
+		snd_sbdsp_command(chip, format);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+		snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_capture_trigger(struct snd_pcm_substream *substream,
+				    int cmd)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		chip->mode |= SB_RATE_LOCK_CAPTURE;
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF);
+		/* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */
+		if (chip->mode & SB_RATE_LOCK_PLAYBACK)
+			snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON);
+		chip->mode &= ~SB_RATE_LOCK_CAPTURE;
+		break;
+	default:
+		result = -EINVAL;
+	}
+	spin_unlock(&chip->reg_lock);
+	return result;
+}
+
+irqreturn_t snd_sb16dsp_interrupt(int irq, void *dev_id)
+{
+	struct snd_sb *chip = dev_id;
+	unsigned char status;
+	int ok;
+
+	spin_lock(&chip->mixer_lock);
+	status = snd_sbmixer_read(chip, SB_DSP4_IRQSTATUS);
+	spin_unlock(&chip->mixer_lock);
+	if ((status & SB_IRQTYPE_MPUIN) && chip->rmidi_callback)
+		chip->rmidi_callback(irq, chip->rmidi->private_data);
+	if (status & SB_IRQTYPE_8BIT) {
+		ok = 0;
+		if (chip->mode & SB_MODE_PLAYBACK_8) {
+			snd_pcm_period_elapsed(chip->playback_substream);
+			snd_sb16_csp_update(chip);
+			ok++;
+		}
+		if (chip->mode & SB_MODE_CAPTURE_8) {
+			snd_pcm_period_elapsed(chip->capture_substream);
+			ok++;
+		}
+		spin_lock(&chip->reg_lock);
+		if (!ok)
+			snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+		snd_sb_ack_8bit(chip);
+		spin_unlock(&chip->reg_lock);
+	}
+	if (status & SB_IRQTYPE_16BIT) {
+		ok = 0;
+		if (chip->mode & SB_MODE_PLAYBACK_16) {
+			snd_pcm_period_elapsed(chip->playback_substream);
+			snd_sb16_csp_update(chip);
+			ok++;
+		}
+		if (chip->mode & SB_MODE_CAPTURE_16) {
+			snd_pcm_period_elapsed(chip->capture_substream);
+			ok++;
+		}
+		spin_lock(&chip->reg_lock);
+		if (!ok)
+			snd_sbdsp_command(chip, SB_DSP_DMA16_OFF);
+		snd_sb_ack_16bit(chip);
+		spin_unlock(&chip->reg_lock);
+	}
+	return IRQ_HANDLED;
+}
+
+/*
+
+ */
+
+static snd_pcm_uframes_t snd_sb16_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	unsigned int dma;
+	size_t ptr;
+
+	dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16;
+	ptr = snd_dma_pointer(dma, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_sb16_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	unsigned int dma;
+	size_t ptr;
+
+	dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16;
+	ptr = snd_dma_pointer(dma, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static struct snd_pcm_hardware snd_sb16_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		0,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		4000,
+	.rate_max =		44100,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_sb16_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		0,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		4000,
+	.rate_max =		44100,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *  open/close
+ */
+
+static int snd_sb16_playback_open(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->mode & SB_MODE_PLAYBACK) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	runtime->hw = snd_sb16_playback;
+
+	/* skip if 16 bit DMA was reserved for capture */
+	if (chip->force_mode16 & SB_MODE_CAPTURE_16)
+		goto __skip_16bit;
+
+	if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_CAPTURE_16)) {
+		chip->mode |= SB_MODE_PLAYBACK_16;
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+		/* Vibra16X hack */
+		if (chip->dma16 <= 3) {
+			runtime->hw.buffer_bytes_max =
+			runtime->hw.period_bytes_max = 64 * 1024;
+		} else {
+			snd_sb16_csp_playback_open(chip, runtime);
+		}
+		goto __open_ok;
+	}
+
+      __skip_16bit:
+	if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_CAPTURE_8)) {
+		chip->mode |= SB_MODE_PLAYBACK_8;
+		/* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */
+		if (chip->dma16 < 0) {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+			chip->mode |= SB_MODE_PLAYBACK_16;
+		} else {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8;
+		}
+		runtime->hw.buffer_bytes_max =
+		runtime->hw.period_bytes_max = 64 * 1024;
+		goto __open_ok;
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return -EAGAIN;
+
+      __open_ok:
+	if (chip->hardware == SB_HW_ALS100)
+		runtime->hw.rate_max = 48000;
+	if (chip->hardware == SB_HW_CS5530) {
+		runtime->hw.buffer_bytes_max = 32 * 1024;
+		runtime->hw.periods_min = 2;
+		runtime->hw.rate_min = 44100;
+	}
+	if (chip->mode & SB_RATE_LOCK)
+		runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate;
+	chip->playback_substream = substream;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_playback_close(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+
+	snd_sb16_csp_playback_close(chip);
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->playback_substream = NULL;
+	chip->mode &= ~SB_MODE_PLAYBACK;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_capture_open(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->mode & SB_MODE_CAPTURE) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	runtime->hw = snd_sb16_capture;
+
+	/* skip if 16 bit DMA was reserved for playback */
+	if (chip->force_mode16 & SB_MODE_PLAYBACK_16)
+		goto __skip_16bit;
+
+	if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_16)) {
+		chip->mode |= SB_MODE_CAPTURE_16;
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+		/* Vibra16X hack */
+		if (chip->dma16 <= 3) {
+			runtime->hw.buffer_bytes_max =
+			runtime->hw.period_bytes_max = 64 * 1024;
+		} else {
+			snd_sb16_csp_capture_open(chip, runtime);
+		}
+		goto __open_ok;
+	}
+
+      __skip_16bit:
+	if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_8)) {
+		chip->mode |= SB_MODE_CAPTURE_8;
+		/* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */
+		if (chip->dma16 < 0) {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE;
+			chip->mode |= SB_MODE_CAPTURE_16;
+		} else {
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8;
+		}
+		runtime->hw.buffer_bytes_max =
+		runtime->hw.period_bytes_max = 64 * 1024;
+		goto __open_ok;
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return -EAGAIN;
+
+      __open_ok:
+	if (chip->hardware == SB_HW_ALS100)
+		runtime->hw.rate_max = 48000;
+	if (chip->hardware == SB_HW_CS5530) {
+		runtime->hw.buffer_bytes_max = 32 * 1024;
+		runtime->hw.periods_min = 2;
+		runtime->hw.rate_min = 44100;
+	}
+	if (chip->mode & SB_RATE_LOCK)
+		runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate;
+	chip->capture_substream = substream;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_capture_close(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+
+	snd_sb16_csp_capture_close(chip);
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->capture_substream = NULL;
+	chip->mode &= ~SB_MODE_CAPTURE;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+/*
+ *  DMA control interface
+ */
+
+static int snd_sb16_set_dma_mode(struct snd_sb *chip, int what)
+{
+	if (chip->dma8 < 0 || chip->dma16 < 0) {
+		if (snd_BUG_ON(what))
+			return -EINVAL;
+		return 0;
+	}
+	if (what == 0) {
+		chip->force_mode16 = 0;
+	} else if (what == 1) {
+		chip->force_mode16 = SB_MODE_PLAYBACK_16;
+	} else if (what == 2) {
+		chip->force_mode16 = SB_MODE_CAPTURE_16;
+	} else {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int snd_sb16_get_dma_mode(struct snd_sb *chip)
+{
+	if (chip->dma8 < 0 || chip->dma16 < 0)
+		return 0;
+	switch (chip->force_mode16) {
+	case SB_MODE_PLAYBACK_16:
+		return 1;
+	case SB_MODE_CAPTURE_16:
+		return 2;
+	default:
+		return 0;
+	}
+}
+
+static int snd_sb16_dma_control_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[3] = {
+		"Auto", "Playback", "Capture"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_sb16_dma_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.enumerated.item[0] = snd_sb16_get_dma_mode(chip);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_sb16_dma_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char nval, oval;
+	int change;
+	
+	if ((nval = ucontrol->value.enumerated.item[0]) > 2)
+		return -EINVAL;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	oval = snd_sb16_get_dma_mode(chip);
+	change = nval != oval;
+	snd_sb16_set_dma_mode(chip, nval);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_sb16_dma_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.name = "16-bit DMA Allocation",
+	.info = snd_sb16_dma_control_info,
+	.get = snd_sb16_dma_control_get,
+	.put = snd_sb16_dma_control_put
+};
+
+/*
+ *  Initialization part
+ */
+ 
+int snd_sb16dsp_configure(struct snd_sb * chip)
+{
+	unsigned long flags;
+	unsigned char irqreg = 0, dmareg = 0, mpureg;
+	unsigned char realirq, realdma, realmpureg;
+	/* note: mpu register should be present only on SB16 Vibra soundcards */
+
+	// printk(KERN_DEBUG "codec->irq=%i, codec->dma8=%i, codec->dma16=%i\n", chip->irq, chip->dma8, chip->dma16);
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	mpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP) & ~0x06;
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	switch (chip->irq) {
+	case 2:
+	case 9:
+		irqreg |= SB_IRQSETUP_IRQ9;
+		break;
+	case 5:
+		irqreg |= SB_IRQSETUP_IRQ5;
+		break;
+	case 7:
+		irqreg |= SB_IRQSETUP_IRQ7;
+		break;
+	case 10:
+		irqreg |= SB_IRQSETUP_IRQ10;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (chip->dma8 >= 0) {
+		switch (chip->dma8) {
+		case 0:
+			dmareg |= SB_DMASETUP_DMA0;
+			break;
+		case 1:
+			dmareg |= SB_DMASETUP_DMA1;
+			break;
+		case 3:
+			dmareg |= SB_DMASETUP_DMA3;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) {
+		switch (chip->dma16) {
+		case 5:
+			dmareg |= SB_DMASETUP_DMA5;
+			break;
+		case 6:
+			dmareg |= SB_DMASETUP_DMA6;
+			break;
+		case 7:
+			dmareg |= SB_DMASETUP_DMA7;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	switch (chip->mpu_port) {
+	case 0x300:
+		mpureg |= 0x04;
+		break;
+	case 0x330:
+		mpureg |= 0x00;
+		break;
+	default:
+		mpureg |= 0x02;	/* disable MPU */
+	}
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+
+	snd_sbmixer_write(chip, SB_DSP4_IRQSETUP, irqreg);
+	realirq = snd_sbmixer_read(chip, SB_DSP4_IRQSETUP);
+
+	snd_sbmixer_write(chip, SB_DSP4_DMASETUP, dmareg);
+	realdma = snd_sbmixer_read(chip, SB_DSP4_DMASETUP);
+
+	snd_sbmixer_write(chip, SB_DSP4_MPUSETUP, mpureg);
+	realmpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP);
+
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	if ((~realirq) & irqreg || (~realdma) & dmareg) {
+		snd_printk(KERN_ERR "SB16 [0x%lx]: unable to set DMA & IRQ (PnP device?)\n", chip->port);
+		snd_printk(KERN_ERR "SB16 [0x%lx]: wanted: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, realirq, realdma, realmpureg);
+		snd_printk(KERN_ERR "SB16 [0x%lx]:    got: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, irqreg, dmareg, mpureg);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static struct snd_pcm_ops snd_sb16_playback_ops = {
+	.open =		snd_sb16_playback_open,
+	.close =	snd_sb16_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_sb16_hw_params,
+	.hw_free =	snd_sb16_hw_free,
+	.prepare =	snd_sb16_playback_prepare,
+	.trigger =	snd_sb16_playback_trigger,
+	.pointer =	snd_sb16_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_sb16_capture_ops = {
+	.open =		snd_sb16_capture_open,
+	.close =	snd_sb16_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_sb16_hw_params,
+	.hw_free =	snd_sb16_hw_free,
+	.prepare =	snd_sb16_capture_prepare,
+	.trigger =	snd_sb16_capture_trigger,
+	.pointer =	snd_sb16_capture_pointer,
+};
+
+int snd_sb16dsp_pcm(struct snd_sb * chip, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_card *card = chip->card;
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(card, "SB16 DSP", device, 1, 1, &pcm)) < 0)
+		return err;
+	sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff);
+	pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb16_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb16_capture_ops);
+
+	if (chip->dma16 >= 0 && chip->dma8 != chip->dma16)
+		snd_ctl_add(card, snd_ctl_new1(&snd_sb16_dma_control, chip));
+	else
+		pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+const struct snd_pcm_ops *snd_sb16dsp_get_pcm_ops(int direction)
+{
+	return direction == SNDRV_PCM_STREAM_PLAYBACK ?
+		&snd_sb16_playback_ops : &snd_sb16_capture_ops;
+}
+
+EXPORT_SYMBOL(snd_sb16dsp_pcm);
+EXPORT_SYMBOL(snd_sb16dsp_get_pcm_ops);
+EXPORT_SYMBOL(snd_sb16dsp_configure);
+EXPORT_SYMBOL(snd_sb16dsp_interrupt);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_sb16_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb16_exit(void)
+{
+}
+
+module_init(alsa_sb16_init)
+module_exit(alsa_sb16_exit)
diff --git a/linux/sound/isa/sb/sb8.c b/linux/sound/isa/sb/sb8.c
new file mode 100644
index 0000000..2259e3f
--- /dev/null
+++ b/linux/sound/isa/sb/sb8.c
@@ -0,0 +1,268 @@
+/*
+ *  Driver for SoundBlaster 1.0/2.0/Pro soundcards and compatible
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/ioport.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Sound Blaster 1.0/2.0/Pro");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 1.0/SB 2.0/SB Pro}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5,7,9,10 */
+static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Sound Blaster soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Sound Blaster soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Sound Blaster soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SB8 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SB8 driver.");
+module_param_array(dma8, int, NULL, 0444);
+MODULE_PARM_DESC(dma8, "8-bit DMA # for SB8 driver.");
+
+struct snd_sb8 {
+	struct resource *fm_res;	/* used to block FM i/o region for legacy cards */
+	struct snd_sb *chip;
+};
+
+static irqreturn_t snd_sb8_interrupt(int irq, void *dev_id)
+{
+	struct snd_sb *chip = dev_id;
+
+	if (chip->open & SB_OPEN_PCM) {
+		return snd_sb8dsp_interrupt(chip);
+	} else {
+		return snd_sb8dsp_midi_interrupt(chip);
+	}
+}
+
+static void snd_sb8_free(struct snd_card *card)
+{
+	struct snd_sb8 *acard = card->private_data;
+
+	if (acard == NULL)
+		return;
+	release_and_free_resource(acard->fm_res);
+}
+
+static int __devinit snd_sb8_match(struct device *pdev, unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+	if (irq[dev] == SNDRV_AUTO_IRQ) {
+		dev_err(pdev, "please specify irq\n");
+		return 0;
+	}
+	if (dma8[dev] == SNDRV_AUTO_DMA) {
+		dev_err(pdev, "please specify dma8\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int __devinit snd_sb8_probe(struct device *pdev, unsigned int dev)
+{
+	struct snd_sb *chip;
+	struct snd_card *card;
+	struct snd_sb8 *acard;
+	struct snd_opl3 *opl3;
+	int err;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_sb8), &card);
+	if (err < 0)
+		return err;
+	acard = card->private_data;
+	card->private_free = snd_sb8_free;
+
+	/* block the 0x388 port to avoid PnP conflicts */
+	acard->fm_res = request_region(0x388, 4, "SoundBlaster FM");
+
+	if (port[dev] != SNDRV_AUTO_PORT) {
+		if ((err = snd_sbdsp_create(card, port[dev], irq[dev],
+					    snd_sb8_interrupt,
+					    dma8[dev],
+					    -1,
+					    SB_HW_AUTO,
+					    &chip)) < 0)
+			goto _err;
+	} else {
+		/* auto-probe legacy ports */
+		static unsigned long possible_ports[] = {
+			0x220, 0x240, 0x260,
+		};
+		int i;
+		for (i = 0; i < ARRAY_SIZE(possible_ports); i++) {
+			err = snd_sbdsp_create(card, possible_ports[i],
+					       irq[dev],
+					       snd_sb8_interrupt,
+					       dma8[dev],
+					       -1,
+					       SB_HW_AUTO,
+					       &chip);
+			if (err >= 0) {
+				port[dev] = possible_ports[i];
+				break;
+			}
+		}
+		if (i >= ARRAY_SIZE(possible_ports)) {
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	acard->chip = chip;
+			
+	if (chip->hardware >= SB_HW_16) {
+		if (chip->hardware == SB_HW_ALS100)
+			snd_printk(KERN_WARNING "ALS100 chip detected at 0x%lx, try snd-als100 module\n",
+				    port[dev]);
+		else
+			snd_printk(KERN_WARNING "SB 16 chip detected at 0x%lx, try snd-sb16 module\n",
+				   port[dev]);
+		err = -ENODEV;
+		goto _err;
+	}
+
+	if ((err = snd_sb8dsp_pcm(chip, 0, NULL)) < 0)
+		goto _err;
+
+	if ((err = snd_sbmixer_new(chip)) < 0)
+		goto _err;
+
+	if (chip->hardware == SB_HW_10 || chip->hardware == SB_HW_20) {
+		if ((err = snd_opl3_create(card, chip->port + 8, 0,
+					   OPL3_HW_AUTO, 1,
+					   &opl3)) < 0) {
+			snd_printk(KERN_WARNING "sb8: no OPL device at 0x%lx\n", chip->port + 8);
+		}
+	} else {
+		if ((err = snd_opl3_create(card, chip->port, chip->port + 2,
+					   OPL3_HW_AUTO, 1,
+					   &opl3)) < 0) {
+			snd_printk(KERN_WARNING "sb8: no OPL device at 0x%lx-0x%lx\n",
+				   chip->port, chip->port + 2);
+		}
+	}
+	if (err >= 0) {
+		if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0)
+			goto _err;
+	}
+
+	if ((err = snd_sb8dsp_midi(chip, 0, NULL)) < 0)
+		goto _err;
+
+	strcpy(card->driver, chip->hardware == SB_HW_PRO ? "SB Pro" : "SB8");
+	strcpy(card->shortname, chip->name);
+	sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d",
+		chip->name,
+		chip->port,
+		irq[dev], dma8[dev]);
+
+	snd_card_set_dev(card, pdev);
+
+	if ((err = snd_card_register(card)) < 0)
+		goto _err;
+
+	dev_set_drvdata(pdev, card);
+	return 0;
+
+ _err:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit snd_sb8_remove(struct device *pdev, unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(pdev));
+	dev_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_sb8_suspend(struct device *dev, unsigned int n,
+			   pm_message_t state)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_sb8 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_sbmixer_suspend(chip);
+	return 0;
+}
+
+static int snd_sb8_resume(struct device *dev, unsigned int n)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct snd_sb8 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_sbdsp_reset(chip);
+	snd_sbmixer_resume(chip);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+#define DEV_NAME "sb8"
+
+static struct isa_driver snd_sb8_driver = {
+	.match		= snd_sb8_match,
+	.probe		= snd_sb8_probe,
+	.remove		= __devexit_p(snd_sb8_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_sb8_suspend,
+	.resume		= snd_sb8_resume,
+#endif
+	.driver		= {
+		.name	= DEV_NAME 
+	},
+};
+
+static int __init alsa_card_sb8_init(void)
+{
+	return isa_register_driver(&snd_sb8_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_sb8_exit(void)
+{
+	isa_unregister_driver(&snd_sb8_driver);
+}
+
+module_init(alsa_card_sb8_init)
+module_exit(alsa_card_sb8_exit)
diff --git a/linux/sound/isa/sb/sb8_main.c b/linux/sound/isa/sb/sb8_main.c
new file mode 100644
index 0000000..7d84c9f
--- /dev/null
+++ b/linux/sound/isa/sb/sb8_main.c
@@ -0,0 +1,645 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *                   Uros Bizjak <uros@kss-loka.si>
+ *
+ *  Routines for control of 8-bit SoundBlaster cards and clones
+ *  Please note: I don't have access to old SB8 soundcards.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * --
+ *
+ * Thu Apr 29 20:36:17 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
+ *   DSP can't respond to commands whilst in "high speed" mode. Caused 
+ *   glitching during playback. Fixed.
+ *
+ * Wed Jul 12 22:02:55 CEST 2000 Uros Bizjak <uros@kss-loka.si>
+ *   Cleaned up and rewrote lowlevel routines.
+ */
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Uros Bizjak <uros@kss-loka.si>");
+MODULE_DESCRIPTION("Routines for control of 8-bit SoundBlaster cards and clones");
+MODULE_LICENSE("GPL");
+
+#define SB8_CLOCK	1000000
+#define SB8_DEN(v)	((SB8_CLOCK + (v) / 2) / (v))
+#define SB8_RATE(v)	(SB8_CLOCK / SB8_DEN(v))
+
+static struct snd_ratnum clock = {
+	.num = SB8_CLOCK,
+	.den_min = 1,
+	.den_max = 256,
+	.den_step = 1,
+};
+
+static struct snd_pcm_hw_constraint_ratnums hw_constraints_clock = {
+	.nrats = 1,
+	.rats = &clock,
+};
+
+static struct snd_ratnum stereo_clocks[] = {
+	{
+		.num = SB8_CLOCK,
+		.den_min = SB8_DEN(22050),
+		.den_max = SB8_DEN(22050),
+		.den_step = 1,
+	},
+	{
+		.num = SB8_CLOCK,
+		.den_min = SB8_DEN(11025),
+		.den_max = SB8_DEN(11025),
+		.den_step = 1,
+	}
+};
+
+static int snd_sb8_hw_constraint_rate_channels(struct snd_pcm_hw_params *params,
+					       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	if (c->min > 1) {
+	  	unsigned int num = 0, den = 0;
+		int err = snd_interval_ratnum(hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE),
+					  2, stereo_clocks, &num, &den);
+		if (err >= 0 && den) {
+			params->rate_num = num;
+			params->rate_den = den;
+		}
+		return err;
+	}
+	return 0;
+}
+
+static int snd_sb8_hw_constraint_channels_rate(struct snd_pcm_hw_params *params,
+					       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	if (r->min > SB8_RATE(22050) || r->max <= SB8_RATE(11025)) {
+		struct snd_interval t = { .min = 1, .max = 1 };
+		return snd_interval_refine(hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS), &t);
+	}
+	return 0;
+}
+
+static int snd_sb8_playback_prepare(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int mixreg, rate, size, count;
+	unsigned char format;
+	unsigned char stereo = runtime->channels > 1;
+	int dma;
+
+	rate = runtime->rate;
+	switch (chip->hardware) {
+	case SB_HW_JAZZ16:
+		if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) {
+			if (chip->mode & SB_MODE_CAPTURE_16)
+				return -EBUSY;
+			else
+				chip->mode |= SB_MODE_PLAYBACK_16;
+		}
+		chip->playback_format = SB_DSP_LO_OUTPUT_AUTO;
+		break;
+	case SB_HW_PRO:
+		if (runtime->channels > 1) {
+			if (snd_BUG_ON(rate != SB8_RATE(11025) &&
+				       rate != SB8_RATE(22050)))
+				return -EINVAL;
+			chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
+			break;
+		}
+		/* fallthru */
+	case SB_HW_201:
+		if (rate > 23000) {
+			chip->playback_format = SB_DSP_HI_OUTPUT_AUTO;
+			break;
+		}
+		/* fallthru */
+	case SB_HW_20:
+		chip->playback_format = SB_DSP_LO_OUTPUT_AUTO;
+		break;
+	case SB_HW_10:
+		chip->playback_format = SB_DSP_OUTPUT;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (chip->mode & SB_MODE_PLAYBACK_16) {
+		format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT;
+		dma = chip->dma16;
+	} else {
+		format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT;
+		chip->mode |= SB_MODE_PLAYBACK_8;
+		dma = chip->dma8;
+	}
+	size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	count = chip->p_period_size = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_sbdsp_command(chip, SB_DSP_SPEAKER_ON);
+	if (chip->hardware == SB_HW_JAZZ16)
+		snd_sbdsp_command(chip, format);
+	else if (stereo) {
+		/* set playback stereo mode */
+		spin_lock(&chip->mixer_lock);
+		mixreg = snd_sbmixer_read(chip, SB_DSP_STEREO_SW);
+		snd_sbmixer_write(chip, SB_DSP_STEREO_SW, mixreg | 0x02);
+		spin_unlock(&chip->mixer_lock);
+
+		/* Soundblaster hardware programming reference guide, 3-23 */
+		snd_sbdsp_command(chip, SB_DSP_DMA8_EXIT);
+		runtime->dma_area[0] = 0x80;
+		snd_dma_program(dma, runtime->dma_addr, 1, DMA_MODE_WRITE);
+		/* force interrupt */
+		snd_sbdsp_command(chip, SB_DSP_OUTPUT);
+		snd_sbdsp_command(chip, 0);
+		snd_sbdsp_command(chip, 0);
+	}
+	snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
+	if (stereo) {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den / 2);
+		spin_lock(&chip->mixer_lock);
+		/* save output filter status and turn it off */
+		mixreg = snd_sbmixer_read(chip, SB_DSP_PLAYBACK_FILT);
+		snd_sbmixer_write(chip, SB_DSP_PLAYBACK_FILT, mixreg | 0x20);
+		spin_unlock(&chip->mixer_lock);
+		/* just use force_mode16 for temporary storate... */
+		chip->force_mode16 = mixreg;
+	} else {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den);
+	}
+	if (chip->playback_format != SB_DSP_OUTPUT) {
+		if (chip->mode & SB_MODE_PLAYBACK_16)
+			count /= 2;
+		count--;
+		snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_dma_program(dma, runtime->dma_addr,
+			size, DMA_MODE_WRITE | DMA_AUTOINIT);
+	return 0;
+}
+
+static int snd_sb8_playback_trigger(struct snd_pcm_substream *substream,
+				    int cmd)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	unsigned int count;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_sbdsp_command(chip, chip->playback_format);
+		if (chip->playback_format == SB_DSP_OUTPUT) {
+			count = chip->p_period_size - 1;
+			snd_sbdsp_command(chip, count & 0xff);
+			snd_sbdsp_command(chip, count >> 8);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (chip->playback_format == SB_DSP_HI_OUTPUT_AUTO) {
+			struct snd_pcm_runtime *runtime = substream->runtime;
+			snd_sbdsp_reset(chip);
+			if (runtime->channels > 1) {
+				spin_lock(&chip->mixer_lock);
+				/* restore output filter and set hardware to mono mode */ 
+				snd_sbmixer_write(chip, SB_DSP_STEREO_SW, chip->force_mode16 & ~0x02);
+				spin_unlock(&chip->mixer_lock);
+			}
+		} else {
+			snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+		}
+		snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_sb8_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_sb8_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_sb8_capture_prepare(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int mixreg, rate, size, count;
+	unsigned char format;
+	unsigned char stereo = runtime->channels > 1;
+	int dma;
+
+	rate = runtime->rate;
+	switch (chip->hardware) {
+	case SB_HW_JAZZ16:
+		if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) {
+			if (chip->mode & SB_MODE_PLAYBACK_16)
+				return -EBUSY;
+			else
+				chip->mode |= SB_MODE_CAPTURE_16;
+		}
+		chip->capture_format = SB_DSP_LO_INPUT_AUTO;
+		break;
+	case SB_HW_PRO:
+		if (runtime->channels > 1) {
+			if (snd_BUG_ON(rate != SB8_RATE(11025) &&
+				       rate != SB8_RATE(22050)))
+				return -EINVAL;
+			chip->capture_format = SB_DSP_HI_INPUT_AUTO;
+			break;
+		}
+		chip->capture_format = (rate > 23000) ? SB_DSP_HI_INPUT_AUTO : SB_DSP_LO_INPUT_AUTO;
+		break;
+	case SB_HW_201:
+		if (rate > 13000) {
+			chip->capture_format = SB_DSP_HI_INPUT_AUTO;
+			break;
+		}
+		/* fallthru */
+	case SB_HW_20:
+		chip->capture_format = SB_DSP_LO_INPUT_AUTO;
+		break;
+	case SB_HW_10:
+		chip->capture_format = SB_DSP_INPUT;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (chip->mode & SB_MODE_CAPTURE_16) {
+		format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT;
+		dma = chip->dma16;
+	} else {
+		format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT;
+		chip->mode |= SB_MODE_CAPTURE_8;
+		dma = chip->dma8;
+	}
+	size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	count = chip->c_period_size = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+	if (chip->hardware == SB_HW_JAZZ16)
+		snd_sbdsp_command(chip, format);
+	else if (stereo)
+		snd_sbdsp_command(chip, SB_DSP_STEREO_8BIT);
+	snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE);
+	if (stereo) {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den / 2);
+		spin_lock(&chip->mixer_lock);
+		/* save input filter status and turn it off */
+		mixreg = snd_sbmixer_read(chip, SB_DSP_CAPTURE_FILT);
+		snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, mixreg | 0x20);
+		spin_unlock(&chip->mixer_lock);
+		/* just use force_mode16 for temporary storate... */
+		chip->force_mode16 = mixreg;
+	} else {
+		snd_sbdsp_command(chip, 256 - runtime->rate_den);
+	}
+	if (chip->capture_format != SB_DSP_INPUT) {
+		if (chip->mode & SB_MODE_PLAYBACK_16)
+			count /= 2;
+		count--;
+		snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE);
+		snd_sbdsp_command(chip, count & 0xff);
+		snd_sbdsp_command(chip, count >> 8);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_dma_program(dma, runtime->dma_addr,
+			size, DMA_MODE_READ | DMA_AUTOINIT);
+	return 0;
+}
+
+static int snd_sb8_capture_trigger(struct snd_pcm_substream *substream,
+				   int cmd)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	unsigned int count;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_sbdsp_command(chip, chip->capture_format);
+		if (chip->capture_format == SB_DSP_INPUT) {
+			count = chip->c_period_size - 1;
+			snd_sbdsp_command(chip, count & 0xff);
+			snd_sbdsp_command(chip, count >> 8);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (chip->capture_format == SB_DSP_HI_INPUT_AUTO) {
+			struct snd_pcm_runtime *runtime = substream->runtime;
+			snd_sbdsp_reset(chip);
+			if (runtime->channels > 1) {
+				/* restore input filter status */
+				spin_lock(&chip->mixer_lock);
+				snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, chip->force_mode16);
+				spin_unlock(&chip->mixer_lock);
+				/* set hardware to mono mode */
+				snd_sbdsp_command(chip, SB_DSP_MONO_8BIT);
+			}
+		} else {
+			snd_sbdsp_command(chip, SB_DSP_DMA8_OFF);
+		}
+		snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+irqreturn_t snd_sb8dsp_interrupt(struct snd_sb *chip)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+
+	snd_sb_ack_8bit(chip);
+	switch (chip->mode) {
+	case SB_MODE_PLAYBACK_16:	/* ok.. playback is active */
+		if (chip->hardware != SB_HW_JAZZ16)
+			break;
+		/* fallthru */
+	case SB_MODE_PLAYBACK_8:
+		substream = chip->playback_substream;
+		runtime = substream->runtime;
+		if (chip->playback_format == SB_DSP_OUTPUT)
+		    	snd_sb8_playback_trigger(substream, SNDRV_PCM_TRIGGER_START);
+		snd_pcm_period_elapsed(substream);
+		break;
+	case SB_MODE_CAPTURE_16:
+		if (chip->hardware != SB_HW_JAZZ16)
+			break;
+		/* fallthru */
+	case SB_MODE_CAPTURE_8:
+		substream = chip->capture_substream;
+		runtime = substream->runtime;
+		if (chip->capture_format == SB_DSP_INPUT)
+		    	snd_sb8_capture_trigger(substream, SNDRV_PCM_TRIGGER_START);
+		snd_pcm_period_elapsed(substream);
+		break;
+	}
+	return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_sb8_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	int dma;
+
+	if (chip->mode & SB_MODE_PLAYBACK_8)
+		dma = chip->dma8;
+	else if (chip->mode & SB_MODE_PLAYBACK_16)
+		dma = chip->dma16;
+	else
+		return 0;
+	ptr = snd_dma_pointer(dma, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_sb8_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	int dma;
+
+	if (chip->mode & SB_MODE_CAPTURE_8)
+		dma = chip->dma8;
+	else if (chip->mode & SB_MODE_CAPTURE_16)
+		dma = chip->dma16;
+	else
+		return 0;
+	ptr = snd_dma_pointer(dma, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static struct snd_pcm_hardware snd_sb8_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		 SNDRV_PCM_FMTBIT_U8,
+	.rates =		(SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
+				 SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050),
+	.rate_min =		4000,
+	.rate_max =		23000,
+	.channels_min =		1,
+	.channels_max =		1,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_sb8_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8,
+	.rates =		(SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 |
+				 SNDRV_PCM_RATE_11025),
+	.rate_min =		4000,
+	.rate_max =		13000,
+	.channels_min =		1,
+	.channels_max =		1,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *
+ */
+ 
+static int snd_sb8_open(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->open) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	chip->open |= SB_OPEN_PCM;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		chip->playback_substream = substream;
+		runtime->hw = snd_sb8_playback;
+	} else {
+		chip->capture_substream = substream;
+		runtime->hw = snd_sb8_capture;
+	}
+	switch (chip->hardware) {
+	case SB_HW_JAZZ16:
+		if (chip->dma16 == 5 || chip->dma16 == 7)
+			runtime->hw.formats |= SNDRV_PCM_FMTBIT_S16_LE;
+		runtime->hw.rates |= SNDRV_PCM_RATE_8000_48000;
+		runtime->hw.rate_min = 4000;
+		runtime->hw.rate_max = 50000;
+		runtime->hw.channels_max = 2;
+		break;
+	case SB_HW_PRO:
+		runtime->hw.rate_max = 44100;
+		runtime->hw.channels_max = 2;
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				    snd_sb8_hw_constraint_rate_channels, NULL,
+				    SNDRV_PCM_HW_PARAM_CHANNELS,
+				    SNDRV_PCM_HW_PARAM_RATE, -1);
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				     snd_sb8_hw_constraint_channels_rate, NULL,
+				     SNDRV_PCM_HW_PARAM_RATE, -1);
+		break;
+	case SB_HW_201:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			runtime->hw.rate_max = 44100;
+		} else {
+			runtime->hw.rate_max = 15000;
+		}
+	default:
+		break;
+	}
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &hw_constraints_clock);
+	if (chip->dma8 > 3 || chip->dma16 >= 0) {
+		snd_pcm_hw_constraint_step(runtime, 0,
+					   SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 2);
+		snd_pcm_hw_constraint_step(runtime, 0,
+					   SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 2);
+		runtime->hw.buffer_bytes_max = 128 * 1024 * 1024;
+		runtime->hw.period_bytes_max = 128 * 1024 * 1024;
+	}
+	return 0;	
+}
+
+static int snd_sb8_close(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	chip->capture_substream = NULL;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->open &= ~SB_OPEN_PCM;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		chip->mode &= ~SB_MODE_PLAYBACK;
+	else
+		chip->mode &= ~SB_MODE_CAPTURE;
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+	return 0;
+}
+
+/*
+ *  Initialization part
+ */
+ 
+static struct snd_pcm_ops snd_sb8_playback_ops = {
+	.open =			snd_sb8_open,
+	.close =		snd_sb8_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_sb8_hw_params,
+	.hw_free =		snd_sb8_hw_free,
+	.prepare =		snd_sb8_playback_prepare,
+	.trigger =		snd_sb8_playback_trigger,
+	.pointer =		snd_sb8_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_sb8_capture_ops = {
+	.open =			snd_sb8_open,
+	.close =		snd_sb8_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_sb8_hw_params,
+	.hw_free =		snd_sb8_hw_free,
+	.prepare =		snd_sb8_capture_prepare,
+	.trigger =		snd_sb8_capture_trigger,
+	.pointer =		snd_sb8_capture_pointer,
+};
+
+int snd_sb8dsp_pcm(struct snd_sb *chip, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_card *card = chip->card;
+	struct snd_pcm *pcm;
+	int err;
+	size_t max_prealloc = 64 * 1024;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(card, "SB8 DSP", device, 1, 1, &pcm)) < 0)
+		return err;
+	sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff);
+	pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb8_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb8_capture_ops);
+
+	if (chip->dma8 > 3 || chip->dma16 >= 0)
+		max_prealloc = 128 * 1024;
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, max_prealloc);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_sb8dsp_pcm);
+EXPORT_SYMBOL(snd_sb8dsp_interrupt);
+  /* sb8_midi.c */
+EXPORT_SYMBOL(snd_sb8dsp_midi_interrupt);
+EXPORT_SYMBOL(snd_sb8dsp_midi);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_sb8_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb8_exit(void)
+{
+}
+
+module_init(alsa_sb8_init)
+module_exit(alsa_sb8_exit)
diff --git a/linux/sound/isa/sb/sb8_midi.c b/linux/sound/isa/sb/sb8_midi.c
new file mode 100644
index 0000000..988a8b7
--- /dev/null
+++ b/linux/sound/isa/sb/sb8_midi.c
@@ -0,0 +1,286 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for control of SoundBlaster cards - MIDI interface
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * --
+ *
+ * Sun May  9 22:54:38 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
+ *   Fixed typo in snd_sb8dsp_midi_new_device which prevented midi from 
+ *   working.
+ *
+ * Sun May 11 12:34:56 UTC 2003 Clemens Ladisch <clemens@ladisch.de>
+ *   Added full duplex UART mode for DSP version 2.0 and later.
+ */
+
+#include <asm/io.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+
+
+irqreturn_t snd_sb8dsp_midi_interrupt(struct snd_sb *chip)
+{
+	struct snd_rawmidi *rmidi;
+	int max = 64;
+	char byte;
+
+	if (!chip)
+		return IRQ_NONE;
+	
+	rmidi = chip->rmidi;
+	if (!rmidi) {
+		inb(SBP(chip, DATA_AVAIL));	/* ack interrupt */
+		return IRQ_NONE;
+	}
+
+	spin_lock(&chip->midi_input_lock);
+	while (max-- > 0) {
+		if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+			byte = inb(SBP(chip, READ));
+			if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
+				snd_rawmidi_receive(chip->midi_substream_input, &byte, 1);
+			}
+		}
+	}
+	spin_unlock(&chip->midi_input_lock);
+	return IRQ_HANDLED;
+}
+
+static int snd_sb8dsp_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip;
+	unsigned int valid_open_flags;
+
+	chip = substream->rmidi->private_data;
+	valid_open_flags = chip->hardware >= SB_HW_20
+		? SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER : 0;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->open & ~valid_open_flags) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	chip->open |= SB_OPEN_MIDI_INPUT;
+	chip->midi_substream_input = substream;
+	if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+		if (chip->hardware >= SB_HW_20)
+			snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static int snd_sb8dsp_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip;
+	unsigned int valid_open_flags;
+
+	chip = substream->rmidi->private_data;
+	valid_open_flags = chip->hardware >= SB_HW_20
+		? SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER : 0;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (chip->open & ~valid_open_flags) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		return -EAGAIN;
+	}
+	chip->open |= SB_OPEN_MIDI_OUTPUT;
+	chip->midi_substream_output = substream;
+	if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+		if (chip->hardware >= SB_HW_20)
+			snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static int snd_sb8dsp_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->open &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER);
+	chip->midi_substream_input = NULL;
+	if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static int snd_sb8dsp_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->open &= ~(SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER);
+	chip->midi_substream_output = NULL;
+	if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+		snd_sbdsp_reset(chip);		/* reset DSP */
+	} else {
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+	return 0;
+}
+
+static void snd_sb8dsp_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct snd_sb *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (up) {
+		if (!(chip->open & SB_OPEN_MIDI_INPUT_TRIGGER)) {
+			if (chip->hardware < SB_HW_20)
+				snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
+			chip->open |= SB_OPEN_MIDI_INPUT_TRIGGER;
+		}
+	} else {
+		if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
+			if (chip->hardware < SB_HW_20)
+				snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
+			chip->open &= ~SB_OPEN_MIDI_INPUT_TRIGGER;
+		}
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+}
+
+static void snd_sb8dsp_midi_output_write(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	struct snd_sb *chip;
+	char byte;
+	int max = 32;
+
+	/* how big is Tx FIFO? */
+	chip = substream->rmidi->private_data;
+	while (max-- > 0) {
+		spin_lock_irqsave(&chip->open_lock, flags);
+		if (snd_rawmidi_transmit_peek(substream, &byte, 1) != 1) {
+			chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
+			del_timer(&chip->midi_timer);
+			spin_unlock_irqrestore(&chip->open_lock, flags);
+			break;
+		}
+		if (chip->hardware >= SB_HW_20) {
+			int timeout = 8;
+			while ((inb(SBP(chip, STATUS)) & 0x80) != 0 && --timeout > 0)
+				;
+			if (timeout == 0) {
+				/* Tx FIFO full - try again later */
+				spin_unlock_irqrestore(&chip->open_lock, flags);
+				break;
+			}
+			outb(byte, SBP(chip, WRITE));
+		} else {
+			snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT);
+			snd_sbdsp_command(chip, byte);
+		}
+		snd_rawmidi_transmit_ack(substream, 1);
+		spin_unlock_irqrestore(&chip->open_lock, flags);
+	}
+}
+
+static void snd_sb8dsp_midi_output_timer(unsigned long data)
+{
+	struct snd_rawmidi_substream *substream = (struct snd_rawmidi_substream *) data;
+	struct snd_sb * chip = substream->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->open_lock, flags);
+	chip->midi_timer.expires = 1 + jiffies;
+	add_timer(&chip->midi_timer);
+	spin_unlock_irqrestore(&chip->open_lock, flags);	
+	snd_sb8dsp_midi_output_write(substream);
+}
+
+static void snd_sb8dsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct snd_sb *chip;
+
+	chip = substream->rmidi->private_data;
+	spin_lock_irqsave(&chip->open_lock, flags);
+	if (up) {
+		if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) {
+			init_timer(&chip->midi_timer);
+			chip->midi_timer.function = snd_sb8dsp_midi_output_timer;
+			chip->midi_timer.data = (unsigned long) substream;
+			chip->midi_timer.expires = 1 + jiffies;
+			add_timer(&chip->midi_timer);
+			chip->open |= SB_OPEN_MIDI_OUTPUT_TRIGGER;
+		}
+	} else {
+		if (chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER) {
+			chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
+		}
+	}
+	spin_unlock_irqrestore(&chip->open_lock, flags);
+
+	if (up)
+		snd_sb8dsp_midi_output_write(substream);
+}
+
+static struct snd_rawmidi_ops snd_sb8dsp_midi_output =
+{
+	.open =		snd_sb8dsp_midi_output_open,
+	.close =	snd_sb8dsp_midi_output_close,
+	.trigger =	snd_sb8dsp_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_sb8dsp_midi_input =
+{
+	.open =		snd_sb8dsp_midi_input_open,
+	.close =	snd_sb8dsp_midi_input_close,
+	.trigger =	snd_sb8dsp_midi_input_trigger,
+};
+
+int snd_sb8dsp_midi(struct snd_sb *chip, int device, struct snd_rawmidi ** rrawmidi)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	if ((err = snd_rawmidi_new(chip->card, "SB8 MIDI", device, 1, 1, &rmidi)) < 0)
+		return err;
+	strcpy(rmidi->name, "SB8 MIDI");
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_sb8dsp_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_sb8dsp_midi_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT;
+	if (chip->hardware >= SB_HW_20)
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = chip;
+	chip->rmidi = rmidi;
+	if (rrawmidi)
+		*rrawmidi = rmidi;
+	return 0;
+}
diff --git a/linux/sound/isa/sb/sb_common.c b/linux/sound/isa/sb/sb_common.c
new file mode 100644
index 0000000..eae6c1c
--- /dev/null
+++ b/linux/sound/isa/sb/sb_common.c
@@ -0,0 +1,323 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *                   Uros Bizjak <uros@kss-loka.si>
+ *
+ *  Lowlevel routines for control of Sound Blaster cards
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ALSA lowlevel driver for Sound Blaster cards");
+MODULE_LICENSE("GPL");
+
+#define BUSY_LOOPS 100000
+
+#undef IO_DEBUG
+
+int snd_sbdsp_command(struct snd_sb *chip, unsigned char val)
+{
+	int i;
+#ifdef IO_DEBUG
+	snd_printk(KERN_DEBUG "command 0x%x\n", val);
+#endif
+	for (i = BUSY_LOOPS; i; i--)
+		if ((inb(SBP(chip, STATUS)) & 0x80) == 0) {
+			outb(val, SBP(chip, COMMAND));
+			return 1;
+		}
+	snd_printd("%s [0x%lx]: timeout (0x%x)\n", __func__, chip->port, val);
+	return 0;
+}
+
+int snd_sbdsp_get_byte(struct snd_sb *chip)
+{
+	int val;
+	int i;
+	for (i = BUSY_LOOPS; i; i--) {
+		if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+			val = inb(SBP(chip, READ));
+#ifdef IO_DEBUG
+			snd_printk(KERN_DEBUG "get_byte 0x%x\n", val);
+#endif
+			return val;
+		}
+	}
+	snd_printd("%s [0x%lx]: timeout\n", __func__, chip->port);
+	return -ENODEV;
+}
+
+int snd_sbdsp_reset(struct snd_sb *chip)
+{
+	int i;
+
+	outb(1, SBP(chip, RESET));
+	udelay(10);
+	outb(0, SBP(chip, RESET));
+	udelay(30);
+	for (i = BUSY_LOOPS; i; i--)
+		if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
+			if (inb(SBP(chip, READ)) == 0xaa)
+				return 0;
+			else
+				break;
+		}
+	snd_printdd("%s [0x%lx] failed...\n", __func__, chip->port);
+	return -ENODEV;
+}
+
+static int snd_sbdsp_version(struct snd_sb * chip)
+{
+	unsigned int result = -ENODEV;
+
+	snd_sbdsp_command(chip, SB_DSP_GET_VERSION);
+	result = (short) snd_sbdsp_get_byte(chip) << 8;
+	result |= (short) snd_sbdsp_get_byte(chip);
+	return result;
+}
+
+static int snd_sbdsp_probe(struct snd_sb * chip)
+{
+	int version;
+	int major, minor;
+	char *str;
+	unsigned long flags;
+
+	/*
+	 *  initialization sequence
+	 */
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (snd_sbdsp_reset(chip) < 0) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return -ENODEV;
+	}
+	version = snd_sbdsp_version(chip);
+	if (version < 0) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return -ENODEV;
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	major = version >> 8;
+	minor = version & 0xff;
+	snd_printdd("SB [0x%lx]: DSP chip found, version = %i.%i\n",
+		    chip->port, major, minor);
+
+	switch (chip->hardware) {
+	case SB_HW_AUTO:
+		switch (major) {
+		case 1:
+			chip->hardware = SB_HW_10;
+			str = "1.0";
+			break;
+		case 2:
+			if (minor) {
+				chip->hardware = SB_HW_201;
+				str = "2.01+";
+			} else {
+				chip->hardware = SB_HW_20;
+				str = "2.0";
+			}
+			break;
+		case 3:
+			chip->hardware = SB_HW_PRO;
+			str = "Pro";
+			break;
+		case 4:
+			chip->hardware = SB_HW_16;
+			str = "16";
+			break;
+		default:
+			snd_printk(KERN_INFO "SB [0x%lx]: unknown DSP chip version %i.%i\n",
+				   chip->port, major, minor);
+			return -ENODEV;
+		}
+		break;
+	case SB_HW_ALS100:
+		str = "16 (ALS-100)";
+		break;
+	case SB_HW_ALS4000:
+		str = "16 (ALS-4000)";
+		break;
+	case SB_HW_DT019X:
+		str = "(DT019X/ALS007)";
+		break;
+	case SB_HW_CS5530:
+		str = "16 (CS5530)";
+		break;
+	case SB_HW_JAZZ16:
+		str = "Pro (Jazz16)";
+		break;
+	default:
+		return -ENODEV;
+	}
+	sprintf(chip->name, "Sound Blaster %s", str);
+	chip->version = (major << 8) | minor;
+	return 0;
+}
+
+static int snd_sbdsp_free(struct snd_sb *chip)
+{
+	if (chip->res_port)
+		release_and_free_resource(chip->res_port);
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *) chip);
+#ifdef CONFIG_ISA
+	if (chip->dma8 >= 0) {
+		disable_dma(chip->dma8);
+		free_dma(chip->dma8);
+	}
+	if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) {
+		disable_dma(chip->dma16);
+		free_dma(chip->dma16);
+	}
+#endif
+	kfree(chip);
+	return 0;
+}
+
+static int snd_sbdsp_dev_free(struct snd_device *device)
+{
+	struct snd_sb *chip = device->device_data;
+	return snd_sbdsp_free(chip);
+}
+
+int snd_sbdsp_create(struct snd_card *card,
+		     unsigned long port,
+		     int irq,
+		     irq_handler_t irq_handler,
+		     int dma8,
+		     int dma16,
+		     unsigned short hardware,
+		     struct snd_sb **r_chip)
+{
+	struct snd_sb *chip;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_sbdsp_dev_free,
+	};
+
+	if (snd_BUG_ON(!r_chip))
+		return -EINVAL;
+	*r_chip = NULL;
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	spin_lock_init(&chip->reg_lock);
+	spin_lock_init(&chip->open_lock);
+	spin_lock_init(&chip->midi_input_lock);
+	spin_lock_init(&chip->mixer_lock);
+	chip->irq = -1;
+	chip->dma8 = -1;
+	chip->dma16 = -1;
+	chip->port = port;
+	
+	if (request_irq(irq, irq_handler,
+			(hardware == SB_HW_ALS4000 ||
+			 hardware == SB_HW_CS5530) ?
+			IRQF_SHARED : IRQF_DISABLED,
+			"SoundBlaster", (void *) chip)) {
+		snd_printk(KERN_ERR "sb: can't grab irq %d\n", irq);
+		snd_sbdsp_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = irq;
+
+	if (hardware == SB_HW_ALS4000)
+		goto __skip_allocation;
+	
+	if ((chip->res_port = request_region(port, 16, "SoundBlaster")) == NULL) {
+		snd_printk(KERN_ERR "sb: can't grab port 0x%lx\n", port);
+		snd_sbdsp_free(chip);
+		return -EBUSY;
+	}
+
+#ifdef CONFIG_ISA
+	if (dma8 >= 0 && request_dma(dma8, "SoundBlaster - 8bit")) {
+		snd_printk(KERN_ERR "sb: can't grab DMA8 %d\n", dma8);
+		snd_sbdsp_free(chip);
+		return -EBUSY;
+	}
+	chip->dma8 = dma8;
+	if (dma16 >= 0) {
+		if (hardware != SB_HW_ALS100 && (dma16 < 5 || dma16 > 7)) {
+			/* no duplex */
+			dma16 = -1;
+		} else if (request_dma(dma16, "SoundBlaster - 16bit")) {
+			snd_printk(KERN_ERR "sb: can't grab DMA16 %d\n", dma16);
+			snd_sbdsp_free(chip);
+			return -EBUSY;
+		}
+	}
+	chip->dma16 = dma16;
+#endif
+
+      __skip_allocation:
+	chip->card = card;
+	chip->hardware = hardware;
+	if ((err = snd_sbdsp_probe(chip)) < 0) {
+		snd_sbdsp_free(chip);
+		return err;
+	}
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_sbdsp_free(chip);
+		return err;
+	}
+	*r_chip = chip;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_sbdsp_command);
+EXPORT_SYMBOL(snd_sbdsp_get_byte);
+EXPORT_SYMBOL(snd_sbdsp_reset);
+EXPORT_SYMBOL(snd_sbdsp_create);
+/* sb_mixer.c */
+EXPORT_SYMBOL(snd_sbmixer_write);
+EXPORT_SYMBOL(snd_sbmixer_read);
+EXPORT_SYMBOL(snd_sbmixer_new);
+EXPORT_SYMBOL(snd_sbmixer_add_ctl);
+#ifdef CONFIG_PM
+EXPORT_SYMBOL(snd_sbmixer_suspend);
+EXPORT_SYMBOL(snd_sbmixer_resume);
+#endif
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_sb_common_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_sb_common_exit(void)
+{
+}
+
+module_init(alsa_sb_common_init)
+module_exit(alsa_sb_common_exit)
diff --git a/linux/sound/isa/sb/sb_mixer.c b/linux/sound/isa/sb/sb_mixer.c
new file mode 100644
index 0000000..6496822
--- /dev/null
+++ b/linux/sound/isa/sb/sb_mixer.c
@@ -0,0 +1,978 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for Sound Blaster mixer control
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/control.h>
+
+#undef IO_DEBUG
+
+void snd_sbmixer_write(struct snd_sb *chip, unsigned char reg, unsigned char data)
+{
+	outb(reg, SBP(chip, MIXER_ADDR));
+	udelay(10);
+	outb(data, SBP(chip, MIXER_DATA));
+	udelay(10);
+#ifdef IO_DEBUG
+	snd_printk(KERN_DEBUG "mixer_write 0x%x 0x%x\n", reg, data);
+#endif
+}
+
+unsigned char snd_sbmixer_read(struct snd_sb *chip, unsigned char reg)
+{
+	unsigned char result;
+
+	outb(reg, SBP(chip, MIXER_ADDR));
+	udelay(10);
+	result = inb(SBP(chip, MIXER_DATA));
+	udelay(10);
+#ifdef IO_DEBUG
+	snd_printk(KERN_DEBUG "mixer_read 0x%x 0x%x\n", reg, result);
+#endif
+	return result;
+}
+
+/*
+ * Single channel mixer element
+ */
+
+static int snd_sbmixer_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 16) & 0xff;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	unsigned char val;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	val = (snd_sbmixer_read(sb, reg) >> shift) & mask;
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int snd_sbmixer_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 16) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned char val, oval;
+
+	val = (ucontrol->value.integer.value[0] & mask) << shift;
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, reg);
+	val = (oval & ~(mask << shift)) | val;
+	change = val != oval;
+	if (change)
+		snd_sbmixer_write(sb, reg, val);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * Double channel mixer element
+ */
+
+static int snd_sbmixer_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_sbmixer_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x07;
+	int right_shift = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	unsigned char left, right;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	left = (snd_sbmixer_read(sb, left_reg) >> left_shift) & mask;
+	right = (snd_sbmixer_read(sb, right_reg) >> right_shift) & mask;
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = left;
+	ucontrol->value.integer.value[1] = right;
+	return 0;
+}
+
+static int snd_sbmixer_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x07;
+	int right_shift = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned char left, right, oleft, oright;
+
+	left = (ucontrol->value.integer.value[0] & mask) << left_shift;
+	right = (ucontrol->value.integer.value[1] & mask) << right_shift;
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	if (left_reg == right_reg) {
+		oleft = snd_sbmixer_read(sb, left_reg);
+		left = (oleft & ~((mask << left_shift) | (mask << right_shift))) | left | right;
+		change = left != oleft;
+		if (change)
+			snd_sbmixer_write(sb, left_reg, left);
+	} else {
+		oleft = snd_sbmixer_read(sb, left_reg);
+		oright = snd_sbmixer_read(sb, right_reg);
+		left = (oleft & ~(mask << left_shift)) | left;
+		right = (oright & ~(mask << right_shift)) | right;
+		change = left != oleft || right != oright;
+		if (change) {
+			snd_sbmixer_write(sb, left_reg, left);
+			snd_sbmixer_write(sb, right_reg, right);
+		}
+	}
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * DT-019x / ALS-007 capture/input switch
+ */
+
+static int snd_dt019x_input_sw_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char *texts[5] = {
+		"CD", "Mic", "Line", "Synth", "Master"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 5;
+	if (uinfo->value.enumerated.item > 4)
+		uinfo->value.enumerated.item = 4;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_dt019x_input_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char oval;
+	
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	switch (oval & 0x07) {
+	case SB_DT019X_CAP_CD:
+		ucontrol->value.enumerated.item[0] = 0;
+		break;
+	case SB_DT019X_CAP_MIC:
+		ucontrol->value.enumerated.item[0] = 1;
+		break;
+	case SB_DT019X_CAP_LINE:
+		ucontrol->value.enumerated.item[0] = 2;
+		break;
+	case SB_DT019X_CAP_MAIN:
+		ucontrol->value.enumerated.item[0] = 4;
+		break;
+	/* To record the synth on these cards you must record the main.   */
+	/* Thus SB_DT019X_CAP_SYNTH == SB_DT019X_CAP_MAIN and would cause */
+	/* duplicate case labels if left uncommented. */
+	/* case SB_DT019X_CAP_SYNTH:
+	 *	ucontrol->value.enumerated.item[0] = 3;
+	 *	break;
+	 */
+	default:
+		ucontrol->value.enumerated.item[0] = 4;
+		break;
+	}
+	return 0;
+}
+
+static int snd_dt019x_input_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval, oval;
+	
+	if (ucontrol->value.enumerated.item[0] > 4)
+		return -EINVAL;
+	switch (ucontrol->value.enumerated.item[0]) {
+	case 0:
+		nval = SB_DT019X_CAP_CD;
+		break;
+	case 1:
+		nval = SB_DT019X_CAP_MIC;
+		break;
+	case 2:
+		nval = SB_DT019X_CAP_LINE;
+		break;
+	case 3:
+		nval = SB_DT019X_CAP_SYNTH;
+		break;
+	case 4:
+		nval = SB_DT019X_CAP_MAIN;
+		break;
+	default:
+		nval = SB_DT019X_CAP_MAIN;
+	}
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW);
+	change = nval != oval;
+	if (change)
+		snd_sbmixer_write(sb, SB_DT019X_CAPTURE_SW, nval);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * ALS4000 mono recording control switch
+ */
+
+static int snd_als4k_mono_capture_route_info(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_info *uinfo)
+{
+	static const char *texts[3] = {
+		"L chan only", "R chan only", "L ch/2 + R ch/2"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_als4k_mono_capture_route_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char oval;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_ALS4000_MONO_IO_CTRL);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	oval >>= 6;
+	if (oval > 2)
+		oval = 2;
+
+	ucontrol->value.enumerated.item[0] = oval;
+	return 0;
+}
+
+static int snd_als4k_mono_capture_route_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval, oval;
+
+	if (ucontrol->value.enumerated.item[0] > 2)
+		return -EINVAL;
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_ALS4000_MONO_IO_CTRL);
+
+	nval = (oval & ~(3 << 6))
+	     | (ucontrol->value.enumerated.item[0] << 6);
+	change = nval != oval;
+	if (change)
+		snd_sbmixer_write(sb, SB_ALS4000_MONO_IO_CTRL, nval);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * SBPRO input multiplexer
+ */
+
+static int snd_sb8mixer_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char *texts[3] = {
+		"Mic", "CD", "Line"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+
+static int snd_sb8mixer_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned char oval;
+	
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	switch ((oval >> 0x01) & 0x03) {
+	case SB_DSP_MIXS_CD:
+		ucontrol->value.enumerated.item[0] = 1;
+		break;
+	case SB_DSP_MIXS_LINE:
+		ucontrol->value.enumerated.item[0] = 2;
+		break;
+	default:
+		ucontrol->value.enumerated.item[0] = 0;
+		break;
+	}
+	return 0;
+}
+
+static int snd_sb8mixer_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int change;
+	unsigned char nval, oval;
+	
+	if (ucontrol->value.enumerated.item[0] > 2)
+		return -EINVAL;
+	switch (ucontrol->value.enumerated.item[0]) {
+	case 1:
+		nval = SB_DSP_MIXS_CD;
+		break;
+	case 2:
+		nval = SB_DSP_MIXS_LINE;
+		break;
+	default:
+		nval = SB_DSP_MIXS_MIC;
+	}
+	nval <<= 1;
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE);
+	nval |= oval & ~0x06;
+	change = nval != oval;
+	if (change)
+		snd_sbmixer_write(sb, SB_DSP_CAPTURE_SOURCE, nval);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+/*
+ * SB16 input switch
+ */
+
+static int snd_sb16mixer_info_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 4;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_sb16mixer_get_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg1 = kcontrol->private_value & 0xff;
+	int reg2 = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x0f;
+	int right_shift = (kcontrol->private_value >> 24) & 0x0f;
+	unsigned char val1, val2;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	val1 = snd_sbmixer_read(sb, reg1);
+	val2 = snd_sbmixer_read(sb, reg2);
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	ucontrol->value.integer.value[0] = (val1 >> left_shift) & 0x01;
+	ucontrol->value.integer.value[1] = (val2 >> left_shift) & 0x01;
+	ucontrol->value.integer.value[2] = (val1 >> right_shift) & 0x01;
+	ucontrol->value.integer.value[3] = (val2 >> right_shift) & 0x01;
+	return 0;
+}                                                                                                                   
+
+static int snd_sb16mixer_put_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg1 = kcontrol->private_value & 0xff;
+	int reg2 = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x0f;
+	int right_shift = (kcontrol->private_value >> 24) & 0x0f;
+	int change;
+	unsigned char val1, val2, oval1, oval2;
+
+	spin_lock_irqsave(&sb->mixer_lock, flags);
+	oval1 = snd_sbmixer_read(sb, reg1);
+	oval2 = snd_sbmixer_read(sb, reg2);
+	val1 = oval1 & ~((1 << left_shift) | (1 << right_shift));
+	val2 = oval2 & ~((1 << left_shift) | (1 << right_shift));
+	val1 |= (ucontrol->value.integer.value[0] & 1) << left_shift;
+	val2 |= (ucontrol->value.integer.value[1] & 1) << left_shift;
+	val1 |= (ucontrol->value.integer.value[2] & 1) << right_shift;
+	val2 |= (ucontrol->value.integer.value[3] & 1) << right_shift;
+	change = val1 != oval1 || val2 != oval2;
+	if (change) {
+		snd_sbmixer_write(sb, reg1, val1);
+		snd_sbmixer_write(sb, reg2, val2);
+	}
+	spin_unlock_irqrestore(&sb->mixer_lock, flags);
+	return change;
+}
+
+
+/*
+ */
+/*
+ */
+int snd_sbmixer_add_ctl(struct snd_sb *chip, const char *name, int index, int type, unsigned long value)
+{
+	static struct snd_kcontrol_new newctls[] = {
+		[SB_MIX_SINGLE] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sbmixer_info_single,
+			.get = snd_sbmixer_get_single,
+			.put = snd_sbmixer_put_single,
+		},
+		[SB_MIX_DOUBLE] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sbmixer_info_double,
+			.get = snd_sbmixer_get_double,
+			.put = snd_sbmixer_put_double,
+		},
+		[SB_MIX_INPUT_SW] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sb16mixer_info_input_sw,
+			.get = snd_sb16mixer_get_input_sw,
+			.put = snd_sb16mixer_put_input_sw,
+		},
+		[SB_MIX_CAPTURE_PRO] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_sb8mixer_info_mux,
+			.get = snd_sb8mixer_get_mux,
+			.put = snd_sb8mixer_put_mux,
+		},
+		[SB_MIX_CAPTURE_DT019X] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_dt019x_input_sw_info,
+			.get = snd_dt019x_input_sw_get,
+			.put = snd_dt019x_input_sw_put,
+		},
+		[SB_MIX_MONO_CAPTURE_ALS4K] = {
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.info = snd_als4k_mono_capture_route_info,
+			.get = snd_als4k_mono_capture_route_get,
+			.put = snd_als4k_mono_capture_route_put,
+		},
+	};
+	struct snd_kcontrol *ctl;
+	int err;
+
+	ctl = snd_ctl_new1(&newctls[type], chip);
+	if (! ctl)
+		return -ENOMEM;
+	strlcpy(ctl->id.name, name, sizeof(ctl->id.name));
+	ctl->id.index = index;
+	ctl->private_value = value;
+	if ((err = snd_ctl_add(chip->card, ctl)) < 0)
+		return err;
+	return 0;
+}
+
+/*
+ * SB 2.0 specific mixer elements
+ */
+
+static struct sbmix_elem snd_sb20_controls[] = {
+	SB_SINGLE("Master Playback Volume", SB_DSP20_MASTER_DEV, 1, 7),
+	SB_SINGLE("PCM Playback Volume", SB_DSP20_PCM_DEV, 1, 3),
+	SB_SINGLE("Synth Playback Volume", SB_DSP20_FM_DEV, 1, 7),
+	SB_SINGLE("CD Playback Volume", SB_DSP20_CD_DEV, 1, 7)
+};
+
+static unsigned char snd_sb20_init_values[][2] = {
+	{ SB_DSP20_MASTER_DEV, 0 },
+	{ SB_DSP20_FM_DEV, 0 },
+};
+
+/*
+ * SB Pro specific mixer elements
+ */
+static struct sbmix_elem snd_sbpro_controls[] = {
+	SB_DOUBLE("Master Playback Volume",
+		  SB_DSP_MASTER_DEV, SB_DSP_MASTER_DEV, 5, 1, 7),
+	SB_DOUBLE("PCM Playback Volume",
+		  SB_DSP_PCM_DEV, SB_DSP_PCM_DEV, 5, 1, 7),
+	SB_SINGLE("PCM Playback Filter", SB_DSP_PLAYBACK_FILT, 5, 1),
+	SB_DOUBLE("Synth Playback Volume",
+		  SB_DSP_FM_DEV, SB_DSP_FM_DEV, 5, 1, 7),
+	SB_DOUBLE("CD Playback Volume", SB_DSP_CD_DEV, SB_DSP_CD_DEV, 5, 1, 7),
+	SB_DOUBLE("Line Playback Volume",
+		  SB_DSP_LINE_DEV, SB_DSP_LINE_DEV, 5, 1, 7),
+	SB_SINGLE("Mic Playback Volume", SB_DSP_MIC_DEV, 1, 3),
+	{
+		.name = "Capture Source",
+		.type = SB_MIX_CAPTURE_PRO
+	},
+	SB_SINGLE("Capture Filter", SB_DSP_CAPTURE_FILT, 5, 1),
+	SB_SINGLE("Capture Low-Pass Filter", SB_DSP_CAPTURE_FILT, 3, 1)
+};
+
+static unsigned char snd_sbpro_init_values[][2] = {
+	{ SB_DSP_MASTER_DEV, 0 },
+	{ SB_DSP_PCM_DEV, 0 },
+	{ SB_DSP_FM_DEV, 0 },
+};
+
+/*
+ * SB16 specific mixer elements
+ */
+static struct sbmix_elem snd_sb16_controls[] = {
+	SB_DOUBLE("Master Playback Volume",
+		  SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31),
+	SB_DOUBLE("PCM Playback Volume",
+		  SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31),
+	SB16_INPUT_SW("Synth Capture Route",
+		      SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 6, 5),
+	SB_DOUBLE("Synth Playback Volume",
+		  SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31),
+	SB16_INPUT_SW("CD Capture Route",
+		      SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 2, 1),
+	SB_DOUBLE("CD Playback Switch",
+		  SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1),
+	SB_DOUBLE("CD Playback Volume",
+		  SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31),
+	SB16_INPUT_SW("Mic Capture Route",
+		      SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 0, 0),
+	SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1),
+	SB_SINGLE("Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31),
+	SB_SINGLE("Beep Volume", SB_DSP4_SPEAKER_DEV, 6, 3),
+	SB_DOUBLE("Capture Volume",
+		  SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3),
+	SB_DOUBLE("Playback Volume",
+		  SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3),
+	SB16_INPUT_SW("Line Capture Route",
+		      SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 4, 3),
+	SB_DOUBLE("Line Playback Switch",
+		  SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1),
+	SB_DOUBLE("Line Playback Volume",
+		  SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31),
+	SB_SINGLE("Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1),
+	SB_SINGLE("3D Enhancement Switch", SB_DSP4_3DSE, 0, 1),
+	SB_DOUBLE("Tone Control - Bass",
+		  SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15),
+	SB_DOUBLE("Tone Control - Treble",
+		  SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15)
+};
+
+static unsigned char snd_sb16_init_values[][2] = {
+	{ SB_DSP4_MASTER_DEV + 0, 0 },
+	{ SB_DSP4_MASTER_DEV + 1, 0 },
+	{ SB_DSP4_PCM_DEV + 0, 0 },
+	{ SB_DSP4_PCM_DEV + 1, 0 },
+	{ SB_DSP4_SYNTH_DEV + 0, 0 },
+	{ SB_DSP4_SYNTH_DEV + 1, 0 },
+	{ SB_DSP4_INPUT_LEFT, 0 },
+	{ SB_DSP4_INPUT_RIGHT, 0 },
+	{ SB_DSP4_OUTPUT_SW, 0 },
+	{ SB_DSP4_SPEAKER_DEV, 0 },
+};
+
+/*
+ * DT019x specific mixer elements
+ */
+static struct sbmix_elem snd_dt019x_controls[] = {
+	/* ALS4000 below has some parts which we might be lacking,
+	 * e.g. snd_als4000_ctl_mono_playback_switch - check it! */
+	SB_DOUBLE("Master Playback Volume",
+		  SB_DT019X_MASTER_DEV, SB_DT019X_MASTER_DEV, 4, 0, 15),
+	SB_DOUBLE("PCM Playback Switch",
+		  SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2, 1, 1),
+	SB_DOUBLE("PCM Playback Volume",
+		  SB_DT019X_PCM_DEV, SB_DT019X_PCM_DEV, 4, 0, 15),
+	SB_DOUBLE("Synth Playback Switch",
+		  SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4, 3, 1),
+	SB_DOUBLE("Synth Playback Volume",
+		  SB_DT019X_SYNTH_DEV, SB_DT019X_SYNTH_DEV, 4, 0, 15),
+	SB_DOUBLE("CD Playback Switch",
+		  SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1),
+	SB_DOUBLE("CD Playback Volume",
+		  SB_DT019X_CD_DEV, SB_DT019X_CD_DEV, 4, 0, 15),
+	SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1),
+	SB_SINGLE("Mic Playback Volume", SB_DT019X_MIC_DEV, 4, 7),
+	SB_SINGLE("Beep Volume", SB_DT019X_SPKR_DEV, 0,  7),
+	SB_DOUBLE("Line Playback Switch",
+		  SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1),
+	SB_DOUBLE("Line Playback Volume",
+		  SB_DT019X_LINE_DEV, SB_DT019X_LINE_DEV, 4, 0, 15),
+	{
+		.name = "Capture Source",
+		.type = SB_MIX_CAPTURE_DT019X
+	}
+};
+
+static unsigned char snd_dt019x_init_values[][2] = {
+        { SB_DT019X_MASTER_DEV, 0 },
+        { SB_DT019X_PCM_DEV, 0 },
+        { SB_DT019X_SYNTH_DEV, 0 },
+        { SB_DT019X_CD_DEV, 0 },
+        { SB_DT019X_MIC_DEV, 0 },	/* Includes PC-speaker in high nibble */
+        { SB_DT019X_LINE_DEV, 0 },
+        { SB_DSP4_OUTPUT_SW, 0 },
+        { SB_DT019X_OUTPUT_SW2, 0 },
+        { SB_DT019X_CAPTURE_SW, 0x06 },
+};
+
+/*
+ * ALS4000 specific mixer elements
+ */
+static struct sbmix_elem snd_als4000_controls[] = {
+	SB_DOUBLE("PCM Playback Switch",
+		  SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2, 1, 1),
+	SB_DOUBLE("Synth Playback Switch",
+		  SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4, 3, 1),
+	SB_SINGLE("Mic Boost (+20dB)", SB_ALS4000_MIC_IN_GAIN, 0, 0x03),
+	SB_SINGLE("Master Mono Playback Switch", SB_ALS4000_MONO_IO_CTRL, 5, 1),
+	{
+		.name = "Master Mono Capture Route",
+		.type = SB_MIX_MONO_CAPTURE_ALS4K
+	},
+	SB_SINGLE("Mono Playback Switch", SB_DT019X_OUTPUT_SW2, 0, 1),
+	SB_SINGLE("Analog Loopback Switch", SB_ALS4000_MIC_IN_GAIN, 7, 0x01),
+	SB_SINGLE("3D Control - Switch", SB_ALS4000_3D_SND_FX, 6, 0x01),
+	SB_SINGLE("Digital Loopback Switch",
+		  SB_ALS4000_CR3_CONFIGURATION, 7, 0x01),
+	/* FIXME: functionality of 3D controls might be swapped, I didn't find
+	 * a description of how to identify what is supposed to be what */
+	SB_SINGLE("3D Control - Level", SB_ALS4000_3D_SND_FX, 0, 0x07),
+	/* FIXME: maybe there's actually some standard 3D ctrl name for it?? */
+	SB_SINGLE("3D Control - Freq", SB_ALS4000_3D_SND_FX, 4, 0x03),
+	/* FIXME: ALS4000a.pdf mentions BBD (Bucket Brigade Device) time delay,
+	 * but what ALSA 3D attribute is that actually? "Center", "Depth",
+	 * "Wide" or "Space" or even "Level"? Assuming "Wide" for now... */
+	SB_SINGLE("3D Control - Wide", SB_ALS4000_3D_TIME_DELAY, 0, 0x0f),
+	SB_SINGLE("3D PowerOff Switch", SB_ALS4000_3D_TIME_DELAY, 4, 0x01),
+	SB_SINGLE("Master Playback 8kHz / 20kHz LPF Switch",
+		  SB_ALS4000_FMDAC, 5, 0x01),
+#ifdef NOT_AVAILABLE
+	SB_SINGLE("FMDAC Switch (Option ?)", SB_ALS4000_FMDAC, 0, 0x01),
+	SB_SINGLE("QSound Mode", SB_ALS4000_QSOUND, 1, 0x1f),
+#endif
+};
+
+static unsigned char snd_als4000_init_values[][2] = {
+	{ SB_DSP4_MASTER_DEV + 0, 0 },
+	{ SB_DSP4_MASTER_DEV + 1, 0 },
+	{ SB_DSP4_PCM_DEV + 0, 0 },
+	{ SB_DSP4_PCM_DEV + 1, 0 },
+	{ SB_DSP4_SYNTH_DEV + 0, 0 },
+	{ SB_DSP4_SYNTH_DEV + 1, 0 },
+	{ SB_DSP4_SPEAKER_DEV, 0 },
+	{ SB_DSP4_OUTPUT_SW, 0 },
+	{ SB_DSP4_INPUT_LEFT, 0 },
+	{ SB_DSP4_INPUT_RIGHT, 0 },
+	{ SB_DT019X_OUTPUT_SW2, 0 },
+	{ SB_ALS4000_MIC_IN_GAIN, 0 },
+};
+
+/*
+ */
+static int snd_sbmixer_init(struct snd_sb *chip,
+			    struct sbmix_elem *controls,
+			    int controls_count,
+			    unsigned char map[][2],
+			    int map_count,
+			    char *name)
+{
+	unsigned long flags;
+	struct snd_card *card = chip->card;
+	int idx, err;
+
+	/* mixer reset */
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	snd_sbmixer_write(chip, 0x00, 0x00);
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+
+	/* mute and zero volume channels */
+	for (idx = 0; idx < map_count; idx++) {
+		spin_lock_irqsave(&chip->mixer_lock, flags);
+		snd_sbmixer_write(chip, map[idx][0], map[idx][1]);
+		spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	}
+
+	for (idx = 0; idx < controls_count; idx++) {
+		err = snd_sbmixer_add_ctl_elem(chip, &controls[idx]);
+		if (err < 0)
+			return err;
+	}
+	snd_component_add(card, name);
+	strcpy(card->mixername, name);
+	return 0;
+}
+
+int snd_sbmixer_new(struct snd_sb *chip)
+{
+	struct snd_card *card;
+	int err;
+
+	if (snd_BUG_ON(!chip || !chip->card))
+		return -EINVAL;
+
+	card = chip->card;
+
+	switch (chip->hardware) {
+	case SB_HW_10:
+		return 0; /* no mixer chip on SB1.x */
+	case SB_HW_20:
+	case SB_HW_201:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_sb20_controls,
+					    ARRAY_SIZE(snd_sb20_controls),
+					    snd_sb20_init_values,
+					    ARRAY_SIZE(snd_sb20_init_values),
+					    "CTL1335")) < 0)
+			return err;
+		break;
+	case SB_HW_PRO:
+	case SB_HW_JAZZ16:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_sbpro_controls,
+					    ARRAY_SIZE(snd_sbpro_controls),
+					    snd_sbpro_init_values,
+					    ARRAY_SIZE(snd_sbpro_init_values),
+					    "CTL1345")) < 0)
+			return err;
+		break;
+	case SB_HW_16:
+	case SB_HW_ALS100:
+	case SB_HW_CS5530:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_sb16_controls,
+					    ARRAY_SIZE(snd_sb16_controls),
+					    snd_sb16_init_values,
+					    ARRAY_SIZE(snd_sb16_init_values),
+					    "CTL1745")) < 0)
+			return err;
+		break;
+	case SB_HW_ALS4000:
+		/* use only the first 16 controls from SB16 */
+		err = snd_sbmixer_init(chip,
+					snd_sb16_controls,
+					16,
+					snd_sb16_init_values,
+					ARRAY_SIZE(snd_sb16_init_values),
+					"ALS4000");
+		if (err < 0)
+			return err;
+		if ((err = snd_sbmixer_init(chip,
+					    snd_als4000_controls,
+					    ARRAY_SIZE(snd_als4000_controls),
+					    snd_als4000_init_values,
+					    ARRAY_SIZE(snd_als4000_init_values),
+					    "ALS4000")) < 0)
+			return err;
+		break;
+	case SB_HW_DT019X:
+		if ((err = snd_sbmixer_init(chip,
+					    snd_dt019x_controls,
+					    ARRAY_SIZE(snd_dt019x_controls),
+					    snd_dt019x_init_values,
+					    ARRAY_SIZE(snd_dt019x_init_values),
+					    "DT019X")) < 0)
+		break;
+	default:
+		strcpy(card->mixername, "???");
+	}
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static unsigned char sb20_saved_regs[] = {
+	SB_DSP20_MASTER_DEV,
+	SB_DSP20_PCM_DEV,
+	SB_DSP20_FM_DEV,
+	SB_DSP20_CD_DEV,
+};
+
+static unsigned char sbpro_saved_regs[] = {
+	SB_DSP_MASTER_DEV,
+	SB_DSP_PCM_DEV,
+	SB_DSP_PLAYBACK_FILT,
+	SB_DSP_FM_DEV,
+	SB_DSP_CD_DEV,
+	SB_DSP_LINE_DEV,
+	SB_DSP_MIC_DEV,
+	SB_DSP_CAPTURE_SOURCE,
+	SB_DSP_CAPTURE_FILT,
+};
+
+static unsigned char sb16_saved_regs[] = {
+	SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1,
+	SB_DSP4_3DSE,
+	SB_DSP4_BASS_DEV, SB_DSP4_BASS_DEV + 1,
+	SB_DSP4_TREBLE_DEV, SB_DSP4_TREBLE_DEV + 1,
+	SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1,
+	SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT,
+	SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1,
+	SB_DSP4_OUTPUT_SW,
+	SB_DSP4_CD_DEV, SB_DSP4_CD_DEV + 1,
+	SB_DSP4_LINE_DEV, SB_DSP4_LINE_DEV + 1,
+	SB_DSP4_MIC_DEV,
+	SB_DSP4_SPEAKER_DEV,
+	SB_DSP4_IGAIN_DEV, SB_DSP4_IGAIN_DEV + 1,
+	SB_DSP4_OGAIN_DEV, SB_DSP4_OGAIN_DEV + 1,
+	SB_DSP4_MIC_AGC
+};
+
+static unsigned char dt019x_saved_regs[] = {
+	SB_DT019X_MASTER_DEV,
+	SB_DT019X_PCM_DEV,
+	SB_DT019X_SYNTH_DEV,
+	SB_DT019X_CD_DEV,
+	SB_DT019X_MIC_DEV,
+	SB_DT019X_SPKR_DEV,
+	SB_DT019X_LINE_DEV,
+	SB_DSP4_OUTPUT_SW,
+	SB_DT019X_OUTPUT_SW2,
+	SB_DT019X_CAPTURE_SW,
+};
+
+static unsigned char als4000_saved_regs[] = {
+	/* please verify in dsheet whether regs to be added
+	   are actually real H/W or just dummy */
+	SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1,
+	SB_DSP4_OUTPUT_SW,
+	SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1,
+	SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT,
+	SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1,
+	SB_DSP4_CD_DEV, SB_DSP4_CD_DEV + 1,
+	SB_DSP4_MIC_DEV,
+	SB_DSP4_SPEAKER_DEV,
+	SB_DSP4_IGAIN_DEV, SB_DSP4_IGAIN_DEV + 1,
+	SB_DSP4_OGAIN_DEV, SB_DSP4_OGAIN_DEV + 1,
+	SB_DT019X_OUTPUT_SW2,
+	SB_ALS4000_MONO_IO_CTRL,
+	SB_ALS4000_MIC_IN_GAIN,
+	SB_ALS4000_FMDAC,
+	SB_ALS4000_3D_SND_FX,
+	SB_ALS4000_3D_TIME_DELAY,
+	SB_ALS4000_CR3_CONFIGURATION,
+};
+
+static void save_mixer(struct snd_sb *chip, unsigned char *regs, int num_regs)
+{
+	unsigned char *val = chip->saved_regs;
+	if (snd_BUG_ON(num_regs > ARRAY_SIZE(chip->saved_regs)))
+		return;
+	for (; num_regs; num_regs--)
+		*val++ = snd_sbmixer_read(chip, *regs++);
+}
+
+static void restore_mixer(struct snd_sb *chip, unsigned char *regs, int num_regs)
+{
+	unsigned char *val = chip->saved_regs;
+	if (snd_BUG_ON(num_regs > ARRAY_SIZE(chip->saved_regs)))
+		return;
+	for (; num_regs; num_regs--)
+		snd_sbmixer_write(chip, *regs++, *val++);
+}
+
+void snd_sbmixer_suspend(struct snd_sb *chip)
+{
+	switch (chip->hardware) {
+	case SB_HW_20:
+	case SB_HW_201:
+		save_mixer(chip, sb20_saved_regs, ARRAY_SIZE(sb20_saved_regs));
+		break;
+	case SB_HW_PRO:
+	case SB_HW_JAZZ16:
+		save_mixer(chip, sbpro_saved_regs, ARRAY_SIZE(sbpro_saved_regs));
+		break;
+	case SB_HW_16:
+	case SB_HW_ALS100:
+	case SB_HW_CS5530:
+		save_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs));
+		break;
+	case SB_HW_ALS4000:
+		save_mixer(chip, als4000_saved_regs, ARRAY_SIZE(als4000_saved_regs));
+		break;
+	case SB_HW_DT019X:
+		save_mixer(chip, dt019x_saved_regs, ARRAY_SIZE(dt019x_saved_regs));
+		break;
+	default:
+		break;
+	}
+}
+
+void snd_sbmixer_resume(struct snd_sb *chip)
+{
+	switch (chip->hardware) {
+	case SB_HW_20:
+	case SB_HW_201:
+		restore_mixer(chip, sb20_saved_regs, ARRAY_SIZE(sb20_saved_regs));
+		break;
+	case SB_HW_PRO:
+	case SB_HW_JAZZ16:
+		restore_mixer(chip, sbpro_saved_regs, ARRAY_SIZE(sbpro_saved_regs));
+		break;
+	case SB_HW_16:
+	case SB_HW_ALS100:
+	case SB_HW_CS5530:
+		restore_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs));
+		break;
+	case SB_HW_ALS4000:
+		restore_mixer(chip, als4000_saved_regs, ARRAY_SIZE(als4000_saved_regs));
+		break;
+	case SB_HW_DT019X:
+		restore_mixer(chip, dt019x_saved_regs, ARRAY_SIZE(dt019x_saved_regs));
+		break;
+	default:
+		break;
+	}
+}
+#endif
diff --git a/linux/sound/isa/sb/sbawe.c b/linux/sound/isa/sb/sbawe.c
new file mode 100644
index 0000000..2ec52a3
--- /dev/null
+++ b/linux/sound/isa/sb/sbawe.c
@@ -0,0 +1,2 @@
+#define SNDRV_SBAWE
+#include "sb16.c"
diff --git a/linux/sound/isa/sc6000.c b/linux/sound/isa/sc6000.c
new file mode 100644
index 0000000..9a8bbf6
--- /dev/null
+++ b/linux/sound/isa/sc6000.c
@@ -0,0 +1,729 @@
+/*
+ *  Driver for Gallant SC-6000 soundcard. This card is also known as
+ *  Audio Excel DSP 16 or Zoltrix AV302.
+ *  These cards use CompuMedia ASC-9308 chip + AD1848 codec.
+ *  SC-6600 and SC-7000 cards are also supported. They are based on
+ *  CompuMedia ASC-9408 chip and CS4231 codec.
+ *
+ *  Copyright (C) 2007 Krzysztof Helt <krzysztof.h1@wp.pl>
+ *
+ *  I don't have documentation for this card. I used the driver
+ *  for OSS/Free included in the kernel source as reference.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/isa.h>
+#include <linux/io.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/opl3.h>
+#include <sound/mpu401.h>
+#include <sound/control.h>
+#define SNDRV_LEGACY_FIND_FREE_IRQ
+#define SNDRV_LEGACY_FIND_FREE_DMA
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Krzysztof Helt");
+MODULE_DESCRIPTION("Gallant SC-6000");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Gallant, SC-6000},"
+			"{AudioExcel, Audio Excel DSP 16},"
+			"{Zoltrix, AV302}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220, 0x240 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5, 7, 9, 10, 11 */
+static long mss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x530, 0xe80 */
+static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+						/* 0x300, 0x310, 0x320, 0x330 */
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 5, 7, 9, 10, 0 */
+static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 0, 1, 3 */
+static bool joystick[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = false };
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for sc-6000 based soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for sc-6000 based soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable sc-6000 based soundcard.");
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for sc-6000 driver.");
+module_param_array(mss_port, long, NULL, 0444);
+MODULE_PARM_DESC(mss_port, "MSS Port # for sc-6000 driver.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port # for sc-6000 driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for sc-6000 driver.");
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for sc-6000 driver.");
+module_param_array(dma, int, NULL, 0444);
+MODULE_PARM_DESC(dma, "DMA # for sc-6000 driver.");
+module_param_array(joystick, bool, NULL, 0444);
+MODULE_PARM_DESC(joystick, "Enable gameport.");
+
+/*
+ * Commands of SC6000's DSP (SBPRO+special).
+ * Some of them are COMMAND_xx, in the future they may change.
+ */
+#define WRITE_MDIRQ_CFG	0x50	/* Set M&I&DRQ mask (the real config)	*/
+#define COMMAND_52	0x52	/*					*/
+#define READ_HARD_CFG	0x58	/* Read Hardware Config (I/O base etc)	*/
+#define COMMAND_5C	0x5c	/*					*/
+#define COMMAND_60	0x60	/*					*/
+#define COMMAND_66	0x66	/*					*/
+#define COMMAND_6C	0x6c	/*					*/
+#define COMMAND_6E	0x6e	/*					*/
+#define COMMAND_88	0x88	/* Unknown command 			*/
+#define DSP_INIT_MSS	0x8c	/* Enable Microsoft Sound System mode	*/
+#define COMMAND_C5	0xc5	/*					*/
+#define GET_DSP_VERSION	0xe1	/* Get DSP Version			*/
+#define GET_DSP_COPYRIGHT 0xe3	/* Get DSP Copyright			*/
+
+/*
+ * Offsets of SC6000 DSP I/O ports. The offset is added to base I/O port
+ * to have the actual I/O port.
+ * Register permissions are:
+ * (wo) == Write Only
+ * (ro) == Read  Only
+ * (w-) == Write
+ * (r-) == Read
+ */
+#define DSP_RESET	0x06	/* offset of DSP RESET		(wo) */
+#define DSP_READ	0x0a	/* offset of DSP READ		(ro) */
+#define DSP_WRITE	0x0c	/* offset of DSP WRITE		(w-) */
+#define DSP_COMMAND	0x0c	/* offset of DSP COMMAND	(w-) */
+#define DSP_STATUS	0x0c	/* offset of DSP STATUS		(r-) */
+#define DSP_DATAVAIL	0x0e	/* offset of DSP DATA AVAILABLE	(ro) */
+
+#define PFX "sc6000: "
+#define DRV_NAME "SC-6000"
+
+/* hardware dependent functions */
+
+/*
+ * sc6000_irq_to_softcfg - Decode irq number into cfg code.
+ */
+static __devinit unsigned char sc6000_irq_to_softcfg(int irq)
+{
+	unsigned char val = 0;
+
+	switch (irq) {
+	case 5:
+		val = 0x28;
+		break;
+	case 7:
+		val = 0x8;
+		break;
+	case 9:
+		val = 0x10;
+		break;
+	case 10:
+		val = 0x18;
+		break;
+	case 11:
+		val = 0x20;
+		break;
+	default:
+		break;
+	}
+	return val;
+}
+
+/*
+ * sc6000_dma_to_softcfg - Decode dma number into cfg code.
+ */
+static __devinit unsigned char sc6000_dma_to_softcfg(int dma)
+{
+	unsigned char val = 0;
+
+	switch (dma) {
+	case 0:
+		val = 1;
+		break;
+	case 1:
+		val = 2;
+		break;
+	case 3:
+		val = 3;
+		break;
+	default:
+		break;
+	}
+	return val;
+}
+
+/*
+ * sc6000_mpu_irq_to_softcfg - Decode MPU-401 irq number into cfg code.
+ */
+static __devinit unsigned char sc6000_mpu_irq_to_softcfg(int mpu_irq)
+{
+	unsigned char val = 0;
+
+	switch (mpu_irq) {
+	case 5:
+		val = 4;
+		break;
+	case 7:
+		val = 0x44;
+		break;
+	case 9:
+		val = 0x84;
+		break;
+	case 10:
+		val = 0xc4;
+		break;
+	default:
+		break;
+	}
+	return val;
+}
+
+static int sc6000_wait_data(char __iomem *vport)
+{
+	int loop = 1000;
+	unsigned char val = 0;
+
+	do {
+		val = ioread8(vport + DSP_DATAVAIL);
+		if (val & 0x80)
+			return 0;
+		cpu_relax();
+	} while (loop--);
+
+	return -EAGAIN;
+}
+
+static int sc6000_read(char __iomem *vport)
+{
+	if (sc6000_wait_data(vport))
+		return -EBUSY;
+
+	return ioread8(vport + DSP_READ);
+
+}
+
+static int sc6000_write(char __iomem *vport, int cmd)
+{
+	unsigned char val;
+	int loop = 500000;
+
+	do {
+		val = ioread8(vport + DSP_STATUS);
+		/*
+		 * DSP ready to receive data if bit 7 of val == 0
+		 */
+		if (!(val & 0x80)) {
+			iowrite8(cmd, vport + DSP_COMMAND);
+			return 0;
+		}
+		cpu_relax();
+	} while (loop--);
+
+	snd_printk(KERN_ERR "DSP Command (0x%x) timeout.\n", cmd);
+
+	return -EIO;
+}
+
+static int __devinit sc6000_dsp_get_answer(char __iomem *vport, int command,
+					   char *data, int data_len)
+{
+	int len = 0;
+
+	if (sc6000_write(vport, command)) {
+		snd_printk(KERN_ERR "CMD 0x%x: failed!\n", command);
+		return -EIO;
+	}
+
+	do {
+		int val = sc6000_read(vport);
+
+		if (val < 0)
+			break;
+
+		data[len++] = val;
+
+	} while (len < data_len);
+
+	/*
+	 * If no more data available, return to the caller, no error if len>0.
+	 * We have no other way to know when the string is finished.
+	 */
+	return len ? len : -EIO;
+}
+
+static int __devinit sc6000_dsp_reset(char __iomem *vport)
+{
+	iowrite8(1, vport + DSP_RESET);
+	udelay(10);
+	iowrite8(0, vport + DSP_RESET);
+	udelay(20);
+	if (sc6000_read(vport) == 0xaa)
+		return 0;
+	return -ENODEV;
+}
+
+/* detection and initialization */
+static int __devinit sc6000_hw_cfg_write(char __iomem *vport, const int *cfg)
+{
+	if (sc6000_write(vport, COMMAND_6C) < 0) {
+		snd_printk(KERN_WARNING "CMD 0x%x: failed!\n", COMMAND_6C);
+		return -EIO;
+	}
+	if (sc6000_write(vport, COMMAND_5C) < 0) {
+		snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_5C);
+		return -EIO;
+	}
+	if (sc6000_write(vport, cfg[0]) < 0) {
+		snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[0]);
+		return -EIO;
+	}
+	if (sc6000_write(vport, cfg[1]) < 0) {
+		snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[1]);
+		return -EIO;
+	}
+	if (sc6000_write(vport, COMMAND_C5) < 0) {
+		snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_C5);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int sc6000_cfg_write(char __iomem *vport, unsigned char softcfg)
+{
+
+	if (sc6000_write(vport, WRITE_MDIRQ_CFG)) {
+		snd_printk(KERN_ERR "CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG);
+		return -EIO;
+	}
+	if (sc6000_write(vport, softcfg)) {
+		snd_printk(KERN_ERR "sc6000_cfg_write: failed!\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+static int sc6000_setup_board(char __iomem *vport, int config)
+{
+	int loop = 10;
+
+	do {
+		if (sc6000_write(vport, COMMAND_88)) {
+			snd_printk(KERN_ERR "CMD 0x%x: failed!\n",
+				   COMMAND_88);
+			return -EIO;
+		}
+	} while ((sc6000_wait_data(vport) < 0) && loop--);
+
+	if (sc6000_read(vport) < 0) {
+		snd_printk(KERN_ERR "sc6000_read after CMD 0x%x: failed\n",
+			   COMMAND_88);
+		return -EIO;
+	}
+
+	if (sc6000_cfg_write(vport, config))
+		return -ENODEV;
+
+	return 0;
+}
+
+static int __devinit sc6000_init_mss(char __iomem *vport, int config,
+				     char __iomem *vmss_port, int mss_config)
+{
+	if (sc6000_write(vport, DSP_INIT_MSS)) {
+		snd_printk(KERN_ERR "sc6000_init_mss [0x%x]: failed!\n",
+			   DSP_INIT_MSS);
+		return -EIO;
+	}
+
+	msleep(10);
+
+	if (sc6000_cfg_write(vport, config))
+		return -EIO;
+
+	iowrite8(mss_config, vmss_port);
+
+	return 0;
+}
+
+static void __devinit sc6000_hw_cfg_encode(char __iomem *vport, int *cfg,
+					   long xport, long xmpu,
+					   long xmss_port, int joystick)
+{
+	cfg[0] = 0;
+	cfg[1] = 0;
+	if (xport == 0x240)
+		cfg[0] |= 1;
+	if (xmpu != SNDRV_AUTO_PORT) {
+		cfg[0] |= (xmpu & 0x30) >> 2;
+		cfg[1] |= 0x20;
+	}
+	if (xmss_port == 0xe80)
+		cfg[0] |= 0x10;
+	cfg[0] |= 0x40;		/* always set */
+	if (!joystick)
+		cfg[0] |= 0x02;
+	cfg[1] |= 0x80;		/* enable WSS system */
+	cfg[1] &= ~0x40;	/* disable IDE */
+	snd_printd("hw cfg %x, %x\n", cfg[0], cfg[1]);
+}
+
+static int __devinit sc6000_init_board(char __iomem *vport,
+					char __iomem *vmss_port, int dev)
+{
+	char answer[15];
+	char version[2];
+	int mss_config = sc6000_irq_to_softcfg(irq[dev]) |
+			 sc6000_dma_to_softcfg(dma[dev]);
+	int config = mss_config |
+		     sc6000_mpu_irq_to_softcfg(mpu_irq[dev]);
+	int err;
+	int old = 0;
+
+	err = sc6000_dsp_reset(vport);
+	if (err < 0) {
+		snd_printk(KERN_ERR "sc6000_dsp_reset: failed!\n");
+		return err;
+	}
+
+	memset(answer, 0, sizeof(answer));
+	err = sc6000_dsp_get_answer(vport, GET_DSP_COPYRIGHT, answer, 15);
+	if (err <= 0) {
+		snd_printk(KERN_ERR "sc6000_dsp_copyright: failed!\n");
+		return -ENODEV;
+	}
+	/*
+	 * My SC-6000 card return "SC-6000" in DSPCopyright, so
+	 * if we have something different, we have to be warned.
+	 */
+	if (strncmp("SC-6000", answer, 7))
+		snd_printk(KERN_WARNING "Warning: non SC-6000 audio card!\n");
+
+	if (sc6000_dsp_get_answer(vport, GET_DSP_VERSION, version, 2) < 2) {
+		snd_printk(KERN_ERR "sc6000_dsp_version: failed!\n");
+		return -ENODEV;
+	}
+	printk(KERN_INFO PFX "Detected model: %s, DSP version %d.%d\n",
+		answer, version[0], version[1]);
+
+	/* set configuration */
+	sc6000_write(vport, COMMAND_5C);
+	if (sc6000_read(vport) < 0)
+		old = 1;
+
+	if (!old) {
+		int cfg[2];
+		sc6000_hw_cfg_encode(vport, &cfg[0], port[dev], mpu_port[dev],
+				     mss_port[dev], joystick[dev]);
+		if (sc6000_hw_cfg_write(vport, cfg) < 0) {
+			snd_printk(KERN_ERR "sc6000_hw_cfg_write: failed!\n");
+			return -EIO;
+		}
+	}
+	err = sc6000_setup_board(vport, config);
+	if (err < 0) {
+		snd_printk(KERN_ERR "sc6000_setup_board: failed!\n");
+		return -ENODEV;
+	}
+
+	sc6000_dsp_reset(vport);
+
+	if (!old) {
+		sc6000_write(vport, COMMAND_60);
+		sc6000_write(vport, 0x02);
+		sc6000_dsp_reset(vport);
+	}
+
+	err = sc6000_setup_board(vport, config);
+	if (err < 0) {
+		snd_printk(KERN_ERR "sc6000_setup_board: failed!\n");
+		return -ENODEV;
+	}
+	err = sc6000_init_mss(vport, config, vmss_port, mss_config);
+	if (err < 0) {
+		snd_printk(KERN_ERR "Cannot initialize "
+			   "Microsoft Sound System mode.\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int __devinit snd_sc6000_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card = chip->card;
+	struct snd_ctl_elem_id id1, id2;
+	int err;
+
+	memset(&id1, 0, sizeof(id1));
+	memset(&id2, 0, sizeof(id2));
+	id1.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	/* reassign AUX0 to FM */
+	strcpy(id1.name, "Aux Playback Switch");
+	strcpy(id2.name, "FM Playback Switch");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "FM Playback Volume");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0)
+		return err;
+	/* reassign AUX1 to CD */
+	strcpy(id1.name, "Aux Playback Switch"); id1.index = 1;
+	strcpy(id2.name, "CD Playback Switch");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0)
+		return err;
+	strcpy(id1.name, "Aux Playback Volume");
+	strcpy(id2.name, "CD Playback Volume");
+	err = snd_ctl_rename_id(card, &id1, &id2);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int __devinit snd_sc6000_match(struct device *devptr, unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+	if (port[dev] == SNDRV_AUTO_PORT) {
+		printk(KERN_ERR PFX "specify IO port\n");
+		return 0;
+	}
+	if (mss_port[dev] == SNDRV_AUTO_PORT) {
+		printk(KERN_ERR PFX "specify MSS port\n");
+		return 0;
+	}
+	if (port[dev] != 0x220 && port[dev] != 0x240) {
+		printk(KERN_ERR PFX "Port must be 0x220 or 0x240\n");
+		return 0;
+	}
+	if (mss_port[dev] != 0x530 && mss_port[dev] != 0xe80) {
+		printk(KERN_ERR PFX "MSS port must be 0x530 or 0xe80\n");
+		return 0;
+	}
+	if (irq[dev] != SNDRV_AUTO_IRQ && !sc6000_irq_to_softcfg(irq[dev])) {
+		printk(KERN_ERR PFX "invalid IRQ %d\n", irq[dev]);
+		return 0;
+	}
+	if (dma[dev] != SNDRV_AUTO_DMA && !sc6000_dma_to_softcfg(dma[dev])) {
+		printk(KERN_ERR PFX "invalid DMA %d\n", dma[dev]);
+		return 0;
+	}
+	if (mpu_port[dev] != SNDRV_AUTO_PORT &&
+	    (mpu_port[dev] & ~0x30L) != 0x300) {
+		printk(KERN_ERR PFX "invalid MPU-401 port %lx\n",
+			mpu_port[dev]);
+		return 0;
+	}
+	if (mpu_port[dev] != SNDRV_AUTO_PORT &&
+	    mpu_irq[dev] != SNDRV_AUTO_IRQ && mpu_irq[dev] != 0 &&
+	    !sc6000_mpu_irq_to_softcfg(mpu_irq[dev])) {
+		printk(KERN_ERR PFX "invalid MPU-401 IRQ %d\n", mpu_irq[dev]);
+		return 0;
+	}
+	return 1;
+}
+
+static int __devinit snd_sc6000_probe(struct device *devptr, unsigned int dev)
+{
+	static int possible_irqs[] = { 5, 7, 9, 10, 11, -1 };
+	static int possible_dmas[] = { 1, 3, 0, -1 };
+	int err;
+	int xirq = irq[dev];
+	int xdma = dma[dev];
+	struct snd_card *card;
+	struct snd_wss *chip;
+	struct snd_opl3 *opl3;
+	char __iomem **vport;
+	char __iomem *vmss_port;
+
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, sizeof(vport),
+				&card);
+	if (err < 0)
+		return err;
+
+	vport = card->private_data;
+	if (xirq == SNDRV_AUTO_IRQ) {
+		xirq = snd_legacy_find_free_irq(possible_irqs);
+		if (xirq < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free IRQ\n");
+			err = -EBUSY;
+			goto err_exit;
+		}
+	}
+
+	if (xdma == SNDRV_AUTO_DMA) {
+		xdma = snd_legacy_find_free_dma(possible_dmas);
+		if (xdma < 0) {
+			snd_printk(KERN_ERR PFX "unable to find a free DMA\n");
+			err = -EBUSY;
+			goto err_exit;
+		}
+	}
+
+	if (!request_region(port[dev], 0x10, DRV_NAME)) {
+		snd_printk(KERN_ERR PFX
+			   "I/O port region is already in use.\n");
+		err = -EBUSY;
+		goto err_exit;
+	}
+	*vport = devm_ioport_map(devptr, port[dev], 0x10);
+	if (*vport == NULL) {
+		snd_printk(KERN_ERR PFX
+			   "I/O port cannot be iomaped.\n");
+		err = -EBUSY;
+		goto err_unmap1;
+	}
+
+	/* to make it marked as used */
+	if (!request_region(mss_port[dev], 4, DRV_NAME)) {
+		snd_printk(KERN_ERR PFX
+			   "SC-6000 port I/O port region is already in use.\n");
+		err = -EBUSY;
+		goto err_unmap1;
+	}
+	vmss_port = devm_ioport_map(devptr, mss_port[dev], 4);
+	if (!vmss_port) {
+		snd_printk(KERN_ERR PFX
+			   "MSS port I/O cannot be iomaped.\n");
+		err = -EBUSY;
+		goto err_unmap2;
+	}
+
+	snd_printd("Initializing BASE[0x%lx] IRQ[%d] DMA[%d] MIRQ[%d]\n",
+		   port[dev], xirq, xdma,
+		   mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]);
+
+	err = sc6000_init_board(*vport, vmss_port, dev);
+	if (err < 0)
+		goto err_unmap2;
+
+	err = snd_wss_create(card, mss_port[dev] + 4,  -1, xirq, xdma, -1,
+			     WSS_HW_DETECT, 0, &chip);
+	if (err < 0)
+		goto err_unmap2;
+
+	err = snd_wss_pcm(chip, 0, NULL);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX
+			   "error creating new WSS PCM device\n");
+		goto err_unmap2;
+	}
+	err = snd_wss_mixer(chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "error creating new WSS mixer\n");
+		goto err_unmap2;
+	}
+	err = snd_sc6000_mixer(chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "the mixer rewrite failed\n");
+		goto err_unmap2;
+	}
+	if (snd_opl3_create(card,
+			    0x388, 0x388 + 2,
+			    OPL3_HW_AUTO, 0, &opl3) < 0) {
+		snd_printk(KERN_ERR PFX "no OPL device at 0x%x-0x%x ?\n",
+			   0x388, 0x388 + 2);
+	} else {
+		err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+		if (err < 0)
+			goto err_unmap2;
+	}
+
+	if (mpu_port[dev] != SNDRV_AUTO_PORT) {
+		if (mpu_irq[dev] == SNDRV_AUTO_IRQ)
+			mpu_irq[dev] = -1;
+		if (snd_mpu401_uart_new(card, 0,
+					MPU401_HW_MPU401,
+					mpu_port[dev], 0,
+					mpu_irq[dev], IRQF_DISABLED,
+					NULL) < 0)
+			snd_printk(KERN_ERR "no MPU-401 device at 0x%lx ?\n",
+					mpu_port[dev]);
+	}
+
+	strcpy(card->driver, DRV_NAME);
+	strcpy(card->shortname, "SC-6000");
+	sprintf(card->longname, "Gallant SC-6000 at 0x%lx, irq %d, dma %d",
+		mss_port[dev], xirq, xdma);
+
+	snd_card_set_dev(card, devptr);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto err_unmap2;
+
+	dev_set_drvdata(devptr, card);
+	return 0;
+
+err_unmap2:
+	sc6000_setup_board(*vport, 0);
+	release_region(mss_port[dev], 4);
+err_unmap1:
+	release_region(port[dev], 0x10);
+err_exit:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit snd_sc6000_remove(struct device *devptr, unsigned int dev)
+{
+	struct snd_card *card = dev_get_drvdata(devptr);
+	char __iomem **vport = card->private_data;
+
+	if (sc6000_setup_board(*vport, 0) < 0)
+		snd_printk(KERN_WARNING "sc6000_setup_board failed on exit!\n");
+
+	release_region(port[dev], 0x10);
+	release_region(mss_port[dev], 4);
+
+	dev_set_drvdata(devptr, NULL);
+	snd_card_free(card);
+	return 0;
+}
+
+static struct isa_driver snd_sc6000_driver = {
+	.match		= snd_sc6000_match,
+	.probe		= snd_sc6000_probe,
+	.remove		= __devexit_p(snd_sc6000_remove),
+	/* FIXME: suspend/resume */
+	.driver		= {
+		.name	= DRV_NAME,
+	},
+};
+
+
+static int __init alsa_card_sc6000_init(void)
+{
+	return isa_register_driver(&snd_sc6000_driver, SNDRV_CARDS);
+}
+
+static void __exit alsa_card_sc6000_exit(void)
+{
+	isa_unregister_driver(&snd_sc6000_driver);
+}
+
+module_init(alsa_card_sc6000_init)
+module_exit(alsa_card_sc6000_exit)
diff --git a/linux/sound/isa/sscape.c b/linux/sound/isa/sscape.c
new file mode 100644
index 0000000..e2d5d2d
--- /dev/null
+++ b/linux/sound/isa/sscape.c
@@ -0,0 +1,1358 @@
+/*
+ *   Low-level ALSA driver for the ENSONIQ SoundScape
+ *   Copyright (c) by Chris Rankin
+ *
+ *   This driver was written in part using information obtained from
+ *   the OSS/Free SoundScape driver, written by Hannu Savolainen.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/pnp.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+
+MODULE_AUTHOR("Chris Rankin");
+MODULE_DESCRIPTION("ENSONIQ SoundScape driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("sndscape.co0");
+MODULE_FIRMWARE("sndscape.co1");
+MODULE_FIRMWARE("sndscape.co2");
+MODULE_FIRMWARE("sndscape.co3");
+MODULE_FIRMWARE("sndscape.co4");
+MODULE_FIRMWARE("scope.cod");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
+static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
+static bool joystick[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index number for SoundScape soundcard");
+
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "Description for SoundScape card");
+
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for SoundScape driver.");
+
+module_param_array(wss_port, long, NULL, 0444);
+MODULE_PARM_DESC(wss_port, "WSS Port # for SoundScape driver.");
+
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for SoundScape driver.");
+
+module_param_array(mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_irq, "MPU401 IRQ # for SoundScape driver.");
+
+module_param_array(dma, int, NULL, 0444);
+MODULE_PARM_DESC(dma, "DMA # for SoundScape driver.");
+
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for SoundScape driver.");
+
+module_param_array(joystick, bool, NULL, 0444);
+MODULE_PARM_DESC(joystick, "Enable gameport.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+
+static struct pnp_card_device_id sscape_pnpids[] = {
+	{ .id = "ENS3081", .devs = { { "ENS0000" } } }, /* Soundscape PnP */
+	{ .id = "ENS4081", .devs = { { "ENS1011" } } },	/* VIVO90 */
+	{ .id = "" }	/* end */
+};
+
+MODULE_DEVICE_TABLE(pnp_card, sscape_pnpids);
+#endif
+
+
+#define HOST_CTRL_IO(i)  ((i) + 2)
+#define HOST_DATA_IO(i)  ((i) + 3)
+#define ODIE_ADDR_IO(i)  ((i) + 4)
+#define ODIE_DATA_IO(i)  ((i) + 5)
+#define CODEC_IO(i)      ((i) + 8)
+
+#define IC_ODIE  1
+#define IC_OPUS  2
+
+#define RX_READY 0x01
+#define TX_READY 0x02
+
+#define CMD_ACK			0x80
+#define CMD_SET_MIDI_VOL	0x84
+#define CMD_GET_MIDI_VOL	0x85
+#define CMD_XXX_MIDI_VOL	0x86
+#define CMD_SET_EXTMIDI		0x8a
+#define CMD_GET_EXTMIDI		0x8b
+#define CMD_SET_MT32		0x8c
+#define CMD_GET_MT32		0x8d
+
+enum GA_REG {
+	GA_INTSTAT_REG = 0,
+	GA_INTENA_REG,
+	GA_DMAA_REG,
+	GA_DMAB_REG,
+	GA_INTCFG_REG,
+	GA_DMACFG_REG,
+	GA_CDCFG_REG,
+	GA_SMCFGA_REG,
+	GA_SMCFGB_REG,
+	GA_HMCTL_REG
+};
+
+#define DMA_8BIT  0x80
+
+
+enum card_type {
+	MEDIA_FX,	/* Sequoia S-1000 */
+	SSCAPE,		/* Sequoia S-2000 */
+	SSCAPE_PNP,
+	SSCAPE_VIVO,
+};
+
+struct soundscape {
+	spinlock_t lock;
+	unsigned io_base;
+	int ic_type;
+	enum card_type type;
+	struct resource *io_res;
+	struct resource *wss_res;
+	struct snd_wss *chip;
+
+	unsigned char midi_vol;
+};
+
+#define INVALID_IRQ  ((unsigned)-1)
+
+
+static inline struct soundscape *get_card_soundscape(struct snd_card *c)
+{
+	return (struct soundscape *) (c->private_data);
+}
+
+/*
+ * Allocates some kernel memory that we can use for DMA.
+ * I think this means that the memory has to map to
+ * contiguous pages of physical memory.
+ */
+static struct snd_dma_buffer *get_dmabuf(struct snd_dma_buffer *buf,
+					 unsigned long size)
+{
+	if (buf) {
+		if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV,
+						 snd_dma_isa_data(),
+						 size, buf) < 0) {
+			snd_printk(KERN_ERR "sscape: Failed to allocate "
+					    "%lu bytes for DMA\n",
+					    size);
+			return NULL;
+		}
+	}
+
+	return buf;
+}
+
+/*
+ * Release the DMA-able kernel memory ...
+ */
+static void free_dmabuf(struct snd_dma_buffer *buf)
+{
+	if (buf && buf->area)
+		snd_dma_free_pages(buf);
+}
+
+/*
+ * This function writes to the SoundScape's control registers,
+ * but doesn't do any locking. It's up to the caller to do that.
+ * This is why this function is "unsafe" ...
+ */
+static inline void sscape_write_unsafe(unsigned io_base, enum GA_REG reg,
+				       unsigned char val)
+{
+	outb(reg, ODIE_ADDR_IO(io_base));
+	outb(val, ODIE_DATA_IO(io_base));
+}
+
+/*
+ * Write to the SoundScape's control registers, and do the
+ * necessary locking ...
+ */
+static void sscape_write(struct soundscape *s, enum GA_REG reg,
+			 unsigned char val)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	sscape_write_unsafe(s->io_base, reg, val);
+	spin_unlock_irqrestore(&s->lock, flags);
+}
+
+/*
+ * Read from the SoundScape's control registers, but leave any
+ * locking to the caller. This is why the function is "unsafe" ...
+ */
+static inline unsigned char sscape_read_unsafe(unsigned io_base,
+					       enum GA_REG reg)
+{
+	outb(reg, ODIE_ADDR_IO(io_base));
+	return inb(ODIE_DATA_IO(io_base));
+}
+
+/*
+ * Puts the SoundScape into "host" mode, as compared to "MIDI" mode
+ */
+static inline void set_host_mode_unsafe(unsigned io_base)
+{
+	outb(0x0, HOST_CTRL_IO(io_base));
+}
+
+/*
+ * Puts the SoundScape into "MIDI" mode, as compared to "host" mode
+ */
+static inline void set_midi_mode_unsafe(unsigned io_base)
+{
+	outb(0x3, HOST_CTRL_IO(io_base));
+}
+
+/*
+ * Read the SoundScape's host-mode control register, but leave
+ * any locking issues to the caller ...
+ */
+static inline int host_read_unsafe(unsigned io_base)
+{
+	int data = -1;
+	if ((inb(HOST_CTRL_IO(io_base)) & RX_READY) != 0)
+		data = inb(HOST_DATA_IO(io_base));
+
+	return data;
+}
+
+/*
+ * Read the SoundScape's host-mode control register, performing
+ * a limited amount of busy-waiting if the register isn't ready.
+ * Also leaves all locking-issues to the caller ...
+ */
+static int host_read_ctrl_unsafe(unsigned io_base, unsigned timeout)
+{
+	int data;
+
+	while (((data = host_read_unsafe(io_base)) < 0) && (timeout != 0)) {
+		udelay(100);
+		--timeout;
+	} /* while */
+
+	return data;
+}
+
+/*
+ * Write to the SoundScape's host-mode control registers, but
+ * leave any locking issues to the caller ...
+ */
+static inline int host_write_unsafe(unsigned io_base, unsigned char data)
+{
+	if ((inb(HOST_CTRL_IO(io_base)) & TX_READY) != 0) {
+		outb(data, HOST_DATA_IO(io_base));
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Write to the SoundScape's host-mode control registers, performing
+ * a limited amount of busy-waiting if the register isn't ready.
+ * Also leaves all locking-issues to the caller ...
+ */
+static int host_write_ctrl_unsafe(unsigned io_base, unsigned char data,
+				  unsigned timeout)
+{
+	int err;
+
+	while (!(err = host_write_unsafe(io_base, data)) && (timeout != 0)) {
+		udelay(100);
+		--timeout;
+	} /* while */
+
+	return err;
+}
+
+
+/*
+ * Check that the MIDI subsystem is operational. If it isn't,
+ * then we will hang the computer if we try to use it ...
+ *
+ * NOTE: This check is based upon observation, not documentation.
+ */
+static inline int verify_mpu401(const struct snd_mpu401 *mpu)
+{
+	return ((inb(MPU401C(mpu)) & 0xc0) == 0x80);
+}
+
+/*
+ * This is apparently the standard way to initailise an MPU-401
+ */
+static inline void initialise_mpu401(const struct snd_mpu401 *mpu)
+{
+	outb(0, MPU401D(mpu));
+}
+
+/*
+ * Tell the SoundScape to activate the AD1845 chip (I think).
+ * The AD1845 detection fails if we *don't* do this, so I
+ * think that this is a good idea ...
+ */
+static void activate_ad1845_unsafe(unsigned io_base)
+{
+	unsigned char val = sscape_read_unsafe(io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(io_base, GA_HMCTL_REG, (val & 0xcf) | 0x10);
+	sscape_write_unsafe(io_base, GA_CDCFG_REG, 0x80);
+}
+
+/*
+ * Do the necessary ALSA-level cleanup to deallocate our driver ...
+ */
+static void soundscape_free(struct snd_card *c)
+{
+	struct soundscape *sscape = get_card_soundscape(c);
+	release_and_free_resource(sscape->io_res);
+	release_and_free_resource(sscape->wss_res);
+	free_dma(sscape->chip->dma1);
+}
+
+/*
+ * Tell the SoundScape to begin a DMA tranfer using the given channel.
+ * All locking issues are left to the caller.
+ */
+static void sscape_start_dma_unsafe(unsigned io_base, enum GA_REG reg)
+{
+	sscape_write_unsafe(io_base, reg,
+			    sscape_read_unsafe(io_base, reg) | 0x01);
+	sscape_write_unsafe(io_base, reg,
+			    sscape_read_unsafe(io_base, reg) & 0xfe);
+}
+
+/*
+ * Wait for a DMA transfer to complete. This is a "limited busy-wait",
+ * and all locking issues are left to the caller.
+ */
+static int sscape_wait_dma_unsafe(unsigned io_base, enum GA_REG reg,
+				  unsigned timeout)
+{
+	while (!(sscape_read_unsafe(io_base, reg) & 0x01) && (timeout != 0)) {
+		udelay(100);
+		--timeout;
+	} /* while */
+
+	return sscape_read_unsafe(io_base, reg) & 0x01;
+}
+
+/*
+ * Wait for the On-Board Processor to return its start-up
+ * acknowledgement sequence. This wait is too long for
+ * us to perform "busy-waiting", and so we must sleep.
+ * This in turn means that we must not be holding any
+ * spinlocks when we call this function.
+ */
+static int obp_startup_ack(struct soundscape *s, unsigned timeout)
+{
+	unsigned long end_time = jiffies + msecs_to_jiffies(timeout);
+
+	do {
+		unsigned long flags;
+		int x;
+
+		spin_lock_irqsave(&s->lock, flags);
+		x = host_read_unsafe(s->io_base);
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (x == 0xfe || x == 0xff)
+			return 1;
+
+		msleep(10);
+	} while (time_before(jiffies, end_time));
+
+	return 0;
+}
+
+/*
+ * Wait for the host to return its start-up acknowledgement
+ * sequence. This wait is too long for us to perform
+ * "busy-waiting", and so we must sleep. This in turn means
+ * that we must not be holding any spinlocks when we call
+ * this function.
+ */
+static int host_startup_ack(struct soundscape *s, unsigned timeout)
+{
+	unsigned long end_time = jiffies + msecs_to_jiffies(timeout);
+
+	do {
+		unsigned long flags;
+		int x;
+
+		spin_lock_irqsave(&s->lock, flags);
+		x = host_read_unsafe(s->io_base);
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (x == 0xfe)
+			return 1;
+
+		msleep(10);
+	} while (time_before(jiffies, end_time));
+
+	return 0;
+}
+
+/*
+ * Upload a byte-stream into the SoundScape using DMA channel A.
+ */
+static int upload_dma_data(struct soundscape *s, const unsigned char *data,
+			   size_t size)
+{
+	unsigned long flags;
+	struct snd_dma_buffer dma;
+	int ret;
+	unsigned char val;
+
+	if (!get_dmabuf(&dma, PAGE_ALIGN(32 * 1024)))
+		return -ENOMEM;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	/*
+	 * Reset the board ...
+	 */
+	val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val & 0x3f);
+
+	/*
+	 * Enable the DMA channels and configure them ...
+	 */
+	val = (s->chip->dma1 << 4) | DMA_8BIT;
+	sscape_write_unsafe(s->io_base, GA_DMAA_REG, val);
+	sscape_write_unsafe(s->io_base, GA_DMAB_REG, 0x20);
+
+	/*
+	 * Take the board out of reset ...
+	 */
+	val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val | 0x80);
+
+	/*
+	 * Upload the firmware to the SoundScape
+	 * board through the DMA channel ...
+	 */
+	while (size != 0) {
+		unsigned long len;
+
+		len = min(size, dma.bytes);
+		memcpy(dma.area, data, len);
+		data += len;
+		size -= len;
+
+		snd_dma_program(s->chip->dma1, dma.addr, len, DMA_MODE_WRITE);
+		sscape_start_dma_unsafe(s->io_base, GA_DMAA_REG);
+		if (!sscape_wait_dma_unsafe(s->io_base, GA_DMAA_REG, 5000)) {
+			/*
+			 * Don't forget to release this spinlock we're holding
+			 */
+			spin_unlock_irqrestore(&s->lock, flags);
+
+			snd_printk(KERN_ERR
+					"sscape: DMA upload has timed out\n");
+			ret = -EAGAIN;
+			goto _release_dma;
+		}
+	} /* while */
+
+	set_host_mode_unsafe(s->io_base);
+	outb(0x0, s->io_base);
+
+	/*
+	 * Boot the board ... (I think)
+	 */
+	val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val | 0x40);
+	spin_unlock_irqrestore(&s->lock, flags);
+
+	/*
+	 * If all has gone well, then the board should acknowledge
+	 * the new upload and tell us that it has rebooted OK. We
+	 * give it 5 seconds (max) ...
+	 */
+	ret = 0;
+	if (!obp_startup_ack(s, 5000)) {
+		snd_printk(KERN_ERR "sscape: No response "
+				    "from on-board processor after upload\n");
+		ret = -EAGAIN;
+	} else if (!host_startup_ack(s, 5000)) {
+		snd_printk(KERN_ERR
+				"sscape: SoundScape failed to initialise\n");
+		ret = -EAGAIN;
+	}
+
+_release_dma:
+	/*
+	 * NOTE!!! We are NOT holding any spinlocks at this point !!!
+	 */
+	sscape_write(s, GA_DMAA_REG, (s->ic_type == IC_OPUS ? 0x40 : 0x70));
+	free_dmabuf(&dma);
+
+	return ret;
+}
+
+/*
+ * Upload the bootblock(?) into the SoundScape. The only
+ * purpose of this block of code seems to be to tell
+ * us which version of the microcode we should be using.
+ */
+static int sscape_upload_bootblock(struct snd_card *card)
+{
+	struct soundscape *sscape = get_card_soundscape(card);
+	unsigned long flags;
+	const struct firmware *init_fw = NULL;
+	int data = 0;
+	int ret;
+
+	ret = request_firmware(&init_fw, "scope.cod", card->dev);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "sscape: Error loading scope.cod");
+		return ret;
+	}
+	ret = upload_dma_data(sscape, init_fw->data, init_fw->size);
+
+	release_firmware(init_fw);
+
+	spin_lock_irqsave(&sscape->lock, flags);
+	if (ret == 0)
+		data = host_read_ctrl_unsafe(sscape->io_base, 100);
+
+	if (data & 0x10)
+		sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2f);
+
+	spin_unlock_irqrestore(&sscape->lock, flags);
+
+	data &= 0xf;
+	if (ret == 0 && data > 7) {
+		snd_printk(KERN_ERR
+				"sscape: timeout reading firmware version\n");
+		ret = -EAGAIN;
+	}
+
+	return (ret == 0) ? data : ret;
+}
+
+/*
+ * Upload the microcode into the SoundScape.
+ */
+static int sscape_upload_microcode(struct snd_card *card, int version)
+{
+	struct soundscape *sscape = get_card_soundscape(card);
+	const struct firmware *init_fw = NULL;
+	char name[14];
+	int err;
+
+	snprintf(name, sizeof(name), "sndscape.co%d", version);
+
+	err = request_firmware(&init_fw, name, card->dev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "sscape: Error loading sndscape.co%d",
+				version);
+		return err;
+	}
+	err = upload_dma_data(sscape, init_fw->data, init_fw->size);
+	if (err == 0)
+		snd_printk(KERN_INFO "sscape: MIDI firmware loaded %d KBs\n",
+				init_fw->size >> 10);
+
+	release_firmware(init_fw);
+
+	return err;
+}
+
+/*
+ * Mixer control for the SoundScape's MIDI device.
+ */
+static int sscape_midi_info(struct snd_kcontrol *ctl,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 127;
+	return 0;
+}
+
+static int sscape_midi_get(struct snd_kcontrol *kctl,
+			   struct snd_ctl_elem_value *uctl)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kctl);
+	struct snd_card *card = chip->card;
+	register struct soundscape *s = get_card_soundscape(card);
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	uctl->value.integer.value[0] = s->midi_vol;
+	spin_unlock_irqrestore(&s->lock, flags);
+	return 0;
+}
+
+static int sscape_midi_put(struct snd_kcontrol *kctl,
+			   struct snd_ctl_elem_value *uctl)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kctl);
+	struct snd_card *card = chip->card;
+	struct soundscape *s = get_card_soundscape(card);
+	unsigned long flags;
+	int change;
+	unsigned char new_val;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	new_val = uctl->value.integer.value[0] & 127;
+	/*
+	 * We need to put the board into HOST mode before we
+	 * can send any volume-changing HOST commands ...
+	 */
+	set_host_mode_unsafe(s->io_base);
+
+	/*
+	 * To successfully change the MIDI volume setting, you seem to
+	 * have to write a volume command, write the new volume value,
+	 * and then perform another volume-related command. Perhaps the
+	 * first command is an "open" and the second command is a "close"?
+	 */
+	if (s->midi_vol == new_val) {
+		change = 0;
+		goto __skip_change;
+	}
+	change = host_write_ctrl_unsafe(s->io_base, CMD_SET_MIDI_VOL, 100)
+		 && host_write_ctrl_unsafe(s->io_base, new_val, 100)
+		 && host_write_ctrl_unsafe(s->io_base, CMD_XXX_MIDI_VOL, 100)
+		 && host_write_ctrl_unsafe(s->io_base, new_val, 100);
+	s->midi_vol = new_val;
+__skip_change:
+
+	/*
+	 * Take the board out of HOST mode and back into MIDI mode ...
+	 */
+	set_midi_mode_unsafe(s->io_base);
+
+	spin_unlock_irqrestore(&s->lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new midi_mixer_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "MIDI",
+	.info = sscape_midi_info,
+	.get = sscape_midi_get,
+	.put = sscape_midi_put
+};
+
+/*
+ * The SoundScape can use two IRQs from a possible set of four.
+ * These IRQs are encoded as bit patterns so that they can be
+ * written to the control registers.
+ */
+static unsigned __devinit get_irq_config(int sscape_type, int irq)
+{
+	static const int valid_irq[] = { 9, 5, 7, 10 };
+	static const int old_irq[] = { 9, 7, 5, 15 };
+	unsigned cfg;
+
+	if (sscape_type == MEDIA_FX) {
+		for (cfg = 0; cfg < ARRAY_SIZE(old_irq); ++cfg)
+			if (irq == old_irq[cfg])
+				return cfg;
+	} else {
+		for (cfg = 0; cfg < ARRAY_SIZE(valid_irq); ++cfg)
+			if (irq == valid_irq[cfg])
+				return cfg;
+	}
+
+	return INVALID_IRQ;
+}
+
+/*
+ * Perform certain arcane port-checks to see whether there
+ * is a SoundScape board lurking behind the given ports.
+ */
+static int __devinit detect_sscape(struct soundscape *s, long wss_io)
+{
+	unsigned long flags;
+	unsigned d;
+	int retval = 0;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	/*
+	 * The following code is lifted from the original OSS driver,
+	 * and as I don't have a datasheet I cannot really comment
+	 * on what it is doing...
+	 */
+	if ((inb(HOST_CTRL_IO(s->io_base)) & 0x78) != 0)
+		goto _done;
+
+	d = inb(ODIE_ADDR_IO(s->io_base)) & 0xf0;
+	if ((d & 0x80) != 0)
+		goto _done;
+
+	if (d == 0)
+		s->ic_type = IC_ODIE;
+	else if ((d & 0x60) != 0)
+		s->ic_type = IC_OPUS;
+	else
+		goto _done;
+
+	outb(0xfa, ODIE_ADDR_IO(s->io_base));
+	if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0a)
+		goto _done;
+
+	outb(0xfe, ODIE_ADDR_IO(s->io_base));
+	if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0e)
+		goto _done;
+
+	outb(0xfe, ODIE_ADDR_IO(s->io_base));
+	d = inb(ODIE_DATA_IO(s->io_base));
+	if (s->type != SSCAPE_VIVO && (d & 0x9f) != 0x0e)
+		goto _done;
+
+	if (s->ic_type == IC_OPUS)
+		activate_ad1845_unsafe(s->io_base);
+
+	if (s->type == SSCAPE_VIVO)
+		wss_io += 4;
+
+	d  = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d | 0xc0);
+
+	/* wait for WSS codec */
+	for (d = 0; d < 500; d++) {
+		if ((inb(wss_io) & 0x80) == 0)
+			break;
+		spin_unlock_irqrestore(&s->lock, flags);
+		msleep(1);
+		spin_lock_irqsave(&s->lock, flags);
+	}
+
+	if ((inb(wss_io) & 0x80) != 0)
+		goto _done;
+
+	if (inb(wss_io + 2) == 0xff)
+		goto _done;
+
+	d  = sscape_read_unsafe(s->io_base, GA_HMCTL_REG) & 0x3f;
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d);
+
+	if ((inb(wss_io) & 0x80) != 0)
+		s->type = MEDIA_FX;
+
+	d = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
+	sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d | 0xc0);
+	/* wait for WSS codec */
+	for (d = 0; d < 500; d++) {
+		if ((inb(wss_io) & 0x80) == 0)
+			break;
+		spin_unlock_irqrestore(&s->lock, flags);
+		msleep(1);
+		spin_lock_irqsave(&s->lock, flags);
+	}
+
+	/*
+	 * SoundScape successfully detected!
+	 */
+	retval = 1;
+
+_done:
+	spin_unlock_irqrestore(&s->lock, flags);
+	return retval;
+}
+
+/*
+ * ALSA callback function, called when attempting to open the MIDI device.
+ * Check that the MIDI firmware has been loaded, because we don't want
+ * to crash the machine. Also check that someone isn't using the hardware
+ * IOCTL device.
+ */
+static int mpu401_open(struct snd_mpu401 *mpu)
+{
+	if (!verify_mpu401(mpu)) {
+		snd_printk(KERN_ERR "sscape: MIDI disabled, "
+				    "please load firmware\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+/*
+ * Initialse an MPU-401 subdevice for MIDI support on the SoundScape.
+ */
+static int __devinit create_mpu401(struct snd_card *card, int devnum,
+				   unsigned long port, int irq)
+{
+	struct soundscape *sscape = get_card_soundscape(card);
+	struct snd_rawmidi *rawmidi;
+	int err;
+
+	err = snd_mpu401_uart_new(card, devnum, MPU401_HW_MPU401, port,
+				  MPU401_INFO_INTEGRATED, irq, IRQF_DISABLED,
+				  &rawmidi);
+	if (err == 0) {
+		struct snd_mpu401 *mpu = rawmidi->private_data;
+		mpu->open_input = mpu401_open;
+		mpu->open_output = mpu401_open;
+		mpu->private_data = sscape;
+
+		initialise_mpu401(mpu);
+	}
+
+	return err;
+}
+
+
+/*
+ * Create an AD1845 PCM subdevice on the SoundScape. The AD1845
+ * is very much like a CS4231, with a few extra bits. We will
+ * try to support at least some of the extra bits by overriding
+ * some of the CS4231 callback.
+ */
+static int __devinit create_ad1845(struct snd_card *card, unsigned port,
+				   int irq, int dma1, int dma2)
+{
+	register struct soundscape *sscape = get_card_soundscape(card);
+	struct snd_wss *chip;
+	int err;
+	int codec_type = WSS_HW_DETECT;
+
+	switch (sscape->type) {
+	case MEDIA_FX:
+	case SSCAPE:
+		/*
+		 * There are some freak examples of early Soundscape cards
+		 * with CS4231 instead of AD1848/CS4248. Unfortunately, the
+		 * CS4231 works only in CS4248 compatibility mode on
+		 * these cards so force it.
+		 */
+		if (sscape->ic_type != IC_OPUS)
+			codec_type = WSS_HW_AD1848;
+		break;
+
+	case SSCAPE_VIVO:
+		port += 4;
+		break;
+	default:
+		break;
+	}
+
+	err = snd_wss_create(card, port, -1, irq, dma1, dma2,
+			     codec_type, WSS_HWSHARE_DMA1, &chip);
+	if (!err) {
+		unsigned long flags;
+		struct snd_pcm *pcm;
+
+		if (sscape->type != SSCAPE_VIVO) {
+			/*
+			 * The input clock frequency on the SoundScape must
+			 * be 14.31818 MHz, because we must set this register
+			 * to get the playback to sound correct ...
+			 */
+			snd_wss_mce_up(chip);
+			spin_lock_irqsave(&chip->reg_lock, flags);
+			snd_wss_out(chip, AD1845_CLOCK, 0x20);
+			spin_unlock_irqrestore(&chip->reg_lock, flags);
+			snd_wss_mce_down(chip);
+
+		}
+
+		err = snd_wss_pcm(chip, 0, &pcm);
+		if (err < 0) {
+			snd_printk(KERN_ERR "sscape: No PCM device "
+					    "for AD1845 chip\n");
+			goto _error;
+		}
+
+		err = snd_wss_mixer(chip);
+		if (err < 0) {
+			snd_printk(KERN_ERR "sscape: No mixer device "
+					    "for AD1845 chip\n");
+			goto _error;
+		}
+		if (chip->hardware != WSS_HW_AD1848) {
+			err = snd_wss_timer(chip, 0, NULL);
+			if (err < 0) {
+				snd_printk(KERN_ERR "sscape: No timer device "
+						    "for AD1845 chip\n");
+				goto _error;
+			}
+		}
+
+		if (sscape->type != SSCAPE_VIVO) {
+			err = snd_ctl_add(card,
+					  snd_ctl_new1(&midi_mixer_ctl, chip));
+			if (err < 0) {
+				snd_printk(KERN_ERR "sscape: Could not create "
+						    "MIDI mixer control\n");
+				goto _error;
+			}
+		}
+
+		sscape->chip = chip;
+	}
+
+_error:
+	return err;
+}
+
+
+/*
+ * Create an ALSA soundcard entry for the SoundScape, using
+ * the given list of port, IRQ and DMA resources.
+ */
+static int __devinit create_sscape(int dev, struct snd_card *card)
+{
+	struct soundscape *sscape = get_card_soundscape(card);
+	unsigned dma_cfg;
+	unsigned irq_cfg;
+	unsigned mpu_irq_cfg;
+	struct resource *io_res;
+	struct resource *wss_res;
+	unsigned long flags;
+	int err;
+	int val;
+	const char *name;
+
+	/*
+	 * Grab IO ports that we will need to probe so that we
+	 * can detect and control this hardware ...
+	 */
+	io_res = request_region(port[dev], 8, "SoundScape");
+	if (!io_res) {
+		snd_printk(KERN_ERR
+			   "sscape: can't grab port 0x%lx\n", port[dev]);
+		return -EBUSY;
+	}
+	wss_res = NULL;
+	if (sscape->type == SSCAPE_VIVO) {
+		wss_res = request_region(wss_port[dev], 4, "SoundScape");
+		if (!wss_res) {
+			snd_printk(KERN_ERR "sscape: can't grab port 0x%lx\n",
+					    wss_port[dev]);
+			err = -EBUSY;
+			goto _release_region;
+		}
+	}
+
+	/*
+	 * Grab one DMA channel ...
+	 */
+	err = request_dma(dma[dev], "SoundScape");
+	if (err < 0) {
+		snd_printk(KERN_ERR "sscape: can't grab DMA %d\n", dma[dev]);
+		goto _release_region;
+	}
+
+	spin_lock_init(&sscape->lock);
+	sscape->io_res = io_res;
+	sscape->wss_res = wss_res;
+	sscape->io_base = port[dev];
+
+	if (!detect_sscape(sscape, wss_port[dev])) {
+		printk(KERN_ERR "sscape: hardware not detected at 0x%x\n",
+			sscape->io_base);
+		err = -ENODEV;
+		goto _release_dma;
+	}
+
+	switch (sscape->type) {
+	case MEDIA_FX:
+		name = "MediaFX/SoundFX";
+		break;
+	case SSCAPE:
+		name = "Soundscape";
+		break;
+	case SSCAPE_PNP:
+		name = "Soundscape PnP";
+		break;
+	case SSCAPE_VIVO:
+		name = "Soundscape VIVO";
+		break;
+	default:
+		name = "unknown Soundscape";
+		break;
+	}
+
+	printk(KERN_INFO "sscape: %s card detected at 0x%x, using IRQ %d, DMA %d\n",
+			 name, sscape->io_base, irq[dev], dma[dev]);
+
+	/*
+	 * Check that the user didn't pass us garbage data ...
+	 */
+	irq_cfg = get_irq_config(sscape->type, irq[dev]);
+	if (irq_cfg == INVALID_IRQ) {
+		snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", irq[dev]);
+		return -ENXIO;
+	}
+
+	mpu_irq_cfg = get_irq_config(sscape->type, mpu_irq[dev]);
+	if (mpu_irq_cfg == INVALID_IRQ) {
+		snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", mpu_irq[dev]);
+		return -ENXIO;
+	}
+
+	/*
+	 * Tell the on-board devices where their resources are (I think -
+	 * I can't be sure without a datasheet ... So many magic values!)
+	 */
+	spin_lock_irqsave(&sscape->lock, flags);
+
+	sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e);
+	sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00);
+
+	/*
+	 * Enable and configure the DMA channels ...
+	 */
+	sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50);
+	dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70);
+	sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg);
+	sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20);
+
+	mpu_irq_cfg |= mpu_irq_cfg << 2;
+	val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xF7;
+	if (joystick[dev])
+		val |= 8;
+	sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0x10);
+	sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG, 0xf0 | mpu_irq_cfg);
+	sscape_write_unsafe(sscape->io_base,
+			    GA_CDCFG_REG, 0x09 | DMA_8BIT
+			    | (dma[dev] << 4) | (irq_cfg << 1));
+	/*
+	 * Enable the master IRQ ...
+	 */
+	sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80);
+
+	spin_unlock_irqrestore(&sscape->lock, flags);
+
+	/*
+	 * We have now enabled the codec chip, and so we should
+	 * detect the AD1845 device ...
+	 */
+	err = create_ad1845(card, wss_port[dev], irq[dev],
+			    dma[dev], dma2[dev]);
+	if (err < 0) {
+		snd_printk(KERN_ERR
+				"sscape: No AD1845 device at 0x%lx, IRQ %d\n",
+				wss_port[dev], irq[dev]);
+		goto _release_dma;
+	}
+	strcpy(card->driver, "SoundScape");
+	strcpy(card->shortname, name);
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s at 0x%lx, IRQ %d, DMA1 %d, DMA2 %d\n",
+		 name, sscape->chip->port, sscape->chip->irq,
+		 sscape->chip->dma1, sscape->chip->dma2);
+
+#define MIDI_DEVNUM  0
+	if (sscape->type != SSCAPE_VIVO) {
+		err = sscape_upload_bootblock(card);
+		if (err >= 0)
+			err = sscape_upload_microcode(card, err);
+
+		if (err == 0) {
+			err = create_mpu401(card, MIDI_DEVNUM, port[dev],
+					    mpu_irq[dev]);
+			if (err < 0) {
+				snd_printk(KERN_ERR "sscape: Failed to create "
+						"MPU-401 device at 0x%lx\n",
+						port[dev]);
+				goto _release_dma;
+			}
+
+			/*
+			 * Initialize mixer
+			 */
+			spin_lock_irqsave(&sscape->lock, flags);
+			sscape->midi_vol = 0;
+			host_write_ctrl_unsafe(sscape->io_base,
+						CMD_SET_MIDI_VOL, 100);
+			host_write_ctrl_unsafe(sscape->io_base,
+						sscape->midi_vol, 100);
+			host_write_ctrl_unsafe(sscape->io_base,
+						CMD_XXX_MIDI_VOL, 100);
+			host_write_ctrl_unsafe(sscape->io_base,
+						sscape->midi_vol, 100);
+			host_write_ctrl_unsafe(sscape->io_base,
+						CMD_SET_EXTMIDI, 100);
+			host_write_ctrl_unsafe(sscape->io_base,
+						0, 100);
+			host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100);
+
+			set_midi_mode_unsafe(sscape->io_base);
+			spin_unlock_irqrestore(&sscape->lock, flags);
+		}
+	}
+
+	/*
+	 * Now that we have successfully created this sound card,
+	 * it is safe to store the pointer.
+	 * NOTE: we only register the sound card's "destructor"
+	 *       function now that our "constructor" has completed.
+	 */
+	card->private_free = soundscape_free;
+
+	return 0;
+
+_release_dma:
+	free_dma(dma[dev]);
+
+_release_region:
+	release_and_free_resource(wss_res);
+	release_and_free_resource(io_res);
+
+	return err;
+}
+
+
+static int __devinit snd_sscape_match(struct device *pdev, unsigned int i)
+{
+	/*
+	 * Make sure we were given ALL of the other parameters.
+	 */
+	if (port[i] == SNDRV_AUTO_PORT)
+		return 0;
+
+	if (irq[i] == SNDRV_AUTO_IRQ ||
+	    mpu_irq[i] == SNDRV_AUTO_IRQ ||
+	    dma[i] == SNDRV_AUTO_DMA) {
+		printk(KERN_INFO
+		       "sscape: insufficient parameters, "
+		       "need IO, IRQ, MPU-IRQ and DMA\n");
+		return 0;
+	}
+
+	return 1;
+}
+
+static int __devinit snd_sscape_probe(struct device *pdev, unsigned int dev)
+{
+	struct snd_card *card;
+	struct soundscape *sscape;
+	int ret;
+
+	ret = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct soundscape), &card);
+	if (ret < 0)
+		return ret;
+
+	sscape = get_card_soundscape(card);
+	sscape->type = SSCAPE;
+
+	dma[dev] &= 0x03;
+	snd_card_set_dev(card, pdev);
+
+	ret = create_sscape(dev, card);
+	if (ret < 0)
+		goto _release_card;
+
+	ret = snd_card_register(card);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "sscape: Failed to register sound card\n");
+		goto _release_card;
+	}
+	dev_set_drvdata(pdev, card);
+	return 0;
+
+_release_card:
+	snd_card_free(card);
+	return ret;
+}
+
+static int __devexit snd_sscape_remove(struct device *devptr, unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	dev_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#define DEV_NAME "sscape"
+
+static struct isa_driver snd_sscape_driver = {
+	.match		= snd_sscape_match,
+	.probe		= snd_sscape_probe,
+	.remove		= __devexit_p(snd_sscape_remove),
+	/* FIXME: suspend/resume */
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+#ifdef CONFIG_PNP
+static inline int __devinit get_next_autoindex(int i)
+{
+	while (i < SNDRV_CARDS && port[i] != SNDRV_AUTO_PORT)
+		++i;
+	return i;
+}
+
+
+static int __devinit sscape_pnp_detect(struct pnp_card_link *pcard,
+				       const struct pnp_card_device_id *pid)
+{
+	static int idx = 0;
+	struct pnp_dev *dev;
+	struct snd_card *card;
+	struct soundscape *sscape;
+	int ret;
+
+	/*
+	 * Allow this function to fail *quietly* if all the ISA PnP
+	 * devices were configured using module parameters instead.
+	 */
+	idx = get_next_autoindex(idx);
+	if (idx >= SNDRV_CARDS)
+		return -ENOSPC;
+
+	/*
+	 * Check that we still have room for another sound card ...
+	 */
+	dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
+	if (!dev)
+		return -ENODEV;
+
+	if (!pnp_is_active(dev)) {
+		if (pnp_activate_dev(dev) < 0) {
+			snd_printk(KERN_INFO "sscape: device is inactive\n");
+			return -EBUSY;
+		}
+	}
+
+	/*
+	 * Create a new ALSA sound card entry, in anticipation
+	 * of detecting our hardware ...
+	 */
+	ret = snd_card_create(index[idx], id[idx], THIS_MODULE,
+			      sizeof(struct soundscape), &card);
+	if (ret < 0)
+		return ret;
+
+	sscape = get_card_soundscape(card);
+
+	/*
+	 * Identify card model ...
+	 */
+	if (!strncmp("ENS4081", pid->id, 7))
+		sscape->type = SSCAPE_VIVO;
+	else
+		sscape->type = SSCAPE_PNP;
+
+	/*
+	 * Read the correct parameters off the ISA PnP bus ...
+	 */
+	port[idx] = pnp_port_start(dev, 0);
+	irq[idx] = pnp_irq(dev, 0);
+	mpu_irq[idx] = pnp_irq(dev, 1);
+	dma[idx] = pnp_dma(dev, 0) & 0x03;
+	if (sscape->type == SSCAPE_PNP) {
+		dma2[idx] = dma[idx];
+		wss_port[idx] = CODEC_IO(port[idx]);
+	} else {
+		wss_port[idx] = pnp_port_start(dev, 1);
+		dma2[idx] = pnp_dma(dev, 1);
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+
+	ret = create_sscape(idx, card);
+	if (ret < 0)
+		goto _release_card;
+
+	ret = snd_card_register(card);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "sscape: Failed to register sound card\n");
+		goto _release_card;
+	}
+
+	pnp_set_card_drvdata(pcard, card);
+	++idx;
+	return 0;
+
+_release_card:
+	snd_card_free(card);
+	return ret;
+}
+
+static void __devexit sscape_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+static struct pnp_card_driver sscape_pnpc_driver = {
+	.flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
+	.name = "sscape",
+	.id_table = sscape_pnpids,
+	.probe = sscape_pnp_detect,
+	.remove = __devexit_p(sscape_pnp_remove),
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init sscape_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_sscape_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&sscape_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit sscape_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&sscape_pnpc_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_sscape_driver);
+}
+
+module_init(sscape_init);
+module_exit(sscape_exit);
diff --git a/linux/sound/isa/wavefront/Makefile b/linux/sound/isa/wavefront/Makefile
new file mode 100644
index 0000000..601bddd
--- /dev/null
+++ b/linux/sound/isa/wavefront/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-wavefront-objs := wavefront.o wavefront_fx.o wavefront_synth.o wavefront_midi.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_WAVEFRONT) += snd-wavefront.o
diff --git a/linux/sound/isa/wavefront/wavefront.c b/linux/sound/isa/wavefront/wavefront.c
new file mode 100644
index 0000000..711670e
--- /dev/null
+++ b/linux/sound/isa/wavefront/wavefront.c
@@ -0,0 +1,688 @@
+/*
+ *  ALSA card-level driver for Turtle Beach Wavefront cards 
+ *						(Maui,Tropez,Tropez+)
+ *
+ *  Copyright (c) 1997-1999 by Paul Barton-Davis <pbd@op.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/opl3.h>
+#include <sound/wss.h>
+#include <sound/snd_wavefront.h>
+
+MODULE_AUTHOR("Paul Barton-Davis <pbd@op.net>");
+MODULE_DESCRIPTION("Turtle Beach Wavefront");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Turtle Beach,Maui/Tropez/Tropez+}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	    /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	    /* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	    /* Enable this card */
+#ifdef CONFIG_PNP
+static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long cs4232_pcm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* PnP setup */
+static int cs4232_pcm_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */
+static long cs4232_mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int cs4232_mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 9,11,12,15 */
+static long ics2115_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */
+static int ics2115_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;    /* 2,9,11,12,15 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	    /* PnP setup */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	    /* 0,1,3,5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	    /* 0,1,3,5,6,7 */
+static int use_cs4232_midi[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for WaveFront soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for WaveFront soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable WaveFront soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "ISA PnP detection for WaveFront soundcards.");
+#endif
+module_param_array(cs4232_pcm_port, long, NULL, 0444);
+MODULE_PARM_DESC(cs4232_pcm_port, "Port # for CS4232 PCM interface.");
+module_param_array(cs4232_pcm_irq, int, NULL, 0444);
+MODULE_PARM_DESC(cs4232_pcm_irq, "IRQ # for CS4232 PCM interface.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for CS4232 PCM interface.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for CS4232 PCM interface.");
+module_param_array(cs4232_mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(cs4232_mpu_port, "port # for CS4232 MPU-401 interface.");
+module_param_array(cs4232_mpu_irq, int, NULL, 0444);
+MODULE_PARM_DESC(cs4232_mpu_irq, "IRQ # for CS4232 MPU-401 interface.");
+module_param_array(ics2115_irq, int, NULL, 0444);
+MODULE_PARM_DESC(ics2115_irq, "IRQ # for ICS2115.");
+module_param_array(ics2115_port, long, NULL, 0444);
+MODULE_PARM_DESC(ics2115_port, "Port # for ICS2115.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port #.");
+module_param_array(use_cs4232_midi, bool, NULL, 0444);
+MODULE_PARM_DESC(use_cs4232_midi, "Use CS4232 MPU-401 interface (inaccessibly located inside your computer)");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+
+static struct pnp_card_device_id snd_wavefront_pnpids[] = {
+	/* Tropez */
+	{ .id = "CSC7532", .devs = { { "CSC0000" }, { "CSC0010" }, { "PnPb006" }, { "CSC0004" } } },
+	/* Tropez+ */
+	{ .id = "CSC7632", .devs = { { "CSC0000" }, { "CSC0010" }, { "PnPb006" }, { "CSC0004" } } },
+	{ .id = "" }
+};
+
+MODULE_DEVICE_TABLE(pnp_card, snd_wavefront_pnpids);
+
+static int __devinit
+snd_wavefront_pnp (int dev, snd_wavefront_card_t *acard, struct pnp_card_link *card,
+		   const struct pnp_card_device_id *id)
+{
+	struct pnp_dev *pdev;
+	int err;
+
+	/* Check for each logical device. */
+
+	/* CS4232 chip (aka "windows sound system") is logical device 0 */
+
+	acard->wss = pnp_request_card_device(card, id->devs[0].id, NULL);
+	if (acard->wss == NULL)
+		return -EBUSY;
+
+	/* there is a game port at logical device 1, but we ignore it completely */
+
+	/* the control interface is logical device 2, but we ignore it
+	   completely. in fact, nobody even seems to know what it
+	   does.
+	*/
+
+	/* Only configure the CS4232 MIDI interface if its been
+	   specifically requested. It is logical device 3.
+	*/
+
+	if (use_cs4232_midi[dev]) {
+		acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+		if (acard->mpu == NULL)
+			return -EBUSY;
+	}
+
+	/* The ICS2115 synth is logical device 4 */
+
+	acard->synth = pnp_request_card_device(card, id->devs[3].id, NULL);
+	if (acard->synth == NULL)
+		return -EBUSY;
+
+	/* PCM/FM initialization */
+
+	pdev = acard->wss;
+
+	/* An interesting note from the Tropez+ FAQ:
+
+	   Q. [Ports] Why is the base address of the WSS I/O ports off by 4?
+
+	   A. WSS I/O requires a block of 8 I/O addresses ("ports"). Of these, the first
+	   4 are used to identify and configure the board. With the advent of PnP,
+	   these first 4 addresses have become obsolete, and software applications
+	   only use the last 4 addresses to control the codec chip. Therefore, the
+	   base address setting "skips past" the 4 unused addresses.
+
+	*/
+
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "PnP WSS pnp configure failure\n");
+		return err;
+	}
+
+	cs4232_pcm_port[dev] = pnp_port_start(pdev, 0);
+	fm_port[dev] = pnp_port_start(pdev, 1);
+	dma1[dev] = pnp_dma(pdev, 0);
+	dma2[dev] = pnp_dma(pdev, 1);
+	cs4232_pcm_irq[dev] = pnp_irq(pdev, 0);
+
+	/* Synth initialization */
+
+	pdev = acard->synth;
+	
+	err = pnp_activate_dev(pdev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "PnP ICS2115 pnp configure failure\n");
+		return err;
+	}
+
+	ics2115_port[dev] = pnp_port_start(pdev, 0);
+	ics2115_irq[dev] = pnp_irq(pdev, 0);
+
+	/* CS4232 MPU initialization. Configure this only if
+	   explicitly requested, since its physically inaccessible and
+	   consumes another IRQ.
+	*/
+
+	if (use_cs4232_midi[dev]) {
+
+		pdev = acard->mpu;
+
+		err = pnp_activate_dev(pdev);
+		if (err < 0) {
+			snd_printk(KERN_ERR "PnP MPU401 pnp configure failure\n");
+			cs4232_mpu_port[dev] = SNDRV_AUTO_PORT;
+		} else {
+			cs4232_mpu_port[dev] = pnp_port_start(pdev, 0);
+			cs4232_mpu_irq[dev] = pnp_irq(pdev, 0);
+		}
+
+		snd_printk (KERN_INFO "CS4232 MPU: port=0x%lx, irq=%i\n", 
+			    cs4232_mpu_port[dev], 
+			    cs4232_mpu_irq[dev]);
+	}
+
+	snd_printdd ("CS4232: pcm port=0x%lx, fm port=0x%lx, dma1=%i, dma2=%i, irq=%i\nICS2115: port=0x%lx, irq=%i\n", 
+		    cs4232_pcm_port[dev], 
+		    fm_port[dev],
+		    dma1[dev], 
+		    dma2[dev], 
+		    cs4232_pcm_irq[dev],
+		    ics2115_port[dev], 
+		    ics2115_irq[dev]);
+	
+	return 0;
+}
+
+#endif /* CONFIG_PNP */
+
+static irqreturn_t snd_wavefront_ics2115_interrupt(int irq, void *dev_id)
+{
+	snd_wavefront_card_t *acard;
+
+	acard = (snd_wavefront_card_t *) dev_id;
+
+	if (acard == NULL) 
+		return IRQ_NONE;
+
+	if (acard->wavefront.interrupts_are_midi) {
+		snd_wavefront_midi_interrupt (acard);
+	} else {
+		snd_wavefront_internal_interrupt (acard);
+	}
+	return IRQ_HANDLED;
+}
+
+static struct snd_hwdep * __devinit
+snd_wavefront_new_synth (struct snd_card *card,
+			 int hw_dev,
+			 snd_wavefront_card_t *acard)
+{
+	struct snd_hwdep *wavefront_synth;
+
+	if (snd_wavefront_detect (acard) < 0) {
+		return NULL;
+	}
+
+	if (snd_wavefront_start (&acard->wavefront) < 0) {
+		return NULL;
+	}
+
+	if (snd_hwdep_new(card, "WaveFront", hw_dev, &wavefront_synth) < 0)
+		return NULL;
+	strcpy (wavefront_synth->name, 
+		"WaveFront (ICS2115) wavetable synthesizer");
+	wavefront_synth->ops.open = snd_wavefront_synth_open;
+	wavefront_synth->ops.release = snd_wavefront_synth_release;
+	wavefront_synth->ops.ioctl = snd_wavefront_synth_ioctl;
+
+	return wavefront_synth;
+}
+
+static struct snd_hwdep * __devinit
+snd_wavefront_new_fx (struct snd_card *card,
+		      int hw_dev,
+		      snd_wavefront_card_t *acard,
+		      unsigned long port)
+
+{
+	struct snd_hwdep *fx_processor;
+
+	if (snd_wavefront_fx_start (&acard->wavefront)) {
+		snd_printk (KERN_ERR "cannot initialize YSS225 FX processor");
+		return NULL;
+	}
+
+	if (snd_hwdep_new (card, "YSS225", hw_dev, &fx_processor) < 0)
+		return NULL;
+	sprintf (fx_processor->name, "YSS225 FX Processor at 0x%lx", port);
+	fx_processor->ops.open = snd_wavefront_fx_open;
+	fx_processor->ops.release = snd_wavefront_fx_release;
+	fx_processor->ops.ioctl = snd_wavefront_fx_ioctl;
+	
+	return fx_processor;
+}
+
+static snd_wavefront_mpu_id internal_id = internal_mpu;
+static snd_wavefront_mpu_id external_id = external_mpu;
+
+static struct snd_rawmidi *__devinit
+snd_wavefront_new_midi (struct snd_card *card,
+			int midi_dev,
+			snd_wavefront_card_t *acard,
+			unsigned long port,
+			snd_wavefront_mpu_id mpu)
+
+{
+	struct snd_rawmidi *rmidi;
+	static int first = 1;
+
+	if (first) {
+		first = 0;
+		acard->wavefront.midi.base = port;
+		if (snd_wavefront_midi_start (acard)) {
+			snd_printk (KERN_ERR "cannot initialize MIDI interface\n");
+			return NULL;
+		}
+	}
+
+	if (snd_rawmidi_new (card, "WaveFront MIDI", midi_dev, 1, 1, &rmidi) < 0)
+		return NULL;
+
+	if (mpu == internal_mpu) {
+		strcpy(rmidi->name, "WaveFront MIDI (Internal)");
+		rmidi->private_data = &internal_id;
+	} else {
+		strcpy(rmidi->name, "WaveFront MIDI (External)");
+		rmidi->private_data = &external_id;
+	}
+
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_wavefront_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_wavefront_midi_input);
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+			     SNDRV_RAWMIDI_INFO_INPUT |
+			     SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	return rmidi;
+}
+
+static void
+snd_wavefront_free(struct snd_card *card)
+{
+	snd_wavefront_card_t *acard = (snd_wavefront_card_t *)card->private_data;
+	
+	if (acard) {
+		release_and_free_resource(acard->wavefront.res_base);
+		if (acard->wavefront.irq > 0)
+			free_irq(acard->wavefront.irq, (void *)acard);
+	}
+}
+
+static int snd_wavefront_card_new(int dev, struct snd_card **cardp)
+{
+	struct snd_card *card;
+	snd_wavefront_card_t *acard;
+	int err;
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(snd_wavefront_card_t), &card);
+	if (err < 0)
+		return err;
+
+	acard = card->private_data;
+	acard->wavefront.irq = -1;
+	spin_lock_init(&acard->wavefront.irq_lock);
+	init_waitqueue_head(&acard->wavefront.interrupt_sleeper);
+	spin_lock_init(&acard->wavefront.midi.open);
+	spin_lock_init(&acard->wavefront.midi.virtual);
+	acard->wavefront.card = card;
+	card->private_free = snd_wavefront_free;
+
+	*cardp = card;
+	return 0;
+}
+
+static int __devinit
+snd_wavefront_probe (struct snd_card *card, int dev)
+{
+	snd_wavefront_card_t *acard = card->private_data;
+	struct snd_wss *chip;
+	struct snd_hwdep *wavefront_synth;
+	struct snd_rawmidi *ics2115_internal_rmidi = NULL;
+	struct snd_rawmidi *ics2115_external_rmidi = NULL;
+	struct snd_hwdep *fx_processor;
+	int hw_dev = 0, midi_dev = 0, err;
+
+	/* --------- PCM --------------- */
+
+	err = snd_wss_create(card, cs4232_pcm_port[dev], -1,
+			     cs4232_pcm_irq[dev], dma1[dev], dma2[dev],
+			     WSS_HW_DETECT, 0, &chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR "can't allocate WSS device\n");
+		return err;
+	}
+
+	err = snd_wss_pcm(chip, 0, NULL);
+	if (err < 0)
+		return err;
+
+	err = snd_wss_timer(chip, 0, NULL);
+	if (err < 0)
+		return err;
+
+	/* ---------- OPL3 synth --------- */
+
+	if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) {
+		struct snd_opl3 *opl3;
+
+		err = snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
+				      OPL3_HW_OPL3_CS, 0, &opl3);
+		if (err < 0) {
+			snd_printk (KERN_ERR "can't allocate or detect OPL3 synth\n");
+			return err;
+		}
+
+		err = snd_opl3_hwdep_new(opl3, hw_dev, 1, NULL);
+		if (err < 0)
+			return err;
+		hw_dev++;
+	}
+
+	/* ------- ICS2115 Wavetable synth ------- */
+
+	acard->wavefront.res_base = request_region(ics2115_port[dev], 16,
+						   "ICS2115");
+	if (acard->wavefront.res_base == NULL) {
+		snd_printk(KERN_ERR "unable to grab ICS2115 i/o region 0x%lx-0x%lx\n",
+			   ics2115_port[dev], ics2115_port[dev] + 16 - 1);
+		return -EBUSY;
+	}
+	if (request_irq(ics2115_irq[dev], snd_wavefront_ics2115_interrupt,
+			IRQF_DISABLED, "ICS2115", acard)) {
+		snd_printk(KERN_ERR "unable to use ICS2115 IRQ %d\n", ics2115_irq[dev]);
+		return -EBUSY;
+	}
+	
+	acard->wavefront.irq = ics2115_irq[dev];
+	acard->wavefront.base = ics2115_port[dev];
+
+	wavefront_synth = snd_wavefront_new_synth(card, hw_dev, acard);
+	if (wavefront_synth == NULL) {
+		snd_printk (KERN_ERR "can't create WaveFront synth device\n");
+		return -ENOMEM;
+	}
+
+	strcpy (wavefront_synth->name, "ICS2115 Wavetable MIDI Synthesizer");
+	wavefront_synth->iface = SNDRV_HWDEP_IFACE_ICS2115;
+	hw_dev++;
+
+	/* --------- Mixer ------------ */
+
+	err = snd_wss_mixer(chip);
+	if (err < 0) {
+		snd_printk (KERN_ERR "can't allocate mixer device\n");
+		return err;
+	}
+
+	/* -------- CS4232 MPU-401 interface -------- */
+
+	if (cs4232_mpu_port[dev] > 0 && cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) {
+		err = snd_mpu401_uart_new(card, midi_dev, MPU401_HW_CS4232,
+					  cs4232_mpu_port[dev], 0,
+					  cs4232_mpu_irq[dev], IRQF_DISABLED,
+					  NULL);
+		if (err < 0) {
+			snd_printk (KERN_ERR "can't allocate CS4232 MPU-401 device\n");
+			return err;
+		}
+		midi_dev++;
+	}
+
+	/* ------ ICS2115 internal MIDI ------------ */
+
+	if (ics2115_port[dev] > 0 && ics2115_port[dev] != SNDRV_AUTO_PORT) {
+		ics2115_internal_rmidi = 
+			snd_wavefront_new_midi (card, 
+						midi_dev,
+						acard,
+						ics2115_port[dev],
+						internal_mpu);
+		if (ics2115_internal_rmidi == NULL) {
+			snd_printk (KERN_ERR "can't setup ICS2115 internal MIDI device\n");
+			return -ENOMEM;
+		}
+		midi_dev++;
+	}
+
+	/* ------ ICS2115 external MIDI ------------ */
+
+	if (ics2115_port[dev] > 0 && ics2115_port[dev] != SNDRV_AUTO_PORT) {
+		ics2115_external_rmidi = 
+			snd_wavefront_new_midi (card, 
+						midi_dev,
+						acard,
+						ics2115_port[dev],
+						external_mpu);
+		if (ics2115_external_rmidi == NULL) {
+			snd_printk (KERN_ERR "can't setup ICS2115 external MIDI device\n");
+			return -ENOMEM;
+		}
+		midi_dev++;
+	}
+
+	/* FX processor for Tropez+ */
+
+	if (acard->wavefront.has_fx) {
+		fx_processor = snd_wavefront_new_fx (card,
+						     hw_dev,
+						     acard,
+						     ics2115_port[dev]);
+		if (fx_processor == NULL) {
+			snd_printk (KERN_ERR "can't setup FX device\n");
+			return -ENOMEM;
+		}
+
+		hw_dev++;
+
+		strcpy(card->driver, "Tropez+");
+		strcpy(card->shortname, "Turtle Beach Tropez+");
+	} else {
+		/* Need a way to distinguish between Maui and Tropez */
+		strcpy(card->driver, "WaveFront");
+		strcpy(card->shortname, "Turtle Beach WaveFront");
+	}
+
+	/* ----- Register the card --------- */
+
+	/* Not safe to include "Turtle Beach" in longname, due to 
+	   length restrictions
+	*/
+
+	sprintf(card->longname, "%s PCM 0x%lx irq %d dma %d",
+		card->driver,
+		chip->port,
+		cs4232_pcm_irq[dev],
+		dma1[dev]);
+
+	if (dma2[dev] >= 0 && dma2[dev] < 8)
+		sprintf(card->longname + strlen(card->longname), "&%d", dma2[dev]);
+
+	if (cs4232_mpu_port[dev] > 0 && cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) {
+		sprintf (card->longname + strlen (card->longname), 
+			 " MPU-401 0x%lx irq %d",
+			 cs4232_mpu_port[dev],
+			 cs4232_mpu_irq[dev]);
+	}
+
+	sprintf (card->longname + strlen (card->longname), 
+		 " SYNTH 0x%lx irq %d",
+		 ics2115_port[dev],
+		 ics2115_irq[dev]);
+
+	return snd_card_register(card);
+}	
+
+static int __devinit snd_wavefront_isa_match(struct device *pdev,
+					     unsigned int dev)
+{
+	if (!enable[dev])
+		return 0;
+#ifdef CONFIG_PNP
+	if (isapnp[dev])
+		return 0;
+#endif
+	if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR "specify CS4232 port\n");
+		return 0;
+	}
+	if (ics2115_port[dev] == SNDRV_AUTO_PORT) {
+		snd_printk(KERN_ERR "specify ICS2115 port\n");
+		return 0;
+	}
+	return 1;
+}
+
+static int __devinit snd_wavefront_isa_probe(struct device *pdev,
+					     unsigned int dev)
+{
+	struct snd_card *card;
+	int err;
+
+	err = snd_wavefront_card_new(dev, &card);
+	if (err < 0)
+		return err;
+	snd_card_set_dev(card, pdev);
+	if ((err = snd_wavefront_probe(card, dev)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	
+	dev_set_drvdata(pdev, card);
+	return 0;
+}
+
+static int __devexit snd_wavefront_isa_remove(struct device *devptr,
+					      unsigned int dev)
+{
+	snd_card_free(dev_get_drvdata(devptr));
+	dev_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#define DEV_NAME "wavefront"
+
+static struct isa_driver snd_wavefront_driver = {
+	.match		= snd_wavefront_isa_match,
+	.probe		= snd_wavefront_isa_probe,
+	.remove		= __devexit_p(snd_wavefront_isa_remove),
+	/* FIXME: suspend, resume */
+	.driver		= {
+		.name	= DEV_NAME
+	},
+};
+
+
+#ifdef CONFIG_PNP
+static int __devinit snd_wavefront_pnp_detect(struct pnp_card_link *pcard,
+					const struct pnp_card_device_id *pid)
+{
+	static int dev;
+	struct snd_card *card;
+	int res;
+
+	for ( ; dev < SNDRV_CARDS; dev++) {
+		if (enable[dev] && isapnp[dev])
+			break;
+	}
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	res = snd_wavefront_card_new(dev, &card);
+	if (res < 0)
+		return res;
+
+	if (snd_wavefront_pnp (dev, card->private_data, pcard, pid) < 0) {
+		if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) {
+			snd_printk (KERN_ERR "isapnp detection failed\n");
+			snd_card_free (card);
+			return -ENODEV;
+		}
+	}
+	snd_card_set_dev(card, &pcard->card->dev);
+
+	if ((res = snd_wavefront_probe(card, dev)) < 0)
+		return res;
+
+	pnp_set_card_drvdata(pcard, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_wavefront_pnp_remove(struct pnp_card_link * pcard)
+{
+	snd_card_free(pnp_get_card_drvdata(pcard));
+	pnp_set_card_drvdata(pcard, NULL);
+}
+
+static struct pnp_card_driver wavefront_pnpc_driver = {
+	.flags		= PNP_DRIVER_RES_DISABLE,
+	.name		= "wavefront",
+	.id_table	= snd_wavefront_pnpids,
+	.probe		= snd_wavefront_pnp_detect,
+	.remove		= __devexit_p(snd_wavefront_pnp_remove),
+	/* FIXME: suspend,resume */
+};
+
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_wavefront_init(void)
+{
+	int err;
+
+	err = isa_register_driver(&snd_wavefront_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+	if (!err)
+		isa_registered = 1;
+
+	err = pnp_register_card_driver(&wavefront_pnpc_driver);
+	if (!err)
+		pnp_registered = 1;
+
+	if (isa_registered)
+		err = 0;
+#endif
+	return err;
+}
+
+static void __exit alsa_card_wavefront_exit(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&wavefront_pnpc_driver);
+	if (isa_registered)
+#endif
+		isa_unregister_driver(&snd_wavefront_driver);
+}
+
+module_init(alsa_card_wavefront_init)
+module_exit(alsa_card_wavefront_exit)
diff --git a/linux/sound/isa/wavefront/wavefront_fx.c b/linux/sound/isa/wavefront/wavefront_fx.c
new file mode 100644
index 0000000..657e2d6
--- /dev/null
+++ b/linux/sound/isa/wavefront/wavefront_fx.c
@@ -0,0 +1,284 @@
+/*
+ *  Copyright (c) 1998-2002 by Paul Davis <pbd@op.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <asm/io.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <sound/core.h>
+#include <sound/snd_wavefront.h>
+#include <sound/initval.h>
+
+/* Control bits for the Load Control Register
+ */
+
+#define FX_LSB_TRANSFER 0x01    /* transfer after DSP LSB byte written */
+#define FX_MSB_TRANSFER 0x02    /* transfer after DSP MSB byte written */
+#define FX_AUTO_INCR    0x04    /* auto-increment DSP address after transfer */
+
+#define WAIT_IDLE	0xff
+
+static int
+wavefront_fx_idle (snd_wavefront_t *dev)
+
+{
+	int i;
+	unsigned int x = 0x80;
+
+	for (i = 0; i < 1000; i++) {
+		x = inb (dev->fx_status);
+		if ((x & 0x80) == 0) {
+			break;
+		}
+	}
+
+	if (x & 0x80) {
+		snd_printk ("FX device never idle.\n");
+		return 0;
+	}
+
+	return (1);
+}
+
+static void
+wavefront_fx_mute (snd_wavefront_t *dev, int onoff)
+
+{
+	if (!wavefront_fx_idle(dev)) {
+		return;
+	}
+
+	outb (onoff ? 0x02 : 0x00, dev->fx_op);
+}
+
+static int
+wavefront_fx_memset (snd_wavefront_t *dev,
+		     int page,
+		     int addr,
+		     int cnt,
+		     unsigned short *data)
+{
+	if (page < 0 || page > 7) {
+		snd_printk ("FX memset: "
+			"page must be >= 0 and <= 7\n");
+		return -(EINVAL);
+	}
+
+	if (addr < 0 || addr > 0x7f) {
+		snd_printk ("FX memset: "
+			"addr must be >= 0 and <= 7f\n");
+		return -(EINVAL);
+	}
+
+	if (cnt == 1) {
+
+		outb (FX_LSB_TRANSFER, dev->fx_lcr);
+		outb (page, dev->fx_dsp_page);
+		outb (addr, dev->fx_dsp_addr);
+		outb ((data[0] >> 8), dev->fx_dsp_msb);
+		outb ((data[0] & 0xff), dev->fx_dsp_lsb);
+
+		snd_printk ("FX: addr %d:%x set to 0x%x\n",
+			page, addr, data[0]);
+
+	} else {
+		int i;
+
+		outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
+		outb (page, dev->fx_dsp_page);
+		outb (addr, dev->fx_dsp_addr);
+
+		for (i = 0; i < cnt; i++) {
+			outb ((data[i] >> 8), dev->fx_dsp_msb);
+			outb ((data[i] & 0xff), dev->fx_dsp_lsb);
+			if (!wavefront_fx_idle (dev)) {
+				break;
+			}
+		}
+
+		if (i != cnt) {
+			snd_printk ("FX memset "
+				    "(0x%x, 0x%x, 0x%lx, %d) incomplete\n",
+				    page, addr, (unsigned long) data, cnt);
+			return -(EIO);
+		}
+	}
+
+	return 0;
+}
+
+int
+snd_wavefront_fx_detect (snd_wavefront_t *dev)
+
+{
+	/* This is a crude check, but its the best one I have for now.
+	   Certainly on the Maui and the Tropez, wavefront_fx_idle() will
+	   report "never idle", which suggests that this test should
+	   work OK.
+	*/
+
+	if (inb (dev->fx_status) & 0x80) {
+		snd_printk ("Hmm, probably a Maui or Tropez.\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+int
+snd_wavefront_fx_open (struct snd_hwdep *hw, struct file *file)
+
+{
+	if (!try_module_get(hw->card->module))
+		return -EFAULT;
+	file->private_data = hw;
+	return 0;
+}
+
+int 
+snd_wavefront_fx_release (struct snd_hwdep *hw, struct file *file)
+
+{
+	module_put(hw->card->module);
+	return 0;
+}
+
+int
+snd_wavefront_fx_ioctl (struct snd_hwdep *sdev, struct file *file,
+			unsigned int cmd, unsigned long arg)
+
+{
+	struct snd_card *card;
+	snd_wavefront_card_t *acard;
+	snd_wavefront_t *dev;
+	wavefront_fx_info r;
+	unsigned short *page_data = NULL;
+	unsigned short *pd;
+	int err = 0;
+
+	card = sdev->card;
+	if (snd_BUG_ON(!card))
+		return -ENODEV;
+	if (snd_BUG_ON(!card->private_data))
+		return -ENODEV;
+
+	acard = card->private_data;
+	dev = &acard->wavefront;
+
+	if (copy_from_user (&r, (void __user *)arg, sizeof (wavefront_fx_info)))
+		return -EFAULT;
+
+	switch (r.request) {
+	case WFFX_MUTE:
+		wavefront_fx_mute (dev, r.data[0]);
+		return -EIO;
+
+	case WFFX_MEMSET:
+		if (r.data[2] <= 0) {
+			snd_printk ("cannot write "
+				"<= 0 bytes to FX\n");
+			return -EIO;
+		} else if (r.data[2] == 1) {
+			pd = (unsigned short *) &r.data[3];
+		} else {
+			if (r.data[2] > 256) {
+				snd_printk ("cannot write "
+					    "> 512 bytes to FX\n");
+				return -EIO;
+			}
+			page_data = memdup_user((unsigned char __user *)
+						r.data[3],
+						r.data[2] * sizeof(short));
+			if (IS_ERR(page_data))
+				return PTR_ERR(page_data);
+			pd = page_data;
+		}
+
+		err = wavefront_fx_memset (dev,
+			     r.data[0], /* page */
+			     r.data[1], /* addr */
+			     r.data[2], /* cnt */
+			     pd);
+		kfree(page_data);
+		break;
+
+	default:
+		snd_printk ("FX: ioctl %d not yet supported\n",
+			    r.request);
+		return -ENOTTY;
+	}
+	return err;
+}
+
+/* YSS225 initialization.
+
+   This code was developed using DOSEMU. The Turtle Beach SETUPSND
+   utility was run with I/O tracing in DOSEMU enabled, and a reconstruction
+   of the port I/O done, using the Yamaha faxback document as a guide
+   to add more logic to the code. Its really pretty weird.
+
+   This is the approach of just dumping the whole I/O
+   sequence as a series of port/value pairs and a simple loop
+   that outputs it.
+*/
+
+int __devinit
+snd_wavefront_fx_start (snd_wavefront_t *dev)
+{
+	unsigned int i;
+	int err;
+	const struct firmware *firmware = NULL;
+
+	if (dev->fx_initialized)
+		return 0;
+
+	err = request_firmware(&firmware, "yamaha/yss225_registers.bin",
+			       dev->card->dev);
+	if (err < 0) {
+		err = -1;
+		goto out;
+	}
+
+	for (i = 0; i + 1 < firmware->size; i += 2) {
+		if (firmware->data[i] >= 8 && firmware->data[i] < 16) {
+			outb(firmware->data[i + 1],
+			     dev->base + firmware->data[i]);
+		} else if (firmware->data[i] == WAIT_IDLE) {
+			if (!wavefront_fx_idle(dev)) {
+				err = -1;
+				goto out;
+			}
+		} else {
+			snd_printk(KERN_ERR "invalid address"
+				   " in register data\n");
+			err = -1;
+			goto out;
+		}
+	}
+
+	dev->fx_initialized = 1;
+	err = 0;
+
+out:
+	release_firmware(firmware);
+	return err;
+}
+
+MODULE_FIRMWARE("yamaha/yss225_registers.bin");
diff --git a/linux/sound/isa/wavefront/wavefront_midi.c b/linux/sound/isa/wavefront/wavefront_midi.c
new file mode 100644
index 0000000..f14a7c0
--- /dev/null
+++ b/linux/sound/isa/wavefront/wavefront_midi.c
@@ -0,0 +1,577 @@
+/*
+ * Copyright (C) by Paul Barton-Davis 1998-1999
+ *
+ * This file is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this
+ * software for more info.  
+ */
+
+/* The low level driver for the WaveFront ICS2115 MIDI interface(s)
+ *
+ * Note that there is also an MPU-401 emulation (actually, a UART-401
+ * emulation) on the CS4232 on the Tropez and Tropez Plus. This code
+ * has nothing to do with that interface at all.
+ *
+ * The interface is essentially just a UART-401, but is has the
+ * interesting property of supporting what Turtle Beach called
+ * "Virtual MIDI" mode. In this mode, there are effectively *two*
+ * MIDI buses accessible via the interface, one that is routed
+ * solely to/from the external WaveFront synthesizer and the other
+ * corresponding to the pin/socket connector used to link external
+ * MIDI devices to the board.
+ *
+ * This driver fully supports this mode, allowing two distinct MIDI
+ * busses to be used completely independently, giving 32 channels of
+ * MIDI routing, 16 to the WaveFront synth and 16 to the external MIDI
+ * bus. The devices are named /dev/snd/midiCnD0 and /dev/snd/midiCnD1,
+ * where `n' is the card number. Note that the device numbers may be
+ * something other than 0 and 1 if the CS4232 UART/MPU-401 interface
+ * is enabled.
+ *
+ * Switching between the two is accomplished externally by the driver
+ * using the two otherwise unused MIDI bytes. See the code for more details.
+ *
+ * NOTE: VIRTUAL MIDI MODE IS ON BY DEFAULT (see lowlevel/isa/wavefront.c)
+ *
+ * The main reason to turn off Virtual MIDI mode is when you want to
+ * tightly couple the WaveFront synth with an external MIDI
+ * device. You won't be able to distinguish the source of any MIDI
+ * data except via SysEx ID, but thats probably OK, since for the most
+ * part, the WaveFront won't be sending any MIDI data at all.
+ *  
+ * The main reason to turn on Virtual MIDI Mode is to provide two
+ * completely independent 16-channel MIDI buses, one to the
+ * WaveFront and one to any external MIDI devices. Given the 32
+ * voice nature of the WaveFront, its pretty easy to find a use
+ * for all 16 channels driving just that synth.
+ *  
+ */
+
+#include <asm/io.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/snd_wavefront.h>
+
+static inline int 
+wf_mpu_status (snd_wavefront_midi_t *midi)
+
+{
+	return inb (midi->mpu_status_port);
+}
+
+static inline int 
+input_avail (snd_wavefront_midi_t *midi)
+
+{
+	return !(wf_mpu_status(midi) & INPUT_AVAIL);
+}
+
+static inline int
+output_ready (snd_wavefront_midi_t *midi)
+
+{
+	return !(wf_mpu_status(midi) & OUTPUT_READY);
+}
+
+static inline int 
+read_data (snd_wavefront_midi_t *midi)
+
+{
+	return inb (midi->mpu_data_port);
+}
+
+static inline void 
+write_data (snd_wavefront_midi_t *midi, unsigned char byte)
+
+{
+	outb (byte, midi->mpu_data_port);
+}
+
+static snd_wavefront_midi_t *
+get_wavefront_midi (struct snd_rawmidi_substream *substream)
+
+{
+	struct snd_card *card;
+	snd_wavefront_card_t *acard;
+
+	if (substream == NULL || substream->rmidi == NULL) 
+	        return NULL;
+
+	card = substream->rmidi->card;
+
+	if (card == NULL) 
+	        return NULL;
+
+	if (card->private_data == NULL) 
+ 	        return NULL;
+
+	acard = card->private_data;
+
+	return &acard->wavefront.midi;
+}
+
+static void snd_wavefront_midi_output_write(snd_wavefront_card_t *card)
+{
+	snd_wavefront_midi_t *midi = &card->wavefront.midi;
+	snd_wavefront_mpu_id  mpu;
+	unsigned long flags;
+	unsigned char midi_byte;
+	int max = 256, mask = 1;
+	int timeout;
+
+	/* Its not OK to try to change the status of "virtuality" of
+	   the MIDI interface while we're outputting stuff.  See
+	   snd_wavefront_midi_{enable,disable}_virtual () for the
+	   other half of this.  
+
+	   The first loop attempts to flush any data from the
+	   current output device, and then the second 
+	   emits the switch byte (if necessary), and starts
+	   outputting data for the output device currently in use.
+	*/
+
+	if (midi->substream_output[midi->output_mpu] == NULL) {
+		goto __second;
+	}
+
+	while (max > 0) {
+
+		/* XXX fix me - no hard timing loops allowed! */
+
+		for (timeout = 30000; timeout > 0; timeout--) {
+			if (output_ready (midi))
+				break;
+		}
+	
+		spin_lock_irqsave (&midi->virtual, flags);
+		if ((midi->mode[midi->output_mpu] & MPU401_MODE_OUTPUT) == 0) {
+			spin_unlock_irqrestore (&midi->virtual, flags);
+			goto __second;
+		}
+		if (output_ready (midi)) {
+			if (snd_rawmidi_transmit(midi->substream_output[midi->output_mpu], &midi_byte, 1) == 1) {
+				if (!midi->isvirtual ||
+					(midi_byte != WF_INTERNAL_SWITCH &&
+					 midi_byte != WF_EXTERNAL_SWITCH))
+					write_data(midi, midi_byte);
+				max--;
+			} else {
+				if (midi->istimer) {
+					if (--midi->istimer <= 0)
+						del_timer(&midi->timer);
+				}
+				midi->mode[midi->output_mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
+				spin_unlock_irqrestore (&midi->virtual, flags);
+				goto __second;
+			}
+		} else {
+			spin_unlock_irqrestore (&midi->virtual, flags);
+			return;
+		}
+		spin_unlock_irqrestore (&midi->virtual, flags);
+	}
+
+      __second:
+
+	if (midi->substream_output[!midi->output_mpu] == NULL) {
+		return;
+	}
+
+	while (max > 0) {
+
+		/* XXX fix me - no hard timing loops allowed! */
+
+		for (timeout = 30000; timeout > 0; timeout--) {
+			if (output_ready (midi))
+				break;
+		}
+	
+		spin_lock_irqsave (&midi->virtual, flags);
+		if (!midi->isvirtual)
+			mask = 0;
+		mpu = midi->output_mpu ^ mask;
+		mask = 0;	/* don't invert the value from now */
+		if ((midi->mode[mpu] & MPU401_MODE_OUTPUT) == 0) {
+			spin_unlock_irqrestore (&midi->virtual, flags);
+			return;
+		}
+		if (snd_rawmidi_transmit_empty(midi->substream_output[mpu]))
+			goto __timer;
+		if (output_ready (midi)) {
+			if (mpu != midi->output_mpu) {
+				write_data(midi, mpu == internal_mpu ?
+							WF_INTERNAL_SWITCH :
+							WF_EXTERNAL_SWITCH);
+				midi->output_mpu = mpu;
+			} else if (snd_rawmidi_transmit(midi->substream_output[mpu], &midi_byte, 1) == 1) {
+				if (!midi->isvirtual ||
+					(midi_byte != WF_INTERNAL_SWITCH &&
+					 midi_byte != WF_EXTERNAL_SWITCH))
+					write_data(midi, midi_byte);
+				max--;
+			} else {
+			      __timer:
+				if (midi->istimer) {
+					if (--midi->istimer <= 0)
+						del_timer(&midi->timer);
+				}
+				midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
+				spin_unlock_irqrestore (&midi->virtual, flags);
+				return;
+			}
+		} else {
+			spin_unlock_irqrestore (&midi->virtual, flags);
+			return;
+		}
+		spin_unlock_irqrestore (&midi->virtual, flags);
+	}
+}
+
+static int snd_wavefront_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (snd_BUG_ON(!substream || !substream->rmidi))
+		return -ENXIO;
+	if (snd_BUG_ON(!substream->rmidi->private_data))
+		return -ENXIO;
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL)
+	        return -EIO;
+
+	spin_lock_irqsave (&midi->open, flags);
+	midi->mode[mpu] |= MPU401_MODE_INPUT;
+	midi->substream_input[mpu] = substream;
+	spin_unlock_irqrestore (&midi->open, flags);
+
+	return 0;
+}
+
+static int snd_wavefront_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (snd_BUG_ON(!substream || !substream->rmidi))
+		return -ENXIO;
+	if (snd_BUG_ON(!substream->rmidi->private_data))
+		return -ENXIO;
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL)
+	        return -EIO;
+
+	spin_lock_irqsave (&midi->open, flags);
+	midi->mode[mpu] |= MPU401_MODE_OUTPUT;
+	midi->substream_output[mpu] = substream;
+	spin_unlock_irqrestore (&midi->open, flags);
+
+	return 0;
+}
+
+static int snd_wavefront_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (snd_BUG_ON(!substream || !substream->rmidi))
+		return -ENXIO;
+	if (snd_BUG_ON(!substream->rmidi->private_data))
+		return -ENXIO;
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL)
+	        return -EIO;
+
+	spin_lock_irqsave (&midi->open, flags);
+	midi->mode[mpu] &= ~MPU401_MODE_INPUT;
+	spin_unlock_irqrestore (&midi->open, flags);
+
+	return 0;
+}
+
+static int snd_wavefront_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (snd_BUG_ON(!substream || !substream->rmidi))
+		return -ENXIO;
+	if (snd_BUG_ON(!substream->rmidi->private_data))
+		return -ENXIO;
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL)
+	        return -EIO;
+
+	spin_lock_irqsave (&midi->open, flags);
+	midi->mode[mpu] &= ~MPU401_MODE_OUTPUT;
+	spin_unlock_irqrestore (&midi->open, flags);
+	return 0;
+}
+
+static void snd_wavefront_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (substream == NULL || substream->rmidi == NULL) 
+	        return;
+
+	if (substream->rmidi->private_data == NULL)
+	        return;
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL) {
+		return;
+	}
+
+	spin_lock_irqsave (&midi->virtual, flags);
+	if (up) {
+		midi->mode[mpu] |= MPU401_MODE_INPUT_TRIGGER;
+	} else {
+		midi->mode[mpu] &= ~MPU401_MODE_INPUT_TRIGGER;
+	}
+	spin_unlock_irqrestore (&midi->virtual, flags);
+}
+
+static void snd_wavefront_midi_output_timer(unsigned long data)
+{
+	snd_wavefront_card_t *card = (snd_wavefront_card_t *)data;
+	snd_wavefront_midi_t *midi = &card->wavefront.midi;
+	unsigned long flags;
+	
+	spin_lock_irqsave (&midi->virtual, flags);
+	midi->timer.expires = 1 + jiffies;
+	add_timer(&midi->timer);
+	spin_unlock_irqrestore (&midi->virtual, flags);
+	snd_wavefront_midi_output_write(card);
+}
+
+static void snd_wavefront_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	snd_wavefront_mpu_id mpu;
+
+	if (substream == NULL || substream->rmidi == NULL) 
+	        return;
+
+	if (substream->rmidi->private_data == NULL)
+	        return;
+
+	mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data);
+
+	if ((midi = get_wavefront_midi (substream)) == NULL) {
+		return;
+	}
+
+	spin_lock_irqsave (&midi->virtual, flags);
+	if (up) {
+		if ((midi->mode[mpu] & MPU401_MODE_OUTPUT_TRIGGER) == 0) {
+			if (!midi->istimer) {
+				init_timer(&midi->timer);
+				midi->timer.function = snd_wavefront_midi_output_timer;
+				midi->timer.data = (unsigned long) substream->rmidi->card->private_data;
+				midi->timer.expires = 1 + jiffies;
+				add_timer(&midi->timer);
+			}
+			midi->istimer++;
+			midi->mode[mpu] |= MPU401_MODE_OUTPUT_TRIGGER;
+		}
+	} else {
+		midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER;
+	}
+	spin_unlock_irqrestore (&midi->virtual, flags);
+
+	if (up)
+		snd_wavefront_midi_output_write((snd_wavefront_card_t *)substream->rmidi->card->private_data);
+}
+
+void
+snd_wavefront_midi_interrupt (snd_wavefront_card_t *card)
+
+{
+	unsigned long flags;
+	snd_wavefront_midi_t *midi;
+	static struct snd_rawmidi_substream *substream = NULL;
+	static int mpu = external_mpu; 
+	int max = 128;
+	unsigned char byte;
+
+	midi = &card->wavefront.midi;
+
+	if (!input_avail (midi)) { /* not for us */
+		snd_wavefront_midi_output_write(card);
+		return;
+	}
+
+	spin_lock_irqsave (&midi->virtual, flags);
+	while (--max) {
+
+		if (input_avail (midi)) {
+			byte = read_data (midi);
+
+			if (midi->isvirtual) {				
+				if (byte == WF_EXTERNAL_SWITCH) {
+					substream = midi->substream_input[external_mpu];
+					mpu = external_mpu;
+				} else if (byte == WF_INTERNAL_SWITCH) { 
+					substream = midi->substream_output[internal_mpu];
+					mpu = internal_mpu;
+				} /* else just leave it as it is */
+			} else {
+				substream = midi->substream_input[internal_mpu];
+				mpu = internal_mpu;
+			}
+
+			if (substream == NULL) {
+				continue;
+			}
+
+			if (midi->mode[mpu] & MPU401_MODE_INPUT_TRIGGER) {
+				snd_rawmidi_receive(substream, &byte, 1);
+			}
+		} else {
+			break;
+		}
+	} 
+	spin_unlock_irqrestore (&midi->virtual, flags);
+
+	snd_wavefront_midi_output_write(card);
+}
+
+void
+snd_wavefront_midi_enable_virtual (snd_wavefront_card_t *card)
+
+{
+	unsigned long flags;
+
+	spin_lock_irqsave (&card->wavefront.midi.virtual, flags);
+	card->wavefront.midi.isvirtual = 1;
+	card->wavefront.midi.output_mpu = internal_mpu;
+	card->wavefront.midi.input_mpu = internal_mpu;
+	spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags);
+}
+
+void
+snd_wavefront_midi_disable_virtual (snd_wavefront_card_t *card)
+
+{
+	unsigned long flags;
+
+	spin_lock_irqsave (&card->wavefront.midi.virtual, flags);
+	// snd_wavefront_midi_input_close (card->ics2115_external_rmidi);
+	// snd_wavefront_midi_output_close (card->ics2115_external_rmidi);
+	card->wavefront.midi.isvirtual = 0;
+	spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags);
+}
+
+int __devinit
+snd_wavefront_midi_start (snd_wavefront_card_t *card)
+
+{
+	int ok, i;
+	unsigned char rbuf[4], wbuf[4];
+	snd_wavefront_t *dev;
+	snd_wavefront_midi_t *midi;
+
+	dev = &card->wavefront;
+	midi = &dev->midi;
+
+	/* The ICS2115 MPU-401 interface doesn't do anything
+	   until its set into UART mode.
+	*/
+
+	/* XXX fix me - no hard timing loops allowed! */
+
+	for (i = 0; i < 30000 && !output_ready (midi); i++);
+
+	if (!output_ready (midi)) {
+		snd_printk ("MIDI interface not ready for command\n");
+		return -1;
+	}
+
+	/* Any interrupts received from now on
+	   are owned by the MIDI side of things.
+	*/
+
+	dev->interrupts_are_midi = 1;
+	
+	outb (UART_MODE_ON, midi->mpu_command_port);
+
+	for (ok = 0, i = 50000; i > 0 && !ok; i--) {
+		if (input_avail (midi)) {
+			if (read_data (midi) == MPU_ACK) {
+				ok = 1;
+				break;
+			}
+		}
+	}
+
+	if (!ok) {
+		snd_printk ("cannot set UART mode for MIDI interface");
+		dev->interrupts_are_midi = 0;
+		return -1;
+	}
+
+	/* Route external MIDI to WaveFront synth (by default) */
+    
+	if (snd_wavefront_cmd (dev, WFC_MISYNTH_ON, rbuf, wbuf)) {
+		snd_printk ("can't enable MIDI-IN-2-synth routing.\n");
+		/* XXX error ? */
+	}
+
+	/* Turn on Virtual MIDI, but first *always* turn it off,
+	   since otherwise consectutive reloads of the driver will
+	   never cause the hardware to generate the initial "internal" or 
+	   "external" source bytes in the MIDI data stream. This
+	   is pretty important, since the internal hardware generally will
+	   be used to generate none or very little MIDI output, and
+	   thus the only source of MIDI data is actually external. Without
+	   the switch bytes, the driver will think it all comes from
+	   the internal interface. Duh.
+	*/
+
+	if (snd_wavefront_cmd (dev, WFC_VMIDI_OFF, rbuf, wbuf)) { 
+		snd_printk ("virtual MIDI mode not disabled\n");
+		return 0; /* We're OK, but missing the external MIDI dev */
+	}
+
+	snd_wavefront_midi_enable_virtual (card);
+
+	if (snd_wavefront_cmd (dev, WFC_VMIDI_ON, rbuf, wbuf)) {
+		snd_printk ("cannot enable virtual MIDI mode.\n");
+		snd_wavefront_midi_disable_virtual (card);
+	} 
+	return 0;
+}
+
+struct snd_rawmidi_ops snd_wavefront_midi_output =
+{
+	.open =		snd_wavefront_midi_output_open,
+	.close =	snd_wavefront_midi_output_close,
+	.trigger =	snd_wavefront_midi_output_trigger,
+};
+
+struct snd_rawmidi_ops snd_wavefront_midi_input =
+{
+	.open =		snd_wavefront_midi_input_open,
+	.close =	snd_wavefront_midi_input_close,
+	.trigger =	snd_wavefront_midi_input_trigger,
+};
+
diff --git a/linux/sound/isa/wavefront/wavefront_synth.c b/linux/sound/isa/wavefront/wavefront_synth.c
new file mode 100644
index 0000000..4fb7b19
--- /dev/null
+++ b/linux/sound/isa/wavefront/wavefront_synth.c
@@ -0,0 +1,2198 @@
+/* Copyright (C) by Paul Barton-Davis 1998-1999
+ *
+ * Some portions of this file are taken from work that is
+ * copyright (C) by Hannu Savolainen 1993-1996
+ *
+ * This program is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.  
+ */
+
+/*  
+ * An ALSA lowlevel driver for Turtle Beach ICS2115 wavetable synth
+ *                                             (Maui, Tropez, Tropez Plus)
+ *
+ * This driver supports the onboard wavetable synthesizer (an ICS2115),
+ * including patch, sample and program loading and unloading, conversion
+ * of GUS patches during loading, and full user-level access to all
+ * WaveFront commands. It tries to provide semi-intelligent patch and
+ * sample management as well.
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/firmware.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/snd_wavefront.h>
+#include <sound/initval.h>
+
+static int wf_raw = 0; /* we normally check for "raw state" to firmware
+			  loading. if non-zero, then during driver loading, the
+			  state of the board is ignored, and we reset the
+			  board and load the firmware anyway.
+		       */
+		   
+static int fx_raw = 1; /* if this is zero, we'll leave the FX processor in
+			  whatever state it is when the driver is loaded.
+			  The default is to download the microprogram and
+			  associated coefficients to set it up for "default"
+			  operation, whatever that means.
+		       */
+
+static int debug_default = 0;  /* you can set this to control debugging
+				  during driver loading. it takes any combination
+				  of the WF_DEBUG_* flags defined in
+				  wavefront.h
+			       */
+
+/* XXX this needs to be made firmware and hardware version dependent */
+
+#define DEFAULT_OSPATH	"wavefront.os"
+static char *ospath = DEFAULT_OSPATH; /* the firmware file name */
+
+static int wait_usecs = 150; /* This magic number seems to give pretty optimal
+				throughput based on my limited experimentation.
+				If you want to play around with it and find a better
+				value, be my guest. Remember, the idea is to
+				get a number that causes us to just busy wait
+				for as many WaveFront commands as possible, without
+				coming up with a number so large that we hog the
+				whole CPU.
+
+				Specifically, with this number, out of about 134,000
+				status waits, only about 250 result in a sleep.
+			    */
+
+static int sleep_interval = 100;   /* HZ/sleep_interval seconds per sleep */
+static int sleep_tries = 50;       /* number of times we'll try to sleep */
+
+static int reset_time = 2;        /* hundreths of a second we wait after a HW
+				     reset for the expected interrupt.
+				  */
+
+static int ramcheck_time = 20;    /* time in seconds to wait while ROM code
+				     checks on-board RAM.
+				  */
+
+static int osrun_time = 10;       /* time in seconds we wait for the OS to
+				     start running.
+				  */
+module_param(wf_raw, int, 0444);
+MODULE_PARM_DESC(wf_raw, "if non-zero, assume that we need to boot the OS");
+module_param(fx_raw, int, 0444);
+MODULE_PARM_DESC(fx_raw, "if non-zero, assume that the FX process needs help");
+module_param(debug_default, int, 0444);
+MODULE_PARM_DESC(debug_default, "debug parameters for card initialization");
+module_param(wait_usecs, int, 0444);
+MODULE_PARM_DESC(wait_usecs, "how long to wait without sleeping, usecs");
+module_param(sleep_interval, int, 0444);
+MODULE_PARM_DESC(sleep_interval, "how long to sleep when waiting for reply");
+module_param(sleep_tries, int, 0444);
+MODULE_PARM_DESC(sleep_tries, "how many times to try sleeping during a wait");
+module_param(ospath, charp, 0444);
+MODULE_PARM_DESC(ospath, "pathname to processed ICS2115 OS firmware");
+module_param(reset_time, int, 0444);
+MODULE_PARM_DESC(reset_time, "how long to wait for a reset to take effect");
+module_param(ramcheck_time, int, 0444);
+MODULE_PARM_DESC(ramcheck_time, "how many seconds to wait for the RAM test");
+module_param(osrun_time, int, 0444);
+MODULE_PARM_DESC(osrun_time, "how many seconds to wait for the ICS2115 OS");
+
+/* if WF_DEBUG not defined, no run-time debugging messages will
+   be available via the debug flag setting. Given the current
+   beta state of the driver, this will remain set until a future 
+   version.
+*/
+
+#define WF_DEBUG 1
+
+#ifdef WF_DEBUG
+
+#define DPRINT(cond, ...) \
+       if ((dev->debug & (cond)) == (cond)) { \
+	     snd_printk (__VA_ARGS__); \
+       }
+#else
+#define DPRINT(cond, args...)
+#endif /* WF_DEBUG */
+
+#define LOGNAME "WaveFront: "
+
+/* bitmasks for WaveFront status port value */
+
+#define STAT_RINTR_ENABLED	0x01
+#define STAT_CAN_READ		0x02
+#define STAT_INTR_READ		0x04
+#define STAT_WINTR_ENABLED	0x10
+#define STAT_CAN_WRITE		0x20
+#define STAT_INTR_WRITE		0x40
+
+static int wavefront_delete_sample (snd_wavefront_t *, int sampnum);
+static int wavefront_find_free_sample (snd_wavefront_t *);
+
+struct wavefront_command {
+	int cmd;
+	char *action;
+	unsigned int read_cnt;
+	unsigned int write_cnt;
+	int need_ack;
+};
+
+static struct {
+	int errno;
+	const char *errstr;
+} wavefront_errors[] = {
+	{ 0x01, "Bad sample number" },
+	{ 0x02, "Out of sample memory" },
+	{ 0x03, "Bad patch number" },
+	{ 0x04, "Error in number of voices" },
+	{ 0x06, "Sample load already in progress" },
+	{ 0x0B, "No sample load request pending" },
+	{ 0x0E, "Bad MIDI channel number" },
+	{ 0x10, "Download Record Error" },
+	{ 0x80, "Success" },
+	{ 0x0 }
+};
+
+#define NEEDS_ACK 1
+
+static struct wavefront_command wavefront_commands[] = {
+	{ WFC_SET_SYNTHVOL, "set synthesizer volume", 0, 1, NEEDS_ACK },
+	{ WFC_GET_SYNTHVOL, "get synthesizer volume", 1, 0, 0},
+	{ WFC_SET_NVOICES, "set number of voices", 0, 1, NEEDS_ACK },
+	{ WFC_GET_NVOICES, "get number of voices", 1, 0, 0 },
+	{ WFC_SET_TUNING, "set synthesizer tuning", 0, 2, NEEDS_ACK },
+	{ WFC_GET_TUNING, "get synthesizer tuning", 2, 0, 0 },
+	{ WFC_DISABLE_CHANNEL, "disable synth channel", 0, 1, NEEDS_ACK },
+	{ WFC_ENABLE_CHANNEL, "enable synth channel", 0, 1, NEEDS_ACK },
+	{ WFC_GET_CHANNEL_STATUS, "get synth channel status", 3, 0, 0 },
+	{ WFC_MISYNTH_OFF, "disable midi-in to synth", 0, 0, NEEDS_ACK },
+	{ WFC_MISYNTH_ON, "enable midi-in to synth", 0, 0, NEEDS_ACK },
+	{ WFC_VMIDI_ON, "enable virtual midi mode", 0, 0, NEEDS_ACK },
+	{ WFC_VMIDI_OFF, "disable virtual midi mode", 0, 0, NEEDS_ACK },
+	{ WFC_MIDI_STATUS, "report midi status", 1, 0, 0 },
+	{ WFC_FIRMWARE_VERSION, "report firmware version", 2, 0, 0 },
+	{ WFC_HARDWARE_VERSION, "report hardware version", 2, 0, 0 },
+	{ WFC_GET_NSAMPLES, "report number of samples", 2, 0, 0 },
+	{ WFC_INSTOUT_LEVELS, "report instantaneous output levels", 7, 0, 0 },
+	{ WFC_PEAKOUT_LEVELS, "report peak output levels", 7, 0, 0 },
+	{ WFC_DOWNLOAD_SAMPLE, "download sample",
+	  0, WF_SAMPLE_BYTES, NEEDS_ACK },
+	{ WFC_DOWNLOAD_BLOCK, "download block", 0, 0, NEEDS_ACK},
+	{ WFC_DOWNLOAD_SAMPLE_HEADER, "download sample header",
+	  0, WF_SAMPLE_HDR_BYTES, NEEDS_ACK },
+	{ WFC_UPLOAD_SAMPLE_HEADER, "upload sample header", 13, 2, 0 },
+
+	/* This command requires a variable number of bytes to be written.
+	   There is a hack in snd_wavefront_cmd() to support this. The actual
+	   count is passed in as the read buffer ptr, cast appropriately.
+	   Ugh.
+	*/
+
+	{ WFC_DOWNLOAD_MULTISAMPLE, "download multisample", 0, 0, NEEDS_ACK },
+
+	/* This one is a hack as well. We just read the first byte of the
+	   response, don't fetch an ACK, and leave the rest to the 
+	   calling function. Ugly, ugly, ugly.
+	*/
+
+	{ WFC_UPLOAD_MULTISAMPLE, "upload multisample", 2, 1, 0 },
+	{ WFC_DOWNLOAD_SAMPLE_ALIAS, "download sample alias",
+	  0, WF_ALIAS_BYTES, NEEDS_ACK },
+	{ WFC_UPLOAD_SAMPLE_ALIAS, "upload sample alias", WF_ALIAS_BYTES, 2, 0},
+	{ WFC_DELETE_SAMPLE, "delete sample", 0, 2, NEEDS_ACK },
+	{ WFC_IDENTIFY_SAMPLE_TYPE, "identify sample type", 5, 2, 0 },
+	{ WFC_UPLOAD_SAMPLE_PARAMS, "upload sample parameters" },
+	{ WFC_REPORT_FREE_MEMORY, "report free memory", 4, 0, 0 },
+	{ WFC_DOWNLOAD_PATCH, "download patch", 0, 134, NEEDS_ACK },
+	{ WFC_UPLOAD_PATCH, "upload patch", 132, 2, 0 },
+	{ WFC_DOWNLOAD_PROGRAM, "download program", 0, 33, NEEDS_ACK },
+	{ WFC_UPLOAD_PROGRAM, "upload program", 32, 1, 0 },
+	{ WFC_DOWNLOAD_EDRUM_PROGRAM, "download enhanced drum program", 0, 9,
+	  NEEDS_ACK},
+	{ WFC_UPLOAD_EDRUM_PROGRAM, "upload enhanced drum program", 8, 1, 0},
+	{ WFC_SET_EDRUM_CHANNEL, "set enhanced drum program channel",
+	  0, 1, NEEDS_ACK },
+	{ WFC_DISABLE_DRUM_PROGRAM, "disable drum program", 0, 1, NEEDS_ACK },
+	{ WFC_REPORT_CHANNEL_PROGRAMS, "report channel program numbers",
+	  32, 0, 0 },
+	{ WFC_NOOP, "the no-op command", 0, 0, NEEDS_ACK },
+	{ 0x00 }
+};
+
+static const char *
+wavefront_errorstr (int errnum)
+
+{
+	int i;
+
+	for (i = 0; wavefront_errors[i].errstr; i++) {
+		if (wavefront_errors[i].errno == errnum) {
+			return wavefront_errors[i].errstr;
+		}
+	}
+
+	return "Unknown WaveFront error";
+}
+
+static struct wavefront_command *
+wavefront_get_command (int cmd) 
+
+{
+	int i;
+
+	for (i = 0; wavefront_commands[i].cmd != 0; i++) {
+		if (cmd == wavefront_commands[i].cmd) {
+			return &wavefront_commands[i];
+		}
+	}
+
+	return NULL;
+}
+
+static inline int
+wavefront_status (snd_wavefront_t *dev) 
+
+{
+	return inb (dev->status_port);
+}
+
+static int
+wavefront_sleep (int limit)
+
+{
+	schedule_timeout_interruptible(limit);
+
+	return signal_pending(current);
+}
+
+static int
+wavefront_wait (snd_wavefront_t *dev, int mask)
+
+{
+	int             i;
+
+	/* Spin for a short period of time, because >99% of all
+	   requests to the WaveFront can be serviced inline like this.
+	*/
+
+	for (i = 0; i < wait_usecs; i += 5) {
+		if (wavefront_status (dev) & mask) {
+			return 1;
+		}
+		udelay(5);
+	}
+
+	for (i = 0; i < sleep_tries; i++) {
+
+		if (wavefront_status (dev) & mask) {
+			return 1;
+		}
+
+		if (wavefront_sleep (HZ/sleep_interval)) {
+			return (0);
+		}
+	}
+
+	return (0);
+}
+
+static int
+wavefront_read (snd_wavefront_t *dev)
+
+{
+	if (wavefront_wait (dev, STAT_CAN_READ))
+		return inb (dev->data_port);
+
+	DPRINT (WF_DEBUG_DATA, "read timeout.\n");
+
+	return -1;
+}
+
+static int
+wavefront_write (snd_wavefront_t *dev, unsigned char data)
+
+{
+	if (wavefront_wait (dev, STAT_CAN_WRITE)) {
+		outb (data, dev->data_port);
+		return 0;
+	}
+
+	DPRINT (WF_DEBUG_DATA, "write timeout.\n");
+
+	return -1;
+}
+
+int
+snd_wavefront_cmd (snd_wavefront_t *dev, 
+		   int cmd, unsigned char *rbuf, unsigned char *wbuf)
+
+{
+	int ack;
+	unsigned int i;
+	int c;
+	struct wavefront_command *wfcmd;
+
+	if ((wfcmd = wavefront_get_command (cmd)) == NULL) {
+		snd_printk ("command 0x%x not supported.\n",
+			cmd);
+		return 1;
+	}
+
+	/* Hack to handle the one variable-size write command. See
+	   wavefront_send_multisample() for the other half of this
+	   gross and ugly strategy.
+	*/
+
+	if (cmd == WFC_DOWNLOAD_MULTISAMPLE) {
+		wfcmd->write_cnt = (unsigned long) rbuf;
+		rbuf = NULL;
+	}
+
+	DPRINT (WF_DEBUG_CMD, "0x%x [%s] (%d,%d,%d)\n",
+			       cmd, wfcmd->action, wfcmd->read_cnt,
+			       wfcmd->write_cnt, wfcmd->need_ack);
+    
+	if (wavefront_write (dev, cmd)) { 
+		DPRINT ((WF_DEBUG_IO|WF_DEBUG_CMD), "cannot request "
+						     "0x%x [%s].\n",
+						     cmd, wfcmd->action);
+		return 1;
+	} 
+
+	if (wfcmd->write_cnt > 0) {
+		DPRINT (WF_DEBUG_DATA, "writing %d bytes "
+					"for 0x%x\n",
+					wfcmd->write_cnt, cmd);
+
+		for (i = 0; i < wfcmd->write_cnt; i++) {
+			if (wavefront_write (dev, wbuf[i])) {
+				DPRINT (WF_DEBUG_IO, "bad write for byte "
+						      "%d of 0x%x [%s].\n",
+						      i, cmd, wfcmd->action);
+				return 1;
+			}
+
+			DPRINT (WF_DEBUG_DATA, "write[%d] = 0x%x\n",
+						i, wbuf[i]);
+		}
+	}
+
+	if (wfcmd->read_cnt > 0) {
+		DPRINT (WF_DEBUG_DATA, "reading %d ints "
+					"for 0x%x\n",
+					wfcmd->read_cnt, cmd);
+
+		for (i = 0; i < wfcmd->read_cnt; i++) {
+
+			if ((c = wavefront_read (dev)) == -1) {
+				DPRINT (WF_DEBUG_IO, "bad read for byte "
+						      "%d of 0x%x [%s].\n",
+						      i, cmd, wfcmd->action);
+				return 1;
+			}
+
+			/* Now handle errors. Lots of special cases here */
+	    
+			if (c == 0xff) { 
+				if ((c = wavefront_read (dev)) == -1) {
+					DPRINT (WF_DEBUG_IO, "bad read for "
+							      "error byte at "
+							      "read byte %d "
+							      "of 0x%x [%s].\n",
+							      i, cmd,
+							      wfcmd->action);
+					return 1;
+				}
+
+				/* Can you believe this madness ? */
+
+				if (c == 1 &&
+				    wfcmd->cmd == WFC_IDENTIFY_SAMPLE_TYPE) {
+					rbuf[0] = WF_ST_EMPTY;
+					return (0);
+
+				} else if (c == 3 &&
+					   wfcmd->cmd == WFC_UPLOAD_PATCH) {
+
+					return 3;
+
+				} else if (c == 1 &&
+					   wfcmd->cmd == WFC_UPLOAD_PROGRAM) {
+
+					return 1;
+
+				} else {
+
+					DPRINT (WF_DEBUG_IO, "error %d (%s) "
+							      "during "
+							      "read for byte "
+							      "%d of 0x%x "
+							      "[%s].\n",
+							      c,
+							      wavefront_errorstr (c),
+							      i, cmd,
+							      wfcmd->action);
+					return 1;
+
+				}
+		
+		} else {
+				rbuf[i] = c;
+			}
+			
+			DPRINT (WF_DEBUG_DATA, "read[%d] = 0x%x\n",i, rbuf[i]);
+		}
+	}
+	
+	if ((wfcmd->read_cnt == 0 && wfcmd->write_cnt == 0) || wfcmd->need_ack) {
+
+		DPRINT (WF_DEBUG_CMD, "reading ACK for 0x%x\n", cmd);
+
+		/* Some commands need an ACK, but return zero instead
+		   of the standard value.
+		*/
+	    
+		if ((ack = wavefront_read (dev)) == 0) {
+			ack = WF_ACK;
+		}
+	
+		if (ack != WF_ACK) {
+			if (ack == -1) {
+				DPRINT (WF_DEBUG_IO, "cannot read ack for "
+						      "0x%x [%s].\n",
+						      cmd, wfcmd->action);
+				return 1;
+		
+			} else {
+				int err = -1; /* something unknown */
+
+				if (ack == 0xff) { /* explicit error */
+		    
+					if ((err = wavefront_read (dev)) == -1) {
+						DPRINT (WF_DEBUG_DATA,
+							"cannot read err "
+							"for 0x%x [%s].\n",
+							cmd, wfcmd->action);
+					}
+				}
+				
+				DPRINT (WF_DEBUG_IO, "0x%x [%s] "
+					"failed (0x%x, 0x%x, %s)\n",
+					cmd, wfcmd->action, ack, err,
+					wavefront_errorstr (err));
+				
+				return -err;
+			}
+		}
+		
+		DPRINT (WF_DEBUG_DATA, "ack received "
+					"for 0x%x [%s]\n",
+					cmd, wfcmd->action);
+	} else {
+
+		DPRINT (WF_DEBUG_CMD, "0x%x [%s] does not need "
+				       "ACK (%d,%d,%d)\n",
+				       cmd, wfcmd->action, wfcmd->read_cnt,
+				       wfcmd->write_cnt, wfcmd->need_ack);
+	}
+
+	return 0;
+	
+}
+
+/***********************************************************************
+WaveFront data munging   
+
+Things here are weird. All data written to the board cannot 
+have its most significant bit set. Any data item with values 
+potentially > 0x7F (127) must be split across multiple bytes.
+
+Sometimes, we need to munge numeric values that are represented on
+the x86 side as 8-32 bit values. Sometimes, we need to munge data
+that is represented on the x86 side as an array of bytes. The most
+efficient approach to handling both cases seems to be to use 2
+different functions for munging and 2 for de-munging. This avoids
+weird casting and worrying about bit-level offsets.
+
+**********************************************************************/
+
+static unsigned char *
+munge_int32 (unsigned int src,
+	     unsigned char *dst,
+	     unsigned int dst_size)
+{
+	unsigned int i;
+
+	for (i = 0; i < dst_size; i++) {
+		*dst = src & 0x7F;  /* Mask high bit of LSB */
+		src = src >> 7;     /* Rotate Right 7 bits  */
+	                            /* Note: we leave the upper bits in place */ 
+
+		dst++;
+ 	};
+	return dst;
+};
+
+static int 
+demunge_int32 (unsigned char* src, int src_size)
+
+{
+	int i;
+ 	int outval = 0;
+	
+ 	for (i = src_size - 1; i >= 0; i--) {
+		outval=(outval<<7)+src[i];
+	}
+
+	return outval;
+};
+
+static 
+unsigned char *
+munge_buf (unsigned char *src, unsigned char *dst, unsigned int dst_size)
+
+{
+	unsigned int i;
+	unsigned int last = dst_size / 2;
+
+	for (i = 0; i < last; i++) {
+		*dst++ = src[i] & 0x7f;
+		*dst++ = src[i] >> 7;
+	}
+	return dst;
+}
+
+static 
+unsigned char *
+demunge_buf (unsigned char *src, unsigned char *dst, unsigned int src_bytes)
+
+{
+	int i;
+	unsigned char *end = src + src_bytes;
+    
+	end = src + src_bytes;
+
+	/* NOTE: src and dst *CAN* point to the same address */
+
+	for (i = 0; src != end; i++) {
+		dst[i] = *src++;
+		dst[i] |= (*src++)<<7;
+	}
+
+	return dst;
+}
+
+/***********************************************************************
+WaveFront: sample, patch and program management.
+***********************************************************************/
+
+static int
+wavefront_delete_sample (snd_wavefront_t *dev, int sample_num)
+
+{
+	unsigned char wbuf[2];
+	int x;
+
+	wbuf[0] = sample_num & 0x7f;
+	wbuf[1] = sample_num >> 7;
+
+	if ((x = snd_wavefront_cmd (dev, WFC_DELETE_SAMPLE, NULL, wbuf)) == 0) {
+		dev->sample_status[sample_num] = WF_ST_EMPTY;
+	}
+
+	return x;
+}
+
+static int
+wavefront_get_sample_status (snd_wavefront_t *dev, int assume_rom)
+
+{
+	int i;
+	unsigned char rbuf[32], wbuf[32];
+	unsigned int    sc_real, sc_alias, sc_multi;
+
+	/* check sample status */
+    
+	if (snd_wavefront_cmd (dev, WFC_GET_NSAMPLES, rbuf, wbuf)) {
+		snd_printk ("cannot request sample count.\n");
+		return -1;
+	} 
+    
+	sc_real = sc_alias = sc_multi = dev->samples_used = 0;
+    
+	for (i = 0; i < WF_MAX_SAMPLE; i++) {
+	
+		wbuf[0] = i & 0x7f;
+		wbuf[1] = i >> 7;
+
+		if (snd_wavefront_cmd (dev, WFC_IDENTIFY_SAMPLE_TYPE, rbuf, wbuf)) {
+			snd_printk(KERN_WARNING "cannot identify sample "
+				   "type of slot %d\n", i);
+			dev->sample_status[i] = WF_ST_EMPTY;
+			continue;
+		}
+
+		dev->sample_status[i] = (WF_SLOT_FILLED|rbuf[0]);
+
+		if (assume_rom) {
+			dev->sample_status[i] |= WF_SLOT_ROM;
+		}
+
+		switch (rbuf[0] & WF_ST_MASK) {
+		case WF_ST_SAMPLE:
+			sc_real++;
+			break;
+		case WF_ST_MULTISAMPLE:
+			sc_multi++;
+			break;
+		case WF_ST_ALIAS:
+			sc_alias++;
+			break;
+		case WF_ST_EMPTY:
+			break;
+
+		default:
+			snd_printk ("unknown sample type for "
+				    "slot %d (0x%x)\n", 
+				    i, rbuf[0]);
+		}
+
+		if (rbuf[0] != WF_ST_EMPTY) {
+			dev->samples_used++;
+		} 
+	}
+
+	snd_printk ("%d samples used (%d real, %d aliases, %d multi), "
+		    "%d empty\n", dev->samples_used, sc_real, sc_alias, sc_multi,
+		    WF_MAX_SAMPLE - dev->samples_used);
+
+
+	return (0);
+
+}
+
+static int
+wavefront_get_patch_status (snd_wavefront_t *dev)
+
+{
+	unsigned char patchbuf[WF_PATCH_BYTES];
+	unsigned char patchnum[2];
+	wavefront_patch *p;
+	int i, x, cnt, cnt2;
+
+	for (i = 0; i < WF_MAX_PATCH; i++) {
+		patchnum[0] = i & 0x7f;
+		patchnum[1] = i >> 7;
+
+		if ((x = snd_wavefront_cmd (dev, WFC_UPLOAD_PATCH, patchbuf,
+					patchnum)) == 0) {
+
+			dev->patch_status[i] |= WF_SLOT_FILLED;
+			p = (wavefront_patch *) patchbuf;
+			dev->sample_status
+				[p->sample_number|(p->sample_msb<<7)] |=
+				WF_SLOT_USED;
+	    
+		} else if (x == 3) { /* Bad patch number */
+			dev->patch_status[i] = 0;
+		} else {
+			snd_printk ("upload patch "
+				    "error 0x%x\n", x);
+			dev->patch_status[i] = 0;
+			return 1;
+		}
+	}
+
+	/* program status has already filled in slot_used bits */
+
+	for (i = 0, cnt = 0, cnt2 = 0; i < WF_MAX_PATCH; i++) {
+		if (dev->patch_status[i] & WF_SLOT_FILLED) {
+			cnt++;
+		}
+		if (dev->patch_status[i] & WF_SLOT_USED) {
+			cnt2++;
+		}
+	
+	}
+	snd_printk ("%d patch slots filled, %d in use\n", cnt, cnt2);
+
+	return (0);
+}
+
+static int
+wavefront_get_program_status (snd_wavefront_t *dev)
+
+{
+	unsigned char progbuf[WF_PROGRAM_BYTES];
+	wavefront_program prog;
+	unsigned char prognum;
+	int i, x, l, cnt;
+
+	for (i = 0; i < WF_MAX_PROGRAM; i++) {
+		prognum = i;
+
+		if ((x = snd_wavefront_cmd (dev, WFC_UPLOAD_PROGRAM, progbuf,
+					&prognum)) == 0) {
+
+			dev->prog_status[i] |= WF_SLOT_USED;
+
+			demunge_buf (progbuf, (unsigned char *) &prog,
+				     WF_PROGRAM_BYTES);
+
+			for (l = 0; l < WF_NUM_LAYERS; l++) {
+				if (prog.layer[l].mute) {
+					dev->patch_status
+						[prog.layer[l].patch_number] |=
+						WF_SLOT_USED;
+				}
+			}
+		} else if (x == 1) { /* Bad program number */
+			dev->prog_status[i] = 0;
+		} else {
+			snd_printk ("upload program "
+				    "error 0x%x\n", x);
+			dev->prog_status[i] = 0;
+		}
+	}
+
+	for (i = 0, cnt = 0; i < WF_MAX_PROGRAM; i++) {
+		if (dev->prog_status[i]) {
+			cnt++;
+		}
+	}
+
+	snd_printk ("%d programs slots in use\n", cnt);
+
+	return (0);
+}
+
+static int
+wavefront_send_patch (snd_wavefront_t *dev, wavefront_patch_info *header)
+
+{
+	unsigned char buf[WF_PATCH_BYTES+2];
+	unsigned char *bptr;
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "downloading patch %d\n",
+				      header->number);
+
+	dev->patch_status[header->number] |= WF_SLOT_FILLED;
+
+	bptr = buf;
+	bptr = munge_int32 (header->number, buf, 2);
+	munge_buf ((unsigned char *)&header->hdr.p, bptr, WF_PATCH_BYTES);
+    
+	if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PATCH, NULL, buf)) {
+		snd_printk ("download patch failed\n");
+		return -(EIO);
+	}
+
+	return (0);
+}
+
+static int
+wavefront_send_program (snd_wavefront_t *dev, wavefront_patch_info *header)
+
+{
+	unsigned char buf[WF_PROGRAM_BYTES+1];
+	int i;
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "downloading program %d\n",
+		header->number);
+
+	dev->prog_status[header->number] = WF_SLOT_USED;
+
+	/* XXX need to zero existing SLOT_USED bit for program_status[i]
+	   where `i' is the program that's being (potentially) overwritten.
+	*/
+    
+	for (i = 0; i < WF_NUM_LAYERS; i++) {
+		if (header->hdr.pr.layer[i].mute) {
+			dev->patch_status[header->hdr.pr.layer[i].patch_number] |=
+				WF_SLOT_USED;
+
+			/* XXX need to mark SLOT_USED for sample used by
+			   patch_number, but this means we have to load it. Ick.
+			*/
+		}
+	}
+
+	buf[0] = header->number;
+	munge_buf ((unsigned char *)&header->hdr.pr, &buf[1], WF_PROGRAM_BYTES);
+    
+	if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PROGRAM, NULL, buf)) {
+		snd_printk ("download patch failed\n");	
+		return -(EIO);
+	}
+
+	return (0);
+}
+
+static int
+wavefront_freemem (snd_wavefront_t *dev)
+
+{
+	char rbuf[8];
+
+	if (snd_wavefront_cmd (dev, WFC_REPORT_FREE_MEMORY, rbuf, NULL)) {
+		snd_printk ("can't get memory stats.\n");
+		return -1;
+	} else {
+		return demunge_int32 (rbuf, 4);
+	}
+}
+
+static int
+wavefront_send_sample (snd_wavefront_t *dev, 
+		       wavefront_patch_info *header,
+		       u16 __user *dataptr,
+		       int data_is_unsigned)
+
+{
+	/* samples are downloaded via a 16-bit wide i/o port
+	   (you could think of it as 2 adjacent 8-bit wide ports
+	   but its less efficient that way). therefore, all
+	   the blocksizes and so forth listed in the documentation,
+	   and used conventionally to refer to sample sizes,
+	   which are given in 8-bit units (bytes), need to be
+	   divided by 2.
+        */
+
+	u16 sample_short = 0;
+	u32 length;
+	u16 __user *data_end = NULL;
+	unsigned int i;
+	const unsigned int max_blksize = 4096/2;
+	unsigned int written;
+	unsigned int blocksize;
+	int dma_ack;
+	int blocknum;
+	unsigned char sample_hdr[WF_SAMPLE_HDR_BYTES];
+	unsigned char *shptr;
+	int skip = 0;
+	int initial_skip = 0;
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "sample %sdownload for slot %d, "
+				      "type %d, %d bytes from 0x%lx\n",
+				      header->size ? "" : "header ", 
+				      header->number, header->subkey,
+				      header->size,
+				      (unsigned long) header->dataptr);
+
+	if (header->number == WAVEFRONT_FIND_FREE_SAMPLE_SLOT) {
+		int x;
+
+		if ((x = wavefront_find_free_sample (dev)) < 0) {
+			return -ENOMEM;
+		}
+		snd_printk ("unspecified sample => %d\n", x);
+		header->number = x;
+	}
+
+	if (header->size) {
+
+		/* XXX it's a debatable point whether or not RDONLY semantics
+		   on the ROM samples should cover just the sample data or
+		   the sample header. For now, it only covers the sample data,
+		   so anyone is free at all times to rewrite sample headers.
+
+		   My reason for this is that we have the sample headers
+		   available in the WFB file for General MIDI, and so these
+		   can always be reset if needed. The sample data, however,
+		   cannot be recovered without a complete reset and firmware
+		   reload of the ICS2115, which is a very expensive operation.
+
+		   So, doing things this way allows us to honor the notion of
+		   "RESETSAMPLES" reasonably cheaply. Note however, that this
+		   is done purely at user level: there is no WFB parser in
+		   this driver, and so a complete reset (back to General MIDI,
+		   or theoretically some other configuration) is the
+		   responsibility of the user level library. 
+
+		   To try to do this in the kernel would be a little
+		   crazy: we'd need 158K of kernel space just to hold
+		   a copy of the patch/program/sample header data.
+		*/
+
+		if (dev->rom_samples_rdonly) {
+			if (dev->sample_status[header->number] & WF_SLOT_ROM) {
+				snd_printk ("sample slot %d "
+					    "write protected\n",
+					    header->number);
+				return -EACCES;
+			}
+		}
+
+		wavefront_delete_sample (dev, header->number);
+	}
+
+	if (header->size) {
+		dev->freemem = wavefront_freemem (dev);
+
+		if (dev->freemem < (int)header->size) {
+			snd_printk ("insufficient memory to "
+				    "load %d byte sample.\n",
+				    header->size);
+			return -ENOMEM;
+		}
+	
+	}
+
+	skip = WF_GET_CHANNEL(&header->hdr.s);
+
+	if (skip > 0 && header->hdr.s.SampleResolution != LINEAR_16BIT) {
+		snd_printk ("channel selection only "
+			    "possible on 16-bit samples");
+		return -(EINVAL);
+	}
+
+	switch (skip) {
+	case 0:
+		initial_skip = 0;
+		skip = 1;
+		break;
+	case 1:
+		initial_skip = 0;
+		skip = 2;
+		break;
+	case 2:
+		initial_skip = 1;
+		skip = 2;
+		break;
+	case 3:
+		initial_skip = 2;
+		skip = 3;
+		break;
+	case 4:
+		initial_skip = 3;
+		skip = 4;
+		break;
+	case 5:
+		initial_skip = 4;
+		skip = 5;
+		break;
+	case 6:
+		initial_skip = 5;
+		skip = 6;
+		break;
+	}
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "channel selection: %d => "
+				      "initial skip = %d, skip = %d\n",
+				      WF_GET_CHANNEL (&header->hdr.s),
+				      initial_skip, skip);
+    
+	/* Be safe, and zero the "Unused" bits ... */
+
+	WF_SET_CHANNEL(&header->hdr.s, 0);
+
+	/* adjust size for 16 bit samples by dividing by two.  We always
+	   send 16 bits per write, even for 8 bit samples, so the length
+	   is always half the size of the sample data in bytes.
+	*/
+
+	length = header->size / 2;
+
+	/* the data we're sent has not been munged, and in fact, the
+	   header we have to send isn't just a munged copy either.
+	   so, build the sample header right here.
+	*/
+
+	shptr = &sample_hdr[0];
+
+	shptr = munge_int32 (header->number, shptr, 2);
+
+	if (header->size) {
+		shptr = munge_int32 (length, shptr, 4);
+	}
+
+	/* Yes, a 4 byte result doesn't contain all of the offset bits,
+	   but the offset only uses 24 bits.
+	*/
+
+	shptr = munge_int32 (*((u32 *) &header->hdr.s.sampleStartOffset),
+			     shptr, 4);
+	shptr = munge_int32 (*((u32 *) &header->hdr.s.loopStartOffset),
+			     shptr, 4);
+	shptr = munge_int32 (*((u32 *) &header->hdr.s.loopEndOffset),
+			     shptr, 4);
+	shptr = munge_int32 (*((u32 *) &header->hdr.s.sampleEndOffset),
+			     shptr, 4);
+	
+	/* This one is truly weird. What kind of weirdo decided that in
+	   a system dominated by 16 and 32 bit integers, they would use
+	   a just 12 bits ?
+	*/
+	
+	shptr = munge_int32 (header->hdr.s.FrequencyBias, shptr, 3);
+	
+	/* Why is this nybblified, when the MSB is *always* zero ? 
+	   Anyway, we can't take address of bitfield, so make a
+	   good-faith guess at where it starts.
+	*/
+	
+	shptr = munge_int32 (*(&header->hdr.s.FrequencyBias+1),
+			     shptr, 2);
+
+	if (snd_wavefront_cmd (dev, 
+			   header->size ?
+			   WFC_DOWNLOAD_SAMPLE : WFC_DOWNLOAD_SAMPLE_HEADER,
+			   NULL, sample_hdr)) {
+		snd_printk ("sample %sdownload refused.\n",
+			    header->size ? "" : "header ");
+		return -(EIO);
+	}
+
+	if (header->size == 0) {
+		goto sent; /* Sorry. Just had to have one somewhere */
+	}
+    
+	data_end = dataptr + length;
+
+	/* Do any initial skip over an unused channel's data */
+
+	dataptr += initial_skip;
+    
+	for (written = 0, blocknum = 0;
+	     written < length; written += max_blksize, blocknum++) {
+	
+		if ((length - written) > max_blksize) {
+			blocksize = max_blksize;
+		} else {
+			/* round to nearest 16-byte value */
+			blocksize = ALIGN(length - written, 8);
+		}
+
+		if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_BLOCK, NULL, NULL)) {
+			snd_printk ("download block "
+				    "request refused.\n");
+			return -(EIO);
+		}
+
+		for (i = 0; i < blocksize; i++) {
+
+			if (dataptr < data_end) {
+		
+				__get_user (sample_short, dataptr);
+				dataptr += skip;
+		
+				if (data_is_unsigned) { /* GUS ? */
+
+					if (WF_SAMPLE_IS_8BIT(&header->hdr.s)) {
+			
+						/* 8 bit sample
+						 resolution, sign
+						 extend both bytes.
+						*/
+			
+						((unsigned char*)
+						 &sample_short)[0] += 0x7f;
+						((unsigned char*)
+						 &sample_short)[1] += 0x7f;
+			
+					} else {
+			
+						/* 16 bit sample
+						 resolution, sign
+						 extend the MSB.
+						*/
+			
+						sample_short += 0x7fff;
+					}
+				}
+
+			} else {
+
+				/* In padding section of final block:
+
+				   Don't fetch unsupplied data from
+				   user space, just continue with
+				   whatever the final value was.
+				*/
+			}
+	    
+			if (i < blocksize - 1) {
+				outw (sample_short, dev->block_port);
+			} else {
+				outw (sample_short, dev->last_block_port);
+			}
+		}
+
+		/* Get "DMA page acknowledge", even though its really
+		   nothing to do with DMA at all.
+		*/
+	
+		if ((dma_ack = wavefront_read (dev)) != WF_DMA_ACK) {
+			if (dma_ack == -1) {
+				snd_printk ("upload sample "
+					    "DMA ack timeout\n");
+				return -(EIO);
+			} else {
+				snd_printk ("upload sample "
+					    "DMA ack error 0x%x\n",
+					    dma_ack);
+				return -(EIO);
+			}
+		}
+	}
+
+	dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_SAMPLE);
+
+	/* Note, label is here because sending the sample header shouldn't
+	   alter the sample_status info at all.
+	*/
+
+ sent:
+	return (0);
+}
+
+static int
+wavefront_send_alias (snd_wavefront_t *dev, wavefront_patch_info *header)
+
+{
+	unsigned char alias_hdr[WF_ALIAS_BYTES];
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "download alias, %d is "
+				      "alias for %d\n",
+				      header->number,
+				      header->hdr.a.OriginalSample);
+    
+	munge_int32 (header->number, &alias_hdr[0], 2);
+	munge_int32 (header->hdr.a.OriginalSample, &alias_hdr[2], 2);
+	munge_int32 (*((unsigned int *)&header->hdr.a.sampleStartOffset),
+		     &alias_hdr[4], 4);
+	munge_int32 (*((unsigned int *)&header->hdr.a.loopStartOffset),
+		     &alias_hdr[8], 4);
+	munge_int32 (*((unsigned int *)&header->hdr.a.loopEndOffset),
+		     &alias_hdr[12], 4);
+	munge_int32 (*((unsigned int *)&header->hdr.a.sampleEndOffset),
+		     &alias_hdr[16], 4);
+	munge_int32 (header->hdr.a.FrequencyBias, &alias_hdr[20], 3);
+	munge_int32 (*(&header->hdr.a.FrequencyBias+1), &alias_hdr[23], 2);
+
+	if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_SAMPLE_ALIAS, NULL, alias_hdr)) {
+		snd_printk ("download alias failed.\n");
+		return -(EIO);
+	}
+
+	dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_ALIAS);
+
+	return (0);
+}
+
+static int
+wavefront_send_multisample (snd_wavefront_t *dev, wavefront_patch_info *header)
+{
+	int i;
+	int num_samples;
+	unsigned char *msample_hdr;
+
+	msample_hdr = kmalloc(sizeof(WF_MSAMPLE_BYTES), GFP_KERNEL);
+	if (! msample_hdr)
+		return -ENOMEM;
+
+	munge_int32 (header->number, &msample_hdr[0], 2);
+
+	/* You'll recall at this point that the "number of samples" value
+	   in a wavefront_multisample struct is actually the log2 of the
+	   real number of samples.
+	*/
+
+	num_samples = (1<<(header->hdr.ms.NumberOfSamples&7));
+	msample_hdr[2] = (unsigned char) header->hdr.ms.NumberOfSamples;
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "multi %d with %d=%d samples\n",
+				      header->number,
+				      header->hdr.ms.NumberOfSamples,
+				      num_samples);
+
+	for (i = 0; i < num_samples; i++) {
+		DPRINT(WF_DEBUG_LOAD_PATCH|WF_DEBUG_DATA, "sample[%d] = %d\n",
+		       i, header->hdr.ms.SampleNumber[i]);
+		munge_int32 (header->hdr.ms.SampleNumber[i],
+		     &msample_hdr[3+(i*2)], 2);
+	}
+    
+	/* Need a hack here to pass in the number of bytes
+	   to be written to the synth. This is ugly, and perhaps
+	   one day, I'll fix it.
+	*/
+
+	if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_MULTISAMPLE, 
+			   (unsigned char *) (long) ((num_samples*2)+3),
+			   msample_hdr)) {
+		snd_printk ("download of multisample failed.\n");
+		kfree(msample_hdr);
+		return -(EIO);
+	}
+
+	dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_MULTISAMPLE);
+
+	kfree(msample_hdr);
+	return (0);
+}
+
+static int
+wavefront_fetch_multisample (snd_wavefront_t *dev, 
+			     wavefront_patch_info *header)
+{
+	int i;
+	unsigned char log_ns[1];
+	unsigned char number[2];
+	int num_samples;
+
+	munge_int32 (header->number, number, 2);
+    
+	if (snd_wavefront_cmd (dev, WFC_UPLOAD_MULTISAMPLE, log_ns, number)) {
+		snd_printk ("upload multisample failed.\n");
+		return -(EIO);
+	}
+    
+	DPRINT (WF_DEBUG_DATA, "msample %d has %d samples\n",
+				header->number, log_ns[0]);
+
+	header->hdr.ms.NumberOfSamples = log_ns[0];
+
+	/* get the number of samples ... */
+
+	num_samples = (1 << log_ns[0]);
+    
+	for (i = 0; i < num_samples; i++) {
+		char d[2];
+		int val;
+	
+		if ((val = wavefront_read (dev)) == -1) {
+			snd_printk ("upload multisample failed "
+				    "during sample loop.\n");
+			return -(EIO);
+		}
+		d[0] = val;
+
+		if ((val = wavefront_read (dev)) == -1) {
+			snd_printk ("upload multisample failed "
+				    "during sample loop.\n");
+			return -(EIO);
+		}
+		d[1] = val;
+	
+		header->hdr.ms.SampleNumber[i] =
+			demunge_int32 ((unsigned char *) d, 2);
+	
+		DPRINT (WF_DEBUG_DATA, "msample sample[%d] = %d\n",
+					i, header->hdr.ms.SampleNumber[i]);
+	}
+
+	return (0);
+}
+
+
+static int
+wavefront_send_drum (snd_wavefront_t *dev, wavefront_patch_info *header)
+
+{
+	unsigned char drumbuf[WF_DRUM_BYTES];
+	wavefront_drum *drum = &header->hdr.d;
+	int i;
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "downloading edrum for MIDI "
+		"note %d, patch = %d\n", 
+		header->number, drum->PatchNumber);
+
+	drumbuf[0] = header->number & 0x7f;
+
+	for (i = 0; i < 4; i++) {
+		munge_int32 (((unsigned char *)drum)[i], &drumbuf[1+(i*2)], 2);
+	}
+
+	if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_EDRUM_PROGRAM, NULL, drumbuf)) {
+		snd_printk ("download drum failed.\n");
+		return -(EIO);
+	}
+
+	return (0);
+}
+
+static int 
+wavefront_find_free_sample (snd_wavefront_t *dev)
+
+{
+	int i;
+
+	for (i = 0; i < WF_MAX_SAMPLE; i++) {
+		if (!(dev->sample_status[i] & WF_SLOT_FILLED)) {
+			return i;
+		}
+	}
+	snd_printk ("no free sample slots!\n");
+	return -1;
+}
+
+#if 0
+static int 
+wavefront_find_free_patch (snd_wavefront_t *dev)
+
+{
+	int i;
+
+	for (i = 0; i < WF_MAX_PATCH; i++) {
+		if (!(dev->patch_status[i] & WF_SLOT_FILLED)) {
+			return i;
+		}
+	}
+	snd_printk ("no free patch slots!\n");
+	return -1;
+}
+#endif
+
+static int
+wavefront_load_patch (snd_wavefront_t *dev, const char __user *addr)
+{
+	wavefront_patch_info *header;
+	int err;
+	
+	header = kmalloc(sizeof(*header), GFP_KERNEL);
+	if (! header)
+		return -ENOMEM;
+
+	if (copy_from_user (header, addr, sizeof(wavefront_patch_info) -
+			    sizeof(wavefront_any))) {
+		snd_printk ("bad address for load patch.\n");
+		err = -EFAULT;
+		goto __error;
+	}
+
+	DPRINT (WF_DEBUG_LOAD_PATCH, "download "
+				      "Sample type: %d "
+				      "Sample number: %d "
+				      "Sample size: %d\n",
+				      header->subkey,
+				      header->number,
+				      header->size);
+
+	switch (header->subkey) {
+	case WF_ST_SAMPLE:  /* sample or sample_header, based on patch->size */
+
+		if (copy_from_user (&header->hdr.s, header->hdrptr,
+				    sizeof (wavefront_sample))) {
+			err = -EFAULT;
+			break;
+		}
+
+		err = wavefront_send_sample (dev, header, header->dataptr, 0);
+		break;
+
+	case WF_ST_MULTISAMPLE:
+
+		if (copy_from_user (&header->hdr.s, header->hdrptr,
+				    sizeof (wavefront_multisample))) {
+			err = -EFAULT;
+			break;
+		}
+
+		err = wavefront_send_multisample (dev, header);
+		break;
+
+	case WF_ST_ALIAS:
+
+		if (copy_from_user (&header->hdr.a, header->hdrptr,
+				    sizeof (wavefront_alias))) {
+			err = -EFAULT;
+			break;
+		}
+
+		err = wavefront_send_alias (dev, header);
+		break;
+
+	case WF_ST_DRUM:
+		if (copy_from_user (&header->hdr.d, header->hdrptr,
+				    sizeof (wavefront_drum))) {
+			err = -EFAULT;
+			break;
+		}
+
+		err = wavefront_send_drum (dev, header);
+		break;
+
+	case WF_ST_PATCH:
+		if (copy_from_user (&header->hdr.p, header->hdrptr,
+				    sizeof (wavefront_patch))) {
+			err = -EFAULT;
+			break;
+		}
+		
+		err = wavefront_send_patch (dev, header);
+		break;
+
+	case WF_ST_PROGRAM:
+		if (copy_from_user (&header->hdr.pr, header->hdrptr,
+				    sizeof (wavefront_program))) {
+			err = -EFAULT;
+			break;
+		}
+
+		err = wavefront_send_program (dev, header);
+		break;
+
+	default:
+		snd_printk ("unknown patch type %d.\n",
+			    header->subkey);
+		err = -EINVAL;
+		break;
+	}
+
+ __error:
+	kfree(header);
+	return err;
+}
+
+/***********************************************************************
+WaveFront: hardware-dependent interface
+***********************************************************************/
+
+static void
+process_sample_hdr (u8 *buf)
+
+{
+	wavefront_sample s;
+	u8 *ptr;
+
+	ptr = buf;
+
+	/* The board doesn't send us an exact copy of a "wavefront_sample"
+	   in response to an Upload Sample Header command. Instead, we 
+	   have to convert the data format back into our data structure,
+	   just as in the Download Sample command, where we have to do
+	   something very similar in the reverse direction.
+	*/
+
+	*((u32 *) &s.sampleStartOffset) = demunge_int32 (ptr, 4); ptr += 4;
+	*((u32 *) &s.loopStartOffset) = demunge_int32 (ptr, 4); ptr += 4;
+	*((u32 *) &s.loopEndOffset) = demunge_int32 (ptr, 4); ptr += 4;
+	*((u32 *) &s.sampleEndOffset) = demunge_int32 (ptr, 4); ptr += 4;
+	*((u32 *) &s.FrequencyBias) = demunge_int32 (ptr, 3); ptr += 3;
+
+	s.SampleResolution = *ptr & 0x3;
+	s.Loop = *ptr & 0x8;
+	s.Bidirectional = *ptr & 0x10;
+	s.Reverse = *ptr & 0x40;
+
+	/* Now copy it back to where it came from */
+
+	memcpy (buf, (unsigned char *) &s, sizeof (wavefront_sample));
+}
+
+static int
+wavefront_synth_control (snd_wavefront_card_t *acard, 
+			 wavefront_control *wc)
+
+{
+	snd_wavefront_t *dev = &acard->wavefront;
+	unsigned char patchnumbuf[2];
+	int i;
+
+	DPRINT (WF_DEBUG_CMD, "synth control with "
+		"cmd 0x%x\n", wc->cmd);
+
+	/* Pre-handling of or for various commands */
+
+	switch (wc->cmd) {
+		
+	case WFC_DISABLE_INTERRUPTS:
+		snd_printk ("interrupts disabled.\n");
+		outb (0x80|0x20, dev->control_port);
+		dev->interrupts_are_midi = 1;
+		return 0;
+
+	case WFC_ENABLE_INTERRUPTS:
+		snd_printk ("interrupts enabled.\n");
+		outb (0x80|0x40|0x20, dev->control_port);
+		dev->interrupts_are_midi = 1;
+		return 0;
+
+	case WFC_INTERRUPT_STATUS:
+		wc->rbuf[0] = dev->interrupts_are_midi;
+		return 0;
+
+	case WFC_ROMSAMPLES_RDONLY:
+		dev->rom_samples_rdonly = wc->wbuf[0];
+		wc->status = 0;
+		return 0;
+
+	case WFC_IDENTIFY_SLOT_TYPE:
+		i = wc->wbuf[0] | (wc->wbuf[1] << 7);
+		if (i <0 || i >= WF_MAX_SAMPLE) {
+			snd_printk ("invalid slot ID %d\n",
+				i);
+			wc->status = EINVAL;
+			return -EINVAL;
+		}
+		wc->rbuf[0] = dev->sample_status[i];
+		wc->status = 0;
+		return 0;
+
+	case WFC_DEBUG_DRIVER:
+		dev->debug = wc->wbuf[0];
+		snd_printk ("debug = 0x%x\n", dev->debug);
+		return 0;
+
+	case WFC_UPLOAD_PATCH:
+		munge_int32 (*((u32 *) wc->wbuf), patchnumbuf, 2);
+		memcpy (wc->wbuf, patchnumbuf, 2);
+		break;
+
+	case WFC_UPLOAD_MULTISAMPLE:
+		/* multisamples have to be handled differently, and
+		   cannot be dealt with properly by snd_wavefront_cmd() alone.
+		*/
+		wc->status = wavefront_fetch_multisample
+			(dev, (wavefront_patch_info *) wc->rbuf);
+		return 0;
+
+	case WFC_UPLOAD_SAMPLE_ALIAS:
+		snd_printk ("support for sample alias upload "
+			"being considered.\n");
+		wc->status = EINVAL;
+		return -EINVAL;
+	}
+
+	wc->status = snd_wavefront_cmd (dev, wc->cmd, wc->rbuf, wc->wbuf);
+
+	/* Post-handling of certain commands.
+
+	   In particular, if the command was an upload, demunge the data
+	   so that the user-level doesn't have to think about it.
+	*/
+
+	if (wc->status == 0) {
+		switch (wc->cmd) {
+			/* intercept any freemem requests so that we know
+			   we are always current with the user-level view
+			   of things.
+			*/
+
+		case WFC_REPORT_FREE_MEMORY:
+			dev->freemem = demunge_int32 (wc->rbuf, 4);
+			break;
+
+		case WFC_UPLOAD_PATCH:
+			demunge_buf (wc->rbuf, wc->rbuf, WF_PATCH_BYTES);
+			break;
+
+		case WFC_UPLOAD_PROGRAM:
+			demunge_buf (wc->rbuf, wc->rbuf, WF_PROGRAM_BYTES);
+			break;
+
+		case WFC_UPLOAD_EDRUM_PROGRAM:
+			demunge_buf (wc->rbuf, wc->rbuf, WF_DRUM_BYTES - 1);
+			break;
+
+		case WFC_UPLOAD_SAMPLE_HEADER:
+			process_sample_hdr (wc->rbuf);
+			break;
+
+		case WFC_UPLOAD_SAMPLE_ALIAS:
+			snd_printk ("support for "
+				    "sample aliases still "
+				    "being considered.\n");
+			break;
+
+		case WFC_VMIDI_OFF:
+			snd_wavefront_midi_disable_virtual (acard);
+			break;
+
+		case WFC_VMIDI_ON:
+			snd_wavefront_midi_enable_virtual (acard);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+int 
+snd_wavefront_synth_open (struct snd_hwdep *hw, struct file *file)
+
+{
+	if (!try_module_get(hw->card->module))
+		return -EFAULT;
+	file->private_data = hw;
+	return 0;
+}
+
+int 
+snd_wavefront_synth_release (struct snd_hwdep *hw, struct file *file)
+
+{
+	module_put(hw->card->module);
+	return 0;
+}
+
+int
+snd_wavefront_synth_ioctl (struct snd_hwdep *hw, struct file *file,
+			   unsigned int cmd, unsigned long arg)
+
+{
+	struct snd_card *card;
+	snd_wavefront_t *dev;
+	snd_wavefront_card_t *acard;
+	wavefront_control *wc;
+	void __user *argp = (void __user *)arg;
+	int err;
+
+	card = (struct snd_card *) hw->card;
+
+	if (snd_BUG_ON(!card))
+		return -ENODEV;
+	if (snd_BUG_ON(!card->private_data))
+		return -ENODEV;
+
+	acard = card->private_data;
+	dev = &acard->wavefront;
+	
+	switch (cmd) {
+	case WFCTL_LOAD_SPP:
+		if (wavefront_load_patch (dev, argp) != 0) {
+			return -EIO;
+		}
+		break;
+
+	case WFCTL_WFCMD:
+		wc = memdup_user(argp, sizeof(*wc));
+		if (IS_ERR(wc))
+			return PTR_ERR(wc);
+
+		if (wavefront_synth_control (acard, wc) < 0)
+			err = -EIO;
+		else if (copy_to_user (argp, wc, sizeof (*wc)))
+			err = -EFAULT;
+		else
+			err = 0;
+		kfree(wc);
+		return err;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+
+/***********************************************************************/
+/*  WaveFront: interface for card-level wavefront module               */
+/***********************************************************************/
+
+void
+snd_wavefront_internal_interrupt (snd_wavefront_card_t *card)
+{
+	snd_wavefront_t *dev = &card->wavefront;
+
+	/*
+	   Some comments on interrupts. I attempted a version of this
+	   driver that used interrupts throughout the code instead of
+	   doing busy and/or sleep-waiting. Alas, it appears that once
+	   the Motorola firmware is downloaded, the card *never*
+	   generates an RX interrupt. These are successfully generated
+	   during firmware loading, and after that wavefront_status()
+	   reports that an interrupt is pending on the card from time
+	   to time, but it never seems to be delivered to this
+	   driver. Note also that wavefront_status() continues to
+	   report that RX interrupts are enabled, suggesting that I
+	   didn't goof up and disable them by mistake.
+
+	   Thus, I stepped back to a prior version of
+	   wavefront_wait(), the only place where this really
+	   matters. Its sad, but I've looked through the code to check
+	   on things, and I really feel certain that the Motorola
+	   firmware prevents RX-ready interrupts.
+	*/
+
+	if ((wavefront_status(dev) & (STAT_INTR_READ|STAT_INTR_WRITE)) == 0) {
+		return;
+	}
+
+	spin_lock(&dev->irq_lock);
+	dev->irq_ok = 1;
+	dev->irq_cnt++;
+	spin_unlock(&dev->irq_lock);
+	wake_up(&dev->interrupt_sleeper);
+}
+
+/* STATUS REGISTER 
+
+0 Host Rx Interrupt Enable (1=Enabled)
+1 Host Rx Register Full (1=Full)
+2 Host Rx Interrupt Pending (1=Interrupt)
+3 Unused
+4 Host Tx Interrupt (1=Enabled)
+5 Host Tx Register empty (1=Empty)
+6 Host Tx Interrupt Pending (1=Interrupt)
+7 Unused
+*/
+
+static int __devinit
+snd_wavefront_interrupt_bits (int irq)
+
+{
+	int bits;
+
+	switch (irq) {
+	case 9:
+		bits = 0x00;
+		break;
+	case 5:
+		bits = 0x08;
+		break;
+	case 12:
+		bits = 0x10;
+		break;
+	case 15:
+		bits = 0x18;
+		break;
+	
+	default:
+		snd_printk ("invalid IRQ %d\n", irq);
+		bits = -1;
+	}
+
+	return bits;
+}
+
+static void __devinit
+wavefront_should_cause_interrupt (snd_wavefront_t *dev, 
+				  int val, int port, unsigned long timeout)
+
+{
+	wait_queue_t wait;
+
+	init_waitqueue_entry(&wait, current);
+	spin_lock_irq(&dev->irq_lock);
+	add_wait_queue(&dev->interrupt_sleeper, &wait);
+	dev->irq_ok = 0;
+	outb (val,port);
+	spin_unlock_irq(&dev->irq_lock);
+	while (!dev->irq_ok && time_before(jiffies, timeout)) {
+		schedule_timeout_uninterruptible(1);
+		barrier();
+	}
+}
+
+static int __devinit
+wavefront_reset_to_cleanliness (snd_wavefront_t *dev)
+
+{
+	int bits;
+	int hwv[2];
+
+	/* IRQ already checked */
+
+	bits = snd_wavefront_interrupt_bits (dev->irq);
+
+	/* try reset of port */
+
+	outb (0x0, dev->control_port); 
+  
+	/* At this point, the board is in reset, and the H/W initialization
+	   register is accessed at the same address as the data port.
+     
+	   Bit 7 - Enable IRQ Driver	
+	   0 - Tri-state the Wave-Board drivers for the PC Bus IRQs
+	   1 - Enable IRQ selected by bits 5:3 to be driven onto the PC Bus.
+     
+	   Bit 6 - MIDI Interface Select
+
+	   0 - Use the MIDI Input from the 26-pin WaveBlaster
+	   compatible header as the serial MIDI source
+	   1 - Use the MIDI Input from the 9-pin D connector as the
+	   serial MIDI source.
+     
+	   Bits 5:3 - IRQ Selection
+	   0 0 0 - IRQ 2/9
+	   0 0 1 - IRQ 5
+	   0 1 0 - IRQ 12
+	   0 1 1 - IRQ 15
+	   1 0 0 - Reserved
+	   1 0 1 - Reserved
+	   1 1 0 - Reserved
+	   1 1 1 - Reserved
+     
+	   Bits 2:1 - Reserved
+	   Bit 0 - Disable Boot ROM
+	   0 - memory accesses to 03FC30-03FFFFH utilize the internal Boot ROM
+	   1 - memory accesses to 03FC30-03FFFFH are directed to external 
+	   storage.
+     
+	*/
+
+	/* configure hardware: IRQ, enable interrupts, 
+	   plus external 9-pin MIDI interface selected
+	*/
+
+	outb (0x80 | 0x40 | bits, dev->data_port);	
+  
+	/* CONTROL REGISTER
+
+	   0 Host Rx Interrupt Enable (1=Enabled)      0x1
+	   1 Unused                                    0x2
+	   2 Unused                                    0x4
+	   3 Unused                                    0x8
+	   4 Host Tx Interrupt Enable                 0x10
+	   5 Mute (0=Mute; 1=Play)                    0x20
+	   6 Master Interrupt Enable (1=Enabled)      0x40
+	   7 Master Reset (0=Reset; 1=Run)            0x80
+
+	   Take us out of reset, mute output, master + TX + RX interrupts on.
+	   
+	   We'll get an interrupt presumably to tell us that the TX
+	   register is clear.
+	*/
+
+	wavefront_should_cause_interrupt(dev, 0x80|0x40|0x10|0x1,
+					 dev->control_port,
+					 (reset_time*HZ)/100);
+
+	/* Note: data port is now the data port, not the h/w initialization
+	   port.
+	 */
+
+	if (!dev->irq_ok) {
+		snd_printk ("intr not received after h/w un-reset.\n");
+		goto gone_bad;
+	} 
+
+	/* Note: data port is now the data port, not the h/w initialization
+	   port.
+
+	   At this point, only "HW VERSION" or "DOWNLOAD OS" commands
+	   will work. So, issue one of them, and wait for TX
+	   interrupt. This can take a *long* time after a cold boot,
+	   while the ISC ROM does its RAM test. The SDK says up to 4
+	   seconds - with 12MB of RAM on a Tropez+, it takes a lot
+	   longer than that (~16secs). Note that the card understands
+	   the difference between a warm and a cold boot, so
+	   subsequent ISC2115 reboots (say, caused by module
+	   reloading) will get through this much faster.
+
+	   XXX Interesting question: why is no RX interrupt received first ?
+	*/
+
+	wavefront_should_cause_interrupt(dev, WFC_HARDWARE_VERSION, 
+					 dev->data_port, ramcheck_time*HZ);
+
+	if (!dev->irq_ok) {
+		snd_printk ("post-RAM-check interrupt not received.\n");
+		goto gone_bad;
+	} 
+
+	if (!wavefront_wait (dev, STAT_CAN_READ)) {
+		snd_printk ("no response to HW version cmd.\n");
+		goto gone_bad;
+	}
+	
+	if ((hwv[0] = wavefront_read (dev)) == -1) {
+		snd_printk ("board not responding correctly.\n");
+		goto gone_bad;
+	}
+
+	if (hwv[0] == 0xFF) { /* NAK */
+
+		/* Board's RAM test failed. Try to read error code,
+		   and tell us about it either way.
+		*/
+		
+		if ((hwv[0] = wavefront_read (dev)) == -1) {
+			snd_printk ("on-board RAM test failed "
+				    "(bad error code).\n");
+		} else {
+			snd_printk ("on-board RAM test failed "
+				    "(error code: 0x%x).\n",
+				hwv[0]);
+		}
+		goto gone_bad;
+	}
+
+	/* We're OK, just get the next byte of the HW version response */
+
+	if ((hwv[1] = wavefront_read (dev)) == -1) {
+		snd_printk ("incorrect h/w response.\n");
+		goto gone_bad;
+	}
+
+	snd_printk ("hardware version %d.%d\n",
+		    hwv[0], hwv[1]);
+
+	return 0;
+
+
+     gone_bad:
+	return (1);
+}
+
+static int __devinit
+wavefront_download_firmware (snd_wavefront_t *dev, char *path)
+
+{
+	const unsigned char *buf;
+	int len, err;
+	int section_cnt_downloaded = 0;
+	const struct firmware *firmware;
+
+	err = request_firmware(&firmware, path, dev->card->dev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "firmware (%s) download failed!!!\n", path);
+		return 1;
+	}
+
+	len = 0;
+	buf = firmware->data;
+	for (;;) {
+		int section_length = *(signed char *)buf;
+		if (section_length == 0)
+			break;
+		if (section_length < 0 || section_length > WF_SECTION_MAX) {
+			snd_printk(KERN_ERR
+				   "invalid firmware section length %d\n",
+				   section_length);
+			goto failure;
+		}
+		buf++;
+		len++;
+
+		if (firmware->size < len + section_length) {
+			snd_printk(KERN_ERR "firmware section read error.\n");
+			goto failure;
+		}
+
+		/* Send command */
+		if (wavefront_write(dev, WFC_DOWNLOAD_OS))
+			goto failure;
+	
+		for (; section_length; section_length--) {
+			if (wavefront_write(dev, *buf))
+				goto failure;
+			buf++;
+			len++;
+		}
+	
+		/* get ACK */
+		if (!wavefront_wait(dev, STAT_CAN_READ)) {
+			snd_printk(KERN_ERR "time out for firmware ACK.\n");
+			goto failure;
+		}
+		err = inb(dev->data_port);
+		if (err != WF_ACK) {
+			snd_printk(KERN_ERR
+				   "download of section #%d not "
+				   "acknowledged, ack = 0x%x\n",
+				   section_cnt_downloaded + 1, err);
+			goto failure;
+		}
+
+		section_cnt_downloaded++;
+	}
+
+	release_firmware(firmware);
+	return 0;
+
+ failure:
+	release_firmware(firmware);
+	snd_printk(KERN_ERR "firmware download failed!!!\n");
+	return 1;
+}
+
+
+static int __devinit
+wavefront_do_reset (snd_wavefront_t *dev)
+
+{
+	char voices[1];
+
+	if (wavefront_reset_to_cleanliness (dev)) {
+		snd_printk ("hw reset failed.\n");
+		goto gone_bad;
+	}
+
+	if (dev->israw) {
+		if (wavefront_download_firmware (dev, ospath)) {
+			goto gone_bad;
+		}
+
+		dev->israw = 0;
+
+		/* Wait for the OS to get running. The protocol for
+		   this is non-obvious, and was determined by
+		   using port-IO tracing in DOSemu and some
+		   experimentation here.
+		   
+		   Rather than using timed waits, use interrupts creatively.
+		*/
+
+		wavefront_should_cause_interrupt (dev, WFC_NOOP,
+						  dev->data_port,
+						  (osrun_time*HZ));
+
+		if (!dev->irq_ok) {
+			snd_printk ("no post-OS interrupt.\n");
+			goto gone_bad;
+		}
+		
+		/* Now, do it again ! */
+		
+		wavefront_should_cause_interrupt (dev, WFC_NOOP,
+						  dev->data_port, (10*HZ));
+		
+		if (!dev->irq_ok) {
+			snd_printk ("no post-OS interrupt(2).\n");
+			goto gone_bad;
+		}
+
+		/* OK, no (RX/TX) interrupts any more, but leave mute
+		   in effect. 
+		*/
+		
+		outb (0x80|0x40, dev->control_port); 
+	}
+
+	/* SETUPSND.EXE asks for sample memory config here, but since i
+	   have no idea how to interpret the result, we'll forget
+	   about it.
+	*/
+	
+	if ((dev->freemem = wavefront_freemem (dev)) < 0) {
+		goto gone_bad;
+	}
+		
+	snd_printk ("available DRAM %dk\n", dev->freemem / 1024);
+
+	if (wavefront_write (dev, 0xf0) ||
+	    wavefront_write (dev, 1) ||
+	    (wavefront_read (dev) < 0)) {
+		dev->debug = 0;
+		snd_printk ("MPU emulation mode not set.\n");
+		goto gone_bad;
+	}
+
+	voices[0] = 32;
+
+	if (snd_wavefront_cmd (dev, WFC_SET_NVOICES, NULL, voices)) {
+		snd_printk ("cannot set number of voices to 32.\n");
+		goto gone_bad;
+	}
+
+
+	return 0;
+
+ gone_bad:
+	/* reset that sucker so that it doesn't bother us. */
+
+	outb (0x0, dev->control_port);
+	dev->interrupts_are_midi = 0;
+	return 1;
+}
+
+int __devinit
+snd_wavefront_start (snd_wavefront_t *dev)
+
+{
+	int samples_are_from_rom;
+
+	/* IMPORTANT: assumes that snd_wavefront_detect() and/or
+	   wavefront_reset_to_cleanliness() has already been called 
+	*/
+
+	if (dev->israw) {
+		samples_are_from_rom = 1;
+	} else {
+		/* XXX is this always true ? */
+		samples_are_from_rom = 0;
+	}
+
+	if (dev->israw || fx_raw) {
+		if (wavefront_do_reset (dev)) {
+			return -1;
+		}
+	}
+	/* Check for FX device, present only on Tropez+ */
+
+	dev->has_fx = (snd_wavefront_fx_detect (dev) == 0);
+
+	if (dev->has_fx && fx_raw) {
+		snd_wavefront_fx_start (dev);
+	}
+
+	wavefront_get_sample_status (dev, samples_are_from_rom);
+	wavefront_get_program_status (dev);
+	wavefront_get_patch_status (dev);
+
+	/* Start normal operation: unreset, master interrupt enabled, no mute
+	*/
+
+	outb (0x80|0x40|0x20, dev->control_port); 
+
+	return (0);
+}
+
+int __devinit
+snd_wavefront_detect (snd_wavefront_card_t *card)
+
+{
+	unsigned char   rbuf[4], wbuf[4];
+	snd_wavefront_t *dev = &card->wavefront;
+	
+	/* returns zero if a WaveFront card is successfully detected.
+	   negative otherwise.
+	*/
+
+	dev->israw = 0;
+	dev->has_fx = 0;
+	dev->debug = debug_default;
+	dev->interrupts_are_midi = 0;
+	dev->irq_cnt = 0;
+	dev->rom_samples_rdonly = 1;
+
+	if (snd_wavefront_cmd (dev, WFC_FIRMWARE_VERSION, rbuf, wbuf) == 0) {
+
+		dev->fw_version[0] = rbuf[0];
+		dev->fw_version[1] = rbuf[1];
+
+		snd_printk ("firmware %d.%d already loaded.\n",
+			    rbuf[0], rbuf[1]);
+
+		/* check that a command actually works */
+      
+		if (snd_wavefront_cmd (dev, WFC_HARDWARE_VERSION,
+				       rbuf, wbuf) == 0) {
+			dev->hw_version[0] = rbuf[0];
+			dev->hw_version[1] = rbuf[1];
+		} else {
+			snd_printk ("not raw, but no "
+				    "hardware version!\n");
+			return -1;
+		}
+
+		if (!wf_raw) {
+			return 0;
+		} else {
+			snd_printk ("reloading firmware as you requested.\n");
+			dev->israw = 1;
+		}
+
+	} else {
+
+		dev->israw = 1;
+		snd_printk ("no response to firmware probe, assume raw.\n");
+
+	}
+
+	return 0;
+}
+
+MODULE_FIRMWARE(DEFAULT_OSPATH);
diff --git a/linux/sound/isa/wss/Makefile b/linux/sound/isa/wss/Makefile
new file mode 100644
index 0000000..454fee7
--- /dev/null
+++ b/linux/sound/isa/wss/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2008 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-wss-lib-objs := wss_lib.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_WSS_LIB) += snd-wss-lib.o
+
diff --git a/linux/sound/isa/wss/wss_lib.c b/linux/sound/isa/wss/wss_lib.c
new file mode 100644
index 0000000..9191b32
--- /dev/null
+++ b/linux/sound/isa/wss/wss_lib.c
@@ -0,0 +1,2303 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for control of CS4231(A)/CS4232/InterWave & compatible chips
+ *
+ *  Bugs:
+ *     - sometimes record brokes playback with WSS portion of
+ *       Yamaha OPL3-SA3 chip
+ *     - CS4231 (GUS MAX) - still trouble with occasional noises
+ *			  - broken initialization?
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <sound/core.h>
+#include <sound/wss.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Routines for control of CS4231(A)/CS4232/InterWave & compatible chips");
+MODULE_LICENSE("GPL");
+
+#if 0
+#define SNDRV_DEBUG_MCE
+#endif
+
+/*
+ *  Some variables
+ */
+
+static unsigned char freq_bits[14] = {
+	/* 5510 */	0x00 | CS4231_XTAL2,
+	/* 6620 */	0x0E | CS4231_XTAL2,
+	/* 8000 */	0x00 | CS4231_XTAL1,
+	/* 9600 */	0x0E | CS4231_XTAL1,
+	/* 11025 */	0x02 | CS4231_XTAL2,
+	/* 16000 */	0x02 | CS4231_XTAL1,
+	/* 18900 */	0x04 | CS4231_XTAL2,
+	/* 22050 */	0x06 | CS4231_XTAL2,
+	/* 27042 */	0x04 | CS4231_XTAL1,
+	/* 32000 */	0x06 | CS4231_XTAL1,
+	/* 33075 */	0x0C | CS4231_XTAL2,
+	/* 37800 */	0x08 | CS4231_XTAL2,
+	/* 44100 */	0x0A | CS4231_XTAL2,
+	/* 48000 */	0x0C | CS4231_XTAL1
+};
+
+static unsigned int rates[14] = {
+	5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050,
+	27042, 32000, 33075, 37800, 44100, 48000
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static int snd_wss_xrate(struct snd_pcm_runtime *runtime)
+{
+	return snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+					  &hw_constraints_rates);
+}
+
+static unsigned char snd_wss_original_image[32] =
+{
+	0x00,			/* 00/00 - lic */
+	0x00,			/* 01/01 - ric */
+	0x9f,			/* 02/02 - la1ic */
+	0x9f,			/* 03/03 - ra1ic */
+	0x9f,			/* 04/04 - la2ic */
+	0x9f,			/* 05/05 - ra2ic */
+	0xbf,			/* 06/06 - loc */
+	0xbf,			/* 07/07 - roc */
+	0x20,			/* 08/08 - pdfr */
+	CS4231_AUTOCALIB,	/* 09/09 - ic */
+	0x00,			/* 0a/10 - pc */
+	0x00,			/* 0b/11 - ti */
+	CS4231_MODE2,		/* 0c/12 - mi */
+	0xfc,			/* 0d/13 - lbc */
+	0x00,			/* 0e/14 - pbru */
+	0x00,			/* 0f/15 - pbrl */
+	0x80,			/* 10/16 - afei */
+	0x01,			/* 11/17 - afeii */
+	0x9f,			/* 12/18 - llic */
+	0x9f,			/* 13/19 - rlic */
+	0x00,			/* 14/20 - tlb */
+	0x00,			/* 15/21 - thb */
+	0x00,			/* 16/22 - la3mic/reserved */
+	0x00,			/* 17/23 - ra3mic/reserved */
+	0x00,			/* 18/24 - afs */
+	0x00,			/* 19/25 - lamoc/version */
+	0xcf,			/* 1a/26 - mioc */
+	0x00,			/* 1b/27 - ramoc/reserved */
+	0x20,			/* 1c/28 - cdfr */
+	0x00,			/* 1d/29 - res4 */
+	0x00,			/* 1e/30 - cbru */
+	0x00,			/* 1f/31 - cbrl */
+};
+
+static unsigned char snd_opti93x_original_image[32] =
+{
+	0x00,		/* 00/00 - l_mixout_outctrl */
+	0x00,		/* 01/01 - r_mixout_outctrl */
+	0x88,		/* 02/02 - l_cd_inctrl */
+	0x88,		/* 03/03 - r_cd_inctrl */
+	0x88,		/* 04/04 - l_a1/fm_inctrl */
+	0x88,		/* 05/05 - r_a1/fm_inctrl */
+	0x80,		/* 06/06 - l_dac_inctrl */
+	0x80,		/* 07/07 - r_dac_inctrl */
+	0x00,		/* 08/08 - ply_dataform_reg */
+	0x00,		/* 09/09 - if_conf */
+	0x00,		/* 0a/10 - pin_ctrl */
+	0x00,		/* 0b/11 - err_init_reg */
+	0x0a,		/* 0c/12 - id_reg */
+	0x00,		/* 0d/13 - reserved */
+	0x00,		/* 0e/14 - ply_upcount_reg */
+	0x00,		/* 0f/15 - ply_lowcount_reg */
+	0x88,		/* 10/16 - reserved/l_a1_inctrl */
+	0x88,		/* 11/17 - reserved/r_a1_inctrl */
+	0x88,		/* 12/18 - l_line_inctrl */
+	0x88,		/* 13/19 - r_line_inctrl */
+	0x88,		/* 14/20 - l_mic_inctrl */
+	0x88,		/* 15/21 - r_mic_inctrl */
+	0x80,		/* 16/22 - l_out_outctrl */
+	0x80,		/* 17/23 - r_out_outctrl */
+	0x00,		/* 18/24 - reserved */
+	0x00,		/* 19/25 - reserved */
+	0x00,		/* 1a/26 - reserved */
+	0x00,		/* 1b/27 - reserved */
+	0x00,		/* 1c/28 - cap_dataform_reg */
+	0x00,		/* 1d/29 - reserved */
+	0x00,		/* 1e/30 - cap_upcount_reg */
+	0x00		/* 1f/31 - cap_lowcount_reg */
+};
+
+/*
+ *  Basic I/O functions
+ */
+
+static inline void wss_outb(struct snd_wss *chip, u8 offset, u8 val)
+{
+	outb(val, chip->port + offset);
+}
+
+static inline u8 wss_inb(struct snd_wss *chip, u8 offset)
+{
+	return inb(chip->port + offset);
+}
+
+static void snd_wss_wait(struct snd_wss *chip)
+{
+	int timeout;
+
+	for (timeout = 250;
+	     timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+	     timeout--)
+		udelay(100);
+}
+
+static void snd_wss_dout(struct snd_wss *chip, unsigned char reg,
+			 unsigned char value)
+{
+	int timeout;
+
+	for (timeout = 250;
+	     timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+	     timeout--)
+		udelay(10);
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+	wss_outb(chip, CS4231P(REG), value);
+	mb();
+}
+
+void snd_wss_out(struct snd_wss *chip, unsigned char reg, unsigned char value)
+{
+	snd_wss_wait(chip);
+#ifdef CONFIG_SND_DEBUG
+	if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk(KERN_DEBUG "out: auto calibration time out "
+			   "- reg = 0x%x, value = 0x%x\n", reg, value);
+#endif
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+	wss_outb(chip, CS4231P(REG), value);
+	chip->image[reg] = value;
+	mb();
+	snd_printdd("codec out - reg 0x%x = 0x%x\n",
+			chip->mce_bit | reg, value);
+}
+EXPORT_SYMBOL(snd_wss_out);
+
+unsigned char snd_wss_in(struct snd_wss *chip, unsigned char reg)
+{
+	snd_wss_wait(chip);
+#ifdef CONFIG_SND_DEBUG
+	if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk(KERN_DEBUG "in: auto calibration time out "
+			   "- reg = 0x%x\n", reg);
+#endif
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
+	mb();
+	return wss_inb(chip, CS4231P(REG));
+}
+EXPORT_SYMBOL(snd_wss_in);
+
+void snd_cs4236_ext_out(struct snd_wss *chip, unsigned char reg,
+			unsigned char val)
+{
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17);
+	wss_outb(chip, CS4231P(REG),
+		 reg | (chip->image[CS4236_EXT_REG] & 0x01));
+	wss_outb(chip, CS4231P(REG), val);
+	chip->eimage[CS4236_REG(reg)] = val;
+#if 0
+	printk(KERN_DEBUG "ext out : reg = 0x%x, val = 0x%x\n", reg, val);
+#endif
+}
+EXPORT_SYMBOL(snd_cs4236_ext_out);
+
+unsigned char snd_cs4236_ext_in(struct snd_wss *chip, unsigned char reg)
+{
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17);
+	wss_outb(chip, CS4231P(REG),
+		 reg | (chip->image[CS4236_EXT_REG] & 0x01));
+#if 1
+	return wss_inb(chip, CS4231P(REG));
+#else
+	{
+		unsigned char res;
+		res = wss_inb(chip, CS4231P(REG));
+		printk(KERN_DEBUG "ext in : reg = 0x%x, val = 0x%x\n",
+		       reg, res);
+		return res;
+	}
+#endif
+}
+EXPORT_SYMBOL(snd_cs4236_ext_in);
+
+#if 0
+
+static void snd_wss_debug(struct snd_wss *chip)
+{
+	printk(KERN_DEBUG
+		"CS4231 REGS:      INDEX = 0x%02x  "
+		"                 STATUS = 0x%02x\n",
+					wss_inb(chip, CS4231P(REGSEL)),
+					wss_inb(chip, CS4231P(STATUS)));
+	printk(KERN_DEBUG
+		"  0x00: left input      = 0x%02x  "
+		"  0x10: alt 1 (CFIG 2)  = 0x%02x\n",
+					snd_wss_in(chip, 0x00),
+					snd_wss_in(chip, 0x10));
+	printk(KERN_DEBUG
+		"  0x01: right input     = 0x%02x  "
+		"  0x11: alt 2 (CFIG 3)  = 0x%02x\n",
+					snd_wss_in(chip, 0x01),
+					snd_wss_in(chip, 0x11));
+	printk(KERN_DEBUG
+		"  0x02: GF1 left input  = 0x%02x  "
+		"  0x12: left line in    = 0x%02x\n",
+					snd_wss_in(chip, 0x02),
+					snd_wss_in(chip, 0x12));
+	printk(KERN_DEBUG
+		"  0x03: GF1 right input = 0x%02x  "
+		"  0x13: right line in   = 0x%02x\n",
+					snd_wss_in(chip, 0x03),
+					snd_wss_in(chip, 0x13));
+	printk(KERN_DEBUG
+		"  0x04: CD left input   = 0x%02x  "
+		"  0x14: timer low       = 0x%02x\n",
+					snd_wss_in(chip, 0x04),
+					snd_wss_in(chip, 0x14));
+	printk(KERN_DEBUG
+		"  0x05: CD right input  = 0x%02x  "
+		"  0x15: timer high      = 0x%02x\n",
+					snd_wss_in(chip, 0x05),
+					snd_wss_in(chip, 0x15));
+	printk(KERN_DEBUG
+		"  0x06: left output     = 0x%02x  "
+		"  0x16: left MIC (PnP)  = 0x%02x\n",
+					snd_wss_in(chip, 0x06),
+					snd_wss_in(chip, 0x16));
+	printk(KERN_DEBUG
+		"  0x07: right output    = 0x%02x  "
+		"  0x17: right MIC (PnP) = 0x%02x\n",
+					snd_wss_in(chip, 0x07),
+					snd_wss_in(chip, 0x17));
+	printk(KERN_DEBUG
+		"  0x08: playback format = 0x%02x  "
+		"  0x18: IRQ status      = 0x%02x\n",
+					snd_wss_in(chip, 0x08),
+					snd_wss_in(chip, 0x18));
+	printk(KERN_DEBUG
+		"  0x09: iface (CFIG 1)  = 0x%02x  "
+		"  0x19: left line out   = 0x%02x\n",
+					snd_wss_in(chip, 0x09),
+					snd_wss_in(chip, 0x19));
+	printk(KERN_DEBUG
+		"  0x0a: pin control     = 0x%02x  "
+		"  0x1a: mono control    = 0x%02x\n",
+					snd_wss_in(chip, 0x0a),
+					snd_wss_in(chip, 0x1a));
+	printk(KERN_DEBUG
+		"  0x0b: init & status   = 0x%02x  "
+		"  0x1b: right line out  = 0x%02x\n",
+					snd_wss_in(chip, 0x0b),
+					snd_wss_in(chip, 0x1b));
+	printk(KERN_DEBUG
+		"  0x0c: revision & mode = 0x%02x  "
+		"  0x1c: record format   = 0x%02x\n",
+					snd_wss_in(chip, 0x0c),
+					snd_wss_in(chip, 0x1c));
+	printk(KERN_DEBUG
+		"  0x0d: loopback        = 0x%02x  "
+		"  0x1d: var freq (PnP)  = 0x%02x\n",
+					snd_wss_in(chip, 0x0d),
+					snd_wss_in(chip, 0x1d));
+	printk(KERN_DEBUG
+		"  0x0e: ply upr count   = 0x%02x  "
+		"  0x1e: ply lwr count   = 0x%02x\n",
+					snd_wss_in(chip, 0x0e),
+					snd_wss_in(chip, 0x1e));
+	printk(KERN_DEBUG
+		"  0x0f: rec upr count   = 0x%02x  "
+		"  0x1f: rec lwr count   = 0x%02x\n",
+					snd_wss_in(chip, 0x0f),
+					snd_wss_in(chip, 0x1f));
+}
+
+#endif
+
+/*
+ *  CS4231 detection / MCE routines
+ */
+
+static void snd_wss_busy_wait(struct snd_wss *chip)
+{
+	int timeout;
+
+	/* huh.. looks like this sequence is proper for CS4231A chip (GUS MAX) */
+	for (timeout = 5; timeout > 0; timeout--)
+		wss_inb(chip, CS4231P(REGSEL));
+	/* end of cleanup sequence */
+	for (timeout = 25000;
+	     timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
+	     timeout--)
+		udelay(10);
+}
+
+void snd_wss_mce_up(struct snd_wss *chip)
+{
+	unsigned long flags;
+	int timeout;
+
+	snd_wss_wait(chip);
+#ifdef CONFIG_SND_DEBUG
+	if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk(KERN_DEBUG
+			   "mce_up - auto calibration time out (0)\n");
+#endif
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->mce_bit |= CS4231_MCE;
+	timeout = wss_inb(chip, CS4231P(REGSEL));
+	if (timeout == 0x80)
+		snd_printk(KERN_DEBUG "mce_up [0x%lx]: "
+			   "serious init problem - codec still busy\n",
+			   chip->port);
+	if (!(timeout & CS4231_MCE))
+		wss_outb(chip, CS4231P(REGSEL),
+			 chip->mce_bit | (timeout & 0x1f));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+EXPORT_SYMBOL(snd_wss_mce_up);
+
+void snd_wss_mce_down(struct snd_wss *chip)
+{
+	unsigned long flags;
+	unsigned long end_time;
+	int timeout;
+	int hw_mask = WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK | WSS_HW_AD1848;
+
+	snd_wss_busy_wait(chip);
+
+#ifdef CONFIG_SND_DEBUG
+	if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+		snd_printk(KERN_DEBUG "mce_down [0x%lx] - "
+			   "auto calibration time out (0)\n",
+			   (long)CS4231P(REGSEL));
+#endif
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->mce_bit &= ~CS4231_MCE;
+	timeout = wss_inb(chip, CS4231P(REGSEL));
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (timeout == 0x80)
+		snd_printk(KERN_DEBUG "mce_down [0x%lx]: "
+			   "serious init problem - codec still busy\n",
+			   chip->port);
+	if ((timeout & CS4231_MCE) == 0 || !(chip->hardware & hw_mask))
+		return;
+
+	/*
+	 * Wait for (possible -- during init auto-calibration may not be set)
+	 * calibration process to start. Needs upto 5 sample periods on AD1848
+	 * which at the slowest possible rate of 5.5125 kHz means 907 us.
+	 */
+	msleep(1);
+
+	snd_printdd("(1) jiffies = %lu\n", jiffies);
+
+	/* check condition up to 250 ms */
+	end_time = jiffies + msecs_to_jiffies(250);
+	while (snd_wss_in(chip, CS4231_TEST_INIT) &
+		CS4231_CALIB_IN_PROGRESS) {
+
+		if (time_after(jiffies, end_time)) {
+			snd_printk(KERN_ERR "mce_down - "
+					"auto calibration time out (2)\n");
+			return;
+		}
+		msleep(1);
+	}
+
+	snd_printdd("(2) jiffies = %lu\n", jiffies);
+
+	/* check condition up to 100 ms */
+	end_time = jiffies + msecs_to_jiffies(100);
+	while (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) {
+		if (time_after(jiffies, end_time)) {
+			snd_printk(KERN_ERR "mce_down - auto calibration time out (3)\n");
+			return;
+		}
+		msleep(1);
+	}
+
+	snd_printdd("(3) jiffies = %lu\n", jiffies);
+	snd_printd("mce_down - exit = 0x%x\n", wss_inb(chip, CS4231P(REGSEL)));
+}
+EXPORT_SYMBOL(snd_wss_mce_down);
+
+static unsigned int snd_wss_get_count(unsigned char format, unsigned int size)
+{
+	switch (format & 0xe0) {
+	case CS4231_LINEAR_16:
+	case CS4231_LINEAR_16_BIG:
+		size >>= 1;
+		break;
+	case CS4231_ADPCM_16:
+		return size >> 2;
+	}
+	if (format & CS4231_STEREO)
+		size >>= 1;
+	return size;
+}
+
+static int snd_wss_trigger(struct snd_pcm_substream *substream,
+			   int cmd)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+	unsigned int what;
+	struct snd_pcm_substream *s;
+	int do_start;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		do_start = 1; break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		do_start = 0; break;
+	default:
+		return -EINVAL;
+	}
+
+	what = 0;
+	snd_pcm_group_for_each_entry(s, substream) {
+		if (s == chip->playback_substream) {
+			what |= CS4231_PLAYBACK_ENABLE;
+			snd_pcm_trigger_done(s, substream);
+		} else if (s == chip->capture_substream) {
+			what |= CS4231_RECORD_ENABLE;
+			snd_pcm_trigger_done(s, substream);
+		}
+	}
+	spin_lock(&chip->reg_lock);
+	if (do_start) {
+		chip->image[CS4231_IFACE_CTRL] |= what;
+		if (chip->trigger)
+			chip->trigger(chip, what, 1);
+	} else {
+		chip->image[CS4231_IFACE_CTRL] &= ~what;
+		if (chip->trigger)
+			chip->trigger(chip, what, 0);
+	}
+	snd_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+	spin_unlock(&chip->reg_lock);
+#if 0
+	snd_wss_debug(chip);
+#endif
+	return result;
+}
+
+/*
+ *  CODEC I/O
+ */
+
+static unsigned char snd_wss_get_rate(unsigned int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(rates); i++)
+		if (rate == rates[i])
+			return freq_bits[i];
+	// snd_BUG();
+	return freq_bits[ARRAY_SIZE(rates) - 1];
+}
+
+static unsigned char snd_wss_get_format(struct snd_wss *chip,
+					int format,
+					int channels)
+{
+	unsigned char rformat;
+
+	rformat = CS4231_LINEAR_8;
+	switch (format) {
+	case SNDRV_PCM_FORMAT_MU_LAW:	rformat = CS4231_ULAW_8; break;
+	case SNDRV_PCM_FORMAT_A_LAW:	rformat = CS4231_ALAW_8; break;
+	case SNDRV_PCM_FORMAT_S16_LE:	rformat = CS4231_LINEAR_16; break;
+	case SNDRV_PCM_FORMAT_S16_BE:	rformat = CS4231_LINEAR_16_BIG; break;
+	case SNDRV_PCM_FORMAT_IMA_ADPCM:	rformat = CS4231_ADPCM_16; break;
+	}
+	if (channels > 1)
+		rformat |= CS4231_STEREO;
+#if 0
+	snd_printk(KERN_DEBUG "get_format: 0x%x (mode=0x%x)\n", format, mode);
+#endif
+	return rformat;
+}
+
+static void snd_wss_calibrate_mute(struct snd_wss *chip, int mute)
+{
+	unsigned long flags;
+
+	mute = mute ? 0x80 : 0;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->calibrate_mute == mute) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return;
+	}
+	if (!mute) {
+		snd_wss_dout(chip, CS4231_LEFT_INPUT,
+			     chip->image[CS4231_LEFT_INPUT]);
+		snd_wss_dout(chip, CS4231_RIGHT_INPUT,
+			     chip->image[CS4231_RIGHT_INPUT]);
+		snd_wss_dout(chip, CS4231_LOOPBACK,
+			     chip->image[CS4231_LOOPBACK]);
+	} else {
+		snd_wss_dout(chip, CS4231_LEFT_INPUT,
+			     0);
+		snd_wss_dout(chip, CS4231_RIGHT_INPUT,
+			     0);
+		snd_wss_dout(chip, CS4231_LOOPBACK,
+			     0xfd);
+	}
+
+	snd_wss_dout(chip, CS4231_AUX1_LEFT_INPUT,
+		     mute | chip->image[CS4231_AUX1_LEFT_INPUT]);
+	snd_wss_dout(chip, CS4231_AUX1_RIGHT_INPUT,
+		     mute | chip->image[CS4231_AUX1_RIGHT_INPUT]);
+	snd_wss_dout(chip, CS4231_AUX2_LEFT_INPUT,
+		     mute | chip->image[CS4231_AUX2_LEFT_INPUT]);
+	snd_wss_dout(chip, CS4231_AUX2_RIGHT_INPUT,
+		     mute | chip->image[CS4231_AUX2_RIGHT_INPUT]);
+	snd_wss_dout(chip, CS4231_LEFT_OUTPUT,
+		     mute | chip->image[CS4231_LEFT_OUTPUT]);
+	snd_wss_dout(chip, CS4231_RIGHT_OUTPUT,
+		     mute | chip->image[CS4231_RIGHT_OUTPUT]);
+	if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+		snd_wss_dout(chip, CS4231_LEFT_LINE_IN,
+			     mute | chip->image[CS4231_LEFT_LINE_IN]);
+		snd_wss_dout(chip, CS4231_RIGHT_LINE_IN,
+			     mute | chip->image[CS4231_RIGHT_LINE_IN]);
+		snd_wss_dout(chip, CS4231_MONO_CTRL,
+			     mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]);
+	}
+	if (chip->hardware == WSS_HW_INTERWAVE) {
+		snd_wss_dout(chip, CS4231_LEFT_MIC_INPUT,
+			     mute | chip->image[CS4231_LEFT_MIC_INPUT]);
+		snd_wss_dout(chip, CS4231_RIGHT_MIC_INPUT,
+			     mute | chip->image[CS4231_RIGHT_MIC_INPUT]);
+		snd_wss_dout(chip, CS4231_LINE_LEFT_OUTPUT,
+			     mute | chip->image[CS4231_LINE_LEFT_OUTPUT]);
+		snd_wss_dout(chip, CS4231_LINE_RIGHT_OUTPUT,
+			     mute | chip->image[CS4231_LINE_RIGHT_OUTPUT]);
+	}
+	chip->calibrate_mute = mute;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_wss_playback_format(struct snd_wss *chip,
+				       struct snd_pcm_hw_params *params,
+				       unsigned char pdfr)
+{
+	unsigned long flags;
+	int full_calib = 1;
+
+	mutex_lock(&chip->mce_mutex);
+	if (chip->hardware == WSS_HW_CS4231A ||
+	    (chip->hardware & WSS_HW_CS4232_MASK)) {
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (pdfr & 0x0f)) {	/* rate is same? */
+			snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+				    chip->image[CS4231_ALT_FEATURE_1] | 0x10);
+			chip->image[CS4231_PLAYBK_FORMAT] = pdfr;
+			snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
+				    chip->image[CS4231_PLAYBK_FORMAT]);
+			snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+				    chip->image[CS4231_ALT_FEATURE_1] &= ~0x10);
+			udelay(100); /* Fixes audible clicks at least on GUS MAX */
+			full_calib = 0;
+		}
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	} else if (chip->hardware == WSS_HW_AD1845) {
+		unsigned rate = params_rate(params);
+
+		/*
+		 * Program the AD1845 correctly for the playback stream.
+		 * Note that we do NOT need to toggle the MCE bit because
+		 * the PLAYBACK_ENABLE bit of the Interface Configuration
+		 * register is set.
+		 *
+		 * NOTE: We seem to need to write to the MSB before the LSB
+		 *       to get the correct sample frequency.
+		 */
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_wss_out(chip, CS4231_PLAYBK_FORMAT, (pdfr & 0xf0));
+		snd_wss_out(chip, AD1845_UPR_FREQ_SEL, (rate >> 8) & 0xff);
+		snd_wss_out(chip, AD1845_LWR_FREQ_SEL, rate & 0xff);
+		full_calib = 0;
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	}
+	if (full_calib) {
+		snd_wss_mce_up(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if (chip->hardware != WSS_HW_INTERWAVE && !chip->single_dma) {
+			if (chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE)
+				pdfr = (pdfr & 0xf0) |
+				       (chip->image[CS4231_REC_FORMAT] & 0x0f);
+		} else {
+			chip->image[CS4231_PLAYBK_FORMAT] = pdfr;
+		}
+		snd_wss_out(chip, CS4231_PLAYBK_FORMAT, pdfr);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		if (chip->hardware == WSS_HW_OPL3SA2)
+			udelay(100);	/* this seems to help */
+		snd_wss_mce_down(chip);
+	}
+	mutex_unlock(&chip->mce_mutex);
+}
+
+static void snd_wss_capture_format(struct snd_wss *chip,
+				   struct snd_pcm_hw_params *params,
+				   unsigned char cdfr)
+{
+	unsigned long flags;
+	int full_calib = 1;
+
+	mutex_lock(&chip->mce_mutex);
+	if (chip->hardware == WSS_HW_CS4231A ||
+	    (chip->hardware & WSS_HW_CS4232_MASK)) {
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (cdfr & 0x0f) ||	/* rate is same? */
+		    (chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) {
+			snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+				chip->image[CS4231_ALT_FEATURE_1] | 0x20);
+			snd_wss_out(chip, CS4231_REC_FORMAT,
+				chip->image[CS4231_REC_FORMAT] = cdfr);
+			snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+				chip->image[CS4231_ALT_FEATURE_1] &= ~0x20);
+			full_calib = 0;
+		}
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	} else if (chip->hardware == WSS_HW_AD1845) {
+		unsigned rate = params_rate(params);
+
+		/*
+		 * Program the AD1845 correctly for the capture stream.
+		 * Note that we do NOT need to toggle the MCE bit because
+		 * the PLAYBACK_ENABLE bit of the Interface Configuration
+		 * register is set.
+		 *
+		 * NOTE: We seem to need to write to the MSB before the LSB
+		 *       to get the correct sample frequency.
+		 */
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_wss_out(chip, CS4231_REC_FORMAT, (cdfr & 0xf0));
+		snd_wss_out(chip, AD1845_UPR_FREQ_SEL, (rate >> 8) & 0xff);
+		snd_wss_out(chip, AD1845_LWR_FREQ_SEL, rate & 0xff);
+		full_calib = 0;
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	}
+	if (full_calib) {
+		snd_wss_mce_up(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if (chip->hardware != WSS_HW_INTERWAVE &&
+		    !(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) {
+			if (chip->single_dma)
+				snd_wss_out(chip, CS4231_PLAYBK_FORMAT, cdfr);
+			else
+				snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
+				   (chip->image[CS4231_PLAYBK_FORMAT] & 0xf0) |
+				   (cdfr & 0x0f));
+			spin_unlock_irqrestore(&chip->reg_lock, flags);
+			snd_wss_mce_down(chip);
+			snd_wss_mce_up(chip);
+			spin_lock_irqsave(&chip->reg_lock, flags);
+		}
+		if (chip->hardware & WSS_HW_AD1848_MASK)
+			snd_wss_out(chip, CS4231_PLAYBK_FORMAT, cdfr);
+		else
+			snd_wss_out(chip, CS4231_REC_FORMAT, cdfr);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		snd_wss_mce_down(chip);
+	}
+	mutex_unlock(&chip->mce_mutex);
+}
+
+/*
+ *  Timer interface
+ */
+
+static unsigned long snd_wss_timer_resolution(struct snd_timer *timer)
+{
+	struct snd_wss *chip = snd_timer_chip(timer);
+	if (chip->hardware & WSS_HW_CS4236B_MASK)
+		return 14467;
+	else
+		return chip->image[CS4231_PLAYBK_FORMAT] & 1 ? 9969 : 9920;
+}
+
+static int snd_wss_timer_start(struct snd_timer *timer)
+{
+	unsigned long flags;
+	unsigned int ticks;
+	struct snd_wss *chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ticks = timer->sticks;
+	if ((chip->image[CS4231_ALT_FEATURE_1] & CS4231_TIMER_ENABLE) == 0 ||
+	    (unsigned char)(ticks >> 8) != chip->image[CS4231_TIMER_HIGH] ||
+	    (unsigned char)ticks != chip->image[CS4231_TIMER_LOW]) {
+		chip->image[CS4231_TIMER_HIGH] = (unsigned char) (ticks >> 8);
+		snd_wss_out(chip, CS4231_TIMER_HIGH,
+			    chip->image[CS4231_TIMER_HIGH]);
+		chip->image[CS4231_TIMER_LOW] = (unsigned char) ticks;
+		snd_wss_out(chip, CS4231_TIMER_LOW,
+			    chip->image[CS4231_TIMER_LOW]);
+		snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+			    chip->image[CS4231_ALT_FEATURE_1] |
+			    CS4231_TIMER_ENABLE);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_wss_timer_stop(struct snd_timer *timer)
+{
+	unsigned long flags;
+	struct snd_wss *chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->image[CS4231_ALT_FEATURE_1] &= ~CS4231_TIMER_ENABLE;
+	snd_wss_out(chip, CS4231_ALT_FEATURE_1,
+		    chip->image[CS4231_ALT_FEATURE_1]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static void snd_wss_init(struct snd_wss *chip)
+{
+	unsigned long flags;
+
+	snd_wss_calibrate_mute(chip, 1);
+	snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk(KERN_DEBUG "init: (1)\n");
+#endif
+	snd_wss_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE |
+					    CS4231_PLAYBACK_PIO |
+					    CS4231_RECORD_ENABLE |
+					    CS4231_RECORD_PIO |
+					    CS4231_CALIB_MODE);
+	chip->image[CS4231_IFACE_CTRL] |= CS4231_AUTOCALIB;
+	snd_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk(KERN_DEBUG "init: (2)\n");
+#endif
+
+	snd_wss_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->image[CS4231_IFACE_CTRL] &= ~CS4231_AUTOCALIB;
+	snd_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+	snd_wss_out(chip,
+		    CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk(KERN_DEBUG "init: (3) - afei = 0x%x\n",
+		   chip->image[CS4231_ALT_FEATURE_1]);
+#endif
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_wss_out(chip, CS4231_ALT_FEATURE_2,
+		    chip->image[CS4231_ALT_FEATURE_2]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	snd_wss_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
+		    chip->image[CS4231_PLAYBK_FORMAT]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk(KERN_DEBUG "init: (4)\n");
+#endif
+
+	snd_wss_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (!(chip->hardware & WSS_HW_AD1848_MASK))
+		snd_wss_out(chip, CS4231_REC_FORMAT,
+			    chip->image[CS4231_REC_FORMAT]);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_down(chip);
+	snd_wss_calibrate_mute(chip, 0);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printk(KERN_DEBUG "init: (5)\n");
+#endif
+}
+
+static int snd_wss_open(struct snd_wss *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	mutex_lock(&chip->open_mutex);
+	if ((chip->mode & mode) ||
+	    ((chip->mode & WSS_MODE_OPEN) && chip->single_dma)) {
+		mutex_unlock(&chip->open_mutex);
+		return -EAGAIN;
+	}
+	if (chip->mode & WSS_MODE_OPEN) {
+		chip->mode |= mode;
+		mutex_unlock(&chip->open_mutex);
+		return 0;
+	}
+	/* ok. now enable and ack CODEC IRQ */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+		snd_wss_out(chip, CS4231_IRQ_STATUS,
+			    CS4231_PLAYBACK_IRQ |
+			    CS4231_RECORD_IRQ |
+			    CS4231_TIMER_IRQ);
+		snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+	}
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	chip->image[CS4231_PIN_CTRL] |= CS4231_IRQ_ENABLE;
+	snd_wss_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);
+	if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+		snd_wss_out(chip, CS4231_IRQ_STATUS,
+			    CS4231_PLAYBACK_IRQ |
+			    CS4231_RECORD_IRQ |
+			    CS4231_TIMER_IRQ);
+		snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	chip->mode = mode;
+	mutex_unlock(&chip->open_mutex);
+	return 0;
+}
+
+static void snd_wss_close(struct snd_wss *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	mutex_lock(&chip->open_mutex);
+	chip->mode &= ~mode;
+	if (chip->mode & WSS_MODE_OPEN) {
+		mutex_unlock(&chip->open_mutex);
+		return;
+	}
+	/* disable IRQ */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (!(chip->hardware & WSS_HW_AD1848_MASK))
+		snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	chip->image[CS4231_PIN_CTRL] &= ~CS4231_IRQ_ENABLE;
+	snd_wss_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);
+
+	/* now disable record & playback */
+
+	if (chip->image[CS4231_IFACE_CTRL] & (CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
+					       CS4231_RECORD_ENABLE | CS4231_RECORD_PIO)) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		snd_wss_mce_up(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
+						     CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
+		snd_wss_out(chip, CS4231_IFACE_CTRL,
+			    chip->image[CS4231_IFACE_CTRL]);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		snd_wss_mce_down(chip);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+	}
+
+	/* clear IRQ again */
+	if (!(chip->hardware & WSS_HW_AD1848_MASK))
+		snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	wss_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	chip->mode = 0;
+	mutex_unlock(&chip->open_mutex);
+}
+
+/*
+ *  timer open/close
+ */
+
+static int snd_wss_timer_open(struct snd_timer *timer)
+{
+	struct snd_wss *chip = snd_timer_chip(timer);
+	snd_wss_open(chip, WSS_MODE_TIMER);
+	return 0;
+}
+
+static int snd_wss_timer_close(struct snd_timer *timer)
+{
+	struct snd_wss *chip = snd_timer_chip(timer);
+	snd_wss_close(chip, WSS_MODE_TIMER);
+	return 0;
+}
+
+static struct snd_timer_hardware snd_wss_timer_table =
+{
+	.flags =	SNDRV_TIMER_HW_AUTO,
+	.resolution =	9945,
+	.ticks =	65535,
+	.open =		snd_wss_timer_open,
+	.close =	snd_wss_timer_close,
+	.c_resolution = snd_wss_timer_resolution,
+	.start =	snd_wss_timer_start,
+	.stop =		snd_wss_timer_stop,
+};
+
+/*
+ *  ok.. exported functions..
+ */
+
+static int snd_wss_playback_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	unsigned char new_pdfr;
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	new_pdfr = snd_wss_get_format(chip, params_format(hw_params),
+				params_channels(hw_params)) |
+				snd_wss_get_rate(params_rate(hw_params));
+	chip->set_playback_format(chip, hw_params, new_pdfr);
+	return 0;
+}
+
+static int snd_wss_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_wss_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long flags;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->p_dma_size = size;
+	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO);
+	snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
+	count = snd_wss_get_count(chip->image[CS4231_PLAYBK_FORMAT], count) - 1;
+	snd_wss_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
+	snd_wss_out(chip, CS4231_PLY_UPR_CNT, (unsigned char) (count >> 8));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+#if 0
+	snd_wss_debug(chip);
+#endif
+	return 0;
+}
+
+static int snd_wss_capture_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	unsigned char new_cdfr;
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	new_cdfr = snd_wss_get_format(chip, params_format(hw_params),
+			   params_channels(hw_params)) |
+			   snd_wss_get_rate(params_rate(hw_params));
+	chip->set_capture_format(chip, hw_params, new_cdfr);
+	return 0;
+}
+
+static int snd_wss_capture_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_wss_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long flags;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->c_dma_size = size;
+	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
+	snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		count = snd_wss_get_count(chip->image[CS4231_PLAYBK_FORMAT],
+					  count);
+	else
+		count = snd_wss_get_count(chip->image[CS4231_REC_FORMAT],
+					  count);
+	count--;
+	if (chip->single_dma && chip->hardware != WSS_HW_INTERWAVE) {
+		snd_wss_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
+		snd_wss_out(chip, CS4231_PLY_UPR_CNT,
+			    (unsigned char) (count >> 8));
+	} else {
+		snd_wss_out(chip, CS4231_REC_LWR_CNT, (unsigned char) count);
+		snd_wss_out(chip, CS4231_REC_UPR_CNT,
+			    (unsigned char) (count >> 8));
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+void snd_wss_overrange(struct snd_wss *chip)
+{
+	unsigned long flags;
+	unsigned char res;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	res = snd_wss_in(chip, CS4231_TEST_INIT);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (res & (0x08 | 0x02))	/* detect overrange only above 0dB; may be user selectable? */
+		chip->capture_substream->runtime->overrange++;
+}
+EXPORT_SYMBOL(snd_wss_overrange);
+
+irqreturn_t snd_wss_interrupt(int irq, void *dev_id)
+{
+	struct snd_wss *chip = dev_id;
+	unsigned char status;
+
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		/* pretend it was the only possible irq for AD1848 */
+		status = CS4231_PLAYBACK_IRQ;
+	else
+		status = snd_wss_in(chip, CS4231_IRQ_STATUS);
+	if (status & CS4231_TIMER_IRQ) {
+		if (chip->timer)
+			snd_timer_interrupt(chip->timer, chip->timer->sticks);
+	}
+	if (chip->single_dma && chip->hardware != WSS_HW_INTERWAVE) {
+		if (status & CS4231_PLAYBACK_IRQ) {
+			if (chip->mode & WSS_MODE_PLAY) {
+				if (chip->playback_substream)
+					snd_pcm_period_elapsed(chip->playback_substream);
+			}
+			if (chip->mode & WSS_MODE_RECORD) {
+				if (chip->capture_substream) {
+					snd_wss_overrange(chip);
+					snd_pcm_period_elapsed(chip->capture_substream);
+				}
+			}
+		}
+	} else {
+		if (status & CS4231_PLAYBACK_IRQ) {
+			if (chip->playback_substream)
+				snd_pcm_period_elapsed(chip->playback_substream);
+		}
+		if (status & CS4231_RECORD_IRQ) {
+			if (chip->capture_substream) {
+				snd_wss_overrange(chip);
+				snd_pcm_period_elapsed(chip->capture_substream);
+			}
+		}
+	}
+
+	spin_lock(&chip->reg_lock);
+	status = ~CS4231_ALL_IRQS | ~status;
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		wss_outb(chip, CS4231P(STATUS), 0);
+	else
+		snd_wss_out(chip, CS4231_IRQ_STATUS, status);
+	spin_unlock(&chip->reg_lock);
+	return IRQ_HANDLED;
+}
+EXPORT_SYMBOL(snd_wss_interrupt);
+
+static snd_pcm_uframes_t snd_wss_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE))
+		return 0;
+	ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_wss_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE))
+		return 0;
+	ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/*
+
+ */
+
+static int snd_ad1848_probe(struct snd_wss *chip)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+	unsigned long flags;
+	unsigned char r;
+	unsigned short hardware = 0;
+	int err = 0;
+	int i;
+
+	while (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) {
+		if (time_after(jiffies, timeout))
+			return -ENODEV;
+		cond_resched();
+	}
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	/* set CS423x MODE 1 */
+	snd_wss_dout(chip, CS4231_MISC_INFO, 0);
+
+	snd_wss_dout(chip, CS4231_RIGHT_INPUT, 0x45); /* 0x55 & ~0x10 */
+	r = snd_wss_in(chip, CS4231_RIGHT_INPUT);
+	if (r != 0x45) {
+		/* RMGE always high on AD1847 */
+		if ((r & ~CS4231_ENABLE_MIC_GAIN) != 0x45) {
+			err = -ENODEV;
+			goto out;
+		}
+		hardware = WSS_HW_AD1847;
+	} else {
+		snd_wss_dout(chip, CS4231_LEFT_INPUT,  0xaa);
+		r = snd_wss_in(chip, CS4231_LEFT_INPUT);
+		/* L/RMGE always low on AT2320 */
+		if ((r | CS4231_ENABLE_MIC_GAIN) != 0xaa) {
+			err = -ENODEV;
+			goto out;
+		}
+	}
+
+	/* clear pending IRQ */
+	wss_inb(chip, CS4231P(STATUS));
+	wss_outb(chip, CS4231P(STATUS), 0);
+	mb();
+
+	if ((chip->hardware & WSS_HW_TYPE_MASK) != WSS_HW_DETECT)
+		goto out;
+
+	if (hardware) {
+		chip->hardware = hardware;
+		goto out;
+	}
+
+	r = snd_wss_in(chip, CS4231_MISC_INFO);
+
+	/* set CS423x MODE 2 */
+	snd_wss_dout(chip, CS4231_MISC_INFO, CS4231_MODE2);
+	for (i = 0; i < 16; i++) {
+		if (snd_wss_in(chip, i) != snd_wss_in(chip, 16 + i)) {
+			/* we have more than 16 registers: check ID */
+			if ((r & 0xf) != 0xa)
+				goto out_mode;
+			/*
+			 * on CMI8330, CS4231_VERSION is volume control and
+			 * can be set to 0
+			 */
+			snd_wss_dout(chip, CS4231_VERSION, 0);
+			r = snd_wss_in(chip, CS4231_VERSION) & 0xe7;
+			if (!r)
+				chip->hardware = WSS_HW_CMI8330;
+			goto out_mode;
+		}
+	}
+	if (r & 0x80)
+		chip->hardware = WSS_HW_CS4248;
+	else
+		chip->hardware = WSS_HW_AD1848;
+out_mode:
+	snd_wss_dout(chip, CS4231_MISC_INFO, 0);
+out:
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return err;
+}
+
+static int snd_wss_probe(struct snd_wss *chip)
+{
+	unsigned long flags;
+	int i, id, rev, regnum;
+	unsigned char *ptr;
+	unsigned int hw;
+
+	id = snd_ad1848_probe(chip);
+	if (id < 0)
+		return id;
+
+	hw = chip->hardware;
+	if ((hw & WSS_HW_TYPE_MASK) == WSS_HW_DETECT) {
+		for (i = 0; i < 50; i++) {
+			mb();
+			if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+				msleep(2);
+			else {
+				spin_lock_irqsave(&chip->reg_lock, flags);
+				snd_wss_out(chip, CS4231_MISC_INFO,
+					    CS4231_MODE2);
+				id = snd_wss_in(chip, CS4231_MISC_INFO) & 0x0f;
+				spin_unlock_irqrestore(&chip->reg_lock, flags);
+				if (id == 0x0a)
+					break;	/* this is valid value */
+			}
+		}
+		snd_printdd("wss: port = 0x%lx, id = 0x%x\n", chip->port, id);
+		if (id != 0x0a)
+			return -ENODEV;	/* no valid device found */
+
+		rev = snd_wss_in(chip, CS4231_VERSION) & 0xe7;
+		snd_printdd("CS4231: VERSION (I25) = 0x%x\n", rev);
+		if (rev == 0x80) {
+			unsigned char tmp = snd_wss_in(chip, 23);
+			snd_wss_out(chip, 23, ~tmp);
+			if (snd_wss_in(chip, 23) != tmp)
+				chip->hardware = WSS_HW_AD1845;
+			else
+				chip->hardware = WSS_HW_CS4231;
+		} else if (rev == 0xa0) {
+			chip->hardware = WSS_HW_CS4231A;
+		} else if (rev == 0xa2) {
+			chip->hardware = WSS_HW_CS4232;
+		} else if (rev == 0xb2) {
+			chip->hardware = WSS_HW_CS4232A;
+		} else if (rev == 0x83) {
+			chip->hardware = WSS_HW_CS4236;
+		} else if (rev == 0x03) {
+			chip->hardware = WSS_HW_CS4236B;
+		} else {
+			snd_printk(KERN_ERR
+				   "unknown CS chip with version 0x%x\n", rev);
+			return -ENODEV;		/* unknown CS4231 chip? */
+		}
+	}
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	wss_inb(chip, CS4231P(STATUS));	/* clear any pendings IRQ */
+	wss_outb(chip, CS4231P(STATUS), 0);
+	mb();
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	if (!(chip->hardware & WSS_HW_AD1848_MASK))
+		chip->image[CS4231_MISC_INFO] = CS4231_MODE2;
+	switch (chip->hardware) {
+	case WSS_HW_INTERWAVE:
+		chip->image[CS4231_MISC_INFO] = CS4231_IW_MODE3;
+		break;
+	case WSS_HW_CS4235:
+	case WSS_HW_CS4236B:
+	case WSS_HW_CS4237B:
+	case WSS_HW_CS4238B:
+	case WSS_HW_CS4239:
+		if (hw == WSS_HW_DETECT3)
+			chip->image[CS4231_MISC_INFO] = CS4231_4236_MODE3;
+		else
+			chip->hardware = WSS_HW_CS4236;
+		break;
+	}
+
+	chip->image[CS4231_IFACE_CTRL] =
+	    (chip->image[CS4231_IFACE_CTRL] & ~CS4231_SINGLE_DMA) |
+	    (chip->single_dma ? CS4231_SINGLE_DMA : 0);
+	if (chip->hardware != WSS_HW_OPTI93X) {
+		chip->image[CS4231_ALT_FEATURE_1] = 0x80;
+		chip->image[CS4231_ALT_FEATURE_2] =
+			chip->hardware == WSS_HW_INTERWAVE ? 0xc2 : 0x01;
+	}
+	/* enable fine grained frequency selection */
+	if (chip->hardware == WSS_HW_AD1845)
+		chip->image[AD1845_PWR_DOWN] = 8;
+
+	ptr = (unsigned char *) &chip->image;
+	regnum = (chip->hardware & WSS_HW_AD1848_MASK) ? 16 : 32;
+	snd_wss_mce_down(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (i = 0; i < regnum; i++)	/* ok.. fill all registers */
+		snd_wss_out(chip, i, *ptr++);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_wss_mce_up(chip);
+	snd_wss_mce_down(chip);
+
+	mdelay(2);
+
+	/* ok.. try check hardware version for CS4236+ chips */
+	if ((hw & WSS_HW_TYPE_MASK) == WSS_HW_DETECT) {
+		if (chip->hardware == WSS_HW_CS4236B) {
+			rev = snd_cs4236_ext_in(chip, CS4236_VERSION);
+			snd_cs4236_ext_out(chip, CS4236_VERSION, 0xff);
+			id = snd_cs4236_ext_in(chip, CS4236_VERSION);
+			snd_cs4236_ext_out(chip, CS4236_VERSION, rev);
+			snd_printdd("CS4231: ext version; rev = 0x%x, id = 0x%x\n", rev, id);
+			if ((id & 0x1f) == 0x1d) {	/* CS4235 */
+				chip->hardware = WSS_HW_CS4235;
+				switch (id >> 5) {
+				case 4:
+				case 5:
+				case 6:
+					break;
+				default:
+					snd_printk(KERN_WARNING
+						"unknown CS4235 chip "
+						"(enhanced version = 0x%x)\n",
+						id);
+				}
+			} else if ((id & 0x1f) == 0x0b) {	/* CS4236/B */
+				switch (id >> 5) {
+				case 4:
+				case 5:
+				case 6:
+				case 7:
+					chip->hardware = WSS_HW_CS4236B;
+					break;
+				default:
+					snd_printk(KERN_WARNING
+						"unknown CS4236 chip "
+						"(enhanced version = 0x%x)\n",
+						id);
+				}
+			} else if ((id & 0x1f) == 0x08) {	/* CS4237B */
+				chip->hardware = WSS_HW_CS4237B;
+				switch (id >> 5) {
+				case 4:
+				case 5:
+				case 6:
+				case 7:
+					break;
+				default:
+					snd_printk(KERN_WARNING
+						"unknown CS4237B chip "
+						"(enhanced version = 0x%x)\n",
+						id);
+				}
+			} else if ((id & 0x1f) == 0x09) {	/* CS4238B */
+				chip->hardware = WSS_HW_CS4238B;
+				switch (id >> 5) {
+				case 5:
+				case 6:
+				case 7:
+					break;
+				default:
+					snd_printk(KERN_WARNING
+						"unknown CS4238B chip "
+						"(enhanced version = 0x%x)\n",
+						id);
+				}
+			} else if ((id & 0x1f) == 0x1e) {	/* CS4239 */
+				chip->hardware = WSS_HW_CS4239;
+				switch (id >> 5) {
+				case 4:
+				case 5:
+				case 6:
+					break;
+				default:
+					snd_printk(KERN_WARNING
+						"unknown CS4239 chip "
+						"(enhanced version = 0x%x)\n",
+						id);
+				}
+			} else {
+				snd_printk(KERN_WARNING
+					   "unknown CS4236/CS423xB chip "
+					   "(enhanced version = 0x%x)\n", id);
+			}
+		}
+	}
+	return 0;		/* all things are ok.. */
+}
+
+/*
+
+ */
+
+static struct snd_pcm_hardware snd_wss_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_SYNC_START),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE),
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5510,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_wss_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_SYNC_START),
+	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM |
+				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE),
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5510,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+
+ */
+
+static int snd_wss_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	runtime->hw = snd_wss_playback;
+
+	/* hardware limitation of older chipsets */
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		runtime->hw.formats &= ~(SNDRV_PCM_FMTBIT_IMA_ADPCM |
+					 SNDRV_PCM_FMTBIT_S16_BE);
+
+	/* hardware bug in InterWave chipset */
+	if (chip->hardware == WSS_HW_INTERWAVE && chip->dma1 > 3)
+		runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_MU_LAW;
+
+	/* hardware limitation of cheap chips */
+	if (chip->hardware == WSS_HW_CS4235 ||
+	    chip->hardware == WSS_HW_CS4239)
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE;
+
+	snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.period_bytes_max);
+
+	if (chip->claim_dma) {
+		if ((err = chip->claim_dma(chip, chip->dma_private_data, chip->dma1)) < 0)
+			return err;
+	}
+
+	err = snd_wss_open(chip, WSS_MODE_PLAY);
+	if (err < 0) {
+		if (chip->release_dma)
+			chip->release_dma(chip, chip->dma_private_data, chip->dma1);
+		snd_free_pages(runtime->dma_area, runtime->dma_bytes);
+		return err;
+	}
+	chip->playback_substream = substream;
+	snd_pcm_set_sync(substream);
+	chip->rate_constraint(runtime);
+	return 0;
+}
+
+static int snd_wss_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	runtime->hw = snd_wss_capture;
+
+	/* hardware limitation of older chipsets */
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		runtime->hw.formats &= ~(SNDRV_PCM_FMTBIT_IMA_ADPCM |
+					 SNDRV_PCM_FMTBIT_S16_BE);
+
+	/* hardware limitation of cheap chips */
+	if (chip->hardware == WSS_HW_CS4235 ||
+	    chip->hardware == WSS_HW_CS4239 ||
+	    chip->hardware == WSS_HW_OPTI93X)
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 |
+				      SNDRV_PCM_FMTBIT_S16_LE;
+
+	snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max);
+	snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max);
+
+	if (chip->claim_dma) {
+		if ((err = chip->claim_dma(chip, chip->dma_private_data, chip->dma2)) < 0)
+			return err;
+	}
+
+	err = snd_wss_open(chip, WSS_MODE_RECORD);
+	if (err < 0) {
+		if (chip->release_dma)
+			chip->release_dma(chip, chip->dma_private_data, chip->dma2);
+		snd_free_pages(runtime->dma_area, runtime->dma_bytes);
+		return err;
+	}
+	chip->capture_substream = substream;
+	snd_pcm_set_sync(substream);
+	chip->rate_constraint(runtime);
+	return 0;
+}
+
+static int snd_wss_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	snd_wss_close(chip, WSS_MODE_PLAY);
+	return 0;
+}
+
+static int snd_wss_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_wss *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	snd_wss_close(chip, WSS_MODE_RECORD);
+	return 0;
+}
+
+static void snd_wss_thinkpad_twiddle(struct snd_wss *chip, int on)
+{
+	int tmp;
+
+	if (!chip->thinkpad_flag)
+		return;
+
+	outb(0x1c, AD1848_THINKPAD_CTL_PORT1);
+	tmp = inb(AD1848_THINKPAD_CTL_PORT2);
+
+	if (on)
+		/* turn it on */
+		tmp |= AD1848_THINKPAD_CS4248_ENABLE_BIT;
+	else
+		/* turn it off */
+		tmp &= ~AD1848_THINKPAD_CS4248_ENABLE_BIT;
+
+	outb(tmp, AD1848_THINKPAD_CTL_PORT2);
+}
+
+#ifdef CONFIG_PM
+
+/* lowlevel suspend callback for CS4231 */
+static void snd_wss_suspend(struct snd_wss *chip)
+{
+	int reg;
+	unsigned long flags;
+
+	snd_pcm_suspend_all(chip->pcm);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (reg = 0; reg < 32; reg++)
+		chip->image[reg] = snd_wss_in(chip, reg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (chip->thinkpad_flag)
+		snd_wss_thinkpad_twiddle(chip, 0);
+}
+
+/* lowlevel resume callback for CS4231 */
+static void snd_wss_resume(struct snd_wss *chip)
+{
+	int reg;
+	unsigned long flags;
+	/* int timeout; */
+
+	if (chip->thinkpad_flag)
+		snd_wss_thinkpad_twiddle(chip, 1);
+	snd_wss_mce_up(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	for (reg = 0; reg < 32; reg++) {
+		switch (reg) {
+		case CS4231_VERSION:
+			break;
+		default:
+			snd_wss_out(chip, reg, chip->image[reg]);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+#if 1
+	snd_wss_mce_down(chip);
+#else
+	/* The following is a workaround to avoid freeze after resume on TP600E.
+	   This is the first half of copy of snd_wss_mce_down(), but doesn't
+	   include rescheduling.  -- iwai
+	   */
+	snd_wss_busy_wait(chip);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->mce_bit &= ~CS4231_MCE;
+	timeout = wss_inb(chip, CS4231P(REGSEL));
+	wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (timeout == 0x80)
+		snd_printk(KERN_ERR "down [0x%lx]: serious init problem "
+			   "- codec still busy\n", chip->port);
+	if ((timeout & CS4231_MCE) == 0 ||
+	    !(chip->hardware & (WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK))) {
+		return;
+	}
+	snd_wss_busy_wait(chip);
+#endif
+}
+#endif /* CONFIG_PM */
+
+static int snd_wss_free(struct snd_wss *chip)
+{
+	release_and_free_resource(chip->res_port);
+	release_and_free_resource(chip->res_cport);
+	if (chip->irq >= 0) {
+		disable_irq(chip->irq);
+		if (!(chip->hwshare & WSS_HWSHARE_IRQ))
+			free_irq(chip->irq, (void *) chip);
+	}
+	if (!(chip->hwshare & WSS_HWSHARE_DMA1) && chip->dma1 >= 0) {
+		snd_dma_disable(chip->dma1);
+		free_dma(chip->dma1);
+	}
+	if (!(chip->hwshare & WSS_HWSHARE_DMA2) &&
+	    chip->dma2 >= 0 && chip->dma2 != chip->dma1) {
+		snd_dma_disable(chip->dma2);
+		free_dma(chip->dma2);
+	}
+	if (chip->timer)
+		snd_device_free(chip->card, chip->timer);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_wss_dev_free(struct snd_device *device)
+{
+	struct snd_wss *chip = device->device_data;
+	return snd_wss_free(chip);
+}
+
+const char *snd_wss_chip_id(struct snd_wss *chip)
+{
+	switch (chip->hardware) {
+	case WSS_HW_CS4231:
+		return "CS4231";
+	case WSS_HW_CS4231A:
+		return "CS4231A";
+	case WSS_HW_CS4232:
+		return "CS4232";
+	case WSS_HW_CS4232A:
+		return "CS4232A";
+	case WSS_HW_CS4235:
+		return "CS4235";
+	case WSS_HW_CS4236:
+		return "CS4236";
+	case WSS_HW_CS4236B:
+		return "CS4236B";
+	case WSS_HW_CS4237B:
+		return "CS4237B";
+	case WSS_HW_CS4238B:
+		return "CS4238B";
+	case WSS_HW_CS4239:
+		return "CS4239";
+	case WSS_HW_INTERWAVE:
+		return "AMD InterWave";
+	case WSS_HW_OPL3SA2:
+		return chip->card->shortname;
+	case WSS_HW_AD1845:
+		return "AD1845";
+	case WSS_HW_OPTI93X:
+		return "OPTi 93x";
+	case WSS_HW_AD1847:
+		return "AD1847";
+	case WSS_HW_AD1848:
+		return "AD1848";
+	case WSS_HW_CS4248:
+		return "CS4248";
+	case WSS_HW_CMI8330:
+		return "CMI8330/C3D";
+	default:
+		return "???";
+	}
+}
+EXPORT_SYMBOL(snd_wss_chip_id);
+
+static int snd_wss_new(struct snd_card *card,
+			  unsigned short hardware,
+			  unsigned short hwshare,
+			  struct snd_wss **rchip)
+{
+	struct snd_wss *chip;
+
+	*rchip = NULL;
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	chip->hardware = hardware;
+	chip->hwshare = hwshare;
+
+	spin_lock_init(&chip->reg_lock);
+	mutex_init(&chip->mce_mutex);
+	mutex_init(&chip->open_mutex);
+	chip->card = card;
+	chip->rate_constraint = snd_wss_xrate;
+	chip->set_playback_format = snd_wss_playback_format;
+	chip->set_capture_format = snd_wss_capture_format;
+	if (chip->hardware == WSS_HW_OPTI93X)
+		memcpy(&chip->image, &snd_opti93x_original_image,
+		       sizeof(snd_opti93x_original_image));
+	else
+		memcpy(&chip->image, &snd_wss_original_image,
+		       sizeof(snd_wss_original_image));
+	if (chip->hardware & WSS_HW_AD1848_MASK) {
+		chip->image[CS4231_PIN_CTRL] = 0;
+		chip->image[CS4231_TEST_INIT] = 0;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+int snd_wss_create(struct snd_card *card,
+		      unsigned long port,
+		      unsigned long cport,
+		      int irq, int dma1, int dma2,
+		      unsigned short hardware,
+		      unsigned short hwshare,
+		      struct snd_wss **rchip)
+{
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_wss_dev_free,
+	};
+	struct snd_wss *chip;
+	int err;
+
+	err = snd_wss_new(card, hardware, hwshare, &chip);
+	if (err < 0)
+		return err;
+
+	chip->irq = -1;
+	chip->dma1 = -1;
+	chip->dma2 = -1;
+
+	chip->res_port = request_region(port, 4, "WSS");
+	if (!chip->res_port) {
+		snd_printk(KERN_ERR "wss: can't grab port 0x%lx\n", port);
+		snd_wss_free(chip);
+		return -EBUSY;
+	}
+	chip->port = port;
+	if ((long)cport >= 0) {
+		chip->res_cport = request_region(cport, 8, "CS4232 Control");
+		if (!chip->res_cport) {
+			snd_printk(KERN_ERR
+				"wss: can't grab control port 0x%lx\n", cport);
+			snd_wss_free(chip);
+			return -ENODEV;
+		}
+	}
+	chip->cport = cport;
+	if (!(hwshare & WSS_HWSHARE_IRQ))
+		if (request_irq(irq, snd_wss_interrupt, IRQF_DISABLED,
+				"WSS", (void *) chip)) {
+			snd_printk(KERN_ERR "wss: can't grab IRQ %d\n", irq);
+			snd_wss_free(chip);
+			return -EBUSY;
+		}
+	chip->irq = irq;
+	if (!(hwshare & WSS_HWSHARE_DMA1) && request_dma(dma1, "WSS - 1")) {
+		snd_printk(KERN_ERR "wss: can't grab DMA1 %d\n", dma1);
+		snd_wss_free(chip);
+		return -EBUSY;
+	}
+	chip->dma1 = dma1;
+	if (!(hwshare & WSS_HWSHARE_DMA2) && dma1 != dma2 &&
+	      dma2 >= 0 && request_dma(dma2, "WSS - 2")) {
+		snd_printk(KERN_ERR "wss: can't grab DMA2 %d\n", dma2);
+		snd_wss_free(chip);
+		return -EBUSY;
+	}
+	if (dma1 == dma2 || dma2 < 0) {
+		chip->single_dma = 1;
+		chip->dma2 = chip->dma1;
+	} else
+		chip->dma2 = dma2;
+
+	if (hardware == WSS_HW_THINKPAD) {
+		chip->thinkpad_flag = 1;
+		chip->hardware = WSS_HW_DETECT; /* reset */
+		snd_wss_thinkpad_twiddle(chip, 1);
+	}
+
+	/* global setup */
+	if (snd_wss_probe(chip) < 0) {
+		snd_wss_free(chip);
+		return -ENODEV;
+	}
+	snd_wss_init(chip);
+
+#if 0
+	if (chip->hardware & WSS_HW_CS4232_MASK) {
+		if (chip->res_cport == NULL)
+			snd_printk(KERN_ERR "CS4232 control port features are "
+				   "not accessible\n");
+	}
+#endif
+
+	/* Register device */
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		snd_wss_free(chip);
+		return err;
+	}
+
+#ifdef CONFIG_PM
+	/* Power Management */
+	chip->suspend = snd_wss_suspend;
+	chip->resume = snd_wss_resume;
+#endif
+
+	*rchip = chip;
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_create);
+
+static struct snd_pcm_ops snd_wss_playback_ops = {
+	.open =		snd_wss_playback_open,
+	.close =	snd_wss_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_wss_playback_hw_params,
+	.hw_free =	snd_wss_playback_hw_free,
+	.prepare =	snd_wss_playback_prepare,
+	.trigger =	snd_wss_trigger,
+	.pointer =	snd_wss_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_wss_capture_ops = {
+	.open =		snd_wss_capture_open,
+	.close =	snd_wss_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_wss_capture_hw_params,
+	.hw_free =	snd_wss_capture_hw_free,
+	.prepare =	snd_wss_capture_prepare,
+	.trigger =	snd_wss_trigger,
+	.pointer =	snd_wss_capture_pointer,
+};
+
+int snd_wss_pcm(struct snd_wss *chip, int device, struct snd_pcm **rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(chip->card, "WSS", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_wss_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_wss_capture_ops);
+
+	/* global setup */
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	if (chip->single_dma)
+		pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+	if (chip->hardware != WSS_HW_INTERWAVE)
+		pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX;
+	strcpy(pcm->name, snd_wss_chip_id(chip));
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_isa_data(),
+					      64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);
+
+	chip->pcm = pcm;
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_pcm);
+
+static void snd_wss_timer_free(struct snd_timer *timer)
+{
+	struct snd_wss *chip = timer->private_data;
+	chip->timer = NULL;
+}
+
+int snd_wss_timer(struct snd_wss *chip, int device, struct snd_timer **rtimer)
+{
+	struct snd_timer *timer;
+	struct snd_timer_id tid;
+	int err;
+
+	/* Timer initialization */
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = chip->card->number;
+	tid.device = device;
+	tid.subdevice = 0;
+	if ((err = snd_timer_new(chip->card, "CS4231", &tid, &timer)) < 0)
+		return err;
+	strcpy(timer->name, snd_wss_chip_id(chip));
+	timer->private_data = chip;
+	timer->private_free = snd_wss_timer_free;
+	timer->hw = snd_wss_timer_table;
+	chip->timer = timer;
+	if (rtimer)
+		*rtimer = timer;
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_timer);
+
+/*
+ *  MIXER part
+ */
+
+static int snd_wss_info_mux(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = {
+		"Line", "Aux", "Mic", "Mix"
+	};
+	static char *opl3sa_texts[4] = {
+		"Line", "CD", "Mic", "Mix"
+	};
+	static char *gusmax_texts[4] = {
+		"Line", "Synth", "Mic", "Mix"
+	};
+	char **ptexts = texts;
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+
+	if (snd_BUG_ON(!chip->card))
+		return -EINVAL;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 2;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3)
+		uinfo->value.enumerated.item = 3;
+	if (!strcmp(chip->card->driver, "GUS MAX"))
+		ptexts = gusmax_texts;
+	switch (chip->hardware) {
+	case WSS_HW_INTERWAVE:
+		ptexts = gusmax_texts;
+		break;
+	case WSS_HW_OPTI93X:
+	case WSS_HW_OPL3SA2:
+		ptexts = opl3sa_texts;
+		break;
+	}
+	strcpy(uinfo->value.enumerated.name, ptexts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_wss_get_mux(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.enumerated.item[0] = (chip->image[CS4231_LEFT_INPUT] & CS4231_MIXS_ALL) >> 6;
+	ucontrol->value.enumerated.item[1] = (chip->image[CS4231_RIGHT_INPUT] & CS4231_MIXS_ALL) >> 6;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_wss_put_mux(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned short left, right;
+	int change;
+
+	if (ucontrol->value.enumerated.item[0] > 3 ||
+	    ucontrol->value.enumerated.item[1] > 3)
+		return -EINVAL;
+	left = ucontrol->value.enumerated.item[0] << 6;
+	right = ucontrol->value.enumerated.item[1] << 6;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	left = (chip->image[CS4231_LEFT_INPUT] & ~CS4231_MIXS_ALL) | left;
+	right = (chip->image[CS4231_RIGHT_INPUT] & ~CS4231_MIXS_ALL) | right;
+	change = left != chip->image[CS4231_LEFT_INPUT] ||
+		 right != chip->image[CS4231_RIGHT_INPUT];
+	snd_wss_out(chip, CS4231_LEFT_INPUT, left);
+	snd_wss_out(chip, CS4231_RIGHT_INPUT, right);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+int snd_wss_info_single(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_info_single);
+
+int snd_wss_get_single(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_get_single);
+
+int snd_wss_put_single(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val;
+
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = (chip->image[reg] & ~(mask << shift)) | val;
+	change = val != chip->image[reg];
+	snd_wss_out(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+EXPORT_SYMBOL(snd_wss_put_single);
+
+int snd_wss_info_double(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_info_double);
+
+int snd_wss_get_double(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (chip->image[right_reg] >> shift_right) & mask;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_get_double);
+
+int snd_wss_put_double(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2;
+
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (left_reg != right_reg) {
+		val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
+		val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2;
+		change = val1 != chip->image[left_reg] ||
+			 val2 != chip->image[right_reg];
+		snd_wss_out(chip, left_reg, val1);
+		snd_wss_out(chip, right_reg, val2);
+	} else {
+		mask = (mask << shift_left) | (mask << shift_right);
+		val1 = (chip->image[left_reg] & ~mask) | val1 | val2;
+		change = val1 != chip->image[left_reg];
+		snd_wss_out(chip, left_reg, val1);
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+EXPORT_SYMBOL(snd_wss_put_double);
+
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
+
+static struct snd_kcontrol_new snd_wss_controls[] = {
+WSS_DOUBLE("PCM Playback Switch", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("PCM Playback Volume", 0,
+		CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1,
+		db_scale_6bit),
+WSS_DOUBLE("Aux Playback Switch", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Aux Playback Volume", 0,
+		CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_DOUBLE("Aux Playback Switch", 1,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Aux Playback Volume", 1,
+		CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_DOUBLE_TLV("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT,
+		0, 0, 15, 0, db_scale_rec_gain),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_wss_info_mux,
+	.get = snd_wss_get_mux,
+	.put = snd_wss_put_mux,
+},
+WSS_DOUBLE("Mic Boost (+20dB)", 0,
+		CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 5, 5, 1, 0),
+WSS_SINGLE("Loopback Capture Switch", 0,
+		CS4231_LOOPBACK, 0, 1, 0),
+WSS_SINGLE_TLV("Loopback Capture Volume", 0, CS4231_LOOPBACK, 2, 63, 1,
+		db_scale_6bit),
+WSS_DOUBLE("Line Playback Switch", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Line Playback Volume", 0,
+		CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1,
+		db_scale_5bit_12db_max),
+WSS_SINGLE("Beep Playback Switch", 0,
+		CS4231_MONO_CTRL, 7, 1, 1),
+WSS_SINGLE_TLV("Beep Playback Volume", 0,
+		CS4231_MONO_CTRL, 0, 15, 1,
+		db_scale_4bit),
+WSS_SINGLE("Mono Output Playback Switch", 0,
+		CS4231_MONO_CTRL, 6, 1, 1),
+WSS_SINGLE("Beep Bypass Playback Switch", 0,
+		CS4231_MONO_CTRL, 5, 1, 0),
+};
+
+int snd_wss_mixer(struct snd_wss *chip)
+{
+	struct snd_card *card;
+	unsigned int idx;
+	int err;
+	int count = ARRAY_SIZE(snd_wss_controls);
+
+	if (snd_BUG_ON(!chip || !chip->pcm))
+		return -EINVAL;
+
+	card = chip->card;
+
+	strcpy(card->mixername, chip->pcm->name);
+
+	/* Use only the first 11 entries on AD1848 */
+	if (chip->hardware & WSS_HW_AD1848_MASK)
+		count = 11;
+	/* There is no loopback on OPTI93X */
+	else if (chip->hardware == WSS_HW_OPTI93X)
+		count = 9;
+
+	for (idx = 0; idx < count; idx++) {
+		err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_wss_controls[idx],
+					     chip));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(snd_wss_mixer);
+
+const struct snd_pcm_ops *snd_wss_get_pcm_ops(int direction)
+{
+	return direction == SNDRV_PCM_STREAM_PLAYBACK ?
+		&snd_wss_playback_ops : &snd_wss_capture_ops;
+}
+EXPORT_SYMBOL(snd_wss_get_pcm_ops);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_wss_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_wss_exit(void)
+{
+}
+
+module_init(alsa_wss_init);
+module_exit(alsa_wss_exit);
diff --git a/linux/sound/last.c b/linux/sound/last.c
new file mode 100644
index 0000000..bdd0857
--- /dev/null
+++ b/linux/sound/last.c
@@ -0,0 +1,41 @@
+/*
+ *  Advanced Linux Sound Architecture
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#define SNDRV_MAIN_OBJECT_FILE
+#include <linux/init.h>
+#include <sound/core.h>
+
+static int __init alsa_sound_last_init(void)
+{
+	int idx, ok = 0;
+	
+	printk(KERN_INFO "ALSA device list:\n");
+	for (idx = 0; idx < SNDRV_CARDS; idx++)
+		if (snd_cards[idx] != NULL) {
+			printk(KERN_INFO "  #%i: %s\n", idx, snd_cards[idx]->longname);
+			ok++;
+		}
+	if (ok == 0)
+		printk(KERN_INFO "  No soundcards found.\n");
+	return 0;
+}
+
+__initcall(alsa_sound_last_init);
diff --git a/linux/sound/mips/Kconfig b/linux/sound/mips/Kconfig
new file mode 100644
index 0000000..a9823fa
--- /dev/null
+++ b/linux/sound/mips/Kconfig
@@ -0,0 +1,34 @@
+# ALSA MIPS drivers
+
+menuconfig SND_MIPS
+	bool "MIPS sound devices"
+	depends on MIPS
+	default y
+	help
+	  Support for sound devices of MIPS architectures.
+
+if SND_MIPS
+
+config SND_SGI_O2
+	tristate "SGI O2 Audio"
+	depends on SGI_IP32
+        help
+                Sound support for the SGI O2 Workstation. 
+
+config SND_SGI_HAL2
+        tristate "SGI HAL2 Audio"
+        depends on SGI_HAS_HAL2
+        help
+                Sound support for the SGI Indy and Indigo2 Workstation.
+
+
+config SND_AU1X00
+	tristate "Au1x00 AC97 Port Driver"
+	depends on SOC_AU1000 || SOC_AU1100 || SOC_AU1500
+	select SND_PCM
+	select SND_AC97_CODEC
+	help
+	  ALSA Sound driver for the Au1x00's AC97 port.
+
+endif	# SND_MIPS
+
diff --git a/linux/sound/mips/Makefile b/linux/sound/mips/Makefile
new file mode 100644
index 0000000..861ec0a
--- /dev/null
+++ b/linux/sound/mips/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for ALSA
+#
+
+snd-au1x00-objs := au1x00.o
+snd-sgi-o2-objs := sgio2audio.o ad1843.o
+snd-sgi-hal2-objs := hal2.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AU1X00) += snd-au1x00.o
+obj-$(CONFIG_SND_SGI_O2) += snd-sgi-o2.o
+obj-$(CONFIG_SND_SGI_HAL2) += snd-sgi-hal2.o
diff --git a/linux/sound/mips/ad1843.c b/linux/sound/mips/ad1843.c
new file mode 100644
index 0000000..c624510
--- /dev/null
+++ b/linux/sound/mips/ad1843.c
@@ -0,0 +1,561 @@
+/*
+ *   AD1843 low level driver
+ *
+ *   Copyright 2003 Vivien Chappelier <vivien.chappelier@linux-mips.org>
+ *   Copyright 2008 Thomas Bogendoerfer <tsbogend@alpha.franken.de>
+ *
+ *   inspired from vwsnd.c (SGI VW audio driver)
+ *     Copyright 1999 Silicon Graphics, Inc.  All rights reserved.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ad1843.h>
+
+/*
+ * AD1843 bitfield definitions.  All are named as in the AD1843 data
+ * sheet, with ad1843_ prepended and individual bit numbers removed.
+ *
+ * E.g., bits LSS0 through LSS2 become ad1843_LSS.
+ *
+ * Only the bitfields we need are defined.
+ */
+
+struct ad1843_bitfield {
+	char reg;
+	char lo_bit;
+	char nbits;
+};
+
+static const struct ad1843_bitfield
+	ad1843_PDNO   = {  0, 14,  1 },	/* Converter Power-Down Flag */
+	ad1843_INIT   = {  0, 15,  1 },	/* Clock Initialization Flag */
+	ad1843_RIG    = {  2,  0,  4 },	/* Right ADC Input Gain */
+	ad1843_RMGE   = {  2,  4,  1 },	/* Right ADC Mic Gain Enable */
+	ad1843_RSS    = {  2,  5,  3 },	/* Right ADC Source Select */
+	ad1843_LIG    = {  2,  8,  4 },	/* Left ADC Input Gain */
+	ad1843_LMGE   = {  2, 12,  1 },	/* Left ADC Mic Gain Enable */
+	ad1843_LSS    = {  2, 13,  3 },	/* Left ADC Source Select */
+	ad1843_RD2M   = {  3,  0,  5 },	/* Right DAC 2 Mix Gain/Atten */
+	ad1843_RD2MM  = {  3,  7,  1 },	/* Right DAC 2 Mix Mute */
+	ad1843_LD2M   = {  3,  8,  5 },	/* Left DAC 2 Mix Gain/Atten */
+	ad1843_LD2MM  = {  3, 15,  1 },	/* Left DAC 2 Mix Mute */
+	ad1843_RX1M   = {  4,  0,  5 },	/* Right Aux 1 Mix Gain/Atten */
+	ad1843_RX1MM  = {  4,  7,  1 },	/* Right Aux 1 Mix Mute */
+	ad1843_LX1M   = {  4,  8,  5 },	/* Left Aux 1 Mix Gain/Atten */
+	ad1843_LX1MM  = {  4, 15,  1 },	/* Left Aux 1 Mix Mute */
+	ad1843_RX2M   = {  5,  0,  5 },	/* Right Aux 2 Mix Gain/Atten */
+	ad1843_RX2MM  = {  5,  7,  1 },	/* Right Aux 2 Mix Mute */
+	ad1843_LX2M   = {  5,  8,  5 },	/* Left Aux 2 Mix Gain/Atten */
+	ad1843_LX2MM  = {  5, 15,  1 },	/* Left Aux 2 Mix Mute */
+	ad1843_RMCM   = {  7,  0,  5 },	/* Right Mic Mix Gain/Atten */
+	ad1843_RMCMM  = {  7,  7,  1 },	/* Right Mic Mix Mute */
+	ad1843_LMCM   = {  7,  8,  5 },	/* Left Mic Mix Gain/Atten */
+	ad1843_LMCMM  = {  7, 15,  1 },	/* Left Mic Mix Mute */
+	ad1843_HPOS   = {  8,  4,  1 },	/* Headphone Output Voltage Swing */
+	ad1843_HPOM   = {  8,  5,  1 },	/* Headphone Output Mute */
+	ad1843_MPOM   = {  8,  6,  1 },	/* Mono Output Mute */
+	ad1843_RDA1G  = {  9,  0,  6 },	/* Right DAC1 Analog/Digital Gain */
+	ad1843_RDA1GM = {  9,  7,  1 },	/* Right DAC1 Analog Mute */
+	ad1843_LDA1G  = {  9,  8,  6 },	/* Left DAC1 Analog/Digital Gain */
+	ad1843_LDA1GM = {  9, 15,  1 },	/* Left DAC1 Analog Mute */
+	ad1843_RDA2G  = { 10,  0,  6 },	/* Right DAC2 Analog/Digital Gain */
+	ad1843_RDA2GM = { 10,  7,  1 },	/* Right DAC2 Analog Mute */
+	ad1843_LDA2G  = { 10,  8,  6 },	/* Left DAC2 Analog/Digital Gain */
+	ad1843_LDA2GM = { 10, 15,  1 },	/* Left DAC2 Analog Mute */
+	ad1843_RDA1AM = { 11,  7,  1 },	/* Right DAC1 Digital Mute */
+	ad1843_LDA1AM = { 11, 15,  1 },	/* Left DAC1 Digital Mute */
+	ad1843_RDA2AM = { 12,  7,  1 },	/* Right DAC2 Digital Mute */
+	ad1843_LDA2AM = { 12, 15,  1 },	/* Left DAC2 Digital Mute */
+	ad1843_ADLC   = { 15,  0,  2 },	/* ADC Left Sample Rate Source */
+	ad1843_ADRC   = { 15,  2,  2 },	/* ADC Right Sample Rate Source */
+	ad1843_DA1C   = { 15,  8,  2 },	/* DAC1 Sample Rate Source */
+	ad1843_DA2C   = { 15, 10,  2 },	/* DAC2 Sample Rate Source */
+	ad1843_C1C    = { 17,  0, 16 },	/* Clock 1 Sample Rate Select */
+	ad1843_C2C    = { 20,  0, 16 },	/* Clock 2 Sample Rate Select */
+	ad1843_C3C    = { 23,  0, 16 },	/* Clock 3 Sample Rate Select */
+	ad1843_DAADL  = { 25,  4,  2 },	/* Digital ADC Left Source Select */
+	ad1843_DAADR  = { 25,  6,  2 },	/* Digital ADC Right Source Select */
+	ad1843_DAMIX  = { 25, 14,  1 },	/* DAC Digital Mix Enable */
+	ad1843_DRSFLT = { 25, 15,  1 },	/* Digital Reampler Filter Mode */
+	ad1843_ADLF   = { 26,  0,  2 }, /* ADC Left Channel Data Format */
+	ad1843_ADRF   = { 26,  2,  2 }, /* ADC Right Channel Data Format */
+	ad1843_ADTLK  = { 26,  4,  1 },	/* ADC Transmit Lock Mode Select */
+	ad1843_SCF    = { 26,  7,  1 },	/* SCLK Frequency Select */
+	ad1843_DA1F   = { 26,  8,  2 },	/* DAC1 Data Format Select */
+	ad1843_DA2F   = { 26, 10,  2 },	/* DAC2 Data Format Select */
+	ad1843_DA1SM  = { 26, 14,  1 },	/* DAC1 Stereo/Mono Mode Select */
+	ad1843_DA2SM  = { 26, 15,  1 },	/* DAC2 Stereo/Mono Mode Select */
+	ad1843_ADLEN  = { 27,  0,  1 },	/* ADC Left Channel Enable */
+	ad1843_ADREN  = { 27,  1,  1 },	/* ADC Right Channel Enable */
+	ad1843_AAMEN  = { 27,  4,  1 },	/* Analog to Analog Mix Enable */
+	ad1843_ANAEN  = { 27,  7,  1 },	/* Analog Channel Enable */
+	ad1843_DA1EN  = { 27,  8,  1 },	/* DAC1 Enable */
+	ad1843_DA2EN  = { 27,  9,  1 },	/* DAC2 Enable */
+	ad1843_DDMEN  = { 27, 12,  1 },	/* DAC2 to DAC1 Mix  Enable */
+	ad1843_C1EN   = { 28, 11,  1 },	/* Clock Generator 1 Enable */
+	ad1843_C2EN   = { 28, 12,  1 },	/* Clock Generator 2 Enable */
+	ad1843_C3EN   = { 28, 13,  1 },	/* Clock Generator 3 Enable */
+	ad1843_PDNI   = { 28, 15,  1 };	/* Converter Power Down */
+
+/*
+ * The various registers of the AD1843 use three different formats for
+ * specifying gain.  The ad1843_gain structure parameterizes the
+ * formats.
+ */
+
+struct ad1843_gain {
+	int	negative;		/* nonzero if gain is negative. */
+	const struct ad1843_bitfield *lfield;
+	const struct ad1843_bitfield *rfield;
+	const struct ad1843_bitfield *lmute;
+	const struct ad1843_bitfield *rmute;
+};
+
+static const struct ad1843_gain ad1843_gain_RECLEV = {
+	.negative = 0,
+	.lfield   = &ad1843_LIG,
+	.rfield   = &ad1843_RIG
+};
+static const struct ad1843_gain ad1843_gain_LINE = {
+	.negative = 1,
+	.lfield   = &ad1843_LX1M,
+	.rfield   = &ad1843_RX1M,
+	.lmute    = &ad1843_LX1MM,
+	.rmute    = &ad1843_RX1MM
+};
+static const struct ad1843_gain ad1843_gain_LINE_2 = {
+	.negative = 1,
+	.lfield   = &ad1843_LDA2G,
+	.rfield   = &ad1843_RDA2G,
+	.lmute    = &ad1843_LDA2GM,
+	.rmute    = &ad1843_RDA2GM
+};
+static const struct ad1843_gain ad1843_gain_MIC = {
+	.negative = 1,
+	.lfield   = &ad1843_LMCM,
+	.rfield   = &ad1843_RMCM,
+	.lmute    = &ad1843_LMCMM,
+	.rmute    = &ad1843_RMCMM
+};
+static const struct ad1843_gain ad1843_gain_PCM_0 = {
+	.negative = 1,
+	.lfield   = &ad1843_LDA1G,
+	.rfield   = &ad1843_RDA1G,
+	.lmute    = &ad1843_LDA1GM,
+	.rmute    = &ad1843_RDA1GM
+};
+static const struct ad1843_gain ad1843_gain_PCM_1 = {
+	.negative = 1,
+	.lfield   = &ad1843_LD2M,
+	.rfield   = &ad1843_RD2M,
+	.lmute    = &ad1843_LD2MM,
+	.rmute    = &ad1843_RD2MM
+};
+
+static const struct ad1843_gain *ad1843_gain[AD1843_GAIN_SIZE] =
+{
+	&ad1843_gain_RECLEV,
+	&ad1843_gain_LINE,
+	&ad1843_gain_LINE_2,
+	&ad1843_gain_MIC,
+	&ad1843_gain_PCM_0,
+	&ad1843_gain_PCM_1,
+};
+
+/* read the current value of an AD1843 bitfield. */
+
+static int ad1843_read_bits(struct snd_ad1843 *ad1843,
+			    const struct ad1843_bitfield *field)
+{
+	int w;
+
+	w = ad1843->read(ad1843->chip, field->reg);
+	return w >> field->lo_bit & ((1 << field->nbits) - 1);
+}
+
+/*
+ * write a new value to an AD1843 bitfield and return the old value.
+ */
+
+static int ad1843_write_bits(struct snd_ad1843 *ad1843,
+			     const struct ad1843_bitfield *field,
+			     int newval)
+{
+	int w, mask, oldval, newbits;
+
+	w = ad1843->read(ad1843->chip, field->reg);
+	mask = ((1 << field->nbits) - 1) << field->lo_bit;
+	oldval = (w & mask) >> field->lo_bit;
+	newbits = (newval << field->lo_bit) & mask;
+	w = (w & ~mask) | newbits;
+	ad1843->write(ad1843->chip, field->reg, w);
+
+	return oldval;
+}
+
+/*
+ * ad1843_read_multi reads multiple bitfields from the same AD1843
+ * register.  It uses a single read cycle to do it.  (Reading the
+ * ad1843 requires 256 bit times at 12.288 MHz, or nearly 20
+ * microseconds.)
+ *
+ * Called like this.
+ *
+ *  ad1843_read_multi(ad1843, nfields,
+ *		      &ad1843_FIELD1, &val1,
+ *		      &ad1843_FIELD2, &val2, ...);
+ */
+
+static void ad1843_read_multi(struct snd_ad1843 *ad1843, int argcount, ...)
+{
+	va_list ap;
+	const struct ad1843_bitfield *fp;
+	int w = 0, mask, *value, reg = -1;
+
+	va_start(ap, argcount);
+	while (--argcount >= 0) {
+		fp = va_arg(ap, const struct ad1843_bitfield *);
+		value = va_arg(ap, int *);
+		if (reg == -1) {
+			reg = fp->reg;
+			w = ad1843->read(ad1843->chip, reg);
+		}
+
+		mask = (1 << fp->nbits) - 1;
+		*value = w >> fp->lo_bit & mask;
+	}
+	va_end(ap);
+}
+
+/*
+ * ad1843_write_multi stores multiple bitfields into the same AD1843
+ * register.  It uses one read and one write cycle to do it.
+ *
+ * Called like this.
+ *
+ *  ad1843_write_multi(ad1843, nfields,
+ *		       &ad1843_FIELD1, val1,
+ *		       &ad1843_FIELF2, val2, ...);
+ */
+
+static void ad1843_write_multi(struct snd_ad1843 *ad1843, int argcount, ...)
+{
+	va_list ap;
+	int reg;
+	const struct ad1843_bitfield *fp;
+	int value;
+	int w, m, mask, bits;
+
+	mask = 0;
+	bits = 0;
+	reg = -1;
+
+	va_start(ap, argcount);
+	while (--argcount >= 0) {
+		fp = va_arg(ap, const struct ad1843_bitfield *);
+		value = va_arg(ap, int);
+		if (reg == -1)
+			reg = fp->reg;
+		else
+			BUG_ON(reg != fp->reg);
+		m = ((1 << fp->nbits) - 1) << fp->lo_bit;
+		mask |= m;
+		bits |= (value << fp->lo_bit) & m;
+	}
+	va_end(ap);
+
+	if (~mask & 0xFFFF)
+		w = ad1843->read(ad1843->chip, reg);
+	else
+		w = 0;
+	w = (w & ~mask) | bits;
+	ad1843->write(ad1843->chip, reg, w);
+}
+
+int ad1843_get_gain_max(struct snd_ad1843 *ad1843, int id)
+{
+	const struct ad1843_gain *gp = ad1843_gain[id];
+	int ret;
+
+	ret = (1 << gp->lfield->nbits);
+	if (!gp->lmute)
+		ret -= 1;
+	return ret;
+}
+
+/*
+ * ad1843_get_gain reads the specified register and extracts the gain value
+ * using the supplied gain type.
+ */
+
+int ad1843_get_gain(struct snd_ad1843 *ad1843, int id)
+{
+	int lg, rg, lm, rm;
+	const struct ad1843_gain *gp = ad1843_gain[id];
+	unsigned short mask = (1 << gp->lfield->nbits) - 1;
+
+	ad1843_read_multi(ad1843, 2, gp->lfield, &lg, gp->rfield, &rg);
+	if (gp->negative) {
+		lg = mask - lg;
+		rg = mask - rg;
+	}
+	if (gp->lmute) {
+		ad1843_read_multi(ad1843, 2, gp->lmute, &lm, gp->rmute, &rm);
+		if (lm)
+			lg = 0;
+		if (rm)
+			rg = 0;
+	}
+	return lg << 0 | rg << 8;
+}
+
+/*
+ * Set an audio channel's gain.
+ *
+ * Returns the new gain, which may be lower than the old gain.
+ */
+
+int ad1843_set_gain(struct snd_ad1843 *ad1843, int id, int newval)
+{
+	const struct ad1843_gain *gp = ad1843_gain[id];
+	unsigned short mask = (1 << gp->lfield->nbits) - 1;
+
+	int lg = (newval >> 0) & mask;
+	int rg = (newval >> 8) & mask;
+	int lm = (lg == 0) ? 1 : 0;
+	int rm = (rg == 0) ? 1 : 0;
+
+	if (gp->negative) {
+		lg = mask - lg;
+		rg = mask - rg;
+	}
+	if (gp->lmute)
+		ad1843_write_multi(ad1843, 2, gp->lmute, lm, gp->rmute, rm);
+	ad1843_write_multi(ad1843, 2, gp->lfield, lg, gp->rfield, rg);
+	return ad1843_get_gain(ad1843, id);
+}
+
+/* Returns the current recording source */
+
+int ad1843_get_recsrc(struct snd_ad1843 *ad1843)
+{
+	int val = ad1843_read_bits(ad1843, &ad1843_LSS);
+
+	if (val < 0 || val > 2) {
+		val = 2;
+		ad1843_write_multi(ad1843, 2,
+				   &ad1843_LSS, val, &ad1843_RSS, val);
+	}
+	return val;
+}
+
+/*
+ * Set recording source.
+ *
+ * Returns newsrc on success, -errno on failure.
+ */
+
+int ad1843_set_recsrc(struct snd_ad1843 *ad1843, int newsrc)
+{
+	if (newsrc < 0 || newsrc > 2)
+		return -EINVAL;
+
+	ad1843_write_multi(ad1843, 2, &ad1843_LSS, newsrc, &ad1843_RSS, newsrc);
+	return newsrc;
+}
+
+/* Setup ad1843 for D/A conversion. */
+
+void ad1843_setup_dac(struct snd_ad1843 *ad1843,
+		      unsigned int id,
+		      unsigned int framerate,
+		      snd_pcm_format_t fmt,
+		      unsigned int channels)
+{
+	int ad_fmt = 0, ad_mode = 0;
+
+	switch (fmt) {
+	case SNDRV_PCM_FORMAT_S8:
+		ad_fmt = 0;
+		break;
+	case SNDRV_PCM_FORMAT_U8:
+		ad_fmt = 0;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		ad_fmt = 1;
+		break;
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		ad_fmt = 2;
+		break;
+	case SNDRV_PCM_FORMAT_A_LAW:
+		ad_fmt = 3;
+		break;
+	default:
+		break;
+	}
+
+	switch (channels) {
+	case 2:
+		ad_mode = 0;
+		break;
+	case 1:
+		ad_mode = 1;
+		break;
+	default:
+		break;
+	}
+
+	if (id) {
+		ad1843_write_bits(ad1843, &ad1843_C2C, framerate);
+		ad1843_write_multi(ad1843, 2,
+				   &ad1843_DA2SM, ad_mode,
+				   &ad1843_DA2F, ad_fmt);
+	} else {
+		ad1843_write_bits(ad1843, &ad1843_C1C, framerate);
+		ad1843_write_multi(ad1843, 2,
+				   &ad1843_DA1SM, ad_mode,
+				   &ad1843_DA1F, ad_fmt);
+	}
+}
+
+void ad1843_shutdown_dac(struct snd_ad1843 *ad1843, unsigned int id)
+{
+	if (id)
+		ad1843_write_bits(ad1843, &ad1843_DA2F, 1);
+	else
+		ad1843_write_bits(ad1843, &ad1843_DA1F, 1);
+}
+
+void ad1843_setup_adc(struct snd_ad1843 *ad1843,
+		      unsigned int framerate,
+		      snd_pcm_format_t fmt,
+		      unsigned int channels)
+{
+	int da_fmt = 0;
+
+	switch (fmt) {
+	case SNDRV_PCM_FORMAT_S8:	da_fmt = 0; break;
+	case SNDRV_PCM_FORMAT_U8:	da_fmt = 0; break;
+	case SNDRV_PCM_FORMAT_S16_LE:	da_fmt = 1; break;
+	case SNDRV_PCM_FORMAT_MU_LAW:	da_fmt = 2; break;
+	case SNDRV_PCM_FORMAT_A_LAW:	da_fmt = 3; break;
+	default:		break;
+	}
+
+	ad1843_write_bits(ad1843, &ad1843_C3C, framerate);
+	ad1843_write_multi(ad1843, 2,
+			   &ad1843_ADLF, da_fmt, &ad1843_ADRF, da_fmt);
+}
+
+void ad1843_shutdown_adc(struct snd_ad1843 *ad1843)
+{
+	/* nothing to do */
+}
+
+/*
+ * Fully initialize the ad1843.  As described in the AD1843 data
+ * sheet, section "START-UP SEQUENCE".  The numbered comments are
+ * subsection headings from the data sheet.  See the data sheet, pages
+ * 52-54, for more info.
+ *
+ * return 0 on success, -errno on failure.  */
+
+int ad1843_init(struct snd_ad1843 *ad1843)
+{
+	unsigned long later;
+
+	if (ad1843_read_bits(ad1843, &ad1843_INIT) != 0) {
+		printk(KERN_ERR "ad1843: AD1843 won't initialize\n");
+		return -EIO;
+	}
+
+	ad1843_write_bits(ad1843, &ad1843_SCF, 1);
+
+	/* 4. Put the conversion resources into standby. */
+	ad1843_write_bits(ad1843, &ad1843_PDNI, 0);
+	later = jiffies + msecs_to_jiffies(500);
+
+	while (ad1843_read_bits(ad1843, &ad1843_PDNO)) {
+		if (time_after(jiffies, later)) {
+			printk(KERN_ERR
+			       "ad1843: AD1843 won't power up\n");
+			return -EIO;
+		}
+		schedule_timeout_interruptible(5);
+	}
+
+	/* 5. Power up the clock generators and enable clock output pins. */
+	ad1843_write_multi(ad1843, 3,
+			   &ad1843_C1EN, 1,
+			   &ad1843_C2EN, 1,
+			   &ad1843_C3EN, 1);
+
+	/* 6. Configure conversion resources while they are in standby. */
+
+	/* DAC1/2 use clock 1/2 as source, ADC uses clock 3.  Always. */
+	ad1843_write_multi(ad1843, 4,
+			   &ad1843_DA1C, 1,
+			   &ad1843_DA2C, 2,
+			   &ad1843_ADLC, 3,
+			   &ad1843_ADRC, 3);
+
+	/* 7. Enable conversion resources. */
+	ad1843_write_bits(ad1843, &ad1843_ADTLK, 1);
+	ad1843_write_multi(ad1843, 7,
+			   &ad1843_ANAEN, 1,
+			   &ad1843_AAMEN, 1,
+			   &ad1843_DA1EN, 1,
+			   &ad1843_DA2EN, 1,
+			   &ad1843_DDMEN, 1,
+			   &ad1843_ADLEN, 1,
+			   &ad1843_ADREN, 1);
+
+	/* 8. Configure conversion resources while they are enabled. */
+
+	/* set gain to 0 for all channels */
+	ad1843_set_gain(ad1843, AD1843_GAIN_RECLEV, 0);
+	ad1843_set_gain(ad1843, AD1843_GAIN_LINE, 0);
+	ad1843_set_gain(ad1843, AD1843_GAIN_LINE_2, 0);
+	ad1843_set_gain(ad1843, AD1843_GAIN_MIC, 0);
+	ad1843_set_gain(ad1843, AD1843_GAIN_PCM_0, 0);
+	ad1843_set_gain(ad1843, AD1843_GAIN_PCM_1, 0);
+
+	/* Unmute all channels. */
+	/* DAC1 */
+	ad1843_write_multi(ad1843, 2, &ad1843_LDA1GM, 0, &ad1843_RDA1GM, 0);
+	/* DAC2 */
+	ad1843_write_multi(ad1843, 2, &ad1843_LDA2GM, 0, &ad1843_RDA2GM, 0);
+
+	/* Set default recording source to Line In and set
+	 * mic gain to +20 dB.
+	 */
+	ad1843_set_recsrc(ad1843, 2);
+	ad1843_write_multi(ad1843, 2, &ad1843_LMGE, 1, &ad1843_RMGE, 1);
+
+	/* Set Speaker Out level to +/- 4V and unmute it. */
+	ad1843_write_multi(ad1843, 3,
+			   &ad1843_HPOS, 1,
+			   &ad1843_HPOM, 0,
+			   &ad1843_MPOM, 0);
+
+	return 0;
+}
diff --git a/linux/sound/mips/au1x00.c b/linux/sound/mips/au1x00.c
new file mode 100644
index 0000000..446cf97
--- /dev/null
+++ b/linux/sound/mips/au1x00.c
@@ -0,0 +1,695 @@
+/*
+ * BRIEF MODULE DESCRIPTION
+ *  Driver for AMD Au1000 MIPS Processor, AC'97 Sound Port
+ *
+ * Copyright 2004 Cooper Street Innovations Inc.
+ * Author: Charles Eidsness	<charles@cooper-street.com>
+ *
+ *  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  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
+ *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+ *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
+ *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * History:
+ *
+ * 2004-09-09 Charles Eidsness	-- Original verion -- based on
+ * 				  sa11xx-uda1341.c ALSA driver and the
+ *				  au1000.c OSS driver.
+ * 2004-09-09 Matt Porter	-- Added support for ALSA 1.0.6
+ *
+ */
+
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/ac97_codec.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1000_dma.h>
+
+MODULE_AUTHOR("Charles Eidsness <charles@cooper-street.com>");
+MODULE_DESCRIPTION("Au1000 AC'97 ALSA Driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{AMD,Au1000 AC'97}}");
+
+#define PLAYBACK 0
+#define CAPTURE 1
+#define AC97_SLOT_3 0x01
+#define AC97_SLOT_4 0x02
+#define AC97_SLOT_6 0x08
+#define AC97_CMD_IRQ 31
+#define READ 0
+#define WRITE 1
+#define READ_WAIT 2
+#define RW_DONE 3
+
+struct au1000_period
+{
+	u32 start;
+	u32 relative_end;	/*realtive to start of buffer*/
+	struct au1000_period * next;
+};
+
+/*Au1000 AC97 Port Control Reisters*/
+struct au1000_ac97_reg {
+	u32 volatile config;
+	u32 volatile status;
+	u32 volatile data;
+	u32 volatile cmd;
+	u32 volatile cntrl;
+};
+
+struct audio_stream {
+	struct snd_pcm_substream *substream;
+	int dma;
+	spinlock_t dma_lock;
+	struct au1000_period * buffer;
+	unsigned int period_size;
+	unsigned int periods;
+};
+
+struct snd_au1000 {
+	struct snd_card *card;
+	struct au1000_ac97_reg volatile *ac97_ioport;
+
+	struct resource *ac97_res_port;
+	spinlock_t ac97_lock;
+	struct snd_ac97 *ac97;
+
+	struct snd_pcm *pcm;
+	struct audio_stream *stream[2];	/* playback & capture */
+};
+
+/*--------------------------- Local Functions --------------------------------*/
+static void
+au1000_set_ac97_xmit_slots(struct snd_au1000 *au1000, long xmit_slots)
+{
+	u32 volatile ac97_config;
+
+	spin_lock(&au1000->ac97_lock);
+	ac97_config = au1000->ac97_ioport->config;
+	ac97_config = ac97_config & ~AC97C_XMIT_SLOTS_MASK;
+	ac97_config |= (xmit_slots << AC97C_XMIT_SLOTS_BIT);
+	au1000->ac97_ioport->config = ac97_config;
+	spin_unlock(&au1000->ac97_lock);
+}
+
+static void
+au1000_set_ac97_recv_slots(struct snd_au1000 *au1000, long recv_slots)
+{
+	u32 volatile ac97_config;
+
+	spin_lock(&au1000->ac97_lock);
+	ac97_config = au1000->ac97_ioport->config;
+	ac97_config = ac97_config & ~AC97C_RECV_SLOTS_MASK;
+	ac97_config |= (recv_slots << AC97C_RECV_SLOTS_BIT);
+	au1000->ac97_ioport->config = ac97_config;
+	spin_unlock(&au1000->ac97_lock);
+}
+
+
+static void
+au1000_release_dma_link(struct audio_stream *stream)
+{
+	struct au1000_period * pointer;
+	struct au1000_period * pointer_next;
+
+	stream->period_size = 0;
+	stream->periods = 0;
+	pointer = stream->buffer;
+	if (! pointer)
+		return;
+	do {
+		pointer_next = pointer->next;
+		kfree(pointer);
+		pointer = pointer_next;
+	} while (pointer != stream->buffer);
+	stream->buffer = NULL;
+}
+
+static int
+au1000_setup_dma_link(struct audio_stream *stream, unsigned int period_bytes,
+		      unsigned int periods)
+{
+	struct snd_pcm_substream *substream = stream->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct au1000_period *pointer;
+	unsigned long dma_start;
+	int i;
+
+	dma_start = virt_to_phys(runtime->dma_area);
+
+	if (stream->period_size == period_bytes &&
+	    stream->periods == periods)
+		return 0; /* not changed */
+
+	au1000_release_dma_link(stream);
+
+	stream->period_size = period_bytes;
+	stream->periods = periods;
+
+	stream->buffer = kmalloc(sizeof(struct au1000_period), GFP_KERNEL);
+	if (! stream->buffer)
+		return -ENOMEM;
+	pointer = stream->buffer;
+	for (i = 0; i < periods; i++) {
+		pointer->start = (u32)(dma_start + (i * period_bytes));
+		pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1);
+		if (i < periods - 1) {
+			pointer->next = kmalloc(sizeof(struct au1000_period), GFP_KERNEL);
+			if (! pointer->next) {
+				au1000_release_dma_link(stream);
+				return -ENOMEM;
+			}
+			pointer = pointer->next;
+		}
+	}
+	pointer->next = stream->buffer;
+	return 0;
+}
+
+static void
+au1000_dma_stop(struct audio_stream *stream)
+{
+	if (snd_BUG_ON(!stream->buffer))
+		return;
+	disable_dma(stream->dma);
+}
+
+static void
+au1000_dma_start(struct audio_stream *stream)
+{
+	if (snd_BUG_ON(!stream->buffer))
+		return;
+
+	init_dma(stream->dma);
+	if (get_dma_active_buffer(stream->dma) == 0) {
+		clear_dma_done0(stream->dma);
+		set_dma_addr0(stream->dma, stream->buffer->start);
+		set_dma_count0(stream->dma, stream->period_size >> 1);
+		set_dma_addr1(stream->dma, stream->buffer->next->start);
+		set_dma_count1(stream->dma, stream->period_size >> 1);
+	} else {
+		clear_dma_done1(stream->dma);
+		set_dma_addr1(stream->dma, stream->buffer->start);
+		set_dma_count1(stream->dma, stream->period_size >> 1);
+		set_dma_addr0(stream->dma, stream->buffer->next->start);
+		set_dma_count0(stream->dma, stream->period_size >> 1);
+	}
+	enable_dma_buffers(stream->dma);
+	start_dma(stream->dma);
+}
+
+static irqreturn_t
+au1000_dma_interrupt(int irq, void *dev_id)
+{
+	struct audio_stream *stream = (struct audio_stream *) dev_id;
+	struct snd_pcm_substream *substream = stream->substream;
+
+	spin_lock(&stream->dma_lock);
+	switch (get_dma_buffer_done(stream->dma)) {
+	case DMA_D0:
+		stream->buffer = stream->buffer->next;
+		clear_dma_done0(stream->dma);
+		set_dma_addr0(stream->dma, stream->buffer->next->start);
+		set_dma_count0(stream->dma, stream->period_size >> 1);
+		enable_dma_buffer0(stream->dma);
+		break;
+	case DMA_D1:
+		stream->buffer = stream->buffer->next;
+		clear_dma_done1(stream->dma);
+		set_dma_addr1(stream->dma, stream->buffer->next->start);
+		set_dma_count1(stream->dma, stream->period_size >> 1);
+		enable_dma_buffer1(stream->dma);
+		break;
+	case (DMA_D0 | DMA_D1):
+		printk(KERN_ERR "DMA %d missed interrupt.\n",stream->dma);
+		au1000_dma_stop(stream);
+		au1000_dma_start(stream);
+		break;
+	case (~DMA_D0 & ~DMA_D1):
+		printk(KERN_ERR "DMA %d empty irq.\n",stream->dma);
+	}
+	spin_unlock(&stream->dma_lock);
+	snd_pcm_period_elapsed(substream);
+	return IRQ_HANDLED;
+}
+
+/*-------------------------- PCM Audio Streams -------------------------------*/
+
+static unsigned int rates[] = {8000, 11025, 16000, 22050};
+static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+	.count	= ARRAY_SIZE(rates),
+	.list	= rates,
+	.mask	= 0,
+};
+
+static struct snd_pcm_hardware snd_au1000_hw =
+{
+	.info			= (SNDRV_PCM_INFO_INTERLEAVED | \
+				SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE,
+	.rates			= (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |
+				SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050),
+	.rate_min		= 8000,
+	.rate_max		= 22050,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= 128*1024,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 16*1024,
+	.periods_min		= 8,
+	.periods_max		= 255,
+	.fifo_size		= 16,
+};
+
+static int
+snd_au1000_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_au1000 *au1000 = substream->pcm->private_data;
+
+	au1000->stream[PLAYBACK]->substream = substream;
+	au1000->stream[PLAYBACK]->buffer = NULL;
+	substream->private_data = au1000->stream[PLAYBACK];
+	substream->runtime->hw = snd_au1000_hw;
+	return (snd_pcm_hw_constraint_list(substream->runtime, 0,
+		SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates) < 0);
+}
+
+static int
+snd_au1000_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_au1000 *au1000 = substream->pcm->private_data;
+
+	au1000->stream[CAPTURE]->substream = substream;
+	au1000->stream[CAPTURE]->buffer = NULL;
+	substream->private_data = au1000->stream[CAPTURE];
+	substream->runtime->hw = snd_au1000_hw;
+	return (snd_pcm_hw_constraint_list(substream->runtime, 0,
+		SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates) < 0);
+}
+
+static int
+snd_au1000_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_au1000 *au1000 = substream->pcm->private_data;
+
+	au1000->stream[PLAYBACK]->substream = NULL;
+	return 0;
+}
+
+static int
+snd_au1000_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_au1000 *au1000 = substream->pcm->private_data;
+
+	au1000->stream[CAPTURE]->substream = NULL;
+	return 0;
+}
+
+static int
+snd_au1000_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	struct audio_stream *stream = substream->private_data;
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	return au1000_setup_dma_link(stream,
+				     params_period_bytes(hw_params),
+				     params_periods(hw_params));
+}
+
+static int
+snd_au1000_hw_free(struct snd_pcm_substream *substream)
+{
+	struct audio_stream *stream = substream->private_data;
+	au1000_release_dma_link(stream);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int
+snd_au1000_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_au1000 *au1000 = substream->pcm->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (runtime->channels == 1)
+		au1000_set_ac97_xmit_slots(au1000, AC97_SLOT_4);
+	else
+		au1000_set_ac97_xmit_slots(au1000, AC97_SLOT_3 | AC97_SLOT_4);
+	snd_ac97_set_rate(au1000->ac97, AC97_PCM_FRONT_DAC_RATE, runtime->rate);
+	return 0;
+}
+
+static int
+snd_au1000_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_au1000 *au1000 = substream->pcm->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (runtime->channels == 1)
+		au1000_set_ac97_recv_slots(au1000, AC97_SLOT_4);
+	else
+		au1000_set_ac97_recv_slots(au1000, AC97_SLOT_3 | AC97_SLOT_4);
+	snd_ac97_set_rate(au1000->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate);
+	return 0;
+}
+
+static int
+snd_au1000_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct audio_stream *stream = substream->private_data;
+	int err = 0;
+
+	spin_lock(&stream->dma_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		au1000_dma_start(stream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		au1000_dma_stop(stream);
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	spin_unlock(&stream->dma_lock);
+	return err;
+}
+
+static snd_pcm_uframes_t
+snd_au1000_pointer(struct snd_pcm_substream *substream)
+{
+	struct audio_stream *stream = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	long location;
+
+	spin_lock(&stream->dma_lock);
+	location = get_dma_residue(stream->dma);
+	spin_unlock(&stream->dma_lock);
+	location = stream->buffer->relative_end - location;
+	if (location == -1)
+		location = 0;
+	return bytes_to_frames(runtime,location);
+}
+
+static struct snd_pcm_ops snd_card_au1000_playback_ops = {
+	.open			= snd_au1000_playback_open,
+	.close			= snd_au1000_playback_close,
+	.ioctl			= snd_pcm_lib_ioctl,
+	.hw_params	        = snd_au1000_hw_params,
+	.hw_free	        = snd_au1000_hw_free,
+	.prepare		= snd_au1000_playback_prepare,
+	.trigger		= snd_au1000_trigger,
+	.pointer		= snd_au1000_pointer,
+};
+
+static struct snd_pcm_ops snd_card_au1000_capture_ops = {
+	.open			= snd_au1000_capture_open,
+	.close			= snd_au1000_capture_close,
+	.ioctl			= snd_pcm_lib_ioctl,
+	.hw_params	        = snd_au1000_hw_params,
+	.hw_free	        = snd_au1000_hw_free,
+	.prepare		= snd_au1000_capture_prepare,
+	.trigger		= snd_au1000_trigger,
+	.pointer		= snd_au1000_pointer,
+};
+
+static int __devinit
+snd_au1000_pcm_new(struct snd_au1000 *au1000)
+{
+	struct snd_pcm *pcm;
+	int err;
+	unsigned long flags;
+
+	if ((err = snd_pcm_new(au1000->card, "AU1000 AC97 PCM", 0, 1, 1, &pcm)) < 0)
+		return err;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+		snd_dma_continuous_data(GFP_KERNEL), 128*1024, 128*1024);
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+		&snd_card_au1000_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+		&snd_card_au1000_capture_ops);
+
+	pcm->private_data = au1000;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "Au1000 AC97 PCM");
+
+	spin_lock_init(&au1000->stream[PLAYBACK]->dma_lock);
+	spin_lock_init(&au1000->stream[CAPTURE]->dma_lock);
+
+	flags = claim_dma_lock();
+	if ((au1000->stream[PLAYBACK]->dma = request_au1000_dma(DMA_ID_AC97C_TX,
+			"AC97 TX", au1000_dma_interrupt, IRQF_DISABLED,
+			au1000->stream[PLAYBACK])) < 0) {
+		release_dma_lock(flags);
+		return -EBUSY;
+	}
+	if ((au1000->stream[CAPTURE]->dma = request_au1000_dma(DMA_ID_AC97C_RX,
+			"AC97 RX", au1000_dma_interrupt, IRQF_DISABLED,
+			au1000->stream[CAPTURE])) < 0){
+		release_dma_lock(flags);
+		return -EBUSY;
+	}
+	/* enable DMA coherency in read/write DMA channels */
+	set_dma_mode(au1000->stream[PLAYBACK]->dma,
+		     get_dma_mode(au1000->stream[PLAYBACK]->dma) & ~DMA_NC);
+	set_dma_mode(au1000->stream[CAPTURE]->dma,
+		     get_dma_mode(au1000->stream[CAPTURE]->dma) & ~DMA_NC);
+	release_dma_lock(flags);
+	au1000->pcm = pcm;
+	return 0;
+}
+
+
+/*-------------------------- AC97 CODEC Control ------------------------------*/
+
+static unsigned short
+snd_au1000_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct snd_au1000 *au1000 = ac97->private_data;
+	u32 volatile cmd;
+	u16 volatile data;
+	int             i;
+
+	spin_lock(&au1000->ac97_lock);
+/* would rather use the interrupt than this polling but it works and I can't
+get the interrupt driven case to work efficiently */
+	for (i = 0; i < 0x5000; i++)
+		if (!(au1000->ac97_ioport->status & AC97C_CP))
+			break;
+	if (i == 0x5000)
+		printk(KERN_ERR "au1000 AC97: AC97 command read timeout\n");
+
+	cmd = (u32) reg & AC97C_INDEX_MASK;
+	cmd |= AC97C_READ;
+	au1000->ac97_ioport->cmd = cmd;
+
+	/* now wait for the data */
+	for (i = 0; i < 0x5000; i++)
+		if (!(au1000->ac97_ioport->status & AC97C_CP))
+			break;
+	if (i == 0x5000) {
+		printk(KERN_ERR "au1000 AC97: AC97 command read timeout\n");
+		spin_unlock(&au1000->ac97_lock);
+		return 0;
+	}
+
+	data = au1000->ac97_ioport->cmd & 0xffff;
+	spin_unlock(&au1000->ac97_lock);
+
+	return data;
+
+}
+
+
+static void
+snd_au1000_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val)
+{
+	struct snd_au1000 *au1000 = ac97->private_data;
+	u32 cmd;
+	int i;
+
+	spin_lock(&au1000->ac97_lock);
+/* would rather use the interrupt than this polling but it works and I can't
+get the interrupt driven case to work efficiently */
+	for (i = 0; i < 0x5000; i++)
+		if (!(au1000->ac97_ioport->status & AC97C_CP))
+			break;
+	if (i == 0x5000)
+		printk(KERN_ERR "au1000 AC97: AC97 command write timeout\n");
+
+	cmd = (u32) reg & AC97C_INDEX_MASK;
+	cmd &= ~AC97C_READ;
+	cmd |= ((u32) val << AC97C_WD_BIT);
+	au1000->ac97_ioport->cmd = cmd;
+	spin_unlock(&au1000->ac97_lock);
+}
+
+static int __devinit
+snd_au1000_ac97_new(struct snd_au1000 *au1000)
+{
+	int err;
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+ 	static struct snd_ac97_bus_ops ops = {
+		.write = snd_au1000_ac97_write,
+		.read = snd_au1000_ac97_read,
+	};
+
+	if ((au1000->ac97_res_port = request_mem_region(CPHYSADDR(AC97C_CONFIG),
+	       		0x100000, "Au1x00 AC97")) == NULL) {
+		snd_printk(KERN_ERR "ALSA AC97: can't grap AC97 port\n");
+		return -EBUSY;
+	}
+	au1000->ac97_ioport = (struct au1000_ac97_reg *)
+		KSEG1ADDR(au1000->ac97_res_port->start);
+
+	spin_lock_init(&au1000->ac97_lock);
+
+	/* configure pins for AC'97
+	TODO: move to board_setup.c */
+	au_writel(au_readl(SYS_PINFUNC) & ~0x02, SYS_PINFUNC);
+
+	/* Initialise Au1000's AC'97 Control Block */
+	au1000->ac97_ioport->cntrl = AC97C_RS | AC97C_CE;
+	udelay(10);
+	au1000->ac97_ioport->cntrl = AC97C_CE;
+	udelay(10);
+
+	/* Initialise External CODEC -- cold reset */
+	au1000->ac97_ioport->config = AC97C_RESET;
+	udelay(10);
+	au1000->ac97_ioport->config = 0x0;
+	mdelay(5);
+
+	/* Initialise AC97 middle-layer */
+	if ((err = snd_ac97_bus(au1000->card, 0, &ops, au1000, &pbus)) < 0)
+ 		return err;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = au1000;
+	if ((err = snd_ac97_mixer(pbus, &ac97, &au1000->ac97)) < 0)
+		return err;
+
+	return 0;
+}
+
+/*------------------------------ Setup / Destroy ----------------------------*/
+
+void
+snd_au1000_free(struct snd_card *card)
+{
+	struct snd_au1000 *au1000 = card->private_data;
+
+	if (au1000->ac97_res_port) {
+		/* put internal AC97 block into reset */
+		au1000->ac97_ioport->cntrl = AC97C_RS;
+		au1000->ac97_ioport = NULL;
+		release_and_free_resource(au1000->ac97_res_port);
+	}
+
+	if (au1000->stream[PLAYBACK]) {
+	  	if (au1000->stream[PLAYBACK]->dma >= 0)
+			free_au1000_dma(au1000->stream[PLAYBACK]->dma);
+		kfree(au1000->stream[PLAYBACK]);
+	}
+
+	if (au1000->stream[CAPTURE]) {
+		if (au1000->stream[CAPTURE]->dma >= 0)
+			free_au1000_dma(au1000->stream[CAPTURE]->dma);
+		kfree(au1000->stream[CAPTURE]);
+	}
+}
+
+
+static struct snd_card *au1000_card;
+
+static int __init
+au1000_init(void)
+{
+	int err;
+	struct snd_card *card;
+	struct snd_au1000 *au1000;
+
+	err = snd_card_create(-1, "AC97", THIS_MODULE,
+			      sizeof(struct snd_au1000), &card);
+	if (err < 0)
+		return err;
+
+	card->private_free = snd_au1000_free;
+	au1000 = card->private_data;
+	au1000->card = card;
+
+	au1000->stream[PLAYBACK] = kmalloc(sizeof(struct audio_stream), GFP_KERNEL);
+	au1000->stream[CAPTURE ] = kmalloc(sizeof(struct audio_stream), GFP_KERNEL);
+	/* so that snd_au1000_free will work as intended */
+ 	au1000->ac97_res_port = NULL;
+	if (au1000->stream[PLAYBACK])
+		au1000->stream[PLAYBACK]->dma = -1;
+	if (au1000->stream[CAPTURE ])
+		au1000->stream[CAPTURE ]->dma = -1;
+
+	if (au1000->stream[PLAYBACK] == NULL ||
+	    au1000->stream[CAPTURE ] == NULL) {
+		snd_card_free(card);
+		return -ENOMEM;
+	}
+
+	if ((err = snd_au1000_ac97_new(au1000)) < 0 ) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_au1000_pcm_new(au1000)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "Au1000-AC97");
+	strcpy(card->shortname, "AMD Au1000-AC97");
+	sprintf(card->longname, "AMD Au1000--AC97 ALSA Driver");
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	printk(KERN_INFO "ALSA AC97: Driver Initialized\n");
+	au1000_card = card;
+	return 0;
+}
+
+static void __exit au1000_exit(void)
+{
+	snd_card_free(au1000_card);
+}
+
+module_init(au1000_init);
+module_exit(au1000_exit);
+
diff --git a/linux/sound/mips/hal2.c b/linux/sound/mips/hal2.c
new file mode 100644
index 0000000..453d343
--- /dev/null
+++ b/linux/sound/mips/hal2.c
@@ -0,0 +1,948 @@
+/*
+ *  Driver for A2 audio system used in SGI machines
+ *  Copyright (c) 2008 Thomas Bogendoerfer <tsbogend@alpha.fanken.de>
+ *
+ *  Based on OSS code from Ladislav Michl <ladis@linux-mips.org>, which
+ *  was based on code from Ulf Carlsson
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <asm/sgi/hpc3.h>
+#include <asm/sgi/ip22.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm-indirect.h>
+#include <sound/initval.h>
+
+#include "hal2.h"
+
+static int index = SNDRV_DEFAULT_IDX1;  /* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;   /* ID for this card */
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for SGI HAL2 soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for SGI HAL2 soundcard.");
+MODULE_DESCRIPTION("ALSA driver for SGI HAL2 audio");
+MODULE_AUTHOR("Thomas Bogendoerfer");
+MODULE_LICENSE("GPL");
+
+
+#define H2_BLOCK_SIZE	1024
+#define H2_BUF_SIZE	16384
+
+struct hal2_pbus {
+	struct hpc3_pbus_dmacregs *pbus;
+	int pbusnr;
+	unsigned int ctrl;		/* Current state of pbus->pbdma_ctrl */
+};
+
+struct hal2_desc {
+	struct hpc_dma_desc desc;
+	u32 pad;			/* padding */
+};
+
+struct hal2_codec {
+	struct snd_pcm_indirect pcm_indirect;
+	struct snd_pcm_substream *substream;
+
+	unsigned char *buffer;
+	dma_addr_t buffer_dma;
+	struct hal2_desc *desc;
+	dma_addr_t desc_dma;
+	int desc_count;
+	struct hal2_pbus pbus;
+	int voices;			/* mono/stereo */
+	unsigned int sample_rate;
+	unsigned int master;		/* Master frequency */
+	unsigned short mod;		/* MOD value */
+	unsigned short inc;		/* INC value */
+};
+
+#define H2_MIX_OUTPUT_ATT	0
+#define H2_MIX_INPUT_GAIN	1
+
+struct snd_hal2 {
+	struct snd_card *card;
+
+	struct hal2_ctl_regs *ctl_regs;	/* HAL2 ctl registers */
+	struct hal2_aes_regs *aes_regs;	/* HAL2 aes registers */
+	struct hal2_vol_regs *vol_regs;	/* HAL2 vol registers */
+	struct hal2_syn_regs *syn_regs;	/* HAL2 syn registers */
+
+	struct hal2_codec dac;
+	struct hal2_codec adc;
+};
+
+#define H2_INDIRECT_WAIT(regs)	while (hal2_read(&regs->isr) & H2_ISR_TSTATUS);
+
+#define H2_READ_ADDR(addr)	(addr | (1<<7))
+#define H2_WRITE_ADDR(addr)	(addr)
+
+static inline u32 hal2_read(u32 *reg)
+{
+	return __raw_readl(reg);
+}
+
+static inline void hal2_write(u32 val, u32 *reg)
+{
+	__raw_writel(val, reg);
+}
+
+
+static u32 hal2_i_read32(struct snd_hal2 *hal2, u16 addr)
+{
+	u32 ret;
+	struct hal2_ctl_regs *regs = hal2->ctl_regs;
+
+	hal2_write(H2_READ_ADDR(addr), &regs->iar);
+	H2_INDIRECT_WAIT(regs);
+	ret = hal2_read(&regs->idr0) & 0xffff;
+	hal2_write(H2_READ_ADDR(addr) | 0x1, &regs->iar);
+	H2_INDIRECT_WAIT(regs);
+	ret |= (hal2_read(&regs->idr0) & 0xffff) << 16;
+	return ret;
+}
+
+static void hal2_i_write16(struct snd_hal2 *hal2, u16 addr, u16 val)
+{
+	struct hal2_ctl_regs *regs = hal2->ctl_regs;
+
+	hal2_write(val, &regs->idr0);
+	hal2_write(0, &regs->idr1);
+	hal2_write(0, &regs->idr2);
+	hal2_write(0, &regs->idr3);
+	hal2_write(H2_WRITE_ADDR(addr), &regs->iar);
+	H2_INDIRECT_WAIT(regs);
+}
+
+static void hal2_i_write32(struct snd_hal2 *hal2, u16 addr, u32 val)
+{
+	struct hal2_ctl_regs *regs = hal2->ctl_regs;
+
+	hal2_write(val & 0xffff, &regs->idr0);
+	hal2_write(val >> 16, &regs->idr1);
+	hal2_write(0, &regs->idr2);
+	hal2_write(0, &regs->idr3);
+	hal2_write(H2_WRITE_ADDR(addr), &regs->iar);
+	H2_INDIRECT_WAIT(regs);
+}
+
+static void hal2_i_setbit16(struct snd_hal2 *hal2, u16 addr, u16 bit)
+{
+	struct hal2_ctl_regs *regs = hal2->ctl_regs;
+
+	hal2_write(H2_READ_ADDR(addr), &regs->iar);
+	H2_INDIRECT_WAIT(regs);
+	hal2_write((hal2_read(&regs->idr0) & 0xffff) | bit, &regs->idr0);
+	hal2_write(0, &regs->idr1);
+	hal2_write(0, &regs->idr2);
+	hal2_write(0, &regs->idr3);
+	hal2_write(H2_WRITE_ADDR(addr), &regs->iar);
+	H2_INDIRECT_WAIT(regs);
+}
+
+static void hal2_i_clearbit16(struct snd_hal2 *hal2, u16 addr, u16 bit)
+{
+	struct hal2_ctl_regs *regs = hal2->ctl_regs;
+
+	hal2_write(H2_READ_ADDR(addr), &regs->iar);
+	H2_INDIRECT_WAIT(regs);
+	hal2_write((hal2_read(&regs->idr0) & 0xffff) & ~bit, &regs->idr0);
+	hal2_write(0, &regs->idr1);
+	hal2_write(0, &regs->idr2);
+	hal2_write(0, &regs->idr3);
+	hal2_write(H2_WRITE_ADDR(addr), &regs->iar);
+	H2_INDIRECT_WAIT(regs);
+}
+
+static int hal2_gain_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	switch ((int)kcontrol->private_value) {
+	case H2_MIX_OUTPUT_ATT:
+		uinfo->value.integer.max = 31;
+		break;
+	case H2_MIX_INPUT_GAIN:
+		uinfo->value.integer.max = 15;
+		break;
+	}
+	return 0;
+}
+
+static int hal2_gain_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_hal2 *hal2 = snd_kcontrol_chip(kcontrol);
+	u32 tmp;
+	int l, r;
+
+	switch ((int)kcontrol->private_value) {
+	case H2_MIX_OUTPUT_ATT:
+		tmp = hal2_i_read32(hal2, H2I_DAC_C2);
+		if (tmp & H2I_C2_MUTE) {
+			l = 0;
+			r = 0;
+		} else {
+			l = 31 - ((tmp >> H2I_C2_L_ATT_SHIFT) & 31);
+			r = 31 - ((tmp >> H2I_C2_R_ATT_SHIFT) & 31);
+		}
+		break;
+	case H2_MIX_INPUT_GAIN:
+		tmp = hal2_i_read32(hal2, H2I_ADC_C2);
+		l = (tmp >> H2I_C2_L_GAIN_SHIFT) & 15;
+		r = (tmp >> H2I_C2_R_GAIN_SHIFT) & 15;
+		break;
+	}
+	ucontrol->value.integer.value[0] = l;
+	ucontrol->value.integer.value[1] = r;
+
+	return 0;
+}
+
+static int hal2_gain_put(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_hal2 *hal2 = snd_kcontrol_chip(kcontrol);
+	u32 old, new;
+	int l, r;
+
+	l = ucontrol->value.integer.value[0];
+	r = ucontrol->value.integer.value[1];
+
+	switch ((int)kcontrol->private_value) {
+	case H2_MIX_OUTPUT_ATT:
+		old = hal2_i_read32(hal2, H2I_DAC_C2);
+		new = old & ~(H2I_C2_L_ATT_M | H2I_C2_R_ATT_M | H2I_C2_MUTE);
+		if (l | r) {
+			l = 31 - l;
+			r = 31 - r;
+			new |= (l << H2I_C2_L_ATT_SHIFT);
+			new |= (r << H2I_C2_R_ATT_SHIFT);
+		} else
+			new |= H2I_C2_L_ATT_M | H2I_C2_R_ATT_M | H2I_C2_MUTE;
+		hal2_i_write32(hal2, H2I_DAC_C2, new);
+		break;
+	case H2_MIX_INPUT_GAIN:
+		old = hal2_i_read32(hal2, H2I_ADC_C2);
+		new = old & ~(H2I_C2_L_GAIN_M | H2I_C2_R_GAIN_M);
+		new |= (l << H2I_C2_L_GAIN_SHIFT);
+		new |= (r << H2I_C2_R_GAIN_SHIFT);
+		hal2_i_write32(hal2, H2I_ADC_C2, new);
+		break;
+	}
+	return old != new;
+}
+
+static struct snd_kcontrol_new hal2_ctrl_headphone __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Headphone Playback Volume",
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = H2_MIX_OUTPUT_ATT,
+	.info           = hal2_gain_info,
+	.get            = hal2_gain_get,
+	.put            = hal2_gain_put,
+};
+
+static struct snd_kcontrol_new hal2_ctrl_mic __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Mic Capture Volume",
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = H2_MIX_INPUT_GAIN,
+	.info           = hal2_gain_info,
+	.get            = hal2_gain_get,
+	.put            = hal2_gain_put,
+};
+
+static int __devinit hal2_mixer_create(struct snd_hal2 *hal2)
+{
+	int err;
+
+	/* mute DAC */
+	hal2_i_write32(hal2, H2I_DAC_C2,
+		       H2I_C2_L_ATT_M | H2I_C2_R_ATT_M | H2I_C2_MUTE);
+	/* mute ADC */
+	hal2_i_write32(hal2, H2I_ADC_C2, 0);
+
+	err = snd_ctl_add(hal2->card,
+			  snd_ctl_new1(&hal2_ctrl_headphone, hal2));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(hal2->card,
+			  snd_ctl_new1(&hal2_ctrl_mic, hal2));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static irqreturn_t hal2_interrupt(int irq, void *dev_id)
+{
+	struct snd_hal2 *hal2 = dev_id;
+	irqreturn_t ret = IRQ_NONE;
+
+	/* decide what caused this interrupt */
+	if (hal2->dac.pbus.pbus->pbdma_ctrl & HPC3_PDMACTRL_INT) {
+		snd_pcm_period_elapsed(hal2->dac.substream);
+		ret = IRQ_HANDLED;
+	}
+	if (hal2->adc.pbus.pbus->pbdma_ctrl & HPC3_PDMACTRL_INT) {
+		snd_pcm_period_elapsed(hal2->adc.substream);
+		ret = IRQ_HANDLED;
+	}
+	return ret;
+}
+
+static int hal2_compute_rate(struct hal2_codec *codec, unsigned int rate)
+{
+	unsigned short mod;
+
+	if (44100 % rate < 48000 % rate) {
+		mod = 4 * 44100 / rate;
+		codec->master = 44100;
+	} else {
+		mod = 4 * 48000 / rate;
+		codec->master = 48000;
+	}
+
+	codec->inc = 4;
+	codec->mod = mod;
+	rate = 4 * codec->master / mod;
+
+	return rate;
+}
+
+static void hal2_set_dac_rate(struct snd_hal2 *hal2)
+{
+	unsigned int master = hal2->dac.master;
+	int inc = hal2->dac.inc;
+	int mod = hal2->dac.mod;
+
+	hal2_i_write16(hal2, H2I_BRES1_C1, (master == 44100) ? 1 : 0);
+	hal2_i_write32(hal2, H2I_BRES1_C2,
+		       ((0xffff & (inc - mod - 1)) << 16) | inc);
+}
+
+static void hal2_set_adc_rate(struct snd_hal2 *hal2)
+{
+	unsigned int master = hal2->adc.master;
+	int inc = hal2->adc.inc;
+	int mod = hal2->adc.mod;
+
+	hal2_i_write16(hal2, H2I_BRES2_C1, (master == 44100) ? 1 : 0);
+	hal2_i_write32(hal2, H2I_BRES2_C2,
+		       ((0xffff & (inc - mod - 1)) << 16) | inc);
+}
+
+static void hal2_setup_dac(struct snd_hal2 *hal2)
+{
+	unsigned int fifobeg, fifoend, highwater, sample_size;
+	struct hal2_pbus *pbus = &hal2->dac.pbus;
+
+	/* Now we set up some PBUS information. The PBUS needs information about
+	 * what portion of the fifo it will use. If it's receiving or
+	 * transmitting, and finally whether the stream is little endian or big
+	 * endian. The information is written later, on the start call.
+	 */
+	sample_size = 2 * hal2->dac.voices;
+	/* Fifo should be set to hold exactly four samples. Highwater mark
+	 * should be set to two samples. */
+	highwater = (sample_size * 2) >> 1;	/* halfwords */
+	fifobeg = 0;				/* playback is first */
+	fifoend = (sample_size * 4) >> 3;	/* doublewords */
+	pbus->ctrl = HPC3_PDMACTRL_RT | HPC3_PDMACTRL_LD |
+		     (highwater << 8) | (fifobeg << 16) | (fifoend << 24);
+	/* We disable everything before we do anything at all */
+	pbus->pbus->pbdma_ctrl = HPC3_PDMACTRL_LD;
+	hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX);
+	/* Setup the HAL2 for playback */
+	hal2_set_dac_rate(hal2);
+	/* Set endianess */
+	hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECTX);
+	/* Set DMA bus */
+	hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr));
+	/* We are using 1st Bresenham clock generator for playback */
+	hal2_i_write16(hal2, H2I_DAC_C1, (pbus->pbusnr << H2I_C1_DMA_SHIFT)
+			| (1 << H2I_C1_CLKID_SHIFT)
+			| (hal2->dac.voices << H2I_C1_DATAT_SHIFT));
+}
+
+static void hal2_setup_adc(struct snd_hal2 *hal2)
+{
+	unsigned int fifobeg, fifoend, highwater, sample_size;
+	struct hal2_pbus *pbus = &hal2->adc.pbus;
+
+	sample_size = 2 * hal2->adc.voices;
+	highwater = (sample_size * 2) >> 1;		/* halfwords */
+	fifobeg = (4 * 4) >> 3;				/* record is second */
+	fifoend = (4 * 4 + sample_size * 4) >> 3;	/* doublewords */
+	pbus->ctrl = HPC3_PDMACTRL_RT | HPC3_PDMACTRL_RCV | HPC3_PDMACTRL_LD |
+		     (highwater << 8) | (fifobeg << 16) | (fifoend << 24);
+	pbus->pbus->pbdma_ctrl = HPC3_PDMACTRL_LD;
+	hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR);
+	/* Setup the HAL2 for record */
+	hal2_set_adc_rate(hal2);
+	/* Set endianess */
+	hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECR);
+	/* Set DMA bus */
+	hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr));
+	/* We are using 2nd Bresenham clock generator for record */
+	hal2_i_write16(hal2, H2I_ADC_C1, (pbus->pbusnr << H2I_C1_DMA_SHIFT)
+			| (2 << H2I_C1_CLKID_SHIFT)
+			| (hal2->adc.voices << H2I_C1_DATAT_SHIFT));
+}
+
+static void hal2_start_dac(struct snd_hal2 *hal2)
+{
+	struct hal2_pbus *pbus = &hal2->dac.pbus;
+
+	pbus->pbus->pbdma_dptr = hal2->dac.desc_dma;
+	pbus->pbus->pbdma_ctrl = pbus->ctrl | HPC3_PDMACTRL_ACT;
+	/* enable DAC */
+	hal2_i_setbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX);
+}
+
+static void hal2_start_adc(struct snd_hal2 *hal2)
+{
+	struct hal2_pbus *pbus = &hal2->adc.pbus;
+
+	pbus->pbus->pbdma_dptr = hal2->adc.desc_dma;
+	pbus->pbus->pbdma_ctrl = pbus->ctrl | HPC3_PDMACTRL_ACT;
+	/* enable ADC */
+	hal2_i_setbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR);
+}
+
+static inline void hal2_stop_dac(struct snd_hal2 *hal2)
+{
+	hal2->dac.pbus.pbus->pbdma_ctrl = HPC3_PDMACTRL_LD;
+	/* The HAL2 itself may remain enabled safely */
+}
+
+static inline void hal2_stop_adc(struct snd_hal2 *hal2)
+{
+	hal2->adc.pbus.pbus->pbdma_ctrl = HPC3_PDMACTRL_LD;
+}
+
+static int hal2_alloc_dmabuf(struct hal2_codec *codec)
+{
+	struct hal2_desc *desc;
+	dma_addr_t desc_dma, buffer_dma;
+	int count = H2_BUF_SIZE / H2_BLOCK_SIZE;
+	int i;
+
+	codec->buffer = dma_alloc_noncoherent(NULL, H2_BUF_SIZE,
+					      &buffer_dma, GFP_KERNEL);
+	if (!codec->buffer)
+		return -ENOMEM;
+	desc = dma_alloc_noncoherent(NULL, count * sizeof(struct hal2_desc),
+				     &desc_dma, GFP_KERNEL);
+	if (!desc) {
+		dma_free_noncoherent(NULL, H2_BUF_SIZE,
+				     codec->buffer, buffer_dma);
+		return -ENOMEM;
+	}
+	codec->buffer_dma = buffer_dma;
+	codec->desc_dma = desc_dma;
+	codec->desc = desc;
+	for (i = 0; i < count; i++) {
+		desc->desc.pbuf = buffer_dma + i * H2_BLOCK_SIZE;
+		desc->desc.cntinfo = HPCDMA_XIE | H2_BLOCK_SIZE;
+		desc->desc.pnext = (i == count - 1) ?
+		      desc_dma : desc_dma + (i + 1) * sizeof(struct hal2_desc);
+		desc++;
+	}
+	dma_cache_sync(NULL, codec->desc, count * sizeof(struct hal2_desc),
+		       DMA_TO_DEVICE);
+	codec->desc_count = count;
+	return 0;
+}
+
+static void hal2_free_dmabuf(struct hal2_codec *codec)
+{
+	dma_free_noncoherent(NULL, codec->desc_count * sizeof(struct hal2_desc),
+			     codec->desc, codec->desc_dma);
+	dma_free_noncoherent(NULL, H2_BUF_SIZE, codec->buffer,
+			     codec->buffer_dma);
+}
+
+static struct snd_pcm_hardware hal2_pcm_hw = {
+	.info = (SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER),
+	.formats =          SNDRV_PCM_FMTBIT_S16_BE,
+	.rates =            SNDRV_PCM_RATE_8000_48000,
+	.rate_min =         8000,
+	.rate_max =         48000,
+	.channels_min =     2,
+	.channels_max =     2,
+	.buffer_bytes_max = 65536,
+	.period_bytes_min = 1024,
+	.period_bytes_max = 65536,
+	.periods_min =      2,
+	.periods_max =      1024,
+};
+
+static int hal2_pcm_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *params)
+{
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int hal2_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int hal2_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+	int err;
+
+	runtime->hw = hal2_pcm_hw;
+
+	err = hal2_alloc_dmabuf(&hal2->dac);
+	if (err)
+		return err;
+	return 0;
+}
+
+static int hal2_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+
+	hal2_free_dmabuf(&hal2->dac);
+	return 0;
+}
+
+static int hal2_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct hal2_codec *dac = &hal2->dac;
+
+	dac->voices = runtime->channels;
+	dac->sample_rate = hal2_compute_rate(dac, runtime->rate);
+	memset(&dac->pcm_indirect, 0, sizeof(dac->pcm_indirect));
+	dac->pcm_indirect.hw_buffer_size = H2_BUF_SIZE;
+	dac->pcm_indirect.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+	dac->substream = substream;
+	hal2_setup_dac(hal2);
+	return 0;
+}
+
+static int hal2_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		hal2->dac.pcm_indirect.hw_io = hal2->dac.buffer_dma;
+		hal2->dac.pcm_indirect.hw_data = 0;
+		substream->ops->ack(substream);
+		hal2_start_dac(hal2);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		hal2_stop_dac(hal2);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t
+hal2_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+	struct hal2_codec *dac = &hal2->dac;
+
+	return snd_pcm_indirect_playback_pointer(substream, &dac->pcm_indirect,
+						 dac->pbus.pbus->pbdma_bptr);
+}
+
+static void hal2_playback_transfer(struct snd_pcm_substream *substream,
+				   struct snd_pcm_indirect *rec, size_t bytes)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+	unsigned char *buf = hal2->dac.buffer + rec->hw_data;
+
+	memcpy(buf, substream->runtime->dma_area + rec->sw_data, bytes);
+	dma_cache_sync(NULL, buf, bytes, DMA_TO_DEVICE);
+
+}
+
+static int hal2_playback_ack(struct snd_pcm_substream *substream)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+	struct hal2_codec *dac = &hal2->dac;
+
+	dac->pcm_indirect.hw_queue_size = H2_BUF_SIZE / 2;
+	snd_pcm_indirect_playback_transfer(substream,
+					   &dac->pcm_indirect,
+					   hal2_playback_transfer);
+	return 0;
+}
+
+static int hal2_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+	struct hal2_codec *adc = &hal2->adc;
+	int err;
+
+	runtime->hw = hal2_pcm_hw;
+
+	err = hal2_alloc_dmabuf(adc);
+	if (err)
+		return err;
+	return 0;
+}
+
+static int hal2_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+
+	hal2_free_dmabuf(&hal2->adc);
+	return 0;
+}
+
+static int hal2_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct hal2_codec *adc = &hal2->adc;
+
+	adc->voices = runtime->channels;
+	adc->sample_rate = hal2_compute_rate(adc, runtime->rate);
+	memset(&adc->pcm_indirect, 0, sizeof(adc->pcm_indirect));
+	adc->pcm_indirect.hw_buffer_size = H2_BUF_SIZE;
+	adc->pcm_indirect.hw_queue_size = H2_BUF_SIZE / 2;
+	adc->pcm_indirect.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+	adc->substream = substream;
+	hal2_setup_adc(hal2);
+	return 0;
+}
+
+static int hal2_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		hal2->adc.pcm_indirect.hw_io = hal2->adc.buffer_dma;
+		hal2->adc.pcm_indirect.hw_data = 0;
+		printk(KERN_DEBUG "buffer_dma %x\n", hal2->adc.buffer_dma);
+		hal2_start_adc(hal2);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		hal2_stop_adc(hal2);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t
+hal2_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+	struct hal2_codec *adc = &hal2->adc;
+
+	return snd_pcm_indirect_capture_pointer(substream, &adc->pcm_indirect,
+						adc->pbus.pbus->pbdma_bptr);
+}
+
+static void hal2_capture_transfer(struct snd_pcm_substream *substream,
+				  struct snd_pcm_indirect *rec, size_t bytes)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+	unsigned char *buf = hal2->adc.buffer + rec->hw_data;
+
+	dma_cache_sync(NULL, buf, bytes, DMA_FROM_DEVICE);
+	memcpy(substream->runtime->dma_area + rec->sw_data, buf, bytes);
+}
+
+static int hal2_capture_ack(struct snd_pcm_substream *substream)
+{
+	struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream);
+	struct hal2_codec *adc = &hal2->adc;
+
+	snd_pcm_indirect_capture_transfer(substream,
+					  &adc->pcm_indirect,
+					  hal2_capture_transfer);
+	return 0;
+}
+
+static struct snd_pcm_ops hal2_playback_ops = {
+	.open =        hal2_playback_open,
+	.close =       hal2_playback_close,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   hal2_pcm_hw_params,
+	.hw_free =     hal2_pcm_hw_free,
+	.prepare =     hal2_playback_prepare,
+	.trigger =     hal2_playback_trigger,
+	.pointer =     hal2_playback_pointer,
+	.ack =         hal2_playback_ack,
+};
+
+static struct snd_pcm_ops hal2_capture_ops = {
+	.open =        hal2_capture_open,
+	.close =       hal2_capture_close,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   hal2_pcm_hw_params,
+	.hw_free =     hal2_pcm_hw_free,
+	.prepare =     hal2_capture_prepare,
+	.trigger =     hal2_capture_trigger,
+	.pointer =     hal2_capture_pointer,
+	.ack =         hal2_capture_ack,
+};
+
+static int __devinit hal2_pcm_create(struct snd_hal2 *hal2)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	/* create first pcm device with one outputs and one input */
+	err = snd_pcm_new(hal2->card, "SGI HAL2 Audio", 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	pcm->private_data = hal2;
+	strcpy(pcm->name, "SGI HAL2");
+
+	/* set operators */
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&hal2_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&hal2_capture_ops);
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+					   snd_dma_continuous_data(GFP_KERNEL),
+					   0, 1024 * 1024);
+
+	return 0;
+}
+
+static int hal2_dev_free(struct snd_device *device)
+{
+	struct snd_hal2 *hal2 = device->device_data;
+
+	free_irq(SGI_HPCDMA_IRQ, hal2);
+	kfree(hal2);
+	return 0;
+}
+
+static struct snd_device_ops hal2_ops = {
+	.dev_free = hal2_dev_free,
+};
+
+static void hal2_init_codec(struct hal2_codec *codec, struct hpc3_regs *hpc3,
+			    int index)
+{
+	codec->pbus.pbusnr = index;
+	codec->pbus.pbus = &hpc3->pbdma[index];
+}
+
+static int hal2_detect(struct snd_hal2 *hal2)
+{
+	unsigned short board, major, minor;
+	unsigned short rev;
+
+	/* reset HAL2 */
+	hal2_write(0, &hal2->ctl_regs->isr);
+
+	/* release reset */
+	hal2_write(H2_ISR_GLOBAL_RESET_N | H2_ISR_CODEC_RESET_N,
+		   &hal2->ctl_regs->isr);
+
+
+	hal2_i_write16(hal2, H2I_RELAY_C, H2I_RELAY_C_STATE);
+	rev = hal2_read(&hal2->ctl_regs->rev);
+	if (rev & H2_REV_AUDIO_PRESENT)
+		return -ENODEV;
+
+	board = (rev & H2_REV_BOARD_M) >> 12;
+	major = (rev & H2_REV_MAJOR_CHIP_M) >> 4;
+	minor = (rev & H2_REV_MINOR_CHIP_M);
+
+	printk(KERN_INFO "SGI HAL2 revision %i.%i.%i\n",
+	       board, major, minor);
+
+	return 0;
+}
+
+static int hal2_create(struct snd_card *card, struct snd_hal2 **rchip)
+{
+	struct snd_hal2 *hal2;
+	struct hpc3_regs *hpc3 = hpc3c0;
+	int err;
+
+	hal2 = kzalloc(sizeof(struct snd_hal2), GFP_KERNEL);
+	if (!hal2)
+		return -ENOMEM;
+
+	hal2->card = card;
+
+	if (request_irq(SGI_HPCDMA_IRQ, hal2_interrupt, IRQF_SHARED,
+			"SGI HAL2", hal2)) {
+		printk(KERN_ERR "HAL2: Can't get irq %d\n", SGI_HPCDMA_IRQ);
+		kfree(hal2);
+		return -EAGAIN;
+	}
+
+	hal2->ctl_regs = (struct hal2_ctl_regs *)hpc3->pbus_extregs[0];
+	hal2->aes_regs = (struct hal2_aes_regs *)hpc3->pbus_extregs[1];
+	hal2->vol_regs = (struct hal2_vol_regs *)hpc3->pbus_extregs[2];
+	hal2->syn_regs = (struct hal2_syn_regs *)hpc3->pbus_extregs[3];
+
+	if (hal2_detect(hal2) < 0) {
+		kfree(hal2);
+		return -ENODEV;
+	}
+
+	hal2_init_codec(&hal2->dac, hpc3, 0);
+	hal2_init_codec(&hal2->adc, hpc3, 1);
+
+	/*
+	 * All DMA channel interfaces in HAL2 are designed to operate with
+	 * PBUS programmed for 2 cycles in D3, 2 cycles in D4 and 2 cycles
+	 * in D5. HAL2 is a 16-bit device which can accept both big and little
+	 * endian format. It assumes that even address bytes are on high
+	 * portion of PBUS (15:8) and assumes that HPC3 is programmed to
+	 * accept a live (unsynchronized) version of P_DREQ_N from HAL2.
+	 */
+#define HAL2_PBUS_DMACFG ((0 << HPC3_DMACFG_D3R_SHIFT) | \
+			  (2 << HPC3_DMACFG_D4R_SHIFT) | \
+			  (2 << HPC3_DMACFG_D5R_SHIFT) | \
+			  (0 << HPC3_DMACFG_D3W_SHIFT) | \
+			  (2 << HPC3_DMACFG_D4W_SHIFT) | \
+			  (2 << HPC3_DMACFG_D5W_SHIFT) | \
+				HPC3_DMACFG_DS16 | \
+				HPC3_DMACFG_EVENHI | \
+				HPC3_DMACFG_RTIME | \
+			  (8 << HPC3_DMACFG_BURST_SHIFT) | \
+				HPC3_DMACFG_DRQLIVE)
+	/*
+	 * Ignore what's mentioned in the specification and write value which
+	 * works in The Real World (TM)
+	 */
+	hpc3->pbus_dmacfg[hal2->dac.pbus.pbusnr][0] = 0x8208844;
+	hpc3->pbus_dmacfg[hal2->adc.pbus.pbusnr][0] = 0x8208844;
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, hal2, &hal2_ops);
+	if (err < 0) {
+		free_irq(SGI_HPCDMA_IRQ, hal2);
+		kfree(hal2);
+		return err;
+	}
+	*rchip = hal2;
+	return 0;
+}
+
+static int __devinit hal2_probe(struct platform_device *pdev)
+{
+	struct snd_card *card;
+	struct snd_hal2 *chip;
+	int err;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	err = hal2_create(card, &chip);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_card_set_dev(card, &pdev->dev);
+
+	err = hal2_pcm_create(chip);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	err = hal2_mixer_create(chip);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "SGI HAL2 Audio");
+	strcpy(card->shortname, "SGI HAL2 Audio");
+	sprintf(card->longname, "%s irq %i",
+		card->shortname,
+		SGI_HPCDMA_IRQ);
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	platform_set_drvdata(pdev, card);
+	return 0;
+}
+
+static int __devexit hal2_remove(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+
+	snd_card_free(card);
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+static struct platform_driver hal2_driver = {
+	.probe	= hal2_probe,
+	.remove	= __devexit_p(hal2_remove),
+	.driver = {
+		.name	= "sgihal2",
+		.owner	= THIS_MODULE,
+	}
+};
+
+static int __init alsa_card_hal2_init(void)
+{
+	return platform_driver_register(&hal2_driver);
+}
+
+static void __exit alsa_card_hal2_exit(void)
+{
+	platform_driver_unregister(&hal2_driver);
+}
+
+module_init(alsa_card_hal2_init);
+module_exit(alsa_card_hal2_exit);
diff --git a/linux/sound/mips/hal2.h b/linux/sound/mips/hal2.h
new file mode 100644
index 0000000..f19828b
--- /dev/null
+++ b/linux/sound/mips/hal2.h
@@ -0,0 +1,245 @@
+#ifndef __HAL2_H
+#define __HAL2_H
+
+/*
+ *  Driver for HAL2 sound processors
+ *  Copyright (c) 1999 Ulf Carlsson <ulfc@bun.falkenberg.se>
+ *  Copyright (c) 2001, 2002, 2003 Ladislav Michl <ladis@linux-mips.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/types.h>
+
+/* Indirect status register */
+
+#define H2_ISR_TSTATUS		0x01	/* RO: transaction status 1=busy */
+#define H2_ISR_USTATUS		0x02	/* RO: utime status bit 1=armed */
+#define H2_ISR_QUAD_MODE	0x04	/* codec mode 0=indigo 1=quad */
+#define H2_ISR_GLOBAL_RESET_N	0x08	/* chip global reset 0=reset */
+#define H2_ISR_CODEC_RESET_N	0x10	/* codec/synth reset 0=reset  */
+
+/* Revision register */
+
+#define H2_REV_AUDIO_PRESENT	0x8000	/* RO: audio present 0=present */
+#define H2_REV_BOARD_M		0x7000	/* RO: bits 14:12, board revision */
+#define H2_REV_MAJOR_CHIP_M	0x00F0	/* RO: bits 7:4, major chip revision */
+#define H2_REV_MINOR_CHIP_M	0x000F	/* RO: bits 3:0, minor chip revision */
+
+/* Indirect address register */
+
+/*
+ * Address of indirect internal register to be accessed. A write to this
+ * register initiates read or write access to the indirect registers in the
+ * HAL2. Note that there af four indirect data registers for write access to
+ * registers larger than 16 byte.
+ */
+
+#define H2_IAR_TYPE_M		0xF000	/* bits 15:12, type of functional */
+					/* block the register resides in */
+					/* 1=DMA Port */
+					/* 9=Global DMA Control */
+					/* 2=Bresenham */
+					/* 3=Unix Timer */
+#define H2_IAR_NUM_M		0x0F00	/* bits 11:8 instance of the */
+					/* blockin which the indirect */
+					/* register resides */
+					/* If IAR_TYPE_M=DMA Port: */
+					/* 1=Synth In */
+					/* 2=AES In */
+					/* 3=AES Out */
+					/* 4=DAC Out */
+					/* 5=ADC Out */
+					/* 6=Synth Control */
+					/* If IAR_TYPE_M=Global DMA Control: */
+					/* 1=Control */
+					/* If IAR_TYPE_M=Bresenham: */
+					/* 1=Bresenham Clock Gen 1 */
+					/* 2=Bresenham Clock Gen 2 */
+					/* 3=Bresenham Clock Gen 3 */
+					/* If IAR_TYPE_M=Unix Timer: */
+					/* 1=Unix Timer */
+#define H2_IAR_ACCESS_SELECT	0x0080	/* 1=read 0=write */
+#define H2_IAR_PARAM		0x000C	/* Parameter Select */
+#define H2_IAR_RB_INDEX_M	0x0003	/* Read Back Index */
+					/* 00:word0 */
+					/* 01:word1 */
+					/* 10:word2 */
+					/* 11:word3 */
+/*
+ * HAL2 internal addressing
+ *
+ * The HAL2 has "indirect registers" (idr) which are accessed by writing to the
+ * Indirect Data registers. Write the address to the Indirect Address register
+ * to transfer the data.
+ *
+ * We define the H2IR_* to the read address and H2IW_* to the write address and
+ * H2I_* to be fields in whatever register is referred to.
+ *
+ * When we write to indirect registers which are larger than one word (16 bit)
+ * we have to fill more than one indirect register before writing. When we read
+ * back however we have to read several times, each time with different Read
+ * Back Indexes (there are defs for doing this easily).
+ */
+
+/*
+ * Relay Control
+ */
+#define H2I_RELAY_C		0x9100
+#define H2I_RELAY_C_STATE	0x01		/* state of RELAY pin signal */
+
+/* DMA port enable */
+
+#define H2I_DMA_PORT_EN		0x9104
+#define H2I_DMA_PORT_EN_SY_IN	0x01		/* Synth_in DMA port */
+#define H2I_DMA_PORT_EN_AESRX	0x02		/* AES receiver DMA port */
+#define H2I_DMA_PORT_EN_AESTX	0x04		/* AES transmitter DMA port */
+#define H2I_DMA_PORT_EN_CODECTX	0x08		/* CODEC transmit DMA port */
+#define H2I_DMA_PORT_EN_CODECR	0x10		/* CODEC receive DMA port */
+
+#define H2I_DMA_END		0x9108 		/* global dma endian select */
+#define H2I_DMA_END_SY_IN	0x01		/* Synth_in DMA port */
+#define H2I_DMA_END_AESRX	0x02		/* AES receiver DMA port */
+#define H2I_DMA_END_AESTX	0x04		/* AES transmitter DMA port */
+#define H2I_DMA_END_CODECTX	0x08		/* CODEC transmit DMA port */
+#define H2I_DMA_END_CODECR	0x10		/* CODEC receive DMA port */
+						/* 0=b_end 1=l_end */
+
+#define H2I_DMA_DRV		0x910C  	/* global PBUS DMA enable */
+
+#define H2I_SYNTH_C		0x1104		/* Synth DMA control */
+
+#define H2I_AESRX_C		0x1204	 	/* AES RX dma control */
+
+#define H2I_C_TS_EN		0x20		/* Timestamp enable */
+#define H2I_C_TS_FRMT		0x40		/* Timestamp format */
+#define H2I_C_NAUDIO		0x80		/* Sign extend */
+
+/* AESRX CTL, 16 bit */
+
+#define H2I_AESTX_C		0x1304		/* AES TX DMA control */
+#define H2I_AESTX_C_CLKID_SHIFT	3		/* Bresenham Clock Gen 1-3 */
+#define H2I_AESTX_C_CLKID_M	0x18
+#define H2I_AESTX_C_DATAT_SHIFT	8		/* 1=mono 2=stereo (3=quad) */
+#define H2I_AESTX_C_DATAT_M	0x300
+
+/* CODEC registers */
+
+#define H2I_DAC_C1		0x1404 		/* DAC DMA control, 16 bit */
+#define H2I_DAC_C2		0x1408		/* DAC DMA control, 32 bit */
+#define H2I_ADC_C1		0x1504 		/* ADC DMA control, 16 bit */
+#define H2I_ADC_C2		0x1508		/* ADC DMA control, 32 bit */
+
+/* Bits in CTL1 register */
+
+#define H2I_C1_DMA_SHIFT	0		/* DMA channel */
+#define H2I_C1_DMA_M		0x7
+#define H2I_C1_CLKID_SHIFT	3		/* Bresenham Clock Gen 1-3 */
+#define H2I_C1_CLKID_M		0x18
+#define H2I_C1_DATAT_SHIFT	8		/* 1=mono 2=stereo (3=quad) */
+#define H2I_C1_DATAT_M		0x300
+
+/* Bits in CTL2 register */
+
+#define H2I_C2_R_GAIN_SHIFT	0		/* right a/d input gain */
+#define H2I_C2_R_GAIN_M		0xf
+#define H2I_C2_L_GAIN_SHIFT	4		/* left a/d input gain */
+#define H2I_C2_L_GAIN_M		0xf0
+#define H2I_C2_R_SEL		0x100		/* right input select */
+#define H2I_C2_L_SEL		0x200		/* left input select */
+#define H2I_C2_MUTE		0x400		/* mute */
+#define H2I_C2_DO1		0x00010000	/* digital output port bit 0 */
+#define H2I_C2_DO2		0x00020000	/* digital output port bit 1 */
+#define H2I_C2_R_ATT_SHIFT	18		/* right d/a output - */
+#define H2I_C2_R_ATT_M		0x007c0000	/* attenuation */
+#define H2I_C2_L_ATT_SHIFT	23		/* left d/a output - */
+#define H2I_C2_L_ATT_M		0x0f800000	/* attenuation */
+
+#define H2I_SYNTH_MAP_C		0x1104		/* synth dma handshake ctrl */
+
+/* Clock generator CTL 1, 16 bit */
+
+#define H2I_BRES1_C1		0x2104
+#define H2I_BRES2_C1		0x2204
+#define H2I_BRES3_C1		0x2304
+
+#define H2I_BRES_C1_SHIFT	0		/* 0=48.0 1=44.1 2=aes_rx */
+#define H2I_BRES_C1_M		0x03
+
+/* Clock generator CTL 2, 32 bit */
+
+#define H2I_BRES1_C2		0x2108
+#define H2I_BRES2_C2		0x2208
+#define H2I_BRES3_C2		0x2308
+
+#define H2I_BRES_C2_INC_SHIFT	0		/* increment value */
+#define H2I_BRES_C2_INC_M	0xffff
+#define H2I_BRES_C2_MOD_SHIFT	16		/* modcontrol value */
+#define H2I_BRES_C2_MOD_M	0xffff0000	/* modctrl=0xffff&(modinc-1) */
+
+/* Unix timer, 64 bit */
+
+#define H2I_UTIME		0x3104
+#define H2I_UTIME_0_LD		0xffff		/* microseconds, LSB's */
+#define H2I_UTIME_1_LD0		0x0f		/* microseconds, MSB's */
+#define H2I_UTIME_1_LD1		0xf0		/* tenths of microseconds */
+#define H2I_UTIME_2_LD		0xffff		/* seconds, LSB's */
+#define H2I_UTIME_3_LD		0xffff		/* seconds, MSB's */
+
+struct hal2_ctl_regs {
+	u32 _unused0[4];
+	u32 isr;		/* 0x10 Status Register */
+	u32 _unused1[3];
+	u32 rev;		/* 0x20 Revision Register */
+	u32 _unused2[3];
+	u32 iar;		/* 0x30 Indirect Address Register */
+	u32 _unused3[3];
+	u32 idr0;		/* 0x40 Indirect Data Register 0 */
+	u32 _unused4[3];
+	u32 idr1;		/* 0x50 Indirect Data Register 1 */
+	u32 _unused5[3];
+	u32 idr2;		/* 0x60 Indirect Data Register 2 */
+	u32 _unused6[3];
+	u32 idr3;		/* 0x70 Indirect Data Register 3 */
+};
+
+struct hal2_aes_regs {
+	u32 rx_stat[2];	/* Status registers */
+	u32 rx_cr[2];		/* Control registers */
+	u32 rx_ud[4];		/* User data window */
+	u32 rx_st[24];		/* Channel status data */
+
+	u32 tx_stat[1];	/* Status register */
+	u32 tx_cr[3];		/* Control registers */
+	u32 tx_ud[4];		/* User data window */
+	u32 tx_st[24];		/* Channel status data */
+};
+
+struct hal2_vol_regs {
+	u32 right;		/* Right volume */
+	u32 left;		/* Left volume */
+};
+
+struct hal2_syn_regs {
+	u32 _unused0[2];
+	u32 page;		/* DOC Page register */
+	u32 regsel;		/* DOC Register selection */
+	u32 dlow;		/* DOC Data low */
+	u32 dhigh;		/* DOC Data high */
+	u32 irq;		/* IRQ Status */
+	u32 dram;		/* DRAM Access */
+};
+
+#endif	/* __HAL2_H */
diff --git a/linux/sound/mips/sgio2audio.c b/linux/sound/mips/sgio2audio.c
new file mode 100644
index 0000000..717604c
--- /dev/null
+++ b/linux/sound/mips/sgio2audio.c
@@ -0,0 +1,989 @@
+/*
+ *   Sound driver for Silicon Graphics O2 Workstations A/V board audio.
+ *
+ *   Copyright 2003 Vivien Chappelier <vivien.chappelier@linux-mips.org>
+ *   Copyright 2008 Thomas Bogendoerfer <tsbogend@alpha.franken.de>
+ *   Mxier part taken from mace_audio.c:
+ *   Copyright 2007 Thorben Jändling <tj.trevelyan@gmail.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <asm/ip32/ip32_ints.h>
+#include <asm/ip32/mace.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#define SNDRV_GET_ID
+#include <sound/initval.h>
+#include <sound/ad1843.h>
+
+
+MODULE_AUTHOR("Vivien Chappelier <vivien.chappelier@linux-mips.org>");
+MODULE_DESCRIPTION("SGI O2 Audio");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Silicon Graphics, O2 Audio}}");
+
+static int index = SNDRV_DEFAULT_IDX1;  /* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;   /* ID for this card */
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for SGI O2 soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for SGI O2 soundcard.");
+
+
+#define AUDIO_CONTROL_RESET              BIT(0) /* 1: reset audio interface */
+#define AUDIO_CONTROL_CODEC_PRESENT      BIT(1) /* 1: codec detected */
+
+#define CODEC_CONTROL_WORD_SHIFT        0
+#define CODEC_CONTROL_READ              BIT(16)
+#define CODEC_CONTROL_ADDRESS_SHIFT     17
+
+#define CHANNEL_CONTROL_RESET           BIT(10) /* 1: reset channel */
+#define CHANNEL_DMA_ENABLE              BIT(9)  /* 1: enable DMA transfer */
+#define CHANNEL_INT_THRESHOLD_DISABLED  (0 << 5) /* interrupt disabled */
+#define CHANNEL_INT_THRESHOLD_25        (1 << 5) /* int on buffer >25% full */
+#define CHANNEL_INT_THRESHOLD_50        (2 << 5) /* int on buffer >50% full */
+#define CHANNEL_INT_THRESHOLD_75        (3 << 5) /* int on buffer >75% full */
+#define CHANNEL_INT_THRESHOLD_EMPTY     (4 << 5) /* int on buffer empty */
+#define CHANNEL_INT_THRESHOLD_NOT_EMPTY (5 << 5) /* int on buffer !empty */
+#define CHANNEL_INT_THRESHOLD_FULL      (6 << 5) /* int on buffer empty */
+#define CHANNEL_INT_THRESHOLD_NOT_FULL  (7 << 5) /* int on buffer !empty */
+
+#define CHANNEL_RING_SHIFT              12
+#define CHANNEL_RING_SIZE               (1 << CHANNEL_RING_SHIFT)
+#define CHANNEL_RING_MASK               (CHANNEL_RING_SIZE - 1)
+
+#define CHANNEL_LEFT_SHIFT 40
+#define CHANNEL_RIGHT_SHIFT 8
+
+struct snd_sgio2audio_chan {
+	int idx;
+	struct snd_pcm_substream *substream;
+	int pos;
+	snd_pcm_uframes_t size;
+	spinlock_t lock;
+};
+
+/* definition of the chip-specific record */
+struct snd_sgio2audio {
+	struct snd_card *card;
+
+	/* codec */
+	struct snd_ad1843 ad1843;
+	spinlock_t ad1843_lock;
+
+	/* channels */
+	struct snd_sgio2audio_chan channel[3];
+
+	/* resources */
+	void *ring_base;
+	dma_addr_t ring_base_dma;
+};
+
+/* AD1843 access */
+
+/*
+ * read_ad1843_reg returns the current contents of a 16 bit AD1843 register.
+ *
+ * Returns unsigned register value on success, -errno on failure.
+ */
+static int read_ad1843_reg(void *priv, int reg)
+{
+	struct snd_sgio2audio *chip = priv;
+	int val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->ad1843_lock, flags);
+
+	writeq((reg << CODEC_CONTROL_ADDRESS_SHIFT) |
+	       CODEC_CONTROL_READ, &mace->perif.audio.codec_control);
+	wmb();
+	val = readq(&mace->perif.audio.codec_control); /* flush bus */
+	udelay(200);
+
+	val = readq(&mace->perif.audio.codec_read);
+
+	spin_unlock_irqrestore(&chip->ad1843_lock, flags);
+	return val;
+}
+
+/*
+ * write_ad1843_reg writes the specified value to a 16 bit AD1843 register.
+ */
+static int write_ad1843_reg(void *priv, int reg, int word)
+{
+	struct snd_sgio2audio *chip = priv;
+	int val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->ad1843_lock, flags);
+
+	writeq((reg << CODEC_CONTROL_ADDRESS_SHIFT) |
+	       (word << CODEC_CONTROL_WORD_SHIFT),
+	       &mace->perif.audio.codec_control);
+	wmb();
+	val = readq(&mace->perif.audio.codec_control); /* flush bus */
+	udelay(200);
+
+	spin_unlock_irqrestore(&chip->ad1843_lock, flags);
+	return 0;
+}
+
+static int sgio2audio_gain_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = ad1843_get_gain_max(&chip->ad1843,
+					     (int)kcontrol->private_value);
+	return 0;
+}
+
+static int sgio2audio_gain_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol);
+	int vol;
+
+	vol = ad1843_get_gain(&chip->ad1843, (int)kcontrol->private_value);
+
+	ucontrol->value.integer.value[0] = (vol >> 8) & 0xFF;
+	ucontrol->value.integer.value[1] = vol & 0xFF;
+
+	return 0;
+}
+
+static int sgio2audio_gain_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol);
+	int newvol, oldvol;
+
+	oldvol = ad1843_get_gain(&chip->ad1843, kcontrol->private_value);
+	newvol = (ucontrol->value.integer.value[0] << 8) |
+		ucontrol->value.integer.value[1];
+
+	newvol = ad1843_set_gain(&chip->ad1843, kcontrol->private_value,
+		newvol);
+
+	return newvol != oldvol;
+}
+
+static int sgio2audio_source_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	static const char *texts[3] = {
+		"Cam Mic", "Mic", "Line"
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item >= 3)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int sgio2audio_source_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = ad1843_get_recsrc(&chip->ad1843);
+	return 0;
+}
+
+static int sgio2audio_source_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol);
+	int newsrc, oldsrc;
+
+	oldsrc = ad1843_get_recsrc(&chip->ad1843);
+	newsrc = ad1843_set_recsrc(&chip->ad1843,
+				   ucontrol->value.enumerated.item[0]);
+
+	return newsrc != oldsrc;
+}
+
+/* dac1/pcm0 mixer control */
+static struct snd_kcontrol_new sgio2audio_ctrl_pcm0 __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "PCM Playback Volume",
+	.index          = 0,
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_PCM_0,
+	.info           = sgio2audio_gain_info,
+	.get            = sgio2audio_gain_get,
+	.put            = sgio2audio_gain_put,
+};
+
+/* dac2/pcm1 mixer control */
+static struct snd_kcontrol_new sgio2audio_ctrl_pcm1 __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "PCM Playback Volume",
+	.index          = 1,
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_PCM_1,
+	.info           = sgio2audio_gain_info,
+	.get            = sgio2audio_gain_get,
+	.put            = sgio2audio_gain_put,
+};
+
+/* record level mixer control */
+static struct snd_kcontrol_new sgio2audio_ctrl_reclevel __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Capture Volume",
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_RECLEV,
+	.info           = sgio2audio_gain_info,
+	.get            = sgio2audio_gain_get,
+	.put            = sgio2audio_gain_put,
+};
+
+/* record level source control */
+static struct snd_kcontrol_new sgio2audio_ctrl_recsource __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Capture Source",
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info           = sgio2audio_source_info,
+	.get            = sgio2audio_source_get,
+	.put            = sgio2audio_source_put,
+};
+
+/* line mixer control */
+static struct snd_kcontrol_new sgio2audio_ctrl_line __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Line Playback Volume",
+	.index          = 0,
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_LINE,
+	.info           = sgio2audio_gain_info,
+	.get            = sgio2audio_gain_get,
+	.put            = sgio2audio_gain_put,
+};
+
+/* cd mixer control */
+static struct snd_kcontrol_new sgio2audio_ctrl_cd __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Line Playback Volume",
+	.index          = 1,
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_LINE_2,
+	.info           = sgio2audio_gain_info,
+	.get            = sgio2audio_gain_get,
+	.put            = sgio2audio_gain_put,
+};
+
+/* mic mixer control */
+static struct snd_kcontrol_new sgio2audio_ctrl_mic __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Mic Playback Volume",
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_MIC,
+	.info           = sgio2audio_gain_info,
+	.get            = sgio2audio_gain_get,
+	.put            = sgio2audio_gain_put,
+};
+
+
+static int __devinit snd_sgio2audio_new_mixer(struct snd_sgio2audio *chip)
+{
+	int err;
+
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&sgio2audio_ctrl_pcm0, chip));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&sgio2audio_ctrl_pcm1, chip));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&sgio2audio_ctrl_reclevel, chip));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&sgio2audio_ctrl_recsource, chip));
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&sgio2audio_ctrl_line, chip));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&sgio2audio_ctrl_cd, chip));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&sgio2audio_ctrl_mic, chip));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/* low-level audio interface DMA */
+
+/* get data out of bounce buffer, count must be a multiple of 32 */
+/* returns 1 if a period has elapsed */
+static int snd_sgio2audio_dma_pull_frag(struct snd_sgio2audio *chip,
+					unsigned int ch, unsigned int count)
+{
+	int ret;
+	unsigned long src_base, src_pos, dst_mask;
+	unsigned char *dst_base;
+	int dst_pos;
+	u64 *src;
+	s16 *dst;
+	u64 x;
+	unsigned long flags;
+	struct snd_pcm_runtime *runtime = chip->channel[ch].substream->runtime;
+
+	spin_lock_irqsave(&chip->channel[ch].lock, flags);
+
+	src_base = (unsigned long) chip->ring_base | (ch << CHANNEL_RING_SHIFT);
+	src_pos = readq(&mace->perif.audio.chan[ch].read_ptr);
+	dst_base = runtime->dma_area;
+	dst_pos = chip->channel[ch].pos;
+	dst_mask = frames_to_bytes(runtime, runtime->buffer_size) - 1;
+
+	/* check if a period has elapsed */
+	chip->channel[ch].size += (count >> 3); /* in frames */
+	ret = chip->channel[ch].size >= runtime->period_size;
+	chip->channel[ch].size %= runtime->period_size;
+
+	while (count) {
+		src = (u64 *)(src_base + src_pos);
+		dst = (s16 *)(dst_base + dst_pos);
+
+		x = *src;
+		dst[0] = (x >> CHANNEL_LEFT_SHIFT) & 0xffff;
+		dst[1] = (x >> CHANNEL_RIGHT_SHIFT) & 0xffff;
+
+		src_pos = (src_pos + sizeof(u64)) & CHANNEL_RING_MASK;
+		dst_pos = (dst_pos + 2 * sizeof(s16)) & dst_mask;
+		count -= sizeof(u64);
+	}
+
+	writeq(src_pos, &mace->perif.audio.chan[ch].read_ptr); /* in bytes */
+	chip->channel[ch].pos = dst_pos;
+
+	spin_unlock_irqrestore(&chip->channel[ch].lock, flags);
+	return ret;
+}
+
+/* put some DMA data in bounce buffer, count must be a multiple of 32 */
+/* returns 1 if a period has elapsed */
+static int snd_sgio2audio_dma_push_frag(struct snd_sgio2audio *chip,
+					unsigned int ch, unsigned int count)
+{
+	int ret;
+	s64 l, r;
+	unsigned long dst_base, dst_pos, src_mask;
+	unsigned char *src_base;
+	int src_pos;
+	u64 *dst;
+	s16 *src;
+	unsigned long flags;
+	struct snd_pcm_runtime *runtime = chip->channel[ch].substream->runtime;
+
+	spin_lock_irqsave(&chip->channel[ch].lock, flags);
+
+	dst_base = (unsigned long)chip->ring_base | (ch << CHANNEL_RING_SHIFT);
+	dst_pos = readq(&mace->perif.audio.chan[ch].write_ptr);
+	src_base = runtime->dma_area;
+	src_pos = chip->channel[ch].pos;
+	src_mask = frames_to_bytes(runtime, runtime->buffer_size) - 1;
+
+	/* check if a period has elapsed */
+	chip->channel[ch].size += (count >> 3); /* in frames */
+	ret = chip->channel[ch].size >= runtime->period_size;
+	chip->channel[ch].size %= runtime->period_size;
+
+	while (count) {
+		src = (s16 *)(src_base + src_pos);
+		dst = (u64 *)(dst_base + dst_pos);
+
+		l = src[0]; /* sign extend */
+		r = src[1]; /* sign extend */
+
+		*dst = ((l & 0x00ffffff) << CHANNEL_LEFT_SHIFT) |
+			((r & 0x00ffffff) << CHANNEL_RIGHT_SHIFT);
+
+		dst_pos = (dst_pos + sizeof(u64)) & CHANNEL_RING_MASK;
+		src_pos = (src_pos + 2 * sizeof(s16)) & src_mask;
+		count -= sizeof(u64);
+	}
+
+	writeq(dst_pos, &mace->perif.audio.chan[ch].write_ptr); /* in bytes */
+	chip->channel[ch].pos = src_pos;
+
+	spin_unlock_irqrestore(&chip->channel[ch].lock, flags);
+	return ret;
+}
+
+static int snd_sgio2audio_dma_start(struct snd_pcm_substream *substream)
+{
+	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream);
+	struct snd_sgio2audio_chan *chan = substream->runtime->private_data;
+	int ch = chan->idx;
+
+	/* reset DMA channel */
+	writeq(CHANNEL_CONTROL_RESET, &mace->perif.audio.chan[ch].control);
+	udelay(10);
+	writeq(0, &mace->perif.audio.chan[ch].control);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		/* push a full buffer */
+		snd_sgio2audio_dma_push_frag(chip, ch, CHANNEL_RING_SIZE - 32);
+	}
+	/* set DMA to wake on 50% empty and enable interrupt */
+	writeq(CHANNEL_DMA_ENABLE | CHANNEL_INT_THRESHOLD_50,
+	       &mace->perif.audio.chan[ch].control);
+	return 0;
+}
+
+static int snd_sgio2audio_dma_stop(struct snd_pcm_substream *substream)
+{
+	struct snd_sgio2audio_chan *chan = substream->runtime->private_data;
+
+	writeq(0, &mace->perif.audio.chan[chan->idx].control);
+	return 0;
+}
+
+static irqreturn_t snd_sgio2audio_dma_in_isr(int irq, void *dev_id)
+{
+	struct snd_sgio2audio_chan *chan = dev_id;
+	struct snd_pcm_substream *substream;
+	struct snd_sgio2audio *chip;
+	int count, ch;
+
+	substream = chan->substream;
+	chip = snd_pcm_substream_chip(substream);
+	ch = chan->idx;
+
+	/* empty the ring */
+	count = CHANNEL_RING_SIZE -
+		readq(&mace->perif.audio.chan[ch].depth) - 32;
+	if (snd_sgio2audio_dma_pull_frag(chip, ch, count))
+		snd_pcm_period_elapsed(substream);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t snd_sgio2audio_dma_out_isr(int irq, void *dev_id)
+{
+	struct snd_sgio2audio_chan *chan = dev_id;
+	struct snd_pcm_substream *substream;
+	struct snd_sgio2audio *chip;
+	int count, ch;
+
+	substream = chan->substream;
+	chip = snd_pcm_substream_chip(substream);
+	ch = chan->idx;
+	/* fill the ring */
+	count = CHANNEL_RING_SIZE -
+		readq(&mace->perif.audio.chan[ch].depth) - 32;
+	if (snd_sgio2audio_dma_push_frag(chip, ch, count))
+		snd_pcm_period_elapsed(substream);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t snd_sgio2audio_error_isr(int irq, void *dev_id)
+{
+	struct snd_sgio2audio_chan *chan = dev_id;
+	struct snd_pcm_substream *substream;
+
+	substream = chan->substream;
+	snd_sgio2audio_dma_stop(substream);
+	snd_sgio2audio_dma_start(substream);
+	return IRQ_HANDLED;
+}
+
+/* PCM part */
+/* PCM hardware definition */
+static struct snd_pcm_hardware snd_sgio2audio_pcm_hw = {
+	.info = (SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER),
+	.formats =          SNDRV_PCM_FMTBIT_S16_BE,
+	.rates =            SNDRV_PCM_RATE_8000_48000,
+	.rate_min =         8000,
+	.rate_max =         48000,
+	.channels_min =     2,
+	.channels_max =     2,
+	.buffer_bytes_max = 65536,
+	.period_bytes_min = 32768,
+	.period_bytes_max = 65536,
+	.periods_min =      1,
+	.periods_max =      1024,
+};
+
+/* PCM playback open callback */
+static int snd_sgio2audio_playback1_open(struct snd_pcm_substream *substream)
+{
+	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = snd_sgio2audio_pcm_hw;
+	runtime->private_data = &chip->channel[1];
+	return 0;
+}
+
+static int snd_sgio2audio_playback2_open(struct snd_pcm_substream *substream)
+{
+	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = snd_sgio2audio_pcm_hw;
+	runtime->private_data = &chip->channel[2];
+	return 0;
+}
+
+/* PCM capture open callback */
+static int snd_sgio2audio_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = snd_sgio2audio_pcm_hw;
+	runtime->private_data = &chip->channel[0];
+	return 0;
+}
+
+/* PCM close callback */
+static int snd_sgio2audio_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->private_data = NULL;
+	return 0;
+}
+
+
+/* hw_params callback */
+static int snd_sgio2audio_pcm_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						params_buffer_bytes(hw_params));
+}
+
+/* hw_free callback */
+static int snd_sgio2audio_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+/* prepare callback */
+static int snd_sgio2audio_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_sgio2audio_chan *chan = substream->runtime->private_data;
+	int ch = chan->idx;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->channel[ch].lock, flags);
+
+	/* Setup the pseudo-dma transfer pointers.  */
+	chip->channel[ch].pos = 0;
+	chip->channel[ch].size = 0;
+	chip->channel[ch].substream = substream;
+
+	/* set AD1843 format */
+	/* hardware format is always S16_LE */
+	switch (substream->stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		ad1843_setup_dac(&chip->ad1843,
+				 ch - 1,
+				 runtime->rate,
+				 SNDRV_PCM_FORMAT_S16_LE,
+				 runtime->channels);
+		break;
+	case SNDRV_PCM_STREAM_CAPTURE:
+		ad1843_setup_adc(&chip->ad1843,
+				 runtime->rate,
+				 SNDRV_PCM_FORMAT_S16_LE,
+				 runtime->channels);
+		break;
+	}
+	spin_unlock_irqrestore(&chip->channel[ch].lock, flags);
+	return 0;
+}
+
+/* trigger callback */
+static int snd_sgio2audio_pcm_trigger(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* start the PCM engine */
+		snd_sgio2audio_dma_start(substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		/* stop the PCM engine */
+		snd_sgio2audio_dma_stop(substream);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/* pointer callback */
+static snd_pcm_uframes_t
+snd_sgio2audio_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream);
+	struct snd_sgio2audio_chan *chan = substream->runtime->private_data;
+
+	/* get the current hardware pointer */
+	return bytes_to_frames(substream->runtime,
+			       chip->channel[chan->idx].pos);
+}
+
+/* operators */
+static struct snd_pcm_ops snd_sgio2audio_playback1_ops = {
+	.open =        snd_sgio2audio_playback1_open,
+	.close =       snd_sgio2audio_pcm_close,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_sgio2audio_pcm_hw_params,
+	.hw_free =     snd_sgio2audio_pcm_hw_free,
+	.prepare =     snd_sgio2audio_pcm_prepare,
+	.trigger =     snd_sgio2audio_pcm_trigger,
+	.pointer =     snd_sgio2audio_pcm_pointer,
+	.page =        snd_pcm_lib_get_vmalloc_page,
+	.mmap =        snd_pcm_lib_mmap_vmalloc,
+};
+
+static struct snd_pcm_ops snd_sgio2audio_playback2_ops = {
+	.open =        snd_sgio2audio_playback2_open,
+	.close =       snd_sgio2audio_pcm_close,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_sgio2audio_pcm_hw_params,
+	.hw_free =     snd_sgio2audio_pcm_hw_free,
+	.prepare =     snd_sgio2audio_pcm_prepare,
+	.trigger =     snd_sgio2audio_pcm_trigger,
+	.pointer =     snd_sgio2audio_pcm_pointer,
+	.page =        snd_pcm_lib_get_vmalloc_page,
+	.mmap =        snd_pcm_lib_mmap_vmalloc,
+};
+
+static struct snd_pcm_ops snd_sgio2audio_capture_ops = {
+	.open =        snd_sgio2audio_capture_open,
+	.close =       snd_sgio2audio_pcm_close,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_sgio2audio_pcm_hw_params,
+	.hw_free =     snd_sgio2audio_pcm_hw_free,
+	.prepare =     snd_sgio2audio_pcm_prepare,
+	.trigger =     snd_sgio2audio_pcm_trigger,
+	.pointer =     snd_sgio2audio_pcm_pointer,
+	.page =        snd_pcm_lib_get_vmalloc_page,
+	.mmap =        snd_pcm_lib_mmap_vmalloc,
+};
+
+/*
+ *  definitions of capture are omitted here...
+ */
+
+/* create a pcm device */
+static int __devinit snd_sgio2audio_new_pcm(struct snd_sgio2audio *chip)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	/* create first pcm device with one outputs and one input */
+	err = snd_pcm_new(chip->card, "SGI O2 Audio", 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	pcm->private_data = chip;
+	strcpy(pcm->name, "SGI O2 DAC1");
+
+	/* set operators */
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_sgio2audio_playback1_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&snd_sgio2audio_capture_ops);
+
+	/* create second  pcm device with one outputs and no input */
+	err = snd_pcm_new(chip->card, "SGI O2 Audio", 1, 1, 0, &pcm);
+	if (err < 0)
+		return err;
+
+	pcm->private_data = chip;
+	strcpy(pcm->name, "SGI O2 DAC2");
+
+	/* set operators */
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_sgio2audio_playback2_ops);
+
+	return 0;
+}
+
+static struct {
+	int idx;
+	int irq;
+	irqreturn_t (*isr)(int, void *);
+	const char *desc;
+} snd_sgio2_isr_table[] = {
+	{
+		.idx = 0,
+		.irq = MACEISA_AUDIO1_DMAT_IRQ,
+		.isr = snd_sgio2audio_dma_in_isr,
+		.desc = "Capture DMA Channel 0"
+	}, {
+		.idx = 0,
+		.irq = MACEISA_AUDIO1_OF_IRQ,
+		.isr = snd_sgio2audio_error_isr,
+		.desc = "Capture Overflow"
+	}, {
+		.idx = 1,
+		.irq = MACEISA_AUDIO2_DMAT_IRQ,
+		.isr = snd_sgio2audio_dma_out_isr,
+		.desc = "Playback DMA Channel 1"
+	}, {
+		.idx = 1,
+		.irq = MACEISA_AUDIO2_MERR_IRQ,
+		.isr = snd_sgio2audio_error_isr,
+		.desc = "Memory Error Channel 1"
+	}, {
+		.idx = 2,
+		.irq = MACEISA_AUDIO3_DMAT_IRQ,
+		.isr = snd_sgio2audio_dma_out_isr,
+		.desc = "Playback DMA Channel 2"
+	}, {
+		.idx = 2,
+		.irq = MACEISA_AUDIO3_MERR_IRQ,
+		.isr = snd_sgio2audio_error_isr,
+		.desc = "Memory Error Channel 2"
+	}
+};
+
+/* ALSA driver */
+
+static int snd_sgio2audio_free(struct snd_sgio2audio *chip)
+{
+	int i;
+
+	/* reset interface */
+	writeq(AUDIO_CONTROL_RESET, &mace->perif.audio.control);
+	udelay(1);
+	writeq(0, &mace->perif.audio.control);
+
+	/* release IRQ's */
+	for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++)
+		free_irq(snd_sgio2_isr_table[i].irq,
+			 &chip->channel[snd_sgio2_isr_table[i].idx]);
+
+	dma_free_coherent(NULL, MACEISA_RINGBUFFERS_SIZE,
+			  chip->ring_base, chip->ring_base_dma);
+
+	/* release card data */
+	kfree(chip);
+	return 0;
+}
+
+static int snd_sgio2audio_dev_free(struct snd_device *device)
+{
+	struct snd_sgio2audio *chip = device->device_data;
+
+	return snd_sgio2audio_free(chip);
+}
+
+static struct snd_device_ops ops = {
+	.dev_free = snd_sgio2audio_dev_free,
+};
+
+static int __devinit snd_sgio2audio_create(struct snd_card *card,
+					   struct snd_sgio2audio **rchip)
+{
+	struct snd_sgio2audio *chip;
+	int i, err;
+
+	*rchip = NULL;
+
+	/* check if a codec is attached to the interface */
+	/* (Audio or Audio/Video board present) */
+	if (!(readq(&mace->perif.audio.control) & AUDIO_CONTROL_CODEC_PRESENT))
+		return -ENOENT;
+
+	chip = kzalloc(sizeof(struct snd_sgio2audio), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+
+	chip->card = card;
+
+	chip->ring_base = dma_alloc_coherent(NULL, MACEISA_RINGBUFFERS_SIZE,
+					     &chip->ring_base_dma, GFP_USER);
+	if (chip->ring_base == NULL) {
+		printk(KERN_ERR
+		       "sgio2audio: could not allocate ring buffers\n");
+		kfree(chip);
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&chip->ad1843_lock);
+
+	/* initialize channels */
+	for (i = 0; i < 3; i++) {
+		spin_lock_init(&chip->channel[i].lock);
+		chip->channel[i].idx = i;
+	}
+
+	/* allocate IRQs */
+	for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) {
+		if (request_irq(snd_sgio2_isr_table[i].irq,
+				snd_sgio2_isr_table[i].isr,
+				0,
+				snd_sgio2_isr_table[i].desc,
+				&chip->channel[snd_sgio2_isr_table[i].idx])) {
+			snd_sgio2audio_free(chip);
+			printk(KERN_ERR "sgio2audio: cannot allocate irq %d\n",
+			       snd_sgio2_isr_table[i].irq);
+			return -EBUSY;
+		}
+	}
+
+	/* reset the interface */
+	writeq(AUDIO_CONTROL_RESET, &mace->perif.audio.control);
+	udelay(1);
+	writeq(0, &mace->perif.audio.control);
+	msleep_interruptible(1); /* give time to recover */
+
+	/* set ring base */
+	writeq(chip->ring_base_dma, &mace->perif.ctrl.ringbase);
+
+	/* attach the AD1843 codec */
+	chip->ad1843.read = read_ad1843_reg;
+	chip->ad1843.write = write_ad1843_reg;
+	chip->ad1843.chip = chip;
+
+	/* initialize the AD1843 codec */
+	err = ad1843_init(&chip->ad1843);
+	if (err < 0) {
+		snd_sgio2audio_free(chip);
+		return err;
+	}
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		snd_sgio2audio_free(chip);
+		return err;
+	}
+	*rchip = chip;
+	return 0;
+}
+
+static int __devinit snd_sgio2audio_probe(struct platform_device *pdev)
+{
+	struct snd_card *card;
+	struct snd_sgio2audio *chip;
+	int err;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	err = snd_sgio2audio_create(card, &chip);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_card_set_dev(card, &pdev->dev);
+
+	err = snd_sgio2audio_new_pcm(chip);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	err = snd_sgio2audio_new_mixer(chip);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "SGI O2 Audio");
+	strcpy(card->shortname, "SGI O2 Audio");
+	sprintf(card->longname, "%s irq %i-%i",
+		card->shortname,
+		MACEISA_AUDIO1_DMAT_IRQ,
+		MACEISA_AUDIO3_MERR_IRQ);
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	platform_set_drvdata(pdev, card);
+	return 0;
+}
+
+static int __devexit snd_sgio2audio_remove(struct platform_device *pdev)
+{
+	struct snd_card *card = platform_get_drvdata(pdev);
+
+	snd_card_free(card);
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+static struct platform_driver sgio2audio_driver = {
+	.probe	= snd_sgio2audio_probe,
+	.remove	= __devexit_p(snd_sgio2audio_remove),
+	.driver = {
+		.name	= "sgio2audio",
+		.owner	= THIS_MODULE,
+	}
+};
+
+static int __init alsa_card_sgio2audio_init(void)
+{
+	return platform_driver_register(&sgio2audio_driver);
+}
+
+static void __exit alsa_card_sgio2audio_exit(void)
+{
+	platform_driver_unregister(&sgio2audio_driver);
+}
+
+module_init(alsa_card_sgio2audio_init)
+module_exit(alsa_card_sgio2audio_exit)
diff --git a/linux/sound/oss/.gitignore b/linux/sound/oss/.gitignore
new file mode 100644
index 0000000..7efb12b
--- /dev/null
+++ b/linux/sound/oss/.gitignore
@@ -0,0 +1,4 @@
+#Ignore generated files
+maui_boot.h
+pss_boot.h
+trix_boot.h
diff --git a/linux/sound/oss/CHANGELOG b/linux/sound/oss/CHANGELOG
new file mode 100644
index 0000000..8706cd6
--- /dev/null
+++ b/linux/sound/oss/CHANGELOG
@@ -0,0 +1,369 @@
+Note these changes relate to Hannu's code and don't include the changes
+made outside of this for modularising the sound
+
+Changelog for version 3.8o
+--------------------------
+
+Since 3.8h
+- Included support for OPL3-SA1 and SoftOSS
+
+Since 3.8
+- Fixed SNDCTL_DSP_GETOSPACE
+- Compatibility fixes for Linux 2.1.47
+
+Since 3.8-beta21
+- Fixed all known bugs (I think).
+
+Since 3.8-beta8
+- Lot of fixes to audio playback code in dmabuf.c
+
+Since 3.8-beta6
+- Fixed the famous Quake delay bug.
+
+Since 3.8-beta5
+- Fixed many bugs in audio playback.
+
+Since 3.8-beta4
+- Just minor changes.
+
+Since 3.8-beta1
+- Major rewrite of audio playback handling.
+- Added AWE32 support by Takashi Iwai (in ./lowlevel/).
+
+Since 3.7-beta#
+- Passing of ioctl() parameters between soundcard.c and other modules has been
+changed so that arg always points to kernel space.
+- Some bugfixes.
+
+Since 3.7-beta5
+- Disabled MIDI input with GUS PnP (Interwave). There seems to be constant
+stream of received 0x00 bytes when the MIDI receiver is enabled.
+
+Since 3.5
+- Changes almost everywhere.
+- Support for OPTi 82C924-based sound cards.
+
+Since 3.5.4-beta8
+- Fixed a bug in handling of non-fragment sized writes in 16 bit/stereo mode
+  with GUS.
+- Limited minimum fragment size with some audio devices (GUS=512 and
+  SB=32). These devices require more time to "recover" from processing
+  of each fragment. 
+
+Since 3.5.4-beta6/7
+- There seems to be problems in the OPTi 82C930 so cards based on this
+  chip don't necessarily work yet. There are problems in detecting the 
+  MIDI interface. Also mixer volumes may be seriously wrong on some systems.
+  You can safely use this driver version with C930 if it looks to work.
+  However please don't complain if you have problems with it. C930 support
+  should be fixed in future releases.
+- Got initialization of GUS PnP to work. With this version GUS PnP should
+  work in GUS compatible mode after initialization using isapnptools.
+- Fixed a bug in handling of full duplex cards in write only mode. This has
+  been causing "audio device opening" errors with RealAudio player.
+
+Since 3.5.4.beta5
+- Changes to OPTi 82C930 driver.
+- Major changes to the Soundscape driver. The driver requires now just one
+  DMA channel. The extra audio/dsp device (the "Not functional" one) used
+  for code download in the earlier versions has been eliminated. There is now
+  just one /dev/dsp# device which is used both for code download and audio.
+
+Since 3.5.4.beta4
+- Minor changes.
+
+Since 3.5.4-beta2
+- Fixed silent playback with ESS 688/1688.
+- Got SB16 to work without the 16 bit DMA channel (only the 8 bit one
+  is required for 8 and 16 bit modes).
+- Added the "lowlevel" subdirectory for additional low level drivers that
+  are not part of USS core. See lowlevel/README for more info.
+- Included support for ACI mixer (by Markus Kuhn). ACI is a mixer used in
+  miroPCM sound cards. See lowlevel/aci.readme for more info.
+- Support for Aztech Washington chipset (AZT2316 ASIC).
+
+Since 3.5.4-beta1
+- Reduced clicking with AD1848.
+- Support for OPTi 82C930. Only half duplex at this time. 16 bit playback
+  is sometimes just white noise (occurs randomly).
+
+Since 3.5.2
+- Major changes to the SB/Jazz16/ESS driver (most parts rewritten).
+  The most noticeable new feature is support for multiple SB cards at the same
+  time.
+- Renamed sb16_midi.c to uart401.c. Also modified it to work also with
+  other MPU401 UART compatible cards than SB16/ESS/Jazz.
+- Some changes which reduce clicking in audio playback.
+- Copying policy is now GPL.
+
+Since 3.5.1
+- TB Maui initialization support
+Since 3.5
+- Improved handling of playback underrun situations.
+
+Since 3.5-beta10
+- Bug fixing
+
+Since 3.5-beta9
+- Fixed for compatibility with Linux 1.3.70 and later.
+- Changed boot time passing of 16 bit DMA channel number to SB driver.
+
+Since 3.5-beta8
+- Minor changes
+
+Since 3.5-beta7
+- enhancements to configure program (by Jeff Tranter):
+  - prompts are in same format as 1.3.x Linux kernel config program
+  - on-line help for each question
+  - fixed some compile warnings detected by gcc/g++ -Wall
+  - minor grammatical changes to prompts
+
+Since 3.5-beta6
+- Fixed bugs in mmap() support.
+- Minor changes to Maui driver.
+
+Since 3.5-beta5
+- Fixed crash after recording with ESS688. It's generally a good
+  idea to stop inbound DMA transfers before freeing the memory
+  buffer. 
+- Fixed handling of AD1845 codec (for example Shuttle Sound System).
+- Few other fixes.
+
+Since 3.5-beta4
+- Fixed bug in handling of uninitialized instruments with GUS.
+
+Since 3.5-beta3
+- Few changes which decrease popping at end/beginning of audio playback.
+
+Since 3.5-beta2
+- Removed MAD16+CS4231 hack made in previous version since it didn't
+  help.
+- Fixed the above bug in proper way and in proper place. Many thanks
+  to James Hightower.
+
+Since 3.5-beta1
+- Bug fixes.
+- Full duplex audio with MAD16+CS4231 may work now. The driver configures
+  SB DMA of MAD16 so that it doesn't conflict with codec's DMA channels.
+  The side effect is that all 8 bit DMA channels (0,1,3) are populated in 
+  duplex mode.
+
+Since 3.5-alpha9
+- Bug fixes (mostly in Jazz16 and ESS1688/688 supports).
+- Temporarily disabled recording with ESS1688/688 since it causes crash.
+- Changed audio buffer partitioning algorithm so that it selects
+  smaller fragment size than earlier. This improves real time capabilities
+  of the driver and makes recording to disk to work better. Unfortunately
+  this change breaks some programs which assume that fragments cannot be
+  shorter than 4096 bytes.
+
+Since 3.5-alpha8
+- Bug fixes
+
+Since 3.5-alpha7
+- Linux kernel compatible configuration (_EXPERIMENTAL_). Enable
+  using command "cd /linux/drivers/sound;make script" and then
+  just run kernel's make config normally.
+- Minor fixes to the SB support. Hopefully the driver works with
+  all SB models now.
+- Added support for ESS ES1688 "AudioDrive" based cards.
+
+Since 3.5-alpha6
+- SB Pro and SB16 supports are no longer separately selectable options.
+  Enabling SB enables them too.
+- Changed all #ifndef EXCLUDE_xx stuff to #ifdef CONFIG_xx. Modified
+configure to handle this. 
+- Removed initialization messages from the
+modularized version. They can be enabled by using init_trace=1 in
+the insmod command line (insmod sound init_trace=1).
+- More AIX stuff.
+- Added support for synchronizing dsp/audio devices with /dev/sequencer.
+- mmap() support for dsp/audio devices.
+
+Since 3.5-alpha5
+- AIX port.
+- Changed some xxx_PATCH macros in soundcard.h to work with
+  big endian machines.
+
+Since 3.5-alpha4
+- Removed the 'setfx' stuff from the version distributed with kernel
+  sources. Running 'setfx' is required again.
+
+Since 3.5-alpha3
+- Moved stuff from the 'setfx' program to the AudioTrix Pro driver.
+
+Since 3.5-alpha2
+- Modifications to makefile and configure.c. Unnecessary sources
+  are no longer compiled. Newly created local.h is also copied to
+  /etc/soundconf. "make oldconfig" reads /etc/soundconf and produces
+  new local.h which is compatible with current version of the driver.
+- Some fixes to the SB16 support.
+- Fixed random protection fault in gus_wave.c
+
+Since 3.5-alpha1
+- Modified to work with Linux-1.3.33 and later
+- Some minor changes
+
+Since 3.0.2
+- Support for CS4232 based PnP cards (AcerMagic S23 etc).
+- Full duplex support for some CS4231, CS4232 and AD1845 based cards
+(GUS MAX, AudioTrix Pro, AcerMagic S23 and many MAD16/Mozart cards
+having a codec mentioned above).
+- Almost fully rewritten loadable modules support.
+- Fixed some bugs.
+- Huge amount of testing (more testing is still required).
+- mmap() support (works with some cards). Requires much more testing.
+- Sample/patch/program loading for TB Maui/Tropez. No initialization
+since TB doesn't allow me to release that code.
+- Using CS4231 compatible codecs as timer for /dev/music.
+
+Since 3.0.1
+- Added allocation of I/O ports, DMA channels and interrupts
+to the initialization code. This may break modules support since
+the driver may not free some resources on unload. Should be fixed soon.
+
+Since 3.0
+- Some important bug fixes. 
+- select() for /dev/dsp and /dev/audio (Linux only).
+(To use select() with read, you have to call read() to start
+the recording. Calling write() kills recording immediately so
+use select() carefully when you are writing a half duplex app.
+Full duplex mode is not implemented yet.) Select works also with
+/dev/sequencer and /dev/music. Maybe with /dev/midi## too.
+
+Since 3.0-beta2
+- Minor fixes.
+- Added Readme.cards
+
+Since 3.0-beta1
+- Minor fixes to the modules support.
+- Eliminated call to sb_free_irq() in ad1848.c
+- Rewritten MAD16&Mozart support (not tested with MAD16 Pro).
+- Fix to DMA initialization of PSS cards.
+- Some fixes to ad1848/cs42xx mixer support (GUS MAX, MSS, etc.)
+- Fixed some bugs in the PSS driver which caused I/O errors with
+  the MSS mode (/dev/dsp).
+
+Since 3.0-950506
+- Recording with GUS MAX fixed. It works when the driver is configured
+  to use two DMA channels with GUS MAX (16 bit ones recommended).
+
+Since 3.0-94xxxx
+- Too many changes
+
+Since 3.0-940818
+- Fixes for Linux 1.1.4x.
+- Disables Disney Sound System with SG NX Pro 16 (less noise).
+
+Since 2.90-2
+- Fixes to soundcard.h
+- Non blocking mode to /dev/sequencer
+- Experimental detection code for Ensoniq Soundscape.
+
+Since 2.90
+- Minor and major bug fixes
+
+Since pre-3.0-940712
+- GUS MAX support
+- Partially working MSS/WSS support (could work with some cards).
+- Hardware u-Law and A-Law support with AD1848/CS4248 and CS4231 codecs
+  (GUS MAX, GUS16, WSS etc). Hardware ADPCM is possible with GUS16 and
+  GUS MAX, but it doesn't work yet.
+Since pre-3.0-940426
+- AD1848/CS4248/CS4231 codec support (MSS, GUS MAX, Aztec, Orchid etc).
+This codec chip is used in various sound cards. This version is developed
+for the 16 bit daughtercard of GUS. It should work with other cards also
+if the following requirements are met:
+	- The I/O, IRQ and DMA settings are jumper selectable or
+	the card is initialized by booting DOS before booting Linux (etc.).
+	- You add the IO, IRQ and DMA settings manually to the local.h.
+	  (Just define GUS16_BASE, GUS16_IRQ and GUS16_DMA). Note that
+	the base address bust be the base address of the codec chip not the
+	card itself. For the GUS16 these are the same but most MSS compatible
+	cards have the codec located at card_base+4.
+- Some minor changes
+
+Since 2.5 (******* MAJOR REWRITE ***********)
+
+This version is based on v2.3. I have tried to maintain two versions
+together so that this one should have the same features than v2.5.
+Something may still be missing. If you notice such things, please let me
+know.
+
+The Readme.v30 contains more details.
+
+- /dev/midi## devices.
+- /dev/sequencer2
+
+Since 2.5-beta2
+- Some fine tuning to the GUS v3.7 mixer code.
+- Fixed speed limits for the plain SB (1.0 to 2.0).
+
+Since 2.5-beta
+- Fixed OPL-3 detection with SB. Caused problems with PAS16.
+- GUS v3.7 mixer support.
+
+Since 2.4
+- Mixer support for Sound Galaxy NX Pro (define __SGNXPRO__ on your local.h).
+- Fixed truncated sound on /dev/dsp when the device is closed.
+- Linear volume mode for GUS
+- Pitch bends larger than +/- 2 octaves.
+- MIDI recording for SB and SB Pro. (Untested).
+- Some other fixes.
+- SB16 MIDI and DSP drivers only initialized if SB16 actually installed.
+- Implemented better detection for OPL-3. This should be useful if you
+  have an old SB Pro (the non-OPL-3 one) or a SB 2.0 clone which has a OPL-3.
+- SVR4.2 support by Ian Hartas. Initial ALPHA TEST version (untested).
+
+Since 2.3b
+- Fixed bug which made it impossible to make long recordings to disk.
+  Recording was not restarted after a buffer overflow situation.
+- Limited mixer support for GUS.
+- Numerous improvements to the GUS driver by Andrew Robinson. Including
+  some click removal etc.
+
+Since 2.3
+- Fixed some minor bugs in the SB16 driver.
+
+Since 2.2b
+- Full SB16 DSP support. 8/16 bit, mono/stereo
+- The SCO and FreeBSD versions should be in sync now. There are some
+  problems with SB16 and GUS in the FreeBSD versions.
+  The DMA buffer allocation of the SCO version has been polished but
+  there could still be some problems. At least it hogs memory.
+  The DMA channel
+  configuration method used in the SCO/System is a hack.
+- Support for the MPU emulation of the SB16.
+- Some big arrays are now allocated boot time. This makes the BSS segment
+  smaller which makes it possible to use the full driver with
+  NetBSD. These arrays are not allocated if no suitable sound card is available.
+- Fixed a bug in the compute_and_set_volume in gus_wave.c
+- Fixed the too fast mono playback problem of SB Pro and PAS16.
+
+Since 2.2
+- Stereo recording for SB Pro. Somehow it was missing and nobody
+  had noticed it earlier.
+- Minor polishing.
+- Interpreting of boot time arguments (sound=) for Linux.
+- Breakup of sb_dsp.c. Parts of the code has been moved to
+  sb_mixer.c and sb_midi.c
+
+Since 2.1
+- Preliminary support for SB16. 
+  - The SB16 mixer is supported in its native mode.
+  - Digitized voice capability up to 44.1 kHz/8 bit/mono
+    (16 bit and stereo support coming in the next release).
+- Fixed some bugs in the digitized voice driver for PAS16.
+- Proper initialization of the SB emulation of latest PAS16 models.
+
+- Significantly improved /dev/dsp and /dev/audio support.
+  - Now supports half duplex mode. It's now possible to record and
+    playback without closing and reopening the device.
+  - It's possible to use smaller buffers than earlier. There is a new
+    ioctl(fd, SNDCTL_DSP_SUBDIVIDE, &n) where n should be 1, 2 or 4.
+    This call instructs the driver to use smaller buffers. The default
+    buffer size (0.5 to 1.0 seconds) is divided by n. Should be called
+    immediately after opening the device.
+
+Since 2.0
+Just cosmetic changes. 
diff --git a/linux/sound/oss/Kconfig b/linux/sound/oss/Kconfig
new file mode 100644
index 0000000..76c0902
--- /dev/null
+++ b/linux/sound/oss/Kconfig
@@ -0,0 +1,547 @@
+# 18 Apr 1998, Michael Elizabeth Chastain, <mailto:mec@shout.net>
+# More hacking for modularisation.
+#
+# Prompt user for primary drivers.
+
+config SOUND_BCM_CS4297A
+	tristate "Crystal Sound CS4297a (for Swarm)"
+	depends on SIBYTE_SWARM
+	help
+	  The BCM91250A has a Crystal CS4297a on synchronous serial
+	  port B (in addition to the DB-9 serial port).  Say Y or M
+	  here to enable the sound chip instead of the UART.  Also
+	  note that CONFIG_KGDB should not be enabled at the same
+	  time, since it also attempts to use this UART port.
+
+config SOUND_VWSND
+	tristate "SGI Visual Workstation Sound"
+	depends on X86_VISWS
+	help
+	  Say Y or M if you have an SGI Visual Workstation and you want to be
+	  able to use its on-board audio.  Read
+	  <file:Documentation/sound/oss/vwsnd> for more info on this driver's
+	  capabilities.
+
+config SOUND_AU1550_AC97
+	tristate "Au1550/Au1200 AC97 Sound"
+	depends on SOC_AU1550 || SOC_AU1200
+
+config SOUND_MSNDCLAS
+	tristate "Support for Turtle Beach MultiSound Classic, Tahiti, Monterey"
+	depends on (m || !STANDALONE) && ISA
+	help
+	  Say M here if you have a Turtle Beach MultiSound Classic, Tahiti or
+	  Monterey (not for the Pinnacle or Fiji).
+
+	  See <file:Documentation/sound/oss/MultiSound> for important information
+	  about this driver.  Note that it has been discontinued, but the
+	  Voyetra Turtle Beach knowledge base entry for it is still available
+	  at <http://www.turtlebeach.com/site/kb_ftp/790.asp>.
+
+comment "Compiled-in MSND Classic support requires firmware during compilation."
+	depends on SOUND_PRIME && SOUND_MSNDCLAS=y
+
+config MSNDCLAS_HAVE_BOOT
+	bool
+	depends on SOUND_MSNDCLAS=y && !STANDALONE
+	default y
+
+config MSNDCLAS_INIT_FILE
+	string "Full pathname of MSNDINIT.BIN firmware file"
+	depends on SOUND_MSNDCLAS
+	default "/etc/sound/msndinit.bin"
+	help
+	  The MultiSound cards have two firmware files which are required for
+	  operation, and are not currently included. These files can be
+	  obtained from Turtle Beach. See
+	  <file:Documentation/sound/oss/MultiSound> for information on how to
+	  obtain this.
+
+config MSNDCLAS_PERM_FILE
+	string "Full pathname of MSNDPERM.BIN firmware file"
+	depends on SOUND_MSNDCLAS
+	default "/etc/sound/msndperm.bin"
+	help
+	  The MultiSound cards have two firmware files which are required for
+	  operation, and are not currently included. These files can be
+	  obtained from Turtle Beach. See
+	  <file:Documentation/sound/oss/MultiSound> for information on how to
+	  obtain this.
+
+config MSNDCLAS_IRQ
+	int "MSND Classic IRQ 5, 7, 9, 10, 11, 12"
+	depends on SOUND_MSNDCLAS=y
+	default "5"
+	help
+	  Interrupt Request line for the MultiSound Classic and related cards.
+
+config MSNDCLAS_MEM
+	hex "MSND Classic memory B0000, C8000, D0000, D8000, E0000, E8000"
+	depends on SOUND_MSNDCLAS=y
+	default "D0000"
+	help
+	  Memory-mapped I/O base address for the MultiSound Classic and
+	  related cards.
+
+config MSNDCLAS_IO
+	hex "MSND Classic I/O 210, 220, 230, 240, 250, 260, 290, 3E0"
+	depends on SOUND_MSNDCLAS=y
+	default "290"
+	help
+	  I/O port address for the MultiSound Classic and related cards.
+
+config SOUND_MSNDPIN
+	tristate "Support for Turtle Beach MultiSound Pinnacle, Fiji"
+	depends on (m || !STANDALONE) && ISA
+	help
+	  Say M here if you have a Turtle Beach MultiSound Pinnacle or Fiji.
+	  See <file:Documentation/sound/oss/MultiSound> for important information
+	  about this driver. Note that it has been discontinued, but the
+	  Voyetra Turtle Beach knowledge base entry for it is still available
+	  at <http://www.turtlebeach.com/site/kb_ftp/600.asp>.
+
+comment "Compiled-in MSND Pinnacle support requires firmware during compilation."
+	depends on SOUND_PRIME && SOUND_MSNDPIN=y
+
+config MSNDPIN_HAVE_BOOT
+	bool
+	depends on SOUND_MSNDPIN=y
+	default y
+
+config MSNDPIN_INIT_FILE
+	string "Full pathname of PNDSPINI.BIN firmware file"
+	depends on SOUND_MSNDPIN
+	default "/etc/sound/pndspini.bin"
+	help
+	  The MultiSound cards have two firmware files which are required
+	  for operation, and are not currently included. These files can be
+	  obtained from Turtle Beach. See
+	  <file:Documentation/sound/oss/MultiSound> for information on how to
+	  obtain this.
+
+config MSNDPIN_PERM_FILE
+	string "Full pathname of PNDSPERM.BIN firmware file"
+	depends on SOUND_MSNDPIN
+	default "/etc/sound/pndsperm.bin"
+	help
+	  The MultiSound cards have two firmware files which are required for
+	  operation, and are not currently included. These files can be
+	  obtained from Turtle Beach. See
+	  <file:Documentation/sound/oss/MultiSound> for information on how to
+	  obtain this.
+
+config MSNDPIN_IRQ
+	int "MSND Pinnacle IRQ 5, 7, 9, 10, 11, 12"
+	depends on SOUND_MSNDPIN=y
+	default "5"
+	help
+	  Interrupt request line for the primary synthesizer on MultiSound
+	  Pinnacle and Fiji sound cards.
+
+config MSNDPIN_MEM
+	hex "MSND Pinnacle memory B0000, C8000, D0000, D8000, E0000, E8000"
+	depends on SOUND_MSNDPIN=y
+	default "D0000"
+	help
+	  Memory-mapped I/O base address for the primary synthesizer on
+	  MultiSound Pinnacle and Fiji sound cards.
+
+config MSNDPIN_IO
+	hex "MSND Pinnacle I/O 210, 220, 230, 240, 250, 260, 290, 3E0"
+	depends on SOUND_MSNDPIN=y
+	default "290"
+	help
+	  Memory-mapped I/O base address for the primary synthesizer on
+	  MultiSound Pinnacle and Fiji sound cards.
+
+config MSNDPIN_DIGITAL
+	bool "MSND Pinnacle has S/PDIF I/O"
+	depends on SOUND_MSNDPIN=y
+	help
+	  If you have the S/PDIF daughter board for the Pinnacle or Fiji,
+	  answer Y here; otherwise, say N. If you have this, you will be able
+	  to play and record from the S/PDIF port (digital signal). See
+	  <file:Documentation/sound/oss/MultiSound> for information on how to make
+	  use of this capability.
+
+config MSNDPIN_NONPNP
+	bool "MSND Pinnacle non-PnP Mode"
+	depends on SOUND_MSNDPIN=y
+	help
+	  The Pinnacle and Fiji card resources can be configured either with
+	  PnP, or through a configuration port. Say Y here if your card is NOT
+	  in PnP mode. For the Pinnacle, configuration in non-PnP mode allows
+	  use of the IDE and joystick peripherals on the card as well; these
+	  do not show up when the card is in PnP mode. Specifying zero for any
+	  resource of a device will disable the device. If you are running the
+	  card in PnP mode, you must say N here and use isapnptools to
+	  configure the card's resources.
+
+comment "MSND Pinnacle DSP section will be configured to above parameters."
+	depends on SOUND_MSNDPIN=y && MSNDPIN_NONPNP
+
+config MSNDPIN_CFG
+	hex "MSND Pinnacle config port 250,260,270"
+	depends on MSNDPIN_NONPNP
+	default "250"
+	help
+	  This is the port which the Pinnacle and Fiji uses to configure the
+	  card's resources when not in PnP mode. If your card is in PnP mode,
+	  then be sure to say N to the previous option, "MSND Pinnacle Non-PnP
+	  Mode".
+
+comment "Pinnacle-specific Device Configuration (0 disables)"
+	depends on SOUND_MSNDPIN=y && MSNDPIN_NONPNP
+
+config MSNDPIN_MPU_IO
+	hex "MSND Pinnacle MPU I/O (e.g. 330)"
+	depends on MSNDPIN_NONPNP
+	default "0"
+	help
+	  Memory-mapped I/O base address for the Kurzweil daughterboard
+	  synthesizer on MultiSound Pinnacle and Fiji sound cards.
+
+config MSNDPIN_MPU_IRQ
+	int "MSND Pinnacle MPU IRQ (e.g. 9)"
+	depends on MSNDPIN_NONPNP
+	default "0"
+	help
+	  Interrupt request number for the Kurzweil daughterboard
+	  synthesizer on MultiSound Pinnacle and Fiji sound cards.
+
+config MSNDPIN_IDE_IO0
+	hex "MSND Pinnacle IDE I/O 0 (e.g. 170)"
+	depends on MSNDPIN_NONPNP
+	default "0"
+	help
+	  CD-ROM drive 0 memory-mapped I/O base address for the MultiSound
+	  Pinnacle and Fiji sound cards.
+
+config MSNDPIN_IDE_IO1
+	hex "MSND Pinnacle IDE I/O 1 (e.g. 376)"
+	depends on MSNDPIN_NONPNP
+	default "0"
+	help
+	  CD-ROM drive 1 memory-mapped I/O base address for the MultiSound
+	  Pinnacle and Fiji sound cards.
+
+config MSNDPIN_IDE_IRQ
+	int "MSND Pinnacle IDE IRQ (e.g. 15)"
+	depends on MSNDPIN_NONPNP
+	default "0"
+	help
+	  Interrupt request number for the IDE CD-ROM interface on the
+	  MultiSound Pinnacle and Fiji sound cards.
+
+config MSNDPIN_JOYSTICK_IO
+	hex "MSND Pinnacle joystick I/O (e.g. 200)"
+	depends on MSNDPIN_NONPNP
+	default "0"
+	help
+	  Memory-mapped I/O base address for the joystick port on MultiSound
+	  Pinnacle and Fiji sound cards.
+
+config MSND_FIFOSIZE
+	int "MSND buffer size (kB)"
+	depends on SOUND_MSNDPIN=y || SOUND_MSNDCLAS=y
+	default "128"
+	help
+	  Configures the size of each audio buffer, in kilobytes, for
+	  recording and playing in the MultiSound drivers (both the Classic
+	  and Pinnacle). Larger values reduce the chance of data overruns at
+	  the expense of overall latency. If unsure, use the default.
+
+menuconfig SOUND_OSS
+	tristate "OSS sound modules"
+	depends on ISA_DMA_API && VIRT_TO_BUS
+	help
+	  OSS is the Open Sound System suite of sound card drivers.  They make
+	  sound programming easier since they provide a common API.  Say Y or
+	  M here (the module will be called sound) if you haven't found a
+	  driver for your sound card above, then pick your driver from the
+	  list below.
+
+if SOUND_OSS
+
+config SOUND_TRACEINIT
+	bool "Verbose initialisation"
+	help
+	  Verbose soundcard initialization -- affects the format of autoprobe
+	  and initialization messages at boot time.
+
+config SOUND_DMAP
+	bool "Persistent DMA buffers"
+	---help---
+	  Linux can often have problems allocating DMA buffers for ISA sound
+	  cards on machines with more than 16MB of RAM. This is because ISA
+	  DMA buffers must exist below the 16MB boundary and it is quite
+	  possible that a large enough free block in this region cannot be
+	  found after the machine has been running for a while. If you say Y
+	  here the DMA buffers (64Kb) will be allocated at boot time and kept
+	  until the shutdown. This option is only useful if you said Y to
+	  "OSS sound modules", above. If you said M to "OSS sound modules"
+	  then you can get the persistent DMA buffer functionality by passing
+	  the command-line argument "dmabuf=1" to the sound module.
+
+	  Say Y unless you have 16MB or more RAM or a PCI sound card.
+
+config SOUND_VMIDI
+	tristate "Loopback MIDI device support"
+	help
+	  Support for MIDI loopback on port 1 or 2.
+
+config SOUND_TRIX
+	tristate "MediaTrix AudioTrix Pro support"
+	help
+	  Answer Y if you have the AudioTriX Pro sound card manufactured
+	  by MediaTrix.
+
+config TRIX_HAVE_BOOT
+	bool "Have TRXPRO.HEX firmware file"
+	depends on SOUND_TRIX=y && !STANDALONE
+	help
+	  The MediaTrix AudioTrix Pro has an on-board microcontroller which
+	  needs to be initialized by downloading the code from the file
+	  TRXPRO.HEX in the DOS driver directory. If you don't have the
+	  TRXPRO.HEX file handy you may skip this step. However, the SB and
+	  MPU-401 modes of AudioTrix Pro will not work without this file!
+
+config TRIX_BOOT_FILE
+	string "Full pathname of TRXPRO.HEX firmware file"
+	depends on TRIX_HAVE_BOOT
+	default "/etc/sound/trxpro.hex"
+	help
+	  Enter the full pathname of your TRXPRO.HEX file, starting from /.
+
+config SOUND_MSS
+	tristate "Microsoft Sound System support"
+	---help---
+	  Again think carefully before answering Y to this question.  It's
+	  safe to answer Y if you have the original Windows Sound System card
+	  made by Microsoft or Aztech SG 16 Pro (or NX16 Pro).  Also you may
+	  say Y in case your card is NOT among these:
+
+	  ATI Stereo F/X, AdLib, Audio Excell DSP16, Cardinal DSP16,
+	  Ensoniq SoundScape (and compatibles made by Reveal and Spea),
+	  Gravis Ultrasound, Gravis Ultrasound ACE, Gravis Ultrasound Max,
+	  Gravis Ultrasound with 16 bit option, Logitech Sound Man 16,
+	  Logitech SoundMan Games, Logitech SoundMan Wave, MAD16 Pro (OPTi
+	  82C929), Media Vision Jazz16, MediaTriX AudioTriX Pro, Microsoft
+	  Windows Sound System (MSS/WSS), Mozart (OAK OTI-601), Orchid
+	  SW32, Personal Sound System (PSS), Pro Audio Spectrum 16, Pro
+	  Audio Studio 16, Pro Sonic 16, Roland MPU-401 MIDI interface,
+	  Sound Blaster 1.0, Sound Blaster 16, Sound Blaster 16ASP, Sound
+	  Blaster 2.0, Sound Blaster AWE32, Sound Blaster Pro, TI TM4000M
+	  notebook, ThunderBoard, Turtle Beach Tropez, Yamaha FM
+	  synthesizers (OPL2, OPL3 and OPL4), 6850 UART MIDI Interface.
+
+	  For cards having native support in VoxWare, consult the card
+	  specific instructions in <file:Documentation/sound/oss/README.OSS>.
+	  Some drivers have their own MSS support and saying Y to this option
+	  will cause a conflict.
+
+	  If you compile the driver into the kernel, you have to add
+	  "ad1848=<io>,<irq>,<dma>,<dma2>[,<type>]" to the kernel command
+	  line.
+
+config SOUND_MPU401
+	tristate "MPU-401 support (NOT for SB16)"
+	---help---
+	  Be careful with this question.  The MPU401 interface is supported by
+	  all sound cards.  However, some natively supported cards have their
+	  own driver for MPU401.  Enabling this MPU401 option with these cards
+	  will cause a conflict.  Also, enabling MPU401 on a system that
+	  doesn't really have a MPU401 could cause some trouble.  If your card
+	  was in the list of supported cards, look at the card specific
+	  instructions in the <file:Documentation/sound/oss/README.OSS> file.  It
+	  is safe to answer Y if you have a true MPU401 MIDI interface card.
+
+	  If you compile the driver into the kernel, you have to add
+	  "mpu401=<io>,<irq>" to the kernel command line.
+
+config SOUND_PAS
+	tristate "ProAudioSpectrum 16 support"
+	---help---
+	  Answer Y only if you have a Pro Audio Spectrum 16, ProAudio Studio
+	  16 or Logitech SoundMan 16 sound card. Answer N if you have some
+	  other card made by Media Vision or Logitech since those are not
+	  PAS16 compatible. Please read <file:Documentation/sound/oss/PAS16>.
+	  It is not necessary to add Sound Blaster support separately; it
+	  is included in PAS support.
+
+	  If you compile the driver into the kernel, you have to add
+	  "pas2=<io>,<irq>,<dma>,<dma2>,<sbio>,<sbirq>,<sbdma>,<sbdma2>
+	  to the kernel command line.
+
+config PAS_JOYSTICK
+	bool "Enable PAS16 joystick port"
+	depends on SOUND_PAS=y
+	help
+	  Say Y here to enable the Pro Audio Spectrum 16's auxiliary joystick
+	  port.
+
+config SOUND_PSS
+	tristate "PSS (AD1848, ADSP-2115, ESC614) support"
+	help
+	  Answer Y or M if you have an Orchid SW32, Cardinal DSP16, Beethoven
+	  ADSP-16 or some other card based on the PSS chipset (AD1848 codec +
+	  ADSP-2115 DSP chip + Echo ESC614 ASIC CHIP). For more information on
+	  how to compile it into the kernel or as a module see the file
+	  <file:Documentation/sound/oss/PSS>.
+
+	  If you compile the driver into the kernel, you have to add
+	  "pss=<io>,<mssio>,<mssirq>,<mssdma>,<mpuio>,<mpuirq>" to the kernel
+	  command line.
+
+config PSS_MIXER
+	bool "Enable PSS mixer (Beethoven ADSP-16 and other compatible)"
+	depends on SOUND_PSS
+	help
+	  Answer Y for Beethoven ADSP-16. You may try to say Y also for other
+	  cards if they have master volume, bass, treble, and you can't
+	  control it under Linux. If you answer N for Beethoven ADSP-16, you
+	  can't control master volume, bass, treble and synth volume.
+
+	  If you said M to "PSS support" above, you may enable or disable this
+	  PSS mixer with the module parameter pss_mixer. For more information
+	  see the file <file:Documentation/sound/oss/PSS>.
+
+config PSS_HAVE_BOOT
+	bool "Have DSPxxx.LD firmware file"
+	depends on SOUND_PSS && !STANDALONE
+	help
+	  If you have the DSPxxx.LD file or SYNTH.LD file for you card, say Y
+	  to include this file. Without this file the synth device (OPL) may
+	  not work.
+
+config PSS_BOOT_FILE
+	string "Full pathname of DSPxxx.LD firmware file"
+	depends on PSS_HAVE_BOOT
+	default "/etc/sound/dsp001.ld"
+	help
+	  Enter the full pathname of your DSPxxx.LD file or SYNTH.LD file,
+	  starting from /.
+
+config SOUND_SB
+	tristate "100% Sound Blaster compatibles (SB16/32/64, ESS, Jazz16) support"
+	---help---
+	  Answer Y if you have an original Sound Blaster card made by Creative
+	  Labs or a 100% hardware compatible clone (like the Thunderboard or
+	  SM Games). For an unknown card you may answer Y if the card claims
+	  to be Sound Blaster-compatible.
+
+	  Please read the file <file:Documentation/sound/oss/Soundblaster>.
+
+	  You should also say Y here for cards based on the Avance Logic
+	  ALS-007 and ALS-1X0 chips (read <file:Documentation/sound/oss/ALS>) and
+	  for cards based on ESS chips (read
+	  <file:Documentation/sound/oss/ESS1868> and
+	  <file:Documentation/sound/oss/ESS>). If you have an SB AWE 32 or SB AWE
+	  64, say Y here and also to "AWE32 synth" below and read
+	  <file:Documentation/sound/oss/INSTALL.awe>. If you have an IBM Mwave
+	  card, say Y here and read <file:Documentation/sound/oss/mwave>.
+
+	  If you compile the driver into the kernel and don't want to use
+	  isapnp, you have to add "sb=<io>,<irq>,<dma>,<dma2>" to the kernel
+	  command line.
+
+	  You can say M here to compile this driver as a module; the module is
+	  called sb.
+
+config SOUND_YM3812
+	tristate "Yamaha FM synthesizer (YM3812/OPL-3) support"
+	---help---
+	  Answer Y if your card has a FM chip made by Yamaha (OPL2/OPL3/OPL4).
+	  Answering Y is usually a safe and recommended choice, however some
+	  cards may have software (TSR) FM emulation. Enabling FM support with
+	  these cards may cause trouble (I don't currently know of any such
+	  cards, however). Please read the file
+	  <file:Documentation/sound/oss/OPL3> if your card has an OPL3 chip.
+
+	  If you compile the driver into the kernel, you have to add
+	  "opl3=<io>" to the kernel command line.
+
+	  If unsure, say Y.
+
+config SOUND_UART6850
+	tristate "6850 UART support"
+	help
+	  This option enables support for MIDI interfaces based on the 6850
+	  UART chip. This interface is rarely found on sound cards. It's safe
+	  to answer N to this question.
+
+	  If you compile the driver into the kernel, you have to add
+	  "uart6850=<io>,<irq>" to the kernel command line.
+
+config SOUND_AEDSP16
+	tristate "Gallant Audio Cards (SC-6000 and SC-6600 based)"
+	---help---
+	  Answer Y if you have a Gallant's Audio Excel DSP 16 card. This
+	  driver supports Audio Excel DSP 16 but not the III nor PnP versions
+	  of this card.
+
+	  The Gallant's Audio Excel DSP 16 card can emulate either an SBPro or
+	  a Microsoft Sound System card, so you should have said Y to either
+	  "100% Sound Blaster compatibles (SB16/32/64, ESS, Jazz16) support"
+	  or "Microsoft Sound System support", above, and you need to answer
+	  the "MSS emulation" and "SBPro emulation" questions below
+	  accordingly. You should say Y to one and only one of these two
+	  questions.
+
+	  Read the <file:Documentation/sound/oss/README.OSS> file and the head of
+	  <file:sound/oss/aedsp16.c> as well as
+	  <file:Documentation/sound/oss/AudioExcelDSP16> to get more information
+	  about this driver and its configuration.
+
+config SC6600
+	bool "SC-6600 based audio cards (new Audio Excel DSP 16)"
+	depends on SOUND_AEDSP16
+	help
+	  The SC6600 is the new version of DSP mounted on the Audio Excel DSP
+	  16 cards. Find in the manual the FCC ID of your audio card and
+	  answer Y if you have an SC6600 DSP.
+
+config SC6600_JOY
+	bool "Activate SC-6600 Joystick Interface"
+	depends on SC6600
+	help
+	  Say Y here in order to use the joystick interface of the Audio Excel
+	  DSP 16 card.
+
+config SC6600_CDROM
+	int "SC-6600 CDROM Interface (4=None, 3=IDE, 1=Panasonic, 0=?Sony?)"
+	depends on SC6600
+	default "4"
+	help
+	  This is used to activate the CD-ROM interface of the Audio Excel
+	  DSP 16 card. Enter: 0 for Sony, 1 for Panasonic, 2 for IDE, 4 for no
+	  CD-ROM present.
+
+config SC6600_CDROMBASE
+	hex "SC-6600 CDROM Interface I/O Address"
+	depends on SC6600
+	default "0"
+	help
+	  Base I/O port address for the CD-ROM interface of the Audio Excel
+	  DSP 16 card.
+
+config SOUND_VIDC
+	tristate "VIDC 16-bit sound"
+	depends on ARM && (ARCH_ACORN || ARCH_CLPS7500)
+	help
+	  16-bit support for the VIDC onboard sound hardware found on Acorn
+	  machines.
+
+config SOUND_WAVEARTIST
+	tristate "Netwinder WaveArtist"
+	depends on ARM && ARCH_NETWINDER
+	help
+	  Say Y here to include support for the Rockwell WaveArtist sound
+	  system.  This driver is mainly for the NetWinder.
+
+config SOUND_KAHLUA
+	tristate "XpressAudio Sound Blaster emulation"
+	depends on SOUND_SB
+
+endif	# SOUND_OSS
+
diff --git a/linux/sound/oss/Makefile b/linux/sound/oss/Makefile
new file mode 100644
index 0000000..96f14dc
--- /dev/null
+++ b/linux/sound/oss/Makefile
@@ -0,0 +1,109 @@
+# Makefile for the Linux sound card driver
+#
+# 18 Apr 1998, Michael Elizabeth Chastain, <mailto:mec@shout.net>
+# Rewritten to use lists instead of if-statements.
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_SOUND_OSS)		+= sound.o
+
+# Please leave it as is, cause the link order is significant !
+
+obj-$(CONFIG_SOUND_AEDSP16)	+= aedsp16.o
+obj-$(CONFIG_SOUND_PSS)		+= pss.o ad1848.o mpu401.o
+obj-$(CONFIG_SOUND_TRIX)	+= trix.o ad1848.o sb_lib.o uart401.o
+obj-$(CONFIG_SOUND_MSS)		+= ad1848.o
+obj-$(CONFIG_SOUND_PAS)		+= pas2.o sb.o sb_lib.o uart401.o
+obj-$(CONFIG_SOUND_SB)		+= sb.o sb_lib.o uart401.o
+obj-$(CONFIG_SOUND_KAHLUA)	+= kahlua.o
+obj-$(CONFIG_SOUND_MPU401)	+= mpu401.o
+obj-$(CONFIG_SOUND_UART6850)	+= uart6850.o
+obj-$(CONFIG_SOUND_YM3812)	+= opl3.o
+obj-$(CONFIG_SOUND_VMIDI)	+= v_midi.o
+obj-$(CONFIG_SOUND_VIDC)	+= vidc_mod.o
+obj-$(CONFIG_SOUND_WAVEARTIST)	+= waveartist.o
+obj-$(CONFIG_SOUND_MSNDCLAS)	+= msnd.o msnd_classic.o
+obj-$(CONFIG_SOUND_MSNDPIN)	+= msnd.o msnd_pinnacle.o
+obj-$(CONFIG_SOUND_VWSND)	+= vwsnd.o
+obj-$(CONFIG_SOUND_AU1550_AC97)	+= au1550_ac97.o ac97_codec.o
+obj-$(CONFIG_SOUND_BCM_CS4297A)	+= swarm_cs4297a.o
+
+obj-$(CONFIG_DMASOUND)		+= dmasound/
+
+# Declare multi-part drivers.
+
+sound-objs	:= 							\
+    dev_table.o soundcard.o 		\
+    audio.o dmabuf.o					\
+    midi_synth.o midibuf.o					\
+    sequencer.o sound_timer.o sys_timer.o
+
+pas2-objs	:= pas2_card.o pas2_midi.o pas2_mixer.o pas2_pcm.o
+sb-objs		:= sb_card.o
+sb_lib-objs	:= sb_common.o sb_audio.o sb_midi.o sb_mixer.o sb_ess.o
+vidc_mod-objs	:= vidc.o vidc_fill.o
+
+hostprogs-y	:= bin2hex hex2hex
+
+# Files generated that shall be removed upon make clean
+clean-files := msndperm.c msndinit.c pndsperm.c pndspini.c \
+               pss_boot.h trix_boot.h
+
+# Firmware files that need translation
+#
+# The translated files are protected by a file that keeps track
+# of what name was used to build them.  If the name changes, they
+# will be forced to be remade.
+#
+
+# Turtle Beach MultiSound
+
+ifeq ($(CONFIG_MSNDCLAS_HAVE_BOOT),y)
+    $(obj)/msnd_classic.o: $(obj)/msndperm.c $(obj)/msndinit.c
+
+    $(obj)/msndperm.c: $(patsubst "%", %, $(CONFIG_MSNDCLAS_PERM_FILE)) $(obj)/bin2hex
+	$(obj)/bin2hex msndperm < $< > $@
+
+    $(obj)/msndinit.c: $(patsubst "%", %, $(CONFIG_MSNDCLAS_INIT_FILE)) $(obj)/bin2hex
+	$(obj)/bin2hex msndinit < $< > $@
+endif
+
+ifeq ($(CONFIG_MSNDPIN_HAVE_BOOT),y)
+    $(obj)/msnd_pinnacle.o: $(obj)/pndsperm.c $(obj)/pndspini.c
+
+    $(obj)/pndsperm.c: $(patsubst "%", %, $(CONFIG_MSNDPIN_PERM_FILE)) $(obj)/bin2hex
+	$(obj)/bin2hex pndsperm < $< > $@
+
+    $(obj)/pndspini.c: $(patsubst "%", %, $(CONFIG_MSNDPIN_INIT_FILE)) $(obj)/bin2hex
+	$(obj)/bin2hex pndspini < $< > $@
+endif
+
+# PSS (ECHO-ADI2111)
+
+$(obj)/pss.o: $(obj)/pss_boot.h
+
+ifeq ($(CONFIG_PSS_HAVE_BOOT),y)
+    $(obj)/pss_boot.h: $(patsubst "%", %, $(CONFIG_PSS_BOOT_FILE)) $(obj)/bin2hex
+	$(obj)/bin2hex pss_synth < $< > $@
+else
+    $(obj)/pss_boot.h:
+	(							\
+	    echo 'static unsigned char * pss_synth = NULL;';	\
+	    echo 'static int pss_synthLen = 0;';		\
+	) > $@
+endif
+
+# MediaTrix AudioTrix Pro
+
+$(obj)/trix.o: $(obj)/trix_boot.h
+
+ifeq ($(CONFIG_TRIX_HAVE_BOOT),y)
+    $(obj)/trix_boot.h: $(patsubst "%", %, $(CONFIG_TRIX_BOOT_FILE)) $(obj)/hex2hex
+	$(obj)/hex2hex -i trix_boot < $< > $@
+else
+    $(obj)/trix_boot.h:
+	(							\
+	    echo 'static unsigned char * trix_boot = NULL;';	\
+	    echo 'static int trix_boot_len = 0;';		\
+	) > $@
+endif
diff --git a/linux/sound/oss/README.FIRST b/linux/sound/oss/README.FIRST
new file mode 100644
index 0000000..90fdcf0
--- /dev/null
+++ b/linux/sound/oss/README.FIRST
@@ -0,0 +1,6 @@
+The modular sound driver patches were funded by Red Hat Software 
+(www.redhat.com). The sound driver here is thus a modified version of 
+Hannu's code. Please bear that in mind when considering the appropriate
+forums for bug reporting. 
+
+Alan Cox
diff --git a/linux/sound/oss/ac97_codec.c b/linux/sound/oss/ac97_codec.c
new file mode 100644
index 0000000..854c303
--- /dev/null
+++ b/linux/sound/oss/ac97_codec.c
@@ -0,0 +1,1203 @@
+/*
+ * ac97_codec.c: Generic AC97 mixer/modem module
+ *
+ * Derived from ac97 mixer in maestro and trident driver.
+ *
+ * Copyright 2000 Silicon Integrated System Corporation
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ **************************************************************************
+ *
+ * The Intel Audio Codec '97 specification is available at:
+ * http://download.intel.com/support/motherboards/desktop/sb/ac97_r23.pdf
+ *
+ **************************************************************************
+ *
+ * History
+ * May 02, 2003 Liam Girdwood <lrg@slimlogic.co.uk>
+ *	Removed non existant WM9700
+ *	Added support for WM9705, WM9708, WM9709, WM9710, WM9711
+ *	WM9712 and WM9717
+ * Mar 28, 2002 Randolph Bentson <bentson@holmsjoen.com>
+ *	corrections to support WM9707 in ViewPad 1000
+ * v0.4 Mar 15 2000 Ollie Lho
+ *	dual codecs support verified with 4 channels output
+ * v0.3 Feb 22 2000 Ollie Lho
+ *	bug fix for record mask setting
+ * v0.2 Feb 10 2000 Ollie Lho
+ *	add ac97_read_proc for /proc/driver/{vendor}/ac97
+ * v0.1 Jan 14 2000 Ollie Lho <ollie@sis.com.tw> 
+ *	Isolated from trident.c to support multiple ac97 codec
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/ac97_codec.h>
+#include <asm/uaccess.h>
+#include <linux/mutex.h>
+
+#define CODEC_ID_BUFSZ 14
+
+static int ac97_read_mixer(struct ac97_codec *codec, int oss_channel);
+static void ac97_write_mixer(struct ac97_codec *codec, int oss_channel, 
+			     unsigned int left, unsigned int right);
+static void ac97_set_mixer(struct ac97_codec *codec, unsigned int oss_mixer, unsigned int val );
+static int ac97_recmask_io(struct ac97_codec *codec, int rw, int mask);
+static int ac97_mixer_ioctl(struct ac97_codec *codec, unsigned int cmd, unsigned long arg);
+
+static int ac97_init_mixer(struct ac97_codec *codec);
+
+static int wolfson_init03(struct ac97_codec * codec);
+static int wolfson_init04(struct ac97_codec * codec);
+static int wolfson_init05(struct ac97_codec * codec);
+static int wolfson_init11(struct ac97_codec * codec);
+static int wolfson_init13(struct ac97_codec * codec);
+static int tritech_init(struct ac97_codec * codec);
+static int tritech_maestro_init(struct ac97_codec * codec);
+static int sigmatel_9708_init(struct ac97_codec *codec);
+static int sigmatel_9721_init(struct ac97_codec *codec);
+static int sigmatel_9744_init(struct ac97_codec *codec);
+static int ad1886_init(struct ac97_codec *codec);
+static int eapd_control(struct ac97_codec *codec, int);
+static int crystal_digital_control(struct ac97_codec *codec, int slots, int rate, int mode);
+static int cmedia_init(struct ac97_codec * codec);
+static int cmedia_digital_control(struct ac97_codec *codec, int slots, int rate, int mode);
+static int generic_digital_control(struct ac97_codec *codec, int slots, int rate, int mode);
+
+
+/*
+ *	AC97 operations.
+ *
+ *	If you are adding a codec then you should be able to use
+ *		eapd_ops - any codec that supports EAPD amp control (most)
+ *		null_ops - any ancient codec that supports nothing
+ *
+ *	The three functions are
+ *		init - used for non AC97 standard initialisation
+ *		amplifier - used to do amplifier control (1=on 0=off)
+ *		digital - switch to digital modes (0 = analog)
+ *
+ *	Not all codecs support all features, not all drivers use all the
+ *	operations yet
+ */
+ 
+static struct ac97_ops null_ops = { NULL, NULL, NULL };
+static struct ac97_ops default_ops = { NULL, eapd_control, NULL };
+static struct ac97_ops default_digital_ops = { NULL, eapd_control, generic_digital_control};
+static struct ac97_ops wolfson_ops03 = { wolfson_init03, NULL, NULL };
+static struct ac97_ops wolfson_ops04 = { wolfson_init04, NULL, NULL };
+static struct ac97_ops wolfson_ops05 = { wolfson_init05, NULL, NULL };
+static struct ac97_ops wolfson_ops11 = { wolfson_init11, NULL, NULL };
+static struct ac97_ops wolfson_ops13 = { wolfson_init13, NULL, NULL };
+static struct ac97_ops tritech_ops = { tritech_init, NULL, NULL };
+static struct ac97_ops tritech_m_ops = { tritech_maestro_init, NULL, NULL };
+static struct ac97_ops sigmatel_9708_ops = { sigmatel_9708_init, NULL, NULL };
+static struct ac97_ops sigmatel_9721_ops = { sigmatel_9721_init, NULL, NULL };
+static struct ac97_ops sigmatel_9744_ops = { sigmatel_9744_init, NULL, NULL };
+static struct ac97_ops crystal_digital_ops = { NULL, eapd_control, crystal_digital_control };
+static struct ac97_ops ad1886_ops = { ad1886_init, eapd_control, NULL };
+static struct ac97_ops cmedia_ops = { NULL, eapd_control, NULL};
+static struct ac97_ops cmedia_digital_ops = { cmedia_init, eapd_control, cmedia_digital_control};
+
+/* sorted by vendor/device id */
+static const struct {
+	u32 id;
+	char *name;
+	struct ac97_ops *ops;
+	int flags;
+} ac97_codec_ids[] = {
+	{0x41445303, "Analog Devices AD1819",	&null_ops},
+	{0x41445340, "Analog Devices AD1881",	&null_ops},
+	{0x41445348, "Analog Devices AD1881A",	&null_ops},
+	{0x41445360, "Analog Devices AD1885",	&default_ops},
+	{0x41445361, "Analog Devices AD1886",	&ad1886_ops},
+	{0x41445370, "Analog Devices AD1981",	&null_ops},
+	{0x41445372, "Analog Devices AD1981A",	&null_ops},
+	{0x41445374, "Analog Devices AD1981B",	&null_ops},
+	{0x41445460, "Analog Devices AD1885",	&default_ops},
+	{0x41445461, "Analog Devices AD1886",	&ad1886_ops},
+	{0x414B4D00, "Asahi Kasei AK4540",	&null_ops},
+	{0x414B4D01, "Asahi Kasei AK4542",	&null_ops},
+	{0x414B4D02, "Asahi Kasei AK4543",	&null_ops},
+	{0x414C4326, "ALC100P",			&null_ops},
+	{0x414C4710, "ALC200/200P",		&null_ops},
+	{0x414C4720, "ALC650",			&default_digital_ops},
+	{0x434D4941, "CMedia",			&cmedia_ops,		AC97_NO_PCM_VOLUME },
+	{0x434D4942, "CMedia",			&cmedia_ops,		AC97_NO_PCM_VOLUME },
+	{0x434D4961, "CMedia",			&cmedia_digital_ops,	AC97_NO_PCM_VOLUME },
+	{0x43525900, "Cirrus Logic CS4297",	&default_ops},
+	{0x43525903, "Cirrus Logic CS4297",	&default_ops},
+	{0x43525913, "Cirrus Logic CS4297A rev A", &default_ops},
+	{0x43525914, "Cirrus Logic CS4297A rev B", &default_ops},
+	{0x43525923, "Cirrus Logic CS4298",	&null_ops},
+	{0x4352592B, "Cirrus Logic CS4294",	&null_ops},
+	{0x4352592D, "Cirrus Logic CS4294",	&null_ops},
+	{0x43525931, "Cirrus Logic CS4299 rev A", &crystal_digital_ops},
+	{0x43525933, "Cirrus Logic CS4299 rev C", &crystal_digital_ops},
+	{0x43525934, "Cirrus Logic CS4299 rev D", &crystal_digital_ops},
+	{0x43585430, "CXT48",			&default_ops,		AC97_DELUDED_MODEM },
+	{0x43585442, "CXT66",			&default_ops,		AC97_DELUDED_MODEM },
+	{0x44543031, "Diamond Technology DT0893", &default_ops},
+	{0x45838308, "ESS Allegro ES1988",	&null_ops},
+	{0x49434511, "ICE1232",			&null_ops}, /* I hope --jk */
+	{0x4e534331, "National Semiconductor LM4549", &null_ops},
+	{0x53494c22, "Silicon Laboratory Si3036", &null_ops},
+	{0x53494c23, "Silicon Laboratory Si3038", &null_ops},
+	{0x545200FF, "TriTech TR?????",		&tritech_m_ops},
+	{0x54524102, "TriTech TR28022",		&null_ops},
+	{0x54524103, "TriTech TR28023",		&null_ops},
+	{0x54524106, "TriTech TR28026",		&null_ops},
+	{0x54524108, "TriTech TR28028",		&tritech_ops},
+	{0x54524123, "TriTech TR A5",		&null_ops},
+	{0x574D4C03, "Wolfson WM9703/07/08/17",	&wolfson_ops03},
+	{0x574D4C04, "Wolfson WM9704M/WM9704Q",	&wolfson_ops04},
+	{0x574D4C05, "Wolfson WM9705/WM9710",   &wolfson_ops05},
+	{0x574D4C09, "Wolfson WM9709",		&null_ops},
+	{0x574D4C12, "Wolfson WM9711/9712",	&wolfson_ops11},
+	{0x574D4C13, "Wolfson WM9713",	&wolfson_ops13, AC97_DEFAULT_POWER_OFF},
+	{0x83847600, "SigmaTel STAC????",	&null_ops},
+	{0x83847604, "SigmaTel STAC9701/3/4/5", &null_ops},
+	{0x83847605, "SigmaTel STAC9704",	&null_ops},
+	{0x83847608, "SigmaTel STAC9708",	&sigmatel_9708_ops},
+	{0x83847609, "SigmaTel STAC9721/23",	&sigmatel_9721_ops},
+	{0x83847644, "SigmaTel STAC9744/45",	&sigmatel_9744_ops},
+	{0x83847652, "SigmaTel STAC9752/53",	&default_ops},
+	{0x83847656, "SigmaTel STAC9756/57",	&sigmatel_9744_ops},
+	{0x83847666, "SigmaTel STAC9750T",	&sigmatel_9744_ops},
+	{0x83847684, "SigmaTel STAC9783/84?",	&null_ops},
+	{0x57454301, "Winbond 83971D",		&null_ops},
+};
+
+/* this table has default mixer values for all OSS mixers. */
+static struct mixer_defaults {
+	int mixer;
+	unsigned int value;
+} mixer_defaults[SOUND_MIXER_NRDEVICES] = {
+	/* all values 0 -> 100 in bytes */
+	{SOUND_MIXER_VOLUME,	0x4343},
+	{SOUND_MIXER_BASS,	0x4343},
+	{SOUND_MIXER_TREBLE,	0x4343},
+	{SOUND_MIXER_PCM,	0x4343},
+	{SOUND_MIXER_SPEAKER,	0x4343},
+	{SOUND_MIXER_LINE,	0x4343},
+	{SOUND_MIXER_MIC,	0x0000},
+	{SOUND_MIXER_CD,	0x4343},
+	{SOUND_MIXER_ALTPCM,	0x4343},
+	{SOUND_MIXER_IGAIN,	0x4343},
+	{SOUND_MIXER_LINE1,	0x4343},
+	{SOUND_MIXER_PHONEIN,	0x4343},
+	{SOUND_MIXER_PHONEOUT,	0x4343},
+	{SOUND_MIXER_VIDEO,	0x4343},
+	{-1,0}
+};
+
+/* table to scale scale from OSS mixer value to AC97 mixer register value */	
+static struct ac97_mixer_hw {
+	unsigned char offset;
+	int scale;
+} ac97_hw[SOUND_MIXER_NRDEVICES]= {
+	[SOUND_MIXER_VOLUME]	=	{AC97_MASTER_VOL_STEREO,64},
+	[SOUND_MIXER_BASS]	=	{AC97_MASTER_TONE,	16},
+	[SOUND_MIXER_TREBLE]	=	{AC97_MASTER_TONE,	16},
+	[SOUND_MIXER_PCM]	=	{AC97_PCMOUT_VOL,	32},
+	[SOUND_MIXER_SPEAKER]	=	{AC97_PCBEEP_VOL,	16},
+	[SOUND_MIXER_LINE]	=	{AC97_LINEIN_VOL,	32},
+	[SOUND_MIXER_MIC]	=	{AC97_MIC_VOL,		32},
+	[SOUND_MIXER_CD]	=	{AC97_CD_VOL,		32},
+	[SOUND_MIXER_ALTPCM]	=	{AC97_HEADPHONE_VOL,	64},
+	[SOUND_MIXER_IGAIN]	=	{AC97_RECORD_GAIN,	16},
+	[SOUND_MIXER_LINE1]	=	{AC97_AUX_VOL,		32},
+	[SOUND_MIXER_PHONEIN]	= 	{AC97_PHONE_VOL,	32},
+	[SOUND_MIXER_PHONEOUT]	= 	{AC97_MASTER_VOL_MONO,	64},
+	[SOUND_MIXER_VIDEO]	=	{AC97_VIDEO_VOL,	32},
+};
+
+/* the following tables allow us to go from OSS <-> ac97 quickly. */
+enum ac97_recsettings {
+	AC97_REC_MIC=0,
+	AC97_REC_CD,
+	AC97_REC_VIDEO,
+	AC97_REC_AUX,
+	AC97_REC_LINE,
+	AC97_REC_STEREO, /* combination of all enabled outputs..  */
+	AC97_REC_MONO,	      /*.. or the mono equivalent */
+	AC97_REC_PHONE
+};
+
+static const unsigned int ac97_rm2oss[] = {
+	[AC97_REC_MIC] 	 = SOUND_MIXER_MIC,
+	[AC97_REC_CD] 	 = SOUND_MIXER_CD,
+	[AC97_REC_VIDEO] = SOUND_MIXER_VIDEO,
+	[AC97_REC_AUX] 	 = SOUND_MIXER_LINE1,
+	[AC97_REC_LINE]  = SOUND_MIXER_LINE,
+	[AC97_REC_STEREO]= SOUND_MIXER_IGAIN,
+	[AC97_REC_PHONE] = SOUND_MIXER_PHONEIN
+};
+
+/* indexed by bit position */
+static const unsigned int ac97_oss_rm[] = {
+	[SOUND_MIXER_MIC] 	= AC97_REC_MIC,
+	[SOUND_MIXER_CD] 	= AC97_REC_CD,
+	[SOUND_MIXER_VIDEO] 	= AC97_REC_VIDEO,
+	[SOUND_MIXER_LINE1] 	= AC97_REC_AUX,
+	[SOUND_MIXER_LINE] 	= AC97_REC_LINE,
+	[SOUND_MIXER_IGAIN]	= AC97_REC_STEREO,
+	[SOUND_MIXER_PHONEIN] 	= AC97_REC_PHONE
+};
+
+static LIST_HEAD(codecs);
+static LIST_HEAD(codec_drivers);
+static DEFINE_MUTEX(codec_mutex);
+
+/* reads the given OSS mixer from the ac97 the caller must have insured that the ac97 knows
+   about that given mixer, and should be holding a spinlock for the card */
+static int ac97_read_mixer(struct ac97_codec *codec, int oss_channel) 
+{
+	u16 val;
+	int ret = 0;
+	int scale;
+	struct ac97_mixer_hw *mh = &ac97_hw[oss_channel];
+
+	val = codec->codec_read(codec , mh->offset);
+
+	if (val & AC97_MUTE) {
+		ret = 0;
+	} else if (AC97_STEREO_MASK & (1 << oss_channel)) {
+		/* nice stereo mixers .. */
+		int left,right;
+
+		left = (val >> 8)  & 0x7f;
+		right = val  & 0x7f;
+
+		if (oss_channel == SOUND_MIXER_IGAIN) {
+			right = (right * 100) / mh->scale;
+			left = (left * 100) / mh->scale;
+		} else {
+			/* these may have 5 or 6 bit resolution */
+			if(oss_channel == SOUND_MIXER_VOLUME || oss_channel == SOUND_MIXER_ALTPCM)
+				scale = (1 << codec->bit_resolution);
+			else
+				scale = mh->scale;
+
+			right = 100 - ((right * 100) / scale);
+			left = 100 - ((left * 100) / scale);
+		}
+		ret = left | (right << 8);
+	} else if (oss_channel == SOUND_MIXER_SPEAKER) {
+		ret = 100 - ((((val & 0x1e)>>1) * 100) / mh->scale);
+	} else if (oss_channel == SOUND_MIXER_PHONEIN) {
+		ret = 100 - (((val & 0x1f) * 100) / mh->scale);
+	} else if (oss_channel == SOUND_MIXER_PHONEOUT) {
+		scale = (1 << codec->bit_resolution);
+		ret = 100 - (((val & 0x1f) * 100) / scale);
+	} else if (oss_channel == SOUND_MIXER_MIC) {
+		ret = 100 - (((val & 0x1f) * 100) / mh->scale);
+		/*  the low bit is optional in the tone sliders and masking
+		    it lets us avoid the 0xf 'bypass'.. */
+	} else if (oss_channel == SOUND_MIXER_BASS) {
+		ret = 100 - ((((val >> 8) & 0xe) * 100) / mh->scale);
+	} else if (oss_channel == SOUND_MIXER_TREBLE) {
+		ret = 100 - (((val & 0xe) * 100) / mh->scale);
+	}
+
+#ifdef DEBUG
+	printk("ac97_codec: read OSS mixer %2d (%s ac97 register 0x%02x), "
+	       "0x%04x -> 0x%04x\n",
+	       oss_channel, codec->id ? "Secondary" : "Primary",
+	       mh->offset, val, ret);
+#endif
+
+	return ret;
+}
+
+/* write the OSS encoded volume to the given OSS encoded mixer, again caller's job to
+   make sure all is well in arg land, call with spinlock held */
+static void ac97_write_mixer(struct ac97_codec *codec, int oss_channel,
+		      unsigned int left, unsigned int right)
+{
+	u16 val = 0;
+	int scale;
+	struct ac97_mixer_hw *mh = &ac97_hw[oss_channel];
+
+#ifdef DEBUG
+	printk("ac97_codec: wrote OSS mixer %2d (%s ac97 register 0x%02x), "
+	       "left vol:%2d, right vol:%2d:",
+	       oss_channel, codec->id ? "Secondary" : "Primary",
+	       mh->offset, left, right);
+#endif
+
+	if (AC97_STEREO_MASK & (1 << oss_channel)) {
+		/* stereo mixers */
+		if (left == 0 && right == 0) {
+			val = AC97_MUTE;
+		} else {
+			if (oss_channel == SOUND_MIXER_IGAIN) {
+				right = (right * mh->scale) / 100;
+				left = (left * mh->scale) / 100;
+				if (right >= mh->scale)
+					right = mh->scale-1;
+				if (left >= mh->scale)
+					left = mh->scale-1;
+			} else {
+				/* these may have 5 or 6 bit resolution */
+				if (oss_channel == SOUND_MIXER_VOLUME ||
+				    oss_channel == SOUND_MIXER_ALTPCM)
+					scale = (1 << codec->bit_resolution);
+				else
+					scale = mh->scale;
+
+				right = ((100 - right) * scale) / 100;
+				left = ((100 - left) * scale) / 100;
+				if (right >= scale)
+					right = scale-1;
+				if (left >= scale)
+					left = scale-1;
+			}
+			val = (left << 8) | right;
+		}
+	} else if (oss_channel == SOUND_MIXER_BASS) {
+		val = codec->codec_read(codec , mh->offset) & ~0x0f00;
+		left = ((100 - left) * mh->scale) / 100;
+		if (left >= mh->scale)
+			left = mh->scale-1;
+		val |= (left << 8) & 0x0e00;
+	} else if (oss_channel == SOUND_MIXER_TREBLE) {
+		val = codec->codec_read(codec , mh->offset) & ~0x000f;
+		left = ((100 - left) * mh->scale) / 100;
+		if (left >= mh->scale)
+			left = mh->scale-1;
+		val |= left & 0x000e;
+	} else if(left == 0) {
+		val = AC97_MUTE;
+	} else if (oss_channel == SOUND_MIXER_SPEAKER) {
+		left = ((100 - left) * mh->scale) / 100;
+		if (left >= mh->scale)
+			left = mh->scale-1;
+		val = left << 1;
+	} else if (oss_channel == SOUND_MIXER_PHONEIN) {
+		left = ((100 - left) * mh->scale) / 100;
+		if (left >= mh->scale)
+			left = mh->scale-1;
+		val = left;
+	} else if (oss_channel == SOUND_MIXER_PHONEOUT) {
+		scale = (1 << codec->bit_resolution);
+		left = ((100 - left) * scale) / 100;
+		if (left >= mh->scale)
+			left = mh->scale-1;
+		val = left;
+	} else if (oss_channel == SOUND_MIXER_MIC) {
+		val = codec->codec_read(codec , mh->offset) & ~0x801f;
+		left = ((100 - left) * mh->scale) / 100;
+		if (left >= mh->scale)
+			left = mh->scale-1;
+		val |= left;
+		/*  the low bit is optional in the tone sliders and masking
+		    it lets us avoid the 0xf 'bypass'.. */
+	}
+#ifdef DEBUG
+	printk(" 0x%04x", val);
+#endif
+
+	codec->codec_write(codec, mh->offset, val);
+
+#ifdef DEBUG
+	val = codec->codec_read(codec, mh->offset);
+	printk(" -> 0x%04x\n", val);
+#endif
+}
+
+/* a thin wrapper for write_mixer */
+static void ac97_set_mixer(struct ac97_codec *codec, unsigned int oss_mixer, unsigned int val ) 
+{
+	unsigned int left,right;
+
+	/* cleanse input a little */
+	right = ((val >> 8)  & 0xff) ;
+	left = (val  & 0xff) ;
+
+	if (right > 100) right = 100;
+	if (left > 100) left = 100;
+
+	codec->mixer_state[oss_mixer] = (right << 8) | left;
+	codec->write_mixer(codec, oss_mixer, left, right);
+}
+
+/* read or write the recmask, the ac97 can really have left and right recording
+   inputs independantly set, but OSS doesn't seem to want us to express that to
+   the user. the caller guarantees that we have a supported bit set, and they
+   must be holding the card's spinlock */
+static int ac97_recmask_io(struct ac97_codec *codec, int rw, int mask) 
+{
+	unsigned int val;
+
+	if (rw) {
+		/* read it from the card */
+		val = codec->codec_read(codec, AC97_RECORD_SELECT);
+#ifdef DEBUG
+		printk("ac97_codec: ac97 recmask to set to 0x%04x\n", val);
+#endif
+		return (1 << ac97_rm2oss[val & 0x07]);
+	}
+
+	/* else, write the first set in the mask as the
+	   output */	
+	/* clear out current set value first (AC97 supports only 1 input!) */
+	val = (1 << ac97_rm2oss[codec->codec_read(codec, AC97_RECORD_SELECT) & 0x07]);
+	if (mask != val)
+	    mask &= ~val;
+       
+	val = ffs(mask); 
+	val = ac97_oss_rm[val-1];
+	val |= val << 8;  /* set both channels */
+
+#ifdef DEBUG
+	printk("ac97_codec: setting ac97 recmask to 0x%04x\n", val);
+#endif
+
+	codec->codec_write(codec, AC97_RECORD_SELECT, val);
+
+	return 0;
+};
+
+static int ac97_mixer_ioctl(struct ac97_codec *codec, unsigned int cmd, unsigned long arg)
+{
+	int i, val = 0;
+
+	if (cmd == SOUND_MIXER_INFO) {
+		mixer_info info;
+		memset(&info, 0, sizeof(info));
+		strlcpy(info.id, codec->name, sizeof(info.id));
+		strlcpy(info.name, codec->name, sizeof(info.name));
+		info.modify_counter = codec->modcnt;
+		if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	}
+	if (cmd == SOUND_OLD_MIXER_INFO) {
+		_old_mixer_info info;
+		memset(&info, 0, sizeof(info));
+		strlcpy(info.id, codec->name, sizeof(info.id));
+		strlcpy(info.name, codec->name, sizeof(info.name));
+		if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	}
+
+	if (_IOC_TYPE(cmd) != 'M' || _SIOC_SIZE(cmd) != sizeof(int))
+		return -EINVAL;
+
+	if (cmd == OSS_GETVERSION)
+		return put_user(SOUND_VERSION, (int __user *)arg);
+
+	if (_SIOC_DIR(cmd) == _SIOC_READ) {
+		switch (_IOC_NR(cmd)) {
+		case SOUND_MIXER_RECSRC: /* give them the current record source */
+			if (!codec->recmask_io) {
+				val = 0;
+			} else {
+				val = codec->recmask_io(codec, 1, 0);
+			}
+			break;
+
+		case SOUND_MIXER_DEVMASK: /* give them the supported mixers */
+			val = codec->supported_mixers;
+			break;
+
+		case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */
+			val = codec->record_sources;
+			break;
+
+		case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */
+			val = codec->stereo_mixers;
+			break;
+
+		case SOUND_MIXER_CAPS:
+			val = SOUND_CAP_EXCL_INPUT;
+			break;
+
+		default: /* read a specific mixer */
+			i = _IOC_NR(cmd);
+
+			if (!supported_mixer(codec, i)) 
+				return -EINVAL;
+
+			/* do we ever want to touch the hardware? */
+		        /* val = codec->read_mixer(codec, i); */
+			val = codec->mixer_state[i];
+ 			break;
+		}
+		return put_user(val, (int __user *)arg);
+	}
+
+	if (_SIOC_DIR(cmd) == (_SIOC_WRITE|_SIOC_READ)) {
+		codec->modcnt++;
+		if (get_user(val, (int __user *)arg))
+			return -EFAULT;
+
+		switch (_IOC_NR(cmd)) {
+		case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */
+			if (!codec->recmask_io) return -EINVAL;
+			if (!val) return 0;
+			if (!(val &= codec->record_sources)) return -EINVAL;
+
+			codec->recmask_io(codec, 0, val);
+
+			return 0;
+		default: /* write a specific mixer */
+			i = _IOC_NR(cmd);
+
+			if (!supported_mixer(codec, i)) 
+				return -EINVAL;
+
+			ac97_set_mixer(codec, i, val);
+
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+/**
+ *	codec_id	-  Turn id1/id2 into a PnP string
+ *	@id1: Vendor ID1
+ *	@id2: Vendor ID2
+ *	@buf: CODEC_ID_BUFSZ byte buffer
+ *
+ *	Fills buf with a zero terminated PnP ident string for the id1/id2
+ *	pair. For convenience the return is the passed in buffer pointer.
+ */
+ 
+static char *codec_id(u16 id1, u16 id2, char *buf)
+{
+	if(id1&0x8080) {
+		snprintf(buf, CODEC_ID_BUFSZ, "0x%04x:0x%04x", id1, id2);
+	} else {
+		buf[0] = (id1 >> 8);
+		buf[1] = (id1 & 0xFF);
+		buf[2] = (id2 >> 8);
+		snprintf(buf+3, CODEC_ID_BUFSZ - 3, "%d", id2&0xFF);
+	}
+	return buf;
+}
+ 
+/**
+ *	ac97_check_modem - Check if the Codec is a modem
+ *	@codec: codec to check
+ *
+ *	Return true if the device is an AC97 1.0 or AC97 2.0 modem
+ */
+ 
+static int ac97_check_modem(struct ac97_codec *codec)
+{
+	/* Check for an AC97 1.0 soft modem (ID1) */
+	if(codec->codec_read(codec, AC97_RESET) & 2)
+		return 1;
+	/* Check for an AC97 2.x soft modem */
+	codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0L);
+	if(codec->codec_read(codec, AC97_EXTENDED_MODEM_ID) & 1)
+		return 1;
+	return 0;
+}
+
+
+/**
+ *	ac97_alloc_codec - Allocate an AC97 codec
+ *
+ *	Returns a new AC97 codec structure. AC97 codecs may become
+ *	refcounted soon so this interface is needed. Returns with
+ *	one reference taken.
+ */
+ 
+struct ac97_codec *ac97_alloc_codec(void)
+{
+	struct ac97_codec *codec = kzalloc(sizeof(struct ac97_codec), GFP_KERNEL);
+	if(!codec)
+		return NULL;
+
+	spin_lock_init(&codec->lock);
+	INIT_LIST_HEAD(&codec->list);
+	return codec;
+}
+
+EXPORT_SYMBOL(ac97_alloc_codec);
+
+/**
+ *	ac97_release_codec -	Release an AC97 codec
+ *	@codec: codec to release
+ *
+ *	Release an allocated AC97 codec. This will be refcounted in
+ *	time but for the moment is trivial. Calls the unregister
+ *	handler if the codec is now defunct.
+ */
+ 
+void ac97_release_codec(struct ac97_codec *codec)
+{
+	/* Remove from the list first, we don't want to be
+	   "rediscovered" */
+	mutex_lock(&codec_mutex);
+	list_del(&codec->list);
+	mutex_unlock(&codec_mutex);
+	/*
+	 *	The driver needs to deal with internal
+	 *	locking to avoid accidents here. 
+	 */
+	if(codec->driver)
+		codec->driver->remove(codec, codec->driver);
+	kfree(codec);
+}
+
+EXPORT_SYMBOL(ac97_release_codec);
+
+/**
+ *	ac97_probe_codec - Initialize and setup AC97-compatible codec
+ *	@codec: (in/out) Kernel info for a single AC97 codec
+ *
+ *	Reset the AC97 codec, then initialize the mixer and
+ *	the rest of the @codec structure.
+ *
+ *	The codec_read and codec_write fields of @codec are
+ *	required to be setup and working when this function
+ *	is called.  All other fields are set by this function.
+ *
+ *	codec_wait field of @codec can optionally be provided
+ *	when calling this function.  If codec_wait is not %NULL,
+ *	this function will call codec_wait any time it is
+ *	necessary to wait for the audio chip to reach the
+ *	codec-ready state.  If codec_wait is %NULL, then
+ *	the default behavior is to call schedule_timeout.
+ *	Currently codec_wait is used to wait for AC97 codec
+ *	reset to complete. 
+ *
+ *     Some codecs will power down when a register reset is
+ *     performed. We now check for such codecs.
+ *
+ *	Returns 1 (true) on success, or 0 (false) on failure.
+ */
+ 
+int ac97_probe_codec(struct ac97_codec *codec)
+{
+	u16 id1, id2;
+	u16 audio;
+	int i;
+	char cidbuf[CODEC_ID_BUFSZ];
+	u16 f;
+	struct list_head *l;
+	struct ac97_driver *d;
+	
+	/* wait for codec-ready state */
+	if (codec->codec_wait)
+		codec->codec_wait(codec);
+	else
+		udelay(10);
+
+	/* will the codec power down if register reset ? */
+	id1 = codec->codec_read(codec, AC97_VENDOR_ID1);
+	id2 = codec->codec_read(codec, AC97_VENDOR_ID2);
+	codec->name = NULL;
+	codec->codec_ops = &null_ops;
+	for (i = 0; i < ARRAY_SIZE(ac97_codec_ids); i++) {
+		if (ac97_codec_ids[i].id == ((id1 << 16) | id2)) {
+			codec->type = ac97_codec_ids[i].id;
+			codec->name = ac97_codec_ids[i].name;
+			codec->codec_ops = ac97_codec_ids[i].ops;
+			codec->flags = ac97_codec_ids[i].flags;
+			break;
+		}
+	}
+
+	codec->model = (id1 << 16) | id2;
+	if ((codec->flags & AC97_DEFAULT_POWER_OFF) == 0) {
+		/* reset codec and wait for the ready bit before we continue */
+		codec->codec_write(codec, AC97_RESET, 0L);
+		if (codec->codec_wait)
+			codec->codec_wait(codec);
+		else
+			udelay(10);
+	}
+
+	/* probing AC97 codec, AC97 2.0 says that bit 15 of register 0x00 (reset) should
+	 * be read zero.
+	 *
+	 * FIXME: is the following comment outdated?  -jgarzik
+	 * Probing of AC97 in this way is not reliable, it is not even SAFE !!
+	 */
+	if ((audio = codec->codec_read(codec, AC97_RESET)) & 0x8000) {
+		printk(KERN_ERR "ac97_codec: %s ac97 codec not present\n",
+		       (codec->id & 0x2) ? (codec->id&1 ? "4th" : "Tertiary")
+		       : (codec->id&1 ? "Secondary":  "Primary"));
+		return 0;
+	}
+	
+	/* probe for Modem Codec */
+	codec->modem = ac97_check_modem(codec);
+
+	/* enable SPDIF */
+	f = codec->codec_read(codec, AC97_EXTENDED_STATUS);
+	if((codec->codec_ops == &null_ops) && (f & 4))
+		codec->codec_ops = &default_digital_ops;
+	
+	/* A device which thinks its a modem but isnt */
+	if(codec->flags & AC97_DELUDED_MODEM)
+		codec->modem = 0;
+		
+	if (codec->name == NULL)
+		codec->name = "Unknown";
+	printk(KERN_INFO "ac97_codec: AC97 %s codec, id: %s (%s)\n", 
+		codec->modem ? "Modem" : (audio ? "Audio" : ""),
+	       codec_id(id1, id2, cidbuf), codec->name);
+
+	if(!ac97_init_mixer(codec))
+		return 0;
+		
+	/* 
+	 *	Attach last so the caller can override the mixer
+	 *	callbacks.
+	 */
+	 
+	mutex_lock(&codec_mutex);
+	list_add(&codec->list, &codecs);
+
+	list_for_each(l, &codec_drivers) {
+		d = list_entry(l, struct ac97_driver, list);
+		if ((codec->model ^ d->codec_id) & d->codec_mask)
+			continue;
+		if(d->probe(codec, d) == 0)
+		{
+			codec->driver = d;
+			break;
+		}
+	}
+
+	mutex_unlock(&codec_mutex);
+	return 1;
+}
+
+static int ac97_init_mixer(struct ac97_codec *codec)
+{
+	u16 cap;
+	int i;
+
+	cap = codec->codec_read(codec, AC97_RESET);
+
+	/* mixer masks */
+	codec->supported_mixers = AC97_SUPPORTED_MASK;
+	codec->stereo_mixers = AC97_STEREO_MASK;
+	codec->record_sources = AC97_RECORD_MASK;
+	if (!(cap & 0x04))
+		codec->supported_mixers &= ~(SOUND_MASK_BASS|SOUND_MASK_TREBLE);
+	if (!(cap & 0x10))
+		codec->supported_mixers &= ~SOUND_MASK_ALTPCM;
+
+
+	/* detect bit resolution */
+	codec->codec_write(codec, AC97_MASTER_VOL_STEREO, 0x2020);
+	if(codec->codec_read(codec, AC97_MASTER_VOL_STEREO) == 0x2020)
+		codec->bit_resolution = 6;
+	else
+		codec->bit_resolution = 5;
+
+	/* generic OSS to AC97 wrapper */
+	codec->read_mixer = ac97_read_mixer;
+	codec->write_mixer = ac97_write_mixer;
+	codec->recmask_io = ac97_recmask_io;
+	codec->mixer_ioctl = ac97_mixer_ioctl;
+
+	/* initialize mixer channel volumes */
+	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
+		struct mixer_defaults *md = &mixer_defaults[i];
+		if (md->mixer == -1) 
+			break;
+		if (!supported_mixer(codec, md->mixer)) 
+			continue;
+		ac97_set_mixer(codec, md->mixer, md->value);
+	}
+
+	/* codec specific initialization for 4-6 channel output or secondary codec stuff */
+	if (codec->codec_ops->init != NULL) {
+		codec->codec_ops->init(codec);
+	}
+
+	/*
+	 *	Volume is MUTE only on this device. We have to initialise
+	 *	it but its useless beyond that.
+	 */
+	if(codec->flags & AC97_NO_PCM_VOLUME)
+	{
+		codec->supported_mixers &= ~SOUND_MASK_PCM;
+		printk(KERN_WARNING "AC97 codec does not have proper volume support.\n");
+	}
+	return 1;
+}
+
+#define AC97_SIGMATEL_ANALOG    0x6c	/* Analog Special */
+#define AC97_SIGMATEL_DAC2INVERT 0x6e
+#define AC97_SIGMATEL_BIAS1     0x70
+#define AC97_SIGMATEL_BIAS2     0x72
+#define AC97_SIGMATEL_MULTICHN  0x74	/* Multi-Channel programming */
+#define AC97_SIGMATEL_CIC1      0x76
+#define AC97_SIGMATEL_CIC2      0x78
+
+
+static int sigmatel_9708_init(struct ac97_codec * codec)
+{
+	u16 codec72, codec6c;
+
+	codec72 = codec->codec_read(codec, AC97_SIGMATEL_BIAS2) & 0x8000;
+	codec6c = codec->codec_read(codec, AC97_SIGMATEL_ANALOG);
+
+	if ((codec72==0) && (codec6c==0)) {
+		codec->codec_write(codec, AC97_SIGMATEL_CIC1, 0xabba);
+		codec->codec_write(codec, AC97_SIGMATEL_CIC2, 0x1000);
+		codec->codec_write(codec, AC97_SIGMATEL_BIAS1, 0xabba);
+		codec->codec_write(codec, AC97_SIGMATEL_BIAS2, 0x0007);
+	} else if ((codec72==0x8000) && (codec6c==0)) {
+		codec->codec_write(codec, AC97_SIGMATEL_CIC1, 0xabba);
+		codec->codec_write(codec, AC97_SIGMATEL_CIC2, 0x1001);
+		codec->codec_write(codec, AC97_SIGMATEL_DAC2INVERT, 0x0008);
+	} else if ((codec72==0x8000) && (codec6c==0x0080)) {
+		/* nothing */
+	}
+	codec->codec_write(codec, AC97_SIGMATEL_MULTICHN, 0x0000);
+	return 0;
+}
+
+
+static int sigmatel_9721_init(struct ac97_codec * codec)
+{
+	/* Only set up secondary codec */
+	if (codec->id == 0)
+		return 0;
+
+	codec->codec_write(codec, AC97_SURROUND_MASTER, 0L);
+
+	/* initialize SigmaTel STAC9721/23 as secondary codec, decoding AC link
+	   sloc 3,4 = 0x01, slot 7,8 = 0x00, */
+	codec->codec_write(codec, AC97_SIGMATEL_MULTICHN, 0x00);
+
+	/* we don't have the crystal when we are on an AMR card, so use
+	   BIT_CLK as our clock source. Write the magic word ABBA and read
+	   back to enable register 0x78 */
+	codec->codec_write(codec, AC97_SIGMATEL_CIC1, 0xabba);
+	codec->codec_read(codec, AC97_SIGMATEL_CIC1);
+
+	/* sync all the clocks*/
+	codec->codec_write(codec, AC97_SIGMATEL_CIC2, 0x3802);
+
+	return 0;
+}
+
+
+static int sigmatel_9744_init(struct ac97_codec * codec)
+{
+	// patch for SigmaTel
+	codec->codec_write(codec, AC97_SIGMATEL_CIC1, 0xabba);
+	codec->codec_write(codec, AC97_SIGMATEL_CIC2, 0x0000); // is this correct? --jk
+	codec->codec_write(codec, AC97_SIGMATEL_BIAS1, 0xabba);
+	codec->codec_write(codec, AC97_SIGMATEL_BIAS2, 0x0002);
+	codec->codec_write(codec, AC97_SIGMATEL_MULTICHN, 0x0000);
+	return 0;
+}
+
+static int cmedia_init(struct ac97_codec *codec)
+{
+	/* Initialise the CMedia 9739 */
+	/*
+		We could set various options here
+		Register 0x20 bit 0x100 sets mic as center bass
+		Also do multi_channel_ctrl &=~0x3000 |=0x1000
+		
+		For now we set up the GPIO and PC beep 
+	*/
+	
+	u16 v;
+	
+	/* MIC */
+	codec->codec_write(codec, 0x64, 0x3000);
+	v = codec->codec_read(codec, 0x64);
+	v &= ~0x8000;
+	codec->codec_write(codec, 0x64, v);
+	codec->codec_write(codec, 0x70, 0x0100);
+	codec->codec_write(codec, 0x72, 0x0020);
+	return 0;
+}
+	
+#define AC97_WM97XX_FMIXER_VOL 0x72
+#define AC97_WM97XX_RMIXER_VOL 0x74
+#define AC97_WM97XX_TEST 0x5a
+#define AC97_WM9704_RPCM_VOL 0x70
+#define AC97_WM9711_OUT3VOL 0x16
+
+static int wolfson_init03(struct ac97_codec * codec)
+{
+	/* this is known to work for the ViewSonic ViewPad 1000 */
+	codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808);
+	codec->codec_write(codec, AC97_GENERAL_PURPOSE, 0x8000);
+	return 0;
+}
+
+static int wolfson_init04(struct ac97_codec * codec)
+{
+	codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808);
+	codec->codec_write(codec, AC97_WM97XX_RMIXER_VOL, 0x0808);
+
+	// patch for DVD noise
+	codec->codec_write(codec, AC97_WM97XX_TEST, 0x0200);
+
+	// init vol as PCM vol
+	codec->codec_write(codec, AC97_WM9704_RPCM_VOL,
+		codec->codec_read(codec, AC97_PCMOUT_VOL));
+
+	/* set rear surround volume */
+	codec->codec_write(codec, AC97_SURROUND_MASTER, 0x0000);
+	return 0;
+}
+
+/* WM9705, WM9710 */
+static int wolfson_init05(struct ac97_codec * codec)
+{
+	/* set front mixer volume */
+	codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808);
+	return 0;
+}
+
+/* WM9711, WM9712 */
+static int wolfson_init11(struct ac97_codec * codec)
+{
+	/* stop pop's during suspend/resume */
+	codec->codec_write(codec, AC97_WM97XX_TEST,
+		codec->codec_read(codec, AC97_WM97XX_TEST) & 0xffbf);
+
+	/* set out3 volume */
+	codec->codec_write(codec, AC97_WM9711_OUT3VOL, 0x0808);
+	return 0;
+}
+
+/* WM9713 */
+static int wolfson_init13(struct ac97_codec * codec)
+{
+	codec->codec_write(codec, AC97_RECORD_GAIN, 0x00a0);
+	codec->codec_write(codec, AC97_POWER_CONTROL, 0x0000);
+	codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0xDA00);
+	codec->codec_write(codec, AC97_EXTEND_MODEM_STAT, 0x3810);
+	codec->codec_write(codec, AC97_PHONE_VOL, 0x0808);
+	codec->codec_write(codec, AC97_PCBEEP_VOL, 0x0808);
+
+	return 0;
+}
+
+static int tritech_init(struct ac97_codec * codec)
+{
+	codec->codec_write(codec, 0x26, 0x0300);
+	codec->codec_write(codec, 0x26, 0x0000);
+	codec->codec_write(codec, AC97_SURROUND_MASTER, 0x0000);
+	codec->codec_write(codec, AC97_RESERVED_3A, 0x0000);
+	return 0;
+}
+
+
+/* copied from drivers/sound/maestro.c */
+static int tritech_maestro_init(struct ac97_codec * codec)
+{
+	/* no idea what this does */
+	codec->codec_write(codec, 0x2A, 0x0001);
+	codec->codec_write(codec, 0x2C, 0x0000);
+	codec->codec_write(codec, 0x2C, 0XFFFF);
+	return 0;
+}
+
+
+
+/* 
+ *	Presario700 workaround 
+ * 	for Jack Sense/SPDIF Register mis-setting causing
+ *	no audible output
+ *	by Santiago Nullo 04/05/2002
+ */
+
+#define AC97_AD1886_JACK_SENSE 0x72
+
+static int ad1886_init(struct ac97_codec * codec)
+{
+	/* from AD1886 Specs */
+	codec->codec_write(codec, AC97_AD1886_JACK_SENSE, 0x0010);
+	return 0;
+}
+
+
+
+
+/*
+ *	This is basically standard AC97. It should work as a default for
+ *	almost all modern codecs. Note that some cards wire EAPD *backwards*
+ *	That side of it is up to the card driver not us to cope with.
+ *
+ */
+
+static int eapd_control(struct ac97_codec * codec, int on)
+{
+	if(on)
+		codec->codec_write(codec, AC97_POWER_CONTROL,
+			codec->codec_read(codec, AC97_POWER_CONTROL)|0x8000);
+	else
+		codec->codec_write(codec, AC97_POWER_CONTROL,
+			codec->codec_read(codec, AC97_POWER_CONTROL)&~0x8000);
+	return 0;
+}
+
+static int generic_digital_control(struct ac97_codec *codec, int slots, int rate, int mode)
+{
+	u16 reg;
+	
+	reg = codec->codec_read(codec, AC97_SPDIF_CONTROL);
+	
+	switch(rate)
+	{
+		/* Off by default */
+		default:
+		case 0:
+			reg = codec->codec_read(codec, AC97_EXTENDED_STATUS);
+			codec->codec_write(codec, AC97_EXTENDED_STATUS, (reg & ~AC97_EA_SPDIF));
+			if(rate == 0)
+				return 0;
+			return -EINVAL;
+		case 1:
+			reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_48K;
+			break;
+		case 2:
+			reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_44K;
+			break;
+		case 3:
+			reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_32K;
+			break;
+	}
+	
+	reg &= ~AC97_SC_CC_MASK;
+	reg |= (mode & AUDIO_CCMASK) << 6;
+	
+	if(mode & AUDIO_DIGITAL)
+		reg |= 2;
+	if(mode & AUDIO_PRO)
+		reg |= 1;
+	if(mode & AUDIO_DRS)
+		reg |= 0x4000;
+
+	codec->codec_write(codec, AC97_SPDIF_CONTROL, reg);
+
+	reg = codec->codec_read(codec, AC97_EXTENDED_STATUS);
+	reg &= (AC97_EA_SLOT_MASK);
+	reg |= AC97_EA_VRA | AC97_EA_SPDIF | slots;
+	codec->codec_write(codec, AC97_EXTENDED_STATUS, reg);
+	
+	reg = codec->codec_read(codec, AC97_EXTENDED_STATUS);
+	if(!(reg & 0x0400))
+	{
+		codec->codec_write(codec, AC97_EXTENDED_STATUS, reg & ~ AC97_EA_SPDIF);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ *	Crystal digital audio control (CS4299)
+ */
+ 
+static int crystal_digital_control(struct ac97_codec *codec, int slots, int rate, int mode)
+{
+	u16 cv;
+
+	if(mode & AUDIO_DIGITAL)
+		return -EINVAL;
+		
+	switch(rate)
+	{
+		case 0: cv = 0x0; break;	/* SPEN off */
+		case 48000: cv = 0x8004; break;	/* 48KHz digital */
+		case 44100: cv = 0x8104; break;	/* 44.1KHz digital */
+		case 32768: 			/* 32Khz */
+		default:
+			return -EINVAL;
+	}
+	codec->codec_write(codec, 0x68, cv);
+	return 0;
+}
+
+/*
+ *	CMedia digital audio control
+ *	Needs more work.
+ */
+ 
+static int cmedia_digital_control(struct ac97_codec *codec, int slots, int rate, int mode)
+{
+	u16 cv;
+
+	if(mode & AUDIO_DIGITAL)
+		return -EINVAL;
+		
+	switch(rate)
+	{
+		case 0:		cv = 0x0001; break;	/* SPEN off */
+		case 48000:	cv = 0x0009; break;	/* 48KHz digital */
+		default:
+			return -EINVAL;
+	}
+	codec->codec_write(codec, 0x2A, 0x05c4);
+	codec->codec_write(codec, 0x6C, cv);
+	
+	/* Switch on mix to surround */
+	cv = codec->codec_read(codec, 0x64);
+	cv &= ~0x0200;
+	if(mode)
+		cv |= 0x0200;
+	codec->codec_write(codec, 0x64, cv);
+	return 0;
+}
+
+
+/* copied from drivers/sound/maestro.c */
+#if 0  /* there has been 1 person on the planet with a pt101 that we
+        know of.  If they care, they can put this back in :) */
+static int pt101_init(struct ac97_codec * codec)
+{
+	printk(KERN_INFO "ac97_codec: PT101 Codec detected, initializing but _not_ installing mixer device.\n");
+	/* who knows.. */
+	codec->codec_write(codec, 0x2A, 0x0001);
+	codec->codec_write(codec, 0x2C, 0x0000);
+	codec->codec_write(codec, 0x2C, 0xFFFF);
+	codec->codec_write(codec, 0x10, 0x9F1F);
+	codec->codec_write(codec, 0x12, 0x0808);
+	codec->codec_write(codec, 0x14, 0x9F1F);
+	codec->codec_write(codec, 0x16, 0x9F1F);
+	codec->codec_write(codec, 0x18, 0x0404);
+	codec->codec_write(codec, 0x1A, 0x0000);
+	codec->codec_write(codec, 0x1C, 0x0000);
+	codec->codec_write(codec, 0x02, 0x0404);
+	codec->codec_write(codec, 0x04, 0x0808);
+	codec->codec_write(codec, 0x0C, 0x801F);
+	codec->codec_write(codec, 0x0E, 0x801F);
+	return 0;
+}
+#endif
+	
+
+EXPORT_SYMBOL(ac97_probe_codec);
+
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/oss/ad1848.c b/linux/sound/oss/ad1848.c
new file mode 100644
index 0000000..4d2a6ae
--- /dev/null
+++ b/linux/sound/oss/ad1848.c
@@ -0,0 +1,3069 @@
+/*
+ * sound/oss/ad1848.c
+ *
+ * The low level driver for the AD1848/CS4248 codec chip which
+ * is used for example in the MS Sound System.
+ *
+ * The CS4231 which is used in the GUS MAX and some other cards is
+ * upwards compatible with AD1848 and this driver is able to drive it.
+ *
+ * CS4231A and AD1845 are upward compatible with CS4231. However
+ * the new features of these chips are different.
+ *
+ * CS4232 is a PnP audio chip which contains a CS4231A (and SB, MPU).
+ * CS4232A is an improved version of CS4232.
+ *
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer	: ioctl code reworked (vmalloc/vfree removed)
+ *			  general sleep/wakeup clean up.
+ * Alan Cox		: reformatted. Fixed SMP bugs. Moved to kernel alloc/free
+ *		          of irqs. Use dev_id.
+ * Christoph Hellwig	: adapted to module_init/module_exit
+ * Aki Laukkanen	: added power management support
+ * Arnaldo C. de Melo	: added missing restore_flags in ad1848_resume
+ * Miguel Freitas       : added ISA PnP support
+ * Alan Cox		: Added CS4236->4239 identification
+ * Daniel T. Cobra	: Alernate config/mixer for later chips
+ * Alan Cox		: Merged chip idents and config code
+ *
+ * TODO
+ *		APM save restore assist code on IBM thinkpad
+ *
+ * Status:
+ *		Tested. Believed fully functional.
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/stddef.h>
+#include <linux/slab.h>
+#include <linux/isapnp.h>
+#include <linux/pnp.h>
+#include <linux/spinlock.h>
+
+#define DEB(x)
+#define DEB1(x)
+#include "sound_config.h"
+
+#include "ad1848.h"
+#include "ad1848_mixer.h"
+
+typedef struct
+{
+	spinlock_t	lock;
+	int             base;
+	int             irq;
+	int             dma1, dma2;
+	int             dual_dma;	/* 1, when two DMA channels allocated */
+	int 		subtype;
+	unsigned char   MCE_bit;
+	unsigned char   saved_regs[64];	/* Includes extended register space */
+	int             debug_flag;
+
+	int             audio_flags;
+	int             record_dev, playback_dev;
+
+	int             xfer_count;
+	int             audio_mode;
+	int             open_mode;
+	int             intr_active;
+	char           *chip_name, *name;
+	int             model;
+#define MD_1848		1
+#define MD_4231		2
+#define MD_4231A	3
+#define MD_1845		4
+#define MD_4232		5
+#define MD_C930		6
+#define MD_IWAVE	7
+#define MD_4235         8 /* Crystal Audio CS4235  */
+#define MD_1845_SSCAPE  9 /* Ensoniq Soundscape PNP*/
+#define MD_4236		10 /* 4236 and higher */
+#define MD_42xB		11 /* CS 42xB */
+#define MD_4239		12 /* CS4239 */
+
+	/* Mixer parameters */
+	int             recmask;
+	int             supported_devices, orig_devices;
+	int             supported_rec_devices, orig_rec_devices;
+	int            *levels;
+	short           mixer_reroute[32];
+	int             dev_no;
+	volatile unsigned long timer_ticks;
+	int             timer_running;
+	int             irq_ok;
+	mixer_ents     *mix_devices;
+	int             mixer_output_port;
+} ad1848_info;
+
+typedef struct ad1848_port_info
+{
+	int             open_mode;
+	int             speed;
+	unsigned char   speed_bits;
+	int             channels;
+	int             audio_format;
+	unsigned char   format_bits;
+}
+ad1848_port_info;
+
+static struct address_info cfg;
+static int nr_ad1848_devs;
+
+static int deskpro_xl;
+static int deskpro_m;
+static int soundpro;
+
+static volatile signed char irq2dev[17] = {
+	-1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+#ifndef EXCLUDE_TIMERS
+static int timer_installed = -1;
+#endif
+
+static int loaded;
+
+static int ad_format_mask[13 /*devc->model */ ] =
+{
+	0,
+	AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW,
+	AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+	AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+	AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW,	/* AD1845 */
+	AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+	AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+	AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+	AFMT_U8 | AFMT_S16_LE /* CS4235 */,
+	AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW	/* Ensoniq Soundscape*/,
+	AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+	AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM,
+	AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW | AFMT_S16_BE | AFMT_IMA_ADPCM
+};
+
+static ad1848_info adev_info[MAX_AUDIO_DEV];
+
+#define io_Index_Addr(d)	((d)->base)
+#define io_Indexed_Data(d)	((d)->base+1)
+#define io_Status(d)		((d)->base+2)
+#define io_Polled_IO(d)		((d)->base+3)
+
+static struct {
+     unsigned char flags;
+#define CAP_F_TIMER 0x01     
+} capabilities [10 /*devc->model */ ] = {
+     {0}
+    ,{0}           /* MD_1848  */
+    ,{CAP_F_TIMER} /* MD_4231  */
+    ,{CAP_F_TIMER} /* MD_4231A */
+    ,{CAP_F_TIMER} /* MD_1845  */
+    ,{CAP_F_TIMER} /* MD_4232  */
+    ,{0}           /* MD_C930  */
+    ,{CAP_F_TIMER} /* MD_IWAVE */
+    ,{0}           /* MD_4235  */
+    ,{CAP_F_TIMER} /* MD_1845_SSCAPE */
+};
+
+#ifdef CONFIG_PNP
+static int isapnp	= 1;
+static int isapnpjump;
+static int reverse;
+
+static int audio_activated;
+#else
+static int isapnp;
+#endif
+
+
+
+static int      ad1848_open(int dev, int mode);
+static void     ad1848_close(int dev);
+static void     ad1848_output_block(int dev, unsigned long buf, int count, int intrflag);
+static void     ad1848_start_input(int dev, unsigned long buf, int count, int intrflag);
+static int      ad1848_prepare_for_output(int dev, int bsize, int bcount);
+static int      ad1848_prepare_for_input(int dev, int bsize, int bcount);
+static void     ad1848_halt(int dev);
+static void     ad1848_halt_input(int dev);
+static void     ad1848_halt_output(int dev);
+static void     ad1848_trigger(int dev, int bits);
+static irqreturn_t adintr(int irq, void *dev_id);
+
+#ifndef EXCLUDE_TIMERS
+static int ad1848_tmr_install(int dev);
+static void ad1848_tmr_reprogram(int dev);
+#endif
+
+static int ad_read(ad1848_info * devc, int reg)
+{
+	int x;
+	int timeout = 900000;
+
+	while (timeout > 0 && inb(devc->base) == 0x80)	/*Are we initializing */
+		timeout--;
+
+	if(reg < 32)
+	{
+		outb(((unsigned char) (reg & 0xff) | devc->MCE_bit), io_Index_Addr(devc));
+		x = inb(io_Indexed_Data(devc));
+	}
+	else
+	{
+		int xreg, xra;
+
+		xreg = (reg & 0xff) - 32;
+		xra = (((xreg & 0x0f) << 4) & 0xf0) | 0x08 | ((xreg & 0x10) >> 2);
+		outb(((unsigned char) (23 & 0xff) | devc->MCE_bit), io_Index_Addr(devc));
+		outb(((unsigned char) (xra & 0xff)), io_Indexed_Data(devc));
+		x = inb(io_Indexed_Data(devc));
+	}
+
+	return x;
+}
+
+static void ad_write(ad1848_info * devc, int reg, int data)
+{
+	int timeout = 900000;
+
+	while (timeout > 0 && inb(devc->base) == 0x80)	/* Are we initializing */
+		timeout--;
+
+	if(reg < 32)
+	{
+		outb(((unsigned char) (reg & 0xff) | devc->MCE_bit), io_Index_Addr(devc));
+		outb(((unsigned char) (data & 0xff)), io_Indexed_Data(devc));
+	}
+	else
+	{
+		int xreg, xra;
+		
+		xreg = (reg & 0xff) - 32;
+		xra = (((xreg & 0x0f) << 4) & 0xf0) | 0x08 | ((xreg & 0x10) >> 2);
+		outb(((unsigned char) (23 & 0xff) | devc->MCE_bit), io_Index_Addr(devc));
+		outb(((unsigned char) (xra & 0xff)), io_Indexed_Data(devc));
+		outb((unsigned char) (data & 0xff), io_Indexed_Data(devc));
+	}
+}
+
+static void wait_for_calibration(ad1848_info * devc)
+{
+	int timeout = 0;
+
+	/*
+	 * Wait until the auto calibration process has finished.
+	 *
+	 * 1)       Wait until the chip becomes ready (reads don't return 0x80).
+	 * 2)       Wait until the ACI bit of I11 gets on and then off.
+	 */
+
+	timeout = 100000;
+	while (timeout > 0 && inb(devc->base) == 0x80)
+		timeout--;
+	if (inb(devc->base) & 0x80)
+		printk(KERN_WARNING "ad1848: Auto calibration timed out(1).\n");
+
+	timeout = 100;
+	while (timeout > 0 && !(ad_read(devc, 11) & 0x20))
+		timeout--;
+	if (!(ad_read(devc, 11) & 0x20))
+		return;
+
+	timeout = 80000;
+	while (timeout > 0 && (ad_read(devc, 11) & 0x20))
+		timeout--;
+	if (ad_read(devc, 11) & 0x20)
+		if ((devc->model != MD_1845) && (devc->model != MD_1845_SSCAPE))
+			printk(KERN_WARNING "ad1848: Auto calibration timed out(3).\n");
+}
+
+static void ad_mute(ad1848_info * devc)
+{
+	int i;
+	unsigned char prev;
+
+	/*
+	 * Save old register settings and mute output channels
+	 */
+	 
+	for (i = 6; i < 8; i++)
+	{
+		prev = devc->saved_regs[i] = ad_read(devc, i);
+	}
+
+}
+
+static void ad_unmute(ad1848_info * devc)
+{
+}
+
+static void ad_enter_MCE(ad1848_info * devc)
+{
+	int timeout = 1000;
+	unsigned short prev;
+
+	while (timeout > 0 && inb(devc->base) == 0x80)	/*Are we initializing */
+		timeout--;
+
+	devc->MCE_bit = 0x40;
+	prev = inb(io_Index_Addr(devc));
+	if (prev & 0x40)
+	{
+		return;
+	}
+	outb((devc->MCE_bit), io_Index_Addr(devc));
+}
+
+static void ad_leave_MCE(ad1848_info * devc)
+{
+	unsigned char prev, acal;
+	int timeout = 1000;
+
+	while (timeout > 0 && inb(devc->base) == 0x80)	/*Are we initializing */
+		timeout--;
+
+	acal = ad_read(devc, 9);
+
+	devc->MCE_bit = 0x00;
+	prev = inb(io_Index_Addr(devc));
+	outb((0x00), io_Index_Addr(devc));	/* Clear the MCE bit */
+
+	if ((prev & 0x40) == 0)	/* Not in MCE mode */
+	{
+		return;
+	}
+	outb((0x00), io_Index_Addr(devc));	/* Clear the MCE bit */
+	if (acal & 0x08)	/* Auto calibration is enabled */
+		wait_for_calibration(devc);
+}
+
+static int ad1848_set_recmask(ad1848_info * devc, int mask)
+{
+	unsigned char   recdev;
+	int             i, n;
+	unsigned long flags;
+
+	mask &= devc->supported_rec_devices;
+
+	/* Rename the mixer bits if necessary */
+	for (i = 0; i < 32; i++)
+	{
+		if (devc->mixer_reroute[i] != i)
+		{
+			if (mask & (1 << i))
+			{
+				mask &= ~(1 << i);
+				mask |= (1 << devc->mixer_reroute[i]);
+			}
+		}
+	}
+	
+	n = 0;
+	for (i = 0; i < 32; i++)	/* Count selected device bits */
+		if (mask & (1 << i))
+			n++;
+
+	spin_lock_irqsave(&devc->lock,flags);
+	if (!soundpro) {
+		if (n == 0)
+			mask = SOUND_MASK_MIC;
+		else if (n != 1) {	/* Too many devices selected */
+			mask &= ~devc->recmask;	/* Filter out active settings */
+
+			n = 0;
+			for (i = 0; i < 32; i++)	/* Count selected device bits */
+				if (mask & (1 << i))
+					n++;
+
+			if (n != 1)
+				mask = SOUND_MASK_MIC;
+		}
+		switch (mask) {
+		case SOUND_MASK_MIC:
+			recdev = 2;
+			break;
+
+		case SOUND_MASK_LINE:
+		case SOUND_MASK_LINE3:
+			recdev = 0;
+			break;
+
+		case SOUND_MASK_CD:
+		case SOUND_MASK_LINE1:
+			recdev = 1;
+			break;
+
+		case SOUND_MASK_IMIX:
+			recdev = 3;
+			break;
+
+		default:
+			mask = SOUND_MASK_MIC;
+			recdev = 2;
+		}
+
+		recdev <<= 6;
+		ad_write(devc, 0, (ad_read(devc, 0) & 0x3f) | recdev);
+		ad_write(devc, 1, (ad_read(devc, 1) & 0x3f) | recdev);
+	} else { /* soundpro */
+		unsigned char val;
+		int set_rec_bit;
+		int j;
+
+		for (i = 0; i < 32; i++) {	/* For each bit */
+			if ((devc->supported_rec_devices & (1 << i)) == 0)
+				continue;	/* Device not supported */
+
+			for (j = LEFT_CHN; j <= RIGHT_CHN; j++) {
+				if (devc->mix_devices[i][j].nbits == 0) /* Inexistent channel */
+					continue;
+
+				/*
+				 * This is tricky:
+				 * set_rec_bit becomes 1 if the corresponding bit in mask is set
+				 * then it gets flipped if the polarity is inverse
+				 */
+				set_rec_bit = ((mask & (1 << i)) != 0) ^ devc->mix_devices[i][j].recpol;
+
+				val = ad_read(devc, devc->mix_devices[i][j].recreg);
+				val &= ~(1 << devc->mix_devices[i][j].recpos);
+				val |= (set_rec_bit << devc->mix_devices[i][j].recpos);
+				ad_write(devc, devc->mix_devices[i][j].recreg, val);
+			}
+		}
+	}
+	spin_unlock_irqrestore(&devc->lock,flags);
+
+	/* Rename the mixer bits back if necessary */
+	for (i = 0; i < 32; i++)
+	{
+		if (devc->mixer_reroute[i] != i)
+		{
+			if (mask & (1 << devc->mixer_reroute[i]))
+			{
+				mask &= ~(1 << devc->mixer_reroute[i]);
+				mask |= (1 << i);
+			}
+		}
+	}
+	devc->recmask = mask;
+	return mask;
+}
+
+static void change_bits(ad1848_info * devc, unsigned char *regval,
+			unsigned char *muteval, int dev, int chn, int newval)
+{
+	unsigned char mask;
+	int shift;
+	int mute;
+	int mutemask;
+	int set_mute_bit;
+
+	set_mute_bit = (newval == 0) ^ devc->mix_devices[dev][chn].mutepol;
+
+	if (devc->mix_devices[dev][chn].polarity == 1)	/* Reverse */
+		newval = 100 - newval;
+
+	mask = (1 << devc->mix_devices[dev][chn].nbits) - 1;
+	shift = devc->mix_devices[dev][chn].bitpos;
+
+	if (devc->mix_devices[dev][chn].mutepos == 8)
+	{			/* if there is no mute bit */
+		mute = 0;	/* No mute bit; do nothing special */
+		mutemask = ~0;	/* No mute bit; do nothing special */
+	}
+	else
+	{
+		mute = (set_mute_bit << devc->mix_devices[dev][chn].mutepos);
+		mutemask = ~(1 << devc->mix_devices[dev][chn].mutepos);
+	}
+
+	newval = (int) ((newval * mask) + 50) / 100;	/* Scale it */
+	*regval &= ~(mask << shift);			/* Clear bits */
+	*regval |= (newval & mask) << shift;		/* Set new value */
+
+	*muteval &= mutemask;
+	*muteval |= mute;
+}
+
+static int ad1848_mixer_get(ad1848_info * devc, int dev)
+{
+	if (!((1 << dev) & devc->supported_devices))
+		return -EINVAL;
+
+	dev = devc->mixer_reroute[dev];
+
+	return devc->levels[dev];
+}
+
+static void ad1848_mixer_set_channel(ad1848_info *devc, int dev, int value, int channel)
+{
+	int regoffs, muteregoffs;
+	unsigned char val, muteval;
+	unsigned long flags;
+
+	regoffs = devc->mix_devices[dev][channel].regno;
+	muteregoffs = devc->mix_devices[dev][channel].mutereg;
+	val = ad_read(devc, regoffs);
+
+	if (muteregoffs != regoffs) {
+		muteval = ad_read(devc, muteregoffs);
+		change_bits(devc, &val, &muteval, dev, channel, value);
+	}
+	else
+		change_bits(devc, &val, &val, dev, channel, value);
+
+	spin_lock_irqsave(&devc->lock,flags);
+	ad_write(devc, regoffs, val);
+	devc->saved_regs[regoffs] = val;
+	if (muteregoffs != regoffs) {
+		ad_write(devc, muteregoffs, muteval);
+		devc->saved_regs[muteregoffs] = muteval;
+	}
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static int ad1848_mixer_set(ad1848_info * devc, int dev, int value)
+{
+	int left = value & 0x000000ff;
+	int right = (value & 0x0000ff00) >> 8;
+	int retvol;
+
+	if (dev > 31)
+		return -EINVAL;
+
+	if (!(devc->supported_devices & (1 << dev)))
+		return -EINVAL;
+
+	dev = devc->mixer_reroute[dev];
+
+	if (devc->mix_devices[dev][LEFT_CHN].nbits == 0)
+		return -EINVAL;
+
+	if (left > 100)
+		left = 100;
+	if (right > 100)
+		right = 100;
+
+	if (devc->mix_devices[dev][RIGHT_CHN].nbits == 0)	/* Mono control */
+		right = left;
+
+	retvol = left | (right << 8);
+
+	/* Scale volumes */
+	left = mix_cvt[left];
+	right = mix_cvt[right];
+
+	devc->levels[dev] = retvol;
+
+	/*
+	 * Set the left channel
+	 */
+	ad1848_mixer_set_channel(devc, dev, left, LEFT_CHN);
+
+	/*
+	 * Set the right channel
+	 */
+	if (devc->mix_devices[dev][RIGHT_CHN].nbits == 0)
+		goto out;
+	ad1848_mixer_set_channel(devc, dev, right, RIGHT_CHN);
+
+ out:
+	return retvol;
+}
+
+static void ad1848_mixer_reset(ad1848_info * devc)
+{
+	int i;
+	char name[32];
+	unsigned long flags;
+
+	devc->mix_devices = &(ad1848_mix_devices[0]);
+
+	sprintf(name, "%s_%d", devc->chip_name, nr_ad1848_devs);
+
+	for (i = 0; i < 32; i++)
+		devc->mixer_reroute[i] = i;
+
+	devc->supported_rec_devices = MODE1_REC_DEVICES;
+
+	switch (devc->model)
+	{
+		case MD_4231:
+		case MD_4231A:
+		case MD_1845:
+		case MD_1845_SSCAPE:
+			devc->supported_devices = MODE2_MIXER_DEVICES;
+			break;
+
+		case MD_C930:
+			devc->supported_devices = C930_MIXER_DEVICES;
+			devc->mix_devices = &(c930_mix_devices[0]);
+			break;
+
+		case MD_IWAVE:
+			devc->supported_devices = MODE3_MIXER_DEVICES;
+			devc->mix_devices = &(iwave_mix_devices[0]);
+			break;
+
+		case MD_42xB:
+		case MD_4239:
+			devc->mix_devices = &(cs42xb_mix_devices[0]);
+			devc->supported_devices = MODE3_MIXER_DEVICES;
+			break;
+		case MD_4232:
+		case MD_4235:
+		case MD_4236:
+			devc->supported_devices = MODE3_MIXER_DEVICES;
+			break;
+
+		case MD_1848:
+			if (soundpro) {
+				devc->supported_devices = SPRO_MIXER_DEVICES;
+				devc->supported_rec_devices = SPRO_REC_DEVICES;
+				devc->mix_devices = &(spro_mix_devices[0]);
+				break;
+			}
+
+		default:
+			devc->supported_devices = MODE1_MIXER_DEVICES;
+	}
+
+	devc->orig_devices = devc->supported_devices;
+	devc->orig_rec_devices = devc->supported_rec_devices;
+
+	devc->levels = load_mixer_volumes(name, default_mixer_levels, 1);
+
+	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+	{
+		if (devc->supported_devices & (1 << i))
+			ad1848_mixer_set(devc, i, devc->levels[i]);
+	}
+	
+	ad1848_set_recmask(devc, SOUND_MASK_MIC);
+	
+	devc->mixer_output_port = devc->levels[31] | AUDIO_HEADPHONE | AUDIO_LINE_OUT;
+
+	spin_lock_irqsave(&devc->lock,flags);
+	if (!soundpro) {
+		if (devc->mixer_output_port & AUDIO_SPEAKER)
+			ad_write(devc, 26, ad_read(devc, 26) & ~0x40);	/* Unmute mono out */
+		else
+			ad_write(devc, 26, ad_read(devc, 26) | 0x40);	/* Mute mono out */
+	} else {
+		/*
+		 * From the "wouldn't it be nice if the mixer API had (better)
+		 * support for custom stuff" category
+		 */
+		/* Enable surround mode and SB16 mixer */
+		ad_write(devc, 16, 0x60);
+	}
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static int ad1848_mixer_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+	ad1848_info *devc = mixer_devs[dev]->devc;
+	int val;
+
+	if (cmd == SOUND_MIXER_PRIVATE1) 
+	{
+		if (get_user(val, (int __user *)arg))
+			return -EFAULT;
+
+		if (val != 0xffff) 
+		{
+			unsigned long flags;
+			val &= (AUDIO_SPEAKER | AUDIO_HEADPHONE | AUDIO_LINE_OUT);
+			devc->mixer_output_port = val;
+			val |= AUDIO_HEADPHONE | AUDIO_LINE_OUT;	/* Always on */
+			devc->mixer_output_port = val;
+			spin_lock_irqsave(&devc->lock,flags);
+			if (val & AUDIO_SPEAKER)
+				ad_write(devc, 26, ad_read(devc, 26) & ~0x40);	/* Unmute mono out */
+			else
+				ad_write(devc, 26, ad_read(devc, 26) | 0x40);		/* Mute mono out */
+			spin_unlock_irqrestore(&devc->lock,flags);
+		}
+		val = devc->mixer_output_port;
+		return put_user(val, (int __user *)arg);
+	}
+	if (cmd == SOUND_MIXER_PRIVATE2)
+	{
+		if (get_user(val, (int __user *)arg))
+			return -EFAULT;
+		return(ad1848_control(AD1848_MIXER_REROUTE, val));
+	}
+	if (((cmd >> 8) & 0xff) == 'M') 
+	{
+		if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+		{
+			switch (cmd & 0xff) 
+			{
+				case SOUND_MIXER_RECSRC:
+					if (get_user(val, (int __user *)arg))
+						return -EFAULT;
+					val = ad1848_set_recmask(devc, val);
+					break;
+				
+				default:
+					if (get_user(val, (int __user *)arg))
+						return -EFAULT;
+					val = ad1848_mixer_set(devc, cmd & 0xff, val);
+					break;
+			} 
+			return put_user(val, (int __user *)arg);
+		}
+		else
+		{
+			switch (cmd & 0xff) 
+			{
+				/*
+				 * Return parameters
+				 */
+			    
+				case SOUND_MIXER_RECSRC:
+					val = devc->recmask;
+					break;
+				
+				case SOUND_MIXER_DEVMASK:
+					val = devc->supported_devices;
+					break;
+				
+				case SOUND_MIXER_STEREODEVS:
+					val = devc->supported_devices;
+					if (devc->model != MD_C930)
+						val &= ~(SOUND_MASK_SPEAKER | SOUND_MASK_IMIX);
+					break;
+				
+				case SOUND_MIXER_RECMASK:
+					val = devc->supported_rec_devices;
+					break;
+
+				case SOUND_MIXER_CAPS:
+					val=SOUND_CAP_EXCL_INPUT;
+					break;
+
+				default:
+					val = ad1848_mixer_get(devc, cmd & 0xff);
+					break;
+			}
+			return put_user(val, (int __user *)arg);
+		}
+	}
+	else
+		return -EINVAL;
+}
+
+static int ad1848_set_speed(int dev, int arg)
+{
+	ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+	ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+	/*
+	 * The sampling speed is encoded in the least significant nibble of I8. The
+	 * LSB selects the clock source (0=24.576 MHz, 1=16.9344 MHz) and other
+	 * three bits select the divisor (indirectly):
+	 *
+	 * The available speeds are in the following table. Keep the speeds in
+	 * the increasing order.
+	 */
+	typedef struct
+	{
+		int             speed;
+		unsigned char   bits;
+	}
+	speed_struct;
+
+	static speed_struct speed_table[] =
+	{
+		{5510, (0 << 1) | 1},
+		{5510, (0 << 1) | 1},
+		{6620, (7 << 1) | 1},
+		{8000, (0 << 1) | 0},
+		{9600, (7 << 1) | 0},
+		{11025, (1 << 1) | 1},
+		{16000, (1 << 1) | 0},
+		{18900, (2 << 1) | 1},
+		{22050, (3 << 1) | 1},
+		{27420, (2 << 1) | 0},
+		{32000, (3 << 1) | 0},
+		{33075, (6 << 1) | 1},
+		{37800, (4 << 1) | 1},
+		{44100, (5 << 1) | 1},
+		{48000, (6 << 1) | 0}
+	};
+
+	int i, n, selected = -1;
+
+	n = sizeof(speed_table) / sizeof(speed_struct);
+
+	if (arg <= 0)
+		return portc->speed;
+
+	if (devc->model == MD_1845 || devc->model == MD_1845_SSCAPE)	/* AD1845 has different timer than others */
+	{
+		if (arg < 4000)
+			arg = 4000;
+		if (arg > 50000)
+			arg = 50000;
+
+		portc->speed = arg;
+		portc->speed_bits = speed_table[3].bits;
+		return portc->speed;
+	}
+	if (arg < speed_table[0].speed)
+		selected = 0;
+	if (arg > speed_table[n - 1].speed)
+		selected = n - 1;
+
+	for (i = 1 /*really */ ; selected == -1 && i < n; i++)
+	{
+		if (speed_table[i].speed == arg)
+			selected = i;
+		else if (speed_table[i].speed > arg)
+		{
+			int diff1, diff2;
+
+			diff1 = arg - speed_table[i - 1].speed;
+			diff2 = speed_table[i].speed - arg;
+
+			if (diff1 < diff2)
+				selected = i - 1;
+			else
+				selected = i;
+		}
+	}
+	if (selected == -1)
+	{
+		printk(KERN_WARNING "ad1848: Can't find speed???\n");
+		selected = 3;
+	}
+	portc->speed = speed_table[selected].speed;
+	portc->speed_bits = speed_table[selected].bits;
+	return portc->speed;
+}
+
+static short ad1848_set_channels(int dev, short arg)
+{
+	ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+	if (arg != 1 && arg != 2)
+		return portc->channels;
+
+	portc->channels = arg;
+	return arg;
+}
+
+static unsigned int ad1848_set_bits(int dev, unsigned int arg)
+{
+	ad1848_info    *devc = (ad1848_info *) audio_devs[dev]->devc;
+	ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+	static struct format_tbl
+	{
+		  int             format;
+		  unsigned char   bits;
+	}
+	format2bits[] =
+	{
+		{
+			0, 0
+		}
+		,
+		{
+			AFMT_MU_LAW, 1
+		}
+		,
+		{
+			AFMT_A_LAW, 3
+		}
+		,
+		{
+			AFMT_IMA_ADPCM, 5
+		}
+		,
+		{
+			AFMT_U8, 0
+		}
+		,
+		{
+			AFMT_S16_LE, 2
+		}
+		,
+		{
+			AFMT_S16_BE, 6
+		}
+		,
+		{
+			AFMT_S8, 0
+		}
+		,
+		{
+			AFMT_U16_LE, 0
+		}
+		,
+		{
+			AFMT_U16_BE, 0
+		}
+	};
+	int i, n = sizeof(format2bits) / sizeof(struct format_tbl);
+
+	if (arg == 0)
+		return portc->audio_format;
+
+	if (!(arg & ad_format_mask[devc->model]))
+		arg = AFMT_U8;
+
+	portc->audio_format = arg;
+
+	for (i = 0; i < n; i++)
+		if (format2bits[i].format == arg)
+		{
+			if ((portc->format_bits = format2bits[i].bits) == 0)
+				return portc->audio_format = AFMT_U8;		/* Was not supported */
+
+			return arg;
+		}
+	/* Still hanging here. Something must be terribly wrong */
+	portc->format_bits = 0;
+	return portc->audio_format = AFMT_U8;
+}
+
+static struct audio_driver ad1848_audio_driver =
+{
+	.owner			= THIS_MODULE,
+	.open			= ad1848_open,
+	.close			= ad1848_close,
+	.output_block		= ad1848_output_block,
+	.start_input		= ad1848_start_input,
+	.prepare_for_input	= ad1848_prepare_for_input,
+	.prepare_for_output	= ad1848_prepare_for_output,
+	.halt_io		= ad1848_halt,
+	.halt_input		= ad1848_halt_input,
+	.halt_output		= ad1848_halt_output,
+	.trigger		= ad1848_trigger,
+	.set_speed		= ad1848_set_speed,
+	.set_bits		= ad1848_set_bits,
+	.set_channels		= ad1848_set_channels
+};
+
+static struct mixer_operations ad1848_mixer_operations =
+{
+	.owner	= THIS_MODULE,
+	.id	= "SOUNDPORT",
+	.name	= "AD1848/CS4248/CS4231",
+	.ioctl	= ad1848_mixer_ioctl
+};
+
+static int ad1848_open(int dev, int mode)
+{
+	ad1848_info    *devc;
+	ad1848_port_info *portc;
+	unsigned long   flags;
+
+	if (dev < 0 || dev >= num_audiodevs)
+		return -ENXIO;
+
+	devc = (ad1848_info *) audio_devs[dev]->devc;
+	portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+	/* here we don't have to protect against intr */
+	spin_lock(&devc->lock);
+	if (portc->open_mode || (devc->open_mode & mode))
+	{
+		spin_unlock(&devc->lock);
+		return -EBUSY;
+	}
+	devc->dual_dma = 0;
+
+	if (audio_devs[dev]->flags & DMA_DUPLEX)
+	{
+		devc->dual_dma = 1;
+	}
+	devc->intr_active = 0;
+	devc->audio_mode = 0;
+	devc->open_mode |= mode;
+	portc->open_mode = mode;
+	spin_unlock(&devc->lock);
+	ad1848_trigger(dev, 0);
+
+	if (mode & OPEN_READ)
+		devc->record_dev = dev;
+	if (mode & OPEN_WRITE)
+		devc->playback_dev = dev;
+/*
+ * Mute output until the playback really starts. This decreases clicking (hope so).
+ */
+	spin_lock_irqsave(&devc->lock,flags);
+	ad_mute(devc);
+	spin_unlock_irqrestore(&devc->lock,flags);
+
+	return 0;
+}
+
+static void ad1848_close(int dev)
+{
+	unsigned long   flags;
+	ad1848_info    *devc = (ad1848_info *) audio_devs[dev]->devc;
+	ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+	DEB(printk("ad1848_close(void)\n"));
+
+	devc->intr_active = 0;
+	ad1848_halt(dev);
+
+	spin_lock_irqsave(&devc->lock,flags);
+
+	devc->audio_mode = 0;
+	devc->open_mode &= ~portc->open_mode;
+	portc->open_mode = 0;
+
+	ad_unmute(devc);
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_output_block(int dev, unsigned long buf, int count, int intrflag)
+{
+	unsigned long   flags, cnt;
+	ad1848_info    *devc = (ad1848_info *) audio_devs[dev]->devc;
+	ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+	cnt = count;
+
+	if (portc->audio_format == AFMT_IMA_ADPCM)
+	{
+		cnt /= 4;
+	}
+	else
+	{
+		if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE))	/* 16 bit data */
+			cnt >>= 1;
+	}
+	if (portc->channels > 1)
+		cnt >>= 1;
+	cnt--;
+
+	if ((devc->audio_mode & PCM_ENABLE_OUTPUT) && (audio_devs[dev]->flags & DMA_AUTOMODE) &&
+	    intrflag &&
+	    cnt == devc->xfer_count)
+	{
+		devc->audio_mode |= PCM_ENABLE_OUTPUT;
+		devc->intr_active = 1;
+		return;	/*
+			 * Auto DMA mode on. No need to react
+			 */
+	}
+	spin_lock_irqsave(&devc->lock,flags);
+
+	ad_write(devc, 15, (unsigned char) (cnt & 0xff));
+	ad_write(devc, 14, (unsigned char) ((cnt >> 8) & 0xff));
+
+	devc->xfer_count = cnt;
+	devc->audio_mode |= PCM_ENABLE_OUTPUT;
+	devc->intr_active = 1;
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_start_input(int dev, unsigned long buf, int count, int intrflag)
+{
+	unsigned long   flags, cnt;
+	ad1848_info    *devc = (ad1848_info *) audio_devs[dev]->devc;
+	ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+	cnt = count;
+	if (portc->audio_format == AFMT_IMA_ADPCM)
+	{
+		cnt /= 4;
+	}
+	else
+	{
+		if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE))	/* 16 bit data */
+			cnt >>= 1;
+	}
+	if (portc->channels > 1)
+		cnt >>= 1;
+	cnt--;
+
+	if ((devc->audio_mode & PCM_ENABLE_INPUT) && (audio_devs[dev]->flags & DMA_AUTOMODE) &&
+		intrflag &&
+		cnt == devc->xfer_count)
+	{
+		devc->audio_mode |= PCM_ENABLE_INPUT;
+		devc->intr_active = 1;
+		return;	/*
+			 * Auto DMA mode on. No need to react
+			 */
+	}
+	spin_lock_irqsave(&devc->lock,flags);
+
+	if (devc->model == MD_1848)
+	{
+		  ad_write(devc, 15, (unsigned char) (cnt & 0xff));
+		  ad_write(devc, 14, (unsigned char) ((cnt >> 8) & 0xff));
+	}
+	else
+	{
+		  ad_write(devc, 31, (unsigned char) (cnt & 0xff));
+		  ad_write(devc, 30, (unsigned char) ((cnt >> 8) & 0xff));
+	}
+
+	ad_unmute(devc);
+
+	devc->xfer_count = cnt;
+	devc->audio_mode |= PCM_ENABLE_INPUT;
+	devc->intr_active = 1;
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static int ad1848_prepare_for_output(int dev, int bsize, int bcount)
+{
+	int             timeout;
+	unsigned char   fs, old_fs, tmp = 0;
+	unsigned long   flags;
+	ad1848_info    *devc = (ad1848_info *) audio_devs[dev]->devc;
+	ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+	ad_mute(devc);
+
+	spin_lock_irqsave(&devc->lock,flags);
+	fs = portc->speed_bits | (portc->format_bits << 5);
+
+	if (portc->channels > 1)
+		fs |= 0x10;
+
+	ad_enter_MCE(devc);	/* Enables changes to the format select reg */
+
+	if (devc->model == MD_1845 || devc->model == MD_1845_SSCAPE) /* Use alternate speed select registers */
+	{
+		fs &= 0xf0;	/* Mask off the rate select bits */
+
+		ad_write(devc, 22, (portc->speed >> 8) & 0xff);	/* Speed MSB */
+		ad_write(devc, 23, portc->speed & 0xff);	/* Speed LSB */
+	}
+	old_fs = ad_read(devc, 8);
+
+	if (devc->model == MD_4232 || devc->model >= MD_4236)
+	{
+		tmp = ad_read(devc, 16);
+		ad_write(devc, 16, tmp | 0x30);
+	}
+	if (devc->model == MD_IWAVE)
+		ad_write(devc, 17, 0xc2);	/* Disable variable frequency select */
+
+	ad_write(devc, 8, fs);
+
+	/*
+	 * Write to I8 starts resynchronization. Wait until it completes.
+	 */
+
+	timeout = 0;
+	while (timeout < 100 && inb(devc->base) != 0x80)
+		timeout++;
+	timeout = 0;
+	while (timeout < 10000 && inb(devc->base) == 0x80)
+		timeout++;
+
+	if (devc->model >= MD_4232)
+		ad_write(devc, 16, tmp & ~0x30);
+
+	ad_leave_MCE(devc);	/*
+				 * Starts the calibration process.
+				 */
+	spin_unlock_irqrestore(&devc->lock,flags);
+	devc->xfer_count = 0;
+
+#ifndef EXCLUDE_TIMERS
+	if (dev == timer_installed && devc->timer_running)
+		if ((fs & 0x01) != (old_fs & 0x01))
+		{
+			ad1848_tmr_reprogram(dev);
+		}
+#endif
+	ad1848_halt_output(dev);
+	return 0;
+}
+
+static int ad1848_prepare_for_input(int dev, int bsize, int bcount)
+{
+	int timeout;
+	unsigned char fs, old_fs, tmp = 0;
+	unsigned long flags;
+	ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+	ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+	if (devc->audio_mode)
+		return 0;
+
+	spin_lock_irqsave(&devc->lock,flags);
+	fs = portc->speed_bits | (portc->format_bits << 5);
+
+	if (portc->channels > 1)
+		fs |= 0x10;
+
+	ad_enter_MCE(devc);	/* Enables changes to the format select reg */
+
+	if ((devc->model == MD_1845) || (devc->model == MD_1845_SSCAPE))	/* Use alternate speed select registers */
+	{
+		fs &= 0xf0;	/* Mask off the rate select bits */
+
+		ad_write(devc, 22, (portc->speed >> 8) & 0xff);	/* Speed MSB */
+		ad_write(devc, 23, portc->speed & 0xff);	/* Speed LSB */
+	}
+	if (devc->model == MD_4232)
+	{
+		tmp = ad_read(devc, 16);
+		ad_write(devc, 16, tmp | 0x30);
+	}
+	if (devc->model == MD_IWAVE)
+		ad_write(devc, 17, 0xc2);	/* Disable variable frequency select */
+
+	/*
+	 * If mode >= 2 (CS4231), set I28. It's the capture format register.
+	 */
+	
+	if (devc->model != MD_1848)
+	{
+		old_fs = ad_read(devc, 28);
+		ad_write(devc, 28, fs);
+
+		/*
+		 * Write to I28 starts resynchronization. Wait until it completes.
+		 */
+		
+		timeout = 0;
+		while (timeout < 100 && inb(devc->base) != 0x80)
+			timeout++;
+
+		timeout = 0;
+		while (timeout < 10000 && inb(devc->base) == 0x80)
+			timeout++;
+
+		if (devc->model != MD_1848 && devc->model != MD_1845 && devc->model != MD_1845_SSCAPE)
+		{
+			/*
+			 * CS4231 compatible devices don't have separate sampling rate selection
+			 * register for recording an playback. The I8 register is shared so we have to
+			 * set the speed encoding bits of it too.
+			 */
+			unsigned char   tmp = portc->speed_bits | (ad_read(devc, 8) & 0xf0);
+
+			ad_write(devc, 8, tmp);
+			/*
+			 * Write to I8 starts resynchronization. Wait until it completes.
+			 */
+			timeout = 0;
+			while (timeout < 100 && inb(devc->base) != 0x80)
+				timeout++;
+
+			timeout = 0;
+			while (timeout < 10000 && inb(devc->base) == 0x80)
+				timeout++;
+		}
+	}
+	else
+	{			/* For AD1848 set I8. */
+
+		old_fs = ad_read(devc, 8);
+		ad_write(devc, 8, fs);
+		/*
+		 * Write to I8 starts resynchronization. Wait until it completes.
+		 */
+		timeout = 0;
+		while (timeout < 100 && inb(devc->base) != 0x80)
+			timeout++;
+		timeout = 0;
+		while (timeout < 10000 && inb(devc->base) == 0x80)
+			timeout++;
+	}
+
+	if (devc->model == MD_4232)
+		ad_write(devc, 16, tmp & ~0x30);
+
+	ad_leave_MCE(devc);	/*
+				 * Starts the calibration process.
+				 */
+	spin_unlock_irqrestore(&devc->lock,flags);
+	devc->xfer_count = 0;
+
+#ifndef EXCLUDE_TIMERS
+	if (dev == timer_installed && devc->timer_running)
+	{
+		if ((fs & 0x01) != (old_fs & 0x01))
+		{
+			ad1848_tmr_reprogram(dev);
+		}
+	}
+#endif
+	ad1848_halt_input(dev);
+	return 0;
+}
+
+static void ad1848_halt(int dev)
+{
+	ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+	ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+
+	unsigned char   bits = ad_read(devc, 9);
+
+	if (bits & 0x01 && (portc->open_mode & OPEN_WRITE))
+		ad1848_halt_output(dev);
+
+	if (bits & 0x02 && (portc->open_mode & OPEN_READ))
+		ad1848_halt_input(dev);
+	devc->audio_mode = 0;
+}
+
+static void ad1848_halt_input(int dev)
+{
+	ad1848_info    *devc = (ad1848_info *) audio_devs[dev]->devc;
+	unsigned long   flags;
+
+	if (!(ad_read(devc, 9) & 0x02))
+		return;		/* Capture not enabled */
+
+	spin_lock_irqsave(&devc->lock,flags);
+
+	ad_mute(devc);
+
+	{
+		int             tmout;
+		
+		if(!isa_dma_bridge_buggy)
+		        disable_dma(audio_devs[dev]->dmap_in->dma);
+
+		for (tmout = 0; tmout < 100000; tmout++)
+			if (ad_read(devc, 11) & 0x10)
+				break;
+		ad_write(devc, 9, ad_read(devc, 9) & ~0x02);	/* Stop capture */
+
+		if(!isa_dma_bridge_buggy)
+		        enable_dma(audio_devs[dev]->dmap_in->dma);
+		devc->audio_mode &= ~PCM_ENABLE_INPUT;
+	}
+
+	outb(0, io_Status(devc));	/* Clear interrupt status */
+	outb(0, io_Status(devc));	/* Clear interrupt status */
+
+	devc->audio_mode &= ~PCM_ENABLE_INPUT;
+
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_halt_output(int dev)
+{
+	ad1848_info *devc = (ad1848_info *) audio_devs[dev]->devc;
+	unsigned long flags;
+
+	if (!(ad_read(devc, 9) & 0x01))
+		return;		/* Playback not enabled */
+
+	spin_lock_irqsave(&devc->lock,flags);
+
+	ad_mute(devc);
+	{
+		int             tmout;
+
+		if(!isa_dma_bridge_buggy)
+		        disable_dma(audio_devs[dev]->dmap_out->dma);
+
+		for (tmout = 0; tmout < 100000; tmout++)
+			if (ad_read(devc, 11) & 0x10)
+				break;
+		ad_write(devc, 9, ad_read(devc, 9) & ~0x01);	/* Stop playback */
+
+		if(!isa_dma_bridge_buggy)
+		       enable_dma(audio_devs[dev]->dmap_out->dma);
+
+		devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
+	}
+
+	outb((0), io_Status(devc));	/* Clear interrupt status */
+	outb((0), io_Status(devc));	/* Clear interrupt status */
+
+	devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
+
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_trigger(int dev, int state)
+{
+	ad1848_info    *devc = (ad1848_info *) audio_devs[dev]->devc;
+	ad1848_port_info *portc = (ad1848_port_info *) audio_devs[dev]->portc;
+	unsigned long   flags;
+	unsigned char   tmp, old;
+
+	spin_lock_irqsave(&devc->lock,flags);
+	state &= devc->audio_mode;
+
+	tmp = old = ad_read(devc, 9);
+
+	if (portc->open_mode & OPEN_READ)
+	{
+		  if (state & PCM_ENABLE_INPUT)
+			  tmp |= 0x02;
+		  else
+			  tmp &= ~0x02;
+	}
+	if (portc->open_mode & OPEN_WRITE)
+	{
+		if (state & PCM_ENABLE_OUTPUT)
+			tmp |= 0x01;
+		else
+			tmp &= ~0x01;
+	}
+	/* ad_mute(devc); */
+	if (tmp != old)
+	{
+		  ad_write(devc, 9, tmp);
+		  ad_unmute(devc);
+	}
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_init_hw(ad1848_info * devc)
+{
+	int i;
+	int *init_values;
+
+	/*
+	 * Initial values for the indirect registers of CS4248/AD1848.
+	 */
+	static int      init_values_a[] =
+	{
+		0xa8, 0xa8, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00,
+		0x00, 0x0c, 0x02, 0x00, 0x8a, 0x01, 0x00, 0x00,
+
+	/* Positions 16 to 31 just for CS4231/2 and ad1845 */
+		0x80, 0x00, 0x10, 0x10, 0x00, 0x00, 0x1f, 0x40,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+
+	static int      init_values_b[] =
+	{
+		/* 
+		   Values for the newer chips
+		   Some of the register initialization values were changed. In
+		   order to get rid of the click that preceded PCM playback,
+		   calibration was disabled on the 10th byte. On that same byte,
+		   dual DMA was enabled; on the 11th byte, ADC dithering was
+		   enabled, since that is theoretically desirable; on the 13th
+		   byte, Mode 3 was selected, to enable access to extended
+		   registers.
+		 */
+		0xa8, 0xa8, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00,
+		0x00, 0x00, 0x06, 0x00, 0xe0, 0x01, 0x00, 0x00,
+ 		0x80, 0x00, 0x10, 0x10, 0x00, 0x00, 0x1f, 0x40,
+ 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+
+	/*
+	 *	Select initialisation data
+	 */
+	 
+	init_values = init_values_a;
+	if(devc->model >= MD_4236)
+		init_values = init_values_b;
+
+	for (i = 0; i < 16; i++)
+		ad_write(devc, i, init_values[i]);
+
+
+	ad_mute(devc);		/* Initialize some variables */
+	ad_unmute(devc);	/* Leave it unmuted now */
+
+	if (devc->model > MD_1848)
+	{
+		if (devc->model == MD_1845_SSCAPE)
+			ad_write(devc, 12, ad_read(devc, 12) | 0x50);
+		else 
+			ad_write(devc, 12, ad_read(devc, 12) | 0x40);		/* Mode2 = enabled */
+
+		if (devc->model == MD_IWAVE)
+			ad_write(devc, 12, 0x6c);	/* Select codec mode 3 */
+
+		if (devc->model != MD_1845_SSCAPE)
+			for (i = 16; i < 32; i++)
+				ad_write(devc, i, init_values[i]);
+
+		if (devc->model == MD_IWAVE)
+			ad_write(devc, 16, 0x30);	/* Playback and capture counters enabled */
+	}
+	if (devc->model > MD_1848)
+	{
+		if (devc->audio_flags & DMA_DUPLEX)
+			ad_write(devc, 9, ad_read(devc, 9) & ~0x04);	/* Dual DMA mode */
+		else
+			ad_write(devc, 9, ad_read(devc, 9) | 0x04);	/* Single DMA mode */
+
+		if (devc->model == MD_1845 || devc->model == MD_1845_SSCAPE)
+			ad_write(devc, 27, ad_read(devc, 27) | 0x08);		/* Alternate freq select enabled */
+
+		if (devc->model == MD_IWAVE)
+		{		/* Some magic Interwave specific initialization */
+			ad_write(devc, 12, 0x6c);	/* Select codec mode 3 */
+			ad_write(devc, 16, 0x30);	/* Playback and capture counters enabled */
+			ad_write(devc, 17, 0xc2);	/* Alternate feature enable */
+		}
+	}
+	else
+	{
+		  devc->audio_flags &= ~DMA_DUPLEX;
+		  ad_write(devc, 9, ad_read(devc, 9) | 0x04);	/* Single DMA mode */
+		  if (soundpro)
+			  ad_write(devc, 12, ad_read(devc, 12) | 0x40);	/* Mode2 = enabled */
+	}
+
+	outb((0), io_Status(devc));	/* Clear pending interrupts */
+
+	/*
+	 * Toggle the MCE bit. It completes the initialization phase.
+	 */
+
+	ad_enter_MCE(devc);	/* In case the bit was off */
+	ad_leave_MCE(devc);
+
+	ad1848_mixer_reset(devc);
+}
+
+int ad1848_detect(struct resource *ports, int *ad_flags, int *osp)
+{
+	unsigned char tmp;
+	ad1848_info *devc = &adev_info[nr_ad1848_devs];
+	unsigned char tmp1 = 0xff, tmp2 = 0xff;
+	int optiC930 = 0;	/* OPTi 82C930 flag */
+	int interwave = 0;
+	int ad1847_flag = 0;
+	int cs4248_flag = 0;
+	int sscape_flag = 0;
+	int io_base = ports->start;
+
+	int i;
+
+	DDB(printk("ad1848_detect(%x)\n", io_base));
+
+	if (ad_flags)
+	{
+		if (*ad_flags == 0x12345678)
+		{
+			interwave = 1;
+			*ad_flags = 0;
+		}
+		
+		if (*ad_flags == 0x87654321)
+		{
+			sscape_flag = 1;
+			*ad_flags = 0;
+		}
+		
+		if (*ad_flags == 0x12345677)
+		{
+		    cs4248_flag = 1;
+		    *ad_flags = 0;
+		}
+	}
+	if (nr_ad1848_devs >= MAX_AUDIO_DEV)
+	{
+		printk(KERN_ERR "ad1848 - Too many audio devices\n");
+		return 0;
+	}
+	spin_lock_init(&devc->lock);
+	devc->base = io_base;
+	devc->irq_ok = 0;
+	devc->timer_running = 0;
+	devc->MCE_bit = 0x40;
+	devc->irq = 0;
+	devc->open_mode = 0;
+	devc->chip_name = devc->name = "AD1848";
+	devc->model = MD_1848;	/* AD1848 or CS4248 */
+	devc->levels = NULL;
+	devc->debug_flag = 0;
+
+	/*
+	 * Check that the I/O address is in use.
+	 *
+	 * The bit 0x80 of the base I/O port is known to be 0 after the
+	 * chip has performed its power on initialization. Just assume
+	 * this has happened before the OS is starting.
+	 *
+	 * If the I/O address is unused, it typically returns 0xff.
+	 */
+
+	if (inb(devc->base) == 0xff)
+	{
+		DDB(printk("ad1848_detect: The base I/O address appears to be dead\n"));
+	}
+
+	/*
+	 * Wait for the device to stop initialization
+	 */
+	
+	DDB(printk("ad1848_detect() - step 0\n"));
+
+	for (i = 0; i < 10000000; i++)
+	{
+		unsigned char   x = inb(devc->base);
+
+		if (x == 0xff || !(x & 0x80))
+			break;
+	}
+
+	DDB(printk("ad1848_detect() - step A\n"));
+
+	if (inb(devc->base) == 0x80)	/* Not ready. Let's wait */
+		ad_leave_MCE(devc);
+
+	if ((inb(devc->base) & 0x80) != 0x00)	/* Not a AD1848 */
+	{
+		DDB(printk("ad1848 detect error - step A (%02x)\n", (int) inb(devc->base)));
+		return 0;
+	}
+	
+	/*
+	 * Test if it's possible to change contents of the indirect registers.
+	 * Registers 0 and 1 are ADC volume registers. The bit 0x10 is read only
+	 * so try to avoid using it.
+	 */
+
+	DDB(printk("ad1848_detect() - step B\n"));
+	ad_write(devc, 0, 0xaa);
+	ad_write(devc, 1, 0x45);	/* 0x55 with bit 0x10 clear */
+
+	if ((tmp1 = ad_read(devc, 0)) != 0xaa || (tmp2 = ad_read(devc, 1)) != 0x45)
+	{
+		if (tmp2 == 0x65)	/* AD1847 has couple of bits hardcoded to 1 */
+			ad1847_flag = 1;
+		else
+		{
+			DDB(printk("ad1848 detect error - step B (%x/%x)\n", tmp1, tmp2));
+			return 0;
+		}
+	}
+	DDB(printk("ad1848_detect() - step C\n"));
+	ad_write(devc, 0, 0x45);
+	ad_write(devc, 1, 0xaa);
+
+	if ((tmp1 = ad_read(devc, 0)) != 0x45 || (tmp2 = ad_read(devc, 1)) != 0xaa)
+	{
+		if (tmp2 == 0x8a)	/* AD1847 has few bits hardcoded to 1 */
+			ad1847_flag = 1;
+		else
+		{
+			DDB(printk("ad1848 detect error - step C (%x/%x)\n", tmp1, tmp2));
+			return 0;
+		}
+	}
+
+	/*
+	 * The indirect register I12 has some read only bits. Let's
+	 * try to change them.
+	 */
+
+	DDB(printk("ad1848_detect() - step D\n"));
+	tmp = ad_read(devc, 12);
+	ad_write(devc, 12, (~tmp) & 0x0f);
+
+	if ((tmp & 0x0f) != ((tmp1 = ad_read(devc, 12)) & 0x0f))
+	{
+		DDB(printk("ad1848 detect error - step D (%x)\n", tmp1));
+		return 0;
+	}
+	
+	/*
+	 * NOTE! Last 4 bits of the reg I12 tell the chip revision.
+	 *   0x01=RevB and 0x0A=RevC.
+	 */
+
+	/*
+	 * The original AD1848/CS4248 has just 15 indirect registers. This means
+	 * that I0 and I16 should return the same value (etc.).
+	 * However this doesn't work with CS4248. Actually it seems to be impossible
+	 * to detect if the chip is a CS4231 or CS4248.
+	 * Ensure that the Mode2 enable bit of I12 is 0. Otherwise this test fails
+	 * with CS4231.
+	 */
+
+	/*
+	 * OPTi 82C930 has mode2 control bit in another place. This test will fail
+	 * with it. Accept this situation as a possible indication of this chip.
+	 */
+
+	DDB(printk("ad1848_detect() - step F\n"));
+	ad_write(devc, 12, 0);	/* Mode2=disabled */
+
+	for (i = 0; i < 16; i++)
+	{
+		if ((tmp1 = ad_read(devc, i)) != (tmp2 = ad_read(devc, i + 16)))
+		{
+			DDB(printk("ad1848 detect step F(%d/%x/%x) - OPTi chip???\n", i, tmp1, tmp2));
+			if (!ad1847_flag)
+				optiC930 = 1;
+			break;
+		}
+	}
+
+	/*
+	 * Try to switch the chip to mode2 (CS4231) by setting the MODE2 bit (0x40).
+	 * The bit 0x80 is always 1 in CS4248 and CS4231.
+	 */
+
+	DDB(printk("ad1848_detect() - step G\n"));
+
+	if (ad_flags && *ad_flags == 400)
+		*ad_flags = 0;
+	else
+		ad_write(devc, 12, 0x40);	/* Set mode2, clear 0x80 */
+
+
+	if (ad_flags)
+		*ad_flags = 0;
+
+	tmp1 = ad_read(devc, 12);
+	if (tmp1 & 0x80)
+	{
+		if (ad_flags)
+			*ad_flags |= AD_F_CS4248;
+
+		devc->chip_name = "CS4248";	/* Our best knowledge just now */
+	}
+	if (optiC930 || (tmp1 & 0xc0) == (0x80 | 0x40))
+	{
+		/*
+		 *      CS4231 detected - is it?
+		 *
+		 *      Verify that setting I0 doesn't change I16.
+		 */
+		
+		DDB(printk("ad1848_detect() - step H\n"));
+		ad_write(devc, 16, 0);	/* Set I16 to known value */
+
+		ad_write(devc, 0, 0x45);
+		if ((tmp1 = ad_read(devc, 16)) != 0x45)	/* No change -> CS4231? */
+		{
+			ad_write(devc, 0, 0xaa);
+			if ((tmp1 = ad_read(devc, 16)) == 0xaa)	/* Rotten bits? */
+			{
+				DDB(printk("ad1848 detect error - step H(%x)\n", tmp1));
+				return 0;
+			}
+			
+			/*
+			 * Verify that some bits of I25 are read only.
+			 */
+
+			DDB(printk("ad1848_detect() - step I\n"));
+			tmp1 = ad_read(devc, 25);	/* Original bits */
+			ad_write(devc, 25, ~tmp1);	/* Invert all bits */
+			if ((ad_read(devc, 25) & 0xe7) == (tmp1 & 0xe7))
+			{
+				int id;
+
+				/*
+				 *      It's at least CS4231
+				 */
+
+				devc->chip_name = "CS4231";
+				devc->model = MD_4231;
+				
+				/*
+				 * It could be an AD1845 or CS4231A as well.
+				 * CS4231 and AD1845 report the same revision info in I25
+				 * while the CS4231A reports different.
+				 */
+
+				id = ad_read(devc, 25);
+				if ((id & 0xe7) == 0x80)	/* Device busy??? */
+					id = ad_read(devc, 25);
+				if ((id & 0xe7) == 0x80)	/* Device still busy??? */
+					id = ad_read(devc, 25);
+				DDB(printk("ad1848_detect() - step J (%02x/%02x)\n", id, ad_read(devc, 25)));
+
+                                if ((id & 0xe7) == 0x80) {
+					/* 
+					 * It must be a CS4231 or AD1845. The register I23 of
+					 * CS4231 is undefined and it appears to be read only.
+					 * AD1845 uses I23 for setting sample rate. Assume
+					 * the chip is AD1845 if I23 is changeable.
+					 */
+
+					unsigned char   tmp = ad_read(devc, 23);
+					ad_write(devc, 23, ~tmp);
+
+					if (interwave)
+					{
+						devc->model = MD_IWAVE;
+						devc->chip_name = "IWave";
+					}
+					else if (ad_read(devc, 23) != tmp)	/* AD1845 ? */
+					{
+						devc->chip_name = "AD1845";
+						devc->model = MD_1845;
+					}
+					else if (cs4248_flag)
+					{
+						if (ad_flags)
+							  *ad_flags |= AD_F_CS4248;
+						devc->chip_name = "CS4248";
+						devc->model = MD_1848;
+						ad_write(devc, 12, ad_read(devc, 12) & ~0x40);	/* Mode2 off */
+					}
+					ad_write(devc, 23, tmp);	/* Restore */
+				}
+				else
+				{
+					switch (id & 0x1f) {
+					case 3: /* CS4236/CS4235/CS42xB/CS4239 */
+						{
+							int xid;
+							ad_write(devc, 12, ad_read(devc, 12) | 0x60); /* switch to mode 3 */
+							ad_write(devc, 23, 0x9c); /* select extended register 25 */
+							xid = inb(io_Indexed_Data(devc));
+							ad_write(devc, 12, ad_read(devc, 12) & ~0x60); /* back to mode 0 */
+							switch (xid & 0x1f)
+							{
+								case 0x00:
+									devc->chip_name = "CS4237B(B)";
+									devc->model = MD_42xB;
+									break;
+								case 0x08:
+									/* Seems to be a 4238 ?? */
+									devc->chip_name = "CS4238";
+									devc->model = MD_42xB;
+									break;
+								case 0x09:
+									devc->chip_name = "CS4238B";
+									devc->model = MD_42xB;
+									break;
+								case 0x0b:
+									devc->chip_name = "CS4236B";
+									devc->model = MD_4236;
+									break;
+								case 0x10:
+									devc->chip_name = "CS4237B";
+									devc->model = MD_42xB;
+									break;
+								case 0x1d:
+									devc->chip_name = "CS4235";
+									devc->model = MD_4235;
+									break;
+								case 0x1e:
+									devc->chip_name = "CS4239";
+									devc->model = MD_4239;
+									break;
+								default:
+									printk("Chip ident is %X.\n", xid&0x1F);
+									devc->chip_name = "CS42xx";
+									devc->model = MD_4232;
+									break;
+							}
+						}
+						break;
+
+					case 2: /* CS4232/CS4232A */
+						devc->chip_name = "CS4232";
+						devc->model = MD_4232;
+						break;
+				
+					case 0:
+						if ((id & 0xe0) == 0xa0)
+						{
+							devc->chip_name = "CS4231A";
+							devc->model = MD_4231A;
+						}
+						else
+						{
+							devc->chip_name = "CS4321";
+							devc->model = MD_4231;
+						}
+						break;
+
+					default: /* maybe */
+						DDB(printk("ad1848: I25 = %02x/%02x\n", ad_read(devc, 25), ad_read(devc, 25) & 0xe7));
+                                                if (optiC930)
+                                                {
+                                                        devc->chip_name = "82C930";
+                                                        devc->model = MD_C930;
+                                                }
+						else
+						{
+							devc->chip_name = "CS4231";
+							devc->model = MD_4231;
+						}
+					}
+				}
+			}
+			ad_write(devc, 25, tmp1);	/* Restore bits */
+
+			DDB(printk("ad1848_detect() - step K\n"));
+		}
+	} else if (tmp1 == 0x0a) {
+		/*
+		 * Is it perhaps a SoundPro CMI8330?
+		 * If so, then we should be able to change indirect registers
+		 * greater than I15 after activating MODE2, even though reading
+		 * back I12 does not show it.
+		 */
+
+		/*
+		 * Let's try comparing register values
+		 */
+		for (i = 0; i < 16; i++) {
+			if ((tmp1 = ad_read(devc, i)) != (tmp2 = ad_read(devc, i + 16))) {
+				DDB(printk("ad1848 detect step H(%d/%x/%x) - SoundPro chip?\n", i, tmp1, tmp2));
+				soundpro = 1;
+				devc->chip_name = "SoundPro CMI 8330";
+				break;
+			}
+		}
+	}
+
+	DDB(printk("ad1848_detect() - step L\n"));
+	if (ad_flags)
+	{
+		  if (devc->model != MD_1848)
+			  *ad_flags |= AD_F_CS4231;
+	}
+	DDB(printk("ad1848_detect() - Detected OK\n"));
+
+	if (devc->model == MD_1848 && ad1847_flag)
+		devc->chip_name = "AD1847";
+
+
+	if (sscape_flag == 1)
+		devc->model = MD_1845_SSCAPE;
+
+	return 1;
+}
+
+int ad1848_init (char *name, struct resource *ports, int irq, int dma_playback,
+		int dma_capture, int share_dma, int *osp, struct module *owner)
+{
+	/*
+	 * NOTE! If irq < 0, there is another driver which has allocated the IRQ
+	 *   so that this driver doesn't need to allocate/deallocate it.
+	 *   The actually used IRQ is ABS(irq).
+	 */
+
+	int my_dev;
+	char dev_name[100];
+	int e;
+
+	ad1848_info  *devc = &adev_info[nr_ad1848_devs];
+
+	ad1848_port_info *portc = NULL;
+
+	devc->irq = (irq > 0) ? irq : 0;
+	devc->open_mode = 0;
+	devc->timer_ticks = 0;
+	devc->dma1 = dma_playback;
+	devc->dma2 = dma_capture;
+	devc->subtype = cfg.card_subtype;
+	devc->audio_flags = DMA_AUTOMODE;
+	devc->playback_dev = devc->record_dev = 0;
+	if (name != NULL)
+		devc->name = name;
+
+	if (name != NULL && name[0] != 0)
+		sprintf(dev_name,
+			"%s (%s)", name, devc->chip_name);
+	else
+		sprintf(dev_name,
+			"Generic audio codec (%s)", devc->chip_name);
+
+	rename_region(ports, devc->name);
+
+	conf_printf2(dev_name, devc->base, devc->irq, dma_playback, dma_capture);
+
+	if (devc->model == MD_1848 || devc->model == MD_C930)
+		devc->audio_flags |= DMA_HARDSTOP;
+
+	if (devc->model > MD_1848)
+	{
+		if (devc->dma1 == devc->dma2 || devc->dma2 == -1 || devc->dma1 == -1)
+			devc->audio_flags &= ~DMA_DUPLEX;
+		else
+			devc->audio_flags |= DMA_DUPLEX;
+	}
+
+	portc = kmalloc(sizeof(ad1848_port_info), GFP_KERNEL);
+	if(portc==NULL) {
+		release_region(devc->base, 4);
+		return -1;
+	}
+
+	if ((my_dev = sound_install_audiodrv(AUDIO_DRIVER_VERSION,
+					     dev_name,
+					     &ad1848_audio_driver,
+					     sizeof(struct audio_driver),
+					     devc->audio_flags,
+					     ad_format_mask[devc->model],
+					     devc,
+					     dma_playback,
+					     dma_capture)) < 0)
+	{
+		release_region(devc->base, 4);
+		kfree(portc);
+		return -1;
+	}
+	
+	audio_devs[my_dev]->portc = portc;
+	audio_devs[my_dev]->mixer_dev = -1;
+	if (owner)
+		audio_devs[my_dev]->d->owner = owner;
+	memset((char *) portc, 0, sizeof(*portc));
+
+	nr_ad1848_devs++;
+
+	ad1848_init_hw(devc);
+
+	if (irq > 0)
+	{
+		devc->dev_no = my_dev;
+		if (request_irq(devc->irq, adintr, 0, devc->name,
+				(void *)(long)my_dev) < 0)
+		{
+			printk(KERN_WARNING "ad1848: Unable to allocate IRQ\n");
+			/* Don't free it either then.. */
+			devc->irq = 0;
+		}
+		if (capabilities[devc->model].flags & CAP_F_TIMER)
+		{
+#ifndef CONFIG_SMP
+			int x;
+			unsigned char tmp = ad_read(devc, 16);
+#endif			
+
+			devc->timer_ticks = 0;
+
+			ad_write(devc, 21, 0x00);	/* Timer MSB */
+			ad_write(devc, 20, 0x10);	/* Timer LSB */
+#ifndef CONFIG_SMP
+			ad_write(devc, 16, tmp | 0x40);	/* Enable timer */
+			for (x = 0; x < 100000 && devc->timer_ticks == 0; x++);
+			ad_write(devc, 16, tmp & ~0x40);	/* Disable timer */
+
+			if (devc->timer_ticks == 0)
+				printk(KERN_WARNING "ad1848: Interrupt test failed (IRQ%d)\n", irq);
+			else
+			{
+				DDB(printk("Interrupt test OK\n"));
+				devc->irq_ok = 1;
+			}
+#else
+			devc->irq_ok = 1;
+#endif			
+		}
+		else
+			devc->irq_ok = 1;	/* Couldn't test. assume it's OK */
+	} else if (irq < 0)
+		irq2dev[-irq] = devc->dev_no = my_dev;
+
+#ifndef EXCLUDE_TIMERS
+	if ((capabilities[devc->model].flags & CAP_F_TIMER) &&
+	    devc->irq_ok)
+		ad1848_tmr_install(my_dev);
+#endif
+
+	if (!share_dma)
+	{
+		if (sound_alloc_dma(dma_playback, devc->name))
+			printk(KERN_WARNING "ad1848.c: Can't allocate DMA%d\n", dma_playback);
+
+		if (dma_capture != dma_playback)
+			if (sound_alloc_dma(dma_capture, devc->name))
+				printk(KERN_WARNING "ad1848.c: Can't allocate DMA%d\n", dma_capture);
+	}
+
+	if ((e = sound_install_mixer(MIXER_DRIVER_VERSION,
+				     dev_name,
+				     &ad1848_mixer_operations,
+				     sizeof(struct mixer_operations),
+				     devc)) >= 0)
+	{
+		audio_devs[my_dev]->mixer_dev = e;
+		if (owner)
+			mixer_devs[e]->owner = owner;
+	}
+	return my_dev;
+}
+
+int ad1848_control(int cmd, int arg)
+{
+	ad1848_info *devc;
+	unsigned long flags;
+
+	if (nr_ad1848_devs < 1)
+		return -ENODEV;
+
+	devc = &adev_info[nr_ad1848_devs - 1];
+
+	switch (cmd)
+	{
+		case AD1848_SET_XTAL:	/* Change clock frequency of AD1845 (only ) */
+			if (devc->model != MD_1845 && devc->model != MD_1845_SSCAPE)
+				return -EINVAL;
+			spin_lock_irqsave(&devc->lock,flags);
+			ad_enter_MCE(devc);
+			ad_write(devc, 29, (ad_read(devc, 29) & 0x1f) | (arg << 5));
+			ad_leave_MCE(devc);
+			spin_unlock_irqrestore(&devc->lock,flags);
+			break;
+
+		case AD1848_MIXER_REROUTE:
+		{
+			int o = (arg >> 8) & 0xff;
+			int n = arg & 0xff;
+
+			if (o < 0 || o >= SOUND_MIXER_NRDEVICES)
+				return -EINVAL;
+
+			if (!(devc->supported_devices & (1 << o)) &&
+			    !(devc->supported_rec_devices & (1 << o)))
+				return -EINVAL;
+
+			if (n == SOUND_MIXER_NONE)
+			{	/* Just hide this control */
+				ad1848_mixer_set(devc, o, 0);	/* Shut up it */
+				devc->supported_devices &= ~(1 << o);
+				devc->supported_rec_devices &= ~(1 << o);
+				break;
+			}
+
+			/* Make the mixer control identified by o to appear as n */
+			if (n < 0 || n >= SOUND_MIXER_NRDEVICES)
+				return -EINVAL;
+
+			devc->mixer_reroute[n] = o;	/* Rename the control */
+			if (devc->supported_devices & (1 << o))
+				devc->supported_devices |= (1 << n);
+			if (devc->supported_rec_devices & (1 << o))
+				devc->supported_rec_devices |= (1 << n);
+
+			devc->supported_devices &= ~(1 << o);
+			devc->supported_rec_devices &= ~(1 << o);
+		}
+		break;
+	}
+	return 0;
+}
+
+void ad1848_unload(int io_base, int irq, int dma_playback, int dma_capture, int share_dma)
+{
+	int i, mixer, dev = 0;
+	ad1848_info *devc = NULL;
+
+	for (i = 0; devc == NULL && i < nr_ad1848_devs; i++)
+	{
+		if (adev_info[i].base == io_base)
+		{
+			devc = &adev_info[i];
+			dev = devc->dev_no;
+		}
+	}
+		
+	if (devc != NULL)
+	{
+		kfree(audio_devs[dev]->portc);
+		release_region(devc->base, 4);
+
+		if (!share_dma)
+		{
+			if (devc->irq > 0) /* There is no point in freeing irq, if it wasn't allocated */
+				free_irq(devc->irq, (void *)(long)devc->dev_no);
+
+			sound_free_dma(dma_playback);
+
+			if (dma_playback != dma_capture)
+				sound_free_dma(dma_capture);
+
+		}
+		mixer = audio_devs[devc->dev_no]->mixer_dev;
+		if(mixer>=0)
+			sound_unload_mixerdev(mixer);
+
+		nr_ad1848_devs--;
+		for ( ; i < nr_ad1848_devs ; i++)
+			adev_info[i] = adev_info[i+1];
+	}
+	else
+		printk(KERN_ERR "ad1848: Can't find device to be unloaded. Base=%x\n", io_base);
+}
+
+static irqreturn_t adintr(int irq, void *dev_id)
+{
+	unsigned char status;
+	ad1848_info *devc;
+	int dev;
+	int alt_stat = 0xff;
+	unsigned char c930_stat = 0;
+	int cnt = 0;
+
+	dev = (long)dev_id;
+	devc = (ad1848_info *) audio_devs[dev]->devc;
+
+interrupt_again:		/* Jump back here if int status doesn't reset */
+
+	status = inb(io_Status(devc));
+
+	if (status == 0x80)
+		printk(KERN_DEBUG "adintr: Why?\n");
+	if (devc->model == MD_1848)
+		outb((0), io_Status(devc));	/* Clear interrupt status */
+
+	if (status & 0x01)
+	{
+		if (devc->model == MD_C930)
+		{		/* 82C930 has interrupt status register in MAD16 register MC11 */
+
+			spin_lock(&devc->lock);
+
+			/* 0xe0e is C930 address port
+			 * 0xe0f is C930 data port
+			 */
+			outb(11, 0xe0e);
+			c930_stat = inb(0xe0f);
+			outb((~c930_stat), 0xe0f);
+
+			spin_unlock(&devc->lock);
+
+			alt_stat = (c930_stat << 2) & 0x30;
+		}
+		else if (devc->model != MD_1848)
+		{
+			spin_lock(&devc->lock);
+			alt_stat = ad_read(devc, 24);
+			ad_write(devc, 24, ad_read(devc, 24) & ~alt_stat);	/* Selective ack */
+			spin_unlock(&devc->lock);
+		}
+
+		if ((devc->open_mode & OPEN_READ) && (devc->audio_mode & PCM_ENABLE_INPUT) && (alt_stat & 0x20))
+		{
+			DMAbuf_inputintr(devc->record_dev);
+		}
+		if ((devc->open_mode & OPEN_WRITE) && (devc->audio_mode & PCM_ENABLE_OUTPUT) &&
+		      (alt_stat & 0x10))
+		{
+			DMAbuf_outputintr(devc->playback_dev, 1);
+		}
+		if (devc->model != MD_1848 && (alt_stat & 0x40))	/* Timer interrupt */
+		{
+			devc->timer_ticks++;
+#ifndef EXCLUDE_TIMERS
+			if (timer_installed == dev && devc->timer_running)
+				sound_timer_interrupt();
+#endif
+		}
+	}
+/*
+ * Sometimes playback or capture interrupts occur while a timer interrupt
+ * is being handled. The interrupt will not be retriggered if we don't
+ * handle it now. Check if an interrupt is still pending and restart
+ * the handler in this case.
+ */
+	if (inb(io_Status(devc)) & 0x01 && cnt++ < 4)
+	{
+		  goto interrupt_again;
+	}
+	return IRQ_HANDLED;
+}
+
+/*
+ *	Experimental initialization sequence for the integrated sound system
+ *	of the Compaq Deskpro M.
+ */
+
+static int init_deskpro_m(struct address_info *hw_config)
+{
+	unsigned char   tmp;
+
+	if ((tmp = inb(0xc44)) == 0xff)
+	{
+		DDB(printk("init_deskpro_m: Dead port 0xc44\n"));
+		return 0;
+	}
+
+	outb(0x10, 0xc44);
+	outb(0x40, 0xc45);
+	outb(0x00, 0xc46);
+	outb(0xe8, 0xc47);
+	outb(0x14, 0xc44);
+	outb(0x40, 0xc45);
+	outb(0x00, 0xc46);
+	outb(0xe8, 0xc47);
+	outb(0x10, 0xc44);
+
+	return 1;
+}
+
+/*
+ *	Experimental initialization sequence for the integrated sound system
+ *	of Compaq Deskpro XL.
+ */
+
+static int init_deskpro(struct address_info *hw_config)
+{
+	unsigned char   tmp;
+
+	if ((tmp = inb(0xc44)) == 0xff)
+	{
+		DDB(printk("init_deskpro: Dead port 0xc44\n"));
+		return 0;
+	}
+	outb((tmp | 0x04), 0xc44);	/* Select bank 1 */
+	if (inb(0xc44) != 0x04)
+	{
+		DDB(printk("init_deskpro: Invalid bank1 signature in port 0xc44\n"));
+		return 0;
+	}
+	/*
+	 * OK. It looks like a Deskpro so let's proceed.
+	 */
+
+	/*
+	 * I/O port 0xc44 Audio configuration register.
+	 *
+	 * bits 0xc0:   Audio revision bits
+	 *              0x00 = Compaq Business Audio
+	 *              0x40 = MS Sound System Compatible (reset default)
+	 *              0x80 = Reserved
+	 *              0xc0 = Reserved
+	 * bit 0x20:    No Wait State Enable
+	 *              0x00 = Disabled (reset default, DMA mode)
+	 *              0x20 = Enabled (programmed I/O mode)
+	 * bit 0x10:    MS Sound System Decode Enable
+	 *              0x00 = Decoding disabled (reset default)
+	 *              0x10 = Decoding enabled
+	 * bit 0x08:    FM Synthesis Decode Enable
+	 *              0x00 = Decoding Disabled (reset default)
+	 *              0x08 = Decoding enabled
+	 * bit 0x04     Bank select
+	 *              0x00 = Bank 0
+	 *              0x04 = Bank 1
+	 * bits 0x03    MSS Base address
+	 *              0x00 = 0x530 (reset default)
+	 *              0x01 = 0x604
+	 *              0x02 = 0xf40
+	 *              0x03 = 0xe80
+	 */
+
+#ifdef DEBUGXL
+	/* Debug printing */
+	printk("Port 0xc44 (before): ");
+	outb((tmp & ~0x04), 0xc44);
+	printk("%02x ", inb(0xc44));
+	outb((tmp | 0x04), 0xc44);
+	printk("%02x\n", inb(0xc44));
+#endif
+
+	/* Set bank 1 of the register */
+	tmp = 0x58;		/* MSS Mode, MSS&FM decode enabled */
+
+	switch (hw_config->io_base)
+	{
+		case 0x530:
+			tmp |= 0x00;
+			break;
+		case 0x604:
+			tmp |= 0x01;
+			break;
+		case 0xf40:
+			tmp |= 0x02;
+			break;
+		case 0xe80:
+			tmp |= 0x03;
+			break;
+		default:
+			DDB(printk("init_deskpro: Invalid MSS port %x\n", hw_config->io_base));
+			return 0;
+	}
+	outb((tmp & ~0x04), 0xc44);	/* Write to bank=0 */
+
+#ifdef DEBUGXL
+	/* Debug printing */
+	printk("Port 0xc44 (after): ");
+	outb((tmp & ~0x04), 0xc44);	/* Select bank=0 */
+	printk("%02x ", inb(0xc44));
+	outb((tmp | 0x04), 0xc44);	/* Select bank=1 */
+	printk("%02x\n", inb(0xc44));
+#endif
+
+	/*
+	 * I/O port 0xc45 FM Address Decode/MSS ID Register.
+	 *
+	 * bank=0, bits 0xfe:   FM synthesis Decode Compare bits 7:1 (default=0x88)
+	 * bank=0, bit 0x01:    SBIC Power Control Bit
+	 *                      0x00 = Powered up
+	 *                      0x01 = Powered down
+	 * bank=1, bits 0xfc:   MSS ID (default=0x40)
+	 */
+
+#ifdef DEBUGXL
+	/* Debug printing */
+	printk("Port 0xc45 (before): ");
+	outb((tmp & ~0x04), 0xc44);	/* Select bank=0 */
+	printk("%02x ", inb(0xc45));
+	outb((tmp | 0x04), 0xc44);	/* Select bank=1 */
+	printk("%02x\n", inb(0xc45));
+#endif
+
+	outb((tmp & ~0x04), 0xc44);	/* Select bank=0 */
+	outb((0x88), 0xc45);	/* FM base 7:0 = 0x88 */
+	outb((tmp | 0x04), 0xc44);	/* Select bank=1 */
+	outb((0x10), 0xc45);	/* MSS ID = 0x10 (MSS port returns 0x04) */
+
+#ifdef DEBUGXL
+	/* Debug printing */
+	printk("Port 0xc45 (after): ");
+	outb((tmp & ~0x04), 0xc44);	/* Select bank=0 */
+	printk("%02x ", inb(0xc45));
+	outb((tmp | 0x04), 0xc44);	/* Select bank=1 */
+	printk("%02x\n", inb(0xc45));
+#endif
+
+
+	/*
+	 * I/O port 0xc46 FM Address Decode/Address ASIC Revision Register.
+	 *
+	 * bank=0, bits 0xff:   FM synthesis Decode Compare bits 15:8 (default=0x03)
+	 * bank=1, bits 0xff:   Audio addressing ASIC id
+	 */
+
+#ifdef DEBUGXL
+	/* Debug printing */
+	printk("Port 0xc46 (before): ");
+	outb((tmp & ~0x04), 0xc44);	/* Select bank=0 */
+	printk("%02x ", inb(0xc46));
+	outb((tmp | 0x04), 0xc44);	/* Select bank=1 */
+	printk("%02x\n", inb(0xc46));
+#endif
+
+	outb((tmp & ~0x04), 0xc44);	/* Select bank=0 */
+	outb((0x03), 0xc46);	/* FM base 15:8 = 0x03 */
+	outb((tmp | 0x04), 0xc44);	/* Select bank=1 */
+	outb((0x11), 0xc46);	/* ASIC ID = 0x11 */
+
+#ifdef DEBUGXL
+	/* Debug printing */
+	printk("Port 0xc46 (after): ");
+	outb((tmp & ~0x04), 0xc44);	/* Select bank=0 */
+	printk("%02x ", inb(0xc46));
+	outb((tmp | 0x04), 0xc44);	/* Select bank=1 */
+	printk("%02x\n", inb(0xc46));
+#endif
+
+	/*
+	 * I/O port 0xc47 FM Address Decode Register.
+	 *
+	 * bank=0, bits 0xff:   Decode enable selection for various FM address bits
+	 * bank=1, bits 0xff:   Reserved
+	 */
+
+#ifdef DEBUGXL
+	/* Debug printing */
+	printk("Port 0xc47 (before): ");
+	outb((tmp & ~0x04), 0xc44);	/* Select bank=0 */
+	printk("%02x ", inb(0xc47));
+	outb((tmp | 0x04), 0xc44);	/* Select bank=1 */
+	printk("%02x\n", inb(0xc47));
+#endif
+
+	outb((tmp & ~0x04), 0xc44);	/* Select bank=0 */
+	outb((0x7c), 0xc47);	/* FM decode enable bits = 0x7c */
+	outb((tmp | 0x04), 0xc44);	/* Select bank=1 */
+	outb((0x00), 0xc47);	/* Reserved bank1 = 0x00 */
+
+#ifdef DEBUGXL
+	/* Debug printing */
+	printk("Port 0xc47 (after): ");
+	outb((tmp & ~0x04), 0xc44);	/* Select bank=0 */
+	printk("%02x ", inb(0xc47));
+	outb((tmp | 0x04), 0xc44);	/* Select bank=1 */
+	printk("%02x\n", inb(0xc47));
+#endif
+
+	/*
+	 * I/O port 0xc6f = Audio Disable Function Register
+	 */
+
+#ifdef DEBUGXL
+	printk("Port 0xc6f (before) = %02x\n", inb(0xc6f));
+#endif
+
+	outb((0x80), 0xc6f);
+
+#ifdef DEBUGXL
+	printk("Port 0xc6f (after) = %02x\n", inb(0xc6f));
+#endif
+
+	return 1;
+}
+
+int probe_ms_sound(struct address_info *hw_config, struct resource *ports)
+{
+	unsigned char   tmp;
+
+	DDB(printk("Entered probe_ms_sound(%x, %d)\n", hw_config->io_base, hw_config->card_subtype));
+
+	if (hw_config->card_subtype == 1)	/* Has no IRQ/DMA registers */
+	{
+		/* check_opl3(0x388, hw_config); */
+		return ad1848_detect(ports, NULL, hw_config->osp);
+	}
+
+	if (deskpro_xl && hw_config->card_subtype == 2)	/* Compaq Deskpro XL */
+	{
+		if (!init_deskpro(hw_config))
+			return 0;
+	}
+
+	if (deskpro_m)	/* Compaq Deskpro M */
+	{
+		if (!init_deskpro_m(hw_config))
+			return 0;
+	}
+
+	/*
+	   * Check if the IO port returns valid signature. The original MS Sound
+	   * system returns 0x04 while some cards (AudioTrix Pro for example)
+	   * return 0x00 or 0x0f.
+	 */
+
+	if ((tmp = inb(hw_config->io_base + 3)) == 0xff)	/* Bus float */
+	{
+		  int             ret;
+
+		  DDB(printk("I/O address is inactive (%x)\n", tmp));
+		  if (!(ret = ad1848_detect(ports, NULL, hw_config->osp)))
+			  return 0;
+		  return 1;
+	}
+	DDB(printk("MSS signature = %x\n", tmp & 0x3f));
+	if ((tmp & 0x3f) != 0x04 &&
+	    (tmp & 0x3f) != 0x0f &&
+	    (tmp & 0x3f) != 0x00)
+	{
+		int ret;
+
+		MDB(printk(KERN_ERR "No MSS signature detected on port 0x%x (0x%x)\n", hw_config->io_base, (int) inb(hw_config->io_base + 3)));
+		DDB(printk("Trying to detect codec anyway but IRQ/DMA may not work\n"));
+		if (!(ret = ad1848_detect(ports, NULL, hw_config->osp)))
+			return 0;
+
+		hw_config->card_subtype = 1;
+		return 1;
+	}
+	if ((hw_config->irq != 5)  &&
+	    (hw_config->irq != 7)  &&
+	    (hw_config->irq != 9)  &&
+	    (hw_config->irq != 10) &&
+	    (hw_config->irq != 11) &&
+	    (hw_config->irq != 12))
+	{
+		printk(KERN_ERR "MSS: Bad IRQ %d\n", hw_config->irq);
+		return 0;
+	}
+	if (hw_config->dma != 0 && hw_config->dma != 1 && hw_config->dma != 3)
+	{
+		  printk(KERN_ERR "MSS: Bad DMA %d\n", hw_config->dma);
+		  return 0;
+	}
+	/*
+	 * Check that DMA0 is not in use with a 8 bit board.
+	 */
+
+	if (hw_config->dma == 0 && inb(hw_config->io_base + 3) & 0x80)
+	{
+		printk(KERN_ERR "MSS: Can't use DMA0 with a 8 bit card/slot\n");
+		return 0;
+	}
+	if (hw_config->irq > 7 && hw_config->irq != 9 && inb(hw_config->io_base + 3) & 0x80)
+	{
+		printk(KERN_ERR "MSS: Can't use IRQ%d with a 8 bit card/slot\n", hw_config->irq);
+		return 0;
+	}
+	return ad1848_detect(ports, NULL, hw_config->osp);
+}
+
+void attach_ms_sound(struct address_info *hw_config, struct resource *ports, struct module *owner)
+{
+	static signed char interrupt_bits[12] =
+	{
+		-1, -1, -1, -1, -1, 0x00, -1, 0x08, -1, 0x10, 0x18, 0x20
+	};
+	signed char     bits;
+	char            dma2_bit = 0;
+
+	static char     dma_bits[4] =
+	{
+		1, 2, 0, 3
+	};
+
+	int config_port = hw_config->io_base + 0;
+	int version_port = hw_config->io_base + 3;
+	int dma = hw_config->dma;
+	int dma2 = hw_config->dma2;
+
+	if (hw_config->card_subtype == 1)	/* Has no IRQ/DMA registers */
+	{
+		hw_config->slots[0] = ad1848_init("MS Sound System", ports,
+						    hw_config->irq,
+						    hw_config->dma,
+						    hw_config->dma2, 0, 
+						    hw_config->osp,
+						    owner);
+		return;
+	}
+	/*
+	 * Set the IRQ and DMA addresses.
+	 */
+
+	bits = interrupt_bits[hw_config->irq];
+	if (bits == -1)
+	{
+		printk(KERN_ERR "MSS: Bad IRQ %d\n", hw_config->irq);
+		release_region(ports->start, 4);
+		release_region(ports->start - 4, 4);
+		return;
+	}
+	outb((bits | 0x40), config_port);
+	if ((inb(version_port) & 0x40) == 0)
+		printk(KERN_ERR "[MSS: IRQ Conflict?]\n");
+
+/*
+ * Handle the capture DMA channel
+ */
+
+	if (dma2 != -1 && dma2 != dma)
+	{
+		if (!((dma == 0 && dma2 == 1) ||
+			(dma == 1 && dma2 == 0) ||
+			(dma == 3 && dma2 == 0)))
+		{	/* Unsupported combination. Try to swap channels */
+			int tmp = dma;
+
+			dma = dma2;
+			dma2 = tmp;
+		}
+		if ((dma == 0 && dma2 == 1) ||
+			(dma == 1 && dma2 == 0) ||
+			(dma == 3 && dma2 == 0))
+		{
+			dma2_bit = 0x04;	/* Enable capture DMA */
+		}
+		else
+		{
+			printk(KERN_WARNING "MSS: Invalid capture DMA\n");
+			dma2 = dma;
+		}
+	}
+	else
+	{
+		dma2 = dma;
+	}
+
+	hw_config->dma = dma;
+	hw_config->dma2 = dma2;
+
+	outb((bits | dma_bits[dma] | dma2_bit), config_port);	/* Write IRQ+DMA setup */
+
+	hw_config->slots[0] = ad1848_init("MS Sound System", ports,
+					  hw_config->irq,
+					  dma, dma2, 0,
+					  hw_config->osp,
+					  THIS_MODULE);
+}
+
+void unload_ms_sound(struct address_info *hw_config)
+{
+	ad1848_unload(hw_config->io_base + 4,
+		      hw_config->irq,
+		      hw_config->dma,
+		      hw_config->dma2, 0);
+	sound_unload_audiodev(hw_config->slots[0]);
+	release_region(hw_config->io_base, 4);
+}
+
+#ifndef EXCLUDE_TIMERS
+
+/*
+ * Timer stuff (for /dev/music).
+ */
+
+static unsigned int current_interval;
+
+static unsigned int ad1848_tmr_start(int dev, unsigned int usecs)
+{
+	unsigned long   flags;
+	ad1848_info    *devc = (ad1848_info *) audio_devs[dev]->devc;
+	unsigned long   xtal_nsecs;	/* nanoseconds per xtal oscillator tick */
+	unsigned long   divider;
+
+	spin_lock_irqsave(&devc->lock,flags);
+
+	/*
+	 * Length of the timer interval (in nanoseconds) depends on the
+	 * selected crystal oscillator. Check this from bit 0x01 of I8.
+	 *
+	 * AD1845 has just one oscillator which has cycle time of 10.050 us
+	 * (when a 24.576 MHz xtal oscillator is used).
+	 *
+	 * Convert requested interval to nanoseconds before computing
+	 * the timer divider.
+	 */
+
+	if (devc->model == MD_1845 || devc->model == MD_1845_SSCAPE)
+		xtal_nsecs = 10050;
+	else if (ad_read(devc, 8) & 0x01)
+		xtal_nsecs = 9920;
+	else
+		xtal_nsecs = 9969;
+
+	divider = (usecs * 1000 + xtal_nsecs / 2) / xtal_nsecs;
+
+	if (divider < 100)	/* Don't allow shorter intervals than about 1ms */
+		divider = 100;
+
+	if (divider > 65535)	/* Overflow check */
+		divider = 65535;
+
+	ad_write(devc, 21, (divider >> 8) & 0xff);	/* Set upper bits */
+	ad_write(devc, 20, divider & 0xff);	/* Set lower bits */
+	ad_write(devc, 16, ad_read(devc, 16) | 0x40);	/* Start the timer */
+	devc->timer_running = 1;
+	spin_unlock_irqrestore(&devc->lock,flags);
+
+	return current_interval = (divider * xtal_nsecs + 500) / 1000;
+}
+
+static void ad1848_tmr_reprogram(int dev)
+{
+	/*
+	 *    Audio driver has changed sampling rate so that a different xtal
+	 *      oscillator was selected. We have to reprogram the timer rate.
+	 */
+
+	ad1848_tmr_start(dev, current_interval);
+	sound_timer_syncinterval(current_interval);
+}
+
+static void ad1848_tmr_disable(int dev)
+{
+	unsigned long   flags;
+	ad1848_info    *devc = (ad1848_info *) audio_devs[dev]->devc;
+
+	spin_lock_irqsave(&devc->lock,flags);
+	ad_write(devc, 16, ad_read(devc, 16) & ~0x40);
+	devc->timer_running = 0;
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void ad1848_tmr_restart(int dev)
+{
+	unsigned long   flags;
+	ad1848_info    *devc = (ad1848_info *) audio_devs[dev]->devc;
+
+	if (current_interval == 0)
+		return;
+
+	spin_lock_irqsave(&devc->lock,flags);
+	ad_write(devc, 16, ad_read(devc, 16) | 0x40);
+	devc->timer_running = 1;
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static struct sound_lowlev_timer ad1848_tmr =
+{
+	0,
+	2,
+	ad1848_tmr_start,
+	ad1848_tmr_disable,
+	ad1848_tmr_restart
+};
+
+static int ad1848_tmr_install(int dev)
+{
+	if (timer_installed != -1)
+		return 0;	/* Don't install another timer */
+
+	timer_installed = ad1848_tmr.dev = dev;
+	sound_timer_init(&ad1848_tmr, audio_devs[dev]->name);
+
+	return 1;
+}
+#endif /* EXCLUDE_TIMERS */
+
+EXPORT_SYMBOL(ad1848_detect);
+EXPORT_SYMBOL(ad1848_init);
+EXPORT_SYMBOL(ad1848_unload);
+EXPORT_SYMBOL(ad1848_control);
+EXPORT_SYMBOL(probe_ms_sound);
+EXPORT_SYMBOL(attach_ms_sound);
+EXPORT_SYMBOL(unload_ms_sound);
+
+static int __initdata io = -1;
+static int __initdata irq = -1;
+static int __initdata dma = -1;
+static int __initdata dma2 = -1;
+static int __initdata type = 0;
+
+module_param(io, int, 0);		/* I/O for a raw AD1848 card */
+module_param(irq, int, 0);		/* IRQ to use */
+module_param(dma, int, 0);		/* First DMA channel */
+module_param(dma2, int, 0);		/* Second DMA channel */
+module_param(type, int, 0);		/* Card type */
+module_param(deskpro_xl, bool, 0);	/* Special magic for Deskpro XL boxen */
+module_param(deskpro_m, bool, 0);	/* Special magic for Deskpro M box */
+module_param(soundpro, bool, 0);	/* More special magic for SoundPro chips */
+
+#ifdef CONFIG_PNP
+module_param(isapnp, int, 0);
+module_param(isapnpjump, int, 0);
+module_param(reverse, bool, 0);
+MODULE_PARM_DESC(isapnp,	"When set to 0, Plug & Play support will be disabled");
+MODULE_PARM_DESC(isapnpjump,	"Jumps to a specific slot in the driver's PnP table. Use the source, Luke.");
+MODULE_PARM_DESC(reverse,	"When set to 1, will reverse ISAPnP search order");
+
+static struct pnp_dev	*ad1848_dev  = NULL;
+
+/* Please add new entries at the end of the table */
+static struct {
+	char *name;
+	unsigned short	card_vendor, card_device,
+			vendor, function;
+	short mss_io, irq, dma, dma2;   /* index into isapnp table */
+        int type;
+} ad1848_isapnp_list[] __initdata = {
+	{"CMI 8330 SoundPRO",
+		ISAPNP_VENDOR('C','M','I'), ISAPNP_DEVICE(0x0001),
+		ISAPNP_VENDOR('@','@','@'), ISAPNP_FUNCTION(0x0001),
+		0, 0, 0,-1, 0},
+        {"CS4232 based card",
+                ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+		ISAPNP_VENDOR('C','S','C'), ISAPNP_FUNCTION(0x0000),
+		0, 0, 0, 1, 0},
+        {"CS4232 based card",
+                ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+		ISAPNP_VENDOR('C','S','C'), ISAPNP_FUNCTION(0x0100),
+		0, 0, 0, 1, 0},
+        {"OPL3-SA2 WSS mode",
+        	ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+		ISAPNP_VENDOR('Y','M','H'), ISAPNP_FUNCTION(0x0021),
+                1, 0, 0, 1, 1},
+	{"Advanced Gravis InterWave Audio",
+		ISAPNP_VENDOR('G','R','V'), ISAPNP_DEVICE(0x0001),
+		ISAPNP_VENDOR('G','R','V'), ISAPNP_FUNCTION(0x0000),
+		0, 0, 0, 1, 0},
+	{NULL}
+};
+
+static struct isapnp_device_id id_table[] __devinitdata = {
+	{	ISAPNP_VENDOR('C','M','I'), ISAPNP_DEVICE(0x0001),
+		ISAPNP_VENDOR('@','@','@'), ISAPNP_FUNCTION(0x0001), 0 },
+        {       ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+		ISAPNP_VENDOR('C','S','C'), ISAPNP_FUNCTION(0x0000), 0 },
+        {       ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+		ISAPNP_VENDOR('C','S','C'), ISAPNP_FUNCTION(0x0100), 0 },
+	/* The main driver for this card is opl3sa2
+        {       ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+		ISAPNP_VENDOR('Y','M','H'), ISAPNP_FUNCTION(0x0021), 0 },
+	*/
+	{	ISAPNP_VENDOR('G','R','V'), ISAPNP_DEVICE(0x0001),
+		ISAPNP_VENDOR('G','R','V'), ISAPNP_FUNCTION(0x0000), 0 },
+	{0}
+};
+
+MODULE_DEVICE_TABLE(isapnp, id_table);
+
+static struct pnp_dev *activate_dev(char *devname, char *resname, struct pnp_dev *dev)
+{
+	int err;
+
+	err = pnp_device_attach(dev);
+	if (err < 0)
+		return(NULL);
+
+	if((err = pnp_activate_dev(dev)) < 0) {
+		printk(KERN_ERR "ad1848: %s %s config failed (out of resources?)[%d]\n", devname, resname, err);
+
+		pnp_device_detach(dev);
+
+		return(NULL);
+	}
+	audio_activated = 1;
+	return(dev);
+}
+
+static struct pnp_dev __init *ad1848_init_generic(struct pnp_card *bus,
+				struct address_info *hw_config, int slot)
+{
+
+	/* Configure Audio device */
+	if((ad1848_dev = pnp_find_dev(bus, ad1848_isapnp_list[slot].vendor, ad1848_isapnp_list[slot].function, NULL)))
+	{
+		if((ad1848_dev = activate_dev(ad1848_isapnp_list[slot].name, "ad1848", ad1848_dev)))
+		{
+			hw_config->io_base 	= pnp_port_start(ad1848_dev, ad1848_isapnp_list[slot].mss_io);
+			hw_config->irq 		= pnp_irq(ad1848_dev, ad1848_isapnp_list[slot].irq);
+			hw_config->dma 		= pnp_dma(ad1848_dev, ad1848_isapnp_list[slot].dma);
+			if(ad1848_isapnp_list[slot].dma2 != -1)
+				hw_config->dma2 = pnp_dma(ad1848_dev, ad1848_isapnp_list[slot].dma2);
+			else
+				hw_config->dma2 = -1;
+                        hw_config->card_subtype = ad1848_isapnp_list[slot].type;
+		} else
+			return(NULL);
+	} else
+		return(NULL);
+
+	return(ad1848_dev);
+}
+
+static int __init ad1848_isapnp_init(struct address_info *hw_config, struct pnp_card *bus, int slot)
+{
+	char *busname = bus->name[0] ? bus->name : ad1848_isapnp_list[slot].name;
+
+	/* Initialize this baby. */
+
+	if(ad1848_init_generic(bus, hw_config, slot)) {
+		/* We got it. */
+
+		printk(KERN_NOTICE "ad1848: PnP reports '%s' at i/o %#x, irq %d, dma %d, %d\n",
+		       busname,
+		       hw_config->io_base, hw_config->irq, hw_config->dma,
+		       hw_config->dma2);
+		return 1;
+	}
+	return 0;
+}
+
+static int __init ad1848_isapnp_probe(struct address_info *hw_config)
+{
+	static int first = 1;
+	int i;
+
+	/* Count entries in sb_isapnp_list */
+	for (i = 0; ad1848_isapnp_list[i].card_vendor != 0; i++);
+	i--;
+
+	/* Check and adjust isapnpjump */
+	if( isapnpjump < 0 || isapnpjump > i) {
+		isapnpjump = reverse ? i : 0;
+		printk(KERN_ERR "ad1848: Valid range for isapnpjump is 0-%d. Adjusted to %d.\n", i, isapnpjump);
+	}
+
+	if(!first || !reverse)
+		i = isapnpjump;
+	first = 0;
+	while(ad1848_isapnp_list[i].card_vendor != 0) {
+		static struct pnp_card *bus = NULL;
+
+		while ((bus = pnp_find_card(
+				ad1848_isapnp_list[i].card_vendor,
+				ad1848_isapnp_list[i].card_device,
+				bus))) {
+
+			if(ad1848_isapnp_init(hw_config, bus, i)) {
+				isapnpjump = i; /* start next search from here */
+				return 0;
+			}
+		}
+		i += reverse ? -1 : 1;
+	}
+
+	return -ENODEV;
+}
+#endif
+
+
+static int __init init_ad1848(void)
+{
+	printk(KERN_INFO "ad1848/cs4248 codec driver Copyright (C) by Hannu Savolainen 1993-1996\n");
+
+#ifdef CONFIG_PNP
+	if(isapnp && (ad1848_isapnp_probe(&cfg) < 0) ) {
+		printk(KERN_NOTICE "ad1848: No ISAPnP cards found, trying standard ones...\n");
+		isapnp = 0;
+	}
+#endif
+
+	if(io != -1) {
+		struct resource *ports;
+	        if( isapnp == 0 )
+	        {
+			if(irq == -1 || dma == -1) {
+				printk(KERN_WARNING "ad1848: must give I/O , IRQ and DMA.\n");
+				return -EINVAL;
+			}
+
+			cfg.irq = irq;
+			cfg.io_base = io;
+			cfg.dma = dma;
+			cfg.dma2 = dma2;
+			cfg.card_subtype = type;
+	        }
+
+		ports = request_region(io + 4, 4, "ad1848");
+
+		if (!ports)
+			return -EBUSY;
+
+		if (!request_region(io, 4, "WSS config")) {
+			release_region(io + 4, 4);
+			return -EBUSY;
+		}
+
+		if (!probe_ms_sound(&cfg, ports)) {
+			release_region(io + 4, 4);
+			release_region(io, 4);
+			return -ENODEV;
+		}
+		attach_ms_sound(&cfg, ports, THIS_MODULE);
+		loaded = 1;
+	}
+	return 0;
+}
+
+static void __exit cleanup_ad1848(void)
+{
+	if(loaded)
+		unload_ms_sound(&cfg);
+
+#ifdef CONFIG_PNP
+	if(ad1848_dev){
+		if(audio_activated)
+			pnp_device_detach(ad1848_dev);
+	}
+#endif
+}
+
+module_init(init_ad1848);
+module_exit(cleanup_ad1848);
+
+#ifndef MODULE
+static int __init setup_ad1848(char *str)
+{
+        /* io, irq, dma, dma2, type */
+	int ints[6];
+	
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+
+	io	= ints[1];
+	irq	= ints[2];
+	dma	= ints[3];
+	dma2	= ints[4];
+	type	= ints[5];
+
+	return 1;
+}
+
+__setup("ad1848=", setup_ad1848);	
+#endif
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/ad1848.h b/linux/sound/oss/ad1848.h
new file mode 100644
index 0000000..b95ebe2
--- /dev/null
+++ b/linux/sound/oss/ad1848.h
@@ -0,0 +1,24 @@
+
+#include <linux/interrupt.h>
+
+#define AD_F_CS4231     0x0001  /* Returned if a CS4232 (or compatible) detected */
+#define AD_F_CS4248     0x0001  /* Returned if a CS4248 (or compatible) detected */
+
+#define         AD1848_SET_XTAL         1
+#define         AD1848_MIXER_REROUTE    2
+
+#define AD1848_REROUTE(oldctl, newctl) \
+                ad1848_control(AD1848_MIXER_REROUTE, ((oldctl)<<8)|(newctl))
+		
+
+int ad1848_init(char *name, struct resource *ports, int irq, int dma_playback,
+	int dma_capture, int share_dma, int *osp, struct module *owner);
+void ad1848_unload (int io_base, int irq, int dma_playback, int dma_capture, int share_dma);
+
+int ad1848_detect (struct resource *ports, int *flags, int *osp);
+int ad1848_control(int cmd, int arg);
+
+void attach_ms_sound(struct address_info * hw_config, struct resource *ports, struct module * owner);
+
+int probe_ms_sound(struct address_info *hw_config, struct resource *ports);
+void unload_ms_sound(struct address_info *hw_info);
diff --git a/linux/sound/oss/ad1848_mixer.h b/linux/sound/oss/ad1848_mixer.h
new file mode 100644
index 0000000..2cf719b
--- /dev/null
+++ b/linux/sound/oss/ad1848_mixer.h
@@ -0,0 +1,253 @@
+/*
+ * sound/oss/ad1848_mixer.h
+ *
+ * Definitions for the mixer of AD1848 and compatible codecs.
+ */
+
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+
+/*
+ * The AD1848 codec has generic input lines called Line, Aux1 and Aux2.
+ * Sound card manufacturers have connected actual inputs (CD, synth, line,
+ * etc) to these inputs in different order. Therefore it's difficult
+ * to assign mixer channels to these inputs correctly. The following
+ * contains two alternative mappings. The first one is for GUS MAX and
+ * the second is just a generic one (line1, line2 and line3).
+ * (Actually this is not a mapping but rather some kind of interleaving
+ * solution).
+ */
+#define MODE1_REC_DEVICES		(SOUND_MASK_LINE3 | SOUND_MASK_MIC | \
+					 SOUND_MASK_LINE1 | SOUND_MASK_IMIX)
+
+#define SPRO_REC_DEVICES		(SOUND_MASK_LINE | SOUND_MASK_MIC | \
+					 SOUND_MASK_CD | SOUND_MASK_LINE1)
+
+#define MODE1_MIXER_DEVICES		(SOUND_MASK_LINE1 | SOUND_MASK_MIC | \
+					 SOUND_MASK_LINE2 | \
+					 SOUND_MASK_IGAIN | \
+					 SOUND_MASK_PCM | SOUND_MASK_IMIX)
+
+#define MODE2_MIXER_DEVICES		(SOUND_MASK_LINE1 | SOUND_MASK_LINE2 | \
+					 SOUND_MASK_MIC | \
+					 SOUND_MASK_LINE3 | SOUND_MASK_SPEAKER | \
+					 SOUND_MASK_IGAIN | \
+					 SOUND_MASK_PCM | SOUND_MASK_IMIX)
+
+#define MODE3_MIXER_DEVICES		(MODE2_MIXER_DEVICES | SOUND_MASK_VOLUME)
+
+/* OPTi 82C930 has no IMIX level control, but it can still be selected as an
+ * input
+ */
+#define C930_MIXER_DEVICES	(SOUND_MASK_LINE1 | SOUND_MASK_LINE2 | \
+				 SOUND_MASK_MIC | SOUND_MASK_VOLUME | \
+				 SOUND_MASK_LINE3 | \
+				 SOUND_MASK_IGAIN | SOUND_MASK_PCM)
+
+#define SPRO_MIXER_DEVICES	(SOUND_MASK_VOLUME | SOUND_MASK_PCM | \
+				 SOUND_MASK_LINE | SOUND_MASK_SYNTH | \
+				 SOUND_MASK_CD | SOUND_MASK_MIC | \
+				 SOUND_MASK_SPEAKER | SOUND_MASK_LINE1 | \
+				 SOUND_MASK_OGAIN)
+
+struct mixer_def {
+	unsigned int regno:6;		/* register number for volume */
+	unsigned int polarity:1;	/* volume polarity: 0=normal, 1=reversed */
+	unsigned int bitpos:3;		/* position of bits in register for volume */
+	unsigned int nbits:3;		/* number of bits in register for volume */
+	unsigned int mutereg:6;		/* register number for mute bit */
+	unsigned int mutepol:1;		/* mute polarity: 0=normal, 1=reversed */
+	unsigned int mutepos:4;		/* position of mute bit in register */
+	unsigned int recreg:6;		/* register number for recording bit */
+	unsigned int recpol:1;		/* recording polarity: 0=normal, 1=reversed */
+	unsigned int recpos:4;		/* position of recording bit in register */
+};
+
+static char mix_cvt[101] = {
+	 0, 0, 3, 7,10,13,16,19,21,23,26,28,30,32,34,35,37,39,40,42,
+	43,45,46,47,49,50,51,52,53,55,56,57,58,59,60,61,62,63,64,65,
+	65,66,67,68,69,70,70,71,72,73,73,74,75,75,76,77,77,78,79,79,
+	80,81,81,82,82,83,84,84,85,85,86,86,87,87,88,88,89,89,90,90,
+	91,91,92,92,93,93,94,94,95,95,96,96,96,97,97,98,98,98,99,99,
+	100
+};
+
+typedef struct mixer_def mixer_ent;
+typedef mixer_ent mixer_ents[2];
+
+/*
+ * Most of the mixer entries work in backwards. Setting the polarity field
+ * makes them to work correctly.
+ *
+ * The channel numbering used by individual sound cards is not fixed. Some
+ * cards have assigned different meanings for the AUX1, AUX2 and LINE inputs.
+ * The current version doesn't try to compensate this.
+ */
+
+#define MIX_ENT(name, reg_l, pola_l, pos_l, len_l, reg_r, pola_r, pos_r, len_r, mute_bit)	\
+	[name] = {{reg_l, pola_l, pos_l, len_l, reg_l, 0, mute_bit, 0, 0, 8},			\
+		  {reg_r, pola_r, pos_r, len_r, reg_r, 0, mute_bit, 0, 0, 8}}
+
+#define MIX_ENT2(name, reg_l, pola_l, pos_l, len_l, mute_reg_l, mute_pola_l, mute_pos_l, \
+		    rec_reg_l, rec_pola_l, rec_pos_l,					 \
+		 reg_r, pola_r, pos_r, len_r, mute_reg_r, mute_pola_r, mute_pos_r,	 \
+		    rec_reg_r, rec_pola_r, rec_pos_r)					 \
+	[name] = {{reg_l, pola_l, pos_l, len_l, mute_reg_l, mute_pola_l, mute_pos_l,	 \
+		    rec_reg_l, rec_pola_l, rec_pos_l},					 \
+		  {reg_r, pola_r, pos_r, len_r, mute_reg_r, mute_pola_r, mute_pos_r,	 \
+		    rec_reg_r, rec_pola_r, rec_pos_r}}
+
+static mixer_ents ad1848_mix_devices[32] = {
+	MIX_ENT(SOUND_MIXER_VOLUME,	27, 1, 0, 4,	29, 1, 0, 4,  8),
+	MIX_ENT(SOUND_MIXER_BASS,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_TREBLE,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_SYNTH,	 4, 1, 0, 5,	 5, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_PCM,	 6, 1, 0, 6,	 7, 1, 0, 6,  7),
+	MIX_ENT(SOUND_MIXER_SPEAKER,	26, 1, 0, 4,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_LINE,	18, 1, 0, 5,	19, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_MIC,	 0, 0, 5, 1,	 1, 0, 5, 1,  8),
+	MIX_ENT(SOUND_MIXER_CD,		 2, 1, 0, 5,	 3, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_IMIX,	13, 1, 2, 6,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_ALTPCM,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_RECLEV,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_IGAIN,	 0, 0, 0, 4,	 1, 0, 0, 4,  8),
+	MIX_ENT(SOUND_MIXER_OGAIN,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_LINE1,	 2, 1, 0, 5,	 3, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_LINE2,	 4, 1, 0, 5,	 5, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_LINE3,	18, 1, 0, 5,	19, 1, 0, 5,  7)
+};
+
+static mixer_ents iwave_mix_devices[32] = {
+	MIX_ENT(SOUND_MIXER_VOLUME,	25, 1, 0, 5,	27, 1, 0, 5,  8),
+	MIX_ENT(SOUND_MIXER_BASS,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_TREBLE,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_SYNTH,	 4, 1, 0, 5,	 5, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_PCM,	 6, 1, 0, 6,	 7, 1, 0, 6,  7),
+	MIX_ENT(SOUND_MIXER_SPEAKER,	26, 1, 0, 4,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_LINE,	18, 1, 0, 5,	19, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_MIC,	 0, 0, 5, 1,	 1, 0, 5, 1,  8),
+	MIX_ENT(SOUND_MIXER_CD,		 2, 1, 0, 5,	 3, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_IMIX,	16, 1, 0, 5,	17, 1, 0, 5,  8),
+	MIX_ENT(SOUND_MIXER_ALTPCM,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_RECLEV,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_IGAIN,	 0, 0, 0, 4,	 1, 0, 0, 4,  8),
+	MIX_ENT(SOUND_MIXER_OGAIN,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_LINE1,	 2, 1, 0, 5,	 3, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_LINE2,	 4, 1, 0, 5,	 5, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_LINE3,	18, 1, 0, 5,	19, 1, 0, 5,  7)
+};
+
+static mixer_ents cs42xb_mix_devices[32] = {
+	/* Digital master volume actually has seven bits, but we only use
+	   six to avoid the discontinuity when the analog gain kicks in. */
+	MIX_ENT(SOUND_MIXER_VOLUME,	46, 1, 0, 6,	47, 1, 0, 6,  7),
+	MIX_ENT(SOUND_MIXER_BASS,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_TREBLE,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_SYNTH,	 4, 1, 0, 5,	 5, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_PCM,	 6, 1, 0, 6,	 7, 1, 0, 6,  7),
+	MIX_ENT(SOUND_MIXER_SPEAKER,	26, 1, 0, 4,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_LINE,	18, 1, 0, 5,	19, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_MIC,	34, 1, 0, 5,	35, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_CD,		 2, 1, 0, 5,	 3, 1, 0, 5,  7),
+	/* For the IMIX entry, it was not possible to use the MIX_ENT macro
+	   because the mute bit is in different positions for the two
+	   channels and requires reverse polarity. */
+	[SOUND_MIXER_IMIX] = {{13, 1, 2, 6, 13, 1, 0, 0, 0, 8},
+		      {42, 1, 0, 6, 42, 1, 7, 0, 0, 8}},
+	MIX_ENT(SOUND_MIXER_ALTPCM,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_RECLEV,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_IGAIN,	 0, 0, 0, 4,	 1, 0, 0, 4,  8),
+	MIX_ENT(SOUND_MIXER_OGAIN,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_LINE1,	 2, 1, 0, 5,	 3, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_LINE2,	 4, 1, 0, 5,	 5, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_LINE3,	38, 1, 0, 6,	39, 1, 0, 6,  7)
+};
+
+/* OPTi 82C930 has somewhat different port addresses.
+ * Note: VOLUME == SPEAKER, SYNTH == LINE2, LINE == LINE3, CD == LINE1
+ * VOLUME, SYNTH, LINE, CD are not enabled above.
+ * MIC is level of mic monitoring direct to output. Same for CD, LINE, etc.
+ */
+static mixer_ents c930_mix_devices[32] = {
+	MIX_ENT(SOUND_MIXER_VOLUME,	22, 1, 1, 5,	23, 1, 1, 5,  7),
+	MIX_ENT(SOUND_MIXER_BASS,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_TREBLE,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_SYNTH,	 4, 1, 1, 4,	 5, 1, 1, 4,  7),
+	MIX_ENT(SOUND_MIXER_PCM,	 6, 1, 0, 5,	 7, 1, 0, 5,  7),
+	MIX_ENT(SOUND_MIXER_SPEAKER,	22, 1, 1, 5,	23, 1, 1, 5,  7),
+	MIX_ENT(SOUND_MIXER_LINE,	18, 1, 1, 4,	19, 1, 1, 4,  7),
+	MIX_ENT(SOUND_MIXER_MIC,	20, 1, 1, 4,	21, 1, 1, 4,  7),
+	MIX_ENT(SOUND_MIXER_CD,		 2, 1, 1, 4,	 3, 1, 1, 4,  7),
+	MIX_ENT(SOUND_MIXER_IMIX,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_ALTPCM,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_RECLEV,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_IGAIN,	 0, 0, 0, 4,	 1, 0, 0, 4,  8),
+	MIX_ENT(SOUND_MIXER_OGAIN,	 0, 0, 0, 0,	 0, 0, 0, 0,  8),
+	MIX_ENT(SOUND_MIXER_LINE1,	 2, 1, 1, 4,	 3, 1, 1, 4,  7),
+	MIX_ENT(SOUND_MIXER_LINE2,	 4, 1, 1, 4,	 5, 1, 1, 4,  7),
+	MIX_ENT(SOUND_MIXER_LINE3,	18, 1, 1, 4,	19, 1, 1, 4,  7)
+};
+
+static mixer_ents spro_mix_devices[32] = {
+	MIX_ENT (SOUND_MIXER_VOLUME,	19, 0, 4, 4,			 19, 0, 0, 4,  8),
+	MIX_ENT (SOUND_MIXER_BASS,	 0, 0, 0, 0,			  0, 0, 0, 0,  8),
+	MIX_ENT (SOUND_MIXER_TREBLE,	 0, 0, 0, 0,			  0, 0, 0, 0,  8),
+	MIX_ENT2(SOUND_MIXER_SYNTH,	 4, 1, 1, 4, 23, 0, 3,  0, 0, 8,
+		 			 5, 1, 1, 4, 23, 0, 3, 0, 0, 8),
+	MIX_ENT (SOUND_MIXER_PCM,	 6, 1, 1, 4,			  7, 1, 1, 4,  8),
+	MIX_ENT (SOUND_MIXER_SPEAKER,	18, 0, 3, 2,			  0, 0, 0, 0,  8),
+	MIX_ENT2(SOUND_MIXER_LINE,	20, 0, 4, 4, 17, 1, 4, 16, 0, 2,
+		 			20, 0, 0, 4, 17, 1, 3, 16, 0, 1),
+	MIX_ENT2(SOUND_MIXER_MIC,	18, 0, 0, 3, 17, 1, 0, 16, 0, 0,
+		 			 0, 0, 0, 0,  0, 0, 0,  0, 0, 0),
+	MIX_ENT2(SOUND_MIXER_CD,	21, 0, 4, 4, 17, 1, 2, 16, 0, 4,
+					21, 0, 0, 4, 17, 1, 1, 16, 0, 3),
+	MIX_ENT (SOUND_MIXER_IMIX,	 0, 0, 0, 0,			  0, 0, 0, 0,  8),
+	MIX_ENT (SOUND_MIXER_ALTPCM,	 0, 0, 0, 0,			  0, 0, 0, 0,  8),
+	MIX_ENT (SOUND_MIXER_RECLEV,	 0, 0, 0, 0,			  0, 0, 0, 0,  8),
+	MIX_ENT (SOUND_MIXER_IGAIN,	 0, 0, 0, 0,			  0, 0, 0, 0,  8),
+	MIX_ENT (SOUND_MIXER_OGAIN,	17, 1, 6, 1,			  0, 0, 0, 0,  8),
+	/* This is external wavetable */
+	MIX_ENT2(SOUND_MIXER_LINE1,	22, 0, 4, 4, 23, 1, 1, 23, 0, 4,
+		 			22, 0, 0, 4, 23, 1, 0, 23, 0, 5),
+};
+
+static int default_mixer_levels[32] =
+{
+	0x3232,			/* Master Volume */
+	0x3232,			/* Bass */
+	0x3232,			/* Treble */
+	0x4b4b,			/* FM */
+	0x3232,			/* PCM */
+	0x1515,			/* PC Speaker */
+	0x2020,			/* Ext Line */
+	0x1010,			/* Mic */
+	0x4b4b,			/* CD */
+	0x0000,			/* Recording monitor */
+	0x4b4b,			/* Second PCM */
+	0x4b4b,			/* Recording level */
+	0x4b4b,			/* Input gain */
+	0x4b4b,			/* Output gain */
+	0x2020,			/* Line1 */
+	0x2020,			/* Line2 */
+	0x1515			/* Line3 (usually line in)*/
+};
+
+#define LEFT_CHN	0
+#define RIGHT_CHN	1
+
+/*
+ * Channel enable bits for ioctl(SOUND_MIXER_PRIVATE1)
+ */
+
+#ifndef AUDIO_SPEAKER
+#define	AUDIO_SPEAKER		0x01	/* Enable mono output */
+#define	AUDIO_HEADPHONE		0x02	/* Sparc only */
+#define	AUDIO_LINE_OUT		0x04	/* Sparc only */
+#endif
diff --git a/linux/sound/oss/aedsp16.c b/linux/sound/oss/aedsp16.c
new file mode 100644
index 0000000..35b5912
--- /dev/null
+++ b/linux/sound/oss/aedsp16.c
@@ -0,0 +1,1373 @@
+/*
+   sound/oss/aedsp16.c
+
+   Audio Excel DSP 16 software configuration routines
+   Copyright (C) 1995,1996,1997,1998  Riccardo Facchetti (fizban@tin.it)
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+/*
+ * Include the main OSS Lite header file. It include all the os, OSS Lite, etc
+ * headers needed by this source.
+ */
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include "sound_config.h"
+
+/*
+
+   READ THIS
+
+   This module started to configure the Audio Excel DSP 16 Sound Card.
+   Now works with the SC-6000 (old aedsp16) and new SC-6600 based cards.
+
+   NOTE: I have NO idea about Audio Excel DSP 16 III. If someone owns this
+   audio card and want to see the kernel support for it, please contact me.
+
+   Audio Excel DSP 16 is an SB pro II, Microsoft Sound System and MPU-401
+   compatible card.
+   It is software-only configurable (no jumpers to hard-set irq/dma/mpu-irq),
+   so before this module, the only way to configure the DSP under linux was
+   boot the MS-DOS loading the sound.sys device driver (this driver soft-
+   configure the sound board hardware by massaging someone of its registers),
+   and then ctrl-alt-del to boot linux with the DSP configured by the DOS
+   driver.
+
+   This module works configuring your Audio Excel DSP 16's irq, dma and
+   mpu-401-irq. The OSS Lite routines rely on the fact that if the
+   hardware is there, they can detect it. The problem with AEDSP16 is
+   that no hardware can be found by the probe routines if the sound card
+   is not configured properly. Sometimes the kernel probe routines can find
+   an SBPRO even when the card is not configured (this is the standard setup
+   of the card), but the SBPRO emulation don't work well if the card is not
+   properly initialized. For this reason
+
+   aedsp16_init_board()
+
+   routine is called before the OSS Lite probe routines try to detect the
+   hardware.
+
+   NOTE (READ THE NOTE TOO, IT CONTAIN USEFUL INFORMATIONS)
+
+   NOTE: Now it works with SC-6000 and SC-6600 based audio cards. The new cards
+   have no jumper switch at all. No more WSS or MPU-401 I/O port switches. They
+   have to be configured by software.
+
+   NOTE: The driver is merged with the new OSS Lite sound driver. It works
+   as a lowlevel driver.
+
+   The Audio Excel DSP 16 Sound Card emulates both SBPRO and MSS;
+   the OSS Lite sound driver can be configured for SBPRO and MSS cards
+   at the same time, but the aedsp16 can't be two cards!!
+   When we configure it, we have to choose the SBPRO or the MSS emulation
+   for AEDSP16. We also can install a *REAL* card of the other type (see [1]).
+
+   NOTE: If someone can test the combination AEDSP16+MSS or AEDSP16+SBPRO
+   please let me know if it works.
+
+   The MPU-401 support can be compiled in together with one of the other
+   two operating modes.
+
+   NOTE: This is something like plug-and-play: we have only to plug
+   the AEDSP16 board in the socket, and then configure and compile
+   a kernel that uses the AEDSP16 software configuration capability.
+   No jumper setting is needed!
+
+   For example, if you want AEDSP16 to be an SBPro, on irq 10, dma 3
+   you have just to make config the OSS Lite package, configuring
+   the AEDSP16 sound card, then activating the SBPro emulation mode
+   and at last configuring IRQ and DMA.
+   Compile the kernel and run it.
+
+   NOTE: This means for SC-6000 cards that you can choose irq and dma,
+   but not the I/O addresses. To change I/O addresses you have to set
+   them with jumpers. For SC-6600 cards you have no jumpers so you have
+   to set up your full card configuration in the make config.
+
+   You can change the irq/dma/mirq settings WITHOUT THE NEED to open
+   your computer and massage the jumpers (there are no irq/dma/mirq
+   jumpers to be configured anyway, only I/O BASE values have to be
+   configured with jumpers)
+
+   For some ununderstandable reason, the card default of irq 7, dma 1,
+   don't work for me. Seems to be an IRQ or DMA conflict. Under heavy
+   HDD work, the kernel start to erupt out a lot of messages like:
+
+   'Sound: DMA timed out - IRQ/DRQ config error?'
+
+   For what I can say, I have NOT any conflict at irq 7 (under linux I'm
+   using the lp polling driver), and dma line 1 is unused as stated by
+   /proc/dma. I can suppose this is a bug of AEDSP16. I know my hardware so
+   I'm pretty sure I have not any conflict, but may be I'm wrong. Who knows!
+   Anyway a setting of irq 10, dma 3 works really fine.
+
+   NOTE: if someone can use AEDSP16 with irq 7, dma 1, please let me know
+   the emulation mode, all the installed hardware and the hardware
+   configuration (irq and dma settings of all the hardware).
+
+   This init module should work with SBPRO+MSS, when one of the two is
+   the AEDSP16 emulation and the other the real card. (see [1])
+   For example:
+
+   AEDSP16 (0x220) in SBPRO emu (0x220) + real MSS + other
+   AEDSP16 (0x220) in MSS emu + real SBPRO (0x240) + other
+
+   MPU401 should work. (see [2])
+
+   [1]
+       ---
+       Date: Mon, 29 Jul 1997 08:35:40 +0100
+       From: Mr S J Greenaway <sjg95@unixfe.rl.ac.uk>
+
+       [...]
+       Just to let you know got my Audio Excel (emulating a MSS) working
+       with my original SB16, thanks for the driver!
+       [...]
+       ---
+
+   [2] Not tested by me for lack of hardware.
+
+   TODO, WISHES AND TECH
+
+   - About I/O ports allocation -
+
+   Request the 2x0h region (port base) in any case if we are using this card.
+
+   NOTE: the "aedsp16 (base)" string with which we are requesting the aedsp16
+   port base region (see code) does not mean necessarily that we are emulating
+   sbpro.  Even if this region is the sbpro I/O ports region, we use this
+   region to access the control registers of the card, and if emulating
+   sbpro, I/O sbpro registers too. If we are emulating MSS, the sbpro
+   registers are not used, in no way, to emulate an sbpro: they are
+   used only for configuration purposes.
+
+   Started Fri Mar 17 16:13:18 MET 1995
+
+   v0.1 (ALPHA, was a user-level program called AudioExcelDSP16.c)
+   - Initial code.
+   v0.2 (ALPHA)
+   - Cleanups.
+   - Integrated with Linux voxware v 2.90-2 kernel sound driver.
+   - SoundBlaster Pro mode configuration.
+   - Microsoft Sound System mode configuration.
+   - MPU-401 mode configuration.
+   v0.3 (ALPHA)
+   - Cleanups.
+   - Rearranged the code to let aedsp16_init_board be more general.
+   - Erased the REALLY_SLOW_IO. We don't need it. Erased the linux/io.h
+   inclusion too. We rely on os.h
+   - Used the  to get a variable
+   len string (we are not sure about the len of Copyright string).
+   This works with any SB and compatible.
+   - Added the code to request_region at device init (should go in
+   the main body of voxware).
+   v0.4 (BETA)
+   - Better configure.c patch for aedsp16 configuration (better
+   logic of inclusion of AEDSP16 support)
+   - Modified the conditional compilation to better support more than
+   one sound card of the emulated type (read the NOTES above)
+   - Moved the sb init routine from the attach to the very first
+   probe in sb_card.c
+   - Rearrangements and cleanups
+   - Wiped out some unnecessary code and variables: this is kernel
+   code so it is better save some TEXT and DATA
+   - Fixed the request_region code. We must allocate the aedsp16 (sbpro)
+   I/O ports in any case because they are used to access the DSP
+   configuration registers and we can not allow anyone to get them.
+   v0.5
+   - cleanups on comments
+   - prep for diffs against v3.0-proto-950402
+   v0.6
+   - removed the request_region()s when compiling the MODULE sound.o
+   because we are not allowed (by the actual voxware structure) to
+   release_region()
+   v0.7 (pre ALPHA, not distributed)
+   - started porting this module to kernel 1.3.84. Dummy probe/attach
+   routines.
+   v0.8 (ALPHA)
+   - attached all the init routines.
+   v0.9 (BETA)
+   - Integrated with linux-pre2.0.7
+   - Integrated with configuration scripts.
+   - Cleaned up and beautyfied the code.
+   v0.9.9 (BETA)
+   - Thanks to Piercarlo Grandi: corrected the conditonal compilation code.
+     Now only the code configured is compiled in, with some memory saving.
+   v0.9.10
+   - Integration into the sound/lowlevel/ section of the sound driver.
+   - Re-organized the code.
+   v0.9.11 (not distributed)
+   - Rewritten the init interface-routines to initialize the AEDSP16 in
+     one shot.
+   - More cosmetics.
+   - SC-6600 support.
+   - More soft/hard configuration.
+   v0.9.12
+   - Refined the v0.9.11 code with conditional compilation to distinguish
+     between SC-6000 and SC-6600 code.
+   v1.0.0
+   - Prep for merging with OSS Lite and Linux kernel 2.1.13
+   - Corrected a bug in request/check/release region calls (thanks to the
+     new kernel exception handling).
+   v1.1
+   - Revamped for integration with new modularized sound drivers: to enhance
+     the flexibility of modular version, I have removed all the conditional
+     compilation for SBPRO, MPU and MSS code. Now it is all managed with
+     the ae_config structure.
+   v1.2
+   - Module informations added.
+   - Removed aedsp16_delay_10msec(), now using mdelay(10)
+   - All data and funcs moved to .*.init section.
+   v1.3
+   Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 2000/09/27
+   - got rid of check_region
+
+   Known Problems:
+   - Audio Excel DSP 16 III don't work with this driver.
+
+   Credits:
+   Many thanks to Gerald Britton <gbritton@CapAccess.org>. He helped me a
+   lot in testing the 0.9.11 and 0.9.12 versions of this driver.
+
+ */
+
+
+#define VERSION "1.3"		/* Version of Audio Excel DSP 16 driver */
+
+#undef	AEDSP16_DEBUG 		/* Define this to 1 to enable debug code     */
+#undef	AEDSP16_DEBUG_MORE 	/* Define this to 1 to enable more debug     */
+#undef	AEDSP16_INFO 		/* Define this to 1 to enable info code      */
+
+#if defined(AEDSP16_DEBUG)
+# define DBG(x)  printk x
+# if defined(AEDSP16_DEBUG_MORE)
+#  define DBG1(x) printk x
+# else
+#  define DBG1(x)
+# endif
+#else
+# define DBG(x)
+# define DBG1(x)
+#endif
+
+/*
+ * Misc definitions
+ */
+#define TRUE	1
+#define FALSE	0
+
+/*
+ * Region Size for request/check/release region.
+ */
+#define IOBASE_REGION_SIZE	0x10
+
+/*
+ * Hardware related defaults
+ */
+#define DEF_AEDSP16_IOB 0x220   /* 0x220(default) 0x240                 */
+#define DEF_AEDSP16_IRQ 7	/* 5 7(default) 9 10 11                 */
+#define DEF_AEDSP16_MRQ 0	/* 5 7 9 10 0(default), 0 means disable */
+#define DEF_AEDSP16_DMA 1	/* 0 1(default) 3                       */
+
+/*
+ * Commands of AEDSP16's DSP (SBPRO+special).
+ * Some of them are COMMAND_xx, in the future they may change.
+ */
+#define WRITE_MDIRQ_CFG   0x50	/* Set M&I&DRQ mask (the real config)   */
+#define COMMAND_52        0x52	/*                                      */
+#define READ_HARD_CFG     0x58	/* Read Hardware Config (I/O base etc)  */
+#define COMMAND_5C        0x5c	/*                                      */
+#define COMMAND_60        0x60	/*                                      */
+#define COMMAND_66        0x66	/*                                      */
+#define COMMAND_6C        0x6c	/*                                      */
+#define COMMAND_6E        0x6e	/*                                      */
+#define COMMAND_88        0x88	/*                                      */
+#define DSP_INIT_MSS      0x8c	/* Enable Microsoft Sound System mode   */
+#define COMMAND_C5        0xc5	/*                                      */
+#define GET_DSP_VERSION   0xe1	/* Get DSP Version                      */
+#define GET_DSP_COPYRIGHT 0xe3	/* Get DSP Copyright                    */
+
+/*
+ * Offsets of AEDSP16 DSP I/O ports. The offset is added to base I/O port
+ * to have the actual I/O port.
+ * Register permissions are:
+ * (wo) == Write Only
+ * (ro) == Read  Only
+ * (w-) == Write
+ * (r-) == Read
+ */
+#define DSP_RESET    0x06	/* offset of DSP RESET             (wo) */
+#define DSP_READ     0x0a	/* offset of DSP READ              (ro) */
+#define DSP_WRITE    0x0c	/* offset of DSP WRITE             (w-) */
+#define DSP_COMMAND  0x0c	/* offset of DSP COMMAND           (w-) */
+#define DSP_STATUS   0x0c	/* offset of DSP STATUS            (r-) */
+#define DSP_DATAVAIL 0x0e	/* offset of DSP DATA AVAILABLE    (ro) */
+
+
+#define RETRY           10	/* Various retry values on I/O opera-   */
+#define STATUSRETRY   1000	/* tions. Sometimes we have to          */
+#define HARDRETRY   500000	/* wait for previous cmd to complete    */
+
+/*
+ * Size of character arrays that store name and version of sound card
+ */
+#define CARDNAMELEN	15	/* Size of the card's name in chars     */
+#define CARDVERLEN	10	/* Size of the card's version in chars	*/
+#define CARDVERDIGITS	2	/* Number of digits in the version	*/
+
+#if defined(CONFIG_SC6600)
+/*
+ * Bitmapped flags of hard configuration
+ */
+/*
+ * Decode macros (xl == low byte, xh = high byte)
+ */
+#define IOBASE(xl)		((xl & 0x01)?0x240:0x220)
+#define JOY(xl)  		(xl & 0x02)
+#define MPUADDR(xl)		( 			\
+				(xl & 0x0C)?0x330:	\
+				(xl & 0x08)?0x320:	\
+				(xl & 0x04)?0x310:	\
+						0x300)
+#define WSSADDR(xl)		((xl & 0x10)?0xE80:0x530)
+#define CDROM(xh)		(xh & 0x20)
+#define CDROMADDR(xh)		(((xh & 0x1F) << 4) + 0x200)
+/*
+ * Encode macros
+ */
+#define BLDIOBASE(xl, val) {		\
+	xl &= ~0x01; 			\
+	if (val == 0x240)		\
+		xl |= 0x01;		\
+	}
+#define BLDJOY(xl, val) {		\
+	xl &= ~0x02; 			\
+	if (val == 1)			\
+		xl |= 0x02;		\
+	}
+#define BLDMPUADDR(xl, val) {		\
+	xl &= ~0x0C;			\
+	switch (val) {			\
+		case 0x330:		\
+			xl |= 0x0C;	\
+			break;		\
+		case 0x320:		\
+			xl |= 0x08;	\
+			break;		\
+		case 0x310:		\
+			xl |= 0x04;	\
+			break;		\
+		case 0x300:		\
+			xl |= 0x00;	\
+			break;		\
+		default:		\
+			xl |= 0x00;	\
+			break;		\
+		}			\
+	}
+#define BLDWSSADDR(xl, val) {		\
+	xl &= ~0x10; 			\
+	if (val == 0xE80)		\
+		xl |= 0x10;		\
+	}
+#define BLDCDROM(xh, val) {		\
+	xh &= ~0x20; 			\
+	if (val == 1)			\
+		xh |= 0x20;		\
+	}
+#define BLDCDROMADDR(xh, val) {		\
+	int tmp = val;			\
+	tmp -= 0x200;			\
+	tmp >>= 4;			\
+	tmp &= 0x1F;			\
+	xh |= tmp;			\
+	xh &= 0x7F;			\
+	xh |= 0x40;			\
+	}
+#endif /* CONFIG_SC6600 */
+
+/*
+ * Bit mapped flags for calling aedsp16_init_board(), and saving the current
+ * emulation mode.
+ */
+#define INIT_NONE   (0   )
+#define INIT_SBPRO  (1<<0)
+#define INIT_MSS    (1<<1)
+#define INIT_MPU401 (1<<2)
+
+static int      soft_cfg __initdata = 0;	/* bitmapped config */
+static int      soft_cfg_mss __initdata = 0;	/* bitmapped mss config */
+static int      ver[CARDVERDIGITS] __initdata = {0, 0};	/* DSP Ver:
+						   hi->ver[0] lo->ver[1] */
+
+#if defined(CONFIG_SC6600)
+static int	hard_cfg[2]     /* lo<-hard_cfg[0] hi<-hard_cfg[1]      */
+                     __initdata = { 0, 0};
+#endif /* CONFIG_SC6600 */
+
+#if defined(CONFIG_SC6600)
+/* Decoded hard configuration */
+struct	d_hcfg {
+	int iobase;
+	int joystick;
+	int mpubase;
+	int wssbase;
+	int cdrom;
+	int cdrombase;
+};
+
+static struct d_hcfg decoded_hcfg __initdata = {0, };
+
+#endif /* CONFIG_SC6600 */
+
+/* orVals contain the values to be or'ed       				*/
+struct orVals {
+	int	val;		/* irq|mirq|dma                         */
+	int	or;		/* soft_cfg |= TheStruct.or             */
+};
+
+/* aedsp16_info contain the audio card configuration                  */
+struct aedsp16_info {
+	int base_io;            /* base I/O address for accessing card  */
+	int irq;                /* irq value for DSP I/O                */
+	int mpu_irq;            /* irq for mpu401 interface I/O         */
+	int dma;                /* dma value for DSP I/O                */
+	int mss_base;           /* base I/O for Microsoft Sound System  */
+	int mpu_base;           /* base I/O for MPU-401 emulation       */
+	int init;               /* Initialization status of the card    */
+};
+
+/*
+ * Magic values that the DSP will eat when configuring irq/mirq/dma
+ */
+/* DSP IRQ conversion array             */
+static struct orVals orIRQ[] __initdata = {
+	{0x05, 0x28},
+	{0x07, 0x08},
+	{0x09, 0x10},
+	{0x0a, 0x18},
+	{0x0b, 0x20},
+	{0x00, 0x00}
+};
+
+/* MPU-401 IRQ conversion array         */
+static struct orVals orMIRQ[] __initdata = {
+	{0x05, 0x04},
+	{0x07, 0x44},
+	{0x09, 0x84},
+	{0x0a, 0xc4},
+	{0x00, 0x00}
+};
+
+/* DMA Channels conversion array        */
+static struct orVals orDMA[] __initdata = {
+	{0x00, 0x01},
+	{0x01, 0x02},
+	{0x03, 0x03},
+	{0x00, 0x00}
+};
+
+static struct aedsp16_info ae_config = {
+	DEF_AEDSP16_IOB,
+	DEF_AEDSP16_IRQ,
+	DEF_AEDSP16_MRQ,
+	DEF_AEDSP16_DMA,
+	-1,
+	-1,
+	INIT_NONE
+};
+
+/*
+ * Buffers to store audio card informations
+ */
+static char     DSPCopyright[CARDNAMELEN + 1] __initdata = {0, };
+static char     DSPVersion[CARDVERLEN + 1] __initdata = {0, };
+
+static int __init aedsp16_wait_data(int port)
+{
+	int             loop = STATUSRETRY;
+	unsigned char   ret = 0;
+
+	DBG1(("aedsp16_wait_data (0x%x): ", port));
+
+	do {
+		  ret = inb(port + DSP_DATAVAIL);
+	/*
+	 * Wait for data available (bit 7 of ret == 1)
+	 */
+	  } while (!(ret & 0x80) && loop--);
+
+	if (ret & 0x80) {
+		DBG1(("success.\n"));
+		return TRUE;
+	}
+
+	DBG1(("failure.\n"));
+	return FALSE;
+}
+
+static int __init aedsp16_read(int port)
+{
+	int inbyte;
+
+	DBG(("    Read DSP Byte (0x%x): ", port));
+
+	if (aedsp16_wait_data(port) == FALSE) {
+		DBG(("failure.\n"));
+		return -1;
+	}
+
+	inbyte = inb(port + DSP_READ);
+
+	DBG(("read [0x%x]/{%c}.\n", inbyte, inbyte));
+
+	return inbyte;
+}
+
+static int __init aedsp16_test_dsp(int port)
+{
+	return ((aedsp16_read(port) == 0xaa) ? TRUE : FALSE);
+}
+
+static int __init aedsp16_dsp_reset(int port)
+{
+	/*
+	 * Reset DSP
+	 */
+
+	DBG(("Reset DSP:\n"));
+
+	outb(1, (port + DSP_RESET));
+	udelay(10);
+	outb(0, (port + DSP_RESET));
+	udelay(10);
+	udelay(10);
+	if (aedsp16_test_dsp(port) == TRUE) {
+		DBG(("success.\n"));
+		return TRUE;
+	} else
+		DBG(("failure.\n"));
+	return FALSE;
+}
+
+static int __init aedsp16_write(int port, int cmd)
+{
+	unsigned char   ret;
+	int             loop = HARDRETRY;
+
+	DBG(("    Write DSP Byte (0x%x) [0x%x]: ", port, cmd));
+
+	do {
+		ret = inb(port + DSP_STATUS);
+		/*
+		 * DSP ready to receive data if bit 7 of ret == 0
+		 */
+		if (!(ret & 0x80)) {
+			outb(cmd, port + DSP_COMMAND);
+			DBG(("success.\n"));
+			return 0;
+		}
+	} while (loop--);
+
+	DBG(("timeout.\n"));
+	printk("[AEDSP16] DSP Command (0x%x) timeout.\n", cmd);
+
+	return -1;
+}
+
+#if defined(CONFIG_SC6600)
+
+#if defined(AEDSP16_INFO) || defined(AEDSP16_DEBUG)
+void __init aedsp16_pinfo(void) {
+	DBG(("\n Base address:  %x\n", decoded_hcfg.iobase));
+	DBG((" Joystick    : %s present\n", decoded_hcfg.joystick?"":" not"));
+	DBG((" WSS addr    :  %x\n", decoded_hcfg.wssbase));
+	DBG((" MPU-401 addr:  %x\n", decoded_hcfg.mpubase));
+	DBG((" CDROM       : %s present\n", (decoded_hcfg.cdrom!=4)?"":" not"));
+	DBG((" CDROMADDR   :  %x\n\n", decoded_hcfg.cdrombase));
+}
+#endif
+
+static void __init aedsp16_hard_decode(void) {
+
+	DBG((" aedsp16_hard_decode: 0x%x, 0x%x\n", hard_cfg[0], hard_cfg[1]));
+
+/*
+ * Decode Cfg Bytes.
+ */
+	decoded_hcfg.iobase	= IOBASE(hard_cfg[0]);
+	decoded_hcfg.joystick	= JOY(hard_cfg[0]);
+	decoded_hcfg.wssbase	= WSSADDR(hard_cfg[0]);
+	decoded_hcfg.mpubase	= MPUADDR(hard_cfg[0]);
+	decoded_hcfg.cdrom	= CDROM(hard_cfg[1]);
+	decoded_hcfg.cdrombase	= CDROMADDR(hard_cfg[1]);
+
+#if defined(AEDSP16_INFO) || defined(AEDSP16_DEBUG)
+	printk(" Original sound card configuration:\n");
+	aedsp16_pinfo();
+#endif
+
+/*
+ * Now set up the real kernel configuration.
+ */
+	decoded_hcfg.iobase	= ae_config.base_io;
+	decoded_hcfg.wssbase	= ae_config.mss_base;
+	decoded_hcfg.mpubase	= ae_config.mpu_base;
+
+#if defined(CONFIG_SC6600_JOY)
+ 	decoded_hcfg.joystick	= CONFIG_SC6600_JOY; /* Enable */
+#endif
+#if defined(CONFIG_SC6600_CDROM)
+	decoded_hcfg.cdrom	= CONFIG_SC6600_CDROM; /* 4:N-3:I-2:G-1:P-0:S */
+#endif
+#if defined(CONFIG_SC6600_CDROMBASE)
+	decoded_hcfg.cdrombase	= CONFIG_SC6600_CDROMBASE; /* 0 Disable */
+#endif
+
+#if defined(AEDSP16_DEBUG)
+	DBG((" New Values:\n"));
+	aedsp16_pinfo();
+#endif
+
+	DBG(("success.\n"));
+}
+
+static void __init aedsp16_hard_encode(void) {
+
+	DBG((" aedsp16_hard_encode: 0x%x, 0x%x\n", hard_cfg[0], hard_cfg[1]));
+
+	hard_cfg[0] = 0;
+	hard_cfg[1] = 0;
+
+	hard_cfg[0] |= 0x20;
+
+	BLDIOBASE (hard_cfg[0], decoded_hcfg.iobase);
+	BLDWSSADDR(hard_cfg[0], decoded_hcfg.wssbase);
+	BLDMPUADDR(hard_cfg[0], decoded_hcfg.mpubase);
+	BLDJOY(hard_cfg[0], decoded_hcfg.joystick);
+	BLDCDROM(hard_cfg[1], decoded_hcfg.cdrom);
+	BLDCDROMADDR(hard_cfg[1], decoded_hcfg.cdrombase);
+
+#if defined(AEDSP16_DEBUG)
+	aedsp16_pinfo();
+#endif
+
+	DBG((" aedsp16_hard_encode: 0x%x, 0x%x\n", hard_cfg[0], hard_cfg[1]));
+	DBG(("success.\n"));
+
+}
+
+static int __init aedsp16_hard_write(int port) {
+
+	DBG(("aedsp16_hard_write:\n"));
+
+	if (aedsp16_write(port, COMMAND_6C)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_6C);
+		DBG(("failure.\n"));
+		return FALSE;
+	}
+	if (aedsp16_write(port, COMMAND_5C)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_5C);
+		DBG(("failure.\n"));
+		return FALSE;
+	}
+	if (aedsp16_write(port, hard_cfg[0])) {
+		printk("[AEDSP16] DATA 0x%x: failed!\n", hard_cfg[0]);
+		DBG(("failure.\n"));
+		return FALSE;
+	}
+	if (aedsp16_write(port, hard_cfg[1])) {
+		printk("[AEDSP16] DATA 0x%x: failed!\n", hard_cfg[1]);
+		DBG(("failure.\n"));
+		return FALSE;
+	}
+	if (aedsp16_write(port, COMMAND_C5)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_C5);
+		DBG(("failure.\n"));
+		return FALSE;
+	}
+
+	DBG(("success.\n"));
+
+	return TRUE;
+}
+
+static int __init aedsp16_hard_read(int port) {
+
+	DBG(("aedsp16_hard_read:\n"));
+
+	if (aedsp16_write(port, READ_HARD_CFG)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", READ_HARD_CFG);
+		DBG(("failure.\n"));
+		return FALSE;
+	}
+
+	if ((hard_cfg[0] = aedsp16_read(port)) == -1) {
+		printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n",
+			READ_HARD_CFG);
+		DBG(("failure.\n"));
+		return FALSE;
+	}
+	if ((hard_cfg[1] = aedsp16_read(port)) == -1) {
+		printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n",
+			READ_HARD_CFG);
+		DBG(("failure.\n"));
+		return FALSE;
+	}
+	if (aedsp16_read(port) == -1) {
+		printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n",
+			READ_HARD_CFG);
+		DBG(("failure.\n"));
+		return FALSE;
+	}
+
+	DBG(("success.\n"));
+
+	return TRUE;
+}
+
+static int __init aedsp16_ext_cfg_write(int port) {
+
+	int extcfg, val;
+
+	if (aedsp16_write(port, COMMAND_66)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_66);
+		return FALSE;
+	}
+
+	extcfg = 7;
+	if (decoded_hcfg.cdrom != 2)
+		extcfg = 0x0F;
+	if ((decoded_hcfg.cdrom == 4) ||
+	    (decoded_hcfg.cdrom == 3))
+		extcfg &= ~2;
+	if (decoded_hcfg.cdrombase == 0)
+		extcfg &= ~2;
+	if (decoded_hcfg.mpubase == 0)
+		extcfg &= ~1;
+
+	if (aedsp16_write(port, extcfg)) {
+		printk("[AEDSP16] Write extcfg: failed!\n");
+		return FALSE;
+	}
+	if (aedsp16_write(port, 0)) {
+		printk("[AEDSP16] Write extcfg: failed!\n");
+		return FALSE;
+	}
+	if (decoded_hcfg.cdrom == 3) {
+		if (aedsp16_write(port, COMMAND_52)) {
+			printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_52);
+			return FALSE;
+		}
+		if ((val = aedsp16_read(port)) == -1) {
+			printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n"
+					, COMMAND_52);
+			return FALSE;
+		}
+		val &= 0x7F;
+		if (aedsp16_write(port, COMMAND_60)) {
+			printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_60);
+			return FALSE;
+		}
+		if (aedsp16_write(port, val)) {
+			printk("[AEDSP16] Write val: failed!\n");
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+#endif /* CONFIG_SC6600 */
+
+static int __init aedsp16_cfg_write(int port) {
+	if (aedsp16_write(port, WRITE_MDIRQ_CFG)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG);
+		return FALSE;
+	}
+	if (aedsp16_write(port, soft_cfg)) {
+		printk("[AEDSP16] Initialization of (M)IRQ and DMA: failed!\n");
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static int __init aedsp16_init_mss(int port)
+{
+	DBG(("aedsp16_init_mss:\n"));
+
+	mdelay(10);
+
+	if (aedsp16_write(port, DSP_INIT_MSS)) {
+		printk("[AEDSP16] aedsp16_init_mss [0x%x]: failed!\n",
+				DSP_INIT_MSS);
+		DBG(("failure.\n"));
+		return FALSE;
+	}
+	
+	mdelay(10);
+
+	if (aedsp16_cfg_write(port) == FALSE)
+		return FALSE;
+
+	outb(soft_cfg_mss, ae_config.mss_base);
+
+	DBG(("success.\n"));
+
+	return TRUE;
+}
+
+static int __init aedsp16_setup_board(int port) {
+	int	loop = RETRY;
+
+#if defined(CONFIG_SC6600)
+	int	val = 0;
+
+	if (aedsp16_hard_read(port) == FALSE) {
+		printk("[AEDSP16] aedsp16_hard_read: failed!\n");
+		return FALSE;
+	}
+
+	if (aedsp16_write(port, COMMAND_52)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_52);
+		return FALSE;
+	}
+
+	if ((val = aedsp16_read(port)) == -1) {
+		printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n",
+				COMMAND_52);
+		return FALSE;
+	}
+#endif
+
+	do {
+		if (aedsp16_write(port, COMMAND_88)) {
+			printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_88);
+			return FALSE;
+		}
+		mdelay(10);
+	} while ((aedsp16_wait_data(port) == FALSE) && loop--);
+
+	if (aedsp16_read(port) == -1) {
+		printk("[AEDSP16] aedsp16_read after CMD 0x%x: failed\n",
+				COMMAND_88);
+		return FALSE;
+	}
+
+#if !defined(CONFIG_SC6600)
+	if (aedsp16_write(port, COMMAND_5C)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_5C);
+		return FALSE;
+	}
+#endif
+
+	if (aedsp16_cfg_write(port) == FALSE)
+		return FALSE;
+
+#if defined(CONFIG_SC6600)
+	if (aedsp16_write(port, COMMAND_60)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_60);
+		return FALSE;
+	}
+	if (aedsp16_write(port, val)) {
+		printk("[AEDSP16] DATA 0x%x: failed!\n", val);
+		return FALSE;
+	}
+	if (aedsp16_write(port, COMMAND_6E)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_6E);
+		return FALSE;
+	}
+	if (aedsp16_write(port, ver[0])) {
+		printk("[AEDSP16] DATA 0x%x: failed!\n", ver[0]);
+		return FALSE;
+	}
+	if (aedsp16_write(port, ver[1])) {
+		printk("[AEDSP16] DATA 0x%x: failed!\n", ver[1]);
+		return FALSE;
+	}
+
+	if (aedsp16_hard_write(port) == FALSE) {
+		printk("[AEDSP16] aedsp16_hard_write: failed!\n");
+		return FALSE;
+	}
+
+	if (aedsp16_write(port, COMMAND_5C)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", COMMAND_5C);
+		return FALSE;
+	}
+
+#if defined(THIS_IS_A_THING_I_HAVE_NOT_TESTED_YET)
+	if (aedsp16_cfg_write(port) == FALSE)
+		return FALSE;
+#endif
+
+#endif
+
+	return TRUE;
+}
+
+static int __init aedsp16_stdcfg(int port) {
+	if (aedsp16_write(port, WRITE_MDIRQ_CFG)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG);
+		return FALSE;
+	}
+	/*
+	 * 0x0A == (IRQ 7, DMA 1, MIRQ 0)
+	 */
+	if (aedsp16_write(port, 0x0A)) {
+		printk("[AEDSP16] aedsp16_stdcfg: failed!\n");
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static int __init aedsp16_dsp_version(int port)
+{
+	int             len = 0;
+	int             ret;
+
+	DBG(("Get DSP Version:\n"));
+
+	if (aedsp16_write(ae_config.base_io, GET_DSP_VERSION)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", GET_DSP_VERSION);
+		DBG(("failed.\n"));
+		return FALSE;
+	}
+
+	do {
+		if ((ret = aedsp16_read(port)) == -1) {
+			DBG(("failed.\n"));
+			return FALSE;
+		}
+	/*
+	 * We already know how many int are stored (2), so we know when the
+	 * string is finished.
+	 */
+		ver[len++] = ret;
+	  } while (len < CARDVERDIGITS);
+	sprintf(DSPVersion, "%d.%d", ver[0], ver[1]);
+
+	DBG(("success.\n"));
+
+	return TRUE;
+}
+
+static int __init aedsp16_dsp_copyright(int port)
+{
+	int             len = 0;
+	int             ret;
+
+	DBG(("Get DSP Copyright:\n"));
+
+	if (aedsp16_write(ae_config.base_io, GET_DSP_COPYRIGHT)) {
+		printk("[AEDSP16] CMD 0x%x: failed!\n", GET_DSP_COPYRIGHT);
+		DBG(("failed.\n"));
+		return FALSE;
+	}
+
+	do {
+		if ((ret = aedsp16_read(port)) == -1) {
+	/*
+	 * If no more data available, return to the caller, no error if len>0.
+	 * We have no other way to know when the string is finished.
+	 */
+			if (len)
+				break;
+			else {
+				DBG(("failed.\n"));
+				return FALSE;
+			}
+		}
+
+		DSPCopyright[len++] = ret;
+
+	  } while (len < CARDNAMELEN);
+
+	DBG(("success.\n"));
+
+	return TRUE;
+}
+
+static void __init aedsp16_init_tables(void)
+{
+	int i = 0;
+
+	memset(DSPCopyright, 0, CARDNAMELEN + 1);
+	memset(DSPVersion, 0, CARDVERLEN + 1);
+
+	for (i = 0; orIRQ[i].or; i++)
+		if (orIRQ[i].val == ae_config.irq) {
+			soft_cfg |= orIRQ[i].or;
+			soft_cfg_mss |= orIRQ[i].or;
+		}
+
+	for (i = 0; orMIRQ[i].or; i++)
+		if (orMIRQ[i].or == ae_config.mpu_irq)
+			soft_cfg |= orMIRQ[i].or;
+
+	for (i = 0; orDMA[i].or; i++)
+		if (orDMA[i].val == ae_config.dma) {
+			soft_cfg |= orDMA[i].or;
+			soft_cfg_mss |= orDMA[i].or;
+		}
+}
+
+static int __init aedsp16_init_board(void)
+{
+	aedsp16_init_tables();
+
+	if (aedsp16_dsp_reset(ae_config.base_io) == FALSE) {
+		printk("[AEDSP16] aedsp16_dsp_reset: failed!\n");
+		return FALSE;
+	}
+	if (aedsp16_dsp_copyright(ae_config.base_io) == FALSE) {
+		printk("[AEDSP16] aedsp16_dsp_copyright: failed!\n");
+		return FALSE;
+	}
+
+	/*
+	 * My AEDSP16 card return SC-6000 in DSPCopyright, so
+	 * if we have something different, we have to be warned.
+	 */
+	if (strcmp("SC-6000", DSPCopyright))
+		printk("[AEDSP16] Warning: non SC-6000 audio card!\n");
+
+	if (aedsp16_dsp_version(ae_config.base_io) == FALSE) {
+		printk("[AEDSP16] aedsp16_dsp_version: failed!\n");
+		return FALSE;
+	}
+
+	if (aedsp16_stdcfg(ae_config.base_io) == FALSE) {
+		printk("[AEDSP16] aedsp16_stdcfg: failed!\n");
+		return FALSE;
+	}
+
+#if defined(CONFIG_SC6600)
+	if (aedsp16_hard_read(ae_config.base_io) == FALSE) {
+		printk("[AEDSP16] aedsp16_hard_read: failed!\n");
+		return FALSE;
+	}
+
+	aedsp16_hard_decode();
+
+	aedsp16_hard_encode();
+
+	if (aedsp16_hard_write(ae_config.base_io) == FALSE) {
+		printk("[AEDSP16] aedsp16_hard_write: failed!\n");
+		return FALSE;
+	}
+
+	if (aedsp16_ext_cfg_write(ae_config.base_io) == FALSE) {
+		printk("[AEDSP16] aedsp16_ext_cfg_write: failed!\n");
+		return FALSE;
+	}
+#endif /* CONFIG_SC6600 */
+
+	if (aedsp16_setup_board(ae_config.base_io) == FALSE) {
+		printk("[AEDSP16] aedsp16_setup_board: failed!\n");
+		return FALSE;
+	}
+
+	if (ae_config.mss_base != -1) {
+		if (ae_config.init & INIT_MSS) {
+			if (aedsp16_init_mss(ae_config.base_io) == FALSE) {
+				printk("[AEDSP16] Can not initialize"
+				       "Microsoft Sound System mode.\n");
+				return FALSE;
+			}
+		}
+	}
+
+#if !defined(MODULE) || defined(AEDSP16_INFO) || defined(AEDSP16_DEBUG)
+
+	printk("Audio Excel DSP 16 init v%s (%s %s) [",
+		VERSION, DSPCopyright,
+		DSPVersion);
+
+	if (ae_config.mpu_base != -1) {
+		if (ae_config.init & INIT_MPU401) {
+			printk("MPU401");
+			if ((ae_config.init & INIT_MSS) ||
+			    (ae_config.init & INIT_SBPRO))
+				printk(" ");
+		}
+	}
+
+	if (ae_config.mss_base == -1) {
+		if (ae_config.init & INIT_SBPRO) {
+			printk("SBPro");
+			if (ae_config.init & INIT_MSS)
+				printk(" ");
+		}
+	}
+
+	if (ae_config.mss_base != -1)
+		if (ae_config.init & INIT_MSS)
+			printk("MSS");
+
+	printk("]\n");
+#endif /* MODULE || AEDSP16_INFO || AEDSP16_DEBUG */
+
+	mdelay(10);
+
+	return TRUE;
+}
+
+static int __init init_aedsp16_sb(void)
+{
+	DBG(("init_aedsp16_sb: "));
+
+/*
+ * If the card is already init'ed MSS, we can not init it to SBPRO too
+ * because the board can not emulate simultaneously MSS and SBPRO.
+ */
+	if (ae_config.init & INIT_MSS)
+		return FALSE;
+	if (ae_config.init & INIT_SBPRO)
+		return FALSE;
+
+	ae_config.init |= INIT_SBPRO;
+
+	DBG(("done.\n"));
+
+	return TRUE;
+}
+
+static void uninit_aedsp16_sb(void)
+{
+	DBG(("uninit_aedsp16_sb: "));
+
+	ae_config.init &= ~INIT_SBPRO;
+
+	DBG(("done.\n"));
+}
+
+static int __init init_aedsp16_mss(void)
+{
+	DBG(("init_aedsp16_mss: "));
+
+/*
+ * If the card is already init'ed SBPRO, we can not init it to MSS too
+ * because the board can not emulate simultaneously MSS and SBPRO.
+ */
+	if (ae_config.init & INIT_SBPRO)
+		return FALSE;
+	if (ae_config.init & INIT_MSS)
+		return FALSE;
+/*
+ * We must allocate the CONFIG_AEDSP16_BASE region too because these are the 
+ * I/O ports to access card's control registers.
+ */
+	if (!(ae_config.init & INIT_MPU401)) {
+		if (!request_region(ae_config.base_io, IOBASE_REGION_SIZE,
+				"aedsp16 (base)")) {
+			printk(
+			"AEDSP16 BASE I/O port region is already in use.\n");
+			return FALSE;
+		}
+	}
+
+	ae_config.init |= INIT_MSS;
+
+	DBG(("done.\n"));
+
+	return TRUE;
+}
+
+static void uninit_aedsp16_mss(void)
+{
+	DBG(("uninit_aedsp16_mss: "));
+
+	if ((!(ae_config.init & INIT_MPU401)) &&
+	   (ae_config.init & INIT_MSS)) {
+		release_region(ae_config.base_io, IOBASE_REGION_SIZE);
+		DBG(("AEDSP16 base region released.\n"));
+	}
+
+	ae_config.init &= ~INIT_MSS;
+	DBG(("done.\n"));
+}
+
+static int __init init_aedsp16_mpu(void)
+{
+	DBG(("init_aedsp16_mpu: "));
+
+	if (ae_config.init & INIT_MPU401)
+		return FALSE;
+
+/*
+ * We must request the CONFIG_AEDSP16_BASE region too because these are the I/O 
+ * ports to access card's control registers.
+ */
+	if (!(ae_config.init & (INIT_MSS | INIT_SBPRO))) {
+		if (!request_region(ae_config.base_io, IOBASE_REGION_SIZE,
+					"aedsp16 (base)")) {
+			printk(
+			"AEDSP16 BASE I/O port region is already in use.\n");
+			return FALSE;
+		}
+	}
+
+	ae_config.init |= INIT_MPU401;
+
+	DBG(("done.\n"));
+
+	return TRUE;
+}
+
+static void uninit_aedsp16_mpu(void)
+{
+	DBG(("uninit_aedsp16_mpu: "));
+
+	if ((!(ae_config.init & (INIT_MSS | INIT_SBPRO))) &&
+	   (ae_config.init & INIT_MPU401)) {
+		release_region(ae_config.base_io, IOBASE_REGION_SIZE);
+		DBG(("AEDSP16 base region released.\n"));
+	}
+
+	ae_config.init &= ~INIT_MPU401;
+
+	DBG(("done.\n"));
+}
+
+static int __init init_aedsp16(void)
+{
+	int initialized = FALSE;
+
+	DBG(("Initializing BASE[0x%x] IRQ[%d] DMA[%d] MIRQ[%d]\n",
+	     ae_config.base_io,ae_config.irq,ae_config.dma,ae_config.mpu_irq));
+
+	if (ae_config.mss_base == -1) {
+		if (init_aedsp16_sb() == FALSE) {
+			uninit_aedsp16_sb();
+		} else {
+			initialized = TRUE;
+		}
+	}
+
+	if (ae_config.mpu_base != -1) {
+		if (init_aedsp16_mpu() == FALSE) {
+			uninit_aedsp16_mpu();
+		} else {
+			initialized = TRUE;
+		}
+	}
+
+/*
+ * In the sequence of init routines, the MSS init MUST be the last!
+ * This because of the special register programming the MSS mode needs.
+ * A board reset would disable the MSS mode restoring the default SBPRO
+ * mode.
+ */
+	if (ae_config.mss_base != -1) {
+		if (init_aedsp16_mss() == FALSE) {
+			uninit_aedsp16_mss();
+		} else {
+			initialized = TRUE;
+		}
+	}
+
+	if (initialized)
+		initialized = aedsp16_init_board();
+	return initialized;
+}
+
+static void __exit uninit_aedsp16(void)
+{
+	if (ae_config.mss_base != -1)
+		uninit_aedsp16_mss();
+	else
+		uninit_aedsp16_sb();
+	if (ae_config.mpu_base != -1)
+		uninit_aedsp16_mpu();
+}
+
+static int __initdata io = -1;
+static int __initdata irq = -1;
+static int __initdata dma = -1;
+static int __initdata mpu_irq = -1;
+static int __initdata mss_base = -1;
+static int __initdata mpu_base = -1;
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O base address (0x220 0x240)");
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "IRQ line (5 7 9 10 11)");
+module_param(dma, int, 0);
+MODULE_PARM_DESC(dma, "dma line (0 1 3)");
+module_param(mpu_irq, int, 0);
+MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ line (5 7 9 10 0)");
+module_param(mss_base, int, 0);
+MODULE_PARM_DESC(mss_base, "MSS emulation I/O base address (0x530 0xE80)");
+module_param(mpu_base, int, 0);
+MODULE_PARM_DESC(mpu_base,"MPU-401 I/O base address (0x300 0x310 0x320 0x330)");
+MODULE_AUTHOR("Riccardo Facchetti <fizban@tin.it>");
+MODULE_DESCRIPTION("Audio Excel DSP 16 Driver Version " VERSION);
+MODULE_LICENSE("GPL");
+
+static int __init do_init_aedsp16(void) {
+	printk("Audio Excel DSP 16 init driver Copyright (C) Riccardo Facchetti 1995-98\n");
+	if (io == -1 || dma == -1 || irq == -1) {
+		printk(KERN_INFO "aedsp16: I/O, IRQ and DMA are mandatory\n");
+		return -EINVAL;
+	}
+
+	ae_config.base_io = io;
+	ae_config.irq = irq;
+	ae_config.dma = dma;
+
+	ae_config.mss_base = mss_base;
+	ae_config.mpu_base = mpu_base;
+	ae_config.mpu_irq = mpu_irq;
+
+	if (init_aedsp16() == FALSE) {
+		printk(KERN_ERR "aedsp16: initialization failed\n");
+		/*
+		 * XXX
+		 * What error should we return here ?
+		 */
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void __exit cleanup_aedsp16(void) {
+	uninit_aedsp16();
+}
+
+module_init(do_init_aedsp16);
+module_exit(cleanup_aedsp16);
+
+#ifndef MODULE
+static int __init setup_aedsp16(char *str)
+{
+	/* io, irq, dma, mss_io, mpu_io, mpu_irq */
+	int ints[7];
+	
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+
+	io	 = ints[1];
+	irq	 = ints[2];
+	dma	 = ints[3];
+	mss_base = ints[4];
+	mpu_base = ints[5];
+	mpu_irq	 = ints[6];
+	return 1;
+}
+
+__setup("aedsp16=", setup_aedsp16);
+#endif
diff --git a/linux/sound/oss/au1550_ac97.c b/linux/sound/oss/au1550_ac97.c
new file mode 100644
index 0000000..a8f626d
--- /dev/null
+++ b/linux/sound/oss/au1550_ac97.c
@@ -0,0 +1,2147 @@
+/*
+ * au1550_ac97.c  --  Sound driver for Alchemy Au1550 MIPS Internet Edge
+ *                    Processor.
+ *
+ * Copyright 2004 Embedded Edge, LLC
+ *	dan@embeddededge.com
+ *
+ * Mostly copied from the au1000.c driver and some from the
+ * PowerMac dbdma driver.
+ * We assume the processor can do memory coherent DMA.
+ *
+ * Ported to 2.6 by Matt Porter <mporter@kernel.crashing.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  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
+ *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+ *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
+ *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#undef DEBUG
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/sound.h>
+#include <linux/slab.h>
+#include <linux/soundcard.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/poll.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/ac97_codec.h>
+#include <linux/mutex.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/hardirq.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+#include <asm/mach-au1x00/au1xxx_dbdma.h>
+#include <asm/mach-au1x00/au1xxx.h>
+
+#undef OSS_DOCUMENTED_MIXER_SEMANTICS
+
+/* misc stuff */
+#define POLL_COUNT   0x50000
+#define AC97_EXT_DACS (AC97_EXTID_SDAC | AC97_EXTID_CDAC | AC97_EXTID_LDAC)
+
+/* The number of DBDMA ring descriptors to allocate.  No sense making
+ * this too large....if you can't keep up with a few you aren't likely
+ * to be able to with lots of them, either.
+ */
+#define NUM_DBDMA_DESCRIPTORS 4
+
+#define err(format, arg...) printk(KERN_ERR format "\n" , ## arg)
+
+/* Boot options
+ * 0 = no VRA, 1 = use VRA if codec supports it
+ */
+static DEFINE_MUTEX(au1550_ac97_mutex);
+static int      vra = 1;
+module_param(vra, bool, 0);
+MODULE_PARM_DESC(vra, "if 1 use VRA if codec supports it");
+
+static struct au1550_state {
+	/* soundcore stuff */
+	int             dev_audio;
+
+	struct ac97_codec *codec;
+	unsigned        codec_base_caps; /* AC'97 reg 00h, "Reset Register" */
+	unsigned        codec_ext_caps;  /* AC'97 reg 28h, "Extended Audio ID" */
+	int             no_vra;		/* do not use VRA */
+
+	spinlock_t      lock;
+	struct mutex open_mutex;
+	struct mutex sem;
+	fmode_t          open_mode;
+	wait_queue_head_t open_wait;
+
+	struct dmabuf {
+		u32		dmanr;
+		unsigned        sample_rate;
+		unsigned	src_factor;
+		unsigned        sample_size;
+		int             num_channels;
+		int		dma_bytes_per_sample;
+		int		user_bytes_per_sample;
+		int		cnt_factor;
+
+		void		*rawbuf;
+		unsigned        buforder;
+		unsigned	numfrag;
+		unsigned        fragshift;
+		void		*nextIn;
+		void		*nextOut;
+		int		count;
+		unsigned        total_bytes;
+		unsigned        error;
+		wait_queue_head_t wait;
+
+		/* redundant, but makes calculations easier */
+		unsigned	fragsize;
+		unsigned	dma_fragsize;
+		unsigned	dmasize;
+		unsigned	dma_qcount;
+
+		/* OSS stuff */
+		unsigned        mapped:1;
+		unsigned        ready:1;
+		unsigned        stopped:1;
+		unsigned        ossfragshift;
+		int             ossmaxfrags;
+		unsigned        subdivision;
+	} dma_dac, dma_adc;
+} au1550_state;
+
+static unsigned
+ld2(unsigned int x)
+{
+	unsigned        r = 0;
+
+	if (x >= 0x10000) {
+		x >>= 16;
+		r += 16;
+	}
+	if (x >= 0x100) {
+		x >>= 8;
+		r += 8;
+	}
+	if (x >= 0x10) {
+		x >>= 4;
+		r += 4;
+	}
+	if (x >= 4) {
+		x >>= 2;
+		r += 2;
+	}
+	if (x >= 2)
+		r++;
+	return r;
+}
+
+static void
+au1550_delay(int msec)
+{
+	if (in_interrupt())
+		return;
+
+	schedule_timeout_uninterruptible(msecs_to_jiffies(msec));
+}
+
+static u16
+rdcodec(struct ac97_codec *codec, u8 addr)
+{
+	struct au1550_state *s = codec->private_data;
+	unsigned long   flags;
+	u32             cmd, val;
+	u16             data;
+	int             i;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	for (i = 0; i < POLL_COUNT; i++) {
+		val = au_readl(PSC_AC97STAT);
+		au_sync();
+		if (!(val & PSC_AC97STAT_CP))
+			break;
+	}
+	if (i == POLL_COUNT)
+		err("rdcodec: codec cmd pending expired!");
+
+	cmd = (u32)PSC_AC97CDC_INDX(addr);
+	cmd |= PSC_AC97CDC_RD;	/* read command */
+	au_writel(cmd, PSC_AC97CDC);
+	au_sync();
+
+	/* now wait for the data
+	*/
+	for (i = 0; i < POLL_COUNT; i++) {
+		val = au_readl(PSC_AC97STAT);
+		au_sync();
+		if (!(val & PSC_AC97STAT_CP))
+			break;
+	}
+	if (i == POLL_COUNT) {
+		err("rdcodec: read poll expired!");
+		data = 0;
+		goto out;
+	}
+
+	/* wait for command done?
+	*/
+	for (i = 0; i < POLL_COUNT; i++) {
+		val = au_readl(PSC_AC97EVNT);
+		au_sync();
+		if (val & PSC_AC97EVNT_CD)
+			break;
+	}
+	if (i == POLL_COUNT) {
+		err("rdcodec: read cmdwait expired!");
+		data = 0;
+		goto out;
+	}
+
+	data = au_readl(PSC_AC97CDC) & 0xffff;
+	au_sync();
+
+	/* Clear command done event.
+	*/
+	au_writel(PSC_AC97EVNT_CD, PSC_AC97EVNT);
+	au_sync();
+
+ out:
+	spin_unlock_irqrestore(&s->lock, flags);
+
+	return data;
+}
+
+
+static void
+wrcodec(struct ac97_codec *codec, u8 addr, u16 data)
+{
+	struct au1550_state *s = codec->private_data;
+	unsigned long   flags;
+	u32             cmd, val;
+	int             i;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	for (i = 0; i < POLL_COUNT; i++) {
+		val = au_readl(PSC_AC97STAT);
+		au_sync();
+		if (!(val & PSC_AC97STAT_CP))
+			break;
+	}
+	if (i == POLL_COUNT)
+		err("wrcodec: codec cmd pending expired!");
+
+	cmd = (u32)PSC_AC97CDC_INDX(addr);
+	cmd |= (u32)data;
+	au_writel(cmd, PSC_AC97CDC);
+	au_sync();
+
+	for (i = 0; i < POLL_COUNT; i++) {
+		val = au_readl(PSC_AC97STAT);
+		au_sync();
+		if (!(val & PSC_AC97STAT_CP))
+			break;
+	}
+	if (i == POLL_COUNT)
+		err("wrcodec: codec cmd pending expired!");
+
+	for (i = 0; i < POLL_COUNT; i++) {
+		val = au_readl(PSC_AC97EVNT);
+		au_sync();
+		if (val & PSC_AC97EVNT_CD)
+			break;
+	}
+	if (i == POLL_COUNT)
+		err("wrcodec: read cmdwait expired!");
+
+	/* Clear command done event.
+	*/
+	au_writel(PSC_AC97EVNT_CD, PSC_AC97EVNT);
+	au_sync();
+
+	spin_unlock_irqrestore(&s->lock, flags);
+}
+
+static void
+waitcodec(struct ac97_codec *codec)
+{
+	u16	temp;
+	u32	val;
+	int	i;
+
+	/* codec_wait is used to wait for a ready state after
+	 * an AC97C_RESET.
+	 */
+	au1550_delay(10);
+
+	/* first poll the CODEC_READY tag bit
+	*/
+	for (i = 0; i < POLL_COUNT; i++) {
+		val = au_readl(PSC_AC97STAT);
+		au_sync();
+		if (val & PSC_AC97STAT_CR)
+			break;
+	}
+	if (i == POLL_COUNT) {
+		err("waitcodec: CODEC_READY poll expired!");
+		return;
+	}
+
+	/* get AC'97 powerdown control/status register
+	*/
+	temp = rdcodec(codec, AC97_POWER_CONTROL);
+
+	/* If anything is powered down, power'em up
+	*/
+	if (temp & 0x7f00) {
+		/* Power on
+		*/
+		wrcodec(codec, AC97_POWER_CONTROL, 0);
+		au1550_delay(100);
+
+		/* Reread
+		*/
+		temp = rdcodec(codec, AC97_POWER_CONTROL);
+	}
+
+	/* Check if Codec REF,ANL,DAC,ADC ready
+	*/
+	if ((temp & 0x7f0f) != 0x000f)
+		err("codec reg 26 status (0x%x) not ready!!", temp);
+}
+
+/* stop the ADC before calling */
+static void
+set_adc_rate(struct au1550_state *s, unsigned rate)
+{
+	struct dmabuf  *adc = &s->dma_adc;
+	struct dmabuf  *dac = &s->dma_dac;
+	unsigned        adc_rate, dac_rate;
+	u16             ac97_extstat;
+
+	if (s->no_vra) {
+		/* calc SRC factor
+		*/
+		adc->src_factor = ((96000 / rate) + 1) >> 1;
+		adc->sample_rate = 48000 / adc->src_factor;
+		return;
+	}
+
+	adc->src_factor = 1;
+
+	ac97_extstat = rdcodec(s->codec, AC97_EXTENDED_STATUS);
+
+	rate = rate > 48000 ? 48000 : rate;
+
+	/* enable VRA
+	*/
+	wrcodec(s->codec, AC97_EXTENDED_STATUS,
+		ac97_extstat | AC97_EXTSTAT_VRA);
+
+	/* now write the sample rate
+	*/
+	wrcodec(s->codec, AC97_PCM_LR_ADC_RATE, (u16) rate);
+
+	/* read it back for actual supported rate
+	*/
+	adc_rate = rdcodec(s->codec, AC97_PCM_LR_ADC_RATE);
+
+	pr_debug("set_adc_rate: set to %d Hz\n", adc_rate);
+
+	/* some codec's don't allow unequal DAC and ADC rates, in which case
+	 * writing one rate reg actually changes both.
+	 */
+	dac_rate = rdcodec(s->codec, AC97_PCM_FRONT_DAC_RATE);
+	if (dac->num_channels > 2)
+		wrcodec(s->codec, AC97_PCM_SURR_DAC_RATE, dac_rate);
+	if (dac->num_channels > 4)
+		wrcodec(s->codec, AC97_PCM_LFE_DAC_RATE, dac_rate);
+
+	adc->sample_rate = adc_rate;
+	dac->sample_rate = dac_rate;
+}
+
+/* stop the DAC before calling */
+static void
+set_dac_rate(struct au1550_state *s, unsigned rate)
+{
+	struct dmabuf  *dac = &s->dma_dac;
+	struct dmabuf  *adc = &s->dma_adc;
+	unsigned        adc_rate, dac_rate;
+	u16             ac97_extstat;
+
+	if (s->no_vra) {
+		/* calc SRC factor
+		*/
+		dac->src_factor = ((96000 / rate) + 1) >> 1;
+		dac->sample_rate = 48000 / dac->src_factor;
+		return;
+	}
+
+	dac->src_factor = 1;
+
+	ac97_extstat = rdcodec(s->codec, AC97_EXTENDED_STATUS);
+
+	rate = rate > 48000 ? 48000 : rate;
+
+	/* enable VRA
+	*/
+	wrcodec(s->codec, AC97_EXTENDED_STATUS,
+		ac97_extstat | AC97_EXTSTAT_VRA);
+
+	/* now write the sample rate
+	*/
+	wrcodec(s->codec, AC97_PCM_FRONT_DAC_RATE, (u16) rate);
+
+	/* I don't support different sample rates for multichannel,
+	 * so make these channels the same.
+	 */
+	if (dac->num_channels > 2)
+		wrcodec(s->codec, AC97_PCM_SURR_DAC_RATE, (u16) rate);
+	if (dac->num_channels > 4)
+		wrcodec(s->codec, AC97_PCM_LFE_DAC_RATE, (u16) rate);
+	/* read it back for actual supported rate
+	*/
+	dac_rate = rdcodec(s->codec, AC97_PCM_FRONT_DAC_RATE);
+
+	pr_debug("set_dac_rate: set to %d Hz\n", dac_rate);
+
+	/* some codec's don't allow unequal DAC and ADC rates, in which case
+	 * writing one rate reg actually changes both.
+	 */
+	adc_rate = rdcodec(s->codec, AC97_PCM_LR_ADC_RATE);
+
+	dac->sample_rate = dac_rate;
+	adc->sample_rate = adc_rate;
+}
+
+static void
+stop_dac(struct au1550_state *s)
+{
+	struct dmabuf  *db = &s->dma_dac;
+	u32		stat;
+	unsigned long   flags;
+
+	if (db->stopped)
+		return;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	au_writel(PSC_AC97PCR_TP, PSC_AC97PCR);
+	au_sync();
+
+	/* Wait for Transmit Busy to show disabled.
+	*/
+	do {
+		stat = au_readl(PSC_AC97STAT);
+		au_sync();
+	} while ((stat & PSC_AC97STAT_TB) != 0);
+
+	au1xxx_dbdma_reset(db->dmanr);
+
+	db->stopped = 1;
+
+	spin_unlock_irqrestore(&s->lock, flags);
+}
+
+static void
+stop_adc(struct au1550_state *s)
+{
+	struct dmabuf  *db = &s->dma_adc;
+	unsigned long   flags;
+	u32		stat;
+
+	if (db->stopped)
+		return;
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	au_writel(PSC_AC97PCR_RP, PSC_AC97PCR);
+	au_sync();
+
+	/* Wait for Receive Busy to show disabled.
+	*/
+	do {
+		stat = au_readl(PSC_AC97STAT);
+		au_sync();
+	} while ((stat & PSC_AC97STAT_RB) != 0);
+
+	au1xxx_dbdma_reset(db->dmanr);
+
+	db->stopped = 1;
+
+	spin_unlock_irqrestore(&s->lock, flags);
+}
+
+
+static void
+set_xmit_slots(int num_channels)
+{
+	u32	ac97_config, stat;
+
+	ac97_config = au_readl(PSC_AC97CFG);
+	au_sync();
+	ac97_config &= ~(PSC_AC97CFG_TXSLOT_MASK | PSC_AC97CFG_DE_ENABLE);
+	au_writel(ac97_config, PSC_AC97CFG);
+	au_sync();
+
+	switch (num_channels) {
+	case 6:		/* stereo with surround and center/LFE,
+			 * slots 3,4,6,7,8,9
+			 */
+		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(6);
+		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(9);
+
+	case 4:		/* stereo with surround, slots 3,4,7,8 */
+		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(7);
+		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(8);
+
+	case 2:		/* stereo, slots 3,4 */
+	case 1:		/* mono */
+		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(3);
+		ac97_config |= PSC_AC97CFG_TXSLOT_ENA(4);
+	}
+
+	au_writel(ac97_config, PSC_AC97CFG);
+	au_sync();
+
+	ac97_config |= PSC_AC97CFG_DE_ENABLE;
+	au_writel(ac97_config, PSC_AC97CFG);
+	au_sync();
+
+	/* Wait for Device ready.
+	*/
+	do {
+		stat = au_readl(PSC_AC97STAT);
+		au_sync();
+	} while ((stat & PSC_AC97STAT_DR) == 0);
+}
+
+static void
+set_recv_slots(int num_channels)
+{
+	u32	ac97_config, stat;
+
+	ac97_config = au_readl(PSC_AC97CFG);
+	au_sync();
+	ac97_config &= ~(PSC_AC97CFG_RXSLOT_MASK | PSC_AC97CFG_DE_ENABLE);
+	au_writel(ac97_config, PSC_AC97CFG);
+	au_sync();
+
+	/* Always enable slots 3 and 4 (stereo). Slot 6 is
+	 * optional Mic ADC, which we don't support yet.
+	 */
+	ac97_config |= PSC_AC97CFG_RXSLOT_ENA(3);
+	ac97_config |= PSC_AC97CFG_RXSLOT_ENA(4);
+
+	au_writel(ac97_config, PSC_AC97CFG);
+	au_sync();
+
+	ac97_config |= PSC_AC97CFG_DE_ENABLE;
+	au_writel(ac97_config, PSC_AC97CFG);
+	au_sync();
+
+	/* Wait for Device ready.
+	*/
+	do {
+		stat = au_readl(PSC_AC97STAT);
+		au_sync();
+	} while ((stat & PSC_AC97STAT_DR) == 0);
+}
+
+/* Hold spinlock for both start_dac() and start_adc() calls */
+static void
+start_dac(struct au1550_state *s)
+{
+	struct dmabuf  *db = &s->dma_dac;
+
+	if (!db->stopped)
+		return;
+
+	set_xmit_slots(db->num_channels);
+	au_writel(PSC_AC97PCR_TC, PSC_AC97PCR);
+	au_sync();
+	au_writel(PSC_AC97PCR_TS, PSC_AC97PCR);
+	au_sync();
+
+	au1xxx_dbdma_start(db->dmanr);
+
+	db->stopped = 0;
+}
+
+static void
+start_adc(struct au1550_state *s)
+{
+	struct dmabuf  *db = &s->dma_adc;
+	int	i;
+
+	if (!db->stopped)
+		return;
+
+	/* Put two buffers on the ring to get things started.
+	*/
+	for (i=0; i<2; i++) {
+		au1xxx_dbdma_put_dest(db->dmanr, virt_to_phys(db->nextIn),
+				db->dma_fragsize, DDMA_FLAGS_IE);
+
+		db->nextIn += db->dma_fragsize;
+		if (db->nextIn >= db->rawbuf + db->dmasize)
+			db->nextIn -= db->dmasize;
+	}
+
+	set_recv_slots(db->num_channels);
+	au1xxx_dbdma_start(db->dmanr);
+	au_writel(PSC_AC97PCR_RC, PSC_AC97PCR);
+	au_sync();
+	au_writel(PSC_AC97PCR_RS, PSC_AC97PCR);
+	au_sync();
+
+	db->stopped = 0;
+}
+
+static int
+prog_dmabuf(struct au1550_state *s, struct dmabuf *db)
+{
+	unsigned user_bytes_per_sec;
+	unsigned        bufs;
+	unsigned        rate = db->sample_rate;
+
+	if (!db->rawbuf) {
+		db->ready = db->mapped = 0;
+		db->buforder = 5;	/* 32 * PAGE_SIZE */
+		db->rawbuf = kmalloc((PAGE_SIZE << db->buforder), GFP_KERNEL);
+		if (!db->rawbuf)
+			return -ENOMEM;
+	}
+
+	db->cnt_factor = 1;
+	if (db->sample_size == 8)
+		db->cnt_factor *= 2;
+	if (db->num_channels == 1)
+		db->cnt_factor *= 2;
+	db->cnt_factor *= db->src_factor;
+
+	db->count = 0;
+	db->dma_qcount = 0;
+	db->nextIn = db->nextOut = db->rawbuf;
+
+	db->user_bytes_per_sample = (db->sample_size>>3) * db->num_channels;
+	db->dma_bytes_per_sample = 2 * ((db->num_channels == 1) ?
+					2 : db->num_channels);
+
+	user_bytes_per_sec = rate * db->user_bytes_per_sample;
+	bufs = PAGE_SIZE << db->buforder;
+	if (db->ossfragshift) {
+		if ((1000 << db->ossfragshift) < user_bytes_per_sec)
+			db->fragshift = ld2(user_bytes_per_sec/1000);
+		else
+			db->fragshift = db->ossfragshift;
+	} else {
+		db->fragshift = ld2(user_bytes_per_sec / 100 /
+				    (db->subdivision ? db->subdivision : 1));
+		if (db->fragshift < 3)
+			db->fragshift = 3;
+	}
+
+	db->fragsize = 1 << db->fragshift;
+	db->dma_fragsize = db->fragsize * db->cnt_factor;
+	db->numfrag = bufs / db->dma_fragsize;
+
+	while (db->numfrag < 4 && db->fragshift > 3) {
+		db->fragshift--;
+		db->fragsize = 1 << db->fragshift;
+		db->dma_fragsize = db->fragsize * db->cnt_factor;
+		db->numfrag = bufs / db->dma_fragsize;
+	}
+
+	if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag)
+		db->numfrag = db->ossmaxfrags;
+
+	db->dmasize = db->dma_fragsize * db->numfrag;
+	memset(db->rawbuf, 0, bufs);
+
+	pr_debug("prog_dmabuf: rate=%d, samplesize=%d, channels=%d\n",
+	    rate, db->sample_size, db->num_channels);
+	pr_debug("prog_dmabuf: fragsize=%d, cnt_factor=%d, dma_fragsize=%d\n",
+	    db->fragsize, db->cnt_factor, db->dma_fragsize);
+	pr_debug("prog_dmabuf: numfrag=%d, dmasize=%d\n", db->numfrag, db->dmasize);
+
+	db->ready = 1;
+	return 0;
+}
+
+static int
+prog_dmabuf_adc(struct au1550_state *s)
+{
+	stop_adc(s);
+	return prog_dmabuf(s, &s->dma_adc);
+
+}
+
+static int
+prog_dmabuf_dac(struct au1550_state *s)
+{
+	stop_dac(s);
+	return prog_dmabuf(s, &s->dma_dac);
+}
+
+
+static void dac_dma_interrupt(int irq, void *dev_id)
+{
+	struct au1550_state *s = (struct au1550_state *) dev_id;
+	struct dmabuf  *db = &s->dma_dac;
+	u32	ac97c_stat;
+
+	spin_lock(&s->lock);
+
+	ac97c_stat = au_readl(PSC_AC97STAT);
+	if (ac97c_stat & (AC97C_XU | AC97C_XO | AC97C_TE))
+		pr_debug("AC97C status = 0x%08x\n", ac97c_stat);
+	db->dma_qcount--;
+
+	if (db->count >= db->fragsize) {
+		if (au1xxx_dbdma_put_source(db->dmanr,
+				virt_to_phys(db->nextOut), db->fragsize,
+				DDMA_FLAGS_IE) == 0) {
+			err("qcount < 2 and no ring room!");
+		}
+		db->nextOut += db->fragsize;
+		if (db->nextOut >= db->rawbuf + db->dmasize)
+			db->nextOut -= db->dmasize;
+		db->count -= db->fragsize;
+		db->total_bytes += db->dma_fragsize;
+		db->dma_qcount++;
+	}
+
+	/* wake up anybody listening */
+	if (waitqueue_active(&db->wait))
+		wake_up(&db->wait);
+
+	spin_unlock(&s->lock);
+}
+
+
+static void adc_dma_interrupt(int irq, void *dev_id)
+{
+	struct	au1550_state *s = (struct au1550_state *)dev_id;
+	struct	dmabuf  *dp = &s->dma_adc;
+	u32	obytes;
+	char	*obuf;
+
+	spin_lock(&s->lock);
+
+	/* Pull the buffer from the dma queue.
+	*/
+	au1xxx_dbdma_get_dest(dp->dmanr, (void *)(&obuf), &obytes);
+
+	if ((dp->count + obytes) > dp->dmasize) {
+		/* Overrun. Stop ADC and log the error
+		*/
+		spin_unlock(&s->lock);
+		stop_adc(s);
+		dp->error++;
+		err("adc overrun");
+		return;
+	}
+
+	/* Put a new empty buffer on the destination DMA.
+	*/
+	au1xxx_dbdma_put_dest(dp->dmanr, virt_to_phys(dp->nextIn),
+			      dp->dma_fragsize, DDMA_FLAGS_IE);
+
+	dp->nextIn += dp->dma_fragsize;
+	if (dp->nextIn >= dp->rawbuf + dp->dmasize)
+		dp->nextIn -= dp->dmasize;
+
+	dp->count += obytes;
+	dp->total_bytes += obytes;
+
+	/* wake up anybody listening
+	*/
+	if (waitqueue_active(&dp->wait))
+		wake_up(&dp->wait);
+
+	spin_unlock(&s->lock);
+}
+
+static loff_t
+au1550_llseek(struct file *file, loff_t offset, int origin)
+{
+	return -ESPIPE;
+}
+
+
+static int
+au1550_open_mixdev(struct inode *inode, struct file *file)
+{
+	mutex_lock(&au1550_ac97_mutex);
+	file->private_data = &au1550_state;
+	mutex_unlock(&au1550_ac97_mutex);
+	return 0;
+}
+
+static int
+au1550_release_mixdev(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static int
+mixdev_ioctl(struct ac97_codec *codec, unsigned int cmd,
+                        unsigned long arg)
+{
+	return codec->mixer_ioctl(codec, cmd, arg);
+}
+
+static long
+au1550_ioctl_mixdev(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct au1550_state *s = file->private_data;
+	struct ac97_codec *codec = s->codec;
+	int ret;
+
+	mutex_lock(&au1550_ac97_mutex);
+	ret = mixdev_ioctl(codec, cmd, arg);
+	mutex_unlock(&au1550_ac97_mutex);
+
+	return ret;
+}
+
+static /*const */ struct file_operations au1550_mixer_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= au1550_llseek,
+	.unlocked_ioctl	= au1550_ioctl_mixdev,
+	.open		= au1550_open_mixdev,
+	.release	= au1550_release_mixdev,
+};
+
+static int
+drain_dac(struct au1550_state *s, int nonblock)
+{
+	unsigned long   flags;
+	int             count, tmo;
+
+	if (s->dma_dac.mapped || !s->dma_dac.ready || s->dma_dac.stopped)
+		return 0;
+
+	for (;;) {
+		spin_lock_irqsave(&s->lock, flags);
+		count = s->dma_dac.count;
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (count <= s->dma_dac.fragsize)
+			break;
+		if (signal_pending(current))
+			break;
+		if (nonblock)
+			return -EBUSY;
+		tmo = 1000 * count / (s->no_vra ?
+				      48000 : s->dma_dac.sample_rate);
+		tmo /= s->dma_dac.dma_bytes_per_sample;
+		au1550_delay(tmo);
+	}
+	if (signal_pending(current))
+		return -ERESTARTSYS;
+	return 0;
+}
+
+static inline u8 S16_TO_U8(s16 ch)
+{
+	return (u8) (ch >> 8) + 0x80;
+}
+static inline s16 U8_TO_S16(u8 ch)
+{
+	return (s16) (ch - 0x80) << 8;
+}
+
+/*
+ * Translates user samples to dma buffer suitable for AC'97 DAC data:
+ *     If mono, copy left channel to right channel in dma buffer.
+ *     If 8 bit samples, cvt to 16-bit before writing to dma buffer.
+ *     If interpolating (no VRA), duplicate every audio frame src_factor times.
+ */
+static int
+translate_from_user(struct dmabuf *db, char* dmabuf, char* userbuf,
+							       int dmacount)
+{
+	int             sample, i;
+	int             interp_bytes_per_sample;
+	int             num_samples;
+	int             mono = (db->num_channels == 1);
+	char            usersample[12];
+	s16             ch, dmasample[6];
+
+	if (db->sample_size == 16 && !mono && db->src_factor == 1) {
+		/* no translation necessary, just copy
+		*/
+		if (copy_from_user(dmabuf, userbuf, dmacount))
+			return -EFAULT;
+		return dmacount;
+	}
+
+	interp_bytes_per_sample = db->dma_bytes_per_sample * db->src_factor;
+	num_samples = dmacount / interp_bytes_per_sample;
+
+	for (sample = 0; sample < num_samples; sample++) {
+		if (copy_from_user(usersample, userbuf,
+				   db->user_bytes_per_sample)) {
+			return -EFAULT;
+		}
+
+		for (i = 0; i < db->num_channels; i++) {
+			if (db->sample_size == 8)
+				ch = U8_TO_S16(usersample[i]);
+			else
+				ch = *((s16 *) (&usersample[i * 2]));
+			dmasample[i] = ch;
+			if (mono)
+				dmasample[i + 1] = ch;	/* right channel */
+		}
+
+		/* duplicate every audio frame src_factor times
+		*/
+		for (i = 0; i < db->src_factor; i++)
+			memcpy(dmabuf, dmasample, db->dma_bytes_per_sample);
+
+		userbuf += db->user_bytes_per_sample;
+		dmabuf += interp_bytes_per_sample;
+	}
+
+	return num_samples * interp_bytes_per_sample;
+}
+
+/*
+ * Translates AC'97 ADC samples to user buffer:
+ *     If mono, send only left channel to user buffer.
+ *     If 8 bit samples, cvt from 16 to 8 bit before writing to user buffer.
+ *     If decimating (no VRA), skip over src_factor audio frames.
+ */
+static int
+translate_to_user(struct dmabuf *db, char* userbuf, char* dmabuf,
+							     int dmacount)
+{
+	int             sample, i;
+	int             interp_bytes_per_sample;
+	int             num_samples;
+	int             mono = (db->num_channels == 1);
+	char            usersample[12];
+
+	if (db->sample_size == 16 && !mono && db->src_factor == 1) {
+		/* no translation necessary, just copy
+		*/
+		if (copy_to_user(userbuf, dmabuf, dmacount))
+			return -EFAULT;
+		return dmacount;
+	}
+
+	interp_bytes_per_sample = db->dma_bytes_per_sample * db->src_factor;
+	num_samples = dmacount / interp_bytes_per_sample;
+
+	for (sample = 0; sample < num_samples; sample++) {
+		for (i = 0; i < db->num_channels; i++) {
+			if (db->sample_size == 8)
+				usersample[i] =
+					S16_TO_U8(*((s16 *) (&dmabuf[i * 2])));
+			else
+				*((s16 *) (&usersample[i * 2])) =
+					*((s16 *) (&dmabuf[i * 2]));
+		}
+
+		if (copy_to_user(userbuf, usersample,
+				 db->user_bytes_per_sample)) {
+			return -EFAULT;
+		}
+
+		userbuf += db->user_bytes_per_sample;
+		dmabuf += interp_bytes_per_sample;
+	}
+
+	return num_samples * interp_bytes_per_sample;
+}
+
+/*
+ * Copy audio data to/from user buffer from/to dma buffer, taking care
+ * that we wrap when reading/writing the dma buffer. Returns actual byte
+ * count written to or read from the dma buffer.
+ */
+static int
+copy_dmabuf_user(struct dmabuf *db, char* userbuf, int count, int to_user)
+{
+	char           *bufptr = to_user ? db->nextOut : db->nextIn;
+	char           *bufend = db->rawbuf + db->dmasize;
+	int             cnt, ret;
+
+	if (bufptr + count > bufend) {
+		int             partial = (int) (bufend - bufptr);
+		if (to_user) {
+			if ((cnt = translate_to_user(db, userbuf,
+						     bufptr, partial)) < 0)
+				return cnt;
+			ret = cnt;
+			if ((cnt = translate_to_user(db, userbuf + partial,
+						     db->rawbuf,
+						     count - partial)) < 0)
+				return cnt;
+			ret += cnt;
+		} else {
+			if ((cnt = translate_from_user(db, bufptr, userbuf,
+						       partial)) < 0)
+				return cnt;
+			ret = cnt;
+			if ((cnt = translate_from_user(db, db->rawbuf,
+						       userbuf + partial,
+						       count - partial)) < 0)
+				return cnt;
+			ret += cnt;
+		}
+	} else {
+		if (to_user)
+			ret = translate_to_user(db, userbuf, bufptr, count);
+		else
+			ret = translate_from_user(db, bufptr, userbuf, count);
+	}
+
+	return ret;
+}
+
+
+static ssize_t
+au1550_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
+{
+	struct au1550_state *s = file->private_data;
+	struct dmabuf  *db = &s->dma_adc;
+	DECLARE_WAITQUEUE(wait, current);
+	ssize_t         ret;
+	unsigned long   flags;
+	int             cnt, usercnt, avail;
+
+	if (db->mapped)
+		return -ENXIO;
+	if (!access_ok(VERIFY_WRITE, buffer, count))
+		return -EFAULT;
+	ret = 0;
+
+	count *= db->cnt_factor;
+
+	mutex_lock(&s->sem);
+	add_wait_queue(&db->wait, &wait);
+
+	while (count > 0) {
+		/* wait for samples in ADC dma buffer
+		*/
+		do {
+			spin_lock_irqsave(&s->lock, flags);
+			if (db->stopped)
+				start_adc(s);
+			avail = db->count;
+			if (avail <= 0)
+				__set_current_state(TASK_INTERRUPTIBLE);
+			spin_unlock_irqrestore(&s->lock, flags);
+			if (avail <= 0) {
+				if (file->f_flags & O_NONBLOCK) {
+					if (!ret)
+						ret = -EAGAIN;
+					goto out;
+				}
+				mutex_unlock(&s->sem);
+				schedule();
+				if (signal_pending(current)) {
+					if (!ret)
+						ret = -ERESTARTSYS;
+					goto out2;
+				}
+				mutex_lock(&s->sem);
+			}
+		} while (avail <= 0);
+
+		/* copy from nextOut to user
+		*/
+		if ((cnt = copy_dmabuf_user(db, buffer,
+					    count > avail ?
+					    avail : count, 1)) < 0) {
+			if (!ret)
+				ret = -EFAULT;
+			goto out;
+		}
+
+		spin_lock_irqsave(&s->lock, flags);
+		db->count -= cnt;
+		db->nextOut += cnt;
+		if (db->nextOut >= db->rawbuf + db->dmasize)
+			db->nextOut -= db->dmasize;
+		spin_unlock_irqrestore(&s->lock, flags);
+
+		count -= cnt;
+		usercnt = cnt / db->cnt_factor;
+		buffer += usercnt;
+		ret += usercnt;
+	}			/* while (count > 0) */
+
+out:
+	mutex_unlock(&s->sem);
+out2:
+	remove_wait_queue(&db->wait, &wait);
+	set_current_state(TASK_RUNNING);
+	return ret;
+}
+
+static ssize_t
+au1550_write(struct file *file, const char *buffer, size_t count, loff_t * ppos)
+{
+	struct au1550_state *s = file->private_data;
+	struct dmabuf  *db = &s->dma_dac;
+	DECLARE_WAITQUEUE(wait, current);
+	ssize_t         ret = 0;
+	unsigned long   flags;
+	int             cnt, usercnt, avail;
+
+	pr_debug("write: count=%d\n", count);
+
+	if (db->mapped)
+		return -ENXIO;
+	if (!access_ok(VERIFY_READ, buffer, count))
+		return -EFAULT;
+
+	count *= db->cnt_factor;
+
+	mutex_lock(&s->sem);
+	add_wait_queue(&db->wait, &wait);
+
+	while (count > 0) {
+		/* wait for space in playback buffer
+		*/
+		do {
+			spin_lock_irqsave(&s->lock, flags);
+			avail = (int) db->dmasize - db->count;
+			if (avail <= 0)
+				__set_current_state(TASK_INTERRUPTIBLE);
+			spin_unlock_irqrestore(&s->lock, flags);
+			if (avail <= 0) {
+				if (file->f_flags & O_NONBLOCK) {
+					if (!ret)
+						ret = -EAGAIN;
+					goto out;
+				}
+				mutex_unlock(&s->sem);
+				schedule();
+				if (signal_pending(current)) {
+					if (!ret)
+						ret = -ERESTARTSYS;
+					goto out2;
+				}
+				mutex_lock(&s->sem);
+			}
+		} while (avail <= 0);
+
+		/* copy from user to nextIn
+		*/
+		if ((cnt = copy_dmabuf_user(db, (char *) buffer,
+					    count > avail ?
+					    avail : count, 0)) < 0) {
+			if (!ret)
+				ret = -EFAULT;
+			goto out;
+		}
+
+		spin_lock_irqsave(&s->lock, flags);
+		db->count += cnt;
+		db->nextIn += cnt;
+		if (db->nextIn >= db->rawbuf + db->dmasize)
+			db->nextIn -= db->dmasize;
+
+		/* If the data is available, we want to keep two buffers
+		 * on the dma queue.  If the queue count reaches zero,
+		 * we know the dma has stopped.
+		 */
+		while ((db->dma_qcount < 2) && (db->count >= db->fragsize)) {
+			if (au1xxx_dbdma_put_source(db->dmanr,
+				virt_to_phys(db->nextOut), db->fragsize,
+				DDMA_FLAGS_IE) == 0) {
+				err("qcount < 2 and no ring room!");
+			}
+			db->nextOut += db->fragsize;
+			if (db->nextOut >= db->rawbuf + db->dmasize)
+				db->nextOut -= db->dmasize;
+			db->total_bytes += db->dma_fragsize;
+			if (db->dma_qcount == 0)
+				start_dac(s);
+			db->dma_qcount++;
+		}
+		spin_unlock_irqrestore(&s->lock, flags);
+
+		count -= cnt;
+		usercnt = cnt / db->cnt_factor;
+		buffer += usercnt;
+		ret += usercnt;
+	}			/* while (count > 0) */
+
+out:
+	mutex_unlock(&s->sem);
+out2:
+	remove_wait_queue(&db->wait, &wait);
+	set_current_state(TASK_RUNNING);
+	return ret;
+}
+
+
+/* No kernel lock - we have our own spinlock */
+static unsigned int
+au1550_poll(struct file *file, struct poll_table_struct *wait)
+{
+	struct au1550_state *s = file->private_data;
+	unsigned long   flags;
+	unsigned int    mask = 0;
+
+	if (file->f_mode & FMODE_WRITE) {
+		if (!s->dma_dac.ready)
+			return 0;
+		poll_wait(file, &s->dma_dac.wait, wait);
+	}
+	if (file->f_mode & FMODE_READ) {
+		if (!s->dma_adc.ready)
+			return 0;
+		poll_wait(file, &s->dma_adc.wait, wait);
+	}
+
+	spin_lock_irqsave(&s->lock, flags);
+
+	if (file->f_mode & FMODE_READ) {
+		if (s->dma_adc.count >= (signed)s->dma_adc.dma_fragsize)
+			mask |= POLLIN | POLLRDNORM;
+	}
+	if (file->f_mode & FMODE_WRITE) {
+		if (s->dma_dac.mapped) {
+			if (s->dma_dac.count >=
+			    (signed)s->dma_dac.dma_fragsize)
+				mask |= POLLOUT | POLLWRNORM;
+		} else {
+			if ((signed) s->dma_dac.dmasize >=
+			    s->dma_dac.count + (signed)s->dma_dac.dma_fragsize)
+				mask |= POLLOUT | POLLWRNORM;
+		}
+	}
+	spin_unlock_irqrestore(&s->lock, flags);
+	return mask;
+}
+
+static int
+au1550_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct au1550_state *s = file->private_data;
+	struct dmabuf  *db;
+	unsigned long   size;
+	int ret = 0;
+
+	mutex_lock(&au1550_ac97_mutex);
+	mutex_lock(&s->sem);
+	if (vma->vm_flags & VM_WRITE)
+		db = &s->dma_dac;
+	else if (vma->vm_flags & VM_READ)
+		db = &s->dma_adc;
+	else {
+		ret = -EINVAL;
+		goto out;
+	}
+	if (vma->vm_pgoff != 0) {
+		ret = -EINVAL;
+		goto out;
+	}
+	size = vma->vm_end - vma->vm_start;
+	if (size > (PAGE_SIZE << db->buforder)) {
+		ret = -EINVAL;
+		goto out;
+	}
+	if (remap_pfn_range(vma, vma->vm_start, page_to_pfn(virt_to_page(db->rawbuf)),
+			     size, vma->vm_page_prot)) {
+		ret = -EAGAIN;
+		goto out;
+	}
+	vma->vm_flags &= ~VM_IO;
+	db->mapped = 1;
+out:
+	mutex_unlock(&s->sem);
+	mutex_unlock(&au1550_ac97_mutex);
+	return ret;
+}
+
+#ifdef DEBUG
+static struct ioctl_str_t {
+	unsigned int    cmd;
+	const char     *str;
+} ioctl_str[] = {
+	{SNDCTL_DSP_RESET, "SNDCTL_DSP_RESET"},
+	{SNDCTL_DSP_SYNC, "SNDCTL_DSP_SYNC"},
+	{SNDCTL_DSP_SPEED, "SNDCTL_DSP_SPEED"},
+	{SNDCTL_DSP_STEREO, "SNDCTL_DSP_STEREO"},
+	{SNDCTL_DSP_GETBLKSIZE, "SNDCTL_DSP_GETBLKSIZE"},
+	{SNDCTL_DSP_SAMPLESIZE, "SNDCTL_DSP_SAMPLESIZE"},
+	{SNDCTL_DSP_CHANNELS, "SNDCTL_DSP_CHANNELS"},
+	{SOUND_PCM_WRITE_CHANNELS, "SOUND_PCM_WRITE_CHANNELS"},
+	{SOUND_PCM_WRITE_FILTER, "SOUND_PCM_WRITE_FILTER"},
+	{SNDCTL_DSP_POST, "SNDCTL_DSP_POST"},
+	{SNDCTL_DSP_SUBDIVIDE, "SNDCTL_DSP_SUBDIVIDE"},
+	{SNDCTL_DSP_SETFRAGMENT, "SNDCTL_DSP_SETFRAGMENT"},
+	{SNDCTL_DSP_GETFMTS, "SNDCTL_DSP_GETFMTS"},
+	{SNDCTL_DSP_SETFMT, "SNDCTL_DSP_SETFMT"},
+	{SNDCTL_DSP_GETOSPACE, "SNDCTL_DSP_GETOSPACE"},
+	{SNDCTL_DSP_GETISPACE, "SNDCTL_DSP_GETISPACE"},
+	{SNDCTL_DSP_NONBLOCK, "SNDCTL_DSP_NONBLOCK"},
+	{SNDCTL_DSP_GETCAPS, "SNDCTL_DSP_GETCAPS"},
+	{SNDCTL_DSP_GETTRIGGER, "SNDCTL_DSP_GETTRIGGER"},
+	{SNDCTL_DSP_SETTRIGGER, "SNDCTL_DSP_SETTRIGGER"},
+	{SNDCTL_DSP_GETIPTR, "SNDCTL_DSP_GETIPTR"},
+	{SNDCTL_DSP_GETOPTR, "SNDCTL_DSP_GETOPTR"},
+	{SNDCTL_DSP_MAPINBUF, "SNDCTL_DSP_MAPINBUF"},
+	{SNDCTL_DSP_MAPOUTBUF, "SNDCTL_DSP_MAPOUTBUF"},
+	{SNDCTL_DSP_SETSYNCRO, "SNDCTL_DSP_SETSYNCRO"},
+	{SNDCTL_DSP_SETDUPLEX, "SNDCTL_DSP_SETDUPLEX"},
+	{SNDCTL_DSP_GETODELAY, "SNDCTL_DSP_GETODELAY"},
+	{SNDCTL_DSP_GETCHANNELMASK, "SNDCTL_DSP_GETCHANNELMASK"},
+	{SNDCTL_DSP_BIND_CHANNEL, "SNDCTL_DSP_BIND_CHANNEL"},
+	{OSS_GETVERSION, "OSS_GETVERSION"},
+	{SOUND_PCM_READ_RATE, "SOUND_PCM_READ_RATE"},
+	{SOUND_PCM_READ_CHANNELS, "SOUND_PCM_READ_CHANNELS"},
+	{SOUND_PCM_READ_BITS, "SOUND_PCM_READ_BITS"},
+	{SOUND_PCM_READ_FILTER, "SOUND_PCM_READ_FILTER"}
+};
+#endif
+
+static int
+dma_count_done(struct dmabuf *db)
+{
+	if (db->stopped)
+		return 0;
+
+	return db->dma_fragsize - au1xxx_get_dma_residue(db->dmanr);
+}
+
+
+static int
+au1550_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct au1550_state *s = file->private_data;
+	unsigned long   flags;
+	audio_buf_info  abinfo;
+	count_info      cinfo;
+	int             count;
+	int             val, mapped, ret, diff;
+
+	mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac.mapped) ||
+		((file->f_mode & FMODE_READ) && s->dma_adc.mapped);
+
+#ifdef DEBUG
+	for (count = 0; count < ARRAY_SIZE(ioctl_str); count++) {
+		if (ioctl_str[count].cmd == cmd)
+			break;
+	}
+	if (count < ARRAY_SIZE(ioctl_str))
+		pr_debug("ioctl %s, arg=0x%lxn", ioctl_str[count].str, arg);
+	else
+		pr_debug("ioctl 0x%x unknown, arg=0x%lx\n", cmd, arg);
+#endif
+
+	switch (cmd) {
+	case OSS_GETVERSION:
+		return put_user(SOUND_VERSION, (int *) arg);
+
+	case SNDCTL_DSP_SYNC:
+		if (file->f_mode & FMODE_WRITE)
+			return drain_dac(s, file->f_flags & O_NONBLOCK);
+		return 0;
+
+	case SNDCTL_DSP_SETDUPLEX:
+		return 0;
+
+	case SNDCTL_DSP_GETCAPS:
+		return put_user(DSP_CAP_DUPLEX | DSP_CAP_REALTIME |
+				DSP_CAP_TRIGGER | DSP_CAP_MMAP, (int *)arg);
+
+	case SNDCTL_DSP_RESET:
+		if (file->f_mode & FMODE_WRITE) {
+			stop_dac(s);
+			synchronize_irq();
+			s->dma_dac.count = s->dma_dac.total_bytes = 0;
+			s->dma_dac.nextIn = s->dma_dac.nextOut =
+				s->dma_dac.rawbuf;
+		}
+		if (file->f_mode & FMODE_READ) {
+			stop_adc(s);
+			synchronize_irq();
+			s->dma_adc.count = s->dma_adc.total_bytes = 0;
+			s->dma_adc.nextIn = s->dma_adc.nextOut =
+				s->dma_adc.rawbuf;
+		}
+		return 0;
+
+	case SNDCTL_DSP_SPEED:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		if (val >= 0) {
+			if (file->f_mode & FMODE_READ) {
+				stop_adc(s);
+				set_adc_rate(s, val);
+			}
+			if (file->f_mode & FMODE_WRITE) {
+				stop_dac(s);
+				set_dac_rate(s, val);
+			}
+			if (s->open_mode & FMODE_READ)
+				if ((ret = prog_dmabuf_adc(s)))
+					return ret;
+			if (s->open_mode & FMODE_WRITE)
+				if ((ret = prog_dmabuf_dac(s)))
+					return ret;
+		}
+		return put_user((file->f_mode & FMODE_READ) ?
+				s->dma_adc.sample_rate :
+				s->dma_dac.sample_rate,
+				(int *)arg);
+
+	case SNDCTL_DSP_STEREO:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		if (file->f_mode & FMODE_READ) {
+			stop_adc(s);
+			s->dma_adc.num_channels = val ? 2 : 1;
+			if ((ret = prog_dmabuf_adc(s)))
+				return ret;
+		}
+		if (file->f_mode & FMODE_WRITE) {
+			stop_dac(s);
+			s->dma_dac.num_channels = val ? 2 : 1;
+			if (s->codec_ext_caps & AC97_EXT_DACS) {
+				/* disable surround and center/lfe in AC'97
+				*/
+				u16 ext_stat = rdcodec(s->codec,
+						       AC97_EXTENDED_STATUS);
+				wrcodec(s->codec, AC97_EXTENDED_STATUS,
+					ext_stat | (AC97_EXTSTAT_PRI |
+						    AC97_EXTSTAT_PRJ |
+						    AC97_EXTSTAT_PRK));
+			}
+			if ((ret = prog_dmabuf_dac(s)))
+				return ret;
+		}
+		return 0;
+
+	case SNDCTL_DSP_CHANNELS:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		if (val != 0) {
+			if (file->f_mode & FMODE_READ) {
+				if (val < 0 || val > 2)
+					return -EINVAL;
+				stop_adc(s);
+				s->dma_adc.num_channels = val;
+				if ((ret = prog_dmabuf_adc(s)))
+					return ret;
+			}
+			if (file->f_mode & FMODE_WRITE) {
+				switch (val) {
+				case 1:
+				case 2:
+					break;
+				case 3:
+				case 5:
+					return -EINVAL;
+				case 4:
+					if (!(s->codec_ext_caps &
+					      AC97_EXTID_SDAC))
+						return -EINVAL;
+					break;
+				case 6:
+					if ((s->codec_ext_caps &
+					     AC97_EXT_DACS) != AC97_EXT_DACS)
+						return -EINVAL;
+					break;
+				default:
+					return -EINVAL;
+				}
+
+				stop_dac(s);
+				if (val <= 2 &&
+				    (s->codec_ext_caps & AC97_EXT_DACS)) {
+					/* disable surround and center/lfe
+					 * channels in AC'97
+					 */
+					u16             ext_stat =
+						rdcodec(s->codec,
+							AC97_EXTENDED_STATUS);
+					wrcodec(s->codec,
+						AC97_EXTENDED_STATUS,
+						ext_stat | (AC97_EXTSTAT_PRI |
+							    AC97_EXTSTAT_PRJ |
+							    AC97_EXTSTAT_PRK));
+				} else if (val >= 4) {
+					/* enable surround, center/lfe
+					 * channels in AC'97
+					 */
+					u16             ext_stat =
+						rdcodec(s->codec,
+							AC97_EXTENDED_STATUS);
+					ext_stat &= ~AC97_EXTSTAT_PRJ;
+					if (val == 6)
+						ext_stat &=
+							~(AC97_EXTSTAT_PRI |
+							  AC97_EXTSTAT_PRK);
+					wrcodec(s->codec,
+						AC97_EXTENDED_STATUS,
+						ext_stat);
+				}
+
+				s->dma_dac.num_channels = val;
+				if ((ret = prog_dmabuf_dac(s)))
+					return ret;
+			}
+		}
+		return put_user(val, (int *) arg);
+
+	case SNDCTL_DSP_GETFMTS:	/* Returns a mask */
+		return put_user(AFMT_S16_LE | AFMT_U8, (int *) arg);
+
+	case SNDCTL_DSP_SETFMT:	/* Selects ONE fmt */
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		if (val != AFMT_QUERY) {
+			if (file->f_mode & FMODE_READ) {
+				stop_adc(s);
+				if (val == AFMT_S16_LE)
+					s->dma_adc.sample_size = 16;
+				else {
+					val = AFMT_U8;
+					s->dma_adc.sample_size = 8;
+				}
+				if ((ret = prog_dmabuf_adc(s)))
+					return ret;
+			}
+			if (file->f_mode & FMODE_WRITE) {
+				stop_dac(s);
+				if (val == AFMT_S16_LE)
+					s->dma_dac.sample_size = 16;
+				else {
+					val = AFMT_U8;
+					s->dma_dac.sample_size = 8;
+				}
+				if ((ret = prog_dmabuf_dac(s)))
+					return ret;
+			}
+		} else {
+			if (file->f_mode & FMODE_READ)
+				val = (s->dma_adc.sample_size == 16) ?
+					AFMT_S16_LE : AFMT_U8;
+			else
+				val = (s->dma_dac.sample_size == 16) ?
+					AFMT_S16_LE : AFMT_U8;
+		}
+		return put_user(val, (int *) arg);
+
+	case SNDCTL_DSP_POST:
+		return 0;
+
+	case SNDCTL_DSP_GETTRIGGER:
+		val = 0;
+		spin_lock_irqsave(&s->lock, flags);
+		if (file->f_mode & FMODE_READ && !s->dma_adc.stopped)
+			val |= PCM_ENABLE_INPUT;
+		if (file->f_mode & FMODE_WRITE && !s->dma_dac.stopped)
+			val |= PCM_ENABLE_OUTPUT;
+		spin_unlock_irqrestore(&s->lock, flags);
+		return put_user(val, (int *) arg);
+
+	case SNDCTL_DSP_SETTRIGGER:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		if (file->f_mode & FMODE_READ) {
+			if (val & PCM_ENABLE_INPUT) {
+				spin_lock_irqsave(&s->lock, flags);
+				start_adc(s);
+				spin_unlock_irqrestore(&s->lock, flags);
+			} else
+				stop_adc(s);
+		}
+		if (file->f_mode & FMODE_WRITE) {
+			if (val & PCM_ENABLE_OUTPUT) {
+				spin_lock_irqsave(&s->lock, flags);
+				start_dac(s);
+				spin_unlock_irqrestore(&s->lock, flags);
+			} else
+				stop_dac(s);
+		}
+		return 0;
+
+	case SNDCTL_DSP_GETOSPACE:
+		if (!(file->f_mode & FMODE_WRITE))
+			return -EINVAL;
+		abinfo.fragsize = s->dma_dac.fragsize;
+		spin_lock_irqsave(&s->lock, flags);
+		count = s->dma_dac.count;
+		count -= dma_count_done(&s->dma_dac);
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (count < 0)
+			count = 0;
+		abinfo.bytes = (s->dma_dac.dmasize - count) /
+			s->dma_dac.cnt_factor;
+		abinfo.fragstotal = s->dma_dac.numfrag;
+		abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift;
+		pr_debug("ioctl SNDCTL_DSP_GETOSPACE: bytes=%d, fragments=%d\n", abinfo.bytes, abinfo.fragments);
+		return copy_to_user((void *) arg, &abinfo,
+				    sizeof(abinfo)) ? -EFAULT : 0;
+
+	case SNDCTL_DSP_GETISPACE:
+		if (!(file->f_mode & FMODE_READ))
+			return -EINVAL;
+		abinfo.fragsize = s->dma_adc.fragsize;
+		spin_lock_irqsave(&s->lock, flags);
+		count = s->dma_adc.count;
+		count += dma_count_done(&s->dma_adc);
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (count < 0)
+			count = 0;
+		abinfo.bytes = count / s->dma_adc.cnt_factor;
+		abinfo.fragstotal = s->dma_adc.numfrag;
+		abinfo.fragments = abinfo.bytes >> s->dma_adc.fragshift;
+		return copy_to_user((void *) arg, &abinfo,
+				    sizeof(abinfo)) ? -EFAULT : 0;
+
+	case SNDCTL_DSP_NONBLOCK:
+		spin_lock(&file->f_lock);
+		file->f_flags |= O_NONBLOCK;
+		spin_unlock(&file->f_lock);
+		return 0;
+
+	case SNDCTL_DSP_GETODELAY:
+		if (!(file->f_mode & FMODE_WRITE))
+			return -EINVAL;
+		spin_lock_irqsave(&s->lock, flags);
+		count = s->dma_dac.count;
+		count -= dma_count_done(&s->dma_dac);
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (count < 0)
+			count = 0;
+		count /= s->dma_dac.cnt_factor;
+		return put_user(count, (int *) arg);
+
+	case SNDCTL_DSP_GETIPTR:
+		if (!(file->f_mode & FMODE_READ))
+			return -EINVAL;
+		spin_lock_irqsave(&s->lock, flags);
+		cinfo.bytes = s->dma_adc.total_bytes;
+		count = s->dma_adc.count;
+		if (!s->dma_adc.stopped) {
+			diff = dma_count_done(&s->dma_adc);
+			count += diff;
+			cinfo.bytes += diff;
+			cinfo.ptr =  virt_to_phys(s->dma_adc.nextIn) + diff -
+				virt_to_phys(s->dma_adc.rawbuf);
+		} else
+			cinfo.ptr = virt_to_phys(s->dma_adc.nextIn) -
+				virt_to_phys(s->dma_adc.rawbuf);
+		if (s->dma_adc.mapped)
+			s->dma_adc.count &= (s->dma_adc.dma_fragsize-1);
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (count < 0)
+			count = 0;
+		cinfo.blocks = count >> s->dma_adc.fragshift;
+		return copy_to_user((void *) arg, &cinfo, sizeof(cinfo));
+
+	case SNDCTL_DSP_GETOPTR:
+		if (!(file->f_mode & FMODE_READ))
+			return -EINVAL;
+		spin_lock_irqsave(&s->lock, flags);
+		cinfo.bytes = s->dma_dac.total_bytes;
+		count = s->dma_dac.count;
+		if (!s->dma_dac.stopped) {
+			diff = dma_count_done(&s->dma_dac);
+			count -= diff;
+			cinfo.bytes += diff;
+			cinfo.ptr = virt_to_phys(s->dma_dac.nextOut) + diff -
+				virt_to_phys(s->dma_dac.rawbuf);
+		} else
+			cinfo.ptr = virt_to_phys(s->dma_dac.nextOut) -
+				virt_to_phys(s->dma_dac.rawbuf);
+		if (s->dma_dac.mapped)
+			s->dma_dac.count &= (s->dma_dac.dma_fragsize-1);
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (count < 0)
+			count = 0;
+		cinfo.blocks = count >> s->dma_dac.fragshift;
+		return copy_to_user((void *) arg, &cinfo, sizeof(cinfo));
+
+	case SNDCTL_DSP_GETBLKSIZE:
+		if (file->f_mode & FMODE_WRITE)
+			return put_user(s->dma_dac.fragsize, (int *) arg);
+		else
+			return put_user(s->dma_adc.fragsize, (int *) arg);
+
+	case SNDCTL_DSP_SETFRAGMENT:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		if (file->f_mode & FMODE_READ) {
+			stop_adc(s);
+			s->dma_adc.ossfragshift = val & 0xffff;
+			s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff;
+			if (s->dma_adc.ossfragshift < 4)
+				s->dma_adc.ossfragshift = 4;
+			if (s->dma_adc.ossfragshift > 15)
+				s->dma_adc.ossfragshift = 15;
+			if (s->dma_adc.ossmaxfrags < 4)
+				s->dma_adc.ossmaxfrags = 4;
+			if ((ret = prog_dmabuf_adc(s)))
+				return ret;
+		}
+		if (file->f_mode & FMODE_WRITE) {
+			stop_dac(s);
+			s->dma_dac.ossfragshift = val & 0xffff;
+			s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff;
+			if (s->dma_dac.ossfragshift < 4)
+				s->dma_dac.ossfragshift = 4;
+			if (s->dma_dac.ossfragshift > 15)
+				s->dma_dac.ossfragshift = 15;
+			if (s->dma_dac.ossmaxfrags < 4)
+				s->dma_dac.ossmaxfrags = 4;
+			if ((ret = prog_dmabuf_dac(s)))
+				return ret;
+		}
+		return 0;
+
+	case SNDCTL_DSP_SUBDIVIDE:
+		if ((file->f_mode & FMODE_READ && s->dma_adc.subdivision) ||
+		    (file->f_mode & FMODE_WRITE && s->dma_dac.subdivision))
+			return -EINVAL;
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		if (val != 1 && val != 2 && val != 4)
+			return -EINVAL;
+		if (file->f_mode & FMODE_READ) {
+			stop_adc(s);
+			s->dma_adc.subdivision = val;
+			if ((ret = prog_dmabuf_adc(s)))
+				return ret;
+		}
+		if (file->f_mode & FMODE_WRITE) {
+			stop_dac(s);
+			s->dma_dac.subdivision = val;
+			if ((ret = prog_dmabuf_dac(s)))
+				return ret;
+		}
+		return 0;
+
+	case SOUND_PCM_READ_RATE:
+		return put_user((file->f_mode & FMODE_READ) ?
+				s->dma_adc.sample_rate :
+				s->dma_dac.sample_rate,
+				(int *)arg);
+
+	case SOUND_PCM_READ_CHANNELS:
+		if (file->f_mode & FMODE_READ)
+			return put_user(s->dma_adc.num_channels, (int *)arg);
+		else
+			return put_user(s->dma_dac.num_channels, (int *)arg);
+
+	case SOUND_PCM_READ_BITS:
+		if (file->f_mode & FMODE_READ)
+			return put_user(s->dma_adc.sample_size, (int *)arg);
+		else
+			return put_user(s->dma_dac.sample_size, (int *)arg);
+
+	case SOUND_PCM_WRITE_FILTER:
+	case SNDCTL_DSP_SETSYNCRO:
+	case SOUND_PCM_READ_FILTER:
+		return -EINVAL;
+	}
+
+	return mixdev_ioctl(s->codec, cmd, arg);
+}
+
+static long
+au1550_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int ret;
+
+	mutex_lock(&au1550_ac97_mutex);
+	ret = au1550_ioctl(file, cmd, arg);
+	mutex_unlock(&au1550_ac97_mutex);
+
+	return ret;
+}
+
+static int
+au1550_open(struct inode *inode, struct file *file)
+{
+	int             minor = MINOR(inode->i_rdev);
+	DECLARE_WAITQUEUE(wait, current);
+	struct au1550_state *s = &au1550_state;
+	int             ret;
+
+#ifdef DEBUG
+	if (file->f_flags & O_NONBLOCK)
+		pr_debug("open: non-blocking\n");
+	else
+		pr_debug("open: blocking\n");
+#endif
+
+	file->private_data = s;
+	mutex_lock(&au1550_ac97_mutex);
+	/* wait for device to become free */
+	mutex_lock(&s->open_mutex);
+	while (s->open_mode & file->f_mode) {
+		ret = -EBUSY;
+		if (file->f_flags & O_NONBLOCK)
+			goto out;
+		add_wait_queue(&s->open_wait, &wait);
+		__set_current_state(TASK_INTERRUPTIBLE);
+		mutex_unlock(&s->open_mutex);
+		schedule();
+		remove_wait_queue(&s->open_wait, &wait);
+		set_current_state(TASK_RUNNING);
+		ret = -ERESTARTSYS;
+		if (signal_pending(current))
+			goto out2;
+		mutex_lock(&s->open_mutex);
+	}
+
+	stop_dac(s);
+	stop_adc(s);
+
+	if (file->f_mode & FMODE_READ) {
+		s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags =
+			s->dma_adc.subdivision = s->dma_adc.total_bytes = 0;
+		s->dma_adc.num_channels = 1;
+		s->dma_adc.sample_size = 8;
+		set_adc_rate(s, 8000);
+		if ((minor & 0xf) == SND_DEV_DSP16)
+			s->dma_adc.sample_size = 16;
+	}
+
+	if (file->f_mode & FMODE_WRITE) {
+		s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags =
+			s->dma_dac.subdivision = s->dma_dac.total_bytes = 0;
+		s->dma_dac.num_channels = 1;
+		s->dma_dac.sample_size = 8;
+		set_dac_rate(s, 8000);
+		if ((minor & 0xf) == SND_DEV_DSP16)
+			s->dma_dac.sample_size = 16;
+	}
+
+	if (file->f_mode & FMODE_READ) {
+		if ((ret = prog_dmabuf_adc(s)))
+			goto out;
+	}
+	if (file->f_mode & FMODE_WRITE) {
+		if ((ret = prog_dmabuf_dac(s)))
+			goto out;
+	}
+
+	s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE);
+	mutex_init(&s->sem);
+	ret = 0;
+out:
+	mutex_unlock(&s->open_mutex);
+out2:
+	mutex_unlock(&au1550_ac97_mutex);
+	return ret;
+}
+
+static int
+au1550_release(struct inode *inode, struct file *file)
+{
+	struct au1550_state *s = file->private_data;
+
+	mutex_lock(&au1550_ac97_mutex);
+
+	if (file->f_mode & FMODE_WRITE) {
+		mutex_unlock(&au1550_ac97_mutex);
+		drain_dac(s, file->f_flags & O_NONBLOCK);
+		mutex_lock(&au1550_ac97_mutex);
+	}
+
+	mutex_lock(&s->open_mutex);
+	if (file->f_mode & FMODE_WRITE) {
+		stop_dac(s);
+		kfree(s->dma_dac.rawbuf);
+		s->dma_dac.rawbuf = NULL;
+	}
+	if (file->f_mode & FMODE_READ) {
+		stop_adc(s);
+		kfree(s->dma_adc.rawbuf);
+		s->dma_adc.rawbuf = NULL;
+	}
+	s->open_mode &= ((~file->f_mode) & (FMODE_READ|FMODE_WRITE));
+	mutex_unlock(&s->open_mutex);
+	wake_up(&s->open_wait);
+	mutex_unlock(&au1550_ac97_mutex);
+	return 0;
+}
+
+static /*const */ struct file_operations au1550_audio_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= au1550_llseek,
+	.read		= au1550_read,
+	.write		= au1550_write,
+	.poll		= au1550_poll,
+	.unlocked_ioctl	= au1550_unlocked_ioctl,
+	.mmap		= au1550_mmap,
+	.open		= au1550_open,
+	.release	= au1550_release,
+};
+
+MODULE_AUTHOR("Advanced Micro Devices (AMD), dan@embeddededge.com");
+MODULE_DESCRIPTION("Au1550 AC97 Audio Driver");
+MODULE_LICENSE("GPL");
+
+
+static int __devinit
+au1550_probe(void)
+{
+	struct au1550_state *s = &au1550_state;
+	int             val;
+
+	memset(s, 0, sizeof(struct au1550_state));
+
+	init_waitqueue_head(&s->dma_adc.wait);
+	init_waitqueue_head(&s->dma_dac.wait);
+	init_waitqueue_head(&s->open_wait);
+	mutex_init(&s->open_mutex);
+	spin_lock_init(&s->lock);
+
+	s->codec = ac97_alloc_codec();
+	if(s->codec == NULL) {
+		err("Out of memory");
+		return -1;
+	}
+	s->codec->private_data = s;
+	s->codec->id = 0;
+	s->codec->codec_read = rdcodec;
+	s->codec->codec_write = wrcodec;
+	s->codec->codec_wait = waitcodec;
+
+	if (!request_mem_region(CPHYSADDR(AC97_PSC_SEL),
+			    0x30, "Au1550 AC97")) {
+		err("AC'97 ports in use");
+	}
+
+	/* Allocate the DMA Channels
+	*/
+	if ((s->dma_dac.dmanr = au1xxx_dbdma_chan_alloc(DBDMA_MEM_CHAN,
+	    DBDMA_AC97_TX_CHAN, dac_dma_interrupt, (void *)s)) == 0) {
+		err("Can't get DAC DMA");
+		goto err_dma1;
+	}
+	au1xxx_dbdma_set_devwidth(s->dma_dac.dmanr, 16);
+	if (au1xxx_dbdma_ring_alloc(s->dma_dac.dmanr,
+					NUM_DBDMA_DESCRIPTORS) == 0) {
+		err("Can't get DAC DMA descriptors");
+		goto err_dma1;
+	}
+
+	if ((s->dma_adc.dmanr = au1xxx_dbdma_chan_alloc(DBDMA_AC97_RX_CHAN,
+	    DBDMA_MEM_CHAN, adc_dma_interrupt, (void *)s)) == 0) {
+		err("Can't get ADC DMA");
+		goto err_dma2;
+	}
+	au1xxx_dbdma_set_devwidth(s->dma_adc.dmanr, 16);
+	if (au1xxx_dbdma_ring_alloc(s->dma_adc.dmanr,
+					NUM_DBDMA_DESCRIPTORS) == 0) {
+		err("Can't get ADC DMA descriptors");
+		goto err_dma2;
+	}
+
+	pr_info("DAC: DMA%d, ADC: DMA%d", DBDMA_AC97_TX_CHAN, DBDMA_AC97_RX_CHAN);
+
+	/* register devices */
+
+	if ((s->dev_audio = register_sound_dsp(&au1550_audio_fops, -1)) < 0)
+		goto err_dev1;
+	if ((s->codec->dev_mixer =
+	     register_sound_mixer(&au1550_mixer_fops, -1)) < 0)
+		goto err_dev2;
+
+	/* The GPIO for the appropriate PSC was configured by the
+	 * board specific start up.
+	 *
+	 * configure PSC for AC'97
+	 */
+	au_writel(0, AC97_PSC_CTRL);	/* Disable PSC */
+	au_sync();
+	au_writel((PSC_SEL_CLK_SERCLK | PSC_SEL_PS_AC97MODE), AC97_PSC_SEL);
+	au_sync();
+
+	/* cold reset the AC'97
+	*/
+	au_writel(PSC_AC97RST_RST, PSC_AC97RST);
+	au_sync();
+	au1550_delay(10);
+	au_writel(0, PSC_AC97RST);
+	au_sync();
+
+	/* need to delay around 500msec(bleech) to give
+	   some CODECs enough time to wakeup */
+	au1550_delay(500);
+
+	/* warm reset the AC'97 to start the bitclk
+	*/
+	au_writel(PSC_AC97RST_SNC, PSC_AC97RST);
+	au_sync();
+	udelay(100);
+	au_writel(0, PSC_AC97RST);
+	au_sync();
+
+	/* Enable PSC
+	*/
+	au_writel(PSC_CTRL_ENABLE, AC97_PSC_CTRL);
+	au_sync();
+
+	/* Wait for PSC ready.
+	*/
+	do {
+		val = au_readl(PSC_AC97STAT);
+		au_sync();
+	} while ((val & PSC_AC97STAT_SR) == 0);
+
+	/* Configure AC97 controller.
+	 * Deep FIFO, 16-bit sample, DMA, make sure DMA matches fifo size.
+	 */
+	val = PSC_AC97CFG_SET_LEN(16);
+	val |= PSC_AC97CFG_RT_FIFO8 | PSC_AC97CFG_TT_FIFO8;
+
+	/* Enable device so we can at least
+	 * talk over the AC-link.
+	 */
+	au_writel(val, PSC_AC97CFG);
+	au_writel(PSC_AC97MSK_ALLMASK, PSC_AC97MSK);
+	au_sync();
+	val |= PSC_AC97CFG_DE_ENABLE;
+	au_writel(val, PSC_AC97CFG);
+	au_sync();
+
+	/* Wait for Device ready.
+	*/
+	do {
+		val = au_readl(PSC_AC97STAT);
+		au_sync();
+	} while ((val & PSC_AC97STAT_DR) == 0);
+
+	/* codec init */
+	if (!ac97_probe_codec(s->codec))
+		goto err_dev3;
+
+	s->codec_base_caps = rdcodec(s->codec, AC97_RESET);
+	s->codec_ext_caps = rdcodec(s->codec, AC97_EXTENDED_ID);
+	pr_info("AC'97 Base/Extended ID = %04x/%04x",
+	     s->codec_base_caps, s->codec_ext_caps);
+
+	if (!(s->codec_ext_caps & AC97_EXTID_VRA)) {
+		/* codec does not support VRA
+		*/
+		s->no_vra = 1;
+	} else if (!vra) {
+		/* Boot option says disable VRA
+		*/
+		u16 ac97_extstat = rdcodec(s->codec, AC97_EXTENDED_STATUS);
+		wrcodec(s->codec, AC97_EXTENDED_STATUS,
+			ac97_extstat & ~AC97_EXTSTAT_VRA);
+		s->no_vra = 1;
+	}
+	if (s->no_vra)
+		pr_info("no VRA, interpolating and decimating");
+
+	/* set mic to be the recording source */
+	val = SOUND_MASK_MIC;
+	mixdev_ioctl(s->codec, SOUND_MIXER_WRITE_RECSRC,
+		     (unsigned long) &val);
+
+	return 0;
+
+ err_dev3:
+	unregister_sound_mixer(s->codec->dev_mixer);
+ err_dev2:
+	unregister_sound_dsp(s->dev_audio);
+ err_dev1:
+	au1xxx_dbdma_chan_free(s->dma_adc.dmanr);
+ err_dma2:
+	au1xxx_dbdma_chan_free(s->dma_dac.dmanr);
+ err_dma1:
+	release_mem_region(CPHYSADDR(AC97_PSC_SEL), 0x30);
+
+	ac97_release_codec(s->codec);
+	return -1;
+}
+
+static void __devinit
+au1550_remove(void)
+{
+	struct au1550_state *s = &au1550_state;
+
+	if (!s)
+		return;
+	synchronize_irq();
+	au1xxx_dbdma_chan_free(s->dma_adc.dmanr);
+	au1xxx_dbdma_chan_free(s->dma_dac.dmanr);
+	release_mem_region(CPHYSADDR(AC97_PSC_SEL), 0x30);
+	unregister_sound_dsp(s->dev_audio);
+	unregister_sound_mixer(s->codec->dev_mixer);
+	ac97_release_codec(s->codec);
+}
+
+static int __init
+init_au1550(void)
+{
+	return au1550_probe();
+}
+
+static void __exit
+cleanup_au1550(void)
+{
+	au1550_remove();
+}
+
+module_init(init_au1550);
+module_exit(cleanup_au1550);
+
+#ifndef MODULE
+
+static int __init
+au1550_setup(char *options)
+{
+	char           *this_opt;
+
+	if (!options || !*options)
+		return 0;
+
+	while ((this_opt = strsep(&options, ","))) {
+		if (!*this_opt)
+			continue;
+		if (!strncmp(this_opt, "vra", 3)) {
+			vra = 1;
+		}
+	}
+
+	return 1;
+}
+
+__setup("au1550_audio=", au1550_setup);
+
+#endif /* MODULE */
diff --git a/linux/sound/oss/audio.c b/linux/sound/oss/audio.c
new file mode 100644
index 0000000..7df48a2
--- /dev/null
+++ b/linux/sound/oss/audio.c
@@ -0,0 +1,985 @@
+/*
+ * sound/oss/audio.c
+ *
+ * Device file manager for /dev/audio
+ */
+
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer   : ioctl code reworked (vmalloc/vfree removed)
+ * Thomas Sailer   : moved several static variables into struct audio_operations
+ *                   (which is grossly misnamed btw.) because they have the same
+ *                   lifetime as the rest in there and dynamic allocation saves
+ *                   12k or so
+ * Thomas Sailer   : use more logical O_NONBLOCK semantics
+ * Daniel Rodriksson: reworked the use of the device specific copy_user
+ *                    still generic
+ * Horst von Brand:  Add missing #include <linux/string.h>
+ * Chris Rankin    : Update the module-usage counter for the coprocessor,
+ *                   and decrement the counters again if we cannot open
+ *                   the audio device.
+ */
+
+#include <linux/stddef.h>
+#include <linux/string.h>
+#include <linux/kmod.h>
+
+#include "sound_config.h"
+#include "ulaw.h"
+#include "coproc.h"
+
+#define NEUTRAL8	0x80
+#define NEUTRAL16	0x00
+
+
+static int             dma_ioctl(int dev, unsigned int cmd, void __user *arg);
+
+static int set_format(int dev, int fmt)
+{
+	if (fmt != AFMT_QUERY)
+	{
+		audio_devs[dev]->local_conversion = 0;
+
+		if (!(audio_devs[dev]->format_mask & fmt))	/* Not supported */
+		{
+			if (fmt == AFMT_MU_LAW)
+			{
+				fmt = AFMT_U8;
+				audio_devs[dev]->local_conversion = CNV_MU_LAW;
+			}
+			else
+				fmt = AFMT_U8;	/* This is always supported */
+		}
+		audio_devs[dev]->audio_format = audio_devs[dev]->d->set_bits(dev, fmt);
+		audio_devs[dev]->local_format = fmt;
+	}
+	else
+		return audio_devs[dev]->local_format;
+
+	if (audio_devs[dev]->local_conversion)
+		return audio_devs[dev]->local_conversion;
+	else 
+		return audio_devs[dev]->local_format;
+}
+
+int audio_open(int dev, struct file *file)
+{
+	int ret;
+	int bits;
+	int dev_type = dev & 0x0f;
+	int mode = translate_mode(file);
+	const struct audio_driver *driver;
+	const struct coproc_operations *coprocessor;
+
+	dev = dev >> 4;
+
+	if (dev_type == SND_DEV_DSP16)
+		bits = 16;
+	else
+		bits = 8;
+
+	if (dev < 0 || dev >= num_audiodevs)
+		return -ENXIO;
+
+	driver = audio_devs[dev]->d;
+
+	if (!try_module_get(driver->owner))
+		return -ENODEV;
+
+	if ((ret = DMAbuf_open(dev, mode)) < 0)
+		goto error_1;
+
+	if ( (coprocessor = audio_devs[dev]->coproc) != NULL ) {
+		if (!try_module_get(coprocessor->owner))
+			goto error_2;
+
+		if ((ret = coprocessor->open(coprocessor->devc, COPR_PCM)) < 0) {
+			printk(KERN_WARNING "Sound: Can't access coprocessor device\n");
+			goto error_3;
+		}
+	}
+	
+	audio_devs[dev]->local_conversion = 0;
+
+	if (dev_type == SND_DEV_AUDIO)
+		set_format(dev, AFMT_MU_LAW);
+	else 
+		set_format(dev, bits);
+
+	audio_devs[dev]->audio_mode = AM_NONE;
+
+	return 0;
+
+	/*
+	 * Clean-up stack: this is what needs (un)doing if
+	 * we can't open the audio device ...
+	 */
+	error_3:
+	module_put(coprocessor->owner);
+
+	error_2:
+	DMAbuf_release(dev, mode);
+
+	error_1:
+	module_put(driver->owner);
+
+	return ret;
+}
+
+static void sync_output(int dev)
+{
+	int             p, i;
+	int             l;
+	struct dma_buffparms *dmap = audio_devs[dev]->dmap_out;
+
+	if (dmap->fragment_size <= 0)
+		return;
+	dmap->flags |= DMA_POST;
+
+	/* Align the write pointer with fragment boundaries */
+	
+	if ((l = dmap->user_counter % dmap->fragment_size) > 0)
+	{
+		int len;
+		unsigned long offs = dmap->user_counter % dmap->bytes_in_use;
+
+		len = dmap->fragment_size - l;
+		memset(dmap->raw_buf + offs, dmap->neutral_byte, len);
+		DMAbuf_move_wrpointer(dev, len);
+	}
+	
+	/*
+	 * Clean all unused buffer fragments.
+	 */
+
+	p = dmap->qtail;
+	dmap->flags |= DMA_POST;
+
+	for (i = dmap->qlen + 1; i < dmap->nbufs; i++)
+	{
+		p = (p + 1) % dmap->nbufs;
+		if (((dmap->raw_buf + p * dmap->fragment_size) + dmap->fragment_size) >
+			(dmap->raw_buf + dmap->buffsize))
+				printk(KERN_ERR "audio: Buffer error 2\n");
+
+		memset(dmap->raw_buf + p * dmap->fragment_size,
+			dmap->neutral_byte,
+			dmap->fragment_size);
+	}
+
+	dmap->flags |= DMA_DIRTY;
+}
+
+void audio_release(int dev, struct file *file)
+{
+	const struct coproc_operations *coprocessor;
+	int mode = translate_mode(file);
+
+	dev = dev >> 4;
+
+	/*
+	 * We do this in DMAbuf_release(). Why are we doing it
+	 * here? Why don't we test the file mode before setting
+	 * both flags? DMAbuf_release() does.
+	 * ...pester...pester...pester...
+	 */
+	audio_devs[dev]->dmap_out->closing = 1;
+	audio_devs[dev]->dmap_in->closing = 1;
+
+	/*
+	 * We need to make sure we allocated the dmap_out buffer
+	 * before we go mucking around with it in sync_output().
+	 */
+	if (mode & OPEN_WRITE)
+		sync_output(dev);
+
+	if ( (coprocessor = audio_devs[dev]->coproc) != NULL ) {
+		coprocessor->close(coprocessor->devc, COPR_PCM);
+		module_put(coprocessor->owner);
+	}
+	DMAbuf_release(dev, mode);
+
+	module_put(audio_devs[dev]->d->owner);
+}
+
+static void translate_bytes(const unsigned char *table, unsigned char *buff, int n)
+{
+	unsigned long   i;
+
+	if (n <= 0)
+		return;
+
+	for (i = 0; i < n; ++i)
+		buff[i] = table[buff[i]];
+}
+
+int audio_write(int dev, struct file *file, const char __user *buf, int count)
+{
+	int c, p, l, buf_size, used, returned;
+	int err;
+	char *dma_buf;
+
+	dev = dev >> 4;
+
+	p = 0;
+	c = count;
+	
+	if(count < 0)
+		return -EINVAL;
+
+	if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+		return -EPERM;
+
+	if (audio_devs[dev]->flags & DMA_DUPLEX)
+		audio_devs[dev]->audio_mode |= AM_WRITE;
+	else
+		audio_devs[dev]->audio_mode = AM_WRITE;
+
+	if (!count)		/* Flush output */
+	{
+		  sync_output(dev);
+		  return 0;
+	}
+	
+	while (c)
+	{
+		if ((err = DMAbuf_getwrbuffer(dev, &dma_buf, &buf_size, !!(file->f_flags & O_NONBLOCK))) < 0)
+		{
+			    /* Handle nonblocking mode */
+			if ((file->f_flags & O_NONBLOCK) && err == -EAGAIN)
+				return p? p : -EAGAIN;	/* No more space. Return # of accepted bytes */
+			return err;
+		}
+		l = c;
+
+		if (l > buf_size)
+			l = buf_size;
+
+		returned = l;
+		used = l;
+		if (!audio_devs[dev]->d->copy_user)
+		{
+			if ((dma_buf + l) >
+				(audio_devs[dev]->dmap_out->raw_buf + audio_devs[dev]->dmap_out->buffsize))
+			{
+				printk(KERN_ERR "audio: Buffer error 3 (%lx,%d), (%lx, %d)\n", (long) dma_buf, l, (long) audio_devs[dev]->dmap_out->raw_buf, (int) audio_devs[dev]->dmap_out->buffsize);
+				return -EDOM;
+			}
+			if (dma_buf < audio_devs[dev]->dmap_out->raw_buf)
+			{
+				printk(KERN_ERR "audio: Buffer error 13 (%lx<%lx)\n", (long) dma_buf, (long) audio_devs[dev]->dmap_out->raw_buf);
+				return -EDOM;
+			}
+			if(copy_from_user(dma_buf, &(buf)[p], l))
+				return -EFAULT;
+		} 
+		else audio_devs[dev]->d->copy_user (dev,
+						dma_buf, 0,
+						buf, p,
+						c, buf_size,
+						&used, &returned,
+						l);
+		l = returned;
+
+		if (audio_devs[dev]->local_conversion & CNV_MU_LAW)
+		{
+			translate_bytes(ulaw_dsp, (unsigned char *) dma_buf, l);
+		}
+		c -= used;
+		p += used;
+		DMAbuf_move_wrpointer(dev, l);
+
+	}
+
+	return count;
+}
+
+int audio_read(int dev, struct file *file, char __user *buf, int count)
+{
+	int             c, p, l;
+	char           *dmabuf;
+	int             buf_no;
+
+	dev = dev >> 4;
+	p = 0;
+	c = count;
+
+	if (!(audio_devs[dev]->open_mode & OPEN_READ))
+		return -EPERM;
+
+	if ((audio_devs[dev]->audio_mode & AM_WRITE) && !(audio_devs[dev]->flags & DMA_DUPLEX))
+		sync_output(dev);
+
+	if (audio_devs[dev]->flags & DMA_DUPLEX)
+		audio_devs[dev]->audio_mode |= AM_READ;
+	else
+		audio_devs[dev]->audio_mode = AM_READ;
+
+	while(c)
+	{
+		if ((buf_no = DMAbuf_getrdbuffer(dev, &dmabuf, &l, !!(file->f_flags & O_NONBLOCK))) < 0)
+		{
+			/*
+			 *	Nonblocking mode handling. Return current # of bytes
+			 */
+
+			if (p > 0) 		/* Avoid throwing away data */
+				return p;	/* Return it instead */
+
+			if ((file->f_flags & O_NONBLOCK) && buf_no == -EAGAIN)
+				return -EAGAIN;
+
+			return buf_no;
+		}
+		if (l > c)
+			l = c;
+
+		/*
+		 * Insert any local processing here.
+		 */
+
+		if (audio_devs[dev]->local_conversion & CNV_MU_LAW)
+		{
+			translate_bytes(dsp_ulaw, (unsigned char *) dmabuf, l);
+		}
+		
+		{
+			char           *fixit = dmabuf;
+
+			if(copy_to_user(&(buf)[p], fixit, l))
+				return -EFAULT;
+		};
+
+		DMAbuf_rmchars(dev, buf_no, l);
+
+		p += l;
+		c -= l;
+	}
+
+	return count - c;
+}
+
+int audio_ioctl(int dev, struct file *file, unsigned int cmd, void __user *arg)
+{
+	int val, count;
+	unsigned long flags;
+	struct dma_buffparms *dmap;
+	int __user *p = arg;
+
+	dev = dev >> 4;
+
+	if (_IOC_TYPE(cmd) == 'C')	{
+		if (audio_devs[dev]->coproc)	/* Coprocessor ioctl */
+			return audio_devs[dev]->coproc->ioctl(audio_devs[dev]->coproc->devc, cmd, arg, 0);
+		/* else
+		        printk(KERN_DEBUG"/dev/dsp%d: No coprocessor for this device\n", dev); */
+		return -ENXIO;
+	}
+	else switch (cmd) 
+	{
+		case SNDCTL_DSP_SYNC:
+			if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+				return 0;
+			if (audio_devs[dev]->dmap_out->fragment_size == 0)
+				return 0;
+			sync_output(dev);
+			DMAbuf_sync(dev);
+			DMAbuf_reset(dev);
+			return 0;
+
+		case SNDCTL_DSP_POST:
+			if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+				return 0;
+			if (audio_devs[dev]->dmap_out->fragment_size == 0)
+				return 0;
+			audio_devs[dev]->dmap_out->flags |= DMA_POST | DMA_DIRTY;
+			sync_output(dev);
+			dma_ioctl(dev, SNDCTL_DSP_POST, NULL);
+			return 0;
+
+		case SNDCTL_DSP_RESET:
+			audio_devs[dev]->audio_mode = AM_NONE;
+			DMAbuf_reset(dev);
+			return 0;
+
+		case SNDCTL_DSP_GETFMTS:
+			val = audio_devs[dev]->format_mask | AFMT_MU_LAW;
+			break;
+	
+		case SNDCTL_DSP_SETFMT:
+			if (get_user(val, p))
+				return -EFAULT;
+			val = set_format(dev, val);
+			break;
+
+		case SNDCTL_DSP_GETISPACE:
+			if (!(audio_devs[dev]->open_mode & OPEN_READ))
+				return 0;
+  			if ((audio_devs[dev]->audio_mode & AM_WRITE) && !(audio_devs[dev]->flags & DMA_DUPLEX))
+  				return -EBUSY;
+			return dma_ioctl(dev, cmd, arg);
+
+		case SNDCTL_DSP_GETOSPACE:
+			if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+				return -EPERM;
+  			if ((audio_devs[dev]->audio_mode & AM_READ) && !(audio_devs[dev]->flags & DMA_DUPLEX))
+  				return -EBUSY;
+			return dma_ioctl(dev, cmd, arg);
+		
+		case SNDCTL_DSP_NONBLOCK:
+			spin_lock(&file->f_lock);
+			file->f_flags |= O_NONBLOCK;
+			spin_unlock(&file->f_lock);
+			return 0;
+
+		case SNDCTL_DSP_GETCAPS:
+				val = 1 | DSP_CAP_MMAP;	/* Revision level of this ioctl() */
+				if (audio_devs[dev]->flags & DMA_DUPLEX &&
+					audio_devs[dev]->open_mode == OPEN_READWRITE)
+					val |= DSP_CAP_DUPLEX;
+				if (audio_devs[dev]->coproc)
+					val |= DSP_CAP_COPROC;
+				if (audio_devs[dev]->d->local_qlen)	/* Device has hidden buffers */
+					val |= DSP_CAP_BATCH;
+				if (audio_devs[dev]->d->trigger)	/* Supports SETTRIGGER */
+					val |= DSP_CAP_TRIGGER;
+				break;
+			
+		case SOUND_PCM_WRITE_RATE:
+			if (get_user(val, p))
+				return -EFAULT;
+			val = audio_devs[dev]->d->set_speed(dev, val);
+			break;
+
+		case SOUND_PCM_READ_RATE:
+			val = audio_devs[dev]->d->set_speed(dev, 0);
+			break;
+			
+		case SNDCTL_DSP_STEREO:
+			if (get_user(val, p))
+				return -EFAULT;
+			if (val > 1 || val < 0)
+				return -EINVAL;
+			val = audio_devs[dev]->d->set_channels(dev, val + 1) - 1;
+			break;
+
+		case SOUND_PCM_WRITE_CHANNELS:
+			if (get_user(val, p))
+				return -EFAULT;
+			val = audio_devs[dev]->d->set_channels(dev, val);
+			break;
+
+		case SOUND_PCM_READ_CHANNELS:
+			val = audio_devs[dev]->d->set_channels(dev, 0);
+			break;
+		
+		case SOUND_PCM_READ_BITS:
+			val = audio_devs[dev]->d->set_bits(dev, 0);
+			break;
+
+		case SNDCTL_DSP_SETDUPLEX:
+			if (audio_devs[dev]->open_mode != OPEN_READWRITE)
+				return -EPERM;
+			return (audio_devs[dev]->flags & DMA_DUPLEX) ? 0 : -EIO;
+
+		case SNDCTL_DSP_PROFILE:
+			if (get_user(val, p))
+				return -EFAULT;
+			if (audio_devs[dev]->open_mode & OPEN_WRITE)
+				audio_devs[dev]->dmap_out->applic_profile = val;
+			if (audio_devs[dev]->open_mode & OPEN_READ)
+				audio_devs[dev]->dmap_in->applic_profile = val;
+			return 0;
+		
+		case SNDCTL_DSP_GETODELAY:
+			dmap = audio_devs[dev]->dmap_out;
+			if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+				return -EINVAL;
+			if (!(dmap->flags & DMA_ALLOC_DONE))
+			{
+				val=0;
+				break;
+			}
+		
+			spin_lock_irqsave(&dmap->lock,flags);
+			/* Compute number of bytes that have been played */
+			count = DMAbuf_get_buffer_pointer (dev, dmap, DMODE_OUTPUT);
+			if (count < dmap->fragment_size && dmap->qhead != 0)
+				count += dmap->bytes_in_use;	/* Pointer wrap not handled yet */
+			count += dmap->byte_counter;
+		
+			/* Substract current count from the number of bytes written by app */
+			count = dmap->user_counter - count;
+			if (count < 0)
+				count = 0;
+			spin_unlock_irqrestore(&dmap->lock,flags);
+			val = count;
+			break;
+		
+		default:
+			return dma_ioctl(dev, cmd, arg);
+	}
+	return put_user(val, p);
+}
+
+void audio_init_devices(void)
+{
+	/*
+	 * NOTE! This routine could be called several times during boot.
+	 */
+}
+
+void reorganize_buffers(int dev, struct dma_buffparms *dmap, int recording)
+{
+	/*
+	 * This routine breaks the physical device buffers to logical ones.
+	 */
+
+	struct audio_operations *dsp_dev = audio_devs[dev];
+
+	unsigned i, n;
+	unsigned sr, nc, sz, bsz;
+
+	sr = dsp_dev->d->set_speed(dev, 0);
+	nc = dsp_dev->d->set_channels(dev, 0);
+	sz = dsp_dev->d->set_bits(dev, 0);
+
+	if (sz == 8)
+		dmap->neutral_byte = NEUTRAL8;
+	else
+		dmap->neutral_byte = NEUTRAL16;
+
+	if (sr < 1 || nc < 1 || sz < 1)
+	{
+/*		printk(KERN_DEBUG "Warning: Invalid PCM parameters[%d] sr=%d, nc=%d, sz=%d\n", dev, sr, nc, sz);*/
+		sr = DSP_DEFAULT_SPEED;
+		nc = 1;
+		sz = 8;
+	}
+	
+	sz = sr * nc * sz;
+
+	sz /= 8;		/* #bits -> #bytes */
+	dmap->data_rate = sz;
+
+	if (!dmap->needs_reorg)
+		return;
+	dmap->needs_reorg = 0;
+
+	if (dmap->fragment_size == 0)
+	{	
+		/* Compute the fragment size using the default algorithm */
+
+		/*
+		 * Compute a buffer size for time not exceeding 1 second.
+		 * Usually this algorithm gives a buffer size for 0.5 to 1.0 seconds
+		 * of sound (using the current speed, sample size and #channels).
+		 */
+
+		bsz = dmap->buffsize;
+		while (bsz > sz)
+			bsz /= 2;
+
+		if (bsz == dmap->buffsize)
+			bsz /= 2;	/* Needs at least 2 buffers */
+
+		/*
+		 *    Split the computed fragment to smaller parts. After 3.5a9
+		 *      the default subdivision is 4 which should give better
+		 *      results when recording.
+		 */
+
+		if (dmap->subdivision == 0)	/* Not already set */
+		{
+			dmap->subdivision = 4;	/* Init to the default value */
+
+			if ((bsz / dmap->subdivision) > 4096)
+				dmap->subdivision *= 2;
+			if ((bsz / dmap->subdivision) < 4096)
+				dmap->subdivision = 1;
+		}
+		bsz /= dmap->subdivision;
+
+		if (bsz < 16)
+			bsz = 16;	/* Just a sanity check */
+
+		dmap->fragment_size = bsz;
+	}
+	else
+	{
+		/*
+		 * The process has specified the buffer size with SNDCTL_DSP_SETFRAGMENT or
+		 * the buffer size computation has already been done.
+		 */
+		if (dmap->fragment_size > (dmap->buffsize / 2))
+			dmap->fragment_size = (dmap->buffsize / 2);
+		bsz = dmap->fragment_size;
+	}
+
+	if (audio_devs[dev]->min_fragment)
+		if (bsz < (1 << audio_devs[dev]->min_fragment))
+			bsz = 1 << audio_devs[dev]->min_fragment;
+	if (audio_devs[dev]->max_fragment)
+		if (bsz > (1 << audio_devs[dev]->max_fragment))
+			bsz = 1 << audio_devs[dev]->max_fragment;
+	bsz &= ~0x07;		/* Force size which is multiple of 8 bytes */
+#ifdef OS_DMA_ALIGN_CHECK
+	OS_DMA_ALIGN_CHECK(bsz);
+#endif
+
+	n = dmap->buffsize / bsz;
+	if (n > MAX_SUB_BUFFERS)
+		n = MAX_SUB_BUFFERS;
+	if (n > dmap->max_fragments)
+		n = dmap->max_fragments;
+
+	if (n < 2)
+	{
+		n = 2;
+		bsz /= 2;
+	}
+	dmap->nbufs = n;
+	dmap->bytes_in_use = n * bsz;
+	dmap->fragment_size = bsz;
+	dmap->max_byte_counter = (dmap->data_rate * 60 * 60) +
+			dmap->bytes_in_use;	/* Approximately one hour */
+
+	if (dmap->raw_buf)
+	{
+		memset(dmap->raw_buf, dmap->neutral_byte, dmap->bytes_in_use);
+	}
+	
+	for (i = 0; i < dmap->nbufs; i++)
+	{
+		dmap->counts[i] = 0;
+	}
+
+	dmap->flags |= DMA_ALLOC_DONE | DMA_EMPTY;
+}
+
+static int dma_subdivide(int dev, struct dma_buffparms *dmap, int fact)
+{
+	if (fact == 0) 
+	{
+		fact = dmap->subdivision;
+		if (fact == 0)
+			fact = 1;
+		return fact;
+	}
+	if (dmap->subdivision != 0 || dmap->fragment_size)	/* Too late to change */
+		return -EINVAL;
+
+	if (fact > MAX_REALTIME_FACTOR)
+		return -EINVAL;
+
+	if (fact != 1 && fact != 2 && fact != 4 && fact != 8 && fact != 16)
+		return -EINVAL;
+
+	dmap->subdivision = fact;
+	return fact;
+}
+
+static int dma_set_fragment(int dev, struct dma_buffparms *dmap, int fact)
+{
+	int bytes, count;
+
+	if (fact == 0)
+		return -EIO;
+
+	if (dmap->subdivision != 0 ||
+	    dmap->fragment_size)	/* Too late to change */
+		return -EINVAL;
+
+	bytes = fact & 0xffff;
+	count = (fact >> 16) & 0x7fff;
+
+	if (count == 0)
+		count = MAX_SUB_BUFFERS;
+	else if (count < MAX_SUB_BUFFERS)
+		count++;
+
+	if (bytes < 4 || bytes > 17)	/* <16 || > 512k */
+		return -EINVAL;
+
+	if (count < 2)
+		return -EINVAL;
+
+	if (audio_devs[dev]->min_fragment > 0)
+		if (bytes < audio_devs[dev]->min_fragment)
+			bytes = audio_devs[dev]->min_fragment;
+
+	if (audio_devs[dev]->max_fragment > 0)
+		if (bytes > audio_devs[dev]->max_fragment)
+			bytes = audio_devs[dev]->max_fragment;
+
+#ifdef OS_DMA_MINBITS
+	if (bytes < OS_DMA_MINBITS)
+		bytes = OS_DMA_MINBITS;
+#endif
+
+	dmap->fragment_size = (1 << bytes);
+	dmap->max_fragments = count;
+
+	if (dmap->fragment_size > dmap->buffsize)
+		dmap->fragment_size = dmap->buffsize;
+
+	if (dmap->fragment_size == dmap->buffsize &&
+	    audio_devs[dev]->flags & DMA_AUTOMODE)
+		dmap->fragment_size /= 2;	/* Needs at least 2 buffers */
+
+	dmap->subdivision = 1;	/* Disable SNDCTL_DSP_SUBDIVIDE */
+	return bytes | ((count - 1) << 16);
+}
+
+static int dma_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+	struct dma_buffparms *dmap_out = audio_devs[dev]->dmap_out;
+	struct dma_buffparms *dmap_in = audio_devs[dev]->dmap_in;
+	struct dma_buffparms *dmap;
+	audio_buf_info info;
+	count_info cinfo;
+	int fact, ret, changed, bits, count, err;
+	unsigned long flags;
+
+	switch (cmd) 
+	{
+		case SNDCTL_DSP_SUBDIVIDE:
+			ret = 0;
+			if (get_user(fact, (int __user *)arg))
+				return -EFAULT;
+			if (audio_devs[dev]->open_mode & OPEN_WRITE)
+				ret = dma_subdivide(dev, dmap_out, fact);
+			if (ret < 0)
+				return ret;
+			if (audio_devs[dev]->open_mode != OPEN_WRITE ||
+				(audio_devs[dev]->flags & DMA_DUPLEX &&
+					audio_devs[dev]->open_mode & OPEN_READ))
+				ret = dma_subdivide(dev, dmap_in, fact);
+			if (ret < 0)
+				return ret;
+			break;
+
+		case SNDCTL_DSP_GETISPACE:
+		case SNDCTL_DSP_GETOSPACE:
+			dmap = dmap_out;
+			if (cmd == SNDCTL_DSP_GETISPACE && !(audio_devs[dev]->open_mode & OPEN_READ))
+				return -EINVAL;
+			if (cmd == SNDCTL_DSP_GETOSPACE && !(audio_devs[dev]->open_mode & OPEN_WRITE))
+				return -EINVAL;
+			if (cmd == SNDCTL_DSP_GETISPACE && audio_devs[dev]->flags & DMA_DUPLEX)
+				dmap = dmap_in;
+			if (dmap->mapping_flags & DMA_MAP_MAPPED)
+				return -EINVAL;
+			if (!(dmap->flags & DMA_ALLOC_DONE))
+				reorganize_buffers(dev, dmap, (cmd == SNDCTL_DSP_GETISPACE));
+			info.fragstotal = dmap->nbufs;
+			if (cmd == SNDCTL_DSP_GETISPACE)
+				info.fragments = dmap->qlen;
+			else 
+			{
+				if (!DMAbuf_space_in_queue(dev))
+					info.fragments = 0;
+				else
+				{
+					info.fragments = DMAbuf_space_in_queue(dev);
+					if (audio_devs[dev]->d->local_qlen) 
+					{
+						int tmp = audio_devs[dev]->d->local_qlen(dev);
+						if (tmp && info.fragments)
+							tmp--;	/*
+								 * This buffer has been counted twice
+								 */
+						info.fragments -= tmp;
+					}
+				}
+			}
+			if (info.fragments < 0)
+				info.fragments = 0;
+			else if (info.fragments > dmap->nbufs)
+				info.fragments = dmap->nbufs;
+
+			info.fragsize = dmap->fragment_size;
+			info.bytes = info.fragments * dmap->fragment_size;
+
+			if (cmd == SNDCTL_DSP_GETISPACE && dmap->qlen)
+				info.bytes -= dmap->counts[dmap->qhead];
+			else 
+			{
+				info.fragments = info.bytes / dmap->fragment_size;
+				info.bytes -= dmap->user_counter % dmap->fragment_size;
+			}
+			if (copy_to_user(arg, &info, sizeof(info)))
+				return -EFAULT;
+			return 0;
+
+		case SNDCTL_DSP_SETTRIGGER:
+			if (get_user(bits, (int __user *)arg))
+				return -EFAULT;
+			bits &= audio_devs[dev]->open_mode;
+			if (audio_devs[dev]->d->trigger == NULL)
+				return -EINVAL;
+			if (!(audio_devs[dev]->flags & DMA_DUPLEX) && (bits & PCM_ENABLE_INPUT) &&
+				(bits & PCM_ENABLE_OUTPUT))
+				return -EINVAL;
+
+			if (bits & PCM_ENABLE_INPUT)
+			{
+				spin_lock_irqsave(&dmap_in->lock,flags);
+				changed = (audio_devs[dev]->enable_bits ^ bits) & PCM_ENABLE_INPUT;
+				if (changed && audio_devs[dev]->go) 
+				{
+					reorganize_buffers(dev, dmap_in, 1);
+					if ((err = audio_devs[dev]->d->prepare_for_input(dev,
+						     dmap_in->fragment_size, dmap_in->nbufs)) < 0) {
+						spin_unlock_irqrestore(&dmap_in->lock,flags);
+						return err;
+					}
+					dmap_in->dma_mode = DMODE_INPUT;
+					audio_devs[dev]->enable_bits |= PCM_ENABLE_INPUT;
+					DMAbuf_activate_recording(dev, dmap_in);
+				} else
+					audio_devs[dev]->enable_bits &= ~PCM_ENABLE_INPUT;
+				spin_unlock_irqrestore(&dmap_in->lock,flags);
+			}
+			if (bits & PCM_ENABLE_OUTPUT)
+			{
+				spin_lock_irqsave(&dmap_out->lock,flags);
+				changed = (audio_devs[dev]->enable_bits ^ bits) & PCM_ENABLE_OUTPUT;
+				if (changed &&
+				    (dmap_out->mapping_flags & DMA_MAP_MAPPED || dmap_out->qlen > 0) &&
+				    audio_devs[dev]->go) 
+				{
+					if (!(dmap_out->flags & DMA_ALLOC_DONE))
+						reorganize_buffers(dev, dmap_out, 0);
+					dmap_out->dma_mode = DMODE_OUTPUT;
+					audio_devs[dev]->enable_bits |= PCM_ENABLE_OUTPUT;
+					dmap_out->counts[dmap_out->qhead] = dmap_out->fragment_size;
+					DMAbuf_launch_output(dev, dmap_out);
+				} else
+					audio_devs[dev]->enable_bits &= ~PCM_ENABLE_OUTPUT;
+				spin_unlock_irqrestore(&dmap_out->lock,flags);
+			}
+#if 0
+			if (changed && audio_devs[dev]->d->trigger)
+				audio_devs[dev]->d->trigger(dev, bits * audio_devs[dev]->go);
+#endif				
+			/* Falls through... */
+
+		case SNDCTL_DSP_GETTRIGGER:
+			ret = audio_devs[dev]->enable_bits;
+			break;
+
+		case SNDCTL_DSP_SETSYNCRO:
+			if (!audio_devs[dev]->d->trigger)
+				return -EINVAL;
+			audio_devs[dev]->d->trigger(dev, 0);
+			audio_devs[dev]->go = 0;
+			return 0;
+
+		case SNDCTL_DSP_GETIPTR:
+			if (!(audio_devs[dev]->open_mode & OPEN_READ))
+				return -EINVAL;
+			spin_lock_irqsave(&dmap_in->lock,flags);
+			cinfo.bytes = dmap_in->byte_counter;
+			cinfo.ptr = DMAbuf_get_buffer_pointer(dev, dmap_in, DMODE_INPUT) & ~3;
+			if (cinfo.ptr < dmap_in->fragment_size && dmap_in->qtail != 0)
+				cinfo.bytes += dmap_in->bytes_in_use;	/* Pointer wrap not handled yet */
+			cinfo.blocks = dmap_in->qlen;
+			cinfo.bytes += cinfo.ptr;
+			if (dmap_in->mapping_flags & DMA_MAP_MAPPED)
+				dmap_in->qlen = 0;	/* Reset interrupt counter */
+			spin_unlock_irqrestore(&dmap_in->lock,flags);
+			if (copy_to_user(arg, &cinfo, sizeof(cinfo)))
+				return -EFAULT;
+			return 0;
+
+		case SNDCTL_DSP_GETOPTR:
+			if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+				return -EINVAL;
+
+			spin_lock_irqsave(&dmap_out->lock,flags);
+			cinfo.bytes = dmap_out->byte_counter;
+			cinfo.ptr = DMAbuf_get_buffer_pointer(dev, dmap_out, DMODE_OUTPUT) & ~3;
+			if (cinfo.ptr < dmap_out->fragment_size && dmap_out->qhead != 0)
+				cinfo.bytes += dmap_out->bytes_in_use;	/* Pointer wrap not handled yet */
+			cinfo.blocks = dmap_out->qlen;
+			cinfo.bytes += cinfo.ptr;
+			if (dmap_out->mapping_flags & DMA_MAP_MAPPED)
+				dmap_out->qlen = 0;	/* Reset interrupt counter */
+			spin_unlock_irqrestore(&dmap_out->lock,flags);
+			if (copy_to_user(arg, &cinfo, sizeof(cinfo)))
+				return -EFAULT;
+			return 0;
+
+		case SNDCTL_DSP_GETODELAY:
+			if (!(audio_devs[dev]->open_mode & OPEN_WRITE))
+				return -EINVAL;
+			if (!(dmap_out->flags & DMA_ALLOC_DONE))
+			{
+				ret=0;
+				break;
+			}
+			spin_lock_irqsave(&dmap_out->lock,flags);
+			/* Compute number of bytes that have been played */
+			count = DMAbuf_get_buffer_pointer (dev, dmap_out, DMODE_OUTPUT);
+			if (count < dmap_out->fragment_size && dmap_out->qhead != 0)
+				count += dmap_out->bytes_in_use;	/* Pointer wrap not handled yet */
+			count += dmap_out->byte_counter;
+			/* Substract current count from the number of bytes written by app */
+			count = dmap_out->user_counter - count;
+			if (count < 0)
+				count = 0;
+			spin_unlock_irqrestore(&dmap_out->lock,flags);
+			ret = count;
+			break;
+
+		case SNDCTL_DSP_POST:
+			if (audio_devs[dev]->dmap_out->qlen > 0)
+				if (!(audio_devs[dev]->dmap_out->flags & DMA_ACTIVE))
+					DMAbuf_launch_output(dev, audio_devs[dev]->dmap_out);
+			return 0;
+
+		case SNDCTL_DSP_GETBLKSIZE:
+			dmap = dmap_out;
+			if (audio_devs[dev]->open_mode & OPEN_WRITE)
+				reorganize_buffers(dev, dmap_out, (audio_devs[dev]->open_mode == OPEN_READ));
+			if (audio_devs[dev]->open_mode == OPEN_READ ||
+			    (audio_devs[dev]->flags & DMA_DUPLEX &&
+			     audio_devs[dev]->open_mode & OPEN_READ))
+				reorganize_buffers(dev, dmap_in, (audio_devs[dev]->open_mode == OPEN_READ));
+			if (audio_devs[dev]->open_mode == OPEN_READ)
+				dmap = dmap_in;
+			ret = dmap->fragment_size;
+			break;
+
+		case SNDCTL_DSP_SETFRAGMENT:
+			ret = 0;
+			if (get_user(fact, (int __user *)arg))
+				return -EFAULT;
+			if (audio_devs[dev]->open_mode & OPEN_WRITE)
+				ret = dma_set_fragment(dev, dmap_out, fact);
+			if (ret < 0)
+				return ret;
+			if (audio_devs[dev]->open_mode == OPEN_READ ||
+			    (audio_devs[dev]->flags & DMA_DUPLEX &&
+			     audio_devs[dev]->open_mode & OPEN_READ))
+				ret = dma_set_fragment(dev, dmap_in, fact);
+			if (ret < 0)
+				return ret;
+			if (!arg) /* don't know what this is good for, but preserve old semantics */
+				return 0;
+			break;
+
+		default:
+			if (!audio_devs[dev]->d->ioctl)
+				return -EINVAL;
+			return audio_devs[dev]->d->ioctl(dev, cmd, arg);
+	}
+	return put_user(ret, (int __user *)arg);
+}
diff --git a/linux/sound/oss/bin2hex.c b/linux/sound/oss/bin2hex.c
new file mode 100644
index 0000000..b59109e
--- /dev/null
+++ b/linux/sound/oss/bin2hex.c
@@ -0,0 +1,39 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+int main( int argc, const char * argv [] )
+{
+    const char * varname;
+    int i = 0;
+    int c;
+    int id = 0;
+
+    if(argv[1] && strcmp(argv[1],"-i")==0)
+    {
+    	argv++;
+    	argc--;
+    	id=1;
+    }
+    	
+    if(argc==1)
+    {
+    	fprintf(stderr, "bin2hex: [-i] firmware\n");
+    	exit(1);
+    }
+    
+    varname = argv[1];
+    printf( "/* automatically generated by bin2hex */\n" );
+    printf( "static unsigned char %s [] %s =\n{\n", varname , id?"__initdata":"");
+
+    while ( ( c = getchar( ) ) != EOF )
+    {
+	if ( i != 0 && i % 10 == 0 )
+	    printf( "\n" );
+	printf( "0x%02lx,", c & 0xFFl );
+	i++;
+    }
+
+    printf( "};\nstatic int %sLen =  %d;\n", varname, i );
+    return 0;
+}
diff --git a/linux/sound/oss/coproc.h b/linux/sound/oss/coproc.h
new file mode 100644
index 0000000..7bec21b
--- /dev/null
+++ b/linux/sound/oss/coproc.h
@@ -0,0 +1,12 @@
+/*
+ * Definitions for various on board processors on the sound cards. For
+ * example DSP processors.
+ */
+
+/*
+ * Coprocessor access types
+ */
+#define COPR_CUSTOM		0x0001	/* Custom applications */
+#define COPR_MIDI		0x0002	/* MIDI (MPU-401) emulation */
+#define COPR_PCM		0x0004	/* Digitized voice applications */
+#define COPR_SYNTH		0x0008	/* Music synthesis */
diff --git a/linux/sound/oss/dev_table.c b/linux/sound/oss/dev_table.c
new file mode 100644
index 0000000..d8cf3e5
--- /dev/null
+++ b/linux/sound/oss/dev_table.c
@@ -0,0 +1,256 @@
+/*
+ * sound/oss/dev_table.c
+ *
+ * Device call tables.
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+#include <linux/init.h>
+
+#include "sound_config.h"
+
+struct audio_operations *audio_devs[MAX_AUDIO_DEV];
+EXPORT_SYMBOL(audio_devs);
+
+int num_audiodevs;
+EXPORT_SYMBOL(num_audiodevs);
+
+struct mixer_operations *mixer_devs[MAX_MIXER_DEV];
+EXPORT_SYMBOL(mixer_devs);
+
+int num_mixers;
+EXPORT_SYMBOL(num_mixers);
+
+struct synth_operations *synth_devs[MAX_SYNTH_DEV+MAX_MIDI_DEV];
+EXPORT_SYMBOL(synth_devs);
+
+int num_synths;
+
+struct midi_operations *midi_devs[MAX_MIDI_DEV];
+EXPORT_SYMBOL(midi_devs);
+
+int num_midis;
+EXPORT_SYMBOL(num_midis);
+
+struct sound_timer_operations *sound_timer_devs[MAX_TIMER_DEV] = {
+	&default_sound_timer, NULL
+};
+EXPORT_SYMBOL(sound_timer_devs);
+
+int num_sound_timers = 1;
+
+
+static int sound_alloc_audiodev(void);
+
+int sound_install_audiodrv(int vers, char *name, struct audio_driver *driver,
+			int driver_size, int flags, unsigned int format_mask,
+			void *devc, int dma1, int dma2)
+{
+	struct audio_driver *d;
+	struct audio_operations *op;
+	int num;
+
+	if (vers != AUDIO_DRIVER_VERSION || driver_size > sizeof(struct audio_driver)) {
+		printk(KERN_ERR "Sound: Incompatible audio driver for %s\n", name);
+		return -(EINVAL);
+	}
+	num = sound_alloc_audiodev();
+
+	if (num == -1) {
+		printk(KERN_ERR "sound: Too many audio drivers\n");
+		return -(EBUSY);
+	}
+	d = (struct audio_driver *) (sound_mem_blocks[sound_nblocks] = vmalloc(sizeof(struct audio_driver)));
+	sound_nblocks++;
+	if (sound_nblocks >= MAX_MEM_BLOCKS)
+		sound_nblocks = MAX_MEM_BLOCKS - 1;
+
+	op = (struct audio_operations *) (sound_mem_blocks[sound_nblocks] = vzalloc(sizeof(struct audio_operations)));
+	sound_nblocks++;
+	if (sound_nblocks >= MAX_MEM_BLOCKS)
+		sound_nblocks = MAX_MEM_BLOCKS - 1;
+
+	if (d == NULL || op == NULL) {
+		printk(KERN_ERR "Sound: Can't allocate driver for (%s)\n", name);
+		sound_unload_audiodev(num);
+		return -(ENOMEM);
+	}
+	init_waitqueue_head(&op->in_sleeper);
+	init_waitqueue_head(&op->out_sleeper);	
+	init_waitqueue_head(&op->poll_sleeper);
+	if (driver_size < sizeof(struct audio_driver))
+		memset((char *) d, 0, sizeof(struct audio_driver));
+
+	memcpy((char *) d, (char *) driver, driver_size);
+
+	op->d = d;
+	strlcpy(op->name, name, sizeof(op->name));
+	op->flags = flags;
+	op->format_mask = format_mask;
+	op->devc = devc;
+
+	/*
+	 *    Hardcoded defaults
+	 */
+	audio_devs[num] = op;
+
+	DMAbuf_init(num, dma1, dma2);
+
+	audio_init_devices();
+	return num;
+}
+EXPORT_SYMBOL(sound_install_audiodrv);
+
+int sound_install_mixer(int vers, char *name, struct mixer_operations *driver,
+	int driver_size, void *devc)
+{
+	struct mixer_operations *op;
+
+	int n = sound_alloc_mixerdev();
+
+	if (n == -1) {
+		printk(KERN_ERR "Sound: Too many mixer drivers\n");
+		return -EBUSY;
+	}
+	if (vers != MIXER_DRIVER_VERSION ||
+		driver_size > sizeof(struct mixer_operations)) {
+		printk(KERN_ERR "Sound: Incompatible mixer driver for %s\n", name);
+		return -EINVAL;
+	}
+	
+	/* FIXME: This leaks a mixer_operations struct every time its called
+	   until you unload sound! */
+	   
+	op = (struct mixer_operations *) (sound_mem_blocks[sound_nblocks] = vzalloc(sizeof(struct mixer_operations)));
+	sound_nblocks++;
+	if (sound_nblocks >= MAX_MEM_BLOCKS)
+		sound_nblocks = MAX_MEM_BLOCKS - 1;
+
+	if (op == NULL) {
+		printk(KERN_ERR "Sound: Can't allocate mixer driver for (%s)\n", name);
+		return -ENOMEM;
+	}
+	memcpy((char *) op, (char *) driver, driver_size);
+
+	strlcpy(op->name, name, sizeof(op->name));
+	op->devc = devc;
+
+	mixer_devs[n] = op;
+	return n;
+}
+EXPORT_SYMBOL(sound_install_mixer);
+
+void sound_unload_audiodev(int dev)
+{
+	if (dev != -1) {
+		DMAbuf_deinit(dev);
+		audio_devs[dev] = NULL;
+		unregister_sound_dsp((dev<<4)+3);
+	}
+}
+EXPORT_SYMBOL(sound_unload_audiodev);
+
+static int sound_alloc_audiodev(void)
+{ 
+	int i = register_sound_dsp(&oss_sound_fops, -1);
+	if(i==-1)
+		return i;
+	i>>=4;
+	if(i>=num_audiodevs)
+		num_audiodevs = i + 1;
+	return i;
+}
+
+int sound_alloc_mididev(void)
+{
+	int i = register_sound_midi(&oss_sound_fops, -1);
+	if(i==-1)
+		return i;
+	i>>=4;
+	if(i>=num_midis)
+		num_midis = i + 1;
+	return i;
+}
+EXPORT_SYMBOL(sound_alloc_mididev);
+
+int sound_alloc_synthdev(void)
+{
+	int i;
+
+	for (i = 0; i < MAX_SYNTH_DEV; i++) {
+		if (synth_devs[i] == NULL) {
+			if (i >= num_synths)
+				num_synths++;
+			return i;
+		}
+	}
+	return -1;
+}
+EXPORT_SYMBOL(sound_alloc_synthdev);
+
+int sound_alloc_mixerdev(void)
+{
+	int i = register_sound_mixer(&oss_sound_fops, -1);
+	if(i==-1)
+		return -1;
+	i>>=4;
+	if(i>=num_mixers)
+		num_mixers = i + 1;
+	return i;
+}
+EXPORT_SYMBOL(sound_alloc_mixerdev);
+
+int sound_alloc_timerdev(void)
+{
+	int i;
+
+	for (i = 0; i < MAX_TIMER_DEV; i++) {
+		if (sound_timer_devs[i] == NULL) {
+			if (i >= num_sound_timers)
+				num_sound_timers++;
+			return i;
+		}
+	}
+	return -1;
+}
+EXPORT_SYMBOL(sound_alloc_timerdev);
+
+void sound_unload_mixerdev(int dev)
+{
+	if (dev != -1) {
+		mixer_devs[dev] = NULL;
+		unregister_sound_mixer(dev<<4);
+		num_mixers--;
+	}
+}
+EXPORT_SYMBOL(sound_unload_mixerdev);
+
+void sound_unload_mididev(int dev)
+{
+	if (dev != -1) {
+		midi_devs[dev] = NULL;
+		unregister_sound_midi((dev<<4)+2);
+	}
+}
+EXPORT_SYMBOL(sound_unload_mididev);
+
+void sound_unload_synthdev(int dev)
+{
+	if (dev != -1)
+		synth_devs[dev] = NULL;
+}
+EXPORT_SYMBOL(sound_unload_synthdev);
+
+void sound_unload_timerdev(int dev)
+{
+	if (dev != -1)
+		sound_timer_devs[dev] = NULL;
+}
+EXPORT_SYMBOL(sound_unload_timerdev);
+
diff --git a/linux/sound/oss/dev_table.h b/linux/sound/oss/dev_table.h
new file mode 100644
index 0000000..b7617be
--- /dev/null
+++ b/linux/sound/oss/dev_table.h
@@ -0,0 +1,390 @@
+/*
+ *	dev_table.h
+ *
+ *	Global definitions for device call tables
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+
+#ifndef _DEV_TABLE_H_
+#define _DEV_TABLE_H_
+
+#include <linux/spinlock.h>
+/*
+ * Sound card numbers 27 to 999. (1 to 26 are defined in soundcard.h)
+ * Numbers 1000 to N are reserved for driver's internal use.
+ */
+
+#define SNDCARD_DESKPROXL		27	/* Compaq Deskpro XL */
+#define SNDCARD_VIDC			28	/* ARMs VIDC */
+#define SNDCARD_SBPNP			29
+#define SNDCARD_SOFTOSS			36
+#define SNDCARD_VMIDI			37
+#define SNDCARD_OPL3SA1			38	/* Note: clash in msnd.h */
+#define SNDCARD_OPL3SA1_SB		39
+#define SNDCARD_OPL3SA1_MPU		40
+#define SNDCARD_WAVEFRONT               41
+#define SNDCARD_OPL3SA2                 42
+#define SNDCARD_OPL3SA2_MPU             43
+#define SNDCARD_WAVEARTIST              44	/* Waveartist */
+#define SNDCARD_OPL3SA2_MSS             45	/* Originally missed */
+#define SNDCARD_AD1816                  88
+
+/*
+ *	NOTE! 	NOTE!	NOTE!	NOTE!
+ *
+ *	If you modify this file, please check the dev_table.c also.
+ *
+ *	NOTE! 	NOTE!	NOTE!	NOTE!
+ */
+
+struct driver_info 
+{
+	char *driver_id;
+	int card_subtype;	/* Driver specific. Usually 0 */
+	int card_type;		/*	From soundcard.h	*/
+	char *name;
+	void (*attach) (struct address_info *hw_config);
+	int (*probe) (struct address_info *hw_config);
+	void (*unload) (struct address_info *hw_config);
+};
+
+struct card_info 
+{
+	int card_type;	/* Link (search key) to the driver list */
+	struct address_info config;
+	int enabled;
+	void *for_driver_use;
+};
+
+
+/*
+ * Device specific parameters (used only by dmabuf.c)
+ */
+#define MAX_SUB_BUFFERS		(32*MAX_REALTIME_FACTOR)
+
+#define DMODE_NONE		0
+#define DMODE_OUTPUT		PCM_ENABLE_OUTPUT
+#define DMODE_INPUT		PCM_ENABLE_INPUT
+
+struct dma_buffparms 
+{
+	int      dma_mode;	/* DMODE_INPUT, DMODE_OUTPUT or DMODE_NONE */
+	int	 closing;
+
+	/*
+ 	 * Pointers to raw buffers
+ 	 */
+
+  	char     *raw_buf;
+    	unsigned long   raw_buf_phys;
+	int buffsize;
+
+     	/*
+         * Device state tables
+         */
+
+	unsigned long flags;
+#define DMA_BUSY	0x00000001
+#define DMA_RESTART	0x00000002
+#define DMA_ACTIVE	0x00000004
+#define DMA_STARTED	0x00000008
+#define DMA_EMPTY	0x00000010	
+#define DMA_ALLOC_DONE	0x00000020
+#define DMA_SYNCING	0x00000040
+#define DMA_DIRTY	0x00000080
+#define DMA_POST	0x00000100
+#define DMA_NODMA	0x00000200
+#define DMA_NOTIMEOUT	0x00000400
+
+	int      open_mode;
+
+	/*
+	 * Queue parameters.
+	 */
+	int      qlen;
+	int      qhead;
+	int      qtail;
+	spinlock_t lock;
+		
+	int	 cfrag;	/* Current incomplete fragment (write) */
+
+	int      nbufs;
+	int      counts[MAX_SUB_BUFFERS];
+	int      subdivision;
+
+	int      fragment_size;
+        int	 needs_reorg;
+	int	 max_fragments;
+
+	int	 bytes_in_use;
+
+	int	 underrun_count;
+	unsigned long	 byte_counter;
+	unsigned long	 user_counter;
+	unsigned long	 max_byte_counter;
+	int	 data_rate; /* Bytes/second */
+
+	int	 mapping_flags;
+#define			DMA_MAP_MAPPED		0x00000001
+	char	neutral_byte;
+	int	dma;		/* DMA channel */
+
+	int     applic_profile;	/* Application profile (APF_*) */
+	/* Interrupt callback stuff */
+	void (*audio_callback) (int dev, int parm);
+	int callback_parm;
+
+	int	 buf_flags[MAX_SUB_BUFFERS];
+#define		 BUFF_EOF		0x00000001 /* Increment eof count */
+#define		 BUFF_DIRTY		0x00000002 /* Buffer written */
+};
+
+/*
+ * Structure for use with various microcontrollers and DSP processors 
+ * in the recent sound cards.
+ */
+typedef struct coproc_operations 
+{
+	char name[64];
+	struct module *owner;
+	int (*open) (void *devc, int sub_device);
+	void (*close) (void *devc, int sub_device);
+	int (*ioctl) (void *devc, unsigned int cmd, void __user * arg, int local);
+	void (*reset) (void *devc);
+
+	void *devc;		/* Driver specific info */
+} coproc_operations;
+
+struct audio_driver 
+{
+	struct module *owner;
+	int (*open) (int dev, int mode);
+	void (*close) (int dev);
+	void (*output_block) (int dev, unsigned long buf, 
+			      int count, int intrflag);
+	void (*start_input) (int dev, unsigned long buf, 
+			     int count, int intrflag);
+	int (*ioctl) (int dev, unsigned int cmd, void __user * arg);
+	int (*prepare_for_input) (int dev, int bufsize, int nbufs);
+	int (*prepare_for_output) (int dev, int bufsize, int nbufs);
+	void (*halt_io) (int dev);
+	int (*local_qlen)(int dev);
+	void (*copy_user) (int dev,
+			char *localbuf, int localoffs,
+                        const char __user *userbuf, int useroffs,
+                        int max_in, int max_out,
+                        int *used, int *returned,
+                        int len);
+	void (*halt_input) (int dev);
+	void (*halt_output) (int dev);
+	void (*trigger) (int dev, int bits);
+	int (*set_speed)(int dev, int speed);
+	unsigned int (*set_bits)(int dev, unsigned int bits);
+	short (*set_channels)(int dev, short channels);
+	void (*postprocess_write)(int dev); 	/* Device spesific postprocessing for written data */
+	void (*preprocess_read)(int dev); 	/* Device spesific preprocessing for read data */
+	void (*mmap)(int dev);
+};
+
+struct audio_operations 
+{
+        char name[128];
+	int flags;
+#define NOTHING_SPECIAL 	0x00
+#define NEEDS_RESTART		0x01
+#define DMA_AUTOMODE		0x02
+#define DMA_DUPLEX		0x04
+#define DMA_PSEUDO_AUTOMODE	0x08
+#define DMA_HARDSTOP		0x10
+#define DMA_EXACT		0x40
+#define DMA_NORESET		0x80
+	int  format_mask;	/* Bitmask for supported audio formats */
+	void *devc;		/* Driver specific info */
+	struct audio_driver *d;
+	void *portc;		/* Driver specific info */
+	struct dma_buffparms *dmap_in, *dmap_out;
+	struct coproc_operations *coproc;
+	int mixer_dev;
+	int enable_bits;
+ 	int open_mode;
+	int go;
+	int min_fragment;	/* 0 == unlimited */
+	int max_fragment;	/* 0 == unlimited */
+	int parent_dev;		/* 0 -> no parent, 1 to n -> parent=parent_dev+1 */
+
+	/* fields formerly in dmabuf.c */
+	wait_queue_head_t in_sleeper;
+	wait_queue_head_t out_sleeper;
+	wait_queue_head_t poll_sleeper;
+
+	/* fields formerly in audio.c */
+	int audio_mode;
+
+#define		AM_NONE		0
+#define		AM_WRITE	OPEN_WRITE
+#define 	AM_READ		OPEN_READ
+
+	int local_format;
+	int audio_format;
+	int local_conversion;
+#define CNV_MU_LAW	0x00000001
+
+	/* large structures at the end to keep offsets small */
+	struct dma_buffparms dmaps[2];
+};
+
+int *load_mixer_volumes(char *name, int *levels, int present);
+
+struct mixer_operations 
+{
+	struct module *owner;
+	char id[16];
+	char name[64];
+	int (*ioctl) (int dev, unsigned int cmd, void __user * arg);
+	
+	void *devc;
+	int modify_counter;
+};
+
+struct synth_operations 
+{
+	struct module *owner;
+	char *id;	/* Unique identifier (ASCII) max 29 char */
+	struct synth_info *info;
+	int midi_dev;
+	int synth_type;
+	int synth_subtype;
+
+	int (*open) (int dev, int mode);
+	void (*close) (int dev);
+	int (*ioctl) (int dev, unsigned int cmd, void __user * arg);
+	int (*kill_note) (int dev, int voice, int note, int velocity);
+	int (*start_note) (int dev, int voice, int note, int velocity);
+	int (*set_instr) (int dev, int voice, int instr);
+	void (*reset) (int dev);
+	void (*hw_control) (int dev, unsigned char *event);
+	int (*load_patch) (int dev, int format, const char __user *addr,
+	     int offs, int count, int pmgr_flag);
+	void (*aftertouch) (int dev, int voice, int pressure);
+	void (*controller) (int dev, int voice, int ctrl_num, int value);
+	void (*panning) (int dev, int voice, int value);
+	void (*volume_method) (int dev, int mode);
+	void (*bender) (int dev, int chn, int value);
+	int (*alloc_voice) (int dev, int chn, int note, struct voice_alloc_info *alloc);
+	void (*setup_voice) (int dev, int voice, int chn);
+	int (*send_sysex)(int dev, unsigned char *bytes, int len);
+
+ 	struct voice_alloc_info alloc;
+ 	struct channel_info chn_info[16];
+	int emulation;
+#define	EMU_GM			1	/* General MIDI */
+#define	EMU_XG			2	/* Yamaha XG */
+#define MAX_SYSEX_BUF	64
+	unsigned char sysex_buf[MAX_SYSEX_BUF];
+	int sysex_ptr;
+};
+
+struct midi_input_info 
+{
+	/* MIDI input scanner variables */
+#define MI_MAX	10
+	volatile int             m_busy;
+    	unsigned char   m_buf[MI_MAX];
+	unsigned char	m_prev_status;	/* For running status */
+    	int             m_ptr;
+#define MST_INIT			0
+#define MST_DATA			1
+#define MST_SYSEX			2
+    	int             m_state;
+    	int             m_left;
+};
+
+struct midi_operations 
+{
+	struct module *owner;
+	struct midi_info info;
+	struct synth_operations *converter;
+	struct midi_input_info in_info;
+	int (*open) (int dev, int mode,
+		void (*inputintr)(int dev, unsigned char data),
+		void (*outputintr)(int dev)
+		);
+	void (*close) (int dev);
+	int (*ioctl) (int dev, unsigned int cmd, void __user * arg);
+	int (*outputc) (int dev, unsigned char data);
+	int (*start_read) (int dev);
+	int (*end_read) (int dev);
+	void (*kick)(int dev);
+	int (*command) (int dev, unsigned char *data);
+	int (*buffer_status) (int dev);
+	int (*prefix_cmd) (int dev, unsigned char status);
+	struct coproc_operations *coproc;
+	void *devc;
+};
+
+struct sound_lowlev_timer 
+{
+	int dev;
+	int priority;
+	unsigned int (*tmr_start)(int dev, unsigned int usecs);
+	void (*tmr_disable)(int dev);
+	void (*tmr_restart)(int dev);
+};
+
+struct sound_timer_operations 
+{
+	struct module *owner;
+	struct sound_timer_info info;
+	int priority;
+	int devlink;
+	int (*open)(int dev, int mode);
+	void (*close)(int dev);
+	int (*event)(int dev, unsigned char *ev);
+	unsigned long (*get_time)(int dev);
+	int (*ioctl) (int dev, unsigned int cmd, void __user * arg);
+	void (*arm_timer)(int dev, long time);
+};
+
+extern struct sound_timer_operations default_sound_timer;
+
+extern struct audio_operations *audio_devs[MAX_AUDIO_DEV];
+extern int num_audiodevs;
+extern struct mixer_operations *mixer_devs[MAX_MIXER_DEV];
+extern int num_mixers;
+extern struct synth_operations *synth_devs[MAX_SYNTH_DEV+MAX_MIDI_DEV];
+extern int num_synths;
+extern struct midi_operations *midi_devs[MAX_MIDI_DEV];
+extern int num_midis;
+extern struct sound_timer_operations * sound_timer_devs[MAX_TIMER_DEV];
+extern int num_sound_timers;
+
+extern int sound_map_buffer (int dev, struct dma_buffparms *dmap, buffmem_desc *info);
+void sound_timer_init (struct sound_lowlev_timer *t, char *name);
+void sound_dma_intr (int dev, struct dma_buffparms *dmap, int chan);
+
+#define AUDIO_DRIVER_VERSION	2
+#define MIXER_DRIVER_VERSION	2
+int sound_install_audiodrv(int vers, char *name, struct audio_driver *driver,
+			int driver_size, int flags, unsigned int format_mask,
+			void *devc, int dma1, int dma2);
+int sound_install_mixer(int vers, char *name, struct mixer_operations *driver,
+			int driver_size, void *devc);
+
+void sound_unload_audiodev(int dev);
+void sound_unload_mixerdev(int dev);
+void sound_unload_mididev(int dev);
+void sound_unload_synthdev(int dev);
+void sound_unload_timerdev(int dev);
+int sound_alloc_mixerdev(void);
+int sound_alloc_timerdev(void);
+int sound_alloc_synthdev(void);
+int sound_alloc_mididev(void);
+#endif	/* _DEV_TABLE_H_ */
+
diff --git a/linux/sound/oss/dmabuf.c b/linux/sound/oss/dmabuf.c
new file mode 100644
index 0000000..bcc3e8e
--- /dev/null
+++ b/linux/sound/oss/dmabuf.c
@@ -0,0 +1,1268 @@
+/*
+ * sound/oss/dmabuf.c
+ *
+ * The DMA buffer manager for digitized voice applications
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Thomas Sailer   : moved several static variables into struct audio_operations
+ *                   (which is grossly misnamed btw.) because they have the same
+ *                   lifetime as the rest in there and dynamic allocation saves
+ *                   12k or so
+ * Thomas Sailer   : remove {in,out}_sleep_flag. It was used for the sleeper to
+ *                   determine if it was woken up by the expiring timeout or by
+ *                   an explicit wake_up. The return value from schedule_timeout
+ *		     can be used instead; if 0, the wakeup was due to the timeout.
+ *
+ * Rob Riggs		Added persistent DMA buffers (1998/10/17)
+ */
+
+#define BE_CONSERVATIVE
+#define SAMPLE_ROUNDUP 0
+
+#include <linux/mm.h>
+#include <linux/gfp.h>
+#include "sound_config.h"
+
+#define DMAP_FREE_ON_CLOSE      0
+#define DMAP_KEEP_ON_CLOSE      1
+extern int sound_dmap_flag;
+
+static void dma_reset_output(int dev);
+static void dma_reset_input(int dev);
+static int local_start_dma(struct audio_operations *adev, unsigned long physaddr, int count, int dma_mode);
+
+
+
+static int debugmem;    	/* switched off by default */
+static int dma_buffsize = DSP_BUFFSIZE;
+
+static long dmabuf_timeout(struct dma_buffparms *dmap)
+{
+	long tmout;
+
+	tmout = (dmap->fragment_size * HZ) / dmap->data_rate;
+	tmout += HZ / 5;	/* Some safety distance */
+	if (tmout < (HZ / 2))
+		tmout = HZ / 2;
+	if (tmout > 20 * HZ)
+		tmout = 20 * HZ;
+	return tmout;
+}
+
+static int sound_alloc_dmap(struct dma_buffparms *dmap)
+{
+	char *start_addr, *end_addr;
+	int dma_pagesize;
+	int sz, size;
+	struct page *page;
+
+	dmap->mapping_flags &= ~DMA_MAP_MAPPED;
+
+	if (dmap->raw_buf != NULL)
+		return 0;	/* Already done */
+	if (dma_buffsize < 4096)
+		dma_buffsize = 4096;
+	dma_pagesize = (dmap->dma < 4) ? (64 * 1024) : (128 * 1024);
+	
+	/*
+	 *	Now check for the Cyrix problem.
+	 */
+	 
+	if(isa_dma_bridge_buggy==2)
+		dma_pagesize=32768;
+	 
+	dmap->raw_buf = NULL;
+	dmap->buffsize = dma_buffsize;
+	if (dmap->buffsize > dma_pagesize)
+		dmap->buffsize = dma_pagesize;
+	start_addr = NULL;
+	/*
+	 * Now loop until we get a free buffer. Try to get smaller buffer if
+	 * it fails. Don't accept smaller than 8k buffer for performance
+	 * reasons.
+	 */
+	while (start_addr == NULL && dmap->buffsize > PAGE_SIZE) {
+		for (sz = 0, size = PAGE_SIZE; size < dmap->buffsize; sz++, size <<= 1);
+		dmap->buffsize = PAGE_SIZE * (1 << sz);
+		start_addr = (char *) __get_free_pages(GFP_ATOMIC|GFP_DMA|__GFP_NOWARN, sz);
+		if (start_addr == NULL)
+			dmap->buffsize /= 2;
+	}
+
+	if (start_addr == NULL) {
+		printk(KERN_WARNING "Sound error: Couldn't allocate DMA buffer\n");
+		return -ENOMEM;
+	} else {
+		/* make some checks */
+		end_addr = start_addr + dmap->buffsize - 1;
+
+		if (debugmem)
+			printk(KERN_DEBUG "sound: start 0x%lx, end 0x%lx\n", (long) start_addr, (long) end_addr);
+		
+		/* now check if it fits into the same dma-pagesize */
+
+		if (((long) start_addr & ~(dma_pagesize - 1)) != ((long) end_addr & ~(dma_pagesize - 1))
+		    || end_addr >= (char *) (MAX_DMA_ADDRESS)) {
+			printk(KERN_ERR "sound: Got invalid address 0x%lx for %db DMA-buffer\n", (long) start_addr, dmap->buffsize);
+			return -EFAULT;
+		}
+	}
+	dmap->raw_buf = start_addr;
+	dmap->raw_buf_phys = virt_to_bus(start_addr);
+
+	for (page = virt_to_page(start_addr); page <= virt_to_page(end_addr); page++)
+		SetPageReserved(page);
+	return 0;
+}
+
+static void sound_free_dmap(struct dma_buffparms *dmap)
+{
+	int sz, size;
+	struct page *page;
+	unsigned long start_addr, end_addr;
+
+	if (dmap->raw_buf == NULL)
+		return;
+	if (dmap->mapping_flags & DMA_MAP_MAPPED)
+		return;		/* Don't free mmapped buffer. Will use it next time */
+	for (sz = 0, size = PAGE_SIZE; size < dmap->buffsize; sz++, size <<= 1);
+
+	start_addr = (unsigned long) dmap->raw_buf;
+	end_addr = start_addr + dmap->buffsize;
+
+	for (page = virt_to_page(start_addr); page <= virt_to_page(end_addr); page++)
+		ClearPageReserved(page);
+
+	free_pages((unsigned long) dmap->raw_buf, sz);
+	dmap->raw_buf = NULL;
+}
+
+
+/* Intel version !!!!!!!!! */
+
+static int sound_start_dma(struct dma_buffparms *dmap, unsigned long physaddr, int count, int dma_mode)
+{
+	unsigned long flags;
+	int chan = dmap->dma;
+
+	/* printk( "Start DMA%d %d, %d\n",  chan,  (int)(physaddr-dmap->raw_buf_phys),  count); */
+
+	flags = claim_dma_lock();
+	disable_dma(chan);
+	clear_dma_ff(chan);
+	set_dma_mode(chan, dma_mode);
+	set_dma_addr(chan, physaddr);
+	set_dma_count(chan, count);
+	enable_dma(chan);
+	release_dma_lock(flags);
+
+	return 0;
+}
+
+static void dma_init_buffers(struct dma_buffparms *dmap)
+{
+	dmap->qlen = dmap->qhead = dmap->qtail = dmap->user_counter = 0;
+	dmap->byte_counter = 0;
+	dmap->max_byte_counter = 8000 * 60 * 60;
+	dmap->bytes_in_use = dmap->buffsize;
+
+	dmap->dma_mode = DMODE_NONE;
+	dmap->mapping_flags = 0;
+	dmap->neutral_byte = 0x80;
+	dmap->data_rate = 8000;
+	dmap->cfrag = -1;
+	dmap->closing = 0;
+	dmap->nbufs = 1;
+	dmap->flags = DMA_BUSY;	/* Other flags off */
+}
+
+static int open_dmap(struct audio_operations *adev, int mode, struct dma_buffparms *dmap)
+{
+	int err;
+	
+	if (dmap->flags & DMA_BUSY)
+		return -EBUSY;
+	if ((err = sound_alloc_dmap(dmap)) < 0)
+		return err;
+
+	if (dmap->raw_buf == NULL) {
+		printk(KERN_WARNING "Sound: DMA buffers not available\n");
+		return -ENOSPC;	/* Memory allocation failed during boot */
+	}
+	if (dmap->dma >= 0 && sound_open_dma(dmap->dma, adev->name)) {
+		printk(KERN_WARNING "Unable to grab(2) DMA%d for the audio driver\n", dmap->dma);
+		return -EBUSY;
+	}
+	dma_init_buffers(dmap);
+	spin_lock_init(&dmap->lock);
+	dmap->open_mode = mode;
+	dmap->subdivision = dmap->underrun_count = 0;
+	dmap->fragment_size = 0;
+	dmap->max_fragments = 65536;	/* Just a large value */
+	dmap->byte_counter = 0;
+	dmap->max_byte_counter = 8000 * 60 * 60;
+	dmap->applic_profile = APF_NORMAL;
+	dmap->needs_reorg = 1;
+	dmap->audio_callback = NULL;
+	dmap->callback_parm = 0;
+	return 0;
+}
+
+static void close_dmap(struct audio_operations *adev, struct dma_buffparms *dmap)
+{
+	unsigned long flags;
+	
+	if (dmap->dma >= 0) {
+		sound_close_dma(dmap->dma);
+		flags=claim_dma_lock();
+		disable_dma(dmap->dma);
+		release_dma_lock(flags);
+	}
+	if (dmap->flags & DMA_BUSY)
+		dmap->dma_mode = DMODE_NONE;
+	dmap->flags &= ~DMA_BUSY;
+	
+	if (sound_dmap_flag == DMAP_FREE_ON_CLOSE)
+		sound_free_dmap(dmap);
+}
+
+
+static unsigned int default_set_bits(int dev, unsigned int bits)
+{
+	mm_segment_t fs = get_fs();
+
+	set_fs(get_ds());
+	audio_devs[dev]->d->ioctl(dev, SNDCTL_DSP_SETFMT, (void __user *)&bits);
+	set_fs(fs);
+	return bits;
+}
+
+static int default_set_speed(int dev, int speed)
+{
+	mm_segment_t fs = get_fs();
+
+	set_fs(get_ds());
+	audio_devs[dev]->d->ioctl(dev, SNDCTL_DSP_SPEED, (void __user *)&speed);
+	set_fs(fs);
+	return speed;
+}
+
+static short default_set_channels(int dev, short channels)
+{
+	int c = channels;
+	mm_segment_t fs = get_fs();
+
+	set_fs(get_ds());
+	audio_devs[dev]->d->ioctl(dev, SNDCTL_DSP_CHANNELS, (void __user *)&c);
+	set_fs(fs);
+	return c;
+}
+
+static void check_driver(struct audio_driver *d)
+{
+	if (d->set_speed == NULL)
+		d->set_speed = default_set_speed;
+	if (d->set_bits == NULL)
+		d->set_bits = default_set_bits;
+	if (d->set_channels == NULL)
+		d->set_channels = default_set_channels;
+}
+
+int DMAbuf_open(int dev, int mode)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	int retval;
+	struct dma_buffparms *dmap_in = NULL;
+	struct dma_buffparms *dmap_out = NULL;
+
+	if (!adev)
+		  return -ENXIO;
+	if (!(adev->flags & DMA_DUPLEX))
+		adev->dmap_in = adev->dmap_out;
+	check_driver(adev->d);
+
+	if ((retval = adev->d->open(dev, mode)) < 0)
+		return retval;
+	dmap_out = adev->dmap_out;
+	dmap_in = adev->dmap_in;
+	if (dmap_in == dmap_out)
+		adev->flags &= ~DMA_DUPLEX;
+
+	if (mode & OPEN_WRITE) {
+		if ((retval = open_dmap(adev, mode, dmap_out)) < 0) {
+			adev->d->close(dev);
+			return retval;
+		}
+	}
+	adev->enable_bits = mode;
+
+	if (mode == OPEN_READ || (mode != OPEN_WRITE && (adev->flags & DMA_DUPLEX))) {
+		if ((retval = open_dmap(adev, mode, dmap_in)) < 0) {
+			adev->d->close(dev);
+			if (mode & OPEN_WRITE)
+				close_dmap(adev, dmap_out);
+			return retval;
+		}
+	}
+	adev->open_mode = mode;
+	adev->go = 1;
+
+	adev->d->set_bits(dev, 8);
+	adev->d->set_channels(dev, 1);
+	adev->d->set_speed(dev, DSP_DEFAULT_SPEED);
+	if (adev->dmap_out->dma_mode == DMODE_OUTPUT) 
+		memset(adev->dmap_out->raw_buf, adev->dmap_out->neutral_byte,
+		       adev->dmap_out->bytes_in_use);
+	return 0;
+}
+/* MUST not hold the spinlock */
+void DMAbuf_reset(int dev)
+{
+	if (audio_devs[dev]->open_mode & OPEN_WRITE)
+		dma_reset_output(dev);
+
+	if (audio_devs[dev]->open_mode & OPEN_READ)
+		dma_reset_input(dev);
+}
+
+static void dma_reset_output(int dev)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	unsigned long flags,f ;
+	struct dma_buffparms *dmap = adev->dmap_out;
+
+	if (!(dmap->flags & DMA_STARTED))	/* DMA is not active */
+		return;
+
+	/*
+	 *	First wait until the current fragment has been played completely
+	 */
+	spin_lock_irqsave(&dmap->lock,flags);
+	adev->dmap_out->flags |= DMA_SYNCING;
+
+	adev->dmap_out->underrun_count = 0;
+	if (!signal_pending(current) && adev->dmap_out->qlen && 
+	    adev->dmap_out->underrun_count == 0){
+		spin_unlock_irqrestore(&dmap->lock,flags);
+		interruptible_sleep_on_timeout(&adev->out_sleeper,
+					       dmabuf_timeout(dmap));
+		spin_lock_irqsave(&dmap->lock,flags);
+	}
+	adev->dmap_out->flags &= ~(DMA_SYNCING | DMA_ACTIVE);
+
+	/*
+	 *	Finally shut the device off
+	 */
+	if (!(adev->flags & DMA_DUPLEX) || !adev->d->halt_output)
+		adev->d->halt_io(dev);
+	else
+		adev->d->halt_output(dev);
+	adev->dmap_out->flags &= ~DMA_STARTED;
+	
+	f=claim_dma_lock();
+	clear_dma_ff(dmap->dma);
+	disable_dma(dmap->dma);
+	release_dma_lock(f);
+	
+	dmap->byte_counter = 0;
+	reorganize_buffers(dev, adev->dmap_out, 0);
+	dmap->qlen = dmap->qhead = dmap->qtail = dmap->user_counter = 0;
+	spin_unlock_irqrestore(&dmap->lock,flags);
+}
+
+static void dma_reset_input(int dev)
+{
+        struct audio_operations *adev = audio_devs[dev];
+	unsigned long flags;
+	struct dma_buffparms *dmap = adev->dmap_in;
+
+	spin_lock_irqsave(&dmap->lock,flags);
+	if (!(adev->flags & DMA_DUPLEX) || !adev->d->halt_input)
+		adev->d->halt_io(dev);
+	else
+		adev->d->halt_input(dev);
+	adev->dmap_in->flags &= ~DMA_STARTED;
+
+	dmap->qlen = dmap->qhead = dmap->qtail = dmap->user_counter = 0;
+	dmap->byte_counter = 0;
+	reorganize_buffers(dev, adev->dmap_in, 1);
+	spin_unlock_irqrestore(&dmap->lock,flags);
+}
+/* MUST be called with holding the dmap->lock */
+void DMAbuf_launch_output(int dev, struct dma_buffparms *dmap)
+{
+	struct audio_operations *adev = audio_devs[dev];
+
+	if (!((adev->enable_bits * adev->go) & PCM_ENABLE_OUTPUT))
+		return;		/* Don't start DMA yet */
+	dmap->dma_mode = DMODE_OUTPUT;
+
+	if (!(dmap->flags & DMA_ACTIVE) || !(adev->flags & DMA_AUTOMODE) || (dmap->flags & DMA_NODMA)) {
+		if (!(dmap->flags & DMA_STARTED)) {
+			reorganize_buffers(dev, dmap, 0);
+			if (adev->d->prepare_for_output(dev, dmap->fragment_size, dmap->nbufs))
+				return;
+			if (!(dmap->flags & DMA_NODMA))
+				local_start_dma(adev, dmap->raw_buf_phys, dmap->bytes_in_use,DMA_MODE_WRITE);
+			dmap->flags |= DMA_STARTED;
+		}
+		if (dmap->counts[dmap->qhead] == 0)
+			dmap->counts[dmap->qhead] = dmap->fragment_size;
+		dmap->dma_mode = DMODE_OUTPUT;
+		adev->d->output_block(dev, dmap->raw_buf_phys + dmap->qhead * dmap->fragment_size,
+				      dmap->counts[dmap->qhead], 1);
+		if (adev->d->trigger)
+			adev->d->trigger(dev,adev->enable_bits * adev->go);
+	}
+	dmap->flags |= DMA_ACTIVE;
+}
+
+int DMAbuf_sync(int dev)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	unsigned long flags;
+	int n = 0;
+	struct dma_buffparms *dmap;
+
+	if (!adev->go && !(adev->enable_bits & PCM_ENABLE_OUTPUT))
+		return 0;
+
+	if (adev->dmap_out->dma_mode == DMODE_OUTPUT) {
+		dmap = adev->dmap_out;
+		spin_lock_irqsave(&dmap->lock,flags);
+		if (dmap->qlen > 0 && !(dmap->flags & DMA_ACTIVE))
+			DMAbuf_launch_output(dev, dmap);
+		adev->dmap_out->flags |= DMA_SYNCING;
+		adev->dmap_out->underrun_count = 0;
+		while (!signal_pending(current) && n++ < adev->dmap_out->nbufs &&
+		       adev->dmap_out->qlen && adev->dmap_out->underrun_count == 0) {
+			long t = dmabuf_timeout(dmap);
+			spin_unlock_irqrestore(&dmap->lock,flags);
+			/* FIXME: not safe may miss events */
+			t = interruptible_sleep_on_timeout(&adev->out_sleeper, t);
+			spin_lock_irqsave(&dmap->lock,flags);
+			if (!t) {
+				adev->dmap_out->flags &= ~DMA_SYNCING;
+				spin_unlock_irqrestore(&dmap->lock,flags);
+				return adev->dmap_out->qlen;
+			}
+		}
+		adev->dmap_out->flags &= ~(DMA_SYNCING | DMA_ACTIVE);
+		
+		/*
+		 * Some devices such as GUS have huge amount of on board RAM for the
+		 * audio data. We have to wait until the device has finished playing.
+		 */
+
+		/* still holding the lock */
+		if (adev->d->local_qlen) {   /* Device has hidden buffers */
+			while (!signal_pending(current) &&
+			       adev->d->local_qlen(dev)){
+				spin_unlock_irqrestore(&dmap->lock,flags);
+				interruptible_sleep_on_timeout(&adev->out_sleeper,
+							       dmabuf_timeout(dmap));
+				spin_lock_irqsave(&dmap->lock,flags);
+			}
+		}
+		spin_unlock_irqrestore(&dmap->lock,flags);
+	}
+	adev->dmap_out->dma_mode = DMODE_NONE;
+	return adev->dmap_out->qlen;
+}
+
+int DMAbuf_release(int dev, int mode)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	struct dma_buffparms *dmap;
+	unsigned long flags;
+
+	dmap = adev->dmap_out;
+	if (adev->open_mode & OPEN_WRITE)
+		adev->dmap_out->closing = 1;
+
+	if (adev->open_mode & OPEN_READ){
+		adev->dmap_in->closing = 1;
+		dmap = adev->dmap_in;
+	}
+	if (adev->open_mode & OPEN_WRITE)
+		if (!(adev->dmap_out->mapping_flags & DMA_MAP_MAPPED))
+			if (!signal_pending(current) && (adev->dmap_out->dma_mode == DMODE_OUTPUT))
+				DMAbuf_sync(dev);
+	if (adev->dmap_out->dma_mode == DMODE_OUTPUT)
+		memset(adev->dmap_out->raw_buf, adev->dmap_out->neutral_byte, adev->dmap_out->bytes_in_use);
+
+	DMAbuf_reset(dev);
+	spin_lock_irqsave(&dmap->lock,flags);
+	adev->d->close(dev);
+
+	if (adev->open_mode & OPEN_WRITE)
+		close_dmap(adev, adev->dmap_out);
+
+	if (adev->open_mode == OPEN_READ ||
+	    (adev->open_mode != OPEN_WRITE &&
+	     (adev->flags & DMA_DUPLEX)))
+		close_dmap(adev, adev->dmap_in);
+	adev->open_mode = 0;
+	spin_unlock_irqrestore(&dmap->lock,flags);
+	return 0;
+}
+/* called with dmap->lock dold */
+int DMAbuf_activate_recording(int dev, struct dma_buffparms *dmap)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	int  err;
+
+	if (!(adev->open_mode & OPEN_READ))
+		return 0;
+	if (!(adev->enable_bits & PCM_ENABLE_INPUT))
+		return 0;
+	if (dmap->dma_mode == DMODE_OUTPUT) {	/* Direction change */
+		/* release lock - it's not recursive */
+		spin_unlock_irq(&dmap->lock);
+		DMAbuf_sync(dev);
+		DMAbuf_reset(dev);
+		spin_lock_irq(&dmap->lock);
+		dmap->dma_mode = DMODE_NONE;
+	}
+	if (!dmap->dma_mode) {
+		reorganize_buffers(dev, dmap, 1);
+		if ((err = adev->d->prepare_for_input(dev,
+				dmap->fragment_size, dmap->nbufs)) < 0)
+			return err;
+		dmap->dma_mode = DMODE_INPUT;
+	}
+	if (!(dmap->flags & DMA_ACTIVE)) {
+		if (dmap->needs_reorg)
+			reorganize_buffers(dev, dmap, 0);
+		local_start_dma(adev, dmap->raw_buf_phys, dmap->bytes_in_use, DMA_MODE_READ);
+		adev->d->start_input(dev, dmap->raw_buf_phys + dmap->qtail * dmap->fragment_size,
+				     dmap->fragment_size, 0);
+		dmap->flags |= DMA_ACTIVE;
+		if (adev->d->trigger)
+			adev->d->trigger(dev, adev->enable_bits * adev->go);
+	}
+	return 0;
+}
+/* acquires lock */
+int DMAbuf_getrdbuffer(int dev, char **buf, int *len, int dontblock)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	unsigned long flags;
+	int err = 0, n = 0;
+	struct dma_buffparms *dmap = adev->dmap_in;
+	int go;
+
+	if (!(adev->open_mode & OPEN_READ))
+		return -EIO;
+	spin_lock_irqsave(&dmap->lock,flags);
+	if (dmap->needs_reorg)
+		reorganize_buffers(dev, dmap, 0);
+	if (adev->dmap_in->mapping_flags & DMA_MAP_MAPPED) {
+/*		  printk(KERN_WARNING "Sound: Can't read from mmapped device (1)\n");*/
+		  spin_unlock_irqrestore(&dmap->lock,flags);
+		  return -EINVAL;
+	} else while (dmap->qlen <= 0 && n++ < 10) {
+		long timeout = MAX_SCHEDULE_TIMEOUT;
+		if (!(adev->enable_bits & PCM_ENABLE_INPUT) || !adev->go) {
+			spin_unlock_irqrestore(&dmap->lock,flags);
+			return -EAGAIN;
+		}
+		if ((err = DMAbuf_activate_recording(dev, dmap)) < 0) {
+			spin_unlock_irqrestore(&dmap->lock,flags);
+			return err;
+		}
+		/* Wait for the next block */
+
+		if (dontblock) {
+			spin_unlock_irqrestore(&dmap->lock,flags);
+			return -EAGAIN;
+		}
+		if ((go = adev->go))
+			timeout = dmabuf_timeout(dmap);
+
+		spin_unlock_irqrestore(&dmap->lock,flags);
+		timeout = interruptible_sleep_on_timeout(&adev->in_sleeper,
+							 timeout);
+		if (!timeout) {
+			/* FIXME: include device name */
+			err = -EIO;
+			printk(KERN_WARNING "Sound: DMA (input) timed out - IRQ/DRQ config error?\n");
+			dma_reset_input(dev);
+		} else
+			err = -EINTR;
+		spin_lock_irqsave(&dmap->lock,flags);
+	}
+	spin_unlock_irqrestore(&dmap->lock,flags);
+
+	if (dmap->qlen <= 0)
+		return err ? err : -EINTR;
+	*buf = &dmap->raw_buf[dmap->qhead * dmap->fragment_size + dmap->counts[dmap->qhead]];
+	*len = dmap->fragment_size - dmap->counts[dmap->qhead];
+
+	return dmap->qhead;
+}
+
+int DMAbuf_rmchars(int dev, int buff_no, int c)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	struct dma_buffparms *dmap = adev->dmap_in;
+	int p = dmap->counts[dmap->qhead] + c;
+
+	if (dmap->mapping_flags & DMA_MAP_MAPPED)
+	{
+/*		  printk("Sound: Can't read from mmapped device (2)\n");*/
+		return -EINVAL;
+	}
+	else if (dmap->qlen <= 0)
+		return -EIO;
+	else if (p >= dmap->fragment_size) {  /* This buffer is completely empty */
+		dmap->counts[dmap->qhead] = 0;
+		dmap->qlen--;
+		dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
+	}
+	else dmap->counts[dmap->qhead] = p;
+
+	return 0;
+}
+/* MUST be called with dmap->lock hold */
+int DMAbuf_get_buffer_pointer(int dev, struct dma_buffparms *dmap, int direction)
+{
+	/*
+	 *	Try to approximate the active byte position of the DMA pointer within the
+	 *	buffer area as well as possible.
+	 */
+
+	int pos;
+	unsigned long f;
+
+	if (!(dmap->flags & DMA_ACTIVE))
+		pos = 0;
+	else {
+		int chan = dmap->dma;
+		
+		f=claim_dma_lock();
+		clear_dma_ff(chan);
+		
+		if(!isa_dma_bridge_buggy)
+			disable_dma(dmap->dma);
+		
+		pos = get_dma_residue(chan);
+		
+		pos = dmap->bytes_in_use - pos;
+
+		if (!(dmap->mapping_flags & DMA_MAP_MAPPED)) {
+			if (direction == DMODE_OUTPUT) {
+				if (dmap->qhead == 0)
+					if (pos > dmap->fragment_size)
+						pos = 0;
+			} else {
+				if (dmap->qtail == 0)
+					if (pos > dmap->fragment_size)
+						pos = 0;
+			}
+		}
+		if (pos < 0)
+			pos = 0;
+		if (pos >= dmap->bytes_in_use)
+			pos = 0;
+		
+		if(!isa_dma_bridge_buggy)
+			enable_dma(dmap->dma);
+			
+		release_dma_lock(f);
+	}
+	/* printk( "%04x ",  pos); */
+
+	return pos;
+}
+
+/*
+ *	DMAbuf_start_devices() is called by the /dev/music driver to start
+ *	one or more audio devices at desired moment.
+ */
+
+void DMAbuf_start_devices(unsigned int devmask)
+{
+	struct audio_operations *adev;
+	int dev;
+
+	for (dev = 0; dev < num_audiodevs; dev++) {
+		if (!(devmask & (1 << dev)))
+			continue;
+		if (!(adev = audio_devs[dev]))
+			continue;
+		if (adev->open_mode == 0)
+			continue;
+		if (adev->go)
+			continue;
+		/* OK to start the device */
+		adev->go = 1;
+		if (adev->d->trigger)
+			adev->d->trigger(dev,adev->enable_bits * adev->go);
+	}
+}
+/* via poll called without a lock ?*/
+int DMAbuf_space_in_queue(int dev)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	int len, max, tmp;
+	struct dma_buffparms *dmap = adev->dmap_out;
+	int lim = dmap->nbufs;
+
+	if (lim < 2)
+		lim = 2;
+
+	if (dmap->qlen >= lim)	/* No space at all */
+		return 0;
+
+	/*
+	 *	Verify that there are no more pending buffers than the limit
+	 *	defined by the process.
+	 */
+
+	max = dmap->max_fragments;
+	if (max > lim)
+		max = lim;
+	len = dmap->qlen;
+
+	if (adev->d->local_qlen) {
+		tmp = adev->d->local_qlen(dev);
+		if (tmp && len)
+			tmp--;	/* This buffer has been counted twice */
+		len += tmp;
+	}
+	if (dmap->byte_counter % dmap->fragment_size)	/* There is a partial fragment */
+		len = len + 1;
+
+	if (len >= max)
+		return 0;
+	return max - len;
+}
+/* MUST not hold the spinlock  - this function may sleep */
+static int output_sleep(int dev, int dontblock)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	int err = 0;
+	struct dma_buffparms *dmap = adev->dmap_out;
+	long timeout;
+	long timeout_value;
+
+	if (dontblock)
+		return -EAGAIN;
+	if (!(adev->enable_bits & PCM_ENABLE_OUTPUT))
+		return -EAGAIN;
+
+	/*
+	 * Wait for free space
+	 */
+	if (signal_pending(current))
+		return -EINTR;
+	timeout = (adev->go && !(dmap->flags & DMA_NOTIMEOUT));
+	if (timeout) 
+		timeout_value = dmabuf_timeout(dmap);
+	else
+		timeout_value = MAX_SCHEDULE_TIMEOUT;
+	timeout_value = interruptible_sleep_on_timeout(&adev->out_sleeper,
+						       timeout_value);
+	if (timeout != MAX_SCHEDULE_TIMEOUT && !timeout_value) {
+		printk(KERN_WARNING "Sound: DMA (output) timed out - IRQ/DRQ config error?\n");
+		dma_reset_output(dev);
+	} else {
+		if (signal_pending(current))
+			err = -EINTR;
+	}
+	return err;
+}
+/* called with the lock held */
+static int find_output_space(int dev, char **buf, int *size)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	struct dma_buffparms *dmap = adev->dmap_out;
+	unsigned long active_offs;
+	long len, offs;
+	int maxfrags;
+	int occupied_bytes = (dmap->user_counter % dmap->fragment_size);
+
+	*buf = dmap->raw_buf;
+	if (!(maxfrags = DMAbuf_space_in_queue(dev)) && !occupied_bytes)
+		return 0;
+
+#ifdef BE_CONSERVATIVE
+	active_offs = dmap->byte_counter + dmap->qhead * dmap->fragment_size;
+#else
+	active_offs = max(DMAbuf_get_buffer_pointer(dev, dmap, DMODE_OUTPUT), 0);
+	/* Check for pointer wrapping situation */
+	if (active_offs >= dmap->bytes_in_use)
+		active_offs = 0;
+	active_offs += dmap->byte_counter;
+#endif
+
+	offs = (dmap->user_counter % dmap->bytes_in_use) & ~SAMPLE_ROUNDUP;
+	if (offs < 0 || offs >= dmap->bytes_in_use) {
+		printk(KERN_ERR "Sound: Got unexpected offs %ld. Giving up.\n", offs);
+		printk("Counter = %ld, bytes=%d\n", dmap->user_counter, dmap->bytes_in_use);
+		return 0;
+	}
+	*buf = dmap->raw_buf + offs;
+
+	len = active_offs + dmap->bytes_in_use - dmap->user_counter;	/* Number of unused bytes in buffer */
+
+	if ((offs + len) > dmap->bytes_in_use)
+		len = dmap->bytes_in_use - offs;
+	if (len < 0) {
+		return 0;
+	}
+	if (len > ((maxfrags * dmap->fragment_size) - occupied_bytes))
+		len = (maxfrags * dmap->fragment_size) - occupied_bytes;
+	*size = len & ~SAMPLE_ROUNDUP;
+	return (*size > 0);
+}
+/* acquires lock  */
+int DMAbuf_getwrbuffer(int dev, char **buf, int *size, int dontblock)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	unsigned long flags;
+	int err = -EIO;
+	struct dma_buffparms *dmap = adev->dmap_out;
+
+	if (dmap->mapping_flags & DMA_MAP_MAPPED) {
+/*		printk(KERN_DEBUG "Sound: Can't write to mmapped device (3)\n");*/
+		return -EINVAL;
+	}
+	spin_lock_irqsave(&dmap->lock,flags);
+	if (dmap->needs_reorg)
+		reorganize_buffers(dev, dmap, 0);
+
+	if (dmap->dma_mode == DMODE_INPUT) {	/* Direction change */
+		spin_unlock_irqrestore(&dmap->lock,flags);
+		DMAbuf_reset(dev);
+		spin_lock_irqsave(&dmap->lock,flags);
+	}
+	dmap->dma_mode = DMODE_OUTPUT;
+
+	while (find_output_space(dev, buf, size) <= 0) {
+		spin_unlock_irqrestore(&dmap->lock,flags);
+		if ((err = output_sleep(dev, dontblock)) < 0) {
+			return err;
+		}
+		spin_lock_irqsave(&dmap->lock,flags);
+	}
+
+	spin_unlock_irqrestore(&dmap->lock,flags);
+	return 0;
+}
+/* has to acquire dmap->lock */
+int DMAbuf_move_wrpointer(int dev, int l)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	struct dma_buffparms *dmap = adev->dmap_out;
+	unsigned long ptr;
+	unsigned long end_ptr, p;
+	int post;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dmap->lock,flags);
+	post= (dmap->flags & DMA_POST);
+	ptr = (dmap->user_counter / dmap->fragment_size) * dmap->fragment_size;
+
+	dmap->flags &= ~DMA_POST;
+	dmap->cfrag = -1;
+	dmap->user_counter += l;
+	dmap->flags |= DMA_DIRTY;
+
+	if (dmap->byte_counter >= dmap->max_byte_counter) {
+		/* Wrap the byte counters */
+		long decr = dmap->byte_counter;
+		dmap->byte_counter = (dmap->byte_counter % dmap->bytes_in_use);
+		decr -= dmap->byte_counter;
+		dmap->user_counter -= decr;
+	}
+	end_ptr = (dmap->user_counter / dmap->fragment_size) * dmap->fragment_size;
+
+	p = (dmap->user_counter - 1) % dmap->bytes_in_use;
+	dmap->neutral_byte = dmap->raw_buf[p];
+
+	/* Update the fragment based bookkeeping too */
+	while (ptr < end_ptr) {
+		dmap->counts[dmap->qtail] = dmap->fragment_size;
+		dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+		dmap->qlen++;
+		ptr += dmap->fragment_size;
+	}
+
+	dmap->counts[dmap->qtail] = dmap->user_counter - ptr;
+
+	/*
+	 *	Let the low level driver perform some postprocessing to
+	 *	the written data.
+	 */
+	if (adev->d->postprocess_write)
+		adev->d->postprocess_write(dev);
+
+	if (!(dmap->flags & DMA_ACTIVE))
+		if (dmap->qlen > 1 || (dmap->qlen > 0 && (post || dmap->qlen >= dmap->nbufs - 1)))
+			DMAbuf_launch_output(dev, dmap);
+
+	spin_unlock_irqrestore(&dmap->lock,flags);
+	return 0;
+}
+
+int DMAbuf_start_dma(int dev, unsigned long physaddr, int count, int dma_mode)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	struct dma_buffparms *dmap = (dma_mode == DMA_MODE_WRITE) ? adev->dmap_out : adev->dmap_in;
+
+	if (dmap->raw_buf == NULL) {
+		printk(KERN_ERR "sound: DMA buffer(1) == NULL\n");
+		printk("Device %d, chn=%s\n", dev, (dmap == adev->dmap_out) ? "out" : "in");
+		return 0;
+	}
+	if (dmap->dma < 0)
+		return 0;
+	sound_start_dma(dmap, physaddr, count, dma_mode);
+	return count;
+}
+EXPORT_SYMBOL(DMAbuf_start_dma);
+
+static int local_start_dma(struct audio_operations *adev, unsigned long physaddr, int count, int dma_mode)
+{
+	struct dma_buffparms *dmap = (dma_mode == DMA_MODE_WRITE) ? adev->dmap_out : adev->dmap_in;
+
+	if (dmap->raw_buf == NULL) {
+		printk(KERN_ERR "sound: DMA buffer(2) == NULL\n");
+		printk(KERN_ERR "Device %s, chn=%s\n", adev->name, (dmap == adev->dmap_out) ? "out" : "in");
+		return 0;
+	}
+	if (dmap->flags & DMA_NODMA)
+		return 1;
+	if (dmap->dma < 0)
+		return 0;
+	sound_start_dma(dmap, dmap->raw_buf_phys, dmap->bytes_in_use, dma_mode | DMA_AUTOINIT);
+	dmap->flags |= DMA_STARTED;
+	return count;
+}
+
+static void finish_output_interrupt(int dev, struct dma_buffparms *dmap)
+{
+	struct audio_operations *adev = audio_devs[dev];
+
+	if (dmap->audio_callback != NULL)
+		dmap->audio_callback(dev, dmap->callback_parm);
+	wake_up(&adev->out_sleeper);
+	wake_up(&adev->poll_sleeper);
+}
+/* called with dmap->lock held in irq context*/
+static void do_outputintr(int dev, int dummy)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	struct dma_buffparms *dmap = adev->dmap_out;
+	int this_fragment;
+
+	if (dmap->raw_buf == NULL) {
+		printk(KERN_ERR "Sound: Error. Audio interrupt (%d) after freeing buffers.\n", dev);
+		return;
+	}
+	if (dmap->mapping_flags & DMA_MAP_MAPPED) {	/* Virtual memory mapped access */
+		/* mmapped access */
+		dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
+		if (dmap->qhead == 0) {	    /* Wrapped */
+			dmap->byte_counter += dmap->bytes_in_use;
+			if (dmap->byte_counter >= dmap->max_byte_counter) {	/* Overflow */
+				long decr = dmap->byte_counter;
+				dmap->byte_counter = (dmap->byte_counter % dmap->bytes_in_use);
+				decr -= dmap->byte_counter;
+				dmap->user_counter -= decr;
+			}
+		}
+		dmap->qlen++;	/* Yes increment it (don't decrement) */
+		if (!(adev->flags & DMA_AUTOMODE))
+			dmap->flags &= ~DMA_ACTIVE;
+		dmap->counts[dmap->qhead] = dmap->fragment_size;
+		DMAbuf_launch_output(dev, dmap);
+		finish_output_interrupt(dev, dmap);
+		return;
+	}
+
+	dmap->qlen--;
+	this_fragment = dmap->qhead;
+	dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
+
+	if (dmap->qhead == 0) {	/* Wrapped */
+		dmap->byte_counter += dmap->bytes_in_use;
+		if (dmap->byte_counter >= dmap->max_byte_counter) {	/* Overflow */
+			long decr = dmap->byte_counter;
+			dmap->byte_counter = (dmap->byte_counter % dmap->bytes_in_use);
+			decr -= dmap->byte_counter;
+			dmap->user_counter -= decr;
+		}
+	}
+	if (!(adev->flags & DMA_AUTOMODE))
+		dmap->flags &= ~DMA_ACTIVE;
+		
+	/*
+	 *	This is  dmap->qlen <= 0 except when closing when
+	 *	dmap->qlen < 0
+	 */
+	 
+	while (dmap->qlen <= -dmap->closing) {
+		dmap->underrun_count++;
+		dmap->qlen++;
+		if ((dmap->flags & DMA_DIRTY) && dmap->applic_profile != APF_CPUINTENS) {
+			dmap->flags &= ~DMA_DIRTY;
+			memset(adev->dmap_out->raw_buf, adev->dmap_out->neutral_byte,
+			       adev->dmap_out->buffsize);
+		}
+		dmap->user_counter += dmap->fragment_size;
+		dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+	}
+	if (dmap->qlen > 0)
+		DMAbuf_launch_output(dev, dmap);
+	finish_output_interrupt(dev, dmap);
+}
+/* called in irq context */
+void DMAbuf_outputintr(int dev, int notify_only)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	unsigned long flags;
+	struct dma_buffparms *dmap = adev->dmap_out;
+
+	spin_lock_irqsave(&dmap->lock,flags);
+	if (!(dmap->flags & DMA_NODMA)) {
+		int chan = dmap->dma, pos, n;
+		unsigned long f;
+		
+		f=claim_dma_lock();
+		
+		if(!isa_dma_bridge_buggy)
+			disable_dma(dmap->dma);
+		clear_dma_ff(chan);
+		pos = dmap->bytes_in_use - get_dma_residue(chan);
+		if(!isa_dma_bridge_buggy)
+			enable_dma(dmap->dma);
+		release_dma_lock(f);
+		
+		pos = pos / dmap->fragment_size;	/* Actual qhead */
+		if (pos < 0 || pos >= dmap->nbufs)
+			pos = 0;
+		n = 0;
+		while (dmap->qhead != pos && n++ < dmap->nbufs)
+			do_outputintr(dev, notify_only);
+	}
+	else
+		do_outputintr(dev, notify_only);
+	spin_unlock_irqrestore(&dmap->lock,flags);
+}
+EXPORT_SYMBOL(DMAbuf_outputintr);
+
+/* called with dmap->lock held in irq context */
+static void do_inputintr(int dev)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	struct dma_buffparms *dmap = adev->dmap_in;
+
+	if (dmap->raw_buf == NULL) {
+		printk(KERN_ERR "Sound: Fatal error. Audio interrupt after freeing buffers.\n");
+		return;
+	}
+	if (dmap->mapping_flags & DMA_MAP_MAPPED) {
+		dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+		if (dmap->qtail == 0) {		/* Wrapped */
+			dmap->byte_counter += dmap->bytes_in_use;
+			if (dmap->byte_counter >= dmap->max_byte_counter) {	/* Overflow */
+				long decr = dmap->byte_counter;
+				dmap->byte_counter = (dmap->byte_counter % dmap->bytes_in_use) + dmap->bytes_in_use;
+				decr -= dmap->byte_counter;
+				dmap->user_counter -= decr;
+			}
+		}
+		dmap->qlen++;
+
+		if (!(adev->flags & DMA_AUTOMODE)) {
+			if (dmap->needs_reorg)
+				reorganize_buffers(dev, dmap, 0);
+			local_start_dma(adev, dmap->raw_buf_phys, dmap->bytes_in_use,DMA_MODE_READ);
+			adev->d->start_input(dev, dmap->raw_buf_phys + dmap->qtail * dmap->fragment_size,
+					     dmap->fragment_size, 1);
+			if (adev->d->trigger)
+				adev->d->trigger(dev, adev->enable_bits * adev->go);
+		}
+		dmap->flags |= DMA_ACTIVE;
+	} else if (dmap->qlen >= (dmap->nbufs - 1)) {
+		printk(KERN_WARNING "Sound: Recording overrun\n");
+		dmap->underrun_count++;
+
+		/* Just throw away the oldest fragment but keep the engine running */
+		dmap->qhead = (dmap->qhead + 1) % dmap->nbufs;
+		dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+	} else if (dmap->qlen >= 0 && dmap->qlen < dmap->nbufs) {
+		dmap->qlen++;
+		dmap->qtail = (dmap->qtail + 1) % dmap->nbufs;
+		if (dmap->qtail == 0) {		/* Wrapped */
+			dmap->byte_counter += dmap->bytes_in_use;
+			if (dmap->byte_counter >= dmap->max_byte_counter) {	/* Overflow */
+				long decr = dmap->byte_counter;
+				dmap->byte_counter = (dmap->byte_counter % dmap->bytes_in_use) + dmap->bytes_in_use;
+				decr -= dmap->byte_counter;
+				dmap->user_counter -= decr;
+			}
+		}
+	}
+	if (!(adev->flags & DMA_AUTOMODE) || (dmap->flags & DMA_NODMA)) {
+		local_start_dma(adev, dmap->raw_buf_phys, dmap->bytes_in_use, DMA_MODE_READ);
+		adev->d->start_input(dev, dmap->raw_buf_phys + dmap->qtail * dmap->fragment_size, dmap->fragment_size, 1);
+		if (adev->d->trigger)
+			adev->d->trigger(dev,adev->enable_bits * adev->go);
+	}
+	dmap->flags |= DMA_ACTIVE;
+	if (dmap->qlen > 0)
+	{
+		wake_up(&adev->in_sleeper);
+		wake_up(&adev->poll_sleeper);
+	}
+}
+/* called in irq context */
+void DMAbuf_inputintr(int dev)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	struct dma_buffparms *dmap = adev->dmap_in;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dmap->lock,flags);
+
+	if (!(dmap->flags & DMA_NODMA)) {
+		int chan = dmap->dma, pos, n;
+		unsigned long f;
+		
+		f=claim_dma_lock();
+		if(!isa_dma_bridge_buggy)
+			disable_dma(dmap->dma);
+		clear_dma_ff(chan);
+		pos = dmap->bytes_in_use - get_dma_residue(chan);
+		if(!isa_dma_bridge_buggy)
+			enable_dma(dmap->dma);
+		release_dma_lock(f);
+
+		pos = pos / dmap->fragment_size;	/* Actual qhead */
+		if (pos < 0 || pos >= dmap->nbufs)
+			pos = 0;
+
+		n = 0;
+		while (dmap->qtail != pos && ++n < dmap->nbufs)
+			do_inputintr(dev);
+	} else
+		do_inputintr(dev);
+	spin_unlock_irqrestore(&dmap->lock,flags);
+}
+EXPORT_SYMBOL(DMAbuf_inputintr);
+
+void DMAbuf_init(int dev, int dma1, int dma2)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	/*
+	 * NOTE! This routine could be called several times.
+	 */
+
+	if (adev && adev->dmap_out == NULL) {
+		if (adev->d == NULL)
+			panic("OSS: audio_devs[%d]->d == NULL\n", dev);
+
+		if (adev->parent_dev) {	 /* Use DMA map of the parent dev */
+			int parent = adev->parent_dev - 1;
+			adev->dmap_out = audio_devs[parent]->dmap_out;
+			adev->dmap_in = audio_devs[parent]->dmap_in;
+		} else {
+			adev->dmap_out = adev->dmap_in = &adev->dmaps[0];
+			adev->dmap_out->dma = dma1;
+			if (adev->flags & DMA_DUPLEX) {
+				adev->dmap_in = &adev->dmaps[1];
+				adev->dmap_in->dma = dma2;
+			}
+		}
+		/* Persistent DMA buffers allocated here */
+		if (sound_dmap_flag == DMAP_KEEP_ON_CLOSE) {
+			if (adev->dmap_in->raw_buf == NULL)
+				sound_alloc_dmap(adev->dmap_in);
+			if (adev->dmap_out->raw_buf == NULL)
+				sound_alloc_dmap(adev->dmap_out);
+		}
+	}
+}
+
+/* No kernel lock - DMAbuf_activate_recording protected by global cli/sti */
+static unsigned int poll_input(struct file * file, int dev, poll_table *wait)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	struct dma_buffparms *dmap = adev->dmap_in;
+
+	if (!(adev->open_mode & OPEN_READ))
+		return 0;
+	if (dmap->mapping_flags & DMA_MAP_MAPPED) {
+		if (dmap->qlen)
+			return POLLIN | POLLRDNORM;
+		return 0;
+	}
+	if (dmap->dma_mode != DMODE_INPUT) {
+		if (dmap->dma_mode == DMODE_NONE &&
+		    adev->enable_bits & PCM_ENABLE_INPUT &&
+		    !dmap->qlen && adev->go) {
+			unsigned long flags;
+			
+			spin_lock_irqsave(&dmap->lock,flags);
+			DMAbuf_activate_recording(dev, dmap);
+			spin_unlock_irqrestore(&dmap->lock,flags);
+		}
+		return 0;
+	}
+	if (!dmap->qlen)
+		return 0;
+	return POLLIN | POLLRDNORM;
+}
+
+static unsigned int poll_output(struct file * file, int dev, poll_table *wait)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	struct dma_buffparms *dmap = adev->dmap_out;
+	
+	if (!(adev->open_mode & OPEN_WRITE))
+		return 0;
+	if (dmap->mapping_flags & DMA_MAP_MAPPED) {
+		if (dmap->qlen)
+			return POLLOUT | POLLWRNORM;
+		return 0;
+	}
+	if (dmap->dma_mode == DMODE_INPUT)
+		return 0;
+	if (dmap->dma_mode == DMODE_NONE)
+		return POLLOUT | POLLWRNORM;
+	if (!DMAbuf_space_in_queue(dev))
+		return 0;
+	return POLLOUT | POLLWRNORM;
+}
+
+unsigned int DMAbuf_poll(struct file * file, int dev, poll_table *wait)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	poll_wait(file, &adev->poll_sleeper, wait);
+	return poll_input(file, dev, wait) | poll_output(file, dev, wait);
+}
+
+void DMAbuf_deinit(int dev)
+{
+	struct audio_operations *adev = audio_devs[dev];
+	/* This routine is called when driver is being unloaded */
+	if (!adev)
+		return;
+
+	/* Persistent DMA buffers deallocated here */
+	if (sound_dmap_flag == DMAP_KEEP_ON_CLOSE) {
+		sound_free_dmap(adev->dmap_out);
+		if (adev->flags & DMA_DUPLEX)
+			sound_free_dmap(adev->dmap_in);
+	}
+}
diff --git a/linux/sound/oss/dmasound/Kconfig b/linux/sound/oss/dmasound/Kconfig
new file mode 100644
index 0000000..f456574
--- /dev/null
+++ b/linux/sound/oss/dmasound/Kconfig
@@ -0,0 +1,45 @@
+config DMASOUND_ATARI
+	tristate "Atari DMA sound support"
+	depends on ATARI && SOUND
+	select DMASOUND
+	help
+	  If you want to use the internal audio of your Atari in Linux, answer
+	  Y to this question. This will provide a Sun-like /dev/audio,
+	  compatible with the Linux/i386 sound system. Otherwise, say N.
+
+	  This driver is also available as a module ( = code which can be
+	  inserted in and removed from the running kernel whenever you
+	  want). If you want to compile it as a module, say M here and read
+	  <file:Documentation/kbuild/modules.txt>.
+
+config DMASOUND_PAULA
+	tristate "Amiga DMA sound support"
+	depends on AMIGA && SOUND
+	select DMASOUND
+	help
+	  If you want to use the internal audio of your Amiga in Linux, answer
+	  Y to this question. This will provide a Sun-like /dev/audio,
+	  compatible with the Linux/i386 sound system. Otherwise, say N.
+
+	  This driver is also available as a module ( = code which can be
+	  inserted in and removed from the running kernel whenever you
+	  want). If you want to compile it as a module, say M here and read
+	  <file:Documentation/kbuild/modules.txt>.
+
+config DMASOUND_Q40
+	tristate "Q40 sound support"
+	depends on Q40 && SOUND
+	select DMASOUND
+	help
+	  If you want to use the internal audio of your Q40 in Linux, answer
+	  Y to this question. This will provide a Sun-like /dev/audio,
+	  compatible with the Linux/i386 sound system. Otherwise, say N.
+
+	  This driver is also available as a module ( = code which can be
+	  inserted in and removed from the running kernel whenever you
+	  want). If you want to compile it as a module, say M here and read
+	  <file:Documentation/kbuild/modules.txt>.
+
+config DMASOUND
+	tristate
+	select SOUND_OSS_CORE
diff --git a/linux/sound/oss/dmasound/Makefile b/linux/sound/oss/dmasound/Makefile
new file mode 100644
index 0000000..3c15316
--- /dev/null
+++ b/linux/sound/oss/dmasound/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the DMA sound driver
+#
+
+obj-$(CONFIG_DMASOUND_ATARI)	+= dmasound_core.o dmasound_atari.o
+obj-$(CONFIG_DMASOUND_PAULA)	+= dmasound_core.o dmasound_paula.o
+obj-$(CONFIG_DMASOUND_Q40)	+= dmasound_core.o dmasound_q40.o
diff --git a/linux/sound/oss/dmasound/dmasound.h b/linux/sound/oss/dmasound/dmasound.h
new file mode 100644
index 0000000..1308d8d
--- /dev/null
+++ b/linux/sound/oss/dmasound/dmasound.h
@@ -0,0 +1,262 @@
+#ifndef _dmasound_h_
+/*
+ *  linux/sound/oss/dmasound/dmasound.h
+ *
+ *
+ *  Minor numbers for the sound driver.
+ *
+ *  Unfortunately Creative called the codec chip of SB as a DSP. For this
+ *  reason the /dev/dsp is reserved for digitized audio use. There is a
+ *  device for true DSP processors but it will be called something else.
+ *  In v3.0 it's /dev/sndproc but this could be a temporary solution.
+ */
+#define _dmasound_h_
+
+#include <linux/types.h>
+
+#define SND_NDEVS	256	/* Number of supported devices */
+#define SND_DEV_CTL	0	/* Control port /dev/mixer */
+#define SND_DEV_SEQ	1	/* Sequencer output /dev/sequencer (FM
+				   synthesizer and MIDI output) */
+#define SND_DEV_MIDIN	2	/* Raw midi access */
+#define SND_DEV_DSP	3	/* Digitized voice /dev/dsp */
+#define SND_DEV_AUDIO	4	/* Sparc compatible /dev/audio */
+#define SND_DEV_DSP16	5	/* Like /dev/dsp but 16 bits/sample */
+#define SND_DEV_STATUS	6	/* /dev/sndstat */
+/* #7 not in use now. Was in 2.4. Free for use after v3.0. */
+#define SND_DEV_SEQ2	8	/* /dev/sequencer, level 2 interface */
+#define SND_DEV_SNDPROC 9	/* /dev/sndproc for programmable devices */
+#define SND_DEV_PSS	SND_DEV_SNDPROC
+
+/* switch on various prinks */
+#define DEBUG_DMASOUND 1
+
+#define MAX_AUDIO_DEV	5
+#define MAX_MIXER_DEV	4
+#define MAX_SYNTH_DEV	3
+#define MAX_MIDI_DEV	6
+#define MAX_TIMER_DEV	3
+
+#define MAX_CATCH_RADIUS	10
+
+#define le2be16(x)	(((x)<<8 & 0xff00) | ((x)>>8 & 0x00ff))
+#define le2be16dbl(x)	(((x)<<8 & 0xff00ff00) | ((x)>>8 & 0x00ff00ff))
+
+#define IOCTL_IN(arg, ret) \
+	do { int error = get_user(ret, (int __user *)(arg)); \
+		if (error) return error; \
+	} while (0)
+#define IOCTL_OUT(arg, ret)	ioctl_return((int __user *)(arg), ret)
+
+static inline int ioctl_return(int __user *addr, int value)
+{
+	return value < 0 ? value : put_user(value, addr);
+}
+
+
+    /*
+     *  Configuration
+     */
+
+#undef HAS_8BIT_TABLES
+
+#if defined(CONFIG_DMASOUND_ATARI) || defined(CONFIG_DMASOUND_ATARI_MODULE) ||\
+    defined(CONFIG_DMASOUND_PAULA) || defined(CONFIG_DMASOUND_PAULA_MODULE) ||\
+    defined(CONFIG_DMASOUND_Q40) || defined(CONFIG_DMASOUND_Q40_MODULE)
+#define HAS_8BIT_TABLES
+#define MIN_BUFFERS	4
+#define MIN_BUFSIZE	(1<<12)	/* in bytes (- where does this come from ?) */
+#define MIN_FRAG_SIZE	8	/* not 100% sure about this */
+#define MAX_BUFSIZE	(1<<17)	/* Limit for Amiga is 128 kb */
+#define MAX_FRAG_SIZE	15	/* allow *4 for mono-8 => stereo-16 (for multi) */
+
+#else /* is pmac and multi is off */
+
+#define MIN_BUFFERS	2
+#define MIN_BUFSIZE	(1<<8)	/* in bytes */
+#define MIN_FRAG_SIZE	8
+#define MAX_BUFSIZE	(1<<18)	/* this is somewhat arbitrary for pmac */
+#define MAX_FRAG_SIZE	16	/* need to allow *4 for mono-8 => stereo-16 */
+#endif
+
+#define DEFAULT_N_BUFFERS 4
+#define DEFAULT_BUFF_SIZE (1<<15)
+
+    /*
+     *  Initialization
+     */
+
+extern int dmasound_init(void);
+#ifdef MODULE
+extern void dmasound_deinit(void);
+#else
+#define dmasound_deinit()	do { } while (0)
+#endif
+
+/* description of the set-up applies to either hard or soft settings */
+
+typedef struct {
+    int format;		/* AFMT_* */
+    int stereo;		/* 0 = mono, 1 = stereo */
+    int size;		/* 8/16 bit*/
+    int speed;		/* speed */
+} SETTINGS;
+
+    /*
+     *  Machine definitions
+     */
+
+typedef struct {
+    const char *name;
+    const char *name2;
+    struct module *owner;
+    void *(*dma_alloc)(unsigned int, gfp_t);
+    void (*dma_free)(void *, unsigned int);
+    int (*irqinit)(void);
+#ifdef MODULE
+    void (*irqcleanup)(void);
+#endif
+    void (*init)(void);
+    void (*silence)(void);
+    int (*setFormat)(int);
+    int (*setVolume)(int);
+    int (*setBass)(int);
+    int (*setTreble)(int);
+    int (*setGain)(int);
+    void (*play)(void);
+    void (*record)(void);		/* optional */
+    void (*mixer_init)(void);		/* optional */
+    int (*mixer_ioctl)(u_int, u_long);	/* optional */
+    int (*write_sq_setup)(void);	/* optional */
+    int (*read_sq_setup)(void);		/* optional */
+    int (*sq_open)(fmode_t);		/* optional */
+    int (*state_info)(char *, size_t);	/* optional */
+    void (*abort_read)(void);		/* optional */
+    int min_dsp_speed;
+    int max_dsp_speed;
+    int version ;
+    int hardware_afmts ;		/* OSS says we only return h'ware info */
+					/* when queried via SNDCTL_DSP_GETFMTS */
+    int capabilities ;		/* low-level reply to SNDCTL_DSP_GETCAPS */
+    SETTINGS default_hard ;	/* open() or init() should set something valid */
+    SETTINGS default_soft ;	/* you can make it look like old OSS, if you want to */
+} MACHINE;
+
+    /*
+     *  Low level stuff
+     */
+
+typedef struct {
+    ssize_t (*ct_ulaw)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+    ssize_t (*ct_alaw)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+    ssize_t (*ct_s8)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+    ssize_t (*ct_u8)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+    ssize_t (*ct_s16be)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+    ssize_t (*ct_u16be)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+    ssize_t (*ct_s16le)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+    ssize_t (*ct_u16le)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+} TRANS;
+
+struct sound_settings {
+    MACHINE mach;	/* machine dependent things */
+    SETTINGS hard;	/* hardware settings */
+    SETTINGS soft;	/* software settings */
+    SETTINGS dsp;	/* /dev/dsp default settings */
+    TRANS *trans_write;	/* supported translations */
+    int volume_left;	/* volume (range is machine dependent) */
+    int volume_right;
+    int bass;		/* tone (range is machine dependent) */
+    int treble;
+    int gain;
+    int minDev;		/* minor device number currently open */
+    spinlock_t lock;
+};
+
+extern struct sound_settings dmasound;
+
+#ifdef HAS_8BIT_TABLES
+extern char dmasound_ulaw2dma8[];
+extern char dmasound_alaw2dma8[];
+#endif
+
+    /*
+     *  Mid level stuff
+     */
+
+static inline int dmasound_set_volume(int volume)
+{
+	return dmasound.mach.setVolume(volume);
+}
+
+static inline int dmasound_set_bass(int bass)
+{
+	return dmasound.mach.setBass ? dmasound.mach.setBass(bass) : 50;
+}
+
+static inline int dmasound_set_treble(int treble)
+{
+	return dmasound.mach.setTreble ? dmasound.mach.setTreble(treble) : 50;
+}
+
+static inline int dmasound_set_gain(int gain)
+{
+	return dmasound.mach.setGain ? dmasound.mach.setGain(gain) : 100;
+}
+
+
+    /*
+     * Sound queue stuff, the heart of the driver
+     */
+
+struct sound_queue {
+    /* buffers allocated for this queue */
+    int numBufs;		/* real limits on what the user can have */
+    int bufSize;		/* in bytes */
+    char **buffers;
+
+    /* current parameters */
+    int locked ;		/* params cannot be modified when != 0 */
+    int user_frags ;		/* user requests this many */
+    int user_frag_size ;	/* of this size */
+    int max_count;		/* actual # fragments <= numBufs */
+    int block_size;		/* internal block size in bytes */
+    int max_active;		/* in-use fragments <= max_count */
+
+    /* it shouldn't be necessary to declare any of these volatile */
+    int front, rear, count;
+    int rear_size;
+    /*
+     *	The use of the playing field depends on the hardware
+     *
+     *	Atari, PMac: The number of frames that are loaded/playing
+     *
+     *	Amiga: Bit 0 is set: a frame is loaded
+     *	       Bit 1 is set: a frame is playing
+     */
+    int active;
+    wait_queue_head_t action_queue, open_queue, sync_queue;
+    int non_blocking;
+    int busy, syncing, xruns, died;
+};
+
+#define SLEEP(queue)		interruptible_sleep_on_timeout(&queue, HZ)
+#define WAKE_UP(queue)		(wake_up_interruptible(&queue))
+
+extern struct sound_queue dmasound_write_sq;
+#define write_sq	dmasound_write_sq
+
+extern int dmasound_catchRadius;
+#define catchRadius	dmasound_catchRadius
+
+/* define the value to be put in the byte-swap reg in mac-io
+   when we want it to swap for us.
+*/
+#define BS_VAL 1
+
+#define SW_INPUT_VOLUME_SCALE	4
+#define SW_INPUT_VOLUME_DEFAULT	(128 / SW_INPUT_VOLUME_SCALE)
+
+extern int expand_read_bal;	/* Balance factor for reading */
+extern uint software_input_volume; /* software implemented recording volume! */
+
+#endif /* _dmasound_h_ */
diff --git a/linux/sound/oss/dmasound/dmasound_atari.c b/linux/sound/oss/dmasound/dmasound_atari.c
new file mode 100644
index 0000000..13c2144
--- /dev/null
+++ b/linux/sound/oss/dmasound/dmasound_atari.c
@@ -0,0 +1,1620 @@
+/*
+ *  linux/sound/oss/dmasound/dmasound_atari.c
+ *
+ *  Atari TT and Falcon DMA Sound Driver
+ *
+ *  See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits
+ *  prior to 28/01/2001
+ *
+ *  28/01/2001 [0.1] Iain Sandoe
+ *		     - added versioning
+ *		     - put in and populated the hardware_afmts field.
+ *             [0.2] - put in SNDCTL_DSP_GETCAPS value.
+ *  01/02/2001 [0.3] - put in default hard/soft settings.
+ */
+
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/soundcard.h>
+#include <linux/mm.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+
+#include <asm/uaccess.h>
+#include <asm/atariints.h>
+#include <asm/atari_stram.h>
+
+#include "dmasound.h"
+
+#define DMASOUND_ATARI_REVISION 0
+#define DMASOUND_ATARI_EDITION 3
+
+extern void atari_microwire_cmd(int cmd);
+
+static int is_falcon;
+static int write_sq_ignore_int;	/* ++TeSche: used for Falcon */
+
+static int expand_bal;	/* Balance factor for expanding (not volume!) */
+static int expand_data;	/* Data for expanding */
+
+
+/*** Translations ************************************************************/
+
+
+/* ++TeSche: radically changed for new expanding purposes...
+ *
+ * These two routines now deal with copying/expanding/translating the samples
+ * from user space into our buffer at the right frequency. They take care about
+ * how much data there's actually to read, how much buffer space there is and
+ * to convert samples into the right frequency/encoding. They will only work on
+ * complete samples so it may happen they leave some bytes in the input stream
+ * if the user didn't write a multiple of the current sample size. They both
+ * return the number of bytes they've used from both streams so you may detect
+ * such a situation. Luckily all programs should be able to cope with that.
+ *
+ * I think I've optimized anything as far as one can do in plain C, all
+ * variables should fit in registers and the loops are really short. There's
+ * one loop for every possible situation. Writing a more generalized and thus
+ * parameterized loop would only produce slower code. Feel free to optimize
+ * this in assembler if you like. :)
+ *
+ * I think these routines belong here because they're not yet really hardware
+ * independent, especially the fact that the Falcon can play 16bit samples
+ * only in stereo is hardcoded in both of them!
+ *
+ * ++geert: split in even more functions (one per format)
+ */
+
+static ssize_t ata_ct_law(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft);
+static ssize_t ata_ct_s8(const u_char __user *userPtr, size_t userCount,
+			 u_char frame[], ssize_t *frameUsed,
+			 ssize_t frameLeft);
+static ssize_t ata_ct_u8(const u_char __user *userPtr, size_t userCount,
+			 u_char frame[], ssize_t *frameUsed,
+			 ssize_t frameLeft);
+static ssize_t ata_ct_s16be(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft);
+static ssize_t ata_ct_u16be(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft);
+static ssize_t ata_ct_s16le(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft);
+static ssize_t ata_ct_u16le(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft);
+static ssize_t ata_ctx_law(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft);
+static ssize_t ata_ctx_s8(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft);
+static ssize_t ata_ctx_u8(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft);
+static ssize_t ata_ctx_s16be(const u_char __user *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft);
+static ssize_t ata_ctx_u16be(const u_char __user *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft);
+static ssize_t ata_ctx_s16le(const u_char __user *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft);
+static ssize_t ata_ctx_u16le(const u_char __user *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft);
+
+
+/*** Low level stuff *********************************************************/
+
+
+static void *AtaAlloc(unsigned int size, gfp_t flags);
+static void AtaFree(void *, unsigned int size);
+static int AtaIrqInit(void);
+#ifdef MODULE
+static void AtaIrqCleanUp(void);
+#endif /* MODULE */
+static int AtaSetBass(int bass);
+static int AtaSetTreble(int treble);
+static void TTSilence(void);
+static void TTInit(void);
+static int TTSetFormat(int format);
+static int TTSetVolume(int volume);
+static int TTSetGain(int gain);
+static void FalconSilence(void);
+static void FalconInit(void);
+static int FalconSetFormat(int format);
+static int FalconSetVolume(int volume);
+static void AtaPlayNextFrame(int index);
+static void AtaPlay(void);
+static irqreturn_t AtaInterrupt(int irq, void *dummy);
+
+/*** Mid level stuff *********************************************************/
+
+static void TTMixerInit(void);
+static void FalconMixerInit(void);
+static int AtaMixerIoctl(u_int cmd, u_long arg);
+static int TTMixerIoctl(u_int cmd, u_long arg);
+static int FalconMixerIoctl(u_int cmd, u_long arg);
+static int AtaWriteSqSetup(void);
+static int AtaSqOpen(fmode_t mode);
+static int TTStateInfo(char *buffer, size_t space);
+static int FalconStateInfo(char *buffer, size_t space);
+
+
+/*** Translations ************************************************************/
+
+
+static ssize_t ata_ct_law(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft)
+{
+	char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8
+							  : dmasound_alaw2dma8;
+	ssize_t count, used;
+	u_char *p = &frame[*frameUsed];
+
+	count = min_t(unsigned long, userCount, frameLeft);
+	if (dmasound.soft.stereo)
+		count &= ~1;
+	used = count;
+	while (count > 0) {
+		u_char data;
+		if (get_user(data, userPtr++))
+			return -EFAULT;
+		*p++ = table[data];
+		count--;
+	}
+	*frameUsed += used;
+	return used;
+}
+
+
+static ssize_t ata_ct_s8(const u_char __user *userPtr, size_t userCount,
+			 u_char frame[], ssize_t *frameUsed,
+			 ssize_t frameLeft)
+{
+	ssize_t count, used;
+	void *p = &frame[*frameUsed];
+
+	count = min_t(unsigned long, userCount, frameLeft);
+	if (dmasound.soft.stereo)
+		count &= ~1;
+	used = count;
+	if (copy_from_user(p, userPtr, count))
+		return -EFAULT;
+	*frameUsed += used;
+	return used;
+}
+
+
+static ssize_t ata_ct_u8(const u_char __user *userPtr, size_t userCount,
+			 u_char frame[], ssize_t *frameUsed,
+			 ssize_t frameLeft)
+{
+	ssize_t count, used;
+
+	if (!dmasound.soft.stereo) {
+		u_char *p = &frame[*frameUsed];
+		count = min_t(unsigned long, userCount, frameLeft);
+		used = count;
+		while (count > 0) {
+			u_char data;
+			if (get_user(data, userPtr++))
+				return -EFAULT;
+			*p++ = data ^ 0x80;
+			count--;
+		}
+	} else {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		count = min_t(unsigned long, userCount, frameLeft)>>1;
+		used = count*2;
+		while (count > 0) {
+			u_short data;
+			if (get_user(data, (u_short __user *)userPtr))
+				return -EFAULT;
+			userPtr += 2;
+			*p++ = data ^ 0x8080;
+			count--;
+		}
+	}
+	*frameUsed += used;
+	return used;
+}
+
+
+static ssize_t ata_ct_s16be(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft)
+{
+	ssize_t count, used;
+
+	if (!dmasound.soft.stereo) {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		count = min_t(unsigned long, userCount, frameLeft)>>1;
+		used = count*2;
+		while (count > 0) {
+			u_short data;
+			if (get_user(data, (u_short __user *)userPtr))
+				return -EFAULT;
+			userPtr += 2;
+			*p++ = data;
+			*p++ = data;
+			count--;
+		}
+		*frameUsed += used*2;
+	} else {
+		void *p = (u_short *)&frame[*frameUsed];
+		count = min_t(unsigned long, userCount, frameLeft) & ~3;
+		used = count;
+		if (copy_from_user(p, userPtr, count))
+			return -EFAULT;
+		*frameUsed += used;
+	}
+	return used;
+}
+
+
+static ssize_t ata_ct_u16be(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft)
+{
+	ssize_t count, used;
+
+	if (!dmasound.soft.stereo) {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		count = min_t(unsigned long, userCount, frameLeft)>>1;
+		used = count*2;
+		while (count > 0) {
+			u_short data;
+			if (get_user(data, (u_short __user *)userPtr))
+				return -EFAULT;
+			userPtr += 2;
+			data ^= 0x8000;
+			*p++ = data;
+			*p++ = data;
+			count--;
+		}
+		*frameUsed += used*2;
+	} else {
+		u_long *p = (u_long *)&frame[*frameUsed];
+		count = min_t(unsigned long, userCount, frameLeft)>>2;
+		used = count*4;
+		while (count > 0) {
+			u_int data;
+			if (get_user(data, (u_int __user *)userPtr))
+				return -EFAULT;
+			userPtr += 4;
+			*p++ = data ^ 0x80008000;
+			count--;
+		}
+		*frameUsed += used;
+	}
+	return used;
+}
+
+
+static ssize_t ata_ct_s16le(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft)
+{
+	ssize_t count, used;
+
+	count = frameLeft;
+	if (!dmasound.soft.stereo) {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		count = min_t(unsigned long, userCount, frameLeft)>>1;
+		used = count*2;
+		while (count > 0) {
+			u_short data;
+			if (get_user(data, (u_short __user *)userPtr))
+				return -EFAULT;
+			userPtr += 2;
+			data = le2be16(data);
+			*p++ = data;
+			*p++ = data;
+			count--;
+		}
+		*frameUsed += used*2;
+	} else {
+		u_long *p = (u_long *)&frame[*frameUsed];
+		count = min_t(unsigned long, userCount, frameLeft)>>2;
+		used = count*4;
+		while (count > 0) {
+			u_long data;
+			if (get_user(data, (u_int __user *)userPtr))
+				return -EFAULT;
+			userPtr += 4;
+			data = le2be16dbl(data);
+			*p++ = data;
+			count--;
+		}
+		*frameUsed += used;
+	}
+	return used;
+}
+
+
+static ssize_t ata_ct_u16le(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft)
+{
+	ssize_t count, used;
+
+	count = frameLeft;
+	if (!dmasound.soft.stereo) {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		count = min_t(unsigned long, userCount, frameLeft)>>1;
+		used = count*2;
+		while (count > 0) {
+			u_short data;
+			if (get_user(data, (u_short __user *)userPtr))
+				return -EFAULT;
+			userPtr += 2;
+			data = le2be16(data) ^ 0x8000;
+			*p++ = data;
+			*p++ = data;
+		}
+		*frameUsed += used*2;
+	} else {
+		u_long *p = (u_long *)&frame[*frameUsed];
+		count = min_t(unsigned long, userCount, frameLeft)>>2;
+		used = count;
+		while (count > 0) {
+			u_long data;
+			if (get_user(data, (u_int __user *)userPtr))
+				return -EFAULT;
+			userPtr += 4;
+			data = le2be16dbl(data) ^ 0x80008000;
+			*p++ = data;
+			count--;
+		}
+		*frameUsed += used;
+	}
+	return used;
+}
+
+
+static ssize_t ata_ctx_law(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8
+							  : dmasound_alaw2dma8;
+	/* this should help gcc to stuff everything into registers */
+	long bal = expand_bal;
+	long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	ssize_t used, usedf;
+
+	used = userCount;
+	usedf = frameLeft;
+	if (!dmasound.soft.stereo) {
+		u_char *p = &frame[*frameUsed];
+		u_char data = expand_data;
+		while (frameLeft) {
+			u_char c;
+			if (bal < 0) {
+				if (!userCount)
+					break;
+				if (get_user(c, userPtr++))
+					return -EFAULT;
+				data = table[c];
+				userCount--;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			frameLeft--;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	} else {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		u_short data = expand_data;
+		while (frameLeft >= 2) {
+			u_char c;
+			if (bal < 0) {
+				if (userCount < 2)
+					break;
+				if (get_user(c, userPtr++))
+					return -EFAULT;
+				data = table[c] << 8;
+				if (get_user(c, userPtr++))
+					return -EFAULT;
+				data |= table[c];
+				userCount -= 2;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			frameLeft -= 2;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	}
+	expand_bal = bal;
+	used -= userCount;
+	*frameUsed += usedf-frameLeft;
+	return used;
+}
+
+
+static ssize_t ata_ctx_s8(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft)
+{
+	/* this should help gcc to stuff everything into registers */
+	long bal = expand_bal;
+	long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	ssize_t used, usedf;
+
+	used = userCount;
+	usedf = frameLeft;
+	if (!dmasound.soft.stereo) {
+		u_char *p = &frame[*frameUsed];
+		u_char data = expand_data;
+		while (frameLeft) {
+			if (bal < 0) {
+				if (!userCount)
+					break;
+				if (get_user(data, userPtr++))
+					return -EFAULT;
+				userCount--;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			frameLeft--;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	} else {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		u_short data = expand_data;
+		while (frameLeft >= 2) {
+			if (bal < 0) {
+				if (userCount < 2)
+					break;
+				if (get_user(data, (u_short __user *)userPtr))
+					return -EFAULT;
+				userPtr += 2;
+				userCount -= 2;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			frameLeft -= 2;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	}
+	expand_bal = bal;
+	used -= userCount;
+	*frameUsed += usedf-frameLeft;
+	return used;
+}
+
+
+static ssize_t ata_ctx_u8(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft)
+{
+	/* this should help gcc to stuff everything into registers */
+	long bal = expand_bal;
+	long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	ssize_t used, usedf;
+
+	used = userCount;
+	usedf = frameLeft;
+	if (!dmasound.soft.stereo) {
+		u_char *p = &frame[*frameUsed];
+		u_char data = expand_data;
+		while (frameLeft) {
+			if (bal < 0) {
+				if (!userCount)
+					break;
+				if (get_user(data, userPtr++))
+					return -EFAULT;
+				data ^= 0x80;
+				userCount--;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			frameLeft--;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	} else {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		u_short data = expand_data;
+		while (frameLeft >= 2) {
+			if (bal < 0) {
+				if (userCount < 2)
+					break;
+				if (get_user(data, (u_short __user *)userPtr))
+					return -EFAULT;
+				userPtr += 2;
+				data ^= 0x8080;
+				userCount -= 2;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			frameLeft -= 2;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	}
+	expand_bal = bal;
+	used -= userCount;
+	*frameUsed += usedf-frameLeft;
+	return used;
+}
+
+
+static ssize_t ata_ctx_s16be(const u_char __user *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft)
+{
+	/* this should help gcc to stuff everything into registers */
+	long bal = expand_bal;
+	long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	ssize_t used, usedf;
+
+	used = userCount;
+	usedf = frameLeft;
+	if (!dmasound.soft.stereo) {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		u_short data = expand_data;
+		while (frameLeft >= 4) {
+			if (bal < 0) {
+				if (userCount < 2)
+					break;
+				if (get_user(data, (u_short __user *)userPtr))
+					return -EFAULT;
+				userPtr += 2;
+				userCount -= 2;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			*p++ = data;
+			frameLeft -= 4;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	} else {
+		u_long *p = (u_long *)&frame[*frameUsed];
+		u_long data = expand_data;
+		while (frameLeft >= 4) {
+			if (bal < 0) {
+				if (userCount < 4)
+					break;
+				if (get_user(data, (u_int __user *)userPtr))
+					return -EFAULT;
+				userPtr += 4;
+				userCount -= 4;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			frameLeft -= 4;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	}
+	expand_bal = bal;
+	used -= userCount;
+	*frameUsed += usedf-frameLeft;
+	return used;
+}
+
+
+static ssize_t ata_ctx_u16be(const u_char __user *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft)
+{
+	/* this should help gcc to stuff everything into registers */
+	long bal = expand_bal;
+	long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	ssize_t used, usedf;
+
+	used = userCount;
+	usedf = frameLeft;
+	if (!dmasound.soft.stereo) {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		u_short data = expand_data;
+		while (frameLeft >= 4) {
+			if (bal < 0) {
+				if (userCount < 2)
+					break;
+				if (get_user(data, (u_short __user *)userPtr))
+					return -EFAULT;
+				userPtr += 2;
+				data ^= 0x8000;
+				userCount -= 2;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			*p++ = data;
+			frameLeft -= 4;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	} else {
+		u_long *p = (u_long *)&frame[*frameUsed];
+		u_long data = expand_data;
+		while (frameLeft >= 4) {
+			if (bal < 0) {
+				if (userCount < 4)
+					break;
+				if (get_user(data, (u_int __user *)userPtr))
+					return -EFAULT;
+				userPtr += 4;
+				data ^= 0x80008000;
+				userCount -= 4;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			frameLeft -= 4;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	}
+	expand_bal = bal;
+	used -= userCount;
+	*frameUsed += usedf-frameLeft;
+	return used;
+}
+
+
+static ssize_t ata_ctx_s16le(const u_char __user *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft)
+{
+	/* this should help gcc to stuff everything into registers */
+	long bal = expand_bal;
+	long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	ssize_t used, usedf;
+
+	used = userCount;
+	usedf = frameLeft;
+	if (!dmasound.soft.stereo) {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		u_short data = expand_data;
+		while (frameLeft >= 4) {
+			if (bal < 0) {
+				if (userCount < 2)
+					break;
+				if (get_user(data, (u_short __user *)userPtr))
+					return -EFAULT;
+				userPtr += 2;
+				data = le2be16(data);
+				userCount -= 2;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			*p++ = data;
+			frameLeft -= 4;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	} else {
+		u_long *p = (u_long *)&frame[*frameUsed];
+		u_long data = expand_data;
+		while (frameLeft >= 4) {
+			if (bal < 0) {
+				if (userCount < 4)
+					break;
+				if (get_user(data, (u_int __user *)userPtr))
+					return -EFAULT;
+				userPtr += 4;
+				data = le2be16dbl(data);
+				userCount -= 4;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			frameLeft -= 4;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	}
+	expand_bal = bal;
+	used -= userCount;
+	*frameUsed += usedf-frameLeft;
+	return used;
+}
+
+
+static ssize_t ata_ctx_u16le(const u_char __user *userPtr, size_t userCount,
+			     u_char frame[], ssize_t *frameUsed,
+			     ssize_t frameLeft)
+{
+	/* this should help gcc to stuff everything into registers */
+	long bal = expand_bal;
+	long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	ssize_t used, usedf;
+
+	used = userCount;
+	usedf = frameLeft;
+	if (!dmasound.soft.stereo) {
+		u_short *p = (u_short *)&frame[*frameUsed];
+		u_short data = expand_data;
+		while (frameLeft >= 4) {
+			if (bal < 0) {
+				if (userCount < 2)
+					break;
+				if (get_user(data, (u_short __user *)userPtr))
+					return -EFAULT;
+				userPtr += 2;
+				data = le2be16(data) ^ 0x8000;
+				userCount -= 2;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			*p++ = data;
+			frameLeft -= 4;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	} else {
+		u_long *p = (u_long *)&frame[*frameUsed];
+		u_long data = expand_data;
+		while (frameLeft >= 4) {
+			if (bal < 0) {
+				if (userCount < 4)
+					break;
+				if (get_user(data, (u_int __user *)userPtr))
+					return -EFAULT;
+				userPtr += 4;
+				data = le2be16dbl(data) ^ 0x80008000;
+				userCount -= 4;
+				bal += hSpeed;
+			}
+			*p++ = data;
+			frameLeft -= 4;
+			bal -= sSpeed;
+		}
+		expand_data = data;
+	}
+	expand_bal = bal;
+	used -= userCount;
+	*frameUsed += usedf-frameLeft;
+	return used;
+}
+
+
+static TRANS transTTNormal = {
+	.ct_ulaw	= ata_ct_law,
+	.ct_alaw	= ata_ct_law,
+	.ct_s8		= ata_ct_s8,
+	.ct_u8		= ata_ct_u8,
+};
+
+static TRANS transTTExpanding = {
+	.ct_ulaw	= ata_ctx_law,
+	.ct_alaw	= ata_ctx_law,
+	.ct_s8		= ata_ctx_s8,
+	.ct_u8		= ata_ctx_u8,
+};
+
+static TRANS transFalconNormal = {
+	.ct_ulaw	= ata_ct_law,
+	.ct_alaw	= ata_ct_law,
+	.ct_s8		= ata_ct_s8,
+	.ct_u8		= ata_ct_u8,
+	.ct_s16be	= ata_ct_s16be,
+	.ct_u16be	= ata_ct_u16be,
+	.ct_s16le	= ata_ct_s16le,
+	.ct_u16le	= ata_ct_u16le
+};
+
+static TRANS transFalconExpanding = {
+	.ct_ulaw	= ata_ctx_law,
+	.ct_alaw	= ata_ctx_law,
+	.ct_s8		= ata_ctx_s8,
+	.ct_u8		= ata_ctx_u8,
+	.ct_s16be	= ata_ctx_s16be,
+	.ct_u16be	= ata_ctx_u16be,
+	.ct_s16le	= ata_ctx_s16le,
+	.ct_u16le	= ata_ctx_u16le,
+};
+
+
+/*** Low level stuff *********************************************************/
+
+
+
+/*
+ * Atari (TT/Falcon)
+ */
+
+static void *AtaAlloc(unsigned int size, gfp_t flags)
+{
+	return atari_stram_alloc(size, "dmasound");
+}
+
+static void AtaFree(void *obj, unsigned int size)
+{
+	atari_stram_free( obj );
+}
+
+static int __init AtaIrqInit(void)
+{
+	/* Set up timer A. Timer A
+	   will receive a signal upon end of playing from the sound
+	   hardware. Furthermore Timer A is able to count events
+	   and will cause an interrupt after a programmed number
+	   of events. So all we need to keep the music playing is
+	   to provide the sound hardware with new data upon
+	   an interrupt from timer A. */
+	st_mfp.tim_ct_a = 0;	/* ++roman: Stop timer before programming! */
+	st_mfp.tim_dt_a = 1;	/* Cause interrupt after first event. */
+	st_mfp.tim_ct_a = 8;	/* Turn on event counting. */
+	/* Register interrupt handler. */
+	if (request_irq(IRQ_MFP_TIMA, AtaInterrupt, IRQ_TYPE_SLOW, "DMA sound",
+			AtaInterrupt))
+		return 0;
+	st_mfp.int_en_a |= 0x20;	/* Turn interrupt on. */
+	st_mfp.int_mk_a |= 0x20;
+	return 1;
+}
+
+#ifdef MODULE
+static void AtaIrqCleanUp(void)
+{
+	st_mfp.tim_ct_a = 0;		/* stop timer */
+	st_mfp.int_en_a &= ~0x20;	/* turn interrupt off */
+	free_irq(IRQ_MFP_TIMA, AtaInterrupt);
+}
+#endif /* MODULE */
+
+
+#define TONE_VOXWARE_TO_DB(v) \
+	(((v) < 0) ? -12 : ((v) > 100) ? 12 : ((v) - 50) * 6 / 25)
+#define TONE_DB_TO_VOXWARE(v) (((v) * 25 + ((v) > 0 ? 5 : -5)) / 6 + 50)
+
+
+static int AtaSetBass(int bass)
+{
+	dmasound.bass = TONE_VOXWARE_TO_DB(bass);
+	atari_microwire_cmd(MW_LM1992_BASS(dmasound.bass));
+	return TONE_DB_TO_VOXWARE(dmasound.bass);
+}
+
+
+static int AtaSetTreble(int treble)
+{
+	dmasound.treble = TONE_VOXWARE_TO_DB(treble);
+	atari_microwire_cmd(MW_LM1992_TREBLE(dmasound.treble));
+	return TONE_DB_TO_VOXWARE(dmasound.treble);
+}
+
+
+
+/*
+ * TT
+ */
+
+
+static void TTSilence(void)
+{
+	tt_dmasnd.ctrl = DMASND_CTRL_OFF;
+	atari_microwire_cmd(MW_LM1992_PSG_HIGH); /* mix in PSG signal 1:1 */
+}
+
+
+static void TTInit(void)
+{
+	int mode, i, idx;
+	const int freq[4] = {50066, 25033, 12517, 6258};
+
+	/* search a frequency that fits into the allowed error range */
+
+	idx = -1;
+	for (i = 0; i < ARRAY_SIZE(freq); i++)
+		/* this isn't as much useful for a TT than for a Falcon, but
+		 * then it doesn't hurt very much to implement it for a TT too.
+		 */
+		if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) < catchRadius)
+			idx = i;
+	if (idx > -1) {
+		dmasound.soft.speed = freq[idx];
+		dmasound.trans_write = &transTTNormal;
+	} else
+		dmasound.trans_write = &transTTExpanding;
+
+	TTSilence();
+	dmasound.hard = dmasound.soft;
+
+	if (dmasound.hard.speed > 50066) {
+		/* we would need to squeeze the sound, but we won't do that */
+		dmasound.hard.speed = 50066;
+		mode = DMASND_MODE_50KHZ;
+		dmasound.trans_write = &transTTNormal;
+	} else if (dmasound.hard.speed > 25033) {
+		dmasound.hard.speed = 50066;
+		mode = DMASND_MODE_50KHZ;
+	} else if (dmasound.hard.speed > 12517) {
+		dmasound.hard.speed = 25033;
+		mode = DMASND_MODE_25KHZ;
+	} else if (dmasound.hard.speed > 6258) {
+		dmasound.hard.speed = 12517;
+		mode = DMASND_MODE_12KHZ;
+	} else {
+		dmasound.hard.speed = 6258;
+		mode = DMASND_MODE_6KHZ;
+	}
+
+	tt_dmasnd.mode = (dmasound.hard.stereo ?
+			  DMASND_MODE_STEREO : DMASND_MODE_MONO) |
+		DMASND_MODE_8BIT | mode;
+
+	expand_bal = -dmasound.soft.speed;
+}
+
+
+static int TTSetFormat(int format)
+{
+	/* TT sound DMA supports only 8bit modes */
+
+	switch (format) {
+	case AFMT_QUERY:
+		return dmasound.soft.format;
+	case AFMT_MU_LAW:
+	case AFMT_A_LAW:
+	case AFMT_S8:
+	case AFMT_U8:
+		break;
+	default:
+		format = AFMT_S8;
+	}
+
+	dmasound.soft.format = format;
+	dmasound.soft.size = 8;
+	if (dmasound.minDev == SND_DEV_DSP) {
+		dmasound.dsp.format = format;
+		dmasound.dsp.size = 8;
+	}
+	TTInit();
+
+	return format;
+}
+
+
+#define VOLUME_VOXWARE_TO_DB(v) \
+	(((v) < 0) ? -40 : ((v) > 100) ? 0 : ((v) * 2) / 5 - 40)
+#define VOLUME_DB_TO_VOXWARE(v) ((((v) + 40) * 5 + 1) / 2)
+
+
+static int TTSetVolume(int volume)
+{
+	dmasound.volume_left = VOLUME_VOXWARE_TO_DB(volume & 0xff);
+	atari_microwire_cmd(MW_LM1992_BALLEFT(dmasound.volume_left));
+	dmasound.volume_right = VOLUME_VOXWARE_TO_DB((volume & 0xff00) >> 8);
+	atari_microwire_cmd(MW_LM1992_BALRIGHT(dmasound.volume_right));
+	return VOLUME_DB_TO_VOXWARE(dmasound.volume_left) |
+	       (VOLUME_DB_TO_VOXWARE(dmasound.volume_right) << 8);
+}
+
+
+#define GAIN_VOXWARE_TO_DB(v) \
+	(((v) < 0) ? -80 : ((v) > 100) ? 0 : ((v) * 4) / 5 - 80)
+#define GAIN_DB_TO_VOXWARE(v) ((((v) + 80) * 5 + 1) / 4)
+
+static int TTSetGain(int gain)
+{
+	dmasound.gain = GAIN_VOXWARE_TO_DB(gain);
+	atari_microwire_cmd(MW_LM1992_VOLUME(dmasound.gain));
+	return GAIN_DB_TO_VOXWARE(dmasound.gain);
+}
+
+
+
+/*
+ * Falcon
+ */
+
+
+static void FalconSilence(void)
+{
+	/* stop playback, set sample rate 50kHz for PSG sound */
+	tt_dmasnd.ctrl = DMASND_CTRL_OFF;
+	tt_dmasnd.mode = DMASND_MODE_50KHZ | DMASND_MODE_STEREO | DMASND_MODE_8BIT;
+	tt_dmasnd.int_div = 0; /* STE compatible divider */
+	tt_dmasnd.int_ctrl = 0x0;
+	tt_dmasnd.cbar_src = 0x0000; /* no matrix inputs */
+	tt_dmasnd.cbar_dst = 0x0000; /* no matrix outputs */
+	tt_dmasnd.dac_src = 1; /* connect ADC to DAC, disconnect matrix */
+	tt_dmasnd.adc_src = 3; /* ADC Input = PSG */
+}
+
+
+static void FalconInit(void)
+{
+	int divider, i, idx;
+	const int freq[8] = {49170, 32780, 24585, 19668, 16390, 12292, 9834, 8195};
+
+	/* search a frequency that fits into the allowed error range */
+
+	idx = -1;
+	for (i = 0; i < ARRAY_SIZE(freq); i++)
+		/* if we will tolerate 3% error 8000Hz->8195Hz (2.38%) would
+		 * be playable without expanding, but that now a kernel runtime
+		 * option
+		 */
+		if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) < catchRadius)
+			idx = i;
+	if (idx > -1) {
+		dmasound.soft.speed = freq[idx];
+		dmasound.trans_write = &transFalconNormal;
+	} else
+		dmasound.trans_write = &transFalconExpanding;
+
+	FalconSilence();
+	dmasound.hard = dmasound.soft;
+
+	if (dmasound.hard.size == 16) {
+		/* the Falcon can play 16bit samples only in stereo */
+		dmasound.hard.stereo = 1;
+	}
+
+	if (dmasound.hard.speed > 49170) {
+		/* we would need to squeeze the sound, but we won't do that */
+		dmasound.hard.speed = 49170;
+		divider = 1;
+		dmasound.trans_write = &transFalconNormal;
+	} else if (dmasound.hard.speed > 32780) {
+		dmasound.hard.speed = 49170;
+		divider = 1;
+	} else if (dmasound.hard.speed > 24585) {
+		dmasound.hard.speed = 32780;
+		divider = 2;
+	} else if (dmasound.hard.speed > 19668) {
+		dmasound.hard.speed = 24585;
+		divider = 3;
+	} else if (dmasound.hard.speed > 16390) {
+		dmasound.hard.speed = 19668;
+		divider = 4;
+	} else if (dmasound.hard.speed > 12292) {
+		dmasound.hard.speed = 16390;
+		divider = 5;
+	} else if (dmasound.hard.speed > 9834) {
+		dmasound.hard.speed = 12292;
+		divider = 7;
+	} else if (dmasound.hard.speed > 8195) {
+		dmasound.hard.speed = 9834;
+		divider = 9;
+	} else {
+		dmasound.hard.speed = 8195;
+		divider = 11;
+	}
+	tt_dmasnd.int_div = divider;
+
+	/* Setup Falcon sound DMA for playback */
+	tt_dmasnd.int_ctrl = 0x4; /* Timer A int at play end */
+	tt_dmasnd.track_select = 0x0; /* play 1 track, track 1 */
+	tt_dmasnd.cbar_src = 0x0001; /* DMA(25MHz) --> DAC */
+	tt_dmasnd.cbar_dst = 0x0000;
+	tt_dmasnd.rec_track_select = 0;
+	tt_dmasnd.dac_src = 2; /* connect matrix to DAC */
+	tt_dmasnd.adc_src = 0; /* ADC Input = Mic */
+
+	tt_dmasnd.mode = (dmasound.hard.stereo ?
+			  DMASND_MODE_STEREO : DMASND_MODE_MONO) |
+		((dmasound.hard.size == 8) ?
+		 DMASND_MODE_8BIT : DMASND_MODE_16BIT) |
+		DMASND_MODE_6KHZ;
+
+	expand_bal = -dmasound.soft.speed;
+}
+
+
+static int FalconSetFormat(int format)
+{
+	int size;
+	/* Falcon sound DMA supports 8bit and 16bit modes */
+
+	switch (format) {
+	case AFMT_QUERY:
+		return dmasound.soft.format;
+	case AFMT_MU_LAW:
+	case AFMT_A_LAW:
+	case AFMT_U8:
+	case AFMT_S8:
+		size = 8;
+		break;
+	case AFMT_S16_BE:
+	case AFMT_U16_BE:
+	case AFMT_S16_LE:
+	case AFMT_U16_LE:
+		size = 16;
+		break;
+	default: /* :-) */
+		size = 8;
+		format = AFMT_S8;
+	}
+
+	dmasound.soft.format = format;
+	dmasound.soft.size = size;
+	if (dmasound.minDev == SND_DEV_DSP) {
+		dmasound.dsp.format = format;
+		dmasound.dsp.size = dmasound.soft.size;
+	}
+
+	FalconInit();
+
+	return format;
+}
+
+
+/* This is for the Falcon output *attenuation* in 1.5dB steps,
+ * i.e. output level from 0 to -22.5dB in -1.5dB steps.
+ */
+#define VOLUME_VOXWARE_TO_ATT(v) \
+	((v) < 0 ? 15 : (v) > 100 ? 0 : 15 - (v) * 3 / 20)
+#define VOLUME_ATT_TO_VOXWARE(v) (100 - (v) * 20 / 3)
+
+
+static int FalconSetVolume(int volume)
+{
+	dmasound.volume_left = VOLUME_VOXWARE_TO_ATT(volume & 0xff);
+	dmasound.volume_right = VOLUME_VOXWARE_TO_ATT((volume & 0xff00) >> 8);
+	tt_dmasnd.output_atten = dmasound.volume_left << 8 | dmasound.volume_right << 4;
+	return VOLUME_ATT_TO_VOXWARE(dmasound.volume_left) |
+	       VOLUME_ATT_TO_VOXWARE(dmasound.volume_right) << 8;
+}
+
+
+static void AtaPlayNextFrame(int index)
+{
+	char *start, *end;
+
+	/* used by AtaPlay() if all doubts whether there really is something
+	 * to be played are already wiped out.
+	 */
+	start = write_sq.buffers[write_sq.front];
+	end = start+((write_sq.count == index) ? write_sq.rear_size
+					       : write_sq.block_size);
+	/* end might not be a legal virtual address. */
+	DMASNDSetEnd(virt_to_phys(end - 1) + 1);
+	DMASNDSetBase(virt_to_phys(start));
+	/* Since only an even number of samples per frame can
+	   be played, we might lose one byte here. (TO DO) */
+	write_sq.front = (write_sq.front+1) % write_sq.max_count;
+	write_sq.active++;
+	tt_dmasnd.ctrl = DMASND_CTRL_ON | DMASND_CTRL_REPEAT;
+}
+
+
+static void AtaPlay(void)
+{
+	/* ++TeSche: Note that write_sq.active is no longer just a flag but
+	 * holds the number of frames the DMA is currently programmed for
+	 * instead, may be 0, 1 (currently being played) or 2 (pre-programmed).
+	 *
+	 * Changes done to write_sq.count and write_sq.active are a bit more
+	 * subtle again so now I must admit I also prefer disabling the irq
+	 * here rather than considering all possible situations. But the point
+	 * is that disabling the irq doesn't have any bad influence on this
+	 * version of the driver as we benefit from having pre-programmed the
+	 * DMA wherever possible: There's no need to reload the DMA at the
+	 * exact time of an interrupt but only at some time while the
+	 * pre-programmed frame is playing!
+	 */
+	atari_disable_irq(IRQ_MFP_TIMA);
+
+	if (write_sq.active == 2 ||	/* DMA is 'full' */
+	    write_sq.count <= 0) {	/* nothing to do */
+		atari_enable_irq(IRQ_MFP_TIMA);
+		return;
+	}
+
+	if (write_sq.active == 0) {
+		/* looks like there's nothing 'in' the DMA yet, so try
+		 * to put two frames into it (at least one is available).
+		 */
+		if (write_sq.count == 1 &&
+		    write_sq.rear_size < write_sq.block_size &&
+		    !write_sq.syncing) {
+			/* hmmm, the only existing frame is not
+			 * yet filled and we're not syncing?
+			 */
+			atari_enable_irq(IRQ_MFP_TIMA);
+			return;
+		}
+		AtaPlayNextFrame(1);
+		if (write_sq.count == 1) {
+			/* no more frames */
+			atari_enable_irq(IRQ_MFP_TIMA);
+			return;
+		}
+		if (write_sq.count == 2 &&
+		    write_sq.rear_size < write_sq.block_size &&
+		    !write_sq.syncing) {
+			/* hmmm, there were two frames, but the second
+			 * one is not yet filled and we're not syncing?
+			 */
+			atari_enable_irq(IRQ_MFP_TIMA);
+			return;
+		}
+		AtaPlayNextFrame(2);
+	} else {
+		/* there's already a frame being played so we may only stuff
+		 * one new into the DMA, but even if this may be the last
+		 * frame existing the previous one is still on write_sq.count.
+		 */
+		if (write_sq.count == 2 &&
+		    write_sq.rear_size < write_sq.block_size &&
+		    !write_sq.syncing) {
+			/* hmmm, the only existing frame is not
+			 * yet filled and we're not syncing?
+			 */
+			atari_enable_irq(IRQ_MFP_TIMA);
+			return;
+		}
+		AtaPlayNextFrame(2);
+	}
+	atari_enable_irq(IRQ_MFP_TIMA);
+}
+
+
+static irqreturn_t AtaInterrupt(int irq, void *dummy)
+{
+#if 0
+	/* ++TeSche: if you should want to test this... */
+	static int cnt;
+	if (write_sq.active == 2)
+		if (++cnt == 10) {
+			/* simulate losing an interrupt */
+			cnt = 0;
+			return IRQ_HANDLED;
+		}
+#endif
+	spin_lock(&dmasound.lock);
+	if (write_sq_ignore_int && is_falcon) {
+		/* ++TeSche: Falcon only: ignore first irq because it comes
+		 * immediately after starting a frame. after that, irqs come
+		 * (almost) like on the TT.
+		 */
+		write_sq_ignore_int = 0;
+		goto out;
+	}
+
+	if (!write_sq.active) {
+		/* playing was interrupted and sq_reset() has already cleared
+		 * the sq variables, so better don't do anything here.
+		 */
+		WAKE_UP(write_sq.sync_queue);
+		goto out;
+	}
+
+	/* Probably ;) one frame is finished. Well, in fact it may be that a
+	 * pre-programmed one is also finished because there has been a long
+	 * delay in interrupt delivery and we've completely lost one, but
+	 * there's no way to detect such a situation. In such a case the last
+	 * frame will be played more than once and the situation will recover
+	 * as soon as the irq gets through.
+	 */
+	write_sq.count--;
+	write_sq.active--;
+
+	if (!write_sq.active) {
+		tt_dmasnd.ctrl = DMASND_CTRL_OFF;
+		write_sq_ignore_int = 1;
+	}
+
+	WAKE_UP(write_sq.action_queue);
+	/* At least one block of the queue is free now
+	   so wake up a writing process blocked because
+	   of a full queue. */
+
+	if ((write_sq.active != 1) || (write_sq.count != 1))
+		/* We must be a bit carefully here: write_sq.count indicates the
+		 * number of buffers used and not the number of frames to be
+		 * played. If write_sq.count==1 and write_sq.active==1 that
+		 * means the only remaining frame was already programmed
+		 * earlier (and is currently running) so we mustn't call
+		 * AtaPlay() here, otherwise we'll play one frame too much.
+		 */
+		AtaPlay();
+
+	if (!write_sq.active) WAKE_UP(write_sq.sync_queue);
+	/* We are not playing after AtaPlay(), so there
+	   is nothing to play any more. Wake up a process
+	   waiting for audio output to drain. */
+out:
+	spin_unlock(&dmasound.lock);
+	return IRQ_HANDLED;
+}
+
+
+/*** Mid level stuff *********************************************************/
+
+
+/*
+ * /dev/mixer abstraction
+ */
+
+#define RECLEVEL_VOXWARE_TO_GAIN(v)	\
+	((v) < 0 ? 0 : (v) > 100 ? 15 : (v) * 3 / 20)
+#define RECLEVEL_GAIN_TO_VOXWARE(v)	(((v) * 20 + 2) / 3)
+
+
+static void __init TTMixerInit(void)
+{
+	atari_microwire_cmd(MW_LM1992_VOLUME(0));
+	dmasound.volume_left = 0;
+	atari_microwire_cmd(MW_LM1992_BALLEFT(0));
+	dmasound.volume_right = 0;
+	atari_microwire_cmd(MW_LM1992_BALRIGHT(0));
+	atari_microwire_cmd(MW_LM1992_TREBLE(0));
+	atari_microwire_cmd(MW_LM1992_BASS(0));
+}
+
+static void __init FalconMixerInit(void)
+{
+	dmasound.volume_left = (tt_dmasnd.output_atten & 0xf00) >> 8;
+	dmasound.volume_right = (tt_dmasnd.output_atten & 0xf0) >> 4;
+}
+
+static int AtaMixerIoctl(u_int cmd, u_long arg)
+{
+	int data;
+	unsigned long flags;
+	switch (cmd) {
+	    case SOUND_MIXER_READ_SPEAKER:
+		    if (is_falcon || MACH_IS_TT) {
+			    int porta;
+			    spin_lock_irqsave(&dmasound.lock, flags);
+			    sound_ym.rd_data_reg_sel = 14;
+			    porta = sound_ym.rd_data_reg_sel;
+			    spin_unlock_irqrestore(&dmasound.lock, flags);
+			    return IOCTL_OUT(arg, porta & 0x40 ? 0 : 100);
+		    }
+		    break;
+	    case SOUND_MIXER_WRITE_VOLUME:
+		    IOCTL_IN(arg, data);
+		    return IOCTL_OUT(arg, dmasound_set_volume(data));
+	    case SOUND_MIXER_WRITE_SPEAKER:
+		    if (is_falcon || MACH_IS_TT) {
+			    int porta;
+			    IOCTL_IN(arg, data);
+			    spin_lock_irqsave(&dmasound.lock, flags);
+			    sound_ym.rd_data_reg_sel = 14;
+			    porta = (sound_ym.rd_data_reg_sel & ~0x40) |
+				    (data < 50 ? 0x40 : 0);
+			    sound_ym.wd_data = porta;
+			    spin_unlock_irqrestore(&dmasound.lock, flags);
+			    return IOCTL_OUT(arg, porta & 0x40 ? 0 : 100);
+		    }
+	}
+	return -EINVAL;
+}
+
+
+static int TTMixerIoctl(u_int cmd, u_long arg)
+{
+	int data;
+	switch (cmd) {
+	    case SOUND_MIXER_READ_RECMASK:
+		return IOCTL_OUT(arg, 0);
+	    case SOUND_MIXER_READ_DEVMASK:
+		return IOCTL_OUT(arg,
+				 SOUND_MASK_VOLUME | SOUND_MASK_TREBLE | SOUND_MASK_BASS |
+				 (MACH_IS_TT ? SOUND_MASK_SPEAKER : 0));
+	    case SOUND_MIXER_READ_STEREODEVS:
+		return IOCTL_OUT(arg, SOUND_MASK_VOLUME);
+	    case SOUND_MIXER_READ_VOLUME:
+		return IOCTL_OUT(arg,
+				 VOLUME_DB_TO_VOXWARE(dmasound.volume_left) |
+				 (VOLUME_DB_TO_VOXWARE(dmasound.volume_right) << 8));
+	    case SOUND_MIXER_READ_BASS:
+		return IOCTL_OUT(arg, TONE_DB_TO_VOXWARE(dmasound.bass));
+	    case SOUND_MIXER_READ_TREBLE:
+		return IOCTL_OUT(arg, TONE_DB_TO_VOXWARE(dmasound.treble));
+	    case SOUND_MIXER_READ_OGAIN:
+		return IOCTL_OUT(arg, GAIN_DB_TO_VOXWARE(dmasound.gain));
+	    case SOUND_MIXER_WRITE_BASS:
+		IOCTL_IN(arg, data);
+		return IOCTL_OUT(arg, dmasound_set_bass(data));
+	    case SOUND_MIXER_WRITE_TREBLE:
+		IOCTL_IN(arg, data);
+		return IOCTL_OUT(arg, dmasound_set_treble(data));
+	    case SOUND_MIXER_WRITE_OGAIN:
+		IOCTL_IN(arg, data);
+		return IOCTL_OUT(arg, dmasound_set_gain(data));
+	}
+	return AtaMixerIoctl(cmd, arg);
+}
+
+static int FalconMixerIoctl(u_int cmd, u_long arg)
+{
+	int data;
+	switch (cmd) {
+	    case SOUND_MIXER_READ_RECMASK:
+		return IOCTL_OUT(arg, SOUND_MASK_MIC);
+	    case SOUND_MIXER_READ_DEVMASK:
+		return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_MIC | SOUND_MASK_SPEAKER);
+	    case SOUND_MIXER_READ_STEREODEVS:
+		return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_MIC);
+	    case SOUND_MIXER_READ_VOLUME:
+		return IOCTL_OUT(arg,
+			VOLUME_ATT_TO_VOXWARE(dmasound.volume_left) |
+			VOLUME_ATT_TO_VOXWARE(dmasound.volume_right) << 8);
+	    case SOUND_MIXER_READ_CAPS:
+		return IOCTL_OUT(arg, SOUND_CAP_EXCL_INPUT);
+	    case SOUND_MIXER_WRITE_MIC:
+		IOCTL_IN(arg, data);
+		tt_dmasnd.input_gain =
+			RECLEVEL_VOXWARE_TO_GAIN(data & 0xff) << 4 |
+			RECLEVEL_VOXWARE_TO_GAIN(data >> 8 & 0xff);
+		/* fall thru, return set value */
+	    case SOUND_MIXER_READ_MIC:
+		return IOCTL_OUT(arg,
+			RECLEVEL_GAIN_TO_VOXWARE(tt_dmasnd.input_gain >> 4 & 0xf) |
+			RECLEVEL_GAIN_TO_VOXWARE(tt_dmasnd.input_gain & 0xf) << 8);
+	}
+	return AtaMixerIoctl(cmd, arg);
+}
+
+static int AtaWriteSqSetup(void)
+{
+	write_sq_ignore_int = 0;
+	return 0 ;
+}
+
+static int AtaSqOpen(fmode_t mode)
+{
+	write_sq_ignore_int = 1;
+	return 0 ;
+}
+
+static int TTStateInfo(char *buffer, size_t space)
+{
+	int len = 0;
+	len += sprintf(buffer+len, "\tvol left  %ddB [-40...  0]\n",
+		       dmasound.volume_left);
+	len += sprintf(buffer+len, "\tvol right %ddB [-40...  0]\n",
+		       dmasound.volume_right);
+	len += sprintf(buffer+len, "\tbass      %ddB [-12...+12]\n",
+		       dmasound.bass);
+	len += sprintf(buffer+len, "\ttreble    %ddB [-12...+12]\n",
+		       dmasound.treble);
+	if (len >= space) {
+		printk(KERN_ERR "dmasound_atari: overflowed state buffer alloc.\n") ;
+		len = space ;
+	}
+	return len;
+}
+
+static int FalconStateInfo(char *buffer, size_t space)
+{
+	int len = 0;
+	len += sprintf(buffer+len, "\tvol left  %ddB [-22.5 ... 0]\n",
+		       dmasound.volume_left);
+	len += sprintf(buffer+len, "\tvol right %ddB [-22.5 ... 0]\n",
+		       dmasound.volume_right);
+	if (len >= space) {
+		printk(KERN_ERR "dmasound_atari: overflowed state buffer alloc.\n") ;
+		len = space ;
+	}
+	return len;
+}
+
+
+/*** Machine definitions *****************************************************/
+
+static SETTINGS def_hard_falcon = {
+	.format		= AFMT_S8,
+	.stereo		= 0,
+	.size		= 8,
+	.speed		= 8195
+} ;
+
+static SETTINGS def_hard_tt = {
+	.format	= AFMT_S8,
+	.stereo	= 0,
+	.size	= 8,
+	.speed	= 12517
+} ;
+
+static SETTINGS def_soft = {
+	.format	= AFMT_U8,
+	.stereo	= 0,
+	.size	= 8,
+	.speed	= 8000
+} ;
+
+static __initdata MACHINE machTT = {
+	.name		= "Atari",
+	.name2		= "TT",
+	.owner		= THIS_MODULE,
+	.dma_alloc	= AtaAlloc,
+	.dma_free	= AtaFree,
+	.irqinit	= AtaIrqInit,
+#ifdef MODULE
+	.irqcleanup	= AtaIrqCleanUp,
+#endif /* MODULE */
+	.init		= TTInit,
+	.silence	= TTSilence,
+	.setFormat	= TTSetFormat,
+	.setVolume	= TTSetVolume,
+	.setBass	= AtaSetBass,
+	.setTreble	= AtaSetTreble,
+	.setGain	= TTSetGain,
+	.play		= AtaPlay,
+	.mixer_init	= TTMixerInit,
+	.mixer_ioctl	= TTMixerIoctl,
+	.write_sq_setup	= AtaWriteSqSetup,
+	.sq_open	= AtaSqOpen,
+	.state_info	= TTStateInfo,
+	.min_dsp_speed	= 6258,
+	.version	= ((DMASOUND_ATARI_REVISION<<8) | DMASOUND_ATARI_EDITION),
+	.hardware_afmts	= AFMT_S8,  /* h'ware-supported formats *only* here */
+	.capabilities	=  DSP_CAP_BATCH	/* As per SNDCTL_DSP_GETCAPS */
+};
+
+static __initdata MACHINE machFalcon = {
+	.name		= "Atari",
+	.name2		= "FALCON",
+	.dma_alloc	= AtaAlloc,
+	.dma_free	= AtaFree,
+	.irqinit	= AtaIrqInit,
+#ifdef MODULE
+	.irqcleanup	= AtaIrqCleanUp,
+#endif /* MODULE */
+	.init		= FalconInit,
+	.silence	= FalconSilence,
+	.setFormat	= FalconSetFormat,
+	.setVolume	= FalconSetVolume,
+	.setBass	= AtaSetBass,
+	.setTreble	= AtaSetTreble,
+	.play		= AtaPlay,
+	.mixer_init	= FalconMixerInit,
+	.mixer_ioctl	= FalconMixerIoctl,
+	.write_sq_setup	= AtaWriteSqSetup,
+	.sq_open	= AtaSqOpen,
+	.state_info	= FalconStateInfo,
+	.min_dsp_speed	= 8195,
+	.version	= ((DMASOUND_ATARI_REVISION<<8) | DMASOUND_ATARI_EDITION),
+	.hardware_afmts	= (AFMT_S8 | AFMT_S16_BE), /* h'ware-supported formats *only* here */
+	.capabilities	=  DSP_CAP_BATCH	/* As per SNDCTL_DSP_GETCAPS */
+};
+
+
+/*** Config & Setup **********************************************************/
+
+
+static int __init dmasound_atari_init(void)
+{
+	if (MACH_IS_ATARI && ATARIHW_PRESENT(PCM_8BIT)) {
+	    if (ATARIHW_PRESENT(CODEC)) {
+		dmasound.mach = machFalcon;
+		dmasound.mach.default_soft = def_soft ;
+		dmasound.mach.default_hard = def_hard_falcon ;
+		is_falcon = 1;
+	    } else if (ATARIHW_PRESENT(MICROWIRE)) {
+		dmasound.mach = machTT;
+		dmasound.mach.default_soft = def_soft ;
+		dmasound.mach.default_hard = def_hard_tt ;
+		is_falcon = 0;
+	    } else
+		return -ENODEV;
+	    if ((st_mfp.int_en_a & st_mfp.int_mk_a & 0x20) == 0)
+		return dmasound_init();
+	    else {
+		printk("DMA sound driver: Timer A interrupt already in use\n");
+		return -EBUSY;
+	    }
+	}
+	return -ENODEV;
+}
+
+static void __exit dmasound_atari_cleanup(void)
+{
+	dmasound_deinit();
+}
+
+module_init(dmasound_atari_init);
+module_exit(dmasound_atari_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/dmasound/dmasound_core.c b/linux/sound/oss/dmasound/dmasound_core.c
new file mode 100644
index 0000000..87e2c72
--- /dev/null
+++ b/linux/sound/oss/dmasound/dmasound_core.c
@@ -0,0 +1,1589 @@
+/*
+ *  linux/sound/oss/dmasound/dmasound_core.c
+ *
+ *
+ *  OSS/Free compatible Atari TT/Falcon and Amiga DMA sound driver for
+ *  Linux/m68k
+ *  Extended to support Power Macintosh for Linux/ppc by Paul Mackerras
+ *
+ *  (c) 1995 by Michael Schlueter & Michael Marte
+ *
+ *  Michael Schlueter (michael@duck.syd.de) did the basic structure of the VFS
+ *  interface and the u-law to signed byte conversion.
+ *
+ *  Michael Marte (marte@informatik.uni-muenchen.de) did the sound queue,
+ *  /dev/mixer, /dev/sndstat and complemented the VFS interface. He would like
+ *  to thank:
+ *    - Michael Schlueter for initial ideas and documentation on the MFP and
+ *	the DMA sound hardware.
+ *    - Therapy? for their CD 'Troublegum' which really made me rock.
+ *
+ *  /dev/sndstat is based on code by Hannu Savolainen, the author of the
+ *  VoxWare family of drivers.
+ *
+ *  This file is subject to the terms and conditions of the GNU General Public
+ *  License.  See the file COPYING in the main directory of this archive
+ *  for more details.
+ *
+ *  History:
+ *
+ *	1995/8/25	First release
+ *
+ *	1995/9/02	Roman Hodek:
+ *			  - Fixed atari_stram_alloc() call, the timer
+ *			    programming and several race conditions
+ *	1995/9/14	Roman Hodek:
+ *			  - After some discussion with Michael Schlueter,
+ *			    revised the interrupt disabling
+ *			  - Slightly speeded up U8->S8 translation by using
+ *			    long operations where possible
+ *			  - Added 4:3 interpolation for /dev/audio
+ *
+ *	1995/9/20	Torsten Scherer:
+ *			  - Fixed a bug in sq_write and changed /dev/audio
+ *			    converting to play at 12517Hz instead of 6258Hz.
+ *
+ *	1995/9/23	Torsten Scherer:
+ *			  - Changed sq_interrupt() and sq_play() to pre-program
+ *			    the DMA for another frame while there's still one
+ *			    running. This allows the IRQ response to be
+ *			    arbitrarily delayed and playing will still continue.
+ *
+ *	1995/10/14	Guenther Kelleter, Torsten Scherer:
+ *			  - Better support for Falcon audio (the Falcon doesn't
+ *			    raise an IRQ at the end of a frame, but at the
+ *			    beginning instead!). uses 'if (codec_dma)' in lots
+ *			    of places to simply switch between Falcon and TT
+ *			    code.
+ *
+ *	1995/11/06	Torsten Scherer:
+ *			  - Started introducing a hardware abstraction scheme
+ *			    (may perhaps also serve for Amigas?)
+ *			  - Can now play samples at almost all frequencies by
+ *			    means of a more generalized expand routine
+ *			  - Takes a good deal of care to cut data only at
+ *			    sample sizes
+ *			  - Buffer size is now a kernel runtime option
+ *			  - Implemented fsync() & several minor improvements
+ *			Guenther Kelleter:
+ *			  - Useful hints and bug fixes
+ *			  - Cross-checked it for Falcons
+ *
+ *	1996/3/9	Geert Uytterhoeven:
+ *			  - Support added for Amiga, A-law, 16-bit little
+ *			    endian.
+ *			  - Unification to drivers/sound/dmasound.c.
+ *
+ *	1996/4/6	Martin Mitchell:
+ *			  - Updated to 1.3 kernel.
+ *
+ *	1996/6/13       Topi Kanerva:
+ *			  - Fixed things that were broken (mainly the amiga
+ *			    14-bit routines)
+ *			  - /dev/sndstat shows now the real hardware frequency
+ *			  - The lowpass filter is disabled by default now
+ *
+ *	1996/9/25	Geert Uytterhoeven:
+ *			  - Modularization
+ *
+ *	1998/6/10	Andreas Schwab:
+ *			  - Converted to use sound_core
+ *
+ *	1999/12/28	Richard Zidlicky:
+ *			  - Added support for Q40
+ *
+ *	2000/2/27	Geert Uytterhoeven:
+ *			  - Clean up and split the code into 4 parts:
+ *			      o dmasound_core: machine-independent code
+ *			      o dmasound_atari: Atari TT and Falcon support
+ *			      o dmasound_awacs: Apple PowerMac support
+ *			      o dmasound_paula: Amiga support
+ *
+ *	2000/3/25	Geert Uytterhoeven:
+ *			  - Integration of dmasound_q40
+ *			  - Small clean ups
+ *
+ *	2001/01/26 [1.0] Iain Sandoe
+ *			  - make /dev/sndstat show revision & edition info.
+ *			  - since dmasound.mach.sq_setup() can fail on pmac
+ *			    its type has been changed to int and the returns
+ *			    are checked.
+ *		   [1.1]  - stop missing translations from being called.
+ *	2001/02/08 [1.2]  - remove unused translation tables & move machine-
+ *			    specific tables to low-level.
+ *			  - return correct info. for SNDCTL_DSP_GETFMTS.
+ *		   [1.3]  - implement SNDCTL_DSP_GETCAPS fully.
+ *		   [1.4]  - make /dev/sndstat text length usage deterministic.
+ *			  - make /dev/sndstat call to low-level
+ *			    dmasound.mach.state_info() pass max space to ll driver.
+ *			  - tidy startup banners and output info.
+ *		   [1.5]  - tidy up a little (removed some unused #defines in
+ *			    dmasound.h)
+ *			  - fix up HAS_RECORD conditionalisation.
+ *			  - add record code in places it is missing...
+ *			  - change buf-sizes to bytes to allow < 1kb for pmac
+ *			    if user param entry is < 256 the value is taken to
+ *			    be in kb > 256 is taken to be in bytes.
+ *			  - make default buff/frag params conditional on
+ *			    machine to allow smaller values for pmac.
+ *			  - made the ioctls, read & write comply with the OSS
+ *			    rules on setting params.
+ *			  - added parsing of _setup() params for record.
+ *	2001/04/04 [1.6]  - fix bug where sample rates higher than maximum were
+ *			    being reported as OK.
+ *			  - fix open() to return -EBUSY as per OSS doc. when
+ *			    audio is in use - this is independent of O_NOBLOCK.
+ *			  - fix bug where SNDCTL_DSP_POST was blocking.
+ */
+
+ /* Record capability notes 30/01/2001:
+  * At present these observations apply only to pmac LL driver (the only one
+  * that can do record, at present).  However, if other LL drivers for machines
+  * with record are added they may apply.
+  *
+  * The fragment parameters for the record and play channels are separate.
+  * However, if the driver is opened O_RDWR there is no way (in the current OSS
+  * API) to specify their values independently for the record and playback
+  * channels.  Since the only common factor between the input & output is the
+  * sample rate (on pmac) it should be possible to open /dev/dspX O_WRONLY and
+  * /dev/dspY O_RDONLY.  The input & output channels could then have different
+  * characteristics (other than the first that sets sample rate claiming the
+  * right to set it for ever).  As it stands, the format, channels, number of
+  * bits & sample rate are assumed to be common.  In the future perhaps these
+  * should be the responsibility of the LL driver - and then if a card really
+  * does not share items between record & playback they can be specified
+  * separately.
+*/
+
+/* Thread-safeness of shared_resources notes: 31/01/2001
+ * If the user opens O_RDWR and then splits record & play between two threads
+ * both of which inherit the fd - and then starts changing things from both
+ * - we will have difficulty telling.
+ *
+ * It's bad application coding - but ...
+ * TODO: think about how to sort this out... without bogging everything down in
+ * semaphores.
+ *
+ * Similarly, the OSS spec says "all changes to parameters must be between
+ * open() and the first read() or write(). - and a bit later on (by
+ * implication) "between SNDCTL_DSP_RESET and the first read() or write() after
+ * it".  If the app is multi-threaded and this rule is broken between threads
+ * we will have trouble spotting it - and the fault will be rather obscure :-(
+ *
+ * We will try and put out at least a kmsg if we see it happen... but I think
+ * it will be quite hard to trap it with an -EXXX return... because we can't
+ * see the fault until after the damage is done.
+*/
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sound.h>
+#include <linux/init.h>
+#include <linux/soundcard.h>
+#include <linux/poll.h>
+#include <linux/mutex.h>
+
+#include <asm/uaccess.h>
+
+#include "dmasound.h"
+
+#define DMASOUND_CORE_REVISION 1
+#define DMASOUND_CORE_EDITION 6
+
+    /*
+     *  Declarations
+     */
+
+static DEFINE_MUTEX(dmasound_core_mutex);
+int dmasound_catchRadius = 0;
+module_param(dmasound_catchRadius, int, 0);
+
+static unsigned int numWriteBufs = DEFAULT_N_BUFFERS;
+module_param(numWriteBufs, int, 0);
+static unsigned int writeBufSize = DEFAULT_BUFF_SIZE ;	/* in bytes */
+module_param(writeBufSize, int, 0);
+
+MODULE_LICENSE("GPL");
+
+#ifdef MODULE
+static int sq_unit = -1;
+static int mixer_unit = -1;
+static int state_unit = -1;
+static int irq_installed;
+#endif /* MODULE */
+
+/* control over who can modify resources shared between play/record */
+static fmode_t shared_resource_owner;
+static int shared_resources_initialised;
+
+    /*
+     *  Mid level stuff
+     */
+
+struct sound_settings dmasound = {
+	.lock = __SPIN_LOCK_UNLOCKED(dmasound.lock)
+};
+
+static inline void sound_silence(void)
+{
+	dmasound.mach.silence(); /* _MUST_ stop DMA */
+}
+
+static inline int sound_set_format(int format)
+{
+	return dmasound.mach.setFormat(format);
+}
+
+
+static int sound_set_speed(int speed)
+{
+	if (speed < 0)
+		return dmasound.soft.speed;
+
+	/* trap out-of-range speed settings.
+	   at present we allow (arbitrarily) low rates - using soft
+	   up-conversion - but we can't allow > max because there is
+	   no soft down-conversion.
+	*/
+	if (dmasound.mach.max_dsp_speed &&
+	   (speed > dmasound.mach.max_dsp_speed))
+		speed = dmasound.mach.max_dsp_speed ;
+
+	dmasound.soft.speed = speed;
+
+	if (dmasound.minDev == SND_DEV_DSP)
+		dmasound.dsp.speed = dmasound.soft.speed;
+
+	return dmasound.soft.speed;
+}
+
+static int sound_set_stereo(int stereo)
+{
+	if (stereo < 0)
+		return dmasound.soft.stereo;
+
+	stereo = !!stereo;    /* should be 0 or 1 now */
+
+	dmasound.soft.stereo = stereo;
+	if (dmasound.minDev == SND_DEV_DSP)
+		dmasound.dsp.stereo = stereo;
+
+	return stereo;
+}
+
+static ssize_t sound_copy_translate(TRANS *trans, const u_char __user *userPtr,
+				    size_t userCount, u_char frame[],
+				    ssize_t *frameUsed, ssize_t frameLeft)
+{
+	ssize_t (*ct_func)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t);
+
+	switch (dmasound.soft.format) {
+	    case AFMT_MU_LAW:
+		ct_func = trans->ct_ulaw;
+		break;
+	    case AFMT_A_LAW:
+		ct_func = trans->ct_alaw;
+		break;
+	    case AFMT_S8:
+		ct_func = trans->ct_s8;
+		break;
+	    case AFMT_U8:
+		ct_func = trans->ct_u8;
+		break;
+	    case AFMT_S16_BE:
+		ct_func = trans->ct_s16be;
+		break;
+	    case AFMT_U16_BE:
+		ct_func = trans->ct_u16be;
+		break;
+	    case AFMT_S16_LE:
+		ct_func = trans->ct_s16le;
+		break;
+	    case AFMT_U16_LE:
+		ct_func = trans->ct_u16le;
+		break;
+	    default:
+		return 0;
+	}
+	/* if the user has requested a non-existent translation don't try
+	   to call it but just return 0 bytes moved
+	*/
+	if (ct_func)
+		return ct_func(userPtr, userCount, frame, frameUsed, frameLeft);
+	return 0;
+}
+
+    /*
+     *  /dev/mixer abstraction
+     */
+
+static struct {
+    int busy;
+    int modify_counter;
+} mixer;
+
+static int mixer_open(struct inode *inode, struct file *file)
+{
+	mutex_lock(&dmasound_core_mutex);
+	if (!try_module_get(dmasound.mach.owner)) {
+		mutex_unlock(&dmasound_core_mutex);
+		return -ENODEV;
+	}
+	mixer.busy = 1;
+	mutex_unlock(&dmasound_core_mutex);
+	return 0;
+}
+
+static int mixer_release(struct inode *inode, struct file *file)
+{
+	mutex_lock(&dmasound_core_mutex);
+	mixer.busy = 0;
+	module_put(dmasound.mach.owner);
+	mutex_unlock(&dmasound_core_mutex);
+	return 0;
+}
+
+static int mixer_ioctl(struct file *file, u_int cmd, u_long arg)
+{
+	if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+	    mixer.modify_counter++;
+	switch (cmd) {
+	    case OSS_GETVERSION:
+		return IOCTL_OUT(arg, SOUND_VERSION);
+	    case SOUND_MIXER_INFO:
+		{
+		    mixer_info info;
+		    memset(&info, 0, sizeof(info));
+		    strlcpy(info.id, dmasound.mach.name2, sizeof(info.id));
+		    strlcpy(info.name, dmasound.mach.name2, sizeof(info.name));
+		    info.modify_counter = mixer.modify_counter;
+		    if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+			    return -EFAULT;
+		    return 0;
+		}
+	}
+	if (dmasound.mach.mixer_ioctl)
+	    return dmasound.mach.mixer_ioctl(cmd, arg);
+	return -EINVAL;
+}
+
+static long mixer_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
+{
+	int ret;
+
+	mutex_lock(&dmasound_core_mutex);
+	ret = mixer_ioctl(file, cmd, arg);
+	mutex_unlock(&dmasound_core_mutex);
+
+	return ret;
+}
+
+static const struct file_operations mixer_fops =
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.unlocked_ioctl	= mixer_unlocked_ioctl,
+	.open		= mixer_open,
+	.release	= mixer_release,
+};
+
+static void mixer_init(void)
+{
+#ifndef MODULE
+	int mixer_unit;
+#endif
+	mixer_unit = register_sound_mixer(&mixer_fops, -1);
+	if (mixer_unit < 0)
+		return;
+
+	mixer.busy = 0;
+	dmasound.treble = 0;
+	dmasound.bass = 0;
+	if (dmasound.mach.mixer_init)
+	    dmasound.mach.mixer_init();
+}
+
+
+    /*
+     *  Sound queue stuff, the heart of the driver
+     */
+
+struct sound_queue dmasound_write_sq;
+static void sq_reset_output(void) ;
+
+static int sq_allocate_buffers(struct sound_queue *sq, int num, int size)
+{
+	int i;
+
+	if (sq->buffers)
+		return 0;
+	sq->numBufs = num;
+	sq->bufSize = size;
+	sq->buffers = kmalloc (num * sizeof(char *), GFP_KERNEL);
+	if (!sq->buffers)
+		return -ENOMEM;
+	for (i = 0; i < num; i++) {
+		sq->buffers[i] = dmasound.mach.dma_alloc(size, GFP_KERNEL);
+		if (!sq->buffers[i]) {
+			while (i--)
+				dmasound.mach.dma_free(sq->buffers[i], size);
+			kfree(sq->buffers);
+			sq->buffers = NULL;
+			return -ENOMEM;
+		}
+	}
+	return 0;
+}
+
+static void sq_release_buffers(struct sound_queue *sq)
+{
+	int i;
+
+	if (sq->buffers) {
+		for (i = 0; i < sq->numBufs; i++)
+			dmasound.mach.dma_free(sq->buffers[i], sq->bufSize);
+		kfree(sq->buffers);
+		sq->buffers = NULL;
+	}
+}
+
+
+static int sq_setup(struct sound_queue *sq)
+{
+	int (*setup_func)(void) = NULL;
+	int hard_frame ;
+
+	if (sq->locked) { /* are we already set? - and not changeable */
+#ifdef DEBUG_DMASOUND
+printk("dmasound_core: tried to sq_setup a locked queue\n") ;
+#endif
+		return -EINVAL ;
+	}
+	sq->locked = 1 ; /* don't think we have a race prob. here _check_ */
+
+	/* make sure that the parameters are set up
+	   This should have been done already...
+	*/
+
+	dmasound.mach.init();
+
+	/* OK.  If the user has set fragment parameters explicitly, then we
+	   should leave them alone... as long as they are valid.
+	   Invalid user fragment params can occur if we allow the whole buffer
+	   to be used when the user requests the fragments sizes (with no soft
+	   x-lation) and then the user subsequently sets a soft x-lation that
+	   requires increased internal buffering.
+
+	   Othwerwise (if the user did not set them) OSS says that we should
+	   select frag params on the basis of 0.5 s output & 0.1 s input
+	   latency. (TODO.  For now we will copy in the defaults.)
+	*/
+
+	if (sq->user_frags <= 0) {
+		sq->max_count = sq->numBufs ;
+		sq->max_active = sq->numBufs ;
+		sq->block_size = sq->bufSize;
+		/* set up the user info */
+		sq->user_frags = sq->numBufs ;
+		sq->user_frag_size = sq->bufSize ;
+		sq->user_frag_size *=
+			(dmasound.soft.size * (dmasound.soft.stereo+1) ) ;
+		sq->user_frag_size /=
+			(dmasound.hard.size * (dmasound.hard.stereo+1) ) ;
+	} else {
+		/* work out requested block size */
+		sq->block_size = sq->user_frag_size ;
+		sq->block_size *=
+			(dmasound.hard.size * (dmasound.hard.stereo+1) ) ;
+		sq->block_size /=
+			(dmasound.soft.size * (dmasound.soft.stereo+1) ) ;
+		/* the user wants to write frag-size chunks */
+		sq->block_size *= dmasound.hard.speed ;
+		sq->block_size /= dmasound.soft.speed ;
+		/* this only works for size values which are powers of 2 */
+		hard_frame =
+			(dmasound.hard.size * (dmasound.hard.stereo+1))/8 ;
+		sq->block_size +=  (hard_frame - 1) ;
+		sq->block_size &= ~(hard_frame - 1) ; /* make sure we are aligned */
+		/* let's just check for obvious mistakes */
+		if ( sq->block_size <= 0 || sq->block_size > sq->bufSize) {
+#ifdef DEBUG_DMASOUND
+printk("dmasound_core: invalid frag size (user set %d)\n", sq->user_frag_size) ;
+#endif
+			sq->block_size = sq->bufSize ;
+		}
+		if ( sq->user_frags <= sq->numBufs ) {
+			sq->max_count = sq->user_frags ;
+			/* if user has set max_active - then use it */
+			sq->max_active = (sq->max_active <= sq->max_count) ?
+				sq->max_active : sq->max_count ;
+		} else {
+#ifdef DEBUG_DMASOUND
+printk("dmasound_core: invalid frag count (user set %d)\n", sq->user_frags) ;
+#endif
+			sq->max_count =
+			sq->max_active = sq->numBufs ;
+		}
+	}
+	sq->front = sq->count = sq->rear_size = 0;
+	sq->syncing = 0;
+	sq->active = 0;
+
+	if (sq == &write_sq) {
+	    sq->rear = -1;
+	    setup_func = dmasound.mach.write_sq_setup;
+	}
+	if (setup_func)
+	    return setup_func();
+	return 0 ;
+}
+
+static inline void sq_play(void)
+{
+	dmasound.mach.play();
+}
+
+static ssize_t sq_write(struct file *file, const char __user *src, size_t uLeft,
+			loff_t *ppos)
+{
+	ssize_t uWritten = 0;
+	u_char *dest;
+	ssize_t uUsed = 0, bUsed, bLeft;
+	unsigned long flags ;
+
+	/* ++TeSche: Is something like this necessary?
+	 * Hey, that's an honest question! Or does any other part of the
+	 * filesystem already checks this situation? I really don't know.
+	 */
+	if (uLeft == 0)
+		return 0;
+
+	/* implement any changes we have made to the soft/hard params.
+	   this is not satisfactory really, all we have done up to now is to
+	   say what we would like - there hasn't been any real checking of capability
+	*/
+
+	if (shared_resources_initialised == 0) {
+		dmasound.mach.init() ;
+		shared_resources_initialised = 1 ;
+	}
+
+	/* set up the sq if it is not already done. This may seem a dumb place
+	   to do it - but it is what OSS requires.  It means that write() can
+	   return memory allocation errors.  To avoid this possibility use the
+	   GETBLKSIZE or GETOSPACE ioctls (after you've fiddled with all the
+	   params you want to change) - these ioctls also force the setup.
+	*/
+
+	if (write_sq.locked == 0) {
+		if ((uWritten = sq_setup(&write_sq)) < 0) return uWritten ;
+		uWritten = 0 ;
+	}
+
+/* FIXME: I think that this may be the wrong behaviour when we get strapped
+	for time and the cpu is close to being (or actually) behind in sending data.
+	- because we've lost the time that the N samples, already in the buffer,
+	would have given us to get here with the next lot from the user.
+*/
+	/* The interrupt doesn't start to play the last, incomplete frame.
+	 * Thus we can append to it without disabling the interrupts! (Note
+	 * also that write_sq.rear isn't affected by the interrupt.)
+	 */
+
+	/* as of 1.6 this behaviour changes if SNDCTL_DSP_POST has been issued:
+	   this will mimic the behaviour of syncing and allow the sq_play() to
+	   queue a partial fragment.  Since sq_play() may/will be called from
+	   the IRQ handler - at least on Pmac we have to deal with it.
+	   The strategy - possibly not optimum - is to kill _POST status if we
+	   get here.  This seems, at least, reasonable - in the sense that POST
+	   is supposed to indicate that we might not write before the queue
+	   is drained - and if we get here in time then it does not apply.
+	*/
+
+	spin_lock_irqsave(&dmasound.lock, flags);
+	write_sq.syncing &= ~2 ; /* take out POST status */
+	spin_unlock_irqrestore(&dmasound.lock, flags);
+
+	if (write_sq.count > 0 &&
+	    (bLeft = write_sq.block_size-write_sq.rear_size) > 0) {
+		dest = write_sq.buffers[write_sq.rear];
+		bUsed = write_sq.rear_size;
+		uUsed = sound_copy_translate(dmasound.trans_write, src, uLeft,
+					     dest, &bUsed, bLeft);
+		if (uUsed <= 0)
+			return uUsed;
+		src += uUsed;
+		uWritten += uUsed;
+		uLeft = (uUsed <= uLeft) ? (uLeft - uUsed) : 0 ; /* paranoia */
+		write_sq.rear_size = bUsed;
+	}
+
+	while (uLeft) {
+		while (write_sq.count >= write_sq.max_active) {
+			sq_play();
+			if (write_sq.non_blocking)
+				return uWritten > 0 ? uWritten : -EAGAIN;
+			SLEEP(write_sq.action_queue);
+			if (signal_pending(current))
+				return uWritten > 0 ? uWritten : -EINTR;
+		}
+
+		/* Here, we can avoid disabling the interrupt by first
+		 * copying and translating the data, and then updating
+		 * the write_sq variables. Until this is done, the interrupt
+		 * won't see the new frame and we can work on it
+		 * undisturbed.
+		 */
+
+		dest = write_sq.buffers[(write_sq.rear+1) % write_sq.max_count];
+		bUsed = 0;
+		bLeft = write_sq.block_size;
+		uUsed = sound_copy_translate(dmasound.trans_write, src, uLeft,
+					     dest, &bUsed, bLeft);
+		if (uUsed <= 0)
+			break;
+		src += uUsed;
+		uWritten += uUsed;
+		uLeft = (uUsed <= uLeft) ? (uLeft - uUsed) : 0 ; /* paranoia */
+		if (bUsed) {
+			write_sq.rear = (write_sq.rear+1) % write_sq.max_count;
+			write_sq.rear_size = bUsed;
+			write_sq.count++;
+		}
+	} /* uUsed may have been 0 */
+
+	sq_play();
+
+	return uUsed < 0? uUsed: uWritten;
+}
+
+static unsigned int sq_poll(struct file *file, struct poll_table_struct *wait)
+{
+	unsigned int mask = 0;
+	int retVal;
+	
+	if (write_sq.locked == 0) {
+		if ((retVal = sq_setup(&write_sq)) < 0)
+			return retVal;
+		return 0;
+	}
+	if (file->f_mode & FMODE_WRITE )
+		poll_wait(file, &write_sq.action_queue, wait);
+	if (file->f_mode & FMODE_WRITE)
+		if (write_sq.count < write_sq.max_active || write_sq.block_size - write_sq.rear_size > 0)
+			mask |= POLLOUT | POLLWRNORM;
+	return mask;
+
+}
+
+static inline void sq_init_waitqueue(struct sound_queue *sq)
+{
+	init_waitqueue_head(&sq->action_queue);
+	init_waitqueue_head(&sq->open_queue);
+	init_waitqueue_head(&sq->sync_queue);
+	sq->busy = 0;
+}
+
+#if 0 /* blocking open() */
+static inline void sq_wake_up(struct sound_queue *sq, struct file *file,
+			      fmode_t mode)
+{
+	if (file->f_mode & mode) {
+		sq->busy = 0; /* CHECK: IS THIS OK??? */
+		WAKE_UP(sq->open_queue);
+	}
+}
+#endif
+
+static int sq_open2(struct sound_queue *sq, struct file *file, fmode_t mode,
+		    int numbufs, int bufsize)
+{
+	int rc = 0;
+
+	if (file->f_mode & mode) {
+		if (sq->busy) {
+#if 0 /* blocking open() */
+			rc = -EBUSY;
+			if (file->f_flags & O_NONBLOCK)
+				return rc;
+			rc = -EINTR;
+			while (sq->busy) {
+				SLEEP(sq->open_queue);
+				if (signal_pending(current))
+					return rc;
+			}
+			rc = 0;
+#else
+			/* OSS manual says we will return EBUSY regardless
+			   of O_NOBLOCK.
+			*/
+			return -EBUSY ;
+#endif
+		}
+		sq->busy = 1; /* Let's play spot-the-race-condition */
+
+		/* allocate the default number & size of buffers.
+		   (i.e. specified in _setup() or as module params)
+		   can't be changed at the moment - but _could_ be perhaps
+		   in the setfragments ioctl.
+		*/
+		if (( rc = sq_allocate_buffers(sq, numbufs, bufsize))) {
+#if 0 /* blocking open() */
+			sq_wake_up(sq, file, mode);
+#else
+			sq->busy = 0 ;
+#endif
+			return rc;
+		}
+
+		sq->non_blocking = file->f_flags & O_NONBLOCK;
+	}
+	return rc;
+}
+
+#define write_sq_init_waitqueue()	sq_init_waitqueue(&write_sq)
+#if 0 /* blocking open() */
+#define write_sq_wake_up(file)		sq_wake_up(&write_sq, file, FMODE_WRITE)
+#endif
+#define write_sq_release_buffers()	sq_release_buffers(&write_sq)
+#define write_sq_open(file)	\
+	sq_open2(&write_sq, file, FMODE_WRITE, numWriteBufs, writeBufSize )
+
+static int sq_open(struct inode *inode, struct file *file)
+{
+	int rc;
+
+	mutex_lock(&dmasound_core_mutex);
+	if (!try_module_get(dmasound.mach.owner)) {
+		mutex_unlock(&dmasound_core_mutex);
+		return -ENODEV;
+	}
+
+	rc = write_sq_open(file); /* checks the f_mode */
+	if (rc)
+		goto out;
+	if (file->f_mode & FMODE_READ) {
+		/* TODO: if O_RDWR, release any resources grabbed by write part */
+		rc = -ENXIO ; /* I think this is what is required by open(2) */
+		goto out;
+	}
+
+	if (dmasound.mach.sq_open)
+	    dmasound.mach.sq_open(file->f_mode);
+
+	/* CHECK whether this is sensible - in the case that dsp0 could be opened
+	  O_RDONLY and dsp1 could be opened O_WRONLY
+	*/
+
+	dmasound.minDev = iminor(inode) & 0x0f;
+
+	/* OK. - we should make some attempt at consistency. At least the H'ware
+	   options should be set with a valid mode.  We will make it that the LL
+	   driver must supply defaults for hard & soft params.
+	*/
+
+	if (shared_resource_owner == 0) {
+		/* you can make this AFMT_U8/mono/8K if you want to mimic old
+		   OSS behaviour - while we still have soft translations ;-) */
+		dmasound.soft = dmasound.mach.default_soft ;
+		dmasound.dsp = dmasound.mach.default_soft ;
+		dmasound.hard = dmasound.mach.default_hard ;
+	}
+
+#ifndef DMASOUND_STRICT_OSS_COMPLIANCE
+	/* none of the current LL drivers can actually do this "native" at the moment
+	   OSS does not really require us to supply /dev/audio if we can't do it.
+	*/
+	if (dmasound.minDev == SND_DEV_AUDIO) {
+		sound_set_speed(8000);
+		sound_set_stereo(0);
+		sound_set_format(AFMT_MU_LAW);
+	}
+#endif
+	mutex_unlock(&dmasound_core_mutex);
+	return 0;
+ out:
+	module_put(dmasound.mach.owner);
+	mutex_unlock(&dmasound_core_mutex);
+	return rc;
+}
+
+static void sq_reset_output(void)
+{
+	sound_silence(); /* this _must_ stop DMA, we might be about to lose the buffers */
+	write_sq.active = 0;
+	write_sq.count = 0;
+	write_sq.rear_size = 0;
+	/* write_sq.front = (write_sq.rear+1) % write_sq.max_count;*/
+	write_sq.front = 0 ;
+	write_sq.rear = -1 ; /* same as for set-up */
+
+	/* OK - we can unlock the parameters and fragment settings */
+	write_sq.locked = 0 ;
+	write_sq.user_frags = 0 ;
+	write_sq.user_frag_size = 0 ;
+}
+
+static void sq_reset(void)
+{
+	sq_reset_output() ;
+	/* we could consider resetting the shared_resources_owner here... but I
+	   think it is probably still rather non-obvious to application writer
+	*/
+
+	/* we release everything else though */
+	shared_resources_initialised = 0 ;
+}
+
+static int sq_fsync(struct file *filp, struct dentry *dentry)
+{
+	int rc = 0;
+	int timeout = 5;
+
+	write_sq.syncing |= 1;
+	sq_play();	/* there may be an incomplete frame waiting */
+
+	while (write_sq.active) {
+		SLEEP(write_sq.sync_queue);
+		if (signal_pending(current)) {
+			/* While waiting for audio output to drain, an
+			 * interrupt occurred.  Stop audio output immediately
+			 * and clear the queue. */
+			sq_reset_output();
+			rc = -EINTR;
+			break;
+		}
+		if (!--timeout) {
+			printk(KERN_WARNING "dmasound: Timeout draining output\n");
+			sq_reset_output();
+			rc = -EIO;
+			break;
+		}
+	}
+
+	/* flag no sync regardless of whether we had a DSP_POST or not */
+	write_sq.syncing = 0 ;
+	return rc;
+}
+
+static int sq_release(struct inode *inode, struct file *file)
+{
+	int rc = 0;
+
+	mutex_lock(&dmasound_core_mutex);
+
+	if (file->f_mode & FMODE_WRITE) {
+		if (write_sq.busy)
+			rc = sq_fsync(file, file->f_path.dentry);
+
+		sq_reset_output() ; /* make sure dma is stopped and all is quiet */
+		write_sq_release_buffers();
+		write_sq.busy = 0;
+	}
+
+	if (file->f_mode & shared_resource_owner) { /* it's us that has them */
+		shared_resource_owner = 0 ;
+		shared_resources_initialised = 0 ;
+		dmasound.hard = dmasound.mach.default_hard ;
+	}
+
+	module_put(dmasound.mach.owner);
+
+#if 0 /* blocking open() */
+	/* Wake up a process waiting for the queue being released.
+	 * Note: There may be several processes waiting for a call
+	 * to open() returning. */
+
+	/* Iain: hmm I don't understand this next comment ... */
+	/* There is probably a DOS atack here. They change the mode flag. */
+	/* XXX add check here,*/
+	read_sq_wake_up(file); /* checks f_mode */
+	write_sq_wake_up(file); /* checks f_mode */
+#endif /* blocking open() */
+
+	mutex_unlock(&dmasound_core_mutex);
+
+	return rc;
+}
+
+/* here we see if we have a right to modify format, channels, size and so on
+   if no-one else has claimed it already then we do...
+
+   TODO: We might change this to mask O_RDWR such that only one or the other channel
+   is the owner - if we have problems.
+*/
+
+static int shared_resources_are_mine(fmode_t md)
+{
+	if (shared_resource_owner)
+		return (shared_resource_owner & md) != 0;
+	else {
+		shared_resource_owner = md ;
+		return 1 ;
+	}
+}
+
+/* if either queue is locked we must deny the right to change shared params
+*/
+
+static int queues_are_quiescent(void)
+{
+	if (write_sq.locked)
+		return 0 ;
+	return 1 ;
+}
+
+/* check and set a queue's fragments per user's wishes...
+   we will check against the pre-defined literals and the actual sizes.
+   This is a bit fraught - because soft translations can mess with our
+   buffer requirements *after* this call - OSS says "call setfrags first"
+*/
+
+/* It is possible to replace all the -EINVAL returns with an override that
+   just puts the allowable value in.  This may be what many OSS apps require
+*/
+
+static int set_queue_frags(struct sound_queue *sq, int bufs, int size)
+{
+	if (sq->locked) {
+#ifdef DEBUG_DMASOUND
+printk("dmasound_core: tried to set_queue_frags on a locked queue\n") ;
+#endif
+		return -EINVAL ;
+	}
+
+	if ((size < MIN_FRAG_SIZE) || (size > MAX_FRAG_SIZE))
+		return -EINVAL ;
+	size = (1<<size) ; /* now in bytes */
+	if (size > sq->bufSize)
+		return -EINVAL ; /* this might still not work */
+
+	if (bufs <= 0)
+		return -EINVAL ;
+	if (bufs > sq->numBufs) /* the user is allowed say "don't care" with 0x7fff */
+		bufs = sq->numBufs ;
+
+	/* there is, currently, no way to specify max_active separately
+	   from max_count.  This could be a LL driver issue - I guess
+	   if there is a requirement for these values to be different then
+	  we will have to pass that info. up to this level.
+	*/
+	sq->user_frags =
+	sq->max_active = bufs ;
+	sq->user_frag_size = size ;
+
+	return 0 ;
+}
+
+static int sq_ioctl(struct file *file, u_int cmd, u_long arg)
+{
+	int val, result;
+	u_long fmt;
+	int data;
+	int size, nbufs;
+	audio_buf_info info;
+
+	switch (cmd) {
+	case SNDCTL_DSP_RESET:
+		sq_reset();
+		return 0;
+		break ;
+	case SNDCTL_DSP_GETFMTS:
+		fmt = dmasound.mach.hardware_afmts ; /* this is what OSS says.. */
+		return IOCTL_OUT(arg, fmt);
+		break ;
+	case SNDCTL_DSP_GETBLKSIZE:
+		/* this should tell the caller about bytes that the app can
+		   read/write - the app doesn't care about our internal buffers.
+		   We force sq_setup() here as per OSS 1.1 (which should
+		   compute the values necessary).
+		   Since there is no mechanism to specify read/write separately, for
+		   fds opened O_RDWR, the write_sq values will, arbitrarily, overwrite
+		   the read_sq ones.
+		*/
+		size = 0 ;
+		if (file->f_mode & FMODE_WRITE) {
+			if ( !write_sq.locked )
+				sq_setup(&write_sq) ;
+			size = write_sq.user_frag_size ;
+		}
+		return IOCTL_OUT(arg, size);
+		break ;
+	case SNDCTL_DSP_POST:
+		/* all we are going to do is to tell the LL that any
+		   partial frags can be queued for output.
+		   The LL will have to clear this flag when last output
+		   is queued.
+		*/
+		write_sq.syncing |= 0x2 ;
+		sq_play() ;
+		return 0 ;
+	case SNDCTL_DSP_SYNC:
+		/* This call, effectively, has the same behaviour as SNDCTL_DSP_RESET
+		   except that it waits for output to finish before resetting
+		   everything - read, however, is killed imediately.
+		*/
+		result = 0 ;
+		if (file->f_mode & FMODE_WRITE) {
+			result = sq_fsync(file, file->f_path.dentry);
+			sq_reset_output() ;
+		}
+		/* if we are the shared resource owner then release them */
+		if (file->f_mode & shared_resource_owner)
+			shared_resources_initialised = 0 ;
+		return result ;
+		break ;
+	case SOUND_PCM_READ_RATE:
+		return IOCTL_OUT(arg, dmasound.soft.speed);
+	case SNDCTL_DSP_SPEED:
+		/* changing this on the fly will have weird effects on the sound.
+		   Where there are rate conversions implemented in soft form - it
+		   will cause the _ctx_xxx() functions to be substituted.
+		   However, there doesn't appear to be any reason to dis-allow it from
+		   a driver pov.
+		*/
+		if (shared_resources_are_mine(file->f_mode)) {
+			IOCTL_IN(arg, data);
+			data = sound_set_speed(data) ;
+			shared_resources_initialised = 0 ;
+			return IOCTL_OUT(arg, data);
+		} else
+			return -EINVAL ;
+		break ;
+	/* OSS says these next 4 actions are undefined when the device is
+	   busy/active - we will just return -EINVAL.
+	   To be allowed to change one - (a) you have to own the right
+	    (b) the queue(s) must be quiescent
+	*/
+	case SNDCTL_DSP_STEREO:
+		if (shared_resources_are_mine(file->f_mode) &&
+		    queues_are_quiescent()) {
+			IOCTL_IN(arg, data);
+			shared_resources_initialised = 0 ;
+			return IOCTL_OUT(arg, sound_set_stereo(data));
+		} else
+			return -EINVAL ;
+		break ;
+	case SOUND_PCM_WRITE_CHANNELS:
+		if (shared_resources_are_mine(file->f_mode) &&
+		    queues_are_quiescent()) {
+			IOCTL_IN(arg, data);
+			/* the user might ask for 20 channels, we will return 1 or 2 */
+			shared_resources_initialised = 0 ;
+			return IOCTL_OUT(arg, sound_set_stereo(data-1)+1);
+		} else
+			return -EINVAL ;
+		break ;
+	case SNDCTL_DSP_SETFMT:
+		if (shared_resources_are_mine(file->f_mode) &&
+		    queues_are_quiescent()) {
+		    	int format;
+			IOCTL_IN(arg, data);
+			shared_resources_initialised = 0 ;
+			format = sound_set_format(data);
+			result = IOCTL_OUT(arg, format);
+			if (result < 0)
+				return result;
+			if (format != data && data != AFMT_QUERY)
+				return -EINVAL;
+			return 0;
+		} else
+			return -EINVAL ;
+	case SNDCTL_DSP_SUBDIVIDE:
+		return -EINVAL ;
+	case SNDCTL_DSP_SETFRAGMENT:
+		/* we can do this independently for the two queues - with the
+		   proviso that for fds opened O_RDWR we cannot separate the
+		   actions and both queues will be set per the last call.
+		   NOTE: this does *NOT* actually set the queue up - merely
+		   registers our intentions.
+		*/
+		IOCTL_IN(arg, data);
+		result = 0 ;
+		nbufs = (data >> 16) & 0x7fff ; /* 0x7fff is 'use maximum' */
+		size = data & 0xffff;
+		if (file->f_mode & FMODE_WRITE) {
+			result = set_queue_frags(&write_sq, nbufs, size) ;
+			if (result)
+				return result ;
+		}
+		/* NOTE: this return value is irrelevant - OSS specifically says that
+		   the value is 'random' and that the user _must_ check the actual
+		   frags values using SNDCTL_DSP_GETBLKSIZE or similar */
+		return IOCTL_OUT(arg, data);
+		break ;
+	case SNDCTL_DSP_GETOSPACE:
+		/*
+		*/
+		if (file->f_mode & FMODE_WRITE) {
+			if ( !write_sq.locked )
+				sq_setup(&write_sq) ;
+			info.fragments = write_sq.max_active - write_sq.count;
+			info.fragstotal = write_sq.max_active;
+			info.fragsize = write_sq.user_frag_size;
+			info.bytes = info.fragments * info.fragsize;
+			if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+				return -EFAULT;
+			return 0;
+		} else
+			return -EINVAL ;
+		break ;
+	case SNDCTL_DSP_GETCAPS:
+		val = dmasound.mach.capabilities & 0xffffff00;
+		return IOCTL_OUT(arg,val);
+
+	default:
+		return mixer_ioctl(file, cmd, arg);
+	}
+	return -EINVAL;
+}
+
+static long sq_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
+{
+	int ret;
+
+	mutex_lock(&dmasound_core_mutex);
+	ret = sq_ioctl(file, cmd, arg);
+	mutex_unlock(&dmasound_core_mutex);
+
+	return ret;
+}
+
+static const struct file_operations sq_fops =
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= sq_write,
+	.poll		= sq_poll,
+	.unlocked_ioctl	= sq_unlocked_ioctl,
+	.open		= sq_open,
+	.release	= sq_release,
+};
+
+static int sq_init(void)
+{
+	const struct file_operations *fops = &sq_fops;
+#ifndef MODULE
+	int sq_unit;
+#endif
+
+	sq_unit = register_sound_dsp(fops, -1);
+	if (sq_unit < 0) {
+		printk(KERN_ERR "dmasound_core: couldn't register fops\n") ;
+		return sq_unit ;
+	}
+
+	write_sq_init_waitqueue();
+
+	/* These parameters will be restored for every clean open()
+	 * in the case of multiple open()s (e.g. dsp0 & dsp1) they
+	 * will be set so long as the shared resources have no owner.
+	 */
+
+	if (shared_resource_owner == 0) {
+		dmasound.soft = dmasound.mach.default_soft ;
+		dmasound.hard = dmasound.mach.default_hard ;
+		dmasound.dsp = dmasound.mach.default_soft ;
+		shared_resources_initialised = 0 ;
+	}
+	return 0 ;
+}
+
+
+    /*
+     *  /dev/sndstat
+     */
+
+/* we allow more space for record-enabled because there are extra output lines.
+   the number here must include the amount we are prepared to give to the low-level
+   driver.
+*/
+
+#define STAT_BUFF_LEN 768
+
+/* this is how much space we will allow the low-level driver to use
+   in the stat buffer.  Currently, 2 * (80 character line + <NL>).
+   We do not police this (it is up to the ll driver to be honest).
+*/
+
+#define LOW_LEVEL_STAT_ALLOC 162
+
+static struct {
+    int busy;
+    char buf[STAT_BUFF_LEN];	/* state.buf should not overflow! */
+    int len, ptr;
+} state;
+
+/* publish this function for use by low-level code, if required */
+
+static char *get_afmt_string(int afmt)
+{
+        switch(afmt) {
+            case AFMT_MU_LAW:
+                return "mu-law";
+                break;
+            case AFMT_A_LAW:
+                return "A-law";
+                break;
+            case AFMT_U8:
+                return "unsigned 8 bit";
+                break;
+            case AFMT_S8:
+                return "signed 8 bit";
+                break;
+            case AFMT_S16_BE:
+                return "signed 16 bit BE";
+                break;
+            case AFMT_U16_BE:
+                return "unsigned 16 bit BE";
+                break;
+            case AFMT_S16_LE:
+                return "signed 16 bit LE";
+                break;
+            case AFMT_U16_LE:
+                return "unsigned 16 bit LE";
+                break;
+	    case 0:
+		return "format not set" ;
+		break ;
+            default:
+                break ;
+        }
+        return "ERROR: Unsupported AFMT_XXXX code" ;
+}
+
+static int state_open(struct inode *inode, struct file *file)
+{
+	char *buffer = state.buf;
+	int len = 0;
+	int ret;
+
+	mutex_lock(&dmasound_core_mutex);
+	ret = -EBUSY;
+	if (state.busy)
+		goto out;
+
+	ret = -ENODEV;
+	if (!try_module_get(dmasound.mach.owner))
+		goto out;
+
+	state.ptr = 0;
+	state.busy = 1;
+
+	len += sprintf(buffer+len, "%sDMA sound driver rev %03d :\n",
+		dmasound.mach.name, (DMASOUND_CORE_REVISION<<4) +
+		((dmasound.mach.version>>8) & 0x0f));
+	len += sprintf(buffer+len,
+		"Core driver edition %02d.%02d : %s driver edition %02d.%02d\n",
+		DMASOUND_CORE_REVISION, DMASOUND_CORE_EDITION, dmasound.mach.name2,
+		(dmasound.mach.version >> 8), (dmasound.mach.version & 0xff)) ;
+
+	/* call the low-level module to fill in any stat info. that it has
+	   if present.  Maximum buffer usage is specified.
+	*/
+
+	if (dmasound.mach.state_info)
+		len += dmasound.mach.state_info(buffer+len,
+			(size_t) LOW_LEVEL_STAT_ALLOC) ;
+
+	/* make usage of the state buffer as deterministic as poss.
+	   exceptional conditions could cause overrun - and this is flagged as
+	   a kernel error.
+	*/
+
+	/* formats and settings */
+
+	len += sprintf(buffer+len,"\t\t === Formats & settings ===\n") ;
+	len += sprintf(buffer+len,"Parameter %20s%20s\n","soft","hard") ;
+	len += sprintf(buffer+len,"Format   :%20s%20s\n",
+		get_afmt_string(dmasound.soft.format),
+		get_afmt_string(dmasound.hard.format));
+
+	len += sprintf(buffer+len,"Samp Rate:%14d s/sec%14d s/sec\n",
+		       dmasound.soft.speed, dmasound.hard.speed);
+
+	len += sprintf(buffer+len,"Channels :%20s%20s\n",
+		       dmasound.soft.stereo ? "stereo" : "mono",
+		       dmasound.hard.stereo ? "stereo" : "mono" );
+
+	/* sound queue status */
+
+	len += sprintf(buffer+len,"\t\t === Sound Queue status ===\n");
+	len += sprintf(buffer+len,"Allocated:%8s%6s\n","Buffers","Size") ;
+	len += sprintf(buffer+len,"%9s:%8d%6d\n",
+		"write", write_sq.numBufs, write_sq.bufSize) ;
+	len += sprintf(buffer+len,
+		"Current  : MaxFrg FragSiz MaxAct Frnt Rear "
+		"Cnt RrSize A B S L  xruns\n") ;
+	len += sprintf(buffer+len,"%9s:%7d%8d%7d%5d%5d%4d%7d%2d%2d%2d%2d%7d\n",
+		"write", write_sq.max_count, write_sq.block_size,
+		write_sq.max_active, write_sq.front, write_sq.rear,
+		write_sq.count, write_sq.rear_size, write_sq.active,
+		write_sq.busy, write_sq.syncing, write_sq.locked, write_sq.xruns) ;
+#ifdef DEBUG_DMASOUND
+printk("dmasound: stat buffer used %d bytes\n", len) ;
+#endif
+
+	if (len >= STAT_BUFF_LEN)
+		printk(KERN_ERR "dmasound_core: stat buffer overflowed!\n");
+
+	state.len = len;
+	ret = 0;
+out:
+	mutex_unlock(&dmasound_core_mutex);
+	return ret;
+}
+
+static int state_release(struct inode *inode, struct file *file)
+{
+	mutex_lock(&dmasound_core_mutex);
+	state.busy = 0;
+	module_put(dmasound.mach.owner);
+	mutex_unlock(&dmasound_core_mutex);
+	return 0;
+}
+
+static ssize_t state_read(struct file *file, char __user *buf, size_t count,
+			  loff_t *ppos)
+{
+	int n = state.len - state.ptr;
+	if (n > count)
+		n = count;
+	if (n <= 0)
+		return 0;
+	if (copy_to_user(buf, &state.buf[state.ptr], n))
+		return -EFAULT;
+	state.ptr += n;
+	return n;
+}
+
+static const struct file_operations state_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= state_read,
+	.open		= state_open,
+	.release	= state_release,
+};
+
+static int state_init(void)
+{
+#ifndef MODULE
+	int state_unit;
+#endif
+	state_unit = register_sound_special(&state_fops, SND_DEV_STATUS);
+	if (state_unit < 0)
+		return state_unit ;
+	state.busy = 0;
+	return 0 ;
+}
+
+
+    /*
+     *  Config & Setup
+     *
+     *  This function is called by _one_ chipset-specific driver
+     */
+
+int dmasound_init(void)
+{
+	int res ;
+#ifdef MODULE
+	if (irq_installed)
+		return -EBUSY;
+#endif
+
+	/* Set up sound queue, /dev/audio and /dev/dsp. */
+
+	/* Set default settings. */
+	if ((res = sq_init()) < 0)
+		return res ;
+
+	/* Set up /dev/sndstat. */
+	if ((res = state_init()) < 0)
+		return res ;
+
+	/* Set up /dev/mixer. */
+	mixer_init();
+
+	if (!dmasound.mach.irqinit()) {
+		printk(KERN_ERR "DMA sound driver: Interrupt initialization failed\n");
+		return -ENODEV;
+	}
+#ifdef MODULE
+	irq_installed = 1;
+#endif
+
+	printk(KERN_INFO "%s DMA sound driver rev %03d installed\n",
+		dmasound.mach.name, (DMASOUND_CORE_REVISION<<4) +
+		((dmasound.mach.version>>8) & 0x0f));
+	printk(KERN_INFO
+		"Core driver edition %02d.%02d : %s driver edition %02d.%02d\n",
+		DMASOUND_CORE_REVISION, DMASOUND_CORE_EDITION, dmasound.mach.name2,
+		(dmasound.mach.version >> 8), (dmasound.mach.version & 0xff)) ;
+	printk(KERN_INFO "Write will use %4d fragments of %7d bytes as default\n",
+		numWriteBufs, writeBufSize) ;
+	return 0;
+}
+
+#ifdef MODULE
+
+void dmasound_deinit(void)
+{
+	if (irq_installed) {
+		sound_silence();
+		dmasound.mach.irqcleanup();
+		irq_installed = 0;
+	}
+
+	write_sq_release_buffers();
+
+	if (mixer_unit >= 0)
+		unregister_sound_mixer(mixer_unit);
+	if (state_unit >= 0)
+		unregister_sound_special(state_unit);
+	if (sq_unit >= 0)
+		unregister_sound_dsp(sq_unit);
+}
+
+#else /* !MODULE */
+
+static int dmasound_setup(char *str)
+{
+	int ints[6], size;
+
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+
+	/* check the bootstrap parameter for "dmasound=" */
+
+	/* FIXME: other than in the most naive of cases there is no sense in these
+	 *	  buffers being other than powers of two.  This is not checked yet.
+	 */
+
+	switch (ints[0]) {
+	case 3:
+		if ((ints[3] < 0) || (ints[3] > MAX_CATCH_RADIUS))
+			printk("dmasound_setup: invalid catch radius, using default = %d\n", catchRadius);
+		else
+			catchRadius = ints[3];
+		/* fall through */
+	case 2:
+		if (ints[1] < MIN_BUFFERS)
+			printk("dmasound_setup: invalid number of buffers, using default = %d\n", numWriteBufs);
+		else
+			numWriteBufs = ints[1];
+		/* fall through */
+	case 1:
+		if ((size = ints[2]) < 256) /* check for small buffer specs */
+			size <<= 10 ;
+                if (size < MIN_BUFSIZE || size > MAX_BUFSIZE)
+                        printk("dmasound_setup: invalid write buffer size, using default = %d\n", writeBufSize);
+                else
+                        writeBufSize = size;
+	case 0:
+		break;
+	default:
+		printk("dmasound_setup: invalid number of arguments\n");
+		return 0;
+	}
+	return 1;
+}
+
+__setup("dmasound=", dmasound_setup);
+
+#endif /* !MODULE */
+
+    /*
+     *  Conversion tables
+     */
+
+#ifdef HAS_8BIT_TABLES
+/* 8 bit mu-law */
+
+char dmasound_ulaw2dma8[] = {
+	-126,	-122,	-118,	-114,	-110,	-106,	-102,	-98,
+	-94,	-90,	-86,	-82,	-78,	-74,	-70,	-66,
+	-63,	-61,	-59,	-57,	-55,	-53,	-51,	-49,
+	-47,	-45,	-43,	-41,	-39,	-37,	-35,	-33,
+	-31,	-30,	-29,	-28,	-27,	-26,	-25,	-24,
+	-23,	-22,	-21,	-20,	-19,	-18,	-17,	-16,
+	-16,	-15,	-15,	-14,	-14,	-13,	-13,	-12,
+	-12,	-11,	-11,	-10,	-10,	-9,	-9,	-8,
+	-8,	-8,	-7,	-7,	-7,	-7,	-6,	-6,
+	-6,	-6,	-5,	-5,	-5,	-5,	-4,	-4,
+	-4,	-4,	-4,	-4,	-3,	-3,	-3,	-3,
+	-3,	-3,	-3,	-3,	-2,	-2,	-2,	-2,
+	-2,	-2,	-2,	-2,	-2,	-2,	-2,	-2,
+	-1,	-1,	-1,	-1,	-1,	-1,	-1,	-1,
+	-1,	-1,	-1,	-1,	-1,	-1,	-1,	-1,
+	-1,	-1,	-1,	-1,	-1,	-1,	-1,	0,
+	125,	121,	117,	113,	109,	105,	101,	97,
+	93,	89,	85,	81,	77,	73,	69,	65,
+	62,	60,	58,	56,	54,	52,	50,	48,
+	46,	44,	42,	40,	38,	36,	34,	32,
+	30,	29,	28,	27,	26,	25,	24,	23,
+	22,	21,	20,	19,	18,	17,	16,	15,
+	15,	14,	14,	13,	13,	12,	12,	11,
+	11,	10,	10,	9,	9,	8,	8,	7,
+	7,	7,	6,	6,	6,	6,	5,	5,
+	5,	5,	4,	4,	4,	4,	3,	3,
+	3,	3,	3,	3,	2,	2,	2,	2,
+	2,	2,	2,	2,	1,	1,	1,	1,
+	1,	1,	1,	1,	1,	1,	1,	1,
+	0,	0,	0,	0,	0,	0,	0,	0,
+	0,	0,	0,	0,	0,	0,	0,	0,
+	0,	0,	0,	0,	0,	0,	0,	0
+};
+
+/* 8 bit A-law */
+
+char dmasound_alaw2dma8[] = {
+	-22,	-21,	-24,	-23,	-18,	-17,	-20,	-19,
+	-30,	-29,	-32,	-31,	-26,	-25,	-28,	-27,
+	-11,	-11,	-12,	-12,	-9,	-9,	-10,	-10,
+	-15,	-15,	-16,	-16,	-13,	-13,	-14,	-14,
+	-86,	-82,	-94,	-90,	-70,	-66,	-78,	-74,
+	-118,	-114,	-126,	-122,	-102,	-98,	-110,	-106,
+	-43,	-41,	-47,	-45,	-35,	-33,	-39,	-37,
+	-59,	-57,	-63,	-61,	-51,	-49,	-55,	-53,
+	-2,	-2,	-2,	-2,	-2,	-2,	-2,	-2,
+	-2,	-2,	-2,	-2,	-2,	-2,	-2,	-2,
+	-1,	-1,	-1,	-1,	-1,	-1,	-1,	-1,
+	-1,	-1,	-1,	-1,	-1,	-1,	-1,	-1,
+	-6,	-6,	-6,	-6,	-5,	-5,	-5,	-5,
+	-8,	-8,	-8,	-8,	-7,	-7,	-7,	-7,
+	-3,	-3,	-3,	-3,	-3,	-3,	-3,	-3,
+	-4,	-4,	-4,	-4,	-4,	-4,	-4,	-4,
+	21,	20,	23,	22,	17,	16,	19,	18,
+	29,	28,	31,	30,	25,	24,	27,	26,
+	10,	10,	11,	11,	8,	8,	9,	9,
+	14,	14,	15,	15,	12,	12,	13,	13,
+	86,	82,	94,	90,	70,	66,	78,	74,
+	118,	114,	126,	122,	102,	98,	110,	106,
+	43,	41,	47,	45,	35,	33,	39,	37,
+	59,	57,	63,	61,	51,	49,	55,	53,
+	1,	1,	1,	1,	1,	1,	1,	1,
+	1,	1,	1,	1,	1,	1,	1,	1,
+	0,	0,	0,	0,	0,	0,	0,	0,
+	0,	0,	0,	0,	0,	0,	0,	0,
+	5,	5,	5,	5,	4,	4,	4,	4,
+	7,	7,	7,	7,	6,	6,	6,	6,
+	2,	2,	2,	2,	2,	2,	2,	2,
+	3,	3,	3,	3,	3,	3,	3,	3
+};
+#endif /* HAS_8BIT_TABLES */
+
+    /*
+     *  Visible symbols for modules
+     */
+
+EXPORT_SYMBOL(dmasound);
+EXPORT_SYMBOL(dmasound_init);
+#ifdef MODULE
+EXPORT_SYMBOL(dmasound_deinit);
+#endif
+EXPORT_SYMBOL(dmasound_write_sq);
+EXPORT_SYMBOL(dmasound_catchRadius);
+#ifdef HAS_8BIT_TABLES
+EXPORT_SYMBOL(dmasound_ulaw2dma8);
+EXPORT_SYMBOL(dmasound_alaw2dma8);
+#endif
diff --git a/linux/sound/oss/dmasound/dmasound_paula.c b/linux/sound/oss/dmasound/dmasound_paula.c
new file mode 100644
index 0000000..87910e9
--- /dev/null
+++ b/linux/sound/oss/dmasound/dmasound_paula.c
@@ -0,0 +1,751 @@
+/*
+ *  linux/sound/oss/dmasound/dmasound_paula.c
+ *
+ *  Amiga `Paula' DMA Sound Driver
+ *
+ *  See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits
+ *  prior to 28/01/2001
+ *
+ *  28/01/2001 [0.1] Iain Sandoe
+ *		     - added versioning
+ *		     - put in and populated the hardware_afmts field.
+ *             [0.2] - put in SNDCTL_DSP_GETCAPS value.
+ *	       [0.3] - put in constraint on state buffer usage.
+ *	       [0.4] - put in default hard/soft settings
+*/
+
+
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/soundcard.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <asm/uaccess.h>
+#include <asm/setup.h>
+#include <asm/amigahw.h>
+#include <asm/amigaints.h>
+#include <asm/machdep.h>
+
+#include "dmasound.h"
+
+#define DMASOUND_PAULA_REVISION 0
+#define DMASOUND_PAULA_EDITION 4
+
+#define custom amiga_custom
+   /*
+    *	The minimum period for audio depends on htotal (for OCS/ECS/AGA)
+    *	(Imported from arch/m68k/amiga/amisound.c)
+    */
+
+extern volatile u_short amiga_audio_min_period;
+
+
+   /*
+    *	amiga_mksound() should be able to restore the period after beeping
+    *	(Imported from arch/m68k/amiga/amisound.c)
+    */
+
+extern u_short amiga_audio_period;
+
+
+   /*
+    *	Audio DMA masks
+    */
+
+#define AMI_AUDIO_OFF	(DMAF_AUD0 | DMAF_AUD1 | DMAF_AUD2 | DMAF_AUD3)
+#define AMI_AUDIO_8	(DMAF_SETCLR | DMAF_MASTER | DMAF_AUD0 | DMAF_AUD1)
+#define AMI_AUDIO_14	(AMI_AUDIO_8 | DMAF_AUD2 | DMAF_AUD3)
+
+
+    /*
+     *  Helper pointers for 16(14)-bit sound
+     */
+
+static int write_sq_block_size_half, write_sq_block_size_quarter;
+
+
+/*** Low level stuff *********************************************************/
+
+
+static void *AmiAlloc(unsigned int size, gfp_t flags);
+static void AmiFree(void *obj, unsigned int size);
+static int AmiIrqInit(void);
+#ifdef MODULE
+static void AmiIrqCleanUp(void);
+#endif
+static void AmiSilence(void);
+static void AmiInit(void);
+static int AmiSetFormat(int format);
+static int AmiSetVolume(int volume);
+static int AmiSetTreble(int treble);
+static void AmiPlayNextFrame(int index);
+static void AmiPlay(void);
+static irqreturn_t AmiInterrupt(int irq, void *dummy);
+
+#ifdef CONFIG_HEARTBEAT
+
+    /*
+     *  Heartbeat interferes with sound since the 7 kHz low-pass filter and the
+     *  power LED are controlled by the same line.
+     */
+
+static void (*saved_heartbeat)(int) = NULL;
+
+static inline void disable_heartbeat(void)
+{
+	if (mach_heartbeat) {
+	    saved_heartbeat = mach_heartbeat;
+	    mach_heartbeat = NULL;
+	}
+	AmiSetTreble(dmasound.treble);
+}
+
+static inline void enable_heartbeat(void)
+{
+	if (saved_heartbeat)
+	    mach_heartbeat = saved_heartbeat;
+}
+#else /* !CONFIG_HEARTBEAT */
+#define disable_heartbeat()	do { } while (0)
+#define enable_heartbeat()	do { } while (0)
+#endif /* !CONFIG_HEARTBEAT */
+
+
+/*** Mid level stuff *********************************************************/
+
+static void AmiMixerInit(void);
+static int AmiMixerIoctl(u_int cmd, u_long arg);
+static int AmiWriteSqSetup(void);
+static int AmiStateInfo(char *buffer, size_t space);
+
+
+/*** Translations ************************************************************/
+
+/* ++TeSche: radically changed for new expanding purposes...
+ *
+ * These two routines now deal with copying/expanding/translating the samples
+ * from user space into our buffer at the right frequency. They take care about
+ * how much data there's actually to read, how much buffer space there is and
+ * to convert samples into the right frequency/encoding. They will only work on
+ * complete samples so it may happen they leave some bytes in the input stream
+ * if the user didn't write a multiple of the current sample size. They both
+ * return the number of bytes they've used from both streams so you may detect
+ * such a situation. Luckily all programs should be able to cope with that.
+ *
+ * I think I've optimized anything as far as one can do in plain C, all
+ * variables should fit in registers and the loops are really short. There's
+ * one loop for every possible situation. Writing a more generalized and thus
+ * parameterized loop would only produce slower code. Feel free to optimize
+ * this in assembler if you like. :)
+ *
+ * I think these routines belong here because they're not yet really hardware
+ * independent, especially the fact that the Falcon can play 16bit samples
+ * only in stereo is hardcoded in both of them!
+ *
+ * ++geert: split in even more functions (one per format)
+ */
+
+
+    /*
+     *  Native format
+     */
+
+static ssize_t ami_ct_s8(const u_char __user *userPtr, size_t userCount,
+			 u_char frame[], ssize_t *frameUsed, ssize_t frameLeft)
+{
+	ssize_t count, used;
+
+	if (!dmasound.soft.stereo) {
+		void *p = &frame[*frameUsed];
+		count = min_t(unsigned long, userCount, frameLeft) & ~1;
+		used = count;
+		if (copy_from_user(p, userPtr, count))
+			return -EFAULT;
+	} else {
+		u_char *left = &frame[*frameUsed>>1];
+		u_char *right = left+write_sq_block_size_half;
+		count = min_t(unsigned long, userCount, frameLeft)>>1 & ~1;
+		used = count*2;
+		while (count > 0) {
+			if (get_user(*left++, userPtr++)
+			    || get_user(*right++, userPtr++))
+				return -EFAULT;
+			count--;
+		}
+	}
+	*frameUsed += used;
+	return used;
+}
+
+
+    /*
+     *  Copy and convert 8 bit data
+     */
+
+#define GENERATE_AMI_CT8(funcname, convsample)				\
+static ssize_t funcname(const u_char __user *userPtr, size_t userCount,	\
+			u_char frame[], ssize_t *frameUsed,		\
+			ssize_t frameLeft)				\
+{									\
+	ssize_t count, used;						\
+									\
+	if (!dmasound.soft.stereo) {					\
+		u_char *p = &frame[*frameUsed];				\
+		count = min_t(size_t, userCount, frameLeft) & ~1;	\
+		used = count;						\
+		while (count > 0) {					\
+			u_char data;					\
+			if (get_user(data, userPtr++))			\
+				return -EFAULT;				\
+			*p++ = convsample(data);			\
+			count--;					\
+		}							\
+	} else {							\
+		u_char *left = &frame[*frameUsed>>1];			\
+		u_char *right = left+write_sq_block_size_half;		\
+		count = min_t(size_t, userCount, frameLeft)>>1 & ~1;	\
+		used = count*2;						\
+		while (count > 0) {					\
+			u_char data;					\
+			if (get_user(data, userPtr++))			\
+				return -EFAULT;				\
+			*left++ = convsample(data);			\
+			if (get_user(data, userPtr++))			\
+				return -EFAULT;				\
+			*right++ = convsample(data);			\
+			count--;					\
+		}							\
+	}								\
+	*frameUsed += used;						\
+	return used;							\
+}
+
+#define AMI_CT_ULAW(x)	(dmasound_ulaw2dma8[(x)])
+#define AMI_CT_ALAW(x)	(dmasound_alaw2dma8[(x)])
+#define AMI_CT_U8(x)	((x) ^ 0x80)
+
+GENERATE_AMI_CT8(ami_ct_ulaw, AMI_CT_ULAW)
+GENERATE_AMI_CT8(ami_ct_alaw, AMI_CT_ALAW)
+GENERATE_AMI_CT8(ami_ct_u8, AMI_CT_U8)
+
+
+    /*
+     *  Copy and convert 16 bit data
+     */
+
+#define GENERATE_AMI_CT_16(funcname, convsample)			\
+static ssize_t funcname(const u_char __user *userPtr, size_t userCount,	\
+			u_char frame[], ssize_t *frameUsed,		\
+			ssize_t frameLeft)				\
+{									\
+	const u_short __user *ptr = (const u_short __user *)userPtr;	\
+	ssize_t count, used;						\
+	u_short data;							\
+									\
+	if (!dmasound.soft.stereo) {					\
+		u_char *high = &frame[*frameUsed>>1];			\
+		u_char *low = high+write_sq_block_size_half;		\
+		count = min_t(size_t, userCount, frameLeft)>>1 & ~1;	\
+		used = count*2;						\
+		while (count > 0) {					\
+			if (get_user(data, ptr++))			\
+				return -EFAULT;				\
+			data = convsample(data);			\
+			*high++ = data>>8;				\
+			*low++ = (data>>2) & 0x3f;			\
+			count--;					\
+		}							\
+	} else {							\
+		u_char *lefth = &frame[*frameUsed>>2];			\
+		u_char *leftl = lefth+write_sq_block_size_quarter;	\
+		u_char *righth = lefth+write_sq_block_size_half;	\
+		u_char *rightl = righth+write_sq_block_size_quarter;	\
+		count = min_t(size_t, userCount, frameLeft)>>2 & ~1;	\
+		used = count*4;						\
+		while (count > 0) {					\
+			if (get_user(data, ptr++))			\
+				return -EFAULT;				\
+			data = convsample(data);			\
+			*lefth++ = data>>8;				\
+			*leftl++ = (data>>2) & 0x3f;			\
+			if (get_user(data, ptr++))			\
+				return -EFAULT;				\
+			data = convsample(data);			\
+			*righth++ = data>>8;				\
+			*rightl++ = (data>>2) & 0x3f;			\
+			count--;					\
+		}							\
+	}								\
+	*frameUsed += used;						\
+	return used;							\
+}
+
+#define AMI_CT_S16BE(x)	(x)
+#define AMI_CT_U16BE(x)	((x) ^ 0x8000)
+#define AMI_CT_S16LE(x)	(le2be16((x)))
+#define AMI_CT_U16LE(x)	(le2be16((x)) ^ 0x8000)
+
+GENERATE_AMI_CT_16(ami_ct_s16be, AMI_CT_S16BE)
+GENERATE_AMI_CT_16(ami_ct_u16be, AMI_CT_U16BE)
+GENERATE_AMI_CT_16(ami_ct_s16le, AMI_CT_S16LE)
+GENERATE_AMI_CT_16(ami_ct_u16le, AMI_CT_U16LE)
+
+
+static TRANS transAmiga = {
+	.ct_ulaw	= ami_ct_ulaw,
+	.ct_alaw	= ami_ct_alaw,
+	.ct_s8		= ami_ct_s8,
+	.ct_u8		= ami_ct_u8,
+	.ct_s16be	= ami_ct_s16be,
+	.ct_u16be	= ami_ct_u16be,
+	.ct_s16le	= ami_ct_s16le,
+	.ct_u16le	= ami_ct_u16le,
+};
+
+/*** Low level stuff *********************************************************/
+
+static inline void StopDMA(void)
+{
+	custom.aud[0].audvol = custom.aud[1].audvol = 0;
+	custom.aud[2].audvol = custom.aud[3].audvol = 0;
+	custom.dmacon = AMI_AUDIO_OFF;
+	enable_heartbeat();
+}
+
+static void *AmiAlloc(unsigned int size, gfp_t flags)
+{
+	return amiga_chip_alloc((long)size, "dmasound [Paula]");
+}
+
+static void AmiFree(void *obj, unsigned int size)
+{
+	amiga_chip_free (obj);
+}
+
+static int __init AmiIrqInit(void)
+{
+	/* turn off DMA for audio channels */
+	StopDMA();
+
+	/* Register interrupt handler. */
+	if (request_irq(IRQ_AMIGA_AUD0, AmiInterrupt, 0, "DMA sound",
+			AmiInterrupt))
+		return 0;
+	return 1;
+}
+
+#ifdef MODULE
+static void AmiIrqCleanUp(void)
+{
+	/* turn off DMA for audio channels */
+	StopDMA();
+	/* release the interrupt */
+	free_irq(IRQ_AMIGA_AUD0, AmiInterrupt);
+}
+#endif /* MODULE */
+
+static void AmiSilence(void)
+{
+	/* turn off DMA for audio channels */
+	StopDMA();
+}
+
+
+static void AmiInit(void)
+{
+	int period, i;
+
+	AmiSilence();
+
+	if (dmasound.soft.speed)
+		period = amiga_colorclock/dmasound.soft.speed-1;
+	else
+		period = amiga_audio_min_period;
+	dmasound.hard = dmasound.soft;
+	dmasound.trans_write = &transAmiga;
+
+	if (period < amiga_audio_min_period) {
+		/* we would need to squeeze the sound, but we won't do that */
+		period = amiga_audio_min_period;
+	} else if (period > 65535) {
+		period = 65535;
+	}
+	dmasound.hard.speed = amiga_colorclock/(period+1);
+
+	for (i = 0; i < 4; i++)
+		custom.aud[i].audper = period;
+	amiga_audio_period = period;
+}
+
+
+static int AmiSetFormat(int format)
+{
+	int size;
+
+	/* Amiga sound DMA supports 8bit and 16bit (pseudo 14 bit) modes */
+
+	switch (format) {
+	case AFMT_QUERY:
+		return dmasound.soft.format;
+	case AFMT_MU_LAW:
+	case AFMT_A_LAW:
+	case AFMT_U8:
+	case AFMT_S8:
+		size = 8;
+		break;
+	case AFMT_S16_BE:
+	case AFMT_U16_BE:
+	case AFMT_S16_LE:
+	case AFMT_U16_LE:
+		size = 16;
+		break;
+	default: /* :-) */
+		size = 8;
+		format = AFMT_S8;
+	}
+
+	dmasound.soft.format = format;
+	dmasound.soft.size = size;
+	if (dmasound.minDev == SND_DEV_DSP) {
+		dmasound.dsp.format = format;
+		dmasound.dsp.size = dmasound.soft.size;
+	}
+	AmiInit();
+
+	return format;
+}
+
+
+#define VOLUME_VOXWARE_TO_AMI(v) \
+	(((v) < 0) ? 0 : ((v) > 100) ? 64 : ((v) * 64)/100)
+#define VOLUME_AMI_TO_VOXWARE(v) ((v)*100/64)
+
+static int AmiSetVolume(int volume)
+{
+	dmasound.volume_left = VOLUME_VOXWARE_TO_AMI(volume & 0xff);
+	custom.aud[0].audvol = dmasound.volume_left;
+	dmasound.volume_right = VOLUME_VOXWARE_TO_AMI((volume & 0xff00) >> 8);
+	custom.aud[1].audvol = dmasound.volume_right;
+	if (dmasound.hard.size == 16) {
+		if (dmasound.volume_left == 64 && dmasound.volume_right == 64) {
+			custom.aud[2].audvol = 1;
+			custom.aud[3].audvol = 1;
+		} else {
+			custom.aud[2].audvol = 0;
+			custom.aud[3].audvol = 0;
+		}
+	}
+	return VOLUME_AMI_TO_VOXWARE(dmasound.volume_left) |
+	       (VOLUME_AMI_TO_VOXWARE(dmasound.volume_right) << 8);
+}
+
+static int AmiSetTreble(int treble)
+{
+	dmasound.treble = treble;
+	if (treble < 50)
+		ciaa.pra &= ~0x02;
+	else
+		ciaa.pra |= 0x02;
+	return treble;
+}
+
+
+#define AMI_PLAY_LOADED		1
+#define AMI_PLAY_PLAYING	2
+#define AMI_PLAY_MASK		3
+
+
+static void AmiPlayNextFrame(int index)
+{
+	u_char *start, *ch0, *ch1, *ch2, *ch3;
+	u_long size;
+
+	/* used by AmiPlay() if all doubts whether there really is something
+	 * to be played are already wiped out.
+	 */
+	start = write_sq.buffers[write_sq.front];
+	size = (write_sq.count == index ? write_sq.rear_size
+					: write_sq.block_size)>>1;
+
+	if (dmasound.hard.stereo) {
+		ch0 = start;
+		ch1 = start+write_sq_block_size_half;
+		size >>= 1;
+	} else {
+		ch0 = start;
+		ch1 = start;
+	}
+
+	disable_heartbeat();
+	custom.aud[0].audvol = dmasound.volume_left;
+	custom.aud[1].audvol = dmasound.volume_right;
+	if (dmasound.hard.size == 8) {
+		custom.aud[0].audlc = (u_short *)ZTWO_PADDR(ch0);
+		custom.aud[0].audlen = size;
+		custom.aud[1].audlc = (u_short *)ZTWO_PADDR(ch1);
+		custom.aud[1].audlen = size;
+		custom.dmacon = AMI_AUDIO_8;
+	} else {
+		size >>= 1;
+		custom.aud[0].audlc = (u_short *)ZTWO_PADDR(ch0);
+		custom.aud[0].audlen = size;
+		custom.aud[1].audlc = (u_short *)ZTWO_PADDR(ch1);
+		custom.aud[1].audlen = size;
+		if (dmasound.volume_left == 64 && dmasound.volume_right == 64) {
+			/* We can play pseudo 14-bit only with the maximum volume */
+			ch3 = ch0+write_sq_block_size_quarter;
+			ch2 = ch1+write_sq_block_size_quarter;
+			custom.aud[2].audvol = 1;  /* we are being affected by the beeps */
+			custom.aud[3].audvol = 1;  /* restoring volume here helps a bit */
+			custom.aud[2].audlc = (u_short *)ZTWO_PADDR(ch2);
+			custom.aud[2].audlen = size;
+			custom.aud[3].audlc = (u_short *)ZTWO_PADDR(ch3);
+			custom.aud[3].audlen = size;
+			custom.dmacon = AMI_AUDIO_14;
+		} else {
+			custom.aud[2].audvol = 0;
+			custom.aud[3].audvol = 0;
+			custom.dmacon = AMI_AUDIO_8;
+		}
+	}
+	write_sq.front = (write_sq.front+1) % write_sq.max_count;
+	write_sq.active |= AMI_PLAY_LOADED;
+}
+
+
+static void AmiPlay(void)
+{
+	int minframes = 1;
+
+	custom.intena = IF_AUD0;
+
+	if (write_sq.active & AMI_PLAY_LOADED) {
+		/* There's already a frame loaded */
+		custom.intena = IF_SETCLR | IF_AUD0;
+		return;
+	}
+
+	if (write_sq.active & AMI_PLAY_PLAYING)
+		/* Increase threshold: frame 1 is already being played */
+		minframes = 2;
+
+	if (write_sq.count < minframes) {
+		/* Nothing to do */
+		custom.intena = IF_SETCLR | IF_AUD0;
+		return;
+	}
+
+	if (write_sq.count <= minframes &&
+	    write_sq.rear_size < write_sq.block_size && !write_sq.syncing) {
+		/* hmmm, the only existing frame is not
+		 * yet filled and we're not syncing?
+		 */
+		custom.intena = IF_SETCLR | IF_AUD0;
+		return;
+	}
+
+	AmiPlayNextFrame(minframes);
+
+	custom.intena = IF_SETCLR | IF_AUD0;
+}
+
+
+static irqreturn_t AmiInterrupt(int irq, void *dummy)
+{
+	int minframes = 1;
+
+	custom.intena = IF_AUD0;
+
+	if (!write_sq.active) {
+		/* Playing was interrupted and sq_reset() has already cleared
+		 * the sq variables, so better don't do anything here.
+		 */
+		WAKE_UP(write_sq.sync_queue);
+		return IRQ_HANDLED;
+	}
+
+	if (write_sq.active & AMI_PLAY_PLAYING) {
+		/* We've just finished a frame */
+		write_sq.count--;
+		WAKE_UP(write_sq.action_queue);
+	}
+
+	if (write_sq.active & AMI_PLAY_LOADED)
+		/* Increase threshold: frame 1 is already being played */
+		minframes = 2;
+
+	/* Shift the flags */
+	write_sq.active = (write_sq.active<<1) & AMI_PLAY_MASK;
+
+	if (!write_sq.active)
+		/* No frame is playing, disable audio DMA */
+		StopDMA();
+
+	custom.intena = IF_SETCLR | IF_AUD0;
+
+	if (write_sq.count >= minframes)
+		/* Try to play the next frame */
+		AmiPlay();
+
+	if (!write_sq.active)
+		/* Nothing to play anymore.
+		   Wake up a process waiting for audio output to drain. */
+		WAKE_UP(write_sq.sync_queue);
+	return IRQ_HANDLED;
+}
+
+/*** Mid level stuff *********************************************************/
+
+
+/*
+ * /dev/mixer abstraction
+ */
+
+static void __init AmiMixerInit(void)
+{
+	dmasound.volume_left = 64;
+	dmasound.volume_right = 64;
+	custom.aud[0].audvol = dmasound.volume_left;
+	custom.aud[3].audvol = 1;	/* For pseudo 14bit */
+	custom.aud[1].audvol = dmasound.volume_right;
+	custom.aud[2].audvol = 1;	/* For pseudo 14bit */
+	dmasound.treble = 50;
+}
+
+static int AmiMixerIoctl(u_int cmd, u_long arg)
+{
+	int data;
+	switch (cmd) {
+	    case SOUND_MIXER_READ_DEVMASK:
+		    return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_TREBLE);
+	    case SOUND_MIXER_READ_RECMASK:
+		    return IOCTL_OUT(arg, 0);
+	    case SOUND_MIXER_READ_STEREODEVS:
+		    return IOCTL_OUT(arg, SOUND_MASK_VOLUME);
+	    case SOUND_MIXER_READ_VOLUME:
+		    return IOCTL_OUT(arg,
+			    VOLUME_AMI_TO_VOXWARE(dmasound.volume_left) |
+			    VOLUME_AMI_TO_VOXWARE(dmasound.volume_right) << 8);
+	    case SOUND_MIXER_WRITE_VOLUME:
+		    IOCTL_IN(arg, data);
+		    return IOCTL_OUT(arg, dmasound_set_volume(data));
+	    case SOUND_MIXER_READ_TREBLE:
+		    return IOCTL_OUT(arg, dmasound.treble);
+	    case SOUND_MIXER_WRITE_TREBLE:
+		    IOCTL_IN(arg, data);
+		    return IOCTL_OUT(arg, dmasound_set_treble(data));
+	}
+	return -EINVAL;
+}
+
+
+static int AmiWriteSqSetup(void)
+{
+	write_sq_block_size_half = write_sq.block_size>>1;
+	write_sq_block_size_quarter = write_sq_block_size_half>>1;
+	return 0;
+}
+
+
+static int AmiStateInfo(char *buffer, size_t space)
+{
+	int len = 0;
+	len += sprintf(buffer+len, "\tsound.volume_left = %d [0...64]\n",
+		       dmasound.volume_left);
+	len += sprintf(buffer+len, "\tsound.volume_right = %d [0...64]\n",
+		       dmasound.volume_right);
+	if (len >= space) {
+		printk(KERN_ERR "dmasound_paula: overflowed state buffer alloc.\n") ;
+		len = space ;
+	}
+	return len;
+}
+
+
+/*** Machine definitions *****************************************************/
+
+static SETTINGS def_hard = {
+	.format	= AFMT_S8,
+	.stereo	= 0,
+	.size	= 8,
+	.speed	= 8000
+} ;
+
+static SETTINGS def_soft = {
+	.format	= AFMT_U8,
+	.stereo	= 0,
+	.size	= 8,
+	.speed	= 8000
+} ;
+
+static MACHINE machAmiga = {
+	.name		= "Amiga",
+	.name2		= "AMIGA",
+	.owner		= THIS_MODULE,
+	.dma_alloc	= AmiAlloc,
+	.dma_free	= AmiFree,
+	.irqinit	= AmiIrqInit,
+#ifdef MODULE
+	.irqcleanup	= AmiIrqCleanUp,
+#endif /* MODULE */
+	.init		= AmiInit,
+	.silence	= AmiSilence,
+	.setFormat	= AmiSetFormat,
+	.setVolume	= AmiSetVolume,
+	.setTreble	= AmiSetTreble,
+	.play		= AmiPlay,
+	.mixer_init	= AmiMixerInit,
+	.mixer_ioctl	= AmiMixerIoctl,
+	.write_sq_setup	= AmiWriteSqSetup,
+	.state_info	= AmiStateInfo,
+	.min_dsp_speed	= 8000,
+	.version	= ((DMASOUND_PAULA_REVISION<<8) | DMASOUND_PAULA_EDITION),
+	.hardware_afmts	= (AFMT_S8 | AFMT_S16_BE), /* h'ware-supported formats *only* here */
+	.capabilities	= DSP_CAP_BATCH          /* As per SNDCTL_DSP_GETCAPS */
+};
+
+
+/*** Config & Setup **********************************************************/
+
+
+static int __init amiga_audio_probe(struct platform_device *pdev)
+{
+	dmasound.mach = machAmiga;
+	dmasound.mach.default_hard = def_hard ;
+	dmasound.mach.default_soft = def_soft ;
+	return dmasound_init();
+}
+
+static int __exit amiga_audio_remove(struct platform_device *pdev)
+{
+	dmasound_deinit();
+	return 0;
+}
+
+static struct platform_driver amiga_audio_driver = {
+	.remove = __exit_p(amiga_audio_remove),
+	.driver   = {
+		.name	= "amiga-audio",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init amiga_audio_init(void)
+{
+	return platform_driver_probe(&amiga_audio_driver, amiga_audio_probe);
+}
+
+module_init(amiga_audio_init);
+
+static void __exit amiga_audio_exit(void)
+{
+	platform_driver_unregister(&amiga_audio_driver);
+}
+
+module_exit(amiga_audio_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:amiga-audio");
diff --git a/linux/sound/oss/dmasound/dmasound_q40.c b/linux/sound/oss/dmasound/dmasound_q40.c
new file mode 100644
index 0000000..99bcb21
--- /dev/null
+++ b/linux/sound/oss/dmasound/dmasound_q40.c
@@ -0,0 +1,638 @@
+/*
+ *  linux/sound/oss/dmasound/dmasound_q40.c
+ *
+ *  Q40 DMA Sound Driver
+ *
+ *  See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits
+ *  prior to 28/01/2001
+ *
+ *  28/01/2001 [0.1] Iain Sandoe
+ *		     - added versioning
+ *		     - put in and populated the hardware_afmts field.
+ *             [0.2] - put in SNDCTL_DSP_GETCAPS value.
+ *	       [0.3] - put in default hard/soft settings.
+ */
+
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/soundcard.h>
+#include <linux/interrupt.h>
+
+#include <asm/uaccess.h>
+#include <asm/q40ints.h>
+#include <asm/q40_master.h>
+
+#include "dmasound.h"
+
+#define DMASOUND_Q40_REVISION 0
+#define DMASOUND_Q40_EDITION 3
+
+static int expand_bal;	/* Balance factor for expanding (not volume!) */
+static int expand_data;	/* Data for expanding */
+
+
+/*** Low level stuff *********************************************************/
+
+
+static void *Q40Alloc(unsigned int size, gfp_t flags);
+static void Q40Free(void *, unsigned int);
+static int Q40IrqInit(void);
+#ifdef MODULE
+static void Q40IrqCleanUp(void);
+#endif
+static void Q40Silence(void);
+static void Q40Init(void);
+static int Q40SetFormat(int format);
+static int Q40SetVolume(int volume);
+static void Q40PlayNextFrame(int index);
+static void Q40Play(void);
+static irqreturn_t Q40StereoInterrupt(int irq, void *dummy);
+static irqreturn_t Q40MonoInterrupt(int irq, void *dummy);
+static void Q40Interrupt(void);
+
+
+/*** Mid level stuff *********************************************************/
+
+
+
+/* userCount, frameUsed, frameLeft == byte counts */
+static ssize_t q40_ct_law(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8;
+	ssize_t count, used;
+	u_char *p = (u_char *) &frame[*frameUsed];
+
+	used = count = min_t(size_t, userCount, frameLeft);
+	if (copy_from_user(p,userPtr,count))
+	  return -EFAULT;
+	while (count > 0) {
+		*p = table[*p]+128;
+		p++;
+		count--;
+	}
+	*frameUsed += used ;
+	return used;
+}
+
+
+static ssize_t q40_ct_s8(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft)
+{
+	ssize_t count, used;
+	u_char *p = (u_char *) &frame[*frameUsed];
+
+	used = count = min_t(size_t, userCount, frameLeft);
+	if (copy_from_user(p,userPtr,count))
+	  return -EFAULT;
+	while (count > 0) {
+		*p = *p + 128;
+		p++;
+		count--;
+	}
+	*frameUsed += used;
+	return used;
+}
+
+static ssize_t q40_ct_u8(const u_char __user *userPtr, size_t userCount,
+			  u_char frame[], ssize_t *frameUsed,
+			  ssize_t frameLeft)
+{
+	ssize_t count, used;
+	u_char *p = (u_char *) &frame[*frameUsed];
+
+	used = count = min_t(size_t, userCount, frameLeft);
+	if (copy_from_user(p,userPtr,count))
+	  return -EFAULT;
+	*frameUsed += used;
+	return used;
+}
+
+
+/* a bit too complicated to optimise right now ..*/
+static ssize_t q40_ctx_law(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft)
+{
+	unsigned char *table = (unsigned char *)
+		(dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8);
+	unsigned int data = expand_data;
+	u_char *p = (u_char *) &frame[*frameUsed];
+	int bal = expand_bal;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int utotal, ftotal;
+
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		u_char c;
+		if (bal < 0) {
+			if (userCount == 0)
+				break;
+			if (get_user(c, userPtr++))
+				return -EFAULT;
+			data = table[c];
+			data += 0x80;
+			userCount--;
+			bal += hSpeed;
+		}
+		*p++ = data;
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_bal = bal;
+	expand_data = data;
+	*frameUsed += (ftotal - frameLeft);
+	utotal -= userCount;
+	return utotal;
+}
+
+
+static ssize_t q40_ctx_s8(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	u_char *p = (u_char *) &frame[*frameUsed];
+	unsigned int data = expand_data;
+	int bal = expand_bal;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int utotal, ftotal;
+
+
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		u_char c;
+		if (bal < 0) {
+			if (userCount == 0)
+				break;
+			if (get_user(c, userPtr++))
+				return -EFAULT;
+			data = c ;
+			data += 0x80;
+			userCount--;
+			bal += hSpeed;
+		}
+		*p++ = data;
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_bal = bal;
+	expand_data = data;
+	*frameUsed += (ftotal - frameLeft);
+	utotal -= userCount;
+	return utotal;
+}
+
+
+static ssize_t q40_ctx_u8(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	u_char *p = (u_char *) &frame[*frameUsed];
+	unsigned int data = expand_data;
+	int bal = expand_bal;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int utotal, ftotal;
+
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		u_char c;
+		if (bal < 0) {
+			if (userCount == 0)
+				break;
+			if (get_user(c, userPtr++))
+				return -EFAULT;
+			data = c ;
+			userCount--;
+			bal += hSpeed;
+		}
+		*p++ = data;
+		frameLeft--;
+		bal -= sSpeed;
+	}
+	expand_bal = bal;
+	expand_data = data;
+	*frameUsed += (ftotal - frameLeft) ;
+	utotal -= userCount;
+	return utotal;
+}
+
+/* compressing versions */
+static ssize_t q40_ctc_law(const u_char __user *userPtr, size_t userCount,
+			    u_char frame[], ssize_t *frameUsed,
+			    ssize_t frameLeft)
+{
+	unsigned char *table = (unsigned char *)
+		(dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8);
+	unsigned int data = expand_data;
+	u_char *p = (u_char *) &frame[*frameUsed];
+	int bal = expand_bal;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int utotal, ftotal;
+ 
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		u_char c;
+		while(bal<0) {
+			if (userCount == 0)
+				goto lout;
+			if (!(bal<(-hSpeed))) {
+				if (get_user(c, userPtr))
+					return -EFAULT;
+				data = 0x80 + table[c];
+			}
+			userPtr++;
+			userCount--;
+			bal += hSpeed;
+		}
+		*p++ = data;
+		frameLeft--;
+		bal -= sSpeed;
+	}
+ lout:
+	expand_bal = bal;
+	expand_data = data;
+	*frameUsed += (ftotal - frameLeft);
+	utotal -= userCount;
+	return utotal;
+}
+
+
+static ssize_t q40_ctc_s8(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	u_char *p = (u_char *) &frame[*frameUsed];
+	unsigned int data = expand_data;
+	int bal = expand_bal;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int utotal, ftotal;
+
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		u_char c;
+		while (bal < 0) {
+			if (userCount == 0)
+				goto lout;
+			if (!(bal<(-hSpeed))) {
+				if (get_user(c, userPtr))
+					return -EFAULT;
+				data = c + 0x80;
+			}
+			userPtr++;
+			userCount--;
+			bal += hSpeed;
+		}
+		*p++ = data;
+		frameLeft--;
+		bal -= sSpeed;
+	}
+ lout:
+	expand_bal = bal;
+	expand_data = data;
+	*frameUsed += (ftotal - frameLeft);
+	utotal -= userCount;
+	return utotal;
+}
+
+
+static ssize_t q40_ctc_u8(const u_char __user *userPtr, size_t userCount,
+			   u_char frame[], ssize_t *frameUsed,
+			   ssize_t frameLeft)
+{
+	u_char *p = (u_char *) &frame[*frameUsed];
+	unsigned int data = expand_data;
+	int bal = expand_bal;
+	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+	int utotal, ftotal;
+
+	ftotal = frameLeft;
+	utotal = userCount;
+	while (frameLeft) {
+		u_char c;
+		while (bal < 0) {
+			if (userCount == 0)
+				goto lout;
+			if (!(bal<(-hSpeed))) {
+				if (get_user(c, userPtr))
+					return -EFAULT;
+				data = c ;
+			}
+			userPtr++;
+			userCount--;
+			bal += hSpeed;
+		}
+		*p++ = data;
+		frameLeft--;
+		bal -= sSpeed;
+	}
+ lout:
+	expand_bal = bal;
+	expand_data = data;
+	*frameUsed += (ftotal - frameLeft) ;
+	utotal -= userCount;
+	return utotal;
+}
+
+
+static TRANS transQ40Normal = {
+	q40_ct_law, q40_ct_law, q40_ct_s8, q40_ct_u8, NULL, NULL, NULL, NULL
+};
+
+static TRANS transQ40Expanding = {
+	q40_ctx_law, q40_ctx_law, q40_ctx_s8, q40_ctx_u8, NULL, NULL, NULL, NULL
+};
+
+static TRANS transQ40Compressing = {
+	q40_ctc_law, q40_ctc_law, q40_ctc_s8, q40_ctc_u8, NULL, NULL, NULL, NULL
+};
+
+
+/*** Low level stuff *********************************************************/
+
+static void *Q40Alloc(unsigned int size, gfp_t flags)
+{
+         return kmalloc(size, flags); /* change to vmalloc */
+}
+
+static void Q40Free(void *ptr, unsigned int size)
+{
+	kfree(ptr);
+}
+
+static int __init Q40IrqInit(void)
+{
+	/* Register interrupt handler. */
+	if (request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,
+		    "DMA sound", Q40Interrupt))
+		return 0;
+
+	return(1);
+}
+
+
+#ifdef MODULE
+static void Q40IrqCleanUp(void)
+{
+        master_outb(0,SAMPLE_ENABLE_REG);
+	free_irq(Q40_IRQ_SAMPLE, Q40Interrupt);
+}
+#endif /* MODULE */
+
+
+static void Q40Silence(void)
+{
+        master_outb(0,SAMPLE_ENABLE_REG);
+	*DAC_LEFT=*DAC_RIGHT=127;
+}
+
+static char *q40_pp;
+static unsigned int q40_sc;
+
+static void Q40PlayNextFrame(int index)
+{
+	u_char *start;
+	u_long size;
+	u_char speed;
+	int error;
+
+	/* used by Q40Play() if all doubts whether there really is something
+	 * to be played are already wiped out.
+	 */
+	start = write_sq.buffers[write_sq.front];
+	size = (write_sq.count == index ? write_sq.rear_size : write_sq.block_size);
+
+	q40_pp=start;
+	q40_sc=size;
+
+	write_sq.front = (write_sq.front+1) % write_sq.max_count;
+	write_sq.active++;
+
+	speed=(dmasound.hard.speed==10000 ? 0 : 1);
+
+	master_outb( 0,SAMPLE_ENABLE_REG);
+	free_irq(Q40_IRQ_SAMPLE, Q40Interrupt);
+	if (dmasound.soft.stereo)
+		error = request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,
+				    "Q40 sound", Q40Interrupt);
+	  else
+		error = request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0,
+				    "Q40 sound", Q40Interrupt);
+	if (error && printk_ratelimit())
+		pr_err("Couldn't register sound interrupt\n");
+
+	master_outb( speed, SAMPLE_RATE_REG);
+	master_outb( 1,SAMPLE_CLEAR_REG);
+	master_outb( 1,SAMPLE_ENABLE_REG);
+}
+
+static void Q40Play(void)
+{
+        unsigned long flags;
+
+	if (write_sq.active || write_sq.count<=0 ) {
+		/* There's already a frame loaded */
+		return;
+	}
+
+	/* nothing in the queue */
+	if (write_sq.count <= 1 && write_sq.rear_size < write_sq.block_size && !write_sq.syncing) {
+	         /* hmmm, the only existing frame is not
+		  * yet filled and we're not syncing?
+		  */
+	         return;
+	}
+	spin_lock_irqsave(&dmasound.lock, flags);
+	Q40PlayNextFrame(1);
+	spin_unlock_irqrestore(&dmasound.lock, flags);
+}
+
+static irqreturn_t Q40StereoInterrupt(int irq, void *dummy)
+{
+	spin_lock(&dmasound.lock);
+        if (q40_sc>1){
+            *DAC_LEFT=*q40_pp++;
+	    *DAC_RIGHT=*q40_pp++;
+	    q40_sc -=2;
+	    master_outb(1,SAMPLE_CLEAR_REG);
+	}else Q40Interrupt();
+	spin_unlock(&dmasound.lock);
+	return IRQ_HANDLED;
+}
+static irqreturn_t Q40MonoInterrupt(int irq, void *dummy)
+{
+	spin_lock(&dmasound.lock);
+        if (q40_sc>0){
+            *DAC_LEFT=*q40_pp;
+	    *DAC_RIGHT=*q40_pp++;
+	    q40_sc --;
+	    master_outb(1,SAMPLE_CLEAR_REG);
+	}else Q40Interrupt();
+	spin_unlock(&dmasound.lock);
+	return IRQ_HANDLED;
+}
+static void Q40Interrupt(void)
+{
+	if (!write_sq.active) {
+	          /* playing was interrupted and sq_reset() has already cleared
+		   * the sq variables, so better don't do anything here.
+		   */
+	           WAKE_UP(write_sq.sync_queue);
+		   master_outb(0,SAMPLE_ENABLE_REG); /* better safe */
+		   goto exit;
+	} else write_sq.active=0;
+	write_sq.count--;
+	Q40Play();
+
+	if (q40_sc<2)
+	      { /* there was nothing to play, disable irq */
+		master_outb(0,SAMPLE_ENABLE_REG);
+		*DAC_LEFT=*DAC_RIGHT=127;
+	      }
+	WAKE_UP(write_sq.action_queue);
+
+ exit:
+	master_outb(1,SAMPLE_CLEAR_REG);
+}
+
+
+static void Q40Init(void)
+{
+	int i, idx;
+	const int freq[] = {10000, 20000};
+
+	/* search a frequency that fits into the allowed error range */
+
+	idx = -1;
+	for (i = 0; i < 2; i++)
+		if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) <= catchRadius)
+			idx = i;
+
+	dmasound.hard = dmasound.soft;
+	/*sound.hard.stereo=1;*/ /* no longer true */
+	dmasound.hard.size=8;
+
+	if (idx > -1) {
+		dmasound.soft.speed = freq[idx];
+		dmasound.trans_write = &transQ40Normal;
+	} else
+		dmasound.trans_write = &transQ40Expanding;
+
+	Q40Silence();
+
+	if (dmasound.hard.speed > 20200) {
+		/* squeeze the sound, we do that */
+		dmasound.hard.speed = 20000;
+		dmasound.trans_write = &transQ40Compressing;
+	} else if (dmasound.hard.speed > 10000) {
+		dmasound.hard.speed = 20000;
+	} else {
+		dmasound.hard.speed = 10000;
+	}
+	expand_bal = -dmasound.soft.speed;
+}
+
+
+static int Q40SetFormat(int format)
+{
+	/* Q40 sound supports only 8bit modes */
+
+	switch (format) {
+	case AFMT_QUERY:
+		return(dmasound.soft.format);
+	case AFMT_MU_LAW:
+	case AFMT_A_LAW:
+	case AFMT_S8:
+	case AFMT_U8:
+		break;
+	default:
+		format = AFMT_S8;
+	}
+
+	dmasound.soft.format = format;
+	dmasound.soft.size = 8;
+	if (dmasound.minDev == SND_DEV_DSP) {
+		dmasound.dsp.format = format;
+		dmasound.dsp.size = 8;
+	}
+	Q40Init();
+
+	return(format);
+}
+
+static int Q40SetVolume(int volume)
+{
+    return 0;
+}
+
+
+/*** Machine definitions *****************************************************/
+
+static SETTINGS def_hard = {
+	.format	= AFMT_U8,
+	.stereo	= 0,
+	.size	= 8,
+	.speed	= 10000
+} ;
+
+static SETTINGS def_soft = {
+	.format	= AFMT_U8,
+	.stereo	= 0,
+	.size	= 8,
+	.speed	= 8000
+} ;
+
+static MACHINE machQ40 = {
+	.name		= "Q40",
+	.name2		= "Q40",
+	.owner		= THIS_MODULE,
+	.dma_alloc	= Q40Alloc,
+	.dma_free	= Q40Free,
+	.irqinit	= Q40IrqInit,
+#ifdef MODULE
+	.irqcleanup	= Q40IrqCleanUp,
+#endif /* MODULE */
+	.init		= Q40Init,
+	.silence	= Q40Silence,
+	.setFormat	= Q40SetFormat,
+	.setVolume	= Q40SetVolume,
+	.play		= Q40Play,
+ 	.min_dsp_speed	= 10000,
+	.version	= ((DMASOUND_Q40_REVISION<<8) | DMASOUND_Q40_EDITION),
+	.hardware_afmts	= AFMT_U8, /* h'ware-supported formats *only* here */
+	.capabilities	= DSP_CAP_BATCH  /* As per SNDCTL_DSP_GETCAPS */
+};
+
+
+/*** Config & Setup **********************************************************/
+
+
+static int __init dmasound_q40_init(void)
+{
+	if (MACH_IS_Q40) {
+	    dmasound.mach = machQ40;
+	    dmasound.mach.default_hard = def_hard ;
+	    dmasound.mach.default_soft = def_soft ;
+	    return dmasound_init();
+	} else
+	    return -ENODEV;
+}
+
+static void __exit dmasound_q40_cleanup(void)
+{
+	dmasound_deinit();
+}
+
+module_init(dmasound_q40_init);
+module_exit(dmasound_q40_cleanup);
+
+MODULE_DESCRIPTION("Q40/Q60 sound driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/hex2hex.c b/linux/sound/oss/hex2hex.c
new file mode 100644
index 0000000..041ef5c
--- /dev/null
+++ b/linux/sound/oss/hex2hex.c
@@ -0,0 +1,101 @@
+/*
+ * hex2hex reads stdin in Intel HEX format and produces an
+ * (unsigned char) array which contains the bytes and writes it
+ * to stdout using C syntax
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define ABANDON(why) { fprintf(stderr, "%s\n", why); exit(1); }
+#define MAX_SIZE (256*1024)
+unsigned char buf[MAX_SIZE];
+
+static int loadhex(FILE *inf, unsigned char *buf)
+{
+	int l=0, c, i;
+
+	while ((c=getc(inf))!=EOF)
+	{
+		if (c == ':')	/* Sync with beginning of line */
+		{
+			int n, check;
+			unsigned char sum;
+			int addr;
+			int linetype;
+
+			if (fscanf(inf, "%02x", &n) != 1)
+			   ABANDON("File format error");
+			sum = n;
+
+			if (fscanf(inf, "%04x", &addr) != 1)
+			   ABANDON("File format error");
+			sum += addr/256;
+			sum += addr%256;
+
+			if (fscanf(inf, "%02x", &linetype) != 1)
+			   ABANDON("File format error");
+			sum += linetype;
+
+			if (linetype != 0)
+			   continue;
+
+			for (i=0;i<n;i++)
+			{
+				if (fscanf(inf, "%02x", &c) != 1)
+			   	   ABANDON("File format error");
+				if (addr >= MAX_SIZE)
+				   ABANDON("File too large");
+				buf[addr++] = c;
+				if (addr > l)
+				   l = addr;
+				sum += c;
+			}
+
+			if (fscanf(inf, "%02x", &check) != 1)
+			   ABANDON("File format error");
+
+			sum = ~sum + 1;
+			if (check != sum)
+			   ABANDON("Line checksum error");
+		}
+	}
+
+	return l;
+}
+
+int main( int argc, const char * argv [] )
+{
+	const char * varline;
+	int i,l;
+	int id=0;
+
+	if(argv[1] && strcmp(argv[1], "-i")==0)
+	{
+		argv++;
+		argc--;
+		id=1;
+	}
+	if(argv[1]==NULL)
+	{
+		fprintf(stderr,"hex2hex: [-i] filename\n");
+		exit(1);
+	}
+	varline = argv[1];
+	l = loadhex(stdin, buf);
+
+	printf("/*\n *\t Computer generated file. Do not edit.\n */\n");
+        printf("static int %s_len = %d;\n", varline, l);
+	printf("static unsigned char %s[] %s = {\n", varline, id?"__initdata":"");
+
+	for (i=0;i<l;i++)
+	{
+		if (i) printf(",");
+		if (i && !(i % 16)) printf("\n");
+		printf("0x%02x", buf[i]);
+	}
+
+	printf("\n};\n\n");
+	return 0;
+}
diff --git a/linux/sound/oss/kahlua.c b/linux/sound/oss/kahlua.c
new file mode 100644
index 0000000..52d06a3
--- /dev/null
+++ b/linux/sound/oss/kahlua.c
@@ -0,0 +1,231 @@
+/*
+ *	Initialisation code for Cyrix/NatSemi VSA1 softaudio
+ *
+ *	(C) Copyright 2003 Red Hat Inc <alan@lxorguk.ukuu.org.uk>
+ *
+ * XpressAudio(tm) is used on the Cyrix MediaGX (now NatSemi Geode) systems.
+ * The older version (VSA1) provides fairly good soundblaster emulation
+ * although there are a couple of bugs: large DMA buffers break record,
+ * and the MPU event handling seems suspect. VSA2 allows the native driver
+ * to control the AC97 audio engine directly and requires a different driver.
+ *
+ * Thanks to National Semiconductor for providing the needed information
+ * on the XpressAudio(tm) internals.
+ *
+ * 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, 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.
+ *
+ * TO DO:
+ *	Investigate whether we can portably support Cognac (5520) in the
+ *	same manner.
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include "sound_config.h"
+
+#include "sb.h"
+
+/*
+ *	Read a soundblaster compatible mixer register.
+ *	In this case we are actually reading an SMI trap
+ *	not real hardware.
+ */
+
+static u8 __devinit mixer_read(unsigned long io, u8 reg)
+{
+	outb(reg, io + 4);
+	udelay(20);
+	reg = inb(io + 5);
+	udelay(20);
+	return reg;
+}
+
+static int __devinit probe_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct address_info *hw_config;
+	unsigned long base;
+	void __iomem *mem;
+	unsigned long io;
+	u16 map;
+	u8 irq, dma8, dma16;
+	int oldquiet;
+	extern int sb_be_quiet;
+		
+	base = pci_resource_start(pdev, 0);
+	if(base == 0UL)
+		return 1;
+	
+	mem = ioremap(base, 128);
+	if (!mem)
+		return 1;
+	map = readw(mem + 0x18);	/* Read the SMI enables */
+	iounmap(mem);
+	
+	/* Map bits
+		0:1	* 0x20 + 0x200 = sb base
+		2	sb enable
+		3	adlib enable
+		5	MPU enable 0x330
+		6	MPU enable 0x300
+		
+	   The other bits may be used internally so must be masked */
+
+	io = 0x220 + 0x20 * (map & 3);	   
+	
+	if(map & (1<<2))
+		printk(KERN_INFO "kahlua: XpressAudio at 0x%lx\n", io);
+	else
+		return 1;
+		
+	if(map & (1<<5))
+		printk(KERN_INFO "kahlua: MPU at 0x300\n");
+	else if(map & (1<<6))
+		printk(KERN_INFO "kahlua: MPU at 0x330\n");
+	
+	irq = mixer_read(io, 0x80) & 0x0F;
+	dma8 = mixer_read(io, 0x81);
+
+	// printk("IRQ=%x MAP=%x DMA=%x\n", irq, map, dma8);
+	
+	if(dma8 & 0x20)
+		dma16 = 5;
+	else if(dma8 & 0x40)
+		dma16 = 6;
+	else if(dma8 & 0x80)
+		dma16 = 7;
+	else
+	{
+		printk(KERN_ERR "kahlua: No 16bit DMA enabled.\n");
+		return 1;
+	}
+		
+	if(dma8 & 0x01)
+		dma8 = 0;
+	else if(dma8 & 0x02)
+		dma8 = 1;
+	else if(dma8 & 0x08)
+		dma8 = 3;
+	else
+	{
+		printk(KERN_ERR "kahlua: No 8bit DMA enabled.\n");
+		return 1;
+	}
+	
+	if(irq & 1)
+		irq = 9;
+	else if(irq & 2)
+		irq = 5;
+	else if(irq & 4)
+		irq = 7;
+	else if(irq & 8)
+		irq = 10;
+	else
+	{
+		printk(KERN_ERR "kahlua: SB IRQ not set.\n");
+		return 1;
+	}
+	
+	printk(KERN_INFO "kahlua: XpressAudio on IRQ %d, DMA %d, %d\n",
+		irq, dma8, dma16);
+	
+	hw_config = kzalloc(sizeof(struct address_info), GFP_KERNEL);
+	if(hw_config == NULL)
+	{
+		printk(KERN_ERR "kahlua: out of memory.\n");
+		return 1;
+	}
+	
+	pci_set_drvdata(pdev, hw_config);
+	
+	hw_config->io_base = io;
+	hw_config->irq = irq;
+	hw_config->dma = dma8;
+	hw_config->dma2 = dma16;
+	hw_config->name = "Cyrix XpressAudio";
+	hw_config->driver_use_1 = SB_NO_MIDI | SB_PCI_IRQ;
+
+	if (!request_region(io, 16, "soundblaster"))
+		goto err_out_free;
+	
+	if(sb_dsp_detect(hw_config, 0, 0, NULL)==0)
+	{
+		printk(KERN_ERR "kahlua: audio not responding.\n");
+		release_region(io, 16);
+		goto err_out_free;
+	}
+
+	oldquiet = sb_be_quiet;	
+	sb_be_quiet = 1;
+	if(sb_dsp_init(hw_config, THIS_MODULE))
+	{
+		sb_be_quiet = oldquiet;
+		goto err_out_free;
+	}
+	sb_be_quiet = oldquiet;
+	
+	return 0;
+
+err_out_free:
+	pci_set_drvdata(pdev, NULL);
+	kfree(hw_config);
+	return 1;
+}
+
+static void __devexit remove_one(struct pci_dev *pdev)
+{
+	struct address_info *hw_config = pci_get_drvdata(pdev);
+	sb_dsp_unload(hw_config, 0);
+	pci_set_drvdata(pdev, NULL);
+	kfree(hw_config);
+}
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("Kahlua VSA1 PCI Audio");
+MODULE_LICENSE("GPL");
+
+/*
+ *	5530 only. The 5510/5520 decode is different.
+ */
+
+static DEFINE_PCI_DEVICE_TABLE(id_tbl) = {
+	{ PCI_VDEVICE(CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO), 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, id_tbl);
+
+static struct pci_driver kahlua_driver = {
+	.name		= "kahlua",
+	.id_table	= id_tbl,
+	.probe		= probe_one,
+	.remove		= __devexit_p(remove_one),
+};
+
+
+static int __init kahlua_init_module(void)
+{
+	printk(KERN_INFO "Cyrix Kahlua VSA1 XpressAudio support (c) Copyright 2003 Red Hat Inc\n");
+	return pci_register_driver(&kahlua_driver);
+}
+
+static void __devexit kahlua_cleanup_module(void)
+{
+	pci_unregister_driver(&kahlua_driver);
+}
+
+
+module_init(kahlua_init_module);
+module_exit(kahlua_cleanup_module);
+
diff --git a/linux/sound/oss/midi_ctrl.h b/linux/sound/oss/midi_ctrl.h
new file mode 100644
index 0000000..3353e5a
--- /dev/null
+++ b/linux/sound/oss/midi_ctrl.h
@@ -0,0 +1,22 @@
+static unsigned char ctrl_def_values[128] =
+{
+	0x40,0x00,0x40,0x40,  0x40,0x40,0x40,0x7f,	/*   0 to   7 */
+	0x40,0x40,0x40,0x7f,  0x40,0x40,0x40,0x40,	/*   8 to  15 */
+	0x40,0x40,0x40,0x40,  0x40,0x40,0x40,0x40,	/*  16 to  23 */
+	0x40,0x40,0x40,0x40,  0x40,0x40,0x40,0x40,	/*  24 to  31 */
+
+	0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,	/*  32 to  39 */
+	0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,	/*  40 to  47 */
+	0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,	/*  48 to  55 */
+	0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,	/*  56 to  63 */
+	
+	0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,	/*  64 to  71 */
+	0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,	/*  72 to  79 */
+	0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,	/*  80 to  87 */
+	0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,	/*  88 to  95 */
+
+	0x00,0x00,0x7f,0x7f,  0x7f,0x7f,0x00,0x00,	/*  96 to 103 */
+	0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,	/* 104 to 111 */
+	0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,	/* 112 to 119 */
+	0x00,0x00,0x00,0x00,  0x00,0x00,0x00,0x00,	/* 120 to 127 */
+};
diff --git a/linux/sound/oss/midi_synth.c b/linux/sound/oss/midi_synth.c
new file mode 100644
index 0000000..3c09374
--- /dev/null
+++ b/linux/sound/oss/midi_synth.c
@@ -0,0 +1,716 @@
+/*
+ * sound/oss/midi_synth.c
+ *
+ * High level midi sequencer manager for dumb MIDI interfaces.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer   : ioctl code reworked (vmalloc/vfree removed)
+ * Andrew Veliath  : fixed running status in MIDI input state machine
+ */
+#define USE_SEQ_MACROS
+#define USE_SIMPLE_MACROS
+
+#include "sound_config.h"
+
+#define _MIDI_SYNTH_C_
+
+#include "midi_synth.h"
+
+static int      midi2synth[MAX_MIDI_DEV];
+static int      sysex_state[MAX_MIDI_DEV] =
+{0};
+static unsigned char prev_out_status[MAX_MIDI_DEV];
+
+#define STORE(cmd) \
+{ \
+  int len; \
+  unsigned char obuf[8]; \
+  cmd; \
+  seq_input_event(obuf, len); \
+}
+
+#define _seqbuf obuf
+#define _seqbufptr 0
+#define _SEQ_ADVBUF(x) len=x
+
+void
+do_midi_msg(int synthno, unsigned char *msg, int mlen)
+{
+	switch (msg[0] & 0xf0)
+	  {
+	  case 0x90:
+		  if (msg[2] != 0)
+		    {
+			    STORE(SEQ_START_NOTE(synthno, msg[0] & 0x0f, msg[1], msg[2]));
+			    break;
+		    }
+		  msg[2] = 64;
+
+	  case 0x80:
+		  STORE(SEQ_STOP_NOTE(synthno, msg[0] & 0x0f, msg[1], msg[2]));
+		  break;
+
+	  case 0xA0:
+		  STORE(SEQ_KEY_PRESSURE(synthno, msg[0] & 0x0f, msg[1], msg[2]));
+		  break;
+
+	  case 0xB0:
+		  STORE(SEQ_CONTROL(synthno, msg[0] & 0x0f,
+				    msg[1], msg[2]));
+		  break;
+
+	  case 0xC0:
+		  STORE(SEQ_SET_PATCH(synthno, msg[0] & 0x0f, msg[1]));
+		  break;
+
+	  case 0xD0:
+		  STORE(SEQ_CHN_PRESSURE(synthno, msg[0] & 0x0f, msg[1]));
+		  break;
+
+	  case 0xE0:
+		  STORE(SEQ_BENDER(synthno, msg[0] & 0x0f,
+			      (msg[1] & 0x7f) | ((msg[2] & 0x7f) << 7)));
+		  break;
+
+	  default:
+		  /* printk( "MPU: Unknown midi channel message %02x\n",  msg[0]); */
+		  ;
+	  }
+}
+EXPORT_SYMBOL(do_midi_msg);
+
+static void
+midi_outc(int midi_dev, int data)
+{
+	int             timeout;
+
+	for (timeout = 0; timeout < 3200; timeout++)
+		if (midi_devs[midi_dev]->outputc(midi_dev, (unsigned char) (data & 0xff)))
+		  {
+			  if (data & 0x80)	/*
+						 * Status byte
+						 */
+				  prev_out_status[midi_dev] =
+				      (unsigned char) (data & 0xff);	/*
+									 * Store for running status
+									 */
+			  return;	/*
+					 * Mission complete
+					 */
+		  }
+	/*
+	 * Sorry! No space on buffers.
+	 */
+	printk("Midi send timed out\n");
+}
+
+static int
+prefix_cmd(int midi_dev, unsigned char status)
+{
+	if ((char *) midi_devs[midi_dev]->prefix_cmd == NULL)
+		return 1;
+
+	return midi_devs[midi_dev]->prefix_cmd(midi_dev, status);
+}
+
+static void
+midi_synth_input(int orig_dev, unsigned char data)
+{
+	int             dev;
+	struct midi_input_info *inc;
+
+	static unsigned char len_tab[] =	/* # of data bytes following a status
+						 */
+	{
+		2,		/* 8x */
+		2,		/* 9x */
+		2,		/* Ax */
+		2,		/* Bx */
+		1,		/* Cx */
+		1,		/* Dx */
+		2,		/* Ex */
+		0		/* Fx */
+	};
+
+	if (orig_dev < 0 || orig_dev > num_midis || midi_devs[orig_dev] == NULL)
+		return;
+
+	if (data == 0xfe)	/* Ignore active sensing */
+		return;
+
+	dev = midi2synth[orig_dev];
+	inc = &midi_devs[orig_dev]->in_info;
+
+	switch (inc->m_state)
+	  {
+	  case MST_INIT:
+		  if (data & 0x80)	/* MIDI status byte */
+		    {
+			    if ((data & 0xf0) == 0xf0)	/* Common message */
+			      {
+				      switch (data)
+					{
+					case 0xf0:	/* Sysex */
+						inc->m_state = MST_SYSEX;
+						break;	/* Sysex */
+
+					case 0xf1:	/* MTC quarter frame */
+					case 0xf3:	/* Song select */
+						inc->m_state = MST_DATA;
+						inc->m_ptr = 1;
+						inc->m_left = 1;
+						inc->m_buf[0] = data;
+						break;
+
+					case 0xf2:	/* Song position pointer */
+						inc->m_state = MST_DATA;
+						inc->m_ptr = 1;
+						inc->m_left = 2;
+						inc->m_buf[0] = data;
+						break;
+
+					default:
+						inc->m_buf[0] = data;
+						inc->m_ptr = 1;
+						do_midi_msg(dev, inc->m_buf, inc->m_ptr);
+						inc->m_ptr = 0;
+						inc->m_left = 0;
+					}
+			    } else
+			      {
+				      inc->m_state = MST_DATA;
+				      inc->m_ptr = 1;
+				      inc->m_left = len_tab[(data >> 4) - 8];
+				      inc->m_buf[0] = inc->m_prev_status = data;
+			      }
+		    } else if (inc->m_prev_status & 0x80) {
+			    /* Data byte (use running status) */
+			    inc->m_ptr = 2;
+			    inc->m_buf[1] = data;
+			    inc->m_buf[0] = inc->m_prev_status;
+			    inc->m_left = len_tab[(inc->m_buf[0] >> 4) - 8] - 1;
+			    if (inc->m_left > 0)
+				    inc->m_state = MST_DATA; /* Not done yet */
+			    else {
+				    inc->m_state = MST_INIT;
+				    do_midi_msg(dev, inc->m_buf, inc->m_ptr);
+				    inc->m_ptr = 0;
+			    }
+		    }
+		  break;	/* MST_INIT */
+
+	  case MST_DATA:
+		  inc->m_buf[inc->m_ptr++] = data;
+		  if (--inc->m_left <= 0)
+		    {
+			    inc->m_state = MST_INIT;
+			    do_midi_msg(dev, inc->m_buf, inc->m_ptr);
+			    inc->m_ptr = 0;
+		    }
+		  break;	/* MST_DATA */
+
+	  case MST_SYSEX:
+		  if (data == 0xf7)	/* Sysex end */
+		    {
+			    inc->m_state = MST_INIT;
+			    inc->m_left = 0;
+			    inc->m_ptr = 0;
+		    }
+		  break;	/* MST_SYSEX */
+
+	  default:
+		  printk("MIDI%d: Unexpected state %d (%02x)\n", orig_dev, inc->m_state, (int) data);
+		  inc->m_state = MST_INIT;
+	  }
+}
+
+static void
+leave_sysex(int dev)
+{
+	int             orig_dev = synth_devs[dev]->midi_dev;
+	int             timeout = 0;
+
+	if (!sysex_state[dev])
+		return;
+
+	sysex_state[dev] = 0;
+
+	while (!midi_devs[orig_dev]->outputc(orig_dev, 0xf7) &&
+	       timeout < 1000)
+		timeout++;
+
+	sysex_state[dev] = 0;
+}
+
+static void
+midi_synth_output(int dev)
+{
+	/*
+	 * Currently NOP
+	 */
+}
+
+int midi_synth_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+	/*
+	 * int orig_dev = synth_devs[dev]->midi_dev;
+	 */
+
+	switch (cmd) {
+
+	case SNDCTL_SYNTH_INFO:
+		if (__copy_to_user(arg, synth_devs[dev]->info, sizeof(struct synth_info)))
+			return -EFAULT;
+		return 0;
+		
+	case SNDCTL_SYNTH_MEMAVL:
+		return 0x7fffffff;
+
+	default:
+		return -EINVAL;
+	}
+}
+EXPORT_SYMBOL(midi_synth_ioctl);
+
+int
+midi_synth_kill_note(int dev, int channel, int note, int velocity)
+{
+	int             orig_dev = synth_devs[dev]->midi_dev;
+	int             msg, chn;
+
+	if (note < 0 || note > 127)
+		return 0;
+	if (channel < 0 || channel > 15)
+		return 0;
+	if (velocity < 0)
+		velocity = 0;
+	if (velocity > 127)
+		velocity = 127;
+
+	leave_sysex(dev);
+
+	msg = prev_out_status[orig_dev] & 0xf0;
+	chn = prev_out_status[orig_dev] & 0x0f;
+
+	if (chn == channel && ((msg == 0x90 && velocity == 64) || msg == 0x80))
+	  {			/*
+				 * Use running status
+				 */
+		  if (!prefix_cmd(orig_dev, note))
+			  return 0;
+
+		  midi_outc(orig_dev, note);
+
+		  if (msg == 0x90)	/*
+					 * Running status = Note on
+					 */
+			  midi_outc(orig_dev, 0);	/*
+							   * Note on with velocity 0 == note
+							   * off
+							 */
+		  else
+			  midi_outc(orig_dev, velocity);
+	} else
+	  {
+		  if (velocity == 64)
+		    {
+			    if (!prefix_cmd(orig_dev, 0x90 | (channel & 0x0f)))
+				    return 0;
+			    midi_outc(orig_dev, 0x90 | (channel & 0x0f));	/*
+										 * Note on
+										 */
+			    midi_outc(orig_dev, note);
+			    midi_outc(orig_dev, 0);	/*
+							 * Zero G
+							 */
+		  } else
+		    {
+			    if (!prefix_cmd(orig_dev, 0x80 | (channel & 0x0f)))
+				    return 0;
+			    midi_outc(orig_dev, 0x80 | (channel & 0x0f));	/*
+										 * Note off
+										 */
+			    midi_outc(orig_dev, note);
+			    midi_outc(orig_dev, velocity);
+		    }
+	  }
+
+	return 0;
+}
+EXPORT_SYMBOL(midi_synth_kill_note);
+
+int
+midi_synth_set_instr(int dev, int channel, int instr_no)
+{
+	int             orig_dev = synth_devs[dev]->midi_dev;
+
+	if (instr_no < 0 || instr_no > 127)
+		instr_no = 0;
+	if (channel < 0 || channel > 15)
+		return 0;
+
+	leave_sysex(dev);
+
+	if (!prefix_cmd(orig_dev, 0xc0 | (channel & 0x0f)))
+		return 0;
+	midi_outc(orig_dev, 0xc0 | (channel & 0x0f));	/*
+							 * Program change
+							 */
+	midi_outc(orig_dev, instr_no);
+
+	return 0;
+}
+EXPORT_SYMBOL(midi_synth_set_instr);
+
+int
+midi_synth_start_note(int dev, int channel, int note, int velocity)
+{
+	int             orig_dev = synth_devs[dev]->midi_dev;
+	int             msg, chn;
+
+	if (note < 0 || note > 127)
+		return 0;
+	if (channel < 0 || channel > 15)
+		return 0;
+	if (velocity < 0)
+		velocity = 0;
+	if (velocity > 127)
+		velocity = 127;
+
+	leave_sysex(dev);
+
+	msg = prev_out_status[orig_dev] & 0xf0;
+	chn = prev_out_status[orig_dev] & 0x0f;
+
+	if (chn == channel && msg == 0x90)
+	  {			/*
+				 * Use running status
+				 */
+		  if (!prefix_cmd(orig_dev, note))
+			  return 0;
+		  midi_outc(orig_dev, note);
+		  midi_outc(orig_dev, velocity);
+	} else
+	  {
+		  if (!prefix_cmd(orig_dev, 0x90 | (channel & 0x0f)))
+			  return 0;
+		  midi_outc(orig_dev, 0x90 | (channel & 0x0f));		/*
+									 * Note on
+									 */
+		  midi_outc(orig_dev, note);
+		  midi_outc(orig_dev, velocity);
+	  }
+	return 0;
+}
+EXPORT_SYMBOL(midi_synth_start_note);
+
+void
+midi_synth_reset(int dev)
+{
+
+	leave_sysex(dev);
+}
+EXPORT_SYMBOL(midi_synth_reset);
+
+int
+midi_synth_open(int dev, int mode)
+{
+	int             orig_dev = synth_devs[dev]->midi_dev;
+	int             err;
+	struct midi_input_info *inc;
+
+	if (orig_dev < 0 || orig_dev >= num_midis || midi_devs[orig_dev] == NULL)
+		return -ENXIO;
+
+	midi2synth[orig_dev] = dev;
+	sysex_state[dev] = 0;
+	prev_out_status[orig_dev] = 0;
+
+	if ((err = midi_devs[orig_dev]->open(orig_dev, mode,
+			       midi_synth_input, midi_synth_output)) < 0)
+		return err;
+	inc = &midi_devs[orig_dev]->in_info;
+
+	/* save_flags(flags);
+	cli(); 
+	don't know against what irqhandler to protect*/
+	inc->m_busy = 0;
+	inc->m_state = MST_INIT;
+	inc->m_ptr = 0;
+	inc->m_left = 0;
+	inc->m_prev_status = 0x00;
+	/* restore_flags(flags); */
+
+	return 1;
+}
+EXPORT_SYMBOL(midi_synth_open);
+
+void
+midi_synth_close(int dev)
+{
+	int             orig_dev = synth_devs[dev]->midi_dev;
+
+	leave_sysex(dev);
+
+	/*
+	 * Shut up the synths by sending just single active sensing message.
+	 */
+	midi_devs[orig_dev]->outputc(orig_dev, 0xfe);
+
+	midi_devs[orig_dev]->close(orig_dev);
+}
+EXPORT_SYMBOL(midi_synth_close);
+
+void
+midi_synth_hw_control(int dev, unsigned char *event)
+{
+}
+EXPORT_SYMBOL(midi_synth_hw_control);
+
+int
+midi_synth_load_patch(int dev, int format, const char __user *addr,
+		      int offs, int count, int pmgr_flag)
+{
+	int             orig_dev = synth_devs[dev]->midi_dev;
+
+	struct sysex_info sysex;
+	int             i;
+	unsigned long   left, src_offs, eox_seen = 0;
+	int             first_byte = 1;
+	int             hdr_size = (unsigned long) &sysex.data[0] - (unsigned long) &sysex;
+
+	leave_sysex(dev);
+
+	if (!prefix_cmd(orig_dev, 0xf0))
+		return 0;
+
+	if (format != SYSEX_PATCH)
+	{
+/*		  printk("MIDI Error: Invalid patch format (key) 0x%x\n", format);*/
+		  return -EINVAL;
+	}
+	if (count < hdr_size)
+	{
+/*		printk("MIDI Error: Patch header too short\n");*/
+		return -EINVAL;
+	}
+	count -= hdr_size;
+
+	/*
+	 * Copy the header from user space but ignore the first bytes which have
+	 * been transferred already.
+	 */
+
+	if(copy_from_user(&((char *) &sysex)[offs], &(addr)[offs], hdr_size - offs))
+		return -EFAULT;
+ 
+ 	if (count < sysex.len)
+	{
+/*		printk(KERN_WARNING "MIDI Warning: Sysex record too short (%d<%d)\n", count, (int) sysex.len);*/
+		sysex.len = count;
+	}
+  	left = sysex.len;
+  	src_offs = 0;
+
+	for (i = 0; i < left && !signal_pending(current); i++)
+	{
+		unsigned char   data;
+
+		if (get_user(data,
+		    (unsigned char __user *)(addr + hdr_size + i)))
+			return -EFAULT;
+
+		eox_seen = (i > 0 && data & 0x80);	/* End of sysex */
+
+		if (eox_seen && data != 0xf7)
+			data = 0xf7;
+
+		if (i == 0)
+		{
+			if (data != 0xf0)
+			{
+				printk(KERN_WARNING "midi_synth: Sysex start missing\n");
+				return -EINVAL;
+			}
+		}
+		while (!midi_devs[orig_dev]->outputc(orig_dev, (unsigned char) (data & 0xff)) &&
+			!signal_pending(current))
+			schedule();
+
+		if (!first_byte && data & 0x80)
+			return 0;
+		first_byte = 0;
+	}
+
+	if (!eox_seen)
+		midi_outc(orig_dev, 0xf7);
+	return 0;
+}
+EXPORT_SYMBOL(midi_synth_load_patch);
+
+void midi_synth_panning(int dev, int channel, int pressure)
+{
+}
+EXPORT_SYMBOL(midi_synth_panning);
+
+void midi_synth_aftertouch(int dev, int channel, int pressure)
+{
+	int             orig_dev = synth_devs[dev]->midi_dev;
+	int             msg, chn;
+
+	if (pressure < 0 || pressure > 127)
+		return;
+	if (channel < 0 || channel > 15)
+		return;
+
+	leave_sysex(dev);
+
+	msg = prev_out_status[orig_dev] & 0xf0;
+	chn = prev_out_status[orig_dev] & 0x0f;
+
+	if (msg != 0xd0 || chn != channel)	/*
+						 * Test for running status
+						 */
+	  {
+		  if (!prefix_cmd(orig_dev, 0xd0 | (channel & 0x0f)))
+			  return;
+		  midi_outc(orig_dev, 0xd0 | (channel & 0x0f));		/*
+									 * Channel pressure
+									 */
+	} else if (!prefix_cmd(orig_dev, pressure))
+		return;
+
+	midi_outc(orig_dev, pressure);
+}
+EXPORT_SYMBOL(midi_synth_aftertouch);
+
+void
+midi_synth_controller(int dev, int channel, int ctrl_num, int value)
+{
+	int             orig_dev = synth_devs[dev]->midi_dev;
+	int             chn, msg;
+
+	if (ctrl_num < 0 || ctrl_num > 127)
+		return;
+	if (channel < 0 || channel > 15)
+		return;
+
+	leave_sysex(dev);
+
+	msg = prev_out_status[orig_dev] & 0xf0;
+	chn = prev_out_status[orig_dev] & 0x0f;
+
+	if (msg != 0xb0 || chn != channel)
+	  {
+		  if (!prefix_cmd(orig_dev, 0xb0 | (channel & 0x0f)))
+			  return;
+		  midi_outc(orig_dev, 0xb0 | (channel & 0x0f));
+	} else if (!prefix_cmd(orig_dev, ctrl_num))
+		return;
+
+	midi_outc(orig_dev, ctrl_num);
+	midi_outc(orig_dev, value & 0x7f);
+}
+EXPORT_SYMBOL(midi_synth_controller);
+
+void
+midi_synth_bender(int dev, int channel, int value)
+{
+	int             orig_dev = synth_devs[dev]->midi_dev;
+	int             msg, prev_chn;
+
+	if (channel < 0 || channel > 15)
+		return;
+
+	if (value < 0 || value > 16383)
+		return;
+
+	leave_sysex(dev);
+
+	msg = prev_out_status[orig_dev] & 0xf0;
+	prev_chn = prev_out_status[orig_dev] & 0x0f;
+
+	if (msg != 0xd0 || prev_chn != channel)		/*
+							 * Test for running status
+							 */
+	  {
+		  if (!prefix_cmd(orig_dev, 0xe0 | (channel & 0x0f)))
+			  return;
+		  midi_outc(orig_dev, 0xe0 | (channel & 0x0f));
+	} else if (!prefix_cmd(orig_dev, value & 0x7f))
+		return;
+
+	midi_outc(orig_dev, value & 0x7f);
+	midi_outc(orig_dev, (value >> 7) & 0x7f);
+}
+EXPORT_SYMBOL(midi_synth_bender);
+
+void
+midi_synth_setup_voice(int dev, int voice, int channel)
+{
+}
+EXPORT_SYMBOL(midi_synth_setup_voice);
+
+int
+midi_synth_send_sysex(int dev, unsigned char *bytes, int len)
+{
+	int             orig_dev = synth_devs[dev]->midi_dev;
+	int             i;
+
+	for (i = 0; i < len; i++)
+	  {
+		  switch (bytes[i])
+		    {
+		    case 0xf0:	/* Start sysex */
+			    if (!prefix_cmd(orig_dev, 0xf0))
+				    return 0;
+			    sysex_state[dev] = 1;
+			    break;
+
+		    case 0xf7:	/* End sysex */
+			    if (!sysex_state[dev])	/* Orphan sysex end */
+				    return 0;
+			    sysex_state[dev] = 0;
+			    break;
+
+		    default:
+			    if (!sysex_state[dev])
+				    return 0;
+
+			    if (bytes[i] & 0x80)	/* Error. Another message before sysex end */
+			      {
+				      bytes[i] = 0xf7;	/* Sysex end */
+				      sysex_state[dev] = 0;
+			      }
+		    }
+
+		  if (!midi_devs[orig_dev]->outputc(orig_dev, bytes[i]))
+		    {
+/*
+ * Hardware level buffer is full. Abort the sysex message.
+ */
+
+			    int             timeout = 0;
+
+			    bytes[i] = 0xf7;
+			    sysex_state[dev] = 0;
+
+			    while (!midi_devs[orig_dev]->outputc(orig_dev, bytes[i]) &&
+				   timeout < 1000)
+				    timeout++;
+		    }
+		  if (!sysex_state[dev])
+			  return 0;
+	  }
+
+	return 0;
+}
+EXPORT_SYMBOL(midi_synth_send_sysex);
+
diff --git a/linux/sound/oss/midi_synth.h b/linux/sound/oss/midi_synth.h
new file mode 100644
index 0000000..6bc9d00
--- /dev/null
+++ b/linux/sound/oss/midi_synth.h
@@ -0,0 +1,47 @@
+int midi_synth_ioctl (int dev,
+	    unsigned int cmd, void __user * arg);
+int midi_synth_kill_note (int dev, int channel, int note, int velocity);
+int midi_synth_set_instr (int dev, int channel, int instr_no);
+int midi_synth_start_note (int dev, int channel, int note, int volume);
+void midi_synth_reset (int dev);
+int midi_synth_open (int dev, int mode);
+void midi_synth_close (int dev);
+void midi_synth_hw_control (int dev, unsigned char *event);
+int midi_synth_load_patch (int dev, int format, const char __user * addr,
+		 int offs, int count, int pmgr_flag);
+void midi_synth_panning (int dev, int channel, int pressure);
+void midi_synth_aftertouch (int dev, int channel, int pressure);
+void midi_synth_controller (int dev, int channel, int ctrl_num, int value);
+void midi_synth_bender (int dev, int chn, int value);
+void midi_synth_setup_voice (int dev, int voice, int chn);
+int midi_synth_send_sysex(int dev, unsigned char *bytes,int len);
+
+#ifndef _MIDI_SYNTH_C_
+static struct synth_info std_synth_info =
+{MIDI_SYNTH_NAME, 0, SYNTH_TYPE_MIDI, 0, 0, 128, 0, 128, MIDI_SYNTH_CAPS};
+
+static struct synth_operations std_midi_synth =
+{
+	.owner		= THIS_MODULE,
+	.id		= "MIDI",
+	.info		= &std_synth_info,
+	.midi_dev	= 0,
+	.synth_type	= SYNTH_TYPE_MIDI,
+	.synth_subtype	= 0,
+	.open		= midi_synth_open,
+	.close		= midi_synth_close,
+	.ioctl		= midi_synth_ioctl,
+	.kill_note	= midi_synth_kill_note,
+	.start_note	= midi_synth_start_note,
+	.set_instr	= midi_synth_set_instr,
+	.reset		= midi_synth_reset,
+	.hw_control	= midi_synth_hw_control,
+	.load_patch	= midi_synth_load_patch,
+	.aftertouch	= midi_synth_aftertouch,
+	.controller	= midi_synth_controller,
+	.panning		= midi_synth_panning,
+	.bender		= midi_synth_bender,
+	.setup_voice	= midi_synth_setup_voice,
+	.send_sysex	= midi_synth_send_sysex
+};
+#endif
diff --git a/linux/sound/oss/midibuf.c b/linux/sound/oss/midibuf.c
new file mode 100644
index 0000000..ceedb1e
--- /dev/null
+++ b/linux/sound/oss/midibuf.c
@@ -0,0 +1,425 @@
+/*
+ * sound/oss/midibuf.c
+ *
+ * Device file manager for /dev/midi#
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer   : ioctl code reworked (vmalloc/vfree removed)
+ */
+#include <linux/stddef.h>
+#include <linux/kmod.h>
+#include <linux/spinlock.h>
+#define MIDIBUF_C
+
+#include "sound_config.h"
+
+
+/*
+ * Don't make MAX_QUEUE_SIZE larger than 4000
+ */
+
+#define MAX_QUEUE_SIZE	4000
+
+static wait_queue_head_t midi_sleeper[MAX_MIDI_DEV];
+static wait_queue_head_t input_sleeper[MAX_MIDI_DEV];
+
+struct midi_buf
+{
+	int len, head, tail;
+	unsigned char queue[MAX_QUEUE_SIZE];
+};
+
+struct midi_parms
+{
+	long prech_timeout;	/*
+				 * Timeout before the first ch
+				 */
+};
+
+static struct midi_buf *midi_out_buf[MAX_MIDI_DEV] = {NULL};
+static struct midi_buf *midi_in_buf[MAX_MIDI_DEV] = {NULL};
+static struct midi_parms parms[MAX_MIDI_DEV];
+
+static void midi_poll(unsigned long dummy);
+
+
+static DEFINE_TIMER(poll_timer, midi_poll, 0, 0);
+
+static volatile int open_devs;
+static DEFINE_SPINLOCK(lock);
+
+#define DATA_AVAIL(q) (q->len)
+#define SPACE_AVAIL(q) (MAX_QUEUE_SIZE - q->len)
+
+#define QUEUE_BYTE(q, data) \
+	if (SPACE_AVAIL(q)) \
+	{ \
+	  unsigned long flags; \
+	  spin_lock_irqsave(&lock, flags); \
+	  q->queue[q->tail] = (data); \
+	  q->len++; q->tail = (q->tail+1) % MAX_QUEUE_SIZE; \
+	  spin_unlock_irqrestore(&lock, flags); \
+	}
+
+#define REMOVE_BYTE(q, data) \
+	if (DATA_AVAIL(q)) \
+	{ \
+	  unsigned long flags; \
+	  spin_lock_irqsave(&lock, flags); \
+	  data = q->queue[q->head]; \
+	  q->len--; q->head = (q->head+1) % MAX_QUEUE_SIZE; \
+	  spin_unlock_irqrestore(&lock, flags); \
+	}
+
+static void drain_midi_queue(int dev)
+{
+
+	/*
+	 * Give the Midi driver time to drain its output queues
+	 */
+
+	if (midi_devs[dev]->buffer_status != NULL)
+		while (!signal_pending(current) && midi_devs[dev]->buffer_status(dev)) 
+			interruptible_sleep_on_timeout(&midi_sleeper[dev],
+						       HZ/10);
+}
+
+static void midi_input_intr(int dev, unsigned char data)
+{
+	if (midi_in_buf[dev] == NULL)
+		return;
+
+	if (data == 0xfe)	/*
+				 * Active sensing
+				 */
+		return;		/*
+				 * Ignore
+				 */
+
+	if (SPACE_AVAIL(midi_in_buf[dev])) {
+		QUEUE_BYTE(midi_in_buf[dev], data);
+		wake_up(&input_sleeper[dev]);
+	}
+}
+
+static void midi_output_intr(int dev)
+{
+	/*
+	 * Currently NOP
+	 */
+}
+
+static void midi_poll(unsigned long dummy)
+{
+	unsigned long   flags;
+	int             dev;
+
+	spin_lock_irqsave(&lock, flags);
+	if (open_devs)
+	{
+		for (dev = 0; dev < num_midis; dev++)
+			if (midi_devs[dev] != NULL && midi_out_buf[dev] != NULL)
+			{
+				while (DATA_AVAIL(midi_out_buf[dev]))
+				{
+					int ok;
+					int c = midi_out_buf[dev]->queue[midi_out_buf[dev]->head];
+
+					spin_unlock_irqrestore(&lock,flags);/* Give some time to others */
+					ok = midi_devs[dev]->outputc(dev, c);
+					spin_lock_irqsave(&lock, flags);
+					if (!ok)
+						break;
+					midi_out_buf[dev]->head = (midi_out_buf[dev]->head + 1) % MAX_QUEUE_SIZE;
+					midi_out_buf[dev]->len--;
+				}
+
+				if (DATA_AVAIL(midi_out_buf[dev]) < 100)
+					wake_up(&midi_sleeper[dev]);
+			}
+		poll_timer.expires = (1) + jiffies;
+		add_timer(&poll_timer);
+		/*
+		 * Come back later
+		 */
+	}
+	spin_unlock_irqrestore(&lock, flags);
+}
+
+int MIDIbuf_open(int dev, struct file *file)
+{
+	int mode, err;
+
+	dev = dev >> 4;
+	mode = translate_mode(file);
+
+	if (num_midis > MAX_MIDI_DEV)
+	{
+		printk(KERN_ERR "midi: Too many midi interfaces\n");
+		num_midis = MAX_MIDI_DEV;
+	}
+	if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
+		  return -ENXIO;
+	/*
+	 *    Interrupts disabled. Be careful
+	 */
+
+	module_put(midi_devs[dev]->owner);
+
+	if ((err = midi_devs[dev]->open(dev, mode,
+				 midi_input_intr, midi_output_intr)) < 0)
+		return err;
+
+	parms[dev].prech_timeout = MAX_SCHEDULE_TIMEOUT;
+	midi_in_buf[dev] = vmalloc(sizeof(struct midi_buf));
+
+	if (midi_in_buf[dev] == NULL)
+	{
+		printk(KERN_WARNING "midi: Can't allocate buffer\n");
+		midi_devs[dev]->close(dev);
+		return -EIO;
+	}
+	midi_in_buf[dev]->len = midi_in_buf[dev]->head = midi_in_buf[dev]->tail = 0;
+
+	midi_out_buf[dev] = vmalloc(sizeof(struct midi_buf));
+
+	if (midi_out_buf[dev] == NULL)
+	{
+		printk(KERN_WARNING "midi: Can't allocate buffer\n");
+		midi_devs[dev]->close(dev);
+		vfree(midi_in_buf[dev]);
+		midi_in_buf[dev] = NULL;
+		return -EIO;
+	}
+	midi_out_buf[dev]->len = midi_out_buf[dev]->head = midi_out_buf[dev]->tail = 0;
+	open_devs++;
+
+	init_waitqueue_head(&midi_sleeper[dev]);
+	init_waitqueue_head(&input_sleeper[dev]);
+
+	if (open_devs < 2)	/* This was first open */
+	{
+		poll_timer.expires = 1 + jiffies;
+		add_timer(&poll_timer);	/* Start polling */
+	}
+	return err;
+}
+
+void MIDIbuf_release(int dev, struct file *file)
+{
+	int mode;
+
+	dev = dev >> 4;
+	mode = translate_mode(file);
+
+	if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
+		return;
+
+	/*
+	 * Wait until the queue is empty
+	 */
+
+	if (mode != OPEN_READ)
+	{
+		midi_devs[dev]->outputc(dev, 0xfe);	/*
+							   * Active sensing to shut the
+							   * devices
+							 */
+
+		while (!signal_pending(current) && DATA_AVAIL(midi_out_buf[dev]))
+			  interruptible_sleep_on(&midi_sleeper[dev]);
+		/*
+		 *	Sync
+		 */
+
+		drain_midi_queue(dev);	/*
+					 * Ensure the output queues are empty
+					 */
+	}
+
+	midi_devs[dev]->close(dev);
+
+	open_devs--;
+	if (open_devs == 0)
+		del_timer_sync(&poll_timer);
+	vfree(midi_in_buf[dev]);
+	vfree(midi_out_buf[dev]);
+	midi_in_buf[dev] = NULL;
+	midi_out_buf[dev] = NULL;
+
+	module_put(midi_devs[dev]->owner);
+}
+
+int MIDIbuf_write(int dev, struct file *file, const char __user *buf, int count)
+{
+	int c, n, i;
+	unsigned char tmp_data;
+
+	dev = dev >> 4;
+
+	if (!count)
+		return 0;
+
+	c = 0;
+
+	while (c < count)
+	{
+		n = SPACE_AVAIL(midi_out_buf[dev]);
+
+		if (n == 0) {	/*
+				 * No space just now.
+				 */
+
+			if (file->f_flags & O_NONBLOCK) {
+				c = -EAGAIN;
+				goto out;
+			}
+
+			interruptible_sleep_on(&midi_sleeper[dev]);
+			if (signal_pending(current)) 
+			{
+				c = -EINTR;
+				goto out;
+			}
+			n = SPACE_AVAIL(midi_out_buf[dev]);
+		}
+		if (n > (count - c))
+			n = count - c;
+
+		for (i = 0; i < n; i++)
+		{
+			/* BROKE BROKE BROKE - CANT DO THIS WITH CLI !! */
+			/* yes, think the same, so I removed the cli() brackets 
+				QUEUE_BYTE is protected against interrupts */
+			if (copy_from_user((char *) &tmp_data, &(buf)[c], 1)) {
+				c = -EFAULT;
+				goto out;
+			}
+			QUEUE_BYTE(midi_out_buf[dev], tmp_data);
+			c++;
+		}
+	}
+out:
+	return c;
+}
+
+
+int MIDIbuf_read(int dev, struct file *file, char __user *buf, int count)
+{
+	int n, c = 0;
+	unsigned char tmp_data;
+
+	dev = dev >> 4;
+
+	if (!DATA_AVAIL(midi_in_buf[dev])) {	/*
+						 * No data yet, wait
+						 */
+ 		if (file->f_flags & O_NONBLOCK) {
+ 			c = -EAGAIN;
+			goto out;
+ 		}
+		interruptible_sleep_on_timeout(&input_sleeper[dev],
+					       parms[dev].prech_timeout);
+
+		if (signal_pending(current))
+			c = -EINTR;	/* The user is getting restless */
+	}
+	if (c == 0 && DATA_AVAIL(midi_in_buf[dev]))	/*
+							 * Got some bytes
+							 */
+	{
+		n = DATA_AVAIL(midi_in_buf[dev]);
+		if (n > count)
+			n = count;
+		c = 0;
+
+		while (c < n)
+		{
+			char *fixit;
+			REMOVE_BYTE(midi_in_buf[dev], tmp_data);
+			fixit = (char *) &tmp_data;
+			/* BROKE BROKE BROKE */
+			/* yes removed the cli() brackets again
+			 should q->len,tail&head be atomic_t? */
+			if (copy_to_user(&(buf)[c], fixit, 1)) {
+				c = -EFAULT;
+				goto out;
+			}
+			c++;
+		}
+	}
+out:
+	return c;
+}
+
+int MIDIbuf_ioctl(int dev, struct file *file,
+		  unsigned int cmd, void __user *arg)
+{
+	int val;
+
+	dev = dev >> 4;
+	
+	if (((cmd >> 8) & 0xff) == 'C') 
+	{
+		if (midi_devs[dev]->coproc)	/* Coprocessor ioctl */
+			return midi_devs[dev]->coproc->ioctl(midi_devs[dev]->coproc->devc, cmd, arg, 0);
+/*		printk("/dev/midi%d: No coprocessor for this device\n", dev);*/
+		return -ENXIO;
+	}
+	else
+	{
+		switch (cmd) 
+		{
+			case SNDCTL_MIDI_PRETIME:
+				if (get_user(val, (int __user *)arg))
+					return -EFAULT;
+				if (val < 0)
+					val = 0;
+				val = (HZ * val) / 10;
+				parms[dev].prech_timeout = val;
+				return put_user(val, (int __user *)arg);
+			
+			default:
+				if (!midi_devs[dev]->ioctl)
+					return -EINVAL;
+				return midi_devs[dev]->ioctl(dev, cmd, arg);
+		}
+	}
+}
+
+/* No kernel lock - fine */
+unsigned int MIDIbuf_poll(int dev, struct file *file, poll_table * wait)
+{
+	unsigned int mask = 0;
+
+	dev = dev >> 4;
+
+	/* input */
+	poll_wait(file, &input_sleeper[dev], wait);
+	if (DATA_AVAIL(midi_in_buf[dev]))
+		mask |= POLLIN | POLLRDNORM;
+
+	/* output */
+	poll_wait(file, &midi_sleeper[dev], wait);
+	if (!SPACE_AVAIL(midi_out_buf[dev]))
+		mask |= POLLOUT | POLLWRNORM;
+	
+	return mask;
+}
+
+
+int MIDIbuf_avail(int dev)
+{
+	if (midi_in_buf[dev])
+		return DATA_AVAIL (midi_in_buf[dev]);
+	return 0;
+}
+EXPORT_SYMBOL(MIDIbuf_avail);
+
diff --git a/linux/sound/oss/mpu401.c b/linux/sound/oss/mpu401.c
new file mode 100644
index 0000000..25e4609
--- /dev/null
+++ b/linux/sound/oss/mpu401.c
@@ -0,0 +1,1806 @@
+/*
+ * sound/oss/mpu401.c
+ *
+ * The low level driver for Roland MPU-401 compatible Midi cards.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer	ioctl code reworked (vmalloc/vfree removed)
+ * Alan Cox		modularisation, use normal request_irq, use dev_id
+ * Bartlomiej Zolnierkiewicz	removed some __init to allow using many drivers
+ * Chris Rankin		Update the module-usage counter for the coprocessor
+ * Zwane Mwaikambo	Changed attach/unload resource freeing
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#define USE_SEQ_MACROS
+#define USE_SIMPLE_MACROS
+
+#include "sound_config.h"
+
+#include "coproc.h"
+#include "mpu401.h"
+
+static int      timer_mode = TMR_INTERNAL, timer_caps = TMR_INTERNAL;
+
+struct mpu_config
+{
+	int             base;	/*
+				 * I/O base
+				 */
+	int             irq;
+	int             opened;	/*
+				 * Open mode
+				 */
+	int             devno;
+	int             synthno;
+	int             uart_mode;
+	int             initialized;
+	int             mode;
+#define MODE_MIDI	1
+#define MODE_SYNTH	2
+	unsigned char   version, revision;
+	unsigned int    capabilities;
+#define MPU_CAP_INTLG	0x10000000
+#define MPU_CAP_SYNC	0x00000010
+#define MPU_CAP_FSK	0x00000020
+#define MPU_CAP_CLS	0x00000040
+#define MPU_CAP_SMPTE 	0x00000080
+#define MPU_CAP_2PORT	0x00000001
+	int             timer_flag;
+
+#define MBUF_MAX	10
+#define BUFTEST(dc) if (dc->m_ptr >= MBUF_MAX || dc->m_ptr < 0) \
+	{printk( "MPU: Invalid buffer pointer %d/%d, s=%d\n",  dc->m_ptr,  dc->m_left,  dc->m_state);dc->m_ptr--;}
+	  int             m_busy;
+	  unsigned char   m_buf[MBUF_MAX];
+	  int             m_ptr;
+	  int             m_state;
+	  int             m_left;
+	  unsigned char   last_status;
+	  void            (*inputintr) (int dev, unsigned char data);
+	  int             shared_irq;
+	  int            *osp;
+	  spinlock_t	lock;
+  };
+
+#define	DATAPORT(base)   (base)
+#define	COMDPORT(base)   (base+1)
+#define	STATPORT(base)   (base+1)
+
+
+static void mpu401_close(int dev);
+
+static inline int mpu401_status(struct mpu_config *devc)
+{
+	return inb(STATPORT(devc->base));
+}
+
+#define input_avail(devc)		(!(mpu401_status(devc)&INPUT_AVAIL))
+#define output_ready(devc)		(!(mpu401_status(devc)&OUTPUT_READY))
+
+static inline void write_command(struct mpu_config *devc, unsigned char cmd)
+{
+	outb(cmd, COMDPORT(devc->base));
+}
+
+static inline int read_data(struct mpu_config *devc)
+{
+	return inb(DATAPORT(devc->base));
+}
+
+static inline void write_data(struct mpu_config *devc, unsigned char byte)
+{
+	outb(byte, DATAPORT(devc->base));
+}
+
+#define	OUTPUT_READY	0x40
+#define	INPUT_AVAIL	0x80
+#define	MPU_ACK		0xFE
+#define	MPU_RESET	0xFF
+#define	UART_MODE_ON	0x3F
+
+static struct mpu_config dev_conf[MAX_MIDI_DEV];
+
+static int n_mpu_devs;
+
+static int reset_mpu401(struct mpu_config *devc);
+static void set_uart_mode(int dev, struct mpu_config *devc, int arg);
+
+static int mpu_timer_init(int midi_dev);
+static void mpu_timer_interrupt(void);
+static void timer_ext_event(struct mpu_config *devc, int event, int parm);
+
+static struct synth_info mpu_synth_info_proto = {
+	"MPU-401 MIDI interface", 
+	0, 
+	SYNTH_TYPE_MIDI, 
+	MIDI_TYPE_MPU401, 
+	0, 128, 
+	0, 128, 
+	SYNTH_CAP_INPUT
+};
+
+static struct synth_info mpu_synth_info[MAX_MIDI_DEV];
+
+/*
+ * States for the input scanner
+ */
+
+#define ST_INIT			0	/* Ready for timing byte or msg */
+#define ST_TIMED		1	/* Leading timing byte rcvd */
+#define ST_DATABYTE		2	/* Waiting for (nr_left) data bytes */
+
+#define ST_SYSMSG		100	/* System message (sysx etc). */
+#define ST_SYSEX		101	/* System exclusive msg */
+#define ST_MTC			102	/* Midi Time Code (MTC) qframe msg */
+#define ST_SONGSEL		103	/* Song select */
+#define ST_SONGPOS		104	/* Song position pointer */
+
+static unsigned char len_tab[] =	/* # of data bytes following a status
+					 */
+{
+	2,			/* 8x */
+	2,			/* 9x */
+	2,			/* Ax */
+	2,			/* Bx */
+	1,			/* Cx */
+	1,			/* Dx */
+	2,			/* Ex */
+	0			/* Fx */
+};
+
+#define STORE(cmd) \
+{ \
+	int len; \
+	unsigned char obuf[8]; \
+	cmd; \
+	seq_input_event(obuf, len); \
+}
+
+#define _seqbuf obuf
+#define _seqbufptr 0
+#define _SEQ_ADVBUF(x) len=x
+
+static int mpu_input_scanner(struct mpu_config *devc, unsigned char midic)
+{
+
+	switch (devc->m_state)
+	{
+		case ST_INIT:
+			switch (midic)
+			{
+				case 0xf8:
+				/* Timer overflow */
+					break;
+
+				case 0xfc:
+					printk("<all end>");
+			 		break;
+
+				case 0xfd:
+					if (devc->timer_flag)
+						mpu_timer_interrupt();
+					break;
+
+				case 0xfe:
+					return MPU_ACK;
+
+				case 0xf0:
+				case 0xf1:
+				case 0xf2:
+				case 0xf3:
+				case 0xf4:
+				case 0xf5:
+				case 0xf6:
+				case 0xf7:
+					printk("<Trk data rq #%d>", midic & 0x0f);
+					break;
+
+				case 0xf9:
+					printk("<conductor rq>");
+					break;
+
+				case 0xff:
+					devc->m_state = ST_SYSMSG;
+					break;
+
+				default:
+					if (midic <= 0xef)
+					{
+						/* printk( "mpu time: %d ",  midic); */
+						devc->m_state = ST_TIMED;
+					}
+					else
+						printk("<MPU: Unknown event %02x> ", midic);
+			}
+			break;
+
+		case ST_TIMED:
+			{
+				int msg = ((int) (midic & 0xf0) >> 4);
+
+				devc->m_state = ST_DATABYTE;
+
+				if (msg < 8)	/* Data byte */
+				{
+					/* printk( "midi msg (running status) "); */
+					msg = ((int) (devc->last_status & 0xf0) >> 4);
+					msg -= 8;
+					devc->m_left = len_tab[msg] - 1;
+
+					devc->m_ptr = 2;
+					devc->m_buf[0] = devc->last_status;
+					devc->m_buf[1] = midic;
+
+					if (devc->m_left <= 0)
+					{
+						devc->m_state = ST_INIT;
+						do_midi_msg(devc->synthno, devc->m_buf, devc->m_ptr);
+						devc->m_ptr = 0;
+					}
+				}
+				else if (msg == 0xf)	/* MPU MARK */
+				{
+					devc->m_state = ST_INIT;
+
+					switch (midic)
+					{
+						case 0xf8:
+							/* printk( "NOP "); */
+							break;
+
+						case 0xf9:
+							/* printk( "meas end "); */
+							break;
+
+						case 0xfc:
+							/* printk( "data end "); */
+							break;
+
+						default:
+							printk("Unknown MPU mark %02x\n", midic);
+					}
+				}
+				else
+				{
+					devc->last_status = midic;
+					/* printk( "midi msg "); */
+					msg -= 8;
+					devc->m_left = len_tab[msg];
+
+					devc->m_ptr = 1;
+					devc->m_buf[0] = midic;
+
+					if (devc->m_left <= 0)
+					{
+						devc->m_state = ST_INIT;
+						do_midi_msg(devc->synthno, devc->m_buf, devc->m_ptr);
+						devc->m_ptr = 0;
+					}
+				}
+			}
+			break;
+
+		case ST_SYSMSG:
+			switch (midic)
+			{
+				case 0xf0:
+					printk("<SYX>");
+					devc->m_state = ST_SYSEX;
+					break;
+
+				case 0xf1:
+					devc->m_state = ST_MTC;
+					break;
+
+				case 0xf2:
+					devc->m_state = ST_SONGPOS;
+					devc->m_ptr = 0;
+					break;
+
+				case 0xf3:
+					devc->m_state = ST_SONGSEL;
+					break;
+
+				case 0xf6:
+					/* printk( "tune_request\n"); */
+					devc->m_state = ST_INIT;
+
+					/*
+					 *    Real time messages
+					 */
+				case 0xf8:
+					/* midi clock */
+					devc->m_state = ST_INIT;
+					timer_ext_event(devc, TMR_CLOCK, 0);
+					break;
+
+				case 0xfA:
+					devc->m_state = ST_INIT;
+					timer_ext_event(devc, TMR_START, 0);
+					break;
+
+				case 0xFB:
+					devc->m_state = ST_INIT;
+					timer_ext_event(devc, TMR_CONTINUE, 0);
+					break;
+
+				case 0xFC:
+					devc->m_state = ST_INIT;
+					timer_ext_event(devc, TMR_STOP, 0);
+					break;
+
+				case 0xFE:
+					/* active sensing */
+					devc->m_state = ST_INIT;
+					break;
+
+				case 0xff:
+					/* printk( "midi hard reset"); */
+					devc->m_state = ST_INIT;
+					break;
+
+				default:
+					printk("unknown MIDI sysmsg %0x\n", midic);
+					devc->m_state = ST_INIT;
+			}
+			break;
+
+		case ST_MTC:
+			devc->m_state = ST_INIT;
+			printk("MTC frame %x02\n", midic);
+			break;
+
+		case ST_SYSEX:
+			if (midic == 0xf7)
+			{
+				printk("<EOX>");
+				devc->m_state = ST_INIT;
+			}
+			else
+				printk("%02x ", midic);
+			break;
+
+		case ST_SONGPOS:
+			BUFTEST(devc);
+			devc->m_buf[devc->m_ptr++] = midic;
+			if (devc->m_ptr == 2)
+			{
+				devc->m_state = ST_INIT;
+				devc->m_ptr = 0;
+				timer_ext_event(devc, TMR_SPP,
+					((devc->m_buf[1] & 0x7f) << 7) |
+					(devc->m_buf[0] & 0x7f));
+			}
+			break;
+
+		case ST_DATABYTE:
+			BUFTEST(devc);
+			devc->m_buf[devc->m_ptr++] = midic;
+			if ((--devc->m_left) <= 0)
+			{
+				devc->m_state = ST_INIT;
+				do_midi_msg(devc->synthno, devc->m_buf, devc->m_ptr);
+				devc->m_ptr = 0;
+			}
+			break;
+
+		default:
+			printk("Bad state %d ", devc->m_state);
+			devc->m_state = ST_INIT;
+	}
+	return 1;
+}
+
+static void mpu401_input_loop(struct mpu_config *devc)
+{
+	unsigned long flags;
+	int busy;
+	int n;
+
+	spin_lock_irqsave(&devc->lock,flags);
+	busy = devc->m_busy;
+	devc->m_busy = 1;
+	spin_unlock_irqrestore(&devc->lock,flags);
+
+	if (busy)		/* Already inside the scanner */
+		return;
+
+	n = 50;
+
+	while (input_avail(devc) && n-- > 0)
+	{
+		unsigned char c = read_data(devc);
+
+		if (devc->mode == MODE_SYNTH)
+		{
+			mpu_input_scanner(devc, c);
+		}
+		else if (devc->opened & OPEN_READ && devc->inputintr != NULL)
+			devc->inputintr(devc->devno, c);
+	}
+	devc->m_busy = 0;
+}
+
+static irqreturn_t mpuintr(int irq, void *dev_id)
+{
+	struct mpu_config *devc;
+	int dev = (int)(unsigned long) dev_id;
+	int handled = 0;
+
+	devc = &dev_conf[dev];
+
+	if (input_avail(devc))
+	{
+		handled = 1;
+		if (devc->base != 0 && (devc->opened & OPEN_READ || devc->mode == MODE_SYNTH))
+			mpu401_input_loop(devc);
+		else
+		{
+			/* Dummy read (just to acknowledge the interrupt) */
+			read_data(devc);
+		}
+	}
+	return IRQ_RETVAL(handled);
+}
+
+static int mpu401_open(int dev, int mode,
+	    void            (*input) (int dev, unsigned char data),
+	    void            (*output) (int dev)
+)
+{
+	int err;
+	struct mpu_config *devc;
+	struct coproc_operations *coprocessor;
+
+	if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
+		return -ENXIO;
+
+	devc = &dev_conf[dev];
+
+	if (devc->opened)
+		  return -EBUSY;
+	/*
+	 *  Verify that the device is really running.
+	 *  Some devices (such as Ensoniq SoundScape don't
+	 *  work before the on board processor (OBP) is initialized
+	 *  by downloading its microcode.
+	 */
+
+	if (!devc->initialized)
+	{
+		if (mpu401_status(devc) == 0xff)	/* Bus float */
+		{
+			printk(KERN_ERR "mpu401: Device not initialized properly\n");
+			return -EIO;
+		}
+		reset_mpu401(devc);
+	}
+
+	if ( (coprocessor = midi_devs[dev]->coproc) != NULL )
+	{
+		if (!try_module_get(coprocessor->owner)) {
+			mpu401_close(dev);
+			return -ENODEV;
+		}
+
+		if ((err = coprocessor->open(coprocessor->devc, COPR_MIDI)) < 0)
+		{
+			printk(KERN_WARNING "MPU-401: Can't access coprocessor device\n");
+			mpu401_close(dev);
+			return err;
+		}
+	}
+	
+	set_uart_mode(dev, devc, 1);
+	devc->mode = MODE_MIDI;
+	devc->synthno = 0;
+
+	mpu401_input_loop(devc);
+
+	devc->inputintr = input;
+	devc->opened = mode;
+
+	return 0;
+}
+
+static void mpu401_close(int dev)
+{
+	struct mpu_config *devc;
+	struct coproc_operations *coprocessor;
+
+	devc = &dev_conf[dev];
+	if (devc->uart_mode)
+		reset_mpu401(devc);	/*
+					 * This disables the UART mode
+					 */
+	devc->mode = 0;
+	devc->inputintr = NULL;
+
+	coprocessor = midi_devs[dev]->coproc;
+	if (coprocessor) {
+		coprocessor->close(coprocessor->devc, COPR_MIDI);
+		module_put(coprocessor->owner);
+	}
+	devc->opened = 0;
+}
+
+static int mpu401_out(int dev, unsigned char midi_byte)
+{
+	int timeout;
+	unsigned long flags;
+
+	struct mpu_config *devc;
+
+	devc = &dev_conf[dev];
+
+	/*
+	 * Sometimes it takes about 30000 loops before the output becomes ready
+	 * (After reset). Normally it takes just about 10 loops.
+	 */
+
+	for (timeout = 30000; timeout > 0 && !output_ready(devc); timeout--);
+
+	spin_lock_irqsave(&devc->lock,flags);
+	if (!output_ready(devc))
+	{
+		printk(KERN_WARNING "mpu401: Send data timeout\n");
+		spin_unlock_irqrestore(&devc->lock,flags);
+		return 0;
+	}
+	write_data(devc, midi_byte);
+	spin_unlock_irqrestore(&devc->lock,flags);
+	return 1;
+}
+
+static int mpu401_command(int dev, mpu_command_rec * cmd)
+{
+	int i, timeout, ok;
+	int ret = 0;
+	unsigned long   flags;
+	struct mpu_config *devc;
+
+	devc = &dev_conf[dev];
+
+	if (devc->uart_mode)	/*
+				 * Not possible in UART mode
+				 */
+	{
+		printk(KERN_WARNING "mpu401: commands not possible in the UART mode\n");
+		return -EINVAL;
+	}
+	/*
+	 * Test for input since pending input seems to block the output.
+	 */
+	if (input_avail(devc))
+		mpu401_input_loop(devc);
+
+	/*
+	 * Sometimes it takes about 50000 loops before the output becomes ready
+	 * (After reset). Normally it takes just about 10 loops.
+	 */
+
+	timeout = 50000;
+retry:
+	if (timeout-- <= 0)
+	{
+		printk(KERN_WARNING "mpu401: Command (0x%x) timeout\n", (int) cmd->cmd);
+		return -EIO;
+	}
+	spin_lock_irqsave(&devc->lock,flags);
+
+	if (!output_ready(devc))
+	{
+		spin_unlock_irqrestore(&devc->lock,flags);
+		goto retry;
+	}
+	write_command(devc, cmd->cmd);
+
+	ok = 0;
+	for (timeout = 50000; timeout > 0 && !ok; timeout--)
+	{
+		if (input_avail(devc))
+		{
+			if (devc->opened && devc->mode == MODE_SYNTH)
+			{
+				if (mpu_input_scanner(devc, read_data(devc)) == MPU_ACK)
+					ok = 1;
+			}
+			else
+			{
+				/* Device is not currently open. Use simpler method */
+				if (read_data(devc) == MPU_ACK)
+					ok = 1;
+			}
+		}
+	}
+	if (!ok)
+	{
+		spin_unlock_irqrestore(&devc->lock,flags);
+		return -EIO;
+	}
+	if (cmd->nr_args)
+	{
+		for (i = 0; i < cmd->nr_args; i++)
+		{
+			for (timeout = 3000; timeout > 0 && !output_ready(devc); timeout--);
+
+			if (!mpu401_out(dev, cmd->data[i]))
+			{
+				spin_unlock_irqrestore(&devc->lock,flags);
+				printk(KERN_WARNING "mpu401: Command (0x%x), parm send failed.\n", (int) cmd->cmd);
+				return -EIO;
+			}
+		}
+	}
+	ret = 0;
+	cmd->data[0] = 0;
+
+	if (cmd->nr_returns)
+	{
+		for (i = 0; i < cmd->nr_returns; i++)
+		{
+			ok = 0;
+			for (timeout = 5000; timeout > 0 && !ok; timeout--)
+				if (input_avail(devc))
+				{
+					cmd->data[i] = read_data(devc);
+					ok = 1;
+				}
+			if (!ok)
+			{
+				spin_unlock_irqrestore(&devc->lock,flags);
+				return -EIO;
+			}
+		}
+	}
+	spin_unlock_irqrestore(&devc->lock,flags);
+	return ret;
+}
+
+static int mpu_cmd(int dev, int cmd, int data)
+{
+	int ret;
+
+	static mpu_command_rec rec;
+
+	rec.cmd = cmd & 0xff;
+	rec.nr_args = ((cmd & 0xf0) == 0xE0);
+	rec.nr_returns = ((cmd & 0xf0) == 0xA0);
+	rec.data[0] = data & 0xff;
+
+	if ((ret = mpu401_command(dev, &rec)) < 0)
+		return ret;
+	return (unsigned char) rec.data[0];
+}
+
+static int mpu401_prefix_cmd(int dev, unsigned char status)
+{
+	struct mpu_config *devc = &dev_conf[dev];
+
+	if (devc->uart_mode)
+		return 1;
+
+	if (status < 0xf0)
+	{
+		if (mpu_cmd(dev, 0xD0, 0) < 0)
+			return 0;
+		return 1;
+	}
+	switch (status)
+	{
+		case 0xF0:
+			if (mpu_cmd(dev, 0xDF, 0) < 0)
+				return 0;
+			return 1;
+
+		default:
+			return 0;
+	}
+}
+
+static int mpu401_start_read(int dev)
+{
+	return 0;
+}
+
+static int mpu401_end_read(int dev)
+{
+	return 0;
+}
+
+static int mpu401_ioctl(int dev, unsigned cmd, void __user *arg)
+{
+	struct mpu_config *devc;
+	mpu_command_rec rec;
+	int val, ret;
+
+	devc = &dev_conf[dev];
+	switch (cmd) 
+	{
+		case SNDCTL_MIDI_MPUMODE:
+			if (!(devc->capabilities & MPU_CAP_INTLG)) { /* No intelligent mode */
+				printk(KERN_WARNING "mpu401: Intelligent mode not supported by the HW\n");
+				return -EINVAL;
+			}
+			if (get_user(val, (int __user *)arg))
+				return -EFAULT;
+			set_uart_mode(dev, devc, !val);
+			return 0;
+
+		case SNDCTL_MIDI_MPUCMD:
+			if (copy_from_user(&rec, arg, sizeof(rec)))
+				return -EFAULT;
+			if ((ret = mpu401_command(dev, &rec)) < 0)
+				return ret;
+			if (copy_to_user(arg, &rec, sizeof(rec)))
+				return -EFAULT;
+			return 0;
+
+		default:
+			return -EINVAL;
+	}
+}
+
+static void mpu401_kick(int dev)
+{
+}
+
+static int mpu401_buffer_status(int dev)
+{
+	return 0;		/*
+				 * No data in buffers
+				 */
+}
+
+static int mpu_synth_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+	int midi_dev;
+	struct mpu_config *devc;
+
+	midi_dev = synth_devs[dev]->midi_dev;
+
+	if (midi_dev < 0 || midi_dev >= num_midis || midi_devs[midi_dev] == NULL)
+		return -ENXIO;
+
+	devc = &dev_conf[midi_dev];
+
+	switch (cmd)
+	{
+
+		case SNDCTL_SYNTH_INFO:
+			if (copy_to_user(arg, &mpu_synth_info[midi_dev],
+					sizeof(struct synth_info)))
+				return -EFAULT;
+			return 0;
+
+		case SNDCTL_SYNTH_MEMAVL:
+			return 0x7fffffff;
+
+		default:
+			return -EINVAL;
+	}
+}
+
+static int mpu_synth_open(int dev, int mode)
+{
+	int midi_dev, err;
+	struct mpu_config *devc;
+	struct coproc_operations *coprocessor;
+
+	midi_dev = synth_devs[dev]->midi_dev;
+
+	if (midi_dev < 0 || midi_dev > num_midis || midi_devs[midi_dev] == NULL)
+		return -ENXIO;
+
+	devc = &dev_conf[midi_dev];
+
+	/*
+	 *  Verify that the device is really running.
+	 *  Some devices (such as Ensoniq SoundScape don't
+	 *  work before the on board processor (OBP) is initialized
+	 *  by downloading its microcode.
+	 */
+
+	if (!devc->initialized)
+	{
+		if (mpu401_status(devc) == 0xff)	/* Bus float */
+		{
+			printk(KERN_ERR "mpu401: Device not initialized properly\n");
+			return -EIO;
+		}
+		reset_mpu401(devc);
+	}
+	if (devc->opened)
+		return -EBUSY;
+	devc->mode = MODE_SYNTH;
+	devc->synthno = dev;
+
+	devc->inputintr = NULL;
+
+	coprocessor = midi_devs[midi_dev]->coproc;
+	if (coprocessor) {
+		if (!try_module_get(coprocessor->owner))
+			return -ENODEV;
+
+		if ((err = coprocessor->open(coprocessor->devc, COPR_MIDI)) < 0)
+		{
+			printk(KERN_WARNING "mpu401: Can't access coprocessor device\n");
+			return err;
+		}
+	}
+	devc->opened = mode;
+	reset_mpu401(devc);
+
+	if (mode & OPEN_READ)
+	{
+		mpu_cmd(midi_dev, 0x8B, 0);	/* Enable data in stop mode */
+		mpu_cmd(midi_dev, 0x34, 0);	/* Return timing bytes in stop mode */
+		mpu_cmd(midi_dev, 0x87, 0);	/* Enable pitch & controller */
+	}
+	return 0;
+}
+
+static void mpu_synth_close(int dev)
+{ 
+	int midi_dev;
+	struct mpu_config *devc;
+	struct coproc_operations *coprocessor;
+
+	midi_dev = synth_devs[dev]->midi_dev;
+
+	devc = &dev_conf[midi_dev];
+	mpu_cmd(midi_dev, 0x15, 0);	/* Stop recording, playback and MIDI */
+	mpu_cmd(midi_dev, 0x8a, 0);	/* Disable data in stopped mode */
+
+	devc->inputintr = NULL;
+
+	coprocessor = midi_devs[midi_dev]->coproc;
+	if (coprocessor) {
+		coprocessor->close(coprocessor->devc, COPR_MIDI);
+		module_put(coprocessor->owner);
+	}
+	devc->opened = 0;
+	devc->mode = 0;
+}
+
+#define MIDI_SYNTH_NAME	"MPU-401 UART Midi"
+#define MIDI_SYNTH_CAPS	SYNTH_CAP_INPUT
+#include "midi_synth.h"
+
+static struct synth_operations mpu401_synth_proto =
+{
+	.owner		= THIS_MODULE,
+	.id		= "MPU401",
+	.info		= NULL,
+	.midi_dev	= 0,
+	.synth_type	= SYNTH_TYPE_MIDI,
+	.synth_subtype	= 0,
+	.open		= mpu_synth_open,
+	.close		= mpu_synth_close,
+	.ioctl		= mpu_synth_ioctl,
+	.kill_note	= midi_synth_kill_note,
+	.start_note	= midi_synth_start_note,
+	.set_instr	= midi_synth_set_instr,
+	.reset		= midi_synth_reset,
+	.hw_control	= midi_synth_hw_control,
+	.load_patch	= midi_synth_load_patch,
+	.aftertouch	= midi_synth_aftertouch,
+	.controller	= midi_synth_controller,
+	.panning	= midi_synth_panning,
+	.bender		= midi_synth_bender,
+	.setup_voice	= midi_synth_setup_voice,
+	.send_sysex	= midi_synth_send_sysex
+};
+
+static struct synth_operations *mpu401_synth_operations[MAX_MIDI_DEV];
+
+static struct midi_operations mpu401_midi_proto =
+{
+	.owner		= THIS_MODULE,
+	.info		= {"MPU-401 Midi", 0, MIDI_CAP_MPU401, SNDCARD_MPU401},
+	.in_info	= {0},
+	.open		= mpu401_open,
+	.close		= mpu401_close,
+	.ioctl		= mpu401_ioctl,
+	.outputc	= mpu401_out,
+	.start_read	= mpu401_start_read,
+	.end_read	= mpu401_end_read,
+	.kick		= mpu401_kick,
+	.buffer_status	= mpu401_buffer_status,
+	.prefix_cmd	= mpu401_prefix_cmd
+};
+
+static struct midi_operations mpu401_midi_operations[MAX_MIDI_DEV];
+
+static void mpu401_chk_version(int n, struct mpu_config *devc)
+{
+	int tmp;
+
+	devc->version = devc->revision = 0;
+
+	tmp = mpu_cmd(n, 0xAC, 0);
+	if (tmp < 0)
+		return;
+	if ((tmp & 0xf0) > 0x20)	/* Why it's larger than 2.x ??? */
+		return;
+	devc->version = tmp;
+
+	if ((tmp = mpu_cmd(n, 0xAD, 0)) < 0) {
+		devc->version = 0;
+		return;
+	}
+	devc->revision = tmp;
+}
+
+int attach_mpu401(struct address_info *hw_config, struct module *owner)
+{
+	unsigned long flags;
+	char revision_char;
+
+	int m, ret;
+	struct mpu_config *devc;
+
+	hw_config->slots[1] = -1;
+	m = sound_alloc_mididev();
+	if (m == -1)
+	{
+		printk(KERN_WARNING "MPU-401: Too many midi devices detected\n");
+		ret = -ENOMEM;
+		goto out_err;
+	}
+	devc = &dev_conf[m];
+	devc->base = hw_config->io_base;
+	devc->osp = hw_config->osp;
+	devc->irq = hw_config->irq;
+	devc->opened = 0;
+	devc->uart_mode = 0;
+	devc->initialized = 0;
+	devc->version = 0;
+	devc->revision = 0;
+	devc->capabilities = 0;
+	devc->timer_flag = 0;
+	devc->m_busy = 0;
+	devc->m_state = ST_INIT;
+	devc->shared_irq = hw_config->always_detect;
+	devc->irq = hw_config->irq;
+	spin_lock_init(&devc->lock);
+
+	if (devc->irq < 0)
+	{
+		devc->irq *= -1;
+		devc->shared_irq = 1;
+	}
+
+	if (!hw_config->always_detect)
+	{
+		/* Verify the hardware again */
+		if (!reset_mpu401(devc))
+		{
+			printk(KERN_WARNING "mpu401: Device didn't respond\n");
+			ret = -ENODEV;
+			goto out_mididev;
+		}
+		if (!devc->shared_irq)
+		{
+			if (request_irq(devc->irq, mpuintr, 0, "mpu401",
+					hw_config) < 0)
+			{
+				printk(KERN_WARNING "mpu401: Failed to allocate IRQ%d\n", devc->irq);
+				ret = -ENOMEM;
+				goto out_mididev;
+			}
+		}
+		spin_lock_irqsave(&devc->lock,flags);
+		mpu401_chk_version(m, devc);
+		if (devc->version == 0)
+			mpu401_chk_version(m, devc);
+		spin_unlock_irqrestore(&devc->lock, flags);
+	}
+
+	if (devc->version != 0)
+		if (mpu_cmd(m, 0xC5, 0) >= 0)	/* Set timebase OK */
+			if (mpu_cmd(m, 0xE0, 120) >= 0)		/* Set tempo OK */
+				devc->capabilities |= MPU_CAP_INTLG;	/* Supports intelligent mode */
+
+
+	mpu401_synth_operations[m] = kmalloc(sizeof(struct synth_operations), GFP_KERNEL);
+
+	if (mpu401_synth_operations[m] == NULL)
+	{
+		printk(KERN_ERR "mpu401: Can't allocate memory\n");
+		ret = -ENOMEM;
+		goto out_irq;
+	}
+	if (!(devc->capabilities & MPU_CAP_INTLG))	/* No intelligent mode */
+	{
+		memcpy((char *) mpu401_synth_operations[m],
+			(char *) &std_midi_synth,
+			 sizeof(struct synth_operations));
+	}
+	else
+	{
+		memcpy((char *) mpu401_synth_operations[m],
+			(char *) &mpu401_synth_proto,
+			 sizeof(struct synth_operations));
+	}
+	if (owner)
+		mpu401_synth_operations[m]->owner = owner;
+
+	memcpy((char *) &mpu401_midi_operations[m],
+	       (char *) &mpu401_midi_proto,
+	       sizeof(struct midi_operations));
+
+	mpu401_midi_operations[m].converter = mpu401_synth_operations[m];
+
+	memcpy((char *) &mpu_synth_info[m],
+	       (char *) &mpu_synth_info_proto,
+	       sizeof(struct synth_info));
+
+	n_mpu_devs++;
+
+	if (devc->version == 0x20 && devc->revision >= 0x07)	/* MusicQuest interface */
+	{
+		int ports = (devc->revision & 0x08) ? 32 : 16;
+
+		devc->capabilities |= MPU_CAP_SYNC | MPU_CAP_SMPTE |
+				MPU_CAP_CLS | MPU_CAP_2PORT;
+
+		revision_char = (devc->revision == 0x7f) ? 'M' : ' ';
+		sprintf(mpu_synth_info[m].name, "MQX-%d%c MIDI Interface #%d",
+				ports,
+				revision_char,
+				n_mpu_devs);
+	}
+	else
+	{
+		revision_char = devc->revision ? devc->revision + '@' : ' ';
+		if ((int) devc->revision > ('Z' - '@'))
+			revision_char = '+';
+
+		devc->capabilities |= MPU_CAP_SYNC | MPU_CAP_FSK;
+
+		if (hw_config->name)
+			sprintf(mpu_synth_info[m].name, "%s (MPU401)", hw_config->name);
+		else
+			sprintf(mpu_synth_info[m].name,
+				"MPU-401 %d.%d%c MIDI #%d",
+				(int) (devc->version & 0xf0) >> 4,
+				devc->version & 0x0f,
+				revision_char,
+				n_mpu_devs);
+	}
+
+	strcpy(mpu401_midi_operations[m].info.name,
+	       mpu_synth_info[m].name);
+
+	conf_printf(mpu_synth_info[m].name, hw_config);
+
+	mpu401_synth_operations[m]->midi_dev = devc->devno = m;
+	mpu401_synth_operations[devc->devno]->info = &mpu_synth_info[devc->devno];
+
+	if (devc->capabilities & MPU_CAP_INTLG)		/* Intelligent mode */
+		hw_config->slots[2] = mpu_timer_init(m);
+
+	midi_devs[m] = &mpu401_midi_operations[devc->devno];
+	
+	if (owner)
+		midi_devs[m]->owner = owner;
+
+	hw_config->slots[1] = m;
+	sequencer_init();
+	
+	return 0;
+
+out_irq:
+	free_irq(devc->irq, hw_config);
+out_mididev:
+	sound_unload_mididev(m);
+out_err:
+	release_region(hw_config->io_base, 2);
+	return ret;
+}
+
+static int reset_mpu401(struct mpu_config *devc)
+{
+	unsigned long flags;
+	int ok, timeout, n;
+	int timeout_limit;
+
+	/*
+	 * Send the RESET command. Try again if no success at the first time.
+	 * (If the device is in the UART mode, it will not ack the reset cmd).
+	 */
+
+	ok = 0;
+
+	timeout_limit = devc->initialized ? 30000 : 100000;
+	devc->initialized = 1;
+
+	for (n = 0; n < 2 && !ok; n++)
+	{
+		for (timeout = timeout_limit; timeout > 0 && !ok; timeout--)
+			  ok = output_ready(devc);
+
+		write_command(devc, MPU_RESET);	/*
+							   * Send MPU-401 RESET Command
+							 */
+
+		/*
+		 * Wait at least 25 msec. This method is not accurate so let's make the
+		 * loop bit longer. Cannot sleep since this is called during boot.
+		 */
+
+		for (timeout = timeout_limit * 2; timeout > 0 && !ok; timeout--)
+		{
+			spin_lock_irqsave(&devc->lock,flags);
+			if (input_avail(devc))
+				if (read_data(devc) == MPU_ACK)
+					ok = 1;
+			spin_unlock_irqrestore(&devc->lock,flags);
+		}
+
+	}
+
+	devc->m_state = ST_INIT;
+	devc->m_ptr = 0;
+	devc->m_left = 0;
+	devc->last_status = 0;
+	devc->uart_mode = 0;
+
+	return ok;
+}
+
+static void set_uart_mode(int dev, struct mpu_config *devc, int arg)
+{
+	if (!arg && (devc->capabilities & MPU_CAP_INTLG))
+		return;
+	if ((devc->uart_mode == 0) == (arg == 0))
+		return;		/* Already set */
+	reset_mpu401(devc);	/* This exits the uart mode */
+
+	if (arg)
+	{
+		if (mpu_cmd(dev, UART_MODE_ON, 0) < 0)
+		{
+			printk(KERN_ERR "mpu401: Can't enter UART mode\n");
+			devc->uart_mode = 0;
+			return;
+		}
+	}
+	devc->uart_mode = arg;
+
+}
+
+int probe_mpu401(struct address_info *hw_config, struct resource *ports)
+{
+	int ok = 0;
+	struct mpu_config tmp_devc;
+
+	tmp_devc.base = hw_config->io_base;
+	tmp_devc.irq = hw_config->irq;
+	tmp_devc.initialized = 0;
+	tmp_devc.opened = 0;
+	tmp_devc.osp = hw_config->osp;
+
+	if (hw_config->always_detect)
+		return 1;
+
+	if (inb(hw_config->io_base + 1) == 0xff)
+	{
+		DDB(printk("MPU401: Port %x looks dead.\n", hw_config->io_base));
+		return 0;	/* Just bus float? */
+	}
+	ok = reset_mpu401(&tmp_devc);
+
+	if (!ok)
+	{
+		DDB(printk("MPU401: Reset failed on port %x\n", hw_config->io_base));
+	}
+	return ok;
+}
+
+void unload_mpu401(struct address_info *hw_config)
+{
+	void *p;
+	int n=hw_config->slots[1];
+		
+	if (n != -1) {
+		release_region(hw_config->io_base, 2);
+		if (hw_config->always_detect == 0 && hw_config->irq > 0)
+			free_irq(hw_config->irq, hw_config);
+		p=mpu401_synth_operations[n];
+		sound_unload_mididev(n);
+		sound_unload_timerdev(hw_config->slots[2]);
+		kfree(p);
+	}
+}
+
+/*****************************************************
+ *      Timer stuff
+ ****************************************************/
+
+static volatile int timer_initialized = 0, timer_open = 0, tmr_running = 0;
+static volatile int curr_tempo, curr_timebase, hw_timebase;
+static int      max_timebase = 8;	/* 8*24=192 ppqn */
+static volatile unsigned long next_event_time;
+static volatile unsigned long curr_ticks, curr_clocks;
+static unsigned long prev_event_time;
+static int      metronome_mode;
+
+static unsigned long clocks2ticks(unsigned long clocks)
+{
+	/*
+	 * The MPU-401 supports just a limited set of possible timebase values.
+	 * Since the applications require more choices, the driver has to
+	 * program the HW to do its best and to convert between the HW and
+	 * actual timebases.
+	 */
+	return ((clocks * curr_timebase) + (hw_timebase / 2)) / hw_timebase;
+}
+
+static void set_timebase(int midi_dev, int val)
+{
+	int hw_val;
+
+	if (val < 48)
+		val = 48;
+	if (val > 1000)
+		val = 1000;
+
+	hw_val = val;
+	hw_val = (hw_val + 12) / 24;
+	if (hw_val > max_timebase)
+		hw_val = max_timebase;
+
+	if (mpu_cmd(midi_dev, 0xC0 | (hw_val & 0x0f), 0) < 0)
+	{
+		printk(KERN_WARNING "mpu401: Can't set HW timebase to %d\n", hw_val * 24);
+		return;
+	}
+	hw_timebase = hw_val * 24;
+	curr_timebase = val;
+
+}
+
+static void tmr_reset(struct mpu_config *devc)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&devc->lock,flags);
+	next_event_time = (unsigned long) -1;
+	prev_event_time = 0;
+	curr_ticks = curr_clocks = 0;
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static void set_timer_mode(int midi_dev)
+{
+	if (timer_mode & TMR_MODE_CLS)
+		mpu_cmd(midi_dev, 0x3c, 0);	/* Use CLS sync */
+	else if (timer_mode & TMR_MODE_SMPTE)
+		mpu_cmd(midi_dev, 0x3d, 0);	/* Use SMPTE sync */
+
+	if (timer_mode & TMR_INTERNAL)
+	{
+		  mpu_cmd(midi_dev, 0x80, 0);	/* Use MIDI sync */
+	}
+	else
+	{
+		if (timer_mode & (TMR_MODE_MIDI | TMR_MODE_CLS))
+		{
+			mpu_cmd(midi_dev, 0x82, 0);		/* Use MIDI sync */
+			mpu_cmd(midi_dev, 0x91, 0);		/* Enable ext MIDI ctrl */
+		}
+		else if (timer_mode & TMR_MODE_FSK)
+			mpu_cmd(midi_dev, 0x81, 0);	/* Use FSK sync */
+	}
+}
+
+static void stop_metronome(int midi_dev)
+{
+	mpu_cmd(midi_dev, 0x84, 0);	/* Disable metronome */
+}
+
+static void setup_metronome(int midi_dev)
+{
+	int numerator, denominator;
+	int clks_per_click, num_32nds_per_beat;
+	int beats_per_measure;
+
+	numerator = ((unsigned) metronome_mode >> 24) & 0xff;
+	denominator = ((unsigned) metronome_mode >> 16) & 0xff;
+	clks_per_click = ((unsigned) metronome_mode >> 8) & 0xff;
+	num_32nds_per_beat = (unsigned) metronome_mode & 0xff;
+	beats_per_measure = (numerator * 4) >> denominator;
+
+	if (!metronome_mode)
+		mpu_cmd(midi_dev, 0x84, 0);	/* Disable metronome */
+	else
+	{
+		mpu_cmd(midi_dev, 0xE4, clks_per_click);
+		mpu_cmd(midi_dev, 0xE6, beats_per_measure);
+		mpu_cmd(midi_dev, 0x83, 0);	/* Enable metronome without accents */
+	}
+}
+
+static int mpu_start_timer(int midi_dev)
+{
+	struct mpu_config *devc= &dev_conf[midi_dev];
+
+	tmr_reset(devc);
+	set_timer_mode(midi_dev);
+
+	if (tmr_running)
+		return TIMER_NOT_ARMED;		/* Already running */
+
+	if (timer_mode & TMR_INTERNAL)
+	{
+		mpu_cmd(midi_dev, 0x02, 0);	/* Send MIDI start */
+		tmr_running = 1;
+		return TIMER_NOT_ARMED;
+	}
+	else
+	{
+		mpu_cmd(midi_dev, 0x35, 0);	/* Enable mode messages to PC */
+		mpu_cmd(midi_dev, 0x38, 0);	/* Enable sys common messages to PC */
+		mpu_cmd(midi_dev, 0x39, 0);	/* Enable real time messages to PC */
+		mpu_cmd(midi_dev, 0x97, 0);	/* Enable system exclusive messages to PC */
+	}
+	return TIMER_ARMED;
+}
+
+static int mpu_timer_open(int dev, int mode)
+{
+	int midi_dev = sound_timer_devs[dev]->devlink;
+	struct mpu_config *devc= &dev_conf[midi_dev];
+
+	if (timer_open)
+		return -EBUSY;
+
+	tmr_reset(devc);
+	curr_tempo = 50;
+	mpu_cmd(midi_dev, 0xE0, 50);
+	curr_timebase = hw_timebase = 120;
+	set_timebase(midi_dev, 120);
+	timer_open = 1;
+	metronome_mode = 0;
+	set_timer_mode(midi_dev);
+
+	mpu_cmd(midi_dev, 0xe7, 0x04);	/* Send all clocks to host */
+	mpu_cmd(midi_dev, 0x95, 0);	/* Enable clock to host */
+
+	return 0;
+}
+
+static void mpu_timer_close(int dev)
+{
+	int midi_dev = sound_timer_devs[dev]->devlink;
+
+	timer_open = tmr_running = 0;
+	mpu_cmd(midi_dev, 0x15, 0);	/* Stop all */
+	mpu_cmd(midi_dev, 0x94, 0);	/* Disable clock to host */
+	mpu_cmd(midi_dev, 0x8c, 0);	/* Disable measure end messages to host */
+	stop_metronome(midi_dev);
+}
+
+static int mpu_timer_event(int dev, unsigned char *event)
+{
+	unsigned char command = event[1];
+	unsigned long parm = *(unsigned int *) &event[4];
+	int midi_dev = sound_timer_devs[dev]->devlink;
+
+	switch (command)
+	{
+		case TMR_WAIT_REL:
+			parm += prev_event_time;
+		case TMR_WAIT_ABS:
+			if (parm > 0)
+			{
+				long time;
+
+				if (parm <= curr_ticks)	/* It's the time */
+					return TIMER_NOT_ARMED;
+				time = parm;
+				next_event_time = prev_event_time = time;
+
+				return TIMER_ARMED;
+			}
+			break;
+
+		case TMR_START:
+			if (tmr_running)
+				break;
+			return mpu_start_timer(midi_dev);
+
+		case TMR_STOP:
+			mpu_cmd(midi_dev, 0x01, 0);	/* Send MIDI stop */
+			stop_metronome(midi_dev);
+			tmr_running = 0;
+			break;
+
+		case TMR_CONTINUE:
+			if (tmr_running)
+				break;
+			mpu_cmd(midi_dev, 0x03, 0);	/* Send MIDI continue */
+			setup_metronome(midi_dev);
+			tmr_running = 1;
+			break;
+
+		case TMR_TEMPO:
+			if (parm)
+			{
+				if (parm < 8)
+					parm = 8;
+			 	if (parm > 250)
+					parm = 250;
+				if (mpu_cmd(midi_dev, 0xE0, parm) < 0)
+					printk(KERN_WARNING "mpu401: Can't set tempo to %d\n", (int) parm);
+				curr_tempo = parm;
+			}
+			break;
+
+		case TMR_ECHO:
+			seq_copy_to_input(event, 8);
+			break;
+
+		case TMR_TIMESIG:
+			if (metronome_mode)	/* Metronome enabled */
+			{
+				metronome_mode = parm;
+				setup_metronome(midi_dev);
+			}
+			break;
+
+		default:;
+	}
+	return TIMER_NOT_ARMED;
+}
+
+static unsigned long mpu_timer_get_time(int dev)
+{
+	if (!timer_open)
+		return 0;
+
+	return curr_ticks;
+}
+
+static int mpu_timer_ioctl(int dev, unsigned int command, void __user *arg)
+{
+	int midi_dev = sound_timer_devs[dev]->devlink;
+	int __user *p = (int __user *)arg;
+
+	switch (command)
+	{
+		case SNDCTL_TMR_SOURCE:
+			{
+				int parm;
+
+				if (get_user(parm, p))
+					return -EFAULT;
+				parm &= timer_caps;
+
+				if (parm != 0)
+				{
+					timer_mode = parm;
+	
+					if (timer_mode & TMR_MODE_CLS)
+						mpu_cmd(midi_dev, 0x3c, 0);		/* Use CLS sync */
+					else if (timer_mode & TMR_MODE_SMPTE)
+						mpu_cmd(midi_dev, 0x3d, 0);		/* Use SMPTE sync */
+				}
+				if (put_user(timer_mode, p))
+					return -EFAULT;
+				return timer_mode;
+			}
+			break;
+
+		case SNDCTL_TMR_START:
+			mpu_start_timer(midi_dev);
+			return 0;
+
+		case SNDCTL_TMR_STOP:
+			tmr_running = 0;
+			mpu_cmd(midi_dev, 0x01, 0);	/* Send MIDI stop */
+			stop_metronome(midi_dev);
+			return 0;
+
+		case SNDCTL_TMR_CONTINUE:
+			if (tmr_running)
+				return 0;
+			tmr_running = 1;
+			mpu_cmd(midi_dev, 0x03, 0);	/* Send MIDI continue */
+			return 0;
+
+		case SNDCTL_TMR_TIMEBASE:
+			{
+				int val;
+				if (get_user(val, p))
+					return -EFAULT;
+				if (val)
+					set_timebase(midi_dev, val);
+				if (put_user(curr_timebase, p))
+					return -EFAULT;
+				return curr_timebase;
+			}
+			break;
+
+		case SNDCTL_TMR_TEMPO:
+			{
+				int val;
+				int ret;
+
+				if (get_user(val, p))
+					return -EFAULT;
+
+				if (val)
+				{
+					if (val < 8)
+						val = 8;
+					if (val > 250)
+						val = 250;
+					if ((ret = mpu_cmd(midi_dev, 0xE0, val)) < 0)
+					{
+						printk(KERN_WARNING "mpu401: Can't set tempo to %d\n", (int) val);
+						return ret;
+					}
+					curr_tempo = val;
+				}
+				if (put_user(curr_tempo, p))
+					return -EFAULT;
+				return curr_tempo;
+			}
+			break;
+
+		case SNDCTL_SEQ_CTRLRATE:
+			{
+				int val;
+				if (get_user(val, p))
+					return -EFAULT;
+
+				if (val != 0)		/* Can't change */
+					return -EINVAL;
+				val = ((curr_tempo * curr_timebase) + 30)/60;
+				if (put_user(val, p))
+					return -EFAULT;
+				return val;
+			}
+			break;
+
+		case SNDCTL_SEQ_GETTIME:
+			if (put_user(curr_ticks, p))
+				return -EFAULT;
+			return curr_ticks;
+
+		case SNDCTL_TMR_METRONOME:
+			if (get_user(metronome_mode, p))
+				return -EFAULT;
+			setup_metronome(midi_dev);
+			return 0;
+
+		default:;
+	}
+	return -EINVAL;
+}
+
+static void mpu_timer_arm(int dev, long time)
+{
+	if (time < 0)
+		time = curr_ticks + 1;
+	else if (time <= curr_ticks)	/* It's the time */
+		return;
+	next_event_time = prev_event_time = time;
+	return;
+}
+
+static struct sound_timer_operations mpu_timer =
+{
+	.owner		= THIS_MODULE,
+	.info		= {"MPU-401 Timer", 0},
+	.priority	= 10,	/* Priority */
+	.devlink	= 0,	/* Local device link */
+	.open		= mpu_timer_open,
+	.close		= mpu_timer_close,
+	.event		= mpu_timer_event,
+	.get_time	= mpu_timer_get_time,
+	.ioctl		= mpu_timer_ioctl,
+	.arm_timer	= mpu_timer_arm
+};
+
+static void mpu_timer_interrupt(void)
+{
+	if (!timer_open)
+		return;
+
+	if (!tmr_running)
+		return;
+
+	curr_clocks++;
+	curr_ticks = clocks2ticks(curr_clocks);
+
+	if (curr_ticks >= next_event_time)
+	{
+		next_event_time = (unsigned long) -1;
+		sequencer_timer(0);
+	}
+}
+
+static void timer_ext_event(struct mpu_config *devc, int event, int parm)
+{
+	int midi_dev = devc->devno;
+
+	if (!devc->timer_flag)
+		return;
+
+	switch (event)
+	{
+		case TMR_CLOCK:
+			printk("<MIDI clk>");
+			break;
+
+		case TMR_START:
+			printk("Ext MIDI start\n");
+			if (!tmr_running)
+			{
+				if (timer_mode & TMR_EXTERNAL)
+				{
+					tmr_running = 1;
+					setup_metronome(midi_dev);
+					next_event_time = 0;
+					STORE(SEQ_START_TIMER());
+				}
+			}
+			break;
+
+		case TMR_STOP:
+			printk("Ext MIDI stop\n");
+			if (timer_mode & TMR_EXTERNAL)
+			{
+				tmr_running = 0;
+				stop_metronome(midi_dev);
+				STORE(SEQ_STOP_TIMER());
+			}
+			break;
+
+		case TMR_CONTINUE:
+			printk("Ext MIDI continue\n");
+			if (timer_mode & TMR_EXTERNAL)
+			{
+				tmr_running = 1;
+				setup_metronome(midi_dev);
+				STORE(SEQ_CONTINUE_TIMER());
+		  	}
+		  	break;
+
+		case TMR_SPP:
+			printk("Songpos: %d\n", parm);
+			if (timer_mode & TMR_EXTERNAL)
+			{
+				STORE(SEQ_SONGPOS(parm));
+			}
+			break;
+	}
+}
+
+static int mpu_timer_init(int midi_dev)
+{
+	struct mpu_config *devc;
+	int n;
+
+	devc = &dev_conf[midi_dev];
+
+	if (timer_initialized)
+		return -1;	/* There is already a similar timer */
+
+	timer_initialized = 1;
+
+	mpu_timer.devlink = midi_dev;
+	dev_conf[midi_dev].timer_flag = 1;
+
+	n = sound_alloc_timerdev();
+	if (n == -1)
+		n = 0;
+	sound_timer_devs[n] = &mpu_timer;
+
+	if (devc->version < 0x20)	/* Original MPU-401 */
+		timer_caps = TMR_INTERNAL | TMR_EXTERNAL | TMR_MODE_FSK | TMR_MODE_MIDI;
+	else
+	{
+		/*
+		 * The version number 2.0 is used (at least) by the
+		 * MusicQuest cards and the Roland Super-MPU.
+		 *
+		 * MusicQuest has given a special meaning to the bits of the
+		 * revision number. The Super-MPU returns 0.
+		 */
+
+		if (devc->revision)
+			timer_caps |= TMR_EXTERNAL | TMR_MODE_MIDI;
+
+		if (devc->revision & 0x02)
+			timer_caps |= TMR_MODE_CLS;
+
+
+		if (devc->revision & 0x40)
+			max_timebase = 10;	/* Has the 216 and 240 ppqn modes */
+	}
+
+	timer_mode = (TMR_INTERNAL | TMR_MODE_MIDI) & timer_caps;
+	return n;
+
+}
+
+EXPORT_SYMBOL(probe_mpu401);
+EXPORT_SYMBOL(attach_mpu401);
+EXPORT_SYMBOL(unload_mpu401);
+
+static struct address_info cfg;
+
+static int io = -1;
+static int irq = -1;
+
+module_param(irq, int, 0);
+module_param(io, int, 0);
+
+static int __init init_mpu401(void)
+{
+	int ret;
+	/* Can be loaded either for module use or to provide functions
+	   to others */
+	if (io != -1 && irq != -1) {
+		struct resource *ports;
+	        cfg.irq = irq;
+		cfg.io_base = io;
+		ports = request_region(io, 2, "mpu401");
+		if (!ports)
+			return -EBUSY;
+		if (probe_mpu401(&cfg, ports) == 0) {
+			release_region(io, 2);
+			return -ENODEV;
+		}
+		if ((ret = attach_mpu401(&cfg, THIS_MODULE)))
+			return ret;
+	}
+	
+	return 0;
+}
+
+static void __exit cleanup_mpu401(void)
+{
+	if (io != -1 && irq != -1) {
+		/* Check for use by, for example, sscape driver */
+		unload_mpu401(&cfg);
+	}
+}
+
+module_init(init_mpu401);
+module_exit(cleanup_mpu401);
+
+#ifndef MODULE
+static int __init setup_mpu401(char *str)
+{
+        /* io, irq */
+	int ints[3];
+	
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+	
+	io = ints[1];
+	irq = ints[2];
+
+	return 1;
+}
+
+__setup("mpu401=", setup_mpu401);
+#endif
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/mpu401.h b/linux/sound/oss/mpu401.h
new file mode 100644
index 0000000..0ad1e9e
--- /dev/null
+++ b/linux/sound/oss/mpu401.h
@@ -0,0 +1,11 @@
+
+/*	From uart401.c */
+int probe_uart401 (struct address_info *hw_config, struct module *owner);
+void unload_uart401 (struct address_info *hw_config);
+
+irqreturn_t uart401intr (int irq, void *dev_id);
+
+/*	From mpu401.c */
+int probe_mpu401(struct address_info *hw_config, struct resource *ports);
+int attach_mpu401(struct address_info * hw_config, struct module *owner);
+void unload_mpu401(struct address_info *hw_info);
diff --git a/linux/sound/oss/msnd.c b/linux/sound/oss/msnd.c
new file mode 100644
index 0000000..c0cc951
--- /dev/null
+++ b/linux/sound/oss/msnd.c
@@ -0,0 +1,413 @@
+/*********************************************************************
+ *
+ * msnd.c - Driver Base
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/vmalloc.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/spinlock.h>
+#include <asm/irq.h>
+#include "msnd.h"
+
+#define LOGNAME			"msnd"
+
+#define MSND_MAX_DEVS		4
+
+static multisound_dev_t		*devs[MSND_MAX_DEVS];
+static int			num_devs;
+
+int msnd_register(multisound_dev_t *dev)
+{
+	int i;
+
+	for (i = 0; i < MSND_MAX_DEVS; ++i)
+		if (devs[i] == NULL)
+			break;
+
+	if (i == MSND_MAX_DEVS)
+		return -ENOMEM;
+
+	devs[i] = dev;
+	++num_devs;
+	return 0;
+}
+
+void msnd_unregister(multisound_dev_t *dev)
+{
+	int i;
+
+	for (i = 0; i < MSND_MAX_DEVS; ++i)
+		if (devs[i] == dev)
+			break;
+
+	if (i == MSND_MAX_DEVS) {
+		printk(KERN_WARNING LOGNAME ": Unregistering unknown device\n");
+		return;
+	}
+
+	devs[i] = NULL;
+	--num_devs;
+}
+
+void msnd_init_queue(void __iomem *base, int start, int size)
+{
+	writew(PCTODSP_BASED(start), base + JQS_wStart);
+	writew(PCTODSP_OFFSET(size) - 1, base + JQS_wSize);
+	writew(0, base + JQS_wHead);
+	writew(0, base + JQS_wTail);
+}
+
+void msnd_fifo_init(msnd_fifo *f)
+{
+	f->data = NULL;
+}
+
+void msnd_fifo_free(msnd_fifo *f)
+{
+	vfree(f->data);
+	f->data = NULL;
+}
+
+int msnd_fifo_alloc(msnd_fifo *f, size_t n)
+{
+	msnd_fifo_free(f);
+	f->data = vmalloc(n);
+	f->n = n;
+	f->tail = 0;
+	f->head = 0;
+	f->len = 0;
+
+	if (!f->data)
+		return -ENOMEM;
+
+	return 0;
+}
+
+void msnd_fifo_make_empty(msnd_fifo *f)
+{
+	f->len = f->tail = f->head = 0;
+}
+
+int msnd_fifo_write_io(msnd_fifo *f, char __iomem *buf, size_t len)
+{
+	int count = 0;
+
+	while ((count < len) && (f->len != f->n)) {
+
+		int nwritten;
+
+		if (f->head <= f->tail) {
+			nwritten = len - count;
+			if (nwritten > f->n - f->tail)
+				nwritten = f->n - f->tail;
+		}
+		else {
+			nwritten = f->head - f->tail;
+			if (nwritten > len - count)
+				nwritten = len - count;
+		}
+
+		memcpy_fromio(f->data + f->tail, buf, nwritten);
+
+		count += nwritten;
+		buf += nwritten;
+		f->len += nwritten;
+		f->tail += nwritten;
+		f->tail %= f->n;
+	}
+
+	return count;
+}
+
+int msnd_fifo_write(msnd_fifo *f, const char *buf, size_t len)
+{
+	int count = 0;
+
+	while ((count < len) && (f->len != f->n)) {
+
+		int nwritten;
+
+		if (f->head <= f->tail) {
+			nwritten = len - count;
+			if (nwritten > f->n - f->tail)
+				nwritten = f->n - f->tail;
+		}
+		else {
+			nwritten = f->head - f->tail;
+			if (nwritten > len - count)
+				nwritten = len - count;
+		}
+
+		memcpy(f->data + f->tail, buf, nwritten);
+
+		count += nwritten;
+		buf += nwritten;
+		f->len += nwritten;
+		f->tail += nwritten;
+		f->tail %= f->n;
+	}
+
+	return count;
+}
+
+int msnd_fifo_read_io(msnd_fifo *f, char __iomem *buf, size_t len)
+{
+	int count = 0;
+
+	while ((count < len) && (f->len > 0)) {
+
+		int nread;
+
+		if (f->tail <= f->head) {
+			nread = len - count;
+			if (nread > f->n - f->head)
+				nread = f->n - f->head;
+		}
+		else {
+			nread = f->tail - f->head;
+			if (nread > len - count)
+				nread = len - count;
+		}
+
+		memcpy_toio(buf, f->data + f->head, nread);
+
+		count += nread;
+		buf += nread;
+		f->len -= nread;
+		f->head += nread;
+		f->head %= f->n;
+	}
+
+	return count;
+}
+
+int msnd_fifo_read(msnd_fifo *f, char *buf, size_t len)
+{
+	int count = 0;
+
+	while ((count < len) && (f->len > 0)) {
+
+		int nread;
+
+		if (f->tail <= f->head) {
+			nread = len - count;
+			if (nread > f->n - f->head)
+				nread = f->n - f->head;
+		}
+		else {
+			nread = f->tail - f->head;
+			if (nread > len - count)
+				nread = len - count;
+		}
+
+		memcpy(buf, f->data + f->head, nread);
+
+		count += nread;
+		buf += nread;
+		f->len -= nread;
+		f->head += nread;
+		f->head %= f->n;
+	}
+
+	return count;
+}
+
+static int msnd_wait_TXDE(multisound_dev_t *dev)
+{
+	register unsigned int io = dev->io;
+	register int timeout = 1000;
+    
+	while(timeout-- > 0)
+		if (msnd_inb(io + HP_ISR) & HPISR_TXDE)
+			return 0;
+
+	return -EIO;
+}
+
+static int msnd_wait_HC0(multisound_dev_t *dev)
+{
+	register unsigned int io = dev->io;
+	register int timeout = 1000;
+
+	while(timeout-- > 0)
+		if (!(msnd_inb(io + HP_CVR) & HPCVR_HC))
+			return 0;
+
+	return -EIO;
+}
+
+int msnd_send_dsp_cmd(multisound_dev_t *dev, BYTE cmd)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (msnd_wait_HC0(dev) == 0) {
+		msnd_outb(cmd, dev->io + HP_CVR);
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return 0;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	printk(KERN_DEBUG LOGNAME ": Send DSP command timeout\n");
+
+	return -EIO;
+}
+
+int msnd_send_word(multisound_dev_t *dev, unsigned char high,
+		   unsigned char mid, unsigned char low)
+{
+	register unsigned int io = dev->io;
+
+	if (msnd_wait_TXDE(dev) == 0) {
+		msnd_outb(high, io + HP_TXH);
+		msnd_outb(mid, io + HP_TXM);
+		msnd_outb(low, io + HP_TXL);
+		return 0;
+	}
+
+	printk(KERN_DEBUG LOGNAME ": Send host word timeout\n");
+
+	return -EIO;
+}
+
+int msnd_upload_host(multisound_dev_t *dev, char *bin, int len)
+{
+	int i;
+
+	if (len % 3 != 0) {
+		printk(KERN_WARNING LOGNAME ": Upload host data not multiple of 3!\n");		
+		return -EINVAL;
+	}
+
+	for (i = 0; i < len; i += 3)
+		if (msnd_send_word(dev, bin[i], bin[i + 1], bin[i + 2]) != 0)
+			return -EIO;
+
+	msnd_inb(dev->io + HP_RXL);
+	msnd_inb(dev->io + HP_CVR);
+
+	return 0;
+}
+
+int msnd_enable_irq(multisound_dev_t *dev)
+{
+	unsigned long flags;
+
+	if (dev->irq_ref++)
+		return 0;
+
+	printk(KERN_DEBUG LOGNAME ": Enabling IRQ\n");
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (msnd_wait_TXDE(dev) == 0) {
+		msnd_outb(msnd_inb(dev->io + HP_ICR) | HPICR_TREQ, dev->io + HP_ICR);
+		if (dev->type == msndClassic)
+			msnd_outb(dev->irqid, dev->io + HP_IRQM);
+		msnd_outb(msnd_inb(dev->io + HP_ICR) & ~HPICR_TREQ, dev->io + HP_ICR);
+		msnd_outb(msnd_inb(dev->io + HP_ICR) | HPICR_RREQ, dev->io + HP_ICR);
+		enable_irq(dev->irq);
+		msnd_init_queue(dev->DSPQ, dev->dspq_data_buff, dev->dspq_buff_size);
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return 0;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	printk(KERN_DEBUG LOGNAME ": Enable IRQ failed\n");
+
+	return -EIO;
+}
+
+int msnd_disable_irq(multisound_dev_t *dev)
+{
+	unsigned long flags;
+
+	if (--dev->irq_ref > 0)
+		return 0;
+
+	if (dev->irq_ref < 0)
+		printk(KERN_DEBUG LOGNAME ": IRQ ref count is %d\n", dev->irq_ref);
+
+	printk(KERN_DEBUG LOGNAME ": Disabling IRQ\n");
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (msnd_wait_TXDE(dev) == 0) {
+		msnd_outb(msnd_inb(dev->io + HP_ICR) & ~HPICR_RREQ, dev->io + HP_ICR);
+		if (dev->type == msndClassic)
+			msnd_outb(HPIRQ_NONE, dev->io + HP_IRQM);
+		disable_irq(dev->irq);
+		spin_unlock_irqrestore(&dev->lock, flags);
+		return 0;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	printk(KERN_DEBUG LOGNAME ": Disable IRQ failed\n");
+
+	return -EIO;
+}
+
+#ifndef LINUX20
+EXPORT_SYMBOL(msnd_register);
+EXPORT_SYMBOL(msnd_unregister);
+
+EXPORT_SYMBOL(msnd_init_queue);
+
+EXPORT_SYMBOL(msnd_fifo_init);
+EXPORT_SYMBOL(msnd_fifo_free);
+EXPORT_SYMBOL(msnd_fifo_alloc);
+EXPORT_SYMBOL(msnd_fifo_make_empty);
+EXPORT_SYMBOL(msnd_fifo_write_io);
+EXPORT_SYMBOL(msnd_fifo_read_io);
+EXPORT_SYMBOL(msnd_fifo_write);
+EXPORT_SYMBOL(msnd_fifo_read);
+
+EXPORT_SYMBOL(msnd_send_dsp_cmd);
+EXPORT_SYMBOL(msnd_send_word);
+EXPORT_SYMBOL(msnd_upload_host);
+
+EXPORT_SYMBOL(msnd_enable_irq);
+EXPORT_SYMBOL(msnd_disable_irq);
+#endif
+
+#ifdef MODULE
+MODULE_AUTHOR				("Andrew Veliath <andrewtv@usa.net>");
+MODULE_DESCRIPTION			("Turtle Beach MultiSound Driver Base");
+MODULE_LICENSE("GPL");
+
+
+int init_module(void)
+{
+	return 0;
+}
+
+void cleanup_module(void)
+{
+}
+#endif
diff --git a/linux/sound/oss/msnd.h b/linux/sound/oss/msnd.h
new file mode 100644
index 0000000..c8be47e
--- /dev/null
+++ b/linux/sound/oss/msnd.h
@@ -0,0 +1,278 @@
+/*********************************************************************
+ *
+ * msnd.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+#ifndef __MSND_H
+#define __MSND_H
+
+#define VERSION			"0.8.3.1"
+
+#define DEFSAMPLERATE		DSP_DEFAULT_SPEED
+#define DEFSAMPLESIZE		AFMT_U8
+#define DEFCHANNELS		1
+
+#define DEFFIFOSIZE		128
+
+#define SNDCARD_MSND		38
+
+#define SRAM_BANK_SIZE		0x8000
+#define SRAM_CNTL_START		0x7F00
+
+#define DSP_BASE_ADDR		0x4000
+#define DSP_BANK_BASE		0x4000
+
+#define	HP_ICR			0x00
+#define	HP_CVR			0x01
+#define	HP_ISR			0x02
+#define	HP_IVR			0x03
+#define HP_NU			0x04
+#define HP_INFO			0x04
+#define	HP_TXH			0x05
+#define	HP_RXH			0x05
+#define	HP_TXM			0x06
+#define	HP_RXM			0x06
+#define	HP_TXL			0x07
+#define	HP_RXL			0x07
+
+#define HP_ICR_DEF		0x00
+#define HP_CVR_DEF		0x12
+#define HP_ISR_DEF		0x06
+#define HP_IVR_DEF		0x0f
+#define HP_NU_DEF		0x00
+
+#define	HP_IRQM			0x09
+
+#define	HPR_BLRC		0x08
+#define	HPR_SPR1		0x09
+#define	HPR_SPR2		0x0A
+#define	HPR_TCL0		0x0B
+#define	HPR_TCL1		0x0C
+#define	HPR_TCL2		0x0D
+#define	HPR_TCL3		0x0E
+#define	HPR_TCL4		0x0F
+
+#define	HPICR_INIT		0x80
+#define HPICR_HM1		0x40
+#define HPICR_HM0		0x20
+#define HPICR_HF1		0x10
+#define HPICR_HF0		0x08
+#define	HPICR_TREQ		0x02
+#define	HPICR_RREQ		0x01
+
+#define HPCVR_HC		0x80
+
+#define	HPISR_HREQ		0x80
+#define HPISR_DMA		0x40
+#define HPISR_HF3		0x10
+#define HPISR_HF2		0x08
+#define	HPISR_TRDY		0x04
+#define	HPISR_TXDE		0x02
+#define	HPISR_RXDF		0x01
+
+#define	HPIO_290		0
+#define	HPIO_260		1
+#define	HPIO_250		2
+#define	HPIO_240		3
+#define	HPIO_230		4
+#define	HPIO_220		5
+#define	HPIO_210		6
+#define	HPIO_3E0		7
+
+#define	HPMEM_NONE		0
+#define	HPMEM_B000		1
+#define	HPMEM_C800		2
+#define	HPMEM_D000		3
+#define	HPMEM_D400		4
+#define	HPMEM_D800		5
+#define	HPMEM_E000		6
+#define	HPMEM_E800		7
+
+#define	HPIRQ_NONE		0
+#define HPIRQ_5			1
+#define HPIRQ_7			2
+#define HPIRQ_9			3
+#define HPIRQ_10		4
+#define HPIRQ_11		5
+#define HPIRQ_12		6
+#define HPIRQ_15		7
+
+#define	HIMT_PLAY_DONE		0x00
+#define	HIMT_RECORD_DONE	0x01
+#define	HIMT_MIDI_EOS		0x02
+#define	HIMT_MIDI_OUT		0x03
+
+#define	HIMT_MIDI_IN_UCHAR	0x0E
+#define	HIMT_DSP		0x0F
+
+#define	HDEX_BASE	       	0x92
+#define	HDEX_PLAY_START		(0 + HDEX_BASE)
+#define	HDEX_PLAY_STOP		(1 + HDEX_BASE)
+#define	HDEX_PLAY_PAUSE		(2 + HDEX_BASE)
+#define	HDEX_PLAY_RESUME	(3 + HDEX_BASE)
+#define	HDEX_RECORD_START	(4 + HDEX_BASE)
+#define	HDEX_RECORD_STOP	(5 + HDEX_BASE)
+#define	HDEX_MIDI_IN_START 	(6 + HDEX_BASE)
+#define	HDEX_MIDI_IN_STOP	(7 + HDEX_BASE)
+#define	HDEX_MIDI_OUT_START	(8 + HDEX_BASE)
+#define	HDEX_MIDI_OUT_STOP	(9 + HDEX_BASE)
+#define	HDEX_AUX_REQ		(10 + HDEX_BASE)
+
+#define HIWORD(l)		((WORD)((((DWORD)(l)) >> 16) & 0xFFFF))
+#define LOWORD(l)		((WORD)(DWORD)(l))
+#define HIBYTE(w)		((BYTE)(((WORD)(w) >> 8) & 0xFF))
+#define LOBYTE(w)		((BYTE)(w))
+#define MAKELONG(low,hi)	((long)(((WORD)(low))|(((DWORD)((WORD)(hi)))<<16)))
+#define MAKEWORD(low,hi)	((WORD)(((BYTE)(low))|(((WORD)((BYTE)(hi)))<<8)))
+
+#define PCTODSP_OFFSET(w)	(USHORT)((w)/2)
+#define PCTODSP_BASED(w)	(USHORT)(((w)/2) + DSP_BASE_ADDR)
+#define DSPTOPC_BASED(w)	(((w) - DSP_BASE_ADDR) * 2)
+
+#ifdef SLOWIO
+#define msnd_outb			outb_p
+#define msnd_inb			inb_p
+#else
+#define msnd_outb			outb
+#define msnd_inb			inb
+#endif
+
+/* JobQueueStruct */
+#define JQS_wStart		0x00
+#define JQS_wSize		0x02
+#define JQS_wHead		0x04
+#define JQS_wTail		0x06
+#define JQS__size		0x08
+
+/* DAQueueDataStruct */
+#define DAQDS_wStart		0x00
+#define DAQDS_wSize		0x02
+#define DAQDS_wFormat		0x04
+#define DAQDS_wSampleSize	0x06
+#define DAQDS_wChannels		0x08
+#define DAQDS_wSampleRate	0x0A
+#define DAQDS_wIntMsg		0x0C
+#define DAQDS_wFlags		0x0E
+#define DAQDS__size		0x10
+
+typedef u8			BYTE;
+typedef u16			USHORT;
+typedef u16			WORD;
+typedef u32			DWORD;
+typedef void __iomem *		LPDAQD;
+
+/* Generic FIFO */
+typedef struct {
+	size_t n, len;
+	char *data;
+	int head, tail;
+} msnd_fifo;
+
+typedef struct multisound_dev {
+	/* Linux device info */
+	char *name;
+	int dsp_minor, mixer_minor;
+	int ext_midi_dev, hdr_midi_dev;
+
+	/* Hardware resources */
+	int io, numio;
+	int memid, irqid;
+	int irq, irq_ref;
+	unsigned char info;
+	void __iomem *base;
+
+	/* Motorola 56k DSP SMA */
+	void __iomem *SMA;
+	void __iomem *DAPQ, *DARQ, *MODQ, *MIDQ, *DSPQ;
+	void __iomem *pwDSPQData, *pwMIDQData, *pwMODQData;
+	int dspq_data_buff, dspq_buff_size;
+
+	/* State variables */
+	enum { msndClassic, msndPinnacle } type;
+	fmode_t mode;
+	unsigned long flags;
+#define F_RESETTING			0
+#define F_HAVEDIGITAL			1
+#define F_AUDIO_WRITE_INUSE		2
+#define F_WRITING			3
+#define F_WRITEBLOCK			4
+#define F_WRITEFLUSH			5
+#define F_AUDIO_READ_INUSE		6
+#define F_READING			7
+#define F_READBLOCK			8
+#define F_EXT_MIDI_INUSE		9
+#define F_HDR_MIDI_INUSE		10
+#define F_DISABLE_WRITE_NDELAY		11
+	wait_queue_head_t writeblock;
+	wait_queue_head_t readblock;
+	wait_queue_head_t writeflush;
+	spinlock_t lock;
+	int nresets;
+	unsigned long recsrc;
+	int left_levels[32];
+	int right_levels[32];
+	int mixer_mod_count;
+	int calibrate_signal;
+	int play_sample_size, play_sample_rate, play_channels;
+	int play_ndelay;
+	int rec_sample_size, rec_sample_rate, rec_channels;
+	int rec_ndelay;
+	BYTE bCurrentMidiPatch;
+
+	/* Digital audio FIFOs */
+	msnd_fifo DAPF, DARF;
+	int fifosize;
+	int last_playbank, last_recbank;
+
+	/* MIDI in callback */
+	void (*midi_in_interrupt)(struct multisound_dev *);
+} multisound_dev_t;
+
+#ifndef mdelay
+#  define mdelay(a)		udelay((a) * 1000)
+#endif
+
+int				msnd_register(multisound_dev_t *dev);
+void				msnd_unregister(multisound_dev_t *dev);
+
+void				msnd_init_queue(void __iomem *, int start, int size);
+
+void				msnd_fifo_init(msnd_fifo *f);
+void				msnd_fifo_free(msnd_fifo *f);
+int				msnd_fifo_alloc(msnd_fifo *f, size_t n);
+void				msnd_fifo_make_empty(msnd_fifo *f);
+int				msnd_fifo_write_io(msnd_fifo *f, char __iomem *buf, size_t len);
+int				msnd_fifo_read_io(msnd_fifo *f, char __iomem *buf, size_t len);
+int				msnd_fifo_write(msnd_fifo *f, const char *buf, size_t len);
+int				msnd_fifo_read(msnd_fifo *f, char *buf, size_t len);
+
+int				msnd_send_dsp_cmd(multisound_dev_t *dev, BYTE cmd);
+int				msnd_send_word(multisound_dev_t *dev, unsigned char high,
+					       unsigned char mid, unsigned char low);
+int				msnd_upload_host(multisound_dev_t *dev, char *bin, int len);
+int				msnd_enable_irq(multisound_dev_t *dev);
+int				msnd_disable_irq(multisound_dev_t *dev);
+
+#endif /* __MSND_H */
diff --git a/linux/sound/oss/msnd_classic.c b/linux/sound/oss/msnd_classic.c
new file mode 100644
index 0000000..3b23a09
--- /dev/null
+++ b/linux/sound/oss/msnd_classic.c
@@ -0,0 +1,3 @@
+/* The work is in msnd_pinnacle.c, just define MSND_CLASSIC before it. */
+#define MSND_CLASSIC
+#include "msnd_pinnacle.c"
diff --git a/linux/sound/oss/msnd_classic.h b/linux/sound/oss/msnd_classic.h
new file mode 100644
index 0000000..1a17dde
--- /dev/null
+++ b/linux/sound/oss/msnd_classic.h
@@ -0,0 +1,185 @@
+/*********************************************************************
+ *
+ * msnd_classic.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 
+ ********************************************************************/
+#ifndef __MSND_CLASSIC_H
+#define __MSND_CLASSIC_H
+
+
+#define DSP_NUMIO				0x10
+
+#define	HP_MEMM					0x08
+
+#define	HP_BITM					0x0E
+#define	HP_WAIT					0x0D
+#define	HP_DSPR					0x0A
+#define	HP_PROR					0x0B
+#define	HP_BLKS					0x0C
+
+#define	HPPRORESET_OFF				0
+#define HPPRORESET_ON				1
+
+#define HPDSPRESET_OFF				0
+#define HPDSPRESET_ON				1
+
+#define HPBLKSEL_0				0
+#define HPBLKSEL_1				1
+
+#define HPWAITSTATE_0				0
+#define HPWAITSTATE_1				1
+
+#define HPBITMODE_16				0
+#define HPBITMODE_8				1
+
+#define	HIDSP_INT_PLAY_UNDER			0x00
+#define	HIDSP_INT_RECORD_OVER			0x01
+#define	HIDSP_INPUT_CLIPPING			0x02
+#define	HIDSP_MIDI_IN_OVER			0x10
+#define	HIDSP_MIDI_OVERRUN_ERR  0x13
+
+#define	HDEXAR_CLEAR_PEAKS			1
+#define	HDEXAR_IN_SET_POTS			2
+#define	HDEXAR_AUX_SET_POTS			3
+#define	HDEXAR_CAL_A_TO_D			4
+#define	HDEXAR_RD_EXT_DSP_BITS			5
+
+#define TIME_PRO_RESET_DONE			0x028A
+#define TIME_PRO_SYSEX				0x0040
+#define TIME_PRO_RESET				0x0032
+
+#define AGND					0x01
+#define SIGNAL					0x02
+
+#define EXT_DSP_BIT_DCAL			0x0001
+#define EXT_DSP_BIT_MIDI_CON			0x0002
+
+#define BUFFSIZE				0x8000
+#define HOSTQ_SIZE				0x40
+
+#define SRAM_CNTL_START				0x7F00
+#define SMA_STRUCT_START			0x7F40
+
+#define DAP_BUFF_SIZE				0x2400
+#define DAR_BUFF_SIZE				0x2000
+
+#define DAPQ_STRUCT_SIZE			0x10
+#define DARQ_STRUCT_SIZE			0x10
+#define DAPQ_BUFF_SIZE				(3 * 0x10)
+#define DARQ_BUFF_SIZE				(3 * 0x10)
+#define MODQ_BUFF_SIZE				0x400
+#define MIDQ_BUFF_SIZE				0x200
+#define DSPQ_BUFF_SIZE				0x40
+
+#define DAPQ_DATA_BUFF				0x6C00
+#define DARQ_DATA_BUFF				0x6C30
+#define MODQ_DATA_BUFF				0x6C60
+#define MIDQ_DATA_BUFF				0x7060
+#define DSPQ_DATA_BUFF				0x7260
+
+#define DAPQ_OFFSET				SRAM_CNTL_START
+#define DARQ_OFFSET				(SRAM_CNTL_START + 0x08)
+#define MODQ_OFFSET				(SRAM_CNTL_START + 0x10)
+#define MIDQ_OFFSET				(SRAM_CNTL_START + 0x18)
+#define DSPQ_OFFSET				(SRAM_CNTL_START + 0x20)
+
+#define MOP_SYNTH				0x10
+#define MOP_EXTOUT				0x32
+#define MOP_EXTTHRU				0x02
+#define MOP_OUTMASK				0x01
+
+#define MIP_EXTIN				0x01
+#define MIP_SYNTH				0x00
+#define MIP_INMASK				0x32
+
+/* Classic SMA Common Data */
+#define SMA_wCurrPlayBytes			0x0000
+#define SMA_wCurrRecordBytes			0x0002
+#define SMA_wCurrPlayVolLeft			0x0004
+#define SMA_wCurrPlayVolRight			0x0006
+#define SMA_wCurrInVolLeft			0x0008
+#define SMA_wCurrInVolRight			0x000a
+#define SMA_wUser_3				0x000c
+#define SMA_wUser_4				0x000e
+#define SMA_dwUser_5				0x0010
+#define SMA_dwUser_6				0x0014
+#define SMA_wUser_7				0x0018
+#define SMA_wReserved_A				0x001a
+#define SMA_wReserved_B				0x001c
+#define SMA_wReserved_C				0x001e
+#define SMA_wReserved_D				0x0020
+#define SMA_wReserved_E				0x0022
+#define SMA_wReserved_F				0x0024
+#define SMA_wReserved_G				0x0026
+#define SMA_wReserved_H				0x0028
+#define SMA_wCurrDSPStatusFlags			0x002a
+#define SMA_wCurrHostStatusFlags		0x002c
+#define SMA_wCurrInputTagBits			0x002e
+#define SMA_wCurrLeftPeak			0x0030
+#define SMA_wCurrRightPeak			0x0032
+#define SMA_wExtDSPbits				0x0034
+#define SMA_bExtHostbits			0x0036
+#define SMA_bBoardLevel				0x0037
+#define SMA_bInPotPosRight			0x0038
+#define SMA_bInPotPosLeft			0x0039
+#define SMA_bAuxPotPosRight			0x003a
+#define SMA_bAuxPotPosLeft			0x003b
+#define SMA_wCurrMastVolLeft			0x003c
+#define SMA_wCurrMastVolRight			0x003e
+#define SMA_bUser_12				0x0040
+#define SMA_bUser_13				0x0041
+#define SMA_wUser_14				0x0042
+#define SMA_wUser_15				0x0044
+#define SMA_wCalFreqAtoD			0x0046
+#define SMA_wUser_16				0x0048
+#define SMA_wUser_17				0x004a
+#define SMA__size				0x004c
+
+#ifdef HAVE_DSPCODEH
+#  include "msndperm.c"
+#  include "msndinit.c"
+#  define PERMCODE		msndperm
+#  define INITCODE		msndinit
+#  define PERMCODESIZE		sizeof(msndperm)
+#  define INITCODESIZE		sizeof(msndinit)
+#else
+#  ifndef CONFIG_MSNDCLAS_INIT_FILE
+#    define CONFIG_MSNDCLAS_INIT_FILE				\
+				"/etc/sound/msndinit.bin"
+#  endif
+#  ifndef CONFIG_MSNDCLAS_PERM_FILE
+#    define CONFIG_MSNDCLAS_PERM_FILE				\
+				"/etc/sound/msndperm.bin"
+#  endif
+#  define PERMCODEFILE		CONFIG_MSNDCLAS_PERM_FILE
+#  define INITCODEFILE		CONFIG_MSNDCLAS_INIT_FILE
+#  define PERMCODE		dspini
+#  define INITCODE		permini
+#  define PERMCODESIZE		sizeof_dspini
+#  define INITCODESIZE		sizeof_permini
+#endif
+#define LONGNAME		"MultiSound (Classic/Monterey/Tahiti)"
+
+#endif /* __MSND_CLASSIC_H */
diff --git a/linux/sound/oss/msnd_pinnacle.c b/linux/sound/oss/msnd_pinnacle.c
new file mode 100644
index 0000000..7b5c77b
--- /dev/null
+++ b/linux/sound/oss/msnd_pinnacle.c
@@ -0,0 +1,1931 @@
+/*********************************************************************
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ * Linux 2.0/2.2 Version
+ *
+ * msnd_pinnacle.c / msnd_classic.c
+ *
+ * -- If MSND_CLASSIC is defined:
+ *
+ *     -> driver for Turtle Beach Classic/Monterey/Tahiti
+ *
+ * -- Else
+ *
+ *     -> driver for Turtle Beach Pinnacle/Fiji
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * 12-3-2000  Modified IO port validation  Steve Sycamore
+ *
+ ********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/gfp.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+#include "sound_config.h"
+#include "sound_firmware.h"
+#ifdef MSND_CLASSIC
+# ifndef __alpha__
+#  define SLOWIO
+# endif
+#endif
+#include "msnd.h"
+#ifdef MSND_CLASSIC
+#  ifdef CONFIG_MSNDCLAS_HAVE_BOOT
+#    define HAVE_DSPCODEH
+#  endif
+#  include "msnd_classic.h"
+#  define LOGNAME			"msnd_classic"
+#else
+#  ifdef CONFIG_MSNDPIN_HAVE_BOOT
+#    define HAVE_DSPCODEH
+#  endif
+#  include "msnd_pinnacle.h"
+#  define LOGNAME			"msnd_pinnacle"
+#endif
+
+#ifndef CONFIG_MSND_WRITE_NDELAY
+#  define CONFIG_MSND_WRITE_NDELAY	1
+#endif
+
+#define get_play_delay_jiffies(size)	((size) * HZ *			\
+					 dev.play_sample_size / 8 /	\
+					 dev.play_sample_rate /		\
+					 dev.play_channels)
+
+#define get_rec_delay_jiffies(size)	((size) * HZ *			\
+					 dev.rec_sample_size / 8 /	\
+					 dev.rec_sample_rate /		\
+					 dev.rec_channels)
+
+static DEFINE_MUTEX(msnd_pinnacle_mutex);
+static multisound_dev_t			dev;
+
+#ifndef HAVE_DSPCODEH
+static char				*dspini, *permini;
+static int				sizeof_dspini, sizeof_permini;
+#endif
+
+static int				dsp_full_reset(void);
+static void				dsp_write_flush(void);
+
+static __inline__ int chk_send_dsp_cmd(multisound_dev_t *dev, register BYTE cmd)
+{
+	if (msnd_send_dsp_cmd(dev, cmd) == 0)
+		return 0;
+	dsp_full_reset();
+	return msnd_send_dsp_cmd(dev, cmd);
+}
+
+static void reset_play_queue(void)
+{
+	int n;
+	LPDAQD lpDAQ;
+
+	dev.last_playbank = -1;
+	writew(PCTODSP_OFFSET(0 * DAQDS__size), dev.DAPQ + JQS_wHead);
+	writew(PCTODSP_OFFSET(0 * DAQDS__size), dev.DAPQ + JQS_wTail);
+
+	for (n = 0, lpDAQ = dev.base + DAPQ_DATA_BUFF; n < 3; ++n, lpDAQ += DAQDS__size) {
+		writew(PCTODSP_BASED((DWORD)(DAP_BUFF_SIZE * n)), lpDAQ + DAQDS_wStart);
+		writew(0, lpDAQ + DAQDS_wSize);
+		writew(1, lpDAQ + DAQDS_wFormat);
+		writew(dev.play_sample_size, lpDAQ + DAQDS_wSampleSize);
+		writew(dev.play_channels, lpDAQ + DAQDS_wChannels);
+		writew(dev.play_sample_rate, lpDAQ + DAQDS_wSampleRate);
+		writew(HIMT_PLAY_DONE * 0x100 + n, lpDAQ + DAQDS_wIntMsg);
+		writew(n, lpDAQ + DAQDS_wFlags);
+	}
+}
+
+static void reset_record_queue(void)
+{
+	int n;
+	LPDAQD lpDAQ;
+	unsigned long flags;
+
+	dev.last_recbank = 2;
+	writew(PCTODSP_OFFSET(0 * DAQDS__size), dev.DARQ + JQS_wHead);
+	writew(PCTODSP_OFFSET(dev.last_recbank * DAQDS__size), dev.DARQ + JQS_wTail);
+
+	/* Critical section: bank 1 access */
+	spin_lock_irqsave(&dev.lock, flags);
+	msnd_outb(HPBLKSEL_1, dev.io + HP_BLKS);
+	memset_io(dev.base, 0, DAR_BUFF_SIZE * 3);
+	msnd_outb(HPBLKSEL_0, dev.io + HP_BLKS);
+	spin_unlock_irqrestore(&dev.lock, flags);
+
+	for (n = 0, lpDAQ = dev.base + DARQ_DATA_BUFF; n < 3; ++n, lpDAQ += DAQDS__size) {
+		writew(PCTODSP_BASED((DWORD)(DAR_BUFF_SIZE * n)) + 0x4000, lpDAQ + DAQDS_wStart);
+		writew(DAR_BUFF_SIZE, lpDAQ + DAQDS_wSize);
+		writew(1, lpDAQ + DAQDS_wFormat);
+		writew(dev.rec_sample_size, lpDAQ + DAQDS_wSampleSize);
+		writew(dev.rec_channels, lpDAQ + DAQDS_wChannels);
+		writew(dev.rec_sample_rate, lpDAQ + DAQDS_wSampleRate);
+		writew(HIMT_RECORD_DONE * 0x100 + n, lpDAQ + DAQDS_wIntMsg);
+		writew(n, lpDAQ + DAQDS_wFlags);
+	}
+}
+
+static void reset_queues(void)
+{
+	if (dev.mode & FMODE_WRITE) {
+		msnd_fifo_make_empty(&dev.DAPF);
+		reset_play_queue();
+	}
+	if (dev.mode & FMODE_READ) {
+		msnd_fifo_make_empty(&dev.DARF);
+		reset_record_queue();
+	}
+}
+
+static int dsp_set_format(struct file *file, int val)
+{
+	int data, i;
+	LPDAQD lpDAQ, lpDARQ;
+
+	lpDAQ = dev.base + DAPQ_DATA_BUFF;
+	lpDARQ = dev.base + DARQ_DATA_BUFF;
+
+	switch (val) {
+	case AFMT_U8:
+	case AFMT_S16_LE:
+		data = val;
+		break;
+	default:
+		data = DEFSAMPLESIZE;
+		break;
+	}
+
+	for (i = 0; i < 3; ++i, lpDAQ += DAQDS__size, lpDARQ += DAQDS__size) {
+		if (file->f_mode & FMODE_WRITE)
+			writew(data, lpDAQ + DAQDS_wSampleSize);
+		if (file->f_mode & FMODE_READ)
+			writew(data, lpDARQ + DAQDS_wSampleSize);
+	}
+	if (file->f_mode & FMODE_WRITE)
+		dev.play_sample_size = data;
+	if (file->f_mode & FMODE_READ)
+		dev.rec_sample_size = data;
+
+	return data;
+}
+
+static int dsp_get_frag_size(void)
+{
+	int size;
+	size = dev.fifosize / 4;
+	if (size > 32 * 1024)
+		size = 32 * 1024;
+	return size;
+}
+
+static int dsp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int val, i, data, tmp;
+	LPDAQD lpDAQ, lpDARQ;
+        audio_buf_info abinfo;
+	unsigned long flags;
+	int __user *p = (int __user *)arg;
+
+	lpDAQ = dev.base + DAPQ_DATA_BUFF;
+	lpDARQ = dev.base + DARQ_DATA_BUFF;
+
+	switch (cmd) {
+	case SNDCTL_DSP_SUBDIVIDE:
+	case SNDCTL_DSP_SETFRAGMENT:
+	case SNDCTL_DSP_SETDUPLEX:
+	case SNDCTL_DSP_POST:
+		return 0;
+
+	case SNDCTL_DSP_GETIPTR:
+	case SNDCTL_DSP_GETOPTR:
+	case SNDCTL_DSP_MAPINBUF:
+	case SNDCTL_DSP_MAPOUTBUF:
+		return -EINVAL;
+
+	case SNDCTL_DSP_GETOSPACE:
+		if (!(file->f_mode & FMODE_WRITE))
+			return -EINVAL;
+		spin_lock_irqsave(&dev.lock, flags);
+		abinfo.fragsize = dsp_get_frag_size();
+                abinfo.bytes = dev.DAPF.n - dev.DAPF.len;
+                abinfo.fragstotal = dev.DAPF.n / abinfo.fragsize;
+                abinfo.fragments = abinfo.bytes / abinfo.fragsize;
+		spin_unlock_irqrestore(&dev.lock, flags);
+		return copy_to_user((void __user *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0;
+
+	case SNDCTL_DSP_GETISPACE:
+		if (!(file->f_mode & FMODE_READ))
+			return -EINVAL;
+		spin_lock_irqsave(&dev.lock, flags);
+		abinfo.fragsize = dsp_get_frag_size();
+                abinfo.bytes = dev.DARF.n - dev.DARF.len;
+                abinfo.fragstotal = dev.DARF.n / abinfo.fragsize;
+                abinfo.fragments = abinfo.bytes / abinfo.fragsize;
+		spin_unlock_irqrestore(&dev.lock, flags);
+		return copy_to_user((void __user *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0;
+
+	case SNDCTL_DSP_RESET:
+		dev.nresets = 0;
+		reset_queues();
+		return 0;
+
+	case SNDCTL_DSP_SYNC:
+		dsp_write_flush();
+		return 0;
+
+	case SNDCTL_DSP_GETBLKSIZE:
+		tmp = dsp_get_frag_size();
+		if (put_user(tmp, p))
+                        return -EFAULT;
+		return 0;
+
+	case SNDCTL_DSP_GETFMTS:
+		val = AFMT_S16_LE | AFMT_U8;
+		if (put_user(val, p))
+			return -EFAULT;
+		return 0;
+
+	case SNDCTL_DSP_SETFMT:
+		if (get_user(val, p))
+			return -EFAULT;
+
+		if (file->f_mode & FMODE_WRITE)
+			data = val == AFMT_QUERY
+				? dev.play_sample_size
+				: dsp_set_format(file, val);
+		else
+			data = val == AFMT_QUERY
+				? dev.rec_sample_size
+				: dsp_set_format(file, val);
+
+		if (put_user(data, p))
+			return -EFAULT;
+		return 0;
+
+	case SNDCTL_DSP_NONBLOCK:
+		if (!test_bit(F_DISABLE_WRITE_NDELAY, &dev.flags) &&
+		    file->f_mode & FMODE_WRITE)
+			dev.play_ndelay = 1;
+		if (file->f_mode & FMODE_READ)
+			dev.rec_ndelay = 1;
+		return 0;
+
+	case SNDCTL_DSP_GETCAPS:
+		val = DSP_CAP_DUPLEX | DSP_CAP_BATCH;
+		if (put_user(val, p))
+			return -EFAULT;
+		return 0;
+
+	case SNDCTL_DSP_SPEED:
+		if (get_user(val, p))
+			return -EFAULT;
+
+		if (val < 8000)
+			val = 8000;
+
+		if (val > 48000)
+			val = 48000;
+
+		data = val;
+
+		for (i = 0; i < 3; ++i, lpDAQ += DAQDS__size, lpDARQ += DAQDS__size) {
+			if (file->f_mode & FMODE_WRITE)
+				writew(data, lpDAQ + DAQDS_wSampleRate);
+			if (file->f_mode & FMODE_READ)
+				writew(data, lpDARQ + DAQDS_wSampleRate);
+		}
+		if (file->f_mode & FMODE_WRITE)
+			dev.play_sample_rate = data;
+		if (file->f_mode & FMODE_READ)
+			dev.rec_sample_rate = data;
+
+		if (put_user(data, p))
+			return -EFAULT;
+		return 0;
+
+	case SNDCTL_DSP_CHANNELS:
+	case SNDCTL_DSP_STEREO:
+		if (get_user(val, p))
+			return -EFAULT;
+
+		if (cmd == SNDCTL_DSP_CHANNELS) {
+			switch (val) {
+			case 1:
+			case 2:
+				data = val;
+				break;
+			default:
+				val = data = 2;
+				break;
+			}
+		} else {
+			switch (val) {
+			case 0:
+				data = 1;
+				break;
+			default:
+				val = 1;
+			case 1:
+				data = 2;
+				break;
+			}
+		}
+
+		for (i = 0; i < 3; ++i, lpDAQ += DAQDS__size, lpDARQ += DAQDS__size) {
+			if (file->f_mode & FMODE_WRITE)
+				writew(data, lpDAQ + DAQDS_wChannels);
+			if (file->f_mode & FMODE_READ)
+				writew(data, lpDARQ + DAQDS_wChannels);
+		}
+		if (file->f_mode & FMODE_WRITE)
+			dev.play_channels = data;
+		if (file->f_mode & FMODE_READ)
+			dev.rec_channels = data;
+
+		if (put_user(val, p))
+			return -EFAULT;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int mixer_get(int d)
+{
+	if (d > 31)
+		return -EINVAL;
+
+	switch (d) {
+	case SOUND_MIXER_VOLUME:
+	case SOUND_MIXER_PCM:
+	case SOUND_MIXER_LINE:
+	case SOUND_MIXER_IMIX:
+	case SOUND_MIXER_LINE1:
+#ifndef MSND_CLASSIC
+	case SOUND_MIXER_MIC:
+	case SOUND_MIXER_SYNTH:
+#endif
+		return (dev.left_levels[d] >> 8) * 100 / 0xff | 
+			(((dev.right_levels[d] >> 8) * 100 / 0xff) << 8);
+	default:
+		return 0;
+	}
+}
+
+#define update_volm(a,b)						\
+	writew((dev.left_levels[a] >> 1) *				\
+	       readw(dev.SMA + SMA_wCurrMastVolLeft) / 0xffff,	\
+	       dev.SMA + SMA_##b##Left);				\
+	writew((dev.right_levels[a] >> 1)  *			\
+	       readw(dev.SMA + SMA_wCurrMastVolRight) / 0xffff,	\
+	       dev.SMA + SMA_##b##Right);
+
+#define update_potm(d,s,ar)						\
+	writeb((dev.left_levels[d] >> 8) *				\
+	       readw(dev.SMA + SMA_wCurrMastVolLeft) / 0xffff,	\
+	       dev.SMA + SMA_##s##Left);				\
+	writeb((dev.right_levels[d] >> 8) *				\
+	       readw(dev.SMA + SMA_wCurrMastVolRight) / 0xffff,	\
+	       dev.SMA + SMA_##s##Right);				\
+	if (msnd_send_word(&dev, 0, 0, ar) == 0)			\
+		chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+
+#define update_pot(d,s,ar)				\
+	writeb(dev.left_levels[d] >> 8,		\
+	       dev.SMA + SMA_##s##Left);		\
+	writeb(dev.right_levels[d] >> 8,		\
+	       dev.SMA + SMA_##s##Right);		\
+	if (msnd_send_word(&dev, 0, 0, ar) == 0)	\
+		chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+
+static int mixer_set(int d, int value)
+{
+	int left = value & 0x000000ff;
+	int right = (value & 0x0000ff00) >> 8;
+	int bLeft, bRight;
+	int wLeft, wRight;
+	int updatemaster = 0;
+
+	if (d > 31)
+		return -EINVAL;
+
+	bLeft = left * 0xff / 100;
+	wLeft = left * 0xffff / 100;
+
+	bRight = right * 0xff / 100;
+	wRight = right * 0xffff / 100;
+
+	dev.left_levels[d] = wLeft;
+	dev.right_levels[d] = wRight;
+
+	switch (d) {
+		/* master volume unscaled controls */
+	case SOUND_MIXER_LINE:			/* line pot control */
+		/* scaled by IMIX in digital mix */
+		writeb(bLeft, dev.SMA + SMA_bInPotPosLeft);
+		writeb(bRight, dev.SMA + SMA_bInPotPosRight);
+		if (msnd_send_word(&dev, 0, 0, HDEXAR_IN_SET_POTS) == 0)
+			chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+		break;
+#ifndef MSND_CLASSIC
+	case SOUND_MIXER_MIC:			/* mic pot control */
+		/* scaled by IMIX in digital mix */
+		writeb(bLeft, dev.SMA + SMA_bMicPotPosLeft);
+		writeb(bRight, dev.SMA + SMA_bMicPotPosRight);
+		if (msnd_send_word(&dev, 0, 0, HDEXAR_MIC_SET_POTS) == 0)
+			chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+		break;
+#endif
+	case SOUND_MIXER_VOLUME:		/* master volume */
+		writew(wLeft, dev.SMA + SMA_wCurrMastVolLeft);
+		writew(wRight, dev.SMA + SMA_wCurrMastVolRight);
+		/* fall through */
+
+	case SOUND_MIXER_LINE1:			/* aux pot control */
+		/* scaled by master volume */
+		/* fall through */
+
+		/* digital controls */
+	case SOUND_MIXER_SYNTH:			/* synth vol (dsp mix) */
+	case SOUND_MIXER_PCM:			/* pcm vol (dsp mix) */
+	case SOUND_MIXER_IMIX:			/* input monitor (dsp mix) */
+		/* scaled by master volume */
+		updatemaster = 1;
+		break;
+
+	default:
+		return 0;
+	}
+
+	if (updatemaster) {
+		/* update master volume scaled controls */
+		update_volm(SOUND_MIXER_PCM, wCurrPlayVol);
+		update_volm(SOUND_MIXER_IMIX, wCurrInVol);
+#ifndef MSND_CLASSIC
+		update_volm(SOUND_MIXER_SYNTH, wCurrMHdrVol);
+#endif
+		update_potm(SOUND_MIXER_LINE1, bAuxPotPos, HDEXAR_AUX_SET_POTS);
+	}
+
+	return mixer_get(d);
+}
+
+static void mixer_setup(void)
+{
+	update_pot(SOUND_MIXER_LINE, bInPotPos, HDEXAR_IN_SET_POTS);
+	update_potm(SOUND_MIXER_LINE1, bAuxPotPos, HDEXAR_AUX_SET_POTS);
+	update_volm(SOUND_MIXER_PCM, wCurrPlayVol);
+	update_volm(SOUND_MIXER_IMIX, wCurrInVol);
+#ifndef MSND_CLASSIC
+	update_pot(SOUND_MIXER_MIC, bMicPotPos, HDEXAR_MIC_SET_POTS);
+	update_volm(SOUND_MIXER_SYNTH, wCurrMHdrVol);
+#endif
+}
+
+static unsigned long set_recsrc(unsigned long recsrc)
+{
+	if (dev.recsrc == recsrc)
+		return dev.recsrc;
+#ifdef HAVE_NORECSRC
+	else if (recsrc == 0)
+		dev.recsrc = 0;
+#endif
+	else
+		dev.recsrc ^= recsrc;
+
+#ifndef MSND_CLASSIC
+	if (dev.recsrc & SOUND_MASK_IMIX) {
+		if (msnd_send_word(&dev, 0, 0, HDEXAR_SET_ANA_IN) == 0)
+			chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+	}
+	else if (dev.recsrc & SOUND_MASK_SYNTH) {
+		if (msnd_send_word(&dev, 0, 0, HDEXAR_SET_SYNTH_IN) == 0)
+			chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+	}
+	else if ((dev.recsrc & SOUND_MASK_DIGITAL1) && test_bit(F_HAVEDIGITAL, &dev.flags)) {
+		if (msnd_send_word(&dev, 0, 0, HDEXAR_SET_DAT_IN) == 0)
+      			chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+	}
+	else {
+#ifdef HAVE_NORECSRC
+		/* Select no input (?) */
+		dev.recsrc = 0;
+#else
+		dev.recsrc = SOUND_MASK_IMIX;
+		if (msnd_send_word(&dev, 0, 0, HDEXAR_SET_ANA_IN) == 0)
+			chk_send_dsp_cmd(&dev, HDEX_AUX_REQ);
+#endif
+	}
+#endif /* MSND_CLASSIC */
+
+	return dev.recsrc;
+}
+
+static unsigned long force_recsrc(unsigned long recsrc)
+{
+	dev.recsrc = 0;
+	return set_recsrc(recsrc);
+}
+
+#define set_mixer_info()							\
+		memset(&info, 0, sizeof(info));					\
+		strlcpy(info.id, "MSNDMIXER", sizeof(info.id));			\
+		strlcpy(info.name, "MultiSound Mixer", sizeof(info.name));
+
+static int mixer_ioctl(unsigned int cmd, unsigned long arg)
+{
+	if (cmd == SOUND_MIXER_INFO) {
+		mixer_info info;
+		set_mixer_info();
+		info.modify_counter = dev.mixer_mod_count;
+		if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	} else if (cmd == SOUND_OLD_MIXER_INFO) {
+		_old_mixer_info info;
+		set_mixer_info();
+		if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	} else if (cmd == SOUND_MIXER_PRIVATE1) {
+		dev.nresets = 0;
+		dsp_full_reset();
+		return 0;
+	} else if (((cmd >> 8) & 0xff) == 'M') {
+		int val = 0;
+
+		if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+			switch (cmd & 0xff) {
+			case SOUND_MIXER_RECSRC:
+				if (get_user(val, (int __user *)arg))
+					return -EFAULT;
+				val = set_recsrc(val);
+				break;
+
+			default:
+				if (get_user(val, (int __user *)arg))
+					return -EFAULT;
+				val = mixer_set(cmd & 0xff, val);
+				break;
+			}
+			++dev.mixer_mod_count;
+			return put_user(val, (int __user *)arg);
+		} else {
+			switch (cmd & 0xff) {
+			case SOUND_MIXER_RECSRC:
+				val = dev.recsrc;
+				break;
+
+			case SOUND_MIXER_DEVMASK:
+			case SOUND_MIXER_STEREODEVS:
+				val =   SOUND_MASK_PCM |
+					SOUND_MASK_LINE |
+					SOUND_MASK_IMIX |
+					SOUND_MASK_LINE1 |
+#ifndef MSND_CLASSIC
+					SOUND_MASK_MIC |
+					SOUND_MASK_SYNTH |
+#endif
+					SOUND_MASK_VOLUME;
+				break;
+				  
+			case SOUND_MIXER_RECMASK:
+#ifdef MSND_CLASSIC
+				val =   0;
+#else
+				val =   SOUND_MASK_IMIX |
+					SOUND_MASK_SYNTH;
+				if (test_bit(F_HAVEDIGITAL, &dev.flags))
+					val |= SOUND_MASK_DIGITAL1;
+#endif
+				break;
+				  
+			case SOUND_MIXER_CAPS:
+				val =   SOUND_CAP_EXCL_INPUT;
+				break;
+
+			default:
+				if ((val = mixer_get(cmd & 0xff)) < 0)
+					return -EINVAL;
+				break;
+			}
+		}
+
+		return put_user(val, (int __user *)arg); 
+	}
+
+	return -EINVAL;
+}
+
+static long dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int minor = iminor(file->f_path.dentry->d_inode);
+	int ret;
+
+	if (cmd == OSS_GETVERSION) {
+		int sound_version = SOUND_VERSION;
+		return put_user(sound_version, (int __user *)arg);
+	}
+
+	ret = -EINVAL;
+
+	mutex_lock(&msnd_pinnacle_mutex);
+	if (minor == dev.dsp_minor)
+		ret = dsp_ioctl(file, cmd, arg);
+	else if (minor == dev.mixer_minor)
+		ret = mixer_ioctl(cmd, arg);
+	mutex_unlock(&msnd_pinnacle_mutex);
+
+	return ret;
+}
+
+static void dsp_write_flush(void)
+{
+	if (!(dev.mode & FMODE_WRITE) || !test_bit(F_WRITING, &dev.flags))
+		return;
+	set_bit(F_WRITEFLUSH, &dev.flags);
+	interruptible_sleep_on_timeout(
+		&dev.writeflush,
+		get_play_delay_jiffies(dev.DAPF.len));
+	clear_bit(F_WRITEFLUSH, &dev.flags);
+	if (!signal_pending(current)) {
+		current->state = TASK_INTERRUPTIBLE;
+		schedule_timeout(get_play_delay_jiffies(DAP_BUFF_SIZE));
+	}
+	clear_bit(F_WRITING, &dev.flags);
+}
+
+static void dsp_halt(struct file *file)
+{
+	if ((file ? file->f_mode : dev.mode) & FMODE_READ) {
+		clear_bit(F_READING, &dev.flags);
+		chk_send_dsp_cmd(&dev, HDEX_RECORD_STOP);
+		msnd_disable_irq(&dev);
+		if (file) {
+			printk(KERN_DEBUG LOGNAME ": Stopping read for %p\n", file);
+			dev.mode &= ~FMODE_READ;
+		}
+		clear_bit(F_AUDIO_READ_INUSE, &dev.flags);
+	}
+	if ((file ? file->f_mode : dev.mode) & FMODE_WRITE) {
+		if (test_bit(F_WRITING, &dev.flags)) {
+			dsp_write_flush();
+			chk_send_dsp_cmd(&dev, HDEX_PLAY_STOP);
+		}
+		msnd_disable_irq(&dev);
+		if (file) {
+			printk(KERN_DEBUG LOGNAME ": Stopping write for %p\n", file);
+			dev.mode &= ~FMODE_WRITE;
+		}
+		clear_bit(F_AUDIO_WRITE_INUSE, &dev.flags);
+	}
+}
+
+static int dsp_release(struct file *file)
+{
+	dsp_halt(file);
+	return 0;
+}
+
+static int dsp_open(struct file *file)
+{
+	if ((file ? file->f_mode : dev.mode) & FMODE_WRITE) {
+		set_bit(F_AUDIO_WRITE_INUSE, &dev.flags);
+		clear_bit(F_WRITING, &dev.flags);
+		msnd_fifo_make_empty(&dev.DAPF);
+		reset_play_queue();
+		if (file) {
+			printk(KERN_DEBUG LOGNAME ": Starting write for %p\n", file);
+			dev.mode |= FMODE_WRITE;
+		}
+		msnd_enable_irq(&dev);
+	}
+	if ((file ? file->f_mode : dev.mode) & FMODE_READ) {
+		set_bit(F_AUDIO_READ_INUSE, &dev.flags);
+		clear_bit(F_READING, &dev.flags);
+		msnd_fifo_make_empty(&dev.DARF);
+		reset_record_queue();
+		if (file) {
+			printk(KERN_DEBUG LOGNAME ": Starting read for %p\n", file);
+			dev.mode |= FMODE_READ;
+		}
+		msnd_enable_irq(&dev);
+	}
+	return 0;
+}
+
+static void set_default_play_audio_parameters(void)
+{
+	dev.play_sample_size = DEFSAMPLESIZE;
+	dev.play_sample_rate = DEFSAMPLERATE;
+	dev.play_channels = DEFCHANNELS;
+}
+
+static void set_default_rec_audio_parameters(void)
+{
+	dev.rec_sample_size = DEFSAMPLESIZE;
+	dev.rec_sample_rate = DEFSAMPLERATE;
+	dev.rec_channels = DEFCHANNELS;
+}
+
+static void set_default_audio_parameters(void)
+{
+	set_default_play_audio_parameters();
+	set_default_rec_audio_parameters();
+}
+
+static int dev_open(struct inode *inode, struct file *file)
+{
+	int minor = iminor(inode);
+	int err = 0;
+
+	mutex_lock(&msnd_pinnacle_mutex);
+	if (minor == dev.dsp_minor) {
+		if ((file->f_mode & FMODE_WRITE &&
+		     test_bit(F_AUDIO_WRITE_INUSE, &dev.flags)) ||
+		    (file->f_mode & FMODE_READ &&
+		     test_bit(F_AUDIO_READ_INUSE, &dev.flags))) {
+			err = -EBUSY;
+			goto out;
+		}
+
+		if ((err = dsp_open(file)) >= 0) {
+			dev.nresets = 0;
+			if (file->f_mode & FMODE_WRITE) {
+				set_default_play_audio_parameters();
+				if (!test_bit(F_DISABLE_WRITE_NDELAY, &dev.flags))
+					dev.play_ndelay = (file->f_flags & O_NDELAY) ? 1 : 0;
+				else
+					dev.play_ndelay = 0;
+			}
+			if (file->f_mode & FMODE_READ) {
+				set_default_rec_audio_parameters();
+				dev.rec_ndelay = (file->f_flags & O_NDELAY) ? 1 : 0;
+			}
+		}
+	}
+	else if (minor == dev.mixer_minor) {
+		/* nothing */
+	} else
+		err = -EINVAL;
+out:
+	mutex_unlock(&msnd_pinnacle_mutex);
+	return err;
+}
+
+static int dev_release(struct inode *inode, struct file *file)
+{
+	int minor = iminor(inode);
+	int err = 0;
+
+	mutex_lock(&msnd_pinnacle_mutex);
+	if (minor == dev.dsp_minor)
+		err = dsp_release(file);
+	else if (minor == dev.mixer_minor) {
+		/* nothing */
+	} else
+		err = -EINVAL;
+	mutex_unlock(&msnd_pinnacle_mutex);
+	return err;
+}
+
+static __inline__ int pack_DARQ_to_DARF(register int bank)
+{
+	register int size, timeout = 3;
+	register WORD wTmp;
+	LPDAQD DAQD;
+
+	/* Increment the tail and check for queue wrap */
+	wTmp = readw(dev.DARQ + JQS_wTail) + PCTODSP_OFFSET(DAQDS__size);
+	if (wTmp > readw(dev.DARQ + JQS_wSize))
+		wTmp = 0;
+	while (wTmp == readw(dev.DARQ + JQS_wHead) && timeout--)
+		udelay(1);
+	writew(wTmp, dev.DARQ + JQS_wTail);
+
+	/* Get our digital audio queue struct */
+	DAQD = bank * DAQDS__size + dev.base + DARQ_DATA_BUFF;
+
+	/* Get length of data */
+	size = readw(DAQD + DAQDS_wSize);
+
+	/* Read data from the head (unprotected bank 1 access okay
+           since this is only called inside an interrupt) */
+	msnd_outb(HPBLKSEL_1, dev.io + HP_BLKS);
+	msnd_fifo_write_io(
+		&dev.DARF,
+		dev.base + bank * DAR_BUFF_SIZE,
+		size);
+	msnd_outb(HPBLKSEL_0, dev.io + HP_BLKS);
+
+	return 1;
+}
+
+static __inline__ int pack_DAPF_to_DAPQ(register int start)
+{
+	register WORD DAPQ_tail;
+	register int protect = start, nbanks = 0;
+	LPDAQD DAQD;
+
+	DAPQ_tail = readw(dev.DAPQ + JQS_wTail);
+	while (DAPQ_tail != readw(dev.DAPQ + JQS_wHead) || start) {
+		register int bank_num = DAPQ_tail / PCTODSP_OFFSET(DAQDS__size);
+		register int n;
+		unsigned long flags;
+
+		/* Write the data to the new tail */
+		if (protect) {
+			/* Critical section: protect fifo in non-interrupt */
+			spin_lock_irqsave(&dev.lock, flags);
+			n = msnd_fifo_read_io(
+				&dev.DAPF,
+				dev.base + bank_num * DAP_BUFF_SIZE,
+				DAP_BUFF_SIZE);
+			spin_unlock_irqrestore(&dev.lock, flags);
+		} else {
+			n = msnd_fifo_read_io(
+				&dev.DAPF,
+				dev.base + bank_num * DAP_BUFF_SIZE,
+				DAP_BUFF_SIZE);
+		}
+		if (!n)
+			break;
+
+		if (start)
+			start = 0;
+
+		/* Get our digital audio queue struct */
+		DAQD = bank_num * DAQDS__size + dev.base + DAPQ_DATA_BUFF;
+
+		/* Write size of this bank */
+		writew(n, DAQD + DAQDS_wSize);
+		++nbanks;
+
+		/* Then advance the tail */
+		DAPQ_tail = (++bank_num % 3) * PCTODSP_OFFSET(DAQDS__size);
+		writew(DAPQ_tail, dev.DAPQ + JQS_wTail);
+		/* Tell the DSP to play the bank */
+		msnd_send_dsp_cmd(&dev, HDEX_PLAY_START);
+	}
+	return nbanks;
+}
+
+static int dsp_read(char __user *buf, size_t len)
+{
+	int count = len;
+	char *page = (char *)__get_free_page(GFP_KERNEL);
+
+	if (!page)
+		return -ENOMEM;
+
+	while (count > 0) {
+		int n, k;
+		unsigned long flags;
+
+		k = PAGE_SIZE;
+		if (k > count)
+			k = count;
+
+		/* Critical section: protect fifo in non-interrupt */
+		spin_lock_irqsave(&dev.lock, flags);
+		n = msnd_fifo_read(&dev.DARF, page, k);
+		spin_unlock_irqrestore(&dev.lock, flags);
+		if (copy_to_user(buf, page, n)) {
+			free_page((unsigned long)page);
+			return -EFAULT;
+		}
+		buf += n;
+		count -= n;
+
+		if (n == k && count)
+			continue;
+
+		if (!test_bit(F_READING, &dev.flags) && dev.mode & FMODE_READ) {
+			dev.last_recbank = -1;
+			if (chk_send_dsp_cmd(&dev, HDEX_RECORD_START) == 0)
+				set_bit(F_READING, &dev.flags);
+		}
+
+		if (dev.rec_ndelay) {
+			free_page((unsigned long)page);
+			return count == len ? -EAGAIN : len - count;
+		}
+
+		if (count > 0) {
+			set_bit(F_READBLOCK, &dev.flags);
+			if (!interruptible_sleep_on_timeout(
+				&dev.readblock,
+				get_rec_delay_jiffies(DAR_BUFF_SIZE)))
+				clear_bit(F_READING, &dev.flags);
+			clear_bit(F_READBLOCK, &dev.flags);
+			if (signal_pending(current)) {
+				free_page((unsigned long)page);
+				return -EINTR;
+			}
+		}
+	}
+	free_page((unsigned long)page);
+	return len - count;
+}
+
+static int dsp_write(const char __user *buf, size_t len)
+{
+	int count = len;
+	char *page = (char *)__get_free_page(GFP_KERNEL);
+
+	if (!page)
+		return -ENOMEM;
+
+	while (count > 0) {
+		int n, k;
+		unsigned long flags;
+
+		k = PAGE_SIZE;
+		if (k > count)
+			k = count;
+
+		if (copy_from_user(page, buf, k)) {
+			free_page((unsigned long)page);
+			return -EFAULT;
+		}
+
+		/* Critical section: protect fifo in non-interrupt */
+		spin_lock_irqsave(&dev.lock, flags);
+		n = msnd_fifo_write(&dev.DAPF, page, k);
+		spin_unlock_irqrestore(&dev.lock, flags);
+		buf += n;
+		count -= n;
+
+		if (count && n == k)
+			continue;
+
+		if (!test_bit(F_WRITING, &dev.flags) && (dev.mode & FMODE_WRITE)) {
+			dev.last_playbank = -1;
+			if (pack_DAPF_to_DAPQ(1) > 0)
+				set_bit(F_WRITING, &dev.flags);
+		}
+
+		if (dev.play_ndelay) {
+			free_page((unsigned long)page);
+			return count == len ? -EAGAIN : len - count;
+		}
+
+		if (count > 0) {
+			set_bit(F_WRITEBLOCK, &dev.flags);
+			interruptible_sleep_on_timeout(
+				&dev.writeblock,
+				get_play_delay_jiffies(DAP_BUFF_SIZE));
+			clear_bit(F_WRITEBLOCK, &dev.flags);
+			if (signal_pending(current)) {
+				free_page((unsigned long)page);
+				return -EINTR;
+			}
+		}
+	}
+
+	free_page((unsigned long)page);
+	return len - count;
+}
+
+static ssize_t dev_read(struct file *file, char __user *buf, size_t count, loff_t *off)
+{
+	int minor = iminor(file->f_path.dentry->d_inode);
+	if (minor == dev.dsp_minor)
+		return dsp_read(buf, count);
+	else
+		return -EINVAL;
+}
+
+static ssize_t dev_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
+{
+	int minor = iminor(file->f_path.dentry->d_inode);
+	if (minor == dev.dsp_minor)
+		return dsp_write(buf, count);
+	else
+		return -EINVAL;
+}
+
+static __inline__ void eval_dsp_msg(register WORD wMessage)
+{
+	switch (HIBYTE(wMessage)) {
+	case HIMT_PLAY_DONE:
+		if (dev.last_playbank == LOBYTE(wMessage) || !test_bit(F_WRITING, &dev.flags))
+			break;
+		dev.last_playbank = LOBYTE(wMessage);
+
+		if (pack_DAPF_to_DAPQ(0) <= 0) {
+			if (!test_bit(F_WRITEBLOCK, &dev.flags)) {
+				if (test_and_clear_bit(F_WRITEFLUSH, &dev.flags))
+					wake_up_interruptible(&dev.writeflush);
+			}
+			clear_bit(F_WRITING, &dev.flags);
+		}
+
+		if (test_bit(F_WRITEBLOCK, &dev.flags))
+			wake_up_interruptible(&dev.writeblock);
+		break;
+
+	case HIMT_RECORD_DONE:
+		if (dev.last_recbank == LOBYTE(wMessage))
+			break;
+		dev.last_recbank = LOBYTE(wMessage);
+
+		pack_DARQ_to_DARF(dev.last_recbank);
+
+		if (test_bit(F_READBLOCK, &dev.flags))
+			wake_up_interruptible(&dev.readblock);
+		break;
+
+	case HIMT_DSP:
+		switch (LOBYTE(wMessage)) {
+#ifndef MSND_CLASSIC
+		case HIDSP_PLAY_UNDER:
+#endif
+		case HIDSP_INT_PLAY_UNDER:
+/*			printk(KERN_DEBUG LOGNAME ": Play underflow\n"); */
+			clear_bit(F_WRITING, &dev.flags);
+			break;
+
+		case HIDSP_INT_RECORD_OVER:
+/*			printk(KERN_DEBUG LOGNAME ": Record overflow\n"); */
+			clear_bit(F_READING, &dev.flags);
+			break;
+
+		default:
+/*			printk(KERN_DEBUG LOGNAME ": DSP message %d 0x%02x\n",
+			LOBYTE(wMessage), LOBYTE(wMessage)); */
+			break;
+		}
+		break;
+
+        case HIMT_MIDI_IN_UCHAR:
+		if (dev.midi_in_interrupt)
+			(*dev.midi_in_interrupt)(&dev);
+		break;
+
+	default:
+/*		printk(KERN_DEBUG LOGNAME ": HIMT message %d 0x%02x\n", HIBYTE(wMessage), HIBYTE(wMessage)); */
+		break;
+	}
+}
+
+static irqreturn_t intr(int irq, void *dev_id)
+{
+	/* Send ack to DSP */
+	msnd_inb(dev.io + HP_RXL);
+
+	/* Evaluate queued DSP messages */
+	while (readw(dev.DSPQ + JQS_wTail) != readw(dev.DSPQ + JQS_wHead)) {
+		register WORD wTmp;
+
+		eval_dsp_msg(readw(dev.pwDSPQData + 2*readw(dev.DSPQ + JQS_wHead)));
+
+		if ((wTmp = readw(dev.DSPQ + JQS_wHead) + 1) > readw(dev.DSPQ + JQS_wSize))
+			writew(0, dev.DSPQ + JQS_wHead);
+		else
+			writew(wTmp, dev.DSPQ + JQS_wHead);
+	}
+	return IRQ_HANDLED;
+}
+
+static const struct file_operations dev_fileops = {
+	.owner		= THIS_MODULE,
+	.read		= dev_read,
+	.write		= dev_write,
+	.unlocked_ioctl	= dev_ioctl,
+	.open		= dev_open,
+	.release	= dev_release,
+	.llseek		= noop_llseek,
+};
+
+static int reset_dsp(void)
+{
+	int timeout = 100;
+
+	msnd_outb(HPDSPRESET_ON, dev.io + HP_DSPR);
+	mdelay(1);
+#ifndef MSND_CLASSIC
+	dev.info = msnd_inb(dev.io + HP_INFO);
+#endif
+	msnd_outb(HPDSPRESET_OFF, dev.io + HP_DSPR);
+	mdelay(1);
+	while (timeout-- > 0) {
+		if (msnd_inb(dev.io + HP_CVR) == HP_CVR_DEF)
+			return 0;
+		mdelay(1);
+	}
+	printk(KERN_ERR LOGNAME ": Cannot reset DSP\n");
+
+	return -EIO;
+}
+
+static int __init probe_multisound(void)
+{
+#ifndef MSND_CLASSIC
+	char *xv, *rev = NULL;
+	char *pin = "Pinnacle", *fiji = "Fiji";
+	char *pinfiji = "Pinnacle/Fiji";
+#endif
+
+	if (!request_region(dev.io, dev.numio, "probing")) {
+		printk(KERN_ERR LOGNAME ": I/O port conflict\n");
+		return -ENODEV;
+	}
+
+	if (reset_dsp() < 0) {
+		release_region(dev.io, dev.numio);
+		return -ENODEV;
+	}
+
+#ifdef MSND_CLASSIC
+	dev.name = "Classic/Tahiti/Monterey";
+	printk(KERN_INFO LOGNAME ": %s, "
+#else
+	switch (dev.info >> 4) {
+	case 0xf: xv = "<= 1.15"; break;
+	case 0x1: xv = "1.18/1.2"; break;
+	case 0x2: xv = "1.3"; break;
+	case 0x3: xv = "1.4"; break;
+	default: xv = "unknown"; break;
+	}
+
+	switch (dev.info & 0x7) {
+	case 0x0: rev = "I"; dev.name = pin; break;
+	case 0x1: rev = "F"; dev.name = pin; break;
+	case 0x2: rev = "G"; dev.name = pin; break;
+	case 0x3: rev = "H"; dev.name = pin; break;
+	case 0x4: rev = "E"; dev.name = fiji; break;
+	case 0x5: rev = "C"; dev.name = fiji; break;
+	case 0x6: rev = "D"; dev.name = fiji; break;
+	case 0x7:
+		rev = "A-B (Fiji) or A-E (Pinnacle)";
+		dev.name = pinfiji;
+		break;
+	}
+	printk(KERN_INFO LOGNAME ": %s revision %s, Xilinx version %s, "
+#endif /* MSND_CLASSIC */
+	       "I/O 0x%x-0x%x, IRQ %d, memory mapped to %p-%p\n",
+	       dev.name,
+#ifndef MSND_CLASSIC
+	       rev, xv,
+#endif
+	       dev.io, dev.io + dev.numio - 1,
+	       dev.irq,
+	       dev.base, dev.base + 0x7fff);
+
+	release_region(dev.io, dev.numio);
+	return 0;
+}
+
+static int init_sma(void)
+{
+	static int initted;
+	WORD mastVolLeft, mastVolRight;
+	unsigned long flags;
+
+#ifdef MSND_CLASSIC
+	msnd_outb(dev.memid, dev.io + HP_MEMM);
+#endif
+	msnd_outb(HPBLKSEL_0, dev.io + HP_BLKS);
+	if (initted) {
+		mastVolLeft = readw(dev.SMA + SMA_wCurrMastVolLeft);
+		mastVolRight = readw(dev.SMA + SMA_wCurrMastVolRight);
+	} else
+		mastVolLeft = mastVolRight = 0;
+	memset_io(dev.base, 0, 0x8000);
+
+	/* Critical section: bank 1 access */
+	spin_lock_irqsave(&dev.lock, flags);
+	msnd_outb(HPBLKSEL_1, dev.io + HP_BLKS);
+	memset_io(dev.base, 0, 0x8000);
+	msnd_outb(HPBLKSEL_0, dev.io + HP_BLKS);
+	spin_unlock_irqrestore(&dev.lock, flags);
+
+	dev.pwDSPQData = (dev.base + DSPQ_DATA_BUFF);
+	dev.pwMODQData = (dev.base + MODQ_DATA_BUFF);
+	dev.pwMIDQData = (dev.base + MIDQ_DATA_BUFF);
+
+	/* Motorola 56k shared memory base */
+	dev.SMA = dev.base + SMA_STRUCT_START;
+
+	/* Digital audio play queue */
+	dev.DAPQ = dev.base + DAPQ_OFFSET;
+	msnd_init_queue(dev.DAPQ, DAPQ_DATA_BUFF, DAPQ_BUFF_SIZE);
+
+	/* Digital audio record queue */
+	dev.DARQ = dev.base + DARQ_OFFSET;
+	msnd_init_queue(dev.DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE);
+
+	/* MIDI out queue */
+	dev.MODQ = dev.base + MODQ_OFFSET;
+	msnd_init_queue(dev.MODQ, MODQ_DATA_BUFF, MODQ_BUFF_SIZE);
+
+	/* MIDI in queue */
+	dev.MIDQ = dev.base + MIDQ_OFFSET;
+	msnd_init_queue(dev.MIDQ, MIDQ_DATA_BUFF, MIDQ_BUFF_SIZE);
+
+	/* DSP -> host message queue */
+	dev.DSPQ = dev.base + DSPQ_OFFSET;
+	msnd_init_queue(dev.DSPQ, DSPQ_DATA_BUFF, DSPQ_BUFF_SIZE);
+
+	/* Setup some DSP values */
+#ifndef MSND_CLASSIC
+	writew(1, dev.SMA + SMA_wCurrPlayFormat);
+	writew(dev.play_sample_size, dev.SMA + SMA_wCurrPlaySampleSize);
+	writew(dev.play_channels, dev.SMA + SMA_wCurrPlayChannels);
+	writew(dev.play_sample_rate, dev.SMA + SMA_wCurrPlaySampleRate);
+#endif
+	writew(dev.play_sample_rate, dev.SMA + SMA_wCalFreqAtoD);
+	writew(mastVolLeft, dev.SMA + SMA_wCurrMastVolLeft);
+	writew(mastVolRight, dev.SMA + SMA_wCurrMastVolRight);
+#ifndef MSND_CLASSIC
+	writel(0x00010000, dev.SMA + SMA_dwCurrPlayPitch);
+	writel(0x00000001, dev.SMA + SMA_dwCurrPlayRate);
+#endif
+	writew(0x303, dev.SMA + SMA_wCurrInputTagBits);
+
+	initted = 1;
+
+	return 0;
+}
+
+static int __init calibrate_adc(WORD srate)
+{
+	writew(srate, dev.SMA + SMA_wCalFreqAtoD);
+	if (dev.calibrate_signal == 0)
+		writew(readw(dev.SMA + SMA_wCurrHostStatusFlags)
+		       | 0x0001, dev.SMA + SMA_wCurrHostStatusFlags);
+	else
+		writew(readw(dev.SMA + SMA_wCurrHostStatusFlags)
+		       & ~0x0001, dev.SMA + SMA_wCurrHostStatusFlags);
+	if (msnd_send_word(&dev, 0, 0, HDEXAR_CAL_A_TO_D) == 0 &&
+	    chk_send_dsp_cmd(&dev, HDEX_AUX_REQ) == 0) {
+		current->state = TASK_INTERRUPTIBLE;
+		schedule_timeout(HZ / 3);
+		return 0;
+	}
+	printk(KERN_WARNING LOGNAME ": ADC calibration failed\n");
+
+	return -EIO;
+}
+
+static int upload_dsp_code(void)
+{
+	msnd_outb(HPBLKSEL_0, dev.io + HP_BLKS);
+#ifndef HAVE_DSPCODEH
+	INITCODESIZE = mod_firmware_load(INITCODEFILE, &INITCODE);
+	if (!INITCODE) {
+		printk(KERN_ERR LOGNAME ": Error loading " INITCODEFILE);
+		return -EBUSY;
+	}
+
+	PERMCODESIZE = mod_firmware_load(PERMCODEFILE, &PERMCODE);
+	if (!PERMCODE) {
+		printk(KERN_ERR LOGNAME ": Error loading " PERMCODEFILE);
+		vfree(INITCODE);
+		return -EBUSY;
+	}
+#endif
+	memcpy_toio(dev.base, PERMCODE, PERMCODESIZE);
+	if (msnd_upload_host(&dev, INITCODE, INITCODESIZE) < 0) {
+		printk(KERN_WARNING LOGNAME ": Error uploading to DSP\n");
+		return -ENODEV;
+	}
+#ifdef HAVE_DSPCODEH
+	printk(KERN_INFO LOGNAME ": DSP firmware uploaded (resident)\n");
+#else
+	printk(KERN_INFO LOGNAME ": DSP firmware uploaded\n");
+#endif
+
+#ifndef HAVE_DSPCODEH
+	vfree(INITCODE);
+	vfree(PERMCODE);
+#endif
+
+	return 0;
+}
+
+#ifdef MSND_CLASSIC
+static void reset_proteus(void)
+{
+	msnd_outb(HPPRORESET_ON, dev.io + HP_PROR);
+	mdelay(TIME_PRO_RESET);
+	msnd_outb(HPPRORESET_OFF, dev.io + HP_PROR);
+	mdelay(TIME_PRO_RESET_DONE);
+}
+#endif
+
+static int initialize(void)
+{
+	int err, timeout;
+
+#ifdef MSND_CLASSIC
+	msnd_outb(HPWAITSTATE_0, dev.io + HP_WAIT);
+	msnd_outb(HPBITMODE_16, dev.io + HP_BITM);
+
+	reset_proteus();
+#endif
+	if ((err = init_sma()) < 0) {
+		printk(KERN_WARNING LOGNAME ": Cannot initialize SMA\n");
+		return err;
+	}
+
+	if ((err = reset_dsp()) < 0)
+		return err;
+
+	if ((err = upload_dsp_code()) < 0) {
+		printk(KERN_WARNING LOGNAME ": Cannot upload DSP code\n");
+		return err;
+	}
+
+	timeout = 200;
+	while (readw(dev.base)) {
+		mdelay(1);
+		if (!timeout--) {
+			printk(KERN_DEBUG LOGNAME ": DSP reset timeout\n");
+			return -EIO;
+		}
+	}
+
+	mixer_setup();
+
+	return 0;
+}
+
+static int dsp_full_reset(void)
+{
+	int rv;
+
+	if (test_bit(F_RESETTING, &dev.flags) || ++dev.nresets > 10)
+		return 0;
+
+	set_bit(F_RESETTING, &dev.flags);
+	printk(KERN_INFO LOGNAME ": DSP reset\n");
+	dsp_halt(NULL);			/* Unconditionally halt */
+	if ((rv = initialize()))
+		printk(KERN_WARNING LOGNAME ": DSP reset failed\n");
+	force_recsrc(dev.recsrc);
+	dsp_open(NULL);
+	clear_bit(F_RESETTING, &dev.flags);
+
+	return rv;
+}
+
+static int __init attach_multisound(void)
+{
+	int err;
+
+	if ((err = request_irq(dev.irq, intr, 0, dev.name, &dev)) < 0) {
+		printk(KERN_ERR LOGNAME ": Couldn't grab IRQ %d\n", dev.irq);
+		return err;
+	}
+	if (request_region(dev.io, dev.numio, dev.name) == NULL) {
+		free_irq(dev.irq, &dev);
+		return -EBUSY;
+	}
+
+	err = dsp_full_reset();
+	if (err < 0) {
+		release_region(dev.io, dev.numio);
+		free_irq(dev.irq, &dev);
+		return err;
+	}
+
+	if ((err = msnd_register(&dev)) < 0) {
+		printk(KERN_ERR LOGNAME ": Unable to register MultiSound\n");
+		release_region(dev.io, dev.numio);
+		free_irq(dev.irq, &dev);
+		return err;
+	}
+
+	if ((dev.dsp_minor = register_sound_dsp(&dev_fileops, -1)) < 0) {
+		printk(KERN_ERR LOGNAME ": Unable to register DSP operations\n");
+		msnd_unregister(&dev);
+		release_region(dev.io, dev.numio);
+		free_irq(dev.irq, &dev);
+		return dev.dsp_minor;
+	}
+
+	if ((dev.mixer_minor = register_sound_mixer(&dev_fileops, -1)) < 0) {
+		printk(KERN_ERR LOGNAME ": Unable to register mixer operations\n");
+		unregister_sound_mixer(dev.mixer_minor);
+		msnd_unregister(&dev);
+		release_region(dev.io, dev.numio);
+		free_irq(dev.irq, &dev);
+		return dev.mixer_minor;
+	}
+
+	dev.ext_midi_dev = dev.hdr_midi_dev = -1;
+
+	disable_irq(dev.irq);
+	calibrate_adc(dev.play_sample_rate);
+#ifndef MSND_CLASSIC
+	force_recsrc(SOUND_MASK_IMIX);
+#endif
+
+	return 0;
+}
+
+static void __exit unload_multisound(void)
+{
+	release_region(dev.io, dev.numio);
+	free_irq(dev.irq, &dev);
+	unregister_sound_mixer(dev.mixer_minor);
+	unregister_sound_dsp(dev.dsp_minor);
+	msnd_unregister(&dev);
+}
+
+#ifndef MSND_CLASSIC
+
+/* Pinnacle/Fiji Logical Device Configuration */
+
+static int __init msnd_write_cfg(int cfg, int reg, int value)
+{
+	msnd_outb(reg, cfg);
+	msnd_outb(value, cfg + 1);
+	if (value != msnd_inb(cfg + 1)) {
+		printk(KERN_ERR LOGNAME ": msnd_write_cfg: I/O error\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+static int __init msnd_write_cfg_io0(int cfg, int num, WORD io)
+{
+	if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (msnd_write_cfg(cfg, IREG_IO0_BASEHI, HIBYTE(io)))
+		return -EIO;
+	if (msnd_write_cfg(cfg, IREG_IO0_BASELO, LOBYTE(io)))
+		return -EIO;
+	return 0;
+}
+
+static int __init msnd_write_cfg_io1(int cfg, int num, WORD io)
+{
+	if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (msnd_write_cfg(cfg, IREG_IO1_BASEHI, HIBYTE(io)))
+		return -EIO;
+	if (msnd_write_cfg(cfg, IREG_IO1_BASELO, LOBYTE(io)))
+		return -EIO;
+	return 0;
+}
+
+static int __init msnd_write_cfg_irq(int cfg, int num, WORD irq)
+{
+	if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (msnd_write_cfg(cfg, IREG_IRQ_NUMBER, LOBYTE(irq)))
+		return -EIO;
+	if (msnd_write_cfg(cfg, IREG_IRQ_TYPE, IRQTYPE_EDGE))
+		return -EIO;
+	return 0;
+}
+
+static int __init msnd_write_cfg_mem(int cfg, int num, int mem)
+{
+	WORD wmem;
+
+	mem >>= 8;
+	mem &= 0xfff;
+	wmem = (WORD)mem;
+	if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (msnd_write_cfg(cfg, IREG_MEMBASEHI, HIBYTE(wmem)))
+		return -EIO;
+	if (msnd_write_cfg(cfg, IREG_MEMBASELO, LOBYTE(wmem)))
+		return -EIO;
+	if (wmem && msnd_write_cfg(cfg, IREG_MEMCONTROL, (MEMTYPE_HIADDR | MEMTYPE_16BIT)))
+		return -EIO;
+	return 0;
+}
+
+static int __init msnd_activate_logical(int cfg, int num)
+{
+	if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (msnd_write_cfg(cfg, IREG_ACTIVATE, LD_ACTIVATE))
+		return -EIO;
+	return 0;
+}
+
+static int __init msnd_write_cfg_logical(int cfg, int num, WORD io0, WORD io1, WORD irq, int mem)
+{
+	if (msnd_write_cfg(cfg, IREG_LOGDEVICE, num))
+		return -EIO;
+	if (msnd_write_cfg_io0(cfg, num, io0))
+		return -EIO;
+	if (msnd_write_cfg_io1(cfg, num, io1))
+		return -EIO;
+	if (msnd_write_cfg_irq(cfg, num, irq))
+		return -EIO;
+	if (msnd_write_cfg_mem(cfg, num, mem))
+		return -EIO;
+	if (msnd_activate_logical(cfg, num))
+		return -EIO;
+	return 0;
+}
+
+typedef struct msnd_pinnacle_cfg_device {
+	WORD io0, io1, irq;
+	int mem;
+} msnd_pinnacle_cfg_t[4];
+
+static int __init msnd_pinnacle_cfg_devices(int cfg, int reset, msnd_pinnacle_cfg_t device)
+{
+	int i;
+
+	/* Reset devices if told to */
+	if (reset) {
+		printk(KERN_INFO LOGNAME ": Resetting all devices\n");
+		for (i = 0; i < 4; ++i)
+			if (msnd_write_cfg_logical(cfg, i, 0, 0, 0, 0))
+				return -EIO;
+	}
+
+	/* Configure specified devices */
+	for (i = 0; i < 4; ++i) {
+
+		switch (i) {
+		case 0:		/* DSP */
+			if (!(device[i].io0 && device[i].irq && device[i].mem))
+				continue;
+			break;
+		case 1:		/* MPU */
+			if (!(device[i].io0 && device[i].irq))
+				continue;
+			printk(KERN_INFO LOGNAME
+			       ": Configuring MPU to I/O 0x%x IRQ %d\n",
+			       device[i].io0, device[i].irq);
+			break;
+		case 2:		/* IDE */
+			if (!(device[i].io0 && device[i].io1 && device[i].irq))
+				continue;
+			printk(KERN_INFO LOGNAME
+			       ": Configuring IDE to I/O 0x%x, 0x%x IRQ %d\n",
+			       device[i].io0, device[i].io1, device[i].irq);
+			break;
+		case 3:		/* Joystick */
+			if (!(device[i].io0))
+				continue;
+			printk(KERN_INFO LOGNAME
+			       ": Configuring joystick to I/O 0x%x\n",
+			       device[i].io0);
+			break;
+		}
+
+		/* Configure the device */
+		if (msnd_write_cfg_logical(cfg, i, device[i].io0, device[i].io1, device[i].irq, device[i].mem))
+			return -EIO;
+	}
+
+	return 0;
+}
+#endif
+
+#ifdef MODULE
+MODULE_AUTHOR				("Andrew Veliath <andrewtv@usa.net>");
+MODULE_DESCRIPTION			("Turtle Beach " LONGNAME " Linux Driver");
+MODULE_LICENSE("GPL");
+
+static int io __initdata =		-1;
+static int irq __initdata =		-1;
+static int mem __initdata =		-1;
+static int write_ndelay __initdata =	-1;
+
+#ifndef MSND_CLASSIC
+/* Pinnacle/Fiji non-PnP Config Port */
+static int cfg __initdata =		-1;
+
+/* Extra Peripheral Configuration */
+static int reset __initdata = 0;
+static int mpu_io __initdata = 0;
+static int mpu_irq __initdata = 0;
+static int ide_io0 __initdata = 0;
+static int ide_io1 __initdata = 0;
+static int ide_irq __initdata = 0;
+static int joystick_io __initdata = 0;
+
+/* If we have the digital daugherboard... */
+static int digital __initdata = 0;
+#endif
+
+static int fifosize __initdata =	DEFFIFOSIZE;
+static int calibrate_signal __initdata = 0;
+
+#else /* not a module */
+
+static int write_ndelay __initdata =	-1;
+
+#ifdef MSND_CLASSIC
+static int io __initdata =		CONFIG_MSNDCLAS_IO;
+static int irq __initdata =		CONFIG_MSNDCLAS_IRQ;
+static int mem __initdata =		CONFIG_MSNDCLAS_MEM;
+#else /* Pinnacle/Fiji */
+
+static int io __initdata =		CONFIG_MSNDPIN_IO;
+static int irq __initdata =		CONFIG_MSNDPIN_IRQ;
+static int mem __initdata =		CONFIG_MSNDPIN_MEM;
+
+/* Pinnacle/Fiji non-PnP Config Port */
+#ifdef CONFIG_MSNDPIN_NONPNP
+#  ifndef CONFIG_MSNDPIN_CFG
+#    define CONFIG_MSNDPIN_CFG		0x250
+#  endif
+#else
+#  ifdef CONFIG_MSNDPIN_CFG
+#    undef CONFIG_MSNDPIN_CFG
+#  endif
+#  define CONFIG_MSNDPIN_CFG		-1
+#endif
+static int cfg __initdata =		CONFIG_MSNDPIN_CFG;
+/* If not a module, we don't need to bother with reset=1 */
+static int reset;
+
+/* Extra Peripheral Configuration (Default: Disable) */
+#ifndef CONFIG_MSNDPIN_MPU_IO
+#  define CONFIG_MSNDPIN_MPU_IO		0
+#endif
+static int mpu_io __initdata =		CONFIG_MSNDPIN_MPU_IO;
+
+#ifndef CONFIG_MSNDPIN_MPU_IRQ
+#  define CONFIG_MSNDPIN_MPU_IRQ	0
+#endif
+static int mpu_irq __initdata =		CONFIG_MSNDPIN_MPU_IRQ;
+
+#ifndef CONFIG_MSNDPIN_IDE_IO0
+#  define CONFIG_MSNDPIN_IDE_IO0	0
+#endif
+static int ide_io0 __initdata =		CONFIG_MSNDPIN_IDE_IO0;
+
+#ifndef CONFIG_MSNDPIN_IDE_IO1
+#  define CONFIG_MSNDPIN_IDE_IO1	0
+#endif
+static int ide_io1 __initdata =		CONFIG_MSNDPIN_IDE_IO1;
+
+#ifndef CONFIG_MSNDPIN_IDE_IRQ
+#  define CONFIG_MSNDPIN_IDE_IRQ	0
+#endif
+static int ide_irq __initdata =		CONFIG_MSNDPIN_IDE_IRQ;
+
+#ifndef CONFIG_MSNDPIN_JOYSTICK_IO
+#  define CONFIG_MSNDPIN_JOYSTICK_IO	0
+#endif
+static int joystick_io __initdata =	CONFIG_MSNDPIN_JOYSTICK_IO;
+
+/* Have SPDIF (Digital) Daughterboard */
+#ifndef CONFIG_MSNDPIN_DIGITAL
+#  define CONFIG_MSNDPIN_DIGITAL	0
+#endif
+static int digital __initdata =		CONFIG_MSNDPIN_DIGITAL;
+
+#endif /* MSND_CLASSIC */
+
+#ifndef CONFIG_MSND_FIFOSIZE
+#  define CONFIG_MSND_FIFOSIZE		DEFFIFOSIZE
+#endif
+static int fifosize __initdata =	CONFIG_MSND_FIFOSIZE;
+
+#ifndef CONFIG_MSND_CALSIGNAL
+#  define CONFIG_MSND_CALSIGNAL		0
+#endif
+static int
+calibrate_signal __initdata =		CONFIG_MSND_CALSIGNAL;
+#endif /* MODULE */
+
+module_param				(io, int, 0);
+module_param				(irq, int, 0);
+module_param				(mem, int, 0);
+module_param				(write_ndelay, int, 0);
+module_param				(fifosize, int, 0);
+module_param				(calibrate_signal, int, 0);
+#ifndef MSND_CLASSIC
+module_param				(digital, bool, 0);
+module_param				(cfg, int, 0);
+module_param				(reset, int, 0);
+module_param				(mpu_io, int, 0);
+module_param				(mpu_irq, int, 0);
+module_param				(ide_io0, int, 0);
+module_param				(ide_io1, int, 0);
+module_param				(ide_irq, int, 0);
+module_param				(joystick_io, int, 0);
+#endif
+
+static int __init msnd_init(void)
+{
+	int err;
+#ifndef MSND_CLASSIC
+	static msnd_pinnacle_cfg_t pinnacle_devs;
+#endif /* MSND_CLASSIC */
+
+	printk(KERN_INFO LOGNAME ": Turtle Beach " LONGNAME " Linux Driver Version "
+	       VERSION ", Copyright (C) 1998 Andrew Veliath\n");
+
+	if (io == -1 || irq == -1 || mem == -1)
+		printk(KERN_WARNING LOGNAME ": io, irq and mem must be set\n");
+
+#ifdef MSND_CLASSIC
+	if (io == -1 ||
+	    !(io == 0x290 ||
+	      io == 0x260 ||
+	      io == 0x250 ||
+	      io == 0x240 ||
+	      io == 0x230 ||
+	      io == 0x220 ||
+	      io == 0x210 ||
+	      io == 0x3e0)) {
+		printk(KERN_ERR LOGNAME ": \"io\" - DSP I/O base must be set to 0x210, 0x220, 0x230, 0x240, 0x250, 0x260, 0x290, or 0x3E0\n");
+		return -EINVAL;
+	}
+#else
+	if (io == -1 ||
+		io < 0x100 ||
+		io > 0x3e0 ||
+		(io % 0x10) != 0) {
+			printk(KERN_ERR LOGNAME ": \"io\" - DSP I/O base must within the range 0x100 to 0x3E0 and must be evenly divisible by 0x10\n");
+			return -EINVAL;
+	}
+#endif /* MSND_CLASSIC */
+
+	if (irq == -1 ||
+	    !(irq == 5 ||
+	      irq == 7 ||
+	      irq == 9 ||
+	      irq == 10 ||
+	      irq == 11 ||
+	      irq == 12)) {
+		printk(KERN_ERR LOGNAME ": \"irq\" - must be set to 5, 7, 9, 10, 11 or 12\n");
+		return -EINVAL;
+	}
+
+	if (mem == -1 ||
+	    !(mem == 0xb0000 ||
+	      mem == 0xc8000 ||
+	      mem == 0xd0000 ||
+	      mem == 0xd8000 ||
+	      mem == 0xe0000 ||
+	      mem == 0xe8000)) {
+		printk(KERN_ERR LOGNAME ": \"mem\" - must be set to "
+		       "0xb0000, 0xc8000, 0xd0000, 0xd8000, 0xe0000 or 0xe8000\n");
+		return -EINVAL;
+	}
+
+#ifdef MSND_CLASSIC
+	switch (irq) {
+	case 5: dev.irqid = HPIRQ_5; break;
+	case 7: dev.irqid = HPIRQ_7; break;
+	case 9: dev.irqid = HPIRQ_9; break;
+	case 10: dev.irqid = HPIRQ_10; break;
+	case 11: dev.irqid = HPIRQ_11; break;
+	case 12: dev.irqid = HPIRQ_12; break;
+	}
+
+	switch (mem) {
+	case 0xb0000: dev.memid = HPMEM_B000; break;
+	case 0xc8000: dev.memid = HPMEM_C800; break;
+	case 0xd0000: dev.memid = HPMEM_D000; break;
+	case 0xd8000: dev.memid = HPMEM_D800; break;
+	case 0xe0000: dev.memid = HPMEM_E000; break;
+	case 0xe8000: dev.memid = HPMEM_E800; break;
+	}
+#else
+	if (cfg == -1) {
+		printk(KERN_INFO LOGNAME ": Assuming PnP mode\n");
+	} else if (cfg != 0x250 && cfg != 0x260 && cfg != 0x270) {
+		printk(KERN_INFO LOGNAME ": Config port must be 0x250, 0x260 or 0x270 (or unspecified for PnP mode)\n");
+		return -EINVAL;
+	} else {
+		printk(KERN_INFO LOGNAME ": Non-PnP mode: configuring at port 0x%x\n", cfg);
+
+		/* DSP */
+		pinnacle_devs[0].io0 = io;
+		pinnacle_devs[0].irq = irq;
+		pinnacle_devs[0].mem = mem;
+
+		/* The following are Pinnacle specific */
+
+		/* MPU */
+		pinnacle_devs[1].io0 = mpu_io;
+		pinnacle_devs[1].irq = mpu_irq;
+
+		/* IDE */
+		pinnacle_devs[2].io0 = ide_io0;
+		pinnacle_devs[2].io1 = ide_io1;
+		pinnacle_devs[2].irq = ide_irq;
+
+		/* Joystick */
+		pinnacle_devs[3].io0 = joystick_io;
+
+		if (!request_region(cfg, 2, "Pinnacle/Fiji Config")) {
+			printk(KERN_ERR LOGNAME ": Config port 0x%x conflict\n", cfg);
+			return -EIO;
+		}
+
+		if (msnd_pinnacle_cfg_devices(cfg, reset, pinnacle_devs)) {
+			printk(KERN_ERR LOGNAME ": Device configuration error\n");
+			release_region(cfg, 2);
+			return -EIO;
+		}
+		release_region(cfg, 2);
+	}
+#endif /* MSND_CLASSIC */
+
+	if (fifosize < 16)
+		fifosize = 16;
+
+	if (fifosize > 1024)
+		fifosize = 1024;
+
+	set_default_audio_parameters();
+#ifdef MSND_CLASSIC
+	dev.type = msndClassic;
+#else
+	dev.type = msndPinnacle;
+#endif
+	dev.io = io;
+	dev.numio = DSP_NUMIO;
+	dev.irq = irq;
+	dev.base = ioremap(mem, 0x8000);
+	dev.fifosize = fifosize * 1024;
+	dev.calibrate_signal = calibrate_signal ? 1 : 0;
+	dev.recsrc = 0;
+	dev.dspq_data_buff = DSPQ_DATA_BUFF;
+	dev.dspq_buff_size = DSPQ_BUFF_SIZE;
+	if (write_ndelay == -1)
+		write_ndelay = CONFIG_MSND_WRITE_NDELAY;
+	if (write_ndelay)
+		clear_bit(F_DISABLE_WRITE_NDELAY, &dev.flags);
+	else
+		set_bit(F_DISABLE_WRITE_NDELAY, &dev.flags);
+#ifndef MSND_CLASSIC
+	if (digital)
+		set_bit(F_HAVEDIGITAL, &dev.flags);
+#endif
+	init_waitqueue_head(&dev.writeblock);
+	init_waitqueue_head(&dev.readblock);
+	init_waitqueue_head(&dev.writeflush);
+	msnd_fifo_init(&dev.DAPF);
+	msnd_fifo_init(&dev.DARF);
+	spin_lock_init(&dev.lock);
+	printk(KERN_INFO LOGNAME ": %u byte audio FIFOs (x2)\n", dev.fifosize);
+	if ((err = msnd_fifo_alloc(&dev.DAPF, dev.fifosize)) < 0) {
+		printk(KERN_ERR LOGNAME ": Couldn't allocate write FIFO\n");
+		return err;
+	}
+
+	if ((err = msnd_fifo_alloc(&dev.DARF, dev.fifosize)) < 0) {
+		printk(KERN_ERR LOGNAME ": Couldn't allocate read FIFO\n");
+		msnd_fifo_free(&dev.DAPF);
+		return err;
+	}
+
+	if ((err = probe_multisound()) < 0) {
+		printk(KERN_ERR LOGNAME ": Probe failed\n");
+		msnd_fifo_free(&dev.DAPF);
+		msnd_fifo_free(&dev.DARF);
+		return err;
+	}
+
+	if ((err = attach_multisound()) < 0) {
+		printk(KERN_ERR LOGNAME ": Attach failed\n");
+		msnd_fifo_free(&dev.DAPF);
+		msnd_fifo_free(&dev.DARF);
+		return err;
+	}
+
+	return 0;
+}
+
+static void __exit msdn_cleanup(void)
+{
+	unload_multisound();
+	msnd_fifo_free(&dev.DAPF);
+	msnd_fifo_free(&dev.DARF);
+}
+
+module_init(msnd_init);
+module_exit(msdn_cleanup);
diff --git a/linux/sound/oss/msnd_pinnacle.h b/linux/sound/oss/msnd_pinnacle.h
new file mode 100644
index 0000000..c18d66c
--- /dev/null
+++ b/linux/sound/oss/msnd_pinnacle.h
@@ -0,0 +1,246 @@
+/*********************************************************************
+ *
+ * msnd_pinnacle.h
+ *
+ * Turtle Beach MultiSound Sound Card Driver for Linux
+ *
+ * Some parts of this header file were derived from the Turtle Beach
+ * MultiSound Driver Development Kit.
+ *
+ * Copyright (C) 1998 Andrew Veliath
+ * Copyright (C) 1993 Turtle Beach Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ********************************************************************/
+#ifndef __MSND_PINNACLE_H
+#define __MSND_PINNACLE_H
+
+
+#define DSP_NUMIO				0x08
+
+#define IREG_LOGDEVICE				0x07
+#define IREG_ACTIVATE				0x30
+#define LD_ACTIVATE				0x01
+#define LD_DISACTIVATE				0x00
+#define IREG_EECONTROL				0x3F
+#define IREG_MEMBASEHI				0x40
+#define IREG_MEMBASELO				0x41
+#define IREG_MEMCONTROL				0x42
+#define IREG_MEMRANGEHI				0x43
+#define IREG_MEMRANGELO				0x44
+#define MEMTYPE_8BIT				0x00
+#define MEMTYPE_16BIT				0x02
+#define MEMTYPE_RANGE				0x00
+#define MEMTYPE_HIADDR				0x01
+#define IREG_IO0_BASEHI				0x60
+#define IREG_IO0_BASELO				0x61
+#define IREG_IO1_BASEHI				0x62
+#define IREG_IO1_BASELO				0x63
+#define IREG_IRQ_NUMBER				0x70
+#define IREG_IRQ_TYPE				0x71
+#define IRQTYPE_HIGH				0x02
+#define IRQTYPE_LOW				0x00
+#define IRQTYPE_LEVEL				0x01
+#define IRQTYPE_EDGE				0x00
+
+#define	HP_DSPR					0x04
+#define	HP_BLKS					0x04
+
+#define HPDSPRESET_OFF				2
+#define HPDSPRESET_ON				0
+
+#define HPBLKSEL_0				2
+#define HPBLKSEL_1				3
+
+#define	HIMT_DAT_OFF				0x03
+
+#define	HIDSP_PLAY_UNDER			0x00
+#define	HIDSP_INT_PLAY_UNDER			0x01
+#define	HIDSP_SSI_TX_UNDER  			0x02
+#define HIDSP_RECQ_OVERFLOW			0x08
+#define HIDSP_INT_RECORD_OVER			0x09
+#define HIDSP_SSI_RX_OVERFLOW			0x0a
+
+#define	HIDSP_MIDI_IN_OVER			0x10
+
+#define	HIDSP_MIDI_FRAME_ERR			0x11
+#define	HIDSP_MIDI_PARITY_ERR			0x12
+#define	HIDSP_MIDI_OVERRUN_ERR			0x13
+
+#define HIDSP_INPUT_CLIPPING			0x20
+#define	HIDSP_MIX_CLIPPING			0x30
+#define HIDSP_DAT_IN_OFF			0x21
+
+#define	HDEXAR_SET_ANA_IN			0
+#define	HDEXAR_CLEAR_PEAKS			1
+#define	HDEXAR_IN_SET_POTS			2
+#define	HDEXAR_AUX_SET_POTS			3
+#define	HDEXAR_CAL_A_TO_D			4
+#define	HDEXAR_RD_EXT_DSP_BITS			5
+
+#define	HDEXAR_SET_SYNTH_IN			4
+#define	HDEXAR_READ_DAT_IN			5
+#define	HDEXAR_MIC_SET_POTS			6
+#define	HDEXAR_SET_DAT_IN			7
+
+#define HDEXAR_SET_SYNTH_48			8
+#define HDEXAR_SET_SYNTH_44			9
+
+#define TIME_PRO_RESET_DONE			0x028A
+#define TIME_PRO_SYSEX				0x001E
+#define TIME_PRO_RESET				0x0032
+
+#define AGND					0x01
+#define SIGNAL					0x02
+
+#define EXT_DSP_BIT_DCAL			0x0001
+#define EXT_DSP_BIT_MIDI_CON			0x0002
+
+#define BUFFSIZE				0x8000
+#define HOSTQ_SIZE				0x40
+
+#define SRAM_CNTL_START				0x7F00
+#define SMA_STRUCT_START			0x7F40
+
+#define DAP_BUFF_SIZE				0x2400
+#define DAR_BUFF_SIZE				0x2000
+
+#define DAPQ_STRUCT_SIZE			0x10
+#define DARQ_STRUCT_SIZE			0x10
+#define DAPQ_BUFF_SIZE				(3 * 0x10)
+#define DARQ_BUFF_SIZE				(3 * 0x10)
+#define MODQ_BUFF_SIZE				0x400
+#define MIDQ_BUFF_SIZE				0x800
+#define DSPQ_BUFF_SIZE				0x5A0
+
+#define DAPQ_DATA_BUFF				0x6C00
+#define DARQ_DATA_BUFF				0x6C30
+#define MODQ_DATA_BUFF				0x6C60
+#define MIDQ_DATA_BUFF				0x7060
+#define DSPQ_DATA_BUFF				0x7860
+
+#define DAPQ_OFFSET				SRAM_CNTL_START
+#define DARQ_OFFSET				(SRAM_CNTL_START + 0x08)
+#define MODQ_OFFSET				(SRAM_CNTL_START + 0x10)
+#define MIDQ_OFFSET				(SRAM_CNTL_START + 0x18)
+#define DSPQ_OFFSET				(SRAM_CNTL_START + 0x20)
+
+#define MOP_WAVEHDR				0
+#define MOP_EXTOUT				1
+#define MOP_HWINIT				0xfe
+#define MOP_NONE				0xff
+#define MOP_MAX					1
+
+#define MIP_EXTIN				0
+#define MIP_WAVEHDR				1
+#define MIP_HWINIT				0xfe
+#define MIP_MAX					1
+
+/* Pinnacle/Fiji SMA Common Data */
+#define SMA_wCurrPlayBytes			0x0000
+#define SMA_wCurrRecordBytes			0x0002
+#define SMA_wCurrPlayVolLeft			0x0004
+#define SMA_wCurrPlayVolRight			0x0006
+#define SMA_wCurrInVolLeft			0x0008
+#define SMA_wCurrInVolRight			0x000a
+#define SMA_wCurrMHdrVolLeft			0x000c
+#define SMA_wCurrMHdrVolRight			0x000e
+#define SMA_dwCurrPlayPitch			0x0010
+#define SMA_dwCurrPlayRate			0x0014
+#define SMA_wCurrMIDIIOPatch			0x0018
+#define SMA_wCurrPlayFormat			0x001a
+#define SMA_wCurrPlaySampleSize			0x001c
+#define SMA_wCurrPlayChannels			0x001e
+#define SMA_wCurrPlaySampleRate			0x0020
+#define SMA_wCurrRecordFormat			0x0022
+#define SMA_wCurrRecordSampleSize		0x0024
+#define SMA_wCurrRecordChannels			0x0026
+#define SMA_wCurrRecordSampleRate		0x0028
+#define SMA_wCurrDSPStatusFlags			0x002a
+#define SMA_wCurrHostStatusFlags		0x002c
+#define SMA_wCurrInputTagBits			0x002e
+#define SMA_wCurrLeftPeak			0x0030
+#define SMA_wCurrRightPeak			0x0032
+#define SMA_bMicPotPosLeft			0x0034
+#define SMA_bMicPotPosRight			0x0035
+#define SMA_bMicPotMaxLeft			0x0036
+#define SMA_bMicPotMaxRight			0x0037
+#define SMA_bInPotPosLeft			0x0038
+#define SMA_bInPotPosRight			0x0039
+#define SMA_bAuxPotPosLeft			0x003a
+#define SMA_bAuxPotPosRight			0x003b
+#define SMA_bInPotMaxLeft			0x003c
+#define SMA_bInPotMaxRight			0x003d
+#define SMA_bAuxPotMaxLeft			0x003e
+#define SMA_bAuxPotMaxRight			0x003f
+#define SMA_bInPotMaxMethod			0x0040
+#define SMA_bAuxPotMaxMethod			0x0041
+#define SMA_wCurrMastVolLeft			0x0042
+#define SMA_wCurrMastVolRight			0x0044
+#define SMA_wCalFreqAtoD			0x0046
+#define SMA_wCurrAuxVolLeft			0x0048
+#define SMA_wCurrAuxVolRight			0x004a
+#define SMA_wCurrPlay1VolLeft			0x004c
+#define SMA_wCurrPlay1VolRight			0x004e
+#define SMA_wCurrPlay2VolLeft			0x0050
+#define SMA_wCurrPlay2VolRight			0x0052
+#define SMA_wCurrPlay3VolLeft			0x0054
+#define SMA_wCurrPlay3VolRight			0x0056
+#define SMA_wCurrPlay4VolLeft			0x0058
+#define SMA_wCurrPlay4VolRight			0x005a
+#define SMA_wCurrPlay1PeakLeft			0x005c
+#define SMA_wCurrPlay1PeakRight			0x005e
+#define SMA_wCurrPlay2PeakLeft			0x0060
+#define SMA_wCurrPlay2PeakRight			0x0062
+#define SMA_wCurrPlay3PeakLeft			0x0064
+#define SMA_wCurrPlay3PeakRight			0x0066
+#define SMA_wCurrPlay4PeakLeft			0x0068
+#define SMA_wCurrPlay4PeakRight			0x006a
+#define SMA_wCurrPlayPeakLeft			0x006c
+#define SMA_wCurrPlayPeakRight			0x006e
+#define SMA_wCurrDATSR				0x0070
+#define SMA_wCurrDATRXCHNL			0x0072
+#define SMA_wCurrDATTXCHNL			0x0074
+#define SMA_wCurrDATRXRate			0x0076
+#define SMA_dwDSPPlayCount			0x0078
+#define SMA__size				0x007c
+
+#ifdef HAVE_DSPCODEH
+#  include "pndsperm.c"
+#  include "pndspini.c"
+#  define PERMCODE		pndsperm
+#  define INITCODE		pndspini
+#  define PERMCODESIZE		sizeof(pndsperm)
+#  define INITCODESIZE		sizeof(pndspini)
+#else
+#  ifndef CONFIG_MSNDPIN_INIT_FILE
+#    define CONFIG_MSNDPIN_INIT_FILE				\
+				"/etc/sound/pndspini.bin"
+#  endif
+#  ifndef CONFIG_MSNDPIN_PERM_FILE
+#    define CONFIG_MSNDPIN_PERM_FILE				\
+				"/etc/sound/pndsperm.bin"
+#  endif
+#  define PERMCODEFILE		CONFIG_MSNDPIN_PERM_FILE
+#  define INITCODEFILE		CONFIG_MSNDPIN_INIT_FILE
+#  define PERMCODE		dspini
+#  define INITCODE		permini
+#  define PERMCODESIZE		sizeof_dspini
+#  define INITCODESIZE		sizeof_permini
+#endif
+#define LONGNAME		"MultiSound (Pinnacle/Fiji)"
+
+#endif /* __MSND_PINNACLE_H */
diff --git a/linux/sound/oss/opl3.c b/linux/sound/oss/opl3.c
new file mode 100644
index 0000000..938c48c
--- /dev/null
+++ b/linux/sound/oss/opl3.c
@@ -0,0 +1,1251 @@
+/*
+ * sound/oss/opl3.c
+ *
+ * A low level driver for Yamaha YM3812 and OPL-3 -chips
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Changes
+ *	Thomas Sailer   	ioctl code reworked (vmalloc/vfree removed)
+ *	Alan Cox		modularisation, fixed sound_mem allocs.
+ *	Christoph Hellwig	Adapted to module_init/module_exit
+ *	Arnaldo C. de Melo	get rid of check_region, use request_region for
+ *				OPL4, release it on exit, some cleanups.
+ *
+ * Status
+ *	Believed to work. Badly needs rewriting a bit to support multiple
+ *	OPL3 devices.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+/*
+ * Major improvements to the FM handling 30AUG92 by Rob Hooft,
+ * hooft@chem.ruu.nl
+ */
+
+#include "sound_config.h"
+
+#include "opl3_hw.h"
+
+#define MAX_VOICE	18
+#define OFFS_4OP	11
+
+struct voice_info
+{
+	unsigned char   keyon_byte;
+	long            bender;
+	long            bender_range;
+	unsigned long   orig_freq;
+	unsigned long   current_freq;
+	int             volume;
+	int             mode;
+	int             panning;	/* 0xffff means not set */
+};
+
+typedef struct opl_devinfo
+{
+	int             base;
+	int             left_io, right_io;
+	int             nr_voice;
+	int             lv_map[MAX_VOICE];
+
+	struct voice_info voc[MAX_VOICE];
+	struct voice_alloc_info *v_alloc;
+	struct channel_info *chn_info;
+
+	struct sbi_instrument i_map[SBFM_MAXINSTR];
+	struct sbi_instrument *act_i[MAX_VOICE];
+
+	struct synth_info fm_info;
+
+	int             busy;
+	int             model;
+	unsigned char   cmask;
+
+	int             is_opl4;
+} opl_devinfo;
+
+static struct opl_devinfo *devc = NULL;
+
+static int      detected_model;
+
+static int      store_instr(int instr_no, struct sbi_instrument *instr);
+static void     freq_to_fnum(int freq, int *block, int *fnum);
+static void     opl3_command(int io_addr, unsigned int addr, unsigned int val);
+static int      opl3_kill_note(int dev, int voice, int note, int velocity);
+
+static void enter_4op_mode(void)
+{
+	int i;
+	static int v4op[MAX_VOICE] = {
+		0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17
+	};
+
+	devc->cmask = 0x3f;	/* Connect all possible 4 OP voice operators */
+	opl3_command(devc->right_io, CONNECTION_SELECT_REGISTER, 0x3f);
+
+	for (i = 0; i < 3; i++)
+		pv_map[i].voice_mode = 4;
+	for (i = 3; i < 6; i++)
+		pv_map[i].voice_mode = 0;
+
+	for (i = 9; i < 12; i++)
+		pv_map[i].voice_mode = 4;
+	for (i = 12; i < 15; i++)
+		pv_map[i].voice_mode = 0;
+
+	for (i = 0; i < 12; i++)
+		devc->lv_map[i] = v4op[i];
+	devc->v_alloc->max_voice = devc->nr_voice = 12;
+}
+
+static int opl3_ioctl(int dev, unsigned int cmd, void __user * arg)
+{
+	struct sbi_instrument ins;
+	
+	switch (cmd) {
+		case SNDCTL_FM_LOAD_INSTR:
+			printk(KERN_WARNING "Warning: Obsolete ioctl(SNDCTL_FM_LOAD_INSTR) used. Fix the program.\n");
+			if (copy_from_user(&ins, arg, sizeof(ins)))
+				return -EFAULT;
+			if (ins.channel < 0 || ins.channel >= SBFM_MAXINSTR) {
+				printk(KERN_WARNING "FM Error: Invalid instrument number %d\n", ins.channel);
+				return -EINVAL;
+			}
+			return store_instr(ins.channel, &ins);
+
+		case SNDCTL_SYNTH_INFO:
+			devc->fm_info.nr_voices = (devc->nr_voice == 12) ? 6 : devc->nr_voice;
+			if (copy_to_user(arg, &devc->fm_info, sizeof(devc->fm_info)))
+				return -EFAULT;
+			return 0;
+
+		case SNDCTL_SYNTH_MEMAVL:
+			return 0x7fffffff;
+
+		case SNDCTL_FM_4OP_ENABLE:
+			if (devc->model == 2)
+				enter_4op_mode();
+			return 0;
+
+		default:
+			return -EINVAL;
+	}
+}
+
+static int opl3_detect(int ioaddr)
+{
+	/*
+	 * This function returns 1 if the FM chip is present at the given I/O port
+	 * The detection algorithm plays with the timer built in the FM chip and
+	 * looks for a change in the status register.
+	 *
+	 * Note! The timers of the FM chip are not connected to AdLib (and compatible)
+	 * boards.
+	 *
+	 * Note2! The chip is initialized if detected.
+	 */
+
+	unsigned char stat1, signature;
+	int i;
+
+	if (devc != NULL)
+	{
+		printk(KERN_ERR "opl3: Only one OPL3 supported.\n");
+		return 0;
+	}
+
+	devc = kzalloc(sizeof(*devc), GFP_KERNEL);
+
+	if (devc == NULL)
+	{
+		printk(KERN_ERR "opl3: Can't allocate memory for the device control "
+			"structure \n ");
+		return 0;
+	}
+
+	strcpy(devc->fm_info.name, "OPL2");
+
+	if (!request_region(ioaddr, 4, devc->fm_info.name)) {
+		printk(KERN_WARNING "opl3: I/O port 0x%x already in use\n", ioaddr);
+		goto cleanup_devc;
+	}
+
+	devc->base = ioaddr;
+
+	/* Reset timers 1 and 2 */
+	opl3_command(ioaddr, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK);
+
+	/* Reset the IRQ of the FM chip */
+	opl3_command(ioaddr, TIMER_CONTROL_REGISTER, IRQ_RESET);
+
+	signature = stat1 = inb(ioaddr);	/* Status register */
+
+	if (signature != 0x00 && signature != 0x06 && signature != 0x02 &&
+		signature != 0x0f)
+	{
+		MDB(printk(KERN_INFO "OPL3 not detected %x\n", signature));
+		goto cleanup_region;
+	}
+
+	if (signature == 0x06)		/* OPL2 */
+	{
+		detected_model = 2;
+	}
+	else if (signature == 0x00 || signature == 0x0f)	/* OPL3 or OPL4 */
+	{
+		unsigned char tmp;
+
+		detected_model = 3;
+
+		/*
+		 * Detect availability of OPL4 (_experimental_). Works probably
+		 * only after a cold boot. In addition the OPL4 port
+		 * of the chip may not be connected to the PC bus at all.
+		 */
+
+		opl3_command(ioaddr + 2, OPL3_MODE_REGISTER, 0x00);
+		opl3_command(ioaddr + 2, OPL3_MODE_REGISTER, OPL3_ENABLE | OPL4_ENABLE);
+
+		if ((tmp = inb(ioaddr)) == 0x02)	/* Have a OPL4 */
+		{
+			detected_model = 4;
+		}
+
+		if (request_region(ioaddr - 8, 2, "OPL4"))	/* OPL4 port was free */
+		{
+			int tmp;
+
+			outb((0x02), ioaddr - 8);	/* Select OPL4 ID register */
+			udelay(10);
+			tmp = inb(ioaddr - 7);		/* Read it */
+			udelay(10);
+
+			if (tmp == 0x20)	/* OPL4 should return 0x20 here */
+			{
+				detected_model = 4;
+				outb((0xF8), ioaddr - 8);	/* Select OPL4 FM mixer control */
+				udelay(10);
+				outb((0x1B), ioaddr - 7);	/* Write value */
+				udelay(10);
+			}
+			else
+			{ /* release OPL4 port */
+				release_region(ioaddr - 8, 2);
+				detected_model = 3;
+			}
+		}
+		opl3_command(ioaddr + 2, OPL3_MODE_REGISTER, 0);
+	}
+	for (i = 0; i < 9; i++)
+		opl3_command(ioaddr, KEYON_BLOCK + i, 0);	/*
+								 * Note off
+								 */
+
+	opl3_command(ioaddr, TEST_REGISTER, ENABLE_WAVE_SELECT);
+	opl3_command(ioaddr, PERCOSSION_REGISTER, 0x00);	/*
+								 * Melodic mode.
+								 */
+	return 1;
+cleanup_region:
+	release_region(ioaddr, 4);
+cleanup_devc:
+	kfree(devc);
+	devc = NULL;
+	return 0;
+}
+
+static int opl3_kill_note  (int devno, int voice, int note, int velocity)
+{
+	 struct physical_voice_info *map;
+
+	 if (voice < 0 || voice >= devc->nr_voice)
+		 return 0;
+
+	 devc->v_alloc->map[voice] = 0;
+
+	 map = &pv_map[devc->lv_map[voice]];
+	 DEB(printk("Kill note %d\n", voice));
+
+	 if (map->voice_mode == 0)
+		 return 0;
+
+	 opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num, devc->voc[voice].keyon_byte & ~0x20);
+	 devc->voc[voice].keyon_byte = 0;
+	 devc->voc[voice].bender = 0;
+	 devc->voc[voice].volume = 64;
+	 devc->voc[voice].panning = 0xffff;	/* Not set */
+	 devc->voc[voice].bender_range = 200;
+	 devc->voc[voice].orig_freq = 0;
+	 devc->voc[voice].current_freq = 0;
+	 devc->voc[voice].mode = 0;
+	 return 0;
+}
+
+#define HIHAT			0
+#define CYMBAL			1
+#define TOMTOM			2
+#define SNARE			3
+#define BDRUM			4
+#define UNDEFINED		TOMTOM
+#define DEFAULT			TOMTOM
+
+static int store_instr(int instr_no, struct sbi_instrument *instr)
+{
+	if (instr->key != FM_PATCH && (instr->key != OPL3_PATCH || devc->model != 2))
+		printk(KERN_WARNING "FM warning: Invalid patch format field (key) 0x%x\n", instr->key);
+	memcpy((char *) &(devc->i_map[instr_no]), (char *) instr, sizeof(*instr));
+	return 0;
+}
+
+static int opl3_set_instr  (int dev, int voice, int instr_no)
+{
+	if (voice < 0 || voice >= devc->nr_voice)
+		return 0;
+	if (instr_no < 0 || instr_no >= SBFM_MAXINSTR)
+		instr_no = 0;	/* Acoustic piano (usually) */
+
+	devc->act_i[voice] = &devc->i_map[instr_no];
+	return 0;
+}
+
+/*
+ * The next table looks magical, but it certainly is not. Its values have
+ * been calculated as table[i]=8*log(i/64)/log(2) with an obvious exception
+ * for i=0. This log-table converts a linear volume-scaling (0..127) to a
+ * logarithmic scaling as present in the FM-synthesizer chips. so :    Volume
+ * 64 =  0 db = relative volume  0 and:    Volume 32 = -6 db = relative
+ * volume -8 it was implemented as a table because it is only 128 bytes and
+ * it saves a lot of log() calculations. (RH)
+ */
+
+static char fm_volume_table[128] =
+{
+	-64, -48, -40, -35, -32, -29, -27, -26,
+	-24, -23, -21, -20, -19, -18, -18, -17,
+	-16, -15, -15, -14, -13, -13, -12, -12,
+	-11, -11, -10, -10, -10, -9, -9, -8,
+	-8, -8, -7, -7, -7, -6, -6, -6,
+	-5, -5, -5, -5, -4, -4, -4, -4,
+	-3, -3, -3, -3, -2, -2, -2, -2,
+	-2, -1, -1, -1, -1, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1,
+	1, 2, 2, 2, 2, 2, 2, 2,
+	3, 3, 3, 3, 3, 3, 3, 4,
+	4, 4, 4, 4, 4, 4, 4, 5,
+	5, 5, 5, 5, 5, 5, 5, 5,
+	6, 6, 6, 6, 6, 6, 6, 6,
+	6, 7, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 8, 8, 8, 8, 8
+};
+
+static void calc_vol(unsigned char *regbyte, int volume, int main_vol)
+{
+	int level = (~*regbyte & 0x3f);
+
+	if (main_vol > 127)
+		main_vol = 127;
+	volume = (volume * main_vol) / 127;
+
+	if (level)
+		level += fm_volume_table[volume];
+
+	if (level > 0x3f)
+		level = 0x3f;
+	if (level < 0)
+		level = 0;
+
+	*regbyte = (*regbyte & 0xc0) | (~level & 0x3f);
+}
+
+static void set_voice_volume(int voice, int volume, int main_vol)
+{
+	unsigned char vol1, vol2, vol3, vol4;
+	struct sbi_instrument *instr;
+	struct physical_voice_info *map;
+
+	if (voice < 0 || voice >= devc->nr_voice)
+		return;
+
+	map = &pv_map[devc->lv_map[voice]];
+	instr = devc->act_i[voice];
+
+	if (!instr)
+		instr = &devc->i_map[0];
+
+	if (instr->channel < 0)
+		return;
+
+	if (devc->voc[voice].mode == 0)
+		return;
+
+	if (devc->voc[voice].mode == 2)
+	{
+		vol1 = instr->operators[2];
+		vol2 = instr->operators[3];
+		if ((instr->operators[10] & 0x01))
+		{
+			calc_vol(&vol1, volume, main_vol);
+			calc_vol(&vol2, volume, main_vol);
+		}
+		else
+		{
+			calc_vol(&vol2, volume, main_vol);
+		}
+		opl3_command(map->ioaddr, KSL_LEVEL + map->op[0], vol1);
+		opl3_command(map->ioaddr, KSL_LEVEL + map->op[1], vol2);
+	}
+	else
+	{	/*
+		 * 4 OP voice
+		 */
+		int connection;
+
+		vol1 = instr->operators[2];
+		vol2 = instr->operators[3];
+		vol3 = instr->operators[OFFS_4OP + 2];
+		vol4 = instr->operators[OFFS_4OP + 3];
+
+		/*
+		 * The connection method for 4 OP devc->voc is defined by the rightmost
+		 * bits at the offsets 10 and 10+OFFS_4OP
+		 */
+
+		connection = ((instr->operators[10] & 0x01) << 1) | (instr->operators[10 + OFFS_4OP] & 0x01);
+
+		switch (connection)
+		{
+			case 0:
+				calc_vol(&vol4, volume, main_vol);
+				break;
+
+			case 1:
+				calc_vol(&vol2, volume, main_vol);
+				calc_vol(&vol4, volume, main_vol);
+				break;
+
+			case 2:
+				calc_vol(&vol1, volume, main_vol);
+				calc_vol(&vol4, volume, main_vol);
+				break;
+
+			case 3:
+				calc_vol(&vol1, volume, main_vol);
+				calc_vol(&vol3, volume, main_vol);
+				calc_vol(&vol4, volume, main_vol);
+				break;
+
+			default:
+				;
+		}
+		opl3_command(map->ioaddr, KSL_LEVEL + map->op[0], vol1);
+		opl3_command(map->ioaddr, KSL_LEVEL + map->op[1], vol2);
+		opl3_command(map->ioaddr, KSL_LEVEL + map->op[2], vol3);
+		opl3_command(map->ioaddr, KSL_LEVEL + map->op[3], vol4);
+	}
+}
+
+static int opl3_start_note (int dev, int voice, int note, int volume)
+{
+	unsigned char data, fpc;
+	int block, fnum, freq, voice_mode, pan;
+	struct sbi_instrument *instr;
+	struct physical_voice_info *map;
+
+	if (voice < 0 || voice >= devc->nr_voice)
+		return 0;
+
+	map = &pv_map[devc->lv_map[voice]];
+	pan = devc->voc[voice].panning;
+
+	if (map->voice_mode == 0)
+		return 0;
+
+	if (note == 255)	/*
+				 * Just change the volume
+				 */
+	{
+		set_voice_volume(voice, volume, devc->voc[voice].volume);
+		return 0;
+	}
+
+	/*
+	 * Kill previous note before playing
+	 */
+	
+	opl3_command(map->ioaddr, KSL_LEVEL + map->op[1], 0xff);	/*
+									 * Carrier
+									 * volume to
+									 * min
+									 */
+	opl3_command(map->ioaddr, KSL_LEVEL + map->op[0], 0xff);	/*
+									 * Modulator
+									 * volume to
+									 */
+
+	if (map->voice_mode == 4)
+	{
+		opl3_command(map->ioaddr, KSL_LEVEL + map->op[2], 0xff);
+		opl3_command(map->ioaddr, KSL_LEVEL + map->op[3], 0xff);
+	}
+
+	opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num, 0x00);	/*
+									 * Note
+									 * off
+									 */
+
+	instr = devc->act_i[voice];
+	
+	if (!instr)
+		instr = &devc->i_map[0];
+
+	if (instr->channel < 0)
+	{
+		printk(KERN_WARNING "opl3: Initializing voice %d with undefined instrument\n", voice);
+		return 0;
+	}
+
+	if (map->voice_mode == 2 && instr->key == OPL3_PATCH)
+		return 0;	/*
+				 * Cannot play
+				 */
+
+	voice_mode = map->voice_mode;
+
+	if (voice_mode == 4)
+	{
+		int voice_shift;
+
+		voice_shift = (map->ioaddr == devc->left_io) ? 0 : 3;
+		voice_shift += map->voice_num;
+
+		if (instr->key != OPL3_PATCH)	/*
+						 * Just 2 OP patch
+						 */
+		{
+			voice_mode = 2;
+			devc->cmask &= ~(1 << voice_shift);
+		}
+		else
+		{
+			devc->cmask |= (1 << voice_shift);
+		}
+
+		opl3_command(devc->right_io, CONNECTION_SELECT_REGISTER, devc->cmask);
+	}
+
+	/*
+	 * Set Sound Characteristics
+	 */
+	
+	opl3_command(map->ioaddr, AM_VIB + map->op[0], instr->operators[0]);
+	opl3_command(map->ioaddr, AM_VIB + map->op[1], instr->operators[1]);
+
+	/*
+	 * Set Attack/Decay
+	 */
+	
+	opl3_command(map->ioaddr, ATTACK_DECAY + map->op[0], instr->operators[4]);
+	opl3_command(map->ioaddr, ATTACK_DECAY + map->op[1], instr->operators[5]);
+
+	/*
+	 * Set Sustain/Release
+	 */
+	
+	opl3_command(map->ioaddr, SUSTAIN_RELEASE + map->op[0], instr->operators[6]);
+	opl3_command(map->ioaddr, SUSTAIN_RELEASE + map->op[1], instr->operators[7]);
+
+	/*
+	 * Set Wave Select
+	 */
+
+	opl3_command(map->ioaddr, WAVE_SELECT + map->op[0], instr->operators[8]);
+	opl3_command(map->ioaddr, WAVE_SELECT + map->op[1], instr->operators[9]);
+
+	/*
+	 * Set Feedback/Connection
+	 */
+	
+	fpc = instr->operators[10];
+
+	if (pan != 0xffff)
+	{
+		fpc &= ~STEREO_BITS;
+		if (pan < -64)
+			fpc |= VOICE_TO_LEFT;
+		else
+			if (pan > 64)
+				fpc |= VOICE_TO_RIGHT;
+			else
+				fpc |= (VOICE_TO_LEFT | VOICE_TO_RIGHT);
+	}
+
+	if (!(fpc & 0x30))
+		fpc |= 0x30;	/*
+				 * Ensure that at least one chn is enabled
+				 */
+	opl3_command(map->ioaddr, FEEDBACK_CONNECTION + map->voice_num, fpc);
+
+	/*
+	 * If the voice is a 4 OP one, initialize the operators 3 and 4 also
+	 */
+
+	if (voice_mode == 4)
+	{
+		/*
+		 * Set Sound Characteristics
+		 */
+	
+		opl3_command(map->ioaddr, AM_VIB + map->op[2], instr->operators[OFFS_4OP + 0]);
+		opl3_command(map->ioaddr, AM_VIB + map->op[3], instr->operators[OFFS_4OP + 1]);
+
+		/*
+		 * Set Attack/Decay
+		 */
+		
+		opl3_command(map->ioaddr, ATTACK_DECAY + map->op[2], instr->operators[OFFS_4OP + 4]);
+		opl3_command(map->ioaddr, ATTACK_DECAY + map->op[3], instr->operators[OFFS_4OP + 5]);
+
+		/*
+		 * Set Sustain/Release
+		 */
+		
+		opl3_command(map->ioaddr, SUSTAIN_RELEASE + map->op[2], instr->operators[OFFS_4OP + 6]);
+		opl3_command(map->ioaddr, SUSTAIN_RELEASE + map->op[3], instr->operators[OFFS_4OP + 7]);
+
+		/*
+		 * Set Wave Select
+		 */
+		
+		opl3_command(map->ioaddr, WAVE_SELECT + map->op[2], instr->operators[OFFS_4OP + 8]);
+		opl3_command(map->ioaddr, WAVE_SELECT + map->op[3], instr->operators[OFFS_4OP + 9]);
+
+		/*
+		 * Set Feedback/Connection
+		 */
+		
+		fpc = instr->operators[OFFS_4OP + 10];
+		if (!(fpc & 0x30))
+			 fpc |= 0x30;	/*
+					 * Ensure that at least one chn is enabled
+					 */
+		opl3_command(map->ioaddr, FEEDBACK_CONNECTION + map->voice_num + 3, fpc);
+	}
+
+	devc->voc[voice].mode = voice_mode;
+	set_voice_volume(voice, volume, devc->voc[voice].volume);
+
+	freq = devc->voc[voice].orig_freq = note_to_freq(note) / 1000;
+
+	/*
+	 * Since the pitch bender may have been set before playing the note, we
+	 * have to calculate the bending now.
+	 */
+
+	freq = compute_finetune(devc->voc[voice].orig_freq, devc->voc[voice].bender, devc->voc[voice].bender_range, 0);
+	devc->voc[voice].current_freq = freq;
+
+	freq_to_fnum(freq, &block, &fnum);
+
+	/*
+	 * Play note
+	 */
+
+	data = fnum & 0xff;	/*
+				 * Least significant bits of fnumber
+				 */
+	opl3_command(map->ioaddr, FNUM_LOW + map->voice_num, data);
+
+	data = 0x20 | ((block & 0x7) << 2) | ((fnum >> 8) & 0x3);
+		 devc->voc[voice].keyon_byte = data;
+	opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num, data);
+	if (voice_mode == 4)
+		opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num + 3, data);
+
+	return 0;
+}
+
+static void freq_to_fnum    (int freq, int *block, int *fnum)
+{
+	int f, octave;
+
+	/*
+	 * Converts the note frequency to block and fnum values for the FM chip
+	 */
+	/*
+	 * First try to compute the block -value (octave) where the note belongs
+	 */
+
+	f = freq;
+
+	octave = 5;
+
+	if (f == 0)
+		octave = 0;
+	else if (f < 261)
+	{
+		while (f < 261)
+		{
+			octave--;
+			f <<= 1;
+		}
+	}
+	else if (f > 493)
+	{
+		while (f > 493)
+		{
+			 octave++;
+			 f >>= 1;
+		}
+	}
+
+	if (octave > 7)
+		octave = 7;
+
+	*fnum = freq * (1 << (20 - octave)) / 49716;
+	*block = octave;
+}
+
+static void opl3_command    (int io_addr, unsigned int addr, unsigned int val)
+{
+	 int i;
+
+	/*
+	 * The original 2-OP synth requires a quite long delay after writing to a
+	 * register. The OPL-3 survives with just two INBs
+	 */
+
+	outb(((unsigned char) (addr & 0xff)), io_addr);
+
+	if (devc->model != 2)
+		udelay(10);
+	else
+		for (i = 0; i < 2; i++)
+			inb(io_addr);
+
+	outb(((unsigned char) (val & 0xff)), io_addr + 1);
+
+	if (devc->model != 2)
+		udelay(30);
+	else
+		for (i = 0; i < 2; i++)
+			inb(io_addr);
+}
+
+static void opl3_reset(int devno)
+{
+	int i;
+
+	for (i = 0; i < 18; i++)
+		devc->lv_map[i] = i;
+
+	for (i = 0; i < devc->nr_voice; i++)
+	{
+		opl3_command(pv_map[devc->lv_map[i]].ioaddr,
+			KSL_LEVEL + pv_map[devc->lv_map[i]].op[0], 0xff);
+
+		opl3_command(pv_map[devc->lv_map[i]].ioaddr,
+			KSL_LEVEL + pv_map[devc->lv_map[i]].op[1], 0xff);
+
+		if (pv_map[devc->lv_map[i]].voice_mode == 4)
+		{
+			opl3_command(pv_map[devc->lv_map[i]].ioaddr,
+				KSL_LEVEL + pv_map[devc->lv_map[i]].op[2], 0xff);
+
+			opl3_command(pv_map[devc->lv_map[i]].ioaddr,
+				KSL_LEVEL + pv_map[devc->lv_map[i]].op[3], 0xff);
+		}
+
+		opl3_kill_note(devno, i, 0, 64);
+	}
+
+	if (devc->model == 2)
+	{
+		devc->v_alloc->max_voice = devc->nr_voice = 18;
+
+		for (i = 0; i < 18; i++)
+			pv_map[i].voice_mode = 2;
+
+	}
+}
+
+static int opl3_open(int dev, int mode)
+{
+	int i;
+
+	if (devc->busy)
+		return -EBUSY;
+	devc->busy = 1;
+
+	devc->v_alloc->max_voice = devc->nr_voice = (devc->model == 2) ? 18 : 9;
+	devc->v_alloc->timestamp = 0;
+
+	for (i = 0; i < 18; i++)
+	{
+		devc->v_alloc->map[i] = 0;
+		devc->v_alloc->alloc_times[i] = 0;
+	}
+
+	devc->cmask = 0x00;	/*
+				 * Just 2 OP mode
+				 */
+	if (devc->model == 2)
+		opl3_command(devc->right_io, CONNECTION_SELECT_REGISTER, devc->cmask);
+	return 0;
+}
+
+static void opl3_close(int dev)
+{
+	devc->busy = 0;
+	devc->v_alloc->max_voice = devc->nr_voice = (devc->model == 2) ? 18 : 9;
+
+	devc->fm_info.nr_drums = 0;
+	devc->fm_info.perc_mode = 0;
+
+	opl3_reset(dev);
+}
+
+static void opl3_hw_control(int dev, unsigned char *event)
+{
+}
+
+static int opl3_load_patch(int dev, int format, const char __user *addr,
+		int offs, int count, int pmgr_flag)
+{
+	struct sbi_instrument ins;
+
+	if (count <sizeof(ins))
+	{
+		printk(KERN_WARNING "FM Error: Patch record too short\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * What the fuck is going on here?  We leave junk in the beginning
+	 * of ins and then check the field pretty close to that beginning?
+	 */
+	if(copy_from_user(&((char *) &ins)[offs], addr + offs, sizeof(ins) - offs))
+		return -EFAULT;
+
+	if (ins.channel < 0 || ins.channel >= SBFM_MAXINSTR)
+	{
+		printk(KERN_WARNING "FM Error: Invalid instrument number %d\n", ins.channel);
+		return -EINVAL;
+	}
+	ins.key = format;
+
+	return store_instr(ins.channel, &ins);
+}
+
+static void opl3_panning(int dev, int voice, int value)
+{
+	devc->voc[voice].panning = value;
+}
+
+static void opl3_volume_method(int dev, int mode)
+{
+}
+
+#define SET_VIBRATO(cell) { \
+	tmp = instr->operators[(cell-1)+(((cell-1)/2)*OFFS_4OP)]; \
+	if (pressure > 110) \
+		tmp |= 0x40;		/* Vibrato on */ \
+	opl3_command (map->ioaddr, AM_VIB + map->op[cell-1], tmp);}
+
+static void opl3_aftertouch(int dev, int voice, int pressure)
+{
+	int tmp;
+	struct sbi_instrument *instr;
+	struct physical_voice_info *map;
+
+	if (voice < 0 || voice >= devc->nr_voice)
+		return;
+
+	map = &pv_map[devc->lv_map[voice]];
+
+	DEB(printk("Aftertouch %d\n", voice));
+
+	if (map->voice_mode == 0)
+		return;
+
+	/*
+	 * Adjust the amount of vibrato depending the pressure
+	 */
+
+	instr = devc->act_i[voice];
+
+	if (!instr)
+		instr = &devc->i_map[0];
+
+	if (devc->voc[voice].mode == 4)
+	{
+		int connection = ((instr->operators[10] & 0x01) << 1) | (instr->operators[10 + OFFS_4OP] & 0x01);
+
+		switch (connection)
+		{
+			case 0:
+				SET_VIBRATO(4);
+				break;
+
+			case 1:
+				SET_VIBRATO(2);
+				SET_VIBRATO(4);
+				break;
+
+			case 2:
+				SET_VIBRATO(1);
+				SET_VIBRATO(4);
+				break;
+
+			case 3:
+				SET_VIBRATO(1);
+				SET_VIBRATO(3);
+				SET_VIBRATO(4);
+				break;
+
+		}
+		/*
+		 * Not implemented yet
+		 */
+	}
+	else
+	{
+		SET_VIBRATO(1);
+
+		if ((instr->operators[10] & 0x01))	/*
+							 * Additive synthesis
+							 */
+			SET_VIBRATO(2);
+	}
+}
+
+#undef SET_VIBRATO
+
+static void bend_pitch(int dev, int voice, int value)
+{
+	unsigned char data;
+	int block, fnum, freq;
+	struct physical_voice_info *map;
+
+	map = &pv_map[devc->lv_map[voice]];
+
+	if (map->voice_mode == 0)
+		return;
+
+	devc->voc[voice].bender = value;
+	if (!value)
+		return;
+	if (!(devc->voc[voice].keyon_byte & 0x20))
+		return;	/*
+			 * Not keyed on
+			 */
+
+	freq = compute_finetune(devc->voc[voice].orig_freq, devc->voc[voice].bender, devc->voc[voice].bender_range, 0);
+	devc->voc[voice].current_freq = freq;
+
+	freq_to_fnum(freq, &block, &fnum);
+
+	data = fnum & 0xff;	/*
+				 * Least significant bits of fnumber
+				 */
+	opl3_command(map->ioaddr, FNUM_LOW + map->voice_num, data);
+
+	data = 0x20 | ((block & 0x7) << 2) | ((fnum >> 8) & 0x3);
+	devc->voc[voice].keyon_byte = data;
+	opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num, data);
+}
+
+static void opl3_controller (int dev, int voice, int ctrl_num, int value)
+{
+	if (voice < 0 || voice >= devc->nr_voice)
+		return;
+
+	switch (ctrl_num)
+	{
+		case CTRL_PITCH_BENDER:
+			bend_pitch(dev, voice, value);
+			break;
+
+		case CTRL_PITCH_BENDER_RANGE:
+			devc->voc[voice].bender_range = value;
+			break;
+
+		case CTL_MAIN_VOLUME:
+			devc->voc[voice].volume = value / 128;
+			break;
+
+		case CTL_PAN:
+			devc->voc[voice].panning = (value * 2) - 128;
+			break;
+	}
+}
+
+static void opl3_bender(int dev, int voice, int value)
+{
+	if (voice < 0 || voice >= devc->nr_voice)
+		return;
+
+	bend_pitch(dev, voice, value - 8192);
+}
+
+static int opl3_alloc_voice(int dev, int chn, int note, struct voice_alloc_info *alloc)
+{
+	int i, p, best, first, avail, best_time = 0x7fffffff;
+	struct sbi_instrument *instr;
+	int is4op;
+	int instr_no;
+
+	if (chn < 0 || chn > 15)
+		instr_no = 0;
+	else
+		instr_no = devc->chn_info[chn].pgm_num;
+
+	instr = &devc->i_map[instr_no];
+	if (instr->channel < 0 ||	/* Instrument not loaded */
+		devc->nr_voice != 12)	/* Not in 4 OP mode */
+		is4op = 0;
+	else if (devc->nr_voice == 12)	/* 4 OP mode */
+		is4op = (instr->key == OPL3_PATCH);
+	else
+		is4op = 0;
+
+	if (is4op)
+	{
+		first = p = 0;
+		avail = 6;
+	}
+	else
+	{
+		if (devc->nr_voice == 12)	/* 4 OP mode. Use the '2 OP only' operators first */
+			first = p = 6;
+		else
+			first = p = 0;
+		avail = devc->nr_voice;
+	}
+
+	/*
+	 *    Now try to find a free voice
+	 */
+	best = first;
+
+	for (i = 0; i < avail; i++)
+	{
+		if (alloc->map[p] == 0)
+		{
+			return p;
+		}
+		if (alloc->alloc_times[p] < best_time)		/* Find oldest playing note */
+		{
+			best_time = alloc->alloc_times[p];
+			best = p;
+		}
+		p = (p + 1) % avail;
+	}
+
+	/*
+	 *    Insert some kind of priority mechanism here.
+	 */
+
+	if (best < 0)
+		best = 0;
+	if (best > devc->nr_voice)
+		best -= devc->nr_voice;
+
+	return best;	/* All devc->voc in use. Select the first one. */
+}
+
+static void opl3_setup_voice(int dev, int voice, int chn)
+{
+	struct channel_info *info =
+	&synth_devs[dev]->chn_info[chn];
+
+	opl3_set_instr(dev, voice, info->pgm_num);
+
+	devc->voc[voice].bender = 0;
+	devc->voc[voice].bender_range = info->bender_range;
+	devc->voc[voice].volume = info->controllers[CTL_MAIN_VOLUME];
+	devc->voc[voice].panning = (info->controllers[CTL_PAN] * 2) - 128;
+}
+
+static struct synth_operations opl3_operations =
+{
+	.owner		= THIS_MODULE,
+	.id		= "OPL",
+	.info		= NULL,
+	.midi_dev	= 0,
+	.synth_type	= SYNTH_TYPE_FM,
+	.synth_subtype	= FM_TYPE_ADLIB,
+	.open		= opl3_open,
+	.close		= opl3_close,
+	.ioctl		= opl3_ioctl,
+	.kill_note	= opl3_kill_note,
+	.start_note	= opl3_start_note,
+	.set_instr	= opl3_set_instr,
+	.reset		= opl3_reset,
+	.hw_control	= opl3_hw_control,
+	.load_patch	= opl3_load_patch,
+	.aftertouch	= opl3_aftertouch,
+	.controller	= opl3_controller,
+	.panning	= opl3_panning,
+	.volume_method	= opl3_volume_method,
+	.bender		= opl3_bender,
+	.alloc_voice	= opl3_alloc_voice,
+	.setup_voice	= opl3_setup_voice
+};
+
+static int opl3_init(int ioaddr, struct module *owner)
+{
+	int i;
+	int me;
+
+	if (devc == NULL)
+	{
+		printk(KERN_ERR "opl3: Device control structure not initialized.\n");
+		return -1;
+	}
+
+	if ((me = sound_alloc_synthdev()) == -1)
+	{
+		printk(KERN_WARNING "opl3: Too many synthesizers\n");
+		return -1;
+	}
+
+	devc->nr_voice = 9;
+
+	devc->fm_info.device = 0;
+	devc->fm_info.synth_type = SYNTH_TYPE_FM;
+	devc->fm_info.synth_subtype = FM_TYPE_ADLIB;
+	devc->fm_info.perc_mode = 0;
+	devc->fm_info.nr_voices = 9;
+	devc->fm_info.nr_drums = 0;
+	devc->fm_info.instr_bank_size = SBFM_MAXINSTR;
+	devc->fm_info.capabilities = 0;
+	devc->left_io = ioaddr;
+	devc->right_io = ioaddr + 2;
+
+	if (detected_model <= 2)
+		devc->model = 1;
+	else
+	{
+		devc->model = 2;
+		if (detected_model == 4)
+			devc->is_opl4 = 1;
+	}
+
+	opl3_operations.info = &devc->fm_info;
+
+	synth_devs[me] = &opl3_operations;
+
+	if (owner)
+		synth_devs[me]->owner = owner;
+	
+	sequencer_init();
+	devc->v_alloc = &opl3_operations.alloc;
+	devc->chn_info = &opl3_operations.chn_info[0];
+
+	if (devc->model == 2)
+	{
+		if (devc->is_opl4) 
+			strcpy(devc->fm_info.name, "Yamaha OPL4/OPL3 FM");
+		else 
+			strcpy(devc->fm_info.name, "Yamaha OPL3");
+
+		devc->v_alloc->max_voice = devc->nr_voice = 18;
+		devc->fm_info.nr_drums = 0;
+		devc->fm_info.synth_subtype = FM_TYPE_OPL3;
+		devc->fm_info.capabilities |= SYNTH_CAP_OPL3;
+
+		for (i = 0; i < 18; i++)
+		{
+			if (pv_map[i].ioaddr == USE_LEFT)
+				pv_map[i].ioaddr = devc->left_io;
+			else
+				pv_map[i].ioaddr = devc->right_io;
+		}
+		opl3_command(devc->right_io, OPL3_MODE_REGISTER, OPL3_ENABLE);
+		opl3_command(devc->right_io, CONNECTION_SELECT_REGISTER, 0x00);
+	}
+	else
+	{
+		strcpy(devc->fm_info.name, "Yamaha OPL2");
+		devc->v_alloc->max_voice = devc->nr_voice = 9;
+		devc->fm_info.nr_drums = 0;
+
+		for (i = 0; i < 18; i++)
+			pv_map[i].ioaddr = devc->left_io;
+	};
+	conf_printf2(devc->fm_info.name, ioaddr, 0, -1, -1);
+
+	for (i = 0; i < SBFM_MAXINSTR; i++)
+		devc->i_map[i].channel = -1;
+
+	return me;
+}
+
+static int me;
+
+static int io = -1;
+
+module_param(io, int, 0);
+
+static int __init init_opl3 (void)
+{
+	printk(KERN_INFO "YM3812 and OPL-3 driver Copyright (C) by Hannu Savolainen, Rob Hooft 1993-1996\n");
+
+	if (io != -1)	/* User loading pure OPL3 module */
+	{
+		if (!opl3_detect(io))
+		{
+			return -ENODEV;
+		}
+
+		me = opl3_init(io, THIS_MODULE);
+	}
+
+	return 0;
+}
+
+static void __exit cleanup_opl3(void)
+{
+	if (devc && io != -1)
+	{
+		if (devc->base) {
+			release_region(devc->base,4);
+			if (devc->is_opl4)
+				release_region(devc->base - 8, 2);
+		}
+		kfree(devc);
+		devc = NULL;
+		sound_unload_synthdev(me);
+	}
+}
+
+module_init(init_opl3);
+module_exit(cleanup_opl3);
+
+#ifndef MODULE
+static int __init setup_opl3(char *str)
+{
+        /* io  */
+	int ints[2];
+	
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+	
+	io = ints[1];
+
+	return 1;
+}
+
+__setup("opl3=", setup_opl3);
+#endif
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/opl3_hw.h b/linux/sound/oss/opl3_hw.h
new file mode 100644
index 0000000..8b11c89
--- /dev/null
+++ b/linux/sound/oss/opl3_hw.h
@@ -0,0 +1,246 @@
+/*
+ *	opl3_hw.h	- Definitions of the OPL-3 registers
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ *	The OPL-3 mode is switched on by writing 0x01, to the offset 5
+ *	of the right side.
+ *
+ *	Another special register at the right side is at offset 4. It contains
+ *	a bit mask defining which voices are used as 4 OP voices.
+ *
+ *	The percussive mode is implemented in the left side only.
+ *
+ *	With the above exceptions the both sides can be operated independently.
+ *	
+ *	A 4 OP voice can be created by setting the corresponding
+ *	bit at offset 4 of the right side.
+ *
+ *	For example setting the rightmost bit (0x01) changes the
+ *	first voice on the right side to the 4 OP mode. The fourth
+ *	voice is made inaccessible.
+ *
+ *	If a voice is set to the 2 OP mode, it works like 2 OP modes
+ *	of the original YM3812 (AdLib). In addition the voice can 
+ *	be connected the left, right or both stereo channels. It can
+ *	even be left unconnected. This works with 4 OP voices also.
+ *
+ *	The stereo connection bits are located in the FEEDBACK_CONNECTION
+ *	register of the voice (0xC0-0xC8). In 4 OP voices these bits are
+ *	in the second half of the voice.
+ */
+
+/*
+ *	Register numbers for the global registers
+ */
+
+#define TEST_REGISTER				0x01
+#define   ENABLE_WAVE_SELECT		0x20
+
+#define TIMER1_REGISTER				0x02
+#define TIMER2_REGISTER				0x03
+#define TIMER_CONTROL_REGISTER			0x04	/* Left side */
+#define   IRQ_RESET			0x80
+#define   TIMER1_MASK			0x40
+#define   TIMER2_MASK			0x20
+#define   TIMER1_START			0x01
+#define   TIMER2_START			0x02
+
+#define CONNECTION_SELECT_REGISTER		0x04	/* Right side */
+#define   RIGHT_4OP_0			0x01
+#define   RIGHT_4OP_1			0x02
+#define   RIGHT_4OP_2			0x04
+#define   LEFT_4OP_0			0x08
+#define   LEFT_4OP_1			0x10
+#define   LEFT_4OP_2			0x20
+
+#define OPL3_MODE_REGISTER			0x05	/* Right side */
+#define   OPL3_ENABLE			0x01
+#define   OPL4_ENABLE			0x02
+
+#define KBD_SPLIT_REGISTER			0x08	/* Left side */
+#define   COMPOSITE_SINE_WAVE_MODE	0x80		/* Don't use with OPL-3? */
+#define   KEYBOARD_SPLIT		0x40
+
+#define PERCOSSION_REGISTER			0xbd	/* Left side only */
+#define   TREMOLO_DEPTH			0x80
+#define   VIBRATO_DEPTH			0x40
+#define	  PERCOSSION_ENABLE		0x20
+#define   BASSDRUM_ON			0x10
+#define   SNAREDRUM_ON			0x08
+#define   TOMTOM_ON			0x04
+#define   CYMBAL_ON			0x02
+#define   HIHAT_ON			0x01
+
+/*
+ *	Offsets to the register banks for operators. To get the
+ *	register number just add the operator offset to the bank offset
+ *
+ *	AM/VIB/EG/KSR/Multiple (0x20 to 0x35)
+ */
+#define AM_VIB					0x20
+#define   TREMOLO_ON			0x80
+#define   VIBRATO_ON			0x40
+#define   SUSTAIN_ON			0x20
+#define   KSR				0x10 	/* Key scaling rate */
+#define   MULTIPLE_MASK		0x0f	/* Frequency multiplier */
+
+ /*
+  *	KSL/Total level (0x40 to 0x55)
+  */
+#define KSL_LEVEL				0x40
+#define   KSL_MASK			0xc0	/* Envelope scaling bits */
+#define   TOTAL_LEVEL_MASK		0x3f	/* Strength (volume) of OP */
+
+/*
+ *	Attack / Decay rate (0x60 to 0x75)
+ */
+#define ATTACK_DECAY				0x60
+#define   ATTACK_MASK			0xf0
+#define   DECAY_MASK			0x0f
+
+/*
+ * Sustain level / Release rate (0x80 to 0x95)
+ */
+#define SUSTAIN_RELEASE				0x80
+#define   SUSTAIN_MASK			0xf0
+#define   RELEASE_MASK			0x0f
+
+/*
+ * Wave select (0xE0 to 0xF5)
+ */
+#define WAVE_SELECT			0xe0
+
+/*
+ *	Offsets to the register banks for voices. Just add to the
+ *	voice number to get the register number.
+ *
+ *	F-Number low bits (0xA0 to 0xA8).
+ */
+#define FNUM_LOW				0xa0
+
+/*
+ *	F-number high bits / Key on / Block (octave) (0xB0 to 0xB8)
+ */
+#define KEYON_BLOCK					0xb0
+#define	  KEYON_BIT				0x20
+#define	  BLOCKNUM_MASK				0x1c
+#define   FNUM_HIGH_MASK			0x03
+
+/*
+ *	Feedback / Connection (0xc0 to 0xc8)
+ *
+ *	These registers have two new bits when the OPL-3 mode
+ *	is selected. These bits controls connecting the voice
+ *	to the stereo channels. For 4 OP voices this bit is
+ *	defined in the second half of the voice (add 3 to the
+ *	register offset).
+ *
+ *	For 4 OP voices the connection bit is used in the
+ *	both halves (gives 4 ways to connect the operators).
+ */
+#define FEEDBACK_CONNECTION				0xc0
+#define   FEEDBACK_MASK				0x0e	/* Valid just for 1st OP of a voice */
+#define   CONNECTION_BIT			0x01
+/*
+ *	In the 4 OP mode there is four possible configurations how the
+ *	operators can be connected together (in 2 OP modes there is just
+ *	AM or FM). The 4 OP connection mode is defined by the rightmost
+ *	bit of the FEEDBACK_CONNECTION (0xC0-0xC8) on the both halves.
+ *
+ *	First half	Second half	Mode
+ *
+ *					 +---+
+ *					 v   |
+ *	0		0		>+-1-+--2--3--4-->
+ *
+ *
+ *					
+ *					 +---+
+ *					 |   |
+ *	0		1		>+-1-+--2-+
+ *						  |->
+ *					>--3----4-+
+ *					
+ *					 +---+
+ *					 |   |
+ *	1		0		>+-1-+-----+
+ *						   |->
+ *					>--2--3--4-+
+ *
+ *					 +---+
+ *					 |   |
+ *	1		1		>+-1-+--+
+ *						|
+ *					>--2--3-+->
+ *						|
+ *					>--4----+
+ */
+#define   STEREO_BITS				0x30	/* OPL-3 only */
+#define     VOICE_TO_LEFT		0x10
+#define     VOICE_TO_RIGHT		0x20
+
+/*
+ * 	Definition table for the physical voices
+ */
+
+struct physical_voice_info {
+		unsigned char voice_num;
+		unsigned char voice_mode; /* 0=unavailable, 2=2 OP, 4=4 OP */
+		unsigned short ioaddr; /* I/O port (left or right side) */
+		unsigned char op[4]; /* Operator offsets */
+	};
+
+/*
+ *	There is 18 possible 2 OP voices
+ *	(9 in the left and 9 in the right).
+ *	The first OP is the modulator and 2nd is the carrier.
+ *
+ *	The first three voices in the both sides may be connected
+ *	with another voice to a 4 OP voice. For example voice 0
+ *	can be connected with voice 3. The operators of voice 3 are
+ *	used as operators 3 and 4 of the new 4 OP voice.
+ *	In this case the 2 OP voice number 0 is the 'first half' and
+ *	voice 3 is the second.
+ */
+
+#define USE_LEFT	0
+#define USE_RIGHT	1
+
+static struct physical_voice_info pv_map[18] =
+{
+/*       No Mode Side		OP1	OP2	OP3   OP4	*/
+/*	---------------------------------------------------	*/
+	{ 0,  2, USE_LEFT,	{0x00,	0x03,	0x08, 0x0b}},
+	{ 1,  2, USE_LEFT,	{0x01,	0x04,	0x09, 0x0c}},
+	{ 2,  2, USE_LEFT,	{0x02,	0x05,	0x0a, 0x0d}},
+
+	{ 3,  2, USE_LEFT,	{0x08,	0x0b,	0x00, 0x00}},
+	{ 4,  2, USE_LEFT,	{0x09,	0x0c,	0x00, 0x00}},
+	{ 5,  2, USE_LEFT,	{0x0a,	0x0d,	0x00, 0x00}},
+
+	{ 6,  2, USE_LEFT,	{0x10,	0x13,	0x00, 0x00}}, /* Used by percussive voices */
+	{ 7,  2, USE_LEFT,	{0x11,	0x14,	0x00, 0x00}}, /* if the percussive mode */
+	{ 8,  2, USE_LEFT,	{0x12,	0x15,	0x00, 0x00}}, /* is selected */
+
+	{ 0,  2, USE_RIGHT,	{0x00,	0x03,	0x08, 0x0b}},
+	{ 1,  2, USE_RIGHT,	{0x01,	0x04,	0x09, 0x0c}},
+	{ 2,  2, USE_RIGHT,	{0x02,	0x05,	0x0a, 0x0d}},
+
+	{ 3,  2, USE_RIGHT,	{0x08,	0x0b,	0x00, 0x00}},
+	{ 4,  2, USE_RIGHT,	{0x09,	0x0c,	0x00, 0x00}},
+	{ 5,  2, USE_RIGHT,	{0x0a,	0x0d,	0x00, 0x00}},
+
+	{ 6,  2, USE_RIGHT,	{0x10,	0x13,	0x00, 0x00}},
+	{ 7,  2, USE_RIGHT,	{0x11,	0x14,	0x00, 0x00}},
+	{ 8,  2, USE_RIGHT,	{0x12,	0x15,	0x00, 0x00}}
+};
+/*
+ *	DMA buffer calls
+ */
diff --git a/linux/sound/oss/os.h b/linux/sound/oss/os.h
new file mode 100644
index 0000000..a1a962d
--- /dev/null
+++ b/linux/sound/oss/os.h
@@ -0,0 +1,46 @@
+#define ALLOW_SELECT
+#undef NO_INLINE_ASM
+#define SHORT_BANNERS
+#define MANUAL_PNP
+#undef  DO_TIMINGS
+
+#include <linux/module.h>
+
+#ifdef __KERNEL__
+#include <linux/string.h>
+#include <linux/fs.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/param.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <asm/page.h>
+#include <asm/system.h>
+#include <linux/vmalloc.h>
+#include <asm/uaccess.h>
+#include <linux/poll.h>
+#include <linux/pci.h>
+#endif
+
+#include <linux/soundcard.h>
+
+#define FALSE	0
+#define TRUE	1
+
+extern int sound_alloc_dma(int chn, char *deviceID);
+extern int sound_open_dma(int chn, char *deviceID);
+extern void sound_free_dma(int chn);
+extern void sound_close_dma(int chn);
+
+extern void reprogram_timer(void);
+
+#define USE_AUTOINIT_DMA
+
+extern void *sound_mem_blocks[1024];
+extern int sound_nblocks;
+
+#undef PSEUDO_DMA_AUTOINIT
+#define ALLOW_BUFFER_MAPPING
+
+extern const struct file_operations oss_sound_fops;
diff --git a/linux/sound/oss/pas2.h b/linux/sound/oss/pas2.h
new file mode 100644
index 0000000..fa12c55
--- /dev/null
+++ b/linux/sound/oss/pas2.h
@@ -0,0 +1,17 @@
+
+/*	From pas_card.c	*/
+int pas_set_intr(int mask);
+int pas_remove_intr(int mask);
+unsigned char pas_read(int ioaddr);
+void pas_write(unsigned char data, int ioaddr);
+
+/*	From pas_audio.c */
+void pas_pcm_interrupt(unsigned char status, int cause);
+void pas_pcm_init(struct address_info *hw_config);
+
+/*	From pas_mixer.c */
+int pas_init_mixer(void);
+
+/*	From pas_midi.c */
+void pas_midi_init(void);
+void pas_midi_interrupt(void);
diff --git a/linux/sound/oss/pas2_card.c b/linux/sound/oss/pas2_card.c
new file mode 100644
index 0000000..7f377ec
--- /dev/null
+++ b/linux/sound/oss/pas2_card.c
@@ -0,0 +1,455 @@
+/*
+ * sound/oss/pas2_card.c
+ *
+ * Detection routine for the Pro Audio Spectrum cards.
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+#include "pas2.h"
+#include "sb.h"
+
+static unsigned char dma_bits[] = {
+	4, 1, 2, 3, 0, 5, 6, 7
+};
+
+static unsigned char irq_bits[] = {
+	0, 0, 1, 2, 3, 4, 5, 6, 0, 1, 7, 8, 9, 0, 10, 11
+};
+
+static unsigned char sb_irq_bits[] = {
+	0x00, 0x00, 0x08, 0x10, 0x00, 0x18, 0x00, 0x20, 
+	0x00, 0x08, 0x28, 0x30, 0x38, 0, 0
+};
+
+static unsigned char sb_dma_bits[] = {
+	0x00, 0x40, 0x80, 0xC0, 0, 0, 0, 0
+};
+
+/*
+ * The Address Translation code is used to convert I/O register addresses to
+ * be relative to the given base -register
+ */
+
+int      	pas_translate_code = 0;
+static int      pas_intr_mask;
+static int      pas_irq;
+static int      pas_sb_base;
+DEFINE_SPINLOCK(pas_lock);
+#ifndef CONFIG_PAS_JOYSTICK
+static int	joystick;
+#else
+static int 	joystick = 1;
+#endif
+#ifdef SYMPHONY_PAS
+static int 	symphony = 1;
+#else
+static int 	symphony;
+#endif
+#ifdef BROKEN_BUS_CLOCK
+static int	broken_bus_clock = 1;
+#else
+static int	broken_bus_clock;
+#endif
+
+static struct address_info cfg;
+static struct address_info cfg2;
+
+char            pas_model = 0;
+static char    *pas_model_names[] = {
+	"", 
+	"Pro AudioSpectrum+", 
+	"CDPC", 
+	"Pro AudioSpectrum 16", 
+	"Pro AudioSpectrum 16D"
+};
+
+/*
+ * pas_read() and pas_write() are equivalents of inb and outb 
+ * These routines perform the I/O address translation required
+ * to support other than the default base address
+ */
+
+extern void     mix_write(unsigned char data, int ioaddr);
+
+unsigned char pas_read(int ioaddr)
+{
+	return inb(ioaddr + pas_translate_code);
+}
+
+void pas_write(unsigned char data, int ioaddr)
+{
+	outb((data), ioaddr + pas_translate_code);
+}
+
+/******************* Begin of the Interrupt Handler ********************/
+
+static irqreturn_t pasintr(int irq, void *dev_id)
+{
+	int             status;
+
+	status = pas_read(0x0B89);
+	pas_write(status, 0x0B89);	/* Clear interrupt */
+
+	if (status & 0x08)
+	{
+		  pas_pcm_interrupt(status, 1);
+		  status &= ~0x08;
+	}
+	if (status & 0x10)
+	{
+		  pas_midi_interrupt();
+		  status &= ~0x10;
+	}
+	return IRQ_HANDLED;
+}
+
+int pas_set_intr(int mask)
+{
+	if (!mask)
+		return 0;
+
+	pas_intr_mask |= mask;
+
+	pas_write(pas_intr_mask, 0x0B8B);
+	return 0;
+}
+
+int pas_remove_intr(int mask)
+{
+	if (!mask)
+		return 0;
+
+	pas_intr_mask &= ~mask;
+	pas_write(pas_intr_mask, 0x0B8B);
+
+	return 0;
+}
+
+/******************* End of the Interrupt handler **********************/
+
+/******************* Begin of the Initialization Code ******************/
+
+static int __init config_pas_hw(struct address_info *hw_config)
+{
+	char            ok = 1;
+	unsigned        int_ptrs;	/* scsi/sound interrupt pointers */
+
+	pas_irq = hw_config->irq;
+
+	pas_write(0x00, 0x0B8B);
+	pas_write(0x36, 0x138B);
+	pas_write(0x36, 0x1388);
+	pas_write(0, 0x1388);
+	pas_write(0x74, 0x138B);
+	pas_write(0x74, 0x1389);
+	pas_write(0, 0x1389);
+
+	pas_write(0x80 | 0x40 | 0x20 | 1, 0x0B8A);
+	pas_write(0x80 | 0x20 | 0x10 | 0x08 | 0x01, 0xF8A);
+	pas_write(0x01 | 0x02 | 0x04 | 0x10	/*
+						 * |
+						 * 0x80
+						 */ , 0xB88);
+
+	pas_write(0x80 | (joystick ? 0x40 : 0), 0xF388);
+
+	if (pas_irq < 0 || pas_irq > 15)
+	{
+		printk(KERN_ERR "PAS16: Invalid IRQ %d", pas_irq);
+		hw_config->irq=-1;
+		ok = 0;
+	}
+	else
+	{
+		int_ptrs = pas_read(0xF38A);
+		int_ptrs = (int_ptrs & 0xf0) | irq_bits[pas_irq];
+		pas_write(int_ptrs, 0xF38A);
+		if (!irq_bits[pas_irq])
+		{
+			printk(KERN_ERR "PAS16: Invalid IRQ %d", pas_irq);
+			hw_config->irq=-1;
+			ok = 0;
+		}
+		else
+		{
+			if (request_irq(pas_irq, pasintr, 0, "PAS16",hw_config) < 0) {
+				printk(KERN_ERR "PAS16: Cannot allocate IRQ %d\n",pas_irq);
+				hw_config->irq=-1;
+				ok = 0;
+			}
+		}
+	}
+
+	if (hw_config->dma < 0 || hw_config->dma > 7)
+	{
+		printk(KERN_ERR "PAS16: Invalid DMA selection %d", hw_config->dma);
+		hw_config->dma=-1;
+		ok = 0;
+	}
+	else
+	{
+		pas_write(dma_bits[hw_config->dma], 0xF389);
+		if (!dma_bits[hw_config->dma])
+		{
+			printk(KERN_ERR "PAS16: Invalid DMA selection %d", hw_config->dma);
+			hw_config->dma=-1;
+			ok = 0;
+		}
+		else
+		{
+			if (sound_alloc_dma(hw_config->dma, "PAS16"))
+			{
+				printk(KERN_ERR "pas2_card.c: Can't allocate DMA channel\n");
+				hw_config->dma=-1;
+				ok = 0;
+			}
+		}
+	}
+
+	/*
+	 * This fixes the timing problems of the PAS due to the Symphony chipset
+	 * as per Media Vision.  Only define this if your PAS doesn't work correctly.
+	 */
+
+	if(symphony)
+	{
+		outb((0x05), 0xa8);
+		outb((0x60), 0xa9);
+	}
+
+	if(broken_bus_clock)
+		pas_write(0x01 | 0x10 | 0x20 | 0x04, 0x8388);
+	else
+		/*
+		 * pas_write(0x01, 0x8388);
+		 */
+		pas_write(0x01 | 0x10 | 0x20, 0x8388);
+
+	pas_write(0x18, 0x838A);	/* ??? */
+	pas_write(0x20 | 0x01, 0x0B8A);		/* Mute off, filter = 17.897 kHz */
+	pas_write(8, 0xBF8A);
+
+	mix_write(0x80 | 5, 0x078B);
+	mix_write(5, 0x078B);
+
+	{
+		struct address_info *sb_config;
+
+		sb_config = &cfg2;
+		if (sb_config->io_base)
+		{
+			unsigned char   irq_dma;
+
+			/*
+			 * Turn on Sound Blaster compatibility
+			 * bit 1 = SB emulation
+			 * bit 0 = MPU401 emulation (CDPC only :-( )
+			 */
+			
+			pas_write(0x02, 0xF788);
+
+			/*
+			 * "Emulation address"
+			 */
+			
+			pas_write((sb_config->io_base >> 4) & 0x0f, 0xF789);
+			pas_sb_base = sb_config->io_base;
+
+			if (!sb_dma_bits[sb_config->dma])
+				printk(KERN_ERR "PAS16 Warning: Invalid SB DMA %d\n\n", sb_config->dma);
+
+			if (!sb_irq_bits[sb_config->irq])
+				printk(KERN_ERR "PAS16 Warning: Invalid SB IRQ %d\n\n", sb_config->irq);
+
+			irq_dma = sb_dma_bits[sb_config->dma] |
+				sb_irq_bits[sb_config->irq];
+
+			pas_write(irq_dma, 0xFB8A);
+		}
+		else
+			pas_write(0x00, 0xF788);
+	}
+
+	if (!ok)
+		printk(KERN_WARNING "PAS16: Driver not enabled\n");
+
+	return ok;
+}
+
+static int __init detect_pas_hw(struct address_info *hw_config)
+{
+	unsigned char   board_id, foo;
+
+	/*
+	 * WARNING: Setting an option like W:1 or so that disables warm boot reset
+	 * of the card will screw up this detect code something fierce. Adding code
+	 * to handle this means possibly interfering with other cards on the bus if
+	 * you have something on base port 0x388. SO be forewarned.
+	 */
+
+	outb((0xBC), 0x9A01);	/* Activate first board */
+	outb((hw_config->io_base >> 2), 0x9A01);	/* Set base address */
+	pas_translate_code = hw_config->io_base - 0x388;
+	pas_write(1, 0xBF88);	/* Select one wait states */
+
+	board_id = pas_read(0x0B8B);
+
+	if (board_id == 0xff)
+		return 0;
+
+	/*
+	 * We probably have a PAS-series board, now check for a PAS16-series board
+	 * by trying to change the board revision bits. PAS16-series hardware won't
+	 * let you do this - the bits are read-only.
+	 */
+
+	foo = board_id ^ 0xe0;
+
+	pas_write(foo, 0x0B8B);
+	foo = pas_read(0x0B8B);
+	pas_write(board_id, 0x0B8B);
+
+	if (board_id != foo)
+		return 0;
+
+	pas_model = pas_read(0xFF88);
+
+	return pas_model;
+}
+
+static void __init attach_pas_card(struct address_info *hw_config)
+{
+	pas_irq = hw_config->irq;
+
+	if (detect_pas_hw(hw_config))
+	{
+
+		if ((pas_model = pas_read(0xFF88)))
+		{
+			char            temp[100];
+
+			sprintf(temp,
+			    "%s rev %d", pas_model_names[(int) pas_model],
+				    pas_read(0x2789));
+			conf_printf(temp, hw_config);
+		}
+		if (config_pas_hw(hw_config))
+		{
+			pas_pcm_init(hw_config);
+			pas_midi_init();
+			pas_init_mixer();
+		}
+	}
+}
+
+static inline int __init probe_pas(struct address_info *hw_config)
+{
+	return detect_pas_hw(hw_config);
+}
+
+static void __exit unload_pas(struct address_info *hw_config)
+{
+	extern int pas_audiodev;
+	extern int pas2_mididev;
+
+	if (hw_config->dma>0)
+		sound_free_dma(hw_config->dma);
+	if (hw_config->irq>0)
+		free_irq(hw_config->irq, hw_config);
+
+	if(pas_audiodev!=-1)
+		sound_unload_mixerdev(audio_devs[pas_audiodev]->mixer_dev);
+	if(pas2_mididev!=-1)
+	        sound_unload_mididev(pas2_mididev);
+	if(pas_audiodev!=-1)
+		sound_unload_audiodev(pas_audiodev);
+}
+
+static int __initdata io	= -1;
+static int __initdata irq	= -1;
+static int __initdata dma	= -1;
+static int __initdata dma16	= -1;	/* Set this for modules that need it */
+
+static int __initdata sb_io	= 0;
+static int __initdata sb_irq	= -1;
+static int __initdata sb_dma	= -1;
+static int __initdata sb_dma16	= -1;
+
+module_param(io, int, 0);
+module_param(irq, int, 0);
+module_param(dma, int, 0);
+module_param(dma16, int, 0);
+
+module_param(sb_io, int, 0);
+module_param(sb_irq, int, 0);
+module_param(sb_dma, int, 0);
+module_param(sb_dma16, int, 0);
+
+module_param(joystick, bool, 0);
+module_param(symphony, bool, 0);
+module_param(broken_bus_clock, bool, 0);
+
+MODULE_LICENSE("GPL");
+
+static int __init init_pas2(void)
+{
+	printk(KERN_INFO "Pro Audio Spectrum driver Copyright (C) by Hannu Savolainen 1993-1996\n");
+
+	cfg.io_base = io;
+	cfg.irq = irq;
+	cfg.dma = dma;
+	cfg.dma2 = dma16;
+
+	cfg2.io_base = sb_io;
+	cfg2.irq = sb_irq;
+	cfg2.dma = sb_dma;
+	cfg2.dma2 = sb_dma16;
+
+	if (cfg.io_base == -1 || cfg.dma == -1 || cfg.irq == -1) {
+		printk(KERN_INFO "I/O, IRQ, DMA and type are mandatory\n");
+		return -EINVAL;
+	}
+
+	if (!probe_pas(&cfg))
+		return -ENODEV;
+	attach_pas_card(&cfg);
+
+	return 0;
+}
+
+static void __exit cleanup_pas2(void)
+{
+	unload_pas(&cfg);
+}
+
+module_init(init_pas2);
+module_exit(cleanup_pas2);
+
+#ifndef MODULE
+static int __init setup_pas2(char *str)
+{
+	/* io, irq, dma, dma2, sb_io, sb_irq, sb_dma, sb_dma2 */
+	int ints[9];
+	
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+
+	io	= ints[1];
+	irq	= ints[2];
+	dma	= ints[3];
+	dma16	= ints[4];
+
+	sb_io	= ints[5];
+	sb_irq	= ints[6];
+	sb_dma	= ints[7];
+	sb_dma16 = ints[8];
+
+	return 1;
+}
+
+__setup("pas2=", setup_pas2);
+#endif
diff --git a/linux/sound/oss/pas2_midi.c b/linux/sound/oss/pas2_midi.c
new file mode 100644
index 0000000..1122d10
--- /dev/null
+++ b/linux/sound/oss/pas2_midi.c
@@ -0,0 +1,262 @@
+/*
+ * sound/oss/pas2_midi.c
+ *
+ * The low level driver for the PAS Midi Interface.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Bartlomiej Zolnierkiewicz	: Added __init to pas_init_mixer()
+ */
+
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+#include "pas2.h"
+
+extern spinlock_t pas_lock;
+
+static int      midi_busy, input_opened;
+static int      my_dev;
+
+int pas2_mididev=-1;
+
+static unsigned char tmp_queue[256];
+static volatile int qlen;
+static volatile unsigned char qhead, qtail;
+
+static void     (*midi_input_intr) (int dev, unsigned char data);
+
+static int pas_midi_open(int dev, int mode,
+	      void            (*input) (int dev, unsigned char data),
+	      void            (*output) (int dev)
+)
+{
+	int             err;
+	unsigned long   flags;
+	unsigned char   ctrl;
+
+
+	if (midi_busy)
+		return -EBUSY;
+
+	/*
+	 * Reset input and output FIFO pointers
+	 */
+	pas_write(0x20 | 0x40,
+		  0x178b);
+
+	spin_lock_irqsave(&pas_lock, flags);
+
+	if ((err = pas_set_intr(0x10)) < 0)
+	{
+		spin_unlock_irqrestore(&pas_lock, flags);
+		return err;
+	}
+	/*
+	 * Enable input available and output FIFO empty interrupts
+	 */
+
+	ctrl = 0;
+	input_opened = 0;
+	midi_input_intr = input;
+
+	if (mode == OPEN_READ || mode == OPEN_READWRITE)
+	{
+		ctrl |= 0x04;	/* Enable input */
+		input_opened = 1;
+	}
+	if (mode == OPEN_WRITE || mode == OPEN_READWRITE)
+	{
+		ctrl |= 0x08 | 0x10;	/* Enable output */
+	}
+	pas_write(ctrl, 0x178b);
+
+	/*
+	 * Acknowledge any pending interrupts
+	 */
+
+	pas_write(0xff, 0x1B88);
+
+	spin_unlock_irqrestore(&pas_lock, flags);
+
+	midi_busy = 1;
+	qlen = qhead = qtail = 0;
+	return 0;
+}
+
+static void pas_midi_close(int dev)
+{
+
+	/*
+	 * Reset FIFO pointers, disable intrs
+	 */
+	pas_write(0x20 | 0x40, 0x178b);
+
+	pas_remove_intr(0x10);
+	midi_busy = 0;
+}
+
+static int dump_to_midi(unsigned char midi_byte)
+{
+	int fifo_space, x;
+
+	fifo_space = ((x = pas_read(0x1B89)) >> 4) & 0x0f;
+
+	/*
+	 * The MIDI FIFO space register and it's documentation is nonunderstandable.
+	 * There seem to be no way to differentiate between buffer full and buffer
+	 * empty situations. For this reason we don't never write the buffer
+	 * completely full. In this way we can assume that 0 (or is it 15)
+	 * means that the buffer is empty.
+	 */
+
+	if (fifo_space < 2 && fifo_space != 0)	/* Full (almost) */
+		return 0;	/* Ask upper layers to retry after some time */
+
+	pas_write(midi_byte, 0x178A);
+
+	return 1;
+}
+
+static int pas_midi_out(int dev, unsigned char midi_byte)
+{
+
+	unsigned long flags;
+
+	/*
+	 * Drain the local queue first
+	 */
+
+	spin_lock_irqsave(&pas_lock, flags);
+
+	while (qlen && dump_to_midi(tmp_queue[qhead]))
+	{
+		qlen--;
+		qhead++;
+	}
+
+	spin_unlock_irqrestore(&pas_lock, flags);
+
+	/*
+	 *	Output the byte if the local queue is empty.
+	 */
+
+	if (!qlen)
+		if (dump_to_midi(midi_byte))
+			return 1;
+
+	/*
+	 *	Put to the local queue
+	 */
+
+	if (qlen >= 256)
+		return 0;	/* Local queue full */
+
+	spin_lock_irqsave(&pas_lock, flags);
+
+	tmp_queue[qtail] = midi_byte;
+	qlen++;
+	qtail++;
+
+	spin_unlock_irqrestore(&pas_lock, flags);
+
+	return 1;
+}
+
+static int pas_midi_start_read(int dev)
+{
+	return 0;
+}
+
+static int pas_midi_end_read(int dev)
+{
+	return 0;
+}
+
+static void pas_midi_kick(int dev)
+{
+}
+
+static int pas_buffer_status(int dev)
+{
+	return qlen;
+}
+
+#define MIDI_SYNTH_NAME	"Pro Audio Spectrum Midi"
+#define MIDI_SYNTH_CAPS	SYNTH_CAP_INPUT
+#include "midi_synth.h"
+
+static struct midi_operations pas_midi_operations =
+{
+	.owner		= THIS_MODULE,
+	.info		= {"Pro Audio Spectrum", 0, 0, SNDCARD_PAS},
+	.converter	= &std_midi_synth,
+	.in_info	= {0},
+	.open		= pas_midi_open,
+	.close		= pas_midi_close,
+	.outputc	= pas_midi_out,
+	.start_read	= pas_midi_start_read,
+	.end_read	= pas_midi_end_read,
+	.kick		= pas_midi_kick,
+	.buffer_status	= pas_buffer_status,
+};
+
+void __init pas_midi_init(void)
+{
+	int dev = sound_alloc_mididev();
+
+	if (dev == -1)
+	{
+		printk(KERN_WARNING "pas_midi_init: Too many midi devices detected\n");
+		return;
+	}
+	std_midi_synth.midi_dev = my_dev = dev;
+	midi_devs[dev] = &pas_midi_operations;
+	pas2_mididev = dev;
+	sequencer_init();
+}
+
+void pas_midi_interrupt(void)
+{
+	unsigned char   stat;
+	int             i, incount;
+
+	stat = pas_read(0x1B88);
+
+	if (stat & 0x04)	/* Input data available */
+	{
+		incount = pas_read(0x1B89) & 0x0f;	/* Input FIFO size */
+		if (!incount)
+			incount = 16;
+
+		for (i = 0; i < incount; i++)
+			if (input_opened)
+			{
+				midi_input_intr(my_dev, pas_read(0x178A));
+			} else
+				pas_read(0x178A);	/* Flush */
+	}
+	if (stat & (0x08 | 0x10))
+	{
+		spin_lock(&pas_lock);/* called in irq context */
+
+		while (qlen && dump_to_midi(tmp_queue[qhead]))
+		{
+			qlen--;
+			qhead++;
+		}
+
+		spin_unlock(&pas_lock);
+	}
+	if (stat & 0x40)
+	{
+		printk(KERN_WARNING "MIDI output overrun %x,%x\n", pas_read(0x1B89), stat);
+	}
+	pas_write(stat, 0x1B88);	/* Acknowledge interrupts */
+}
diff --git a/linux/sound/oss/pas2_mixer.c b/linux/sound/oss/pas2_mixer.c
new file mode 100644
index 0000000..a0bcb85
--- /dev/null
+++ b/linux/sound/oss/pas2_mixer.c
@@ -0,0 +1,336 @@
+
+/*
+ * sound/oss/pas2_mixer.c
+ *
+ * Mixer routines for the Pro Audio Spectrum cards.
+ */
+
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer   : ioctl code reworked (vmalloc/vfree removed)
+ * Bartlomiej Zolnierkiewicz : added __init to pas_init_mixer()
+ */
+#include <linux/init.h>
+#include "sound_config.h"
+
+#include "pas2.h"
+
+#ifndef DEB
+#define DEB(what)		/* (what) */
+#endif
+
+extern int      pas_translate_code;
+extern char     pas_model;
+extern int     *pas_osp;
+extern int      pas_audiodev;
+
+static int      rec_devices = (SOUND_MASK_MIC);		/* Default recording source */
+static int      mode_control;
+
+#define POSSIBLE_RECORDING_DEVICES	(SOUND_MASK_SYNTH | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
+					 SOUND_MASK_CD | SOUND_MASK_ALTPCM)
+
+#define SUPPORTED_MIXER_DEVICES		(SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
+					 SOUND_MASK_CD | SOUND_MASK_ALTPCM | SOUND_MASK_IMIX | \
+					 SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_RECLEV)
+
+static int     *levels;
+
+static int      default_levels[32] =
+{
+	0x3232,			/* Master Volume */
+	0x3232,			/* Bass */
+	0x3232,			/* Treble */
+	0x5050,			/* FM */
+	0x4b4b,			/* PCM */
+	0x3232,			/* PC Speaker */
+	0x4b4b,			/* Ext Line */
+	0x4b4b,			/* Mic */
+	0x4b4b,			/* CD */
+	0x6464,			/* Recording monitor */
+	0x4b4b,			/* SB PCM */
+	0x6464			/* Recording level */
+};
+
+void
+mix_write(unsigned char data, int ioaddr)
+{
+	/*
+	 * The Revision D cards have a problem with their MVA508 interface. The
+	 * kludge-o-rama fix is to make a 16-bit quantity with identical LSB and
+	 * MSBs out of the output byte and to do a 16-bit out to the mixer port -
+	 * 1. We need to do this because it isn't timing problem but chip access
+	 * sequence problem.
+	 */
+
+	if (pas_model == 4)
+	  {
+		  outw(data | (data << 8), (ioaddr + pas_translate_code) - 1);
+		  outb((0x80), 0);
+	} else
+		pas_write(data, ioaddr);
+}
+
+static int
+mixer_output(int right_vol, int left_vol, int div, int bits,
+	     int mixer)		/* Input or output mixer */
+{
+	int             left = left_vol * div / 100;
+	int             right = right_vol * div / 100;
+
+
+	if (bits & 0x10)
+	  {
+		  left |= mixer;
+		  right |= mixer;
+	  }
+	if (bits == 0x03 || bits == 0x04)
+	  {
+		  mix_write(0x80 | bits, 0x078B);
+		  mix_write(left, 0x078B);
+		  right_vol = left_vol;
+	} else
+	  {
+		  mix_write(0x80 | 0x20 | bits, 0x078B);
+		  mix_write(left, 0x078B);
+		  mix_write(0x80 | 0x40 | bits, 0x078B);
+		  mix_write(right, 0x078B);
+	  }
+
+	return (left_vol | (right_vol << 8));
+}
+
+static void
+set_mode(int new_mode)
+{
+	mix_write(0x80 | 0x05, 0x078B);
+	mix_write(new_mode, 0x078B);
+
+	mode_control = new_mode;
+}
+
+static int
+pas_mixer_set(int whichDev, unsigned int level)
+{
+	int             left, right, devmask, changed, i, mixer = 0;
+
+	DEB(printk("static int pas_mixer_set(int whichDev = %d, unsigned int level = %X)\n", whichDev, level));
+
+	left = level & 0x7f;
+	right = (level & 0x7f00) >> 8;
+
+	if (whichDev < SOUND_MIXER_NRDEVICES) {
+		if ((1 << whichDev) & rec_devices)
+			mixer = 0x20;
+		else
+			mixer = 0x00;
+	}
+
+	switch (whichDev)
+	  {
+	  case SOUND_MIXER_VOLUME:	/* Master volume (0-63) */
+		  levels[whichDev] = mixer_output(right, left, 63, 0x01, 0);
+		  break;
+
+		  /*
+		   * Note! Bass and Treble are mono devices. Will use just the left
+		   * channel.
+		   */
+	  case SOUND_MIXER_BASS:	/* Bass (0-12) */
+		  levels[whichDev] = mixer_output(right, left, 12, 0x03, 0);
+		  break;
+	  case SOUND_MIXER_TREBLE:	/* Treble (0-12) */
+		  levels[whichDev] = mixer_output(right, left, 12, 0x04, 0);
+		  break;
+
+	  case SOUND_MIXER_SYNTH:	/* Internal synthesizer (0-31) */
+		  levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x00, mixer);
+		  break;
+	  case SOUND_MIXER_PCM:	/* PAS PCM (0-31) */
+		  levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x05, mixer);
+		  break;
+	  case SOUND_MIXER_ALTPCM:	/* SB PCM (0-31) */
+		  levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x07, mixer);
+		  break;
+	  case SOUND_MIXER_SPEAKER:	/* PC speaker (0-31) */
+		  levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x06, mixer);
+		  break;
+	  case SOUND_MIXER_LINE:	/* External line (0-31) */
+		  levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x02, mixer);
+		  break;
+	  case SOUND_MIXER_CD:	/* CD (0-31) */
+		  levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x03, mixer);
+		  break;
+	  case SOUND_MIXER_MIC:	/* External microphone (0-31) */
+		  levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x04, mixer);
+		  break;
+	  case SOUND_MIXER_IMIX:	/* Recording monitor (0-31) (Output mixer only) */
+		  levels[whichDev] = mixer_output(right, left, 31, 0x10 | 0x01,
+						  0x00);
+		  break;
+	  case SOUND_MIXER_RECLEV:	/* Recording level (0-15) */
+		  levels[whichDev] = mixer_output(right, left, 15, 0x02, 0);
+		  break;
+
+
+	  case SOUND_MIXER_RECSRC:
+		  devmask = level & POSSIBLE_RECORDING_DEVICES;
+
+		  changed = devmask ^ rec_devices;
+		  rec_devices = devmask;
+
+		  for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+			  if (changed & (1 << i))
+			    {
+				    pas_mixer_set(i, levels[i]);
+			    }
+		  return rec_devices;
+		  break;
+
+	  default:
+		  return -EINVAL;
+	  }
+
+	return (levels[whichDev]);
+}
+
+/*****/
+
+static void
+pas_mixer_reset(void)
+{
+	int             foo;
+
+	DEB(printk("pas2_mixer.c: void pas_mixer_reset(void)\n"));
+
+	for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++)
+		pas_mixer_set(foo, levels[foo]);
+
+	set_mode(0x04 | 0x01);
+}
+
+static int pas_mixer_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+	int level,v ;
+	int __user *p = (int __user *)arg;
+
+	DEB(printk("pas2_mixer.c: int pas_mixer_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg));
+	if (cmd == SOUND_MIXER_PRIVATE1) { /* Set loudness bit */
+		if (get_user(level, p))
+			return -EFAULT;
+		if (level == -1)  /* Return current settings */
+			level = (mode_control & 0x04);
+		else {
+			mode_control &= ~0x04;
+			if (level)
+				mode_control |= 0x04;
+			set_mode(mode_control);
+		}
+		level = !!level;
+		return put_user(level, p);
+	}
+	if (cmd == SOUND_MIXER_PRIVATE2) { /* Set enhance bit */
+		if (get_user(level, p))
+			return -EFAULT;
+		if (level == -1) { /* Return current settings */
+			if (!(mode_control & 0x03))
+				level = 0;
+			else
+				level = ((mode_control & 0x03) + 1) * 20;
+		} else {
+			int i = 0;
+			
+			level &= 0x7f;
+			if (level)
+				i = (level / 20) - 1;
+			mode_control &= ~0x03;
+			mode_control |= i & 0x03;
+			set_mode(mode_control);
+			if (i)
+				i = (i + 1) * 20;
+			level = i;
+		}
+		return put_user(level, p);
+	}
+	if (cmd == SOUND_MIXER_PRIVATE3) { /* Set mute bit */
+		if (get_user(level, p))
+			return -EFAULT;
+		if (level == -1)	/* Return current settings */
+			level = !(pas_read(0x0B8A) & 0x20);
+		else {
+			if (level)
+				pas_write(pas_read(0x0B8A) & (~0x20), 0x0B8A);
+			else
+				pas_write(pas_read(0x0B8A) | 0x20, 0x0B8A);
+
+			level = !(pas_read(0x0B8A) & 0x20);
+		}
+		return put_user(level, p);
+	}
+	if (((cmd >> 8) & 0xff) == 'M') {
+		if (get_user(v, p))
+			return -EFAULT;
+		if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+			v = pas_mixer_set(cmd & 0xff, v);
+		} else {
+			switch (cmd & 0xff) {
+			case SOUND_MIXER_RECSRC:
+				v = rec_devices;
+				break;
+				
+			case SOUND_MIXER_STEREODEVS:
+				v = SUPPORTED_MIXER_DEVICES & ~(SOUND_MASK_BASS | SOUND_MASK_TREBLE);
+				break;
+				
+			case SOUND_MIXER_DEVMASK:
+				v = SUPPORTED_MIXER_DEVICES;
+				break;
+				
+			case SOUND_MIXER_RECMASK:
+				v = POSSIBLE_RECORDING_DEVICES & SUPPORTED_MIXER_DEVICES;
+				break;
+				
+			case SOUND_MIXER_CAPS:
+				v = 0;	/* No special capabilities */
+				break;
+				
+			default:
+				v = levels[cmd & 0xff];
+				break;
+			}
+		}
+		return put_user(v, p);
+	}
+	return -EINVAL;
+}
+
+static struct mixer_operations pas_mixer_operations =
+{
+	.owner	= THIS_MODULE,
+	.id	= "PAS16",
+	.name	= "Pro Audio Spectrum 16",
+	.ioctl	= pas_mixer_ioctl
+};
+
+int __init
+pas_init_mixer(void)
+{
+	int             d;
+
+	levels = load_mixer_volumes("PAS16_1", default_levels, 1);
+
+	pas_mixer_reset();
+
+	if ((d = sound_alloc_mixerdev()) != -1)
+	  {
+		  audio_devs[pas_audiodev]->mixer_dev = d;
+		  mixer_devs[d] = &pas_mixer_operations;
+	  }
+	return 1;
+}
diff --git a/linux/sound/oss/pas2_pcm.c b/linux/sound/oss/pas2_pcm.c
new file mode 100644
index 0000000..8f7d175
--- /dev/null
+++ b/linux/sound/oss/pas2_pcm.c
@@ -0,0 +1,437 @@
+/*
+ * pas2_pcm.c Audio routines for PAS16
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer   : ioctl code reworked (vmalloc/vfree removed)
+ * Alan Cox	   : Swatted a double allocation of device bug. Made a few
+ *		     more things module options.
+ * Bartlomiej Zolnierkiewicz : Added __init to pas_pcm_init()
+ */
+
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/timex.h>
+#include "sound_config.h"
+
+#include "pas2.h"
+
+#ifndef DEB
+#define DEB(WHAT)
+#endif
+
+#define PAS_PCM_INTRBITS (0x08)
+/*
+ * Sample buffer timer interrupt enable
+ */
+
+#define PCM_NON	0
+#define PCM_DAC	1
+#define PCM_ADC	2
+
+static unsigned long pcm_speed; 	/* sampling rate */
+static unsigned char pcm_channels = 1;	/* channels (1 or 2) */
+static unsigned char pcm_bits = 8;	/* bits/sample (8 or 16) */
+static unsigned char pcm_filter;	/* filter FLAG */
+static unsigned char pcm_mode = PCM_NON;
+static unsigned long pcm_count;
+static unsigned short pcm_bitsok = 8;	/* mask of OK bits */
+static int      pcm_busy;
+int             pas_audiodev = -1;
+static int      open_mode;
+
+extern spinlock_t pas_lock;
+
+static int pcm_set_speed(int arg)
+{
+	int foo, tmp;
+	unsigned long flags;
+
+	if (arg == 0)
+		return pcm_speed;
+
+	if (arg > 44100)
+		arg = 44100;
+	if (arg < 5000)
+		arg = 5000;
+
+	if (pcm_channels & 2)
+	{
+		foo = ((CLOCK_TICK_RATE / 2) + (arg / 2)) / arg;
+		arg = ((CLOCK_TICK_RATE / 2) + (foo / 2)) / foo;
+	}
+	else
+	{
+		foo = (CLOCK_TICK_RATE + (arg / 2)) / arg;
+		arg = (CLOCK_TICK_RATE + (foo / 2)) / foo;
+	}
+
+	pcm_speed = arg;
+
+	tmp = pas_read(0x0B8A);
+
+	/*
+	 * Set anti-aliasing filters according to sample rate. You really *NEED*
+	 * to enable this feature for all normal recording unless you want to
+	 * experiment with aliasing effects.
+	 * These filters apply to the selected "recording" source.
+	 * I (pfw) don't know the encoding of these 5 bits. The values shown
+	 * come from the SDK found on ftp.uwp.edu:/pub/msdos/proaudio/.
+	 *
+	 * I cleared bit 5 of these values, since that bit controls the master
+	 * mute flag. (Olav Wölfelschneider)
+	 *
+	 */
+#if !defined NO_AUTO_FILTER_SET
+	tmp &= 0xe0;
+	if (pcm_speed >= 2 * 17897)
+		tmp |= 0x01;
+	else if (pcm_speed >= 2 * 15909)
+		tmp |= 0x02;
+	else if (pcm_speed >= 2 * 11931)
+		tmp |= 0x09;
+	else if (pcm_speed >= 2 * 8948)
+		tmp |= 0x11;
+	else if (pcm_speed >= 2 * 5965)
+		tmp |= 0x19;
+	else if (pcm_speed >= 2 * 2982)
+		tmp |= 0x04;
+	pcm_filter = tmp;
+#endif
+
+	spin_lock_irqsave(&pas_lock, flags);
+
+	pas_write(tmp & ~(0x40 | 0x80), 0x0B8A);
+	pas_write(0x00 | 0x30 | 0x04, 0x138B);
+	pas_write(foo & 0xff, 0x1388);
+	pas_write((foo >> 8) & 0xff, 0x1388);
+	pas_write(tmp, 0x0B8A);
+
+	spin_unlock_irqrestore(&pas_lock, flags);
+
+	return pcm_speed;
+}
+
+static int pcm_set_channels(int arg)
+{
+
+	if ((arg != 1) && (arg != 2))
+		return pcm_channels;
+
+	if (arg != pcm_channels)
+	{
+		pas_write(pas_read(0xF8A) ^ 0x20, 0xF8A);
+
+		pcm_channels = arg;
+		pcm_set_speed(pcm_speed);	/* The speed must be reinitialized */
+	}
+	return pcm_channels;
+}
+
+static int pcm_set_bits(int arg)
+{
+	if (arg == 0)
+		return pcm_bits;
+
+	if ((arg & pcm_bitsok) != arg)
+		return pcm_bits;
+
+	if (arg != pcm_bits)
+	{
+		pas_write(pas_read(0x8389) ^ 0x04, 0x8389);
+
+		pcm_bits = arg;
+	}
+	return pcm_bits;
+}
+
+static int pas_audio_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+	int val, ret;
+	int __user *p = arg;
+
+	DEB(printk("pas2_pcm.c: static int pas_audio_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg));
+
+	switch (cmd) 
+	{
+	case SOUND_PCM_WRITE_RATE:
+		if (get_user(val, p)) 
+			return -EFAULT;
+		ret = pcm_set_speed(val);
+		break;
+
+	case SOUND_PCM_READ_RATE:
+		ret = pcm_speed;
+		break;
+		
+	case SNDCTL_DSP_STEREO:
+		if (get_user(val, p)) 
+			return -EFAULT;
+		ret = pcm_set_channels(val + 1) - 1;
+		break;
+
+	case SOUND_PCM_WRITE_CHANNELS:
+		if (get_user(val, p)) 
+			return -EFAULT;
+		ret = pcm_set_channels(val);
+		break;
+
+	case SOUND_PCM_READ_CHANNELS:
+		ret = pcm_channels;
+		break;
+
+	case SNDCTL_DSP_SETFMT:
+		if (get_user(val, p))
+			return -EFAULT;
+		ret = pcm_set_bits(val);
+		break;
+		
+	case SOUND_PCM_READ_BITS:
+		ret = pcm_bits;
+		break;
+  
+	default:
+		return -EINVAL;
+	}
+	return put_user(ret, p);
+}
+
+static void pas_audio_reset(int dev)
+{
+	DEB(printk("pas2_pcm.c: static void pas_audio_reset(void)\n"));
+
+	pas_write(pas_read(0xF8A) & ~0x40, 0xF8A);	/* Disable PCM */
+}
+
+static int pas_audio_open(int dev, int mode)
+{
+	int             err;
+	unsigned long   flags;
+
+	DEB(printk("pas2_pcm.c: static int pas_audio_open(int mode = %X)\n", mode));
+
+	spin_lock_irqsave(&pas_lock, flags);
+	if (pcm_busy)
+	{
+		spin_unlock_irqrestore(&pas_lock, flags);
+		return -EBUSY;
+	}
+	pcm_busy = 1;
+	spin_unlock_irqrestore(&pas_lock, flags);
+
+	if ((err = pas_set_intr(PAS_PCM_INTRBITS)) < 0)
+		return err;
+
+
+	pcm_count = 0;
+	open_mode = mode;
+
+	return 0;
+}
+
+static void pas_audio_close(int dev)
+{
+	unsigned long   flags;
+
+	DEB(printk("pas2_pcm.c: static void pas_audio_close(void)\n"));
+
+	spin_lock_irqsave(&pas_lock, flags);
+
+	pas_audio_reset(dev);
+	pas_remove_intr(PAS_PCM_INTRBITS);
+	pcm_mode = PCM_NON;
+
+	pcm_busy = 0;
+	spin_unlock_irqrestore(&pas_lock, flags);
+}
+
+static void pas_audio_output_block(int dev, unsigned long buf, int count,
+		       int intrflag)
+{
+	unsigned long   flags, cnt;
+
+	DEB(printk("pas2_pcm.c: static void pas_audio_output_block(char *buf = %P, int count = %X)\n", buf, count));
+
+	cnt = count;
+	if (audio_devs[dev]->dmap_out->dma > 3)
+		cnt >>= 1;
+
+	if (audio_devs[dev]->flags & DMA_AUTOMODE &&
+	    intrflag &&
+	    cnt == pcm_count)
+		return;
+
+	spin_lock_irqsave(&pas_lock, flags);
+
+	pas_write(pas_read(0xF8A) & ~0x40,
+		  0xF8A);
+
+	/* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */
+
+	if (audio_devs[dev]->dmap_out->dma > 3)
+		count >>= 1;
+
+	if (count != pcm_count)
+	{
+		pas_write(pas_read(0x0B8A) & ~0x80, 0x0B8A);
+		pas_write(0x40 | 0x30 | 0x04, 0x138B);
+		pas_write(count & 0xff, 0x1389);
+		pas_write((count >> 8) & 0xff, 0x1389);
+		pas_write(pas_read(0x0B8A) | 0x80, 0x0B8A);
+
+		pcm_count = count;
+	}
+	pas_write(pas_read(0x0B8A) | 0x80 | 0x40, 0x0B8A);
+#ifdef NO_TRIGGER
+	pas_write(pas_read(0xF8A) | 0x40 | 0x10, 0xF8A);
+#endif
+
+	pcm_mode = PCM_DAC;
+
+	spin_unlock_irqrestore(&pas_lock, flags);
+}
+
+static void pas_audio_start_input(int dev, unsigned long buf, int count,
+		      int intrflag)
+{
+	unsigned long   flags;
+	int             cnt;
+
+	DEB(printk("pas2_pcm.c: static void pas_audio_start_input(char *buf = %P, int count = %X)\n", buf, count));
+
+	cnt = count;
+	if (audio_devs[dev]->dmap_out->dma > 3)
+		cnt >>= 1;
+
+	if (audio_devs[pas_audiodev]->flags & DMA_AUTOMODE &&
+	    intrflag &&
+	    cnt == pcm_count)
+		return;
+
+	spin_lock_irqsave(&pas_lock, flags);
+
+	/* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */
+
+	if (audio_devs[dev]->dmap_out->dma > 3)
+		count >>= 1;
+
+	if (count != pcm_count)
+	{
+		pas_write(pas_read(0x0B8A) & ~0x80, 0x0B8A);
+		pas_write(0x40 | 0x30 | 0x04, 0x138B);
+		pas_write(count & 0xff, 0x1389);
+		pas_write((count >> 8) & 0xff, 0x1389);
+		pas_write(pas_read(0x0B8A) | 0x80, 0x0B8A);
+
+		pcm_count = count;
+	}
+	pas_write(pas_read(0x0B8A) | 0x80 | 0x40, 0x0B8A);
+#ifdef NO_TRIGGER
+	pas_write((pas_read(0xF8A) | 0x40) & ~0x10, 0xF8A);
+#endif
+
+	pcm_mode = PCM_ADC;
+
+	spin_unlock_irqrestore(&pas_lock, flags);
+}
+
+#ifndef NO_TRIGGER
+static void pas_audio_trigger(int dev, int state)
+{
+	unsigned long   flags;
+
+	spin_lock_irqsave(&pas_lock, flags);
+	state &= open_mode;
+
+	if (state & PCM_ENABLE_OUTPUT)
+		pas_write(pas_read(0xF8A) | 0x40 | 0x10, 0xF8A);
+	else if (state & PCM_ENABLE_INPUT)
+		pas_write((pas_read(0xF8A) | 0x40) & ~0x10, 0xF8A);
+	else
+		pas_write(pas_read(0xF8A) & ~0x40, 0xF8A);
+
+	spin_unlock_irqrestore(&pas_lock, flags);
+}
+#endif
+
+static int pas_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+	pas_audio_reset(dev);
+	return 0;
+}
+
+static int pas_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+	pas_audio_reset(dev);
+	return 0;
+}
+
+static struct audio_driver pas_audio_driver =
+{
+	.owner			= THIS_MODULE,
+	.open			= pas_audio_open,
+	.close			= pas_audio_close,
+	.output_block		= pas_audio_output_block,
+	.start_input		= pas_audio_start_input,
+	.ioctl			= pas_audio_ioctl,
+	.prepare_for_input	= pas_audio_prepare_for_input,
+	.prepare_for_output	= pas_audio_prepare_for_output,
+	.halt_io		= pas_audio_reset,
+	.trigger		= pas_audio_trigger
+};
+
+void __init pas_pcm_init(struct address_info *hw_config)
+{
+	DEB(printk("pas2_pcm.c: long pas_pcm_init()\n"));
+
+	pcm_bitsok = 8;
+	if (pas_read(0xEF8B) & 0x08)
+		pcm_bitsok |= 16;
+
+	pcm_set_speed(DSP_DEFAULT_SPEED);
+
+	if ((pas_audiodev = sound_install_audiodrv(AUDIO_DRIVER_VERSION,
+					"Pro Audio Spectrum",
+					&pas_audio_driver,
+					sizeof(struct audio_driver),
+					DMA_AUTOMODE,
+					AFMT_U8 | AFMT_S16_LE,
+					NULL,
+					hw_config->dma,
+					hw_config->dma)) < 0)
+		printk(KERN_WARNING "PAS16: Too many PCM devices available\n");
+}
+
+void pas_pcm_interrupt(unsigned char status, int cause)
+{
+	if (cause == 1)
+	{
+		/*
+		 * Halt the PCM first. Otherwise we don't have time to start a new
+		 * block before the PCM chip proceeds to the next sample
+		 */
+
+		if (!(audio_devs[pas_audiodev]->flags & DMA_AUTOMODE))
+			pas_write(pas_read(0xF8A) & ~0x40, 0xF8A);
+
+		switch (pcm_mode)
+		{
+			case PCM_DAC:
+				DMAbuf_outputintr(pas_audiodev, 1);
+				break;
+
+			case PCM_ADC:
+				DMAbuf_inputintr(pas_audiodev);
+				break;
+
+			default:
+				printk(KERN_WARNING "PAS: Unexpected PCM interrupt\n");
+		}
+	}
+}
diff --git a/linux/sound/oss/pss.c b/linux/sound/oss/pss.c
new file mode 100644
index 0000000..9b800ce
--- /dev/null
+++ b/linux/sound/oss/pss.c
@@ -0,0 +1,1266 @@
+/*
+ * sound/oss/pss.c
+ *
+ * The low level driver for the Personal Sound System (ECHO ESC614).
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer	ioctl code reworked (vmalloc/vfree removed)
+ * Alan Cox		modularisation, clean up.
+ *
+ * 98-02-21: Vladimir Michl <vladimir.michl@upol.cz>
+ *          Added mixer device for Beethoven ADSP-16 (master volume,
+ *	    bass, treble, synth), only for speakers.
+ *          Fixed bug in pss_write (exchange parameters)
+ *          Fixed config port of SB
+ *          Requested two regions for PSS (PSS mixer, PSS config)
+ *          Modified pss_download_boot
+ *          To probe_pss_mss added test for initialize AD1848
+ * 98-05-28: Vladimir Michl <vladimir.michl@upol.cz>
+ *          Fixed computation of mixer volumes
+ * 04-05-1999: Anthony Barbachan <barbcode@xmen.cis.fordham.edu>
+ *          Added code that allows the user to enable his cdrom and/or 
+ *          joystick through the module parameters pss_cdrom_port and 
+ *          pss_enable_joystick.  pss_cdrom_port takes a port address as its
+ *          argument.  pss_enable_joystick takes either a 0 or a non-0 as its
+ *          argument.
+ * 04-06-1999: Anthony Barbachan <barbcode@xmen.cis.fordham.edu>
+ *          Separated some code into new functions for easier reuse.  
+ *          Cleaned up and streamlined new code.  Added code to allow a user 
+ *          to only use this driver for enabling non-sound components 
+ *          through the new module parameter pss_no_sound (flag).  Added 
+ *          code that would allow a user to decide whether the driver should 
+ *          reset the configured hardware settings for the PSS board through 
+ *          the module parameter pss_keep_settings (flag).   This flag will 
+ *          allow a user to free up resources in use by this card if needbe, 
+ *          furthermore it allows him to use this driver to just enable the 
+ *          emulations and then be unloaded as it is no longer needed.  Both 
+ *          new settings are only available to this driver if compiled as a 
+ *          module.  The default settings of all new parameters are set to 
+ *          load the driver as it did in previous versions.
+ * 04-07-1999: Anthony Barbachan <barbcode@xmen.cis.fordham.edu>
+ *          Added module parameter pss_firmware to allow the user to tell 
+ *          the driver where the firmware file is located.  The default 
+ *          setting is the previous hardcoded setting "/etc/sound/pss_synth".
+ * 00-03-03: Christoph Hellwig <chhellwig@infradead.org>
+ *	    Adapted to module_init/module_exit
+ * 11-10-2000: Bartlomiej Zolnierkiewicz <bkz@linux-ide.org>
+ *	    Added __init to probe_pss(), attach_pss() and probe_pss_mpu()
+ * 02-Jan-2001: Chris Rankin
+ *          Specify that this module owns the coprocessor
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+
+#include "sound_config.h"
+#include "sound_firmware.h"
+
+#include "ad1848.h"
+#include "mpu401.h"
+
+/*
+ * PSS registers.
+ */
+#define REG(x)	(devc->base+x)
+#define	PSS_DATA	0
+#define	PSS_STATUS	2
+#define PSS_CONTROL	2
+#define	PSS_ID		4
+#define	PSS_IRQACK	4
+#define	PSS_PIO		0x1a
+
+/*
+ * Config registers
+ */
+#define CONF_PSS	0x10
+#define CONF_WSS	0x12
+#define CONF_SB		0x14
+#define CONF_CDROM	0x16
+#define CONF_MIDI	0x18
+
+/*
+ * Status bits.
+ */
+#define PSS_FLAG3     0x0800
+#define PSS_FLAG2     0x0400
+#define PSS_FLAG1     0x1000
+#define PSS_FLAG0     0x0800
+#define PSS_WRITE_EMPTY  0x8000
+#define PSS_READ_FULL    0x4000
+
+/*
+ * WSS registers
+ */
+#define WSS_INDEX 4
+#define WSS_DATA 5
+
+/*
+ * WSS status bits
+ */
+#define WSS_INITIALIZING 0x80
+#define WSS_AUTOCALIBRATION 0x20
+
+#define NO_WSS_MIXER	-1
+
+#include "coproc.h"
+
+#include "pss_boot.h"
+
+/* If compiled into kernel, it enable or disable pss mixer */
+#ifdef CONFIG_PSS_MIXER
+static int pss_mixer = 1;
+#else
+static int pss_mixer;
+#endif
+
+
+typedef struct pss_mixerdata {
+	unsigned int volume_l;
+	unsigned int volume_r;
+	unsigned int bass;
+	unsigned int treble;
+	unsigned int synth;
+} pss_mixerdata;
+
+typedef struct pss_confdata {
+	int             base;
+	int             irq;
+	int             dma;
+	int            *osp;
+	pss_mixerdata   mixer;
+	int             ad_mixer_dev;
+} pss_confdata;
+  
+static pss_confdata pss_data;
+static pss_confdata *devc = &pss_data;
+static DEFINE_SPINLOCK(lock);
+
+static int      pss_initialized;
+static int      nonstandard_microcode;
+static int	pss_cdrom_port = -1;	/* Parameter for the PSS cdrom port */
+static int	pss_enable_joystick;    /* Parameter for enabling the joystick */
+static coproc_operations pss_coproc_operations;
+
+static void pss_write(pss_confdata *devc, int data)
+{
+	unsigned long i, limit;
+
+	limit = jiffies + HZ/10;	/* The timeout is 0.1 seconds */
+	/*
+	 * Note! the i<5000000 is an emergency exit. The dsp_command() is sometimes
+	 * called while interrupts are disabled. This means that the timer is
+	 * disabled also. However the timeout situation is a abnormal condition.
+	 * Normally the DSP should be ready to accept commands after just couple of
+	 * loops.
+	 */
+
+	for (i = 0; i < 5000000 && time_before(jiffies, limit); i++)
+ 	{
+ 		if (inw(REG(PSS_STATUS)) & PSS_WRITE_EMPTY)
+ 		{
+ 			outw(data, REG(PSS_DATA));
+ 			return;
+ 		}
+ 	}
+ 	printk(KERN_WARNING "PSS: DSP Command (%04x) Timeout.\n", data);
+}
+
+static int __init probe_pss(struct address_info *hw_config)
+{
+	unsigned short id;
+	int irq, dma;
+
+	devc->base = hw_config->io_base;
+	irq = devc->irq = hw_config->irq;
+	dma = devc->dma = hw_config->dma;
+	devc->osp = hw_config->osp;
+
+	if (devc->base != 0x220 && devc->base != 0x240)
+		if (devc->base != 0x230 && devc->base != 0x250)		/* Some cards use these */
+			return 0;
+
+	if (!request_region(devc->base, 0x10, "PSS mixer, SB emulation")) {
+		printk(KERN_ERR "PSS: I/O port conflict\n");
+		return 0;
+	}
+	id = inw(REG(PSS_ID));
+	if ((id >> 8) != 'E') {
+		printk(KERN_ERR "No PSS signature detected at 0x%x (0x%x)\n",  devc->base,  id); 
+		release_region(devc->base, 0x10);
+		return 0;
+	}
+	if (!request_region(devc->base + 0x10, 0x9, "PSS config")) {
+		printk(KERN_ERR "PSS: I/O port conflict\n");
+		release_region(devc->base, 0x10);
+		return 0;
+	}
+	return 1;
+}
+
+static int set_irq(pss_confdata * devc, int dev, int irq)
+{
+	static unsigned short irq_bits[16] =
+	{
+		0x0000, 0x0000, 0x0000, 0x0008,
+		0x0000, 0x0010, 0x0000, 0x0018,
+		0x0000, 0x0020, 0x0028, 0x0030,
+		0x0038, 0x0000, 0x0000, 0x0000
+	};
+
+	unsigned short  tmp, bits;
+
+	if (irq < 0 || irq > 15)
+		return 0;
+
+	tmp = inw(REG(dev)) & ~0x38;	/* Load confreg, mask IRQ bits out */
+
+	if ((bits = irq_bits[irq]) == 0 && irq != 0)
+	{
+		printk(KERN_ERR "PSS: Invalid IRQ %d\n", irq);
+		return 0;
+	}
+	outw(tmp | bits, REG(dev));
+	return 1;
+}
+
+static void set_io_base(pss_confdata * devc, int dev, int base)
+{
+	unsigned short  tmp = inw(REG(dev)) & 0x003f;
+	unsigned short  bits = (base & 0x0ffc) << 4;
+
+	outw(bits | tmp, REG(dev));
+}
+
+static int set_dma(pss_confdata * devc, int dev, int dma)
+{
+	static unsigned short dma_bits[8] =
+	{
+		0x0001, 0x0002, 0x0000, 0x0003,
+		0x0000, 0x0005, 0x0006, 0x0007
+	};
+
+	unsigned short  tmp, bits;
+
+	if (dma < 0 || dma > 7)
+		return 0;
+
+	tmp = inw(REG(dev)) & ~0x07;	/* Load confreg, mask DMA bits out */
+
+	if ((bits = dma_bits[dma]) == 0 && dma != 4)
+	{
+		  printk(KERN_ERR "PSS: Invalid DMA %d\n", dma);
+		  return 0;
+	}
+	outw(tmp | bits, REG(dev));
+	return 1;
+}
+
+static int pss_reset_dsp(pss_confdata * devc)
+{
+	unsigned long   i, limit = jiffies + HZ/10;
+
+	outw(0x2000, REG(PSS_CONTROL));
+	for (i = 0; i < 32768 && time_after_eq(limit, jiffies); i++)
+		inw(REG(PSS_CONTROL));
+	outw(0x0000, REG(PSS_CONTROL));
+	return 1;
+}
+
+static int pss_put_dspword(pss_confdata * devc, unsigned short word)
+{
+	int i, val;
+
+	for (i = 0; i < 327680; i++)
+	{
+		val = inw(REG(PSS_STATUS));
+		if (val & PSS_WRITE_EMPTY)
+		{
+			outw(word, REG(PSS_DATA));
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int pss_get_dspword(pss_confdata * devc, unsigned short *word)
+{
+	int i, val;
+
+	for (i = 0; i < 327680; i++)
+	{
+		val = inw(REG(PSS_STATUS));
+		if (val & PSS_READ_FULL)
+		{
+			*word = inw(REG(PSS_DATA));
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int pss_download_boot(pss_confdata * devc, unsigned char *block, int size, int flags)
+{
+	int i, val, count;
+	unsigned long limit;
+
+	if (flags & CPF_FIRST)
+	{
+/*_____ Warn DSP software that a boot is coming */
+		outw(0x00fe, REG(PSS_DATA));
+
+		limit = jiffies + HZ/10;
+		for (i = 0; i < 32768 && time_before(jiffies, limit); i++)
+			if (inw(REG(PSS_DATA)) == 0x5500)
+				break;
+
+		outw(*block++, REG(PSS_DATA));
+		pss_reset_dsp(devc);
+	}
+	count = 1;
+	while ((flags&CPF_LAST) || count<size )
+	{
+		int j;
+
+		for (j = 0; j < 327670; j++)
+		{
+/*_____ Wait for BG to appear */
+			if (inw(REG(PSS_STATUS)) & PSS_FLAG3)
+				break;
+		}
+
+		if (j == 327670)
+		{
+			/* It's ok we timed out when the file was empty */
+			if (count >= size && flags & CPF_LAST)
+				break;
+			else
+			{
+				printk("\n");
+				printk(KERN_ERR "PSS: Download timeout problems, byte %d=%d\n", count, size);
+				return 0;
+			}
+		}
+/*_____ Send the next byte */
+		if (count >= size) 
+		{
+			/* If not data in block send 0xffff */
+			outw (0xffff, REG (PSS_DATA));
+		}
+		else
+		{
+			/*_____ Send the next byte */
+			outw (*block++, REG (PSS_DATA));
+		};
+		count++;
+	}
+
+	if (flags & CPF_LAST)
+	{
+/*_____ Why */
+		outw(0, REG(PSS_DATA));
+
+		limit = jiffies + HZ/10;
+		for (i = 0; i < 32768 && time_after_eq(limit, jiffies); i++)
+			val = inw(REG(PSS_STATUS));
+
+		limit = jiffies + HZ/10;
+		for (i = 0; i < 32768 && time_after_eq(limit, jiffies); i++)
+		{
+			val = inw(REG(PSS_STATUS));
+			if (val & 0x4000)
+				break;
+		}
+
+		/* now read the version */
+		for (i = 0; i < 32000; i++)
+		{
+			val = inw(REG(PSS_STATUS));
+			if (val & PSS_READ_FULL)
+				break;
+		}
+		if (i == 32000)
+			return 0;
+
+		val = inw(REG(PSS_DATA));
+		/* printk( "<PSS: microcode version %d.%d loaded>",  val/16,  val % 16); */
+	}
+	return 1;
+}
+
+/* Mixer */
+static void set_master_volume(pss_confdata *devc, int left, int right)
+{
+	static unsigned char log_scale[101] =  {
+		0xdb, 0xe0, 0xe3, 0xe5, 0xe7, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xed, 0xee,
+		0xef, 0xef, 0xf0, 0xf0, 0xf1, 0xf1, 0xf2, 0xf2, 0xf2, 0xf3, 0xf3, 0xf3,
+		0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf6, 0xf7,
+		0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9,
+		0xf9, 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb,
+		0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc,
+		0xfc, 0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd,
+		0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+		0xfe, 0xfe, 0xff, 0xff, 0xff
+	};
+	pss_write(devc, 0x0010);
+	pss_write(devc, log_scale[left] | 0x0000);
+	pss_write(devc, 0x0010);
+	pss_write(devc, log_scale[right] | 0x0100);
+}
+
+static void set_synth_volume(pss_confdata *devc, int volume)
+{
+	int vol = ((0x8000*volume)/100L);
+	pss_write(devc, 0x0080);
+	pss_write(devc, vol);
+	pss_write(devc, 0x0081);
+	pss_write(devc, vol);
+}
+
+static void set_bass(pss_confdata *devc, int level)
+{
+	int vol = (int)(((0xfd - 0xf0) * level)/100L) + 0xf0;
+	pss_write(devc, 0x0010);
+	pss_write(devc, vol | 0x0200);
+};
+
+static void set_treble(pss_confdata *devc, int level)
+{	
+	int vol = (((0xfd - 0xf0) * level)/100L) + 0xf0;
+	pss_write(devc, 0x0010);
+	pss_write(devc, vol | 0x0300);
+};
+
+static void pss_mixer_reset(pss_confdata *devc)
+{
+	set_master_volume(devc, 33, 33);
+	set_bass(devc, 50);
+	set_treble(devc, 50);
+	set_synth_volume(devc, 30);
+	pss_write (devc, 0x0010);
+	pss_write (devc, 0x0800 | 0xce);	/* Stereo */
+	
+	if(pss_mixer)
+	{
+		devc->mixer.volume_l = devc->mixer.volume_r = 33;
+		devc->mixer.bass = 50;
+		devc->mixer.treble = 50;
+		devc->mixer.synth = 30;
+	}
+}
+
+static int set_volume_mono(unsigned __user *p, unsigned int *aleft)
+{
+	unsigned int left, volume;
+	if (get_user(volume, p))
+		return -EFAULT;
+	
+	left = volume & 0xff;
+	if (left > 100)
+		left = 100;
+	*aleft = left;
+	return 0;
+}
+
+static int set_volume_stereo(unsigned __user *p,
+			     unsigned int *aleft,
+			     unsigned int *aright)
+{
+	unsigned int left, right, volume;
+	if (get_user(volume, p))
+		return -EFAULT;
+
+	left = volume & 0xff;
+	if (left > 100)
+		left = 100;
+	right = (volume >> 8) & 0xff;
+	if (right > 100)
+		right = 100;
+	*aleft = left;
+	*aright = right;
+	return 0;
+}
+
+static int ret_vol_mono(int left)
+{
+	return ((left << 8) | left);
+}
+
+static int ret_vol_stereo(int left, int right)
+{
+	return ((right << 8) | left);
+}
+
+static int call_ad_mixer(pss_confdata *devc,unsigned int cmd, void __user *arg)
+{
+	if (devc->ad_mixer_dev != NO_WSS_MIXER) 
+		return mixer_devs[devc->ad_mixer_dev]->ioctl(devc->ad_mixer_dev, cmd, arg);
+	else 
+		return -EINVAL;
+}
+
+static int pss_mixer_ioctl (int dev, unsigned int cmd, void __user *arg)
+{
+	pss_confdata *devc = mixer_devs[dev]->devc;
+	int cmdf = cmd & 0xff;
+	
+	if ((cmdf != SOUND_MIXER_VOLUME) && (cmdf != SOUND_MIXER_BASS) &&
+		(cmdf != SOUND_MIXER_TREBLE) && (cmdf != SOUND_MIXER_SYNTH) &&
+		(cmdf != SOUND_MIXER_DEVMASK) && (cmdf != SOUND_MIXER_STEREODEVS) &&
+		(cmdf != SOUND_MIXER_RECMASK) && (cmdf != SOUND_MIXER_CAPS) &&
+		(cmdf != SOUND_MIXER_RECSRC)) 
+	{
+		return call_ad_mixer(devc, cmd, arg);
+	}
+	
+	if (((cmd >> 8) & 0xff) != 'M')	
+		return -EINVAL;
+		
+	if (_SIOC_DIR (cmd) & _SIOC_WRITE)
+	{
+		switch (cmdf)	
+		{
+			case SOUND_MIXER_RECSRC:
+				if (devc->ad_mixer_dev != NO_WSS_MIXER)
+					return call_ad_mixer(devc, cmd, arg);
+				else
+				{
+					int v;
+					if (get_user(v, (int __user *)arg))
+						return -EFAULT;
+					if (v != 0)
+						return -EINVAL;
+					return 0;
+				}
+			case SOUND_MIXER_VOLUME:
+				if (set_volume_stereo(arg,
+					&devc->mixer.volume_l,
+					&devc->mixer.volume_r))
+					return -EFAULT;
+				set_master_volume(devc, devc->mixer.volume_l,
+					devc->mixer.volume_r);
+				return ret_vol_stereo(devc->mixer.volume_l,
+					devc->mixer.volume_r);
+		  
+			case SOUND_MIXER_BASS:
+				if (set_volume_mono(arg, &devc->mixer.bass))
+					return -EFAULT;
+				set_bass(devc, devc->mixer.bass);
+				return ret_vol_mono(devc->mixer.bass);
+		  
+			case SOUND_MIXER_TREBLE:
+				if (set_volume_mono(arg, &devc->mixer.treble))
+					return -EFAULT;
+				set_treble(devc, devc->mixer.treble);
+				return ret_vol_mono(devc->mixer.treble);
+		  
+			case SOUND_MIXER_SYNTH:
+				if (set_volume_mono(arg, &devc->mixer.synth))
+					return -EFAULT;
+				set_synth_volume(devc, devc->mixer.synth);
+				return ret_vol_mono(devc->mixer.synth);
+		  
+			default:
+				return -EINVAL;
+		}
+	}
+	else			
+	{
+		int val, and_mask = 0, or_mask = 0;
+		/*
+		 * Return parameters
+		 */
+		switch (cmdf)
+		{
+			case SOUND_MIXER_DEVMASK:
+				if (call_ad_mixer(devc, cmd, arg) == -EINVAL)
+					break;
+				and_mask = ~0;
+				or_mask = SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_SYNTH;
+				break;
+		  
+			case SOUND_MIXER_STEREODEVS:
+				if (call_ad_mixer(devc, cmd, arg) == -EINVAL)
+					break;
+				and_mask = ~0;
+				or_mask = SOUND_MASK_VOLUME;
+				break;
+		  
+			case SOUND_MIXER_RECMASK:
+				if (devc->ad_mixer_dev != NO_WSS_MIXER)
+					return call_ad_mixer(devc, cmd, arg);
+				break;
+
+			case SOUND_MIXER_CAPS:
+				if (devc->ad_mixer_dev != NO_WSS_MIXER)
+					return call_ad_mixer(devc, cmd, arg);
+				or_mask = SOUND_CAP_EXCL_INPUT;
+				break;
+
+			case SOUND_MIXER_RECSRC:
+				if (devc->ad_mixer_dev != NO_WSS_MIXER)
+					return call_ad_mixer(devc, cmd, arg);
+				break;
+
+			case SOUND_MIXER_VOLUME:
+				or_mask =  ret_vol_stereo(devc->mixer.volume_l, devc->mixer.volume_r);
+				break;
+			  
+			case SOUND_MIXER_BASS:
+				or_mask =  ret_vol_mono(devc->mixer.bass);
+				break;
+			  
+			case SOUND_MIXER_TREBLE:
+				or_mask = ret_vol_mono(devc->mixer.treble);
+				break;
+			  
+			case SOUND_MIXER_SYNTH:
+				or_mask = ret_vol_mono(devc->mixer.synth);
+				break;
+			default:
+				return -EINVAL;
+		}
+		if (get_user(val, (int __user *)arg))
+			return -EFAULT;
+		val &= and_mask;
+		val |= or_mask;
+		if (put_user(val, (int __user *)arg))
+			return -EFAULT;
+		return val;
+	}
+}
+
+static struct mixer_operations pss_mixer_operations =
+{
+	.owner	= THIS_MODULE,
+	.id	= "SOUNDPORT",
+	.name	= "PSS-AD1848",
+	.ioctl	= pss_mixer_ioctl
+};
+
+static void disable_all_emulations(void)
+{
+	outw(0x0000, REG(CONF_PSS));	/* 0x0400 enables joystick */
+	outw(0x0000, REG(CONF_WSS));
+	outw(0x0000, REG(CONF_SB));
+	outw(0x0000, REG(CONF_MIDI));
+	outw(0x0000, REG(CONF_CDROM));
+}
+
+static void configure_nonsound_components(void)
+{
+	/* Configure Joystick port */
+
+	if(pss_enable_joystick)
+	{
+		outw(0x0400, REG(CONF_PSS));	/* 0x0400 enables joystick */
+		printk(KERN_INFO "PSS: joystick enabled.\n");
+	}
+	else
+	{
+		printk(KERN_INFO "PSS: joystick port not enabled.\n");
+	}
+
+	/* Configure CDROM port */
+
+	if (pss_cdrom_port == -1) {	/* If cdrom port enablation wasn't requested */
+		printk(KERN_INFO "PSS: CDROM port not enabled.\n");
+	} else if (check_region(pss_cdrom_port, 2)) {
+		printk(KERN_ERR "PSS: CDROM I/O port conflict.\n");
+	} else {
+		set_io_base(devc, CONF_CDROM, pss_cdrom_port);
+		printk(KERN_INFO "PSS: CDROM I/O port set to 0x%x.\n", pss_cdrom_port);
+	}
+}
+
+static int __init attach_pss(struct address_info *hw_config)
+{
+	unsigned short  id;
+	char tmp[100];
+
+	devc->base = hw_config->io_base;
+	devc->irq = hw_config->irq;
+	devc->dma = hw_config->dma;
+	devc->osp = hw_config->osp;
+	devc->ad_mixer_dev = NO_WSS_MIXER;
+
+	if (!probe_pss(hw_config))
+		return 0;
+
+	id = inw(REG(PSS_ID)) & 0x00ff;
+
+	/*
+	 * Disable all emulations. Will be enabled later (if required).
+	 */
+	 
+	disable_all_emulations();
+
+#ifdef YOU_REALLY_WANT_TO_ALLOCATE_THESE_RESOURCES
+	if (sound_alloc_dma(hw_config->dma, "PSS"))
+	{
+		printk("pss.c: Can't allocate DMA channel.\n");
+		release_region(hw_config->io_base, 0x10);
+		release_region(hw_config->io_base+0x10, 0x9);
+		return 0;
+	}
+	if (!set_irq(devc, CONF_PSS, devc->irq))
+	{
+		printk("PSS: IRQ allocation error.\n");
+		release_region(hw_config->io_base, 0x10);
+		release_region(hw_config->io_base+0x10, 0x9);
+		return 0;
+	}
+	if (!set_dma(devc, CONF_PSS, devc->dma))
+	{
+		printk(KERN_ERR "PSS: DMA allocation error\n");
+		release_region(hw_config->io_base, 0x10);
+		release_region(hw_config->io_base+0x10, 0x9);
+		return 0;
+	}
+#endif
+
+	configure_nonsound_components();
+	pss_initialized = 1;
+	sprintf(tmp, "ECHO-PSS  Rev. %d", id);
+	conf_printf(tmp, hw_config);
+	return 1;
+}
+
+static int __init probe_pss_mpu(struct address_info *hw_config)
+{
+	struct resource *ports;
+	int timeout;
+
+	if (!pss_initialized)
+		return 0;
+
+	ports = request_region(hw_config->io_base, 2, "mpu401");
+
+	if (!ports) {
+		printk(KERN_ERR "PSS: MPU I/O port conflict\n");
+		return 0;
+	}
+	set_io_base(devc, CONF_MIDI, hw_config->io_base);
+	if (!set_irq(devc, CONF_MIDI, hw_config->irq)) {
+		printk(KERN_ERR "PSS: MIDI IRQ allocation error.\n");
+		goto fail;
+	}
+	if (!pss_synthLen) {
+		printk(KERN_ERR "PSS: Can't enable MPU. MIDI synth microcode not available.\n");
+		goto fail;
+	}
+	if (!pss_download_boot(devc, pss_synth, pss_synthLen, CPF_FIRST | CPF_LAST)) {
+		printk(KERN_ERR "PSS: Unable to load MIDI synth microcode to DSP.\n");
+		goto fail;
+	}
+
+	/*
+	 * Finally wait until the DSP algorithm has initialized itself and
+	 * deactivates receive interrupt.
+	 */
+
+	for (timeout = 900000; timeout > 0; timeout--)
+	{
+		if ((inb(hw_config->io_base + 1) & 0x80) == 0)	/* Input data avail */
+			inb(hw_config->io_base);	/* Discard it */
+		else
+			break;	/* No more input */
+	}
+
+	if (!probe_mpu401(hw_config, ports))
+		goto fail;
+
+	attach_mpu401(hw_config, THIS_MODULE);	/* Slot 1 */
+	if (hw_config->slots[1] != -1)	/* The MPU driver installed itself */
+		midi_devs[hw_config->slots[1]]->coproc = &pss_coproc_operations;
+	return 1;
+fail:
+	release_region(hw_config->io_base, 2);
+	return 0;
+}
+
+static int pss_coproc_open(void *dev_info, int sub_device)
+{
+	switch (sub_device)
+	{
+		case COPR_MIDI:
+			if (pss_synthLen == 0)
+			{
+				printk(KERN_ERR "PSS: MIDI synth microcode not available.\n");
+				return -EIO;
+			}
+			if (nonstandard_microcode)
+				if (!pss_download_boot(devc, pss_synth, pss_synthLen, CPF_FIRST | CPF_LAST))
+			{
+				printk(KERN_ERR "PSS: Unable to load MIDI synth microcode to DSP.\n");
+				return -EIO;
+			}
+			nonstandard_microcode = 0;
+			break;
+
+		default:
+			break;
+	}
+	return 0;
+}
+
+static void pss_coproc_close(void *dev_info, int sub_device)
+{
+	return;
+}
+
+static void pss_coproc_reset(void *dev_info)
+{
+	if (pss_synthLen)
+		if (!pss_download_boot(devc, pss_synth, pss_synthLen, CPF_FIRST | CPF_LAST))
+		{
+			printk(KERN_ERR "PSS: Unable to load MIDI synth microcode to DSP.\n");
+		}
+	nonstandard_microcode = 0;
+}
+
+static int download_boot_block(void *dev_info, copr_buffer * buf)
+{
+	if (buf->len <= 0 || buf->len > sizeof(buf->data))
+		return -EINVAL;
+
+	if (!pss_download_boot(devc, buf->data, buf->len, buf->flags))
+	{
+		printk(KERN_ERR "PSS: Unable to load microcode block to DSP.\n");
+		return -EIO;
+	}
+	nonstandard_microcode = 1;	/* The MIDI microcode has been overwritten */
+	return 0;
+}
+
+static int pss_coproc_ioctl(void *dev_info, unsigned int cmd, void __user *arg, int local)
+{
+	copr_buffer *buf;
+	copr_msg *mbuf;
+	copr_debug_buf dbuf;
+	unsigned short tmp;
+	unsigned long flags;
+	unsigned short *data;
+	int i, err;
+	/* printk( "PSS coproc ioctl %x %x %d\n",  cmd,  arg,  local); */
+	
+	switch (cmd) 
+	{
+		case SNDCTL_COPR_RESET:
+			pss_coproc_reset(dev_info);
+			return 0;
+
+		case SNDCTL_COPR_LOAD:
+			buf = vmalloc(sizeof(copr_buffer));
+			if (buf == NULL)
+				return -ENOSPC;
+			if (copy_from_user(buf, arg, sizeof(copr_buffer))) {
+				vfree(buf);
+				return -EFAULT;
+			}
+			err = download_boot_block(dev_info, buf);
+			vfree(buf);
+			return err;
+		
+		case SNDCTL_COPR_SENDMSG:
+			mbuf = vmalloc(sizeof(copr_msg));
+			if (mbuf == NULL)
+				return -ENOSPC;
+			if (copy_from_user(mbuf, arg, sizeof(copr_msg))) {
+				vfree(mbuf);
+				return -EFAULT;
+			}
+			data = (unsigned short *)(mbuf->data);
+			spin_lock_irqsave(&lock, flags);
+			for (i = 0; i < mbuf->len; i++) {
+				if (!pss_put_dspword(devc, *data++)) {
+					spin_unlock_irqrestore(&lock,flags);
+					mbuf->len = i;	/* feed back number of WORDs sent */
+					err = copy_to_user(arg, mbuf, sizeof(copr_msg));
+					vfree(mbuf);
+					return err ? -EFAULT : -EIO;
+				}
+			}
+			spin_unlock_irqrestore(&lock,flags);
+			vfree(mbuf);
+			return 0;
+
+		case SNDCTL_COPR_RCVMSG:
+			err = 0;
+			mbuf = vmalloc(sizeof(copr_msg));
+			if (mbuf == NULL)
+				return -ENOSPC;
+			data = (unsigned short *)mbuf->data;
+			spin_lock_irqsave(&lock, flags);
+			for (i = 0; i < sizeof(mbuf->data)/sizeof(unsigned short); i++) {
+				mbuf->len = i;	/* feed back number of WORDs read */
+				if (!pss_get_dspword(devc, data++)) {
+					if (i == 0)
+						err = -EIO;
+					break;
+				}
+			}
+			spin_unlock_irqrestore(&lock,flags);
+			if (copy_to_user(arg, mbuf, sizeof(copr_msg)))
+				err = -EFAULT;
+			vfree(mbuf);
+			return err;
+		
+		case SNDCTL_COPR_RDATA:
+			if (copy_from_user(&dbuf, arg, sizeof(dbuf)))
+				return -EFAULT;
+			spin_lock_irqsave(&lock, flags);
+			if (!pss_put_dspword(devc, 0x00d0)) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			if (!pss_put_dspword(devc, (unsigned short)(dbuf.parm1 & 0xffff))) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			if (!pss_get_dspword(devc, &tmp)) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			dbuf.parm1 = tmp;
+			spin_unlock_irqrestore(&lock,flags);
+			if (copy_to_user(arg, &dbuf, sizeof(dbuf)))
+				return -EFAULT;
+			return 0;
+		
+		case SNDCTL_COPR_WDATA:
+			if (copy_from_user(&dbuf, arg, sizeof(dbuf)))
+				return -EFAULT;
+			spin_lock_irqsave(&lock, flags);
+			if (!pss_put_dspword(devc, 0x00d1)) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			if (!pss_put_dspword(devc, (unsigned short) (dbuf.parm1 & 0xffff))) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			tmp = (unsigned int)dbuf.parm2 & 0xffff;
+			if (!pss_put_dspword(devc, tmp)) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			spin_unlock_irqrestore(&lock,flags);
+			return 0;
+		
+		case SNDCTL_COPR_WCODE:
+			if (copy_from_user(&dbuf, arg, sizeof(dbuf)))
+				return -EFAULT;
+			spin_lock_irqsave(&lock, flags);
+			if (!pss_put_dspword(devc, 0x00d3)) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			if (!pss_put_dspword(devc, (unsigned short)(dbuf.parm1 & 0xffff))) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			tmp = (unsigned int)dbuf.parm2 & 0x00ff;
+			if (!pss_put_dspword(devc, tmp)) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			tmp = ((unsigned int)dbuf.parm2 >> 8) & 0xffff;
+			if (!pss_put_dspword(devc, tmp)) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			spin_unlock_irqrestore(&lock,flags);
+			return 0;
+		
+		case SNDCTL_COPR_RCODE:
+			if (copy_from_user(&dbuf, arg, sizeof(dbuf)))
+				return -EFAULT;
+			spin_lock_irqsave(&lock, flags);
+			if (!pss_put_dspword(devc, 0x00d2)) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			if (!pss_put_dspword(devc, (unsigned short)(dbuf.parm1 & 0xffff))) {
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			if (!pss_get_dspword(devc, &tmp)) { /* Read MSB */
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			dbuf.parm1 = tmp << 8;
+			if (!pss_get_dspword(devc, &tmp)) { /* Read LSB */
+				spin_unlock_irqrestore(&lock,flags);
+				return -EIO;
+			}
+			dbuf.parm1 |= tmp & 0x00ff;
+			spin_unlock_irqrestore(&lock,flags);
+			if (copy_to_user(arg, &dbuf, sizeof(dbuf)))
+				return -EFAULT;
+			return 0;
+
+		default:
+			return -EINVAL;
+	}
+	return -EINVAL;
+}
+
+static coproc_operations pss_coproc_operations =
+{
+	"ADSP-2115",
+	THIS_MODULE,
+	pss_coproc_open,
+	pss_coproc_close,
+	pss_coproc_ioctl,
+	pss_coproc_reset,
+	&pss_data
+};
+
+static int __init probe_pss_mss(struct address_info *hw_config)
+{
+	volatile int timeout;
+	struct resource *ports;
+	int        my_mix = -999;	/* gcc shut up */
+
+	if (!pss_initialized)
+		return 0;
+
+	if (!request_region(hw_config->io_base, 4, "WSS config")) {
+		printk(KERN_ERR "PSS: WSS I/O port conflicts.\n");
+		return 0;
+	}
+	ports = request_region(hw_config->io_base + 4, 4, "ad1848");
+	if (!ports) {
+		printk(KERN_ERR "PSS: WSS I/O port conflicts.\n");
+		release_region(hw_config->io_base, 4);
+		return 0;
+	}
+	set_io_base(devc, CONF_WSS, hw_config->io_base);
+	if (!set_irq(devc, CONF_WSS, hw_config->irq)) {
+		printk("PSS: WSS IRQ allocation error.\n");
+		goto fail;
+	}
+	if (!set_dma(devc, CONF_WSS, hw_config->dma)) {
+		printk(KERN_ERR "PSS: WSS DMA allocation error\n");
+		goto fail;
+	}
+	/*
+	 * For some reason the card returns 0xff in the WSS status register
+	 * immediately after boot. Probably MIDI+SB emulation algorithm
+	 * downloaded to the ADSP2115 spends some time initializing the card.
+	 * Let's try to wait until it finishes this task.
+	 */
+	for (timeout = 0; timeout < 100000 && (inb(hw_config->io_base + WSS_INDEX) &
+	  WSS_INITIALIZING); timeout++)
+		;
+
+	outb((0x0b), hw_config->io_base + WSS_INDEX);	/* Required by some cards */
+
+	for (timeout = 0; (inb(hw_config->io_base + WSS_DATA) & WSS_AUTOCALIBRATION) &&
+	  (timeout < 100000); timeout++)
+		;
+
+	if (!probe_ms_sound(hw_config, ports))
+		goto fail;
+
+	devc->ad_mixer_dev = NO_WSS_MIXER;
+	if (pss_mixer) 
+	{
+		if ((my_mix = sound_install_mixer (MIXER_DRIVER_VERSION,
+			"PSS-SPEAKERS and AD1848 (through MSS audio codec)",
+			&pss_mixer_operations,
+			sizeof (struct mixer_operations),
+			devc)) < 0) 
+		{
+			printk(KERN_ERR "Could not install PSS mixer\n");
+			goto fail;
+		}
+	}
+	pss_mixer_reset(devc);
+	attach_ms_sound(hw_config, ports, THIS_MODULE);	/* Slot 0 */
+
+	if (hw_config->slots[0] != -1)
+	{
+		/* The MSS driver installed itself */
+		audio_devs[hw_config->slots[0]]->coproc = &pss_coproc_operations;
+		if (pss_mixer && (num_mixers == (my_mix + 2)))
+		{
+			/* The MSS mixer installed */
+			devc->ad_mixer_dev = audio_devs[hw_config->slots[0]]->mixer_dev;
+		}
+	}
+	return 1;
+fail:
+	release_region(hw_config->io_base + 4, 4);
+	release_region(hw_config->io_base, 4);
+	return 0;
+}
+
+static inline void __exit unload_pss(struct address_info *hw_config)
+{
+	release_region(hw_config->io_base, 0x10);
+	release_region(hw_config->io_base+0x10, 0x9);
+}
+
+static inline void __exit unload_pss_mpu(struct address_info *hw_config)
+{
+	unload_mpu401(hw_config);
+}
+
+static inline void __exit unload_pss_mss(struct address_info *hw_config)
+{
+	unload_ms_sound(hw_config);
+}
+
+
+static struct address_info cfg;
+static struct address_info cfg2;
+static struct address_info cfg_mpu;
+
+static int pss_io __initdata	= -1;
+static int mss_io __initdata	= -1;
+static int mss_irq __initdata	= -1;
+static int mss_dma __initdata	= -1;
+static int mpu_io __initdata	= -1;
+static int mpu_irq __initdata	= -1;
+static int pss_no_sound = 0;	/* Just configure non-sound components */
+static int pss_keep_settings  = 1;	/* Keep hardware settings at module exit */
+static char *pss_firmware = "/etc/sound/pss_synth";
+
+module_param(pss_io, int, 0);
+MODULE_PARM_DESC(pss_io, "Set i/o base of PSS card (probably 0x220 or 0x240)");
+module_param(mss_io, int, 0);
+MODULE_PARM_DESC(mss_io, "Set WSS (audio) i/o base (0x530, 0x604, 0xE80, 0xF40, or other. Address must end in 0 or 4 and must be from 0x100 to 0xFF4)");
+module_param(mss_irq, int, 0);
+MODULE_PARM_DESC(mss_irq, "Set WSS (audio) IRQ (3, 5, 7, 9, 10, 11, 12)");
+module_param(mss_dma, int, 0);
+MODULE_PARM_DESC(mss_dma, "Set WSS (audio) DMA (0, 1, 3)");
+module_param(mpu_io, int, 0);
+MODULE_PARM_DESC(mpu_io, "Set MIDI i/o base (0x330 or other. Address must be on 4 location boundaries and must be from 0x100 to 0xFFC)");
+module_param(mpu_irq, int, 0);
+MODULE_PARM_DESC(mpu_irq, "Set MIDI IRQ (3, 5, 7, 9, 10, 11, 12)");
+module_param(pss_cdrom_port, int, 0);
+MODULE_PARM_DESC(pss_cdrom_port, "Set the PSS CDROM port i/o base (0x340 or other)");
+module_param(pss_enable_joystick, bool, 0);
+MODULE_PARM_DESC(pss_enable_joystick, "Enables the PSS joystick port (1 to enable, 0 to disable)");
+module_param(pss_no_sound, bool, 0);
+MODULE_PARM_DESC(pss_no_sound, "Configure sound compoents (0 - no, 1 - yes)");
+module_param(pss_keep_settings, bool, 0);
+MODULE_PARM_DESC(pss_keep_settings, "Keep hardware setting at driver unloading (0 - no, 1 - yes)");
+module_param(pss_firmware, charp, 0);
+MODULE_PARM_DESC(pss_firmware, "Location of the firmware file (default - /etc/sound/pss_synth)");
+module_param(pss_mixer, bool, 0);
+MODULE_PARM_DESC(pss_mixer, "Enable (1) or disable (0) PSS mixer (controlling of output volume, bass, treble, synth volume). The mixer is not available on all PSS cards.");
+MODULE_AUTHOR("Hannu Savolainen, Vladimir Michl");
+MODULE_DESCRIPTION("Module for PSS sound cards (based on AD1848, ADSP-2115 and ESC614). This module includes control of output amplifier and synth volume of the Beethoven ADSP-16 card (this may work with other PSS cards).");
+MODULE_LICENSE("GPL");
+
+
+static int fw_load = 0;
+static int pssmpu = 0, pssmss = 0;
+
+/*
+ *    Load a PSS sound card module
+ */
+
+static int __init init_pss(void)
+{
+
+	if(pss_no_sound)		/* If configuring only nonsound components */
+	{
+		cfg.io_base = pss_io;
+		if(!probe_pss(&cfg))
+			return -ENODEV;
+		printk(KERN_INFO "ECHO-PSS  Rev. %d\n", inw(REG(PSS_ID)) & 0x00ff);
+		printk(KERN_INFO "PSS: loading in no sound mode.\n");
+		disable_all_emulations();
+		configure_nonsound_components();
+		release_region(pss_io, 0x10);
+		release_region(pss_io + 0x10, 0x9);
+		return 0;
+	}
+
+	cfg.io_base = pss_io;
+
+	cfg2.io_base = mss_io;
+	cfg2.irq = mss_irq;
+	cfg2.dma = mss_dma;
+
+	cfg_mpu.io_base = mpu_io;
+	cfg_mpu.irq = mpu_irq;
+
+	if (cfg.io_base == -1 || cfg2.io_base == -1 || cfg2.irq == -1 || cfg.dma == -1) {
+		printk(KERN_INFO "pss: mss_io, mss_dma, mss_irq and pss_io must be set.\n");
+		return -EINVAL;
+	}
+
+	if (!pss_synth) {
+		fw_load = 1;
+		pss_synthLen = mod_firmware_load(pss_firmware, (void *) &pss_synth);
+	}
+	if (!attach_pss(&cfg))
+		return -ENODEV;
+	/*
+	 *    Attach stuff
+	 */
+	if (probe_pss_mpu(&cfg_mpu))
+		pssmpu = 1;
+
+	if (probe_pss_mss(&cfg2))
+		pssmss = 1;
+
+	return 0;
+}
+
+static void __exit cleanup_pss(void)
+{
+	if(!pss_no_sound)
+	{
+		if(fw_load && pss_synth)
+			vfree(pss_synth);
+		if(pssmss)
+			unload_pss_mss(&cfg2);
+		if(pssmpu)
+			unload_pss_mpu(&cfg_mpu);
+		unload_pss(&cfg);
+	}
+
+	if(!pss_keep_settings)	/* Keep hardware settings if asked */
+	{
+		disable_all_emulations();
+		printk(KERN_INFO "Resetting PSS sound card configurations.\n");
+	}
+}
+
+module_init(init_pss);
+module_exit(cleanup_pss);
+
+#ifndef MODULE
+static int __init setup_pss(char *str)
+{
+	/* io, mss_io, mss_irq, mss_dma, mpu_io, mpu_irq */
+	int ints[7];
+	
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+
+	pss_io	= ints[1];
+	mss_io	= ints[2];
+	mss_irq	= ints[3];
+	mss_dma	= ints[4];
+	mpu_io	= ints[5];
+	mpu_irq	= ints[6];
+
+	return 1;
+}
+
+__setup("pss=", setup_pss);
+#endif
diff --git a/linux/sound/oss/sb.h b/linux/sound/oss/sb.h
new file mode 100644
index 0000000..77e8891
--- /dev/null
+++ b/linux/sound/oss/sb.h
@@ -0,0 +1,185 @@
+#define DSP_RESET	(devc->base + 0x6)
+#define DSP_READ	(devc->base + 0xA)
+#define DSP_WRITE	(devc->base + 0xC)
+#define DSP_COMMAND	(devc->base + 0xC)
+#define DSP_STATUS	(devc->base + 0xC)
+#define DSP_DATA_AVAIL	(devc->base + 0xE)
+#define DSP_DATA_AVL16	(devc->base + 0xF)
+#define MIXER_ADDR	(devc->base + 0x4)
+#define MIXER_DATA	(devc->base + 0x5)
+#define OPL3_LEFT	(devc->base + 0x0)
+#define OPL3_RIGHT	(devc->base + 0x2)
+#define OPL3_BOTH	(devc->base + 0x8)
+/* DSP Commands */
+
+#define DSP_CMD_SPKON		0xD1
+#define DSP_CMD_SPKOFF		0xD3
+#define DSP_CMD_DMAON		0xD0
+#define DSP_CMD_DMAOFF		0xD4
+
+#define IMODE_NONE		0
+#define IMODE_OUTPUT		PCM_ENABLE_OUTPUT
+#define IMODE_INPUT		PCM_ENABLE_INPUT
+#define IMODE_INIT		3
+#define IMODE_MIDI		4
+
+#define NORMAL_MIDI	0
+#define UART_MIDI	1
+
+
+/*
+ * Device models
+ */
+#define MDL_NONE	0
+#define MDL_SB1		1	/* SB1.0 or 1.5 */
+#define MDL_SB2		2	/* SB2.0 */
+#define MDL_SB201	3	/* SB2.01 */
+#define MDL_SBPRO	4	/* SB Pro */
+#define MDL_SB16	5	/* SB16/32/AWE */
+#define MDL_SBPNP 	6	/* SB16/32/AWE PnP */
+#define MDL_JAZZ	10	/* Media Vision Jazz16 */
+#define MDL_SMW		11	/* Logitech SoundMan Wave (Jazz16) */
+#define MDL_ESS		12	/* ESS ES688 and ES1688 */
+#define MDL_AZTECH	13	/* Aztech Sound Galaxy family */
+#define MDL_ES1868MIDI	14	/* MIDI port of ESS1868 */
+#define MDL_AEDSP	15	/* Audio Excel DSP 16 */
+#define MDL_ESSPCI	16	/* ESS PCI card */
+#define MDL_YMPCI	17	/* Yamaha PCI sb in emulation */
+
+#define SUBMDL_ALS007	42	/* ALS-007 differs from SB16 only in mixer */
+				/* register assignment */
+#define SUBMDL_ALS100	43	/* ALS-100 allows sampling rates of up */
+				/* to 48kHz */
+				
+/*
+ * Config flags
+ */
+#define SB_NO_MIDI	0x00000001
+#define SB_NO_MIXER	0x00000002
+#define SB_NO_AUDIO	0x00000004
+#define SB_NO_RECORDING	0x00000008 /* No audio recording */
+#define SB_MIDI_ONLY	(SB_NO_AUDIO|SB_NO_MIXER)
+#define SB_PCI_IRQ	0x00000010 /* PCI shared IRQ */
+
+struct mixer_def {
+	unsigned int regno: 8;
+	unsigned int bitoffs:4;
+	unsigned int nbits:4;
+};
+
+typedef struct mixer_def mixer_tab[32][2];
+typedef struct mixer_def mixer_ent;
+
+struct sb_module_options
+{
+	int  esstype;	/* ESS chip type */
+	int  acer;	/* Do acer notebook init? */
+	int  sm_games;	/* Logitech soundman games? */
+};
+
+typedef struct sb_devc {
+	   int dev;
+
+	/* Hardware parameters */
+	   int *osp;
+	   int minor, major;
+	   int type;
+	   int model, submodel;
+	   int caps;
+#	define SBCAP_STEREO	0x00000001
+#	define SBCAP_16BITS	0x00000002
+
+	/* Hardware resources */
+	   int base;
+	   int irq;
+	   int dma8, dma16;
+	   
+	   int pcibase;		/* For ESS Maestro etc */
+
+	/* State variables */
+ 	   int opened;
+	/* new audio fields for full duplex support */
+	   int fullduplex;
+	   int duplex;
+	   int speed, bits, channels;
+	   volatile int irq_ok;
+	   volatile int intr_active, irq_mode;
+	/* duplicate audio fields for full duplex support */
+	   volatile int intr_active_16, irq_mode_16;
+
+	/* Mixer fields */
+	   int *levels;
+	   mixer_tab *iomap;
+	   size_t iomap_sz; /* number or records in the iomap table */
+	   int mixer_caps, recmask, outmask, supported_devices;
+	   int supported_rec_devices, supported_out_devices;
+	   int my_mixerdev;
+	   int sbmixnum;
+
+	/* Audio fields */
+	   unsigned long trg_buf;
+	   int      trigger_bits;
+	   int      trg_bytes;
+	   int      trg_intrflag;
+	   int      trg_restart;
+	/* duplicate audio fields for full duplex support */
+	   unsigned long trg_buf_16;
+	   int      trigger_bits_16;
+	   int      trg_bytes_16;
+	   int      trg_intrflag_16;
+	   int      trg_restart_16;
+
+	   unsigned char tconst;
+	
+	/* MIDI fields */
+	   int my_mididev;
+	   int input_opened;
+	   int midi_broken;
+	   void (*midi_input_intr) (int dev, unsigned char data);
+	   void *midi_irq_cookie;		/* IRQ cookie for the midi */
+
+	   spinlock_t lock;
+
+	   struct sb_module_options sbmo;	/* Module options */
+
+	} sb_devc;
+	
+/*
+ *	PCI card types
+ */
+
+#define	SB_PCI_ESSMAESTRO	1	/* ESS Maestro Legacy */
+#define	SB_PCI_YAMAHA		2	/* Yamaha Legacy */
+
+/* 
+ *	Functions
+ */
+ 
+int sb_dsp_command (sb_devc *devc, unsigned char val);
+int sb_dsp_get_byte(sb_devc * devc);
+int sb_dsp_reset (sb_devc *devc);
+void sb_setmixer (sb_devc *devc, unsigned int port, unsigned int value);
+unsigned int sb_getmixer (sb_devc *devc, unsigned int port);
+int sb_dsp_detect (struct address_info *hw_config, int pci, int pciio, struct sb_module_options *sbmo);
+int sb_dsp_init (struct address_info *hw_config, struct module *owner);
+void sb_dsp_unload(struct address_info *hw_config, int sbmpu);
+int sb_mixer_init(sb_devc *devc, struct module *owner);
+void sb_mixer_unload(sb_devc *devc);
+void sb_mixer_set_stereo (sb_devc *devc, int mode);
+void smw_mixer_init(sb_devc *devc);
+void sb_dsp_midi_init (sb_devc *devc, struct module *owner);
+void sb_audio_init (sb_devc *devc, char *name, struct module *owner);
+void sb_midi_interrupt (sb_devc *devc);
+void sb_chgmixer (sb_devc * devc, unsigned int reg, unsigned int mask, unsigned int val);
+int sb_common_mixer_set(sb_devc * devc, int dev, int left, int right);
+
+int sb_audio_open(int dev, int mode);
+void sb_audio_close(int dev);
+
+/*	From sb_common.c */
+void sb_dsp_disable_midi(int port);
+int probe_sbmpu (struct address_info *hw_config, struct module *owner);
+void unload_sbmpu (struct address_info *hw_config);
+
+void unload_sb16(struct address_info *hw_info);
+void unload_sb16midi(struct address_info *hw_info);
diff --git a/linux/sound/oss/sb_audio.c b/linux/sound/oss/sb_audio.c
new file mode 100644
index 0000000..733b014
--- /dev/null
+++ b/linux/sound/oss/sb_audio.c
@@ -0,0 +1,1098 @@
+/*
+ * sound/oss/sb_audio.c
+ *
+ * Audio routines for Sound Blaster compatible cards.
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Changes
+ *	Alan Cox	:	Formatting and clean ups
+ *
+ * Status
+ *	Mostly working. Weird uart bug causing irq storms
+ *
+ * Daniel J. Rodriksson: Changes to make sb16 work full duplex.
+ *                       Maybe other 16 bit cards in this code could behave
+ *                       the same.
+ * Chris Rankin:         Use spinlocks instead of CLI/STI
+ */
+
+#include <linux/spinlock.h>
+
+#include "sound_config.h"
+
+#include "sb_mixer.h"
+#include "sb.h"
+
+#include "sb_ess.h"
+
+int sb_audio_open(int dev, int mode)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+	unsigned long flags;
+
+	if (devc == NULL)
+	{
+		  printk(KERN_ERR "Sound Blaster: incomplete initialization.\n");
+		  return -ENXIO;
+	}
+	if (devc->caps & SB_NO_RECORDING && mode & OPEN_READ)
+	{
+		if (mode == OPEN_READ)
+			return -EPERM;
+	}
+	spin_lock_irqsave(&devc->lock, flags);
+	if (devc->opened)
+	{
+		  spin_unlock_irqrestore(&devc->lock, flags);
+		  return -EBUSY;
+	}
+	if (devc->dma16 != -1 && devc->dma16 != devc->dma8 && !devc->duplex)
+	{
+		if (sound_open_dma(devc->dma16, "Sound Blaster 16 bit"))
+		{
+		  	spin_unlock_irqrestore(&devc->lock, flags);
+			return -EBUSY;
+		}
+	}
+	devc->opened = mode;
+	spin_unlock_irqrestore(&devc->lock, flags);
+
+	devc->irq_mode = IMODE_NONE;
+	devc->irq_mode_16 = IMODE_NONE;
+	devc->fullduplex = devc->duplex &&
+		((mode & OPEN_READ) && (mode & OPEN_WRITE));
+	sb_dsp_reset(devc);
+
+	/* At first glance this check isn't enough, some ESS chips might not 
+	 * have a RECLEV. However if they don't common_mixer_set will refuse 
+	 * cause devc->iomap has no register mapping for RECLEV
+	 */
+	if (devc->model == MDL_ESS) ess_mixer_reload (devc, SOUND_MIXER_RECLEV);
+
+	/* The ALS007 seems to require that the DSP be removed from the output */
+	/* in order for recording to be activated properly.  This is done by   */
+	/* setting the appropriate bits of the output control register 4ch to  */
+	/* zero.  This code assumes that the output control registers are not  */
+	/* used anywhere else and therefore the DSP bits are *always* ON for   */
+	/* output and OFF for sampling.                                        */
+
+	if (devc->submodel == SUBMDL_ALS007) 
+	{
+		if (mode & OPEN_READ) 
+			sb_setmixer(devc,ALS007_OUTPUT_CTRL2,
+				sb_getmixer(devc,ALS007_OUTPUT_CTRL2) & 0xf9);
+		else
+			sb_setmixer(devc,ALS007_OUTPUT_CTRL2,
+				sb_getmixer(devc,ALS007_OUTPUT_CTRL2) | 0x06);
+	}
+	return 0;
+}
+
+void sb_audio_close(int dev)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	/* fix things if mmap turned off fullduplex */
+	if(devc->duplex
+	   && !devc->fullduplex
+	   && (devc->opened & OPEN_READ) && (devc->opened & OPEN_WRITE))
+	{
+		struct dma_buffparms *dmap_temp;
+		dmap_temp = audio_devs[dev]->dmap_out;
+		audio_devs[dev]->dmap_out = audio_devs[dev]->dmap_in;
+		audio_devs[dev]->dmap_in = dmap_temp;
+	}
+	audio_devs[dev]->dmap_out->dma = devc->dma8;
+	audio_devs[dev]->dmap_in->dma = ( devc->duplex ) ?
+		devc->dma16 : devc->dma8;
+
+	if (devc->dma16 != -1 && devc->dma16 != devc->dma8 && !devc->duplex)
+		sound_close_dma(devc->dma16);
+
+	/* For ALS007, turn DSP output back on if closing the device for read */
+	
+	if ((devc->submodel == SUBMDL_ALS007) && (devc->opened & OPEN_READ)) 
+	{
+		sb_setmixer(devc,ALS007_OUTPUT_CTRL2,
+			sb_getmixer(devc,ALS007_OUTPUT_CTRL2) | 0x06);
+	}
+	devc->opened = 0;
+}
+
+static void sb_set_output_parms(int dev, unsigned long buf, int nr_bytes,
+		    int intrflag)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (!devc->fullduplex || devc->bits == AFMT_S16_LE)
+	{
+		devc->trg_buf = buf;
+		devc->trg_bytes = nr_bytes;
+		devc->trg_intrflag = intrflag;
+		devc->irq_mode = IMODE_OUTPUT;
+	}
+	else
+	{
+		devc->trg_buf_16 = buf;
+		devc->trg_bytes_16 = nr_bytes;
+		devc->trg_intrflag_16 = intrflag;
+		devc->irq_mode_16 = IMODE_OUTPUT;
+	}
+}
+
+static void sb_set_input_parms(int dev, unsigned long buf, int count, int intrflag)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (!devc->fullduplex || devc->bits != AFMT_S16_LE)
+	{
+		devc->trg_buf = buf;
+		devc->trg_bytes = count;
+		devc->trg_intrflag = intrflag;
+		devc->irq_mode = IMODE_INPUT;
+	}
+	else
+	{
+		devc->trg_buf_16 = buf;
+		devc->trg_bytes_16 = count;
+		devc->trg_intrflag_16 = intrflag;
+		devc->irq_mode_16 = IMODE_INPUT;
+	}
+}
+
+/*
+ * SB1.x compatible routines 
+ */
+
+static void sb1_audio_output_block(int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+	unsigned long flags;
+	int count = nr_bytes;
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	/* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */
+
+	if (audio_devs[dev]->dmap_out->dma > 3)
+		count >>= 1;
+	count--;
+
+	devc->irq_mode = IMODE_OUTPUT;
+
+	spin_lock_irqsave(&devc->lock, flags);
+	if (sb_dsp_command(devc, 0x14))		/* 8 bit DAC using DMA */
+	{
+		sb_dsp_command(devc, (unsigned char) (count & 0xff));
+		sb_dsp_command(devc, (unsigned char) ((count >> 8) & 0xff));
+	}
+	else
+		printk(KERN_WARNING "Sound Blaster:  unable to start DAC.\n");
+	spin_unlock_irqrestore(&devc->lock, flags);
+	devc->intr_active = 1;
+}
+
+static void sb1_audio_start_input(int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+	unsigned long flags;
+	int count = nr_bytes;
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	/*
+	 * Start a DMA input to the buffer pointed by dmaqtail
+	 */
+
+	/* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */
+
+	if (audio_devs[dev]->dmap_out->dma > 3)
+		count >>= 1;
+	count--;
+
+	devc->irq_mode = IMODE_INPUT;
+
+	spin_lock_irqsave(&devc->lock, flags);
+	if (sb_dsp_command(devc, 0x24))		/* 8 bit ADC using DMA */
+	{
+		sb_dsp_command(devc, (unsigned char) (count & 0xff));
+		sb_dsp_command(devc, (unsigned char) ((count >> 8) & 0xff));
+	}
+	else
+		printk(KERN_ERR "Sound Blaster:  unable to start ADC.\n");
+	spin_unlock_irqrestore(&devc->lock, flags);
+
+	devc->intr_active = 1;
+}
+
+static void sb1_audio_trigger(int dev, int bits)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	bits &= devc->irq_mode;
+
+	if (!bits)
+		sb_dsp_command(devc, 0xd0);	/* Halt DMA */
+	else
+	{
+		switch (devc->irq_mode)
+		{
+			case IMODE_INPUT:
+				sb1_audio_start_input(dev, devc->trg_buf, devc->trg_bytes,
+						devc->trg_intrflag);
+				break;
+
+			case IMODE_OUTPUT:
+				sb1_audio_output_block(dev, devc->trg_buf, devc->trg_bytes,
+						devc->trg_intrflag);
+				break;
+		}
+	}
+	devc->trigger_bits = bits;
+}
+
+static int sb1_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+	unsigned long flags;
+
+	spin_lock_irqsave(&devc->lock, flags);
+	if (sb_dsp_command(devc, 0x40))
+		sb_dsp_command(devc, devc->tconst);
+	sb_dsp_command(devc, DSP_CMD_SPKOFF);
+	spin_unlock_irqrestore(&devc->lock, flags);
+
+	devc->trigger_bits = 0;
+	return 0;
+}
+
+static int sb1_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+	unsigned long flags;
+
+	spin_lock_irqsave(&devc->lock, flags);
+	if (sb_dsp_command(devc, 0x40))
+		sb_dsp_command(devc, devc->tconst);
+	sb_dsp_command(devc, DSP_CMD_SPKON);
+	spin_unlock_irqrestore(&devc->lock, flags);
+	devc->trigger_bits = 0;
+	return 0;
+}
+
+static int sb1_audio_set_speed(int dev, int speed)
+{
+	int max_speed = 23000;
+	sb_devc *devc = audio_devs[dev]->devc;
+	int tmp;
+
+	if (devc->opened & OPEN_READ)
+		max_speed = 13000;
+
+	if (speed > 0)
+	{
+		if (speed < 4000)
+			speed = 4000;
+
+		if (speed > max_speed)
+			speed = max_speed;
+
+		devc->tconst = (256 - ((1000000 + speed / 2) / speed)) & 0xff;
+		tmp = 256 - devc->tconst;
+		speed = (1000000 + tmp / 2) / tmp;
+
+		devc->speed = speed;
+	}
+	return devc->speed;
+}
+
+static short sb1_audio_set_channels(int dev, short channels)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+	return devc->channels = 1;
+}
+
+static unsigned int sb1_audio_set_bits(int dev, unsigned int bits)
+{
+	sb_devc        *devc = audio_devs[dev]->devc;
+	return devc->bits = 8;
+}
+
+static void sb1_audio_halt_xfer(int dev)
+{
+	unsigned long flags;
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	spin_lock_irqsave(&devc->lock, flags);
+	sb_dsp_reset(devc);
+	spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+/*
+ * SB 2.0 and SB 2.01 compatible routines
+ */
+
+static void sb20_audio_output_block(int dev, unsigned long buf, int nr_bytes,
+			int intrflag)
+{
+	unsigned long flags;
+	int count = nr_bytes;
+	sb_devc *devc = audio_devs[dev]->devc;
+	unsigned char cmd;
+
+	/* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */
+
+	if (audio_devs[dev]->dmap_out->dma > 3)
+		count >>= 1;
+	count--;
+
+	devc->irq_mode = IMODE_OUTPUT;
+
+	spin_lock_irqsave(&devc->lock, flags);
+	if (sb_dsp_command(devc, 0x48))		/* DSP Block size */
+	{
+		sb_dsp_command(devc, (unsigned char) (count & 0xff));
+		sb_dsp_command(devc, (unsigned char) ((count >> 8) & 0xff));
+
+		if (devc->speed * devc->channels <= 23000)
+			cmd = 0x1c;	/* 8 bit PCM output */
+		else
+			cmd = 0x90;	/* 8 bit high speed PCM output (SB2.01/Pro) */
+
+		if (!sb_dsp_command(devc, cmd))
+			printk(KERN_ERR "Sound Blaster:  unable to start DAC.\n");
+	}
+	else
+		printk(KERN_ERR "Sound Blaster: unable to start DAC.\n");
+	spin_unlock_irqrestore(&devc->lock, flags);
+	devc->intr_active = 1;
+}
+
+static void sb20_audio_start_input(int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+	unsigned long flags;
+	int count = nr_bytes;
+	sb_devc *devc = audio_devs[dev]->devc;
+	unsigned char cmd;
+
+	/*
+	 * Start a DMA input to the buffer pointed by dmaqtail
+	 */
+
+	/* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */
+
+	if (audio_devs[dev]->dmap_out->dma > 3)
+		count >>= 1;
+	count--;
+
+	devc->irq_mode = IMODE_INPUT;
+
+	spin_lock_irqsave(&devc->lock, flags);
+	if (sb_dsp_command(devc, 0x48))		/* DSP Block size */
+	{
+		sb_dsp_command(devc, (unsigned char) (count & 0xff));
+		sb_dsp_command(devc, (unsigned char) ((count >> 8) & 0xff));
+
+		if (devc->speed * devc->channels <= (devc->major == 3 ? 23000 : 13000))
+			cmd = 0x2c;	/* 8 bit PCM input */
+		else
+			cmd = 0x98;	/* 8 bit high speed PCM input (SB2.01/Pro) */
+
+		if (!sb_dsp_command(devc, cmd))
+			printk(KERN_ERR "Sound Blaster:  unable to start ADC.\n");
+	}
+	else
+		printk(KERN_ERR "Sound Blaster:  unable to start ADC.\n");
+	spin_unlock_irqrestore(&devc->lock, flags);
+	devc->intr_active = 1;
+}
+
+static void sb20_audio_trigger(int dev, int bits)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+	bits &= devc->irq_mode;
+
+	if (!bits)
+		sb_dsp_command(devc, 0xd0);	/* Halt DMA */
+	else
+	{
+		switch (devc->irq_mode)
+		{
+			case IMODE_INPUT:
+				sb20_audio_start_input(dev, devc->trg_buf, devc->trg_bytes,
+						devc->trg_intrflag);
+				break;
+
+			case IMODE_OUTPUT:
+				sb20_audio_output_block(dev, devc->trg_buf, devc->trg_bytes,
+						devc->trg_intrflag);
+			    break;
+		}
+	}
+	devc->trigger_bits = bits;
+}
+
+/*
+ * SB2.01 specific speed setup
+ */
+
+static int sb201_audio_set_speed(int dev, int speed)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+	int tmp;
+	int s = speed * devc->channels;
+
+	if (speed > 0)
+	{
+		if (speed < 4000)
+			speed = 4000;
+		if (speed > 44100)
+			speed = 44100;
+		if (devc->opened & OPEN_READ && speed > 15000)
+			speed = 15000;
+		devc->tconst = (256 - ((1000000 + s / 2) / s)) & 0xff;
+		tmp = 256 - devc->tconst;
+		speed = ((1000000 + tmp / 2) / tmp) / devc->channels;
+
+		devc->speed = speed;
+	}
+	return devc->speed;
+}
+
+/*
+ * SB Pro specific routines
+ */
+
+static int sbpro_audio_prepare_for_input(int dev, int bsize, int bcount)
+{				/* For SB Pro and Jazz16 */
+	sb_devc *devc = audio_devs[dev]->devc;
+	unsigned long flags;
+	unsigned char bits = 0;
+
+	if (devc->dma16 >= 0 && devc->dma16 != devc->dma8)
+		audio_devs[dev]->dmap_out->dma = audio_devs[dev]->dmap_in->dma =
+			devc->bits == 16 ? devc->dma16 : devc->dma8;
+
+	if (devc->model == MDL_JAZZ || devc->model == MDL_SMW)
+		if (devc->bits == AFMT_S16_LE)
+			bits = 0x04;	/* 16 bit mode */
+
+	spin_lock_irqsave(&devc->lock, flags);
+	if (sb_dsp_command(devc, 0x40))
+		sb_dsp_command(devc, devc->tconst);
+	sb_dsp_command(devc, DSP_CMD_SPKOFF);
+	if (devc->channels == 1)
+		sb_dsp_command(devc, 0xa0 | bits);	/* Mono input */
+	else
+		sb_dsp_command(devc, 0xa8 | bits);	/* Stereo input */
+	spin_unlock_irqrestore(&devc->lock, flags);
+
+	devc->trigger_bits = 0;
+	return 0;
+}
+
+static int sbpro_audio_prepare_for_output(int dev, int bsize, int bcount)
+{				/* For SB Pro and Jazz16 */
+	sb_devc *devc = audio_devs[dev]->devc;
+	unsigned long flags;
+	unsigned char tmp;
+	unsigned char bits = 0;
+
+	if (devc->dma16 >= 0 && devc->dma16 != devc->dma8)
+		audio_devs[dev]->dmap_out->dma = audio_devs[dev]->dmap_in->dma = devc->bits == 16 ? devc->dma16 : devc->dma8;
+	if (devc->model == MDL_SBPRO)
+		sb_mixer_set_stereo(devc, devc->channels == 2);
+
+	spin_lock_irqsave(&devc->lock, flags);
+	if (sb_dsp_command(devc, 0x40))
+		sb_dsp_command(devc, devc->tconst);
+	sb_dsp_command(devc, DSP_CMD_SPKON);
+
+	if (devc->model == MDL_JAZZ || devc->model == MDL_SMW)
+	{
+		if (devc->bits == AFMT_S16_LE)
+			bits = 0x04;	/* 16 bit mode */
+
+		if (devc->channels == 1)
+			sb_dsp_command(devc, 0xa0 | bits);	/* Mono output */
+		else
+			sb_dsp_command(devc, 0xa8 | bits);	/* Stereo output */
+		spin_unlock_irqrestore(&devc->lock, flags);
+	}
+	else
+	{
+		spin_unlock_irqrestore(&devc->lock, flags);
+		tmp = sb_getmixer(devc, 0x0e);
+		if (devc->channels == 1)
+			tmp &= ~0x02;
+		else
+			tmp |= 0x02;
+		sb_setmixer(devc, 0x0e, tmp);
+	}
+	devc->trigger_bits = 0;
+	return 0;
+}
+
+static int sbpro_audio_set_speed(int dev, int speed)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (speed > 0)
+	{
+		if (speed < 4000)
+			speed = 4000;
+		if (speed > 44100)
+			speed = 44100;
+		if (devc->channels > 1 && speed > 22050)
+			speed = 22050;
+		sb201_audio_set_speed(dev, speed);
+	}
+	return devc->speed;
+}
+
+static short sbpro_audio_set_channels(int dev, short channels)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (channels == 1 || channels == 2)
+	{
+		if (channels != devc->channels)
+		{
+			devc->channels = channels;
+			if (devc->model == MDL_SBPRO && devc->channels == 2)
+				sbpro_audio_set_speed(dev, devc->speed);
+		}
+	}
+	return devc->channels;
+}
+
+static int jazz16_audio_set_speed(int dev, int speed)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (speed > 0)
+	{
+		int tmp;
+		int s = speed * devc->channels;
+
+		if (speed < 5000)
+			speed = 5000;
+		if (speed > 44100)
+			speed = 44100;
+
+		devc->tconst = (256 - ((1000000 + s / 2) / s)) & 0xff;
+
+		tmp = 256 - devc->tconst;
+		speed = ((1000000 + tmp / 2) / tmp) / devc->channels;
+
+		devc->speed = speed;
+	}
+	return devc->speed;
+}
+
+/*
+ * SB16 specific routines
+ */
+
+static int sb16_audio_set_speed(int dev, int speed)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+	int	max_speed = devc->submodel == SUBMDL_ALS100 ? 48000 : 44100;
+
+	if (speed > 0)
+	{
+		if (speed < 5000)
+			speed = 5000;
+
+		if (speed > max_speed)
+			speed = max_speed;
+
+		devc->speed = speed;
+	}
+	return devc->speed;
+}
+
+static unsigned int sb16_audio_set_bits(int dev, unsigned int bits)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (bits != 0)
+	{
+		if (bits == AFMT_U8 || bits == AFMT_S16_LE)
+			devc->bits = bits;
+		else
+			devc->bits = AFMT_U8;
+	}
+
+	return devc->bits;
+}
+
+static int sb16_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (!devc->fullduplex)
+	{
+		audio_devs[dev]->dmap_out->dma =
+			audio_devs[dev]->dmap_in->dma =
+				devc->bits == AFMT_S16_LE ?
+					devc->dma16 : devc->dma8;
+	}
+	else if (devc->bits == AFMT_S16_LE)
+	{
+		audio_devs[dev]->dmap_out->dma = devc->dma8;
+		audio_devs[dev]->dmap_in->dma = devc->dma16;
+	}
+	else
+	{
+		audio_devs[dev]->dmap_out->dma = devc->dma16;
+		audio_devs[dev]->dmap_in->dma = devc->dma8;
+	}
+
+	devc->trigger_bits = 0;
+	return 0;
+}
+
+static int sb16_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (!devc->fullduplex)
+	{
+		audio_devs[dev]->dmap_out->dma =
+			audio_devs[dev]->dmap_in->dma =
+				devc->bits == AFMT_S16_LE ?
+					devc->dma16 : devc->dma8;
+	}
+	else if (devc->bits == AFMT_S16_LE)
+	{
+		audio_devs[dev]->dmap_out->dma = devc->dma8;
+		audio_devs[dev]->dmap_in->dma = devc->dma16;
+	}
+	else
+	{
+		audio_devs[dev]->dmap_out->dma = devc->dma16;
+		audio_devs[dev]->dmap_in->dma = devc->dma8;
+	}
+
+	devc->trigger_bits = 0;
+	return 0;
+}
+
+static void sb16_audio_output_block(int dev, unsigned long buf, int count,
+			int intrflag)
+{
+	unsigned long   flags, cnt;
+	sb_devc        *devc = audio_devs[dev]->devc;
+	unsigned long   bits;
+
+	if (!devc->fullduplex || devc->bits == AFMT_S16_LE)
+	{
+		devc->irq_mode = IMODE_OUTPUT;
+		devc->intr_active = 1;
+	}
+	else
+	{
+		devc->irq_mode_16 = IMODE_OUTPUT;
+		devc->intr_active_16 = 1;
+	}
+
+	/* save value */
+	spin_lock_irqsave(&devc->lock, flags);
+	bits = devc->bits;
+	if (devc->fullduplex)
+		devc->bits = (devc->bits == AFMT_S16_LE) ?
+			AFMT_U8 : AFMT_S16_LE;
+	spin_unlock_irqrestore(&devc->lock, flags);
+
+	cnt = count;
+	if (devc->bits == AFMT_S16_LE)
+		cnt >>= 1;
+	cnt--;
+
+	spin_lock_irqsave(&devc->lock, flags);
+
+	/* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */
+
+	sb_dsp_command(devc, 0x41);
+	sb_dsp_command(devc, (unsigned char) ((devc->speed >> 8) & 0xff));
+	sb_dsp_command(devc, (unsigned char) (devc->speed & 0xff));
+
+	sb_dsp_command(devc, (devc->bits == AFMT_S16_LE ? 0xb6 : 0xc6));
+	sb_dsp_command(devc, ((devc->channels == 2 ? 0x20 : 0) +
+			      (devc->bits == AFMT_S16_LE ? 0x10 : 0)));
+	sb_dsp_command(devc, (unsigned char) (cnt & 0xff));
+	sb_dsp_command(devc, (unsigned char) (cnt >> 8));
+
+	/* restore real value after all programming */
+	devc->bits = bits;
+	spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+
+/*
+ *	This fails on the Cyrix MediaGX. If you don't have the DMA enabled
+ *	before the first sample arrives it locks up. However even if you
+ *	do enable the DMA in time you just get DMA timeouts and missing
+ *	interrupts and stuff, so for now I've not bothered fixing this either.
+ */
+ 
+static void sb16_audio_start_input(int dev, unsigned long buf, int count, int intrflag)
+{
+	unsigned long   flags, cnt;
+	sb_devc        *devc = audio_devs[dev]->devc;
+
+	if (!devc->fullduplex || devc->bits != AFMT_S16_LE)
+	{
+		devc->irq_mode = IMODE_INPUT;
+		devc->intr_active = 1;
+	}
+	else
+	{
+		devc->irq_mode_16 = IMODE_INPUT;
+		devc->intr_active_16 = 1;
+	}
+
+	cnt = count;
+	if (devc->bits == AFMT_S16_LE)
+		cnt >>= 1;
+	cnt--;
+
+	spin_lock_irqsave(&devc->lock, flags);
+
+	/* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */
+
+	sb_dsp_command(devc, 0x42);
+	sb_dsp_command(devc, (unsigned char) ((devc->speed >> 8) & 0xff));
+	sb_dsp_command(devc, (unsigned char) (devc->speed & 0xff));
+
+	sb_dsp_command(devc, (devc->bits == AFMT_S16_LE ? 0xbe : 0xce));
+	sb_dsp_command(devc, ((devc->channels == 2 ? 0x20 : 0) +
+			      (devc->bits == AFMT_S16_LE ? 0x10 : 0)));
+	sb_dsp_command(devc, (unsigned char) (cnt & 0xff));
+	sb_dsp_command(devc, (unsigned char) (cnt >> 8));
+
+	spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+static void sb16_audio_trigger(int dev, int bits)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	int bits_16 = bits & devc->irq_mode_16;
+	bits &= devc->irq_mode;
+
+	if (!bits && !bits_16)
+		sb_dsp_command(devc, 0xd0);	/* Halt DMA */
+	else
+	{
+		if (bits)
+		{
+			switch (devc->irq_mode)
+			{
+				case IMODE_INPUT:
+					sb16_audio_start_input(dev,
+							devc->trg_buf,
+							devc->trg_bytes,
+							devc->trg_intrflag);
+					break;
+
+				case IMODE_OUTPUT:
+					sb16_audio_output_block(dev,
+							devc->trg_buf,
+							devc->trg_bytes,
+							devc->trg_intrflag);
+					break;
+			}
+		}
+		if (bits_16)
+		{
+			switch (devc->irq_mode_16)
+			{
+				case IMODE_INPUT:
+					sb16_audio_start_input(dev,
+							devc->trg_buf_16,
+							devc->trg_bytes_16,
+							devc->trg_intrflag_16);
+					break;
+
+				case IMODE_OUTPUT:
+					sb16_audio_output_block(dev,
+							devc->trg_buf_16,
+							devc->trg_bytes_16,
+							devc->trg_intrflag_16);
+					break;
+			}
+		}
+	}
+
+	devc->trigger_bits = bits | bits_16;
+}
+
+static unsigned char lbuf8[2048];
+static signed short *lbuf16 = (signed short *)lbuf8;
+#define LBUFCOPYSIZE 1024
+static void
+sb16_copy_from_user(int dev,
+		char *localbuf, int localoffs,
+		const char __user *userbuf, int useroffs,
+		int max_in, int max_out,
+		int *used, int *returned,
+		int len)
+{
+	sb_devc       *devc = audio_devs[dev]->devc;
+	int           i, c, p, locallen;
+	unsigned char *buf8;
+	signed short  *buf16;
+
+	/* if not duplex no conversion */
+	if (!devc->fullduplex)
+	{
+		if (copy_from_user(localbuf + localoffs,
+				   userbuf + useroffs, len))
+			return;
+		*used = len;
+		*returned = len;
+	}
+	else if (devc->bits == AFMT_S16_LE)
+	{
+		/* 16 -> 8 */
+		/* max_in >> 1, max number of samples in ( 16 bits ) */
+		/* max_out, max number of samples out ( 8 bits ) */
+		/* len, number of samples that will be taken ( 16 bits )*/
+		/* c, count of samples remaining in buffer ( 16 bits )*/
+		/* p, count of samples already processed ( 16 bits )*/
+		len = ( (max_in >> 1) > max_out) ? max_out : (max_in >> 1);
+		c = len;
+		p = 0;
+		buf8 = (unsigned char *)(localbuf + localoffs);
+		while (c)
+		{
+			locallen = (c >= LBUFCOPYSIZE ? LBUFCOPYSIZE : c);
+			/* << 1 in order to get 16 bit samples */
+			if (copy_from_user(lbuf16,
+					   userbuf + useroffs + (p << 1),
+					   locallen << 1))
+				return;
+			for (i = 0; i < locallen; i++)
+			{
+				buf8[p+i] = ~((lbuf16[i] >> 8) & 0xff) ^ 0x80;
+			}
+			c -= locallen; p += locallen;
+		}
+		/* used = ( samples * 16 bits size ) */
+		*used =  max_in  > ( max_out << 1) ? (max_out << 1) : max_in;
+		/* returned = ( samples * 8 bits size ) */
+		*returned = len;
+	}
+	else
+	{
+		/* 8 -> 16 */
+		/* max_in, max number of samples in ( 8 bits ) */
+		/* max_out >> 1, max number of samples out ( 16 bits ) */
+		/* len, number of samples that will be taken ( 8 bits )*/
+		/* c, count of samples remaining in buffer ( 8 bits )*/
+		/* p, count of samples already processed ( 8 bits )*/
+		len = max_in > (max_out >> 1) ? (max_out >> 1) : max_in;
+		c = len;
+		p = 0;
+		buf16 = (signed short *)(localbuf + localoffs);
+		while (c)
+		{
+			locallen = (c >= LBUFCOPYSIZE ? LBUFCOPYSIZE : c);
+			if (copy_from_user(lbuf8,
+					   userbuf+useroffs + p,
+					   locallen))
+				return;
+			for (i = 0; i < locallen; i++)
+			{
+				buf16[p+i] = (~lbuf8[i] ^ 0x80) << 8;
+			}
+	      		c -= locallen; p += locallen;
+		}
+		/* used = ( samples * 8 bits size ) */
+		*used = len;
+		/* returned = ( samples * 16 bits size ) */
+		*returned = len << 1;
+	}
+}
+
+static void
+sb16_audio_mmap(int dev)
+{
+	sb_devc       *devc = audio_devs[dev]->devc;
+	devc->fullduplex = 0;
+}
+
+static struct audio_driver sb1_audio_driver =	/* SB1.x */
+{
+	.owner			= THIS_MODULE,
+	.open			= sb_audio_open,
+	.close			= sb_audio_close,
+	.output_block		= sb_set_output_parms,
+	.start_input		= sb_set_input_parms,
+	.prepare_for_input	= sb1_audio_prepare_for_input,
+	.prepare_for_output	= sb1_audio_prepare_for_output,
+	.halt_io		= sb1_audio_halt_xfer,
+	.trigger		= sb1_audio_trigger,
+	.set_speed		= sb1_audio_set_speed,
+	.set_bits		= sb1_audio_set_bits,
+	.set_channels		= sb1_audio_set_channels
+};
+
+static struct audio_driver sb20_audio_driver =	/* SB2.0 */
+{
+	.owner			= THIS_MODULE,
+	.open			= sb_audio_open,
+	.close			= sb_audio_close,
+	.output_block		= sb_set_output_parms,
+	.start_input		= sb_set_input_parms,
+	.prepare_for_input	= sb1_audio_prepare_for_input,
+	.prepare_for_output	= sb1_audio_prepare_for_output,
+	.halt_io		= sb1_audio_halt_xfer,
+	.trigger		= sb20_audio_trigger,
+	.set_speed		= sb1_audio_set_speed,
+	.set_bits		= sb1_audio_set_bits,
+	.set_channels		= sb1_audio_set_channels
+};
+
+static struct audio_driver sb201_audio_driver =		/* SB2.01 */
+{
+	.owner			= THIS_MODULE,
+	.open			= sb_audio_open,
+	.close			= sb_audio_close,
+	.output_block		= sb_set_output_parms,
+	.start_input		= sb_set_input_parms,
+	.prepare_for_input	= sb1_audio_prepare_for_input,
+	.prepare_for_output	= sb1_audio_prepare_for_output,
+	.halt_io		= sb1_audio_halt_xfer,
+	.trigger		= sb20_audio_trigger,
+	.set_speed		= sb201_audio_set_speed,
+	.set_bits		= sb1_audio_set_bits,
+	.set_channels		= sb1_audio_set_channels
+};
+
+static struct audio_driver sbpro_audio_driver =		/* SB Pro */
+{
+	.owner			= THIS_MODULE,
+	.open			= sb_audio_open,
+	.close			= sb_audio_close,
+	.output_block		= sb_set_output_parms,
+	.start_input		= sb_set_input_parms,
+	.prepare_for_input	= sbpro_audio_prepare_for_input,
+	.prepare_for_output	= sbpro_audio_prepare_for_output,
+	.halt_io		= sb1_audio_halt_xfer,
+	.trigger		= sb20_audio_trigger,
+	.set_speed		= sbpro_audio_set_speed,
+	.set_bits		= sb1_audio_set_bits,
+	.set_channels		= sbpro_audio_set_channels
+};
+
+static struct audio_driver jazz16_audio_driver =	/* Jazz16 and SM Wave */
+{
+	.owner			= THIS_MODULE,
+	.open			= sb_audio_open,
+	.close			= sb_audio_close,
+	.output_block		= sb_set_output_parms,
+	.start_input		= sb_set_input_parms,
+	.prepare_for_input	= sbpro_audio_prepare_for_input,
+	.prepare_for_output	= sbpro_audio_prepare_for_output,
+	.halt_io		= sb1_audio_halt_xfer,
+	.trigger		= sb20_audio_trigger,
+	.set_speed		= jazz16_audio_set_speed,
+	.set_bits		= sb16_audio_set_bits,
+	.set_channels		= sbpro_audio_set_channels
+};
+
+static struct audio_driver sb16_audio_driver =	/* SB16 */
+{
+	.owner			= THIS_MODULE,
+	.open			= sb_audio_open,
+	.close			= sb_audio_close,
+	.output_block		= sb_set_output_parms,
+	.start_input		= sb_set_input_parms,
+	.prepare_for_input	= sb16_audio_prepare_for_input,
+	.prepare_for_output	= sb16_audio_prepare_for_output,
+	.halt_io		= sb1_audio_halt_xfer,
+	.copy_user		= sb16_copy_from_user,
+	.trigger		= sb16_audio_trigger,
+	.set_speed		= sb16_audio_set_speed,
+	.set_bits		= sb16_audio_set_bits,
+	.set_channels		= sbpro_audio_set_channels,
+	.mmap			= sb16_audio_mmap
+};
+
+void sb_audio_init(sb_devc * devc, char *name, struct module *owner)
+{
+	int audio_flags = 0;
+	int format_mask = AFMT_U8;
+
+	struct audio_driver *driver = &sb1_audio_driver;
+
+	switch (devc->model)
+	{
+		case MDL_SB1:	/* SB1.0 or SB 1.5 */
+			DDB(printk("Will use standard SB1.x driver\n"));
+			audio_flags = DMA_HARDSTOP;
+			break;
+
+		case MDL_SB2:
+			DDB(printk("Will use SB2.0 driver\n"));
+			audio_flags = DMA_AUTOMODE;
+			driver = &sb20_audio_driver;
+			break;
+
+		case MDL_SB201:
+			DDB(printk("Will use SB2.01 (high speed) driver\n"));
+			audio_flags = DMA_AUTOMODE;
+			driver = &sb201_audio_driver;
+			break;
+
+		case MDL_JAZZ:
+		case MDL_SMW:
+			DDB(printk("Will use Jazz16 driver\n"));
+			audio_flags = DMA_AUTOMODE;
+			format_mask |= AFMT_S16_LE;
+			driver = &jazz16_audio_driver;
+			break;
+
+		case MDL_ESS:
+			DDB(printk("Will use ESS ES688/1688 driver\n"));
+			driver = ess_audio_init (devc, &audio_flags, &format_mask);
+			break;
+
+		case MDL_SB16:
+			DDB(printk("Will use SB16 driver\n"));
+			audio_flags = DMA_AUTOMODE;
+			format_mask |= AFMT_S16_LE;
+			if (devc->dma8 != devc->dma16 && devc->dma16 != -1)
+			{
+				audio_flags |= DMA_DUPLEX;
+				devc->duplex = 1;
+			}
+			driver = &sb16_audio_driver;
+			break;
+
+		default:
+			DDB(printk("Will use SB Pro driver\n"));
+			audio_flags = DMA_AUTOMODE;
+			driver = &sbpro_audio_driver;
+	}
+
+	if (owner)
+			driver->owner = owner;
+	
+	if ((devc->dev = sound_install_audiodrv(AUDIO_DRIVER_VERSION,
+				name,driver, sizeof(struct audio_driver),
+				audio_flags, format_mask, devc,
+				devc->dma8,
+				devc->duplex ? devc->dma16 : devc->dma8)) < 0)
+	{
+		  printk(KERN_ERR "Sound Blaster:  unable to install audio.\n");
+		  return;
+	}
+	audio_devs[devc->dev]->mixer_dev = devc->my_mixerdev;
+	audio_devs[devc->dev]->min_fragment = 5;
+}
diff --git a/linux/sound/oss/sb_card.c b/linux/sound/oss/sb_card.c
new file mode 100644
index 0000000..84ef4d0
--- /dev/null
+++ b/linux/sound/oss/sb_card.c
@@ -0,0 +1,354 @@
+/*
+ * sound/oss/sb_card.c
+ *
+ * Detection routine for the ISA Sound Blaster and compatable sound
+ * cards.
+ *
+ * This file is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this
+ * software for more info.
+ *
+ * This is a complete rewrite of the detection routines. This was
+ * prompted by the PnP API change during v2.5 and the ugly state the
+ * code was in.
+ *
+ * Copyright (C) by Paul Laufer 2002. Based on code originally by
+ * Hannu Savolainen which was modified by many others over the
+ * years. Authors specifically mentioned in the previous version were:
+ * Daniel Stone, Alessandro Zummo, Jeff Garzik, Arnaldo Carvalho de
+ * Melo, Daniel Church, and myself.
+ *
+ * 02-05-2003 Original Release, Paul Laufer <paul@laufernet.com>
+ * 02-07-2003 Bug made it into first release. Take two.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include "sound_config.h"
+#include "sb_mixer.h"
+#include "sb.h"
+#ifdef CONFIG_PNP
+#include <linux/pnp.h>
+#endif /* CONFIG_PNP */
+#include "sb_card.h"
+
+MODULE_DESCRIPTION("OSS Soundblaster ISA PnP and legacy sound driver");
+MODULE_LICENSE("GPL");
+
+extern void *smw_free;
+
+static int __initdata mpu_io	= 0;
+static int __initdata io	= -1;
+static int __initdata irq	= -1;
+static int __initdata dma	= -1;
+static int __initdata dma16	= -1;
+static int __initdata type	= 0; /* Can set this to a specific card type */
+static int __initdata esstype   = 0; /* ESS chip type */
+static int __initdata acer 	= 0; /* Do acer notebook init? */
+static int __initdata sm_games 	= 0; /* Logitech soundman games? */
+
+static struct sb_card_config *legacy = NULL;
+
+#ifdef CONFIG_PNP
+static int pnp_registered;
+static int __initdata pnp       = 1;
+/*
+static int __initdata uart401	= 0;
+*/
+#else
+static int __initdata pnp       = 0;
+#endif
+
+module_param(io, int, 000);
+MODULE_PARM_DESC(io,       "Soundblaster i/o base address (0x220,0x240,0x260,0x280)");
+module_param(irq, int, 000);
+MODULE_PARM_DESC(irq,	   "IRQ (5,7,9,10)");
+module_param(dma, int, 000);
+MODULE_PARM_DESC(dma,	   "8-bit DMA channel (0,1,3)");
+module_param(dma16, int, 000);
+MODULE_PARM_DESC(dma16,	   "16-bit DMA channel (5,6,7)");
+module_param(mpu_io, int, 000);
+MODULE_PARM_DESC(mpu_io,   "MPU base address");
+module_param(type, int, 000);
+MODULE_PARM_DESC(type,	   "You can set this to specific card type (doesn't " \
+		 "work with pnp)");
+module_param(sm_games, int, 000);
+MODULE_PARM_DESC(sm_games, "Enable support for Logitech soundman games " \
+		 "(doesn't work with pnp)");
+module_param(esstype, int, 000);
+MODULE_PARM_DESC(esstype,  "ESS chip type (doesn't work with pnp)");
+module_param(acer, int, 000);
+MODULE_PARM_DESC(acer,	   "Set this to detect cards in some ACER notebooks "\
+		 "(doesn't work with pnp)");
+
+#ifdef CONFIG_PNP
+module_param(pnp, int, 000);
+MODULE_PARM_DESC(pnp,     "Went set to 0 will disable detection using PnP. "\
+		  "Default is 1.\n");
+/* Not done yet.... */
+/*
+module_param(uart401, int, 000);
+MODULE_PARM_DESC(uart401,  "When set to 1, will attempt to detect and enable"\
+		 "the mpu on some clones");
+*/
+#endif /* CONFIG_PNP */
+
+/* OSS subsystem card registration shared by PnP and legacy routines */
+static int sb_register_oss(struct sb_card_config *scc, struct sb_module_options *sbmo)
+{
+	if (!request_region(scc->conf.io_base, 16, "soundblaster")) {
+		printk(KERN_ERR "sb: ports busy.\n");
+		kfree(scc);
+		return -EBUSY;
+	}
+
+	if (!sb_dsp_detect(&scc->conf, 0, 0, sbmo)) {
+		release_region(scc->conf.io_base, 16);
+		printk(KERN_ERR "sb: Failed DSP Detect.\n");
+		kfree(scc);
+		return -ENODEV;
+	}
+	if(!sb_dsp_init(&scc->conf, THIS_MODULE)) {
+		printk(KERN_ERR "sb: Failed DSP init.\n");
+		kfree(scc);
+		return -ENODEV;
+	}
+	if(scc->mpucnf.io_base > 0) {
+		scc->mpu = 1;
+		printk(KERN_INFO "sb: Turning on MPU\n");
+		if(!probe_sbmpu(&scc->mpucnf, THIS_MODULE))
+			scc->mpu = 0;
+	}
+
+	return 1;
+}
+
+static void sb_unload(struct sb_card_config *scc)
+{
+	sb_dsp_unload(&scc->conf, 0);
+	if(scc->mpu)
+		unload_sbmpu(&scc->mpucnf);
+	kfree(scc);
+}
+
+/* Register legacy card with OSS subsystem */
+static int __init sb_init_legacy(void)
+{
+	struct sb_module_options sbmo = {0};
+
+	if((legacy = kzalloc(sizeof(struct sb_card_config), GFP_KERNEL)) == NULL) {
+		printk(KERN_ERR "sb: Error: Could not allocate memory\n");
+		return -ENOMEM;
+	}
+
+	legacy->conf.io_base      = io;
+	legacy->conf.irq          = irq;
+	legacy->conf.dma          = dma;
+	legacy->conf.dma2         = dma16;
+	legacy->conf.card_subtype = type;
+
+	legacy->mpucnf.io_base = mpu_io;
+	legacy->mpucnf.irq     = -1;
+	legacy->mpucnf.dma     = -1;
+	legacy->mpucnf.dma2    = -1;
+
+	sbmo.esstype  = esstype;
+	sbmo.sm_games = sm_games;
+	sbmo.acer     = acer;
+
+	return sb_register_oss(legacy, &sbmo);
+}
+
+#ifdef CONFIG_PNP
+
+/* Populate the OSS subsystem structures with information from PnP */
+static void sb_dev2cfg(struct pnp_dev *dev, struct sb_card_config *scc)
+{
+	scc->conf.io_base   = -1;
+	scc->conf.irq       = -1;
+	scc->conf.dma       = -1;
+	scc->conf.dma2      = -1;
+	scc->mpucnf.io_base = -1;
+	scc->mpucnf.irq     = -1;
+	scc->mpucnf.dma     = -1;
+	scc->mpucnf.dma2    = -1;
+
+	/* All clones layout their PnP tables differently and some use
+	   different logical devices for the MPU */
+	if(!strncmp("CTL",scc->card_id,3)) {
+		scc->conf.io_base   = pnp_port_start(dev,0);
+		scc->conf.irq       = pnp_irq(dev,0);
+		scc->conf.dma       = pnp_dma(dev,0);
+		scc->conf.dma2      = pnp_dma(dev,1);
+		scc->mpucnf.io_base = pnp_port_start(dev,1);
+		return;
+	}
+	if(!strncmp("tBA",scc->card_id,3)) {
+		scc->conf.io_base   = pnp_port_start(dev,0);
+		scc->conf.irq       = pnp_irq(dev,0);
+		scc->conf.dma       = pnp_dma(dev,0);
+		scc->conf.dma2      = pnp_dma(dev,1);
+		return;
+	}
+	if(!strncmp("ESS",scc->card_id,3)) {
+		scc->conf.io_base   = pnp_port_start(dev,0);
+		scc->conf.irq       = pnp_irq(dev,0);
+		scc->conf.dma       = pnp_dma(dev,0);
+		scc->conf.dma2      = pnp_dma(dev,1);
+	       	scc->mpucnf.io_base = pnp_port_start(dev,2);
+		return;
+	}
+	if(!strncmp("CMI",scc->card_id,3)) {
+		scc->conf.io_base = pnp_port_start(dev,0);
+		scc->conf.irq     = pnp_irq(dev,0);
+		scc->conf.dma     = pnp_dma(dev,0);
+		scc->conf.dma2    = pnp_dma(dev,1);
+		return;
+	}
+	if(!strncmp("RWB",scc->card_id,3)) {
+		scc->conf.io_base = pnp_port_start(dev,0);
+		scc->conf.irq     = pnp_irq(dev,0);
+		scc->conf.dma     = pnp_dma(dev,0);
+		return;
+	}
+	if(!strncmp("ALS",scc->card_id,3)) {
+		if(!strncmp("ALS0007",scc->card_id,7)) {
+			scc->conf.io_base = pnp_port_start(dev,0);
+			scc->conf.irq     = pnp_irq(dev,0);
+			scc->conf.dma     = pnp_dma(dev,0);
+		} else {
+			scc->conf.io_base = pnp_port_start(dev,0);
+			scc->conf.irq     = pnp_irq(dev,0);
+			scc->conf.dma     = pnp_dma(dev,1);
+			scc->conf.dma2    = pnp_dma(dev,0);
+		}
+		return;
+	}
+	if(!strncmp("RTL",scc->card_id,3)) {
+		scc->conf.io_base = pnp_port_start(dev,0);
+		scc->conf.irq     = pnp_irq(dev,0);
+		scc->conf.dma     = pnp_dma(dev,1);
+		scc->conf.dma2    = pnp_dma(dev,0);
+	}
+}
+
+static unsigned int sb_pnp_devices;
+
+/* Probe callback function for the PnP API */
+static int sb_pnp_probe(struct pnp_card_link *card, const struct pnp_card_device_id *card_id)
+{
+	struct sb_card_config *scc;
+	struct sb_module_options sbmo = {0}; /* Default to 0 for PnP */
+	struct pnp_dev *dev = pnp_request_card_device(card, card_id->devs[0].id, NULL);
+	
+	if(!dev){
+		return -EBUSY;
+	}
+
+	if((scc = kzalloc(sizeof(struct sb_card_config), GFP_KERNEL)) == NULL) {
+		printk(KERN_ERR "sb: Error: Could not allocate memory\n");
+		return -ENOMEM;
+	}
+
+	printk(KERN_INFO "sb: PnP: Found Card Named = \"%s\", Card PnP id = " \
+	       "%s, Device PnP id = %s\n", card->card->name, card_id->id,
+	       dev->id->id);
+
+	scc->card_id = card_id->id;
+	scc->dev_id = dev->id->id;
+	sb_dev2cfg(dev, scc);
+
+	printk(KERN_INFO "sb: PnP:      Detected at: io=0x%x, irq=%d, " \
+	       "dma=%d, dma16=%d\n", scc->conf.io_base, scc->conf.irq,
+	       scc->conf.dma, scc->conf.dma2);
+
+	pnp_set_card_drvdata(card, scc);
+	sb_pnp_devices++;
+
+	return sb_register_oss(scc, &sbmo);
+}
+
+static void sb_pnp_remove(struct pnp_card_link *card)
+{
+	struct sb_card_config *scc = pnp_get_card_drvdata(card);
+
+	if(!scc)
+		return;
+
+	printk(KERN_INFO "sb: PnP: Removing %s\n", scc->card_id);
+
+	sb_unload(scc);
+}
+
+static struct pnp_card_driver sb_pnp_driver = {
+	.name          = "OSS SndBlstr", /* 16 character limit */
+	.id_table      = sb_pnp_card_table,
+	.probe         = sb_pnp_probe,
+	.remove        = sb_pnp_remove,
+};
+MODULE_DEVICE_TABLE(pnp_card, sb_pnp_card_table);
+#endif /* CONFIG_PNP */
+
+static void sb_unregister_all(void)
+{
+#ifdef CONFIG_PNP
+	if (pnp_registered)
+		pnp_unregister_card_driver(&sb_pnp_driver);
+#endif
+}
+
+static int __init sb_init(void)
+{
+	int lres = 0;
+	int pres = 0;
+
+	printk(KERN_INFO "sb: Init: Starting Probe...\n");
+
+	if(io != -1 && irq != -1 && dma != -1) {
+		printk(KERN_INFO "sb: Probing legacy card with io=%x, "\
+		       "irq=%d, dma=%d, dma16=%d\n",io, irq, dma, dma16);
+		lres = sb_init_legacy();
+	} else if((io != -1 || irq != -1 || dma != -1) ||
+		  (!pnp && (io == -1 && irq == -1 && dma == -1)))
+		printk(KERN_ERR "sb: Error: At least io, irq, and dma "\
+		       "must be set for legacy cards.\n");
+
+#ifdef CONFIG_PNP
+	if(pnp) {
+		int err = pnp_register_card_driver(&sb_pnp_driver);
+		if (!err)
+			pnp_registered = 1;
+		pres = sb_pnp_devices;
+	}
+#endif
+	printk(KERN_INFO "sb: Init: Done\n");
+
+	/* If either PnP or Legacy registered a card then return
+	 * success */
+	if (pres == 0 && lres <= 0) {
+		sb_unregister_all();
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit sb_exit(void)
+{
+	printk(KERN_INFO "sb: Unloading...\n");
+
+	/* Unload legacy card */
+	if (legacy) {
+		printk (KERN_INFO "sb: Unloading legacy card\n");
+		sb_unload(legacy);
+	}
+
+	sb_unregister_all();
+
+	vfree(smw_free);
+	smw_free = NULL;
+}
+
+module_init(sb_init);
+module_exit(sb_exit);
diff --git a/linux/sound/oss/sb_card.h b/linux/sound/oss/sb_card.h
new file mode 100644
index 0000000..5535cff
--- /dev/null
+++ b/linux/sound/oss/sb_card.h
@@ -0,0 +1,149 @@
+/*
+ * sound/oss/sb_card.h
+ *
+ * This file is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this
+ * software for more info.
+ *
+ * 02-05-2002 Original Release, Paul Laufer <paul@laufernet.com>
+ */
+
+struct sb_card_config {
+	struct address_info conf;
+	struct address_info mpucnf;
+	const  char         *card_id;
+	const  char         *dev_id;
+	int                 mpu;
+};
+
+#ifdef CONFIG_PNP
+
+/*
+ * SoundBlaster PnP tables and structures.
+ */
+
+/* Card PnP ID Table */
+static struct pnp_card_device_id sb_pnp_card_table[] = {
+	/* Sound Blaster 16 */
+	{.id = "CTL0024", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster 16 */
+	{.id = "CTL0025", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster 16 */
+	{.id = "CTL0026", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster 16 */
+	{.id = "CTL0027", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster 16 */
+	{.id = "CTL0028", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster 16 */
+	{.id = "CTL0029", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster 16 */
+	{.id = "CTL002a", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster 16 */
+	{.id = "CTL002b", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster 16 */
+	{.id = "CTL002c", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster 16 */
+	{.id = "CTL00ed", .driver_data = 0, .devs = { {.id="CTL0041"}, } },
+	/* Sound Blaster 16 */
+	{.id = "CTL0086", .driver_data = 0, .devs = { {.id="CTL0041"}, } },
+	/* Sound Blaster Vibra16S */
+	{.id = "CTL0051", .driver_data = 0, .devs = { {.id="CTL0001"}, } },
+	/* Sound Blaster Vibra16C */
+	{.id = "CTL0070", .driver_data = 0, .devs = { {.id="CTL0001"}, } },
+	/* Sound Blaster Vibra16CL */
+	{.id = "CTL0080", .driver_data = 0, .devs = { {.id="CTL0041"}, } },
+	/* Sound Blaster Vibra16CL */
+	{.id = "CTL00F0", .driver_data = 0, .devs = { {.id="CTL0043"}, } },
+	/* Sound Blaster AWE 32 */
+	{.id = "CTL0039", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster AWE 32 */
+	{.id = "CTL0042", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster AWE 32 */
+	{.id = "CTL0043", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster AWE 32 */
+	{.id = "CTL0044", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster AWE 32 */
+	{.id = "CTL0045", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster AWE 32 */
+	{.id = "CTL0046", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster AWE 32 */
+	{.id = "CTL0047", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster AWE 32 */
+	{.id = "CTL0048", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster AWE 32 */
+	{.id = "CTL0054", .driver_data = 0, .devs = { {.id="CTL0031"}, } },
+	/* Sound Blaster AWE 32 */
+	{.id = "CTL009C", .driver_data = 0, .devs = { {.id="CTL0041"}, } },
+	/* Createive SB32 PnP */
+	{.id = "CTL009F", .driver_data = 0, .devs = { {.id="CTL0041"}, } },
+	/* Sound Blaster AWE 64 */
+	{.id = "CTL009D", .driver_data = 0, .devs = { {.id="CTL0042"}, } },
+	/* Sound Blaster AWE 64 Gold */
+	{.id = "CTL009E", .driver_data = 0, .devs = { {.id="CTL0044"}, } },
+	/* Sound Blaster AWE 64 Gold */
+	{.id = "CTL00B2", .driver_data = 0, .devs = { {.id="CTL0044"}, } },
+	/* Sound Blaster AWE 64 */
+	{.id = "CTL00C1", .driver_data = 0, .devs = { {.id="CTL0042"}, } },
+	/* Sound Blaster AWE 64 */
+	{.id = "CTL00C3", .driver_data = 0, .devs = { {.id="CTL0045"}, } },
+	/* Sound Blaster AWE 64 */
+	{.id = "CTL00C5", .driver_data = 0, .devs = { {.id="CTL0045"}, } },
+	/* Sound Blaster AWE 64 */
+	{.id = "CTL00C7", .driver_data = 0, .devs = { {.id="CTL0045"}, } },
+	/* Sound Blaster AWE 64 */
+	{.id = "CTL00E4", .driver_data = 0, .devs = { {.id="CTL0045"}, } },
+	/* Sound Blaster AWE 64 */
+	{.id = "CTL00E9", .driver_data = 0, .devs = { {.id="CTL0045"}, } },
+	/* ESS 1868 */
+	{.id = "ESS0968", .driver_data = 0, .devs = { {.id="ESS0968"}, } },
+	/* ESS 1868 */
+	{.id = "ESS1868", .driver_data = 0, .devs = { {.id="ESS1868"}, } },
+	/* ESS 1868 */
+	{.id = "ESS1868", .driver_data = 0, .devs = { {.id="ESS8611"}, } },
+	/* ESS 1869 PnP AudioDrive */
+	{.id = "ESS0003", .driver_data = 0, .devs = { {.id="ESS1869"}, } },
+	/* ESS 1869 */
+	{.id = "ESS1869", .driver_data = 0, .devs = { {.id="ESS1869"}, } },
+	/* ESS 1878 */
+	{.id = "ESS1878", .driver_data = 0, .devs = { {.id="ESS1878"}, } },
+	/* ESS 1879 */
+	{.id = "ESS1879", .driver_data = 0, .devs = { {.id="ESS1879"}, } },
+	/* CMI 8330 SoundPRO */
+	{.id = "CMI0001", .driver_data = 0, .devs = { {.id="@X@0001"},
+						     {.id="@H@0001"},
+						     {.id="@@@0001"}, } },
+	/* Diamond DT0197H */
+	{.id = "RWR1688", .driver_data = 0, .devs = { {.id="@@@0001"},
+						     {.id="@X@0001"},
+						     {.id="@H@0001"}, } },
+	/* ALS007 */
+	{.id = "ALS0007", .driver_data = 0, .devs = { {.id="@@@0001"},
+						     {.id="@X@0001"},
+						     {.id="@H@0001"}, } },
+	/* ALS100 */
+	{.id = "ALS0001", .driver_data = 0, .devs = { {.id="@@@0001"},
+						     {.id="@X@0001"},
+						     {.id="@H@0001"}, } },
+	/* ALS110 */
+	{.id = "ALS0110", .driver_data = 0, .devs = { {.id="@@@1001"},
+						     {.id="@X@1001"},
+						     {.id="@H@0001"}, } },
+	/* ALS120 */
+	{.id = "ALS0120", .driver_data = 0, .devs = { {.id="@@@2001"},
+						     {.id="@X@2001"},
+						     {.id="@H@0001"}, } },
+	/* ALS200 */
+	{.id = "ALS0200", .driver_data = 0, .devs = { {.id="@@@0020"},
+						     {.id="@X@0030"},
+						     {.id="@H@0001"}, } },
+	/* ALS200 */
+	{.id = "RTL3000", .driver_data = 0, .devs = { {.id="@@@2001"},
+						     {.id="@X@2001"},
+						     {.id="@H@0001"}, } },
+	/* Sound Blaster 16 (Virtual PC 2004) */
+	{.id = "tBA03b0", .driver_data = 0, .devs = { {.id="PNPb003"}, } },
+	/* -end- */
+	{.id = "", }
+};
+
+#endif
diff --git a/linux/sound/oss/sb_common.c b/linux/sound/oss/sb_common.c
new file mode 100644
index 0000000..7d42c54
--- /dev/null
+++ b/linux/sound/oss/sb_common.c
@@ -0,0 +1,1292 @@
+/*
+ * sound/oss/sb_common.c
+ *
+ * Common routines for Sound Blaster compatible cards.
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Daniel J. Rodriksson: Modified sbintr to handle 8 and 16 bit interrupts
+ *                       for full duplex support ( only sb16 by now )
+ * Rolf Fokkens:	 Added (BETA?) support for ES1887 chips.
+ * (fokkensr@vertis.nl)	 Which means: You can adjust the recording levels.
+ *
+ * 2000/01/18 - separated sb_card and sb_common -
+ * Jeff Garzik <jgarzik@pobox.com>
+ *
+ * 2000/09/18 - got rid of attach_uart401
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ *
+ * 2001/01/26 - replaced CLI/STI with spinlocks
+ * Chris Rankin <rankinc@zipworld.com.au>
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+
+#include "sound_config.h"
+#include "sound_firmware.h"
+
+#include "mpu401.h"
+
+#include "sb_mixer.h"
+#include "sb.h"
+#include "sb_ess.h"
+
+/*
+ * global module flag
+ */
+
+int sb_be_quiet;
+
+static sb_devc *detected_devc;	/* For communication from probe to init */
+static sb_devc *last_devc;	/* For MPU401 initialization */
+
+static unsigned char jazz_irq_bits[] = {
+	0, 0, 2, 3, 0, 1, 0, 4, 0, 2, 5, 0, 0, 0, 0, 6
+};
+
+static unsigned char jazz_dma_bits[] = {
+	0, 1, 0, 2, 0, 3, 0, 4
+};
+
+void *smw_free;
+
+/*
+ * Jazz16 chipset specific control variables
+ */
+
+static int jazz16_base;			/* Not detected */
+static unsigned char jazz16_bits;	/* I/O relocation bits */
+static DEFINE_SPINLOCK(jazz16_lock);
+
+/*
+ * Logitech Soundman Wave specific initialization code
+ */
+
+#ifdef SMW_MIDI0001_INCLUDED
+#include "smw-midi0001.h"
+#else
+static unsigned char *smw_ucode;
+static int      smw_ucodeLen;
+
+#endif
+
+static sb_devc *last_sb;		/* Last sb loaded */
+
+int sb_dsp_command(sb_devc * devc, unsigned char val)
+{
+	int i;
+	unsigned long limit;
+
+	limit = jiffies + HZ / 10;	/* Timeout */
+	
+	/*
+	 * Note! the i<500000 is an emergency exit. The sb_dsp_command() is sometimes
+	 * called while interrupts are disabled. This means that the timer is
+	 * disabled also. However the timeout situation is a abnormal condition.
+	 * Normally the DSP should be ready to accept commands after just couple of
+	 * loops.
+	 */
+
+	for (i = 0; i < 500000 && (limit-jiffies)>0; i++)
+	{
+		if ((inb(DSP_STATUS) & 0x80) == 0)
+		{
+			outb((val), DSP_COMMAND);
+			return 1;
+		}
+	}
+	printk(KERN_WARNING "Sound Blaster:  DSP command(%x) timeout.\n", val);
+	return 0;
+}
+
+int sb_dsp_get_byte(sb_devc * devc)
+{
+	int i;
+
+	for (i = 1000; i; i--)
+	{
+		if (inb(DSP_DATA_AVAIL) & 0x80)
+			return inb(DSP_READ);
+	}
+	return 0xffff;
+}
+
+static void sb_intr (sb_devc *devc)
+{
+	int status;
+	unsigned char   src = 0xff;
+
+	if (devc->model == MDL_SB16)
+	{
+		src = sb_getmixer(devc, IRQ_STAT);	/* Interrupt source register */
+
+		if (src & 4)						/* MPU401 interrupt */
+			if(devc->midi_irq_cookie)
+				uart401intr(devc->irq, devc->midi_irq_cookie);
+
+		if (!(src & 3))
+			return;	/* Not a DSP interrupt */
+	}
+	if (devc->intr_active && (!devc->fullduplex || (src & 0x01)))
+	{
+		switch (devc->irq_mode)
+		{
+			case IMODE_OUTPUT:
+				DMAbuf_outputintr(devc->dev, 1);
+				break;
+
+			case IMODE_INPUT:
+				DMAbuf_inputintr(devc->dev);
+				break;
+
+			case IMODE_INIT:
+				break;
+
+			case IMODE_MIDI:
+				sb_midi_interrupt(devc);
+				break;
+
+			default:
+				/* printk(KERN_WARNING "Sound Blaster: Unexpected interrupt\n"); */
+				;
+		}
+	}
+	else if (devc->intr_active_16 && (src & 0x02))
+	{
+		switch (devc->irq_mode_16)
+		{
+			case IMODE_OUTPUT:
+				DMAbuf_outputintr(devc->dev, 1);
+				break;
+
+			case IMODE_INPUT:
+				DMAbuf_inputintr(devc->dev);
+				break;
+
+			case IMODE_INIT:
+				break;
+
+			default:
+				/* printk(KERN_WARNING "Sound Blaster: Unexpected interrupt\n"); */
+				;
+		}
+	}
+	/*
+	 * Acknowledge interrupts 
+	 */
+
+	if (src & 0x01)
+		status = inb(DSP_DATA_AVAIL);
+
+	if (devc->model == MDL_SB16 && src & 0x02)
+		status = inb(DSP_DATA_AVL16);
+}
+
+static void pci_intr(sb_devc *devc)
+{
+	int src = inb(devc->pcibase+0x1A);
+	src&=3;
+	if(src)
+		sb_intr(devc);
+}
+
+static irqreturn_t sbintr(int irq, void *dev_id)
+{
+	sb_devc *devc = dev_id;
+
+	devc->irq_ok = 1;
+
+	switch (devc->model) {
+	case MDL_ESSPCI:
+		pci_intr (devc);
+		break;
+		
+	case MDL_ESS:
+		ess_intr (devc);
+		break;
+	default:
+		sb_intr (devc);
+		break;
+	}
+	return IRQ_HANDLED;
+}
+
+int sb_dsp_reset(sb_devc * devc)
+{
+	int loopc;
+
+	DEB(printk("Entered sb_dsp_reset()\n"));
+
+	if (devc->model == MDL_ESS) return ess_dsp_reset (devc);
+
+	/* This is only for non-ESS chips */
+
+	outb(1, DSP_RESET);
+
+	udelay(10);
+	outb(0, DSP_RESET);
+	udelay(30);
+
+	for (loopc = 0; loopc < 1000 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++);
+
+	if (inb(DSP_READ) != 0xAA)
+	{
+		DDB(printk("sb: No response to RESET\n"));
+		return 0;	/* Sorry */
+	}
+
+	DEB(printk("sb_dsp_reset() OK\n"));
+
+	return 1;
+}
+
+static void dsp_get_vers(sb_devc * devc)
+{
+	int i;
+
+	unsigned long   flags;
+
+	DDB(printk("Entered dsp_get_vers()\n"));
+	spin_lock_irqsave(&devc->lock, flags);
+	devc->major = devc->minor = 0;
+	sb_dsp_command(devc, 0xe1);	/* Get version */
+
+	for (i = 100000; i; i--)
+	{
+		if (inb(DSP_DATA_AVAIL) & 0x80)
+		{
+			if (devc->major == 0)
+				devc->major = inb(DSP_READ);
+			else
+			{
+				devc->minor = inb(DSP_READ);
+				break;
+			}
+		}
+	}
+	spin_unlock_irqrestore(&devc->lock, flags);
+	DDB(printk("DSP version %d.%02d\n", devc->major, devc->minor));
+}
+
+static int sb16_set_dma_hw(sb_devc * devc)
+{
+	int bits;
+
+	if (devc->dma8 != 0 && devc->dma8 != 1 && devc->dma8 != 3)
+	{
+		printk(KERN_ERR "SB16: Invalid 8 bit DMA (%d)\n", devc->dma8);
+		return 0;
+	}
+	bits = (1 << devc->dma8);
+
+	if (devc->dma16 >= 5 && devc->dma16 <= 7)
+		bits |= (1 << devc->dma16);
+
+	sb_setmixer(devc, DMA_NR, bits);
+	return 1;
+}
+
+static void sb16_set_mpu_port(sb_devc * devc, struct address_info *hw_config)
+{
+	/*
+	 * This routine initializes new MIDI port setup register of SB Vibra (CT2502).
+	 */
+	unsigned char   bits = sb_getmixer(devc, 0x84) & ~0x06;
+
+	switch (hw_config->io_base)
+	{
+		case 0x300:
+			sb_setmixer(devc, 0x84, bits | 0x04);
+			break;
+
+		case 0x330:
+			sb_setmixer(devc, 0x84, bits | 0x00);
+			break;
+
+		default:
+			sb_setmixer(devc, 0x84, bits | 0x02);		/* Disable MPU */
+			printk(KERN_ERR "SB16: Invalid MIDI I/O port %x\n", hw_config->io_base);
+	}
+}
+
+static int sb16_set_irq_hw(sb_devc * devc, int level)
+{
+	int ival;
+
+	switch (level)
+	{
+		case 5:
+			ival = 2;
+			break;
+		case 7:
+			ival = 4;
+			break;
+		case 9:
+			ival = 1;
+			break;
+		case 10:
+			ival = 8;
+			break;
+		default:
+			printk(KERN_ERR "SB16: Invalid IRQ%d\n", level);
+			return 0;
+	}
+	sb_setmixer(devc, IRQ_NR, ival);
+	return 1;
+}
+
+static void relocate_Jazz16(sb_devc * devc, struct address_info *hw_config)
+{
+	unsigned char bits = 0;
+	unsigned long flags;
+
+	if (jazz16_base != 0 && jazz16_base != hw_config->io_base)
+		return;
+
+	switch (hw_config->io_base)
+	{
+		case 0x220:
+			bits = 1;
+			break;
+		case 0x240:
+			bits = 2;
+			break;
+		case 0x260:
+			bits = 3;
+			break;
+		default:
+			return;
+	}
+	bits = jazz16_bits = bits << 5;
+	jazz16_base = hw_config->io_base;
+
+	/*
+	 *	Magic wake up sequence by writing to 0x201 (aka Joystick port)
+	 */
+	spin_lock_irqsave(&jazz16_lock, flags);
+	outb((0xAF), 0x201);
+	outb((0x50), 0x201);
+	outb((bits), 0x201);
+	spin_unlock_irqrestore(&jazz16_lock, flags);
+}
+
+static int init_Jazz16(sb_devc * devc, struct address_info *hw_config)
+{
+	char name[100];
+	/*
+	 * First try to check that the card has Jazz16 chip. It identifies itself
+	 * by returning 0x12 as response to DSP command 0xfa.
+	 */
+
+	if (!sb_dsp_command(devc, 0xfa))
+		return 0;
+
+	if (sb_dsp_get_byte(devc) != 0x12)
+		return 0;
+
+	/*
+	 * OK so far. Now configure the IRQ and DMA channel used by the card.
+	 */
+	if (hw_config->irq < 1 || hw_config->irq > 15 || jazz_irq_bits[hw_config->irq] == 0)
+	{
+		printk(KERN_ERR "Jazz16: Invalid interrupt (IRQ%d)\n", hw_config->irq);
+		return 0;
+	}
+	if (hw_config->dma < 0 || hw_config->dma > 3 || jazz_dma_bits[hw_config->dma] == 0)
+	{
+		  printk(KERN_ERR "Jazz16: Invalid 8 bit DMA (DMA%d)\n", hw_config->dma);
+		  return 0;
+	}
+	if (hw_config->dma2 < 0)
+	{
+		printk(KERN_ERR "Jazz16: No 16 bit DMA channel defined\n");
+		return 0;
+	}
+	if (hw_config->dma2 < 5 || hw_config->dma2 > 7 || jazz_dma_bits[hw_config->dma2] == 0)
+	{
+		printk(KERN_ERR "Jazz16: Invalid 16 bit DMA (DMA%d)\n", hw_config->dma2);
+		return 0;
+	}
+	devc->dma16 = hw_config->dma2;
+
+	if (!sb_dsp_command(devc, 0xfb))
+		return 0;
+
+	if (!sb_dsp_command(devc, jazz_dma_bits[hw_config->dma] |
+			(jazz_dma_bits[hw_config->dma2] << 4)))
+		return 0;
+
+	if (!sb_dsp_command(devc, jazz_irq_bits[hw_config->irq]))
+		return 0;
+
+	/*
+	 * Now we have configured a standard Jazz16 device. 
+	 */
+	devc->model = MDL_JAZZ;
+	strcpy(name, "Jazz16");
+
+	hw_config->name = "Jazz16";
+	devc->caps |= SB_NO_MIDI;
+	return 1;
+}
+
+static void relocate_ess1688(sb_devc * devc)
+{
+	unsigned char bits;
+
+	switch (devc->base)
+	{
+		case 0x220:
+			bits = 0x04;
+			break;
+		case 0x230:
+			bits = 0x05;
+			break;
+		case 0x240:
+			bits = 0x06;
+			break;
+		case 0x250:
+			bits = 0x07;
+			break;
+		default:
+			return;	/* Wrong port */
+	}
+
+	DDB(printk("Doing ESS1688 address selection\n"));
+	
+	/*
+	 * ES1688 supports two alternative ways for software address config.
+	 * First try the so called Read-Sequence-Key method.
+	 */
+
+	/* Reset the sequence logic */
+	inb(0x229);
+	inb(0x229);
+	inb(0x229);
+
+	/* Perform the read sequence */
+	inb(0x22b);
+	inb(0x229);
+	inb(0x22b);
+	inb(0x229);
+	inb(0x229);
+	inb(0x22b);
+	inb(0x229);
+
+	/* Select the base address by reading from it. Then probe using the port. */
+	inb(devc->base);
+	if (sb_dsp_reset(devc))	/* Bingo */
+		return;
+
+#if 0				/* This causes system lockups (Nokia 386/25 at least) */
+	/*
+	 * The last resort is the system control register method.
+	 */
+
+	outb((0x00), 0xfb);	/* 0xFB is the unlock register */
+	outb((0x00), 0xe0);	/* Select index 0 */
+	outb((bits), 0xe1);	/* Write the config bits */
+	outb((0x00), 0xf9);	/* 0xFB is the lock register */
+#endif
+}
+
+int sb_dsp_detect(struct address_info *hw_config, int pci, int pciio, struct sb_module_options *sbmo)
+{
+	sb_devc sb_info;
+	sb_devc *devc = &sb_info;
+
+	memset((char *) &sb_info, 0, sizeof(sb_info));	/* Zero everything */
+
+	/* Copy module options in place */
+	if(sbmo) memcpy(&devc->sbmo, sbmo, sizeof(struct sb_module_options));
+
+	sb_info.my_mididev = -1;
+	sb_info.my_mixerdev = -1;
+	sb_info.dev = -1;
+
+	/*
+	 * Initialize variables 
+	 */
+	
+	DDB(printk("sb_dsp_detect(%x) entered\n", hw_config->io_base));
+
+	spin_lock_init(&devc->lock);
+	devc->type = hw_config->card_subtype;
+
+	devc->base = hw_config->io_base;
+	devc->irq = hw_config->irq;
+	devc->dma8 = hw_config->dma;
+
+	devc->dma16 = -1;
+	devc->pcibase = pciio;
+	
+	if(pci == SB_PCI_ESSMAESTRO)
+	{
+		devc->model = MDL_ESSPCI;
+		devc->caps |= SB_PCI_IRQ;
+		hw_config->driver_use_1 |= SB_PCI_IRQ;
+		hw_config->card_subtype	= MDL_ESSPCI;
+	}
+	
+	if(pci == SB_PCI_YAMAHA)
+	{
+		devc->model = MDL_YMPCI;
+		devc->caps |= SB_PCI_IRQ;
+		hw_config->driver_use_1 |= SB_PCI_IRQ;
+		hw_config->card_subtype	= MDL_YMPCI;
+		
+		printk("Yamaha PCI mode.\n");
+	}
+	
+	if (devc->sbmo.acer)
+	{
+		unsigned long flags;
+
+		spin_lock_irqsave(&devc->lock, flags);
+		inb(devc->base + 0x09);
+		inb(devc->base + 0x09);
+		inb(devc->base + 0x09);
+		inb(devc->base + 0x0b);
+		inb(devc->base + 0x09);
+		inb(devc->base + 0x0b);
+		inb(devc->base + 0x09);
+		inb(devc->base + 0x09);
+		inb(devc->base + 0x0b);
+		inb(devc->base + 0x09);
+		inb(devc->base + 0x00);
+		spin_unlock_irqrestore(&devc->lock, flags);
+	}
+	/*
+	 * Detect the device
+	 */
+
+	if (sb_dsp_reset(devc))
+		dsp_get_vers(devc);
+	else
+		devc->major = 0;
+
+	if (devc->type == 0 || devc->type == MDL_JAZZ || devc->type == MDL_SMW)
+		if (devc->major == 0 || (devc->major == 3 && devc->minor == 1))
+			relocate_Jazz16(devc, hw_config);
+
+	if (devc->major == 0 && (devc->type == MDL_ESS || devc->type == 0))
+		relocate_ess1688(devc);
+
+	if (!sb_dsp_reset(devc))
+	{
+		DDB(printk("SB reset failed\n"));
+#ifdef MODULE
+		printk(KERN_INFO "sb: dsp reset failed.\n");
+#endif
+		return 0;
+	}
+	if (devc->major == 0)
+		dsp_get_vers(devc);
+
+	if (devc->major == 3 && devc->minor == 1)
+	{
+		if (devc->type == MDL_AZTECH)		/* SG Washington? */
+		{
+			if (sb_dsp_command(devc, 0x09))
+				if (sb_dsp_command(devc, 0x00))	/* Enter WSS mode */
+				{
+					int i;
+
+					/* Have some delay */
+					for (i = 0; i < 10000; i++)
+						inb(DSP_DATA_AVAIL);
+					devc->caps = SB_NO_AUDIO | SB_NO_MIDI;	/* Mixer only */
+					devc->model = MDL_AZTECH;
+				}
+		}
+	}
+	
+	if(devc->type == MDL_ESSPCI)
+		devc->model = MDL_ESSPCI;
+		
+	if(devc->type == MDL_YMPCI)
+	{
+		printk("YMPCI selected\n");
+		devc->model = MDL_YMPCI;
+	}
+		
+	/*
+	 * Save device information for sb_dsp_init()
+	 */
+
+
+	detected_devc = kmalloc(sizeof(sb_devc), GFP_KERNEL);
+	if (detected_devc == NULL)
+	{
+		printk(KERN_ERR "sb: Can't allocate memory for device information\n");
+		return 0;
+	}
+	memcpy(detected_devc, devc, sizeof(sb_devc));
+	MDB(printk(KERN_INFO "SB %d.%02d detected OK (%x)\n", devc->major, devc->minor, hw_config->io_base));
+	return 1;
+}
+
+int sb_dsp_init(struct address_info *hw_config, struct module *owner)
+{
+	sb_devc *devc;
+	char name[100];
+	extern int sb_be_quiet;
+	int	mixer22, mixer30;
+	
+/*
+ * Check if we had detected a SB device earlier
+ */
+	DDB(printk("sb_dsp_init(%x) entered\n", hw_config->io_base));
+	name[0] = 0;
+
+	if (detected_devc == NULL)
+	{
+		MDB(printk("No detected device\n"));
+		return 0;
+	}
+	devc = detected_devc;
+	detected_devc = NULL;
+
+	if (devc->base != hw_config->io_base)
+	{
+		DDB(printk("I/O port mismatch\n"));
+		release_region(devc->base, 16);
+		return 0;
+	}
+	/*
+	 * Now continue initialization of the device
+	 */
+
+	devc->caps = hw_config->driver_use_1;
+
+	if (!((devc->caps & SB_NO_AUDIO) && (devc->caps & SB_NO_MIDI)) && hw_config->irq > 0)
+	{			/* IRQ setup */
+		
+		/*
+		 *	ESS PCI cards do shared PCI IRQ stuff. Since they
+		 *	will get shared PCI irq lines we must cope.
+		 */
+		 
+		int i=(devc->caps&SB_PCI_IRQ)?IRQF_SHARED:0;
+		
+		if (request_irq(hw_config->irq, sbintr, i, "soundblaster", devc) < 0)
+		{
+			printk(KERN_ERR "SB: Can't allocate IRQ%d\n", hw_config->irq);
+			release_region(devc->base, 16);
+			return 0;
+		}
+		devc->irq_ok = 0;
+
+		if (devc->major == 4)
+			if (!sb16_set_irq_hw(devc, devc->irq))	/* Unsupported IRQ */
+			{
+				free_irq(devc->irq, devc);
+				release_region(devc->base, 16);
+				return 0;
+			}
+		if ((devc->type == 0 || devc->type == MDL_ESS) &&
+			devc->major == 3 && devc->minor == 1)
+		{		/* Handle various chipsets which claim they are SB Pro compatible */
+			if ((devc->type != 0 && devc->type != MDL_ESS) ||
+				!ess_init(devc, hw_config))
+			{
+				if ((devc->type != 0 && devc->type != MDL_JAZZ &&
+					 devc->type != MDL_SMW) || !init_Jazz16(devc, hw_config))
+				{
+					DDB(printk("This is a genuine SB Pro\n"));
+				}
+			}
+		}
+		if (devc->major == 4 && devc->minor <= 11 )	/* Won't work */
+			devc->irq_ok = 1;
+		else
+		{
+			int n;
+
+			for (n = 0; n < 3 && devc->irq_ok == 0; n++)
+			{
+				if (sb_dsp_command(devc, 0xf2))	/* Cause interrupt immediately */
+				{
+					int i;
+
+					for (i = 0; !devc->irq_ok && i < 10000; i++);
+				}
+			}
+			if (!devc->irq_ok)
+				printk(KERN_WARNING "sb: Interrupt test on IRQ%d failed - Probable IRQ conflict\n", devc->irq);
+			else
+			{
+				DDB(printk("IRQ test OK (IRQ%d)\n", devc->irq));
+			}
+		}
+	}			/* IRQ setup */
+
+	last_sb = devc;
+	
+	switch (devc->major)
+	{
+		case 1:		/* SB 1.0 or 1.5 */
+			devc->model = hw_config->card_subtype = MDL_SB1;
+			break;
+
+		case 2:		/* SB 2.x */
+			if (devc->minor == 0)
+				devc->model = hw_config->card_subtype = MDL_SB2;
+			else
+				devc->model = hw_config->card_subtype = MDL_SB201;
+			break;
+
+		case 3:		/* SB Pro and most clones */
+			switch (devc->model) {
+			case 0:
+				devc->model = hw_config->card_subtype = MDL_SBPRO;
+				if (hw_config->name == NULL)
+					hw_config->name = "Sound Blaster Pro (8 BIT ONLY)";
+				break;
+			case MDL_ESS:
+				ess_dsp_init(devc, hw_config);
+				break;
+			}
+			break;
+
+		case 4:
+			devc->model = hw_config->card_subtype = MDL_SB16;
+			/* 
+			 * ALS007 and ALS100 return DSP version 4.2 and have 2 post-reset !=0
+			 * registers at 0x3c and 0x4c (output ctrl registers on ALS007) whereas
+			 * a "standard" SB16 doesn't have a register at 0x4c.  ALS100 actively
+			 * updates register 0x22 whenever 0x30 changes, as per the SB16 spec.
+			 * Since ALS007 doesn't, this can be used to differentiate the 2 cards.
+			 */
+			if ((devc->minor == 2) && sb_getmixer(devc,0x3c) && sb_getmixer(devc,0x4c)) 
+			{
+				mixer30 = sb_getmixer(devc,0x30);
+				sb_setmixer(devc,0x22,(mixer22=sb_getmixer(devc,0x22)) & 0x0f);
+				sb_setmixer(devc,0x30,0xff);
+				/* ALS100 will force 0x30 to 0xf8 like SB16; ALS007 will allow 0xff. */
+				/* Register 0x22 & 0xf0 on ALS100 == 0xf0; on ALS007 it == 0x10.     */
+				if ((sb_getmixer(devc,0x30) != 0xff) || ((sb_getmixer(devc,0x22) & 0xf0) != 0x10)) 
+				{
+					devc->submodel = SUBMDL_ALS100;
+					if (hw_config->name == NULL)
+						hw_config->name = "Sound Blaster 16 (ALS-100)";
+        			}
+        			else
+        			{
+        				sb_setmixer(devc,0x3c,0x1f);    /* Enable all inputs */
+					sb_setmixer(devc,0x4c,0x1f);
+					sb_setmixer(devc,0x22,mixer22); /* Restore 0x22 to original value */
+					devc->submodel = SUBMDL_ALS007;
+					if (hw_config->name == NULL)
+						hw_config->name = "Sound Blaster 16 (ALS-007)";
+				}
+				sb_setmixer(devc,0x30,mixer30);
+			}
+			else if (hw_config->name == NULL)
+				hw_config->name = "Sound Blaster 16";
+
+			if (hw_config->dma2 == -1)
+				devc->dma16 = devc->dma8;
+			else if (hw_config->dma2 < 5 || hw_config->dma2 > 7)
+			{
+				printk(KERN_WARNING  "SB16: Bad or missing 16 bit DMA channel\n");
+				devc->dma16 = devc->dma8;
+			}
+			else
+				devc->dma16 = hw_config->dma2;
+
+			if(!sb16_set_dma_hw(devc)) {
+				free_irq(devc->irq, devc);
+			        release_region(hw_config->io_base, 16);
+				return 0;
+			}
+
+			devc->caps |= SB_NO_MIDI;
+	}
+
+	if (!(devc->caps & SB_NO_MIXER))
+		if (devc->major == 3 || devc->major == 4)
+			sb_mixer_init(devc, owner);
+
+	if (!(devc->caps & SB_NO_MIDI))
+		sb_dsp_midi_init(devc, owner);
+
+	if (hw_config->name == NULL)
+		hw_config->name = "Sound Blaster (8 BIT/MONO ONLY)";
+
+	sprintf(name, "%s (%d.%02d)", hw_config->name, devc->major, devc->minor);
+	conf_printf(name, hw_config);
+
+	/*
+	 * Assuming that a sound card is Sound Blaster (compatible) is the most common
+	 * configuration error and the mother of all problems. Usually sound cards
+	 * emulate SB Pro but in addition they have a 16 bit native mode which should be
+	 * used in Unix. See Readme.cards for more information about configuring OSS/Free
+	 * properly.
+	 */
+	if (devc->model <= MDL_SBPRO)
+	{
+		if (devc->major == 3 && devc->minor != 1)	/* "True" SB Pro should have v3.1 (rare ones may have 3.2). */
+		{
+			printk(KERN_INFO "This sound card may not be fully Sound Blaster Pro compatible.\n");
+			printk(KERN_INFO "In many cases there is another way to configure OSS so that\n");
+			printk(KERN_INFO "it works properly with OSS (for example in 16 bit mode).\n");
+			printk(KERN_INFO "Please ignore this message if you _really_ have a SB Pro.\n");
+		}
+		else if (!sb_be_quiet && devc->model == MDL_SBPRO)
+		{
+			printk(KERN_INFO "SB DSP version is just %d.%02d which means that your card is\n", devc->major, devc->minor);
+			printk(KERN_INFO "several years old (8 bit only device) or alternatively the sound driver\n");
+			printk(KERN_INFO "is incorrectly configured.\n");
+		}
+	}
+	hw_config->card_subtype = devc->model;
+	hw_config->slots[0]=devc->dev;
+	last_devc = devc;	/* For SB MPU detection */
+
+	if (!(devc->caps & SB_NO_AUDIO) && devc->dma8 >= 0)
+	{
+		if (sound_alloc_dma(devc->dma8, "SoundBlaster8"))
+		{
+			printk(KERN_WARNING "Sound Blaster: Can't allocate 8 bit DMA channel %d\n", devc->dma8);
+		}
+		if (devc->dma16 >= 0 && devc->dma16 != devc->dma8)
+		{
+			if (sound_alloc_dma(devc->dma16, "SoundBlaster16"))
+				printk(KERN_WARNING "Sound Blaster:  can't allocate 16 bit DMA channel %d.\n", devc->dma16);
+		}
+		sb_audio_init(devc, name, owner);
+		hw_config->slots[0]=devc->dev;
+	}
+	else
+	{
+		MDB(printk("Sound Blaster:  no audio devices found.\n"));
+	}
+	return 1;
+}
+
+/* if (sbmpu) below we allow mpu401 to manage the midi devs
+   otherwise we have to unload them. (Andrzej Krzysztofowicz) */
+   
+void sb_dsp_unload(struct address_info *hw_config, int sbmpu)
+{
+	sb_devc *devc;
+
+	devc = audio_devs[hw_config->slots[0]]->devc;
+
+	if (devc && devc->base == hw_config->io_base)
+	{
+		if ((devc->model & MDL_ESS) && devc->pcibase)
+			release_region(devc->pcibase, 8);
+
+		release_region(devc->base, 16);
+
+		if (!(devc->caps & SB_NO_AUDIO))
+		{
+			sound_free_dma(devc->dma8);
+			if (devc->dma16 >= 0)
+				sound_free_dma(devc->dma16);
+		}
+		if (!(devc->caps & SB_NO_AUDIO && devc->caps & SB_NO_MIDI))
+		{
+			if (devc->irq > 0)
+				free_irq(devc->irq, devc);
+
+			sb_mixer_unload(devc);
+			/* We don't have to do this bit any more the UART401 is its own
+				master  -- Krzysztof Halasa */
+			/* But we have to do it, if UART401 is not detected */
+			if (!sbmpu)
+				sound_unload_mididev(devc->my_mididev);
+			sound_unload_audiodev(devc->dev);
+		}
+		kfree(devc);
+	}
+	else
+		release_region(hw_config->io_base, 16);
+
+	kfree(detected_devc);
+}
+
+/*
+ *	Mixer access routines
+ *
+ *	ES1887 modifications: some mixer registers reside in the
+ *	range above 0xa0. These must be accessed in another way.
+ */
+
+void sb_setmixer(sb_devc * devc, unsigned int port, unsigned int value)
+{
+	unsigned long flags;
+
+	if (devc->model == MDL_ESS) {
+		ess_setmixer (devc, port, value);
+		return;
+	}
+
+	spin_lock_irqsave(&devc->lock, flags);
+
+	outb(((unsigned char) (port & 0xff)), MIXER_ADDR);
+	udelay(20);
+	outb(((unsigned char) (value & 0xff)), MIXER_DATA);
+	udelay(20);
+
+	spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+unsigned int sb_getmixer(sb_devc * devc, unsigned int port)
+{
+	unsigned int val;
+	unsigned long flags;
+
+	if (devc->model == MDL_ESS) return ess_getmixer (devc, port);
+
+	spin_lock_irqsave(&devc->lock, flags);
+
+	outb(((unsigned char) (port & 0xff)), MIXER_ADDR);
+	udelay(20);
+	val = inb(MIXER_DATA);
+	udelay(20);
+
+	spin_unlock_irqrestore(&devc->lock, flags);
+
+	return val;
+}
+
+void sb_chgmixer
+	(sb_devc * devc, unsigned int reg, unsigned int mask, unsigned int val)
+{
+	int value;
+
+	value = sb_getmixer(devc, reg);
+	value = (value & ~mask) | (val & mask);
+	sb_setmixer(devc, reg, value);
+}
+
+/*
+ *	MPU401 MIDI initialization.
+ */
+
+static void smw_putmem(sb_devc * devc, int base, int addr, unsigned char val)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&jazz16_lock, flags);  /* NOT the SB card? */
+
+	outb((addr & 0xff), base + 1);	/* Low address bits */
+	outb((addr >> 8), base + 2);	/* High address bits */
+	outb((val), base);	/* Data */
+
+	spin_unlock_irqrestore(&jazz16_lock, flags);
+}
+
+static unsigned char smw_getmem(sb_devc * devc, int base, int addr)
+{
+	unsigned long flags;
+	unsigned char val;
+
+	spin_lock_irqsave(&jazz16_lock, flags);  /* NOT the SB card? */
+
+	outb((addr & 0xff), base + 1);	/* Low address bits */
+	outb((addr >> 8), base + 2);	/* High address bits */
+	val = inb(base);	/* Data */
+
+	spin_unlock_irqrestore(&jazz16_lock, flags);
+	return val;
+}
+
+static int smw_midi_init(sb_devc * devc, struct address_info *hw_config)
+{
+	int mpu_base = hw_config->io_base;
+	int mp_base = mpu_base + 4;		/* Microcontroller base */
+	int i;
+	unsigned char control;
+
+
+	/*
+	 *  Reset the microcontroller so that the RAM can be accessed
+	 */
+
+	control = inb(mpu_base + 7);
+	outb((control | 3), mpu_base + 7);	/* Set last two bits to 1 (?) */
+	outb(((control & 0xfe) | 2), mpu_base + 7);	/* xxxxxxx0 resets the mc */
+
+	mdelay(3);	/* Wait at least 1ms */
+
+	outb((control & 0xfc), mpu_base + 7);	/* xxxxxx00 enables RAM */
+
+	/*
+	 *  Detect microcontroller by probing the 8k RAM area
+	 */
+	smw_putmem(devc, mp_base, 0, 0x00);
+	smw_putmem(devc, mp_base, 1, 0xff);
+	udelay(10);
+
+	if (smw_getmem(devc, mp_base, 0) != 0x00 || smw_getmem(devc, mp_base, 1) != 0xff)
+	{
+		DDB(printk("SM Wave: No microcontroller RAM detected (%02x, %02x)\n", smw_getmem(devc, mp_base, 0), smw_getmem(devc, mp_base, 1)));
+		return 0;	/* No RAM */
+	}
+	/*
+	 *  There is RAM so assume it's really a SM Wave
+	 */
+
+	devc->model = MDL_SMW;
+	smw_mixer_init(devc);
+
+#ifdef MODULE
+	if (!smw_ucode)
+	{
+		smw_ucodeLen = mod_firmware_load("/etc/sound/midi0001.bin", (void *) &smw_ucode);
+		smw_free = smw_ucode;
+	}
+#endif
+	if (smw_ucodeLen > 0)
+	{
+		if (smw_ucodeLen != 8192)
+		{
+			printk(KERN_ERR "SM Wave: Invalid microcode (MIDI0001.BIN) length\n");
+			return 1;
+		}
+		/*
+		 *  Download microcode
+		 */
+
+		for (i = 0; i < 8192; i++)
+			smw_putmem(devc, mp_base, i, smw_ucode[i]);
+
+		/*
+		 *  Verify microcode
+		 */
+
+		for (i = 0; i < 8192; i++)
+			if (smw_getmem(devc, mp_base, i) != smw_ucode[i])
+			{
+				printk(KERN_ERR "SM Wave: Microcode verification failed\n");
+				return 0;
+			}
+	}
+	control = 0;
+#ifdef SMW_SCSI_IRQ
+	/*
+	 * Set the SCSI interrupt (IRQ2/9, IRQ3 or IRQ10). The SCSI interrupt
+	 * is disabled by default.
+	 *
+	 * FIXME - make this a module option
+	 *
+	 * BTW the Zilog 5380 SCSI controller is located at MPU base + 0x10.
+	 */
+	{
+		static unsigned char scsi_irq_bits[] = {
+			0, 0, 3, 1, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0
+		};
+		control |= scsi_irq_bits[SMW_SCSI_IRQ] << 6;
+	}
+#endif
+
+#ifdef SMW_OPL4_ENABLE
+	/*
+	 *  Make the OPL4 chip visible on the PC bus at 0x380.
+	 *
+	 *  There is no need to enable this feature since this driver
+	 *  doesn't support OPL4 yet. Also there is no RAM in SM Wave so
+	 *  enabling OPL4 is pretty useless.
+	 */
+	control |= 0x10;	/* Uses IRQ12 if bit 0x20 == 0 */
+	/* control |= 0x20;      Uncomment this if you want to use IRQ7 */
+#endif
+	outb((control | 0x03), mpu_base + 7);	/* xxxxxx11 restarts */
+	hw_config->name = "SoundMan Wave";
+	return 1;
+}
+
+static int init_Jazz16_midi(sb_devc * devc, struct address_info *hw_config)
+{
+	int mpu_base = hw_config->io_base;
+	int sb_base = devc->base;
+	int irq = hw_config->irq;
+
+	unsigned char bits = 0;
+	unsigned long flags;
+
+	if (irq < 0)
+		irq *= -1;
+
+	if (irq < 1 || irq > 15 ||
+	    jazz_irq_bits[irq] == 0)
+	{
+		printk(KERN_ERR "Jazz16: Invalid MIDI interrupt (IRQ%d)\n", irq);
+		return 0;
+	}
+	switch (sb_base)
+	{
+		case 0x220:
+			bits = 1;
+			break;
+		case 0x240:
+			bits = 2;
+			break;
+		case 0x260:
+			bits = 3;
+			break;
+		default:
+			return 0;
+	}
+	bits = jazz16_bits = bits << 5;
+	switch (mpu_base)
+	{
+		case 0x310:
+			bits |= 1;
+			break;
+		case 0x320:
+			bits |= 2;
+			break;
+		case 0x330:
+			bits |= 3;
+			break;
+		default:
+			printk(KERN_ERR "Jazz16: Invalid MIDI I/O port %x\n", mpu_base);
+			return 0;
+	}
+	/*
+	 *	Magic wake up sequence by writing to 0x201 (aka Joystick port)
+	 */
+	spin_lock_irqsave(&jazz16_lock, flags);
+	outb(0xAF, 0x201);
+	outb(0x50, 0x201);
+	outb(bits, 0x201);
+	spin_unlock_irqrestore(&jazz16_lock, flags);
+
+	hw_config->name = "Jazz16";
+	smw_midi_init(devc, hw_config);
+
+	if (!sb_dsp_command(devc, 0xfb))
+		return 0;
+
+	if (!sb_dsp_command(devc, jazz_dma_bits[devc->dma8] |
+			    (jazz_dma_bits[devc->dma16] << 4)))
+		return 0;
+
+	if (!sb_dsp_command(devc, jazz_irq_bits[devc->irq] |
+			    (jazz_irq_bits[irq] << 4)))
+		return 0;
+
+	return 1;
+}
+
+int probe_sbmpu(struct address_info *hw_config, struct module *owner)
+{
+	sb_devc *devc = last_devc;
+	int ret;
+
+	if (last_devc == NULL)
+		return 0;
+
+	last_devc = NULL;
+
+	if (hw_config->io_base <= 0)
+	{
+		/* The real vibra16 is fine about this, but we have to go
+		   wipe up after Cyrix again */
+		   	   
+		if(devc->model == MDL_SB16 && devc->minor >= 12)
+		{
+			unsigned char   bits = sb_getmixer(devc, 0x84) & ~0x06;
+			sb_setmixer(devc, 0x84, bits | 0x02);		/* Disable MPU */
+		}
+		return 0;
+	}
+
+#if defined(CONFIG_SOUND_MPU401)
+	if (devc->model == MDL_ESS)
+	{
+		struct resource *ports;
+		ports = request_region(hw_config->io_base, 2, "mpu401");
+		if (!ports) {
+			printk(KERN_ERR "sbmpu: I/O port conflict (%x)\n", hw_config->io_base);
+			return 0;
+		}
+		if (!ess_midi_init(devc, hw_config)) {
+			release_region(hw_config->io_base, 2);
+			return 0;
+		}
+		hw_config->name = "ESS1xxx MPU";
+		devc->midi_irq_cookie = NULL;
+		if (!probe_mpu401(hw_config, ports)) {
+			release_region(hw_config->io_base, 2);
+			return 0;
+		}
+		attach_mpu401(hw_config, owner);
+		if (last_sb->irq == -hw_config->irq)
+			last_sb->midi_irq_cookie =
+				(void *)(long) hw_config->slots[1];
+		return 1;
+	}
+#endif
+
+	switch (devc->model)
+	{
+		case MDL_SB16:
+			if (hw_config->io_base != 0x300 && hw_config->io_base != 0x330)
+			{
+				printk(KERN_ERR "SB16: Invalid MIDI port %x\n", hw_config->io_base);
+				return 0;
+			}
+			hw_config->name = "Sound Blaster 16";
+			if (hw_config->irq < 3 || hw_config->irq == devc->irq)
+				hw_config->irq = -devc->irq;
+			if (devc->minor > 12)		/* What is Vibra's version??? */
+				sb16_set_mpu_port(devc, hw_config);
+			break;
+
+		case MDL_JAZZ:
+			if (hw_config->irq < 3 || hw_config->irq == devc->irq)
+				hw_config->irq = -devc->irq;
+			if (!init_Jazz16_midi(devc, hw_config))
+				return 0;
+			break;
+
+		case MDL_YMPCI:
+			hw_config->name = "Yamaha PCI Legacy";
+			printk("Yamaha PCI legacy UART401 check.\n");
+			break;
+		default:
+			return 0;
+	}
+	
+	ret = probe_uart401(hw_config, owner);
+	if (ret)
+		last_sb->midi_irq_cookie=midi_devs[hw_config->slots[4]]->devc;
+	return ret;
+}
+
+void unload_sbmpu(struct address_info *hw_config)
+{
+#if defined(CONFIG_SOUND_MPU401)
+	if (!strcmp (hw_config->name, "ESS1xxx MPU")) {
+		unload_mpu401(hw_config);
+		return;
+	}
+#endif
+	unload_uart401(hw_config);
+}
+
+EXPORT_SYMBOL(sb_dsp_init);
+EXPORT_SYMBOL(sb_dsp_detect);
+EXPORT_SYMBOL(sb_dsp_unload);
+EXPORT_SYMBOL(sb_be_quiet);
+EXPORT_SYMBOL(probe_sbmpu);
+EXPORT_SYMBOL(unload_sbmpu);
+EXPORT_SYMBOL(smw_free);
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/sb_ess.c b/linux/sound/oss/sb_ess.c
new file mode 100644
index 0000000..9890cf2
--- /dev/null
+++ b/linux/sound/oss/sb_ess.c
@@ -0,0 +1,1831 @@
+#undef FKS_LOGGING
+#undef FKS_TEST
+
+/*
+ * tabs should be 4 spaces, in vi(m): set tabstop=4
+ *
+ * TODO: 	consistency speed calculations!!
+ *			cleanup!
+ * ????:	Did I break MIDI support?
+ *
+ * History:
+ *
+ * Rolf Fokkens	 (Dec 20 1998):	ES188x recording level support on a per
+ * fokkensr@vertis.nl			input basis.
+ *				 (Dec 24 1998):	Recognition of ES1788, ES1887, ES1888,
+ *								ES1868, ES1869 and ES1878. Could be used for
+ *								specific handling in the future. All except
+ *								ES1887 and ES1888 and ES688 are handled like
+ *								ES1688.
+ *				 (Dec 27 1998):	RECLEV for all (?) ES1688+ chips. ES188x now
+ *								have the "Dec 20" support + RECLEV
+ *				 (Jan  2 1999):	Preparation for Full Duplex. This means
+ *								Audio 2 is now used for playback when dma16
+ *								is specified. The next step would be to use
+ *								Audio 1 and Audio 2 at the same time.
+ *				 (Jan  9 1999):	Put all ESS stuff into sb_ess.[ch], this
+ *								includes both the ESS stuff that has been in
+ *								sb_*[ch] before I touched it and the ESS support
+ *								I added later
+ *				 (Jan 23 1999):	Full Duplex seems to work. I wrote a small
+ *								test proggy which works OK. Haven't found
+ *								any applications to test it though. So why did
+ *								I bother to create it anyway?? :) Just for
+ *								fun.
+ *				 (May  2 1999):	I tried to be too smart by "introducing"
+ *								ess_calc_best_speed (). The idea was that two
+ *								dividers could be used to setup a samplerate,
+ *								ess_calc_best_speed () would choose the best.
+ *								This works for playback, but results in
+ *								recording problems for high samplerates. I
+ *								fixed this by removing ess_calc_best_speed ()
+ *								and just doing what the documentation says. 
+ * Andy Sloane   (Jun  4 1999): Stole some code from ALSA to fix the playback
+ * andy@guildsoftware.com		speed on ES1869, ES1879, ES1887, and ES1888.
+ * 								1879's were previously ignored by this driver;
+ * 								added (untested) support for those.
+ * Cvetan Ivanov (Oct 27 1999): Fixed ess_dsp_init to call ess_set_dma_hw for
+ * zezo@inet.bg					_ALL_ ESS models, not only ES1887
+ *
+ * This files contains ESS chip specifics. It's based on the existing ESS
+ * handling as it resided in sb_common.c, sb_mixer.c and sb_audio.c. This
+ * file adds features like:
+ * - Chip Identification (as shown in /proc/sound)
+ * - RECLEV support for ES1688 and later
+ * - 6 bits playback level support chips later than ES1688
+ * - Recording level support on a per-device basis for ES1887
+ * - Full-Duplex for ES1887
+ *
+ * Full duplex is enabled by specifying dma16. While the normal dma must
+ * be one of 0, 1 or 3, dma16 can be one of 0, 1, 3 or 5. DMA 5 is a 16 bit
+ * DMA channel, while the others are 8 bit..
+ *
+ * ESS detection isn't full proof (yet). If it fails an additional module
+ * parameter esstype can be specified to be one of the following:
+ * -1, 0, 688, 1688, 1868, 1869, 1788, 1887, 1888
+ * -1 means: mimic 2.0 behaviour, 
+ *  0 means: auto detect.
+ *   others: explicitly specify chip
+ * -1 is default, cause auto detect still doesn't work.
+ */
+
+/*
+ * About the documentation
+ *
+ * I don't know if the chips all are OK, but the documentation is buggy. 'cause
+ * I don't have all the cips myself, there's a lot I cannot verify. I'll try to
+ * keep track of my latest insights about his here. If you have additional info,
+ * please enlighten me (fokkensr@vertis.nl)!
+ *
+ * I had the impression that ES1688 also has 6 bit master volume control. The
+ * documentation about ES1888 (rev C, october '95) claims that ES1888 has
+ * the following features ES1688 doesn't have:
+ * - 6 bit master volume
+ * - Full Duplex
+ * So ES1688 apparently doesn't have 6 bit master volume control, but the
+ * ES1688 does have RECLEV control. Makes me wonder: does ES688 have it too?
+ * Without RECLEV ES688 won't be much fun I guess.
+ *
+ * From the ES1888 (rev C, october '95) documentation I got the impression
+ * that registers 0x68 to 0x6e don't exist which means: no recording volume
+ * controls. To my surprise the ES888 documentation (1/14/96) claims that
+ * ES888 does have these record mixer registers, but that ES1888 doesn't have
+ * 0x69 and 0x6b. So the rest should be there.
+ *
+ * I'm trying to get ES1887 Full Duplex. Audio 2 is playback only, while Audio 2
+ * is both record and playback. I think I should use Audio 2 for all playback.
+ *
+ * The documentation is an adventure: it's close but not fully accurate. I
+ * found out that after a reset some registers are *NOT* reset, though the
+ * docs say the would be. Interesting ones are 0x7f, 0x7d and 0x7a. They are
+ * related to the Audio 2 channel. I also was surprised about the consequences
+ * of writing 0x00 to 0x7f (which should be done by reset): The ES1887 moves
+ * into ES1888 mode. This means that it claims IRQ 11, which happens to be my
+ * ISDN adapter. Needless to say it no longer worked. I now understand why
+ * after rebooting 0x7f already was 0x05, the value of my choice: the BIOS
+ * did it.
+ *
+ * Oh, and this is another trap: in ES1887 docs mixer register 0x70 is
+ * described as if it's exactly the same as register 0xa1. This is *NOT* true.
+ * The description of 0x70 in ES1869 docs is accurate however.
+ * Well, the assumption about ES1869 was wrong: register 0x70 is very much
+ * like register 0xa1, except that bit 7 is always 1, whatever you want
+ * it to be.
+ *
+ * When using audio 2 mixer register 0x72 seems te be meaningless. Only 0xa2
+ * has effect.
+ *
+ * Software reset not being able to reset all registers is great! Especially
+ * the fact that register 0x78 isn't reset is great when you wanna change back
+ * to single dma operation (simplex): audio 2 is still operational, and uses
+ * the same dma as audio 1: your ess changes into a funny echo machine.
+ *
+ * Received the news that ES1688 is detected as a ES1788. Did some thinking:
+ * the ES1887 detection scheme suggests in step 2 to try if bit 3 of register
+ * 0x64 can be changed. This is inaccurate, first I inverted the * check: "If
+ * can be modified, it's a 1688", which lead to a correct detection
+ * of my ES1887. It resulted however in bad detection of 1688 (reported by mail)
+ * and 1868 (if no PnP detection first): they result in a 1788 being detected.
+ * I don't have docs on 1688, but I do have docs on 1868: The documentation is
+ * probably inaccurate in the fact that I should check bit 2, not bit 3. This
+ * is what I do now.
+ */
+
+/*
+ * About recognition of ESS chips
+ *
+ * The distinction of ES688, ES1688, ES1788, ES1887 and ES1888 is described in
+ * a (preliminary ??) datasheet on ES1887. Its aim is to identify ES1887, but
+ * during detection the text claims that "this chip may be ..." when a step
+ * fails. This scheme is used to distinct between the above chips.
+ * It appears however that some PnP chips like ES1868 are recognized as ES1788
+ * by the ES1887 detection scheme. These PnP chips can be detected in another
+ * way however: ES1868, ES1869 and ES1878 can be recognized (full proof I think)
+ * by repeatedly reading mixer register 0x40. This is done by ess_identify in
+ * sb_common.c.
+ * This results in the following detection steps:
+ * - distinct between ES688 and ES1688+ (as always done in this driver)
+ *   if ES688 we're ready
+ * - try to detect ES1868, ES1869 or ES1878
+ *   if successful we're ready
+ * - try to detect ES1888, ES1887 or ES1788
+ *   if successful we're ready
+ * - Dunno. Must be 1688. Will do in general
+ *
+ * About RECLEV support:
+ *
+ * The existing ES1688 support didn't take care of the ES1688+ recording
+ * levels very well. Whenever a device was selected (recmask) for recording
+ * its recording level was loud, and it couldn't be changed. The fact that
+ * internal register 0xb4 could take care of RECLEV, didn't work meaning until
+ * its value was restored every time the chip was reset; this reset the
+ * value of 0xb4 too. I guess that's what 4front also had (have?) trouble with.
+ *
+ * About ES1887 support:
+ *
+ * The ES1887 has separate registers to control the recording levels, for all
+ * inputs. The ES1887 specific software makes these levels the same as their
+ * corresponding playback levels, unless recmask says they aren't recorded. In
+ * the latter case the recording volumes are 0.
+ * Now recording levels of inputs can be controlled, by changing the playback
+ * levels. Futhermore several devices can be recorded together (which is not
+ * possible with the ES1688).
+ * Besides the separate recording level control for each input, the common
+ * recording level can also be controlled by RECLEV as described above.
+ *
+ * Not only ES1887 have this recording mixer. I know the following from the
+ * documentation:
+ * ES688	no
+ * ES1688	no
+ * ES1868	no
+ * ES1869	yes
+ * ES1878	no
+ * ES1879	yes
+ * ES1888	no/yes	Contradicting documentation; most recent: yes
+ * ES1946	yes		This is a PCI chip; not handled by this driver
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+
+#include "sound_config.h"
+#include "sb_mixer.h"
+#include "sb.h"
+
+#include "sb_ess.h"
+
+#define ESSTYPE_LIKE20	-1		/* Mimic 2.0 behaviour					*/
+#define ESSTYPE_DETECT	0		/* Mimic 2.0 behaviour					*/
+
+#define SUBMDL_ES1788	0x10	/* Subtype ES1788 for specific handling */
+#define SUBMDL_ES1868	0x11	/* Subtype ES1868 for specific handling */
+#define SUBMDL_ES1869	0x12	/* Subtype ES1869 for specific handling */
+#define SUBMDL_ES1878	0x13	/* Subtype ES1878 for specific handling */
+#define SUBMDL_ES1879	0x16    /* ES1879 was initially forgotten */
+#define SUBMDL_ES1887	0x14	/* Subtype ES1887 for specific handling */
+#define SUBMDL_ES1888	0x15	/* Subtype ES1888 for specific handling */
+
+#define SB_CAP_ES18XX_RATE 0x100
+
+#define ES1688_CLOCK1 795444 /* 128 - div */
+#define ES1688_CLOCK2 397722 /* 256 - div */
+#define ES18XX_CLOCK1 793800 /* 128 - div */
+#define ES18XX_CLOCK2 768000 /* 256 - div */
+
+#ifdef FKS_LOGGING
+static void ess_show_mixerregs (sb_devc *devc);
+#endif
+static int ess_read (sb_devc * devc, unsigned char reg);
+static int ess_write (sb_devc * devc, unsigned char reg, unsigned char data);
+static void ess_chgmixer
+	(sb_devc * devc, unsigned int reg, unsigned int mask, unsigned int val);
+
+/****************************************************************************
+ *																			*
+ *									ESS audio								*
+ *																			*
+ ****************************************************************************/
+
+struct ess_command {short cmd; short data;};
+
+/*
+ * Commands for initializing Audio 1 for input (record)
+ */
+static struct ess_command ess_i08m[] =		/* input 8 bit mono */
+	{ {0xb7, 0x51}, {0xb7, 0xd0}, {-1, 0} };
+static struct ess_command ess_i16m[] =		/* input 16 bit mono */
+	{ {0xb7, 0x71}, {0xb7, 0xf4}, {-1, 0} };
+static struct ess_command ess_i08s[] =		/* input 8 bit stereo */
+	{ {0xb7, 0x51}, {0xb7, 0x98}, {-1, 0} };
+static struct ess_command ess_i16s[] =		/* input 16 bit stereo */
+	{ {0xb7, 0x71}, {0xb7, 0xbc}, {-1, 0} };
+
+static struct ess_command *ess_inp_cmds[] =
+	{ ess_i08m, ess_i16m, ess_i08s, ess_i16s };
+
+
+/*
+ * Commands for initializing Audio 1 for output (playback)
+ */
+static struct ess_command ess_o08m[] =		/* output 8 bit mono */
+	{ {0xb6, 0x80}, {0xb7, 0x51}, {0xb7, 0xd0}, {-1, 0} };
+static struct ess_command ess_o16m[] =		/* output 16 bit mono */
+	{ {0xb6, 0x00}, {0xb7, 0x71}, {0xb7, 0xf4}, {-1, 0} };
+static struct ess_command ess_o08s[] =		/* output 8 bit stereo */
+	{ {0xb6, 0x80}, {0xb7, 0x51}, {0xb7, 0x98}, {-1, 0} };
+static struct ess_command ess_o16s[] =		/* output 16 bit stereo */
+	{ {0xb6, 0x00}, {0xb7, 0x71}, {0xb7, 0xbc}, {-1, 0} };
+
+static struct ess_command *ess_out_cmds[] =
+	{ ess_o08m, ess_o16m, ess_o08s, ess_o16s };
+
+static void ess_exec_commands
+	(sb_devc *devc, struct ess_command *cmdtab[])
+{
+	struct ess_command *cmd;
+
+	cmd = cmdtab [ ((devc->channels != 1) << 1) + (devc->bits != AFMT_U8) ];
+
+	while (cmd->cmd != -1) {
+		ess_write (devc, cmd->cmd, cmd->data);
+		cmd++;
+	}
+}
+
+static void ess_change
+	(sb_devc *devc, unsigned int reg, unsigned int mask, unsigned int val)
+{
+	int value;
+
+	value = ess_read (devc, reg);
+	value = (value & ~mask) | (val & mask);
+	ess_write (devc, reg, value);
+}
+
+static void ess_set_output_parms
+	(int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (devc->duplex) {
+		devc->trg_buf_16 = buf;
+		devc->trg_bytes_16 = nr_bytes;
+		devc->trg_intrflag_16 = intrflag;
+		devc->irq_mode_16 = IMODE_OUTPUT;
+	} else {
+		devc->trg_buf = buf;
+		devc->trg_bytes = nr_bytes;
+		devc->trg_intrflag = intrflag;
+		devc->irq_mode = IMODE_OUTPUT;
+	}
+}
+
+static void ess_set_input_parms
+	(int dev, unsigned long buf, int count, int intrflag)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	devc->trg_buf = buf;
+	devc->trg_bytes = count;
+	devc->trg_intrflag = intrflag;
+	devc->irq_mode = IMODE_INPUT;
+}
+
+static int ess_calc_div (int clock, int revert, int *speedp, int *diffp)
+{
+	int divider;
+	int speed, diff;
+	int retval;
+
+	speed   = *speedp;
+	divider = (clock + speed / 2) / speed;
+	retval  = revert - divider;
+	if (retval > revert - 1) {
+		retval  = revert - 1;
+		divider = revert - retval;
+	}
+	/* This line is suggested. Must be wrong I think
+	*speedp = (clock + divider / 2) / divider;
+	So I chose the next one */
+
+	*speedp	= clock / divider;
+	diff	= speed - *speedp;
+	if (diff < 0) diff =-diff;
+	*diffp  = diff;
+
+	return retval;
+}
+
+static int ess_calc_best_speed
+	(int clock1, int rev1, int clock2, int rev2, int *divp, int *speedp)
+{
+	int speed1 = *speedp, speed2 = *speedp;
+	int div1, div2;
+	int diff1, diff2;
+	int retval;
+
+	div1 = ess_calc_div (clock1, rev1, &speed1, &diff1);
+	div2 = ess_calc_div (clock2, rev2, &speed2, &diff2);
+
+	if (diff1 < diff2) {
+		*divp   = div1;
+		*speedp = speed1;
+		retval  = 1;
+	} else {
+	/*	*divp   = div2; */
+		*divp   = 0x80 | div2;
+		*speedp = speed2;
+		retval  = 2;
+	}
+
+	return retval;
+}
+
+/*
+ * Depending on the audiochannel ESS devices can
+ * have different clock settings. These are made consistent for duplex
+ * however.
+ * callers of ess_speed only do an audionum suggestion, which means
+ * input suggests 1, output suggests 2. This suggestion is only true
+ * however when doing duplex.
+ */
+static void ess_common_speed (sb_devc *devc, int *speedp, int *divp)
+{
+	int diff = 0, div;
+
+	if (devc->duplex) {
+		/*
+		 * The 0x80 is important for the first audio channel
+		 */
+		if (devc->submodel == SUBMDL_ES1888) {
+			div = 0x80 | ess_calc_div (795500, 256, speedp, &diff);
+		} else {
+			div = 0x80 | ess_calc_div (795500, 128, speedp, &diff);
+		}
+	} else if(devc->caps & SB_CAP_ES18XX_RATE) {
+		if (devc->submodel == SUBMDL_ES1888) {
+			ess_calc_best_speed(397700, 128, 795500, 256, 
+						&div, speedp);
+		} else {
+			ess_calc_best_speed(ES18XX_CLOCK1, 128, ES18XX_CLOCK2, 256, 
+						&div, speedp);
+		}
+	} else {
+		if (*speedp > 22000) {
+			div = 0x80 | ess_calc_div (ES1688_CLOCK1, 256, speedp, &diff);
+		} else {
+			div = 0x00 | ess_calc_div (ES1688_CLOCK2, 128, speedp, &diff);
+		}
+	}
+	*divp = div;
+}
+
+static void ess_speed (sb_devc *devc, int audionum)
+{
+	int speed;
+	int div, div2;
+
+	ess_common_speed (devc, &(devc->speed), &div);
+
+#ifdef FKS_REG_LOGGING
+printk (KERN_INFO "FKS: ess_speed (%d) b speed = %d, div=%x\n", audionum, devc->speed, div);
+#endif
+
+	/* Set filter roll-off to 90% of speed/2 */
+	speed = (devc->speed * 9) / 20;
+
+	div2 = 256 - 7160000 / (speed * 82);
+
+	if (!devc->duplex) audionum = 1;
+
+	if (audionum == 1) {
+		/* Change behaviour of register A1 *
+		sb_chg_mixer(devc, 0x71, 0x20, 0x20)
+		* For ES1869 only??? */
+		ess_write (devc, 0xa1, div);
+		ess_write (devc, 0xa2, div2);
+	} else {
+		ess_setmixer (devc, 0x70, div);
+		/*
+		 * FKS: fascinating: 0x72 doesn't seem to work.
+		 */
+		ess_write (devc, 0xa2, div2);
+		ess_setmixer (devc, 0x72, div2);
+	}
+}
+
+static int ess_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	ess_speed(devc, 1);
+
+	sb_dsp_command(devc, DSP_CMD_SPKOFF);
+
+	ess_write (devc, 0xb8, 0x0e);	/* Auto init DMA mode */
+	ess_change (devc, 0xa8, 0x03, 3 - devc->channels);	/* Mono/stereo */
+	ess_write (devc, 0xb9, 2);	/* Demand mode (4 bytes/DMA request) */
+
+	ess_exec_commands (devc, ess_inp_cmds);
+
+	ess_change (devc, 0xb1, 0xf0, 0x50);
+	ess_change (devc, 0xb2, 0xf0, 0x50);
+
+	devc->trigger_bits = 0;
+	return 0;
+}
+
+static int ess_audio_prepare_for_output_audio1 (int dev, int bsize, int bcount)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	sb_dsp_reset(devc);
+	ess_speed(devc, 1);
+	ess_write (devc, 0xb8, 4);	/* Auto init DMA mode */
+	ess_change (devc, 0xa8, 0x03, 3 - devc->channels);	/* Mono/stereo */
+	ess_write (devc, 0xb9, 2);	/* Demand mode (4 bytes/request) */
+
+	ess_exec_commands (devc, ess_out_cmds);
+
+	ess_change (devc, 0xb1, 0xf0, 0x50);	/* Enable DMA */
+	ess_change (devc, 0xb2, 0xf0, 0x50);	/* Enable IRQ */
+
+	sb_dsp_command(devc, DSP_CMD_SPKON);	/* There be sound! */
+
+	devc->trigger_bits = 0;
+	return 0;
+}
+
+static int ess_audio_prepare_for_output_audio2 (int dev, int bsize, int bcount)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+	unsigned char bits;
+
+/* FKS: qqq
+	sb_dsp_reset(devc);
+*/
+
+	/*
+	 * Auto-Initialize:
+	 * DMA mode + demand mode (8 bytes/request, yes I want it all!)
+	 * But leave 16-bit DMA bit untouched!
+	 */
+	ess_chgmixer (devc, 0x78, 0xd0, 0xd0);
+
+	ess_speed(devc, 2);
+
+	/* bits 4:3 on ES1887 represent recording source. Keep them! */
+	bits = ess_getmixer (devc, 0x7a) & 0x18;
+
+	/* Set stereo/mono */
+	if (devc->channels != 1) bits |= 0x02;
+
+	/* Init DACs; UNSIGNED mode for 8 bit; SIGNED mode for 16 bit */
+	if (devc->bits != AFMT_U8) bits |= 0x05;	/* 16 bit */
+
+	/* Enable DMA, IRQ will be shared (hopefully)*/
+	bits |= 0x60;
+
+	ess_setmixer (devc, 0x7a, bits);
+
+	ess_mixer_reload (devc, SOUND_MIXER_PCM);	/* There be sound! */
+
+	devc->trigger_bits = 0;
+	return 0;
+}
+
+static int ess_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "ess_audio_prepare_for_output: dma_out=%d,dma_in=%d\n"
+, audio_devs[dev]->dmap_out->dma, audio_devs[dev]->dmap_in->dma);
+#endif
+
+	if (devc->duplex) {
+		return ess_audio_prepare_for_output_audio2 (dev, bsize, bcount);
+	} else {
+		return ess_audio_prepare_for_output_audio1 (dev, bsize, bcount);
+	}
+}
+
+static void ess_audio_halt_xfer(int dev)
+{
+	unsigned long flags;
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	spin_lock_irqsave(&devc->lock, flags);
+	sb_dsp_reset(devc);
+	spin_unlock_irqrestore(&devc->lock, flags);
+
+	/*
+	 * Audio 2 may still be operational! Creates awful sounds!
+	 */
+	if (devc->duplex) ess_chgmixer(devc, 0x78, 0x03, 0x00);
+}
+
+static void ess_audio_start_input
+	(int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+	int count = nr_bytes;
+	sb_devc *devc = audio_devs[dev]->devc;
+	short c = -nr_bytes;
+
+	/*
+	 * Start a DMA input to the buffer pointed by dmaqtail
+	 */
+
+	if (audio_devs[dev]->dmap_in->dma > 3) count >>= 1;
+	count--;
+
+	devc->irq_mode = IMODE_INPUT;
+
+	ess_write (devc, 0xa4, (unsigned char) ((unsigned short) c & 0xff));
+	ess_write (devc, 0xa5, (unsigned char) (((unsigned short) c >> 8) & 0xff));
+
+	ess_change (devc, 0xb8, 0x0f, 0x0f);	/* Go */
+	devc->intr_active = 1;
+}
+
+static void ess_audio_output_block_audio1
+	(int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+	int count = nr_bytes;
+	sb_devc *devc = audio_devs[dev]->devc;
+	short c = -nr_bytes;
+
+	if (audio_devs[dev]->dmap_out->dma > 3)
+		count >>= 1;
+	count--;
+
+	devc->irq_mode = IMODE_OUTPUT;
+
+	ess_write (devc, 0xa4, (unsigned char) ((unsigned short) c & 0xff));
+	ess_write (devc, 0xa5, (unsigned char) (((unsigned short) c >> 8) & 0xff));
+
+	ess_change (devc, 0xb8, 0x05, 0x05);	/* Go */
+	devc->intr_active = 1;
+}
+
+static void ess_audio_output_block_audio2
+	(int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+	int count = nr_bytes;
+	sb_devc *devc = audio_devs[dev]->devc;
+	short c = -nr_bytes;
+
+	if (audio_devs[dev]->dmap_out->dma > 3) count >>= 1;
+	count--;
+
+	ess_setmixer (devc, 0x74, (unsigned char) ((unsigned short) c & 0xff));
+	ess_setmixer (devc, 0x76, (unsigned char) (((unsigned short) c >> 8) & 0xff));
+	ess_chgmixer (devc, 0x78, 0x03, 0x03);   /* Go */
+
+	devc->irq_mode_16 = IMODE_OUTPUT;
+		devc->intr_active_16 = 1;
+}
+
+static void ess_audio_output_block
+	(int dev, unsigned long buf, int nr_bytes, int intrflag)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (devc->duplex) {
+		ess_audio_output_block_audio2 (dev, buf, nr_bytes, intrflag);
+	} else {
+		ess_audio_output_block_audio1 (dev, buf, nr_bytes, intrflag);
+	}
+}
+
+/*
+ * FKS: the if-statements for both bits and bits_16 are quite alike.
+ * Combine this...
+ */
+static void ess_audio_trigger(int dev, int bits)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	int bits_16 = bits & devc->irq_mode_16;
+	bits &= devc->irq_mode;
+
+	if (!bits && !bits_16) {
+		/* FKS oh oh.... wrong?? for dma 16? */
+		sb_dsp_command(devc, 0xd0);	/* Halt DMA */
+	}
+
+	if (bits) {
+		switch (devc->irq_mode)
+		{
+			case IMODE_INPUT:
+				ess_audio_start_input(dev, devc->trg_buf, devc->trg_bytes,
+					devc->trg_intrflag);
+				break;
+
+			case IMODE_OUTPUT:
+				ess_audio_output_block(dev, devc->trg_buf, devc->trg_bytes,
+					devc->trg_intrflag);
+				break;
+		}
+	}
+
+	if (bits_16) {
+		switch (devc->irq_mode_16) {
+		case IMODE_INPUT:
+			ess_audio_start_input(dev, devc->trg_buf_16, devc->trg_bytes_16,
+					devc->trg_intrflag_16);
+			break;
+
+		case IMODE_OUTPUT:
+			ess_audio_output_block(dev, devc->trg_buf_16, devc->trg_bytes_16,
+					devc->trg_intrflag_16);
+			break;
+		}
+	}
+
+	devc->trigger_bits = bits | bits_16;
+}
+
+static int ess_audio_set_speed(int dev, int speed)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+	int minspeed, maxspeed, dummydiv;
+
+	if (speed > 0) {
+		minspeed = (devc->duplex ? 6215  : 5000 );
+		maxspeed = (devc->duplex ? 44100 : 48000);
+		if (speed < minspeed) speed = minspeed;
+		if (speed > maxspeed) speed = maxspeed;
+
+		ess_common_speed (devc, &speed, &dummydiv);
+
+		devc->speed = speed;
+	}
+	return devc->speed;
+}
+
+/*
+ * FKS: This is a one-on-one copy of sb1_audio_set_bits
+ */
+static unsigned int ess_audio_set_bits(int dev, unsigned int bits)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (bits != 0) {
+		if (bits == AFMT_U8 || bits == AFMT_S16_LE) {
+			devc->bits = bits;
+		} else {
+			devc->bits = AFMT_U8;
+		}
+	}
+
+	return devc->bits;
+}
+
+/*
+ * FKS: This is a one-on-one copy of sbpro_audio_set_channels
+ * (*) Modified it!!
+ */
+static short ess_audio_set_channels(int dev, short channels)
+{
+	sb_devc *devc = audio_devs[dev]->devc;
+
+	if (channels == 1 || channels == 2) devc->channels = channels;
+
+	return devc->channels;
+}
+
+static struct audio_driver ess_audio_driver =   /* ESS ES688/1688 */
+{
+	.owner			= THIS_MODULE,
+	.open			= sb_audio_open,
+	.close			= sb_audio_close,
+	.output_block		= ess_set_output_parms,
+	.start_input		= ess_set_input_parms,
+	.prepare_for_input	= ess_audio_prepare_for_input,
+	.prepare_for_output	= ess_audio_prepare_for_output,
+	.halt_io		= ess_audio_halt_xfer,
+	.trigger		= ess_audio_trigger,
+	.set_speed		= ess_audio_set_speed,
+	.set_bits		= ess_audio_set_bits,
+	.set_channels		= ess_audio_set_channels
+};
+
+/*
+ * ess_audio_init must be called from sb_audio_init
+ */
+struct audio_driver *ess_audio_init
+		(sb_devc *devc, int *audio_flags, int *format_mask)
+{
+	*audio_flags = DMA_AUTOMODE;
+	*format_mask |= AFMT_S16_LE;
+
+	if (devc->duplex) {
+		int tmp_dma;
+		/*
+		 * sb_audio_init thinks dma8 is for playback and
+		 * dma16 is for record. Not now! So swap them.
+		 */
+		tmp_dma		= devc->dma16;
+		devc->dma16	= devc->dma8;
+		devc->dma8	= tmp_dma;
+
+		*audio_flags |= DMA_DUPLEX;
+	}
+
+	return &ess_audio_driver;
+}
+
+/****************************************************************************
+ *																			*
+ *								ESS common									*
+ *																			*
+ ****************************************************************************/
+static void ess_handle_channel
+	(char *channel, int dev, int intr_active, unsigned char flag, int irq_mode)
+{
+	if (!intr_active || !flag) return;
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: ess_handle_channel %s irq_mode=%d\n", channel, irq_mode);
+#endif
+	switch (irq_mode) {
+		case IMODE_OUTPUT:
+			DMAbuf_outputintr (dev, 1);
+			break;
+
+		case IMODE_INPUT:
+			DMAbuf_inputintr (dev);
+			break;
+
+		case IMODE_INIT:
+			break;
+
+		default:;
+			/* printk(KERN_WARNING "ESS: Unexpected interrupt\n"); */
+	}
+}
+
+/*
+ * FKS: TODO!!! Finish this!
+ *
+ * I think midi stuff uses uart401, without interrupts.
+ * So IMODE_MIDI isn't a value for devc->irq_mode.
+ */
+void ess_intr (sb_devc *devc)
+{
+	int				status;
+	unsigned char	src;
+
+	if (devc->submodel == SUBMDL_ES1887) {
+		src = ess_getmixer (devc, 0x7f) >> 4;
+	} else {
+		src = 0xff;
+	}
+
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: sbintr src=%x\n",(int)src);
+#endif
+	ess_handle_channel
+		( "Audio 1"
+		, devc->dev, devc->intr_active   , src & 0x01, devc->irq_mode   );
+	ess_handle_channel
+		( "Audio 2"
+		, devc->dev, devc->intr_active_16, src & 0x02, devc->irq_mode_16);
+	/*
+	 * Acknowledge interrupts
+	 */
+	if (devc->submodel == SUBMDL_ES1887 && (src & 0x02)) {
+		ess_chgmixer (devc, 0x7a, 0x80, 0x00);
+	}
+
+	if (src & 0x01) {
+		status = inb(DSP_DATA_AVAIL);
+	}
+}
+
+static void ess_extended (sb_devc * devc)
+{
+	/* Enable extended mode */
+
+	sb_dsp_command(devc, 0xc6);
+}
+
+static int ess_write (sb_devc * devc, unsigned char reg, unsigned char data)
+{
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: write reg %x: %x\n", reg, data);
+#endif
+	/* Write a byte to an extended mode register of ES1688 */
+
+	if (!sb_dsp_command(devc, reg))
+		return 0;
+
+	return sb_dsp_command(devc, data);
+}
+
+static int ess_read (sb_devc * devc, unsigned char reg)
+{
+	/* Read a byte from an extended mode register of ES1688 */
+
+	/* Read register command */
+	if (!sb_dsp_command(devc, 0xc0)) return -1;
+
+	if (!sb_dsp_command(devc, reg )) return -1;
+
+	return sb_dsp_get_byte(devc);
+}
+
+int ess_dsp_reset(sb_devc * devc)
+{
+	int loopc;
+
+#ifdef FKS_REG_LOGGING
+printk(KERN_INFO "FKS: ess_dsp_reset 1\n");
+ess_show_mixerregs (devc);
+#endif
+
+	DEB(printk("Entered ess_dsp_reset()\n"));
+
+	outb(3, DSP_RESET); /* Reset FIFO too */
+
+	udelay(10);
+	outb(0, DSP_RESET);
+	udelay(30);
+
+	for (loopc = 0; loopc < 1000 && !(inb(DSP_DATA_AVAIL) & 0x80); loopc++);
+
+	if (inb(DSP_READ) != 0xAA) {
+		DDB(printk("sb: No response to RESET\n"));
+		return 0;   /* Sorry */
+	}
+	ess_extended (devc);
+
+	DEB(printk("sb_dsp_reset() OK\n"));
+
+#ifdef FKS_LOGGING
+printk(KERN_INFO "FKS: dsp_reset 2\n");
+ess_show_mixerregs (devc);
+#endif
+
+	return 1;
+}
+
+static int ess_irq_bits (int irq)
+{
+	switch (irq) {
+	case 2:
+	case 9:
+		return 0;
+
+	case 5:
+		return 1;
+
+	case 7:
+		return 2;
+
+	case 10:
+		return 3;
+
+	default:
+		printk(KERN_ERR "ESS1688: Invalid IRQ %d\n", irq);
+		return -1;
+	}
+}
+
+/*
+ *	Set IRQ configuration register for all ESS models
+ */
+static int ess_common_set_irq_hw (sb_devc * devc)
+{
+	int irq_bits;
+
+	if ((irq_bits = ess_irq_bits (devc->irq)) == -1) return 0;
+
+	if (!ess_write (devc, 0xb1, 0x50 | (irq_bits << 2))) {
+		printk(KERN_ERR "ES1688: Failed to write to IRQ config register\n");
+		return 0;
+	}
+	return 1;
+}
+
+/*
+ * I wanna use modern ES1887 mixer irq handling. Funny is the
+ * fact that my BIOS wants the same. But suppose someone's BIOS
+ * doesn't do this!
+ * This is independent of duplex. If there's a 1887 this will
+ * prevent it from going into 1888 mode.
+ */
+static void ess_es1887_set_irq_hw (sb_devc * devc)
+{
+	int irq_bits;
+
+	if ((irq_bits = ess_irq_bits (devc->irq)) == -1) return;
+
+	ess_chgmixer (devc, 0x7f, 0x0f, 0x01 | ((irq_bits + 1) << 1));
+}
+
+static int ess_set_irq_hw (sb_devc * devc)
+{
+	if (devc->submodel == SUBMDL_ES1887) ess_es1887_set_irq_hw (devc);
+
+	return ess_common_set_irq_hw (devc);
+}
+
+#ifdef FKS_TEST
+
+/*
+ * FKS_test:
+ *	for ES1887: 00, 18, non wr bits: 0001 1000
+ *	for ES1868: 00, b8, non wr bits: 1011 1000
+ *	for ES1888: 00, f8, non wr bits: 1111 1000
+ *	for ES1688: 00, f8, non wr bits: 1111 1000
+ *	+   ES968
+ */
+
+static void FKS_test (sb_devc * devc)
+{
+	int val1, val2;
+	val1 = ess_getmixer (devc, 0x64);
+	ess_setmixer (devc, 0x64, ~val1);
+	val2 = ess_getmixer (devc, 0x64) ^ ~val1;
+	ess_setmixer (devc, 0x64, val1);
+	val1 ^= ess_getmixer (devc, 0x64);
+printk (KERN_INFO "FKS: FKS_test %02x, %02x\n", (val1 & 0x0ff), (val2 & 0x0ff));
+};
+#endif
+
+static unsigned int ess_identify (sb_devc * devc)
+{
+	unsigned int val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&devc->lock, flags);
+	outb(((unsigned char) (0x40 & 0xff)), MIXER_ADDR);
+
+	udelay(20);
+	val  = inb(MIXER_DATA) << 8;
+	udelay(20);
+	val |= inb(MIXER_DATA);
+	udelay(20);
+	spin_unlock_irqrestore(&devc->lock, flags);
+
+	return val;
+}
+
+/*
+ * ESS technology describes a detection scheme in their docs. It involves
+ * fiddling with the bits in certain mixer registers. ess_probe is supposed
+ * to help.
+ *
+ * FKS: tracing shows ess_probe writes wrong value to 0x64. Bit 3 reads 1, but
+ * should be written 0 only. Check this.
+ */
+static int ess_probe (sb_devc * devc, int reg, int xorval)
+{
+	int  val1, val2, val3;
+
+	val1 = ess_getmixer (devc, reg);
+	val2 = val1 ^ xorval;
+	ess_setmixer (devc, reg, val2);
+	val3 = ess_getmixer (devc, reg);
+	ess_setmixer (devc, reg, val1);
+
+	return (val2 == val3);
+}
+
+int ess_init(sb_devc * devc, struct address_info *hw_config)
+{
+	unsigned char cfg;
+	int ess_major = 0, ess_minor = 0;
+	int i;
+	static char name[100], modelname[10];
+
+	/*
+	 * Try to detect ESS chips.
+	 */
+
+	sb_dsp_command(devc, 0xe7); /* Return identification */
+
+	for (i = 1000; i; i--) {
+		if (inb(DSP_DATA_AVAIL) & 0x80) {
+			if (ess_major == 0) {
+				ess_major = inb(DSP_READ);
+			} else {
+				ess_minor = inb(DSP_READ);
+				break;
+			}
+		}
+	}
+
+	if (ess_major == 0) return 0;
+
+	if (ess_major == 0x48 && (ess_minor & 0xf0) == 0x80) {
+		sprintf(name, "ESS ES488 AudioDrive (rev %d)",
+			ess_minor & 0x0f);
+		hw_config->name = name;
+		devc->model = MDL_SBPRO;
+		return 1;
+	}
+
+	/*
+	 * This the detection heuristic of ESS technology, though somewhat
+	 * changed to actually make it work.
+	 * This results in the following detection steps:
+	 * - distinct between ES688 and ES1688+ (as always done in this driver)
+	 *   if ES688 we're ready
+	 * - try to detect ES1868, ES1869 or ES1878 (ess_identify)
+	 *   if successful we're ready
+	 * - try to detect ES1888, ES1887 or ES1788 (aim: detect ES1887)
+	 *   if successful we're ready
+	 * - Dunno. Must be 1688. Will do in general
+	 *
+	 * This is the most BETA part of the software: Will the detection
+	 * always work?
+	 */
+	devc->model = MDL_ESS;
+	devc->submodel = ess_minor & 0x0f;
+
+	if (ess_major == 0x68 && (ess_minor & 0xf0) == 0x80) {
+		char *chip = NULL;
+		int submodel = -1;
+
+		switch (devc->sbmo.esstype) {
+		case ESSTYPE_DETECT:
+		case ESSTYPE_LIKE20:
+			break;
+		case 688:
+			submodel = 0x00;
+			break;
+		case 1688:
+			submodel = 0x08;
+			break;
+		case 1868:
+			submodel = SUBMDL_ES1868;
+			break;
+		case 1869:
+			submodel = SUBMDL_ES1869;
+			break;
+		case 1788:
+			submodel = SUBMDL_ES1788;
+			break;
+		case 1878:
+			submodel = SUBMDL_ES1878;
+			break;
+		case 1879:
+			submodel = SUBMDL_ES1879;
+			break;
+		case 1887:
+			submodel = SUBMDL_ES1887;
+			break;
+		case 1888:
+			submodel = SUBMDL_ES1888;
+			break;
+		default:
+			printk (KERN_ERR "Invalid esstype=%d specified\n", devc->sbmo.esstype);
+			return 0;
+		};
+		if (submodel != -1) {
+			devc->submodel = submodel;
+			sprintf (modelname, "ES%d", devc->sbmo.esstype);
+			chip = modelname;
+		};
+		if (chip == NULL && (ess_minor & 0x0f) < 8) {
+			chip = "ES688";
+		};
+#ifdef FKS_TEST
+FKS_test (devc);
+#endif
+		/*
+		 * If Nothing detected yet, and we want 2.0 behaviour...
+		 * Then let's assume it's ES1688.
+		 */
+		if (chip == NULL && devc->sbmo.esstype == ESSTYPE_LIKE20) {
+			chip = "ES1688";
+		};
+
+		if (chip == NULL) {
+			int type;
+
+			type = ess_identify (devc);
+
+			switch (type) {
+			case 0x1868:
+				chip = "ES1868";
+				devc->submodel = SUBMDL_ES1868;
+				break;
+			case 0x1869:
+				chip = "ES1869";
+				devc->submodel = SUBMDL_ES1869;
+				break;
+			case 0x1878:
+				chip = "ES1878";
+				devc->submodel = SUBMDL_ES1878;
+				break;
+			case 0x1879:
+				chip = "ES1879";
+				devc->submodel = SUBMDL_ES1879;
+				break;
+			default:
+				if ((type & 0x00ff) != ((type >> 8) & 0x00ff)) {
+					printk ("ess_init: Unrecognized %04x\n", type);
+				}
+			};
+		};
+#if 0
+		/*
+		 * this one failed:
+		 * the probing of bit 4 is another thought: from ES1788 and up, all
+		 * chips seem to have hardware volume control. Bit 4 is readonly to
+		 * check if a hardware volume interrupt has fired.
+		 * Cause ES688/ES1688 don't have this feature, bit 4 might be writeable
+		 * for these chips.
+		 */
+		if (chip == NULL && !ess_probe(devc, 0x64, (1 << 4))) {
+#endif
+		/*
+		 * the probing of bit 2 is my idea. The ES1887 docs want me to probe
+		 * bit 3. This results in ES1688 being detected as ES1788.
+		 * Bit 2 is for "Enable HWV IRQE", but as ES(1)688 chips don't have
+		 * HardWare Volume, I think they don't have this IRQE.
+		 */
+		if (chip == NULL && ess_probe(devc, 0x64, (1 << 2))) {
+			if (ess_probe (devc, 0x70, 0x7f)) {
+				if (ess_probe (devc, 0x64, (1 << 5))) {
+					chip = "ES1887";
+					devc->submodel = SUBMDL_ES1887;
+				} else {
+					chip = "ES1888";
+					devc->submodel = SUBMDL_ES1888;
+				}
+			} else {
+				chip = "ES1788";
+				devc->submodel = SUBMDL_ES1788;
+			}
+		};
+		if (chip == NULL) {
+			chip = "ES1688";
+		};
+
+	    printk ( KERN_INFO "ESS chip %s %s%s\n"
+               , chip
+               , ( devc->sbmo.esstype == ESSTYPE_DETECT || devc->sbmo.esstype == ESSTYPE_LIKE20
+                 ? "detected"
+                 : "specified"
+                 )
+               , ( devc->sbmo.esstype == ESSTYPE_LIKE20
+                 ? " (kernel 2.0 compatible)"
+                 : ""
+                 )
+               );
+
+		sprintf(name,"ESS %s AudioDrive (rev %d)", chip, ess_minor & 0x0f);
+	} else {
+		strcpy(name, "Jazz16");
+	}
+
+	/* AAS: info stolen from ALSA: these boards have different clocks */
+	switch(devc->submodel) {
+/* APPARENTLY NOT 1869 AND 1887
+		case SUBMDL_ES1869:
+		case SUBMDL_ES1887:
+*/		
+		case SUBMDL_ES1888:
+			devc->caps |= SB_CAP_ES18XX_RATE;
+			break;
+	}
+
+	hw_config->name = name;
+	/* FKS: sb_dsp_reset to enable extended mode???? */
+	sb_dsp_reset(devc); /* Turn on extended mode */
+
+	/*
+	 *  Enable joystick and OPL3
+	 */
+	cfg = ess_getmixer (devc, 0x40);
+	ess_setmixer (devc, 0x40, cfg | 0x03);
+	if (devc->submodel >= 8) {		/* ES1688 */
+		devc->caps |= SB_NO_MIDI;   /* ES1688 uses MPU401 MIDI mode */
+	}
+	sb_dsp_reset (devc);
+
+	/*
+	 * This is important! If it's not done, the IRQ probe in sb_dsp_init
+	 * may fail.
+	 */
+	return ess_set_irq_hw (devc);
+}
+
+static int ess_set_dma_hw(sb_devc * devc)
+{
+	unsigned char cfg, dma_bits = 0, dma16_bits;
+	int dma;
+
+#ifdef FKS_LOGGING
+printk(KERN_INFO "ess_set_dma_hw: dma8=%d,dma16=%d,dup=%d\n"
+, devc->dma8, devc->dma16, devc->duplex);
+#endif
+
+	/*
+	 * FKS: It seems as if this duplex flag isn't set yet. Check it.
+	 */
+	dma = devc->dma8;
+
+	if (dma > 3 || dma < 0 || dma == 2) {
+		dma_bits = 0;
+		printk(KERN_ERR "ESS1688: Invalid DMA8 %d\n", dma);
+		return 0;
+	} else {
+		/* Extended mode DMA enable */
+		cfg = 0x50;
+
+		if (dma == 3) {
+			dma_bits = 3;
+		} else {
+			dma_bits = dma + 1;
+		}
+	}
+
+	if (!ess_write (devc, 0xb2, cfg | (dma_bits << 2))) {
+		printk(KERN_ERR "ESS1688: Failed to write to DMA config register\n");
+		return 0;
+	}
+
+	if (devc->duplex) {
+		dma = devc->dma16;
+		dma16_bits = 0;
+
+		if (dma >= 0) {
+			switch (dma) {
+			case 0:
+				dma_bits = 0x04;
+				break;
+			case 1:
+				dma_bits = 0x05;
+				break;
+			case 3:
+				dma_bits = 0x06;
+				break;
+			case 5:
+				dma_bits   = 0x07;
+				dma16_bits = 0x20;
+				break;
+			default:
+				printk(KERN_ERR "ESS1887: Invalid DMA16 %d\n", dma);
+				return 0;
+			};
+			ess_chgmixer (devc, 0x78, 0x20, dma16_bits);
+			ess_chgmixer (devc, 0x7d, 0x07, dma_bits);
+		}
+	}
+	return 1;
+}
+
+/*
+ * This one is called from sb_dsp_init.
+ *
+ * Return values:
+ *  0: Failed
+ *  1: Succeeded or doesn't apply (not SUBMDL_ES1887)
+ */
+int ess_dsp_init (sb_devc *devc, struct address_info *hw_config)
+{
+	/*
+	 * Caller also checks this, but anyway
+	 */
+	if (devc->model != MDL_ESS) {
+		printk (KERN_INFO "ess_dsp_init for non ESS chip\n");
+		return 1;
+	}
+	/*
+	 * This for ES1887 to run Full Duplex. Actually ES1888
+	 * is allowed to do so too. I have no idea yet if this
+	 * will work for ES1888 however.
+	 *
+	 * For SB16 having both dma8 and dma16 means enable
+	 * Full Duplex. Let's try this for ES1887 too
+	 *
+	 */
+	if (devc->submodel == SUBMDL_ES1887) {
+		if (hw_config->dma2 != -1) {
+			devc->dma16 = hw_config->dma2;
+		}
+		/*
+		 * devc->duplex initialization is put here, cause
+		 * ess_set_dma_hw needs it.
+		 */
+		if (devc->dma8 != devc->dma16 && devc->dma16 != -1) {
+			devc->duplex = 1;
+		}
+	}
+	if (!ess_set_dma_hw (devc)) {
+		free_irq(devc->irq, devc);
+		return 0;
+	}
+	return 1;
+}
+
+/****************************************************************************
+ *																			*
+ *									ESS mixer								*
+ *																			*
+ ****************************************************************************/
+
+#define ES688_RECORDING_DEVICES	\
+			( SOUND_MASK_LINE	| SOUND_MASK_MIC	| SOUND_MASK_CD		)
+#define ES688_MIXER_DEVICES		\
+			( SOUND_MASK_SYNTH	| SOUND_MASK_PCM	| SOUND_MASK_LINE	\
+			| SOUND_MASK_MIC	| SOUND_MASK_CD		| SOUND_MASK_VOLUME	\
+			| SOUND_MASK_LINE2	| SOUND_MASK_SPEAKER					)
+
+#define ES1688_RECORDING_DEVICES	\
+			( ES688_RECORDING_DEVICES					)
+#define ES1688_MIXER_DEVICES		\
+			( ES688_MIXER_DEVICES | SOUND_MASK_RECLEV	)
+
+#define ES1887_RECORDING_DEVICES	\
+			( ES1688_RECORDING_DEVICES | SOUND_MASK_LINE2 | SOUND_MASK_SYNTH)
+#define ES1887_MIXER_DEVICES		\
+			( ES1688_MIXER_DEVICES											)
+
+/*
+ * Mixer registers of ES1887
+ *
+ * These registers specifically take care of recording levels. To make the
+ * mapping from playback devices to recording devices every recording
+ * devices = playback device + ES_REC_MIXER_RECDIFF
+ */
+#define ES_REC_MIXER_RECBASE	(SOUND_MIXER_LINE3 + 1)
+#define ES_REC_MIXER_RECDIFF	(ES_REC_MIXER_RECBASE - SOUND_MIXER_SYNTH)
+
+#define ES_REC_MIXER_RECSYNTH	(SOUND_MIXER_SYNTH	 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECPCM		(SOUND_MIXER_PCM	 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECSPEAKER	(SOUND_MIXER_SPEAKER + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE	(SOUND_MIXER_LINE	 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECMIC		(SOUND_MIXER_MIC	 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECCD		(SOUND_MIXER_CD		 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECIMIX	(SOUND_MIXER_IMIX	 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECALTPCM	(SOUND_MIXER_ALTPCM	 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECRECLEV	(SOUND_MIXER_RECLEV	 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECIGAIN	(SOUND_MIXER_IGAIN	 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECOGAIN	(SOUND_MIXER_OGAIN	 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE1	(SOUND_MIXER_LINE1	 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE2	(SOUND_MIXER_LINE2	 + ES_REC_MIXER_RECDIFF)
+#define ES_REC_MIXER_RECLINE3	(SOUND_MIXER_LINE3	 + ES_REC_MIXER_RECDIFF)
+
+static mixer_tab es688_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME,			0x32, 7, 4, 0x32, 3, 4),
+MIX_ENT(SOUND_MIXER_BASS,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH,			0x36, 7, 4, 0x36, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM,			0x14, 7, 4, 0x14, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER,		0x3c, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE,			0x3e, 7, 4, 0x3e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC,			0x1a, 7, 4, 0x1a, 3, 4),
+MIX_ENT(SOUND_MIXER_CD,				0x38, 7, 4, 0x38, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_IGAIN,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2,			0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3,			0x00, 0, 0, 0x00, 0, 0)
+};
+
+/*
+ * The ES1688 specifics... hopefully correct...
+ * - 6 bit master volume
+ *   I was wrong, ES1888 docs say ES1688 didn't have it.
+ * - RECLEV control
+ * These may apply to ES688 too. I have no idea.
+ */
+static mixer_tab es1688_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME,			0x32, 7, 4, 0x32, 3, 4),
+MIX_ENT(SOUND_MIXER_BASS,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH,			0x36, 7, 4, 0x36, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM,			0x14, 7, 4, 0x14, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER,		0x3c, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE,			0x3e, 7, 4, 0x3e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC,			0x1a, 7, 4, 0x1a, 3, 4),
+MIX_ENT(SOUND_MIXER_CD,				0x38, 7, 4, 0x38, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV,			0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2,			0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3,			0x00, 0, 0, 0x00, 0, 0)
+};
+
+static mixer_tab es1688later_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME,			0x60, 5, 6, 0x62, 5, 6),
+MIX_ENT(SOUND_MIXER_BASS,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH,			0x36, 7, 4, 0x36, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM,			0x14, 7, 4, 0x14, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER,		0x3c, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE,			0x3e, 7, 4, 0x3e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC,			0x1a, 7, 4, 0x1a, 3, 4),
+MIX_ENT(SOUND_MIXER_CD,				0x38, 7, 4, 0x38, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV,			0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2,			0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3,			0x00, 0, 0, 0x00, 0, 0)
+};
+
+/*
+ * This one is for all ESS chips with a record mixer.
+ * It's not used (yet) however
+ */
+static mixer_tab es_rec_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME,			0x60, 5, 6, 0x62, 5, 6),
+MIX_ENT(SOUND_MIXER_BASS,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH,			0x36, 7, 4, 0x36, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM,			0x14, 7, 4, 0x14, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER,		0x3c, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE,			0x3e, 7, 4, 0x3e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC,			0x1a, 7, 4, 0x1a, 3, 4),
+MIX_ENT(SOUND_MIXER_CD,				0x38, 7, 4, 0x38, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV,			0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2,			0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSYNTH,		0x6b, 7, 4, 0x6b, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECPCM,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSPEAKER,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE,		0x6e, 7, 4, 0x6e, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECMIC,		0x68, 7, 4, 0x68, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECCD,			0x6a, 7, 4, 0x6a, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECIMIX,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECALTPCM,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECRECLEV,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECIGAIN,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECOGAIN,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE1,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE2,		0x6c, 7, 4, 0x6c, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECLINE3,		0x00, 0, 0, 0x00, 0, 0)
+};
+
+/*
+ * This one is for ES1887. It's little different from es_rec_mix: it
+ * has 0x7c for PCM playback level. This is because ES1887 uses
+ * Audio 2 for playback.
+ */
+static mixer_tab es1887_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME,			0x60, 5, 6, 0x62, 5, 6),
+MIX_ENT(SOUND_MIXER_BASS,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH,			0x36, 7, 4, 0x36, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM,			0x7c, 7, 4, 0x7c, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER,		0x3c, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE,			0x3e, 7, 4, 0x3e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC,			0x1a, 7, 4, 0x1a, 3, 4),
+MIX_ENT(SOUND_MIXER_CD,				0x38, 7, 4, 0x38, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV,			0xb4, 7, 4, 0xb4, 3, 4),
+MIX_ENT(SOUND_MIXER_IGAIN,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE1,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE2,			0x3a, 7, 4, 0x3a, 3, 4),
+MIX_ENT(SOUND_MIXER_LINE3,			0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSYNTH,		0x6b, 7, 4, 0x6b, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECPCM,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECSPEAKER,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE,		0x6e, 7, 4, 0x6e, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECMIC,		0x68, 7, 4, 0x68, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECCD,			0x6a, 7, 4, 0x6a, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECIMIX,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECALTPCM,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECRECLEV,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECIGAIN,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECOGAIN,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE1,		0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(ES_REC_MIXER_RECLINE2,		0x6c, 7, 4, 0x6c, 3, 4),
+MIX_ENT(ES_REC_MIXER_RECLINE3,		0x00, 0, 0, 0x00, 0, 0)
+};
+
+static int ess_has_rec_mixer (int submodel)
+{
+	switch (submodel) {
+	case SUBMDL_ES1887:
+		return 1;
+	default:
+		return 0;
+	};
+};
+
+#ifdef FKS_LOGGING
+static int ess_mixer_mon_regs[]
+	= { 0x70, 0x71, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7d, 0x7f
+	  , 0xa1, 0xa2, 0xa4, 0xa5, 0xa8, 0xa9
+	  , 0xb1, 0xb2, 0xb4, 0xb5, 0xb6, 0xb7, 0xb9
+	  , 0x00};
+
+static void ess_show_mixerregs (sb_devc *devc)
+{
+	int *mp = ess_mixer_mon_regs;
+
+return;
+
+	while (*mp != 0) {
+		printk (KERN_INFO "res (%x)=%x\n", *mp, (int)(ess_getmixer (devc, *mp)));
+		mp++;
+	}
+}
+#endif
+
+void ess_setmixer (sb_devc * devc, unsigned int port, unsigned int value)
+{
+	unsigned long flags;
+
+#ifdef FKS_LOGGING
+printk(KERN_INFO "FKS: write mixer %x: %x\n", port, value);
+#endif
+
+	spin_lock_irqsave(&devc->lock, flags);
+	if (port >= 0xa0) {
+		ess_write (devc, port, value);
+	} else {
+		outb(((unsigned char) (port & 0xff)), MIXER_ADDR);
+
+		udelay(20);
+		outb(((unsigned char) (value & 0xff)), MIXER_DATA);
+		udelay(20);
+	};
+	spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+unsigned int ess_getmixer (sb_devc * devc, unsigned int port)
+{
+	unsigned int val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&devc->lock, flags);
+
+	if (port >= 0xa0) {
+		val = ess_read (devc, port);
+	} else {
+		outb(((unsigned char) (port & 0xff)), MIXER_ADDR);
+
+		udelay(20);
+		val = inb(MIXER_DATA);
+		udelay(20);
+	}
+	spin_unlock_irqrestore(&devc->lock, flags);
+
+	return val;
+}
+
+static void ess_chgmixer
+	(sb_devc * devc, unsigned int reg, unsigned int mask, unsigned int val)
+{
+	int value;
+
+	value = ess_getmixer (devc, reg);
+	value = (value & ~mask) | (val & mask);
+	ess_setmixer (devc, reg, value);
+}
+
+/*
+ * ess_mixer_init must be called from sb_mixer_init
+ */
+void ess_mixer_init (sb_devc * devc)
+{
+	devc->mixer_caps = SOUND_CAP_EXCL_INPUT;
+
+	/*
+	* Take care of ES1887 specifics...
+	*/
+	switch (devc->submodel) {
+	case SUBMDL_ES1887:
+		devc->supported_devices		= ES1887_MIXER_DEVICES;
+		devc->supported_rec_devices	= ES1887_RECORDING_DEVICES;
+#ifdef FKS_LOGGING
+printk (KERN_INFO "FKS: ess_mixer_init dup = %d\n", devc->duplex);
+#endif
+		if (devc->duplex) {
+			devc->iomap				= &es1887_mix;
+			devc->iomap_sz                          = ARRAY_SIZE(es1887_mix);
+		} else {
+			devc->iomap				= &es_rec_mix;
+			devc->iomap_sz                          = ARRAY_SIZE(es_rec_mix);
+		}
+		break;
+	default:
+		if (devc->submodel < 8) {
+			devc->supported_devices		= ES688_MIXER_DEVICES;
+			devc->supported_rec_devices	= ES688_RECORDING_DEVICES;
+			devc->iomap					= &es688_mix;
+			devc->iomap_sz                                  = ARRAY_SIZE(es688_mix);
+		} else {
+			/*
+			 * es1688 has 4 bits master vol.
+			 * later chips have 6 bits (?)
+			 */
+			devc->supported_devices		= ES1688_MIXER_DEVICES;
+			devc->supported_rec_devices	= ES1688_RECORDING_DEVICES;
+			if (devc->submodel < 0x10) {
+				devc->iomap				= &es1688_mix;
+				devc->iomap_sz                          = ARRAY_SIZE(es688_mix);
+			} else {
+				devc->iomap				= &es1688later_mix;
+				devc->iomap_sz                          = ARRAY_SIZE(es1688later_mix);
+			}
+		}
+	}
+}
+
+/*
+ * Changing playback levels at an ESS chip with record mixer means having to
+ * take care of recording levels of recorded inputs (devc->recmask) too!
+ */
+int ess_mixer_set(sb_devc *devc, int dev, int left, int right)
+{
+	if (ess_has_rec_mixer (devc->submodel) && (devc->recmask & (1 << dev))) {
+		sb_common_mixer_set (devc, dev + ES_REC_MIXER_RECDIFF, left, right);
+	}
+	return sb_common_mixer_set (devc, dev, left, right);
+}
+
+/*
+ * After a sb_dsp_reset extended register 0xb4 (RECLEV) is reset too. After
+ * sb_dsp_reset RECLEV has to be restored. This is where ess_mixer_reload
+ * helps.
+ */
+void ess_mixer_reload (sb_devc *devc, int dev)
+{
+	int left, right, value;
+
+	value = devc->levels[dev];
+	left  = value & 0x000000ff;
+	right = (value & 0x0000ff00) >> 8;
+
+	sb_common_mixer_set(devc, dev, left, right);
+}
+
+static int es_rec_set_recmask(sb_devc * devc, int mask)
+{
+	int i, i_mask, cur_mask, diff_mask;
+	int value, left, right;
+
+#ifdef FKS_LOGGING
+printk (KERN_INFO "FKS: es_rec_set_recmask mask = %x\n", mask);
+#endif
+	/*
+	 * Changing the recmask on an ESS chip with recording mixer means:
+	 * (1) Find the differences
+	 * (2) For "turned-on"  inputs: make the recording level the playback level
+	 * (3) For "turned-off" inputs: make the recording level zero
+	 */
+	cur_mask  = devc->recmask;
+	diff_mask = (cur_mask ^ mask);
+
+	for (i = 0; i < 32; i++) {
+		i_mask = (1 << i);
+		if (diff_mask & i_mask) {	/* Difference? (1)  */
+			if (mask & i_mask) {	/* Turn it on  (2)  */
+				value = devc->levels[i];
+				left  = value & 0x000000ff;
+				right = (value & 0x0000ff00) >> 8;
+			} else {				/* Turn it off (3)  */
+				left  = 0;
+				right = 0;
+			}
+			sb_common_mixer_set(devc, i + ES_REC_MIXER_RECDIFF, left, right);
+		}
+	}
+	return mask;
+}
+
+int ess_set_recmask(sb_devc * devc, int *mask)
+{
+	/* This applies to ESS chips with record mixers only! */
+
+	if (ess_has_rec_mixer (devc->submodel)) {
+		*mask	= es_rec_set_recmask (devc, *mask);
+		return 1;									/* Applied		*/
+	} else {
+		return 0;									/* Not applied	*/
+	}
+}
+
+/*
+ * ess_mixer_reset must be called from sb_mixer_reset
+ */
+int ess_mixer_reset (sb_devc * devc)
+{
+	/*
+	 * Separate actions for ESS chips with a record mixer:
+	 */
+	if (ess_has_rec_mixer (devc->submodel)) {
+		switch (devc->submodel) {
+		case SUBMDL_ES1887:
+			/*
+			 * Separate actions for ES1887:
+			 * Change registers 7a and 1c to make the record mixer the
+			 * actual recording source.
+			 */
+			ess_chgmixer(devc, 0x7a, 0x18, 0x08);
+			ess_chgmixer(devc, 0x1c, 0x07, 0x07);
+			break;
+		};
+		/*
+		 * Call set_recmask for proper initialization
+		 */
+		devc->recmask = devc->supported_rec_devices;
+		es_rec_set_recmask(devc, 0);
+		devc->recmask = 0;
+
+		return 1;	/* We took care of recmask.				*/
+	} else {
+		return 0;	/* We didn't take care; caller do it	*/
+	}
+}
+
+/****************************************************************************
+ *																			*
+ *								ESS midi									*
+ *																			*
+ ****************************************************************************/
+
+/*
+ * FKS: IRQ may be shared. Hm. And if so? Then What?
+ */
+int ess_midi_init(sb_devc * devc, struct address_info *hw_config)
+{
+	unsigned char   cfg, tmp;
+
+	cfg = ess_getmixer (devc, 0x40) & 0x03;
+
+	if (devc->submodel < 8) {
+		ess_setmixer (devc, 0x40, cfg | 0x03);	/* Enable OPL3 & joystick */
+		return 0;  					 /* ES688 doesn't support MPU401 mode */
+	}
+	tmp = (hw_config->io_base & 0x0f0) >> 4;
+
+	if (tmp > 3) {
+		ess_setmixer (devc, 0x40, cfg);
+		return 0;
+	}
+	cfg |= tmp << 3;
+
+	tmp = 1;		/* MPU enabled without interrupts */
+
+	/* May be shared: if so the value is -ve */
+
+	switch (abs(hw_config->irq)) {
+		case 9:
+			tmp = 0x4;
+			break;
+		case 5:
+			tmp = 0x5;
+			break;
+		case 7:
+			tmp = 0x6;
+			break;
+		case 10:
+			tmp = 0x7;
+			break;
+		default:
+			return 0;
+	}
+
+	cfg |= tmp << 5;
+	ess_setmixer (devc, 0x40, cfg | 0x03);
+
+	return 1;
+}
+
diff --git a/linux/sound/oss/sb_ess.h b/linux/sound/oss/sb_ess.h
new file mode 100644
index 0000000..38aa072
--- /dev/null
+++ b/linux/sound/oss/sb_ess.h
@@ -0,0 +1,34 @@
+/*
+ * Created: 9-Jan-1999 Rolf Fokkens
+ */
+
+extern void ess_intr
+		(sb_devc *devc);
+extern int ess_dsp_init
+		(sb_devc *devc, struct address_info *hw_config);
+
+extern struct audio_driver *ess_audio_init
+		(sb_devc *devc, int *audio_flags, int *format_mask);
+extern int ess_midi_init
+		(sb_devc *devc, struct address_info *hw_config);
+extern void ess_mixer_init
+		(sb_devc *devc);
+
+extern int ess_init
+		(sb_devc *devc, struct address_info *hw_config);
+extern int ess_dsp_reset
+		(sb_devc *devc);
+
+extern void ess_setmixer
+		(sb_devc *devc, unsigned int port, unsigned int value);
+extern unsigned int ess_getmixer
+		(sb_devc *devc, unsigned int port);
+extern int ess_mixer_set
+		(sb_devc *devc, int dev, int left, int right);
+extern int ess_mixer_reset
+		(sb_devc *devc);
+extern void ess_mixer_reload
+		(sb_devc * devc, int dev);
+extern int ess_set_recmask
+		(sb_devc *devc, int *mask);
+
diff --git a/linux/sound/oss/sb_midi.c b/linux/sound/oss/sb_midi.c
new file mode 100644
index 0000000..f139028
--- /dev/null
+++ b/linux/sound/oss/sb_midi.c
@@ -0,0 +1,206 @@
+/*
+ * sound/oss/sb_midi.c
+ *
+ * The low level driver for the Sound Blaster DS chips.
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+
+#include "sound_config.h"
+
+#include "sb.h"
+#undef SB_TEST_IRQ
+
+/*
+ * The DSP channel can be used either for input or output. Variable
+ * 'sb_irq_mode' will be set when the program calls read or write first time
+ * after open. Current version doesn't support mode changes without closing
+ * and reopening the device. Support for this feature may be implemented in a
+ * future version of this driver.
+ */
+
+
+static int sb_midi_open(int dev, int mode,
+	     void            (*input) (int dev, unsigned char data),
+	     void            (*output) (int dev)
+)
+{
+	sb_devc *devc = midi_devs[dev]->devc;
+	unsigned long flags;
+
+	if (devc == NULL)
+		return -ENXIO;
+
+	spin_lock_irqsave(&devc->lock, flags);
+	if (devc->opened)
+	{
+		spin_unlock_irqrestore(&devc->lock, flags);
+		return -EBUSY;
+	}
+	devc->opened = 1;
+	spin_unlock_irqrestore(&devc->lock, flags);
+
+	devc->irq_mode = IMODE_MIDI;
+	devc->midi_broken = 0;
+
+	sb_dsp_reset(devc);
+
+	if (!sb_dsp_command(devc, 0x35))	/* Start MIDI UART mode */
+	{
+		  devc->opened = 0;
+		  return -EIO;
+	}
+	devc->intr_active = 1;
+
+	if (mode & OPEN_READ)
+	{
+		devc->input_opened = 1;
+		devc->midi_input_intr = input;
+	}
+	return 0;
+}
+
+static void sb_midi_close(int dev)
+{
+	sb_devc *devc = midi_devs[dev]->devc;
+	unsigned long flags;
+
+	if (devc == NULL)
+		return;
+
+	spin_lock_irqsave(&devc->lock, flags);
+	sb_dsp_reset(devc);
+	devc->intr_active = 0;
+	devc->input_opened = 0;
+	devc->opened = 0;
+	spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+static int sb_midi_out(int dev, unsigned char midi_byte)
+{
+	sb_devc *devc = midi_devs[dev]->devc;
+
+	if (devc == NULL)
+		return 1;
+
+	if (devc->midi_broken)
+		return 1;
+
+	if (!sb_dsp_command(devc, midi_byte))
+	{
+		devc->midi_broken = 1;
+		return 1;
+	}
+	return 1;
+}
+
+static int sb_midi_start_read(int dev)
+{
+	return 0;
+}
+
+static int sb_midi_end_read(int dev)
+{
+	sb_devc *devc = midi_devs[dev]->devc;
+
+	if (devc == NULL)
+		return -ENXIO;
+
+	sb_dsp_reset(devc);
+	devc->intr_active = 0;
+	return 0;
+}
+
+static int sb_midi_ioctl(int dev, unsigned cmd, void __user *arg)
+{
+        return -EINVAL;
+}
+
+void sb_midi_interrupt(sb_devc * devc)
+{
+	unsigned long   flags;
+	unsigned char   data;
+
+	if (devc == NULL)
+		return;
+
+	spin_lock_irqsave(&devc->lock, flags);
+
+	data = inb(DSP_READ);
+	if (devc->input_opened)
+		devc->midi_input_intr(devc->my_mididev, data);
+
+	spin_unlock_irqrestore(&devc->lock, flags);
+}
+
+#define MIDI_SYNTH_NAME	"Sound Blaster Midi"
+#define MIDI_SYNTH_CAPS	0
+#include "midi_synth.h"
+
+static struct midi_operations sb_midi_operations =
+{
+	.owner		= THIS_MODULE,
+	.info		= {"Sound Blaster", 0, 0, SNDCARD_SB},
+	.converter	= &std_midi_synth,
+	.in_info	= {0},
+	.open		= sb_midi_open,
+	.close		= sb_midi_close,
+	.ioctl		= sb_midi_ioctl,
+	.outputc	= sb_midi_out,
+	.start_read	= sb_midi_start_read,
+	.end_read	= sb_midi_end_read,
+};
+
+void sb_dsp_midi_init(sb_devc * devc, struct module *owner)
+{
+	int dev;
+
+	if (devc->model < 2)	/* No MIDI support for SB 1.x */
+		return;
+
+	dev = sound_alloc_mididev();
+
+	if (dev == -1)
+	{
+		printk(KERN_ERR "sb_midi: too many MIDI devices detected\n");
+		return;
+	}
+	std_midi_synth.midi_dev = devc->my_mididev = dev;
+	midi_devs[dev] = kmalloc(sizeof(struct midi_operations), GFP_KERNEL);
+	if (midi_devs[dev] == NULL)
+	{
+		printk(KERN_WARNING "Sound Blaster:  failed to allocate MIDI memory.\n");
+		sound_unload_mididev(dev);
+		  return;
+	}
+	memcpy((char *) midi_devs[dev], (char *) &sb_midi_operations,
+	       sizeof(struct midi_operations));
+
+	if (owner)
+			midi_devs[dev]->owner = owner;
+	
+	midi_devs[dev]->devc = devc;
+
+
+	midi_devs[dev]->converter = kmalloc(sizeof(struct synth_operations), GFP_KERNEL);
+	if (midi_devs[dev]->converter == NULL)
+	{
+		  printk(KERN_WARNING "Sound Blaster:  failed to allocate MIDI memory.\n");
+		  kfree(midi_devs[dev]);
+		  sound_unload_mididev(dev);
+		  return;
+	}
+	memcpy((char *) midi_devs[dev]->converter, (char *) &std_midi_synth,
+	       sizeof(struct synth_operations));
+
+	midi_devs[dev]->converter->id = "SBMIDI";
+	sequencer_init();
+}
diff --git a/linux/sound/oss/sb_mixer.c b/linux/sound/oss/sb_mixer.c
new file mode 100644
index 0000000..2039d31
--- /dev/null
+++ b/linux/sound/oss/sb_mixer.c
@@ -0,0 +1,770 @@
+/*
+ * sound/oss/sb_mixer.c
+ *
+ * The low level mixer driver for the Sound Blaster compatible cards.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer				: ioctl code reworked (vmalloc/vfree removed)
+ * Rolf Fokkens (Dec 20 1998)	: Moved ESS stuff into sb_ess.[ch]
+ * Stanislav Voronyi <stas@esc.kharkov.com>	: Support for AWE 3DSE device (Jun 7 1999)
+ */
+
+#include <linux/slab.h>
+
+#include "sound_config.h"
+
+#define __SB_MIXER_C__
+
+#include "sb.h"
+#include "sb_mixer.h"
+
+#include "sb_ess.h"
+
+#define SBPRO_RECORDING_DEVICES	(SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD)
+
+/* Same as SB Pro, unless I find otherwise */
+#define SGNXPRO_RECORDING_DEVICES SBPRO_RECORDING_DEVICES
+
+#define SBPRO_MIXER_DEVICES		(SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | \
+					 SOUND_MASK_CD | SOUND_MASK_VOLUME)
+
+/* SG NX Pro has treble and bass settings on the mixer. The 'speaker'
+ * channel is the COVOX/DisneySoundSource emulation volume control
+ * on the mixer. It does NOT control speaker volume. Should have own
+ * mask eventually?
+ */
+#define SGNXPRO_MIXER_DEVICES	(SBPRO_MIXER_DEVICES|SOUND_MASK_BASS| \
+				 SOUND_MASK_TREBLE|SOUND_MASK_SPEAKER )
+
+#define SB16_RECORDING_DEVICES		(SOUND_MASK_SYNTH | SOUND_MASK_LINE | SOUND_MASK_MIC | \
+					 SOUND_MASK_CD)
+
+#define SB16_OUTFILTER_DEVICES		(SOUND_MASK_LINE | SOUND_MASK_MIC | \
+					 SOUND_MASK_CD)
+
+#define SB16_MIXER_DEVICES		(SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
+					 SOUND_MASK_CD | \
+					 SOUND_MASK_IGAIN | SOUND_MASK_OGAIN | \
+					 SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | \
+					SOUND_MASK_IMIX)
+
+/* These are the only devices that are working at the moment.  Others could
+ * be added once they are identified and a method is found to control them.
+ */
+#define ALS007_MIXER_DEVICES	(SOUND_MASK_SYNTH | SOUND_MASK_LINE | \
+				 SOUND_MASK_PCM | SOUND_MASK_MIC | \
+				 SOUND_MASK_CD | \
+				 SOUND_MASK_VOLUME)
+
+static mixer_tab sbpro_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME,	0x22, 7, 4, 0x22, 3, 4),
+MIX_ENT(SOUND_MIXER_BASS,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH,	0x26, 7, 4, 0x26, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM,	0x04, 7, 4, 0x04, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE,	0x2e, 7, 4, 0x2e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC,	0x0a, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_CD,		0x28, 7, 4, 0x28, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV,	0x00, 0, 0, 0x00, 0, 0)
+};
+
+static mixer_tab sb16_mix = {
+MIX_ENT(SOUND_MIXER_VOLUME,	0x30, 7, 5, 0x31, 7, 5),
+MIX_ENT(SOUND_MIXER_BASS,	0x46, 7, 4, 0x47, 7, 4),
+MIX_ENT(SOUND_MIXER_TREBLE,	0x44, 7, 4, 0x45, 7, 4),
+MIX_ENT(SOUND_MIXER_SYNTH,	0x34, 7, 5, 0x35, 7, 5),
+MIX_ENT(SOUND_MIXER_PCM,	0x32, 7, 5, 0x33, 7, 5),
+MIX_ENT(SOUND_MIXER_SPEAKER,	0x3b, 7, 2, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE,	0x38, 7, 5, 0x39, 7, 5),
+MIX_ENT(SOUND_MIXER_MIC,	0x3a, 7, 5, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_CD,		0x36, 7, 5, 0x37, 7, 5),
+MIX_ENT(SOUND_MIXER_IMIX,	0x3c, 0, 1, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV,	0x3f, 7, 2, 0x40, 7, 2), /* Obsolete. Use IGAIN */
+MIX_ENT(SOUND_MIXER_IGAIN,	0x3f, 7, 2, 0x40, 7, 2),
+MIX_ENT(SOUND_MIXER_OGAIN,	0x41, 7, 2, 0x42, 7, 2)
+};
+
+static mixer_tab als007_mix = 
+{
+MIX_ENT(SOUND_MIXER_VOLUME,	0x62, 7, 4, 0x62, 3, 4),
+MIX_ENT(SOUND_MIXER_BASS,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_TREBLE,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_SYNTH,	0x66, 7, 4, 0x66, 3, 4),
+MIX_ENT(SOUND_MIXER_PCM,	0x64, 7, 4, 0x64, 3, 4),
+MIX_ENT(SOUND_MIXER_SPEAKER,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_LINE,	0x6e, 7, 4, 0x6e, 3, 4),
+MIX_ENT(SOUND_MIXER_MIC,	0x6a, 2, 3, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_CD,		0x68, 7, 4, 0x68, 3, 4),
+MIX_ENT(SOUND_MIXER_IMIX,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_ALTPCM,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_RECLEV,	0x00, 0, 0, 0x00, 0, 0), /* Obsolete. Use IGAIN */
+MIX_ENT(SOUND_MIXER_IGAIN,	0x00, 0, 0, 0x00, 0, 0),
+MIX_ENT(SOUND_MIXER_OGAIN,	0x00, 0, 0, 0x00, 0, 0)
+};
+
+
+/* SM_GAMES          Master volume is lower and PCM & FM volumes
+			     higher than with SB Pro. This improves the
+			     sound quality */
+
+static int smg_default_levels[32] =
+{
+  0x2020,			/* Master Volume */
+  0x4b4b,			/* Bass */
+  0x4b4b,			/* Treble */
+  0x6464,			/* FM */
+  0x6464,			/* PCM */
+  0x4b4b,			/* PC Speaker */
+  0x4b4b,			/* Ext Line */
+  0x0000,			/* Mic */
+  0x4b4b,			/* CD */
+  0x4b4b,			/* Recording monitor */
+  0x4b4b,			/* SB PCM */
+  0x4b4b,			/* Recording level */
+  0x4b4b,			/* Input gain */
+  0x4b4b,			/* Output gain */
+  0x4040,			/* Line1 */
+  0x4040,			/* Line2 */
+  0x1515			/* Line3 */
+};
+
+static int sb_default_levels[32] =
+{
+  0x5a5a,			/* Master Volume */
+  0x4b4b,			/* Bass */
+  0x4b4b,			/* Treble */
+  0x4b4b,			/* FM */
+  0x4b4b,			/* PCM */
+  0x4b4b,			/* PC Speaker */
+  0x4b4b,			/* Ext Line */
+  0x1010,			/* Mic */
+  0x4b4b,			/* CD */
+  0x0000,			/* Recording monitor */
+  0x4b4b,			/* SB PCM */
+  0x4b4b,			/* Recording level */
+  0x4b4b,			/* Input gain */
+  0x4b4b,			/* Output gain */
+  0x4040,			/* Line1 */
+  0x4040,			/* Line2 */
+  0x1515			/* Line3 */
+};
+
+static unsigned char sb16_recmasks_L[SOUND_MIXER_NRDEVICES] =
+{
+	0x00,	/* SOUND_MIXER_VOLUME	*/
+	0x00,	/* SOUND_MIXER_BASS	*/
+	0x00,	/* SOUND_MIXER_TREBLE	*/
+	0x40,	/* SOUND_MIXER_SYNTH	*/
+	0x00,	/* SOUND_MIXER_PCM	*/
+	0x00,	/* SOUND_MIXER_SPEAKER	*/
+	0x10,	/* SOUND_MIXER_LINE	*/
+	0x01,	/* SOUND_MIXER_MIC	*/
+	0x04,	/* SOUND_MIXER_CD	*/
+	0x00,	/* SOUND_MIXER_IMIX	*/
+	0x00,	/* SOUND_MIXER_ALTPCM	*/
+	0x00,	/* SOUND_MIXER_RECLEV	*/
+	0x00,	/* SOUND_MIXER_IGAIN	*/
+	0x00	/* SOUND_MIXER_OGAIN	*/
+};
+
+static unsigned char sb16_recmasks_R[SOUND_MIXER_NRDEVICES] =
+{
+	0x00,	/* SOUND_MIXER_VOLUME	*/
+	0x00,	/* SOUND_MIXER_BASS	*/
+	0x00,	/* SOUND_MIXER_TREBLE	*/
+	0x20,	/* SOUND_MIXER_SYNTH	*/
+	0x00,	/* SOUND_MIXER_PCM	*/
+	0x00,	/* SOUND_MIXER_SPEAKER	*/
+	0x08,	/* SOUND_MIXER_LINE	*/
+	0x01,	/* SOUND_MIXER_MIC	*/
+	0x02,	/* SOUND_MIXER_CD	*/
+	0x00,	/* SOUND_MIXER_IMIX	*/
+	0x00,	/* SOUND_MIXER_ALTPCM	*/
+	0x00,	/* SOUND_MIXER_RECLEV	*/
+	0x00,	/* SOUND_MIXER_IGAIN	*/
+	0x00	/* SOUND_MIXER_OGAIN	*/
+};
+
+static char     smw_mix_regs[] =	/* Left mixer registers */
+{
+  0x0b,				/* SOUND_MIXER_VOLUME */
+  0x0d,				/* SOUND_MIXER_BASS */
+  0x0d,				/* SOUND_MIXER_TREBLE */
+  0x05,				/* SOUND_MIXER_SYNTH */
+  0x09,				/* SOUND_MIXER_PCM */
+  0x00,				/* SOUND_MIXER_SPEAKER */
+  0x03,				/* SOUND_MIXER_LINE */
+  0x01,				/* SOUND_MIXER_MIC */
+  0x07,				/* SOUND_MIXER_CD */
+  0x00,				/* SOUND_MIXER_IMIX */
+  0x00,				/* SOUND_MIXER_ALTPCM */
+  0x00,				/* SOUND_MIXER_RECLEV */
+  0x00,				/* SOUND_MIXER_IGAIN */
+  0x00,				/* SOUND_MIXER_OGAIN */
+  0x00,				/* SOUND_MIXER_LINE1 */
+  0x00,				/* SOUND_MIXER_LINE2 */
+  0x00				/* SOUND_MIXER_LINE3 */
+};
+
+static int      sbmixnum = 1;
+
+static void     sb_mixer_reset(sb_devc * devc);
+
+void sb_mixer_set_stereo(sb_devc * devc, int mode)
+{
+	sb_chgmixer(devc, OUT_FILTER, STEREO_DAC, (mode ? STEREO_DAC : MONO_DAC));
+}
+
+static int detect_mixer(sb_devc * devc)
+{
+	/* Just trust the mixer is there */
+	return 1;
+}
+
+static void change_bits(sb_devc * devc, unsigned char *regval, int dev, int chn, int newval)
+{
+	unsigned char mask;
+	int shift;
+
+	mask = (1 << (*devc->iomap)[dev][chn].nbits) - 1;
+	newval = (int) ((newval * mask) + 50) / 100;	/* Scale */
+
+	shift = (*devc->iomap)[dev][chn].bitoffs - (*devc->iomap)[dev][LEFT_CHN].nbits + 1;
+
+	*regval &= ~(mask << shift);	/* Mask out previous value */
+	*regval |= (newval & mask) << shift;	/* Set the new value */
+}
+
+static int sb_mixer_get(sb_devc * devc, int dev)
+{
+	if (!((1 << dev) & devc->supported_devices))
+		return -EINVAL;
+	return devc->levels[dev];
+}
+
+void smw_mixer_init(sb_devc * devc)
+{
+	int i;
+
+	sb_setmixer(devc, 0x00, 0x18);	/* Mute unused (Telephone) line */
+	sb_setmixer(devc, 0x10, 0x38);	/* Config register 2 */
+
+	devc->supported_devices = 0;
+	for (i = 0; i < sizeof(smw_mix_regs); i++)
+		if (smw_mix_regs[i] != 0)
+			devc->supported_devices |= (1 << i);
+
+	devc->supported_rec_devices = devc->supported_devices &
+		~(SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_PCM | SOUND_MASK_VOLUME);
+	sb_mixer_reset(devc);
+}
+
+int sb_common_mixer_set(sb_devc * devc, int dev, int left, int right)
+{
+	int regoffs;
+	unsigned char val;
+
+	if ((dev < 0) || (dev >= devc->iomap_sz))
+		return -EINVAL;
+
+	regoffs = (*devc->iomap)[dev][LEFT_CHN].regno;
+
+	if (regoffs == 0)
+		return -EINVAL;
+
+	val = sb_getmixer(devc, regoffs);
+	change_bits(devc, &val, dev, LEFT_CHN, left);
+
+	if ((*devc->iomap)[dev][RIGHT_CHN].regno != regoffs)	/*
+								 * Change register
+								 */
+	{
+		sb_setmixer(devc, regoffs, val);	/*
+							 * Save the old one
+							 */
+		regoffs = (*devc->iomap)[dev][RIGHT_CHN].regno;
+
+		if (regoffs == 0)
+			return left | (left << 8);	/*
+							 * Just left channel present
+							 */
+
+		val = sb_getmixer(devc, regoffs);	/*
+							 * Read the new one
+							 */
+	}
+	change_bits(devc, &val, dev, RIGHT_CHN, right);
+
+	sb_setmixer(devc, regoffs, val);
+
+	return left | (right << 8);
+}
+
+static int smw_mixer_set(sb_devc * devc, int dev, int left, int right)
+{
+	int reg, val;
+
+	switch (dev)
+	{
+		case SOUND_MIXER_VOLUME:
+			sb_setmixer(devc, 0x0b, 96 - (96 * left / 100));	/* 96=mute, 0=max */
+			sb_setmixer(devc, 0x0c, 96 - (96 * right / 100));
+			break;
+
+		case SOUND_MIXER_BASS:
+		case SOUND_MIXER_TREBLE:
+			devc->levels[dev] = left | (right << 8);
+			/* Set left bass and treble values */
+			val = ((devc->levels[SOUND_MIXER_TREBLE] & 0xff) * 16 / (unsigned) 100) << 4;
+			val |= ((devc->levels[SOUND_MIXER_BASS] & 0xff) * 16 / (unsigned) 100) & 0x0f;
+			sb_setmixer(devc, 0x0d, val);
+
+			/* Set right bass and treble values */
+			val = (((devc->levels[SOUND_MIXER_TREBLE] >> 8) & 0xff) * 16 / (unsigned) 100) << 4;
+			val |= (((devc->levels[SOUND_MIXER_BASS] >> 8) & 0xff) * 16 / (unsigned) 100) & 0x0f;
+			sb_setmixer(devc, 0x0e, val);
+		
+			break;
+
+		default:
+			/* bounds check */
+			if (dev < 0 || dev >= ARRAY_SIZE(smw_mix_regs))
+				return -EINVAL;
+			reg = smw_mix_regs[dev];
+			if (reg == 0)
+				return -EINVAL;
+			sb_setmixer(devc, reg, (24 - (24 * left / 100)) | 0x20);	/* 24=mute, 0=max */
+			sb_setmixer(devc, reg + 1, (24 - (24 * right / 100)) | 0x40);
+	}
+
+	devc->levels[dev] = left | (right << 8);
+	return left | (right << 8);
+}
+
+static int sb_mixer_set(sb_devc * devc, int dev, int value)
+{
+	int left = value & 0x000000ff;
+	int right = (value & 0x0000ff00) >> 8;
+	int retval;
+
+	if (left > 100)
+		left = 100;
+	if (right > 100)
+		right = 100;
+
+	if ((dev < 0) || (dev > 31))
+		return -EINVAL;
+
+	if (!(devc->supported_devices & (1 << dev)))	/*
+							 * Not supported
+							 */
+		return -EINVAL;
+
+	/* Differentiate depending on the chipsets */
+	switch (devc->model) {
+	case MDL_SMW:
+		retval = smw_mixer_set(devc, dev, left, right);
+		break;
+	case MDL_ESS:
+		retval = ess_mixer_set(devc, dev, left, right);
+		break;
+	default:
+		retval = sb_common_mixer_set(devc, dev, left, right);
+	}
+	if (retval >= 0) devc->levels[dev] = retval;
+
+	return retval;
+}
+
+/*
+ * set_recsrc doesn't apply to ES188x
+ */
+static void set_recsrc(sb_devc * devc, int src)
+{
+	sb_setmixer(devc, RECORD_SRC, (sb_getmixer(devc, RECORD_SRC) & ~7) | (src & 0x7));
+}
+
+static int set_recmask(sb_devc * devc, int mask)
+{
+	int devmask, i;
+	unsigned char  regimageL, regimageR;
+
+	devmask = mask & devc->supported_rec_devices;
+
+	switch (devc->model)
+	{
+		case MDL_SBPRO:
+		case MDL_ESS:
+		case MDL_JAZZ:
+		case MDL_SMW:
+			if (devc->model == MDL_ESS && ess_set_recmask (devc, &devmask)) {
+				break;
+			};
+			if (devmask != SOUND_MASK_MIC &&
+				devmask != SOUND_MASK_LINE &&
+				devmask != SOUND_MASK_CD)
+			{
+				/*
+				 * More than one device selected. Drop the
+				 * previous selection
+				 */
+				devmask &= ~devc->recmask;
+			}
+			if (devmask != SOUND_MASK_MIC &&
+				devmask != SOUND_MASK_LINE &&
+				devmask != SOUND_MASK_CD)
+			{
+				/*
+				 * More than one device selected. Default to
+				 * mic
+				 */
+				devmask = SOUND_MASK_MIC;
+			}
+			if (devmask ^ devc->recmask)	/*
+							 *	Input source changed
+							 */
+			{
+				switch (devmask)
+				{
+					case SOUND_MASK_MIC:
+						set_recsrc(devc, SRC__MIC);
+						break;
+
+					case SOUND_MASK_LINE:
+						set_recsrc(devc, SRC__LINE);
+						break;
+
+					case SOUND_MASK_CD:
+						set_recsrc(devc, SRC__CD);
+						break;
+
+					default:
+						set_recsrc(devc, SRC__MIC);
+				}
+			}
+			break;
+
+		case MDL_SB16:
+			if (!devmask)
+				devmask = SOUND_MASK_MIC;
+
+			if (devc->submodel == SUBMDL_ALS007) 
+			{
+				switch (devmask) 
+				{
+					case SOUND_MASK_LINE:
+						sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_LINE);
+						break;
+					case SOUND_MASK_CD:
+						sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_CD);
+						break;
+					case SOUND_MASK_SYNTH:
+						sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_SYNTH);
+						break;
+					default:           /* Also takes care of SOUND_MASK_MIC case */
+						sb_setmixer(devc, ALS007_RECORD_SRC, ALS007_MIC);
+						break;
+				}
+			}
+			else
+			{
+				regimageL = regimageR = 0;
+				for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+				{
+					if ((1 << i) & devmask)
+					{
+						regimageL |= sb16_recmasks_L[i];
+						regimageR |= sb16_recmasks_R[i];
+					}
+					sb_setmixer (devc, SB16_IMASK_L, regimageL);
+					sb_setmixer (devc, SB16_IMASK_R, regimageR);
+				}
+			}
+			break;
+	}
+	devc->recmask = devmask;
+	return devc->recmask;
+}
+
+static int set_outmask(sb_devc * devc, int mask)
+{
+	int devmask, i;
+	unsigned char  regimage;
+
+	devmask = mask & devc->supported_out_devices;
+
+	switch (devc->model)
+	{
+		case MDL_SB16:
+			if (devc->submodel == SUBMDL_ALS007) 
+				break;
+			else
+			{
+				regimage = 0;
+				for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+				{
+					if ((1 << i) & devmask)
+					{
+						regimage |= (sb16_recmasks_L[i] | sb16_recmasks_R[i]);
+					}
+					sb_setmixer (devc, SB16_OMASK, regimage);
+				}
+			}
+			break;
+		default:
+			break;
+	}
+
+	devc->outmask = devmask;
+	return devc->outmask;
+}
+
+static int sb_mixer_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+	sb_devc *devc = mixer_devs[dev]->devc;
+	int val, ret;
+	int __user *p = arg;
+
+	/*
+	 * Use ioctl(fd, SOUND_MIXER_AGC, &mode) to turn AGC off (0) or on (1).
+	 * Use ioctl(fd, SOUND_MIXER_3DSE, &mode) to turn 3DSE off (0) or on (1)
+	 *					      or mode==2 put 3DSE state to mode.
+	 */
+	if (devc->model == MDL_SB16) {
+		if (cmd == SOUND_MIXER_AGC) 
+		{
+			if (get_user(val, p))
+				return -EFAULT;
+			sb_setmixer(devc, 0x43, (~val) & 0x01);
+			return 0;
+		}
+		if (cmd == SOUND_MIXER_3DSE) 
+		{
+			/* I put here 15, but I don't know the exact version.
+			   At least my 4.13 havn't 3DSE, 4.16 has it. */
+			if (devc->minor < 15)
+				return -EINVAL;
+			if (get_user(val, p))
+				return -EFAULT;
+			if (val == 0 || val == 1)
+				sb_chgmixer(devc, AWE_3DSE, 0x01, val);
+			else if (val == 2)
+			{
+				ret = sb_getmixer(devc, AWE_3DSE)&0x01;
+				return put_user(ret, p);
+			}
+			else
+				return -EINVAL;
+			return 0;
+		}
+	}
+	if (((cmd >> 8) & 0xff) == 'M') 
+	{
+		if (_SIOC_DIR(cmd) & _SIOC_WRITE) 
+		{
+			if (get_user(val, p))
+				return -EFAULT;
+			switch (cmd & 0xff) 
+			{
+				case SOUND_MIXER_RECSRC:
+					ret = set_recmask(devc, val);
+					break;
+
+				case SOUND_MIXER_OUTSRC:
+					ret = set_outmask(devc, val);
+					break;
+
+				default:
+					ret = sb_mixer_set(devc, cmd & 0xff, val);
+			}
+		}
+		else switch (cmd & 0xff) 
+		{
+			case SOUND_MIXER_RECSRC:
+				ret = devc->recmask;
+				break;
+				  
+			case SOUND_MIXER_OUTSRC:
+				ret = devc->outmask;
+				break;
+				  
+			case SOUND_MIXER_DEVMASK:
+				ret = devc->supported_devices;
+				break;
+				  
+			case SOUND_MIXER_STEREODEVS:
+				ret = devc->supported_devices;
+				/* The ESS seems to have stereo mic controls */
+				if (devc->model == MDL_ESS)
+					ret &= ~(SOUND_MASK_SPEAKER|SOUND_MASK_IMIX);
+				else if (devc->model != MDL_JAZZ && devc->model != MDL_SMW)
+					ret &= ~(SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_IMIX);
+				break;
+				  
+			case SOUND_MIXER_RECMASK:
+				ret = devc->supported_rec_devices;
+				break;
+				  
+			case SOUND_MIXER_OUTMASK:
+				ret = devc->supported_out_devices;
+				break;
+				  
+			case SOUND_MIXER_CAPS:
+				ret = devc->mixer_caps;
+				break;
+				    
+			default:
+				ret = sb_mixer_get(devc, cmd & 0xff);
+				break;
+		}
+		return put_user(ret, p); 
+	} else
+		return -EINVAL;
+}
+
+static struct mixer_operations sb_mixer_operations =
+{
+	.owner	= THIS_MODULE,
+	.id	= "SB",
+	.name	= "Sound Blaster",
+	.ioctl	= sb_mixer_ioctl
+};
+
+static struct mixer_operations als007_mixer_operations =
+{
+	.owner	= THIS_MODULE,
+	.id	= "ALS007",
+	.name	= "Avance ALS-007",
+	.ioctl	= sb_mixer_ioctl
+};
+
+static void sb_mixer_reset(sb_devc * devc)
+{
+	char name[32];
+	int i;
+
+	sprintf(name, "SB_%d", devc->sbmixnum);
+
+	if (devc->sbmo.sm_games)
+		devc->levels = load_mixer_volumes(name, smg_default_levels, 1);
+	else
+		devc->levels = load_mixer_volumes(name, sb_default_levels, 1);
+
+	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+		sb_mixer_set(devc, i, devc->levels[i]);
+
+	if (devc->model != MDL_ESS || !ess_mixer_reset (devc)) {
+		set_recmask(devc, SOUND_MASK_MIC);
+	};
+}
+
+int sb_mixer_init(sb_devc * devc, struct module *owner)
+{
+	int mixer_type = 0;
+	int m;
+
+	devc->sbmixnum = sbmixnum++;
+	devc->levels = NULL;
+
+	sb_setmixer(devc, 0x00, 0);	/* Reset mixer */
+
+	if (!(mixer_type = detect_mixer(devc)))
+		return 0;	/* No mixer. Why? */
+
+	switch (devc->model)
+	{
+		case MDL_ESSPCI:
+		case MDL_YMPCI:
+		case MDL_SBPRO:
+		case MDL_AZTECH:
+		case MDL_JAZZ:
+			devc->mixer_caps = SOUND_CAP_EXCL_INPUT;
+			devc->supported_devices = SBPRO_MIXER_DEVICES;
+			devc->supported_rec_devices = SBPRO_RECORDING_DEVICES;
+			devc->iomap = &sbpro_mix;
+			devc->iomap_sz = ARRAY_SIZE(sbpro_mix);
+			break;
+
+		case MDL_ESS:
+			ess_mixer_init (devc);
+			break;
+
+		case MDL_SMW:
+			devc->mixer_caps = SOUND_CAP_EXCL_INPUT;
+			devc->supported_devices = 0;
+			devc->supported_rec_devices = 0;
+			devc->iomap = &sbpro_mix;
+			devc->iomap_sz = ARRAY_SIZE(sbpro_mix);
+			smw_mixer_init(devc);
+			break;
+
+		case MDL_SB16:
+			devc->mixer_caps = 0;
+			devc->supported_rec_devices = SB16_RECORDING_DEVICES;
+			devc->supported_out_devices = SB16_OUTFILTER_DEVICES;
+			if (devc->submodel != SUBMDL_ALS007)
+			{
+				devc->supported_devices = SB16_MIXER_DEVICES;
+				devc->iomap = &sb16_mix;
+				devc->iomap_sz = ARRAY_SIZE(sb16_mix);
+			}
+			else
+			{
+				devc->supported_devices = ALS007_MIXER_DEVICES;
+				devc->iomap = &als007_mix;
+				devc->iomap_sz = ARRAY_SIZE(als007_mix);
+			}
+			break;
+
+		default:
+			printk(KERN_WARNING "sb_mixer: Unsupported mixer type %d\n", devc->model);
+			return 0;
+	}
+
+	m = sound_alloc_mixerdev();
+	if (m == -1)
+		return 0;
+
+	mixer_devs[m] = kmalloc(sizeof(struct mixer_operations), GFP_KERNEL);
+	if (mixer_devs[m] == NULL)
+	{
+		printk(KERN_ERR "sb_mixer: Can't allocate memory\n");
+		sound_unload_mixerdev(m);
+		return 0;
+	}
+
+	if (devc->submodel != SUBMDL_ALS007)
+		memcpy ((char *) mixer_devs[m], (char *) &sb_mixer_operations, sizeof (struct mixer_operations));
+	else
+		memcpy ((char *) mixer_devs[m], (char *) &als007_mixer_operations, sizeof (struct mixer_operations));
+
+	mixer_devs[m]->devc = devc;
+
+	if (owner)
+			 mixer_devs[m]->owner = owner;
+	
+	devc->my_mixerdev = m;
+	sb_mixer_reset(devc);
+	return 1;
+}
+
+void sb_mixer_unload(sb_devc *devc)
+{
+	if (devc->my_mixerdev == -1)
+		return;
+
+	kfree(mixer_devs[devc->my_mixerdev]);
+	sound_unload_mixerdev(devc->my_mixerdev);
+	sbmixnum--;
+}
diff --git a/linux/sound/oss/sb_mixer.h b/linux/sound/oss/sb_mixer.h
new file mode 100644
index 0000000..4b9425f
--- /dev/null
+++ b/linux/sound/oss/sb_mixer.h
@@ -0,0 +1,105 @@
+/*
+ * sound/oss/sb_mixer.h
+ * 
+ * Definitions for the SB Pro and SB16 mixers
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+/*
+ * Modified:
+ *	Hunyue Yau	Jan 6 1994
+ *	Added defines for the Sound Galaxy NX Pro mixer.
+ *
+ *	Rolf Fokkens	Dec 20 1998
+ *	Added defines for some ES188x chips.
+ *
+ *	Rolf Fokkens	Dec 27 1998
+ *	Moved static stuff to sb_mixer.c
+ *
+ */
+/*
+ * Mixer registers
+ * 
+ * NOTE!	RECORD_SRC == IN_FILTER
+ */
+
+/* 
+ * Mixer registers of SB Pro
+ */
+#define VOC_VOL		0x04
+#define MIC_VOL		0x0A
+#define MIC_MIX		0x0A
+#define RECORD_SRC	0x0C
+#define IN_FILTER	0x0C
+#define OUT_FILTER	0x0E
+#define MASTER_VOL	0x22
+#define FM_VOL		0x26
+#define CD_VOL		0x28
+#define LINE_VOL	0x2E
+#define IRQ_NR		0x80
+#define DMA_NR		0x81
+#define IRQ_STAT	0x82
+#define OPSW		0x3c
+
+/*
+ * Additional registers on the SG NX Pro 
+ */
+#define COVOX_VOL	0x42
+#define TREBLE_LVL	0x44
+#define BASS_LVL	0x46
+
+#define FREQ_HI         (1 << 3)/* Use High-frequency ANFI filters */
+#define FREQ_LOW        0	/* Use Low-frequency ANFI filters */
+#define FILT_ON         0	/* Yes, 0 to turn it on, 1 for off */
+#define FILT_OFF        (1 << 5)
+
+#define MONO_DAC	0x00
+#define STEREO_DAC	0x02
+
+/*
+ * Mixer registers of SB16
+ */
+#define SB16_OMASK	0x3c
+#define SB16_IMASK_L	0x3d
+#define SB16_IMASK_R	0x3e
+
+#define LEFT_CHN	0
+#define RIGHT_CHN	1
+
+/*
+ * 3DSE register of AWE32/64
+ */
+#define AWE_3DSE	0x90
+
+/*
+ * Mixer registers of ALS007
+ */
+#define ALS007_RECORD_SRC	0x6c
+#define ALS007_OUTPUT_CTRL1	0x3c
+#define ALS007_OUTPUT_CTRL2	0x4c
+
+#define MIX_ENT(name, reg_l, bit_l, len_l, reg_r, bit_r, len_r)	\
+	{{reg_l, bit_l, len_l}, {reg_r, bit_r, len_r}}
+
+/*
+ *	Recording sources (SB Pro)
+ */
+
+#define SRC__MIC         1	/* Select Microphone recording source */
+#define SRC__CD          3	/* Select CD recording source */
+#define SRC__LINE        7	/* Use Line-in for recording source */
+
+/*
+ *	Recording sources for ALS-007
+ */
+
+#define ALS007_MIC	4
+#define ALS007_LINE	6
+#define ALS007_CD	2
+#define ALS007_SYNTH	7
diff --git a/linux/sound/oss/sequencer.c b/linux/sound/oss/sequencer.c
new file mode 100644
index 0000000..5ea1098
--- /dev/null
+++ b/linux/sound/oss/sequencer.c
@@ -0,0 +1,1671 @@
+/*
+ * sound/oss/sequencer.c
+ *
+ * The sequencer personality manager.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer   : ioctl code reworked (vmalloc/vfree removed)
+ * Alan Cox	   : reformatted and fixed a pair of null pointer bugs
+ */
+#include <linux/kmod.h>
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+#include "midi_ctrl.h"
+
+static int      sequencer_ok;
+static struct sound_timer_operations *tmr;
+static int      tmr_no = -1;	/* Currently selected timer */
+static int      pending_timer = -1;	/* For timer change operation */
+extern unsigned long seq_time;
+
+static int      obsolete_api_used;
+static DEFINE_SPINLOCK(lock);
+
+/*
+ * Local counts for number of synth and MIDI devices. These are initialized
+ * by the sequencer_open.
+ */
+static int      max_mididev;
+static int      max_synthdev;
+
+/*
+ * The seq_mode gives the operating mode of the sequencer:
+ *      1 = level1 (the default)
+ *      2 = level2 (extended capabilities)
+ */
+
+#define SEQ_1	1
+#define SEQ_2	2
+static int      seq_mode = SEQ_1;
+
+static DECLARE_WAIT_QUEUE_HEAD(seq_sleeper);
+static DECLARE_WAIT_QUEUE_HEAD(midi_sleeper);
+
+static int      midi_opened[MAX_MIDI_DEV];
+
+static int      midi_written[MAX_MIDI_DEV];
+
+static unsigned long prev_input_time;
+static int      prev_event_time;
+
+#include "tuning.h"
+
+#define EV_SZ	8
+#define IEV_SZ	8
+
+static unsigned char *queue;
+static unsigned char *iqueue;
+
+static volatile int qhead, qtail, qlen;
+static volatile int iqhead, iqtail, iqlen;
+static volatile int seq_playing;
+static volatile int sequencer_busy;
+static int      output_threshold;
+static long     pre_event_timeout;
+static unsigned synth_open_mask;
+
+static int      seq_queue(unsigned char *note, char nonblock);
+static void     seq_startplay(void);
+static int      seq_sync(void);
+static void     seq_reset(void);
+
+#if MAX_SYNTH_DEV > 15
+#error Too many synthesizer devices enabled.
+#endif
+
+int sequencer_read(int dev, struct file *file, char __user *buf, int count)
+{
+	int c = count, p = 0;
+	int ev_len;
+	unsigned long flags;
+
+	dev = dev >> 4;
+
+	ev_len = seq_mode == SEQ_1 ? 4 : 8;
+
+	spin_lock_irqsave(&lock,flags);
+
+	if (!iqlen)
+	{
+		spin_unlock_irqrestore(&lock,flags);
+ 		if (file->f_flags & O_NONBLOCK) {
+  			return -EAGAIN;
+  		}
+
+ 		interruptible_sleep_on_timeout(&midi_sleeper,
+					       pre_event_timeout);
+		spin_lock_irqsave(&lock,flags);
+		if (!iqlen)
+		{
+			spin_unlock_irqrestore(&lock,flags);
+			return 0;
+		}
+	}
+	while (iqlen && c >= ev_len)
+	{
+		char *fixit = (char *) &iqueue[iqhead * IEV_SZ];
+		spin_unlock_irqrestore(&lock,flags);
+		if (copy_to_user(&(buf)[p], fixit, ev_len))
+			return count - c;
+		p += ev_len;
+		c -= ev_len;
+
+		spin_lock_irqsave(&lock,flags);
+		iqhead = (iqhead + 1) % SEQ_MAX_QUEUE;
+		iqlen--;
+	}
+	spin_unlock_irqrestore(&lock,flags);
+	return count - c;
+}
+
+static void sequencer_midi_output(int dev)
+{
+	/*
+	 * Currently NOP
+	 */
+}
+
+void seq_copy_to_input(unsigned char *event_rec, int len)
+{
+	unsigned long flags;
+
+	/*
+	 * Verify that the len is valid for the current mode.
+	 */
+
+	if (len != 4 && len != 8)
+		return;
+	if ((seq_mode == SEQ_1) != (len == 4))
+		return;
+
+	if (iqlen >= (SEQ_MAX_QUEUE - 1))
+		return;		/* Overflow */
+
+	spin_lock_irqsave(&lock,flags);
+	memcpy(&iqueue[iqtail * IEV_SZ], event_rec, len);
+	iqlen++;
+	iqtail = (iqtail + 1) % SEQ_MAX_QUEUE;
+	wake_up(&midi_sleeper);
+	spin_unlock_irqrestore(&lock,flags);
+}
+EXPORT_SYMBOL(seq_copy_to_input);
+
+static void sequencer_midi_input(int dev, unsigned char data)
+{
+	unsigned int tstamp;
+	unsigned char event_rec[4];
+
+	if (data == 0xfe)	/* Ignore active sensing */
+		return;
+
+	tstamp = jiffies - seq_time;
+
+	if (tstamp != prev_input_time)
+	{
+		tstamp = (tstamp << 8) | SEQ_WAIT;
+		seq_copy_to_input((unsigned char *) &tstamp, 4);
+		prev_input_time = tstamp;
+	}
+	event_rec[0] = SEQ_MIDIPUTC;
+	event_rec[1] = data;
+	event_rec[2] = dev;
+	event_rec[3] = 0;
+
+	seq_copy_to_input(event_rec, 4);
+}
+
+void seq_input_event(unsigned char *event_rec, int len)
+{
+	unsigned long this_time;
+
+	if (seq_mode == SEQ_2)
+		this_time = tmr->get_time(tmr_no);
+	else
+		this_time = jiffies - seq_time;
+
+	if (this_time != prev_input_time)
+	{
+		unsigned char   tmp_event[8];
+
+		tmp_event[0] = EV_TIMING;
+		tmp_event[1] = TMR_WAIT_ABS;
+		tmp_event[2] = 0;
+		tmp_event[3] = 0;
+		*(unsigned int *) &tmp_event[4] = this_time;
+
+		seq_copy_to_input(tmp_event, 8);
+		prev_input_time = this_time;
+	}
+	seq_copy_to_input(event_rec, len);
+}
+EXPORT_SYMBOL(seq_input_event);
+
+int sequencer_write(int dev, struct file *file, const char __user *buf, int count)
+{
+	unsigned char event_rec[EV_SZ], ev_code;
+	int p = 0, c, ev_size;
+	int mode = translate_mode(file);
+
+	dev = dev >> 4;
+
+	DEB(printk("sequencer_write(dev=%d, count=%d)\n", dev, count));
+
+	if (mode == OPEN_READ)
+		return -EIO;
+
+	c = count;
+
+	while (c >= 4)
+	{
+		if (copy_from_user((char *) event_rec, &(buf)[p], 4))
+			goto out;
+		ev_code = event_rec[0];
+
+		if (ev_code == SEQ_FULLSIZE)
+		{
+			int err, fmt;
+
+			dev = *(unsigned short *) &event_rec[2];
+			if (dev < 0 || dev >= max_synthdev || synth_devs[dev] == NULL)
+				return -ENXIO;
+
+			if (!(synth_open_mask & (1 << dev)))
+				return -ENXIO;
+
+			fmt = (*(short *) &event_rec[0]) & 0xffff;
+			err = synth_devs[dev]->load_patch(dev, fmt, buf, p + 4, c, 0);
+			if (err < 0)
+				return err;
+
+			return err;
+		}
+		if (ev_code >= 128)
+		{
+			if (seq_mode == SEQ_2 && ev_code == SEQ_EXTENDED)
+			{
+				printk(KERN_WARNING "Sequencer: Invalid level 2 event %x\n", ev_code);
+				return -EINVAL;
+			}
+			ev_size = 8;
+
+			if (c < ev_size)
+			{
+				if (!seq_playing)
+					seq_startplay();
+				return count - c;
+			}
+			if (copy_from_user((char *)&event_rec[4],
+					   &(buf)[p + 4], 4))
+				goto out;
+
+		}
+		else
+		{
+			if (seq_mode == SEQ_2)
+			{
+				printk(KERN_WARNING "Sequencer: 4 byte event in level 2 mode\n");
+				return -EINVAL;
+			}
+			ev_size = 4;
+
+			if (event_rec[0] != SEQ_MIDIPUTC)
+				obsolete_api_used = 1;
+		}
+
+		if (event_rec[0] == SEQ_MIDIPUTC)
+		{
+			if (!midi_opened[event_rec[2]])
+			{
+				int err, mode;
+				int dev = event_rec[2];
+
+				if (dev >= max_mididev || midi_devs[dev]==NULL)
+				{
+					/*printk("Sequencer Error: Nonexistent MIDI device %d\n", dev);*/
+					return -ENXIO;
+				}
+				mode = translate_mode(file);
+
+				if ((err = midi_devs[dev]->open(dev, mode,
+								sequencer_midi_input, sequencer_midi_output)) < 0)
+				{
+					seq_reset();
+					printk(KERN_WARNING "Sequencer Error: Unable to open Midi #%d\n", dev);
+					return err;
+				}
+				midi_opened[dev] = 1;
+			}
+		}
+		if (!seq_queue(event_rec, (file->f_flags & (O_NONBLOCK) ? 1 : 0)))
+		{
+			int processed = count - c;
+
+			if (!seq_playing)
+				seq_startplay();
+
+			if (!processed && (file->f_flags & O_NONBLOCK))
+				return -EAGAIN;
+			else
+				return processed;
+		}
+		p += ev_size;
+		c -= ev_size;
+	}
+
+	if (!seq_playing)
+		seq_startplay();
+out:
+	return count;
+}
+
+static int seq_queue(unsigned char *note, char nonblock)
+{
+
+	/*
+	 * Test if there is space in the queue
+	 */
+
+	if (qlen >= SEQ_MAX_QUEUE)
+		if (!seq_playing)
+			seq_startplay();	/*
+						 * Give chance to drain the queue
+						 */
+
+	if (!nonblock && qlen >= SEQ_MAX_QUEUE && !waitqueue_active(&seq_sleeper)) {
+		/*
+		 * Sleep until there is enough space on the queue
+		 */
+		interruptible_sleep_on(&seq_sleeper);
+	}
+	if (qlen >= SEQ_MAX_QUEUE)
+	{
+		return 0;	/*
+				 * To be sure
+				 */
+	}
+	memcpy(&queue[qtail * EV_SZ], note, EV_SZ);
+
+	qtail = (qtail + 1) % SEQ_MAX_QUEUE;
+	qlen++;
+
+	return 1;
+}
+
+static int extended_event(unsigned char *q)
+{
+	int dev = q[2];
+
+	if (dev < 0 || dev >= max_synthdev)
+		return -ENXIO;
+
+	if (!(synth_open_mask & (1 << dev)))
+		return -ENXIO;
+
+	switch (q[1])
+	{
+		case SEQ_NOTEOFF:
+			synth_devs[dev]->kill_note(dev, q[3], q[4], q[5]);
+			break;
+
+		case SEQ_NOTEON:
+			if (q[4] > 127 && q[4] != 255)
+				return 0;
+
+			if (q[5] == 0)
+			{
+				synth_devs[dev]->kill_note(dev, q[3], q[4], q[5]);
+				break;
+			}
+			synth_devs[dev]->start_note(dev, q[3], q[4], q[5]);
+			break;
+
+		case SEQ_PGMCHANGE:
+			synth_devs[dev]->set_instr(dev, q[3], q[4]);
+			break;
+
+		case SEQ_AFTERTOUCH:
+			synth_devs[dev]->aftertouch(dev, q[3], q[4]);
+			break;
+
+		case SEQ_BALANCE:
+			synth_devs[dev]->panning(dev, q[3], (char) q[4]);
+			break;
+
+		case SEQ_CONTROLLER:
+			synth_devs[dev]->controller(dev, q[3], q[4], (short) (q[5] | (q[6] << 8)));
+			break;
+
+		case SEQ_VOLMODE:
+			if (synth_devs[dev]->volume_method != NULL)
+				synth_devs[dev]->volume_method(dev, q[3]);
+			break;
+
+		default:
+			return -EINVAL;
+	}
+	return 0;
+}
+
+static int find_voice(int dev, int chn, int note)
+{
+	unsigned short key;
+	int i;
+
+	key = (chn << 8) | (note + 1);
+	for (i = 0; i < synth_devs[dev]->alloc.max_voice; i++)
+		if (synth_devs[dev]->alloc.map[i] == key)
+			return i;
+	return -1;
+}
+
+static int alloc_voice(int dev, int chn, int note)
+{
+	unsigned short  key;
+	int voice;
+
+	key = (chn << 8) | (note + 1);
+
+	voice = synth_devs[dev]->alloc_voice(dev, chn, note,
+					     &synth_devs[dev]->alloc);
+	synth_devs[dev]->alloc.map[voice] = key;
+	synth_devs[dev]->alloc.alloc_times[voice] =
+			synth_devs[dev]->alloc.timestamp++;
+	return voice;
+}
+
+static void seq_chn_voice_event(unsigned char *event_rec)
+{
+#define dev event_rec[1]
+#define cmd event_rec[2]
+#define chn event_rec[3]
+#define note event_rec[4]
+#define parm event_rec[5]
+
+	int voice = -1;
+
+	if ((int) dev > max_synthdev || synth_devs[dev] == NULL)
+		return;
+	if (!(synth_open_mask & (1 << dev)))
+		return;
+	if (!synth_devs[dev])
+		return;
+
+	if (seq_mode == SEQ_2)
+	{
+		if (synth_devs[dev]->alloc_voice)
+			voice = find_voice(dev, chn, note);
+
+		if (cmd == MIDI_NOTEON && parm == 0)
+		{
+			cmd = MIDI_NOTEOFF;
+			parm = 64;
+		}
+	}
+
+	switch (cmd)
+	{
+		case MIDI_NOTEON:
+			if (note > 127 && note != 255)	/* Not a seq2 feature */
+				return;
+
+			if (voice == -1 && seq_mode == SEQ_2 && synth_devs[dev]->alloc_voice)
+			{
+				/* Internal synthesizer (FM, GUS, etc) */
+				voice = alloc_voice(dev, chn, note);
+			}
+			if (voice == -1)
+				voice = chn;
+
+			if (seq_mode == SEQ_2 && (int) dev < num_synths)
+			{
+				/*
+				 * The MIDI channel 10 is a percussive channel. Use the note
+				 * number to select the proper patch (128 to 255) to play.
+				 */
+
+				if (chn == 9)
+				{
+					synth_devs[dev]->set_instr(dev, voice, 128 + note);
+					synth_devs[dev]->chn_info[chn].pgm_num = 128 + note;
+				}
+				synth_devs[dev]->setup_voice(dev, voice, chn);
+			}
+			synth_devs[dev]->start_note(dev, voice, note, parm);
+			break;
+
+		case MIDI_NOTEOFF:
+			if (voice == -1)
+				voice = chn;
+			synth_devs[dev]->kill_note(dev, voice, note, parm);
+			break;
+
+		case MIDI_KEY_PRESSURE:
+			if (voice == -1)
+				voice = chn;
+			synth_devs[dev]->aftertouch(dev, voice, parm);
+			break;
+
+		default:;
+	}
+#undef dev
+#undef cmd
+#undef chn
+#undef note
+#undef parm
+}
+
+
+static void seq_chn_common_event(unsigned char *event_rec)
+{
+	unsigned char dev = event_rec[1];
+	unsigned char cmd = event_rec[2];
+	unsigned char chn = event_rec[3];
+	unsigned char p1 = event_rec[4];
+
+	/* unsigned char p2 = event_rec[5]; */
+	unsigned short w14 = *(short *) &event_rec[6];
+
+	if ((int) dev > max_synthdev || synth_devs[dev] == NULL)
+		return;
+	if (!(synth_open_mask & (1 << dev)))
+		return;
+	if (!synth_devs[dev])
+		return;
+
+	switch (cmd)
+	{
+		case MIDI_PGM_CHANGE:
+			if (seq_mode == SEQ_2)
+			{
+				synth_devs[dev]->chn_info[chn].pgm_num = p1;
+				if ((int) dev >= num_synths)
+					synth_devs[dev]->set_instr(dev, chn, p1);
+			}
+			else
+				synth_devs[dev]->set_instr(dev, chn, p1);
+
+			break;
+
+		case MIDI_CTL_CHANGE:
+			if (seq_mode == SEQ_2)
+			{
+				if (chn > 15 || p1 > 127)
+					break;
+
+				synth_devs[dev]->chn_info[chn].controllers[p1] = w14 & 0x7f;
+
+				if (p1 < 32)	/* Setting MSB should clear LSB to 0 */
+					synth_devs[dev]->chn_info[chn].controllers[p1 + 32] = 0;
+
+				if ((int) dev < num_synths)
+				{
+					int val = w14 & 0x7f;
+					int i, key;
+
+					if (p1 < 64)	/* Combine MSB and LSB */
+					{
+						val = ((synth_devs[dev]->
+							chn_info[chn].controllers[p1 & ~32] & 0x7f) << 7)
+							| (synth_devs[dev]->
+							chn_info[chn].controllers[p1 | 32] & 0x7f);
+						p1 &= ~32;
+					}
+					/* Handle all playing notes on this channel */
+
+					key = ((int) chn << 8);
+
+					for (i = 0; i < synth_devs[dev]->alloc.max_voice; i++)
+						if ((synth_devs[dev]->alloc.map[i] & 0xff00) == key)
+							synth_devs[dev]->controller(dev, i, p1, val);
+				}
+				else
+					synth_devs[dev]->controller(dev, chn, p1, w14);
+			}
+			else	/* Mode 1 */
+				synth_devs[dev]->controller(dev, chn, p1, w14);
+			break;
+
+		case MIDI_PITCH_BEND:
+			if (seq_mode == SEQ_2)
+			{
+				synth_devs[dev]->chn_info[chn].bender_value = w14;
+
+				if ((int) dev < num_synths)
+				{
+					/* Handle all playing notes on this channel */
+					int i, key;
+
+					key = (chn << 8);
+
+					for (i = 0; i < synth_devs[dev]->alloc.max_voice; i++)
+						if ((synth_devs[dev]->alloc.map[i] & 0xff00) == key)
+							synth_devs[dev]->bender(dev, i, w14);
+				}
+				else
+					synth_devs[dev]->bender(dev, chn, w14);
+			}
+			else	/* MODE 1 */
+				synth_devs[dev]->bender(dev, chn, w14);
+			break;
+
+		default:;
+	}
+}
+
+static int seq_timing_event(unsigned char *event_rec)
+{
+	unsigned char cmd = event_rec[1];
+	unsigned int parm = *(int *) &event_rec[4];
+
+	if (seq_mode == SEQ_2)
+	{
+		int ret;
+
+		if ((ret = tmr->event(tmr_no, event_rec)) == TIMER_ARMED)
+			if ((SEQ_MAX_QUEUE - qlen) >= output_threshold)
+				wake_up(&seq_sleeper);
+		return ret;
+	}
+	switch (cmd)
+	{
+		case TMR_WAIT_REL:
+			parm += prev_event_time;
+
+			/*
+			 * NOTE!  No break here. Execution of TMR_WAIT_REL continues in the
+			 * next case (TMR_WAIT_ABS)
+			 */
+
+		case TMR_WAIT_ABS:
+			if (parm > 0)
+			{
+				long time;
+
+				time = parm;
+				prev_event_time = time;
+
+				seq_playing = 1;
+				request_sound_timer(time);
+
+				if ((SEQ_MAX_QUEUE - qlen) >= output_threshold)
+					wake_up(&seq_sleeper);
+				return TIMER_ARMED;
+			}
+			break;
+
+		case TMR_START:
+			seq_time = jiffies;
+			prev_input_time = 0;
+			prev_event_time = 0;
+			break;
+
+		case TMR_STOP:
+			break;
+
+		case TMR_CONTINUE:
+			break;
+
+		case TMR_TEMPO:
+			break;
+
+		case TMR_ECHO:
+			if (seq_mode == SEQ_2)
+				seq_copy_to_input(event_rec, 8);
+			else
+			{
+				parm = (parm << 8 | SEQ_ECHO);
+				seq_copy_to_input((unsigned char *) &parm, 4);
+			}
+			break;
+
+		default:;
+	}
+
+	return TIMER_NOT_ARMED;
+}
+
+static void seq_local_event(unsigned char *event_rec)
+{
+	unsigned char   cmd = event_rec[1];
+	unsigned int    parm = *((unsigned int *) &event_rec[4]);
+
+	switch (cmd)
+	{
+		case LOCL_STARTAUDIO:
+			DMAbuf_start_devices(parm);
+			break;
+
+		default:;
+	}
+}
+
+static void seq_sysex_message(unsigned char *event_rec)
+{
+	unsigned int dev = event_rec[1];
+	int i, l = 0;
+	unsigned char  *buf = &event_rec[2];
+
+	if (dev > max_synthdev)
+		return;
+	if (!(synth_open_mask & (1 << dev)))
+		return;
+	if (!synth_devs[dev])
+		return;
+
+	l = 0;
+	for (i = 0; i < 6 && buf[i] != 0xff; i++)
+		l = i + 1;
+
+	if (!synth_devs[dev]->send_sysex)
+		return;
+	if (l > 0)
+		synth_devs[dev]->send_sysex(dev, buf, l);
+}
+
+static int play_event(unsigned char *q)
+{
+	/*
+	 * NOTE! This routine returns
+	 *   0 = normal event played.
+	 *   1 = Timer armed. Suspend playback until timer callback.
+	 *   2 = MIDI output buffer full. Restore queue and suspend until timer
+	 */
+	unsigned int *delay;
+
+	switch (q[0])
+	{
+		case SEQ_NOTEOFF:
+			if (synth_open_mask & (1 << 0))
+				if (synth_devs[0])
+					synth_devs[0]->kill_note(0, q[1], 255, q[3]);
+			break;
+
+		case SEQ_NOTEON:
+			if (q[4] < 128 || q[4] == 255)
+				if (synth_open_mask & (1 << 0))
+					if (synth_devs[0])
+						synth_devs[0]->start_note(0, q[1], q[2], q[3]);
+			break;
+
+		case SEQ_WAIT:
+			delay = (unsigned int *) q;	/*
+							 * Bytes 1 to 3 are containing the *
+							 * delay in 'ticks'
+							 */
+			*delay = (*delay >> 8) & 0xffffff;
+
+			if (*delay > 0)
+			{
+				long time;
+
+				seq_playing = 1;
+				time = *delay;
+				prev_event_time = time;
+
+				request_sound_timer(time);
+
+				if ((SEQ_MAX_QUEUE - qlen) >= output_threshold)
+					wake_up(&seq_sleeper);
+				/*
+				 * The timer is now active and will reinvoke this function
+				 * after the timer expires. Return to the caller now.
+				 */
+				return 1;
+			}
+			break;
+
+		case SEQ_PGMCHANGE:
+			if (synth_open_mask & (1 << 0))
+				if (synth_devs[0])
+					synth_devs[0]->set_instr(0, q[1], q[2]);
+			break;
+
+		case SEQ_SYNCTIMER: 	/*
+					 * Reset timer
+					 */
+			seq_time = jiffies;
+			prev_input_time = 0;
+			prev_event_time = 0;
+			break;
+
+		case SEQ_MIDIPUTC:	/*
+					 * Put a midi character
+					 */
+			if (midi_opened[q[2]])
+			{
+				int dev;
+
+				dev = q[2];
+
+				if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
+					break;
+
+				if (!midi_devs[dev]->outputc(dev, q[1]))
+				{
+					/*
+					 * Output FIFO is full. Wait one timer cycle and try again.
+					 */
+
+					seq_playing = 1;
+					request_sound_timer(-1);
+					return 2;
+				}
+				else
+					midi_written[dev] = 1;
+			}
+			break;
+
+		case SEQ_ECHO:
+			seq_copy_to_input(q, 4);	/*
+							 * Echo back to the process
+							 */
+			break;
+
+		case SEQ_PRIVATE:
+			if ((int) q[1] < max_synthdev)
+				synth_devs[q[1]]->hw_control(q[1], q);
+			break;
+
+		case SEQ_EXTENDED:
+			extended_event(q);
+			break;
+
+		case EV_CHN_VOICE:
+			seq_chn_voice_event(q);
+			break;
+
+		case EV_CHN_COMMON:
+			seq_chn_common_event(q);
+			break;
+
+		case EV_TIMING:
+			if (seq_timing_event(q) == TIMER_ARMED)
+			{
+				return 1;
+			}
+			break;
+
+		case EV_SEQ_LOCAL:
+			seq_local_event(q);
+			break;
+
+		case EV_SYSEX:
+			seq_sysex_message(q);
+			break;
+
+		default:;
+	}
+	return 0;
+}
+
+/* called also as timer in irq context */
+static void seq_startplay(void)
+{
+	int this_one, action;
+	unsigned long flags;
+
+	while (qlen > 0)
+	{
+
+		spin_lock_irqsave(&lock,flags);
+		qhead = ((this_one = qhead) + 1) % SEQ_MAX_QUEUE;
+		qlen--;
+		spin_unlock_irqrestore(&lock,flags);
+
+		seq_playing = 1;
+
+		if ((action = play_event(&queue[this_one * EV_SZ])))
+		{		/* Suspend playback. Next timer routine invokes this routine again */
+			if (action == 2)
+			{
+				qlen++;
+				qhead = this_one;
+			}
+			return;
+		}
+	}
+
+	seq_playing = 0;
+
+	if ((SEQ_MAX_QUEUE - qlen) >= output_threshold)
+		wake_up(&seq_sleeper);
+}
+
+static void reset_controllers(int dev, unsigned char *controller, int update_dev)
+{
+	int i;
+	for (i = 0; i < 128; i++)
+		controller[i] = ctrl_def_values[i];
+}
+
+static void setup_mode2(void)
+{
+	int dev;
+
+	max_synthdev = num_synths;
+
+	for (dev = 0; dev < num_midis; dev++)
+	{
+		if (midi_devs[dev] && midi_devs[dev]->converter != NULL)
+		{
+			synth_devs[max_synthdev++] = midi_devs[dev]->converter;
+		}
+	}
+
+	for (dev = 0; dev < max_synthdev; dev++)
+	{
+		int chn;
+
+		synth_devs[dev]->sysex_ptr = 0;
+		synth_devs[dev]->emulation = 0;
+
+		for (chn = 0; chn < 16; chn++)
+		{
+			synth_devs[dev]->chn_info[chn].pgm_num = 0;
+			reset_controllers(dev,
+				synth_devs[dev]->chn_info[chn].controllers,0);
+			synth_devs[dev]->chn_info[chn].bender_value = (1 << 7);	/* Neutral */
+			synth_devs[dev]->chn_info[chn].bender_range = 200;
+		}
+	}
+	max_mididev = 0;
+	seq_mode = SEQ_2;
+}
+
+int sequencer_open(int dev, struct file *file)
+{
+	int retval, mode, i;
+	int level, tmp;
+
+	if (!sequencer_ok)
+		sequencer_init();
+
+	level = ((dev & 0x0f) == SND_DEV_SEQ2) ? 2 : 1;
+
+	dev = dev >> 4;
+	mode = translate_mode(file);
+
+	DEB(printk("sequencer_open(dev=%d)\n", dev));
+
+	if (!sequencer_ok)
+	{
+/*		printk("Sound card: sequencer not initialized\n");*/
+		return -ENXIO;
+	}
+	if (dev)		/* Patch manager device (obsolete) */
+		return -ENXIO;
+
+	if(synth_devs[dev] == NULL)
+		request_module("synth0");
+
+	if (mode == OPEN_READ)
+	{
+		if (!num_midis)
+		{
+			/*printk("Sequencer: No MIDI devices. Input not possible\n");*/
+			sequencer_busy = 0;
+			return -ENXIO;
+		}
+	}
+	if (sequencer_busy)
+	{
+		return -EBUSY;
+	}
+	sequencer_busy = 1;
+	obsolete_api_used = 0;
+
+	max_mididev = num_midis;
+	max_synthdev = num_synths;
+	pre_event_timeout = MAX_SCHEDULE_TIMEOUT;
+	seq_mode = SEQ_1;
+
+	if (pending_timer != -1)
+	{
+		tmr_no = pending_timer;
+		pending_timer = -1;
+	}
+	if (tmr_no == -1)	/* Not selected yet */
+	{
+		int i, best;
+
+		best = -1;
+		for (i = 0; i < num_sound_timers; i++)
+			if (sound_timer_devs[i] && sound_timer_devs[i]->priority > best)
+			{
+				tmr_no = i;
+				best = sound_timer_devs[i]->priority;
+			}
+		if (tmr_no == -1)	/* Should not be */
+			tmr_no = 0;
+	}
+	tmr = sound_timer_devs[tmr_no];
+
+	if (level == 2)
+	{
+		if (tmr == NULL)
+		{
+			/*printk("sequencer: No timer for level 2\n");*/
+			sequencer_busy = 0;
+			return -ENXIO;
+		}
+		setup_mode2();
+	}
+	if (!max_synthdev && !max_mididev)
+	{
+		sequencer_busy=0;
+		return -ENXIO;
+	}
+
+	synth_open_mask = 0;
+
+	for (i = 0; i < max_mididev; i++)
+	{
+		midi_opened[i] = 0;
+		midi_written[i] = 0;
+	}
+
+	for (i = 0; i < max_synthdev; i++)
+	{
+		if (synth_devs[i]==NULL)
+			continue;
+
+		if (!try_module_get(synth_devs[i]->owner))
+			continue;
+
+		if ((tmp = synth_devs[i]->open(i, mode)) < 0)
+		{
+			printk(KERN_WARNING "Sequencer: Warning! Cannot open synth device #%d (%d)\n", i, tmp);
+			if (synth_devs[i]->midi_dev)
+				printk(KERN_WARNING "(Maps to MIDI dev #%d)\n", synth_devs[i]->midi_dev);
+		}
+		else
+		{
+			synth_open_mask |= (1 << i);
+			if (synth_devs[i]->midi_dev)
+				midi_opened[synth_devs[i]->midi_dev] = 1;
+		}
+	}
+
+	seq_time = jiffies;
+
+	prev_input_time = 0;
+	prev_event_time = 0;
+
+	if (seq_mode == SEQ_1 && (mode == OPEN_READ || mode == OPEN_READWRITE))
+	{
+		/*
+		 * Initialize midi input devices
+		 */
+
+		for (i = 0; i < max_mididev; i++)
+			if (!midi_opened[i] && midi_devs[i])
+			{
+				if (!try_module_get(midi_devs[i]->owner))
+					continue;
+	
+				if ((retval = midi_devs[i]->open(i, mode,
+					sequencer_midi_input, sequencer_midi_output)) >= 0)
+				{
+					midi_opened[i] = 1;
+				}
+			}
+	}
+
+	if (seq_mode == SEQ_2) {
+		if (try_module_get(tmr->owner))
+			tmr->open(tmr_no, seq_mode);
+	}
+
+ 	init_waitqueue_head(&seq_sleeper);
+ 	init_waitqueue_head(&midi_sleeper);
+	output_threshold = SEQ_MAX_QUEUE / 2;
+
+	return 0;
+}
+
+static void seq_drain_midi_queues(void)
+{
+	int i, n;
+
+	/*
+	 * Give the Midi drivers time to drain their output queues
+	 */
+
+	n = 1;
+
+	while (!signal_pending(current) && n)
+	{
+		n = 0;
+
+		for (i = 0; i < max_mididev; i++)
+			if (midi_opened[i] && midi_written[i])
+				if (midi_devs[i]->buffer_status != NULL)
+					if (midi_devs[i]->buffer_status(i))
+						n++;
+
+		/*
+		 * Let's have a delay
+		 */
+
+ 		if (n)
+ 			interruptible_sleep_on_timeout(&seq_sleeper,
+						       HZ/10);
+	}
+}
+
+void sequencer_release(int dev, struct file *file)
+{
+	int i;
+	int mode = translate_mode(file);
+
+	dev = dev >> 4;
+
+	DEB(printk("sequencer_release(dev=%d)\n", dev));
+
+	/*
+	 * Wait until the queue is empty (if we don't have nonblock)
+	 */
+
+	if (mode != OPEN_READ && !(file->f_flags & O_NONBLOCK))
+	{
+		while (!signal_pending(current) && qlen > 0)
+		{
+  			seq_sync();
+ 			interruptible_sleep_on_timeout(&seq_sleeper,
+						       3*HZ);
+ 			/* Extra delay */
+		}
+	}
+
+	if (mode != OPEN_READ)
+		seq_drain_midi_queues();	/*
+						 * Ensure the output queues are empty
+						 */
+	seq_reset();
+	if (mode != OPEN_READ)
+		seq_drain_midi_queues();	/*
+						 * Flush the all notes off messages
+						 */
+
+	for (i = 0; i < max_synthdev; i++)
+	{
+		if (synth_open_mask & (1 << i))	/*
+						 * Actually opened
+						 */
+			if (synth_devs[i])
+			{
+				synth_devs[i]->close(i);
+
+				module_put(synth_devs[i]->owner);
+
+				if (synth_devs[i]->midi_dev)
+					midi_opened[synth_devs[i]->midi_dev] = 0;
+			}
+	}
+
+	for (i = 0; i < max_mididev; i++)
+	{
+		if (midi_opened[i]) {
+			midi_devs[i]->close(i);
+			module_put(midi_devs[i]->owner);
+		}
+	}
+
+	if (seq_mode == SEQ_2) {
+		tmr->close(tmr_no);
+		module_put(tmr->owner);
+	}
+
+	if (obsolete_api_used)
+		printk(KERN_WARNING "/dev/music: Obsolete (4 byte) API was used by %s\n", current->comm);
+	sequencer_busy = 0;
+}
+
+static int seq_sync(void)
+{
+	if (qlen && !seq_playing && !signal_pending(current))
+		seq_startplay();
+
+ 	if (qlen > 0)
+ 		interruptible_sleep_on_timeout(&seq_sleeper, HZ);
+	return qlen;
+}
+
+static void midi_outc(int dev, unsigned char data)
+{
+	/*
+	 * NOTE! Calls sleep(). Don't call this from interrupt.
+	 */
+
+	int n;
+	unsigned long flags;
+
+	/*
+	 * This routine sends one byte to the Midi channel.
+	 * If the output FIFO is full, it waits until there
+	 * is space in the queue
+	 */
+
+	n = 3 * HZ;		/* Timeout */
+
+	spin_lock_irqsave(&lock,flags);
+ 	while (n && !midi_devs[dev]->outputc(dev, data)) {
+ 		interruptible_sleep_on_timeout(&seq_sleeper, HZ/25);
+  		n--;
+  	}
+	spin_unlock_irqrestore(&lock,flags);
+}
+
+static void seq_reset(void)
+{
+	/*
+	 * NOTE! Calls sleep(). Don't call this from interrupt.
+	 */
+
+	int i;
+	int chn;
+	unsigned long flags;
+
+	sound_stop_timer();
+
+	seq_time = jiffies;
+	prev_input_time = 0;
+	prev_event_time = 0;
+
+	qlen = qhead = qtail = 0;
+	iqlen = iqhead = iqtail = 0;
+
+	for (i = 0; i < max_synthdev; i++)
+		if (synth_open_mask & (1 << i))
+			if (synth_devs[i])
+				synth_devs[i]->reset(i);
+
+	if (seq_mode == SEQ_2)
+	{
+		for (chn = 0; chn < 16; chn++)
+			for (i = 0; i < max_synthdev; i++)
+				if (synth_open_mask & (1 << i))
+					if (synth_devs[i])
+					{
+						synth_devs[i]->controller(i, chn, 123, 0);	/* All notes off */
+						synth_devs[i]->controller(i, chn, 121, 0);	/* Reset all ctl */
+						synth_devs[i]->bender(i, chn, 1 << 13);	/* Bender off */
+					}
+	}
+	else	/* seq_mode == SEQ_1 */
+	{
+		for (i = 0; i < max_mididev; i++)
+			if (midi_written[i])	/*
+						 * Midi used. Some notes may still be playing
+						 */
+			{
+				/*
+				 *      Sending just a ACTIVE SENSING message should be enough to stop all
+				 *      playing notes. Since there are devices not recognizing the
+				 *      active sensing, we have to send some all notes off messages also.
+				 */
+				midi_outc(i, 0xfe);
+
+				for (chn = 0; chn < 16; chn++)
+				{
+					midi_outc(i, (unsigned char) (0xb0 + (chn & 0x0f)));		/* control change */
+					midi_outc(i, 0x7b);	/* All notes off */
+					midi_outc(i, 0);	/* Dummy parameter */
+				}
+
+				midi_devs[i]->close(i);
+
+				midi_written[i] = 0;
+				midi_opened[i] = 0;
+			}
+	}
+
+	seq_playing = 0;
+
+	spin_lock_irqsave(&lock,flags);
+
+	if (waitqueue_active(&seq_sleeper)) {
+		/*      printk( "Sequencer Warning: Unexpected sleeping process - Waking up\n"); */
+		wake_up(&seq_sleeper);
+	}
+	spin_unlock_irqrestore(&lock,flags);
+}
+
+static void seq_panic(void)
+{
+	/*
+	 * This routine is called by the application in case the user
+	 * wants to reset the system to the default state.
+	 */
+
+	seq_reset();
+
+	/*
+	 * Since some of the devices don't recognize the active sensing and
+	 * all notes off messages, we have to shut all notes manually.
+	 *
+	 *      TO BE IMPLEMENTED LATER
+	 */
+
+	/*
+	 * Also return the controllers to their default states
+	 */
+}
+
+int sequencer_ioctl(int dev, struct file *file, unsigned int cmd, void __user *arg)
+{
+	int midi_dev, orig_dev, val, err;
+	int mode = translate_mode(file);
+	struct synth_info inf;
+	struct seq_event_rec event_rec;
+	unsigned long flags;
+	int __user *p = arg;
+
+	orig_dev = dev = dev >> 4;
+
+	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:
+			if (seq_mode != SEQ_2)
+				return -EINVAL;
+			return tmr->ioctl(tmr_no, cmd, arg);
+
+		case SNDCTL_TMR_SELECT:
+			if (seq_mode != SEQ_2)
+				return -EINVAL;
+			if (get_user(pending_timer, p))
+				return -EFAULT;
+			if (pending_timer < 0 || pending_timer >= num_sound_timers || sound_timer_devs[pending_timer] == NULL)
+			{
+				pending_timer = -1;
+				return -EINVAL;
+			}
+			val = pending_timer;
+			break;
+
+		case SNDCTL_SEQ_PANIC:
+			seq_panic();
+			return -EINVAL;
+
+		case SNDCTL_SEQ_SYNC:
+			if (mode == OPEN_READ)
+				return 0;
+			while (qlen > 0 && !signal_pending(current))
+				seq_sync();
+			return qlen ? -EINTR : 0;
+
+		case SNDCTL_SEQ_RESET:
+			seq_reset();
+			return 0;
+
+		case SNDCTL_SEQ_TESTMIDI:
+			if (__get_user(midi_dev, p))
+				return -EFAULT;
+			if (midi_dev < 0 || midi_dev >= max_mididev || !midi_devs[midi_dev])
+				return -ENXIO;
+
+			if (!midi_opened[midi_dev] &&
+				(err = midi_devs[midi_dev]->open(midi_dev, mode, sequencer_midi_input,
+						     sequencer_midi_output)) < 0)
+				return err;
+			midi_opened[midi_dev] = 1;
+			return 0;
+
+		case SNDCTL_SEQ_GETINCOUNT:
+			if (mode == OPEN_WRITE)
+				return 0;
+			val = iqlen;
+			break;
+
+		case SNDCTL_SEQ_GETOUTCOUNT:
+			if (mode == OPEN_READ)
+				return 0;
+			val = SEQ_MAX_QUEUE - qlen;
+			break;
+
+		case SNDCTL_SEQ_GETTIME:
+			if (seq_mode == SEQ_2)
+				return tmr->ioctl(tmr_no, cmd, arg);
+			val = jiffies - seq_time;
+			break;
+
+		case SNDCTL_SEQ_CTRLRATE:
+			/*
+			 * If *arg == 0, just return the current rate
+			 */
+			if (seq_mode == SEQ_2)
+				return tmr->ioctl(tmr_no, cmd, arg);
+
+			if (get_user(val, p))
+				return -EFAULT;
+			if (val != 0)
+				return -EINVAL;
+			val = HZ;
+			break;
+
+		case SNDCTL_SEQ_RESETSAMPLES:
+		case SNDCTL_SYNTH_REMOVESAMPLE:
+		case SNDCTL_SYNTH_CONTROL:
+			if (get_user(dev, p))
+				return -EFAULT;
+			if (dev < 0 || dev >= num_synths || synth_devs[dev] == NULL)
+				return -ENXIO;
+			if (!(synth_open_mask & (1 << dev)) && !orig_dev)
+				return -EBUSY;
+			return synth_devs[dev]->ioctl(dev, cmd, arg);
+
+		case SNDCTL_SEQ_NRSYNTHS:
+			val = max_synthdev;
+			break;
+
+		case SNDCTL_SEQ_NRMIDIS:
+			val = max_mididev;
+			break;
+
+		case SNDCTL_SYNTH_MEMAVL:
+			if (get_user(dev, p))
+				return -EFAULT;
+			if (dev < 0 || dev >= num_synths || synth_devs[dev] == NULL)
+				return -ENXIO;
+			if (!(synth_open_mask & (1 << dev)) && !orig_dev)
+				return -EBUSY;
+			val = synth_devs[dev]->ioctl(dev, cmd, arg);
+			break;
+
+		case SNDCTL_FM_4OP_ENABLE:
+			if (get_user(dev, p))
+				return -EFAULT;
+			if (dev < 0 || dev >= num_synths || synth_devs[dev] == NULL)
+				return -ENXIO;
+			if (!(synth_open_mask & (1 << dev)))
+				return -ENXIO;
+			synth_devs[dev]->ioctl(dev, cmd, arg);
+			return 0;
+
+		case SNDCTL_SYNTH_INFO:
+			if (get_user(dev, &((struct synth_info __user *)arg)->device))
+				return -EFAULT;
+			if (dev < 0 || dev >= max_synthdev)
+				return -ENXIO;
+			if (!(synth_open_mask & (1 << dev)) && !orig_dev)
+				return -EBUSY;
+			return synth_devs[dev]->ioctl(dev, cmd, arg);
+
+		/* Like SYNTH_INFO but returns ID in the name field */
+		case SNDCTL_SYNTH_ID:
+			if (get_user(dev, &((struct synth_info __user *)arg)->device))
+				return -EFAULT;
+			if (dev < 0 || dev >= max_synthdev)
+				return -ENXIO;
+			if (!(synth_open_mask & (1 << dev)) && !orig_dev)
+				return -EBUSY;
+			memcpy(&inf, synth_devs[dev]->info, sizeof(inf));
+			strlcpy(inf.name, synth_devs[dev]->id, sizeof(inf.name));
+			inf.device = dev;
+			return copy_to_user(arg, &inf, sizeof(inf))?-EFAULT:0;
+
+		case SNDCTL_SEQ_OUTOFBAND:
+			if (copy_from_user(&event_rec, arg, sizeof(event_rec)))
+				return -EFAULT;
+			spin_lock_irqsave(&lock,flags);
+			play_event(event_rec.arr);
+			spin_unlock_irqrestore(&lock,flags);
+			return 0;
+
+		case SNDCTL_MIDI_INFO:
+			if (get_user(dev, &((struct midi_info __user *)arg)->device))
+				return -EFAULT;
+			if (dev < 0 || dev >= max_mididev || !midi_devs[dev])
+				return -ENXIO;
+			midi_devs[dev]->info.device = dev;
+			return copy_to_user(arg, &midi_devs[dev]->info, sizeof(struct midi_info))?-EFAULT:0;
+
+		case SNDCTL_SEQ_THRESHOLD:
+			if (get_user(val, p))
+				return -EFAULT;
+			if (val < 1)
+				val = 1;
+			if (val >= SEQ_MAX_QUEUE)
+				val = SEQ_MAX_QUEUE - 1;
+			output_threshold = val;
+			return 0;
+
+		case SNDCTL_MIDI_PRETIME:
+			if (get_user(val, p))
+				return -EFAULT;
+			if (val < 0)
+				val = 0;
+			val = (HZ * val) / 10;
+			pre_event_timeout = val;
+			break;
+
+		default:
+			if (mode == OPEN_READ)
+				return -EIO;
+			if (!synth_devs[0])
+				return -ENXIO;
+			if (!(synth_open_mask & (1 << 0)))
+				return -ENXIO;
+			if (!synth_devs[0]->ioctl)
+				return -EINVAL;
+			return synth_devs[0]->ioctl(0, cmd, arg);
+	}
+	return put_user(val, p);
+}
+
+/* No kernel lock - we're using the global irq lock here */
+unsigned int sequencer_poll(int dev, struct file *file, poll_table * wait)
+{
+	unsigned long flags;
+	unsigned int mask = 0;
+
+	dev = dev >> 4;
+
+	spin_lock_irqsave(&lock,flags);
+	/* input */
+	poll_wait(file, &midi_sleeper, wait);
+	if (iqlen)
+		mask |= POLLIN | POLLRDNORM;
+
+	/* output */
+	poll_wait(file, &seq_sleeper, wait);
+	if ((SEQ_MAX_QUEUE - qlen) >= output_threshold)
+		mask |= POLLOUT | POLLWRNORM;
+	spin_unlock_irqrestore(&lock,flags);
+	return mask;
+}
+
+
+void sequencer_timer(unsigned long dummy)
+{
+	seq_startplay();
+}
+EXPORT_SYMBOL(sequencer_timer);
+
+int note_to_freq(int note_num)
+{
+
+	/*
+	 * This routine converts a midi note to a frequency (multiplied by 1000)
+	 */
+
+	int note, octave, note_freq;
+	static int notes[] =
+	{
+		261632, 277189, 293671, 311132, 329632, 349232,
+		369998, 391998, 415306, 440000, 466162, 493880
+	};
+
+#define BASE_OCTAVE	5
+
+	octave = note_num / 12;
+	note = note_num % 12;
+
+	note_freq = notes[note];
+
+	if (octave < BASE_OCTAVE)
+		note_freq >>= (BASE_OCTAVE - octave);
+	else if (octave > BASE_OCTAVE)
+		note_freq <<= (octave - BASE_OCTAVE);
+
+	/*
+	 * note_freq >>= 1;
+	 */
+
+	return note_freq;
+}
+EXPORT_SYMBOL(note_to_freq);
+
+unsigned long compute_finetune(unsigned long base_freq, int bend, int range,
+		 int vibrato_cents)
+{
+	unsigned long amount;
+	int negative, semitones, cents, multiplier = 1;
+
+	if (!bend)
+		return base_freq;
+	if (!range)
+		return base_freq;
+
+	if (!base_freq)
+		return base_freq;
+
+	if (range >= 8192)
+		range = 8192;
+
+	bend = bend * range / 8192;	/* Convert to cents */
+	bend += vibrato_cents;
+
+	if (!bend)
+		return base_freq;
+
+	negative = bend < 0 ? 1 : 0;
+
+	if (bend < 0)
+		bend *= -1;
+	if (bend > range)
+		bend = range;
+
+	/*
+	   if (bend > 2399)
+	   bend = 2399;
+	 */
+	while (bend > 2399)
+	{
+		multiplier *= 4;
+		bend -= 2400;
+	}
+
+	semitones = bend / 100;
+	cents = bend % 100;
+
+	amount = (int) (semitone_tuning[semitones] * multiplier * cent_tuning[cents]) / 10000;
+
+	if (negative)
+		return (base_freq * 10000) / amount;	/* Bend down */
+	else
+		return (base_freq * amount) / 10000;	/* Bend up */
+}
+EXPORT_SYMBOL(compute_finetune);
+
+void sequencer_init(void)
+{
+	if (sequencer_ok)
+		return;
+	queue = vmalloc(SEQ_MAX_QUEUE * EV_SZ);
+	if (queue == NULL)
+	{
+		printk(KERN_ERR "sequencer: Can't allocate memory for sequencer output queue\n");
+		return;
+	}
+	iqueue = vmalloc(SEQ_MAX_QUEUE * IEV_SZ);
+	if (iqueue == NULL)
+	{
+		printk(KERN_ERR "sequencer: Can't allocate memory for sequencer input queue\n");
+		vfree(queue);
+		return;
+	}
+	sequencer_ok = 1;
+}
+EXPORT_SYMBOL(sequencer_init);
+
+void sequencer_unload(void)
+{
+	vfree(queue);
+	vfree(iqueue);
+	queue = iqueue = NULL;
+}
diff --git a/linux/sound/oss/sound_calls.h b/linux/sound/oss/sound_calls.h
new file mode 100644
index 0000000..87d8ad4
--- /dev/null
+++ b/linux/sound/oss/sound_calls.h
@@ -0,0 +1,87 @@
+/*
+ *	DMA buffer calls
+ */
+
+int DMAbuf_open(int dev, int mode);
+int DMAbuf_release(int dev, int mode);
+int DMAbuf_getwrbuffer(int dev, char **buf, int *size, int dontblock);
+int DMAbuf_getrdbuffer(int dev, char **buf, int *len, int dontblock);
+int DMAbuf_rmchars(int dev, int buff_no, int c);
+int DMAbuf_start_output(int dev, int buff_no, int l);
+int DMAbuf_move_wrpointer(int dev, int l);
+/* int DMAbuf_ioctl(int dev, unsigned int cmd, void __user *arg, int local); */
+void DMAbuf_init(int dev, int dma1, int dma2);
+void DMAbuf_deinit(int dev);
+int DMAbuf_start_dma (int dev, unsigned long physaddr, int count, int dma_mode);
+void DMAbuf_inputintr(int dev);
+void DMAbuf_outputintr(int dev, int underflow_flag);
+struct dma_buffparms;
+int DMAbuf_space_in_queue (int dev);
+int DMAbuf_activate_recording (int dev, struct dma_buffparms *dmap);
+int DMAbuf_get_buffer_pointer (int dev, struct dma_buffparms *dmap, int direction);
+void DMAbuf_launch_output(int dev, struct dma_buffparms *dmap);
+unsigned int DMAbuf_poll(struct file *file, int dev, poll_table *wait);
+void DMAbuf_start_devices(unsigned int devmask);
+void DMAbuf_reset (int dev);
+int DMAbuf_sync (int dev);
+
+/*
+ *	System calls for /dev/dsp and /dev/audio (audio.c)
+ */
+
+int audio_read (int dev, struct file *file, char __user *buf, int count);
+int audio_write (int dev, struct file *file, const char __user *buf, int count);
+int audio_open (int dev, struct file *file);
+void audio_release (int dev, struct file *file);
+int audio_ioctl (int dev, struct file *file,
+	   unsigned int cmd, void __user *arg);
+void audio_init_devices (void);
+void reorganize_buffers (int dev, struct dma_buffparms *dmap, int recording);
+
+/*
+ *	System calls for the /dev/sequencer
+ */
+
+int sequencer_read (int dev, struct file *file, char __user *buf, int count);
+int sequencer_write (int dev, struct file *file, const char __user *buf, int count);
+int sequencer_open (int dev, struct file *file);
+void sequencer_release (int dev, struct file *file);
+int sequencer_ioctl (int dev, struct file *file, unsigned int cmd, void __user *arg);
+unsigned int sequencer_poll(int dev, struct file *file, poll_table * wait);
+
+void sequencer_init (void);
+void sequencer_unload (void);
+void sequencer_timer(unsigned long dummy);
+int note_to_freq(int note_num);
+unsigned long compute_finetune(unsigned long base_freq, int bend, int range,
+			       int vibrato_bend);
+void seq_input_event(unsigned char *event, int len);
+void seq_copy_to_input (unsigned char *event, int len);
+
+/*
+ *	System calls for the /dev/midi
+ */
+
+int MIDIbuf_read (int dev, struct file *file, char __user *buf, int count);
+int MIDIbuf_write (int dev, struct file *file, const char __user *buf, int count);
+int MIDIbuf_open (int dev, struct file *file);
+void MIDIbuf_release (int dev, struct file *file);
+int MIDIbuf_ioctl (int dev, struct file *file, unsigned int cmd, void __user *arg);
+unsigned int MIDIbuf_poll(int dev, struct file *file, poll_table * wait);
+int MIDIbuf_avail(int dev);
+
+void MIDIbuf_bytes_received(int dev, unsigned char *buf, int count);
+
+
+/*	From soundcard.c	*/
+void request_sound_timer (int count);
+void sound_stop_timer(void);
+void conf_printf(char *name, struct address_info *hw_config);
+void conf_printf2(char *name, int base, int irq, int dma, int dma2);
+
+/*	From sound_timer.c */
+void sound_timer_interrupt(void);
+void sound_timer_syncinterval(unsigned int new_usecs);
+
+/*      From midi_synth.c       */
+void do_midi_msg (int synthno, unsigned char *msg, int mlen);
diff --git a/linux/sound/oss/sound_config.h b/linux/sound/oss/sound_config.h
new file mode 100644
index 0000000..9d35c4c
--- /dev/null
+++ b/linux/sound/oss/sound_config.h
@@ -0,0 +1,147 @@
+/* sound_config.h
+ *
+ * A driver for sound cards, misc. configuration parameters.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+
+
+#ifndef  _SOUND_CONFIG_H_
+#define  _SOUND_CONFIG_H_
+
+#include <linux/fs.h>
+#include <linux/sound.h>
+
+#include "os.h"
+#include "soundvers.h"
+
+
+#ifndef SND_DEFAULT_ENABLE
+#define SND_DEFAULT_ENABLE	1
+#endif
+
+#ifndef MAX_REALTIME_FACTOR
+#define MAX_REALTIME_FACTOR	4
+#endif
+
+/*
+ * Use always 64k buffer size. There is no reason to use shorter.
+ */
+#undef DSP_BUFFSIZE
+#define DSP_BUFFSIZE		(64*1024)
+
+#ifndef DSP_BUFFCOUNT
+#define DSP_BUFFCOUNT		1	/* 1 is recommended. */
+#endif
+
+#define FM_MONO		0x388	/* This is the I/O address used by AdLib */
+
+#ifndef CONFIG_PAS_BASE
+#define CONFIG_PAS_BASE	0x388
+#endif
+
+/* SEQ_MAX_QUEUE is the maximum number of sequencer events buffered by the
+   driver. (There is no need to alter this) */
+#define SEQ_MAX_QUEUE	1024
+
+#define SBFM_MAXINSTR		(256)	/* Size of the FM Instrument bank */
+/* 128 instruments for general MIDI setup and 16 unassigned	 */
+
+#define SND_NDEVS	256	/* Number of supported devices */
+
+#define DSP_DEFAULT_SPEED	8000
+
+#define MAX_AUDIO_DEV	5
+#define MAX_MIXER_DEV	5
+#define MAX_SYNTH_DEV	5
+#define MAX_MIDI_DEV	6
+#define MAX_TIMER_DEV	4
+
+struct address_info {
+	int io_base;
+	int irq;
+	int dma;
+	int dma2;
+	int always_detect;	/* 1=Trust me, it's there */
+	char *name;
+	int driver_use_1;	/* Driver defined field 1 */
+	int driver_use_2;	/* Driver defined field 2 */
+	int *osp;	/* OS specific info */
+	int card_subtype;	/* Driver specific. Usually 0 */
+	void *memptr;           /* Module memory chainer */
+	int slots[6];           /* To remember driver slot ids */
+};
+
+#define SYNTH_MAX_VOICES	32
+
+struct voice_alloc_info {
+		int max_voice;
+		int used_voices;
+		int ptr;		/* For device specific use */
+		unsigned short map[SYNTH_MAX_VOICES]; /* (ch << 8) | (note+1) */
+		int timestamp;
+		int alloc_times[SYNTH_MAX_VOICES];
+	};
+
+struct channel_info {
+		int pgm_num;
+		int bender_value;
+		int bender_range;
+		unsigned char controllers[128];
+	};
+
+/*
+ * Process wakeup reasons
+ */
+#define WK_NONE		0x00
+#define WK_WAKEUP	0x01
+#define WK_TIMEOUT	0x02
+#define WK_SIGNAL	0x04
+#define WK_SLEEP	0x08
+#define WK_SELECT	0x10
+#define WK_ABORT	0x20
+
+#define OPEN_READ	PCM_ENABLE_INPUT
+#define OPEN_WRITE	PCM_ENABLE_OUTPUT
+#define OPEN_READWRITE	(OPEN_READ|OPEN_WRITE)
+
+static inline int translate_mode(struct file *file)
+{
+	if (OPEN_READ == (__force int)FMODE_READ &&
+	    OPEN_WRITE == (__force int)FMODE_WRITE)
+		return (__force int)(file->f_mode & (FMODE_READ | FMODE_WRITE));
+	else
+		return ((file->f_mode & FMODE_READ) ? OPEN_READ : 0) |
+			((file->f_mode & FMODE_WRITE) ? OPEN_WRITE : 0);
+}
+
+#include "sound_calls.h"
+#include "dev_table.h"
+
+#ifndef DEB
+#define DEB(x)
+#endif
+
+#ifndef DDB
+#define DDB(x) do {} while (0)
+#endif
+
+#ifndef MDB
+#ifdef MODULE
+#define MDB(x) x
+#else
+#define MDB(x)
+#endif
+#endif
+
+#define TIMER_ARMED	121234
+#define TIMER_NOT_ARMED	1
+
+#define MAX_MEM_BLOCKS 1024
+
+#endif
diff --git a/linux/sound/oss/sound_firmware.h b/linux/sound/oss/sound_firmware.h
new file mode 100644
index 0000000..0a0cbfd
--- /dev/null
+++ b/linux/sound/oss/sound_firmware.h
@@ -0,0 +1,2 @@
+extern int mod_firmware_load(const char *fn, char **fp);
+
diff --git a/linux/sound/oss/sound_timer.c b/linux/sound/oss/sound_timer.c
new file mode 100644
index 0000000..48cda6c
--- /dev/null
+++ b/linux/sound/oss/sound_timer.c
@@ -0,0 +1,327 @@
+/*
+ * sound/oss/sound_timer.c
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer   : ioctl code reworked (vmalloc/vfree removed)
+ */
+#include <linux/string.h>
+#include <linux/spinlock.h>
+
+#include "sound_config.h"
+
+static volatile int initialized, opened, tmr_running;
+static volatile time_t tmr_offs, tmr_ctr;
+static volatile unsigned long ticks_offs;
+static volatile int curr_tempo, curr_timebase;
+static volatile unsigned long curr_ticks;
+static volatile unsigned long next_event_time;
+static unsigned long prev_event_time;
+static volatile unsigned long usecs_per_tmr;	/* Length of the current interval */
+
+static struct sound_lowlev_timer *tmr;
+static DEFINE_SPINLOCK(lock);
+
+static unsigned long tmr2ticks(int tmr_value)
+{
+	/*
+	 *    Convert timer ticks to MIDI ticks
+	 */
+
+	unsigned long tmp;
+	unsigned long scale;
+
+	tmp = tmr_value * usecs_per_tmr;	/* Convert to usecs */
+	scale = (60 * 1000000) / (curr_tempo * curr_timebase);	/* usecs per MIDI tick */
+	return (tmp + (scale / 2)) / scale;
+}
+
+void reprogram_timer(void)
+{
+	unsigned long   usecs_per_tick;
+
+	/*
+	 *	The user is changing the timer rate before setting a timer
+	 *	slap, bad bad not allowed.
+	 */
+	 
+	if(!tmr)
+		return;
+		
+	usecs_per_tick = (60 * 1000000) / (curr_tempo * curr_timebase);
+
+	/*
+	 * Don't kill the system by setting too high timer rate
+	 */
+	if (usecs_per_tick < 2000)
+		usecs_per_tick = 2000;
+
+	usecs_per_tmr = tmr->tmr_start(tmr->dev, usecs_per_tick);
+}
+
+void sound_timer_syncinterval(unsigned int new_usecs)
+{
+	/*
+	 *    This routine is called by the hardware level if
+	 *      the clock frequency has changed for some reason.
+	 */
+	tmr_offs = tmr_ctr;
+	ticks_offs += tmr2ticks(tmr_ctr);
+	tmr_ctr = 0;
+	usecs_per_tmr = new_usecs;
+}
+EXPORT_SYMBOL(sound_timer_syncinterval);
+
+static void tmr_reset(void)
+{
+	unsigned long   flags;
+
+	spin_lock_irqsave(&lock,flags);
+	tmr_offs = 0;
+	ticks_offs = 0;
+	tmr_ctr = 0;
+	next_event_time = (unsigned long) -1;
+	prev_event_time = 0;
+	curr_ticks = 0;
+	spin_unlock_irqrestore(&lock,flags);
+}
+
+static int timer_open(int dev, int mode)
+{
+	if (opened)
+		return -EBUSY;
+	tmr_reset();
+	curr_tempo = 60;
+	curr_timebase = 100;
+	opened = 1;
+	reprogram_timer();
+	return 0;
+}
+
+static void timer_close(int dev)
+{
+	opened = tmr_running = 0;
+	tmr->tmr_disable(tmr->dev);
+}
+
+static int timer_event(int dev, unsigned char *event)
+{
+	unsigned char cmd = event[1];
+	unsigned long parm = *(int *) &event[4];
+
+	switch (cmd)
+	{
+		case TMR_WAIT_REL:
+			parm += prev_event_time;
+		case TMR_WAIT_ABS:
+			if (parm > 0)
+			{
+				long time;
+
+				if (parm <= curr_ticks)	/* It's the time */
+					return TIMER_NOT_ARMED;
+				time = parm;
+				next_event_time = prev_event_time = time;
+				return TIMER_ARMED;
+			}
+			break;
+
+		case TMR_START:
+			tmr_reset();
+			tmr_running = 1;
+			reprogram_timer();
+			break;
+
+		case TMR_STOP:
+			tmr_running = 0;
+			break;
+
+		case TMR_CONTINUE:
+			tmr_running = 1;
+			reprogram_timer();
+			break;
+
+		case TMR_TEMPO:
+			if (parm)
+			{
+				if (parm < 8)
+					parm = 8;
+				if (parm > 250)
+					parm = 250;
+				tmr_offs = tmr_ctr;
+				ticks_offs += tmr2ticks(tmr_ctr);
+				tmr_ctr = 0;
+				curr_tempo = parm;
+				reprogram_timer();
+			}
+			break;
+
+		case TMR_ECHO:
+			seq_copy_to_input(event, 8);
+			break;
+
+		default:;
+	}
+	return TIMER_NOT_ARMED;
+}
+
+static unsigned long timer_get_time(int dev)
+{
+	if (!opened)
+		return 0;
+	return curr_ticks;
+}
+
+static int timer_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+	int __user *p = arg;
+	int val;
+
+	switch (cmd) 
+	{
+		case SNDCTL_TMR_SOURCE:
+			val = TMR_INTERNAL;
+			break;
+
+		case SNDCTL_TMR_START:
+			tmr_reset();
+			tmr_running = 1;
+			return 0;
+		
+		case SNDCTL_TMR_STOP:
+			tmr_running = 0;
+			return 0;
+
+		case SNDCTL_TMR_CONTINUE:
+			tmr_running = 1;
+			return 0;
+
+		case SNDCTL_TMR_TIMEBASE:
+			if (get_user(val, p))
+				return -EFAULT;
+			if (val) 
+			{
+				if (val < 1)
+					val = 1;
+				if (val > 1000)
+					val = 1000;
+				curr_timebase = val;
+			}
+			val = curr_timebase;
+			break;
+
+		case SNDCTL_TMR_TEMPO:
+			if (get_user(val, p))
+				return -EFAULT;
+			if (val) 
+			{
+				if (val < 8)
+					val = 8;
+				if (val > 250)
+					val = 250;
+				tmr_offs = tmr_ctr;
+				ticks_offs += tmr2ticks(tmr_ctr);
+				tmr_ctr = 0;
+				curr_tempo = val;
+				reprogram_timer();
+			}
+			val = curr_tempo;
+			break;
+
+		case SNDCTL_SEQ_CTRLRATE:
+			if (get_user(val, p))
+				return -EFAULT;
+			if (val != 0)	/* Can't change */
+				return -EINVAL;
+			val = ((curr_tempo * curr_timebase) + 30) / 60;
+			break;
+		
+		case SNDCTL_SEQ_GETTIME:
+			val = curr_ticks;
+			break;
+		
+		case SNDCTL_TMR_METRONOME:
+		default:
+			return -EINVAL;
+	}
+	return put_user(val, p);
+}
+
+static void timer_arm(int dev, long time)
+{
+	if (time < 0)
+		time = curr_ticks + 1;
+	else if (time <= curr_ticks)	/* It's the time */
+		return;
+
+	next_event_time = prev_event_time = time;
+	return;
+}
+
+static struct sound_timer_operations sound_timer =
+{
+	.owner		= THIS_MODULE,
+	.info		= {"Sound Timer", 0},
+	.priority	= 1,	/* Priority */
+	.devlink	= 0,	/* Local device link */
+	.open		= timer_open,
+	.close		= timer_close,
+	.event		= timer_event,
+	.get_time	= timer_get_time,
+	.ioctl		= timer_ioctl,
+	.arm_timer	= timer_arm
+};
+
+void sound_timer_interrupt(void)
+{
+	unsigned long flags;
+	
+	if (!opened)
+		return;
+
+	tmr->tmr_restart(tmr->dev);
+
+	if (!tmr_running)
+		return;
+
+	spin_lock_irqsave(&lock,flags);
+	tmr_ctr++;
+	curr_ticks = ticks_offs + tmr2ticks(tmr_ctr);
+
+	if (curr_ticks >= next_event_time)
+	{
+		next_event_time = (unsigned long) -1;
+		sequencer_timer(0);
+	}
+	spin_unlock_irqrestore(&lock,flags);
+}
+EXPORT_SYMBOL(sound_timer_interrupt);
+
+void  sound_timer_init(struct sound_lowlev_timer *t, char *name)
+{
+	int n;
+
+	if (initialized)
+	{
+		if (t->priority <= tmr->priority)
+			return;	/* There is already a similar or better timer */
+		tmr = t;
+		return;
+	}
+	initialized = 1;
+	tmr = t;
+
+	n = sound_alloc_timerdev();
+	if (n == -1)
+		n = 0;		/* Overwrite the system timer */
+	strcpy(sound_timer.info.name, name);
+	sound_timer_devs[n] = &sound_timer;
+}
+EXPORT_SYMBOL(sound_timer_init);
+
diff --git a/linux/sound/oss/soundcard.c b/linux/sound/oss/soundcard.c
new file mode 100644
index 0000000..fcb14a0
--- /dev/null
+++ b/linux/sound/oss/soundcard.c
@@ -0,0 +1,755 @@
+/*
+ * linux/sound/oss/soundcard.c
+ *
+ * Sound card driver for Linux
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ *
+ * Thomas Sailer     : ioctl code reworked (vmalloc/vfree removed)
+ *                   integrated sound_switch.c
+ * Stefan Reinauer   : integrated /proc/sound (equals to /dev/sndstat,
+ *                   which should disappear in the near future)
+ * Eric Dumas	     : devfs support (22-Jan-98) <dumas@linux.eu.org> with
+ *                   fixups by C. Scott Ananian <cananian@alumni.princeton.edu>
+ * Richard Gooch     : moved common (non OSS-specific) devices to sound_core.c
+ * Rob Riggs	     : Added persistent DMA buffers support (1998/10/17)
+ * Christoph Hellwig : Some cleanup work (2000/03/01)
+ */
+
+
+#include "sound_config.h"
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/fcntl.h>
+#include <linux/ctype.h>
+#include <linux/stddef.h>
+#include <linux/kmod.h>
+#include <linux/kernel.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <linux/wait.h>
+#include <linux/ioport.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/device.h>
+
+/*
+ * This ought to be moved into include/asm/dma.h
+ */
+#ifndef valid_dma
+#define valid_dma(n) ((n) >= 0 && (n) < MAX_DMA_CHANNELS && (n) != 4)
+#endif
+
+/*
+ * Table for permanently allocated memory (used when unloading the module)
+ */
+void *          sound_mem_blocks[MAX_MEM_BLOCKS];
+static DEFINE_MUTEX(soundcard_mutex);
+int             sound_nblocks = 0;
+
+/* Persistent DMA buffers */
+#ifdef CONFIG_SOUND_DMAP
+int             sound_dmap_flag = 1;
+#else
+int             sound_dmap_flag = 0;
+#endif
+
+static char     dma_alloc_map[MAX_DMA_CHANNELS];
+
+#define DMA_MAP_UNAVAIL		0
+#define DMA_MAP_FREE		1
+#define DMA_MAP_BUSY		2
+
+
+unsigned long seq_time = 0;	/* Time for /dev/sequencer */
+extern struct class *sound_class;
+
+/*
+ * Table for configurable mixer volume handling
+ */
+static mixer_vol_table mixer_vols[MAX_MIXER_DEV];
+static int num_mixer_volumes;
+
+int *load_mixer_volumes(char *name, int *levels, int present)
+{
+	int             i, n;
+
+	for (i = 0; i < num_mixer_volumes; i++) {
+		if (strncmp(name, mixer_vols[i].name, 32) == 0) {
+			if (present)
+				mixer_vols[i].num = i;
+			return mixer_vols[i].levels;
+		}
+	}
+	if (num_mixer_volumes >= MAX_MIXER_DEV) {
+		printk(KERN_ERR "Sound: Too many mixers (%s)\n", name);
+		return levels;
+	}
+	n = num_mixer_volumes++;
+
+	strncpy(mixer_vols[n].name, name, 32);
+
+	if (present)
+		mixer_vols[n].num = n;
+	else
+		mixer_vols[n].num = -1;
+
+	for (i = 0; i < 32; i++)
+		mixer_vols[n].levels[i] = levels[i];
+	return mixer_vols[n].levels;
+}
+EXPORT_SYMBOL(load_mixer_volumes);
+
+static int set_mixer_levels(void __user * arg)
+{
+        /* mixer_vol_table is 174 bytes, so IMHO no reason to not allocate it on the stack */
+	mixer_vol_table buf;   
+
+	if (__copy_from_user(&buf, arg, sizeof(buf)))
+		return -EFAULT;
+	load_mixer_volumes(buf.name, buf.levels, 0);
+	if (__copy_to_user(arg, &buf, sizeof(buf)))
+		return -EFAULT;
+	return 0;
+}
+
+static int get_mixer_levels(void __user * arg)
+{
+	int n;
+
+	if (__get_user(n, (int __user *)(&(((mixer_vol_table __user *)arg)->num))))
+		return -EFAULT;
+	if (n < 0 || n >= num_mixer_volumes)
+		return -EINVAL;
+	if (__copy_to_user(arg, &mixer_vols[n], sizeof(mixer_vol_table)))
+		return -EFAULT;
+	return 0;
+}
+
+/* 4K page size but our output routines use some slack for overruns */
+#define PROC_BLOCK_SIZE (3*1024)
+
+static ssize_t sound_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+	int dev = iminor(file->f_path.dentry->d_inode);
+	int ret = -EINVAL;
+
+	/*
+	 *	The OSS drivers aren't remotely happy without this locking,
+	 *	and unless someone fixes them when they are about to bite the
+	 *	big one anyway, we might as well bandage here..
+	 */
+	 
+	mutex_lock(&soundcard_mutex);
+	
+	DEB(printk("sound_read(dev=%d, count=%d)\n", dev, count));
+	switch (dev & 0x0f) {
+	case SND_DEV_DSP:
+	case SND_DEV_DSP16:
+	case SND_DEV_AUDIO:
+		ret = audio_read(dev, file, buf, count);
+		break;
+
+	case SND_DEV_SEQ:
+	case SND_DEV_SEQ2:
+		ret = sequencer_read(dev, file, buf, count);
+		break;
+
+	case SND_DEV_MIDIN:
+		ret = MIDIbuf_read(dev, file, buf, count);
+	}
+	mutex_unlock(&soundcard_mutex);
+	return ret;
+}
+
+static ssize_t sound_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	int dev = iminor(file->f_path.dentry->d_inode);
+	int ret = -EINVAL;
+	
+	mutex_lock(&soundcard_mutex);
+	DEB(printk("sound_write(dev=%d, count=%d)\n", dev, count));
+	switch (dev & 0x0f) {
+	case SND_DEV_SEQ:
+	case SND_DEV_SEQ2:
+		ret =  sequencer_write(dev, file, buf, count);
+		break;
+
+	case SND_DEV_DSP:
+	case SND_DEV_DSP16:
+	case SND_DEV_AUDIO:
+		ret = audio_write(dev, file, buf, count);
+		break;
+
+	case SND_DEV_MIDIN:
+		ret =  MIDIbuf_write(dev, file, buf, count);
+		break;
+	}
+	mutex_unlock(&soundcard_mutex);
+	return ret;
+}
+
+static int sound_open(struct inode *inode, struct file *file)
+{
+	int dev = iminor(inode);
+	int retval;
+
+	DEB(printk("sound_open(dev=%d)\n", dev));
+	if ((dev >= SND_NDEVS) || (dev < 0)) {
+		printk(KERN_ERR "Invalid minor device %d\n", dev);
+		return -ENXIO;
+	}
+	mutex_lock(&soundcard_mutex);
+	switch (dev & 0x0f) {
+	case SND_DEV_CTL:
+		dev >>= 4;
+		if (dev >= 0 && dev < MAX_MIXER_DEV && mixer_devs[dev] == NULL) {
+			request_module("mixer%d", dev);
+		}
+		retval = -ENXIO;
+		if (dev && (dev >= num_mixers || mixer_devs[dev] == NULL))
+			break;
+	
+		if (!try_module_get(mixer_devs[dev]->owner))
+			break;
+
+		retval = 0;
+		break;
+
+	case SND_DEV_SEQ:
+	case SND_DEV_SEQ2:
+		retval = sequencer_open(dev, file);
+		break;
+
+	case SND_DEV_MIDIN:
+		retval = MIDIbuf_open(dev, file);
+		break;
+
+	case SND_DEV_DSP:
+	case SND_DEV_DSP16:
+	case SND_DEV_AUDIO:
+		retval = audio_open(dev, file);
+		break;
+
+	default:
+		printk(KERN_ERR "Invalid minor device %d\n", dev);
+		retval = -ENXIO;
+	}
+
+	mutex_unlock(&soundcard_mutex);
+	return retval;
+}
+
+static int sound_release(struct inode *inode, struct file *file)
+{
+	int dev = iminor(inode);
+
+	mutex_lock(&soundcard_mutex);
+	DEB(printk("sound_release(dev=%d)\n", dev));
+	switch (dev & 0x0f) {
+	case SND_DEV_CTL:
+		module_put(mixer_devs[dev >> 4]->owner);
+		break;
+		
+	case SND_DEV_SEQ:
+	case SND_DEV_SEQ2:
+		sequencer_release(dev, file);
+		break;
+
+	case SND_DEV_MIDIN:
+		MIDIbuf_release(dev, file);
+		break;
+
+	case SND_DEV_DSP:
+	case SND_DEV_DSP16:
+	case SND_DEV_AUDIO:
+		audio_release(dev, file);
+		break;
+
+	default:
+		printk(KERN_ERR "Sound error: Releasing unknown device 0x%02x\n", dev);
+	}
+	mutex_unlock(&soundcard_mutex);
+
+	return 0;
+}
+
+static int get_mixer_info(int dev, void __user *arg)
+{
+	mixer_info info;
+	memset(&info, 0, sizeof(info));
+	strlcpy(info.id, mixer_devs[dev]->id, sizeof(info.id));
+	strlcpy(info.name, mixer_devs[dev]->name, sizeof(info.name));
+	info.modify_counter = mixer_devs[dev]->modify_counter;
+	if (__copy_to_user(arg, &info,  sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int get_old_mixer_info(int dev, void __user *arg)
+{
+	_old_mixer_info info;
+	memset(&info, 0, sizeof(info));
+ 	strlcpy(info.id, mixer_devs[dev]->id, sizeof(info.id));
+ 	strlcpy(info.name, mixer_devs[dev]->name, sizeof(info.name));
+ 	if (copy_to_user(arg, &info,  sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int sound_mixer_ioctl(int mixdev, unsigned int cmd, void __user *arg)
+{
+ 	if (mixdev < 0 || mixdev >= MAX_MIXER_DEV)
+ 		return -ENXIO;
+ 	/* Try to load the mixer... */
+ 	if (mixer_devs[mixdev] == NULL) {
+ 		request_module("mixer%d", mixdev);
+ 	}
+ 	if (mixdev >= num_mixers || !mixer_devs[mixdev])
+ 		return -ENXIO;
+	if (cmd == SOUND_MIXER_INFO)
+		return get_mixer_info(mixdev, arg);
+	if (cmd == SOUND_OLD_MIXER_INFO)
+		return get_old_mixer_info(mixdev, arg);
+	if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+		mixer_devs[mixdev]->modify_counter++;
+	if (!mixer_devs[mixdev]->ioctl)
+		return -EINVAL;
+	return mixer_devs[mixdev]->ioctl(mixdev, cmd, arg);
+}
+
+static long sound_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int len = 0, dtype;
+	int dev = iminor(file->f_dentry->d_inode);
+	long ret = -EINVAL;
+	void __user *p = (void __user *)arg;
+
+	if (_SIOC_DIR(cmd) != _SIOC_NONE && _SIOC_DIR(cmd) != 0) {
+		/*
+		 * Have to validate the address given by the process.
+		 */
+		len = _SIOC_SIZE(cmd);
+		if (len < 1 || len > 65536 || !p)
+			return -EFAULT;
+		if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+			if (!access_ok(VERIFY_READ, p, len))
+				return -EFAULT;
+		if (_SIOC_DIR(cmd) & _SIOC_READ)
+			if (!access_ok(VERIFY_WRITE, p, len))
+				return -EFAULT;
+	}
+	DEB(printk("sound_ioctl(dev=%d, cmd=0x%x, arg=0x%x)\n", dev, cmd, arg));
+	if (cmd == OSS_GETVERSION)
+		return __put_user(SOUND_VERSION, (int __user *)p);
+	
+	mutex_lock(&soundcard_mutex);
+	if (_IOC_TYPE(cmd) == 'M' && num_mixers > 0 &&   /* Mixer ioctl */
+	    (dev & 0x0f) != SND_DEV_CTL) {              
+		dtype = dev & 0x0f;
+		switch (dtype) {
+		case SND_DEV_DSP:
+		case SND_DEV_DSP16:
+		case SND_DEV_AUDIO:
+			ret = sound_mixer_ioctl(audio_devs[dev >> 4]->mixer_dev,
+						 cmd, p);
+			break;			
+		default:
+			ret = sound_mixer_ioctl(dev >> 4, cmd, p);
+			break;
+		}
+		mutex_unlock(&soundcard_mutex);
+		return ret;
+	}
+
+	switch (dev & 0x0f) {
+	case SND_DEV_CTL:
+		if (cmd == SOUND_MIXER_GETLEVELS)
+			ret = get_mixer_levels(p);
+		else if (cmd == SOUND_MIXER_SETLEVELS)
+			ret = set_mixer_levels(p);
+		else
+			ret = sound_mixer_ioctl(dev >> 4, cmd, p);
+		break;
+
+	case SND_DEV_SEQ:
+	case SND_DEV_SEQ2:
+		ret = sequencer_ioctl(dev, file, cmd, p);
+		break;
+
+	case SND_DEV_DSP:
+	case SND_DEV_DSP16:
+	case SND_DEV_AUDIO:
+		ret = audio_ioctl(dev, file, cmd, p);
+		break;
+
+	case SND_DEV_MIDIN:
+		ret = MIDIbuf_ioctl(dev, file, cmd, p);
+		break;
+
+	}
+	mutex_unlock(&soundcard_mutex);
+	return ret;
+}
+
+static unsigned int sound_poll(struct file *file, poll_table * wait)
+{
+	struct inode *inode = file->f_path.dentry->d_inode;
+	int dev = iminor(inode);
+
+	DEB(printk("sound_poll(dev=%d)\n", dev));
+	switch (dev & 0x0f) {
+	case SND_DEV_SEQ:
+	case SND_DEV_SEQ2:
+		return sequencer_poll(dev, file, wait);
+
+	case SND_DEV_MIDIN:
+		return MIDIbuf_poll(dev, file, wait);
+
+	case SND_DEV_DSP:
+	case SND_DEV_DSP16:
+	case SND_DEV_AUDIO:
+		return DMAbuf_poll(file, dev >> 4, wait);
+	}
+	return 0;
+}
+
+static int sound_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	int dev_class;
+	unsigned long size;
+	struct dma_buffparms *dmap = NULL;
+	int dev = iminor(file->f_path.dentry->d_inode);
+
+	dev_class = dev & 0x0f;
+	dev >>= 4;
+
+	if (dev_class != SND_DEV_DSP && dev_class != SND_DEV_DSP16 && dev_class != SND_DEV_AUDIO) {
+		printk(KERN_ERR "Sound: mmap() not supported for other than audio devices\n");
+		return -EINVAL;
+	}
+	mutex_lock(&soundcard_mutex);
+	if (vma->vm_flags & VM_WRITE)	/* Map write and read/write to the output buf */
+		dmap = audio_devs[dev]->dmap_out;
+	else if (vma->vm_flags & VM_READ)
+		dmap = audio_devs[dev]->dmap_in;
+	else {
+		printk(KERN_ERR "Sound: Undefined mmap() access\n");
+		mutex_unlock(&soundcard_mutex);
+		return -EINVAL;
+	}
+
+	if (dmap == NULL) {
+		printk(KERN_ERR "Sound: mmap() error. dmap == NULL\n");
+		mutex_unlock(&soundcard_mutex);
+		return -EIO;
+	}
+	if (dmap->raw_buf == NULL) {
+		printk(KERN_ERR "Sound: mmap() called when raw_buf == NULL\n");
+		mutex_unlock(&soundcard_mutex);
+		return -EIO;
+	}
+	if (dmap->mapping_flags) {
+		printk(KERN_ERR "Sound: mmap() called twice for the same DMA buffer\n");
+		mutex_unlock(&soundcard_mutex);
+		return -EIO;
+	}
+	if (vma->vm_pgoff != 0) {
+		printk(KERN_ERR "Sound: mmap() offset must be 0.\n");
+		mutex_unlock(&soundcard_mutex);
+		return -EINVAL;
+	}
+	size = vma->vm_end - vma->vm_start;
+
+	if (size != dmap->bytes_in_use) {
+		printk(KERN_WARNING "Sound: mmap() size = %ld. Should be %d\n", size, dmap->bytes_in_use);
+	}
+	if (remap_pfn_range(vma, vma->vm_start,
+			virt_to_phys(dmap->raw_buf) >> PAGE_SHIFT,
+			vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
+		mutex_unlock(&soundcard_mutex);
+		return -EAGAIN;
+	}
+
+	dmap->mapping_flags |= DMA_MAP_MAPPED;
+
+	if( audio_devs[dev]->d->mmap)
+		audio_devs[dev]->d->mmap(dev);
+
+	memset(dmap->raw_buf,
+	       dmap->neutral_byte,
+	       dmap->bytes_in_use);
+	mutex_unlock(&soundcard_mutex);
+	return 0;
+}
+
+const struct file_operations oss_sound_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= sound_read,
+	.write		= sound_write,
+	.poll		= sound_poll,
+	.unlocked_ioctl	= sound_ioctl,
+	.mmap		= sound_mmap,
+	.open		= sound_open,
+	.release	= sound_release,
+};
+
+/*
+ *	Create the required special subdevices
+ */
+ 
+static int create_special_devices(void)
+{
+	int seq1,seq2;
+	seq1=register_sound_special(&oss_sound_fops, 1);
+	if(seq1==-1)
+		goto bad;
+	seq2=register_sound_special(&oss_sound_fops, 8);
+	if(seq2!=-1)
+		return 0;
+	unregister_sound_special(1);
+bad:
+	return -1;
+}
+
+
+/* These device names follow the official Linux device list,
+ * Documentation/devices.txt.  Let us know if there are other
+ * common names we should support for compatibility.
+ * Only those devices not created by the generic code in sound_core.c are
+ * registered here.
+ */
+static const struct {
+	unsigned short minor;
+	char *name;
+	umode_t mode;
+	int *num;
+} dev_list[] = { /* list of minor devices */
+/* seems to be some confusion here -- this device is not in the device list */
+	{SND_DEV_DSP16,     "dspW",	 S_IWUGO | S_IRUSR | S_IRGRP,
+	 &num_audiodevs},
+	{SND_DEV_AUDIO,     "audio",	 S_IWUGO | S_IRUSR | S_IRGRP,
+	 &num_audiodevs},
+};
+
+static int dmabuf;
+static int dmabug;
+
+module_param(dmabuf, int, 0444);
+module_param(dmabug, int, 0444);
+
+static int __init oss_init(void)
+{
+	int             err;
+	int i, j;
+	
+#ifdef CONFIG_PCI
+	if(dmabug)
+		isa_dma_bridge_buggy = dmabug;
+#endif
+
+	err = create_special_devices();
+	if (err) {
+		printk(KERN_ERR "sound: driver already loaded/included in kernel\n");
+		return err;
+	}
+
+	/* Protecting the innocent */
+	sound_dmap_flag = (dmabuf > 0 ? 1 : 0);
+
+	for (i = 0; i < ARRAY_SIZE(dev_list); i++) {
+		device_create(sound_class, NULL,
+			      MKDEV(SOUND_MAJOR, dev_list[i].minor), NULL,
+			      "%s", dev_list[i].name);
+
+		if (!dev_list[i].num)
+			continue;
+
+		for (j = 1; j < *dev_list[i].num; j++)
+			device_create(sound_class, NULL,
+				      MKDEV(SOUND_MAJOR,
+					    dev_list[i].minor + (j*0x10)),
+				      NULL, "%s%d", dev_list[i].name, j);
+	}
+
+	if (sound_nblocks >= MAX_MEM_BLOCKS - 1)
+		printk(KERN_ERR "Sound warning: Deallocation table was too small.\n");
+	
+	return 0;
+}
+
+static void __exit oss_cleanup(void)
+{
+	int i, j;
+
+	for (i = 0; i < ARRAY_SIZE(dev_list); i++) {
+		device_destroy(sound_class, MKDEV(SOUND_MAJOR, dev_list[i].minor));
+		if (!dev_list[i].num)
+			continue;
+		for (j = 1; j < *dev_list[i].num; j++)
+			device_destroy(sound_class, MKDEV(SOUND_MAJOR, dev_list[i].minor + (j*0x10)));
+	}
+	
+	unregister_sound_special(1);
+	unregister_sound_special(8);
+
+	sound_stop_timer();
+
+	sequencer_unload();
+
+	for (i = 0; i < MAX_DMA_CHANNELS; i++)
+		if (dma_alloc_map[i] != DMA_MAP_UNAVAIL) {
+			printk(KERN_ERR "Sound: Hmm, DMA%d was left allocated - fixed\n", i);
+			sound_free_dma(i);
+		}
+
+	for (i = 0; i < sound_nblocks; i++)
+		vfree(sound_mem_blocks[i]);
+
+}
+
+module_init(oss_init);
+module_exit(oss_cleanup);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("OSS Sound subsystem");
+MODULE_AUTHOR("Hannu Savolainen, et al.");
+
+
+int sound_alloc_dma(int chn, char *deviceID)
+{
+	int err;
+
+	if ((err = request_dma(chn, deviceID)) != 0)
+		return err;
+
+	dma_alloc_map[chn] = DMA_MAP_FREE;
+
+	return 0;
+}
+EXPORT_SYMBOL(sound_alloc_dma);
+
+int sound_open_dma(int chn, char *deviceID)
+{
+	if (!valid_dma(chn)) {
+		printk(KERN_ERR "sound_open_dma: Invalid DMA channel %d\n", chn);
+		return 1;
+	}
+
+	if (dma_alloc_map[chn] != DMA_MAP_FREE) {
+		printk("sound_open_dma: DMA channel %d busy or not allocated (%d)\n", chn, dma_alloc_map[chn]);
+		return 1;
+	}
+	dma_alloc_map[chn] = DMA_MAP_BUSY;
+	return 0;
+}
+EXPORT_SYMBOL(sound_open_dma);
+
+void sound_free_dma(int chn)
+{
+	if (dma_alloc_map[chn] == DMA_MAP_UNAVAIL) {
+		/* printk( "sound_free_dma: Bad access to DMA channel %d\n",  chn); */
+		return;
+	}
+	free_dma(chn);
+	dma_alloc_map[chn] = DMA_MAP_UNAVAIL;
+}
+EXPORT_SYMBOL(sound_free_dma);
+
+void sound_close_dma(int chn)
+{
+	if (dma_alloc_map[chn] != DMA_MAP_BUSY) {
+		printk(KERN_ERR "sound_close_dma: Bad access to DMA channel %d\n", chn);
+		return;
+	}
+	dma_alloc_map[chn] = DMA_MAP_FREE;
+}
+EXPORT_SYMBOL(sound_close_dma);
+
+static void do_sequencer_timer(unsigned long dummy)
+{
+	sequencer_timer(0);
+}
+
+
+static DEFINE_TIMER(seq_timer, do_sequencer_timer, 0, 0);
+
+void request_sound_timer(int count)
+{
+	extern unsigned long seq_time;
+
+	if (count < 0) {
+		seq_timer.expires = (-count) + jiffies;
+		add_timer(&seq_timer);
+		return;
+	}
+	count += seq_time;
+
+	count -= jiffies;
+
+	if (count < 1)
+		count = 1;
+
+	seq_timer.expires = (count) + jiffies;
+	add_timer(&seq_timer);
+}
+
+void sound_stop_timer(void)
+{
+	del_timer(&seq_timer);
+}
+
+void conf_printf(char *name, struct address_info *hw_config)
+{
+#ifndef CONFIG_SOUND_TRACEINIT
+	return;
+#else
+	printk("<%s> at 0x%03x", name, hw_config->io_base);
+
+	if (hw_config->irq)
+		printk(" irq %d", (hw_config->irq > 0) ? hw_config->irq : -hw_config->irq);
+
+	if (hw_config->dma != -1 || hw_config->dma2 != -1)
+	{
+		printk(" dma %d", hw_config->dma);
+		if (hw_config->dma2 != -1)
+			printk(",%d", hw_config->dma2);
+	}
+	printk("\n");
+#endif
+}
+EXPORT_SYMBOL(conf_printf);
+
+void conf_printf2(char *name, int base, int irq, int dma, int dma2)
+{
+#ifndef CONFIG_SOUND_TRACEINIT
+	return;
+#else
+	printk("<%s> at 0x%03x", name, base);
+
+	if (irq)
+		printk(" irq %d", (irq > 0) ? irq : -irq);
+
+	if (dma != -1 || dma2 != -1)
+	{
+		  printk(" dma %d", dma);
+		  if (dma2 != -1)
+			  printk(",%d", dma2);
+	}
+	printk("\n");
+#endif
+}
+EXPORT_SYMBOL(conf_printf2);
+
diff --git a/linux/sound/oss/soundvers.h b/linux/sound/oss/soundvers.h
new file mode 100644
index 0000000..e9084d2
--- /dev/null
+++ b/linux/sound/oss/soundvers.h
@@ -0,0 +1,2 @@
+#define SOUND_VERSION_STRING "3.8s2++-971130"
+#define SOUND_INTERNAL_VERSION 0x030804
diff --git a/linux/sound/oss/swarm_cs4297a.c b/linux/sound/oss/swarm_cs4297a.c
new file mode 100644
index 0000000..44357d8
--- /dev/null
+++ b/linux/sound/oss/swarm_cs4297a.c
@@ -0,0 +1,2768 @@
+/*******************************************************************************
+*
+*      "swarm_cs4297a.c" --  Cirrus Logic-Crystal CS4297a linux audio driver.
+*
+*      Copyright (C) 2001  Broadcom Corporation.
+*      Copyright (C) 2000,2001  Cirrus Logic Corp.  
+*            -- adapted from drivers by Thomas Sailer, 
+*            -- but don't bug him; Problems should go to:
+*            -- tom woller (twoller@crystal.cirrus.com) or
+*               (audio@crystal.cirrus.com).
+*            -- adapted from cs4281 PCI driver for cs4297a on
+*               BCM1250 Synchronous Serial interface
+*               (Kip Walker, Broadcom Corp.)
+*      Copyright (C) 2004  Maciej W. Rozycki
+*      Copyright (C) 2005 Ralf Baechle (ralf@linux-mips.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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+* Module command line parameters:
+*   none
+*
+*  Supported devices:
+*  /dev/dsp    standard /dev/dsp device, (mostly) OSS compatible
+*  /dev/mixer  standard /dev/mixer device, (mostly) OSS compatible
+*  /dev/midi   simple MIDI UART interface, no ioctl
+*
+* Modification History
+* 08/20/00 trw - silence and no stopping DAC until release
+* 08/23/00 trw - added CS_DBG statements, fix interrupt hang issue on DAC stop.
+* 09/18/00 trw - added 16bit only record with conversion 
+* 09/24/00 trw - added Enhanced Full duplex (separate simultaneous 
+*                capture/playback rates)
+* 10/03/00 trw - fixed mmap (fixed GRECORD and the XMMS mmap test plugin  
+*                libOSSm.so)
+* 10/11/00 trw - modified for 2.4.0-test9 kernel enhancements (NR_MAP removal)
+* 11/03/00 trw - fixed interrupt loss/stutter, added debug.
+* 11/10/00 bkz - added __devinit to cs4297a_hw_init()
+* 11/10/00 trw - fixed SMP and capture spinlock hang.
+* 12/04/00 trw - cleaned up CSDEBUG flags and added "defaultorder" moduleparm.
+* 12/05/00 trw - fixed polling (myth2), and added underrun swptr fix.
+* 12/08/00 trw - added PM support. 
+* 12/14/00 trw - added wrapper code, builds under 2.4.0, 2.2.17-20, 2.2.17-8 
+*		 (RH/Dell base), 2.2.18, 2.2.12.  cleaned up code mods by ident.
+* 12/19/00 trw - added PM support for 2.2 base (apm_callback). other PM cleanup.
+* 12/21/00 trw - added fractional "defaultorder" inputs. if >100 then use 
+*		 defaultorder-100 as power of 2 for the buffer size. example:
+*		 106 = 2^(106-100) = 2^6 = 64 bytes for the buffer size.
+*
+*******************************************************************************/
+
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/sound.h>
+#include <linux/slab.h>
+#include <linux/soundcard.h>
+#include <linux/ac97_codec.h>
+#include <linux/pci.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/mutex.h>
+#include <linux/kernel.h>
+
+#include <asm/byteorder.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include <asm/sibyte/sb1250_regs.h>
+#include <asm/sibyte/sb1250_int.h>
+#include <asm/sibyte/sb1250_dma.h>
+#include <asm/sibyte/sb1250_scd.h>
+#include <asm/sibyte/sb1250_syncser.h>
+#include <asm/sibyte/sb1250_mac.h>
+#include <asm/sibyte/sb1250.h>
+
+struct cs4297a_state;
+
+static DEFINE_MUTEX(swarm_cs4297a_mutex);
+static void stop_dac(struct cs4297a_state *s);
+static void stop_adc(struct cs4297a_state *s);
+static void start_dac(struct cs4297a_state *s);
+static void start_adc(struct cs4297a_state *s);
+#undef OSS_DOCUMENTED_MIXER_SEMANTICS
+
+// --------------------------------------------------------------------- 
+
+#define CS4297a_MAGIC           0xf00beef1
+
+// buffer order determines the size of the dma buffer for the driver.
+// under Linux, a smaller buffer allows more responsiveness from many of the 
+// applications (e.g. games).  A larger buffer allows some of the apps (esound) 
+// to not underrun the dma buffer as easily.  As default, use 32k (order=3)
+// rather than 64k as some of the games work more responsively.
+// log base 2( buff sz = 32k).
+
+//
+// Turn on/off debugging compilation by commenting out "#define CSDEBUG"
+//
+#define CSDEBUG 0
+#if CSDEBUG
+#define CSDEBUG_INTERFACE 1
+#else
+#undef CSDEBUG_INTERFACE
+#endif
+//
+// cs_debugmask areas
+//
+#define CS_INIT	 	0x00000001	// initialization and probe functions
+#define CS_ERROR 	0x00000002	// tmp debugging bit placeholder
+#define CS_INTERRUPT	0x00000004	// interrupt handler (separate from all other)
+#define CS_FUNCTION 	0x00000008	// enter/leave functions
+#define CS_WAVE_WRITE 	0x00000010	// write information for wave
+#define CS_WAVE_READ 	0x00000020	// read information for wave
+#define CS_AC97         0x00000040      // AC97 register access
+#define CS_DESCR        0x00000080      // descriptor management
+#define CS_OPEN		0x00000400	// all open functions in the driver
+#define CS_RELEASE	0x00000800	// all release functions in the driver
+#define CS_PARMS	0x00001000	// functional and operational parameters
+#define CS_IOCTL	0x00002000	// ioctl (non-mixer)
+#define CS_TMP		0x10000000	// tmp debug mask bit
+
+//
+// CSDEBUG is usual mode is set to 1, then use the
+// cs_debuglevel and cs_debugmask to turn on or off debugging.
+// Debug level of 1 has been defined to be kernel errors and info
+// that should be printed on any released driver.
+//
+#if CSDEBUG
+#define CS_DBGOUT(mask,level,x) if((cs_debuglevel >= (level)) && ((mask) & cs_debugmask) ) {x;}
+#else
+#define CS_DBGOUT(mask,level,x)
+#endif
+
+#if CSDEBUG
+static unsigned long cs_debuglevel = 4;	// levels range from 1-9
+static unsigned long cs_debugmask = CS_INIT /*| CS_IOCTL*/;
+module_param(cs_debuglevel, int, 0);
+module_param(cs_debugmask, int, 0);
+#endif
+#define CS_TRUE 	1
+#define CS_FALSE 	0
+
+#define CS_TYPE_ADC 0
+#define CS_TYPE_DAC 1
+
+#define SER_BASE    (A_SER_BASE_1 + KSEG1)
+#define SS_CSR(t)   (SER_BASE+t)
+#define SS_TXTBL(t) (SER_BASE+R_SER_TX_TABLE_BASE+(t*8))
+#define SS_RXTBL(t) (SER_BASE+R_SER_RX_TABLE_BASE+(t*8))
+
+#define FRAME_BYTES            32
+#define FRAME_SAMPLE_BYTES      4
+
+/* Should this be variable? */
+#define SAMPLE_BUF_SIZE        (16*1024)
+#define SAMPLE_FRAME_COUNT     (SAMPLE_BUF_SIZE / FRAME_SAMPLE_BYTES)
+/* The driver can explode/shrink the frames to/from a smaller sample
+   buffer */
+#define DMA_BLOAT_FACTOR       1
+#define DMA_DESCR              (SAMPLE_FRAME_COUNT / DMA_BLOAT_FACTOR)
+#define DMA_BUF_SIZE           (DMA_DESCR * FRAME_BYTES)
+
+/* Use the maxmium count (255 == 5.1 ms between interrupts) */
+#define DMA_INT_CNT            ((1 << S_DMA_INT_PKTCNT) - 1)
+
+/* Figure this out: how many TX DMAs ahead to schedule a reg access */
+#define REG_LATENCY            150
+
+#define FRAME_TX_US             20
+
+#define SERDMA_NEXTBUF(d,f) (((d)->f+1) % (d)->ringsz)
+
+static const char invalid_magic[] =
+    KERN_CRIT "cs4297a: invalid magic value\n";
+
+#define VALIDATE_STATE(s)                          \
+({                                                 \
+        if (!(s) || (s)->magic != CS4297a_MAGIC) { \
+                printk(invalid_magic);             \
+                return -ENXIO;                     \
+        }                                          \
+})
+
+struct list_head cs4297a_devs = { &cs4297a_devs, &cs4297a_devs };
+
+typedef struct serdma_descr_s {
+        u64 descr_a;
+        u64 descr_b;
+} serdma_descr_t;
+
+typedef unsigned long paddr_t;
+
+typedef struct serdma_s {
+        unsigned         ringsz;
+        serdma_descr_t  *descrtab;
+        serdma_descr_t  *descrtab_end;
+        paddr_t          descrtab_phys;
+        
+        serdma_descr_t  *descr_add;
+        serdma_descr_t  *descr_rem;
+        
+        u64  *dma_buf;           // buffer for DMA contents (frames)
+        paddr_t          dma_buf_phys;
+        u16  *sample_buf;		// tmp buffer for sample conversions
+        u16  *sb_swptr;
+        u16  *sb_hwptr;
+        u16  *sb_end;
+
+        dma_addr_t dmaaddr;
+//        unsigned buforder;	// Log base 2 of 'dma_buf' size in bytes..
+        unsigned numfrag;	// # of 'fragments' in the buffer.
+        unsigned fragshift;	// Log base 2 of fragment size.
+        unsigned hwptr, swptr;
+        unsigned total_bytes;	// # bytes process since open.
+        unsigned blocks;	// last returned blocks value GETOPTR
+        unsigned wakeup;	// interrupt occurred on block 
+        int count;
+        unsigned underrun;	// underrun flag
+        unsigned error;	// over/underrun 
+        wait_queue_head_t wait;
+        wait_queue_head_t reg_wait;
+        // redundant, but makes calculations easier 
+        unsigned fragsize;	// 2**fragshift..
+        unsigned sbufsz;	// 2**buforder.
+        unsigned fragsamples;
+        // OSS stuff 
+        unsigned mapped:1;	// Buffer mapped in cs4297a_mmap()?
+        unsigned ready:1;	// prog_dmabuf_dac()/adc() successful?
+        unsigned endcleared:1;
+        unsigned type:1;	// adc or dac buffer (CS_TYPE_XXX)
+        unsigned ossfragshift;
+        int ossmaxfrags;
+        unsigned subdivision;
+} serdma_t;
+
+struct cs4297a_state {
+	// magic 
+	unsigned int magic;
+
+	struct list_head list;
+
+	// soundcore stuff 
+	int dev_audio;
+	int dev_mixer;
+
+	// hardware resources 
+	unsigned int irq;
+
+        struct {
+                unsigned int rx_ovrrn; /* FIFO */
+                unsigned int rx_overflow; /* staging buffer */
+                unsigned int tx_underrun;
+                unsigned int rx_bad;
+                unsigned int rx_good;
+        } stats;
+
+	// mixer registers 
+	struct {
+		unsigned short vol[10];
+		unsigned int recsrc;
+		unsigned int modcnt;
+		unsigned short micpreamp;
+	} mix;
+
+	// wave stuff   
+	struct properties {
+		unsigned fmt;
+		unsigned fmt_original;	// original requested format
+		unsigned channels;
+		unsigned rate;
+	} prop_dac, prop_adc;
+	unsigned conversion:1;	// conversion from 16 to 8 bit in progress
+	unsigned ena;
+	spinlock_t lock;
+	struct mutex open_mutex;
+	struct mutex open_sem_adc;
+	struct mutex open_sem_dac;
+	fmode_t open_mode;
+	wait_queue_head_t open_wait;
+	wait_queue_head_t open_wait_adc;
+	wait_queue_head_t open_wait_dac;
+
+	dma_addr_t dmaaddr_sample_buf;
+	unsigned buforder_sample_buf;	// Log base 2 of 'dma_buf' size in bytes..
+
+        serdma_t dma_dac, dma_adc;
+
+        volatile u16 read_value;
+        volatile u16 read_reg;
+        volatile u64 reg_request;
+};
+
+#if 1
+#define prog_codec(a,b)
+#define dealloc_dmabuf(a,b);
+#endif
+
+static int prog_dmabuf_adc(struct cs4297a_state *s)
+{
+	s->dma_adc.ready = 1;
+	return 0;
+}
+
+
+static int prog_dmabuf_dac(struct cs4297a_state *s)
+{
+	s->dma_dac.ready = 1;
+	return 0;
+}
+
+static void clear_advance(void *buf, unsigned bsize, unsigned bptr,
+			  unsigned len, unsigned char c)
+{
+	if (bptr + len > bsize) {
+		unsigned x = bsize - bptr;
+		memset(((char *) buf) + bptr, c, x);
+		bptr = 0;
+		len -= x;
+	}
+	CS_DBGOUT(CS_WAVE_WRITE, 4, printk(KERN_INFO
+		"cs4297a: clear_advance(): memset %d at 0x%.8x for %d size \n",
+			(unsigned)c, (unsigned)((char *) buf) + bptr, len));
+	memset(((char *) buf) + bptr, c, len);
+}
+
+#if CSDEBUG
+
+// DEBUG ROUTINES
+
+#define SOUND_MIXER_CS_GETDBGLEVEL 	_SIOWR('M',120, int)
+#define SOUND_MIXER_CS_SETDBGLEVEL 	_SIOWR('M',121, int)
+#define SOUND_MIXER_CS_GETDBGMASK 	_SIOWR('M',122, int)
+#define SOUND_MIXER_CS_SETDBGMASK 	_SIOWR('M',123, int)
+
+static void cs_printioctl(unsigned int x)
+{
+	unsigned int i;
+	unsigned char vidx;
+	// Index of mixtable1[] member is Device ID 
+	// and must be <= SOUND_MIXER_NRDEVICES.
+	// Value of array member is index into s->mix.vol[]
+	static const unsigned char mixtable1[SOUND_MIXER_NRDEVICES] = {
+		[SOUND_MIXER_PCM] = 1,	// voice 
+		[SOUND_MIXER_LINE1] = 2,	// AUX
+		[SOUND_MIXER_CD] = 3,	// CD 
+		[SOUND_MIXER_LINE] = 4,	// Line 
+		[SOUND_MIXER_SYNTH] = 5,	// FM
+		[SOUND_MIXER_MIC] = 6,	// Mic 
+		[SOUND_MIXER_SPEAKER] = 7,	// Speaker 
+		[SOUND_MIXER_RECLEV] = 8,	// Recording level 
+		[SOUND_MIXER_VOLUME] = 9	// Master Volume 
+	};
+
+	switch (x) {
+	case SOUND_MIXER_CS_GETDBGMASK:
+		CS_DBGOUT(CS_IOCTL, 4,
+			  printk("SOUND_MIXER_CS_GETDBGMASK:\n"));
+		break;
+	case SOUND_MIXER_CS_GETDBGLEVEL:
+		CS_DBGOUT(CS_IOCTL, 4,
+			  printk("SOUND_MIXER_CS_GETDBGLEVEL:\n"));
+		break;
+	case SOUND_MIXER_CS_SETDBGMASK:
+		CS_DBGOUT(CS_IOCTL, 4,
+			  printk("SOUND_MIXER_CS_SETDBGMASK:\n"));
+		break;
+	case SOUND_MIXER_CS_SETDBGLEVEL:
+		CS_DBGOUT(CS_IOCTL, 4,
+			  printk("SOUND_MIXER_CS_SETDBGLEVEL:\n"));
+		break;
+	case OSS_GETVERSION:
+		CS_DBGOUT(CS_IOCTL, 4, printk("OSS_GETVERSION:\n"));
+		break;
+	case SNDCTL_DSP_SYNC:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SYNC:\n"));
+		break;
+	case SNDCTL_DSP_SETDUPLEX:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETDUPLEX:\n"));
+		break;
+	case SNDCTL_DSP_GETCAPS:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETCAPS:\n"));
+		break;
+	case SNDCTL_DSP_RESET:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_RESET:\n"));
+		break;
+	case SNDCTL_DSP_SPEED:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SPEED:\n"));
+		break;
+	case SNDCTL_DSP_STEREO:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_STEREO:\n"));
+		break;
+	case SNDCTL_DSP_CHANNELS:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_CHANNELS:\n"));
+		break;
+	case SNDCTL_DSP_GETFMTS:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETFMTS:\n"));
+		break;
+	case SNDCTL_DSP_SETFMT:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETFMT:\n"));
+		break;
+	case SNDCTL_DSP_POST:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_POST:\n"));
+		break;
+	case SNDCTL_DSP_GETTRIGGER:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETTRIGGER:\n"));
+		break;
+	case SNDCTL_DSP_SETTRIGGER:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETTRIGGER:\n"));
+		break;
+	case SNDCTL_DSP_GETOSPACE:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETOSPACE:\n"));
+		break;
+	case SNDCTL_DSP_GETISPACE:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETISPACE:\n"));
+		break;
+	case SNDCTL_DSP_NONBLOCK:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_NONBLOCK:\n"));
+		break;
+	case SNDCTL_DSP_GETODELAY:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETODELAY:\n"));
+		break;
+	case SNDCTL_DSP_GETIPTR:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETIPTR:\n"));
+		break;
+	case SNDCTL_DSP_GETOPTR:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETOPTR:\n"));
+		break;
+	case SNDCTL_DSP_GETBLKSIZE:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETBLKSIZE:\n"));
+		break;
+	case SNDCTL_DSP_SETFRAGMENT:
+		CS_DBGOUT(CS_IOCTL, 4,
+			  printk("SNDCTL_DSP_SETFRAGMENT:\n"));
+		break;
+	case SNDCTL_DSP_SUBDIVIDE:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SUBDIVIDE:\n"));
+		break;
+	case SOUND_PCM_READ_RATE:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_RATE:\n"));
+		break;
+	case SOUND_PCM_READ_CHANNELS:
+		CS_DBGOUT(CS_IOCTL, 4,
+			  printk("SOUND_PCM_READ_CHANNELS:\n"));
+		break;
+	case SOUND_PCM_READ_BITS:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_BITS:\n"));
+		break;
+	case SOUND_PCM_WRITE_FILTER:
+		CS_DBGOUT(CS_IOCTL, 4,
+			  printk("SOUND_PCM_WRITE_FILTER:\n"));
+		break;
+	case SNDCTL_DSP_SETSYNCRO:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETSYNCRO:\n"));
+		break;
+	case SOUND_PCM_READ_FILTER:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_FILTER:\n"));
+		break;
+	case SOUND_MIXER_PRIVATE1:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE1:\n"));
+		break;
+	case SOUND_MIXER_PRIVATE2:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE2:\n"));
+		break;
+	case SOUND_MIXER_PRIVATE3:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE3:\n"));
+		break;
+	case SOUND_MIXER_PRIVATE4:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE4:\n"));
+		break;
+	case SOUND_MIXER_PRIVATE5:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE5:\n"));
+		break;
+	case SOUND_MIXER_INFO:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_INFO:\n"));
+		break;
+	case SOUND_OLD_MIXER_INFO:
+		CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_OLD_MIXER_INFO:\n"));
+		break;
+
+	default:
+		switch (_IOC_NR(x)) {
+		case SOUND_MIXER_VOLUME:
+			CS_DBGOUT(CS_IOCTL, 4,
+				  printk("SOUND_MIXER_VOLUME:\n"));
+			break;
+		case SOUND_MIXER_SPEAKER:
+			CS_DBGOUT(CS_IOCTL, 4,
+				  printk("SOUND_MIXER_SPEAKER:\n"));
+			break;
+		case SOUND_MIXER_RECLEV:
+			CS_DBGOUT(CS_IOCTL, 4,
+				  printk("SOUND_MIXER_RECLEV:\n"));
+			break;
+		case SOUND_MIXER_MIC:
+			CS_DBGOUT(CS_IOCTL, 4,
+				  printk("SOUND_MIXER_MIC:\n"));
+			break;
+		case SOUND_MIXER_SYNTH:
+			CS_DBGOUT(CS_IOCTL, 4,
+				  printk("SOUND_MIXER_SYNTH:\n"));
+			break;
+		case SOUND_MIXER_RECSRC:
+			CS_DBGOUT(CS_IOCTL, 4,
+				  printk("SOUND_MIXER_RECSRC:\n"));
+			break;
+		case SOUND_MIXER_DEVMASK:
+			CS_DBGOUT(CS_IOCTL, 4,
+				  printk("SOUND_MIXER_DEVMASK:\n"));
+			break;
+		case SOUND_MIXER_RECMASK:
+			CS_DBGOUT(CS_IOCTL, 4,
+				  printk("SOUND_MIXER_RECMASK:\n"));
+			break;
+		case SOUND_MIXER_STEREODEVS:
+			CS_DBGOUT(CS_IOCTL, 4,
+				  printk("SOUND_MIXER_STEREODEVS:\n"));
+			break;
+		case SOUND_MIXER_CAPS:
+			CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_CAPS:\n"));
+			break;
+		default:
+			i = _IOC_NR(x);
+			if (i >= SOUND_MIXER_NRDEVICES
+			    || !(vidx = mixtable1[i])) {
+				CS_DBGOUT(CS_IOCTL, 4, printk
+					("UNKNOWN IOCTL: 0x%.8x NR=%d\n",
+						x, i));
+			} else {
+				CS_DBGOUT(CS_IOCTL, 4, printk
+					("SOUND_MIXER_IOCTL AC9x: 0x%.8x NR=%d\n",
+						x, i));
+			}
+			break;
+		}
+	}
+}
+#endif
+
+
+static int ser_init(struct cs4297a_state *s)
+{
+        int i;
+
+        CS_DBGOUT(CS_INIT, 2, 
+                  printk(KERN_INFO "cs4297a: Setting up serial parameters\n"));
+
+        __raw_writeq(M_SYNCSER_CMD_RX_RESET | M_SYNCSER_CMD_TX_RESET, SS_CSR(R_SER_CMD));
+
+        __raw_writeq(M_SYNCSER_MSB_FIRST, SS_CSR(R_SER_MODE));
+        __raw_writeq(32, SS_CSR(R_SER_MINFRM_SZ));
+        __raw_writeq(32, SS_CSR(R_SER_MAXFRM_SZ));
+
+        __raw_writeq(1, SS_CSR(R_SER_TX_RD_THRSH));
+        __raw_writeq(4, SS_CSR(R_SER_TX_WR_THRSH));
+        __raw_writeq(8, SS_CSR(R_SER_RX_RD_THRSH));
+
+        /* This looks good from experimentation */
+        __raw_writeq((M_SYNCSER_TXSYNC_INT | V_SYNCSER_TXSYNC_DLY(0) | M_SYNCSER_TXCLK_EXT |
+               M_SYNCSER_RXSYNC_INT | V_SYNCSER_RXSYNC_DLY(1) | M_SYNCSER_RXCLK_EXT | M_SYNCSER_RXSYNC_EDGE),
+              SS_CSR(R_SER_LINE_MODE));
+
+        /* This looks good from experimentation */
+        __raw_writeq(V_SYNCSER_SEQ_COUNT(14) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_STROBE,
+              SS_TXTBL(0));
+        __raw_writeq(V_SYNCSER_SEQ_COUNT(15) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_BYTE,
+              SS_TXTBL(1));
+        __raw_writeq(V_SYNCSER_SEQ_COUNT(13) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_BYTE,
+              SS_TXTBL(2));
+        __raw_writeq(V_SYNCSER_SEQ_COUNT( 0) | M_SYNCSER_SEQ_ENABLE |
+              M_SYNCSER_SEQ_STROBE | M_SYNCSER_SEQ_LAST, SS_TXTBL(3));
+
+        __raw_writeq(V_SYNCSER_SEQ_COUNT(14) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_STROBE,
+              SS_RXTBL(0));
+        __raw_writeq(V_SYNCSER_SEQ_COUNT(15) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_BYTE,
+              SS_RXTBL(1));
+        __raw_writeq(V_SYNCSER_SEQ_COUNT(13) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_BYTE,
+              SS_RXTBL(2));
+        __raw_writeq(V_SYNCSER_SEQ_COUNT( 0) | M_SYNCSER_SEQ_ENABLE | M_SYNCSER_SEQ_STROBE |
+              M_SYNCSER_SEQ_LAST, SS_RXTBL(3));
+
+        for (i=4; i<16; i++) {
+                /* Just in case... */
+                __raw_writeq(M_SYNCSER_SEQ_LAST, SS_TXTBL(i));
+                __raw_writeq(M_SYNCSER_SEQ_LAST, SS_RXTBL(i));
+        }
+
+        return 0;
+}
+
+static int init_serdma(serdma_t *dma)
+{
+        CS_DBGOUT(CS_INIT, 2,
+                  printk(KERN_ERR "cs4297a: desc - %d sbufsize - %d dbufsize - %d\n",
+                         DMA_DESCR, SAMPLE_BUF_SIZE, DMA_BUF_SIZE));
+
+        /* Descriptors */
+        dma->ringsz = DMA_DESCR;
+        dma->descrtab = kzalloc(dma->ringsz * sizeof(serdma_descr_t), GFP_KERNEL);
+        if (!dma->descrtab) {
+                printk(KERN_ERR "cs4297a: kzalloc descrtab failed\n");
+                return -1;
+        }
+        dma->descrtab_end = dma->descrtab + dma->ringsz;
+	/* XXX bloddy mess, use proper DMA API here ...  */
+	dma->descrtab_phys = CPHYSADDR((long)dma->descrtab);
+        dma->descr_add = dma->descr_rem = dma->descrtab;
+
+        /* Frame buffer area */
+        dma->dma_buf = kzalloc(DMA_BUF_SIZE, GFP_KERNEL);
+        if (!dma->dma_buf) {
+                printk(KERN_ERR "cs4297a: kzalloc dma_buf failed\n");
+                kfree(dma->descrtab);
+                return -1;
+        }
+        dma->dma_buf_phys = CPHYSADDR((long)dma->dma_buf);
+
+        /* Samples buffer area */
+        dma->sbufsz = SAMPLE_BUF_SIZE;
+        dma->sample_buf = kmalloc(dma->sbufsz, GFP_KERNEL);
+        if (!dma->sample_buf) {
+                printk(KERN_ERR "cs4297a: kmalloc sample_buf failed\n");
+                kfree(dma->descrtab);
+                kfree(dma->dma_buf);
+                return -1;
+        }
+        dma->sb_swptr = dma->sb_hwptr = dma->sample_buf;
+        dma->sb_end = (u16 *)((void *)dma->sample_buf + dma->sbufsz);
+        dma->fragsize = dma->sbufsz >> 1;
+
+        CS_DBGOUT(CS_INIT, 4, 
+                  printk(KERN_ERR "cs4297a: descrtab - %08x dma_buf - %x sample_buf - %x\n",
+                         (int)dma->descrtab, (int)dma->dma_buf, 
+                         (int)dma->sample_buf));
+
+        return 0;
+}
+
+static int dma_init(struct cs4297a_state *s)
+{
+        int i;
+
+        CS_DBGOUT(CS_INIT, 2, 
+                  printk(KERN_INFO "cs4297a: Setting up DMA\n"));
+
+        if (init_serdma(&s->dma_adc) ||
+            init_serdma(&s->dma_dac))
+                return -1;
+
+        if (__raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_RX))||
+            __raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_TX))) {
+                panic("DMA state corrupted?!");
+        }
+
+        /* Initialize now - the descr/buffer pairings will never
+           change... */
+        for (i=0; i<DMA_DESCR; i++) {
+                s->dma_dac.descrtab[i].descr_a = M_DMA_SERRX_SOP | V_DMA_DSCRA_A_SIZE(1) | 
+                        (s->dma_dac.dma_buf_phys + i*FRAME_BYTES);
+                s->dma_dac.descrtab[i].descr_b = V_DMA_DSCRB_PKT_SIZE(FRAME_BYTES);
+                s->dma_adc.descrtab[i].descr_a = V_DMA_DSCRA_A_SIZE(1) |
+                        (s->dma_adc.dma_buf_phys + i*FRAME_BYTES);
+                s->dma_adc.descrtab[i].descr_b = 0;
+        }
+
+        __raw_writeq((M_DMA_EOP_INT_EN | V_DMA_INT_PKTCNT(DMA_INT_CNT) |
+               V_DMA_RINGSZ(DMA_DESCR) | M_DMA_TDX_EN),
+              SS_CSR(R_SER_DMA_CONFIG0_RX));
+        __raw_writeq(M_DMA_L2CA, SS_CSR(R_SER_DMA_CONFIG1_RX));
+        __raw_writeq(s->dma_adc.descrtab_phys, SS_CSR(R_SER_DMA_DSCR_BASE_RX));
+
+        __raw_writeq(V_DMA_RINGSZ(DMA_DESCR), SS_CSR(R_SER_DMA_CONFIG0_TX));
+        __raw_writeq(M_DMA_L2CA | M_DMA_NO_DSCR_UPDT, SS_CSR(R_SER_DMA_CONFIG1_TX));
+        __raw_writeq(s->dma_dac.descrtab_phys, SS_CSR(R_SER_DMA_DSCR_BASE_TX));
+
+        /* Prep the receive DMA descriptor ring */
+        __raw_writeq(DMA_DESCR, SS_CSR(R_SER_DMA_DSCR_COUNT_RX));
+
+        __raw_writeq(M_SYNCSER_DMA_RX_EN | M_SYNCSER_DMA_TX_EN, SS_CSR(R_SER_DMA_ENABLE));
+
+        __raw_writeq((M_SYNCSER_RX_SYNC_ERR | M_SYNCSER_RX_OVERRUN | M_SYNCSER_RX_EOP_COUNT),
+              SS_CSR(R_SER_INT_MASK));
+
+        /* Enable the rx/tx; let the codec warm up to the sync and
+           start sending good frames before the receive FIFO is
+           enabled */
+        __raw_writeq(M_SYNCSER_CMD_TX_EN, SS_CSR(R_SER_CMD));
+        udelay(1000);
+        __raw_writeq(M_SYNCSER_CMD_RX_EN | M_SYNCSER_CMD_TX_EN, SS_CSR(R_SER_CMD));
+
+        /* XXXKW is this magic? (the "1" part) */
+        while ((__raw_readq(SS_CSR(R_SER_STATUS)) & 0xf1) != 1)
+                ;
+
+        CS_DBGOUT(CS_INIT, 4, 
+                  printk(KERN_INFO "cs4297a: status: %08x\n",
+                         (unsigned int)(__raw_readq(SS_CSR(R_SER_STATUS)) & 0xffffffff)));
+
+        return 0;
+}
+
+static int serdma_reg_access(struct cs4297a_state *s, u64 data)
+{
+        serdma_t *d = &s->dma_dac;
+        u64 *data_p;
+        unsigned swptr;
+        unsigned long flags;
+        serdma_descr_t *descr;
+
+        if (s->reg_request) {
+                printk(KERN_ERR "cs4297a: attempt to issue multiple reg_access\n");
+                return -1;
+        }
+
+        if (s->ena & FMODE_WRITE) {
+                /* Since a writer has the DSP open, we have to mux the
+                   request in */
+                s->reg_request = data;
+                interruptible_sleep_on(&s->dma_dac.reg_wait);
+                /* XXXKW how can I deal with the starvation case where
+                   the opener isn't writing? */
+        } else {
+                /* Be safe when changing ring pointers */
+		spin_lock_irqsave(&s->lock, flags);
+                if (d->hwptr != d->swptr) {
+                        printk(KERN_ERR "cs4297a: reg access found bookkeeping error (hw/sw = %d/%d\n",
+                               d->hwptr, d->swptr);
+                        spin_unlock_irqrestore(&s->lock, flags);
+                        return -1;
+                }
+                swptr = d->swptr;
+                d->hwptr = d->swptr = (d->swptr + 1) % d->ringsz;
+		spin_unlock_irqrestore(&s->lock, flags);
+
+                descr = &d->descrtab[swptr];
+                data_p = &d->dma_buf[swptr * 4];
+		*data_p = cpu_to_be64(data);
+                __raw_writeq(1, SS_CSR(R_SER_DMA_DSCR_COUNT_TX));
+                CS_DBGOUT(CS_DESCR, 4,
+                          printk(KERN_INFO "cs4297a: add_tx  %p (%x -> %x)\n",
+                                 data_p, swptr, d->hwptr));
+        }
+
+        CS_DBGOUT(CS_FUNCTION, 6,
+                  printk(KERN_INFO "cs4297a: serdma_reg_access()-\n"));
+        
+        return 0;
+}
+
+//****************************************************************************
+// "cs4297a_read_ac97" -- Reads an AC97 register
+//****************************************************************************
+static int cs4297a_read_ac97(struct cs4297a_state *s, u32 offset,
+			    u32 * value)
+{
+        CS_DBGOUT(CS_AC97, 1,
+                  printk(KERN_INFO "cs4297a: read reg %2x\n", offset));
+        if (serdma_reg_access(s, (0xCLL << 60) | (1LL << 47) | ((u64)(offset & 0x7F) << 40)))
+                return -1;
+
+        interruptible_sleep_on(&s->dma_adc.reg_wait);
+        *value = s->read_value;
+        CS_DBGOUT(CS_AC97, 2,
+                  printk(KERN_INFO "cs4297a: rdr reg %x -> %x\n", s->read_reg, s->read_value));
+
+        return 0;
+}
+
+
+//****************************************************************************
+// "cs4297a_write_ac97()"-- writes an AC97 register
+//****************************************************************************
+static int cs4297a_write_ac97(struct cs4297a_state *s, u32 offset,
+			     u32 value)
+{
+        CS_DBGOUT(CS_AC97, 1,
+                  printk(KERN_INFO "cs4297a: write reg %2x -> %04x\n", offset, value));
+        return (serdma_reg_access(s, (0xELL << 60) | ((u64)(offset & 0x7F) << 40) | ((value & 0xffff) << 12)));
+}
+
+static void stop_dac(struct cs4297a_state *s)
+{
+	unsigned long flags;
+
+	CS_DBGOUT(CS_WAVE_WRITE, 3, printk(KERN_INFO "cs4297a: stop_dac():\n"));
+	spin_lock_irqsave(&s->lock, flags);
+	s->ena &= ~FMODE_WRITE;
+#if 0
+        /* XXXKW what do I really want here?  My theory for now is
+           that I just flip the "ena" bit, and the interrupt handler
+           will stop processing the xmit channel */
+        __raw_writeq((s->ena & FMODE_READ) ? M_SYNCSER_DMA_RX_EN : 0,
+              SS_CSR(R_SER_DMA_ENABLE));
+#endif
+
+	spin_unlock_irqrestore(&s->lock, flags);
+}
+
+
+static void start_dac(struct cs4297a_state *s)
+{
+	unsigned long flags;
+
+	CS_DBGOUT(CS_FUNCTION, 3, printk(KERN_INFO "cs4297a: start_dac()+\n"));
+	spin_lock_irqsave(&s->lock, flags);
+	if (!(s->ena & FMODE_WRITE) && (s->dma_dac.mapped ||
+					(s->dma_dac.count > 0
+	    				&& s->dma_dac.ready))) {
+		s->ena |= FMODE_WRITE;
+                /* XXXKW what do I really want here?  My theory for
+                   now is that I just flip the "ena" bit, and the
+                   interrupt handler will start processing the xmit
+                   channel */
+
+		CS_DBGOUT(CS_WAVE_WRITE | CS_PARMS, 8, printk(KERN_INFO
+			"cs4297a: start_dac(): start dma\n"));
+
+	}
+	spin_unlock_irqrestore(&s->lock, flags);
+	CS_DBGOUT(CS_FUNCTION, 3,
+		  printk(KERN_INFO "cs4297a: start_dac()-\n"));
+}
+
+
+static void stop_adc(struct cs4297a_state *s)
+{
+	unsigned long flags;
+
+	CS_DBGOUT(CS_FUNCTION, 3,
+		  printk(KERN_INFO "cs4297a: stop_adc()+\n"));
+
+	spin_lock_irqsave(&s->lock, flags);
+	s->ena &= ~FMODE_READ;
+
+	if (s->conversion == 1) {
+		s->conversion = 0;
+		s->prop_adc.fmt = s->prop_adc.fmt_original;
+	}
+        /* Nothing to do really, I need to keep the DMA going
+           XXXKW when do I get here, and is there more I should do? */
+	spin_unlock_irqrestore(&s->lock, flags);
+	CS_DBGOUT(CS_FUNCTION, 3,
+		  printk(KERN_INFO "cs4297a: stop_adc()-\n"));
+}
+
+
+static void start_adc(struct cs4297a_state *s)
+{
+	unsigned long flags;
+
+	CS_DBGOUT(CS_FUNCTION, 2,
+		  printk(KERN_INFO "cs4297a: start_adc()+\n"));
+
+	if (!(s->ena & FMODE_READ) &&
+	    (s->dma_adc.mapped || s->dma_adc.count <=
+	     (signed) (s->dma_adc.sbufsz - 2 * s->dma_adc.fragsize))
+	    && s->dma_adc.ready) {
+		if (s->prop_adc.fmt & AFMT_S8 || s->prop_adc.fmt & AFMT_U8) {
+			// 
+			// now only use 16 bit capture, due to truncation issue
+			// in the chip, noticable distortion occurs.
+			// allocate buffer and then convert from 16 bit to 
+			// 8 bit for the user buffer.
+			//
+			s->prop_adc.fmt_original = s->prop_adc.fmt;
+			if (s->prop_adc.fmt & AFMT_S8) {
+				s->prop_adc.fmt &= ~AFMT_S8;
+				s->prop_adc.fmt |= AFMT_S16_LE;
+			}
+			if (s->prop_adc.fmt & AFMT_U8) {
+				s->prop_adc.fmt &= ~AFMT_U8;
+				s->prop_adc.fmt |= AFMT_U16_LE;
+			}
+			//
+			// prog_dmabuf_adc performs a stop_adc() but that is
+			// ok since we really haven't started the DMA yet.
+			//
+			prog_codec(s, CS_TYPE_ADC);
+
+                        prog_dmabuf_adc(s);
+			s->conversion = 1;
+		}
+		spin_lock_irqsave(&s->lock, flags);
+		s->ena |= FMODE_READ;
+                /* Nothing to do really, I am probably already
+                   DMAing...  XXXKW when do I get here, and is there
+                   more I should do? */
+		spin_unlock_irqrestore(&s->lock, flags);
+
+		CS_DBGOUT(CS_PARMS, 6, printk(KERN_INFO
+			 "cs4297a: start_adc(): start adc\n"));
+	}
+	CS_DBGOUT(CS_FUNCTION, 2,
+		  printk(KERN_INFO "cs4297a: start_adc()-\n"));
+
+}
+
+
+// call with spinlock held! 
+static void cs4297a_update_ptr(struct cs4297a_state *s, int intflag)
+{
+	int good_diff, diff, diff2;
+        u64 *data_p, data;
+        u32 *s_ptr;
+	unsigned hwptr;
+        u32 status;
+        serdma_t *d;
+        serdma_descr_t *descr;
+
+	// update ADC pointer 
+        status = intflag ? __raw_readq(SS_CSR(R_SER_STATUS)) : 0;
+
+	if ((s->ena & FMODE_READ) || (status & (M_SYNCSER_RX_EOP_COUNT))) {
+                d = &s->dma_adc;
+                hwptr = (unsigned) (((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_RX)) & M_DMA_CURDSCR_ADDR) -
+                                     d->descrtab_phys) / sizeof(serdma_descr_t));
+
+                if (s->ena & FMODE_READ) {
+                        CS_DBGOUT(CS_FUNCTION, 2, 
+                                  printk(KERN_INFO "cs4297a: upd_rcv sw->hw->hw %x/%x/%x (int-%d)n",
+                                         d->swptr, d->hwptr, hwptr, intflag));
+                        /* Number of DMA buffers available for software: */
+                        diff2 = diff = (d->ringsz + hwptr - d->hwptr) % d->ringsz;
+                        d->hwptr = hwptr;
+                        good_diff = 0;
+                        s_ptr = (u32 *)&(d->dma_buf[d->swptr*4]);
+                        descr = &d->descrtab[d->swptr];
+                        while (diff2--) {
+				u64 data = be64_to_cpu(*(u64 *)s_ptr);
+                                u64 descr_a;
+                                u16 left, right;
+                                descr_a = descr->descr_a;
+                                descr->descr_a &= ~M_DMA_SERRX_SOP;
+                                if ((descr_a & M_DMA_DSCRA_A_ADDR) != CPHYSADDR((long)s_ptr)) {
+                                        printk(KERN_ERR "cs4297a: RX Bad address (read)\n");
+                                }
+                                if (((data & 0x9800000000000000) != 0x9800000000000000) ||
+                                    (!(descr_a & M_DMA_SERRX_SOP)) ||
+                                    (G_DMA_DSCRB_PKT_SIZE(descr->descr_b) != FRAME_BYTES)) {
+                                        s->stats.rx_bad++;
+                                        printk(KERN_DEBUG "cs4297a: RX Bad attributes (read)\n");
+                                        continue;
+                                }
+                                s->stats.rx_good++;
+                                if ((data >> 61) == 7) {
+                                        s->read_value = (data >> 12) & 0xffff;
+                                        s->read_reg = (data >> 40) & 0x7f;
+                                        wake_up(&d->reg_wait);
+                                }
+                                if (d->count && (d->sb_hwptr == d->sb_swptr)) {
+                                        s->stats.rx_overflow++;
+                                        printk(KERN_DEBUG "cs4297a: RX overflow\n");
+                                        continue;
+                                }
+                                good_diff++;
+				left = ((be32_to_cpu(s_ptr[1]) & 0xff) << 8) |
+				       ((be32_to_cpu(s_ptr[2]) >> 24) & 0xff);
+				right = (be32_to_cpu(s_ptr[2]) >> 4) & 0xffff;
+				*d->sb_hwptr++ = cpu_to_be16(left);
+				*d->sb_hwptr++ = cpu_to_be16(right);
+                                if (d->sb_hwptr == d->sb_end)
+                                        d->sb_hwptr = d->sample_buf;
+                                descr++;
+                                if (descr == d->descrtab_end) {
+                                        descr = d->descrtab;
+                                        s_ptr = (u32 *)s->dma_adc.dma_buf;
+                                } else {
+                                        s_ptr += 8;
+                                }
+                        }
+                        d->total_bytes += good_diff * FRAME_SAMPLE_BYTES;
+                        d->count += good_diff * FRAME_SAMPLE_BYTES;
+                        if (d->count > d->sbufsz) {
+                                printk(KERN_ERR "cs4297a: bogus receive overflow!!\n");
+                        }
+                        d->swptr = (d->swptr + diff) % d->ringsz;
+                        __raw_writeq(diff, SS_CSR(R_SER_DMA_DSCR_COUNT_RX));
+                        if (d->mapped) {
+                                if (d->count >= (signed) d->fragsize)
+                                        wake_up(&d->wait);
+                        } else {
+                                if (d->count > 0) {
+                                        CS_DBGOUT(CS_WAVE_READ, 4,
+                                                  printk(KERN_INFO
+                                                         "cs4297a: update count -> %d\n", d->count));
+                                        wake_up(&d->wait);
+                                }
+                        }
+                } else {
+                        /* Receive is going even if no one is
+                           listening (for register accesses and to
+                           avoid FIFO overrun) */
+                        diff2 = diff = (hwptr + d->ringsz - d->hwptr) % d->ringsz;
+                        if (!diff) {
+                                printk(KERN_ERR "cs4297a: RX full or empty?\n");
+                        }
+                        
+                        descr = &d->descrtab[d->swptr];
+                        data_p = &d->dma_buf[d->swptr*4];
+
+                        /* Force this to happen at least once; I got
+                           here because of an interrupt, so there must
+                           be a buffer to process. */
+                        do {
+				data = be64_to_cpu(*data_p);
+                                if ((descr->descr_a & M_DMA_DSCRA_A_ADDR) != CPHYSADDR((long)data_p)) {
+                                        printk(KERN_ERR "cs4297a: RX Bad address %d (%llx %lx)\n", d->swptr,
+                                               (long long)(descr->descr_a & M_DMA_DSCRA_A_ADDR),
+                                               (long)CPHYSADDR((long)data_p));
+                                }
+                                if (!(data & (1LL << 63)) ||
+                                    !(descr->descr_a & M_DMA_SERRX_SOP) ||
+                                    (G_DMA_DSCRB_PKT_SIZE(descr->descr_b) != FRAME_BYTES)) {
+                                        s->stats.rx_bad++;
+                                        printk(KERN_DEBUG "cs4297a: RX Bad attributes\n");
+                                } else {
+                                        s->stats.rx_good++;
+                                        if ((data >> 61) == 7) {
+                                                s->read_value = (data >> 12) & 0xffff;
+                                                s->read_reg = (data >> 40) & 0x7f;
+                                                wake_up(&d->reg_wait);
+                                        }
+                                }
+                                descr->descr_a &= ~M_DMA_SERRX_SOP;
+                                descr++;
+                                d->swptr++;
+                                data_p += 4;
+                                if (descr == d->descrtab_end) {
+                                        descr = d->descrtab;
+                                        d->swptr = 0;
+                                        data_p = d->dma_buf;
+                                }
+                                __raw_writeq(1, SS_CSR(R_SER_DMA_DSCR_COUNT_RX));
+                        } while (--diff);
+                        d->hwptr = hwptr;
+
+                        CS_DBGOUT(CS_DESCR, 6, 
+                                  printk(KERN_INFO "cs4297a: hw/sw %x/%x\n", d->hwptr, d->swptr));
+                }
+
+		CS_DBGOUT(CS_PARMS, 8, printk(KERN_INFO
+			"cs4297a: cs4297a_update_ptr(): s=0x%.8x hwptr=%d total_bytes=%d count=%d \n",
+				(unsigned)s, d->hwptr, 
+				d->total_bytes, d->count));
+	}
+
+        /* XXXKW worry about s->reg_request -- there is a starvation
+           case if s->ena has FMODE_WRITE on, but the client isn't
+           doing writes */
+
+	// update DAC pointer 
+	//
+	// check for end of buffer, means that we are going to wait for another interrupt
+	// to allow silence to fill the fifos on the part, to keep pops down to a minimum.
+	//
+	if (s->ena & FMODE_WRITE) {
+                serdma_t *d = &s->dma_dac;
+                hwptr = (unsigned) (((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_TX)) & M_DMA_CURDSCR_ADDR) -
+                                     d->descrtab_phys) / sizeof(serdma_descr_t));
+                diff = (d->ringsz + hwptr - d->hwptr) % d->ringsz;
+                CS_DBGOUT(CS_WAVE_WRITE, 4, printk(KERN_INFO
+                                                   "cs4297a: cs4297a_update_ptr(): hw/hw/sw %x/%x/%x diff %d count %d\n",
+                                                   d->hwptr, hwptr, d->swptr, diff, d->count));
+                d->hwptr = hwptr;
+                /* XXXKW stereo? conversion? Just assume 2 16-bit samples for now */
+                d->total_bytes += diff * FRAME_SAMPLE_BYTES;
+		if (d->mapped) {
+			d->count += diff * FRAME_SAMPLE_BYTES;
+			if (d->count >= d->fragsize) {
+				d->wakeup = 1;
+				wake_up(&d->wait);
+				if (d->count > d->sbufsz)
+					d->count &= d->sbufsz - 1;
+			}
+		} else {
+			d->count -= diff * FRAME_SAMPLE_BYTES;
+			if (d->count <= 0) {
+				//
+				// fill with silence, and do not shut down the DAC.
+				// Continue to play silence until the _release.
+				//
+				CS_DBGOUT(CS_WAVE_WRITE, 6, printk(KERN_INFO
+					"cs4297a: cs4297a_update_ptr(): memset %d at 0x%.8x for %d size \n",
+						(unsigned)(s->prop_dac.fmt & 
+						(AFMT_U8 | AFMT_U16_LE)) ? 0x80 : 0, 
+						(unsigned)d->dma_buf, 
+						d->ringsz));
+				memset(d->dma_buf, 0, d->ringsz * FRAME_BYTES);
+				if (d->count < 0) {
+					d->underrun = 1;
+                                        s->stats.tx_underrun++;
+					d->count = 0;
+					CS_DBGOUT(CS_ERROR, 9, printk(KERN_INFO
+					 "cs4297a: cs4297a_update_ptr(): underrun\n"));
+				}
+			} else if (d->count <=
+				   (signed) d->fragsize
+				   && !d->endcleared) {
+                          /* XXXKW what is this for? */
+				clear_advance(d->dma_buf,
+					      d->sbufsz,
+					      d->swptr,
+					      d->fragsize,
+					      0);
+				d->endcleared = 1;
+			}
+			if ( (d->count <= (signed) d->sbufsz/2) || intflag)
+			{
+                                CS_DBGOUT(CS_WAVE_WRITE, 4,
+                                          printk(KERN_INFO
+                                                 "cs4297a: update count -> %d\n", d->count));
+				wake_up(&d->wait);
+			}
+		}
+		CS_DBGOUT(CS_PARMS, 8, printk(KERN_INFO
+			"cs4297a: cs4297a_update_ptr(): s=0x%.8x hwptr=%d total_bytes=%d count=%d \n",
+				(unsigned) s, d->hwptr, 
+				d->total_bytes, d->count));
+	}
+}
+
+static int mixer_ioctl(struct cs4297a_state *s, unsigned int cmd,
+		       unsigned long arg)
+{
+	// Index to mixer_src[] is value of AC97 Input Mux Select Reg.
+	// Value of array member is recording source Device ID Mask.
+	static const unsigned int mixer_src[8] = {
+		SOUND_MASK_MIC, SOUND_MASK_CD, 0, SOUND_MASK_LINE1,
+		SOUND_MASK_LINE, SOUND_MASK_VOLUME, 0, 0
+	};
+
+	// Index of mixtable1[] member is Device ID 
+	// and must be <= SOUND_MIXER_NRDEVICES.
+	// Value of array member is index into s->mix.vol[]
+	static const unsigned char mixtable1[SOUND_MIXER_NRDEVICES] = {
+		[SOUND_MIXER_PCM] = 1,	// voice 
+		[SOUND_MIXER_LINE1] = 2,	// AUX
+		[SOUND_MIXER_CD] = 3,	// CD 
+		[SOUND_MIXER_LINE] = 4,	// Line 
+		[SOUND_MIXER_SYNTH] = 5,	// FM
+		[SOUND_MIXER_MIC] = 6,	// Mic 
+		[SOUND_MIXER_SPEAKER] = 7,	// Speaker 
+		[SOUND_MIXER_RECLEV] = 8,	// Recording level 
+		[SOUND_MIXER_VOLUME] = 9	// Master Volume 
+	};
+
+	static const unsigned mixreg[] = {
+		AC97_PCMOUT_VOL,
+		AC97_AUX_VOL,
+		AC97_CD_VOL,
+		AC97_LINEIN_VOL
+	};
+	unsigned char l, r, rl, rr, vidx;
+	unsigned char attentbl[11] =
+	    { 63, 42, 26, 17, 14, 11, 8, 6, 4, 2, 0 };
+	unsigned temp1;
+	int i, val;
+
+	VALIDATE_STATE(s);
+	CS_DBGOUT(CS_FUNCTION, 4, printk(KERN_INFO
+		 "cs4297a: mixer_ioctl(): s=0x%.8x cmd=0x%.8x\n",
+			 (unsigned) s, cmd));
+#if CSDEBUG
+	cs_printioctl(cmd);
+#endif
+#if CSDEBUG_INTERFACE
+
+	if ((cmd == SOUND_MIXER_CS_GETDBGMASK) ||
+	    (cmd == SOUND_MIXER_CS_SETDBGMASK) ||
+	    (cmd == SOUND_MIXER_CS_GETDBGLEVEL) ||
+	    (cmd == SOUND_MIXER_CS_SETDBGLEVEL))
+	{
+		switch (cmd) {
+
+		case SOUND_MIXER_CS_GETDBGMASK:
+			return put_user(cs_debugmask,
+					(unsigned long *) arg);
+
+		case SOUND_MIXER_CS_GETDBGLEVEL:
+			return put_user(cs_debuglevel,
+					(unsigned long *) arg);
+
+		case SOUND_MIXER_CS_SETDBGMASK:
+			if (get_user(val, (unsigned long *) arg))
+				return -EFAULT;
+			cs_debugmask = val;
+			return 0;
+
+		case SOUND_MIXER_CS_SETDBGLEVEL:
+			if (get_user(val, (unsigned long *) arg))
+				return -EFAULT;
+			cs_debuglevel = val;
+			return 0;
+		default:
+			CS_DBGOUT(CS_ERROR, 1, printk(KERN_INFO
+				"cs4297a: mixer_ioctl(): ERROR unknown debug cmd\n"));
+			return 0;
+		}
+	}
+#endif
+
+	if (cmd == SOUND_MIXER_PRIVATE1) {
+                return -EINVAL;
+	}
+	if (cmd == SOUND_MIXER_PRIVATE2) {
+		// enable/disable/query spatializer 
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		if (val != -1) {
+			temp1 = (val & 0x3f) >> 2;
+			cs4297a_write_ac97(s, AC97_3D_CONTROL, temp1);
+			cs4297a_read_ac97(s, AC97_GENERAL_PURPOSE,
+					 &temp1);
+			cs4297a_write_ac97(s, AC97_GENERAL_PURPOSE,
+					  temp1 | 0x2000);
+		}
+		cs4297a_read_ac97(s, AC97_3D_CONTROL, &temp1);
+		return put_user((temp1 << 2) | 3, (int *) arg);
+	}
+	if (cmd == SOUND_MIXER_INFO) {
+		mixer_info info;
+		memset(&info, 0, sizeof(info));
+		strlcpy(info.id, "CS4297a", sizeof(info.id));
+		strlcpy(info.name, "Crystal CS4297a", sizeof(info.name));
+		info.modify_counter = s->mix.modcnt;
+		if (copy_to_user((void *) arg, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	}
+	if (cmd == SOUND_OLD_MIXER_INFO) {
+		_old_mixer_info info;
+		memset(&info, 0, sizeof(info));
+		strlcpy(info.id, "CS4297a", sizeof(info.id));
+		strlcpy(info.name, "Crystal CS4297a", sizeof(info.name));
+		if (copy_to_user((void *) arg, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	}
+	if (cmd == OSS_GETVERSION)
+		return put_user(SOUND_VERSION, (int *) arg);
+
+	if (_IOC_TYPE(cmd) != 'M' || _SIOC_SIZE(cmd) != sizeof(int))
+		return -EINVAL;
+
+	// If ioctl has only the SIOC_READ bit(bit 31)
+	// on, process the only-read commands. 
+	if (_SIOC_DIR(cmd) == _SIOC_READ) {
+		switch (_IOC_NR(cmd)) {
+		case SOUND_MIXER_RECSRC:	// Arg contains a bit for each recording source 
+			cs4297a_read_ac97(s, AC97_RECORD_SELECT,
+					 &temp1);
+			return put_user(mixer_src[temp1 & 7], (int *) arg);
+
+		case SOUND_MIXER_DEVMASK:	// Arg contains a bit for each supported device 
+			return put_user(SOUND_MASK_PCM | SOUND_MASK_LINE |
+					SOUND_MASK_VOLUME | SOUND_MASK_RECLEV,
+                                        (int *) arg);
+
+		case SOUND_MIXER_RECMASK:	// Arg contains a bit for each supported recording source 
+			return put_user(SOUND_MASK_LINE | SOUND_MASK_VOLUME,
+                                        (int *) arg);
+
+		case SOUND_MIXER_STEREODEVS:	// Mixer channels supporting stereo 
+			return put_user(SOUND_MASK_PCM | SOUND_MASK_LINE |
+					SOUND_MASK_VOLUME | SOUND_MASK_RECLEV,
+                                        (int *) arg);
+
+		case SOUND_MIXER_CAPS:
+			return put_user(SOUND_CAP_EXCL_INPUT, (int *) arg);
+
+		default:
+			i = _IOC_NR(cmd);
+			if (i >= SOUND_MIXER_NRDEVICES
+			    || !(vidx = mixtable1[i]))
+				return -EINVAL;
+			return put_user(s->mix.vol[vidx - 1], (int *) arg);
+		}
+	}
+	// If ioctl doesn't have both the SIOC_READ and 
+	// the SIOC_WRITE bit set, return invalid.
+	if (_SIOC_DIR(cmd) != (_SIOC_READ | _SIOC_WRITE))
+		return -EINVAL;
+
+	// Increment the count of volume writes.
+	s->mix.modcnt++;
+
+	// Isolate the command; it must be a write.
+	switch (_IOC_NR(cmd)) {
+
+	case SOUND_MIXER_RECSRC:	// Arg contains a bit for each recording source 
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		i = hweight32(val);	// i = # bits on in val.
+		if (i != 1)	// One & only 1 bit must be on.
+			return 0;
+		for (i = 0; i < sizeof(mixer_src) / sizeof(int); i++) {
+			if (val == mixer_src[i]) {
+				temp1 = (i << 8) | i;
+				cs4297a_write_ac97(s,
+						  AC97_RECORD_SELECT,
+						  temp1);
+				return 0;
+			}
+		}
+		return 0;
+
+	case SOUND_MIXER_VOLUME:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		l = val & 0xff;
+		if (l > 100)
+			l = 100;	// Max soundcard.h vol is 100.
+		if (l < 6) {
+			rl = 63;
+			l = 0;
+		} else
+			rl = attentbl[(10 * l) / 100];	// Convert 0-100 vol to 63-0 atten.
+
+		r = (val >> 8) & 0xff;
+		if (r > 100)
+			r = 100;	// Max right volume is 100, too
+		if (r < 6) {
+			rr = 63;
+			r = 0;
+		} else
+			rr = attentbl[(10 * r) / 100];	// Convert volume to attenuation.
+
+		if ((rl > 60) && (rr > 60))	// If both l & r are 'low',          
+			temp1 = 0x8000;	//  turn on the mute bit.
+		else
+			temp1 = 0;
+
+		temp1 |= (rl << 8) | rr;
+
+		cs4297a_write_ac97(s, AC97_MASTER_VOL_STEREO, temp1);
+		cs4297a_write_ac97(s, AC97_PHONE_VOL, temp1);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+		s->mix.vol[8] = ((unsigned int) r << 8) | l;
+#else
+		s->mix.vol[8] = val;
+#endif
+		return put_user(s->mix.vol[8], (int *) arg);
+
+	case SOUND_MIXER_SPEAKER:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		l = val & 0xff;
+		if (l > 100)
+			l = 100;
+		if (l < 3) {
+			rl = 0;
+			l = 0;
+		} else {
+			rl = (l * 2 - 5) / 13;	// Convert 0-100 range to 0-15.
+			l = (rl * 13 + 5) / 2;
+		}
+
+		if (rl < 3) {
+			temp1 = 0x8000;
+			rl = 0;
+		} else
+			temp1 = 0;
+		rl = 15 - rl;	// Convert volume to attenuation.
+		temp1 |= rl << 1;
+		cs4297a_write_ac97(s, AC97_PCBEEP_VOL, temp1);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+		s->mix.vol[6] = l << 8;
+#else
+		s->mix.vol[6] = val;
+#endif
+		return put_user(s->mix.vol[6], (int *) arg);
+
+	case SOUND_MIXER_RECLEV:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		l = val & 0xff;
+		if (l > 100)
+			l = 100;
+		r = (val >> 8) & 0xff;
+		if (r > 100)
+			r = 100;
+		rl = (l * 2 - 5) / 13;	// Convert 0-100 scale to 0-15.
+		rr = (r * 2 - 5) / 13;
+		if (rl < 3 && rr < 3)
+			temp1 = 0x8000;
+		else
+			temp1 = 0;
+
+		temp1 = temp1 | (rl << 8) | rr;
+		cs4297a_write_ac97(s, AC97_RECORD_GAIN, temp1);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+		s->mix.vol[7] = ((unsigned int) r << 8) | l;
+#else
+		s->mix.vol[7] = val;
+#endif
+		return put_user(s->mix.vol[7], (int *) arg);
+
+	case SOUND_MIXER_MIC:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		l = val & 0xff;
+		if (l > 100)
+			l = 100;
+		if (l < 1) {
+			l = 0;
+			rl = 0;
+		} else {
+			rl = ((unsigned) l * 5 - 4) / 16;	// Convert 0-100 range to 0-31.
+			l = (rl * 16 + 4) / 5;
+		}
+		cs4297a_read_ac97(s, AC97_MIC_VOL, &temp1);
+		temp1 &= 0x40;	// Isolate 20db gain bit.
+		if (rl < 3) {
+			temp1 |= 0x8000;
+			rl = 0;
+		}
+		rl = 31 - rl;	// Convert volume to attenuation.
+		temp1 |= rl;
+		cs4297a_write_ac97(s, AC97_MIC_VOL, temp1);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+		s->mix.vol[5] = val << 8;
+#else
+		s->mix.vol[5] = val;
+#endif
+		return put_user(s->mix.vol[5], (int *) arg);
+
+
+	case SOUND_MIXER_SYNTH:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		l = val & 0xff;
+		if (l > 100)
+			l = 100;
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		r = (val >> 8) & 0xff;
+		if (r > 100)
+			r = 100;
+		rl = (l * 2 - 11) / 3;	// Convert 0-100 range to 0-63.
+		rr = (r * 2 - 11) / 3;
+		if (rl < 3)	// If l is low, turn on
+			temp1 = 0x0080;	//  the mute bit.
+		else
+			temp1 = 0;
+
+		rl = 63 - rl;	// Convert vol to attenuation.
+//		writel(temp1 | rl, s->pBA0 + FMLVC);
+		if (rr < 3)	//  If rr is low, turn on
+			temp1 = 0x0080;	//   the mute bit.
+		else
+			temp1 = 0;
+		rr = 63 - rr;	// Convert vol to attenuation.
+//		writel(temp1 | rr, s->pBA0 + FMRVC);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+		s->mix.vol[4] = (r << 8) | l;
+#else
+		s->mix.vol[4] = val;
+#endif
+		return put_user(s->mix.vol[4], (int *) arg);
+
+
+	default:
+		CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO
+			"cs4297a: mixer_ioctl(): default\n"));
+
+		i = _IOC_NR(cmd);
+		if (i >= SOUND_MIXER_NRDEVICES || !(vidx = mixtable1[i]))
+			return -EINVAL;
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		l = val & 0xff;
+		if (l > 100)
+			l = 100;
+		if (l < 1) {
+			l = 0;
+			rl = 31;
+		} else
+			rl = (attentbl[(l * 10) / 100]) >> 1;
+
+		r = (val >> 8) & 0xff;
+		if (r > 100)
+			r = 100;
+		if (r < 1) {
+			r = 0;
+			rr = 31;
+		} else
+			rr = (attentbl[(r * 10) / 100]) >> 1;
+		if ((rl > 30) && (rr > 30))
+			temp1 = 0x8000;
+		else
+			temp1 = 0;
+		temp1 = temp1 | (rl << 8) | rr;
+		cs4297a_write_ac97(s, mixreg[vidx - 1], temp1);
+
+#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
+		s->mix.vol[vidx - 1] = ((unsigned int) r << 8) | l;
+#else
+		s->mix.vol[vidx - 1] = val;
+#endif
+		return put_user(s->mix.vol[vidx - 1], (int *) arg);
+	}
+}
+
+
+// --------------------------------------------------------------------- 
+
+static int cs4297a_open_mixdev(struct inode *inode, struct file *file)
+{
+	int minor = iminor(inode);
+	struct cs4297a_state *s=NULL;
+	struct list_head *entry;
+
+	CS_DBGOUT(CS_FUNCTION | CS_OPEN, 4,
+		  printk(KERN_INFO "cs4297a: cs4297a_open_mixdev()+\n"));
+
+	mutex_lock(&swarm_cs4297a_mutex);
+	list_for_each(entry, &cs4297a_devs)
+	{
+		s = list_entry(entry, struct cs4297a_state, list);
+		if(s->dev_mixer == minor)
+			break;
+	}
+	if (!s)
+	{
+		CS_DBGOUT(CS_FUNCTION | CS_OPEN | CS_ERROR, 2,
+			printk(KERN_INFO "cs4297a: cs4297a_open_mixdev()- -ENODEV\n"));
+
+		mutex_unlock(&swarm_cs4297a_mutex);
+		return -ENODEV;
+	}
+	VALIDATE_STATE(s);
+	file->private_data = s;
+
+	CS_DBGOUT(CS_FUNCTION | CS_OPEN, 4,
+		  printk(KERN_INFO "cs4297a: cs4297a_open_mixdev()- 0\n"));
+	mutex_unlock(&swarm_cs4297a_mutex);
+
+	return nonseekable_open(inode, file);
+}
+
+
+static int cs4297a_release_mixdev(struct inode *inode, struct file *file)
+{
+	struct cs4297a_state *s =
+	    (struct cs4297a_state *) file->private_data;
+
+	VALIDATE_STATE(s);
+	return 0;
+}
+
+
+static int cs4297a_ioctl_mixdev(struct file *file,
+			       unsigned int cmd, unsigned long arg)
+{
+	int ret;
+	mutex_lock(&swarm_cs4297a_mutex);
+	ret = mixer_ioctl((struct cs4297a_state *) file->private_data, cmd,
+			   arg);
+	mutex_unlock(&swarm_cs4297a_mutex);
+	return ret;
+}
+
+
+// ******************************************************************************************
+//   Mixer file operations struct.
+// ******************************************************************************************
+static const struct file_operations cs4297a_mixer_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.unlocked_ioctl	= cs4297a_ioctl_mixdev,
+	.open		= cs4297a_open_mixdev,
+	.release	= cs4297a_release_mixdev,
+};
+
+// --------------------------------------------------------------------- 
+
+
+static int drain_adc(struct cs4297a_state *s, int nonblock)
+{
+        /* This routine serves no purpose currently - any samples
+           sitting in the receive queue will just be processed by the
+           background consumer.  This would be different if DMA
+           actually stopped when there were no clients. */
+	return 0;
+}
+
+static int drain_dac(struct cs4297a_state *s, int nonblock)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	unsigned long flags;
+        unsigned hwptr;
+	unsigned tmo;
+	int count;
+
+	if (s->dma_dac.mapped)
+		return 0;
+        if (nonblock)
+                return -EBUSY;
+	add_wait_queue(&s->dma_dac.wait, &wait);
+        while ((count = __raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_TX))) ||
+               (s->dma_dac.count > 0)) {
+                if (!signal_pending(current)) {
+                        set_current_state(TASK_INTERRUPTIBLE);
+                        /* XXXKW is this calculation working? */
+                        tmo = ((count * FRAME_TX_US) * HZ) / 1000000;
+                        schedule_timeout(tmo + 1);
+                } else {
+                        /* XXXKW do I care if there is a signal pending? */
+                }
+        }
+        spin_lock_irqsave(&s->lock, flags);
+        /* Reset the bookkeeping */
+        hwptr = (int)(((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_TX)) & M_DMA_CURDSCR_ADDR) -
+                       s->dma_dac.descrtab_phys) / sizeof(serdma_descr_t));
+        s->dma_dac.hwptr = s->dma_dac.swptr = hwptr;
+        spin_unlock_irqrestore(&s->lock, flags);
+	remove_wait_queue(&s->dma_dac.wait, &wait);
+	current->state = TASK_RUNNING;
+	return 0;
+}
+
+
+// --------------------------------------------------------------------- 
+
+static ssize_t cs4297a_read(struct file *file, char *buffer, size_t count,
+			   loff_t * ppos)
+{
+	struct cs4297a_state *s =
+	    (struct cs4297a_state *) file->private_data;
+	ssize_t ret;
+	unsigned long flags;
+	int cnt, count_fr, cnt_by;
+	unsigned copied = 0;
+
+	CS_DBGOUT(CS_FUNCTION | CS_WAVE_READ, 2,
+		  printk(KERN_INFO "cs4297a: cs4297a_read()+ %d \n", count));
+
+	VALIDATE_STATE(s);
+	if (s->dma_adc.mapped)
+		return -ENXIO;
+	if (!s->dma_adc.ready && (ret = prog_dmabuf_adc(s)))
+		return ret;
+	if (!access_ok(VERIFY_WRITE, buffer, count))
+		return -EFAULT;
+	ret = 0;
+//
+// "count" is the amount of bytes to read (from app), is decremented each loop
+//      by the amount of bytes that have been returned to the user buffer.
+// "cnt" is the running total of each read from the buffer (changes each loop)
+// "buffer" points to the app's buffer
+// "ret" keeps a running total of the amount of bytes that have been copied
+//      to the user buffer.
+// "copied" is the total bytes copied into the user buffer for each loop.
+//
+	while (count > 0) {
+		CS_DBGOUT(CS_WAVE_READ, 8, printk(KERN_INFO
+			"_read() count>0 count=%d .count=%d .swptr=%d .hwptr=%d \n",
+				count, s->dma_adc.count,
+				s->dma_adc.swptr, s->dma_adc.hwptr));
+		spin_lock_irqsave(&s->lock, flags);
+
+                /* cnt will be the number of available samples (16-bit
+                   stereo); it starts out as the maxmimum consequetive
+                   samples */
+		cnt = (s->dma_adc.sb_end - s->dma_adc.sb_swptr) / 2;
+                count_fr = s->dma_adc.count / FRAME_SAMPLE_BYTES;
+
+		// dma_adc.count is the current total bytes that have not been read.
+		// if the amount of unread bytes from the current sw pointer to the
+		// end of the buffer is greater than the current total bytes that
+		// have not been read, then set the "cnt" (unread bytes) to the
+		// amount of unread bytes.  
+
+		if (count_fr < cnt)
+			cnt = count_fr;
+                cnt_by = cnt * FRAME_SAMPLE_BYTES;
+		spin_unlock_irqrestore(&s->lock, flags);
+		//
+		// if we are converting from 8/16 then we need to copy
+		// twice the number of 16 bit bytes then 8 bit bytes.
+		// 
+		if (s->conversion) {
+			if (cnt_by > (count * 2)) {
+				cnt = (count * 2) / FRAME_SAMPLE_BYTES;
+                                cnt_by = count * 2;
+                        }
+		} else {
+			if (cnt_by > count) {
+				cnt = count / FRAME_SAMPLE_BYTES;
+                                cnt_by = count;
+                        }
+		}
+		//
+		// "cnt" NOW is the smaller of the amount that will be read,
+		// and the amount that is requested in this read (or partial).
+		// if there are no bytes in the buffer to read, then start the
+		// ADC and wait for the interrupt handler to wake us up.
+		//
+		if (cnt <= 0) {
+
+			// start up the dma engine and then continue back to the top of
+			// the loop when wake up occurs.
+			start_adc(s);
+			if (file->f_flags & O_NONBLOCK)
+				return ret ? ret : -EAGAIN;
+			interruptible_sleep_on(&s->dma_adc.wait);
+			if (signal_pending(current))
+				return ret ? ret : -ERESTARTSYS;
+			continue;
+		}
+		// there are bytes in the buffer to read.
+		// copy from the hw buffer over to the user buffer.
+		// user buffer is designated by "buffer"
+		// virtual address to copy from is dma_buf+swptr
+		// the "cnt" is the number of bytes to read.
+
+		CS_DBGOUT(CS_WAVE_READ, 2, printk(KERN_INFO
+			"_read() copy_to cnt=%d count=%d ", cnt_by, count));
+		CS_DBGOUT(CS_WAVE_READ, 8, printk(KERN_INFO
+			 " .sbufsz=%d .count=%d buffer=0x%.8x ret=%d\n",
+				 s->dma_adc.sbufsz, s->dma_adc.count,
+				 (unsigned) buffer, ret));
+
+		if (copy_to_user (buffer, ((void *)s->dma_adc.sb_swptr), cnt_by))
+			return ret ? ret : -EFAULT;
+                copied = cnt_by;
+
+                /* Return the descriptors */
+		spin_lock_irqsave(&s->lock, flags);
+                CS_DBGOUT(CS_FUNCTION, 2, 
+                          printk(KERN_INFO "cs4297a: upd_rcv sw->hw %x/%x\n", s->dma_adc.swptr, s->dma_adc.hwptr));
+		s->dma_adc.count -= cnt_by;
+                s->dma_adc.sb_swptr += cnt * 2;
+                if (s->dma_adc.sb_swptr == s->dma_adc.sb_end)
+                        s->dma_adc.sb_swptr = s->dma_adc.sample_buf;
+		spin_unlock_irqrestore(&s->lock, flags);
+		count -= copied;
+		buffer += copied;
+		ret += copied;
+		start_adc(s);
+	}
+	CS_DBGOUT(CS_FUNCTION | CS_WAVE_READ, 2,
+		  printk(KERN_INFO "cs4297a: cs4297a_read()- %d\n", ret));
+	return ret;
+}
+
+
+static ssize_t cs4297a_write(struct file *file, const char *buffer,
+			    size_t count, loff_t * ppos)
+{
+	struct cs4297a_state *s =
+	    (struct cs4297a_state *) file->private_data;
+	ssize_t ret;
+	unsigned long flags;
+	unsigned swptr, hwptr;
+	int cnt;
+
+	CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE, 2,
+		  printk(KERN_INFO "cs4297a: cs4297a_write()+ count=%d\n",
+			 count));
+	VALIDATE_STATE(s);
+
+	if (s->dma_dac.mapped)
+		return -ENXIO;
+	if (!s->dma_dac.ready && (ret = prog_dmabuf_dac(s)))
+		return ret;
+	if (!access_ok(VERIFY_READ, buffer, count))
+		return -EFAULT;
+	ret = 0;
+	while (count > 0) {
+                serdma_t *d = &s->dma_dac;
+                int copy_cnt;
+                u32 *s_tmpl;
+                u32 *t_tmpl;
+                u32 left, right;
+                int swap = (s->prop_dac.fmt == AFMT_S16_LE) || (s->prop_dac.fmt == AFMT_U16_LE);
+                
+                /* XXXXXX this is broken for BLOAT_FACTOR */
+		spin_lock_irqsave(&s->lock, flags);
+		if (d->count < 0) {
+			d->count = 0;
+			d->swptr = d->hwptr;
+		}
+		if (d->underrun) {
+			d->underrun = 0;
+                        hwptr = (unsigned) (((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_TX)) & M_DMA_CURDSCR_ADDR) -
+                                             d->descrtab_phys) / sizeof(serdma_descr_t));
+			d->swptr = d->hwptr = hwptr;
+		}
+		swptr = d->swptr;
+		cnt = d->sbufsz - (swptr * FRAME_SAMPLE_BYTES);
+                /* Will this write fill up the buffer? */
+		if (d->count + cnt > d->sbufsz)
+			cnt = d->sbufsz - d->count;
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (cnt > count)
+			cnt = count;
+		if (cnt <= 0) {
+			start_dac(s);
+			if (file->f_flags & O_NONBLOCK)
+				return ret ? ret : -EAGAIN;
+			interruptible_sleep_on(&d->wait);
+			if (signal_pending(current))
+				return ret ? ret : -ERESTARTSYS;
+			continue;
+		}
+		if (copy_from_user(d->sample_buf, buffer, cnt))
+			return ret ? ret : -EFAULT;
+
+                copy_cnt = cnt;
+                s_tmpl = (u32 *)d->sample_buf;
+                t_tmpl = (u32 *)(d->dma_buf + (swptr * 4));
+
+                /* XXXKW assuming 16-bit stereo! */
+                do {
+			u32 tmp;
+
+			t_tmpl[0] = cpu_to_be32(0x98000000);
+
+			tmp = be32_to_cpu(s_tmpl[0]);
+			left = tmp & 0xffff;
+			right = tmp >> 16;
+			if (swap) {
+				left = swab16(left);
+				right = swab16(right);
+			}
+			t_tmpl[1] = cpu_to_be32(left >> 8);
+			t_tmpl[2] = cpu_to_be32(((left & 0xff) << 24) |
+						(right << 4));
+
+                        s_tmpl++;
+                        t_tmpl += 8;
+                        copy_cnt -= 4;
+                } while (copy_cnt);
+
+                /* Mux in any pending read/write accesses */
+                if (s->reg_request) {
+			*(u64 *)(d->dma_buf + (swptr * 4)) |=
+				cpu_to_be64(s->reg_request);
+                        s->reg_request = 0;
+                        wake_up(&s->dma_dac.reg_wait);
+                }
+
+                CS_DBGOUT(CS_WAVE_WRITE, 4,
+                          printk(KERN_INFO
+                                 "cs4297a: copy in %d to swptr %x\n", cnt, swptr));
+
+		swptr = (swptr + (cnt/FRAME_SAMPLE_BYTES)) % d->ringsz;
+                __raw_writeq(cnt/FRAME_SAMPLE_BYTES, SS_CSR(R_SER_DMA_DSCR_COUNT_TX));
+		spin_lock_irqsave(&s->lock, flags);
+		d->swptr = swptr;
+		d->count += cnt;
+		d->endcleared = 0;
+		spin_unlock_irqrestore(&s->lock, flags);
+		count -= cnt;
+		buffer += cnt;
+		ret += cnt;
+		start_dac(s);
+	}
+	CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE, 2,
+		  printk(KERN_INFO "cs4297a: cs4297a_write()- %d\n", ret));
+	return ret;
+}
+
+
+static unsigned int cs4297a_poll(struct file *file,
+				struct poll_table_struct *wait)
+{
+	struct cs4297a_state *s =
+	    (struct cs4297a_state *) file->private_data;
+	unsigned long flags;
+	unsigned int mask = 0;
+
+	CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE | CS_WAVE_READ, 4,
+		  printk(KERN_INFO "cs4297a: cs4297a_poll()+\n"));
+	VALIDATE_STATE(s);
+	if (file->f_mode & FMODE_WRITE) {
+		CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE | CS_WAVE_READ, 4,
+			  printk(KERN_INFO
+				 "cs4297a: cs4297a_poll() wait on FMODE_WRITE\n"));
+		if(!s->dma_dac.ready && prog_dmabuf_dac(s))
+			return 0;
+		poll_wait(file, &s->dma_dac.wait, wait);
+	}
+	if (file->f_mode & FMODE_READ) {
+		CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE | CS_WAVE_READ, 4,
+			  printk(KERN_INFO
+				 "cs4297a: cs4297a_poll() wait on FMODE_READ\n"));
+		if(!s->dma_dac.ready && prog_dmabuf_adc(s))
+			return 0;
+		poll_wait(file, &s->dma_adc.wait, wait);
+	}
+	spin_lock_irqsave(&s->lock, flags);
+	cs4297a_update_ptr(s,CS_FALSE);
+	if (file->f_mode & FMODE_WRITE) {
+		if (s->dma_dac.mapped) {
+			if (s->dma_dac.count >=
+			    (signed) s->dma_dac.fragsize) {
+				if (s->dma_dac.wakeup)
+					mask |= POLLOUT | POLLWRNORM;
+				else
+					mask = 0;
+				s->dma_dac.wakeup = 0;
+			}
+		} else {
+			if ((signed) (s->dma_dac.sbufsz/2) >= s->dma_dac.count)
+				mask |= POLLOUT | POLLWRNORM;
+		}
+	} else if (file->f_mode & FMODE_READ) {
+		if (s->dma_adc.mapped) {
+			if (s->dma_adc.count >= (signed) s->dma_adc.fragsize) 
+				mask |= POLLIN | POLLRDNORM;
+		} else {
+			if (s->dma_adc.count > 0)
+				mask |= POLLIN | POLLRDNORM;
+		}
+	}
+	spin_unlock_irqrestore(&s->lock, flags);
+	CS_DBGOUT(CS_FUNCTION | CS_WAVE_WRITE | CS_WAVE_READ, 4,
+		  printk(KERN_INFO "cs4297a: cs4297a_poll()- 0x%.8x\n",
+			 mask));
+	return mask;
+}
+
+
+static int cs4297a_mmap(struct file *file, struct vm_area_struct *vma)
+{
+        /* XXXKW currently no mmap support */
+        return -EINVAL;
+	return 0;
+}
+
+
+static int cs4297a_ioctl(struct file *file,
+			unsigned int cmd, unsigned long arg)
+{
+	struct cs4297a_state *s =
+	    (struct cs4297a_state *) file->private_data;
+	unsigned long flags;
+	audio_buf_info abinfo;
+	count_info cinfo;
+	int val, mapped, ret;
+
+	CS_DBGOUT(CS_FUNCTION|CS_IOCTL, 4, printk(KERN_INFO
+		 "cs4297a: cs4297a_ioctl(): file=0x%.8x cmd=0x%.8x\n",
+			 (unsigned) file, cmd));
+#if CSDEBUG
+	cs_printioctl(cmd);
+#endif
+	VALIDATE_STATE(s);
+	mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac.mapped) ||
+	    ((file->f_mode & FMODE_READ) && s->dma_adc.mapped);
+	switch (cmd) {
+	case OSS_GETVERSION:
+		CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+			"cs4297a: cs4297a_ioctl(): SOUND_VERSION=0x%.8x\n",
+				 SOUND_VERSION));
+		return put_user(SOUND_VERSION, (int *) arg);
+
+	case SNDCTL_DSP_SYNC:
+		CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO
+			 "cs4297a: cs4297a_ioctl(): DSP_SYNC\n"));
+		if (file->f_mode & FMODE_WRITE)
+			return drain_dac(s,
+					 0 /*file->f_flags & O_NONBLOCK */
+					 );
+		return 0;
+
+	case SNDCTL_DSP_SETDUPLEX:
+		return 0;
+
+	case SNDCTL_DSP_GETCAPS:
+		return put_user(DSP_CAP_DUPLEX | DSP_CAP_REALTIME |
+				DSP_CAP_TRIGGER | DSP_CAP_MMAP,
+				(int *) arg);
+
+	case SNDCTL_DSP_RESET:
+		CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO
+			 "cs4297a: cs4297a_ioctl(): DSP_RESET\n"));
+		if (file->f_mode & FMODE_WRITE) {
+			stop_dac(s);
+			synchronize_irq(s->irq);
+                        s->dma_dac.count = s->dma_dac.total_bytes =
+                                s->dma_dac.blocks = s->dma_dac.wakeup = 0;
+			s->dma_dac.swptr = s->dma_dac.hwptr =
+                                (int)(((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_TX)) & M_DMA_CURDSCR_ADDR) -
+                                       s->dma_dac.descrtab_phys) / sizeof(serdma_descr_t));
+		}
+		if (file->f_mode & FMODE_READ) {
+			stop_adc(s);
+			synchronize_irq(s->irq);
+                        s->dma_adc.count = s->dma_adc.total_bytes =
+                                s->dma_adc.blocks = s->dma_dac.wakeup = 0;
+			s->dma_adc.swptr = s->dma_adc.hwptr =
+                                (int)(((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_RX)) & M_DMA_CURDSCR_ADDR) -
+                                       s->dma_adc.descrtab_phys) / sizeof(serdma_descr_t));
+		}
+		return 0;
+
+	case SNDCTL_DSP_SPEED:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+			 "cs4297a: cs4297a_ioctl(): DSP_SPEED val=%d -> 48000\n", val));
+                val = 48000;
+                return put_user(val, (int *) arg);
+
+	case SNDCTL_DSP_STEREO:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+			 "cs4297a: cs4297a_ioctl(): DSP_STEREO val=%d\n", val));
+		if (file->f_mode & FMODE_READ) {
+			stop_adc(s);
+			s->dma_adc.ready = 0;
+			s->prop_adc.channels = val ? 2 : 1;
+		}
+		if (file->f_mode & FMODE_WRITE) {
+			stop_dac(s);
+			s->dma_dac.ready = 0;
+			s->prop_dac.channels = val ? 2 : 1;
+		}
+		return 0;
+
+	case SNDCTL_DSP_CHANNELS:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+			 "cs4297a: cs4297a_ioctl(): DSP_CHANNELS val=%d\n",
+				 val));
+		if (val != 0) {
+			if (file->f_mode & FMODE_READ) {
+				stop_adc(s);
+				s->dma_adc.ready = 0;
+				if (val >= 2)
+					s->prop_adc.channels = 2;
+				else
+					s->prop_adc.channels = 1;
+			}
+			if (file->f_mode & FMODE_WRITE) {
+				stop_dac(s);
+				s->dma_dac.ready = 0;
+				if (val >= 2)
+					s->prop_dac.channels = 2;
+				else
+					s->prop_dac.channels = 1;
+			}
+		}
+
+		if (file->f_mode & FMODE_WRITE)
+			val = s->prop_dac.channels;
+		else if (file->f_mode & FMODE_READ)
+			val = s->prop_adc.channels;
+
+		return put_user(val, (int *) arg);
+
+	case SNDCTL_DSP_GETFMTS:	// Returns a mask 
+		CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+			"cs4297a: cs4297a_ioctl(): DSP_GETFMT val=0x%.8x\n",
+				 AFMT_S16_LE | AFMT_U16_LE | AFMT_S8 |
+				 AFMT_U8));
+		return put_user(AFMT_S16_LE | AFMT_U16_LE | AFMT_S8 |
+				AFMT_U8, (int *) arg);
+
+	case SNDCTL_DSP_SETFMT:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+			 "cs4297a: cs4297a_ioctl(): DSP_SETFMT val=0x%.8x\n",
+				 val));
+		if (val != AFMT_QUERY) {
+			if (file->f_mode & FMODE_READ) {
+				stop_adc(s);
+				s->dma_adc.ready = 0;
+				if (val != AFMT_S16_LE
+				    && val != AFMT_U16_LE && val != AFMT_S8
+				    && val != AFMT_U8)
+					val = AFMT_U8;
+				s->prop_adc.fmt = val;
+				s->prop_adc.fmt_original = s->prop_adc.fmt;
+			}
+			if (file->f_mode & FMODE_WRITE) {
+				stop_dac(s);
+				s->dma_dac.ready = 0;
+				if (val != AFMT_S16_LE
+				    && val != AFMT_U16_LE && val != AFMT_S8
+				    && val != AFMT_U8)
+					val = AFMT_U8;
+				s->prop_dac.fmt = val;
+				s->prop_dac.fmt_original = s->prop_dac.fmt;
+			}
+		} else {
+			if (file->f_mode & FMODE_WRITE)
+				val = s->prop_dac.fmt_original;
+			else if (file->f_mode & FMODE_READ)
+				val = s->prop_adc.fmt_original;
+		}
+		CS_DBGOUT(CS_IOCTL | CS_PARMS, 4, printk(KERN_INFO
+		  "cs4297a: cs4297a_ioctl(): DSP_SETFMT return val=0x%.8x\n", 
+			val));
+		return put_user(val, (int *) arg);
+
+	case SNDCTL_DSP_POST:
+		CS_DBGOUT(CS_IOCTL, 4, printk(KERN_INFO
+			 "cs4297a: cs4297a_ioctl(): DSP_POST\n"));
+		return 0;
+
+	case SNDCTL_DSP_GETTRIGGER:
+		val = 0;
+		if (file->f_mode & s->ena & FMODE_READ)
+			val |= PCM_ENABLE_INPUT;
+		if (file->f_mode & s->ena & FMODE_WRITE)
+			val |= PCM_ENABLE_OUTPUT;
+		return put_user(val, (int *) arg);
+
+	case SNDCTL_DSP_SETTRIGGER:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		if (file->f_mode & FMODE_READ) {
+			if (val & PCM_ENABLE_INPUT) {
+				if (!s->dma_adc.ready
+				    && (ret = prog_dmabuf_adc(s)))
+					return ret;
+				start_adc(s);
+			} else
+				stop_adc(s);
+		}
+		if (file->f_mode & FMODE_WRITE) {
+			if (val & PCM_ENABLE_OUTPUT) {
+				if (!s->dma_dac.ready
+				    && (ret = prog_dmabuf_dac(s)))
+					return ret;
+				start_dac(s);
+			} else
+				stop_dac(s);
+		}
+		return 0;
+
+	case SNDCTL_DSP_GETOSPACE:
+		if (!(file->f_mode & FMODE_WRITE))
+			return -EINVAL;
+		if (!s->dma_dac.ready && (val = prog_dmabuf_dac(s)))
+			return val;
+		spin_lock_irqsave(&s->lock, flags);
+		cs4297a_update_ptr(s,CS_FALSE);
+		abinfo.fragsize = s->dma_dac.fragsize;
+		if (s->dma_dac.mapped)
+			abinfo.bytes = s->dma_dac.sbufsz;
+		else
+			abinfo.bytes =
+			    s->dma_dac.sbufsz - s->dma_dac.count;
+		abinfo.fragstotal = s->dma_dac.numfrag;
+		abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift;
+		CS_DBGOUT(CS_FUNCTION | CS_PARMS, 4, printk(KERN_INFO
+			"cs4297a: cs4297a_ioctl(): GETOSPACE .fragsize=%d .bytes=%d .fragstotal=%d .fragments=%d\n",
+				abinfo.fragsize,abinfo.bytes,abinfo.fragstotal,
+				abinfo.fragments));
+		spin_unlock_irqrestore(&s->lock, flags);
+		return copy_to_user((void *) arg, &abinfo,
+				    sizeof(abinfo)) ? -EFAULT : 0;
+
+	case SNDCTL_DSP_GETISPACE:
+		if (!(file->f_mode & FMODE_READ))
+			return -EINVAL;
+		if (!s->dma_adc.ready && (val = prog_dmabuf_adc(s)))
+			return val;
+		spin_lock_irqsave(&s->lock, flags);
+		cs4297a_update_ptr(s,CS_FALSE);
+		if (s->conversion) {
+			abinfo.fragsize = s->dma_adc.fragsize / 2;
+			abinfo.bytes = s->dma_adc.count / 2;
+			abinfo.fragstotal = s->dma_adc.numfrag;
+			abinfo.fragments =
+			    abinfo.bytes >> (s->dma_adc.fragshift - 1);
+		} else {
+			abinfo.fragsize = s->dma_adc.fragsize;
+			abinfo.bytes = s->dma_adc.count;
+			abinfo.fragstotal = s->dma_adc.numfrag;
+			abinfo.fragments =
+			    abinfo.bytes >> s->dma_adc.fragshift;
+		}
+		spin_unlock_irqrestore(&s->lock, flags);
+		return copy_to_user((void *) arg, &abinfo,
+				    sizeof(abinfo)) ? -EFAULT : 0;
+
+	case SNDCTL_DSP_NONBLOCK:
+		spin_lock(&file->f_lock);
+		file->f_flags |= O_NONBLOCK;
+		spin_unlock(&file->f_lock);
+		return 0;
+
+	case SNDCTL_DSP_GETODELAY:
+		if (!(file->f_mode & FMODE_WRITE))
+			return -EINVAL;
+		if(!s->dma_dac.ready && prog_dmabuf_dac(s))
+			return 0;
+		spin_lock_irqsave(&s->lock, flags);
+		cs4297a_update_ptr(s,CS_FALSE);
+		val = s->dma_dac.count;
+		spin_unlock_irqrestore(&s->lock, flags);
+		return put_user(val, (int *) arg);
+
+	case SNDCTL_DSP_GETIPTR:
+		if (!(file->f_mode & FMODE_READ))
+			return -EINVAL;
+		if(!s->dma_adc.ready && prog_dmabuf_adc(s))
+			return 0;
+		spin_lock_irqsave(&s->lock, flags);
+		cs4297a_update_ptr(s,CS_FALSE);
+		cinfo.bytes = s->dma_adc.total_bytes;
+		if (s->dma_adc.mapped) {
+			cinfo.blocks =
+			    (cinfo.bytes >> s->dma_adc.fragshift) -
+			    s->dma_adc.blocks;
+			s->dma_adc.blocks =
+			    cinfo.bytes >> s->dma_adc.fragshift;
+		} else {
+			if (s->conversion) {
+				cinfo.blocks =
+				    s->dma_adc.count /
+				    2 >> (s->dma_adc.fragshift - 1);
+			} else
+				cinfo.blocks =
+				    s->dma_adc.count >> s->dma_adc.
+				    fragshift;
+		}
+		if (s->conversion)
+			cinfo.ptr = s->dma_adc.hwptr / 2;
+		else
+			cinfo.ptr = s->dma_adc.hwptr;
+		if (s->dma_adc.mapped)
+			s->dma_adc.count &= s->dma_adc.fragsize - 1;
+		spin_unlock_irqrestore(&s->lock, flags);
+		return copy_to_user((void *) arg, &cinfo, sizeof(cinfo)) ? -EFAULT : 0;
+
+	case SNDCTL_DSP_GETOPTR:
+		if (!(file->f_mode & FMODE_WRITE))
+			return -EINVAL;
+		if(!s->dma_dac.ready && prog_dmabuf_dac(s))
+			return 0;
+		spin_lock_irqsave(&s->lock, flags);
+		cs4297a_update_ptr(s,CS_FALSE);
+		cinfo.bytes = s->dma_dac.total_bytes;
+		if (s->dma_dac.mapped) {
+			cinfo.blocks =
+			    (cinfo.bytes >> s->dma_dac.fragshift) -
+			    s->dma_dac.blocks;
+			s->dma_dac.blocks =
+			    cinfo.bytes >> s->dma_dac.fragshift;
+		} else {
+			cinfo.blocks =
+			    s->dma_dac.count >> s->dma_dac.fragshift;
+		}
+		cinfo.ptr = s->dma_dac.hwptr;
+		if (s->dma_dac.mapped)
+			s->dma_dac.count &= s->dma_dac.fragsize - 1;
+		spin_unlock_irqrestore(&s->lock, flags);
+		return copy_to_user((void *) arg, &cinfo, sizeof(cinfo)) ? -EFAULT : 0;
+
+	case SNDCTL_DSP_GETBLKSIZE:
+		if (file->f_mode & FMODE_WRITE) {
+			if ((val = prog_dmabuf_dac(s)))
+				return val;
+			return put_user(s->dma_dac.fragsize, (int *) arg);
+		}
+		if ((val = prog_dmabuf_adc(s)))
+			return val;
+		if (s->conversion)
+			return put_user(s->dma_adc.fragsize / 2,
+					(int *) arg);
+		else
+			return put_user(s->dma_adc.fragsize, (int *) arg);
+
+	case SNDCTL_DSP_SETFRAGMENT:
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		return 0;	// Say OK, but do nothing.
+
+	case SNDCTL_DSP_SUBDIVIDE:
+		if ((file->f_mode & FMODE_READ && s->dma_adc.subdivision)
+		    || (file->f_mode & FMODE_WRITE
+			&& s->dma_dac.subdivision)) return -EINVAL;
+		if (get_user(val, (int *) arg))
+			return -EFAULT;
+		if (val != 1 && val != 2 && val != 4)
+			return -EINVAL;
+		if (file->f_mode & FMODE_READ)
+			s->dma_adc.subdivision = val;
+		else if (file->f_mode & FMODE_WRITE)
+			s->dma_dac.subdivision = val;
+		return 0;
+
+	case SOUND_PCM_READ_RATE:
+		if (file->f_mode & FMODE_READ)
+			return put_user(s->prop_adc.rate, (int *) arg);
+		else if (file->f_mode & FMODE_WRITE)
+			return put_user(s->prop_dac.rate, (int *) arg);
+
+	case SOUND_PCM_READ_CHANNELS:
+		if (file->f_mode & FMODE_READ)
+			return put_user(s->prop_adc.channels, (int *) arg);
+		else if (file->f_mode & FMODE_WRITE)
+			return put_user(s->prop_dac.channels, (int *) arg);
+
+	case SOUND_PCM_READ_BITS:
+		if (file->f_mode & FMODE_READ)
+			return
+			    put_user(
+				     (s->prop_adc.
+				      fmt & (AFMT_S8 | AFMT_U8)) ? 8 : 16,
+				     (int *) arg);
+		else if (file->f_mode & FMODE_WRITE)
+			return
+			    put_user(
+				     (s->prop_dac.
+				      fmt & (AFMT_S8 | AFMT_U8)) ? 8 : 16,
+				     (int *) arg);
+
+	case SOUND_PCM_WRITE_FILTER:
+	case SNDCTL_DSP_SETSYNCRO:
+	case SOUND_PCM_READ_FILTER:
+		return -EINVAL;
+	}
+	return mixer_ioctl(s, cmd, arg);
+}
+
+static long cs4297a_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
+{
+	int ret;
+
+	mutex_lock(&swarm_cs4297a_mutex);
+	ret = cs4297a_ioctl(file, cmd, arg);
+	mutex_unlock(&swarm_cs4297a_mutex);
+
+	return ret;
+}
+
+static int cs4297a_release(struct inode *inode, struct file *file)
+{
+	struct cs4297a_state *s =
+	    (struct cs4297a_state *) file->private_data;
+
+        CS_DBGOUT(CS_FUNCTION | CS_RELEASE, 2, printk(KERN_INFO
+		 "cs4297a: cs4297a_release(): inode=0x%.8x file=0x%.8x f_mode=0x%x\n",
+			 (unsigned) inode, (unsigned) file, file->f_mode));
+	VALIDATE_STATE(s);
+
+	if (file->f_mode & FMODE_WRITE) {
+		drain_dac(s, file->f_flags & O_NONBLOCK);
+		mutex_lock(&s->open_sem_dac);
+		stop_dac(s);
+		dealloc_dmabuf(s, &s->dma_dac);
+		s->open_mode &= ~FMODE_WRITE;
+		mutex_unlock(&s->open_sem_dac);
+		wake_up(&s->open_wait_dac);
+	}
+	if (file->f_mode & FMODE_READ) {
+		drain_adc(s, file->f_flags & O_NONBLOCK);
+		mutex_lock(&s->open_sem_adc);
+		stop_adc(s);
+		dealloc_dmabuf(s, &s->dma_adc);
+		s->open_mode &= ~FMODE_READ;
+		mutex_unlock(&s->open_sem_adc);
+		wake_up(&s->open_wait_adc);
+	}
+	return 0;
+}
+
+static int cs4297a_locked_open(struct inode *inode, struct file *file)
+{
+	int minor = iminor(inode);
+	struct cs4297a_state *s=NULL;
+	struct list_head *entry;
+
+	CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, printk(KERN_INFO
+		"cs4297a: cs4297a_open(): inode=0x%.8x file=0x%.8x f_mode=0x%x\n",
+			(unsigned) inode, (unsigned) file, file->f_mode));
+	CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, printk(KERN_INFO
+                "cs4297a: status = %08x\n", (int)__raw_readq(SS_CSR(R_SER_STATUS_DEBUG))));
+
+	list_for_each(entry, &cs4297a_devs)
+	{
+		s = list_entry(entry, struct cs4297a_state, list);
+
+		if (!((s->dev_audio ^ minor) & ~0xf))
+			break;
+	}
+	if (entry == &cs4297a_devs)
+		return -ENODEV;
+	if (!s) {
+		CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2, printk(KERN_INFO
+			"cs4297a: cs4297a_open(): Error - unable to find audio state struct\n"));
+		return -ENODEV;
+	}
+	VALIDATE_STATE(s);
+	file->private_data = s;
+
+	// wait for device to become free 
+	if (!(file->f_mode & (FMODE_WRITE | FMODE_READ))) {
+		CS_DBGOUT(CS_FUNCTION | CS_OPEN | CS_ERROR, 2, printk(KERN_INFO
+			 "cs4297a: cs4297a_open(): Error - must open READ and/or WRITE\n"));
+		return -ENODEV;
+	}
+	if (file->f_mode & FMODE_WRITE) {
+                if (__raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_TX)) != 0) {
+                        printk(KERN_ERR "cs4297a: TX pipe needs to drain\n");
+                        while (__raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_TX)))
+                                ;
+                }
+          
+		mutex_lock(&s->open_sem_dac);
+		while (s->open_mode & FMODE_WRITE) {
+			if (file->f_flags & O_NONBLOCK) {
+				mutex_unlock(&s->open_sem_dac);
+				return -EBUSY;
+			}
+			mutex_unlock(&s->open_sem_dac);
+			interruptible_sleep_on(&s->open_wait_dac);
+
+			if (signal_pending(current)) {
+                                printk("open - sig pending\n");
+				return -ERESTARTSYS;
+                        }
+			mutex_lock(&s->open_sem_dac);
+		}
+	}
+	if (file->f_mode & FMODE_READ) {
+		mutex_lock(&s->open_sem_adc);
+		while (s->open_mode & FMODE_READ) {
+			if (file->f_flags & O_NONBLOCK) {
+				mutex_unlock(&s->open_sem_adc);
+				return -EBUSY;
+			}
+			mutex_unlock(&s->open_sem_adc);
+			interruptible_sleep_on(&s->open_wait_adc);
+
+			if (signal_pending(current)) {
+                                printk("open - sig pending\n");
+				return -ERESTARTSYS;
+                        }
+			mutex_lock(&s->open_sem_adc);
+		}
+	}
+	s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE);
+	if (file->f_mode & FMODE_READ) {
+		s->prop_adc.fmt = AFMT_S16_BE;
+		s->prop_adc.fmt_original = s->prop_adc.fmt;
+		s->prop_adc.channels = 2;
+		s->prop_adc.rate = 48000;
+		s->conversion = 0;
+		s->ena &= ~FMODE_READ;
+		s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags =
+		    s->dma_adc.subdivision = 0;
+		mutex_unlock(&s->open_sem_adc);
+
+		if (prog_dmabuf_adc(s)) {
+			CS_DBGOUT(CS_OPEN | CS_ERROR, 2, printk(KERN_ERR
+				"cs4297a: adc Program dmabufs failed.\n"));
+			cs4297a_release(inode, file);
+			return -ENOMEM;
+		}
+	}
+	if (file->f_mode & FMODE_WRITE) {
+		s->prop_dac.fmt = AFMT_S16_BE;
+		s->prop_dac.fmt_original = s->prop_dac.fmt;
+		s->prop_dac.channels = 2;
+		s->prop_dac.rate = 48000;
+		s->conversion = 0;
+		s->ena &= ~FMODE_WRITE;
+		s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags =
+		    s->dma_dac.subdivision = 0;
+		mutex_unlock(&s->open_sem_dac);
+
+		if (prog_dmabuf_dac(s)) {
+			CS_DBGOUT(CS_OPEN | CS_ERROR, 2, printk(KERN_ERR
+				"cs4297a: dac Program dmabufs failed.\n"));
+			cs4297a_release(inode, file);
+			return -ENOMEM;
+		}
+	}
+	CS_DBGOUT(CS_FUNCTION | CS_OPEN, 2,
+		  printk(KERN_INFO "cs4297a: cs4297a_open()- 0\n"));
+	return nonseekable_open(inode, file);
+}
+
+static int cs4297a_open(struct inode *inode, struct file *file)
+{
+	int ret;
+
+	mutex_lock(&swarm_cs4297a_mutex);
+	ret = cs4297a_open(inode, file);
+	mutex_unlock(&swarm_cs4297a_mutex);
+
+	return ret;
+}
+
+// ******************************************************************************************
+//   Wave (audio) file operations struct.
+// ******************************************************************************************
+static const struct file_operations cs4297a_audio_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= cs4297a_read,
+	.write		= cs4297a_write,
+	.poll		= cs4297a_poll,
+	.unlocked_ioctl	= cs4297a_unlocked_ioctl,
+	.mmap		= cs4297a_mmap,
+	.open		= cs4297a_open,
+	.release	= cs4297a_release,
+};
+
+static void cs4297a_interrupt(int irq, void *dev_id)
+{
+	struct cs4297a_state *s = (struct cs4297a_state *) dev_id;
+        u32 status;
+
+        status = __raw_readq(SS_CSR(R_SER_STATUS_DEBUG));
+
+        CS_DBGOUT(CS_INTERRUPT, 6, printk(KERN_INFO
+                 "cs4297a: cs4297a_interrupt() HISR=0x%.8x\n", status));
+
+#if 0
+        /* XXXKW what check *should* be done here? */
+        if (!(status & (M_SYNCSER_RX_EOP_COUNT | M_SYNCSER_RX_OVERRUN | M_SYNCSER_RX_SYNC_ERR))) {
+                status = __raw_readq(SS_CSR(R_SER_STATUS));
+                printk(KERN_ERR "cs4297a: unexpected interrupt (status %08x)\n", status);
+                return;
+        }
+#endif
+
+        if (status & M_SYNCSER_RX_SYNC_ERR) {
+                status = __raw_readq(SS_CSR(R_SER_STATUS));
+                printk(KERN_ERR "cs4297a: rx sync error (status %08x)\n", status);
+                return;
+        }
+
+        if (status & M_SYNCSER_RX_OVERRUN) {
+                int newptr, i;
+                s->stats.rx_ovrrn++;
+                printk(KERN_ERR "cs4297a: receive FIFO overrun\n");
+
+                /* Fix things up: get the receive descriptor pool
+                   clean and give them back to the hardware */
+                while (__raw_readq(SS_CSR(R_SER_DMA_DSCR_COUNT_RX)))
+                        ;
+                newptr = (unsigned) (((__raw_readq(SS_CSR(R_SER_DMA_CUR_DSCR_ADDR_RX)) & M_DMA_CURDSCR_ADDR) -
+                                     s->dma_adc.descrtab_phys) / sizeof(serdma_descr_t));
+                for (i=0; i<DMA_DESCR; i++) {
+                        s->dma_adc.descrtab[i].descr_a &= ~M_DMA_SERRX_SOP;
+                }
+                s->dma_adc.swptr = s->dma_adc.hwptr = newptr;
+                s->dma_adc.count = 0;
+                s->dma_adc.sb_swptr = s->dma_adc.sb_hwptr = s->dma_adc.sample_buf;
+                __raw_writeq(DMA_DESCR, SS_CSR(R_SER_DMA_DSCR_COUNT_RX));
+        }
+
+	spin_lock(&s->lock);
+	cs4297a_update_ptr(s,CS_TRUE);
+	spin_unlock(&s->lock);
+
+	CS_DBGOUT(CS_INTERRUPT, 6, printk(KERN_INFO
+		  "cs4297a: cs4297a_interrupt()-\n"));
+}
+
+#if 0
+static struct initvol {
+	int mixch;
+	int vol;
+} initvol[] __initdata = {
+
+  	{SOUND_MIXER_WRITE_VOLUME, 0x4040},
+        {SOUND_MIXER_WRITE_PCM, 0x4040},
+        {SOUND_MIXER_WRITE_SYNTH, 0x4040},
+	{SOUND_MIXER_WRITE_CD, 0x4040},
+	{SOUND_MIXER_WRITE_LINE, 0x4040},
+	{SOUND_MIXER_WRITE_LINE1, 0x4040},
+	{SOUND_MIXER_WRITE_RECLEV, 0x0000},
+	{SOUND_MIXER_WRITE_SPEAKER, 0x4040},
+	{SOUND_MIXER_WRITE_MIC, 0x0000}
+};
+#endif
+
+static int __init cs4297a_init(void)
+{
+	struct cs4297a_state *s;
+	u32 pwr, id;
+	mm_segment_t fs;
+	int rval;
+#ifndef CONFIG_BCM_CS4297A_CSWARM
+	u64 cfg;
+	int mdio_val;
+#endif
+
+	CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, printk(KERN_INFO 
+		"cs4297a: cs4297a_init_module()+ \n"));
+
+#ifndef CONFIG_BCM_CS4297A_CSWARM
+        mdio_val = __raw_readq(KSEG1 + A_MAC_REGISTER(2, R_MAC_MDIO)) &
+                (M_MAC_MDIO_DIR|M_MAC_MDIO_OUT);
+
+        /* Check syscfg for synchronous serial on port 1 */
+        cfg = __raw_readq(KSEG1 + A_SCD_SYSTEM_CFG);
+        if (!(cfg & M_SYS_SER1_ENABLE)) {
+                __raw_writeq(cfg | M_SYS_SER1_ENABLE, KSEG1+A_SCD_SYSTEM_CFG);
+                cfg = __raw_readq(KSEG1 + A_SCD_SYSTEM_CFG);
+                if (!(cfg & M_SYS_SER1_ENABLE)) {
+                  printk(KERN_INFO "cs4297a: serial port 1 not configured for synchronous operation\n");
+                  return -1;
+                }
+
+                printk(KERN_INFO "cs4297a: serial port 1 switching to synchronous operation\n");
+                
+                /* Force the codec (on SWARM) to reset by clearing
+                   GENO, preserving MDIO (no effect on CSWARM) */
+                __raw_writeq(mdio_val, KSEG1+A_MAC_REGISTER(2, R_MAC_MDIO));
+                udelay(10);
+        }
+
+        /* Now set GENO */
+        __raw_writeq(mdio_val | M_MAC_GENC, KSEG1+A_MAC_REGISTER(2, R_MAC_MDIO));
+        /* Give the codec some time to finish resetting (start the bit clock) */
+        udelay(100);
+#endif
+
+	if (!(s = kzalloc(sizeof(struct cs4297a_state), GFP_KERNEL))) {
+		CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR
+		      "cs4297a: probe() no memory for state struct.\n"));
+		return -1;
+	}
+        s->magic = CS4297a_MAGIC;
+	init_waitqueue_head(&s->dma_adc.wait);
+	init_waitqueue_head(&s->dma_dac.wait);
+	init_waitqueue_head(&s->dma_adc.reg_wait);
+	init_waitqueue_head(&s->dma_dac.reg_wait);
+	init_waitqueue_head(&s->open_wait);
+	init_waitqueue_head(&s->open_wait_adc);
+	init_waitqueue_head(&s->open_wait_dac);
+	mutex_init(&s->open_sem_adc);
+	mutex_init(&s->open_sem_dac);
+	spin_lock_init(&s->lock);
+
+        s->irq = K_INT_SER_1;
+
+	if (request_irq
+	    (s->irq, cs4297a_interrupt, 0, "Crystal CS4297a", s)) {
+		CS_DBGOUT(CS_INIT | CS_ERROR, 1,
+			  printk(KERN_ERR "cs4297a: irq %u in use\n", s->irq));
+		goto err_irq;
+	}
+	if ((s->dev_audio = register_sound_dsp(&cs4297a_audio_fops, -1)) <
+	    0) {
+		CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_ERR
+			 "cs4297a: probe() register_sound_dsp() failed.\n"));
+		goto err_dev1;
+	}
+	if ((s->dev_mixer = register_sound_mixer(&cs4297a_mixer_fops, -1)) <
+	    0) {
+		CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_ERR
+			 "cs4297a: probe() register_sound_mixer() failed.\n"));
+		goto err_dev2;
+	}
+
+        if (ser_init(s) || dma_init(s)) {
+		CS_DBGOUT(CS_INIT | CS_ERROR, 1, printk(KERN_ERR
+			 "cs4297a: ser_init failed.\n"));
+		goto err_dev3;
+        }
+
+        do {
+                udelay(4000);
+                rval = cs4297a_read_ac97(s, AC97_POWER_CONTROL, &pwr);
+        } while (!rval && (pwr != 0xf));
+
+        if (!rval) {
+		char *sb1250_duart_present;
+
+                fs = get_fs();
+                set_fs(KERNEL_DS);
+#if 0
+                val = SOUND_MASK_LINE;
+                mixer_ioctl(s, SOUND_MIXER_WRITE_RECSRC, (unsigned long) &val);
+                for (i = 0; i < ARRAY_SIZE(initvol); i++) {
+                        val = initvol[i].vol;
+                        mixer_ioctl(s, initvol[i].mixch, (unsigned long) &val);
+                }
+//                cs4297a_write_ac97(s, 0x18, 0x0808);
+#else
+                //                cs4297a_write_ac97(s, 0x5e, 0x180);
+                cs4297a_write_ac97(s, 0x02, 0x0808);
+                cs4297a_write_ac97(s, 0x18, 0x0808);
+#endif
+                set_fs(fs);
+
+                list_add(&s->list, &cs4297a_devs);
+
+                cs4297a_read_ac97(s, AC97_VENDOR_ID1, &id);
+
+		sb1250_duart_present = symbol_get(sb1250_duart_present);
+		if (sb1250_duart_present)
+			sb1250_duart_present[1] = 0;
+
+                printk(KERN_INFO "cs4297a: initialized (vendor id = %x)\n", id);
+
+                CS_DBGOUT(CS_INIT | CS_FUNCTION, 2,
+                          printk(KERN_INFO "cs4297a: cs4297a_init_module()-\n"));
+                
+                return 0;
+        }
+
+ err_dev3:
+	unregister_sound_mixer(s->dev_mixer);
+ err_dev2:
+	unregister_sound_dsp(s->dev_audio);
+ err_dev1:
+	free_irq(s->irq, s);
+ err_irq:
+	kfree(s);
+
+        printk(KERN_INFO "cs4297a: initialization failed\n");
+
+        return -1;
+}
+
+static void __exit cs4297a_cleanup(void)
+{
+        /*
+          XXXKW 
+           disable_irq, free_irq
+           drain DMA queue
+           disable DMA
+           disable TX/RX
+           free memory
+        */
+	CS_DBGOUT(CS_INIT | CS_FUNCTION, 2,
+		  printk(KERN_INFO "cs4297a: cleanup_cs4297a() finished\n"));
+}
+
+// --------------------------------------------------------------------- 
+
+MODULE_AUTHOR("Kip Walker, Broadcom Corp.");
+MODULE_DESCRIPTION("Cirrus Logic CS4297a Driver for Broadcom SWARM board");
+
+// --------------------------------------------------------------------- 
+
+module_init(cs4297a_init);
+module_exit(cs4297a_cleanup);
diff --git a/linux/sound/oss/sys_timer.c b/linux/sound/oss/sys_timer.c
new file mode 100644
index 0000000..8db6aef
--- /dev/null
+++ b/linux/sound/oss/sys_timer.c
@@ -0,0 +1,285 @@
+/*
+ * sound/oss/sys_timer.c
+ *
+ * The default timer for the Level 2 sequencer interface
+ * Uses the (1/HZ sec) timer of kernel.
+ */
+/*
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ */
+/*
+ * Thomas Sailer   : ioctl code reworked (vmalloc/vfree removed)
+ * Andrew Veliath  : adapted tmr2ticks from level 1 sequencer (avoid overflow)
+ */
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+static volatile int opened, tmr_running;
+static volatile time_t tmr_offs, tmr_ctr;
+static volatile unsigned long ticks_offs;
+static volatile int curr_tempo, curr_timebase;
+static volatile unsigned long curr_ticks;
+static volatile unsigned long next_event_time;
+static unsigned long prev_event_time;
+
+static void     poll_def_tmr(unsigned long dummy);
+static DEFINE_SPINLOCK(lock);
+static DEFINE_TIMER(def_tmr, poll_def_tmr, 0, 0);
+
+static unsigned long
+tmr2ticks(int tmr_value)
+{
+	/*
+	 *    Convert timer ticks to MIDI ticks
+	 */
+
+	unsigned long tmp;
+	unsigned long scale;
+
+	/* tmr_value (ticks per sec) *
+	   1000000 (usecs per sec) / HZ (ticks per sec) -=> usecs */
+	tmp = tmr_value * (1000000 / HZ);
+	scale = (60 * 1000000) / (curr_tempo * curr_timebase);	/* usecs per MIDI tick */
+	return (tmp + scale / 2) / scale;
+}
+
+static void
+poll_def_tmr(unsigned long dummy)
+{
+
+	if (opened)
+	  {
+
+		  {
+			  def_tmr.expires = (1) + jiffies;
+			  add_timer(&def_tmr);
+		  };
+
+		  if (tmr_running)
+		    {
+				spin_lock(&lock);
+			    tmr_ctr++;
+			    curr_ticks = ticks_offs + tmr2ticks(tmr_ctr);
+
+			    if (curr_ticks >= next_event_time)
+			      {
+				      next_event_time = (unsigned long) -1;
+				      sequencer_timer(0);
+			      }
+				spin_unlock(&lock);
+		    }
+	  }
+}
+
+static void
+tmr_reset(void)
+{
+	unsigned long   flags;
+
+	spin_lock_irqsave(&lock,flags);
+	tmr_offs = 0;
+	ticks_offs = 0;
+	tmr_ctr = 0;
+	next_event_time = (unsigned long) -1;
+	prev_event_time = 0;
+	curr_ticks = 0;
+	spin_unlock_irqrestore(&lock,flags);
+}
+
+static int
+def_tmr_open(int dev, int mode)
+{
+	if (opened)
+		return -EBUSY;
+
+	tmr_reset();
+	curr_tempo = 60;
+	curr_timebase = 100;
+	opened = 1;
+	{
+		def_tmr.expires = (1) + jiffies;
+		add_timer(&def_tmr);
+	};
+
+	return 0;
+}
+
+static void
+def_tmr_close(int dev)
+{
+	opened = tmr_running = 0;
+	del_timer(&def_tmr);
+}
+
+static int
+def_tmr_event(int dev, unsigned char *event)
+{
+	unsigned char   cmd = event[1];
+	unsigned long   parm = *(int *) &event[4];
+
+	switch (cmd)
+	  {
+	  case TMR_WAIT_REL:
+		  parm += prev_event_time;
+	  case TMR_WAIT_ABS:
+		  if (parm > 0)
+		    {
+			    long            time;
+
+			    if (parm <= curr_ticks)	/* It's the time */
+				    return TIMER_NOT_ARMED;
+
+			    time = parm;
+			    next_event_time = prev_event_time = time;
+
+			    return TIMER_ARMED;
+		    }
+		  break;
+
+	  case TMR_START:
+		  tmr_reset();
+		  tmr_running = 1;
+		  break;
+
+	  case TMR_STOP:
+		  tmr_running = 0;
+		  break;
+
+	  case TMR_CONTINUE:
+		  tmr_running = 1;
+		  break;
+
+	  case TMR_TEMPO:
+		  if (parm)
+		    {
+			    if (parm < 8)
+				    parm = 8;
+			    if (parm > 360)
+				    parm = 360;
+			    tmr_offs = tmr_ctr;
+			    ticks_offs += tmr2ticks(tmr_ctr);
+			    tmr_ctr = 0;
+			    curr_tempo = parm;
+		    }
+		  break;
+
+	  case TMR_ECHO:
+		  seq_copy_to_input(event, 8);
+		  break;
+
+	  default:;
+	  }
+
+	return TIMER_NOT_ARMED;
+}
+
+static unsigned long
+def_tmr_get_time(int dev)
+{
+	if (!opened)
+		return 0;
+
+	return curr_ticks;
+}
+
+/* same as sound_timer.c:timer_ioctl!? */
+static int def_tmr_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+	int __user *p = arg;
+	int val;
+
+	switch (cmd) {
+	case SNDCTL_TMR_SOURCE:
+		return __put_user(TMR_INTERNAL, p);
+
+	case SNDCTL_TMR_START:
+		tmr_reset();
+		tmr_running = 1;
+		return 0;
+
+	case SNDCTL_TMR_STOP:
+		tmr_running = 0;
+		return 0;
+
+	case SNDCTL_TMR_CONTINUE:
+		tmr_running = 1;
+		return 0;
+
+	case SNDCTL_TMR_TIMEBASE:
+		if (__get_user(val, p))
+			return -EFAULT;
+		if (val) {
+			if (val < 1)
+				val = 1;
+			if (val > 1000)
+				val = 1000;
+			curr_timebase = val;
+		}
+		return __put_user(curr_timebase, p);
+
+	case SNDCTL_TMR_TEMPO:
+		if (__get_user(val, p))
+			return -EFAULT;
+		if (val) {
+			if (val < 8)
+				val = 8;
+			if (val > 250)
+				val = 250;
+			tmr_offs = tmr_ctr;
+			ticks_offs += tmr2ticks(tmr_ctr);
+			tmr_ctr = 0;
+			curr_tempo = val;
+			reprogram_timer();
+		}
+		return __put_user(curr_tempo, p);
+
+	case SNDCTL_SEQ_CTRLRATE:
+		if (__get_user(val, p))
+			return -EFAULT;
+		if (val != 0)	/* Can't change */
+			return -EINVAL;
+		val = ((curr_tempo * curr_timebase) + 30) / 60;
+		return __put_user(val, p);
+		
+	case SNDCTL_SEQ_GETTIME:
+		return __put_user(curr_ticks, p);
+		
+	case SNDCTL_TMR_METRONOME:
+		/* NOP */
+		break;
+		
+	default:;
+	}
+	return -EINVAL;
+}
+
+static void
+def_tmr_arm(int dev, long time)
+{
+	if (time < 0)
+		time = curr_ticks + 1;
+	else if (time <= curr_ticks)	/* It's the time */
+		return;
+
+	next_event_time = prev_event_time = time;
+
+	return;
+}
+
+struct sound_timer_operations default_sound_timer =
+{
+	.owner		= THIS_MODULE,
+	.info		= {"System clock", 0},
+	.priority	= 0,	/* Priority */
+	.devlink	= 0,	/* Local device link */
+	.open		= def_tmr_open,
+	.close		= def_tmr_close,
+	.event		= def_tmr_event,
+	.get_time	= def_tmr_get_time,
+	.ioctl		= def_tmr_ioctl,
+	.arm_timer	= def_tmr_arm
+};
diff --git a/linux/sound/oss/trix.c b/linux/sound/oss/trix.c
new file mode 100644
index 0000000..e04169e
--- /dev/null
+++ b/linux/sound/oss/trix.c
@@ -0,0 +1,525 @@
+/*
+ * sound/oss/trix.c
+ *
+ * Low level driver for the MediaTrix AudioTrix Pro
+ * (MT-0002-PC Control Chip)
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Changes
+ *	Alan Cox		Modularisation, cleanup.
+ *	Christoph Hellwig	Adapted to module_init/module_exit
+ *	Arnaldo C. de Melo	Got rid of attach_uart401
+ */
+ 
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include "sound_config.h"
+#include "sb.h"
+#include "sound_firmware.h"
+
+#include "ad1848.h"
+#include "mpu401.h"
+
+#include "trix_boot.h"
+
+static int mpu;
+
+static int joystick;
+
+static unsigned char trix_read(int addr)
+{
+	outb(((unsigned char) addr), 0x390);	/* MT-0002-PC ASIC address */
+	return inb(0x391);	/* MT-0002-PC ASIC data */
+}
+
+static void trix_write(int addr, int data)
+{
+	outb(((unsigned char) addr), 0x390);	/* MT-0002-PC ASIC address */
+	outb(((unsigned char) data), 0x391);	/* MT-0002-PC ASIC data */
+}
+
+static void download_boot(int base)
+{
+	int i = 0, n = trix_boot_len;
+
+	if (trix_boot_len == 0)
+		return;
+
+	trix_write(0xf8, 0x00);	/* ??????? */
+	outb((0x01), base + 6);	/* Clear the internal data pointer */
+	outb((0x00), base + 6);	/* Restart */
+
+	/*
+	   *  Write the boot code to the RAM upload/download register.
+	   *  Each write increments the internal data pointer.
+	 */
+	outb((0x01), base + 6);	/* Clear the internal data pointer */
+	outb((0x1A), 0x390);	/* Select RAM download/upload port */
+
+	for (i = 0; i < n; i++)
+		outb((trix_boot[i]), 0x391);
+	for (i = n; i < 10016; i++)	/* Clear up to first 16 bytes of data RAM */
+		outb((0x00), 0x391);
+	outb((0x00), base + 6);	/* Reset */
+	outb((0x50), 0x390);	/* ?????? */
+
+}
+
+static int trix_set_wss_port(struct address_info *hw_config)
+{
+	unsigned char   addr_bits;
+
+	if (trix_read(0x15) != 0x71)	/* No ASIC signature */
+	{
+		MDB(printk(KERN_ERR "No AudioTrix ASIC signature found\n"));
+		return 0;
+	}
+
+	/*
+	 * Reset some registers.
+	 */
+
+	trix_write(0x13, 0);
+	trix_write(0x14, 0);
+
+	/*
+	 * Configure the ASIC to place the codec to the proper I/O location
+	 */
+
+	switch (hw_config->io_base)
+	{
+		case 0x530:
+			addr_bits = 0;
+			break;
+		case 0x604:
+			addr_bits = 1;
+			break;
+		case 0xE80:
+			addr_bits = 2;
+			break;
+		case 0xF40:
+			addr_bits = 3;
+			break;
+		default:
+			return 0;
+	}
+
+	trix_write(0x19, (trix_read(0x19) & 0x03) | addr_bits);
+	return 1;
+}
+
+/*
+ *    Probe and attach routines for the Windows Sound System mode of
+ *      AudioTrix Pro
+ */
+
+static int __init init_trix_wss(struct address_info *hw_config)
+{
+	static unsigned char dma_bits[4] = {
+		1, 2, 0, 3
+	};
+	struct resource *ports;
+	int config_port = hw_config->io_base + 0;
+	int dma1 = hw_config->dma, dma2 = hw_config->dma2;
+	int old_num_mixers = num_mixers;
+	u8 config, bits;
+	int ret;
+ 
+	switch(hw_config->irq) {
+	case 7:
+		bits = 8;
+		break;
+	case 9:
+		bits = 0x10;
+		break;
+	case 10:
+		bits = 0x18;
+		break;
+	case 11:
+		bits = 0x20;
+		break;
+	default:
+		printk(KERN_ERR "AudioTrix: Bad WSS IRQ %d\n", hw_config->irq);
+		return 0;
+	}
+
+	switch (dma1) {
+	case 0:
+	case 1:
+	case 3:
+		break;
+	default:
+		printk(KERN_ERR "AudioTrix: Bad WSS DMA %d\n", dma1);
+		return 0;
+	}
+
+	switch (dma2) {
+	case -1:
+	case 0:
+	case 1:
+	case 3:
+		break;
+	default:
+		printk(KERN_ERR "AudioTrix: Bad capture DMA %d\n", dma2);
+		return 0;
+	}
+
+	/*
+	 * Check if the IO port returns valid signature. The original MS Sound
+	 * system returns 0x04 while some cards (AudioTrix Pro for example)
+	 * return 0x00.
+	 */
+	ports = request_region(hw_config->io_base + 4, 4, "ad1848");
+	if (!ports) {
+		printk(KERN_ERR "AudioTrix: MSS I/O port conflict (%x)\n", hw_config->io_base);
+		return 0;
+	}
+
+	if (!request_region(hw_config->io_base, 4, "MSS config")) {
+		printk(KERN_ERR "AudioTrix: MSS I/O port conflict (%x)\n", hw_config->io_base);
+		release_region(hw_config->io_base + 4, 4);
+		return 0;
+	}
+
+	if (!trix_set_wss_port(hw_config))
+		goto fail;
+
+	config = inb(hw_config->io_base + 3);
+
+	if ((config & 0x3f) != 0x00)
+	{
+		MDB(printk(KERN_ERR "No MSS signature detected on port 0x%x\n", hw_config->io_base));
+		goto fail;
+	}
+
+	/*
+	 * Check that DMA0 is not in use with a 8 bit board.
+	 */
+
+	if (dma1 == 0 && config & 0x80)
+	{
+		printk(KERN_ERR "AudioTrix: Can't use DMA0 with a 8 bit card slot\n");
+		goto fail;
+	}
+	if (hw_config->irq > 9 && config & 0x80)
+	{
+		printk(KERN_ERR "AudioTrix: Can't use IRQ%d with a 8 bit card slot\n", hw_config->irq);
+		goto fail;
+	}
+
+	ret = ad1848_detect(ports, NULL, hw_config->osp);
+	if (!ret)
+		goto fail;
+
+	if (joystick==1)
+		trix_write(0x15, 0x80);
+
+	/*
+	 * Set the IRQ and DMA addresses.
+	 */
+
+	outb((bits | 0x40), config_port);
+
+	if (dma2 == -1 || dma2 == dma1)
+	{
+		  bits |= dma_bits[dma1];
+		  dma2 = dma1;
+	}
+	else
+	{
+		unsigned char tmp;
+
+		tmp = trix_read(0x13) & ~30;
+		trix_write(0x13, tmp | 0x80 | (dma1 << 4));
+
+		tmp = trix_read(0x14) & ~30;
+		trix_write(0x14, tmp | 0x80 | (dma2 << 4));
+	}
+
+	outb((bits), config_port);	/* Write IRQ+DMA setup */
+
+	hw_config->slots[0] = ad1848_init("AudioTrix Pro", ports,
+					  hw_config->irq,
+					  dma1,
+					  dma2,
+					  0,
+					  hw_config->osp,
+					  THIS_MODULE);
+
+	if (num_mixers > old_num_mixers)	/* Mixer got installed */
+	{
+		AD1848_REROUTE(SOUND_MIXER_LINE1, SOUND_MIXER_LINE);	/* Line in */
+		AD1848_REROUTE(SOUND_MIXER_LINE2, SOUND_MIXER_CD);
+		AD1848_REROUTE(SOUND_MIXER_LINE3, SOUND_MIXER_SYNTH);		/* OPL4 */
+		AD1848_REROUTE(SOUND_MIXER_SPEAKER, SOUND_MIXER_ALTPCM);	/* SB */
+	}
+	return 1;
+
+fail:
+	release_region(hw_config->io_base, 4);
+	release_region(hw_config->io_base + 4, 4);
+	return 0;
+}
+
+static int __init probe_trix_sb(struct address_info *hw_config)
+{
+
+	int tmp;
+	unsigned char conf;
+	extern int sb_be_quiet;
+	int old_quiet;
+	static signed char irq_translate[] = {
+		-1, -1, -1, 0, 1, 2, -1, 3
+	};
+
+	if (trix_boot_len == 0)
+		return 0;	/* No boot code -> no fun */
+
+	if ((hw_config->io_base & 0xffffff8f) != 0x200)
+		return 0;
+
+	tmp = hw_config->irq;
+	if (tmp > 7)
+		return 0;
+	if (irq_translate[tmp] == -1)
+		return 0;
+
+	tmp = hw_config->dma;
+	if (tmp != 1 && tmp != 3)
+		return 0;
+
+	if (!request_region(hw_config->io_base, 16, "soundblaster")) {
+		printk(KERN_ERR "AudioTrix: SB I/O port conflict (%x)\n", hw_config->io_base);
+		return 0;
+	}
+
+	conf = 0x84;		/* DMA and IRQ enable */
+	conf |= hw_config->io_base & 0x70;	/* I/O address bits */
+	conf |= irq_translate[hw_config->irq];
+	if (hw_config->dma == 3)
+		conf |= 0x08;
+	trix_write(0x1b, conf);
+
+	download_boot(hw_config->io_base);
+
+	hw_config->name = "AudioTrix SB";
+	if (!sb_dsp_detect(hw_config, 0, 0, NULL)) {
+		release_region(hw_config->io_base, 16);
+		return 0;
+	}
+
+	hw_config->driver_use_1 = SB_NO_MIDI | SB_NO_MIXER | SB_NO_RECORDING;
+
+	/* Prevent false alarms */
+	old_quiet = sb_be_quiet;
+	sb_be_quiet = 1;
+
+	sb_dsp_init(hw_config, THIS_MODULE);
+
+	sb_be_quiet = old_quiet;
+	return 1;
+}
+
+static int __init probe_trix_mpu(struct address_info *hw_config)
+{
+	unsigned char conf;
+	static int irq_bits[] = {
+		-1, -1, -1, 1, 2, 3, -1, 4, -1, 5
+	};
+
+	if (hw_config->irq > 9)
+	{
+		printk(KERN_ERR "AudioTrix: Bad MPU IRQ %d\n", hw_config->irq);
+		return 0;
+	}
+	if (irq_bits[hw_config->irq] == -1)
+	{
+		printk(KERN_ERR "AudioTrix: Bad MPU IRQ %d\n", hw_config->irq);
+		return 0;
+	}
+	switch (hw_config->io_base)
+	{
+		case 0x330:
+			conf = 0x00;
+			break;
+		case 0x370:
+			conf = 0x04;
+			break;
+		case 0x3b0:
+			conf = 0x08;
+			break;
+		case 0x3f0:
+			conf = 0x0c;
+			break;
+		default:
+			return 0;	/* Invalid port */
+	}
+
+	conf |= irq_bits[hw_config->irq] << 4;
+	trix_write(0x19, (trix_read(0x19) & 0x83) | conf);
+	hw_config->name = "AudioTrix Pro";
+	return probe_uart401(hw_config, THIS_MODULE);
+}
+
+static void __exit unload_trix_wss(struct address_info *hw_config)
+{
+	int dma2 = hw_config->dma2;
+
+	if (dma2 == -1)
+		dma2 = hw_config->dma;
+
+	release_region(0x390, 2);
+	release_region(hw_config->io_base, 4);
+
+	ad1848_unload(hw_config->io_base + 4,
+		      hw_config->irq,
+		      hw_config->dma,
+		      dma2,
+		      0);
+	sound_unload_audiodev(hw_config->slots[0]);
+}
+
+static inline void __exit unload_trix_mpu(struct address_info *hw_config)
+{
+	unload_uart401(hw_config);
+}
+
+static inline void __exit unload_trix_sb(struct address_info *hw_config)
+{
+	sb_dsp_unload(hw_config, mpu);
+}
+
+static struct address_info cfg;
+static struct address_info cfg2;
+static struct address_info cfg_mpu;
+
+static int sb;
+static int fw_load;
+
+static int __initdata io	= -1;
+static int __initdata irq	= -1;
+static int __initdata dma	= -1;
+static int __initdata dma2	= -1;	/* Set this for modules that need it */
+static int __initdata sb_io	= -1;
+static int __initdata sb_dma	= -1;
+static int __initdata sb_irq	= -1;
+static int __initdata mpu_io	= -1;
+static int __initdata mpu_irq	= -1;
+
+module_param(io, int, 0);
+module_param(irq, int, 0);
+module_param(dma, int, 0);
+module_param(dma2, int, 0);
+module_param(sb_io, int, 0);
+module_param(sb_dma, int, 0);
+module_param(sb_irq, int, 0);
+module_param(mpu_io, int, 0);
+module_param(mpu_irq, int, 0);
+module_param(joystick, bool, 0);
+
+static int __init init_trix(void)
+{
+	printk(KERN_INFO "MediaTrix audio driver Copyright (C) by Hannu Savolainen 1993-1996\n");
+
+	cfg.io_base = io;
+	cfg.irq = irq;
+	cfg.dma = dma;
+	cfg.dma2 = dma2;
+
+	cfg2.io_base = sb_io;
+	cfg2.irq = sb_irq;
+	cfg2.dma = sb_dma;
+
+	cfg_mpu.io_base = mpu_io;
+	cfg_mpu.irq = mpu_irq;
+
+	if (cfg.io_base == -1 || cfg.dma == -1 || cfg.irq == -1) {
+		printk(KERN_INFO "I/O, IRQ, DMA and type are mandatory\n");
+		return -EINVAL;
+	}
+
+	if (cfg2.io_base != -1 && (cfg2.irq == -1 || cfg2.dma == -1)) {
+		printk(KERN_INFO "CONFIG_SB_IRQ and CONFIG_SB_DMA must be specified if SB_IO is set.\n");
+		return -EINVAL;
+	}
+	if (cfg_mpu.io_base != -1 && cfg_mpu.irq == -1) {
+		printk(KERN_INFO "CONFIG_MPU_IRQ must be specified if MPU_IO is set.\n");
+		return -EINVAL;
+	}
+	if (!trix_boot)
+	{
+		fw_load = 1;
+		trix_boot_len = mod_firmware_load("/etc/sound/trxpro.bin",
+						    (char **) &trix_boot);
+	}
+
+	if (!request_region(0x390, 2, "AudioTrix")) {
+		printk(KERN_ERR "AudioTrix: Config port I/O conflict\n");
+		return -ENODEV;
+	}
+
+	if (!init_trix_wss(&cfg)) {
+		release_region(0x390, 2);
+		return -ENODEV;
+	}
+
+	/*
+	 *    We must attach in the right order to get the firmware
+	 *      loaded up in time.
+	 */
+
+	if (cfg2.io_base != -1) {
+		sb = probe_trix_sb(&cfg2);
+	}
+	
+	if (cfg_mpu.io_base != -1)
+		mpu = probe_trix_mpu(&cfg_mpu);
+
+	return 0;
+}
+
+static void __exit cleanup_trix(void)
+{
+	if (fw_load && trix_boot)
+		vfree(trix_boot);
+	if (sb)
+		unload_trix_sb(&cfg2);
+	if (mpu)
+		unload_trix_mpu(&cfg_mpu);
+	unload_trix_wss(&cfg);
+}
+
+module_init(init_trix);
+module_exit(cleanup_trix);
+
+#ifndef MODULE
+static int __init setup_trix (char *str)
+{
+	/* io, irq, dma, dma2, sb_io, sb_irq, sb_dma, mpu_io, mpu_irq */
+	int ints[9];
+	
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+
+	io	= ints[1];
+	irq	= ints[2];
+	dma	= ints[3];
+	dma2	= ints[4];
+	sb_io	= ints[5];
+	sb_irq	= ints[6];
+	sb_dma	= ints[6];
+	mpu_io	= ints[7];
+	mpu_irq	= ints[8];
+
+	return 1;
+}
+
+__setup("trix=", setup_trix);
+#endif
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/tuning.h b/linux/sound/oss/tuning.h
new file mode 100644
index 0000000..a73e3dd
--- /dev/null
+++ b/linux/sound/oss/tuning.h
@@ -0,0 +1,23 @@
+static unsigned short semitone_tuning[24] =
+{
+/*   0 */ 10000, 10595, 11225, 11892, 12599, 13348, 14142, 14983, 
+/*   8 */ 15874, 16818, 17818, 18877, 20000, 21189, 22449, 23784, 
+/*  16 */ 25198, 26697, 28284, 29966, 31748, 33636, 35636, 37755
+};
+
+static unsigned short cent_tuning[100] =
+{
+/*   0 */ 10000, 10006, 10012, 10017, 10023, 10029, 10035, 10041, 
+/*   8 */ 10046, 10052, 10058, 10064, 10070, 10075, 10081, 10087, 
+/*  16 */ 10093, 10099, 10105, 10110, 10116, 10122, 10128, 10134, 
+/*  24 */ 10140, 10145, 10151, 10157, 10163, 10169, 10175, 10181, 
+/*  32 */ 10187, 10192, 10198, 10204, 10210, 10216, 10222, 10228, 
+/*  40 */ 10234, 10240, 10246, 10251, 10257, 10263, 10269, 10275, 
+/*  48 */ 10281, 10287, 10293, 10299, 10305, 10311, 10317, 10323, 
+/*  56 */ 10329, 10335, 10341, 10347, 10353, 10359, 10365, 10371, 
+/*  64 */ 10377, 10383, 10389, 10395, 10401, 10407, 10413, 10419, 
+/*  72 */ 10425, 10431, 10437, 10443, 10449, 10455, 10461, 10467, 
+/*  80 */ 10473, 10479, 10485, 10491, 10497, 10503, 10509, 10515, 
+/*  88 */ 10521, 10528, 10534, 10540, 10546, 10552, 10558, 10564, 
+/*  96 */ 10570, 10576, 10582, 10589
+};
diff --git a/linux/sound/oss/uart401.c b/linux/sound/oss/uart401.c
new file mode 100644
index 0000000..8e514a6
--- /dev/null
+++ b/linux/sound/oss/uart401.c
@@ -0,0 +1,482 @@
+/*
+ * sound/oss/uart401.c
+ *
+ * MPU-401 UART driver (formerly uart401_midi.c)
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Changes:
+ *	Alan Cox		Reformatted, removed sound_mem usage, use normal Linux
+ *				interrupt allocation. Protect against bogus unload
+ *				Fixed to allow IRQ > 15
+ *	Christoph Hellwig	Adapted to module_init/module_exit
+ *	Arnaldo C. de Melo	got rid of check_region
+ *
+ * Status:
+ *		Untested
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+#include "mpu401.h"
+
+typedef struct uart401_devc
+{
+	int             base;
+	int             irq;
+	int            *osp;
+	void            (*midi_input_intr) (int dev, unsigned char data);
+	int             opened, disabled;
+	volatile unsigned char input_byte;
+	int             my_dev;
+	int             share_irq;
+	spinlock_t	lock;
+}
+uart401_devc;
+
+#define	DATAPORT   (devc->base)
+#define	COMDPORT   (devc->base+1)
+#define	STATPORT   (devc->base+1)
+
+static int uart401_status(uart401_devc * devc)
+{
+	return inb(STATPORT);
+}
+
+#define input_avail(devc) (!(uart401_status(devc)&INPUT_AVAIL))
+#define output_ready(devc)	(!(uart401_status(devc)&OUTPUT_READY))
+
+static void uart401_cmd(uart401_devc * devc, unsigned char cmd)
+{
+	outb((cmd), COMDPORT);
+}
+
+static int uart401_read(uart401_devc * devc)
+{
+	return inb(DATAPORT);
+}
+
+static void uart401_write(uart401_devc * devc, unsigned char byte)
+{
+	outb((byte), DATAPORT);
+}
+
+#define	OUTPUT_READY	0x40
+#define	INPUT_AVAIL	0x80
+#define	MPU_ACK		0xFE
+#define	MPU_RESET	0xFF
+#define	UART_MODE_ON	0x3F
+
+static int      reset_uart401(uart401_devc * devc);
+static void     enter_uart_mode(uart401_devc * devc);
+
+static void uart401_input_loop(uart401_devc * devc)
+{
+	int work_limit=30000;
+	
+	while (input_avail(devc) && --work_limit)
+	{
+		unsigned char   c = uart401_read(devc);
+
+		if (c == MPU_ACK)
+			devc->input_byte = c;
+		else if (devc->opened & OPEN_READ && devc->midi_input_intr)
+			devc->midi_input_intr(devc->my_dev, c);
+	}
+	if(work_limit==0)
+		printk(KERN_WARNING "Too much work in interrupt on uart401 (0x%X). UART jabbering ??\n", devc->base);
+}
+
+irqreturn_t uart401intr(int irq, void *dev_id)
+{
+	uart401_devc *devc = dev_id;
+
+	if (devc == NULL)
+	{
+		printk(KERN_ERR "uart401: bad devc\n");
+		return IRQ_NONE;
+	}
+
+	if (input_avail(devc))
+		uart401_input_loop(devc);
+	return IRQ_HANDLED;
+}
+
+static int
+uart401_open(int dev, int mode,
+	     void            (*input) (int dev, unsigned char data),
+	     void            (*output) (int dev)
+)
+{
+	uart401_devc *devc = (uart401_devc *) midi_devs[dev]->devc;
+
+	if (devc->opened)
+		return -EBUSY;
+
+	/* Flush the UART */
+	
+	while (input_avail(devc))
+		uart401_read(devc);
+
+	devc->midi_input_intr = input;
+	devc->opened = mode;
+	enter_uart_mode(devc);
+	devc->disabled = 0;
+
+	return 0;
+}
+
+static void uart401_close(int dev)
+{
+	uart401_devc *devc = (uart401_devc *) midi_devs[dev]->devc;
+
+	reset_uart401(devc);
+	devc->opened = 0;
+}
+
+static int uart401_out(int dev, unsigned char midi_byte)
+{
+	int timeout;
+	unsigned long flags;
+	uart401_devc *devc = (uart401_devc *) midi_devs[dev]->devc;
+
+	if (devc->disabled)
+		return 1;
+	/*
+	 * Test for input since pending input seems to block the output.
+	 */
+
+	spin_lock_irqsave(&devc->lock,flags);	
+	if (input_avail(devc))
+		uart401_input_loop(devc);
+
+	spin_unlock_irqrestore(&devc->lock,flags);
+
+	/*
+	 * Sometimes it takes about 13000 loops before the output becomes ready
+	 * (After reset). Normally it takes just about 10 loops.
+	 */
+
+	for (timeout = 30000; timeout > 0 && !output_ready(devc); timeout--);
+
+	if (!output_ready(devc))
+	{
+		  printk(KERN_WARNING "uart401: Timeout - Device not responding\n");
+		  devc->disabled = 1;
+		  reset_uart401(devc);
+		  enter_uart_mode(devc);
+		  return 1;
+	}
+	uart401_write(devc, midi_byte);
+	return 1;
+}
+
+static inline int uart401_start_read(int dev)
+{
+	return 0;
+}
+
+static inline int uart401_end_read(int dev)
+{
+	return 0;
+}
+
+static inline void uart401_kick(int dev)
+{
+}
+
+static inline int uart401_buffer_status(int dev)
+{
+	return 0;
+}
+
+#define MIDI_SYNTH_NAME	"MPU-401 UART"
+#define MIDI_SYNTH_CAPS	SYNTH_CAP_INPUT
+#include "midi_synth.h"
+
+static const struct midi_operations uart401_operations =
+{
+	.owner		= THIS_MODULE,
+	.info		= {"MPU-401 (UART) MIDI", 0, 0, SNDCARD_MPU401},
+	.converter	= &std_midi_synth,
+	.in_info	= {0},
+	.open		= uart401_open,
+	.close		= uart401_close,
+	.outputc	= uart401_out,
+	.start_read	= uart401_start_read,
+	.end_read	= uart401_end_read,
+	.kick		= uart401_kick,
+	.buffer_status	= uart401_buffer_status,
+};
+
+static void enter_uart_mode(uart401_devc * devc)
+{
+	int ok, timeout;
+	unsigned long flags;
+
+	spin_lock_irqsave(&devc->lock,flags);	
+	for (timeout = 30000; timeout > 0 && !output_ready(devc); timeout--);
+
+	devc->input_byte = 0;
+	uart401_cmd(devc, UART_MODE_ON);
+
+	ok = 0;
+	for (timeout = 50000; timeout > 0 && !ok; timeout--)
+		if (devc->input_byte == MPU_ACK)
+			ok = 1;
+		else if (input_avail(devc))
+			if (uart401_read(devc) == MPU_ACK)
+				ok = 1;
+
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static int reset_uart401(uart401_devc * devc)
+{
+	int ok, timeout, n;
+
+	/*
+	 * Send the RESET command. Try again if no success at the first time.
+	 */
+
+	ok = 0;
+
+	for (n = 0; n < 2 && !ok; n++)
+	{
+		for (timeout = 30000; timeout > 0 && !output_ready(devc); timeout--);
+		devc->input_byte = 0;
+		uart401_cmd(devc, MPU_RESET);
+
+		/*
+		 * Wait at least 25 msec. This method is not accurate so let's make the
+		 * loop bit longer. Cannot sleep since this is called during boot.
+		 */
+
+		for (timeout = 50000; timeout > 0 && !ok; timeout--)
+		{
+			if (devc->input_byte == MPU_ACK)	/* Interrupt */
+				ok = 1;
+			else if (input_avail(devc))
+			{
+				if (uart401_read(devc) == MPU_ACK)
+					ok = 1;
+			}
+		}
+	}
+
+
+	if (ok)
+	{
+		DEB(printk("Reset UART401 OK\n"));
+	}
+	else
+		DDB(printk("Reset UART401 failed - No hardware detected.\n"));
+
+	if (ok)
+		uart401_input_loop(devc);	/*
+						 * Flush input before enabling interrupts
+						 */
+
+	return ok;
+}
+
+int probe_uart401(struct address_info *hw_config, struct module *owner)
+{
+	uart401_devc *devc;
+	char *name = "MPU-401 (UART) MIDI";
+	int ok = 0;
+	unsigned long flags;
+
+	DDB(printk("Entered probe_uart401()\n"));
+
+	/* Default to "not found" */
+	hw_config->slots[4] = -1;
+
+	if (!request_region(hw_config->io_base, 4, "MPU-401 UART")) {
+		printk(KERN_INFO "uart401: could not request_region(%d, 4)\n", hw_config->io_base);
+		return 0;
+	}
+
+	devc = kmalloc(sizeof(uart401_devc), GFP_KERNEL);
+	if (!devc) {
+		printk(KERN_WARNING "uart401: Can't allocate memory\n");
+		goto cleanup_region;
+	}
+
+	devc->base = hw_config->io_base;
+	devc->irq = hw_config->irq;
+	devc->osp = hw_config->osp;
+	devc->midi_input_intr = NULL;
+	devc->opened = 0;
+	devc->input_byte = 0;
+	devc->my_dev = 0;
+	devc->share_irq = 0;
+	spin_lock_init(&devc->lock);
+
+	spin_lock_irqsave(&devc->lock,flags);	
+	ok = reset_uart401(devc);
+	spin_unlock_irqrestore(&devc->lock,flags);
+
+	if (!ok)
+		goto cleanup_devc;
+
+	if (hw_config->name)
+		name = hw_config->name;
+
+	if (devc->irq < 0) {
+		devc->share_irq = 1;
+		devc->irq *= -1;
+	} else
+		devc->share_irq = 0;
+
+	if (!devc->share_irq)
+		if (request_irq(devc->irq, uart401intr, 0, "MPU-401 UART", devc) < 0) {
+			printk(KERN_WARNING "uart401: Failed to allocate IRQ%d\n", devc->irq);
+			devc->share_irq = 1;
+		}
+	devc->my_dev = sound_alloc_mididev();
+	enter_uart_mode(devc);
+
+	if (devc->my_dev == -1) {
+		printk(KERN_INFO "uart401: Too many midi devices detected\n");
+		goto cleanup_irq;
+	}
+	conf_printf(name, hw_config);
+	midi_devs[devc->my_dev] = kmalloc(sizeof(struct midi_operations), GFP_KERNEL);
+	if (!midi_devs[devc->my_dev]) {
+		printk(KERN_ERR "uart401: Failed to allocate memory\n");
+		goto cleanup_unload_mididev;
+	}
+	memcpy(midi_devs[devc->my_dev], &uart401_operations, sizeof(struct midi_operations));
+
+	if (owner)
+		midi_devs[devc->my_dev]->owner = owner;
+	
+	midi_devs[devc->my_dev]->devc = devc;
+	midi_devs[devc->my_dev]->converter = kmalloc(sizeof(struct synth_operations), GFP_KERNEL);
+	if (!midi_devs[devc->my_dev]->converter) {
+		printk(KERN_WARNING "uart401: Failed to allocate memory\n");
+		goto cleanup_midi_devs;
+	}
+	memcpy(midi_devs[devc->my_dev]->converter, &std_midi_synth, sizeof(struct synth_operations));
+	strcpy(midi_devs[devc->my_dev]->info.name, name);
+	midi_devs[devc->my_dev]->converter->id = "UART401";
+	midi_devs[devc->my_dev]->converter->midi_dev = devc->my_dev;
+
+	if (owner)
+		midi_devs[devc->my_dev]->converter->owner = owner;
+
+	hw_config->slots[4] = devc->my_dev;
+	sequencer_init();
+	devc->opened = 0;
+	return 1;
+cleanup_midi_devs:
+	kfree(midi_devs[devc->my_dev]);
+cleanup_unload_mididev:
+	sound_unload_mididev(devc->my_dev);
+cleanup_irq:
+	if (!devc->share_irq)
+		free_irq(devc->irq, devc);
+cleanup_devc:
+	kfree(devc);
+cleanup_region:
+	release_region(hw_config->io_base, 4);
+	return 0;
+}
+
+void unload_uart401(struct address_info *hw_config)
+{
+	uart401_devc *devc;
+	int n=hw_config->slots[4];
+	
+	/* Not set up */
+	if(n==-1 || midi_devs[n]==NULL)
+		return;
+		
+	/* Not allocated (erm ??) */
+	
+	devc = midi_devs[hw_config->slots[4]]->devc;
+	if (devc == NULL)
+		return;
+
+	reset_uart401(devc);
+	release_region(hw_config->io_base, 4);
+
+	if (!devc->share_irq)
+		free_irq(devc->irq, devc);
+	if (devc)
+	{
+		kfree(midi_devs[devc->my_dev]->converter);
+		kfree(midi_devs[devc->my_dev]);
+		kfree(devc);
+		devc = NULL;
+	}
+	/* This kills midi_devs[x] */
+	sound_unload_mididev(hw_config->slots[4]);
+}
+
+EXPORT_SYMBOL(probe_uart401);
+EXPORT_SYMBOL(unload_uart401);
+EXPORT_SYMBOL(uart401intr);
+
+static struct address_info cfg_mpu;
+
+static int io = -1;
+static int irq = -1;
+
+module_param(io, int, 0444);
+module_param(irq, int, 0444);
+
+
+static int __init init_uart401(void)
+{
+	cfg_mpu.irq = irq;
+	cfg_mpu.io_base = io;
+
+	/* Can be loaded either for module use or to provide functions
+	   to others */
+	if (cfg_mpu.io_base != -1 && cfg_mpu.irq != -1) {
+		printk(KERN_INFO "MPU-401 UART driver Copyright (C) Hannu Savolainen 1993-1997");
+		if (!probe_uart401(&cfg_mpu, THIS_MODULE))
+			return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void __exit cleanup_uart401(void)
+{
+	if (cfg_mpu.io_base != -1 && cfg_mpu.irq != -1)
+		unload_uart401(&cfg_mpu);
+}
+
+module_init(init_uart401);
+module_exit(cleanup_uart401);
+
+#ifndef MODULE
+static int __init setup_uart401(char *str)
+{
+	/* io, irq */
+	int ints[3];
+	
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+
+	io = ints[1];
+	irq = ints[2];
+	
+	return 1;
+}
+
+__setup("uart401=", setup_uart401);
+#endif
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/uart6850.c b/linux/sound/oss/uart6850.c
new file mode 100644
index 0000000..f3f914a
--- /dev/null
+++ b/linux/sound/oss/uart6850.c
@@ -0,0 +1,361 @@
+/*
+ * sound/oss/uart6850.c
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ * Extended by Alan Cox for Red Hat Software. Now a loadable MIDI driver.
+ * 28/4/97 - (C) Copyright Alan Cox. Released under the GPL version 2.
+ *
+ * Alan Cox:		Updated for new modular code. Removed snd_* irq handling. Now
+ *			uses native linux resources
+ * Christoph Hellwig:	Adapted to module_init/module_exit
+ * Jeff Garzik:		Made it work again, in theory
+ *			FIXME: If the request_irq() succeeds, the probe succeeds. Ug.
+ *
+ *	Status: Testing required (no shit -jgarzik)
+ *
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+/* Mon Nov 22 22:38:35 MET 1993 marco@driq.home.usn.nl:
+ *      added 6850 support, used with COVOX SoundMaster II and custom cards.
+ */
+
+#include "sound_config.h"
+
+static int uart6850_base = 0x330;
+
+static int *uart6850_osp;
+
+#define	DATAPORT   (uart6850_base)
+#define	COMDPORT   (uart6850_base+1)
+#define	STATPORT   (uart6850_base+1)
+
+static int uart6850_status(void)
+{
+	return inb(STATPORT);
+}
+
+#define input_avail()		(uart6850_status()&INPUT_AVAIL)
+#define output_ready()		(uart6850_status()&OUTPUT_READY)
+
+static void uart6850_cmd(unsigned char cmd)
+{
+	outb(cmd, COMDPORT);
+}
+
+static int uart6850_read(void)
+{
+	return inb(DATAPORT);
+}
+
+static void uart6850_write(unsigned char byte)
+{
+	outb(byte, DATAPORT);
+}
+
+#define	OUTPUT_READY	0x02	/* Mask for data ready Bit */
+#define	INPUT_AVAIL	0x01	/* Mask for Data Send Ready Bit */
+
+#define	UART_RESET	0x95
+#define	UART_MODE_ON	0x03
+
+static int uart6850_opened;
+static int uart6850_irq;
+static int uart6850_detected;
+static int my_dev;
+static DEFINE_SPINLOCK(lock);
+
+static void (*midi_input_intr) (int dev, unsigned char data);
+static void poll_uart6850(unsigned long dummy);
+
+
+static DEFINE_TIMER(uart6850_timer, poll_uart6850, 0, 0);
+
+static void uart6850_input_loop(void)
+{
+	int count = 10;
+
+	while (count)
+	{
+		/*
+		 * Not timed out
+		 */
+		if (input_avail())
+		{
+			unsigned char c = uart6850_read();
+			count = 100;
+			if (uart6850_opened & OPEN_READ)
+				midi_input_intr(my_dev, c);
+		}
+		else
+		{
+			while (!input_avail() && count)
+				count--;
+		}
+	}
+}
+
+static irqreturn_t m6850intr(int irq, void *dev_id)
+{
+	if (input_avail())
+		uart6850_input_loop();
+	return IRQ_HANDLED;
+}
+
+/*
+ *	It looks like there is no input interrupts in the UART mode. Let's try
+ *	polling.
+ */
+
+static void poll_uart6850(unsigned long dummy)
+{
+	unsigned long flags;
+
+	if (!(uart6850_opened & OPEN_READ))
+		return;		/* Device has been closed */
+
+	spin_lock_irqsave(&lock,flags);
+	if (input_avail())
+		uart6850_input_loop();
+
+	uart6850_timer.expires = 1 + jiffies;
+	add_timer(&uart6850_timer);
+	
+	/*
+	 *	Come back later
+	 */
+
+	spin_unlock_irqrestore(&lock,flags);
+}
+
+static int uart6850_open(int dev, int mode,
+	      void            (*input) (int dev, unsigned char data),
+	      void            (*output) (int dev)
+)
+{
+	if (uart6850_opened)
+	{
+/*		  printk("Midi6850: Midi busy\n");*/
+		  return -EBUSY;
+	};
+
+	uart6850_cmd(UART_RESET);
+	uart6850_input_loop();
+	midi_input_intr = input;
+	uart6850_opened = mode;
+	poll_uart6850(0);	/*
+				 * Enable input polling
+				 */
+
+	return 0;
+}
+
+static void uart6850_close(int dev)
+{
+	uart6850_cmd(UART_MODE_ON);
+	del_timer(&uart6850_timer);
+	uart6850_opened = 0;
+}
+
+static int uart6850_out(int dev, unsigned char midi_byte)
+{
+	int timeout;
+	unsigned long flags;
+
+	/*
+	 * Test for input since pending input seems to block the output.
+	 */
+
+	spin_lock_irqsave(&lock,flags);
+
+	if (input_avail())
+		uart6850_input_loop();
+
+	spin_unlock_irqrestore(&lock,flags);
+
+	/*
+	 * Sometimes it takes about 13000 loops before the output becomes ready
+	 * (After reset). Normally it takes just about 10 loops.
+	 */
+
+	for (timeout = 30000; timeout > 0 && !output_ready(); timeout--);	/*
+										 * Wait
+										 */
+	if (!output_ready())
+	{
+		printk(KERN_WARNING "Midi6850: Timeout\n");
+		return 0;
+	}
+	uart6850_write(midi_byte);
+	return 1;
+}
+
+static inline int uart6850_command(int dev, unsigned char *midi_byte)
+{
+	return 1;
+}
+
+static inline int uart6850_start_read(int dev)
+{
+	return 0;
+}
+
+static inline int uart6850_end_read(int dev)
+{
+	return 0;
+}
+
+static inline void uart6850_kick(int dev)
+{
+}
+
+static inline int uart6850_buffer_status(int dev)
+{
+	return 0;		/*
+				 * No data in buffers
+				 */
+}
+
+#define MIDI_SYNTH_NAME	"6850 UART Midi"
+#define MIDI_SYNTH_CAPS	SYNTH_CAP_INPUT
+#include "midi_synth.h"
+
+static struct midi_operations uart6850_operations =
+{
+	.owner		= THIS_MODULE,
+	.info		= {"6850 UART", 0, 0, SNDCARD_UART6850},
+	.converter	= &std_midi_synth,
+	.in_info	= {0},
+	.open		= uart6850_open,
+	.close		= uart6850_close,
+	.outputc	= uart6850_out,
+	.start_read	= uart6850_start_read,
+	.end_read	= uart6850_end_read,
+	.kick		= uart6850_kick,
+	.command	= uart6850_command,
+	.buffer_status	= uart6850_buffer_status
+};
+
+
+static void __init attach_uart6850(struct address_info *hw_config)
+{
+	int ok, timeout;
+	unsigned long   flags;
+
+	if (!uart6850_detected)
+		return;
+
+	if ((my_dev = sound_alloc_mididev()) == -1)
+	{
+		printk(KERN_INFO "uart6850: Too many midi devices detected\n");
+		return;
+	}
+	uart6850_base = hw_config->io_base;
+	uart6850_osp = hw_config->osp;
+	uart6850_irq = hw_config->irq;
+
+	spin_lock_irqsave(&lock,flags);
+
+	for (timeout = 30000; timeout > 0 && !output_ready(); timeout--);	/*
+										 * Wait
+										 */
+	uart6850_cmd(UART_MODE_ON);
+	ok = 1;
+	spin_unlock_irqrestore(&lock,flags);
+
+	conf_printf("6850 Midi Interface", hw_config);
+
+	std_midi_synth.midi_dev = my_dev;
+	hw_config->slots[4] = my_dev;
+	midi_devs[my_dev] = &uart6850_operations;
+	sequencer_init();
+}
+
+static inline int reset_uart6850(void)
+{
+	uart6850_read();
+	return 1;		/*
+				 * OK
+				 */
+}
+
+static int __init probe_uart6850(struct address_info *hw_config)
+{
+	int ok;
+
+	uart6850_osp = hw_config->osp;
+	uart6850_base = hw_config->io_base;
+	uart6850_irq = hw_config->irq;
+
+	if (request_irq(uart6850_irq, m6850intr, 0, "MIDI6850", NULL) < 0)
+		return 0;
+
+	ok = reset_uart6850();
+	uart6850_detected = ok;
+	return ok;
+}
+
+static void __exit unload_uart6850(struct address_info *hw_config)
+{
+	free_irq(hw_config->irq, NULL);
+	sound_unload_mididev(hw_config->slots[4]);
+}
+
+static struct address_info cfg_mpu;
+
+static int __initdata io = -1;
+static int __initdata irq = -1;
+
+module_param(io, int, 0);
+module_param(irq, int, 0);
+
+static int __init init_uart6850(void)
+{
+	cfg_mpu.io_base = io;
+	cfg_mpu.irq = irq;
+
+	if (cfg_mpu.io_base == -1 || cfg_mpu.irq == -1) {
+		printk(KERN_INFO "uart6850: irq and io must be set.\n");
+		return -EINVAL;
+	}
+
+	if (probe_uart6850(&cfg_mpu))
+		return -ENODEV;
+	attach_uart6850(&cfg_mpu);
+
+	return 0;
+}
+
+static void __exit cleanup_uart6850(void)
+{
+	unload_uart6850(&cfg_mpu);
+}
+
+module_init(init_uart6850);
+module_exit(cleanup_uart6850);
+
+#ifndef MODULE
+static int __init setup_uart6850(char *str)
+{
+	/* io, irq */
+	int ints[3];
+	
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+	
+	io = ints[1];
+	irq = ints[2];
+
+	return 1;
+}
+__setup("uart6850=", setup_uart6850);
+#endif
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/ulaw.h b/linux/sound/oss/ulaw.h
new file mode 100644
index 0000000..0ff8c0a
--- /dev/null
+++ b/linux/sound/oss/ulaw.h
@@ -0,0 +1,69 @@
+static unsigned char ulaw_dsp[] = {
+     3,    7,   11,   15,   19,   23,   27,   31,
+    35,   39,   43,   47,   51,   55,   59,   63,
+    66,   68,   70,   72,   74,   76,   78,   80,
+    82,   84,   86,   88,   90,   92,   94,   96,
+    98,   99,  100,  101,  102,  103,  104,  105,
+   106,  107,  108,  109,  110,  111,  112,  113,
+   113,  114,  114,  115,  115,  116,  116,  117,
+   117,  118,  118,  119,  119,  120,  120,  121,
+   121,  121,  122,  122,  122,  122,  123,  123,
+   123,  123,  124,  124,  124,  124,  125,  125,
+   125,  125,  125,  125,  126,  126,  126,  126,
+   126,  126,  126,  126,  127,  127,  127,  127,
+   127,  127,  127,  127,  127,  127,  127,  127,
+   128,  128,  128,  128,  128,  128,  128,  128,
+   128,  128,  128,  128,  128,  128,  128,  128,
+   128,  128,  128,  128,  128,  128,  128,  128,
+   253,  249,  245,  241,  237,  233,  229,  225,
+   221,  217,  213,  209,  205,  201,  197,  193,
+   190,  188,  186,  184,  182,  180,  178,  176,
+   174,  172,  170,  168,  166,  164,  162,  160,
+   158,  157,  156,  155,  154,  153,  152,  151,
+   150,  149,  148,  147,  146,  145,  144,  143,
+   143,  142,  142,  141,  141,  140,  140,  139,
+   139,  138,  138,  137,  137,  136,  136,  135,
+   135,  135,  134,  134,  134,  134,  133,  133,
+   133,  133,  132,  132,  132,  132,  131,  131,
+   131,  131,  131,  131,  130,  130,  130,  130,
+   130,  130,  130,  130,  129,  129,  129,  129,
+   129,  129,  129,  129,  129,  129,  129,  129,
+   128,  128,  128,  128,  128,  128,  128,  128,
+   128,  128,  128,  128,  128,  128,  128,  128,
+   128,  128,  128,  128,  128,  128,  128,  128,
+};
+
+static unsigned char dsp_ulaw[] = {
+     0,    0,    0,    0,    0,    1,    1,    1,
+     1,    2,    2,    2,    2,    3,    3,    3,
+     3,    4,    4,    4,    4,    5,    5,    5,
+     5,    6,    6,    6,    6,    7,    7,    7,
+     7,    8,    8,    8,    8,    9,    9,    9,
+     9,   10,   10,   10,   10,   11,   11,   11,
+    11,   12,   12,   12,   12,   13,   13,   13,
+    13,   14,   14,   14,   14,   15,   15,   15,
+    15,   16,   16,   17,   17,   18,   18,   19,
+    19,   20,   20,   21,   21,   22,   22,   23,
+    23,   24,   24,   25,   25,   26,   26,   27,
+    27,   28,   28,   29,   29,   30,   30,   31,
+    31,   32,   33,   34,   35,   36,   37,   38,
+    39,   40,   41,   42,   43,   44,   45,   46,
+    47,   49,   51,   53,   55,   57,   59,   61,
+    63,   66,   70,   74,   78,   84,   92,  104,
+   254,  231,  219,  211,  205,  201,  197,  193,
+   190,  188,  186,  184,  182,  180,  178,  176,
+   175,  174,  173,  172,  171,  170,  169,  168,
+   167,  166,  165,  164,  163,  162,  161,  160,
+   159,  159,  158,  158,  157,  157,  156,  156,
+   155,  155,  154,  154,  153,  153,  152,  152,
+   151,  151,  150,  150,  149,  149,  148,  148,
+   147,  147,  146,  146,  145,  145,  144,  144,
+   143,  143,  143,  143,  142,  142,  142,  142,
+   141,  141,  141,  141,  140,  140,  140,  140,
+   139,  139,  139,  139,  138,  138,  138,  138,
+   137,  137,  137,  137,  136,  136,  136,  136,
+   135,  135,  135,  135,  134,  134,  134,  134,
+   133,  133,  133,  133,  132,  132,  132,  132,
+   131,  131,  131,  131,  130,  130,  130,  130,
+   129,  129,  129,  129,  128,  128,  128,  128,
+};
diff --git a/linux/sound/oss/v_midi.c b/linux/sound/oss/v_midi.c
new file mode 100644
index 0000000..f0b4151
--- /dev/null
+++ b/linux/sound/oss/v_midi.c
@@ -0,0 +1,290 @@
+/*
+ * sound/oss/v_midi.c
+ *
+ * The low level driver for the Sound Blaster DS chips.
+ *
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1996
+ *
+ * USS/Lite for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ * ??
+ *
+ * Changes
+ *	Alan Cox		Modularisation, changed memory allocations
+ *	Christoph Hellwig	Adapted to module_init/module_exit
+ *
+ * Status
+ *	Untested
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include "sound_config.h"
+
+#include "v_midi.h"
+
+static vmidi_devc *v_devc[2] = { NULL, NULL};
+static int midi1,midi2;
+static void *midi_mem = NULL;
+
+/*
+ * The DSP channel can be used either for input or output. Variable
+ * 'sb_irq_mode' will be set when the program calls read or write first time
+ * after open. Current version doesn't support mode changes without closing
+ * and reopening the device. Support for this feature may be implemented in a
+ * future version of this driver.
+ */
+
+
+static int v_midi_open (int dev, int mode,
+	      void            (*input) (int dev, unsigned char data),
+	      void            (*output) (int dev)
+)
+{
+	vmidi_devc *devc = midi_devs[dev]->devc;
+	unsigned long flags;
+
+	if (devc == NULL)
+		return -(ENXIO);
+
+	spin_lock_irqsave(&devc->lock,flags);
+	if (devc->opened)
+	{
+		spin_unlock_irqrestore(&devc->lock,flags);
+		return -(EBUSY);
+	}
+	devc->opened = 1;
+	spin_unlock_irqrestore(&devc->lock,flags);
+
+	devc->intr_active = 1;
+
+	if (mode & OPEN_READ)
+	{
+		devc->input_opened = 1;
+		devc->midi_input_intr = input;
+	}
+
+	return 0;
+}
+
+static void v_midi_close (int dev)
+{
+	vmidi_devc *devc = midi_devs[dev]->devc;
+	unsigned long flags;
+
+	if (devc == NULL)
+		return;
+
+	spin_lock_irqsave(&devc->lock,flags);
+	devc->intr_active = 0;
+	devc->input_opened = 0;
+	devc->opened = 0;
+	spin_unlock_irqrestore(&devc->lock,flags);
+}
+
+static int v_midi_out (int dev, unsigned char midi_byte)
+{
+	vmidi_devc *devc = midi_devs[dev]->devc;
+	vmidi_devc *pdevc;
+
+	if (devc == NULL)
+		return -ENXIO;
+
+	pdevc = midi_devs[devc->pair_mididev]->devc;
+	if (pdevc->input_opened > 0){
+		if (MIDIbuf_avail(pdevc->my_mididev) > 500)
+			return 0;
+		pdevc->midi_input_intr (pdevc->my_mididev, midi_byte);
+	}
+	return 1;
+}
+
+static inline int v_midi_start_read (int dev)
+{
+	return 0;
+}
+
+static int v_midi_end_read (int dev)
+{
+	vmidi_devc *devc = midi_devs[dev]->devc;
+	if (devc == NULL)
+		return -ENXIO;
+
+	devc->intr_active = 0;
+	return 0;
+}
+
+/* why -EPERM and not -EINVAL?? */
+
+static inline int v_midi_ioctl (int dev, unsigned cmd, void __user *arg)
+{
+	return -EPERM;
+}
+
+
+#define MIDI_SYNTH_NAME	"Loopback MIDI"
+#define MIDI_SYNTH_CAPS	SYNTH_CAP_INPUT
+
+#include "midi_synth.h"
+
+static struct midi_operations v_midi_operations =
+{
+	.owner		= THIS_MODULE,
+	.info		= {"Loopback MIDI Port 1", 0, 0, SNDCARD_VMIDI},
+	.converter	= &std_midi_synth,
+	.in_info	= {0},
+	.open		= v_midi_open,
+	.close		= v_midi_close,
+	.ioctl		= v_midi_ioctl,
+	.outputc	= v_midi_out,
+	.start_read	= v_midi_start_read,
+	.end_read	= v_midi_end_read,
+};
+
+static struct midi_operations v_midi_operations2 =
+{
+	.owner		= THIS_MODULE,
+	.info		= {"Loopback MIDI Port 2", 0, 0, SNDCARD_VMIDI},
+	.converter	= &std_midi_synth,
+	.in_info	= {0},
+	.open		= v_midi_open,
+	.close		= v_midi_close,
+	.ioctl		= v_midi_ioctl,
+	.outputc	= v_midi_out,
+	.start_read	= v_midi_start_read,
+	.end_read	= v_midi_end_read,
+};
+
+/*
+ *	We kmalloc just one of these - it makes life simpler and the code
+ *	cleaner and the memory handling far more efficient
+ */
+ 
+struct vmidi_memory
+{
+	/* Must be first */
+	struct midi_operations m_ops[2];
+	struct synth_operations s_ops[2];
+	struct vmidi_devc v_ops[2];
+};
+
+static void __init attach_v_midi (struct address_info *hw_config)
+{
+	struct vmidi_memory *m;
+	/* printk("Attaching v_midi device.....\n"); */
+
+	midi1 = sound_alloc_mididev();
+	if (midi1 == -1)
+	{
+		printk(KERN_ERR "v_midi: Too many midi devices detected\n");
+		return;
+	}
+	
+	m = kmalloc(sizeof(struct vmidi_memory), GFP_KERNEL);
+	if (m == NULL)
+	{
+		printk(KERN_WARNING "Loopback MIDI: Failed to allocate memory\n");
+		sound_unload_mididev(midi1);
+		return;
+	}
+	
+	midi_mem = m;
+	
+	midi_devs[midi1] = &m->m_ops[0];
+	
+
+	midi2 = sound_alloc_mididev();
+	if (midi2 == -1)
+	{
+		printk (KERN_ERR "v_midi: Too many midi devices detected\n");
+		kfree(m);
+		sound_unload_mididev(midi1);
+		return;
+	}
+
+	midi_devs[midi2] = &m->m_ops[1];
+
+	/* printk("VMIDI1: %d   VMIDI2: %d\n",midi1,midi2); */
+
+	/* for MIDI-1 */
+	v_devc[0] = &m->v_ops[0];
+	memcpy ((char *) midi_devs[midi1], (char *) &v_midi_operations,
+		sizeof (struct midi_operations));
+
+	v_devc[0]->my_mididev = midi1;
+	v_devc[0]->pair_mididev = midi2;
+	v_devc[0]->opened = v_devc[0]->input_opened = 0;
+	v_devc[0]->intr_active = 0;
+	v_devc[0]->midi_input_intr = NULL;
+	spin_lock_init(&v_devc[0]->lock);
+
+	midi_devs[midi1]->devc = v_devc[0];
+
+	midi_devs[midi1]->converter = &m->s_ops[0];
+	std_midi_synth.midi_dev = midi1;
+	memcpy ((char *) midi_devs[midi1]->converter, (char *) &std_midi_synth,
+		sizeof (struct synth_operations));
+	midi_devs[midi1]->converter->id = "V_MIDI 1";
+
+	/* for MIDI-2 */
+	v_devc[1] = &m->v_ops[1];
+
+	memcpy ((char *) midi_devs[midi2], (char *) &v_midi_operations2,
+		sizeof (struct midi_operations));
+
+	v_devc[1]->my_mididev = midi2;
+	v_devc[1]->pair_mididev = midi1;
+	v_devc[1]->opened = v_devc[1]->input_opened = 0;
+	v_devc[1]->intr_active = 0;
+	v_devc[1]->midi_input_intr = NULL;
+	spin_lock_init(&v_devc[1]->lock);
+
+	midi_devs[midi2]->devc = v_devc[1];
+	midi_devs[midi2]->converter = &m->s_ops[1];
+
+	std_midi_synth.midi_dev = midi2;
+	memcpy ((char *) midi_devs[midi2]->converter, (char *) &std_midi_synth,
+		sizeof (struct synth_operations));
+	midi_devs[midi2]->converter->id = "V_MIDI 2";
+
+	sequencer_init();
+	/* printk("Attached v_midi device\n"); */
+}
+
+static inline int __init probe_v_midi(struct address_info *hw_config)
+{
+	return(1);	/* always OK */
+}
+
+
+static void __exit unload_v_midi(struct address_info *hw_config)
+{
+	sound_unload_mididev(midi1);
+	sound_unload_mididev(midi2);
+	kfree(midi_mem);
+}
+
+static struct address_info cfg; /* dummy */
+
+static int __init init_vmidi(void)
+{
+	printk("MIDI Loopback device driver\n");
+	if (!probe_v_midi(&cfg))
+		return -ENODEV;
+	attach_v_midi(&cfg);
+
+	return 0;
+}
+
+static void __exit cleanup_vmidi(void)
+{
+	unload_v_midi(&cfg);
+}
+
+module_init(init_vmidi);
+module_exit(cleanup_vmidi);
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/v_midi.h b/linux/sound/oss/v_midi.h
new file mode 100644
index 0000000..08e2185
--- /dev/null
+++ b/linux/sound/oss/v_midi.h
@@ -0,0 +1,14 @@
+typedef struct vmidi_devc {
+	   int dev;
+
+	/* State variables */
+	   int opened;
+	   spinlock_t lock;
+
+	/* MIDI fields */
+	   int my_mididev;
+	   int pair_mididev;
+	   int input_opened;
+	   int intr_active;
+	   void (*midi_input_intr) (int dev, unsigned char data);
+	} vmidi_devc;
diff --git a/linux/sound/oss/vidc.c b/linux/sound/oss/vidc.c
new file mode 100644
index 0000000..f0e0caa
--- /dev/null
+++ b/linux/sound/oss/vidc.c
@@ -0,0 +1,558 @@
+/*
+ *  linux/drivers/sound/vidc.c
+ *
+ *  Copyright (C) 1997-2000 by Russell King <rmk@arm.linux.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  VIDC20 audio driver.
+ *
+ * The VIDC20 sound hardware consists of the VIDC20 itself, a DAC and a DMA
+ * engine.  The DMA transfers fixed-format (16-bit little-endian linear)
+ * samples to the VIDC20, which then transfers this data serially to the
+ * DACs.  The samplerate is controlled by the VIDC.
+ *
+ * We currently support a mixer device, but it is currently non-functional.
+ */
+
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+
+#include <mach/hardware.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/hardware/iomd.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+
+#include "sound_config.h"
+#include "vidc.h"
+
+#ifndef _SIOC_TYPE
+#define _SIOC_TYPE(x)	_IOC_TYPE(x)
+#endif
+#ifndef _SIOC_NR
+#define _SIOC_NR(x)	_IOC_NR(x)
+#endif
+
+#define VIDC_SOUND_CLOCK	(250000)
+#define VIDC_SOUND_CLOCK_EXT	(176400)
+
+/*
+ * When using SERIAL SOUND mode (external DAC), the number of physical
+ * channels is fixed at 2.
+ */
+static int		vidc_busy;
+static int		vidc_adev;
+static int		vidc_audio_rate;
+static char		vidc_audio_format;
+static char		vidc_audio_channels;
+
+static unsigned char	vidc_level_l[SOUND_MIXER_NRDEVICES] = {
+	85,		/* master	*/
+	50,		/* bass		*/
+	50,		/* treble	*/
+	0,		/* synth	*/
+	75,		/* pcm		*/
+	0,		/* speaker	*/
+	100,		/* ext line	*/
+	0,		/* mic		*/
+	100,		/* CD		*/
+	0,
+};
+
+static unsigned char	vidc_level_r[SOUND_MIXER_NRDEVICES] = {
+	85,		/* master	*/
+	50,		/* bass		*/
+	50,		/* treble	*/
+	0,		/* synth	*/
+	75,		/* pcm		*/
+	0,		/* speaker	*/
+	100,		/* ext line	*/
+	0,		/* mic		*/
+	100,		/* CD		*/
+	0,
+};
+
+static unsigned int	vidc_audio_volume_l;	/* left PCM vol, 0 - 65536 */
+static unsigned int	vidc_audio_volume_r;	/* right PCM vol, 0 - 65536 */
+
+extern void	vidc_update_filler(int bits, int channels);
+extern int	softoss_dev;
+
+static void
+vidc_mixer_set(int mdev, unsigned int level)
+{
+	unsigned int lev_l = level & 0x007f;
+	unsigned int lev_r = (level & 0x7f00) >> 8;
+	unsigned int mlev_l, mlev_r;
+
+	if (lev_l > 100)
+		lev_l = 100;
+	if (lev_r > 100)
+		lev_r = 100;
+
+#define SCALE(lev,master)	((lev) * (master) * 65536 / 10000)
+
+	mlev_l = vidc_level_l[SOUND_MIXER_VOLUME];
+	mlev_r = vidc_level_r[SOUND_MIXER_VOLUME];
+
+	switch (mdev) {
+	case SOUND_MIXER_VOLUME:
+	case SOUND_MIXER_PCM:
+		vidc_level_l[mdev] = lev_l;
+		vidc_level_r[mdev] = lev_r;
+
+		vidc_audio_volume_l = SCALE(lev_l, mlev_l);
+		vidc_audio_volume_r = SCALE(lev_r, mlev_r);
+/*printk("VIDC: PCM vol %05X %05X\n", vidc_audio_volume_l, vidc_audio_volume_r);*/
+		break;
+	}
+#undef SCALE
+}
+
+static int vidc_mixer_ioctl(int dev, unsigned int cmd, void __user *arg)
+{
+	unsigned int val;
+	unsigned int mdev;
+
+	if (_SIOC_TYPE(cmd) != 'M')
+		return -EINVAL;
+
+	mdev = _SIOC_NR(cmd);
+
+	if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+		if (get_user(val, (unsigned int __user *)arg))
+			return -EFAULT;
+
+		if (mdev < SOUND_MIXER_NRDEVICES)
+			vidc_mixer_set(mdev, val);
+		else
+			return -EINVAL;
+	}
+
+	/*
+	 * Return parameters
+	 */
+	switch (mdev) {
+	case SOUND_MIXER_RECSRC:
+		val = 0;
+		break;
+
+	case SOUND_MIXER_DEVMASK:
+		val = SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_SYNTH;
+		break;
+
+	case SOUND_MIXER_STEREODEVS:
+		val = SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_SYNTH;
+		break;
+
+	case SOUND_MIXER_RECMASK:
+		val = 0;
+		break;
+
+	case SOUND_MIXER_CAPS:
+		val = 0;
+		break;
+
+	default:
+		if (mdev < SOUND_MIXER_NRDEVICES)
+			val = vidc_level_l[mdev] | vidc_level_r[mdev] << 8;
+		else
+			return -EINVAL;
+	}
+
+	return put_user(val, (unsigned int __user *)arg) ? -EFAULT : 0;
+}
+
+static unsigned int vidc_audio_set_format(int dev, unsigned int fmt)
+{
+	switch (fmt) {
+	default:
+		fmt = AFMT_S16_LE;
+	case AFMT_U8:
+	case AFMT_S8:
+	case AFMT_S16_LE:
+		vidc_audio_format = fmt;
+		vidc_update_filler(vidc_audio_format, vidc_audio_channels);
+	case AFMT_QUERY:
+		break;
+	}
+	return vidc_audio_format;
+}
+
+#define my_abs(i) ((i)<0 ? -(i) : (i))
+
+static int vidc_audio_set_speed(int dev, int rate)
+{
+	if (rate) {
+		unsigned int hwctrl, hwrate, hwrate_ext, rate_int, rate_ext;
+		unsigned int diff_int, diff_ext;
+		unsigned int newsize, new2size;
+
+		hwctrl = 0x00000003;
+
+		/* Using internal clock */
+		hwrate = (((VIDC_SOUND_CLOCK * 2) / rate) + 1) >> 1;
+		if (hwrate < 3)
+			hwrate = 3;
+		if (hwrate > 255)
+			hwrate = 255;
+
+		/* Using exernal clock */
+		hwrate_ext = (((VIDC_SOUND_CLOCK_EXT * 2) / rate) + 1) >> 1;
+		if (hwrate_ext < 3)
+			hwrate_ext = 3;
+		if (hwrate_ext > 255)
+			hwrate_ext = 255;
+
+		rate_int = VIDC_SOUND_CLOCK / hwrate;
+		rate_ext = VIDC_SOUND_CLOCK_EXT / hwrate_ext;
+
+		/* Chose between external and internal clock */
+		diff_int = my_abs(rate_ext-rate);
+		diff_ext = my_abs(rate_int-rate);
+		if (diff_ext < diff_int) {
+			/*printk("VIDC: external %d %d %d\n", rate, rate_ext, hwrate_ext);*/
+			hwrate=hwrate_ext;
+			hwctrl=0x00000002;
+			/* Allow roughly 0.4% tolerance */
+			if (diff_ext > (rate/256))
+				rate=rate_ext;
+		} else {
+			/*printk("VIDC: internal %d %d %d\n", rate, rate_int, hwrate);*/
+			hwctrl=0x00000003;
+			/* Allow rougly 0.4% tolerance */
+			if (diff_int > (rate/256))
+				rate=rate_int;
+		}
+
+		vidc_writel(0xb0000000 | (hwrate - 2));
+		vidc_writel(0xb1000000 | hwctrl);
+
+		newsize = (10000 / hwrate) & ~3;
+		if (newsize < 208)
+			newsize = 208;
+		if (newsize > 4096)
+			newsize = 4096;
+		for (new2size = 128; new2size < newsize; new2size <<= 1);
+		if (new2size - newsize > newsize - (new2size >> 1))
+			new2size >>= 1;
+		if (new2size > 4096) {
+			printk(KERN_ERR "VIDC: error: dma buffer (%d) %d > 4K\n",
+				newsize, new2size);
+			new2size = 4096;
+		}
+		/*printk("VIDC: dma size %d\n", new2size);*/
+		dma_bufsize = new2size;
+		vidc_audio_rate = rate;
+	}
+	return vidc_audio_rate;
+}
+
+static short vidc_audio_set_channels(int dev, short channels)
+{
+	switch (channels) {
+	default:
+		channels = 2;
+	case 1:
+	case 2:
+		vidc_audio_channels = channels;
+		vidc_update_filler(vidc_audio_format, vidc_audio_channels);
+	case 0:
+		break;
+	}
+	return vidc_audio_channels;
+}
+
+/*
+ * Open the device
+ */
+static int vidc_audio_open(int dev, int mode)
+{
+	/* This audio device does not have recording capability */
+	if (mode == OPEN_READ)
+		return -EPERM;
+
+	if (vidc_busy)
+		return -EBUSY;
+
+	vidc_busy = 1;
+	return 0;
+}
+
+/*
+ * Close the device
+ */
+static void vidc_audio_close(int dev)
+{
+	vidc_busy = 0;
+}
+
+/*
+ * Output a block via DMA to sound device.
+ *
+ * We just set the DMA start and count; the DMA interrupt routine
+ * will take care of formatting the samples (via the appropriate
+ * vidc_filler routine), and flag via vidc_audio_dma_interrupt when
+ * more data is required.
+ */
+static void
+vidc_audio_output_block(int dev, unsigned long buf, int total_count, int one)
+{
+	struct dma_buffparms *dmap = audio_devs[dev]->dmap_out;
+	unsigned long flags;
+
+	local_irq_save(flags);
+	dma_start = buf - (unsigned long)dmap->raw_buf_phys + (unsigned long)dmap->raw_buf;
+	dma_count = total_count;
+	local_irq_restore(flags);
+}
+
+static void
+vidc_audio_start_input(int dev, unsigned long buf, int count, int intrflag)
+{
+}
+
+static int vidc_audio_prepare_for_input(int dev, int bsize, int bcount)
+{
+	return -EINVAL;
+}
+
+static irqreturn_t vidc_audio_dma_interrupt(void)
+{
+	DMAbuf_outputintr(vidc_adev, 1);
+	return IRQ_HANDLED;
+}
+
+/*
+ * Prepare for outputting samples.
+ *
+ * Each buffer that will be passed will be `bsize' bytes long,
+ * with a total of `bcount' buffers.
+ */
+static int vidc_audio_prepare_for_output(int dev, int bsize, int bcount)
+{
+	struct audio_operations *adev = audio_devs[dev];
+
+	dma_interrupt = NULL;
+	adev->dmap_out->flags |= DMA_NODMA;
+
+	return 0;
+}
+
+/*
+ * Stop our current operation.
+ */
+static void vidc_audio_reset(int dev)
+{
+	dma_interrupt = NULL;
+}
+
+static int vidc_audio_local_qlen(int dev)
+{
+	return /*dma_count !=*/ 0;
+}
+
+static void vidc_audio_trigger(int dev, int enable_bits)
+{
+	struct audio_operations *adev = audio_devs[dev];
+
+	if (enable_bits & PCM_ENABLE_OUTPUT) {
+		if (!(adev->dmap_out->flags & DMA_ACTIVE)) {
+			unsigned long flags;
+
+			local_irq_save(flags);
+
+			/* prevent recusion */
+			adev->dmap_out->flags |= DMA_ACTIVE;
+
+			dma_interrupt = vidc_audio_dma_interrupt;
+			vidc_sound_dma_irq(0, NULL);
+			iomd_writeb(DMA_CR_E | 0x10, IOMD_SD0CR);
+
+			local_irq_restore(flags);
+		}
+	}
+}
+
+static struct audio_driver vidc_audio_driver =
+{
+	.owner			= THIS_MODULE,
+	.open			= vidc_audio_open,
+	.close			= vidc_audio_close,
+	.output_block		= vidc_audio_output_block,
+	.start_input		= vidc_audio_start_input,
+	.prepare_for_input	= vidc_audio_prepare_for_input,
+	.prepare_for_output	= vidc_audio_prepare_for_output,
+	.halt_io		= vidc_audio_reset,
+	.local_qlen		= vidc_audio_local_qlen,
+	.trigger		= vidc_audio_trigger,
+	.set_speed		= vidc_audio_set_speed,
+	.set_bits		= vidc_audio_set_format,
+	.set_channels		= vidc_audio_set_channels
+};
+
+static struct mixer_operations vidc_mixer_operations = {
+	.owner		= THIS_MODULE,
+	.id		= "VIDC",
+	.name		= "VIDCsound",
+	.ioctl		= vidc_mixer_ioctl
+};
+
+void vidc_update_filler(int format, int channels)
+{
+#define TYPE(fmt,ch) (((fmt)<<2) | ((ch)&3))
+
+	switch (TYPE(format, channels)) {
+	default:
+	case TYPE(AFMT_U8, 1):
+		vidc_filler = vidc_fill_1x8_u;
+		break;
+
+	case TYPE(AFMT_U8, 2):
+		vidc_filler = vidc_fill_2x8_u;
+		break;
+
+	case TYPE(AFMT_S8, 1):
+		vidc_filler = vidc_fill_1x8_s;
+		break;
+
+	case TYPE(AFMT_S8, 2):
+		vidc_filler = vidc_fill_2x8_s;
+		break;
+
+	case TYPE(AFMT_S16_LE, 1):
+		vidc_filler = vidc_fill_1x16_s;
+		break;
+
+	case TYPE(AFMT_S16_LE, 2):
+		vidc_filler = vidc_fill_2x16_s;
+		break;
+	}
+}
+
+static void __init attach_vidc(struct address_info *hw_config)
+{
+	char name[32];
+	int i, adev;
+
+	sprintf(name, "VIDC %d-bit sound", hw_config->card_subtype);
+	conf_printf(name, hw_config);
+	memset(dma_buf, 0, sizeof(dma_buf));
+
+	adev = sound_install_audiodrv(AUDIO_DRIVER_VERSION, name,
+			&vidc_audio_driver, sizeof(vidc_audio_driver),
+			DMA_AUTOMODE, AFMT_U8 | AFMT_S8 | AFMT_S16_LE,
+			NULL, hw_config->dma, hw_config->dma2);
+
+	if (adev < 0)
+		goto audio_failed;
+
+	/*
+	 * 1024 bytes => 64 buffers
+	 */
+	audio_devs[adev]->min_fragment = 10;
+	audio_devs[adev]->mixer_dev = num_mixers;
+
+	audio_devs[adev]->mixer_dev =
+		sound_install_mixer(MIXER_DRIVER_VERSION,
+				name, &vidc_mixer_operations,
+				sizeof(vidc_mixer_operations), NULL);
+
+	if (audio_devs[adev]->mixer_dev < 0)
+		goto mixer_failed;
+
+	for (i = 0; i < 2; i++) {
+		dma_buf[i] = get_zeroed_page(GFP_KERNEL);
+		if (!dma_buf[i]) {
+			printk(KERN_ERR "%s: can't allocate required buffers\n",
+				name);
+			goto mem_failed;
+		}
+		dma_pbuf[i] = virt_to_phys((void *)dma_buf[i]);
+	}
+
+	if (sound_alloc_dma(hw_config->dma, hw_config->name)) {
+		printk(KERN_ERR "%s: DMA %d is in  use\n", name, hw_config->dma);
+		goto dma_failed;
+	}
+
+	if (request_irq(hw_config->irq, vidc_sound_dma_irq, 0,
+			hw_config->name, &dma_start)) {
+		printk(KERN_ERR "%s: IRQ %d is in use\n", name, hw_config->irq);
+		goto irq_failed;
+	}
+	vidc_adev = adev;
+	vidc_mixer_set(SOUND_MIXER_VOLUME, (85 | 85 << 8));
+
+	return;
+
+irq_failed:
+	sound_free_dma(hw_config->dma);
+dma_failed:
+mem_failed:
+	for (i = 0; i < 2; i++)
+		free_page(dma_buf[i]);
+	sound_unload_mixerdev(audio_devs[adev]->mixer_dev);
+mixer_failed:
+	sound_unload_audiodev(adev);
+audio_failed:
+	return;
+}
+
+static int __init probe_vidc(struct address_info *hw_config)
+{
+	hw_config->irq		= IRQ_DMAS0;
+	hw_config->dma		= DMA_VIRTUAL_SOUND;
+	hw_config->dma2		= -1;
+	hw_config->card_subtype	= 16;
+	hw_config->name		= "VIDC20";
+	return 1;
+}
+
+static void __exit unload_vidc(struct address_info *hw_config)
+{
+	int i, adev = vidc_adev;
+
+	vidc_adev = -1;
+
+	free_irq(hw_config->irq, &dma_start);
+	sound_free_dma(hw_config->dma);
+
+	if (adev >= 0) {
+		sound_unload_mixerdev(audio_devs[adev]->mixer_dev);
+		sound_unload_audiodev(adev);
+		for (i = 0; i < 2; i++)
+			free_page(dma_buf[i]);
+	}
+}
+
+static struct address_info cfg;
+
+static int __init init_vidc(void)
+{
+	if (probe_vidc(&cfg) == 0)
+		return -ENODEV;
+
+	attach_vidc(&cfg);
+
+	return 0;
+}
+
+static void __exit cleanup_vidc(void)
+{
+	unload_vidc(&cfg);
+}
+
+module_init(init_vidc);
+module_exit(cleanup_vidc);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("VIDC20 audio driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/vidc.h b/linux/sound/oss/vidc.h
new file mode 100644
index 0000000..0d14247
--- /dev/null
+++ b/linux/sound/oss/vidc.h
@@ -0,0 +1,63 @@
+/*
+ *  linux/drivers/sound/vidc.h
+ *
+ *  Copyright (C) 1997 Russell King <rmk@arm.linux.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  VIDC sound function prototypes
+ */
+
+/* vidc_fill.S */
+
+/*
+ * Filler routines for different channels and sample sizes
+ */
+
+extern unsigned long vidc_fill_1x8_u(unsigned long ibuf, unsigned long iend,
+				     unsigned long obuf, int mask);
+extern unsigned long vidc_fill_2x8_u(unsigned long ibuf, unsigned long iend,
+				     unsigned long obuf, int mask);
+extern unsigned long vidc_fill_1x8_s(unsigned long ibuf, unsigned long iend,
+				     unsigned long obuf, int mask);
+extern unsigned long vidc_fill_2x8_s(unsigned long ibuf, unsigned long iend,
+				     unsigned long obuf, int mask);
+extern unsigned long vidc_fill_1x16_s(unsigned long ibuf, unsigned long iend,
+				      unsigned long obuf, int mask);
+extern unsigned long vidc_fill_2x16_s(unsigned long ibuf, unsigned long iend,
+				      unsigned long obuf, int mask);
+
+/*
+ * DMA Interrupt handler
+ */
+
+extern irqreturn_t vidc_sound_dma_irq(int irqnr, void *ref);
+
+/*
+ * Filler routine pointer
+ */
+
+extern unsigned long (*vidc_filler) (unsigned long ibuf, unsigned long iend,
+				     unsigned long obuf, int mask);
+
+/*
+ * Virtual DMA buffer exhausted
+ */
+
+extern irqreturn_t (*dma_interrupt) (void);
+
+/*
+ * Virtual DMA buffer addresses
+ */
+
+extern unsigned long dma_start, dma_count, dma_bufsize;
+extern unsigned long dma_buf[2], dma_pbuf[2];
+
+/* vidc_synth.c */
+
+extern void     vidc_synth_init(struct address_info *hw_config);
+extern void	vidc_synth_exit(struct address_info *hw_config);
+extern int      vidc_synth_get_volume(void);
+extern int      vidc_synth_set_volume(int vol);
diff --git a/linux/sound/oss/vidc_fill.S b/linux/sound/oss/vidc_fill.S
new file mode 100644
index 0000000..bed3492
--- /dev/null
+++ b/linux/sound/oss/vidc_fill.S
@@ -0,0 +1,218 @@
+/*
+ *  linux/drivers/sound/vidc_fill.S
+ *
+ *  Copyright (C) 1997 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  Filler routines for DMA buffers
+ */
+#include <linux/linkage.h>
+#include <asm/assembler.h>
+#include <mach/hardware.h>
+#include <asm/hardware/iomd.h>
+
+		.text
+
+ENTRY(vidc_fill_1x8_u)
+		mov	ip, #0xff00
+1:		cmp	r0, r1
+		bge	vidc_clear
+		ldrb	r4, [r0], #1
+		eor	r4, r4, #0x80
+		and	r4, ip, r4, lsl #8
+		orr	r4, r4, r4, lsl #16
+		str	r4, [r2], #4
+		cmp	r2, r3
+		blt	1b
+		mov	pc, lr
+
+ENTRY(vidc_fill_2x8_u)
+		mov	ip, #0xff00
+1:		cmp	r0, r1
+		bge	vidc_clear
+		ldr	r4, [r0], #2
+		and	r5, r4, ip
+		and	r4, ip, r4, lsl #8
+		orr	r4, r4, r5, lsl #16
+		orr	r4, r4, r4, lsr #8
+		str	r4, [r2], #4
+		cmp	r2, r3
+		blt	1b
+		mov	pc, lr
+
+ENTRY(vidc_fill_1x8_s)
+		mov	ip, #0xff00
+1:		cmp	r0, r1
+		bge	vidc_clear
+		ldrb	r4, [r0], #1
+		and	r4, ip, r4, lsl #8
+		orr	r4, r4, r4, lsl #16
+		str	r4, [r2], #4
+		cmp	r2, r3
+		blt	1b
+		mov	pc, lr
+
+ENTRY(vidc_fill_2x8_s)
+		mov	ip, #0xff00
+1:		cmp	r0, r1
+		bge	vidc_clear
+		ldr	r4, [r0], #2
+		and	r5, r4, ip
+		and	r4, ip, r4, lsl #8
+		orr	r4, r4, r5, lsl #16
+		orr	r4, r4, r4, lsr #8
+		str	r4, [r2], #4
+		cmp	r2, r3
+		blt	1b
+		mov	pc, lr
+
+ENTRY(vidc_fill_1x16_s)
+		mov	ip, #0xff00
+		orr	ip, ip, ip, lsr #8
+1:		cmp	r0, r1
+		bge	vidc_clear
+		ldr	r5, [r0], #2
+		and	r4, r5, ip
+		orr	r4, r4, r4, lsl #16
+		str	r4, [r2], #4
+		cmp	r0, r1
+		addlt	r0, r0, #2
+		andlt	r4, r5, ip, lsl #16
+		orrlt	r4, r4, r4, lsr #16
+		strlt	r4, [r2], #4
+		cmp	r2, r3
+		blt	1b
+		mov	pc, lr
+
+ENTRY(vidc_fill_2x16_s)
+		mov	ip, #0xff00
+		orr	ip, ip, ip, lsr #8
+1:		cmp	r0, r1
+		bge	vidc_clear
+		ldr	r4, [r0], #4
+		str	r4, [r2], #4
+		cmp	r0, r1
+		ldrlt	r4, [r0], #4
+		strlt	r4, [r2], #4
+		cmp	r2, r3
+		blt	1b
+		mov	pc, lr
+
+ENTRY(vidc_fill_noaudio)
+		mov	r0, #0
+		mov	r1, #0
+2:		mov	r4, #0
+		mov	r5, #0
+1:		cmp	r2, r3
+		stmltia	r2!, {r0, r1, r4, r5}
+		blt	1b
+		mov	pc, lr
+
+ENTRY(vidc_clear)
+		mov	r0, #0
+		mov	r1, #0
+		tst	r2, #4
+		str	r0, [r2], #4
+		tst	r2, #8
+		stmia	r2!, {r0, r1}
+		b	2b
+
+/*
+ * Call filler routines with:
+ *  r0 = phys address
+ *  r1 = phys end
+ *  r2 = buffer
+ * Returns:
+ *  r0 = new buffer address
+ *  r2 = new buffer finish
+ *  r4 = corrupted
+ *  r5 = corrupted
+ *  ip = corrupted
+ */
+
+ENTRY(vidc_sound_dma_irq)
+		stmfd	sp!, {r4 - r8, lr}
+		ldr	r8, =dma_start
+		ldmia	r8, {r0, r1, r2, r3, r4, r5}
+		teq	r1, #0
+		adreq	r4, vidc_fill_noaudio
+		moveq	r7, #1 << 31
+		movne	r7, #0
+		mov	ip, #IOMD_BASE & 0xff000000
+		orr	ip, ip, #IOMD_BASE & 0x00ff0000
+		ldrb	r6, [ip, #IOMD_SD0ST]
+		tst	r6, #DMA_ST_OFL			@ Check for overrun
+		eorne	r6, r6, #DMA_ST_AB
+		tst	r6, #DMA_ST_AB
+		moveq	r2, r3				@ DMAing A, update B
+		add	r3, r2, r5			@ End of DMA buffer
+		add	r1, r1, r0			@ End of virtual DMA buffer
+		mov	lr, pc
+		mov	pc, r4				@ Call fill routine (uses r4, ip)
+		sub	r1, r1, r0			@ Remaining length
+		stmia	r8, {r0, r1}
+		mov	r0, #0
+		tst	r2, #4				@ Round buffer up to 4 words
+		strne	r0, [r2], #4
+		tst	r2, #8
+		strne	r0, [r2], #4
+		strne	r0, [r2], #4
+		sub	r2, r2, #16
+		mov	r2, r2, lsl #20
+		movs	r2, r2, lsr #20
+		orreq	r2, r2, #1 << 30		@ Set L bit
+		orr	r2, r2, r7
+		ldmdb	r8, {r3, r4, r5}
+		tst	r6, #DMA_ST_AB
+		mov	ip, #IOMD_BASE & 0xff000000
+		orr	ip, ip, #IOMD_BASE & 0x00ff0000
+		streq	r4, [ip, #IOMD_SD0CURB]
+		strne	r5, [ip, #IOMD_SD0CURA]
+		streq	r2, [ip, #IOMD_SD0ENDB]
+		strne	r2, [ip, #IOMD_SD0ENDA]
+		ldr	lr, [ip, #IOMD_SD0ST]
+		tst	lr, #DMA_ST_OFL
+		bne	1f
+		tst	r6, #DMA_ST_AB
+		strne	r4, [ip, #IOMD_SD0CURB]
+		streq	r5, [ip, #IOMD_SD0CURA]
+		strne	r2, [ip, #IOMD_SD0ENDB]
+		streq	r2, [ip, #IOMD_SD0ENDA]
+1:		teq	r7, #0
+		mov	r0, #0x10
+		strneb	r0, [ip, #IOMD_SD0CR]
+		ldmfd	sp!, {r4 - r8, lr}
+		mov	r0, #1				@ IRQ_HANDLED
+		teq	r1, #0				@ If we have no more
+		movne	pc, lr
+		teq	r3, #0
+		movne	pc, r3				@ Call interrupt routine
+		mov	pc, lr
+
+		.data
+		.globl	dma_interrupt
+dma_interrupt:
+		.long	0				@ r3
+		.globl	dma_pbuf
+dma_pbuf:
+		.long	0				@ r4
+		.long	0				@ r5
+		.globl	dma_start
+dma_start:
+		.long	0				@ r0
+		.globl	dma_count
+dma_count:
+		.long	0				@ r1
+		.globl	dma_buf
+dma_buf:
+		.long	0				@ r2
+		.long	0				@ r3
+		.globl	vidc_filler
+vidc_filler:
+		.long	vidc_fill_noaudio		@ r4
+		.globl	dma_bufsize
+dma_bufsize:
+		.long	0x1000				@ r5
diff --git a/linux/sound/oss/vwsnd.c b/linux/sound/oss/vwsnd.c
new file mode 100644
index 0000000..643f111
--- /dev/null
+++ b/linux/sound/oss/vwsnd.c
@@ -0,0 +1,3498 @@
+/*
+ * Sound driver for Silicon Graphics 320 and 540 Visual Workstations'
+ * onboard audio.  See notes in Documentation/sound/oss/vwsnd .
+ *
+ * Copyright 1999 Silicon Graphics, Inc.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#undef VWSND_DEBUG			/* define for debugging */
+
+/*
+ * XXX to do -
+ *
+ *	External sync.
+ *	Rename swbuf, hwbuf, u&i, hwptr&swptr to something rational.
+ *	Bug - if select() called before read(), pcm_setup() not called.
+ *	Bug - output doesn't stop soon enough if process killed.
+ */
+
+/*
+ * Things to test -
+ *
+ *	Will readv/writev work?  Write a test.
+ *
+ *	insmod/rmmod 100 million times.
+ *
+ *	Run I/O until int ptrs wrap around (roughly 6.2 hours @ DAT
+ *	rate).
+ *
+ *	Concurrent threads banging on mixer simultaneously, both UP
+ *	and SMP kernels.  Especially, watch for thread A changing
+ *	OUTSRC while thread B changes gain -- both write to the same
+ *	ad1843 register.
+ *
+ *	What happens if a client opens /dev/audio then forks?
+ *	Do two procs have /dev/audio open?  Test.
+ *
+ *	Pump audio through the CD, MIC and line inputs and verify that
+ *	they mix/mute into the output.
+ *
+ *	Apps:
+ *		amp
+ *		mpg123
+ *		x11amp
+ *		mxv
+ *		kmedia
+ *		esound
+ *		need more input apps
+ *
+ *	Run tests while bombarding with signals.  setitimer(2) will do it...  */
+
+/*
+ * This driver is organized in nine sections.
+ * The nine sections are:
+ *
+ *	debug stuff
+ * 	low level lithium access
+ *	high level lithium access
+ *	AD1843 access
+ *	PCM I/O
+ *	audio driver
+ *	mixer driver
+ *	probe/attach/unload
+ *	initialization and loadable kernel module interface
+ *
+ * That is roughly the order of increasing abstraction, so forward
+ * dependencies are minimal.
+ */
+
+/*
+ * Locking Notes
+ *
+ *	INC_USE_COUNT and DEC_USE_COUNT keep track of the number of
+ *	open descriptors to this driver. They store it in vwsnd_use_count.
+ * 	The global device list, vwsnd_dev_list,	is immutable when the IN_USE
+ *	is true.
+ *
+ *	devc->open_lock is a semaphore that is used to enforce the
+ *	single reader/single writer rule for /dev/audio.  The rule is
+ *	that each device may have at most one reader and one writer.
+ *	Open will block until the previous client has closed the
+ *	device, unless O_NONBLOCK is specified.
+ *
+ *	The semaphore devc->io_mutex serializes PCM I/O syscalls.  This
+ *	is unnecessary in Linux 2.2, because the kernel lock
+ *	serializes read, write, and ioctl globally, but it's there,
+ *	ready for the brave, new post-kernel-lock world.
+ *
+ *	Locking between interrupt and baselevel is handled by the
+ *	"lock" spinlock in vwsnd_port (one lock each for read and
+ *	write).  Each half holds the lock just long enough to see what
+ *	area it owns and update its pointers.  See pcm_output() and
+ *	pcm_input() for most of the gory stuff.
+ *
+ *	devc->mix_mutex serializes all mixer ioctls.  This is also
+ *	redundant because of the kernel lock.
+ *
+ *	The lowest level lock is lith->lithium_lock.  It is a
+ *	spinlock which is held during the two-register tango of
+ *	reading/writing an AD1843 register.  See
+ *	li_{read,write}_ad1843_reg().
+ */
+
+/*
+ * Sample Format Notes
+ *
+ *	Lithium's DMA engine has two formats: 16-bit 2's complement
+ *	and 8-bit unsigned .  16-bit transfers the data unmodified, 2
+ *	bytes per sample.  8-bit unsigned transfers 1 byte per sample
+ *	and XORs each byte with 0x80.  Lithium can input or output
+ *	either mono or stereo in either format.
+ *
+ *	The AD1843 has four formats: 16-bit 2's complement, 8-bit
+ *	unsigned, 8-bit mu-Law and 8-bit A-Law.
+ *
+ *	This driver supports five formats: AFMT_S8, AFMT_U8,
+ *	AFMT_MU_LAW, AFMT_A_LAW, and AFMT_S16_LE.
+ *
+ *	For AFMT_U8 output, we keep the AD1843 in 16-bit mode, and
+ *	rely on Lithium's XOR to translate between U8 and S8.
+ *
+ *	For AFMT_S8, AFMT_MU_LAW and AFMT_A_LAW output, we have to XOR
+ *	the 0x80 bit in software to compensate for Lithium's XOR.
+ *	This happens in pcm_copy_{in,out}().
+ *
+ * Changes:
+ * 11-10-2000	Bartlomiej Zolnierkiewicz <bkz@linux-ide.org>
+ *		Added some __init/__exit
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include <asm/visws/cobalt.h>
+
+#include "sound_config.h"
+
+/*****************************************************************************/
+/* debug stuff */
+
+#ifdef VWSND_DEBUG
+
+static DEFINE_MUTEX(vwsnd_mutex);
+static int shut_up = 1;
+
+/*
+ * dbgassert - called when an assertion fails.
+ */
+
+static void dbgassert(const char *fcn, int line, const char *expr)
+{
+	if (in_interrupt())
+		panic("ASSERTION FAILED IN INTERRUPT, %s:%s:%d %s\n",
+		      __FILE__, fcn, line, expr);
+	else {
+		int x;
+		printk(KERN_ERR "ASSERTION FAILED, %s:%s:%d %s\n",
+		       __FILE__, fcn, line, expr);
+		x = * (volatile int *) 0; /* force proc to exit */
+	}
+}
+
+/*
+ * Bunch of useful debug macros:
+ *
+ *	ASSERT	- print unless e nonzero (panic if in interrupt)
+ *	DBGDO	- include arbitrary code if debugging
+ *	DBGX	- debug print raw (w/o function name)
+ *	DBGP	- debug print w/ function name
+ *	DBGE	- debug print function entry
+ *	DBGC	- debug print function call
+ *	DBGR	- debug print function return
+ *	DBGXV	- debug print raw when verbose
+ *	DBGPV	- debug print when verbose
+ *	DBGEV	- debug print function entry when verbose
+ *	DBGRV	- debug print function return when verbose
+ */
+
+#define ASSERT(e)      ((e) ? (void) 0 : dbgassert(__func__, __LINE__, #e))
+#define DBGDO(x)            x
+#define DBGX(fmt, args...)  (in_interrupt() ? 0 : printk(KERN_ERR fmt, ##args))
+#define DBGP(fmt, args...)  (DBGX("%s: " fmt, __func__ , ##args))
+#define DBGE(fmt, args...)  (DBGX("%s" fmt, __func__ , ##args))
+#define DBGC(rtn)           (DBGP("calling %s\n", rtn))
+#define DBGR()              (DBGP("returning\n"))
+#define DBGXV(fmt, args...) (shut_up ? 0 : DBGX(fmt, ##args))
+#define DBGPV(fmt, args...) (shut_up ? 0 : DBGP(fmt, ##args))
+#define DBGEV(fmt, args...) (shut_up ? 0 : DBGE(fmt, ##args))
+#define DBGCV(rtn)          (shut_up ? 0 : DBGC(rtn))
+#define DBGRV()             (shut_up ? 0 : DBGR())
+
+#else /* !VWSND_DEBUG */
+
+#define ASSERT(e)           ((void) 0)
+#define DBGDO(x)            /* don't */
+#define DBGX(fmt, args...)  ((void) 0)
+#define DBGP(fmt, args...)  ((void) 0)
+#define DBGE(fmt, args...)  ((void) 0)
+#define DBGC(rtn)           ((void) 0)
+#define DBGR()              ((void) 0)
+#define DBGPV(fmt, args...) ((void) 0)
+#define DBGXV(fmt, args...) ((void) 0)
+#define DBGEV(fmt, args...) ((void) 0)
+#define DBGCV(rtn)          ((void) 0)
+#define DBGRV()             ((void) 0)
+
+#endif /* !VWSND_DEBUG */
+
+/*****************************************************************************/
+/* low level lithium access */
+
+/*
+ * We need to talk to Lithium registers on three pages.  Here are
+ * the pages' offsets from the base address (0xFF001000).
+ */
+
+enum {
+	LI_PAGE0_OFFSET = 0x01000 - 0x1000, /* FF001000 */
+	LI_PAGE1_OFFSET = 0x0F000 - 0x1000, /* FF00F000 */
+	LI_PAGE2_OFFSET = 0x10000 - 0x1000, /* FF010000 */
+};
+
+/* low-level lithium data */
+
+typedef struct lithium {
+	void *		page0;		/* virtual addresses */
+	void *		page1;
+	void *		page2;
+	spinlock_t	lock;		/* protects codec and UST/MSC access */
+} lithium_t;
+
+/*
+ * li_destroy destroys the lithium_t structure and vm mappings.
+ */
+
+static void li_destroy(lithium_t *lith)
+{
+	if (lith->page0) {
+		iounmap(lith->page0);
+		lith->page0 = NULL;
+	}
+	if (lith->page1) {
+		iounmap(lith->page1);
+		lith->page1 = NULL;
+	}
+	if (lith->page2) {
+		iounmap(lith->page2);
+		lith->page2 = NULL;
+	}
+}
+
+/*
+ * li_create initializes the lithium_t structure and sets up vm mappings
+ * to access the registers.
+ * Returns 0 on success, -errno on failure.
+ */
+
+static int __init li_create(lithium_t *lith, unsigned long baseaddr)
+{
+	spin_lock_init(&lith->lock);
+	lith->page0 = ioremap_nocache(baseaddr + LI_PAGE0_OFFSET, PAGE_SIZE);
+	lith->page1 = ioremap_nocache(baseaddr + LI_PAGE1_OFFSET, PAGE_SIZE);
+	lith->page2 = ioremap_nocache(baseaddr + LI_PAGE2_OFFSET, PAGE_SIZE);
+	if (!lith->page0 || !lith->page1 || !lith->page2) {
+		li_destroy(lith);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+/*
+ * basic register accessors - read/write long/byte
+ */
+
+static __inline__ unsigned long li_readl(lithium_t *lith, int off)
+{
+	return * (volatile unsigned long *) (lith->page0 + off);
+}
+
+static __inline__ unsigned char li_readb(lithium_t *lith, int off)
+{
+	return * (volatile unsigned char *) (lith->page0 + off);
+}
+
+static __inline__ void li_writel(lithium_t *lith, int off, unsigned long val)
+{
+	* (volatile unsigned long *) (lith->page0 + off) = val;
+}
+
+static __inline__ void li_writeb(lithium_t *lith, int off, unsigned char val)
+{
+	* (volatile unsigned char *) (lith->page0 + off) = val;
+}
+
+/*****************************************************************************/
+/* High Level Lithium Access */
+
+/*
+ * Lithium DMA Notes
+ *
+ * Lithium has two dedicated DMA channels for audio.  They are known
+ * as comm1 and comm2 (communication areas 1 and 2).  Comm1 is for
+ * input, and comm2 is for output.  Each is controlled by three
+ * registers: BASE (base address), CFG (config) and CCTL
+ * (config/control).
+ *
+ * Each DMA channel points to a physically contiguous ring buffer in
+ * main memory of up to 8 Kbytes.  (This driver always uses 8 Kb.)
+ * There are three pointers into the ring buffer: read, write, and
+ * trigger.  The pointers are 8 bits each.  Each pointer points to
+ * 32-byte "chunks" of data.  The DMA engine moves 32 bytes at a time,
+ * so there is no finer-granularity control.
+ *
+ * In comm1, the hardware updates the write ptr, and software updates
+ * the read ptr.  In comm2, it's the opposite: hardware updates the
+ * read ptr, and software updates the write ptr.  I designate the
+ * hardware-updated ptr as the hwptr, and the software-updated ptr as
+ * the swptr.
+ *
+ * The trigger ptr and trigger mask are used to trigger interrupts.
+ * From the Lithium spec, section 5.6.8, revision of 12/15/1998:
+ *
+ *	Trigger Mask Value
+ *
+ *	A three bit wide field that represents a power of two mask
+ *	that is used whenever the trigger pointer is compared to its
+ *	respective read or write pointer.  A value of zero here
+ *	implies a mask of 0xFF and a value of seven implies a mask
+ *	0x01.  This value can be used to sub-divide the ring buffer
+ *	into pie sections so that interrupts monitor the progress of
+ *	hardware from section to section.
+ *
+ * My interpretation of that is, whenever the hw ptr is updated, it is
+ * compared with the trigger ptr, and the result is masked by the
+ * trigger mask.  (Actually, by the complement of the trigger mask.)
+ * If the result is zero, an interrupt is triggered.  I.e., interrupt
+ * if ((hwptr & ~mask) == (trptr & ~mask)).  The mask is formed from
+ * the trigger register value as mask = (1 << (8 - tmreg)) - 1.
+ *
+ * In yet different words, setting tmreg to 0 causes an interrupt after
+ * every 256 DMA chunks (8192 bytes) or once per traversal of the
+ * ring buffer.  Setting it to 7 caues an interrupt every 2 DMA chunks
+ * (64 bytes) or 128 times per traversal of the ring buffer.
+ */
+
+/* Lithium register offsets and bit definitions */
+
+#define LI_HOST_CONTROLLER	0x000
+# define LI_HC_RESET		 0x00008000
+# define LI_HC_LINK_ENABLE	 0x00004000
+# define LI_HC_LINK_FAILURE	 0x00000004
+# define LI_HC_LINK_CODEC	 0x00000002
+# define LI_HC_LINK_READY	 0x00000001
+
+#define LI_INTR_STATUS		0x010
+#define LI_INTR_MASK		0x014
+# define LI_INTR_LINK_ERR	 0x00008000
+# define LI_INTR_COMM2_TRIG	 0x00000008
+# define LI_INTR_COMM2_UNDERFLOW 0x00000004
+# define LI_INTR_COMM1_TRIG	 0x00000002
+# define LI_INTR_COMM1_OVERFLOW  0x00000001
+
+#define LI_CODEC_COMMAND	0x018
+# define LI_CC_BUSY		 0x00008000
+# define LI_CC_DIR		 0x00000080
+#  define LI_CC_DIR_RD		  LI_CC_DIR
+#  define LI_CC_DIR_WR		(!LI_CC_DIR)
+# define LI_CC_ADDR_MASK	 0x0000007F
+
+#define LI_CODEC_DATA		0x01C
+
+#define LI_COMM1_BASE		0x100
+#define LI_COMM1_CTL		0x104
+# define LI_CCTL_RESET		 0x80000000
+# define LI_CCTL_SIZE		 0x70000000
+# define LI_CCTL_DMA_ENABLE	 0x08000000
+# define LI_CCTL_TMASK		 0x07000000 /* trigger mask */
+# define LI_CCTL_TPTR		 0x00FF0000 /* trigger pointer */
+# define LI_CCTL_RPTR		 0x0000FF00
+# define LI_CCTL_WPTR		 0x000000FF
+#define LI_COMM1_CFG		0x108
+# define LI_CCFG_LOCK		 0x00008000
+# define LI_CCFG_SLOT		 0x00000070
+# define LI_CCFG_DIRECTION	 0x00000008
+#  define LI_CCFG_DIR_IN	(!LI_CCFG_DIRECTION)
+#  define LI_CCFG_DIR_OUT	  LI_CCFG_DIRECTION
+# define LI_CCFG_MODE		 0x00000004
+#  define LI_CCFG_MODE_MONO	(!LI_CCFG_MODE)
+#  define LI_CCFG_MODE_STEREO	  LI_CCFG_MODE
+# define LI_CCFG_FORMAT		 0x00000003
+#  define LI_CCFG_FMT_8BIT	  0x00000000
+#  define LI_CCFG_FMT_16BIT	  0x00000001
+#define LI_COMM2_BASE		0x10C
+#define LI_COMM2_CTL		0x110
+ /* bit definitions are the same as LI_COMM1_CTL */
+#define LI_COMM2_CFG		0x114
+ /* bit definitions are the same as LI_COMM1_CFG */
+
+#define LI_UST_LOW		0x200	/* 64-bit Unadjusted System Time is */
+#define LI_UST_HIGH		0x204	/* microseconds since boot */
+
+#define LI_AUDIO1_UST		0x300	/* UST-MSC pairs */
+#define LI_AUDIO1_MSC		0x304	/* MSC (Media Stream Counter) */
+#define LI_AUDIO2_UST		0x308	/* counts samples actually */
+#define LI_AUDIO2_MSC		0x30C	/* processed as of time UST */
+
+/* 
+ * Lithium's DMA engine operates on chunks of 32 bytes.  We call that
+ * a DMACHUNK.
+ */
+
+#define DMACHUNK_SHIFT 5
+#define DMACHUNK_SIZE (1 << DMACHUNK_SHIFT)
+#define BYTES_TO_CHUNKS(bytes) ((bytes) >> DMACHUNK_SHIFT)
+#define CHUNKS_TO_BYTES(chunks) ((chunks) << DMACHUNK_SHIFT)
+
+/*
+ * Two convenient macros to shift bitfields into/out of position.
+ *
+ * Observe that (mask & -mask) is (1 << low_set_bit_of(mask)).
+ * As long as mask is constant, we trust the compiler will change the
+ * multipy and divide into shifts.
+ */
+
+#define SHIFT_FIELD(val, mask) (((val) * ((mask) & -(mask))) & (mask))
+#define UNSHIFT_FIELD(val, mask) (((val) & (mask)) / ((mask) & -(mask)))
+
+/*
+ * dma_chan_desc is invariant information about a Lithium
+ * DMA channel.  There are two instances, li_comm1 and li_comm2.
+ *
+ * Note that the CCTL register fields are write ptr and read ptr, but what
+ * we care about are which pointer is updated by software and which by
+ * hardware.
+ */
+
+typedef struct dma_chan_desc {
+	int basereg;
+	int cfgreg;
+	int ctlreg;
+	int hwptrreg;
+	int swptrreg;
+	int ustreg;
+	int mscreg;
+	unsigned long swptrmask;
+	int ad1843_slot;
+	int direction;			/* LI_CCTL_DIR_IN/OUT */
+} dma_chan_desc_t;
+
+static const dma_chan_desc_t li_comm1 = {
+	LI_COMM1_BASE,			/* base register offset */
+	LI_COMM1_CFG,			/* config register offset */
+	LI_COMM1_CTL,			/* control register offset */
+	LI_COMM1_CTL + 0,		/* hw ptr reg offset (write ptr) */
+	LI_COMM1_CTL + 1,		/* sw ptr reg offset (read ptr) */
+	LI_AUDIO1_UST,			/* ust reg offset */
+	LI_AUDIO1_MSC,			/* msc reg offset */
+	LI_CCTL_RPTR,			/* sw ptr bitmask in ctlval */
+	2,				/* ad1843 serial slot */
+	LI_CCFG_DIR_IN			/* direction */
+};
+
+static const dma_chan_desc_t li_comm2 = {
+	LI_COMM2_BASE,			/* base register offset */
+	LI_COMM2_CFG,			/* config register offset */
+	LI_COMM2_CTL,			/* control register offset */
+	LI_COMM2_CTL + 1,		/* hw ptr reg offset (read ptr) */
+	LI_COMM2_CTL + 0,		/* sw ptr reg offset (writr ptr) */
+	LI_AUDIO2_UST,			/* ust reg offset */
+	LI_AUDIO2_MSC,			/* msc reg offset */
+	LI_CCTL_WPTR,			/* sw ptr bitmask in ctlval */
+	2,				/* ad1843 serial slot */
+	LI_CCFG_DIR_OUT			/* direction */
+};
+
+/*
+ * dma_chan is variable information about a Lithium DMA channel.
+ *
+ * The desc field points to invariant information.
+ * The lith field points to a lithium_t which is passed
+ * to li_read* and li_write* to access the registers.
+ * The *val fields shadow the lithium registers' contents.
+ */
+
+typedef struct dma_chan {
+	const dma_chan_desc_t *desc;
+	lithium_t      *lith;
+	unsigned long   baseval;
+	unsigned long	cfgval;
+	unsigned long	ctlval;
+} dma_chan_t;
+
+/*
+ * ustmsc is a UST/MSC pair (Unadjusted System Time/Media Stream Counter).
+ * UST is time in microseconds since the system booted, and MSC is a
+ * counter that increments with every audio sample.
+ */
+
+typedef struct ustmsc {
+	unsigned long long ust;
+	unsigned long msc;
+} ustmsc_t;
+
+/*
+ * li_ad1843_wait waits until lithium says the AD1843 register
+ * exchange is not busy.  Returns 0 on success, -EBUSY on timeout.
+ *
+ * Locking: must be called with lithium_lock held.
+ */
+
+static int li_ad1843_wait(lithium_t *lith)
+{
+	unsigned long later = jiffies + 2;
+	while (li_readl(lith, LI_CODEC_COMMAND) & LI_CC_BUSY)
+		if (time_after_eq(jiffies, later))
+			return -EBUSY;
+	return 0;
+}
+
+/*
+ * li_read_ad1843_reg returns the current contents of a 16 bit AD1843 register.
+ *
+ * Returns unsigned register value on success, -errno on failure.
+ */
+
+static int li_read_ad1843_reg(lithium_t *lith, int reg)
+{
+	int val;
+
+	ASSERT(!in_interrupt());
+	spin_lock(&lith->lock);
+	{
+		val = li_ad1843_wait(lith);
+		if (val == 0) {
+			li_writel(lith, LI_CODEC_COMMAND, LI_CC_DIR_RD | reg);
+			val = li_ad1843_wait(lith);
+		}
+		if (val == 0)
+			val = li_readl(lith, LI_CODEC_DATA);
+	}
+	spin_unlock(&lith->lock);
+
+	DBGXV("li_read_ad1843_reg(lith=0x%p, reg=%d) returns 0x%04x\n",
+	      lith, reg, val);
+
+	return val;
+}
+
+/*
+ * li_write_ad1843_reg writes the specified value to a 16 bit AD1843 register.
+ */
+
+static void li_write_ad1843_reg(lithium_t *lith, int reg, int newval)
+{
+	spin_lock(&lith->lock);
+	{
+		if (li_ad1843_wait(lith) == 0) {
+			li_writel(lith, LI_CODEC_DATA, newval);
+			li_writel(lith, LI_CODEC_COMMAND, LI_CC_DIR_WR | reg);
+		}
+	}
+	spin_unlock(&lith->lock);
+}
+
+/*
+ * li_setup_dma calculates all the register settings for DMA in a particular
+ * mode.  It takes too many arguments.
+ */
+
+static void li_setup_dma(dma_chan_t *chan,
+			 const dma_chan_desc_t *desc,
+			 lithium_t *lith,
+			 unsigned long buffer_paddr,
+			 int bufshift,
+			 int fragshift,
+			 int channels,
+			 int sampsize)
+{
+	unsigned long mode, format;
+	unsigned long size, tmask;
+
+	DBGEV("(chan=0x%p, desc=0x%p, lith=0x%p, buffer_paddr=0x%lx, "
+	     "bufshift=%d, fragshift=%d, channels=%d, sampsize=%d)\n",
+	     chan, desc, lith, buffer_paddr,
+	     bufshift, fragshift, channels, sampsize);
+
+	/* Reset the channel first. */
+
+	li_writel(lith, desc->ctlreg, LI_CCTL_RESET);
+
+	ASSERT(channels == 1 || channels == 2);
+	if (channels == 2)
+		mode = LI_CCFG_MODE_STEREO;
+	else
+		mode = LI_CCFG_MODE_MONO;
+	ASSERT(sampsize == 1 || sampsize == 2);
+	if (sampsize == 2)
+		format = LI_CCFG_FMT_16BIT;
+	else
+		format = LI_CCFG_FMT_8BIT;
+	chan->desc = desc;
+	chan->lith = lith;
+
+	/*
+	 * Lithium DMA address register takes a 40-bit physical
+	 * address, right-shifted by 8 so it fits in 32 bits.  Bit 37
+	 * must be set -- it enables cache coherence.
+	 */
+
+	ASSERT(!(buffer_paddr & 0xFF));
+	chan->baseval = (buffer_paddr >> 8) | 1 << (37 - 8);
+
+	chan->cfgval = ((chan->cfgval & ~LI_CCFG_LOCK) |
+			SHIFT_FIELD(desc->ad1843_slot, LI_CCFG_SLOT) |
+			desc->direction |
+			mode |
+			format);
+
+	size = bufshift - 6;
+	tmask = 13 - fragshift;		/* See Lithium DMA Notes above. */
+	ASSERT(size >= 2 && size <= 7);
+	ASSERT(tmask >= 1 && tmask <= 7);
+	chan->ctlval = ((chan->ctlval & ~LI_CCTL_RESET) |
+			SHIFT_FIELD(size, LI_CCTL_SIZE) |
+			(chan->ctlval & ~LI_CCTL_DMA_ENABLE) |
+			SHIFT_FIELD(tmask, LI_CCTL_TMASK) |
+			SHIFT_FIELD(0, LI_CCTL_TPTR));
+
+	DBGPV("basereg 0x%x = 0x%lx\n", desc->basereg, chan->baseval);
+	DBGPV("cfgreg 0x%x = 0x%lx\n", desc->cfgreg, chan->cfgval);
+	DBGPV("ctlreg 0x%x = 0x%lx\n", desc->ctlreg, chan->ctlval);
+
+	li_writel(lith, desc->basereg, chan->baseval);
+	li_writel(lith, desc->cfgreg, chan->cfgval);
+	li_writel(lith, desc->ctlreg, chan->ctlval);
+
+	DBGRV();
+}
+
+static void li_shutdown_dma(dma_chan_t *chan)
+{
+	lithium_t *lith = chan->lith;
+	void * lith1 = lith->page1;
+
+	DBGEV("(chan=0x%p)\n", chan);
+	
+	chan->ctlval &= ~LI_CCTL_DMA_ENABLE;
+	DBGPV("ctlreg 0x%x = 0x%lx\n", chan->desc->ctlreg, chan->ctlval);
+	li_writel(lith, chan->desc->ctlreg, chan->ctlval);
+
+	/*
+	 * Offset 0x500 on Lithium page 1 is an undocumented,
+	 * unsupported register that holds the zero sample value.
+	 * Lithium is supposed to output zero samples when DMA is
+	 * inactive, and repeat the last sample when DMA underflows.
+	 * But it has a bug, where, after underflow occurs, the zero
+	 * sample is not reset.
+	 *
+	 * I expect this to break in a future rev of Lithium.
+	 */
+
+	if (lith1 && chan->desc->direction == LI_CCFG_DIR_OUT)
+		* (volatile unsigned long *) (lith1 + 0x500) = 0;
+}
+
+/*
+ * li_activate_dma always starts dma at the beginning of the buffer.
+ *
+ * N.B., these may be called from interrupt.
+ */
+
+static __inline__ void li_activate_dma(dma_chan_t *chan)
+{
+	chan->ctlval |= LI_CCTL_DMA_ENABLE;
+	DBGPV("ctlval = 0x%lx\n", chan->ctlval);
+	li_writel(chan->lith, chan->desc->ctlreg, chan->ctlval);
+}
+
+static void li_deactivate_dma(dma_chan_t *chan)
+{
+	lithium_t *lith = chan->lith;
+	void * lith2 = lith->page2;
+
+	chan->ctlval &= ~(LI_CCTL_DMA_ENABLE | LI_CCTL_RPTR | LI_CCTL_WPTR);
+	DBGPV("ctlval = 0x%lx\n", chan->ctlval);
+	DBGPV("ctlreg 0x%x = 0x%lx\n", chan->desc->ctlreg, chan->ctlval);
+	li_writel(lith, chan->desc->ctlreg, chan->ctlval);
+
+	/*
+	 * Offsets 0x98 and 0x9C on Lithium page 2 are undocumented,
+	 * unsupported registers that are internal copies of the DMA
+	 * read and write pointers.  Because of a Lithium bug, these
+	 * registers aren't zeroed correctly when DMA is shut off.  So
+	 * we whack them directly.
+	 *
+	 * I expect this to break in a future rev of Lithium.
+	 */
+
+	if (lith2 && chan->desc->direction == LI_CCFG_DIR_OUT) {
+		* (volatile unsigned long *) (lith2 + 0x98) = 0;
+		* (volatile unsigned long *) (lith2 + 0x9C) = 0;
+	}
+}
+
+/*
+ * read/write the ring buffer pointers.  These routines' arguments and results
+ * are byte offsets from the beginning of the ring buffer.
+ */
+
+static __inline__ int li_read_swptr(dma_chan_t *chan)
+{
+	const unsigned long mask = chan->desc->swptrmask;
+
+	return CHUNKS_TO_BYTES(UNSHIFT_FIELD(chan->ctlval, mask));
+}
+
+static __inline__ int li_read_hwptr(dma_chan_t *chan)
+{
+	return CHUNKS_TO_BYTES(li_readb(chan->lith, chan->desc->hwptrreg));
+}
+
+static __inline__ void li_write_swptr(dma_chan_t *chan, int val)
+{
+	const unsigned long mask = chan->desc->swptrmask;
+
+	ASSERT(!(val & ~CHUNKS_TO_BYTES(0xFF)));
+	val = BYTES_TO_CHUNKS(val);
+	chan->ctlval = (chan->ctlval & ~mask) | SHIFT_FIELD(val, mask);
+	li_writeb(chan->lith, chan->desc->swptrreg, val);
+}
+
+/* li_read_USTMSC() returns a UST/MSC pair for the given channel. */
+
+static void li_read_USTMSC(dma_chan_t *chan, ustmsc_t *ustmsc)
+{
+	lithium_t *lith = chan->lith;
+	const dma_chan_desc_t *desc = chan->desc;
+	unsigned long now_low, now_high0, now_high1, chan_ust;
+
+	spin_lock(&lith->lock);
+	{
+		/*
+		 * retry until we do all five reads without the
+		 * high word changing.  (High word increments
+		 * every 2^32 microseconds, i.e., not often)
+		 */
+		do {
+			now_high0 = li_readl(lith, LI_UST_HIGH);
+			now_low = li_readl(lith, LI_UST_LOW);
+
+			/*
+			 * Lithium guarantees these two reads will be
+			 * atomic -- ust will not increment after msc
+			 * is read.
+			 */
+
+			ustmsc->msc = li_readl(lith, desc->mscreg);
+			chan_ust = li_readl(lith, desc->ustreg);
+
+			now_high1 = li_readl(lith, LI_UST_HIGH);
+		} while (now_high0 != now_high1);
+	}	
+	spin_unlock(&lith->lock);
+	ustmsc->ust = ((unsigned long long) now_high0 << 32 | chan_ust);
+}
+
+static void li_enable_interrupts(lithium_t *lith, unsigned int mask)
+{
+	DBGEV("(lith=0x%p, mask=0x%x)\n", lith, mask);
+
+	/* clear any already-pending interrupts. */
+
+	li_writel(lith, LI_INTR_STATUS, mask);
+
+	/* enable the interrupts. */
+
+	mask |= li_readl(lith, LI_INTR_MASK);
+	li_writel(lith, LI_INTR_MASK, mask);
+}
+
+static void li_disable_interrupts(lithium_t *lith, unsigned int mask)
+{
+	unsigned int keepmask;
+
+	DBGEV("(lith=0x%p, mask=0x%x)\n", lith, mask);
+
+	/* disable the interrupts */
+
+	keepmask = li_readl(lith, LI_INTR_MASK) & ~mask;
+	li_writel(lith, LI_INTR_MASK, keepmask);
+
+	/* clear any pending interrupts. */
+
+	li_writel(lith, LI_INTR_STATUS, mask);
+}
+
+/* Get the interrupt status and clear all pending interrupts. */
+
+static unsigned int li_get_clear_intr_status(lithium_t *lith)
+{
+	unsigned int status;
+
+	status = li_readl(lith, LI_INTR_STATUS);
+	li_writel(lith, LI_INTR_STATUS, ~0);
+	return status & li_readl(lith, LI_INTR_MASK);
+}
+
+static int li_init(lithium_t *lith)
+{
+	/* 1. System power supplies stabilize. */
+
+	/* 2. Assert the ~RESET signal. */
+
+	li_writel(lith, LI_HOST_CONTROLLER, LI_HC_RESET);
+	udelay(1);
+
+	/* 3. Deassert the ~RESET signal and enter a wait period to allow
+	   the AD1843 internal clocks and the external crystal oscillator
+	   to stabilize. */
+
+	li_writel(lith, LI_HOST_CONTROLLER, LI_HC_LINK_ENABLE);
+	udelay(1);
+
+	return 0;
+}
+
+/*****************************************************************************/
+/* AD1843 access */
+
+/*
+ * AD1843 bitfield definitions.  All are named as in the AD1843 data
+ * sheet, with ad1843_ prepended and individual bit numbers removed.
+ *
+ * E.g., bits LSS0 through LSS2 become ad1843_LSS.
+ *
+ * Only the bitfields we need are defined.
+ */
+
+typedef struct ad1843_bitfield {
+	char reg;
+	char lo_bit;
+	char nbits;
+} ad1843_bitfield_t;
+
+static const ad1843_bitfield_t
+	ad1843_PDNO   = {  0, 14,  1 },	/* Converter Power-Down Flag */
+	ad1843_INIT   = {  0, 15,  1 },	/* Clock Initialization Flag */
+	ad1843_RIG    = {  2,  0,  4 },	/* Right ADC Input Gain */
+	ad1843_RMGE   = {  2,  4,  1 },	/* Right ADC Mic Gain Enable */
+	ad1843_RSS    = {  2,  5,  3 },	/* Right ADC Source Select */
+	ad1843_LIG    = {  2,  8,  4 },	/* Left ADC Input Gain */
+	ad1843_LMGE   = {  2, 12,  1 },	/* Left ADC Mic Gain Enable */
+	ad1843_LSS    = {  2, 13,  3 },	/* Left ADC Source Select */
+	ad1843_RX1M   = {  4,  0,  5 },	/* Right Aux 1 Mix Gain/Atten */
+	ad1843_RX1MM  = {  4,  7,  1 },	/* Right Aux 1 Mix Mute */
+	ad1843_LX1M   = {  4,  8,  5 },	/* Left Aux 1 Mix Gain/Atten */
+	ad1843_LX1MM  = {  4, 15,  1 },	/* Left Aux 1 Mix Mute */
+	ad1843_RX2M   = {  5,  0,  5 },	/* Right Aux 2 Mix Gain/Atten */
+	ad1843_RX2MM  = {  5,  7,  1 },	/* Right Aux 2 Mix Mute */
+	ad1843_LX2M   = {  5,  8,  5 },	/* Left Aux 2 Mix Gain/Atten */
+	ad1843_LX2MM  = {  5, 15,  1 },	/* Left Aux 2 Mix Mute */
+	ad1843_RMCM   = {  7,  0,  5 },	/* Right Mic Mix Gain/Atten */
+	ad1843_RMCMM  = {  7,  7,  1 },	/* Right Mic Mix Mute */
+	ad1843_LMCM   = {  7,  8,  5 },	/* Left Mic Mix Gain/Atten */
+	ad1843_LMCMM  = {  7, 15,  1 },	/* Left Mic Mix Mute */
+	ad1843_HPOS   = {  8,  4,  1 },	/* Headphone Output Voltage Swing */
+	ad1843_HPOM   = {  8,  5,  1 },	/* Headphone Output Mute */
+	ad1843_RDA1G  = {  9,  0,  6 },	/* Right DAC1 Analog/Digital Gain */
+	ad1843_RDA1GM = {  9,  7,  1 },	/* Right DAC1 Analog Mute */
+	ad1843_LDA1G  = {  9,  8,  6 },	/* Left DAC1 Analog/Digital Gain */
+	ad1843_LDA1GM = {  9, 15,  1 },	/* Left DAC1 Analog Mute */
+	ad1843_RDA1AM = { 11,  7,  1 },	/* Right DAC1 Digital Mute */
+	ad1843_LDA1AM = { 11, 15,  1 },	/* Left DAC1 Digital Mute */
+	ad1843_ADLC   = { 15,  0,  2 },	/* ADC Left Sample Rate Source */
+	ad1843_ADRC   = { 15,  2,  2 },	/* ADC Right Sample Rate Source */
+	ad1843_DA1C   = { 15,  8,  2 },	/* DAC1 Sample Rate Source */
+	ad1843_C1C    = { 17,  0, 16 },	/* Clock 1 Sample Rate Select */
+	ad1843_C2C    = { 20,  0, 16 },	/* Clock 1 Sample Rate Select */
+	ad1843_DAADL  = { 25,  4,  2 },	/* Digital ADC Left Source Select */
+	ad1843_DAADR  = { 25,  6,  2 },	/* Digital ADC Right Source Select */
+	ad1843_DRSFLT = { 25, 15,  1 },	/* Digital Reampler Filter Mode */
+	ad1843_ADLF   = { 26,  0,  2 }, /* ADC Left Channel Data Format */
+	ad1843_ADRF   = { 26,  2,  2 }, /* ADC Right Channel Data Format */
+	ad1843_ADTLK  = { 26,  4,  1 },	/* ADC Transmit Lock Mode Select */
+	ad1843_SCF    = { 26,  7,  1 },	/* SCLK Frequency Select */
+	ad1843_DA1F   = { 26,  8,  2 },	/* DAC1 Data Format Select */
+	ad1843_DA1SM  = { 26, 14,  1 },	/* DAC1 Stereo/Mono Mode Select */
+	ad1843_ADLEN  = { 27,  0,  1 },	/* ADC Left Channel Enable */
+	ad1843_ADREN  = { 27,  1,  1 },	/* ADC Right Channel Enable */
+	ad1843_AAMEN  = { 27,  4,  1 },	/* Analog to Analog Mix Enable */
+	ad1843_ANAEN  = { 27,  7,  1 },	/* Analog Channel Enable */
+	ad1843_DA1EN  = { 27,  8,  1 },	/* DAC1 Enable */
+	ad1843_DA2EN  = { 27,  9,  1 },	/* DAC2 Enable */
+	ad1843_C1EN   = { 28, 11,  1 },	/* Clock Generator 1 Enable */
+	ad1843_C2EN   = { 28, 12,  1 },	/* Clock Generator 2 Enable */
+	ad1843_PDNI   = { 28, 15,  1 };	/* Converter Power Down */
+
+/*
+ * The various registers of the AD1843 use three different formats for
+ * specifying gain.  The ad1843_gain structure parameterizes the
+ * formats.
+ */
+
+typedef struct ad1843_gain {
+
+	int	negative;		/* nonzero if gain is negative. */
+	const ad1843_bitfield_t *lfield;
+	const ad1843_bitfield_t *rfield;
+
+} ad1843_gain_t;
+
+static const ad1843_gain_t ad1843_gain_RECLEV
+				= { 0, &ad1843_LIG,   &ad1843_RIG };
+static const ad1843_gain_t ad1843_gain_LINE
+				= { 1, &ad1843_LX1M,  &ad1843_RX1M };
+static const ad1843_gain_t ad1843_gain_CD
+				= { 1, &ad1843_LX2M,  &ad1843_RX2M };
+static const ad1843_gain_t ad1843_gain_MIC
+				= { 1, &ad1843_LMCM,  &ad1843_RMCM };
+static const ad1843_gain_t ad1843_gain_PCM
+				= { 1, &ad1843_LDA1G, &ad1843_RDA1G };
+
+/* read the current value of an AD1843 bitfield. */
+
+static int ad1843_read_bits(lithium_t *lith, const ad1843_bitfield_t *field)
+{
+	int w = li_read_ad1843_reg(lith, field->reg);
+	int val = w >> field->lo_bit & ((1 << field->nbits) - 1);
+
+	DBGXV("ad1843_read_bits(lith=0x%p, field->{%d %d %d}) returns 0x%x\n",
+	      lith, field->reg, field->lo_bit, field->nbits, val);
+
+	return val;
+}
+
+/*
+ * write a new value to an AD1843 bitfield and return the old value.
+ */
+
+static int ad1843_write_bits(lithium_t *lith,
+			     const ad1843_bitfield_t *field,
+			     int newval)
+{
+	int w = li_read_ad1843_reg(lith, field->reg);
+	int mask = ((1 << field->nbits) - 1) << field->lo_bit;
+	int oldval = (w & mask) >> field->lo_bit;
+	int newbits = (newval << field->lo_bit) & mask;
+	w = (w & ~mask) | newbits;
+	(void) li_write_ad1843_reg(lith, field->reg, w);
+
+	DBGXV("ad1843_write_bits(lith=0x%p, field->{%d %d %d}, val=0x%x) "
+	      "returns 0x%x\n",
+	      lith, field->reg, field->lo_bit, field->nbits, newval,
+	      oldval);
+
+	return oldval;
+}
+
+/*
+ * ad1843_read_multi reads multiple bitfields from the same AD1843
+ * register.  It uses a single read cycle to do it.  (Reading the
+ * ad1843 requires 256 bit times at 12.288 MHz, or nearly 20
+ * microseconds.)
+ *
+ * Called ike this.
+ *
+ *  ad1843_read_multi(lith, nfields,
+ *		      &ad1843_FIELD1, &val1,
+ *		      &ad1843_FIELD2, &val2, ...);
+ */
+
+static void ad1843_read_multi(lithium_t *lith, int argcount, ...)
+{
+	va_list ap;
+	const ad1843_bitfield_t *fp;
+	int w = 0, mask, *value, reg = -1;
+
+	va_start(ap, argcount);
+	while (--argcount >= 0) {
+		fp = va_arg(ap, const ad1843_bitfield_t *);
+		value = va_arg(ap, int *);
+		if (reg == -1) {
+			reg = fp->reg;
+			w = li_read_ad1843_reg(lith, reg);
+		}
+		ASSERT(reg == fp->reg);
+		mask = (1 << fp->nbits) - 1;
+		*value = w >> fp->lo_bit & mask;
+	}
+	va_end(ap);
+}
+
+/*
+ * ad1843_write_multi stores multiple bitfields into the same AD1843
+ * register.  It uses one read and one write cycle to do it.
+ *
+ * Called like this.
+ *
+ *  ad1843_write_multi(lith, nfields,
+ *		       &ad1843_FIELD1, val1,
+ *		       &ad1843_FIELF2, val2, ...);
+ */
+
+static void ad1843_write_multi(lithium_t *lith, int argcount, ...)
+{
+	va_list ap;
+	int reg;
+	const ad1843_bitfield_t *fp;
+	int value;
+	int w, m, mask, bits;
+
+	mask = 0;
+	bits = 0;
+	reg = -1;
+
+	va_start(ap, argcount);
+	while (--argcount >= 0) {
+		fp = va_arg(ap, const ad1843_bitfield_t *);
+		value = va_arg(ap, int);
+		if (reg == -1)
+			reg = fp->reg;
+		ASSERT(fp->reg == reg);
+		m = ((1 << fp->nbits) - 1) << fp->lo_bit;
+		mask |= m;
+		bits |= (value << fp->lo_bit) & m;
+	}
+	va_end(ap);
+	ASSERT(!(bits & ~mask));
+	if (~mask & 0xFFFF)
+		w = li_read_ad1843_reg(lith, reg);
+	else
+		w = 0;
+	w = (w & ~mask) | bits;
+	(void) li_write_ad1843_reg(lith, reg, w);
+}
+
+/*
+ * ad1843_get_gain reads the specified register and extracts the gain value
+ * using the supplied gain type.  It returns the gain in OSS format.
+ */
+
+static int ad1843_get_gain(lithium_t *lith, const ad1843_gain_t *gp)
+{
+	int lg, rg;
+	unsigned short mask = (1 << gp->lfield->nbits) - 1;
+
+	ad1843_read_multi(lith, 2, gp->lfield, &lg, gp->rfield, &rg);
+	if (gp->negative) {
+		lg = mask - lg;
+		rg = mask - rg;
+	}
+	lg = (lg * 100 + (mask >> 1)) / mask;
+	rg = (rg * 100 + (mask >> 1)) / mask;
+	return lg << 0 | rg << 8;
+}
+
+/*
+ * Set an audio channel's gain. Converts from OSS format to AD1843's
+ * format.
+ *
+ * Returns the new gain, which may be lower than the old gain.
+ */
+
+static int ad1843_set_gain(lithium_t *lith,
+			   const ad1843_gain_t *gp,
+			   int newval)
+{
+	unsigned short mask = (1 << gp->lfield->nbits) - 1;
+
+	int lg = newval >> 0 & 0xFF;
+	int rg = newval >> 8;
+	if (lg < 0 || lg > 100 || rg < 0 || rg > 100)
+		return -EINVAL;
+	lg = (lg * mask + (mask >> 1)) / 100;
+	rg = (rg * mask + (mask >> 1)) / 100;
+	if (gp->negative) {
+		lg = mask - lg;
+		rg = mask - rg;
+	}
+	ad1843_write_multi(lith, 2, gp->lfield, lg, gp->rfield, rg);
+	return ad1843_get_gain(lith, gp);
+}
+
+/* Returns the current recording source, in OSS format. */
+
+static int ad1843_get_recsrc(lithium_t *lith)
+{
+	int ls = ad1843_read_bits(lith, &ad1843_LSS);
+
+	switch (ls) {
+	case 1:
+		return SOUND_MASK_MIC;
+	case 2:
+		return SOUND_MASK_LINE;
+	case 3:
+		return SOUND_MASK_CD;
+	case 6:
+		return SOUND_MASK_PCM;
+	default:
+		ASSERT(0);
+		return -1;
+	}
+}
+
+/*
+ * Enable/disable digital resample mode in the AD1843.
+ *
+ * The AD1843 requires that ADL, ADR, DA1 and DA2 be powered down
+ * while switching modes.  So we save DA1's state (DA2's state is not
+ * interesting), power them down, switch into/out of resample mode,
+ * power them up, and restore state.
+ *
+ * This will cause audible glitches if D/A or A/D is going on, so the
+ * driver disallows that (in mixer_write_ioctl()).
+ *
+ * The open question is, is this worth doing?  I'm leaving it in,
+ * because it's written, but...
+ */
+
+static void ad1843_set_resample_mode(lithium_t *lith, int onoff)
+{
+	/* Save DA1 mute and gain (addr 9 is DA1 analog gain/attenuation) */
+	int save_da1 = li_read_ad1843_reg(lith, 9);
+
+	/* Power down A/D and D/A. */
+	ad1843_write_multi(lith, 4,
+			   &ad1843_DA1EN, 0,
+			   &ad1843_DA2EN, 0,
+			   &ad1843_ADLEN, 0,
+			   &ad1843_ADREN, 0);
+
+	/* Switch mode */
+	ASSERT(onoff == 0 || onoff == 1);
+	ad1843_write_bits(lith, &ad1843_DRSFLT, onoff);
+
+ 	/* Power up A/D and D/A. */
+	ad1843_write_multi(lith, 3,
+			   &ad1843_DA1EN, 1,
+			   &ad1843_ADLEN, 1,
+			   &ad1843_ADREN, 1);
+
+	/* Restore DA1 mute and gain. */
+	li_write_ad1843_reg(lith, 9, save_da1);
+}
+
+/*
+ * Set recording source.  Arg newsrc specifies an OSS channel mask.
+ *
+ * The complication is that when we switch into/out of loopback mode
+ * (i.e., src = SOUND_MASK_PCM), we change the AD1843 into/out of
+ * digital resampling mode.
+ *
+ * Returns newsrc on success, -errno on failure.
+ */
+
+static int ad1843_set_recsrc(lithium_t *lith, int newsrc)
+{
+	int bits;
+	int oldbits;
+
+	switch (newsrc) {
+	case SOUND_MASK_PCM:
+		bits = 6;
+		break;
+
+	case SOUND_MASK_MIC:
+		bits = 1;
+		break;
+
+	case SOUND_MASK_LINE:
+		bits = 2;
+		break;
+
+	case SOUND_MASK_CD:
+		bits = 3;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	oldbits = ad1843_read_bits(lith, &ad1843_LSS);
+	if (newsrc == SOUND_MASK_PCM && oldbits != 6) {
+		DBGP("enabling digital resample mode\n");
+		ad1843_set_resample_mode(lith, 1);
+		ad1843_write_multi(lith, 2,
+				   &ad1843_DAADL, 2,
+				   &ad1843_DAADR, 2);
+	} else if (newsrc != SOUND_MASK_PCM && oldbits == 6) {
+		DBGP("disabling digital resample mode\n");
+		ad1843_set_resample_mode(lith, 0);
+		ad1843_write_multi(lith, 2,
+				   &ad1843_DAADL, 0,
+				   &ad1843_DAADR, 0);
+	}
+	ad1843_write_multi(lith, 2, &ad1843_LSS, bits, &ad1843_RSS, bits);
+	return newsrc;
+}
+
+/*
+ * Return current output sources, in OSS format.
+ */
+
+static int ad1843_get_outsrc(lithium_t *lith)
+{
+	int pcm, line, mic, cd;
+
+	pcm  = ad1843_read_bits(lith, &ad1843_LDA1GM) ? 0 : SOUND_MASK_PCM;
+	line = ad1843_read_bits(lith, &ad1843_LX1MM)  ? 0 : SOUND_MASK_LINE;
+	cd   = ad1843_read_bits(lith, &ad1843_LX2MM)  ? 0 : SOUND_MASK_CD;
+	mic  = ad1843_read_bits(lith, &ad1843_LMCMM)  ? 0 : SOUND_MASK_MIC;
+
+	return pcm | line | cd | mic;
+}
+
+/*
+ * Set output sources.  Arg is a mask of active sources in OSS format.
+ *
+ * Returns source mask on success, -errno on failure.
+ */
+
+static int ad1843_set_outsrc(lithium_t *lith, int mask)
+{
+	int pcm, line, mic, cd;
+
+	if (mask & ~(SOUND_MASK_PCM | SOUND_MASK_LINE |
+		     SOUND_MASK_CD | SOUND_MASK_MIC))
+		return -EINVAL;
+	pcm  = (mask & SOUND_MASK_PCM)  ? 0 : 1;
+	line = (mask & SOUND_MASK_LINE) ? 0 : 1;
+	mic  = (mask & SOUND_MASK_MIC)  ? 0 : 1;
+	cd   = (mask & SOUND_MASK_CD)   ? 0 : 1;
+
+	ad1843_write_multi(lith, 2, &ad1843_LDA1GM, pcm, &ad1843_RDA1GM, pcm);
+	ad1843_write_multi(lith, 2, &ad1843_LX1MM, line, &ad1843_RX1MM, line);
+	ad1843_write_multi(lith, 2, &ad1843_LX2MM, cd,   &ad1843_RX2MM, cd);
+	ad1843_write_multi(lith, 2, &ad1843_LMCMM, mic,  &ad1843_RMCMM, mic);
+
+	return mask;
+}
+
+/* Setup ad1843 for D/A conversion. */
+
+static void ad1843_setup_dac(lithium_t *lith,
+			     int framerate,
+			     int fmt,
+			     int channels)
+{
+	int ad_fmt = 0, ad_mode = 0;
+
+	DBGEV("(lith=0x%p, framerate=%d, fmt=%d, channels=%d)\n",
+	      lith, framerate, fmt, channels);
+
+	switch (fmt) {
+	case AFMT_S8:		ad_fmt = 1; break;
+	case AFMT_U8:		ad_fmt = 1; break;
+	case AFMT_S16_LE:	ad_fmt = 1; break;
+	case AFMT_MU_LAW:	ad_fmt = 2; break;
+	case AFMT_A_LAW:	ad_fmt = 3; break;
+	default:		ASSERT(0);
+	}
+
+	switch (channels) {
+	case 2:			ad_mode = 0; break;
+	case 1:			ad_mode = 1; break;
+	default:		ASSERT(0);
+	}
+		
+	DBGPV("ad_mode = %d, ad_fmt = %d\n", ad_mode, ad_fmt);
+	ASSERT(framerate >= 4000 && framerate <= 49000);
+	ad1843_write_bits(lith, &ad1843_C1C, framerate);
+	ad1843_write_multi(lith, 2,
+			   &ad1843_DA1SM, ad_mode, &ad1843_DA1F, ad_fmt);
+}
+
+static void ad1843_shutdown_dac(lithium_t *lith)
+{
+	ad1843_write_bits(lith, &ad1843_DA1F, 1);
+}
+
+static void ad1843_setup_adc(lithium_t *lith, int framerate, int fmt, int channels)
+{
+	int da_fmt = 0;
+
+	DBGEV("(lith=0x%p, framerate=%d, fmt=%d, channels=%d)\n",
+	      lith, framerate, fmt, channels);
+
+	switch (fmt) {
+	case AFMT_S8:		da_fmt = 1; break;
+	case AFMT_U8:		da_fmt = 1; break;
+	case AFMT_S16_LE:	da_fmt = 1; break;
+	case AFMT_MU_LAW:	da_fmt = 2; break;
+	case AFMT_A_LAW:	da_fmt = 3; break;
+	default:		ASSERT(0);
+	}
+
+	DBGPV("da_fmt = %d\n", da_fmt);
+	ASSERT(framerate >= 4000 && framerate <= 49000);
+	ad1843_write_bits(lith, &ad1843_C2C, framerate);
+	ad1843_write_multi(lith, 2,
+			   &ad1843_ADLF, da_fmt, &ad1843_ADRF, da_fmt);
+}
+
+static void ad1843_shutdown_adc(lithium_t *lith)
+{
+	/* nothing to do */
+}
+
+/*
+ * Fully initialize the ad1843.  As described in the AD1843 data
+ * sheet, section "START-UP SEQUENCE".  The numbered comments are
+ * subsection headings from the data sheet.  See the data sheet, pages
+ * 52-54, for more info.
+ *
+ * return 0 on success, -errno on failure.  */
+
+static int __init ad1843_init(lithium_t *lith)
+{
+	unsigned long later;
+	int err;
+
+	err = li_init(lith);
+	if (err)
+		return err;
+
+	if (ad1843_read_bits(lith, &ad1843_INIT) != 0) {
+		printk(KERN_ERR "vwsnd sound: AD1843 won't initialize\n");
+		return -EIO;
+	}
+
+	ad1843_write_bits(lith, &ad1843_SCF, 1);
+
+	/* 4. Put the conversion resources into standby. */
+
+	ad1843_write_bits(lith, &ad1843_PDNI, 0);
+	later = jiffies + HZ / 2;	/* roughly half a second */
+	DBGDO(shut_up++);
+	while (ad1843_read_bits(lith, &ad1843_PDNO)) {
+		if (time_after(jiffies, later)) {
+			printk(KERN_ERR
+			       "vwsnd audio: AD1843 won't power up\n");
+			return -EIO;
+		}
+		schedule();
+	}
+	DBGDO(shut_up--);
+
+	/* 5. Power up the clock generators and enable clock output pins. */
+
+	ad1843_write_multi(lith, 2, &ad1843_C1EN, 1, &ad1843_C2EN, 1);
+
+	/* 6. Configure conversion resources while they are in standby. */
+
+ 	/* DAC1 uses clock 1 as source, ADC uses clock 2.  Always. */
+
+	ad1843_write_multi(lith, 3,
+			   &ad1843_DA1C, 1,
+			   &ad1843_ADLC, 2,
+			   &ad1843_ADRC, 2);
+
+	/* 7. Enable conversion resources. */
+
+	ad1843_write_bits(lith, &ad1843_ADTLK, 1);
+	ad1843_write_multi(lith, 5,
+			   &ad1843_ANAEN, 1,
+			   &ad1843_AAMEN, 1,
+			   &ad1843_DA1EN, 1,
+			   &ad1843_ADLEN, 1,
+			   &ad1843_ADREN, 1);
+
+	/* 8. Configure conversion resources while they are enabled. */
+
+	ad1843_write_bits(lith, &ad1843_DA1C, 1);
+
+	/* Unmute all channels. */
+
+	ad1843_set_outsrc(lith,
+			  (SOUND_MASK_PCM | SOUND_MASK_LINE |
+			   SOUND_MASK_MIC | SOUND_MASK_CD));
+	ad1843_write_multi(lith, 2, &ad1843_LDA1AM, 0, &ad1843_RDA1AM, 0);
+
+	/* Set default recording source to Line In and set
+	 * mic gain to +20 dB.
+	 */
+
+	ad1843_set_recsrc(lith, SOUND_MASK_LINE);
+	ad1843_write_multi(lith, 2, &ad1843_LMGE, 1, &ad1843_RMGE, 1);
+
+	/* Set Speaker Out level to +/- 4V and unmute it. */
+
+	ad1843_write_multi(lith, 2, &ad1843_HPOS, 1, &ad1843_HPOM, 0);
+
+	return 0;
+}
+
+/*****************************************************************************/
+/* PCM I/O */
+
+#define READ_INTR_MASK  (LI_INTR_COMM1_TRIG | LI_INTR_COMM1_OVERFLOW)
+#define WRITE_INTR_MASK (LI_INTR_COMM2_TRIG | LI_INTR_COMM2_UNDERFLOW)
+
+typedef enum vwsnd_port_swstate {	/* software state */
+	SW_OFF,
+	SW_INITIAL,
+	SW_RUN,
+	SW_DRAIN,
+} vwsnd_port_swstate_t;
+
+typedef enum vwsnd_port_hwstate {	/* hardware state */
+	HW_STOPPED,
+	HW_RUNNING,
+} vwsnd_port_hwstate_t;
+
+/*
+ * These flags are read by ISR, but only written at baseline.
+ */
+
+typedef enum vwsnd_port_flags {
+	DISABLED = 1 << 0,
+	ERFLOWN  = 1 << 1,		/* overflown or underflown */
+	HW_BUSY  = 1 << 2,
+} vwsnd_port_flags_t;
+
+/*
+ * vwsnd_port is the per-port data structure.  Each device has two
+ * ports, one for input and one for output.
+ *
+ * Locking:
+ *
+ *	port->lock protects: hwstate, flags, swb_[iu]_avail.
+ *
+ *	devc->io_mutex protects: swstate, sw_*, swb_[iu]_idx.
+ *
+ *	everything else is only written by open/release or
+ *	pcm_{setup,shutdown}(), which are serialized by a
+ *	combination of devc->open_mutex and devc->io_mutex.
+ */
+
+typedef struct vwsnd_port {
+
+	spinlock_t	lock;
+	wait_queue_head_t queue;
+	vwsnd_port_swstate_t swstate;
+	vwsnd_port_hwstate_t hwstate;
+	vwsnd_port_flags_t flags;
+
+	int		sw_channels;
+	int		sw_samplefmt;
+	int		sw_framerate;
+	int		sample_size;
+	int		frame_size;
+	unsigned int	zero_word;	/* zero for the sample format */
+
+	int		sw_fragshift;
+	int		sw_fragcount;
+	int		sw_subdivshift;
+
+	unsigned int	hw_fragshift;
+	unsigned int	hw_fragsize;
+	unsigned int	hw_fragcount;
+
+	int		hwbuf_size;
+	unsigned long	hwbuf_paddr;
+	unsigned long	hwbuf_vaddr;
+	void *		hwbuf;		/* hwbuf == hwbuf_vaddr */
+	int		hwbuf_max;	/* max bytes to preload */
+
+	void *		swbuf;
+	unsigned int	swbuf_size;	/* size in bytes */
+	unsigned int	swb_u_idx;	/* index of next user byte */
+	unsigned int	swb_i_idx;	/* index of next intr byte */
+	unsigned int	swb_u_avail;	/* # bytes avail to user */
+	unsigned int	swb_i_avail;	/* # bytes avail to intr */
+
+	dma_chan_t	chan;
+
+	/* Accounting */
+
+	int		byte_count;
+	int		frag_count;
+	int		MSC_offset;
+
+} vwsnd_port_t;
+
+/* vwsnd_dev is the per-device data structure. */
+
+typedef struct vwsnd_dev {
+	struct vwsnd_dev *next_dev;
+	int		audio_minor;	/* minor number of audio device */
+	int		mixer_minor;	/* minor number of mixer device */
+
+	struct mutex open_mutex;
+	struct mutex io_mutex;
+	struct mutex mix_mutex;
+	fmode_t		open_mode;
+	wait_queue_head_t open_wait;
+
+	lithium_t	lith;
+
+	vwsnd_port_t	rport;
+	vwsnd_port_t	wport;
+} vwsnd_dev_t;
+
+static vwsnd_dev_t *vwsnd_dev_list;	/* linked list of all devices */
+
+static atomic_t vwsnd_use_count = ATOMIC_INIT(0);
+
+# define INC_USE_COUNT (atomic_inc(&vwsnd_use_count))
+# define DEC_USE_COUNT (atomic_dec(&vwsnd_use_count))
+# define IN_USE        (atomic_read(&vwsnd_use_count) != 0)
+
+/*
+ * Lithium can only DMA multiples of 32 bytes.  Its DMA buffer may
+ * be up to 8 Kb.  This driver always uses 8 Kb.
+ *
+ * Memory bug workaround -- I'm not sure what's going on here, but
+ * somehow pcm_copy_out() was triggering segv's going on to the next
+ * page of the hw buffer.  So, I make the hw buffer one size bigger
+ * than we actually use.  That way, the following page is allocated
+ * and mapped, and no error.  I suspect that something is broken
+ * in Cobalt, but haven't really investigated.  HBO is the actual
+ * size of the buffer, and HWBUF_ORDER is what we allocate.
+ */
+
+#define HWBUF_SHIFT 13
+#define HWBUF_SIZE (1 << HWBUF_SHIFT)
+# define HBO         (HWBUF_SHIFT > PAGE_SHIFT ? HWBUF_SHIFT - PAGE_SHIFT : 0)
+# define HWBUF_ORDER (HBO + 1)		/* next size bigger */
+#define MIN_SPEED 4000
+#define MAX_SPEED 49000
+
+#define MIN_FRAGSHIFT			(DMACHUNK_SHIFT + 1)
+#define MAX_FRAGSHIFT			(PAGE_SHIFT)
+#define MIN_FRAGSIZE			(1 << MIN_FRAGSHIFT)
+#define MAX_FRAGSIZE			(1 << MAX_FRAGSHIFT)
+#define MIN_FRAGCOUNT(fragsize)		3
+#define MAX_FRAGCOUNT(fragsize)		(32 * PAGE_SIZE / (fragsize))
+#define DEFAULT_FRAGSHIFT		12
+#define DEFAULT_FRAGCOUNT		16
+#define DEFAULT_SUBDIVSHIFT		0
+
+/*
+ * The software buffer (swbuf) is a ring buffer shared between user
+ * level and interrupt level.  Each level owns some of the bytes in
+ * the buffer, and may give bytes away by calling swb_inc_{u,i}().
+ * User level calls _u for user, and interrupt level calls _i for
+ * interrupt.
+ *
+ * port->swb_{u,i}_avail is the number of bytes available to that level.
+ *
+ * port->swb_{u,i}_idx is the index of the first available byte in the
+ * buffer.
+ *
+ * Each level calls swb_inc_{u,i}() to atomically increment its index,
+ * recalculate the number of bytes available for both sides, and
+ * return the number of bytes available.  Since each side can only
+ * give away bytes, the other side can only increase the number of
+ * bytes available to this side.  Each side updates its own index
+ * variable, swb_{u,i}_idx, so no lock is needed to read it.
+ *
+ * To query the number of bytes available, call swb_inc_{u,i} with an
+ * increment of zero.
+ */
+
+static __inline__ unsigned int __swb_inc_u(vwsnd_port_t *port, int inc)
+{
+	if (inc) {
+		port->swb_u_idx += inc;
+		port->swb_u_idx %= port->swbuf_size;
+		port->swb_u_avail -= inc;
+		port->swb_i_avail += inc;
+	}
+	return port->swb_u_avail;
+}
+
+static __inline__ unsigned int swb_inc_u(vwsnd_port_t *port, int inc)
+{
+	unsigned long flags;
+	unsigned int ret;
+
+	spin_lock_irqsave(&port->lock, flags);
+	{
+		ret = __swb_inc_u(port, inc);
+	}
+	spin_unlock_irqrestore(&port->lock, flags);
+	return ret;
+}
+
+static __inline__ unsigned int __swb_inc_i(vwsnd_port_t *port, int inc)
+{
+	if (inc) {
+		port->swb_i_idx += inc;
+		port->swb_i_idx %= port->swbuf_size;
+		port->swb_i_avail -= inc;
+		port->swb_u_avail += inc;
+	}
+	return port->swb_i_avail;
+}
+
+static __inline__ unsigned int swb_inc_i(vwsnd_port_t *port, int inc)
+{
+	unsigned long flags;
+	unsigned int ret;
+
+	spin_lock_irqsave(&port->lock, flags);
+	{
+		ret = __swb_inc_i(port, inc);
+	}
+	spin_unlock_irqrestore(&port->lock, flags);
+	return ret;
+}
+
+/*
+ * pcm_setup - this routine initializes all port state after
+ * mode-setting ioctls have been done, but before the first I/O is
+ * done.
+ *
+ * Locking: called with devc->io_mutex held.
+ *
+ * Returns 0 on success, -errno on failure.
+ */
+
+static int pcm_setup(vwsnd_dev_t *devc,
+		     vwsnd_port_t *rport,
+		     vwsnd_port_t *wport)
+{
+	vwsnd_port_t *aport = rport ? rport : wport;
+	int sample_size;
+	unsigned int zero_word;
+
+	DBGEV("(devc=0x%p, rport=0x%p, wport=0x%p)\n", devc, rport, wport);
+
+	ASSERT(aport != NULL);
+	if (aport->swbuf != NULL)
+		return 0;
+	switch (aport->sw_samplefmt) {
+	case AFMT_MU_LAW:
+		sample_size = 1;
+		zero_word = 0xFFFFFFFF ^ 0x80808080;
+		break;
+
+	case AFMT_A_LAW:
+		sample_size = 1;
+		zero_word = 0xD5D5D5D5 ^ 0x80808080;
+		break;
+
+	case AFMT_U8:
+		sample_size = 1;
+		zero_word = 0x80808080;
+		break;
+
+	case AFMT_S8:
+		sample_size = 1;
+		zero_word = 0x00000000;
+		break;
+
+	case AFMT_S16_LE:
+		sample_size = 2;
+		zero_word = 0x00000000;
+		break;
+
+	default:
+		sample_size = 0;	/* prevent compiler warning */
+		zero_word = 0;
+		ASSERT(0);
+	}
+	aport->sample_size  = sample_size;
+	aport->zero_word    = zero_word;
+	aport->frame_size   = aport->sw_channels * aport->sample_size;
+	aport->hw_fragshift = aport->sw_fragshift - aport->sw_subdivshift;
+	aport->hw_fragsize  = 1 << aport->hw_fragshift;
+	aport->hw_fragcount = aport->sw_fragcount << aport->sw_subdivshift;
+	ASSERT(aport->hw_fragsize >= MIN_FRAGSIZE);
+	ASSERT(aport->hw_fragsize <= MAX_FRAGSIZE);
+	ASSERT(aport->hw_fragcount >= MIN_FRAGCOUNT(aport->hw_fragsize));
+	ASSERT(aport->hw_fragcount <= MAX_FRAGCOUNT(aport->hw_fragsize));
+	if (rport) {
+		int hwfrags, swfrags;
+		rport->hwbuf_max = aport->hwbuf_size - DMACHUNK_SIZE;
+		hwfrags = rport->hwbuf_max >> aport->hw_fragshift;
+		swfrags = aport->hw_fragcount - hwfrags;
+		if (swfrags < 2)
+			swfrags = 2;
+		rport->swbuf_size = swfrags * aport->hw_fragsize;
+		DBGPV("hwfrags = %d, swfrags = %d\n", hwfrags, swfrags);
+		DBGPV("read hwbuf_max = %d, swbuf_size = %d\n",
+		     rport->hwbuf_max, rport->swbuf_size);
+	}
+	if (wport) {
+		int hwfrags, swfrags;
+		int total_bytes = aport->hw_fragcount * aport->hw_fragsize;
+		wport->hwbuf_max = aport->hwbuf_size - DMACHUNK_SIZE;
+		if (wport->hwbuf_max > total_bytes)
+			wport->hwbuf_max = total_bytes;
+		hwfrags = wport->hwbuf_max >> aport->hw_fragshift;
+		DBGPV("hwfrags = %d\n", hwfrags);
+		swfrags = aport->hw_fragcount - hwfrags;
+		if (swfrags < 2)
+			swfrags = 2;
+		wport->swbuf_size = swfrags * aport->hw_fragsize;
+		DBGPV("hwfrags = %d, swfrags = %d\n", hwfrags, swfrags);
+		DBGPV("write hwbuf_max = %d, swbuf_size = %d\n",
+		     wport->hwbuf_max, wport->swbuf_size);
+	}
+
+	aport->swb_u_idx    = 0;
+	aport->swb_i_idx    = 0;
+	aport->byte_count   = 0;
+
+	/*
+	 * Is this a Cobalt bug?  We need to make this buffer extend
+	 * one page further than we actually use -- somehow memcpy
+	 * causes an exceptoin otherwise.  I suspect there's a bug in
+	 * Cobalt (or somewhere) where it's generating a fault on a
+	 * speculative load or something.  Obviously, I haven't taken
+	 * the time to track it down.
+	 */
+
+	aport->swbuf        = vmalloc(aport->swbuf_size + PAGE_SIZE);
+	if (!aport->swbuf)
+		return -ENOMEM;
+	if (rport && wport) {
+		ASSERT(aport == rport);
+		ASSERT(wport->swbuf == NULL);
+		/* One extra page - see comment above. */
+		wport->swbuf = vmalloc(aport->swbuf_size + PAGE_SIZE);
+		if (!wport->swbuf) {
+			vfree(aport->swbuf);
+			aport->swbuf = NULL;
+			return -ENOMEM;
+		}
+		wport->sample_size  = rport->sample_size;
+		wport->zero_word    = rport->zero_word;
+		wport->frame_size   = rport->frame_size;
+		wport->hw_fragshift = rport->hw_fragshift;
+		wport->hw_fragsize  = rport->hw_fragsize;
+		wport->hw_fragcount = rport->hw_fragcount;
+		wport->swbuf_size   = rport->swbuf_size;
+		wport->hwbuf_max    = rport->hwbuf_max;
+		wport->swb_u_idx    = rport->swb_u_idx;
+		wport->swb_i_idx    = rport->swb_i_idx;
+		wport->byte_count   = rport->byte_count;
+	}
+	if (rport) {
+		rport->swb_u_avail = 0;
+		rport->swb_i_avail = rport->swbuf_size;
+		rport->swstate = SW_RUN;
+		li_setup_dma(&rport->chan,
+			     &li_comm1,
+			     &devc->lith,
+			     rport->hwbuf_paddr,
+			     HWBUF_SHIFT,
+			     rport->hw_fragshift,
+			     rport->sw_channels,
+			     rport->sample_size);
+		ad1843_setup_adc(&devc->lith,
+				 rport->sw_framerate,
+				 rport->sw_samplefmt,
+				 rport->sw_channels);
+		li_enable_interrupts(&devc->lith, READ_INTR_MASK);
+		if (!(rport->flags & DISABLED)) {
+			ustmsc_t ustmsc;
+			rport->hwstate = HW_RUNNING;
+			li_activate_dma(&rport->chan);
+			li_read_USTMSC(&rport->chan, &ustmsc);
+			rport->MSC_offset = ustmsc.msc;
+		}
+	}
+	if (wport) {
+		if (wport->hwbuf_max > wport->swbuf_size)
+			wport->hwbuf_max = wport->swbuf_size;
+		wport->flags &= ~ERFLOWN;
+		wport->swb_u_avail = wport->swbuf_size;
+		wport->swb_i_avail = 0;
+		wport->swstate = SW_RUN;
+		li_setup_dma(&wport->chan,
+			     &li_comm2,
+			     &devc->lith,
+			     wport->hwbuf_paddr,
+			     HWBUF_SHIFT,
+			     wport->hw_fragshift,
+			     wport->sw_channels,
+			     wport->sample_size);
+		ad1843_setup_dac(&devc->lith,
+				 wport->sw_framerate,
+				 wport->sw_samplefmt,
+				 wport->sw_channels);
+		li_enable_interrupts(&devc->lith, WRITE_INTR_MASK);
+	}
+	DBGRV();
+	return 0;
+}
+
+/*
+ * pcm_shutdown_port - shut down one port (direction) for PCM I/O.
+ * Only called from pcm_shutdown.
+ */
+
+static void pcm_shutdown_port(vwsnd_dev_t *devc,
+			      vwsnd_port_t *aport,
+			      unsigned int mask)
+{
+	unsigned long flags;
+	vwsnd_port_hwstate_t hwstate;
+	DECLARE_WAITQUEUE(wait, current);
+
+	aport->swstate = SW_INITIAL;
+	add_wait_queue(&aport->queue, &wait);
+	while (1) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		spin_lock_irqsave(&aport->lock, flags);
+		{
+			hwstate = aport->hwstate;
+		}		
+		spin_unlock_irqrestore(&aport->lock, flags);
+		if (hwstate == HW_STOPPED)
+			break;
+		schedule();
+	}
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&aport->queue, &wait);
+	li_disable_interrupts(&devc->lith, mask);
+	if (aport == &devc->rport)
+		ad1843_shutdown_adc(&devc->lith);
+	else /* aport == &devc->wport) */
+		ad1843_shutdown_dac(&devc->lith);
+	li_shutdown_dma(&aport->chan);
+	vfree(aport->swbuf);
+	aport->swbuf = NULL;
+	aport->byte_count = 0;
+}
+
+/*
+ * pcm_shutdown undoes what pcm_setup did.
+ * Also sets the ports' swstate to newstate.
+ */
+
+static void pcm_shutdown(vwsnd_dev_t *devc,
+			 vwsnd_port_t *rport,
+			 vwsnd_port_t *wport)
+{
+	DBGEV("(devc=0x%p, rport=0x%p, wport=0x%p)\n", devc, rport, wport);
+
+	if (rport && rport->swbuf) {
+		DBGPV("shutting down rport\n");
+		pcm_shutdown_port(devc, rport, READ_INTR_MASK);
+	}
+	if (wport && wport->swbuf) {
+		DBGPV("shutting down wport\n");
+		pcm_shutdown_port(devc, wport, WRITE_INTR_MASK);
+	}
+	DBGRV();
+}
+
+static void pcm_copy_in(vwsnd_port_t *rport, int swidx, int hwidx, int nb)
+{
+	char *src = rport->hwbuf + hwidx;
+	char *dst = rport->swbuf + swidx;
+	int fmt = rport->sw_samplefmt;
+
+	DBGPV("swidx = %d, hwidx = %d\n", swidx, hwidx);
+	ASSERT(rport->hwbuf != NULL);
+	ASSERT(rport->swbuf != NULL);
+	ASSERT(nb > 0 && (nb % 32) == 0);
+	ASSERT(swidx % 32 == 0 && hwidx % 32 == 0);
+	ASSERT(swidx >= 0 && swidx + nb <= rport->swbuf_size);
+	ASSERT(hwidx >= 0 && hwidx + nb <= rport->hwbuf_size);
+
+	if (fmt == AFMT_MU_LAW || fmt == AFMT_A_LAW || fmt == AFMT_S8) {
+
+		/* See Sample Format Notes above. */
+
+		char *end = src + nb;
+		while (src < end)
+			*dst++ = *src++ ^ 0x80;
+	} else
+		memcpy(dst, src, nb);
+}
+
+static void pcm_copy_out(vwsnd_port_t *wport, int swidx, int hwidx, int nb)
+{
+	char *src = wport->swbuf + swidx;
+	char *dst = wport->hwbuf + hwidx;
+	int fmt = wport->sw_samplefmt;
+
+	ASSERT(nb > 0 && (nb % 32) == 0);
+	ASSERT(wport->hwbuf != NULL);
+	ASSERT(wport->swbuf != NULL);
+	ASSERT(swidx % 32 == 0 && hwidx % 32 == 0);
+	ASSERT(swidx >= 0 && swidx + nb <= wport->swbuf_size);
+	ASSERT(hwidx >= 0 && hwidx + nb <= wport->hwbuf_size);
+	if (fmt == AFMT_MU_LAW || fmt == AFMT_A_LAW || fmt == AFMT_S8) {
+
+		/* See Sample Format Notes above. */
+
+		char *end = src + nb;
+		while (src < end)
+			*dst++ = *src++ ^ 0x80;
+	} else
+		memcpy(dst, src, nb);
+}
+
+/*
+ * pcm_output() is called both from baselevel and from interrupt level.
+ * This is where audio frames are copied into the hardware-accessible
+ * ring buffer.
+ *
+ * Locking note: The part of this routine that figures out what to do
+ * holds wport->lock.  The longer part releases wport->lock, but sets
+ * wport->flags & HW_BUSY.  Afterward, it reacquires wport->lock, and
+ * checks for more work to do.
+ *
+ * If another thread calls pcm_output() while HW_BUSY is set, it
+ * returns immediately, knowing that the thread that set HW_BUSY will
+ * look for more work to do before returning.
+ *
+ * This has the advantage that port->lock is held for several short
+ * periods instead of one long period.  Also, when pcm_output is
+ * called from base level, it reenables interrupts.
+ */
+
+static void pcm_output(vwsnd_dev_t *devc, int erflown, int nb)
+{
+	vwsnd_port_t *wport = &devc->wport;
+	const int hwmax  = wport->hwbuf_max;
+	const int hwsize = wport->hwbuf_size;
+	const int swsize = wport->swbuf_size;
+	const int fragsize = wport->hw_fragsize;
+	unsigned long iflags;
+
+	DBGEV("(devc=0x%p, erflown=%d, nb=%d)\n", devc, erflown, nb);
+	spin_lock_irqsave(&wport->lock, iflags);
+	if (erflown)
+		wport->flags |= ERFLOWN;
+	(void) __swb_inc_u(wport, nb);
+	if (wport->flags & HW_BUSY) {
+		spin_unlock_irqrestore(&wport->lock, iflags);
+		DBGPV("returning: HW BUSY\n");
+		return;
+	}
+	if (wport->flags & DISABLED) {
+		spin_unlock_irqrestore(&wport->lock, iflags);
+		DBGPV("returning: DISABLED\n");
+		return;
+	}
+	wport->flags |= HW_BUSY;
+	while (1) {
+		int swptr, hwptr, hw_avail, sw_avail, swidx;
+		vwsnd_port_hwstate_t hwstate = wport->hwstate;
+		vwsnd_port_swstate_t swstate = wport->swstate;
+		int hw_unavail;
+		ustmsc_t ustmsc;
+
+		hwptr = li_read_hwptr(&wport->chan);
+		swptr = li_read_swptr(&wport->chan);
+		hw_unavail = (swptr - hwptr + hwsize) % hwsize;
+		hw_avail = (hwmax - hw_unavail) & -fragsize;
+		sw_avail = wport->swb_i_avail & -fragsize;
+		if (sw_avail && swstate == SW_RUN) {
+			if (wport->flags & ERFLOWN) {
+				wport->flags &= ~ERFLOWN;
+			}
+		} else if (swstate == SW_INITIAL ||
+			 swstate == SW_OFF ||
+			 (swstate == SW_DRAIN &&
+			  !sw_avail &&
+			  (wport->flags & ERFLOWN))) {
+			DBGP("stopping.  hwstate = %d\n", hwstate);
+			if (hwstate != HW_STOPPED) {
+				li_deactivate_dma(&wport->chan);
+				wport->hwstate = HW_STOPPED;
+			}
+			wake_up(&wport->queue);
+			break;
+		}
+		if (!sw_avail || !hw_avail)
+			break;
+		spin_unlock_irqrestore(&wport->lock, iflags);
+
+		/*
+		 * We gave up the port lock, but we have the HW_BUSY flag.
+		 * Proceed without accessing any nonlocal state.
+		 * Do not exit the loop -- must check for more work.
+		 */
+
+		swidx = wport->swb_i_idx;
+		nb = hw_avail;
+		if (nb > sw_avail)
+			nb = sw_avail;
+		if (nb > hwsize - swptr)
+			nb = hwsize - swptr; /* don't overflow hwbuf */
+		if (nb > swsize - swidx)
+			nb = swsize - swidx; /* don't overflow swbuf */
+		ASSERT(nb > 0);
+		if (nb % fragsize) {
+			DBGP("nb = %d, fragsize = %d\n", nb, fragsize);
+			DBGP("hw_avail = %d\n", hw_avail);
+			DBGP("sw_avail = %d\n", sw_avail);
+			DBGP("hwsize = %d, swptr = %d\n", hwsize, swptr);
+			DBGP("swsize = %d, swidx = %d\n", swsize, swidx);
+		}
+		ASSERT(!(nb % fragsize));
+		DBGPV("copying swb[%d..%d] to hwb[%d..%d]\n",
+		      swidx, swidx + nb, swptr, swptr + nb);
+		pcm_copy_out(wport, swidx, swptr, nb);
+		li_write_swptr(&wport->chan, (swptr + nb) % hwsize);
+		spin_lock_irqsave(&wport->lock, iflags);
+		if (hwstate == HW_STOPPED) {
+			DBGPV("starting\n");
+			li_activate_dma(&wport->chan);
+			wport->hwstate = HW_RUNNING;
+			li_read_USTMSC(&wport->chan, &ustmsc);
+			ASSERT(wport->byte_count % wport->frame_size == 0);
+			wport->MSC_offset = ustmsc.msc - wport->byte_count / wport->frame_size;
+		}
+		__swb_inc_i(wport, nb);
+		wport->byte_count += nb;
+		wport->frag_count += nb / fragsize;
+		ASSERT(nb % fragsize == 0);
+		wake_up(&wport->queue);
+	}
+	wport->flags &= ~HW_BUSY;
+	spin_unlock_irqrestore(&wport->lock, iflags);
+	DBGRV();
+}
+
+/*
+ * pcm_input() is called both from baselevel and from interrupt level.
+ * This is where audio frames are copied out of the hardware-accessible
+ * ring buffer.
+ *
+ * Locking note: The part of this routine that figures out what to do
+ * holds rport->lock.  The longer part releases rport->lock, but sets
+ * rport->flags & HW_BUSY.  Afterward, it reacquires rport->lock, and
+ * checks for more work to do.
+ *
+ * If another thread calls pcm_input() while HW_BUSY is set, it
+ * returns immediately, knowing that the thread that set HW_BUSY will
+ * look for more work to do before returning.
+ *
+ * This has the advantage that port->lock is held for several short
+ * periods instead of one long period.  Also, when pcm_input is
+ * called from base level, it reenables interrupts.
+ */
+
+static void pcm_input(vwsnd_dev_t *devc, int erflown, int nb)
+{
+	vwsnd_port_t *rport = &devc->rport;
+	const int hwmax  = rport->hwbuf_max;
+	const int hwsize = rport->hwbuf_size;
+	const int swsize = rport->swbuf_size;
+	const int fragsize = rport->hw_fragsize;
+	unsigned long iflags;
+
+	DBGEV("(devc=0x%p, erflown=%d, nb=%d)\n", devc, erflown, nb);
+
+	spin_lock_irqsave(&rport->lock, iflags);
+	if (erflown)
+		rport->flags |= ERFLOWN;
+	(void) __swb_inc_u(rport, nb);
+	if (rport->flags & HW_BUSY || !rport->swbuf) {
+		spin_unlock_irqrestore(&rport->lock, iflags);
+		DBGPV("returning: HW BUSY or !swbuf\n");
+		return;
+	}
+	if (rport->flags & DISABLED) {
+		spin_unlock_irqrestore(&rport->lock, iflags);
+		DBGPV("returning: DISABLED\n");
+		return;
+	}
+	rport->flags |= HW_BUSY;
+	while (1) {
+		int swptr, hwptr, hw_avail, sw_avail, swidx;
+		vwsnd_port_hwstate_t hwstate = rport->hwstate;
+		vwsnd_port_swstate_t swstate = rport->swstate;
+
+		hwptr = li_read_hwptr(&rport->chan);
+		swptr = li_read_swptr(&rport->chan);
+		hw_avail = (hwptr - swptr + hwsize) % hwsize & -fragsize;
+		if (hw_avail > hwmax)
+			hw_avail = hwmax;
+		sw_avail = rport->swb_i_avail & -fragsize;
+		if (swstate != SW_RUN) {
+			DBGP("stopping.  hwstate = %d\n", hwstate);
+			if (hwstate != HW_STOPPED) {
+				li_deactivate_dma(&rport->chan);
+				rport->hwstate = HW_STOPPED;
+			}
+			wake_up(&rport->queue);
+			break;
+		}
+		if (!sw_avail || !hw_avail)
+			break;
+		spin_unlock_irqrestore(&rport->lock, iflags);
+
+		/*
+		 * We gave up the port lock, but we have the HW_BUSY flag.
+		 * Proceed without accessing any nonlocal state.
+		 * Do not exit the loop -- must check for more work.
+		 */
+
+		swidx = rport->swb_i_idx;
+		nb = hw_avail;
+		if (nb > sw_avail)
+			nb = sw_avail;
+		if (nb > hwsize - swptr)
+			nb = hwsize - swptr; /* don't overflow hwbuf */
+		if (nb > swsize - swidx)
+			nb = swsize - swidx; /* don't overflow swbuf */
+		ASSERT(nb > 0);
+		if (nb % fragsize) {
+			DBGP("nb = %d, fragsize = %d\n", nb, fragsize);
+			DBGP("hw_avail = %d\n", hw_avail);
+			DBGP("sw_avail = %d\n", sw_avail);
+			DBGP("hwsize = %d, swptr = %d\n", hwsize, swptr);
+			DBGP("swsize = %d, swidx = %d\n", swsize, swidx);
+		}
+		ASSERT(!(nb % fragsize));
+		DBGPV("copying hwb[%d..%d] to swb[%d..%d]\n",
+		      swptr, swptr + nb, swidx, swidx + nb);
+		pcm_copy_in(rport, swidx, swptr, nb);
+		li_write_swptr(&rport->chan, (swptr + nb) % hwsize);
+		spin_lock_irqsave(&rport->lock, iflags);
+		__swb_inc_i(rport, nb);
+		rport->byte_count += nb;
+		rport->frag_count += nb / fragsize;
+		ASSERT(nb % fragsize == 0);
+		wake_up(&rport->queue);
+	}
+	rport->flags &= ~HW_BUSY;
+	spin_unlock_irqrestore(&rport->lock, iflags);
+	DBGRV();
+}
+
+/*
+ * pcm_flush_frag() writes zero samples to fill the current fragment,
+ * then flushes it to the hardware.
+ *
+ * It is only meaningful to flush output, not input.
+ */
+
+static void pcm_flush_frag(vwsnd_dev_t *devc)
+{
+	vwsnd_port_t *wport = &devc->wport;
+
+	DBGPV("swstate = %d\n", wport->swstate);
+	if (wport->swstate == SW_RUN) {
+		int idx = wport->swb_u_idx;
+		int end = (idx + wport->hw_fragsize - 1)
+			>> wport->hw_fragshift
+			<< wport->hw_fragshift;
+		int nb = end - idx;
+		DBGPV("clearing %d bytes\n", nb);
+		if (nb)
+			memset(wport->swbuf + idx,
+			       (char) wport->zero_word,
+			       nb);
+		wport->swstate = SW_DRAIN;
+		pcm_output(devc, 0, nb);
+	}
+	DBGRV();
+}
+
+/*
+ * Wait for output to drain.  This sleeps uninterruptibly because
+ * there is nothing intelligent we can do if interrupted.  This
+ * means the process will be delayed in responding to the signal.
+ */
+
+static void pcm_write_sync(vwsnd_dev_t *devc)
+{
+	vwsnd_port_t *wport = &devc->wport;
+	DECLARE_WAITQUEUE(wait, current);
+	unsigned long flags;
+	vwsnd_port_hwstate_t hwstate;
+
+	DBGEV("(devc=0x%p)\n", devc);
+	add_wait_queue(&wport->queue, &wait);
+	while (1) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		spin_lock_irqsave(&wport->lock, flags);
+		{
+			hwstate = wport->hwstate;
+		}
+		spin_unlock_irqrestore(&wport->lock, flags);
+		if (hwstate == HW_STOPPED)
+			break;
+		schedule();
+	}
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&wport->queue, &wait);
+	DBGPV("swstate = %d, hwstate = %d\n", wport->swstate, wport->hwstate);
+	DBGRV();
+}
+
+/*****************************************************************************/
+/* audio driver */
+
+/*
+ * seek on an audio device always fails.
+ */
+
+static void vwsnd_audio_read_intr(vwsnd_dev_t *devc, unsigned int status)
+{
+	int overflown = status & LI_INTR_COMM1_OVERFLOW;
+
+	if (status & READ_INTR_MASK)
+		pcm_input(devc, overflown, 0);
+}
+
+static void vwsnd_audio_write_intr(vwsnd_dev_t *devc, unsigned int status)
+{
+	int underflown = status & LI_INTR_COMM2_UNDERFLOW;
+
+	if (status & WRITE_INTR_MASK)
+		pcm_output(devc, underflown, 0);
+}
+
+static irqreturn_t vwsnd_audio_intr(int irq, void *dev_id)
+{
+	vwsnd_dev_t *devc = dev_id;
+	unsigned int status;
+
+	DBGEV("(irq=%d, dev_id=0x%p)\n", irq, dev_id);
+
+	status = li_get_clear_intr_status(&devc->lith);
+	vwsnd_audio_read_intr(devc, status);
+	vwsnd_audio_write_intr(devc, status);
+	return IRQ_HANDLED;
+}
+
+static ssize_t vwsnd_audio_do_read(struct file *file,
+				   char *buffer,
+				   size_t count,
+				   loff_t *ppos)
+{
+	vwsnd_dev_t *devc = file->private_data;
+	vwsnd_port_t *rport = ((file->f_mode & FMODE_READ) ?
+			       &devc->rport : NULL);
+	int ret, nb;
+
+	DBGEV("(file=0x%p, buffer=0x%p, count=%d, ppos=0x%p)\n",
+	     file, buffer, count, ppos);
+
+	if (!rport)
+		return -EINVAL;
+
+	if (rport->swbuf == NULL) {
+		vwsnd_port_t *wport = (file->f_mode & FMODE_WRITE) ?
+			&devc->wport : NULL;
+		ret = pcm_setup(devc, rport, wport);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (!access_ok(VERIFY_READ, buffer, count))
+		return -EFAULT;
+	ret = 0;
+	while (count) {
+		DECLARE_WAITQUEUE(wait, current);
+		add_wait_queue(&rport->queue, &wait);
+		while ((nb = swb_inc_u(rport, 0)) == 0) {
+			DBGPV("blocking\n");
+			set_current_state(TASK_INTERRUPTIBLE);
+			if (rport->flags & DISABLED ||
+			    file->f_flags & O_NONBLOCK) {
+				current->state = TASK_RUNNING;
+				remove_wait_queue(&rport->queue, &wait);
+				return ret ? ret : -EAGAIN;
+			}
+			schedule();
+			if (signal_pending(current)) {
+				current->state = TASK_RUNNING;
+				remove_wait_queue(&rport->queue, &wait);
+				return ret ? ret : -ERESTARTSYS;
+			}
+		}
+		current->state = TASK_RUNNING;
+		remove_wait_queue(&rport->queue, &wait);
+		pcm_input(devc, 0, 0);
+		/* nb bytes are available in userbuf. */
+		if (nb > count)
+			nb = count;
+		DBGPV("nb = %d\n", nb);
+		if (copy_to_user(buffer, rport->swbuf + rport->swb_u_idx, nb))
+			return -EFAULT;
+		(void) swb_inc_u(rport, nb);
+		buffer += nb;
+		count -= nb;
+		ret += nb;
+	}
+	DBGPV("returning %d\n", ret);
+	return ret;
+}
+
+static ssize_t vwsnd_audio_read(struct file *file,
+				char *buffer,
+				size_t count,
+				loff_t *ppos)
+{
+	vwsnd_dev_t *devc = file->private_data;
+	ssize_t ret;
+
+	mutex_lock(&devc->io_mutex);
+	ret = vwsnd_audio_do_read(file, buffer, count, ppos);
+	mutex_unlock(&devc->io_mutex);
+	return ret;
+}
+
+static ssize_t vwsnd_audio_do_write(struct file *file,
+				    const char *buffer,
+				    size_t count,
+				    loff_t *ppos)
+{
+	vwsnd_dev_t *devc = file->private_data;
+	vwsnd_port_t *wport = ((file->f_mode & FMODE_WRITE) ?
+			       &devc->wport : NULL);
+	int ret, nb;
+
+	DBGEV("(file=0x%p, buffer=0x%p, count=%d, ppos=0x%p)\n",
+	      file, buffer, count, ppos);
+
+	if (!wport)
+		return -EINVAL;
+
+	if (wport->swbuf == NULL) {
+		vwsnd_port_t *rport = (file->f_mode & FMODE_READ) ?
+			&devc->rport : NULL;
+		ret = pcm_setup(devc, rport, wport);
+		if (ret < 0)
+			return ret;
+	}
+	if (!access_ok(VERIFY_WRITE, buffer, count))
+		return -EFAULT;
+	ret = 0;
+	while (count) {
+		DECLARE_WAITQUEUE(wait, current);
+		add_wait_queue(&wport->queue, &wait);
+		while ((nb = swb_inc_u(wport, 0)) == 0) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			if (wport->flags & DISABLED ||
+			    file->f_flags & O_NONBLOCK) {
+				current->state = TASK_RUNNING;
+				remove_wait_queue(&wport->queue, &wait);
+				return ret ? ret : -EAGAIN;
+			}
+			schedule();
+			if (signal_pending(current)) {
+				current->state = TASK_RUNNING;
+				remove_wait_queue(&wport->queue, &wait);
+				return ret ? ret : -ERESTARTSYS;
+			}
+		}
+		current->state = TASK_RUNNING;
+		remove_wait_queue(&wport->queue, &wait);
+		/* nb bytes are available in userbuf. */
+		if (nb > count)
+			nb = count;
+		DBGPV("nb = %d\n", nb);
+		if (copy_from_user(wport->swbuf + wport->swb_u_idx, buffer, nb))
+			return -EFAULT;
+		pcm_output(devc, 0, nb);
+		buffer += nb;
+		count -= nb;
+		ret += nb;
+	}
+	DBGPV("returning %d\n", ret);
+	return ret;
+}
+
+static ssize_t vwsnd_audio_write(struct file *file,
+				 const char *buffer,
+				 size_t count,
+				 loff_t *ppos)
+{
+	vwsnd_dev_t *devc = file->private_data;
+	ssize_t ret;
+
+	mutex_lock(&devc->io_mutex);
+	ret = vwsnd_audio_do_write(file, buffer, count, ppos);
+	mutex_unlock(&devc->io_mutex);
+	return ret;
+}
+
+/* No kernel lock - fine */
+static unsigned int vwsnd_audio_poll(struct file *file,
+				     struct poll_table_struct *wait)
+{
+	vwsnd_dev_t *devc = (vwsnd_dev_t *) file->private_data;
+	vwsnd_port_t *rport = (file->f_mode & FMODE_READ) ?
+		&devc->rport : NULL;
+	vwsnd_port_t *wport = (file->f_mode & FMODE_WRITE) ?
+		&devc->wport : NULL;
+	unsigned int mask = 0;
+
+	DBGEV("(file=0x%p, wait=0x%p)\n", file, wait);
+
+	ASSERT(rport || wport);
+	if (rport) {
+		poll_wait(file, &rport->queue, wait);
+		if (swb_inc_u(rport, 0))
+			mask |= (POLLIN | POLLRDNORM);
+	}
+	if (wport) {
+		poll_wait(file, &wport->queue, wait);
+		if (wport->swbuf == NULL || swb_inc_u(wport, 0))
+			mask |= (POLLOUT | POLLWRNORM);
+	}
+
+	DBGPV("returning 0x%x\n", mask);
+	return mask;
+}
+
+static int vwsnd_audio_do_ioctl(struct file *file,
+				unsigned int cmd,
+				unsigned long arg)
+{
+	vwsnd_dev_t *devc = (vwsnd_dev_t *) file->private_data;
+	vwsnd_port_t *rport = (file->f_mode & FMODE_READ) ?
+		&devc->rport : NULL;
+	vwsnd_port_t *wport = (file->f_mode & FMODE_WRITE) ?
+		&devc->wport : NULL;
+	vwsnd_port_t *aport = rport ? rport : wport;
+	struct audio_buf_info buf_info;
+	struct count_info info;
+	unsigned long flags;
+	int ival;
+
+	
+	DBGEV("(file=0x%p, cmd=0x%x, arg=0x%lx)\n",
+	      file, cmd, arg);
+	switch (cmd) {
+	case OSS_GETVERSION:		/* _SIOR ('M', 118, int) */
+		DBGX("OSS_GETVERSION\n");
+		ival = SOUND_VERSION;
+		return put_user(ival, (int *) arg);
+
+	case SNDCTL_DSP_GETCAPS:	/* _SIOR ('P',15, int) */
+		DBGX("SNDCTL_DSP_GETCAPS\n");
+		ival = DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER;
+		return put_user(ival, (int *) arg);
+
+	case SNDCTL_DSP_GETFMTS:	/* _SIOR ('P',11, int) */
+		DBGX("SNDCTL_DSP_GETFMTS\n");
+		ival = (AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW |
+			AFMT_U8 | AFMT_S8);
+		return put_user(ival, (int *) arg);
+		break;
+
+	case SOUND_PCM_READ_RATE:	/* _SIOR ('P', 2, int) */
+		DBGX("SOUND_PCM_READ_RATE\n");
+		ival = aport->sw_framerate;
+		return put_user(ival, (int *) arg);
+
+	case SOUND_PCM_READ_CHANNELS:	/* _SIOR ('P', 6, int) */
+		DBGX("SOUND_PCM_READ_CHANNELS\n");
+		ival = aport->sw_channels;
+		return put_user(ival, (int *) arg);
+
+	case SNDCTL_DSP_SPEED:		/* _SIOWR('P', 2, int) */
+		if (get_user(ival, (int *) arg))
+			return -EFAULT;
+		DBGX("SNDCTL_DSP_SPEED %d\n", ival);
+		if (ival) {
+			if (aport->swstate != SW_INITIAL) {
+				DBGX("SNDCTL_DSP_SPEED failed: swstate = %d\n",
+				     aport->swstate);
+				return -EINVAL;
+			}
+			if (ival < MIN_SPEED)
+				ival = MIN_SPEED;
+			if (ival > MAX_SPEED)
+				ival = MAX_SPEED;
+			if (rport)
+				rport->sw_framerate = ival;
+			if (wport)
+				wport->sw_framerate = ival;
+		} else
+			ival = aport->sw_framerate;
+		return put_user(ival, (int *) arg);
+
+	case SNDCTL_DSP_STEREO:		/* _SIOWR('P', 3, int) */
+		if (get_user(ival, (int *) arg))
+			return -EFAULT;
+		DBGX("SNDCTL_DSP_STEREO %d\n", ival);
+		if (ival != 0 && ival != 1)
+			return -EINVAL;
+		if (aport->swstate != SW_INITIAL)
+			return -EINVAL;
+		if (rport)
+			rport->sw_channels = ival + 1;
+		if (wport)
+			wport->sw_channels = ival + 1;
+		return put_user(ival, (int *) arg);
+
+	case SNDCTL_DSP_CHANNELS:	/* _SIOWR('P', 6, int) */
+		if (get_user(ival, (int *) arg))
+			return -EFAULT;
+		DBGX("SNDCTL_DSP_CHANNELS %d\n", ival);
+		if (ival != 1 && ival != 2)
+			return -EINVAL;
+		if (aport->swstate != SW_INITIAL)
+			return -EINVAL;
+		if (rport)
+			rport->sw_channels = ival;
+		if (wport)
+			wport->sw_channels = ival;
+		return put_user(ival, (int *) arg);
+
+	case SNDCTL_DSP_GETBLKSIZE:	/* _SIOWR('P', 4, int) */
+		ival = pcm_setup(devc, rport, wport);
+		if (ival < 0) {
+			DBGX("SNDCTL_DSP_GETBLKSIZE failed, errno %d\n", ival);
+			return ival;
+		}
+		ival = 1 << aport->sw_fragshift;
+		DBGX("SNDCTL_DSP_GETBLKSIZE returning %d\n", ival);
+		return put_user(ival, (int *) arg);
+
+	case SNDCTL_DSP_SETFRAGMENT:	/* _SIOWR('P',10, int) */
+		if (get_user(ival, (int *) arg))
+			return -EFAULT;
+		DBGX("SNDCTL_DSP_SETFRAGMENT %d:%d\n",
+		     ival >> 16, ival & 0xFFFF);
+		if (aport->swstate != SW_INITIAL)
+			return -EINVAL;
+		{
+			int sw_fragshift = ival & 0xFFFF;
+			int sw_subdivshift = aport->sw_subdivshift;
+			int hw_fragshift = sw_fragshift - sw_subdivshift;
+			int sw_fragcount = (ival >> 16) & 0xFFFF;
+			int hw_fragsize;
+			if (hw_fragshift < MIN_FRAGSHIFT)
+				hw_fragshift = MIN_FRAGSHIFT;
+			if (hw_fragshift > MAX_FRAGSHIFT)
+				hw_fragshift = MAX_FRAGSHIFT;
+			sw_fragshift = hw_fragshift + aport->sw_subdivshift;
+			hw_fragsize = 1 << hw_fragshift;
+			if (sw_fragcount < MIN_FRAGCOUNT(hw_fragsize))
+				sw_fragcount = MIN_FRAGCOUNT(hw_fragsize);
+			if (sw_fragcount > MAX_FRAGCOUNT(hw_fragsize))
+				sw_fragcount = MAX_FRAGCOUNT(hw_fragsize);
+			DBGPV("sw_fragshift = %d\n", sw_fragshift);
+			DBGPV("rport = 0x%p, wport = 0x%p\n", rport, wport);
+			if (rport) {
+				rport->sw_fragshift = sw_fragshift;
+				rport->sw_fragcount = sw_fragcount;
+			}
+			if (wport) {
+				wport->sw_fragshift = sw_fragshift;
+				wport->sw_fragcount = sw_fragcount;
+			}
+			ival = sw_fragcount << 16 | sw_fragshift;
+		}
+		DBGX("SNDCTL_DSP_SETFRAGMENT returns %d:%d\n",
+		      ival >> 16, ival & 0xFFFF);
+		return put_user(ival, (int *) arg);
+
+	case SNDCTL_DSP_SUBDIVIDE:	/* _SIOWR('P', 9, int) */
+                if (get_user(ival, (int *) arg))
+			return -EFAULT;
+		DBGX("SNDCTL_DSP_SUBDIVIDE %d\n", ival);
+		if (aport->swstate != SW_INITIAL)
+			return -EINVAL;
+		{
+			int subdivshift;
+			int hw_fragshift, hw_fragsize, hw_fragcount;
+			switch (ival) {
+			case 1: subdivshift = 0; break;
+			case 2: subdivshift = 1; break;
+			case 4: subdivshift = 2; break;
+			default: return -EINVAL;
+			}
+			hw_fragshift = aport->sw_fragshift - subdivshift;
+			if (hw_fragshift < MIN_FRAGSHIFT ||
+			    hw_fragshift > MAX_FRAGSHIFT)
+				return -EINVAL;
+			hw_fragsize = 1 << hw_fragshift;
+			hw_fragcount = aport->sw_fragcount >> subdivshift;
+			if (hw_fragcount < MIN_FRAGCOUNT(hw_fragsize) ||
+			    hw_fragcount > MAX_FRAGCOUNT(hw_fragsize))
+				return -EINVAL;
+			if (rport)
+				rport->sw_subdivshift = subdivshift;
+			if (wport)
+				wport->sw_subdivshift = subdivshift;
+		}
+		return 0;
+
+	case SNDCTL_DSP_SETFMT:		/* _SIOWR('P',5, int) */
+		if (get_user(ival, (int *) arg))
+			return -EFAULT;
+		DBGX("SNDCTL_DSP_SETFMT %d\n", ival);
+		if (ival != AFMT_QUERY) {
+			if (aport->swstate != SW_INITIAL) {
+				DBGP("SETFMT failed, swstate = %d\n",
+				     aport->swstate);
+				return -EINVAL;
+			}
+			switch (ival) {
+			case AFMT_MU_LAW:
+			case AFMT_A_LAW:
+			case AFMT_U8:
+			case AFMT_S8:
+			case AFMT_S16_LE:
+				if (rport)
+					rport->sw_samplefmt = ival;
+				if (wport)
+					wport->sw_samplefmt = ival;
+				break;
+			default:
+				return -EINVAL;
+			}
+		}
+		ival = aport->sw_samplefmt;
+		return put_user(ival, (int *) arg);
+
+	case SNDCTL_DSP_GETOSPACE:	/* _SIOR ('P',12, audio_buf_info) */
+		DBGXV("SNDCTL_DSP_GETOSPACE\n");
+		if (!wport)
+			return -EINVAL;
+		ival = pcm_setup(devc, rport, wport);
+		if (ival < 0)
+			return ival;
+		ival = swb_inc_u(wport, 0);
+		buf_info.fragments = ival >> wport->sw_fragshift;
+		buf_info.fragstotal = wport->sw_fragcount;
+		buf_info.fragsize = 1 << wport->sw_fragshift;
+		buf_info.bytes = ival;
+		DBGXV("SNDCTL_DSP_GETOSPACE returns { %d %d %d %d }\n",
+		     buf_info.fragments, buf_info.fragstotal,
+		     buf_info.fragsize, buf_info.bytes);
+		if (copy_to_user((void *) arg, &buf_info, sizeof buf_info))
+			return -EFAULT;
+		return 0;
+
+	case SNDCTL_DSP_GETISPACE:	/* _SIOR ('P',13, audio_buf_info) */
+		DBGX("SNDCTL_DSP_GETISPACE\n");
+		if (!rport)
+			return -EINVAL;
+		ival = pcm_setup(devc, rport, wport);
+		if (ival < 0)
+			return ival;
+		ival = swb_inc_u(rport, 0);
+		buf_info.fragments = ival >> rport->sw_fragshift;
+		buf_info.fragstotal = rport->sw_fragcount;
+		buf_info.fragsize = 1 << rport->sw_fragshift;
+		buf_info.bytes = ival;
+		DBGX("SNDCTL_DSP_GETISPACE returns { %d %d %d %d }\n",
+		     buf_info.fragments, buf_info.fragstotal,
+		     buf_info.fragsize, buf_info.bytes);
+		if (copy_to_user((void *) arg, &buf_info, sizeof buf_info))
+			return -EFAULT;
+		return 0;
+
+	case SNDCTL_DSP_NONBLOCK:	/* _SIO  ('P',14) */
+		DBGX("SNDCTL_DSP_NONBLOCK\n");
+		spin_lock(&file->f_lock);
+		file->f_flags |= O_NONBLOCK;
+		spin_unlock(&file->f_lock);
+		return 0;
+
+	case SNDCTL_DSP_RESET:		/* _SIO  ('P', 0) */
+		DBGX("SNDCTL_DSP_RESET\n");
+		/*
+		 * Nothing special needs to be done for input.  Input
+		 * samples sit in swbuf, but it will be reinitialized
+		 * to empty when pcm_setup() is called.
+		 */
+		if (wport && wport->swbuf) {
+			wport->swstate = SW_INITIAL;
+			pcm_output(devc, 0, 0);
+			pcm_write_sync(devc);
+		}
+		pcm_shutdown(devc, rport, wport);
+		return 0;
+
+	case SNDCTL_DSP_SYNC:		/* _SIO  ('P', 1) */
+		DBGX("SNDCTL_DSP_SYNC\n");
+		if (wport) {
+			pcm_flush_frag(devc);
+			pcm_write_sync(devc);
+		}
+		pcm_shutdown(devc, rport, wport);
+		return 0;
+
+	case SNDCTL_DSP_POST:		/* _SIO  ('P', 8) */
+		DBGX("SNDCTL_DSP_POST\n");
+		if (!wport)
+			return -EINVAL;
+		pcm_flush_frag(devc);
+		return 0;
+
+	case SNDCTL_DSP_GETIPTR:	/* _SIOR ('P', 17, count_info) */
+		DBGX("SNDCTL_DSP_GETIPTR\n");
+		if (!rport)
+			return -EINVAL;
+		spin_lock_irqsave(&rport->lock, flags);
+		{
+			ustmsc_t ustmsc;
+			if (rport->hwstate == HW_RUNNING) {
+				ASSERT(rport->swstate == SW_RUN);
+				li_read_USTMSC(&rport->chan, &ustmsc);
+				info.bytes = ustmsc.msc - rport->MSC_offset;
+				info.bytes *= rport->frame_size;
+			} else {
+				info.bytes = rport->byte_count;
+			}
+			info.blocks = rport->frag_count;
+			info.ptr = 0;	/* not implemented */
+			rport->frag_count = 0;
+		}
+		spin_unlock_irqrestore(&rport->lock, flags);
+		if (copy_to_user((void *) arg, &info, sizeof info))
+			return -EFAULT;
+		return 0;
+
+	case SNDCTL_DSP_GETOPTR:	/* _SIOR ('P',18, count_info) */
+		DBGX("SNDCTL_DSP_GETOPTR\n");
+		if (!wport)
+			return -EINVAL;
+		spin_lock_irqsave(&wport->lock, flags);
+		{
+			ustmsc_t ustmsc;
+			if (wport->hwstate == HW_RUNNING) {
+				ASSERT(wport->swstate == SW_RUN);
+				li_read_USTMSC(&wport->chan, &ustmsc);
+				info.bytes = ustmsc.msc - wport->MSC_offset;
+				info.bytes *= wport->frame_size;
+			} else {
+				info.bytes = wport->byte_count;
+			}
+			info.blocks = wport->frag_count;
+			info.ptr = 0;	/* not implemented */
+			wport->frag_count = 0;
+		}
+		spin_unlock_irqrestore(&wport->lock, flags);
+		if (copy_to_user((void *) arg, &info, sizeof info))
+			return -EFAULT;
+		return 0;
+
+	case SNDCTL_DSP_GETODELAY:	/* _SIOR ('P', 23, int) */
+		DBGX("SNDCTL_DSP_GETODELAY\n");
+		if (!wport)
+			return -EINVAL;
+		spin_lock_irqsave(&wport->lock, flags);
+		{
+			int fsize = wport->frame_size;
+			ival = wport->swb_i_avail / fsize;
+			if (wport->hwstate == HW_RUNNING) {
+				int swptr, hwptr, hwframes, hwbytes, hwsize;
+				int totalhwbytes;
+				ustmsc_t ustmsc;
+
+				hwsize = wport->hwbuf_size;
+				swptr = li_read_swptr(&wport->chan);
+				li_read_USTMSC(&wport->chan, &ustmsc);
+				hwframes = ustmsc.msc - wport->MSC_offset;
+				totalhwbytes = hwframes * fsize;
+				hwptr = totalhwbytes % hwsize;
+				hwbytes = (swptr - hwptr + hwsize) % hwsize;
+				ival += hwbytes / fsize;
+			}
+		}
+		spin_unlock_irqrestore(&wport->lock, flags);
+		return put_user(ival, (int *) arg);
+
+	case SNDCTL_DSP_PROFILE:	/* _SIOW ('P', 23, int) */
+		DBGX("SNDCTL_DSP_PROFILE\n");
+
+		/*
+		 * Thomas Sailer explains SNDCTL_DSP_PROFILE
+		 * (private email, March 24, 1999):
+		 *
+		 *     This gives the sound driver a hint on what it
+		 *     should do with partial fragments
+		 *     (i.e. fragments partially filled with write).
+		 *     This can direct the driver to zero them or
+		 *     leave them alone.  But don't ask me what this
+		 *     is good for, my driver just zeroes the last
+		 *     fragment before the receiver stops, no idea
+		 *     what good for any other behaviour could
+		 *     be. Implementing it as NOP seems safe.
+		 */
+
+		break;
+
+	case SNDCTL_DSP_GETTRIGGER:	/* _SIOR ('P',16, int) */
+		DBGX("SNDCTL_DSP_GETTRIGGER\n");
+		ival = 0;
+		if (rport) {
+			spin_lock_irqsave(&rport->lock, flags);
+			{
+				if (!(rport->flags & DISABLED))
+					ival |= PCM_ENABLE_INPUT;
+			}
+			spin_unlock_irqrestore(&rport->lock, flags);
+		}
+		if (wport) {
+			spin_lock_irqsave(&wport->lock, flags);
+			{
+				if (!(wport->flags & DISABLED))
+					ival |= PCM_ENABLE_OUTPUT;
+			}
+			spin_unlock_irqrestore(&wport->lock, flags);
+		}
+		return put_user(ival, (int *) arg);
+
+	case SNDCTL_DSP_SETTRIGGER:	/* _SIOW ('P',16, int) */
+		if (get_user(ival, (int *) arg))
+			return -EFAULT;
+		DBGX("SNDCTL_DSP_SETTRIGGER %d\n", ival);
+
+		/*
+		 * If user is disabling I/O and port is not in initial
+		 * state, fail with EINVAL.
+		 */
+
+		if (((rport && !(ival & PCM_ENABLE_INPUT)) ||
+		     (wport && !(ival & PCM_ENABLE_OUTPUT))) &&
+		    aport->swstate != SW_INITIAL)
+			return -EINVAL;
+
+		if (rport) {
+			vwsnd_port_hwstate_t hwstate;
+			spin_lock_irqsave(&rport->lock, flags);
+			{
+				hwstate = rport->hwstate;
+				if (ival & PCM_ENABLE_INPUT)
+					rport->flags &= ~DISABLED;
+				else
+					rport->flags |= DISABLED;
+			}
+			spin_unlock_irqrestore(&rport->lock, flags);
+			if (hwstate != HW_RUNNING && ival & PCM_ENABLE_INPUT) {
+
+				if (rport->swstate == SW_INITIAL)
+					pcm_setup(devc, rport, wport);
+				else
+					li_activate_dma(&rport->chan);
+			}
+		}
+		if (wport) {
+			vwsnd_port_flags_t pflags;
+			spin_lock_irqsave(&wport->lock, flags);
+			{
+				pflags = wport->flags;
+				if (ival & PCM_ENABLE_OUTPUT)
+					wport->flags &= ~DISABLED;
+				else
+					wport->flags |= DISABLED;
+			}
+			spin_unlock_irqrestore(&wport->lock, flags);
+			if (pflags & DISABLED && ival & PCM_ENABLE_OUTPUT) {
+				if (wport->swstate == SW_RUN)
+					pcm_output(devc, 0, 0);
+			}
+		}
+		return 0;
+
+	default:
+		DBGP("unknown ioctl 0x%x\n", cmd);
+		return -EINVAL;
+	}
+	DBGP("unimplemented ioctl 0x%x\n", cmd);
+	return -EINVAL;
+}
+
+static long vwsnd_audio_ioctl(struct file *file,
+				unsigned int cmd,
+				unsigned long arg)
+{
+	vwsnd_dev_t *devc = (vwsnd_dev_t *) file->private_data;
+	int ret;
+
+	mutex_lock(&vwsnd_mutex);
+	mutex_lock(&devc->io_mutex);
+	ret = vwsnd_audio_do_ioctl(file, cmd, arg);
+	mutex_unlock(&devc->io_mutex);
+	mutex_unlock(&vwsnd_mutex);
+
+	return ret;
+}
+
+/* No mmap. */
+
+static int vwsnd_audio_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	DBGE("(file=0x%p, vma=0x%p)\n", file, vma);
+	return -ENODEV;
+}
+
+/*
+ * Open the audio device for read and/or write.
+ *
+ * Returns 0 on success, -errno on failure.
+ */
+
+static int vwsnd_audio_open(struct inode *inode, struct file *file)
+{
+	vwsnd_dev_t *devc;
+	int minor = iminor(inode);
+	int sw_samplefmt;
+
+	DBGE("(inode=0x%p, file=0x%p)\n", inode, file);
+
+	mutex_lock(&vwsnd_mutex);
+	INC_USE_COUNT;
+	for (devc = vwsnd_dev_list; devc; devc = devc->next_dev)
+		if ((devc->audio_minor & ~0x0F) == (minor & ~0x0F))
+			break;
+
+	if (devc == NULL) {
+		DEC_USE_COUNT;
+		mutex_unlock(&vwsnd_mutex);
+		return -ENODEV;
+	}
+
+	mutex_lock(&devc->open_mutex);
+	while (devc->open_mode & file->f_mode) {
+		mutex_unlock(&devc->open_mutex);
+		if (file->f_flags & O_NONBLOCK) {
+			DEC_USE_COUNT;
+			mutex_unlock(&vwsnd_mutex);
+			return -EBUSY;
+		}
+		interruptible_sleep_on(&devc->open_wait);
+		if (signal_pending(current)) {
+			DEC_USE_COUNT;
+			mutex_unlock(&vwsnd_mutex);
+			return -ERESTARTSYS;
+		}
+		mutex_lock(&devc->open_mutex);
+	}
+	devc->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE);
+	mutex_unlock(&devc->open_mutex);
+
+	/* get default sample format from minor number. */
+
+	sw_samplefmt = 0;
+	if ((minor & 0xF) == SND_DEV_DSP)
+		sw_samplefmt = AFMT_U8;
+	else if ((minor & 0xF) == SND_DEV_AUDIO)
+		sw_samplefmt = AFMT_MU_LAW;
+	else if ((minor & 0xF) == SND_DEV_DSP16)
+		sw_samplefmt = AFMT_S16_LE;
+	else
+		ASSERT(0);
+
+	/* Initialize vwsnd_ports. */
+
+	mutex_lock(&devc->io_mutex);
+	{
+		if (file->f_mode & FMODE_READ) {
+			devc->rport.swstate        = SW_INITIAL;
+			devc->rport.flags          = 0;
+			devc->rport.sw_channels    = 1;
+			devc->rport.sw_samplefmt   = sw_samplefmt;
+			devc->rport.sw_framerate   = 8000;
+			devc->rport.sw_fragshift   = DEFAULT_FRAGSHIFT;
+			devc->rport.sw_fragcount   = DEFAULT_FRAGCOUNT;
+			devc->rport.sw_subdivshift = DEFAULT_SUBDIVSHIFT;
+			devc->rport.byte_count     = 0;
+			devc->rport.frag_count     = 0;
+		}
+		if (file->f_mode & FMODE_WRITE) {
+			devc->wport.swstate        = SW_INITIAL;
+			devc->wport.flags          = 0;
+			devc->wport.sw_channels    = 1;
+			devc->wport.sw_samplefmt   = sw_samplefmt;
+			devc->wport.sw_framerate   = 8000;
+			devc->wport.sw_fragshift   = DEFAULT_FRAGSHIFT;
+			devc->wport.sw_fragcount   = DEFAULT_FRAGCOUNT;
+			devc->wport.sw_subdivshift = DEFAULT_SUBDIVSHIFT;
+			devc->wport.byte_count     = 0;
+			devc->wport.frag_count     = 0;
+		}
+	}
+	mutex_unlock(&devc->io_mutex);
+
+	file->private_data = devc;
+	DBGRV();
+	mutex_unlock(&vwsnd_mutex);
+	return 0;
+}
+
+/*
+ * Release (close) the audio device.
+ */
+
+static int vwsnd_audio_release(struct inode *inode, struct file *file)
+{
+	vwsnd_dev_t *devc = (vwsnd_dev_t *) file->private_data;
+	vwsnd_port_t *wport = NULL, *rport = NULL;
+	int err = 0;
+
+	mutex_lock(&vwsnd_mutex);
+	mutex_lock(&devc->io_mutex);
+	{
+		DBGEV("(inode=0x%p, file=0x%p)\n", inode, file);
+
+		if (file->f_mode & FMODE_READ)
+			rport = &devc->rport;
+		if (file->f_mode & FMODE_WRITE) {
+			wport = &devc->wport;
+			pcm_flush_frag(devc);
+			pcm_write_sync(devc);
+		}
+		pcm_shutdown(devc, rport, wport);
+		if (rport)
+			rport->swstate = SW_OFF;
+		if (wport)
+			wport->swstate = SW_OFF;
+	}
+	mutex_unlock(&devc->io_mutex);
+
+	mutex_lock(&devc->open_mutex);
+	{
+		devc->open_mode &= ~file->f_mode;
+	}
+	mutex_unlock(&devc->open_mutex);
+	wake_up(&devc->open_wait);
+	DEC_USE_COUNT;
+	DBGR();
+	mutex_unlock(&vwsnd_mutex);
+	return err;
+}
+
+static const struct file_operations vwsnd_audio_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.read =		vwsnd_audio_read,
+	.write =	vwsnd_audio_write,
+	.poll =		vwsnd_audio_poll,
+	.unlocked_ioctl = vwsnd_audio_ioctl,
+	.mmap =		vwsnd_audio_mmap,
+	.open =		vwsnd_audio_open,
+	.release =	vwsnd_audio_release,
+};
+
+/*****************************************************************************/
+/* mixer driver */
+
+/* open the mixer device. */
+
+static int vwsnd_mixer_open(struct inode *inode, struct file *file)
+{
+	vwsnd_dev_t *devc;
+
+	DBGEV("(inode=0x%p, file=0x%p)\n", inode, file);
+
+	INC_USE_COUNT;
+	mutex_lock(&vwsnd_mutex);
+	for (devc = vwsnd_dev_list; devc; devc = devc->next_dev)
+		if (devc->mixer_minor == iminor(inode))
+			break;
+
+	if (devc == NULL) {
+		DEC_USE_COUNT;
+		mutex_unlock(&vwsnd_mutex);
+		return -ENODEV;
+	}
+	file->private_data = devc;
+	mutex_unlock(&vwsnd_mutex);
+	return 0;
+}
+
+/* release (close) the mixer device. */
+
+static int vwsnd_mixer_release(struct inode *inode, struct file *file)
+{
+	DBGEV("(inode=0x%p, file=0x%p)\n", inode, file);
+	DEC_USE_COUNT;
+	return 0;
+}
+
+/* mixer_read_ioctl handles all read ioctls on the mixer device. */
+
+static int mixer_read_ioctl(vwsnd_dev_t *devc, unsigned int nr, void __user *arg)
+{
+	int val = -1;
+
+	DBGEV("(devc=0x%p, nr=0x%x, arg=0x%p)\n", devc, nr, arg);
+
+	switch (nr) {
+	case SOUND_MIXER_CAPS:
+		val = SOUND_CAP_EXCL_INPUT;
+		break;
+
+	case SOUND_MIXER_DEVMASK:
+		val = (SOUND_MASK_PCM | SOUND_MASK_LINE |
+		       SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_RECLEV);
+		break;
+
+	case SOUND_MIXER_STEREODEVS:
+		val = (SOUND_MASK_PCM | SOUND_MASK_LINE |
+		       SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_RECLEV);
+		break;
+
+	case SOUND_MIXER_OUTMASK:
+		val = (SOUND_MASK_PCM | SOUND_MASK_LINE |
+		       SOUND_MASK_MIC | SOUND_MASK_CD);
+		break;
+
+	case SOUND_MIXER_RECMASK:
+		val = (SOUND_MASK_PCM | SOUND_MASK_LINE |
+		       SOUND_MASK_MIC | SOUND_MASK_CD);
+		break;
+
+	case SOUND_MIXER_PCM:
+		val = ad1843_get_gain(&devc->lith, &ad1843_gain_PCM);
+		break;
+
+	case SOUND_MIXER_LINE:
+		val = ad1843_get_gain(&devc->lith, &ad1843_gain_LINE);
+		break;
+
+	case SOUND_MIXER_MIC:
+		val = ad1843_get_gain(&devc->lith, &ad1843_gain_MIC);
+		break;
+
+	case SOUND_MIXER_CD:
+		val = ad1843_get_gain(&devc->lith, &ad1843_gain_CD);
+		break;
+
+	case SOUND_MIXER_RECLEV:
+		val = ad1843_get_gain(&devc->lith, &ad1843_gain_RECLEV);
+		break;
+
+	case SOUND_MIXER_RECSRC:
+		val = ad1843_get_recsrc(&devc->lith);
+		break;
+
+	case SOUND_MIXER_OUTSRC:
+		val = ad1843_get_outsrc(&devc->lith);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	return put_user(val, (int __user *) arg);
+}
+
+/* mixer_write_ioctl handles all write ioctls on the mixer device. */
+
+static int mixer_write_ioctl(vwsnd_dev_t *devc, unsigned int nr, void __user *arg)
+{
+	int val;
+	int err;
+
+	DBGEV("(devc=0x%p, nr=0x%x, arg=0x%p)\n", devc, nr, arg);
+
+	err = get_user(val, (int __user *) arg);
+	if (err)
+		return -EFAULT;
+	switch (nr) {
+	case SOUND_MIXER_PCM:
+		val = ad1843_set_gain(&devc->lith, &ad1843_gain_PCM, val);
+		break;
+
+	case SOUND_MIXER_LINE:
+		val = ad1843_set_gain(&devc->lith, &ad1843_gain_LINE, val);
+		break;
+
+	case SOUND_MIXER_MIC:
+		val = ad1843_set_gain(&devc->lith, &ad1843_gain_MIC, val);
+		break;
+
+	case SOUND_MIXER_CD:
+		val = ad1843_set_gain(&devc->lith, &ad1843_gain_CD, val);
+		break;
+
+	case SOUND_MIXER_RECLEV:
+		val = ad1843_set_gain(&devc->lith, &ad1843_gain_RECLEV, val);
+		break;
+
+	case SOUND_MIXER_RECSRC:
+		if (devc->rport.swbuf || devc->wport.swbuf)
+			return -EBUSY;	/* can't change recsrc while running */
+		val = ad1843_set_recsrc(&devc->lith, val);
+		break;
+
+	case SOUND_MIXER_OUTSRC:
+		val = ad1843_set_outsrc(&devc->lith, val);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	if (val < 0)
+		return val;
+	return put_user(val, (int __user *) arg);
+}
+
+/* This is the ioctl entry to the mixer driver. */
+
+static long vwsnd_mixer_ioctl(struct file *file,
+			      unsigned int cmd,
+			      unsigned long arg)
+{
+	vwsnd_dev_t *devc = (vwsnd_dev_t *) file->private_data;
+	const unsigned int nrmask = _IOC_NRMASK << _IOC_NRSHIFT;
+	const unsigned int nr = (cmd & nrmask) >> _IOC_NRSHIFT;
+	int retval;
+
+	DBGEV("(devc=0x%p, cmd=0x%x, arg=0x%lx)\n", devc, cmd, arg);
+
+	mutex_lock(&vwsnd_mutex);
+	mutex_lock(&devc->mix_mutex);
+	{
+		if ((cmd & ~nrmask) == MIXER_READ(0))
+			retval = mixer_read_ioctl(devc, nr, (void __user *) arg);
+		else if ((cmd & ~nrmask) == MIXER_WRITE(0))
+			retval = mixer_write_ioctl(devc, nr, (void __user *) arg);
+		else
+			retval = -EINVAL;
+	}
+	mutex_unlock(&devc->mix_mutex);
+	mutex_unlock(&vwsnd_mutex);
+	return retval;
+}
+
+static const struct file_operations vwsnd_mixer_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.unlocked_ioctl = vwsnd_mixer_ioctl,
+	.open =		vwsnd_mixer_open,
+	.release =	vwsnd_mixer_release,
+};
+
+/*****************************************************************************/
+/* probe/attach/unload */
+
+/* driver probe routine.  Return nonzero if hardware is found. */
+
+static int __init probe_vwsnd(struct address_info *hw_config)
+{
+	lithium_t lith;
+	int w;
+	unsigned long later;
+
+	DBGEV("(hw_config=0x%p)\n", hw_config);
+
+	/* XXX verify lithium present (to prevent crash on non-vw) */
+
+	if (li_create(&lith, hw_config->io_base) != 0) {
+		printk(KERN_WARNING "probe_vwsnd: can't map lithium\n");
+		return 0;
+	}
+	later = jiffies + 2;
+	li_writel(&lith, LI_HOST_CONTROLLER, LI_HC_LINK_ENABLE);
+	do {
+		w = li_readl(&lith, LI_HOST_CONTROLLER);
+	} while (w == LI_HC_LINK_ENABLE && time_before(jiffies, later));
+	
+	li_destroy(&lith);
+
+	DBGPV("HC = 0x%04x\n", w);
+
+	if ((w == LI_HC_LINK_ENABLE) || (w & LI_HC_LINK_CODEC)) {
+
+		/* This may indicate a beta machine with no audio,
+		 * or a future machine with different audio.
+		 * On beta-release 320 w/ no audio, HC == 0x4000 */
+
+		printk(KERN_WARNING "probe_vwsnd: audio codec not found\n");
+		return 0;
+	}
+
+	if (w & LI_HC_LINK_FAILURE) {
+		printk(KERN_WARNING "probe_vwsnd: can't init audio codec\n");
+		return 0;
+	}
+
+	printk(KERN_INFO "vwsnd: lithium audio at mmio %#x irq %d\n",
+		hw_config->io_base, hw_config->irq);
+
+	return 1;
+}
+
+/*
+ * driver attach routine.  Initialize driver data structures and
+ * initialize hardware.  A new vwsnd_dev_t is allocated and put
+ * onto the global list, vwsnd_dev_list.
+ *
+ * Return +minor_dev on success, -errno on failure.
+ */
+
+static int __init attach_vwsnd(struct address_info *hw_config)
+{
+	vwsnd_dev_t *devc = NULL;
+	int err = -ENOMEM;
+
+	DBGEV("(hw_config=0x%p)\n", hw_config);
+
+	devc = kmalloc(sizeof (vwsnd_dev_t), GFP_KERNEL);
+	if (devc == NULL)
+		goto fail0;
+
+	err = li_create(&devc->lith, hw_config->io_base);
+	if (err)
+		goto fail1;
+
+	init_waitqueue_head(&devc->open_wait);
+
+	devc->rport.hwbuf_size = HWBUF_SIZE;
+	devc->rport.hwbuf_vaddr = __get_free_pages(GFP_KERNEL, HWBUF_ORDER);
+	if (!devc->rport.hwbuf_vaddr)
+		goto fail2;
+	devc->rport.hwbuf = (void *) devc->rport.hwbuf_vaddr;
+	devc->rport.hwbuf_paddr = virt_to_phys(devc->rport.hwbuf);
+
+	/*
+	 * Quote from the NT driver:
+	 *
+	 * // WARNING!!! HACK to setup output dma!!!
+	 * // This is required because even on output there is some data
+	 * // trickling into the input DMA channel.  This is a bug in the
+	 * // Lithium microcode.
+	 * // --sde
+	 *
+	 * We set the input side's DMA base address here.  It will remain
+	 * valid until the driver is unloaded.
+	 */
+
+	li_writel(&devc->lith, LI_COMM1_BASE,
+		  devc->rport.hwbuf_paddr >> 8 | 1 << (37 - 8));
+
+	devc->wport.hwbuf_size = HWBUF_SIZE;
+	devc->wport.hwbuf_vaddr = __get_free_pages(GFP_KERNEL, HWBUF_ORDER);
+	if (!devc->wport.hwbuf_vaddr)
+		goto fail3;
+	devc->wport.hwbuf = (void *) devc->wport.hwbuf_vaddr;
+	devc->wport.hwbuf_paddr = virt_to_phys(devc->wport.hwbuf);
+	DBGP("wport hwbuf = 0x%p\n", devc->wport.hwbuf);
+
+	DBGDO(shut_up++);
+	err = ad1843_init(&devc->lith);
+	DBGDO(shut_up--);
+	if (err)
+		goto fail4;
+
+	/* install interrupt handler */
+
+	err = request_irq(hw_config->irq, vwsnd_audio_intr, 0, "vwsnd", devc);
+	if (err)
+		goto fail5;
+
+	/* register this device's drivers. */
+
+	devc->audio_minor = register_sound_dsp(&vwsnd_audio_fops, -1);
+	if ((err = devc->audio_minor) < 0) {
+		DBGDO(printk(KERN_WARNING
+			     "attach_vwsnd: register_sound_dsp error %d\n",
+			     err));
+		goto fail6;
+	}
+	devc->mixer_minor = register_sound_mixer(&vwsnd_mixer_fops,
+						 devc->audio_minor >> 4);
+	if ((err = devc->mixer_minor) < 0) {
+		DBGDO(printk(KERN_WARNING
+			     "attach_vwsnd: register_sound_mixer error %d\n",
+			     err));
+		goto fail7;
+	}
+
+	/* Squirrel away device indices for unload routine. */
+
+	hw_config->slots[0] = devc->audio_minor;
+
+	/* Initialize as much of *devc as possible */
+
+	mutex_init(&devc->open_mutex);
+	mutex_init(&devc->io_mutex);
+	mutex_init(&devc->mix_mutex);
+	devc->open_mode = 0;
+	spin_lock_init(&devc->rport.lock);
+	init_waitqueue_head(&devc->rport.queue);
+	devc->rport.swstate = SW_OFF;
+	devc->rport.hwstate = HW_STOPPED;
+	devc->rport.flags = 0;
+	devc->rport.swbuf = NULL;
+	spin_lock_init(&devc->wport.lock);
+	init_waitqueue_head(&devc->wport.queue);
+	devc->wport.swstate = SW_OFF;
+	devc->wport.hwstate = HW_STOPPED;
+	devc->wport.flags = 0;
+	devc->wport.swbuf = NULL;
+
+	/* Success.  Link us onto the local device list. */
+
+	devc->next_dev = vwsnd_dev_list;
+	vwsnd_dev_list = devc;
+	return devc->audio_minor;
+
+	/* So many ways to fail.  Undo what we did. */
+
+ fail7:
+	unregister_sound_dsp(devc->audio_minor);
+ fail6:
+	free_irq(hw_config->irq, devc);
+ fail5:
+ fail4:
+	free_pages(devc->wport.hwbuf_vaddr, HWBUF_ORDER);
+ fail3:
+	free_pages(devc->rport.hwbuf_vaddr, HWBUF_ORDER);
+ fail2:
+	li_destroy(&devc->lith);
+ fail1:
+	kfree(devc);
+ fail0:
+	return err;
+}
+
+static int __exit unload_vwsnd(struct address_info *hw_config)
+{
+	vwsnd_dev_t *devc, **devcp;
+
+	DBGE("()\n");
+
+	devcp = &vwsnd_dev_list;
+	while ((devc = *devcp)) {
+		if (devc->audio_minor == hw_config->slots[0]) {
+			*devcp = devc->next_dev;
+			break;
+		}
+		devcp = &devc->next_dev;
+	}
+
+	if (!devc)
+		return -ENODEV;
+
+	unregister_sound_mixer(devc->mixer_minor);
+	unregister_sound_dsp(devc->audio_minor);
+	free_irq(hw_config->irq, devc);
+	free_pages(devc->wport.hwbuf_vaddr, HWBUF_ORDER);
+	free_pages(devc->rport.hwbuf_vaddr, HWBUF_ORDER);
+	li_destroy(&devc->lith);
+	kfree(devc);
+
+	return 0;
+}
+
+/*****************************************************************************/
+/* initialization and loadable kernel module interface */
+
+static struct address_info the_hw_config = {
+	0xFF001000,			/* lithium phys addr  */
+	CO_IRQ(CO_APIC_LI_AUDIO)	/* irq */
+};
+
+MODULE_DESCRIPTION("SGI Visual Workstation sound module");
+MODULE_AUTHOR("Bob Miller <kbob@sgi.com>");
+MODULE_LICENSE("GPL");
+
+static int __init init_vwsnd(void)
+{
+	int err;
+
+	DBGXV("\n");
+	DBGXV("sound::vwsnd::init_module()\n");
+
+	if (!probe_vwsnd(&the_hw_config))
+		return -ENODEV;
+
+	err = attach_vwsnd(&the_hw_config);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static void __exit cleanup_vwsnd(void)
+{
+	DBGX("sound::vwsnd::cleanup_module()\n");
+
+	unload_vwsnd(&the_hw_config);
+}
+
+module_init(init_vwsnd);
+module_exit(cleanup_vwsnd);
diff --git a/linux/sound/oss/waveartist.c b/linux/sound/oss/waveartist.c
new file mode 100644
index 0000000..5246874
--- /dev/null
+++ b/linux/sound/oss/waveartist.c
@@ -0,0 +1,2025 @@
+/*
+ * linux/sound/oss/waveartist.c
+ *
+ * The low level driver for the RWA010 Rockwell Wave Artist
+ * codec chip used in the Rebel.com NetWinder.
+ *
+ * Cleaned up and integrated into 2.1 by Russell King (rmk@arm.linux.org.uk)
+ * and Pat Beirne (patb@corel.ca)
+ *
+ *
+ * Copyright (C) by Rebel.com 1998-1999
+ *
+ * RWA010 specs received under NDA from Rockwell
+ *
+ * Copyright (C) by Hannu Savolainen 1993-1997
+ *
+ * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
+ * Version 2 (June 1991). See the "COPYING" file distributed with this software
+ * for more info.
+ *
+ * Changes:
+ * 11-10-2000	Bartlomiej Zolnierkiewicz <bkz@linux-ide.org>
+ *		Added __init to waveartist_init()
+ */
+
+/* Debugging */
+#define DEBUG_CMD	1
+#define DEBUG_OUT	2
+#define DEBUG_IN	4
+#define DEBUG_INTR	8
+#define DEBUG_MIXER	16
+#define DEBUG_TRIGGER	32
+
+#define debug_flg (0)
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+
+#include "sound_config.h"
+#include "waveartist.h"
+
+#ifdef CONFIG_ARM
+#include <mach/hardware.h>
+#include <asm/mach-types.h>
+#endif
+
+#ifndef NO_DMA
+#define NO_DMA	255
+#endif
+
+#define SUPPORTED_MIXER_DEVICES		(SOUND_MASK_SYNTH      |\
+					 SOUND_MASK_PCM        |\
+					 SOUND_MASK_LINE       |\
+					 SOUND_MASK_MIC        |\
+					 SOUND_MASK_LINE1      |\
+					 SOUND_MASK_RECLEV     |\
+					 SOUND_MASK_VOLUME     |\
+					 SOUND_MASK_IMIX)
+
+static unsigned short levels[SOUND_MIXER_NRDEVICES] = {
+	0x5555,		/* Master Volume	 */
+	0x0000,		/* Bass			 */
+	0x0000,		/* Treble		 */
+	0x2323,		/* Synth (FM)		 */
+	0x4b4b,		/* PCM			 */
+	0x6464,		/* PC Speaker		 */
+	0x0000,		/* Ext Line		 */
+	0x0000,		/* Mic			 */
+	0x0000,		/* CD			 */
+	0x6464,		/* Recording monitor	 */
+	0x0000,		/* SB PCM (ALT PCM)	 */
+	0x0000,		/* Recording level	 */
+	0x6464,		/* Input gain		 */
+	0x6464,		/* Output gain		 */
+	0x0000,		/* Line1 (Aux1)		 */
+	0x0000,		/* Line2 (Aux2)		 */
+	0x0000,		/* Line3 (Aux3)		 */
+	0x0000,		/* Digital1		 */
+	0x0000,		/* Digital2		 */
+	0x0000,		/* Digital3		 */
+	0x0000,		/* Phone In		 */
+	0x6464,		/* Phone Out		 */
+	0x0000,		/* Video		 */
+	0x0000,		/* Radio		 */
+	0x0000		/* Monitor		 */
+};
+
+typedef struct {
+	struct address_info  hw;	/* hardware */
+	char		*chip_name;
+
+	int		xfer_count;
+	int		audio_mode;
+	int		open_mode;
+	int		audio_flags;
+	int		record_dev;
+	int		playback_dev;
+	int		dev_no;
+
+	/* Mixer parameters */
+	const struct waveartist_mixer_info *mix;
+
+	unsigned short	*levels;	   /* cache of volume settings   */
+	int		recmask;	   /* currently enabled recording device! */
+
+#ifdef CONFIG_ARCH_NETWINDER
+	signed int	slider_vol;	   /* hardware slider volume     */
+	unsigned int	handset_detect	:1;
+	unsigned int	telephone_detect:1;
+	unsigned int	no_autoselect	:1;/* handset/telephone autoselects a path */
+	unsigned int	spkr_mute_state	:1;/* set by ioctl or autoselect */
+	unsigned int	line_mute_state	:1;/* set by ioctl or autoselect */
+	unsigned int	use_slider	:1;/* use slider setting for o/p vol */
+#endif
+} wavnc_info;
+
+/*
+ * This is the implementation specific mixer information.
+ */
+struct waveartist_mixer_info {
+	unsigned int	supported_devs;	   /* Supported devices */
+	unsigned int	recording_devs;	   /* Recordable devies */
+	unsigned int	stereo_devs;	   /* Stereo devices	*/
+
+	unsigned int	(*select_input)(wavnc_info *, unsigned int,
+					unsigned char *, unsigned char *);
+	int		(*decode_mixer)(wavnc_info *, int,
+					unsigned char, unsigned char);
+	int		(*get_mixer)(wavnc_info *, int);
+};
+
+typedef struct wavnc_port_info {
+	int		open_mode;
+	int		speed;
+	int		channels;
+	int		audio_format;
+} wavnc_port_info;
+
+static int		nr_waveartist_devs;
+static wavnc_info	adev_info[MAX_AUDIO_DEV];
+static DEFINE_SPINLOCK(waveartist_lock);
+
+#ifndef CONFIG_ARCH_NETWINDER
+#define machine_is_netwinder() 0
+#else
+static struct timer_list vnc_timer;
+static void vnc_configure_mixer(wavnc_info *devc, unsigned int input_mask);
+static int vnc_private_ioctl(int dev, unsigned int cmd, int __user *arg);
+static void vnc_slider_tick(unsigned long data);
+#endif
+
+static inline void
+waveartist_set_ctlr(struct address_info *hw, unsigned char clear, unsigned char set)
+{
+	unsigned int ctlr_port = hw->io_base + CTLR;
+
+	clear = ~clear & inb(ctlr_port);
+
+	outb(clear | set, ctlr_port);
+}
+
+/* Toggle IRQ acknowledge line
+ */
+static inline void
+waveartist_iack(wavnc_info *devc)
+{
+	unsigned int ctlr_port = devc->hw.io_base + CTLR;
+	int old_ctlr;
+
+	old_ctlr = inb(ctlr_port) & ~IRQ_ACK;
+
+	outb(old_ctlr | IRQ_ACK, ctlr_port);
+	outb(old_ctlr, ctlr_port);
+}
+
+static inline int
+waveartist_sleep(int timeout_ms)
+{
+	unsigned int timeout = msecs_to_jiffies(timeout_ms*100);
+	return schedule_timeout_interruptible(timeout);
+}
+
+static int
+waveartist_reset(wavnc_info *devc)
+{
+	struct address_info *hw = &devc->hw;
+	unsigned int timeout, res = -1;
+
+	waveartist_set_ctlr(hw, -1, RESET);
+	waveartist_sleep(2);
+	waveartist_set_ctlr(hw, RESET, 0);
+
+	timeout = 500;
+	do {
+		mdelay(2);
+
+		if (inb(hw->io_base + STATR) & CMD_RF) {
+			res = inw(hw->io_base + CMDR);
+			if (res == 0x55aa)
+				break;
+		}
+	} while (--timeout);
+
+	if (timeout == 0) {
+		printk(KERN_WARNING "WaveArtist: reset timeout ");
+		if (res != (unsigned int)-1)
+			printk("(res=%04X)", res);
+		printk("\n");
+		return 1;
+	}
+	return 0;
+}
+
+/* Helper function to send and receive words
+ * from WaveArtist.  It handles all the handshaking
+ * and can send or receive multiple words.
+ */
+static int
+waveartist_cmd(wavnc_info *devc,
+		int nr_cmd, unsigned int *cmd,
+		int nr_resp, unsigned int *resp)
+{
+	unsigned int io_base = devc->hw.io_base;
+	unsigned int timed_out = 0;
+	unsigned int i;
+
+	if (debug_flg & DEBUG_CMD) {
+		printk("waveartist_cmd: cmd=");
+
+		for (i = 0; i < nr_cmd; i++)
+			printk("%04X ", cmd[i]);
+
+		printk("\n");
+	}
+
+	if (inb(io_base + STATR) & CMD_RF) {
+		int old_data;
+
+		/* flush the port
+		 */
+
+		old_data = inw(io_base + CMDR);
+
+		if (debug_flg & DEBUG_CMD)
+			printk("flushed %04X...", old_data);
+
+		udelay(10);
+	}
+
+	for (i = 0; !timed_out && i < nr_cmd; i++) {
+		int count;
+
+		for (count = 5000; count; count--)
+			if (inb(io_base + STATR) & CMD_WE)
+				break;
+
+		if (!count)
+			timed_out = 1;
+		else
+			outw(cmd[i], io_base + CMDR);
+	}
+
+	for (i = 0; !timed_out && i < nr_resp; i++) {
+		int count;
+
+		for (count = 5000; count; count--)
+			if (inb(io_base + STATR) & CMD_RF)
+				break;
+
+		if (!count)
+			timed_out = 1;
+		else
+			resp[i] = inw(io_base + CMDR);
+	}
+
+	if (debug_flg & DEBUG_CMD) {
+		if (!timed_out) {
+			printk("waveartist_cmd: resp=");
+
+			for (i = 0; i < nr_resp; i++)
+				printk("%04X ", resp[i]);
+
+			printk("\n");
+		} else
+			printk("waveartist_cmd: timed out\n");
+	}
+
+	return timed_out ? 1 : 0;
+}
+
+/*
+ * Send one command word
+ */
+static inline int
+waveartist_cmd1(wavnc_info *devc, unsigned int cmd)
+{
+	return waveartist_cmd(devc, 1, &cmd, 0, NULL);
+}
+
+/*
+ * Send one command, receive one word
+ */
+static inline unsigned int
+waveartist_cmd1_r(wavnc_info *devc, unsigned int cmd)
+{
+	unsigned int ret;
+
+	waveartist_cmd(devc, 1, &cmd, 1, &ret);
+
+	return ret;
+}
+
+/*
+ * Send a double command, receive one
+ * word (and throw it away)
+ */
+static inline int
+waveartist_cmd2(wavnc_info *devc, unsigned int cmd, unsigned int arg)
+{
+	unsigned int vals[2];
+
+	vals[0] = cmd;
+	vals[1] = arg;
+
+	return waveartist_cmd(devc, 2, vals, 1, vals);
+}
+
+/*
+ * Send a triple command
+ */
+static inline int
+waveartist_cmd3(wavnc_info *devc, unsigned int cmd,
+		unsigned int arg1, unsigned int arg2)
+{
+	unsigned int vals[3];
+
+	vals[0] = cmd;
+	vals[1] = arg1;
+	vals[2] = arg2;
+
+	return waveartist_cmd(devc, 3, vals, 0, NULL);
+}
+
+static int
+waveartist_getrev(wavnc_info *devc, char *rev)
+{
+	unsigned int temp[2];
+	unsigned int cmd = WACMD_GETREV;
+
+	waveartist_cmd(devc, 1, &cmd, 2, temp);
+
+	rev[0] = temp[0] >> 8;
+	rev[1] = temp[0] & 255;
+	rev[2] = '\0';
+
+	return temp[0];
+}
+
+static void waveartist_halt_output(int dev);
+static void waveartist_halt_input(int dev);
+static void waveartist_halt(int dev);
+static void waveartist_trigger(int dev, int state);
+
+static int
+waveartist_open(int dev, int mode)
+{
+	wavnc_info	*devc;
+	wavnc_port_info	*portc;
+	unsigned long	flags;
+
+	if (dev < 0 || dev >= num_audiodevs)
+		return -ENXIO;
+
+	devc  = (wavnc_info *) audio_devs[dev]->devc;
+	portc = (wavnc_port_info *) audio_devs[dev]->portc;
+
+	spin_lock_irqsave(&waveartist_lock, flags);
+	if (portc->open_mode || (devc->open_mode & mode)) {
+		spin_unlock_irqrestore(&waveartist_lock, flags);
+		return -EBUSY;
+	}
+
+	devc->audio_mode  = 0;
+	devc->open_mode  |= mode;
+	portc->open_mode  = mode;
+	waveartist_trigger(dev, 0);
+
+	if (mode & OPEN_READ)
+		devc->record_dev = dev;
+	if (mode & OPEN_WRITE)
+		devc->playback_dev = dev;
+	spin_unlock_irqrestore(&waveartist_lock, flags);
+
+	return 0;
+}
+
+static void
+waveartist_close(int dev)
+{
+	wavnc_info	*devc = (wavnc_info *) audio_devs[dev]->devc;
+	wavnc_port_info	*portc = (wavnc_port_info *) audio_devs[dev]->portc;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&waveartist_lock, flags);
+
+	waveartist_halt(dev);
+
+	devc->audio_mode = 0;
+	devc->open_mode &= ~portc->open_mode;
+	portc->open_mode = 0;
+
+	spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static void
+waveartist_output_block(int dev, unsigned long buf, int __count, int intrflag)
+{
+	wavnc_port_info	*portc = (wavnc_port_info *) audio_devs[dev]->portc;
+	wavnc_info	*devc = (wavnc_info *) audio_devs[dev]->devc;
+	unsigned long	flags;
+	unsigned int	count = __count; 
+
+	if (debug_flg & DEBUG_OUT)
+		printk("waveartist: output block, buf=0x%lx, count=0x%x...\n",
+			buf, count);
+	/*
+	 * 16 bit data
+	 */
+	if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE))
+		count >>= 1;
+
+	if (portc->channels > 1)
+		count >>= 1;
+
+	count -= 1;
+
+	if (devc->audio_mode & PCM_ENABLE_OUTPUT &&
+	    audio_devs[dev]->flags & DMA_AUTOMODE &&
+	    intrflag &&
+	    count == devc->xfer_count) {
+		devc->audio_mode |= PCM_ENABLE_OUTPUT;
+		return;	/*
+			 * Auto DMA mode on. No need to react
+			 */
+	}
+
+	spin_lock_irqsave(&waveartist_lock, flags);
+
+	/*
+	 * set sample count
+	 */
+	waveartist_cmd2(devc, WACMD_OUTPUTSIZE, count);
+
+	devc->xfer_count = count;
+	devc->audio_mode |= PCM_ENABLE_OUTPUT;
+
+	spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static void
+waveartist_start_input(int dev, unsigned long buf, int __count, int intrflag)
+{
+	wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+	wavnc_info	*devc = (wavnc_info *) audio_devs[dev]->devc;
+	unsigned long	flags;
+	unsigned int	count = __count;
+
+	if (debug_flg & DEBUG_IN)
+		printk("waveartist: start input, buf=0x%lx, count=0x%x...\n",
+			buf, count);
+
+	if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE))	/* 16 bit data */
+		count >>= 1;
+
+	if (portc->channels > 1)
+		count >>= 1;
+
+	count -= 1;
+
+	if (devc->audio_mode & PCM_ENABLE_INPUT &&
+	    audio_devs[dev]->flags & DMA_AUTOMODE &&
+	    intrflag &&
+	    count == devc->xfer_count) {
+		devc->audio_mode |= PCM_ENABLE_INPUT;
+		return;	/*
+			 * Auto DMA mode on. No need to react
+			 */
+	}
+
+	spin_lock_irqsave(&waveartist_lock, flags);
+
+	/*
+	 * set sample count
+	 */
+	waveartist_cmd2(devc, WACMD_INPUTSIZE, count);
+
+	devc->xfer_count = count;
+	devc->audio_mode |= PCM_ENABLE_INPUT;
+
+	spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static int
+waveartist_ioctl(int dev, unsigned int cmd, void __user * arg)
+{
+	return -EINVAL;
+}
+
+static unsigned int
+waveartist_get_speed(wavnc_port_info *portc)
+{
+	unsigned int speed;
+
+	/*
+	 * program the speed, channels, bits
+	 */
+	if (portc->speed == 8000)
+		speed = 0x2E71;
+	else if (portc->speed == 11025)
+		speed = 0x4000;
+	else if (portc->speed == 22050)
+		speed = 0x8000;
+	else if (portc->speed == 44100)
+		speed = 0x0;
+	else {
+		/*
+		 * non-standard - just calculate
+		 */
+		speed = portc->speed << 16;
+
+		speed = (speed / 44100) & 65535;
+	}
+
+	return speed;
+}
+
+static unsigned int
+waveartist_get_bits(wavnc_port_info *portc)
+{
+	unsigned int bits;
+
+	if (portc->audio_format == AFMT_S16_LE)
+		bits = 1;
+	else if (portc->audio_format == AFMT_S8)
+		bits = 0;
+	else
+		bits = 2;	//default AFMT_U8
+
+	return bits;
+}
+
+static int
+waveartist_prepare_for_input(int dev, int bsize, int bcount)
+{
+	unsigned long	flags;
+	wavnc_info	*devc = (wavnc_info *) audio_devs[dev]->devc;
+	wavnc_port_info	*portc = (wavnc_port_info *) audio_devs[dev]->portc;
+	unsigned int	speed, bits;
+
+	if (devc->audio_mode)
+		return 0;
+
+	speed = waveartist_get_speed(portc);
+	bits  = waveartist_get_bits(portc);
+
+	spin_lock_irqsave(&waveartist_lock, flags);
+
+	if (waveartist_cmd2(devc, WACMD_INPUTFORMAT, bits))
+		printk(KERN_WARNING "waveartist: error setting the "
+		       "record format to %d\n", portc->audio_format);
+
+	if (waveartist_cmd2(devc, WACMD_INPUTCHANNELS, portc->channels))
+		printk(KERN_WARNING "waveartist: error setting record "
+		       "to %d channels\n", portc->channels);
+
+	/*
+	 * write cmd SetSampleSpeedTimeConstant
+	 */
+	if (waveartist_cmd2(devc, WACMD_INPUTSPEED, speed))
+		printk(KERN_WARNING "waveartist: error setting the record "
+		       "speed to %dHz.\n", portc->speed);
+
+	if (waveartist_cmd2(devc, WACMD_INPUTDMA, 1))
+		printk(KERN_WARNING "waveartist: error setting the record "
+		       "data path to 0x%X\n", 1);
+
+	if (waveartist_cmd2(devc, WACMD_INPUTFORMAT, bits))
+		printk(KERN_WARNING "waveartist: error setting the record "
+		       "format to %d\n", portc->audio_format);
+
+	devc->xfer_count = 0;
+	spin_unlock_irqrestore(&waveartist_lock, flags);
+	waveartist_halt_input(dev);
+
+	if (debug_flg & DEBUG_INTR) {
+		printk("WA CTLR reg: 0x%02X.\n",
+		       inb(devc->hw.io_base + CTLR));
+		printk("WA STAT reg: 0x%02X.\n",
+		       inb(devc->hw.io_base + STATR));
+		printk("WA IRQS reg: 0x%02X.\n",
+		       inb(devc->hw.io_base + IRQSTAT));
+	}
+
+	return 0;
+}
+
+static int
+waveartist_prepare_for_output(int dev, int bsize, int bcount)
+{
+	unsigned long	flags;
+	wavnc_info	*devc = (wavnc_info *) audio_devs[dev]->devc;
+	wavnc_port_info	*portc = (wavnc_port_info *) audio_devs[dev]->portc;
+	unsigned int	speed, bits;
+
+	/*
+	 * program the speed, channels, bits
+	 */
+	speed = waveartist_get_speed(portc);
+	bits  = waveartist_get_bits(portc);
+
+	spin_lock_irqsave(&waveartist_lock, flags);
+
+	if (waveartist_cmd2(devc, WACMD_OUTPUTSPEED, speed) &&
+	    waveartist_cmd2(devc, WACMD_OUTPUTSPEED, speed))
+		printk(KERN_WARNING "waveartist: error setting the playback "
+		       "speed to %dHz.\n", portc->speed);
+
+	if (waveartist_cmd2(devc, WACMD_OUTPUTCHANNELS, portc->channels))
+		printk(KERN_WARNING "waveartist: error setting the playback "
+		       "to %d channels\n", portc->channels);
+
+	if (waveartist_cmd2(devc, WACMD_OUTPUTDMA, 0))
+		printk(KERN_WARNING "waveartist: error setting the playback "
+		       "data path to 0x%X\n", 0);
+
+	if (waveartist_cmd2(devc, WACMD_OUTPUTFORMAT, bits))
+		printk(KERN_WARNING "waveartist: error setting the playback "
+		       "format to %d\n", portc->audio_format);
+
+	devc->xfer_count = 0;
+	spin_unlock_irqrestore(&waveartist_lock, flags);
+	waveartist_halt_output(dev);
+
+	if (debug_flg & DEBUG_INTR) {
+		printk("WA CTLR reg: 0x%02X.\n",inb(devc->hw.io_base + CTLR));
+		printk("WA STAT reg: 0x%02X.\n",inb(devc->hw.io_base + STATR));
+		printk("WA IRQS reg: 0x%02X.\n",inb(devc->hw.io_base + IRQSTAT));
+	}
+
+	return 0;
+}
+
+static void
+waveartist_halt(int dev)
+{
+	wavnc_port_info	*portc = (wavnc_port_info *) audio_devs[dev]->portc;
+	wavnc_info	*devc;
+
+	if (portc->open_mode & OPEN_WRITE)
+		waveartist_halt_output(dev);
+
+	if (portc->open_mode & OPEN_READ)
+		waveartist_halt_input(dev);
+
+	devc = (wavnc_info *) audio_devs[dev]->devc;
+	devc->audio_mode = 0;
+}
+
+static void
+waveartist_halt_input(int dev)
+{
+	wavnc_info	*devc = (wavnc_info *) audio_devs[dev]->devc;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&waveartist_lock, flags);
+
+	/*
+	 * Stop capture
+	 */
+	waveartist_cmd1(devc, WACMD_INPUTSTOP);
+
+	devc->audio_mode &= ~PCM_ENABLE_INPUT;
+
+	/*
+	 * Clear interrupt by toggling
+	 * the IRQ_ACK bit in CTRL
+	 */
+	if (inb(devc->hw.io_base + STATR) & IRQ_REQ)
+		waveartist_iack(devc);
+
+//	devc->audio_mode &= ~PCM_ENABLE_INPUT;
+
+	spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static void
+waveartist_halt_output(int dev)
+{
+	wavnc_info	*devc = (wavnc_info *) audio_devs[dev]->devc;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&waveartist_lock, flags);
+
+	waveartist_cmd1(devc, WACMD_OUTPUTSTOP);
+
+	devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
+
+	/*
+	 * Clear interrupt by toggling
+	 * the IRQ_ACK bit in CTRL
+	 */
+	if (inb(devc->hw.io_base + STATR) & IRQ_REQ)
+		waveartist_iack(devc);
+
+//	devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
+
+	spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static void
+waveartist_trigger(int dev, int state)
+{
+	wavnc_info	*devc = (wavnc_info *) audio_devs[dev]->devc;
+	wavnc_port_info	*portc = (wavnc_port_info *) audio_devs[dev]->portc;
+	unsigned long	flags;
+
+	if (debug_flg & DEBUG_TRIGGER) {
+		printk("wavnc: audio trigger ");
+		if (state & PCM_ENABLE_INPUT)
+			printk("in ");
+		if (state & PCM_ENABLE_OUTPUT)
+			printk("out");
+		printk("\n");
+	}
+
+	spin_lock_irqsave(&waveartist_lock, flags);
+
+	state &= devc->audio_mode;
+
+	if (portc->open_mode & OPEN_READ &&
+	    state & PCM_ENABLE_INPUT)
+		/*
+		 * enable ADC Data Transfer to PC
+		 */
+		waveartist_cmd1(devc, WACMD_INPUTSTART);
+
+	if (portc->open_mode & OPEN_WRITE &&
+	    state & PCM_ENABLE_OUTPUT)
+		/*
+		 * enable DAC data transfer from PC
+		 */
+		waveartist_cmd1(devc, WACMD_OUTPUTSTART);
+
+	spin_unlock_irqrestore(&waveartist_lock, flags);
+}
+
+static int
+waveartist_set_speed(int dev, int arg)
+{
+	wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+
+	if (arg <= 0)
+		return portc->speed;
+
+	if (arg < 5000)
+		arg = 5000;
+	if (arg > 44100)
+		arg = 44100;
+
+	portc->speed = arg;
+	return portc->speed;
+
+}
+
+static short
+waveartist_set_channels(int dev, short arg)
+{
+	wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+
+	if (arg != 1 && arg != 2)
+		return portc->channels;
+
+	portc->channels = arg;
+	return arg;
+}
+
+static unsigned int
+waveartist_set_bits(int dev, unsigned int arg)
+{
+	wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
+
+	if (arg == 0)
+		return portc->audio_format;
+
+	if ((arg != AFMT_U8) && (arg != AFMT_S16_LE) && (arg != AFMT_S8))
+		arg = AFMT_U8;
+
+	portc->audio_format = arg;
+
+	return arg;
+}
+
+static struct audio_driver waveartist_audio_driver = {
+	.owner			= THIS_MODULE,
+	.open			= waveartist_open,
+	.close			= waveartist_close,
+	.output_block		= waveartist_output_block,
+	.start_input		= waveartist_start_input,
+	.ioctl			= waveartist_ioctl,
+	.prepare_for_input	= waveartist_prepare_for_input,
+	.prepare_for_output	= waveartist_prepare_for_output,
+	.halt_io		= waveartist_halt,
+	.halt_input		= waveartist_halt_input,
+	.halt_output		= waveartist_halt_output,
+	.trigger		= waveartist_trigger,
+	.set_speed		= waveartist_set_speed,
+	.set_bits		= waveartist_set_bits,
+	.set_channels		= waveartist_set_channels
+};
+
+
+static irqreturn_t
+waveartist_intr(int irq, void *dev_id)
+{
+	wavnc_info *devc = dev_id;
+	int	   irqstatus, status;
+
+	spin_lock(&waveartist_lock);
+	irqstatus = inb(devc->hw.io_base + IRQSTAT);
+	status    = inb(devc->hw.io_base + STATR);
+
+	if (debug_flg & DEBUG_INTR)
+		printk("waveartist_intr: stat=%02x, irqstat=%02x\n",
+		       status, irqstatus);
+
+	if (status & IRQ_REQ)	/* Clear interrupt */
+		waveartist_iack(devc);
+	else
+		printk(KERN_WARNING "waveartist: unexpected interrupt\n");
+
+	if (irqstatus & 0x01) {
+		int temp = 1;
+
+		/* PCM buffer done
+		 */
+		if ((status & DMA0) && (devc->audio_mode & PCM_ENABLE_OUTPUT)) {
+			DMAbuf_outputintr(devc->playback_dev, 1);
+			temp = 0;
+		}
+		if ((status & DMA1) && (devc->audio_mode & PCM_ENABLE_INPUT)) {
+			DMAbuf_inputintr(devc->record_dev);
+			temp = 0;
+		}
+		if (temp)	//default:
+			printk(KERN_WARNING "waveartist: Unknown interrupt\n");
+	}
+	if (irqstatus & 0x2)
+		// We do not use SB mode natively...
+		printk(KERN_WARNING "waveartist: Unexpected SB interrupt...\n");
+	spin_unlock(&waveartist_lock);
+	return IRQ_HANDLED;
+}
+
+/* -------------------------------------------------------------------------
+ * Mixer stuff
+ */
+struct mix_ent {
+	unsigned char	reg_l;
+	unsigned char	reg_r;
+	unsigned char	shift;
+	unsigned char	max;
+};
+
+static const struct mix_ent mix_devs[SOUND_MIXER_NRDEVICES] = {
+	{ 2, 6, 1,  7 }, /* SOUND_MIXER_VOLUME   */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_BASS     */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_TREBLE   */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_SYNTH    */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_PCM      */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_SPEAKER  */
+	{ 0, 4, 6, 31 }, /* SOUND_MIXER_LINE     */
+	{ 2, 6, 4,  3 }, /* SOUND_MIXER_MIC      */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_CD       */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_IMIX     */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_ALTPCM   */
+#if 0
+	{ 3, 7, 0, 10 }, /* SOUND_MIXER_RECLEV   */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_IGAIN    */
+#else
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_RECLEV   */
+	{ 3, 7, 0,  7 }, /* SOUND_MIXER_IGAIN    */
+#endif
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_OGAIN    */
+	{ 0, 4, 1, 31 }, /* SOUND_MIXER_LINE1    */
+	{ 1, 5, 6, 31 }, /* SOUND_MIXER_LINE2    */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_LINE3    */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_DIGITAL1 */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_DIGITAL2 */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_DIGITAL3 */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_PHONEIN  */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_PHONEOUT */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_VIDEO    */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_RADIO    */
+	{ 0, 0, 0,  0 }  /* SOUND_MIXER_MONITOR  */
+};
+
+static void
+waveartist_mixer_update(wavnc_info *devc, int whichDev)
+{
+	unsigned int lev_left, lev_right;
+
+	lev_left  = devc->levels[whichDev] & 0xff;
+	lev_right = devc->levels[whichDev] >> 8;
+
+	if (lev_left > 100)
+		lev_left = 100;
+	if (lev_right > 100)
+		lev_right = 100;
+
+#define SCALE(lev,max)	((lev) * (max) / 100)
+
+	if (machine_is_netwinder() && whichDev == SOUND_MIXER_PHONEOUT)
+		whichDev = SOUND_MIXER_VOLUME;
+
+	if (mix_devs[whichDev].reg_l || mix_devs[whichDev].reg_r) {
+		const struct mix_ent *mix = mix_devs + whichDev;
+		unsigned int mask, left, right;
+
+		mask = mix->max << mix->shift;
+		lev_left  = SCALE(lev_left,  mix->max) << mix->shift;
+		lev_right = SCALE(lev_right, mix->max) << mix->shift;
+
+		/* read left setting */
+		left  = waveartist_cmd1_r(devc, WACMD_GET_LEVEL |
+					       mix->reg_l << 8);
+
+		/* read right setting */
+		right = waveartist_cmd1_r(devc, WACMD_GET_LEVEL |
+						mix->reg_r << 8);
+
+		left  = (left  & ~mask) | (lev_left  & mask);
+		right = (right & ~mask) | (lev_right & mask);
+
+		/* write left,right back */
+		waveartist_cmd3(devc, WACMD_SET_MIXER, left, right);
+	} else {
+		switch(whichDev) {
+		case SOUND_MIXER_PCM:
+			waveartist_cmd3(devc, WACMD_SET_LEVEL,
+					SCALE(lev_left,  32767),
+					SCALE(lev_right, 32767));
+			break;
+
+		case SOUND_MIXER_SYNTH:
+			waveartist_cmd3(devc, 0x0100 | WACMD_SET_LEVEL,
+					SCALE(lev_left,  32767),
+					SCALE(lev_right, 32767));
+			break;
+		}
+	}
+}
+
+/*
+ * Set the ADC MUX to the specified values.  We do NOT do any
+ * checking of the values passed, since we assume that the
+ * relevant *_select_input function has done that for us.
+ */
+static void
+waveartist_set_adc_mux(wavnc_info *devc, char left_dev, char right_dev)
+{
+	unsigned int reg_08, reg_09;
+
+	reg_08 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x0800);
+	reg_09 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x0900);
+
+	reg_08 = (reg_08 & ~0x3f) | right_dev << 3 | left_dev;
+
+	waveartist_cmd3(devc, WACMD_SET_MIXER, reg_08, reg_09);
+}
+
+/*
+ * Decode a recording mask into a mixer selection as follows:
+ *
+ *     OSS Source	WA Source	Actual source
+ *  SOUND_MASK_IMIX	Mixer		Mixer output (same as AD1848)
+ *  SOUND_MASK_LINE	Line		Line in
+ *  SOUND_MASK_LINE1	Aux 1		Aux 1 in
+ *  SOUND_MASK_LINE2	Aux 2		Aux 2 in
+ *  SOUND_MASK_MIC	Mic		Microphone
+ */
+static unsigned int
+waveartist_select_input(wavnc_info *devc, unsigned int recmask,
+			unsigned char *dev_l, unsigned char *dev_r)
+{
+	unsigned int recdev = ADC_MUX_NONE;
+
+	if (recmask & SOUND_MASK_IMIX) {
+		recmask = SOUND_MASK_IMIX;
+		recdev = ADC_MUX_MIXER;
+	} else if (recmask & SOUND_MASK_LINE2) {
+		recmask = SOUND_MASK_LINE2;
+		recdev = ADC_MUX_AUX2;
+	} else if (recmask & SOUND_MASK_LINE1) {
+		recmask = SOUND_MASK_LINE1;
+		recdev = ADC_MUX_AUX1;
+	} else if (recmask & SOUND_MASK_LINE) {
+		recmask = SOUND_MASK_LINE;
+		recdev = ADC_MUX_LINE;
+	} else if (recmask & SOUND_MASK_MIC) {
+		recmask = SOUND_MASK_MIC;
+		recdev = ADC_MUX_MIC;
+	}
+
+	*dev_l = *dev_r = recdev;
+
+	return recmask;
+}
+
+static int
+waveartist_decode_mixer(wavnc_info *devc, int dev, unsigned char lev_l,
+			unsigned char lev_r)
+{
+	switch (dev) {
+	case SOUND_MIXER_VOLUME:
+	case SOUND_MIXER_SYNTH:
+	case SOUND_MIXER_PCM:
+	case SOUND_MIXER_LINE:
+	case SOUND_MIXER_MIC:
+	case SOUND_MIXER_IGAIN:
+	case SOUND_MIXER_LINE1:
+	case SOUND_MIXER_LINE2:
+		devc->levels[dev] = lev_l | lev_r << 8;
+		break;
+
+	case SOUND_MIXER_IMIX:
+		break;
+
+	default:
+		dev = -EINVAL;
+		break;
+	}
+
+	return dev;
+}
+
+static int waveartist_get_mixer(wavnc_info *devc, int dev)
+{
+	return devc->levels[dev];
+}
+
+static const struct waveartist_mixer_info waveartist_mixer = {
+	.supported_devs	= SUPPORTED_MIXER_DEVICES | SOUND_MASK_IGAIN,
+	.recording_devs	= SOUND_MASK_LINE  | SOUND_MASK_MIC   |
+			SOUND_MASK_LINE1 | SOUND_MASK_LINE2 |
+			SOUND_MASK_IMIX,
+	.stereo_devs	= (SUPPORTED_MIXER_DEVICES | SOUND_MASK_IGAIN) & ~
+			(SOUND_MASK_SPEAKER | SOUND_MASK_IMIX),
+	.select_input	= waveartist_select_input,
+	.decode_mixer	= waveartist_decode_mixer,
+	.get_mixer	= waveartist_get_mixer,
+};
+
+static void
+waveartist_set_recmask(wavnc_info *devc, unsigned int recmask)
+{
+	unsigned char dev_l, dev_r;
+
+	recmask &= devc->mix->recording_devs;
+
+	/*
+	 * If more than one recording device selected,
+	 * disable the device that is currently in use.
+	 */
+	if (hweight32(recmask) > 1)
+		recmask &= ~devc->recmask;
+
+	/*
+	 * Translate the recording device mask into
+	 * the ADC multiplexer settings.
+	 */
+	devc->recmask = devc->mix->select_input(devc, recmask,
+						&dev_l, &dev_r);
+
+	waveartist_set_adc_mux(devc, dev_l, dev_r);
+}
+
+static int
+waveartist_set_mixer(wavnc_info *devc, int dev, unsigned int level)
+{
+	unsigned int lev_left  = level & 0x00ff;
+	unsigned int lev_right = (level & 0xff00) >> 8;
+
+	if (lev_left > 100)
+		lev_left = 100;
+	if (lev_right > 100)
+		lev_right = 100;
+
+	/*
+	 * Mono devices have their right volume forced to their
+	 * left volume.  (from ALSA driver OSS emulation).
+	 */
+	if (!(devc->mix->stereo_devs & (1 << dev)))
+		lev_right = lev_left;
+
+	dev = devc->mix->decode_mixer(devc, dev, lev_left, lev_right);
+
+	if (dev >= 0)
+		waveartist_mixer_update(devc, dev);
+
+	return dev < 0 ? dev : 0;
+}
+
+static int
+waveartist_mixer_ioctl(int dev, unsigned int cmd, void __user * arg)
+{
+	wavnc_info *devc = (wavnc_info *)audio_devs[dev]->devc;
+	int ret = 0, val, nr;
+
+	/*
+	 * All SOUND_MIXER_* ioctls use type 'M'
+	 */
+	if (((cmd >> 8) & 255) != 'M')
+		return -ENOIOCTLCMD;
+
+#ifdef CONFIG_ARCH_NETWINDER
+	if (machine_is_netwinder()) {
+		ret = vnc_private_ioctl(dev, cmd, arg);
+		if (ret != -ENOIOCTLCMD)
+			return ret;
+		else
+			ret = 0;
+	}
+#endif
+
+	nr = cmd & 0xff;
+
+	if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+		if (get_user(val, (int __user *)arg))
+			return -EFAULT;
+
+		switch (nr) {
+		case SOUND_MIXER_RECSRC:
+			waveartist_set_recmask(devc, val);
+			break;
+
+		default:
+			ret = -EINVAL;
+			if (nr < SOUND_MIXER_NRDEVICES &&
+			    devc->mix->supported_devs & (1 << nr))
+				ret = waveartist_set_mixer(devc, nr, val);
+		}
+	}
+
+	if (ret == 0 && _SIOC_DIR(cmd) & _SIOC_READ) {
+		ret = -EINVAL;
+
+		switch (nr) {
+		case SOUND_MIXER_RECSRC:
+			ret = devc->recmask;
+			break;
+
+		case SOUND_MIXER_DEVMASK:
+			ret = devc->mix->supported_devs;
+			break;
+
+		case SOUND_MIXER_STEREODEVS:
+			ret = devc->mix->stereo_devs;
+			break;
+
+		case SOUND_MIXER_RECMASK:
+			ret = devc->mix->recording_devs;
+			break;
+
+		case SOUND_MIXER_CAPS:
+			ret = SOUND_CAP_EXCL_INPUT;
+			break;
+
+		default:
+			if (nr < SOUND_MIXER_NRDEVICES)
+				ret = devc->mix->get_mixer(devc, nr);
+			break;
+		}
+
+		if (ret >= 0)
+			ret = put_user(ret, (int __user *)arg) ? -EFAULT : 0;
+	}
+
+	return ret;
+}
+
+static struct mixer_operations waveartist_mixer_operations =
+{
+	.owner	= THIS_MODULE,
+	.id	= "WaveArtist",
+	.name	= "WaveArtist",
+	.ioctl	= waveartist_mixer_ioctl
+};
+
+static void
+waveartist_mixer_reset(wavnc_info *devc)
+{
+	int i;
+
+	if (debug_flg & DEBUG_MIXER)
+		printk("%s: mixer_reset\n", devc->hw.name);
+
+	/*
+	 * reset mixer cmd
+	 */
+	waveartist_cmd1(devc, WACMD_RST_MIXER);
+
+	/*
+	 * set input for ADC to come from 'quiet'
+	 * turn on default modes
+	 */
+	waveartist_cmd3(devc, WACMD_SET_MIXER, 0x9800, 0xa836);
+
+	/*
+	 * set mixer input select to none, RX filter gains 0 dB
+	 */
+	waveartist_cmd3(devc, WACMD_SET_MIXER, 0x4c00, 0x8c00);
+
+	/*
+	 * set bit 0 reg 2 to 1 - unmute MonoOut
+	 */
+	waveartist_cmd3(devc, WACMD_SET_MIXER, 0x2801, 0x6800);
+
+	/* set default input device = internal mic
+	 * current recording device = none
+	 */
+	waveartist_set_recmask(devc, 0);
+
+	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+		waveartist_mixer_update(devc, i);
+}
+
+static int __init waveartist_init(wavnc_info *devc)
+{
+	wavnc_port_info *portc;
+	char rev[3], dev_name[64];
+	int my_dev;
+
+	if (waveartist_reset(devc))
+		return -ENODEV;
+
+	sprintf(dev_name, "%s (%s", devc->hw.name, devc->chip_name);
+
+	if (waveartist_getrev(devc, rev)) {
+		strcat(dev_name, " rev. ");
+		strcat(dev_name, rev);
+	}
+	strcat(dev_name, ")");
+
+	conf_printf2(dev_name, devc->hw.io_base, devc->hw.irq,
+		     devc->hw.dma, devc->hw.dma2);
+
+	portc = kzalloc(sizeof(wavnc_port_info), GFP_KERNEL);
+	if (portc == NULL)
+		goto nomem;
+
+	my_dev = sound_install_audiodrv(AUDIO_DRIVER_VERSION, dev_name,
+			&waveartist_audio_driver, sizeof(struct audio_driver),
+			devc->audio_flags, AFMT_U8 | AFMT_S16_LE | AFMT_S8,
+			devc, devc->hw.dma, devc->hw.dma2);
+
+	if (my_dev < 0)
+		goto free;
+
+	audio_devs[my_dev]->portc = portc;
+
+	waveartist_mixer_reset(devc);
+
+	/*
+	 * clear any pending interrupt
+	 */
+	waveartist_iack(devc);
+
+	if (request_irq(devc->hw.irq, waveartist_intr, 0, devc->hw.name, devc) < 0) {
+		printk(KERN_ERR "%s: IRQ %d in use\n",
+			devc->hw.name, devc->hw.irq);
+		goto uninstall;
+	}
+
+	if (sound_alloc_dma(devc->hw.dma, devc->hw.name)) {
+		printk(KERN_ERR "%s: Can't allocate DMA%d\n",
+			devc->hw.name, devc->hw.dma);
+		goto uninstall_irq;
+	}
+
+	if (devc->hw.dma != devc->hw.dma2 && devc->hw.dma2 != NO_DMA)
+		if (sound_alloc_dma(devc->hw.dma2, devc->hw.name)) {
+			printk(KERN_ERR "%s: can't allocate DMA%d\n",
+				devc->hw.name, devc->hw.dma2);
+			goto uninstall_dma;
+		}
+
+	waveartist_set_ctlr(&devc->hw, 0, DMA1_IE | DMA0_IE);
+
+	audio_devs[my_dev]->mixer_dev =
+		sound_install_mixer(MIXER_DRIVER_VERSION,
+				dev_name,
+				&waveartist_mixer_operations,
+				sizeof(struct mixer_operations),
+				devc);
+
+	return my_dev;
+
+uninstall_dma:
+	sound_free_dma(devc->hw.dma);
+
+uninstall_irq:
+	free_irq(devc->hw.irq, devc);
+
+uninstall:
+	sound_unload_audiodev(my_dev);
+
+free:
+	kfree(portc);
+
+nomem:
+	return -1;
+}
+
+static int __init probe_waveartist(struct address_info *hw_config)
+{
+	wavnc_info *devc = &adev_info[nr_waveartist_devs];
+
+	if (nr_waveartist_devs >= MAX_AUDIO_DEV) {
+		printk(KERN_WARNING "waveartist: too many audio devices\n");
+		return 0;
+	}
+
+	if (!request_region(hw_config->io_base, 15, hw_config->name))  {
+		printk(KERN_WARNING "WaveArtist: I/O port conflict\n");
+		return 0;
+	}
+
+	if (hw_config->irq > 15 || hw_config->irq < 0) {
+		release_region(hw_config->io_base, 15);
+		printk(KERN_WARNING "WaveArtist: Bad IRQ %d\n",
+		       hw_config->irq);
+		return 0;
+	}
+
+	if (hw_config->dma != 3) {
+		release_region(hw_config->io_base, 15);
+		printk(KERN_WARNING "WaveArtist: Bad DMA %d\n",
+		       hw_config->dma);
+		return 0;
+	}
+
+	hw_config->name = "WaveArtist";
+	devc->hw = *hw_config;
+	devc->open_mode = 0;
+	devc->chip_name = "RWA-010";
+
+	return 1;
+}
+
+static void __init
+attach_waveartist(struct address_info *hw, const struct waveartist_mixer_info *mix)
+{
+	wavnc_info *devc = &adev_info[nr_waveartist_devs];
+
+	/*
+	 * NOTE! If irq < 0, there is another driver which has allocated the
+	 *   IRQ so that this driver doesn't need to allocate/deallocate it.
+	 *   The actually used IRQ is ABS(irq).
+	 */
+	devc->hw = *hw;
+	devc->hw.irq = (hw->irq > 0) ? hw->irq : 0;
+	devc->open_mode = 0;
+	devc->playback_dev = 0;
+	devc->record_dev = 0;
+	devc->audio_flags = DMA_AUTOMODE;
+	devc->levels = levels;
+
+	if (hw->dma != hw->dma2 && hw->dma2 != NO_DMA)
+		devc->audio_flags |= DMA_DUPLEX;
+
+	devc->mix = mix;
+	devc->dev_no = waveartist_init(devc);
+
+	if (devc->dev_no < 0)
+		release_region(hw->io_base, 15);
+	else {
+#ifdef CONFIG_ARCH_NETWINDER
+		if (machine_is_netwinder()) {
+			init_timer(&vnc_timer);
+			vnc_timer.function = vnc_slider_tick;
+			vnc_timer.expires  = jiffies;
+			vnc_timer.data     = nr_waveartist_devs;
+			add_timer(&vnc_timer);
+
+			vnc_configure_mixer(devc, 0);
+
+			devc->no_autoselect = 1;
+		}
+#endif
+		nr_waveartist_devs += 1;
+	}
+}
+
+static void __exit unload_waveartist(struct address_info *hw)
+{
+	wavnc_info *devc = NULL;
+	int i;
+
+	for (i = 0; i < nr_waveartist_devs; i++)
+		if (hw->io_base == adev_info[i].hw.io_base) {
+			devc = adev_info + i;
+			break;
+		}
+
+	if (devc != NULL) {
+		int mixer;
+
+#ifdef CONFIG_ARCH_NETWINDER
+		if (machine_is_netwinder())
+			del_timer(&vnc_timer);
+#endif
+
+		release_region(devc->hw.io_base, 15);
+
+		waveartist_set_ctlr(&devc->hw, DMA1_IE|DMA0_IE, 0);
+
+		if (devc->hw.irq >= 0)
+			free_irq(devc->hw.irq, devc);
+
+		sound_free_dma(devc->hw.dma);
+
+		if (devc->hw.dma != devc->hw.dma2 &&
+		    devc->hw.dma2 != NO_DMA)
+			sound_free_dma(devc->hw.dma2);
+
+		mixer = audio_devs[devc->dev_no]->mixer_dev;
+
+		if (mixer >= 0)
+			sound_unload_mixerdev(mixer);
+
+		if (devc->dev_no >= 0)
+			sound_unload_audiodev(devc->dev_no);
+
+		nr_waveartist_devs -= 1;
+
+		for (; i < nr_waveartist_devs; i++)
+			adev_info[i] = adev_info[i + 1];
+	} else
+		printk(KERN_WARNING "waveartist: can't find device "
+		       "to unload\n");
+}
+
+#ifdef CONFIG_ARCH_NETWINDER
+
+/*
+ * Rebel.com Netwinder specifics...
+ */
+
+#include <asm/hardware/dec21285.h>
+ 
+#define	VNC_TIMER_PERIOD (HZ/4)	//check slider 4 times/sec
+
+#define	MIXER_PRIVATE3_RESET	0x53570000
+#define	MIXER_PRIVATE3_READ	0x53570001
+#define	MIXER_PRIVATE3_WRITE	0x53570002
+
+#define	VNC_MUTE_INTERNAL_SPKR	0x01	//the sw mute on/off control bit
+#define	VNC_MUTE_LINE_OUT	0x10
+#define VNC_PHONE_DETECT	0x20
+#define VNC_HANDSET_DETECT	0x40
+#define VNC_DISABLE_AUTOSWITCH	0x80
+
+static inline void
+vnc_mute_spkr(wavnc_info *devc)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&nw_gpio_lock, flags);
+	nw_cpld_modify(CPLD_UNMUTE, devc->spkr_mute_state ? 0 : CPLD_UNMUTE);
+	spin_unlock_irqrestore(&nw_gpio_lock, flags);
+}
+
+static void
+vnc_mute_lout(wavnc_info *devc)
+{
+	unsigned int left, right;
+
+	left  = waveartist_cmd1_r(devc, WACMD_GET_LEVEL);
+	right = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x400);
+
+	if (devc->line_mute_state) {
+		left &= ~1;
+		right &= ~1;
+	} else {
+		left |= 1;
+		right |= 1;
+	}
+	waveartist_cmd3(devc, WACMD_SET_MIXER, left, right);
+		
+}
+
+static int
+vnc_volume_slider(wavnc_info *devc)
+{
+	static signed int old_slider_volume;
+	unsigned long flags;
+	signed int volume = 255;
+
+	*CSR_TIMER1_LOAD = 0x00ffffff;
+
+	spin_lock_irqsave(&waveartist_lock, flags);
+
+	outb(0xFF, 0x201);
+	*CSR_TIMER1_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_DIV1;
+
+	while (volume && (inb(0x201) & 0x01))
+		volume--;
+
+	*CSR_TIMER1_CNTL = 0;
+
+	spin_unlock_irqrestore(&waveartist_lock,flags);
+	
+	volume = 0x00ffffff - *CSR_TIMER1_VALUE;
+
+
+#ifndef REVERSE
+	volume = 150 - (volume >> 5);
+#else
+	volume = (volume >> 6) - 25;
+#endif
+
+	if (volume < 0)
+		volume = 0;
+
+	if (volume > 100)
+		volume = 100;
+
+	/*
+	 * slider quite often reads +-8, so debounce this random noise
+	 */
+	if (abs(volume - old_slider_volume) > 7) {
+		old_slider_volume = volume;
+
+		if (debug_flg & DEBUG_MIXER)
+			printk(KERN_DEBUG "Slider volume: %d.\n", volume);
+	}
+
+	return old_slider_volume;
+}
+
+/*
+ * Decode a recording mask into a mixer selection on the NetWinder
+ * as follows:
+ *
+ *     OSS Source	WA Source	Actual source
+ *  SOUND_MASK_IMIX	Mixer		Mixer output (same as AD1848)
+ *  SOUND_MASK_LINE	Line		Line in
+ *  SOUND_MASK_LINE1	Left Mic	Handset
+ *  SOUND_MASK_PHONEIN	Left Aux	Telephone microphone
+ *  SOUND_MASK_MIC	Right Mic	Builtin microphone
+ */
+static unsigned int
+netwinder_select_input(wavnc_info *devc, unsigned int recmask,
+		       unsigned char *dev_l, unsigned char *dev_r)
+{
+	unsigned int recdev_l = ADC_MUX_NONE, recdev_r = ADC_MUX_NONE;
+
+	if (recmask & SOUND_MASK_IMIX) {
+		recmask = SOUND_MASK_IMIX;
+		recdev_l = ADC_MUX_MIXER;
+		recdev_r = ADC_MUX_MIXER;
+	} else if (recmask & SOUND_MASK_LINE) {
+		recmask = SOUND_MASK_LINE;
+		recdev_l = ADC_MUX_LINE;
+		recdev_r = ADC_MUX_LINE;
+	} else if (recmask & SOUND_MASK_LINE1) {
+		recmask = SOUND_MASK_LINE1;
+		waveartist_cmd1(devc, WACMD_SET_MONO); /* left */
+		recdev_l = ADC_MUX_MIC;
+		recdev_r = ADC_MUX_NONE;
+	} else if (recmask & SOUND_MASK_PHONEIN) {
+		recmask = SOUND_MASK_PHONEIN;
+		waveartist_cmd1(devc, WACMD_SET_MONO); /* left */
+		recdev_l = ADC_MUX_AUX1;
+		recdev_r = ADC_MUX_NONE;
+	} else if (recmask & SOUND_MASK_MIC) {
+		recmask = SOUND_MASK_MIC;
+		waveartist_cmd1(devc, WACMD_SET_MONO | 0x100);	/* right */
+		recdev_l = ADC_MUX_NONE;
+		recdev_r = ADC_MUX_MIC;
+	}
+
+	*dev_l = recdev_l;
+	*dev_r = recdev_r;
+
+	return recmask;
+}
+
+static int
+netwinder_decode_mixer(wavnc_info *devc, int dev, unsigned char lev_l,
+		       unsigned char lev_r)
+{
+	switch (dev) {
+	case SOUND_MIXER_VOLUME:
+	case SOUND_MIXER_SYNTH:
+	case SOUND_MIXER_PCM:
+	case SOUND_MIXER_LINE:
+	case SOUND_MIXER_IGAIN:
+		devc->levels[dev] = lev_l | lev_r << 8;
+		break;
+
+	case SOUND_MIXER_MIC:		/* right mic only */
+		devc->levels[SOUND_MIXER_MIC] &= 0xff;
+		devc->levels[SOUND_MIXER_MIC] |= lev_l << 8;
+		break;
+
+	case SOUND_MIXER_LINE1:		/* left mic only  */
+		devc->levels[SOUND_MIXER_MIC] &= 0xff00;
+		devc->levels[SOUND_MIXER_MIC] |= lev_l;
+		dev = SOUND_MIXER_MIC;
+		break;
+
+	case SOUND_MIXER_PHONEIN:	/* left aux only  */
+		devc->levels[SOUND_MIXER_LINE1] = lev_l;
+		dev = SOUND_MIXER_LINE1;
+		break;
+
+	case SOUND_MIXER_IMIX:
+	case SOUND_MIXER_PHONEOUT:
+		break;
+
+	default:
+		dev = -EINVAL;
+		break;
+	}
+	return dev;
+}
+
+static int netwinder_get_mixer(wavnc_info *devc, int dev)
+{
+	int levels;
+
+	switch (dev) {
+	case SOUND_MIXER_VOLUME:
+	case SOUND_MIXER_SYNTH:
+	case SOUND_MIXER_PCM:
+	case SOUND_MIXER_LINE:
+	case SOUND_MIXER_IGAIN:
+		levels = devc->levels[dev];
+		break;
+
+	case SOUND_MIXER_MIC:		/* builtin mic: right mic only */
+		levels = devc->levels[SOUND_MIXER_MIC] >> 8;
+		levels |= levels << 8;
+		break;
+
+	case SOUND_MIXER_LINE1:		/* handset mic: left mic only */
+		levels = devc->levels[SOUND_MIXER_MIC] & 0xff;
+		levels |= levels << 8;
+		break;
+
+	case SOUND_MIXER_PHONEIN:	/* phone mic: left aux1 only */
+		levels = devc->levels[SOUND_MIXER_LINE1] & 0xff;
+		levels |= levels << 8;
+		break;
+
+	default:
+		levels = 0;
+	}
+
+	return levels;
+}
+
+/*
+ * Waveartist specific mixer information.
+ */
+static const struct waveartist_mixer_info netwinder_mixer = {
+	.supported_devs	= SOUND_MASK_VOLUME  | SOUND_MASK_SYNTH   |
+			SOUND_MASK_PCM     | SOUND_MASK_SPEAKER |
+			SOUND_MASK_LINE    | SOUND_MASK_MIC     |
+			SOUND_MASK_IMIX    | SOUND_MASK_LINE1   |
+			SOUND_MASK_PHONEIN | SOUND_MASK_PHONEOUT|
+			SOUND_MASK_IGAIN,
+
+	.recording_devs	= SOUND_MASK_LINE    | SOUND_MASK_MIC     |
+			SOUND_MASK_IMIX    | SOUND_MASK_LINE1   |
+			SOUND_MASK_PHONEIN,
+
+	.stereo_devs	= SOUND_MASK_VOLUME  | SOUND_MASK_SYNTH   |
+			SOUND_MASK_PCM     | SOUND_MASK_LINE    |
+			SOUND_MASK_IMIX    | SOUND_MASK_IGAIN,
+
+	.select_input	= netwinder_select_input,
+	.decode_mixer	= netwinder_decode_mixer,
+	.get_mixer	= netwinder_get_mixer,
+};
+
+static void
+vnc_configure_mixer(wavnc_info *devc, unsigned int recmask)
+{
+	if (!devc->no_autoselect) {
+		if (devc->handset_detect) {
+			recmask = SOUND_MASK_LINE1;
+			devc->spkr_mute_state = devc->line_mute_state = 1;
+		} else if (devc->telephone_detect) {
+			recmask = SOUND_MASK_PHONEIN;
+			devc->spkr_mute_state = devc->line_mute_state = 1;
+		} else {
+			/* unless someone has asked for LINE-IN,
+			 * we default to MIC
+			 */
+			if ((devc->recmask & SOUND_MASK_LINE) == 0)
+				devc->recmask = SOUND_MASK_MIC;
+			devc->spkr_mute_state = devc->line_mute_state = 0;
+		}
+		vnc_mute_spkr(devc);
+		vnc_mute_lout(devc);
+
+		if (recmask != devc->recmask)
+			waveartist_set_recmask(devc, recmask);
+	}
+}
+
+static int
+vnc_slider(wavnc_info *devc)
+{
+	signed int slider_volume;
+	unsigned int temp, old_hs, old_td;
+
+	/*
+	 * read the "buttons" state.
+	 *  Bit 4 = 0 means handset present
+	 *  Bit 5 = 1 means phone offhook
+	 */
+	temp = inb(0x201);
+
+	old_hs = devc->handset_detect;
+	old_td = devc->telephone_detect;
+
+	devc->handset_detect = !(temp & 0x10);
+	devc->telephone_detect = !!(temp & 0x20);
+
+	if (!devc->no_autoselect &&
+	    (old_hs != devc->handset_detect ||
+	     old_td != devc->telephone_detect))
+		vnc_configure_mixer(devc, devc->recmask);
+
+	slider_volume = vnc_volume_slider(devc);
+
+	/*
+	 * If we're using software controlled volume, and
+	 * the slider moves by more than 20%, then we
+	 * switch back to slider controlled volume.
+	 */
+	if (abs(devc->slider_vol - slider_volume) > 20)
+		devc->use_slider = 1;
+
+	/*
+	 * use only left channel
+	 */
+	temp = levels[SOUND_MIXER_VOLUME] & 0xFF;
+
+	if (slider_volume != temp && devc->use_slider) {
+		devc->slider_vol = slider_volume;
+
+		waveartist_set_mixer(devc, SOUND_MIXER_VOLUME,
+			slider_volume | slider_volume << 8);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+static void
+vnc_slider_tick(unsigned long data)
+{
+	int next_timeout;
+
+	if (vnc_slider(adev_info + data))
+		next_timeout = 5;	// mixer reported change
+	else
+		next_timeout = VNC_TIMER_PERIOD;
+
+	mod_timer(&vnc_timer, jiffies + next_timeout);
+}
+
+static int
+vnc_private_ioctl(int dev, unsigned int cmd, int __user * arg)
+{
+	wavnc_info *devc = (wavnc_info *)audio_devs[dev]->devc;
+	int val;
+
+	switch (cmd) {
+	case SOUND_MIXER_PRIVATE1:
+	{
+		u_int prev_spkr_mute, prev_line_mute, prev_auto_state;
+		int val;
+
+		if (get_user(val, arg))
+			return -EFAULT;
+
+		/* check if parameter is logical */
+		if (val & ~(VNC_MUTE_INTERNAL_SPKR |
+			    VNC_MUTE_LINE_OUT |
+			    VNC_DISABLE_AUTOSWITCH))
+			return -EINVAL;
+
+		prev_auto_state = devc->no_autoselect;
+		prev_spkr_mute  = devc->spkr_mute_state;
+		prev_line_mute  = devc->line_mute_state;
+
+		devc->no_autoselect   = (val & VNC_DISABLE_AUTOSWITCH) ? 1 : 0;
+		devc->spkr_mute_state = (val & VNC_MUTE_INTERNAL_SPKR) ? 1 : 0;
+		devc->line_mute_state = (val & VNC_MUTE_LINE_OUT) ? 1 : 0;
+
+		if (prev_spkr_mute != devc->spkr_mute_state)
+			vnc_mute_spkr(devc);
+
+		if (prev_line_mute != devc->line_mute_state)
+			vnc_mute_lout(devc);
+
+		if (prev_auto_state != devc->no_autoselect)
+			vnc_configure_mixer(devc, devc->recmask);
+
+		return 0;
+	}
+
+	case SOUND_MIXER_PRIVATE2:
+		if (get_user(val, arg))
+			return -EFAULT;
+
+		switch (val) {
+#define VNC_SOUND_PAUSE         0x53    //to pause the DSP
+#define VNC_SOUND_RESUME        0x57    //to unpause the DSP
+		case VNC_SOUND_PAUSE:
+			waveartist_cmd1(devc, 0x16);
+			break;
+
+		case VNC_SOUND_RESUME:
+			waveartist_cmd1(devc, 0x18);
+			break;
+
+		default:
+			return -EINVAL;
+		}
+		return 0;
+
+	/* private ioctl to allow bulk access to waveartist */
+	case SOUND_MIXER_PRIVATE3:
+	{
+		unsigned long	flags;
+		int		mixer_reg[15], i, val;
+
+		if (get_user(val, arg))
+			return -EFAULT;
+		if (copy_from_user(mixer_reg, (void *)val, sizeof(mixer_reg)))
+			return -EFAULT;
+
+		switch (mixer_reg[14]) {
+		case MIXER_PRIVATE3_RESET:
+			waveartist_mixer_reset(devc);
+			break;
+
+		case MIXER_PRIVATE3_WRITE:
+			waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[0], mixer_reg[4]);
+			waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[1], mixer_reg[5]);
+			waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[2], mixer_reg[6]);
+			waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[3], mixer_reg[7]);
+			waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[8], mixer_reg[9]);
+
+			waveartist_cmd3(devc, WACMD_SET_LEVEL, mixer_reg[10], mixer_reg[11]);
+			waveartist_cmd3(devc, WACMD_SET_LEVEL, mixer_reg[12], mixer_reg[13]);
+			break;
+
+		case MIXER_PRIVATE3_READ:
+			spin_lock_irqsave(&waveartist_lock, flags);
+
+			for (i = 0x30; i < 14 << 8; i += 1 << 8)
+				waveartist_cmd(devc, 1, &i, 1, mixer_reg + (i >> 8));
+
+			spin_unlock_irqrestore(&waveartist_lock, flags);
+
+			if (copy_to_user((void *)val, mixer_reg, sizeof(mixer_reg)))
+				return -EFAULT;
+			break;
+
+		default:
+			return -EINVAL;
+		}
+		return 0;
+	}
+
+	/* read back the state from PRIVATE1 */
+	case SOUND_MIXER_PRIVATE4:
+		val = (devc->spkr_mute_state  ? VNC_MUTE_INTERNAL_SPKR : 0) |
+		      (devc->line_mute_state  ? VNC_MUTE_LINE_OUT      : 0) |
+		      (devc->handset_detect   ? VNC_HANDSET_DETECT     : 0) |
+		      (devc->telephone_detect ? VNC_PHONE_DETECT       : 0) |
+		      (devc->no_autoselect    ? VNC_DISABLE_AUTOSWITCH : 0);
+
+		return put_user(val, arg) ? -EFAULT : 0;
+	}
+
+	if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+		/*
+		 * special case for master volume: if we
+		 * received this call - switch from hw
+		 * volume control to a software volume
+		 * control, till the hw volume is modified
+		 * to signal that user wants to be back in
+		 * hardware...
+		 */
+		if ((cmd & 0xff) == SOUND_MIXER_VOLUME)
+			devc->use_slider = 0;
+
+		/* speaker output            */
+		if ((cmd & 0xff) == SOUND_MIXER_SPEAKER) {
+			unsigned int val, l, r;
+
+			if (get_user(val, arg))
+				return -EFAULT;
+
+			l = val & 0x7f;
+			r = (val & 0x7f00) >> 8;
+			val = (l + r) / 2;
+			devc->levels[SOUND_MIXER_SPEAKER] = val | (val << 8);
+			devc->spkr_mute_state = (val <= 50);
+			vnc_mute_spkr(devc);
+			return 0;
+		}
+	}
+
+	return -ENOIOCTLCMD;
+}
+
+#endif
+
+static struct address_info cfg;
+
+static int attached;
+
+static int __initdata io = 0;
+static int __initdata irq = 0;
+static int __initdata dma = 0;
+static int __initdata dma2 = 0;
+
+
+static int __init init_waveartist(void)
+{
+	const struct waveartist_mixer_info *mix;
+
+	if (!io && machine_is_netwinder()) {
+		/*
+		 * The NetWinder WaveArtist is at a fixed address.
+		 * If the user does not supply an address, use the
+		 * well-known parameters.
+		 */
+		io   = 0x250;
+		irq  = 12;
+		dma  = 3;
+		dma2 = 7;
+	}
+
+	mix = &waveartist_mixer;
+#ifdef CONFIG_ARCH_NETWINDER
+	if (machine_is_netwinder())
+		mix = &netwinder_mixer;
+#endif
+
+	cfg.io_base = io;
+	cfg.irq = irq;
+	cfg.dma = dma;
+	cfg.dma2 = dma2;
+
+	if (!probe_waveartist(&cfg))
+		return -ENODEV;
+
+	attach_waveartist(&cfg, mix);
+	attached = 1;
+
+	return 0;
+}
+
+static void __exit cleanup_waveartist(void)
+{
+	if (attached)
+		unload_waveartist(&cfg);
+}
+
+module_init(init_waveartist);
+module_exit(cleanup_waveartist);
+
+#ifndef MODULE
+static int __init setup_waveartist(char *str)
+{
+	/* io, irq, dma, dma2 */
+	int ints[5];
+	
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+	
+	io	= ints[1];
+	irq	= ints[2];
+	dma	= ints[3];
+	dma2	= ints[4];
+
+	return 1;
+}
+__setup("waveartist=", setup_waveartist);
+#endif
+
+MODULE_DESCRIPTION("Rockwell WaveArtist RWA-010 sound driver");
+module_param(io, int, 0);		/* IO base */
+module_param(irq, int, 0);		/* IRQ */
+module_param(dma, int, 0);		/* DMA */
+module_param(dma2, int, 0);		/* DMA2 */
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/waveartist.h b/linux/sound/oss/waveartist.h
new file mode 100644
index 0000000..dac4ca9
--- /dev/null
+++ b/linux/sound/oss/waveartist.h
@@ -0,0 +1,92 @@
+/*
+ * linux/sound/oss/waveartist.h
+ *
+ * def file for Rockwell RWA010 chip set, as installed in Rebel.com NetWinder
+ */
+
+//registers
+#define CMDR	0
+#define DATR	2
+#define CTLR	4
+#define	STATR	5
+#define	IRQSTAT	12
+
+//bit defs
+//reg STATR
+#define	CMD_WE	0x80
+#define	CMD_RF	0x40
+#define	DAT_WE	0x20
+#define	DAT_RF	0x10
+
+#define	IRQ_REQ	0x08
+#define	DMA1	0x04
+#define	DMA0	0x02
+
+//bit defs
+//reg CTLR
+#define	CMD_WEIE	0x80
+#define	CMD_RFIE	0x40
+#define	DAT_WEIE	0x20
+#define	DAT_RFIE	0x10
+
+#define	RESET	0x08
+#define	DMA1_IE	0x04
+#define	DMA0_IE	0x02
+#define	IRQ_ACK	0x01
+
+//commands
+
+#define	WACMD_SYSTEMID		0x00
+#define WACMD_GETREV		0x00
+#define	WACMD_INPUTFORMAT	0x10	//0-8S, 1-16S, 2-8U
+#define	WACMD_INPUTCHANNELS	0x11	//1-Mono, 2-Stereo
+#define	WACMD_INPUTSPEED	0x12	//sampling rate
+#define	WACMD_INPUTDMA		0x13	//0-8bit, 1-16bit, 2-PIO
+#define	WACMD_INPUTSIZE		0x14	//samples to interrupt
+#define	WACMD_INPUTSTART	0x15	//start ADC
+#define	WACMD_INPUTPAUSE	0x16	//pause ADC
+#define	WACMD_INPUTSTOP		0x17	//stop ADC
+#define	WACMD_INPUTRESUME	0x18	//resume ADC
+#define	WACMD_INPUTPIO		0x19	//PIO ADC
+
+#define	WACMD_OUTPUTFORMAT	0x20	//0-8S, 1-16S, 2-8U
+#define	WACMD_OUTPUTCHANNELS	0x21	//1-Mono, 2-Stereo
+#define	WACMD_OUTPUTSPEED	0x22	//sampling rate
+#define	WACMD_OUTPUTDMA		0x23	//0-8bit, 1-16bit, 2-PIO
+#define	WACMD_OUTPUTSIZE	0x24	//samples to interrupt
+#define	WACMD_OUTPUTSTART	0x25	//start ADC
+#define	WACMD_OUTPUTPAUSE	0x26	//pause ADC
+#define	WACMD_OUTPUTSTOP	0x27	//stop ADC
+#define	WACMD_OUTPUTRESUME	0x28	//resume ADC
+#define	WACMD_OUTPUTPIO		0x29	//PIO ADC
+
+#define	WACMD_GET_LEVEL		0x30
+#define	WACMD_SET_LEVEL		0x31
+#define	WACMD_SET_MIXER		0x32
+#define	WACMD_RST_MIXER		0x33
+#define	WACMD_SET_MONO		0x34
+
+/*
+ * Definitions for left/right recording input mux
+ */
+#define ADC_MUX_NONE	0
+#define ADC_MUX_MIXER	1
+#define ADC_MUX_LINE	2
+#define ADC_MUX_AUX2	3
+#define ADC_MUX_AUX1	4
+#define ADC_MUX_MIC	5
+
+/*
+ * Definitions for mixer gain settings
+ */
+#define MIX_GAIN_LINE	0	/* line in	 */
+#define MIX_GAIN_AUX1	1	/* aux1		 */
+#define MIX_GAIN_AUX2	2	/* aux2		 */
+#define MIX_GAIN_XMIC	3	/* crossover mic */
+#define MIX_GAIN_MIC	4	/* normal mic	 */
+#define MIX_GAIN_PREMIC	5	/* preamp mic	 */
+#define MIX_GAIN_OUT	6	/* output	 */
+#define MIX_GAIN_MONO	7	/* mono in	 */
+
+int wa_sendcmd(unsigned int cmd);
+int wa_writecmd(unsigned int cmd, unsigned int arg);
diff --git a/linux/sound/parisc/Kconfig b/linux/sound/parisc/Kconfig
new file mode 100644
index 0000000..9b61d95
--- /dev/null
+++ b/linux/sound/parisc/Kconfig
@@ -0,0 +1,20 @@
+# ALSA PA-RISC drivers
+
+menuconfig SND_GSC
+	bool "GSC sound devices"
+	depends on GSC
+	default y
+	help
+	  Support for GSC sound devices on PA-RISC architectures.
+
+if SND_GSC
+
+config SND_HARMONY
+	tristate "Harmony/Vivace sound chip"
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for the Harmony/Vivace sound
+	  chip found in most GSC-based PA-RISC workstations.  It's frequently
+	  provided as part of the Lasi multi-function IC.
+
+endif	# SND_GSC
diff --git a/linux/sound/parisc/Makefile b/linux/sound/parisc/Makefile
new file mode 100644
index 0000000..b91e750
--- /dev/null
+++ b/linux/sound/parisc/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for ALSA
+#
+
+snd-harmony-objs := harmony.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_HARMONY) += snd-harmony.o
diff --git a/linux/sound/parisc/harmony.c b/linux/sound/parisc/harmony.c
new file mode 100644
index 0000000..f47f9e2
--- /dev/null
+++ b/linux/sound/parisc/harmony.c
@@ -0,0 +1,1047 @@
+/* Hewlett-Packard Harmony audio driver
+ *
+ *   This is a driver for the Harmony audio chipset found
+ *   on the LASI ASIC of various early HP PA-RISC workstations.
+ *
+ *   Copyright (C) 2004, Kyle McMartin <kyle@{debian.org,parisc-linux.org}>
+ *
+ *     Based on the previous Harmony incarnations by,
+ *       Copyright 2000 (c) Linuxcare Canada, Alex deVries
+ *       Copyright 2000-2003 (c) Helge Deller
+ *       Copyright 2001 (c) Matthieu Delahaye
+ *       Copyright 2001 (c) Jean-Christophe Vaugeois
+ *       Copyright 2003 (c) Laurent Canet
+ *       Copyright 2004 (c) Stuart Brady
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License, version 2, as
+ *   published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Notes:
+ *   - graveyard and silence buffers last for lifetime of
+ *     the driver. playback and capture buffers are allocated
+ *     per _open()/_close().
+ * 
+ * TODO:
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/rawmidi.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+
+#include <asm/io.h>
+#include <asm/hardware.h>
+#include <asm/parisc-device.h>
+
+#include "harmony.h"
+
+static int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for Harmony driver.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for Harmony driver.");
+
+
+static struct parisc_device_id snd_harmony_devtable[] = {
+	/* bushmaster / flounder */
+	{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007A }, 
+	/* 712 / 715 */
+	{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007B }, 
+	/* pace */
+	{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007E }, 
+	/* outfield / coral II */
+	{ HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007F },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(parisc, snd_harmony_devtable);
+
+#define NAME "harmony"
+#define PFX  NAME ": "
+
+static unsigned int snd_harmony_rates[] = {
+	5512, 6615, 8000, 9600,
+	11025, 16000, 18900, 22050,
+	27428, 32000, 33075, 37800,
+	44100, 48000
+};
+
+static unsigned int rate_bits[14] = {
+	HARMONY_SR_5KHZ, HARMONY_SR_6KHZ, HARMONY_SR_8KHZ,
+	HARMONY_SR_9KHZ, HARMONY_SR_11KHZ, HARMONY_SR_16KHZ,
+	HARMONY_SR_18KHZ, HARMONY_SR_22KHZ, HARMONY_SR_27KHZ,
+	HARMONY_SR_32KHZ, HARMONY_SR_33KHZ, HARMONY_SR_37KHZ,
+	HARMONY_SR_44KHZ, HARMONY_SR_48KHZ
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraint_rates = {
+	.count = ARRAY_SIZE(snd_harmony_rates),
+	.list = snd_harmony_rates,
+	.mask = 0,
+};
+
+static inline unsigned long
+harmony_read(struct snd_harmony *h, unsigned r)
+{
+	return __raw_readl(h->iobase + r);
+}
+
+static inline void
+harmony_write(struct snd_harmony *h, unsigned r, unsigned long v)
+{
+	__raw_writel(v, h->iobase + r);
+}
+
+static inline void
+harmony_wait_for_control(struct snd_harmony *h)
+{
+	while (harmony_read(h, HARMONY_CNTL) & HARMONY_CNTL_C) ;
+}
+
+static inline void
+harmony_reset(struct snd_harmony *h)
+{
+	harmony_write(h, HARMONY_RESET, 1);
+	mdelay(50);
+	harmony_write(h, HARMONY_RESET, 0);
+}
+
+static void
+harmony_disable_interrupts(struct snd_harmony *h)
+{
+	u32 dstatus;
+	harmony_wait_for_control(h);
+	dstatus = harmony_read(h, HARMONY_DSTATUS);
+	dstatus &= ~HARMONY_DSTATUS_IE;
+	harmony_write(h, HARMONY_DSTATUS, dstatus);
+}
+
+static void
+harmony_enable_interrupts(struct snd_harmony *h)
+{
+	u32 dstatus;
+	harmony_wait_for_control(h);
+	dstatus = harmony_read(h, HARMONY_DSTATUS);
+	dstatus |= HARMONY_DSTATUS_IE;
+	harmony_write(h, HARMONY_DSTATUS, dstatus);
+}
+
+static void
+harmony_mute(struct snd_harmony *h)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&h->mixer_lock, flags);
+	harmony_wait_for_control(h);
+	harmony_write(h, HARMONY_GAINCTL, HARMONY_GAIN_SILENCE);
+	spin_unlock_irqrestore(&h->mixer_lock, flags);
+}
+
+static void
+harmony_unmute(struct snd_harmony *h)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&h->mixer_lock, flags);
+	harmony_wait_for_control(h);
+	harmony_write(h, HARMONY_GAINCTL, h->st.gain);
+	spin_unlock_irqrestore(&h->mixer_lock, flags);
+}
+
+static void
+harmony_set_control(struct snd_harmony *h)
+{
+	u32 ctrl;
+	unsigned long flags;
+
+	spin_lock_irqsave(&h->lock, flags);
+
+	ctrl = (HARMONY_CNTL_C      |
+		(h->st.format << 6) |
+		(h->st.stereo << 5) |
+		(h->st.rate));
+
+	harmony_wait_for_control(h);
+	harmony_write(h, HARMONY_CNTL, ctrl);
+
+	spin_unlock_irqrestore(&h->lock, flags);
+}
+
+static irqreturn_t
+snd_harmony_interrupt(int irq, void *dev)
+{
+	u32 dstatus;
+	struct snd_harmony *h = dev;
+
+	spin_lock(&h->lock);
+	harmony_disable_interrupts(h);
+	harmony_wait_for_control(h);
+	dstatus = harmony_read(h, HARMONY_DSTATUS);
+	spin_unlock(&h->lock);
+
+	if (dstatus & HARMONY_DSTATUS_PN) {
+		if (h->psubs && h->st.playing) {
+			spin_lock(&h->lock);
+			h->pbuf.buf += h->pbuf.count; /* PAGE_SIZE */
+			h->pbuf.buf %= h->pbuf.size; /* MAX_BUFS*PAGE_SIZE */
+
+			harmony_write(h, HARMONY_PNXTADD, 
+				      h->pbuf.addr + h->pbuf.buf);
+			h->stats.play_intr++;
+			spin_unlock(&h->lock);
+                        snd_pcm_period_elapsed(h->psubs);
+		} else {
+			spin_lock(&h->lock);
+			harmony_write(h, HARMONY_PNXTADD, h->sdma.addr);
+			h->stats.silence_intr++;
+			spin_unlock(&h->lock);
+		}
+	}
+
+	if (dstatus & HARMONY_DSTATUS_RN) {
+		if (h->csubs && h->st.capturing) {
+			spin_lock(&h->lock);
+			h->cbuf.buf += h->cbuf.count;
+			h->cbuf.buf %= h->cbuf.size;
+
+			harmony_write(h, HARMONY_RNXTADD,
+				      h->cbuf.addr + h->cbuf.buf);
+			h->stats.rec_intr++;
+			spin_unlock(&h->lock);
+                        snd_pcm_period_elapsed(h->csubs);
+		} else {
+			spin_lock(&h->lock);
+			harmony_write(h, HARMONY_RNXTADD, h->gdma.addr);
+			h->stats.graveyard_intr++;
+			spin_unlock(&h->lock);
+		}
+	}
+
+	spin_lock(&h->lock);
+	harmony_enable_interrupts(h);
+	spin_unlock(&h->lock);
+
+	return IRQ_HANDLED;
+}
+
+static unsigned int 
+snd_harmony_rate_bits(int rate)
+{
+	unsigned int i;
+	
+	for (i = 0; i < ARRAY_SIZE(snd_harmony_rates); i++)
+		if (snd_harmony_rates[i] == rate)
+			return rate_bits[i];
+
+	return HARMONY_SR_44KHZ;
+}
+
+static struct snd_pcm_hardware snd_harmony_playback =
+{
+	.info =	(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | 
+		 SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER),
+	.formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW |
+		    SNDRV_PCM_FMTBIT_A_LAW),
+	.rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 |
+		  SNDRV_PCM_RATE_KNOT),
+	.rate_min = 5512,
+	.rate_max = 48000,
+	.channels_min =	1,
+	.channels_max =	2,
+	.buffer_bytes_max = MAX_BUF_SIZE,
+	.period_bytes_min = BUF_SIZE,
+	.period_bytes_max = BUF_SIZE,
+	.periods_min = 1,
+	.periods_max = MAX_BUFS,
+	.fifo_size = 0,
+};
+
+static struct snd_pcm_hardware snd_harmony_capture =
+{
+        .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+                 SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID |
+                 SNDRV_PCM_INFO_BLOCK_TRANSFER),
+        .formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW |
+                    SNDRV_PCM_FMTBIT_A_LAW),
+        .rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 |
+		  SNDRV_PCM_RATE_KNOT),
+        .rate_min = 5512,
+        .rate_max = 48000,
+        .channels_min = 1,
+        .channels_max = 2,
+        .buffer_bytes_max = MAX_BUF_SIZE,
+        .period_bytes_min = BUF_SIZE,
+        .period_bytes_max = BUF_SIZE,
+        .periods_min = 1,
+        .periods_max = MAX_BUFS,
+        .fifo_size = 0,
+};
+
+static int
+snd_harmony_playback_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+	struct snd_harmony *h = snd_pcm_substream_chip(ss);
+
+	if (h->st.capturing)
+		return -EBUSY;
+
+	spin_lock(&h->lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		h->st.playing = 1;
+		harmony_write(h, HARMONY_PNXTADD, h->pbuf.addr);
+		harmony_write(h, HARMONY_RNXTADD, h->gdma.addr);
+		harmony_unmute(h);
+		harmony_enable_interrupts(h);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		h->st.playing = 0;
+		harmony_mute(h);
+		harmony_write(h, HARMONY_PNXTADD, h->sdma.addr);
+		harmony_disable_interrupts(h);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	default:
+		spin_unlock(&h->lock);
+		snd_BUG();
+		return -EINVAL;
+	}
+	spin_unlock(&h->lock);
+	
+	return 0;
+}
+
+static int
+snd_harmony_capture_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+        struct snd_harmony *h = snd_pcm_substream_chip(ss);
+
+	if (h->st.playing)
+		return -EBUSY;
+
+	spin_lock(&h->lock);
+        switch (cmd) {
+        case SNDRV_PCM_TRIGGER_START:
+		h->st.capturing = 1;
+                harmony_write(h, HARMONY_PNXTADD, h->sdma.addr);
+                harmony_write(h, HARMONY_RNXTADD, h->cbuf.addr);
+		harmony_unmute(h);
+                harmony_enable_interrupts(h);
+		break;
+        case SNDRV_PCM_TRIGGER_STOP:
+		h->st.capturing = 0;
+		harmony_mute(h);
+		harmony_write(h, HARMONY_RNXTADD, h->gdma.addr);
+		harmony_disable_interrupts(h);
+		break;
+        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+        case SNDRV_PCM_TRIGGER_SUSPEND:
+	default:
+		spin_unlock(&h->lock);
+		snd_BUG();
+                return -EINVAL;
+        }
+	spin_unlock(&h->lock);
+		
+        return 0;
+}
+
+static int
+snd_harmony_set_data_format(struct snd_harmony *h, int fmt, int force)
+{
+	int o = h->st.format;
+	int n;
+
+	switch(fmt) {
+	case SNDRV_PCM_FORMAT_S16_BE:
+		n = HARMONY_DF_16BIT_LINEAR;
+		break;
+	case SNDRV_PCM_FORMAT_A_LAW:
+		n = HARMONY_DF_8BIT_ALAW;
+		break;
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		n = HARMONY_DF_8BIT_ULAW;
+		break;
+	default:
+		n = HARMONY_DF_16BIT_LINEAR;
+		break;
+	}
+
+	if (force || o != n) {
+		snd_pcm_format_set_silence(fmt, h->sdma.area, SILENCE_BUFSZ / 
+					   (snd_pcm_format_physical_width(fmt)
+					    / 8));
+	}
+
+	return n;
+}
+
+static int
+snd_harmony_playback_prepare(struct snd_pcm_substream *ss)
+{
+	struct snd_harmony *h = snd_pcm_substream_chip(ss);
+	struct snd_pcm_runtime *rt = ss->runtime;
+	
+	if (h->st.capturing)
+		return -EBUSY;
+	
+	h->pbuf.size = snd_pcm_lib_buffer_bytes(ss);
+	h->pbuf.count = snd_pcm_lib_period_bytes(ss);
+	if (h->pbuf.buf >= h->pbuf.size)
+		h->pbuf.buf = 0;
+	h->st.playing = 0;
+
+	h->st.rate = snd_harmony_rate_bits(rt->rate);
+	h->st.format = snd_harmony_set_data_format(h, rt->format, 0);
+	
+	if (rt->channels == 2)
+		h->st.stereo = HARMONY_SS_STEREO;
+	else
+		h->st.stereo = HARMONY_SS_MONO;
+
+	harmony_set_control(h);
+
+	h->pbuf.addr = rt->dma_addr;
+
+	return 0;
+}
+
+static int
+snd_harmony_capture_prepare(struct snd_pcm_substream *ss)
+{
+        struct snd_harmony *h = snd_pcm_substream_chip(ss);
+        struct snd_pcm_runtime *rt = ss->runtime;
+
+	if (h->st.playing)
+		return -EBUSY;
+
+        h->cbuf.size = snd_pcm_lib_buffer_bytes(ss);
+        h->cbuf.count = snd_pcm_lib_period_bytes(ss);
+	if (h->cbuf.buf >= h->cbuf.size)
+	        h->cbuf.buf = 0;
+	h->st.capturing = 0;
+
+        h->st.rate = snd_harmony_rate_bits(rt->rate);
+        h->st.format = snd_harmony_set_data_format(h, rt->format, 0);
+
+        if (rt->channels == 2)
+                h->st.stereo = HARMONY_SS_STEREO;
+        else
+                h->st.stereo = HARMONY_SS_MONO;
+
+        harmony_set_control(h);
+
+        h->cbuf.addr = rt->dma_addr;
+
+        return 0;
+}
+
+static snd_pcm_uframes_t 
+snd_harmony_playback_pointer(struct snd_pcm_substream *ss)
+{
+	struct snd_pcm_runtime *rt = ss->runtime;
+	struct snd_harmony *h = snd_pcm_substream_chip(ss);
+	unsigned long pcuradd;
+	unsigned long played;
+
+	if (!(h->st.playing) || (h->psubs == NULL)) 
+		return 0;
+
+	if ((h->pbuf.addr == 0) || (h->pbuf.size == 0))
+		return 0;
+	
+	pcuradd = harmony_read(h, HARMONY_PCURADD);
+	played = pcuradd - h->pbuf.addr;
+
+#ifdef HARMONY_DEBUG
+	printk(KERN_DEBUG PFX "playback_pointer is 0x%lx-0x%lx = %d bytes\n", 
+	       pcuradd, h->pbuf.addr, played);	
+#endif
+
+	if (pcuradd > h->pbuf.addr + h->pbuf.size) {
+		return 0;
+	}
+
+	return bytes_to_frames(rt, played);
+}
+
+static snd_pcm_uframes_t
+snd_harmony_capture_pointer(struct snd_pcm_substream *ss)
+{
+        struct snd_pcm_runtime *rt = ss->runtime;
+        struct snd_harmony *h = snd_pcm_substream_chip(ss);
+        unsigned long rcuradd;
+        unsigned long caught;
+
+        if (!(h->st.capturing) || (h->csubs == NULL))
+                return 0;
+
+        if ((h->cbuf.addr == 0) || (h->cbuf.size == 0))
+                return 0;
+
+        rcuradd = harmony_read(h, HARMONY_RCURADD);
+        caught = rcuradd - h->cbuf.addr;
+
+#ifdef HARMONY_DEBUG
+        printk(KERN_DEBUG PFX "capture_pointer is 0x%lx-0x%lx = %d bytes\n",
+               rcuradd, h->cbuf.addr, caught);
+#endif
+
+        if (rcuradd > h->cbuf.addr + h->cbuf.size) {
+		return 0;
+	}
+
+        return bytes_to_frames(rt, caught);
+}
+
+static int 
+snd_harmony_playback_open(struct snd_pcm_substream *ss)
+{
+	struct snd_harmony *h = snd_pcm_substream_chip(ss);
+	struct snd_pcm_runtime *rt = ss->runtime;
+	int err;
+	
+	h->psubs = ss;
+	rt->hw = snd_harmony_playback;
+	snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE, 
+				   &hw_constraint_rates);
+	
+	err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0)
+		return err;
+	
+	return 0;
+}
+
+static int
+snd_harmony_capture_open(struct snd_pcm_substream *ss)
+{
+        struct snd_harmony *h = snd_pcm_substream_chip(ss);
+        struct snd_pcm_runtime *rt = ss->runtime;
+        int err;
+
+        h->csubs = ss;
+        rt->hw = snd_harmony_capture;
+        snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE,
+                                   &hw_constraint_rates);
+
+        err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
+        if (err < 0)
+                return err;
+
+        return 0;
+}
+
+static int 
+snd_harmony_playback_close(struct snd_pcm_substream *ss)
+{
+	struct snd_harmony *h = snd_pcm_substream_chip(ss);
+	h->psubs = NULL;
+	return 0;
+}
+
+static int
+snd_harmony_capture_close(struct snd_pcm_substream *ss)
+{
+        struct snd_harmony *h = snd_pcm_substream_chip(ss);
+        h->csubs = NULL;
+        return 0;
+}
+
+static int 
+snd_harmony_hw_params(struct snd_pcm_substream *ss,
+		      struct snd_pcm_hw_params *hw)
+{
+	int err;
+	struct snd_harmony *h = snd_pcm_substream_chip(ss);
+	
+	err = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw));
+	if (err > 0 && h->dma.type == SNDRV_DMA_TYPE_CONTINUOUS)
+		ss->runtime->dma_addr = __pa(ss->runtime->dma_area);
+	
+	return err;
+}
+
+static int 
+snd_harmony_hw_free(struct snd_pcm_substream *ss) 
+{
+	return snd_pcm_lib_free_pages(ss);
+}
+
+static struct snd_pcm_ops snd_harmony_playback_ops = {
+	.open =	snd_harmony_playback_open,
+	.close = snd_harmony_playback_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_harmony_hw_params,
+	.hw_free = snd_harmony_hw_free,
+	.prepare = snd_harmony_playback_prepare,
+	.trigger = snd_harmony_playback_trigger,
+ 	.pointer = snd_harmony_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_harmony_capture_ops = {
+        .open = snd_harmony_capture_open,
+        .close = snd_harmony_capture_close,
+        .ioctl = snd_pcm_lib_ioctl,
+        .hw_params = snd_harmony_hw_params,
+        .hw_free = snd_harmony_hw_free,
+        .prepare = snd_harmony_capture_prepare,
+        .trigger = snd_harmony_capture_trigger,
+        .pointer = snd_harmony_capture_pointer,
+};
+
+static int 
+snd_harmony_pcm_init(struct snd_harmony *h)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (snd_BUG_ON(!h))
+		return -EINVAL;
+
+	harmony_disable_interrupts(h);
+	
+   	err = snd_pcm_new(h->card, "harmony", 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+	
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, 
+			&snd_harmony_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&snd_harmony_capture_ops);
+
+	pcm->private_data = h;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "harmony");
+	h->pcm = pcm;
+
+	h->psubs = NULL;
+	h->csubs = NULL;
+	
+	/* initialize graveyard buffer */
+	h->dma.type = SNDRV_DMA_TYPE_DEV;
+	h->dma.dev = &h->dev->dev;
+	err = snd_dma_alloc_pages(h->dma.type,
+				  h->dma.dev,
+				  BUF_SIZE*GRAVEYARD_BUFS,
+				  &h->gdma);
+	if (err < 0) {
+		printk(KERN_ERR PFX "cannot allocate graveyard buffer!\n");
+		return err;
+	}
+	
+	/* initialize silence buffers */
+	err = snd_dma_alloc_pages(h->dma.type,
+				  h->dma.dev,
+				  BUF_SIZE*SILENCE_BUFS,
+				  &h->sdma);
+	if (err < 0) {
+		printk(KERN_ERR PFX "cannot allocate silence buffer!\n");
+		return err;
+	}
+
+	/* pre-allocate space for DMA */
+	err = snd_pcm_lib_preallocate_pages_for_all(pcm, h->dma.type,
+						    h->dma.dev,
+						    MAX_BUF_SIZE, 
+						    MAX_BUF_SIZE);
+	if (err < 0) {
+		printk(KERN_ERR PFX "buffer allocation error: %d\n", err);
+		return err;
+	}
+
+	h->st.format = snd_harmony_set_data_format(h,
+		SNDRV_PCM_FORMAT_S16_BE, 1);
+
+	return 0;
+}
+
+static void 
+snd_harmony_set_new_gain(struct snd_harmony *h)
+{
+ 	harmony_wait_for_control(h);
+	harmony_write(h, HARMONY_GAINCTL, h->st.gain);
+}
+
+static int 
+snd_harmony_mixercontrol_info(struct snd_kcontrol *kc, 
+			      struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kc->private_value >> 16) & 0xff;
+	int left_shift = (kc->private_value) & 0xff;
+	int right_shift = (kc->private_value >> 8) & 0xff;
+	
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : 
+		       SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = left_shift == right_shift ? 1 : 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+
+	return 0;
+}
+
+static int 
+snd_harmony_volume_get(struct snd_kcontrol *kc, 
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_harmony *h = snd_kcontrol_chip(kc);
+	int shift_left = (kc->private_value) & 0xff;
+	int shift_right = (kc->private_value >> 8) & 0xff;
+	int mask = (kc->private_value >> 16) & 0xff;
+	int invert = (kc->private_value >> 24) & 0xff;
+	int left, right;
+	
+	spin_lock_irq(&h->mixer_lock);
+
+	left = (h->st.gain >> shift_left) & mask;
+	right = (h->st.gain >> shift_right) & mask;
+	if (invert) {
+		left = mask - left;
+		right = mask - right;
+	}
+	
+	ucontrol->value.integer.value[0] = left;
+	if (shift_left != shift_right)
+		ucontrol->value.integer.value[1] = right;
+
+	spin_unlock_irq(&h->mixer_lock);
+
+	return 0;
+}  
+
+static int 
+snd_harmony_volume_put(struct snd_kcontrol *kc, 
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_harmony *h = snd_kcontrol_chip(kc);
+	int shift_left = (kc->private_value) & 0xff;
+	int shift_right = (kc->private_value >> 8) & 0xff;
+	int mask = (kc->private_value >> 16) & 0xff;
+	int invert = (kc->private_value >> 24) & 0xff;
+	int left, right;
+	int old_gain = h->st.gain;
+	
+	spin_lock_irq(&h->mixer_lock);
+
+	left = ucontrol->value.integer.value[0] & mask;
+	if (invert)
+		left = mask - left;
+	h->st.gain &= ~( (mask << shift_left ) );
+ 	h->st.gain |= (left << shift_left);
+
+	if (shift_left != shift_right) {
+		right = ucontrol->value.integer.value[1] & mask;
+		if (invert)
+			right = mask - right;
+		h->st.gain &= ~( (mask << shift_right) );
+		h->st.gain |= (right << shift_right);
+	}
+
+	snd_harmony_set_new_gain(h);
+
+	spin_unlock_irq(&h->mixer_lock);
+	
+	return h->st.gain != old_gain;
+}
+
+static int 
+snd_harmony_captureroute_info(struct snd_kcontrol *kc, 
+			      struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = { "Line", "Mic" };
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int 
+snd_harmony_captureroute_get(struct snd_kcontrol *kc, 
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_harmony *h = snd_kcontrol_chip(kc);
+	int value;
+	
+	spin_lock_irq(&h->mixer_lock);
+
+	value = (h->st.gain >> HARMONY_GAIN_IS_SHIFT) & 1;
+	ucontrol->value.enumerated.item[0] = value;
+
+	spin_unlock_irq(&h->mixer_lock);
+
+	return 0;
+}  
+
+static int 
+snd_harmony_captureroute_put(struct snd_kcontrol *kc, 
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_harmony *h = snd_kcontrol_chip(kc);
+	int value;
+	int old_gain = h->st.gain;
+	
+	spin_lock_irq(&h->mixer_lock);
+
+	value = ucontrol->value.enumerated.item[0] & 1;
+	h->st.gain &= ~HARMONY_GAIN_IS_MASK;
+ 	h->st.gain |= value << HARMONY_GAIN_IS_SHIFT;
+
+	snd_harmony_set_new_gain(h);
+
+	spin_unlock_irq(&h->mixer_lock);
+	
+	return h->st.gain != old_gain;
+}
+
+#define HARMONY_CONTROLS	ARRAY_SIZE(snd_harmony_controls)
+
+#define HARMONY_VOLUME(xname, left_shift, right_shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,                \
+  .info = snd_harmony_mixercontrol_info,                             \
+  .get = snd_harmony_volume_get, .put = snd_harmony_volume_put,      \
+  .private_value = ((left_shift) | ((right_shift) << 8) |            \
+                   ((mask) << 16) | ((invert) << 24)) }
+
+static struct snd_kcontrol_new snd_harmony_controls[] = {
+	HARMONY_VOLUME("Master Playback Volume", HARMONY_GAIN_LO_SHIFT, 
+		       HARMONY_GAIN_RO_SHIFT, HARMONY_GAIN_OUT, 1),
+	HARMONY_VOLUME("Capture Volume", HARMONY_GAIN_LI_SHIFT,
+		       HARMONY_GAIN_RI_SHIFT, HARMONY_GAIN_IN, 0),
+	HARMONY_VOLUME("Monitor Volume", HARMONY_GAIN_MA_SHIFT,
+		       HARMONY_GAIN_MA_SHIFT, HARMONY_GAIN_MA, 1),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Input Route",
+		.info = snd_harmony_captureroute_info,
+		.get = snd_harmony_captureroute_get,
+		.put = snd_harmony_captureroute_put
+	},
+	HARMONY_VOLUME("Internal Speaker Switch", HARMONY_GAIN_SE_SHIFT,
+		       HARMONY_GAIN_SE_SHIFT, 1, 0),
+	HARMONY_VOLUME("Line-Out Switch", HARMONY_GAIN_LE_SHIFT,
+		       HARMONY_GAIN_LE_SHIFT, 1, 0),
+	HARMONY_VOLUME("Headphones Switch", HARMONY_GAIN_HE_SHIFT,
+		       HARMONY_GAIN_HE_SHIFT, 1, 0),
+};
+
+static void __devinit
+snd_harmony_mixer_reset(struct snd_harmony *h)
+{
+	harmony_mute(h);
+	harmony_reset(h);
+	h->st.gain = HARMONY_GAIN_DEFAULT;
+	harmony_unmute(h);
+}
+
+static int __devinit
+snd_harmony_mixer_init(struct snd_harmony *h)
+{
+	struct snd_card *card;
+	int idx, err;
+
+	if (snd_BUG_ON(!h))
+		return -EINVAL;
+	card = h->card;
+	strcpy(card->mixername, "Harmony Gain control interface");
+
+	for (idx = 0; idx < HARMONY_CONTROLS; idx++) {
+		err = snd_ctl_add(card, 
+				  snd_ctl_new1(&snd_harmony_controls[idx], h));
+		if (err < 0)
+			return err;
+	}
+	
+	snd_harmony_mixer_reset(h);
+
+	return 0;
+}
+
+static int
+snd_harmony_free(struct snd_harmony *h)
+{
+        if (h->gdma.addr)
+                snd_dma_free_pages(&h->gdma);
+        if (h->sdma.addr)
+                snd_dma_free_pages(&h->sdma);
+
+	if (h->irq >= 0)
+		free_irq(h->irq, h);
+
+	if (h->iobase)
+		iounmap(h->iobase);
+
+	parisc_set_drvdata(h->dev, NULL);
+
+	kfree(h);
+	return 0;
+}
+
+static int
+snd_harmony_dev_free(struct snd_device *dev)
+{
+	struct snd_harmony *h = dev->device_data;
+	return snd_harmony_free(h);
+}
+
+static int __devinit
+snd_harmony_create(struct snd_card *card, 
+		   struct parisc_device *padev, 
+		   struct snd_harmony **rchip)
+{
+	int err;
+	struct snd_harmony *h;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_harmony_dev_free,
+	};
+
+	*rchip = NULL;
+
+	h = kzalloc(sizeof(*h), GFP_KERNEL);
+	if (h == NULL)
+		return -ENOMEM;
+
+	h->hpa = padev->hpa.start;
+	h->card = card;
+	h->dev = padev;
+	h->irq = -1;
+	h->iobase = ioremap_nocache(padev->hpa.start, HARMONY_SIZE);
+	if (h->iobase == NULL) {
+		printk(KERN_ERR PFX "unable to remap hpa 0x%lx\n",
+		       (unsigned long)padev->hpa.start);
+		err = -EBUSY;
+		goto free_and_ret;
+	}
+		
+	err = request_irq(padev->irq, snd_harmony_interrupt, 0,
+			  "harmony", h);
+	if (err) {
+		printk(KERN_ERR PFX "could not obtain interrupt %d",
+		       padev->irq);
+		goto free_and_ret;
+	}
+	h->irq = padev->irq;
+
+	spin_lock_init(&h->mixer_lock);
+	spin_lock_init(&h->lock);
+
+        if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+                                  h, &ops)) < 0) {
+                goto free_and_ret;
+        }
+
+	snd_card_set_dev(card, &padev->dev);
+
+	*rchip = h;
+
+	return 0;
+
+free_and_ret:
+	snd_harmony_free(h);
+	return err;
+}
+
+static int __devinit
+snd_harmony_probe(struct parisc_device *padev)
+{
+	int err;
+	struct snd_card *card;
+	struct snd_harmony *h;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	err = snd_harmony_create(card, padev, &h);
+	if (err < 0)
+		goto free_and_ret;
+
+	err = snd_harmony_pcm_init(h);
+	if (err < 0)
+		goto free_and_ret;
+
+	err = snd_harmony_mixer_init(h);
+	if (err < 0)
+		goto free_and_ret;
+
+	strcpy(card->driver, "harmony");
+	strcpy(card->shortname, "Harmony");
+	sprintf(card->longname, "%s at 0x%lx, irq %i",
+		card->shortname, h->hpa, h->irq);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto free_and_ret;
+
+	parisc_set_drvdata(padev, card);
+	return 0;
+
+free_and_ret:
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit
+snd_harmony_remove(struct parisc_device *padev)
+{
+	snd_card_free(parisc_get_drvdata(padev));
+	parisc_set_drvdata(padev, NULL);
+	return 0;
+}
+
+static struct parisc_driver snd_harmony_driver = {
+	.name = "harmony",
+	.id_table = snd_harmony_devtable,
+	.probe = snd_harmony_probe,
+	.remove = __devexit_p(snd_harmony_remove),
+};
+
+static int __init 
+alsa_harmony_init(void)
+{
+	return register_parisc_driver(&snd_harmony_driver);
+}
+
+static void __exit
+alsa_harmony_fini(void)
+{
+	unregister_parisc_driver(&snd_harmony_driver);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kyle McMartin <kyle@parisc-linux.org>");
+MODULE_DESCRIPTION("Harmony sound driver");
+
+module_init(alsa_harmony_init);
+module_exit(alsa_harmony_fini);
diff --git a/linux/sound/parisc/harmony.h b/linux/sound/parisc/harmony.h
new file mode 100644
index 0000000..2e43452
--- /dev/null
+++ b/linux/sound/parisc/harmony.h
@@ -0,0 +1,154 @@
+/* Hewlett-Packard Harmony audio driver
+ * Copyright (C) 2004, Kyle McMartin <kyle@parisc-linux.org>
+ */
+
+#ifndef __HARMONY_H__
+#define __HARMONY_H__
+
+struct harmony_buffer {
+        unsigned long addr;
+        int buf;
+        int count;
+        int size;
+        int coherent;
+};
+
+struct snd_harmony {
+        int irq;
+
+        unsigned long hpa; /* hard physical address */
+        void __iomem *iobase; /* remapped io address */
+
+        struct parisc_device *dev;
+
+        struct {
+                u32 gain;
+                u32 rate;
+                u32 format;
+                u32 stereo;
+		int playing;
+		int capturing;
+        } st;
+
+        struct snd_dma_device dma; /* playback/capture */
+        struct harmony_buffer pbuf;
+	struct harmony_buffer cbuf;
+
+        struct snd_dma_buffer gdma; /* graveyard */
+        struct snd_dma_buffer sdma; /* silence */
+
+        struct {
+                unsigned long play_intr;
+	        unsigned long rec_intr;
+                unsigned long graveyard_intr;
+                unsigned long silence_intr;
+        } stats;
+
+        struct snd_pcm *pcm;
+        struct snd_card *card;
+        struct snd_pcm_substream *psubs;
+	struct snd_pcm_substream *csubs;
+        struct snd_info_entry *proc;
+
+        spinlock_t lock;
+        spinlock_t mixer_lock;
+};
+
+#define MAX_PCM_DEVICES     1
+#define MAX_PCM_SUBSTREAMS  4
+#define MAX_MIDI_DEVICES    0
+
+#define HARMONY_SIZE       64
+
+#define BUF_SIZE     PAGE_SIZE
+#define MAX_BUFS     16
+#define MAX_BUF_SIZE (MAX_BUFS * BUF_SIZE)
+
+#define PLAYBACK_BUFS    MAX_BUFS
+#define RECORD_BUFS      MAX_BUFS
+#define GRAVEYARD_BUFS   1
+#define GRAVEYARD_BUFSZ  (GRAVEYARD_BUFS*BUF_SIZE)
+#define SILENCE_BUFS     1
+#define SILENCE_BUFSZ    (SILENCE_BUFS*BUF_SIZE)
+
+#define HARMONY_ID       0x000
+#define HARMONY_RESET    0x004
+#define HARMONY_CNTL     0x008
+#define HARMONY_GAINCTL  0x00c
+#define HARMONY_PNXTADD  0x010
+#define HARMONY_PCURADD  0x014
+#define HARMONY_RNXTADD  0x018
+#define HARMONY_RCURADD  0x01c
+#define HARMONY_DSTATUS  0x020
+#define HARMONY_OV       0x024
+#define HARMONY_PIO      0x028
+#define HARMONY_DIAG     0x03c
+
+#define HARMONY_CNTL_C          0x80000000
+#define HARMONY_CNTL_ST         0x00000020
+#define HARMONY_CNTL_44100      0x00000015      /* HARMONY_SR_44KHZ */
+#define HARMONY_CNTL_8000       0x00000008      /* HARMONY_SR_8KHZ */
+
+#define HARMONY_DSTATUS_ID      0x00000000 /* interrupts off */
+#define HARMONY_DSTATUS_PN      0x00000200 /* playback fill */
+#define HARMONY_DSTATUS_RN      0x00000002 /* record fill */
+#define HARMONY_DSTATUS_IE      0x80000000 /* interrupts on */
+
+#define HARMONY_DF_16BIT_LINEAR 0x00000000
+#define HARMONY_DF_8BIT_ULAW    0x00000001
+#define HARMONY_DF_8BIT_ALAW    0x00000002
+
+#define HARMONY_SS_MONO         0x00000000
+#define HARMONY_SS_STEREO       0x00000001
+
+#define HARMONY_GAIN_SILENCE    0x01F00FFF
+#define HARMONY_GAIN_DEFAULT    0x01F00FFF
+
+#define HARMONY_GAIN_HE_SHIFT   27 /* headphones enabled */
+#define HARMONY_GAIN_HE_MASK    (1 << HARMONY_GAIN_HE_SHIFT)
+#define HARMONY_GAIN_LE_SHIFT   26 /* line-out enabled */
+#define HARMONY_GAIN_LE_MASK    (1 << HARMONY_GAIN_LE_SHIFT)
+#define HARMONY_GAIN_SE_SHIFT   25 /* internal-speaker enabled */
+#define HARMONY_GAIN_SE_MASK    (1 << HARMONY_GAIN_SE_SHIFT)
+#define HARMONY_GAIN_IS_SHIFT   24 /* input select - 0 for line, 1 for mic */
+#define HARMONY_GAIN_IS_MASK    (1 << HARMONY_GAIN_IS_SHIFT)
+
+/* monitor attenuation */
+#define HARMONY_GAIN_MA         0x0f
+#define HARMONY_GAIN_MA_SHIFT   20
+#define HARMONY_GAIN_MA_MASK    (HARMONY_GAIN_MA << HARMONY_GAIN_MA_SHIFT)
+
+/* input gain */
+#define HARMONY_GAIN_IN         0x0f
+#define HARMONY_GAIN_LI_SHIFT   16
+#define HARMONY_GAIN_LI_MASK    (HARMONY_GAIN_IN << HARMONY_GAIN_LI_SHIFT)
+#define HARMONY_GAIN_RI_SHIFT   12
+#define HARMONY_GAIN_RI_MASK    (HARMONY_GAIN_IN << HARMONY_GAIN_RI_SHIFT)
+
+/* output gain (master volume) */
+#define HARMONY_GAIN_OUT        0x3f
+#define HARMONY_GAIN_LO_SHIFT   6
+#define HARMONY_GAIN_LO_MASK    (HARMONY_GAIN_OUT << HARMONY_GAIN_LO_SHIFT)
+#define HARMONY_GAIN_RO_SHIFT   0
+#define HARMONY_GAIN_RO_MASK    (HARMONY_GAIN_OUT << HARMONY_GAIN_RO_SHIFT)
+
+#define HARMONY_MAX_OUT (HARMONY_GAIN_RO_MASK >> HARMONY_GAIN_RO_SHIFT)
+#define HARMONY_MAX_IN  (HARMONY_GAIN_RI_MASK >> HARMONY_GAIN_RI_SHIFT)
+#define HARMONY_MAX_MON (HARMONY_GAIN_MA_MASK >> HARMONY_GAIN_MA_SHIFT)
+
+#define HARMONY_SR_8KHZ         0x08
+#define HARMONY_SR_16KHZ        0x09
+#define HARMONY_SR_27KHZ        0x0A
+#define HARMONY_SR_32KHZ        0x0B
+#define HARMONY_SR_48KHZ        0x0E
+#define HARMONY_SR_9KHZ         0x0F
+#define HARMONY_SR_5KHZ         0x10
+#define HARMONY_SR_11KHZ        0x11
+#define HARMONY_SR_18KHZ        0x12
+#define HARMONY_SR_22KHZ        0x13
+#define HARMONY_SR_37KHZ        0x14
+#define HARMONY_SR_44KHZ        0x15
+#define HARMONY_SR_33KHZ        0x16
+#define HARMONY_SR_6KHZ         0x17
+
+#endif /* __HARMONY_H__ */
diff --git a/linux/sound/pci/Kconfig b/linux/sound/pci/Kconfig
new file mode 100644
index 0000000..12e3465
--- /dev/null
+++ b/linux/sound/pci/Kconfig
@@ -0,0 +1,856 @@
+# ALSA PCI drivers
+
+menuconfig SND_PCI
+	bool "PCI sound devices"
+	depends on PCI
+	default y
+	help
+	  Support for sound devices connected via the PCI bus.
+
+if SND_PCI
+
+config SND_AD1889
+	tristate "Analog Devices AD1889"
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for the integrated AC97 sound
+	  device found in particular on the Hewlett-Packard [BCJ]-xxx0
+	  class PA-RISC workstations, using the AD1819 codec.
+
+	  To compile this as a module, choose M here: the module
+	  will be called snd-ad1889.
+
+config SND_ALS300
+	tristate "Avance Logic ALS300/ALS300+"
+	select SND_PCM
+	select SND_AC97_CODEC
+	select SND_OPL3_LIB
+	help
+	  Say 'Y' or 'M' to include support for Avance Logic ALS300/ALS300+
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-als300
+
+config SND_ALS4000
+	tristate "Avance Logic ALS4000"
+	depends on ISA_DMA_API
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	select SND_SB_COMMON
+	help
+	  Say Y here to include support for soundcards based on Avance Logic
+	  ALS4000 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-als4000.
+
+config SND_ALI5451
+	tristate "ALi M5451 PCI Audio Controller"
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for the integrated AC97 sound
+	  device on motherboards using the ALi M5451 Audio Controller
+	  (M1535/M1535D/M1535+/M1535D+ south bridges).  Newer chipsets
+	  use the "Intel/SiS/nVidia/AMD/ALi AC97 Controller" driver.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ali5451.
+
+config SND_ASIHPI
+	tristate "AudioScience ASIxxxx"
+	depends on X86
+	select FW_LOADER
+	select SND_PCM
+	select SND_HWDEP
+	help
+	  Say Y here to include support for AudioScience ASI sound cards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-asihpi.
+
+config SND_ATIIXP
+	tristate "ATI IXP AC97 Controller"
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for the integrated AC97 sound
+	  device on motherboards with ATI chipsets (ATI IXP 150/200/250/
+	  300/400).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-atiixp.
+
+config SND_ATIIXP_MODEM
+	tristate "ATI IXP Modem"
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for the integrated MC97 modem on
+	  motherboards with ATI chipsets (ATI IXP 150/200/250).
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-atiixp-modem.
+
+config SND_AU8810
+	tristate "Aureal Advantage"
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for Aureal Advantage soundcards.
+
+	  Supported features: Hardware Mixer, SRC, EQ and SPDIF output.
+	  3D support code is in place, but not yet useable. For more info,
+	  email the ALSA developer list, or <mjander@users.sourceforge.net>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-au8810.
+
+config SND_AU8820
+	tristate "Aureal Vortex"
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for Aureal Vortex soundcards.
+
+	  Supported features: Hardware Mixer and SRC. For more info, email
+	  the ALSA developer list, or <mjander@users.sourceforge.net>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-au8820.
+
+config SND_AU8830
+	tristate "Aureal Vortex 2"
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for Aureal Vortex 2 soundcards.
+
+	  Supported features: Hardware Mixer, SRC, EQ and SPDIF output.
+	  3D support code is in place, but not yet useable. For more info,
+	  email the ALSA developer list, or <mjander@users.sourceforge.net>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-au8830.
+
+config SND_AW2
+	tristate "Emagic Audiowerk 2"
+	help
+	  Say Y here to include support for Emagic Audiowerk 2 soundcards.
+
+	  Supported features: Analog and SPDIF output. Analog or SPDIF input.
+	  Note: Switch between analog and digital input does not always work.
+	  It can produce continuous noise. The workaround is to switch again
+	  (and again) between digital and analog input until it works.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-aw2.
+
+
+config SND_AZT3328
+	tristate "Aztech AZF3328 / PCI168"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	select SND_RAWMIDI
+	help
+	  Say Y here to include support for Aztech AZF3328 (PCI168)
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-azt3328.
+
+config SND_BT87X
+	tristate "Bt87x Audio Capture"
+	select SND_PCM
+	help
+	  If you want to record audio from TV cards based on
+	  Brooktree Bt878/Bt879 chips, say Y here and read
+	  <file:Documentation/sound/alsa/Bt87x.txt>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-bt87x.
+
+config SND_BT87X_OVERCLOCK
+	bool "Bt87x Audio overclocking"
+	depends on SND_BT87X
+	help
+	  Say Y here if 448000 Hz isn't enough for you and you want to
+	  record from the analog input with up to 1792000 Hz.
+
+	  Higher sample rates won't hurt your hardware, but audio
+	  quality may suffer.
+
+config SND_CA0106
+	tristate "SB Audigy LS / Live 24bit"
+	select SND_AC97_CODEC
+	select SND_RAWMIDI
+	select SND_VMASTER
+	help
+	  Say Y here to include support for the Sound Blaster Audigy LS
+	  and Live 24bit.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ca0106.
+
+config SND_CMIPCI
+	tristate "C-Media 8338, 8738, 8768, 8770"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_PCM
+	help
+	  If you want to use soundcards based on C-Media CMI8338, CMI8738,
+	  CMI8768 or CMI8770 chips, say Y here and read
+	  <file:Documentation/sound/alsa/CMIPCI.txt>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cmipci.
+
+config SND_OXYGEN_LIB
+        tristate
+
+config SND_OXYGEN
+	tristate "C-Media 8788 (Oxygen)"
+	select SND_OXYGEN_LIB
+	select SND_PCM
+	select SND_MPU401_UART
+	help
+	  Say Y here to include support for sound cards based on the
+	  C-Media CMI8788 (Oxygen HD Audio) chip:
+	   * Asound A-8788
+	   * AuzenTech X-Meridian
+	   * Bgears b-Enspirer
+	   * Club3D Theatron DTS
+	   * HT-Omega Claro (plus)
+	   * HT-Omega Claro halo (XT)
+	   * Razer Barracuda AC-1
+	   * Sondigo Inferno
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-oxygen.
+
+config SND_CS4281
+	tristate "Cirrus Logic (Sound Fusion) CS4281"
+	select SND_OPL3_LIB
+	select SND_RAWMIDI
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for Cirrus Logic CS4281 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cs4281.
+
+config SND_CS46XX
+	tristate "Cirrus Logic (Sound Fusion) CS4280/CS461x/CS462x/CS463x"
+	select SND_RAWMIDI
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for Cirrus Logic CS4610/CS4612/
+	  CS4614/CS4615/CS4622/CS4624/CS4630/CS4280 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cs46xx.
+
+config SND_CS46XX_NEW_DSP
+	bool "Cirrus Logic (Sound Fusion) New DSP support"
+	depends on SND_CS46XX
+	default y
+	help
+	  Say Y here to use a new DSP image for SPDIF and dual codecs.
+
+	  This works better than the old code, so say Y.
+
+config SND_CS5530
+	tristate "CS5530 Audio"
+	depends on ISA_DMA_API
+	select SND_SB16_DSP
+	help
+	  Say Y here to include support for audio on Cyrix/NatSemi CS5530 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cs5530.
+
+config SND_CS5535AUDIO
+	tristate "CS5535/CS5536 Audio"
+	select SND_PCM
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for audio on CS5535 chips. It is
+	  referred to as NS CS5535 IO or AMD CS5535 IO companion in
+	  various literature. This driver also supports the CS5536 audio
+	  device. However, for both chips, on certain boards, you may
+	  need to use ac97_quirk=hp_only if your board has physically
+	  mapped headphone out to master output. If that works for you,
+	  send lspci -vvv output to the mailing list so that your board
+	  can be identified in the quirks list.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-cs5535audio.
+
+config SND_CTXFI
+	tristate "Creative Sound Blaster X-Fi"
+	select SND_PCM
+	help
+	  If you want to use soundcards based on Creative Sound Blastr X-Fi
+	  boards with 20k1 or 20k2 chips, say Y here.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ctxfi.
+
+config SND_DARLA20
+	tristate "(Echoaudio) Darla20"
+	select FW_LOADER
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Darla.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-darla20
+
+config SND_GINA20
+	tristate "(Echoaudio) Gina20"
+	select FW_LOADER
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Gina.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-gina20
+
+config SND_LAYLA20
+	tristate "(Echoaudio) Layla20"
+	select FW_LOADER
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Layla.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-layla20
+
+config SND_DARLA24
+	tristate "(Echoaudio) Darla24"
+	select FW_LOADER
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Darla24.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-darla24
+
+config SND_GINA24
+	tristate "(Echoaudio) Gina24"
+	select FW_LOADER
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Gina24.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-gina24
+
+config SND_LAYLA24
+	tristate "(Echoaudio) Layla24"
+	select FW_LOADER
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Layla24.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-layla24
+
+config SND_MONA
+	tristate "(Echoaudio) Mona"
+	select FW_LOADER
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Mona.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-mona
+
+config SND_MIA
+	tristate "(Echoaudio) Mia"
+	select FW_LOADER
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Mia and Mia-midi.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-mia
+
+config SND_ECHO3G
+	tristate "(Echoaudio) 3G cards"
+	select FW_LOADER
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Gina3G and Layla3G.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-echo3g
+
+config SND_INDIGO
+	tristate "(Echoaudio) Indigo"
+	select FW_LOADER
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Indigo.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-indigo
+
+config SND_INDIGOIO
+	tristate "(Echoaudio) Indigo IO"
+	select FW_LOADER
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Indigo IO.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-indigoio
+
+config SND_INDIGODJ
+	tristate "(Echoaudio) Indigo DJ"
+	select FW_LOADER
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Indigo DJ.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-indigodj
+
+config SND_INDIGOIOX
+	tristate "(Echoaudio) Indigo IOx"
+	select FW_LOADER
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Indigo IOx.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-indigoiox
+
+config SND_INDIGODJX
+	tristate "(Echoaudio) Indigo DJx"
+	select FW_LOADER
+	select SND_PCM
+	help
+	  Say 'Y' or 'M' to include support for Echoaudio Indigo DJx.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-indigodjx
+
+config SND_EMU10K1
+	tristate "Emu10k1 (SB Live!, Audigy, E-mu APS)"
+	select FW_LOADER
+	select SND_HWDEP
+	select SND_RAWMIDI
+	select SND_AC97_CODEC
+	help
+	  Say Y to include support for Sound Blaster PCI 512, Live!,
+	  Audigy and E-mu APS (partially supported) soundcards.
+
+	  The confusing multitude of mixer controls is documented in
+	  <file:Documentation/sound/alsa/SB-Live-mixer.txt> and
+	  <file:Documentation/sound/alsa/Audigy-mixer.txt>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-emu10k1.
+
+config SND_EMU10K1X
+	tristate "Emu10k1X (Dell OEM Version)"
+	select SND_AC97_CODEC
+	select SND_RAWMIDI
+	help
+	  Say Y here to include support for the Dell OEM version of the
+	  Sound Blaster Live!.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-emu10k1x.
+
+config SND_ENS1370
+	tristate "(Creative) Ensoniq AudioPCI 1370"
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say Y here to include support for Ensoniq AudioPCI ES1370 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ens1370.
+
+config SND_ENS1371
+	tristate "(Creative) Ensoniq AudioPCI 1371/1373"
+	select SND_RAWMIDI
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for Ensoniq AudioPCI ES1371 chips and
+	  Sound Blaster PCI 64 or 128 soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ens1371.
+
+config SND_ES1938
+	tristate "ESS ES1938/1946/1969 (Solo-1)"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for soundcards based on ESS Solo-1
+	  (ES1938, ES1946, ES1969) chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-es1938.
+
+config SND_ES1968
+	tristate "ESS ES1968/1978 (Maestro-1/2/2E)"
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for soundcards based on ESS Maestro
+	  1/2/2E chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-es1968.
+
+config SND_ES1968_INPUT
+	bool "Enable input device for es1968 volume buttons"
+	depends on SND_ES1968
+	depends on INPUT=y || INPUT=SND_ES1968
+	help
+	  If you say Y here, you will get an input device which reports
+	  keypresses for the volume buttons connected to the es1968 chip.
+	  If you say N the buttons will directly control the master volume.
+	  It is recommended to say Y.
+
+config SND_FM801
+	tristate "ForteMedia FM801"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for soundcards based on the ForteMedia
+	  FM801 chip.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-fm801.
+
+config SND_FM801_TEA575X_BOOL
+	bool "ForteMedia FM801 + TEA5757 tuner"
+	depends on SND_FM801
+	depends on VIDEO_V4L2=y || VIDEO_V4L2=SND_FM801
+	help
+	  Say Y here to include support for soundcards based on the ForteMedia
+	  FM801 chip with a TEA5757 tuner connected to GPIO1-3 pins (Media
+	  Forte SF256-PCS-02) into the snd-fm801 driver.
+
+config SND_FM801_TEA575X
+	tristate
+	depends on SND_FM801_TEA575X_BOOL
+	default SND_FM801
+
+source "sound/pci/hda/Kconfig"
+
+config SND_HDSP
+	tristate "RME Hammerfall DSP Audio"
+	select SND_HWDEP
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say Y here to include support for RME Hammerfall DSP Audio
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-hdsp.
+
+comment "Don't forget to add built-in firmwares for HDSP driver"
+	depends on SND_HDSP=y
+
+config SND_HDSPM
+	tristate "RME Hammerfall DSP MADI"
+	select SND_HWDEP
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say Y here to include support for RME Hammerfall DSP MADI
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-hdspm.
+
+config SND_HIFIER
+	tristate "TempoTec HiFier Fantasia"
+	select SND_OXYGEN_LIB
+	select SND_PCM
+	select SND_MPU401_UART
+	help
+	  Say Y here to include support for the MediaTek/TempoTec HiFier
+	  Fantasia sound card.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-hifier.
+
+config SND_ICE1712
+	tristate "ICEnsemble ICE1712 (Envy24)"
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	select BITREVERSE
+	help
+	  Say Y here to include support for soundcards based on the
+	  ICE1712 (Envy24) chip.
+
+	  Currently supported hardware is: M-Audio Delta 1010(LT),
+	  DiO 2496, 66, 44, 410, Audiophile 24/96; Digigram VX442;
+	  TerraTec EWX 24/96, EWS 88MT/D, DMX 6Fire, Phase 88;
+	  Hoontech SoundTrack DSP 24/Value/Media7.1; Event EZ8;
+	  Lionstracs Mediastation, Terrasoniq TS 88.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ice1712.
+
+config SND_ICE1724
+	tristate "ICE/VT1724/1720 (Envy24HT/PT)"
+	select SND_RAWMIDI
+	select SND_AC97_CODEC
+	select SND_VMASTER
+	help
+	  Say Y here to include support for soundcards based on
+	  ICE/VT1724/1720 (Envy24HT/PT) chips.
+
+	  Currently supported hardware is: AMP AUDIO2000; M-Audio
+	  Revolution 5.1, 7.1, Audiophile 192; TerraTec Aureon 5.1 Sky,
+	  7.1 Space/Universe, Phase 22/28; Onkyo SE-90PCI, SE-200PCI;
+	  AudioTrak Prodigy 192, 7.1 (HIFI/LT/XT), HD2; Hercules
+	  Fortissimo IV; ESI Juli@; Pontis MS300; EGO-SYS WaveTerminal
+	  192M; Albatron K8X800 Pro II; Chaintech ZNF3-150/250, 9CJS,
+	  AV-710; Shuttle SN25P.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ice1724.
+
+config SND_INTEL8X0
+	tristate "Intel/SiS/nVidia/AMD/ALi AC97 Controller"
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for the integrated AC97 sound
+	  device on motherboards with Intel/SiS/nVidia/AMD chipsets, or
+	  ALi chipsets using the M5455 Audio Controller.  (There is a
+	  separate driver for ALi M5451 Audio Controllers.)
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-intel8x0.
+
+config SND_INTEL8X0M
+	tristate "Intel/SiS/nVidia/AMD MC97 Modem"
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for the integrated MC97 modem on
+	  motherboards with Intel/SiS/nVidia/AMD chipsets.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-intel8x0m.
+
+config SND_KORG1212
+	tristate "Korg 1212 IO"
+	select SND_PCM
+	help
+	  Say Y here to include support for Korg 1212IO soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-korg1212.
+
+config SND_LX6464ES
+	tristate "Digigram LX6464ES"
+	select SND_PCM
+	help
+	  Say Y here to include support for Digigram LX6464ES boards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-lx6464es.
+
+
+config SND_MAESTRO3
+	tristate "ESS Allegro/Maestro3"
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for soundcards based on ESS Maestro 3
+	  (Allegro) chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-maestro3.
+
+config SND_MAESTRO3_INPUT
+	bool "Enable input device for maestro3 volume buttons"
+	depends on SND_MAESTRO3
+	depends on INPUT=y || INPUT=SND_MAESTRO3
+	help
+	  If you say Y here, you will get an input device which reports
+	  keypresses for the volume buttons connected to the maestro3 chip.
+	  If you say N the buttons will directly control the master volume.
+	  It is recommended to say Y.
+
+config SND_MIXART
+	tristate "Digigram miXart"
+	select SND_HWDEP
+	select SND_PCM
+	help
+	  If you want to use Digigram miXart soundcards, say Y here and
+	  read <file:Documentation/sound/alsa/MIXART.txt>.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-mixart.
+
+config SND_NM256
+	tristate "NeoMagic NM256AV/ZX"
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for NeoMagic NM256AV/ZX chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-nm256.
+
+config SND_PCXHR
+	tristate "Digigram PCXHR"
+	select SND_PCM
+	select SND_HWDEP
+	help
+	  Say Y here to include support for Digigram PCXHR boards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-pcxhr.
+
+config SND_RIPTIDE
+	tristate "Conexant Riptide"
+	select FW_LOADER
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say 'Y' or 'M' to include support for Conexant Riptide chip.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-riptide
+
+config SND_RME32
+	tristate "RME Digi32, 32/8, 32 PRO"
+	select SND_PCM
+	help
+	  Say Y to include support for RME Digi32, Digi32 PRO and
+	  Digi32/8 (Sek'd Prodif32, Prodif96 and Prodif Gold) audio
+	  devices.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-rme32.
+
+config SND_RME96
+	tristate "RME Digi96, 96/8, 96/8 PRO"
+	select SND_PCM
+	help
+	  Say Y here to include support for RME Digi96, Digi96/8 and
+	  Digi96/8 PRO/PAD/PST soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-rme96.
+
+config SND_RME9652
+	tristate "RME Digi9652 (Hammerfall)"
+	select SND_PCM
+	help
+	  Say Y here to include support for RME Hammerfall (RME
+	  Digi9652/Digi9636) soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-rme9652.
+
+config SND_SIS7019
+	tristate "SiS 7019 Audio Accelerator"
+	depends on X86 && !X86_64
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for the SiS 7019 Audio Accelerator.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sis7019.
+
+config SND_SONICVIBES
+	tristate "S3 SonicVibes"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for soundcards based on the S3
+	  SonicVibes chip.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sonicvibes.
+
+config SND_TRIDENT
+	tristate "Trident 4D-Wave DX/NX; SiS 7018"
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for soundcards based on Trident
+	  4D-Wave DX/NX or SiS 7018 chips.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-trident.
+
+config SND_VIA82XX
+	tristate "VIA 82C686A/B, 8233/8235 AC97 Controller"
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for the integrated AC97 sound
+	  device on motherboards with VIA chipsets.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-via82xx.
+
+config SND_VIA82XX_MODEM
+	tristate "VIA 82C686A/B, 8233 based Modems"
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for the integrated MC97 modem on
+	  motherboards with VIA chipsets.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-via82xx-modem.
+
+config SND_VIRTUOSO
+	tristate "Asus Virtuoso 66/100/200 (Xonar)"
+	select SND_OXYGEN_LIB
+	select SND_PCM
+	select SND_MPU401_UART
+	select SND_JACK if INPUT=y || INPUT=SND
+	help
+	  Say Y here to include support for sound cards based on the
+	  Asus AV66/AV100/AV200 chips, i.e., Xonar D1, DX, D2, D2X, DS,
+	  Essence ST (Deluxe), and Essence STX.
+	  Support for the HDAV1.3 (Deluxe) is incomplete; for the
+	  HDAV1.3 Slim and Xense, missing.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-virtuoso.
+
+config SND_VX222
+	tristate "Digigram VX222"
+	select SND_VX_LIB
+	help
+	  Say Y here to include support for Digigram VX222 soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-vx222.
+
+config SND_YMFPCI
+	tristate "Yamaha YMF724/740/744/754"
+	select SND_OPL3_LIB
+	select SND_MPU401_UART
+	select SND_AC97_CODEC
+	help
+	  Say Y here to include support for Yamaha PCI audio chips -
+	  YMF724, YMF724F, YMF740, YMF740C, YMF744, YMF754.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ymfpci.
+
+endif	# SND_PCI
diff --git a/linux/sound/pci/Makefile b/linux/sound/pci/Makefile
new file mode 100644
index 0000000..9cf4348
--- /dev/null
+++ b/linux/sound/pci/Makefile
@@ -0,0 +1,81 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ad1889-objs := ad1889.o
+snd-als300-objs := als300.o
+snd-als4000-objs := als4000.o
+snd-atiixp-objs := atiixp.o
+snd-atiixp-modem-objs := atiixp_modem.o
+snd-azt3328-objs := azt3328.o
+snd-bt87x-objs := bt87x.o
+snd-cmipci-objs := cmipci.o
+snd-cs4281-objs := cs4281.o
+snd-cs5530-objs := cs5530.o
+snd-ens1370-objs := ens1370.o ak4531_codec.o
+snd-ens1371-objs := ens1371.o
+snd-es1938-objs := es1938.o
+snd-es1968-objs := es1968.o
+snd-fm801-objs := fm801.o
+snd-intel8x0-objs := intel8x0.o
+snd-intel8x0m-objs := intel8x0m.o
+snd-maestro3-objs := maestro3.o
+snd-rme32-objs := rme32.o
+snd-rme96-objs := rme96.o
+snd-sis7019-objs := sis7019.o
+snd-sonicvibes-objs := sonicvibes.o
+snd-via82xx-objs := via82xx.o
+snd-via82xx-modem-objs := via82xx_modem.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AD1889) += snd-ad1889.o
+obj-$(CONFIG_SND_ALS300) += snd-als300.o
+obj-$(CONFIG_SND_ALS4000) += snd-als4000.o
+obj-$(CONFIG_SND_ATIIXP) += snd-atiixp.o
+obj-$(CONFIG_SND_ATIIXP_MODEM) += snd-atiixp-modem.o
+obj-$(CONFIG_SND_AZT3328) += snd-azt3328.o
+obj-$(CONFIG_SND_BT87X) += snd-bt87x.o
+obj-$(CONFIG_SND_CMIPCI) += snd-cmipci.o
+obj-$(CONFIG_SND_CS4281) += snd-cs4281.o
+obj-$(CONFIG_SND_CS5530) += snd-cs5530.o
+obj-$(CONFIG_SND_ENS1370) += snd-ens1370.o
+obj-$(CONFIG_SND_ENS1371) += snd-ens1371.o
+obj-$(CONFIG_SND_ES1938) += snd-es1938.o
+obj-$(CONFIG_SND_ES1968) += snd-es1968.o
+obj-$(CONFIG_SND_FM801) += snd-fm801.o
+obj-$(CONFIG_SND_INTEL8X0) += snd-intel8x0.o
+obj-$(CONFIG_SND_INTEL8X0M) += snd-intel8x0m.o
+obj-$(CONFIG_SND_MAESTRO3) += snd-maestro3.o
+obj-$(CONFIG_SND_RME32) += snd-rme32.o
+obj-$(CONFIG_SND_RME96) += snd-rme96.o
+obj-$(CONFIG_SND_SIS7019) += snd-sis7019.o
+obj-$(CONFIG_SND_SONICVIBES) += snd-sonicvibes.o
+obj-$(CONFIG_SND_VIA82XX) += snd-via82xx.o
+obj-$(CONFIG_SND_VIA82XX_MODEM) += snd-via82xx-modem.o
+
+obj-$(CONFIG_SND) += \
+	ac97/ \
+	ali5451/ \
+	asihpi/ \
+	au88x0/ \
+	aw2/ \
+	ctxfi/ \
+	ca0106/ \
+	cs46xx/ \
+	cs5535audio/ \
+	lx6464es/ \
+	echoaudio/ \
+	emu10k1/ \
+	hda/ \
+	ice1712/ \
+	korg1212/ \
+	mixart/ \
+	nm256/ \
+	oxygen/ \
+	pcxhr/ \
+	riptide/ \
+	rme9652/ \
+	trident/ \
+	ymfpci/ \
+	vx222/
diff --git a/linux/sound/pci/ac97/Makefile b/linux/sound/pci/ac97/Makefile
new file mode 100644
index 0000000..41fa322
--- /dev/null
+++ b/linux/sound/pci/ac97/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ac97-codec-y := ac97_codec.o ac97_pcm.o
+snd-ac97-codec-$(CONFIG_PROC_FS) += ac97_proc.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AC97_CODEC) += snd-ac97-codec.o
diff --git a/linux/sound/pci/ac97/ac97_codec.c b/linux/sound/pci/ac97/ac97_codec.c
new file mode 100644
index 0000000..a7630e9
--- /dev/null
+++ b/linux/sound/pci/ac97/ac97_codec.c
@@ -0,0 +1,2917 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Universal interface for Audio Codec '97
+ *
+ *  For more details look to AC '97 component specification revision 2.2
+ *  by Intel Corporation (http://developer.intel.com).
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include "ac97_id.h"
+
+#include "ac97_patch.c"
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Universal interface for Audio Codec '97");
+MODULE_LICENSE("GPL");
+
+static int enable_loopback;
+
+module_param(enable_loopback, bool, 0444);
+MODULE_PARM_DESC(enable_loopback, "Enable AC97 ADC/DAC Loopback Control");
+
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+static int power_save = CONFIG_SND_AC97_POWER_SAVE_DEFAULT;
+module_param(power_save, int, 0644);
+MODULE_PARM_DESC(power_save, "Automatic power-saving timeout "
+		 "(in second, 0 = disable).");
+#endif
+/*
+
+ */
+
+struct ac97_codec_id {
+	unsigned int id;
+	unsigned int mask;
+	const char *name;
+	int (*patch)(struct snd_ac97 *ac97);
+	int (*mpatch)(struct snd_ac97 *ac97);
+	unsigned int flags;
+};
+
+static const struct ac97_codec_id snd_ac97_codec_id_vendors[] = {
+{ 0x41445300, 0xffffff00, "Analog Devices",	NULL,	NULL },
+{ 0x414b4d00, 0xffffff00, "Asahi Kasei",	NULL,	NULL },
+{ 0x414c4300, 0xffffff00, "Realtek",		NULL,	NULL },
+{ 0x414c4700, 0xffffff00, "Realtek",		NULL,	NULL },
+{ 0x434d4900, 0xffffff00, "C-Media Electronics", NULL,	NULL },
+{ 0x43525900, 0xffffff00, "Cirrus Logic",	NULL,	NULL },
+{ 0x43585400, 0xffffff00, "Conexant",           NULL,	NULL },
+{ 0x44543000, 0xffffff00, "Diamond Technology", NULL,	NULL },
+{ 0x454d4300, 0xffffff00, "eMicro",		NULL,	NULL },
+{ 0x45838300, 0xffffff00, "ESS Technology",	NULL,	NULL },
+{ 0x48525300, 0xffffff00, "Intersil",		NULL,	NULL },
+{ 0x49434500, 0xffffff00, "ICEnsemble",		NULL,	NULL },
+{ 0x49544500, 0xffffff00, "ITE Tech.Inc",	NULL,	NULL },
+{ 0x4e534300, 0xffffff00, "National Semiconductor", NULL, NULL },
+{ 0x50534300, 0xffffff00, "Philips",		NULL,	NULL },
+{ 0x53494c00, 0xffffff00, "Silicon Laboratory",	NULL,	NULL },
+{ 0x53544d00, 0xffffff00, "STMicroelectronics",	NULL,	NULL },
+{ 0x54524100, 0xffffff00, "TriTech",		NULL,	NULL },
+{ 0x54584e00, 0xffffff00, "Texas Instruments",	NULL,	NULL },
+{ 0x56494100, 0xffffff00, "VIA Technologies",   NULL,	NULL },
+{ 0x57454300, 0xffffff00, "Winbond",		NULL,	NULL },
+{ 0x574d4c00, 0xffffff00, "Wolfson",		NULL,	NULL },
+{ 0x594d4800, 0xffffff00, "Yamaha",		NULL,	NULL },
+{ 0x83847600, 0xffffff00, "SigmaTel",		NULL,	NULL },
+{ 0,	      0, 	  NULL,			NULL,	NULL }
+};
+
+static const struct ac97_codec_id snd_ac97_codec_ids[] = {
+{ 0x41445303, 0xffffffff, "AD1819",		patch_ad1819,	NULL },
+{ 0x41445340, 0xffffffff, "AD1881",		patch_ad1881,	NULL },
+{ 0x41445348, 0xffffffff, "AD1881A",		patch_ad1881,	NULL },
+{ 0x41445360, 0xffffffff, "AD1885",		patch_ad1885,	NULL },
+{ 0x41445361, 0xffffffff, "AD1886",		patch_ad1886,	NULL },
+{ 0x41445362, 0xffffffff, "AD1887",		patch_ad1881,	NULL },
+{ 0x41445363, 0xffffffff, "AD1886A",		patch_ad1881,	NULL },
+{ 0x41445368, 0xffffffff, "AD1888",		patch_ad1888,	NULL },
+{ 0x41445370, 0xffffffff, "AD1980",		patch_ad1980,	NULL },
+{ 0x41445372, 0xffffffff, "AD1981A",		patch_ad1981a,	NULL },
+{ 0x41445374, 0xffffffff, "AD1981B",		patch_ad1981b,	NULL },
+{ 0x41445375, 0xffffffff, "AD1985",		patch_ad1985,	NULL },
+{ 0x41445378, 0xffffffff, "AD1986",		patch_ad1986,	NULL },
+{ 0x414b4d00, 0xffffffff, "AK4540",		NULL,		NULL },
+{ 0x414b4d01, 0xffffffff, "AK4542",		NULL,		NULL },
+{ 0x414b4d02, 0xffffffff, "AK4543",		NULL,		NULL },
+{ 0x414b4d06, 0xffffffff, "AK4544A",		NULL,		NULL },
+{ 0x414b4d07, 0xffffffff, "AK4545",		NULL,		NULL },
+{ 0x414c4300, 0xffffff00, "ALC100,100P", 	NULL,		NULL },
+{ 0x414c4710, 0xfffffff0, "ALC200,200P",	NULL,		NULL },
+{ 0x414c4721, 0xffffffff, "ALC650D",		NULL,	NULL }, /* already patched */
+{ 0x414c4722, 0xffffffff, "ALC650E",		NULL,	NULL }, /* already patched */
+{ 0x414c4723, 0xffffffff, "ALC650F",		NULL,	NULL }, /* already patched */
+{ 0x414c4720, 0xfffffff0, "ALC650",		patch_alc650,	NULL },
+{ 0x414c4730, 0xffffffff, "ALC101",		NULL,		NULL },
+{ 0x414c4740, 0xfffffff0, "ALC202",		NULL,		NULL },
+{ 0x414c4750, 0xfffffff0, "ALC250",		NULL,		NULL },
+{ 0x414c4760, 0xfffffff0, "ALC655",		patch_alc655,	NULL },
+{ 0x414c4770, 0xfffffff0, "ALC203",		patch_alc203,	NULL },
+{ 0x414c4781, 0xffffffff, "ALC658D",		NULL,	NULL }, /* already patched */
+{ 0x414c4780, 0xfffffff0, "ALC658",		patch_alc655,	NULL },
+{ 0x414c4790, 0xfffffff0, "ALC850",		patch_alc850,	NULL },
+{ 0x434d4941, 0xffffffff, "CMI9738",		patch_cm9738,	NULL },
+{ 0x434d4961, 0xffffffff, "CMI9739",		patch_cm9739,	NULL },
+{ 0x434d4969, 0xffffffff, "CMI9780",		patch_cm9780,	NULL },
+{ 0x434d4978, 0xffffffff, "CMI9761A",		patch_cm9761,	NULL },
+{ 0x434d4982, 0xffffffff, "CMI9761B",		patch_cm9761,	NULL },
+{ 0x434d4983, 0xffffffff, "CMI9761A+",		patch_cm9761,	NULL },
+{ 0x43525900, 0xfffffff8, "CS4297",		NULL,		NULL },
+{ 0x43525910, 0xfffffff8, "CS4297A",		patch_cirrus_spdif,	NULL },
+{ 0x43525920, 0xfffffff8, "CS4298",		patch_cirrus_spdif,		NULL },
+{ 0x43525928, 0xfffffff8, "CS4294",		NULL,		NULL },
+{ 0x43525930, 0xfffffff8, "CS4299",		patch_cirrus_cs4299,	NULL },
+{ 0x43525948, 0xfffffff8, "CS4201",		NULL,		NULL },
+{ 0x43525958, 0xfffffff8, "CS4205",		patch_cirrus_spdif,	NULL },
+{ 0x43525960, 0xfffffff8, "CS4291",		NULL,		NULL },
+{ 0x43525970, 0xfffffff8, "CS4202",		NULL,		NULL },
+{ 0x43585421, 0xffffffff, "HSD11246",		NULL,		NULL },	// SmartMC II
+{ 0x43585428, 0xfffffff8, "Cx20468",		patch_conexant,	NULL }, // SmartAMC fixme: the mask might be different
+{ 0x43585430, 0xffffffff, "Cx20468-31",		patch_conexant, NULL },
+{ 0x43585431, 0xffffffff, "Cx20551",           patch_cx20551,  NULL },
+{ 0x44543031, 0xfffffff0, "DT0398",		NULL,		NULL },
+{ 0x454d4328, 0xffffffff, "EM28028",		NULL,		NULL },  // same as TR28028?
+{ 0x45838308, 0xffffffff, "ESS1988",		NULL,		NULL },
+{ 0x48525300, 0xffffff00, "HMP9701",		NULL,		NULL },
+{ 0x49434501, 0xffffffff, "ICE1230",		NULL,		NULL },
+{ 0x49434511, 0xffffffff, "ICE1232",		NULL,		NULL }, // alias VIA VT1611A?
+{ 0x49434514, 0xffffffff, "ICE1232A",		NULL,		NULL },
+{ 0x49434551, 0xffffffff, "VT1616", 		patch_vt1616,	NULL }, 
+{ 0x49434552, 0xffffffff, "VT1616i",		patch_vt1616,	NULL }, // VT1616 compatible (chipset integrated)
+{ 0x49544520, 0xffffffff, "IT2226E",		NULL,		NULL },
+{ 0x49544561, 0xffffffff, "IT2646E",		patch_it2646,	NULL },
+{ 0x4e534300, 0xffffffff, "LM4540,43,45,46,48",	NULL,		NULL }, // only guess --jk
+{ 0x4e534331, 0xffffffff, "LM4549",		NULL,		NULL },
+{ 0x4e534350, 0xffffffff, "LM4550",		patch_lm4550,  	NULL }, // volume wrap fix 
+{ 0x50534304, 0xffffffff, "UCB1400",		patch_ucb1400,	NULL },
+{ 0x53494c20, 0xffffffe0, "Si3036,8",		mpatch_si3036,	mpatch_si3036, AC97_MODEM_PATCH },
+{ 0x53544d02, 0xffffffff, "ST7597",		NULL,		NULL },
+{ 0x54524102, 0xffffffff, "TR28022",		NULL,		NULL },
+{ 0x54524103, 0xffffffff, "TR28023",		NULL,		NULL },
+{ 0x54524106, 0xffffffff, "TR28026",		NULL,		NULL },
+{ 0x54524108, 0xffffffff, "TR28028",		patch_tritech_tr28028,	NULL }, // added by xin jin [07/09/99]
+{ 0x54524123, 0xffffffff, "TR28602",		NULL,		NULL }, // only guess --jk [TR28023 = eMicro EM28023 (new CT1297)]
+{ 0x54584e20, 0xffffffff, "TLC320AD9xC",	NULL,		NULL },
+{ 0x56494161, 0xffffffff, "VIA1612A",		NULL,		NULL }, // modified ICE1232 with S/PDIF
+{ 0x56494170, 0xffffffff, "VIA1617A",		patch_vt1617a,	NULL }, // modified VT1616 with S/PDIF
+{ 0x56494182, 0xffffffff, "VIA1618",		patch_vt1618,   NULL },
+{ 0x57454301, 0xffffffff, "W83971D",		NULL,		NULL },
+{ 0x574d4c00, 0xffffffff, "WM9701,WM9701A",	NULL,		NULL },
+{ 0x574d4C03, 0xffffffff, "WM9703,WM9707,WM9708,WM9717", patch_wolfson03, NULL},
+{ 0x574d4C04, 0xffffffff, "WM9704M,WM9704Q",	patch_wolfson04, NULL},
+{ 0x574d4C05, 0xffffffff, "WM9705,WM9710",	patch_wolfson05, NULL},
+{ 0x574d4C09, 0xffffffff, "WM9709",		NULL,		NULL},
+{ 0x574d4C12, 0xffffffff, "WM9711,WM9712,WM9715",	patch_wolfson11, NULL},
+{ 0x574d4c13, 0xffffffff, "WM9713,WM9714",	patch_wolfson13, NULL, AC97_DEFAULT_POWER_OFF},
+{ 0x594d4800, 0xffffffff, "YMF743",		patch_yamaha_ymf743,	NULL },
+{ 0x594d4802, 0xffffffff, "YMF752",		NULL,		NULL },
+{ 0x594d4803, 0xffffffff, "YMF753",		patch_yamaha_ymf753,	NULL },
+{ 0x83847600, 0xffffffff, "STAC9700,83,84",	patch_sigmatel_stac9700,	NULL },
+{ 0x83847604, 0xffffffff, "STAC9701,3,4,5",	NULL,		NULL },
+{ 0x83847605, 0xffffffff, "STAC9704",		NULL,		NULL },
+{ 0x83847608, 0xffffffff, "STAC9708,11",	patch_sigmatel_stac9708,	NULL },
+{ 0x83847609, 0xffffffff, "STAC9721,23",	patch_sigmatel_stac9721,	NULL },
+{ 0x83847644, 0xffffffff, "STAC9744",		patch_sigmatel_stac9744,	NULL },
+{ 0x83847650, 0xffffffff, "STAC9750,51",	NULL,		NULL },	// patch?
+{ 0x83847652, 0xffffffff, "STAC9752,53",	NULL,		NULL }, // patch?
+{ 0x83847656, 0xffffffff, "STAC9756,57",	patch_sigmatel_stac9756,	NULL },
+{ 0x83847658, 0xffffffff, "STAC9758,59",	patch_sigmatel_stac9758,	NULL },
+{ 0x83847666, 0xffffffff, "STAC9766,67",	NULL,		NULL }, // patch?
+{ 0, 	      0,	  NULL,			NULL,		NULL }
+};
+
+
+static void update_power_regs(struct snd_ac97 *ac97);
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+#define ac97_is_power_save_mode(ac97) \
+	((ac97->scaps & AC97_SCAP_POWER_SAVE) && power_save)
+#else
+#define ac97_is_power_save_mode(ac97) 0
+#endif
+
+
+/*
+ *  I/O routines
+ */
+
+static int snd_ac97_valid_reg(struct snd_ac97 *ac97, unsigned short reg)
+{
+	/* filter some registers for buggy codecs */
+	switch (ac97->id) {
+	case AC97_ID_ST_AC97_ID4:
+		if (reg == 0x08)
+			return 0;
+		/* fall through */
+	case AC97_ID_ST7597:
+		if (reg == 0x22 || reg == 0x7a)
+			return 1;
+		/* fall through */
+	case AC97_ID_AK4540:
+	case AC97_ID_AK4542:
+		if (reg <= 0x1c || reg == 0x20 || reg == 0x26 || reg >= 0x7c)
+			return 1;
+		return 0;
+	case AC97_ID_AD1819:	/* AD1819 */
+	case AC97_ID_AD1881:	/* AD1881 */
+	case AC97_ID_AD1881A:	/* AD1881A */
+		if (reg >= 0x3a && reg <= 0x6e)	/* 0x59 */
+			return 0;
+		return 1;
+	case AC97_ID_AD1885:	/* AD1885 */
+	case AC97_ID_AD1886:	/* AD1886 */
+	case AC97_ID_AD1886A:	/* AD1886A - !!verify!! --jk */
+	case AC97_ID_AD1887:	/* AD1887 - !!verify!! --jk */
+		if (reg == 0x5a)
+			return 1;
+		if (reg >= 0x3c && reg <= 0x6e)	/* 0x59 */
+			return 0;
+		return 1;
+	case AC97_ID_STAC9700:
+	case AC97_ID_STAC9704:
+	case AC97_ID_STAC9705:
+	case AC97_ID_STAC9708:
+	case AC97_ID_STAC9721:
+	case AC97_ID_STAC9744:
+	case AC97_ID_STAC9756:
+		if (reg <= 0x3a || reg >= 0x5a)
+			return 1;
+		return 0;
+	}
+	return 1;
+}
+
+/**
+ * snd_ac97_write - write a value on the given register
+ * @ac97: the ac97 instance
+ * @reg: the register to change
+ * @value: the value to set
+ *
+ * Writes a value on the given register.  This will invoke the write
+ * callback directly after the register check.
+ * This function doesn't change the register cache unlike
+ * #snd_ca97_write_cache(), so use this only when you don't want to
+ * reflect the change to the suspend/resume state.
+ */
+void snd_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short value)
+{
+	if (!snd_ac97_valid_reg(ac97, reg))
+		return;
+	if ((ac97->id & 0xffffff00) == AC97_ID_ALC100) {
+		/* Fix H/W bug of ALC100/100P */
+		if (reg == AC97_MASTER || reg == AC97_HEADPHONE)
+			ac97->bus->ops->write(ac97, AC97_RESET, 0);	/* reset audio codec */
+	}
+	ac97->bus->ops->write(ac97, reg, value);
+}
+
+EXPORT_SYMBOL(snd_ac97_write);
+
+/**
+ * snd_ac97_read - read a value from the given register
+ * 
+ * @ac97: the ac97 instance
+ * @reg: the register to read
+ *
+ * Reads a value from the given register.  This will invoke the read
+ * callback directly after the register check.
+ *
+ * Returns the read value.
+ */
+unsigned short snd_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	if (!snd_ac97_valid_reg(ac97, reg))
+		return 0;
+	return ac97->bus->ops->read(ac97, reg);
+}
+
+/* read a register - return the cached value if already read */
+static inline unsigned short snd_ac97_read_cache(struct snd_ac97 *ac97, unsigned short reg)
+{
+	if (! test_bit(reg, ac97->reg_accessed)) {
+		ac97->regs[reg] = ac97->bus->ops->read(ac97, reg);
+		// set_bit(reg, ac97->reg_accessed);
+	}
+	return ac97->regs[reg];
+}
+
+EXPORT_SYMBOL(snd_ac97_read);
+
+/**
+ * snd_ac97_write_cache - write a value on the given register and update the cache
+ * @ac97: the ac97 instance
+ * @reg: the register to change
+ * @value: the value to set
+ *
+ * Writes a value on the given register and updates the register
+ * cache.  The cached values are used for the cached-read and the
+ * suspend/resume.
+ */
+void snd_ac97_write_cache(struct snd_ac97 *ac97, unsigned short reg, unsigned short value)
+{
+	if (!snd_ac97_valid_reg(ac97, reg))
+		return;
+	mutex_lock(&ac97->reg_mutex);
+	ac97->regs[reg] = value;
+	ac97->bus->ops->write(ac97, reg, value);
+	set_bit(reg, ac97->reg_accessed);
+	mutex_unlock(&ac97->reg_mutex);
+}
+
+EXPORT_SYMBOL(snd_ac97_write_cache);
+
+/**
+ * snd_ac97_update - update the value on the given register
+ * @ac97: the ac97 instance
+ * @reg: the register to change
+ * @value: the value to set
+ *
+ * Compares the value with the register cache and updates the value
+ * only when the value is changed.
+ *
+ * Returns 1 if the value is changed, 0 if no change, or a negative
+ * code on failure.
+ */
+int snd_ac97_update(struct snd_ac97 *ac97, unsigned short reg, unsigned short value)
+{
+	int change;
+
+	if (!snd_ac97_valid_reg(ac97, reg))
+		return -EINVAL;
+	mutex_lock(&ac97->reg_mutex);
+	change = ac97->regs[reg] != value;
+	if (change) {
+		ac97->regs[reg] = value;
+		ac97->bus->ops->write(ac97, reg, value);
+	}
+	set_bit(reg, ac97->reg_accessed);
+	mutex_unlock(&ac97->reg_mutex);
+	return change;
+}
+
+EXPORT_SYMBOL(snd_ac97_update);
+
+/**
+ * snd_ac97_update_bits - update the bits on the given register
+ * @ac97: the ac97 instance
+ * @reg: the register to change
+ * @mask: the bit-mask to change
+ * @value: the value to set
+ *
+ * Updates the masked-bits on the given register only when the value
+ * is changed.
+ *
+ * Returns 1 if the bits are changed, 0 if no change, or a negative
+ * code on failure.
+ */
+int snd_ac97_update_bits(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value)
+{
+	int change;
+
+	if (!snd_ac97_valid_reg(ac97, reg))
+		return -EINVAL;
+	mutex_lock(&ac97->reg_mutex);
+	change = snd_ac97_update_bits_nolock(ac97, reg, mask, value);
+	mutex_unlock(&ac97->reg_mutex);
+	return change;
+}
+
+EXPORT_SYMBOL(snd_ac97_update_bits);
+
+/* no lock version - see snd_ac97_update_bits() */
+int snd_ac97_update_bits_nolock(struct snd_ac97 *ac97, unsigned short reg,
+				unsigned short mask, unsigned short value)
+{
+	int change;
+	unsigned short old, new;
+
+	old = snd_ac97_read_cache(ac97, reg);
+	new = (old & ~mask) | (value & mask);
+	change = old != new;
+	if (change) {
+		ac97->regs[reg] = new;
+		ac97->bus->ops->write(ac97, reg, new);
+	}
+	set_bit(reg, ac97->reg_accessed);
+	return change;
+}
+
+static int snd_ac97_ad18xx_update_pcm_bits(struct snd_ac97 *ac97, int codec, unsigned short mask, unsigned short value)
+{
+	int change;
+	unsigned short old, new, cfg;
+
+	mutex_lock(&ac97->page_mutex);
+	old = ac97->spec.ad18xx.pcmreg[codec];
+	new = (old & ~mask) | (value & mask);
+	change = old != new;
+	if (change) {
+		mutex_lock(&ac97->reg_mutex);
+		cfg = snd_ac97_read_cache(ac97, AC97_AD_SERIAL_CFG);
+		ac97->spec.ad18xx.pcmreg[codec] = new;
+		/* select single codec */
+		ac97->bus->ops->write(ac97, AC97_AD_SERIAL_CFG,
+				 (cfg & ~0x7000) |
+				 ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]);
+		/* update PCM bits */
+		ac97->bus->ops->write(ac97, AC97_PCM, new);
+		/* select all codecs */
+		ac97->bus->ops->write(ac97, AC97_AD_SERIAL_CFG,
+				 cfg | 0x7000);
+		mutex_unlock(&ac97->reg_mutex);
+	}
+	mutex_unlock(&ac97->page_mutex);
+	return change;
+}
+
+/*
+ * Controls
+ */
+
+static int snd_ac97_info_enum_double(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value;
+	
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = e->shift_l == e->shift_r ? 1 : 2;
+	uinfo->value.enumerated.items = e->mask;
+	
+	if (uinfo->value.enumerated.item > e->mask - 1)
+		uinfo->value.enumerated.item = e->mask - 1;
+	strcpy(uinfo->value.enumerated.name, e->texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ac97_get_enum_double(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value;
+	unsigned short val, bitmask;
+	
+	for (bitmask = 1; bitmask < e->mask; bitmask <<= 1)
+		;
+	val = snd_ac97_read_cache(ac97, e->reg);
+	ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (bitmask - 1);
+	if (e->shift_l != e->shift_r)
+		ucontrol->value.enumerated.item[1] = (val >> e->shift_r) & (bitmask - 1);
+
+	return 0;
+}
+
+static int snd_ac97_put_enum_double(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	struct ac97_enum *e = (struct ac97_enum *)kcontrol->private_value;
+	unsigned short val;
+	unsigned short mask, bitmask;
+	
+	for (bitmask = 1; bitmask < e->mask; bitmask <<= 1)
+		;
+	if (ucontrol->value.enumerated.item[0] > e->mask - 1)
+		return -EINVAL;
+	val = ucontrol->value.enumerated.item[0] << e->shift_l;
+	mask = (bitmask - 1) << e->shift_l;
+	if (e->shift_l != e->shift_r) {
+		if (ucontrol->value.enumerated.item[1] > e->mask - 1)
+			return -EINVAL;
+		val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+		mask |= (bitmask - 1) << e->shift_r;
+	}
+	return snd_ac97_update_bits(ac97, e->reg, mask, val);
+}
+
+/* save/restore ac97 v2.3 paging */
+static int snd_ac97_page_save(struct snd_ac97 *ac97, int reg, struct snd_kcontrol *kcontrol)
+{
+	int page_save = -1;
+	if ((kcontrol->private_value & (1<<25)) &&
+	    (ac97->ext_id & AC97_EI_REV_MASK) >= AC97_EI_REV_23 &&
+	    (reg >= 0x60 && reg < 0x70)) {
+		unsigned short page = (kcontrol->private_value >> 26) & 0x0f;
+		mutex_lock(&ac97->page_mutex); /* lock paging */
+		page_save = snd_ac97_read(ac97, AC97_INT_PAGING) & AC97_PAGE_MASK;
+		snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page);
+	}
+	return page_save;
+}
+
+static void snd_ac97_page_restore(struct snd_ac97 *ac97, int page_save)
+{
+	if (page_save >= 0) {
+		snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page_save);
+		mutex_unlock(&ac97->page_mutex); /* unlock paging */
+	}
+}
+
+/* volume and switch controls */
+static int snd_ac97_info_volsw(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0x0f;
+	int rshift = (kcontrol->private_value >> 12) & 0x0f;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = shift == rshift ? 1 : 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_ac97_get_volsw(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0x0f;
+	int rshift = (kcontrol->private_value >> 12) & 0x0f;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0x01;
+	int page_save;
+
+	page_save = snd_ac97_page_save(ac97, reg, kcontrol);
+	ucontrol->value.integer.value[0] = (snd_ac97_read_cache(ac97, reg) >> shift) & mask;
+	if (shift != rshift)
+		ucontrol->value.integer.value[1] = (snd_ac97_read_cache(ac97, reg) >> rshift) & mask;
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		if (shift != rshift)
+			ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	snd_ac97_page_restore(ac97, page_save);
+	return 0;
+}
+
+static int snd_ac97_put_volsw(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0x0f;
+	int rshift = (kcontrol->private_value >> 12) & 0x0f;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0x01;
+	int err, page_save;
+	unsigned short val, val2, val_mask;
+	
+	page_save = snd_ac97_page_save(ac97, reg, kcontrol);
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val_mask = mask << shift;
+	val = val << shift;
+	if (shift != rshift) {
+		val2 = (ucontrol->value.integer.value[1] & mask);
+		if (invert)
+			val2 = mask - val2;
+		val_mask |= mask << rshift;
+		val |= val2 << rshift;
+	}
+	err = snd_ac97_update_bits(ac97, reg, val_mask, val);
+	snd_ac97_page_restore(ac97, page_save);
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+	/* check analog mixer power-down */
+	if ((val_mask & 0x8000) &&
+	    (kcontrol->private_value & (1<<30))) {
+		if (val & 0x8000)
+			ac97->power_up &= ~(1 << (reg>>1));
+		else
+			ac97->power_up |= 1 << (reg>>1);
+		update_power_regs(ac97);
+	}
+#endif
+	return err;
+}
+
+static const struct snd_kcontrol_new snd_ac97_controls_master_mono[2] = {
+AC97_SINGLE("Master Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+AC97_SINGLE("Master Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1)
+};
+
+static const struct snd_kcontrol_new snd_ac97_controls_tone[2] = {
+AC97_SINGLE("Tone Control - Bass", AC97_MASTER_TONE, 8, 15, 1),
+AC97_SINGLE("Tone Control - Treble", AC97_MASTER_TONE, 0, 15, 1)
+};
+
+static const struct snd_kcontrol_new snd_ac97_controls_pc_beep[2] = {
+AC97_SINGLE("Beep Playback Switch", AC97_PC_BEEP, 15, 1, 1),
+AC97_SINGLE("Beep Playback Volume", AC97_PC_BEEP, 1, 15, 1)
+};
+
+static const struct snd_kcontrol_new snd_ac97_controls_mic_boost =
+	AC97_SINGLE("Mic Boost (+20dB)", AC97_MIC, 6, 1, 0);
+
+
+static const char* std_rec_sel[] = {"Mic", "CD", "Video", "Aux", "Line", "Mix", "Mix Mono", "Phone"};
+static const char* std_3d_path[] = {"pre 3D", "post 3D"};
+static const char* std_mix[] = {"Mix", "Mic"};
+static const char* std_mic[] = {"Mic1", "Mic2"};
+
+static const struct ac97_enum std_enum[] = {
+AC97_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, std_rec_sel),
+AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, std_3d_path),
+AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, std_mix),
+AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, std_mic),
+};
+
+static const struct snd_kcontrol_new snd_ac97_control_capture_src = 
+AC97_ENUM("Capture Source", std_enum[0]); 
+
+static const struct snd_kcontrol_new snd_ac97_control_capture_vol =
+AC97_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 15, 0);
+
+static const struct snd_kcontrol_new snd_ac97_controls_mic_capture[2] = {
+AC97_SINGLE("Mic Capture Switch", AC97_REC_GAIN_MIC, 15, 1, 1),
+AC97_SINGLE("Mic Capture Volume", AC97_REC_GAIN_MIC, 0, 15, 0)
+};
+
+enum {
+	AC97_GENERAL_PCM_OUT = 0,
+	AC97_GENERAL_STEREO_ENHANCEMENT,
+	AC97_GENERAL_3D,
+	AC97_GENERAL_LOUDNESS,
+	AC97_GENERAL_MONO,
+	AC97_GENERAL_MIC,
+	AC97_GENERAL_LOOPBACK
+};
+
+static const struct snd_kcontrol_new snd_ac97_controls_general[7] = {
+AC97_ENUM("PCM Out Path & Mute", std_enum[1]),
+AC97_SINGLE("Simulated Stereo Enhancement", AC97_GENERAL_PURPOSE, 14, 1, 0),
+AC97_SINGLE("3D Control - Switch", AC97_GENERAL_PURPOSE, 13, 1, 0),
+AC97_SINGLE("Loudness (bass boost)", AC97_GENERAL_PURPOSE, 12, 1, 0),
+AC97_ENUM("Mono Output Select", std_enum[2]),
+AC97_ENUM("Mic Select", std_enum[3]),
+AC97_SINGLE("ADC/DAC Loopback", AC97_GENERAL_PURPOSE, 7, 1, 0)
+};
+
+static const struct snd_kcontrol_new snd_ac97_controls_3d[2] = {
+AC97_SINGLE("3D Control - Center", AC97_3D_CONTROL, 8, 15, 0),
+AC97_SINGLE("3D Control - Depth", AC97_3D_CONTROL, 0, 15, 0)
+};
+
+static const struct snd_kcontrol_new snd_ac97_controls_center[2] = {
+AC97_SINGLE("Center Playback Switch", AC97_CENTER_LFE_MASTER, 7, 1, 1),
+AC97_SINGLE("Center Playback Volume", AC97_CENTER_LFE_MASTER, 0, 31, 1)
+};
+
+static const struct snd_kcontrol_new snd_ac97_controls_lfe[2] = {
+AC97_SINGLE("LFE Playback Switch", AC97_CENTER_LFE_MASTER, 15, 1, 1),
+AC97_SINGLE("LFE Playback Volume", AC97_CENTER_LFE_MASTER, 8, 31, 1)
+};
+
+static const struct snd_kcontrol_new snd_ac97_control_eapd =
+AC97_SINGLE("External Amplifier", AC97_POWERDOWN, 15, 1, 1);
+
+static const struct snd_kcontrol_new snd_ac97_controls_modem_switches[2] = {
+AC97_SINGLE("Off-hook Switch", AC97_GPIO_STATUS, 0, 1, 0),
+AC97_SINGLE("Caller ID Switch", AC97_GPIO_STATUS, 2, 1, 0)
+};
+
+/* change the existing EAPD control as inverted */
+static void set_inv_eapd(struct snd_ac97 *ac97, struct snd_kcontrol *kctl)
+{
+	kctl->private_value = AC97_SINGLE_VALUE(AC97_POWERDOWN, 15, 1, 0);
+	snd_ac97_update_bits(ac97, AC97_POWERDOWN, (1<<15), (1<<15)); /* EAPD up */
+	ac97->scaps |= AC97_SCAP_INV_EAPD;
+}
+
+static int snd_ac97_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+                        
+static int snd_ac97_spdif_cmask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
+					   IEC958_AES0_NONAUDIO |
+					   IEC958_AES0_CON_EMPHASIS_5015 |
+					   IEC958_AES0_CON_NOT_COPYRIGHT;
+	ucontrol->value.iec958.status[1] = IEC958_AES1_CON_CATEGORY |
+					   IEC958_AES1_CON_ORIGINAL;
+	ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS;
+	return 0;
+}
+                        
+static int snd_ac97_spdif_pmask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	/* FIXME: AC'97 spec doesn't say which bits are used for what */
+	ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
+					   IEC958_AES0_NONAUDIO |
+					   IEC958_AES0_PRO_FS |
+					   IEC958_AES0_PRO_EMPHASIS_5015;
+	return 0;
+}
+
+static int snd_ac97_spdif_default_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ac97->reg_mutex);
+	ucontrol->value.iec958.status[0] = ac97->spdif_status & 0xff;
+	ucontrol->value.iec958.status[1] = (ac97->spdif_status >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (ac97->spdif_status >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (ac97->spdif_status >> 24) & 0xff;
+	mutex_unlock(&ac97->reg_mutex);
+	return 0;
+}
+                        
+static int snd_ac97_spdif_default_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned int new = 0;
+	unsigned short val = 0;
+	int change;
+
+	new = val = ucontrol->value.iec958.status[0] & (IEC958_AES0_PROFESSIONAL|IEC958_AES0_NONAUDIO);
+	if (ucontrol->value.iec958.status[0] & IEC958_AES0_PROFESSIONAL) {
+		new |= ucontrol->value.iec958.status[0] & (IEC958_AES0_PRO_FS|IEC958_AES0_PRO_EMPHASIS_5015);
+		switch (new & IEC958_AES0_PRO_FS) {
+		case IEC958_AES0_PRO_FS_44100: val |= 0<<12; break;
+		case IEC958_AES0_PRO_FS_48000: val |= 2<<12; break;
+		case IEC958_AES0_PRO_FS_32000: val |= 3<<12; break;
+		default:		       val |= 1<<12; break;
+		}
+		if ((new & IEC958_AES0_PRO_EMPHASIS) == IEC958_AES0_PRO_EMPHASIS_5015)
+			val |= 1<<3;
+	} else {
+		new |= ucontrol->value.iec958.status[0] & (IEC958_AES0_CON_EMPHASIS_5015|IEC958_AES0_CON_NOT_COPYRIGHT);
+		new |= ((ucontrol->value.iec958.status[1] & (IEC958_AES1_CON_CATEGORY|IEC958_AES1_CON_ORIGINAL)) << 8);
+		new |= ((ucontrol->value.iec958.status[3] & IEC958_AES3_CON_FS) << 24);
+		if ((new & IEC958_AES0_CON_EMPHASIS) == IEC958_AES0_CON_EMPHASIS_5015)
+			val |= 1<<3;
+		if (!(new & IEC958_AES0_CON_NOT_COPYRIGHT))
+			val |= 1<<2;
+		val |= ((new >> 8) & 0xff) << 4;	// category + original
+		switch ((new >> 24) & 0xff) {
+		case IEC958_AES3_CON_FS_44100: val |= 0<<12; break;
+		case IEC958_AES3_CON_FS_48000: val |= 2<<12; break;
+		case IEC958_AES3_CON_FS_32000: val |= 3<<12; break;
+		default:		       val |= 1<<12; break;
+		}
+	}
+
+	mutex_lock(&ac97->reg_mutex);
+	change = ac97->spdif_status != new;
+	ac97->spdif_status = new;
+
+	if (ac97->flags & AC97_CS_SPDIF) {
+		int x = (val >> 12) & 0x03;
+		switch (x) {
+		case 0: x = 1; break;  // 44.1
+		case 2: x = 0; break;  // 48.0
+		default: x = 0; break; // illegal.
+		}
+		change |= snd_ac97_update_bits_nolock(ac97, AC97_CSR_SPDIF, 0x3fff, ((val & 0xcfff) | (x << 12)));
+	} else if (ac97->flags & AC97_CX_SPDIF) {
+		int v;
+		v = new & (IEC958_AES0_CON_EMPHASIS_5015|IEC958_AES0_CON_NOT_COPYRIGHT) ? 0 : AC97_CXR_COPYRGT;
+		v |= new & IEC958_AES0_NONAUDIO ? AC97_CXR_SPDIF_AC3 : AC97_CXR_SPDIF_PCM;
+		change |= snd_ac97_update_bits_nolock(ac97, AC97_CXR_AUDIO_MISC, 
+						      AC97_CXR_SPDIF_MASK | AC97_CXR_COPYRGT,
+						      v);
+	} else if (ac97->id == AC97_ID_YMF743) {
+		change |= snd_ac97_update_bits_nolock(ac97,
+						      AC97_YMF7X3_DIT_CTRL,
+						      0xff38,
+						      ((val << 4) & 0xff00) |
+						      ((val << 2) & 0x0038));
+	} else {
+		unsigned short extst = snd_ac97_read_cache(ac97, AC97_EXTENDED_STATUS);
+		snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); /* turn off */
+
+		change |= snd_ac97_update_bits_nolock(ac97, AC97_SPDIF, 0x3fff, val);
+		if (extst & AC97_EA_SPDIF) {
+			snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF); /* turn on again */
+                }
+	}
+	mutex_unlock(&ac97->reg_mutex);
+
+	return change;
+}
+
+static int snd_ac97_put_spsa(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	// int invert = (kcontrol->private_value >> 24) & 0xff;
+	unsigned short value, old, new;
+	int change;
+
+	value = (ucontrol->value.integer.value[0] & mask);
+
+	mutex_lock(&ac97->reg_mutex);
+	mask <<= shift;
+	value <<= shift;
+	old = snd_ac97_read_cache(ac97, reg);
+	new = (old & ~mask) | value;
+	change = old != new;
+
+	if (change) {
+		unsigned short extst = snd_ac97_read_cache(ac97, AC97_EXTENDED_STATUS);
+		snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0); /* turn off */
+		change = snd_ac97_update_bits_nolock(ac97, reg, mask, value);
+		if (extst & AC97_EA_SPDIF)
+			snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF); /* turn on again */
+	}
+	mutex_unlock(&ac97->reg_mutex);
+	return change;
+}
+
+static const struct snd_kcontrol_new snd_ac97_controls_spdif[5] = {
+	{
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
+		.info = snd_ac97_spdif_mask_info,
+		.get = snd_ac97_spdif_cmask_get,
+	},
+	{
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK),
+		.info = snd_ac97_spdif_mask_info,
+		.get = snd_ac97_spdif_pmask_get,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+		.info = snd_ac97_spdif_mask_info,
+		.get = snd_ac97_spdif_default_get,
+		.put = snd_ac97_spdif_default_put,
+	},
+
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH),AC97_EXTENDED_STATUS, 2, 1, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "AC97-SPSA",
+		.info = snd_ac97_info_volsw,
+		.get = snd_ac97_get_volsw,
+		.put = snd_ac97_put_spsa,
+		.private_value = AC97_SINGLE_VALUE(AC97_EXTENDED_STATUS, 4, 3, 0)
+	},
+};
+
+#define AD18XX_PCM_BITS(xname, codec, lshift, rshift, mask) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ac97_ad18xx_pcm_info_bits, \
+  .get = snd_ac97_ad18xx_pcm_get_bits, .put = snd_ac97_ad18xx_pcm_put_bits, \
+  .private_value = (codec) | ((lshift) << 8) | ((rshift) << 12) | ((mask) << 16) }
+
+static int snd_ac97_ad18xx_pcm_info_bits(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int mask = (kcontrol->private_value >> 16) & 0x0f;
+	int lshift = (kcontrol->private_value >> 8) & 0x0f;
+	int rshift = (kcontrol->private_value >> 12) & 0x0f;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	if (lshift != rshift && (ac97->flags & AC97_STEREO_MUTES))
+		uinfo->count = 2;
+	else
+		uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_ac97_ad18xx_pcm_get_bits(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int codec = kcontrol->private_value & 3;
+	int lshift = (kcontrol->private_value >> 8) & 0x0f;
+	int rshift = (kcontrol->private_value >> 12) & 0x0f;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	
+	ucontrol->value.integer.value[0] = mask - ((ac97->spec.ad18xx.pcmreg[codec] >> lshift) & mask);
+	if (lshift != rshift && (ac97->flags & AC97_STEREO_MUTES))
+		ucontrol->value.integer.value[1] = mask - ((ac97->spec.ad18xx.pcmreg[codec] >> rshift) & mask);
+	return 0;
+}
+
+static int snd_ac97_ad18xx_pcm_put_bits(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int codec = kcontrol->private_value & 3;
+	int lshift = (kcontrol->private_value >> 8) & 0x0f;
+	int rshift = (kcontrol->private_value >> 12) & 0x0f;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	unsigned short val, valmask;
+	
+	val = (mask - (ucontrol->value.integer.value[0] & mask)) << lshift;
+	valmask = mask << lshift;
+	if (lshift != rshift && (ac97->flags & AC97_STEREO_MUTES)) {
+		val |= (mask - (ucontrol->value.integer.value[1] & mask)) << rshift;
+		valmask |= mask << rshift;
+	}
+	return snd_ac97_ad18xx_update_pcm_bits(ac97, codec, valmask, val);
+}
+
+#define AD18XX_PCM_VOLUME(xname, codec) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ac97_ad18xx_pcm_info_volume, \
+  .get = snd_ac97_ad18xx_pcm_get_volume, .put = snd_ac97_ad18xx_pcm_put_volume, \
+  .private_value = codec }
+
+static int snd_ac97_ad18xx_pcm_info_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 31;
+	return 0;
+}
+
+static int snd_ac97_ad18xx_pcm_get_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int codec = kcontrol->private_value & 3;
+	
+	mutex_lock(&ac97->page_mutex);
+	ucontrol->value.integer.value[0] = 31 - ((ac97->spec.ad18xx.pcmreg[codec] >> 0) & 31);
+	ucontrol->value.integer.value[1] = 31 - ((ac97->spec.ad18xx.pcmreg[codec] >> 8) & 31);
+	mutex_unlock(&ac97->page_mutex);
+	return 0;
+}
+
+static int snd_ac97_ad18xx_pcm_put_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int codec = kcontrol->private_value & 3;
+	unsigned short val1, val2;
+	
+	val1 = 31 - (ucontrol->value.integer.value[0] & 31);
+	val2 = 31 - (ucontrol->value.integer.value[1] & 31);
+	return snd_ac97_ad18xx_update_pcm_bits(ac97, codec, 0x1f1f, (val1 << 8) | val2);
+}
+
+static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_pcm[2] = {
+AD18XX_PCM_BITS("PCM Playback Switch", 0, 15, 7, 1),
+AD18XX_PCM_VOLUME("PCM Playback Volume", 0)
+};
+
+static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_surround[2] = {
+AD18XX_PCM_BITS("Surround Playback Switch", 1, 15, 7, 1),
+AD18XX_PCM_VOLUME("Surround Playback Volume", 1)
+};
+
+static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_center[2] = {
+AD18XX_PCM_BITS("Center Playback Switch", 2, 15, 15, 1),
+AD18XX_PCM_BITS("Center Playback Volume", 2, 8, 8, 31)
+};
+
+static const struct snd_kcontrol_new snd_ac97_controls_ad18xx_lfe[2] = {
+AD18XX_PCM_BITS("LFE Playback Switch", 2, 7, 7, 1),
+AD18XX_PCM_BITS("LFE Playback Volume", 2, 0, 0, 31)
+};
+
+/*
+ *
+ */
+
+static void snd_ac97_powerdown(struct snd_ac97 *ac97);
+
+static int snd_ac97_bus_free(struct snd_ac97_bus *bus)
+{
+	if (bus) {
+		snd_ac97_bus_proc_done(bus);
+		kfree(bus->pcms);
+		if (bus->private_free)
+			bus->private_free(bus);
+		kfree(bus);
+	}
+	return 0;
+}
+
+static int snd_ac97_bus_dev_free(struct snd_device *device)
+{
+	struct snd_ac97_bus *bus = device->device_data;
+	return snd_ac97_bus_free(bus);
+}
+
+static int snd_ac97_free(struct snd_ac97 *ac97)
+{
+	if (ac97) {
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+		cancel_delayed_work(&ac97->power_work);
+		flush_scheduled_work();
+#endif
+		snd_ac97_proc_done(ac97);
+		if (ac97->bus)
+			ac97->bus->codec[ac97->num] = NULL;
+		if (ac97->private_free)
+			ac97->private_free(ac97);
+		kfree(ac97);
+	}
+	return 0;
+}
+
+static int snd_ac97_dev_free(struct snd_device *device)
+{
+	struct snd_ac97 *ac97 = device->device_data;
+	snd_ac97_powerdown(ac97); /* for avoiding click noises during shut down */
+	return snd_ac97_free(ac97);
+}
+
+static int snd_ac97_try_volume_mix(struct snd_ac97 * ac97, int reg)
+{
+	unsigned short val, mask = 0x8000;
+
+	if (! snd_ac97_valid_reg(ac97, reg))
+		return 0;
+
+	switch (reg) {
+	case AC97_MASTER_TONE:
+		return ac97->caps & 0x04 ? 1 : 0;
+	case AC97_HEADPHONE:
+		return ac97->caps & 0x10 ? 1 : 0;
+	case AC97_REC_GAIN_MIC:
+		return ac97->caps & 0x01 ? 1 : 0;
+	case AC97_3D_CONTROL:
+		if (ac97->caps & 0x7c00) {
+			val = snd_ac97_read(ac97, reg);
+			/* if nonzero - fixed and we can't set it */
+			return val == 0;
+		}
+		return 0;
+	case AC97_CENTER_LFE_MASTER:	/* center */
+		if ((ac97->ext_id & AC97_EI_CDAC) == 0)
+			return 0;
+		break;
+	case AC97_CENTER_LFE_MASTER+1:	/* lfe */
+		if ((ac97->ext_id & AC97_EI_LDAC) == 0)
+			return 0;
+		reg = AC97_CENTER_LFE_MASTER;
+		mask = 0x0080;
+		break;
+	case AC97_SURROUND_MASTER:
+		if ((ac97->ext_id & AC97_EI_SDAC) == 0)
+			return 0;
+		break;
+	}
+
+	val = snd_ac97_read(ac97, reg);
+	if (!(val & mask)) {
+		/* nothing seems to be here - mute flag is not set */
+		/* try another test */
+		snd_ac97_write_cache(ac97, reg, val | mask);
+		val = snd_ac97_read(ac97, reg);
+		val = snd_ac97_read(ac97, reg);
+		if (!(val & mask))
+			return 0;	/* nothing here */
+	}
+	return 1;		/* success, useable */
+}
+
+static void check_volume_resolution(struct snd_ac97 *ac97, int reg, unsigned char *lo_max, unsigned char *hi_max)
+{
+	unsigned short cbit[3] = { 0x20, 0x10, 0x01 };
+	unsigned char max[3] = { 63, 31, 15 };
+	int i;
+
+	/* first look up the static resolution table */
+	if (ac97->res_table) {
+		const struct snd_ac97_res_table *tbl;
+		for (tbl = ac97->res_table; tbl->reg; tbl++) {
+			if (tbl->reg == reg) {
+				*lo_max = tbl->bits & 0xff;
+				*hi_max = (tbl->bits >> 8) & 0xff;
+				return;
+			}
+		}
+	}
+
+	*lo_max = *hi_max = 0;
+	for (i = 0 ; i < ARRAY_SIZE(cbit); i++) {
+		unsigned short val;
+		snd_ac97_write(ac97, reg, 0x8080 | cbit[i] | (cbit[i] << 8));
+		/* Do the read twice due to buffers on some ac97 codecs.
+		 * e.g. The STAC9704 returns exactly what you wrote to the register
+		 * if you read it immediately. This causes the detect routine to fail.
+		 */
+		val = snd_ac97_read(ac97, reg);
+		val = snd_ac97_read(ac97, reg);
+		if (! *lo_max && (val & 0x7f) == cbit[i])
+			*lo_max = max[i];
+		if (! *hi_max && ((val >> 8) & 0x7f) == cbit[i])
+			*hi_max = max[i];
+		if (*lo_max && *hi_max)
+			break;
+	}
+}
+
+static int snd_ac97_try_bit(struct snd_ac97 * ac97, int reg, int bit)
+{
+	unsigned short mask, val, orig, res;
+
+	mask = 1 << bit;
+	orig = snd_ac97_read(ac97, reg);
+	val = orig ^ mask;
+	snd_ac97_write(ac97, reg, val);
+	res = snd_ac97_read(ac97, reg);
+	snd_ac97_write_cache(ac97, reg, orig);
+	return res == val;
+}
+
+/* check the volume resolution of center/lfe */
+static void snd_ac97_change_volume_params2(struct snd_ac97 * ac97, int reg, int shift, unsigned char *max)
+{
+	unsigned short val, val1;
+
+	*max = 63;
+	val = 0x8080 | (0x20 << shift);
+	snd_ac97_write(ac97, reg, val);
+	val1 = snd_ac97_read(ac97, reg);
+	if (val != val1) {
+		*max = 31;
+	}
+	/* reset volume to zero */
+	snd_ac97_write_cache(ac97, reg, 0x8080);
+}
+
+static inline int printable(unsigned int x)
+{
+	x &= 0xff;
+	if (x < ' ' || x >= 0x71) {
+		if (x <= 0x89)
+			return x - 0x71 + 'A';
+		return '?';
+	}
+	return x;
+}
+
+static struct snd_kcontrol *snd_ac97_cnew(const struct snd_kcontrol_new *_template,
+					  struct snd_ac97 * ac97)
+{
+	struct snd_kcontrol_new template;
+	memcpy(&template, _template, sizeof(template));
+	template.index = ac97->num;
+	return snd_ctl_new1(&template, ac97);
+}
+
+/*
+ * create mute switch(es) for normal stereo controls
+ */
+static int snd_ac97_cmute_new_stereo(struct snd_card *card, char *name, int reg,
+				     int check_stereo, int check_amix,
+				     struct snd_ac97 *ac97)
+{
+	struct snd_kcontrol *kctl;
+	int err;
+	unsigned short val, val1, mute_mask;
+
+	if (! snd_ac97_valid_reg(ac97, reg))
+		return 0;
+
+	mute_mask = 0x8000;
+	val = snd_ac97_read(ac97, reg);
+	if (check_stereo || (ac97->flags & AC97_STEREO_MUTES)) {
+		/* check whether both mute bits work */
+		val1 = val | 0x8080;
+		snd_ac97_write(ac97, reg, val1);
+		if (val1 == snd_ac97_read(ac97, reg))
+			mute_mask = 0x8080;
+	}
+	if (mute_mask == 0x8080) {
+		struct snd_kcontrol_new tmp = AC97_DOUBLE(name, reg, 15, 7, 1, 1);
+		if (check_amix)
+			tmp.private_value |= (1 << 30);
+		tmp.index = ac97->num;
+		kctl = snd_ctl_new1(&tmp, ac97);
+	} else {
+		struct snd_kcontrol_new tmp = AC97_SINGLE(name, reg, 15, 1, 1);
+		if (check_amix)
+			tmp.private_value |= (1 << 30);
+		tmp.index = ac97->num;
+		kctl = snd_ctl_new1(&tmp, ac97);
+	}
+	err = snd_ctl_add(card, kctl);
+	if (err < 0)
+		return err;
+	/* mute as default */
+	snd_ac97_write_cache(ac97, reg, val | mute_mask);
+	return 0;
+}
+
+/*
+ * set dB information
+ */
+static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
+
+static const unsigned int *find_db_scale(unsigned int maxval)
+{
+	switch (maxval) {
+	case 0x0f: return db_scale_4bit;
+	case 0x1f: return db_scale_5bit;
+	case 0x3f: return db_scale_6bit;
+	}
+	return NULL;
+}
+
+static void set_tlv_db_scale(struct snd_kcontrol *kctl, const unsigned int *tlv)
+{
+	kctl->tlv.p = tlv;
+	if (tlv)
+		kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+}
+
+/*
+ * create a volume for normal stereo/mono controls
+ */
+static int snd_ac97_cvol_new(struct snd_card *card, char *name, int reg, unsigned int lo_max,
+			     unsigned int hi_max, struct snd_ac97 *ac97)
+{
+	int err;
+	struct snd_kcontrol *kctl;
+
+	if (! snd_ac97_valid_reg(ac97, reg))
+		return 0;
+	if (hi_max) {
+		/* invert */
+		struct snd_kcontrol_new tmp = AC97_DOUBLE(name, reg, 8, 0, lo_max, 1);
+		tmp.index = ac97->num;
+		kctl = snd_ctl_new1(&tmp, ac97);
+	} else {
+		/* invert */
+		struct snd_kcontrol_new tmp = AC97_SINGLE(name, reg, 0, lo_max, 1);
+		tmp.index = ac97->num;
+		kctl = snd_ctl_new1(&tmp, ac97);
+	}
+	if (reg >= AC97_PHONE && reg <= AC97_PCM)
+		set_tlv_db_scale(kctl, db_scale_5bit_12db_max);
+	else
+		set_tlv_db_scale(kctl, find_db_scale(lo_max));
+	err = snd_ctl_add(card, kctl);
+	if (err < 0)
+		return err;
+	snd_ac97_write_cache(ac97, reg,
+			     (snd_ac97_read(ac97, reg) & 0x8080) |
+			     lo_max | (hi_max << 8));
+	return 0;
+}
+
+/*
+ * create a mute-switch and a volume for normal stereo/mono controls
+ */
+static int snd_ac97_cmix_new_stereo(struct snd_card *card, const char *pfx,
+				    int reg, int check_stereo, int check_amix,
+				    struct snd_ac97 *ac97)
+{
+	int err;
+	char name[44];
+	unsigned char lo_max, hi_max;
+
+	if (! snd_ac97_valid_reg(ac97, reg))
+		return 0;
+
+	if (snd_ac97_try_bit(ac97, reg, 15)) {
+		sprintf(name, "%s Switch", pfx);
+		if ((err = snd_ac97_cmute_new_stereo(card, name, reg,
+						     check_stereo, check_amix,
+						     ac97)) < 0)
+			return err;
+	}
+	check_volume_resolution(ac97, reg, &lo_max, &hi_max);
+	if (lo_max) {
+		sprintf(name, "%s Volume", pfx);
+		if ((err = snd_ac97_cvol_new(card, name, reg, lo_max, hi_max, ac97)) < 0)
+			return err;
+	}
+	return 0;
+}
+
+#define snd_ac97_cmix_new(card, pfx, reg, acheck, ac97) \
+	snd_ac97_cmix_new_stereo(card, pfx, reg, 0, acheck, ac97)
+#define snd_ac97_cmute_new(card, name, reg, acheck, ac97) \
+	snd_ac97_cmute_new_stereo(card, name, reg, 0, acheck, ac97)
+
+static unsigned int snd_ac97_determine_spdif_rates(struct snd_ac97 *ac97);
+
+static int snd_ac97_mixer_build(struct snd_ac97 * ac97)
+{
+	struct snd_card *card = ac97->bus->card;
+	struct snd_kcontrol *kctl;
+	int err;
+	unsigned int idx;
+	unsigned char max;
+
+	/* build master controls */
+	/* AD claims to remove this control from AD1887, although spec v2.2 does not allow this */
+	if (snd_ac97_try_volume_mix(ac97, AC97_MASTER)) {
+		if (ac97->flags & AC97_HAS_NO_MASTER_VOL)
+			err = snd_ac97_cmute_new(card, "Master Playback Switch",
+						 AC97_MASTER, 0, ac97);
+		else
+			err = snd_ac97_cmix_new(card, "Master Playback",
+						AC97_MASTER, 0, ac97);
+		if (err < 0)
+			return err;
+	}
+
+	ac97->regs[AC97_CENTER_LFE_MASTER] = 0x8080;
+
+	/* build center controls */
+	if ((snd_ac97_try_volume_mix(ac97, AC97_CENTER_LFE_MASTER)) 
+		&& !(ac97->flags & AC97_AD_MULTI)) {
+		if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_center[0], ac97))) < 0)
+			return err;
+		if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_center[1], ac97))) < 0)
+			return err;
+		snd_ac97_change_volume_params2(ac97, AC97_CENTER_LFE_MASTER, 0, &max);
+		kctl->private_value &= ~(0xff << 16);
+		kctl->private_value |= (int)max << 16;
+		set_tlv_db_scale(kctl, find_db_scale(max));
+		snd_ac97_write_cache(ac97, AC97_CENTER_LFE_MASTER, ac97->regs[AC97_CENTER_LFE_MASTER] | max);
+	}
+
+	/* build LFE controls */
+	if ((snd_ac97_try_volume_mix(ac97, AC97_CENTER_LFE_MASTER+1))
+		&& !(ac97->flags & AC97_AD_MULTI)) {
+		if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_lfe[0], ac97))) < 0)
+			return err;
+		if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_lfe[1], ac97))) < 0)
+			return err;
+		snd_ac97_change_volume_params2(ac97, AC97_CENTER_LFE_MASTER, 8, &max);
+		kctl->private_value &= ~(0xff << 16);
+		kctl->private_value |= (int)max << 16;
+		set_tlv_db_scale(kctl, find_db_scale(max));
+		snd_ac97_write_cache(ac97, AC97_CENTER_LFE_MASTER, ac97->regs[AC97_CENTER_LFE_MASTER] | max << 8);
+	}
+
+	/* build surround controls */
+	if ((snd_ac97_try_volume_mix(ac97, AC97_SURROUND_MASTER)) 
+		&& !(ac97->flags & AC97_AD_MULTI)) {
+		/* Surround Master (0x38) is with stereo mutes */
+		if ((err = snd_ac97_cmix_new_stereo(card, "Surround Playback",
+						    AC97_SURROUND_MASTER, 1, 0,
+						    ac97)) < 0)
+			return err;
+	}
+
+	/* build headphone controls */
+	if (snd_ac97_try_volume_mix(ac97, AC97_HEADPHONE)) {
+		if ((err = snd_ac97_cmix_new(card, "Headphone Playback",
+					     AC97_HEADPHONE, 0, ac97)) < 0)
+			return err;
+	}
+	
+	/* build master mono controls */
+	if (snd_ac97_try_volume_mix(ac97, AC97_MASTER_MONO)) {
+		if ((err = snd_ac97_cmix_new(card, "Master Mono Playback",
+					     AC97_MASTER_MONO, 0, ac97)) < 0)
+			return err;
+	}
+	
+	/* build master tone controls */
+	if (!(ac97->flags & AC97_HAS_NO_TONE)) {
+		if (snd_ac97_try_volume_mix(ac97, AC97_MASTER_TONE)) {
+			for (idx = 0; idx < 2; idx++) {
+				if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_tone[idx], ac97))) < 0)
+					return err;
+				if (ac97->id == AC97_ID_YMF743 ||
+				    ac97->id == AC97_ID_YMF753) {
+					kctl->private_value &= ~(0xff << 16);
+					kctl->private_value |= 7 << 16;
+				}
+			}
+			snd_ac97_write_cache(ac97, AC97_MASTER_TONE, 0x0f0f);
+		}
+	}
+	
+	/* build Beep controls */
+	if (!(ac97->flags & AC97_HAS_NO_PC_BEEP) && 
+		((ac97->flags & AC97_HAS_PC_BEEP) ||
+	    snd_ac97_try_volume_mix(ac97, AC97_PC_BEEP))) {
+		for (idx = 0; idx < 2; idx++)
+			if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_pc_beep[idx], ac97))) < 0)
+				return err;
+		set_tlv_db_scale(kctl, db_scale_4bit);
+		snd_ac97_write_cache(ac97, AC97_PC_BEEP,
+				     snd_ac97_read(ac97, AC97_PC_BEEP) | 0x801e);
+	}
+	
+	/* build Phone controls */
+	if (!(ac97->flags & AC97_HAS_NO_PHONE)) {
+		if (snd_ac97_try_volume_mix(ac97, AC97_PHONE)) {
+			if ((err = snd_ac97_cmix_new(card, "Phone Playback",
+						     AC97_PHONE, 1, ac97)) < 0)
+				return err;
+		}
+	}
+	
+	/* build MIC controls */
+	if (!(ac97->flags & AC97_HAS_NO_MIC)) {
+		if (snd_ac97_try_volume_mix(ac97, AC97_MIC)) {
+			if ((err = snd_ac97_cmix_new(card, "Mic Playback",
+						     AC97_MIC, 1, ac97)) < 0)
+				return err;
+			if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_mic_boost, ac97))) < 0)
+				return err;
+		}
+	}
+
+	/* build Line controls */
+	if (snd_ac97_try_volume_mix(ac97, AC97_LINE)) {
+		if ((err = snd_ac97_cmix_new(card, "Line Playback",
+					     AC97_LINE, 1, ac97)) < 0)
+			return err;
+	}
+	
+	/* build CD controls */
+	if (!(ac97->flags & AC97_HAS_NO_CD)) {
+		if (snd_ac97_try_volume_mix(ac97, AC97_CD)) {
+			if ((err = snd_ac97_cmix_new(card, "CD Playback",
+						     AC97_CD, 1, ac97)) < 0)
+				return err;
+		}
+	}
+	
+	/* build Video controls */
+	if (!(ac97->flags & AC97_HAS_NO_VIDEO)) {
+		if (snd_ac97_try_volume_mix(ac97, AC97_VIDEO)) {
+			if ((err = snd_ac97_cmix_new(card, "Video Playback",
+						     AC97_VIDEO, 1, ac97)) < 0)
+				return err;
+		}
+	}
+
+	/* build Aux controls */
+	if (!(ac97->flags & AC97_HAS_NO_AUX)) {
+		if (snd_ac97_try_volume_mix(ac97, AC97_AUX)) {
+			if ((err = snd_ac97_cmix_new(card, "Aux Playback",
+						     AC97_AUX, 1, ac97)) < 0)
+				return err;
+		}
+	}
+
+	/* build PCM controls */
+	if (ac97->flags & AC97_AD_MULTI) {
+		unsigned short init_val;
+		if (ac97->flags & AC97_STEREO_MUTES)
+			init_val = 0x9f9f;
+		else
+			init_val = 0x9f1f;
+		for (idx = 0; idx < 2; idx++)
+			if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_pcm[idx], ac97))) < 0)
+				return err;
+		set_tlv_db_scale(kctl, db_scale_5bit);
+		ac97->spec.ad18xx.pcmreg[0] = init_val;
+		if (ac97->scaps & AC97_SCAP_SURROUND_DAC) {
+			for (idx = 0; idx < 2; idx++)
+				if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_surround[idx], ac97))) < 0)
+					return err;
+			set_tlv_db_scale(kctl, db_scale_5bit);
+			ac97->spec.ad18xx.pcmreg[1] = init_val;
+		}
+		if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC) {
+			for (idx = 0; idx < 2; idx++)
+				if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_center[idx], ac97))) < 0)
+					return err;
+			set_tlv_db_scale(kctl, db_scale_5bit);
+			for (idx = 0; idx < 2; idx++)
+				if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_ad18xx_lfe[idx], ac97))) < 0)
+					return err;
+			set_tlv_db_scale(kctl, db_scale_5bit);
+			ac97->spec.ad18xx.pcmreg[2] = init_val;
+		}
+		snd_ac97_write_cache(ac97, AC97_PCM, init_val);
+	} else {
+		if (!(ac97->flags & AC97_HAS_NO_STD_PCM)) {
+			if (ac97->flags & AC97_HAS_NO_PCM_VOL)
+				err = snd_ac97_cmute_new(card,
+							 "PCM Playback Switch",
+							 AC97_PCM, 0, ac97);
+			else
+				err = snd_ac97_cmix_new(card, "PCM Playback",
+							AC97_PCM, 0, ac97);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	/* build Capture controls */
+	if (!(ac97->flags & AC97_HAS_NO_REC_GAIN)) {
+		if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_control_capture_src, ac97))) < 0)
+			return err;
+		if (snd_ac97_try_bit(ac97, AC97_REC_GAIN, 15)) {
+			err = snd_ac97_cmute_new(card, "Capture Switch",
+						 AC97_REC_GAIN, 0, ac97);
+			if (err < 0)
+				return err;
+		}
+		if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_control_capture_vol, ac97))) < 0)
+			return err;
+		set_tlv_db_scale(kctl, db_scale_rec_gain);
+		snd_ac97_write_cache(ac97, AC97_REC_SEL, 0x0000);
+		snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x0000);
+	}
+	/* build MIC Capture controls */
+	if (snd_ac97_try_volume_mix(ac97, AC97_REC_GAIN_MIC)) {
+		for (idx = 0; idx < 2; idx++)
+			if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_mic_capture[idx], ac97))) < 0)
+				return err;
+		set_tlv_db_scale(kctl, db_scale_rec_gain);
+		snd_ac97_write_cache(ac97, AC97_REC_GAIN_MIC, 0x0000);
+	}
+
+	/* build PCM out path & mute control */
+	if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 15)) {
+		if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_PCM_OUT], ac97))) < 0)
+			return err;
+	}
+
+	/* build Simulated Stereo Enhancement control */
+	if (ac97->caps & 0x0008) {
+		if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_STEREO_ENHANCEMENT], ac97))) < 0)
+			return err;
+	}
+
+	/* build 3D Stereo Enhancement control */
+	if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 13)) {
+		if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_3D], ac97))) < 0)
+			return err;
+	}
+
+	/* build Loudness control */
+	if (ac97->caps & 0x0020) {
+		if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_LOUDNESS], ac97))) < 0)
+			return err;
+	}
+
+	/* build Mono output select control */
+	if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 9)) {
+		if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_MONO], ac97))) < 0)
+			return err;
+	}
+
+	/* build Mic select control */
+	if (snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 8)) {
+		if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_MIC], ac97))) < 0)
+			return err;
+	}
+
+	/* build ADC/DAC loopback control */
+	if (enable_loopback && snd_ac97_try_bit(ac97, AC97_GENERAL_PURPOSE, 7)) {
+		if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_general[AC97_GENERAL_LOOPBACK], ac97))) < 0)
+			return err;
+	}
+
+	snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, ~AC97_GP_DRSS_MASK, 0x0000);
+
+	/* build 3D controls */
+	if (ac97->build_ops->build_3d) {
+		ac97->build_ops->build_3d(ac97);
+	} else {
+		if (snd_ac97_try_volume_mix(ac97, AC97_3D_CONTROL)) {
+			unsigned short val;
+			val = 0x0707;
+			snd_ac97_write(ac97, AC97_3D_CONTROL, val);
+			val = snd_ac97_read(ac97, AC97_3D_CONTROL);
+			val = val == 0x0606;
+			if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+				return err;
+			if (val)
+				kctl->private_value = AC97_3D_CONTROL | (9 << 8) | (7 << 16);
+			if ((err = snd_ctl_add(card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[1], ac97))) < 0)
+				return err;
+			if (val)
+				kctl->private_value = AC97_3D_CONTROL | (1 << 8) | (7 << 16);
+			snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000);
+		}
+	}
+
+	/* build S/PDIF controls */
+
+	/* Hack for ASUS P5P800-VM, which does not indicate S/PDIF capability */
+	if (ac97->subsystem_vendor == 0x1043 &&
+	    ac97->subsystem_device == 0x810f)
+		ac97->ext_id |= AC97_EI_SPDIF;
+
+	if ((ac97->ext_id & AC97_EI_SPDIF) && !(ac97->scaps & AC97_SCAP_NO_SPDIF)) {
+		if (ac97->build_ops->build_spdif) {
+			if ((err = ac97->build_ops->build_spdif(ac97)) < 0)
+				return err;
+		} else {
+			for (idx = 0; idx < 5; idx++)
+				if ((err = snd_ctl_add(card, snd_ac97_cnew(&snd_ac97_controls_spdif[idx], ac97))) < 0)
+					return err;
+			if (ac97->build_ops->build_post_spdif) {
+				if ((err = ac97->build_ops->build_post_spdif(ac97)) < 0)
+					return err;
+			}
+			/* set default PCM S/PDIF params */
+			/* consumer,PCM audio,no copyright,no preemphasis,PCM coder,original,48000Hz */
+			snd_ac97_write_cache(ac97, AC97_SPDIF, 0x2a20);
+			ac97->rates[AC97_RATES_SPDIF] = snd_ac97_determine_spdif_rates(ac97);
+		}
+		ac97->spdif_status = SNDRV_PCM_DEFAULT_CON_SPDIF;
+	}
+	
+	/* build chip specific controls */
+	if (ac97->build_ops->build_specific)
+		if ((err = ac97->build_ops->build_specific(ac97)) < 0)
+			return err;
+
+	if (snd_ac97_try_bit(ac97, AC97_POWERDOWN, 15)) {
+		kctl = snd_ac97_cnew(&snd_ac97_control_eapd, ac97);
+		if (! kctl)
+			return -ENOMEM;
+		if (ac97->scaps & AC97_SCAP_INV_EAPD)
+			set_inv_eapd(ac97, kctl);
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int snd_ac97_modem_build(struct snd_card *card, struct snd_ac97 * ac97)
+{
+	int err, idx;
+
+	/*
+	printk(KERN_DEBUG "AC97_GPIO_CFG = %x\n",
+	       snd_ac97_read(ac97,AC97_GPIO_CFG));
+	*/
+	snd_ac97_write(ac97, AC97_GPIO_CFG, 0xffff & ~(AC97_GPIO_LINE1_OH));
+	snd_ac97_write(ac97, AC97_GPIO_POLARITY, 0xffff & ~(AC97_GPIO_LINE1_OH));
+	snd_ac97_write(ac97, AC97_GPIO_STICKY, 0xffff);
+	snd_ac97_write(ac97, AC97_GPIO_WAKEUP, 0x0);
+	snd_ac97_write(ac97, AC97_MISC_AFE, 0x0);
+
+	/* build modem switches */
+	for (idx = 0; idx < ARRAY_SIZE(snd_ac97_controls_modem_switches); idx++)
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ac97_controls_modem_switches[idx], ac97))) < 0)
+			return err;
+
+	/* build chip specific controls */
+	if (ac97->build_ops->build_specific)
+		if ((err = ac97->build_ops->build_specific(ac97)) < 0)
+			return err;
+
+	return 0;
+}
+
+static int snd_ac97_test_rate(struct snd_ac97 *ac97, int reg, int shadow_reg, int rate)
+{
+	unsigned short val;
+	unsigned int tmp;
+
+	tmp = ((unsigned int)rate * ac97->bus->clock) / 48000;
+	snd_ac97_write_cache(ac97, reg, tmp & 0xffff);
+	if (shadow_reg)
+		snd_ac97_write_cache(ac97, shadow_reg, tmp & 0xffff);
+	val = snd_ac97_read(ac97, reg);
+	return val == (tmp & 0xffff);
+}
+
+static void snd_ac97_determine_rates(struct snd_ac97 *ac97, int reg, int shadow_reg, unsigned int *r_result)
+{
+	unsigned int result = 0;
+	unsigned short saved;
+
+	if (ac97->bus->no_vra) {
+		*r_result = SNDRV_PCM_RATE_48000;
+		if ((ac97->flags & AC97_DOUBLE_RATE) &&
+		    reg == AC97_PCM_FRONT_DAC_RATE)
+			*r_result |= SNDRV_PCM_RATE_96000;
+		return;
+	}
+
+	saved = snd_ac97_read(ac97, reg);
+	if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE)
+		snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
+				     AC97_EA_DRA, 0);
+	/* test a non-standard rate */
+	if (snd_ac97_test_rate(ac97, reg, shadow_reg, 11000))
+		result |= SNDRV_PCM_RATE_CONTINUOUS;
+	/* let's try to obtain standard rates */
+	if (snd_ac97_test_rate(ac97, reg, shadow_reg, 8000))
+		result |= SNDRV_PCM_RATE_8000;
+	if (snd_ac97_test_rate(ac97, reg, shadow_reg, 11025))
+		result |= SNDRV_PCM_RATE_11025;
+	if (snd_ac97_test_rate(ac97, reg, shadow_reg, 16000))
+		result |= SNDRV_PCM_RATE_16000;
+	if (snd_ac97_test_rate(ac97, reg, shadow_reg, 22050))
+		result |= SNDRV_PCM_RATE_22050;
+	if (snd_ac97_test_rate(ac97, reg, shadow_reg, 32000))
+		result |= SNDRV_PCM_RATE_32000;
+	if (snd_ac97_test_rate(ac97, reg, shadow_reg, 44100))
+		result |= SNDRV_PCM_RATE_44100;
+	if (snd_ac97_test_rate(ac97, reg, shadow_reg, 48000))
+		result |= SNDRV_PCM_RATE_48000;
+	if ((ac97->flags & AC97_DOUBLE_RATE) &&
+	    reg == AC97_PCM_FRONT_DAC_RATE) {
+		/* test standard double rates */
+		snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
+				     AC97_EA_DRA, AC97_EA_DRA);
+		if (snd_ac97_test_rate(ac97, reg, shadow_reg, 64000 / 2))
+			result |= SNDRV_PCM_RATE_64000;
+		if (snd_ac97_test_rate(ac97, reg, shadow_reg, 88200 / 2))
+			result |= SNDRV_PCM_RATE_88200;
+		if (snd_ac97_test_rate(ac97, reg, shadow_reg, 96000 / 2))
+			result |= SNDRV_PCM_RATE_96000;
+		/* some codecs don't support variable double rates */
+		if (!snd_ac97_test_rate(ac97, reg, shadow_reg, 76100 / 2))
+			result &= ~SNDRV_PCM_RATE_CONTINUOUS;
+		snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
+				     AC97_EA_DRA, 0);
+	}
+	/* restore the default value */
+	snd_ac97_write_cache(ac97, reg, saved);
+	if (shadow_reg)
+		snd_ac97_write_cache(ac97, shadow_reg, saved);
+	*r_result = result;
+}
+
+/* check AC97_SPDIF register to accept which sample rates */
+static unsigned int snd_ac97_determine_spdif_rates(struct snd_ac97 *ac97)
+{
+	unsigned int result = 0;
+	int i;
+	static unsigned short ctl_bits[] = {
+		AC97_SC_SPSR_44K, AC97_SC_SPSR_32K, AC97_SC_SPSR_48K
+	};
+	static unsigned int rate_bits[] = {
+		SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_32000, SNDRV_PCM_RATE_48000
+	};
+
+	for (i = 0; i < (int)ARRAY_SIZE(ctl_bits); i++) {
+		snd_ac97_update_bits(ac97, AC97_SPDIF, AC97_SC_SPSR_MASK, ctl_bits[i]);
+		if ((snd_ac97_read(ac97, AC97_SPDIF) & AC97_SC_SPSR_MASK) == ctl_bits[i])
+			result |= rate_bits[i];
+	}
+	return result;
+}
+
+/* look for the codec id table matching with the given id */
+static const struct ac97_codec_id *look_for_codec_id(const struct ac97_codec_id *table,
+						     unsigned int id)
+{
+	const struct ac97_codec_id *pid;
+
+	for (pid = table; pid->id; pid++)
+		if (pid->id == (id & pid->mask))
+			return pid;
+	return NULL;
+}
+
+void snd_ac97_get_name(struct snd_ac97 *ac97, unsigned int id, char *name, int modem)
+{
+	const struct ac97_codec_id *pid;
+
+	sprintf(name, "0x%x %c%c%c", id,
+		printable(id >> 24),
+		printable(id >> 16),
+		printable(id >> 8));
+	pid = look_for_codec_id(snd_ac97_codec_id_vendors, id);
+	if (! pid)
+		return;
+
+	strcpy(name, pid->name);
+	if (ac97 && pid->patch) {
+		if ((modem && (pid->flags & AC97_MODEM_PATCH)) ||
+		    (! modem && ! (pid->flags & AC97_MODEM_PATCH)))
+			pid->patch(ac97);
+	} 
+
+	pid = look_for_codec_id(snd_ac97_codec_ids, id);
+	if (pid) {
+		strcat(name, " ");
+		strcat(name, pid->name);
+		if (pid->mask != 0xffffffff)
+			sprintf(name + strlen(name), " rev %d", id & ~pid->mask);
+		if (ac97 && pid->patch) {
+			if ((modem && (pid->flags & AC97_MODEM_PATCH)) ||
+			    (! modem && ! (pid->flags & AC97_MODEM_PATCH)))
+				pid->patch(ac97);
+		}
+	} else
+		sprintf(name + strlen(name), " id %x", id & 0xff);
+}
+
+/**
+ * snd_ac97_get_short_name - retrieve codec name
+ * @ac97: the codec instance
+ *
+ * Returns the short identifying name of the codec.
+ */
+const char *snd_ac97_get_short_name(struct snd_ac97 *ac97)
+{
+	const struct ac97_codec_id *pid;
+
+	for (pid = snd_ac97_codec_ids; pid->id; pid++)
+		if (pid->id == (ac97->id & pid->mask))
+			return pid->name;
+	return "unknown codec";
+}
+
+EXPORT_SYMBOL(snd_ac97_get_short_name);
+
+/* wait for a while until registers are accessible after RESET
+ * return 0 if ok, negative not ready
+ */
+static int ac97_reset_wait(struct snd_ac97 *ac97, int timeout, int with_modem)
+{
+	unsigned long end_time;
+	unsigned short val;
+
+	end_time = jiffies + timeout;
+	do {
+		
+		/* use preliminary reads to settle the communication */
+		snd_ac97_read(ac97, AC97_RESET);
+		snd_ac97_read(ac97, AC97_VENDOR_ID1);
+		snd_ac97_read(ac97, AC97_VENDOR_ID2);
+		/* modem? */
+		if (with_modem) {
+			val = snd_ac97_read(ac97, AC97_EXTENDED_MID);
+			if (val != 0xffff && (val & 1) != 0)
+				return 0;
+		}
+		if (ac97->scaps & AC97_SCAP_DETECT_BY_VENDOR) {
+			/* probably only Xbox issue - all registers are read as zero */
+			val = snd_ac97_read(ac97, AC97_VENDOR_ID1);
+			if (val != 0 && val != 0xffff)
+				return 0;
+		} else {
+			/* because the PCM or MASTER volume registers can be modified,
+			 * the REC_GAIN register is used for tests
+			 */
+			/* test if we can write to the record gain volume register */
+			snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x8a05);
+			if ((snd_ac97_read(ac97, AC97_REC_GAIN) & 0x7fff) == 0x0a05)
+				return 0;
+		}
+		schedule_timeout_uninterruptible(1);
+	} while (time_after_eq(end_time, jiffies));
+	return -ENODEV;
+}
+
+/**
+ * snd_ac97_bus - create an AC97 bus component
+ * @card: the card instance
+ * @num: the bus number
+ * @ops: the bus callbacks table
+ * @private_data: private data pointer for the new instance
+ * @rbus: the pointer to store the new AC97 bus instance.
+ *
+ * Creates an AC97 bus component.  An struct snd_ac97_bus instance is newly
+ * allocated and initialized.
+ *
+ * The ops table must include valid callbacks (at least read and
+ * write).  The other callbacks, wait and reset, are not mandatory.
+ * 
+ * The clock is set to 48000.  If another clock is needed, set
+ * (*rbus)->clock manually.
+ *
+ * The AC97 bus instance is registered as a low-level device, so you don't
+ * have to release it manually.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_ac97_bus(struct snd_card *card, int num, struct snd_ac97_bus_ops *ops,
+		 void *private_data, struct snd_ac97_bus **rbus)
+{
+	int err;
+	struct snd_ac97_bus *bus;
+	static struct snd_device_ops dev_ops = {
+		.dev_free =	snd_ac97_bus_dev_free,
+	};
+
+	if (snd_BUG_ON(!card))
+		return -EINVAL;
+	bus = kzalloc(sizeof(*bus), GFP_KERNEL);
+	if (bus == NULL)
+		return -ENOMEM;
+	bus->card = card;
+	bus->num = num;
+	bus->ops = ops;
+	bus->private_data = private_data;
+	bus->clock = 48000;
+	spin_lock_init(&bus->bus_lock);
+	snd_ac97_bus_proc_init(bus);
+	if ((err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops)) < 0) {
+		snd_ac97_bus_free(bus);
+		return err;
+	}
+	if (rbus)
+		*rbus = bus;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ac97_bus);
+
+/* stop no dev release warning */
+static void ac97_device_release(struct device * dev)
+{
+}
+
+/* register ac97 codec to bus */
+static int snd_ac97_dev_register(struct snd_device *device)
+{
+	struct snd_ac97 *ac97 = device->device_data;
+	int err;
+
+	ac97->dev.bus = &ac97_bus_type;
+	ac97->dev.parent = ac97->bus->card->dev;
+	ac97->dev.release = ac97_device_release;
+	dev_set_name(&ac97->dev, "%d-%d:%s",
+		     ac97->bus->card->number, ac97->num,
+		     snd_ac97_get_short_name(ac97));
+	if ((err = device_register(&ac97->dev)) < 0) {
+		snd_printk(KERN_ERR "Can't register ac97 bus\n");
+		ac97->dev.bus = NULL;
+		return err;
+	}
+	return 0;
+}
+
+/* disconnect ac97 codec */
+static int snd_ac97_dev_disconnect(struct snd_device *device)
+{
+	struct snd_ac97 *ac97 = device->device_data;
+	if (ac97->dev.bus)
+		device_unregister(&ac97->dev);
+	return 0;
+}
+
+/* build_ops to do nothing */
+static struct snd_ac97_build_ops null_build_ops;
+
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+static void do_update_power(struct work_struct *work)
+{
+	update_power_regs(
+		container_of(work, struct snd_ac97, power_work.work));
+}
+#endif
+
+/**
+ * snd_ac97_mixer - create an Codec97 component
+ * @bus: the AC97 bus which codec is attached to
+ * @template: the template of ac97, including index, callbacks and
+ *         the private data.
+ * @rac97: the pointer to store the new ac97 instance.
+ *
+ * Creates an Codec97 component.  An struct snd_ac97 instance is newly
+ * allocated and initialized from the template.  The codec
+ * is then initialized by the standard procedure.
+ *
+ * The template must include the codec number (num) and address (addr),
+ * and the private data (private_data).
+ * 
+ * The ac97 instance is registered as a low-level device, so you don't
+ * have to release it manually.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97)
+{
+	int err;
+	struct snd_ac97 *ac97;
+	struct snd_card *card;
+	char name[64];
+	unsigned long end_time;
+	unsigned int reg;
+	const struct ac97_codec_id *pid;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_ac97_dev_free,
+		.dev_register =	snd_ac97_dev_register,
+		.dev_disconnect =	snd_ac97_dev_disconnect,
+	};
+
+	if (rac97)
+		*rac97 = NULL;
+	if (snd_BUG_ON(!bus || !template))
+		return -EINVAL;
+	if (snd_BUG_ON(template->num >= 4))
+		return -EINVAL;
+	if (bus->codec[template->num])
+		return -EBUSY;
+
+	card = bus->card;
+	ac97 = kzalloc(sizeof(*ac97), GFP_KERNEL);
+	if (ac97 == NULL)
+		return -ENOMEM;
+	ac97->private_data = template->private_data;
+	ac97->private_free = template->private_free;
+	ac97->bus = bus;
+	ac97->pci = template->pci;
+	ac97->num = template->num;
+	ac97->addr = template->addr;
+	ac97->scaps = template->scaps;
+	ac97->res_table = template->res_table;
+	bus->codec[ac97->num] = ac97;
+	mutex_init(&ac97->reg_mutex);
+	mutex_init(&ac97->page_mutex);
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+	INIT_DELAYED_WORK(&ac97->power_work, do_update_power);
+#endif
+
+#ifdef CONFIG_PCI
+	if (ac97->pci) {
+		pci_read_config_word(ac97->pci, PCI_SUBSYSTEM_VENDOR_ID, &ac97->subsystem_vendor);
+		pci_read_config_word(ac97->pci, PCI_SUBSYSTEM_ID, &ac97->subsystem_device);
+	}
+#endif
+	if (bus->ops->reset) {
+		bus->ops->reset(ac97);
+		goto __access_ok;
+	}
+
+	ac97->id = snd_ac97_read(ac97, AC97_VENDOR_ID1) << 16;
+	ac97->id |= snd_ac97_read(ac97, AC97_VENDOR_ID2);
+	if (ac97->id && ac97->id != (unsigned int)-1) {
+		pid = look_for_codec_id(snd_ac97_codec_ids, ac97->id);
+		if (pid && (pid->flags & AC97_DEFAULT_POWER_OFF))
+			goto __access_ok;
+	}
+
+	/* reset to defaults */
+	if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO))
+		snd_ac97_write(ac97, AC97_RESET, 0);
+	if (!(ac97->scaps & AC97_SCAP_SKIP_MODEM))
+		snd_ac97_write(ac97, AC97_EXTENDED_MID, 0);
+	if (bus->ops->wait)
+		bus->ops->wait(ac97);
+	else {
+		udelay(50);
+		if (ac97->scaps & AC97_SCAP_SKIP_AUDIO)
+			err = ac97_reset_wait(ac97, msecs_to_jiffies(500), 1);
+		else {
+			err = ac97_reset_wait(ac97, msecs_to_jiffies(500), 0);
+			if (err < 0)
+				err = ac97_reset_wait(ac97,
+						      msecs_to_jiffies(500), 1);
+		}
+		if (err < 0) {
+			snd_printk(KERN_WARNING "AC'97 %d does not respond - RESET\n", ac97->num);
+			/* proceed anyway - it's often non-critical */
+		}
+	}
+      __access_ok:
+	ac97->id = snd_ac97_read(ac97, AC97_VENDOR_ID1) << 16;
+	ac97->id |= snd_ac97_read(ac97, AC97_VENDOR_ID2);
+	if (! (ac97->scaps & AC97_SCAP_DETECT_BY_VENDOR) &&
+	    (ac97->id == 0x00000000 || ac97->id == 0xffffffff)) {
+		snd_printk(KERN_ERR "AC'97 %d access is not valid [0x%x], removing mixer.\n", ac97->num, ac97->id);
+		snd_ac97_free(ac97);
+		return -EIO;
+	}
+	pid = look_for_codec_id(snd_ac97_codec_ids, ac97->id);
+	if (pid)
+		ac97->flags |= pid->flags;
+	
+	/* test for AC'97 */
+	if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO) && !(ac97->scaps & AC97_SCAP_AUDIO)) {
+		/* test if we can write to the record gain volume register */
+		snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x8a06);
+		if (((err = snd_ac97_read(ac97, AC97_REC_GAIN)) & 0x7fff) == 0x0a06)
+			ac97->scaps |= AC97_SCAP_AUDIO;
+	}
+	if (ac97->scaps & AC97_SCAP_AUDIO) {
+		ac97->caps = snd_ac97_read(ac97, AC97_RESET);
+		ac97->ext_id = snd_ac97_read(ac97, AC97_EXTENDED_ID);
+		if (ac97->ext_id == 0xffff)	/* invalid combination */
+			ac97->ext_id = 0;
+	}
+
+	/* test for MC'97 */
+	if (!(ac97->scaps & AC97_SCAP_SKIP_MODEM) && !(ac97->scaps & AC97_SCAP_MODEM)) {
+		ac97->ext_mid = snd_ac97_read(ac97, AC97_EXTENDED_MID);
+		if (ac97->ext_mid == 0xffff)	/* invalid combination */
+			ac97->ext_mid = 0;
+		if (ac97->ext_mid & 1)
+			ac97->scaps |= AC97_SCAP_MODEM;
+	}
+
+	if (!ac97_is_audio(ac97) && !ac97_is_modem(ac97)) {
+		if (!(ac97->scaps & (AC97_SCAP_SKIP_AUDIO|AC97_SCAP_SKIP_MODEM)))
+			snd_printk(KERN_ERR "AC'97 %d access error (not audio or modem codec)\n", ac97->num);
+		snd_ac97_free(ac97);
+		return -EACCES;
+	}
+
+	if (bus->ops->reset) // FIXME: always skipping?
+		goto __ready_ok;
+
+	/* FIXME: add powerdown control */
+	if (ac97_is_audio(ac97)) {
+		/* nothing should be in powerdown mode */
+		snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0);
+		if (! (ac97->flags & AC97_DEFAULT_POWER_OFF)) {
+			snd_ac97_write_cache(ac97, AC97_RESET, 0); /* reset to defaults */
+			udelay(100);
+			snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0);
+		}
+		/* nothing should be in powerdown mode */
+		snd_ac97_write_cache(ac97, AC97_GENERAL_PURPOSE, 0);
+		end_time = jiffies + msecs_to_jiffies(5000);
+		do {
+			if ((snd_ac97_read(ac97, AC97_POWERDOWN) & 0x0f) == 0x0f)
+				goto __ready_ok;
+			schedule_timeout_uninterruptible(1);
+		} while (time_after_eq(end_time, jiffies));
+		snd_printk(KERN_WARNING "AC'97 %d analog subsections not ready\n", ac97->num);
+	}
+
+	/* FIXME: add powerdown control */
+	if (ac97_is_modem(ac97)) {
+		unsigned char tmp;
+
+		/* nothing should be in powerdown mode */
+		/* note: it's important to set the rate at first */
+		tmp = AC97_MEA_GPIO;
+		if (ac97->ext_mid & AC97_MEI_LINE1) {
+			snd_ac97_write_cache(ac97, AC97_LINE1_RATE, 8000);
+			tmp |= AC97_MEA_ADC1 | AC97_MEA_DAC1;
+		}
+		if (ac97->ext_mid & AC97_MEI_LINE2) {
+			snd_ac97_write_cache(ac97, AC97_LINE2_RATE, 8000);
+			tmp |= AC97_MEA_ADC2 | AC97_MEA_DAC2;
+		}
+		if (ac97->ext_mid & AC97_MEI_HANDSET) {
+			snd_ac97_write_cache(ac97, AC97_HANDSET_RATE, 8000);
+			tmp |= AC97_MEA_HADC | AC97_MEA_HDAC;
+		}
+		snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0);
+		udelay(100);
+		/* nothing should be in powerdown mode */
+		snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0);
+		end_time = jiffies + msecs_to_jiffies(100);
+		do {
+			if ((snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS) & tmp) == tmp)
+				goto __ready_ok;
+			schedule_timeout_uninterruptible(1);
+		} while (time_after_eq(end_time, jiffies));
+		snd_printk(KERN_WARNING "MC'97 %d converters and GPIO not ready (0x%x)\n", ac97->num, snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS));
+	}
+	
+      __ready_ok:
+	if (ac97_is_audio(ac97))
+		ac97->addr = (ac97->ext_id & AC97_EI_ADDR_MASK) >> AC97_EI_ADDR_SHIFT;
+	else
+		ac97->addr = (ac97->ext_mid & AC97_MEI_ADDR_MASK) >> AC97_MEI_ADDR_SHIFT;
+	if (ac97->ext_id & 0x01c9) {	/* L/R, MIC, SDAC, LDAC VRA support */
+		reg = snd_ac97_read(ac97, AC97_EXTENDED_STATUS);
+		reg |= ac97->ext_id & 0x01c0; /* LDAC/SDAC/CDAC */
+		if (! bus->no_vra)
+			reg |= ac97->ext_id & 0x0009; /* VRA/VRM */
+		snd_ac97_write_cache(ac97, AC97_EXTENDED_STATUS, reg);
+	}
+	if ((ac97->ext_id & AC97_EI_DRA) && bus->dra) {
+		/* Intel controllers require double rate data to be put in
+		 * slots 7+8, so let's hope the codec supports it. */
+		snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, AC97_GP_DRSS_MASK, AC97_GP_DRSS_78);
+		if ((snd_ac97_read(ac97, AC97_GENERAL_PURPOSE) & AC97_GP_DRSS_MASK) == AC97_GP_DRSS_78)
+			ac97->flags |= AC97_DOUBLE_RATE;
+		/* restore to slots 10/11 to avoid the confliction with surrounds */
+		snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, AC97_GP_DRSS_MASK, 0);
+	}
+	if (ac97->ext_id & AC97_EI_VRA) {	/* VRA support */
+		snd_ac97_determine_rates(ac97, AC97_PCM_FRONT_DAC_RATE, 0, &ac97->rates[AC97_RATES_FRONT_DAC]);
+		snd_ac97_determine_rates(ac97, AC97_PCM_LR_ADC_RATE, 0, &ac97->rates[AC97_RATES_ADC]);
+	} else {
+		ac97->rates[AC97_RATES_FRONT_DAC] = SNDRV_PCM_RATE_48000;
+		if (ac97->flags & AC97_DOUBLE_RATE)
+			ac97->rates[AC97_RATES_FRONT_DAC] |= SNDRV_PCM_RATE_96000;
+		ac97->rates[AC97_RATES_ADC] = SNDRV_PCM_RATE_48000;
+	}
+	if (ac97->ext_id & AC97_EI_SPDIF) {
+		/* codec specific code (patch) should override these values */
+		ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_32000;
+	}
+	if (ac97->ext_id & AC97_EI_VRM) {	/* MIC VRA support */
+		snd_ac97_determine_rates(ac97, AC97_PCM_MIC_ADC_RATE, 0, &ac97->rates[AC97_RATES_MIC_ADC]);
+	} else {
+		ac97->rates[AC97_RATES_MIC_ADC] = SNDRV_PCM_RATE_48000;
+	}
+	if (ac97->ext_id & AC97_EI_SDAC) {	/* SDAC support */
+		snd_ac97_determine_rates(ac97, AC97_PCM_SURR_DAC_RATE, AC97_PCM_FRONT_DAC_RATE, &ac97->rates[AC97_RATES_SURR_DAC]);
+		ac97->scaps |= AC97_SCAP_SURROUND_DAC;
+	}
+	if (ac97->ext_id & AC97_EI_LDAC) {	/* LDAC support */
+		snd_ac97_determine_rates(ac97, AC97_PCM_LFE_DAC_RATE, AC97_PCM_FRONT_DAC_RATE, &ac97->rates[AC97_RATES_LFE_DAC]);
+		ac97->scaps |= AC97_SCAP_CENTER_LFE_DAC;
+	}
+	/* additional initializations */
+	if (bus->ops->init)
+		bus->ops->init(ac97);
+	snd_ac97_get_name(ac97, ac97->id, name, !ac97_is_audio(ac97));
+	snd_ac97_get_name(NULL, ac97->id, name, !ac97_is_audio(ac97));  // ac97->id might be changed in the special setup code
+	if (! ac97->build_ops)
+		ac97->build_ops = &null_build_ops;
+
+	if (ac97_is_audio(ac97)) {
+		char comp[16];
+		if (card->mixername[0] == '\0') {
+			strcpy(card->mixername, name);
+		} else {
+			if (strlen(card->mixername) + 1 + strlen(name) + 1 <= sizeof(card->mixername)) {
+				strcat(card->mixername, ",");
+				strcat(card->mixername, name);
+			}
+		}
+		sprintf(comp, "AC97a:%08x", ac97->id);
+		if ((err = snd_component_add(card, comp)) < 0) {
+			snd_ac97_free(ac97);
+			return err;
+		}
+		if (snd_ac97_mixer_build(ac97) < 0) {
+			snd_ac97_free(ac97);
+			return -ENOMEM;
+		}
+	}
+	if (ac97_is_modem(ac97)) {
+		char comp[16];
+		if (card->mixername[0] == '\0') {
+			strcpy(card->mixername, name);
+		} else {
+			if (strlen(card->mixername) + 1 + strlen(name) + 1 <= sizeof(card->mixername)) {
+				strcat(card->mixername, ",");
+				strcat(card->mixername, name);
+			}
+		}
+		sprintf(comp, "AC97m:%08x", ac97->id);
+		if ((err = snd_component_add(card, comp)) < 0) {
+			snd_ac97_free(ac97);
+			return err;
+		}
+		if (snd_ac97_modem_build(card, ac97) < 0) {
+			snd_ac97_free(ac97);
+			return -ENOMEM;
+		}
+	}
+	if (ac97_is_audio(ac97))
+		update_power_regs(ac97);
+	snd_ac97_proc_init(ac97);
+	if ((err = snd_device_new(card, SNDRV_DEV_CODEC, ac97, &ops)) < 0) {
+		snd_ac97_free(ac97);
+		return err;
+	}
+	*rac97 = ac97;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ac97_mixer);
+
+/*
+ * Power down the chip.
+ *
+ * MASTER and HEADPHONE registers are muted but the register cache values
+ * are not changed, so that the values can be restored in snd_ac97_resume().
+ */
+static void snd_ac97_powerdown(struct snd_ac97 *ac97)
+{
+	unsigned short power;
+
+	if (ac97_is_audio(ac97)) {
+		/* some codecs have stereo mute bits */
+		snd_ac97_write(ac97, AC97_MASTER, 0x9f9f);
+		snd_ac97_write(ac97, AC97_HEADPHONE, 0x9f9f);
+	}
+
+	/* surround, CLFE, mic powerdown */
+	power = ac97->regs[AC97_EXTENDED_STATUS];
+	if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
+		power |= AC97_EA_PRJ;
+	if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
+		power |= AC97_EA_PRI | AC97_EA_PRK;
+	power |= AC97_EA_PRL;
+	snd_ac97_write(ac97, AC97_EXTENDED_STATUS, power);
+
+	/* powerdown external amplifier */
+	if (ac97->scaps & AC97_SCAP_INV_EAPD)
+		power = ac97->regs[AC97_POWERDOWN] & ~AC97_PD_EAPD;
+	else if (! (ac97->scaps & AC97_SCAP_EAPD_LED))
+		power = ac97->regs[AC97_POWERDOWN] | AC97_PD_EAPD;
+	power |= AC97_PD_PR6;	/* Headphone amplifier powerdown */
+	power |= AC97_PD_PR0 | AC97_PD_PR1;	/* ADC & DAC powerdown */
+	snd_ac97_write(ac97, AC97_POWERDOWN, power);
+	udelay(100);
+	power |= AC97_PD_PR2;	/* Analog Mixer powerdown (Vref on) */
+	snd_ac97_write(ac97, AC97_POWERDOWN, power);
+	if (ac97_is_power_save_mode(ac97)) {
+		power |= AC97_PD_PR3;	/* Analog Mixer powerdown */
+		snd_ac97_write(ac97, AC97_POWERDOWN, power);
+		udelay(100);
+		/* AC-link powerdown, internal Clk disable */
+		/* FIXME: this may cause click noises on some boards */
+		power |= AC97_PD_PR4 | AC97_PD_PR5;
+		snd_ac97_write(ac97, AC97_POWERDOWN, power);
+	}
+}
+
+
+struct ac97_power_reg {
+	unsigned short reg;
+	unsigned short power_reg;
+	unsigned short mask;
+};
+
+enum { PWIDX_ADC, PWIDX_FRONT, PWIDX_CLFE, PWIDX_SURR, PWIDX_MIC, PWIDX_SIZE };
+
+static struct ac97_power_reg power_regs[PWIDX_SIZE] = {
+	[PWIDX_ADC] = { AC97_PCM_LR_ADC_RATE, AC97_POWERDOWN, AC97_PD_PR0},
+	[PWIDX_FRONT] = { AC97_PCM_FRONT_DAC_RATE, AC97_POWERDOWN, AC97_PD_PR1},
+	[PWIDX_CLFE] = { AC97_PCM_LFE_DAC_RATE, AC97_EXTENDED_STATUS,
+			 AC97_EA_PRI | AC97_EA_PRK},
+	[PWIDX_SURR] = { AC97_PCM_SURR_DAC_RATE, AC97_EXTENDED_STATUS,
+			 AC97_EA_PRJ},
+	[PWIDX_MIC] = { AC97_PCM_MIC_ADC_RATE, AC97_EXTENDED_STATUS,
+			AC97_EA_PRL},
+};
+
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+/**
+ * snd_ac97_update_power - update the powerdown register
+ * @ac97: the codec instance
+ * @reg: the rate register, e.g. AC97_PCM_FRONT_DAC_RATE
+ * @powerup: non-zero when power up the part
+ *
+ * Update the AC97 powerdown register bits of the given part.
+ */
+int snd_ac97_update_power(struct snd_ac97 *ac97, int reg, int powerup)
+{
+	int i;
+
+	if (! ac97)
+		return 0;
+
+	if (reg) {
+		/* SPDIF requires DAC power, too */
+		if (reg == AC97_SPDIF)
+			reg = AC97_PCM_FRONT_DAC_RATE;
+		for (i = 0; i < PWIDX_SIZE; i++) {
+			if (power_regs[i].reg == reg) {
+				if (powerup)
+					ac97->power_up |= (1 << i);
+				else
+					ac97->power_up &= ~(1 << i);
+				break;
+			}
+		}
+	}
+
+	if (ac97_is_power_save_mode(ac97) && !powerup)
+		/* adjust power-down bits after two seconds delay
+		 * (for avoiding loud click noises for many (OSS) apps
+		 *  that open/close frequently)
+		 */
+		schedule_delayed_work(&ac97->power_work,
+				      msecs_to_jiffies(power_save * 1000));
+	else {
+		cancel_delayed_work(&ac97->power_work);
+		update_power_regs(ac97);
+	}
+
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ac97_update_power);
+#endif /* CONFIG_SND_AC97_POWER_SAVE */
+
+static void update_power_regs(struct snd_ac97 *ac97)
+{
+	unsigned int power_up, bits;
+	int i;
+
+	power_up = (1 << PWIDX_FRONT) | (1 << PWIDX_ADC);
+	power_up |= (1 << PWIDX_MIC);
+	if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
+		power_up |= (1 << PWIDX_SURR);
+	if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
+		power_up |= (1 << PWIDX_CLFE);
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+	if (ac97_is_power_save_mode(ac97))
+		power_up = ac97->power_up;
+#endif
+	if (power_up) {
+		if (ac97->regs[AC97_POWERDOWN] & AC97_PD_PR2) {
+			/* needs power-up analog mix and vref */
+			snd_ac97_update_bits(ac97, AC97_POWERDOWN,
+					     AC97_PD_PR3, 0);
+			msleep(1);
+			snd_ac97_update_bits(ac97, AC97_POWERDOWN,
+					     AC97_PD_PR2, 0);
+		}
+	}
+	for (i = 0; i < PWIDX_SIZE; i++) {
+		if (power_up & (1 << i))
+			bits = 0;
+		else
+			bits = power_regs[i].mask;
+		snd_ac97_update_bits(ac97, power_regs[i].power_reg,
+				     power_regs[i].mask, bits);
+	}
+	if (! power_up) {
+		if (! (ac97->regs[AC97_POWERDOWN] & AC97_PD_PR2)) {
+			/* power down analog mix and vref */
+			snd_ac97_update_bits(ac97, AC97_POWERDOWN,
+					     AC97_PD_PR2, AC97_PD_PR2);
+			snd_ac97_update_bits(ac97, AC97_POWERDOWN,
+					     AC97_PD_PR3, AC97_PD_PR3);
+		}
+	}
+}
+
+
+#ifdef CONFIG_PM
+/**
+ * snd_ac97_suspend - General suspend function for AC97 codec
+ * @ac97: the ac97 instance
+ *
+ * Suspends the codec, power down the chip.
+ */
+void snd_ac97_suspend(struct snd_ac97 *ac97)
+{
+	if (! ac97)
+		return;
+	if (ac97->build_ops->suspend)
+		ac97->build_ops->suspend(ac97);
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+	cancel_delayed_work(&ac97->power_work);
+	flush_scheduled_work();
+#endif
+	snd_ac97_powerdown(ac97);
+}
+
+EXPORT_SYMBOL(snd_ac97_suspend);
+
+/*
+ * restore ac97 status
+ */
+static void snd_ac97_restore_status(struct snd_ac97 *ac97)
+{
+	int i;
+
+	for (i = 2; i < 0x7c ; i += 2) {
+		if (i == AC97_POWERDOWN || i == AC97_EXTENDED_ID)
+			continue;
+		/* restore only accessible registers
+		 * some chip (e.g. nm256) may hang up when unsupported registers
+		 * are accessed..!
+		 */
+		if (test_bit(i, ac97->reg_accessed)) {
+			snd_ac97_write(ac97, i, ac97->regs[i]);
+			snd_ac97_read(ac97, i);
+		}
+	}
+}
+
+/*
+ * restore IEC958 status
+ */
+static void snd_ac97_restore_iec958(struct snd_ac97 *ac97)
+{
+	if (ac97->ext_id & AC97_EI_SPDIF) {
+		if (ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_SPDIF) {
+			/* reset spdif status */
+			snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
+			snd_ac97_write(ac97, AC97_EXTENDED_STATUS, ac97->regs[AC97_EXTENDED_STATUS]);
+			if (ac97->flags & AC97_CS_SPDIF)
+				snd_ac97_write(ac97, AC97_CSR_SPDIF, ac97->regs[AC97_CSR_SPDIF]);
+			else
+				snd_ac97_write(ac97, AC97_SPDIF, ac97->regs[AC97_SPDIF]);
+			snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF); /* turn on again */
+		}
+	}
+}
+
+/**
+ * snd_ac97_resume - General resume function for AC97 codec
+ * @ac97: the ac97 instance
+ *
+ * Do the standard resume procedure, power up and restoring the
+ * old register values.
+ */
+void snd_ac97_resume(struct snd_ac97 *ac97)
+{
+	unsigned long end_time;
+
+	if (! ac97)
+		return;
+
+	if (ac97->bus->ops->reset) {
+		ac97->bus->ops->reset(ac97);
+		goto  __reset_ready;
+	}
+
+	snd_ac97_write(ac97, AC97_POWERDOWN, 0);
+	if (! (ac97->flags & AC97_DEFAULT_POWER_OFF)) {
+		if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO))
+			snd_ac97_write(ac97, AC97_RESET, 0);
+		else if (!(ac97->scaps & AC97_SCAP_SKIP_MODEM))
+			snd_ac97_write(ac97, AC97_EXTENDED_MID, 0);
+		udelay(100);
+		snd_ac97_write(ac97, AC97_POWERDOWN, 0);
+	}
+	snd_ac97_write(ac97, AC97_GENERAL_PURPOSE, 0);
+
+	snd_ac97_write(ac97, AC97_POWERDOWN, ac97->regs[AC97_POWERDOWN]);
+	if (ac97_is_audio(ac97)) {
+		ac97->bus->ops->write(ac97, AC97_MASTER, 0x8101);
+		end_time = jiffies + msecs_to_jiffies(100);
+		do {
+			if (snd_ac97_read(ac97, AC97_MASTER) == 0x8101)
+				break;
+			schedule_timeout_uninterruptible(1);
+		} while (time_after_eq(end_time, jiffies));
+		/* FIXME: extra delay */
+		ac97->bus->ops->write(ac97, AC97_MASTER, 0x8000);
+		if (snd_ac97_read(ac97, AC97_MASTER) != 0x8000)
+			msleep(250);
+	} else {
+		end_time = jiffies + msecs_to_jiffies(100);
+		do {
+			unsigned short val = snd_ac97_read(ac97, AC97_EXTENDED_MID);
+			if (val != 0xffff && (val & 1) != 0)
+				break;
+			schedule_timeout_uninterruptible(1);
+		} while (time_after_eq(end_time, jiffies));
+	}
+__reset_ready:
+
+	if (ac97->bus->ops->init)
+		ac97->bus->ops->init(ac97);
+
+	if (ac97->build_ops->resume)
+		ac97->build_ops->resume(ac97);
+	else {
+		snd_ac97_restore_status(ac97);
+		snd_ac97_restore_iec958(ac97);
+	}
+}
+
+EXPORT_SYMBOL(snd_ac97_resume);
+#endif
+
+
+/*
+ * Hardware tuning
+ */
+static void set_ctl_name(char *dst, const char *src, const char *suffix)
+{
+	if (suffix)
+		sprintf(dst, "%s %s", src, suffix);
+	else
+		strcpy(dst, src);
+}	
+
+/* remove the control with the given name and optional suffix */
+static int snd_ac97_remove_ctl(struct snd_ac97 *ac97, const char *name,
+			       const char *suffix)
+{
+	struct snd_ctl_elem_id id;
+	memset(&id, 0, sizeof(id));
+	set_ctl_name(id.name, name, suffix);
+	id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	return snd_ctl_remove_id(ac97->bus->card, &id);
+}
+
+static struct snd_kcontrol *ctl_find(struct snd_ac97 *ac97, const char *name, const char *suffix)
+{
+	struct snd_ctl_elem_id sid;
+	memset(&sid, 0, sizeof(sid));
+	set_ctl_name(sid.name, name, suffix);
+	sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	return snd_ctl_find_id(ac97->bus->card, &sid);
+}
+
+/* rename the control with the given name and optional suffix */
+static int snd_ac97_rename_ctl(struct snd_ac97 *ac97, const char *src,
+			       const char *dst, const char *suffix)
+{
+	struct snd_kcontrol *kctl = ctl_find(ac97, src, suffix);
+	if (kctl) {
+		set_ctl_name(kctl->id.name, dst, suffix);
+		return 0;
+	}
+	return -ENOENT;
+}
+
+/* rename both Volume and Switch controls - don't check the return value */
+static void snd_ac97_rename_vol_ctl(struct snd_ac97 *ac97, const char *src,
+				    const char *dst)
+{
+	snd_ac97_rename_ctl(ac97, src, dst, "Switch");
+	snd_ac97_rename_ctl(ac97, src, dst, "Volume");
+}
+
+/* swap controls */
+static int snd_ac97_swap_ctl(struct snd_ac97 *ac97, const char *s1,
+			     const char *s2, const char *suffix)
+{
+	struct snd_kcontrol *kctl1, *kctl2;
+	kctl1 = ctl_find(ac97, s1, suffix);
+	kctl2 = ctl_find(ac97, s2, suffix);
+	if (kctl1 && kctl2) {
+		set_ctl_name(kctl1->id.name, s2, suffix);
+		set_ctl_name(kctl2->id.name, s1, suffix);
+		return 0;
+	}
+	return -ENOENT;
+}
+
+#if 1
+/* bind hp and master controls instead of using only hp control */
+static int bind_hp_volsw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	int err = snd_ac97_put_volsw(kcontrol, ucontrol);
+	if (err > 0) {
+		unsigned long priv_saved = kcontrol->private_value;
+		kcontrol->private_value = (kcontrol->private_value & ~0xff) | AC97_HEADPHONE;
+		snd_ac97_put_volsw(kcontrol, ucontrol);
+		kcontrol->private_value = priv_saved;
+	}
+	return err;
+}
+
+/* ac97 tune: bind Master and Headphone controls */
+static int tune_hp_only(struct snd_ac97 *ac97)
+{
+	struct snd_kcontrol *msw = ctl_find(ac97, "Master Playback Switch", NULL);
+	struct snd_kcontrol *mvol = ctl_find(ac97, "Master Playback Volume", NULL);
+	if (! msw || ! mvol)
+		return -ENOENT;
+	msw->put = bind_hp_volsw_put;
+	mvol->put = bind_hp_volsw_put;
+	snd_ac97_remove_ctl(ac97, "Headphone Playback", "Switch");
+	snd_ac97_remove_ctl(ac97, "Headphone Playback", "Volume");
+	return 0;
+}
+
+#else
+/* ac97 tune: use Headphone control as master */
+static int tune_hp_only(struct snd_ac97 *ac97)
+{
+	if (ctl_find(ac97, "Headphone Playback Switch", NULL) == NULL)
+		return -ENOENT;
+	snd_ac97_remove_ctl(ac97, "Master Playback", "Switch");
+	snd_ac97_remove_ctl(ac97, "Master Playback", "Volume");
+	snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Master Playback");
+	return 0;
+}
+#endif
+
+/* ac97 tune: swap Headphone and Master controls */
+static int tune_swap_hp(struct snd_ac97 *ac97)
+{
+	if (ctl_find(ac97, "Headphone Playback Switch", NULL) == NULL)
+		return -ENOENT;
+	snd_ac97_rename_vol_ctl(ac97, "Master Playback", "Line-Out Playback");
+	snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Master Playback");
+	return 0;
+}
+
+/* ac97 tune: swap Surround and Master controls */
+static int tune_swap_surround(struct snd_ac97 *ac97)
+{
+	if (snd_ac97_swap_ctl(ac97, "Master Playback", "Surround Playback", "Switch") ||
+	    snd_ac97_swap_ctl(ac97, "Master Playback", "Surround Playback", "Volume"))
+		return -ENOENT;
+	return 0;
+}
+
+/* ac97 tune: set up mic sharing for AD codecs */
+static int tune_ad_sharing(struct snd_ac97 *ac97)
+{
+	unsigned short scfg;
+	if ((ac97->id & 0xffffff00) != 0x41445300) {
+		snd_printk(KERN_ERR "ac97_quirk AD_SHARING is only for AD codecs\n");
+		return -EINVAL;
+	}
+	/* Turn on OMS bit to route microphone to back panel */
+	scfg = snd_ac97_read(ac97, AC97_AD_SERIAL_CFG);
+	snd_ac97_write_cache(ac97, AC97_AD_SERIAL_CFG, scfg | 0x0200);
+	return 0;
+}
+
+static const struct snd_kcontrol_new snd_ac97_alc_jack_detect = 
+AC97_SINGLE("Jack Detect", AC97_ALC650_CLOCK, 5, 1, 0);
+
+/* ac97 tune: set up ALC jack-select */
+static int tune_alc_jack(struct snd_ac97 *ac97)
+{
+	if ((ac97->id & 0xffffff00) != 0x414c4700) {
+		snd_printk(KERN_ERR "ac97_quirk ALC_JACK is only for Realtek codecs\n");
+		return -EINVAL;
+	}
+	snd_ac97_update_bits(ac97, 0x7a, 0x20, 0x20); /* select jack detect function */
+	snd_ac97_update_bits(ac97, 0x7a, 0x01, 0x01); /* Line-out auto mute */
+	if (ac97->id == AC97_ID_ALC658D)
+		snd_ac97_update_bits(ac97, 0x74, 0x0800, 0x0800);
+	return snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&snd_ac97_alc_jack_detect, ac97));
+}
+
+/* ac97 tune: inversed EAPD bit */
+static int tune_inv_eapd(struct snd_ac97 *ac97)
+{
+	struct snd_kcontrol *kctl = ctl_find(ac97, "External Amplifier", NULL);
+	if (! kctl)
+		return -ENOENT;
+	set_inv_eapd(ac97, kctl);
+	return 0;
+}
+
+static int master_mute_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	int err = snd_ac97_put_volsw(kcontrol, ucontrol);
+	if (err > 0) {
+		struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+		int shift = (kcontrol->private_value >> 8) & 0x0f;
+		int rshift = (kcontrol->private_value >> 12) & 0x0f;
+		unsigned short mask;
+		if (shift != rshift)
+			mask = 0x8080;
+		else
+			mask = 0x8000;
+		snd_ac97_update_bits(ac97, AC97_POWERDOWN, 0x8000,
+				     (ac97->regs[AC97_MASTER] & mask) == mask ?
+				     0x8000 : 0);
+	}
+	return err;
+}
+
+/* ac97 tune: EAPD controls mute LED bound with the master mute */
+static int tune_mute_led(struct snd_ac97 *ac97)
+{
+	struct snd_kcontrol *msw = ctl_find(ac97, "Master Playback Switch", NULL);
+	if (! msw)
+		return -ENOENT;
+	msw->put = master_mute_sw_put;
+	snd_ac97_remove_ctl(ac97, "External Amplifier", NULL);
+	snd_ac97_update_bits(ac97, AC97_POWERDOWN, 0x8000, 0x8000); /* mute LED on */
+	ac97->scaps |= AC97_SCAP_EAPD_LED;
+	return 0;
+}
+
+static int hp_master_mute_sw_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	int err = bind_hp_volsw_put(kcontrol, ucontrol);
+	if (err > 0) {
+		struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+		int shift = (kcontrol->private_value >> 8) & 0x0f;
+		int rshift = (kcontrol->private_value >> 12) & 0x0f;
+		unsigned short mask;
+		if (shift != rshift)
+			mask = 0x8080;
+		else
+			mask = 0x8000;
+		snd_ac97_update_bits(ac97, AC97_POWERDOWN, 0x8000,
+				     (ac97->regs[AC97_MASTER] & mask) == mask ?
+				     0x8000 : 0);
+	}
+	return err;
+}
+
+static int tune_hp_mute_led(struct snd_ac97 *ac97)
+{
+	struct snd_kcontrol *msw = ctl_find(ac97, "Master Playback Switch", NULL);
+	struct snd_kcontrol *mvol = ctl_find(ac97, "Master Playback Volume", NULL);
+	if (! msw || ! mvol)
+		return -ENOENT;
+	msw->put = hp_master_mute_sw_put;
+	mvol->put = bind_hp_volsw_put;
+	snd_ac97_remove_ctl(ac97, "External Amplifier", NULL);
+	snd_ac97_remove_ctl(ac97, "Headphone Playback", "Switch");
+	snd_ac97_remove_ctl(ac97, "Headphone Playback", "Volume");
+	snd_ac97_update_bits(ac97, AC97_POWERDOWN, 0x8000, 0x8000); /* mute LED on */
+	return 0;
+}
+
+struct quirk_table {
+	const char *name;
+	int (*func)(struct snd_ac97 *);
+};
+
+static struct quirk_table applicable_quirks[] = {
+	{ "none", NULL },
+	{ "hp_only", tune_hp_only },
+	{ "swap_hp", tune_swap_hp },
+	{ "swap_surround", tune_swap_surround },
+	{ "ad_sharing", tune_ad_sharing },
+	{ "alc_jack", tune_alc_jack },
+	{ "inv_eapd", tune_inv_eapd },
+	{ "mute_led", tune_mute_led },
+	{ "hp_mute_led", tune_hp_mute_led },
+};
+
+/* apply the quirk with the given type */
+static int apply_quirk(struct snd_ac97 *ac97, int type)
+{
+	if (type <= 0)
+		return 0;
+	else if (type >= ARRAY_SIZE(applicable_quirks))
+		return -EINVAL;
+	if (applicable_quirks[type].func)
+		return applicable_quirks[type].func(ac97);
+	return 0;
+}
+
+/* apply the quirk with the given name */
+static int apply_quirk_str(struct snd_ac97 *ac97, const char *typestr)
+{
+	int i;
+	struct quirk_table *q;
+
+	for (i = 0; i < ARRAY_SIZE(applicable_quirks); i++) {
+		q = &applicable_quirks[i];
+		if (q->name && ! strcmp(typestr, q->name))
+			return apply_quirk(ac97, i);
+	}
+	/* for compatibility, accept the numbers, too */
+	if (*typestr >= '0' && *typestr <= '9')
+		return apply_quirk(ac97, (int)simple_strtoul(typestr, NULL, 10));
+	return -EINVAL;
+}
+
+/**
+ * snd_ac97_tune_hardware - tune up the hardware
+ * @ac97: the ac97 instance
+ * @quirk: quirk list
+ * @override: explicit quirk value (overrides the list if non-NULL)
+ *
+ * Do some workaround for each pci device, such as renaming of the
+ * headphone (true line-out) control as "Master".
+ * The quirk-list must be terminated with a zero-filled entry.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+
+int snd_ac97_tune_hardware(struct snd_ac97 *ac97, struct ac97_quirk *quirk, const char *override)
+{
+	int result;
+
+	/* quirk overriden? */
+	if (override && strcmp(override, "-1") && strcmp(override, "default")) {
+		result = apply_quirk_str(ac97, override);
+		if (result < 0)
+			snd_printk(KERN_ERR "applying quirk type %s failed (%d)\n", override, result);
+		return result;
+	}
+
+	if (! quirk)
+		return -EINVAL;
+
+	for (; quirk->subvendor; quirk++) {
+		if (quirk->subvendor != ac97->subsystem_vendor)
+			continue;
+		if ((! quirk->mask && quirk->subdevice == ac97->subsystem_device) ||
+		    quirk->subdevice == (quirk->mask & ac97->subsystem_device)) {
+			if (quirk->codec_id && quirk->codec_id != ac97->id)
+				continue;
+			snd_printdd("ac97 quirk for %s (%04x:%04x)\n", quirk->name, ac97->subsystem_vendor, ac97->subsystem_device);
+			result = apply_quirk(ac97, quirk->type);
+			if (result < 0)
+				snd_printk(KERN_ERR "applying quirk type %d for %s failed (%d)\n", quirk->type, quirk->name, result);
+			return result;
+		}
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ac97_tune_hardware);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_ac97_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_ac97_exit(void)
+{
+}
+
+module_init(alsa_ac97_init)
+module_exit(alsa_ac97_exit)
diff --git a/linux/sound/pci/ac97/ac97_id.h b/linux/sound/pci/ac97/ac97_id.h
new file mode 100644
index 0000000..d603147
--- /dev/null
+++ b/linux/sound/pci/ac97/ac97_id.h
@@ -0,0 +1,66 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Universal interface for Audio Codec '97
+ *
+ *  For more details look to AC '97 component specification revision 2.2
+ *  by Intel Corporation (http://developer.intel.com).
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#define AC97_ID_AK4540		0x414b4d00
+#define AC97_ID_AK4542		0x414b4d01
+#define AC97_ID_AD1819		0x41445303
+#define AC97_ID_AD1881		0x41445340
+#define AC97_ID_AD1881A		0x41445348
+#define AC97_ID_AD1885		0x41445360
+#define AC97_ID_AD1886		0x41445361
+#define AC97_ID_AD1887		0x41445362
+#define AC97_ID_AD1886A		0x41445363
+#define AC97_ID_AD1980 		0x41445370
+#define AC97_ID_TR28028		0x54524108
+#define AC97_ID_STAC9700	0x83847600
+#define AC97_ID_STAC9704	0x83847604
+#define AC97_ID_STAC9705	0x83847605
+#define AC97_ID_STAC9708	0x83847608
+#define AC97_ID_STAC9721	0x83847609
+#define AC97_ID_STAC9744	0x83847644
+#define AC97_ID_STAC9756	0x83847656
+#define AC97_ID_CS4297A		0x43525910
+#define AC97_ID_CS4299		0x43525930
+#define AC97_ID_CS4201		0x43525948
+#define AC97_ID_CS4205		0x43525958
+#define AC97_ID_CS_MASK		0xfffffff8	/* bit 0-2: rev */
+#define AC97_ID_ALC100		0x414c4300
+#define AC97_ID_ALC650		0x414c4720
+#define AC97_ID_ALC650D		0x414c4721
+#define AC97_ID_ALC650E		0x414c4722
+#define AC97_ID_ALC650F		0x414c4723
+#define AC97_ID_ALC655		0x414c4760
+#define AC97_ID_ALC658		0x414c4780
+#define AC97_ID_ALC658D		0x414c4781
+#define AC97_ID_ALC850		0x414c4790
+#define AC97_ID_YMF743		0x594d4800
+#define AC97_ID_YMF753		0x594d4803
+#define AC97_ID_VT1616		0x49434551
+#define AC97_ID_CM9738		0x434d4941
+#define AC97_ID_CM9739		0x434d4961
+#define AC97_ID_CM9761_78	0x434d4978
+#define AC97_ID_CM9761_82	0x434d4982
+#define AC97_ID_CM9761_83	0x434d4983
+#define AC97_ID_ST7597		0x53544d02
+#define AC97_ID_ST_AC97_ID4	0x53544d04
diff --git a/linux/sound/pci/ac97/ac97_local.h b/linux/sound/pci/ac97/ac97_local.h
new file mode 100644
index 0000000..c276a5e
--- /dev/null
+++ b/linux/sound/pci/ac97/ac97_local.h
@@ -0,0 +1,41 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Universal interface for Audio Codec '97
+ *
+ *  For more details look to AC '97 component specification revision 2.2
+ *  by Intel Corporation (http://developer.intel.com).
+ *
+ *
+ *   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
+ *
+ */
+
+void snd_ac97_get_name(struct snd_ac97 *ac97, unsigned int id, char *name,
+		       int modem);
+int snd_ac97_update_bits_nolock(struct snd_ac97 *ac97, unsigned short reg,
+				unsigned short mask, unsigned short value);
+
+/* ac97_proc.c */
+#ifdef CONFIG_PROC_FS
+void snd_ac97_bus_proc_init(struct snd_ac97_bus * ac97);
+void snd_ac97_bus_proc_done(struct snd_ac97_bus * ac97);
+void snd_ac97_proc_init(struct snd_ac97 * ac97);
+void snd_ac97_proc_done(struct snd_ac97 * ac97);
+#else
+#define snd_ac97_bus_proc_init(ac97_bus_t) do { } while (0)
+#define snd_ac97_bus_proc_done(ac97_bus_t) do { } while (0)
+#define snd_ac97_proc_init(ac97_t) do { } while (0)
+#define snd_ac97_proc_done(ac97_t) do { } while (0)
+#endif
diff --git a/linux/sound/pci/ac97/ac97_patch.c b/linux/sound/pci/ac97/ac97_patch.c
new file mode 100644
index 0000000..e68c98e
--- /dev/null
+++ b/linux/sound/pci/ac97/ac97_patch.c
@@ -0,0 +1,3912 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Universal interface for Audio Codec '97
+ *
+ *  For more details look to AC '97 component specification revision 2.2
+ *  by Intel Corporation (http://developer.intel.com) and to datasheets
+ *  for specific codecs.
+ *
+ *
+ *   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 "ac97_local.h"
+#include "ac97_patch.h"
+
+/*
+ *  Chip specific initialization
+ */
+
+static int patch_build_controls(struct snd_ac97 * ac97, const struct snd_kcontrol_new *controls, int count)
+{
+	int idx, err;
+
+	for (idx = 0; idx < count; idx++)
+		if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&controls[idx], ac97))) < 0)
+			return err;
+	return 0;
+}
+
+/* replace with a new TLV */
+static void reset_tlv(struct snd_ac97 *ac97, const char *name,
+		      const unsigned int *tlv)
+{
+	struct snd_ctl_elem_id sid;
+	struct snd_kcontrol *kctl;
+	memset(&sid, 0, sizeof(sid));
+	strcpy(sid.name, name);
+	sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	kctl = snd_ctl_find_id(ac97->bus->card, &sid);
+	if (kctl && kctl->tlv.p)
+		kctl->tlv.p = tlv;
+}
+
+/* set to the page, update bits and restore the page */
+static int ac97_update_bits_page(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value, unsigned short page)
+{
+	unsigned short page_save;
+	int ret;
+
+	mutex_lock(&ac97->page_mutex);
+	page_save = snd_ac97_read(ac97, AC97_INT_PAGING) & AC97_PAGE_MASK;
+	snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page);
+	ret = snd_ac97_update_bits(ac97, reg, mask, value);
+	snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page_save);
+	mutex_unlock(&ac97->page_mutex); /* unlock paging */
+	return ret;
+}
+
+/*
+ * shared line-in/mic controls
+ */
+static int ac97_enum_text_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo,
+			       const char **texts, unsigned int nums)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = nums;
+	if (uinfo->value.enumerated.item > nums - 1)
+		uinfo->value.enumerated.item = nums - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int ac97_surround_jack_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char *texts[] = { "Shared", "Independent" };
+	return ac97_enum_text_info(kcontrol, uinfo, texts, 2);
+}
+
+static int ac97_surround_jack_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = ac97->indep_surround;
+	return 0;
+}
+
+static int ac97_surround_jack_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned char indep = !!ucontrol->value.enumerated.item[0];
+
+	if (indep != ac97->indep_surround) {
+		ac97->indep_surround = indep;
+		if (ac97->build_ops->update_jacks)
+			ac97->build_ops->update_jacks(ac97);
+		return 1;
+	}
+	return 0;
+}
+
+static int ac97_channel_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char *texts[] = { "2ch", "4ch", "6ch", "8ch" };
+	return ac97_enum_text_info(kcontrol, uinfo, texts,
+		kcontrol->private_value);
+}
+
+static int ac97_channel_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = ac97->channel_mode;
+	return 0;
+}
+
+static int ac97_channel_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned char mode = ucontrol->value.enumerated.item[0];
+
+	if (mode >= kcontrol->private_value)
+		return -EINVAL;
+
+	if (mode != ac97->channel_mode) {
+		ac97->channel_mode = mode;
+		if (ac97->build_ops->update_jacks)
+			ac97->build_ops->update_jacks(ac97);
+		return 1;
+	}
+	return 0;
+}
+
+#define AC97_SURROUND_JACK_MODE_CTL \
+	{ \
+		.iface	= SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name	= "Surround Jack Mode", \
+		.info = ac97_surround_jack_mode_info, \
+		.get = ac97_surround_jack_mode_get, \
+		.put = ac97_surround_jack_mode_put, \
+	}
+/* 6ch */
+#define AC97_CHANNEL_MODE_CTL \
+	{ \
+		.iface	= SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name	= "Channel Mode", \
+		.info = ac97_channel_mode_info, \
+		.get = ac97_channel_mode_get, \
+		.put = ac97_channel_mode_put, \
+		.private_value = 3, \
+	}
+/* 4ch */
+#define AC97_CHANNEL_MODE_4CH_CTL \
+	{ \
+		.iface	= SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name	= "Channel Mode", \
+		.info = ac97_channel_mode_info, \
+		.get = ac97_channel_mode_get, \
+		.put = ac97_channel_mode_put, \
+		.private_value = 2, \
+	}
+/* 8ch */
+#define AC97_CHANNEL_MODE_8CH_CTL \
+	{ \
+		.iface  = SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name   = "Channel Mode", \
+		.info = ac97_channel_mode_info, \
+		.get = ac97_channel_mode_get, \
+		.put = ac97_channel_mode_put, \
+		.private_value = 4, \
+	}
+
+static inline int is_surround_on(struct snd_ac97 *ac97)
+{
+	return ac97->channel_mode >= 1;
+}
+
+static inline int is_clfe_on(struct snd_ac97 *ac97)
+{
+	return ac97->channel_mode >= 2;
+}
+
+/* system has shared jacks with surround out enabled */
+static inline int is_shared_surrout(struct snd_ac97 *ac97)
+{
+	return !ac97->indep_surround && is_surround_on(ac97);
+}
+
+/* system has shared jacks with center/lfe out enabled */
+static inline int is_shared_clfeout(struct snd_ac97 *ac97)
+{
+	return !ac97->indep_surround && is_clfe_on(ac97);
+}
+
+/* system has shared jacks with line in enabled */
+static inline int is_shared_linein(struct snd_ac97 *ac97)
+{
+	return !ac97->indep_surround && !is_surround_on(ac97);
+}
+
+/* system has shared jacks with mic in enabled */
+static inline int is_shared_micin(struct snd_ac97 *ac97)
+{
+	return !ac97->indep_surround && !is_clfe_on(ac97);
+}
+
+static inline int alc850_is_aux_back_surround(struct snd_ac97 *ac97)
+{
+	return is_surround_on(ac97);
+}
+
+/* The following snd_ac97_ymf753_... items added by David Shust (dshust@shustring.com) */
+/* Modified for YMF743 by Keita Maehara <maehara@debian.org> */
+
+/* It is possible to indicate to the Yamaha YMF7x3 the type of
+   speakers being used. */
+
+static int snd_ac97_ymf7x3_info_speaker(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[3] = {
+		"Standard", "Small", "Smaller"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ac97_ymf7x3_get_speaker(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	val = ac97->regs[AC97_YMF7X3_3D_MODE_SEL];
+	val = (val >> 10) & 3;
+	if (val > 0)    /* 0 = invalid */
+		val--;
+	ucontrol->value.enumerated.item[0] = val;
+	return 0;
+}
+
+static int snd_ac97_ymf7x3_put_speaker(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	if (ucontrol->value.enumerated.item[0] > 2)
+		return -EINVAL;
+	val = (ucontrol->value.enumerated.item[0] + 1) << 10;
+	return snd_ac97_update(ac97, AC97_YMF7X3_3D_MODE_SEL, val);
+}
+
+static const struct snd_kcontrol_new snd_ac97_ymf7x3_controls_speaker =
+{
+	.iface  = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name   = "3D Control - Speaker",
+	.info   = snd_ac97_ymf7x3_info_speaker,
+	.get    = snd_ac97_ymf7x3_get_speaker,
+	.put    = snd_ac97_ymf7x3_put_speaker,
+};
+
+/* It is possible to indicate to the Yamaha YMF7x3 the source to
+   direct to the S/PDIF output. */
+static int snd_ac97_ymf7x3_spdif_source_info(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = { "AC-Link", "A/D Converter" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ac97_ymf7x3_spdif_source_get(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	val = ac97->regs[AC97_YMF7X3_DIT_CTRL];
+	ucontrol->value.enumerated.item[0] = (val >> 1) & 1;
+	return 0;
+}
+
+static int snd_ac97_ymf7x3_spdif_source_put(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	if (ucontrol->value.enumerated.item[0] > 1)
+		return -EINVAL;
+	val = ucontrol->value.enumerated.item[0] << 1;
+	return snd_ac97_update_bits(ac97, AC97_YMF7X3_DIT_CTRL, 0x0002, val);
+}
+
+static int patch_yamaha_ymf7x3_3d(struct snd_ac97 *ac97)
+{
+	struct snd_kcontrol *kctl;
+	int err;
+
+	kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97);
+	err = snd_ctl_add(ac97->bus->card, kctl);
+	if (err < 0)
+		return err;
+	strcpy(kctl->id.name, "3D Control - Wide");
+	kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 9, 7, 0);
+	snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000);
+	err = snd_ctl_add(ac97->bus->card,
+			  snd_ac97_cnew(&snd_ac97_ymf7x3_controls_speaker,
+					ac97));
+	if (err < 0)
+		return err;
+	snd_ac97_write_cache(ac97, AC97_YMF7X3_3D_MODE_SEL, 0x0c00);
+	return 0;
+}
+
+static const struct snd_kcontrol_new snd_ac97_yamaha_ymf743_controls_spdif[3] =
+{
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+		    AC97_YMF7X3_DIT_CTRL, 0, 1, 0),
+	{
+		.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name	= SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Source",
+		.info	= snd_ac97_ymf7x3_spdif_source_info,
+		.get	= snd_ac97_ymf7x3_spdif_source_get,
+		.put	= snd_ac97_ymf7x3_spdif_source_put,
+	},
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("", NONE, NONE) "Mute",
+		    AC97_YMF7X3_DIT_CTRL, 2, 1, 1)
+};
+
+static int patch_yamaha_ymf743_build_spdif(struct snd_ac97 *ac97)
+{
+	int err;
+
+	err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3);
+	if (err < 0)
+		return err;
+	err = patch_build_controls(ac97,
+				   snd_ac97_yamaha_ymf743_controls_spdif, 3);
+	if (err < 0)
+		return err;
+	/* set default PCM S/PDIF params */
+	/* PCM audio,no copyright,no preemphasis,PCM coder,original */
+	snd_ac97_write_cache(ac97, AC97_YMF7X3_DIT_CTRL, 0xa201);
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_yamaha_ymf743_ops = {
+	.build_spdif	= patch_yamaha_ymf743_build_spdif,
+	.build_3d	= patch_yamaha_ymf7x3_3d,
+};
+
+static int patch_yamaha_ymf743(struct snd_ac97 *ac97)
+{
+	ac97->build_ops = &patch_yamaha_ymf743_ops;
+	ac97->caps |= AC97_BC_BASS_TREBLE;
+	ac97->caps |= 0x04 << 10; /* Yamaha 3D enhancement */
+	ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */
+	ac97->ext_id |= AC97_EI_SPDIF; /* force the detection of spdif */
+	return 0;
+}
+
+/* The AC'97 spec states that the S/PDIF signal is to be output at pin 48.
+   The YMF753 will output the S/PDIF signal to pin 43, 47 (EAPD), or 48.
+   By default, no output pin is selected, and the S/PDIF signal is not output.
+   There is also a bit to mute S/PDIF output in a vendor-specific register. */
+static int snd_ac97_ymf753_spdif_output_pin_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[3] = { "Disabled", "Pin 43", "Pin 48" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ac97_ymf753_spdif_output_pin_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	val = ac97->regs[AC97_YMF7X3_DIT_CTRL];
+	ucontrol->value.enumerated.item[0] = (val & 0x0008) ? 2 : (val & 0x0020) ? 1 : 0;
+	return 0;
+}
+
+static int snd_ac97_ymf753_spdif_output_pin_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	if (ucontrol->value.enumerated.item[0] > 2)
+		return -EINVAL;
+	val = (ucontrol->value.enumerated.item[0] == 2) ? 0x0008 :
+	      (ucontrol->value.enumerated.item[0] == 1) ? 0x0020 : 0;
+	return snd_ac97_update_bits(ac97, AC97_YMF7X3_DIT_CTRL, 0x0028, val);
+	/* The following can be used to direct S/PDIF output to pin 47 (EAPD).
+	   snd_ac97_write_cache(ac97, 0x62, snd_ac97_read(ac97, 0x62) | 0x0008); */
+}
+
+static const struct snd_kcontrol_new snd_ac97_ymf753_controls_spdif[3] = {
+	{
+		.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name	= SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+		.info	= snd_ac97_ymf7x3_spdif_source_info,
+		.get	= snd_ac97_ymf7x3_spdif_source_get,
+		.put	= snd_ac97_ymf7x3_spdif_source_put,
+	},
+	{
+		.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name	= SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Output Pin",
+		.info	= snd_ac97_ymf753_spdif_output_pin_info,
+		.get	= snd_ac97_ymf753_spdif_output_pin_get,
+		.put	= snd_ac97_ymf753_spdif_output_pin_put,
+	},
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("", NONE, NONE) "Mute",
+		    AC97_YMF7X3_DIT_CTRL, 2, 1, 1)
+};
+
+static int patch_yamaha_ymf753_post_spdif(struct snd_ac97 * ac97)
+{
+	int err;
+
+	if ((err = patch_build_controls(ac97, snd_ac97_ymf753_controls_spdif, ARRAY_SIZE(snd_ac97_ymf753_controls_spdif))) < 0)
+		return err;
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_yamaha_ymf753_ops = {
+	.build_3d	= patch_yamaha_ymf7x3_3d,
+	.build_post_spdif = patch_yamaha_ymf753_post_spdif
+};
+
+static int patch_yamaha_ymf753(struct snd_ac97 * ac97)
+{
+	/* Patch for Yamaha YMF753, Copyright (c) by David Shust, dshust@shustring.com.
+	   This chip has nonstandard and extended behaviour with regard to its S/PDIF output.
+	   The AC'97 spec states that the S/PDIF signal is to be output at pin 48.
+	   The YMF753 will ouput the S/PDIF signal to pin 43, 47 (EAPD), or 48.
+	   By default, no output pin is selected, and the S/PDIF signal is not output.
+	   There is also a bit to mute S/PDIF output in a vendor-specific register.
+	*/
+	ac97->build_ops = &patch_yamaha_ymf753_ops;
+	ac97->caps |= AC97_BC_BASS_TREBLE;
+	ac97->caps |= 0x04 << 10; /* Yamaha 3D enhancement */
+	return 0;
+}
+
+/*
+ * May 2, 2003 Liam Girdwood <lrg@slimlogic.co.uk>
+ *  removed broken wolfson00 patch.
+ *  added support for WM9705,WM9708,WM9709,WM9710,WM9711,WM9712 and WM9717.
+ */
+
+static const struct snd_kcontrol_new wm97xx_snd_ac97_controls[] = {
+AC97_DOUBLE("Front Playback Volume", AC97_WM97XX_FMIXER_VOL, 8, 0, 31, 1),
+AC97_SINGLE("Front Playback Switch", AC97_WM97XX_FMIXER_VOL, 15, 1, 1),
+};
+
+static int patch_wolfson_wm9703_specific(struct snd_ac97 * ac97)
+{
+	/* This is known to work for the ViewSonic ViewPad 1000
+	 * Randolph Bentson <bentson@holmsjoen.com>
+	 * WM9703/9707/9708/9717 
+	 */
+	int err, i;
+	
+	for (i = 0; i < ARRAY_SIZE(wm97xx_snd_ac97_controls); i++) {
+		if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm97xx_snd_ac97_controls[i], ac97))) < 0)
+			return err;
+	}
+	snd_ac97_write_cache(ac97,  AC97_WM97XX_FMIXER_VOL, 0x0808);
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_wolfson_wm9703_ops = {
+	.build_specific = patch_wolfson_wm9703_specific,
+};
+
+static int patch_wolfson03(struct snd_ac97 * ac97)
+{
+	ac97->build_ops = &patch_wolfson_wm9703_ops;
+	return 0;
+}
+
+static const struct snd_kcontrol_new wm9704_snd_ac97_controls[] = {
+AC97_DOUBLE("Front Playback Volume", AC97_WM97XX_FMIXER_VOL, 8, 0, 31, 1),
+AC97_SINGLE("Front Playback Switch", AC97_WM97XX_FMIXER_VOL, 15, 1, 1),
+AC97_DOUBLE("Rear Playback Volume", AC97_WM9704_RMIXER_VOL, 8, 0, 31, 1),
+AC97_SINGLE("Rear Playback Switch", AC97_WM9704_RMIXER_VOL, 15, 1, 1),
+AC97_DOUBLE("Rear DAC Volume", AC97_WM9704_RPCM_VOL, 8, 0, 31, 1),
+AC97_DOUBLE("Surround Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1),
+};
+
+static int patch_wolfson_wm9704_specific(struct snd_ac97 * ac97)
+{
+	int err, i;
+	for (i = 0; i < ARRAY_SIZE(wm9704_snd_ac97_controls); i++) {
+		if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm9704_snd_ac97_controls[i], ac97))) < 0)
+			return err;
+	}
+	/* patch for DVD noise */
+	snd_ac97_write_cache(ac97, AC97_WM9704_TEST, 0x0200);
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_wolfson_wm9704_ops = {
+	.build_specific = patch_wolfson_wm9704_specific,
+};
+
+static int patch_wolfson04(struct snd_ac97 * ac97)
+{
+	/* WM9704M/9704Q */
+	ac97->build_ops = &patch_wolfson_wm9704_ops;
+	return 0;
+}
+
+static int patch_wolfson05(struct snd_ac97 * ac97)
+{
+	/* WM9705, WM9710 */
+	ac97->build_ops = &patch_wolfson_wm9703_ops;
+#ifdef CONFIG_TOUCHSCREEN_WM9705
+	/* WM9705 touchscreen uses AUX and VIDEO for touch */
+	ac97->flags |= AC97_HAS_NO_VIDEO | AC97_HAS_NO_AUX;
+#endif
+	return 0;
+}
+
+static const char* wm9711_alc_select[] = {"None", "Left", "Right", "Stereo"};
+static const char* wm9711_alc_mix[] = {"Stereo", "Right", "Left", "None"};
+static const char* wm9711_out3_src[] = {"Left", "VREF", "Left + Right", "Mono"};
+static const char* wm9711_out3_lrsrc[] = {"Master Mix", "Headphone Mix"};
+static const char* wm9711_rec_adc[] = {"Stereo", "Left", "Right", "Mute"};
+static const char* wm9711_base[] = {"Linear Control", "Adaptive Boost"};
+static const char* wm9711_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
+static const char* wm9711_mic[] = {"Mic 1", "Differential", "Mic 2", "Stereo"};
+static const char* wm9711_rec_sel[] = 
+	{"Mic 1", "NC", "NC", "Master Mix", "Line", "Headphone Mix", "Phone Mix", "Phone"};
+static const char* wm9711_ng_type[] = {"Constant Gain", "Mute"};
+
+static const struct ac97_enum wm9711_enum[] = {
+AC97_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9711_alc_select),
+AC97_ENUM_SINGLE(AC97_VIDEO, 10, 4, wm9711_alc_mix),
+AC97_ENUM_SINGLE(AC97_AUX, 9, 4, wm9711_out3_src),
+AC97_ENUM_SINGLE(AC97_AUX, 8, 2, wm9711_out3_lrsrc),
+AC97_ENUM_SINGLE(AC97_REC_SEL, 12, 4, wm9711_rec_adc),
+AC97_ENUM_SINGLE(AC97_MASTER_TONE, 15, 2, wm9711_base),
+AC97_ENUM_DOUBLE(AC97_REC_GAIN, 14, 6, 2, wm9711_rec_gain),
+AC97_ENUM_SINGLE(AC97_MIC, 5, 4, wm9711_mic),
+AC97_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, wm9711_rec_sel),
+AC97_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9711_ng_type),
+};
+
+static const struct snd_kcontrol_new wm9711_snd_ac97_controls[] = {
+AC97_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
+AC97_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
+AC97_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0),
+AC97_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
+AC97_ENUM("ALC Function", wm9711_enum[0]),
+AC97_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 1),
+AC97_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 1),
+AC97_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
+AC97_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
+AC97_ENUM("ALC NG Type", wm9711_enum[9]),
+AC97_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 1),
+
+AC97_SINGLE("Side Tone Switch", AC97_VIDEO, 15, 1, 1),
+AC97_SINGLE("Side Tone Volume", AC97_VIDEO, 12, 7, 1),
+AC97_ENUM("ALC Headphone Mux", wm9711_enum[1]),
+AC97_SINGLE("ALC Headphone Volume", AC97_VIDEO, 7, 7, 1),
+
+AC97_SINGLE("Out3 Switch", AC97_AUX, 15, 1, 1),
+AC97_SINGLE("Out3 ZC Switch", AC97_AUX, 7, 1, 0),
+AC97_ENUM("Out3 Mux", wm9711_enum[2]),
+AC97_ENUM("Out3 LR Mux", wm9711_enum[3]),
+AC97_SINGLE("Out3 Volume", AC97_AUX, 0, 31, 1),
+
+AC97_SINGLE("Beep to Headphone Switch", AC97_PC_BEEP, 15, 1, 1),
+AC97_SINGLE("Beep to Headphone Volume", AC97_PC_BEEP, 12, 7, 1),
+AC97_SINGLE("Beep to Side Tone Switch", AC97_PC_BEEP, 11, 1, 1),
+AC97_SINGLE("Beep to Side Tone Volume", AC97_PC_BEEP, 8, 7, 1),
+AC97_SINGLE("Beep to Phone Switch", AC97_PC_BEEP, 7, 1, 1),
+AC97_SINGLE("Beep to Phone Volume", AC97_PC_BEEP, 4, 7, 1),
+
+AC97_SINGLE("Aux to Headphone Switch", AC97_CD, 15, 1, 1),
+AC97_SINGLE("Aux to Headphone Volume", AC97_CD, 12, 7, 1),
+AC97_SINGLE("Aux to Side Tone Switch", AC97_CD, 11, 1, 1),
+AC97_SINGLE("Aux to Side Tone Volume", AC97_CD, 8, 7, 1),
+AC97_SINGLE("Aux to Phone Switch", AC97_CD, 7, 1, 1),
+AC97_SINGLE("Aux to Phone Volume", AC97_CD, 4, 7, 1),
+
+AC97_SINGLE("Phone to Headphone Switch", AC97_PHONE, 15, 1, 1),
+AC97_SINGLE("Phone to Master Switch", AC97_PHONE, 14, 1, 1),
+
+AC97_SINGLE("Line to Headphone Switch", AC97_LINE, 15, 1, 1),
+AC97_SINGLE("Line to Master Switch", AC97_LINE, 14, 1, 1),
+AC97_SINGLE("Line to Phone Switch", AC97_LINE, 13, 1, 1),
+
+AC97_SINGLE("PCM Playback to Headphone Switch", AC97_PCM, 15, 1, 1),
+AC97_SINGLE("PCM Playback to Master Switch", AC97_PCM, 14, 1, 1),
+AC97_SINGLE("PCM Playback to Phone Switch", AC97_PCM, 13, 1, 1),
+
+AC97_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0),
+AC97_ENUM("Capture to Phone Mux", wm9711_enum[4]),
+AC97_SINGLE("Capture to Phone 20dB Boost Switch", AC97_REC_SEL, 11, 1, 1),
+AC97_ENUM("Capture Select", wm9711_enum[8]),
+
+AC97_SINGLE("3D Upper Cut-off Switch", AC97_3D_CONTROL, 5, 1, 1),
+AC97_SINGLE("3D Lower Cut-off Switch", AC97_3D_CONTROL, 4, 1, 1),
+
+AC97_ENUM("Bass Control", wm9711_enum[5]),
+AC97_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1),
+AC97_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1),
+AC97_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0),
+
+AC97_SINGLE("ADC Switch", AC97_REC_GAIN, 15, 1, 1),
+AC97_ENUM("Capture Volume Steps", wm9711_enum[6]),
+AC97_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 63, 1),
+AC97_SINGLE("Capture ZC Switch", AC97_REC_GAIN, 7, 1, 0),
+
+AC97_SINGLE("Mic 1 to Phone Switch", AC97_MIC, 14, 1, 1),
+AC97_SINGLE("Mic 2 to Phone Switch", AC97_MIC, 13, 1, 1),
+AC97_ENUM("Mic Select Source", wm9711_enum[7]),
+AC97_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1),
+AC97_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1),
+AC97_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 7, 1, 0),
+
+AC97_SINGLE("Master Left Inv Switch", AC97_MASTER, 6, 1, 0),
+AC97_SINGLE("Master ZC Switch", AC97_MASTER, 7, 1, 0),
+AC97_SINGLE("Headphone ZC Switch", AC97_HEADPHONE, 7, 1, 0),
+AC97_SINGLE("Mono ZC Switch", AC97_MASTER_MONO, 7, 1, 0),
+};
+
+static int patch_wolfson_wm9711_specific(struct snd_ac97 * ac97)
+{
+	int err, i;
+	
+	for (i = 0; i < ARRAY_SIZE(wm9711_snd_ac97_controls); i++) {
+		if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm9711_snd_ac97_controls[i], ac97))) < 0)
+			return err;
+	}
+	snd_ac97_write_cache(ac97,  AC97_CODEC_CLASS_REV, 0x0808);
+	snd_ac97_write_cache(ac97,  AC97_PCI_SVID, 0x0808);
+	snd_ac97_write_cache(ac97,  AC97_VIDEO, 0x0808);
+	snd_ac97_write_cache(ac97,  AC97_AUX, 0x0808);
+	snd_ac97_write_cache(ac97,  AC97_PC_BEEP, 0x0808);
+	snd_ac97_write_cache(ac97,  AC97_CD, 0x0000);
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_wolfson_wm9711_ops = {
+	.build_specific = patch_wolfson_wm9711_specific,
+};
+
+static int patch_wolfson11(struct snd_ac97 * ac97)
+{
+	/* WM9711, WM9712 */
+	ac97->build_ops = &patch_wolfson_wm9711_ops;
+
+	ac97->flags |= AC97_HAS_NO_REC_GAIN | AC97_STEREO_MUTES | AC97_HAS_NO_MIC |
+		AC97_HAS_NO_PC_BEEP | AC97_HAS_NO_VIDEO | AC97_HAS_NO_CD;
+	
+	return 0;
+}
+
+static const char* wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"};
+static const char* wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"};
+static const char* wm9713_rec_src[] = 
+	{"Mic 1", "Mic 2", "Line", "Mono In", "Headphone Mix", "Master Mix", 
+	"Mono Mix", "Zh"};
+static const char* wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
+static const char* wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"};
+static const char* wm9713_mono_pga[] = {"Vmid", "Zh", "Mono Mix", "Inv 1"};
+static const char* wm9713_spk_pga[] = 
+	{"Vmid", "Zh", "Headphone Mix", "Master Mix", "Inv", "NC", "NC", "NC"};
+static const char* wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone Mix", "NC"};
+static const char* wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "NC"};
+static const char* wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "NC"};
+static const char* wm9713_dac_inv[] = 
+	{"Off", "Mono Mix", "Master Mix", "Headphone Mix L", "Headphone Mix R", 
+	"Headphone Mix Mono", "NC", "Vmid"};
+static const char* wm9713_base[] = {"Linear Control", "Adaptive Boost"};
+static const char* wm9713_ng_type[] = {"Constant Gain", "Mute"};
+
+static const struct ac97_enum wm9713_enum[] = {
+AC97_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer),
+AC97_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux),
+AC97_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux),
+AC97_ENUM_DOUBLE(AC97_VIDEO, 3, 0, 8, wm9713_rec_src),
+AC97_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain),
+AC97_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select),
+AC97_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga),
+AC97_ENUM_DOUBLE(AC97_REC_GAIN, 11, 8, 8, wm9713_spk_pga),
+AC97_ENUM_DOUBLE(AC97_REC_GAIN, 6, 4, 4, wm9713_hp_pga),
+AC97_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga),
+AC97_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga),
+AC97_ENUM_DOUBLE(AC97_REC_GAIN_MIC, 13, 10, 8, wm9713_dac_inv),
+AC97_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_base),
+AC97_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type),
+};
+
+static const struct snd_kcontrol_new wm13_snd_ac97_controls[] = {
+AC97_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1),
+AC97_SINGLE("Line In to Headphone Switch", AC97_PC_BEEP, 15, 1, 1),
+AC97_SINGLE("Line In to Master Switch", AC97_PC_BEEP, 14, 1, 1),
+AC97_SINGLE("Line In to Mono Switch", AC97_PC_BEEP, 13, 1, 1),
+
+AC97_DOUBLE("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1),
+AC97_SINGLE("PCM Playback to Headphone Switch", AC97_PHONE, 15, 1, 1),
+AC97_SINGLE("PCM Playback to Master Switch", AC97_PHONE, 14, 1, 1),
+AC97_SINGLE("PCM Playback to Mono Switch", AC97_PHONE, 13, 1, 1),
+
+AC97_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1),
+AC97_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1),
+AC97_SINGLE("Mic 1 to Mono Switch", AC97_LINE, 7, 1, 1),
+AC97_SINGLE("Mic 2 to Mono Switch", AC97_LINE, 6, 1, 1),
+AC97_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0),
+AC97_ENUM("Mic to Headphone Mux", wm9713_enum[0]),
+AC97_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1),
+
+AC97_SINGLE("Capture Switch", AC97_CD, 15, 1, 1),
+AC97_ENUM("Capture Volume Steps", wm9713_enum[4]),
+AC97_DOUBLE("Capture Volume", AC97_CD, 8, 0, 15, 0),
+AC97_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0),
+
+AC97_ENUM("Capture to Headphone Mux", wm9713_enum[1]),
+AC97_SINGLE("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1),
+AC97_ENUM("Capture to Mono Mux", wm9713_enum[2]),
+AC97_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0),
+AC97_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0),
+AC97_ENUM("Capture Select", wm9713_enum[3]),
+
+AC97_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
+AC97_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
+AC97_SINGLE("ALC Decay Time ", AC97_CODEC_CLASS_REV, 4, 15, 0),
+AC97_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
+AC97_ENUM("ALC Function", wm9713_enum[5]),
+AC97_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0),
+AC97_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0),
+AC97_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
+AC97_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
+AC97_ENUM("ALC NG Type", wm9713_enum[13]),
+AC97_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0),
+
+AC97_DOUBLE("Master ZC Switch", AC97_MASTER, 14, 6, 1, 0),
+AC97_DOUBLE("Headphone ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0),
+AC97_DOUBLE("Out3/4 ZC Switch", AC97_MASTER_MONO, 14, 6, 1, 0),
+AC97_SINGLE("Master Right Switch", AC97_MASTER, 7, 1, 1),
+AC97_SINGLE("Headphone Right Switch", AC97_HEADPHONE, 7, 1, 1),
+AC97_SINGLE("Out3/4 Right Switch", AC97_MASTER_MONO, 7, 1, 1),
+
+AC97_SINGLE("Mono In to Headphone Switch", AC97_MASTER_TONE, 15, 1, 1),
+AC97_SINGLE("Mono In to Master Switch", AC97_MASTER_TONE, 14, 1, 1),
+AC97_SINGLE("Mono In Volume", AC97_MASTER_TONE, 8, 31, 1),
+AC97_SINGLE("Mono Switch", AC97_MASTER_TONE, 7, 1, 1),
+AC97_SINGLE("Mono ZC Switch", AC97_MASTER_TONE, 6, 1, 0),
+AC97_SINGLE("Mono Volume", AC97_MASTER_TONE, 0, 31, 1),
+
+AC97_SINGLE("Beep to Headphone Switch", AC97_AUX, 15, 1, 1),
+AC97_SINGLE("Beep to Headphone Volume", AC97_AUX, 12, 7, 1),
+AC97_SINGLE("Beep to Master Switch", AC97_AUX, 11, 1, 1),
+AC97_SINGLE("Beep to Master Volume", AC97_AUX, 8, 7, 1),
+AC97_SINGLE("Beep to Mono Switch", AC97_AUX, 7, 1, 1),
+AC97_SINGLE("Beep to Mono Volume", AC97_AUX, 4, 7, 1),
+
+AC97_SINGLE("Voice to Headphone Switch", AC97_PCM, 15, 1, 1),
+AC97_SINGLE("Voice to Headphone Volume", AC97_PCM, 12, 7, 1),
+AC97_SINGLE("Voice to Master Switch", AC97_PCM, 11, 1, 1),
+AC97_SINGLE("Voice to Master Volume", AC97_PCM, 8, 7, 1),
+AC97_SINGLE("Voice to Mono Switch", AC97_PCM, 7, 1, 1),
+AC97_SINGLE("Voice to Mono Volume", AC97_PCM, 4, 7, 1),
+
+AC97_SINGLE("Aux to Headphone Switch", AC97_REC_SEL, 15, 1, 1),
+AC97_SINGLE("Aux to Headphone Volume", AC97_REC_SEL, 12, 7, 1),
+AC97_SINGLE("Aux to Master Switch", AC97_REC_SEL, 11, 1, 1),
+AC97_SINGLE("Aux to Master Volume", AC97_REC_SEL, 8, 7, 1),
+AC97_SINGLE("Aux to Mono Switch", AC97_REC_SEL, 7, 1, 1),
+AC97_SINGLE("Aux to Mono Volume", AC97_REC_SEL, 4, 7, 1),
+
+AC97_ENUM("Mono Input Mux", wm9713_enum[6]),
+AC97_ENUM("Master Input Mux", wm9713_enum[7]),
+AC97_ENUM("Headphone Input Mux", wm9713_enum[8]),
+AC97_ENUM("Out 3 Input Mux", wm9713_enum[9]),
+AC97_ENUM("Out 4 Input Mux", wm9713_enum[10]),
+
+AC97_ENUM("Bass Control", wm9713_enum[12]),
+AC97_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1),
+AC97_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1),
+AC97_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0),
+AC97_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1),
+AC97_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1),
+};
+
+static const struct snd_kcontrol_new wm13_snd_ac97_controls_3d[] = {
+AC97_ENUM("Inv Input Mux", wm9713_enum[11]),
+AC97_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0),
+AC97_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0),
+AC97_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1),
+};
+
+static int patch_wolfson_wm9713_3d (struct snd_ac97 * ac97)
+{
+	int err, i;
+    
+	for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_3d); i++) {
+		if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_3d[i], ac97))) < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int patch_wolfson_wm9713_specific(struct snd_ac97 * ac97)
+{
+	int err, i;
+	
+	for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls); i++) {
+		if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls[i], ac97))) < 0)
+			return err;
+	}
+	snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x0808);
+	snd_ac97_write_cache(ac97, AC97_PHONE, 0x0808);
+	snd_ac97_write_cache(ac97, AC97_MIC, 0x0808);
+	snd_ac97_write_cache(ac97, AC97_LINE, 0x00da);
+	snd_ac97_write_cache(ac97, AC97_CD, 0x0808);
+	snd_ac97_write_cache(ac97, AC97_VIDEO, 0xd612);
+	snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x1ba0);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static void patch_wolfson_wm9713_suspend (struct snd_ac97 * ac97)
+{
+	snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xfeff);
+	snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0xffff);
+}
+
+static void patch_wolfson_wm9713_resume (struct snd_ac97 * ac97)
+{
+	snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xda00);
+	snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0x3810);
+	snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0x0);
+}
+#endif
+
+static struct snd_ac97_build_ops patch_wolfson_wm9713_ops = {
+	.build_specific = patch_wolfson_wm9713_specific,
+	.build_3d = patch_wolfson_wm9713_3d,
+#ifdef CONFIG_PM	
+	.suspend = patch_wolfson_wm9713_suspend,
+	.resume = patch_wolfson_wm9713_resume
+#endif
+};
+
+static int patch_wolfson13(struct snd_ac97 * ac97)
+{
+	/* WM9713, WM9714 */
+	ac97->build_ops = &patch_wolfson_wm9713_ops;
+
+	ac97->flags |= AC97_HAS_NO_REC_GAIN | AC97_STEREO_MUTES | AC97_HAS_NO_PHONE |
+		AC97_HAS_NO_PC_BEEP | AC97_HAS_NO_VIDEO | AC97_HAS_NO_CD | AC97_HAS_NO_TONE |
+		AC97_HAS_NO_STD_PCM;
+    	ac97->scaps &= ~AC97_SCAP_MODEM;
+
+	snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xda00);
+	snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0x3810);
+	snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0x0);
+
+	return 0;
+}
+
+/*
+ * Tritech codec
+ */
+static int patch_tritech_tr28028(struct snd_ac97 * ac97)
+{
+	snd_ac97_write_cache(ac97, 0x26, 0x0300);
+	snd_ac97_write_cache(ac97, 0x26, 0x0000);
+	snd_ac97_write_cache(ac97, AC97_SURROUND_MASTER, 0x0000);
+	snd_ac97_write_cache(ac97, AC97_SPDIF, 0x0000);
+	return 0;
+}
+
+/*
+ * Sigmatel STAC97xx codecs
+ */
+static int patch_sigmatel_stac9700_3d(struct snd_ac97 * ac97)
+{
+	struct snd_kcontrol *kctl;
+	int err;
+
+	if ((err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+		return err;
+	strcpy(kctl->id.name, "3D Control Sigmatel - Depth");
+	kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 2, 3, 0);
+	snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000);
+	return 0;
+}
+
+static int patch_sigmatel_stac9708_3d(struct snd_ac97 * ac97)
+{
+	struct snd_kcontrol *kctl;
+	int err;
+
+	if ((err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+		return err;
+	strcpy(kctl->id.name, "3D Control Sigmatel - Depth");
+	kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 0, 3, 0);
+	if ((err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+		return err;
+	strcpy(kctl->id.name, "3D Control Sigmatel - Rear Depth");
+	kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 2, 3, 0);
+	snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000);
+	return 0;
+}
+
+static const struct snd_kcontrol_new snd_ac97_sigmatel_4speaker =
+AC97_SINGLE("Sigmatel 4-Speaker Stereo Playback Switch",
+		AC97_SIGMATEL_DAC2INVERT, 2, 1, 0);
+
+/* "Sigmatel " removed due to excessive name length: */
+static const struct snd_kcontrol_new snd_ac97_sigmatel_phaseinvert =
+AC97_SINGLE("Surround Phase Inversion Playback Switch",
+		AC97_SIGMATEL_DAC2INVERT, 3, 1, 0);
+
+static const struct snd_kcontrol_new snd_ac97_sigmatel_controls[] = {
+AC97_SINGLE("Sigmatel DAC 6dB Attenuate", AC97_SIGMATEL_ANALOG, 1, 1, 0),
+AC97_SINGLE("Sigmatel ADC 6dB Attenuate", AC97_SIGMATEL_ANALOG, 0, 1, 0)
+};
+
+static int patch_sigmatel_stac97xx_specific(struct snd_ac97 * ac97)
+{
+	int err;
+
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_ANALOG, snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG) & ~0x0003);
+	if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_ANALOG, 1))
+		if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_controls[0], 1)) < 0)
+			return err;
+	if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_ANALOG, 0))
+		if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_controls[1], 1)) < 0)
+			return err;
+	if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_DAC2INVERT, 2))
+		if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_4speaker, 1)) < 0)
+			return err;
+	if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_DAC2INVERT, 3))
+		if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_phaseinvert, 1)) < 0)
+			return err;
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_sigmatel_stac9700_ops = {
+	.build_3d	= patch_sigmatel_stac9700_3d,
+	.build_specific	= patch_sigmatel_stac97xx_specific
+};
+
+static int patch_sigmatel_stac9700(struct snd_ac97 * ac97)
+{
+	ac97->build_ops = &patch_sigmatel_stac9700_ops;
+	return 0;
+}
+
+static int snd_ac97_stac9708_put_bias(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int err;
+
+	mutex_lock(&ac97->page_mutex);
+	snd_ac97_write(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
+	err = snd_ac97_update_bits(ac97, AC97_SIGMATEL_BIAS2, 0x0010,
+				   (ucontrol->value.integer.value[0] & 1) << 4);
+	snd_ac97_write(ac97, AC97_SIGMATEL_BIAS1, 0);
+	mutex_unlock(&ac97->page_mutex);
+	return err;
+}
+
+static const struct snd_kcontrol_new snd_ac97_stac9708_bias_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Sigmatel Output Bias Switch",
+	.info = snd_ac97_info_volsw,
+	.get = snd_ac97_get_volsw,
+	.put = snd_ac97_stac9708_put_bias,
+	.private_value = AC97_SINGLE_VALUE(AC97_SIGMATEL_BIAS2, 4, 1, 0),
+};
+
+static int patch_sigmatel_stac9708_specific(struct snd_ac97 *ac97)
+{
+	int err;
+
+	/* the register bit is writable, but the function is not implemented: */
+	snd_ac97_remove_ctl(ac97, "PCM Out Path & Mute", NULL);
+
+	snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Sigmatel Surround Playback");
+	if ((err = patch_build_controls(ac97, &snd_ac97_stac9708_bias_control, 1)) < 0)
+		return err;
+	return patch_sigmatel_stac97xx_specific(ac97);
+}
+
+static struct snd_ac97_build_ops patch_sigmatel_stac9708_ops = {
+	.build_3d	= patch_sigmatel_stac9708_3d,
+	.build_specific	= patch_sigmatel_stac9708_specific
+};
+
+static int patch_sigmatel_stac9708(struct snd_ac97 * ac97)
+{
+	unsigned int codec72, codec6c;
+
+	ac97->build_ops = &patch_sigmatel_stac9708_ops;
+	ac97->caps |= 0x10;	/* HP (sigmatel surround) support */
+
+	codec72 = snd_ac97_read(ac97, AC97_SIGMATEL_BIAS2) & 0x8000;
+	codec6c = snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG);
+
+	if ((codec72==0) && (codec6c==0)) {
+		snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
+		snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x1000);
+		snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
+		snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0007);
+	} else if ((codec72==0x8000) && (codec6c==0)) {
+		snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
+		snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x1001);
+		snd_ac97_write_cache(ac97, AC97_SIGMATEL_DAC2INVERT, 0x0008);
+	} else if ((codec72==0x8000) && (codec6c==0x0080)) {
+		/* nothing */
+	}
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
+	return 0;
+}
+
+static int patch_sigmatel_stac9721(struct snd_ac97 * ac97)
+{
+	ac97->build_ops = &patch_sigmatel_stac9700_ops;
+	if (snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG) == 0) {
+		// patch for SigmaTel
+		snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
+		snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x4000);
+		snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
+		snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002);
+	}
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
+	return 0;
+}
+
+static int patch_sigmatel_stac9744(struct snd_ac97 * ac97)
+{
+	// patch for SigmaTel
+	ac97->build_ops = &patch_sigmatel_stac9700_ops;
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x0000);	/* is this correct? --jk */
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002);
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
+	return 0;
+}
+
+static int patch_sigmatel_stac9756(struct snd_ac97 * ac97)
+{
+	// patch for SigmaTel
+	ac97->build_ops = &patch_sigmatel_stac9700_ops;
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x0000);	/* is this correct? --jk */
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002);
+	snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
+	return 0;
+}
+
+static int snd_ac97_stac9758_output_jack_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[5] = { "Input/Disabled", "Front Output",
+		"Rear Output", "Center/LFE Output", "Mixer Output" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 5;
+	if (uinfo->value.enumerated.item > 4)
+		uinfo->value.enumerated.item = 4;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ac97_stac9758_output_jack_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int shift = kcontrol->private_value;
+	unsigned short val;
+
+	val = ac97->regs[AC97_SIGMATEL_OUTSEL] >> shift;
+	if (!(val & 4))
+		ucontrol->value.enumerated.item[0] = 0;
+	else
+		ucontrol->value.enumerated.item[0] = 1 + (val & 3);
+	return 0;
+}
+
+static int snd_ac97_stac9758_output_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int shift = kcontrol->private_value;
+	unsigned short val;
+
+	if (ucontrol->value.enumerated.item[0] > 4)
+		return -EINVAL;
+	if (ucontrol->value.enumerated.item[0] == 0)
+		val = 0;
+	else
+		val = 4 | (ucontrol->value.enumerated.item[0] - 1);
+	return ac97_update_bits_page(ac97, AC97_SIGMATEL_OUTSEL,
+				     7 << shift, val << shift, 0);
+}
+
+static int snd_ac97_stac9758_input_jack_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[7] = { "Mic2 Jack", "Mic1 Jack", "Line In Jack",
+		"Front Jack", "Rear Jack", "Center/LFE Jack", "Mute" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 7;
+	if (uinfo->value.enumerated.item > 6)
+		uinfo->value.enumerated.item = 6;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ac97_stac9758_input_jack_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int shift = kcontrol->private_value;
+	unsigned short val;
+
+	val = ac97->regs[AC97_SIGMATEL_INSEL];
+	ucontrol->value.enumerated.item[0] = (val >> shift) & 7;
+	return 0;
+}
+
+static int snd_ac97_stac9758_input_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int shift = kcontrol->private_value;
+
+	return ac97_update_bits_page(ac97, AC97_SIGMATEL_INSEL, 7 << shift,
+				     ucontrol->value.enumerated.item[0] << shift, 0);
+}
+
+static int snd_ac97_stac9758_phonesel_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[3] = { "None", "Front Jack", "Rear Jack" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ac97_stac9758_phonesel_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = ac97->regs[AC97_SIGMATEL_IOMISC] & 3;
+	return 0;
+}
+
+static int snd_ac97_stac9758_phonesel_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+	return ac97_update_bits_page(ac97, AC97_SIGMATEL_IOMISC, 3,
+				     ucontrol->value.enumerated.item[0], 0);
+}
+
+#define STAC9758_OUTPUT_JACK(xname, shift) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = snd_ac97_stac9758_output_jack_info, \
+	.get = snd_ac97_stac9758_output_jack_get, \
+	.put = snd_ac97_stac9758_output_jack_put, \
+	.private_value = shift }
+#define STAC9758_INPUT_JACK(xname, shift) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = snd_ac97_stac9758_input_jack_info, \
+	.get = snd_ac97_stac9758_input_jack_get, \
+	.put = snd_ac97_stac9758_input_jack_put, \
+	.private_value = shift }
+static const struct snd_kcontrol_new snd_ac97_sigmatel_stac9758_controls[] = {
+	STAC9758_OUTPUT_JACK("Mic1 Jack", 1),
+	STAC9758_OUTPUT_JACK("LineIn Jack", 4),
+	STAC9758_OUTPUT_JACK("Front Jack", 7),
+	STAC9758_OUTPUT_JACK("Rear Jack", 10),
+	STAC9758_OUTPUT_JACK("Center/LFE Jack", 13),
+	STAC9758_INPUT_JACK("Mic Input Source", 0),
+	STAC9758_INPUT_JACK("Line Input Source", 8),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphone Amp",
+		.info = snd_ac97_stac9758_phonesel_info,
+		.get = snd_ac97_stac9758_phonesel_get,
+		.put = snd_ac97_stac9758_phonesel_put
+	},
+	AC97_SINGLE("Exchange Center/LFE", AC97_SIGMATEL_IOMISC, 4, 1, 0),
+	AC97_SINGLE("Headphone +3dB Boost", AC97_SIGMATEL_IOMISC, 8, 1, 0)
+};
+
+static int patch_sigmatel_stac9758_specific(struct snd_ac97 *ac97)
+{
+	int err;
+
+	err = patch_sigmatel_stac97xx_specific(ac97);
+	if (err < 0)
+		return err;
+	err = patch_build_controls(ac97, snd_ac97_sigmatel_stac9758_controls,
+				   ARRAY_SIZE(snd_ac97_sigmatel_stac9758_controls));
+	if (err < 0)
+		return err;
+	/* DAC-A direct */
+	snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Front Playback");
+	/* DAC-A to Mix = PCM */
+	/* DAC-B direct = Surround */
+	/* DAC-B to Mix */
+	snd_ac97_rename_vol_ctl(ac97, "Video Playback", "Surround Mix Playback");
+	/* DAC-C direct = Center/LFE */
+
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_sigmatel_stac9758_ops = {
+	.build_3d	= patch_sigmatel_stac9700_3d,
+	.build_specific	= patch_sigmatel_stac9758_specific
+};
+
+static int patch_sigmatel_stac9758(struct snd_ac97 * ac97)
+{
+	static unsigned short regs[4] = {
+		AC97_SIGMATEL_OUTSEL,
+		AC97_SIGMATEL_IOMISC,
+		AC97_SIGMATEL_INSEL,
+		AC97_SIGMATEL_VARIOUS
+	};
+	static unsigned short def_regs[4] = {
+		/* OUTSEL */ 0xd794, /* CL:CL, SR:SR, LO:MX, LI:DS, MI:DS */
+		/* IOMISC */ 0x2001,
+		/* INSEL */ 0x0201, /* LI:LI, MI:M1 */
+		/* VARIOUS */ 0x0040
+	};
+	static unsigned short m675_regs[4] = {
+		/* OUTSEL */ 0xfc70, /* CL:MX, SR:MX, LO:DS, LI:MX, MI:DS */
+		/* IOMISC */ 0x2102, /* HP amp on */
+		/* INSEL */ 0x0203, /* LI:LI, MI:FR */
+		/* VARIOUS */ 0x0041 /* stereo mic */
+	};
+	unsigned short *pregs = def_regs;
+	int i;
+
+	/* Gateway M675 notebook */
+	if (ac97->pci && 
+	    ac97->subsystem_vendor == 0x107b &&
+	    ac97->subsystem_device == 0x0601)
+	    	pregs = m675_regs;
+
+	// patch for SigmaTel
+	ac97->build_ops = &patch_sigmatel_stac9758_ops;
+	/* FIXME: assume only page 0 for writing cache */
+	snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, AC97_PAGE_VENDOR);
+	for (i = 0; i < 4; i++)
+		snd_ac97_write_cache(ac97, regs[i], pregs[i]);
+
+	ac97->flags |= AC97_STEREO_MUTES;
+	return 0;
+}
+
+/*
+ * Cirrus Logic CS42xx codecs
+ */
+static const struct snd_kcontrol_new snd_ac97_cirrus_controls_spdif[2] = {
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), AC97_CSR_SPDIF, 15, 1, 0),
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "AC97-SPSA", AC97_CSR_ACMODE, 0, 3, 0)
+};
+
+static int patch_cirrus_build_spdif(struct snd_ac97 * ac97)
+{
+	int err;
+
+	/* con mask, pro mask, default */
+	if ((err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3)) < 0)
+		return err;
+	/* switch, spsa */
+	if ((err = patch_build_controls(ac97, &snd_ac97_cirrus_controls_spdif[0], 1)) < 0)
+		return err;
+	switch (ac97->id & AC97_ID_CS_MASK) {
+	case AC97_ID_CS4205:
+		if ((err = patch_build_controls(ac97, &snd_ac97_cirrus_controls_spdif[1], 1)) < 0)
+			return err;
+		break;
+	}
+	/* set default PCM S/PDIF params */
+	/* consumer,PCM audio,no copyright,no preemphasis,PCM coder,original,48000Hz */
+	snd_ac97_write_cache(ac97, AC97_CSR_SPDIF, 0x0a20);
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_cirrus_ops = {
+	.build_spdif = patch_cirrus_build_spdif
+};
+
+static int patch_cirrus_spdif(struct snd_ac97 * ac97)
+{
+	/* Basically, the cs4201/cs4205/cs4297a has non-standard sp/dif registers.
+	   WHY CAN'T ANYONE FOLLOW THE BLOODY SPEC?  *sigh*
+	   - sp/dif EA ID is not set, but sp/dif is always present.
+	   - enable/disable is spdif register bit 15.
+	   - sp/dif control register is 0x68.  differs from AC97:
+	   - valid is bit 14 (vs 15)
+	   - no DRS
+	   - only 44.1/48k [00 = 48, 01=44,1] (AC97 is 00=44.1, 10=48)
+	   - sp/dif ssource select is in 0x5e bits 0,1.
+	*/
+
+	ac97->build_ops = &patch_cirrus_ops;
+	ac97->flags |= AC97_CS_SPDIF; 
+	ac97->rates[AC97_RATES_SPDIF] &= ~SNDRV_PCM_RATE_32000;
+        ac97->ext_id |= AC97_EI_SPDIF;	/* force the detection of spdif */
+	snd_ac97_write_cache(ac97, AC97_CSR_ACMODE, 0x0080);
+	return 0;
+}
+
+static int patch_cirrus_cs4299(struct snd_ac97 * ac97)
+{
+	/* force the detection of PC Beep */
+	ac97->flags |= AC97_HAS_PC_BEEP;
+	
+	return patch_cirrus_spdif(ac97);
+}
+
+/*
+ * Conexant codecs
+ */
+static const struct snd_kcontrol_new snd_ac97_conexant_controls_spdif[1] = {
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), AC97_CXR_AUDIO_MISC, 3, 1, 0),
+};
+
+static int patch_conexant_build_spdif(struct snd_ac97 * ac97)
+{
+	int err;
+
+	/* con mask, pro mask, default */
+	if ((err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3)) < 0)
+		return err;
+	/* switch */
+	if ((err = patch_build_controls(ac97, &snd_ac97_conexant_controls_spdif[0], 1)) < 0)
+		return err;
+	/* set default PCM S/PDIF params */
+	/* consumer,PCM audio,no copyright,no preemphasis,PCM coder,original,48000Hz */
+	snd_ac97_write_cache(ac97, AC97_CXR_AUDIO_MISC,
+			     snd_ac97_read(ac97, AC97_CXR_AUDIO_MISC) & ~(AC97_CXR_SPDIFEN|AC97_CXR_COPYRGT|AC97_CXR_SPDIF_MASK));
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_conexant_ops = {
+	.build_spdif = patch_conexant_build_spdif
+};
+
+static int patch_conexant(struct snd_ac97 * ac97)
+{
+	ac97->build_ops = &patch_conexant_ops;
+	ac97->flags |= AC97_CX_SPDIF;
+        ac97->ext_id |= AC97_EI_SPDIF;	/* force the detection of spdif */
+	ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */
+	return 0;
+}
+
+static int patch_cx20551(struct snd_ac97 *ac97)
+{
+	snd_ac97_update_bits(ac97, 0x5c, 0x01, 0x01);
+	return 0;
+}
+
+/*
+ * Analog Device AD18xx, AD19xx codecs
+ */
+#ifdef CONFIG_PM
+static void ad18xx_resume(struct snd_ac97 *ac97)
+{
+	static unsigned short setup_regs[] = {
+		AC97_AD_MISC, AC97_AD_SERIAL_CFG, AC97_AD_JACK_SPDIF,
+	};
+	int i, codec;
+
+	for (i = 0; i < (int)ARRAY_SIZE(setup_regs); i++) {
+		unsigned short reg = setup_regs[i];
+		if (test_bit(reg, ac97->reg_accessed)) {
+			snd_ac97_write(ac97, reg, ac97->regs[reg]);
+			snd_ac97_read(ac97, reg);
+		}
+	}
+
+	if (! (ac97->flags & AC97_AD_MULTI))
+		/* normal restore */
+		snd_ac97_restore_status(ac97);
+	else {
+		/* restore the AD18xx codec configurations */
+		for (codec = 0; codec < 3; codec++) {
+			if (! ac97->spec.ad18xx.id[codec])
+				continue;
+			/* select single codec */
+			snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
+					     ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]);
+			ac97->bus->ops->write(ac97, AC97_AD_CODEC_CFG, ac97->spec.ad18xx.codec_cfg[codec]);
+		}
+		/* select all codecs */
+		snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
+
+		/* restore status */
+		for (i = 2; i < 0x7c ; i += 2) {
+			if (i == AC97_POWERDOWN || i == AC97_EXTENDED_ID)
+				continue;
+			if (test_bit(i, ac97->reg_accessed)) {
+				/* handle multi codecs for AD18xx */
+				if (i == AC97_PCM) {
+					for (codec = 0; codec < 3; codec++) {
+						if (! ac97->spec.ad18xx.id[codec])
+							continue;
+						/* select single codec */
+						snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
+								     ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]);
+						/* update PCM bits */
+						ac97->bus->ops->write(ac97, AC97_PCM, ac97->spec.ad18xx.pcmreg[codec]);
+					}
+					/* select all codecs */
+					snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
+					continue;
+				} else if (i == AC97_AD_TEST ||
+					   i == AC97_AD_CODEC_CFG ||
+					   i == AC97_AD_SERIAL_CFG)
+					continue; /* ignore */
+			}
+			snd_ac97_write(ac97, i, ac97->regs[i]);
+			snd_ac97_read(ac97, i);
+		}
+	}
+
+	snd_ac97_restore_iec958(ac97);
+}
+
+static void ad1888_resume(struct snd_ac97 *ac97)
+{
+	ad18xx_resume(ac97);
+	snd_ac97_write_cache(ac97, AC97_CODEC_CLASS_REV, 0x8080);
+}
+
+#endif
+
+static const struct snd_ac97_res_table ad1819_restbl[] = {
+	{ AC97_PHONE, 0x9f1f },
+	{ AC97_MIC, 0x9f1f },
+	{ AC97_LINE, 0x9f1f },
+	{ AC97_CD, 0x9f1f },
+	{ AC97_VIDEO, 0x9f1f },
+	{ AC97_AUX, 0x9f1f },
+	{ AC97_PCM, 0x9f1f },
+	{ } /* terminator */
+};
+
+static int patch_ad1819(struct snd_ac97 * ac97)
+{
+	unsigned short scfg;
+
+	// patch for Analog Devices
+	scfg = snd_ac97_read(ac97, AC97_AD_SERIAL_CFG);
+	snd_ac97_write_cache(ac97, AC97_AD_SERIAL_CFG, scfg | 0x7000); /* select all codecs */
+	ac97->res_table = ad1819_restbl;
+	return 0;
+}
+
+static unsigned short patch_ad1881_unchained(struct snd_ac97 * ac97, int idx, unsigned short mask)
+{
+	unsigned short val;
+
+	// test for unchained codec
+	snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, mask);
+	snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0000);	/* ID0C, ID1C, SDIE = off */
+	val = snd_ac97_read(ac97, AC97_VENDOR_ID2);
+	if ((val & 0xff40) != 0x5340)
+		return 0;
+	ac97->spec.ad18xx.unchained[idx] = mask;
+	ac97->spec.ad18xx.id[idx] = val;
+	ac97->spec.ad18xx.codec_cfg[idx] = 0x0000;
+	return mask;
+}
+
+static int patch_ad1881_chained1(struct snd_ac97 * ac97, int idx, unsigned short codec_bits)
+{
+	static int cfg_bits[3] = { 1<<12, 1<<14, 1<<13 };
+	unsigned short val;
+	
+	snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, cfg_bits[idx]);
+	snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0004);	// SDIE
+	val = snd_ac97_read(ac97, AC97_VENDOR_ID2);
+	if ((val & 0xff40) != 0x5340)
+		return 0;
+	if (codec_bits)
+		snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, codec_bits);
+	ac97->spec.ad18xx.chained[idx] = cfg_bits[idx];
+	ac97->spec.ad18xx.id[idx] = val;
+	ac97->spec.ad18xx.codec_cfg[idx] = codec_bits ? codec_bits : 0x0004;
+	return 1;
+}
+
+static void patch_ad1881_chained(struct snd_ac97 * ac97, int unchained_idx, int cidx1, int cidx2)
+{
+	// already detected?
+	if (ac97->spec.ad18xx.unchained[cidx1] || ac97->spec.ad18xx.chained[cidx1])
+		cidx1 = -1;
+	if (ac97->spec.ad18xx.unchained[cidx2] || ac97->spec.ad18xx.chained[cidx2])
+		cidx2 = -1;
+	if (cidx1 < 0 && cidx2 < 0)
+		return;
+	// test for chained codecs
+	snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
+			     ac97->spec.ad18xx.unchained[unchained_idx]);
+	snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0002);		// ID1C
+	ac97->spec.ad18xx.codec_cfg[unchained_idx] = 0x0002;
+	if (cidx1 >= 0) {
+		if (cidx2 < 0)
+			patch_ad1881_chained1(ac97, cidx1, 0);
+		else if (patch_ad1881_chained1(ac97, cidx1, 0x0006))	// SDIE | ID1C
+			patch_ad1881_chained1(ac97, cidx2, 0);
+		else if (patch_ad1881_chained1(ac97, cidx2, 0x0006))	// SDIE | ID1C
+			patch_ad1881_chained1(ac97, cidx1, 0);
+	} else if (cidx2 >= 0) {
+		patch_ad1881_chained1(ac97, cidx2, 0);
+	}
+}
+
+static struct snd_ac97_build_ops patch_ad1881_build_ops = {
+#ifdef CONFIG_PM
+	.resume = ad18xx_resume
+#endif
+};
+
+static int patch_ad1881(struct snd_ac97 * ac97)
+{
+	static const char cfg_idxs[3][2] = {
+		{2, 1},
+		{0, 2},
+		{0, 1}
+	};
+	
+	// patch for Analog Devices
+	unsigned short codecs[3];
+	unsigned short val;
+	int idx, num;
+
+	val = snd_ac97_read(ac97, AC97_AD_SERIAL_CFG);
+	snd_ac97_write_cache(ac97, AC97_AD_SERIAL_CFG, val);
+	codecs[0] = patch_ad1881_unchained(ac97, 0, (1<<12));
+	codecs[1] = patch_ad1881_unchained(ac97, 1, (1<<14));
+	codecs[2] = patch_ad1881_unchained(ac97, 2, (1<<13));
+
+	if (! (codecs[0] || codecs[1] || codecs[2]))
+		goto __end;
+
+	for (idx = 0; idx < 3; idx++)
+		if (ac97->spec.ad18xx.unchained[idx])
+			patch_ad1881_chained(ac97, idx, cfg_idxs[idx][0], cfg_idxs[idx][1]);
+
+	if (ac97->spec.ad18xx.id[1]) {
+		ac97->flags |= AC97_AD_MULTI;
+		ac97->scaps |= AC97_SCAP_SURROUND_DAC;
+	}
+	if (ac97->spec.ad18xx.id[2]) {
+		ac97->flags |= AC97_AD_MULTI;
+		ac97->scaps |= AC97_SCAP_CENTER_LFE_DAC;
+	}
+
+      __end:
+	/* select all codecs */
+	snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
+	/* check if only one codec is present */
+	for (idx = num = 0; idx < 3; idx++)
+		if (ac97->spec.ad18xx.id[idx])
+			num++;
+	if (num == 1) {
+		/* ok, deselect all ID bits */
+		snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0000);
+		ac97->spec.ad18xx.codec_cfg[0] = 
+			ac97->spec.ad18xx.codec_cfg[1] = 
+			ac97->spec.ad18xx.codec_cfg[2] = 0x0000;
+	}
+	/* required for AD1886/AD1885 combination */
+	ac97->ext_id = snd_ac97_read(ac97, AC97_EXTENDED_ID);
+	if (ac97->spec.ad18xx.id[0]) {
+		ac97->id &= 0xffff0000;
+		ac97->id |= ac97->spec.ad18xx.id[0];
+	}
+	ac97->build_ops = &patch_ad1881_build_ops;
+	return 0;
+}
+
+static const struct snd_kcontrol_new snd_ac97_controls_ad1885[] = {
+	AC97_SINGLE("Digital Mono Direct", AC97_AD_MISC, 11, 1, 0),
+	/* AC97_SINGLE("Digital Audio Mode", AC97_AD_MISC, 12, 1, 0), */ /* seems problematic */
+	AC97_SINGLE("Low Power Mixer", AC97_AD_MISC, 14, 1, 0),
+	AC97_SINGLE("Zero Fill DAC", AC97_AD_MISC, 15, 1, 0),
+	AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 9, 1, 1), /* inverted */
+	AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 8, 1, 1), /* inverted */
+};
+
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit_6db_max, -8850, 150, 0);
+
+static int patch_ad1885_specific(struct snd_ac97 * ac97)
+{
+	int err;
+
+	if ((err = patch_build_controls(ac97, snd_ac97_controls_ad1885, ARRAY_SIZE(snd_ac97_controls_ad1885))) < 0)
+		return err;
+	reset_tlv(ac97, "Headphone Playback Volume",
+		  db_scale_6bit_6db_max);
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_ad1885_build_ops = {
+	.build_specific = &patch_ad1885_specific,
+#ifdef CONFIG_PM
+	.resume = ad18xx_resume
+#endif
+};
+
+static int patch_ad1885(struct snd_ac97 * ac97)
+{
+	patch_ad1881(ac97);
+	/* This is required to deal with the Intel D815EEAL2 */
+	/* i.e. Line out is actually headphone out from codec */
+
+	/* set default */
+	snd_ac97_write_cache(ac97, AC97_AD_MISC, 0x0404);
+
+	ac97->build_ops = &patch_ad1885_build_ops;
+	return 0;
+}
+
+static int patch_ad1886_specific(struct snd_ac97 * ac97)
+{
+	reset_tlv(ac97, "Headphone Playback Volume",
+		  db_scale_6bit_6db_max);
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_ad1886_build_ops = {
+	.build_specific = &patch_ad1886_specific,
+#ifdef CONFIG_PM
+	.resume = ad18xx_resume
+#endif
+};
+
+static int patch_ad1886(struct snd_ac97 * ac97)
+{
+	patch_ad1881(ac97);
+	/* Presario700 workaround */
+	/* for Jack Sense/SPDIF Register misetting causing */
+	snd_ac97_write_cache(ac97, AC97_AD_JACK_SPDIF, 0x0010);
+	ac97->build_ops = &patch_ad1886_build_ops;
+	return 0;
+}
+
+/* MISC bits (AD1888/AD1980/AD1985 register 0x76) */
+#define AC97_AD198X_MBC		0x0003	/* mic boost */
+#define AC97_AD198X_MBC_20	0x0000	/* +20dB */
+#define AC97_AD198X_MBC_10	0x0001	/* +10dB */
+#define AC97_AD198X_MBC_30	0x0002	/* +30dB */
+#define AC97_AD198X_VREFD	0x0004	/* VREF high-Z */
+#define AC97_AD198X_VREFH	0x0008	/* 0=2.25V, 1=3.7V */
+#define AC97_AD198X_VREF_0	0x000c	/* 0V (AD1985 only) */
+#define AC97_AD198X_VREF_MASK	(AC97_AD198X_VREFH | AC97_AD198X_VREFD)
+#define AC97_AD198X_VREF_SHIFT	2
+#define AC97_AD198X_SRU		0x0010	/* sample rate unlock */
+#define AC97_AD198X_LOSEL	0x0020	/* LINE_OUT amplifiers input select */
+#define AC97_AD198X_2MIC	0x0040	/* 2-channel mic select */
+#define AC97_AD198X_SPRD	0x0080	/* SPREAD enable */
+#define AC97_AD198X_DMIX0	0x0100	/* downmix mode: */
+					/*  0 = 6-to-4, 1 = 6-to-2 downmix */
+#define AC97_AD198X_DMIX1	0x0200	/* downmix mode: 1 = enabled */
+#define AC97_AD198X_HPSEL	0x0400	/* headphone amplifier input select */
+#define AC97_AD198X_CLDIS	0x0800	/* center/lfe disable */
+#define AC97_AD198X_LODIS	0x1000	/* LINE_OUT disable */
+#define AC97_AD198X_MSPLT	0x2000	/* mute split */
+#define AC97_AD198X_AC97NC	0x4000	/* AC97 no compatible mode */
+#define AC97_AD198X_DACZ	0x8000	/* DAC zero-fill mode */
+
+/* MISC 1 bits (AD1986 register 0x76) */
+#define AC97_AD1986_MBC		0x0003	/* mic boost */
+#define AC97_AD1986_MBC_20	0x0000	/* +20dB */
+#define AC97_AD1986_MBC_10	0x0001	/* +10dB */
+#define AC97_AD1986_MBC_30	0x0002	/* +30dB */
+#define AC97_AD1986_LISEL0	0x0004	/* LINE_IN select bit 0 */
+#define AC97_AD1986_LISEL1	0x0008	/* LINE_IN select bit 1 */
+#define AC97_AD1986_LISEL_MASK	(AC97_AD1986_LISEL1 | AC97_AD1986_LISEL0)
+#define AC97_AD1986_LISEL_LI	0x0000  /* LINE_IN pins as LINE_IN source */
+#define AC97_AD1986_LISEL_SURR	0x0004  /* SURROUND pins as LINE_IN source */
+#define AC97_AD1986_LISEL_MIC	0x0008  /* MIC_1/2 pins as LINE_IN source */
+#define AC97_AD1986_SRU		0x0010	/* sample rate unlock */
+#define AC97_AD1986_SOSEL	0x0020	/* SURROUND_OUT amplifiers input sel */
+#define AC97_AD1986_2MIC	0x0040	/* 2-channel mic select */
+#define AC97_AD1986_SPRD	0x0080	/* SPREAD enable */
+#define AC97_AD1986_DMIX0	0x0100	/* downmix mode: */
+					/*  0 = 6-to-4, 1 = 6-to-2 downmix */
+#define AC97_AD1986_DMIX1	0x0200	/* downmix mode: 1 = enabled */
+#define AC97_AD1986_CLDIS	0x0800	/* center/lfe disable */
+#define AC97_AD1986_SODIS	0x1000	/* SURROUND_OUT disable */
+#define AC97_AD1986_MSPLT	0x2000	/* mute split (read only 1) */
+#define AC97_AD1986_AC97NC	0x4000	/* AC97 no compatible mode (r/o 1) */
+#define AC97_AD1986_DACZ	0x8000	/* DAC zero-fill mode */
+
+/* MISC 2 bits (AD1986 register 0x70) */
+#define AC97_AD_MISC2		0x70	/* Misc Control Bits 2 (AD1986) */
+
+#define AC97_AD1986_CVREF0	0x0004	/* C/LFE VREF_OUT 2.25V */
+#define AC97_AD1986_CVREF1	0x0008	/* C/LFE VREF_OUT 0V */
+#define AC97_AD1986_CVREF2	0x0010	/* C/LFE VREF_OUT 3.7V */
+#define AC97_AD1986_CVREF_MASK \
+	(AC97_AD1986_CVREF2 | AC97_AD1986_CVREF1 | AC97_AD1986_CVREF0)
+#define AC97_AD1986_JSMAP	0x0020	/* Jack Sense Mapping 1 = alternate */
+#define AC97_AD1986_MMDIS	0x0080	/* Mono Mute Disable */
+#define AC97_AD1986_MVREF0	0x0400	/* MIC VREF_OUT 2.25V */
+#define AC97_AD1986_MVREF1	0x0800	/* MIC VREF_OUT 0V */
+#define AC97_AD1986_MVREF2	0x1000	/* MIC VREF_OUT 3.7V */
+#define AC97_AD1986_MVREF_MASK \
+	(AC97_AD1986_MVREF2 | AC97_AD1986_MVREF1 | AC97_AD1986_MVREF0)
+
+/* MISC 3 bits (AD1986 register 0x7a) */
+#define AC97_AD_MISC3		0x7a	/* Misc Control Bits 3 (AD1986) */
+
+#define AC97_AD1986_MMIX	0x0004	/* Mic Mix, left/right */
+#define AC97_AD1986_GPO		0x0008	/* General Purpose Out */
+#define AC97_AD1986_LOHPEN	0x0010	/* LINE_OUT headphone drive */
+#define AC97_AD1986_LVREF0	0x0100	/* LINE_OUT VREF_OUT 2.25V */
+#define AC97_AD1986_LVREF1	0x0200	/* LINE_OUT VREF_OUT 0V */
+#define AC97_AD1986_LVREF2	0x0400	/* LINE_OUT VREF_OUT 3.7V */
+#define AC97_AD1986_LVREF_MASK \
+	(AC97_AD1986_LVREF2 | AC97_AD1986_LVREF1 | AC97_AD1986_LVREF0)
+#define AC97_AD1986_JSINVA	0x0800	/* Jack Sense Invert SENSE_A */
+#define AC97_AD1986_LOSEL	0x1000	/* LINE_OUT amplifiers input select */
+#define AC97_AD1986_HPSEL0	0x2000	/* Headphone amplifiers */
+					/*   input select Surround DACs */
+#define AC97_AD1986_HPSEL1	0x4000	/* Headphone amplifiers input */
+					/*   select C/LFE DACs */
+#define AC97_AD1986_JSINVB	0x8000	/* Jack Sense Invert SENSE_B */
+
+/* Serial Config bits (AD1986 register 0x74) (incomplete) */
+#define AC97_AD1986_OMS0	0x0100	/* Optional Mic Selector bit 0 */
+#define AC97_AD1986_OMS1	0x0200	/* Optional Mic Selector bit 1 */
+#define AC97_AD1986_OMS2	0x0400	/* Optional Mic Selector bit 2 */
+#define AC97_AD1986_OMS_MASK \
+	(AC97_AD1986_OMS2 | AC97_AD1986_OMS1 | AC97_AD1986_OMS0)
+#define AC97_AD1986_OMS_M	0x0000  /* MIC_1/2 pins are MIC sources */
+#define AC97_AD1986_OMS_L	0x0100  /* LINE_IN pins are MIC sources */
+#define AC97_AD1986_OMS_C	0x0200  /* Center/LFE pins are MCI sources */
+#define AC97_AD1986_OMS_MC	0x0400  /* Mix of MIC and C/LFE pins */
+					/*   are MIC sources */
+#define AC97_AD1986_OMS_ML	0x0500  /* MIX of MIC and LINE_IN pins */
+					/*   are MIC sources */
+#define AC97_AD1986_OMS_LC	0x0600  /* MIX of LINE_IN and C/LFE pins */
+					/*   are MIC sources */
+#define AC97_AD1986_OMS_MLC	0x0700  /* MIX of MIC, LINE_IN, C/LFE pins */
+					/*   are MIC sources */
+
+
+static int snd_ac97_ad198x_spdif_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = { "AC-Link", "A/D Converter" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ac97_ad198x_spdif_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	val = ac97->regs[AC97_AD_SERIAL_CFG];
+	ucontrol->value.enumerated.item[0] = (val >> 2) & 1;
+	return 0;
+}
+
+static int snd_ac97_ad198x_spdif_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	if (ucontrol->value.enumerated.item[0] > 1)
+		return -EINVAL;
+	val = ucontrol->value.enumerated.item[0] << 2;
+	return snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x0004, val);
+}
+
+static const struct snd_kcontrol_new snd_ac97_ad198x_spdif_source = {
+	.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name	= SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+	.info	= snd_ac97_ad198x_spdif_source_info,
+	.get	= snd_ac97_ad198x_spdif_source_get,
+	.put	= snd_ac97_ad198x_spdif_source_put,
+};
+
+static int patch_ad198x_post_spdif(struct snd_ac97 * ac97)
+{
+ 	return patch_build_controls(ac97, &snd_ac97_ad198x_spdif_source, 1);
+}
+
+static const struct snd_kcontrol_new snd_ac97_ad1981x_jack_sense[] = {
+	AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 11, 1, 0),
+	AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0),
+};
+
+/* black list to avoid HP/Line jack-sense controls
+ * (SS vendor << 16 | device)
+ */
+static unsigned int ad1981_jacks_blacklist[] = {
+	0x10140523, /* Thinkpad R40 */
+	0x10140534, /* Thinkpad X31 */
+	0x10140537, /* Thinkpad T41p */
+	0x1014053e, /* Thinkpad R40e */
+	0x10140554, /* Thinkpad T42p/R50p */
+	0x10140567, /* Thinkpad T43p 2668-G7U */
+	0x10140581, /* Thinkpad X41-2527 */
+	0x10280160, /* Dell Dimension 2400 */
+	0x104380b0, /* Asus A7V8X-MX */
+	0x11790241, /* Toshiba Satellite A-15 S127 */
+	0x1179ff10, /* Toshiba P500 */
+	0x144dc01a, /* Samsung NP-X20C004/SEG */
+	0 /* end */
+};
+
+static int check_list(struct snd_ac97 *ac97, const unsigned int *list)
+{
+	u32 subid = ((u32)ac97->subsystem_vendor << 16) | ac97->subsystem_device;
+	for (; *list; list++)
+		if (*list == subid)
+			return 1;
+	return 0;
+}
+
+static int patch_ad1981a_specific(struct snd_ac97 * ac97)
+{
+	if (check_list(ac97, ad1981_jacks_blacklist))
+		return 0;
+	return patch_build_controls(ac97, snd_ac97_ad1981x_jack_sense,
+				    ARRAY_SIZE(snd_ac97_ad1981x_jack_sense));
+}
+
+static struct snd_ac97_build_ops patch_ad1981a_build_ops = {
+	.build_post_spdif = patch_ad198x_post_spdif,
+	.build_specific = patch_ad1981a_specific,
+#ifdef CONFIG_PM
+	.resume = ad18xx_resume
+#endif
+};
+
+/* white list to enable HP jack-sense bits
+ * (SS vendor << 16 | device)
+ */
+static unsigned int ad1981_jacks_whitelist[] = {
+	0x0e11005a, /* HP nc4000/4010 */
+	0x103c0890, /* HP nc6000 */
+	0x103c0938, /* HP nc4220 */
+	0x103c099c, /* HP nx6110 */
+	0x103c0944, /* HP nc6220 */
+	0x103c0934, /* HP nc8220 */
+	0x103c006d, /* HP nx9105 */
+	0x17340088, /* FSC Scenic-W */
+	0 /* end */
+};
+
+static void check_ad1981_hp_jack_sense(struct snd_ac97 *ac97)
+{
+	if (check_list(ac97, ad1981_jacks_whitelist))
+		/* enable headphone jack sense */
+		snd_ac97_update_bits(ac97, AC97_AD_JACK_SPDIF, 1<<11, 1<<11);
+}
+
+static int patch_ad1981a(struct snd_ac97 *ac97)
+{
+	patch_ad1881(ac97);
+	ac97->build_ops = &patch_ad1981a_build_ops;
+	snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD198X_MSPLT, AC97_AD198X_MSPLT);
+	ac97->flags |= AC97_STEREO_MUTES;
+	check_ad1981_hp_jack_sense(ac97);
+	return 0;
+}
+
+static const struct snd_kcontrol_new snd_ac97_ad198x_2cmic =
+AC97_SINGLE("Stereo Mic", AC97_AD_MISC, 6, 1, 0);
+
+static int patch_ad1981b_specific(struct snd_ac97 *ac97)
+{
+	int err;
+
+	if ((err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1)) < 0)
+		return err;
+	if (check_list(ac97, ad1981_jacks_blacklist))
+		return 0;
+	return patch_build_controls(ac97, snd_ac97_ad1981x_jack_sense,
+				    ARRAY_SIZE(snd_ac97_ad1981x_jack_sense));
+}
+
+static struct snd_ac97_build_ops patch_ad1981b_build_ops = {
+	.build_post_spdif = patch_ad198x_post_spdif,
+	.build_specific = patch_ad1981b_specific,
+#ifdef CONFIG_PM
+	.resume = ad18xx_resume
+#endif
+};
+
+static int patch_ad1981b(struct snd_ac97 *ac97)
+{
+	patch_ad1881(ac97);
+	ac97->build_ops = &patch_ad1981b_build_ops;
+	snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD198X_MSPLT, AC97_AD198X_MSPLT);
+	ac97->flags |= AC97_STEREO_MUTES;
+	check_ad1981_hp_jack_sense(ac97);
+	return 0;
+}
+
+#define snd_ac97_ad1888_lohpsel_info	snd_ctl_boolean_mono_info
+
+static int snd_ac97_ad1888_lohpsel_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	val = ac97->regs[AC97_AD_MISC];
+	ucontrol->value.integer.value[0] = !(val & AC97_AD198X_LOSEL);
+	if (ac97->spec.ad18xx.lo_as_master)
+		ucontrol->value.integer.value[0] =
+			!ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_ac97_ad1888_lohpsel_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	val = !ucontrol->value.integer.value[0];
+	if (ac97->spec.ad18xx.lo_as_master)
+		val = !val;
+	val = val ? (AC97_AD198X_LOSEL | AC97_AD198X_HPSEL) : 0;
+	return snd_ac97_update_bits(ac97, AC97_AD_MISC,
+				    AC97_AD198X_LOSEL | AC97_AD198X_HPSEL, val);
+}
+
+static int snd_ac97_ad1888_downmix_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[3] = {"Off", "6 -> 4", "6 -> 2"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ac97_ad1888_downmix_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	val = ac97->regs[AC97_AD_MISC];
+	if (!(val & AC97_AD198X_DMIX1))
+		ucontrol->value.enumerated.item[0] = 0;
+	else
+		ucontrol->value.enumerated.item[0] = 1 + ((val >> 8) & 1);
+	return 0;
+}
+
+static int snd_ac97_ad1888_downmix_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	if (ucontrol->value.enumerated.item[0] > 2)
+		return -EINVAL;
+	if (ucontrol->value.enumerated.item[0] == 0)
+		val = 0;
+	else
+		val = AC97_AD198X_DMIX1 |
+			((ucontrol->value.enumerated.item[0] - 1) << 8);
+	return snd_ac97_update_bits(ac97, AC97_AD_MISC,
+				    AC97_AD198X_DMIX0 | AC97_AD198X_DMIX1, val);
+}
+
+static void ad1888_update_jacks(struct snd_ac97 *ac97)
+{
+	unsigned short val = 0;
+	/* clear LODIS if shared jack is to be used for Surround out */
+	if (!ac97->spec.ad18xx.lo_as_master && is_shared_linein(ac97))
+		val |= (1 << 12);
+	/* clear CLDIS if shared jack is to be used for C/LFE out */
+	if (is_shared_micin(ac97))
+		val |= (1 << 11);
+	/* shared Line-In */
+	snd_ac97_update_bits(ac97, AC97_AD_MISC, (1 << 11) | (1 << 12), val);
+}
+
+static const struct snd_kcontrol_new snd_ac97_ad1888_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Exchange Front/Surround",
+		.info = snd_ac97_ad1888_lohpsel_info,
+		.get = snd_ac97_ad1888_lohpsel_get,
+		.put = snd_ac97_ad1888_lohpsel_put
+	},
+	AC97_SINGLE("V_REFOUT Enable", AC97_AD_MISC, AC97_AD_VREFD_SHIFT, 1, 1),
+	AC97_SINGLE("High Pass Filter Enable", AC97_AD_TEST2,
+			AC97_AD_HPFD_SHIFT, 1, 1),
+	AC97_SINGLE("Spread Front to Surround and Center/LFE", AC97_AD_MISC, 7, 1, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Downmix",
+		.info = snd_ac97_ad1888_downmix_info,
+		.get = snd_ac97_ad1888_downmix_get,
+		.put = snd_ac97_ad1888_downmix_put
+	},
+	AC97_SURROUND_JACK_MODE_CTL,
+	AC97_CHANNEL_MODE_CTL,
+
+	AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 10, 1, 0),
+	AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0),
+};
+
+static int patch_ad1888_specific(struct snd_ac97 *ac97)
+{
+	if (!ac97->spec.ad18xx.lo_as_master) {
+		/* rename 0x04 as "Master" and 0x02 as "Master Surround" */
+		snd_ac97_rename_vol_ctl(ac97, "Master Playback",
+					"Master Surround Playback");
+		snd_ac97_rename_vol_ctl(ac97, "Headphone Playback",
+					"Master Playback");
+	}
+	return patch_build_controls(ac97, snd_ac97_ad1888_controls, ARRAY_SIZE(snd_ac97_ad1888_controls));
+}
+
+static struct snd_ac97_build_ops patch_ad1888_build_ops = {
+	.build_post_spdif = patch_ad198x_post_spdif,
+	.build_specific = patch_ad1888_specific,
+#ifdef CONFIG_PM
+	.resume = ad1888_resume,
+#endif
+	.update_jacks = ad1888_update_jacks,
+};
+
+static int patch_ad1888(struct snd_ac97 * ac97)
+{
+	unsigned short misc;
+	
+	patch_ad1881(ac97);
+	ac97->build_ops = &patch_ad1888_build_ops;
+
+	/*
+	 * LO can be used as a real line-out on some devices,
+	 * and we need to revert the front/surround mixer switches
+	 */
+	if (ac97->subsystem_vendor == 0x1043 &&
+	    ac97->subsystem_device == 0x1193) /* ASUS A9T laptop */
+		ac97->spec.ad18xx.lo_as_master = 1;
+
+	misc = snd_ac97_read(ac97, AC97_AD_MISC);
+	/* AD-compatible mode */
+	/* Stereo mutes enabled */
+	misc |= AC97_AD198X_MSPLT | AC97_AD198X_AC97NC;
+	if (!ac97->spec.ad18xx.lo_as_master)
+		/* Switch FRONT/SURROUND LINE-OUT/HP-OUT default connection */
+		/* it seems that most vendors connect line-out connector to
+		 * headphone out of AC'97
+		 */
+		misc |= AC97_AD198X_LOSEL | AC97_AD198X_HPSEL;
+
+	snd_ac97_write_cache(ac97, AC97_AD_MISC, misc);
+	ac97->flags |= AC97_STEREO_MUTES;
+	return 0;
+}
+
+static int patch_ad1980_specific(struct snd_ac97 *ac97)
+{
+	int err;
+
+	if ((err = patch_ad1888_specific(ac97)) < 0)
+		return err;
+	return patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1);
+}
+
+static struct snd_ac97_build_ops patch_ad1980_build_ops = {
+	.build_post_spdif = patch_ad198x_post_spdif,
+	.build_specific = patch_ad1980_specific,
+#ifdef CONFIG_PM
+	.resume = ad18xx_resume,
+#endif
+	.update_jacks = ad1888_update_jacks,
+};
+
+static int patch_ad1980(struct snd_ac97 * ac97)
+{
+	patch_ad1888(ac97);
+	ac97->build_ops = &patch_ad1980_build_ops;
+	return 0;
+}
+
+static int snd_ac97_ad1985_vrefout_info(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = {"High-Z", "3.7 V", "2.25 V", "0 V"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3)
+		uinfo->value.enumerated.item = 3;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ac97_ad1985_vrefout_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	static const int reg2ctrl[4] = {2, 0, 1, 3};
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+	val = (ac97->regs[AC97_AD_MISC] & AC97_AD198X_VREF_MASK)
+	      >> AC97_AD198X_VREF_SHIFT;
+	ucontrol->value.enumerated.item[0] = reg2ctrl[val];
+	return 0;
+}
+
+static int snd_ac97_ad1985_vrefout_put(struct snd_kcontrol *kcontrol, 
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	static const int ctrl2reg[4] = {1, 2, 0, 3};
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	if (ucontrol->value.enumerated.item[0] > 3)
+		return -EINVAL;
+	val = ctrl2reg[ucontrol->value.enumerated.item[0]]
+	      << AC97_AD198X_VREF_SHIFT;
+	return snd_ac97_update_bits(ac97, AC97_AD_MISC,
+				    AC97_AD198X_VREF_MASK, val);
+}
+
+static const struct snd_kcontrol_new snd_ac97_ad1985_controls[] = {
+	AC97_SINGLE("Exchange Center/LFE", AC97_AD_SERIAL_CFG, 3, 1, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Exchange Front/Surround",
+		.info = snd_ac97_ad1888_lohpsel_info,
+		.get = snd_ac97_ad1888_lohpsel_get,
+		.put = snd_ac97_ad1888_lohpsel_put
+	},
+	AC97_SINGLE("High Pass Filter Enable", AC97_AD_TEST2, 12, 1, 1),
+	AC97_SINGLE("Spread Front to Surround and Center/LFE",
+		    AC97_AD_MISC, 7, 1, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Downmix",
+		.info = snd_ac97_ad1888_downmix_info,
+		.get = snd_ac97_ad1888_downmix_get,
+		.put = snd_ac97_ad1888_downmix_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "V_REFOUT",
+		.info = snd_ac97_ad1985_vrefout_info,
+		.get = snd_ac97_ad1985_vrefout_get,
+		.put = snd_ac97_ad1985_vrefout_put
+	},
+	AC97_SURROUND_JACK_MODE_CTL,
+	AC97_CHANNEL_MODE_CTL,
+
+	AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 10, 1, 0),
+	AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0),
+};
+
+static void ad1985_update_jacks(struct snd_ac97 *ac97)
+{
+	ad1888_update_jacks(ac97);
+	/* clear OMS if shared jack is to be used for C/LFE out */
+	snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 1 << 9,
+			     is_shared_micin(ac97) ? 1 << 9 : 0);
+}
+
+static int patch_ad1985_specific(struct snd_ac97 *ac97)
+{
+	int err;
+
+	/* rename 0x04 as "Master" and 0x02 as "Master Surround" */
+	snd_ac97_rename_vol_ctl(ac97, "Master Playback",
+				"Master Surround Playback");
+	snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Master Playback");
+
+	if ((err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1)) < 0)
+		return err;
+
+	return patch_build_controls(ac97, snd_ac97_ad1985_controls,
+				    ARRAY_SIZE(snd_ac97_ad1985_controls));
+}
+
+static struct snd_ac97_build_ops patch_ad1985_build_ops = {
+	.build_post_spdif = patch_ad198x_post_spdif,
+	.build_specific = patch_ad1985_specific,
+#ifdef CONFIG_PM
+	.resume = ad18xx_resume,
+#endif
+	.update_jacks = ad1985_update_jacks,
+};
+
+static int patch_ad1985(struct snd_ac97 * ac97)
+{
+	unsigned short misc;
+	
+	patch_ad1881(ac97);
+	ac97->build_ops = &patch_ad1985_build_ops;
+	misc = snd_ac97_read(ac97, AC97_AD_MISC);
+	/* switch front/surround line-out/hp-out */
+	/* AD-compatible mode */
+	/* Stereo mutes enabled */
+	snd_ac97_write_cache(ac97, AC97_AD_MISC, misc |
+			     AC97_AD198X_LOSEL |
+			     AC97_AD198X_HPSEL |
+			     AC97_AD198X_MSPLT |
+			     AC97_AD198X_AC97NC);
+	ac97->flags |= AC97_STEREO_MUTES;
+
+	/* update current jack configuration */
+	ad1985_update_jacks(ac97);
+
+	/* on AD1985 rev. 3, AC'97 revision bits are zero */
+	ac97->ext_id = (ac97->ext_id & ~AC97_EI_REV_MASK) | AC97_EI_REV_23;
+	return 0;
+}
+
+#define snd_ac97_ad1986_bool_info	snd_ctl_boolean_mono_info
+
+static int snd_ac97_ad1986_lososel_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	val = ac97->regs[AC97_AD_MISC3];
+	ucontrol->value.integer.value[0] = (val & AC97_AD1986_LOSEL) != 0;
+	return 0;
+}
+
+static int snd_ac97_ad1986_lososel_put(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int ret0;
+	int ret1;
+	int sprd = (ac97->regs[AC97_AD_MISC] & AC97_AD1986_SPRD) != 0;
+
+	ret0 = snd_ac97_update_bits(ac97, AC97_AD_MISC3, AC97_AD1986_LOSEL,
+					ucontrol->value.integer.value[0] != 0
+				    ? AC97_AD1986_LOSEL : 0);
+	if (ret0 < 0)
+		return ret0;
+
+	/* SOSEL is set to values of "Spread" or "Exchange F/S" controls */
+	ret1 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SOSEL,
+				    (ucontrol->value.integer.value[0] != 0
+				     || sprd)
+				    ? AC97_AD1986_SOSEL : 0);
+	if (ret1 < 0)
+		return ret1;
+
+	return (ret0 > 0 || ret1 > 0) ? 1 : 0;
+}
+
+static int snd_ac97_ad1986_spread_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	val = ac97->regs[AC97_AD_MISC];
+	ucontrol->value.integer.value[0] = (val & AC97_AD1986_SPRD) != 0;
+	return 0;
+}
+
+static int snd_ac97_ad1986_spread_put(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	int ret0;
+	int ret1;
+	int sprd = (ac97->regs[AC97_AD_MISC3] & AC97_AD1986_LOSEL) != 0;
+
+	ret0 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SPRD,
+					ucontrol->value.integer.value[0] != 0
+				    ? AC97_AD1986_SPRD : 0);
+	if (ret0 < 0)
+		return ret0;
+
+	/* SOSEL is set to values of "Spread" or "Exchange F/S" controls */
+	ret1 = snd_ac97_update_bits(ac97, AC97_AD_MISC, AC97_AD1986_SOSEL,
+				    (ucontrol->value.integer.value[0] != 0
+				     || sprd)
+				    ? AC97_AD1986_SOSEL : 0);
+	if (ret1 < 0)
+		return ret1;
+
+	return (ret0 > 0 || ret1 > 0) ? 1 : 0;
+}
+
+static int snd_ac97_ad1986_miclisel_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = ac97->spec.ad18xx.swap_mic_linein;
+	return 0;
+}
+
+static int snd_ac97_ad1986_miclisel_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned char swap = ucontrol->value.integer.value[0] != 0;
+
+	if (swap != ac97->spec.ad18xx.swap_mic_linein) {
+		ac97->spec.ad18xx.swap_mic_linein = swap;
+		if (ac97->build_ops->update_jacks)
+			ac97->build_ops->update_jacks(ac97);
+		return 1;
+	}
+	return 0;
+}
+
+static int snd_ac97_ad1986_vrefout_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	/* Use MIC_1/2 V_REFOUT as the "get" value */
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+	unsigned short reg = ac97->regs[AC97_AD_MISC2];
+	if ((reg & AC97_AD1986_MVREF0) != 0)
+		val = 2;
+	else if ((reg & AC97_AD1986_MVREF1) != 0)
+		val = 3;
+	else if ((reg & AC97_AD1986_MVREF2) != 0)
+		val = 1;
+	else
+		val = 0;
+	ucontrol->value.enumerated.item[0] = val;
+	return 0;
+}
+
+static int snd_ac97_ad1986_vrefout_put(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short cval;
+	unsigned short lval;
+	unsigned short mval;
+	int cret;
+	int lret;
+	int mret;
+
+	switch (ucontrol->value.enumerated.item[0])
+	{
+	case 0: /* High-Z */
+		cval = 0;
+		lval = 0;
+		mval = 0;
+		break;
+	case 1: /* 3.7 V */
+		cval = AC97_AD1986_CVREF2;
+		lval = AC97_AD1986_LVREF2;
+		mval = AC97_AD1986_MVREF2;
+		break;
+	case 2: /* 2.25 V */
+		cval = AC97_AD1986_CVREF0;
+		lval = AC97_AD1986_LVREF0;
+		mval = AC97_AD1986_MVREF0;
+		break;
+	case 3: /* 0 V */
+		cval = AC97_AD1986_CVREF1;
+		lval = AC97_AD1986_LVREF1;
+		mval = AC97_AD1986_MVREF1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	cret = snd_ac97_update_bits(ac97, AC97_AD_MISC2,
+				    AC97_AD1986_CVREF_MASK, cval);
+	if (cret < 0)
+		return cret;
+	lret = snd_ac97_update_bits(ac97, AC97_AD_MISC3,
+				    AC97_AD1986_LVREF_MASK, lval);
+	if (lret < 0)
+		return lret;
+	mret = snd_ac97_update_bits(ac97, AC97_AD_MISC2,
+				    AC97_AD1986_MVREF_MASK, mval);
+	if (mret < 0)
+		return mret;
+
+	return (cret > 0 || lret > 0 || mret > 0) ? 1 : 0;
+}
+
+static const struct snd_kcontrol_new snd_ac97_ad1986_controls[] = {
+	AC97_SINGLE("Exchange Center/LFE", AC97_AD_SERIAL_CFG, 3, 1, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Exchange Front/Surround",
+		.info = snd_ac97_ad1986_bool_info,
+		.get = snd_ac97_ad1986_lososel_get,
+		.put = snd_ac97_ad1986_lososel_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Exchange Mic/Line In",
+		.info = snd_ac97_ad1986_bool_info,
+		.get = snd_ac97_ad1986_miclisel_get,
+		.put = snd_ac97_ad1986_miclisel_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Spread Front to Surround and Center/LFE",
+		.info = snd_ac97_ad1986_bool_info,
+		.get = snd_ac97_ad1986_spread_get,
+		.put = snd_ac97_ad1986_spread_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Downmix",
+		.info = snd_ac97_ad1888_downmix_info,
+		.get = snd_ac97_ad1888_downmix_get,
+		.put = snd_ac97_ad1888_downmix_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "V_REFOUT",
+		.info = snd_ac97_ad1985_vrefout_info,
+		.get = snd_ac97_ad1986_vrefout_get,
+		.put = snd_ac97_ad1986_vrefout_put
+	},
+	AC97_SURROUND_JACK_MODE_CTL,
+	AC97_CHANNEL_MODE_CTL,
+
+	AC97_SINGLE("Headphone Jack Sense", AC97_AD_JACK_SPDIF, 10, 1, 0),
+	AC97_SINGLE("Line Jack Sense", AC97_AD_JACK_SPDIF, 12, 1, 0)
+};
+
+static void ad1986_update_jacks(struct snd_ac97 *ac97)
+{
+	unsigned short misc_val = 0;
+	unsigned short ser_val;
+
+	/* disable SURROUND and CENTER/LFE if not surround mode */
+	if (!is_surround_on(ac97))
+		misc_val |= AC97_AD1986_SODIS;
+	if (!is_clfe_on(ac97))
+		misc_val |= AC97_AD1986_CLDIS;
+
+	/* select line input (default=LINE_IN, SURROUND or MIC_1/2) */
+	if (is_shared_linein(ac97))
+		misc_val |= AC97_AD1986_LISEL_SURR;
+	else if (ac97->spec.ad18xx.swap_mic_linein != 0)
+		misc_val |= AC97_AD1986_LISEL_MIC;
+	snd_ac97_update_bits(ac97, AC97_AD_MISC,
+			     AC97_AD1986_SODIS | AC97_AD1986_CLDIS |
+			     AC97_AD1986_LISEL_MASK,
+			     misc_val);
+
+	/* select microphone input (MIC_1/2, Center/LFE or LINE_IN) */
+	if (is_shared_micin(ac97))
+		ser_val = AC97_AD1986_OMS_C;
+	else if (ac97->spec.ad18xx.swap_mic_linein != 0)
+		ser_val = AC97_AD1986_OMS_L;
+	else
+		ser_val = AC97_AD1986_OMS_M;
+	snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG,
+			     AC97_AD1986_OMS_MASK,
+			     ser_val);
+}
+
+static int patch_ad1986_specific(struct snd_ac97 *ac97)
+{
+	int err;
+
+	if ((err = patch_build_controls(ac97, &snd_ac97_ad198x_2cmic, 1)) < 0)
+		return err;
+
+	return patch_build_controls(ac97, snd_ac97_ad1986_controls,
+				    ARRAY_SIZE(snd_ac97_ad1985_controls));
+}
+
+static struct snd_ac97_build_ops patch_ad1986_build_ops = {
+	.build_post_spdif = patch_ad198x_post_spdif,
+	.build_specific = patch_ad1986_specific,
+#ifdef CONFIG_PM
+	.resume = ad18xx_resume,
+#endif
+	.update_jacks = ad1986_update_jacks,
+};
+
+static int patch_ad1986(struct snd_ac97 * ac97)
+{
+	patch_ad1881(ac97);
+	ac97->build_ops = &patch_ad1986_build_ops;
+	ac97->flags |= AC97_STEREO_MUTES;
+
+	/* update current jack configuration */
+	ad1986_update_jacks(ac97);
+
+	return 0;
+}
+
+/*
+ * realtek ALC203: use mono-out for pin 37
+ */
+static int patch_alc203(struct snd_ac97 *ac97)
+{
+	snd_ac97_update_bits(ac97, 0x7a, 0x400, 0x400);
+	return 0;
+}
+
+/*
+ * realtek ALC65x/850 codecs
+ */
+static void alc650_update_jacks(struct snd_ac97 *ac97)
+{
+	int shared;
+	
+	/* shared Line-In / Surround Out */
+	shared = is_shared_surrout(ac97);
+	snd_ac97_update_bits(ac97, AC97_ALC650_MULTICH, 1 << 9,
+			     shared ? (1 << 9) : 0);
+	/* update shared Mic In / Center/LFE Out */
+	shared = is_shared_clfeout(ac97);
+	/* disable/enable vref */
+	snd_ac97_update_bits(ac97, AC97_ALC650_CLOCK, 1 << 12,
+			     shared ? (1 << 12) : 0);
+	/* turn on/off center-on-mic */
+	snd_ac97_update_bits(ac97, AC97_ALC650_MULTICH, 1 << 10,
+			     shared ? (1 << 10) : 0);
+	/* GPIO0 high for mic */
+	snd_ac97_update_bits(ac97, AC97_ALC650_GPIO_STATUS, 0x100,
+			     shared ? 0 : 0x100);
+}
+
+static const struct snd_kcontrol_new snd_ac97_controls_alc650[] = {
+	AC97_SINGLE("Duplicate Front", AC97_ALC650_MULTICH, 0, 1, 0),
+	AC97_SINGLE("Surround Down Mix", AC97_ALC650_MULTICH, 1, 1, 0),
+	AC97_SINGLE("Center/LFE Down Mix", AC97_ALC650_MULTICH, 2, 1, 0),
+	AC97_SINGLE("Exchange Center/LFE", AC97_ALC650_MULTICH, 3, 1, 0),
+	/* 4: Analog Input To Surround */
+	/* 5: Analog Input To Center/LFE */
+	/* 6: Independent Master Volume Right */
+	/* 7: Independent Master Volume Left */
+	/* 8: reserved */
+	/* 9: Line-In/Surround share */
+	/* 10: Mic/CLFE share */
+	/* 11-13: in IEC958 controls */
+	AC97_SINGLE("Swap Surround Slot", AC97_ALC650_MULTICH, 14, 1, 0),
+#if 0 /* always set in patch_alc650 */
+	AC97_SINGLE("IEC958 Input Clock Enable", AC97_ALC650_CLOCK, 0, 1, 0),
+	AC97_SINGLE("IEC958 Input Pin Enable", AC97_ALC650_CLOCK, 1, 1, 0),
+	AC97_SINGLE("Surround DAC Switch", AC97_ALC650_SURR_DAC_VOL, 15, 1, 1),
+	AC97_DOUBLE("Surround DAC Volume", AC97_ALC650_SURR_DAC_VOL, 8, 0, 31, 1),
+	AC97_SINGLE("Center/LFE DAC Switch", AC97_ALC650_LFE_DAC_VOL, 15, 1, 1),
+	AC97_DOUBLE("Center/LFE DAC Volume", AC97_ALC650_LFE_DAC_VOL, 8, 0, 31, 1),
+#endif
+	AC97_SURROUND_JACK_MODE_CTL,
+	AC97_CHANNEL_MODE_CTL,
+};
+
+static const struct snd_kcontrol_new snd_ac97_spdif_controls_alc650[] = {
+        AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_ALC650_MULTICH, 11, 1, 0),
+        AC97_SINGLE("Analog to IEC958 Output", AC97_ALC650_MULTICH, 12, 1, 0),
+	/* disable this controls since it doesn't work as expected */
+	/* AC97_SINGLE("IEC958 Input Monitor", AC97_ALC650_MULTICH, 13, 1, 0), */
+};
+
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_3db_max, -4350, 150, 0);
+
+static int patch_alc650_specific(struct snd_ac97 * ac97)
+{
+	int err;
+
+	if ((err = patch_build_controls(ac97, snd_ac97_controls_alc650, ARRAY_SIZE(snd_ac97_controls_alc650))) < 0)
+		return err;
+	if (ac97->ext_id & AC97_EI_SPDIF) {
+		if ((err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc650, ARRAY_SIZE(snd_ac97_spdif_controls_alc650))) < 0)
+			return err;
+	}
+	if (ac97->id != AC97_ID_ALC650F)
+		reset_tlv(ac97, "Master Playback Volume",
+			  db_scale_5bit_3db_max);
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_alc650_ops = {
+	.build_specific	= patch_alc650_specific,
+	.update_jacks = alc650_update_jacks
+};
+
+static int patch_alc650(struct snd_ac97 * ac97)
+{
+	unsigned short val;
+
+	ac97->build_ops = &patch_alc650_ops;
+
+	/* determine the revision */
+	val = snd_ac97_read(ac97, AC97_ALC650_REVISION) & 0x3f;
+	if (val < 3)
+		ac97->id = 0x414c4720;          /* Old version */
+	else if (val < 0x10)
+		ac97->id = 0x414c4721;          /* D version */
+	else if (val < 0x20)
+		ac97->id = 0x414c4722;          /* E version */
+	else if (val < 0x30)
+		ac97->id = 0x414c4723;          /* F version */
+
+	/* revision E or F */
+	/* FIXME: what about revision D ? */
+	ac97->spec.dev_flags = (ac97->id == 0x414c4722 ||
+				ac97->id == 0x414c4723);
+
+	/* enable AC97_ALC650_GPIO_SETUP, AC97_ALC650_CLOCK for R/W */
+	snd_ac97_write_cache(ac97, AC97_ALC650_GPIO_STATUS, 
+		snd_ac97_read(ac97, AC97_ALC650_GPIO_STATUS) | 0x8000);
+
+	/* Enable SPDIF-IN only on Rev.E and above */
+	val = snd_ac97_read(ac97, AC97_ALC650_CLOCK);
+	/* SPDIF IN with pin 47 */
+	if (ac97->spec.dev_flags &&
+	    /* ASUS A6KM requires EAPD */
+	    ! (ac97->subsystem_vendor == 0x1043 &&
+	       ac97->subsystem_device == 0x1103))
+		val |= 0x03; /* enable */
+	else
+		val &= ~0x03; /* disable */
+	snd_ac97_write_cache(ac97, AC97_ALC650_CLOCK, val);
+
+	/* set default: slot 3,4,7,8,6,9
+	   spdif-in monitor off, analog-spdif off, spdif-in off
+	   center on mic off, surround on line-in off
+	   downmix off, duplicate front off
+	*/
+	snd_ac97_write_cache(ac97, AC97_ALC650_MULTICH, 0);
+
+	/* set GPIO0 for mic bias */
+	/* GPIO0 pin output, no interrupt, high */
+	snd_ac97_write_cache(ac97, AC97_ALC650_GPIO_SETUP,
+			     snd_ac97_read(ac97, AC97_ALC650_GPIO_SETUP) | 0x01);
+	snd_ac97_write_cache(ac97, AC97_ALC650_GPIO_STATUS,
+			     (snd_ac97_read(ac97, AC97_ALC650_GPIO_STATUS) | 0x100) & ~0x10);
+
+	/* full DAC volume */
+	snd_ac97_write_cache(ac97, AC97_ALC650_SURR_DAC_VOL, 0x0808);
+	snd_ac97_write_cache(ac97, AC97_ALC650_LFE_DAC_VOL, 0x0808);
+	return 0;
+}
+
+static void alc655_update_jacks(struct snd_ac97 *ac97)
+{
+	int shared;
+	
+	/* shared Line-In / Surround Out */
+	shared = is_shared_surrout(ac97);
+	ac97_update_bits_page(ac97, AC97_ALC650_MULTICH, 1 << 9,
+			      shared ? (1 << 9) : 0, 0);
+	/* update shared Mic In / Center/LFE Out */
+	shared = is_shared_clfeout(ac97);
+	/* misc control; vrefout disable */
+	snd_ac97_update_bits(ac97, AC97_ALC650_CLOCK, 1 << 12,
+			     shared ? (1 << 12) : 0);
+	ac97_update_bits_page(ac97, AC97_ALC650_MULTICH, 1 << 10,
+			      shared ? (1 << 10) : 0, 0);
+}
+
+static const struct snd_kcontrol_new snd_ac97_controls_alc655[] = {
+	AC97_PAGE_SINGLE("Duplicate Front", AC97_ALC650_MULTICH, 0, 1, 0, 0),
+	AC97_SURROUND_JACK_MODE_CTL,
+	AC97_CHANNEL_MODE_CTL,
+};
+
+static int alc655_iec958_route_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts_655[3] = { "PCM", "Analog In", "IEC958 In" };
+	static char *texts_658[4] = { "PCM", "Analog1 In", "Analog2 In", "IEC958 In" };
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = ac97->spec.dev_flags ? 4 : 3;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+	       ac97->spec.dev_flags ?
+	       texts_658[uinfo->value.enumerated.item] :
+	       texts_655[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int alc655_iec958_route_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	val = ac97->regs[AC97_ALC650_MULTICH];
+	val = (val >> 12) & 3;
+	if (ac97->spec.dev_flags && val == 3)
+		val = 0;
+	ucontrol->value.enumerated.item[0] = val;
+	return 0;
+}
+
+static int alc655_iec958_route_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+	return ac97_update_bits_page(ac97, AC97_ALC650_MULTICH, 3 << 12,
+				     (unsigned short)ucontrol->value.enumerated.item[0] << 12,
+				     0);
+}
+
+static const struct snd_kcontrol_new snd_ac97_spdif_controls_alc655[] = {
+        AC97_PAGE_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_ALC650_MULTICH, 11, 1, 0, 0),
+	/* disable this controls since it doesn't work as expected */
+        /* AC97_PAGE_SINGLE("IEC958 Input Monitor", AC97_ALC650_MULTICH, 14, 1, 0, 0), */
+	{
+		.iface  = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name   = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+		.info   = alc655_iec958_route_info,
+		.get    = alc655_iec958_route_get,
+		.put    = alc655_iec958_route_put,
+	},
+};
+
+static int patch_alc655_specific(struct snd_ac97 * ac97)
+{
+	int err;
+
+	if ((err = patch_build_controls(ac97, snd_ac97_controls_alc655, ARRAY_SIZE(snd_ac97_controls_alc655))) < 0)
+		return err;
+	if (ac97->ext_id & AC97_EI_SPDIF) {
+		if ((err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc655, ARRAY_SIZE(snd_ac97_spdif_controls_alc655))) < 0)
+			return err;
+	}
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_alc655_ops = {
+	.build_specific	= patch_alc655_specific,
+	.update_jacks = alc655_update_jacks
+};
+
+static int patch_alc655(struct snd_ac97 * ac97)
+{
+	unsigned int val;
+
+	if (ac97->id == AC97_ID_ALC658) {
+		ac97->spec.dev_flags = 1; /* ALC658 */
+		if ((snd_ac97_read(ac97, AC97_ALC650_REVISION) & 0x3f) == 2) {
+			ac97->id = AC97_ID_ALC658D;
+			ac97->spec.dev_flags = 2;
+		}
+	}
+
+	ac97->build_ops = &patch_alc655_ops;
+
+	/* assume only page 0 for writing cache */
+	snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, AC97_PAGE_VENDOR);
+
+	/* adjust default values */
+	val = snd_ac97_read(ac97, 0x7a); /* misc control */
+	if (ac97->spec.dev_flags) /* ALC658 */
+		val &= ~(1 << 1); /* Pin 47 is spdif input pin */
+	else { /* ALC655 */
+		if (ac97->subsystem_vendor == 0x1462 &&
+		    (ac97->subsystem_device == 0x0131 || /* MSI S270 laptop */
+		     ac97->subsystem_device == 0x0161 || /* LG K1 Express */
+		     ac97->subsystem_device == 0x0351 || /* MSI L725 laptop */
+		     ac97->subsystem_device == 0x0471 || /* MSI L720 laptop */
+		     ac97->subsystem_device == 0x0061))  /* MSI S250 laptop */
+			val &= ~(1 << 1); /* Pin 47 is EAPD (for internal speaker) */
+		else
+			val |= (1 << 1); /* Pin 47 is spdif input pin */
+		/* this seems missing on some hardwares */
+		ac97->ext_id |= AC97_EI_SPDIF;
+	}
+	val &= ~(1 << 12); /* vref enable */
+	snd_ac97_write_cache(ac97, 0x7a, val);
+	/* set default: spdif-in enabled,
+	   spdif-in monitor off, spdif-in PCM off
+	   center on mic off, surround on line-in off
+	   duplicate front off
+	*/
+	snd_ac97_write_cache(ac97, AC97_ALC650_MULTICH, 1<<15);
+
+	/* full DAC volume */
+	snd_ac97_write_cache(ac97, AC97_ALC650_SURR_DAC_VOL, 0x0808);
+	snd_ac97_write_cache(ac97, AC97_ALC650_LFE_DAC_VOL, 0x0808);
+
+	/* update undocumented bit... */
+	if (ac97->id == AC97_ID_ALC658D)
+		snd_ac97_update_bits(ac97, 0x74, 0x0800, 0x0800);
+
+	return 0;
+}
+
+
+#define AC97_ALC850_JACK_SELECT	0x76
+#define AC97_ALC850_MISC1	0x7a
+#define AC97_ALC850_MULTICH    0x6a
+
+static void alc850_update_jacks(struct snd_ac97 *ac97)
+{
+	int shared;
+	int aux_is_back_surround;
+	
+	/* shared Line-In / Surround Out */
+	shared = is_shared_surrout(ac97);
+	/* SURR 1kOhm (bit4), Amp (bit5) */
+	snd_ac97_update_bits(ac97, AC97_ALC850_MISC1, (1<<4)|(1<<5),
+			     shared ? (1<<5) : (1<<4));
+	/* LINE-IN = 0, SURROUND = 2 */
+	snd_ac97_update_bits(ac97, AC97_ALC850_JACK_SELECT, 7 << 12,
+			     shared ? (2<<12) : (0<<12));
+	/* update shared Mic In / Center/LFE Out */
+	shared = is_shared_clfeout(ac97);
+	/* Vref disable (bit12), 1kOhm (bit13) */
+	snd_ac97_update_bits(ac97, AC97_ALC850_MISC1, (1<<12)|(1<<13),
+			     shared ? (1<<12) : (1<<13));
+	/* MIC-IN = 1, CENTER-LFE = 5 */
+	snd_ac97_update_bits(ac97, AC97_ALC850_JACK_SELECT, 7 << 4,
+			     shared ? (5<<4) : (1<<4));
+
+	aux_is_back_surround = alc850_is_aux_back_surround(ac97);
+	/* Aux is Back Surround */
+	snd_ac97_update_bits(ac97, AC97_ALC850_MULTICH, 1 << 10,
+				 aux_is_back_surround ? (1<<10) : (0<<10));
+}
+
+static const struct snd_kcontrol_new snd_ac97_controls_alc850[] = {
+	AC97_PAGE_SINGLE("Duplicate Front", AC97_ALC650_MULTICH, 0, 1, 0, 0),
+	AC97_SINGLE("Mic Front Input Switch", AC97_ALC850_JACK_SELECT, 15, 1, 1),
+	AC97_SURROUND_JACK_MODE_CTL,
+	AC97_CHANNEL_MODE_8CH_CTL,
+};
+
+static int patch_alc850_specific(struct snd_ac97 *ac97)
+{
+	int err;
+
+	if ((err = patch_build_controls(ac97, snd_ac97_controls_alc850, ARRAY_SIZE(snd_ac97_controls_alc850))) < 0)
+		return err;
+	if (ac97->ext_id & AC97_EI_SPDIF) {
+		if ((err = patch_build_controls(ac97, snd_ac97_spdif_controls_alc655, ARRAY_SIZE(snd_ac97_spdif_controls_alc655))) < 0)
+			return err;
+	}
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_alc850_ops = {
+	.build_specific	= patch_alc850_specific,
+	.update_jacks = alc850_update_jacks
+};
+
+static int patch_alc850(struct snd_ac97 *ac97)
+{
+	ac97->build_ops = &patch_alc850_ops;
+
+	ac97->spec.dev_flags = 0; /* for IEC958 playback route - ALC655 compatible */
+	ac97->flags |= AC97_HAS_8CH;
+
+	/* assume only page 0 for writing cache */
+	snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, AC97_PAGE_VENDOR);
+
+	/* adjust default values */
+	/* set default: spdif-in enabled,
+	   spdif-in monitor off, spdif-in PCM off
+	   center on mic off, surround on line-in off
+	   duplicate front off
+	   NB default bit 10=0 = Aux is Capture, not Back Surround
+	*/
+	snd_ac97_write_cache(ac97, AC97_ALC650_MULTICH, 1<<15);
+	/* SURR_OUT: on, Surr 1kOhm: on, Surr Amp: off, Front 1kOhm: off
+	 * Front Amp: on, Vref: enable, Center 1kOhm: on, Mix: on
+	 */
+	snd_ac97_write_cache(ac97, 0x7a, (1<<1)|(1<<4)|(0<<5)|(1<<6)|
+			     (1<<7)|(0<<12)|(1<<13)|(0<<14));
+	/* detection UIO2,3: all path floating, UIO3: MIC, Vref2: disable,
+	 * UIO1: FRONT, Vref3: disable, UIO3: LINE, Front-Mic: mute
+	 */
+	snd_ac97_write_cache(ac97, 0x76, (0<<0)|(0<<2)|(1<<4)|(1<<7)|(2<<8)|
+			     (1<<11)|(0<<12)|(1<<15));
+
+	/* full DAC volume */
+	snd_ac97_write_cache(ac97, AC97_ALC650_SURR_DAC_VOL, 0x0808);
+	snd_ac97_write_cache(ac97, AC97_ALC650_LFE_DAC_VOL, 0x0808);
+	return 0;
+}
+
+
+/*
+ * C-Media CM97xx codecs
+ */
+static void cm9738_update_jacks(struct snd_ac97 *ac97)
+{
+	/* shared Line-In / Surround Out */
+	snd_ac97_update_bits(ac97, AC97_CM9738_VENDOR_CTRL, 1 << 10,
+			     is_shared_surrout(ac97) ? (1 << 10) : 0);
+}
+
+static const struct snd_kcontrol_new snd_ac97_cm9738_controls[] = {
+	AC97_SINGLE("Duplicate Front", AC97_CM9738_VENDOR_CTRL, 13, 1, 0),
+	AC97_SURROUND_JACK_MODE_CTL,
+	AC97_CHANNEL_MODE_4CH_CTL,
+};
+
+static int patch_cm9738_specific(struct snd_ac97 * ac97)
+{
+	return patch_build_controls(ac97, snd_ac97_cm9738_controls, ARRAY_SIZE(snd_ac97_cm9738_controls));
+}
+
+static struct snd_ac97_build_ops patch_cm9738_ops = {
+	.build_specific	= patch_cm9738_specific,
+	.update_jacks = cm9738_update_jacks
+};
+
+static int patch_cm9738(struct snd_ac97 * ac97)
+{
+	ac97->build_ops = &patch_cm9738_ops;
+	/* FIXME: can anyone confirm below? */
+	/* CM9738 has no PCM volume although the register reacts */
+	ac97->flags |= AC97_HAS_NO_PCM_VOL;
+	snd_ac97_write_cache(ac97, AC97_PCM, 0x8000);
+
+	return 0;
+}
+
+static int snd_ac97_cmedia_spdif_playback_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "Analog", "Digital" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ac97_cmedia_spdif_playback_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	val = ac97->regs[AC97_CM9739_SPDIF_CTRL];
+	ucontrol->value.enumerated.item[0] = (val >> 1) & 0x01;
+	return 0;
+}
+
+static int snd_ac97_cmedia_spdif_playback_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+	return snd_ac97_update_bits(ac97, AC97_CM9739_SPDIF_CTRL,
+				    0x01 << 1, 
+				    (ucontrol->value.enumerated.item[0] & 0x01) << 1);
+}
+
+static const struct snd_kcontrol_new snd_ac97_cm9739_controls_spdif[] = {
+	/* BIT 0: SPDI_EN - always true */
+	{ /* BIT 1: SPDIFS */
+		.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name	= SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+		.info	= snd_ac97_cmedia_spdif_playback_source_info,
+		.get	= snd_ac97_cmedia_spdif_playback_source_get,
+		.put	= snd_ac97_cmedia_spdif_playback_source_put,
+	},
+	/* BIT 2: IG_SPIV */
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Valid Switch", AC97_CM9739_SPDIF_CTRL, 2, 1, 0),
+	/* BIT 3: SPI2F */
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Monitor", AC97_CM9739_SPDIF_CTRL, 3, 1, 0), 
+	/* BIT 4: SPI2SDI */
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_CM9739_SPDIF_CTRL, 4, 1, 0),
+	/* BIT 8: SPD32 - 32bit SPDIF - not supported yet */
+};
+
+static void cm9739_update_jacks(struct snd_ac97 *ac97)
+{
+	/* shared Line-In / Surround Out */
+	snd_ac97_update_bits(ac97, AC97_CM9739_MULTI_CHAN, 1 << 10,
+			     is_shared_surrout(ac97) ? (1 << 10) : 0);
+	/* shared Mic In / Center/LFE Out **/
+	snd_ac97_update_bits(ac97, AC97_CM9739_MULTI_CHAN, 0x3000,
+			     is_shared_clfeout(ac97) ? 0x1000 : 0x2000);
+}
+
+static const struct snd_kcontrol_new snd_ac97_cm9739_controls[] = {
+	AC97_SURROUND_JACK_MODE_CTL,
+	AC97_CHANNEL_MODE_CTL,
+};
+
+static int patch_cm9739_specific(struct snd_ac97 * ac97)
+{
+	return patch_build_controls(ac97, snd_ac97_cm9739_controls, ARRAY_SIZE(snd_ac97_cm9739_controls));
+}
+
+static int patch_cm9739_post_spdif(struct snd_ac97 * ac97)
+{
+	return patch_build_controls(ac97, snd_ac97_cm9739_controls_spdif, ARRAY_SIZE(snd_ac97_cm9739_controls_spdif));
+}
+
+static struct snd_ac97_build_ops patch_cm9739_ops = {
+	.build_specific	= patch_cm9739_specific,
+	.build_post_spdif = patch_cm9739_post_spdif,
+	.update_jacks = cm9739_update_jacks
+};
+
+static int patch_cm9739(struct snd_ac97 * ac97)
+{
+	unsigned short val;
+
+	ac97->build_ops = &patch_cm9739_ops;
+
+	/* CM9739/A has no Master and PCM volume although the register reacts */
+	ac97->flags |= AC97_HAS_NO_MASTER_VOL | AC97_HAS_NO_PCM_VOL;
+	snd_ac97_write_cache(ac97, AC97_MASTER, 0x8000);
+	snd_ac97_write_cache(ac97, AC97_PCM, 0x8000);
+
+	/* check spdif */
+	val = snd_ac97_read(ac97, AC97_EXTENDED_STATUS);
+	if (val & AC97_EA_SPCV) {
+		/* enable spdif in */
+		snd_ac97_write_cache(ac97, AC97_CM9739_SPDIF_CTRL,
+				     snd_ac97_read(ac97, AC97_CM9739_SPDIF_CTRL) | 0x01);
+		ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */
+	} else {
+		ac97->ext_id &= ~AC97_EI_SPDIF; /* disable extended-id */
+		ac97->rates[AC97_RATES_SPDIF] = 0;
+	}
+
+	/* set-up multi channel */
+	/* bit 14: 0 = SPDIF, 1 = EAPD */
+	/* bit 13: enable internal vref output for mic */
+	/* bit 12: disable center/lfe (swithable) */
+	/* bit 10: disable surround/line (switchable) */
+	/* bit 9: mix 2 surround off */
+	/* bit 4: undocumented; 0 mutes the CM9739A, which defaults to 1 */
+	/* bit 3: undocumented; surround? */
+	/* bit 0: dB */
+	val = snd_ac97_read(ac97, AC97_CM9739_MULTI_CHAN) & (1 << 4);
+	val |= (1 << 3);
+	val |= (1 << 13);
+	if (! (ac97->ext_id & AC97_EI_SPDIF))
+		val |= (1 << 14);
+	snd_ac97_write_cache(ac97, AC97_CM9739_MULTI_CHAN, val);
+
+	/* FIXME: set up GPIO */
+	snd_ac97_write_cache(ac97, 0x70, 0x0100);
+	snd_ac97_write_cache(ac97, 0x72, 0x0020);
+	/* Special exception for ASUS W1000/CMI9739. It does not have an SPDIF in. */
+	if (ac97->pci &&
+	     ac97->subsystem_vendor == 0x1043 &&
+	     ac97->subsystem_device == 0x1843) {
+		snd_ac97_write_cache(ac97, AC97_CM9739_SPDIF_CTRL,
+			snd_ac97_read(ac97, AC97_CM9739_SPDIF_CTRL) & ~0x01);
+		snd_ac97_write_cache(ac97, AC97_CM9739_MULTI_CHAN,
+			snd_ac97_read(ac97, AC97_CM9739_MULTI_CHAN) | (1 << 14));
+	}
+
+	return 0;
+}
+
+#define AC97_CM9761_MULTI_CHAN	0x64
+#define AC97_CM9761_FUNC	0x66
+#define AC97_CM9761_SPDIF_CTRL	0x6c
+
+static void cm9761_update_jacks(struct snd_ac97 *ac97)
+{
+	/* FIXME: check the bits for each model
+	 *        model 83 is confirmed to work
+	 */
+	static unsigned short surr_on[3][2] = {
+		{ 0x0008, 0x0000 }, /* 9761-78 & 82 */
+		{ 0x0000, 0x0008 }, /* 9761-82 rev.B */
+		{ 0x0000, 0x0008 }, /* 9761-83 */
+	};
+	static unsigned short clfe_on[3][2] = {
+		{ 0x0000, 0x1000 }, /* 9761-78 & 82 */
+		{ 0x1000, 0x0000 }, /* 9761-82 rev.B */
+		{ 0x0000, 0x1000 }, /* 9761-83 */
+	};
+	static unsigned short surr_shared[3][2] = {
+		{ 0x0000, 0x0400 }, /* 9761-78 & 82 */
+		{ 0x0000, 0x0400 }, /* 9761-82 rev.B */
+		{ 0x0000, 0x0400 }, /* 9761-83 */
+	};
+	static unsigned short clfe_shared[3][2] = {
+		{ 0x2000, 0x0880 }, /* 9761-78 & 82 */
+		{ 0x0000, 0x2880 }, /* 9761-82 rev.B */
+		{ 0x2000, 0x0800 }, /* 9761-83 */
+	};
+	unsigned short val = 0;
+
+	val |= surr_on[ac97->spec.dev_flags][is_surround_on(ac97)];
+	val |= clfe_on[ac97->spec.dev_flags][is_clfe_on(ac97)];
+	val |= surr_shared[ac97->spec.dev_flags][is_shared_surrout(ac97)];
+	val |= clfe_shared[ac97->spec.dev_flags][is_shared_clfeout(ac97)];
+
+	snd_ac97_update_bits(ac97, AC97_CM9761_MULTI_CHAN, 0x3c88, val);
+}
+
+static const struct snd_kcontrol_new snd_ac97_cm9761_controls[] = {
+	AC97_SURROUND_JACK_MODE_CTL,
+	AC97_CHANNEL_MODE_CTL,
+};
+
+static int cm9761_spdif_out_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "AC-Link", "ADC", "SPDIF-In" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int cm9761_spdif_out_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+	if (ac97->regs[AC97_CM9761_FUNC] & 0x1)
+		ucontrol->value.enumerated.item[0] = 2; /* SPDIF-loopback */
+	else if (ac97->regs[AC97_CM9761_SPDIF_CTRL] & 0x2)
+		ucontrol->value.enumerated.item[0] = 1; /* ADC loopback */
+	else
+		ucontrol->value.enumerated.item[0] = 0; /* AC-link */
+	return 0;
+}
+
+static int cm9761_spdif_out_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+
+	if (ucontrol->value.enumerated.item[0] == 2)
+		return snd_ac97_update_bits(ac97, AC97_CM9761_FUNC, 0x1, 0x1);
+	snd_ac97_update_bits(ac97, AC97_CM9761_FUNC, 0x1, 0);
+	return snd_ac97_update_bits(ac97, AC97_CM9761_SPDIF_CTRL, 0x2,
+				    ucontrol->value.enumerated.item[0] == 1 ? 0x2 : 0);
+}
+
+static const char *cm9761_dac_clock[] = { "AC-Link", "SPDIF-In", "Both" };
+static const struct ac97_enum cm9761_dac_clock_enum =
+	AC97_ENUM_SINGLE(AC97_CM9761_SPDIF_CTRL, 9, 3, cm9761_dac_clock);
+
+static const struct snd_kcontrol_new snd_ac97_cm9761_controls_spdif[] = {
+	{ /* BIT 1: SPDIFS */
+		.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name	= SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+		.info = cm9761_spdif_out_source_info,
+		.get = cm9761_spdif_out_source_get,
+		.put = cm9761_spdif_out_source_put,
+	},
+	/* BIT 2: IG_SPIV */
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Valid Switch", AC97_CM9761_SPDIF_CTRL, 2, 1, 0),
+	/* BIT 3: SPI2F */
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,NONE) "Monitor", AC97_CM9761_SPDIF_CTRL, 3, 1, 0), 
+	/* BIT 4: SPI2SDI */
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), AC97_CM9761_SPDIF_CTRL, 4, 1, 0),
+	/* BIT 9-10: DAC_CTL */
+	AC97_ENUM("DAC Clock Source", cm9761_dac_clock_enum),
+};
+
+static int patch_cm9761_post_spdif(struct snd_ac97 * ac97)
+{
+	return patch_build_controls(ac97, snd_ac97_cm9761_controls_spdif, ARRAY_SIZE(snd_ac97_cm9761_controls_spdif));
+}
+
+static int patch_cm9761_specific(struct snd_ac97 * ac97)
+{
+	return patch_build_controls(ac97, snd_ac97_cm9761_controls, ARRAY_SIZE(snd_ac97_cm9761_controls));
+}
+
+static struct snd_ac97_build_ops patch_cm9761_ops = {
+	.build_specific	= patch_cm9761_specific,
+	.build_post_spdif = patch_cm9761_post_spdif,
+	.update_jacks = cm9761_update_jacks
+};
+
+static int patch_cm9761(struct snd_ac97 *ac97)
+{
+	unsigned short val;
+
+	/* CM9761 has no PCM volume although the register reacts */
+	/* Master volume seems to have _some_ influence on the analog
+	 * input sounds
+	 */
+	ac97->flags |= /*AC97_HAS_NO_MASTER_VOL |*/ AC97_HAS_NO_PCM_VOL;
+	snd_ac97_write_cache(ac97, AC97_MASTER, 0x8808);
+	snd_ac97_write_cache(ac97, AC97_PCM, 0x8808);
+
+	ac97->spec.dev_flags = 0; /* 1 = model 82 revision B, 2 = model 83 */
+	if (ac97->id == AC97_ID_CM9761_82) {
+		unsigned short tmp;
+		/* check page 1, reg 0x60 */
+		val = snd_ac97_read(ac97, AC97_INT_PAGING);
+		snd_ac97_write_cache(ac97, AC97_INT_PAGING, (val & ~0x0f) | 0x01);
+		tmp = snd_ac97_read(ac97, 0x60);
+		ac97->spec.dev_flags = tmp & 1; /* revision B? */
+		snd_ac97_write_cache(ac97, AC97_INT_PAGING, val);
+	} else if (ac97->id == AC97_ID_CM9761_83)
+		ac97->spec.dev_flags = 2;
+
+	ac97->build_ops = &patch_cm9761_ops;
+
+	/* enable spdif */
+	/* force the SPDIF bit in ext_id - codec doesn't set this bit! */
+        ac97->ext_id |= AC97_EI_SPDIF;
+	/* to be sure: we overwrite the ext status bits */
+	snd_ac97_write_cache(ac97, AC97_EXTENDED_STATUS, 0x05c0);
+	/* Don't set 0x0200 here.  This results in the silent analog output */
+	snd_ac97_write_cache(ac97, AC97_CM9761_SPDIF_CTRL, 0x0001); /* enable spdif-in */
+	ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */
+
+	/* set-up multi channel */
+	/* bit 15: pc master beep off
+	 * bit 14: pin47 = EAPD/SPDIF
+	 * bit 13: vref ctl [= cm9739]
+	 * bit 12: CLFE control (reverted on rev B)
+	 * bit 11: Mic/center share (reverted on rev B)
+	 * bit 10: suddound/line share
+	 * bit  9: Analog-in mix -> surround
+	 * bit  8: Analog-in mix -> CLFE
+	 * bit  7: Mic/LFE share (mic/center/lfe)
+	 * bit  5: vref select (9761A)
+	 * bit  4: front control
+	 * bit  3: surround control (revereted with rev B)
+	 * bit  2: front mic
+	 * bit  1: stereo mic
+	 * bit  0: mic boost level (0=20dB, 1=30dB)
+	 */
+
+#if 0
+	if (ac97->spec.dev_flags)
+		val = 0x0214;
+	else
+		val = 0x321c;
+#endif
+	val = snd_ac97_read(ac97, AC97_CM9761_MULTI_CHAN);
+	val |= (1 << 4); /* front on */
+	snd_ac97_write_cache(ac97, AC97_CM9761_MULTI_CHAN, val);
+
+	/* FIXME: set up GPIO */
+	snd_ac97_write_cache(ac97, 0x70, 0x0100);
+	snd_ac97_write_cache(ac97, 0x72, 0x0020);
+
+	return 0;
+}
+       
+#define AC97_CM9780_SIDE	0x60
+#define AC97_CM9780_JACK	0x62
+#define AC97_CM9780_MIXER	0x64
+#define AC97_CM9780_MULTI_CHAN	0x66
+#define AC97_CM9780_SPDIF	0x6c
+
+static const char *cm9780_ch_select[] = { "Front", "Side", "Center/LFE", "Rear" };
+static const struct ac97_enum cm9780_ch_select_enum =
+	AC97_ENUM_SINGLE(AC97_CM9780_MULTI_CHAN, 6, 4, cm9780_ch_select);
+static const struct snd_kcontrol_new cm9780_controls[] = {
+	AC97_DOUBLE("Side Playback Switch", AC97_CM9780_SIDE, 15, 7, 1, 1),
+	AC97_DOUBLE("Side Playback Volume", AC97_CM9780_SIDE, 8, 0, 31, 0),
+	AC97_ENUM("Side Playback Route", cm9780_ch_select_enum),
+};
+
+static int patch_cm9780_specific(struct snd_ac97 *ac97)
+{
+	return patch_build_controls(ac97, cm9780_controls, ARRAY_SIZE(cm9780_controls));
+}
+
+static struct snd_ac97_build_ops patch_cm9780_ops = {
+	.build_specific	= patch_cm9780_specific,
+	.build_post_spdif = patch_cm9761_post_spdif	/* identical with CM9761 */
+};
+
+static int patch_cm9780(struct snd_ac97 *ac97)
+{
+	unsigned short val;
+
+	ac97->build_ops = &patch_cm9780_ops;
+
+	/* enable spdif */
+	if (ac97->ext_id & AC97_EI_SPDIF) {
+		ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */
+		val = snd_ac97_read(ac97, AC97_CM9780_SPDIF);
+		val |= 0x1; /* SPDI_EN */
+		snd_ac97_write_cache(ac97, AC97_CM9780_SPDIF, val);
+	}
+
+	return 0;
+}
+
+/*
+ * VIA VT1616 codec
+ */
+static const struct snd_kcontrol_new snd_ac97_controls_vt1616[] = {
+AC97_SINGLE("DC Offset removal", 0x5a, 10, 1, 0),
+AC97_SINGLE("Alternate Level to Surround Out", 0x5a, 15, 1, 0),
+AC97_SINGLE("Downmix LFE and Center to Front", 0x5a, 12, 1, 0),
+AC97_SINGLE("Downmix Surround to Front", 0x5a, 11, 1, 0),
+};
+
+static const char *slave_vols_vt1616[] = {
+	"Front Playback Volume",
+	"Surround Playback Volume",
+	"Center Playback Volume",
+	"LFE Playback Volume",
+	NULL
+};
+
+static const char *slave_sws_vt1616[] = {
+	"Front Playback Switch",
+	"Surround Playback Switch",
+	"Center Playback Switch",
+	"LFE Playback Switch",
+	NULL
+};
+
+/* find a mixer control element with the given name */
+static struct snd_kcontrol *snd_ac97_find_mixer_ctl(struct snd_ac97 *ac97,
+						    const char *name)
+{
+	struct snd_ctl_elem_id id;
+	memset(&id, 0, sizeof(id));
+	id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(id.name, name);
+	return snd_ctl_find_id(ac97->bus->card, &id);
+}
+
+/* create a virtual master control and add slaves */
+static int snd_ac97_add_vmaster(struct snd_ac97 *ac97, char *name,
+				const unsigned int *tlv, const char **slaves)
+{
+	struct snd_kcontrol *kctl;
+	const char **s;
+	int err;
+
+	kctl = snd_ctl_make_virtual_master(name, tlv);
+	if (!kctl)
+		return -ENOMEM;
+	err = snd_ctl_add(ac97->bus->card, kctl);
+	if (err < 0)
+		return err;
+
+	for (s = slaves; *s; s++) {
+		struct snd_kcontrol *sctl;
+
+		sctl = snd_ac97_find_mixer_ctl(ac97, *s);
+		if (!sctl) {
+			snd_printdd("Cannot find slave %s, skipped\n", *s);
+			continue;
+		}
+		err = snd_ctl_add_slave(kctl, sctl);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int patch_vt1616_specific(struct snd_ac97 * ac97)
+{
+	struct snd_kcontrol *kctl;
+	int err;
+
+	if (snd_ac97_try_bit(ac97, 0x5a, 9))
+		if ((err = patch_build_controls(ac97, &snd_ac97_controls_vt1616[0], 1)) < 0)
+			return err;
+	if ((err = patch_build_controls(ac97, &snd_ac97_controls_vt1616[1], ARRAY_SIZE(snd_ac97_controls_vt1616) - 1)) < 0)
+		return err;
+
+	/* There is already a misnamed master switch.  Rename it.  */
+	kctl = snd_ac97_find_mixer_ctl(ac97, "Master Playback Volume");
+	if (!kctl)
+		return -EINVAL;
+
+	snd_ac97_rename_vol_ctl(ac97, "Master Playback", "Front Playback");
+
+	err = snd_ac97_add_vmaster(ac97, "Master Playback Volume",
+				   kctl->tlv.p, slave_vols_vt1616);
+	if (err < 0)
+		return err;
+
+	err = snd_ac97_add_vmaster(ac97, "Master Playback Switch",
+				   NULL, slave_sws_vt1616);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_vt1616_ops = {
+	.build_specific	= patch_vt1616_specific
+};
+
+static int patch_vt1616(struct snd_ac97 * ac97)
+{
+	ac97->build_ops = &patch_vt1616_ops;
+	return 0;
+}
+
+/*
+ * VT1617A codec
+ */
+
+/*
+ * unfortunately, the vt1617a stashes the twiddlers required for
+ * noodling the i/o jacks on 2 different regs. that means that we can't
+ * use the easy way provided by AC97_ENUM_DOUBLE() we have to write
+ * are own funcs.
+ *
+ * NB: this is absolutely and utterly different from the vt1618. dunno
+ * about the 1616.
+ */
+
+/* copied from ac97_surround_jack_mode_info() */
+static int snd_ac97_vt1617a_smart51_info(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_info *uinfo)
+{
+	/* ordering in this list reflects vt1617a docs for Reg 20 and
+	 * 7a and Table 6 that lays out the matrix NB WRT Table6: SM51
+	 * is SM51EN *AND* it's Bit14, not Bit15 so the table is very
+	 * counter-intuitive */ 
+
+	static const char* texts[] = { "LineIn Mic1", "LineIn Mic1 Mic3",
+				       "Surr LFE/C Mic3", "LineIn LFE/C Mic3",
+				       "LineIn Mic2", "LineIn Mic2 Mic1",
+				       "Surr LFE Mic1", "Surr LFE Mic1 Mic2"};
+	return ac97_enum_text_info(kcontrol, uinfo, texts, 8);
+}
+
+static int snd_ac97_vt1617a_smart51_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	ushort usSM51, usMS;  
+
+	struct snd_ac97 *pac97;
+	
+	pac97 = snd_kcontrol_chip(kcontrol); /* grab codec handle */
+
+	/* grab our desired bits, then mash them together in a manner
+	 * consistent with Table 6 on page 17 in the 1617a docs */
+ 
+	usSM51 = snd_ac97_read(pac97, 0x7a) >> 14;
+	usMS   = snd_ac97_read(pac97, 0x20) >> 8;
+  
+	ucontrol->value.enumerated.item[0] = (usSM51 << 1) + usMS;
+
+	return 0;
+}
+
+static int snd_ac97_vt1617a_smart51_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	ushort usSM51, usMS, usReg;  
+
+	struct snd_ac97 *pac97;
+
+	pac97 = snd_kcontrol_chip(kcontrol); /* grab codec handle */
+
+	usSM51 = ucontrol->value.enumerated.item[0] >> 1;
+	usMS   = ucontrol->value.enumerated.item[0] &  1;
+
+	/* push our values into the register - consider that things will be left
+	 * in a funky state if the write fails */
+
+	usReg = snd_ac97_read(pac97, 0x7a);
+	snd_ac97_write_cache(pac97, 0x7a, (usReg & 0x3FFF) + (usSM51 << 14));
+	usReg = snd_ac97_read(pac97, 0x20);
+	snd_ac97_write_cache(pac97, 0x20, (usReg & 0xFEFF) + (usMS   <<  8));
+
+	return 0;
+}
+
+static const struct snd_kcontrol_new snd_ac97_controls_vt1617a[] = {
+
+	AC97_SINGLE("Center/LFE Exchange", 0x5a, 8, 1, 0),
+	/*
+	 * These are used to enable/disable surround sound on motherboards
+	 * that have 3 bidirectional analog jacks
+	 */
+	{
+		.iface         = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name          = "Smart 5.1 Select",
+		.info          = snd_ac97_vt1617a_smart51_info,
+		.get           = snd_ac97_vt1617a_smart51_get,
+		.put           = snd_ac97_vt1617a_smart51_put,
+	},
+};
+
+static int patch_vt1617a(struct snd_ac97 * ac97)
+{
+	int err = 0;
+	int val;
+
+	/* we choose to not fail out at this point, but we tell the
+	   caller when we return */
+
+	err = patch_build_controls(ac97, &snd_ac97_controls_vt1617a[0],
+				   ARRAY_SIZE(snd_ac97_controls_vt1617a));
+
+	/* bring analog power consumption to normal by turning off the
+	 * headphone amplifier, like WinXP driver for EPIA SP
+	 */
+	/* We need to check the bit before writing it.
+	 * On some (many?) hardwares, setting bit actually clears it!
+	 */
+	val = snd_ac97_read(ac97, 0x5c);
+	if (!(val & 0x20))
+		snd_ac97_write_cache(ac97, 0x5c, 0x20);
+
+	ac97->ext_id |= AC97_EI_SPDIF;	/* force the detection of spdif */
+	ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000;
+	ac97->build_ops = &patch_vt1616_ops;
+
+	return err;
+}
+
+/* VIA VT1618 8 CHANNEL AC97 CODEC
+ *
+ * VIA implements 'Smart 5.1' completely differently on the 1618 than
+ * it does on the 1617a. awesome! They seem to have sourced this
+ * particular revision of the technology from somebody else, it's
+ * called Universal Audio Jack and it shows up on some other folk's chips
+ * as well.
+ *
+ * ordering in this list reflects vt1618 docs for Reg 60h and
+ * the block diagram, DACs are as follows:
+ *
+ *        OUT_O -> Front,
+ *	  OUT_1 -> Surround,
+ *	  OUT_2 -> C/LFE
+ *
+ * Unlike the 1617a, each OUT has a consistent set of mappings
+ * for all bitpatterns other than 00:
+ *
+ *        01       Unmixed Output
+ *        10       Line In
+ *        11       Mic  In
+ *
+ * Special Case of 00:
+ *
+ *        OUT_0    Mixed Output
+ *        OUT_1    Reserved
+ *        OUT_2    Reserved
+ *
+ * I have no idea what the hell Reserved does, but on an MSI
+ * CN700T, i have to set it to get 5.1 output - YMMV, bad
+ * shit may happen.
+ *
+ * If other chips use Universal Audio Jack, then this code might be applicable
+ * to them.
+ */
+
+struct vt1618_uaj_item {
+	unsigned short mask;
+	unsigned short shift;
+	const char *items[4];
+};
+
+/* This list reflects the vt1618 docs for Vendor Defined Register 0x60. */
+
+static struct vt1618_uaj_item vt1618_uaj[3] = {
+	{
+		/* speaker jack */
+		.mask  = 0x03,
+		.shift = 0,
+		.items = {
+			"Speaker Out", "DAC Unmixed Out", "Line In", "Mic In"
+		}
+	},
+	{
+		/* line jack */
+		.mask  = 0x0c,
+		.shift = 2,
+		.items = {
+			"Surround Out", "DAC Unmixed Out", "Line In", "Mic In"
+		}
+	},
+	{
+		/* mic jack */
+		.mask  = 0x30,
+		.shift = 4,
+		.items = {
+			"Center LFE Out", "DAC Unmixed Out", "Line In", "Mic In"
+		},
+	},
+};
+
+static int snd_ac97_vt1618_UAJ_info(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	return ac97_enum_text_info(kcontrol, uinfo,
+				   vt1618_uaj[kcontrol->private_value].items,
+				   4);
+}
+
+/* All of the vt1618 Universal Audio Jack twiddlers are on
+ * Vendor Defined Register 0x60, page 0. The bits, and thus
+ * the mask, are the only thing that changes
+ */
+static int snd_ac97_vt1618_UAJ_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned short datpag, uaj;
+	struct snd_ac97 *pac97 = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&pac97->page_mutex);
+
+	datpag = snd_ac97_read(pac97, AC97_INT_PAGING) & AC97_PAGE_MASK;
+	snd_ac97_update_bits(pac97, AC97_INT_PAGING, AC97_PAGE_MASK, 0);
+
+	uaj = snd_ac97_read(pac97, 0x60) &
+		vt1618_uaj[kcontrol->private_value].mask;
+
+	snd_ac97_update_bits(pac97, AC97_INT_PAGING, AC97_PAGE_MASK, datpag);
+	mutex_unlock(&pac97->page_mutex);
+
+	ucontrol->value.enumerated.item[0] = uaj >>
+		vt1618_uaj[kcontrol->private_value].shift;
+
+	return 0;
+}
+
+static int snd_ac97_vt1618_UAJ_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	return ac97_update_bits_page(snd_kcontrol_chip(kcontrol), 0x60,
+				     vt1618_uaj[kcontrol->private_value].mask,
+				     ucontrol->value.enumerated.item[0]<<
+				     vt1618_uaj[kcontrol->private_value].shift,
+				     0);
+}
+
+/* config aux in jack - not found on 3 jack motherboards or soundcards */
+
+static int snd_ac97_vt1618_aux_info(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	static const char *txt_aux[] = {"Aux In", "Back Surr Out"};
+
+	return ac97_enum_text_info(kcontrol, uinfo, txt_aux, 2);
+}
+
+static int snd_ac97_vt1618_aux_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.enumerated.item[0] =
+		(snd_ac97_read(snd_kcontrol_chip(kcontrol), 0x5c) & 0x0008)>>3;
+	return 0;
+}
+
+static int snd_ac97_vt1618_aux_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	/* toggle surround rear dac power */
+
+	snd_ac97_update_bits(snd_kcontrol_chip(kcontrol), 0x5c, 0x0008,
+			     ucontrol->value.enumerated.item[0] << 3);
+
+	/* toggle aux in surround rear out jack */
+
+	return snd_ac97_update_bits(snd_kcontrol_chip(kcontrol), 0x76, 0x0008,
+				    ucontrol->value.enumerated.item[0] << 3);
+}
+
+static const struct snd_kcontrol_new snd_ac97_controls_vt1618[] = {
+	AC97_SINGLE("Exchange Center/LFE", 0x5a,  8, 1,     0),
+	AC97_SINGLE("DC Offset",           0x5a, 10, 1,     0),
+	AC97_SINGLE("Soft Mute",           0x5c,  0, 1,     1),
+	AC97_SINGLE("Headphone Amp",       0x5c,  5, 1,     1),
+	AC97_DOUBLE("Back Surr Volume",    0x5e,  8, 0, 31, 1),
+	AC97_SINGLE("Back Surr Switch",    0x5e, 15, 1,     1),
+	{
+		.iface         = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name          = "Speaker Jack Mode",
+		.info          = snd_ac97_vt1618_UAJ_info,
+		.get           = snd_ac97_vt1618_UAJ_get,
+		.put           = snd_ac97_vt1618_UAJ_put,
+		.private_value = 0
+	},
+	{
+		.iface         = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name          = "Line Jack Mode",
+		.info          = snd_ac97_vt1618_UAJ_info,
+		.get           = snd_ac97_vt1618_UAJ_get,
+		.put           = snd_ac97_vt1618_UAJ_put,
+		.private_value = 1
+	},
+	{
+		.iface         = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name          = "Mic Jack Mode",
+		.info          = snd_ac97_vt1618_UAJ_info,
+		.get           = snd_ac97_vt1618_UAJ_get,
+		.put           = snd_ac97_vt1618_UAJ_put,
+		.private_value = 2
+	},
+	{
+		.iface         = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name          = "Aux Jack Mode",
+		.info          = snd_ac97_vt1618_aux_info,
+		.get           = snd_ac97_vt1618_aux_get,
+		.put           = snd_ac97_vt1618_aux_put,
+	}
+};
+
+static int patch_vt1618(struct snd_ac97 *ac97)
+{
+	return patch_build_controls(ac97, snd_ac97_controls_vt1618,
+				    ARRAY_SIZE(snd_ac97_controls_vt1618));
+}
+
+/*
+ */
+static void it2646_update_jacks(struct snd_ac97 *ac97)
+{
+	/* shared Line-In / Surround Out */
+	snd_ac97_update_bits(ac97, 0x76, 1 << 9,
+			     is_shared_surrout(ac97) ? (1<<9) : 0);
+	/* shared Mic / Center/LFE Out */
+	snd_ac97_update_bits(ac97, 0x76, 1 << 10,
+			     is_shared_clfeout(ac97) ? (1<<10) : 0);
+}
+
+static const struct snd_kcontrol_new snd_ac97_controls_it2646[] = {
+	AC97_SURROUND_JACK_MODE_CTL,
+	AC97_CHANNEL_MODE_CTL,
+};
+
+static const struct snd_kcontrol_new snd_ac97_spdif_controls_it2646[] = {
+	AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), 0x76, 11, 1, 0),
+	AC97_SINGLE("Analog to IEC958 Output", 0x76, 12, 1, 0),
+	AC97_SINGLE("IEC958 Input Monitor", 0x76, 13, 1, 0),
+};
+
+static int patch_it2646_specific(struct snd_ac97 * ac97)
+{
+	int err;
+	if ((err = patch_build_controls(ac97, snd_ac97_controls_it2646, ARRAY_SIZE(snd_ac97_controls_it2646))) < 0)
+		return err;
+	if ((err = patch_build_controls(ac97, snd_ac97_spdif_controls_it2646, ARRAY_SIZE(snd_ac97_spdif_controls_it2646))) < 0)
+		return err;
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_it2646_ops = {
+	.build_specific	= patch_it2646_specific,
+	.update_jacks = it2646_update_jacks
+};
+
+static int patch_it2646(struct snd_ac97 * ac97)
+{
+	ac97->build_ops = &patch_it2646_ops;
+	/* full DAC volume */
+	snd_ac97_write_cache(ac97, 0x5E, 0x0808);
+	snd_ac97_write_cache(ac97, 0x7A, 0x0808);
+	return 0;
+}
+
+/*
+ * Si3036 codec
+ */
+
+#define AC97_SI3036_CHIP_ID     0x5a
+#define AC97_SI3036_LINE_CFG    0x5c
+
+static const struct snd_kcontrol_new snd_ac97_controls_si3036[] = {
+AC97_DOUBLE("Modem Speaker Volume", 0x5c, 14, 12, 3, 1)
+};
+
+static int patch_si3036_specific(struct snd_ac97 * ac97)
+{
+	int idx, err;
+	for (idx = 0; idx < ARRAY_SIZE(snd_ac97_controls_si3036); idx++)
+		if ((err = snd_ctl_add(ac97->bus->card, snd_ctl_new1(&snd_ac97_controls_si3036[idx], ac97))) < 0)
+			return err;
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_si3036_ops = {
+	.build_specific	= patch_si3036_specific,
+};
+
+static int mpatch_si3036(struct snd_ac97 * ac97)
+{
+	ac97->build_ops = &patch_si3036_ops;
+	snd_ac97_write_cache(ac97, 0x5c, 0xf210 );
+	snd_ac97_write_cache(ac97, 0x68, 0);
+	return 0;
+}
+
+/*
+ * LM 4550 Codec
+ *
+ * We use a static resolution table since LM4550 codec cannot be
+ * properly autoprobed to determine the resolution via
+ * check_volume_resolution().
+ */
+
+static struct snd_ac97_res_table lm4550_restbl[] = {
+	{ AC97_MASTER, 0x1f1f },
+	{ AC97_HEADPHONE, 0x1f1f },
+	{ AC97_MASTER_MONO, 0x001f },
+	{ AC97_PC_BEEP, 0x001f },	/* LSB is ignored */
+	{ AC97_PHONE, 0x001f },
+	{ AC97_MIC, 0x001f },
+	{ AC97_LINE, 0x1f1f },
+	{ AC97_CD, 0x1f1f },
+	{ AC97_VIDEO, 0x1f1f },
+	{ AC97_AUX, 0x1f1f },
+	{ AC97_PCM, 0x1f1f },
+	{ AC97_REC_GAIN, 0x0f0f },
+	{ } /* terminator */
+};
+
+static int patch_lm4550(struct snd_ac97 *ac97)
+{
+	ac97->res_table = lm4550_restbl;
+	return 0;
+}
+
+/* 
+ *  UCB1400 codec (http://www.semiconductors.philips.com/acrobat_download/datasheets/UCB1400-02.pdf)
+ */
+static const struct snd_kcontrol_new snd_ac97_controls_ucb1400[] = {
+/* enable/disable headphone driver which allows direct connection to
+   stereo headphone without the use of external DC blocking
+   capacitors */
+AC97_SINGLE("Headphone Driver", 0x6a, 6, 1, 0),
+/* Filter used to compensate the DC offset is added in the ADC to remove idle
+   tones from the audio band. */
+AC97_SINGLE("DC Filter", 0x6a, 4, 1, 0),
+/* Control smart-low-power mode feature. Allows automatic power down
+   of unused blocks in the ADC analog front end and the PLL. */
+AC97_SINGLE("Smart Low Power Mode", 0x6c, 4, 3, 0),
+};
+
+static int patch_ucb1400_specific(struct snd_ac97 * ac97)
+{
+	int idx, err;
+	for (idx = 0; idx < ARRAY_SIZE(snd_ac97_controls_ucb1400); idx++)
+		if ((err = snd_ctl_add(ac97->bus->card, snd_ctl_new1(&snd_ac97_controls_ucb1400[idx], ac97))) < 0)
+			return err;
+	return 0;
+}
+
+static struct snd_ac97_build_ops patch_ucb1400_ops = {
+	.build_specific	= patch_ucb1400_specific,
+};
+
+static int patch_ucb1400(struct snd_ac97 * ac97)
+{
+	ac97->build_ops = &patch_ucb1400_ops;
+	/* enable headphone driver and smart low power mode by default */
+	snd_ac97_write_cache(ac97, 0x6a, 0x0050);
+	snd_ac97_write_cache(ac97, 0x6c, 0x0030);
+	return 0;
+}
diff --git a/linux/sound/pci/ac97/ac97_patch.h b/linux/sound/pci/ac97/ac97_patch.h
new file mode 100644
index 0000000..47bf8df
--- /dev/null
+++ b/linux/sound/pci/ac97/ac97_patch.h
@@ -0,0 +1,95 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Universal interface for Audio Codec '97
+ *
+ *  For more details look to AC '97 component specification revision 2.2
+ *  by Intel Corporation (http://developer.intel.com).
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#define AC97_SINGLE_VALUE(reg,shift,mask,invert) \
+	((reg) | ((shift) << 8) | ((shift) << 12) | ((mask) << 16) | \
+	 ((invert) << 24))
+#define AC97_PAGE_SINGLE_VALUE(reg,shift,mask,invert,page) \
+	(AC97_SINGLE_VALUE(reg,shift,mask,invert) | (1<<25) | ((page) << 26))
+#define AC97_SINGLE(xname, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_ac97_info_volsw,		\
+  .get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \
+  .private_value =  AC97_SINGLE_VALUE(reg, shift, mask, invert) }
+#define AC97_PAGE_SINGLE(xname, reg, shift, mask, invert, page)		\
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_ac97_info_volsw,		\
+  .get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \
+  .private_value =  AC97_PAGE_SINGLE_VALUE(reg, shift, mask, invert, page) }
+#define AC97_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+  .info = snd_ac97_info_volsw,		\
+  .get = snd_ac97_get_volsw, .put = snd_ac97_put_volsw, \
+  .private_value = (reg) | ((shift_left) << 8) | ((shift_right) << 12) | ((mask) << 16) | ((invert) << 24) }
+
+/* enum control */
+struct ac97_enum {
+	unsigned char reg;
+	unsigned char shift_l;
+	unsigned char shift_r;
+	unsigned short mask;
+	const char **texts;
+};
+
+#define AC97_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, xtexts) \
+{ .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \
+  .mask = xmask, .texts = xtexts }
+#define AC97_ENUM_SINGLE(xreg, xshift, xmask, xtexts) \
+	AC97_ENUM_DOUBLE(xreg, xshift, xshift, xmask, xtexts)
+#define AC97_ENUM(xname, xenum) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_ac97_info_enum_double,		    \
+  .get = snd_ac97_get_enum_double, .put = snd_ac97_put_enum_double, \
+  .private_value = (unsigned long)&xenum }
+
+/* ac97_codec.c */
+static const struct snd_kcontrol_new snd_ac97_controls_3d[];
+static const struct snd_kcontrol_new snd_ac97_controls_spdif[];
+static struct snd_kcontrol *snd_ac97_cnew(const struct snd_kcontrol_new *_template,
+					  struct snd_ac97 * ac97);
+static int snd_ac97_info_volsw(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo);
+static int snd_ac97_get_volsw(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol);
+static int snd_ac97_put_volsw(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol);
+static int snd_ac97_try_bit(struct snd_ac97 * ac97, int reg, int bit);
+static int snd_ac97_remove_ctl(struct snd_ac97 *ac97, const char *name,
+			       const char *suffix);
+static int snd_ac97_rename_ctl(struct snd_ac97 *ac97, const char *src,
+			       const char *dst, const char *suffix);
+static int snd_ac97_swap_ctl(struct snd_ac97 *ac97, const char *s1,
+			     const char *s2, const char *suffix);
+static void snd_ac97_rename_vol_ctl(struct snd_ac97 *ac97, const char *src,
+				    const char *dst);
+#ifdef CONFIG_PM
+static void snd_ac97_restore_status(struct snd_ac97 *ac97);
+static void snd_ac97_restore_iec958(struct snd_ac97 *ac97);
+#endif
+static int snd_ac97_info_enum_double(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo);
+static int snd_ac97_get_enum_double(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol);
+static int snd_ac97_put_enum_double(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol);
diff --git a/linux/sound/pci/ac97/ac97_pcm.c b/linux/sound/pci/ac97/ac97_pcm.c
new file mode 100644
index 0000000..48cbda9
--- /dev/null
+++ b/linux/sound/pci/ac97/ac97_pcm.c
@@ -0,0 +1,736 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Universal interface for Audio Codec '97
+ *
+ *  For more details look to AC '97 component specification revision 2.2
+ *  by Intel Corporation (http://developer.intel.com) and to datasheets
+ *  for specific codecs.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#include "ac97_id.h"
+#include "ac97_local.h"
+
+/*
+ *  PCM support
+ */
+
+static unsigned char rate_reg_tables[2][4][9] = {
+{
+  /* standard rates */
+  {
+  	/* 3&4 front, 7&8 rear, 6&9 center/lfe */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 3 */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 4 */
+	0xff,				/* slot 5 */
+	AC97_PCM_LFE_DAC_RATE,		/* slot 6 */
+	AC97_PCM_SURR_DAC_RATE,		/* slot 7 */
+	AC97_PCM_SURR_DAC_RATE,		/* slot 8 */
+	AC97_PCM_LFE_DAC_RATE,		/* slot 9 */
+	0xff,				/* slot 10 */
+	0xff,				/* slot 11 */
+  },
+  {
+  	/* 7&8 front, 6&9 rear, 10&11 center/lfe */
+	0xff,				/* slot 3 */
+	0xff,				/* slot 4 */
+	0xff,				/* slot 5 */
+	AC97_PCM_SURR_DAC_RATE,		/* slot 6 */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 7 */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 8 */
+	AC97_PCM_SURR_DAC_RATE,		/* slot 9 */
+	AC97_PCM_LFE_DAC_RATE,		/* slot 10 */
+	AC97_PCM_LFE_DAC_RATE,		/* slot 11 */
+  },
+  {
+  	/* 6&9 front, 10&11 rear, 3&4 center/lfe */
+	AC97_PCM_LFE_DAC_RATE,		/* slot 3 */
+	AC97_PCM_LFE_DAC_RATE,		/* slot 4 */
+	0xff,				/* slot 5 */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 6 */
+	0xff,				/* slot 7 */
+	0xff,				/* slot 8 */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 9 */
+	AC97_PCM_SURR_DAC_RATE,		/* slot 10 */
+	AC97_PCM_SURR_DAC_RATE,		/* slot 11 */
+  },
+  {
+  	/* 10&11 front, 3&4 rear, 7&8 center/lfe */
+	AC97_PCM_SURR_DAC_RATE,		/* slot 3 */
+	AC97_PCM_SURR_DAC_RATE,		/* slot 4 */
+	0xff,				/* slot 5 */
+	0xff,				/* slot 6 */
+	AC97_PCM_LFE_DAC_RATE,		/* slot 7 */
+	AC97_PCM_LFE_DAC_RATE,		/* slot 8 */
+	0xff,				/* slot 9 */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 10 */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 11 */
+  },
+},
+{
+  /* double rates */
+  {
+  	/* 3&4 front, 7&8 front (t+1) */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 3 */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 4 */
+	0xff,				/* slot 5 */
+	0xff,				/* slot 6 */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 7 */
+	AC97_PCM_FRONT_DAC_RATE,	/* slot 8 */
+	0xff,				/* slot 9 */
+	0xff,				/* slot 10 */
+	0xff,				/* slot 11 */
+  },
+  {
+	/* not specified in the specification */
+	0xff,				/* slot 3 */
+	0xff,				/* slot 4 */
+	0xff,				/* slot 5 */
+	0xff,				/* slot 6 */
+	0xff,				/* slot 7 */
+	0xff,				/* slot 8 */
+	0xff,				/* slot 9 */
+	0xff,				/* slot 10 */
+	0xff,				/* slot 11 */
+  },
+  {
+	0xff,				/* slot 3 */
+	0xff,				/* slot 4 */
+	0xff,				/* slot 5 */
+	0xff,				/* slot 6 */
+	0xff,				/* slot 7 */
+	0xff,				/* slot 8 */
+	0xff,				/* slot 9 */
+	0xff,				/* slot 10 */
+	0xff,				/* slot 11 */
+  },
+  {
+	0xff,				/* slot 3 */
+	0xff,				/* slot 4 */
+	0xff,				/* slot 5 */
+	0xff,				/* slot 6 */
+	0xff,				/* slot 7 */
+	0xff,				/* slot 8 */
+	0xff,				/* slot 9 */
+	0xff,				/* slot 10 */
+	0xff,				/* slot 11 */
+  }
+}};
+
+/* FIXME: more various mappings for ADC? */
+static unsigned char rate_cregs[9] = {
+	AC97_PCM_LR_ADC_RATE,	/* 3 */
+	AC97_PCM_LR_ADC_RATE,	/* 4 */
+	0xff,			/* 5 */
+	AC97_PCM_MIC_ADC_RATE,	/* 6 */
+	0xff,			/* 7 */
+	0xff,			/* 8 */
+	0xff,			/* 9 */
+	0xff,			/* 10 */
+	0xff,			/* 11 */
+};
+
+static unsigned char get_slot_reg(struct ac97_pcm *pcm, unsigned short cidx,
+				  unsigned short slot, int dbl)
+{
+	if (slot < 3)
+		return 0xff;
+	if (slot > 11)
+		return 0xff;
+	if (pcm->spdif)
+		return AC97_SPDIF; /* pseudo register */
+	if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return rate_reg_tables[dbl][pcm->r[dbl].rate_table[cidx]][slot - 3];
+	else
+		return rate_cregs[slot - 3];
+}
+
+static int set_spdif_rate(struct snd_ac97 *ac97, unsigned short rate)
+{
+	unsigned short old, bits, reg, mask;
+	unsigned int sbits;
+
+	if (! (ac97->ext_id & AC97_EI_SPDIF))
+		return -ENODEV;
+
+	/* TODO: double rate support */
+	if (ac97->flags & AC97_CS_SPDIF) {
+		switch (rate) {
+		case 48000: bits = 0; break;
+		case 44100: bits = 1 << AC97_SC_SPSR_SHIFT; break;
+		default: /* invalid - disable output */
+			snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
+			return -EINVAL;
+		}
+		reg = AC97_CSR_SPDIF;
+		mask = 1 << AC97_SC_SPSR_SHIFT;
+	} else {
+		if (ac97->id == AC97_ID_CM9739 && rate != 48000) {
+			snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
+			return -EINVAL;
+		}
+		switch (rate) {
+		case 44100: bits = AC97_SC_SPSR_44K; break;
+		case 48000: bits = AC97_SC_SPSR_48K; break;
+		case 32000: bits = AC97_SC_SPSR_32K; break;
+		default: /* invalid - disable output */
+			snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
+			return -EINVAL;
+		}
+		reg = AC97_SPDIF;
+		mask = AC97_SC_SPSR_MASK;
+	}
+
+	mutex_lock(&ac97->reg_mutex);
+	old = snd_ac97_read(ac97, reg) & mask;
+	if (old != bits) {
+		snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, 0);
+		snd_ac97_update_bits_nolock(ac97, reg, mask, bits);
+		/* update the internal spdif bits */
+		sbits = ac97->spdif_status;
+		if (sbits & IEC958_AES0_PROFESSIONAL) {
+			sbits &= ~IEC958_AES0_PRO_FS;
+			switch (rate) {
+			case 44100: sbits |= IEC958_AES0_PRO_FS_44100; break;
+			case 48000: sbits |= IEC958_AES0_PRO_FS_48000; break;
+			case 32000: sbits |= IEC958_AES0_PRO_FS_32000; break;
+			}
+		} else {
+			sbits &= ~(IEC958_AES3_CON_FS << 24);
+			switch (rate) {
+			case 44100: sbits |= IEC958_AES3_CON_FS_44100<<24; break;
+			case 48000: sbits |= IEC958_AES3_CON_FS_48000<<24; break;
+			case 32000: sbits |= IEC958_AES3_CON_FS_32000<<24; break;
+			}
+		}
+		ac97->spdif_status = sbits;
+	}
+	snd_ac97_update_bits_nolock(ac97, AC97_EXTENDED_STATUS, AC97_EA_SPDIF, AC97_EA_SPDIF);
+	mutex_unlock(&ac97->reg_mutex);
+	return 0;
+}
+
+/**
+ * snd_ac97_set_rate - change the rate of the given input/output.
+ * @ac97: the ac97 instance
+ * @reg: the register to change
+ * @rate: the sample rate to set
+ *
+ * Changes the rate of the given input/output on the codec.
+ * If the codec doesn't support VAR, the rate must be 48000 (except
+ * for SPDIF).
+ *
+ * The valid registers are AC97_PMC_MIC_ADC_RATE,
+ * AC97_PCM_FRONT_DAC_RATE, AC97_PCM_LR_ADC_RATE.
+ * AC97_PCM_SURR_DAC_RATE and AC97_PCM_LFE_DAC_RATE are accepted
+ * if the codec supports them.
+ * AC97_SPDIF is accepted as a pseudo register to modify the SPDIF
+ * status bits.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ */
+int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate)
+{
+	int dbl;
+	unsigned int tmp;
+	
+	dbl = rate > 48000;
+	if (dbl) {
+		if (!(ac97->flags & AC97_DOUBLE_RATE))
+			return -EINVAL;
+		if (reg != AC97_PCM_FRONT_DAC_RATE)
+			return -EINVAL;
+	}
+
+	snd_ac97_update_power(ac97, reg, 1);
+	switch (reg) {
+	case AC97_PCM_MIC_ADC_RATE:
+		if ((ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_VRM) == 0)	/* MIC VRA */
+			if (rate != 48000)
+				return -EINVAL;
+		break;
+	case AC97_PCM_FRONT_DAC_RATE:
+	case AC97_PCM_LR_ADC_RATE:
+		if ((ac97->regs[AC97_EXTENDED_STATUS] & AC97_EA_VRA) == 0)	/* VRA */
+			if (rate != 48000 && rate != 96000)
+				return -EINVAL;
+		break;
+	case AC97_PCM_SURR_DAC_RATE:
+		if (! (ac97->scaps & AC97_SCAP_SURROUND_DAC))
+			return -EINVAL;
+		break;
+	case AC97_PCM_LFE_DAC_RATE:
+		if (! (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC))
+			return -EINVAL;
+		break;
+	case AC97_SPDIF:
+		/* special case */
+		return set_spdif_rate(ac97, rate);
+	default:
+		return -EINVAL;
+	}
+	if (dbl)
+		rate /= 2;
+	tmp = (rate * ac97->bus->clock) / 48000;
+	if (tmp > 65535)
+		return -EINVAL;
+	if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE)
+		snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
+				     AC97_EA_DRA, dbl ? AC97_EA_DRA : 0);
+	snd_ac97_update(ac97, reg, tmp & 0xffff);
+	snd_ac97_read(ac97, reg);
+	if ((ac97->ext_id & AC97_EI_DRA) && reg == AC97_PCM_FRONT_DAC_RATE) {
+		/* Intel controllers require double rate data to be put in
+		 * slots 7+8
+		 */
+		snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE,
+				     AC97_GP_DRSS_MASK,
+				     dbl ? AC97_GP_DRSS_78 : 0);
+		snd_ac97_read(ac97, AC97_GENERAL_PURPOSE);
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ac97_set_rate);
+
+static unsigned short get_pslots(struct snd_ac97 *ac97, unsigned char *rate_table, unsigned short *spdif_slots)
+{
+	if (!ac97_is_audio(ac97))
+		return 0;
+	if (ac97_is_rev22(ac97) || ac97_can_amap(ac97)) {
+		unsigned short slots = 0;
+		if (ac97_is_rev22(ac97)) {
+			/* Note: it's simply emulation of AMAP behaviour */
+			u16 es;
+			es = ac97->regs[AC97_EXTENDED_ID] &= ~AC97_EI_DACS_SLOT_MASK;
+			switch (ac97->addr) {
+			case 1:
+			case 2: es |= (1<<AC97_EI_DACS_SLOT_SHIFT); break;
+			case 3: es |= (2<<AC97_EI_DACS_SLOT_SHIFT); break;
+			}
+			snd_ac97_write_cache(ac97, AC97_EXTENDED_ID, es);
+		}
+		switch (ac97->addr) {
+		case 0:
+			slots |= (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
+			if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
+				slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
+			if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
+				slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
+			if (ac97->ext_id & AC97_EI_SPDIF) {
+				if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
+					*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT)|(1<<AC97_SLOT_SPDIF_RIGHT);
+				else if (!(ac97->scaps & AC97_SCAP_CENTER_LFE_DAC))
+					*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
+				else
+					*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
+			}
+			*rate_table = 0;
+			break;
+		case 1:
+		case 2:
+			slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
+			if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
+				slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
+			if (ac97->ext_id & AC97_EI_SPDIF) {
+				if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
+					*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
+				else
+					*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
+			}
+			*rate_table = 1;
+			break;
+		case 3:
+			slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
+			if (ac97->ext_id & AC97_EI_SPDIF)
+				*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
+			*rate_table = 2;
+			break;
+		}
+		return slots;
+	} else {
+		unsigned short slots;
+		slots = (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
+		if (ac97->scaps & AC97_SCAP_SURROUND_DAC)
+			slots |= (1<<AC97_SLOT_PCM_SLEFT)|(1<<AC97_SLOT_PCM_SRIGHT);
+		if (ac97->scaps & AC97_SCAP_CENTER_LFE_DAC)
+			slots |= (1<<AC97_SLOT_PCM_CENTER)|(1<<AC97_SLOT_LFE);
+		if (ac97->ext_id & AC97_EI_SPDIF) {
+			if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC))
+				*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT)|(1<<AC97_SLOT_SPDIF_RIGHT);
+			else if (!(ac97->scaps & AC97_SCAP_CENTER_LFE_DAC))
+				*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT1)|(1<<AC97_SLOT_SPDIF_RIGHT1);
+			else
+				*spdif_slots = (1<<AC97_SLOT_SPDIF_LEFT2)|(1<<AC97_SLOT_SPDIF_RIGHT2);
+		}
+		*rate_table = 0;
+		return slots;
+	}
+}
+
+static unsigned short get_cslots(struct snd_ac97 *ac97)
+{
+	unsigned short slots;
+
+	if (!ac97_is_audio(ac97))
+		return 0;
+	slots = (1<<AC97_SLOT_PCM_LEFT)|(1<<AC97_SLOT_PCM_RIGHT);
+	slots |= (1<<AC97_SLOT_MIC);
+	return slots;
+}
+
+static unsigned int get_rates(struct ac97_pcm *pcm, unsigned int cidx, unsigned short slots, int dbl)
+{
+	int i, idx;
+	unsigned int rates = ~0;
+	unsigned char reg;
+
+	for (i = 3; i < 12; i++) {
+		if (!(slots & (1 << i)))
+			continue;
+		reg = get_slot_reg(pcm, cidx, i, dbl);
+		switch (reg) {
+		case AC97_PCM_FRONT_DAC_RATE:	idx = AC97_RATES_FRONT_DAC; break;
+		case AC97_PCM_SURR_DAC_RATE:	idx = AC97_RATES_SURR_DAC; break;
+		case AC97_PCM_LFE_DAC_RATE:	idx = AC97_RATES_LFE_DAC; break;
+		case AC97_PCM_LR_ADC_RATE:	idx = AC97_RATES_ADC; break;
+		case AC97_PCM_MIC_ADC_RATE:	idx = AC97_RATES_MIC_ADC; break;
+		default:			idx = AC97_RATES_SPDIF; break;
+		}
+		rates &= pcm->r[dbl].codec[cidx]->rates[idx];
+	}
+	if (!dbl)
+		rates &= ~(SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 |
+			   SNDRV_PCM_RATE_96000);
+	return rates;
+}
+
+/**
+ * snd_ac97_pcm_assign - assign AC97 slots to given PCM streams
+ * @bus: the ac97 bus instance
+ * @pcms_count: count of PCMs to be assigned
+ * @pcms: PCMs to be assigned
+ *
+ * It assigns available AC97 slots for given PCMs. If none or only
+ * some slots are available, pcm->xxx.slots and pcm->xxx.rslots[] members
+ * are reduced and might be zero.
+ */
+int snd_ac97_pcm_assign(struct snd_ac97_bus *bus,
+			unsigned short pcms_count,
+			const struct ac97_pcm *pcms)
+{
+	int i, j, k;
+	const struct ac97_pcm *pcm;
+	struct ac97_pcm *rpcms, *rpcm;
+	unsigned short avail_slots[2][4];
+	unsigned char rate_table[2][4];
+	unsigned short tmp, slots;
+	unsigned short spdif_slots[4];
+	unsigned int rates;
+	struct snd_ac97 *codec;
+
+	rpcms = kcalloc(pcms_count, sizeof(struct ac97_pcm), GFP_KERNEL);
+	if (rpcms == NULL)
+		return -ENOMEM;
+	memset(avail_slots, 0, sizeof(avail_slots));
+	memset(rate_table, 0, sizeof(rate_table));
+	memset(spdif_slots, 0, sizeof(spdif_slots));
+	for (i = 0; i < 4; i++) {
+		codec = bus->codec[i];
+		if (!codec)
+			continue;
+		avail_slots[0][i] = get_pslots(codec, &rate_table[0][i], &spdif_slots[i]);
+		avail_slots[1][i] = get_cslots(codec);
+		if (!(codec->scaps & AC97_SCAP_INDEP_SDIN)) {
+			for (j = 0; j < i; j++) {
+				if (bus->codec[j])
+					avail_slots[1][i] &= ~avail_slots[1][j];
+			}
+		}
+	}
+	/* first step - exclusive devices */
+	for (i = 0; i < pcms_count; i++) {
+		pcm = &pcms[i];
+		rpcm = &rpcms[i];
+		/* low-level driver thinks that it's more clever */
+		if (pcm->copy_flag) {
+			*rpcm = *pcm;
+			continue;
+		}
+		rpcm->stream = pcm->stream;
+		rpcm->exclusive = pcm->exclusive;
+		rpcm->spdif = pcm->spdif;
+		rpcm->private_value = pcm->private_value;
+		rpcm->bus = bus;
+		rpcm->rates = ~0;
+		slots = pcm->r[0].slots;
+		for (j = 0; j < 4 && slots; j++) {
+			if (!bus->codec[j])
+				continue;
+			rates = ~0;
+			if (pcm->spdif && pcm->stream == 0)
+				tmp = spdif_slots[j];
+			else
+				tmp = avail_slots[pcm->stream][j];
+			if (pcm->exclusive) {
+				/* exclusive access */
+				tmp &= slots;
+				for (k = 0; k < i; k++) {
+					if (rpcm->stream == rpcms[k].stream)
+						tmp &= ~rpcms[k].r[0].rslots[j];
+				}
+			} else {
+				/* non-exclusive access */
+				tmp &= pcm->r[0].slots;
+			}
+			if (tmp) {
+				rpcm->r[0].rslots[j] = tmp;
+				rpcm->r[0].codec[j] = bus->codec[j];
+				rpcm->r[0].rate_table[j] = rate_table[pcm->stream][j];
+				if (bus->no_vra)
+					rates = SNDRV_PCM_RATE_48000;
+				else
+					rates = get_rates(rpcm, j, tmp, 0);
+				if (pcm->exclusive)
+					avail_slots[pcm->stream][j] &= ~tmp;
+			}
+			slots &= ~tmp;
+			rpcm->r[0].slots |= tmp;
+			rpcm->rates &= rates;
+		}
+		/* for double rate, we check the first codec only */
+		if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+		    bus->codec[0] && (bus->codec[0]->flags & AC97_DOUBLE_RATE) &&
+		    rate_table[pcm->stream][0] == 0) {
+			tmp = (1<<AC97_SLOT_PCM_LEFT) | (1<<AC97_SLOT_PCM_RIGHT) |
+			      (1<<AC97_SLOT_PCM_LEFT_0) | (1<<AC97_SLOT_PCM_RIGHT_0);
+			if ((tmp & pcm->r[1].slots) == tmp) {
+				rpcm->r[1].slots = tmp;
+				rpcm->r[1].rslots[0] = tmp;
+				rpcm->r[1].rate_table[0] = 0;
+				rpcm->r[1].codec[0] = bus->codec[0];
+				if (pcm->exclusive)
+					avail_slots[pcm->stream][0] &= ~tmp;
+				if (bus->no_vra)
+					rates = SNDRV_PCM_RATE_96000;
+				else
+					rates = get_rates(rpcm, 0, tmp, 1);
+				rpcm->rates |= rates;
+			}
+		}
+		if (rpcm->rates == ~0)
+			rpcm->rates = 0; /* not used */
+	}
+	bus->pcms_count = pcms_count;
+	bus->pcms = rpcms;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ac97_pcm_assign);
+
+/**
+ * snd_ac97_pcm_open - opens the given AC97 pcm
+ * @pcm: the ac97 pcm instance
+ * @rate: rate in Hz, if codec does not support VRA, this value must be 48000Hz
+ * @cfg: output stream characteristics
+ * @slots: a subset of allocated slots (snd_ac97_pcm_assign) for this pcm
+ *
+ * It locks the specified slots and sets the given rate to AC97 registers.
+ */
+int snd_ac97_pcm_open(struct ac97_pcm *pcm, unsigned int rate,
+		      enum ac97_pcm_cfg cfg, unsigned short slots)
+{
+	struct snd_ac97_bus *bus;
+	int i, cidx, r, ok_flag;
+	unsigned int reg_ok[4] = {0,0,0,0};
+	unsigned char reg;
+	int err = 0;
+
+	r = rate > 48000;
+	bus = pcm->bus;
+	if (cfg == AC97_PCM_CFG_SPDIF) {
+		for (cidx = 0; cidx < 4; cidx++)
+			if (bus->codec[cidx] && (bus->codec[cidx]->ext_id & AC97_EI_SPDIF)) {
+				err = set_spdif_rate(bus->codec[cidx], rate);
+				if (err < 0)
+					return err;
+			}
+	}
+	spin_lock_irq(&pcm->bus->bus_lock);
+	for (i = 3; i < 12; i++) {
+		if (!(slots & (1 << i)))
+			continue;
+		ok_flag = 0;
+		for (cidx = 0; cidx < 4; cidx++) {
+			if (bus->used_slots[pcm->stream][cidx] & (1 << i)) {
+				spin_unlock_irq(&pcm->bus->bus_lock);
+				err = -EBUSY;
+				goto error;
+			}
+			if (pcm->r[r].rslots[cidx] & (1 << i)) {
+				bus->used_slots[pcm->stream][cidx] |= (1 << i);
+				ok_flag++;
+			}
+		}
+		if (!ok_flag) {
+			spin_unlock_irq(&pcm->bus->bus_lock);
+			snd_printk(KERN_ERR "cannot find configuration for AC97 slot %i\n", i);
+			err = -EAGAIN;
+			goto error;
+		}
+	}
+	pcm->cur_dbl = r;
+	spin_unlock_irq(&pcm->bus->bus_lock);
+	for (i = 3; i < 12; i++) {
+		if (!(slots & (1 << i)))
+			continue;
+		for (cidx = 0; cidx < 4; cidx++) {
+			if (pcm->r[r].rslots[cidx] & (1 << i)) {
+				reg = get_slot_reg(pcm, cidx, i, r);
+				if (reg == 0xff) {
+					snd_printk(KERN_ERR "invalid AC97 slot %i?\n", i);
+					continue;
+				}
+				if (reg_ok[cidx] & (1 << (reg - AC97_PCM_FRONT_DAC_RATE)))
+					continue;
+				//printk(KERN_DEBUG "setting ac97 reg 0x%x to rate %d\n", reg, rate);
+				err = snd_ac97_set_rate(pcm->r[r].codec[cidx], reg, rate);
+				if (err < 0)
+					snd_printk(KERN_ERR "error in snd_ac97_set_rate: cidx=%d, reg=0x%x, rate=%d, err=%d\n", cidx, reg, rate, err);
+				else
+					reg_ok[cidx] |= (1 << (reg - AC97_PCM_FRONT_DAC_RATE));
+			}
+		}
+	}
+	pcm->aslots = slots;
+	return 0;
+
+ error:
+	pcm->aslots = slots;
+	snd_ac97_pcm_close(pcm);
+	return err;
+}
+
+EXPORT_SYMBOL(snd_ac97_pcm_open);
+
+/**
+ * snd_ac97_pcm_close - closes the given AC97 pcm
+ * @pcm: the ac97 pcm instance
+ *
+ * It frees the locked AC97 slots.
+ */
+int snd_ac97_pcm_close(struct ac97_pcm *pcm)
+{
+	struct snd_ac97_bus *bus;
+	unsigned short slots = pcm->aslots;
+	int i, cidx;
+
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+	int r = pcm->cur_dbl;
+	for (i = 3; i < 12; i++) {
+		if (!(slots & (1 << i)))
+			continue;
+		for (cidx = 0; cidx < 4; cidx++) {
+			if (pcm->r[r].rslots[cidx] & (1 << i)) {
+				int reg = get_slot_reg(pcm, cidx, i, r);
+				snd_ac97_update_power(pcm->r[r].codec[cidx],
+						      reg, 0);
+			}
+		}
+	}
+#endif
+
+	bus = pcm->bus;
+	spin_lock_irq(&pcm->bus->bus_lock);
+	for (i = 3; i < 12; i++) {
+		if (!(slots & (1 << i)))
+			continue;
+		for (cidx = 0; cidx < 4; cidx++)
+			bus->used_slots[pcm->stream][cidx] &= ~(1 << i);
+	}
+	pcm->aslots = 0;
+	pcm->cur_dbl = 0;
+	spin_unlock_irq(&pcm->bus->bus_lock);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_ac97_pcm_close);
+
+static int double_rate_hw_constraint_rate(struct snd_pcm_hw_params *params,
+					  struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	if (channels->min > 2) {
+		static const struct snd_interval single_rates = {
+			.min = 1,
+			.max = 48000,
+		};
+		struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+		return snd_interval_refine(rate, &single_rates);
+	}
+	return 0;
+}
+
+static int double_rate_hw_constraint_channels(struct snd_pcm_hw_params *params,
+					      struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	if (rate->min > 48000) {
+		static const struct snd_interval double_rate_channels = {
+			.min = 2,
+			.max = 2,
+		};
+		struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+		return snd_interval_refine(channels, &double_rate_channels);
+	}
+	return 0;
+}
+
+/**
+ * snd_ac97_pcm_double_rate_rules - set double rate constraints
+ * @runtime: the runtime of the ac97 front playback pcm
+ *
+ * Installs the hardware constraint rules to prevent using double rates and
+ * more than two channels at the same time.
+ */
+int snd_ac97_pcm_double_rate_rules(struct snd_pcm_runtime *runtime)
+{
+	int err;
+
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				  double_rate_hw_constraint_rate, NULL,
+				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				  double_rate_hw_constraint_channels, NULL,
+				  SNDRV_PCM_HW_PARAM_RATE, -1);
+	return err;
+}
+
+EXPORT_SYMBOL(snd_ac97_pcm_double_rate_rules);
diff --git a/linux/sound/pci/ac97/ac97_proc.c b/linux/sound/pci/ac97/ac97_proc.c
new file mode 100644
index 0000000..6320bf0
--- /dev/null
+++ b/linux/sound/pci/ac97/ac97_proc.c
@@ -0,0 +1,490 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Universal interface for Audio Codec '97
+ *
+ *  For more details look to AC '97 component specification revision 2.2
+ *  by Intel Corporation (http://developer.intel.com).
+ *
+ *
+ *   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/mutex.h>
+
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#include "ac97_local.h"
+#include "ac97_id.h"
+
+/*
+ * proc interface
+ */
+
+static void snd_ac97_proc_read_functions(struct snd_ac97 *ac97, struct snd_info_buffer *buffer)
+{
+	int header = 0, function;
+	unsigned short info, sense_info;
+	static const char *function_names[12] = {
+		"Master Out", "AUX Out", "Center/LFE Out", "SPDIF Out",
+		"Phone In", "Mic 1", "Mic 2", "Line In", "CD In", "Video In",
+		"Aux In", "Mono Out"
+	};
+	static const char *locations[8] = {
+		"Rear I/O Panel", "Front Panel", "Motherboard", "Dock/External",
+		"reserved", "reserved", "reserved", "NC/unused"
+	};
+
+	for (function = 0; function < 12; ++function) {
+		snd_ac97_write(ac97, AC97_FUNC_SELECT, function << 1);
+		info = snd_ac97_read(ac97, AC97_FUNC_INFO);
+		if (!(info & 0x0001))
+			continue;
+		if (!header) {
+			snd_iprintf(buffer, "\n                    Gain     Inverted  Buffer delay  Location\n");
+			header = 1;
+		}
+		sense_info = snd_ac97_read(ac97, AC97_SENSE_INFO);
+		snd_iprintf(buffer, "%-17s: %3d.%d dBV    %c      %2d/fs         %s\n",
+			    function_names[function],
+			    (info & 0x8000 ? -1 : 1) * ((info & 0x7000) >> 12) * 3 / 2,
+			    ((info & 0x0800) >> 11) * 5,
+			    info & 0x0400 ? 'X' : '-',
+			    (info & 0x03e0) >> 5,
+			    locations[sense_info >> 13]);
+	}
+}
+
+static const char *snd_ac97_stereo_enhancements[] =
+{
+  /*   0 */ "No 3D Stereo Enhancement",
+  /*   1 */ "Analog Devices Phat Stereo",
+  /*   2 */ "Creative Stereo Enhancement",
+  /*   3 */ "National Semi 3D Stereo Enhancement",
+  /*   4 */ "YAMAHA Ymersion",
+  /*   5 */ "BBE 3D Stereo Enhancement",
+  /*   6 */ "Crystal Semi 3D Stereo Enhancement",
+  /*   7 */ "Qsound QXpander",
+  /*   8 */ "Spatializer 3D Stereo Enhancement",
+  /*   9 */ "SRS 3D Stereo Enhancement",
+  /*  10 */ "Platform Tech 3D Stereo Enhancement",
+  /*  11 */ "AKM 3D Audio",
+  /*  12 */ "Aureal Stereo Enhancement",
+  /*  13 */ "Aztech 3D Enhancement",
+  /*  14 */ "Binaura 3D Audio Enhancement",
+  /*  15 */ "ESS Technology Stereo Enhancement",
+  /*  16 */ "Harman International VMAx",
+  /*  17 */ "Nvidea/IC Ensemble/KS Waves 3D Stereo Enhancement",
+  /*  18 */ "Philips Incredible Sound",
+  /*  19 */ "Texas Instruments 3D Stereo Enhancement",
+  /*  20 */ "VLSI Technology 3D Stereo Enhancement",
+  /*  21 */ "TriTech 3D Stereo Enhancement",
+  /*  22 */ "Realtek 3D Stereo Enhancement",
+  /*  23 */ "Samsung 3D Stereo Enhancement",
+  /*  24 */ "Wolfson Microelectronics 3D Enhancement",
+  /*  25 */ "Delta Integration 3D Enhancement",
+  /*  26 */ "SigmaTel 3D Enhancement",
+  /*  27 */ "IC Ensemble/KS Waves",
+  /*  28 */ "Rockwell 3D Stereo Enhancement",
+  /*  29 */ "Reserved 29",
+  /*  30 */ "Reserved 30",
+  /*  31 */ "Reserved 31"
+};
+
+static void snd_ac97_proc_read_main(struct snd_ac97 *ac97, struct snd_info_buffer *buffer, int subidx)
+{
+	char name[64];
+	unsigned short val, tmp, ext, mext;
+	static const char *spdif_slots[4] = { " SPDIF=3/4", " SPDIF=7/8", " SPDIF=6/9", " SPDIF=10/11" };
+	static const char *spdif_rates[4] = { " Rate=44.1kHz", " Rate=res", " Rate=48kHz", " Rate=32kHz" };
+	static const char *spdif_rates_cs4205[4] = { " Rate=48kHz", " Rate=44.1kHz", " Rate=res", " Rate=res" };
+	static const char *double_rate_slots[4] = { "10/11", "7/8", "reserved", "reserved" };
+
+	snd_ac97_get_name(NULL, ac97->id, name, 0);
+	snd_iprintf(buffer, "%d-%d/%d: %s\n\n", ac97->addr, ac97->num, subidx, name);
+
+	if ((ac97->scaps & AC97_SCAP_AUDIO) == 0)
+		goto __modem;
+
+        snd_iprintf(buffer, "PCI Subsys Vendor: 0x%04x\n",
+	            ac97->subsystem_vendor);
+        snd_iprintf(buffer, "PCI Subsys Device: 0x%04x\n\n",
+                    ac97->subsystem_device);
+
+	snd_iprintf(buffer, "Flags: %x\n", ac97->flags);
+
+	if ((ac97->ext_id & AC97_EI_REV_MASK) >= AC97_EI_REV_23) {
+		val = snd_ac97_read(ac97, AC97_INT_PAGING);
+		snd_ac97_update_bits(ac97, AC97_INT_PAGING,
+				     AC97_PAGE_MASK, AC97_PAGE_1);
+		tmp = snd_ac97_read(ac97, AC97_CODEC_CLASS_REV);
+		snd_iprintf(buffer, "Revision         : 0x%02x\n", tmp & 0xff);
+		snd_iprintf(buffer, "Compat. Class    : 0x%02x\n", (tmp >> 8) & 0x1f);
+		snd_iprintf(buffer, "Subsys. Vendor ID: 0x%04x\n",
+			    snd_ac97_read(ac97, AC97_PCI_SVID));
+		snd_iprintf(buffer, "Subsys. ID       : 0x%04x\n\n",
+			    snd_ac97_read(ac97, AC97_PCI_SID));
+		snd_ac97_update_bits(ac97, AC97_INT_PAGING,
+				     AC97_PAGE_MASK, val & AC97_PAGE_MASK);
+	}
+
+	// val = snd_ac97_read(ac97, AC97_RESET);
+	val = ac97->caps;
+	snd_iprintf(buffer, "Capabilities     :%s%s%s%s%s%s\n",
+	    	    val & AC97_BC_DEDICATED_MIC ? " -dedicated MIC PCM IN channel-" : "",
+		    val & AC97_BC_RESERVED1 ? " -reserved1-" : "",
+		    val & AC97_BC_BASS_TREBLE ? " -bass & treble-" : "",
+		    val & AC97_BC_SIM_STEREO ? " -simulated stereo-" : "",
+		    val & AC97_BC_HEADPHONE ? " -headphone out-" : "",
+		    val & AC97_BC_LOUDNESS ? " -loudness-" : "");
+	tmp = ac97->caps & AC97_BC_DAC_MASK;
+	snd_iprintf(buffer, "DAC resolution   : %s%s%s%s\n",
+		    tmp == AC97_BC_16BIT_DAC ? "16-bit" : "",
+		    tmp == AC97_BC_18BIT_DAC ? "18-bit" : "",
+		    tmp == AC97_BC_20BIT_DAC ? "20-bit" : "",
+		    tmp == AC97_BC_DAC_MASK ? "???" : "");
+	tmp = ac97->caps & AC97_BC_ADC_MASK;
+	snd_iprintf(buffer, "ADC resolution   : %s%s%s%s\n",
+		    tmp == AC97_BC_16BIT_ADC ? "16-bit" : "",
+		    tmp == AC97_BC_18BIT_ADC ? "18-bit" : "",
+		    tmp == AC97_BC_20BIT_ADC ? "20-bit" : "",
+		    tmp == AC97_BC_ADC_MASK ? "???" : "");
+	snd_iprintf(buffer, "3D enhancement   : %s\n",
+		snd_ac97_stereo_enhancements[(val >> 10) & 0x1f]);
+	snd_iprintf(buffer, "\nCurrent setup\n");
+	val = snd_ac97_read(ac97, AC97_MIC);
+	snd_iprintf(buffer, "Mic gain         : %s [%s]\n", val & 0x0040 ? "+20dB" : "+0dB", ac97->regs[AC97_MIC] & 0x0040 ? "+20dB" : "+0dB");
+	val = snd_ac97_read(ac97, AC97_GENERAL_PURPOSE);
+	snd_iprintf(buffer, "POP path         : %s 3D\n"
+		    "Sim. stereo      : %s\n"
+		    "3D enhancement   : %s\n"
+		    "Loudness         : %s\n"
+		    "Mono output      : %s\n"
+		    "Mic select       : %s\n"
+		    "ADC/DAC loopback : %s\n",
+		    val & 0x8000 ? "post" : "pre",
+		    val & 0x4000 ? "on" : "off",
+		    val & 0x2000 ? "on" : "off",
+		    val & 0x1000 ? "on" : "off",
+		    val & 0x0200 ? "Mic" : "MIX",
+		    val & 0x0100 ? "Mic2" : "Mic1",
+		    val & 0x0080 ? "on" : "off");
+	if (ac97->ext_id & AC97_EI_DRA)
+		snd_iprintf(buffer, "Double rate slots: %s\n",
+			    double_rate_slots[(val >> 10) & 3]);
+
+	ext = snd_ac97_read(ac97, AC97_EXTENDED_ID);
+	if (ext == 0)
+		goto __modem;
+		
+	snd_iprintf(buffer, "Extended ID      : codec=%i rev=%i%s%s%s%s DSA=%i%s%s%s%s\n",
+			(ext & AC97_EI_ADDR_MASK) >> AC97_EI_ADDR_SHIFT,
+			(ext & AC97_EI_REV_MASK) >> AC97_EI_REV_SHIFT,
+			ext & AC97_EI_AMAP ? " AMAP" : "",
+			ext & AC97_EI_LDAC ? " LDAC" : "",
+			ext & AC97_EI_SDAC ? " SDAC" : "",
+			ext & AC97_EI_CDAC ? " CDAC" : "",
+			(ext & AC97_EI_DACS_SLOT_MASK) >> AC97_EI_DACS_SLOT_SHIFT,
+			ext & AC97_EI_VRM ? " VRM" : "",
+			ext & AC97_EI_SPDIF ? " SPDIF" : "",
+			ext & AC97_EI_DRA ? " DRA" : "",
+			ext & AC97_EI_VRA ? " VRA" : "");
+	val = snd_ac97_read(ac97, AC97_EXTENDED_STATUS);
+	snd_iprintf(buffer, "Extended status  :%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
+			val & AC97_EA_PRL ? " PRL" : "",
+			val & AC97_EA_PRK ? " PRK" : "",
+			val & AC97_EA_PRJ ? " PRJ" : "",
+			val & AC97_EA_PRI ? " PRI" : "",
+			val & AC97_EA_SPCV ? " SPCV" : "",
+			val & AC97_EA_MDAC ? " MADC" : "",
+			val & AC97_EA_LDAC ? " LDAC" : "",
+			val & AC97_EA_SDAC ? " SDAC" : "",
+			val & AC97_EA_CDAC ? " CDAC" : "",
+			ext & AC97_EI_SPDIF ? spdif_slots[(val & AC97_EA_SPSA_SLOT_MASK) >> AC97_EA_SPSA_SLOT_SHIFT] : "",
+			val & AC97_EA_VRM ? " VRM" : "",
+			val & AC97_EA_SPDIF ? " SPDIF" : "",
+			val & AC97_EA_DRA ? " DRA" : "",
+			val & AC97_EA_VRA ? " VRA" : "");
+	if (ext & AC97_EI_VRA) {	/* VRA */
+		val = snd_ac97_read(ac97, AC97_PCM_FRONT_DAC_RATE);
+		snd_iprintf(buffer, "PCM front DAC    : %iHz\n", val);
+		if (ext & AC97_EI_SDAC) {
+			val = snd_ac97_read(ac97, AC97_PCM_SURR_DAC_RATE);
+			snd_iprintf(buffer, "PCM Surr DAC     : %iHz\n", val);
+		}
+		if (ext & AC97_EI_LDAC) {
+			val = snd_ac97_read(ac97, AC97_PCM_LFE_DAC_RATE);
+			snd_iprintf(buffer, "PCM LFE DAC      : %iHz\n", val);
+		}
+		val = snd_ac97_read(ac97, AC97_PCM_LR_ADC_RATE);
+		snd_iprintf(buffer, "PCM ADC          : %iHz\n", val);
+	}
+	if (ext & AC97_EI_VRM) {
+		val = snd_ac97_read(ac97, AC97_PCM_MIC_ADC_RATE);
+		snd_iprintf(buffer, "PCM MIC ADC      : %iHz\n", val);
+	}
+	if ((ext & AC97_EI_SPDIF) || (ac97->flags & AC97_CS_SPDIF) ||
+	    (ac97->id == AC97_ID_YMF743)) {
+	        if (ac97->flags & AC97_CS_SPDIF)
+			val = snd_ac97_read(ac97, AC97_CSR_SPDIF);
+		else if (ac97->id == AC97_ID_YMF743) {
+			val = snd_ac97_read(ac97, AC97_YMF7X3_DIT_CTRL);
+			val = 0x2000 | (val & 0xff00) >> 4 | (val & 0x38) >> 2;
+		} else
+			val = snd_ac97_read(ac97, AC97_SPDIF);
+
+		snd_iprintf(buffer, "SPDIF Control    :%s%s%s%s Category=0x%x Generation=%i%s%s%s\n",
+			val & AC97_SC_PRO ? " PRO" : " Consumer",
+			val & AC97_SC_NAUDIO ? " Non-audio" : " PCM",
+			val & AC97_SC_COPY ? "" : " Copyright",
+			val & AC97_SC_PRE ? " Preemph50/15" : "",
+			(val & AC97_SC_CC_MASK) >> AC97_SC_CC_SHIFT,
+			(val & AC97_SC_L) >> 11,
+			(ac97->flags & AC97_CS_SPDIF) ?
+			    spdif_rates_cs4205[(val & AC97_SC_SPSR_MASK) >> AC97_SC_SPSR_SHIFT] :
+			    spdif_rates[(val & AC97_SC_SPSR_MASK) >> AC97_SC_SPSR_SHIFT],
+			(ac97->flags & AC97_CS_SPDIF) ?
+			    (val & AC97_SC_DRS ? " Validity" : "") :
+			    (val & AC97_SC_DRS ? " DRS" : ""),
+			(ac97->flags & AC97_CS_SPDIF) ?
+			    (val & AC97_SC_V ? " Enabled" : "") :
+			    (val & AC97_SC_V ? " Validity" : ""));
+		/* ALC650 specific*/
+		if ((ac97->id & 0xfffffff0) == 0x414c4720 &&
+		    (snd_ac97_read(ac97, AC97_ALC650_CLOCK) & 0x01)) {
+			val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS2);
+			if (val & AC97_ALC650_CLOCK_LOCK) {
+				val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS1);
+				snd_iprintf(buffer, "SPDIF In Status  :%s%s%s%s Category=0x%x Generation=%i",
+					    val & AC97_ALC650_PRO ? " PRO" : " Consumer",
+					    val & AC97_ALC650_NAUDIO ? " Non-audio" : " PCM",
+					    val & AC97_ALC650_COPY ? "" : " Copyright",
+					    val & AC97_ALC650_PRE ? " Preemph50/15" : "",
+					    (val & AC97_ALC650_CC_MASK) >> AC97_ALC650_CC_SHIFT,
+					    (val & AC97_ALC650_L) >> 15);
+				val = snd_ac97_read(ac97, AC97_ALC650_SPDIF_INPUT_STATUS2);
+				snd_iprintf(buffer, "%s Accuracy=%i%s%s\n",
+					    spdif_rates[(val & AC97_ALC650_SPSR_MASK) >> AC97_ALC650_SPSR_SHIFT],
+					    (val & AC97_ALC650_CLOCK_ACCURACY) >> AC97_ALC650_CLOCK_SHIFT,
+					    (val & AC97_ALC650_CLOCK_LOCK ? " Locked" : " Unlocked"),
+					    (val & AC97_ALC650_V ? " Validity?" : ""));
+			} else {
+				snd_iprintf(buffer, "SPDIF In Status  : Not Locked\n");
+			}
+		}
+	}
+	if ((ac97->ext_id & AC97_EI_REV_MASK) >= AC97_EI_REV_23) {
+		val = snd_ac97_read(ac97, AC97_INT_PAGING);
+		snd_ac97_update_bits(ac97, AC97_INT_PAGING,
+				     AC97_PAGE_MASK, AC97_PAGE_1);
+		snd_ac97_proc_read_functions(ac97, buffer);
+		snd_ac97_update_bits(ac97, AC97_INT_PAGING,
+				     AC97_PAGE_MASK, val & AC97_PAGE_MASK);
+	}
+
+
+      __modem:
+	mext = snd_ac97_read(ac97, AC97_EXTENDED_MID);
+	if (mext == 0)
+		return;
+	
+	snd_iprintf(buffer, "Extended modem ID: codec=%i%s%s%s%s%s\n",
+			(mext & AC97_MEI_ADDR_MASK) >> AC97_MEI_ADDR_SHIFT,
+			mext & AC97_MEI_CID2 ? " CID2" : "",
+			mext & AC97_MEI_CID1 ? " CID1" : "",
+			mext & AC97_MEI_HANDSET ? " HSET" : "",
+			mext & AC97_MEI_LINE2 ? " LIN2" : "",
+			mext & AC97_MEI_LINE1 ? " LIN1" : "");
+	val = snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS);
+	snd_iprintf(buffer, "Modem status     :%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
+			val & AC97_MEA_GPIO ? " GPIO" : "",
+			val & AC97_MEA_MREF ? " MREF" : "",
+			val & AC97_MEA_ADC1 ? " ADC1" : "",
+			val & AC97_MEA_DAC1 ? " DAC1" : "",
+			val & AC97_MEA_ADC2 ? " ADC2" : "",
+			val & AC97_MEA_DAC2 ? " DAC2" : "",
+			val & AC97_MEA_HADC ? " HADC" : "",
+			val & AC97_MEA_HDAC ? " HDAC" : "",
+			val & AC97_MEA_PRA ? " PRA(GPIO)" : "",
+			val & AC97_MEA_PRB ? " PRB(res)" : "",
+			val & AC97_MEA_PRC ? " PRC(ADC1)" : "",
+			val & AC97_MEA_PRD ? " PRD(DAC1)" : "",
+			val & AC97_MEA_PRE ? " PRE(ADC2)" : "",
+			val & AC97_MEA_PRF ? " PRF(DAC2)" : "",
+			val & AC97_MEA_PRG ? " PRG(HADC)" : "",
+			val & AC97_MEA_PRH ? " PRH(HDAC)" : "");
+	if (mext & AC97_MEI_LINE1) {
+		val = snd_ac97_read(ac97, AC97_LINE1_RATE);
+		snd_iprintf(buffer, "Line1 rate       : %iHz\n", val);
+	}
+	if (mext & AC97_MEI_LINE2) {
+		val = snd_ac97_read(ac97, AC97_LINE2_RATE);
+		snd_iprintf(buffer, "Line2 rate       : %iHz\n", val);
+	}
+	if (mext & AC97_MEI_HANDSET) {
+		val = snd_ac97_read(ac97, AC97_HANDSET_RATE);
+		snd_iprintf(buffer, "Headset rate     : %iHz\n", val);
+	}
+}
+
+static void snd_ac97_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_ac97 *ac97 = entry->private_data;
+	
+	mutex_lock(&ac97->page_mutex);
+	if ((ac97->id & 0xffffff40) == AC97_ID_AD1881) {	// Analog Devices AD1881/85/86
+		int idx;
+		for (idx = 0; idx < 3; idx++)
+			if (ac97->spec.ad18xx.id[idx]) {
+				/* select single codec */
+				snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
+						     ac97->spec.ad18xx.unchained[idx] | ac97->spec.ad18xx.chained[idx]);
+				snd_ac97_proc_read_main(ac97, buffer, idx);
+				snd_iprintf(buffer, "\n\n");
+			}
+		/* select all codecs */
+		snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
+		
+		snd_iprintf(buffer, "\nAD18XX configuration\n");
+		snd_iprintf(buffer, "Unchained        : 0x%04x,0x%04x,0x%04x\n",
+			ac97->spec.ad18xx.unchained[0],
+			ac97->spec.ad18xx.unchained[1],
+			ac97->spec.ad18xx.unchained[2]);
+		snd_iprintf(buffer, "Chained          : 0x%04x,0x%04x,0x%04x\n",
+			ac97->spec.ad18xx.chained[0],
+			ac97->spec.ad18xx.chained[1],
+			ac97->spec.ad18xx.chained[2]);
+	} else {
+		snd_ac97_proc_read_main(ac97, buffer, 0);
+	}
+	mutex_unlock(&ac97->page_mutex);
+}
+
+#ifdef CONFIG_SND_DEBUG
+/* direct register write for debugging */
+static void snd_ac97_proc_regs_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_ac97 *ac97 = entry->private_data;
+	char line[64];
+	unsigned int reg, val;
+	mutex_lock(&ac97->page_mutex);
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		if (sscanf(line, "%x %x", &reg, &val) != 2)
+			continue;
+		/* register must be even */
+		if (reg < 0x80 && (reg & 1) == 0 && val <= 0xffff)
+			snd_ac97_write_cache(ac97, reg, val);
+	}
+	mutex_unlock(&ac97->page_mutex);
+}
+#endif
+
+static void snd_ac97_proc_regs_read_main(struct snd_ac97 *ac97, struct snd_info_buffer *buffer, int subidx)
+{
+	int reg, val;
+
+	for (reg = 0; reg < 0x80; reg += 2) {
+		val = snd_ac97_read(ac97, reg);
+		snd_iprintf(buffer, "%i:%02x = %04x\n", subidx, reg, val);
+	}
+}
+
+static void snd_ac97_proc_regs_read(struct snd_info_entry *entry, 
+				    struct snd_info_buffer *buffer)
+{
+	struct snd_ac97 *ac97 = entry->private_data;
+
+	mutex_lock(&ac97->page_mutex);
+	if ((ac97->id & 0xffffff40) == AC97_ID_AD1881) {	// Analog Devices AD1881/85/86
+
+		int idx;
+		for (idx = 0; idx < 3; idx++)
+			if (ac97->spec.ad18xx.id[idx]) {
+				/* select single codec */
+				snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
+						     ac97->spec.ad18xx.unchained[idx] | ac97->spec.ad18xx.chained[idx]);
+				snd_ac97_proc_regs_read_main(ac97, buffer, idx);
+			}
+		/* select all codecs */
+		snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
+	} else {
+		snd_ac97_proc_regs_read_main(ac97, buffer, 0);
+	}	
+	mutex_unlock(&ac97->page_mutex);
+}
+
+void snd_ac97_proc_init(struct snd_ac97 * ac97)
+{
+	struct snd_info_entry *entry;
+	char name[32];
+	const char *prefix;
+
+	if (ac97->bus->proc == NULL)
+		return;
+	prefix = ac97_is_audio(ac97) ? "ac97" : "mc97";
+	sprintf(name, "%s#%d-%d", prefix, ac97->addr, ac97->num);
+	if ((entry = snd_info_create_card_entry(ac97->bus->card, name, ac97->bus->proc)) != NULL) {
+		snd_info_set_text_ops(entry, ac97, snd_ac97_proc_read);
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	ac97->proc = entry;
+	sprintf(name, "%s#%d-%d+regs", prefix, ac97->addr, ac97->num);
+	if ((entry = snd_info_create_card_entry(ac97->bus->card, name, ac97->bus->proc)) != NULL) {
+		snd_info_set_text_ops(entry, ac97, snd_ac97_proc_regs_read);
+#ifdef CONFIG_SND_DEBUG
+		entry->mode |= S_IWUSR;
+		entry->c.text.write = snd_ac97_proc_regs_write;
+#endif
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	ac97->proc_regs = entry;
+}
+
+void snd_ac97_proc_done(struct snd_ac97 * ac97)
+{
+	snd_info_free_entry(ac97->proc_regs);
+	ac97->proc_regs = NULL;
+	snd_info_free_entry(ac97->proc);
+	ac97->proc = NULL;
+}
+
+void snd_ac97_bus_proc_init(struct snd_ac97_bus * bus)
+{
+	struct snd_info_entry *entry;
+	char name[32];
+
+	sprintf(name, "codec97#%d", bus->num);
+	if ((entry = snd_info_create_card_entry(bus->card, name, bus->card->proc_root)) != NULL) {
+		entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	bus->proc = entry;
+}
+
+void snd_ac97_bus_proc_done(struct snd_ac97_bus * bus)
+{
+	snd_info_free_entry(bus->proc);
+	bus->proc = NULL;
+}
diff --git a/linux/sound/pci/ad1889.c b/linux/sound/pci/ad1889.c
new file mode 100644
index 0000000..4382d0f
--- /dev/null
+++ b/linux/sound/pci/ad1889.c
@@ -0,0 +1,1077 @@
+/* Analog Devices 1889 audio driver
+ *
+ * This is a driver for the AD1889 PCI audio chipset found
+ * on the HP PA-RISC [BCJ]-xxx0 workstations.
+ *
+ * Copyright (C) 2004-2005, Kyle McMartin <kyle@parisc-linux.org>
+ * Copyright (C) 2005, Thibaut Varene <varenet@parisc-linux.org>
+ *   Based on the OSS AD1889 driver by Randolph Chung <tausq@debian.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * TODO:
+ *	Do we need to take care of CCS register?
+ *	Maybe we could use finer grained locking (separate locks for pb/cap)?
+ * Wishlist:
+ *	Control Interface (mixer) support
+ *	Better AC97 support (VSR...)?
+ *	PM support
+ *	MIDI support
+ *	Game Port support
+ *	SG DMA support (this will need *alot* of work)
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/compiler.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/ac97_codec.h>
+
+#include <asm/io.h>
+
+#include "ad1889.h"
+#include "ac97/ac97_id.h"
+
+#define	AD1889_DRVVER	"Version: 1.7"
+
+MODULE_AUTHOR("Kyle McMartin <kyle@parisc-linux.org>, Thibaut Varene <t-bone@parisc-linux.org>");
+MODULE_DESCRIPTION("Analog Devices AD1889 ALSA sound driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Analog Devices,AD1889}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the AD1889 soundcard.");
+
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the AD1889 soundcard.");
+
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable AD1889 soundcard.");
+
+static char *ac97_quirk[SNDRV_CARDS];
+module_param_array(ac97_quirk, charp, NULL, 0444);
+MODULE_PARM_DESC(ac97_quirk, "AC'97 workaround for strange hardware.");
+
+#define DEVNAME "ad1889"
+#define PFX	DEVNAME ": "
+
+/* let's use the global sound debug interfaces */
+#define ad1889_debug(fmt, arg...) snd_printd(KERN_DEBUG fmt, ## arg)
+
+/* keep track of some hw registers */
+struct ad1889_register_state {
+	u16 reg;	/* reg setup */
+	u32 addr;	/* dma base address */
+	unsigned long size;	/* DMA buffer size */
+};
+
+struct snd_ad1889 {
+	struct snd_card *card;
+	struct pci_dev *pci;
+
+	int irq;
+	unsigned long bar;
+	void __iomem *iobase;
+
+	struct snd_ac97 *ac97;
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_pcm *pcm;
+	struct snd_info_entry *proc;
+
+	struct snd_pcm_substream *psubs;
+	struct snd_pcm_substream *csubs;
+
+	/* playback register state */
+	struct ad1889_register_state wave;
+	struct ad1889_register_state ramc;
+
+	spinlock_t lock;
+};
+
+static inline u16
+ad1889_readw(struct snd_ad1889 *chip, unsigned reg)
+{
+	return readw(chip->iobase + reg);
+}
+
+static inline void
+ad1889_writew(struct snd_ad1889 *chip, unsigned reg, u16 val)
+{
+	writew(val, chip->iobase + reg);
+}
+
+static inline u32
+ad1889_readl(struct snd_ad1889 *chip, unsigned reg)
+{
+	return readl(chip->iobase + reg);
+}
+
+static inline void
+ad1889_writel(struct snd_ad1889 *chip, unsigned reg, u32 val)
+{
+	writel(val, chip->iobase + reg);
+}
+
+static inline void
+ad1889_unmute(struct snd_ad1889 *chip)
+{
+	u16 st;
+	st = ad1889_readw(chip, AD_DS_WADA) & 
+		~(AD_DS_WADA_RWAM | AD_DS_WADA_LWAM);
+	ad1889_writew(chip, AD_DS_WADA, st);
+	ad1889_readw(chip, AD_DS_WADA);
+}
+
+static inline void
+ad1889_mute(struct snd_ad1889 *chip)
+{
+	u16 st;
+	st = ad1889_readw(chip, AD_DS_WADA) | AD_DS_WADA_RWAM | AD_DS_WADA_LWAM;
+	ad1889_writew(chip, AD_DS_WADA, st);
+	ad1889_readw(chip, AD_DS_WADA);
+}
+
+static inline void
+ad1889_load_adc_buffer_address(struct snd_ad1889 *chip, u32 address)
+{
+	ad1889_writel(chip, AD_DMA_ADCBA, address);
+	ad1889_writel(chip, AD_DMA_ADCCA, address);
+}
+
+static inline void
+ad1889_load_adc_buffer_count(struct snd_ad1889 *chip, u32 count)
+{
+	ad1889_writel(chip, AD_DMA_ADCBC, count);
+	ad1889_writel(chip, AD_DMA_ADCCC, count);
+}
+
+static inline void
+ad1889_load_adc_interrupt_count(struct snd_ad1889 *chip, u32 count)
+{
+	ad1889_writel(chip, AD_DMA_ADCIB, count);
+	ad1889_writel(chip, AD_DMA_ADCIC, count);
+}
+
+static inline void
+ad1889_load_wave_buffer_address(struct snd_ad1889 *chip, u32 address)
+{
+	ad1889_writel(chip, AD_DMA_WAVBA, address);
+	ad1889_writel(chip, AD_DMA_WAVCA, address);
+}
+
+static inline void
+ad1889_load_wave_buffer_count(struct snd_ad1889 *chip, u32 count)
+{
+	ad1889_writel(chip, AD_DMA_WAVBC, count);
+	ad1889_writel(chip, AD_DMA_WAVCC, count);
+}
+
+static inline void
+ad1889_load_wave_interrupt_count(struct snd_ad1889 *chip, u32 count)
+{
+	ad1889_writel(chip, AD_DMA_WAVIB, count);
+	ad1889_writel(chip, AD_DMA_WAVIC, count);
+}
+
+static void
+ad1889_channel_reset(struct snd_ad1889 *chip, unsigned int channel)
+{
+	u16 reg;
+	
+	if (channel & AD_CHAN_WAV) {
+		/* Disable wave channel */
+		reg = ad1889_readw(chip, AD_DS_WSMC) & ~AD_DS_WSMC_WAEN;
+		ad1889_writew(chip, AD_DS_WSMC, reg);
+		chip->wave.reg = reg;
+		
+		/* disable IRQs */
+		reg = ad1889_readw(chip, AD_DMA_WAV);
+		reg &= AD_DMA_IM_DIS;
+		reg &= ~AD_DMA_LOOP;
+		ad1889_writew(chip, AD_DMA_WAV, reg);
+
+		/* clear IRQ and address counters and pointers */
+		ad1889_load_wave_buffer_address(chip, 0x0);
+		ad1889_load_wave_buffer_count(chip, 0x0);
+		ad1889_load_wave_interrupt_count(chip, 0x0);
+
+		/* flush */
+		ad1889_readw(chip, AD_DMA_WAV);
+	}
+	
+	if (channel & AD_CHAN_ADC) {
+		/* Disable ADC channel */
+		reg = ad1889_readw(chip, AD_DS_RAMC) & ~AD_DS_RAMC_ADEN;
+		ad1889_writew(chip, AD_DS_RAMC, reg);
+		chip->ramc.reg = reg;
+
+		reg = ad1889_readw(chip, AD_DMA_ADC);
+		reg &= AD_DMA_IM_DIS;
+		reg &= ~AD_DMA_LOOP;
+		ad1889_writew(chip, AD_DMA_ADC, reg);
+	
+		ad1889_load_adc_buffer_address(chip, 0x0);
+		ad1889_load_adc_buffer_count(chip, 0x0);
+		ad1889_load_adc_interrupt_count(chip, 0x0);
+
+		/* flush */
+		ad1889_readw(chip, AD_DMA_ADC);
+	}
+}
+
+static u16
+snd_ad1889_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct snd_ad1889 *chip = ac97->private_data;
+	return ad1889_readw(chip, AD_AC97_BASE + reg);
+}
+
+static void
+snd_ad1889_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val)
+{
+	struct snd_ad1889 *chip = ac97->private_data;
+	ad1889_writew(chip, AD_AC97_BASE + reg, val);
+}
+
+static int
+snd_ad1889_ac97_ready(struct snd_ad1889 *chip)
+{
+	int retry = 400; /* average needs 352 msec */
+	
+	while (!(ad1889_readw(chip, AD_AC97_ACIC) & AD_AC97_ACIC_ACRDY) 
+			&& --retry)
+		mdelay(1);
+	if (!retry) {
+		snd_printk(KERN_ERR PFX "[%s] Link is not ready.\n",
+		       __func__);
+		return -EIO;
+	}
+	ad1889_debug("[%s] ready after %d ms\n", __func__, 400 - retry);
+
+	return 0;
+}
+
+static int 
+snd_ad1889_hw_params(struct snd_pcm_substream *substream,
+			struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, 
+					params_buffer_bytes(hw_params));
+}
+
+static int
+snd_ad1889_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static struct snd_pcm_hardware snd_ad1889_playback_hw = {
+	.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min = 8000,	/* docs say 7000, but we're lazy */
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = PERIOD_BYTES_MAX,
+	.periods_min = PERIODS_MIN,
+	.periods_max = PERIODS_MAX,
+	/*.fifo_size = 0,*/
+};
+
+static struct snd_pcm_hardware snd_ad1889_capture_hw = {
+	.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rates = SNDRV_PCM_RATE_48000,
+	.rate_min = 48000,	/* docs say we could to VSR, but we're lazy */
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = PERIOD_BYTES_MAX,
+	.periods_min = PERIODS_MIN,
+	.periods_max = PERIODS_MAX,
+	/*.fifo_size = 0,*/
+};
+
+static int
+snd_ad1889_playback_open(struct snd_pcm_substream *ss)
+{
+	struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
+	struct snd_pcm_runtime *rt = ss->runtime;
+
+	chip->psubs = ss;
+	rt->hw = snd_ad1889_playback_hw;
+
+	return 0;
+}
+
+static int
+snd_ad1889_capture_open(struct snd_pcm_substream *ss)
+{
+	struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
+	struct snd_pcm_runtime *rt = ss->runtime;
+
+	chip->csubs = ss;
+	rt->hw = snd_ad1889_capture_hw;
+
+	return 0;
+}
+
+static int
+snd_ad1889_playback_close(struct snd_pcm_substream *ss)
+{
+	struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
+	chip->psubs = NULL;
+	return 0;
+}
+
+static int
+snd_ad1889_capture_close(struct snd_pcm_substream *ss)
+{
+	struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
+	chip->csubs = NULL;
+	return 0;
+}
+
+static int
+snd_ad1889_playback_prepare(struct snd_pcm_substream *ss)
+{
+	struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
+	struct snd_pcm_runtime *rt = ss->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(ss);
+	unsigned int count = snd_pcm_lib_period_bytes(ss);
+	u16 reg;
+
+	ad1889_channel_reset(chip, AD_CHAN_WAV);
+
+	reg = ad1889_readw(chip, AD_DS_WSMC);
+	
+	/* Mask out 16-bit / Stereo */
+	reg &= ~(AD_DS_WSMC_WA16 | AD_DS_WSMC_WAST);
+
+	if (snd_pcm_format_width(rt->format) == 16)
+		reg |= AD_DS_WSMC_WA16;
+
+	if (rt->channels > 1)
+		reg |= AD_DS_WSMC_WAST;
+
+	/* let's make sure we don't clobber ourselves */
+	spin_lock_irq(&chip->lock);
+	
+	chip->wave.size = size;
+	chip->wave.reg = reg;
+	chip->wave.addr = rt->dma_addr;
+
+	ad1889_writew(chip, AD_DS_WSMC, chip->wave.reg);
+	
+	/* Set sample rates on the codec */
+	ad1889_writew(chip, AD_DS_WAS, rt->rate);
+
+	/* Set up DMA */
+	ad1889_load_wave_buffer_address(chip, chip->wave.addr);
+	ad1889_load_wave_buffer_count(chip, size);
+	ad1889_load_wave_interrupt_count(chip, count);
+
+	/* writes flush */
+	ad1889_readw(chip, AD_DS_WSMC);
+	
+	spin_unlock_irq(&chip->lock);
+	
+	ad1889_debug("prepare playback: addr = 0x%x, count = %u, "
+			"size = %u, reg = 0x%x, rate = %u\n", chip->wave.addr,
+			count, size, reg, rt->rate);
+	return 0;
+}
+
+static int
+snd_ad1889_capture_prepare(struct snd_pcm_substream *ss)
+{
+	struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
+	struct snd_pcm_runtime *rt = ss->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(ss);
+	unsigned int count = snd_pcm_lib_period_bytes(ss);
+	u16 reg;
+
+	ad1889_channel_reset(chip, AD_CHAN_ADC);
+	
+	reg = ad1889_readw(chip, AD_DS_RAMC);
+
+	/* Mask out 16-bit / Stereo */
+	reg &= ~(AD_DS_RAMC_AD16 | AD_DS_RAMC_ADST);
+
+	if (snd_pcm_format_width(rt->format) == 16)
+		reg |= AD_DS_RAMC_AD16;
+
+	if (rt->channels > 1)
+		reg |= AD_DS_RAMC_ADST;
+
+	/* let's make sure we don't clobber ourselves */
+	spin_lock_irq(&chip->lock);
+	
+	chip->ramc.size = size;
+	chip->ramc.reg = reg;
+	chip->ramc.addr = rt->dma_addr;
+
+	ad1889_writew(chip, AD_DS_RAMC, chip->ramc.reg);
+
+	/* Set up DMA */
+	ad1889_load_adc_buffer_address(chip, chip->ramc.addr);
+	ad1889_load_adc_buffer_count(chip, size);
+	ad1889_load_adc_interrupt_count(chip, count);
+
+	/* writes flush */
+	ad1889_readw(chip, AD_DS_RAMC);
+	
+	spin_unlock_irq(&chip->lock);
+	
+	ad1889_debug("prepare capture: addr = 0x%x, count = %u, "
+			"size = %u, reg = 0x%x, rate = %u\n", chip->ramc.addr,
+			count, size, reg, rt->rate);
+	return 0;
+}
+
+/* this is called in atomic context with IRQ disabled.
+   Must be as fast as possible and not sleep.
+   DMA should be *triggered* by this call.
+   The WSMC "WAEN" bit triggers DMA Wave On/Off */
+static int
+snd_ad1889_playback_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+	u16 wsmc;
+	struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
+	
+	wsmc = ad1889_readw(chip, AD_DS_WSMC);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* enable DMA loop & interrupts */
+		ad1889_writew(chip, AD_DMA_WAV, AD_DMA_LOOP | AD_DMA_IM_CNT);
+		wsmc |= AD_DS_WSMC_WAEN;
+		/* 1 to clear CHSS bit */
+		ad1889_writel(chip, AD_DMA_CHSS, AD_DMA_CHSS_WAVS);
+		ad1889_unmute(chip);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		ad1889_mute(chip);
+		wsmc &= ~AD_DS_WSMC_WAEN;
+		break;
+	default:
+		snd_BUG();
+		return -EINVAL;
+	}
+	
+	chip->wave.reg = wsmc;
+	ad1889_writew(chip, AD_DS_WSMC, wsmc);	
+	ad1889_readw(chip, AD_DS_WSMC);	/* flush */
+
+	/* reset the chip when STOP - will disable IRQs */
+	if (cmd == SNDRV_PCM_TRIGGER_STOP)
+		ad1889_channel_reset(chip, AD_CHAN_WAV);
+
+	return 0;
+}
+
+/* this is called in atomic context with IRQ disabled.
+   Must be as fast as possible and not sleep.
+   DMA should be *triggered* by this call.
+   The RAMC "ADEN" bit triggers DMA ADC On/Off */
+static int
+snd_ad1889_capture_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+	u16 ramc;
+	struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
+
+	ramc = ad1889_readw(chip, AD_DS_RAMC);
+	
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* enable DMA loop & interrupts */
+		ad1889_writew(chip, AD_DMA_ADC, AD_DMA_LOOP | AD_DMA_IM_CNT);
+		ramc |= AD_DS_RAMC_ADEN;
+		/* 1 to clear CHSS bit */
+		ad1889_writel(chip, AD_DMA_CHSS, AD_DMA_CHSS_ADCS);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		ramc &= ~AD_DS_RAMC_ADEN;
+		break;
+	default:
+		return -EINVAL;
+	}
+	
+	chip->ramc.reg = ramc;
+	ad1889_writew(chip, AD_DS_RAMC, ramc);	
+	ad1889_readw(chip, AD_DS_RAMC);	/* flush */
+	
+	/* reset the chip when STOP - will disable IRQs */
+	if (cmd == SNDRV_PCM_TRIGGER_STOP)
+		ad1889_channel_reset(chip, AD_CHAN_ADC);
+		
+	return 0;
+}
+
+/* Called in atomic context with IRQ disabled */
+static snd_pcm_uframes_t
+snd_ad1889_playback_pointer(struct snd_pcm_substream *ss)
+{
+	size_t ptr = 0;
+	struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
+
+	if (unlikely(!(chip->wave.reg & AD_DS_WSMC_WAEN)))
+		return 0;
+
+	ptr = ad1889_readl(chip, AD_DMA_WAVCA);
+	ptr -= chip->wave.addr;
+	
+	if (snd_BUG_ON(ptr >= chip->wave.size))
+		return 0;
+	
+	return bytes_to_frames(ss->runtime, ptr);
+}
+
+/* Called in atomic context with IRQ disabled */
+static snd_pcm_uframes_t
+snd_ad1889_capture_pointer(struct snd_pcm_substream *ss)
+{
+	size_t ptr = 0;
+	struct snd_ad1889 *chip = snd_pcm_substream_chip(ss);
+
+	if (unlikely(!(chip->ramc.reg & AD_DS_RAMC_ADEN)))
+		return 0;
+
+	ptr = ad1889_readl(chip, AD_DMA_ADCCA);
+	ptr -= chip->ramc.addr;
+
+	if (snd_BUG_ON(ptr >= chip->ramc.size))
+		return 0;
+	
+	return bytes_to_frames(ss->runtime, ptr);
+}
+
+static struct snd_pcm_ops snd_ad1889_playback_ops = {
+	.open = snd_ad1889_playback_open,
+	.close = snd_ad1889_playback_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_ad1889_hw_params,
+	.hw_free = snd_ad1889_hw_free,
+	.prepare = snd_ad1889_playback_prepare,
+	.trigger = snd_ad1889_playback_trigger,
+	.pointer = snd_ad1889_playback_pointer, 
+};
+
+static struct snd_pcm_ops snd_ad1889_capture_ops = {
+	.open = snd_ad1889_capture_open,
+	.close = snd_ad1889_capture_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_ad1889_hw_params,
+	.hw_free = snd_ad1889_hw_free,
+	.prepare = snd_ad1889_capture_prepare,
+	.trigger = snd_ad1889_capture_trigger,
+	.pointer = snd_ad1889_capture_pointer, 
+};
+
+static irqreturn_t
+snd_ad1889_interrupt(int irq, void *dev_id)
+{
+	unsigned long st;
+	struct snd_ad1889 *chip = dev_id;
+
+	st = ad1889_readl(chip, AD_DMA_DISR);
+
+	/* clear ISR */
+	ad1889_writel(chip, AD_DMA_DISR, st);
+
+	st &= AD_INTR_MASK;
+
+	if (unlikely(!st))
+		return IRQ_NONE;
+
+	if (st & (AD_DMA_DISR_PMAI|AD_DMA_DISR_PTAI))
+		ad1889_debug("Unexpected master or target abort interrupt!\n");
+
+	if ((st & AD_DMA_DISR_WAVI) && chip->psubs)
+		snd_pcm_period_elapsed(chip->psubs);
+	if ((st & AD_DMA_DISR_ADCI) && chip->csubs)
+		snd_pcm_period_elapsed(chip->csubs);
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit
+snd_ad1889_pcm_init(struct snd_ad1889 *chip, int device, struct snd_pcm **rpcm)
+{
+	int err;
+	struct snd_pcm *pcm;
+
+	if (rpcm)
+		*rpcm = NULL;
+
+	err = snd_pcm_new(chip->card, chip->card->driver, device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, 
+			&snd_ad1889_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&snd_ad1889_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, chip->card->shortname);
+	
+	chip->pcm = pcm;
+	chip->psubs = NULL;
+	chip->csubs = NULL;
+
+	err = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						snd_dma_pci_data(chip->pci),
+						BUFFER_BYTES_MAX / 2,
+						BUFFER_BYTES_MAX);
+
+	if (err < 0) {
+		snd_printk(KERN_ERR PFX "buffer allocation error: %d\n", err);
+		return err;
+	}
+	
+	if (rpcm)
+		*rpcm = pcm;
+	
+	return 0;
+}
+
+static void
+snd_ad1889_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_ad1889 *chip = entry->private_data;
+	u16 reg;
+	int tmp;
+
+	reg = ad1889_readw(chip, AD_DS_WSMC);
+	snd_iprintf(buffer, "Wave output: %s\n",
+			(reg & AD_DS_WSMC_WAEN) ? "enabled" : "disabled");
+	snd_iprintf(buffer, "Wave Channels: %s\n",
+			(reg & AD_DS_WSMC_WAST) ? "stereo" : "mono");
+	snd_iprintf(buffer, "Wave Quality: %d-bit linear\n",
+			(reg & AD_DS_WSMC_WA16) ? 16 : 8);
+	
+	/* WARQ is at offset 12 */
+	tmp = (reg & AD_DS_WSMC_WARQ) ?
+			(((reg & AD_DS_WSMC_WARQ >> 12) & 0x01) ? 12 : 18) : 4;
+	tmp /= (reg & AD_DS_WSMC_WAST) ? 2 : 1;
+	
+	snd_iprintf(buffer, "Wave FIFO: %d %s words\n\n", tmp,
+			(reg & AD_DS_WSMC_WAST) ? "stereo" : "mono");
+				
+	
+	snd_iprintf(buffer, "Synthesis output: %s\n",
+			reg & AD_DS_WSMC_SYEN ? "enabled" : "disabled");
+	
+	/* SYRQ is at offset 4 */
+	tmp = (reg & AD_DS_WSMC_SYRQ) ?
+			(((reg & AD_DS_WSMC_SYRQ >> 4) & 0x01) ? 12 : 18) : 4;
+	tmp /= (reg & AD_DS_WSMC_WAST) ? 2 : 1;
+	
+	snd_iprintf(buffer, "Synthesis FIFO: %d %s words\n\n", tmp,
+			(reg & AD_DS_WSMC_WAST) ? "stereo" : "mono");
+
+	reg = ad1889_readw(chip, AD_DS_RAMC);
+	snd_iprintf(buffer, "ADC input: %s\n",
+			(reg & AD_DS_RAMC_ADEN) ? "enabled" : "disabled");
+	snd_iprintf(buffer, "ADC Channels: %s\n",
+			(reg & AD_DS_RAMC_ADST) ? "stereo" : "mono");
+	snd_iprintf(buffer, "ADC Quality: %d-bit linear\n",
+			(reg & AD_DS_RAMC_AD16) ? 16 : 8);
+	
+	/* ACRQ is at offset 4 */
+	tmp = (reg & AD_DS_RAMC_ACRQ) ?
+			(((reg & AD_DS_RAMC_ACRQ >> 4) & 0x01) ? 12 : 18) : 4;
+	tmp /= (reg & AD_DS_RAMC_ADST) ? 2 : 1;
+	
+	snd_iprintf(buffer, "ADC FIFO: %d %s words\n\n", tmp,
+			(reg & AD_DS_RAMC_ADST) ? "stereo" : "mono");
+	
+	snd_iprintf(buffer, "Resampler input: %s\n",
+			reg & AD_DS_RAMC_REEN ? "enabled" : "disabled");
+			
+	/* RERQ is at offset 12 */
+	tmp = (reg & AD_DS_RAMC_RERQ) ?
+			(((reg & AD_DS_RAMC_RERQ >> 12) & 0x01) ? 12 : 18) : 4;
+	tmp /= (reg & AD_DS_RAMC_ADST) ? 2 : 1;
+	
+	snd_iprintf(buffer, "Resampler FIFO: %d %s words\n\n", tmp,
+			(reg & AD_DS_WSMC_WAST) ? "stereo" : "mono");
+				
+	
+	/* doc says LSB represents -1.5dB, but the max value (-94.5dB)
+	suggests that LSB is -3dB, which is more coherent with the logarithmic
+	nature of the dB scale */
+	reg = ad1889_readw(chip, AD_DS_WADA);
+	snd_iprintf(buffer, "Left: %s, -%d dB\n",
+			(reg & AD_DS_WADA_LWAM) ? "mute" : "unmute",
+			((reg & AD_DS_WADA_LWAA) >> 8) * 3);
+	reg = ad1889_readw(chip, AD_DS_WADA);
+	snd_iprintf(buffer, "Right: %s, -%d dB\n",
+			(reg & AD_DS_WADA_RWAM) ? "mute" : "unmute",
+			((reg & AD_DS_WADA_RWAA) >> 8) * 3);
+	
+	reg = ad1889_readw(chip, AD_DS_WAS);
+	snd_iprintf(buffer, "Wave samplerate: %u Hz\n", reg);
+	reg = ad1889_readw(chip, AD_DS_RES);
+	snd_iprintf(buffer, "Resampler samplerate: %u Hz\n", reg);
+}
+
+static void __devinit
+snd_ad1889_proc_init(struct snd_ad1889 *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(chip->card, chip->card->driver, &entry))
+		snd_info_set_text_ops(entry, chip, snd_ad1889_proc_read);
+}
+
+static struct ac97_quirk ac97_quirks[] = {
+	{
+		.subvendor = 0x11d4,	/* AD */
+		.subdevice = 0x1889,	/* AD1889 */
+		.codec_id = AC97_ID_AD1819,
+		.name = "AD1889",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{ } /* terminator */
+};
+
+static void __devinit
+snd_ad1889_ac97_xinit(struct snd_ad1889 *chip)
+{
+	u16 reg;
+
+	reg = ad1889_readw(chip, AD_AC97_ACIC);
+	reg |= AD_AC97_ACIC_ACRD;		/* Reset Disable */
+	ad1889_writew(chip, AD_AC97_ACIC, reg);
+	ad1889_readw(chip, AD_AC97_ACIC);	/* flush posted write */
+	udelay(10);
+	/* Interface Enable */
+	reg |= AD_AC97_ACIC_ACIE;
+	ad1889_writew(chip, AD_AC97_ACIC, reg);
+	
+	snd_ad1889_ac97_ready(chip);
+
+	/* Audio Stream Output | Variable Sample Rate Mode */
+	reg = ad1889_readw(chip, AD_AC97_ACIC);
+	reg |= AD_AC97_ACIC_ASOE | AD_AC97_ACIC_VSRM;
+	ad1889_writew(chip, AD_AC97_ACIC, reg);
+	ad1889_readw(chip, AD_AC97_ACIC); /* flush posted write */
+
+}
+
+static void
+snd_ad1889_ac97_bus_free(struct snd_ac97_bus *bus)
+{
+	struct snd_ad1889 *chip = bus->private_data;
+	chip->ac97_bus = NULL;
+}
+
+static void
+snd_ad1889_ac97_free(struct snd_ac97 *ac97)
+{
+	struct snd_ad1889 *chip = ac97->private_data;
+	chip->ac97 = NULL;
+}
+
+static int __devinit
+snd_ad1889_ac97_init(struct snd_ad1889 *chip, const char *quirk_override)
+{
+	int err;
+	struct snd_ac97_template ac97;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_ad1889_ac97_write,
+		.read = snd_ad1889_ac97_read,
+	};
+
+	/* doing that here, it works. */
+	snd_ad1889_ac97_xinit(chip);
+
+	err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus);
+	if (err < 0)
+		return err;
+	
+	chip->ac97_bus->private_free = snd_ad1889_ac97_bus_free;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.private_free = snd_ad1889_ac97_free;
+	ac97.pci = chip->pci;
+
+	err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97);
+	if (err < 0)
+		return err;
+		
+	snd_ac97_tune_hardware(chip->ac97, ac97_quirks, quirk_override);
+	
+	return 0;
+}
+
+static int
+snd_ad1889_free(struct snd_ad1889 *chip)
+{
+	if (chip->irq < 0)
+		goto skip_hw;
+
+	spin_lock_irq(&chip->lock);
+
+	ad1889_mute(chip);
+
+	/* Turn off interrupt on count and zero DMA registers */
+	ad1889_channel_reset(chip, AD_CHAN_WAV | AD_CHAN_ADC);
+
+	/* clear DISR. If we don't, we'd better jump off the Eiffel Tower */
+	ad1889_writel(chip, AD_DMA_DISR, AD_DMA_DISR_PTAI | AD_DMA_DISR_PMAI);
+	ad1889_readl(chip, AD_DMA_DISR);	/* flush, dammit! */
+
+	spin_unlock_irq(&chip->lock);
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+
+skip_hw:
+	if (chip->iobase)
+		iounmap(chip->iobase);
+
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+
+	kfree(chip);
+	return 0;
+}
+
+static int
+snd_ad1889_dev_free(struct snd_device *device) 
+{
+	struct snd_ad1889 *chip = device->device_data;
+	return snd_ad1889_free(chip);
+}
+
+static int __devinit
+snd_ad1889_init(struct snd_ad1889 *chip) 
+{
+	ad1889_writew(chip, AD_DS_CCS, AD_DS_CCS_CLKEN); /* turn on clock */
+	ad1889_readw(chip, AD_DS_CCS);	/* flush posted write */
+
+	mdelay(10);
+
+	/* enable Master and Target abort interrupts */
+	ad1889_writel(chip, AD_DMA_DISR, AD_DMA_DISR_PMAE | AD_DMA_DISR_PTAE);
+
+	return 0;
+}
+
+static int __devinit
+snd_ad1889_create(struct snd_card *card,
+		  struct pci_dev *pci,
+		  struct snd_ad1889 **rchip)
+{
+	int err;
+
+	struct snd_ad1889 *chip;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_ad1889_dev_free,
+	};
+
+	*rchip = NULL;
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	/* check PCI availability (32bit DMA) */
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(32)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32)) < 0) {
+		printk(KERN_ERR PFX "error setting 32-bit DMA mask.\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	/* allocate chip specific data with zero-filled memory */
+	if ((chip = kzalloc(sizeof(*chip), GFP_KERNEL)) == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	chip->card = card;
+	card->private_data = chip;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	/* (1) PCI resource allocation */
+	if ((err = pci_request_regions(pci, card->driver)) < 0)
+		goto free_and_ret;
+
+	chip->bar = pci_resource_start(pci, 0);
+	chip->iobase = pci_ioremap_bar(pci, 0);
+	if (chip->iobase == NULL) {
+		printk(KERN_ERR PFX "unable to reserve region.\n");
+		err = -EBUSY;
+		goto free_and_ret;
+	}
+	
+	pci_set_master(pci);
+
+	spin_lock_init(&chip->lock);	/* only now can we call ad1889_free */
+
+	if (request_irq(pci->irq, snd_ad1889_interrupt,
+			IRQF_SHARED, card->driver, chip)) {
+		printk(KERN_ERR PFX "cannot obtain IRQ %d\n", pci->irq);
+		snd_ad1889_free(chip);
+		return -EBUSY;
+	}
+
+	chip->irq = pci->irq;
+	synchronize_irq(chip->irq);
+
+	/* (2) initialization of the chip hardware */
+	if ((err = snd_ad1889_init(chip)) < 0) {
+		snd_ad1889_free(chip);
+		return err;
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_ad1889_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rchip = chip;
+
+	return 0;
+
+free_and_ret:
+	kfree(chip);
+	pci_disable_device(pci);
+
+	return err;
+}
+
+static int __devinit
+snd_ad1889_probe(struct pci_dev *pci,
+		 const struct pci_device_id *pci_id)
+{
+	int err;
+	static int devno;
+	struct snd_card *card;
+	struct snd_ad1889 *chip;
+
+	/* (1) */
+	if (devno >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[devno]) {
+		devno++;
+		return -ENOENT;
+	}
+
+	/* (2) */
+	err = snd_card_create(index[devno], id[devno], THIS_MODULE, 0, &card);
+	/* XXX REVISIT: we can probably allocate chip in this call */
+	if (err < 0)
+		return err;
+
+	strcpy(card->driver, "AD1889");
+	strcpy(card->shortname, "Analog Devices AD1889");
+
+	/* (3) */
+	err = snd_ad1889_create(card, pci, &chip);
+	if (err < 0)
+		goto free_and_ret;
+
+	/* (4) */
+	sprintf(card->longname, "%s at 0x%lx irq %i",
+		card->shortname, chip->bar, chip->irq);
+
+	/* (5) */
+	/* register AC97 mixer */
+	err = snd_ad1889_ac97_init(chip, ac97_quirk[devno]);
+	if (err < 0)
+		goto free_and_ret;
+	
+	err = snd_ad1889_pcm_init(chip, 0, NULL);
+	if (err < 0)
+		goto free_and_ret;
+
+	/* register proc interface */
+	snd_ad1889_proc_init(chip);
+
+	/* (6) */
+	err = snd_card_register(card);
+	if (err < 0)
+		goto free_and_ret;
+
+	/* (7) */
+	pci_set_drvdata(pci, card);
+
+	devno++;
+	return 0;
+
+free_and_ret:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit
+snd_ad1889_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static DEFINE_PCI_DEVICE_TABLE(snd_ad1889_ids) = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_ANALOG_DEVICES, PCI_DEVICE_ID_AD1889JS) },
+	{ 0, },
+};
+MODULE_DEVICE_TABLE(pci, snd_ad1889_ids);
+
+static struct pci_driver ad1889_pci_driver = {
+	.name = "AD1889 Audio",
+	.id_table = snd_ad1889_ids,
+	.probe = snd_ad1889_probe,
+	.remove = __devexit_p(snd_ad1889_remove),
+};
+
+static int __init
+alsa_ad1889_init(void)
+{
+	return pci_register_driver(&ad1889_pci_driver);
+}
+
+static void __exit
+alsa_ad1889_fini(void)
+{
+	pci_unregister_driver(&ad1889_pci_driver);
+}
+
+module_init(alsa_ad1889_init);
+module_exit(alsa_ad1889_fini);
diff --git a/linux/sound/pci/ad1889.h b/linux/sound/pci/ad1889.h
new file mode 100644
index 0000000..5e6dad5
--- /dev/null
+++ b/linux/sound/pci/ad1889.h
@@ -0,0 +1,189 @@
+/* Analog Devices 1889 audio driver
+ * Copyright (C) 2004, Kyle McMartin <kyle@parisc-linux.org>
+ */
+
+#ifndef __AD1889_H__
+#define __AD1889_H__
+
+#define AD_DS_WSMC	0x00 /* wave/synthesis channel mixer control */
+#define  AD_DS_WSMC_SYEN 0x0004 /* synthesis channel enable */
+#define  AD_DS_WSMC_SYRQ 0x0030 /* synth. fifo request point */
+#define  AD_DS_WSMC_WA16 0x0100 /* wave channel 16bit select */
+#define  AD_DS_WSMC_WAST 0x0200 /* wave channel stereo select */
+#define  AD_DS_WSMC_WAEN 0x0400 /* wave channel enable */
+#define  AD_DS_WSMC_WARQ 0x3000 /* wave fifo request point */
+
+#define AD_DS_RAMC	0x02 /* resampler/ADC channel mixer control */
+#define  AD_DS_RAMC_AD16 0x0001 /* ADC channel 16bit select */
+#define  AD_DS_RAMC_ADST 0x0002 /* ADC channel stereo select */
+#define  AD_DS_RAMC_ADEN 0x0004 /* ADC channel enable */
+#define  AD_DS_RAMC_ACRQ 0x0030 /* ADC fifo request point */
+#define  AD_DS_RAMC_REEN 0x0400 /* resampler channel enable */
+#define  AD_DS_RAMC_RERQ 0x3000 /* res. fifo request point */
+
+#define AD_DS_WADA	0x04 /* wave channel mix attenuation */
+#define  AD_DS_WADA_RWAM 0x0080 /* right wave mute */
+#define  AD_DS_WADA_RWAA 0x001f /* right wave attenuation */
+#define  AD_DS_WADA_LWAM 0x8000 /* left wave mute */
+#define  AD_DS_WADA_LWAA 0x3e00 /* left wave attenuation */
+
+#define AD_DS_SYDA	0x06 /* synthesis channel mix attenuation */
+#define  AD_DS_SYDA_RSYM 0x0080 /* right synthesis mute */
+#define  AD_DS_SYDA_RSYA 0x001f /* right synthesis attenuation */
+#define  AD_DS_SYDA_LSYM 0x8000 /* left synthesis mute */
+#define  AD_DS_SYDA_LSYA 0x3e00 /* left synthesis attenuation */
+
+#define AD_DS_WAS	0x08 /* wave channel sample rate */
+#define  AD_DS_WAS_WAS   0xffff /* sample rate mask */
+
+#define AD_DS_RES	0x0a /* resampler channel sample rate */
+#define  AD_DS_RES_RES   0xffff /* sample rate mask */
+
+#define AD_DS_CCS	0x0c /* chip control/status */
+#define  AD_DS_CCS_ADO   0x0001 /* ADC channel overflow */
+#define  AD_DS_CCS_REO   0x0002 /* resampler channel overflow */
+#define  AD_DS_CCS_SYU   0x0004 /* synthesis channel underflow */
+#define  AD_DS_CCS_WAU   0x0008 /* wave channel underflow */
+/* bits 4 -> 7, 9, 11 -> 14 reserved */
+#define  AD_DS_CCS_XTD   0x0100 /* xtd delay control (4096 clock cycles) */
+#define  AD_DS_CCS_PDALL 0x0400 /* power */
+#define  AD_DS_CCS_CLKEN 0x8000 /* clock */
+
+#define AD_DMA_RESBA	0x40 /* RES base address */
+#define AD_DMA_RESCA	0x44 /* RES current address */
+#define AD_DMA_RESBC	0x48 /* RES base count */
+#define AD_DMA_RESCC	0x4c /* RES current count */
+
+#define AD_DMA_ADCBA	0x50 /* ADC base address */
+#define AD_DMA_ADCCA	0x54 /* ADC current address */
+#define AD_DMA_ADCBC	0x58 /* ADC base count */
+#define AD_DMA_ADCCC	0x5c /* ADC current count */
+
+#define AD_DMA_SYNBA	0x60 /* synth base address */
+#define AD_DMA_SYNCA	0x64 /* synth current address */
+#define AD_DMA_SYNBC	0x68 /* synth base count */
+#define AD_DMA_SYNCC	0x6c /* synth current count */
+
+#define AD_DMA_WAVBA	0x70 /* wave base address */
+#define AD_DMA_WAVCA	0x74 /* wave current address */
+#define AD_DMA_WAVBC	0x78 /* wave base count */
+#define AD_DMA_WAVCC	0x7c /* wave current count */
+
+#define AD_DMA_RESIC	0x80 /* RES dma interrupt current byte count */
+#define AD_DMA_RESIB	0x84 /* RES dma interrupt base byte count */
+
+#define AD_DMA_ADCIC	0x88 /* ADC dma interrupt current byte count */
+#define AD_DMA_ADCIB	0x8c /* ADC dma interrupt base byte count */
+
+#define AD_DMA_SYNIC	0x90 /* synth dma interrupt current byte count */
+#define AD_DMA_SYNIB	0x94 /* synth dma interrupt base byte count */
+
+#define AD_DMA_WAVIC	0x98 /* wave dma interrupt current byte count */
+#define AD_DMA_WAVIB	0x9c /* wave dma interrupt base byte count */
+
+#define  AD_DMA_ICC	0xffffff /* current byte count mask */
+#define  AD_DMA_IBC	0xffffff /* base byte count mask */
+/* bits 24 -> 31 reserved */
+
+/* 4 bytes pad */
+#define AD_DMA_ADC	0xa8	/* ADC      dma control and status */
+#define AD_DMA_SYNTH	0xb0	/* Synth    dma control and status */
+#define AD_DMA_WAV	0xb8	/* wave     dma control and status */
+#define AD_DMA_RES	0xa0	/* Resample dma control and status */
+
+#define  AD_DMA_SGDE	0x0001 /* SGD mode enable */
+#define  AD_DMA_LOOP	0x0002 /* loop enable */
+#define  AD_DMA_IM	0x000c /* interrupt mode mask */
+#define  AD_DMA_IM_DIS	(~AD_DMA_IM)	/* disable */
+#define  AD_DMA_IM_CNT	0x0004 /* interrupt on count */
+#define  AD_DMA_IM_SGD	0x0008 /* interrupt on SGD flag */
+#define  AD_DMA_IM_EOL	0x000c /* interrupt on End of Linked List */
+#define  AD_DMA_SGDS	0x0030 /* SGD status */
+#define  AD_DMA_SFLG	0x0040 /* SGD flag */
+#define  AD_DMA_EOL	0x0080 /* SGD end of list */
+/* bits 8 -> 15 reserved */
+
+#define AD_DMA_DISR	0xc0 /* dma interrupt status */
+#define  AD_DMA_DISR_RESI 0x000001 /* resampler channel interrupt */
+#define  AD_DMA_DISR_ADCI 0x000002 /* ADC channel interrupt */
+#define  AD_DMA_DISR_SYNI 0x000004 /* synthesis channel interrupt */
+#define  AD_DMA_DISR_WAVI 0x000008 /* wave channel interrupt */
+/* bits 4, 5 reserved */
+#define  AD_DMA_DISR_SEPS 0x000040 /* serial eeprom status */
+/* bits 7 -> 13 reserved */
+#define  AD_DMA_DISR_PMAI 0x004000 /* pci master abort interrupt */
+#define  AD_DMA_DISR_PTAI 0x008000 /* pci target abort interrupt */
+#define  AD_DMA_DISR_PTAE 0x010000 /* pci target abort interrupt enable */
+#define  AD_DMA_DISR_PMAE 0x020000 /* pci master abort interrupt enable */
+/* bits 19 -> 31 reserved */
+
+/* interrupt mask */
+#define  AD_INTR_MASK     (AD_DMA_DISR_RESI|AD_DMA_DISR_ADCI| \
+                           AD_DMA_DISR_WAVI|AD_DMA_DISR_SYNI| \
+                           AD_DMA_DISR_PMAI|AD_DMA_DISR_PTAI)
+
+#define AD_DMA_CHSS	0xc4 /* dma channel stop status */
+#define  AD_DMA_CHSS_RESS 0x000001 /* resampler channel stopped */
+#define  AD_DMA_CHSS_ADCS 0x000002 /* ADC channel stopped */
+#define  AD_DMA_CHSS_SYNS 0x000004 /* synthesis channel stopped */
+#define  AD_DMA_CHSS_WAVS 0x000008 /* wave channel stopped */
+
+#define AD_GPIO_IPC	0xc8	/* gpio port control */
+#define AD_GPIO_OP	0xca	/* gpio output port status */
+#define AD_GPIO_IP	0xcc	/* gpio  input port status */
+
+#define AD_AC97_BASE	0x100	/* ac97 base register */
+
+#define AD_AC97_RESET   0x100   /* reset */
+
+#define AD_AC97_PWR_CTL	0x126	/* == AC97_POWERDOWN */
+#define  AD_AC97_PWR_ADC 0x0001 /* ADC ready status */
+#define  AD_AC97_PWR_DAC 0x0002 /* DAC ready status */
+#define  AD_AC97_PWR_PR0 0x0100 /* PR0 (ADC) powerdown */
+#define  AD_AC97_PWR_PR1 0x0200 /* PR1 (DAC) powerdown */
+
+#define AD_MISC_CTL     0x176 /* misc control */
+#define  AD_MISC_CTL_DACZ   0x8000 /* set for zero fill, unset for repeat */
+#define  AD_MISC_CTL_ARSR   0x0001 /* set for SR1, unset for SR0 */
+#define  AD_MISC_CTL_ALSR   0x0100
+#define  AD_MISC_CTL_DLSR   0x0400
+#define  AD_MISC_CTL_DRSR   0x0004
+
+#define AD_AC97_SR0     0x178 /* sample rate 0, 0xbb80 == 48K */
+#define  AD_AC97_SR0_48K 0xbb80 /* 48KHz */
+#define AD_AC97_SR1     0x17a /* sample rate 1 */
+
+#define AD_AC97_ACIC	0x180 /* ac97 codec interface control */
+#define  AD_AC97_ACIC_ACIE  0x0001 /* analog codec interface enable */
+#define  AD_AC97_ACIC_ACRD  0x0002 /* analog codec reset disable */
+#define  AD_AC97_ACIC_ASOE  0x0004 /* audio stream output enable */
+#define  AD_AC97_ACIC_VSRM  0x0008 /* variable sample rate mode */
+#define  AD_AC97_ACIC_FSDH  0x0100 /* force SDATA_OUT high */
+#define  AD_AC97_ACIC_FSYH  0x0200 /* force sync high */
+#define  AD_AC97_ACIC_ACRDY 0x8000 /* analog codec ready status */
+/* bits 10 -> 14 reserved */
+
+
+#define AD_DS_MEMSIZE	512
+#define AD_OPL_MEMSIZE	16
+#define AD_MIDI_MEMSIZE	16
+
+#define AD_WAV_STATE	0
+#define AD_ADC_STATE	1
+#define AD_MAX_STATES	2
+
+#define AD_CHAN_WAV	0x0001
+#define AD_CHAN_ADC	0x0002
+#define AD_CHAN_RES	0x0004
+#define AD_CHAN_SYN	0x0008
+
+
+/* The chip would support 4 GB buffers and 16 MB periods,
+ * but let's not overdo it ... */
+#define BUFFER_BYTES_MAX	(256 * 1024)
+#define PERIOD_BYTES_MIN	32
+#define PERIOD_BYTES_MAX	(BUFFER_BYTES_MAX / 2)
+#define PERIODS_MIN		2
+#define PERIODS_MAX		(BUFFER_BYTES_MAX / PERIOD_BYTES_MIN)
+
+#endif /* __AD1889_H__ */
diff --git a/linux/sound/pci/ak4531_codec.c b/linux/sound/pci/ak4531_codec.c
new file mode 100644
index 0000000..fd135e3
--- /dev/null
+++ b/linux/sound/pci/ak4531_codec.c
@@ -0,0 +1,493 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Universal routines for AK4531 codec
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/ak4531_codec.h>
+#include <sound/tlv.h>
+
+/*
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Universal routines for AK4531 codec");
+MODULE_LICENSE("GPL");
+*/
+
+#ifdef CONFIG_PROC_FS
+static void snd_ak4531_proc_init(struct snd_card *card, struct snd_ak4531 *ak4531);
+#else
+#define snd_ak4531_proc_init(card,ak)
+#endif
+
+/*
+ *
+ */
+ 
+#if 0
+
+static void snd_ak4531_dump(struct snd_ak4531 *ak4531)
+{
+	int idx;
+	
+	for (idx = 0; idx < 0x19; idx++)
+		printk(KERN_DEBUG "ak4531 0x%x: 0x%x\n",
+		       idx, ak4531->regs[idx]);
+}
+
+#endif
+
+/*
+ *
+ */
+
+#define AK4531_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_ak4531_info_single, \
+  .get = snd_ak4531_get_single, .put = snd_ak4531_put_single, \
+  .private_value = reg | (shift << 16) | (mask << 24) | (invert << 22) }
+#define AK4531_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv)    \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .name = xname, .index = xindex, \
+  .info = snd_ak4531_info_single, \
+  .get = snd_ak4531_get_single, .put = snd_ak4531_put_single, \
+  .private_value = reg | (shift << 16) | (mask << 24) | (invert << 22), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_ak4531_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+ 
+static int snd_ak4531_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 16) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int val;
+
+	mutex_lock(&ak4531->reg_mutex);
+	val = (ak4531->regs[reg] >> shift) & mask;
+	mutex_unlock(&ak4531->reg_mutex);
+	if (invert) {
+		val = mask - val;
+	}
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int snd_ak4531_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 16) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	int val;
+
+	val = ucontrol->value.integer.value[0] & mask;
+	if (invert) {
+		val = mask - val;
+	}
+	val <<= shift;
+	mutex_lock(&ak4531->reg_mutex);
+	val = (ak4531->regs[reg] & ~(mask << shift)) | val;
+	change = val != ak4531->regs[reg];
+	ak4531->write(ak4531, reg, ak4531->regs[reg] = val);
+	mutex_unlock(&ak4531->reg_mutex);
+	return change;
+}
+
+#define AK4531_DOUBLE(xname, xindex, left_reg, right_reg, left_shift, right_shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_ak4531_info_double, \
+  .get = snd_ak4531_get_double, .put = snd_ak4531_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (left_shift << 16) | (right_shift << 19) | (mask << 24) | (invert << 22) }
+#define AK4531_DOUBLE_TLV(xname, xindex, left_reg, right_reg, left_shift, right_shift, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .name = xname, .index = xindex, \
+  .info = snd_ak4531_info_double, \
+  .get = snd_ak4531_get_double, .put = snd_ak4531_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (left_shift << 16) | (right_shift << 19) | (mask << 24) | (invert << 22), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_ak4531_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+ 
+static int snd_ak4531_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x07;
+	int right_shift = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int left, right;
+
+	mutex_lock(&ak4531->reg_mutex);
+	left = (ak4531->regs[left_reg] >> left_shift) & mask;
+	right = (ak4531->regs[right_reg] >> right_shift) & mask;
+	mutex_unlock(&ak4531->reg_mutex);
+	if (invert) {
+		left = mask - left;
+		right = mask - right;
+	}
+	ucontrol->value.integer.value[0] = left;
+	ucontrol->value.integer.value[1] = right;
+	return 0;
+}
+
+static int snd_ak4531_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x07;
+	int right_shift = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	int left, right;
+
+	left = ucontrol->value.integer.value[0] & mask;
+	right = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		left = mask - left;
+		right = mask - right;
+	}
+	left <<= left_shift;
+	right <<= right_shift;
+	mutex_lock(&ak4531->reg_mutex);
+	if (left_reg == right_reg) {
+		left = (ak4531->regs[left_reg] & ~((mask << left_shift) | (mask << right_shift))) | left | right;
+		change = left != ak4531->regs[left_reg];
+		ak4531->write(ak4531, left_reg, ak4531->regs[left_reg] = left);
+	} else {
+		left = (ak4531->regs[left_reg] & ~(mask << left_shift)) | left;
+		right = (ak4531->regs[right_reg] & ~(mask << right_shift)) | right;
+		change = left != ak4531->regs[left_reg] || right != ak4531->regs[right_reg];
+		ak4531->write(ak4531, left_reg, ak4531->regs[left_reg] = left);
+		ak4531->write(ak4531, right_reg, ak4531->regs[right_reg] = right);
+	}
+	mutex_unlock(&ak4531->reg_mutex);
+	return change;
+}
+
+#define AK4531_INPUT_SW(xname, xindex, reg1, reg2, left_shift, right_shift) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_ak4531_info_input_sw, \
+  .get = snd_ak4531_get_input_sw, .put = snd_ak4531_put_input_sw, \
+  .private_value = reg1 | (reg2 << 8) | (left_shift << 16) | (right_shift << 24) }
+
+static int snd_ak4531_info_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 4;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+ 
+static int snd_ak4531_get_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
+	int reg1 = kcontrol->private_value & 0xff;
+	int reg2 = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x0f;
+	int right_shift = (kcontrol->private_value >> 24) & 0x0f;
+
+	mutex_lock(&ak4531->reg_mutex);
+	ucontrol->value.integer.value[0] = (ak4531->regs[reg1] >> left_shift) & 1;
+	ucontrol->value.integer.value[1] = (ak4531->regs[reg2] >> left_shift) & 1;
+	ucontrol->value.integer.value[2] = (ak4531->regs[reg1] >> right_shift) & 1;
+	ucontrol->value.integer.value[3] = (ak4531->regs[reg2] >> right_shift) & 1;
+	mutex_unlock(&ak4531->reg_mutex);
+	return 0;
+}
+
+static int snd_ak4531_put_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol);
+	int reg1 = kcontrol->private_value & 0xff;
+	int reg2 = (kcontrol->private_value >> 8) & 0xff;
+	int left_shift = (kcontrol->private_value >> 16) & 0x0f;
+	int right_shift = (kcontrol->private_value >> 24) & 0x0f;
+	int change;
+	int val1, val2;
+
+	mutex_lock(&ak4531->reg_mutex);
+	val1 = ak4531->regs[reg1] & ~((1 << left_shift) | (1 << right_shift));
+	val2 = ak4531->regs[reg2] & ~((1 << left_shift) | (1 << right_shift));
+	val1 |= (ucontrol->value.integer.value[0] & 1) << left_shift;
+	val2 |= (ucontrol->value.integer.value[1] & 1) << left_shift;
+	val1 |= (ucontrol->value.integer.value[2] & 1) << right_shift;
+	val2 |= (ucontrol->value.integer.value[3] & 1) << right_shift;
+	change = val1 != ak4531->regs[reg1] || val2 != ak4531->regs[reg2];
+	ak4531->write(ak4531, reg1, ak4531->regs[reg1] = val1);
+	ak4531->write(ak4531, reg2, ak4531->regs[reg2] = val2);
+	mutex_unlock(&ak4531->reg_mutex);
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_master, -6200, 200, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_mono, -2800, 400, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_input, -5000, 200, 0);
+
+static struct snd_kcontrol_new snd_ak4531_controls[] __devinitdata = {
+
+AK4531_DOUBLE_TLV("Master Playback Switch", 0,
+		  AK4531_LMASTER, AK4531_RMASTER, 7, 7, 1, 1,
+		  db_scale_master),
+AK4531_DOUBLE("Master Playback Volume", 0, AK4531_LMASTER, AK4531_RMASTER, 0, 0, 0x1f, 1),
+
+AK4531_SINGLE_TLV("Master Mono Playback Switch", 0, AK4531_MONO_OUT, 7, 1, 1,
+		  db_scale_mono),
+AK4531_SINGLE("Master Mono Playback Volume", 0, AK4531_MONO_OUT, 0, 0x07, 1),
+
+AK4531_DOUBLE("PCM Switch", 0, AK4531_LVOICE, AK4531_RVOICE, 7, 7, 1, 1),
+AK4531_DOUBLE_TLV("PCM Volume", 0, AK4531_LVOICE, AK4531_RVOICE, 0, 0, 0x1f, 1,
+		  db_scale_input),
+AK4531_DOUBLE("PCM Playback Switch", 0, AK4531_OUT_SW2, AK4531_OUT_SW2, 3, 2, 1, 0),
+AK4531_DOUBLE("PCM Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 2, 2, 1, 0),
+
+AK4531_DOUBLE("PCM Switch", 1, AK4531_LFM, AK4531_RFM, 7, 7, 1, 1),
+AK4531_DOUBLE_TLV("PCM Volume", 1, AK4531_LFM, AK4531_RFM, 0, 0, 0x1f, 1,
+		  db_scale_input),
+AK4531_DOUBLE("PCM Playback Switch", 1, AK4531_OUT_SW1, AK4531_OUT_SW1, 6, 5, 1, 0),
+AK4531_INPUT_SW("PCM Capture Route", 1, AK4531_LIN_SW1, AK4531_RIN_SW1, 6, 5),
+
+AK4531_DOUBLE("CD Switch", 0, AK4531_LCD, AK4531_RCD, 7, 7, 1, 1),
+AK4531_DOUBLE_TLV("CD Volume", 0, AK4531_LCD, AK4531_RCD, 0, 0, 0x1f, 1,
+		  db_scale_input),
+AK4531_DOUBLE("CD Playback Switch", 0, AK4531_OUT_SW1, AK4531_OUT_SW1, 2, 1, 1, 0),
+AK4531_INPUT_SW("CD Capture Route", 0, AK4531_LIN_SW1, AK4531_RIN_SW1, 2, 1),
+
+AK4531_DOUBLE("Line Switch", 0, AK4531_LLINE, AK4531_RLINE, 7, 7, 1, 1),
+AK4531_DOUBLE_TLV("Line Volume", 0, AK4531_LLINE, AK4531_RLINE, 0, 0, 0x1f, 1,
+		  db_scale_input),
+AK4531_DOUBLE("Line Playback Switch", 0, AK4531_OUT_SW1, AK4531_OUT_SW1, 4, 3, 1, 0),
+AK4531_INPUT_SW("Line Capture Route", 0, AK4531_LIN_SW1, AK4531_RIN_SW1, 4, 3),
+
+AK4531_DOUBLE("Aux Switch", 0, AK4531_LAUXA, AK4531_RAUXA, 7, 7, 1, 1),
+AK4531_DOUBLE_TLV("Aux Volume", 0, AK4531_LAUXA, AK4531_RAUXA, 0, 0, 0x1f, 1,
+		  db_scale_input),
+AK4531_DOUBLE("Aux Playback Switch", 0, AK4531_OUT_SW2, AK4531_OUT_SW2, 5, 4, 1, 0),
+AK4531_INPUT_SW("Aux Capture Route", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 4, 3),
+
+AK4531_SINGLE("Mono Switch", 0, AK4531_MONO1, 7, 1, 1),
+AK4531_SINGLE_TLV("Mono Volume", 0, AK4531_MONO1, 0, 0x1f, 1, db_scale_input),
+AK4531_SINGLE("Mono Playback Switch", 0, AK4531_OUT_SW2, 0, 1, 0),
+AK4531_DOUBLE("Mono Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 0, 0, 1, 0),
+
+AK4531_SINGLE("Mono Switch", 1, AK4531_MONO2, 7, 1, 1),
+AK4531_SINGLE_TLV("Mono Volume", 1, AK4531_MONO2, 0, 0x1f, 1, db_scale_input),
+AK4531_SINGLE("Mono Playback Switch", 1, AK4531_OUT_SW2, 1, 1, 0),
+AK4531_DOUBLE("Mono Capture Switch", 1, AK4531_LIN_SW2, AK4531_RIN_SW2, 1, 1, 1, 0),
+
+AK4531_SINGLE_TLV("Mic Volume", 0, AK4531_MIC, 0, 0x1f, 1, db_scale_input),
+AK4531_SINGLE("Mic Switch", 0, AK4531_MIC, 7, 1, 1),
+AK4531_SINGLE("Mic Playback Switch", 0, AK4531_OUT_SW1, 0, 1, 0),
+AK4531_DOUBLE("Mic Capture Switch", 0, AK4531_LIN_SW1, AK4531_RIN_SW1, 0, 0, 1, 0),
+
+AK4531_DOUBLE("Mic Bypass Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 7, 7, 1, 0),
+AK4531_DOUBLE("Mono1 Bypass Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 6, 6, 1, 0),
+AK4531_DOUBLE("Mono2 Bypass Capture Switch", 0, AK4531_LIN_SW2, AK4531_RIN_SW2, 5, 5, 1, 0),
+
+AK4531_SINGLE("AD Input Select", 0, AK4531_AD_IN, 0, 1, 0),
+AK4531_SINGLE("Mic Boost (+30dB)", 0, AK4531_MIC_GAIN, 0, 1, 0)
+};
+
+static int snd_ak4531_free(struct snd_ak4531 *ak4531)
+{
+	if (ak4531) {
+		if (ak4531->private_free)
+			ak4531->private_free(ak4531);
+		kfree(ak4531);
+	}
+	return 0;
+}
+
+static int snd_ak4531_dev_free(struct snd_device *device)
+{
+	struct snd_ak4531 *ak4531 = device->device_data;
+	return snd_ak4531_free(ak4531);
+}
+
+static u8 snd_ak4531_initial_map[0x19 + 1] = {
+	0x9f,		/* 00: Master Volume Lch */
+	0x9f,		/* 01: Master Volume Rch */
+	0x9f,		/* 02: Voice Volume Lch */
+	0x9f,		/* 03: Voice Volume Rch */
+	0x9f,		/* 04: FM Volume Lch */
+	0x9f,		/* 05: FM Volume Rch */
+	0x9f,		/* 06: CD Audio Volume Lch */
+	0x9f,		/* 07: CD Audio Volume Rch */
+	0x9f,		/* 08: Line Volume Lch */
+	0x9f,		/* 09: Line Volume Rch */
+	0x9f,		/* 0a: Aux Volume Lch */
+	0x9f,		/* 0b: Aux Volume Rch */
+	0x9f,		/* 0c: Mono1 Volume */
+	0x9f,		/* 0d: Mono2 Volume */
+	0x9f,		/* 0e: Mic Volume */
+	0x87,		/* 0f: Mono-out Volume */
+	0x00,		/* 10: Output Mixer SW1 */
+	0x00,		/* 11: Output Mixer SW2 */
+	0x00,		/* 12: Lch Input Mixer SW1 */
+	0x00,		/* 13: Rch Input Mixer SW1 */
+	0x00,		/* 14: Lch Input Mixer SW2 */
+	0x00,		/* 15: Rch Input Mixer SW2 */
+	0x00,		/* 16: Reset & Power Down */
+	0x00,		/* 17: Clock Select */
+	0x00,		/* 18: AD Input Select */
+	0x01		/* 19: Mic Amp Setup */
+};
+
+int __devinit snd_ak4531_mixer(struct snd_card *card,
+			       struct snd_ak4531 *_ak4531,
+			       struct snd_ak4531 **rak4531)
+{
+	unsigned int idx;
+	int err;
+	struct snd_ak4531 *ak4531;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_ak4531_dev_free,
+	};
+
+	if (snd_BUG_ON(!card || !_ak4531))
+		return -EINVAL;
+	if (rak4531)
+		*rak4531 = NULL;
+	ak4531 = kzalloc(sizeof(*ak4531), GFP_KERNEL);
+	if (ak4531 == NULL)
+		return -ENOMEM;
+	*ak4531 = *_ak4531;
+	mutex_init(&ak4531->reg_mutex);
+	if ((err = snd_component_add(card, "AK4531")) < 0) {
+		snd_ak4531_free(ak4531);
+		return err;
+	}
+	strcpy(card->mixername, "Asahi Kasei AK4531");
+	ak4531->write(ak4531, AK4531_RESET, 0x03);	/* no RST, PD */
+	udelay(100);
+	ak4531->write(ak4531, AK4531_CLOCK, 0x00);	/* CODEC ADC and CODEC DAC use {LR,B}CLK2 and run off LRCLK2 PLL */
+	for (idx = 0; idx <= 0x19; idx++) {
+		if (idx == AK4531_RESET || idx == AK4531_CLOCK)
+			continue;
+		ak4531->write(ak4531, idx, ak4531->regs[idx] = snd_ak4531_initial_map[idx]);	/* recording source is mixer */
+	}
+	for (idx = 0; idx < ARRAY_SIZE(snd_ak4531_controls); idx++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ak4531_controls[idx], ak4531))) < 0) {
+			snd_ak4531_free(ak4531);
+			return err;
+		}
+	}
+	snd_ak4531_proc_init(card, ak4531);
+	if ((err = snd_device_new(card, SNDRV_DEV_CODEC, ak4531, &ops)) < 0) {
+		snd_ak4531_free(ak4531);
+		return err;
+	}
+
+#if 0
+	snd_ak4531_dump(ak4531);
+#endif
+	if (rak4531)
+		*rak4531 = ak4531;
+	return 0;
+}
+
+/*
+ * power management
+ */
+#ifdef CONFIG_PM
+void snd_ak4531_suspend(struct snd_ak4531 *ak4531)
+{
+	/* mute */
+	ak4531->write(ak4531, AK4531_LMASTER, 0x9f);
+	ak4531->write(ak4531, AK4531_RMASTER, 0x9f);
+	/* powerdown */
+	ak4531->write(ak4531, AK4531_RESET, 0x01);
+}
+
+void snd_ak4531_resume(struct snd_ak4531 *ak4531)
+{
+	int idx;
+
+	/* initialize */
+	ak4531->write(ak4531, AK4531_RESET, 0x03);
+	udelay(100);
+	ak4531->write(ak4531, AK4531_CLOCK, 0x00);
+	/* restore mixer registers */
+	for (idx = 0; idx <= 0x19; idx++) {
+		if (idx == AK4531_RESET || idx == AK4531_CLOCK)
+			continue;
+		ak4531->write(ak4531, idx, ak4531->regs[idx]);
+	}
+}
+#endif
+
+#ifdef CONFIG_PROC_FS
+/*
+ * /proc interface
+ */
+
+static void snd_ak4531_proc_read(struct snd_info_entry *entry, 
+				 struct snd_info_buffer *buffer)
+{
+	struct snd_ak4531 *ak4531 = entry->private_data;
+
+	snd_iprintf(buffer, "Asahi Kasei AK4531\n\n");
+	snd_iprintf(buffer, "Recording source   : %s\n"
+		    "MIC gain           : %s\n",
+		    ak4531->regs[AK4531_AD_IN] & 1 ? "external" : "mixer",
+		    ak4531->regs[AK4531_MIC_GAIN] & 1 ? "+30dB" : "+0dB");
+}
+
+static void __devinit
+snd_ak4531_proc_init(struct snd_card *card, struct snd_ak4531 *ak4531)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(card, "ak4531", &entry))
+		snd_info_set_text_ops(entry, ak4531, snd_ak4531_proc_read);
+}
+#endif
diff --git a/linux/sound/pci/ali5451/Makefile b/linux/sound/pci/ali5451/Makefile
new file mode 100644
index 0000000..713459c
--- /dev/null
+++ b/linux/sound/pci/ali5451/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ali5451-objs := ali5451.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_ALI5451) += snd-ali5451.o
diff --git a/linux/sound/pci/ali5451/ali5451.c b/linux/sound/pci/ali5451/ali5451.c
new file mode 100644
index 0000000..5c6e322
--- /dev/null
+++ b/linux/sound/pci/ali5451/ali5451.c
@@ -0,0 +1,2319 @@
+/*
+ *  Matt Wu <Matt_Wu@acersoftech.com.cn>
+ *  Apr 26, 2001
+ *  Routines for control of ALi pci audio M5451
+ *
+ *  BUGS:
+ *    --
+ *
+ *  TODO:
+ *    --
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public Lcodecnse as published by
+ *   the Free Software Foundation; either version 2 of the Lcodecnse, 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 Lcodecnse for more details.
+ *
+ *   You should have received a copy of the GNU General Public Lcodecnse
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/ac97_codec.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Matt Wu <Matt_Wu@acersoftech.com.cn>");
+MODULE_DESCRIPTION("ALI M5451");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ALI,M5451,pci},{ALI,M5451}}");
+
+static int index = SNDRV_DEFAULT_IDX1;	/* Index */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+static int pcm_channels = 32;
+static int spdif;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for ALI M5451 PCI Audio.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for ALI M5451 PCI Audio.");
+module_param(pcm_channels, int, 0444);
+MODULE_PARM_DESC(pcm_channels, "PCM Channels");
+module_param(spdif, bool, 0444);
+MODULE_PARM_DESC(spdif, "Support SPDIF I/O");
+
+/* just for backward compatibility */
+static int enable;
+module_param(enable, bool, 0444);
+
+
+/*
+ *  Debug part definitions
+ */
+
+/* #define ALI_DEBUG */
+
+#ifdef ALI_DEBUG
+#define snd_ali_printk(format, args...) printk(KERN_DEBUG format, ##args);
+#else
+#define snd_ali_printk(format, args...)
+#endif
+
+/*
+ *  Constants definition
+ */
+
+#define DEVICE_ID_ALI5451	((PCI_VENDOR_ID_AL<<16)|PCI_DEVICE_ID_AL_M5451)
+
+
+#define ALI_CHANNELS		32
+
+#define ALI_PCM_IN_CHANNEL	31
+#define ALI_SPDIF_IN_CHANNEL	19
+#define ALI_SPDIF_OUT_CHANNEL	15
+#define ALI_CENTER_CHANNEL	24
+#define ALI_LEF_CHANNEL		23
+#define ALI_SURR_LEFT_CHANNEL	26
+#define ALI_SURR_RIGHT_CHANNEL	25
+#define ALI_MODEM_IN_CHANNEL    21
+#define ALI_MODEM_OUT_CHANNEL   20
+
+#define	SNDRV_ALI_VOICE_TYPE_PCM	01
+#define SNDRV_ALI_VOICE_TYPE_OTH	02
+
+#define	ALI_5451_V02		0x02
+
+/*
+ *  Direct Registers
+ */
+
+#define ALI_LEGACY_DMAR0        0x00  /* ADR0 */
+#define ALI_LEGACY_DMAR4        0x04  /* CNT0 */
+#define ALI_LEGACY_DMAR11       0x0b  /* MOD  */
+#define ALI_LEGACY_DMAR15       0x0f  /* MMR  */
+#define ALI_MPUR0		0x20
+#define ALI_MPUR1		0x21
+#define ALI_MPUR2		0x22
+#define ALI_MPUR3		0x23
+
+#define	ALI_AC97_WRITE		0x40
+#define ALI_AC97_READ		0x44
+
+#define ALI_SCTRL		0x48
+#define   ALI_SPDIF_OUT_ENABLE		0x20
+#define   ALI_SCTRL_LINE_IN2		(1 << 9)
+#define   ALI_SCTRL_GPIO_IN2		(1 << 13)
+#define   ALI_SCTRL_LINE_OUT_EN 	(1 << 20)
+#define   ALI_SCTRL_GPIO_OUT_EN 	(1 << 23)
+#define   ALI_SCTRL_CODEC1_READY	(1 << 24)
+#define   ALI_SCTRL_CODEC2_READY	(1 << 25)
+#define ALI_AC97_GPIO		0x4c
+#define   ALI_AC97_GPIO_ENABLE		0x8000
+#define   ALI_AC97_GPIO_DATA_SHIFT	16
+#define ALI_SPDIF_CS		0x70
+#define ALI_SPDIF_CTRL		0x74
+#define   ALI_SPDIF_IN_FUNC_ENABLE	0x02
+#define   ALI_SPDIF_IN_CH_STATUS	0x40
+#define   ALI_SPDIF_OUT_CH_STATUS	0xbf
+#define ALI_START		0x80
+#define ALI_STOP		0x84
+#define ALI_CSPF		0x90
+#define ALI_AINT		0x98
+#define ALI_GC_CIR		0xa0
+	#define ENDLP_IE		0x00001000
+	#define MIDLP_IE		0x00002000
+#define ALI_AINTEN		0xa4
+#define ALI_VOLUME		0xa8
+#define ALI_SBDELTA_DELTA_R     0xac
+#define ALI_MISCINT		0xb0
+	#define ADDRESS_IRQ		0x00000020
+	#define TARGET_REACHED		0x00008000
+	#define MIXER_OVERFLOW		0x00000800
+	#define MIXER_UNDERFLOW		0x00000400
+	#define GPIO_IRQ		0x01000000
+#define ALI_SBBL_SBCL           0xc0
+#define ALI_SBCTRL_SBE2R_SBDD   0xc4
+#define ALI_STIMER		0xc8
+#define ALI_GLOBAL_CONTROL	0xd4
+#define   ALI_SPDIF_OUT_SEL_PCM		0x00000400 /* bit 10 */
+#define   ALI_SPDIF_IN_SUPPORT		0x00000800 /* bit 11 */
+#define   ALI_SPDIF_OUT_CH_ENABLE	0x00008000 /* bit 15 */
+#define   ALI_SPDIF_IN_CH_ENABLE	0x00080000 /* bit 19 */
+#define   ALI_PCM_IN_ENABLE		0x80000000 /* bit 31 */
+
+#define ALI_CSO_ALPHA_FMS	0xe0
+#define ALI_LBA			0xe4
+#define ALI_ESO_DELTA		0xe8
+#define ALI_GVSEL_PAN_VOC_CTRL_EC	0xf0
+#define ALI_EBUF1		0xf4
+#define ALI_EBUF2		0xf8
+
+#define ALI_REG(codec, x) ((codec)->port + x)
+
+#define MAX_CODECS 2
+
+
+struct snd_ali;
+struct snd_ali_voice;
+
+struct snd_ali_channel_control {
+	/* register data */
+	struct REGDATA {
+		unsigned int start;
+		unsigned int stop;
+		unsigned int aint;
+		unsigned int ainten;
+	} data;
+		
+	/* register addresses */
+	struct REGS {
+		unsigned int start;
+		unsigned int stop;
+		unsigned int aint;
+		unsigned int ainten;
+		unsigned int ac97read;
+		unsigned int ac97write;
+	} regs;
+
+};
+
+struct snd_ali_voice {
+	unsigned int number;
+	unsigned int use :1,
+		pcm :1,
+		midi :1,
+		mode :1,
+		synth :1,
+		running :1;
+
+	/* PCM data */
+	struct snd_ali *codec;
+	struct snd_pcm_substream *substream;
+	struct snd_ali_voice *extra;
+	
+	int eso;                /* final ESO value for channel */
+	int count;              /* runtime->period_size */
+
+	/* --- */
+
+	void *private_data;
+	void (*private_free)(void *private_data);
+};
+
+
+struct snd_alidev {
+
+	struct snd_ali_voice voices[ALI_CHANNELS];	
+
+	unsigned int	chcnt;			/* num of opened channels */
+	unsigned int	chmap;			/* bitmap for opened channels */
+	unsigned int synthcount;
+
+};
+
+
+#define ALI_GLOBAL_REGS		56
+#define ALI_CHANNEL_REGS	8
+struct snd_ali_image {
+	u32 regs[ALI_GLOBAL_REGS];
+	u32 channel_regs[ALI_CHANNELS][ALI_CHANNEL_REGS];
+};
+
+
+struct snd_ali {
+	int		irq;
+	unsigned long	port;
+	unsigned char	revision;
+
+	unsigned int hw_initialized :1;
+	unsigned int spdif_support :1;
+
+	struct pci_dev	*pci;
+	struct pci_dev	*pci_m1533;
+	struct pci_dev	*pci_m7101;
+
+	struct snd_card	*card;
+	struct snd_pcm	*pcm[MAX_CODECS];
+	struct snd_alidev	synth;
+	struct snd_ali_channel_control chregs;
+
+	/* S/PDIF Mask */
+	unsigned int	spdif_mask;
+
+	unsigned int spurious_irq_count;
+	unsigned int spurious_irq_max_delta;
+
+	unsigned int num_of_codecs;
+
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97 *ac97[MAX_CODECS];
+	unsigned short	ac97_ext_id;
+	unsigned short	ac97_ext_status;
+
+	spinlock_t	reg_lock;
+	spinlock_t	voice_alloc;
+
+#ifdef CONFIG_PM
+	struct snd_ali_image *image;
+#endif
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_ali_ids) = {
+	{PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M5451), 0, 0, 0},
+	{0, }
+};
+MODULE_DEVICE_TABLE(pci, snd_ali_ids);
+
+static void snd_ali_clear_voices(struct snd_ali *, unsigned int, unsigned int);
+static unsigned short snd_ali_codec_peek(struct snd_ali *, int, unsigned short);
+static void snd_ali_codec_poke(struct snd_ali *, int, unsigned short,
+			       unsigned short);
+
+/*
+ *  AC97 ACCESS
+ */
+
+static inline unsigned int snd_ali_5451_peek(struct snd_ali *codec,
+					     unsigned int port)
+{
+	return (unsigned int)inl(ALI_REG(codec, port)); 
+}
+
+static inline void snd_ali_5451_poke(struct snd_ali *codec,
+				     unsigned int port,
+				     unsigned int val)
+{
+	outl((unsigned int)val, ALI_REG(codec, port));
+}
+
+static int snd_ali_codec_ready(struct snd_ali *codec,
+			       unsigned int port)
+{
+	unsigned long end_time;
+	unsigned int res;
+	
+	end_time = jiffies + msecs_to_jiffies(250);
+
+	for (;;) {
+		res = snd_ali_5451_peek(codec,port);
+		if (!(res & 0x8000))
+			return 0;
+		if (!time_after_eq(end_time, jiffies))
+			break;
+		schedule_timeout_uninterruptible(1);
+	}
+
+	snd_ali_5451_poke(codec, port, res & ~0x8000);
+	snd_printdd("ali_codec_ready: codec is not ready.\n ");
+	return -EIO;
+}
+
+static int snd_ali_stimer_ready(struct snd_ali *codec)
+{
+	unsigned long end_time;
+	unsigned long dwChk1,dwChk2;
+	
+	dwChk1 = snd_ali_5451_peek(codec, ALI_STIMER);
+	end_time = jiffies + msecs_to_jiffies(250);
+
+	for (;;) {
+		dwChk2 = snd_ali_5451_peek(codec, ALI_STIMER);
+		if (dwChk2 != dwChk1)
+			return 0;
+		if (!time_after_eq(end_time, jiffies))
+			break;
+		schedule_timeout_uninterruptible(1);
+	}
+
+	snd_printk(KERN_ERR "ali_stimer_read: stimer is not ready.\n");
+	return -EIO;
+}
+
+static void snd_ali_codec_poke(struct snd_ali *codec,int secondary,
+			       unsigned short reg,
+			       unsigned short val)
+{
+	unsigned int dwVal;
+	unsigned int port;
+
+	if (reg >= 0x80) {
+		snd_printk(KERN_ERR "ali_codec_poke: reg(%xh) invalid.\n", reg);
+		return;
+	}
+
+	port = codec->chregs.regs.ac97write;
+
+	if (snd_ali_codec_ready(codec, port) < 0)
+		return;
+	if (snd_ali_stimer_ready(codec) < 0)
+		return;
+
+	dwVal  = (unsigned int) (reg & 0xff);
+	dwVal |= 0x8000 | (val << 16);
+	if (secondary)
+		dwVal |= 0x0080;
+	if (codec->revision == ALI_5451_V02)
+		dwVal |= 0x0100;
+
+	snd_ali_5451_poke(codec, port, dwVal);
+
+	return ;
+}
+
+static unsigned short snd_ali_codec_peek(struct snd_ali *codec,
+					 int secondary,
+					 unsigned short reg)
+{
+	unsigned int dwVal;
+	unsigned int port;
+
+	if (reg >= 0x80) {
+		snd_printk(KERN_ERR "ali_codec_peek: reg(%xh) invalid.\n", reg);
+		return ~0;
+	}
+
+	port = codec->chregs.regs.ac97read;
+
+	if (snd_ali_codec_ready(codec, port) < 0)
+		return ~0;
+	if (snd_ali_stimer_ready(codec) < 0)
+		return ~0;
+
+	dwVal  = (unsigned int) (reg & 0xff);
+	dwVal |= 0x8000;				/* bit 15*/
+	if (secondary)
+		dwVal |= 0x0080;
+
+	snd_ali_5451_poke(codec, port, dwVal);
+
+	if (snd_ali_stimer_ready(codec) < 0)
+		return ~0;
+	if (snd_ali_codec_ready(codec, port) < 0)
+		return ~0;
+	
+	return (snd_ali_5451_peek(codec, port) & 0xffff0000) >> 16;
+}
+
+static void snd_ali_codec_write(struct snd_ac97 *ac97,
+				unsigned short reg,
+				unsigned short val )
+{
+	struct snd_ali *codec = ac97->private_data;
+
+	snd_ali_printk("codec_write: reg=%xh data=%xh.\n", reg, val);
+	if (reg == AC97_GPIO_STATUS) {
+		outl((val << ALI_AC97_GPIO_DATA_SHIFT) | ALI_AC97_GPIO_ENABLE,
+		     ALI_REG(codec, ALI_AC97_GPIO));
+		return;
+	}
+	snd_ali_codec_poke(codec, ac97->num, reg, val);
+	return ;
+}
+
+
+static unsigned short snd_ali_codec_read(struct snd_ac97 *ac97,
+					 unsigned short reg)
+{
+	struct snd_ali *codec = ac97->private_data;
+
+	snd_ali_printk("codec_read reg=%xh.\n", reg);
+	return snd_ali_codec_peek(codec, ac97->num, reg);
+}
+
+/*
+ *	AC97 Reset
+ */
+
+static int snd_ali_reset_5451(struct snd_ali *codec)
+{
+	struct pci_dev *pci_dev;
+	unsigned short wCount, wReg;
+	unsigned int   dwVal;
+	
+	pci_dev = codec->pci_m1533;
+	if (pci_dev) {
+		pci_read_config_dword(pci_dev, 0x7c, &dwVal);
+		pci_write_config_dword(pci_dev, 0x7c, dwVal | 0x08000000);
+		udelay(5000);
+		pci_read_config_dword(pci_dev, 0x7c, &dwVal);
+		pci_write_config_dword(pci_dev, 0x7c, dwVal & 0xf7ffffff);
+		udelay(5000);
+	}
+	
+	pci_dev = codec->pci;
+	pci_read_config_dword(pci_dev, 0x44, &dwVal);
+	pci_write_config_dword(pci_dev, 0x44, dwVal | 0x000c0000);
+	udelay(500);
+	pci_read_config_dword(pci_dev, 0x44, &dwVal);
+	pci_write_config_dword(pci_dev, 0x44, dwVal & 0xfffbffff);
+	udelay(5000);
+	
+	wCount = 200;
+	while(wCount--) {
+		wReg = snd_ali_codec_peek(codec, 0, AC97_POWERDOWN);
+		if ((wReg & 0x000f) == 0x000f)
+			return 0;
+		udelay(5000);
+	}
+
+	/* non-fatal if you have a non PM capable codec */
+	/* snd_printk(KERN_WARNING "ali5451: reset time out\n"); */
+	return 0;
+}
+
+/*
+ *  ALI 5451 Controller
+ */
+
+static void snd_ali_enable_special_channel(struct snd_ali *codec,
+					   unsigned int channel)
+{
+	unsigned long dwVal;
+
+	dwVal  = inl(ALI_REG(codec, ALI_GLOBAL_CONTROL));
+	dwVal |= 1 << (channel & 0x0000001f);
+	outl(dwVal, ALI_REG(codec, ALI_GLOBAL_CONTROL));
+}
+
+static void snd_ali_disable_special_channel(struct snd_ali *codec,
+					    unsigned int channel)
+{
+	unsigned long dwVal;
+
+	dwVal  = inl(ALI_REG(codec, ALI_GLOBAL_CONTROL));
+	dwVal &= ~(1 << (channel & 0x0000001f));
+	outl(dwVal, ALI_REG(codec, ALI_GLOBAL_CONTROL));
+}
+
+static void snd_ali_enable_address_interrupt(struct snd_ali *codec)
+{
+	unsigned int gc;
+
+	gc  = inl(ALI_REG(codec, ALI_GC_CIR));
+	gc |= ENDLP_IE;
+	gc |= MIDLP_IE;
+	outl( gc, ALI_REG(codec, ALI_GC_CIR));
+}
+
+static void snd_ali_disable_address_interrupt(struct snd_ali *codec)
+{
+	unsigned int gc;
+
+	gc  = inl(ALI_REG(codec, ALI_GC_CIR));
+	gc &= ~ENDLP_IE;
+	gc &= ~MIDLP_IE;
+	outl(gc, ALI_REG(codec, ALI_GC_CIR));
+}
+
+static void snd_ali_disable_voice_irq(struct snd_ali *codec,
+				      unsigned int channel)
+{
+	unsigned int mask;
+	struct snd_ali_channel_control *pchregs = &(codec->chregs);
+
+	snd_ali_printk("disable_voice_irq channel=%d\n",channel);
+
+	mask = 1 << (channel & 0x1f);
+	pchregs->data.ainten  = inl(ALI_REG(codec, pchregs->regs.ainten));
+	pchregs->data.ainten &= ~mask;
+	outl(pchregs->data.ainten, ALI_REG(codec, pchregs->regs.ainten));
+}
+
+static int snd_ali_alloc_pcm_channel(struct snd_ali *codec, int channel)
+{
+	unsigned int idx =  channel & 0x1f;
+
+	if (codec->synth.chcnt >= ALI_CHANNELS){
+		snd_printk(KERN_ERR
+			   "ali_alloc_pcm_channel: no free channels.\n");
+		return -1;
+	}
+
+	if (!(codec->synth.chmap & (1 << idx))) {
+		codec->synth.chmap |= 1 << idx;
+		codec->synth.chcnt++;
+		snd_ali_printk("alloc_pcm_channel no. %d.\n",idx);
+		return idx;
+	}
+	return -1;
+}
+
+static int snd_ali_find_free_channel(struct snd_ali * codec, int rec)
+{
+	int idx;
+	int result = -1;
+
+	snd_ali_printk("find_free_channel: for %s\n",rec ? "rec" : "pcm");
+
+	/* recording */
+	if (rec) {
+		if (codec->spdif_support &&
+		    (inl(ALI_REG(codec, ALI_GLOBAL_CONTROL)) &
+		     ALI_SPDIF_IN_SUPPORT))
+			idx = ALI_SPDIF_IN_CHANNEL;
+		else
+			idx = ALI_PCM_IN_CHANNEL;
+
+		result = snd_ali_alloc_pcm_channel(codec, idx);
+		if (result >= 0)
+			return result;
+		else {
+			snd_printk(KERN_ERR "ali_find_free_channel: "
+				   "record channel is busy now.\n");
+			return -1;
+		}
+	}
+
+	/* playback... */
+	if (codec->spdif_support &&
+	    (inl(ALI_REG(codec, ALI_GLOBAL_CONTROL)) &
+	     ALI_SPDIF_OUT_CH_ENABLE)) {
+		idx = ALI_SPDIF_OUT_CHANNEL;
+		result = snd_ali_alloc_pcm_channel(codec, idx);
+		if (result >= 0)
+			return result;
+		else
+			snd_printk(KERN_ERR "ali_find_free_channel: "
+				   "S/PDIF out channel is in busy now.\n");
+	}
+
+	for (idx = 0; idx < ALI_CHANNELS; idx++) {
+		result = snd_ali_alloc_pcm_channel(codec, idx);
+		if (result >= 0)
+			return result;
+	}
+	snd_printk(KERN_ERR "ali_find_free_channel: no free channels.\n");
+	return -1;
+}
+
+static void snd_ali_free_channel_pcm(struct snd_ali *codec, int channel)
+{
+	unsigned int idx = channel & 0x0000001f;
+
+	snd_ali_printk("free_channel_pcm channel=%d\n",channel);
+
+	if (channel < 0 || channel >= ALI_CHANNELS)
+		return;
+
+	if (!(codec->synth.chmap & (1 << idx))) {
+		snd_printk(KERN_ERR "ali_free_channel_pcm: "
+			   "channel %d is not in use.\n", channel);
+		return;
+	} else {
+		codec->synth.chmap &= ~(1 << idx);
+		codec->synth.chcnt--;
+	}
+}
+
+static void snd_ali_stop_voice(struct snd_ali *codec, unsigned int channel)
+{
+	unsigned int mask = 1 << (channel & 0x1f);
+
+	snd_ali_printk("stop_voice: channel=%d\n",channel);
+	outl(mask, ALI_REG(codec, codec->chregs.regs.stop));
+}
+
+/*
+ *    S/PDIF Part
+ */
+
+static void snd_ali_delay(struct snd_ali *codec,int interval)
+{
+	unsigned long  begintimer,currenttimer;
+
+	begintimer   = inl(ALI_REG(codec, ALI_STIMER));
+	currenttimer = inl(ALI_REG(codec, ALI_STIMER));
+
+	while (currenttimer < begintimer + interval) {
+		if (snd_ali_stimer_ready(codec) < 0)
+			break;
+		currenttimer = inl(ALI_REG(codec,  ALI_STIMER));
+		cpu_relax();
+	}
+}
+
+static void snd_ali_detect_spdif_rate(struct snd_ali *codec)
+{
+	u16 wval;
+	u16 count = 0;
+	u8  bval, R1 = 0, R2;
+
+	bval  = inb(ALI_REG(codec, ALI_SPDIF_CTRL + 1));
+	bval |= 0x1F;
+	outb(bval, ALI_REG(codec, ALI_SPDIF_CTRL + 1));
+
+	while ((R1 < 0x0b || R1 > 0x0e) && R1 != 0x12 && count <= 50000) {
+		count ++;
+		snd_ali_delay(codec, 6);
+		bval = inb(ALI_REG(codec, ALI_SPDIF_CTRL + 1));
+		R1 = bval & 0x1F;
+	}
+
+	if (count > 50000) {
+		snd_printk(KERN_ERR "ali_detect_spdif_rate: timeout!\n");
+		return;
+	}
+
+	for (count = 0; count <= 50000; count++) {
+		snd_ali_delay(codec, 6);
+		bval = inb(ALI_REG(codec,ALI_SPDIF_CTRL + 1));
+		R2 = bval & 0x1F;
+		if (R2 != R1)
+			R1 = R2;
+		else
+			break;
+	}
+
+	if (count > 50000) {
+		snd_printk(KERN_ERR "ali_detect_spdif_rate: timeout!\n");
+		return;
+	}
+
+	if (R2 >= 0x0b && R2 <= 0x0e) {
+		wval  = inw(ALI_REG(codec, ALI_SPDIF_CTRL + 2));
+		wval &= 0xe0f0;
+		wval |= (0x09 << 8) | 0x05;
+		outw(wval, ALI_REG(codec, ALI_SPDIF_CTRL + 2));
+
+		bval  = inb(ALI_REG(codec, ALI_SPDIF_CS + 3)) & 0xf0;
+		outb(bval | 0x02, ALI_REG(codec, ALI_SPDIF_CS + 3));
+	} else if (R2 == 0x12) {
+		wval  = inw(ALI_REG(codec, ALI_SPDIF_CTRL + 2));
+		wval &= 0xe0f0;
+		wval |= (0x0e << 8) | 0x08;
+		outw(wval, ALI_REG(codec, ALI_SPDIF_CTRL + 2));
+
+		bval  = inb(ALI_REG(codec,ALI_SPDIF_CS + 3)) & 0xf0;
+		outb(bval | 0x03, ALI_REG(codec, ALI_SPDIF_CS + 3));
+	}
+}
+
+static unsigned int snd_ali_get_spdif_in_rate(struct snd_ali *codec)
+{
+	u32	dwRate;
+	u8	bval;
+
+	bval  = inb(ALI_REG(codec, ALI_SPDIF_CTRL));
+	bval &= 0x7f;
+	bval |= 0x40;
+	outb(bval, ALI_REG(codec, ALI_SPDIF_CTRL));
+
+	snd_ali_detect_spdif_rate(codec);
+
+	bval  = inb(ALI_REG(codec, ALI_SPDIF_CS + 3));
+	bval &= 0x0f;
+
+	switch (bval) {
+	case 0: dwRate = 44100; break;
+	case 1: dwRate = 48000; break;
+	case 2: dwRate = 32000; break;
+	default: dwRate = 0; break;
+	}
+
+	return dwRate;
+}
+
+static void snd_ali_enable_spdif_in(struct snd_ali *codec)
+{	
+	unsigned int dwVal;
+
+	dwVal = inl(ALI_REG(codec, ALI_GLOBAL_CONTROL));
+	dwVal |= ALI_SPDIF_IN_SUPPORT;
+	outl(dwVal, ALI_REG(codec, ALI_GLOBAL_CONTROL));
+
+	dwVal = inb(ALI_REG(codec, ALI_SPDIF_CTRL));
+	dwVal |= 0x02;
+	outb(dwVal, ALI_REG(codec, ALI_SPDIF_CTRL));
+
+	snd_ali_enable_special_channel(codec, ALI_SPDIF_IN_CHANNEL);
+}
+
+static void snd_ali_disable_spdif_in(struct snd_ali *codec)
+{
+	unsigned int dwVal;
+	
+	dwVal = inl(ALI_REG(codec, ALI_GLOBAL_CONTROL));
+	dwVal &= ~ALI_SPDIF_IN_SUPPORT;
+	outl(dwVal, ALI_REG(codec, ALI_GLOBAL_CONTROL));
+	
+	snd_ali_disable_special_channel(codec, ALI_SPDIF_IN_CHANNEL);	
+}
+
+
+static void snd_ali_set_spdif_out_rate(struct snd_ali *codec, unsigned int rate)
+{
+	unsigned char  bVal;
+	unsigned int  dwRate;
+	
+	switch (rate) {
+	case 32000: dwRate = 0x300; break;
+	case 48000: dwRate = 0x200; break;
+	default: dwRate = 0; break;
+	}
+	
+	bVal  = inb(ALI_REG(codec, ALI_SPDIF_CTRL));
+	bVal &= (unsigned char)(~(1<<6));
+	
+	bVal |= 0x80;		/* select right */
+	outb(bVal, ALI_REG(codec, ALI_SPDIF_CTRL));
+	outb(dwRate | 0x20, ALI_REG(codec, ALI_SPDIF_CS + 2));
+	
+	bVal &= ~0x80;	/* select left */
+	outb(bVal, ALI_REG(codec, ALI_SPDIF_CTRL));
+	outw(rate | 0x10, ALI_REG(codec, ALI_SPDIF_CS + 2));
+}
+
+static void snd_ali_enable_spdif_out(struct snd_ali *codec)
+{
+	unsigned short wVal;
+	unsigned char bVal;
+        struct pci_dev *pci_dev;
+
+        pci_dev = codec->pci_m1533;
+        if (pci_dev == NULL)
+                return;
+        pci_read_config_byte(pci_dev, 0x61, &bVal);
+        bVal |= 0x40;
+        pci_write_config_byte(pci_dev, 0x61, bVal);
+        pci_read_config_byte(pci_dev, 0x7d, &bVal);
+        bVal |= 0x01;
+        pci_write_config_byte(pci_dev, 0x7d, bVal);
+
+        pci_read_config_byte(pci_dev, 0x7e, &bVal);
+        bVal &= (~0x20);
+        bVal |= 0x10;
+        pci_write_config_byte(pci_dev, 0x7e, bVal);
+
+	bVal = inb(ALI_REG(codec, ALI_SCTRL));
+	outb(bVal | ALI_SPDIF_OUT_ENABLE, ALI_REG(codec, ALI_SCTRL));
+
+	bVal = inb(ALI_REG(codec, ALI_SPDIF_CTRL));
+	outb(bVal & ALI_SPDIF_OUT_CH_STATUS, ALI_REG(codec, ALI_SPDIF_CTRL));
+   
+	wVal = inw(ALI_REG(codec, ALI_GLOBAL_CONTROL));
+	wVal |= ALI_SPDIF_OUT_SEL_PCM;
+	outw(wVal, ALI_REG(codec, ALI_GLOBAL_CONTROL));
+	snd_ali_disable_special_channel(codec, ALI_SPDIF_OUT_CHANNEL);
+}
+
+static void snd_ali_enable_spdif_chnout(struct snd_ali *codec)
+{
+	unsigned short wVal;
+
+	wVal  = inw(ALI_REG(codec, ALI_GLOBAL_CONTROL));
+   	wVal &= ~ALI_SPDIF_OUT_SEL_PCM;
+   	outw(wVal, ALI_REG(codec, ALI_GLOBAL_CONTROL));
+/*
+	wVal = inw(ALI_REG(codec, ALI_SPDIF_CS));
+	if (flag & ALI_SPDIF_OUT_NON_PCM)
+   		wVal |= 0x0002;
+	else	
+		wVal &= (~0x0002);
+   	outw(wVal, ALI_REG(codec, ALI_SPDIF_CS));
+*/
+	snd_ali_enable_special_channel(codec, ALI_SPDIF_OUT_CHANNEL);
+}
+
+static void snd_ali_disable_spdif_chnout(struct snd_ali *codec)
+{
+	unsigned short wVal;
+
+  	wVal  = inw(ALI_REG(codec, ALI_GLOBAL_CONTROL));
+   	wVal |= ALI_SPDIF_OUT_SEL_PCM;
+   	outw(wVal, ALI_REG(codec, ALI_GLOBAL_CONTROL));
+
+	snd_ali_enable_special_channel(codec, ALI_SPDIF_OUT_CHANNEL);
+}
+
+static void snd_ali_disable_spdif_out(struct snd_ali *codec)
+{
+	unsigned char  bVal;
+
+	bVal = inb(ALI_REG(codec, ALI_SCTRL));
+	outb(bVal & ~ALI_SPDIF_OUT_ENABLE, ALI_REG(codec, ALI_SCTRL));
+
+	snd_ali_disable_spdif_chnout(codec);
+}
+
+static void snd_ali_update_ptr(struct snd_ali *codec, int channel)
+{
+	struct snd_ali_voice *pvoice;
+	struct snd_pcm_runtime *runtime;
+	struct snd_ali_channel_control *pchregs;
+	unsigned int old, mask;
+#ifdef ALI_DEBUG
+	unsigned int temp, cspf;
+#endif
+
+	pchregs = &(codec->chregs);
+
+	/* check if interrupt occurred for channel */
+	old  = pchregs->data.aint;
+	mask = 1U << (channel & 0x1f);
+
+	if (!(old & mask))
+		return;
+
+	pvoice = &codec->synth.voices[channel];
+	runtime = pvoice->substream->runtime;
+
+	udelay(100);
+	spin_lock(&codec->reg_lock);
+
+	if (pvoice->pcm && pvoice->substream) {
+		/* pcm interrupt */
+#ifdef ALI_DEBUG
+		outb((u8)(pvoice->number), ALI_REG(codec, ALI_GC_CIR));
+		temp = inw(ALI_REG(codec, ALI_CSO_ALPHA_FMS + 2));
+		cspf = (inl(ALI_REG(codec, ALI_CSPF)) & mask) == mask;
+#endif
+		if (pvoice->running) {
+			snd_ali_printk("update_ptr: cso=%4.4x cspf=%d.\n",
+				       (u16)temp, cspf);
+			spin_unlock(&codec->reg_lock);
+			snd_pcm_period_elapsed(pvoice->substream);
+			spin_lock(&codec->reg_lock);
+		} else {
+			snd_ali_stop_voice(codec, channel);
+			snd_ali_disable_voice_irq(codec, channel);
+		}	
+	} else if (codec->synth.voices[channel].synth) {
+		/* synth interrupt */
+	} else if (codec->synth.voices[channel].midi) {
+		/* midi interrupt */
+	} else {
+		/* unknown interrupt */
+		snd_ali_stop_voice(codec, channel);
+		snd_ali_disable_voice_irq(codec, channel);
+	}
+	spin_unlock(&codec->reg_lock);
+	outl(mask,ALI_REG(codec,pchregs->regs.aint));
+	pchregs->data.aint = old & (~mask);
+}
+
+static irqreturn_t snd_ali_card_interrupt(int irq, void *dev_id)
+{
+	struct snd_ali 	*codec = dev_id;
+	int channel;
+	unsigned int audio_int;
+	struct snd_ali_channel_control *pchregs;
+
+	if (codec == NULL || !codec->hw_initialized)
+		return IRQ_NONE;
+
+	audio_int = inl(ALI_REG(codec, ALI_MISCINT));
+	if (!audio_int)
+		return IRQ_NONE;
+
+	pchregs = &(codec->chregs);
+	if (audio_int & ADDRESS_IRQ) {
+		/* get interrupt status for all channels */
+		pchregs->data.aint = inl(ALI_REG(codec, pchregs->regs.aint));
+		for (channel = 0; channel < ALI_CHANNELS; channel++)
+			snd_ali_update_ptr(codec, channel);
+	}
+	outl((TARGET_REACHED | MIXER_OVERFLOW | MIXER_UNDERFLOW),
+		ALI_REG(codec, ALI_MISCINT));
+
+	return IRQ_HANDLED;
+}
+
+
+static struct snd_ali_voice *snd_ali_alloc_voice(struct snd_ali * codec,
+						 int type, int rec, int channel)
+{
+	struct snd_ali_voice *pvoice;
+	int idx;
+
+	snd_ali_printk("alloc_voice: type=%d rec=%d\n", type, rec);
+
+	spin_lock_irq(&codec->voice_alloc);
+	if (type == SNDRV_ALI_VOICE_TYPE_PCM) {
+		idx = channel > 0 ? snd_ali_alloc_pcm_channel(codec, channel) :
+			snd_ali_find_free_channel(codec,rec);
+		if (idx < 0) {
+			snd_printk(KERN_ERR "ali_alloc_voice: err.\n");
+			spin_unlock_irq(&codec->voice_alloc);
+			return NULL;
+		}
+		pvoice = &(codec->synth.voices[idx]);
+		pvoice->codec = codec;
+		pvoice->use = 1;
+		pvoice->pcm = 1;
+		pvoice->mode = rec;
+		spin_unlock_irq(&codec->voice_alloc);
+		return pvoice;
+	}
+	spin_unlock_irq(&codec->voice_alloc);
+	return NULL;
+}
+
+
+static void snd_ali_free_voice(struct snd_ali * codec,
+			       struct snd_ali_voice *pvoice)
+{
+	void (*private_free)(void *);
+	void *private_data;
+
+	snd_ali_printk("free_voice: channel=%d\n",pvoice->number);
+	if (!pvoice->use)
+		return;
+	snd_ali_clear_voices(codec, pvoice->number, pvoice->number);
+	spin_lock_irq(&codec->voice_alloc);
+	private_free = pvoice->private_free;
+	private_data = pvoice->private_data;
+	pvoice->private_free = NULL;
+	pvoice->private_data = NULL;
+	if (pvoice->pcm)
+		snd_ali_free_channel_pcm(codec, pvoice->number);
+	pvoice->use = pvoice->pcm = pvoice->synth = 0;
+	pvoice->substream = NULL;
+	spin_unlock_irq(&codec->voice_alloc);
+	if (private_free)
+		private_free(private_data);
+}
+
+
+static void snd_ali_clear_voices(struct snd_ali *codec,
+				 unsigned int v_min,
+				 unsigned int v_max)
+{
+	unsigned int i;
+
+	for (i = v_min; i <= v_max; i++) {
+		snd_ali_stop_voice(codec, i);
+		snd_ali_disable_voice_irq(codec, i);
+	}
+}
+
+static void snd_ali_write_voice_regs(struct snd_ali *codec,
+			 unsigned int Channel,
+			 unsigned int LBA,
+			 unsigned int CSO,
+			 unsigned int ESO,
+			 unsigned int DELTA,
+			 unsigned int ALPHA_FMS,
+			 unsigned int GVSEL,
+			 unsigned int PAN,
+			 unsigned int VOL,
+			 unsigned int CTRL,
+			 unsigned int EC)
+{
+	unsigned int ctlcmds[4];
+	
+	outb((unsigned char)(Channel & 0x001f), ALI_REG(codec, ALI_GC_CIR));
+
+	ctlcmds[0] =  (CSO << 16) | (ALPHA_FMS & 0x0000ffff);
+	ctlcmds[1] =  LBA;
+	ctlcmds[2] =  (ESO << 16) | (DELTA & 0x0ffff);
+	ctlcmds[3] =  (GVSEL << 31) |
+		      ((PAN & 0x0000007f) << 24) |
+		      ((VOL & 0x000000ff) << 16) |
+		      ((CTRL & 0x0000000f) << 12) |
+		      (EC & 0x00000fff);
+
+	outb(Channel, ALI_REG(codec, ALI_GC_CIR));
+
+	outl(ctlcmds[0], ALI_REG(codec, ALI_CSO_ALPHA_FMS));
+	outl(ctlcmds[1], ALI_REG(codec, ALI_LBA));
+	outl(ctlcmds[2], ALI_REG(codec, ALI_ESO_DELTA));
+	outl(ctlcmds[3], ALI_REG(codec, ALI_GVSEL_PAN_VOC_CTRL_EC));
+
+	outl(0x30000000, ALI_REG(codec, ALI_EBUF1));	/* Still Mode */
+	outl(0x30000000, ALI_REG(codec, ALI_EBUF2));	/* Still Mode */
+}
+
+static unsigned int snd_ali_convert_rate(unsigned int rate, int rec)
+{
+	unsigned int delta;
+
+	if (rate < 4000)
+		rate = 4000;
+	if (rate > 48000)
+		rate = 48000;
+
+	if (rec) {
+		if (rate == 44100)
+			delta = 0x116a;
+		else if (rate == 8000)
+			delta = 0x6000;
+		else if (rate == 48000)
+			delta = 0x1000;
+		else
+			delta = ((48000 << 12) / rate) & 0x0000ffff;
+	} else {
+		if (rate == 44100)
+			delta = 0xeb3;
+		else if (rate == 8000)
+			delta = 0x2ab;
+		else if (rate == 48000)
+			delta = 0x1000;
+		else 
+			delta = (((rate << 12) + rate) / 48000) & 0x0000ffff;
+	}
+
+	return delta;
+}
+
+static unsigned int snd_ali_control_mode(struct snd_pcm_substream *substream)
+{
+	unsigned int CTRL;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	/* set ctrl mode
+	   CTRL default: 8-bit (unsigned) mono, loop mode enabled
+	 */
+	CTRL = 0x00000001;
+	if (snd_pcm_format_width(runtime->format) == 16)
+		CTRL |= 0x00000008;	/* 16-bit data */
+	if (!snd_pcm_format_unsigned(runtime->format))
+		CTRL |= 0x00000002;	/* signed data */
+	if (runtime->channels > 1)
+		CTRL |= 0x00000004;	/* stereo data */
+	return CTRL;
+}
+
+/*
+ *  PCM part
+ */
+
+static int snd_ali_trigger(struct snd_pcm_substream *substream,
+			       int cmd)
+				    
+{
+	struct snd_ali *codec = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *s;
+	unsigned int what, whati, capture_flag;
+	struct snd_ali_voice *pvoice, *evoice;
+	unsigned int val;
+	int do_start;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		do_start = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		do_start = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	what = whati = capture_flag = 0;
+	snd_pcm_group_for_each_entry(s, substream) {
+		if ((struct snd_ali *) snd_pcm_substream_chip(s) == codec) {
+			pvoice = s->runtime->private_data;
+			evoice = pvoice->extra;
+			what |= 1 << (pvoice->number & 0x1f);
+			if (evoice == NULL)
+				whati |= 1 << (pvoice->number & 0x1f);
+			else {
+				whati |= 1 << (evoice->number & 0x1f);
+				what |= 1 << (evoice->number & 0x1f);
+			}
+			if (do_start) {
+				pvoice->running = 1;
+				if (evoice != NULL)
+					evoice->running = 1;
+			} else {
+				pvoice->running = 0;
+				if (evoice != NULL)
+					evoice->running = 0;
+			}
+			snd_pcm_trigger_done(s, substream);
+			if (pvoice->mode)
+				capture_flag = 1;
+		}
+	}
+	spin_lock(&codec->reg_lock);
+	if (!do_start)
+		outl(what, ALI_REG(codec, ALI_STOP));
+	val = inl(ALI_REG(codec, ALI_AINTEN));
+	if (do_start)
+		val |= whati;
+	else
+		val &= ~whati;
+	outl(val, ALI_REG(codec, ALI_AINTEN));
+	if (do_start)
+		outl(what, ALI_REG(codec, ALI_START));
+	snd_ali_printk("trigger: what=%xh whati=%xh\n", what, whati);
+	spin_unlock(&codec->reg_lock);
+
+	return 0;
+}
+
+static int snd_ali_playback_hw_params(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_ali *codec = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ali_voice *pvoice = runtime->private_data;
+	struct snd_ali_voice *evoice = pvoice->extra;
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	
+	/* voice management */
+
+	if (params_buffer_size(hw_params) / 2 !=
+	    params_period_size(hw_params)) {
+		if (!evoice) {
+			evoice = snd_ali_alloc_voice(codec,
+						     SNDRV_ALI_VOICE_TYPE_PCM,
+						     0, -1);
+			if (!evoice)
+				return -ENOMEM;
+			pvoice->extra = evoice;
+			evoice->substream = substream;
+		}
+	} else {
+		if (evoice) {
+			snd_ali_free_voice(codec, evoice);
+			pvoice->extra = evoice = NULL;
+		}
+	}
+
+	return 0;
+}
+
+static int snd_ali_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_ali *codec = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ali_voice *pvoice = runtime->private_data;
+	struct snd_ali_voice *evoice = pvoice ? pvoice->extra : NULL;
+
+	snd_pcm_lib_free_pages(substream);
+	if (evoice) {
+		snd_ali_free_voice(codec, evoice);
+		pvoice->extra = NULL;
+	}
+	return 0;
+}
+
+static int snd_ali_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int snd_ali_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_ali_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ali *codec = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ali_voice *pvoice = runtime->private_data;
+	struct snd_ali_voice *evoice = pvoice->extra;
+
+	unsigned int LBA;
+	unsigned int Delta;
+	unsigned int ESO;
+	unsigned int CTRL;
+	unsigned int GVSEL;
+	unsigned int PAN;
+	unsigned int VOL;
+	unsigned int EC;
+	
+	snd_ali_printk("playback_prepare ...\n");
+
+	spin_lock_irq(&codec->reg_lock);	
+	
+	/* set Delta (rate) value */
+	Delta = snd_ali_convert_rate(runtime->rate, 0);
+
+	if (pvoice->number == ALI_SPDIF_IN_CHANNEL || 
+	    pvoice->number == ALI_PCM_IN_CHANNEL)
+		snd_ali_disable_special_channel(codec, pvoice->number);
+	else if (codec->spdif_support &&
+		 (inl(ALI_REG(codec, ALI_GLOBAL_CONTROL)) &
+		  ALI_SPDIF_OUT_CH_ENABLE)
+		 && pvoice->number == ALI_SPDIF_OUT_CHANNEL) {
+		snd_ali_set_spdif_out_rate(codec, runtime->rate);
+		Delta = 0x1000;
+	}
+	
+	/* set Loop Back Address */
+	LBA = runtime->dma_addr;
+
+	/* set interrupt count size */
+	pvoice->count = runtime->period_size;
+
+	/* set target ESO for channel */
+	pvoice->eso = runtime->buffer_size; 
+
+	snd_ali_printk("playback_prepare: eso=%xh count=%xh\n",
+		       pvoice->eso, pvoice->count);
+
+	/* set ESO to capture first MIDLP interrupt */
+	ESO = pvoice->eso -1;
+	/* set ctrl mode */
+	CTRL = snd_ali_control_mode(substream);
+
+	GVSEL = 1;
+	PAN = 0;
+	VOL = 0;
+	EC = 0;
+	snd_ali_printk("playback_prepare:\n");
+	snd_ali_printk("ch=%d, Rate=%d Delta=%xh,GVSEL=%xh,PAN=%xh,CTRL=%xh\n",
+		       pvoice->number,runtime->rate,Delta,GVSEL,PAN,CTRL);
+	snd_ali_write_voice_regs(codec,
+				 pvoice->number,
+				 LBA,
+				 0,	/* cso */
+				 ESO,
+				 Delta,
+				 0,	/* alpha */
+				 GVSEL,
+				 PAN,
+				 VOL,
+				 CTRL,
+				 EC);
+	if (evoice) {
+		evoice->count = pvoice->count;
+		evoice->eso = pvoice->count << 1;
+		ESO = evoice->eso - 1;
+		snd_ali_write_voice_regs(codec,
+					 evoice->number,
+					 LBA,
+					 0,	/* cso */
+					 ESO,
+					 Delta,
+					 0,	/* alpha */
+					 GVSEL,
+					 0x7f,
+					 0x3ff,
+					 CTRL,
+					 EC);
+	}
+	spin_unlock_irq(&codec->reg_lock);
+	return 0;
+}
+
+
+static int snd_ali_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ali *codec = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ali_voice *pvoice = runtime->private_data;
+	unsigned int LBA;
+	unsigned int Delta;
+	unsigned int ESO;
+	unsigned int CTRL;
+	unsigned int GVSEL;
+	unsigned int PAN;
+	unsigned int VOL;
+	unsigned int EC;
+	u8	 bValue;
+
+	spin_lock_irq(&codec->reg_lock);
+
+	snd_ali_printk("ali_prepare...\n");
+
+	snd_ali_enable_special_channel(codec,pvoice->number);
+
+	Delta = (pvoice->number == ALI_MODEM_IN_CHANNEL ||
+		 pvoice->number == ALI_MODEM_OUT_CHANNEL) ? 
+		0x1000 : snd_ali_convert_rate(runtime->rate, pvoice->mode);
+
+	/* Prepare capture intr channel */
+	if (pvoice->number == ALI_SPDIF_IN_CHANNEL) {
+
+		unsigned int rate;
+		
+		spin_unlock_irq(&codec->reg_lock);
+		if (codec->revision != ALI_5451_V02)
+			return -1;
+
+		rate = snd_ali_get_spdif_in_rate(codec);
+		if (rate == 0) {
+			snd_printk(KERN_WARNING "ali_capture_preapre: "
+				   "spdif rate detect err!\n");
+			rate = 48000;
+		}
+		spin_lock_irq(&codec->reg_lock);
+		bValue = inb(ALI_REG(codec,ALI_SPDIF_CTRL));
+		if (bValue & 0x10) {
+			outb(bValue,ALI_REG(codec,ALI_SPDIF_CTRL));
+			printk(KERN_WARNING "clear SPDIF parity error flag.\n");
+		}
+
+		if (rate != 48000)
+			Delta = ((rate << 12) / runtime->rate) & 0x00ffff;
+	}
+
+	/* set target ESO for channel  */
+	pvoice->eso = runtime->buffer_size; 
+
+	/* set interrupt count size  */
+	pvoice->count = runtime->period_size;
+
+	/* set Loop Back Address  */
+	LBA = runtime->dma_addr;
+
+	/* set ESO to capture first MIDLP interrupt  */
+	ESO = pvoice->eso - 1;
+	CTRL = snd_ali_control_mode(substream);
+	GVSEL = 0;
+	PAN = 0x00;
+	VOL = 0x00;
+	EC = 0;
+
+	snd_ali_write_voice_regs(    codec,
+				     pvoice->number,
+				     LBA,
+				     0,	/* cso */
+				     ESO,
+				     Delta,
+				     0,	/* alpha */
+				     GVSEL,
+				     PAN,
+				     VOL,
+				     CTRL,
+				     EC);
+
+	spin_unlock_irq(&codec->reg_lock);
+
+	return 0;
+}
+
+
+static snd_pcm_uframes_t
+snd_ali_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ali *codec = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ali_voice *pvoice = runtime->private_data;
+	unsigned int cso;
+
+	spin_lock(&codec->reg_lock);
+	if (!pvoice->running) {
+		spin_unlock(&codec->reg_lock);
+		return 0;
+	}
+	outb(pvoice->number, ALI_REG(codec, ALI_GC_CIR));
+	cso = inw(ALI_REG(codec, ALI_CSO_ALPHA_FMS + 2));
+	spin_unlock(&codec->reg_lock);
+	snd_ali_printk("playback pointer returned cso=%xh.\n", cso);
+
+	return cso;
+}
+
+
+static snd_pcm_uframes_t snd_ali_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ali *codec = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ali_voice *pvoice = runtime->private_data;
+	unsigned int cso;
+
+	spin_lock(&codec->reg_lock);
+	if (!pvoice->running) {
+		spin_unlock_irq(&codec->reg_lock);
+		return 0;
+	}
+	outb(pvoice->number, ALI_REG(codec, ALI_GC_CIR));
+	cso = inw(ALI_REG(codec, ALI_CSO_ALPHA_FMS + 2));
+	spin_unlock(&codec->reg_lock);
+
+	return cso;
+}
+
+static struct snd_pcm_hardware snd_ali_playback =
+{
+	.info =		(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+			 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+			 SNDRV_PCM_INFO_MMAP_VALID |
+			 SNDRV_PCM_INFO_RESUME |
+			 SNDRV_PCM_INFO_SYNC_START),
+	.formats =	(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+			 SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =	SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(256*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(256*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *  Capture support device description
+ */
+
+static struct snd_pcm_hardware snd_ali_capture =
+{
+	.info =		(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+			 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+			 SNDRV_PCM_INFO_MMAP_VALID |
+			 SNDRV_PCM_INFO_RESUME |
+			 SNDRV_PCM_INFO_SYNC_START),
+	.formats =	(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+			 SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =	SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static void snd_ali_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+	struct snd_ali_voice *pvoice = runtime->private_data;
+	struct snd_ali *codec;
+
+	if (pvoice) {
+		codec = pvoice->codec;
+		snd_ali_free_voice(pvoice->codec, pvoice);
+	}
+}
+
+static int snd_ali_open(struct snd_pcm_substream *substream, int rec,
+			int channel, struct snd_pcm_hardware *phw)
+{
+	struct snd_ali *codec = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ali_voice *pvoice;
+
+	pvoice = snd_ali_alloc_voice(codec, SNDRV_ALI_VOICE_TYPE_PCM, rec,
+				     channel);
+	if (!pvoice)
+		return -EAGAIN;
+
+	pvoice->substream = substream;
+	runtime->private_data = pvoice;
+	runtime->private_free = snd_ali_pcm_free_substream;
+
+	runtime->hw = *phw;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+				     0, 64*1024);
+	return 0;
+}
+
+static int snd_ali_playback_open(struct snd_pcm_substream *substream)
+{
+	return snd_ali_open(substream, 0, -1, &snd_ali_playback);
+}
+
+static int snd_ali_capture_open(struct snd_pcm_substream *substream)
+{
+	return snd_ali_open(substream, 1, -1, &snd_ali_capture);
+}
+
+static int snd_ali_playback_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int snd_ali_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ali *codec = snd_pcm_substream_chip(substream);
+	struct snd_ali_voice *pvoice = substream->runtime->private_data;
+
+	snd_ali_disable_special_channel(codec,pvoice->number);
+
+	return 0;
+}
+
+static struct snd_pcm_ops snd_ali_playback_ops = {
+	.open =		snd_ali_playback_open,
+	.close =	snd_ali_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ali_playback_hw_params,
+	.hw_free =	snd_ali_playback_hw_free,
+	.prepare =	snd_ali_playback_prepare,
+	.trigger =	snd_ali_trigger,
+	.pointer =	snd_ali_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_ali_capture_ops = {
+	.open =		snd_ali_capture_open,
+	.close =	snd_ali_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ali_hw_params,
+	.hw_free =	snd_ali_hw_free,
+	.prepare =	snd_ali_prepare,
+	.trigger =	snd_ali_trigger,
+	.pointer =	snd_ali_pointer,
+};
+
+/*
+ * Modem PCM
+ */
+
+static int snd_ali_modem_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_ali *chip = snd_pcm_substream_chip(substream);
+	unsigned int modem_num = chip->num_of_codecs - 1;
+	snd_ac97_write(chip->ac97[modem_num], AC97_LINE1_RATE,
+		       params_rate(hw_params));
+	snd_ac97_write(chip->ac97[modem_num], AC97_LINE1_LEVEL, 0);
+	return snd_ali_hw_params(substream, hw_params);
+}
+
+static struct snd_pcm_hardware snd_ali_modem =
+{
+	.info =		(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+			 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+			 SNDRV_PCM_INFO_MMAP_VALID |
+			 SNDRV_PCM_INFO_RESUME |
+			 SNDRV_PCM_INFO_SYNC_START),
+	.formats =	SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =	(SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000 |
+			 SNDRV_PCM_RATE_16000),
+	.rate_min =		8000,
+	.rate_max =		16000,
+	.channels_min =		1,
+	.channels_max =		1,
+	.buffer_bytes_max =	(256*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(256*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static int snd_ali_modem_open(struct snd_pcm_substream *substream, int rec,
+			      int channel)
+{
+	static unsigned int rates[] = {8000, 9600, 12000, 16000};
+	static struct snd_pcm_hw_constraint_list hw_constraint_rates = {
+		.count = ARRAY_SIZE(rates),
+		.list = rates,
+		.mask = 0,
+	};
+	int err = snd_ali_open(substream, rec, channel, &snd_ali_modem);
+
+	if (err)
+		return err;
+	return snd_pcm_hw_constraint_list(substream->runtime, 0,
+			SNDRV_PCM_HW_PARAM_RATE, &hw_constraint_rates);
+}
+
+static int snd_ali_modem_playback_open(struct snd_pcm_substream *substream)
+{
+	return snd_ali_modem_open(substream, 0, ALI_MODEM_OUT_CHANNEL);
+}
+
+static int snd_ali_modem_capture_open(struct snd_pcm_substream *substream)
+{
+	return snd_ali_modem_open(substream, 1, ALI_MODEM_IN_CHANNEL);
+}
+
+static struct snd_pcm_ops snd_ali_modem_playback_ops = {
+	.open =		snd_ali_modem_playback_open,
+	.close =	snd_ali_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ali_modem_hw_params,
+	.hw_free =	snd_ali_hw_free,
+	.prepare =	snd_ali_prepare,
+	.trigger =	snd_ali_trigger,
+	.pointer =	snd_ali_pointer,
+};
+
+static struct snd_pcm_ops snd_ali_modem_capture_ops = {
+	.open =		snd_ali_modem_capture_open,
+	.close =	snd_ali_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ali_modem_hw_params,
+	.hw_free =	snd_ali_hw_free,
+	.prepare =	snd_ali_prepare,
+	.trigger =	snd_ali_trigger,
+	.pointer =	snd_ali_pointer,
+};
+
+
+struct ali_pcm_description {
+	char *name;
+	unsigned int playback_num;
+	unsigned int capture_num;
+	struct snd_pcm_ops *playback_ops;
+	struct snd_pcm_ops *capture_ops;
+	unsigned short class;
+};
+
+
+static void snd_ali_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_ali *codec = pcm->private_data;
+	codec->pcm[pcm->device] = NULL;
+}
+
+
+static int __devinit snd_ali_pcm(struct snd_ali * codec, int device,
+				 struct ali_pcm_description *desc)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(codec->card, desc->name, device,
+			  desc->playback_num, desc->capture_num, &pcm);
+	if (err < 0) {
+		snd_printk(KERN_ERR "snd_ali_pcm: err called snd_pcm_new.\n");
+		return err;
+	}
+	pcm->private_data = codec;
+	pcm->private_free = snd_ali_pcm_free;
+	if (desc->playback_ops)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				desc->playback_ops);
+	if (desc->capture_ops)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+				desc->capture_ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(codec->pci),
+					      64*1024, 128*1024);
+
+	pcm->info_flags = 0;
+	pcm->dev_class = desc->class;
+	pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+	strcpy(pcm->name, desc->name);
+	codec->pcm[0] = pcm;
+	return 0;
+}
+
+static struct ali_pcm_description ali_pcms[] = {
+	{ .name = "ALI 5451",
+	  .playback_num = ALI_CHANNELS,
+	  .capture_num = 1,
+	  .playback_ops = &snd_ali_playback_ops,
+	  .capture_ops = &snd_ali_capture_ops
+	},
+	{ .name = "ALI 5451 modem",
+	  .playback_num = 1,
+	  .capture_num = 1,
+	  .playback_ops = &snd_ali_modem_playback_ops,
+	  .capture_ops = &snd_ali_modem_capture_ops,
+	  .class = SNDRV_PCM_CLASS_MODEM
+	}
+};
+
+static int __devinit snd_ali_build_pcms(struct snd_ali *codec)
+{
+	int i, err;
+	for (i = 0; i < codec->num_of_codecs && i < ARRAY_SIZE(ali_pcms); i++) {
+		err = snd_ali_pcm(codec, i, &ali_pcms[i]);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+
+#define ALI5451_SPDIF(xname, xindex, value) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\
+.info = snd_ali5451_spdif_info, .get = snd_ali5451_spdif_get, \
+.put = snd_ali5451_spdif_put, .private_value = value}
+
+#define snd_ali5451_spdif_info		snd_ctl_boolean_mono_info
+
+static int snd_ali5451_spdif_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ali *codec = kcontrol->private_data;
+	unsigned int spdif_enable;
+
+	spdif_enable = ucontrol->value.integer.value[0] ? 1 : 0;
+
+	spin_lock_irq(&codec->reg_lock);
+	switch (kcontrol->private_value) {
+	case 0:
+		spdif_enable = (codec->spdif_mask & 0x02) ? 1 : 0;
+		break;
+	case 1:
+		spdif_enable = ((codec->spdif_mask & 0x02) &&
+			  (codec->spdif_mask & 0x04)) ? 1 : 0;
+		break;
+	case 2:
+		spdif_enable = (codec->spdif_mask & 0x01) ? 1 : 0;
+		break;
+	default:
+		break;
+	}
+	ucontrol->value.integer.value[0] = spdif_enable;
+	spin_unlock_irq(&codec->reg_lock);
+	return 0;
+}
+
+static int snd_ali5451_spdif_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ali *codec = kcontrol->private_data;
+	unsigned int change = 0, spdif_enable = 0;
+
+	spdif_enable = ucontrol->value.integer.value[0] ? 1 : 0;
+
+	spin_lock_irq(&codec->reg_lock);
+	switch (kcontrol->private_value) {
+	case 0:
+		change = (codec->spdif_mask & 0x02) ? 1 : 0;
+		change = change ^ spdif_enable;
+		if (change) {
+			if (spdif_enable) {
+				codec->spdif_mask |= 0x02;
+				snd_ali_enable_spdif_out(codec);
+			} else {
+				codec->spdif_mask &= ~(0x02);
+				codec->spdif_mask &= ~(0x04);
+				snd_ali_disable_spdif_out(codec);
+			}
+		}
+		break;
+	case 1: 
+		change = (codec->spdif_mask & 0x04) ? 1 : 0;
+		change = change ^ spdif_enable;
+		if (change && (codec->spdif_mask & 0x02)) {
+			if (spdif_enable) {
+				codec->spdif_mask |= 0x04;
+				snd_ali_enable_spdif_chnout(codec);
+			} else {
+				codec->spdif_mask &= ~(0x04);
+				snd_ali_disable_spdif_chnout(codec);
+			}
+		}
+		break;
+	case 2:
+		change = (codec->spdif_mask & 0x01) ? 1 : 0;
+		change = change ^ spdif_enable;
+		if (change) {
+			if (spdif_enable) {
+				codec->spdif_mask |= 0x01;
+				snd_ali_enable_spdif_in(codec);
+			} else {
+				codec->spdif_mask &= ~(0x01);
+				snd_ali_disable_spdif_in(codec);
+			}
+		}
+		break;
+	default:
+		break;
+	}
+	spin_unlock_irq(&codec->reg_lock);
+	
+	return change;
+}
+
+static struct snd_kcontrol_new snd_ali5451_mixer_spdif[] __devinitdata = {
+	/* spdif aplayback switch */
+	/* FIXME: "IEC958 Playback Switch" may conflict with one on ac97_codec */
+	ALI5451_SPDIF(SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH), 0, 0),
+	/* spdif out to spdif channel */
+	ALI5451_SPDIF(SNDRV_CTL_NAME_IEC958("Channel Output ",NONE,SWITCH), 0, 1),
+	/* spdif in from spdif channel */
+	ALI5451_SPDIF(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), 0, 2)
+};
+
+static int __devinit snd_ali_mixer(struct snd_ali * codec)
+{
+	struct snd_ac97_template ac97;
+	unsigned int idx;
+	int i, err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_ali_codec_write,
+		.read = snd_ali_codec_read,
+	};
+
+	err = snd_ac97_bus(codec->card, 0, &ops, codec, &codec->ac97_bus);
+	if (err < 0)
+		return err;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = codec;
+
+	for (i = 0; i < codec->num_of_codecs; i++) {
+		ac97.num = i;
+		err = snd_ac97_mixer(codec->ac97_bus, &ac97, &codec->ac97[i]);
+		if (err < 0) {
+			snd_printk(KERN_ERR
+				   "ali mixer %d creating error.\n", i);
+			if (i == 0)
+				return err;
+			codec->num_of_codecs = 1;
+			break;
+		}
+	}
+
+	if (codec->spdif_support) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_ali5451_mixer_spdif); idx++) {
+			err = snd_ctl_add(codec->card,
+					  snd_ctl_new1(&snd_ali5451_mixer_spdif[idx], codec));
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int ali_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_ali *chip = card->private_data;
+	struct snd_ali_image *im;
+	int i, j;
+
+	im = chip->image;
+	if (!im)
+		return 0;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	for (i = 0; i < chip->num_of_codecs; i++) {
+		snd_pcm_suspend_all(chip->pcm[i]);
+		snd_ac97_suspend(chip->ac97[i]);
+	}
+
+	spin_lock_irq(&chip->reg_lock);
+	
+	im->regs[ALI_MISCINT >> 2] = inl(ALI_REG(chip, ALI_MISCINT));
+	/* im->regs[ALI_START >> 2] = inl(ALI_REG(chip, ALI_START)); */
+	im->regs[ALI_STOP >> 2] = inl(ALI_REG(chip, ALI_STOP));
+	
+	/* disable all IRQ bits */
+	outl(0, ALI_REG(chip, ALI_MISCINT));
+	
+	for (i = 0; i < ALI_GLOBAL_REGS; i++) {	
+		if ((i*4 == ALI_MISCINT) || (i*4 == ALI_STOP))
+			continue;
+		im->regs[i] = inl(ALI_REG(chip, i*4));
+	}
+	
+	for (i = 0; i < ALI_CHANNELS; i++) {
+		outb(i, ALI_REG(chip, ALI_GC_CIR));
+		for (j = 0; j < ALI_CHANNEL_REGS; j++) 
+			im->channel_regs[i][j] = inl(ALI_REG(chip, j*4 + 0xe0));
+	}
+
+	/* stop all HW channel */
+	outl(0xffffffff, ALI_REG(chip, ALI_STOP));
+
+	spin_unlock_irq(&chip->reg_lock);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int ali_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_ali *chip = card->private_data;
+	struct snd_ali_image *im;
+	int i, j;
+
+	im = chip->image;
+	if (!im)
+		return 0;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "ali5451: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	spin_lock_irq(&chip->reg_lock);
+	
+	for (i = 0; i < ALI_CHANNELS; i++) {
+		outb(i, ALI_REG(chip, ALI_GC_CIR));
+		for (j = 0; j < ALI_CHANNEL_REGS; j++) 
+			outl(im->channel_regs[i][j], ALI_REG(chip, j*4 + 0xe0));
+	}
+	
+	for (i = 0; i < ALI_GLOBAL_REGS; i++) {	
+		if ((i*4 == ALI_MISCINT) || (i*4 == ALI_STOP) ||
+		    (i*4 == ALI_START))
+			continue;
+		outl(im->regs[i], ALI_REG(chip, i*4));
+	}
+	
+	/* start HW channel */
+	outl(im->regs[ALI_START >> 2], ALI_REG(chip, ALI_START));
+	/* restore IRQ enable bits */
+	outl(im->regs[ALI_MISCINT >> 2], ALI_REG(chip, ALI_MISCINT));
+	
+	spin_unlock_irq(&chip->reg_lock);
+
+	for (i = 0 ; i < chip->num_of_codecs; i++)
+		snd_ac97_resume(chip->ac97[i]);
+	
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static int snd_ali_free(struct snd_ali * codec)
+{
+	if (codec->hw_initialized)
+		snd_ali_disable_address_interrupt(codec);
+	if (codec->irq >= 0)
+		free_irq(codec->irq, codec);
+	if (codec->port)
+		pci_release_regions(codec->pci);
+	pci_disable_device(codec->pci);
+#ifdef CONFIG_PM
+	kfree(codec->image);
+#endif
+	pci_dev_put(codec->pci_m1533);
+	pci_dev_put(codec->pci_m7101);
+	kfree(codec);
+	return 0;
+}
+
+static int snd_ali_chip_init(struct snd_ali *codec)
+{
+	unsigned int legacy;
+	unsigned char temp;
+	struct pci_dev *pci_dev;
+
+	snd_ali_printk("chip initializing ... \n");
+
+	if (snd_ali_reset_5451(codec)) {
+		snd_printk(KERN_ERR "ali_chip_init: reset 5451 error.\n");
+		return -1;
+	}
+
+	if (codec->revision == ALI_5451_V02) {
+        	pci_dev = codec->pci_m1533;
+		pci_read_config_byte(pci_dev, 0x59, &temp);
+		temp |= 0x80;
+		pci_write_config_byte(pci_dev, 0x59, temp);
+	
+		pci_dev = codec->pci_m7101;
+		pci_read_config_byte(pci_dev, 0xb8, &temp);
+		temp |= 0x20;
+		pci_write_config_byte(pci_dev, 0xB8, temp);
+	}
+
+	pci_read_config_dword(codec->pci, 0x44, &legacy);
+	legacy &= 0xff00ff00;
+	legacy |= 0x000800aa;
+	pci_write_config_dword(codec->pci, 0x44, legacy);
+
+	outl(0x80000001, ALI_REG(codec, ALI_GLOBAL_CONTROL));
+	outl(0x00000000, ALI_REG(codec, ALI_AINTEN));
+	outl(0xffffffff, ALI_REG(codec, ALI_AINT));
+	outl(0x00000000, ALI_REG(codec, ALI_VOLUME));
+	outb(0x10, 	 ALI_REG(codec, ALI_MPUR2));
+
+	codec->ac97_ext_id = snd_ali_codec_peek(codec, 0, AC97_EXTENDED_ID);
+	codec->ac97_ext_status = snd_ali_codec_peek(codec, 0,
+						    AC97_EXTENDED_STATUS);
+	if (codec->spdif_support) {
+		snd_ali_enable_spdif_out(codec);
+		codec->spdif_mask = 0x00000002;
+	}
+
+	codec->num_of_codecs = 1;
+
+	/* secondary codec - modem */
+	if (inl(ALI_REG(codec, ALI_SCTRL)) & ALI_SCTRL_CODEC2_READY) {
+		codec->num_of_codecs++;
+		outl(inl(ALI_REG(codec, ALI_SCTRL)) |
+		     (ALI_SCTRL_LINE_IN2 | ALI_SCTRL_GPIO_IN2 |
+		      ALI_SCTRL_LINE_OUT_EN),
+		     ALI_REG(codec, ALI_SCTRL));
+	}
+
+	snd_ali_printk("chip initialize succeed.\n");
+	return 0;
+
+}
+
+/* proc for register dump */
+static void snd_ali_proc_read(struct snd_info_entry *entry,
+			      struct snd_info_buffer *buf)
+{
+	struct snd_ali *codec = entry->private_data;
+	int i;
+	for (i = 0; i < 256 ; i+= 4)
+		snd_iprintf(buf, "%02x: %08x\n", i, inl(ALI_REG(codec, i)));
+}
+
+static void __devinit snd_ali_proc_init(struct snd_ali *codec)
+{
+	struct snd_info_entry *entry;
+	if (!snd_card_proc_new(codec->card, "ali5451", &entry))
+		snd_info_set_text_ops(entry, codec, snd_ali_proc_read);
+}
+
+static int __devinit snd_ali_resources(struct snd_ali *codec)
+{
+	int err;
+
+	snd_ali_printk("resources allocation ...\n");
+	err = pci_request_regions(codec->pci, "ALI 5451");
+	if (err < 0)
+		return err;
+	codec->port = pci_resource_start(codec->pci, 0);
+
+	if (request_irq(codec->pci->irq, snd_ali_card_interrupt,
+			IRQF_SHARED, "ALI 5451", codec)) {
+		snd_printk(KERN_ERR "Unable to request irq.\n");
+		return -EBUSY;
+	}
+	codec->irq = codec->pci->irq;
+	snd_ali_printk("resources allocated.\n");
+	return 0;
+}
+static int snd_ali_dev_free(struct snd_device *device)
+{
+	struct snd_ali *codec = device->device_data;
+	snd_ali_free(codec);
+	return 0;
+}
+
+static int __devinit snd_ali_create(struct snd_card *card,
+				    struct pci_dev *pci,
+				    int pcm_streams,
+				    int spdif_support,
+				    struct snd_ali ** r_ali)
+{
+	struct snd_ali *codec;
+	int i, err;
+	unsigned short cmdw;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_ali_dev_free,
+        };
+
+	*r_ali = NULL;
+
+	snd_ali_printk("creating ...\n");
+
+	/* enable PCI device */
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+	/* check, if we can restrict PCI DMA transfers to 31 bits */
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(31)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(31)) < 0) {
+		snd_printk(KERN_ERR "architecture does not support "
+			   "31bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	codec = kzalloc(sizeof(*codec), GFP_KERNEL);
+	if (!codec) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&codec->reg_lock);
+	spin_lock_init(&codec->voice_alloc);
+
+	codec->card = card;
+	codec->pci = pci;
+	codec->irq = -1;
+	codec->revision = pci->revision;
+	codec->spdif_support = spdif_support;
+
+	if (pcm_streams < 1)
+		pcm_streams = 1;
+	if (pcm_streams > 32)
+		pcm_streams = 32;
+	
+	pci_set_master(pci);
+	pci_read_config_word(pci, PCI_COMMAND, &cmdw);
+	if ((cmdw & PCI_COMMAND_IO) != PCI_COMMAND_IO) {
+		cmdw |= PCI_COMMAND_IO;
+		pci_write_config_word(pci, PCI_COMMAND, cmdw);
+	}
+	pci_set_master(pci);
+	
+	if (snd_ali_resources(codec)) {
+		snd_ali_free(codec);
+		return -EBUSY;
+	}
+
+	synchronize_irq(pci->irq);
+
+	codec->synth.chmap = 0;
+	codec->synth.chcnt = 0;
+	codec->spdif_mask = 0;
+	codec->synth.synthcount = 0;
+
+	if (codec->revision == ALI_5451_V02)
+		codec->chregs.regs.ac97read = ALI_AC97_WRITE;
+	else
+		codec->chregs.regs.ac97read = ALI_AC97_READ;
+	codec->chregs.regs.ac97write = ALI_AC97_WRITE;
+
+	codec->chregs.regs.start  = ALI_START;
+	codec->chregs.regs.stop   = ALI_STOP;
+	codec->chregs.regs.aint   = ALI_AINT;
+	codec->chregs.regs.ainten = ALI_AINTEN;
+
+	codec->chregs.data.start  = 0x00;
+	codec->chregs.data.stop   = 0x00;
+	codec->chregs.data.aint   = 0x00;
+	codec->chregs.data.ainten = 0x00;
+
+	/* M1533: southbridge */
+	codec->pci_m1533 = pci_get_device(0x10b9, 0x1533, NULL);
+	if (!codec->pci_m1533) {
+		snd_printk(KERN_ERR "ali5451: cannot find ALi 1533 chip.\n");
+		snd_ali_free(codec);
+		return -ENODEV;
+	}
+	/* M7101: power management */
+	codec->pci_m7101 = pci_get_device(0x10b9, 0x7101, NULL);
+	if (!codec->pci_m7101 && codec->revision == ALI_5451_V02) {
+		snd_printk(KERN_ERR "ali5451: cannot find ALi 7101 chip.\n");
+		snd_ali_free(codec);
+		return -ENODEV;
+	}
+
+	snd_ali_printk("snd_device_new is called.\n");
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, codec, &ops);
+	if (err < 0) {
+		snd_ali_free(codec);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	/* initialise synth voices*/
+	for (i = 0; i < ALI_CHANNELS; i++)
+		codec->synth.voices[i].number = i;
+
+	err = snd_ali_chip_init(codec);
+	if (err < 0) {
+		snd_printk(KERN_ERR "ali create: chip init error.\n");
+		return err;
+	}
+
+#ifdef CONFIG_PM
+	codec->image = kmalloc(sizeof(*codec->image), GFP_KERNEL);
+	if (!codec->image)
+		snd_printk(KERN_WARNING "can't allocate apm buffer\n");
+#endif
+
+	snd_ali_enable_address_interrupt(codec);
+	codec->hw_initialized = 1;
+
+	*r_ali = codec;
+	snd_ali_printk("created.\n");
+	return 0;
+}
+
+static int __devinit snd_ali_probe(struct pci_dev *pci,
+				   const struct pci_device_id *pci_id)
+{
+	struct snd_card *card;
+	struct snd_ali *codec;
+	int err;
+
+	snd_ali_printk("probe ...\n");
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	err = snd_ali_create(card, pci, pcm_channels, spdif, &codec);
+	if (err < 0)
+		goto error;
+	card->private_data = codec;
+
+	snd_ali_printk("mixer building ...\n");
+	err = snd_ali_mixer(codec);
+	if (err < 0)
+		goto error;
+	
+	snd_ali_printk("pcm building ...\n");
+	err = snd_ali_build_pcms(codec);
+	if (err < 0)
+		goto error;
+
+	snd_ali_proc_init(codec);
+
+	strcpy(card->driver, "ALI5451");
+	strcpy(card->shortname, "ALI 5451");
+	
+	sprintf(card->longname, "%s at 0x%lx, irq %i",
+		card->shortname, codec->port, codec->irq);
+
+	snd_ali_printk("register card.\n");
+	err = snd_card_register(card);
+	if (err < 0)
+		goto error;
+
+	pci_set_drvdata(pci, card);
+	return 0;
+
+ error:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit snd_ali_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "ALI 5451",
+	.id_table = snd_ali_ids,
+	.probe = snd_ali_probe,
+	.remove = __devexit_p(snd_ali_remove),
+#ifdef CONFIG_PM
+	.suspend = ali_suspend,
+	.resume = ali_resume,
+#endif
+};                                
+
+static int __init alsa_card_ali_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_ali_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_ali_init)
+module_exit(alsa_card_ali_exit)
diff --git a/linux/sound/pci/als300.c b/linux/sound/pci/als300.c
new file mode 100644
index 0000000..d7653cb
--- /dev/null
+++ b/linux/sound/pci/als300.c
@@ -0,0 +1,870 @@
+/*
+ *  als300.c - driver for Avance Logic ALS300/ALS300+ soundcards.
+ *  Copyright (C) 2005 by Ash Willis <ashwillis@programmer.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *  TODO
+ *  4 channel playback for ALS300+
+ *  gameport
+ *  mpu401
+ *  opl3
+ *
+ *  NOTES
+ *  The BLOCK_COUNTER registers for the ALS300(+) return a figure related to
+ *  the position in the current period, NOT the whole buffer. It is important
+ *  to know which period we are in so we can calculate the correct pointer.
+ *  This is why we always use 2 periods. We can then use a flip-flop variable
+ *  to keep track of what period we are in.
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#include <asm/io.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/ac97_codec.h>
+#include <sound/opl3.h>
+
+/* snd_als300_set_irq_flag */
+#define IRQ_DISABLE		0
+#define IRQ_ENABLE		1
+
+/* I/O port layout */
+#define AC97_ACCESS		0x00
+#define AC97_READ		0x04
+#define AC97_STATUS		0x06
+#define   AC97_DATA_AVAIL		(1<<6)
+#define   AC97_BUSY			(1<<7)
+#define ALS300_IRQ_STATUS	0x07		/* ALS300 Only */
+#define   IRQ_PLAYBACK			(1<<3)
+#define   IRQ_CAPTURE			(1<<2)
+#define GCR_DATA		0x08
+#define GCR_INDEX		0x0C
+#define ALS300P_DRAM_IRQ_STATUS	0x0D		/* ALS300+ Only */
+#define MPU_IRQ_STATUS		0x0E		/* ALS300 Rev. E+, ALS300+ */
+#define ALS300P_IRQ_STATUS	0x0F		/* ALS300+ Only */
+
+/* General Control Registers */
+#define PLAYBACK_START		0x80
+#define PLAYBACK_END		0x81
+#define PLAYBACK_CONTROL	0x82
+#define   TRANSFER_START		(1<<16)
+#define   FIFO_PAUSE			(1<<17)
+#define RECORD_START		0x83
+#define RECORD_END		0x84
+#define RECORD_CONTROL		0x85
+#define DRAM_WRITE_CONTROL	0x8B
+#define   WRITE_TRANS_START		(1<<16)
+#define   DRAM_MODE_2			(1<<17)
+#define MISC_CONTROL		0x8C
+#define   IRQ_SET_BIT			(1<<15)
+#define   VMUTE_NORMAL			(1<<20)
+#define   MMUTE_NORMAL			(1<<21)
+#define MUS_VOC_VOL		0x8E
+#define PLAYBACK_BLOCK_COUNTER	0x9A
+#define RECORD_BLOCK_COUNTER	0x9B
+
+#define DEBUG_CALLS	0
+#define DEBUG_PLAY_REC	0
+
+#if DEBUG_CALLS
+#define snd_als300_dbgcalls(format, args...) printk(KERN_DEBUG format, ##args)
+#define snd_als300_dbgcallenter() printk(KERN_ERR "--> %s\n", __func__)
+#define snd_als300_dbgcallleave() printk(KERN_ERR "<-- %s\n", __func__)
+#else
+#define snd_als300_dbgcalls(format, args...)
+#define snd_als300_dbgcallenter()
+#define snd_als300_dbgcallleave()
+#endif
+
+#if DEBUG_PLAY_REC
+#define snd_als300_dbgplay(format, args...) printk(KERN_ERR format, ##args)
+#else
+#define snd_als300_dbgplay(format, args...)
+#endif		
+
+enum {DEVICE_ALS300, DEVICE_ALS300_PLUS};
+
+MODULE_AUTHOR("Ash Willis <ashwillis@programmer.net>");
+MODULE_DESCRIPTION("Avance Logic ALS300");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Avance Logic,ALS300},{Avance Logic,ALS300+}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+struct snd_als300 {
+	unsigned long port;
+	spinlock_t reg_lock;
+	struct snd_card *card;
+	struct pci_dev *pci;
+
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *playback_substream;
+	struct snd_pcm_substream *capture_substream;
+
+	struct snd_ac97 *ac97;
+	struct snd_opl3 *opl3;
+
+	struct resource *res_port;
+
+	int irq;
+
+	int chip_type; /* ALS300 or ALS300+ */
+
+	char revision;	
+};
+
+struct snd_als300_substream_data {
+	int period_flipflop;
+	int control_register;
+	int block_counter_register;
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_als300_ids) = {
+	{ 0x4005, 0x0300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEVICE_ALS300 },
+	{ 0x4005, 0x0308, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEVICE_ALS300_PLUS },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_als300_ids);
+
+static inline u32 snd_als300_gcr_read(unsigned long port, unsigned short reg)
+{
+	outb(reg, port+GCR_INDEX);
+	return inl(port+GCR_DATA);
+}
+
+static inline void snd_als300_gcr_write(unsigned long port,
+						unsigned short reg, u32 val)
+{
+	outb(reg, port+GCR_INDEX);
+	outl(val, port+GCR_DATA);
+}
+
+/* Enable/Disable Interrupts */
+static void snd_als300_set_irq_flag(struct snd_als300 *chip, int cmd)
+{
+	u32 tmp = snd_als300_gcr_read(chip->port, MISC_CONTROL);
+	snd_als300_dbgcallenter();
+
+	/* boolean XOR check, since old vs. new hardware have
+	   directly reversed bit setting for ENABLE and DISABLE.
+	   ALS300+ acts like newer versions of ALS300 */
+	if (((chip->revision > 5 || chip->chip_type == DEVICE_ALS300_PLUS) ^
+						(cmd == IRQ_ENABLE)) == 0)
+		tmp |= IRQ_SET_BIT;
+	else
+		tmp &= ~IRQ_SET_BIT;
+	snd_als300_gcr_write(chip->port, MISC_CONTROL, tmp);
+	snd_als300_dbgcallleave();
+}
+
+static int snd_als300_free(struct snd_als300 *chip)
+{
+	snd_als300_dbgcallenter();
+	snd_als300_set_irq_flag(chip, IRQ_DISABLE);
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	snd_als300_dbgcallleave();
+	return 0;
+}
+
+static int snd_als300_dev_free(struct snd_device *device)
+{
+	struct snd_als300 *chip = device->device_data;
+	return snd_als300_free(chip);
+}
+
+static irqreturn_t snd_als300_interrupt(int irq, void *dev_id)
+{
+	u8 status;
+	struct snd_als300 *chip = dev_id;
+	struct snd_als300_substream_data *data;
+
+	status = inb(chip->port+ALS300_IRQ_STATUS);
+	if (!status) /* shared IRQ, for different device?? Exit ASAP! */
+		return IRQ_NONE;
+
+	/* ACK everything ASAP */
+	outb(status, chip->port+ALS300_IRQ_STATUS);
+	if (status & IRQ_PLAYBACK) {
+		if (chip->pcm && chip->playback_substream) {
+			data = chip->playback_substream->runtime->private_data;
+			data->period_flipflop ^= 1;
+			snd_pcm_period_elapsed(chip->playback_substream);
+			snd_als300_dbgplay("IRQ_PLAYBACK\n");
+		}
+	}
+	if (status & IRQ_CAPTURE) {
+		if (chip->pcm && chip->capture_substream) {
+			data = chip->capture_substream->runtime->private_data;
+			data->period_flipflop ^= 1;
+			snd_pcm_period_elapsed(chip->capture_substream);
+			snd_als300_dbgplay("IRQ_CAPTURE\n");
+		}
+	}
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t snd_als300plus_interrupt(int irq, void *dev_id)
+{
+	u8 general, mpu, dram;
+	struct snd_als300 *chip = dev_id;
+	struct snd_als300_substream_data *data;
+	
+	general = inb(chip->port+ALS300P_IRQ_STATUS);
+	mpu = inb(chip->port+MPU_IRQ_STATUS);
+	dram = inb(chip->port+ALS300P_DRAM_IRQ_STATUS);
+
+	/* shared IRQ, for different device?? Exit ASAP! */
+	if ((general == 0) && ((mpu & 0x80) == 0) && ((dram & 0x01) == 0))
+		return IRQ_NONE;
+
+	if (general & IRQ_PLAYBACK) {
+		if (chip->pcm && chip->playback_substream) {
+			outb(IRQ_PLAYBACK, chip->port+ALS300P_IRQ_STATUS);
+			data = chip->playback_substream->runtime->private_data;
+			data->period_flipflop ^= 1;
+			snd_pcm_period_elapsed(chip->playback_substream);
+			snd_als300_dbgplay("IRQ_PLAYBACK\n");
+		}
+	}
+	if (general & IRQ_CAPTURE) {
+		if (chip->pcm && chip->capture_substream) {
+			outb(IRQ_CAPTURE, chip->port+ALS300P_IRQ_STATUS);
+			data = chip->capture_substream->runtime->private_data;
+			data->period_flipflop ^= 1;
+			snd_pcm_period_elapsed(chip->capture_substream);
+			snd_als300_dbgplay("IRQ_CAPTURE\n");
+		}
+	}
+	/* FIXME: Ack other interrupt types. Not important right now as
+	 * those other devices aren't enabled. */
+	return IRQ_HANDLED;
+}
+
+static void __devexit snd_als300_remove(struct pci_dev *pci)
+{
+	snd_als300_dbgcallenter();
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+	snd_als300_dbgcallleave();
+}
+
+static unsigned short snd_als300_ac97_read(struct snd_ac97 *ac97,
+							unsigned short reg)
+{
+	int i;
+	struct snd_als300 *chip = ac97->private_data;
+
+	for (i = 0; i < 1000; i++) {
+		if ((inb(chip->port+AC97_STATUS) & (AC97_BUSY)) == 0)
+			break;
+		udelay(10);
+	}
+	outl((reg << 24) | (1 << 31), chip->port+AC97_ACCESS);
+
+	for (i = 0; i < 1000; i++) {
+		if ((inb(chip->port+AC97_STATUS) & (AC97_DATA_AVAIL)) != 0)
+			break;
+		udelay(10);
+	}
+	return inw(chip->port+AC97_READ);
+}
+
+static void snd_als300_ac97_write(struct snd_ac97 *ac97,
+				unsigned short reg, unsigned short val)
+{
+	int i;
+	struct snd_als300 *chip = ac97->private_data;
+
+	for (i = 0; i < 1000; i++) {
+		if ((inb(chip->port+AC97_STATUS) & (AC97_BUSY)) == 0)
+			break;
+		udelay(10);
+	}
+	outl((reg << 24) | val, chip->port+AC97_ACCESS);
+}
+
+static int snd_als300_ac97(struct snd_als300 *chip)
+{
+	struct snd_ac97_bus *bus;
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_als300_ac97_write,
+		.read = snd_als300_ac97_read,
+	};
+
+	snd_als300_dbgcallenter();
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &bus)) < 0)
+		return err;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+
+	snd_als300_dbgcallleave();
+	return snd_ac97_mixer(bus, &ac97, &chip->ac97);
+}
+
+/* hardware definition
+ *
+ * In AC97 mode, we always use 48k/16bit/stereo.
+ * Any request to change data type is ignored by
+ * the card when it is running outside of legacy
+ * mode.
+ */
+static struct snd_pcm_hardware snd_als300_playback_hw =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_PAUSE |
+				SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S16,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	64 * 1024,
+	.period_bytes_min =	64,
+	.period_bytes_max =	32 * 1024,
+	.periods_min =		2,
+	.periods_max =		2,
+};
+
+static struct snd_pcm_hardware snd_als300_capture_hw =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_PAUSE |
+				SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S16,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	64 * 1024,
+	.period_bytes_min =	64,
+	.period_bytes_max =	32 * 1024,
+	.periods_min =		2,
+	.periods_max =		2,
+};
+
+static int snd_als300_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_als300 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_als300_substream_data *data = kzalloc(sizeof(*data),
+								GFP_KERNEL);
+
+	snd_als300_dbgcallenter();
+	chip->playback_substream = substream;
+	runtime->hw = snd_als300_playback_hw;
+	runtime->private_data = data;
+	data->control_register = PLAYBACK_CONTROL;
+	data->block_counter_register = PLAYBACK_BLOCK_COUNTER;
+	snd_als300_dbgcallleave();
+	return 0;
+}
+
+static int snd_als300_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_als300 *chip = snd_pcm_substream_chip(substream);
+	struct snd_als300_substream_data *data;
+
+	data = substream->runtime->private_data;
+	snd_als300_dbgcallenter();
+	kfree(data);
+	chip->playback_substream = NULL;
+	snd_pcm_lib_free_pages(substream);
+	snd_als300_dbgcallleave();
+	return 0;
+}
+
+static int snd_als300_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_als300 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_als300_substream_data *data = kzalloc(sizeof(*data),
+								GFP_KERNEL);
+
+	snd_als300_dbgcallenter();
+	chip->capture_substream = substream;
+	runtime->hw = snd_als300_capture_hw;
+	runtime->private_data = data;
+	data->control_register = RECORD_CONTROL;
+	data->block_counter_register = RECORD_BLOCK_COUNTER;
+	snd_als300_dbgcallleave();
+	return 0;
+}
+
+static int snd_als300_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_als300 *chip = snd_pcm_substream_chip(substream);
+	struct snd_als300_substream_data *data;
+
+	data = substream->runtime->private_data;
+	snd_als300_dbgcallenter();
+	kfree(data);
+	chip->capture_substream = NULL;
+	snd_pcm_lib_free_pages(substream);
+	snd_als300_dbgcallleave();
+	return 0;
+}
+
+static int snd_als300_pcm_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int snd_als300_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_als300_playback_prepare(struct snd_pcm_substream *substream)
+{
+	u32 tmp;
+	struct snd_als300 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned short period_bytes = snd_pcm_lib_period_bytes(substream);
+	unsigned short buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
+	
+	snd_als300_dbgcallenter();
+	spin_lock_irq(&chip->reg_lock);
+	tmp = snd_als300_gcr_read(chip->port, PLAYBACK_CONTROL);
+	tmp &= ~TRANSFER_START;
+
+	snd_als300_dbgplay("Period bytes: %d Buffer bytes %d\n",
+						period_bytes, buffer_bytes);
+	
+	/* set block size */
+	tmp &= 0xffff0000;
+	tmp |= period_bytes - 1;
+	snd_als300_gcr_write(chip->port, PLAYBACK_CONTROL, tmp);
+
+	/* set dma area */
+	snd_als300_gcr_write(chip->port, PLAYBACK_START,
+					runtime->dma_addr);
+	snd_als300_gcr_write(chip->port, PLAYBACK_END,
+					runtime->dma_addr + buffer_bytes - 1);
+	spin_unlock_irq(&chip->reg_lock);
+	snd_als300_dbgcallleave();
+	return 0;
+}
+
+static int snd_als300_capture_prepare(struct snd_pcm_substream *substream)
+{
+	u32 tmp;
+	struct snd_als300 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned short period_bytes = snd_pcm_lib_period_bytes(substream);
+	unsigned short buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
+
+	snd_als300_dbgcallenter();
+	spin_lock_irq(&chip->reg_lock);
+	tmp = snd_als300_gcr_read(chip->port, RECORD_CONTROL);
+	tmp &= ~TRANSFER_START;
+
+	snd_als300_dbgplay("Period bytes: %d Buffer bytes %d\n", period_bytes,
+							buffer_bytes);
+
+	/* set block size */
+	tmp &= 0xffff0000;
+	tmp |= period_bytes - 1;
+
+	/* set dma area */
+	snd_als300_gcr_write(chip->port, RECORD_CONTROL, tmp);
+	snd_als300_gcr_write(chip->port, RECORD_START,
+					runtime->dma_addr);
+	snd_als300_gcr_write(chip->port, RECORD_END,
+					runtime->dma_addr + buffer_bytes - 1);
+	spin_unlock_irq(&chip->reg_lock);
+	snd_als300_dbgcallleave();
+	return 0;
+}
+
+static int snd_als300_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_als300 *chip = snd_pcm_substream_chip(substream);
+	u32 tmp;
+	struct snd_als300_substream_data *data;
+	unsigned short reg;
+	int ret = 0;
+
+	data = substream->runtime->private_data;
+	reg = data->control_register;
+
+	snd_als300_dbgcallenter();
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		tmp = snd_als300_gcr_read(chip->port, reg);
+		data->period_flipflop = 1;
+		snd_als300_gcr_write(chip->port, reg, tmp | TRANSFER_START);
+		snd_als300_dbgplay("TRIGGER START\n");
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		tmp = snd_als300_gcr_read(chip->port, reg);
+		snd_als300_gcr_write(chip->port, reg, tmp & ~TRANSFER_START);
+		snd_als300_dbgplay("TRIGGER STOP\n");
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		tmp = snd_als300_gcr_read(chip->port, reg);
+		snd_als300_gcr_write(chip->port, reg, tmp | FIFO_PAUSE);
+		snd_als300_dbgplay("TRIGGER PAUSE\n");
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		tmp = snd_als300_gcr_read(chip->port, reg);
+		snd_als300_gcr_write(chip->port, reg, tmp & ~FIFO_PAUSE);
+		snd_als300_dbgplay("TRIGGER RELEASE\n");
+		break;
+	default:
+		snd_als300_dbgplay("TRIGGER INVALID\n");
+		ret = -EINVAL;
+	}
+	spin_unlock(&chip->reg_lock);
+	snd_als300_dbgcallleave();
+	return ret;
+}
+
+static snd_pcm_uframes_t snd_als300_pointer(struct snd_pcm_substream *substream)
+{
+	u16 current_ptr;
+	struct snd_als300 *chip = snd_pcm_substream_chip(substream);
+	struct snd_als300_substream_data *data;
+	unsigned short period_bytes;
+
+	data = substream->runtime->private_data;
+	period_bytes = snd_pcm_lib_period_bytes(substream);
+	
+	snd_als300_dbgcallenter();
+	spin_lock(&chip->reg_lock);
+	current_ptr = (u16) snd_als300_gcr_read(chip->port,
+					data->block_counter_register) + 4;
+	spin_unlock(&chip->reg_lock);
+	if (current_ptr > period_bytes)
+		current_ptr = 0;
+	else
+		current_ptr = period_bytes - current_ptr;
+
+	if (data->period_flipflop == 0)
+		current_ptr += period_bytes;
+	snd_als300_dbgplay("Pointer (bytes): %d\n", current_ptr);
+	snd_als300_dbgcallleave();
+	return bytes_to_frames(substream->runtime, current_ptr);
+}
+
+static struct snd_pcm_ops snd_als300_playback_ops = {
+	.open =		snd_als300_playback_open,
+	.close =	snd_als300_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_als300_pcm_hw_params,
+	.hw_free =	snd_als300_pcm_hw_free,
+	.prepare =	snd_als300_playback_prepare,
+	.trigger =	snd_als300_trigger,
+	.pointer =	snd_als300_pointer,
+};
+
+static struct snd_pcm_ops snd_als300_capture_ops = {
+	.open =		snd_als300_capture_open,
+	.close =	snd_als300_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_als300_pcm_hw_params,
+	.hw_free =	snd_als300_pcm_hw_free,
+	.prepare =	snd_als300_capture_prepare,
+	.trigger =	snd_als300_trigger,
+	.pointer =	snd_als300_pointer,
+};
+
+static int __devinit snd_als300_new_pcm(struct snd_als300 *chip)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	snd_als300_dbgcallenter();
+	err = snd_pcm_new(chip->card, "ALS300", 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+	pcm->private_data = chip;
+	strcpy(pcm->name, "ALS300");
+	chip->pcm = pcm;
+
+	/* set operators */
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				&snd_als300_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&snd_als300_capture_ops);
+
+	/* pre-allocation of buffers */
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+	snd_dma_pci_data(chip->pci), 64*1024, 64*1024);
+	snd_als300_dbgcallleave();
+	return 0;
+}
+
+static void snd_als300_init(struct snd_als300 *chip)
+{
+	unsigned long flags;
+	u32 tmp;
+	
+	snd_als300_dbgcallenter();
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	chip->revision = (snd_als300_gcr_read(chip->port, MISC_CONTROL) >> 16)
+								& 0x0000000F;
+	/* Setup DRAM */
+	tmp = snd_als300_gcr_read(chip->port, DRAM_WRITE_CONTROL);
+	snd_als300_gcr_write(chip->port, DRAM_WRITE_CONTROL,
+						(tmp | DRAM_MODE_2)
+						& ~WRITE_TRANS_START);
+
+	/* Enable IRQ output */
+	snd_als300_set_irq_flag(chip, IRQ_ENABLE);
+
+	/* Unmute hardware devices so their outputs get routed to
+	 * the onboard mixer */
+	tmp = snd_als300_gcr_read(chip->port, MISC_CONTROL);
+	snd_als300_gcr_write(chip->port, MISC_CONTROL,
+			tmp | VMUTE_NORMAL | MMUTE_NORMAL);
+
+	/* Reset volumes */
+	snd_als300_gcr_write(chip->port, MUS_VOC_VOL, 0);
+
+	/* Make sure playback transfer is stopped */
+	tmp = snd_als300_gcr_read(chip->port, PLAYBACK_CONTROL);
+	snd_als300_gcr_write(chip->port, PLAYBACK_CONTROL,
+			tmp & ~TRANSFER_START);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_als300_dbgcallleave();
+}
+
+static int __devinit snd_als300_create(struct snd_card *card,
+				       struct pci_dev *pci, int chip_type,
+				       struct snd_als300 **rchip)
+{
+	struct snd_als300 *chip;
+	void *irq_handler;
+	int err;
+
+	static struct snd_device_ops ops = {
+		.dev_free = snd_als300_dev_free,
+	};
+	*rchip = NULL;
+
+	snd_als300_dbgcallenter();
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(28)) < 0 ||
+		pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(28)) < 0) {
+		printk(KERN_ERR "error setting 28bit DMA mask\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+	pci_set_master(pci);
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	chip->chip_type = chip_type;
+	spin_lock_init(&chip->reg_lock);
+
+	if ((err = pci_request_regions(pci, "ALS300")) < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+	chip->port = pci_resource_start(pci, 0);
+
+	if (chip->chip_type == DEVICE_ALS300_PLUS)
+		irq_handler = snd_als300plus_interrupt;
+	else
+		irq_handler = snd_als300_interrupt;
+
+	if (request_irq(pci->irq, irq_handler, IRQF_SHARED,
+			card->shortname, chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_als300_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+
+
+	snd_als300_init(chip);
+
+	err = snd_als300_ac97(chip);
+	if (err < 0) {
+		snd_printk(KERN_WARNING "Could not create ac97\n");
+		snd_als300_free(chip);
+		return err;
+	}
+
+	if ((err = snd_als300_new_pcm(chip)) < 0) {
+		snd_printk(KERN_WARNING "Could not create PCM\n");
+		snd_als300_free(chip);
+		return err;
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+						chip, &ops)) < 0) {
+		snd_als300_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rchip = chip;
+	snd_als300_dbgcallleave();
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_als300_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_als300 *chip = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_ac97_suspend(chip->ac97);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_als300_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_als300 *chip = card->private_data;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "als300: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_als300_init(chip);
+	snd_ac97_resume(chip->ac97);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static int __devinit snd_als300_probe(struct pci_dev *pci,
+                             const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_als300 *chip;
+	int err, chip_type;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+
+	if (err < 0)
+		return err;
+
+	chip_type = pci_id->driver_data;
+
+	if ((err = snd_als300_create(card, pci, chip_type, &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = chip;
+
+	strcpy(card->driver, "ALS300");
+	if (chip->chip_type == DEVICE_ALS300_PLUS)
+		/* don't know much about ALS300+ yet
+		 * print revision number for now */
+		sprintf(card->shortname, "ALS300+ (Rev. %d)", chip->revision);
+	else
+		sprintf(card->shortname, "ALS300 (Rev. %c)", 'A' +
+							chip->revision - 1);
+	sprintf(card->longname, "%s at 0x%lx irq %i",
+				card->shortname, chip->port, chip->irq);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static struct pci_driver driver = {
+	.name = "ALS300",
+	.id_table = snd_als300_ids,
+	.probe = snd_als300_probe,
+	.remove = __devexit_p(snd_als300_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_als300_suspend,
+	.resume = snd_als300_resume,
+#endif
+};
+
+static int __init alsa_card_als300_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_als300_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_als300_init)
+module_exit(alsa_card_als300_exit)
diff --git a/linux/sound/pci/als4000.c b/linux/sound/pci/als4000.c
new file mode 100644
index 0000000..0e247cb
--- /dev/null
+++ b/linux/sound/pci/als4000.c
@@ -0,0 +1,1060 @@
+/*
+ *  card-als4000.c - driver for Avance Logic ALS4000 based soundcards.
+ *  Copyright (C) 2000 by Bart Hartgers <bart@etpmod.phys.tue.nl>,
+ *			  Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (C) 2002, 2008 by Andreas Mohr <hw7oshyuv3001@sneakemail.com>
+ *
+ *  Framework borrowed from Massimo Piccioni's card-als100.c.
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * NOTES
+ *
+ *  Since Avance does not provide any meaningful documentation, and I
+ *  bought an ALS4000 based soundcard, I was forced to base this driver
+ *  on reverse engineering.
+ *
+ *  Note: this is no longer true (thank you!):
+ *  pretty verbose chip docu (ALS4000a.PDF) can be found on the ALSA web site.
+ *  Page numbers stated anywhere below with the "SPECS_PAGE:" tag
+ *  refer to: ALS4000a.PDF specs Ver 1.0, May 28th, 1998.
+ *
+ *  The ALS4000 seems to be the PCI-cousin of the ALS100. It contains an
+ *  ALS100-like SB DSP/mixer, an OPL3 synth, a MPU401 and a gameport 
+ *  interface. These subsystems can be mapped into ISA io-port space, 
+ *  using the PCI-interface. In addition, the PCI-bit provides DMA and IRQ 
+ *  services to the subsystems.
+ * 
+ * While ALS4000 is very similar to a SoundBlaster, the differences in
+ * DMA and capturing require more changes to the SoundBlaster than
+ * desirable, so I made this separate driver.
+ * 
+ * The ALS4000 can do real full duplex playback/capture.
+ *
+ * FMDAC:
+ * - 0x4f -> port 0x14
+ * - port 0x15 |= 1
+ *
+ * Enable/disable 3D sound:
+ * - 0x50 -> port 0x14
+ * - change bit 6 (0x40) of port 0x15
+ *
+ * Set QSound:
+ * - 0xdb -> port 0x14
+ * - set port 0x15:
+ *   0x3e (mode 3), 0x3c (mode 2), 0x3a (mode 1), 0x38 (mode 0)
+ *
+ * Set KSound:
+ * - value -> some port 0x0c0d
+ *
+ * ToDo:
+ * - by default, don't enable legacy game and use PCI game I/O
+ * - power management? (card can do voice wakeup according to datasheet!!)
+ */
+
+#include <asm/io.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/gameport.h>
+#include <linux/moduleparam.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Bart Hartgers <bart@etpmod.phys.tue.nl>, Andreas Mohr");
+MODULE_DESCRIPTION("Avance Logic ALS4000");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Avance Logic,ALS4000}}");
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+#define SUPPORT_JOYSTICK 1
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+#ifdef SUPPORT_JOYSTICK
+static int joystick_port[SNDRV_CARDS];
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ALS4000 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ALS4000 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ALS4000 soundcard.");
+#ifdef SUPPORT_JOYSTICK
+module_param_array(joystick_port, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_port, "Joystick port address for ALS4000 soundcard. (0 = disabled)");
+#endif
+
+struct snd_card_als4000 {
+	/* most frequent access first */
+	unsigned long iobase;
+	struct pci_dev *pci;
+	struct snd_sb *chip;
+#ifdef SUPPORT_JOYSTICK
+	struct gameport *gameport;
+#endif
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_als4000_ids) = {
+	{ 0x4005, 0x4000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },   /* ALS4000 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_als4000_ids);
+
+enum als4k_iobase_t {
+	/* IOx: B == Byte, W = Word, D = DWord; SPECS_PAGE: 37 */
+	ALS4K_IOD_00_AC97_ACCESS = 0x00,
+	ALS4K_IOW_04_AC97_READ = 0x04,
+	ALS4K_IOB_06_AC97_STATUS = 0x06,
+	ALS4K_IOB_07_IRQSTATUS = 0x07,
+	ALS4K_IOD_08_GCR_DATA = 0x08,
+	ALS4K_IOB_0C_GCR_INDEX = 0x0c,
+	ALS4K_IOB_0E_IRQTYPE_SB_CR1E_MPU = 0x0e,
+	ALS4K_IOB_10_ADLIB_ADDR0 = 0x10,
+	ALS4K_IOB_11_ADLIB_ADDR1 = 0x11,
+	ALS4K_IOB_12_ADLIB_ADDR2 = 0x12,
+	ALS4K_IOB_13_ADLIB_ADDR3 = 0x13,
+	ALS4K_IOB_14_MIXER_INDEX = 0x14,
+	ALS4K_IOB_15_MIXER_DATA = 0x15,
+	ALS4K_IOB_16_ESP_RESET = 0x16,
+	ALS4K_IOB_16_ACK_FOR_CR1E = 0x16, /* 2nd function */
+	ALS4K_IOB_18_OPL_ADDR0 = 0x18,
+	ALS4K_IOB_19_OPL_ADDR1 = 0x19,
+	ALS4K_IOB_1A_ESP_RD_DATA = 0x1a,
+	ALS4K_IOB_1C_ESP_CMD_DATA = 0x1c,
+	ALS4K_IOB_1C_ESP_WR_STATUS = 0x1c, /* 2nd function */
+	ALS4K_IOB_1E_ESP_RD_STATUS8 = 0x1e,
+	ALS4K_IOB_1F_ESP_RD_STATUS16 = 0x1f,
+	ALS4K_IOB_20_ESP_GAMEPORT_200 = 0x20,
+	ALS4K_IOB_21_ESP_GAMEPORT_201 = 0x21,
+	ALS4K_IOB_30_MIDI_DATA = 0x30,
+	ALS4K_IOB_31_MIDI_STATUS = 0x31,
+	ALS4K_IOB_31_MIDI_COMMAND = 0x31, /* 2nd function */
+};
+
+enum als4k_iobase_0e_t {
+	ALS4K_IOB_0E_MPU_IRQ = 0x10,
+	ALS4K_IOB_0E_CR1E_IRQ = 0x40,
+	ALS4K_IOB_0E_SB_DMA_IRQ = 0x80,
+};
+
+enum als4k_gcr_t { /* all registers 32bit wide; SPECS_PAGE: 38 to 42 */
+	ALS4K_GCR8C_MISC_CTRL = 0x8c,
+	ALS4K_GCR90_TEST_MODE_REG = 0x90,
+	ALS4K_GCR91_DMA0_ADDR = 0x91,
+	ALS4K_GCR92_DMA0_MODE_COUNT = 0x92,
+	ALS4K_GCR93_DMA1_ADDR = 0x93,
+	ALS4K_GCR94_DMA1_MODE_COUNT = 0x94,
+	ALS4K_GCR95_DMA3_ADDR = 0x95,
+	ALS4K_GCR96_DMA3_MODE_COUNT = 0x96,
+	ALS4K_GCR99_DMA_EMULATION_CTRL = 0x99,
+	ALS4K_GCRA0_FIFO1_CURRENT_ADDR = 0xa0,
+	ALS4K_GCRA1_FIFO1_STATUS_BYTECOUNT = 0xa1,
+	ALS4K_GCRA2_FIFO2_PCIADDR = 0xa2,
+	ALS4K_GCRA3_FIFO2_COUNT = 0xa3,
+	ALS4K_GCRA4_FIFO2_CURRENT_ADDR = 0xa4,
+	ALS4K_GCRA5_FIFO1_STATUS_BYTECOUNT = 0xa5,
+	ALS4K_GCRA6_PM_CTRL = 0xa6,
+	ALS4K_GCRA7_PCI_ACCESS_STORAGE = 0xa7,
+	ALS4K_GCRA8_LEGACY_CFG1 = 0xa8,
+	ALS4K_GCRA9_LEGACY_CFG2 = 0xa9,
+	ALS4K_GCRFF_DUMMY_SCRATCH = 0xff,
+};
+
+enum als4k_gcr8c_t {
+	ALS4K_GCR8C_IRQ_MASK_CTRL_ENABLE = 0x8000,
+	ALS4K_GCR8C_CHIP_REV_MASK = 0xf0000
+};
+
+static inline void snd_als4k_iobase_writeb(unsigned long iobase,
+						enum als4k_iobase_t reg,
+						u8 val)
+{
+	outb(val, iobase + reg);
+}
+
+static inline void snd_als4k_iobase_writel(unsigned long iobase,
+						enum als4k_iobase_t reg,
+						u32 val)
+{
+	outl(val, iobase + reg);
+}
+
+static inline u8 snd_als4k_iobase_readb(unsigned long iobase,
+						enum als4k_iobase_t reg)
+{
+	return inb(iobase + reg);
+}
+
+static inline u32 snd_als4k_iobase_readl(unsigned long iobase,
+						enum als4k_iobase_t reg)
+{
+	return inl(iobase + reg);
+}
+
+static inline void snd_als4k_gcr_write_addr(unsigned long iobase,
+						 enum als4k_gcr_t reg,
+						 u32 val)
+{
+	snd_als4k_iobase_writeb(iobase, ALS4K_IOB_0C_GCR_INDEX, reg);
+	snd_als4k_iobase_writel(iobase, ALS4K_IOD_08_GCR_DATA, val);
+}
+
+static inline void snd_als4k_gcr_write(struct snd_sb *sb,
+					 enum als4k_gcr_t reg,
+					 u32 val)
+{
+	snd_als4k_gcr_write_addr(sb->alt_port, reg, val);
+}	
+
+static inline u32 snd_als4k_gcr_read_addr(unsigned long iobase,
+						 enum als4k_gcr_t reg)
+{
+	/* SPECS_PAGE: 37/38 */
+	snd_als4k_iobase_writeb(iobase, ALS4K_IOB_0C_GCR_INDEX, reg);
+	return snd_als4k_iobase_readl(iobase, ALS4K_IOD_08_GCR_DATA);
+}
+
+static inline u32 snd_als4k_gcr_read(struct snd_sb *sb, enum als4k_gcr_t reg)
+{
+	return snd_als4k_gcr_read_addr(sb->alt_port, reg);
+}
+
+enum als4k_cr_t { /* all registers 8bit wide; SPECS_PAGE: 20 to 23 */
+	ALS4K_CR0_SB_CONFIG = 0x00,
+	ALS4K_CR2_MISC_CONTROL = 0x02,
+	ALS4K_CR3_CONFIGURATION = 0x03,
+	ALS4K_CR17_FIFO_STATUS = 0x17,
+	ALS4K_CR18_ESP_MAJOR_VERSION = 0x18,
+	ALS4K_CR19_ESP_MINOR_VERSION = 0x19,
+	ALS4K_CR1A_MPU401_UART_MODE_CONTROL = 0x1a,
+	ALS4K_CR1C_FIFO2_BLOCK_LENGTH_LO = 0x1c,
+	ALS4K_CR1D_FIFO2_BLOCK_LENGTH_HI = 0x1d,
+	ALS4K_CR1E_FIFO2_CONTROL = 0x1e, /* secondary PCM FIFO (recording) */
+	ALS4K_CR3A_MISC_CONTROL = 0x3a,
+	ALS4K_CR3B_CRC32_BYTE0 = 0x3b, /* for testing, activate via CR3A */
+	ALS4K_CR3C_CRC32_BYTE1 = 0x3c,
+	ALS4K_CR3D_CRC32_BYTE2 = 0x3d,
+	ALS4K_CR3E_CRC32_BYTE3 = 0x3e,
+};
+
+enum als4k_cr0_t {
+	ALS4K_CR0_DMA_CONTIN_MODE_CTRL = 0x02, /* IRQ/FIFO controlled for 0/1 */
+	ALS4K_CR0_DMA_90H_MODE_CTRL = 0x04, /* IRQ/FIFO controlled for 0/1 */
+	ALS4K_CR0_MX80_81_REG_WRITE_ENABLE = 0x80,
+};
+
+static inline void snd_als4_cr_write(struct snd_sb *chip,
+					enum als4k_cr_t reg,
+					u8 data)
+{
+	/* Control Register is reg | 0xc0 (bit 7, 6 set) on sbmixer_index
+	 * NOTE: assumes chip->mixer_lock to be locked externally already!
+	 * SPECS_PAGE: 6 */
+	snd_sbmixer_write(chip, reg | 0xc0, data);
+}
+
+static inline u8 snd_als4_cr_read(struct snd_sb *chip,
+					enum als4k_cr_t reg)
+{
+	/* NOTE: assumes chip->mixer_lock to be locked externally already! */
+	return snd_sbmixer_read(chip, reg | 0xc0);
+}
+
+
+
+static void snd_als4000_set_rate(struct snd_sb *chip, unsigned int rate)
+{
+	if (!(chip->mode & SB_RATE_LOCK)) {
+		snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_OUT);
+		snd_sbdsp_command(chip, rate>>8);
+		snd_sbdsp_command(chip, rate);
+	}
+}
+
+static inline void snd_als4000_set_capture_dma(struct snd_sb *chip,
+					       dma_addr_t addr, unsigned size)
+{
+	/* SPECS_PAGE: 40 */
+	snd_als4k_gcr_write(chip, ALS4K_GCRA2_FIFO2_PCIADDR, addr);
+	snd_als4k_gcr_write(chip, ALS4K_GCRA3_FIFO2_COUNT, (size-1));
+}
+
+static inline void snd_als4000_set_playback_dma(struct snd_sb *chip,
+						dma_addr_t addr,
+						unsigned size)
+{
+	/* SPECS_PAGE: 38 */
+	snd_als4k_gcr_write(chip, ALS4K_GCR91_DMA0_ADDR, addr);
+	snd_als4k_gcr_write(chip, ALS4K_GCR92_DMA0_MODE_COUNT,
+							(size-1)|0x180000);
+}
+
+#define ALS4000_FORMAT_SIGNED	(1<<0)
+#define ALS4000_FORMAT_16BIT	(1<<1)
+#define ALS4000_FORMAT_STEREO	(1<<2)
+
+static int snd_als4000_get_format(struct snd_pcm_runtime *runtime)
+{
+	int result;
+
+	result = 0;
+	if (snd_pcm_format_signed(runtime->format))
+		result |= ALS4000_FORMAT_SIGNED;
+	if (snd_pcm_format_physical_width(runtime->format) == 16)
+		result |= ALS4000_FORMAT_16BIT;
+	if (runtime->channels > 1)
+		result |= ALS4000_FORMAT_STEREO;
+	return result;
+}
+
+/* structure for setting up playback */
+static const struct {
+	unsigned char dsp_cmd, dma_on, dma_off, format;
+} playback_cmd_vals[]={
+/* ALS4000_FORMAT_U8_MONO */
+{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_UNS_MONO },
+/* ALS4000_FORMAT_S8_MONO */	
+{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_SIGN_MONO },
+/* ALS4000_FORMAT_U16L_MONO */
+{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_UNS_MONO },
+/* ALS4000_FORMAT_S16L_MONO */
+{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_SIGN_MONO },
+/* ALS4000_FORMAT_U8_STEREO */
+{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_UNS_STEREO },
+/* ALS4000_FORMAT_S8_STEREO */	
+{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_SIGN_STEREO },
+/* ALS4000_FORMAT_U16L_STEREO */
+{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_UNS_STEREO },
+/* ALS4000_FORMAT_S16L_STEREO */
+{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_SIGN_STEREO },
+};
+#define playback_cmd(chip) (playback_cmd_vals[(chip)->playback_format])
+
+/* structure for setting up capture */
+enum { CMD_WIDTH8=0x04, CMD_SIGNED=0x10, CMD_MONO=0x80, CMD_STEREO=0xA0 };
+static const unsigned char capture_cmd_vals[]=
+{
+CMD_WIDTH8|CMD_MONO,			/* ALS4000_FORMAT_U8_MONO */
+CMD_WIDTH8|CMD_SIGNED|CMD_MONO,		/* ALS4000_FORMAT_S8_MONO */	
+CMD_MONO,				/* ALS4000_FORMAT_U16L_MONO */
+CMD_SIGNED|CMD_MONO,			/* ALS4000_FORMAT_S16L_MONO */
+CMD_WIDTH8|CMD_STEREO,			/* ALS4000_FORMAT_U8_STEREO */
+CMD_WIDTH8|CMD_SIGNED|CMD_STEREO,	/* ALS4000_FORMAT_S8_STEREO */	
+CMD_STEREO,				/* ALS4000_FORMAT_U16L_STEREO */
+CMD_SIGNED|CMD_STEREO,			/* ALS4000_FORMAT_S16L_STEREO */
+};	
+#define capture_cmd(chip) (capture_cmd_vals[(chip)->capture_format])
+
+static int snd_als4000_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_als4000_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_als4000_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long size;
+	unsigned count;
+
+	chip->capture_format = snd_als4000_get_format(runtime);
+		
+	size = snd_pcm_lib_buffer_bytes(substream);
+	count = snd_pcm_lib_period_bytes(substream);
+	
+	if (chip->capture_format & ALS4000_FORMAT_16BIT)
+		count >>= 1;
+	count--;
+
+	spin_lock_irq(&chip->reg_lock);
+	snd_als4000_set_rate(chip, runtime->rate);
+	snd_als4000_set_capture_dma(chip, runtime->dma_addr, size);
+	spin_unlock_irq(&chip->reg_lock);
+	spin_lock_irq(&chip->mixer_lock);
+	snd_als4_cr_write(chip, ALS4K_CR1C_FIFO2_BLOCK_LENGTH_LO, count & 0xff);
+	snd_als4_cr_write(chip, ALS4K_CR1D_FIFO2_BLOCK_LENGTH_HI, count >> 8);
+	spin_unlock_irq(&chip->mixer_lock);
+	return 0;
+}
+
+static int snd_als4000_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long size;
+	unsigned count;
+
+	chip->playback_format = snd_als4000_get_format(runtime);
+	
+	size = snd_pcm_lib_buffer_bytes(substream);
+	count = snd_pcm_lib_period_bytes(substream);
+	
+	if (chip->playback_format & ALS4000_FORMAT_16BIT)
+		count >>= 1;
+	count--;
+	
+	/* FIXME: from second playback on, there's a lot more clicks and pops
+	 * involved here than on first playback. Fiddling with
+	 * tons of different settings didn't help (DMA, speaker on/off,
+	 * reordering, ...). Something seems to get enabled on playback
+	 * that I haven't found out how to disable again, which then causes
+	 * the switching pops to reach the speakers the next time here. */
+	spin_lock_irq(&chip->reg_lock);
+	snd_als4000_set_rate(chip, runtime->rate);
+	snd_als4000_set_playback_dma(chip, runtime->dma_addr, size);
+	
+	/* SPEAKER_ON not needed, since dma_on seems to also enable speaker */
+	/* snd_sbdsp_command(chip, SB_DSP_SPEAKER_ON); */
+	snd_sbdsp_command(chip, playback_cmd(chip).dsp_cmd);
+	snd_sbdsp_command(chip, playback_cmd(chip).format);
+	snd_sbdsp_command(chip, count & 0xff);
+	snd_sbdsp_command(chip, count >> 8);
+	snd_sbdsp_command(chip, playback_cmd(chip).dma_off);	
+	spin_unlock_irq(&chip->reg_lock);
+	
+	return 0;
+}
+
+static int snd_als4000_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+	
+	/* FIXME race condition in here!!!
+	   chip->mode non-atomic update gets consistently protected
+	   by reg_lock always, _except_ for this place!!
+	   Probably need to take reg_lock as outer (or inner??) lock, too.
+	   (or serialize both lock operations? probably not, though... - racy?)
+	*/
+	spin_lock(&chip->mixer_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		chip->mode |= SB_RATE_LOCK_CAPTURE;
+		snd_als4_cr_write(chip, ALS4K_CR1E_FIFO2_CONTROL,
+							 capture_cmd(chip));
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		chip->mode &= ~SB_RATE_LOCK_CAPTURE;
+		snd_als4_cr_write(chip, ALS4K_CR1E_FIFO2_CONTROL,
+							 capture_cmd(chip));
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	spin_unlock(&chip->mixer_lock);
+	return result;
+}
+
+static int snd_als4000_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		chip->mode |= SB_RATE_LOCK_PLAYBACK;
+		snd_sbdsp_command(chip, playback_cmd(chip).dma_on);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		snd_sbdsp_command(chip, playback_cmd(chip).dma_off);
+		chip->mode &= ~SB_RATE_LOCK_PLAYBACK;
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	spin_unlock(&chip->reg_lock);
+	return result;
+}
+
+static snd_pcm_uframes_t snd_als4000_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	unsigned int result;
+
+	spin_lock(&chip->reg_lock);	
+	result = snd_als4k_gcr_read(chip, ALS4K_GCRA4_FIFO2_CURRENT_ADDR);
+	spin_unlock(&chip->reg_lock);
+	result &= 0xffff;
+	return bytes_to_frames( substream->runtime, result );
+}
+
+static snd_pcm_uframes_t snd_als4000_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	unsigned result;
+
+	spin_lock(&chip->reg_lock);	
+	result = snd_als4k_gcr_read(chip, ALS4K_GCRA0_FIFO1_CURRENT_ADDR);
+	spin_unlock(&chip->reg_lock);
+	result &= 0xffff;
+	return bytes_to_frames( substream->runtime, result );
+}
+
+/* FIXME: this IRQ routine doesn't really support IRQ sharing (we always
+ * return IRQ_HANDLED no matter whether we actually had an IRQ flag or not).
+ * ALS4000a.PDF writes that while ACKing IRQ in PCI block will *not* ACK
+ * the IRQ in the SB core, ACKing IRQ in SB block *will* ACK the PCI IRQ
+ * register (alt_port + ALS4K_IOB_0E_IRQTYPE_SB_CR1E_MPU). Probably something
+ * could be optimized here to query/write one register only...
+ * And even if both registers need to be queried, then there's still the
+ * question of whether it's actually correct to ACK PCI IRQ before reading
+ * SB IRQ like we do now, since ALS4000a.PDF mentions that PCI IRQ will *clear*
+ * SB IRQ status.
+ * (hmm, SPECS_PAGE: 38 mentions it the other way around!)
+ * And do we *really* need the lock here for *reading* SB_DSP4_IRQSTATUS??
+ * */
+static irqreturn_t snd_als4000_interrupt(int irq, void *dev_id)
+{
+	struct snd_sb *chip = dev_id;
+	unsigned pci_irqstatus;
+	unsigned sb_irqstatus;
+
+	/* find out which bit of the ALS4000 PCI block produced the interrupt,
+	   SPECS_PAGE: 38, 5 */
+	pci_irqstatus = snd_als4k_iobase_readb(chip->alt_port,
+				 ALS4K_IOB_0E_IRQTYPE_SB_CR1E_MPU);
+	if ((pci_irqstatus & ALS4K_IOB_0E_SB_DMA_IRQ)
+	 && (chip->playback_substream)) /* playback */
+		snd_pcm_period_elapsed(chip->playback_substream);
+	if ((pci_irqstatus & ALS4K_IOB_0E_CR1E_IRQ)
+	 && (chip->capture_substream)) /* capturing */
+		snd_pcm_period_elapsed(chip->capture_substream);
+	if ((pci_irqstatus & ALS4K_IOB_0E_MPU_IRQ)
+	 && (chip->rmidi)) /* MPU401 interrupt */
+		snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+	/* ACK the PCI block IRQ */
+	snd_als4k_iobase_writeb(chip->alt_port,
+			 ALS4K_IOB_0E_IRQTYPE_SB_CR1E_MPU, pci_irqstatus);
+	
+	spin_lock(&chip->mixer_lock);
+	/* SPECS_PAGE: 20 */
+	sb_irqstatus = snd_sbmixer_read(chip, SB_DSP4_IRQSTATUS);
+	spin_unlock(&chip->mixer_lock);
+	
+	if (sb_irqstatus & SB_IRQTYPE_8BIT)
+		snd_sb_ack_8bit(chip);
+	if (sb_irqstatus & SB_IRQTYPE_16BIT)
+		snd_sb_ack_16bit(chip);
+	if (sb_irqstatus & SB_IRQTYPE_MPUIN)
+		inb(chip->mpu_port);
+	if (sb_irqstatus & ALS4K_IRQTYPE_CR1E_DMA)
+		snd_als4k_iobase_readb(chip->alt_port,
+					ALS4K_IOB_16_ACK_FOR_CR1E);
+
+	/* printk(KERN_INFO "als4000: irq 0x%04x 0x%04x\n",
+					 pci_irqstatus, sb_irqstatus); */
+
+	/* only ack the things we actually handled above */
+	return IRQ_RETVAL(
+	     (pci_irqstatus & (ALS4K_IOB_0E_SB_DMA_IRQ|ALS4K_IOB_0E_CR1E_IRQ|
+				ALS4K_IOB_0E_MPU_IRQ))
+	  || (sb_irqstatus & (SB_IRQTYPE_8BIT|SB_IRQTYPE_16BIT|
+				SB_IRQTYPE_MPUIN|ALS4K_IRQTYPE_CR1E_DMA))
+	);
+}
+
+/*****************************************************************/
+
+static struct snd_pcm_hardware snd_als4000_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+				SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE,	/* formats */
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0
+};
+
+static struct snd_pcm_hardware snd_als4000_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+				SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE,	/* formats */
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	64,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0
+};
+
+/*****************************************************************/
+
+static int snd_als4000_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	chip->playback_substream = substream;
+	runtime->hw = snd_als4000_playback;
+	return 0;
+}
+
+static int snd_als4000_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_als4000_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	chip->capture_substream = substream;
+	runtime->hw = snd_als4000_capture;
+	return 0;
+}
+
+static int snd_als4000_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_sb *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+/******************************************************************/
+
+static struct snd_pcm_ops snd_als4000_playback_ops = {
+	.open =		snd_als4000_playback_open,
+	.close =	snd_als4000_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_als4000_hw_params,
+	.hw_free =	snd_als4000_hw_free,
+	.prepare =	snd_als4000_playback_prepare,
+	.trigger =	snd_als4000_playback_trigger,
+	.pointer =	snd_als4000_playback_pointer
+};
+
+static struct snd_pcm_ops snd_als4000_capture_ops = {
+	.open =		snd_als4000_capture_open,
+	.close =	snd_als4000_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_als4000_hw_params,
+	.hw_free =	snd_als4000_hw_free,
+	.prepare =	snd_als4000_capture_prepare,
+	.trigger =	snd_als4000_capture_trigger,
+	.pointer =	snd_als4000_capture_pointer
+};
+
+static int __devinit snd_als4000_pcm(struct snd_sb *chip, int device)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(chip->card, "ALS4000 DSP", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+	pcm->private_data = chip;
+	pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_als4000_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_als4000_capture_ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+					      64*1024, 64*1024);
+
+	chip->pcm = pcm;
+
+	return 0;
+}
+
+/******************************************************************/
+
+static void snd_als4000_set_addr(unsigned long iobase,
+					unsigned int sb_io,
+					unsigned int mpu_io,
+					unsigned int opl_io,
+					unsigned int game_io)
+{
+	u32 cfg1 = 0;
+	u32 cfg2 = 0;
+
+	if (mpu_io > 0)
+		cfg2 |= (mpu_io | 1) << 16;
+	if (sb_io > 0)
+		cfg2 |= (sb_io | 1);
+	if (game_io > 0)
+		cfg1 |= (game_io | 1) << 16;
+	if (opl_io > 0)
+		cfg1 |= (opl_io | 1);
+	snd_als4k_gcr_write_addr(iobase, ALS4K_GCRA8_LEGACY_CFG1, cfg1);
+	snd_als4k_gcr_write_addr(iobase, ALS4K_GCRA9_LEGACY_CFG2, cfg2);
+}
+
+static void snd_als4000_configure(struct snd_sb *chip)
+{
+	u8 tmp;
+	int i;
+
+	/* do some more configuration */
+	spin_lock_irq(&chip->mixer_lock);
+	tmp = snd_als4_cr_read(chip, ALS4K_CR0_SB_CONFIG);
+	snd_als4_cr_write(chip, ALS4K_CR0_SB_CONFIG,
+				tmp|ALS4K_CR0_MX80_81_REG_WRITE_ENABLE);
+	/* always select DMA channel 0, since we do not actually use DMA
+	 * SPECS_PAGE: 19/20 */
+	snd_sbmixer_write(chip, SB_DSP4_DMASETUP, SB_DMASETUP_DMA0);
+	snd_als4_cr_write(chip, ALS4K_CR0_SB_CONFIG,
+				 tmp & ~ALS4K_CR0_MX80_81_REG_WRITE_ENABLE);
+	spin_unlock_irq(&chip->mixer_lock);
+	
+	spin_lock_irq(&chip->reg_lock);
+	/* enable interrupts */
+	snd_als4k_gcr_write(chip, ALS4K_GCR8C_MISC_CTRL,
+					ALS4K_GCR8C_IRQ_MASK_CTRL_ENABLE);
+
+	/* SPECS_PAGE: 39 */
+	for (i = ALS4K_GCR91_DMA0_ADDR; i <= ALS4K_GCR96_DMA3_MODE_COUNT; ++i)
+		snd_als4k_gcr_write(chip, i, 0);
+	/* enable burst mode to prevent dropouts during high PCI bus usage */
+	snd_als4k_gcr_write(chip, ALS4K_GCR99_DMA_EMULATION_CTRL,
+		(snd_als4k_gcr_read(chip, ALS4K_GCR99_DMA_EMULATION_CTRL) & ~0x07) | 0x04);
+	spin_unlock_irq(&chip->reg_lock);
+}
+
+#ifdef SUPPORT_JOYSTICK
+static int __devinit snd_als4000_create_gameport(struct snd_card_als4000 *acard, int dev)
+{
+	struct gameport *gp;
+	struct resource *r;
+	int io_port;
+
+	if (joystick_port[dev] == 0)
+		return -ENODEV;
+
+	if (joystick_port[dev] == 1) { /* auto-detect */
+		for (io_port = 0x200; io_port <= 0x218; io_port += 8) {
+			r = request_region(io_port, 8, "ALS4000 gameport");
+			if (r)
+				break;
+		}
+	} else {
+		io_port = joystick_port[dev];
+		r = request_region(io_port, 8, "ALS4000 gameport");
+	}
+
+	if (!r) {
+		printk(KERN_WARNING "als4000: cannot reserve joystick ports\n");
+		return -EBUSY;
+	}
+
+	acard->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "als4000: cannot allocate memory for gameport\n");
+		release_and_free_resource(r);
+		return -ENOMEM;
+	}
+
+	gameport_set_name(gp, "ALS4000 Gameport");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(acard->pci));
+	gameport_set_dev_parent(gp, &acard->pci->dev);
+	gp->io = io_port;
+	gameport_set_port_data(gp, r);
+
+	/* Enable legacy joystick port */
+	snd_als4000_set_addr(acard->iobase, 0, 0, 0, 1);
+
+	gameport_register_port(acard->gameport);
+
+	return 0;
+}
+
+static void snd_als4000_free_gameport(struct snd_card_als4000 *acard)
+{
+	if (acard->gameport) {
+		struct resource *r = gameport_get_port_data(acard->gameport);
+
+		gameport_unregister_port(acard->gameport);
+		acard->gameport = NULL;
+
+		/* disable joystick */
+		snd_als4000_set_addr(acard->iobase, 0, 0, 0, 0);
+
+		release_and_free_resource(r);
+	}
+}
+#else
+static inline int snd_als4000_create_gameport(struct snd_card_als4000 *acard, int dev) { return -ENOSYS; }
+static inline void snd_als4000_free_gameport(struct snd_card_als4000 *acard) { }
+#endif
+
+static void snd_card_als4000_free( struct snd_card *card )
+{
+	struct snd_card_als4000 *acard = card->private_data;
+
+	/* make sure that interrupts are disabled */
+	snd_als4k_gcr_write_addr(acard->iobase, ALS4K_GCR8C_MISC_CTRL, 0);
+	/* free resources */
+	snd_als4000_free_gameport(acard);
+	pci_release_regions(acard->pci);
+	pci_disable_device(acard->pci);
+}
+
+static int __devinit snd_card_als4000_probe(struct pci_dev *pci,
+					  const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_card_als4000 *acard;
+	unsigned long iobase;
+	struct snd_sb *chip;
+	struct snd_opl3 *opl3;
+	unsigned short word;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	/* enable PCI device */
+	if ((err = pci_enable_device(pci)) < 0) {
+		return err;
+	}
+	/* check, if we can restrict PCI DMA transfers to 24 bits */
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(24)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(24)) < 0) {
+		snd_printk(KERN_ERR "architecture does not support 24bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	if ((err = pci_request_regions(pci, "ALS4000")) < 0) {
+		pci_disable_device(pci);
+		return err;
+	}
+	iobase = pci_resource_start(pci, 0);
+
+	pci_read_config_word(pci, PCI_COMMAND, &word);
+	pci_write_config_word(pci, PCI_COMMAND, word | PCI_COMMAND_IO);
+	pci_set_master(pci);
+	
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 
+			      sizeof(*acard) /* private_data: acard */,
+			      &card);
+	if (err < 0) {
+		pci_release_regions(pci);
+		pci_disable_device(pci);
+		return err;
+	}
+
+	acard = card->private_data;
+	acard->pci = pci;
+	acard->iobase = iobase;
+	card->private_free = snd_card_als4000_free;
+
+	/* disable all legacy ISA stuff */
+	snd_als4000_set_addr(acard->iobase, 0, 0, 0, 0);
+
+	if ((err = snd_sbdsp_create(card,
+				    iobase + ALS4K_IOB_10_ADLIB_ADDR0,
+				    pci->irq,
+		/* internally registered as IRQF_SHARED in case of ALS4000 SB */
+				    snd_als4000_interrupt,
+				    -1,
+				    -1,
+				    SB_HW_ALS4000,
+				    &chip)) < 0) {
+		goto out_err;
+	}
+	acard->chip = chip;
+
+	chip->pci = pci;
+	chip->alt_port = iobase;
+	snd_card_set_dev(card, &pci->dev);
+
+	snd_als4000_configure(chip);
+
+	strcpy(card->driver, "ALS4000");
+	strcpy(card->shortname, "Avance Logic ALS4000");
+	sprintf(card->longname, "%s at 0x%lx, irq %i",
+		card->shortname, chip->alt_port, chip->irq);
+
+	if ((err = snd_mpu401_uart_new( card, 0, MPU401_HW_ALS4000,
+					iobase + ALS4K_IOB_30_MIDI_DATA,
+					MPU401_INFO_INTEGRATED,
+					pci->irq, 0, &chip->rmidi)) < 0) {
+		printk(KERN_ERR "als4000: no MPU-401 device at 0x%lx?\n",
+				iobase + ALS4K_IOB_30_MIDI_DATA);
+		goto out_err;
+	}
+	/* FIXME: ALS4000 has interesting MPU401 configuration features
+	 * at ALS4K_CR1A_MPU401_UART_MODE_CONTROL
+	 * (pass-thru / UART switching, fast MIDI clock, etc.),
+	 * however there doesn't seem to be an ALSA API for this...
+	 * SPECS_PAGE: 21 */
+
+	if ((err = snd_als4000_pcm(chip, 0)) < 0) {
+		goto out_err;
+	}
+	if ((err = snd_sbmixer_new(chip)) < 0) {
+		goto out_err;
+	}	    
+
+	if (snd_opl3_create(card,
+				iobase + ALS4K_IOB_10_ADLIB_ADDR0,
+				iobase + ALS4K_IOB_12_ADLIB_ADDR2,
+			    OPL3_HW_AUTO, 1, &opl3) < 0) {
+		printk(KERN_ERR "als4000: no OPL device at 0x%lx-0x%lx?\n",
+			   iobase + ALS4K_IOB_10_ADLIB_ADDR0,
+			   iobase + ALS4K_IOB_12_ADLIB_ADDR2);
+	} else {
+		if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+			goto out_err;
+		}
+	}
+
+	snd_als4000_create_gameport(acard, dev);
+
+	if ((err = snd_card_register(card)) < 0) {
+		goto out_err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	err = 0;
+	goto out;
+
+out_err:
+	snd_card_free(card);
+	
+out:
+	return err;
+}
+
+static void __devexit snd_card_als4000_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_als4000_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_card_als4000 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	
+	snd_pcm_suspend_all(chip->pcm);
+	snd_sbmixer_suspend(chip);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_als4000_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_card_als4000 *acard = card->private_data;
+	struct snd_sb *chip = acard->chip;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "als4000: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_als4000_configure(chip);
+	snd_sbdsp_reset(chip);
+	snd_sbmixer_resume(chip);
+
+#ifdef SUPPORT_JOYSTICK
+	if (acard->gameport)
+		snd_als4000_set_addr(acard->iobase, 0, 0, 0, 1);
+#endif
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+
+static struct pci_driver driver = {
+	.name = "ALS4000",
+	.id_table = snd_als4000_ids,
+	.probe = snd_card_als4000_probe,
+	.remove = __devexit_p(snd_card_als4000_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_als4000_suspend,
+	.resume = snd_als4000_resume,
+#endif
+};
+
+static int __init alsa_card_als4000_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_als4000_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_als4000_init)
+module_exit(alsa_card_als4000_exit)
diff --git a/linux/sound/pci/asihpi/Makefile b/linux/sound/pci/asihpi/Makefile
new file mode 100644
index 0000000..391830a
--- /dev/null
+++ b/linux/sound/pci/asihpi/Makefile
@@ -0,0 +1,5 @@
+snd-asihpi-objs := asihpi.o hpioctl.o hpimsginit.o\
+	hpicmn.o hpifunc.o hpidebug.o hpidspcd.o\
+	hpios.o hpi6000.o hpi6205.o hpimsgx.o
+
+obj-$(CONFIG_SND_ASIHPI) += snd-asihpi.o
diff --git a/linux/sound/pci/asihpi/asihpi.c b/linux/sound/pci/asihpi/asihpi.c
new file mode 100644
index 0000000..c80b0b8
--- /dev/null
+++ b/linux/sound/pci/asihpi/asihpi.c
@@ -0,0 +1,3007 @@
+/*
+ *  Asihpi soundcard
+ *  Copyright (c) by AudioScience Inc <alsa@audioscience.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of version 2 of the GNU General Public License as
+ *   published by the Free Software Foundation;
+ *
+ *   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
+ *
+ *
+ *  The following is not a condition of use, merely a request:
+ *  If you modify this program, particularly if you fix errors, AudioScience Inc
+ *  would appreciate it if you grant us the right to use those modifications
+ *  for any purpose including commercial applications.
+ */
+/* >0: print Hw params, timer vars. >1: print stream write/copy sizes  */
+#define REALLY_VERBOSE_LOGGING 0
+
+#if REALLY_VERBOSE_LOGGING
+#define VPRINTK1 snd_printd
+#else
+#define VPRINTK1(...)
+#endif
+
+#if REALLY_VERBOSE_LOGGING > 1
+#define VPRINTK2 snd_printd
+#else
+#define VPRINTK2(...)
+#endif
+
+#ifndef ASI_STYLE_NAMES
+/* not sure how ALSA style name should look */
+#define ASI_STYLE_NAMES 1
+#endif
+
+#include "hpi_internal.h"
+#include "hpimsginit.h"
+#include "hpioctl.h"
+
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/hwdep.h>
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("AudioScience inc. <support@audioscience.com>");
+MODULE_DESCRIPTION("AudioScience ALSA ASI5000 ASI6000 ASI87xx ASI89xx");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+static int enable_hpi_hwdep = 1;
+
+module_param_array(index, int, NULL, S_IRUGO);
+MODULE_PARM_DESC(index, "ALSA index value for AudioScience soundcard.");
+
+module_param_array(id, charp, NULL, S_IRUGO);
+MODULE_PARM_DESC(id, "ALSA ID string for AudioScience soundcard.");
+
+module_param_array(enable, bool, NULL, S_IRUGO);
+MODULE_PARM_DESC(enable, "ALSA enable AudioScience soundcard.");
+
+module_param(enable_hpi_hwdep, bool, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(enable_hpi_hwdep,
+		"ALSA enable HPI hwdep for AudioScience soundcard ");
+
+/* identify driver */
+#ifdef KERNEL_ALSA_BUILD
+static char *build_info = "built using headers from kernel source";
+module_param(build_info, charp, S_IRUGO);
+MODULE_PARM_DESC(build_info, "built using headers from kernel source");
+#else
+static char *build_info = "built within ALSA source";
+module_param(build_info, charp, S_IRUGO);
+MODULE_PARM_DESC(build_info, "built within ALSA source");
+#endif
+
+/* set to 1 to dump every control from adapter to log */
+static const int mixer_dump;
+
+#define DEFAULT_SAMPLERATE 44100
+static int adapter_fs = DEFAULT_SAMPLERATE;
+
+static struct hpi_hsubsys *ss;	/* handle to HPI audio subsystem */
+
+/* defaults */
+#define PERIODS_MIN 2
+#define PERIOD_BYTES_MIN  2304
+#define BUFFER_BYTES_MAX (512 * 1024)
+
+/*#define TIMER_MILLISECONDS 20
+#define FORCE_TIMER_JIFFIES ((TIMER_MILLISECONDS * HZ + 999)/1000)
+*/
+
+#define MAX_CLOCKSOURCES (HPI_SAMPLECLOCK_SOURCE_LAST + 1 + 7)
+
+struct clk_source {
+	int source;
+	int index;
+	char *name;
+};
+
+struct clk_cache {
+	int count;
+	int has_local;
+	struct clk_source s[MAX_CLOCKSOURCES];
+};
+
+/* Per card data */
+struct snd_card_asihpi {
+	struct snd_card *card;
+	struct pci_dev *pci;
+	u16 adapter_index;
+	u32 serial_number;
+	u16 type;
+	u16 version;
+	u16 num_outstreams;
+	u16 num_instreams;
+
+	u32 h_mixer;
+	struct clk_cache cc;
+
+	u16 support_mmap;
+	u16 support_grouping;
+	u16 support_mrx;
+	u16 update_interval_frames;
+	u16 in_max_chans;
+	u16 out_max_chans;
+};
+
+/* Per stream data */
+struct snd_card_asihpi_pcm {
+	struct timer_list timer;
+	unsigned int respawn_timer;
+	unsigned int hpi_buffer_attached;
+	unsigned int pcm_size;
+	unsigned int pcm_count;
+	unsigned int bytes_per_sec;
+	unsigned int pcm_irq_pos;	/* IRQ position */
+	unsigned int pcm_buf_pos;	/* position in buffer */
+	struct snd_pcm_substream *substream;
+	u32 h_stream;
+	struct hpi_format format;
+};
+
+/* universal stream verbs work with out or in stream handles */
+
+/* Functions to allow driver to give a buffer to HPI for busmastering */
+
+static u16 hpi_stream_host_buffer_attach(
+	struct hpi_hsubsys *hS,
+	u32 h_stream,   /* handle to outstream. */
+	u32 size_in_bytes, /* size in bytes of bus mastering buffer */
+	u32 pci_address
+)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	unsigned int obj = hpi_handle_object(h_stream);
+
+	if (!h_stream)
+		return HPI_ERROR_INVALID_OBJ;
+	hpi_init_message_response(&hm, &hr, obj,
+			obj == HPI_OBJ_OSTREAM ?
+				HPI_OSTREAM_HOSTBUFFER_ALLOC :
+				HPI_ISTREAM_HOSTBUFFER_ALLOC);
+
+	hpi_handle_to_indexes(h_stream, &hm.adapter_index,
+				&hm.obj_index);
+
+	hm.u.d.u.buffer.buffer_size = size_in_bytes;
+	hm.u.d.u.buffer.pci_address = pci_address;
+	hm.u.d.u.buffer.command = HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+static u16 hpi_stream_host_buffer_detach(
+	struct hpi_hsubsys *hS,
+	u32  h_stream
+)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	unsigned int obj = hpi_handle_object(h_stream);
+
+	if (!h_stream)
+		return HPI_ERROR_INVALID_OBJ;
+
+	hpi_init_message_response(&hm, &hr,  obj,
+			obj == HPI_OBJ_OSTREAM ?
+				HPI_OSTREAM_HOSTBUFFER_FREE :
+				HPI_ISTREAM_HOSTBUFFER_FREE);
+
+	hpi_handle_to_indexes(h_stream, &hm.adapter_index,
+				&hm.obj_index);
+	hm.u.d.u.buffer.command = HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+static inline u16 hpi_stream_start(struct hpi_hsubsys *hS, u32 h_stream)
+{
+	if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM)
+		return hpi_outstream_start(hS, h_stream);
+	else
+		return hpi_instream_start(hS, h_stream);
+}
+
+static inline u16 hpi_stream_stop(struct hpi_hsubsys *hS, u32 h_stream)
+{
+	if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM)
+		return hpi_outstream_stop(hS, h_stream);
+	else
+		return hpi_instream_stop(hS, h_stream);
+}
+
+static inline u16 hpi_stream_get_info_ex(
+    struct hpi_hsubsys *hS,
+    u32 h_stream,
+    u16        *pw_state,
+    u32        *pbuffer_size,
+    u32        *pdata_in_buffer,
+    u32        *psample_count,
+    u32        *pauxiliary_data
+)
+{
+	if (hpi_handle_object(h_stream)  ==  HPI_OBJ_OSTREAM)
+		return hpi_outstream_get_info_ex(hS, h_stream, pw_state,
+					pbuffer_size, pdata_in_buffer,
+					psample_count, pauxiliary_data);
+	else
+		return hpi_instream_get_info_ex(hS, h_stream, pw_state,
+					pbuffer_size, pdata_in_buffer,
+					psample_count, pauxiliary_data);
+}
+
+static inline u16 hpi_stream_group_add(struct hpi_hsubsys *hS,
+					u32 h_master,
+					u32 h_stream)
+{
+	if (hpi_handle_object(h_master) ==  HPI_OBJ_OSTREAM)
+		return hpi_outstream_group_add(hS, h_master, h_stream);
+	else
+		return hpi_instream_group_add(hS, h_master, h_stream);
+}
+
+static inline u16 hpi_stream_group_reset(struct hpi_hsubsys *hS,
+						u32 h_stream)
+{
+	if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM)
+		return hpi_outstream_group_reset(hS, h_stream);
+	else
+		return hpi_instream_group_reset(hS, h_stream);
+}
+
+static inline u16 hpi_stream_group_get_map(struct hpi_hsubsys *hS,
+				u32 h_stream, u32 *mo, u32 *mi)
+{
+	if (hpi_handle_object(h_stream) ==  HPI_OBJ_OSTREAM)
+		return hpi_outstream_group_get_map(hS, h_stream, mo, mi);
+	else
+		return hpi_instream_group_get_map(hS, h_stream, mo, mi);
+}
+
+static u16 handle_error(u16 err, int line, char *filename)
+{
+	if (err)
+		printk(KERN_WARNING
+			"in file %s, line %d: HPI error %d\n",
+			filename, line, err);
+	return err;
+}
+
+#define hpi_handle_error(x)  handle_error(x, __LINE__, __FILE__)
+
+/***************************** GENERAL PCM ****************/
+#if REALLY_VERBOSE_LOGGING
+static void print_hwparams(struct snd_pcm_hw_params *p)
+{
+	snd_printd("HWPARAMS \n");
+	snd_printd("samplerate %d \n", params_rate(p));
+	snd_printd("channels %d \n", params_channels(p));
+	snd_printd("format %d \n", params_format(p));
+	snd_printd("subformat %d \n", params_subformat(p));
+	snd_printd("buffer bytes %d \n", params_buffer_bytes(p));
+	snd_printd("period bytes %d \n", params_period_bytes(p));
+	snd_printd("access %d \n", params_access(p));
+	snd_printd("period_size %d \n", params_period_size(p));
+	snd_printd("periods %d \n", params_periods(p));
+	snd_printd("buffer_size %d \n", params_buffer_size(p));
+}
+#else
+#define print_hwparams(x)
+#endif
+
+static snd_pcm_format_t hpi_to_alsa_formats[] = {
+	-1,			/* INVALID */
+	SNDRV_PCM_FORMAT_U8,	/* HPI_FORMAT_PCM8_UNSIGNED        1 */
+	SNDRV_PCM_FORMAT_S16,	/* HPI_FORMAT_PCM16_SIGNED         2 */
+	-1,			/* HPI_FORMAT_MPEG_L1              3 */
+	SNDRV_PCM_FORMAT_MPEG,	/* HPI_FORMAT_MPEG_L2              4 */
+	SNDRV_PCM_FORMAT_MPEG,	/* HPI_FORMAT_MPEG_L3              5 */
+	-1,			/* HPI_FORMAT_DOLBY_AC2            6 */
+	-1,			/* HPI_FORMAT_DOLBY_AC3            7 */
+	SNDRV_PCM_FORMAT_S16_BE,/* HPI_FORMAT_PCM16_BIGENDIAN      8 */
+	-1,			/* HPI_FORMAT_AA_TAGIT1_HITS       9 */
+	-1,			/* HPI_FORMAT_AA_TAGIT1_INSERTS   10 */
+	SNDRV_PCM_FORMAT_S32,	/* HPI_FORMAT_PCM32_SIGNED        11 */
+	-1,			/* HPI_FORMAT_RAW_BITSTREAM       12 */
+	-1,			/* HPI_FORMAT_AA_TAGIT1_HITS_EX1  13 */
+	SNDRV_PCM_FORMAT_FLOAT,	/* HPI_FORMAT_PCM32_FLOAT         14 */
+#if 1
+	/* ALSA can't handle 3 byte sample size together with power-of-2
+	 *  constraint on buffer_bytes, so disable this format
+	 */
+	-1
+#else
+	/* SNDRV_PCM_FORMAT_S24_3LE */	/* { HPI_FORMAT_PCM24_SIGNED        15 */
+#endif
+};
+
+
+static int snd_card_asihpi_format_alsa2hpi(snd_pcm_format_t alsa_format,
+					   u16 *hpi_format)
+{
+	u16 format;
+
+	for (format = HPI_FORMAT_PCM8_UNSIGNED;
+	     format <= HPI_FORMAT_PCM24_SIGNED; format++) {
+		if (hpi_to_alsa_formats[format] == alsa_format) {
+			*hpi_format = format;
+			return 0;
+		}
+	}
+
+	snd_printd(KERN_WARNING "failed match for alsa format %d\n",
+		   alsa_format);
+	*hpi_format = 0;
+	return -EINVAL;
+}
+
+static void snd_card_asihpi_pcm_samplerates(struct snd_card_asihpi *asihpi,
+					 struct snd_pcm_hardware *pcmhw)
+{
+	u16 err;
+	u32 h_control;
+	u32 sample_rate;
+	int idx;
+	unsigned int rate_min = 200000;
+	unsigned int rate_max = 0;
+	unsigned int rates = 0;
+
+	if (asihpi->support_mrx) {
+		rates |= SNDRV_PCM_RATE_CONTINUOUS;
+		rates |= SNDRV_PCM_RATE_8000_96000;
+		rate_min = 8000;
+		rate_max = 100000;
+	} else {
+		/* on cards without SRC,
+		   valid rates are determined by sampleclock */
+		err = hpi_mixer_get_control(ss, asihpi->h_mixer,
+					  HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
+					  HPI_CONTROL_SAMPLECLOCK, &h_control);
+		if (err) {
+			snd_printk(KERN_ERR
+				"no local sampleclock, err %d\n", err);
+		}
+
+		for (idx = 0; idx < 100; idx++) {
+			if (hpi_sample_clock_query_local_rate(ss,
+				h_control, idx, &sample_rate)) {
+				if (!idx)
+					snd_printk(KERN_ERR
+						"local rate query failed\n");
+
+				break;
+			}
+
+			rate_min = min(rate_min, sample_rate);
+			rate_max = max(rate_max, sample_rate);
+
+			switch (sample_rate) {
+			case 5512:
+				rates |= SNDRV_PCM_RATE_5512;
+				break;
+			case 8000:
+				rates |= SNDRV_PCM_RATE_8000;
+				break;
+			case 11025:
+				rates |= SNDRV_PCM_RATE_11025;
+				break;
+			case 16000:
+				rates |= SNDRV_PCM_RATE_16000;
+				break;
+			case 22050:
+				rates |= SNDRV_PCM_RATE_22050;
+				break;
+			case 32000:
+				rates |= SNDRV_PCM_RATE_32000;
+				break;
+			case 44100:
+				rates |= SNDRV_PCM_RATE_44100;
+				break;
+			case 48000:
+				rates |= SNDRV_PCM_RATE_48000;
+				break;
+			case 64000:
+				rates |= SNDRV_PCM_RATE_64000;
+				break;
+			case 88200:
+				rates |= SNDRV_PCM_RATE_88200;
+				break;
+			case 96000:
+				rates |= SNDRV_PCM_RATE_96000;
+				break;
+			case 176400:
+				rates |= SNDRV_PCM_RATE_176400;
+				break;
+			case 192000:
+				rates |= SNDRV_PCM_RATE_192000;
+				break;
+			default: /* some other rate */
+				rates |= SNDRV_PCM_RATE_KNOT;
+			}
+		}
+	}
+
+	/* printk(KERN_INFO "Supported rates %X %d %d\n",
+	   rates, rate_min, rate_max); */
+	pcmhw->rates = rates;
+	pcmhw->rate_min = rate_min;
+	pcmhw->rate_max = rate_max;
+}
+
+static int snd_card_asihpi_pcm_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+	struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
+	int err;
+	u16 format;
+	int width;
+	unsigned int bytes_per_sec;
+
+	print_hwparams(params);
+	err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+	if (err < 0)
+		return err;
+	err = snd_card_asihpi_format_alsa2hpi(params_format(params), &format);
+	if (err)
+		return err;
+
+	VPRINTK1(KERN_INFO "format %d, %d chans, %d_hz\n",
+				format, params_channels(params),
+				params_rate(params));
+
+	hpi_handle_error(hpi_format_create(&dpcm->format,
+			params_channels(params),
+			format, params_rate(params), 0, 0));
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		if (hpi_instream_reset(ss, dpcm->h_stream) != 0)
+			return -EINVAL;
+
+		if (hpi_instream_set_format(ss,
+			dpcm->h_stream, &dpcm->format) != 0)
+			return -EINVAL;
+	}
+
+	dpcm->hpi_buffer_attached = 0;
+	if (card->support_mmap) {
+
+		err = hpi_stream_host_buffer_attach(ss, dpcm->h_stream,
+			params_buffer_bytes(params),  runtime->dma_addr);
+		if (err == 0) {
+			snd_printd(KERN_INFO
+				"stream_host_buffer_attach succeeded %u %lu\n",
+				params_buffer_bytes(params),
+				(unsigned long)runtime->dma_addr);
+		} else {
+			snd_printd(KERN_INFO
+					"stream_host_buffer_attach error %d\n",
+					err);
+			return -ENOMEM;
+		}
+
+		err = hpi_stream_get_info_ex(ss, dpcm->h_stream, NULL,
+						&dpcm->hpi_buffer_attached,
+						NULL, NULL, NULL);
+
+		snd_printd(KERN_INFO "stream_host_buffer_attach status 0x%x\n",
+				dpcm->hpi_buffer_attached);
+	}
+	bytes_per_sec = params_rate(params) * params_channels(params);
+	width = snd_pcm_format_width(params_format(params));
+	bytes_per_sec *= width;
+	bytes_per_sec /= 8;
+	if (width < 0 || bytes_per_sec == 0)
+		return -EINVAL;
+
+	dpcm->bytes_per_sec = bytes_per_sec;
+	dpcm->pcm_size = params_buffer_bytes(params);
+	dpcm->pcm_count = params_period_bytes(params);
+	snd_printd(KERN_INFO "pcm_size=%d, pcm_count=%d, bps=%d\n",
+			dpcm->pcm_size, dpcm->pcm_count, bytes_per_sec);
+
+	dpcm->pcm_irq_pos = 0;
+	dpcm->pcm_buf_pos = 0;
+	return 0;
+}
+
+static void snd_card_asihpi_pcm_timer_start(struct snd_pcm_substream *
+					    substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+	int expiry;
+
+	expiry = (dpcm->pcm_count * HZ / dpcm->bytes_per_sec);
+	/* wait longer the first time, for samples to propagate */
+	expiry = max(expiry, 20);
+	dpcm->timer.expires = jiffies + expiry;
+	dpcm->respawn_timer = 1;
+	add_timer(&dpcm->timer);
+}
+
+static void snd_card_asihpi_pcm_timer_stop(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+
+	dpcm->respawn_timer = 0;
+	del_timer(&dpcm->timer);
+}
+
+static int snd_card_asihpi_trigger(struct snd_pcm_substream *substream,
+					   int cmd)
+{
+	struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data;
+	struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *s;
+	u16 e;
+
+	snd_printd("trigger %dstream %d\n",
+			substream->stream, substream->number);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_pcm_group_for_each_entry(s, substream) {
+			struct snd_card_asihpi_pcm *ds;
+			ds = s->runtime->private_data;
+
+			if (snd_pcm_substream_chip(s) != card)
+				continue;
+
+			if ((s->stream == SNDRV_PCM_STREAM_PLAYBACK) &&
+				(card->support_mmap)) {
+				/* How do I know how much valid data is present
+				* in buffer? Just guessing 2 periods, but if
+				* buffer is bigger it may contain even more
+				* data??
+				*/
+				unsigned int preload = ds->pcm_count * 2;
+				VPRINTK2("preload %d\n", preload);
+				hpi_handle_error(hpi_outstream_write_buf(
+						ss, ds->h_stream,
+						&s->runtime->dma_area[0],
+						preload,
+						&ds->format));
+			}
+
+			if (card->support_grouping) {
+				VPRINTK1("\t_group %dstream %d\n", s->stream,
+						s->number);
+				e = hpi_stream_group_add(ss,
+					dpcm->h_stream,
+					ds->h_stream);
+				if (!e) {
+					snd_pcm_trigger_done(s, substream);
+				} else {
+					hpi_handle_error(e);
+					break;
+				}
+			} else
+				break;
+		}
+		snd_printd("start\n");
+		/* start the master stream */
+		snd_card_asihpi_pcm_timer_start(substream);
+		hpi_handle_error(hpi_stream_start(ss, dpcm->h_stream));
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_card_asihpi_pcm_timer_stop(substream);
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (snd_pcm_substream_chip(s) != card)
+				continue;
+
+			/*? workaround linked streams don't
+			transition to SETUP 20070706*/
+			s->runtime->status->state = SNDRV_PCM_STATE_SETUP;
+
+			if (card->support_grouping) {
+				VPRINTK1("\t_group %dstream %d\n", s->stream,
+					s->number);
+				snd_pcm_trigger_done(s, substream);
+			} else
+				break;
+		}
+		snd_printd("stop\n");
+
+		/* _prepare and _hwparams reset the stream */
+		hpi_handle_error(hpi_stream_stop(ss, dpcm->h_stream));
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			hpi_handle_error(
+				hpi_outstream_reset(ss, dpcm->h_stream));
+
+		if (card->support_grouping)
+			hpi_handle_error(hpi_stream_group_reset(ss,
+						dpcm->h_stream));
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		snd_printd("pause release\n");
+		hpi_handle_error(hpi_stream_start(ss, dpcm->h_stream));
+		snd_card_asihpi_pcm_timer_start(substream);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		snd_printd("pause\n");
+		snd_card_asihpi_pcm_timer_stop(substream);
+		hpi_handle_error(hpi_stream_stop(ss, dpcm->h_stream));
+		break;
+	default:
+		snd_printd("\tINVALID\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int
+snd_card_asihpi_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+	if (dpcm->hpi_buffer_attached)
+		hpi_stream_host_buffer_detach(ss, dpcm->h_stream);
+
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static void snd_card_asihpi_runtime_free(struct snd_pcm_runtime *runtime)
+{
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+	kfree(dpcm);
+}
+
+/*algorithm outline
+ Without linking degenerates to getting single stream pos etc
+ Without mmap 2nd loop degenerates to snd_pcm_period_elapsed
+*/
+/*
+buf_pos=get_buf_pos(s);
+for_each_linked_stream(s) {
+	buf_pos=get_buf_pos(s);
+	min_buf_pos = modulo_min(min_buf_pos, buf_pos, pcm_size)
+	new_data = min(new_data, calc_new_data(buf_pos,irq_pos)
+}
+timer.expires = jiffies + predict_next_period_ready(min_buf_pos);
+for_each_linked_stream(s) {
+	s->buf_pos = min_buf_pos;
+	if (new_data > pcm_count) {
+		if (mmap) {
+			irq_pos = (irq_pos + pcm_count) % pcm_size;
+			if (playback) {
+				write(pcm_count);
+			} else {
+				read(pcm_count);
+			}
+		}
+		snd_pcm_period_elapsed(s);
+	}
+}
+*/
+
+/** Minimum of 2 modulo values.  Works correctly when the difference between
+* the values is less than half the modulus
+*/
+static inline unsigned int modulo_min(unsigned int a, unsigned int b,
+					unsigned long int modulus)
+{
+	unsigned int result;
+	if (((a-b) % modulus) < (modulus/2))
+		result = b;
+	else
+		result = a;
+
+	return result;
+}
+
+/** Timer function, equivalent to interrupt service routine for cards
+*/
+static void snd_card_asihpi_timer_function(unsigned long data)
+{
+	struct snd_card_asihpi_pcm *dpcm = (struct snd_card_asihpi_pcm *)data;
+	struct snd_card_asihpi *card = snd_pcm_substream_chip(dpcm->substream);
+	struct snd_pcm_runtime *runtime;
+	struct snd_pcm_substream *s;
+	unsigned int newdata = 0;
+	unsigned int buf_pos, min_buf_pos = 0;
+	unsigned int remdata, xfercount, next_jiffies;
+	int first = 1;
+	u16 state;
+	u32 buffer_size, data_avail, samples_played, aux;
+
+	/* find minimum newdata and buffer pos in group */
+	snd_pcm_group_for_each_entry(s, dpcm->substream) {
+		struct snd_card_asihpi_pcm *ds = s->runtime->private_data;
+		runtime = s->runtime;
+
+		if (snd_pcm_substream_chip(s) != card)
+			continue;
+
+		hpi_handle_error(hpi_stream_get_info_ex(ss,
+					ds->h_stream, &state,
+					&buffer_size, &data_avail,
+					&samples_played, &aux));
+
+		/* number of bytes in on-card buffer */
+		runtime->delay = aux;
+
+		if (state == HPI_STATE_DRAINED) {
+			snd_printd(KERN_WARNING  "outstream %d drained\n",
+					s->number);
+			snd_pcm_stop(s, SNDRV_PCM_STATE_XRUN);
+			return;
+		}
+
+		if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			buf_pos = frames_to_bytes(runtime, samples_played);
+		} else {
+			buf_pos = data_avail + ds->pcm_irq_pos;
+		}
+
+		if (first) {
+			/* can't statically init min when wrap is involved */
+			min_buf_pos = buf_pos;
+			newdata = (buf_pos - ds->pcm_irq_pos) % ds->pcm_size;
+			first = 0;
+		} else {
+			min_buf_pos =
+				modulo_min(min_buf_pos, buf_pos, UINT_MAX+1L);
+			newdata = min(
+				(buf_pos - ds->pcm_irq_pos) % ds->pcm_size,
+				newdata);
+		}
+
+		VPRINTK1("PB timer hw_ptr x%04lX, appl_ptr x%04lX\n",
+			(unsigned long)frames_to_bytes(runtime,
+						runtime->status->hw_ptr),
+			(unsigned long)frames_to_bytes(runtime,
+						runtime->control->appl_ptr));
+		VPRINTK1("%d S=%d, irq=%04X, pos=x%04X, left=x%04X,"
+			" aux=x%04X space=x%04X\n", s->number,
+			state,	ds->pcm_irq_pos, buf_pos, (int)data_avail,
+			(int)aux, buffer_size-data_avail);
+	}
+
+	remdata = newdata % dpcm->pcm_count;
+	xfercount = newdata - remdata; /* a multiple of pcm_count */
+	next_jiffies = ((dpcm->pcm_count-remdata) * HZ / dpcm->bytes_per_sec)+1;
+	next_jiffies = max(next_jiffies, 2U * HZ / 1000U);
+	dpcm->timer.expires = jiffies + next_jiffies;
+	VPRINTK1("jif %d buf pos x%04X newdata x%04X xc x%04X\n",
+			next_jiffies, min_buf_pos, newdata, xfercount);
+
+	snd_pcm_group_for_each_entry(s, dpcm->substream) {
+		struct snd_card_asihpi_pcm *ds = s->runtime->private_data;
+		ds->pcm_buf_pos = min_buf_pos;
+
+		if (xfercount) {
+			if (card->support_mmap) {
+				ds->pcm_irq_pos = ds->pcm_irq_pos + xfercount;
+				if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+					VPRINTK2("write OS%d x%04x\n",
+							s->number,
+							ds->pcm_count);
+					hpi_handle_error(
+						hpi_outstream_write_buf(
+							ss, ds->h_stream,
+							&s->runtime->
+								dma_area[0],
+							xfercount,
+							&ds->format));
+				} else {
+					VPRINTK2("read IS%d x%04x\n",
+						s->number,
+						dpcm->pcm_count);
+					hpi_handle_error(
+						hpi_instream_read_buf(
+							ss, ds->h_stream,
+							NULL, xfercount));
+				}
+			} /* else R/W will be handled by read/write callbacks */
+			snd_pcm_period_elapsed(s);
+		}
+	}
+
+	if (dpcm->respawn_timer)
+		add_timer(&dpcm->timer);
+}
+
+/***************************** PLAYBACK OPS ****************/
+static int snd_card_asihpi_playback_ioctl(struct snd_pcm_substream *substream,
+					  unsigned int cmd, void *arg)
+{
+	/* snd_printd(KERN_INFO "Playback ioctl %d\n", cmd); */
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static int snd_card_asihpi_playback_prepare(struct snd_pcm_substream *
+					    substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+
+	snd_printd(KERN_INFO "playback prepare %d\n", substream->number);
+
+	hpi_handle_error(hpi_outstream_reset(ss, dpcm->h_stream));
+	dpcm->pcm_irq_pos = 0;
+	dpcm->pcm_buf_pos = 0;
+
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_card_asihpi_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+	snd_pcm_uframes_t ptr;
+
+	u32 samples_played;
+	u16 err;
+
+	if (!snd_pcm_stream_linked(substream)) {
+		/* NOTE, can use samples played for playback position here and
+		* in timer fn because it LAGS the actual read pointer, and is a
+		* better representation of actual playout position
+		*/
+		err = hpi_outstream_get_info_ex(ss, dpcm->h_stream, NULL,
+					NULL, NULL,
+					&samples_played, NULL);
+		hpi_handle_error(err);
+
+		dpcm->pcm_buf_pos = frames_to_bytes(runtime, samples_played);
+	}
+	/* else must return most conservative value found in timer func
+	 * by looping over all streams
+	 */
+
+	ptr = bytes_to_frames(runtime, dpcm->pcm_buf_pos  % dpcm->pcm_size);
+	VPRINTK2("playback_pointer=%04ld\n", (unsigned long)ptr);
+	return ptr;
+}
+
+static void snd_card_asihpi_playback_format(struct snd_card_asihpi *asihpi,
+						u32 h_stream,
+						struct snd_pcm_hardware *pcmhw)
+{
+	struct hpi_format hpi_format;
+	u16 format;
+	u16 err;
+	u32 h_control;
+	u32 sample_rate = 48000;
+
+	/* on cards without SRC, must query at valid rate,
+	* maybe set by external sync
+	*/
+	err = hpi_mixer_get_control(ss, asihpi->h_mixer,
+				  HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
+				  HPI_CONTROL_SAMPLECLOCK, &h_control);
+
+	if (!err)
+		err = hpi_sample_clock_get_sample_rate(ss, h_control,
+				&sample_rate);
+
+	for (format = HPI_FORMAT_PCM8_UNSIGNED;
+	     format <= HPI_FORMAT_PCM24_SIGNED; format++) {
+		err = hpi_format_create(&hpi_format,
+					2, format, sample_rate, 128000, 0);
+		if (!err)
+			err = hpi_outstream_query_format(ss, h_stream,
+							&hpi_format);
+		if (!err && (hpi_to_alsa_formats[format] != -1))
+			pcmhw->formats |=
+				(1ULL << hpi_to_alsa_formats[format]);
+	}
+}
+
+static struct snd_pcm_hardware snd_card_asihpi_playback = {
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+	.periods_min = PERIODS_MIN,
+	.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+	.fifo_size = 0,
+};
+
+static int snd_card_asihpi_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm;
+	struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
+	int err;
+
+	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
+	if (dpcm == NULL)
+		return -ENOMEM;
+
+	err =
+	    hpi_outstream_open(ss, card->adapter_index,
+			      substream->number, &dpcm->h_stream);
+	hpi_handle_error(err);
+	if (err)
+		kfree(dpcm);
+	if (err == HPI_ERROR_OBJ_ALREADY_OPEN)
+		return -EBUSY;
+	if (err)
+		return -EIO;
+
+	/*? also check ASI5000 samplerate source
+	    If external, only support external rate.
+	    If internal and other stream playing, cant switch
+	*/
+
+	init_timer(&dpcm->timer);
+	dpcm->timer.data = (unsigned long) dpcm;
+	dpcm->timer.function = snd_card_asihpi_timer_function;
+	dpcm->substream = substream;
+	runtime->private_data = dpcm;
+	runtime->private_free = snd_card_asihpi_runtime_free;
+
+	snd_card_asihpi_playback.channels_max = card->out_max_chans;
+	/*?snd_card_asihpi_playback.period_bytes_min =
+	card->out_max_chans * 4096; */
+
+	snd_card_asihpi_playback_format(card, dpcm->h_stream,
+					&snd_card_asihpi_playback);
+
+	snd_card_asihpi_pcm_samplerates(card,  &snd_card_asihpi_playback);
+
+	snd_card_asihpi_playback.info = SNDRV_PCM_INFO_INTERLEAVED |
+					SNDRV_PCM_INFO_DOUBLE |
+					SNDRV_PCM_INFO_BATCH |
+					SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					SNDRV_PCM_INFO_PAUSE;
+
+	if (card->support_mmap)
+		snd_card_asihpi_playback.info |= SNDRV_PCM_INFO_MMAP |
+						SNDRV_PCM_INFO_MMAP_VALID;
+
+	if (card->support_grouping)
+		snd_card_asihpi_playback.info |= SNDRV_PCM_INFO_SYNC_START;
+
+	/* struct is copied, so can create initializer dynamically */
+	runtime->hw = snd_card_asihpi_playback;
+
+	if (card->support_mmap)
+		err = snd_pcm_hw_constraint_pow2(runtime, 0,
+					SNDRV_PCM_HW_PARAM_BUFFER_BYTES);
+	if (err < 0)
+		return err;
+
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+		card->update_interval_frames);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+		card->update_interval_frames * 4, UINT_MAX);
+
+	snd_pcm_set_sync(substream);
+
+	snd_printd(KERN_INFO "playback open\n");
+
+	return 0;
+}
+
+static int snd_card_asihpi_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+
+	hpi_handle_error(hpi_outstream_close(ss, dpcm->h_stream));
+	snd_printd(KERN_INFO "playback close\n");
+
+	return 0;
+}
+
+static int snd_card_asihpi_playback_copy(struct snd_pcm_substream *substream,
+					int channel,
+					snd_pcm_uframes_t pos,
+					void __user *src,
+					snd_pcm_uframes_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+	unsigned int len;
+
+	len = frames_to_bytes(runtime, count);
+
+	if (copy_from_user(runtime->dma_area, src, len))
+		return -EFAULT;
+
+	VPRINTK2(KERN_DEBUG "playback copy%d %u bytes\n",
+			substream->number, len);
+
+	hpi_handle_error(hpi_outstream_write_buf(ss, dpcm->h_stream,
+				runtime->dma_area, len, &dpcm->format));
+
+	return 0;
+}
+
+static int snd_card_asihpi_playback_silence(struct snd_pcm_substream *
+					    substream, int channel,
+					    snd_pcm_uframes_t pos,
+					    snd_pcm_uframes_t count)
+{
+	unsigned int len;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+
+	len = frames_to_bytes(runtime, count);
+	snd_printd(KERN_INFO "playback silence  %u bytes\n", len);
+
+	memset(runtime->dma_area, 0, len);
+	hpi_handle_error(hpi_outstream_write_buf(ss, dpcm->h_stream,
+				runtime->dma_area, len, &dpcm->format));
+	return 0;
+}
+
+static struct snd_pcm_ops snd_card_asihpi_playback_ops = {
+	.open = snd_card_asihpi_playback_open,
+	.close = snd_card_asihpi_playback_close,
+	.ioctl = snd_card_asihpi_playback_ioctl,
+	.hw_params = snd_card_asihpi_pcm_hw_params,
+	.hw_free = snd_card_asihpi_hw_free,
+	.prepare = snd_card_asihpi_playback_prepare,
+	.trigger = snd_card_asihpi_trigger,
+	.pointer = snd_card_asihpi_playback_pointer,
+	.copy = snd_card_asihpi_playback_copy,
+	.silence = snd_card_asihpi_playback_silence,
+};
+
+static struct snd_pcm_ops snd_card_asihpi_playback_mmap_ops = {
+	.open = snd_card_asihpi_playback_open,
+	.close = snd_card_asihpi_playback_close,
+	.ioctl = snd_card_asihpi_playback_ioctl,
+	.hw_params = snd_card_asihpi_pcm_hw_params,
+	.hw_free = snd_card_asihpi_hw_free,
+	.prepare = snd_card_asihpi_playback_prepare,
+	.trigger = snd_card_asihpi_trigger,
+	.pointer = snd_card_asihpi_playback_pointer,
+};
+
+/***************************** CAPTURE OPS ****************/
+static snd_pcm_uframes_t
+snd_card_asihpi_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+
+	VPRINTK2("capture pointer %d=%d\n",
+			substream->number, dpcm->pcm_buf_pos);
+	/* NOTE Unlike playback can't use actual dwSamplesPlayed
+		for the capture position, because those samples aren't yet in
+		the local buffer available for reading.
+	*/
+	return bytes_to_frames(runtime, dpcm->pcm_buf_pos % dpcm->pcm_size);
+}
+
+static int snd_card_asihpi_capture_ioctl(struct snd_pcm_substream *substream,
+					 unsigned int cmd, void *arg)
+{
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static int snd_card_asihpi_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+
+	hpi_handle_error(hpi_instream_reset(ss, dpcm->h_stream));
+	dpcm->pcm_irq_pos = 0;
+	dpcm->pcm_buf_pos = 0;
+
+	snd_printd("capture prepare %d\n", substream->number);
+	return 0;
+}
+
+
+
+static void snd_card_asihpi_capture_format(struct snd_card_asihpi *asihpi,
+					u32 h_stream,
+					 struct snd_pcm_hardware *pcmhw)
+{
+  struct hpi_format hpi_format;
+	u16 format;
+	u16 err;
+	u32 h_control;
+	u32 sample_rate = 48000;
+
+	/* on cards without SRC, must query at valid rate,
+		maybe set by external sync */
+	err = hpi_mixer_get_control(ss, asihpi->h_mixer,
+				  HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
+				  HPI_CONTROL_SAMPLECLOCK, &h_control);
+
+	if (!err)
+		err = hpi_sample_clock_get_sample_rate(ss, h_control,
+			&sample_rate);
+
+	for (format = HPI_FORMAT_PCM8_UNSIGNED;
+		format <= HPI_FORMAT_PCM24_SIGNED; format++) {
+
+		err = hpi_format_create(&hpi_format, 2, format,
+				sample_rate, 128000, 0);
+		if (!err)
+			err = hpi_instream_query_format(ss, h_stream,
+					    &hpi_format);
+		if (!err)
+			pcmhw->formats |=
+				(1ULL << hpi_to_alsa_formats[format]);
+	}
+}
+
+
+static struct snd_pcm_hardware snd_card_asihpi_capture = {
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+	.periods_min = PERIODS_MIN,
+	.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+	.fifo_size = 0,
+};
+
+static int snd_card_asihpi_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi *card = snd_pcm_substream_chip(substream);
+	struct snd_card_asihpi_pcm *dpcm;
+	int err;
+
+	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
+	if (dpcm == NULL)
+		return -ENOMEM;
+
+	snd_printd("hpi_instream_open adapter %d stream %d\n",
+		   card->adapter_index, substream->number);
+
+	err = hpi_handle_error(
+	    hpi_instream_open(ss, card->adapter_index,
+			     substream->number, &dpcm->h_stream));
+	if (err)
+		kfree(dpcm);
+	if (err == HPI_ERROR_OBJ_ALREADY_OPEN)
+		return -EBUSY;
+	if (err)
+		return -EIO;
+
+
+	init_timer(&dpcm->timer);
+	dpcm->timer.data = (unsigned long) dpcm;
+	dpcm->timer.function = snd_card_asihpi_timer_function;
+	dpcm->substream = substream;
+	runtime->private_data = dpcm;
+	runtime->private_free = snd_card_asihpi_runtime_free;
+
+	snd_card_asihpi_capture.channels_max = card->in_max_chans;
+	snd_card_asihpi_capture_format(card, dpcm->h_stream,
+				       &snd_card_asihpi_capture);
+	snd_card_asihpi_pcm_samplerates(card,  &snd_card_asihpi_capture);
+	snd_card_asihpi_capture.info = SNDRV_PCM_INFO_INTERLEAVED;
+
+	if (card->support_mmap)
+		snd_card_asihpi_capture.info |= SNDRV_PCM_INFO_MMAP |
+						SNDRV_PCM_INFO_MMAP_VALID;
+
+	runtime->hw = snd_card_asihpi_capture;
+
+	if (card->support_mmap)
+		err = snd_pcm_hw_constraint_pow2(runtime, 0,
+					SNDRV_PCM_HW_PARAM_BUFFER_BYTES);
+	if (err < 0)
+		return err;
+
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+		card->update_interval_frames);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+		card->update_interval_frames * 2, UINT_MAX);
+
+	snd_pcm_set_sync(substream);
+
+	return 0;
+}
+
+static int snd_card_asihpi_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_card_asihpi_pcm *dpcm = substream->runtime->private_data;
+
+	hpi_handle_error(hpi_instream_close(ss, dpcm->h_stream));
+	return 0;
+}
+
+static int snd_card_asihpi_capture_copy(struct snd_pcm_substream *substream,
+				int channel, snd_pcm_uframes_t pos,
+				void __user *dst, snd_pcm_uframes_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_card_asihpi_pcm *dpcm = runtime->private_data;
+	u32 data_size;
+
+	data_size = frames_to_bytes(runtime, count);
+
+	VPRINTK2("capture copy%d %d bytes\n", substream->number, data_size);
+	hpi_handle_error(hpi_instream_read_buf(ss, dpcm->h_stream,
+				runtime->dma_area, data_size));
+
+	/* Used by capture_pointer */
+	dpcm->pcm_irq_pos = dpcm->pcm_irq_pos + data_size;
+
+	if (copy_to_user(dst, runtime->dma_area, data_size))
+		return -EFAULT;
+
+	return 0;
+}
+
+static struct snd_pcm_ops snd_card_asihpi_capture_mmap_ops = {
+	.open = snd_card_asihpi_capture_open,
+	.close = snd_card_asihpi_capture_close,
+	.ioctl = snd_card_asihpi_capture_ioctl,
+	.hw_params = snd_card_asihpi_pcm_hw_params,
+	.hw_free = snd_card_asihpi_hw_free,
+	.prepare = snd_card_asihpi_capture_prepare,
+	.trigger = snd_card_asihpi_trigger,
+	.pointer = snd_card_asihpi_capture_pointer,
+};
+
+static struct snd_pcm_ops snd_card_asihpi_capture_ops = {
+	.open = snd_card_asihpi_capture_open,
+	.close = snd_card_asihpi_capture_close,
+	.ioctl = snd_card_asihpi_capture_ioctl,
+	.hw_params = snd_card_asihpi_pcm_hw_params,
+	.hw_free = snd_card_asihpi_hw_free,
+	.prepare = snd_card_asihpi_capture_prepare,
+	.trigger = snd_card_asihpi_trigger,
+	.pointer = snd_card_asihpi_capture_pointer,
+	.copy = snd_card_asihpi_capture_copy
+};
+
+static int __devinit snd_card_asihpi_pcm_new(struct snd_card_asihpi *asihpi,
+				      int device, int substreams)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(asihpi->card, "asihpi PCM", device,
+			 asihpi->num_outstreams, asihpi->num_instreams,
+			 &pcm);
+	if (err < 0)
+		return err;
+	/* pointer to ops struct is stored, dont change ops afterwards! */
+	if (asihpi->support_mmap) {
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				&snd_card_asihpi_playback_mmap_ops);
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&snd_card_asihpi_capture_mmap_ops);
+	} else {
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				&snd_card_asihpi_playback_ops);
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&snd_card_asihpi_capture_ops);
+	}
+
+	pcm->private_data = asihpi;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "asihpi PCM");
+
+	/*? do we want to emulate MMAP for non-BBM cards?
+	Jack doesn't work with ALSAs MMAP emulation - WHY NOT? */
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						snd_dma_pci_data(asihpi->pci),
+						64*1024, BUFFER_BYTES_MAX);
+
+	return 0;
+}
+
+/***************************** MIXER CONTROLS ****************/
+struct hpi_control {
+	u32 h_control;
+	u16 control_type;
+	u16 src_node_type;
+	u16 src_node_index;
+	u16 dst_node_type;
+	u16 dst_node_index;
+	u16 band;
+	char name[44]; /* copied to snd_ctl_elem_id.name[44]; */
+};
+
+static char *asihpi_tuner_band_names[] =
+{
+	"invalid",
+	"AM",
+	"FM mono",
+	"TV NTSC-M",
+	"FM stereo",
+	"AUX",
+	"TV PAL BG",
+	"TV PAL I",
+	"TV PAL DK",
+	"TV SECAM",
+};
+
+compile_time_assert(
+	(ARRAY_SIZE(asihpi_tuner_band_names) ==
+		(HPI_TUNER_BAND_LAST+1)),
+	assert_tuner_band_names_size);
+
+#if ASI_STYLE_NAMES
+static char *asihpi_src_names[] =
+{
+	"no source",
+	"outstream",
+	"line_in",
+	"aes_in",
+	"tuner",
+	"RF",
+	"clock",
+	"bitstr",
+	"mic",
+	"cobranet",
+	"analog_in",
+	"adapter",
+};
+#else
+static char *asihpi_src_names[] =
+{
+	"no source",
+	"PCM playback",
+	"line in",
+	"digital in",
+	"tuner",
+	"RF",
+	"clock",
+	"bitstream",
+	"mic",
+	"cobranet in",
+	"analog in",
+	"adapter",
+};
+#endif
+
+compile_time_assert(
+	(ARRAY_SIZE(asihpi_src_names) ==
+		(HPI_SOURCENODE_LAST_INDEX-HPI_SOURCENODE_NONE+1)),
+	assert_src_names_size);
+
+#if ASI_STYLE_NAMES
+static char *asihpi_dst_names[] =
+{
+	"no destination",
+	"instream",
+	"line_out",
+	"aes_out",
+	"RF",
+	"speaker" ,
+	"cobranet",
+	"analog_out",
+};
+#else
+static char *asihpi_dst_names[] =
+{
+	"no destination",
+	"PCM capture",
+	"line out",
+	"digital out",
+	"RF",
+	"speaker",
+	"cobranet out",
+	"analog out"
+};
+#endif
+
+compile_time_assert(
+	(ARRAY_SIZE(asihpi_dst_names) ==
+		(HPI_DESTNODE_LAST_INDEX-HPI_DESTNODE_NONE+1)),
+	assert_dst_names_size);
+
+static inline int ctl_add(struct snd_card *card, struct snd_kcontrol_new *ctl,
+				struct snd_card_asihpi *asihpi)
+{
+	int err;
+
+	err = snd_ctl_add(card, snd_ctl_new1(ctl, asihpi));
+	if (err < 0)
+		return err;
+	else if (mixer_dump)
+		snd_printk(KERN_INFO "added %s(%d)\n", ctl->name, ctl->index);
+
+	return 0;
+}
+
+/* Convert HPI control name and location into ALSA control name */
+static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control,
+				struct hpi_control *hpi_ctl,
+				char *name)
+{
+	memset(snd_control, 0, sizeof(*snd_control));
+	snd_control->name = hpi_ctl->name;
+	snd_control->private_value = hpi_ctl->h_control;
+	snd_control->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	snd_control->index = 0;
+
+	if (hpi_ctl->src_node_type && hpi_ctl->dst_node_type)
+		sprintf(hpi_ctl->name, "%s%d to %s%d %s",
+			asihpi_src_names[hpi_ctl->src_node_type],
+			hpi_ctl->src_node_index,
+			asihpi_dst_names[hpi_ctl->dst_node_type],
+			hpi_ctl->dst_node_index,
+			name);
+	else if (hpi_ctl->dst_node_type) {
+		sprintf(hpi_ctl->name, "%s%d %s",
+		asihpi_dst_names[hpi_ctl->dst_node_type],
+		hpi_ctl->dst_node_index,
+		name);
+	} else {
+		sprintf(hpi_ctl->name, "%s%d %s",
+		asihpi_src_names[hpi_ctl->src_node_type],
+		hpi_ctl->src_node_index,
+		name);
+	}
+}
+
+/*------------------------------------------------------------
+   Volume controls
+ ------------------------------------------------------------*/
+#define VOL_STEP_mB 1
+static int snd_asihpi_volume_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	u32 h_control = kcontrol->private_value;
+	u16 err;
+	/* native gains are in millibels */
+	short min_gain_mB;
+	short max_gain_mB;
+	short step_gain_mB;
+
+	err = hpi_volume_query_range(ss, h_control,
+			&min_gain_mB, &max_gain_mB, &step_gain_mB);
+	if (err) {
+		max_gain_mB = 0;
+		min_gain_mB = -10000;
+		step_gain_mB = VOL_STEP_mB;
+	}
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = min_gain_mB / VOL_STEP_mB;
+	uinfo->value.integer.max = max_gain_mB / VOL_STEP_mB;
+	uinfo->value.integer.step = step_gain_mB / VOL_STEP_mB;
+	return 0;
+}
+
+static int snd_asihpi_volume_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	u32 h_control = kcontrol->private_value;
+	short an_gain_mB[HPI_MAX_CHANNELS];
+
+	hpi_handle_error(hpi_volume_get_gain(ss, h_control, an_gain_mB));
+	ucontrol->value.integer.value[0] = an_gain_mB[0] / VOL_STEP_mB;
+	ucontrol->value.integer.value[1] = an_gain_mB[1] / VOL_STEP_mB;
+
+	return 0;
+}
+
+static int snd_asihpi_volume_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	int change;
+	u32 h_control = kcontrol->private_value;
+	short an_gain_mB[HPI_MAX_CHANNELS];
+
+	an_gain_mB[0] =
+	    (ucontrol->value.integer.value[0]) * VOL_STEP_mB;
+	an_gain_mB[1] =
+	    (ucontrol->value.integer.value[1]) * VOL_STEP_mB;
+	/*  change = asihpi->mixer_volume[addr][0] != left ||
+	   asihpi->mixer_volume[addr][1] != right;
+	 */
+	change = 1;
+	hpi_handle_error(hpi_volume_set_gain(ss, h_control, an_gain_mB));
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_100, -10000, VOL_STEP_mB, 0);
+
+static int __devinit snd_asihpi_volume_add(struct snd_card_asihpi *asihpi,
+					struct hpi_control *hpi_ctl)
+{
+	struct snd_card *card = asihpi->card;
+	struct snd_kcontrol_new snd_control;
+
+	asihpi_ctl_init(&snd_control, hpi_ctl, "volume");
+	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+	snd_control.info = snd_asihpi_volume_info;
+	snd_control.get = snd_asihpi_volume_get;
+	snd_control.put = snd_asihpi_volume_put;
+	snd_control.tlv.p = db_scale_100;
+
+	return ctl_add(card, &snd_control, asihpi);
+}
+
+/*------------------------------------------------------------
+   Level controls
+ ------------------------------------------------------------*/
+static int snd_asihpi_level_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	u32 h_control = kcontrol->private_value;
+	u16 err;
+	short min_gain_mB;
+	short max_gain_mB;
+	short step_gain_mB;
+
+	err =
+	    hpi_level_query_range(ss, h_control, &min_gain_mB,
+			       &max_gain_mB, &step_gain_mB);
+	if (err) {
+		max_gain_mB = 2400;
+		min_gain_mB = -1000;
+		step_gain_mB = 100;
+	}
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = min_gain_mB / HPI_UNITS_PER_dB;
+	uinfo->value.integer.max = max_gain_mB / HPI_UNITS_PER_dB;
+	uinfo->value.integer.step = step_gain_mB / HPI_UNITS_PER_dB;
+	return 0;
+}
+
+static int snd_asihpi_level_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	u32 h_control = kcontrol->private_value;
+	short an_gain_mB[HPI_MAX_CHANNELS];
+
+	hpi_handle_error(hpi_level_get_gain(ss, h_control, an_gain_mB));
+	ucontrol->value.integer.value[0] =
+	    an_gain_mB[0] / HPI_UNITS_PER_dB;
+	ucontrol->value.integer.value[1] =
+	    an_gain_mB[1] / HPI_UNITS_PER_dB;
+
+	return 0;
+}
+
+static int snd_asihpi_level_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	int change;
+	u32 h_control = kcontrol->private_value;
+	short an_gain_mB[HPI_MAX_CHANNELS];
+
+	an_gain_mB[0] =
+	    (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB;
+	an_gain_mB[1] =
+	    (ucontrol->value.integer.value[1]) * HPI_UNITS_PER_dB;
+	/*  change = asihpi->mixer_level[addr][0] != left ||
+	   asihpi->mixer_level[addr][1] != right;
+	 */
+	change = 1;
+	hpi_handle_error(hpi_level_set_gain(ss, h_control, an_gain_mB));
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_level, -1000, 100, 0);
+
+static int __devinit snd_asihpi_level_add(struct snd_card_asihpi *asihpi,
+					struct hpi_control *hpi_ctl)
+{
+	struct snd_card *card = asihpi->card;
+	struct snd_kcontrol_new snd_control;
+
+	/* can't use 'volume' cos some nodes have volume as well */
+	asihpi_ctl_init(&snd_control, hpi_ctl, "level");
+	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+	snd_control.info = snd_asihpi_level_info;
+	snd_control.get = snd_asihpi_level_get;
+	snd_control.put = snd_asihpi_level_put;
+	snd_control.tlv.p = db_scale_level;
+
+	return ctl_add(card, &snd_control, asihpi);
+}
+
+/*------------------------------------------------------------
+   AESEBU controls
+ ------------------------------------------------------------*/
+
+/* AESEBU format */
+static char *asihpi_aesebu_format_names[] =
+{
+	"N/A",
+	"S/PDIF",
+	"AES/EBU",
+};
+
+static int snd_asihpi_aesebu_format_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+			uinfo->value.enumerated.items - 1;
+
+	strcpy(uinfo->value.enumerated.name,
+		asihpi_aesebu_format_names[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int snd_asihpi_aesebu_format_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol,
+			u16 (*func)(const struct hpi_hsubsys *, u32, u16 *))
+{
+	u32 h_control = kcontrol->private_value;
+	u16 source, err;
+
+	err = func(ss, h_control, &source);
+
+	/* default to N/A */
+	ucontrol->value.enumerated.item[0] = 0;
+	/* return success but set the control to N/A */
+	if (err)
+		return 0;
+	if (source == HPI_AESEBU_FORMAT_SPDIF)
+		ucontrol->value.enumerated.item[0] = 1;
+	if (source == HPI_AESEBU_FORMAT_AESEBU)
+		ucontrol->value.enumerated.item[0] = 2;
+
+	return 0;
+}
+
+static int snd_asihpi_aesebu_format_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol,
+			 u16 (*func)(const struct hpi_hsubsys *, u32, u16))
+{
+	u32 h_control = kcontrol->private_value;
+
+	/* default to S/PDIF */
+	u16 source = HPI_AESEBU_FORMAT_SPDIF;
+
+	if (ucontrol->value.enumerated.item[0] == 1)
+		source = HPI_AESEBU_FORMAT_SPDIF;
+	if (ucontrol->value.enumerated.item[0] == 2)
+		source = HPI_AESEBU_FORMAT_AESEBU;
+
+	if (func(ss, h_control, source) != 0)
+		return -EINVAL;
+
+	return 1;
+}
+
+static int snd_asihpi_aesebu_rx_format_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol) {
+	return snd_asihpi_aesebu_format_get(kcontrol, ucontrol,
+					HPI_AESEBU__receiver_get_format);
+}
+
+static int snd_asihpi_aesebu_rx_format_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol) {
+	return snd_asihpi_aesebu_format_put(kcontrol, ucontrol,
+					HPI_AESEBU__receiver_set_format);
+}
+
+static int snd_asihpi_aesebu_rxstatus_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0X1F;
+	uinfo->value.integer.step = 1;
+
+	return 0;
+}
+
+static int snd_asihpi_aesebu_rxstatus_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol) {
+
+	u32 h_control = kcontrol->private_value;
+	u16 status;
+
+	hpi_handle_error(HPI_AESEBU__receiver_get_error_status(
+				ss, h_control, &status));
+	ucontrol->value.integer.value[0] = status;
+	return 0;
+}
+
+static int __devinit snd_asihpi_aesebu_rx_add(struct snd_card_asihpi *asihpi,
+					struct hpi_control *hpi_ctl)
+{
+	struct snd_card *card = asihpi->card;
+	struct snd_kcontrol_new snd_control;
+
+	asihpi_ctl_init(&snd_control, hpi_ctl, "format");
+	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+	snd_control.info = snd_asihpi_aesebu_format_info;
+	snd_control.get = snd_asihpi_aesebu_rx_format_get;
+	snd_control.put = snd_asihpi_aesebu_rx_format_put;
+
+
+	if (ctl_add(card, &snd_control, asihpi) < 0)
+		return -EINVAL;
+
+	asihpi_ctl_init(&snd_control, hpi_ctl, "status");
+	snd_control.access =
+	    SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ;
+	snd_control.info = snd_asihpi_aesebu_rxstatus_info;
+	snd_control.get = snd_asihpi_aesebu_rxstatus_get;
+
+	return ctl_add(card, &snd_control, asihpi);
+}
+
+static int snd_asihpi_aesebu_tx_format_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol) {
+	return snd_asihpi_aesebu_format_get(kcontrol, ucontrol,
+					HPI_AESEBU__transmitter_get_format);
+}
+
+static int snd_asihpi_aesebu_tx_format_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol) {
+	return snd_asihpi_aesebu_format_put(kcontrol, ucontrol,
+					HPI_AESEBU__transmitter_set_format);
+}
+
+
+static int __devinit snd_asihpi_aesebu_tx_add(struct snd_card_asihpi *asihpi,
+					struct hpi_control *hpi_ctl)
+{
+	struct snd_card *card = asihpi->card;
+	struct snd_kcontrol_new snd_control;
+
+	asihpi_ctl_init(&snd_control, hpi_ctl, "format");
+	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+	snd_control.info = snd_asihpi_aesebu_format_info;
+	snd_control.get = snd_asihpi_aesebu_tx_format_get;
+	snd_control.put = snd_asihpi_aesebu_tx_format_put;
+
+	return ctl_add(card, &snd_control, asihpi);
+}
+
+/*------------------------------------------------------------
+   Tuner controls
+ ------------------------------------------------------------*/
+
+/* Gain */
+
+static int snd_asihpi_tuner_gain_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	u32 h_control = kcontrol->private_value;
+	u16 err;
+	short idx;
+	u16 gain_range[3];
+
+	for (idx = 0; idx < 3; idx++) {
+		err = hpi_tuner_query_gain(ss, h_control,
+					  idx, &gain_range[idx]);
+		if (err != 0)
+			return err;
+	}
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = ((int)gain_range[0]) / HPI_UNITS_PER_dB;
+	uinfo->value.integer.max = ((int)gain_range[1]) / HPI_UNITS_PER_dB;
+	uinfo->value.integer.step = ((int) gain_range[2]) / HPI_UNITS_PER_dB;
+	return 0;
+}
+
+static int snd_asihpi_tuner_gain_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	/*
+	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
+	*/
+	u32 h_control = kcontrol->private_value;
+	short gain;
+
+	hpi_handle_error(hpi_tuner_get_gain(ss, h_control, &gain));
+	ucontrol->value.integer.value[0] = gain / HPI_UNITS_PER_dB;
+
+	return 0;
+}
+
+static int snd_asihpi_tuner_gain_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	/*
+	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
+	*/
+	u32 h_control = kcontrol->private_value;
+	short gain;
+
+	gain = (ucontrol->value.integer.value[0]) * HPI_UNITS_PER_dB;
+	hpi_handle_error(hpi_tuner_set_gain(ss, h_control, gain));
+
+	return 1;
+}
+
+/* Band  */
+
+static int asihpi_tuner_band_query(struct snd_kcontrol *kcontrol,
+					u16 *band_list, u32 len) {
+	u32 h_control = kcontrol->private_value;
+	u16 err = 0;
+	u32 i;
+
+	for (i = 0; i < len; i++) {
+		err = hpi_tuner_query_band(ss,
+				h_control, i, &band_list[i]);
+		if (err != 0)
+			break;
+	}
+
+	if (err && (err != HPI_ERROR_INVALID_OBJ_INDEX))
+		return -EIO;
+
+	return i;
+}
+
+static int snd_asihpi_tuner_band_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	u16 tuner_bands[HPI_TUNER_BAND_LAST];
+	int num_bands = 0;
+
+	num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands,
+				HPI_TUNER_BAND_LAST);
+
+	if (num_bands < 0)
+		return num_bands;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = num_bands;
+
+	if (num_bands > 0) {
+		if (uinfo->value.enumerated.item >=
+					uinfo->value.enumerated.items)
+			uinfo->value.enumerated.item =
+				uinfo->value.enumerated.items - 1;
+
+		strcpy(uinfo->value.enumerated.name,
+			asihpi_tuner_band_names[
+				tuner_bands[uinfo->value.enumerated.item]]);
+
+	}
+	return 0;
+}
+
+static int snd_asihpi_tuner_band_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	u32 h_control = kcontrol->private_value;
+	/*
+	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
+	*/
+	u16 band, idx;
+	u16 tuner_bands[HPI_TUNER_BAND_LAST];
+	u32 num_bands = 0;
+
+	num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands,
+				HPI_TUNER_BAND_LAST);
+
+	hpi_handle_error(hpi_tuner_get_band(ss, h_control, &band));
+
+	ucontrol->value.enumerated.item[0] = -1;
+	for (idx = 0; idx < HPI_TUNER_BAND_LAST; idx++)
+		if (tuner_bands[idx] == band) {
+			ucontrol->value.enumerated.item[0] = idx;
+			break;
+		}
+
+	return 0;
+}
+
+static int snd_asihpi_tuner_band_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	/*
+	struct snd_card_asihpi *asihpi = snd_kcontrol_chip(kcontrol);
+	*/
+	u32 h_control = kcontrol->private_value;
+	u16 band;
+	u16 tuner_bands[HPI_TUNER_BAND_LAST];
+	u32 num_bands = 0;
+
+	num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands,
+			HPI_TUNER_BAND_LAST);
+
+	band = tuner_bands[ucontrol->value.enumerated.item[0]];
+	hpi_handle_error(hpi_tuner_set_band(ss, h_control, band));
+
+	return 1;
+}
+
+/* Freq */
+
+static int snd_asihpi_tuner_freq_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	u32 h_control = kcontrol->private_value;
+	u16 err;
+	u16 tuner_bands[HPI_TUNER_BAND_LAST];
+	u16 num_bands = 0, band_iter, idx;
+	u32 freq_range[3], temp_freq_range[3];
+
+	num_bands = asihpi_tuner_band_query(kcontrol, tuner_bands,
+			HPI_TUNER_BAND_LAST);
+
+	freq_range[0] = INT_MAX;
+	freq_range[1] = 0;
+	freq_range[2] = INT_MAX;
+
+	for (band_iter = 0; band_iter < num_bands; band_iter++) {
+		for (idx = 0; idx < 3; idx++) {
+			err = hpi_tuner_query_frequency(ss, h_control,
+				idx, tuner_bands[band_iter],
+				&temp_freq_range[idx]);
+			if (err != 0)
+				return err;
+		}
+
+		/* skip band with bogus stepping */
+		if (temp_freq_range[2] <= 0)
+			continue;
+
+		if (temp_freq_range[0] < freq_range[0])
+			freq_range[0] = temp_freq_range[0];
+		if (temp_freq_range[1] > freq_range[1])
+			freq_range[1] = temp_freq_range[1];
+		if (temp_freq_range[2] < freq_range[2])
+			freq_range[2] = temp_freq_range[2];
+	}
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = ((int)freq_range[0]);
+	uinfo->value.integer.max = ((int)freq_range[1]);
+	uinfo->value.integer.step = ((int)freq_range[2]);
+	return 0;
+}
+
+static int snd_asihpi_tuner_freq_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	u32 h_control = kcontrol->private_value;
+	u32 freq;
+
+	hpi_handle_error(hpi_tuner_get_frequency(ss, h_control, &freq));
+	ucontrol->value.integer.value[0] = freq;
+
+	return 0;
+}
+
+static int snd_asihpi_tuner_freq_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	u32 h_control = kcontrol->private_value;
+	u32 freq;
+
+	freq = ucontrol->value.integer.value[0];
+	hpi_handle_error(hpi_tuner_set_frequency(ss, h_control, freq));
+
+	return 1;
+}
+
+/* Tuner control group initializer  */
+static int __devinit snd_asihpi_tuner_add(struct snd_card_asihpi *asihpi,
+					struct hpi_control *hpi_ctl)
+{
+	struct snd_card *card = asihpi->card;
+	struct snd_kcontrol_new snd_control;
+
+	snd_control.private_value = hpi_ctl->h_control;
+	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+
+	if (!hpi_tuner_get_gain(ss, hpi_ctl->h_control, NULL)) {
+		asihpi_ctl_init(&snd_control, hpi_ctl, "gain");
+		snd_control.info = snd_asihpi_tuner_gain_info;
+		snd_control.get = snd_asihpi_tuner_gain_get;
+		snd_control.put = snd_asihpi_tuner_gain_put;
+
+		if (ctl_add(card, &snd_control, asihpi) < 0)
+			return -EINVAL;
+	}
+
+	asihpi_ctl_init(&snd_control, hpi_ctl, "band");
+	snd_control.info = snd_asihpi_tuner_band_info;
+	snd_control.get = snd_asihpi_tuner_band_get;
+	snd_control.put = snd_asihpi_tuner_band_put;
+
+	if (ctl_add(card, &snd_control, asihpi) < 0)
+		return -EINVAL;
+
+	asihpi_ctl_init(&snd_control, hpi_ctl, "freq");
+	snd_control.info = snd_asihpi_tuner_freq_info;
+	snd_control.get = snd_asihpi_tuner_freq_get;
+	snd_control.put = snd_asihpi_tuner_freq_put;
+
+	return ctl_add(card, &snd_control, asihpi);
+}
+
+/*------------------------------------------------------------
+   Meter controls
+ ------------------------------------------------------------*/
+static int snd_asihpi_meter_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = HPI_MAX_CHANNELS;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0x7FFFFFFF;
+	return 0;
+}
+
+/* linear values for 10dB steps */
+static int log2lin[] = {
+	0x7FFFFFFF, /* 0dB */
+	679093956,
+	214748365,
+	 67909396,
+	 21474837,
+	  6790940,
+	  2147484, /* -60dB */
+	   679094,
+	   214748, /* -80 */
+	    67909,
+	    21475, /* -100 */
+	     6791,
+	     2147,
+	      679,
+	      214,
+	       68,
+	       21,
+		7,
+		2
+};
+
+static int snd_asihpi_meter_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	u32 h_control = kcontrol->private_value;
+	short an_gain_mB[HPI_MAX_CHANNELS], i;
+	u16 err;
+
+	err = hpi_meter_get_peak(ss, h_control, an_gain_mB);
+
+	for (i = 0; i < HPI_MAX_CHANNELS; i++) {
+		if (err) {
+			ucontrol->value.integer.value[i] = 0;
+		} else if (an_gain_mB[i] >= 0) {
+			ucontrol->value.integer.value[i] =
+				an_gain_mB[i] << 16;
+		} else {
+			/* -ve is log value in millibels < -60dB,
+			* convert to (roughly!) linear,
+			*/
+			ucontrol->value.integer.value[i] =
+					log2lin[an_gain_mB[i] / -1000];
+		}
+	}
+	return 0;
+}
+
+static int __devinit snd_asihpi_meter_add(struct snd_card_asihpi *asihpi,
+					struct hpi_control *hpi_ctl, int subidx)
+{
+	struct snd_card *card = asihpi->card;
+	struct snd_kcontrol_new snd_control;
+
+	asihpi_ctl_init(&snd_control, hpi_ctl, "meter");
+	snd_control.access =
+	    SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ;
+	snd_control.info = snd_asihpi_meter_info;
+	snd_control.get = snd_asihpi_meter_get;
+
+	snd_control.index = subidx;
+
+	return ctl_add(card, &snd_control, asihpi);
+}
+
+/*------------------------------------------------------------
+   Multiplexer controls
+ ------------------------------------------------------------*/
+static int snd_card_asihpi_mux_count_sources(struct snd_kcontrol *snd_control)
+{
+	u32 h_control = snd_control->private_value;
+	struct hpi_control hpi_ctl;
+	int s, err;
+	for (s = 0; s < 32; s++) {
+		err = hpi_multiplexer_query_source(ss, h_control, s,
+						  &hpi_ctl.
+						  src_node_type,
+						  &hpi_ctl.
+						  src_node_index);
+		if (err)
+			break;
+	}
+	return s;
+}
+
+static int snd_asihpi_mux_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	int err;
+	u16 src_node_type, src_node_index;
+	u32 h_control = kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items =
+	    snd_card_asihpi_mux_count_sources(kcontrol);
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+		    uinfo->value.enumerated.items - 1;
+
+	err =
+	    hpi_multiplexer_query_source(ss, h_control,
+					uinfo->value.enumerated.item,
+					&src_node_type, &src_node_index);
+
+	sprintf(uinfo->value.enumerated.name, "%s %d",
+		asihpi_src_names[src_node_type - HPI_SOURCENODE_NONE],
+		src_node_index);
+	return 0;
+}
+
+static int snd_asihpi_mux_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	u32 h_control = kcontrol->private_value;
+	u16 source_type, source_index;
+	u16 src_node_type, src_node_index;
+	int s;
+
+	hpi_handle_error(hpi_multiplexer_get_source(ss, h_control,
+				&source_type, &source_index));
+	/* Should cache this search result! */
+	for (s = 0; s < 256; s++) {
+		if (hpi_multiplexer_query_source(ss, h_control, s,
+					    &src_node_type, &src_node_index))
+			break;
+
+		if ((source_type == src_node_type)
+		    && (source_index == src_node_index)) {
+			ucontrol->value.enumerated.item[0] = s;
+			return 0;
+		}
+	}
+	snd_printd(KERN_WARNING
+		"control %x failed to match mux source %hu %hu\n",
+		h_control, source_type, source_index);
+	ucontrol->value.enumerated.item[0] = 0;
+	return 0;
+}
+
+static int snd_asihpi_mux_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	int change;
+	u32 h_control = kcontrol->private_value;
+	u16 source_type, source_index;
+	u16 e;
+
+	change = 1;
+
+	e = hpi_multiplexer_query_source(ss, h_control,
+				    ucontrol->value.enumerated.item[0],
+				    &source_type, &source_index);
+	if (!e)
+		hpi_handle_error(
+			hpi_multiplexer_set_source(ss, h_control,
+						source_type, source_index));
+	return change;
+}
+
+
+static int  __devinit snd_asihpi_mux_add(struct snd_card_asihpi *asihpi,
+					struct hpi_control *hpi_ctl)
+{
+	struct snd_card *card = asihpi->card;
+	struct snd_kcontrol_new snd_control;
+
+#if ASI_STYLE_NAMES
+	asihpi_ctl_init(&snd_control, hpi_ctl, "multiplexer");
+#else
+	asihpi_ctl_init(&snd_control, hpi_ctl, "route");
+#endif
+	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+	snd_control.info = snd_asihpi_mux_info;
+	snd_control.get = snd_asihpi_mux_get;
+	snd_control.put = snd_asihpi_mux_put;
+
+	return ctl_add(card, &snd_control, asihpi);
+
+}
+
+/*------------------------------------------------------------
+   Channel mode controls
+ ------------------------------------------------------------*/
+static int snd_asihpi_cmode_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	static char *mode_names[HPI_CHANNEL_MODE_LAST] = {
+		"normal", "swap",
+		"from_left", "from_right",
+		"to_left", "to_right"
+	};
+
+	u32 h_control = kcontrol->private_value;
+	u16 mode;
+	int i;
+
+	/* HPI channel mode values can be from 1 to 6
+	Some adapters only support a contiguous subset
+	*/
+	for (i = 0; i < HPI_CHANNEL_MODE_LAST; i++)
+		if (hpi_channel_mode_query_mode(
+			ss,  h_control, i, &mode))
+			break;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = i;
+
+	if (uinfo->value.enumerated.item >= i)
+		uinfo->value.enumerated.item = i - 1;
+
+	strcpy(uinfo->value.enumerated.name,
+	       mode_names[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int snd_asihpi_cmode_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	u32 h_control = kcontrol->private_value;
+	u16 mode;
+
+	if (hpi_channel_mode_get(ss, h_control, &mode))
+		mode = 1;
+
+	ucontrol->value.enumerated.item[0] = mode - 1;
+
+	return 0;
+}
+
+static int snd_asihpi_cmode_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	int change;
+	u32 h_control = kcontrol->private_value;
+
+	change = 1;
+
+	hpi_handle_error(hpi_channel_mode_set(ss, h_control,
+			   ucontrol->value.enumerated.item[0] + 1));
+	return change;
+}
+
+
+static int __devinit snd_asihpi_cmode_add(struct snd_card_asihpi *asihpi,
+					struct hpi_control *hpi_ctl)
+{
+	struct snd_card *card = asihpi->card;
+	struct snd_kcontrol_new snd_control;
+
+	asihpi_ctl_init(&snd_control, hpi_ctl, "channel mode");
+	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+	snd_control.info = snd_asihpi_cmode_info;
+	snd_control.get = snd_asihpi_cmode_get;
+	snd_control.put = snd_asihpi_cmode_put;
+
+	return ctl_add(card, &snd_control, asihpi);
+}
+
+/*------------------------------------------------------------
+   Sampleclock source  controls
+ ------------------------------------------------------------*/
+
+static char *sampleclock_sources[MAX_CLOCKSOURCES] =
+    { "N/A", "local PLL", "AES/EBU sync", "word external", "word header",
+	  "SMPTE", "AES/EBU in1", "auto", "network", "invalid",
+	  "prev module",
+	  "AES/EBU in2", "AES/EBU in3", "AES/EBU in4", "AES/EBU in5",
+	  "AES/EBU in6", "AES/EBU in7", "AES/EBU in8"};
+
+
+
+static int snd_asihpi_clksrc_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_card_asihpi *asihpi =
+			(struct snd_card_asihpi *)(kcontrol->private_data);
+	struct clk_cache *clkcache = &asihpi->cc;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = clkcache->count;
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+				uinfo->value.enumerated.items - 1;
+
+	strcpy(uinfo->value.enumerated.name,
+	       clkcache->s[uinfo->value.enumerated.item].name);
+	return 0;
+}
+
+static int snd_asihpi_clksrc_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_card_asihpi *asihpi =
+			(struct snd_card_asihpi *)(kcontrol->private_data);
+	struct clk_cache *clkcache = &asihpi->cc;
+	u32 h_control = kcontrol->private_value;
+	u16 source, srcindex = 0;
+	int i;
+
+	ucontrol->value.enumerated.item[0] = 0;
+	if (hpi_sample_clock_get_source(ss, h_control, &source))
+		source = 0;
+
+	if (source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT)
+		if (hpi_sample_clock_get_source_index(ss, h_control, &srcindex))
+			srcindex = 0;
+
+	for (i = 0; i < clkcache->count; i++)
+		if ((clkcache->s[i].source == source) &&
+			(clkcache->s[i].index == srcindex))
+			break;
+
+	ucontrol->value.enumerated.item[0] = i;
+
+	return 0;
+}
+
+static int snd_asihpi_clksrc_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_card_asihpi *asihpi =
+			(struct snd_card_asihpi *)(kcontrol->private_data);
+	struct clk_cache *clkcache = &asihpi->cc;
+	int change, item;
+	u32 h_control = kcontrol->private_value;
+
+	change = 1;
+	item = ucontrol->value.enumerated.item[0];
+	if (item >= clkcache->count)
+		item = clkcache->count-1;
+
+	hpi_handle_error(hpi_sample_clock_set_source(ss,
+				h_control, clkcache->s[item].source));
+
+	if (clkcache->s[item].source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT)
+		hpi_handle_error(hpi_sample_clock_set_source_index(ss,
+				h_control, clkcache->s[item].index));
+	return change;
+}
+
+/*------------------------------------------------------------
+   Clkrate controls
+ ------------------------------------------------------------*/
+/* Need to change this to enumerated control with list of rates */
+static int snd_asihpi_clklocal_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 8000;
+	uinfo->value.integer.max = 192000;
+	uinfo->value.integer.step = 100;
+
+	return 0;
+}
+
+static int snd_asihpi_clklocal_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	u32 h_control = kcontrol->private_value;
+	u32 rate;
+	u16 e;
+
+	e = hpi_sample_clock_get_local_rate(ss, h_control, &rate);
+	if (!e)
+		ucontrol->value.integer.value[0] = rate;
+	else
+		ucontrol->value.integer.value[0] = 0;
+	return 0;
+}
+
+static int snd_asihpi_clklocal_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	int change;
+	u32 h_control = kcontrol->private_value;
+
+	/*  change = asihpi->mixer_clkrate[addr][0] != left ||
+	   asihpi->mixer_clkrate[addr][1] != right;
+	 */
+	change = 1;
+	hpi_handle_error(hpi_sample_clock_set_local_rate(ss, h_control,
+				      ucontrol->value.integer.value[0]));
+	return change;
+}
+
+static int snd_asihpi_clkrate_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 8000;
+	uinfo->value.integer.max = 192000;
+	uinfo->value.integer.step = 100;
+
+	return 0;
+}
+
+static int snd_asihpi_clkrate_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	u32 h_control = kcontrol->private_value;
+	u32 rate;
+	u16 e;
+
+	e = hpi_sample_clock_get_sample_rate(ss, h_control, &rate);
+	if (!e)
+		ucontrol->value.integer.value[0] = rate;
+	else
+		ucontrol->value.integer.value[0] = 0;
+	return 0;
+}
+
+static int __devinit snd_asihpi_sampleclock_add(struct snd_card_asihpi *asihpi,
+					struct hpi_control *hpi_ctl)
+{
+	struct snd_card *card = asihpi->card;
+	struct snd_kcontrol_new snd_control;
+
+	struct clk_cache *clkcache = &asihpi->cc;
+	u32 hSC =  hpi_ctl->h_control;
+	int has_aes_in = 0;
+	int i, j;
+	u16 source;
+
+	snd_control.private_value = hpi_ctl->h_control;
+
+	clkcache->has_local = 0;
+
+	for (i = 0; i <= HPI_SAMPLECLOCK_SOURCE_LAST; i++) {
+		if  (hpi_sample_clock_query_source(ss, hSC,
+				i, &source))
+			break;
+		clkcache->s[i].source = source;
+		clkcache->s[i].index = 0;
+		clkcache->s[i].name = sampleclock_sources[source];
+		if (source == HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT)
+			has_aes_in = 1;
+		if (source == HPI_SAMPLECLOCK_SOURCE_LOCAL)
+			clkcache->has_local = 1;
+	}
+	if (has_aes_in)
+		/* already will have picked up index 0 above */
+		for (j = 1; j < 8; j++) {
+			if (hpi_sample_clock_query_source_index(ss, hSC,
+				j, HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT,
+				&source))
+				break;
+			clkcache->s[i].source =
+				HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT;
+			clkcache->s[i].index = j;
+			clkcache->s[i].name = sampleclock_sources[
+					j+HPI_SAMPLECLOCK_SOURCE_LAST];
+			i++;
+		}
+	clkcache->count = i;
+
+	asihpi_ctl_init(&snd_control, hpi_ctl, "source");
+	snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ;
+	snd_control.info = snd_asihpi_clksrc_info;
+	snd_control.get = snd_asihpi_clksrc_get;
+	snd_control.put = snd_asihpi_clksrc_put;
+	if (ctl_add(card, &snd_control, asihpi) < 0)
+		return -EINVAL;
+
+
+	if (clkcache->has_local) {
+		asihpi_ctl_init(&snd_control, hpi_ctl, "local_rate");
+		snd_control.access = SNDRV_CTL_ELEM_ACCESS_READWRITE ;
+		snd_control.info = snd_asihpi_clklocal_info;
+		snd_control.get = snd_asihpi_clklocal_get;
+		snd_control.put = snd_asihpi_clklocal_put;
+
+
+		if (ctl_add(card, &snd_control, asihpi) < 0)
+			return -EINVAL;
+	}
+
+	asihpi_ctl_init(&snd_control, hpi_ctl, "rate");
+	snd_control.access =
+	    SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_READ;
+	snd_control.info = snd_asihpi_clkrate_info;
+	snd_control.get = snd_asihpi_clkrate_get;
+
+	return ctl_add(card, &snd_control, asihpi);
+}
+/*------------------------------------------------------------
+   Mixer
+ ------------------------------------------------------------*/
+
+static int __devinit snd_card_asihpi_mixer_new(struct snd_card_asihpi *asihpi)
+{
+	struct snd_card *card = asihpi->card;
+	unsigned int idx = 0;
+	unsigned int subindex = 0;
+	int err;
+	struct hpi_control hpi_ctl, prev_ctl;
+
+	if (snd_BUG_ON(!asihpi))
+		return -EINVAL;
+	strcpy(card->mixername, "asihpi mixer");
+
+	err =
+	    hpi_mixer_open(ss, asihpi->adapter_index,
+			  &asihpi->h_mixer);
+	hpi_handle_error(err);
+	if (err)
+		return -err;
+
+	memset(&prev_ctl, 0, sizeof(prev_ctl));
+	prev_ctl.control_type = -1;
+
+	for (idx = 0; idx < 2000; idx++) {
+		err = hpi_mixer_get_control_by_index(
+				ss, asihpi->h_mixer,
+				idx,
+				&hpi_ctl.src_node_type,
+				&hpi_ctl.src_node_index,
+				&hpi_ctl.dst_node_type,
+				&hpi_ctl.dst_node_index,
+				&hpi_ctl.control_type,
+				&hpi_ctl.h_control);
+		if (err) {
+			if (err == HPI_ERROR_CONTROL_DISABLED) {
+				if (mixer_dump)
+					snd_printk(KERN_INFO
+						   "disabled HPI control(%d)\n",
+						   idx);
+				continue;
+			} else
+				break;
+
+		}
+
+		hpi_ctl.src_node_type -= HPI_SOURCENODE_NONE;
+		hpi_ctl.dst_node_type -= HPI_DESTNODE_NONE;
+
+		/* ASI50xx in SSX mode has multiple meters on the same node.
+		   Use subindex to create distinct ALSA controls
+		   for any duplicated controls.
+		*/
+		if ((hpi_ctl.control_type == prev_ctl.control_type) &&
+		    (hpi_ctl.src_node_type == prev_ctl.src_node_type) &&
+		    (hpi_ctl.src_node_index == prev_ctl.src_node_index) &&
+		    (hpi_ctl.dst_node_type == prev_ctl.dst_node_type) &&
+		    (hpi_ctl.dst_node_index == prev_ctl.dst_node_index))
+			subindex++;
+		else
+			subindex = 0;
+
+		prev_ctl = hpi_ctl;
+
+		switch (hpi_ctl.control_type) {
+		case HPI_CONTROL_VOLUME:
+			err = snd_asihpi_volume_add(asihpi, &hpi_ctl);
+			break;
+		case HPI_CONTROL_LEVEL:
+			err = snd_asihpi_level_add(asihpi, &hpi_ctl);
+			break;
+		case HPI_CONTROL_MULTIPLEXER:
+			err = snd_asihpi_mux_add(asihpi, &hpi_ctl);
+			break;
+		case HPI_CONTROL_CHANNEL_MODE:
+			err = snd_asihpi_cmode_add(asihpi, &hpi_ctl);
+			break;
+		case HPI_CONTROL_METER:
+			err = snd_asihpi_meter_add(asihpi, &hpi_ctl, subindex);
+			break;
+		case HPI_CONTROL_SAMPLECLOCK:
+			err = snd_asihpi_sampleclock_add(
+						asihpi, &hpi_ctl);
+			break;
+		case HPI_CONTROL_CONNECTION:	/* ignore these */
+			continue;
+		case HPI_CONTROL_TUNER:
+			err = snd_asihpi_tuner_add(asihpi, &hpi_ctl);
+			break;
+		case HPI_CONTROL_AESEBU_TRANSMITTER:
+			err = snd_asihpi_aesebu_tx_add(asihpi, &hpi_ctl);
+			break;
+		case HPI_CONTROL_AESEBU_RECEIVER:
+			err = snd_asihpi_aesebu_rx_add(asihpi, &hpi_ctl);
+			break;
+		case HPI_CONTROL_VOX:
+		case HPI_CONTROL_BITSTREAM:
+		case HPI_CONTROL_MICROPHONE:
+		case HPI_CONTROL_PARAMETRIC_EQ:
+		case HPI_CONTROL_COMPANDER:
+		default:
+			if (mixer_dump)
+				snd_printk(KERN_INFO
+					"untranslated HPI control"
+					"(%d) %d %d %d %d %d\n",
+					idx,
+					hpi_ctl.control_type,
+					hpi_ctl.src_node_type,
+					hpi_ctl.src_node_index,
+					hpi_ctl.dst_node_type,
+					hpi_ctl.dst_node_index);
+			continue;
+		};
+		if (err < 0)
+			return err;
+	}
+	if (HPI_ERROR_INVALID_OBJ_INDEX != err)
+		hpi_handle_error(err);
+
+	snd_printk(KERN_INFO "%d mixer controls found\n", idx);
+
+	return 0;
+}
+
+/*------------------------------------------------------------
+   /proc interface
+ ------------------------------------------------------------*/
+
+static void
+snd_asihpi_proc_read(struct snd_info_entry *entry,
+			struct snd_info_buffer *buffer)
+{
+	struct snd_card_asihpi *asihpi = entry->private_data;
+	u16 version;
+	u32 h_control;
+	u32 rate = 0;
+	u16 source = 0;
+	int err;
+
+	snd_iprintf(buffer, "ASIHPI driver proc file\n");
+	snd_iprintf(buffer,
+		"adapter ID=%4X\n_index=%d\n"
+		"num_outstreams=%d\n_num_instreams=%d\n",
+		asihpi->type, asihpi->adapter_index,
+		asihpi->num_outstreams, asihpi->num_instreams);
+
+	version = asihpi->version;
+	snd_iprintf(buffer,
+		"serial#=%d\n_hw version %c%d\nDSP code version %03d\n",
+		asihpi->serial_number, ((version >> 3) & 0xf) + 'A',
+		version & 0x7,
+		((version >> 13) * 100) + ((version >> 7) & 0x3f));
+
+	err = hpi_mixer_get_control(ss, asihpi->h_mixer,
+				  HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
+				  HPI_CONTROL_SAMPLECLOCK, &h_control);
+
+	if (!err) {
+		err = hpi_sample_clock_get_sample_rate(ss,
+					h_control, &rate);
+		err += hpi_sample_clock_get_source(ss, h_control, &source);
+
+		if (!err)
+			snd_iprintf(buffer, "sample_clock=%d_hz, source %s\n",
+			rate, sampleclock_sources[source]);
+	}
+
+}
+
+
+static void __devinit snd_asihpi_proc_init(struct snd_card_asihpi *asihpi)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(asihpi->card, "info", &entry))
+		snd_info_set_text_ops(entry, asihpi, snd_asihpi_proc_read);
+}
+
+/*------------------------------------------------------------
+   HWDEP
+ ------------------------------------------------------------*/
+
+static int snd_asihpi_hpi_open(struct snd_hwdep *hw, struct file *file)
+{
+	if (enable_hpi_hwdep)
+		return 0;
+	else
+		return -ENODEV;
+
+}
+
+static int snd_asihpi_hpi_release(struct snd_hwdep *hw, struct file *file)
+{
+	if (enable_hpi_hwdep)
+		return asihpi_hpi_release(file);
+	else
+		return -ENODEV;
+}
+
+static int snd_asihpi_hpi_ioctl(struct snd_hwdep *hw, struct file *file,
+				unsigned int cmd, unsigned long arg)
+{
+	if (enable_hpi_hwdep)
+		return asihpi_hpi_ioctl(file, cmd, arg);
+	else
+		return -ENODEV;
+}
+
+
+/* results in /dev/snd/hwC#D0 file for each card with index #
+   also /proc/asound/hwdep will contain '#-00: asihpi (HPI) for each card'
+*/
+static int __devinit snd_asihpi_hpi_new(struct snd_card_asihpi *asihpi,
+	int device, struct snd_hwdep **rhwdep)
+{
+	struct snd_hwdep *hw;
+	int err;
+
+	if (rhwdep)
+		*rhwdep = NULL;
+	err = snd_hwdep_new(asihpi->card, "HPI", device, &hw);
+	if (err < 0)
+		return err;
+	strcpy(hw->name, "asihpi (HPI)");
+	hw->iface = SNDRV_HWDEP_IFACE_LAST;
+	hw->ops.open = snd_asihpi_hpi_open;
+	hw->ops.ioctl = snd_asihpi_hpi_ioctl;
+	hw->ops.release = snd_asihpi_hpi_release;
+	hw->private_data = asihpi;
+	if (rhwdep)
+		*rhwdep = hw;
+	return 0;
+}
+
+/*------------------------------------------------------------
+   CARD
+ ------------------------------------------------------------*/
+static int __devinit snd_asihpi_probe(struct pci_dev *pci_dev,
+				       const struct pci_device_id *pci_id)
+{
+	int err;
+
+	u16 version;
+	int pcm_substreams;
+
+	struct hpi_adapter *hpi_card;
+	struct snd_card *card;
+	struct snd_card_asihpi *asihpi;
+
+	u32 h_control;
+	u32 h_stream;
+
+	static int dev;
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	/* Should this be enable[hpi_card->index] ? */
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = asihpi_adapter_probe(pci_dev, pci_id);
+	if (err < 0)
+		return err;
+
+	hpi_card = pci_get_drvdata(pci_dev);
+	/* first try to give the card the same index as its hardware index */
+	err = snd_card_create(hpi_card->index,
+			      id[hpi_card->index], THIS_MODULE,
+			      sizeof(struct snd_card_asihpi),
+			      &card);
+	if (err < 0) {
+		/* if that fails, try the default index==next available */
+		err =
+		    snd_card_create(index[dev], id[dev],
+				    THIS_MODULE,
+				    sizeof(struct snd_card_asihpi),
+				    &card);
+		if (err < 0)
+			return err;
+		snd_printk(KERN_WARNING
+			"**** WARNING **** adapter index %d->ALSA index %d\n",
+			hpi_card->index, card->number);
+	}
+
+	asihpi = (struct snd_card_asihpi *) card->private_data;
+	asihpi->card = card;
+	asihpi->pci = hpi_card->pci;
+	asihpi->adapter_index = hpi_card->index;
+	hpi_handle_error(hpi_adapter_get_info(ss,
+				 asihpi->adapter_index,
+				 &asihpi->num_outstreams,
+				 &asihpi->num_instreams,
+				 &asihpi->version,
+				 &asihpi->serial_number, &asihpi->type));
+
+	version = asihpi->version;
+	snd_printk(KERN_INFO "adapter ID=%4X index=%d num_outstreams=%d "
+			"num_instreams=%d S/N=%d\n"
+			"hw version %c%d DSP code version %03d\n",
+			asihpi->type, asihpi->adapter_index,
+			asihpi->num_outstreams,
+			asihpi->num_instreams, asihpi->serial_number,
+			((version >> 3) & 0xf) + 'A',
+			version & 0x7,
+			((version >> 13) * 100) + ((version >> 7) & 0x3f));
+
+	pcm_substreams = asihpi->num_outstreams;
+	if (pcm_substreams < asihpi->num_instreams)
+		pcm_substreams = asihpi->num_instreams;
+
+	err = hpi_adapter_get_property(ss, asihpi->adapter_index,
+		HPI_ADAPTER_PROPERTY_CAPS1,
+		NULL, &asihpi->support_grouping);
+	if (err)
+		asihpi->support_grouping = 0;
+
+	err = hpi_adapter_get_property(ss, asihpi->adapter_index,
+		HPI_ADAPTER_PROPERTY_CAPS2,
+		&asihpi->support_mrx, NULL);
+	if (err)
+		asihpi->support_mrx = 0;
+
+	err = hpi_adapter_get_property(ss, asihpi->adapter_index,
+		HPI_ADAPTER_PROPERTY_INTERVAL,
+		NULL, &asihpi->update_interval_frames);
+	if (err)
+		asihpi->update_interval_frames = 512;
+
+	hpi_handle_error(hpi_instream_open(ss, asihpi->adapter_index,
+			     0, &h_stream));
+
+	err = hpi_instream_host_buffer_free(ss, h_stream);
+	asihpi->support_mmap = (!err);
+
+	hpi_handle_error(hpi_instream_close(ss, h_stream));
+
+	err = hpi_adapter_get_property(ss, asihpi->adapter_index,
+		HPI_ADAPTER_PROPERTY_CURCHANNELS,
+		&asihpi->in_max_chans, &asihpi->out_max_chans);
+	if (err) {
+		asihpi->in_max_chans = 2;
+		asihpi->out_max_chans = 2;
+	}
+
+	snd_printk(KERN_INFO "supports mmap:%d grouping:%d mrx:%d\n",
+			asihpi->support_mmap,
+			asihpi->support_grouping,
+			asihpi->support_mrx
+	      );
+
+
+	err = snd_card_asihpi_pcm_new(asihpi, 0, pcm_substreams);
+	if (err < 0) {
+		snd_printk(KERN_ERR "pcm_new failed\n");
+		goto __nodev;
+	}
+	err = snd_card_asihpi_mixer_new(asihpi);
+	if (err < 0) {
+		snd_printk(KERN_ERR "mixer_new failed\n");
+		goto __nodev;
+	}
+
+	err = hpi_mixer_get_control(ss, asihpi->h_mixer,
+				  HPI_SOURCENODE_CLOCK_SOURCE, 0, 0, 0,
+				  HPI_CONTROL_SAMPLECLOCK, &h_control);
+
+	if (!err)
+		err = hpi_sample_clock_set_local_rate(
+			ss, h_control, adapter_fs);
+
+	snd_asihpi_proc_init(asihpi);
+
+	/* always create, can be enabled or disabled dynamically
+	    by enable_hwdep  module param*/
+	snd_asihpi_hpi_new(asihpi, 0, NULL);
+
+	if (asihpi->support_mmap)
+		strcpy(card->driver, "ASIHPI-MMAP");
+	else
+		strcpy(card->driver, "ASIHPI");
+
+	sprintf(card->shortname, "AudioScience ASI%4X", asihpi->type);
+	sprintf(card->longname, "%s %i",
+			card->shortname, asihpi->adapter_index);
+	err = snd_card_register(card);
+	if (!err) {
+		hpi_card->snd_card_asihpi = card;
+		dev++;
+		return 0;
+	}
+__nodev:
+	snd_card_free(card);
+	snd_printk(KERN_ERR "snd_asihpi_probe error %d\n", err);
+	return err;
+
+}
+
+static void __devexit snd_asihpi_remove(struct pci_dev *pci_dev)
+{
+	struct hpi_adapter *hpi_card = pci_get_drvdata(pci_dev);
+
+	snd_card_free(hpi_card->snd_card_asihpi);
+	hpi_card->snd_card_asihpi = NULL;
+	asihpi_adapter_remove(pci_dev);
+}
+
+static DEFINE_PCI_DEVICE_TABLE(asihpi_pci_tbl) = {
+	{HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DSP6205,
+		HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
+		(kernel_ulong_t)HPI_6205},
+	{HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_PCI2040,
+		HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
+		(kernel_ulong_t)HPI_6000},
+	{0,}
+};
+MODULE_DEVICE_TABLE(pci, asihpi_pci_tbl);
+
+static struct pci_driver driver = {
+	.name = "asihpi",
+	.id_table = asihpi_pci_tbl,
+	.probe = snd_asihpi_probe,
+	.remove = __devexit_p(snd_asihpi_remove),
+#ifdef CONFIG_PM
+/*	.suspend = snd_asihpi_suspend,
+	.resume = snd_asihpi_resume, */
+#endif
+};
+
+static int __init snd_asihpi_init(void)
+{
+	asihpi_init();
+	return pci_register_driver(&driver);
+}
+
+static void __exit snd_asihpi_exit(void)
+{
+
+	pci_unregister_driver(&driver);
+	asihpi_exit();
+}
+
+module_init(snd_asihpi_init)
+module_exit(snd_asihpi_exit)
+
diff --git a/linux/sound/pci/asihpi/hpi.h b/linux/sound/pci/asihpi/hpi.h
new file mode 100644
index 0000000..23399d0
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpi.h
@@ -0,0 +1,2035 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+*/
+/** \file hpi.h
+
+ AudioScience Hardware Programming Interface (HPI)
+ public API definition.
+
+ The HPI is a low-level hardware abstraction layer to all
+ AudioScience digital audio adapters
+*/
+/*
+ You must define one operating system that the HPI is to be compiled under
+ HPI_OS_WIN32_USER   32bit Windows
+ HPI_OS_DSP_C6000    DSP TI C6000  (automatically set)
+ HPI_OS_WDM          Windows WDM kernel driver
+ HPI_OS_LINUX        Linux userspace
+ HPI_OS_LINUX_KERNEL Linux kernel (automatically set)
+
+(C) Copyright AudioScience Inc. 1998-2010
+******************************************************************************/
+#ifndef _HPI_H_
+#define _HPI_H_
+/* HPI Version
+If HPI_VER_MINOR is odd then its a development release not intended for the
+public. If HPI_VER_MINOR is even then is a release version
+i.e 3.05.02 is a development version
+*/
+#define HPI_VERSION_CONSTRUCTOR(maj, min, rel) \
+	((maj << 16) + (min << 8) + rel)
+
+#define HPI_VER_MAJOR(v) ((int)(v >> 16))
+#define HPI_VER_MINOR(v) ((int)((v >> 8) & 0xFF))
+#define HPI_VER_RELEASE(v) ((int)(v & 0xFF))
+
+/* Use single digits for versions less that 10 to avoid octal. */
+#define HPI_VER HPI_VERSION_CONSTRUCTOR(4L, 4, 1)
+#define HPI_VER_STRING "4.04.01"
+
+/* Library version as documented in hpi-api-versions.txt */
+#define HPI_LIB_VER  HPI_VERSION_CONSTRUCTOR(9, 0, 0)
+
+#include <linux/types.h>
+#define HPI_EXCLUDE_DEPRECATED
+
+/******************************************************************************/
+/******************************************************************************/
+/********       HPI API DEFINITIONS                                       *****/
+/******************************************************************************/
+/******************************************************************************/
+/*******************************************/
+/**  Audio format types
+\ingroup stream
+*/
+enum HPI_FORMATS {
+/** Used internally on adapter. */
+	HPI_FORMAT_MIXER_NATIVE = 0,
+/** 8-bit unsigned PCM. Windows equivalent is WAVE_FORMAT_PCM. */
+	HPI_FORMAT_PCM8_UNSIGNED = 1,
+/** 16-bit signed PCM. Windows equivalent is WAVE_FORMAT_PCM. */
+	HPI_FORMAT_PCM16_SIGNED = 2,
+/** MPEG-1 Layer-1. */
+	HPI_FORMAT_MPEG_L1 = 3,
+/** MPEG-1 Layer-2.
+
+Windows equivalent is WAVE_FORMAT_MPEG.
+
+The following table shows what combinations of mode and bitrate are possible:
+
+<table border=1 cellspacing=0 cellpadding=5>
+<tr>
+<td><p><b>Bitrate (kbs)</b></p>
+<td><p><b>Mono</b></p>
+<td><p><b>Stereo,<br>Joint Stereo or<br>Dual Channel</b></p>
+
+<tr><td>32<td>X<td>_
+<tr><td>40<td>_<td>_
+<tr><td>48<td>X<td>_
+<tr><td>56<td>X<td>_
+<tr><td>64<td>X<td>X
+<tr><td>80<td>X<td>_
+<tr><td>96<td>X<td>X
+<tr><td>112<td>X<td>X
+<tr><td>128<td>X<td>X
+<tr><td>160<td>X<td>X
+<tr><td>192<td>X<td>X
+<tr><td>224<td>_<td>X
+<tr><td>256<td>-<td>X
+<tr><td>320<td>-<td>X
+<tr><td>384<td>_<td>X
+</table>
+*/
+	HPI_FORMAT_MPEG_L2 = 4,
+/** MPEG-1 Layer-3.
+Windows equivalent is WAVE_FORMAT_MPEG.
+
+The following table shows what combinations of mode and bitrate are possible:
+
+<table border=1 cellspacing=0 cellpadding=5>
+<tr>
+<td><p><b>Bitrate (kbs)</b></p>
+<td><p><b>Mono<br>Stereo @ 8,<br>11.025 and<br>12kHz*</b></p>
+<td><p><b>Mono<br>Stereo @ 16,<br>22.050 and<br>24kHz*</b></p>
+<td><p><b>Mono<br>Stereo @ 32,<br>44.1 and<br>48kHz</b></p>
+
+<tr><td>16<td>X<td>X<td>_
+<tr><td>24<td>X<td>X<td>_
+<tr><td>32<td>X<td>X<td>X
+<tr><td>40<td>X<td>X<td>X
+<tr><td>48<td>X<td>X<td>X
+<tr><td>56<td>X<td>X<td>X
+<tr><td>64<td>X<td>X<td>X
+<tr><td>80<td>_<td>X<td>X
+<tr><td>96<td>_<td>X<td>X
+<tr><td>112<td>_<td>X<td>X
+<tr><td>128<td>_<td>X<td>X
+<tr><td>144<td>_<td>X<td>_
+<tr><td>160<td>_<td>X<td>X
+<tr><td>192<td>_<td>_<td>X
+<tr><td>224<td>_<td>_<td>X
+<tr><td>256<td>-<td>_<td>X
+<tr><td>320<td>-<td>_<td>X
+</table>
+\b * Available on the ASI6000 series only
+*/
+	HPI_FORMAT_MPEG_L3 = 5,
+/** Dolby AC-2. */
+	HPI_FORMAT_DOLBY_AC2 = 6,
+/** Dolbt AC-3. */
+	HPI_FORMAT_DOLBY_AC3 = 7,
+/** 16-bit PCM big-endian. */
+	HPI_FORMAT_PCM16_BIGENDIAN = 8,
+/** TAGIT-1 algorithm - hits. */
+	HPI_FORMAT_AA_TAGIT1_HITS = 9,
+/** TAGIT-1 algorithm - inserts. */
+	HPI_FORMAT_AA_TAGIT1_INSERTS = 10,
+/** 32-bit signed PCM. Windows equivalent is WAVE_FORMAT_PCM.
+Each sample is a 32bit word. The most significant 24 bits contain a 24-bit
+sample and the least significant 8 bits are set to 0.
+*/
+	HPI_FORMAT_PCM32_SIGNED = 11,
+/** Raw bitstream - unknown format. */
+	HPI_FORMAT_RAW_BITSTREAM = 12,
+/** TAGIT-1 algorithm hits - extended. */
+	HPI_FORMAT_AA_TAGIT1_HITS_EX1 = 13,
+/** 32-bit PCM as an IEEE float. Windows equivalent is WAVE_FORMAT_IEEE_FLOAT.
+Each sample is a 32bit word in IEEE754 floating point format.
+The range is +1.0 to -1.0, which corresponds to digital fullscale.
+*/
+	HPI_FORMAT_PCM32_FLOAT = 14,
+/** 24-bit PCM signed. Windows equivalent is WAVE_FORMAT_PCM. */
+	HPI_FORMAT_PCM24_SIGNED = 15,
+/** OEM format 1 - private. */
+	HPI_FORMAT_OEM1 = 16,
+/** OEM format 2 - private. */
+	HPI_FORMAT_OEM2 = 17,
+/** Undefined format. */
+	HPI_FORMAT_UNDEFINED = 0xffff
+};
+
+/******************************************* in/out Stream states */
+/*******************************************/
+/** Stream States
+\ingroup stream
+*/
+enum HPI_STREAM_STATES {
+	/** State stopped - stream is stopped. */
+	HPI_STATE_STOPPED = 1,
+	/** State playing - stream is playing audio. */
+	HPI_STATE_PLAYING = 2,
+	/** State recording - stream is recording. */
+	HPI_STATE_RECORDING = 3,
+	/** State drained - playing stream ran out of data to play. */
+	HPI_STATE_DRAINED = 4,
+	/** State generate sine - to be implemented. */
+	HPI_STATE_SINEGEN = 5,
+	/** State wait - used for inter-card sync to mean waiting for all
+		cards to be ready. */
+	HPI_STATE_WAIT = 6
+};
+/******************************************* mixer source node types */
+/** Source node types
+\ingroup mixer
+*/
+enum HPI_SOURCENODES {
+	/** This define can be used instead of 0 to indicate
+	that there is no valid source node. A control that
+	exists on a destination node can be searched for using a source
+	node value of either 0, or HPI_SOURCENODE_NONE */
+	HPI_SOURCENODE_NONE = 100,
+	/** Out Stream (Play) node. */
+	HPI_SOURCENODE_OSTREAM = 101,
+	/** Line in node - could be analog, AES/EBU or network. */
+	HPI_SOURCENODE_LINEIN = 102,
+	HPI_SOURCENODE_AESEBU_IN = 103,	     /**< AES/EBU input node. */
+	HPI_SOURCENODE_TUNER = 104,	     /**< tuner node. */
+	HPI_SOURCENODE_RF = 105,	     /**< RF input node. */
+	HPI_SOURCENODE_CLOCK_SOURCE = 106,   /**< clock source node. */
+	HPI_SOURCENODE_RAW_BITSTREAM = 107,  /**< raw bitstream node. */
+	HPI_SOURCENODE_MICROPHONE = 108,     /**< microphone node. */
+	/** Cobranet input node -
+	    Audio samples come from the Cobranet network and into the device. */
+	HPI_SOURCENODE_COBRANET = 109,
+	HPI_SOURCENODE_ANALOG = 110,	     /**< analog input node. */
+	HPI_SOURCENODE_ADAPTER = 111,	     /**< adapter node. */
+	/* !!!Update this  AND hpidebug.h if you add a new sourcenode type!!! */
+	HPI_SOURCENODE_LAST_INDEX = 111	     /**< largest ID */
+		/* AX6 max sourcenode types = 15 */
+};
+
+/******************************************* mixer dest node types */
+/** Destination node types
+\ingroup mixer
+*/
+enum HPI_DESTNODES {
+	/** This define can be used instead of 0 to indicate
+	that there is no valid destination node. A control that
+	exists on a source node can be searched for using a destination
+	node value of either 0, or HPI_DESTNODE_NONE */
+	HPI_DESTNODE_NONE = 200,
+	/** In Stream (Record) node. */
+	HPI_DESTNODE_ISTREAM = 201,
+	HPI_DESTNODE_LINEOUT = 202,	    /**< line out node. */
+	HPI_DESTNODE_AESEBU_OUT = 203,	     /**< AES/EBU output node. */
+	HPI_DESTNODE_RF = 204,		     /**< RF output node. */
+	HPI_DESTNODE_SPEAKER = 205,	     /**< speaker output node. */
+	/** Cobranet output node -
+	    Audio samples from the device are sent out on the Cobranet network.*/
+	HPI_DESTNODE_COBRANET = 206,
+	HPI_DESTNODE_ANALOG = 207,	     /**< analog output node. */
+
+	/* !!!Update this AND hpidebug.h if you add a new destnode type!!! */
+	HPI_DESTNODE_LAST_INDEX = 207	     /**< largest ID */
+		/* AX6 max destnode types = 15 */
+};
+
+/*******************************************/
+/** Mixer control types
+\ingroup mixer
+*/
+enum HPI_CONTROLS {
+	HPI_CONTROL_GENERIC = 0,	/**< generic control. */
+	HPI_CONTROL_CONNECTION = 1, /**< A connection between nodes. */
+	HPI_CONTROL_VOLUME = 2,	      /**< volume control - works in dB_fs. */
+	HPI_CONTROL_METER = 3,	/**< peak meter control. */
+	HPI_CONTROL_MUTE = 4,	/*mute control - not used at present. */
+	HPI_CONTROL_MULTIPLEXER = 5,	/**< multiplexer control. */
+
+	HPI_CONTROL_AESEBU_TRANSMITTER = 6,	/**< AES/EBU transmitter control. */
+	HPI_CONTROL_AESEBUTX = HPI_CONTROL_AESEBU_TRANSMITTER,
+
+	HPI_CONTROL_AESEBU_RECEIVER = 7, /**< AES/EBU receiver control. */
+	HPI_CONTROL_AESEBURX = HPI_CONTROL_AESEBU_RECEIVER,
+
+	HPI_CONTROL_LEVEL = 8, /**< level/trim control - works in d_bu. */
+	HPI_CONTROL_TUNER = 9,	/**< tuner control. */
+/*      HPI_CONTROL_ONOFFSWITCH =       10 */
+	HPI_CONTROL_VOX = 11,	/**< vox control. */
+/*      HPI_CONTROL_AES18_TRANSMITTER = 12 */
+/*      HPI_CONTROL_AES18_RECEIVER = 13 */
+/*      HPI_CONTROL_AES18_BLOCKGENERATOR  = 14 */
+	HPI_CONTROL_CHANNEL_MODE = 15,	/**< channel mode control. */
+
+	HPI_CONTROL_BITSTREAM = 16,	/**< bitstream control. */
+	HPI_CONTROL_SAMPLECLOCK = 17,	/**< sample clock control. */
+	HPI_CONTROL_MICROPHONE = 18,	/**< microphone control. */
+	HPI_CONTROL_PARAMETRIC_EQ = 19,	/**< parametric EQ control. */
+	HPI_CONTROL_EQUALIZER = HPI_CONTROL_PARAMETRIC_EQ,
+
+	HPI_CONTROL_COMPANDER = 20,	/**< compander control. */
+	HPI_CONTROL_COBRANET = 21,	/**< cobranet control. */
+	HPI_CONTROL_TONEDETECTOR = 22,	/**< tone detector control. */
+	HPI_CONTROL_SILENCEDETECTOR = 23,	/**< silence detector control. */
+	HPI_CONTROL_PAD = 24,	/**< tuner PAD control. */
+	HPI_CONTROL_SRC = 25,	/**< samplerate converter control. */
+	HPI_CONTROL_UNIVERSAL = 26,	/**< universal control. */
+
+/*  !!! Update this AND hpidebug.h if you add a new control type!!!*/
+	HPI_CONTROL_LAST_INDEX = 26 /**<highest control type ID */
+/* WARNING types 256 or greater impact bit packing in all AX6 DSP code */
+};
+
+/* Shorthand names that match attribute names */
+
+/******************************************* ADAPTER ATTRIBUTES ****/
+
+/** Adapter properties
+These are used in HPI_AdapterSetProperty() and HPI_AdapterGetProperty()
+\ingroup adapter
+*/
+enum HPI_ADAPTER_PROPERTIES {
+/** \internal Used in dwProperty field of HPI_AdapterSetProperty() and
+HPI_AdapterGetProperty(). This errata applies to all ASI6000 cards with both
+analog and digital outputs. The CS4224 A/D+D/A has a one sample delay between
+left and right channels on both its input (ADC) and output (DAC).
+More details are available in Cirrus Logic errata ER284B2.
+PDF available from www.cirrus.com, released by Cirrus in 2001.
+*/
+	HPI_ADAPTER_PROPERTY_ERRATA_1 = 1,
+
+/** Adapter grouping property
+Indicates whether the adapter supports the grouping API (for ASIO and SSX2)
+*/
+	HPI_ADAPTER_PROPERTY_GROUPING = 2,
+
+/** Driver SSX2 property
+Tells the kernel driver to turn on SSX2 stream mapping.
+This feature is not used by the DSP. In fact the call is completely processed
+by the driver and is not passed on to the DSP at all.
+*/
+	HPI_ADAPTER_PROPERTY_ENABLE_SSX2 = 3,
+
+/** Adapter SSX2 property
+Indicates the state of the adapter's SSX2 setting. This setting is stored in
+non-volatile memory on the adapter. A typical call sequence would be to use
+HPI_ADAPTER_PROPERTY_SSX2_SETTING to set SSX2 on the adapter and then to reload
+the driver. The driver would query HPI_ADAPTER_PROPERTY_SSX2_SETTING during startup
+and if SSX2 is set, it would then call HPI_ADAPTER_PROPERTY_ENABLE_SSX2 to enable
+SSX2 stream mapping within the kernel level of the driver.
+*/
+	HPI_ADAPTER_PROPERTY_SSX2_SETTING = 4,
+
+/** Base number for readonly properties */
+	HPI_ADAPTER_PROPERTY_READONLYBASE = 256,
+
+/** Readonly adapter latency property.
+This property returns in the input and output latency in samples.
+Property 1 is the estimated input latency
+in samples, while Property 2 is that output latency in  samples.
+*/
+	HPI_ADAPTER_PROPERTY_LATENCY = 256,
+
+/** Readonly adapter granularity property.
+The granulariy is the smallest size chunk of stereo samples that is processed by
+the adapter.
+This property returns the record granularity in samples in Property 1.
+Property 2 returns the play granularity.
+*/
+	HPI_ADAPTER_PROPERTY_GRANULARITY = 257,
+
+/** Readonly adapter number of current channels property.
+Property 1 is the number of record channels per record device.
+Property 2 is the number of play channels per playback device.*/
+	HPI_ADAPTER_PROPERTY_CURCHANNELS = 258,
+
+/** Readonly adapter software version.
+The SOFTWARE_VERSION property returns the version of the software running
+on the adapter as Major.Minor.Release.
+Property 1 contains Major in bits 15..8 and Minor in bits 7..0.
+Property 2 contains Release in bits 7..0. */
+	HPI_ADAPTER_PROPERTY_SOFTWARE_VERSION = 259,
+
+/** Readonly adapter MAC address MSBs.
+The MAC_ADDRESS_MSB property returns
+the most significant 32 bits of the MAC address.
+Property 1 contains bits 47..32 of the MAC address.
+Property 2 contains bits 31..16 of the MAC address. */
+	HPI_ADAPTER_PROPERTY_MAC_ADDRESS_MSB = 260,
+
+/** Readonly adapter MAC address LSBs
+The MAC_ADDRESS_LSB property returns
+the least significant 16 bits of the MAC address.
+Property 1 contains bits 15..0 of the MAC address. */
+	HPI_ADAPTER_PROPERTY_MAC_ADDRESS_LSB = 261,
+
+/** Readonly extended adapter type number
+The EXTENDED_ADAPTER_TYPE property returns the 4 digits of an extended
+adapter type, i.e ASI8920-0022, 0022 is the extended type.
+The digits are returned as ASCII characters rather than the hex digits that
+are returned for the main type
+Property 1 returns the 1st two (left most) digits, i.e "00"
+in the example above, the upper byte being the left most digit.
+Property 2 returns the 2nd two digits, i.e "22" in the example above*/
+	HPI_ADAPTER_PROPERTY_EXTENDED_ADAPTER_TYPE = 262,
+
+/** Readonly debug log buffer information */
+	HPI_ADAPTER_PROPERTY_LOGTABLEN = 263,
+	HPI_ADAPTER_PROPERTY_LOGTABBEG = 264,
+
+/** Readonly adapter IP address
+For 192.168.1.101
+Property 1 returns the 1st two (left most) digits, i.e 192*256 + 168
+in the example above, the upper byte being the left most digit.
+Property 2 returns the 2nd two digits, i.e 1*256 + 101 in the example above, */
+	HPI_ADAPTER_PROPERTY_IP_ADDRESS = 265,
+
+/** Readonly adapter buffer processed count. Returns a buffer processed count
+that is incremented every time all buffers for all streams are updated. This
+is useful for checking completion of all stream operations across the adapter
+when using grouped streams.
+*/
+	HPI_ADAPTER_PROPERTY_BUFFER_UPDATE_COUNT = 266,
+
+/** Readonly mixer and stream intervals
+
+These intervals are  measured in mixer frames.
+To convert to time, divide  by the adapter samplerate.
+
+The mixer interval is the number of frames processed in one mixer iteration.
+The stream update interval is the interval at which streams check for and
+process data, and BBM host buffer counters are updated.
+
+Property 1 is the mixer interval in mixer frames.
+Property 2 is the stream update interval in mixer frames.
+*/
+	HPI_ADAPTER_PROPERTY_INTERVAL = 267,
+/** Adapter capabilities 1
+Property 1 - adapter can do multichannel (SSX1)
+Property 2 - adapter can do stream grouping (supports SSX2)
+*/
+	HPI_ADAPTER_PROPERTY_CAPS1 = 268,
+/** Adapter capabilities 2
+Property 1 - adapter can do samplerate conversion (MRX)
+Property 2 - adapter can do timestretch (TSX)
+*/
+	HPI_ADAPTER_PROPERTY_CAPS2 = 269,
+
+/** Readonly adapter sync header connection count.
+*/
+	HPI_ADAPTER_PROPERTY_SYNC_HEADER_CONNECTIONS = 270,
+/** Readonly supports SSX2 property.
+Indicates the adapter supports SSX2 in some mode setting. The
+return value is true (1) or false (0). If the current adapter
+mode is MONO SSX2 is disabled, even though this property will
+return true.
+*/
+	HPI_ADAPTER_PROPERTY_SUPPORTS_SSX2 = 271
+};
+
+/** Adapter mode commands
+
+Used in wQueryOrSet field of HPI_AdapterSetModeEx().
+\ingroup adapter
+*/
+enum HPI_ADAPTER_MODE_CMDS {
+	HPI_ADAPTER_MODE_SET = 0,
+	HPI_ADAPTER_MODE_QUERY = 1
+};
+
+/** Adapter Modes
+	These are used by HPI_AdapterSetModeEx()
+
+\warning - more than 16 possible modes breaks
+a bitmask in the Windows WAVE DLL
+\ingroup adapter
+*/
+enum HPI_ADAPTER_MODES {
+/** 4 outstream mode.
+- ASI6114: 1 instream
+- ASI6044: 4 instreams
+- ASI6012: 1 instream
+- ASI6102: no instreams
+- ASI6022, ASI6122: 2 instreams
+- ASI5111, ASI5101: 2 instreams
+- ASI652x, ASI662x: 2 instreams
+- ASI654x, ASI664x: 4 instreams
+*/
+	HPI_ADAPTER_MODE_4OSTREAM = 1,
+
+/** 6 outstream mode.
+- ASI6012: 1 instream,
+- ASI6022, ASI6122: 2 instreams
+- ASI652x, ASI662x: 4 instreams
+*/
+	HPI_ADAPTER_MODE_6OSTREAM = 2,
+
+/** 8 outstream mode.
+- ASI6114: 8 instreams
+- ASI6118: 8 instreams
+- ASI6585: 8 instreams
+*/
+	HPI_ADAPTER_MODE_8OSTREAM = 3,
+
+/** 16 outstream mode.
+- ASI6416 16 instreams
+- ASI6518, ASI6618 16 instreams
+- ASI6118 16 mono out and in streams
+*/
+	HPI_ADAPTER_MODE_16OSTREAM = 4,
+
+/** one outstream mode.
+- ASI5111 1 outstream, 1 instream
+*/
+	HPI_ADAPTER_MODE_1OSTREAM = 5,
+
+/** ASI504X mode 1. 12 outstream, 4 instream 0 to 48kHz sample rates
+	(see ASI504X datasheet for more info).
+*/
+	HPI_ADAPTER_MODE_1 = 6,
+
+/** ASI504X mode 2. 4 outstreams, 4 instreams at 0 to 192kHz sample rates
+	(see ASI504X datasheet for more info).
+*/
+	HPI_ADAPTER_MODE_2 = 7,
+
+/** ASI504X mode 3. 4 outstreams, 4 instreams at 0 to 192kHz sample rates
+	(see ASI504X datasheet for more info).
+*/
+	HPI_ADAPTER_MODE_3 = 8,
+
+/** ASI504X multichannel mode.
+	2 outstreams -> 4 line outs = 1 to 8 channel streams),
+	4 lineins -> 1 instream (1 to 8 channel streams) at 0-48kHz.
+	For more info see the SSX Specification.
+*/
+	HPI_ADAPTER_MODE_MULTICHANNEL = 9,
+
+/** 12 outstream mode.
+- ASI6514, ASI6614: 2 instreams
+- ASI6540,ASI6544: 8 instreams
+- ASI6640,ASI6644: 8 instreams
+*/
+	HPI_ADAPTER_MODE_12OSTREAM = 10,
+
+/** 9 outstream mode.
+- ASI6044: 8 instreams
+*/
+	HPI_ADAPTER_MODE_9OSTREAM = 11,
+
+/** mono mode.
+- ASI6416: 16 outstreams/instreams
+- ASI5402: 2 outstreams/instreams
+*/
+	HPI_ADAPTER_MODE_MONO = 12,
+
+/** Low latency mode.
+- ASI6416/ASI6316: 1 16 channel outstream and instream
+*/
+	HPI_ADAPTER_MODE_LOW_LATENCY = 13
+};
+
+/* Note, adapters can have more than one capability -
+encoding as bitfield is recommended. */
+#define HPI_CAPABILITY_NONE             (0)
+#define HPI_CAPABILITY_MPEG_LAYER3      (1)
+
+/* Set this equal to maximum capability index,
+Must not be greater than 32 - see axnvdef.h */
+#define HPI_CAPABILITY_MAX                      1
+/* #define HPI_CAPABILITY_AAC              2 */
+
+/******************************************* STREAM ATTRIBUTES ****/
+
+/** MPEG Ancillary Data modes
+
+The mode for the ancillary data insertion or extraction to operate in.
+\ingroup stream
+*/
+enum HPI_MPEG_ANC_MODES {
+	/** the MPEG frames have energy information stored in them (5 bytes per stereo frame, 3 per mono) */
+	HPI_MPEG_ANC_HASENERGY = 0,
+	/** the entire ancillary data field is taken up by data from the Anc data buffer
+	On encode, the encoder will insert the energy bytes before filling the remainder
+	of the ancillary data space with data from the ancillary data buffer.
+	*/
+	HPI_MPEG_ANC_RAW = 1
+};
+
+/** Ancillary Data Alignment
+\ingroup instream
+*/
+enum HPI_ISTREAM_MPEG_ANC_ALIGNS {
+	/** data is packed against the end of data, then padded to the end of frame */
+	HPI_MPEG_ANC_ALIGN_LEFT = 0,
+	/** data is packed against the end of the frame */
+	HPI_MPEG_ANC_ALIGN_RIGHT = 1
+};
+
+/** MPEG modes
+MPEG modes - can be used optionally for HPI_FormatCreate()
+parameter dwAttributes.
+
+Using any mode setting other than HPI_MPEG_MODE_DEFAULT
+with single channel format will return an error.
+\ingroup stream
+*/
+enum HPI_MPEG_MODES {
+/** Causes the MPEG-1 Layer II bitstream to be recorded
+in single_channel mode when the number of channels is 1 and in stereo when the
+number of channels is 2. */
+	HPI_MPEG_MODE_DEFAULT = 0,
+	/** Standard stereo without joint-stereo compression */
+	HPI_MPEG_MODE_STEREO = 1,
+	/** Joint stereo  */
+	HPI_MPEG_MODE_JOINTSTEREO = 2,
+	/** Left and Right channels are completely independent */
+	HPI_MPEG_MODE_DUALCHANNEL = 3
+};
+/******************************************* MIXER ATTRIBUTES ****/
+
+/* \defgroup mixer_flags Mixer flags for HPI_MIXER_GET_CONTROL_MULTIPLE_VALUES
+{
+*/
+#define HPI_MIXER_GET_CONTROL_MULTIPLE_CHANGED  (0)
+#define HPI_MIXER_GET_CONTROL_MULTIPLE_RESET    (1)
+/*}*/
+
+/** Commands used by HPI_MixerStore()
+\ingroup mixer
+*/
+enum HPI_MIXER_STORE_COMMAND {
+/** Save all mixer control settings. */
+	HPI_MIXER_STORE_SAVE = 1,
+/** Restore all controls from saved. */
+	HPI_MIXER_STORE_RESTORE = 2,
+/** Delete saved control settings. */
+	HPI_MIXER_STORE_DELETE = 3,
+/** Enable auto storage of some control settings. */
+	HPI_MIXER_STORE_ENABLE = 4,
+/** Disable auto storage of some control settings. */
+	HPI_MIXER_STORE_DISABLE = 5,
+/** Save the attributes of a single control. */
+	HPI_MIXER_STORE_SAVE_SINGLE = 6
+};
+
+/************************************* CONTROL ATTRIBUTE VALUES ****/
+/** Used by mixer plugin enable functions
+
+E.g. HPI_ParametricEQ_SetState()
+\ingroup mixer
+*/
+enum HPI_SWITCH_STATES {
+	HPI_SWITCH_OFF = 0,	/**< turn the mixer plugin on. */
+	HPI_SWITCH_ON = 1	/**< turn the mixer plugin off. */
+};
+
+/* Volume control special gain values */
+/** volumes units are 100ths of a dB
+\ingroup volume
+*/
+#define HPI_UNITS_PER_dB                100
+/** turns volume control OFF or MUTE
+\ingroup volume
+*/
+#define HPI_GAIN_OFF                    (-100 * HPI_UNITS_PER_dB)
+
+/** value returned for no signal
+\ingroup meter
+*/
+#define HPI_METER_MINIMUM               (-150 * HPI_UNITS_PER_dB)
+
+/** autofade profiles
+\ingroup volume
+*/
+enum HPI_VOLUME_AUTOFADES {
+/** log fade - dB attenuation changes linearly over time */
+	HPI_VOLUME_AUTOFADE_LOG = 2,
+/** linear fade - amplitude changes linearly */
+	HPI_VOLUME_AUTOFADE_LINEAR = 3
+};
+
+/** The physical encoding format of the AESEBU I/O.
+
+Used in HPI_AESEBU_Transmitter_SetFormat(), HPI_AESEBU_Receiver_SetFormat()
+along with related Get and Query functions
+\ingroup aestx
+*/
+enum HPI_AESEBU_FORMATS {
+/** AES/EBU physical format - AES/EBU balanced "professional"  */
+	HPI_AESEBU_FORMAT_AESEBU = 1,
+/** AES/EBU physical format - S/PDIF unbalanced "consumer"      */
+	HPI_AESEBU_FORMAT_SPDIF = 2
+};
+
+/** AES/EBU error status bits
+
+Returned by HPI_AESEBU_Receiver_GetErrorStatus()
+\ingroup aesrx
+*/
+enum HPI_AESEBU_ERRORS {
+/**  bit0: 1 when PLL is not locked */
+	HPI_AESEBU_ERROR_NOT_LOCKED = 0x01,
+/**  bit1: 1 when signal quality is poor */
+	HPI_AESEBU_ERROR_POOR_QUALITY = 0x02,
+/** bit2: 1 when there is a parity error */
+	HPI_AESEBU_ERROR_PARITY_ERROR = 0x04,
+/**  bit3: 1 when there is a bi-phase coding violation */
+	HPI_AESEBU_ERROR_BIPHASE_VIOLATION = 0x08,
+/**  bit4: 1 when the validity bit is high */
+	HPI_AESEBU_ERROR_VALIDITY = 0x10,
+/**  bit5: 1 when the CRC error bit is high */
+	HPI_AESEBU_ERROR_CRC = 0x20
+};
+
+/** \addtogroup pad
+\{
+*/
+/** The text string containing the station/channel combination. */
+#define HPI_PAD_CHANNEL_NAME_LEN        16
+/** The text string containing the artist. */
+#define HPI_PAD_ARTIST_LEN              64
+/** The text string containing the title. */
+#define HPI_PAD_TITLE_LEN               64
+/** The text string containing the comment. */
+#define HPI_PAD_COMMENT_LEN             256
+/** The PTY when the tuner has not recieved any PTY. */
+#define HPI_PAD_PROGRAM_TYPE_INVALID    0xffff
+/** \} */
+
+/** Data types for PTY string translation.
+\ingroup rds
+*/
+enum eHPI_RDS_type {
+	HPI_RDS_DATATYPE_RDS = 0,	/**< RDS bitstream.*/
+	HPI_RDS_DATATYPE_RBDS = 1	/**< RBDS bitstream.*/
+};
+
+/** Tuner bands
+
+Used for HPI_Tuner_SetBand(),HPI_Tuner_GetBand()
+\ingroup tuner
+*/
+enum HPI_TUNER_BAND {
+	HPI_TUNER_BAND_AM = 1,	 /**< AM band */
+	HPI_TUNER_BAND_FM = 2,	 /**< FM band (mono) */
+	HPI_TUNER_BAND_TV_NTSC_M = 3,	 /**< NTSC-M TV band*/
+	HPI_TUNER_BAND_TV = 3,	/* use TV_NTSC_M */
+	HPI_TUNER_BAND_FM_STEREO = 4,	 /**< FM band (stereo) */
+	HPI_TUNER_BAND_AUX = 5,	 /**< auxiliary input */
+	HPI_TUNER_BAND_TV_PAL_BG = 6,	 /**< PAL-B/G TV band*/
+	HPI_TUNER_BAND_TV_PAL_I = 7,	 /**< PAL-I TV band*/
+	HPI_TUNER_BAND_TV_PAL_DK = 8,	 /**< PAL-D/K TV band*/
+	HPI_TUNER_BAND_TV_SECAM_L = 9,	 /**< SECAM-L TV band*/
+	HPI_TUNER_BAND_LAST = 9	/**< the index of the last tuner band. */
+};
+
+/** Tuner mode attributes
+
+Used by HPI_Tuner_SetMode(), HPI_Tuner_GetMode()
+\ingroup tuner
+
+*/
+enum HPI_TUNER_MODES {
+	HPI_TUNER_MODE_RSS = 1,	/**< control  RSS */
+	HPI_TUNER_MODE_RDS = 2	/**< control  RBDS/RDS */
+};
+
+/** Tuner mode attribute values
+
+Used by HPI_Tuner_SetMode(), HPI_Tuner_GetMode()
+\ingroup tuner
+*/
+enum HPI_TUNER_MODE_VALUES {
+/* RSS attribute values */
+	HPI_TUNER_MODE_RSS_DISABLE = 0,	/**< RSS disable */
+	HPI_TUNER_MODE_RSS_ENABLE = 1,	/**< RSS enable */
+
+/* RDS mode attributes */
+	HPI_TUNER_MODE_RDS_DISABLE = 0,	/**< RDS - disabled */
+	HPI_TUNER_MODE_RDS_RDS = 1,  /**< RDS - RDS mode */
+	HPI_TUNER_MODE_RDS_RBDS = 2 /**<  RDS - RBDS mode */
+};
+
+/** Tuner Level settings
+\ingroup tuner
+*/
+enum HPI_TUNER_LEVEL {
+	HPI_TUNER_LEVEL_AVERAGE = 0,
+	HPI_TUNER_LEVEL_RAW = 1
+};
+
+/** Tuner Status Bits
+
+These bitfield values are returned by a call to HPI_Tuner_GetStatus().
+Multiple fields are returned from a single call.
+\ingroup tuner
+*/
+enum HPI_TUNER_STATUS_BITS {
+	HPI_TUNER_VIDEO_COLOR_PRESENT = 0x0001,	/**< video color is present. */
+	HPI_TUNER_VIDEO_IS_60HZ = 0x0020,	/**< 60 hz video detected. */
+	HPI_TUNER_VIDEO_HORZ_SYNC_MISSING = 0x0040,	/**< video HSYNC is missing. */
+	HPI_TUNER_VIDEO_STATUS_VALID = 0x0100,	/**< video status is valid. */
+	HPI_TUNER_PLL_LOCKED = 0x1000,		/**< the tuner's PLL is locked. */
+	HPI_TUNER_FM_STEREO = 0x2000,		/**< tuner reports back FM stereo. */
+	HPI_TUNER_DIGITAL = 0x0200,		/**< tuner reports digital programming. */
+	HPI_TUNER_MULTIPROGRAM = 0x0400		/**< tuner reports multiple programs. */
+};
+
+/** Channel Modes
+Used for HPI_ChannelModeSet/Get()
+\ingroup channelmode
+*/
+enum HPI_CHANNEL_MODES {
+/** Left channel out = left channel in, Right channel out = right channel in. */
+	HPI_CHANNEL_MODE_NORMAL = 1,
+/** Left channel out = right channel in, Right channel out = left channel in. */
+	HPI_CHANNEL_MODE_SWAP = 2,
+/** Left channel out = left channel in, Right channel out = left channel in. */
+	HPI_CHANNEL_MODE_LEFT_TO_STEREO = 3,
+/** Left channel out = right channel in, Right channel out = right channel in.*/
+	HPI_CHANNEL_MODE_RIGHT_TO_STEREO = 4,
+/** Left channel out = (left channel in + right channel in)/2,
+    Right channel out = mute. */
+	HPI_CHANNEL_MODE_STEREO_TO_LEFT = 5,
+/** Left channel out = mute,
+    Right channel out = (right channel in + left channel in)/2. */
+	HPI_CHANNEL_MODE_STEREO_TO_RIGHT = 6,
+	HPI_CHANNEL_MODE_LAST = 6
+};
+
+/** SampleClock source values
+\ingroup sampleclock
+*/
+enum HPI_SAMPLECLOCK_SOURCES {
+/** The sampleclock output is derived from its local samplerate generator.
+    The local samplerate may be set using HPI_SampleClock_SetLocalRate(). */
+	HPI_SAMPLECLOCK_SOURCE_LOCAL = 1,
+/** The adapter is clocked from a dedicated AES/EBU SampleClock input.*/
+	HPI_SAMPLECLOCK_SOURCE_AESEBU_SYNC = 2,
+/** From external wordclock connector */
+	HPI_SAMPLECLOCK_SOURCE_WORD = 3,
+/** Board-to-board header */
+	HPI_SAMPLECLOCK_SOURCE_WORD_HEADER = 4,
+/** FUTURE - SMPTE clock. */
+	HPI_SAMPLECLOCK_SOURCE_SMPTE = 5,
+/** One of the aesebu inputs */
+	HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT = 6,
+/** From a network interface e.g. Cobranet or Livewire at either 48 or 96kHz */
+	HPI_SAMPLECLOCK_SOURCE_NETWORK = 8,
+/** From previous adjacent module (ASI2416 only)*/
+	HPI_SAMPLECLOCK_SOURCE_PREV_MODULE = 10,
+/*! Update this if you add a new clock source.*/
+	HPI_SAMPLECLOCK_SOURCE_LAST = 10
+};
+
+/** Equalizer filter types. Used by HPI_ParametricEQ_SetBand()
+\ingroup parmeq
+*/
+enum HPI_FILTER_TYPE {
+	HPI_FILTER_TYPE_BYPASS = 0,	/**< filter is turned off */
+
+	HPI_FILTER_TYPE_LOWSHELF = 1,	/**< EQ low shelf */
+	HPI_FILTER_TYPE_HIGHSHELF = 2,	/**< EQ high shelf */
+	HPI_FILTER_TYPE_EQ_BAND = 3,	/**< EQ gain */
+
+	HPI_FILTER_TYPE_LOWPASS = 4,	/**< standard low pass */
+	HPI_FILTER_TYPE_HIGHPASS = 5,	/**< standard high pass */
+	HPI_FILTER_TYPE_BANDPASS = 6,	/**< standard band pass */
+	HPI_FILTER_TYPE_BANDSTOP = 7	/**< standard band stop/notch */
+};
+
+/** Async Event sources
+\ingroup async
+*/
+enum ASYNC_EVENT_SOURCES {
+	HPI_ASYNC_EVENT_GPIO = 1,	/**< GPIO event. */
+	HPI_ASYNC_EVENT_SILENCE = 2,	/**< silence event detected. */
+	HPI_ASYNC_EVENT_TONE = 3	/**< tone event detected. */
+};
+/*******************************************/
+/** HPI Error codes
+
+Almost all HPI functions return an error code
+A return value of zero means there was no error.
+Otherwise one of these error codes is returned.
+Error codes can be converted to a descriptive string using HPI_GetErrorText()
+
+\note When a new error code is added HPI_GetErrorText() MUST be updated.
+\note Codes 1-100 are reserved for driver use
+\ingroup utility
+*/
+enum HPI_ERROR_CODES {
+	/** Message type does not exist. */
+	HPI_ERROR_INVALID_TYPE = 100,
+	/** Object type does not exist. */
+	HPI_ERROR_INVALID_OBJ = 101,
+	/** Function does not exist. */
+	HPI_ERROR_INVALID_FUNC = 102,
+	/** The specified object (adapter/Stream) does not exist. */
+	HPI_ERROR_INVALID_OBJ_INDEX = 103,
+	/** Trying to access an object that has not been opened yet. */
+	HPI_ERROR_OBJ_NOT_OPEN = 104,
+	/** Trying to open an already open object. */
+	HPI_ERROR_OBJ_ALREADY_OPEN = 105,
+	/** PCI, ISA resource not valid. */
+	HPI_ERROR_INVALID_RESOURCE = 106,
+	/** GetInfo call from SubSysFindAdapters failed. */
+	HPI_ERROR_SUBSYSFINDADAPTERS_GETINFO = 107,
+	/** Default response was never updated with actual error code. */
+	HPI_ERROR_INVALID_RESPONSE = 108,
+	/** wSize field of response was not updated,
+	indicating that the message was not processed. */
+	HPI_ERROR_PROCESSING_MESSAGE = 109,
+	/** The network did not respond in a timely manner. */
+	HPI_ERROR_NETWORK_TIMEOUT = 110,
+	/** An HPI handle is invalid (uninitialised?). */
+	HPI_ERROR_INVALID_HANDLE = 111,
+	/** A function or attribute has not been implemented yet. */
+	HPI_ERROR_UNIMPLEMENTED = 112,
+	/** There are too many clients attempting to access a network resource. */
+	HPI_ERROR_NETWORK_TOO_MANY_CLIENTS = 113,
+	/** Response buffer passed to HPI_Message was smaller than returned response */
+	HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL = 114,
+	/** The returned response did not match the sent message */
+	HPI_ERROR_RESPONSE_MISMATCH = 115,
+
+	/** Too many adapters.*/
+	HPI_ERROR_TOO_MANY_ADAPTERS = 200,
+	/** Bad adpater. */
+	HPI_ERROR_BAD_ADAPTER = 201,
+	/** Adapter number out of range or not set properly. */
+	HPI_ERROR_BAD_ADAPTER_NUMBER = 202,
+	/** 2 adapters with the same adapter number. */
+	HPI_DUPLICATE_ADAPTER_NUMBER = 203,
+	/** DSP code failed to bootload. */
+	HPI_ERROR_DSP_BOOTLOAD = 204,
+	/** Adapter failed DSP code self test. */
+	HPI_ERROR_DSP_SELFTEST = 205,
+	/** Couldn't find or open the DSP code file. */
+	HPI_ERROR_DSP_FILE_NOT_FOUND = 206,
+	/** Internal DSP hardware error. */
+	HPI_ERROR_DSP_HARDWARE = 207,
+	/** Could not allocate memory in DOS. */
+	HPI_ERROR_DOS_MEMORY_ALLOC = 208,
+	/** Could not allocate memory */
+	HPI_ERROR_MEMORY_ALLOC = 208,
+	/** Failed to correctly load/config PLD .*/
+	HPI_ERROR_PLD_LOAD = 209,
+	/** Unexpected end of file, block length too big etc. */
+	HPI_ERROR_DSP_FILE_FORMAT = 210,
+
+	/** Found but could not open DSP code file. */
+	HPI_ERROR_DSP_FILE_ACCESS_DENIED = 211,
+	/** First DSP code section header not found in DSP file. */
+	HPI_ERROR_DSP_FILE_NO_HEADER = 212,
+	/** File read operation on DSP code file failed. */
+	HPI_ERROR_DSP_FILE_READ_ERROR = 213,
+	/** DSP code for adapter family not found. */
+	HPI_ERROR_DSP_SECTION_NOT_FOUND = 214,
+	/** Other OS specific error opening DSP file. */
+	HPI_ERROR_DSP_FILE_OTHER_ERROR = 215,
+	/** Sharing violation opening DSP code file. */
+	HPI_ERROR_DSP_FILE_SHARING_VIOLATION = 216,
+	/** DSP code section header had size == 0. */
+	HPI_ERROR_DSP_FILE_NULL_HEADER = 217,
+
+	/** Base number for flash errors. */
+	HPI_ERROR_FLASH = 220,
+
+	/** Flash has bad checksum */
+	HPI_ERROR_BAD_CHECKSUM = (HPI_ERROR_FLASH + 1),
+	HPI_ERROR_BAD_SEQUENCE = (HPI_ERROR_FLASH + 2),
+	HPI_ERROR_FLASH_ERASE = (HPI_ERROR_FLASH + 3),
+	HPI_ERROR_FLASH_PROGRAM = (HPI_ERROR_FLASH + 4),
+	HPI_ERROR_FLASH_VERIFY = (HPI_ERROR_FLASH + 5),
+	HPI_ERROR_FLASH_TYPE = (HPI_ERROR_FLASH + 6),
+	HPI_ERROR_FLASH_START = (HPI_ERROR_FLASH + 7),
+
+	/** Reserved for OEMs. */
+	HPI_ERROR_RESERVED_1 = 290,
+
+	/** Stream does not exist. */
+	HPI_ERROR_INVALID_STREAM = 300,
+	/** Invalid compression format. */
+	HPI_ERROR_INVALID_FORMAT = 301,
+	/** Invalid format samplerate */
+	HPI_ERROR_INVALID_SAMPLERATE = 302,
+	/** Invalid format number of channels. */
+	HPI_ERROR_INVALID_CHANNELS = 303,
+	/** Invalid format bitrate. */
+	HPI_ERROR_INVALID_BITRATE = 304,
+	/** Invalid datasize used for stream read/write. */
+	HPI_ERROR_INVALID_DATASIZE = 305,
+	/** Stream buffer is full during stream write. */
+	HPI_ERROR_BUFFER_FULL = 306,
+	/** Stream buffer is empty during stream read. */
+	HPI_ERROR_BUFFER_EMPTY = 307,
+	/** Invalid datasize used for stream read/write. */
+	HPI_ERROR_INVALID_DATA_TRANSFER = 308,
+	/** Packet ordering error for stream read/write. */
+	HPI_ERROR_INVALID_PACKET_ORDER = 309,
+
+	/** Object can't do requested operation in its current
+	state, eg set format, change rec mux state while recording.*/
+	HPI_ERROR_INVALID_OPERATION = 310,
+
+	/** Where an SRG is shared amongst streams, an incompatible samplerate is one
+	that is different to any currently playing or recording stream. */
+	HPI_ERROR_INCOMPATIBLE_SAMPLERATE = 311,
+	/** Adapter mode is illegal.*/
+	HPI_ERROR_BAD_ADAPTER_MODE = 312,
+
+	/** There have been too many attempts to set the adapter's
+	capabilities (using bad keys), the card should be returned
+	to ASI if further capabilities updates are required */
+	HPI_ERROR_TOO_MANY_CAPABILITY_CHANGE_ATTEMPTS = 313,
+	/** Streams on different adapters cannot be grouped. */
+	HPI_ERROR_NO_INTERADAPTER_GROUPS = 314,
+	/** Streams on different DSPs cannot be grouped. */
+	HPI_ERROR_NO_INTERDSP_GROUPS = 315,
+
+	/** Invalid mixer node for this adapter. */
+	HPI_ERROR_INVALID_NODE = 400,
+	/** Invalid control. */
+	HPI_ERROR_INVALID_CONTROL = 401,
+	/** Invalid control value was passed. */
+	HPI_ERROR_INVALID_CONTROL_VALUE = 402,
+	/** Control attribute not supported by this control. */
+	HPI_ERROR_INVALID_CONTROL_ATTRIBUTE = 403,
+	/** Control is disabled. */
+	HPI_ERROR_CONTROL_DISABLED = 404,
+	/** I2C transaction failed due to a missing ACK. */
+	HPI_ERROR_CONTROL_I2C_MISSING_ACK = 405,
+	/** Control is busy, or coming out of
+	reset and cannot be accessed at this time. */
+	HPI_ERROR_CONTROL_NOT_READY = 407,
+
+	/** Non volatile memory */
+	HPI_ERROR_NVMEM_BUSY = 450,
+	HPI_ERROR_NVMEM_FULL = 451,
+	HPI_ERROR_NVMEM_FAIL = 452,
+
+	/** I2C */
+	HPI_ERROR_I2C_MISSING_ACK = HPI_ERROR_CONTROL_I2C_MISSING_ACK,
+	HPI_ERROR_I2C_BAD_ADR = 460,
+
+	/** Entity errors */
+	HPI_ERROR_ENTITY_TYPE_MISMATCH = 470,
+	HPI_ERROR_ENTITY_ITEM_COUNT = 471,
+	HPI_ERROR_ENTITY_TYPE_INVALID = 472,
+	HPI_ERROR_ENTITY_ROLE_INVALID = 473,
+
+	/* AES18 specific errors were 500..507 */
+
+	/** custom error to use for debugging */
+	HPI_ERROR_CUSTOM = 600,
+
+	/** hpioct32.c can't obtain mutex */
+	HPI_ERROR_MUTEX_TIMEOUT = 700,
+
+	/** errors from HPI backends have values >= this */
+	HPI_ERROR_BACKEND_BASE = 900,
+
+	/** indicates a cached u16 value is invalid. */
+	HPI_ERROR_ILLEGAL_CACHE_VALUE = 0xffff
+};
+
+/** \defgroup maximums HPI maximum values
+\{
+*/
+/** Maximum number of adapters per HPI sub-system
+   WARNING: modifying this value changes the response structure size.*/
+#define HPI_MAX_ADAPTERS                20
+/** Maximum number of in or out streams per adapter */
+#define HPI_MAX_STREAMS                 16
+#define HPI_MAX_CHANNELS                2	/* per stream */
+#define HPI_MAX_NODES                   8	/* per mixer ? */
+#define HPI_MAX_CONTROLS                4	/* per node ? */
+/** maximum number of ancillary bytes per MPEG frame */
+#define HPI_MAX_ANC_BYTES_PER_FRAME     (64)
+#define HPI_STRING_LEN                  16
+
+/** Velocity units */
+#define HPI_OSTREAM_VELOCITY_UNITS      4096
+/** OutStream timescale units */
+#define HPI_OSTREAM_TIMESCALE_UNITS     10000
+/** OutStream timescale passthrough - turns timescaling on in passthough mode */
+#define HPI_OSTREAM_TIMESCALE_PASSTHROUGH       99999
+
+/**\}*/
+
+/* ////////////////////////////////////////////////////////////////////// */
+/* STRUCTURES */
+#ifndef DISABLE_PRAGMA_PACK1
+#pragma pack(push, 1)
+#endif
+
+/** Structure containing sample format information.
+    See also HPI_FormatCreate().
+  */
+struct hpi_format {
+	u32 sample_rate;
+				/**< 11025, 32000, 44100 ... */
+	u32 bit_rate;	      /**< for MPEG */
+	u32 attributes;
+				/**< Stereo/JointStereo/Mono */
+	u16 mode_legacy;
+				/**< Legacy ancillary mode or idle bit  */
+	u16 unused;	      /**< unused */
+	u16 channels; /**< 1,2..., (or ancillary mode or idle bit */
+	u16 format;   /**< HPI_FORMAT_PCM16, _MPEG etc. see #HPI_FORMATS. */
+};
+
+struct hpi_anc_frame {
+	u32 valid_bits_in_this_frame;
+	u8 b_data[HPI_MAX_ANC_BYTES_PER_FRAME];
+};
+
+/** An object for containing a single async event.
+*/
+struct hpi_async_event {
+	u16 event_type;	/**< type of event. \sa async_event  */
+	u16 sequence;  /**< sequence number, allows lost event detection */
+	u32 state;    /**< new state */
+	u32 h_object;	 /**< handle to the object returning the event. */
+	union {
+		struct {
+			u16 index; /**< GPIO bit index. */
+		} gpio;
+		struct {
+			u16 node_index;	/**< what node is the control on ? */
+			u16 node_type;	/**< what type of node is the control on ? */
+		} control;
+	} u;
+};
+
+/*/////////////////////////////////////////////////////////////////////////// */
+/* Public HPI Entity related definitions                                     */
+
+struct hpi_entity;
+
+enum e_entity_type {
+	entity_type_null,
+	entity_type_sequence,	/* sequence of potentially heterogeneous TLV entities */
+
+	entity_type_reference,	/* refers to a TLV entity or NULL */
+
+	entity_type_int,	/* 32 bit */
+	entity_type_float,	/* ieee754 binary 32 bit encoding */
+	entity_type_double,
+
+	entity_type_cstring,
+	entity_type_octet,
+	entity_type_ip4_address,
+	entity_type_ip6_address,
+	entity_type_mac_address,
+
+	LAST_ENTITY_TYPE
+};
+
+enum e_entity_role {
+	entity_role_null,
+	entity_role_value,
+	entity_role_classname,
+
+	entity_role_units,
+	entity_role_flags,
+	entity_role_range,
+
+	entity_role_mapping,
+	entity_role_enum,
+
+	entity_role_instance_of,
+	entity_role_depends_on,
+	entity_role_member_of_group,
+	entity_role_value_constraint,
+	entity_role_parameter_port,
+
+	entity_role_block,
+	entity_role_node_group,
+	entity_role_audio_port,
+	entity_role_clock_port,
+	LAST_ENTITY_ROLE
+};
+
+/* skip host side function declarations for
+   DSP compile and documentation extraction */
+
+struct hpi_hsubsys {
+	int not_really_used;
+};
+
+#ifndef DISABLE_PRAGMA_PACK1
+#pragma pack(pop)
+#endif
+
+/*////////////////////////////////////////////////////////////////////////// */
+/* HPI FUNCTIONS */
+
+/*/////////////////////////// */
+/* DATA and FORMAT and STREAM */
+
+u16 hpi_stream_estimate_buffer_size(struct hpi_format *pF,
+	u32 host_polling_rate_in_milli_seconds, u32 *recommended_buffer_size);
+
+/*/////////// */
+/* SUB SYSTEM */
+struct hpi_hsubsys *hpi_subsys_create(void
+	);
+
+void hpi_subsys_free(const struct hpi_hsubsys *ph_subsys);
+
+u16 hpi_subsys_get_version(const struct hpi_hsubsys *ph_subsys,
+	u32 *pversion);
+
+u16 hpi_subsys_get_version_ex(const struct hpi_hsubsys *ph_subsys,
+	u32 *pversion_ex);
+
+u16 hpi_subsys_get_info(const struct hpi_hsubsys *ph_subsys, u32 *pversion,
+	u16 *pw_num_adapters, u16 aw_adapter_list[], u16 list_length);
+
+u16 hpi_subsys_find_adapters(const struct hpi_hsubsys *ph_subsys,
+	u16 *pw_num_adapters, u16 aw_adapter_list[], u16 list_length);
+
+u16 hpi_subsys_get_num_adapters(const struct hpi_hsubsys *ph_subsys,
+	int *pn_num_adapters);
+
+u16 hpi_subsys_get_adapter(const struct hpi_hsubsys *ph_subsys, int iterator,
+	u32 *padapter_index, u16 *pw_adapter_type);
+
+u16 hpi_subsys_ssx2_bypass(const struct hpi_hsubsys *ph_subsys, u16 bypass);
+
+u16 hpi_subsys_set_host_network_interface(const struct hpi_hsubsys *ph_subsys,
+	const char *sz_interface);
+
+/*///////// */
+/* ADAPTER */
+
+u16 hpi_adapter_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index);
+
+u16 hpi_adapter_close(const struct hpi_hsubsys *ph_subsys, u16 adapter_index);
+
+u16 hpi_adapter_get_info(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 *pw_num_outstreams, u16 *pw_num_instreams,
+	u16 *pw_version, u32 *pserial_number, u16 *pw_adapter_type);
+
+u16 hpi_adapter_get_module_by_index(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 module_index, u16 *pw_num_outputs,
+	u16 *pw_num_inputs, u16 *pw_version, u32 *pserial_number,
+	u16 *pw_module_type, u32 *ph_module);
+
+u16 hpi_adapter_set_mode(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u32 adapter_mode);
+
+u16 hpi_adapter_set_mode_ex(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u32 adapter_mode, u16 query_or_set);
+
+u16 hpi_adapter_get_mode(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u32 *padapter_mode);
+
+u16 hpi_adapter_get_assert(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 *assert_present, char *psz_assert,
+	u16 *pw_line_number);
+
+u16 hpi_adapter_get_assert_ex(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 *assert_present, char *psz_assert,
+	u32 *pline_number, u16 *pw_assert_on_dsp);
+
+u16 hpi_adapter_test_assert(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 assert_id);
+
+u16 hpi_adapter_enable_capability(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 capability, u32 key);
+
+u16 hpi_adapter_self_test(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index);
+
+u16 hpi_adapter_debug_read(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u32 dsp_address, char *p_bytes, int *count_bytes);
+
+u16 hpi_adapter_set_property(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 property, u16 paramter1, u16 paramter2);
+
+u16 hpi_adapter_get_property(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 property, u16 *pw_paramter1,
+	u16 *pw_paramter2);
+
+u16 hpi_adapter_enumerate_property(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 index, u16 what_to_enumerate,
+	u16 property_index, u32 *psetting);
+
+/*////////////// */
+/* NonVol Memory */
+u16 hpi_nv_memory_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u32 *ph_nv_memory, u16 *pw_size_in_bytes);
+
+u16 hpi_nv_memory_read_byte(const struct hpi_hsubsys *ph_subsys,
+	u32 h_nv_memory, u16 index, u16 *pw_data);
+
+u16 hpi_nv_memory_write_byte(const struct hpi_hsubsys *ph_subsys,
+	u32 h_nv_memory, u16 index, u16 data);
+
+/*////////////// */
+/* Digital I/O */
+u16 hpi_gpio_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u32 *ph_gpio, u16 *pw_number_input_bits, u16 *pw_number_output_bits);
+
+u16 hpi_gpio_read_bit(const struct hpi_hsubsys *ph_subsys, u32 h_gpio,
+	u16 bit_index, u16 *pw_bit_data);
+
+u16 hpi_gpio_read_all_bits(const struct hpi_hsubsys *ph_subsys, u32 h_gpio,
+	u16 aw_all_bit_data[4]
+	);
+
+u16 hpi_gpio_write_bit(const struct hpi_hsubsys *ph_subsys, u32 h_gpio,
+	u16 bit_index, u16 bit_data);
+
+u16 hpi_gpio_write_status(const struct hpi_hsubsys *ph_subsys, u32 h_gpio,
+	u16 aw_all_bit_data[4]
+	);
+
+/**********************/
+/* Async Event Object */
+/**********************/
+u16 hpi_async_event_open(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u32 *ph_async);
+
+u16 hpi_async_event_close(const struct hpi_hsubsys *ph_subsys, u32 h_async);
+
+u16 hpi_async_event_wait(const struct hpi_hsubsys *ph_subsys, u32 h_async,
+	u16 maximum_events, struct hpi_async_event *p_events,
+	u16 *pw_number_returned);
+
+u16 hpi_async_event_get_count(const struct hpi_hsubsys *ph_subsys,
+	u32 h_async, u16 *pw_count);
+
+u16 hpi_async_event_get(const struct hpi_hsubsys *ph_subsys, u32 h_async,
+	u16 maximum_events, struct hpi_async_event *p_events,
+	u16 *pw_number_returned);
+
+/*/////////// */
+/* WATCH-DOG  */
+u16 hpi_watchdog_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u32 *ph_watchdog);
+
+u16 hpi_watchdog_set_time(const struct hpi_hsubsys *ph_subsys, u32 h_watchdog,
+	u32 time_millisec);
+
+u16 hpi_watchdog_ping(const struct hpi_hsubsys *ph_subsys, u32 h_watchdog);
+
+/**************/
+/* OUT STREAM */
+/**************/
+u16 hpi_outstream_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u16 outstream_index, u32 *ph_outstream);
+
+u16 hpi_outstream_close(const struct hpi_hsubsys *ph_subsys, u32 h_outstream);
+
+u16 hpi_outstream_get_info_ex(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u16 *pw_state, u32 *pbuffer_size, u32 *pdata_to_play,
+	u32 *psamples_played, u32 *pauxiliary_data_to_play);
+
+u16 hpi_outstream_write_buf(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, const u8 *pb_write_buf, u32 bytes_to_write,
+	const struct hpi_format *p_format);
+
+u16 hpi_outstream_start(const struct hpi_hsubsys *ph_subsys, u32 h_outstream);
+
+u16 hpi_outstream_wait_start(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream);
+
+u16 hpi_outstream_stop(const struct hpi_hsubsys *ph_subsys, u32 h_outstream);
+
+u16 hpi_outstream_sinegen(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream);
+
+u16 hpi_outstream_reset(const struct hpi_hsubsys *ph_subsys, u32 h_outstream);
+
+u16 hpi_outstream_query_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, struct hpi_format *p_format);
+
+u16 hpi_outstream_set_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, struct hpi_format *p_format);
+
+u16 hpi_outstream_set_punch_in_out(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 punch_in_sample, u32 punch_out_sample);
+
+u16 hpi_outstream_set_velocity(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, short velocity);
+
+u16 hpi_outstream_ancillary_reset(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u16 mode);
+
+u16 hpi_outstream_ancillary_get_info(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 *pframes_available);
+
+u16 hpi_outstream_ancillary_read(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, struct hpi_anc_frame *p_anc_frame_buffer,
+	u32 anc_frame_buffer_size_in_bytes,
+	u32 number_of_ancillary_frames_to_read);
+
+u16 hpi_outstream_set_time_scale(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 time_scaleX10000);
+
+u16 hpi_outstream_host_buffer_allocate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 size_in_bytes);
+
+u16 hpi_outstream_host_buffer_free(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream);
+
+u16 hpi_outstream_group_add(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 h_stream);
+
+u16 hpi_outstream_group_get_map(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 *poutstream_map, u32 *pinstream_map);
+
+u16 hpi_outstream_group_reset(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream);
+
+/*////////// */
+/* IN_STREAM */
+u16 hpi_instream_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u16 instream_index, u32 *ph_instream);
+
+u16 hpi_instream_close(const struct hpi_hsubsys *ph_subsys, u32 h_instream);
+
+u16 hpi_instream_query_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, const struct hpi_format *p_format);
+
+u16 hpi_instream_set_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, const struct hpi_format *p_format);
+
+u16 hpi_instream_read_buf(const struct hpi_hsubsys *ph_subsys, u32 h_instream,
+	u8 *pb_read_buf, u32 bytes_to_read);
+
+u16 hpi_instream_start(const struct hpi_hsubsys *ph_subsys, u32 h_instream);
+
+u16 hpi_instream_wait_start(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream);
+
+u16 hpi_instream_stop(const struct hpi_hsubsys *ph_subsys, u32 h_instream);
+
+u16 hpi_instream_reset(const struct hpi_hsubsys *ph_subsys, u32 h_instream);
+
+u16 hpi_instream_get_info_ex(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u16 *pw_state, u32 *pbuffer_size, u32 *pdata_recorded,
+	u32 *psamples_recorded, u32 *pauxiliary_data_recorded);
+
+u16 hpi_instream_ancillary_reset(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u16 bytes_per_frame, u16 mode, u16 alignment,
+	u16 idle_bit);
+
+u16 hpi_instream_ancillary_get_info(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u32 *pframe_space);
+
+u16 hpi_instream_ancillary_write(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, const struct hpi_anc_frame *p_anc_frame_buffer,
+	u32 anc_frame_buffer_size_in_bytes,
+	u32 number_of_ancillary_frames_to_write);
+
+u16 hpi_instream_host_buffer_allocate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u32 size_in_bytes);
+
+u16 hpi_instream_host_buffer_free(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream);
+
+u16 hpi_instream_group_add(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u32 h_stream);
+
+u16 hpi_instream_group_get_map(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u32 *poutstream_map, u32 *pinstream_map);
+
+u16 hpi_instream_group_reset(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream);
+
+/*********/
+/* MIXER */
+/*********/
+u16 hpi_mixer_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u32 *ph_mixer);
+
+u16 hpi_mixer_close(const struct hpi_hsubsys *ph_subsys, u32 h_mixer);
+
+u16 hpi_mixer_get_control(const struct hpi_hsubsys *ph_subsys, u32 h_mixer,
+	u16 src_node_type, u16 src_node_type_index, u16 dst_node_type,
+	u16 dst_node_type_index, u16 control_type, u32 *ph_control);
+
+u16 hpi_mixer_get_control_by_index(const struct hpi_hsubsys *ph_subsys,
+	u32 h_mixer, u16 control_index, u16 *pw_src_node_type,
+	u16 *pw_src_node_index, u16 *pw_dst_node_type, u16 *pw_dst_node_index,
+	u16 *pw_control_type, u32 *ph_control);
+
+u16 hpi_mixer_store(const struct hpi_hsubsys *ph_subsys, u32 h_mixer,
+	enum HPI_MIXER_STORE_COMMAND command, u16 index);
+/*************************/
+/* mixer CONTROLS                */
+/*************************/
+/*************************/
+/* volume control                */
+/*************************/
+u16 hpi_volume_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_gain0_01dB[HPI_MAX_CHANNELS]
+	);
+
+u16 hpi_volume_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_gain0_01dB_out[HPI_MAX_CHANNELS]
+	);
+
+#define hpi_volume_get_range hpi_volume_query_range
+u16 hpi_volume_query_range(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short *min_gain_01dB, short *max_gain_01dB, short *step_gain_01dB);
+
+u16 hpi_volume_query_channels(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_volume, u32 *p_channels);
+
+u16 hpi_volume_auto_fade(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_stop_gain0_01dB[HPI_MAX_CHANNELS], u32 duration_ms);
+
+u16 hpi_volume_auto_fade_profile(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, short an_stop_gain0_01dB[HPI_MAX_CHANNELS],
+	u32 duration_ms, u16 profile);
+
+/*************************/
+/* level control         */
+/*************************/
+u16 hpi_level_query_range(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short *min_gain_01dB, short *max_gain_01dB, short *step_gain_01dB);
+
+u16 hpi_level_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_gain0_01dB[HPI_MAX_CHANNELS]
+	);
+
+u16 hpi_level_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_gain0_01dB_out[HPI_MAX_CHANNELS]
+	);
+
+/*************************/
+/* meter control                 */
+/*************************/
+u16 hpi_meter_query_channels(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_meter, u32 *p_channels);
+
+u16 hpi_meter_get_peak(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_peak0_01dB_out[HPI_MAX_CHANNELS]
+	);
+
+u16 hpi_meter_get_rms(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_peak0_01dB_out[HPI_MAX_CHANNELS]
+	);
+
+u16 hpi_meter_set_peak_ballistics(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 attack, u16 decay);
+
+u16 hpi_meter_set_rms_ballistics(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 attack, u16 decay);
+
+u16 hpi_meter_get_peak_ballistics(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *attack, u16 *decay);
+
+u16 hpi_meter_get_rms_ballistics(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *attack, u16 *decay);
+
+/*************************/
+/* channel mode control  */
+/*************************/
+u16 hpi_channel_mode_query_mode(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_mode, const u32 index, u16 *pw_mode);
+
+u16 hpi_channel_mode_set(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u16 mode);
+
+u16 hpi_channel_mode_get(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u16 *mode);
+
+/*************************/
+/* Tuner control                 */
+/*************************/
+u16 hpi_tuner_query_band(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_tuner, const u32 index, u16 *pw_band);
+
+u16 hpi_tuner_set_band(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u16 band);
+
+u16 hpi_tuner_get_band(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u16 *pw_band);
+
+u16 hpi_tuner_query_frequency(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_tuner, const u32 index, const u16 band, u32 *pfreq);
+
+u16 hpi_tuner_set_frequency(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 freq_ink_hz);
+
+u16 hpi_tuner_get_frequency(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pw_freq_ink_hz);
+
+u16 hpi_tuner_getRF_level(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short *pw_level);
+
+u16 hpi_tuner_get_rawRF_level(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, short *pw_level);
+
+u16 hpi_tuner_query_gain(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_tuner, const u32 index, u16 *pw_gain);
+
+u16 hpi_tuner_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short gain);
+
+u16 hpi_tuner_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short *pn_gain);
+
+u16 hpi_tuner_get_status(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u16 *pw_status_mask, u16 *pw_status);
+
+u16 hpi_tuner_set_mode(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 mode, u32 value);
+
+u16 hpi_tuner_get_mode(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 mode, u32 *pn_value);
+
+u16 hpi_tuner_getRDS(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	char *p_rds_data);
+
+u16 hpi_tuner_query_deemphasis(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_tuner, const u32 index, const u16 band, u32 *pdeemphasis);
+
+u16 hpi_tuner_set_deemphasis(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 deemphasis);
+u16 hpi_tuner_get_deemphasis(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pdeemphasis);
+
+u16 hpi_tuner_query_program(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_tuner, u32 *pbitmap_program);
+
+u16 hpi_tuner_set_program(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 program);
+
+u16 hpi_tuner_get_program(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 *pprogram);
+
+u16 hpi_tuner_get_hd_radio_dsp_version(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, char *psz_dsp_version, const u32 string_size);
+
+u16 hpi_tuner_get_hd_radio_sdk_version(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, char *psz_sdk_version, const u32 string_size);
+
+u16 hpi_tuner_get_hd_radio_signal_quality(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pquality);
+
+u16 hpi_tuner_get_hd_radio_signal_blend(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pblend);
+
+u16 hpi_tuner_set_hd_radio_signal_blend(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, const u32 blend);
+
+/****************************/
+/* PADs control             */
+/****************************/
+
+u16 HPI_PAD__get_channel_name(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, char *psz_string, const u32 string_length);
+
+u16 HPI_PAD__get_artist(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	char *psz_string, const u32 string_length);
+
+u16 HPI_PAD__get_title(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	char *psz_string, const u32 string_length);
+
+u16 HPI_PAD__get_comment(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	char *psz_string, const u32 string_length);
+
+u16 HPI_PAD__get_program_type(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *ppTY);
+
+u16 HPI_PAD__get_rdsPI(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 *ppI);
+
+u16 HPI_PAD__get_program_type_string(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, const u32 data_type, const u32 pTY, char *psz_string,
+	const u32 string_length);
+
+/****************************/
+/* AES/EBU Receiver control */
+/****************************/
+u16 HPI_AESEBU__receiver_query_format(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_aes_rx, const u32 index, u16 *pw_format);
+
+u16 HPI_AESEBU__receiver_set_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 source);
+
+u16 HPI_AESEBU__receiver_get_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_source);
+
+u16 HPI_AESEBU__receiver_get_sample_rate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *psample_rate);
+
+u16 HPI_AESEBU__receiver_get_user_data(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, u16 *pw_data);
+
+u16 HPI_AESEBU__receiver_get_channel_status(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, u16 index, u16 *pw_data);
+
+u16 HPI_AESEBU__receiver_get_error_status(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_error_data);
+
+/*******************************/
+/* AES/EBU Transmitter control */
+/*******************************/
+u16 HPI_AESEBU__transmitter_set_sample_rate(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, u32 sample_rate);
+
+u16 HPI_AESEBU__transmitter_set_user_data(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, u16 data);
+
+u16 HPI_AESEBU__transmitter_set_channel_status(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, u16 index, u16 data);
+
+u16 HPI_AESEBU__transmitter_get_channel_status(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, u16 index, u16 *pw_data);
+
+u16 HPI_AESEBU__transmitter_query_format(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_aes_tx, const u32 index, u16 *pw_format);
+
+u16 HPI_AESEBU__transmitter_set_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 output_format);
+
+u16 HPI_AESEBU__transmitter_get_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_output_format);
+
+/***********************/
+/* multiplexer control */
+/***********************/
+u16 hpi_multiplexer_set_source(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 source_node_type, u16 source_node_index);
+
+u16 hpi_multiplexer_get_source(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *source_node_type, u16 *source_node_index);
+
+u16 hpi_multiplexer_query_source(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, u16 *source_node_type,
+	u16 *source_node_index);
+
+/***************/
+/* VOX control */
+/***************/
+u16 hpi_vox_set_threshold(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_gain0_01dB);
+
+u16 hpi_vox_get_threshold(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short *an_gain0_01dB);
+
+/*********************/
+/* Bitstream control */
+/*********************/
+u16 hpi_bitstream_set_clock_edge(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 edge_type);
+
+u16 hpi_bitstream_set_data_polarity(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 polarity);
+
+u16 hpi_bitstream_get_activity(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_clk_activity, u16 *pw_data_activity);
+
+/***********************/
+/* SampleClock control */
+/***********************/
+
+u16 hpi_sample_clock_query_source(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_clock, const u32 index, u16 *pw_source);
+
+u16 hpi_sample_clock_set_source(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 source);
+
+u16 hpi_sample_clock_get_source(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_source);
+
+u16 hpi_sample_clock_query_source_index(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_clock, const u32 index, const u32 source,
+	u16 *pw_source_index);
+
+u16 hpi_sample_clock_set_source_index(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 source_index);
+
+u16 hpi_sample_clock_get_source_index(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_source_index);
+
+u16 hpi_sample_clock_get_sample_rate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *psample_rate);
+
+u16 hpi_sample_clock_query_local_rate(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_clock, const u32 index, u32 *psource);
+
+u16 hpi_sample_clock_set_local_rate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 sample_rate);
+
+u16 hpi_sample_clock_get_local_rate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *psample_rate);
+
+u16 hpi_sample_clock_set_auto(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 enable);
+
+u16 hpi_sample_clock_get_auto(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *penable);
+
+u16 hpi_sample_clock_set_local_rate_lock(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 lock);
+
+u16 hpi_sample_clock_get_local_rate_lock(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *plock);
+
+/***********************/
+/* Microphone control */
+/***********************/
+u16 hpi_microphone_set_phantom_power(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 on_off);
+
+u16 hpi_microphone_get_phantom_power(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_on_off);
+
+/*******************************
+  Parametric Equalizer control
+*******************************/
+u16 hpi_parametricEQ__get_info(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_number_of_bands, u16 *pw_enabled);
+
+u16 hpi_parametricEQ__set_state(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 on_off);
+
+u16 hpi_parametricEQ__set_band(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, u16 type, u32 frequency_hz, short q100,
+	short gain0_01dB);
+
+u16 hpi_parametricEQ__get_band(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, u16 *pn_type, u32 *pfrequency_hz,
+	short *pnQ100, short *pn_gain0_01dB);
+
+u16 hpi_parametricEQ__get_coeffs(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, short coeffs[5]
+	);
+
+/*******************************
+  Compressor Expander control
+*******************************/
+
+u16 hpi_compander_set_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 on);
+
+u16 hpi_compander_get_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pon);
+
+u16 hpi_compander_set_makeup_gain(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, short makeup_gain0_01dB);
+
+u16 hpi_compander_get_makeup_gain(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, short *pn_makeup_gain0_01dB);
+
+u16 hpi_compander_set_attack_time_constant(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, u32 index, u32 attack);
+
+u16 hpi_compander_get_attack_time_constant(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, u32 index, u32 *pw_attack);
+
+u16 hpi_compander_set_decay_time_constant(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 index, u32 decay);
+
+u16 hpi_compander_get_decay_time_constant(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 index, u32 *pw_decay);
+
+u16 hpi_compander_set_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 index, short threshold0_01dB);
+
+u16 hpi_compander_get_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 index, short *pn_threshold0_01dB);
+
+u16 hpi_compander_set_ratio(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 index, u32 ratio100);
+
+u16 hpi_compander_get_ratio(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 index, u32 *pw_ratio100);
+
+/*******************************
+  Cobranet HMI control
+*******************************/
+u16 hpi_cobranet_hmi_write(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 hmi_address, u32 byte_count, u8 *pb_data);
+
+u16 hpi_cobranet_hmi_read(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 hmi_address, u32 max_byte_count, u32 *pbyte_count, u8 *pb_data);
+
+u16 hpi_cobranet_hmi_get_status(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pstatus, u32 *preadable_size,
+	u32 *pwriteable_size);
+
+/*Read the current IP address
+*/
+u16 hpi_cobranet_getI_paddress(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pi_paddress);
+
+/* Write the current IP address
+*/
+u16 hpi_cobranet_setI_paddress(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 i_paddress);
+
+/* Read the static IP address
+*/
+u16 hpi_cobranet_get_staticI_paddress(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pi_paddress);
+
+/* Write the static IP address
+*/
+u16 hpi_cobranet_set_staticI_paddress(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 i_paddress);
+
+/* Read the MAC address
+*/
+u16 hpi_cobranet_getMA_caddress(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pmAC_MS_bs, u32 *pmAC_LS_bs);
+
+/*******************************
+  Tone Detector control
+*******************************/
+u16 hpi_tone_detector_get_state(const struct hpi_hsubsys *ph_subsys, u32 hC,
+	u32 *state);
+
+u16 hpi_tone_detector_set_enable(const struct hpi_hsubsys *ph_subsys, u32 hC,
+	u32 enable);
+
+u16 hpi_tone_detector_get_enable(const struct hpi_hsubsys *ph_subsys, u32 hC,
+	u32 *enable);
+
+u16 hpi_tone_detector_set_event_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, u32 event_enable);
+
+u16 hpi_tone_detector_get_event_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, u32 *event_enable);
+
+u16 hpi_tone_detector_set_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, int threshold);
+
+u16 hpi_tone_detector_get_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, int *threshold);
+
+u16 hpi_tone_detector_get_frequency(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, u32 index, u32 *frequency);
+
+/*******************************
+  Silence Detector control
+*******************************/
+u16 hpi_silence_detector_get_state(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, u32 *state);
+
+u16 hpi_silence_detector_set_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, u32 enable);
+
+u16 hpi_silence_detector_get_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, u32 *enable);
+
+u16 hpi_silence_detector_set_event_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, u32 event_enable);
+
+u16 hpi_silence_detector_get_event_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, u32 *event_enable);
+
+u16 hpi_silence_detector_set_delay(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, u32 delay);
+
+u16 hpi_silence_detector_get_delay(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, u32 *delay);
+
+u16 hpi_silence_detector_set_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, int threshold);
+
+u16 hpi_silence_detector_get_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 hC, int *threshold);
+
+/*******************************
+  Universal control
+*******************************/
+u16 hpi_entity_find_next(struct hpi_entity *container_entity,
+	enum e_entity_type type, enum e_entity_role role, int recursive_flag,
+	struct hpi_entity **current_match);
+
+u16 hpi_entity_copy_value_from(struct hpi_entity *entity,
+	enum e_entity_type type, size_t item_count, void *value_dst_p);
+
+u16 hpi_entity_unpack(struct hpi_entity *entity, enum e_entity_type *type,
+	size_t *items, enum e_entity_role *role, void **value);
+
+u16 hpi_entity_alloc_and_pack(const enum e_entity_type type,
+	const size_t item_count, const enum e_entity_role role, void *value,
+	struct hpi_entity **entity);
+
+void hpi_entity_free(struct hpi_entity *entity);
+
+u16 hpi_universal_info(const struct hpi_hsubsys *ph_subsys, u32 hC,
+	struct hpi_entity **info);
+
+u16 hpi_universal_get(const struct hpi_hsubsys *ph_subsys, u32 hC,
+	struct hpi_entity **value);
+
+u16 hpi_universal_set(const struct hpi_hsubsys *ph_subsys, u32 hC,
+	struct hpi_entity *value);
+
+/*/////////// */
+/* DSP CLOCK  */
+/*/////////// */
+u16 hpi_clock_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u32 *ph_dsp_clock);
+
+u16 hpi_clock_set_time(const struct hpi_hsubsys *ph_subsys, u32 h_clock,
+	u16 hour, u16 minute, u16 second, u16 milli_second);
+
+u16 hpi_clock_get_time(const struct hpi_hsubsys *ph_subsys, u32 h_clock,
+	u16 *pw_hour, u16 *pw_minute, u16 *pw_second, u16 *pw_milli_second);
+
+/*/////////// */
+/* PROFILE        */
+/*/////////// */
+u16 hpi_profile_open_all(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 profile_index, u32 *ph_profile,
+	u16 *pw_max_profiles);
+
+u16 hpi_profile_get(const struct hpi_hsubsys *ph_subsys, u32 h_profile,
+	u16 index, u16 *pw_seconds, u32 *pmicro_seconds, u32 *pcall_count,
+	u32 *pmax_micro_seconds, u32 *pmin_micro_seconds);
+
+u16 hpi_profile_start_all(const struct hpi_hsubsys *ph_subsys, u32 h_profile);
+
+u16 hpi_profile_stop_all(const struct hpi_hsubsys *ph_subsys, u32 h_profile);
+
+u16 hpi_profile_get_name(const struct hpi_hsubsys *ph_subsys, u32 h_profile,
+	u16 index, char *sz_profile_name, u16 profile_name_length);
+
+u16 hpi_profile_get_utilization(const struct hpi_hsubsys *ph_subsys,
+	u32 h_profile, u32 *putilization);
+
+/*//////////////////// */
+/* UTILITY functions */
+
+u16 hpi_format_create(struct hpi_format *p_format, u16 channels, u16 format,
+	u32 sample_rate, u32 bit_rate, u32 attributes);
+
+/* Until it's verified, this function is for Windows OSs only */
+
+#endif	 /*_H_HPI_ */
+/*
+///////////////////////////////////////////////////////////////////////////////
+// See CVS for history.  Last complete set in rev 1.146
+////////////////////////////////////////////////////////////////////////////////
+*/
diff --git a/linux/sound/pci/asihpi/hpi6000.c b/linux/sound/pci/asihpi/hpi6000.c
new file mode 100644
index 0000000..1b9bf93
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpi6000.c
@@ -0,0 +1,1847 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+ Hardware Programming Interface (HPI) for AudioScience ASI6200 series adapters.
+ These PCI bus adapters are based on the TI C6711 DSP.
+
+ Exported functions:
+ void HPI_6000(struct hpi_message *phm, struct hpi_response *phr)
+
+ #defines
+ HIDE_PCI_ASSERTS to show the PCI asserts
+ PROFILE_DSP2 get profile data from DSP2 if present (instead of DSP 1)
+
+(C) Copyright AudioScience Inc. 1998-2003
+*******************************************************************************/
+#define SOURCEFILE_NAME "hpi6000.c"
+
+#include "hpi_internal.h"
+#include "hpimsginit.h"
+#include "hpidebug.h"
+#include "hpi6000.h"
+#include "hpidspcd.h"
+#include "hpicmn.h"
+
+#define HPI_HIF_BASE (0x00000200)	/* start of C67xx internal RAM */
+#define HPI_HIF_ADDR(member) \
+	(HPI_HIF_BASE + offsetof(struct hpi_hif_6000, member))
+#define HPI_HIF_ERROR_MASK      0x4000
+
+/* HPI6000 specific error codes */
+
+#define HPI6000_ERROR_BASE                              900
+#define HPI6000_ERROR_MSG_RESP_IDLE_TIMEOUT             901
+#define HPI6000_ERROR_MSG_RESP_SEND_MSG_ACK             902
+#define HPI6000_ERROR_MSG_RESP_GET_RESP_ACK             903
+#define HPI6000_ERROR_MSG_GET_ADR                       904
+#define HPI6000_ERROR_RESP_GET_ADR                      905
+#define HPI6000_ERROR_MSG_RESP_BLOCKWRITE32             906
+#define HPI6000_ERROR_MSG_RESP_BLOCKREAD32              907
+#define HPI6000_ERROR_MSG_INVALID_DSP_INDEX             908
+#define HPI6000_ERROR_CONTROL_CACHE_PARAMS              909
+
+#define HPI6000_ERROR_SEND_DATA_IDLE_TIMEOUT            911
+#define HPI6000_ERROR_SEND_DATA_ACK                     912
+#define HPI6000_ERROR_SEND_DATA_ADR                     913
+#define HPI6000_ERROR_SEND_DATA_TIMEOUT                 914
+#define HPI6000_ERROR_SEND_DATA_CMD                     915
+#define HPI6000_ERROR_SEND_DATA_WRITE                   916
+#define HPI6000_ERROR_SEND_DATA_IDLECMD                 917
+#define HPI6000_ERROR_SEND_DATA_VERIFY                  918
+
+#define HPI6000_ERROR_GET_DATA_IDLE_TIMEOUT             921
+#define HPI6000_ERROR_GET_DATA_ACK                      922
+#define HPI6000_ERROR_GET_DATA_CMD                      923
+#define HPI6000_ERROR_GET_DATA_READ                     924
+#define HPI6000_ERROR_GET_DATA_IDLECMD                  925
+
+#define HPI6000_ERROR_CONTROL_CACHE_ADDRLEN             951
+#define HPI6000_ERROR_CONTROL_CACHE_READ                952
+#define HPI6000_ERROR_CONTROL_CACHE_FLUSH               953
+
+#define HPI6000_ERROR_MSG_RESP_GETRESPCMD               961
+#define HPI6000_ERROR_MSG_RESP_IDLECMD                  962
+#define HPI6000_ERROR_MSG_RESP_BLOCKVERIFY32            963
+
+/* adapter init errors */
+#define HPI6000_ERROR_UNHANDLED_SUBSYS_ID               930
+
+/* can't access PCI2040 */
+#define HPI6000_ERROR_INIT_PCI2040                      931
+/* can't access DSP HPI i/f */
+#define HPI6000_ERROR_INIT_DSPHPI                       932
+/* can't access internal DSP memory */
+#define HPI6000_ERROR_INIT_DSPINTMEM                    933
+/* can't access SDRAM - test#1 */
+#define HPI6000_ERROR_INIT_SDRAM1                       934
+/* can't access SDRAM - test#2 */
+#define HPI6000_ERROR_INIT_SDRAM2                       935
+
+#define HPI6000_ERROR_INIT_VERIFY                       938
+
+#define HPI6000_ERROR_INIT_NOACK                        939
+
+#define HPI6000_ERROR_INIT_PLDTEST1                     941
+#define HPI6000_ERROR_INIT_PLDTEST2                     942
+
+/* local defines */
+
+#define HIDE_PCI_ASSERTS
+#define PROFILE_DSP2
+
+/* for PCI2040 i/f chip */
+/* HPI CSR registers */
+/* word offsets from CSR base */
+/* use when io addresses defined as u32 * */
+
+#define INTERRUPT_EVENT_SET     0
+#define INTERRUPT_EVENT_CLEAR   1
+#define INTERRUPT_MASK_SET      2
+#define INTERRUPT_MASK_CLEAR    3
+#define HPI_ERROR_REPORT        4
+#define HPI_RESET               5
+#define HPI_DATA_WIDTH          6
+
+#define MAX_DSPS 2
+/* HPI registers, spaced 8K bytes = 2K words apart */
+#define DSP_SPACING             0x800
+
+#define CONTROL                 0x0000
+#define ADDRESS                 0x0200
+#define DATA_AUTOINC            0x0400
+#define DATA                    0x0600
+
+#define TIMEOUT 500000
+
+struct dsp_obj {
+	__iomem u32 *prHPI_control;
+	__iomem u32 *prHPI_address;
+	__iomem u32 *prHPI_data;
+	__iomem u32 *prHPI_data_auto_inc;
+	char c_dsp_rev;		/*A, B */
+	u32 control_cache_address_on_dsp;
+	u32 control_cache_length_on_dsp;
+	struct hpi_adapter_obj *pa_parent_adapter;
+};
+
+struct hpi_hw_obj {
+	__iomem u32 *dw2040_HPICSR;
+	__iomem u32 *dw2040_HPIDSP;
+
+	u16 num_dsp;
+	struct dsp_obj ado[MAX_DSPS];
+
+	u32 message_buffer_address_on_dsp;
+	u32 response_buffer_address_on_dsp;
+	u32 pCI2040HPI_error_count;
+
+	struct hpi_control_cache_single control_cache[HPI_NMIXER_CONTROLS];
+	struct hpi_control_cache *p_cache;
+};
+
+static u16 hpi6000_dsp_block_write32(struct hpi_adapter_obj *pao,
+	u16 dsp_index, u32 hpi_address, u32 *source, u32 count);
+static u16 hpi6000_dsp_block_read32(struct hpi_adapter_obj *pao,
+	u16 dsp_index, u32 hpi_address, u32 *dest, u32 count);
+
+static short hpi6000_adapter_boot_load_dsp(struct hpi_adapter_obj *pao,
+	u32 *pos_error_code);
+static short hpi6000_check_PCI2040_error_flag(struct hpi_adapter_obj *pao,
+	u16 read_or_write);
+#define H6READ 1
+#define H6WRITE 0
+
+static short hpi6000_update_control_cache(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm);
+static short hpi6000_message_response_sequence(struct hpi_adapter_obj *pao,
+	u16 dsp_index, struct hpi_message *phm, struct hpi_response *phr);
+
+static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm,
+	struct hpi_response *phr);
+
+static short hpi6000_wait_dsp_ack(struct hpi_adapter_obj *pao, u16 dsp_index,
+	u32 ack_value);
+
+static short hpi6000_send_host_command(struct hpi_adapter_obj *pao,
+	u16 dsp_index, u32 host_cmd);
+
+static void hpi6000_send_dsp_interrupt(struct dsp_obj *pdo);
+
+static short hpi6000_send_data(struct hpi_adapter_obj *pao, u16 dsp_index,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static short hpi6000_get_data(struct hpi_adapter_obj *pao, u16 dsp_index,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void hpi_write_word(struct dsp_obj *pdo, u32 address, u32 data);
+
+static u32 hpi_read_word(struct dsp_obj *pdo, u32 address);
+
+static void hpi_write_block(struct dsp_obj *pdo, u32 address, u32 *pdata,
+	u32 length);
+
+static void hpi_read_block(struct dsp_obj *pdo, u32 address, u32 *pdata,
+	u32 length);
+
+static void subsys_create_adapter(struct hpi_message *phm,
+	struct hpi_response *phr);
+
+static void subsys_delete_adapter(struct hpi_message *phm,
+	struct hpi_response *phr);
+
+static void adapter_get_asserts(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static short create_adapter_obj(struct hpi_adapter_obj *pao,
+	u32 *pos_error_code);
+
+/* local globals */
+
+static u16 gw_pci_read_asserts;	/* used to count PCI2040 errors */
+static u16 gw_pci_write_asserts;	/* used to count PCI2040 errors */
+
+static void subsys_message(struct hpi_message *phm, struct hpi_response *phr)
+{
+
+	switch (phm->function) {
+	case HPI_SUBSYS_OPEN:
+	case HPI_SUBSYS_CLOSE:
+	case HPI_SUBSYS_GET_INFO:
+	case HPI_SUBSYS_DRIVER_UNLOAD:
+	case HPI_SUBSYS_DRIVER_LOAD:
+	case HPI_SUBSYS_FIND_ADAPTERS:
+		/* messages that should not get here */
+		phr->error = HPI_ERROR_UNIMPLEMENTED;
+		break;
+	case HPI_SUBSYS_CREATE_ADAPTER:
+		subsys_create_adapter(phm, phr);
+		break;
+	case HPI_SUBSYS_DELETE_ADAPTER:
+		subsys_delete_adapter(phm, phr);
+		break;
+	default:
+		phr->error = HPI_ERROR_INVALID_FUNC;
+		break;
+	}
+}
+
+static void control_message(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+
+	switch (phm->function) {
+	case HPI_CONTROL_GET_STATE:
+		if (pao->has_control_cache) {
+			u16 err;
+			err = hpi6000_update_control_cache(pao, phm);
+
+			if (err) {
+				phr->error = err;
+				break;
+			}
+
+			if (hpi_check_control_cache(((struct hpi_hw_obj *)
+						pao->priv)->p_cache, phm,
+					phr))
+				break;
+		}
+		hw_message(pao, phm, phr);
+		break;
+	case HPI_CONTROL_GET_INFO:
+		hw_message(pao, phm, phr);
+		break;
+	case HPI_CONTROL_SET_STATE:
+		hw_message(pao, phm, phr);
+		hpi_sync_control_cache(((struct hpi_hw_obj *)pao->priv)->
+			p_cache, phm, phr);
+		break;
+	default:
+		phr->error = HPI_ERROR_INVALID_FUNC;
+		break;
+	}
+}
+
+static void adapter_message(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	switch (phm->function) {
+	case HPI_ADAPTER_GET_INFO:
+		hw_message(pao, phm, phr);
+		break;
+	case HPI_ADAPTER_GET_ASSERT:
+		adapter_get_asserts(pao, phm, phr);
+		break;
+	case HPI_ADAPTER_OPEN:
+	case HPI_ADAPTER_CLOSE:
+	case HPI_ADAPTER_TEST_ASSERT:
+	case HPI_ADAPTER_SELFTEST:
+	case HPI_ADAPTER_GET_MODE:
+	case HPI_ADAPTER_SET_MODE:
+	case HPI_ADAPTER_FIND_OBJECT:
+	case HPI_ADAPTER_GET_PROPERTY:
+	case HPI_ADAPTER_SET_PROPERTY:
+	case HPI_ADAPTER_ENUM_PROPERTY:
+		hw_message(pao, phm, phr);
+		break;
+	default:
+		phr->error = HPI_ERROR_INVALID_FUNC;
+		break;
+	}
+}
+
+static void outstream_message(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	switch (phm->function) {
+	case HPI_OSTREAM_HOSTBUFFER_ALLOC:
+	case HPI_OSTREAM_HOSTBUFFER_FREE:
+		/* Don't let these messages go to the HW function because
+		 * they're called without allocating the spinlock.
+		 * For the HPI6000 adapters the HW would return
+		 * HPI_ERROR_INVALID_FUNC anyway.
+		 */
+		phr->error = HPI_ERROR_INVALID_FUNC;
+		break;
+	default:
+		hw_message(pao, phm, phr);
+		return;
+	}
+}
+
+static void instream_message(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+
+	switch (phm->function) {
+	case HPI_ISTREAM_HOSTBUFFER_ALLOC:
+	case HPI_ISTREAM_HOSTBUFFER_FREE:
+		/* Don't let these messages go to the HW function because
+		 * they're called without allocating the spinlock.
+		 * For the HPI6000 adapters the HW would return
+		 * HPI_ERROR_INVALID_FUNC anyway.
+		 */
+		phr->error = HPI_ERROR_INVALID_FUNC;
+		break;
+	default:
+		hw_message(pao, phm, phr);
+		return;
+	}
+}
+
+/************************************************************************/
+/** HPI_6000()
+ * Entry point from HPIMAN
+ * All calls to the HPI start here
+ */
+void HPI_6000(struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_adapter_obj *pao = NULL;
+
+	/* subsytem messages get executed by every HPI. */
+	/* All other messages are ignored unless the adapter index matches */
+	/* an adapter in the HPI */
+	HPI_DEBUG_LOG(DEBUG, "O %d,F %x\n", phm->object, phm->function);
+
+	/* if Dsp has crashed then do not communicate with it any more */
+	if (phm->object != HPI_OBJ_SUBSYSTEM) {
+		pao = hpi_find_adapter(phm->adapter_index);
+		if (!pao) {
+			HPI_DEBUG_LOG(DEBUG,
+				" %d,%d refused, for another HPI?\n",
+				phm->object, phm->function);
+			return;
+		}
+
+		if (pao->dsp_crashed >= 10) {
+			hpi_init_response(phr, phm->object, phm->function,
+				HPI_ERROR_DSP_HARDWARE);
+			HPI_DEBUG_LOG(DEBUG, " %d,%d dsp crashed.\n",
+				phm->object, phm->function);
+			return;
+		}
+	}
+	/* Init default response including the size field */
+	if (phm->function != HPI_SUBSYS_CREATE_ADAPTER)
+		hpi_init_response(phr, phm->object, phm->function,
+			HPI_ERROR_PROCESSING_MESSAGE);
+
+	switch (phm->type) {
+	case HPI_TYPE_MESSAGE:
+		switch (phm->object) {
+		case HPI_OBJ_SUBSYSTEM:
+			subsys_message(phm, phr);
+			break;
+
+		case HPI_OBJ_ADAPTER:
+			phr->size =
+				sizeof(struct hpi_response_header) +
+				sizeof(struct hpi_adapter_res);
+			adapter_message(pao, phm, phr);
+			break;
+
+		case HPI_OBJ_CONTROL:
+			control_message(pao, phm, phr);
+			break;
+
+		case HPI_OBJ_OSTREAM:
+			outstream_message(pao, phm, phr);
+			break;
+
+		case HPI_OBJ_ISTREAM:
+			instream_message(pao, phm, phr);
+			break;
+
+		default:
+			hw_message(pao, phm, phr);
+			break;
+		}
+		break;
+
+	default:
+		phr->error = HPI_ERROR_INVALID_TYPE;
+		break;
+	}
+}
+
+/************************************************************************/
+/* SUBSYSTEM */
+
+/* create an adapter object and initialise it based on resource information
+ * passed in in the message
+ * NOTE - you cannot use this function AND the FindAdapters function at the
+ * same time, the application must use only one of them to get the adapters
+ */
+static void subsys_create_adapter(struct hpi_message *phm,
+	struct hpi_response *phr)
+{
+	/* create temp adapter obj, because we don't know what index yet */
+	struct hpi_adapter_obj ao;
+	struct hpi_adapter_obj *pao;
+	u32 os_error_code;
+	short error = 0;
+	u32 dsp_index = 0;
+
+	HPI_DEBUG_LOG(VERBOSE, "subsys_create_adapter\n");
+
+	memset(&ao, 0, sizeof(ao));
+
+	/* this HPI only creates adapters for TI/PCI2040 based devices */
+	if (phm->u.s.resource.bus_type != HPI_BUS_PCI)
+		return;
+	if (phm->u.s.resource.r.pci->vendor_id != HPI_PCI_VENDOR_ID_TI)
+		return;
+	if (phm->u.s.resource.r.pci->device_id != HPI_PCI_DEV_ID_PCI2040)
+		return;
+
+	ao.priv = kzalloc(sizeof(struct hpi_hw_obj), GFP_KERNEL);
+	if (!ao.priv) {
+		HPI_DEBUG_LOG(ERROR, "cant get mem for adapter object\n");
+		phr->error = HPI_ERROR_MEMORY_ALLOC;
+		return;
+	}
+
+	/* create the adapter object based on the resource information */
+	/*? memcpy(&ao.Pci,&phm->u.s.Resource.r.Pci,sizeof(ao.Pci)); */
+	ao.pci = *phm->u.s.resource.r.pci;
+
+	error = create_adapter_obj(&ao, &os_error_code);
+	if (!error)
+		error = hpi_add_adapter(&ao);
+	if (error) {
+		phr->u.s.data = os_error_code;
+		kfree(ao.priv);
+		phr->error = error;
+		return;
+	}
+	/* need to update paParentAdapter */
+	pao = hpi_find_adapter(ao.index);
+	if (!pao) {
+		/* We just added this adapter, why can't we find it!? */
+		HPI_DEBUG_LOG(ERROR, "lost adapter after boot\n");
+		phr->error = 950;
+		return;
+	}
+
+	for (dsp_index = 0; dsp_index < MAX_DSPS; dsp_index++) {
+		struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv;
+		phw->ado[dsp_index].pa_parent_adapter = pao;
+	}
+
+	phr->u.s.aw_adapter_list[ao.index] = ao.adapter_type;
+	phr->u.s.adapter_index = ao.index;
+	phr->u.s.num_adapters++;
+	phr->error = 0;
+}
+
+static void subsys_delete_adapter(struct hpi_message *phm,
+	struct hpi_response *phr)
+{
+	struct hpi_adapter_obj *pao = NULL;
+	struct hpi_hw_obj *phw;
+
+	pao = hpi_find_adapter(phm->adapter_index);
+	if (!pao)
+		return;
+
+	phw = (struct hpi_hw_obj *)pao->priv;
+
+	if (pao->has_control_cache)
+		hpi_free_control_cache(phw->p_cache);
+
+	hpi_delete_adapter(pao);
+	kfree(phw);
+
+	phr->error = 0;
+}
+
+/* this routine is called from SubSysFindAdapter and SubSysCreateAdapter */
+static short create_adapter_obj(struct hpi_adapter_obj *pao,
+	u32 *pos_error_code)
+{
+	short boot_error = 0;
+	u32 dsp_index = 0;
+	u32 control_cache_size = 0;
+	u32 control_cache_count = 0;
+	struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv;
+
+	/* init error reporting */
+	pao->dsp_crashed = 0;
+
+	/* The PCI2040 has the following address map */
+	/* BAR0 - 4K = HPI control and status registers on PCI2040 (HPI CSR) */
+	/* BAR1 - 32K = HPI registers on DSP */
+	phw->dw2040_HPICSR = pao->pci.ap_mem_base[0];
+	phw->dw2040_HPIDSP = pao->pci.ap_mem_base[1];
+	HPI_DEBUG_LOG(VERBOSE, "csr %p, dsp %p\n", phw->dw2040_HPICSR,
+		phw->dw2040_HPIDSP);
+
+	/* set addresses for the possible DSP HPI interfaces */
+	for (dsp_index = 0; dsp_index < MAX_DSPS; dsp_index++) {
+		phw->ado[dsp_index].prHPI_control =
+			phw->dw2040_HPIDSP + (CONTROL +
+			DSP_SPACING * dsp_index);
+
+		phw->ado[dsp_index].prHPI_address =
+			phw->dw2040_HPIDSP + (ADDRESS +
+			DSP_SPACING * dsp_index);
+		phw->ado[dsp_index].prHPI_data =
+			phw->dw2040_HPIDSP + (DATA + DSP_SPACING * dsp_index);
+
+		phw->ado[dsp_index].prHPI_data_auto_inc =
+			phw->dw2040_HPIDSP + (DATA_AUTOINC +
+			DSP_SPACING * dsp_index);
+
+		HPI_DEBUG_LOG(VERBOSE, "ctl %p, adr %p, dat %p, dat++ %p\n",
+			phw->ado[dsp_index].prHPI_control,
+			phw->ado[dsp_index].prHPI_address,
+			phw->ado[dsp_index].prHPI_data,
+			phw->ado[dsp_index].prHPI_data_auto_inc);
+
+		phw->ado[dsp_index].pa_parent_adapter = pao;
+	}
+
+	phw->pCI2040HPI_error_count = 0;
+	pao->has_control_cache = 0;
+
+	/* Set the default number of DSPs on this card */
+	/* This is (conditionally) adjusted after bootloading */
+	/* of the first DSP in the bootload section. */
+	phw->num_dsp = 1;
+
+	boot_error = hpi6000_adapter_boot_load_dsp(pao, pos_error_code);
+	if (boot_error)
+		return boot_error;
+
+	HPI_DEBUG_LOG(INFO, "bootload DSP OK\n");
+
+	phw->message_buffer_address_on_dsp = 0L;
+	phw->response_buffer_address_on_dsp = 0L;
+
+	/* get info about the adapter by asking the adapter */
+	/* send a HPI_ADAPTER_GET_INFO message */
+	{
+		struct hpi_message hM;
+		struct hpi_response hR0;	/* response from DSP 0 */
+		struct hpi_response hR1;	/* response from DSP 1 */
+		u16 error = 0;
+
+		HPI_DEBUG_LOG(VERBOSE, "send ADAPTER_GET_INFO\n");
+		memset(&hM, 0, sizeof(hM));
+		hM.type = HPI_TYPE_MESSAGE;
+		hM.size = sizeof(struct hpi_message);
+		hM.object = HPI_OBJ_ADAPTER;
+		hM.function = HPI_ADAPTER_GET_INFO;
+		hM.adapter_index = 0;
+		memset(&hR0, 0, sizeof(hR0));
+		memset(&hR1, 0, sizeof(hR1));
+		hR0.size = sizeof(hR0);
+		hR1.size = sizeof(hR1);
+
+		error = hpi6000_message_response_sequence(pao, 0, &hM, &hR0);
+		if (hR0.error) {
+			HPI_DEBUG_LOG(DEBUG, "message error %d\n", hR0.error);
+			return hR0.error;
+		}
+		if (phw->num_dsp == 2) {
+			error = hpi6000_message_response_sequence(pao, 1, &hM,
+				&hR1);
+			if (error)
+				return error;
+		}
+		pao->adapter_type = hR0.u.a.adapter_type;
+		pao->index = hR0.u.a.adapter_index;
+	}
+
+	memset(&phw->control_cache[0], 0,
+		sizeof(struct hpi_control_cache_single) *
+		HPI_NMIXER_CONTROLS);
+	/* Read the control cache length to figure out if it is turned on */
+	control_cache_size =
+		hpi_read_word(&phw->ado[0],
+		HPI_HIF_ADDR(control_cache_size_in_bytes));
+	if (control_cache_size) {
+		control_cache_count =
+			hpi_read_word(&phw->ado[0],
+			HPI_HIF_ADDR(control_cache_count));
+		pao->has_control_cache = 1;
+
+		phw->p_cache =
+			hpi_alloc_control_cache(control_cache_count,
+			control_cache_size, (struct hpi_control_cache_info *)
+			&phw->control_cache[0]
+			);
+		if (!phw->p_cache)
+			pao->has_control_cache = 0;
+	} else
+		pao->has_control_cache = 0;
+
+	HPI_DEBUG_LOG(DEBUG, "get adapter info ASI%04X index %d\n",
+		pao->adapter_type, pao->index);
+	pao->open = 0;	/* upon creation the adapter is closed */
+	return 0;
+}
+
+/************************************************************************/
+/* ADAPTER */
+
+static void adapter_get_asserts(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+#ifndef HIDE_PCI_ASSERTS
+	/* if we have PCI2040 asserts then collect them */
+	if ((gw_pci_read_asserts > 0) || (gw_pci_write_asserts > 0)) {
+		phr->u.a.serial_number =
+			gw_pci_read_asserts * 100 + gw_pci_write_asserts;
+		phr->u.a.adapter_index = 1;	/* assert count */
+		phr->u.a.adapter_type = -1;	/* "dsp index" */
+		strcpy(phr->u.a.sz_adapter_assert, "PCI2040 error");
+		gw_pci_read_asserts = 0;
+		gw_pci_write_asserts = 0;
+		phr->error = 0;
+	} else
+#endif
+		hw_message(pao, phm, phr);	/*get DSP asserts */
+
+	return;
+}
+
+/************************************************************************/
+/* LOW-LEVEL */
+
+static short hpi6000_adapter_boot_load_dsp(struct hpi_adapter_obj *pao,
+	u32 *pos_error_code)
+{
+	struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv;
+	short error;
+	u32 timeout;
+	u32 read = 0;
+	u32 i = 0;
+	u32 data = 0;
+	u32 j = 0;
+	u32 test_addr = 0x80000000;
+	u32 test_data = 0x00000001;
+	u32 dw2040_reset = 0;
+	u32 dsp_index = 0;
+	u32 endian = 0;
+	u32 adapter_info = 0;
+	u32 delay = 0;
+
+	struct dsp_code dsp_code;
+	u16 boot_load_family = 0;
+
+	/* NOTE don't use wAdapterType in this routine. It is not setup yet */
+
+	switch (pao->pci.subsys_device_id) {
+	case 0x5100:
+	case 0x5110:	/* ASI5100 revB or higher with C6711D */
+	case 0x5200:	/* ASI5200 PC_ie version of ASI5100 */
+	case 0x6100:
+	case 0x6200:
+		boot_load_family = HPI_ADAPTER_FAMILY_ASI(0x6200);
+		break;
+	default:
+		return HPI6000_ERROR_UNHANDLED_SUBSYS_ID;
+	}
+
+	/* reset all DSPs, indicate two DSPs are present
+	 * set RST3-=1 to disconnect HAD8 to set DSP in little endian mode
+	 */
+	endian = 0;
+	dw2040_reset = 0x0003000F;
+	iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET);
+
+	/* read back register to make sure PCI2040 chip is functioning
+	 * note that bits 4..15 are read-only and so should always return zero,
+	 * even though we wrote 1 to them
+	 */
+	for (i = 0; i < 1000; i++)
+		delay = ioread32(phw->dw2040_HPICSR + HPI_RESET);
+	if (delay != dw2040_reset) {
+		HPI_DEBUG_LOG(ERROR, "INIT_PCI2040 %x %x\n", dw2040_reset,
+			delay);
+		return HPI6000_ERROR_INIT_PCI2040;
+	}
+
+	/* Indicate that DSP#0,1 is a C6X */
+	iowrite32(0x00000003, phw->dw2040_HPICSR + HPI_DATA_WIDTH);
+	/* set Bit30 and 29 - which will prevent Target aborts from being
+	 * issued upon HPI or GP error
+	 */
+	iowrite32(0x60000000, phw->dw2040_HPICSR + INTERRUPT_MASK_SET);
+
+	/* isolate DSP HAD8 line from PCI2040 so that
+	 * Little endian can be set by pullup
+	 */
+	dw2040_reset = dw2040_reset & (~(endian << 3));
+	iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET);
+
+	phw->ado[0].c_dsp_rev = 'B';	/* revB */
+	phw->ado[1].c_dsp_rev = 'B';	/* revB */
+
+	/*Take both DSPs out of reset, setting HAD8 to the correct Endian */
+	dw2040_reset = dw2040_reset & (~0x00000001);	/* start DSP 0 */
+	iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET);
+	dw2040_reset = dw2040_reset & (~0x00000002);	/* start DSP 1 */
+	iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET);
+
+	/* set HAD8 back to PCI2040, now that DSP set to little endian mode */
+	dw2040_reset = dw2040_reset & (~0x00000008);
+	iowrite32(dw2040_reset, phw->dw2040_HPICSR + HPI_RESET);
+	/*delay to allow DSP to get going */
+	for (i = 0; i < 100; i++)
+		delay = ioread32(phw->dw2040_HPICSR + HPI_RESET);
+
+	/* loop through all DSPs, downloading DSP code */
+	for (dsp_index = 0; dsp_index < phw->num_dsp; dsp_index++) {
+		struct dsp_obj *pdo = &phw->ado[dsp_index];
+
+		/* configure DSP so that we download code into the SRAM */
+		/* set control reg for little endian, HWOB=1 */
+		iowrite32(0x00010001, pdo->prHPI_control);
+
+		/* test access to the HPI address register (HPIA) */
+		test_data = 0x00000001;
+		for (j = 0; j < 32; j++) {
+			iowrite32(test_data, pdo->prHPI_address);
+			data = ioread32(pdo->prHPI_address);
+			if (data != test_data) {
+				HPI_DEBUG_LOG(ERROR, "INIT_DSPHPI %x %x %x\n",
+					test_data, data, dsp_index);
+				return HPI6000_ERROR_INIT_DSPHPI;
+			}
+			test_data = test_data << 1;
+		}
+
+/* if C6713 the setup PLL to generate 225MHz from 25MHz.
+* Since the PLLDIV1 read is sometimes wrong, even on a C6713,
+* we're going to do this unconditionally
+*/
+/* PLLDIV1 should have a value of 8000 after reset */
+/*
+	if (HpiReadWord(pdo,0x01B7C118) == 0x8000)
+*/
+		{
+			/* C6713 datasheet says we cannot program PLL from HPI,
+			 * and indeed if we try to set the PLL multiply from the
+			 * HPI, the PLL does not seem to lock,
+			 * so we enable the PLL and use the default of x 7
+			 */
+			/* bypass PLL */
+			hpi_write_word(pdo, 0x01B7C100, 0x0000);
+			for (i = 0; i < 100; i++)
+				delay = ioread32(phw->dw2040_HPICSR +
+					HPI_RESET);
+
+			/*  ** use default of PLL  x7 ** */
+			/* EMIF = 225/3=75MHz */
+			hpi_write_word(pdo, 0x01B7C120, 0x8002);
+			/* peri = 225/2 */
+			hpi_write_word(pdo, 0x01B7C11C, 0x8001);
+			/* cpu  = 225/1 */
+			hpi_write_word(pdo, 0x01B7C118, 0x8000);
+			/* ~200us delay */
+			for (i = 0; i < 2000; i++)
+				delay = ioread32(phw->dw2040_HPICSR +
+					HPI_RESET);
+			/* PLL not bypassed */
+			hpi_write_word(pdo, 0x01B7C100, 0x0001);
+			/* ~200us delay */
+			for (i = 0; i < 2000; i++)
+				delay = ioread32(phw->dw2040_HPICSR +
+					HPI_RESET);
+		}
+
+		/* test r/w to internal DSP memory
+		 * C6711 has L2 cache mapped to 0x0 when reset
+		 *
+		 *  revB - because of bug 3.0.1 last HPI read
+		 * (before HPI address issued) must be non-autoinc
+		 */
+		/* test each bit in the 32bit word */
+		for (i = 0; i < 100; i++) {
+			test_addr = 0x00000000;
+			test_data = 0x00000001;
+			for (j = 0; j < 32; j++) {
+				hpi_write_word(pdo, test_addr + i, test_data);
+				data = hpi_read_word(pdo, test_addr + i);
+				if (data != test_data) {
+					HPI_DEBUG_LOG(ERROR,
+						"DSP mem %x %x %x %x\n",
+						test_addr + i, test_data,
+						data, dsp_index);
+
+					return HPI6000_ERROR_INIT_DSPINTMEM;
+				}
+				test_data = test_data << 1;
+			}
+		}
+
+		/* memory map of ASI6200
+		   00000000-0000FFFF    16Kx32 internal program
+		   01800000-019FFFFF    Internal peripheral
+		   80000000-807FFFFF    CE0 2Mx32 SDRAM running @ 100MHz
+		   90000000-9000FFFF    CE1 Async peripherals:
+
+		   EMIF config
+		   ------------
+		   Global EMIF control
+		   0 -
+		   1 -
+		   2 -
+		   3 CLK2EN = 1   CLKOUT2 enabled
+		   4 CLK1EN = 0   CLKOUT1 disabled
+		   5 EKEN = 1 <--!! C6713 specific, enables ECLKOUT
+		   6 -
+		   7 NOHOLD = 1   external HOLD disabled
+		   8 HOLDA = 0    HOLDA output is low
+		   9 HOLD = 0             HOLD input is low
+		   10 ARDY = 1    ARDY input is high
+		   11 BUSREQ = 0   BUSREQ output is low
+		   12,13 Reserved = 1
+		 */
+		hpi_write_word(pdo, 0x01800000, 0x34A8);
+
+		/* EMIF CE0 setup - 2Mx32 Sync DRAM
+		   31..28       Wr setup
+		   27..22       Wr strobe
+		   21..20       Wr hold
+		   19..16       Rd setup
+		   15..14       -
+		   13..8        Rd strobe
+		   7..4         MTYPE   0011            Sync DRAM 32bits
+		   3            Wr hold MSB
+		   2..0         Rd hold
+		 */
+		hpi_write_word(pdo, 0x01800008, 0x00000030);
+
+		/* EMIF SDRAM Extension
+		   31-21        0
+		   20           WR2RD = 0
+		   19-18        WR2DEAC = 1
+		   17           WR2WR = 0
+		   16-15        R2WDQM = 2
+		   14-12        RD2WR = 4
+		   11-10        RD2DEAC = 1
+		   9            RD2RD = 1
+		   8-7          THZP = 10b
+		   6-5          TWR  = 2-1 = 01b (tWR = 10ns)
+		   4            TRRD = 0b = 2 ECLK (tRRD = 14ns)
+		   3-1          TRAS = 5-1 = 100b (Tras=42ns = 5 ECLK)
+		   1            CAS latency = 3 ECLK
+		   (for Micron 2M32-7 operating at 100Mhz)
+		 */
+
+		/* need to use this else DSP code crashes */
+		hpi_write_word(pdo, 0x01800020, 0x001BDF29);
+
+		/* EMIF SDRAM control - set up for a 2Mx32 SDRAM (512x32x4 bank)
+		   31           -               -
+		   30           SDBSZ   1               4 bank
+		   29..28       SDRSZ   00              11 row address pins
+		   27..26       SDCSZ   01              8 column address pins
+		   25           RFEN    1               refersh enabled
+		   24           INIT    1               init SDRAM
+		   23..20       TRCD    0001
+		   19..16       TRP             0001
+		   15..12       TRC             0110
+		   11..0        -               -
+		 */
+		/*      need to use this else DSP code crashes */
+		hpi_write_word(pdo, 0x01800018, 0x47117000);
+
+		/* EMIF SDRAM Refresh Timing */
+		hpi_write_word(pdo, 0x0180001C, 0x00000410);
+
+		/*MIF CE1 setup - Async peripherals
+		   @100MHz bus speed, each cycle is 10ns,
+		   31..28       Wr setup  = 1
+		   27..22       Wr strobe = 3                   30ns
+		   21..20       Wr hold = 1
+		   19..16       Rd setup =1
+		   15..14       Ta = 2
+		   13..8        Rd strobe = 3                   30ns
+		   7..4         MTYPE   0010            Async 32bits
+		   3            Wr hold MSB =0
+		   2..0         Rd hold = 1
+		 */
+		{
+			u32 cE1 =
+				(1L << 28) | (3L << 22) | (1L << 20) | (1L <<
+				16) | (2L << 14) | (3L << 8) | (2L << 4) | 1L;
+			hpi_write_word(pdo, 0x01800004, cE1);
+		}
+
+		/* delay a little to allow SDRAM and DSP to "get going" */
+
+		for (i = 0; i < 1000; i++)
+			delay = ioread32(phw->dw2040_HPICSR + HPI_RESET);
+
+		/* test access to SDRAM */
+		{
+			test_addr = 0x80000000;
+			test_data = 0x00000001;
+			/* test each bit in the 32bit word */
+			for (j = 0; j < 32; j++) {
+				hpi_write_word(pdo, test_addr, test_data);
+				data = hpi_read_word(pdo, test_addr);
+				if (data != test_data) {
+					HPI_DEBUG_LOG(ERROR,
+						"DSP dram %x %x %x %x\n",
+						test_addr, test_data, data,
+						dsp_index);
+
+					return HPI6000_ERROR_INIT_SDRAM1;
+				}
+				test_data = test_data << 1;
+			}
+			/* test every Nth address in the DRAM */
+#define DRAM_SIZE_WORDS 0x200000	/*2_mx32 */
+#define DRAM_INC 1024
+			test_addr = 0x80000000;
+			test_data = 0x0;
+			for (i = 0; i < DRAM_SIZE_WORDS; i = i + DRAM_INC) {
+				hpi_write_word(pdo, test_addr + i, test_data);
+				test_data++;
+			}
+			test_addr = 0x80000000;
+			test_data = 0x0;
+			for (i = 0; i < DRAM_SIZE_WORDS; i = i + DRAM_INC) {
+				data = hpi_read_word(pdo, test_addr + i);
+				if (data != test_data) {
+					HPI_DEBUG_LOG(ERROR,
+						"DSP dram %x %x %x %x\n",
+						test_addr + i, test_data,
+						data, dsp_index);
+					return HPI6000_ERROR_INIT_SDRAM2;
+				}
+				test_data++;
+			}
+
+		}
+
+		/* write the DSP code down into the DSPs memory */
+		/*HpiDspCode_Open(nBootLoadFamily,&DspCode,pdwOsErrorCode); */
+		dsp_code.ps_dev = pao->pci.p_os_data;
+
+		error = hpi_dsp_code_open(boot_load_family, &dsp_code,
+			pos_error_code);
+
+		if (error)
+			return error;
+
+		while (1) {
+			u32 length;
+			u32 address;
+			u32 type;
+			u32 *pcode;
+
+			error = hpi_dsp_code_read_word(&dsp_code, &length);
+			if (error)
+				break;
+			if (length == 0xFFFFFFFF)
+				break;	/* end of code */
+
+			error = hpi_dsp_code_read_word(&dsp_code, &address);
+			if (error)
+				break;
+			error = hpi_dsp_code_read_word(&dsp_code, &type);
+			if (error)
+				break;
+			error = hpi_dsp_code_read_block(length, &dsp_code,
+				&pcode);
+			if (error)
+				break;
+			error = hpi6000_dsp_block_write32(pao, (u16)dsp_index,
+				address, pcode, length);
+			if (error)
+				break;
+		}
+
+		if (error) {
+			hpi_dsp_code_close(&dsp_code);
+			return error;
+		}
+		/* verify that code was written correctly */
+		/* this time through, assume no errors in DSP code file/array */
+		hpi_dsp_code_rewind(&dsp_code);
+		while (1) {
+			u32 length;
+			u32 address;
+			u32 type;
+			u32 *pcode;
+
+			hpi_dsp_code_read_word(&dsp_code, &length);
+			if (length == 0xFFFFFFFF)
+				break;	/* end of code */
+
+			hpi_dsp_code_read_word(&dsp_code, &address);
+			hpi_dsp_code_read_word(&dsp_code, &type);
+			hpi_dsp_code_read_block(length, &dsp_code, &pcode);
+
+			for (i = 0; i < length; i++) {
+				data = hpi_read_word(pdo, address);
+				if (data != *pcode) {
+					error = HPI6000_ERROR_INIT_VERIFY;
+					HPI_DEBUG_LOG(ERROR,
+						"DSP verify %x %x %x %x\n",
+						address, *pcode, data,
+						dsp_index);
+					break;
+				}
+				pcode++;
+				address += 4;
+			}
+			if (error)
+				break;
+		}
+		hpi_dsp_code_close(&dsp_code);
+		if (error)
+			return error;
+
+		/* zero out the hostmailbox */
+		{
+			u32 address = HPI_HIF_ADDR(host_cmd);
+			for (i = 0; i < 4; i++) {
+				hpi_write_word(pdo, address, 0);
+				address += 4;
+			}
+		}
+		/* write the DSP number into the hostmailbox */
+		/* structure before starting the DSP */
+		hpi_write_word(pdo, HPI_HIF_ADDR(dsp_number), dsp_index);
+
+		/* write the DSP adapter Info into the */
+		/* hostmailbox before starting the DSP */
+		if (dsp_index > 0)
+			hpi_write_word(pdo, HPI_HIF_ADDR(adapter_info),
+				adapter_info);
+
+		/* step 3. Start code by sending interrupt */
+		iowrite32(0x00030003, pdo->prHPI_control);
+		for (i = 0; i < 10000; i++)
+			delay = ioread32(phw->dw2040_HPICSR + HPI_RESET);
+
+		/* wait for a non-zero value in hostcmd -
+		 * indicating initialization is complete
+		 *
+		 * Init could take a while if DSP checks SDRAM memory
+		 * Was 200000. Increased to 2000000 for ASI8801 so we
+		 * don't get 938 errors.
+		 */
+		timeout = 2000000;
+		while (timeout) {
+			do {
+				read = hpi_read_word(pdo,
+					HPI_HIF_ADDR(host_cmd));
+			} while (--timeout
+				&& hpi6000_check_PCI2040_error_flag(pao,
+					H6READ));
+
+			if (read)
+				break;
+			/* The following is a workaround for bug #94:
+			 * Bluescreen on install and subsequent boots on a
+			 * DELL PowerEdge 600SC PC with 1.8GHz P4 and
+			 * ServerWorks chipset. Without this delay the system
+			 * locks up with a bluescreen (NOT GPF or pagefault).
+			 */
+			else
+				hpios_delay_micro_seconds(1000);
+		}
+		if (timeout == 0)
+			return HPI6000_ERROR_INIT_NOACK;
+
+		/* read the DSP adapter Info from the */
+		/* hostmailbox structure after starting the DSP */
+		if (dsp_index == 0) {
+			/*u32 dwTestData=0; */
+			u32 mask = 0;
+
+			adapter_info =
+				hpi_read_word(pdo,
+				HPI_HIF_ADDR(adapter_info));
+			if (HPI_ADAPTER_FAMILY_ASI
+				(HPI_HIF_ADAPTER_INFO_EXTRACT_ADAPTER
+					(adapter_info)) ==
+				HPI_ADAPTER_FAMILY_ASI(0x6200))
+				/* all 6200 cards have this many DSPs */
+				phw->num_dsp = 2;
+
+			/* test that the PLD is programmed */
+			/* and we can read/write 24bits */
+#define PLD_BASE_ADDRESS 0x90000000L	/*for ASI6100/6200/8800 */
+
+			switch (boot_load_family) {
+			case HPI_ADAPTER_FAMILY_ASI(0x6200):
+				/* ASI6100/6200 has 24bit path to FPGA */
+				mask = 0xFFFFFF00L;
+				/* ASI5100 uses AX6 code, */
+				/* but has no PLD r/w register to test */
+				if (HPI_ADAPTER_FAMILY_ASI(pao->pci.
+						subsys_device_id) ==
+					HPI_ADAPTER_FAMILY_ASI(0x5100))
+					mask = 0x00000000L;
+				/* ASI5200 uses AX6 code, */
+				/* but has no PLD r/w register to test */
+				if (HPI_ADAPTER_FAMILY_ASI(pao->pci.
+						subsys_device_id) ==
+					HPI_ADAPTER_FAMILY_ASI(0x5200))
+					mask = 0x00000000L;
+				break;
+			case HPI_ADAPTER_FAMILY_ASI(0x8800):
+				/* ASI8800 has 16bit path to FPGA */
+				mask = 0xFFFF0000L;
+				break;
+			}
+			test_data = 0xAAAAAA00L & mask;
+			/* write to 24 bit Debug register (D31-D8) */
+			hpi_write_word(pdo, PLD_BASE_ADDRESS + 4L, test_data);
+			read = hpi_read_word(pdo,
+				PLD_BASE_ADDRESS + 4L) & mask;
+			if (read != test_data) {
+				HPI_DEBUG_LOG(ERROR, "PLD %x %x\n", test_data,
+					read);
+				return HPI6000_ERROR_INIT_PLDTEST1;
+			}
+			test_data = 0x55555500L & mask;
+			hpi_write_word(pdo, PLD_BASE_ADDRESS + 4L, test_data);
+			read = hpi_read_word(pdo,
+				PLD_BASE_ADDRESS + 4L) & mask;
+			if (read != test_data) {
+				HPI_DEBUG_LOG(ERROR, "PLD %x %x\n", test_data,
+					read);
+				return HPI6000_ERROR_INIT_PLDTEST2;
+			}
+		}
+	}	/* for numDSP */
+	return 0;
+}
+
+#define PCI_TIMEOUT 100
+
+static int hpi_set_address(struct dsp_obj *pdo, u32 address)
+{
+	u32 timeout = PCI_TIMEOUT;
+
+	do {
+		iowrite32(address, pdo->prHPI_address);
+	} while (hpi6000_check_PCI2040_error_flag(pdo->pa_parent_adapter,
+			H6WRITE)
+		&& --timeout);
+
+	if (timeout)
+		return 0;
+
+	return 1;
+}
+
+/* write one word to the HPI port */
+static void hpi_write_word(struct dsp_obj *pdo, u32 address, u32 data)
+{
+	if (hpi_set_address(pdo, address))
+		return;
+	iowrite32(data, pdo->prHPI_data);
+}
+
+/* read one word from the HPI port */
+static u32 hpi_read_word(struct dsp_obj *pdo, u32 address)
+{
+	u32 data = 0;
+
+	if (hpi_set_address(pdo, address))
+		return 0;	/*? no way to return error */
+
+	/* take care of errata in revB DSP (2.0.1) */
+	data = ioread32(pdo->prHPI_data);
+	return data;
+}
+
+/* write a block of 32bit words to the DSP HPI port using auto-inc mode */
+static void hpi_write_block(struct dsp_obj *pdo, u32 address, u32 *pdata,
+	u32 length)
+{
+	u16 length16 = length - 1;
+
+	if (length == 0)
+		return;
+
+	if (hpi_set_address(pdo, address))
+		return;
+
+	iowrite32_rep(pdo->prHPI_data_auto_inc, pdata, length16);
+
+	/* take care of errata in revB DSP (2.0.1) */
+	/* must end with non auto-inc */
+	iowrite32(*(pdata + length - 1), pdo->prHPI_data);
+}
+
+/** read a block of 32bit words from the DSP HPI port using auto-inc mode
+ */
+static void hpi_read_block(struct dsp_obj *pdo, u32 address, u32 *pdata,
+	u32 length)
+{
+	u16 length16 = length - 1;
+
+	if (length == 0)
+		return;
+
+	if (hpi_set_address(pdo, address))
+		return;
+
+	ioread32_rep(pdo->prHPI_data_auto_inc, pdata, length16);
+
+	/* take care of errata in revB DSP (2.0.1) */
+	/* must end with non auto-inc */
+	*(pdata + length - 1) = ioread32(pdo->prHPI_data);
+}
+
+static u16 hpi6000_dsp_block_write32(struct hpi_adapter_obj *pao,
+	u16 dsp_index, u32 hpi_address, u32 *source, u32 count)
+{
+	struct dsp_obj *pdo =
+		&(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index];
+	u32 time_out = PCI_TIMEOUT;
+	int c6711_burst_size = 128;
+	u32 local_hpi_address = hpi_address;
+	int local_count = count;
+	int xfer_size;
+	u32 *pdata = source;
+
+	while (local_count) {
+		if (local_count > c6711_burst_size)
+			xfer_size = c6711_burst_size;
+		else
+			xfer_size = local_count;
+
+		time_out = PCI_TIMEOUT;
+		do {
+			hpi_write_block(pdo, local_hpi_address, pdata,
+				xfer_size);
+		} while (hpi6000_check_PCI2040_error_flag(pao, H6WRITE)
+			&& --time_out);
+
+		if (!time_out)
+			break;
+		pdata += xfer_size;
+		local_hpi_address += sizeof(u32) * xfer_size;
+		local_count -= xfer_size;
+	}
+
+	if (time_out)
+		return 0;
+	else
+		return 1;
+}
+
+static u16 hpi6000_dsp_block_read32(struct hpi_adapter_obj *pao,
+	u16 dsp_index, u32 hpi_address, u32 *dest, u32 count)
+{
+	struct dsp_obj *pdo =
+		&(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index];
+	u32 time_out = PCI_TIMEOUT;
+	int c6711_burst_size = 16;
+	u32 local_hpi_address = hpi_address;
+	int local_count = count;
+	int xfer_size;
+	u32 *pdata = dest;
+	u32 loop_count = 0;
+
+	while (local_count) {
+		if (local_count > c6711_burst_size)
+			xfer_size = c6711_burst_size;
+		else
+			xfer_size = local_count;
+
+		time_out = PCI_TIMEOUT;
+		do {
+			hpi_read_block(pdo, local_hpi_address, pdata,
+				xfer_size);
+		} while (hpi6000_check_PCI2040_error_flag(pao, H6READ)
+			&& --time_out);
+		if (!time_out)
+			break;
+
+		pdata += xfer_size;
+		local_hpi_address += sizeof(u32) * xfer_size;
+		local_count -= xfer_size;
+		loop_count++;
+	}
+
+	if (time_out)
+		return 0;
+	else
+		return 1;
+}
+
+static short hpi6000_message_response_sequence(struct hpi_adapter_obj *pao,
+	u16 dsp_index, struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv;
+	struct dsp_obj *pdo = &phw->ado[dsp_index];
+	u32 timeout;
+	u16 ack;
+	u32 address;
+	u32 length;
+	u32 *p_data;
+	u16 error = 0;
+
+	/* does the DSP we are referencing exist? */
+	if (dsp_index >= phw->num_dsp)
+		return HPI6000_ERROR_MSG_INVALID_DSP_INDEX;
+
+	ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_IDLE);
+	if (ack & HPI_HIF_ERROR_MASK) {
+		pao->dsp_crashed++;
+		return HPI6000_ERROR_MSG_RESP_IDLE_TIMEOUT;
+	}
+	pao->dsp_crashed = 0;
+
+	/* send the message */
+
+	/* get the address and size */
+	if (phw->message_buffer_address_on_dsp == 0) {
+		timeout = TIMEOUT;
+		do {
+			address =
+				hpi_read_word(pdo,
+				HPI_HIF_ADDR(message_buffer_address));
+			phw->message_buffer_address_on_dsp = address;
+		} while (hpi6000_check_PCI2040_error_flag(pao, H6READ)
+			&& --timeout);
+		if (!timeout)
+			return HPI6000_ERROR_MSG_GET_ADR;
+	} else
+		address = phw->message_buffer_address_on_dsp;
+
+	/*        dwLength = sizeof(struct hpi_message); */
+	length = phm->size;
+
+	/* send it */
+	p_data = (u32 *)phm;
+	if (hpi6000_dsp_block_write32(pao, dsp_index, address, p_data,
+			(u16)length / 4))
+		return HPI6000_ERROR_MSG_RESP_BLOCKWRITE32;
+
+	if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_GET_RESP))
+		return HPI6000_ERROR_MSG_RESP_GETRESPCMD;
+	hpi6000_send_dsp_interrupt(pdo);
+
+	ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_GET_RESP);
+	if (ack & HPI_HIF_ERROR_MASK)
+		return HPI6000_ERROR_MSG_RESP_GET_RESP_ACK;
+
+	/* get the address and size */
+	if (phw->response_buffer_address_on_dsp == 0) {
+		timeout = TIMEOUT;
+		do {
+			address =
+				hpi_read_word(pdo,
+				HPI_HIF_ADDR(response_buffer_address));
+		} while (hpi6000_check_PCI2040_error_flag(pao, H6READ)
+			&& --timeout);
+		phw->response_buffer_address_on_dsp = address;
+
+		if (!timeout)
+			return HPI6000_ERROR_RESP_GET_ADR;
+	} else
+		address = phw->response_buffer_address_on_dsp;
+
+	/* read the length of the response back from the DSP */
+	timeout = TIMEOUT;
+	do {
+		length = hpi_read_word(pdo, HPI_HIF_ADDR(length));
+	} while (hpi6000_check_PCI2040_error_flag(pao, H6READ) && --timeout);
+	if (!timeout)
+		length = sizeof(struct hpi_response);
+
+	/* get it */
+	p_data = (u32 *)phr;
+	if (hpi6000_dsp_block_read32(pao, dsp_index, address, p_data,
+			(u16)length / 4))
+		return HPI6000_ERROR_MSG_RESP_BLOCKREAD32;
+
+	/* set i/f back to idle */
+	if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_IDLE))
+		return HPI6000_ERROR_MSG_RESP_IDLECMD;
+	hpi6000_send_dsp_interrupt(pdo);
+
+	error = hpi_validate_response(phm, phr);
+	return error;
+}
+
+/* have to set up the below defines to match stuff in the MAP file */
+
+#define MSG_ADDRESS (HPI_HIF_BASE+0x18)
+#define MSG_LENGTH 11
+#define RESP_ADDRESS (HPI_HIF_BASE+0x44)
+#define RESP_LENGTH 16
+#define QUEUE_START  (HPI_HIF_BASE+0x88)
+#define QUEUE_SIZE 0x8000
+
+static short hpi6000_send_data_check_adr(u32 address, u32 length_in_dwords)
+{
+/*#define CHECKING       // comment this line in to enable checking */
+#ifdef CHECKING
+	if (address < (u32)MSG_ADDRESS)
+		return 0;
+	if (address > (u32)(QUEUE_START + QUEUE_SIZE))
+		return 0;
+	if ((address + (length_in_dwords << 2)) >
+		(u32)(QUEUE_START + QUEUE_SIZE))
+		return 0;
+#else
+	(void)address;
+	(void)length_in_dwords;
+	return 1;
+#endif
+}
+
+static short hpi6000_send_data(struct hpi_adapter_obj *pao, u16 dsp_index,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct dsp_obj *pdo =
+		&(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index];
+	u32 data_sent = 0;
+	u16 ack;
+	u32 length, address;
+	u32 *p_data = (u32 *)phm->u.d.u.data.pb_data;
+	u16 time_out = 8;
+
+	(void)phr;
+
+	/* round dwDataSize down to nearest 4 bytes */
+	while ((data_sent < (phm->u.d.u.data.data_size & ~3L))
+		&& --time_out) {
+		ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_IDLE);
+		if (ack & HPI_HIF_ERROR_MASK)
+			return HPI6000_ERROR_SEND_DATA_IDLE_TIMEOUT;
+
+		if (hpi6000_send_host_command(pao, dsp_index,
+				HPI_HIF_SEND_DATA))
+			return HPI6000_ERROR_SEND_DATA_CMD;
+
+		hpi6000_send_dsp_interrupt(pdo);
+
+		ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_SEND_DATA);
+
+		if (ack & HPI_HIF_ERROR_MASK)
+			return HPI6000_ERROR_SEND_DATA_ACK;
+
+		do {
+			/* get the address and size */
+			address = hpi_read_word(pdo, HPI_HIF_ADDR(address));
+			/* DSP returns number of DWORDS */
+			length = hpi_read_word(pdo, HPI_HIF_ADDR(length));
+		} while (hpi6000_check_PCI2040_error_flag(pao, H6READ));
+
+		if (!hpi6000_send_data_check_adr(address, length))
+			return HPI6000_ERROR_SEND_DATA_ADR;
+
+		/* send the data. break data into 512 DWORD blocks (2K bytes)
+		 * and send using block write. 2Kbytes is the max as this is the
+		 * memory window given to the HPI data register by the PCI2040
+		 */
+
+		{
+			u32 len = length;
+			u32 blk_len = 512;
+			while (len) {
+				if (len < blk_len)
+					blk_len = len;
+				if (hpi6000_dsp_block_write32(pao, dsp_index,
+						address, p_data, blk_len))
+					return HPI6000_ERROR_SEND_DATA_WRITE;
+				address += blk_len * 4;
+				p_data += blk_len;
+				len -= blk_len;
+			}
+		}
+
+		if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_IDLE))
+			return HPI6000_ERROR_SEND_DATA_IDLECMD;
+
+		hpi6000_send_dsp_interrupt(pdo);
+
+		data_sent += length * 4;
+	}
+	if (!time_out)
+		return HPI6000_ERROR_SEND_DATA_TIMEOUT;
+	return 0;
+}
+
+static short hpi6000_get_data(struct hpi_adapter_obj *pao, u16 dsp_index,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct dsp_obj *pdo =
+		&(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index];
+	u32 data_got = 0;
+	u16 ack;
+	u32 length, address;
+	u32 *p_data = (u32 *)phm->u.d.u.data.pb_data;
+
+	(void)phr;	/* this parameter not used! */
+
+	/* round dwDataSize down to nearest 4 bytes */
+	while (data_got < (phm->u.d.u.data.data_size & ~3L)) {
+		ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_IDLE);
+		if (ack & HPI_HIF_ERROR_MASK)
+			return HPI6000_ERROR_GET_DATA_IDLE_TIMEOUT;
+
+		if (hpi6000_send_host_command(pao, dsp_index,
+				HPI_HIF_GET_DATA))
+			return HPI6000_ERROR_GET_DATA_CMD;
+		hpi6000_send_dsp_interrupt(pdo);
+
+		ack = hpi6000_wait_dsp_ack(pao, dsp_index, HPI_HIF_GET_DATA);
+
+		if (ack & HPI_HIF_ERROR_MASK)
+			return HPI6000_ERROR_GET_DATA_ACK;
+
+		/* get the address and size */
+		do {
+			address = hpi_read_word(pdo, HPI_HIF_ADDR(address));
+			length = hpi_read_word(pdo, HPI_HIF_ADDR(length));
+		} while (hpi6000_check_PCI2040_error_flag(pao, H6READ));
+
+		/* read the data */
+		{
+			u32 len = length;
+			u32 blk_len = 512;
+			while (len) {
+				if (len < blk_len)
+					blk_len = len;
+				if (hpi6000_dsp_block_read32(pao, dsp_index,
+						address, p_data, blk_len))
+					return HPI6000_ERROR_GET_DATA_READ;
+				address += blk_len * 4;
+				p_data += blk_len;
+				len -= blk_len;
+			}
+		}
+
+		if (hpi6000_send_host_command(pao, dsp_index, HPI_HIF_IDLE))
+			return HPI6000_ERROR_GET_DATA_IDLECMD;
+		hpi6000_send_dsp_interrupt(pdo);
+
+		data_got += length * 4;
+	}
+	return 0;
+}
+
+static void hpi6000_send_dsp_interrupt(struct dsp_obj *pdo)
+{
+	iowrite32(0x00030003, pdo->prHPI_control);	/* DSPINT */
+}
+
+static short hpi6000_send_host_command(struct hpi_adapter_obj *pao,
+	u16 dsp_index, u32 host_cmd)
+{
+	struct dsp_obj *pdo =
+		&(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index];
+	u32 timeout = TIMEOUT;
+
+	/* set command */
+	do {
+		hpi_write_word(pdo, HPI_HIF_ADDR(host_cmd), host_cmd);
+		/* flush the FIFO */
+		hpi_set_address(pdo, HPI_HIF_ADDR(host_cmd));
+	} while (hpi6000_check_PCI2040_error_flag(pao, H6WRITE) && --timeout);
+
+	/* reset the interrupt bit */
+	iowrite32(0x00040004, pdo->prHPI_control);
+
+	if (timeout)
+		return 0;
+	else
+		return 1;
+}
+
+/* if the PCI2040 has recorded an HPI timeout, reset the error and return 1 */
+static short hpi6000_check_PCI2040_error_flag(struct hpi_adapter_obj *pao,
+	u16 read_or_write)
+{
+	u32 hPI_error;
+
+	struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv;
+
+	/* read the error bits from the PCI2040 */
+	hPI_error = ioread32(phw->dw2040_HPICSR + HPI_ERROR_REPORT);
+	if (hPI_error) {
+		/* reset the error flag */
+		iowrite32(0L, phw->dw2040_HPICSR + HPI_ERROR_REPORT);
+		phw->pCI2040HPI_error_count++;
+		if (read_or_write == 1)
+			gw_pci_read_asserts++;	   /************* inc global */
+		else
+			gw_pci_write_asserts++;
+		return 1;
+	} else
+		return 0;
+}
+
+static short hpi6000_wait_dsp_ack(struct hpi_adapter_obj *pao, u16 dsp_index,
+	u32 ack_value)
+{
+	struct dsp_obj *pdo =
+		&(*(struct hpi_hw_obj *)pao->priv).ado[dsp_index];
+	u32 ack = 0L;
+	u32 timeout;
+	u32 hPIC = 0L;
+
+	/* wait for host interrupt to signal ack is ready */
+	timeout = TIMEOUT;
+	while (--timeout) {
+		hPIC = ioread32(pdo->prHPI_control);
+		if (hPIC & 0x04)	/* 0x04 = HINT from DSP */
+			break;
+	}
+	if (timeout == 0)
+		return HPI_HIF_ERROR_MASK;
+
+	/* wait for dwAckValue */
+	timeout = TIMEOUT;
+	while (--timeout) {
+		/* read the ack mailbox */
+		ack = hpi_read_word(pdo, HPI_HIF_ADDR(dsp_ack));
+		if (ack == ack_value)
+			break;
+		if ((ack & HPI_HIF_ERROR_MASK)
+			&& !hpi6000_check_PCI2040_error_flag(pao, H6READ))
+			break;
+		/*for (i=0;i<1000;i++) */
+		/*      dwPause=i+1; */
+	}
+	if (ack & HPI_HIF_ERROR_MASK)
+		/* indicates bad read from DSP -
+		   typically 0xffffff is read for some reason */
+		ack = HPI_HIF_ERROR_MASK;
+
+	if (timeout == 0)
+		ack = HPI_HIF_ERROR_MASK;
+	return (short)ack;
+}
+
+static short hpi6000_update_control_cache(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm)
+{
+	const u16 dsp_index = 0;
+	struct hpi_hw_obj *phw = (struct hpi_hw_obj *)pao->priv;
+	struct dsp_obj *pdo = &phw->ado[dsp_index];
+	u32 timeout;
+	u32 cache_dirty_flag;
+	u16 err;
+
+	hpios_dsplock_lock(pao);
+
+	timeout = TIMEOUT;
+	do {
+		cache_dirty_flag =
+			hpi_read_word((struct dsp_obj *)pdo,
+			HPI_HIF_ADDR(control_cache_is_dirty));
+	} while (hpi6000_check_PCI2040_error_flag(pao, H6READ) && --timeout);
+	if (!timeout) {
+		err = HPI6000_ERROR_CONTROL_CACHE_PARAMS;
+		goto unlock;
+	}
+
+	if (cache_dirty_flag) {
+		/* read the cached controls */
+		u32 address;
+		u32 length;
+
+		timeout = TIMEOUT;
+		if (pdo->control_cache_address_on_dsp == 0) {
+			do {
+				address =
+					hpi_read_word((struct dsp_obj *)pdo,
+					HPI_HIF_ADDR(control_cache_address));
+
+				length = hpi_read_word((struct dsp_obj *)pdo,
+					HPI_HIF_ADDR
+					(control_cache_size_in_bytes));
+			} while (hpi6000_check_PCI2040_error_flag(pao, H6READ)
+				&& --timeout);
+			if (!timeout) {
+				err = HPI6000_ERROR_CONTROL_CACHE_ADDRLEN;
+				goto unlock;
+			}
+			pdo->control_cache_address_on_dsp = address;
+			pdo->control_cache_length_on_dsp = length;
+		} else {
+			address = pdo->control_cache_address_on_dsp;
+			length = pdo->control_cache_length_on_dsp;
+		}
+
+		if (hpi6000_dsp_block_read32(pao, dsp_index, address,
+				(u32 *)&phw->control_cache[0],
+				length / sizeof(u32))) {
+			err = HPI6000_ERROR_CONTROL_CACHE_READ;
+			goto unlock;
+		}
+		do {
+			hpi_write_word((struct dsp_obj *)pdo,
+				HPI_HIF_ADDR(control_cache_is_dirty), 0);
+			/* flush the FIFO */
+			hpi_set_address(pdo, HPI_HIF_ADDR(host_cmd));
+		} while (hpi6000_check_PCI2040_error_flag(pao, H6WRITE)
+			&& --timeout);
+		if (!timeout) {
+			err = HPI6000_ERROR_CONTROL_CACHE_FLUSH;
+			goto unlock;
+		}
+
+	}
+	err = 0;
+
+unlock:
+	hpios_dsplock_unlock(pao);
+	return err;
+}
+
+/** Get dsp index for multi DSP adapters only */
+static u16 get_dsp_index(struct hpi_adapter_obj *pao, struct hpi_message *phm)
+{
+	u16 ret = 0;
+	switch (phm->object) {
+	case HPI_OBJ_ISTREAM:
+		if (phm->obj_index < 2)
+			ret = 1;
+		break;
+	case HPI_OBJ_PROFILE:
+		ret = phm->obj_index;
+		break;
+	default:
+		break;
+	}
+	return ret;
+}
+
+/** Complete transaction with DSP
+
+Send message, get response, send or get stream data if any.
+*/
+static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm,
+	struct hpi_response *phr)
+{
+	u16 error = 0;
+	u16 dsp_index = 0;
+	u16 num_dsp = ((struct hpi_hw_obj *)pao->priv)->num_dsp;
+
+	if (num_dsp < 2)
+		dsp_index = 0;
+	else {
+		dsp_index = get_dsp_index(pao, phm);
+
+		/* is this  checked on the DSP anyway? */
+		if ((phm->function == HPI_ISTREAM_GROUP_ADD)
+			|| (phm->function == HPI_OSTREAM_GROUP_ADD)) {
+			struct hpi_message hm;
+			u16 add_index;
+			hm.obj_index = phm->u.d.u.stream.stream_index;
+			hm.object = phm->u.d.u.stream.object_type;
+			add_index = get_dsp_index(pao, &hm);
+			if (add_index != dsp_index) {
+				phr->error = HPI_ERROR_NO_INTERDSP_GROUPS;
+				return;
+			}
+		}
+	}
+
+	hpios_dsplock_lock(pao);
+	error = hpi6000_message_response_sequence(pao, dsp_index, phm, phr);
+
+	/* maybe an error response */
+	if (error) {
+		/* something failed in the HPI/DSP interface */
+		phr->error = error;
+		/* just the header of the response is valid */
+		phr->size = sizeof(struct hpi_response_header);
+		goto err;
+	}
+
+	if (phr->error != 0)	/* something failed in the DSP */
+		goto err;
+
+	switch (phm->function) {
+	case HPI_OSTREAM_WRITE:
+	case HPI_ISTREAM_ANC_WRITE:
+		error = hpi6000_send_data(pao, dsp_index, phm, phr);
+		break;
+	case HPI_ISTREAM_READ:
+	case HPI_OSTREAM_ANC_READ:
+		error = hpi6000_get_data(pao, dsp_index, phm, phr);
+		break;
+	case HPI_ADAPTER_GET_ASSERT:
+		phr->u.a.adapter_index = 0;	/* dsp 0 default */
+		if (num_dsp == 2) {
+			if (!phr->u.a.adapter_type) {
+				/* no assert from dsp 0, check dsp 1 */
+				error = hpi6000_message_response_sequence(pao,
+					1, phm, phr);
+				phr->u.a.adapter_index = 1;
+			}
+		}
+	}
+
+	if (error)
+		phr->error = error;
+
+err:
+	hpios_dsplock_unlock(pao);
+	return;
+}
diff --git a/linux/sound/pci/asihpi/hpi6000.h b/linux/sound/pci/asihpi/hpi6000.h
new file mode 100644
index 0000000..4c7d507
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpi6000.h
@@ -0,0 +1,70 @@
+/*****************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+Public declarations for DSP Proramming Interface to TI C6701
+
+Shared between hpi6000.c and DSP code
+
+(C) Copyright AudioScience Inc. 1998-2003
+******************************************************************************/
+
+#ifndef _HPI6000_H_
+#define _HPI6000_H_
+
+#define HPI_NMIXER_CONTROLS 200
+
+/*
+ * Control caching is always supported in the HPI code.
+ * The DSP should make sure that dwControlCacheSizeInBytes is initialized to 0
+ * during boot to make it in-active.
+ */
+struct hpi_hif_6000 {
+	u32 host_cmd;
+	u32 dsp_ack;
+	u32 address;
+	u32 length;
+	u32 message_buffer_address;
+	u32 response_buffer_address;
+	u32 dsp_number;
+	u32 adapter_info;
+	u32 control_cache_is_dirty;
+	u32 control_cache_address;
+	u32 control_cache_size_in_bytes;
+	u32 control_cache_count;
+};
+
+#define HPI_HIF_PACK_ADAPTER_INFO(adapter, version_major, version_minor) \
+		((adapter << 16) | (version_major << 8) | version_minor)
+#define HPI_HIF_ADAPTER_INFO_EXTRACT_ADAPTER(adapterinfo) \
+		((adapterinfo >> 16) & 0xffff)
+#define HPI_HIF_ADAPTER_INFO_EXTRACT_HWVERSION_MAJOR(adapterinfo) \
+		((adapterinfo >> 8) & 0xff)
+#define HPI_HIF_ADAPTER_INFO_EXTRACT_HWVERSION_MINOR(adapterinfo) \
+		(adapterinfo & 0xff)
+
+/* Command/status exchanged between host and DSP */
+#define HPI_HIF_IDLE            0
+#define HPI_HIF_SEND_MSG        1
+#define HPI_HIF_GET_RESP        2
+#define HPI_HIF_DATA_MASK       0x10
+#define HPI_HIF_SEND_DATA       0x13
+#define HPI_HIF_GET_DATA        0x14
+#define HPI_HIF_SEND_DONE       5
+#define HPI_HIF_RESET           9
+
+#endif				/* _HPI6000_H_ */
diff --git a/linux/sound/pci/asihpi/hpi6205.c b/linux/sound/pci/asihpi/hpi6205.c
new file mode 100644
index 0000000..2672f65
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpi6205.c
@@ -0,0 +1,2331 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+ Hardware Programming Interface (HPI) for AudioScience
+ ASI50xx, AS51xx, ASI6xxx, ASI87xx ASI89xx series adapters.
+ These PCI and PCIe bus adapters are based on a
+ TMS320C6205 PCI bus mastering DSP,
+ and (except ASI50xx) TI TMS320C6xxx floating point DSP
+
+ Exported function:
+ void HPI_6205(struct hpi_message *phm, struct hpi_response *phr)
+
+(C) Copyright AudioScience Inc. 1998-2010
+*******************************************************************************/
+#define SOURCEFILE_NAME "hpi6205.c"
+
+#include "hpi_internal.h"
+#include "hpimsginit.h"
+#include "hpidebug.h"
+#include "hpi6205.h"
+#include "hpidspcd.h"
+#include "hpicmn.h"
+
+/*****************************************************************************/
+/* HPI6205 specific error codes */
+#define HPI6205_ERROR_BASE                      1000
+/*#define HPI6205_ERROR_MEM_ALLOC 1001 */
+#define HPI6205_ERROR_6205_NO_IRQ               1002
+#define HPI6205_ERROR_6205_INIT_FAILED          1003
+/*#define HPI6205_ERROR_MISSING_DSPCODE 1004 */
+#define HPI6205_ERROR_UNKNOWN_PCI_DEVICE        1005
+#define HPI6205_ERROR_6205_REG                  1006
+#define HPI6205_ERROR_6205_DSPPAGE              1007
+#define HPI6205_ERROR_BAD_DSPINDEX              1008
+#define HPI6205_ERROR_C6713_HPIC                1009
+#define HPI6205_ERROR_C6713_HPIA                1010
+#define HPI6205_ERROR_C6713_PLL                 1011
+#define HPI6205_ERROR_DSP_INTMEM                1012
+#define HPI6205_ERROR_DSP_EXTMEM                1013
+#define HPI6205_ERROR_DSP_PLD                   1014
+#define HPI6205_ERROR_MSG_RESP_IDLE_TIMEOUT     1015
+#define HPI6205_ERROR_MSG_RESP_TIMEOUT          1016
+#define HPI6205_ERROR_6205_EEPROM               1017
+#define HPI6205_ERROR_DSP_EMIF                  1018
+
+#define hpi6205_error(dsp_index, err) (err)
+/*****************************************************************************/
+/* for C6205 PCI i/f */
+/* Host Status Register (HSR) bitfields */
+#define C6205_HSR_INTSRC        0x01
+#define C6205_HSR_INTAVAL       0x02
+#define C6205_HSR_INTAM         0x04
+#define C6205_HSR_CFGERR        0x08
+#define C6205_HSR_EEREAD        0x10
+/* Host-to-DSP Control Register (HDCR) bitfields */
+#define C6205_HDCR_WARMRESET    0x01
+#define C6205_HDCR_DSPINT       0x02
+#define C6205_HDCR_PCIBOOT      0x04
+/* DSP Page Register (DSPP) bitfields, */
+/* defines 4 Mbyte page that BAR0 points to */
+#define C6205_DSPP_MAP1         0x400
+
+/* BAR0 maps to prefetchable 4 Mbyte memory block set by DSPP.
+ * BAR1 maps to non-prefetchable 8 Mbyte memory block
+ * of DSP memory mapped registers (starting at 0x01800000).
+ * 0x01800000 is hardcoded in the PCI i/f, so that only the offset from this
+ * needs to be added to the BAR1 base address set in the PCI config reg
+ */
+#define C6205_BAR1_PCI_IO_OFFSET (0x027FFF0L)
+#define C6205_BAR1_HSR  (C6205_BAR1_PCI_IO_OFFSET)
+#define C6205_BAR1_HDCR (C6205_BAR1_PCI_IO_OFFSET+4)
+#define C6205_BAR1_DSPP (C6205_BAR1_PCI_IO_OFFSET+8)
+
+/* used to control LED (revA) and reset C6713 (revB) */
+#define C6205_BAR0_TIMER1_CTL (0x01980000L)
+
+/* For first 6713 in CE1 space, using DA17,16,2 */
+#define HPICL_ADDR      0x01400000L
+#define HPICH_ADDR      0x01400004L
+#define HPIAL_ADDR      0x01410000L
+#define HPIAH_ADDR      0x01410004L
+#define HPIDIL_ADDR     0x01420000L
+#define HPIDIH_ADDR     0x01420004L
+#define HPIDL_ADDR      0x01430000L
+#define HPIDH_ADDR      0x01430004L
+
+#define C6713_EMIF_GCTL         0x01800000
+#define C6713_EMIF_CE1          0x01800004
+#define C6713_EMIF_CE0          0x01800008
+#define C6713_EMIF_CE2          0x01800010
+#define C6713_EMIF_CE3          0x01800014
+#define C6713_EMIF_SDRAMCTL     0x01800018
+#define C6713_EMIF_SDRAMTIMING  0x0180001C
+#define C6713_EMIF_SDRAMEXT     0x01800020
+
+struct hpi_hw_obj {
+	/* PCI registers */
+	__iomem u32 *prHSR;
+	__iomem u32 *prHDCR;
+	__iomem u32 *prDSPP;
+
+	u32 dsp_page;
+
+	struct consistent_dma_area h_locked_mem;
+	struct bus_master_interface *p_interface_buffer;
+
+	u16 flag_outstream_just_reset[HPI_MAX_STREAMS];
+	/* a non-NULL handle means there is an HPI allocated buffer */
+	struct consistent_dma_area instream_host_buffers[HPI_MAX_STREAMS];
+	struct consistent_dma_area outstream_host_buffers[HPI_MAX_STREAMS];
+	/* non-zero size means a buffer exists, may be external */
+	u32 instream_host_buffer_size[HPI_MAX_STREAMS];
+	u32 outstream_host_buffer_size[HPI_MAX_STREAMS];
+
+	struct consistent_dma_area h_control_cache;
+	struct consistent_dma_area h_async_event_buffer;
+/*      struct hpi_control_cache_single *pControlCache; */
+	struct hpi_async_event *p_async_event_buffer;
+	struct hpi_control_cache *p_cache;
+};
+
+/*****************************************************************************/
+/* local prototypes */
+
+#define check_before_bbm_copy(status, p_bbm_data, l_first_write, l_second_write)
+
+static int wait_dsp_ack(struct hpi_hw_obj *phw, int state, int timeout_us);
+
+static void send_dsp_command(struct hpi_hw_obj *phw, int cmd);
+
+static u16 adapter_boot_load_dsp(struct hpi_adapter_obj *pao,
+	u32 *pos_error_code);
+
+static u16 message_response_sequence(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm,
+	struct hpi_response *phr);
+
+#define HPI6205_TIMEOUT 1000000
+
+static void subsys_create_adapter(struct hpi_message *phm,
+	struct hpi_response *phr);
+static void subsys_delete_adapter(struct hpi_message *phm,
+	struct hpi_response *phr);
+
+static u16 create_adapter_obj(struct hpi_adapter_obj *pao,
+	u32 *pos_error_code);
+
+static void delete_adapter_obj(struct hpi_adapter_obj *pao);
+
+static void outstream_host_buffer_allocate(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void outstream_host_buffer_get_info(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void outstream_host_buffer_free(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+static void outstream_write(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void outstream_get_info(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void outstream_start(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void outstream_open(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void outstream_reset(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void instream_host_buffer_allocate(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void instream_host_buffer_get_info(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void instream_host_buffer_free(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void instream_read(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void instream_get_info(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static void instream_start(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr);
+
+static u32 boot_loader_read_mem32(struct hpi_adapter_obj *pao, int dsp_index,
+	u32 address);
+
+static u16 boot_loader_write_mem32(struct hpi_adapter_obj *pao, int dsp_index,
+	u32 address, u32 data);
+
+static u16 boot_loader_config_emif(struct hpi_adapter_obj *pao,
+	int dsp_index);
+
+static u16 boot_loader_test_memory(struct hpi_adapter_obj *pao, int dsp_index,
+	u32 address, u32 length);
+
+static u16 boot_loader_test_internal_memory(struct hpi_adapter_obj *pao,
+	int dsp_index);
+
+static u16 boot_loader_test_external_memory(struct hpi_adapter_obj *pao,
+	int dsp_index);
+
+static u16 boot_loader_test_pld(struct hpi_adapter_obj *pao, int dsp_index);
+
+/*****************************************************************************/
+
+static void subsys_message(struct hpi_message *phm, struct hpi_response *phr)
+{
+
+	switch (phm->function) {
+	case HPI_SUBSYS_OPEN:
+	case HPI_SUBSYS_CLOSE:
+	case HPI_SUBSYS_GET_INFO:
+	case HPI_SUBSYS_DRIVER_UNLOAD:
+	case HPI_SUBSYS_DRIVER_LOAD:
+	case HPI_SUBSYS_FIND_ADAPTERS:
+		/* messages that should not get here */
+		phr->error = HPI_ERROR_UNIMPLEMENTED;
+		break;
+	case HPI_SUBSYS_CREATE_ADAPTER:
+		subsys_create_adapter(phm, phr);
+		break;
+	case HPI_SUBSYS_DELETE_ADAPTER:
+		subsys_delete_adapter(phm, phr);
+		break;
+	default:
+		phr->error = HPI_ERROR_INVALID_FUNC;
+		break;
+	}
+}
+
+static void control_message(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+
+	struct hpi_hw_obj *phw = pao->priv;
+
+	switch (phm->function) {
+	case HPI_CONTROL_GET_STATE:
+		if (pao->has_control_cache) {
+			rmb();	/* make sure we see updates DM_aed from DSP */
+			if (hpi_check_control_cache(phw->p_cache, phm, phr))
+				break;
+		}
+		hw_message(pao, phm, phr);
+		break;
+	case HPI_CONTROL_GET_INFO:
+		hw_message(pao, phm, phr);
+		break;
+	case HPI_CONTROL_SET_STATE:
+		hw_message(pao, phm, phr);
+		if (pao->has_control_cache)
+			hpi_sync_control_cache(phw->p_cache, phm, phr);
+		break;
+	default:
+		phr->error = HPI_ERROR_INVALID_FUNC;
+		break;
+	}
+}
+
+static void adapter_message(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	switch (phm->function) {
+	default:
+		hw_message(pao, phm, phr);
+		break;
+	}
+}
+
+static void outstream_message(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+
+	if (phm->obj_index >= HPI_MAX_STREAMS) {
+		phr->error = HPI_ERROR_INVALID_STREAM;
+		HPI_DEBUG_LOG(WARNING,
+			"message referencing invalid stream %d "
+			"on adapter index %d\n", phm->obj_index,
+			phm->adapter_index);
+		return;
+	}
+
+	switch (phm->function) {
+	case HPI_OSTREAM_WRITE:
+		outstream_write(pao, phm, phr);
+		break;
+	case HPI_OSTREAM_GET_INFO:
+		outstream_get_info(pao, phm, phr);
+		break;
+	case HPI_OSTREAM_HOSTBUFFER_ALLOC:
+		outstream_host_buffer_allocate(pao, phm, phr);
+		break;
+	case HPI_OSTREAM_HOSTBUFFER_GET_INFO:
+		outstream_host_buffer_get_info(pao, phm, phr);
+		break;
+	case HPI_OSTREAM_HOSTBUFFER_FREE:
+		outstream_host_buffer_free(pao, phm, phr);
+		break;
+	case HPI_OSTREAM_START:
+		outstream_start(pao, phm, phr);
+		break;
+	case HPI_OSTREAM_OPEN:
+		outstream_open(pao, phm, phr);
+		break;
+	case HPI_OSTREAM_RESET:
+		outstream_reset(pao, phm, phr);
+		break;
+	default:
+		hw_message(pao, phm, phr);
+		break;
+	}
+}
+
+static void instream_message(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+
+	if (phm->obj_index >= HPI_MAX_STREAMS) {
+		phr->error = HPI_ERROR_INVALID_STREAM;
+		HPI_DEBUG_LOG(WARNING,
+			"message referencing invalid stream %d "
+			"on adapter index %d\n", phm->obj_index,
+			phm->adapter_index);
+		return;
+	}
+
+	switch (phm->function) {
+	case HPI_ISTREAM_READ:
+		instream_read(pao, phm, phr);
+		break;
+	case HPI_ISTREAM_GET_INFO:
+		instream_get_info(pao, phm, phr);
+		break;
+	case HPI_ISTREAM_HOSTBUFFER_ALLOC:
+		instream_host_buffer_allocate(pao, phm, phr);
+		break;
+	case HPI_ISTREAM_HOSTBUFFER_GET_INFO:
+		instream_host_buffer_get_info(pao, phm, phr);
+		break;
+	case HPI_ISTREAM_HOSTBUFFER_FREE:
+		instream_host_buffer_free(pao, phm, phr);
+		break;
+	case HPI_ISTREAM_START:
+		instream_start(pao, phm, phr);
+		break;
+	default:
+		hw_message(pao, phm, phr);
+		break;
+	}
+}
+
+/*****************************************************************************/
+/** Entry point to this HPI backend
+ * All calls to the HPI start here
+ */
+void HPI_6205(struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_adapter_obj *pao = NULL;
+
+	/* subsytem messages are processed by every HPI.
+	 * All other messages are ignored unless the adapter index matches
+	 * an adapter in the HPI
+	 */
+	HPI_DEBUG_LOG(DEBUG, "HPI obj=%d, func=%d\n", phm->object,
+		phm->function);
+
+	/* if Dsp has crashed then do not communicate with it any more */
+	if (phm->object != HPI_OBJ_SUBSYSTEM) {
+		pao = hpi_find_adapter(phm->adapter_index);
+		if (!pao) {
+			HPI_DEBUG_LOG(DEBUG,
+				" %d,%d refused, for another HPI?\n",
+				phm->object, phm->function);
+			return;
+		}
+
+		if ((pao->dsp_crashed >= 10)
+			&& (phm->function != HPI_ADAPTER_DEBUG_READ)) {
+			/* allow last resort debug read even after crash */
+			hpi_init_response(phr, phm->object, phm->function,
+				HPI_ERROR_DSP_HARDWARE);
+			HPI_DEBUG_LOG(WARNING, " %d,%d dsp crashed.\n",
+				phm->object, phm->function);
+			return;
+		}
+	}
+
+	/* Init default response  */
+	if (phm->function != HPI_SUBSYS_CREATE_ADAPTER)
+		hpi_init_response(phr, phm->object, phm->function,
+			HPI_ERROR_PROCESSING_MESSAGE);
+
+	HPI_DEBUG_LOG(VERBOSE, "start of switch\n");
+	switch (phm->type) {
+	case HPI_TYPE_MESSAGE:
+		switch (phm->object) {
+		case HPI_OBJ_SUBSYSTEM:
+			subsys_message(phm, phr);
+			break;
+
+		case HPI_OBJ_ADAPTER:
+			phr->size =
+				sizeof(struct hpi_response_header) +
+				sizeof(struct hpi_adapter_res);
+			adapter_message(pao, phm, phr);
+			break;
+
+		case HPI_OBJ_CONTROLEX:
+		case HPI_OBJ_CONTROL:
+			control_message(pao, phm, phr);
+			break;
+
+		case HPI_OBJ_OSTREAM:
+			outstream_message(pao, phm, phr);
+			break;
+
+		case HPI_OBJ_ISTREAM:
+			instream_message(pao, phm, phr);
+			break;
+
+		default:
+			hw_message(pao, phm, phr);
+			break;
+		}
+		break;
+
+	default:
+		phr->error = HPI_ERROR_INVALID_TYPE;
+		break;
+	}
+}
+
+/*****************************************************************************/
+/* SUBSYSTEM */
+
+/** Create an adapter object and initialise it based on resource information
+ * passed in in the message
+ * *** NOTE - you cannot use this function AND the FindAdapters function at the
+ * same time, the application must use only one of them to get the adapters ***
+ */
+static void subsys_create_adapter(struct hpi_message *phm,
+	struct hpi_response *phr)
+{
+	/* create temp adapter obj, because we don't know what index yet */
+	struct hpi_adapter_obj ao;
+	u32 os_error_code;
+	u16 err;
+
+	HPI_DEBUG_LOG(DEBUG, " subsys_create_adapter\n");
+
+	memset(&ao, 0, sizeof(ao));
+
+	/* this HPI only creates adapters for TI/PCI devices */
+	if (phm->u.s.resource.bus_type != HPI_BUS_PCI)
+		return;
+	if (phm->u.s.resource.r.pci->vendor_id != HPI_PCI_VENDOR_ID_TI)
+		return;
+	if (phm->u.s.resource.r.pci->device_id != HPI_PCI_DEV_ID_DSP6205)
+		return;
+
+	ao.priv = kzalloc(sizeof(struct hpi_hw_obj), GFP_KERNEL);
+	if (!ao.priv) {
+		HPI_DEBUG_LOG(ERROR, "cant get mem for adapter object\n");
+		phr->error = HPI_ERROR_MEMORY_ALLOC;
+		return;
+	}
+
+	ao.pci = *phm->u.s.resource.r.pci;
+	err = create_adapter_obj(&ao, &os_error_code);
+	if (!err)
+		err = hpi_add_adapter(&ao);
+	if (err) {
+		phr->u.s.data = os_error_code;
+		delete_adapter_obj(&ao);
+		phr->error = err;
+		return;
+	}
+
+	phr->u.s.aw_adapter_list[ao.index] = ao.adapter_type;
+	phr->u.s.adapter_index = ao.index;
+	phr->u.s.num_adapters++;
+	phr->error = 0;
+}
+
+/** delete an adapter - required by WDM driver */
+static void subsys_delete_adapter(struct hpi_message *phm,
+	struct hpi_response *phr)
+{
+	struct hpi_adapter_obj *pao;
+	struct hpi_hw_obj *phw;
+
+	pao = hpi_find_adapter(phm->adapter_index);
+	if (!pao) {
+		phr->error = HPI_ERROR_INVALID_OBJ_INDEX;
+		return;
+	}
+	phw = (struct hpi_hw_obj *)pao->priv;
+	/* reset adapter h/w */
+	/* Reset C6713 #1 */
+	boot_loader_write_mem32(pao, 0, C6205_BAR0_TIMER1_CTL, 0);
+	/* reset C6205 */
+	iowrite32(C6205_HDCR_WARMRESET, phw->prHDCR);
+
+	delete_adapter_obj(pao);
+	phr->error = 0;
+}
+
+/** Create adapter object
+  allocate buffers, bootload DSPs, initialise control cache
+*/
+static u16 create_adapter_obj(struct hpi_adapter_obj *pao,
+	u32 *pos_error_code)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	struct bus_master_interface *interface;
+	u32 phys_addr;
+#ifndef HPI6205_NO_HSR_POLL
+	u32 time_out = HPI6205_TIMEOUT;
+	u32 temp1;
+#endif
+	int i;
+	u16 err;
+
+	/* init error reporting */
+	pao->dsp_crashed = 0;
+
+	for (i = 0; i < HPI_MAX_STREAMS; i++)
+		phw->flag_outstream_just_reset[i] = 1;
+
+	/* The C6205 memory area 1 is 8Mbyte window into DSP registers */
+	phw->prHSR =
+		pao->pci.ap_mem_base[1] +
+		C6205_BAR1_HSR / sizeof(*pao->pci.ap_mem_base[1]);
+	phw->prHDCR =
+		pao->pci.ap_mem_base[1] +
+		C6205_BAR1_HDCR / sizeof(*pao->pci.ap_mem_base[1]);
+	phw->prDSPP =
+		pao->pci.ap_mem_base[1] +
+		C6205_BAR1_DSPP / sizeof(*pao->pci.ap_mem_base[1]);
+
+	pao->has_control_cache = 0;
+
+	if (hpios_locked_mem_alloc(&phw->h_locked_mem,
+			sizeof(struct bus_master_interface),
+			pao->pci.p_os_data))
+		phw->p_interface_buffer = NULL;
+	else if (hpios_locked_mem_get_virt_addr(&phw->h_locked_mem,
+			(void *)&phw->p_interface_buffer))
+		phw->p_interface_buffer = NULL;
+
+	HPI_DEBUG_LOG(DEBUG, "interface buffer address %p\n",
+		phw->p_interface_buffer);
+
+	if (phw->p_interface_buffer) {
+		memset((void *)phw->p_interface_buffer, 0,
+			sizeof(struct bus_master_interface));
+		phw->p_interface_buffer->dsp_ack = H620_HIF_UNKNOWN;
+	}
+
+	err = adapter_boot_load_dsp(pao, pos_error_code);
+	if (err)
+		/* no need to clean up as SubSysCreateAdapter */
+		/* calls DeleteAdapter on error. */
+		return err;
+
+	HPI_DEBUG_LOG(INFO, "load DSP code OK\n");
+
+	/* allow boot load even if mem alloc wont work */
+	if (!phw->p_interface_buffer)
+		return hpi6205_error(0, HPI_ERROR_MEMORY_ALLOC);
+
+	interface = phw->p_interface_buffer;
+
+#ifndef HPI6205_NO_HSR_POLL
+	/* wait for first interrupt indicating the DSP init is done */
+	time_out = HPI6205_TIMEOUT * 10;
+	temp1 = 0;
+	while (((temp1 & C6205_HSR_INTSRC) == 0) && --time_out)
+		temp1 = ioread32(phw->prHSR);
+
+	if (temp1 & C6205_HSR_INTSRC)
+		HPI_DEBUG_LOG(INFO,
+			"interrupt confirming DSP code running OK\n");
+	else {
+		HPI_DEBUG_LOG(ERROR,
+			"timed out waiting for interrupt "
+			"confirming DSP code running\n");
+		return hpi6205_error(0, HPI6205_ERROR_6205_NO_IRQ);
+	}
+
+	/* reset the interrupt */
+	iowrite32(C6205_HSR_INTSRC, phw->prHSR);
+#endif
+
+	/* make sure the DSP has started ok */
+	if (!wait_dsp_ack(phw, H620_HIF_RESET, HPI6205_TIMEOUT * 10)) {
+		HPI_DEBUG_LOG(ERROR, "timed out waiting reset state \n");
+		return hpi6205_error(0, HPI6205_ERROR_6205_INIT_FAILED);
+	}
+	/* Note that *pao, *phw are zeroed after allocation,
+	 * so pointers and flags are NULL by default.
+	 * Allocate bus mastering control cache buffer and tell the DSP about it
+	 */
+	if (interface->control_cache.number_of_controls) {
+		void *p_control_cache_virtual;
+
+		err = hpios_locked_mem_alloc(&phw->h_control_cache,
+			interface->control_cache.size_in_bytes,
+			pao->pci.p_os_data);
+		if (!err)
+			err = hpios_locked_mem_get_virt_addr(&phw->
+				h_control_cache, &p_control_cache_virtual);
+		if (!err) {
+			memset(p_control_cache_virtual, 0,
+				interface->control_cache.size_in_bytes);
+
+			phw->p_cache =
+				hpi_alloc_control_cache(interface->
+				control_cache.number_of_controls,
+				interface->control_cache.size_in_bytes,
+				(struct hpi_control_cache_info *)
+				p_control_cache_virtual);
+			if (!phw->p_cache)
+				err = HPI_ERROR_MEMORY_ALLOC;
+		}
+		if (!err) {
+			err = hpios_locked_mem_get_phys_addr(&phw->
+				h_control_cache, &phys_addr);
+			interface->control_cache.physical_address32 =
+				phys_addr;
+		}
+
+		if (!err)
+			pao->has_control_cache = 1;
+		else {
+			if (hpios_locked_mem_valid(&phw->h_control_cache))
+				hpios_locked_mem_free(&phw->h_control_cache);
+			pao->has_control_cache = 0;
+		}
+	}
+	/* allocate bus mastering async buffer and tell the DSP about it */
+	if (interface->async_buffer.b.size) {
+		err = hpios_locked_mem_alloc(&phw->h_async_event_buffer,
+			interface->async_buffer.b.size *
+			sizeof(struct hpi_async_event), pao->pci.p_os_data);
+		if (!err)
+			err = hpios_locked_mem_get_virt_addr
+				(&phw->h_async_event_buffer, (void *)
+				&phw->p_async_event_buffer);
+		if (!err)
+			memset((void *)phw->p_async_event_buffer, 0,
+				interface->async_buffer.b.size *
+				sizeof(struct hpi_async_event));
+		if (!err) {
+			err = hpios_locked_mem_get_phys_addr
+				(&phw->h_async_event_buffer, &phys_addr);
+			interface->async_buffer.physical_address32 =
+				phys_addr;
+		}
+		if (err) {
+			if (hpios_locked_mem_valid(&phw->
+					h_async_event_buffer)) {
+				hpios_locked_mem_free
+					(&phw->h_async_event_buffer);
+				phw->p_async_event_buffer = NULL;
+			}
+		}
+	}
+	send_dsp_command(phw, H620_HIF_IDLE);
+
+	{
+		struct hpi_message hM;
+		struct hpi_response hR;
+		u32 max_streams;
+
+		HPI_DEBUG_LOG(VERBOSE, "init ADAPTER_GET_INFO\n");
+		memset(&hM, 0, sizeof(hM));
+		hM.type = HPI_TYPE_MESSAGE;
+		hM.size = sizeof(hM);
+		hM.object = HPI_OBJ_ADAPTER;
+		hM.function = HPI_ADAPTER_GET_INFO;
+		hM.adapter_index = 0;
+		memset(&hR, 0, sizeof(hR));
+		hR.size = sizeof(hR);
+
+		err = message_response_sequence(pao, &hM, &hR);
+		if (err) {
+			HPI_DEBUG_LOG(ERROR, "message transport error %d\n",
+				err);
+			return err;
+		}
+		if (hR.error)
+			return hR.error;
+
+		pao->adapter_type = hR.u.a.adapter_type;
+		pao->index = hR.u.a.adapter_index;
+
+		max_streams = hR.u.a.num_outstreams + hR.u.a.num_instreams;
+
+		hpios_locked_mem_prepare((max_streams * 6) / 10, max_streams,
+			65536, pao->pci.p_os_data);
+
+		HPI_DEBUG_LOG(VERBOSE,
+			"got adapter info type %x index %d serial %d\n",
+			hR.u.a.adapter_type, hR.u.a.adapter_index,
+			hR.u.a.serial_number);
+	}
+
+	pao->open = 0;	/* upon creation the adapter is closed */
+
+	HPI_DEBUG_LOG(INFO, "bootload DSP OK\n");
+	return 0;
+}
+
+/** Free memory areas allocated by adapter
+ * this routine is called from SubSysDeleteAdapter,
+  * and SubSysCreateAdapter if duplicate index
+*/
+static void delete_adapter_obj(struct hpi_adapter_obj *pao)
+{
+	struct hpi_hw_obj *phw;
+	int i;
+
+	phw = pao->priv;
+
+	if (hpios_locked_mem_valid(&phw->h_async_event_buffer)) {
+		hpios_locked_mem_free(&phw->h_async_event_buffer);
+		phw->p_async_event_buffer = NULL;
+	}
+
+	if (hpios_locked_mem_valid(&phw->h_control_cache)) {
+		hpios_locked_mem_free(&phw->h_control_cache);
+		hpi_free_control_cache(phw->p_cache);
+	}
+
+	if (hpios_locked_mem_valid(&phw->h_locked_mem)) {
+		hpios_locked_mem_free(&phw->h_locked_mem);
+		phw->p_interface_buffer = NULL;
+	}
+
+	for (i = 0; i < HPI_MAX_STREAMS; i++)
+		if (hpios_locked_mem_valid(&phw->instream_host_buffers[i])) {
+			hpios_locked_mem_free(&phw->instream_host_buffers[i]);
+			/*?phw->InStreamHostBuffers[i] = NULL; */
+			phw->instream_host_buffer_size[i] = 0;
+		}
+
+	for (i = 0; i < HPI_MAX_STREAMS; i++)
+		if (hpios_locked_mem_valid(&phw->outstream_host_buffers[i])) {
+			hpios_locked_mem_free(&phw->outstream_host_buffers
+				[i]);
+			phw->outstream_host_buffer_size[i] = 0;
+		}
+
+	hpios_locked_mem_unprepare(pao->pci.p_os_data);
+
+	hpi_delete_adapter(pao);
+	kfree(phw);
+}
+
+/*****************************************************************************/
+/* OutStream Host buffer functions */
+
+/** Allocate or attach buffer for busmastering
+*/
+static void outstream_host_buffer_allocate(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	u16 err = 0;
+	u32 command = phm->u.d.u.buffer.command;
+	struct hpi_hw_obj *phw = pao->priv;
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+
+	hpi_init_response(phr, phm->object, phm->function, 0);
+
+	if (command == HPI_BUFFER_CMD_EXTERNAL
+		|| command == HPI_BUFFER_CMD_INTERNAL_ALLOC) {
+		/* ALLOC phase, allocate a buffer with power of 2 size,
+		   get its bus address for PCI bus mastering
+		 */
+		phm->u.d.u.buffer.buffer_size =
+			roundup_pow_of_two(phm->u.d.u.buffer.buffer_size);
+		/* return old size and allocated size,
+		   so caller can detect change */
+		phr->u.d.u.stream_info.data_available =
+			phw->outstream_host_buffer_size[phm->obj_index];
+		phr->u.d.u.stream_info.buffer_size =
+			phm->u.d.u.buffer.buffer_size;
+
+		if (phw->outstream_host_buffer_size[phm->obj_index] ==
+			phm->u.d.u.buffer.buffer_size) {
+			/* Same size, no action required */
+			return;
+		}
+
+		if (hpios_locked_mem_valid(&phw->outstream_host_buffers[phm->
+					obj_index]))
+			hpios_locked_mem_free(&phw->outstream_host_buffers
+				[phm->obj_index]);
+
+		err = hpios_locked_mem_alloc(&phw->outstream_host_buffers
+			[phm->obj_index], phm->u.d.u.buffer.buffer_size,
+			pao->pci.p_os_data);
+
+		if (err) {
+			phr->error = HPI_ERROR_INVALID_DATASIZE;
+			phw->outstream_host_buffer_size[phm->obj_index] = 0;
+			return;
+		}
+
+		err = hpios_locked_mem_get_phys_addr
+			(&phw->outstream_host_buffers[phm->obj_index],
+			&phm->u.d.u.buffer.pci_address);
+		/* get the phys addr into msg for single call alloc caller
+		 * needs to do this for split alloc (or use the same message)
+		 * return the phy address for split alloc in the respose too
+		 */
+		phr->u.d.u.stream_info.auxiliary_data_available =
+			phm->u.d.u.buffer.pci_address;
+
+		if (err) {
+			hpios_locked_mem_free(&phw->outstream_host_buffers
+				[phm->obj_index]);
+			phw->outstream_host_buffer_size[phm->obj_index] = 0;
+			phr->error = HPI_ERROR_MEMORY_ALLOC;
+			return;
+		}
+	}
+
+	if (command == HPI_BUFFER_CMD_EXTERNAL
+		|| command == HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER) {
+		/* GRANT phase.  Set up the BBM status, tell the DSP about
+		   the buffer so it can start using BBM.
+		 */
+		struct hpi_hostbuffer_status *status;
+
+		if (phm->u.d.u.buffer.buffer_size & (phm->u.d.u.buffer.
+				buffer_size - 1)) {
+			HPI_DEBUG_LOG(ERROR,
+				"buffer size must be 2^N not %d\n",
+				phm->u.d.u.buffer.buffer_size);
+			phr->error = HPI_ERROR_INVALID_DATASIZE;
+			return;
+		}
+		phw->outstream_host_buffer_size[phm->obj_index] =
+			phm->u.d.u.buffer.buffer_size;
+		status = &interface->outstream_host_buffer_status[phm->
+			obj_index];
+		status->samples_processed = 0;
+		status->stream_state = HPI_STATE_STOPPED;
+		status->dSP_index = 0;
+		status->host_index = status->dSP_index;
+		status->size_in_bytes = phm->u.d.u.buffer.buffer_size;
+
+		hw_message(pao, phm, phr);
+
+		if (phr->error
+			&& hpios_locked_mem_valid(&phw->
+				outstream_host_buffers[phm->obj_index])) {
+			hpios_locked_mem_free(&phw->outstream_host_buffers
+				[phm->obj_index]);
+			phw->outstream_host_buffer_size[phm->obj_index] = 0;
+		}
+	}
+}
+
+static void outstream_host_buffer_get_info(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+	struct hpi_hostbuffer_status *status;
+	u8 *p_bbm_data;
+
+	if (hpios_locked_mem_valid(&phw->outstream_host_buffers[phm->
+				obj_index])) {
+		if (hpios_locked_mem_get_virt_addr(&phw->
+				outstream_host_buffers[phm->obj_index],
+				(void *)&p_bbm_data)) {
+			phr->error = HPI_ERROR_INVALID_OPERATION;
+			return;
+		}
+		status = &interface->outstream_host_buffer_status[phm->
+			obj_index];
+		hpi_init_response(phr, HPI_OBJ_OSTREAM,
+			HPI_OSTREAM_HOSTBUFFER_GET_INFO, 0);
+		phr->u.d.u.hostbuffer_info.p_buffer = p_bbm_data;
+		phr->u.d.u.hostbuffer_info.p_status = status;
+	} else {
+		hpi_init_response(phr, HPI_OBJ_OSTREAM,
+			HPI_OSTREAM_HOSTBUFFER_GET_INFO,
+			HPI_ERROR_INVALID_OPERATION);
+	}
+}
+
+static void outstream_host_buffer_free(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	u32 command = phm->u.d.u.buffer.command;
+
+	if (phw->outstream_host_buffer_size[phm->obj_index]) {
+		if (command == HPI_BUFFER_CMD_EXTERNAL
+			|| command == HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER) {
+			phw->outstream_host_buffer_size[phm->obj_index] = 0;
+			hw_message(pao, phm, phr);
+			/* Tell adapter to stop using the host buffer. */
+		}
+		if (command == HPI_BUFFER_CMD_EXTERNAL
+			|| command == HPI_BUFFER_CMD_INTERNAL_FREE)
+			hpios_locked_mem_free(&phw->outstream_host_buffers
+				[phm->obj_index]);
+	}
+	/* Should HPI_ERROR_INVALID_OPERATION be returned
+	   if no host buffer is allocated? */
+	else
+		hpi_init_response(phr, HPI_OBJ_OSTREAM,
+			HPI_OSTREAM_HOSTBUFFER_FREE, 0);
+
+}
+
+static u32 outstream_get_space_available(struct hpi_hostbuffer_status *status)
+{
+	return status->size_in_bytes - (status->host_index -
+		status->dSP_index);
+}
+
+static void outstream_write(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+	struct hpi_hostbuffer_status *status;
+	u32 space_available;
+
+	if (!phw->outstream_host_buffer_size[phm->obj_index]) {
+		/* there  is no BBM buffer, write via message */
+		hw_message(pao, phm, phr);
+		return;
+	}
+
+	hpi_init_response(phr, phm->object, phm->function, 0);
+	status = &interface->outstream_host_buffer_status[phm->obj_index];
+
+	if (phw->flag_outstream_just_reset[phm->obj_index]) {
+		/* First OutStremWrite() call following reset will write data to the
+		   adapter's buffers, reducing delay before stream can start. The DSP
+		   takes care of setting the stream data format using format information
+		   embedded in phm.
+		 */
+		int partial_write = 0;
+		unsigned int original_size = 0;
+
+		phw->flag_outstream_just_reset[phm->obj_index] = 0;
+
+		/* Send the first buffer to the DSP the old way. */
+		/* Limit size of first transfer - */
+		/* expect that this will not usually be triggered. */
+		if (phm->u.d.u.data.data_size > HPI6205_SIZEOF_DATA) {
+			partial_write = 1;
+			original_size = phm->u.d.u.data.data_size;
+			phm->u.d.u.data.data_size = HPI6205_SIZEOF_DATA;
+		}
+		/* write it */
+		phm->function = HPI_OSTREAM_WRITE;
+		hw_message(pao, phm, phr);
+
+		if (phr->error)
+			return;
+
+		/* update status information that the DSP would typically
+		 * update (and will update next time the DSP
+		 * buffer update task reads data from the host BBM buffer)
+		 */
+		status->auxiliary_data_available = phm->u.d.u.data.data_size;
+		status->host_index += phm->u.d.u.data.data_size;
+		status->dSP_index += phm->u.d.u.data.data_size;
+
+		/* if we did a full write, we can return from here. */
+		if (!partial_write)
+			return;
+
+		/* tweak buffer parameters and let the rest of the */
+		/* buffer land in internal BBM buffer */
+		phm->u.d.u.data.data_size =
+			original_size - HPI6205_SIZEOF_DATA;
+		phm->u.d.u.data.pb_data += HPI6205_SIZEOF_DATA;
+	}
+
+	space_available = outstream_get_space_available(status);
+	if (space_available < phm->u.d.u.data.data_size) {
+		phr->error = HPI_ERROR_INVALID_DATASIZE;
+		return;
+	}
+
+	/* HostBuffers is used to indicate host buffer is internally allocated.
+	   otherwise, assumed external, data written externally */
+	if (phm->u.d.u.data.pb_data
+		&& hpios_locked_mem_valid(&phw->outstream_host_buffers[phm->
+				obj_index])) {
+		u8 *p_bbm_data;
+		u32 l_first_write;
+		u8 *p_app_data = (u8 *)phm->u.d.u.data.pb_data;
+
+		if (hpios_locked_mem_get_virt_addr(&phw->
+				outstream_host_buffers[phm->obj_index],
+				(void *)&p_bbm_data)) {
+			phr->error = HPI_ERROR_INVALID_OPERATION;
+			return;
+		}
+
+		/* either all data,
+		   or enough to fit from current to end of BBM buffer */
+		l_first_write =
+			min(phm->u.d.u.data.data_size,
+			status->size_in_bytes -
+			(status->host_index & (status->size_in_bytes - 1)));
+
+		memcpy(p_bbm_data +
+			(status->host_index & (status->size_in_bytes - 1)),
+			p_app_data, l_first_write);
+		/* remaining data if any */
+		memcpy(p_bbm_data, p_app_data + l_first_write,
+			phm->u.d.u.data.data_size - l_first_write);
+	}
+	status->host_index += phm->u.d.u.data.data_size;
+}
+
+static void outstream_get_info(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+	struct hpi_hostbuffer_status *status;
+
+	if (!phw->outstream_host_buffer_size[phm->obj_index]) {
+		hw_message(pao, phm, phr);
+		return;
+	}
+
+	hpi_init_response(phr, phm->object, phm->function, 0);
+
+	status = &interface->outstream_host_buffer_status[phm->obj_index];
+
+	phr->u.d.u.stream_info.state = (u16)status->stream_state;
+	phr->u.d.u.stream_info.samples_transferred =
+		status->samples_processed;
+	phr->u.d.u.stream_info.buffer_size = status->size_in_bytes;
+	phr->u.d.u.stream_info.data_available =
+		status->size_in_bytes - outstream_get_space_available(status);
+	phr->u.d.u.stream_info.auxiliary_data_available =
+		status->auxiliary_data_available;
+}
+
+static void outstream_start(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	hw_message(pao, phm, phr);
+}
+
+static void outstream_reset(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	phw->flag_outstream_just_reset[phm->obj_index] = 1;
+	hw_message(pao, phm, phr);
+}
+
+static void outstream_open(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	outstream_reset(pao, phm, phr);
+}
+
+/*****************************************************************************/
+/* InStream Host buffer functions */
+
+static void instream_host_buffer_allocate(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	u16 err = 0;
+	u32 command = phm->u.d.u.buffer.command;
+	struct hpi_hw_obj *phw = pao->priv;
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+
+	hpi_init_response(phr, phm->object, phm->function, 0);
+
+	if (command == HPI_BUFFER_CMD_EXTERNAL
+		|| command == HPI_BUFFER_CMD_INTERNAL_ALLOC) {
+
+		phm->u.d.u.buffer.buffer_size =
+			roundup_pow_of_two(phm->u.d.u.buffer.buffer_size);
+		phr->u.d.u.stream_info.data_available =
+			phw->instream_host_buffer_size[phm->obj_index];
+		phr->u.d.u.stream_info.buffer_size =
+			phm->u.d.u.buffer.buffer_size;
+
+		if (phw->instream_host_buffer_size[phm->obj_index] ==
+			phm->u.d.u.buffer.buffer_size) {
+			/* Same size, no action required */
+			return;
+		}
+
+		if (hpios_locked_mem_valid(&phw->instream_host_buffers[phm->
+					obj_index]))
+			hpios_locked_mem_free(&phw->instream_host_buffers
+				[phm->obj_index]);
+
+		err = hpios_locked_mem_alloc(&phw->instream_host_buffers[phm->
+				obj_index], phm->u.d.u.buffer.buffer_size,
+			pao->pci.p_os_data);
+
+		if (err) {
+			phr->error = HPI_ERROR_INVALID_DATASIZE;
+			phw->instream_host_buffer_size[phm->obj_index] = 0;
+			return;
+		}
+
+		err = hpios_locked_mem_get_phys_addr
+			(&phw->instream_host_buffers[phm->obj_index],
+			&phm->u.d.u.buffer.pci_address);
+		/* get the phys addr into msg for single call alloc. Caller
+		   needs to do this for split alloc so return the phy address */
+		phr->u.d.u.stream_info.auxiliary_data_available =
+			phm->u.d.u.buffer.pci_address;
+		if (err) {
+			hpios_locked_mem_free(&phw->instream_host_buffers
+				[phm->obj_index]);
+			phw->instream_host_buffer_size[phm->obj_index] = 0;
+			phr->error = HPI_ERROR_MEMORY_ALLOC;
+			return;
+		}
+	}
+
+	if (command == HPI_BUFFER_CMD_EXTERNAL
+		|| command == HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER) {
+		struct hpi_hostbuffer_status *status;
+
+		if (phm->u.d.u.buffer.buffer_size & (phm->u.d.u.buffer.
+				buffer_size - 1)) {
+			HPI_DEBUG_LOG(ERROR,
+				"buffer size must be 2^N not %d\n",
+				phm->u.d.u.buffer.buffer_size);
+			phr->error = HPI_ERROR_INVALID_DATASIZE;
+			return;
+		}
+
+		phw->instream_host_buffer_size[phm->obj_index] =
+			phm->u.d.u.buffer.buffer_size;
+		status = &interface->instream_host_buffer_status[phm->
+			obj_index];
+		status->samples_processed = 0;
+		status->stream_state = HPI_STATE_STOPPED;
+		status->dSP_index = 0;
+		status->host_index = status->dSP_index;
+		status->size_in_bytes = phm->u.d.u.buffer.buffer_size;
+
+		hw_message(pao, phm, phr);
+		if (phr->error
+			&& hpios_locked_mem_valid(&phw->
+				instream_host_buffers[phm->obj_index])) {
+			hpios_locked_mem_free(&phw->instream_host_buffers
+				[phm->obj_index]);
+			phw->instream_host_buffer_size[phm->obj_index] = 0;
+		}
+	}
+}
+
+static void instream_host_buffer_get_info(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+	struct hpi_hostbuffer_status *status;
+	u8 *p_bbm_data;
+
+	if (hpios_locked_mem_valid(&phw->instream_host_buffers[phm->
+				obj_index])) {
+		if (hpios_locked_mem_get_virt_addr(&phw->
+				instream_host_buffers[phm->obj_index],
+				(void *)&p_bbm_data)) {
+			phr->error = HPI_ERROR_INVALID_OPERATION;
+			return;
+		}
+		status = &interface->instream_host_buffer_status[phm->
+			obj_index];
+		hpi_init_response(phr, HPI_OBJ_ISTREAM,
+			HPI_ISTREAM_HOSTBUFFER_GET_INFO, 0);
+		phr->u.d.u.hostbuffer_info.p_buffer = p_bbm_data;
+		phr->u.d.u.hostbuffer_info.p_status = status;
+	} else {
+		hpi_init_response(phr, HPI_OBJ_ISTREAM,
+			HPI_ISTREAM_HOSTBUFFER_GET_INFO,
+			HPI_ERROR_INVALID_OPERATION);
+	}
+}
+
+static void instream_host_buffer_free(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	u32 command = phm->u.d.u.buffer.command;
+
+	if (phw->instream_host_buffer_size[phm->obj_index]) {
+		if (command == HPI_BUFFER_CMD_EXTERNAL
+			|| command == HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER) {
+			phw->instream_host_buffer_size[phm->obj_index] = 0;
+			hw_message(pao, phm, phr);
+		}
+
+		if (command == HPI_BUFFER_CMD_EXTERNAL
+			|| command == HPI_BUFFER_CMD_INTERNAL_FREE)
+			hpios_locked_mem_free(&phw->instream_host_buffers
+				[phm->obj_index]);
+
+	} else {
+		/* Should HPI_ERROR_INVALID_OPERATION be returned
+		   if no host buffer is allocated? */
+		hpi_init_response(phr, HPI_OBJ_ISTREAM,
+			HPI_ISTREAM_HOSTBUFFER_FREE, 0);
+
+	}
+
+}
+
+static void instream_start(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	hw_message(pao, phm, phr);
+}
+
+static u32 instream_get_bytes_available(struct hpi_hostbuffer_status *status)
+{
+	return status->dSP_index - status->host_index;
+}
+
+static void instream_read(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+	struct hpi_hostbuffer_status *status;
+	u32 data_available;
+	u8 *p_bbm_data;
+	u32 l_first_read;
+	u8 *p_app_data = (u8 *)phm->u.d.u.data.pb_data;
+
+	if (!phw->instream_host_buffer_size[phm->obj_index]) {
+		hw_message(pao, phm, phr);
+		return;
+	}
+	hpi_init_response(phr, phm->object, phm->function, 0);
+
+	status = &interface->instream_host_buffer_status[phm->obj_index];
+	data_available = instream_get_bytes_available(status);
+	if (data_available < phm->u.d.u.data.data_size) {
+		phr->error = HPI_ERROR_INVALID_DATASIZE;
+		return;
+	}
+
+	if (hpios_locked_mem_valid(&phw->instream_host_buffers[phm->
+				obj_index])) {
+		if (hpios_locked_mem_get_virt_addr(&phw->
+				instream_host_buffers[phm->obj_index],
+				(void *)&p_bbm_data)) {
+			phr->error = HPI_ERROR_INVALID_OPERATION;
+			return;
+		}
+
+		/* either all data,
+		   or enough to fit from current to end of BBM buffer */
+		l_first_read =
+			min(phm->u.d.u.data.data_size,
+			status->size_in_bytes -
+			(status->host_index & (status->size_in_bytes - 1)));
+
+		memcpy(p_app_data,
+			p_bbm_data +
+			(status->host_index & (status->size_in_bytes - 1)),
+			l_first_read);
+		/* remaining data if any */
+		memcpy(p_app_data + l_first_read, p_bbm_data,
+			phm->u.d.u.data.data_size - l_first_read);
+	}
+	status->host_index += phm->u.d.u.data.data_size;
+}
+
+static void instream_get_info(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+	struct hpi_hostbuffer_status *status;
+	if (!phw->instream_host_buffer_size[phm->obj_index]) {
+		hw_message(pao, phm, phr);
+		return;
+	}
+
+	status = &interface->instream_host_buffer_status[phm->obj_index];
+
+	hpi_init_response(phr, phm->object, phm->function, 0);
+
+	phr->u.d.u.stream_info.state = (u16)status->stream_state;
+	phr->u.d.u.stream_info.samples_transferred =
+		status->samples_processed;
+	phr->u.d.u.stream_info.buffer_size = status->size_in_bytes;
+	phr->u.d.u.stream_info.data_available =
+		instream_get_bytes_available(status);
+	phr->u.d.u.stream_info.auxiliary_data_available =
+		status->auxiliary_data_available;
+}
+
+/*****************************************************************************/
+/* LOW-LEVEL */
+#define HPI6205_MAX_FILES_TO_LOAD 2
+
+static u16 adapter_boot_load_dsp(struct hpi_adapter_obj *pao,
+	u32 *pos_error_code)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	struct dsp_code dsp_code;
+	u16 boot_code_id[HPI6205_MAX_FILES_TO_LOAD];
+	u16 firmware_id = pao->pci.subsys_device_id;
+	u32 temp;
+	int dsp = 0, i = 0;
+	u16 err = 0;
+
+	boot_code_id[0] = HPI_ADAPTER_ASI(0x6205);
+
+	/* special cases where firmware_id != subsys ID */
+	switch (firmware_id) {
+	case HPI_ADAPTER_FAMILY_ASI(0x5000):
+		boot_code_id[0] = firmware_id;
+		firmware_id = 0;
+		break;
+	case HPI_ADAPTER_FAMILY_ASI(0x5300):
+	case HPI_ADAPTER_FAMILY_ASI(0x5400):
+	case HPI_ADAPTER_FAMILY_ASI(0x6300):
+		firmware_id = HPI_ADAPTER_FAMILY_ASI(0x6400);
+		break;
+	case HPI_ADAPTER_FAMILY_ASI(0x5600):
+	case HPI_ADAPTER_FAMILY_ASI(0x6500):
+		firmware_id = HPI_ADAPTER_FAMILY_ASI(0x6600);
+		break;
+	case HPI_ADAPTER_FAMILY_ASI(0x8800):
+		firmware_id = HPI_ADAPTER_FAMILY_ASI(0x8900);
+		break;
+	}
+	boot_code_id[1] = firmware_id;
+
+	/* reset DSP by writing a 1 to the WARMRESET bit */
+	temp = C6205_HDCR_WARMRESET;
+	iowrite32(temp, phw->prHDCR);
+	hpios_delay_micro_seconds(1000);
+
+	/* check that PCI i/f was configured by EEPROM */
+	temp = ioread32(phw->prHSR);
+	if ((temp & (C6205_HSR_CFGERR | C6205_HSR_EEREAD)) !=
+		C6205_HSR_EEREAD)
+		return hpi6205_error(0, HPI6205_ERROR_6205_EEPROM);
+	temp |= 0x04;
+	/* disable PINTA interrupt */
+	iowrite32(temp, phw->prHSR);
+
+	/* check control register reports PCI boot mode */
+	temp = ioread32(phw->prHDCR);
+	if (!(temp & C6205_HDCR_PCIBOOT))
+		return hpi6205_error(0, HPI6205_ERROR_6205_REG);
+
+	/* try writing a couple of numbers to the DSP page register */
+	/* and reading them back. */
+	temp = 1;
+	iowrite32(temp, phw->prDSPP);
+	if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP))
+		return hpi6205_error(0, HPI6205_ERROR_6205_DSPPAGE);
+	temp = 2;
+	iowrite32(temp, phw->prDSPP);
+	if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP))
+		return hpi6205_error(0, HPI6205_ERROR_6205_DSPPAGE);
+	temp = 3;
+	iowrite32(temp, phw->prDSPP);
+	if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP))
+		return hpi6205_error(0, HPI6205_ERROR_6205_DSPPAGE);
+	/* reset DSP page to the correct number */
+	temp = 0;
+	iowrite32(temp, phw->prDSPP);
+	if ((temp | C6205_DSPP_MAP1) != ioread32(phw->prDSPP))
+		return hpi6205_error(0, HPI6205_ERROR_6205_DSPPAGE);
+	phw->dsp_page = 0;
+
+	/* release 6713 from reset before 6205 is bootloaded.
+	   This ensures that the EMIF is inactive,
+	   and the 6713 HPI gets the correct bootmode etc
+	 */
+	if (boot_code_id[1] != 0) {
+		/* DSP 1 is a C6713 */
+		/* CLKX0 <- '1' release the C6205 bootmode pulldowns */
+		boot_loader_write_mem32(pao, 0, (0x018C0024L), 0x00002202);
+		hpios_delay_micro_seconds(100);
+		/* Reset the 6713 #1 - revB */
+		boot_loader_write_mem32(pao, 0, C6205_BAR0_TIMER1_CTL, 0);
+
+		/* dummy read every 4 words for 6205 advisory 1.4.4 */
+		boot_loader_read_mem32(pao, 0, 0);
+
+		hpios_delay_micro_seconds(100);
+		/* Release C6713 from reset - revB */
+		boot_loader_write_mem32(pao, 0, C6205_BAR0_TIMER1_CTL, 4);
+		hpios_delay_micro_seconds(100);
+	}
+
+	for (dsp = 0; dsp < HPI6205_MAX_FILES_TO_LOAD; dsp++) {
+		/* is there a DSP to load? */
+		if (boot_code_id[dsp] == 0)
+			continue;
+
+		err = boot_loader_config_emif(pao, dsp);
+		if (err)
+			return err;
+
+		err = boot_loader_test_internal_memory(pao, dsp);
+		if (err)
+			return err;
+
+		err = boot_loader_test_external_memory(pao, dsp);
+		if (err)
+			return err;
+
+		err = boot_loader_test_pld(pao, dsp);
+		if (err)
+			return err;
+
+		/* write the DSP code down into the DSPs memory */
+		dsp_code.ps_dev = pao->pci.p_os_data;
+		err = hpi_dsp_code_open(boot_code_id[dsp], &dsp_code,
+			pos_error_code);
+		if (err)
+			return err;
+
+		while (1) {
+			u32 length;
+			u32 address;
+			u32 type;
+			u32 *pcode;
+
+			err = hpi_dsp_code_read_word(&dsp_code, &length);
+			if (err)
+				break;
+			if (length == 0xFFFFFFFF)
+				break;	/* end of code */
+
+			err = hpi_dsp_code_read_word(&dsp_code, &address);
+			if (err)
+				break;
+			err = hpi_dsp_code_read_word(&dsp_code, &type);
+			if (err)
+				break;
+			err = hpi_dsp_code_read_block(length, &dsp_code,
+				&pcode);
+			if (err)
+				break;
+			for (i = 0; i < (int)length; i++) {
+				err = boot_loader_write_mem32(pao, dsp,
+					address, *pcode);
+				if (err)
+					break;
+				/* dummy read every 4 words */
+				/* for 6205 advisory 1.4.4 */
+				if (i % 4 == 0)
+					boot_loader_read_mem32(pao, dsp,
+						address);
+				pcode++;
+				address += 4;
+			}
+
+		}
+		if (err) {
+			hpi_dsp_code_close(&dsp_code);
+			return err;
+		}
+
+		/* verify code */
+		hpi_dsp_code_rewind(&dsp_code);
+		while (1) {
+			u32 length = 0;
+			u32 address = 0;
+			u32 type = 0;
+			u32 *pcode = NULL;
+			u32 data = 0;
+
+			hpi_dsp_code_read_word(&dsp_code, &length);
+			if (length == 0xFFFFFFFF)
+				break;	/* end of code */
+
+			hpi_dsp_code_read_word(&dsp_code, &address);
+			hpi_dsp_code_read_word(&dsp_code, &type);
+			hpi_dsp_code_read_block(length, &dsp_code, &pcode);
+
+			for (i = 0; i < (int)length; i++) {
+				data = boot_loader_read_mem32(pao, dsp,
+					address);
+				if (data != *pcode) {
+					err = 0;
+					break;
+				}
+				pcode++;
+				address += 4;
+			}
+			if (err)
+				break;
+		}
+		hpi_dsp_code_close(&dsp_code);
+		if (err)
+			return err;
+	}
+
+	/* After bootloading all DSPs, start DSP0 running
+	 * The DSP0 code will handle starting and synchronizing with its slaves
+	 */
+	if (phw->p_interface_buffer) {
+		/* we need to tell the card the physical PCI address */
+		u32 physicalPC_iaddress;
+		struct bus_master_interface *interface =
+			phw->p_interface_buffer;
+		u32 host_mailbox_address_on_dsp;
+		u32 physicalPC_iaddress_verify = 0;
+		int time_out = 10;
+		/* set ack so we know when DSP is ready to go */
+		/* (dwDspAck will be changed to HIF_RESET) */
+		interface->dsp_ack = H620_HIF_UNKNOWN;
+		wmb();	/* ensure ack is written before dsp writes back */
+
+		err = hpios_locked_mem_get_phys_addr(&phw->h_locked_mem,
+			&physicalPC_iaddress);
+
+		/* locate the host mailbox on the DSP. */
+		host_mailbox_address_on_dsp = 0x80000000;
+		while ((physicalPC_iaddress != physicalPC_iaddress_verify)
+			&& time_out--) {
+			err = boot_loader_write_mem32(pao, 0,
+				host_mailbox_address_on_dsp,
+				physicalPC_iaddress);
+			physicalPC_iaddress_verify =
+				boot_loader_read_mem32(pao, 0,
+				host_mailbox_address_on_dsp);
+		}
+	}
+	HPI_DEBUG_LOG(DEBUG, "starting DS_ps running\n");
+	/* enable interrupts */
+	temp = ioread32(phw->prHSR);
+	temp &= ~(u32)C6205_HSR_INTAM;
+	iowrite32(temp, phw->prHSR);
+
+	/* start code running... */
+	temp = ioread32(phw->prHDCR);
+	temp |= (u32)C6205_HDCR_DSPINT;
+	iowrite32(temp, phw->prHDCR);
+
+	/* give the DSP 10ms to start up */
+	hpios_delay_micro_seconds(10000);
+	return err;
+
+}
+
+/*****************************************************************************/
+/* Bootloader utility functions */
+
+static u32 boot_loader_read_mem32(struct hpi_adapter_obj *pao, int dsp_index,
+	u32 address)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	u32 data = 0;
+	__iomem u32 *p_data;
+
+	if (dsp_index == 0) {
+		/* DSP 0 is always C6205 */
+		if ((address >= 0x01800000) & (address < 0x02000000)) {
+			/* BAR1 register access */
+			p_data = pao->pci.ap_mem_base[1] +
+				(address & 0x007fffff) /
+				sizeof(*pao->pci.ap_mem_base[1]);
+			/* HPI_DEBUG_LOG(WARNING,
+			   "BAR1 access %08x\n", dwAddress); */
+		} else {
+			u32 dw4M_page = address >> 22L;
+			if (dw4M_page != phw->dsp_page) {
+				phw->dsp_page = dw4M_page;
+				/* *INDENT OFF* */
+				iowrite32(phw->dsp_page, phw->prDSPP);
+				/* *INDENT-ON* */
+			}
+			address &= 0x3fffff;	/* address within 4M page */
+			/* BAR0 memory access */
+			p_data = pao->pci.ap_mem_base[0] +
+				address / sizeof(u32);
+		}
+		data = ioread32(p_data);
+	} else if (dsp_index == 1) {
+		/* DSP 1 is a C6713 */
+		u32 lsb;
+		boot_loader_write_mem32(pao, 0, HPIAL_ADDR, address);
+		boot_loader_write_mem32(pao, 0, HPIAH_ADDR, address >> 16);
+		lsb = boot_loader_read_mem32(pao, 0, HPIDL_ADDR);
+		data = boot_loader_read_mem32(pao, 0, HPIDH_ADDR);
+		data = (data << 16) | (lsb & 0xFFFF);
+	}
+	return data;
+}
+
+static u16 boot_loader_write_mem32(struct hpi_adapter_obj *pao, int dsp_index,
+	u32 address, u32 data)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	u16 err = 0;
+	__iomem u32 *p_data;
+	/*      u32 dwVerifyData=0; */
+
+	if (dsp_index == 0) {
+		/* DSP 0 is always C6205 */
+		if ((address >= 0x01800000) & (address < 0x02000000)) {
+			/* BAR1 - DSP  register access using */
+			/* Non-prefetchable PCI access */
+			p_data = pao->pci.ap_mem_base[1] +
+				(address & 0x007fffff) /
+				sizeof(*pao->pci.ap_mem_base[1]);
+		} else {
+			/* BAR0 access - all of DSP memory using */
+			/* pre-fetchable PCI access */
+			u32 dw4M_page = address >> 22L;
+			if (dw4M_page != phw->dsp_page) {
+				phw->dsp_page = dw4M_page;
+				/* *INDENT-OFF* */
+				iowrite32(phw->dsp_page, phw->prDSPP);
+				/* *INDENT-ON* */
+			}
+			address &= 0x3fffff;	/* address within 4M page */
+			p_data = pao->pci.ap_mem_base[0] +
+				address / sizeof(u32);
+		}
+		iowrite32(data, p_data);
+	} else if (dsp_index == 1) {
+		/* DSP 1 is a C6713 */
+		boot_loader_write_mem32(pao, 0, HPIAL_ADDR, address);
+		boot_loader_write_mem32(pao, 0, HPIAH_ADDR, address >> 16);
+
+		/* dummy read every 4 words for 6205 advisory 1.4.4 */
+		boot_loader_read_mem32(pao, 0, 0);
+
+		boot_loader_write_mem32(pao, 0, HPIDL_ADDR, data);
+		boot_loader_write_mem32(pao, 0, HPIDH_ADDR, data >> 16);
+
+		/* dummy read every 4 words for 6205 advisory 1.4.4 */
+		boot_loader_read_mem32(pao, 0, 0);
+	} else
+		err = hpi6205_error(dsp_index, HPI6205_ERROR_BAD_DSPINDEX);
+	return err;
+}
+
+static u16 boot_loader_config_emif(struct hpi_adapter_obj *pao, int dsp_index)
+{
+	u16 err = 0;
+
+	if (dsp_index == 0) {
+		u32 setting;
+
+		/* DSP 0 is always C6205 */
+
+		/* Set the EMIF */
+		/* memory map of C6205 */
+		/* 00000000-0000FFFF    16Kx32 internal program */
+		/* 00400000-00BFFFFF    CE0     2Mx32 SDRAM running @ 100MHz */
+
+		/* EMIF config */
+		/*------------ */
+		/* Global EMIF control */
+		boot_loader_write_mem32(pao, dsp_index, 0x01800000, 0x3779);
+#define WS_OFS 28
+#define WST_OFS 22
+#define WH_OFS 20
+#define RS_OFS 16
+#define RST_OFS 8
+#define MTYPE_OFS 4
+#define RH_OFS 0
+
+		/* EMIF CE0 setup - 2Mx32 Sync DRAM on ASI5000 cards only */
+		setting = 0x00000030;
+		boot_loader_write_mem32(pao, dsp_index, 0x01800008, setting);
+		if (setting != boot_loader_read_mem32(pao, dsp_index,
+				0x01800008))
+			return hpi6205_error(dsp_index,
+				HPI6205_ERROR_DSP_EMIF);
+
+		/* EMIF CE1 setup - 32 bit async. This is 6713 #1 HPI, */
+		/* which occupies D15..0. 6713 starts at 27MHz, so need */
+		/* plenty of wait states. See dsn8701.rtf, and 6713 errata. */
+		/* WST should be 71, but 63  is max possible */
+		setting =
+			(1L << WS_OFS) | (63L << WST_OFS) | (1L << WH_OFS) |
+			(1L << RS_OFS) | (63L << RST_OFS) | (1L << RH_OFS) |
+			(2L << MTYPE_OFS);
+		boot_loader_write_mem32(pao, dsp_index, 0x01800004, setting);
+		if (setting != boot_loader_read_mem32(pao, dsp_index,
+				0x01800004))
+			return hpi6205_error(dsp_index,
+				HPI6205_ERROR_DSP_EMIF);
+
+		/* EMIF CE2 setup - 32 bit async. This is 6713 #2 HPI, */
+		/* which occupies D15..0. 6713 starts at 27MHz, so need */
+		/* plenty of wait states */
+		setting =
+			(1L << WS_OFS) | (28L << WST_OFS) | (1L << WH_OFS) |
+			(1L << RS_OFS) | (63L << RST_OFS) | (1L << RH_OFS) |
+			(2L << MTYPE_OFS);
+		boot_loader_write_mem32(pao, dsp_index, 0x01800010, setting);
+		if (setting != boot_loader_read_mem32(pao, dsp_index,
+				0x01800010))
+			return hpi6205_error(dsp_index,
+				HPI6205_ERROR_DSP_EMIF);
+
+		/* EMIF CE3 setup - 32 bit async. */
+		/* This is the PLD on the ASI5000 cards only */
+		setting =
+			(1L << WS_OFS) | (10L << WST_OFS) | (1L << WH_OFS) |
+			(1L << RS_OFS) | (10L << RST_OFS) | (1L << RH_OFS) |
+			(2L << MTYPE_OFS);
+		boot_loader_write_mem32(pao, dsp_index, 0x01800014, setting);
+		if (setting != boot_loader_read_mem32(pao, dsp_index,
+				0x01800014))
+			return hpi6205_error(dsp_index,
+				HPI6205_ERROR_DSP_EMIF);
+
+		/* set EMIF SDRAM control for 2Mx32 SDRAM (512x32x4 bank) */
+		/*  need to use this else DSP code crashes? */
+		boot_loader_write_mem32(pao, dsp_index, 0x01800018,
+			0x07117000);
+
+		/* EMIF SDRAM Refresh Timing */
+		/* EMIF SDRAM timing  (orig = 0x410, emulator = 0x61a) */
+		boot_loader_write_mem32(pao, dsp_index, 0x0180001C,
+			0x00000410);
+
+	} else if (dsp_index == 1) {
+		/* test access to the C6713s HPI registers */
+		u32 write_data = 0, read_data = 0, i = 0;
+
+		/* Set up HPIC for little endian, by setiing HPIC:HWOB=1 */
+		write_data = 1;
+		boot_loader_write_mem32(pao, 0, HPICL_ADDR, write_data);
+		boot_loader_write_mem32(pao, 0, HPICH_ADDR, write_data);
+		/* C67 HPI is on lower 16bits of 32bit EMIF */
+		read_data =
+			0xFFF7 & boot_loader_read_mem32(pao, 0, HPICL_ADDR);
+		if (write_data != read_data) {
+			err = hpi6205_error(dsp_index,
+				HPI6205_ERROR_C6713_HPIC);
+			HPI_DEBUG_LOG(ERROR, "HPICL %x %x\n", write_data,
+				read_data);
+
+			return err;
+		}
+		/* HPIA - walking ones test */
+		write_data = 1;
+		for (i = 0; i < 32; i++) {
+			boot_loader_write_mem32(pao, 0, HPIAL_ADDR,
+				write_data);
+			boot_loader_write_mem32(pao, 0, HPIAH_ADDR,
+				(write_data >> 16));
+			read_data =
+				0xFFFF & boot_loader_read_mem32(pao, 0,
+				HPIAL_ADDR);
+			read_data =
+				read_data | ((0xFFFF &
+					boot_loader_read_mem32(pao, 0,
+						HPIAH_ADDR))
+				<< 16);
+			if (read_data != write_data) {
+				err = hpi6205_error(dsp_index,
+					HPI6205_ERROR_C6713_HPIA);
+				HPI_DEBUG_LOG(ERROR, "HPIA %x %x\n",
+					write_data, read_data);
+				return err;
+			}
+			write_data = write_data << 1;
+		}
+
+		/* setup C67x PLL
+		 *  ** C6713 datasheet says we cannot program PLL from HPI,
+		 * and indeed if we try to set the PLL multiply from the HPI,
+		 * the PLL does not seem to lock, so we enable the PLL and
+		 * use the default multiply of x 7, which for a 27MHz clock
+		 * gives a DSP speed of 189MHz
+		 */
+		/* bypass PLL */
+		boot_loader_write_mem32(pao, dsp_index, 0x01B7C100, 0x0000);
+		hpios_delay_micro_seconds(1000);
+		/* EMIF = 189/3=63MHz */
+		boot_loader_write_mem32(pao, dsp_index, 0x01B7C120, 0x8002);
+		/* peri = 189/2 */
+		boot_loader_write_mem32(pao, dsp_index, 0x01B7C11C, 0x8001);
+		/* cpu  = 189/1 */
+		boot_loader_write_mem32(pao, dsp_index, 0x01B7C118, 0x8000);
+		hpios_delay_micro_seconds(1000);
+		/* ** SGT test to take GPO3 high when we start the PLL */
+		/* and low when the delay is completed */
+		/* FSX0 <- '1' (GPO3) */
+		boot_loader_write_mem32(pao, 0, (0x018C0024L), 0x00002A0A);
+		/* PLL not bypassed */
+		boot_loader_write_mem32(pao, dsp_index, 0x01B7C100, 0x0001);
+		hpios_delay_micro_seconds(1000);
+		/* FSX0 <- '0' (GPO3) */
+		boot_loader_write_mem32(pao, 0, (0x018C0024L), 0x00002A02);
+
+		/* 6205 EMIF CE1 resetup - 32 bit async. */
+		/* Now 6713 #1 is running at 189MHz can reduce waitstates */
+		boot_loader_write_mem32(pao, 0, 0x01800004,	/* CE1 */
+			(1L << WS_OFS) | (8L << WST_OFS) | (1L << WH_OFS) |
+			(1L << RS_OFS) | (12L << RST_OFS) | (1L << RH_OFS) |
+			(2L << MTYPE_OFS));
+
+		hpios_delay_micro_seconds(1000);
+
+		/* check that we can read one of the PLL registers */
+		/* PLL should not be bypassed! */
+		if ((boot_loader_read_mem32(pao, dsp_index, 0x01B7C100) & 0xF)
+			!= 0x0001) {
+			err = hpi6205_error(dsp_index,
+				HPI6205_ERROR_C6713_PLL);
+			return err;
+		}
+		/* setup C67x EMIF  (note this is the only use of
+		   BAR1 via BootLoader_WriteMem32) */
+		boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_GCTL,
+			0x000034A8);
+		boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_CE0,
+			0x00000030);
+		boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_SDRAMEXT,
+			0x001BDF29);
+		boot_loader_write_mem32(pao, dsp_index, C6713_EMIF_SDRAMCTL,
+			0x47117000);
+		boot_loader_write_mem32(pao, dsp_index,
+			C6713_EMIF_SDRAMTIMING, 0x00000410);
+
+		hpios_delay_micro_seconds(1000);
+	} else if (dsp_index == 2) {
+		/* DSP 2 is a C6713 */
+
+	} else
+		err = hpi6205_error(dsp_index, HPI6205_ERROR_BAD_DSPINDEX);
+	return err;
+}
+
+static u16 boot_loader_test_memory(struct hpi_adapter_obj *pao, int dsp_index,
+	u32 start_address, u32 length)
+{
+	u32 i = 0, j = 0;
+	u32 test_addr = 0;
+	u32 test_data = 0, data = 0;
+
+	length = 1000;
+
+	/* for 1st word, test each bit in the 32bit word, */
+	/* dwLength specifies number of 32bit words to test */
+	/*for(i=0; i<dwLength; i++) */
+	i = 0;
+	{
+		test_addr = start_address + i * 4;
+		test_data = 0x00000001;
+		for (j = 0; j < 32; j++) {
+			boot_loader_write_mem32(pao, dsp_index, test_addr,
+				test_data);
+			data = boot_loader_read_mem32(pao, dsp_index,
+				test_addr);
+			if (data != test_data) {
+				HPI_DEBUG_LOG(VERBOSE,
+					"memtest error details  "
+					"%08x %08x %08x %i\n", test_addr,
+					test_data, data, dsp_index);
+				return 1;	/* error */
+			}
+			test_data = test_data << 1;
+		}	/* for(j) */
+	}	/* for(i) */
+
+	/* for the next 100 locations test each location, leaving it as zero */
+	/* write a zero to the next word in memory before we read */
+	/* the previous write to make sure every memory location is unique */
+	for (i = 0; i < 100; i++) {
+		test_addr = start_address + i * 4;
+		test_data = 0xA5A55A5A;
+		boot_loader_write_mem32(pao, dsp_index, test_addr, test_data);
+		boot_loader_write_mem32(pao, dsp_index, test_addr + 4, 0);
+		data = boot_loader_read_mem32(pao, dsp_index, test_addr);
+		if (data != test_data) {
+			HPI_DEBUG_LOG(VERBOSE,
+				"memtest error details  "
+				"%08x %08x %08x %i\n", test_addr, test_data,
+				data, dsp_index);
+			return 1;	/* error */
+		}
+		/* leave location as zero */
+		boot_loader_write_mem32(pao, dsp_index, test_addr, 0x0);
+	}
+
+	/* zero out entire memory block */
+	for (i = 0; i < length; i++) {
+		test_addr = start_address + i * 4;
+		boot_loader_write_mem32(pao, dsp_index, test_addr, 0x0);
+	}
+	return 0;
+}
+
+static u16 boot_loader_test_internal_memory(struct hpi_adapter_obj *pao,
+	int dsp_index)
+{
+	int err = 0;
+	if (dsp_index == 0) {
+		/* DSP 0 is a C6205 */
+		/* 64K prog mem */
+		err = boot_loader_test_memory(pao, dsp_index, 0x00000000,
+			0x10000);
+		if (!err)
+			/* 64K data mem */
+			err = boot_loader_test_memory(pao, dsp_index,
+				0x80000000, 0x10000);
+	} else if ((dsp_index == 1) || (dsp_index == 2)) {
+		/* DSP 1&2 are a C6713 */
+		/* 192K internal mem */
+		err = boot_loader_test_memory(pao, dsp_index, 0x00000000,
+			0x30000);
+		if (!err)
+			/* 64K internal mem / L2 cache */
+			err = boot_loader_test_memory(pao, dsp_index,
+				0x00030000, 0x10000);
+	} else
+		return hpi6205_error(dsp_index, HPI6205_ERROR_BAD_DSPINDEX);
+
+	if (err)
+		return hpi6205_error(dsp_index, HPI6205_ERROR_DSP_INTMEM);
+	else
+		return 0;
+}
+
+static u16 boot_loader_test_external_memory(struct hpi_adapter_obj *pao,
+	int dsp_index)
+{
+	u32 dRAM_start_address = 0;
+	u32 dRAM_size = 0;
+
+	if (dsp_index == 0) {
+		/* only test for SDRAM if an ASI5000 card */
+		if (pao->pci.subsys_device_id == 0x5000) {
+			/* DSP 0 is always C6205 */
+			dRAM_start_address = 0x00400000;
+			dRAM_size = 0x200000;
+			/*dwDRAMinc=1024; */
+		} else
+			return 0;
+	} else if ((dsp_index == 1) || (dsp_index == 2)) {
+		/* DSP 1 is a C6713 */
+		dRAM_start_address = 0x80000000;
+		dRAM_size = 0x200000;
+		/*dwDRAMinc=1024; */
+	} else
+		return hpi6205_error(dsp_index, HPI6205_ERROR_BAD_DSPINDEX);
+
+	if (boot_loader_test_memory(pao, dsp_index, dRAM_start_address,
+			dRAM_size))
+		return hpi6205_error(dsp_index, HPI6205_ERROR_DSP_EXTMEM);
+	return 0;
+}
+
+static u16 boot_loader_test_pld(struct hpi_adapter_obj *pao, int dsp_index)
+{
+	u32 data = 0;
+	if (dsp_index == 0) {
+		/* only test for DSP0 PLD on ASI5000 card */
+		if (pao->pci.subsys_device_id == 0x5000) {
+			/* PLD is located at CE3=0x03000000 */
+			data = boot_loader_read_mem32(pao, dsp_index,
+				0x03000008);
+			if ((data & 0xF) != 0x5)
+				return hpi6205_error(dsp_index,
+					HPI6205_ERROR_DSP_PLD);
+			data = boot_loader_read_mem32(pao, dsp_index,
+				0x0300000C);
+			if ((data & 0xF) != 0xA)
+				return hpi6205_error(dsp_index,
+					HPI6205_ERROR_DSP_PLD);
+		}
+	} else if (dsp_index == 1) {
+		/* DSP 1 is a C6713 */
+		if (pao->pci.subsys_device_id == 0x8700) {
+			/* PLD is located at CE1=0x90000000 */
+			data = boot_loader_read_mem32(pao, dsp_index,
+				0x90000010);
+			if ((data & 0xFF) != 0xAA)
+				return hpi6205_error(dsp_index,
+					HPI6205_ERROR_DSP_PLD);
+			/* 8713 - LED on */
+			boot_loader_write_mem32(pao, dsp_index, 0x90000000,
+				0x02);
+		}
+	}
+	return 0;
+}
+
+/** Transfer data to or from DSP
+ nOperation = H620_H620_HIF_SEND_DATA or H620_HIF_GET_DATA
+*/
+static short hpi6205_transfer_data(struct hpi_adapter_obj *pao, u8 *p_data,
+	u32 data_size, int operation)
+{
+	struct hpi_hw_obj *phw = pao->priv;
+	u32 data_transferred = 0;
+	u16 err = 0;
+#ifndef HPI6205_NO_HSR_POLL
+	u32 time_out;
+#endif
+	u32 temp2;
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+
+	if (!p_data)
+		return HPI_ERROR_INVALID_DATA_TRANSFER;
+
+	data_size &= ~3L;	/* round data_size down to nearest 4 bytes */
+
+	/* make sure state is IDLE */
+	if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT))
+		return HPI_ERROR_DSP_HARDWARE;
+
+	while (data_transferred < data_size) {
+		u32 this_copy = data_size - data_transferred;
+
+		if (this_copy > HPI6205_SIZEOF_DATA)
+			this_copy = HPI6205_SIZEOF_DATA;
+
+		if (operation == H620_HIF_SEND_DATA)
+			memcpy((void *)&interface->u.b_data[0],
+				&p_data[data_transferred], this_copy);
+
+		interface->transfer_size_in_bytes = this_copy;
+
+#ifdef HPI6205_NO_HSR_POLL
+		/* DSP must change this back to nOperation */
+		interface->dsp_ack = H620_HIF_IDLE;
+#endif
+
+		send_dsp_command(phw, operation);
+
+#ifdef HPI6205_NO_HSR_POLL
+		temp2 = wait_dsp_ack(phw, operation, HPI6205_TIMEOUT);
+		HPI_DEBUG_LOG(DEBUG, "spun %d times for data xfer of %d\n",
+			HPI6205_TIMEOUT - temp2, this_copy);
+
+		if (!temp2) {
+			/* timed out */
+			HPI_DEBUG_LOG(ERROR,
+				"timed out waiting for " "state %d got %d\n",
+				operation, interface->dsp_ack);
+
+			break;
+		}
+#else
+		/* spin waiting on the result */
+		time_out = HPI6205_TIMEOUT;
+		temp2 = 0;
+		while ((temp2 == 0) && time_out--) {
+			/* give 16k bus mastering transfer time to happen */
+			/*(16k / 132Mbytes/s = 122usec) */
+			hpios_delay_micro_seconds(20);
+			temp2 = ioread32(phw->prHSR);
+			temp2 &= C6205_HSR_INTSRC;
+		}
+		HPI_DEBUG_LOG(DEBUG, "spun %d times for data xfer of %d\n",
+			HPI6205_TIMEOUT - time_out, this_copy);
+		if (temp2 == C6205_HSR_INTSRC) {
+			HPI_DEBUG_LOG(VERBOSE,
+				"interrupt from HIF <data> OK\n");
+			/*
+			   if(interface->dwDspAck != nOperation) {
+			   HPI_DEBUG_LOG(DEBUG("interface->dwDspAck=%d,
+			   expected %d \n",
+			   interface->dwDspAck,nOperation);
+			   }
+			 */
+		}
+/* need to handle this differently... */
+		else {
+			HPI_DEBUG_LOG(ERROR,
+				"interrupt from HIF <data> BAD\n");
+			err = HPI_ERROR_DSP_HARDWARE;
+		}
+
+		/* reset the interrupt from the DSP */
+		iowrite32(C6205_HSR_INTSRC, phw->prHSR);
+#endif
+		if (operation == H620_HIF_GET_DATA)
+			memcpy(&p_data[data_transferred],
+				(void *)&interface->u.b_data[0], this_copy);
+
+		data_transferred += this_copy;
+	}
+	if (interface->dsp_ack != operation)
+		HPI_DEBUG_LOG(DEBUG, "interface->dsp_ack=%d, expected %d\n",
+			interface->dsp_ack, operation);
+	/*                      err=HPI_ERROR_DSP_HARDWARE; */
+
+	send_dsp_command(phw, H620_HIF_IDLE);
+
+	return err;
+}
+
+/* wait for up to timeout_us microseconds for the DSP
+   to signal state by DMA into dwDspAck
+*/
+static int wait_dsp_ack(struct hpi_hw_obj *phw, int state, int timeout_us)
+{
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+	int t = timeout_us / 4;
+
+	rmb();	/* ensure interface->dsp_ack is up to date */
+	while ((interface->dsp_ack != state) && --t) {
+		hpios_delay_micro_seconds(4);
+		rmb();	/* DSP changes dsp_ack by DMA */
+	}
+
+	/*HPI_DEBUG_LOG(VERBOSE, "Spun %d for %d\n", timeout_us/4-t, state); */
+	return t * 4;
+}
+
+/* set the busmaster interface to cmd, then interrupt the DSP */
+static void send_dsp_command(struct hpi_hw_obj *phw, int cmd)
+{
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+
+	u32 r;
+
+	interface->host_cmd = cmd;
+	wmb();	/* DSP gets state by DMA, make sure it is written to memory */
+	/* before we interrupt the DSP */
+	r = ioread32(phw->prHDCR);
+	r |= (u32)C6205_HDCR_DSPINT;
+	iowrite32(r, phw->prHDCR);
+	r &= ~(u32)C6205_HDCR_DSPINT;
+	iowrite32(r, phw->prHDCR);
+}
+
+static unsigned int message_count;
+
+static u16 message_response_sequence(struct hpi_adapter_obj *pao,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+#ifndef HPI6205_NO_HSR_POLL
+	u32 temp2;
+#endif
+	u32 time_out, time_out2;
+	struct hpi_hw_obj *phw = pao->priv;
+	struct bus_master_interface *interface = phw->p_interface_buffer;
+	u16 err = 0;
+
+	message_count++;
+	/* Assume buffer of type struct bus_master_interface
+	   is allocated "noncacheable" */
+
+	if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT)) {
+		HPI_DEBUG_LOG(DEBUG, "timeout waiting for idle\n");
+		return hpi6205_error(0, HPI6205_ERROR_MSG_RESP_IDLE_TIMEOUT);
+	}
+	interface->u.message_buffer = *phm;
+	/* signal we want a response */
+	send_dsp_command(phw, H620_HIF_GET_RESP);
+
+	time_out2 = wait_dsp_ack(phw, H620_HIF_GET_RESP, HPI6205_TIMEOUT);
+
+	if (time_out2 == 0) {
+		HPI_DEBUG_LOG(ERROR,
+			"(%u) timed out waiting for " "GET_RESP state [%x]\n",
+			message_count, interface->dsp_ack);
+	} else {
+		HPI_DEBUG_LOG(VERBOSE,
+			"(%u) transition to GET_RESP after %u\n",
+			message_count, HPI6205_TIMEOUT - time_out2);
+	}
+	/* spin waiting on HIF interrupt flag (end of msg process) */
+	time_out = HPI6205_TIMEOUT;
+
+#ifndef HPI6205_NO_HSR_POLL
+	temp2 = 0;
+	while ((temp2 == 0) && --time_out) {
+		temp2 = ioread32(phw->prHSR);
+		temp2 &= C6205_HSR_INTSRC;
+		hpios_delay_micro_seconds(1);
+	}
+	if (temp2 == C6205_HSR_INTSRC) {
+		rmb();	/* ensure we see latest value for dsp_ack */
+		if ((interface->dsp_ack != H620_HIF_GET_RESP)) {
+			HPI_DEBUG_LOG(DEBUG,
+				"(%u)interface->dsp_ack(0x%x) != "
+				"H620_HIF_GET_RESP, t=%u\n", message_count,
+				interface->dsp_ack,
+				HPI6205_TIMEOUT - time_out);
+		} else {
+			HPI_DEBUG_LOG(VERBOSE,
+				"(%u)int with GET_RESP after %u\n",
+				message_count, HPI6205_TIMEOUT - time_out);
+		}
+
+	} else {
+		/* can we do anything else in response to the error ? */
+		HPI_DEBUG_LOG(ERROR,
+			"interrupt from HIF module BAD (function %x)\n",
+			phm->function);
+	}
+
+	/* reset the interrupt from the DSP */
+	iowrite32(C6205_HSR_INTSRC, phw->prHSR);
+#endif
+
+	/* read the result */
+	if (time_out != 0)
+		*phr = interface->u.response_buffer;
+
+	/* set interface back to idle */
+	send_dsp_command(phw, H620_HIF_IDLE);
+
+	if ((time_out == 0) || (time_out2 == 0)) {
+		HPI_DEBUG_LOG(DEBUG, "something timed out!\n");
+		return hpi6205_error(0, HPI6205_ERROR_MSG_RESP_TIMEOUT);
+	}
+	/* special case for adapter close - */
+	/* wait for the DSP to indicate it is idle */
+	if (phm->function == HPI_ADAPTER_CLOSE) {
+		if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT)) {
+			HPI_DEBUG_LOG(DEBUG,
+				"timeout waiting for idle "
+				"(on adapter_close)\n");
+			return hpi6205_error(0,
+				HPI6205_ERROR_MSG_RESP_IDLE_TIMEOUT);
+		}
+	}
+	err = hpi_validate_response(phm, phr);
+	return err;
+}
+
+static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm,
+	struct hpi_response *phr)
+{
+
+	u16 err = 0;
+
+	hpios_dsplock_lock(pao);
+
+	err = message_response_sequence(pao, phm, phr);
+
+	/* maybe an error response */
+	if (err) {
+		/* something failed in the HPI/DSP interface */
+		phr->error = err;
+		pao->dsp_crashed++;
+
+		/* just the header of the response is valid */
+		phr->size = sizeof(struct hpi_response_header);
+		goto err;
+	} else
+		pao->dsp_crashed = 0;
+
+	if (phr->error != 0)	/* something failed in the DSP */
+		goto err;
+
+	switch (phm->function) {
+	case HPI_OSTREAM_WRITE:
+	case HPI_ISTREAM_ANC_WRITE:
+		err = hpi6205_transfer_data(pao, phm->u.d.u.data.pb_data,
+			phm->u.d.u.data.data_size, H620_HIF_SEND_DATA);
+		break;
+
+	case HPI_ISTREAM_READ:
+	case HPI_OSTREAM_ANC_READ:
+		err = hpi6205_transfer_data(pao, phm->u.d.u.data.pb_data,
+			phm->u.d.u.data.data_size, H620_HIF_GET_DATA);
+		break;
+
+	case HPI_CONTROL_SET_STATE:
+		if (phm->object == HPI_OBJ_CONTROLEX
+			&& phm->u.cx.attribute == HPI_COBRANET_SET_DATA)
+			err = hpi6205_transfer_data(pao,
+				phm->u.cx.u.cobranet_bigdata.pb_data,
+				phm->u.cx.u.cobranet_bigdata.byte_count,
+				H620_HIF_SEND_DATA);
+		break;
+
+	case HPI_CONTROL_GET_STATE:
+		if (phm->object == HPI_OBJ_CONTROLEX
+			&& phm->u.cx.attribute == HPI_COBRANET_GET_DATA)
+			err = hpi6205_transfer_data(pao,
+				phm->u.cx.u.cobranet_bigdata.pb_data,
+				phr->u.cx.u.cobranet_data.byte_count,
+				H620_HIF_GET_DATA);
+		break;
+	}
+	phr->error = err;
+
+err:
+	hpios_dsplock_unlock(pao);
+
+	return;
+}
diff --git a/linux/sound/pci/asihpi/hpi6205.h b/linux/sound/pci/asihpi/hpi6205.h
new file mode 100644
index 0000000..1adae08
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpi6205.h
@@ -0,0 +1,93 @@
+/*****************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+Host Interface module for an ASI6205 based
+bus mastering PCI adapter.
+
+Copyright AudioScience, Inc., 2003
+******************************************************************************/
+
+#ifndef _HPI6205_H_
+#define _HPI6205_H_
+
+/* transitional conditional compile shared between host and DSP */
+/* #define HPI6205_NO_HSR_POLL */
+
+#include "hpi_internal.h"
+
+/***********************************************************
+	Defines used for basic messaging
+************************************************************/
+#define H620_HIF_RESET          0
+#define H620_HIF_IDLE           1
+#define H620_HIF_GET_RESP       2
+#define H620_HIF_DATA_DONE      3
+#define H620_HIF_DATA_MASK      0x10
+#define H620_HIF_SEND_DATA      0x14
+#define H620_HIF_GET_DATA       0x15
+#define H620_HIF_UNKNOWN                0x0000ffff
+
+/***********************************************************
+	Types used for mixer control caching
+************************************************************/
+
+#define H620_MAX_ISTREAMS 32
+#define H620_MAX_OSTREAMS 32
+#define HPI_NMIXER_CONTROLS 2048
+
+/*********************************************************************
+This is used for dynamic control cache allocation
+**********************************************************************/
+struct controlcache_6205 {
+	u32 number_of_controls;
+	u32 physical_address32;
+	u32 size_in_bytes;
+};
+
+/*********************************************************************
+This is used for dynamic allocation of async event array
+**********************************************************************/
+struct async_event_buffer_6205 {
+	u32 physical_address32;
+	u32 spare;
+	struct hpi_fifo_buffer b;
+};
+
+/***********************************************************
+The Host located memory buffer that the 6205 will bus master
+in and out of.
+************************************************************/
+#define HPI6205_SIZEOF_DATA (16*1024)
+struct bus_master_interface {
+	u32 host_cmd;
+	u32 dsp_ack;
+	u32 transfer_size_in_bytes;
+	union {
+		struct hpi_message message_buffer;
+		struct hpi_response response_buffer;
+		u8 b_data[HPI6205_SIZEOF_DATA];
+	} u;
+	struct controlcache_6205 control_cache;
+	struct async_event_buffer_6205 async_buffer;
+	struct hpi_hostbuffer_status
+	 instream_host_buffer_status[H620_MAX_ISTREAMS];
+	struct hpi_hostbuffer_status
+	 outstream_host_buffer_status[H620_MAX_OSTREAMS];
+};
+
+#endif
diff --git a/linux/sound/pci/asihpi/hpi_internal.h b/linux/sound/pci/asihpi/hpi_internal.h
new file mode 100644
index 0000000..16f502d
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpi_internal.h
@@ -0,0 +1,1650 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+HPI internal definitions
+
+(C) Copyright AudioScience Inc. 1996-2009
+******************************************************************************/
+
+#ifndef _HPI_INTERNAL_H_
+#define _HPI_INTERNAL_H_
+
+#include "hpi.h"
+/** maximum number of memory regions mapped to an adapter */
+#define HPI_MAX_ADAPTER_MEM_SPACES (2)
+
+/* Each OS needs its own hpios.h, or specific define as above */
+#include "hpios.h"
+
+/* physical memory allocation */
+void hpios_locked_mem_init(void
+	);
+void hpios_locked_mem_free_all(void
+	);
+#define hpios_locked_mem_prepare(a, b, c, d);
+#define hpios_locked_mem_unprepare(a)
+
+/** Allocate and map an area of locked memory for bus master DMA operations.
+
+On success, *pLockedMemeHandle is a valid handle, and 0 is returned
+On error *pLockedMemHandle marked invalid, non-zero returned.
+
+If this function succeeds, then HpiOs_LockedMem_GetVirtAddr() and
+HpiOs_LockedMem_GetPyhsAddr() will always succed on the returned handle.
+*/
+u16 hpios_locked_mem_alloc(struct consistent_dma_area *p_locked_mem_handle,
+							   /**< memory handle */
+	u32 size, /**< size in bytes to allocate */
+	struct pci_dev *p_os_reference
+	/**< OS specific data required for memory allocation */
+	);
+
+/** Free mapping and memory represented by LockedMemHandle
+
+Frees any resources, then invalidates the handle.
+Returns 0 on success, 1 if handle is invalid.
+
+*/
+u16 hpios_locked_mem_free(struct consistent_dma_area *locked_mem_handle);
+
+/** Get the physical PCI address of memory represented by LockedMemHandle.
+
+If handle is invalid *pPhysicalAddr is set to zero and return 1
+*/
+u16 hpios_locked_mem_get_phys_addr(struct consistent_dma_area
+	*locked_mem_handle, u32 *p_physical_addr);
+
+/** Get the CPU address of of memory represented by LockedMemHandle.
+
+If handle is NULL *ppvVirtualAddr is set to NULL and return 1
+*/
+u16 hpios_locked_mem_get_virt_addr(struct consistent_dma_area
+	*locked_mem_handle, void **ppv_virtual_addr);
+
+/** Check that handle is valid
+i.e it represents a valid memory area
+*/
+u16 hpios_locked_mem_valid(struct consistent_dma_area *locked_mem_handle);
+
+/* timing/delay */
+void hpios_delay_micro_seconds(u32 num_micro_sec);
+
+struct hpi_message;
+struct hpi_response;
+
+typedef void hpi_handler_func(struct hpi_message *, struct hpi_response *);
+
+/* If the assert fails, compiler complains
+   something like size of array `msg' is negative.
+   Unlike linux BUILD_BUG_ON, this works outside function scope.
+*/
+#define compile_time_assert(cond, msg) \
+    typedef char ASSERT_##msg[(cond) ? 1 : -1]
+
+/*/////////////////////////////////////////////////////////////////////////// */
+/* Private HPI Entity related definitions                                     */
+
+#define STR_SIZE_FIELD_MAX 65535U
+#define STR_TYPE_FIELD_MAX 255U
+#define STR_ROLE_FIELD_MAX 255U
+
+struct hpi_entity_str {
+	u16 size;
+	u8 type;
+	u8 role;
+};
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable : 4200)
+#endif
+
+struct hpi_entity {
+	struct hpi_entity_str header;
+#if ! defined(HPI_OS_DSP_C6000) || (defined(HPI_OS_DSP_C6000) && (__TI_COMPILER_VERSION__ > 6000008))
+	/* DSP C6000 compiler v6.0.8 and lower
+	   do not support  flexible array member */
+	u8 value[];
+#else
+	/* NOTE! Using sizeof(struct hpi_entity) will give erroneous results */
+#define HPI_INTERNAL_WARN_ABOUT_ENTITY_VALUE
+	u8 value[1];
+#endif
+};
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+/******************************************* bus types */
+enum HPI_BUSES {
+	HPI_BUS_ISAPNP = 1,
+	HPI_BUS_PCI = 2,
+	HPI_BUS_USB = 3,
+	HPI_BUS_NET = 4
+};
+
+/******************************************* CONTROL ATTRIBUTES ****/
+/* (in order of control type ID */
+
+/* This allows for 255 control types, 256 unique attributes each */
+#define HPI_CTL_ATTR(ctl, ai) (HPI_CONTROL_##ctl * 0x100 + ai)
+
+/* Get the sub-index of the attribute for a control type */
+#define HPI_CTL_ATTR_INDEX(i) (i&0xff)
+
+/* Extract the control from the control attribute */
+#define HPI_CTL_ATTR_CONTROL(i) (i>>8)
+
+/* Generic control attributes.  */
+
+/** Enable a control.
+0=disable, 1=enable
+\note generic to all mixer plugins?
+*/
+#define HPI_GENERIC_ENABLE HPI_CTL_ATTR(GENERIC, 1)
+
+/** Enable event generation for a control.
+0=disable, 1=enable
+\note generic to all controls that can generate events
+*/
+#define HPI_GENERIC_EVENT_ENABLE HPI_CTL_ATTR(GENERIC, 2)
+
+/* Volume Control attributes */
+#define HPI_VOLUME_GAIN                 HPI_CTL_ATTR(VOLUME, 1)
+#define HPI_VOLUME_AUTOFADE             HPI_CTL_ATTR(VOLUME, 2)
+
+/** For HPI_ControlQuery() to get the number of channels of a volume control*/
+#define HPI_VOLUME_NUM_CHANNELS         HPI_CTL_ATTR(VOLUME, 6)
+#define HPI_VOLUME_RANGE                HPI_CTL_ATTR(VOLUME, 10)
+
+/** Level Control attributes */
+#define HPI_LEVEL_GAIN                  HPI_CTL_ATTR(LEVEL, 1)
+#define HPI_LEVEL_RANGE                 HPI_CTL_ATTR(LEVEL, 10)
+
+/* Meter Control attributes */
+/** return RMS signal level */
+#define HPI_METER_RMS                   HPI_CTL_ATTR(METER, 1)
+/** return peak signal level */
+#define HPI_METER_PEAK                  HPI_CTL_ATTR(METER, 2)
+/** ballistics for ALL rms meters on adapter */
+#define HPI_METER_RMS_BALLISTICS        HPI_CTL_ATTR(METER, 3)
+/** ballistics for ALL peak meters on adapter */
+#define HPI_METER_PEAK_BALLISTICS       HPI_CTL_ATTR(METER, 4)
+
+/** For HPI_ControlQuery() to get the number of channels of a meter control*/
+#define HPI_METER_NUM_CHANNELS          HPI_CTL_ATTR(METER, 5)
+
+/* Multiplexer control attributes */
+#define HPI_MULTIPLEXER_SOURCE          HPI_CTL_ATTR(MULTIPLEXER, 1)
+#define HPI_MULTIPLEXER_QUERYSOURCE     HPI_CTL_ATTR(MULTIPLEXER, 2)
+
+/** AES/EBU transmitter control attributes */
+/** AESEBU or SPDIF */
+#define HPI_AESEBUTX_FORMAT             HPI_CTL_ATTR(AESEBUTX, 1)
+#define HPI_AESEBUTX_SAMPLERATE         HPI_CTL_ATTR(AESEBUTX, 3)
+#define HPI_AESEBUTX_CHANNELSTATUS      HPI_CTL_ATTR(AESEBUTX, 4)
+#define HPI_AESEBUTX_USERDATA           HPI_CTL_ATTR(AESEBUTX, 5)
+
+/** AES/EBU receiver control attributes */
+#define HPI_AESEBURX_FORMAT             HPI_CTL_ATTR(AESEBURX, 1)
+#define HPI_AESEBURX_ERRORSTATUS        HPI_CTL_ATTR(AESEBURX, 2)
+#define HPI_AESEBURX_SAMPLERATE         HPI_CTL_ATTR(AESEBURX, 3)
+#define HPI_AESEBURX_CHANNELSTATUS      HPI_CTL_ATTR(AESEBURX, 4)
+#define HPI_AESEBURX_USERDATA           HPI_CTL_ATTR(AESEBURX, 5)
+
+/** \defgroup tuner_defs Tuners
+\{
+*/
+/** \defgroup tuner_attrs Tuner control attributes
+\{
+*/
+#define HPI_TUNER_BAND                  HPI_CTL_ATTR(TUNER, 1)
+#define HPI_TUNER_FREQ                  HPI_CTL_ATTR(TUNER, 2)
+#define HPI_TUNER_LEVEL                 HPI_CTL_ATTR(TUNER, 3)
+#define HPI_TUNER_AUDIOMUTE             HPI_CTL_ATTR(TUNER, 4)
+/* use TUNER_STATUS instead */
+#define HPI_TUNER_VIDEO_STATUS          HPI_CTL_ATTR(TUNER, 5)
+#define HPI_TUNER_GAIN                  HPI_CTL_ATTR(TUNER, 6)
+#define HPI_TUNER_STATUS                HPI_CTL_ATTR(TUNER, 7)
+#define HPI_TUNER_MODE                  HPI_CTL_ATTR(TUNER, 8)
+/** RDS data. */
+#define HPI_TUNER_RDS                   HPI_CTL_ATTR(TUNER, 9)
+/** Audio pre-emphasis. */
+#define HPI_TUNER_DEEMPHASIS            HPI_CTL_ATTR(TUNER, 10)
+/** HD Radio tuner program control. */
+#define HPI_TUNER_PROGRAM               HPI_CTL_ATTR(TUNER, 11)
+/** HD Radio tuner digital signal quality. */
+#define HPI_TUNER_HDRADIO_SIGNAL_QUALITY        HPI_CTL_ATTR(TUNER, 12)
+/** HD Radio SDK firmware version. */
+#define HPI_TUNER_HDRADIO_SDK_VERSION   HPI_CTL_ATTR(TUNER, 13)
+/** HD Radio DSP firmware version. */
+#define HPI_TUNER_HDRADIO_DSP_VERSION   HPI_CTL_ATTR(TUNER, 14)
+/** HD Radio signal blend (force analog, or automatic). */
+#define HPI_TUNER_HDRADIO_BLEND         HPI_CTL_ATTR(TUNER, 15)
+
+/** \} */
+
+/** \defgroup pads_attrs Tuner PADs control attributes
+\{
+*/
+/** The text string containing the station/channel combination. */
+#define HPI_PAD_CHANNEL_NAME            HPI_CTL_ATTR(PAD, 1)
+/** The text string containing the artist. */
+#define HPI_PAD_ARTIST                  HPI_CTL_ATTR(PAD, 2)
+/** The text string containing the title. */
+#define HPI_PAD_TITLE                   HPI_CTL_ATTR(PAD, 3)
+/** The text string containing the comment. */
+#define HPI_PAD_COMMENT                 HPI_CTL_ATTR(PAD, 4)
+/** The integer containing the PTY code. */
+#define HPI_PAD_PROGRAM_TYPE            HPI_CTL_ATTR(PAD, 5)
+/** The integer containing the program identification. */
+#define HPI_PAD_PROGRAM_ID              HPI_CTL_ATTR(PAD, 6)
+/** The integer containing whether traffic information is supported.
+Contains either 1 or 0. */
+#define HPI_PAD_TA_SUPPORT              HPI_CTL_ATTR(PAD, 7)
+/** The integer containing whether traffic announcement is in progress.
+Contains either 1 or 0. */
+#define HPI_PAD_TA_ACTIVE               HPI_CTL_ATTR(PAD, 8)
+/** \} */
+/** \} */
+
+/* VOX control attributes */
+#define HPI_VOX_THRESHOLD               HPI_CTL_ATTR(VOX, 1)
+
+/*?? channel mode used hpi_multiplexer_source attribute == 1 */
+#define HPI_CHANNEL_MODE_MODE HPI_CTL_ATTR(CHANNEL_MODE, 1)
+
+/** \defgroup channel_modes Channel Modes
+Used for HPI_ChannelModeSet/Get()
+\{
+*/
+/** Left channel out = left channel in, Right channel out = right channel in. */
+#define HPI_CHANNEL_MODE_NORMAL                 1
+/** Left channel out = right channel in, Right channel out = left channel in. */
+#define HPI_CHANNEL_MODE_SWAP                   2
+/** Left channel out = left channel in, Right channel out = left channel in. */
+#define HPI_CHANNEL_MODE_LEFT_TO_STEREO         3
+/** Left channel out = right channel in, Right channel out = right channel in.*/
+#define HPI_CHANNEL_MODE_RIGHT_TO_STEREO        4
+/** Left channel out = (left channel in + right channel in)/2,
+    Right channel out = mute. */
+#define HPI_CHANNEL_MODE_STEREO_TO_LEFT         5
+/** Left channel out = mute,
+    Right channel out = (right channel in + left channel in)/2. */
+#define HPI_CHANNEL_MODE_STEREO_TO_RIGHT        6
+#define HPI_CHANNEL_MODE_LAST                   6
+/** \} */
+
+/* Bitstream control set attributes */
+#define HPI_BITSTREAM_DATA_POLARITY     HPI_CTL_ATTR(BITSTREAM, 1)
+#define HPI_BITSTREAM_CLOCK_EDGE        HPI_CTL_ATTR(BITSTREAM, 2)
+#define HPI_BITSTREAM_CLOCK_SOURCE      HPI_CTL_ATTR(BITSTREAM, 3)
+
+#define HPI_POLARITY_POSITIVE           0
+#define HPI_POLARITY_NEGATIVE           1
+
+/* Bitstream control get attributes */
+#define HPI_BITSTREAM_ACTIVITY          1
+
+/* SampleClock control attributes */
+#define HPI_SAMPLECLOCK_SOURCE                  HPI_CTL_ATTR(SAMPLECLOCK, 1)
+#define HPI_SAMPLECLOCK_SAMPLERATE              HPI_CTL_ATTR(SAMPLECLOCK, 2)
+#define HPI_SAMPLECLOCK_SOURCE_INDEX            HPI_CTL_ATTR(SAMPLECLOCK, 3)
+#define HPI_SAMPLECLOCK_LOCAL_SAMPLERATE\
+	HPI_CTL_ATTR(SAMPLECLOCK, 4)
+#define HPI_SAMPLECLOCK_AUTO                    HPI_CTL_ATTR(SAMPLECLOCK, 5)
+#define HPI_SAMPLECLOCK_LOCAL_LOCK                      HPI_CTL_ATTR(SAMPLECLOCK, 6)
+
+/* Microphone control attributes */
+#define HPI_MICROPHONE_PHANTOM_POWER HPI_CTL_ATTR(MICROPHONE, 1)
+
+/** Equalizer control attributes */
+/** Used to get number of filters in an EQ. (Can't set) */
+#define HPI_EQUALIZER_NUM_FILTERS HPI_CTL_ATTR(EQUALIZER, 1)
+/** Set/get the filter by type, freq, Q, gain */
+#define HPI_EQUALIZER_FILTER HPI_CTL_ATTR(EQUALIZER, 2)
+/** Get the biquad coefficients */
+#define HPI_EQUALIZER_COEFFICIENTS HPI_CTL_ATTR(EQUALIZER, 3)
+
+/* Note compander also uses HPI_GENERIC_ENABLE */
+#define HPI_COMPANDER_PARAMS     HPI_CTL_ATTR(COMPANDER, 1)
+#define HPI_COMPANDER_MAKEUPGAIN HPI_CTL_ATTR(COMPANDER, 2)
+#define HPI_COMPANDER_THRESHOLD  HPI_CTL_ATTR(COMPANDER, 3)
+#define HPI_COMPANDER_RATIO      HPI_CTL_ATTR(COMPANDER, 4)
+#define HPI_COMPANDER_ATTACK     HPI_CTL_ATTR(COMPANDER, 5)
+#define HPI_COMPANDER_DECAY      HPI_CTL_ATTR(COMPANDER, 6)
+
+/* Cobranet control attributes. */
+#define HPI_COBRANET_SET         HPI_CTL_ATTR(COBRANET, 1)
+#define HPI_COBRANET_GET         HPI_CTL_ATTR(COBRANET, 2)
+#define HPI_COBRANET_SET_DATA    HPI_CTL_ATTR(COBRANET, 3)
+#define HPI_COBRANET_GET_DATA    HPI_CTL_ATTR(COBRANET, 4)
+#define HPI_COBRANET_GET_STATUS  HPI_CTL_ATTR(COBRANET, 5)
+#define HPI_COBRANET_SEND_PACKET HPI_CTL_ATTR(COBRANET, 6)
+#define HPI_COBRANET_GET_PACKET  HPI_CTL_ATTR(COBRANET, 7)
+
+/*------------------------------------------------------------
+ Cobranet Chip Bridge - copied from HMI.H
+------------------------------------------------------------*/
+#define  HPI_COBRANET_HMI_cobra_bridge           0x20000
+#define  HPI_COBRANET_HMI_cobra_bridge_tx_pkt_buf \
+	(HPI_COBRANET_HMI_cobra_bridge + 0x1000)
+#define  HPI_COBRANET_HMI_cobra_bridge_rx_pkt_buf \
+	(HPI_COBRANET_HMI_cobra_bridge + 0x2000)
+#define  HPI_COBRANET_HMI_cobra_if_table1         0x110000
+#define  HPI_COBRANET_HMI_cobra_if_phy_address \
+	(HPI_COBRANET_HMI_cobra_if_table1 + 0xd)
+#define  HPI_COBRANET_HMI_cobra_protocolIP       0x72000
+#define  HPI_COBRANET_HMI_cobra_ip_mon_currentIP \
+	(HPI_COBRANET_HMI_cobra_protocolIP + 0x0)
+#define  HPI_COBRANET_HMI_cobra_ip_mon_staticIP \
+	(HPI_COBRANET_HMI_cobra_protocolIP + 0x2)
+#define  HPI_COBRANET_HMI_cobra_sys              0x100000
+#define  HPI_COBRANET_HMI_cobra_sys_desc \
+		(HPI_COBRANET_HMI_cobra_sys + 0x0)
+#define  HPI_COBRANET_HMI_cobra_sys_objectID \
+	(HPI_COBRANET_HMI_cobra_sys + 0x100)
+#define  HPI_COBRANET_HMI_cobra_sys_contact \
+	(HPI_COBRANET_HMI_cobra_sys + 0x200)
+#define  HPI_COBRANET_HMI_cobra_sys_name \
+		(HPI_COBRANET_HMI_cobra_sys + 0x300)
+#define  HPI_COBRANET_HMI_cobra_sys_location \
+	(HPI_COBRANET_HMI_cobra_sys + 0x400)
+
+/*------------------------------------------------------------
+ Cobranet Chip Status bits
+------------------------------------------------------------*/
+#define HPI_COBRANET_HMI_STATUS_RXPACKET 2
+#define HPI_COBRANET_HMI_STATUS_TXPACKET 3
+
+/*------------------------------------------------------------
+ Ethernet header size
+------------------------------------------------------------*/
+#define HPI_ETHERNET_HEADER_SIZE (16)
+
+/* These defines are used to fill in protocol information for an Ethernet packet
+    sent using HMI on CS18102 */
+/** ID supplied by Cirrius for ASI packets. */
+#define HPI_ETHERNET_PACKET_ID                  0x85
+/** Simple packet - no special routing required */
+#define HPI_ETHERNET_PACKET_V1                  0x01
+/** This packet must make its way to the host across the HPI interface */
+#define HPI_ETHERNET_PACKET_HOSTED_VIA_HMI      0x20
+/** This packet must make its way to the host across the HPI interface */
+#define HPI_ETHERNET_PACKET_HOSTED_VIA_HMI_V1   0x21
+/** This packet must make its way to the host across the HPI interface */
+#define HPI_ETHERNET_PACKET_HOSTED_VIA_HPI      0x40
+/** This packet must make its way to the host across the HPI interface */
+#define HPI_ETHERNET_PACKET_HOSTED_VIA_HPI_V1   0x41
+
+#define HPI_ETHERNET_UDP_PORT (44600)	/*!< UDP messaging port */
+
+/** Base network time out is set to 100 milli-seconds. */
+#define HPI_ETHERNET_TIMEOUT_MS      (100)
+
+/** \defgroup tonedet_attr Tonedetector attributes
+\{
+Used by HPI_ToneDetector_Set() and HPI_ToneDetector_Get()
+*/
+
+/** Set the threshold level of a tonedetector,
+Threshold is a -ve number in units of dB/100,
+*/
+#define HPI_TONEDETECTOR_THRESHOLD HPI_CTL_ATTR(TONEDETECTOR, 1)
+
+/** Get the current state of tonedetection
+The result is a bitmap of detected tones.  pairs of bits represent the left
+and right channels, with left channel in LSB.
+The lowest frequency detector state is in the LSB
+*/
+#define HPI_TONEDETECTOR_STATE HPI_CTL_ATTR(TONEDETECTOR, 2)
+
+/** Get the frequency of a tonedetector band.
+*/
+#define HPI_TONEDETECTOR_FREQUENCY HPI_CTL_ATTR(TONEDETECTOR, 3)
+
+/**\}*/
+
+/** \defgroup silencedet_attr SilenceDetector attributes
+\{
+*/
+
+/** Get the current state of tonedetection
+The result is a bitmap with 1s for silent channels. Left channel is in LSB
+*/
+#define HPI_SILENCEDETECTOR_STATE \
+  HPI_CTL_ATTR(SILENCEDETECTOR, 2)
+
+/** Set the threshold level of a SilenceDetector,
+Threshold is a -ve number in units of dB/100,
+*/
+#define HPI_SILENCEDETECTOR_THRESHOLD \
+  HPI_CTL_ATTR(SILENCEDETECTOR, 1)
+
+/** get/set the silence time before the detector triggers
+*/
+#define HPI_SILENCEDETECTOR_DELAY \
+       HPI_CTL_ATTR(SILENCEDETECTOR, 3)
+
+/**\}*/
+
+/* Locked memory buffer alloc/free phases */
+/** use one message to allocate or free physical memory */
+#define HPI_BUFFER_CMD_EXTERNAL                 0
+/** alloc physical memory */
+#define HPI_BUFFER_CMD_INTERNAL_ALLOC           1
+/** send physical memory address to adapter */
+#define HPI_BUFFER_CMD_INTERNAL_GRANTADAPTER    2
+/** notify adapter to stop using physical buffer */
+#define HPI_BUFFER_CMD_INTERNAL_REVOKEADAPTER   3
+/** free physical buffer */
+#define HPI_BUFFER_CMD_INTERNAL_FREE            4
+
+/******************************************* CONTROLX ATTRIBUTES ****/
+/* NOTE: All controlx attributes must be unique, unlike control attributes */
+
+/*****************************************************************************/
+/*****************************************************************************/
+/********               HPI LOW LEVEL MESSAGES                  *******/
+/*****************************************************************************/
+/*****************************************************************************/
+/** Pnp ids */
+/** "ASI"  - actual is "ASX" - need to change */
+#define HPI_ID_ISAPNP_AUDIOSCIENCE      0x0669
+/** PCI vendor ID that AudioScience uses */
+#define HPI_PCI_VENDOR_ID_AUDIOSCIENCE  0x175C
+/** PCI vendor ID that the DSP56301 has */
+#define HPI_PCI_VENDOR_ID_MOTOROLA      0x1057
+/** PCI vendor ID that TI uses */
+#define HPI_PCI_VENDOR_ID_TI            0x104C
+
+#define HPI_PCI_DEV_ID_PCI2040          0xAC60
+/** TI's C6205 PCI interface has this ID */
+#define HPI_PCI_DEV_ID_DSP6205          0xA106
+
+#define HPI_USB_VENDOR_ID_AUDIOSCIENCE  0x1257
+#define HPI_USB_W2K_TAG                 0x57495341	/* "ASIW"       */
+#define HPI_USB_LINUX_TAG               0x4C495341	/* "ASIL"       */
+
+/** First 2 hex digits define the adapter family */
+#define HPI_ADAPTER_FAMILY_MASK         0xff00
+#define HPI_MODULE_FAMILY_MASK          0xfff0
+
+#define HPI_ADAPTER_FAMILY_ASI(f)   (f & HPI_ADAPTER_FAMILY_MASK)
+#define HPI_MODULE_FAMILY_ASI(f)   (f & HPI_MODULE_FAMILY_MASK)
+#define HPI_ADAPTER_ASI(f)   (f)
+
+/******************************************* message types */
+#define HPI_TYPE_MESSAGE                        1
+#define HPI_TYPE_RESPONSE                       2
+#define HPI_TYPE_DATA                           3
+#define HPI_TYPE_SSX2BYPASS_MESSAGE             4
+
+/******************************************* object types */
+#define HPI_OBJ_SUBSYSTEM                       1
+#define HPI_OBJ_ADAPTER                         2
+#define HPI_OBJ_OSTREAM                         3
+#define HPI_OBJ_ISTREAM                         4
+#define HPI_OBJ_MIXER                           5
+#define HPI_OBJ_NODE                            6
+#define HPI_OBJ_CONTROL                         7
+#define HPI_OBJ_NVMEMORY                        8
+#define HPI_OBJ_GPIO                            9
+#define HPI_OBJ_WATCHDOG                        10
+#define HPI_OBJ_CLOCK                           11
+#define HPI_OBJ_PROFILE                         12
+#define HPI_OBJ_CONTROLEX                       13
+#define HPI_OBJ_ASYNCEVENT                      14
+
+#define HPI_OBJ_MAXINDEX                        14
+
+/******************************************* methods/functions */
+
+#define HPI_OBJ_FUNCTION_SPACING        0x100
+#define HPI_MAKE_INDEX(obj, index) (obj * HPI_OBJ_FUNCTION_SPACING + index)
+#define HPI_EXTRACT_INDEX(fn) (fn & 0xff)
+
+/* SUB-SYSTEM */
+#define HPI_SUBSYS_OPEN                 HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 1)
+#define HPI_SUBSYS_GET_VERSION          HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 2)
+#define HPI_SUBSYS_GET_INFO             HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 3)
+#define HPI_SUBSYS_FIND_ADAPTERS        HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 4)
+#define HPI_SUBSYS_CREATE_ADAPTER       HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 5)
+#define HPI_SUBSYS_CLOSE                HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 6)
+#define HPI_SUBSYS_DELETE_ADAPTER       HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 7)
+#define HPI_SUBSYS_DRIVER_LOAD          HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 8)
+#define HPI_SUBSYS_DRIVER_UNLOAD        HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 9)
+#define HPI_SUBSYS_READ_PORT_8          HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 10)
+#define HPI_SUBSYS_WRITE_PORT_8         HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 11)
+#define HPI_SUBSYS_GET_NUM_ADAPTERS     HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 12)
+#define HPI_SUBSYS_GET_ADAPTER          HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 13)
+#define HPI_SUBSYS_SET_NETWORK_INTERFACE HPI_MAKE_INDEX(HPI_OBJ_SUBSYSTEM, 14)
+#define HPI_SUBSYS_FUNCTION_COUNT 14
+/* ADAPTER */
+#define HPI_ADAPTER_OPEN                HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 1)
+#define HPI_ADAPTER_CLOSE               HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 2)
+#define HPI_ADAPTER_GET_INFO            HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 3)
+#define HPI_ADAPTER_GET_ASSERT          HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 4)
+#define HPI_ADAPTER_TEST_ASSERT         HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 5)
+#define HPI_ADAPTER_SET_MODE            HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 6)
+#define HPI_ADAPTER_GET_MODE            HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 7)
+#define HPI_ADAPTER_ENABLE_CAPABILITY   HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 8)
+#define HPI_ADAPTER_SELFTEST            HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 9)
+#define HPI_ADAPTER_FIND_OBJECT         HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 10)
+#define HPI_ADAPTER_QUERY_FLASH         HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 11)
+#define HPI_ADAPTER_START_FLASH         HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 12)
+#define HPI_ADAPTER_PROGRAM_FLASH       HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 13)
+#define HPI_ADAPTER_SET_PROPERTY        HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 14)
+#define HPI_ADAPTER_GET_PROPERTY        HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 15)
+#define HPI_ADAPTER_ENUM_PROPERTY       HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 16)
+#define HPI_ADAPTER_MODULE_INFO         HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 17)
+#define HPI_ADAPTER_DEBUG_READ          HPI_MAKE_INDEX(HPI_OBJ_ADAPTER, 18)
+#define HPI_ADAPTER_FUNCTION_COUNT 18
+/* OUTPUT STREAM */
+#define HPI_OSTREAM_OPEN                HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 1)
+#define HPI_OSTREAM_CLOSE               HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 2)
+#define HPI_OSTREAM_WRITE               HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 3)
+#define HPI_OSTREAM_START               HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 4)
+#define HPI_OSTREAM_STOP                HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 5)
+#define HPI_OSTREAM_RESET               HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 6)
+#define HPI_OSTREAM_GET_INFO            HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 7)
+#define HPI_OSTREAM_QUERY_FORMAT        HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 8)
+#define HPI_OSTREAM_DATA                HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 9)
+#define HPI_OSTREAM_SET_VELOCITY        HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 10)
+#define HPI_OSTREAM_SET_PUNCHINOUT      HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 11)
+#define HPI_OSTREAM_SINEGEN             HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 12)
+#define HPI_OSTREAM_ANC_RESET           HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 13)
+#define HPI_OSTREAM_ANC_GET_INFO        HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 14)
+#define HPI_OSTREAM_ANC_READ            HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 15)
+#define HPI_OSTREAM_SET_TIMESCALE       HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 16)
+#define HPI_OSTREAM_SET_FORMAT          HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 17)
+#define HPI_OSTREAM_HOSTBUFFER_ALLOC    HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 18)
+#define HPI_OSTREAM_HOSTBUFFER_FREE     HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 19)
+#define HPI_OSTREAM_GROUP_ADD           HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 20)
+#define HPI_OSTREAM_GROUP_GETMAP        HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 21)
+#define HPI_OSTREAM_GROUP_RESET         HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 22)
+#define HPI_OSTREAM_HOSTBUFFER_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 23)
+#define HPI_OSTREAM_WAIT_START          HPI_MAKE_INDEX(HPI_OBJ_OSTREAM, 24)
+#define HPI_OSTREAM_FUNCTION_COUNT              24
+/* INPUT STREAM */
+#define HPI_ISTREAM_OPEN                HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 1)
+#define HPI_ISTREAM_CLOSE               HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 2)
+#define HPI_ISTREAM_SET_FORMAT          HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 3)
+#define HPI_ISTREAM_READ                HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 4)
+#define HPI_ISTREAM_START               HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 5)
+#define HPI_ISTREAM_STOP                HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 6)
+#define HPI_ISTREAM_RESET               HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 7)
+#define HPI_ISTREAM_GET_INFO            HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 8)
+#define HPI_ISTREAM_QUERY_FORMAT        HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 9)
+#define HPI_ISTREAM_ANC_RESET           HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 10)
+#define HPI_ISTREAM_ANC_GET_INFO        HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 11)
+#define HPI_ISTREAM_ANC_WRITE           HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 12)
+#define HPI_ISTREAM_HOSTBUFFER_ALLOC    HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 13)
+#define HPI_ISTREAM_HOSTBUFFER_FREE     HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 14)
+#define HPI_ISTREAM_GROUP_ADD           HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 15)
+#define HPI_ISTREAM_GROUP_GETMAP        HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 16)
+#define HPI_ISTREAM_GROUP_RESET         HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 17)
+#define HPI_ISTREAM_HOSTBUFFER_GET_INFO HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 18)
+#define HPI_ISTREAM_WAIT_START          HPI_MAKE_INDEX(HPI_OBJ_ISTREAM, 19)
+#define HPI_ISTREAM_FUNCTION_COUNT              19
+/* MIXER */
+/* NOTE:
+   GET_NODE_INFO, SET_CONNECTION, GET_CONNECTIONS are not currently used */
+#define HPI_MIXER_OPEN                  HPI_MAKE_INDEX(HPI_OBJ_MIXER, 1)
+#define HPI_MIXER_CLOSE                 HPI_MAKE_INDEX(HPI_OBJ_MIXER, 2)
+#define HPI_MIXER_GET_INFO              HPI_MAKE_INDEX(HPI_OBJ_MIXER, 3)
+#define HPI_MIXER_GET_NODE_INFO         HPI_MAKE_INDEX(HPI_OBJ_MIXER, 4)
+#define HPI_MIXER_GET_CONTROL           HPI_MAKE_INDEX(HPI_OBJ_MIXER, 5)
+#define HPI_MIXER_SET_CONNECTION        HPI_MAKE_INDEX(HPI_OBJ_MIXER, 6)
+#define HPI_MIXER_GET_CONNECTIONS       HPI_MAKE_INDEX(HPI_OBJ_MIXER, 7)
+#define HPI_MIXER_GET_CONTROL_BY_INDEX  HPI_MAKE_INDEX(HPI_OBJ_MIXER, 8)
+#define HPI_MIXER_GET_CONTROL_ARRAY_BY_INDEX  HPI_MAKE_INDEX(HPI_OBJ_MIXER, 9)
+#define HPI_MIXER_GET_CONTROL_MULTIPLE_VALUES HPI_MAKE_INDEX(HPI_OBJ_MIXER, 10)
+#define HPI_MIXER_STORE                 HPI_MAKE_INDEX(HPI_OBJ_MIXER, 11)
+#define HPI_MIXER_FUNCTION_COUNT        11
+/* MIXER CONTROLS */
+#define HPI_CONTROL_GET_INFO            HPI_MAKE_INDEX(HPI_OBJ_CONTROL, 1)
+#define HPI_CONTROL_GET_STATE           HPI_MAKE_INDEX(HPI_OBJ_CONTROL, 2)
+#define HPI_CONTROL_SET_STATE           HPI_MAKE_INDEX(HPI_OBJ_CONTROL, 3)
+#define HPI_CONTROL_FUNCTION_COUNT 3
+/* NONVOL MEMORY */
+#define HPI_NVMEMORY_OPEN               HPI_MAKE_INDEX(HPI_OBJ_NVMEMORY, 1)
+#define HPI_NVMEMORY_READ_BYTE          HPI_MAKE_INDEX(HPI_OBJ_NVMEMORY, 2)
+#define HPI_NVMEMORY_WRITE_BYTE         HPI_MAKE_INDEX(HPI_OBJ_NVMEMORY, 3)
+#define HPI_NVMEMORY_FUNCTION_COUNT 3
+/* GPIO */
+#define HPI_GPIO_OPEN                   HPI_MAKE_INDEX(HPI_OBJ_GPIO, 1)
+#define HPI_GPIO_READ_BIT               HPI_MAKE_INDEX(HPI_OBJ_GPIO, 2)
+#define HPI_GPIO_WRITE_BIT              HPI_MAKE_INDEX(HPI_OBJ_GPIO, 3)
+#define HPI_GPIO_READ_ALL               HPI_MAKE_INDEX(HPI_OBJ_GPIO, 4)
+#define HPI_GPIO_WRITE_STATUS           HPI_MAKE_INDEX(HPI_OBJ_GPIO, 5)
+#define HPI_GPIO_FUNCTION_COUNT 5
+/* ASYNC EVENT */
+#define HPI_ASYNCEVENT_OPEN             HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 1)
+#define HPI_ASYNCEVENT_CLOSE            HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 2)
+#define HPI_ASYNCEVENT_WAIT             HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 3)
+#define HPI_ASYNCEVENT_GETCOUNT         HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 4)
+#define HPI_ASYNCEVENT_GET              HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 5)
+#define HPI_ASYNCEVENT_SENDEVENTS       HPI_MAKE_INDEX(HPI_OBJ_ASYNCEVENT, 6)
+#define HPI_ASYNCEVENT_FUNCTION_COUNT 6
+/* WATCH-DOG */
+#define HPI_WATCHDOG_OPEN               HPI_MAKE_INDEX(HPI_OBJ_WATCHDOG, 1)
+#define HPI_WATCHDOG_SET_TIME           HPI_MAKE_INDEX(HPI_OBJ_WATCHDOG, 2)
+#define HPI_WATCHDOG_PING               HPI_MAKE_INDEX(HPI_OBJ_WATCHDOG, 3)
+/* CLOCK */
+#define HPI_CLOCK_OPEN                  HPI_MAKE_INDEX(HPI_OBJ_CLOCK, 1)
+#define HPI_CLOCK_SET_TIME              HPI_MAKE_INDEX(HPI_OBJ_CLOCK, 2)
+#define HPI_CLOCK_GET_TIME              HPI_MAKE_INDEX(HPI_OBJ_CLOCK, 3)
+/* PROFILE */
+#define HPI_PROFILE_OPEN_ALL            HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 1)
+#define HPI_PROFILE_START_ALL           HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 2)
+#define HPI_PROFILE_STOP_ALL            HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 3)
+#define HPI_PROFILE_GET                 HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 4)
+#define HPI_PROFILE_GET_IDLECOUNT       HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 5)
+#define HPI_PROFILE_GET_NAME            HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 6)
+#define HPI_PROFILE_GET_UTILIZATION     HPI_MAKE_INDEX(HPI_OBJ_PROFILE, 7)
+#define HPI_PROFILE_FUNCTION_COUNT 7
+/* ////////////////////////////////////////////////////////////////////// */
+/* PRIVATE ATTRIBUTES */
+
+/* ////////////////////////////////////////////////////////////////////// */
+/* STRUCTURES */
+#ifndef DISABLE_PRAGMA_PACK1
+#pragma pack(push, 1)
+#endif
+
+/** PCI bus resource */
+struct hpi_pci {
+	u32 __iomem *ap_mem_base[HPI_MAX_ADAPTER_MEM_SPACES];
+	struct pci_dev *p_os_data;
+
+#ifndef HPI64BIT		/* keep structure size constant */
+	u32 padding[HPI_MAX_ADAPTER_MEM_SPACES + 1];
+#endif
+	u16 vendor_id;
+	u16 device_id;
+	u16 subsys_vendor_id;
+	u16 subsys_device_id;
+	u16 bus_number;
+	u16 device_number;
+	u32 interrupt;
+};
+
+struct hpi_resource {
+	union {
+		const struct hpi_pci *pci;
+		const char *net_if;
+	} r;
+#ifndef HPI64BIT		/* keep structure size constant */
+	u32 pad_to64;
+#endif
+	u16 bus_type;		/* HPI_BUS_PNPISA, _PCI, _USB etc */
+	u16 padding;
+
+};
+
+/** Format info used inside struct hpi_message
+    Not the same as public API struct hpi_format */
+struct hpi_msg_format {
+	u32 sample_rate;
+				/**< 11025, 32000, 44100 ... */
+	u32 bit_rate;	      /**< for MPEG */
+	u32 attributes;
+				/**< Stereo/JointStereo/Mono */
+	u16 channels;	      /**< 1,2..., (or ancillary mode or idle bit */
+	u16 format; /**< HPI_FORMAT_PCM16, _MPEG etc. see \ref HPI_FORMATS. */
+};
+
+/**  Buffer+format structure.
+	 Must be kept 7 * 32 bits to match public struct hpi_datastruct */
+struct hpi_msg_data {
+	struct hpi_msg_format format;
+	u8 *pb_data;
+#ifndef HPI64BIT
+	u32 padding;
+#endif
+	u32 data_size;
+};
+
+/** struct hpi_datastructure used up to 3.04 driver */
+struct hpi_data_legacy32 {
+	struct hpi_format format;
+	u32 pb_data;
+	u32 data_size;
+};
+
+#ifdef HPI64BIT
+/* Compatibility version of struct hpi_data*/
+struct hpi_data_compat32 {
+	struct hpi_msg_format format;
+	u32 pb_data;
+	u32 padding;
+	u32 data_size;
+};
+#endif
+
+struct hpi_buffer {
+  /** placehoder for backward compatability (see dwBufferSize) */
+	struct hpi_msg_format reserved;
+	u32 command;	/**< HPI_BUFFER_CMD_xxx*/
+	u32 pci_address; /**< PCI physical address of buffer for DSP DMA */
+	u32 buffer_size; /**< must line up with data_size of HPI_DATA*/
+};
+
+/*/////////////////////////////////////////////////////////////////////////// */
+/* This is used for background buffer bus mastering stream buffers.           */
+struct hpi_hostbuffer_status {
+	u32 samples_processed;
+	u32 auxiliary_data_available;
+	u32 stream_state;
+	/* DSP index in to the host bus master buffer. */
+	u32 dSP_index;
+	/* Host index in to the host bus master buffer. */
+	u32 host_index;
+	u32 size_in_bytes;
+};
+
+struct hpi_streamid {
+	u16 object_type;
+		    /**< Type of object, HPI_OBJ_OSTREAM or HPI_OBJ_ISTREAM. */
+	u16 stream_index; /**< outstream or instream index. */
+};
+
+struct hpi_punchinout {
+	u32 punch_in_sample;
+	u32 punch_out_sample;
+};
+
+struct hpi_subsys_msg {
+	struct hpi_resource resource;
+};
+
+struct hpi_subsys_res {
+	u32 version;
+	u32 data;		/* used to return extended version */
+	u16 num_adapters;	/* number of adapters */
+	u16 adapter_index;
+	u16 aw_adapter_list[HPI_MAX_ADAPTERS];
+};
+
+struct hpi_adapter_msg {
+	u32 adapter_mode;	/* adapter mode */
+	u16 assert_id;		/* assert number for "test assert" call
+				   object_index for find object call
+				   query_or_set for hpi_adapter_set_mode_ex() */
+	u16 object_type;	/* for adapter find object call */
+};
+
+union hpi_adapterx_msg {
+	struct hpi_adapter_msg adapter;
+	struct {
+		u32 offset;
+	} query_flash;
+	struct {
+		u32 offset;
+		u32 length;
+		u32 key;
+	} start_flash;
+	struct {
+		u32 checksum;
+		u16 sequence;
+		u16 length;
+		u16 offset; /**< offset from start of msg to data */
+		u16 unused;
+	} program_flash;
+	struct {
+		u16 property;
+		u16 parameter1;
+		u16 parameter2;
+	} property_set;
+	struct {
+		u16 index;
+		u16 what;
+		u16 property_index;
+	} property_enum;
+	struct {
+		u16 index;
+	} module_info;
+	struct {
+		u32 dsp_address;
+		u32 count_bytes;
+	} debug_read;
+};
+
+struct hpi_adapter_res {
+	u32 serial_number;
+	u16 adapter_type;
+	u16 adapter_index;	/* is this needed? also used for dsp_index */
+	u16 num_instreams;
+	u16 num_outstreams;
+	u16 num_mixers;
+	u16 version;
+	u8 sz_adapter_assert[HPI_STRING_LEN];
+};
+
+union hpi_adapterx_res {
+	struct hpi_adapter_res adapter;
+	struct {
+		u32 checksum;
+		u32 length;
+		u32 version;
+	} query_flash;
+	struct {
+		u16 sequence;
+	} program_flash;
+	struct {
+		u16 parameter1;
+		u16 parameter2;
+	} property_get;
+};
+
+struct hpi_stream_msg {
+	union {
+		struct hpi_msg_data data;
+		struct hpi_data_legacy32 data32;
+		u16 velocity;
+		struct hpi_punchinout pio;
+		u32 time_scale;
+		struct hpi_buffer buffer;
+		struct hpi_streamid stream;
+	} u;
+};
+
+struct hpi_stream_res {
+	union {
+		struct {
+			/* size of hardware buffer */
+			u32 buffer_size;
+			/* OutStream - data to play,
+			   InStream - data recorded */
+			u32 data_available;
+			/* OutStream - samples played,
+			   InStream - samples recorded */
+			u32 samples_transferred;
+			/* Adapter - OutStream - data to play,
+			   InStream - data recorded */
+			u32 auxiliary_data_available;
+			u16 state;	/* HPI_STATE_PLAYING, _STATE_STOPPED */
+			u16 padding;
+		} stream_info;
+		struct {
+			u32 buffer_size;
+			u32 data_available;
+			u32 samples_transfered;
+			u16 state;
+			u16 outstream_index;
+			u16 instream_index;
+			u16 padding;
+			u32 auxiliary_data_available;
+		} legacy_stream_info;
+		struct {
+			/* bitmap of grouped OutStreams */
+			u32 outstream_group_map;
+			/* bitmap of grouped InStreams */
+			u32 instream_group_map;
+		} group_info;
+		struct {
+			/* pointer to the buffer */
+			u8 *p_buffer;
+			/* pointer to the hostbuffer status */
+			struct hpi_hostbuffer_status *p_status;
+		} hostbuffer_info;
+	} u;
+};
+
+struct hpi_mixer_msg {
+	u16 control_index;
+	u16 control_type;	/* = HPI_CONTROL_METER _VOLUME etc */
+	u16 padding1;		/* maintain alignment of subsequent fields */
+	u16 node_type1;		/* = HPI_SOURCENODE_LINEIN etc */
+	u16 node_index1;	/* = 0..N */
+	u16 node_type2;
+	u16 node_index2;
+	u16 padding2;		/* round to 4 bytes */
+};
+
+struct hpi_mixer_res {
+	u16 src_node_type;	/* = HPI_SOURCENODE_LINEIN etc */
+	u16 src_node_index;	/* = 0..N */
+	u16 dst_node_type;
+	u16 dst_node_index;
+	/* Also controlType for MixerGetControlByIndex */
+	u16 control_index;
+	/* may indicate which DSP the control is located on */
+	u16 dsp_index;
+};
+
+union hpi_mixerx_msg {
+	struct {
+		u16 starting_index;
+		u16 flags;
+		u32 length_in_bytes;	/* length in bytes of p_data */
+		u32 p_data;	/* pointer to a data array */
+	} gcabi;
+	struct {
+		u16 command;
+		u16 index;
+	} store;		/* for HPI_MIXER_STORE message */
+};
+
+union hpi_mixerx_res {
+	struct {
+		u32 bytes_returned;	/* size of items returned */
+		u32 p_data;	/* pointer to data array */
+		u16 more_to_do;	/* indicates if there is more to do */
+	} gcabi;
+};
+
+struct hpi_control_msg {
+	u16 attribute;		/* control attribute or property */
+	u16 saved_index;
+	u32 param1;		/* generic parameter 1 */
+	u32 param2;		/* generic parameter 2 */
+	short an_log_value[HPI_MAX_CHANNELS];
+};
+
+struct hpi_control_union_msg {
+	u16 attribute;		/* control attribute or property */
+	u16 saved_index;	/* only used in ctrl save/restore */
+	union {
+		struct {
+			u32 param1;	/* generic parameter 1 */
+			u32 param2;	/* generic parameter 2 */
+			short an_log_value[HPI_MAX_CHANNELS];
+		} old;
+		union {
+			u32 frequency;
+			u32 gain;
+			u32 band;
+			u32 deemphasis;
+			u32 program;
+			struct {
+				u32 mode;
+				u32 value;
+			} mode;
+			u32 blend;
+		} tuner;
+	} u;
+};
+
+struct hpi_control_res {
+	/* Could make union. dwParam, anLogValue never used in same response */
+	u32 param1;
+	u32 param2;
+	short an_log_value[HPI_MAX_CHANNELS];
+};
+
+union hpi_control_union_res {
+	struct {
+		u32 param1;
+		u32 param2;
+		short an_log_value[HPI_MAX_CHANNELS];
+	} old;
+	union {
+		u32 band;
+		u32 frequency;
+		u32 gain;
+		u32 level;
+		u32 deemphasis;
+		struct {
+			u32 data[2];
+			u32 bLER;
+		} rds;
+	} tuner;
+	struct {
+		char sz_data[8];
+		u32 remaining_chars;
+	} chars8;
+	char c_data12[12];
+};
+
+/* HPI_CONTROLX_STRUCTURES */
+
+/* Message */
+
+/** Used for all HMI variables where max length <= 8 bytes
+*/
+struct hpi_controlx_msg_cobranet_data {
+	u32 hmi_address;
+	u32 byte_count;
+	u32 data[2];
+};
+
+/** Used for string data, and for packet bridge
+*/
+struct hpi_controlx_msg_cobranet_bigdata {
+	u32 hmi_address;
+	u32 byte_count;
+	u8 *pb_data;
+#ifndef HPI64BIT
+	u32 padding;
+#endif
+};
+
+/** Used for PADS control reading of string fields.
+*/
+struct hpi_controlx_msg_pad_data {
+	u32 field;
+	u32 byte_count;
+	u8 *pb_data;
+#ifndef HPI64BIT
+	u32 padding;
+#endif
+};
+
+/** Used for generic data
+*/
+
+struct hpi_controlx_msg_generic {
+	u32 param1;
+	u32 param2;
+};
+
+struct hpi_controlx_msg {
+	u16 attribute;		/* control attribute or property */
+	u16 saved_index;
+	union {
+		struct hpi_controlx_msg_cobranet_data cobranet_data;
+		struct hpi_controlx_msg_cobranet_bigdata cobranet_bigdata;
+		struct hpi_controlx_msg_generic generic;
+		struct hpi_controlx_msg_pad_data pad_data;
+		/*struct param_value universal_value; */
+		/* nothing extra to send for status read */
+	} u;
+};
+
+/* Response */
+/**
+*/
+struct hpi_controlx_res_cobranet_data {
+	u32 byte_count;
+	u32 data[2];
+};
+
+struct hpi_controlx_res_cobranet_bigdata {
+	u32 byte_count;
+};
+
+struct hpi_controlx_res_cobranet_status {
+	u32 status;
+	u32 readable_size;
+	u32 writeable_size;
+};
+
+struct hpi_controlx_res_generic {
+	u32 param1;
+	u32 param2;
+};
+
+struct hpi_controlx_res {
+	union {
+		struct hpi_controlx_res_cobranet_bigdata cobranet_bigdata;
+		struct hpi_controlx_res_cobranet_data cobranet_data;
+		struct hpi_controlx_res_cobranet_status cobranet_status;
+		struct hpi_controlx_res_generic generic;
+		/*struct param_info universal_info; */
+		/*struct param_value universal_value; */
+	} u;
+};
+
+struct hpi_nvmemory_msg {
+	u16 address;
+	u16 data;
+};
+
+struct hpi_nvmemory_res {
+	u16 size_in_bytes;
+	u16 data;
+};
+
+struct hpi_gpio_msg {
+	u16 bit_index;
+	u16 bit_data;
+};
+
+struct hpi_gpio_res {
+	u16 number_input_bits;
+	u16 number_output_bits;
+	u16 bit_data[4];
+};
+
+struct hpi_async_msg {
+	u32 events;
+	u16 maximum_events;
+	u16 padding;
+};
+
+struct hpi_async_res {
+	union {
+		struct {
+			u16 count;
+		} count;
+		struct {
+			u32 events;
+			u16 number_returned;
+			u16 padding;
+		} get;
+		struct hpi_async_event event;
+	} u;
+};
+
+struct hpi_watchdog_msg {
+	u32 time_ms;
+};
+
+struct hpi_watchdog_res {
+	u32 time_ms;
+};
+
+struct hpi_clock_msg {
+	u16 hours;
+	u16 minutes;
+	u16 seconds;
+	u16 milli_seconds;
+};
+
+struct hpi_clock_res {
+	u16 size_in_bytes;
+	u16 hours;
+	u16 minutes;
+	u16 seconds;
+	u16 milli_seconds;
+	u16 padding;
+};
+
+struct hpi_profile_msg {
+	u16 bin_index;
+	u16 padding;
+};
+
+struct hpi_profile_res_open {
+	u16 max_profiles;
+};
+
+struct hpi_profile_res_time {
+	u32 micro_seconds;
+	u32 call_count;
+	u32 max_micro_seconds;
+	u32 min_micro_seconds;
+	u16 seconds;
+};
+
+struct hpi_profile_res_name {
+	u8 sz_name[32];
+};
+
+struct hpi_profile_res {
+	union {
+		struct hpi_profile_res_open o;
+		struct hpi_profile_res_time t;
+		struct hpi_profile_res_name n;
+	} u;
+};
+
+struct hpi_message_header {
+	u16 size;		/* total size in bytes */
+	u8 type;		/* HPI_TYPE_MESSAGE  */
+	u8 version;		/* message version */
+	u16 object;		/* HPI_OBJ_* */
+	u16 function;		/* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */
+	u16 adapter_index;	/* the adapter index */
+	u16 obj_index;		/* */
+};
+
+struct hpi_message {
+	/* following fields must match HPI_MESSAGE_HEADER */
+	u16 size;		/* total size in bytes */
+	u8 type;		/* HPI_TYPE_MESSAGE  */
+	u8 version;		/* message version */
+	u16 object;		/* HPI_OBJ_* */
+	u16 function;		/* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */
+	u16 adapter_index;	/* the adapter index */
+	u16 obj_index;		/*  */
+	union {
+		struct hpi_subsys_msg s;
+		struct hpi_adapter_msg a;
+		union hpi_adapterx_msg ax;
+		struct hpi_stream_msg d;
+		struct hpi_mixer_msg m;
+		union hpi_mixerx_msg mx;	/* extended mixer; */
+		struct hpi_control_msg c;	/* mixer control; */
+		/* identical to struct hpi_control_msg,
+		   but field naming is improved */
+		struct hpi_control_union_msg cu;
+		struct hpi_controlx_msg cx;	/* extended mixer control; */
+		struct hpi_nvmemory_msg n;
+		struct hpi_gpio_msg l;	/* digital i/o */
+		struct hpi_watchdog_msg w;
+		struct hpi_clock_msg t;	/* dsp time */
+		struct hpi_profile_msg p;
+		struct hpi_async_msg as;
+		char fixed_size[32];
+	} u;
+};
+
+#define HPI_MESSAGE_SIZE_BY_OBJECT { \
+	sizeof(struct hpi_message_header) ,   /* default, no object type 0 */ \
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_subsys_msg),\
+	sizeof(struct hpi_message_header) + sizeof(union hpi_adapterx_msg),\
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_stream_msg),\
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_stream_msg),\
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_mixer_msg),\
+	sizeof(struct hpi_message_header) ,   /* no node message */ \
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_control_msg),\
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_nvmemory_msg),\
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_gpio_msg),\
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_watchdog_msg),\
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_clock_msg),\
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_profile_msg),\
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_controlx_msg),\
+	sizeof(struct hpi_message_header) + sizeof(struct hpi_async_msg) \
+}
+
+struct hpi_response_header {
+	u16 size;
+	u8 type;		/* HPI_TYPE_RESPONSE  */
+	u8 version;		/* response version */
+	u16 object;		/* HPI_OBJ_* */
+	u16 function;		/* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */
+	u16 error;		/* HPI_ERROR_xxx */
+	u16 specific_error;	/* adapter specific error */
+};
+
+struct hpi_response {
+/* following fields must match HPI_RESPONSE_HEADER */
+	u16 size;
+	u8 type;		/* HPI_TYPE_RESPONSE  */
+	u8 version;		/* response version */
+	u16 object;		/* HPI_OBJ_* */
+	u16 function;		/* HPI_SUBSYS_xxx, HPI_ADAPTER_xxx */
+	u16 error;		/* HPI_ERROR_xxx */
+	u16 specific_error;	/* adapter specific error */
+	union {
+		struct hpi_subsys_res s;
+		struct hpi_adapter_res a;
+		union hpi_adapterx_res ax;
+		struct hpi_stream_res d;
+		struct hpi_mixer_res m;
+		union hpi_mixerx_res mx;	/* extended mixer; */
+		struct hpi_control_res c;	/* mixer control; */
+		/* identical to hpi_control_res, but field naming is improved */
+		union hpi_control_union_res cu;
+		struct hpi_controlx_res cx;	/* extended mixer control; */
+		struct hpi_nvmemory_res n;
+		struct hpi_gpio_res l;	/* digital i/o */
+		struct hpi_watchdog_res w;
+		struct hpi_clock_res t;	/* dsp time */
+		struct hpi_profile_res p;
+		struct hpi_async_res as;
+		u8 bytes[52];
+	} u;
+};
+
+#define HPI_RESPONSE_SIZE_BY_OBJECT { \
+	sizeof(struct hpi_response_header) ,/* default, no object type 0 */ \
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_subsys_res),\
+	sizeof(struct hpi_response_header) + sizeof(union  hpi_adapterx_res),\
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_stream_res),\
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_stream_res),\
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_mixer_res),\
+	sizeof(struct hpi_response_header) , /* no node response */ \
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_control_res),\
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_nvmemory_res),\
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_gpio_res),\
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_watchdog_res),\
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_clock_res),\
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_profile_res),\
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_controlx_res),\
+	sizeof(struct hpi_response_header) + sizeof(struct hpi_async_res) \
+}
+
+/*********************** version 1 message/response *****************************/
+#define HPINET_ETHERNET_DATA_SIZE (1500)
+#define HPINET_IP_HDR_SIZE (20)
+#define HPINET_IP_DATA_SIZE (HPINET_ETHERNET_DATA_SIZE - HPINET_IP_HDR_SIZE)
+#define HPINET_UDP_HDR_SIZE (8)
+#define HPINET_UDP_DATA_SIZE (HPINET_IP_DATA_SIZE - HPINET_UDP_HDR_SIZE)
+#define HPINET_ASI_HDR_SIZE (2)
+#define HPINET_ASI_DATA_SIZE (HPINET_UDP_DATA_SIZE - HPINET_ASI_HDR_SIZE)
+
+#define HPI_MAX_PAYLOAD_SIZE (HPINET_ASI_DATA_SIZE - 2)
+
+/* New style message/response, but still V0 compatible */
+struct hpi_msg_adapter_get_info {
+	struct hpi_message_header h;
+};
+
+struct hpi_res_adapter_get_info {
+	struct hpi_response_header h;	/*v0 */
+	struct hpi_adapter_res p;
+};
+
+/* padding is so these are same size as v0 hpi_message */
+struct hpi_msg_adapter_query_flash {
+	struct hpi_message_header h;
+	u32 offset;
+	u8 pad_to_version0_size[sizeof(struct hpi_message) -	/* V0 res */
+		sizeof(struct hpi_message_header) - 1 * sizeof(u32)];
+};
+
+/* padding is so these are same size as v0 hpi_response */
+struct hpi_res_adapter_query_flash {
+	struct hpi_response_header h;
+	u32 checksum;
+	u32 length;
+	u32 version;
+	u8 pad_to_version0_size[sizeof(struct hpi_response) -	/* V0 res */
+		sizeof(struct hpi_response_header) - 3 * sizeof(u32)];
+};
+
+struct hpi_msg_adapter_start_flash {
+	struct hpi_message_header h;
+	u32 offset;
+	u32 length;
+	u32 key;
+	u8 pad_to_version0_size[sizeof(struct hpi_message) -	/* V0 res */
+		sizeof(struct hpi_message_header) - 3 * sizeof(u32)];
+};
+
+struct hpi_res_adapter_start_flash {
+	struct hpi_response_header h;
+	u8 pad_to_version0_size[sizeof(struct hpi_response) -	/* V0 res */
+		sizeof(struct hpi_response_header)];
+};
+
+struct hpi_msg_adapter_program_flash_payload {
+	u32 checksum;
+	u16 sequence;
+	u16 length;
+	u16 offset; /**< offset from start of msg to data */
+	u16 unused;
+	/* ensure sizeof(header + payload) == sizeof(hpi_message_V0)
+	   because old firmware expects data after message of this size */
+	u8 pad_to_version0_size[sizeof(struct hpi_message) -	/* V0 message */
+		sizeof(struct hpi_message_header) - sizeof(u32) -
+		4 * sizeof(u16)];
+};
+
+struct hpi_msg_adapter_program_flash {
+	struct hpi_message_header h;
+	struct hpi_msg_adapter_program_flash_payload p;
+	u32 data[256];
+};
+
+struct hpi_res_adapter_program_flash {
+	struct hpi_response_header h;
+	u16 sequence;
+	u8 pad_to_version0_size[sizeof(struct hpi_response) -	/* V0 res */
+		sizeof(struct hpi_response_header) - sizeof(u16)];
+};
+
+#if 1
+#define hpi_message_header_v1 hpi_message_header
+#define hpi_response_header_v1 hpi_response_header
+#else
+/* V1 headers in Addition to v0 headers */
+struct hpi_message_header_v1 {
+	struct hpi_message_header h0;
+/* struct {
+} h1; */
+};
+
+struct hpi_response_header_v1 {
+	struct hpi_response_header h0;
+	struct {
+		u16 adapter_index;	/* the adapter index */
+		u16 obj_index;	/* object index */
+	} h1;
+};
+#endif
+
+/* STRV HPI Packet */
+struct hpi_msg_strv {
+	struct hpi_message_header h;
+	struct hpi_entity strv;
+};
+
+struct hpi_res_strv {
+	struct hpi_response_header h;
+	struct hpi_entity strv;
+};
+#define MIN_STRV_PACKET_SIZE sizeof(struct hpi_res_strv)
+
+struct hpi_msg_payload_v0 {
+	struct hpi_message_header h;
+	union {
+		struct hpi_subsys_msg s;
+		struct hpi_adapter_msg a;
+		union hpi_adapterx_msg ax;
+		struct hpi_stream_msg d;
+		struct hpi_mixer_msg m;
+		union hpi_mixerx_msg mx;
+		struct hpi_control_msg c;
+		struct hpi_control_union_msg cu;
+		struct hpi_controlx_msg cx;
+		struct hpi_nvmemory_msg n;
+		struct hpi_gpio_msg l;
+		struct hpi_watchdog_msg w;
+		struct hpi_clock_msg t;
+		struct hpi_profile_msg p;
+		struct hpi_async_msg as;
+	} u;
+};
+
+struct hpi_res_payload_v0 {
+	struct hpi_response_header h;
+	union {
+		struct hpi_subsys_res s;
+		struct hpi_adapter_res a;
+		union hpi_adapterx_res ax;
+		struct hpi_stream_res d;
+		struct hpi_mixer_res m;
+		union hpi_mixerx_res mx;
+		struct hpi_control_res c;
+		union hpi_control_union_res cu;
+		struct hpi_controlx_res cx;
+		struct hpi_nvmemory_res n;
+		struct hpi_gpio_res l;
+		struct hpi_watchdog_res w;
+		struct hpi_clock_res t;
+		struct hpi_profile_res p;
+		struct hpi_async_res as;
+	} u;
+};
+
+union hpi_message_buffer_v1 {
+	struct hpi_message m0;	/* version 0 */
+	struct hpi_message_header_v1 h;
+	unsigned char buf[HPI_MAX_PAYLOAD_SIZE];
+};
+
+union hpi_response_buffer_v1 {
+	struct hpi_response r0;	/* version 0 */
+	struct hpi_response_header_v1 h;
+	unsigned char buf[HPI_MAX_PAYLOAD_SIZE];
+};
+
+compile_time_assert((sizeof(union hpi_message_buffer_v1) <=
+		HPI_MAX_PAYLOAD_SIZE), message_buffer_ok);
+compile_time_assert((sizeof(union hpi_response_buffer_v1) <=
+		HPI_MAX_PAYLOAD_SIZE), response_buffer_ok);
+
+/*////////////////////////////////////////////////////////////////////////// */
+/* declarations for compact control calls  */
+struct hpi_control_defn {
+	u8 type;
+	u8 channels;
+	u8 src_node_type;
+	u8 src_node_index;
+	u8 dest_node_type;
+	u8 dest_node_index;
+};
+
+/*////////////////////////////////////////////////////////////////////////// */
+/* declarations for control caching (internal to HPI<->DSP interaction)      */
+
+/** A compact representation of (part of) a controls state.
+Used for efficient transfer of the control state
+between DSP and host or across a network
+*/
+struct hpi_control_cache_info {
+	/** one of HPI_CONTROL_* */
+	u8 control_type;
+	/** The total size of cached information in 32-bit words. */
+	u8 size_in32bit_words;
+	/** The original index of the control on the DSP */
+	u16 control_index;
+};
+
+struct hpi_control_cache_single {
+	struct hpi_control_cache_info i;
+	union {
+		struct {	/* volume */
+			short an_log[2];
+		} v;
+		struct {	/* peak meter */
+			short an_log_peak[2];
+			short an_logRMS[2];
+		} p;
+		struct {	/* channel mode */
+			u16 mode;
+		} m;
+		struct {	/* multiplexer */
+			u16 source_node_type;
+			u16 source_node_index;
+		} x;
+		struct {	/* level/trim */
+			short an_log[2];
+		} l;
+		struct {	/* tuner - partial caching.
+				   some attributes go to the DSP. */
+			u32 freq_ink_hz;
+			u16 band;
+			u16 level;
+		} t;
+		struct {	/* AESEBU rx status */
+			u32 error_status;
+			u32 source;
+		} aes3rx;
+		struct {	/* AESEBU tx */
+			u32 format;
+		} aes3tx;
+		struct {	/* tone detector */
+			u16 state;
+		} tone;
+		struct {	/* silence detector */
+			u32 state;
+			u32 count;
+		} silence;
+		struct {	/* sample clock */
+			u16 source;
+			u16 source_index;
+			u32 sample_rate;
+		} clk;
+		struct {	/* microphone control */
+			u16 state;
+		} phantom_power;
+		struct {	/* generic control */
+			u32 dw1;
+			u32 dw2;
+		} g;
+	} u;
+};
+
+struct hpi_control_cache_pad {
+	struct hpi_control_cache_info i;
+	u32 field_valid_flags;
+	u8 c_channel[8];
+	u8 c_artist[40];
+	u8 c_title[40];
+	u8 c_comment[200];
+	u32 pTY;
+	u32 pI;
+	u32 traffic_supported;
+	u32 traffic_anouncement;
+};
+
+/*/////////////////////////////////////////////////////////////////////////// */
+/* declarations for 2^N sized FIFO buffer (internal to HPI<->DSP interaction) */
+struct hpi_fifo_buffer {
+	u32 size;
+	u32 dSP_index;
+	u32 host_index;
+};
+
+#ifndef DISABLE_PRAGMA_PACK1
+#pragma pack(pop)
+#endif
+
+/* skip host side function declarations for DSP
+   compile and documentation extraction */
+
+char hpi_handle_object(const u32 handle);
+
+void hpi_handle_to_indexes(const u32 handle, u16 *pw_adapter_index,
+	u16 *pw_object_index);
+
+u32 hpi_indexes_to_handle(const char c_object, const u16 adapter_index,
+	const u16 object_index);
+
+/*////////////////////////////////////////////////////////////////////////// */
+
+/* main HPI entry point */
+hpi_handler_func hpi_send_recv;
+
+/* UDP message */
+void hpi_send_recvUDP(struct hpi_message *phm, struct hpi_response *phr,
+	const unsigned int timeout);
+
+/* used in PnP OS/driver */
+u16 hpi_subsys_create_adapter(const struct hpi_hsubsys *ph_subsys,
+	const struct hpi_resource *p_resource, u16 *pw_adapter_index);
+
+u16 hpi_subsys_delete_adapter(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index);
+
+u16 hpi_outstream_host_buffer_get_info(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u8 **pp_buffer,
+	struct hpi_hostbuffer_status **pp_status);
+
+u16 hpi_instream_host_buffer_get_info(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u8 **pp_buffer,
+	struct hpi_hostbuffer_status **pp_status);
+
+u16 hpi_adapter_restart(u16 adapter_index);
+
+/*
+The following 3 functions were last declared in header files for
+driver 3.10. HPI_ControlQuery() used to be the recommended way
+of getting a volume range. Declared here for binary asihpi32.dll
+compatibility.
+*/
+
+void hpi_format_to_msg(struct hpi_msg_format *pMF,
+	const struct hpi_format *pF);
+void hpi_stream_response_to_legacy(struct hpi_stream_res *pSR);
+
+/*////////////////////////////////////////////////////////////////////////// */
+/* declarations for individual HPI entry points */
+hpi_handler_func HPI_1000;
+hpi_handler_func HPI_6000;
+hpi_handler_func HPI_6205;
+hpi_handler_func HPI_COMMON;
+
+#endif				/* _HPI_INTERNAL_H_ */
diff --git a/linux/sound/pci/asihpi/hpicmn.c b/linux/sound/pci/asihpi/hpicmn.c
new file mode 100644
index 0000000..d67f4d3
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpicmn.c
@@ -0,0 +1,643 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+\file hpicmn.c
+
+ Common functions used by hpixxxx.c modules
+
+(C) Copyright AudioScience Inc. 1998-2003
+*******************************************************************************/
+#define SOURCEFILE_NAME "hpicmn.c"
+
+#include "hpi_internal.h"
+#include "hpidebug.h"
+#include "hpicmn.h"
+
+struct hpi_adapters_list {
+	struct hpios_spinlock list_lock;
+	struct hpi_adapter_obj adapter[HPI_MAX_ADAPTERS];
+	u16 gw_num_adapters;
+};
+
+static struct hpi_adapters_list adapters;
+
+/**
+* Given an HPI Message that was sent out and a response that was received,
+* validate that the response has the correct fields filled in,
+* i.e ObjectType, Function etc
+**/
+u16 hpi_validate_response(struct hpi_message *phm, struct hpi_response *phr)
+{
+	u16 error = 0;
+
+	if ((phr->type != HPI_TYPE_RESPONSE)
+		|| (phr->object != phm->object)
+		|| (phr->function != phm->function))
+		error = HPI_ERROR_INVALID_RESPONSE;
+
+	return error;
+}
+
+u16 hpi_add_adapter(struct hpi_adapter_obj *pao)
+{
+	u16 retval = 0;
+	/*HPI_ASSERT(pao->wAdapterType); */
+
+	hpios_alistlock_lock(&adapters);
+
+	if (pao->index >= HPI_MAX_ADAPTERS) {
+		retval = HPI_ERROR_BAD_ADAPTER_NUMBER;
+		goto unlock;
+	}
+
+	if (adapters.adapter[pao->index].adapter_type) {
+		{
+			retval = HPI_DUPLICATE_ADAPTER_NUMBER;
+			goto unlock;
+		}
+	}
+	adapters.adapter[pao->index] = *pao;
+	hpios_dsplock_init(&adapters.adapter[pao->index]);
+	adapters.gw_num_adapters++;
+
+unlock:
+	hpios_alistlock_un_lock(&adapters);
+	return retval;
+}
+
+void hpi_delete_adapter(struct hpi_adapter_obj *pao)
+{
+	memset(pao, 0, sizeof(struct hpi_adapter_obj));
+
+	hpios_alistlock_lock(&adapters);
+	adapters.gw_num_adapters--;	/* dec the number of adapters */
+	hpios_alistlock_un_lock(&adapters);
+}
+
+/**
+* FindAdapter returns a pointer to the struct hpi_adapter_obj with
+* index wAdapterIndex in an HPI_ADAPTERS_LIST structure.
+*
+*/
+struct hpi_adapter_obj *hpi_find_adapter(u16 adapter_index)
+{
+	struct hpi_adapter_obj *pao = NULL;
+
+	if (adapter_index >= HPI_MAX_ADAPTERS) {
+		HPI_DEBUG_LOG(VERBOSE, "find_adapter invalid index %d ",
+			adapter_index);
+		return NULL;
+	}
+
+	pao = &adapters.adapter[adapter_index];
+	if (pao->adapter_type != 0) {
+		/*
+		   HPI_DEBUG_LOG(VERBOSE, "Found adapter index %d\n",
+		   wAdapterIndex);
+		 */
+		return pao;
+	} else {
+		/*
+		   HPI_DEBUG_LOG(VERBOSE, "No adapter index %d\n",
+		   wAdapterIndex);
+		 */
+		return NULL;
+	}
+}
+
+/**
+*
+* wipe an HPI_ADAPTERS_LIST structure.
+*
+**/
+static void wipe_adapter_list(void
+	)
+{
+	memset(&adapters, 0, sizeof(adapters));
+}
+
+/**
+* SubSysGetAdapters fills awAdapterList in an struct hpi_response structure
+* with all adapters in the given HPI_ADAPTERS_LIST.
+*
+*/
+static void subsys_get_adapters(struct hpi_response *phr)
+{
+	/* fill in the response adapter array with the position */
+	/* identified by the adapter number/index of the adapters in */
+	/* this HPI */
+	/* i.e. if we have an A120 with it's jumper set to */
+	/* Adapter Number 2 then put an Adapter type A120 in the */
+	/* array in position 1 */
+	/* NOTE: AdapterNumber is 1..N, Index is 0..N-1 */
+
+	/* input:  NONE */
+	/* output: wNumAdapters */
+	/*                 awAdapter[] */
+	/* */
+
+	short i;
+	struct hpi_adapter_obj *pao = NULL;
+
+	HPI_DEBUG_LOG(VERBOSE, "subsys_get_adapters\n");
+
+	/* for each adapter, place it's type in the position of the array */
+	/* corresponding to it's adapter number */
+	for (i = 0; i < adapters.gw_num_adapters; i++) {
+		pao = &adapters.adapter[i];
+		if (phr->u.s.aw_adapter_list[pao->index] != 0) {
+			phr->error = HPI_DUPLICATE_ADAPTER_NUMBER;
+			phr->specific_error = pao->index;
+			return;
+		}
+		phr->u.s.aw_adapter_list[pao->index] = pao->adapter_type;
+	}
+
+	phr->u.s.num_adapters = adapters.gw_num_adapters;
+	phr->error = 0;	/* the function completed OK; */
+}
+
+static unsigned int control_cache_alloc_check(struct hpi_control_cache *pC)
+{
+	unsigned int i;
+	int cached = 0;
+	if (!pC)
+		return 0;
+	if ((!pC->init) && (pC->p_cache != NULL) && (pC->control_count)
+		&& (pC->cache_size_in_bytes)
+		) {
+		u32 *p_master_cache;
+		pC->init = 1;
+
+		p_master_cache = (u32 *)pC->p_cache;
+		HPI_DEBUG_LOG(VERBOSE, "check %d controls\n",
+			pC->control_count);
+		for (i = 0; i < pC->control_count; i++) {
+			struct hpi_control_cache_info *info =
+				(struct hpi_control_cache_info *)
+				p_master_cache;
+
+			if (info->control_type) {
+				pC->p_info[i] = info;
+				cached++;
+			} else
+				pC->p_info[i] = NULL;
+
+			if (info->size_in32bit_words)
+				p_master_cache += info->size_in32bit_words;
+			else
+				p_master_cache +=
+					sizeof(struct
+					hpi_control_cache_single) /
+					sizeof(u32);
+
+			HPI_DEBUG_LOG(VERBOSE,
+				"cached %d, pinfo %p index %d type %d\n",
+				cached, pC->p_info[i], info->control_index,
+				info->control_type);
+		}
+		/*
+		   We didn't find anything to cache, so try again later !
+		 */
+		if (!cached)
+			pC->init = 0;
+	}
+	return pC->init;
+}
+
+/** Find a control.
+*/
+static short find_control(struct hpi_message *phm,
+	struct hpi_control_cache *p_cache, struct hpi_control_cache_info **pI,
+	u16 *pw_control_index)
+{
+	*pw_control_index = phm->obj_index;
+
+	if (!control_cache_alloc_check(p_cache)) {
+		HPI_DEBUG_LOG(VERBOSE,
+			"control_cache_alloc_check() failed. adap%d ci%d\n",
+			phm->adapter_index, *pw_control_index);
+		return 0;
+	}
+
+	*pI = p_cache->p_info[*pw_control_index];
+	if (!*pI) {
+		HPI_DEBUG_LOG(VERBOSE, "uncached adap %d, control %d\n",
+			phm->adapter_index, *pw_control_index);
+		return 0;
+	} else {
+		HPI_DEBUG_LOG(VERBOSE, "find_control() type %d\n",
+			(*pI)->control_type);
+	}
+	return 1;
+}
+
+/** Used by the kernel driver to figure out if a buffer needs mapping.
+ */
+short hpi_check_buffer_mapping(struct hpi_control_cache *p_cache,
+	struct hpi_message *phm, void **p, unsigned int *pN)
+{
+	*pN = 0;
+	*p = NULL;
+	if ((phm->function == HPI_CONTROL_GET_STATE)
+		&& (phm->object == HPI_OBJ_CONTROLEX)
+		) {
+		u16 control_index;
+		struct hpi_control_cache_info *pI;
+
+		if (!find_control(phm, p_cache, &pI, &control_index))
+			return 0;
+	}
+	return 0;
+}
+
+/* allow unified treatment of several string fields within struct */
+#define HPICMN_PAD_OFS_AND_SIZE(m)  {\
+	offsetof(struct hpi_control_cache_pad, m), \
+	sizeof(((struct hpi_control_cache_pad *)(NULL))->m) }
+
+struct pad_ofs_size {
+	unsigned int offset;
+	unsigned int field_size;
+};
+
+static struct pad_ofs_size pad_desc[] = {
+	HPICMN_PAD_OFS_AND_SIZE(c_channel),	/* HPI_PAD_CHANNEL_NAME */
+	HPICMN_PAD_OFS_AND_SIZE(c_artist),	/* HPI_PAD_ARTIST */
+	HPICMN_PAD_OFS_AND_SIZE(c_title),	/* HPI_PAD_TITLE */
+	HPICMN_PAD_OFS_AND_SIZE(c_comment),	/* HPI_PAD_COMMENT */
+};
+
+/** CheckControlCache checks the cache and fills the struct hpi_response
+ * accordingly. It returns one if a cache hit occurred, zero otherwise.
+ */
+short hpi_check_control_cache(struct hpi_control_cache *p_cache,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	short found = 1;
+	u16 control_index;
+	struct hpi_control_cache_info *pI;
+	struct hpi_control_cache_single *pC;
+	struct hpi_control_cache_pad *p_pad;
+
+	if (!find_control(phm, p_cache, &pI, &control_index))
+		return 0;
+
+	phr->error = 0;
+
+	/* pC is the default cached control strucure. May be cast to
+	   something else in the following switch statement.
+	 */
+	pC = (struct hpi_control_cache_single *)pI;
+	p_pad = (struct hpi_control_cache_pad *)pI;
+
+	switch (pI->control_type) {
+
+	case HPI_CONTROL_METER:
+		if (phm->u.c.attribute == HPI_METER_PEAK) {
+			phr->u.c.an_log_value[0] = pC->u.p.an_log_peak[0];
+			phr->u.c.an_log_value[1] = pC->u.p.an_log_peak[1];
+		} else if (phm->u.c.attribute == HPI_METER_RMS) {
+			phr->u.c.an_log_value[0] = pC->u.p.an_logRMS[0];
+			phr->u.c.an_log_value[1] = pC->u.p.an_logRMS[1];
+		} else
+			found = 0;
+		break;
+	case HPI_CONTROL_VOLUME:
+		if (phm->u.c.attribute == HPI_VOLUME_GAIN) {
+			phr->u.c.an_log_value[0] = pC->u.v.an_log[0];
+			phr->u.c.an_log_value[1] = pC->u.v.an_log[1];
+		} else
+			found = 0;
+		break;
+	case HPI_CONTROL_MULTIPLEXER:
+		if (phm->u.c.attribute == HPI_MULTIPLEXER_SOURCE) {
+			phr->u.c.param1 = pC->u.x.source_node_type;
+			phr->u.c.param2 = pC->u.x.source_node_index;
+		} else {
+			found = 0;
+		}
+		break;
+	case HPI_CONTROL_CHANNEL_MODE:
+		if (phm->u.c.attribute == HPI_CHANNEL_MODE_MODE)
+			phr->u.c.param1 = pC->u.m.mode;
+		else
+			found = 0;
+		break;
+	case HPI_CONTROL_LEVEL:
+		if (phm->u.c.attribute == HPI_LEVEL_GAIN) {
+			phr->u.c.an_log_value[0] = pC->u.l.an_log[0];
+			phr->u.c.an_log_value[1] = pC->u.l.an_log[1];
+		} else
+			found = 0;
+		break;
+	case HPI_CONTROL_TUNER:
+		if (phm->u.c.attribute == HPI_TUNER_FREQ)
+			phr->u.c.param1 = pC->u.t.freq_ink_hz;
+		else if (phm->u.c.attribute == HPI_TUNER_BAND)
+			phr->u.c.param1 = pC->u.t.band;
+		else if ((phm->u.c.attribute == HPI_TUNER_LEVEL)
+			&& (phm->u.c.param1 == HPI_TUNER_LEVEL_AVERAGE))
+			if (pC->u.t.level == HPI_ERROR_ILLEGAL_CACHE_VALUE) {
+				phr->u.c.param1 = 0;
+				phr->error =
+					HPI_ERROR_INVALID_CONTROL_ATTRIBUTE;
+			} else
+				phr->u.c.param1 = pC->u.t.level;
+		else
+			found = 0;
+		break;
+	case HPI_CONTROL_AESEBU_RECEIVER:
+		if (phm->u.c.attribute == HPI_AESEBURX_ERRORSTATUS)
+			phr->u.c.param1 = pC->u.aes3rx.error_status;
+		else if (phm->u.c.attribute == HPI_AESEBURX_FORMAT)
+			phr->u.c.param1 = pC->u.aes3rx.source;
+		else
+			found = 0;
+		break;
+	case HPI_CONTROL_AESEBU_TRANSMITTER:
+		if (phm->u.c.attribute == HPI_AESEBUTX_FORMAT)
+			phr->u.c.param1 = pC->u.aes3tx.format;
+		else
+			found = 0;
+		break;
+	case HPI_CONTROL_TONEDETECTOR:
+		if (phm->u.c.attribute == HPI_TONEDETECTOR_STATE)
+			phr->u.c.param1 = pC->u.tone.state;
+		else
+			found = 0;
+		break;
+	case HPI_CONTROL_SILENCEDETECTOR:
+		if (phm->u.c.attribute == HPI_SILENCEDETECTOR_STATE) {
+			phr->u.c.param1 = pC->u.silence.state;
+			phr->u.c.param2 = pC->u.silence.count;
+		} else
+			found = 0;
+		break;
+	case HPI_CONTROL_MICROPHONE:
+		if (phm->u.c.attribute == HPI_MICROPHONE_PHANTOM_POWER)
+			phr->u.c.param1 = pC->u.phantom_power.state;
+		else
+			found = 0;
+		break;
+	case HPI_CONTROL_SAMPLECLOCK:
+		if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE)
+			phr->u.c.param1 = pC->u.clk.source;
+		else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE_INDEX) {
+			if (pC->u.clk.source_index ==
+				HPI_ERROR_ILLEGAL_CACHE_VALUE) {
+				phr->u.c.param1 = 0;
+				phr->error =
+					HPI_ERROR_INVALID_CONTROL_ATTRIBUTE;
+			} else
+				phr->u.c.param1 = pC->u.clk.source_index;
+		} else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SAMPLERATE)
+			phr->u.c.param1 = pC->u.clk.sample_rate;
+		else
+			found = 0;
+		break;
+	case HPI_CONTROL_PAD:
+
+		if (!(p_pad->field_valid_flags & (1 <<
+					HPI_CTL_ATTR_INDEX(phm->u.c.
+						attribute)))) {
+			phr->error = HPI_ERROR_INVALID_CONTROL_ATTRIBUTE;
+			break;
+		}
+
+		if (phm->u.c.attribute == HPI_PAD_PROGRAM_ID)
+			phr->u.c.param1 = p_pad->pI;
+		else if (phm->u.c.attribute == HPI_PAD_PROGRAM_TYPE)
+			phr->u.c.param1 = p_pad->pTY;
+		else {
+			unsigned int index =
+				HPI_CTL_ATTR_INDEX(phm->u.c.attribute) - 1;
+			unsigned int offset = phm->u.c.param1;
+			unsigned int pad_string_len, field_size;
+			char *pad_string;
+			unsigned int tocopy;
+
+			HPI_DEBUG_LOG(VERBOSE, "PADS HPI_PADS_ %d\n",
+				phm->u.c.attribute);
+
+			if (index > ARRAY_SIZE(pad_desc) - 1) {
+				phr->error =
+					HPI_ERROR_INVALID_CONTROL_ATTRIBUTE;
+				break;
+			}
+
+			pad_string = ((char *)p_pad) + pad_desc[index].offset;
+			field_size = pad_desc[index].field_size;
+			/* Ensure null terminator */
+			pad_string[field_size - 1] = 0;
+
+			pad_string_len = strlen(pad_string) + 1;
+
+			if (offset > pad_string_len) {
+				phr->error = HPI_ERROR_INVALID_CONTROL_VALUE;
+				break;
+			}
+
+			tocopy = pad_string_len - offset;
+			if (tocopy > sizeof(phr->u.cu.chars8.sz_data))
+				tocopy = sizeof(phr->u.cu.chars8.sz_data);
+
+			HPI_DEBUG_LOG(VERBOSE,
+				"PADS memcpy(%d), offset %d \n", tocopy,
+				offset);
+			memcpy(phr->u.cu.chars8.sz_data, &pad_string[offset],
+				tocopy);
+
+			phr->u.cu.chars8.remaining_chars =
+				pad_string_len - offset - tocopy;
+		}
+		break;
+	default:
+		found = 0;
+		break;
+	}
+
+	if (found)
+		HPI_DEBUG_LOG(VERBOSE,
+			"cached adap %d, ctl %d, type %d, attr %d\n",
+			phm->adapter_index, pI->control_index,
+			pI->control_type, phm->u.c.attribute);
+	else
+		HPI_DEBUG_LOG(VERBOSE,
+			"uncached adap %d, ctl %d, ctl type %d\n",
+			phm->adapter_index, pI->control_index,
+			pI->control_type);
+
+	if (found)
+		phr->size =
+			sizeof(struct hpi_response_header) +
+			sizeof(struct hpi_control_res);
+
+	return found;
+}
+
+/** Updates the cache with Set values.
+
+Only update if no error.
+Volume and Level return the limited values in the response, so use these
+Multiplexer does so use sent values
+*/
+void hpi_sync_control_cache(struct hpi_control_cache *p_cache,
+	struct hpi_message *phm, struct hpi_response *phr)
+{
+	u16 control_index;
+	struct hpi_control_cache_single *pC;
+	struct hpi_control_cache_info *pI;
+
+	if (phr->error)
+		return;
+
+	if (!find_control(phm, p_cache, &pI, &control_index))
+		return;
+
+	/* pC is the default cached control strucure.
+	   May be cast to something else in the following switch statement.
+	 */
+	pC = (struct hpi_control_cache_single *)pI;
+
+	switch (pI->control_type) {
+	case HPI_CONTROL_VOLUME:
+		if (phm->u.c.attribute == HPI_VOLUME_GAIN) {
+			pC->u.v.an_log[0] = phr->u.c.an_log_value[0];
+			pC->u.v.an_log[1] = phr->u.c.an_log_value[1];
+		}
+		break;
+	case HPI_CONTROL_MULTIPLEXER:
+		/* mux does not return its setting on Set command. */
+		if (phm->u.c.attribute == HPI_MULTIPLEXER_SOURCE) {
+			pC->u.x.source_node_type = (u16)phm->u.c.param1;
+			pC->u.x.source_node_index = (u16)phm->u.c.param2;
+		}
+		break;
+	case HPI_CONTROL_CHANNEL_MODE:
+		/* mode does not return its setting on Set command. */
+		if (phm->u.c.attribute == HPI_CHANNEL_MODE_MODE)
+			pC->u.m.mode = (u16)phm->u.c.param1;
+		break;
+	case HPI_CONTROL_LEVEL:
+		if (phm->u.c.attribute == HPI_LEVEL_GAIN) {
+			pC->u.v.an_log[0] = phr->u.c.an_log_value[0];
+			pC->u.v.an_log[1] = phr->u.c.an_log_value[1];
+		}
+		break;
+	case HPI_CONTROL_MICROPHONE:
+		if (phm->u.c.attribute == HPI_MICROPHONE_PHANTOM_POWER)
+			pC->u.phantom_power.state = (u16)phm->u.c.param1;
+		break;
+	case HPI_CONTROL_AESEBU_TRANSMITTER:
+		if (phm->u.c.attribute == HPI_AESEBUTX_FORMAT)
+			pC->u.aes3tx.format = phm->u.c.param1;
+		break;
+	case HPI_CONTROL_AESEBU_RECEIVER:
+		if (phm->u.c.attribute == HPI_AESEBURX_FORMAT)
+			pC->u.aes3rx.source = phm->u.c.param1;
+		break;
+	case HPI_CONTROL_SAMPLECLOCK:
+		if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE)
+			pC->u.clk.source = (u16)phm->u.c.param1;
+		else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SOURCE_INDEX)
+			pC->u.clk.source_index = (u16)phm->u.c.param1;
+		else if (phm->u.c.attribute == HPI_SAMPLECLOCK_SAMPLERATE)
+			pC->u.clk.sample_rate = phm->u.c.param1;
+		break;
+	default:
+		break;
+	}
+}
+
+struct hpi_control_cache *hpi_alloc_control_cache(const u32
+	number_of_controls, const u32 size_in_bytes,
+	struct hpi_control_cache_info *pDSP_control_buffer)
+{
+	struct hpi_control_cache *p_cache =
+		kmalloc(sizeof(*p_cache), GFP_KERNEL);
+	if (!p_cache)
+		return NULL;
+	p_cache->p_info =
+		kmalloc(sizeof(*p_cache->p_info) * number_of_controls,
+			GFP_KERNEL);
+	if (!p_cache->p_info) {
+		kfree(p_cache);
+		return NULL;
+	}
+	p_cache->cache_size_in_bytes = size_in_bytes;
+	p_cache->control_count = number_of_controls;
+	p_cache->p_cache =
+		(struct hpi_control_cache_single *)pDSP_control_buffer;
+	p_cache->init = 0;
+	return p_cache;
+}
+
+void hpi_free_control_cache(struct hpi_control_cache *p_cache)
+{
+	if (p_cache->init) {
+		kfree(p_cache->p_info);
+		p_cache->p_info = NULL;
+		p_cache->init = 0;
+		kfree(p_cache);
+	}
+}
+
+static void subsys_message(struct hpi_message *phm, struct hpi_response *phr)
+{
+
+	switch (phm->function) {
+	case HPI_SUBSYS_OPEN:
+	case HPI_SUBSYS_CLOSE:
+	case HPI_SUBSYS_DRIVER_UNLOAD:
+		phr->error = 0;
+		break;
+	case HPI_SUBSYS_DRIVER_LOAD:
+		wipe_adapter_list();
+		hpios_alistlock_init(&adapters);
+		phr->error = 0;
+		break;
+	case HPI_SUBSYS_GET_INFO:
+		subsys_get_adapters(phr);
+		break;
+	case HPI_SUBSYS_CREATE_ADAPTER:
+	case HPI_SUBSYS_DELETE_ADAPTER:
+		phr->error = 0;
+		break;
+	default:
+		phr->error = HPI_ERROR_INVALID_FUNC;
+		break;
+	}
+}
+
+void HPI_COMMON(struct hpi_message *phm, struct hpi_response *phr)
+{
+	switch (phm->type) {
+	case HPI_TYPE_MESSAGE:
+		switch (phm->object) {
+		case HPI_OBJ_SUBSYSTEM:
+			subsys_message(phm, phr);
+			break;
+		}
+		break;
+
+	default:
+		phr->error = HPI_ERROR_INVALID_TYPE;
+		break;
+	}
+}
diff --git a/linux/sound/pci/asihpi/hpicmn.h b/linux/sound/pci/asihpi/hpicmn.h
new file mode 100644
index 0000000..6229022
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpicmn.h
@@ -0,0 +1,64 @@
+/**
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+*/
+
+struct hpi_adapter_obj {
+	struct hpi_pci pci;	/* PCI info - bus#,dev#,address etc */
+	u16 adapter_type;	/* ASI6701 etc */
+	u16 index;		/* */
+	u16 open;		/* =1 when adapter open */
+	u16 mixer_open;
+
+	struct hpios_spinlock dsp_lock;
+
+	u16 dsp_crashed;
+	u16 has_control_cache;
+	void *priv;
+};
+
+struct hpi_control_cache {
+	u32 init;	     /**< indicates whether the
+				structures are initialized */
+	u32 control_count;
+	u32 cache_size_in_bytes;
+	struct hpi_control_cache_info
+	**p_info;		 /**< pointer to allocated memory of
+				lookup pointers. */
+	struct hpi_control_cache_single
+	*p_cache;		 /**< pointer to DSP's control cache. */
+};
+
+struct hpi_adapter_obj *hpi_find_adapter(u16 adapter_index);
+u16 hpi_add_adapter(struct hpi_adapter_obj *pao);
+
+void hpi_delete_adapter(struct hpi_adapter_obj *pao);
+
+short hpi_check_control_cache(struct hpi_control_cache *pC,
+	struct hpi_message *phm, struct hpi_response *phr);
+struct hpi_control_cache *hpi_alloc_control_cache(const u32
+	number_of_controls, const u32 size_in_bytes,
+	struct hpi_control_cache_info
+	*pDSP_control_buffer);
+void hpi_free_control_cache(struct hpi_control_cache *p_cache);
+
+void hpi_sync_control_cache(struct hpi_control_cache *pC,
+	struct hpi_message *phm, struct hpi_response *phr);
+u16 hpi_validate_response(struct hpi_message *phm, struct hpi_response *phr);
+short hpi_check_buffer_mapping(struct hpi_control_cache *p_cache,
+	struct hpi_message *phm, void **p, unsigned int *pN);
diff --git a/linux/sound/pci/asihpi/hpidebug.c b/linux/sound/pci/asihpi/hpidebug.c
new file mode 100644
index 0000000..949836e
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpidebug.c
@@ -0,0 +1,225 @@
+/************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+Debug macro translation.
+
+************************************************************************/
+
+#include "hpi_internal.h"
+#include "hpidebug.h"
+
+/* Debug level; 0 quiet; 1 informative, 2 debug, 3 verbose debug.  */
+int hpi_debug_level = HPI_DEBUG_LEVEL_DEFAULT;
+
+void hpi_debug_init(void)
+{
+	printk(KERN_INFO "debug start\n");
+}
+
+int hpi_debug_level_set(int level)
+{
+	int old_level;
+
+	old_level = hpi_debug_level;
+	hpi_debug_level = level;
+	return old_level;
+}
+
+int hpi_debug_level_get(void)
+{
+	return hpi_debug_level;
+}
+
+#ifdef HPIOS_DEBUG_PRINT
+/* implies OS has no printf-like function */
+#include <stdarg.h>
+
+void hpi_debug_printf(char *fmt, ...)
+{
+	va_list arglist;
+	char buffer[128];
+
+	va_start(arglist, fmt);
+
+	if (buffer[0])
+		HPIOS_DEBUG_PRINT(buffer);
+	va_end(arglist);
+}
+#endif
+
+struct treenode {
+	void *array;
+	unsigned int num_elements;
+};
+
+#define make_treenode_from_array(nodename, array) \
+static void *tmp_strarray_##nodename[] = array; \
+static struct treenode nodename = { \
+	&tmp_strarray_##nodename, \
+	ARRAY_SIZE(tmp_strarray_##nodename) \
+};
+
+#define get_treenode_elem(node_ptr, idx, type)  \
+	(&(*((type *)(node_ptr)->array)[idx]))
+
+make_treenode_from_array(hpi_control_type_strings, HPI_CONTROL_TYPE_STRINGS)
+
+	make_treenode_from_array(hpi_subsys_strings, HPI_SUBSYS_STRINGS)
+	make_treenode_from_array(hpi_adapter_strings, HPI_ADAPTER_STRINGS)
+	make_treenode_from_array(hpi_istream_strings, HPI_ISTREAM_STRINGS)
+	make_treenode_from_array(hpi_ostream_strings, HPI_OSTREAM_STRINGS)
+	make_treenode_from_array(hpi_mixer_strings, HPI_MIXER_STRINGS)
+	make_treenode_from_array(hpi_node_strings,
+	{
+	"NODE is invalid object"})
+
+	make_treenode_from_array(hpi_control_strings, HPI_CONTROL_STRINGS)
+	make_treenode_from_array(hpi_nvmemory_strings, HPI_OBJ_STRINGS)
+	make_treenode_from_array(hpi_digitalio_strings, HPI_DIGITALIO_STRINGS)
+	make_treenode_from_array(hpi_watchdog_strings, HPI_WATCHDOG_STRINGS)
+	make_treenode_from_array(hpi_clock_strings, HPI_CLOCK_STRINGS)
+	make_treenode_from_array(hpi_profile_strings, HPI_PROFILE_STRINGS)
+	make_treenode_from_array(hpi_asyncevent_strings, HPI_ASYNCEVENT_STRINGS)
+#define HPI_FUNCTION_STRINGS \
+{ \
+  &hpi_subsys_strings,\
+  &hpi_adapter_strings,\
+  &hpi_ostream_strings,\
+  &hpi_istream_strings,\
+  &hpi_mixer_strings,\
+  &hpi_node_strings,\
+  &hpi_control_strings,\
+  &hpi_nvmemory_strings,\
+  &hpi_digitalio_strings,\
+  &hpi_watchdog_strings,\
+  &hpi_clock_strings,\
+  &hpi_profile_strings,\
+  &hpi_control_strings, \
+  &hpi_asyncevent_strings \
+}
+	make_treenode_from_array(hpi_function_strings, HPI_FUNCTION_STRINGS)
+
+	compile_time_assert(HPI_OBJ_MAXINDEX == 14, obj_list_doesnt_match);
+
+static char *hpi_function_string(unsigned int function)
+{
+	unsigned int object;
+	struct treenode *tmp;
+
+	object = function / HPI_OBJ_FUNCTION_SPACING;
+	function = function - object * HPI_OBJ_FUNCTION_SPACING;
+
+	if (object == 0 || object == HPI_OBJ_NODE
+		|| object > hpi_function_strings.num_elements)
+		return "invalid object";
+
+	tmp = get_treenode_elem(&hpi_function_strings, object - 1,
+		struct treenode *);
+
+	if (function == 0 || function > tmp->num_elements)
+		return "invalid function";
+
+	return get_treenode_elem(tmp, function - 1, char *);
+}
+
+void hpi_debug_message(struct hpi_message *phm, char *sz_fileline)
+{
+	if (phm) {
+		if ((phm->object <= HPI_OBJ_MAXINDEX) && phm->object) {
+			u16 index = 0;
+			u16 attrib = 0;
+			int is_control = 0;
+
+			index = phm->obj_index;
+			switch (phm->object) {
+			case HPI_OBJ_ADAPTER:
+			case HPI_OBJ_PROFILE:
+				break;
+			case HPI_OBJ_MIXER:
+				if (phm->function ==
+					HPI_MIXER_GET_CONTROL_BY_INDEX)
+					index = phm->u.m.control_index;
+				break;
+			case HPI_OBJ_OSTREAM:
+			case HPI_OBJ_ISTREAM:
+				break;
+
+			case HPI_OBJ_CONTROLEX:
+			case HPI_OBJ_CONTROL:
+				if (phm->version == 1)
+					attrib = HPI_CTL_ATTR(UNIVERSAL, 1);
+				else
+					attrib = phm->u.c.attribute;
+				is_control = 1;
+				break;
+			default:
+				break;
+			}
+
+			if (is_control && (attrib & 0xFF00)) {
+				int control_type = (attrib & 0xFF00) >> 8;
+				int attr_index = HPI_CTL_ATTR_INDEX(attrib);
+				/* note the KERN facility level
+				   is in szFileline already */
+				printk("%s adapter %d %s "
+					"ctrl_index x%04x %s %d\n",
+					sz_fileline, phm->adapter_index,
+					hpi_function_string(phm->function),
+					index,
+					get_treenode_elem
+					(&hpi_control_type_strings,
+						control_type, char *),
+					attr_index);
+
+			} else
+				printk("%s adapter %d %s "
+					"idx x%04x attr x%04x \n",
+					sz_fileline, phm->adapter_index,
+					hpi_function_string(phm->function),
+					index, attrib);
+		} else {
+			printk("adap=%d, invalid obj=%d, func=0x%x\n",
+				phm->adapter_index, phm->object,
+				phm->function);
+		}
+	} else
+		printk(KERN_ERR
+			"NULL message pointer to hpi_debug_message!\n");
+}
+
+void hpi_debug_data(u16 *pdata, u32 len)
+{
+	u32 i;
+	int j;
+	int k;
+	int lines;
+	int cols = 8;
+
+	lines = (len + cols - 1) / cols;
+	if (lines > 8)
+		lines = 8;
+
+	for (i = 0, j = 0; j < lines; j++) {
+		printk(KERN_DEBUG "%p:", (pdata + i));
+
+		for (k = 0; k < cols && i < len; i++, k++)
+			printk("%s%04x", k == 0 ? "" : " ", pdata[i]);
+
+		printk("\n");
+	}
+}
diff --git a/linux/sound/pci/asihpi/hpidebug.h b/linux/sound/pci/asihpi/hpidebug.h
new file mode 100644
index 0000000..a2f0952
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpidebug.h
@@ -0,0 +1,385 @@
+/*****************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+Debug macros.
+
+*****************************************************************************/
+
+#ifndef _HPIDEBUG_H
+#define _HPIDEBUG_H
+
+#include "hpi_internal.h"
+
+/* Define debugging levels.  */
+enum { HPI_DEBUG_LEVEL_ERROR = 0,	/* always log errors */
+	HPI_DEBUG_LEVEL_WARNING = 1,
+	HPI_DEBUG_LEVEL_NOTICE = 2,
+	HPI_DEBUG_LEVEL_INFO = 3,
+	HPI_DEBUG_LEVEL_DEBUG = 4,
+	HPI_DEBUG_LEVEL_VERBOSE = 5	/* same printk level as DEBUG */
+};
+
+#define HPI_DEBUG_LEVEL_DEFAULT HPI_DEBUG_LEVEL_NOTICE
+
+/* an OS can define an extra flag string that is appended to
+   the start of each message, eg see hpios_linux.h */
+
+#ifdef SOURCEFILE_NAME
+#define FILE_LINE  SOURCEFILE_NAME ":" __stringify(__LINE__) " "
+#else
+#define FILE_LINE  __FILE__ ":" __stringify(__LINE__) " "
+#endif
+
+#if defined(HPI_DEBUG) && defined(_WINDOWS)
+#define HPI_DEBUGBREAK() debug_break()
+#else
+#define HPI_DEBUGBREAK()
+#endif
+
+#define HPI_DEBUG_ASSERT(expression) \
+	do { \
+		if (!(expression)) {\
+			printk(KERN_ERR  FILE_LINE\
+				"ASSERT " __stringify(expression));\
+			HPI_DEBUGBREAK();\
+		} \
+	} while (0)
+
+#define HPI_DEBUG_LOG(level, ...) \
+	do { \
+		if (hpi_debug_level >= HPI_DEBUG_LEVEL_##level) { \
+			printk(HPI_DEBUG_FLAG_##level \
+			FILE_LINE  __VA_ARGS__); \
+		} \
+	} while (0)
+
+void hpi_debug_init(void);
+int hpi_debug_level_set(int level);
+int hpi_debug_level_get(void);
+/* needed by Linux driver for dynamic debug level changes */
+extern int hpi_debug_level;
+
+void hpi_debug_message(struct hpi_message *phm, char *sz_fileline);
+
+void hpi_debug_data(u16 *pdata, u32 len);
+
+#define HPI_DEBUG_DATA(pdata, len)                                      \
+	do {                                                            \
+		if (hpi_debug_level >= HPI_DEBUG_LEVEL_VERBOSE) \
+			hpi_debug_data(pdata, len); \
+	} while (0)
+
+#define HPI_DEBUG_MESSAGE(level, phm)                                   \
+	do {                                                            \
+		if (hpi_debug_level >= HPI_DEBUG_LEVEL_##level) {         \
+			hpi_debug_message(phm,HPI_DEBUG_FLAG_##level    \
+				FILE_LINE __stringify(level));\
+		}                                                       \
+	} while (0)
+
+#define HPI_DEBUG_RESPONSE(phr)                                         \
+	do {                                                            \
+		if ((hpi_debug_level >= HPI_DEBUG_LEVEL_DEBUG) && (phr->error))\
+			HPI_DEBUG_LOG(ERROR, \
+				"HPI response - error# %d\n", \
+				phr->error); \
+		else if (hpi_debug_level >= HPI_DEBUG_LEVEL_VERBOSE) \
+			HPI_DEBUG_LOG(VERBOSE, "HPI response OK\n");\
+	} while (0)
+
+#ifndef compile_time_assert
+#define compile_time_assert(cond, msg) \
+    typedef char msg[(cond) ? 1 : -1]
+#endif
+
+	  /* check that size is exactly some number */
+#define function_count_check(sym, size) \
+    compile_time_assert((sym##_FUNCTION_COUNT) == (size),\
+	    strings_match_defs_##sym)
+
+/* These strings should be generated using a macro which defines
+   the corresponding symbol values.  */
+#define HPI_OBJ_STRINGS \
+{                               \
+  "HPI_OBJ_SUBSYSTEM",        \
+  "HPI_OBJ_ADAPTER",          \
+  "HPI_OBJ_OSTREAM",          \
+  "HPI_OBJ_ISTREAM",          \
+  "HPI_OBJ_MIXER",            \
+  "HPI_OBJ_NODE",             \
+  "HPI_OBJ_CONTROL",          \
+  "HPI_OBJ_NVMEMORY",         \
+  "HPI_OBJ_DIGITALIO",        \
+  "HPI_OBJ_WATCHDOG",         \
+  "HPI_OBJ_CLOCK",            \
+  "HPI_OBJ_PROFILE",          \
+  "HPI_OBJ_CONTROLEX"         \
+}
+
+#define HPI_SUBSYS_STRINGS      \
+{                               \
+  "HPI_SUBSYS_OPEN",          \
+  "HPI_SUBSYS_GET_VERSION",   \
+  "HPI_SUBSYS_GET_INFO",      \
+  "HPI_SUBSYS_FIND_ADAPTERS", \
+  "HPI_SUBSYS_CREATE_ADAPTER",\
+  "HPI_SUBSYS_CLOSE",         \
+  "HPI_SUBSYS_DELETE_ADAPTER", \
+  "HPI_SUBSYS_DRIVER_LOAD", \
+  "HPI_SUBSYS_DRIVER_UNLOAD", \
+  "HPI_SUBSYS_READ_PORT_8",   \
+  "HPI_SUBSYS_WRITE_PORT_8",  \
+  "HPI_SUBSYS_GET_NUM_ADAPTERS",\
+  "HPI_SUBSYS_GET_ADAPTER",   \
+  "HPI_SUBSYS_SET_NETWORK_INTERFACE"\
+}
+function_count_check(HPI_SUBSYS, 14);
+
+#define HPI_ADAPTER_STRINGS     \
+{                               \
+  "HPI_ADAPTER_OPEN",         \
+  "HPI_ADAPTER_CLOSE",        \
+  "HPI_ADAPTER_GET_INFO",     \
+  "HPI_ADAPTER_GET_ASSERT",   \
+  "HPI_ADAPTER_TEST_ASSERT",    \
+  "HPI_ADAPTER_SET_MODE",       \
+  "HPI_ADAPTER_GET_MODE",       \
+  "HPI_ADAPTER_ENABLE_CAPABILITY",\
+  "HPI_ADAPTER_SELFTEST",        \
+  "HPI_ADAPTER_FIND_OBJECT",     \
+  "HPI_ADAPTER_QUERY_FLASH",     \
+  "HPI_ADAPTER_START_FLASH",     \
+  "HPI_ADAPTER_PROGRAM_FLASH",   \
+  "HPI_ADAPTER_SET_PROPERTY",    \
+  "HPI_ADAPTER_GET_PROPERTY",    \
+  "HPI_ADAPTER_ENUM_PROPERTY",    \
+  "HPI_ADAPTER_MODULE_INFO",    \
+  "HPI_ADAPTER_DEBUG_READ"    \
+}
+
+function_count_check(HPI_ADAPTER, 18);
+
+#define HPI_OSTREAM_STRINGS     \
+{                               \
+  "HPI_OSTREAM_OPEN",         \
+  "HPI_OSTREAM_CLOSE",        \
+  "HPI_OSTREAM_WRITE",        \
+  "HPI_OSTREAM_START",        \
+  "HPI_OSTREAM_STOP",         \
+  "HPI_OSTREAM_RESET",                \
+  "HPI_OSTREAM_GET_INFO",     \
+  "HPI_OSTREAM_QUERY_FORMAT", \
+  "HPI_OSTREAM_DATA",         \
+  "HPI_OSTREAM_SET_VELOCITY", \
+  "HPI_OSTREAM_SET_PUNCHINOUT", \
+  "HPI_OSTREAM_SINEGEN",        \
+  "HPI_OSTREAM_ANC_RESET",      \
+  "HPI_OSTREAM_ANC_GET_INFO",   \
+  "HPI_OSTREAM_ANC_READ",       \
+  "HPI_OSTREAM_SET_TIMESCALE",\
+  "HPI_OSTREAM_SET_FORMAT", \
+  "HPI_OSTREAM_HOSTBUFFER_ALLOC", \
+  "HPI_OSTREAM_HOSTBUFFER_FREE", \
+  "HPI_OSTREAM_GROUP_ADD",\
+  "HPI_OSTREAM_GROUP_GETMAP", \
+  "HPI_OSTREAM_GROUP_RESET", \
+  "HPI_OSTREAM_HOSTBUFFER_GET_INFO", \
+  "HPI_OSTREAM_WAIT_START", \
+}
+function_count_check(HPI_OSTREAM, 24);
+
+#define HPI_ISTREAM_STRINGS     \
+{                               \
+  "HPI_ISTREAM_OPEN",         \
+  "HPI_ISTREAM_CLOSE",        \
+  "HPI_ISTREAM_SET_FORMAT",   \
+  "HPI_ISTREAM_READ",         \
+  "HPI_ISTREAM_START",        \
+  "HPI_ISTREAM_STOP",         \
+  "HPI_ISTREAM_RESET",        \
+  "HPI_ISTREAM_GET_INFO",     \
+  "HPI_ISTREAM_QUERY_FORMAT", \
+  "HPI_ISTREAM_ANC_RESET",      \
+  "HPI_ISTREAM_ANC_GET_INFO",   \
+  "HPI_ISTREAM_ANC_WRITE",   \
+  "HPI_ISTREAM_HOSTBUFFER_ALLOC",\
+  "HPI_ISTREAM_HOSTBUFFER_FREE", \
+  "HPI_ISTREAM_GROUP_ADD", \
+  "HPI_ISTREAM_GROUP_GETMAP", \
+  "HPI_ISTREAM_GROUP_RESET", \
+  "HPI_ISTREAM_HOSTBUFFER_GET_INFO", \
+  "HPI_ISTREAM_WAIT_START", \
+}
+function_count_check(HPI_ISTREAM, 19);
+
+#define HPI_MIXER_STRINGS       \
+{                               \
+  "HPI_MIXER_OPEN",           \
+  "HPI_MIXER_CLOSE",          \
+  "HPI_MIXER_GET_INFO",       \
+  "HPI_MIXER_GET_NODE_INFO",  \
+  "HPI_MIXER_GET_CONTROL",    \
+  "HPI_MIXER_SET_CONNECTION", \
+  "HPI_MIXER_GET_CONNECTIONS",        \
+  "HPI_MIXER_GET_CONTROL_BY_INDEX",   \
+  "HPI_MIXER_GET_CONTROL_ARRAY_BY_INDEX",     \
+  "HPI_MIXER_GET_CONTROL_MULTIPLE_VALUES",    \
+  "HPI_MIXER_STORE",  \
+}
+function_count_check(HPI_MIXER, 11);
+
+#define HPI_CONTROL_STRINGS     \
+{                               \
+  "HPI_CONTROL_GET_INFO",     \
+  "HPI_CONTROL_GET_STATE",    \
+  "HPI_CONTROL_SET_STATE"     \
+}
+function_count_check(HPI_CONTROL, 3);
+
+#define HPI_NVMEMORY_STRINGS    \
+{                               \
+  "HPI_NVMEMORY_OPEN",        \
+  "HPI_NVMEMORY_READ_BYTE",   \
+  "HPI_NVMEMORY_WRITE_BYTE"   \
+}
+function_count_check(HPI_NVMEMORY, 3);
+
+#define HPI_DIGITALIO_STRINGS   \
+{                               \
+  "HPI_GPIO_OPEN",            \
+  "HPI_GPIO_READ_BIT",        \
+  "HPI_GPIO_WRITE_BIT",       \
+  "HPI_GPIO_READ_ALL",                \
+  "HPI_GPIO_WRITE_STATUS"\
+}
+function_count_check(HPI_GPIO, 5);
+
+#define HPI_WATCHDOG_STRINGS    \
+{                               \
+  "HPI_WATCHDOG_OPEN",        \
+  "HPI_WATCHDOG_SET_TIME",    \
+  "HPI_WATCHDOG_PING"         \
+}
+
+#define HPI_CLOCK_STRINGS       \
+{                               \
+  "HPI_CLOCK_OPEN",           \
+  "HPI_CLOCK_SET_TIME",       \
+  "HPI_CLOCK_GET_TIME"        \
+}
+
+#define HPI_PROFILE_STRINGS     \
+{                               \
+  "HPI_PROFILE_OPEN_ALL",     \
+  "HPI_PROFILE_START_ALL",    \
+  "HPI_PROFILE_STOP_ALL",     \
+  "HPI_PROFILE_GET",          \
+  "HPI_PROFILE_GET_IDLECOUNT",  \
+  "HPI_PROFILE_GET_NAME",       \
+  "HPI_PROFILE_GET_UTILIZATION" \
+}
+function_count_check(HPI_PROFILE, 7);
+
+#define HPI_ASYNCEVENT_STRINGS  \
+{                               \
+  "HPI_ASYNCEVENT_OPEN",\
+  "HPI_ASYNCEVENT_CLOSE  ",\
+  "HPI_ASYNCEVENT_WAIT",\
+  "HPI_ASYNCEVENT_GETCOUNT",\
+  "HPI_ASYNCEVENT_GET",\
+  "HPI_ASYNCEVENT_SENDEVENTS"\
+}
+function_count_check(HPI_ASYNCEVENT, 6);
+
+#define HPI_CONTROL_TYPE_STRINGS \
+{ \
+	"null control", \
+	"HPI_CONTROL_CONNECTION", \
+	"HPI_CONTROL_VOLUME", \
+	"HPI_CONTROL_METER", \
+	"HPI_CONTROL_MUTE", \
+	"HPI_CONTROL_MULTIPLEXER", \
+	"HPI_CONTROL_AESEBU_TRANSMITTER", \
+	"HPI_CONTROL_AESEBU_RECEIVER", \
+	"HPI_CONTROL_LEVEL", \
+	"HPI_CONTROL_TUNER", \
+	"HPI_CONTROL_ONOFFSWITCH", \
+	"HPI_CONTROL_VOX", \
+	"HPI_CONTROL_AES18_TRANSMITTER", \
+	"HPI_CONTROL_AES18_RECEIVER", \
+	"HPI_CONTROL_AES18_BLOCKGENERATOR", \
+	"HPI_CONTROL_CHANNEL_MODE", \
+	"HPI_CONTROL_BITSTREAM", \
+	"HPI_CONTROL_SAMPLECLOCK", \
+	"HPI_CONTROL_MICROPHONE", \
+	"HPI_CONTROL_PARAMETRIC_EQ", \
+	"HPI_CONTROL_COMPANDER", \
+	"HPI_CONTROL_COBRANET", \
+	"HPI_CONTROL_TONE_DETECT", \
+	"HPI_CONTROL_SILENCE_DETECT", \
+	"HPI_CONTROL_PAD", \
+	"HPI_CONTROL_SRC" ,\
+	"HPI_CONTROL_UNIVERSAL" \
+}
+
+compile_time_assert((HPI_CONTROL_LAST_INDEX + 1 == 27),
+	controltype_strings_match_defs);
+
+#define HPI_SOURCENODE_STRINGS \
+{ \
+	"no source", \
+	"HPI_SOURCENODE_OSTREAM", \
+	"HPI_SOURCENODE_LINEIN", \
+	"HPI_SOURCENODE_AESEBU_IN", \
+	"HPI_SOURCENODE_TUNER", \
+	"HPI_SOURCENODE_RF", \
+	"HPI_SOURCENODE_CLOCK_SOURCE", \
+	"HPI_SOURCENODE_RAW_BITSTREAM", \
+	"HPI_SOURCENODE_MICROPHONE", \
+	"HPI_SOURCENODE_COBRANET", \
+	"HPI_SOURCENODE_ANALOG", \
+	"HPI_SOURCENODE_ADAPTER" \
+}
+
+compile_time_assert((HPI_SOURCENODE_LAST_INDEX - HPI_SOURCENODE_NONE + 1) ==
+	(12), sourcenode_strings_match_defs);
+
+#define HPI_DESTNODE_STRINGS \
+{ \
+	"no destination", \
+	"HPI_DESTNODE_ISTREAM", \
+	"HPI_DESTNODE_LINEOUT", \
+	"HPI_DESTNODE_AESEBU_OUT", \
+	"HPI_DESTNODE_RF", \
+	"HPI_DESTNODE_SPEAKER", \
+	"HPI_DESTNODE_COBRANET", \
+	"HPI_DESTNODE_ANALOG" \
+}
+compile_time_assert((HPI_DESTNODE_LAST_INDEX - HPI_DESTNODE_NONE + 1) == (8),
+	destnode_strings_match_defs);
+
+#define HPI_CONTROL_CHANNEL_MODE_STRINGS \
+{ \
+	"XXX HPI_CHANNEL_MODE_ERROR XXX", \
+	"HPI_CHANNEL_MODE_NORMAL", \
+	"HPI_CHANNEL_MODE_SWAP", \
+	"HPI_CHANNEL_MODE_LEFT_ONLY", \
+	"HPI_CHANNEL_MODE_RIGHT_ONLY" \
+}
+
+#endif				/* _HPIDEBUG_H  */
diff --git a/linux/sound/pci/asihpi/hpidspcd.c b/linux/sound/pci/asihpi/hpidspcd.c
new file mode 100644
index 0000000..9b10d9a
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpidspcd.c
@@ -0,0 +1,172 @@
+/***********************************************************************/
+/*!
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+\file
+Functions for reading DSP code to load into DSP
+
+(Linux only:) If DSPCODE_FIRMWARE_LOADER is defined, code is read using
+hotplug firmware loader from individual dsp code files
+
+If neither of the above is defined, code is read from linked arrays.
+DSPCODE_ARRAY is defined.
+
+HPI_INCLUDE_**** must be defined
+and the appropriate hzz?????.c or hex?????.c linked in
+
+ */
+/***********************************************************************/
+#define SOURCEFILE_NAME "hpidspcd.c"
+#include "hpidspcd.h"
+#include "hpidebug.h"
+
+/**
+ Header structure for binary dsp code file (see asidsp.doc)
+ This structure must match that used in s2bin.c for generation of asidsp.bin
+ */
+
+#ifndef DISABLE_PRAGMA_PACK1
+#pragma pack(push, 1)
+#endif
+
+struct code_header {
+	u32 size;
+	char type[4];
+	u32 adapter;
+	u32 version;
+	u32 crc;
+};
+
+#ifndef DISABLE_PRAGMA_PACK1
+#pragma pack(pop)
+#endif
+
+#define HPI_VER_DECIMAL ((int)(HPI_VER_MAJOR(HPI_VER) * 10000 + \
+	    HPI_VER_MINOR(HPI_VER) * 100 + HPI_VER_RELEASE(HPI_VER)))
+
+/***********************************************************************/
+#include "linux/pci.h"
+/*-------------------------------------------------------------------*/
+short hpi_dsp_code_open(u32 adapter, struct dsp_code *ps_dsp_code,
+	u32 *pos_error_code)
+{
+	const struct firmware *ps_firmware = ps_dsp_code->ps_firmware;
+	struct code_header header;
+	char fw_name[20];
+	int err;
+
+	sprintf(fw_name, "asihpi/dsp%04x.bin", adapter);
+	HPI_DEBUG_LOG(INFO, "requesting firmware for %s\n", fw_name);
+
+	err = request_firmware(&ps_firmware, fw_name,
+		&ps_dsp_code->ps_dev->dev);
+	if (err != 0) {
+		HPI_DEBUG_LOG(ERROR, "%d, request_firmware failed for  %s\n",
+			err, fw_name);
+		goto error1;
+	}
+	if (ps_firmware->size < sizeof(header)) {
+		HPI_DEBUG_LOG(ERROR, "header size too small %s\n", fw_name);
+		goto error2;
+	}
+	memcpy(&header, ps_firmware->data, sizeof(header));
+	if (header.adapter != adapter) {
+		HPI_DEBUG_LOG(ERROR, "adapter type incorrect %4x != %4x\n",
+			header.adapter, adapter);
+		goto error2;
+	}
+	if (header.size != ps_firmware->size) {
+		HPI_DEBUG_LOG(ERROR, "code size wrong  %d != %ld\n",
+			header.size, (unsigned long)ps_firmware->size);
+		goto error2;
+	}
+
+	if (header.version / 10000 != HPI_VER_DECIMAL / 10000) {
+		HPI_DEBUG_LOG(ERROR,
+			"firmware major version mismatch "
+			"DSP image %d != driver %d\n", header.version,
+			HPI_VER_DECIMAL);
+		goto error2;
+	}
+
+	if (header.version != HPI_VER_DECIMAL) {
+		HPI_DEBUG_LOG(WARNING,
+			"version mismatch  DSP image %d != driver %d\n",
+			header.version, HPI_VER_DECIMAL);
+		/* goto error2;  still allow driver to load */
+	}
+
+	HPI_DEBUG_LOG(INFO, "dsp code %s opened\n", fw_name);
+	ps_dsp_code->ps_firmware = ps_firmware;
+	ps_dsp_code->block_length = header.size / sizeof(u32);
+	ps_dsp_code->word_count = sizeof(header) / sizeof(u32);
+	ps_dsp_code->version = header.version;
+	ps_dsp_code->crc = header.crc;
+	return 0;
+
+error2:
+	release_firmware(ps_firmware);
+error1:
+	ps_dsp_code->ps_firmware = NULL;
+	ps_dsp_code->block_length = 0;
+	return HPI_ERROR_DSP_FILE_NOT_FOUND;
+}
+
+/*-------------------------------------------------------------------*/
+void hpi_dsp_code_close(struct dsp_code *ps_dsp_code)
+{
+	if (ps_dsp_code->ps_firmware != NULL) {
+		HPI_DEBUG_LOG(DEBUG, "dsp code closed\n");
+		release_firmware(ps_dsp_code->ps_firmware);
+		ps_dsp_code->ps_firmware = NULL;
+	}
+}
+
+/*-------------------------------------------------------------------*/
+void hpi_dsp_code_rewind(struct dsp_code *ps_dsp_code)
+{
+	/* Go back to start of  data, after header */
+	ps_dsp_code->word_count = sizeof(struct code_header) / sizeof(u32);
+}
+
+/*-------------------------------------------------------------------*/
+short hpi_dsp_code_read_word(struct dsp_code *ps_dsp_code, u32 *pword)
+{
+	if (ps_dsp_code->word_count + 1 > ps_dsp_code->block_length)
+		return (HPI_ERROR_DSP_FILE_FORMAT);
+
+	*pword = ((u32 *)(ps_dsp_code->ps_firmware->data))[ps_dsp_code->
+		word_count];
+	ps_dsp_code->word_count++;
+	return 0;
+}
+
+/*-------------------------------------------------------------------*/
+short hpi_dsp_code_read_block(size_t words_requested,
+	struct dsp_code *ps_dsp_code, u32 **ppblock)
+{
+	if (ps_dsp_code->word_count + words_requested >
+		ps_dsp_code->block_length)
+		return HPI_ERROR_DSP_FILE_FORMAT;
+
+	*ppblock =
+		((u32 *)(ps_dsp_code->ps_firmware->data)) +
+		ps_dsp_code->word_count;
+	ps_dsp_code->word_count += words_requested;
+	return 0;
+}
diff --git a/linux/sound/pci/asihpi/hpidspcd.h b/linux/sound/pci/asihpi/hpidspcd.h
new file mode 100644
index 0000000..d7c2403
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpidspcd.h
@@ -0,0 +1,104 @@
+/***********************************************************************/
+/**
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+\file
+Functions for reading DSP code to load into DSP
+
+ hpi_dspcode_defines HPI DSP code loading method
+Define exactly one of these to select how the DSP code is supplied to
+the adapter.
+
+End users writing applications that use the HPI interface do not have to
+use any of the below defines; they are only necessary for building drivers
+
+HPI_DSPCODE_FILE:
+DSP code is supplied as a file that is opened and read from by the driver.
+
+HPI_DSPCODE_FIRMWARE:
+DSP code is read using the hotplug firmware loader module.
+     Only valid when compiling the HPI kernel driver under Linux.
+*/
+/***********************************************************************/
+#ifndef _HPIDSPCD_H_
+#define _HPIDSPCD_H_
+
+#include "hpi_internal.h"
+
+#ifndef DISABLE_PRAGMA_PACK1
+#pragma pack(push, 1)
+#endif
+
+/** Descriptor for dspcode from firmware loader */
+struct dsp_code {
+	/**  Firmware descriptor */
+	const struct firmware *ps_firmware;
+	struct pci_dev *ps_dev;
+	/** Expected number of words in the whole dsp code,INCL header */
+	long int block_length;
+	/** Number of words read so far */
+	long int word_count;
+	/** Version read from dsp code file */
+	u32 version;
+	/** CRC read from dsp code file */
+	u32 crc;
+};
+
+#ifndef DISABLE_PRAGMA_PACK1
+#pragma pack(pop)
+#endif
+
+/** Prepare *psDspCode to refer to the requuested adapter.
+ Searches the file, or selects the appropriate linked array
+
+\return 0 for success, or error code if requested code is not available
+*/
+short hpi_dsp_code_open(
+	/** Code identifier, usually adapter family */
+	u32 adapter,
+	/** Pointer to DSP code control structure */
+	struct dsp_code *ps_dsp_code,
+	/** Pointer to dword to receive OS specific error code */
+	u32 *pos_error_code);
+
+/** Close the DSP code file */
+void hpi_dsp_code_close(struct dsp_code *ps_dsp_code);
+
+/** Rewind to the beginning of the DSP code file (for verify) */
+void hpi_dsp_code_rewind(struct dsp_code *ps_dsp_code);
+
+/** Read one word from the dsp code file
+	\return 0 for success, or error code if eof, or block length exceeded
+*/
+short hpi_dsp_code_read_word(struct dsp_code *ps_dsp_code,
+				      /**< DSP code descriptor */
+	u32 *pword /**< where to store the read word */
+	);
+
+/** Get a block of dsp code into an internal buffer, and provide a pointer to
+that buffer. (If dsp code is already an array in memory, it is referenced,
+not copied.)
+
+\return Error if requested number of words are not available
+*/
+short hpi_dsp_code_read_block(size_t words_requested,
+	struct dsp_code *ps_dsp_code,
+	/* Pointer to store (Pointer to code buffer) */
+	u32 **ppblock);
+
+#endif
diff --git a/linux/sound/pci/asihpi/hpifunc.c b/linux/sound/pci/asihpi/hpifunc.c
new file mode 100644
index 0000000..1e92eb6
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpifunc.c
@@ -0,0 +1,3940 @@
+
+#include "hpi_internal.h"
+#include "hpimsginit.h"
+
+#include "hpidebug.h"
+
+struct hpi_handle {
+	unsigned int obj_index:12;
+	unsigned int obj_type:4;
+	unsigned int adapter_index:14;
+	unsigned int spare:1;
+	unsigned int read_only:1;
+};
+
+union handle_word {
+	struct hpi_handle h;
+	u32 w;
+};
+
+u32 hpi_indexes_to_handle(const char c_object, const u16 adapter_index,
+	const u16 object_index)
+{
+	union handle_word handle;
+
+	handle.h.adapter_index = adapter_index;
+	handle.h.spare = 0;
+	handle.h.read_only = 0;
+	handle.h.obj_type = c_object;
+	handle.h.obj_index = object_index;
+	return handle.w;
+}
+
+void hpi_handle_to_indexes(const u32 handle, u16 *pw_adapter_index,
+	u16 *pw_object_index)
+{
+	union handle_word uhandle;
+	uhandle.w = handle;
+
+	if (pw_adapter_index)
+		*pw_adapter_index = (u16)uhandle.h.adapter_index;
+	if (pw_object_index)
+		*pw_object_index = (u16)uhandle.h.obj_index;
+}
+
+char hpi_handle_object(const u32 handle)
+{
+	union handle_word uhandle;
+	uhandle.w = handle;
+	return (char)uhandle.h.obj_type;
+}
+
+#define u32TOINDEX(h, i1) \
+do {\
+	if (h == 0) \
+		return HPI_ERROR_INVALID_OBJ; \
+	else \
+		hpi_handle_to_indexes(h, i1, NULL); \
+} while (0)
+
+#define u32TOINDEXES(h, i1, i2) \
+do {\
+	if (h == 0) \
+		return HPI_ERROR_INVALID_OBJ; \
+	else \
+		hpi_handle_to_indexes(h, i1, i2);\
+} while (0)
+
+void hpi_format_to_msg(struct hpi_msg_format *pMF,
+	const struct hpi_format *pF)
+{
+	pMF->sample_rate = pF->sample_rate;
+	pMF->bit_rate = pF->bit_rate;
+	pMF->attributes = pF->attributes;
+	pMF->channels = pF->channels;
+	pMF->format = pF->format;
+}
+
+static void hpi_msg_to_format(struct hpi_format *pF,
+	struct hpi_msg_format *pMF)
+{
+	pF->sample_rate = pMF->sample_rate;
+	pF->bit_rate = pMF->bit_rate;
+	pF->attributes = pMF->attributes;
+	pF->channels = pMF->channels;
+	pF->format = pMF->format;
+	pF->mode_legacy = 0;
+	pF->unused = 0;
+}
+
+void hpi_stream_response_to_legacy(struct hpi_stream_res *pSR)
+{
+	pSR->u.legacy_stream_info.auxiliary_data_available =
+		pSR->u.stream_info.auxiliary_data_available;
+	pSR->u.legacy_stream_info.state = pSR->u.stream_info.state;
+}
+
+static struct hpi_hsubsys gh_subsys;
+
+struct hpi_hsubsys *hpi_subsys_create(void)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	memset(&gh_subsys, 0, sizeof(struct hpi_hsubsys));
+
+	{
+		hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+			HPI_SUBSYS_OPEN);
+		hpi_send_recv(&hm, &hr);
+
+		if (hr.error == 0)
+			return &gh_subsys;
+
+	}
+	return NULL;
+}
+
+void hpi_subsys_free(const struct hpi_hsubsys *ph_subsys)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_CLOSE);
+	hpi_send_recv(&hm, &hr);
+
+}
+
+u16 hpi_subsys_get_version(const struct hpi_hsubsys *ph_subsys, u32 *pversion)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_GET_VERSION);
+	hpi_send_recv(&hm, &hr);
+	*pversion = hr.u.s.version;
+	return hr.error;
+}
+
+u16 hpi_subsys_get_version_ex(const struct hpi_hsubsys *ph_subsys,
+	u32 *pversion_ex)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_GET_VERSION);
+	hpi_send_recv(&hm, &hr);
+	*pversion_ex = hr.u.s.data;
+	return hr.error;
+}
+
+u16 hpi_subsys_get_info(const struct hpi_hsubsys *ph_subsys, u32 *pversion,
+	u16 *pw_num_adapters, u16 aw_adapter_list[], u16 list_length)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_GET_INFO);
+
+	hpi_send_recv(&hm, &hr);
+
+	*pversion = hr.u.s.version;
+	if (list_length > HPI_MAX_ADAPTERS)
+		memcpy(aw_adapter_list, &hr.u.s.aw_adapter_list,
+			HPI_MAX_ADAPTERS);
+	else
+		memcpy(aw_adapter_list, &hr.u.s.aw_adapter_list, list_length);
+	*pw_num_adapters = hr.u.s.num_adapters;
+	return hr.error;
+}
+
+u16 hpi_subsys_find_adapters(const struct hpi_hsubsys *ph_subsys,
+	u16 *pw_num_adapters, u16 aw_adapter_list[], u16 list_length)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_FIND_ADAPTERS);
+
+	hpi_send_recv(&hm, &hr);
+
+	if (list_length > HPI_MAX_ADAPTERS) {
+		memcpy(aw_adapter_list, &hr.u.s.aw_adapter_list,
+			HPI_MAX_ADAPTERS * sizeof(u16));
+		memset(&aw_adapter_list[HPI_MAX_ADAPTERS], 0,
+			(list_length - HPI_MAX_ADAPTERS) * sizeof(u16));
+	} else
+		memcpy(aw_adapter_list, &hr.u.s.aw_adapter_list,
+			list_length * sizeof(u16));
+	*pw_num_adapters = hr.u.s.num_adapters;
+
+	return hr.error;
+}
+
+u16 hpi_subsys_create_adapter(const struct hpi_hsubsys *ph_subsys,
+	const struct hpi_resource *p_resource, u16 *pw_adapter_index)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_CREATE_ADAPTER);
+	hm.u.s.resource = *p_resource;
+
+	hpi_send_recv(&hm, &hr);
+
+	*pw_adapter_index = hr.u.s.adapter_index;
+	return hr.error;
+}
+
+u16 hpi_subsys_delete_adapter(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_DELETE_ADAPTER);
+	hm.adapter_index = adapter_index;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_subsys_get_num_adapters(const struct hpi_hsubsys *ph_subsys,
+	int *pn_num_adapters)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_GET_NUM_ADAPTERS);
+	hpi_send_recv(&hm, &hr);
+	*pn_num_adapters = (int)hr.u.s.num_adapters;
+	return hr.error;
+}
+
+u16 hpi_subsys_get_adapter(const struct hpi_hsubsys *ph_subsys, int iterator,
+	u32 *padapter_index, u16 *pw_adapter_type)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_GET_ADAPTER);
+	hm.adapter_index = (u16)iterator;
+	hpi_send_recv(&hm, &hr);
+	*padapter_index = (int)hr.u.s.adapter_index;
+	*pw_adapter_type = hr.u.s.aw_adapter_list[0];
+	return hr.error;
+}
+
+u16 hpi_subsys_set_host_network_interface(const struct hpi_hsubsys *ph_subsys,
+	const char *sz_interface)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_SET_NETWORK_INTERFACE);
+	if (sz_interface == NULL)
+		return HPI_ERROR_INVALID_RESOURCE;
+	hm.u.s.resource.r.net_if = sz_interface;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_adapter_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_OPEN);
+	hm.adapter_index = adapter_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+
+}
+
+u16 hpi_adapter_close(const struct hpi_hsubsys *ph_subsys, u16 adapter_index)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_CLOSE);
+	hm.adapter_index = adapter_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_adapter_set_mode(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u32 adapter_mode)
+{
+	return hpi_adapter_set_mode_ex(ph_subsys, adapter_index, adapter_mode,
+		HPI_ADAPTER_MODE_SET);
+}
+
+u16 hpi_adapter_set_mode_ex(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u32 adapter_mode, u16 query_or_set)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_SET_MODE);
+	hm.adapter_index = adapter_index;
+	hm.u.a.adapter_mode = adapter_mode;
+	hm.u.a.assert_id = query_or_set;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_adapter_get_mode(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u32 *padapter_mode)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_GET_MODE);
+	hm.adapter_index = adapter_index;
+	hpi_send_recv(&hm, &hr);
+	if (padapter_mode)
+		*padapter_mode = hr.u.a.serial_number;
+	return hr.error;
+}
+
+u16 hpi_adapter_get_info(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 *pw_num_outstreams, u16 *pw_num_instreams,
+	u16 *pw_version, u32 *pserial_number, u16 *pw_adapter_type)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_GET_INFO);
+	hm.adapter_index = adapter_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	*pw_adapter_type = hr.u.a.adapter_type;
+	*pw_num_outstreams = hr.u.a.num_outstreams;
+	*pw_num_instreams = hr.u.a.num_instreams;
+	*pw_version = hr.u.a.version;
+	*pserial_number = hr.u.a.serial_number;
+	return hr.error;
+}
+
+u16 hpi_adapter_get_module_by_index(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 module_index, u16 *pw_num_outputs,
+	u16 *pw_num_inputs, u16 *pw_version, u32 *pserial_number,
+	u16 *pw_module_type, u32 *ph_module)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_MODULE_INFO);
+	hm.adapter_index = adapter_index;
+	hm.u.ax.module_info.index = module_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	*pw_module_type = hr.u.a.adapter_type;
+	*pw_num_outputs = hr.u.a.num_outstreams;
+	*pw_num_inputs = hr.u.a.num_instreams;
+	*pw_version = hr.u.a.version;
+	*pserial_number = hr.u.a.serial_number;
+	*ph_module = 0;
+
+	return hr.error;
+}
+
+u16 hpi_adapter_get_assert(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 *assert_present, char *psz_assert,
+	u16 *pw_line_number)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_GET_ASSERT);
+	hm.adapter_index = adapter_index;
+	hpi_send_recv(&hm, &hr);
+
+	*assert_present = 0;
+
+	if (!hr.error) {
+
+		*pw_line_number = (u16)hr.u.a.serial_number;
+		if (*pw_line_number) {
+
+			int i;
+			char *src = (char *)hr.u.a.sz_adapter_assert;
+			char *dst = psz_assert;
+
+			*assert_present = 1;
+
+			for (i = 0; i < HPI_STRING_LEN; i++) {
+				char c;
+				c = *src++;
+				*dst++ = c;
+				if (c == 0)
+					break;
+			}
+
+		}
+	}
+	return hr.error;
+}
+
+u16 hpi_adapter_get_assert_ex(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 *assert_present, char *psz_assert,
+	u32 *pline_number, u16 *pw_assert_on_dsp)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_GET_ASSERT);
+	hm.adapter_index = adapter_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	*assert_present = 0;
+
+	if (!hr.error) {
+
+		*pline_number = hr.u.a.serial_number;
+
+		*assert_present = hr.u.a.adapter_type;
+
+		*pw_assert_on_dsp = hr.u.a.adapter_index;
+
+		if (!*assert_present && *pline_number)
+
+			*assert_present = 1;
+
+		if (*assert_present) {
+
+			int i;
+			char *src = (char *)hr.u.a.sz_adapter_assert;
+			char *dst = psz_assert;
+
+			for (i = 0; i < HPI_STRING_LEN; i++) {
+				char c;
+				c = *src++;
+				*dst++ = c;
+				if (c == 0)
+					break;
+			}
+
+		} else {
+			*psz_assert = 0;
+		}
+	}
+	return hr.error;
+}
+
+u16 hpi_adapter_test_assert(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 assert_id)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_TEST_ASSERT);
+	hm.adapter_index = adapter_index;
+	hm.u.a.assert_id = assert_id;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_adapter_enable_capability(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 capability, u32 key)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_ENABLE_CAPABILITY);
+	hm.adapter_index = adapter_index;
+	hm.u.a.assert_id = capability;
+	hm.u.a.adapter_mode = key;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_adapter_self_test(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_SELFTEST);
+	hm.adapter_index = adapter_index;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_adapter_debug_read(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u32 dsp_address, char *p_buffer, int *count_bytes)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_DEBUG_READ);
+
+	hr.size = sizeof(hr);
+
+	hm.adapter_index = adapter_index;
+	hm.u.ax.debug_read.dsp_address = dsp_address;
+
+	if (*count_bytes > (int)sizeof(hr.u.bytes))
+		*count_bytes = sizeof(hr.u.bytes);
+
+	hm.u.ax.debug_read.count_bytes = *count_bytes;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (!hr.error) {
+		*count_bytes = hr.size - 12;
+		memcpy(p_buffer, &hr.u.bytes, *count_bytes);
+	} else
+		*count_bytes = 0;
+	return hr.error;
+}
+
+u16 hpi_adapter_set_property(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 property, u16 parameter1, u16 parameter2)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_SET_PROPERTY);
+	hm.adapter_index = adapter_index;
+	hm.u.ax.property_set.property = property;
+	hm.u.ax.property_set.parameter1 = parameter1;
+	hm.u.ax.property_set.parameter2 = parameter2;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_adapter_get_property(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 property, u16 *pw_parameter1,
+	u16 *pw_parameter2)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_GET_PROPERTY);
+	hm.adapter_index = adapter_index;
+	hm.u.ax.property_set.property = property;
+
+	hpi_send_recv(&hm, &hr);
+	if (!hr.error) {
+		if (pw_parameter1)
+			*pw_parameter1 = hr.u.ax.property_get.parameter1;
+		if (pw_parameter2)
+			*pw_parameter2 = hr.u.ax.property_get.parameter2;
+	}
+
+	return hr.error;
+}
+
+u16 hpi_adapter_enumerate_property(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 index, u16 what_to_enumerate,
+	u16 property_index, u32 *psetting)
+{
+	return 0;
+}
+
+u16 hpi_format_create(struct hpi_format *p_format, u16 channels, u16 format,
+	u32 sample_rate, u32 bit_rate, u32 attributes)
+{
+	u16 error = 0;
+	struct hpi_msg_format fmt;
+
+	switch (channels) {
+	case 1:
+	case 2:
+	case 4:
+	case 6:
+	case 8:
+	case 16:
+		break;
+	default:
+		error = HPI_ERROR_INVALID_CHANNELS;
+		return error;
+	}
+	fmt.channels = channels;
+
+	switch (format) {
+	case HPI_FORMAT_PCM16_SIGNED:
+	case HPI_FORMAT_PCM24_SIGNED:
+	case HPI_FORMAT_PCM32_SIGNED:
+	case HPI_FORMAT_PCM32_FLOAT:
+	case HPI_FORMAT_PCM16_BIGENDIAN:
+	case HPI_FORMAT_PCM8_UNSIGNED:
+	case HPI_FORMAT_MPEG_L1:
+	case HPI_FORMAT_MPEG_L2:
+	case HPI_FORMAT_MPEG_L3:
+	case HPI_FORMAT_DOLBY_AC2:
+	case HPI_FORMAT_AA_TAGIT1_HITS:
+	case HPI_FORMAT_AA_TAGIT1_INSERTS:
+	case HPI_FORMAT_RAW_BITSTREAM:
+	case HPI_FORMAT_AA_TAGIT1_HITS_EX1:
+	case HPI_FORMAT_OEM1:
+	case HPI_FORMAT_OEM2:
+		break;
+	default:
+		error = HPI_ERROR_INVALID_FORMAT;
+		return error;
+	}
+	fmt.format = format;
+
+	if (sample_rate < 8000L) {
+		error = HPI_ERROR_INCOMPATIBLE_SAMPLERATE;
+		sample_rate = 8000L;
+	}
+	if (sample_rate > 200000L) {
+		error = HPI_ERROR_INCOMPATIBLE_SAMPLERATE;
+		sample_rate = 200000L;
+	}
+	fmt.sample_rate = sample_rate;
+
+	switch (format) {
+	case HPI_FORMAT_MPEG_L1:
+	case HPI_FORMAT_MPEG_L2:
+	case HPI_FORMAT_MPEG_L3:
+		fmt.bit_rate = bit_rate;
+		break;
+	case HPI_FORMAT_PCM16_SIGNED:
+	case HPI_FORMAT_PCM16_BIGENDIAN:
+		fmt.bit_rate = channels * sample_rate * 2;
+		break;
+	case HPI_FORMAT_PCM32_SIGNED:
+	case HPI_FORMAT_PCM32_FLOAT:
+		fmt.bit_rate = channels * sample_rate * 4;
+		break;
+	case HPI_FORMAT_PCM8_UNSIGNED:
+		fmt.bit_rate = channels * sample_rate;
+		break;
+	default:
+		fmt.bit_rate = 0;
+	}
+
+	switch (format) {
+	case HPI_FORMAT_MPEG_L2:
+		if ((channels == 1)
+			&& (attributes != HPI_MPEG_MODE_DEFAULT)) {
+			attributes = HPI_MPEG_MODE_DEFAULT;
+			error = HPI_ERROR_INVALID_FORMAT;
+		} else if (attributes > HPI_MPEG_MODE_DUALCHANNEL) {
+			attributes = HPI_MPEG_MODE_DEFAULT;
+			error = HPI_ERROR_INVALID_FORMAT;
+		}
+		fmt.attributes = attributes;
+		break;
+	default:
+		fmt.attributes = attributes;
+	}
+
+	hpi_msg_to_format(p_format, &fmt);
+	return error;
+}
+
+u16 hpi_stream_estimate_buffer_size(struct hpi_format *p_format,
+	u32 host_polling_rate_in_milli_seconds, u32 *recommended_buffer_size)
+{
+
+	u32 bytes_per_second;
+	u32 size;
+	u16 channels;
+	struct hpi_format *pF = p_format;
+
+	channels = pF->channels;
+
+	switch (pF->format) {
+	case HPI_FORMAT_PCM16_BIGENDIAN:
+	case HPI_FORMAT_PCM16_SIGNED:
+		bytes_per_second = pF->sample_rate * 2L * channels;
+		break;
+	case HPI_FORMAT_PCM24_SIGNED:
+		bytes_per_second = pF->sample_rate * 3L * channels;
+		break;
+	case HPI_FORMAT_PCM32_SIGNED:
+	case HPI_FORMAT_PCM32_FLOAT:
+		bytes_per_second = pF->sample_rate * 4L * channels;
+		break;
+	case HPI_FORMAT_PCM8_UNSIGNED:
+		bytes_per_second = pF->sample_rate * 1L * channels;
+		break;
+	case HPI_FORMAT_MPEG_L1:
+	case HPI_FORMAT_MPEG_L2:
+	case HPI_FORMAT_MPEG_L3:
+		bytes_per_second = pF->bit_rate / 8L;
+		break;
+	case HPI_FORMAT_DOLBY_AC2:
+
+		bytes_per_second = 256000L / 8L;
+		break;
+	default:
+		return HPI_ERROR_INVALID_FORMAT;
+	}
+	size = (bytes_per_second * host_polling_rate_in_milli_seconds * 2) /
+		1000L;
+
+	*recommended_buffer_size =
+		roundup_pow_of_two(((size + 4095L) & ~4095L));
+	return 0;
+}
+
+u16 hpi_outstream_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u16 outstream_index, u32 *ph_outstream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_OPEN);
+	hm.adapter_index = adapter_index;
+	hm.obj_index = outstream_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (hr.error == 0)
+		*ph_outstream =
+			hpi_indexes_to_handle(HPI_OBJ_OSTREAM, adapter_index,
+			outstream_index);
+	else
+		*ph_outstream = 0;
+	return hr.error;
+}
+
+u16 hpi_outstream_close(const struct hpi_hsubsys *ph_subsys, u32 h_outstream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_HOSTBUFFER_FREE);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_GROUP_RESET);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_CLOSE);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_get_info_ex(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u16 *pw_state, u32 *pbuffer_size, u32 *pdata_to_play,
+	u32 *psamples_played, u32 *pauxiliary_data_to_play)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_GET_INFO);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	if (pw_state)
+		*pw_state = hr.u.d.u.stream_info.state;
+	if (pbuffer_size)
+		*pbuffer_size = hr.u.d.u.stream_info.buffer_size;
+	if (pdata_to_play)
+		*pdata_to_play = hr.u.d.u.stream_info.data_available;
+	if (psamples_played)
+		*psamples_played = hr.u.d.u.stream_info.samples_transferred;
+	if (pauxiliary_data_to_play)
+		*pauxiliary_data_to_play =
+			hr.u.d.u.stream_info.auxiliary_data_available;
+	return hr.error;
+}
+
+u16 hpi_outstream_write_buf(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, const u8 *pb_data, u32 bytes_to_write,
+	const struct hpi_format *p_format)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_WRITE);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hm.u.d.u.data.pb_data = (u8 *)pb_data;
+	hm.u.d.u.data.data_size = bytes_to_write;
+
+	hpi_format_to_msg(&hm.u.d.u.data.format, p_format);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_start(const struct hpi_hsubsys *ph_subsys, u32 h_outstream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_START);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_wait_start(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_WAIT_START);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_stop(const struct hpi_hsubsys *ph_subsys, u32 h_outstream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_STOP);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_sinegen(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_SINEGEN);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_reset(const struct hpi_hsubsys *ph_subsys, u32 h_outstream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_RESET);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_query_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, struct hpi_format *p_format)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_QUERY_FORMAT);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_format_to_msg(&hm.u.d.u.data.format, p_format);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_set_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, struct hpi_format *p_format)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_SET_FORMAT);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_format_to_msg(&hm.u.d.u.data.format, p_format);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_set_velocity(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, short velocity)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_SET_VELOCITY);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hm.u.d.u.velocity = velocity;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_set_punch_in_out(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 punch_in_sample, u32 punch_out_sample)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_SET_PUNCHINOUT);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+
+	hm.u.d.u.pio.punch_in_sample = punch_in_sample;
+	hm.u.d.u.pio.punch_out_sample = punch_out_sample;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_ancillary_reset(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u16 mode)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_ANC_RESET);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hm.u.d.u.data.format.channels = mode;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_outstream_ancillary_get_info(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 *pframes_available)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_ANC_GET_INFO);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+	if (hr.error == 0) {
+		if (pframes_available)
+			*pframes_available =
+				hr.u.d.u.stream_info.data_available /
+				sizeof(struct hpi_anc_frame);
+	}
+	return hr.error;
+}
+
+u16 hpi_outstream_ancillary_read(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, struct hpi_anc_frame *p_anc_frame_buffer,
+	u32 anc_frame_buffer_size_in_bytes,
+	u32 number_of_ancillary_frames_to_read)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_ANC_READ);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hm.u.d.u.data.pb_data = (u8 *)p_anc_frame_buffer;
+	hm.u.d.u.data.data_size =
+		number_of_ancillary_frames_to_read *
+		sizeof(struct hpi_anc_frame);
+	if (hm.u.d.u.data.data_size <= anc_frame_buffer_size_in_bytes)
+		hpi_send_recv(&hm, &hr);
+	else
+		hr.error = HPI_ERROR_INVALID_DATA_TRANSFER;
+	return hr.error;
+}
+
+u16 hpi_outstream_set_time_scale(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 time_scale)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_SET_TIMESCALE);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+
+	hm.u.d.u.time_scale = time_scale;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_outstream_host_buffer_allocate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 size_in_bytes)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_HOSTBUFFER_ALLOC);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hm.u.d.u.data.data_size = size_in_bytes;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_outstream_host_buffer_get_info(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u8 **pp_buffer,
+	struct hpi_hostbuffer_status **pp_status)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_HOSTBUFFER_GET_INFO);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	if (hr.error == 0) {
+		if (pp_buffer)
+			*pp_buffer = hr.u.d.u.hostbuffer_info.p_buffer;
+		if (pp_status)
+			*pp_status = hr.u.d.u.hostbuffer_info.p_status;
+	}
+	return hr.error;
+}
+
+u16 hpi_outstream_host_buffer_free(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_HOSTBUFFER_FREE);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_outstream_group_add(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 h_stream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	u16 adapter;
+	char c_obj_type;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_GROUP_ADD);
+	hr.error = 0;
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	c_obj_type = hpi_handle_object(h_stream);
+	switch (c_obj_type) {
+	case HPI_OBJ_OSTREAM:
+		hm.u.d.u.stream.object_type = HPI_OBJ_OSTREAM;
+		u32TOINDEXES(h_stream, &adapter,
+			&hm.u.d.u.stream.stream_index);
+		break;
+	case HPI_OBJ_ISTREAM:
+		hm.u.d.u.stream.object_type = HPI_OBJ_ISTREAM;
+		u32TOINDEXES(h_stream, &adapter,
+			&hm.u.d.u.stream.stream_index);
+		break;
+	default:
+		return HPI_ERROR_INVALID_STREAM;
+	}
+	if (adapter != hm.adapter_index)
+		return HPI_ERROR_NO_INTERADAPTER_GROUPS;
+
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_outstream_group_get_map(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream, u32 *poutstream_map, u32 *pinstream_map)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_GROUP_GETMAP);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	if (poutstream_map)
+		*poutstream_map = hr.u.d.u.group_info.outstream_group_map;
+	if (pinstream_map)
+		*pinstream_map = hr.u.d.u.group_info.instream_group_map;
+
+	return hr.error;
+}
+
+u16 hpi_outstream_group_reset(const struct hpi_hsubsys *ph_subsys,
+	u32 h_outstream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+		HPI_OSTREAM_GROUP_RESET);
+	u32TOINDEXES(h_outstream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_instream_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u16 instream_index, u32 *ph_instream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_OPEN);
+	hm.adapter_index = adapter_index;
+	hm.obj_index = instream_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (hr.error == 0)
+		*ph_instream =
+			hpi_indexes_to_handle(HPI_OBJ_ISTREAM, adapter_index,
+			instream_index);
+	else
+		*ph_instream = 0;
+
+	return hr.error;
+}
+
+u16 hpi_instream_close(const struct hpi_hsubsys *ph_subsys, u32 h_instream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_HOSTBUFFER_FREE);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_GROUP_RESET);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_CLOSE);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_instream_query_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, const struct hpi_format *p_format)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_QUERY_FORMAT);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hpi_format_to_msg(&hm.u.d.u.data.format, p_format);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_instream_set_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, const struct hpi_format *p_format)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_SET_FORMAT);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hpi_format_to_msg(&hm.u.d.u.data.format, p_format);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_instream_read_buf(const struct hpi_hsubsys *ph_subsys, u32 h_instream,
+	u8 *pb_data, u32 bytes_to_read)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_READ);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hm.u.d.u.data.data_size = bytes_to_read;
+	hm.u.d.u.data.pb_data = pb_data;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_instream_start(const struct hpi_hsubsys *ph_subsys, u32 h_instream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_START);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_instream_wait_start(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_WAIT_START);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_instream_stop(const struct hpi_hsubsys *ph_subsys, u32 h_instream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_STOP);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_instream_reset(const struct hpi_hsubsys *ph_subsys, u32 h_instream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_RESET);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_instream_get_info_ex(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u16 *pw_state, u32 *pbuffer_size, u32 *pdata_recorded,
+	u32 *psamples_recorded, u32 *pauxiliary_data_recorded)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_GET_INFO);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	if (pw_state)
+		*pw_state = hr.u.d.u.stream_info.state;
+	if (pbuffer_size)
+		*pbuffer_size = hr.u.d.u.stream_info.buffer_size;
+	if (pdata_recorded)
+		*pdata_recorded = hr.u.d.u.stream_info.data_available;
+	if (psamples_recorded)
+		*psamples_recorded = hr.u.d.u.stream_info.samples_transferred;
+	if (pauxiliary_data_recorded)
+		*pauxiliary_data_recorded =
+			hr.u.d.u.stream_info.auxiliary_data_available;
+	return hr.error;
+}
+
+u16 hpi_instream_ancillary_reset(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u16 bytes_per_frame, u16 mode, u16 alignment,
+	u16 idle_bit)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_ANC_RESET);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hm.u.d.u.data.format.attributes = bytes_per_frame;
+	hm.u.d.u.data.format.format = (mode << 8) | (alignment & 0xff);
+	hm.u.d.u.data.format.channels = idle_bit;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_instream_ancillary_get_info(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u32 *pframe_space)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_ANC_GET_INFO);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+	if (pframe_space)
+		*pframe_space =
+			(hr.u.d.u.stream_info.buffer_size -
+			hr.u.d.u.stream_info.data_available) /
+			sizeof(struct hpi_anc_frame);
+	return hr.error;
+}
+
+u16 hpi_instream_ancillary_write(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, const struct hpi_anc_frame *p_anc_frame_buffer,
+	u32 anc_frame_buffer_size_in_bytes,
+	u32 number_of_ancillary_frames_to_write)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_ANC_WRITE);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hm.u.d.u.data.pb_data = (u8 *)p_anc_frame_buffer;
+	hm.u.d.u.data.data_size =
+		number_of_ancillary_frames_to_write *
+		sizeof(struct hpi_anc_frame);
+	if (hm.u.d.u.data.data_size <= anc_frame_buffer_size_in_bytes)
+		hpi_send_recv(&hm, &hr);
+	else
+		hr.error = HPI_ERROR_INVALID_DATA_TRANSFER;
+	return hr.error;
+}
+
+u16 hpi_instream_host_buffer_allocate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u32 size_in_bytes)
+{
+
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_HOSTBUFFER_ALLOC);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hm.u.d.u.data.data_size = size_in_bytes;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_instream_host_buffer_get_info(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u8 **pp_buffer,
+	struct hpi_hostbuffer_status **pp_status)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_HOSTBUFFER_GET_INFO);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	if (hr.error == 0) {
+		if (pp_buffer)
+			*pp_buffer = hr.u.d.u.hostbuffer_info.p_buffer;
+		if (pp_status)
+			*pp_status = hr.u.d.u.hostbuffer_info.p_status;
+	}
+	return hr.error;
+}
+
+u16 hpi_instream_host_buffer_free(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream)
+{
+
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_HOSTBUFFER_FREE);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_instream_group_add(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u32 h_stream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	u16 adapter;
+	char c_obj_type;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_GROUP_ADD);
+	hr.error = 0;
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	c_obj_type = hpi_handle_object(h_stream);
+
+	switch (c_obj_type) {
+	case HPI_OBJ_OSTREAM:
+		hm.u.d.u.stream.object_type = HPI_OBJ_OSTREAM;
+		u32TOINDEXES(h_stream, &adapter,
+			&hm.u.d.u.stream.stream_index);
+		break;
+	case HPI_OBJ_ISTREAM:
+		hm.u.d.u.stream.object_type = HPI_OBJ_ISTREAM;
+		u32TOINDEXES(h_stream, &adapter,
+			&hm.u.d.u.stream.stream_index);
+		break;
+	default:
+		return HPI_ERROR_INVALID_STREAM;
+	}
+
+	if (adapter != hm.adapter_index)
+		return HPI_ERROR_NO_INTERADAPTER_GROUPS;
+
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_instream_group_get_map(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream, u32 *poutstream_map, u32 *pinstream_map)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_HOSTBUFFER_FREE);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	if (poutstream_map)
+		*poutstream_map = hr.u.d.u.group_info.outstream_group_map;
+	if (pinstream_map)
+		*pinstream_map = hr.u.d.u.group_info.instream_group_map;
+
+	return hr.error;
+}
+
+u16 hpi_instream_group_reset(const struct hpi_hsubsys *ph_subsys,
+	u32 h_instream)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+		HPI_ISTREAM_GROUP_RESET);
+	u32TOINDEXES(h_instream, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_mixer_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u32 *ph_mixer)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN);
+	hm.adapter_index = adapter_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (hr.error == 0)
+		*ph_mixer =
+			hpi_indexes_to_handle(HPI_OBJ_MIXER, adapter_index,
+			0);
+	else
+		*ph_mixer = 0;
+	return hr.error;
+}
+
+u16 hpi_mixer_close(const struct hpi_hsubsys *ph_subsys, u32 h_mixer)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_CLOSE);
+	u32TOINDEX(h_mixer, &hm.adapter_index);
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+u16 hpi_mixer_get_control(const struct hpi_hsubsys *ph_subsys, u32 h_mixer,
+	u16 src_node_type, u16 src_node_type_index, u16 dst_node_type,
+	u16 dst_node_type_index, u16 control_type, u32 *ph_control)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER,
+		HPI_MIXER_GET_CONTROL);
+	u32TOINDEX(h_mixer, &hm.adapter_index);
+	hm.u.m.node_type1 = src_node_type;
+	hm.u.m.node_index1 = src_node_type_index;
+	hm.u.m.node_type2 = dst_node_type;
+	hm.u.m.node_index2 = dst_node_type_index;
+	hm.u.m.control_type = control_type;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (hr.error == 0)
+		*ph_control =
+			hpi_indexes_to_handle(HPI_OBJ_CONTROL,
+			hm.adapter_index, hr.u.m.control_index);
+	else
+		*ph_control = 0;
+	return hr.error;
+}
+
+u16 hpi_mixer_get_control_by_index(const struct hpi_hsubsys *ph_subsys,
+	u32 h_mixer, u16 control_index, u16 *pw_src_node_type,
+	u16 *pw_src_node_index, u16 *pw_dst_node_type, u16 *pw_dst_node_index,
+	u16 *pw_control_type, u32 *ph_control)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER,
+		HPI_MIXER_GET_CONTROL_BY_INDEX);
+	u32TOINDEX(h_mixer, &hm.adapter_index);
+	hm.u.m.control_index = control_index;
+	hpi_send_recv(&hm, &hr);
+
+	if (pw_src_node_type) {
+		*pw_src_node_type =
+			hr.u.m.src_node_type + HPI_SOURCENODE_NONE;
+		*pw_src_node_index = hr.u.m.src_node_index;
+		*pw_dst_node_type = hr.u.m.dst_node_type + HPI_DESTNODE_NONE;
+		*pw_dst_node_index = hr.u.m.dst_node_index;
+	}
+	if (pw_control_type)
+		*pw_control_type = hr.u.m.control_index;
+
+	if (ph_control) {
+		if (hr.error == 0)
+			*ph_control =
+				hpi_indexes_to_handle(HPI_OBJ_CONTROL,
+				hm.adapter_index, control_index);
+		else
+			*ph_control = 0;
+	}
+	return hr.error;
+}
+
+u16 hpi_mixer_store(const struct hpi_hsubsys *ph_subsys, u32 h_mixer,
+	enum HPI_MIXER_STORE_COMMAND command, u16 index)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_STORE);
+	u32TOINDEX(h_mixer, &hm.adapter_index);
+	hm.u.mx.store.command = command;
+	hm.u.mx.store.index = index;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+static
+u16 hpi_control_param_set(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_control, const u16 attrib, const u32 param1,
+	const u32 param2)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_SET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = attrib;
+	hm.u.c.param1 = param1;
+	hm.u.c.param2 = param2;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+static u16 hpi_control_log_set2(u32 h_control, u16 attrib, short sv0,
+	short sv1)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_SET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = attrib;
+	hm.u.c.an_log_value[0] = sv0;
+	hm.u.c.an_log_value[1] = sv1;
+	hpi_send_recv(&hm, &hr);
+	return hr.error;
+}
+
+static
+u16 hpi_control_param_get(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_control, const u16 attrib, u32 param1, u32 param2,
+	u32 *pparam1, u32 *pparam2)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = attrib;
+	hm.u.c.param1 = param1;
+	hm.u.c.param2 = param2;
+	hpi_send_recv(&hm, &hr);
+
+	*pparam1 = hr.u.c.param1;
+	if (pparam2)
+		*pparam2 = hr.u.c.param2;
+
+	return hr.error;
+}
+
+#define hpi_control_param1_get(s, h, a, p1) \
+		hpi_control_param_get(s, h, a, 0, 0, p1, NULL)
+#define hpi_control_param2_get(s, h, a, p1, p2) \
+		hpi_control_param_get(s, h, a, 0, 0, p1, p2)
+
+static u16 hpi_control_log_get2(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 attrib, short *sv0, short *sv1)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = attrib;
+
+	hpi_send_recv(&hm, &hr);
+	*sv0 = hr.u.c.an_log_value[0];
+	if (sv1)
+		*sv1 = hr.u.c.an_log_value[1];
+	return hr.error;
+}
+
+static
+u16 hpi_control_query(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_control, const u16 attrib, const u32 index,
+	const u32 param, u32 *psetting)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_INFO);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+
+	hm.u.c.attribute = attrib;
+	hm.u.c.param1 = index;
+	hm.u.c.param2 = param;
+
+	hpi_send_recv(&hm, &hr);
+	*psetting = hr.u.c.param1;
+
+	return hr.error;
+}
+
+static u16 hpi_control_get_string(const u32 h_control, const u16 attribute,
+	char *psz_string, const u32 string_length)
+{
+	unsigned int sub_string_index = 0, j = 0;
+	char c = 0;
+	unsigned int n = 0;
+	u16 hE = 0;
+
+	if ((string_length < 1) || (string_length > 256))
+		return HPI_ERROR_INVALID_CONTROL_VALUE;
+	for (sub_string_index = 0; sub_string_index < string_length;
+		sub_string_index += 8) {
+		struct hpi_message hm;
+		struct hpi_response hr;
+
+		hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+			HPI_CONTROL_GET_STATE);
+		u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+		hm.u.c.attribute = attribute;
+		hm.u.c.param1 = sub_string_index;
+		hm.u.c.param2 = 0;
+		hpi_send_recv(&hm, &hr);
+
+		if (sub_string_index == 0
+			&& (hr.u.cu.chars8.remaining_chars + 8) >
+			string_length)
+			return HPI_ERROR_INVALID_CONTROL_VALUE;
+
+		if (hr.error) {
+			hE = hr.error;
+			break;
+		}
+		for (j = 0; j < 8; j++) {
+			c = hr.u.cu.chars8.sz_data[j];
+			psz_string[sub_string_index + j] = c;
+			n++;
+			if (n >= string_length) {
+				psz_string[string_length - 1] = 0;
+				hE = HPI_ERROR_INVALID_CONTROL_VALUE;
+				break;
+			}
+			if (c == 0)
+				break;
+		}
+
+		if ((hr.u.cu.chars8.remaining_chars == 0)
+			&& ((sub_string_index + j) < string_length)
+			&& (c != 0)) {
+			c = 0;
+			psz_string[sub_string_index + j] = c;
+		}
+		if (c == 0)
+			break;
+	}
+	return hE;
+}
+
+u16 HPI_AESEBU__receiver_query_format(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_aes_rx, const u32 index, u16 *pw_format)
+{
+	u32 qr;
+	u16 err;
+
+	err = hpi_control_query(ph_subsys, h_aes_rx, HPI_AESEBURX_FORMAT,
+		index, 0, &qr);
+	*pw_format = (u16)qr;
+	return err;
+}
+
+u16 HPI_AESEBU__receiver_set_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 format)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_AESEBURX_FORMAT, format, 0);
+}
+
+u16 HPI_AESEBU__receiver_get_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_format)
+{
+	u16 err;
+	u32 param;
+
+	err = hpi_control_param1_get(ph_subsys, h_control,
+		HPI_AESEBURX_FORMAT, &param);
+	if (!err && pw_format)
+		*pw_format = (u16)param;
+
+	return err;
+}
+
+u16 HPI_AESEBU__receiver_get_sample_rate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *psample_rate)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_AESEBURX_SAMPLERATE, psample_rate);
+}
+
+u16 HPI_AESEBU__receiver_get_user_data(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, u16 *pw_data)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_AESEBURX_USERDATA;
+	hm.u.c.param1 = index;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (pw_data)
+		*pw_data = (u16)hr.u.c.param2;
+	return hr.error;
+}
+
+u16 HPI_AESEBU__receiver_get_channel_status(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, u16 index, u16 *pw_data)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_AESEBURX_CHANNELSTATUS;
+	hm.u.c.param1 = index;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (pw_data)
+		*pw_data = (u16)hr.u.c.param2;
+	return hr.error;
+}
+
+u16 HPI_AESEBU__receiver_get_error_status(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_error_data)
+{
+	u32 error_data = 0;
+	u16 error = 0;
+
+	error = hpi_control_param1_get(ph_subsys, h_control,
+		HPI_AESEBURX_ERRORSTATUS, &error_data);
+	if (pw_error_data)
+		*pw_error_data = (u16)error_data;
+	return error;
+}
+
+u16 HPI_AESEBU__transmitter_set_sample_rate(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, u32 sample_rate)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_AESEBUTX_SAMPLERATE, sample_rate, 0);
+}
+
+u16 HPI_AESEBU__transmitter_set_user_data(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, u16 data)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_AESEBUTX_USERDATA, index, data);
+}
+
+u16 HPI_AESEBU__transmitter_set_channel_status(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, u16 index, u16 data)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_AESEBUTX_CHANNELSTATUS, index, data);
+}
+
+u16 HPI_AESEBU__transmitter_get_channel_status(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, u16 index, u16 *pw_data)
+{
+	return HPI_ERROR_INVALID_OPERATION;
+}
+
+u16 HPI_AESEBU__transmitter_query_format(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_aes_tx, const u32 index, u16 *pw_format)
+{
+	u32 qr;
+	u16 err;
+
+	err = hpi_control_query(ph_subsys, h_aes_tx, HPI_AESEBUTX_FORMAT,
+		index, 0, &qr);
+	*pw_format = (u16)qr;
+	return err;
+}
+
+u16 HPI_AESEBU__transmitter_set_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 output_format)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_AESEBUTX_FORMAT, output_format, 0);
+}
+
+u16 HPI_AESEBU__transmitter_get_format(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_output_format)
+{
+	u16 err;
+	u32 param;
+
+	err = hpi_control_param1_get(ph_subsys, h_control,
+		HPI_AESEBUTX_FORMAT, &param);
+	if (!err && pw_output_format)
+		*pw_output_format = (u16)param;
+
+	return err;
+}
+
+u16 hpi_bitstream_set_clock_edge(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 edge_type)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_BITSTREAM_CLOCK_EDGE, edge_type, 0);
+}
+
+u16 hpi_bitstream_set_data_polarity(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 polarity)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_BITSTREAM_DATA_POLARITY, polarity, 0);
+}
+
+u16 hpi_bitstream_get_activity(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_clk_activity, u16 *pw_data_activity)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_BITSTREAM_ACTIVITY;
+	hpi_send_recv(&hm, &hr);
+	if (pw_clk_activity)
+		*pw_clk_activity = (u16)hr.u.c.param1;
+	if (pw_data_activity)
+		*pw_data_activity = (u16)hr.u.c.param2;
+	return hr.error;
+}
+
+u16 hpi_channel_mode_query_mode(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_mode, const u32 index, u16 *pw_mode)
+{
+	u32 qr;
+	u16 err;
+
+	err = hpi_control_query(ph_subsys, h_mode, HPI_CHANNEL_MODE_MODE,
+		index, 0, &qr);
+	*pw_mode = (u16)qr;
+	return err;
+}
+
+u16 hpi_channel_mode_set(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u16 mode)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_CHANNEL_MODE_MODE, mode, 0);
+}
+
+u16 hpi_channel_mode_get(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u16 *mode)
+{
+	u32 mode32 = 0;
+	u16 error = hpi_control_param1_get(ph_subsys, h_control,
+		HPI_CHANNEL_MODE_MODE, &mode32);
+	if (mode)
+		*mode = (u16)mode32;
+	return error;
+}
+
+u16 hpi_cobranet_hmi_write(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 hmi_address, u32 byte_count, u8 *pb_data)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROLEX,
+		HPI_CONTROL_SET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+
+	hm.u.cx.u.cobranet_data.byte_count = byte_count;
+	hm.u.cx.u.cobranet_data.hmi_address = hmi_address;
+
+	if (byte_count <= 8) {
+		memcpy(hm.u.cx.u.cobranet_data.data, pb_data, byte_count);
+		hm.u.cx.attribute = HPI_COBRANET_SET;
+	} else {
+		hm.u.cx.u.cobranet_bigdata.pb_data = pb_data;
+		hm.u.cx.attribute = HPI_COBRANET_SET_DATA;
+	}
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_cobranet_hmi_read(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 hmi_address, u32 max_byte_count, u32 *pbyte_count, u8 *pb_data)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROLEX,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+
+	hm.u.cx.u.cobranet_data.byte_count = max_byte_count;
+	hm.u.cx.u.cobranet_data.hmi_address = hmi_address;
+
+	if (max_byte_count <= 8) {
+		hm.u.cx.attribute = HPI_COBRANET_GET;
+	} else {
+		hm.u.cx.u.cobranet_bigdata.pb_data = pb_data;
+		hm.u.cx.attribute = HPI_COBRANET_GET_DATA;
+	}
+
+	hpi_send_recv(&hm, &hr);
+	if (!hr.error && pb_data) {
+
+		*pbyte_count = hr.u.cx.u.cobranet_data.byte_count;
+
+		if (*pbyte_count < max_byte_count)
+			max_byte_count = *pbyte_count;
+
+		if (hm.u.cx.attribute == HPI_COBRANET_GET) {
+			memcpy(pb_data, hr.u.cx.u.cobranet_data.data,
+				max_byte_count);
+		} else {
+
+		}
+
+	}
+	return hr.error;
+}
+
+u16 hpi_cobranet_hmi_get_status(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pstatus, u32 *preadable_size,
+	u32 *pwriteable_size)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROLEX,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+
+	hm.u.cx.attribute = HPI_COBRANET_GET_STATUS;
+
+	hpi_send_recv(&hm, &hr);
+	if (!hr.error) {
+		if (pstatus)
+			*pstatus = hr.u.cx.u.cobranet_status.status;
+		if (preadable_size)
+			*preadable_size =
+				hr.u.cx.u.cobranet_status.readable_size;
+		if (pwriteable_size)
+			*pwriteable_size =
+				hr.u.cx.u.cobranet_status.writeable_size;
+	}
+	return hr.error;
+}
+
+u16 hpi_cobranet_getI_paddress(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pi_paddress)
+{
+	u32 byte_count;
+	u32 iP;
+	u16 error;
+
+	error = hpi_cobranet_hmi_read(ph_subsys, h_control,
+		HPI_COBRANET_HMI_cobra_ip_mon_currentIP, 4, &byte_count,
+		(u8 *)&iP);
+
+	*pi_paddress =
+		((iP & 0xff000000) >> 8) | ((iP & 0x00ff0000) << 8) | ((iP &
+			0x0000ff00) >> 8) | ((iP & 0x000000ff) << 8);
+
+	if (error)
+		*pi_paddress = 0;
+
+	return error;
+
+}
+
+u16 hpi_cobranet_setI_paddress(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 i_paddress)
+{
+	u32 iP;
+	u16 error;
+
+	iP = ((i_paddress & 0xff000000) >> 8) | ((i_paddress & 0x00ff0000) <<
+		8) | ((i_paddress & 0x0000ff00) >> 8) | ((i_paddress &
+			0x000000ff) << 8);
+
+	error = hpi_cobranet_hmi_write(ph_subsys, h_control,
+		HPI_COBRANET_HMI_cobra_ip_mon_currentIP, 4, (u8 *)&iP);
+
+	return error;
+
+}
+
+u16 hpi_cobranet_get_staticI_paddress(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pi_paddress)
+{
+	u32 byte_count;
+	u32 iP;
+	u16 error;
+	error = hpi_cobranet_hmi_read(ph_subsys, h_control,
+		HPI_COBRANET_HMI_cobra_ip_mon_staticIP, 4, &byte_count,
+		(u8 *)&iP);
+
+	*pi_paddress =
+		((iP & 0xff000000) >> 8) | ((iP & 0x00ff0000) << 8) | ((iP &
+			0x0000ff00) >> 8) | ((iP & 0x000000ff) << 8);
+
+	if (error)
+		*pi_paddress = 0;
+
+	return error;
+
+}
+
+u16 hpi_cobranet_set_staticI_paddress(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 i_paddress)
+{
+	u32 iP;
+	u16 error;
+
+	iP = ((i_paddress & 0xff000000) >> 8) | ((i_paddress & 0x00ff0000) <<
+		8) | ((i_paddress & 0x0000ff00) >> 8) | ((i_paddress &
+			0x000000ff) << 8);
+
+	error = hpi_cobranet_hmi_write(ph_subsys, h_control,
+		HPI_COBRANET_HMI_cobra_ip_mon_staticIP, 4, (u8 *)&iP);
+
+	return error;
+
+}
+
+u16 hpi_cobranet_getMA_caddress(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pmAC_MS_bs, u32 *pmAC_LS_bs)
+{
+	u32 byte_count;
+	u16 error;
+	u32 mAC;
+
+	error = hpi_cobranet_hmi_read(ph_subsys, h_control,
+		HPI_COBRANET_HMI_cobra_if_phy_address, 4, &byte_count,
+		(u8 *)&mAC);
+	*pmAC_MS_bs =
+		((mAC & 0xff000000) >> 8) | ((mAC & 0x00ff0000) << 8) | ((mAC
+			& 0x0000ff00) >> 8) | ((mAC & 0x000000ff) << 8);
+	error += hpi_cobranet_hmi_read(ph_subsys, h_control,
+		HPI_COBRANET_HMI_cobra_if_phy_address + 1, 4, &byte_count,
+		(u8 *)&mAC);
+	*pmAC_LS_bs =
+		((mAC & 0xff000000) >> 8) | ((mAC & 0x00ff0000) << 8) | ((mAC
+			& 0x0000ff00) >> 8) | ((mAC & 0x000000ff) << 8);
+
+	if (error) {
+		*pmAC_MS_bs = 0;
+		*pmAC_LS_bs = 0;
+	}
+
+	return error;
+}
+
+u16 hpi_compander_set_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 enable)
+{
+	return hpi_control_param_set(ph_subsys, h_control, HPI_GENERIC_ENABLE,
+		enable, 0);
+}
+
+u16 hpi_compander_get_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *enable)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_GENERIC_ENABLE, enable);
+}
+
+u16 hpi_compander_set_makeup_gain(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, short makeup_gain0_01dB)
+{
+	return hpi_control_log_set2(h_control, HPI_COMPANDER_MAKEUPGAIN,
+		makeup_gain0_01dB, 0);
+}
+
+u16 hpi_compander_get_makeup_gain(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, short *makeup_gain0_01dB)
+{
+	return hpi_control_log_get2(ph_subsys, h_control,
+		HPI_COMPANDER_MAKEUPGAIN, makeup_gain0_01dB, NULL);
+}
+
+u16 hpi_compander_set_attack_time_constant(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, unsigned int index, u32 attack)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_COMPANDER_ATTACK, attack, index);
+}
+
+u16 hpi_compander_get_attack_time_constant(const struct hpi_hsubsys
+	*ph_subsys, u32 h_control, unsigned int index, u32 *attack)
+{
+	return hpi_control_param_get(ph_subsys, h_control,
+		HPI_COMPANDER_ATTACK, 0, index, attack, NULL);
+}
+
+u16 hpi_compander_set_decay_time_constant(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, unsigned int index, u32 decay)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_COMPANDER_DECAY, decay, index);
+}
+
+u16 hpi_compander_get_decay_time_constant(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, unsigned int index, u32 *decay)
+{
+	return hpi_control_param_get(ph_subsys, h_control,
+		HPI_COMPANDER_DECAY, 0, index, decay, NULL);
+
+}
+
+u16 hpi_compander_set_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, unsigned int index, short threshold0_01dB)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_SET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_COMPANDER_THRESHOLD;
+	hm.u.c.param2 = index;
+	hm.u.c.an_log_value[0] = threshold0_01dB;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_compander_get_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, unsigned int index, short *threshold0_01dB)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_COMPANDER_THRESHOLD;
+	hm.u.c.param2 = index;
+
+	hpi_send_recv(&hm, &hr);
+	*threshold0_01dB = hr.u.c.an_log_value[0];
+
+	return hr.error;
+}
+
+u16 hpi_compander_set_ratio(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 index, u32 ratio100)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_COMPANDER_RATIO, ratio100, index);
+}
+
+u16 hpi_compander_get_ratio(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 index, u32 *ratio100)
+{
+	return hpi_control_param_get(ph_subsys, h_control,
+		HPI_COMPANDER_RATIO, 0, index, ratio100, NULL);
+}
+
+u16 hpi_level_query_range(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short *min_gain_01dB, short *max_gain_01dB, short *step_gain_01dB)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_LEVEL_RANGE;
+
+	hpi_send_recv(&hm, &hr);
+	if (hr.error) {
+		hr.u.c.an_log_value[0] = 0;
+		hr.u.c.an_log_value[1] = 0;
+		hr.u.c.param1 = 0;
+	}
+	if (min_gain_01dB)
+		*min_gain_01dB = hr.u.c.an_log_value[0];
+	if (max_gain_01dB)
+		*max_gain_01dB = hr.u.c.an_log_value[1];
+	if (step_gain_01dB)
+		*step_gain_01dB = (short)hr.u.c.param1;
+	return hr.error;
+}
+
+u16 hpi_level_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_gain0_01dB[HPI_MAX_CHANNELS]
+	)
+{
+	return hpi_control_log_set2(h_control, HPI_LEVEL_GAIN,
+		an_gain0_01dB[0], an_gain0_01dB[1]);
+}
+
+u16 hpi_level_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_gain0_01dB[HPI_MAX_CHANNELS]
+	)
+{
+	return hpi_control_log_get2(ph_subsys, h_control, HPI_LEVEL_GAIN,
+		&an_gain0_01dB[0], &an_gain0_01dB[1]);
+}
+
+u16 hpi_meter_query_channels(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_meter, u32 *p_channels)
+{
+	return hpi_control_query(ph_subsys, h_meter, HPI_METER_NUM_CHANNELS,
+		0, 0, p_channels);
+}
+
+u16 hpi_meter_get_peak(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_peakdB[HPI_MAX_CHANNELS]
+	)
+{
+	short i = 0;
+
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.obj_index = hm.obj_index;
+	hm.u.c.attribute = HPI_METER_PEAK;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (!hr.error)
+		memcpy(an_peakdB, hr.u.c.an_log_value,
+			sizeof(short) * HPI_MAX_CHANNELS);
+	else
+		for (i = 0; i < HPI_MAX_CHANNELS; i++)
+			an_peakdB[i] = HPI_METER_MINIMUM;
+	return hr.error;
+}
+
+u16 hpi_meter_get_rms(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_rmsdB[HPI_MAX_CHANNELS]
+	)
+{
+	short i = 0;
+
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_METER_RMS;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (!hr.error)
+		memcpy(an_rmsdB, hr.u.c.an_log_value,
+			sizeof(short) * HPI_MAX_CHANNELS);
+	else
+		for (i = 0; i < HPI_MAX_CHANNELS; i++)
+			an_rmsdB[i] = HPI_METER_MINIMUM;
+
+	return hr.error;
+}
+
+u16 hpi_meter_set_rms_ballistics(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 attack, u16 decay)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_METER_RMS_BALLISTICS, attack, decay);
+}
+
+u16 hpi_meter_get_rms_ballistics(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pn_attack, u16 *pn_decay)
+{
+	u32 attack;
+	u32 decay;
+	u16 error;
+
+	error = hpi_control_param2_get(ph_subsys, h_control,
+		HPI_METER_RMS_BALLISTICS, &attack, &decay);
+
+	if (pn_attack)
+		*pn_attack = (unsigned short)attack;
+	if (pn_decay)
+		*pn_decay = (unsigned short)decay;
+
+	return error;
+}
+
+u16 hpi_meter_set_peak_ballistics(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 attack, u16 decay)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_METER_PEAK_BALLISTICS, attack, decay);
+}
+
+u16 hpi_meter_get_peak_ballistics(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pn_attack, u16 *pn_decay)
+{
+	u32 attack;
+	u32 decay;
+	u16 error;
+
+	error = hpi_control_param2_get(ph_subsys, h_control,
+		HPI_METER_PEAK_BALLISTICS, &attack, &decay);
+
+	if (pn_attack)
+		*pn_attack = (short)attack;
+	if (pn_decay)
+		*pn_decay = (short)decay;
+
+	return error;
+}
+
+u16 hpi_microphone_set_phantom_power(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 on_off)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_MICROPHONE_PHANTOM_POWER, (u32)on_off, 0);
+}
+
+u16 hpi_microphone_get_phantom_power(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_on_off)
+{
+	u16 error = 0;
+	u32 on_off = 0;
+	error = hpi_control_param1_get(ph_subsys, h_control,
+		HPI_MICROPHONE_PHANTOM_POWER, &on_off);
+	if (pw_on_off)
+		*pw_on_off = (u16)on_off;
+	return error;
+}
+
+u16 hpi_multiplexer_set_source(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 source_node_type, u16 source_node_index)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_MULTIPLEXER_SOURCE, source_node_type, source_node_index);
+}
+
+u16 hpi_multiplexer_get_source(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *source_node_type, u16 *source_node_index)
+{
+	u32 node, index;
+	u16 error = hpi_control_param2_get(ph_subsys, h_control,
+		HPI_MULTIPLEXER_SOURCE, &node,
+		&index);
+	if (source_node_type)
+		*source_node_type = (u16)node;
+	if (source_node_index)
+		*source_node_index = (u16)index;
+	return error;
+}
+
+u16 hpi_multiplexer_query_source(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, u16 *source_node_type,
+	u16 *source_node_index)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_MULTIPLEXER_QUERYSOURCE;
+	hm.u.c.param1 = index;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (source_node_type)
+		*source_node_type = (u16)hr.u.c.param1;
+	if (source_node_index)
+		*source_node_index = (u16)hr.u.c.param2;
+	return hr.error;
+}
+
+u16 hpi_parametricEQ__get_info(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_number_of_bands, u16 *pw_on_off)
+{
+	u32 oB = 0;
+	u32 oO = 0;
+	u16 error = 0;
+
+	error = hpi_control_param2_get(ph_subsys, h_control,
+		HPI_EQUALIZER_NUM_FILTERS, &oO, &oB);
+	if (pw_number_of_bands)
+		*pw_number_of_bands = (u16)oB;
+	if (pw_on_off)
+		*pw_on_off = (u16)oO;
+	return error;
+}
+
+u16 hpi_parametricEQ__set_state(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 on_off)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_EQUALIZER_NUM_FILTERS, on_off, 0);
+}
+
+u16 hpi_parametricEQ__get_band(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, u16 *pn_type, u32 *pfrequency_hz,
+	short *pnQ100, short *pn_gain0_01dB)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_EQUALIZER_FILTER;
+	hm.u.c.param2 = index;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (pfrequency_hz)
+		*pfrequency_hz = hr.u.c.param1;
+	if (pn_type)
+		*pn_type = (u16)(hr.u.c.param2 >> 16);
+	if (pnQ100)
+		*pnQ100 = hr.u.c.an_log_value[1];
+	if (pn_gain0_01dB)
+		*pn_gain0_01dB = hr.u.c.an_log_value[0];
+
+	return hr.error;
+}
+
+u16 hpi_parametricEQ__set_band(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, u16 type, u32 frequency_hz, short q100,
+	short gain0_01dB)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_SET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+
+	hm.u.c.param1 = frequency_hz;
+	hm.u.c.param2 = (index & 0xFFFFL) + ((u32)type << 16);
+	hm.u.c.an_log_value[0] = gain0_01dB;
+	hm.u.c.an_log_value[1] = q100;
+	hm.u.c.attribute = HPI_EQUALIZER_FILTER;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_parametricEQ__get_coeffs(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 index, short coeffs[5]
+	)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_EQUALIZER_COEFFICIENTS;
+	hm.u.c.param2 = index;
+
+	hpi_send_recv(&hm, &hr);
+
+	coeffs[0] = (short)hr.u.c.an_log_value[0];
+	coeffs[1] = (short)hr.u.c.an_log_value[1];
+	coeffs[2] = (short)hr.u.c.param1;
+	coeffs[3] = (short)(hr.u.c.param1 >> 16);
+	coeffs[4] = (short)hr.u.c.param2;
+
+	return hr.error;
+}
+
+u16 hpi_sample_clock_query_source(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_clock, const u32 index, u16 *pw_source)
+{
+	u32 qr;
+	u16 err;
+
+	err = hpi_control_query(ph_subsys, h_clock, HPI_SAMPLECLOCK_SOURCE,
+		index, 0, &qr);
+	*pw_source = (u16)qr;
+	return err;
+}
+
+u16 hpi_sample_clock_set_source(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 source)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_SAMPLECLOCK_SOURCE, source, 0);
+}
+
+u16 hpi_sample_clock_get_source(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_source)
+{
+	u16 error = 0;
+	u32 source = 0;
+	error = hpi_control_param1_get(ph_subsys, h_control,
+		HPI_SAMPLECLOCK_SOURCE, &source);
+	if (!error)
+		if (pw_source)
+			*pw_source = (u16)source;
+	return error;
+}
+
+u16 hpi_sample_clock_query_source_index(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_clock, const u32 index, const u32 source,
+	u16 *pw_source_index)
+{
+	u32 qr;
+	u16 err;
+
+	err = hpi_control_query(ph_subsys, h_clock,
+		HPI_SAMPLECLOCK_SOURCE_INDEX, index, source, &qr);
+	*pw_source_index = (u16)qr;
+	return err;
+}
+
+u16 hpi_sample_clock_set_source_index(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 source_index)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_SAMPLECLOCK_SOURCE_INDEX, source_index, 0);
+}
+
+u16 hpi_sample_clock_get_source_index(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u16 *pw_source_index)
+{
+	u16 error = 0;
+	u32 source_index = 0;
+	error = hpi_control_param1_get(ph_subsys, h_control,
+		HPI_SAMPLECLOCK_SOURCE_INDEX, &source_index);
+	if (!error)
+		if (pw_source_index)
+			*pw_source_index = (u16)source_index;
+	return error;
+}
+
+u16 hpi_sample_clock_query_local_rate(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_clock, const u32 index, u32 *prate)
+{
+	u16 err;
+	err = hpi_control_query(ph_subsys, h_clock,
+		HPI_SAMPLECLOCK_LOCAL_SAMPLERATE, index, 0, prate);
+
+	return err;
+}
+
+u16 hpi_sample_clock_set_local_rate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 sample_rate)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_SAMPLECLOCK_LOCAL_SAMPLERATE, sample_rate, 0);
+}
+
+u16 hpi_sample_clock_get_local_rate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *psample_rate)
+{
+	u16 error = 0;
+	u32 sample_rate = 0;
+	error = hpi_control_param1_get(ph_subsys, h_control,
+		HPI_SAMPLECLOCK_LOCAL_SAMPLERATE, &sample_rate);
+	if (!error)
+		if (psample_rate)
+			*psample_rate = sample_rate;
+	return error;
+}
+
+u16 hpi_sample_clock_get_sample_rate(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *psample_rate)
+{
+	u16 error = 0;
+	u32 sample_rate = 0;
+	error = hpi_control_param1_get(ph_subsys, h_control,
+		HPI_SAMPLECLOCK_SAMPLERATE, &sample_rate);
+	if (!error)
+		if (psample_rate)
+			*psample_rate = sample_rate;
+	return error;
+}
+
+u16 hpi_sample_clock_set_auto(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 enable)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_SAMPLECLOCK_AUTO, enable, 0);
+}
+
+u16 hpi_sample_clock_get_auto(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *penable)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_SAMPLECLOCK_AUTO, penable);
+}
+
+u16 hpi_sample_clock_set_local_rate_lock(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 lock)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_SAMPLECLOCK_LOCAL_LOCK, lock, 0);
+}
+
+u16 hpi_sample_clock_get_local_rate_lock(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *plock)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_SAMPLECLOCK_LOCAL_LOCK, plock);
+}
+
+u16 hpi_tone_detector_get_frequency(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 index, u32 *frequency)
+{
+	return hpi_control_param_get(ph_subsys, h_control,
+		HPI_TONEDETECTOR_FREQUENCY, index, 0, frequency, NULL);
+}
+
+u16 hpi_tone_detector_get_state(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *state)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_TONEDETECTOR_STATE, state);
+}
+
+u16 hpi_tone_detector_set_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 enable)
+{
+	return hpi_control_param_set(ph_subsys, h_control, HPI_GENERIC_ENABLE,
+		(u32)enable, 0);
+}
+
+u16 hpi_tone_detector_get_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *enable)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_GENERIC_ENABLE, enable);
+}
+
+u16 hpi_tone_detector_set_event_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 event_enable)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_GENERIC_EVENT_ENABLE, (u32)event_enable, 0);
+}
+
+u16 hpi_tone_detector_get_event_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *event_enable)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_GENERIC_EVENT_ENABLE, event_enable);
+}
+
+u16 hpi_tone_detector_set_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, int threshold)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_TONEDETECTOR_THRESHOLD, (u32)threshold, 0);
+}
+
+u16 hpi_tone_detector_get_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, int *threshold)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_TONEDETECTOR_THRESHOLD, (u32 *)threshold);
+}
+
+u16 hpi_silence_detector_get_state(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *state)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_SILENCEDETECTOR_STATE, state);
+}
+
+u16 hpi_silence_detector_set_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 enable)
+{
+	return hpi_control_param_set(ph_subsys, h_control, HPI_GENERIC_ENABLE,
+		(u32)enable, 0);
+}
+
+u16 hpi_silence_detector_get_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *enable)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_GENERIC_ENABLE, enable);
+}
+
+u16 hpi_silence_detector_set_event_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 event_enable)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_GENERIC_EVENT_ENABLE, event_enable, 0);
+}
+
+u16 hpi_silence_detector_get_event_enable(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *event_enable)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_GENERIC_EVENT_ENABLE, event_enable);
+}
+
+u16 hpi_silence_detector_set_delay(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 delay)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_SILENCEDETECTOR_DELAY, delay, 0);
+}
+
+u16 hpi_silence_detector_get_delay(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *delay)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_SILENCEDETECTOR_DELAY, delay);
+}
+
+u16 hpi_silence_detector_set_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, int threshold)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_SILENCEDETECTOR_THRESHOLD, threshold, 0);
+}
+
+u16 hpi_silence_detector_get_threshold(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, int *threshold)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_SILENCEDETECTOR_THRESHOLD, (u32 *)threshold);
+}
+
+u16 hpi_tuner_query_band(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_tuner, const u32 index, u16 *pw_band)
+{
+	u32 qr;
+	u16 err;
+
+	err = hpi_control_query(ph_subsys, h_tuner, HPI_TUNER_BAND, index, 0,
+		&qr);
+	*pw_band = (u16)qr;
+	return err;
+}
+
+u16 hpi_tuner_set_band(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u16 band)
+{
+	return hpi_control_param_set(ph_subsys, h_control, HPI_TUNER_BAND,
+		band, 0);
+}
+
+u16 hpi_tuner_get_band(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u16 *pw_band)
+{
+	u32 band = 0;
+	u16 error = 0;
+
+	error = hpi_control_param1_get(ph_subsys, h_control, HPI_TUNER_BAND,
+		&band);
+	if (pw_band)
+		*pw_band = (u16)band;
+	return error;
+}
+
+u16 hpi_tuner_query_frequency(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_tuner, const u32 index, const u16 band, u32 *pfreq)
+{
+	return hpi_control_query(ph_subsys, h_tuner, HPI_TUNER_FREQ, index,
+		band, pfreq);
+}
+
+u16 hpi_tuner_set_frequency(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 freq_ink_hz)
+{
+	return hpi_control_param_set(ph_subsys, h_control, HPI_TUNER_FREQ,
+		freq_ink_hz, 0);
+}
+
+u16 hpi_tuner_get_frequency(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pw_freq_ink_hz)
+{
+	return hpi_control_param1_get(ph_subsys, h_control, HPI_TUNER_FREQ,
+		pw_freq_ink_hz);
+}
+
+u16 hpi_tuner_query_gain(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_tuner, const u32 index, u16 *pw_gain)
+{
+	u32 qr;
+	u16 err;
+
+	err = hpi_control_query(ph_subsys, h_tuner, HPI_TUNER_BAND, index, 0,
+		&qr);
+	*pw_gain = (u16)qr;
+	return err;
+}
+
+u16 hpi_tuner_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short gain)
+{
+	return hpi_control_param_set(ph_subsys, h_control, HPI_TUNER_GAIN,
+		gain, 0);
+}
+
+u16 hpi_tuner_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short *pn_gain)
+{
+	u32 gain = 0;
+	u16 error = 0;
+
+	error = hpi_control_param1_get(ph_subsys, h_control, HPI_TUNER_GAIN,
+		&gain);
+	if (pn_gain)
+		*pn_gain = (u16)gain;
+	return error;
+}
+
+u16 hpi_tuner_getRF_level(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short *pw_level)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_TUNER_LEVEL;
+	hm.u.c.param1 = HPI_TUNER_LEVEL_AVERAGE;
+	hpi_send_recv(&hm, &hr);
+	if (pw_level)
+		*pw_level = (short)hr.u.c.param1;
+	return hr.error;
+}
+
+u16 hpi_tuner_get_rawRF_level(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, short *pw_level)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_TUNER_LEVEL;
+	hm.u.c.param1 = HPI_TUNER_LEVEL_RAW;
+	hpi_send_recv(&hm, &hr);
+	if (pw_level)
+		*pw_level = (short)hr.u.c.param1;
+	return hr.error;
+}
+
+u16 hpi_tuner_query_deemphasis(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_tuner, const u32 index, const u16 band, u32 *pdeemphasis)
+{
+	return hpi_control_query(ph_subsys, h_tuner, HPI_TUNER_DEEMPHASIS,
+		index, band, pdeemphasis);
+}
+
+u16 hpi_tuner_set_deemphasis(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 deemphasis)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_TUNER_DEEMPHASIS, deemphasis, 0);
+}
+
+u16 hpi_tuner_get_deemphasis(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pdeemphasis)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_TUNER_DEEMPHASIS, pdeemphasis);
+}
+
+u16 hpi_tuner_query_program(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_tuner, u32 *pbitmap_program)
+{
+	return hpi_control_query(ph_subsys, h_tuner, HPI_TUNER_PROGRAM, 0, 0,
+		pbitmap_program);
+}
+
+u16 hpi_tuner_set_program(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 program)
+{
+	return hpi_control_param_set(ph_subsys, h_control, HPI_TUNER_PROGRAM,
+		program, 0);
+}
+
+u16 hpi_tuner_get_program(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 *pprogram)
+{
+	return hpi_control_param1_get(ph_subsys, h_control, HPI_TUNER_PROGRAM,
+		pprogram);
+}
+
+u16 hpi_tuner_get_hd_radio_dsp_version(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, char *psz_dsp_version, const u32 string_size)
+{
+	return hpi_control_get_string(h_control,
+		HPI_TUNER_HDRADIO_DSP_VERSION, psz_dsp_version, string_size);
+}
+
+u16 hpi_tuner_get_hd_radio_sdk_version(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, char *psz_sdk_version, const u32 string_size)
+{
+	return hpi_control_get_string(h_control,
+		HPI_TUNER_HDRADIO_SDK_VERSION, psz_sdk_version, string_size);
+}
+
+u16 hpi_tuner_get_status(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u16 *pw_status_mask, u16 *pw_status)
+{
+	u32 status = 0;
+	u16 error = 0;
+
+	error = hpi_control_param1_get(ph_subsys, h_control, HPI_TUNER_STATUS,
+		&status);
+	if (pw_status) {
+		if (!error) {
+			*pw_status_mask = (u16)(status >> 16);
+			*pw_status = (u16)(status & 0xFFFF);
+		} else {
+			*pw_status_mask = 0;
+			*pw_status = 0;
+		}
+	}
+	return error;
+}
+
+u16 hpi_tuner_set_mode(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 mode, u32 value)
+{
+	return hpi_control_param_set(ph_subsys, h_control, HPI_TUNER_MODE,
+		mode, value);
+}
+
+u16 hpi_tuner_get_mode(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 mode, u32 *pn_value)
+{
+	return hpi_control_param_get(ph_subsys, h_control, HPI_TUNER_MODE,
+		mode, 0, pn_value, NULL);
+}
+
+u16 hpi_tuner_get_hd_radio_signal_quality(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pquality)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_TUNER_HDRADIO_SIGNAL_QUALITY, pquality);
+}
+
+u16 hpi_tuner_get_hd_radio_signal_blend(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *pblend)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_TUNER_HDRADIO_BLEND, pblend);
+}
+
+u16 hpi_tuner_set_hd_radio_signal_blend(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, const u32 blend)
+{
+	return hpi_control_param_set(ph_subsys, h_control,
+		HPI_TUNER_HDRADIO_BLEND, blend, 0);
+}
+
+u16 hpi_tuner_getRDS(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	char *p_data)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_TUNER_RDS;
+	hpi_send_recv(&hm, &hr);
+	if (p_data) {
+		*(u32 *)&p_data[0] = hr.u.cu.tuner.rds.data[0];
+		*(u32 *)&p_data[4] = hr.u.cu.tuner.rds.data[1];
+		*(u32 *)&p_data[8] = hr.u.cu.tuner.rds.bLER;
+	}
+	return hr.error;
+}
+
+u16 HPI_PAD__get_channel_name(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, char *psz_string, const u32 data_length)
+{
+	return hpi_control_get_string(h_control, HPI_PAD_CHANNEL_NAME,
+		psz_string, data_length);
+}
+
+u16 HPI_PAD__get_artist(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	char *psz_string, const u32 data_length)
+{
+	return hpi_control_get_string(h_control, HPI_PAD_ARTIST, psz_string,
+		data_length);
+}
+
+u16 HPI_PAD__get_title(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	char *psz_string, const u32 data_length)
+{
+	return hpi_control_get_string(h_control, HPI_PAD_TITLE, psz_string,
+		data_length);
+}
+
+u16 HPI_PAD__get_comment(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	char *psz_string, const u32 data_length)
+{
+	return hpi_control_get_string(h_control, HPI_PAD_COMMENT, psz_string,
+		data_length);
+}
+
+u16 HPI_PAD__get_program_type(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, u32 *ppTY)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_PAD_PROGRAM_TYPE, ppTY);
+}
+
+u16 HPI_PAD__get_rdsPI(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	u32 *ppI)
+{
+	return hpi_control_param1_get(ph_subsys, h_control,
+		HPI_PAD_PROGRAM_ID, ppI);
+}
+
+u16 hpi_volume_query_channels(const struct hpi_hsubsys *ph_subsys,
+	const u32 h_volume, u32 *p_channels)
+{
+	return hpi_control_query(ph_subsys, h_volume, HPI_VOLUME_NUM_CHANNELS,
+		0, 0, p_channels);
+}
+
+u16 hpi_volume_set_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_log_gain[HPI_MAX_CHANNELS]
+	)
+{
+	return hpi_control_log_set2(h_control, HPI_VOLUME_GAIN,
+		an_log_gain[0], an_log_gain[1]);
+}
+
+u16 hpi_volume_get_gain(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_log_gain[HPI_MAX_CHANNELS]
+	)
+{
+	return hpi_control_log_get2(ph_subsys, h_control, HPI_VOLUME_GAIN,
+		&an_log_gain[0], &an_log_gain[1]);
+}
+
+u16 hpi_volume_query_range(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short *min_gain_01dB, short *max_gain_01dB, short *step_gain_01dB)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_VOLUME_RANGE;
+
+	hpi_send_recv(&hm, &hr);
+	if (hr.error) {
+		hr.u.c.an_log_value[0] = 0;
+		hr.u.c.an_log_value[1] = 0;
+		hr.u.c.param1 = 0;
+	}
+	if (min_gain_01dB)
+		*min_gain_01dB = hr.u.c.an_log_value[0];
+	if (max_gain_01dB)
+		*max_gain_01dB = hr.u.c.an_log_value[1];
+	if (step_gain_01dB)
+		*step_gain_01dB = (short)hr.u.c.param1;
+	return hr.error;
+}
+
+u16 hpi_volume_auto_fade_profile(const struct hpi_hsubsys *ph_subsys,
+	u32 h_control, short an_stop_gain0_01dB[HPI_MAX_CHANNELS],
+	u32 duration_ms, u16 profile)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_SET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+
+	memcpy(hm.u.c.an_log_value, an_stop_gain0_01dB,
+		sizeof(short) * HPI_MAX_CHANNELS);
+
+	hm.u.c.attribute = HPI_VOLUME_AUTOFADE;
+	hm.u.c.param1 = duration_ms;
+	hm.u.c.param2 = profile;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_volume_auto_fade(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_stop_gain0_01dB[HPI_MAX_CHANNELS], u32 duration_ms)
+{
+	return hpi_volume_auto_fade_profile(ph_subsys, h_control,
+		an_stop_gain0_01dB, duration_ms, HPI_VOLUME_AUTOFADE_LOG);
+}
+
+u16 hpi_vox_set_threshold(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short an_gain0_01dB)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_SET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_VOX_THRESHOLD;
+
+	hm.u.c.an_log_value[0] = an_gain0_01dB;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_vox_get_threshold(const struct hpi_hsubsys *ph_subsys, u32 h_control,
+	short *an_gain0_01dB)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL,
+		HPI_CONTROL_GET_STATE);
+	u32TOINDEXES(h_control, &hm.adapter_index, &hm.obj_index);
+	hm.u.c.attribute = HPI_VOX_THRESHOLD;
+
+	hpi_send_recv(&hm, &hr);
+
+	*an_gain0_01dB = hr.u.c.an_log_value[0];
+
+	return hr.error;
+}
+
+static size_t strv_packet_size = MIN_STRV_PACKET_SIZE;
+
+static size_t entity_type_to_size[LAST_ENTITY_TYPE] = {
+	0,
+	sizeof(struct hpi_entity),
+	sizeof(void *),
+
+	sizeof(int),
+	sizeof(float),
+	sizeof(double),
+
+	sizeof(char),
+	sizeof(char),
+
+	4 * sizeof(char),
+	16 * sizeof(char),
+	6 * sizeof(char),
+};
+
+static inline size_t hpi_entity_size(struct hpi_entity *entity_ptr)
+{
+	return entity_ptr->header.size;
+}
+
+static inline size_t hpi_entity_header_size(struct hpi_entity *entity_ptr)
+{
+	return sizeof(entity_ptr->header);
+}
+
+static inline size_t hpi_entity_value_size(struct hpi_entity *entity_ptr)
+{
+	return hpi_entity_size(entity_ptr) -
+		hpi_entity_header_size(entity_ptr);
+}
+
+static inline size_t hpi_entity_item_count(struct hpi_entity *entity_ptr)
+{
+	return hpi_entity_value_size(entity_ptr) /
+		entity_type_to_size[entity_ptr->header.type];
+}
+
+static inline struct hpi_entity *hpi_entity_ptr_to_next(struct hpi_entity
+	*entity_ptr)
+{
+	return (void *)(((u8 *)entity_ptr) + hpi_entity_size(entity_ptr));
+}
+
+static inline u16 hpi_entity_check_type(const enum e_entity_type t)
+{
+	if (t >= 0 && t < STR_TYPE_FIELD_MAX)
+		return 0;
+	return HPI_ERROR_ENTITY_TYPE_INVALID;
+}
+
+static inline u16 hpi_entity_check_role(const enum e_entity_role r)
+{
+	if (r >= 0 && r < STR_ROLE_FIELD_MAX)
+		return 0;
+	return HPI_ERROR_ENTITY_ROLE_INVALID;
+}
+
+static u16 hpi_entity_get_next(struct hpi_entity *entity, int recursive_flag,
+	void *guard_p, struct hpi_entity **next)
+{
+	HPI_DEBUG_ASSERT(entity != NULL);
+	HPI_DEBUG_ASSERT(next != NULL);
+	HPI_DEBUG_ASSERT(hpi_entity_size(entity) != 0);
+
+	if (guard_p <= (void *)entity) {
+		*next = NULL;
+		return 0;
+	}
+
+	if (recursive_flag && entity->header.type == entity_type_sequence)
+		*next = (struct hpi_entity *)entity->value;
+	else
+		*next = (struct hpi_entity *)hpi_entity_ptr_to_next(entity);
+
+	if (guard_p <= (void *)*next) {
+		*next = NULL;
+		return 0;
+	}
+
+	HPI_DEBUG_ASSERT(guard_p >= (void *)hpi_entity_ptr_to_next(*next));
+	return 0;
+}
+
+u16 hpi_entity_find_next(struct hpi_entity *container_entity,
+	enum e_entity_type type, enum e_entity_role role, int recursive_flag,
+	struct hpi_entity **current_match)
+{
+	struct hpi_entity *tmp = NULL;
+	void *guard_p = NULL;
+
+	HPI_DEBUG_ASSERT(container_entity != NULL);
+	guard_p = hpi_entity_ptr_to_next(container_entity);
+
+	if (*current_match != NULL)
+		hpi_entity_get_next(*current_match, recursive_flag, guard_p,
+			&tmp);
+	else
+		hpi_entity_get_next(container_entity, 1, guard_p, &tmp);
+
+	while (tmp) {
+		u16 err;
+
+		HPI_DEBUG_ASSERT((void *)tmp >= (void *)container_entity);
+
+		if ((!type || tmp->header.type == type) && (!role
+				|| tmp->header.role == role)) {
+			*current_match = tmp;
+			return 0;
+		}
+
+		err = hpi_entity_get_next(tmp, recursive_flag, guard_p,
+			current_match);
+		if (err)
+			return err;
+
+		tmp = *current_match;
+	}
+
+	*current_match = NULL;
+	return 0;
+}
+
+void hpi_entity_free(struct hpi_entity *entity)
+{
+	kfree(entity);
+}
+
+static u16 hpi_entity_alloc_and_copy(struct hpi_entity *src,
+	struct hpi_entity **dst)
+{
+	size_t buf_size;
+	HPI_DEBUG_ASSERT(dst != NULL);
+	HPI_DEBUG_ASSERT(src != NULL);
+
+	buf_size = hpi_entity_size(src);
+	*dst = kmalloc(buf_size, GFP_KERNEL);
+	if (*dst == NULL)
+		return HPI_ERROR_MEMORY_ALLOC;
+	memcpy(*dst, src, buf_size);
+	return 0;
+}
+
+u16 hpi_universal_info(const struct hpi_hsubsys *ph_subsys, u32 hC,
+	struct hpi_entity **info)
+{
+	struct hpi_msg_strv hm;
+	struct hpi_res_strv *phr;
+	u16 hpi_err;
+	int remaining_attempts = 2;
+	size_t resp_packet_size = 1024;
+
+	*info = NULL;
+
+	while (remaining_attempts--) {
+		phr = kmalloc(resp_packet_size, GFP_KERNEL);
+		HPI_DEBUG_ASSERT(phr != NULL);
+
+		hpi_init_message_responseV1(&hm.h, (u16)sizeof(hm), &phr->h,
+			(u16)resp_packet_size, HPI_OBJ_CONTROL,
+			HPI_CONTROL_GET_INFO);
+		u32TOINDEXES(hC, &hm.h.adapter_index, &hm.h.obj_index);
+
+		hm.strv.header.size = sizeof(hm.strv);
+		phr->strv.header.size = resp_packet_size - sizeof(phr->h);
+
+		hpi_send_recv((struct hpi_message *)&hm.h,
+			(struct hpi_response *)&phr->h);
+		if (phr->h.error == HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL) {
+
+			HPI_DEBUG_ASSERT(phr->h.specific_error >
+				MIN_STRV_PACKET_SIZE
+				&& phr->h.specific_error < 1500);
+			resp_packet_size = phr->h.specific_error;
+		} else {
+			remaining_attempts = 0;
+			if (!phr->h.error)
+				hpi_entity_alloc_and_copy(&phr->strv, info);
+		}
+
+		hpi_err = phr->h.error;
+		kfree(phr);
+	}
+
+	return hpi_err;
+}
+
+u16 hpi_universal_get(const struct hpi_hsubsys *ph_subsys, u32 hC,
+	struct hpi_entity **value)
+{
+	struct hpi_msg_strv hm;
+	struct hpi_res_strv *phr;
+	u16 hpi_err;
+	int remaining_attempts = 2;
+
+	*value = NULL;
+
+	while (remaining_attempts--) {
+		phr = kmalloc(strv_packet_size, GFP_KERNEL);
+		if (!phr)
+			return HPI_ERROR_MEMORY_ALLOC;
+
+		hpi_init_message_responseV1(&hm.h, (u16)sizeof(hm), &phr->h,
+			(u16)strv_packet_size, HPI_OBJ_CONTROL,
+			HPI_CONTROL_GET_STATE);
+		u32TOINDEXES(hC, &hm.h.adapter_index, &hm.h.obj_index);
+
+		hm.strv.header.size = sizeof(hm.strv);
+		phr->strv.header.size = strv_packet_size - sizeof(phr->h);
+
+		hpi_send_recv((struct hpi_message *)&hm.h,
+			(struct hpi_response *)&phr->h);
+		if (phr->h.error == HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL) {
+
+			HPI_DEBUG_ASSERT(phr->h.specific_error >
+				MIN_STRV_PACKET_SIZE
+				&& phr->h.specific_error < 1000);
+			strv_packet_size = phr->h.specific_error;
+		} else {
+			remaining_attempts = 0;
+			if (!phr->h.error)
+				hpi_entity_alloc_and_copy(&phr->strv, value);
+		}
+
+		hpi_err = phr->h.error;
+		kfree(phr);
+	}
+
+	return hpi_err;
+}
+
+u16 hpi_universal_set(const struct hpi_hsubsys *ph_subsys, u32 hC,
+	struct hpi_entity *value)
+{
+	struct hpi_msg_strv *phm;
+	struct hpi_res_strv hr;
+
+	phm = kmalloc(sizeof(phm->h) + value->header.size, GFP_KERNEL);
+	HPI_DEBUG_ASSERT(phm != NULL);
+
+	hpi_init_message_responseV1(&phm->h,
+		sizeof(phm->h) + value->header.size, &hr.h, sizeof(hr),
+		HPI_OBJ_CONTROL, HPI_CONTROL_SET_STATE);
+	u32TOINDEXES(hC, &phm->h.adapter_index, &phm->h.obj_index);
+	hr.strv.header.size = sizeof(hr.strv);
+
+	memcpy(&phm->strv, value, value->header.size);
+	hpi_send_recv((struct hpi_message *)&phm->h,
+		(struct hpi_response *)&hr.h);
+
+	return hr.h.error;
+}
+
+u16 hpi_entity_alloc_and_pack(const enum e_entity_type type,
+	const size_t item_count, const enum e_entity_role role, void *value,
+	struct hpi_entity **entity)
+{
+	size_t bytes_to_copy, total_size;
+	u16 hE = 0;
+	*entity = NULL;
+
+	hE = hpi_entity_check_type(type);
+	if (hE)
+		return hE;
+
+	HPI_DEBUG_ASSERT(role > entity_role_null && type < LAST_ENTITY_TYPE);
+
+	bytes_to_copy = entity_type_to_size[type] * item_count;
+	total_size = hpi_entity_header_size(*entity) + bytes_to_copy;
+
+	HPI_DEBUG_ASSERT(total_size >= hpi_entity_header_size(*entity)
+		&& total_size < STR_SIZE_FIELD_MAX);
+
+	*entity = kmalloc(total_size, GFP_KERNEL);
+	if (*entity == NULL)
+		return HPI_ERROR_MEMORY_ALLOC;
+	memcpy((*entity)->value, value, bytes_to_copy);
+	(*entity)->header.size =
+		hpi_entity_header_size(*entity) + bytes_to_copy;
+	(*entity)->header.type = type;
+	(*entity)->header.role = role;
+	return 0;
+}
+
+u16 hpi_entity_copy_value_from(struct hpi_entity *entity,
+	enum e_entity_type type, size_t item_count, void *value_dst_p)
+{
+	size_t bytes_to_copy;
+
+	if (entity->header.type != type)
+		return HPI_ERROR_ENTITY_TYPE_MISMATCH;
+
+	if (hpi_entity_item_count(entity) != item_count)
+		return HPI_ERROR_ENTITY_ITEM_COUNT;
+
+	bytes_to_copy = entity_type_to_size[type] * item_count;
+	memcpy(value_dst_p, entity->value, bytes_to_copy);
+	return 0;
+}
+
+u16 hpi_entity_unpack(struct hpi_entity *entity, enum e_entity_type *type,
+	size_t *item_count, enum e_entity_role *role, void **value)
+{
+	u16 err = 0;
+	HPI_DEBUG_ASSERT(entity != NULL);
+
+	if (type)
+		*type = entity->header.type;
+
+	if (role)
+		*role = entity->header.role;
+
+	if (value)
+		*value = entity->value;
+
+	if (item_count != NULL) {
+		if (entity->header.type == entity_type_sequence) {
+			void *guard_p = hpi_entity_ptr_to_next(entity);
+			struct hpi_entity *next = NULL;
+			void *contents = entity->value;
+
+			*item_count = 0;
+			while (contents < guard_p) {
+				(*item_count)++;
+				err = hpi_entity_get_next(contents, 0,
+					guard_p, &next);
+				if (next == NULL || err)
+					break;
+				contents = next;
+			}
+		} else {
+			*item_count = hpi_entity_item_count(entity);
+		}
+	}
+	return err;
+}
+
+u16 hpi_gpio_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u32 *ph_gpio, u16 *pw_number_input_bits, u16 *pw_number_output_bits)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_GPIO, HPI_GPIO_OPEN);
+	hm.adapter_index = adapter_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (hr.error == 0) {
+		*ph_gpio =
+			hpi_indexes_to_handle(HPI_OBJ_GPIO, adapter_index, 0);
+		if (pw_number_input_bits)
+			*pw_number_input_bits = hr.u.l.number_input_bits;
+		if (pw_number_output_bits)
+			*pw_number_output_bits = hr.u.l.number_output_bits;
+	} else
+		*ph_gpio = 0;
+	return hr.error;
+}
+
+u16 hpi_gpio_read_bit(const struct hpi_hsubsys *ph_subsys, u32 h_gpio,
+	u16 bit_index, u16 *pw_bit_data)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_GPIO, HPI_GPIO_READ_BIT);
+	u32TOINDEX(h_gpio, &hm.adapter_index);
+	hm.u.l.bit_index = bit_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	*pw_bit_data = hr.u.l.bit_data[0];
+	return hr.error;
+}
+
+u16 hpi_gpio_read_all_bits(const struct hpi_hsubsys *ph_subsys, u32 h_gpio,
+	u16 aw_all_bit_data[4]
+	)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_GPIO, HPI_GPIO_READ_ALL);
+	u32TOINDEX(h_gpio, &hm.adapter_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	if (aw_all_bit_data) {
+		aw_all_bit_data[0] = hr.u.l.bit_data[0];
+		aw_all_bit_data[1] = hr.u.l.bit_data[1];
+		aw_all_bit_data[2] = hr.u.l.bit_data[2];
+		aw_all_bit_data[3] = hr.u.l.bit_data[3];
+	}
+	return hr.error;
+}
+
+u16 hpi_gpio_write_bit(const struct hpi_hsubsys *ph_subsys, u32 h_gpio,
+	u16 bit_index, u16 bit_data)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_GPIO, HPI_GPIO_WRITE_BIT);
+	u32TOINDEX(h_gpio, &hm.adapter_index);
+	hm.u.l.bit_index = bit_index;
+	hm.u.l.bit_data = bit_data;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_gpio_write_status(const struct hpi_hsubsys *ph_subsys, u32 h_gpio,
+	u16 aw_all_bit_data[4]
+	)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_GPIO,
+		HPI_GPIO_WRITE_STATUS);
+	u32TOINDEX(h_gpio, &hm.adapter_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	if (aw_all_bit_data) {
+		aw_all_bit_data[0] = hr.u.l.bit_data[0];
+		aw_all_bit_data[1] = hr.u.l.bit_data[1];
+		aw_all_bit_data[2] = hr.u.l.bit_data[2];
+		aw_all_bit_data[3] = hr.u.l.bit_data[3];
+	}
+	return hr.error;
+}
+
+u16 hpi_async_event_open(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u32 *ph_async)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ASYNCEVENT,
+		HPI_ASYNCEVENT_OPEN);
+	hm.adapter_index = adapter_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (hr.error == 0)
+
+		*ph_async =
+			hpi_indexes_to_handle(HPI_OBJ_ASYNCEVENT,
+			adapter_index, 0);
+	else
+		*ph_async = 0;
+	return hr.error;
+
+}
+
+u16 hpi_async_event_close(const struct hpi_hsubsys *ph_subsys, u32 h_async)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ASYNCEVENT,
+		HPI_ASYNCEVENT_OPEN);
+	u32TOINDEX(h_async, &hm.adapter_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_async_event_wait(const struct hpi_hsubsys *ph_subsys, u32 h_async,
+	u16 maximum_events, struct hpi_async_event *p_events,
+	u16 *pw_number_returned)
+{
+
+	return 0;
+}
+
+u16 hpi_async_event_get_count(const struct hpi_hsubsys *ph_subsys,
+	u32 h_async, u16 *pw_count)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ASYNCEVENT,
+		HPI_ASYNCEVENT_GETCOUNT);
+	u32TOINDEX(h_async, &hm.adapter_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	if (hr.error == 0)
+		if (pw_count)
+			*pw_count = hr.u.as.u.count.count;
+
+	return hr.error;
+}
+
+u16 hpi_async_event_get(const struct hpi_hsubsys *ph_subsys, u32 h_async,
+	u16 maximum_events, struct hpi_async_event *p_events,
+	u16 *pw_number_returned)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ASYNCEVENT,
+		HPI_ASYNCEVENT_GET);
+	u32TOINDEX(h_async, &hm.adapter_index);
+
+	hpi_send_recv(&hm, &hr);
+	if (!hr.error) {
+		memcpy(p_events, &hr.u.as.u.event,
+			sizeof(struct hpi_async_event));
+		*pw_number_returned = 1;
+	}
+
+	return hr.error;
+}
+
+u16 hpi_nv_memory_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u32 *ph_nv_memory, u16 *pw_size_in_bytes)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_NVMEMORY,
+		HPI_NVMEMORY_OPEN);
+	hm.adapter_index = adapter_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (hr.error == 0) {
+		*ph_nv_memory =
+			hpi_indexes_to_handle(HPI_OBJ_NVMEMORY, adapter_index,
+			0);
+		if (pw_size_in_bytes)
+			*pw_size_in_bytes = hr.u.n.size_in_bytes;
+	} else
+		*ph_nv_memory = 0;
+	return hr.error;
+}
+
+u16 hpi_nv_memory_read_byte(const struct hpi_hsubsys *ph_subsys,
+	u32 h_nv_memory, u16 index, u16 *pw_data)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_NVMEMORY,
+		HPI_NVMEMORY_READ_BYTE);
+	u32TOINDEX(h_nv_memory, &hm.adapter_index);
+	hm.u.n.address = index;
+
+	hpi_send_recv(&hm, &hr);
+
+	*pw_data = hr.u.n.data;
+	return hr.error;
+}
+
+u16 hpi_nv_memory_write_byte(const struct hpi_hsubsys *ph_subsys,
+	u32 h_nv_memory, u16 index, u16 data)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_NVMEMORY,
+		HPI_NVMEMORY_WRITE_BYTE);
+	u32TOINDEX(h_nv_memory, &hm.adapter_index);
+	hm.u.n.address = index;
+	hm.u.n.data = data;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_profile_open_all(const struct hpi_hsubsys *ph_subsys,
+	u16 adapter_index, u16 profile_index, u32 *ph_profile,
+	u16 *pw_max_profiles)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE,
+		HPI_PROFILE_OPEN_ALL);
+	hm.adapter_index = adapter_index;
+	hm.obj_index = profile_index;
+	hpi_send_recv(&hm, &hr);
+
+	*pw_max_profiles = hr.u.p.u.o.max_profiles;
+	if (hr.error == 0)
+		*ph_profile =
+			hpi_indexes_to_handle(HPI_OBJ_PROFILE, adapter_index,
+			profile_index);
+	else
+		*ph_profile = 0;
+	return hr.error;
+}
+
+u16 hpi_profile_get(const struct hpi_hsubsys *ph_subsys, u32 h_profile,
+	u16 bin_index, u16 *pw_seconds, u32 *pmicro_seconds, u32 *pcall_count,
+	u32 *pmax_micro_seconds, u32 *pmin_micro_seconds)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE, HPI_PROFILE_GET);
+	u32TOINDEXES(h_profile, &hm.adapter_index, &hm.obj_index);
+	hm.u.p.bin_index = bin_index;
+	hpi_send_recv(&hm, &hr);
+	if (pw_seconds)
+		*pw_seconds = hr.u.p.u.t.seconds;
+	if (pmicro_seconds)
+		*pmicro_seconds = hr.u.p.u.t.micro_seconds;
+	if (pcall_count)
+		*pcall_count = hr.u.p.u.t.call_count;
+	if (pmax_micro_seconds)
+		*pmax_micro_seconds = hr.u.p.u.t.max_micro_seconds;
+	if (pmin_micro_seconds)
+		*pmin_micro_seconds = hr.u.p.u.t.min_micro_seconds;
+	return hr.error;
+}
+
+u16 hpi_profile_get_utilization(const struct hpi_hsubsys *ph_subsys,
+	u32 h_profile, u32 *putilization)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE,
+		HPI_PROFILE_GET_UTILIZATION);
+	u32TOINDEXES(h_profile, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+	if (hr.error) {
+		if (putilization)
+			*putilization = 0;
+	} else {
+		if (putilization)
+			*putilization = hr.u.p.u.t.call_count;
+	}
+	return hr.error;
+}
+
+u16 hpi_profile_get_name(const struct hpi_hsubsys *ph_subsys, u32 h_profile,
+	u16 bin_index, char *sz_name, u16 name_length)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE,
+		HPI_PROFILE_GET_NAME);
+	u32TOINDEXES(h_profile, &hm.adapter_index, &hm.obj_index);
+	hm.u.p.bin_index = bin_index;
+	hpi_send_recv(&hm, &hr);
+	if (hr.error) {
+		if (sz_name)
+			strcpy(sz_name, "??");
+	} else {
+		if (sz_name)
+			memcpy(sz_name, (char *)hr.u.p.u.n.sz_name,
+				name_length);
+	}
+	return hr.error;
+}
+
+u16 hpi_profile_start_all(const struct hpi_hsubsys *ph_subsys, u32 h_profile)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE,
+		HPI_PROFILE_START_ALL);
+	u32TOINDEXES(h_profile, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_profile_stop_all(const struct hpi_hsubsys *ph_subsys, u32 h_profile)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_PROFILE,
+		HPI_PROFILE_STOP_ALL);
+	u32TOINDEXES(h_profile, &hm.adapter_index, &hm.obj_index);
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_watchdog_open(const struct hpi_hsubsys *ph_subsys, u16 adapter_index,
+	u32 *ph_watchdog)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_WATCHDOG,
+		HPI_WATCHDOG_OPEN);
+	hm.adapter_index = adapter_index;
+
+	hpi_send_recv(&hm, &hr);
+
+	if (hr.error == 0)
+		*ph_watchdog =
+			hpi_indexes_to_handle(HPI_OBJ_WATCHDOG, adapter_index,
+			0);
+	else
+		*ph_watchdog = 0;
+	return hr.error;
+}
+
+u16 hpi_watchdog_set_time(const struct hpi_hsubsys *ph_subsys, u32 h_watchdog,
+	u32 time_millisec)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_WATCHDOG,
+		HPI_WATCHDOG_SET_TIME);
+	u32TOINDEX(h_watchdog, &hm.adapter_index);
+	hm.u.w.time_ms = time_millisec;
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
+
+u16 hpi_watchdog_ping(const struct hpi_hsubsys *ph_subsys, u32 h_watchdog)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_WATCHDOG,
+		HPI_WATCHDOG_PING);
+	u32TOINDEX(h_watchdog, &hm.adapter_index);
+
+	hpi_send_recv(&hm, &hr);
+
+	return hr.error;
+}
diff --git a/linux/sound/pci/asihpi/hpimsginit.c b/linux/sound/pci/asihpi/hpimsginit.c
new file mode 100644
index 0000000..8e1d099
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpimsginit.c
@@ -0,0 +1,130 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+ Hardware Programming Interface (HPI) Utility functions.
+
+ (C) Copyright AudioScience Inc. 2007
+*******************************************************************************/
+
+#include "hpi_internal.h"
+#include "hpimsginit.h"
+
+/* The actual message size for each object type */
+static u16 msg_size[HPI_OBJ_MAXINDEX + 1] = HPI_MESSAGE_SIZE_BY_OBJECT;
+/* The actual response size for each object type */
+static u16 res_size[HPI_OBJ_MAXINDEX + 1] = HPI_RESPONSE_SIZE_BY_OBJECT;
+/* Flag to enable alternate message type for SSX2 bypass. */
+static u16 gwSSX2_bypass;
+
+/** \internal
+  * Used by ASIO driver to disable SSX2 for a single process
+  * \param phSubSys Pointer to HPI subsystem handle.
+  * \param wBypass New bypass setting 0 = off, nonzero = on
+  * \return Previous bypass setting.
+  */
+u16 hpi_subsys_ssx2_bypass(const struct hpi_hsubsys *ph_subsys, u16 bypass)
+{
+	u16 old_value = gwSSX2_bypass;
+
+	gwSSX2_bypass = bypass;
+
+	return old_value;
+}
+
+/** \internal
+  * initialize the HPI message structure
+  */
+static void hpi_init_message(struct hpi_message *phm, u16 object,
+	u16 function)
+{
+	memset(phm, 0, sizeof(*phm));
+	if ((object > 0) && (object <= HPI_OBJ_MAXINDEX))
+		phm->size = msg_size[object];
+	else
+		phm->size = sizeof(*phm);
+
+	if (gwSSX2_bypass)
+		phm->type = HPI_TYPE_SSX2BYPASS_MESSAGE;
+	else
+		phm->type = HPI_TYPE_MESSAGE;
+	phm->object = object;
+	phm->function = function;
+	phm->version = 0;
+	/* Expect adapter index to be set by caller */
+}
+
+/** \internal
+  * initialize the HPI response structure
+  */
+void hpi_init_response(struct hpi_response *phr, u16 object, u16 function,
+	u16 error)
+{
+	memset(phr, 0, sizeof(*phr));
+	phr->type = HPI_TYPE_RESPONSE;
+	if ((object > 0) && (object <= HPI_OBJ_MAXINDEX))
+		phr->size = res_size[object];
+	else
+		phr->size = sizeof(*phr);
+	phr->object = object;
+	phr->function = function;
+	phr->error = error;
+	phr->specific_error = 0;
+	phr->version = 0;
+}
+
+void hpi_init_message_response(struct hpi_message *phm,
+	struct hpi_response *phr, u16 object, u16 function)
+{
+	hpi_init_message(phm, object, function);
+	/* default error return if the response is
+	   not filled in by the callee */
+	hpi_init_response(phr, object, function,
+		HPI_ERROR_PROCESSING_MESSAGE);
+}
+
+static void hpi_init_messageV1(struct hpi_message_header *phm, u16 size,
+	u16 object, u16 function)
+{
+	memset(phm, 0, sizeof(*phm));
+	if ((object > 0) && (object <= HPI_OBJ_MAXINDEX)) {
+		phm->size = size;
+		phm->type = HPI_TYPE_MESSAGE;
+		phm->object = object;
+		phm->function = function;
+		phm->version = 1;
+		/* Expect adapter index to be set by caller */
+	}
+}
+
+void hpi_init_responseV1(struct hpi_response_header *phr, u16 size,
+	u16 object, u16 function)
+{
+	memset(phr, 0, sizeof(*phr));
+	phr->size = size;
+	phr->version = 1;
+	phr->type = HPI_TYPE_RESPONSE;
+	phr->error = HPI_ERROR_PROCESSING_MESSAGE;
+}
+
+void hpi_init_message_responseV1(struct hpi_message_header *phm, u16 msg_size,
+	struct hpi_response_header *phr, u16 res_size, u16 object,
+	u16 function)
+{
+	hpi_init_messageV1(phm, msg_size, object, function);
+	hpi_init_responseV1(phr, res_size, object, function);
+}
diff --git a/linux/sound/pci/asihpi/hpimsginit.h b/linux/sound/pci/asihpi/hpimsginit.h
new file mode 100644
index 0000000..864ad02
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpimsginit.h
@@ -0,0 +1,40 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+ Hardware Programming Interface (HPI) Utility functions
+
+ (C) Copyright AudioScience Inc. 2007
+*******************************************************************************/
+/* Initialise response headers, or msg/response pairs.
+Note that it is valid to just init a response e.g. when a lower level is preparing
+a response to a message.
+However, when sending a message, a matching response buffer always must be prepared
+*/
+
+void hpi_init_response(struct hpi_response *phr, u16 object, u16 function,
+	u16 error);
+
+void hpi_init_message_response(struct hpi_message *phm,
+	struct hpi_response *phr, u16 object, u16 function);
+
+void hpi_init_responseV1(struct hpi_response_header *phr, u16 size,
+	u16 object, u16 function);
+
+void hpi_init_message_responseV1(struct hpi_message_header *phm, u16 msg_size,
+	struct hpi_response_header *phr, u16 res_size, u16 object,
+	u16 function);
diff --git a/linux/sound/pci/asihpi/hpimsgx.c b/linux/sound/pci/asihpi/hpimsgx.c
new file mode 100644
index 0000000..f01ab96
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpimsgx.c
@@ -0,0 +1,907 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+Extended Message Function With Response Cacheing
+
+(C) Copyright AudioScience Inc. 2002
+*****************************************************************************/
+#define SOURCEFILE_NAME "hpimsgx.c"
+#include "hpi_internal.h"
+#include "hpimsginit.h"
+#include "hpimsgx.h"
+#include "hpidebug.h"
+
+static struct pci_device_id asihpi_pci_tbl[] = {
+#include "hpipcida.h"
+};
+
+static struct hpios_spinlock msgx_lock;
+
+static hpi_handler_func *hpi_entry_points[HPI_MAX_ADAPTERS];
+
+static hpi_handler_func *hpi_lookup_entry_point_function(const struct hpi_pci
+	*pci_info)
+{
+
+	int i;
+
+	for (i = 0; asihpi_pci_tbl[i].vendor != 0; i++) {
+		if (asihpi_pci_tbl[i].vendor != PCI_ANY_ID
+			&& asihpi_pci_tbl[i].vendor != pci_info->vendor_id)
+			continue;
+		if (asihpi_pci_tbl[i].device != PCI_ANY_ID
+			&& asihpi_pci_tbl[i].device != pci_info->device_id)
+			continue;
+		if (asihpi_pci_tbl[i].subvendor != PCI_ANY_ID
+			&& asihpi_pci_tbl[i].subvendor !=
+			pci_info->subsys_vendor_id)
+			continue;
+		if (asihpi_pci_tbl[i].subdevice != PCI_ANY_ID
+			&& asihpi_pci_tbl[i].subdevice !=
+			pci_info->subsys_device_id)
+			continue;
+
+		HPI_DEBUG_LOG(DEBUG, " %x,%lu\n", i,
+			asihpi_pci_tbl[i].driver_data);
+		return (hpi_handler_func *) asihpi_pci_tbl[i].driver_data;
+	}
+
+	return NULL;
+}
+
+static inline void hw_entry_point(struct hpi_message *phm,
+	struct hpi_response *phr)
+{
+
+	hpi_handler_func *ep;
+
+	if (phm->adapter_index < HPI_MAX_ADAPTERS) {
+		ep = (hpi_handler_func *) hpi_entry_points[phm->
+			adapter_index];
+		if (ep) {
+			HPI_DEBUG_MESSAGE(DEBUG, phm);
+			ep(phm, phr);
+			HPI_DEBUG_RESPONSE(phr);
+			return;
+		}
+	}
+	hpi_init_response(phr, phm->object, phm->function,
+		HPI_ERROR_PROCESSING_MESSAGE);
+}
+
+static void adapter_open(struct hpi_message *phm, struct hpi_response *phr);
+static void adapter_close(struct hpi_message *phm, struct hpi_response *phr);
+
+static void mixer_open(struct hpi_message *phm, struct hpi_response *phr);
+static void mixer_close(struct hpi_message *phm, struct hpi_response *phr);
+
+static void outstream_open(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner);
+static void outstream_close(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner);
+static void instream_open(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner);
+static void instream_close(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner);
+
+static void HPIMSGX__reset(u16 adapter_index);
+static u16 HPIMSGX__init(struct hpi_message *phm, struct hpi_response *phr);
+static void HPIMSGX__cleanup(u16 adapter_index, void *h_owner);
+
+#ifndef DISABLE_PRAGMA_PACK1
+#pragma pack(push, 1)
+#endif
+
+struct hpi_subsys_response {
+	struct hpi_response_header h;
+	struct hpi_subsys_res s;
+};
+
+struct hpi_adapter_response {
+	struct hpi_response_header h;
+	struct hpi_adapter_res a;
+};
+
+struct hpi_mixer_response {
+	struct hpi_response_header h;
+	struct hpi_mixer_res m;
+};
+
+struct hpi_stream_response {
+	struct hpi_response_header h;
+	struct hpi_stream_res d;
+};
+
+struct adapter_info {
+	u16 type;
+	u16 num_instreams;
+	u16 num_outstreams;
+};
+
+struct asi_open_state {
+	int open_flag;
+	void *h_owner;
+};
+
+#ifndef DISABLE_PRAGMA_PACK1
+#pragma pack(pop)
+#endif
+
+/* Globals */
+static struct hpi_adapter_response rESP_HPI_ADAPTER_OPEN[HPI_MAX_ADAPTERS];
+
+static struct hpi_stream_response
+	rESP_HPI_OSTREAM_OPEN[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS];
+
+static struct hpi_stream_response
+	rESP_HPI_ISTREAM_OPEN[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS];
+
+static struct hpi_mixer_response rESP_HPI_MIXER_OPEN[HPI_MAX_ADAPTERS];
+
+static struct hpi_subsys_response gRESP_HPI_SUBSYS_FIND_ADAPTERS;
+
+static struct adapter_info aDAPTER_INFO[HPI_MAX_ADAPTERS];
+
+/* use these to keep track of opens from user mode apps/DLLs */
+static struct asi_open_state
+	outstream_user_open[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS];
+
+static struct asi_open_state
+	instream_user_open[HPI_MAX_ADAPTERS][HPI_MAX_STREAMS];
+
+static void subsys_message(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner)
+{
+	switch (phm->function) {
+	case HPI_SUBSYS_GET_VERSION:
+		hpi_init_response(phr, HPI_OBJ_SUBSYSTEM,
+			HPI_SUBSYS_GET_VERSION, 0);
+		phr->u.s.version = HPI_VER >> 8;	/* return major.minor */
+		phr->u.s.data = HPI_VER;	/* return major.minor.release */
+		break;
+	case HPI_SUBSYS_OPEN:
+		/*do not propagate the message down the chain */
+		hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_OPEN, 0);
+		break;
+	case HPI_SUBSYS_CLOSE:
+		/*do not propagate the message down the chain */
+		hpi_init_response(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_CLOSE,
+			0);
+		HPIMSGX__cleanup(HPIMSGX_ALLADAPTERS, h_owner);
+		break;
+	case HPI_SUBSYS_DRIVER_LOAD:
+		/* Initialize this module's internal state */
+		hpios_msgxlock_init(&msgx_lock);
+		memset(&hpi_entry_points, 0, sizeof(hpi_entry_points));
+		hpios_locked_mem_init();
+		/* Init subsys_findadapters response to no-adapters */
+		HPIMSGX__reset(HPIMSGX_ALLADAPTERS);
+		hpi_init_response(phr, HPI_OBJ_SUBSYSTEM,
+			HPI_SUBSYS_DRIVER_LOAD, 0);
+		/* individual HPIs dont implement driver load */
+		HPI_COMMON(phm, phr);
+		break;
+	case HPI_SUBSYS_DRIVER_UNLOAD:
+		HPI_COMMON(phm, phr);
+		HPIMSGX__cleanup(HPIMSGX_ALLADAPTERS, h_owner);
+		hpios_locked_mem_free_all();
+		hpi_init_response(phr, HPI_OBJ_SUBSYSTEM,
+			HPI_SUBSYS_DRIVER_UNLOAD, 0);
+		return;
+
+	case HPI_SUBSYS_GET_INFO:
+		HPI_COMMON(phm, phr);
+		break;
+
+	case HPI_SUBSYS_FIND_ADAPTERS:
+		memcpy(phr, &gRESP_HPI_SUBSYS_FIND_ADAPTERS,
+			sizeof(gRESP_HPI_SUBSYS_FIND_ADAPTERS));
+		break;
+	case HPI_SUBSYS_GET_NUM_ADAPTERS:
+		memcpy(phr, &gRESP_HPI_SUBSYS_FIND_ADAPTERS,
+			sizeof(gRESP_HPI_SUBSYS_FIND_ADAPTERS));
+		phr->function = HPI_SUBSYS_GET_NUM_ADAPTERS;
+		break;
+	case HPI_SUBSYS_GET_ADAPTER:
+		{
+			int count = phm->adapter_index;
+			int index = 0;
+			hpi_init_response(phr, HPI_OBJ_SUBSYSTEM,
+				HPI_SUBSYS_GET_ADAPTER, 0);
+
+			/* This is complicated by the fact that we want to
+			 * "skip" 0's in the adapter list.
+			 * First, make sure we are pointing to a
+			 * non-zero adapter type.
+			 */
+			while (gRESP_HPI_SUBSYS_FIND_ADAPTERS.
+				s.aw_adapter_list[index] == 0) {
+				index++;
+				if (index >= HPI_MAX_ADAPTERS)
+					break;
+			}
+			while (count) {
+				/* move on to the next adapter */
+				index++;
+				if (index >= HPI_MAX_ADAPTERS)
+					break;
+				while (gRESP_HPI_SUBSYS_FIND_ADAPTERS.
+					s.aw_adapter_list[index] == 0) {
+					index++;
+					if (index >= HPI_MAX_ADAPTERS)
+						break;
+				}
+				count--;
+			}
+
+			if (index < HPI_MAX_ADAPTERS) {
+				phr->u.s.adapter_index = (u16)index;
+				phr->u.s.aw_adapter_list[0] =
+					gRESP_HPI_SUBSYS_FIND_ADAPTERS.
+					s.aw_adapter_list[index];
+			} else {
+				phr->u.s.adapter_index = 0;
+				phr->u.s.aw_adapter_list[0] = 0;
+				phr->error = HPI_ERROR_BAD_ADAPTER_NUMBER;
+			}
+			break;
+		}
+	case HPI_SUBSYS_CREATE_ADAPTER:
+		HPIMSGX__init(phm, phr);
+		break;
+	case HPI_SUBSYS_DELETE_ADAPTER:
+		HPIMSGX__cleanup(phm->adapter_index, h_owner);
+		{
+			struct hpi_message hm;
+			struct hpi_response hr;
+			/* call to HPI_ADAPTER_CLOSE */
+			hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+				HPI_ADAPTER_CLOSE);
+			hm.adapter_index = phm->adapter_index;
+			hw_entry_point(&hm, &hr);
+		}
+		hw_entry_point(phm, phr);
+		gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.
+			aw_adapter_list[phm->adapter_index]
+			= 0;
+		hpi_entry_points[phm->adapter_index] = NULL;
+		break;
+	default:
+		hw_entry_point(phm, phr);
+		break;
+	}
+}
+
+static void adapter_message(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner)
+{
+	switch (phm->function) {
+	case HPI_ADAPTER_OPEN:
+		adapter_open(phm, phr);
+		break;
+	case HPI_ADAPTER_CLOSE:
+		adapter_close(phm, phr);
+		break;
+	default:
+		hw_entry_point(phm, phr);
+		break;
+	}
+}
+
+static void mixer_message(struct hpi_message *phm, struct hpi_response *phr)
+{
+	switch (phm->function) {
+	case HPI_MIXER_OPEN:
+		mixer_open(phm, phr);
+		break;
+	case HPI_MIXER_CLOSE:
+		mixer_close(phm, phr);
+		break;
+	default:
+		hw_entry_point(phm, phr);
+		break;
+	}
+}
+
+static void outstream_message(struct hpi_message *phm,
+	struct hpi_response *phr, void *h_owner)
+{
+	if (phm->obj_index >= aDAPTER_INFO[phm->adapter_index].num_outstreams) {
+		hpi_init_response(phr, HPI_OBJ_OSTREAM, phm->function,
+			HPI_ERROR_INVALID_OBJ_INDEX);
+		return;
+	}
+
+	switch (phm->function) {
+	case HPI_OSTREAM_OPEN:
+		outstream_open(phm, phr, h_owner);
+		break;
+	case HPI_OSTREAM_CLOSE:
+		outstream_close(phm, phr, h_owner);
+		break;
+	default:
+		hw_entry_point(phm, phr);
+		break;
+	}
+}
+
+static void instream_message(struct hpi_message *phm,
+	struct hpi_response *phr, void *h_owner)
+{
+	if (phm->obj_index >= aDAPTER_INFO[phm->adapter_index].num_instreams) {
+		hpi_init_response(phr, HPI_OBJ_ISTREAM, phm->function,
+			HPI_ERROR_INVALID_OBJ_INDEX);
+		return;
+	}
+
+	switch (phm->function) {
+	case HPI_ISTREAM_OPEN:
+		instream_open(phm, phr, h_owner);
+		break;
+	case HPI_ISTREAM_CLOSE:
+		instream_close(phm, phr, h_owner);
+		break;
+	default:
+		hw_entry_point(phm, phr);
+		break;
+	}
+}
+
+/* NOTE: HPI_Message() must be defined in the driver as a wrapper for
+ * HPI_MessageEx so that functions in hpifunc.c compile.
+ */
+void hpi_send_recv_ex(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner)
+{
+	HPI_DEBUG_MESSAGE(DEBUG, phm);
+
+	if (phm->type != HPI_TYPE_MESSAGE) {
+		hpi_init_response(phr, phm->object, phm->function,
+			HPI_ERROR_INVALID_TYPE);
+		return;
+	}
+
+	if (phm->adapter_index >= HPI_MAX_ADAPTERS
+		&& phm->adapter_index != HPIMSGX_ALLADAPTERS) {
+		hpi_init_response(phr, phm->object, phm->function,
+			HPI_ERROR_BAD_ADAPTER_NUMBER);
+		return;
+	}
+
+	switch (phm->object) {
+	case HPI_OBJ_SUBSYSTEM:
+		subsys_message(phm, phr, h_owner);
+		break;
+
+	case HPI_OBJ_ADAPTER:
+		adapter_message(phm, phr, h_owner);
+		break;
+
+	case HPI_OBJ_MIXER:
+		mixer_message(phm, phr);
+		break;
+
+	case HPI_OBJ_OSTREAM:
+		outstream_message(phm, phr, h_owner);
+		break;
+
+	case HPI_OBJ_ISTREAM:
+		instream_message(phm, phr, h_owner);
+		break;
+
+	default:
+		hw_entry_point(phm, phr);
+		break;
+	}
+	HPI_DEBUG_RESPONSE(phr);
+#if 1
+	if (phr->error >= HPI_ERROR_BACKEND_BASE) {
+		void *ep = NULL;
+		char *ep_name;
+
+		HPI_DEBUG_MESSAGE(ERROR, phm);
+
+		if (phm->adapter_index < HPI_MAX_ADAPTERS)
+			ep = hpi_entry_points[phm->adapter_index];
+
+		/* Don't need this? Have adapter index in debug info
+		   Know at driver load time index->backend mapping */
+		if (ep == HPI_6000)
+			ep_name = "HPI_6000";
+		else if (ep == HPI_6205)
+			ep_name = "HPI_6205";
+		else
+			ep_name = "unknown";
+
+		HPI_DEBUG_LOG(ERROR, "HPI %s response - error# %d\n", ep_name,
+			phr->error);
+
+		if (hpi_debug_level >= HPI_DEBUG_LEVEL_VERBOSE)
+			hpi_debug_data((u16 *)phm,
+				sizeof(*phm) / sizeof(u16));
+	}
+#endif
+}
+
+static void adapter_open(struct hpi_message *phm, struct hpi_response *phr)
+{
+	HPI_DEBUG_LOG(VERBOSE, "adapter_open\n");
+	memcpy(phr, &rESP_HPI_ADAPTER_OPEN[phm->adapter_index],
+		sizeof(rESP_HPI_ADAPTER_OPEN[0]));
+}
+
+static void adapter_close(struct hpi_message *phm, struct hpi_response *phr)
+{
+	HPI_DEBUG_LOG(VERBOSE, "adapter_close\n");
+	hpi_init_response(phr, HPI_OBJ_ADAPTER, HPI_ADAPTER_CLOSE, 0);
+}
+
+static void mixer_open(struct hpi_message *phm, struct hpi_response *phr)
+{
+	memcpy(phr, &rESP_HPI_MIXER_OPEN[phm->adapter_index],
+		sizeof(rESP_HPI_MIXER_OPEN[0]));
+}
+
+static void mixer_close(struct hpi_message *phm, struct hpi_response *phr)
+{
+	hpi_init_response(phr, HPI_OBJ_MIXER, HPI_MIXER_CLOSE, 0);
+}
+
+static void instream_open(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner)
+{
+
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_response(phr, HPI_OBJ_ISTREAM, HPI_ISTREAM_OPEN, 0);
+
+	hpios_msgxlock_lock(&msgx_lock);
+
+	if (instream_user_open[phm->adapter_index][phm->obj_index].open_flag)
+		phr->error = HPI_ERROR_OBJ_ALREADY_OPEN;
+	else if (rESP_HPI_ISTREAM_OPEN[phm->adapter_index]
+		[phm->obj_index].h.error)
+		memcpy(phr,
+			&rESP_HPI_ISTREAM_OPEN[phm->adapter_index][phm->
+				obj_index],
+			sizeof(rESP_HPI_ISTREAM_OPEN[0][0]));
+	else {
+		instream_user_open[phm->adapter_index][phm->
+			obj_index].open_flag = 1;
+		hpios_msgxlock_un_lock(&msgx_lock);
+
+		/* issue a reset */
+		hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+			HPI_ISTREAM_RESET);
+		hm.adapter_index = phm->adapter_index;
+		hm.obj_index = phm->obj_index;
+		hw_entry_point(&hm, &hr);
+
+		hpios_msgxlock_lock(&msgx_lock);
+		if (hr.error) {
+			instream_user_open[phm->adapter_index][phm->
+				obj_index].open_flag = 0;
+			phr->error = hr.error;
+		} else {
+			instream_user_open[phm->adapter_index][phm->
+				obj_index].open_flag = 1;
+			instream_user_open[phm->adapter_index][phm->
+				obj_index].h_owner = h_owner;
+			memcpy(phr,
+				&rESP_HPI_ISTREAM_OPEN[phm->adapter_index]
+				[phm->obj_index],
+				sizeof(rESP_HPI_ISTREAM_OPEN[0][0]));
+		}
+	}
+	hpios_msgxlock_un_lock(&msgx_lock);
+}
+
+static void instream_close(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner)
+{
+
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_response(phr, HPI_OBJ_ISTREAM, HPI_ISTREAM_CLOSE, 0);
+
+	hpios_msgxlock_lock(&msgx_lock);
+	if (h_owner ==
+		instream_user_open[phm->adapter_index][phm->
+			obj_index].h_owner) {
+		/* HPI_DEBUG_LOG(INFO,"closing adapter %d "
+		   "instream %d owned by %p\n",
+		   phm->wAdapterIndex, phm->wObjIndex, hOwner); */
+		instream_user_open[phm->adapter_index][phm->
+			obj_index].h_owner = NULL;
+		hpios_msgxlock_un_lock(&msgx_lock);
+		/* issue a reset */
+		hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+			HPI_ISTREAM_RESET);
+		hm.adapter_index = phm->adapter_index;
+		hm.obj_index = phm->obj_index;
+		hw_entry_point(&hm, &hr);
+		hpios_msgxlock_lock(&msgx_lock);
+		if (hr.error) {
+			instream_user_open[phm->adapter_index][phm->
+				obj_index].h_owner = h_owner;
+			phr->error = hr.error;
+		} else {
+			instream_user_open[phm->adapter_index][phm->
+				obj_index].open_flag = 0;
+			instream_user_open[phm->adapter_index][phm->
+				obj_index].h_owner = NULL;
+		}
+	} else {
+		HPI_DEBUG_LOG(WARNING,
+			"%p trying to close %d instream %d owned by %p\n",
+			h_owner, phm->adapter_index, phm->obj_index,
+			instream_user_open[phm->adapter_index][phm->
+				obj_index].h_owner);
+		phr->error = HPI_ERROR_OBJ_NOT_OPEN;
+	}
+	hpios_msgxlock_un_lock(&msgx_lock);
+}
+
+static void outstream_open(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner)
+{
+
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_response(phr, HPI_OBJ_OSTREAM, HPI_OSTREAM_OPEN, 0);
+
+	hpios_msgxlock_lock(&msgx_lock);
+
+	if (outstream_user_open[phm->adapter_index][phm->obj_index].open_flag)
+		phr->error = HPI_ERROR_OBJ_ALREADY_OPEN;
+	else if (rESP_HPI_OSTREAM_OPEN[phm->adapter_index]
+		[phm->obj_index].h.error)
+		memcpy(phr,
+			&rESP_HPI_OSTREAM_OPEN[phm->adapter_index][phm->
+				obj_index],
+			sizeof(rESP_HPI_OSTREAM_OPEN[0][0]));
+	else {
+		outstream_user_open[phm->adapter_index][phm->
+			obj_index].open_flag = 1;
+		hpios_msgxlock_un_lock(&msgx_lock);
+
+		/* issue a reset */
+		hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+			HPI_OSTREAM_RESET);
+		hm.adapter_index = phm->adapter_index;
+		hm.obj_index = phm->obj_index;
+		hw_entry_point(&hm, &hr);
+
+		hpios_msgxlock_lock(&msgx_lock);
+		if (hr.error) {
+			outstream_user_open[phm->adapter_index][phm->
+				obj_index].open_flag = 0;
+			phr->error = hr.error;
+		} else {
+			outstream_user_open[phm->adapter_index][phm->
+				obj_index].open_flag = 1;
+			outstream_user_open[phm->adapter_index][phm->
+				obj_index].h_owner = h_owner;
+			memcpy(phr,
+				&rESP_HPI_OSTREAM_OPEN[phm->adapter_index]
+				[phm->obj_index],
+				sizeof(rESP_HPI_OSTREAM_OPEN[0][0]));
+		}
+	}
+	hpios_msgxlock_un_lock(&msgx_lock);
+}
+
+static void outstream_close(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner)
+{
+
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_response(phr, HPI_OBJ_OSTREAM, HPI_OSTREAM_CLOSE, 0);
+
+	hpios_msgxlock_lock(&msgx_lock);
+
+	if (h_owner ==
+		outstream_user_open[phm->adapter_index][phm->
+			obj_index].h_owner) {
+		/* HPI_DEBUG_LOG(INFO,"closing adapter %d "
+		   "outstream %d owned by %p\n",
+		   phm->wAdapterIndex, phm->wObjIndex, hOwner); */
+		outstream_user_open[phm->adapter_index][phm->
+			obj_index].h_owner = NULL;
+		hpios_msgxlock_un_lock(&msgx_lock);
+		/* issue a reset */
+		hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+			HPI_OSTREAM_RESET);
+		hm.adapter_index = phm->adapter_index;
+		hm.obj_index = phm->obj_index;
+		hw_entry_point(&hm, &hr);
+		hpios_msgxlock_lock(&msgx_lock);
+		if (hr.error) {
+			outstream_user_open[phm->adapter_index][phm->
+				obj_index].h_owner = h_owner;
+			phr->error = hr.error;
+		} else {
+			outstream_user_open[phm->adapter_index][phm->
+				obj_index].open_flag = 0;
+			outstream_user_open[phm->adapter_index][phm->
+				obj_index].h_owner = NULL;
+		}
+	} else {
+		HPI_DEBUG_LOG(WARNING,
+			"%p trying to close %d outstream %d owned by %p\n",
+			h_owner, phm->adapter_index, phm->obj_index,
+			outstream_user_open[phm->adapter_index][phm->
+				obj_index].h_owner);
+		phr->error = HPI_ERROR_OBJ_NOT_OPEN;
+	}
+	hpios_msgxlock_un_lock(&msgx_lock);
+}
+
+static u16 adapter_prepare(u16 adapter)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	/* Open the adapter and streams */
+	u16 i;
+
+	/* call to HPI_ADAPTER_OPEN */
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_OPEN);
+	hm.adapter_index = adapter;
+	hw_entry_point(&hm, &hr);
+	memcpy(&rESP_HPI_ADAPTER_OPEN[adapter], &hr,
+		sizeof(rESP_HPI_ADAPTER_OPEN[0]));
+	if (hr.error)
+		return hr.error;
+
+	/* call to HPI_ADAPTER_GET_INFO */
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER,
+		HPI_ADAPTER_GET_INFO);
+	hm.adapter_index = adapter;
+	hw_entry_point(&hm, &hr);
+	if (hr.error)
+		return hr.error;
+
+	aDAPTER_INFO[adapter].num_outstreams = hr.u.a.num_outstreams;
+	aDAPTER_INFO[adapter].num_instreams = hr.u.a.num_instreams;
+	aDAPTER_INFO[adapter].type = hr.u.a.adapter_type;
+
+	gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.aw_adapter_list[adapter] =
+		hr.u.a.adapter_type;
+	gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.num_adapters++;
+	if (gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.num_adapters > HPI_MAX_ADAPTERS)
+		gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.num_adapters =
+			HPI_MAX_ADAPTERS;
+
+	/* call to HPI_OSTREAM_OPEN */
+	for (i = 0; i < aDAPTER_INFO[adapter].num_outstreams; i++) {
+		hpi_init_message_response(&hm, &hr, HPI_OBJ_OSTREAM,
+			HPI_OSTREAM_OPEN);
+		hm.adapter_index = adapter;
+		hm.obj_index = i;
+		hw_entry_point(&hm, &hr);
+		memcpy(&rESP_HPI_OSTREAM_OPEN[adapter][i], &hr,
+			sizeof(rESP_HPI_OSTREAM_OPEN[0][0]));
+		outstream_user_open[adapter][i].open_flag = 0;
+		outstream_user_open[adapter][i].h_owner = NULL;
+	}
+
+	/* call to HPI_ISTREAM_OPEN */
+	for (i = 0; i < aDAPTER_INFO[adapter].num_instreams; i++) {
+		hpi_init_message_response(&hm, &hr, HPI_OBJ_ISTREAM,
+			HPI_ISTREAM_OPEN);
+		hm.adapter_index = adapter;
+		hm.obj_index = i;
+		hw_entry_point(&hm, &hr);
+		memcpy(&rESP_HPI_ISTREAM_OPEN[adapter][i], &hr,
+			sizeof(rESP_HPI_ISTREAM_OPEN[0][0]));
+		instream_user_open[adapter][i].open_flag = 0;
+		instream_user_open[adapter][i].h_owner = NULL;
+	}
+
+	/* call to HPI_MIXER_OPEN */
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN);
+	hm.adapter_index = adapter;
+	hw_entry_point(&hm, &hr);
+	memcpy(&rESP_HPI_MIXER_OPEN[adapter], &hr,
+		sizeof(rESP_HPI_MIXER_OPEN[0]));
+
+	return gRESP_HPI_SUBSYS_FIND_ADAPTERS.h.error;
+}
+
+static void HPIMSGX__reset(u16 adapter_index)
+{
+	int i;
+	u16 adapter;
+	struct hpi_response hr;
+
+	if (adapter_index == HPIMSGX_ALLADAPTERS) {
+		/* reset all responses to contain errors */
+		hpi_init_response(&hr, HPI_OBJ_SUBSYSTEM,
+			HPI_SUBSYS_FIND_ADAPTERS, 0);
+		memcpy(&gRESP_HPI_SUBSYS_FIND_ADAPTERS, &hr,
+			sizeof(gRESP_HPI_SUBSYS_FIND_ADAPTERS));
+
+		for (adapter = 0; adapter < HPI_MAX_ADAPTERS; adapter++) {
+
+			hpi_init_response(&hr, HPI_OBJ_ADAPTER,
+				HPI_ADAPTER_OPEN, HPI_ERROR_BAD_ADAPTER);
+			memcpy(&rESP_HPI_ADAPTER_OPEN[adapter], &hr,
+				sizeof(rESP_HPI_ADAPTER_OPEN[adapter]));
+
+			hpi_init_response(&hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN,
+				HPI_ERROR_INVALID_OBJ);
+			memcpy(&rESP_HPI_MIXER_OPEN[adapter], &hr,
+				sizeof(rESP_HPI_MIXER_OPEN[adapter]));
+
+			for (i = 0; i < HPI_MAX_STREAMS; i++) {
+				hpi_init_response(&hr, HPI_OBJ_OSTREAM,
+					HPI_OSTREAM_OPEN,
+					HPI_ERROR_INVALID_OBJ);
+				memcpy(&rESP_HPI_OSTREAM_OPEN[adapter][i],
+					&hr,
+					sizeof(rESP_HPI_OSTREAM_OPEN[adapter]
+						[i]));
+				hpi_init_response(&hr, HPI_OBJ_ISTREAM,
+					HPI_ISTREAM_OPEN,
+					HPI_ERROR_INVALID_OBJ);
+				memcpy(&rESP_HPI_ISTREAM_OPEN[adapter][i],
+					&hr,
+					sizeof(rESP_HPI_ISTREAM_OPEN[adapter]
+						[i]));
+			}
+		}
+	} else if (adapter_index < HPI_MAX_ADAPTERS) {
+		rESP_HPI_ADAPTER_OPEN[adapter_index].h.error =
+			HPI_ERROR_BAD_ADAPTER;
+		rESP_HPI_MIXER_OPEN[adapter_index].h.error =
+			HPI_ERROR_INVALID_OBJ;
+		for (i = 0; i < HPI_MAX_STREAMS; i++) {
+			rESP_HPI_OSTREAM_OPEN[adapter_index][i].h.error =
+				HPI_ERROR_INVALID_OBJ;
+			rESP_HPI_ISTREAM_OPEN[adapter_index][i].h.error =
+				HPI_ERROR_INVALID_OBJ;
+		}
+		if (gRESP_HPI_SUBSYS_FIND_ADAPTERS.
+			s.aw_adapter_list[adapter_index]) {
+			gRESP_HPI_SUBSYS_FIND_ADAPTERS.
+				s.aw_adapter_list[adapter_index] = 0;
+			gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.num_adapters--;
+		}
+	}
+}
+
+static u16 HPIMSGX__init(struct hpi_message *phm,
+	/* HPI_SUBSYS_CREATE_ADAPTER structure with */
+	/* resource list or NULL=find all */
+	struct hpi_response *phr
+	/* response from HPI_ADAPTER_GET_INFO */
+	)
+{
+	hpi_handler_func *entry_point_func;
+	struct hpi_response hr;
+
+	if (gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.num_adapters >= HPI_MAX_ADAPTERS)
+		return HPI_ERROR_BAD_ADAPTER_NUMBER;
+
+	/* Init response here so we can pass in previous adapter list */
+	hpi_init_response(&hr, phm->object, phm->function,
+		HPI_ERROR_INVALID_OBJ);
+	memcpy(hr.u.s.aw_adapter_list,
+		gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.aw_adapter_list,
+		sizeof(gRESP_HPI_SUBSYS_FIND_ADAPTERS.s.aw_adapter_list));
+
+	entry_point_func =
+		hpi_lookup_entry_point_function(phm->u.s.resource.r.pci);
+
+	if (entry_point_func) {
+		HPI_DEBUG_MESSAGE(DEBUG, phm);
+		entry_point_func(phm, &hr);
+	} else {
+		phr->error = HPI_ERROR_PROCESSING_MESSAGE;
+		return phr->error;
+	}
+	if (hr.error == 0) {
+		/* the adapter was created succesfully
+		   save the mapping for future use */
+		hpi_entry_points[hr.u.s.adapter_index] = entry_point_func;
+		/* prepare adapter (pre-open streams etc.) */
+		HPI_DEBUG_LOG(DEBUG,
+			"HPI_SUBSYS_CREATE_ADAPTER successful,"
+			" preparing adapter\n");
+		adapter_prepare(hr.u.s.adapter_index);
+	}
+	memcpy(phr, &hr, hr.size);
+	return phr->error;
+}
+
+static void HPIMSGX__cleanup(u16 adapter_index, void *h_owner)
+{
+	int i, adapter, adapter_limit;
+
+	if (!h_owner)
+		return;
+
+	if (adapter_index == HPIMSGX_ALLADAPTERS) {
+		adapter = 0;
+		adapter_limit = HPI_MAX_ADAPTERS;
+	} else {
+		adapter = adapter_index;
+		adapter_limit = adapter + 1;
+	}
+
+	for (; adapter < adapter_limit; adapter++) {
+		/*      printk(KERN_INFO "Cleanup adapter #%d\n",wAdapter); */
+		for (i = 0; i < HPI_MAX_STREAMS; i++) {
+			if (h_owner ==
+				outstream_user_open[adapter][i].h_owner) {
+				struct hpi_message hm;
+				struct hpi_response hr;
+
+				HPI_DEBUG_LOG(DEBUG,
+					"close adapter %d ostream %d\n",
+					adapter, i);
+
+				hpi_init_message_response(&hm, &hr,
+					HPI_OBJ_OSTREAM, HPI_OSTREAM_RESET);
+				hm.adapter_index = (u16)adapter;
+				hm.obj_index = (u16)i;
+				hw_entry_point(&hm, &hr);
+
+				hm.function = HPI_OSTREAM_HOSTBUFFER_FREE;
+				hw_entry_point(&hm, &hr);
+
+				hm.function = HPI_OSTREAM_GROUP_RESET;
+				hw_entry_point(&hm, &hr);
+
+				outstream_user_open[adapter][i].open_flag = 0;
+				outstream_user_open[adapter][i].h_owner =
+					NULL;
+			}
+			if (h_owner == instream_user_open[adapter][i].h_owner) {
+				struct hpi_message hm;
+				struct hpi_response hr;
+
+				HPI_DEBUG_LOG(DEBUG,
+					"close adapter %d istream %d\n",
+					adapter, i);
+
+				hpi_init_message_response(&hm, &hr,
+					HPI_OBJ_ISTREAM, HPI_ISTREAM_RESET);
+				hm.adapter_index = (u16)adapter;
+				hm.obj_index = (u16)i;
+				hw_entry_point(&hm, &hr);
+
+				hm.function = HPI_ISTREAM_HOSTBUFFER_FREE;
+				hw_entry_point(&hm, &hr);
+
+				hm.function = HPI_ISTREAM_GROUP_RESET;
+				hw_entry_point(&hm, &hr);
+
+				instream_user_open[adapter][i].open_flag = 0;
+				instream_user_open[adapter][i].h_owner = NULL;
+			}
+		}
+	}
+}
diff --git a/linux/sound/pci/asihpi/hpimsgx.h b/linux/sound/pci/asihpi/hpimsgx.h
new file mode 100644
index 0000000..fd49e75
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpimsgx.h
@@ -0,0 +1,36 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+ HPI Extended Message Handler Functions
+
+(C) Copyright AudioScience Inc. 1997-2003
+******************************************************************************/
+
+#ifndef _HPIMSGX_H_
+#define _HPIMSGX_H_
+
+#include "hpi_internal.h"
+
+#define HPIMSGX_ALLADAPTERS     (0xFFFF)
+
+void hpi_send_recv_ex(struct hpi_message *phm, struct hpi_response *phr,
+	void *h_owner);
+
+#define HPI_MESSAGE_LOWER_LAYER hpi_send_recv_ex
+
+#endif				/* _HPIMSGX_H_ */
diff --git a/linux/sound/pci/asihpi/hpioctl.c b/linux/sound/pci/asihpi/hpioctl.c
new file mode 100644
index 0000000..22dbd91
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpioctl.c
@@ -0,0 +1,491 @@
+/*******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+Common Linux HPI ioctl and module probe/remove functions
+*******************************************************************************/
+#define SOURCEFILE_NAME "hpioctl.c"
+
+#include "hpi_internal.h"
+#include "hpimsginit.h"
+#include "hpidebug.h"
+#include "hpimsgx.h"
+#include "hpioctl.h"
+
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <asm/uaccess.h>
+#include <linux/stringify.h>
+
+#ifdef MODULE_FIRMWARE
+MODULE_FIRMWARE("asihpi/dsp5000.bin");
+MODULE_FIRMWARE("asihpi/dsp6200.bin");
+MODULE_FIRMWARE("asihpi/dsp6205.bin");
+MODULE_FIRMWARE("asihpi/dsp6400.bin");
+MODULE_FIRMWARE("asihpi/dsp6600.bin");
+MODULE_FIRMWARE("asihpi/dsp8700.bin");
+MODULE_FIRMWARE("asihpi/dsp8900.bin");
+#endif
+
+static int prealloc_stream_buf;
+module_param(prealloc_stream_buf, int, S_IRUGO);
+MODULE_PARM_DESC(prealloc_stream_buf,
+	"preallocate size for per-adapter stream buffer");
+
+/* Allow the debug level to be changed after module load.
+ E.g.   echo 2 > /sys/module/asihpi/parameters/hpiDebugLevel
+*/
+module_param(hpi_debug_level, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(hpi_debug_level, "debug verbosity 0..5");
+
+/* List of adapters found */
+static struct hpi_adapter adapters[HPI_MAX_ADAPTERS];
+
+/* Wrapper function to HPI_Message to enable dumping of the
+   message and response types.
+*/
+static void hpi_send_recv_f(struct hpi_message *phm, struct hpi_response *phr,
+	struct file *file)
+{
+	int adapter = phm->adapter_index;
+
+	if ((adapter >= HPI_MAX_ADAPTERS || adapter < 0)
+		&& (phm->object != HPI_OBJ_SUBSYSTEM))
+		phr->error = HPI_ERROR_INVALID_OBJ_INDEX;
+	else
+		hpi_send_recv_ex(phm, phr, file);
+}
+
+/* This is called from hpifunc.c functions, called by ALSA
+ * (or other kernel process) In this case there is no file descriptor
+ * available for the message cache code
+ */
+void hpi_send_recv(struct hpi_message *phm, struct hpi_response *phr)
+{
+	hpi_send_recv_f(phm, phr, HOWNER_KERNEL);
+}
+
+EXPORT_SYMBOL(hpi_send_recv);
+/* for radio-asihpi */
+
+int asihpi_hpi_release(struct file *file)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+/* HPI_DEBUG_LOG(INFO,"hpi_release file %p, pid %d\n", file, current->pid); */
+	/* close the subsystem just in case the application forgot to. */
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_CLOSE);
+	hpi_send_recv_ex(&hm, &hr, file);
+	return 0;
+}
+
+long asihpi_hpi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct hpi_ioctl_linux __user *phpi_ioctl_data;
+	void __user *puhm;
+	void __user *puhr;
+	union hpi_message_buffer_v1 *hm;
+	union hpi_response_buffer_v1 *hr;
+	u16 res_max_size;
+	u32 uncopied_bytes;
+	struct hpi_adapter *pa = NULL;
+	int err = 0;
+
+	if (cmd != HPI_IOCTL_LINUX)
+		return -EINVAL;
+
+	hm = kmalloc(sizeof(*hm), GFP_KERNEL);
+	hr = kmalloc(sizeof(*hr), GFP_KERNEL);
+	if (!hm || !hr) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	phpi_ioctl_data = (struct hpi_ioctl_linux __user *)arg;
+
+	/* Read the message and response pointers from user space.  */
+	if (get_user(puhm, &phpi_ioctl_data->phm) ||
+	    get_user(puhr, &phpi_ioctl_data->phr)) {
+		err = -EFAULT;
+		goto out;
+	}
+
+	/* Now read the message size and data from user space.  */
+	if (get_user(hm->h.size, (u16 __user *)puhm)) {
+		err = -EFAULT;
+		goto out;
+	}
+	if (hm->h.size > sizeof(*hm))
+		hm->h.size = sizeof(*hm);
+
+	/*printk(KERN_INFO "message size %d\n", hm->h.wSize); */
+
+	uncopied_bytes = copy_from_user(hm, puhm, hm->h.size);
+	if (uncopied_bytes) {
+		HPI_DEBUG_LOG(ERROR, "uncopied bytes %d\n", uncopied_bytes);
+		err = -EFAULT;
+		goto out;
+	}
+
+	if (get_user(res_max_size, (u16 __user *)puhr)) {
+		err = -EFAULT;
+		goto out;
+	}
+	/* printk(KERN_INFO "user response size %d\n", res_max_size); */
+	if (res_max_size < sizeof(struct hpi_response_header)) {
+		HPI_DEBUG_LOG(WARNING, "small res size %d\n", res_max_size);
+		err = -EFAULT;
+		goto out;
+	}
+
+	pa = &adapters[hm->h.adapter_index];
+	hr->h.size = 0;
+	if (hm->h.object == HPI_OBJ_SUBSYSTEM) {
+		switch (hm->h.function) {
+		case HPI_SUBSYS_CREATE_ADAPTER:
+		case HPI_SUBSYS_DELETE_ADAPTER:
+			/* Application must not use these functions! */
+			hr->h.size = sizeof(hr->h);
+			hr->h.error = HPI_ERROR_INVALID_OPERATION;
+			hr->h.function = hm->h.function;
+			uncopied_bytes = copy_to_user(puhr, hr, hr->h.size);
+			if (uncopied_bytes)
+				err = -EFAULT;
+			else
+				err = 0;
+			goto out;
+
+		default:
+			hpi_send_recv_f(&hm->m0, &hr->r0, file);
+		}
+	} else {
+		u16 __user *ptr = NULL;
+		u32 size = 0;
+
+		/* -1=no data 0=read from user mem, 1=write to user mem */
+		int wrflag = -1;
+		u32 adapter = hm->h.adapter_index;
+
+		if ((hm->h.adapter_index > HPI_MAX_ADAPTERS) || (!pa->type)) {
+			hpi_init_response(&hr->r0, HPI_OBJ_ADAPTER,
+				HPI_ADAPTER_OPEN,
+				HPI_ERROR_BAD_ADAPTER_NUMBER);
+
+			uncopied_bytes =
+				copy_to_user(puhr, hr, sizeof(hr->h));
+			if (uncopied_bytes)
+				err = -EFAULT;
+			else
+				err = 0;
+			goto out;
+		}
+
+		if (mutex_lock_interruptible(&adapters[adapter].mutex)) {
+			err = -EINTR;
+			goto out;
+		}
+
+		/* Dig out any pointers embedded in the message.  */
+		switch (hm->h.function) {
+		case HPI_OSTREAM_WRITE:
+		case HPI_ISTREAM_READ:{
+				/* Yes, sparse, this is correct. */
+				ptr = (u16 __user *)hm->m0.u.d.u.data.pb_data;
+				size = hm->m0.u.d.u.data.data_size;
+
+				/* Allocate buffer according to application request.
+				   ?Is it better to alloc/free for the duration
+				   of the transaction?
+				 */
+				if (pa->buffer_size < size) {
+					HPI_DEBUG_LOG(DEBUG,
+						"realloc adapter %d stream "
+						"buffer from %zd to %d\n",
+						hm->h.adapter_index,
+						pa->buffer_size, size);
+					if (pa->p_buffer) {
+						pa->buffer_size = 0;
+						vfree(pa->p_buffer);
+					}
+					pa->p_buffer = vmalloc(size);
+					if (pa->p_buffer)
+						pa->buffer_size = size;
+					else {
+						HPI_DEBUG_LOG(ERROR,
+							"HPI could not allocate "
+							"stream buffer size %d\n",
+							size);
+
+						mutex_unlock(&adapters
+							[adapter].mutex);
+						err = -EINVAL;
+						goto out;
+					}
+				}
+
+				hm->m0.u.d.u.data.pb_data = pa->p_buffer;
+				if (hm->h.function == HPI_ISTREAM_READ)
+					/* from card, WRITE to user mem */
+					wrflag = 1;
+				else
+					wrflag = 0;
+				break;
+			}
+
+		default:
+			size = 0;
+			break;
+		}
+
+		if (size && (wrflag == 0)) {
+			uncopied_bytes =
+				copy_from_user(pa->p_buffer, ptr, size);
+			if (uncopied_bytes)
+				HPI_DEBUG_LOG(WARNING,
+					"missed %d of %d "
+					"bytes from user\n", uncopied_bytes,
+					size);
+		}
+
+		hpi_send_recv_f(&hm->m0, &hr->r0, file);
+
+		if (size && (wrflag == 1)) {
+			uncopied_bytes =
+				copy_to_user(ptr, pa->p_buffer, size);
+			if (uncopied_bytes)
+				HPI_DEBUG_LOG(WARNING,
+					"missed %d of %d " "bytes to user\n",
+					uncopied_bytes, size);
+		}
+
+		mutex_unlock(&adapters[adapter].mutex);
+	}
+
+	/* on return response size must be set */
+	/*printk(KERN_INFO "response size %d\n", hr->h.wSize); */
+
+	if (!hr->h.size) {
+		HPI_DEBUG_LOG(ERROR, "response zero size\n");
+		err = -EFAULT;
+		goto out;
+	}
+
+	if (hr->h.size > res_max_size) {
+		HPI_DEBUG_LOG(ERROR, "response too big %d %d\n", hr->h.size,
+			res_max_size);
+		/*HPI_DEBUG_MESSAGE(ERROR, hm); */
+		err = -EFAULT;
+		goto out;
+	}
+
+	uncopied_bytes = copy_to_user(puhr, hr, hr->h.size);
+	if (uncopied_bytes) {
+		HPI_DEBUG_LOG(ERROR, "uncopied bytes %d\n", uncopied_bytes);
+		err = -EFAULT;
+		goto out;
+	}
+
+out:
+	kfree(hm);
+	kfree(hr);
+	return err;
+}
+
+int __devinit asihpi_adapter_probe(struct pci_dev *pci_dev,
+	const struct pci_device_id *pci_id)
+{
+	int err, idx, nm;
+	unsigned int memlen;
+	struct hpi_message hm;
+	struct hpi_response hr;
+	struct hpi_adapter adapter;
+	struct hpi_pci pci;
+
+	memset(&adapter, 0, sizeof(adapter));
+
+	printk(KERN_DEBUG "probe PCI device (%04x:%04x,%04x:%04x,%04x)\n",
+		pci_dev->vendor, pci_dev->device, pci_dev->subsystem_vendor,
+		pci_dev->subsystem_device, pci_dev->devfn);
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_CREATE_ADAPTER);
+	hpi_init_response(&hr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_CREATE_ADAPTER,
+		HPI_ERROR_PROCESSING_MESSAGE);
+
+	hm.adapter_index = -1;	/* an invalid index */
+
+	/* fill in HPI_PCI information from kernel provided information */
+	adapter.pci = pci_dev;
+
+	nm = HPI_MAX_ADAPTER_MEM_SPACES;
+
+	for (idx = 0; idx < nm; idx++) {
+		HPI_DEBUG_LOG(INFO, "resource %d %s %08llx-%08llx %04llx\n",
+			idx, pci_dev->resource[idx].name,
+			(unsigned long long)pci_resource_start(pci_dev, idx),
+			(unsigned long long)pci_resource_end(pci_dev, idx),
+			(unsigned long long)pci_resource_flags(pci_dev, idx));
+
+		if (pci_resource_flags(pci_dev, idx) & IORESOURCE_MEM) {
+			memlen = pci_resource_len(pci_dev, idx);
+			adapter.ap_remapped_mem_base[idx] =
+				ioremap(pci_resource_start(pci_dev, idx),
+				memlen);
+			if (!adapter.ap_remapped_mem_base[idx]) {
+				HPI_DEBUG_LOG(ERROR,
+					"ioremap failed, aborting\n");
+				/* unmap previously mapped pci mem space */
+				goto err;
+			}
+		}
+
+		pci.ap_mem_base[idx] = adapter.ap_remapped_mem_base[idx];
+	}
+
+	/* could replace Pci with direct pointer to pci_dev for linux
+	   Instead wrap accessor functions for IDs etc.
+	   Would it work for windows?
+	 */
+	pci.bus_number = pci_dev->bus->number;
+	pci.vendor_id = (u16)pci_dev->vendor;
+	pci.device_id = (u16)pci_dev->device;
+	pci.subsys_vendor_id = (u16)(pci_dev->subsystem_vendor & 0xffff);
+	pci.subsys_device_id = (u16)(pci_dev->subsystem_device & 0xffff);
+	pci.device_number = pci_dev->devfn;
+	pci.interrupt = pci_dev->irq;
+	pci.p_os_data = pci_dev;
+
+	hm.u.s.resource.bus_type = HPI_BUS_PCI;
+	hm.u.s.resource.r.pci = &pci;
+
+	/* call CreateAdapterObject on the relevant hpi module */
+	hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL);
+	if (hr.error)
+		goto err;
+
+	if (prealloc_stream_buf) {
+		adapter.p_buffer = vmalloc(prealloc_stream_buf);
+		if (!adapter.p_buffer) {
+			HPI_DEBUG_LOG(ERROR,
+				"HPI could not allocate "
+				"kernel buffer size %d\n",
+				prealloc_stream_buf);
+			goto err;
+		}
+	}
+
+	adapter.index = hr.u.s.adapter_index;
+	adapter.type = hr.u.s.aw_adapter_list[adapter.index];
+	hm.adapter_index = adapter.index;
+
+	err = hpi_adapter_open(NULL, adapter.index);
+	if (err)
+		goto err;
+
+	adapter.snd_card_asihpi = NULL;
+	/* WARNING can't init mutex in 'adapter'
+	 * and then copy it to adapters[] ?!?!
+	 */
+	adapters[hr.u.s.adapter_index] = adapter;
+	mutex_init(&adapters[adapter.index].mutex);
+	pci_set_drvdata(pci_dev, &adapters[adapter.index]);
+
+	printk(KERN_INFO "probe found adapter ASI%04X HPI index #%d.\n",
+		adapter.type, adapter.index);
+
+	return 0;
+
+err:
+	for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; idx++) {
+		if (adapter.ap_remapped_mem_base[idx]) {
+			iounmap(adapter.ap_remapped_mem_base[idx]);
+			adapter.ap_remapped_mem_base[idx] = NULL;
+		}
+	}
+
+	if (adapter.p_buffer) {
+		adapter.buffer_size = 0;
+		vfree(adapter.p_buffer);
+	}
+
+	HPI_DEBUG_LOG(ERROR, "adapter_probe failed\n");
+	return -ENODEV;
+}
+
+void __devexit asihpi_adapter_remove(struct pci_dev *pci_dev)
+{
+	int idx;
+	struct hpi_message hm;
+	struct hpi_response hr;
+	struct hpi_adapter *pa;
+	pa = pci_get_drvdata(pci_dev);
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_DELETE_ADAPTER);
+	hm.adapter_index = pa->index;
+	hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL);
+
+	/* unmap PCI memory space, mapped during device init. */
+	for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; idx++) {
+		if (pa->ap_remapped_mem_base[idx]) {
+			iounmap(pa->ap_remapped_mem_base[idx]);
+			pa->ap_remapped_mem_base[idx] = NULL;
+		}
+	}
+
+	if (pa->p_buffer) {
+		pa->buffer_size = 0;
+		vfree(pa->p_buffer);
+	}
+
+	pci_set_drvdata(pci_dev, NULL);
+	/*
+	   printk(KERN_INFO "PCI device (%04x:%04x,%04x:%04x,%04x),"
+	   " HPI index # %d, removed.\n",
+	   pci_dev->vendor, pci_dev->device,
+	   pci_dev->subsystem_vendor,
+	   pci_dev->subsystem_device, pci_dev->devfn,
+	   pa->index);
+	 */
+}
+
+void __init asihpi_init(void)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	memset(adapters, 0, sizeof(adapters));
+
+	printk(KERN_INFO "ASIHPI driver " HPI_VER_STRING "\n");
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_DRIVER_LOAD);
+	hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL);
+}
+
+void asihpi_exit(void)
+{
+	struct hpi_message hm;
+	struct hpi_response hr;
+
+	hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM,
+		HPI_SUBSYS_DRIVER_UNLOAD);
+	hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL);
+}
diff --git a/linux/sound/pci/asihpi/hpioctl.h b/linux/sound/pci/asihpi/hpioctl.h
new file mode 100644
index 0000000..847f72f
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpioctl.h
@@ -0,0 +1,38 @@
+/*******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+Linux HPI ioctl, and shared module init functions
+*******************************************************************************/
+
+int __devinit asihpi_adapter_probe(struct pci_dev *pci_dev,
+	const struct pci_device_id *pci_id);
+void __devexit asihpi_adapter_remove(struct pci_dev *pci_dev);
+void __init asihpi_init(void);
+void __exit asihpi_exit(void);
+
+int asihpi_hpi_release(struct file *file);
+
+long asihpi_hpi_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+
+/* This is called from hpifunc.c functions, called by ALSA
+ * (or other kernel process) In this case there is no file descriptor
+ * available for the message cache code
+ */
+void hpi_send_recv(struct hpi_message *phm, struct hpi_response *phr);
+
+#define HOWNER_KERNEL ((void *)-1)
diff --git a/linux/sound/pci/asihpi/hpios.c b/linux/sound/pci/asihpi/hpios.c
new file mode 100644
index 0000000..742ee12
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpios.c
@@ -0,0 +1,91 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+HPI Operating System function implementation for Linux
+
+(C) Copyright AudioScience Inc. 1997-2003
+******************************************************************************/
+#define SOURCEFILE_NAME "hpios.c"
+#include "hpi_internal.h"
+#include "hpidebug.h"
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+void hpios_delay_micro_seconds(u32 num_micro_sec)
+{
+	if ((usecs_to_jiffies(num_micro_sec) > 1) && !in_interrupt()) {
+		/* MUST NOT SCHEDULE IN INTERRUPT CONTEXT! */
+		schedule_timeout_uninterruptible(usecs_to_jiffies
+			(num_micro_sec));
+	} else if (num_micro_sec <= 2000)
+		udelay(num_micro_sec);
+	else
+		mdelay(num_micro_sec / 1000);
+
+}
+
+void hpios_locked_mem_init(void)
+{
+}
+
+/** Allocated an area of locked memory for bus master DMA operations.
+
+On error, return -ENOMEM, and *pMemArea.size = 0
+*/
+u16 hpios_locked_mem_alloc(struct consistent_dma_area *p_mem_area, u32 size,
+	struct pci_dev *pdev)
+{
+	/*?? any benefit in using managed dmam_alloc_coherent? */
+	p_mem_area->vaddr =
+		dma_alloc_coherent(&pdev->dev, size, &p_mem_area->dma_handle,
+		GFP_DMA32 | GFP_KERNEL);
+
+	if (p_mem_area->vaddr) {
+		HPI_DEBUG_LOG(DEBUG, "allocated %d bytes, dma 0x%x vma %p\n",
+			size, (unsigned int)p_mem_area->dma_handle,
+			p_mem_area->vaddr);
+		p_mem_area->pdev = &pdev->dev;
+		p_mem_area->size = size;
+		return 0;
+	} else {
+		HPI_DEBUG_LOG(WARNING,
+			"failed to allocate %d bytes locked memory\n", size);
+		p_mem_area->size = 0;
+		return -ENOMEM;
+	}
+}
+
+u16 hpios_locked_mem_free(struct consistent_dma_area *p_mem_area)
+{
+	if (p_mem_area->size) {
+		dma_free_coherent(p_mem_area->pdev, p_mem_area->size,
+			p_mem_area->vaddr, p_mem_area->dma_handle);
+		HPI_DEBUG_LOG(DEBUG, "freed %lu bytes, dma 0x%x vma %p\n",
+			(unsigned long)p_mem_area->size,
+			(unsigned int)p_mem_area->dma_handle,
+			p_mem_area->vaddr);
+		p_mem_area->size = 0;
+		return 0;
+	} else {
+		return 1;
+	}
+}
+
+void hpios_locked_mem_free_all(void)
+{
+}
diff --git a/linux/sound/pci/asihpi/hpios.h b/linux/sound/pci/asihpi/hpios.h
new file mode 100644
index 0000000..370f39b
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpios.h
@@ -0,0 +1,169 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+HPI Operating System Specific macros for Linux Kernel driver
+
+(C) Copyright AudioScience Inc. 1997-2003
+******************************************************************************/
+#ifndef _HPIOS_H_
+#define _HPIOS_H_
+
+#undef HPI_OS_LINUX_KERNEL
+#define HPI_OS_LINUX_KERNEL
+
+#define HPI_OS_DEFINED
+#define HPI_KERNEL_MODE
+
+#define HPI_REASSIGN_DUPLICATE_ADAPTER_IDX
+
+#include <linux/io.h>
+#include <asm/system.h>
+#include <linux/ioctl.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+
+#define HPI_NO_OS_FILE_OPS
+
+#ifdef CONFIG_64BIT
+#define HPI64BIT
+#endif
+
+/** Details of a memory area allocated with  pci_alloc_consistent
+Need all info for parameters to pci_free_consistent
+*/
+struct consistent_dma_area {
+	struct device *pdev;
+	/* looks like dma-mapping dma_devres ?! */
+	size_t size;
+	void *vaddr;
+	dma_addr_t dma_handle;
+};
+
+static inline u16 hpios_locked_mem_get_phys_addr(struct consistent_dma_area
+	*locked_mem_handle, u32 *p_physical_addr)
+{
+	*p_physical_addr = locked_mem_handle->dma_handle;
+	return 0;
+}
+
+static inline u16 hpios_locked_mem_get_virt_addr(struct consistent_dma_area
+	*locked_mem_handle, void **pp_virtual_addr)
+{
+	*pp_virtual_addr = locked_mem_handle->vaddr;
+	return 0;
+}
+
+static inline u16 hpios_locked_mem_valid(struct consistent_dma_area
+	*locked_mem_handle)
+{
+	return locked_mem_handle->size != 0;
+}
+
+struct hpi_ioctl_linux {
+	void __user *phm;
+	void __user *phr;
+};
+
+/* Conflict?: H is already used by a number of drivers hid, bluetooth hci,
+   and some sound drivers sb16, hdsp, emu10k. AFAIK 0xFC is ununsed command
+*/
+#define HPI_IOCTL_LINUX _IOWR('H', 0xFC, struct hpi_ioctl_linux)
+
+#define HPI_DEBUG_FLAG_ERROR   KERN_ERR
+#define HPI_DEBUG_FLAG_WARNING KERN_WARNING
+#define HPI_DEBUG_FLAG_NOTICE  KERN_NOTICE
+#define HPI_DEBUG_FLAG_INFO    KERN_INFO
+#define HPI_DEBUG_FLAG_DEBUG   KERN_DEBUG
+#define HPI_DEBUG_FLAG_VERBOSE KERN_DEBUG	/* kernel has no verbose */
+
+#include <linux/spinlock.h>
+
+#define HPI_LOCKING
+
+struct hpios_spinlock {
+	spinlock_t lock;	/* SEE hpios_spinlock */
+	int lock_context;
+};
+
+/* The reason for all this evilness is that ALSA calls some of a drivers
+ * operators in atomic context, and some not.  But all our functions channel
+ * through the HPI_Message conduit, so we can't handle the different context
+ * per function
+ */
+#define IN_LOCK_BH 1
+#define IN_LOCK_IRQ 0
+static inline void cond_lock(struct hpios_spinlock *l)
+{
+	if (irqs_disabled()) {
+		/* NO bh or isr can execute on this processor,
+		   so ordinary lock will do
+		 */
+		spin_lock(&((l)->lock));
+		l->lock_context = IN_LOCK_IRQ;
+	} else {
+		spin_lock_bh(&((l)->lock));
+		l->lock_context = IN_LOCK_BH;
+	}
+}
+
+static inline void cond_unlock(struct hpios_spinlock *l)
+{
+	if (l->lock_context == IN_LOCK_BH)
+		spin_unlock_bh(&((l)->lock));
+	else
+		spin_unlock(&((l)->lock));
+}
+
+#define hpios_msgxlock_init(obj)      spin_lock_init(&(obj)->lock)
+#define hpios_msgxlock_lock(obj)   cond_lock(obj)
+#define hpios_msgxlock_un_lock(obj) cond_unlock(obj)
+
+#define hpios_dsplock_init(obj)       spin_lock_init(&(obj)->dsp_lock.lock)
+#define hpios_dsplock_lock(obj)    cond_lock(&(obj)->dsp_lock)
+#define hpios_dsplock_unlock(obj)  cond_unlock(&(obj)->dsp_lock)
+
+#ifdef CONFIG_SND_DEBUG
+#define HPI_DEBUG
+#endif
+
+#define HPI_ALIST_LOCKING
+#define hpios_alistlock_init(obj)    spin_lock_init(&((obj)->list_lock.lock))
+#define hpios_alistlock_lock(obj) spin_lock(&((obj)->list_lock.lock))
+#define hpios_alistlock_un_lock(obj) spin_unlock(&((obj)->list_lock.lock))
+
+struct hpi_adapter {
+	/* mutex prevents contention for one card
+	   between multiple user programs (via ioctl) */
+	struct mutex mutex;
+	u16 index;
+	u16 type;
+
+	/* ALSA card structure */
+	void *snd_card_asihpi;
+
+	char *p_buffer;
+	size_t buffer_size;
+	struct pci_dev *pci;
+	void __iomem *ap_remapped_mem_base[HPI_MAX_ADAPTER_MEM_SPACES];
+};
+
+#endif
diff --git a/linux/sound/pci/asihpi/hpipcida.h b/linux/sound/pci/asihpi/hpipcida.h
new file mode 100644
index 0000000..bb30868
--- /dev/null
+++ b/linux/sound/pci/asihpi/hpipcida.h
@@ -0,0 +1,37 @@
+/******************************************************************************
+
+    AudioScience HPI driver
+    Copyright (C) 1997-2010  AudioScience Inc. <support@audioscience.com>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of version 2 of the GNU General Public License as
+    published by the Free Software Foundation;
+
+    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
+
+ Array initializer for PCI card IDs
+
+(C) Copyright AudioScience Inc. 1998-2003
+*******************************************************************************/
+
+/*NOTE: when adding new lines to this header file
+  they MUST be grouped by HPI entry point.
+*/
+
+{
+HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_DSP6205,
+		HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
+		(kernel_ulong_t) HPI_6205}
+, {
+HPI_PCI_VENDOR_ID_TI, HPI_PCI_DEV_ID_PCI2040,
+		HPI_PCI_VENDOR_ID_AUDIOSCIENCE, PCI_ANY_ID, 0, 0,
+		(kernel_ulong_t) HPI_6000}
+, {
+0}
diff --git a/linux/sound/pci/atiixp.c b/linux/sound/pci/atiixp.c
new file mode 100644
index 0000000..49d572a
--- /dev/null
+++ b/linux/sound/pci/atiixp.c
@@ -0,0 +1,1726 @@
+/*
+ *   ALSA driver for ATI IXP 150/200/250/300 AC97 controllers
+ *
+ *	Copyright (c) 2004 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 <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ATI IXP AC97 controller");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ATI,IXP150/200/250/300/400/600}}");
+
+static int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+static int ac97_clock = 48000;
+static char *ac97_quirk;
+static int spdif_aclink = 1;
+static int ac97_codec = -1;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for ATI IXP controller.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for ATI IXP controller.");
+module_param(ac97_clock, int, 0444);
+MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (default 48000Hz).");
+module_param(ac97_quirk, charp, 0444);
+MODULE_PARM_DESC(ac97_quirk, "AC'97 workaround for strange hardware.");
+module_param(ac97_codec, int, 0444);
+MODULE_PARM_DESC(ac97_codec, "Specify codec instead of probing.");
+module_param(spdif_aclink, bool, 0444);
+MODULE_PARM_DESC(spdif_aclink, "S/PDIF over AC-link.");
+
+/* just for backward compatibility */
+static int enable;
+module_param(enable, bool, 0444);
+
+
+/*
+ */
+
+#define ATI_REG_ISR			0x00	/* interrupt source */
+#define  ATI_REG_ISR_IN_XRUN		(1U<<0)
+#define  ATI_REG_ISR_IN_STATUS		(1U<<1)
+#define  ATI_REG_ISR_OUT_XRUN		(1U<<2)
+#define  ATI_REG_ISR_OUT_STATUS		(1U<<3)
+#define  ATI_REG_ISR_SPDF_XRUN		(1U<<4)
+#define  ATI_REG_ISR_SPDF_STATUS	(1U<<5)
+#define  ATI_REG_ISR_PHYS_INTR		(1U<<8)
+#define  ATI_REG_ISR_PHYS_MISMATCH	(1U<<9)
+#define  ATI_REG_ISR_CODEC0_NOT_READY	(1U<<10)
+#define  ATI_REG_ISR_CODEC1_NOT_READY	(1U<<11)
+#define  ATI_REG_ISR_CODEC2_NOT_READY	(1U<<12)
+#define  ATI_REG_ISR_NEW_FRAME		(1U<<13)
+
+#define ATI_REG_IER			0x04	/* interrupt enable */
+#define  ATI_REG_IER_IN_XRUN_EN		(1U<<0)
+#define  ATI_REG_IER_IO_STATUS_EN	(1U<<1)
+#define  ATI_REG_IER_OUT_XRUN_EN	(1U<<2)
+#define  ATI_REG_IER_OUT_XRUN_COND	(1U<<3)
+#define  ATI_REG_IER_SPDF_XRUN_EN	(1U<<4)
+#define  ATI_REG_IER_SPDF_STATUS_EN	(1U<<5)
+#define  ATI_REG_IER_PHYS_INTR_EN	(1U<<8)
+#define  ATI_REG_IER_PHYS_MISMATCH_EN	(1U<<9)
+#define  ATI_REG_IER_CODEC0_INTR_EN	(1U<<10)
+#define  ATI_REG_IER_CODEC1_INTR_EN	(1U<<11)
+#define  ATI_REG_IER_CODEC2_INTR_EN	(1U<<12)
+#define  ATI_REG_IER_NEW_FRAME_EN	(1U<<13)	/* (RO */
+#define  ATI_REG_IER_SET_BUS_BUSY	(1U<<14)	/* (WO) audio is running */
+
+#define ATI_REG_CMD			0x08	/* command */
+#define  ATI_REG_CMD_POWERDOWN		(1U<<0)
+#define  ATI_REG_CMD_RECEIVE_EN		(1U<<1)
+#define  ATI_REG_CMD_SEND_EN		(1U<<2)
+#define  ATI_REG_CMD_STATUS_MEM		(1U<<3)
+#define  ATI_REG_CMD_SPDF_OUT_EN	(1U<<4)
+#define  ATI_REG_CMD_SPDF_STATUS_MEM	(1U<<5)
+#define  ATI_REG_CMD_SPDF_THRESHOLD	(3U<<6)
+#define  ATI_REG_CMD_SPDF_THRESHOLD_SHIFT	6
+#define  ATI_REG_CMD_IN_DMA_EN		(1U<<8)
+#define  ATI_REG_CMD_OUT_DMA_EN		(1U<<9)
+#define  ATI_REG_CMD_SPDF_DMA_EN	(1U<<10)
+#define  ATI_REG_CMD_SPDF_OUT_STOPPED	(1U<<11)
+#define  ATI_REG_CMD_SPDF_CONFIG_MASK	(7U<<12)
+#define   ATI_REG_CMD_SPDF_CONFIG_34	(1U<<12)
+#define   ATI_REG_CMD_SPDF_CONFIG_78	(2U<<12)
+#define   ATI_REG_CMD_SPDF_CONFIG_69	(3U<<12)
+#define   ATI_REG_CMD_SPDF_CONFIG_01	(4U<<12)
+#define  ATI_REG_CMD_INTERLEAVE_SPDF	(1U<<16)
+#define  ATI_REG_CMD_AUDIO_PRESENT	(1U<<20)
+#define  ATI_REG_CMD_INTERLEAVE_IN	(1U<<21)
+#define  ATI_REG_CMD_INTERLEAVE_OUT	(1U<<22)
+#define  ATI_REG_CMD_LOOPBACK_EN	(1U<<23)
+#define  ATI_REG_CMD_PACKED_DIS		(1U<<24)
+#define  ATI_REG_CMD_BURST_EN		(1U<<25)
+#define  ATI_REG_CMD_PANIC_EN		(1U<<26)
+#define  ATI_REG_CMD_MODEM_PRESENT	(1U<<27)
+#define  ATI_REG_CMD_ACLINK_ACTIVE	(1U<<28)
+#define  ATI_REG_CMD_AC_SOFT_RESET	(1U<<29)
+#define  ATI_REG_CMD_AC_SYNC		(1U<<30)
+#define  ATI_REG_CMD_AC_RESET		(1U<<31)
+
+#define ATI_REG_PHYS_OUT_ADDR		0x0c
+#define  ATI_REG_PHYS_OUT_CODEC_MASK	(3U<<0)
+#define  ATI_REG_PHYS_OUT_RW		(1U<<2)
+#define  ATI_REG_PHYS_OUT_ADDR_EN	(1U<<8)
+#define  ATI_REG_PHYS_OUT_ADDR_SHIFT	9
+#define  ATI_REG_PHYS_OUT_DATA_SHIFT	16
+
+#define ATI_REG_PHYS_IN_ADDR		0x10
+#define  ATI_REG_PHYS_IN_READ_FLAG	(1U<<8)
+#define  ATI_REG_PHYS_IN_ADDR_SHIFT	9
+#define  ATI_REG_PHYS_IN_DATA_SHIFT	16
+
+#define ATI_REG_SLOTREQ			0x14
+
+#define ATI_REG_COUNTER			0x18
+#define  ATI_REG_COUNTER_SLOT		(3U<<0)	/* slot # */
+#define  ATI_REG_COUNTER_BITCLOCK	(31U<<8)
+
+#define ATI_REG_IN_FIFO_THRESHOLD	0x1c
+
+#define ATI_REG_IN_DMA_LINKPTR		0x20
+#define ATI_REG_IN_DMA_DT_START		0x24	/* RO */
+#define ATI_REG_IN_DMA_DT_NEXT		0x28	/* RO */
+#define ATI_REG_IN_DMA_DT_CUR		0x2c	/* RO */
+#define ATI_REG_IN_DMA_DT_SIZE		0x30
+
+#define ATI_REG_OUT_DMA_SLOT		0x34
+#define  ATI_REG_OUT_DMA_SLOT_BIT(x)	(1U << ((x) - 3))
+#define  ATI_REG_OUT_DMA_SLOT_MASK	0x1ff
+#define  ATI_REG_OUT_DMA_THRESHOLD_MASK	0xf800
+#define  ATI_REG_OUT_DMA_THRESHOLD_SHIFT	11
+
+#define ATI_REG_OUT_DMA_LINKPTR		0x38
+#define ATI_REG_OUT_DMA_DT_START	0x3c	/* RO */
+#define ATI_REG_OUT_DMA_DT_NEXT		0x40	/* RO */
+#define ATI_REG_OUT_DMA_DT_CUR		0x44	/* RO */
+#define ATI_REG_OUT_DMA_DT_SIZE		0x48
+
+#define ATI_REG_SPDF_CMD		0x4c
+#define  ATI_REG_SPDF_CMD_LFSR		(1U<<4)
+#define  ATI_REG_SPDF_CMD_SINGLE_CH	(1U<<5)
+#define  ATI_REG_SPDF_CMD_LFSR_ACC	(0xff<<8)	/* RO */
+
+#define ATI_REG_SPDF_DMA_LINKPTR	0x50
+#define ATI_REG_SPDF_DMA_DT_START	0x54	/* RO */
+#define ATI_REG_SPDF_DMA_DT_NEXT	0x58	/* RO */
+#define ATI_REG_SPDF_DMA_DT_CUR		0x5c	/* RO */
+#define ATI_REG_SPDF_DMA_DT_SIZE	0x60
+
+#define ATI_REG_MODEM_MIRROR		0x7c
+#define ATI_REG_AUDIO_MIRROR		0x80
+
+#define ATI_REG_6CH_REORDER		0x84	/* reorder slots for 6ch */
+#define  ATI_REG_6CH_REORDER_EN		(1U<<0)	/* 3,4,7,8,6,9 -> 3,4,6,9,7,8 */
+
+#define ATI_REG_FIFO_FLUSH		0x88
+#define  ATI_REG_FIFO_OUT_FLUSH		(1U<<0)
+#define  ATI_REG_FIFO_IN_FLUSH		(1U<<1)
+
+/* LINKPTR */
+#define  ATI_REG_LINKPTR_EN		(1U<<0)
+
+/* [INT|OUT|SPDIF]_DMA_DT_SIZE */
+#define  ATI_REG_DMA_DT_SIZE		(0xffffU<<0)
+#define  ATI_REG_DMA_FIFO_USED		(0x1fU<<16)
+#define  ATI_REG_DMA_FIFO_FREE		(0x1fU<<21)
+#define  ATI_REG_DMA_STATE		(7U<<26)
+
+
+#define ATI_MAX_DESCRIPTORS	256	/* max number of descriptor packets */
+
+
+struct atiixp;
+
+/*
+ * DMA packate descriptor
+ */
+
+struct atiixp_dma_desc {
+	u32 addr;	/* DMA buffer address */
+	u16 status;	/* status bits */
+	u16 size;	/* size of the packet in dwords */
+	u32 next;	/* address of the next packet descriptor */
+};
+
+/*
+ * stream enum
+ */
+enum { ATI_DMA_PLAYBACK, ATI_DMA_CAPTURE, ATI_DMA_SPDIF, NUM_ATI_DMAS }; /* DMAs */
+enum { ATI_PCM_OUT, ATI_PCM_IN, ATI_PCM_SPDIF, NUM_ATI_PCMS }; /* AC97 pcm slots */
+enum { ATI_PCMDEV_ANALOG, ATI_PCMDEV_DIGITAL, NUM_ATI_PCMDEVS }; /* pcm devices */
+
+#define NUM_ATI_CODECS	3
+
+
+/*
+ * constants and callbacks for each DMA type
+ */
+struct atiixp_dma_ops {
+	int type;			/* ATI_DMA_XXX */
+	unsigned int llp_offset;	/* LINKPTR offset */
+	unsigned int dt_cur;		/* DT_CUR offset */
+	/* called from open callback */
+	void (*enable_dma)(struct atiixp *chip, int on);
+	/* called from trigger (START/STOP) */
+	void (*enable_transfer)(struct atiixp *chip, int on);
+ 	/* called from trigger (STOP only) */
+	void (*flush_dma)(struct atiixp *chip);
+};
+
+/*
+ * DMA stream
+ */
+struct atiixp_dma {
+	const struct atiixp_dma_ops *ops;
+	struct snd_dma_buffer desc_buf;
+	struct snd_pcm_substream *substream;	/* assigned PCM substream */
+	unsigned int buf_addr, buf_bytes;	/* DMA buffer address, bytes */
+	unsigned int period_bytes, periods;
+	int opened;
+	int running;
+	int suspended;
+	int pcm_open_flag;
+	int ac97_pcm_type;	/* index # of ac97_pcm to access, -1 = not used */
+	unsigned int saved_curptr;
+};
+
+/*
+ * ATI IXP chip
+ */
+struct atiixp {
+	struct snd_card *card;
+	struct pci_dev *pci;
+
+	unsigned long addr;
+	void __iomem *remap_addr;
+	int irq;
+	
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97 *ac97[NUM_ATI_CODECS];
+
+	spinlock_t reg_lock;
+
+	struct atiixp_dma dmas[NUM_ATI_DMAS];
+	struct ac97_pcm *pcms[NUM_ATI_PCMS];
+	struct snd_pcm *pcmdevs[NUM_ATI_PCMDEVS];
+
+	int max_channels;		/* max. channels for PCM out */
+
+	unsigned int codec_not_ready_bits;	/* for codec detection */
+
+	int spdif_over_aclink;		/* passed from the module option */
+	struct mutex open_mutex;	/* playback open mutex */
+};
+
+
+/*
+ */
+static DEFINE_PCI_DEVICE_TABLE(snd_atiixp_ids) = {
+	{ PCI_VDEVICE(ATI, 0x4341), 0 }, /* SB200 */
+	{ PCI_VDEVICE(ATI, 0x4361), 0 }, /* SB300 */
+	{ PCI_VDEVICE(ATI, 0x4370), 0 }, /* SB400 */
+	{ PCI_VDEVICE(ATI, 0x4382), 0 }, /* SB600 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_atiixp_ids);
+
+static struct snd_pci_quirk atiixp_quirks[] __devinitdata = {
+	SND_PCI_QUIRK(0x105b, 0x0c81, "Foxconn RC4107MA-RS2", 0),
+	SND_PCI_QUIRK(0x15bd, 0x3100, "DFI RS482", 0),
+	{ } /* terminator */
+};
+
+/*
+ * lowlevel functions
+ */
+
+/*
+ * update the bits of the given register.
+ * return 1 if the bits changed.
+ */
+static int snd_atiixp_update_bits(struct atiixp *chip, unsigned int reg,
+				 unsigned int mask, unsigned int value)
+{
+	void __iomem *addr = chip->remap_addr + reg;
+	unsigned int data, old_data;
+	old_data = data = readl(addr);
+	data &= ~mask;
+	data |= value;
+	if (old_data == data)
+		return 0;
+	writel(data, addr);
+	return 1;
+}
+
+/*
+ * macros for easy use
+ */
+#define atiixp_write(chip,reg,value) \
+	writel(value, chip->remap_addr + ATI_REG_##reg)
+#define atiixp_read(chip,reg) \
+	readl(chip->remap_addr + ATI_REG_##reg)
+#define atiixp_update(chip,reg,mask,val) \
+	snd_atiixp_update_bits(chip, ATI_REG_##reg, mask, val)
+
+/*
+ * handling DMA packets
+ *
+ * we allocate a linear buffer for the DMA, and split it to  each packet.
+ * in a future version, a scatter-gather buffer should be implemented.
+ */
+
+#define ATI_DESC_LIST_SIZE \
+	PAGE_ALIGN(ATI_MAX_DESCRIPTORS * sizeof(struct atiixp_dma_desc))
+
+/*
+ * build packets ring for the given buffer size.
+ *
+ * IXP handles the buffer descriptors, which are connected as a linked
+ * list.  although we can change the list dynamically, in this version,
+ * a static RING of buffer descriptors is used.
+ *
+ * the ring is built in this function, and is set up to the hardware. 
+ */
+static int atiixp_build_dma_packets(struct atiixp *chip, struct atiixp_dma *dma,
+				    struct snd_pcm_substream *substream,
+				    unsigned int periods,
+				    unsigned int period_bytes)
+{
+	unsigned int i;
+	u32 addr, desc_addr;
+	unsigned long flags;
+
+	if (periods > ATI_MAX_DESCRIPTORS)
+		return -ENOMEM;
+
+	if (dma->desc_buf.area == NULL) {
+		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+					snd_dma_pci_data(chip->pci),
+					ATI_DESC_LIST_SIZE,
+					&dma->desc_buf) < 0)
+			return -ENOMEM;
+		dma->period_bytes = dma->periods = 0; /* clear */
+	}
+
+	if (dma->periods == periods && dma->period_bytes == period_bytes)
+		return 0;
+
+	/* reset DMA before changing the descriptor table */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	writel(0, chip->remap_addr + dma->ops->llp_offset);
+	dma->ops->enable_dma(chip, 0);
+	dma->ops->enable_dma(chip, 1);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	/* fill the entries */
+	addr = (u32)substream->runtime->dma_addr;
+	desc_addr = (u32)dma->desc_buf.addr;
+	for (i = 0; i < periods; i++) {
+		struct atiixp_dma_desc *desc;
+		desc = &((struct atiixp_dma_desc *)dma->desc_buf.area)[i];
+		desc->addr = cpu_to_le32(addr);
+		desc->status = 0;
+		desc->size = period_bytes >> 2; /* in dwords */
+		desc_addr += sizeof(struct atiixp_dma_desc);
+		if (i == periods - 1)
+			desc->next = cpu_to_le32((u32)dma->desc_buf.addr);
+		else
+			desc->next = cpu_to_le32(desc_addr);
+		addr += period_bytes;
+	}
+
+	writel((u32)dma->desc_buf.addr | ATI_REG_LINKPTR_EN,
+	       chip->remap_addr + dma->ops->llp_offset);
+
+	dma->period_bytes = period_bytes;
+	dma->periods = periods;
+
+	return 0;
+}
+
+/*
+ * remove the ring buffer and release it if assigned
+ */
+static void atiixp_clear_dma_packets(struct atiixp *chip, struct atiixp_dma *dma,
+				     struct snd_pcm_substream *substream)
+{
+	if (dma->desc_buf.area) {
+		writel(0, chip->remap_addr + dma->ops->llp_offset);
+		snd_dma_free_pages(&dma->desc_buf);
+		dma->desc_buf.area = NULL;
+	}
+}
+
+/*
+ * AC97 interface
+ */
+static int snd_atiixp_acquire_codec(struct atiixp *chip)
+{
+	int timeout = 1000;
+
+	while (atiixp_read(chip, PHYS_OUT_ADDR) & ATI_REG_PHYS_OUT_ADDR_EN) {
+		if (! timeout--) {
+			snd_printk(KERN_WARNING "atiixp: codec acquire timeout\n");
+			return -EBUSY;
+		}
+		udelay(1);
+	}
+	return 0;
+}
+
+static unsigned short snd_atiixp_codec_read(struct atiixp *chip, unsigned short codec, unsigned short reg)
+{
+	unsigned int data;
+	int timeout;
+
+	if (snd_atiixp_acquire_codec(chip) < 0)
+		return 0xffff;
+	data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) |
+		ATI_REG_PHYS_OUT_ADDR_EN |
+		ATI_REG_PHYS_OUT_RW |
+		codec;
+	atiixp_write(chip, PHYS_OUT_ADDR, data);
+	if (snd_atiixp_acquire_codec(chip) < 0)
+		return 0xffff;
+	timeout = 1000;
+	do {
+		data = atiixp_read(chip, PHYS_IN_ADDR);
+		if (data & ATI_REG_PHYS_IN_READ_FLAG)
+			return data >> ATI_REG_PHYS_IN_DATA_SHIFT;
+		udelay(1);
+	} while (--timeout);
+	/* time out may happen during reset */
+	if (reg < 0x7c)
+		snd_printk(KERN_WARNING "atiixp: codec read timeout (reg %x)\n", reg);
+	return 0xffff;
+}
+
+
+static void snd_atiixp_codec_write(struct atiixp *chip, unsigned short codec,
+				   unsigned short reg, unsigned short val)
+{
+	unsigned int data;
+    
+	if (snd_atiixp_acquire_codec(chip) < 0)
+		return;
+	data = ((unsigned int)val << ATI_REG_PHYS_OUT_DATA_SHIFT) |
+		((unsigned int)reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) |
+		ATI_REG_PHYS_OUT_ADDR_EN | codec;
+	atiixp_write(chip, PHYS_OUT_ADDR, data);
+}
+
+
+static unsigned short snd_atiixp_ac97_read(struct snd_ac97 *ac97,
+					   unsigned short reg)
+{
+	struct atiixp *chip = ac97->private_data;
+	return snd_atiixp_codec_read(chip, ac97->num, reg);
+    
+}
+
+static void snd_atiixp_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+				  unsigned short val)
+{
+	struct atiixp *chip = ac97->private_data;
+	snd_atiixp_codec_write(chip, ac97->num, reg, val);
+}
+
+/*
+ * reset AC link
+ */
+static int snd_atiixp_aclink_reset(struct atiixp *chip)
+{
+	int timeout;
+
+	/* reset powerdoewn */
+	if (atiixp_update(chip, CMD, ATI_REG_CMD_POWERDOWN, 0))
+		udelay(10);
+
+	/* perform a software reset */
+	atiixp_update(chip, CMD, ATI_REG_CMD_AC_SOFT_RESET, ATI_REG_CMD_AC_SOFT_RESET);
+	atiixp_read(chip, CMD);
+	udelay(10);
+	atiixp_update(chip, CMD, ATI_REG_CMD_AC_SOFT_RESET, 0);
+    
+	timeout = 10;
+	while (! (atiixp_read(chip, CMD) & ATI_REG_CMD_ACLINK_ACTIVE)) {
+		/* do a hard reset */
+		atiixp_update(chip, CMD, ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET,
+			      ATI_REG_CMD_AC_SYNC);
+		atiixp_read(chip, CMD);
+		mdelay(1);
+		atiixp_update(chip, CMD, ATI_REG_CMD_AC_RESET, ATI_REG_CMD_AC_RESET);
+		if (--timeout) {
+			snd_printk(KERN_ERR "atiixp: codec reset timeout\n");
+			break;
+		}
+	}
+
+	/* deassert RESET and assert SYNC to make sure */
+	atiixp_update(chip, CMD, ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET,
+		      ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_atiixp_aclink_down(struct atiixp *chip)
+{
+	// if (atiixp_read(chip, MODEM_MIRROR) & 0x1) /* modem running, too? */
+	//	return -EBUSY;
+	atiixp_update(chip, CMD,
+		     ATI_REG_CMD_POWERDOWN | ATI_REG_CMD_AC_RESET,
+		     ATI_REG_CMD_POWERDOWN);
+	return 0;
+}
+#endif
+
+/*
+ * auto-detection of codecs
+ *
+ * the IXP chip can generate interrupts for the non-existing codecs.
+ * NEW_FRAME interrupt is used to make sure that the interrupt is generated
+ * even if all three codecs are connected.
+ */
+
+#define ALL_CODEC_NOT_READY \
+	    (ATI_REG_ISR_CODEC0_NOT_READY |\
+	     ATI_REG_ISR_CODEC1_NOT_READY |\
+	     ATI_REG_ISR_CODEC2_NOT_READY)
+#define CODEC_CHECK_BITS (ALL_CODEC_NOT_READY|ATI_REG_ISR_NEW_FRAME)
+
+static int __devinit ac97_probing_bugs(struct pci_dev *pci)
+{
+	const struct snd_pci_quirk *q;
+
+	q = snd_pci_quirk_lookup(pci, atiixp_quirks);
+	if (q) {
+		snd_printdd(KERN_INFO "Atiixp quirk for %s.  "
+			    "Forcing codec %d\n", q->name, q->value);
+		return q->value;
+	}
+	/* this hardware doesn't need workarounds.  Probe for codec */
+	return -1;
+}
+
+static int __devinit snd_atiixp_codec_detect(struct atiixp *chip)
+{
+	int timeout;
+
+	chip->codec_not_ready_bits = 0;
+	if (ac97_codec == -1)
+		ac97_codec = ac97_probing_bugs(chip->pci);
+	if (ac97_codec >= 0) {
+		chip->codec_not_ready_bits |= 
+			CODEC_CHECK_BITS ^ (1 << (ac97_codec + 10));
+		return 0;
+	}
+
+	atiixp_write(chip, IER, CODEC_CHECK_BITS);
+	/* wait for the interrupts */
+	timeout = 50;
+	while (timeout-- > 0) {
+		mdelay(1);
+		if (chip->codec_not_ready_bits)
+			break;
+	}
+	atiixp_write(chip, IER, 0); /* disable irqs */
+
+	if ((chip->codec_not_ready_bits & ALL_CODEC_NOT_READY) == ALL_CODEC_NOT_READY) {
+		snd_printk(KERN_ERR "atiixp: no codec detected!\n");
+		return -ENXIO;
+	}
+	return 0;
+}
+
+
+/*
+ * enable DMA and irqs
+ */
+static int snd_atiixp_chip_start(struct atiixp *chip)
+{
+	unsigned int reg;
+
+	/* set up spdif, enable burst mode */
+	reg = atiixp_read(chip, CMD);
+	reg |= 0x02 << ATI_REG_CMD_SPDF_THRESHOLD_SHIFT;
+	reg |= ATI_REG_CMD_BURST_EN;
+	atiixp_write(chip, CMD, reg);
+
+	reg = atiixp_read(chip, SPDF_CMD);
+	reg &= ~(ATI_REG_SPDF_CMD_LFSR|ATI_REG_SPDF_CMD_SINGLE_CH);
+	atiixp_write(chip, SPDF_CMD, reg);
+
+	/* clear all interrupt source */
+	atiixp_write(chip, ISR, 0xffffffff);
+	/* enable irqs */
+	atiixp_write(chip, IER,
+		     ATI_REG_IER_IO_STATUS_EN |
+		     ATI_REG_IER_IN_XRUN_EN |
+		     ATI_REG_IER_OUT_XRUN_EN |
+		     ATI_REG_IER_SPDF_XRUN_EN |
+		     ATI_REG_IER_SPDF_STATUS_EN);
+	return 0;
+}
+
+
+/*
+ * disable DMA and IRQs
+ */
+static int snd_atiixp_chip_stop(struct atiixp *chip)
+{
+	/* clear interrupt source */
+	atiixp_write(chip, ISR, atiixp_read(chip, ISR));
+	/* disable irqs */
+	atiixp_write(chip, IER, 0);
+	return 0;
+}
+
+
+/*
+ * PCM section
+ */
+
+/*
+ * pointer callback simplly reads XXX_DMA_DT_CUR register as the current
+ * position.  when SG-buffer is implemented, the offset must be calculated
+ * correctly...
+ */
+static snd_pcm_uframes_t snd_atiixp_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct atiixp_dma *dma = runtime->private_data;
+	unsigned int curptr;
+	int timeout = 1000;
+
+	while (timeout--) {
+		curptr = readl(chip->remap_addr + dma->ops->dt_cur);
+		if (curptr < dma->buf_addr)
+			continue;
+		curptr -= dma->buf_addr;
+		if (curptr >= dma->buf_bytes)
+			continue;
+		return bytes_to_frames(runtime, curptr);
+	}
+	snd_printd("atiixp: invalid DMA pointer read 0x%x (buf=%x)\n",
+		   readl(chip->remap_addr + dma->ops->dt_cur), dma->buf_addr);
+	return 0;
+}
+
+/*
+ * XRUN detected, and stop the PCM substream
+ */
+static void snd_atiixp_xrun_dma(struct atiixp *chip, struct atiixp_dma *dma)
+{
+	if (! dma->substream || ! dma->running)
+		return;
+	snd_printdd("atiixp: XRUN detected (DMA %d)\n", dma->ops->type);
+	snd_pcm_stop(dma->substream, SNDRV_PCM_STATE_XRUN);
+}
+
+/*
+ * the period ack.  update the substream.
+ */
+static void snd_atiixp_update_dma(struct atiixp *chip, struct atiixp_dma *dma)
+{
+	if (! dma->substream || ! dma->running)
+		return;
+	snd_pcm_period_elapsed(dma->substream);
+}
+
+/* set BUS_BUSY interrupt bit if any DMA is running */
+/* call with spinlock held */
+static void snd_atiixp_check_bus_busy(struct atiixp *chip)
+{
+	unsigned int bus_busy;
+	if (atiixp_read(chip, CMD) & (ATI_REG_CMD_SEND_EN |
+				      ATI_REG_CMD_RECEIVE_EN |
+				      ATI_REG_CMD_SPDF_OUT_EN))
+		bus_busy = ATI_REG_IER_SET_BUS_BUSY;
+	else
+		bus_busy = 0;
+	atiixp_update(chip, IER, ATI_REG_IER_SET_BUS_BUSY, bus_busy);
+}
+
+/* common trigger callback
+ * calling the lowlevel callbacks in it
+ */
+static int snd_atiixp_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	struct atiixp_dma *dma = substream->runtime->private_data;
+	int err = 0;
+
+	if (snd_BUG_ON(!dma->ops->enable_transfer ||
+		       !dma->ops->flush_dma))
+		return -EINVAL;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		dma->ops->enable_transfer(chip, 1);
+		dma->running = 1;
+		dma->suspended = 0;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		dma->ops->enable_transfer(chip, 0);
+		dma->running = 0;
+		dma->suspended = cmd == SNDRV_PCM_TRIGGER_SUSPEND;
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	if (! err) {
+		snd_atiixp_check_bus_busy(chip);
+		if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+			dma->ops->flush_dma(chip);
+			snd_atiixp_check_bus_busy(chip);
+		}
+	}
+	spin_unlock(&chip->reg_lock);
+	return err;
+}
+
+
+/*
+ * lowlevel callbacks for each DMA type
+ *
+ * every callback is supposed to be called in chip->reg_lock spinlock
+ */
+
+/* flush FIFO of analog OUT DMA */
+static void atiixp_out_flush_dma(struct atiixp *chip)
+{
+	atiixp_write(chip, FIFO_FLUSH, ATI_REG_FIFO_OUT_FLUSH);
+}
+
+/* enable/disable analog OUT DMA */
+static void atiixp_out_enable_dma(struct atiixp *chip, int on)
+{
+	unsigned int data;
+	data = atiixp_read(chip, CMD);
+	if (on) {
+		if (data & ATI_REG_CMD_OUT_DMA_EN)
+			return;
+		atiixp_out_flush_dma(chip);
+		data |= ATI_REG_CMD_OUT_DMA_EN;
+	} else
+		data &= ~ATI_REG_CMD_OUT_DMA_EN;
+	atiixp_write(chip, CMD, data);
+}
+
+/* start/stop transfer over OUT DMA */
+static void atiixp_out_enable_transfer(struct atiixp *chip, int on)
+{
+	atiixp_update(chip, CMD, ATI_REG_CMD_SEND_EN,
+		      on ? ATI_REG_CMD_SEND_EN : 0);
+}
+
+/* enable/disable analog IN DMA */
+static void atiixp_in_enable_dma(struct atiixp *chip, int on)
+{
+	atiixp_update(chip, CMD, ATI_REG_CMD_IN_DMA_EN,
+		      on ? ATI_REG_CMD_IN_DMA_EN : 0);
+}
+
+/* start/stop analog IN DMA */
+static void atiixp_in_enable_transfer(struct atiixp *chip, int on)
+{
+	if (on) {
+		unsigned int data = atiixp_read(chip, CMD);
+		if (! (data & ATI_REG_CMD_RECEIVE_EN)) {
+			data |= ATI_REG_CMD_RECEIVE_EN;
+#if 0 /* FIXME: this causes the endless loop */
+			/* wait until slot 3/4 are finished */
+			while ((atiixp_read(chip, COUNTER) &
+				ATI_REG_COUNTER_SLOT) != 5)
+				;
+#endif
+			atiixp_write(chip, CMD, data);
+		}
+	} else
+		atiixp_update(chip, CMD, ATI_REG_CMD_RECEIVE_EN, 0);
+}
+
+/* flush FIFO of analog IN DMA */
+static void atiixp_in_flush_dma(struct atiixp *chip)
+{
+	atiixp_write(chip, FIFO_FLUSH, ATI_REG_FIFO_IN_FLUSH);
+}
+
+/* enable/disable SPDIF OUT DMA */
+static void atiixp_spdif_enable_dma(struct atiixp *chip, int on)
+{
+	atiixp_update(chip, CMD, ATI_REG_CMD_SPDF_DMA_EN,
+		      on ? ATI_REG_CMD_SPDF_DMA_EN : 0);
+}
+
+/* start/stop SPDIF OUT DMA */
+static void atiixp_spdif_enable_transfer(struct atiixp *chip, int on)
+{
+	unsigned int data;
+	data = atiixp_read(chip, CMD);
+	if (on)
+		data |= ATI_REG_CMD_SPDF_OUT_EN;
+	else
+		data &= ~ATI_REG_CMD_SPDF_OUT_EN;
+	atiixp_write(chip, CMD, data);
+}
+
+/* flush FIFO of SPDIF OUT DMA */
+static void atiixp_spdif_flush_dma(struct atiixp *chip)
+{
+	int timeout;
+
+	/* DMA off, transfer on */
+	atiixp_spdif_enable_dma(chip, 0);
+	atiixp_spdif_enable_transfer(chip, 1);
+	
+	timeout = 100;
+	do {
+		if (! (atiixp_read(chip, SPDF_DMA_DT_SIZE) & ATI_REG_DMA_FIFO_USED))
+			break;
+		udelay(1);
+	} while (timeout-- > 0);
+
+	atiixp_spdif_enable_transfer(chip, 0);
+}
+
+/* set up slots and formats for SPDIF OUT */
+static int snd_atiixp_spdif_prepare(struct snd_pcm_substream *substream)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&chip->reg_lock);
+	if (chip->spdif_over_aclink) {
+		unsigned int data;
+		/* enable slots 10/11 */
+		atiixp_update(chip, CMD, ATI_REG_CMD_SPDF_CONFIG_MASK,
+			      ATI_REG_CMD_SPDF_CONFIG_01);
+		data = atiixp_read(chip, OUT_DMA_SLOT) & ~ATI_REG_OUT_DMA_SLOT_MASK;
+		data |= ATI_REG_OUT_DMA_SLOT_BIT(10) |
+			ATI_REG_OUT_DMA_SLOT_BIT(11);
+		data |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT;
+		atiixp_write(chip, OUT_DMA_SLOT, data);
+		atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_OUT,
+			      substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE ?
+			      ATI_REG_CMD_INTERLEAVE_OUT : 0);
+	} else {
+		atiixp_update(chip, CMD, ATI_REG_CMD_SPDF_CONFIG_MASK, 0);
+		atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_SPDF, 0);
+	}
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+/* set up slots and formats for analog OUT */
+static int snd_atiixp_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	unsigned int data;
+
+	spin_lock_irq(&chip->reg_lock);
+	data = atiixp_read(chip, OUT_DMA_SLOT) & ~ATI_REG_OUT_DMA_SLOT_MASK;
+	switch (substream->runtime->channels) {
+	case 8:
+		data |= ATI_REG_OUT_DMA_SLOT_BIT(10) |
+			ATI_REG_OUT_DMA_SLOT_BIT(11);
+		/* fallthru */
+	case 6:
+		data |= ATI_REG_OUT_DMA_SLOT_BIT(7) |
+			ATI_REG_OUT_DMA_SLOT_BIT(8);
+		/* fallthru */
+	case 4:
+		data |= ATI_REG_OUT_DMA_SLOT_BIT(6) |
+			ATI_REG_OUT_DMA_SLOT_BIT(9);
+		/* fallthru */
+	default:
+		data |= ATI_REG_OUT_DMA_SLOT_BIT(3) |
+			ATI_REG_OUT_DMA_SLOT_BIT(4);
+		break;
+	}
+
+	/* set output threshold */
+	data |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT;
+	atiixp_write(chip, OUT_DMA_SLOT, data);
+
+	atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_OUT,
+		      substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE ?
+		      ATI_REG_CMD_INTERLEAVE_OUT : 0);
+
+	/*
+	 * enable 6 channel re-ordering bit if needed
+	 */
+	atiixp_update(chip, 6CH_REORDER, ATI_REG_6CH_REORDER_EN,
+		      substream->runtime->channels >= 6 ? ATI_REG_6CH_REORDER_EN: 0);
+    
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+/* set up slots and formats for analog IN */
+static int snd_atiixp_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&chip->reg_lock);
+	atiixp_update(chip, CMD, ATI_REG_CMD_INTERLEAVE_IN,
+		      substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE ?
+		      ATI_REG_CMD_INTERLEAVE_IN : 0);
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+/*
+ * hw_params - allocate the buffer and set up buffer descriptors
+ */
+static int snd_atiixp_pcm_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *hw_params)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	struct atiixp_dma *dma = substream->runtime->private_data;
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	dma->buf_addr = substream->runtime->dma_addr;
+	dma->buf_bytes = params_buffer_bytes(hw_params);
+
+	err = atiixp_build_dma_packets(chip, dma, substream,
+				       params_periods(hw_params),
+				       params_period_bytes(hw_params));
+	if (err < 0)
+		return err;
+
+	if (dma->ac97_pcm_type >= 0) {
+		struct ac97_pcm *pcm = chip->pcms[dma->ac97_pcm_type];
+		/* PCM is bound to AC97 codec(s)
+		 * set up the AC97 codecs
+		 */
+		if (dma->pcm_open_flag) {
+			snd_ac97_pcm_close(pcm);
+			dma->pcm_open_flag = 0;
+		}
+		err = snd_ac97_pcm_open(pcm, params_rate(hw_params),
+					params_channels(hw_params),
+					pcm->r[0].slots);
+		if (err >= 0)
+			dma->pcm_open_flag = 1;
+	}
+
+	return err;
+}
+
+static int snd_atiixp_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	struct atiixp_dma *dma = substream->runtime->private_data;
+
+	if (dma->pcm_open_flag) {
+		struct ac97_pcm *pcm = chip->pcms[dma->ac97_pcm_type];
+		snd_ac97_pcm_close(pcm);
+		dma->pcm_open_flag = 0;
+	}
+	atiixp_clear_dma_packets(chip, dma, substream);
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+
+/*
+ * pcm hardware definition, identical for all DMA types
+ */
+static struct snd_pcm_hardware snd_atiixp_pcm_hw =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	256 * 1024,
+	.period_bytes_min =	32,
+	.period_bytes_max =	128 * 1024,
+	.periods_min =		2,
+	.periods_max =		ATI_MAX_DESCRIPTORS,
+};
+
+static int snd_atiixp_pcm_open(struct snd_pcm_substream *substream,
+			       struct atiixp_dma *dma, int pcm_type)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	if (snd_BUG_ON(!dma->ops || !dma->ops->enable_dma))
+		return -EINVAL;
+
+	if (dma->opened)
+		return -EBUSY;
+	dma->substream = substream;
+	runtime->hw = snd_atiixp_pcm_hw;
+	dma->ac97_pcm_type = pcm_type;
+	if (pcm_type >= 0) {
+		runtime->hw.rates = chip->pcms[pcm_type]->rates;
+		snd_pcm_limit_hw_rates(runtime);
+	} else {
+		/* direct SPDIF */
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE;
+	}
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+	runtime->private_data = dma;
+
+	/* enable DMA bits */
+	spin_lock_irq(&chip->reg_lock);
+	dma->ops->enable_dma(chip, 1);
+	spin_unlock_irq(&chip->reg_lock);
+	dma->opened = 1;
+
+	return 0;
+}
+
+static int snd_atiixp_pcm_close(struct snd_pcm_substream *substream,
+				struct atiixp_dma *dma)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	/* disable DMA bits */
+	if (snd_BUG_ON(!dma->ops || !dma->ops->enable_dma))
+		return -EINVAL;
+	spin_lock_irq(&chip->reg_lock);
+	dma->ops->enable_dma(chip, 0);
+	spin_unlock_irq(&chip->reg_lock);
+	dma->substream = NULL;
+	dma->opened = 0;
+	return 0;
+}
+
+/*
+ */
+static int snd_atiixp_playback_open(struct snd_pcm_substream *substream)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	int err;
+
+	mutex_lock(&chip->open_mutex);
+	err = snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_PLAYBACK], 0);
+	mutex_unlock(&chip->open_mutex);
+	if (err < 0)
+		return err;
+	substream->runtime->hw.channels_max = chip->max_channels;
+	if (chip->max_channels > 2)
+		/* channels must be even */
+		snd_pcm_hw_constraint_step(substream->runtime, 0,
+					   SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+	return 0;
+}
+
+static int snd_atiixp_playback_close(struct snd_pcm_substream *substream)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	int err;
+	mutex_lock(&chip->open_mutex);
+	err = snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_PLAYBACK]);
+	mutex_unlock(&chip->open_mutex);
+	return err;
+}
+
+static int snd_atiixp_capture_open(struct snd_pcm_substream *substream)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	return snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_CAPTURE], 1);
+}
+
+static int snd_atiixp_capture_close(struct snd_pcm_substream *substream)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	return snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_CAPTURE]);
+}
+
+static int snd_atiixp_spdif_open(struct snd_pcm_substream *substream)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	int err;
+	mutex_lock(&chip->open_mutex);
+	if (chip->spdif_over_aclink) /* share DMA_PLAYBACK */
+		err = snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_PLAYBACK], 2);
+	else
+		err = snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_SPDIF], -1);
+	mutex_unlock(&chip->open_mutex);
+	return err;
+}
+
+static int snd_atiixp_spdif_close(struct snd_pcm_substream *substream)
+{
+	struct atiixp *chip = snd_pcm_substream_chip(substream);
+	int err;
+	mutex_lock(&chip->open_mutex);
+	if (chip->spdif_over_aclink)
+		err = snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_PLAYBACK]);
+	else
+		err = snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_SPDIF]);
+	mutex_unlock(&chip->open_mutex);
+	return err;
+}
+
+/* AC97 playback */
+static struct snd_pcm_ops snd_atiixp_playback_ops = {
+	.open =		snd_atiixp_playback_open,
+	.close =	snd_atiixp_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_atiixp_pcm_hw_params,
+	.hw_free =	snd_atiixp_pcm_hw_free,
+	.prepare =	snd_atiixp_playback_prepare,
+	.trigger =	snd_atiixp_pcm_trigger,
+	.pointer =	snd_atiixp_pcm_pointer,
+};
+
+/* AC97 capture */
+static struct snd_pcm_ops snd_atiixp_capture_ops = {
+	.open =		snd_atiixp_capture_open,
+	.close =	snd_atiixp_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_atiixp_pcm_hw_params,
+	.hw_free =	snd_atiixp_pcm_hw_free,
+	.prepare =	snd_atiixp_capture_prepare,
+	.trigger =	snd_atiixp_pcm_trigger,
+	.pointer =	snd_atiixp_pcm_pointer,
+};
+
+/* SPDIF playback */
+static struct snd_pcm_ops snd_atiixp_spdif_ops = {
+	.open =		snd_atiixp_spdif_open,
+	.close =	snd_atiixp_spdif_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_atiixp_pcm_hw_params,
+	.hw_free =	snd_atiixp_pcm_hw_free,
+	.prepare =	snd_atiixp_spdif_prepare,
+	.trigger =	snd_atiixp_pcm_trigger,
+	.pointer =	snd_atiixp_pcm_pointer,
+};
+
+static struct ac97_pcm atiixp_pcm_defs[] __devinitdata = {
+	/* front PCM */
+	{
+		.exclusive = 1,
+		.r = {	{
+				.slots = (1 << AC97_SLOT_PCM_LEFT) |
+					 (1 << AC97_SLOT_PCM_RIGHT) |
+					 (1 << AC97_SLOT_PCM_CENTER) |
+					 (1 << AC97_SLOT_PCM_SLEFT) |
+					 (1 << AC97_SLOT_PCM_SRIGHT) |
+					 (1 << AC97_SLOT_LFE)
+			}
+		}
+	},
+	/* PCM IN #1 */
+	{
+		.stream = 1,
+		.exclusive = 1,
+		.r = {	{
+				.slots = (1 << AC97_SLOT_PCM_LEFT) |
+					 (1 << AC97_SLOT_PCM_RIGHT)
+			}
+		}
+	},
+	/* S/PDIF OUT (optional) */
+	{
+		.exclusive = 1,
+		.spdif = 1,
+		.r = {	{
+				.slots = (1 << AC97_SLOT_SPDIF_LEFT2) |
+					 (1 << AC97_SLOT_SPDIF_RIGHT2)
+			}
+		}
+	},
+};
+
+static struct atiixp_dma_ops snd_atiixp_playback_dma_ops = {
+	.type = ATI_DMA_PLAYBACK,
+	.llp_offset = ATI_REG_OUT_DMA_LINKPTR,
+	.dt_cur = ATI_REG_OUT_DMA_DT_CUR,
+	.enable_dma = atiixp_out_enable_dma,
+	.enable_transfer = atiixp_out_enable_transfer,
+	.flush_dma = atiixp_out_flush_dma,
+};
+	
+static struct atiixp_dma_ops snd_atiixp_capture_dma_ops = {
+	.type = ATI_DMA_CAPTURE,
+	.llp_offset = ATI_REG_IN_DMA_LINKPTR,
+	.dt_cur = ATI_REG_IN_DMA_DT_CUR,
+	.enable_dma = atiixp_in_enable_dma,
+	.enable_transfer = atiixp_in_enable_transfer,
+	.flush_dma = atiixp_in_flush_dma,
+};
+	
+static struct atiixp_dma_ops snd_atiixp_spdif_dma_ops = {
+	.type = ATI_DMA_SPDIF,
+	.llp_offset = ATI_REG_SPDF_DMA_LINKPTR,
+	.dt_cur = ATI_REG_SPDF_DMA_DT_CUR,
+	.enable_dma = atiixp_spdif_enable_dma,
+	.enable_transfer = atiixp_spdif_enable_transfer,
+	.flush_dma = atiixp_spdif_flush_dma,
+};
+	
+
+static int __devinit snd_atiixp_pcm_new(struct atiixp *chip)
+{
+	struct snd_pcm *pcm;
+	struct snd_ac97_bus *pbus = chip->ac97_bus;
+	int err, i, num_pcms;
+
+	/* initialize constants */
+	chip->dmas[ATI_DMA_PLAYBACK].ops = &snd_atiixp_playback_dma_ops;
+	chip->dmas[ATI_DMA_CAPTURE].ops = &snd_atiixp_capture_dma_ops;
+	if (! chip->spdif_over_aclink)
+		chip->dmas[ATI_DMA_SPDIF].ops = &snd_atiixp_spdif_dma_ops;
+
+	/* assign AC97 pcm */
+	if (chip->spdif_over_aclink)
+		num_pcms = 3;
+	else
+		num_pcms = 2;
+	err = snd_ac97_pcm_assign(pbus, num_pcms, atiixp_pcm_defs);
+	if (err < 0)
+		return err;
+	for (i = 0; i < num_pcms; i++)
+		chip->pcms[i] = &pbus->pcms[i];
+
+	chip->max_channels = 2;
+	if (pbus->pcms[ATI_PCM_OUT].r[0].slots & (1 << AC97_SLOT_PCM_SLEFT)) {
+		if (pbus->pcms[ATI_PCM_OUT].r[0].slots & (1 << AC97_SLOT_LFE))
+			chip->max_channels = 6;
+		else
+			chip->max_channels = 4;
+	}
+
+	/* PCM #0: analog I/O */
+	err = snd_pcm_new(chip->card, "ATI IXP AC97",
+			  ATI_PCMDEV_ANALOG, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_atiixp_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_atiixp_capture_ops);
+	pcm->private_data = chip;
+	strcpy(pcm->name, "ATI IXP AC97");
+	chip->pcmdevs[ATI_PCMDEV_ANALOG] = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci),
+					      64*1024, 128*1024);
+
+	/* no SPDIF support on codec? */
+	if (chip->pcms[ATI_PCM_SPDIF] && ! chip->pcms[ATI_PCM_SPDIF]->rates)
+		return 0;
+		
+	/* FIXME: non-48k sample rate doesn't work on my test machine with AD1888 */
+	if (chip->pcms[ATI_PCM_SPDIF])
+		chip->pcms[ATI_PCM_SPDIF]->rates = SNDRV_PCM_RATE_48000;
+
+	/* PCM #1: spdif playback */
+	err = snd_pcm_new(chip->card, "ATI IXP IEC958",
+			  ATI_PCMDEV_DIGITAL, 1, 0, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_atiixp_spdif_ops);
+	pcm->private_data = chip;
+	if (chip->spdif_over_aclink)
+		strcpy(pcm->name, "ATI IXP IEC958 (AC97)");
+	else
+		strcpy(pcm->name, "ATI IXP IEC958 (Direct)");
+	chip->pcmdevs[ATI_PCMDEV_DIGITAL] = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci),
+					      64*1024, 128*1024);
+
+	/* pre-select AC97 SPDIF slots 10/11 */
+	for (i = 0; i < NUM_ATI_CODECS; i++) {
+		if (chip->ac97[i])
+			snd_ac97_update_bits(chip->ac97[i],
+					     AC97_EXTENDED_STATUS,
+					     0x03 << 4, 0x03 << 4);
+	}
+
+	return 0;
+}
+
+
+
+/*
+ * interrupt handler
+ */
+static irqreturn_t snd_atiixp_interrupt(int irq, void *dev_id)
+{
+	struct atiixp *chip = dev_id;
+	unsigned int status;
+
+	status = atiixp_read(chip, ISR);
+
+	if (! status)
+		return IRQ_NONE;
+
+	/* process audio DMA */
+	if (status & ATI_REG_ISR_OUT_XRUN)
+		snd_atiixp_xrun_dma(chip,  &chip->dmas[ATI_DMA_PLAYBACK]);
+	else if (status & ATI_REG_ISR_OUT_STATUS)
+		snd_atiixp_update_dma(chip, &chip->dmas[ATI_DMA_PLAYBACK]);
+	if (status & ATI_REG_ISR_IN_XRUN)
+		snd_atiixp_xrun_dma(chip,  &chip->dmas[ATI_DMA_CAPTURE]);
+	else if (status & ATI_REG_ISR_IN_STATUS)
+		snd_atiixp_update_dma(chip, &chip->dmas[ATI_DMA_CAPTURE]);
+	if (! chip->spdif_over_aclink) {
+		if (status & ATI_REG_ISR_SPDF_XRUN)
+			snd_atiixp_xrun_dma(chip,  &chip->dmas[ATI_DMA_SPDIF]);
+		else if (status & ATI_REG_ISR_SPDF_STATUS)
+			snd_atiixp_update_dma(chip, &chip->dmas[ATI_DMA_SPDIF]);
+	}
+
+	/* for codec detection */
+	if (status & CODEC_CHECK_BITS) {
+		unsigned int detected;
+		detected = status & CODEC_CHECK_BITS;
+		spin_lock(&chip->reg_lock);
+		chip->codec_not_ready_bits |= detected;
+		atiixp_update(chip, IER, detected, 0); /* disable the detected irqs */
+		spin_unlock(&chip->reg_lock);
+	}
+
+	/* ack */
+	atiixp_write(chip, ISR, status);
+
+	return IRQ_HANDLED;
+}
+
+
+/*
+ * ac97 mixer section
+ */
+
+static struct ac97_quirk ac97_quirks[] __devinitdata = {
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x006b,
+		.name = "HP Pavilion ZV5030US",
+		.type = AC97_TUNE_MUTE_LED
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x308b,
+		.name = "HP nx6125",
+		.type = AC97_TUNE_MUTE_LED
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x3091,
+		.name = "unknown HP",
+		.type = AC97_TUNE_MUTE_LED
+	},
+	{ } /* terminator */
+};
+
+static int __devinit snd_atiixp_mixer_new(struct atiixp *chip, int clock,
+					  const char *quirk_override)
+{
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	int i, err;
+	int codec_count;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_atiixp_ac97_write,
+		.read = snd_atiixp_ac97_read,
+	};
+	static unsigned int codec_skip[NUM_ATI_CODECS] = {
+		ATI_REG_ISR_CODEC0_NOT_READY,
+		ATI_REG_ISR_CODEC1_NOT_READY,
+		ATI_REG_ISR_CODEC2_NOT_READY,
+	};
+
+	if (snd_atiixp_codec_detect(chip) < 0)
+		return -ENXIO;
+
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus)) < 0)
+		return err;
+	pbus->clock = clock;
+	chip->ac97_bus = pbus;
+
+	codec_count = 0;
+	for (i = 0; i < NUM_ATI_CODECS; i++) {
+		if (chip->codec_not_ready_bits & codec_skip[i])
+			continue;
+		memset(&ac97, 0, sizeof(ac97));
+		ac97.private_data = chip;
+		ac97.pci = chip->pci;
+		ac97.num = i;
+		ac97.scaps = AC97_SCAP_SKIP_MODEM | AC97_SCAP_POWER_SAVE;
+		if (! chip->spdif_over_aclink)
+			ac97.scaps |= AC97_SCAP_NO_SPDIF;
+		if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97[i])) < 0) {
+			chip->ac97[i] = NULL; /* to be sure */
+			snd_printdd("atiixp: codec %d not available for audio\n", i);
+			continue;
+		}
+		codec_count++;
+	}
+
+	if (! codec_count) {
+		snd_printk(KERN_ERR "atiixp: no codec available\n");
+		return -ENODEV;
+	}
+
+	snd_ac97_tune_hardware(chip->ac97[0], ac97_quirks, quirk_override);
+
+	return 0;
+}
+
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+static int snd_atiixp_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct atiixp *chip = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	for (i = 0; i < NUM_ATI_PCMDEVS; i++)
+		if (chip->pcmdevs[i]) {
+			struct atiixp_dma *dma = &chip->dmas[i];
+			if (dma->substream && dma->running)
+				dma->saved_curptr = readl(chip->remap_addr +
+							  dma->ops->dt_cur);
+			snd_pcm_suspend_all(chip->pcmdevs[i]);
+		}
+	for (i = 0; i < NUM_ATI_CODECS; i++)
+		snd_ac97_suspend(chip->ac97[i]);
+	snd_atiixp_aclink_down(chip);
+	snd_atiixp_chip_stop(chip);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_atiixp_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct atiixp *chip = card->private_data;
+	int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "atiixp: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_atiixp_aclink_reset(chip);
+	snd_atiixp_chip_start(chip);
+
+	for (i = 0; i < NUM_ATI_CODECS; i++)
+		snd_ac97_resume(chip->ac97[i]);
+
+	for (i = 0; i < NUM_ATI_PCMDEVS; i++)
+		if (chip->pcmdevs[i]) {
+			struct atiixp_dma *dma = &chip->dmas[i];
+			if (dma->substream && dma->suspended) {
+				dma->ops->enable_dma(chip, 1);
+				dma->substream->ops->prepare(dma->substream);
+				writel((u32)dma->desc_buf.addr | ATI_REG_LINKPTR_EN,
+				       chip->remap_addr + dma->ops->llp_offset);
+				writel(dma->saved_curptr, chip->remap_addr +
+				       dma->ops->dt_cur);
+			}
+		}
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+
+#ifdef CONFIG_PROC_FS
+/*
+ * proc interface for register dump
+ */
+
+static void snd_atiixp_proc_read(struct snd_info_entry *entry,
+				 struct snd_info_buffer *buffer)
+{
+	struct atiixp *chip = entry->private_data;
+	int i;
+
+	for (i = 0; i < 256; i += 4)
+		snd_iprintf(buffer, "%02x: %08x\n", i, readl(chip->remap_addr + i));
+}
+
+static void __devinit snd_atiixp_proc_init(struct atiixp *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(chip->card, "atiixp", &entry))
+		snd_info_set_text_ops(entry, chip, snd_atiixp_proc_read);
+}
+#else /* !CONFIG_PROC_FS */
+#define snd_atiixp_proc_init(chip)
+#endif
+
+
+/*
+ * destructor
+ */
+
+static int snd_atiixp_free(struct atiixp *chip)
+{
+	if (chip->irq < 0)
+		goto __hw_end;
+	snd_atiixp_chip_stop(chip);
+
+      __hw_end:
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	if (chip->remap_addr)
+		iounmap(chip->remap_addr);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_atiixp_dev_free(struct snd_device *device)
+{
+	struct atiixp *chip = device->device_data;
+	return snd_atiixp_free(chip);
+}
+
+/*
+ * constructor for chip instance
+ */
+static int __devinit snd_atiixp_create(struct snd_card *card,
+				      struct pci_dev *pci,
+				      struct atiixp **r_chip)
+{
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_atiixp_dev_free,
+	};
+	struct atiixp *chip;
+	int err;
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&chip->reg_lock);
+	mutex_init(&chip->open_mutex);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	if ((err = pci_request_regions(pci, "ATI IXP AC97")) < 0) {
+		pci_disable_device(pci);
+		kfree(chip);
+		return err;
+	}
+	chip->addr = pci_resource_start(pci, 0);
+	chip->remap_addr = pci_ioremap_bar(pci, 0);
+	if (chip->remap_addr == NULL) {
+		snd_printk(KERN_ERR "AC'97 space ioremap problem\n");
+		snd_atiixp_free(chip);
+		return -EIO;
+	}
+
+	if (request_irq(pci->irq, snd_atiixp_interrupt, IRQF_SHARED,
+			card->shortname, chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_atiixp_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+	pci_set_master(pci);
+	synchronize_irq(chip->irq);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_atiixp_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*r_chip = chip;
+	return 0;
+}
+
+
+static int __devinit snd_atiixp_probe(struct pci_dev *pci,
+				     const struct pci_device_id *pci_id)
+{
+	struct snd_card *card;
+	struct atiixp *chip;
+	int err;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	strcpy(card->driver, spdif_aclink ? "ATIIXP" : "ATIIXP-SPDMA");
+	strcpy(card->shortname, "ATI IXP");
+	if ((err = snd_atiixp_create(card, pci, &chip)) < 0)
+		goto __error;
+	card->private_data = chip;
+
+	if ((err = snd_atiixp_aclink_reset(chip)) < 0)
+		goto __error;
+
+	chip->spdif_over_aclink = spdif_aclink;
+
+	if ((err = snd_atiixp_mixer_new(chip, ac97_clock, ac97_quirk)) < 0)
+		goto __error;
+
+	if ((err = snd_atiixp_pcm_new(chip)) < 0)
+		goto __error;
+	
+	snd_atiixp_proc_init(chip);
+
+	snd_atiixp_chip_start(chip);
+
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s rev %x with %s at %#lx, irq %i", card->shortname,
+		 pci->revision,
+		 chip->ac97[0] ? snd_ac97_get_short_name(chip->ac97[0]) : "?",
+		 chip->addr, chip->irq);
+
+	if ((err = snd_card_register(card)) < 0)
+		goto __error;
+
+	pci_set_drvdata(pci, card);
+	return 0;
+
+ __error:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit snd_atiixp_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "ATI IXP AC97 controller",
+	.id_table = snd_atiixp_ids,
+	.probe = snd_atiixp_probe,
+	.remove = __devexit_p(snd_atiixp_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_atiixp_suspend,
+	.resume = snd_atiixp_resume,
+#endif
+};
+
+
+static int __init alsa_card_atiixp_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_atiixp_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_atiixp_init)
+module_exit(alsa_card_atiixp_exit)
diff --git a/linux/sound/pci/atiixp_modem.c b/linux/sound/pci/atiixp_modem.c
new file mode 100644
index 0000000..91d7036
--- /dev/null
+++ b/linux/sound/pci/atiixp_modem.c
@@ -0,0 +1,1357 @@
+/*
+ *   ALSA driver for ATI IXP 150/200/250 AC97 modem controllers
+ *
+ *	Copyright (c) 2004 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 <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ATI IXP MC97 controller");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ATI,IXP150/200/250}}");
+
+static int index = -2; /* Exclude the first card */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+static int ac97_clock = 48000;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for ATI IXP controller.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for ATI IXP controller.");
+module_param(ac97_clock, int, 0444);
+MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (default 48000Hz).");
+
+/* just for backward compatibility */
+static int enable;
+module_param(enable, bool, 0444);
+
+
+/*
+ */
+
+#define ATI_REG_ISR			0x00	/* interrupt source */
+#define  ATI_REG_ISR_MODEM_IN_XRUN	(1U<<0)
+#define  ATI_REG_ISR_MODEM_IN_STATUS	(1U<<1)
+#define  ATI_REG_ISR_MODEM_OUT1_XRUN	(1U<<2)
+#define  ATI_REG_ISR_MODEM_OUT1_STATUS	(1U<<3)
+#define  ATI_REG_ISR_MODEM_OUT2_XRUN	(1U<<4)
+#define  ATI_REG_ISR_MODEM_OUT2_STATUS	(1U<<5)
+#define  ATI_REG_ISR_MODEM_OUT3_XRUN	(1U<<6)
+#define  ATI_REG_ISR_MODEM_OUT3_STATUS	(1U<<7)
+#define  ATI_REG_ISR_PHYS_INTR		(1U<<8)
+#define  ATI_REG_ISR_PHYS_MISMATCH	(1U<<9)
+#define  ATI_REG_ISR_CODEC0_NOT_READY	(1U<<10)
+#define  ATI_REG_ISR_CODEC1_NOT_READY	(1U<<11)
+#define  ATI_REG_ISR_CODEC2_NOT_READY	(1U<<12)
+#define  ATI_REG_ISR_NEW_FRAME		(1U<<13)
+#define  ATI_REG_ISR_MODEM_GPIO_DATA	(1U<<14)
+
+#define ATI_REG_IER			0x04	/* interrupt enable */
+#define  ATI_REG_IER_MODEM_IN_XRUN_EN	(1U<<0)
+#define  ATI_REG_IER_MODEM_STATUS_EN	(1U<<1)
+#define  ATI_REG_IER_MODEM_OUT1_XRUN_EN	(1U<<2)
+#define  ATI_REG_IER_MODEM_OUT2_XRUN_EN	(1U<<4)
+#define  ATI_REG_IER_MODEM_OUT3_XRUN_EN	(1U<<6)
+#define  ATI_REG_IER_PHYS_INTR_EN	(1U<<8)
+#define  ATI_REG_IER_PHYS_MISMATCH_EN	(1U<<9)
+#define  ATI_REG_IER_CODEC0_INTR_EN	(1U<<10)
+#define  ATI_REG_IER_CODEC1_INTR_EN	(1U<<11)
+#define  ATI_REG_IER_CODEC2_INTR_EN	(1U<<12)
+#define  ATI_REG_IER_NEW_FRAME_EN	(1U<<13)	/* (RO */
+#define  ATI_REG_IER_MODEM_GPIO_DATA_EN	(1U<<14)	/* (WO) modem is running */
+#define  ATI_REG_IER_MODEM_SET_BUS_BUSY	(1U<<15)
+
+#define ATI_REG_CMD			0x08	/* command */
+#define  ATI_REG_CMD_POWERDOWN	(1U<<0)
+#define  ATI_REG_CMD_MODEM_RECEIVE_EN	(1U<<1)	/* modem only */
+#define  ATI_REG_CMD_MODEM_SEND1_EN	(1U<<2)	/* modem only */
+#define  ATI_REG_CMD_MODEM_SEND2_EN	(1U<<3)	/* modem only */
+#define  ATI_REG_CMD_MODEM_SEND3_EN	(1U<<4)	/* modem only */
+#define  ATI_REG_CMD_MODEM_STATUS_MEM	(1U<<5)	/* modem only */
+#define  ATI_REG_CMD_MODEM_IN_DMA_EN	(1U<<8)	/* modem only */
+#define  ATI_REG_CMD_MODEM_OUT_DMA1_EN	(1U<<9)	/* modem only */
+#define  ATI_REG_CMD_MODEM_OUT_DMA2_EN	(1U<<10)	/* modem only */
+#define  ATI_REG_CMD_MODEM_OUT_DMA3_EN	(1U<<11)	/* modem only */
+#define  ATI_REG_CMD_AUDIO_PRESENT	(1U<<20)
+#define  ATI_REG_CMD_MODEM_GPIO_THRU_DMA	(1U<<22)	/* modem only */
+#define  ATI_REG_CMD_LOOPBACK_EN	(1U<<23)
+#define  ATI_REG_CMD_PACKED_DIS		(1U<<24)
+#define  ATI_REG_CMD_BURST_EN		(1U<<25)
+#define  ATI_REG_CMD_PANIC_EN		(1U<<26)
+#define  ATI_REG_CMD_MODEM_PRESENT	(1U<<27)
+#define  ATI_REG_CMD_ACLINK_ACTIVE	(1U<<28)
+#define  ATI_REG_CMD_AC_SOFT_RESET	(1U<<29)
+#define  ATI_REG_CMD_AC_SYNC		(1U<<30)
+#define  ATI_REG_CMD_AC_RESET		(1U<<31)
+
+#define ATI_REG_PHYS_OUT_ADDR		0x0c
+#define  ATI_REG_PHYS_OUT_CODEC_MASK	(3U<<0)
+#define  ATI_REG_PHYS_OUT_RW		(1U<<2)
+#define  ATI_REG_PHYS_OUT_ADDR_EN	(1U<<8)
+#define  ATI_REG_PHYS_OUT_ADDR_SHIFT	9
+#define  ATI_REG_PHYS_OUT_DATA_SHIFT	16
+
+#define ATI_REG_PHYS_IN_ADDR		0x10
+#define  ATI_REG_PHYS_IN_READ_FLAG	(1U<<8)
+#define  ATI_REG_PHYS_IN_ADDR_SHIFT	9
+#define  ATI_REG_PHYS_IN_DATA_SHIFT	16
+
+#define ATI_REG_SLOTREQ			0x14
+
+#define ATI_REG_COUNTER			0x18
+#define  ATI_REG_COUNTER_SLOT		(3U<<0)	/* slot # */
+#define  ATI_REG_COUNTER_BITCLOCK	(31U<<8)
+
+#define ATI_REG_IN_FIFO_THRESHOLD	0x1c
+
+#define ATI_REG_MODEM_IN_DMA_LINKPTR	0x20
+#define ATI_REG_MODEM_IN_DMA_DT_START	0x24	/* RO */
+#define ATI_REG_MODEM_IN_DMA_DT_NEXT	0x28	/* RO */
+#define ATI_REG_MODEM_IN_DMA_DT_CUR	0x2c	/* RO */
+#define ATI_REG_MODEM_IN_DMA_DT_SIZE	0x30
+#define ATI_REG_MODEM_OUT_FIFO		0x34	/* output threshold */
+#define  ATI_REG_MODEM_OUT1_DMA_THRESHOLD_MASK	(0xf<<16)
+#define  ATI_REG_MODEM_OUT1_DMA_THRESHOLD_SHIFT	16
+#define ATI_REG_MODEM_OUT_DMA1_LINKPTR	0x38
+#define ATI_REG_MODEM_OUT_DMA2_LINKPTR	0x3c
+#define ATI_REG_MODEM_OUT_DMA3_LINKPTR	0x40
+#define ATI_REG_MODEM_OUT_DMA1_DT_START	0x44
+#define ATI_REG_MODEM_OUT_DMA1_DT_NEXT	0x48
+#define ATI_REG_MODEM_OUT_DMA1_DT_CUR	0x4c
+#define ATI_REG_MODEM_OUT_DMA2_DT_START	0x50
+#define ATI_REG_MODEM_OUT_DMA2_DT_NEXT	0x54
+#define ATI_REG_MODEM_OUT_DMA2_DT_CUR	0x58
+#define ATI_REG_MODEM_OUT_DMA3_DT_START	0x5c
+#define ATI_REG_MODEM_OUT_DMA3_DT_NEXT	0x60
+#define ATI_REG_MODEM_OUT_DMA3_DT_CUR	0x64
+#define ATI_REG_MODEM_OUT_DMA12_DT_SIZE	0x68
+#define ATI_REG_MODEM_OUT_DMA3_DT_SIZE	0x6c
+#define ATI_REG_MODEM_OUT_FIFO_USED     0x70
+#define ATI_REG_MODEM_OUT_GPIO		0x74
+#define  ATI_REG_MODEM_OUT_GPIO_EN	   1
+#define  ATI_REG_MODEM_OUT_GPIO_DATA_SHIFT 5
+#define ATI_REG_MODEM_IN_GPIO		0x78
+
+#define ATI_REG_MODEM_MIRROR		0x7c
+#define ATI_REG_AUDIO_MIRROR		0x80
+
+#define ATI_REG_MODEM_FIFO_FLUSH	0x88
+#define  ATI_REG_MODEM_FIFO_OUT1_FLUSH	(1U<<0)
+#define  ATI_REG_MODEM_FIFO_OUT2_FLUSH	(1U<<1)
+#define  ATI_REG_MODEM_FIFO_OUT3_FLUSH	(1U<<2)
+#define  ATI_REG_MODEM_FIFO_IN_FLUSH	(1U<<3)
+
+/* LINKPTR */
+#define  ATI_REG_LINKPTR_EN		(1U<<0)
+
+#define ATI_MAX_DESCRIPTORS	256	/* max number of descriptor packets */
+
+
+struct atiixp_modem;
+
+/*
+ * DMA packate descriptor
+ */
+
+struct atiixp_dma_desc {
+	u32 addr;	/* DMA buffer address */
+	u16 status;	/* status bits */
+	u16 size;	/* size of the packet in dwords */
+	u32 next;	/* address of the next packet descriptor */
+};
+
+/*
+ * stream enum
+ */
+enum { ATI_DMA_PLAYBACK, ATI_DMA_CAPTURE, NUM_ATI_DMAS }; /* DMAs */
+enum { ATI_PCM_OUT, ATI_PCM_IN, NUM_ATI_PCMS }; /* AC97 pcm slots */
+enum { ATI_PCMDEV_ANALOG, NUM_ATI_PCMDEVS }; /* pcm devices */
+
+#define NUM_ATI_CODECS	3
+
+
+/*
+ * constants and callbacks for each DMA type
+ */
+struct atiixp_dma_ops {
+	int type;			/* ATI_DMA_XXX */
+	unsigned int llp_offset;	/* LINKPTR offset */
+	unsigned int dt_cur;		/* DT_CUR offset */
+	/* called from open callback */
+	void (*enable_dma)(struct atiixp_modem *chip, int on);
+	/* called from trigger (START/STOP) */
+	void (*enable_transfer)(struct atiixp_modem *chip, int on);
+ 	/* called from trigger (STOP only) */
+	void (*flush_dma)(struct atiixp_modem *chip);
+};
+
+/*
+ * DMA stream
+ */
+struct atiixp_dma {
+	const struct atiixp_dma_ops *ops;
+	struct snd_dma_buffer desc_buf;
+	struct snd_pcm_substream *substream;	/* assigned PCM substream */
+	unsigned int buf_addr, buf_bytes;	/* DMA buffer address, bytes */
+	unsigned int period_bytes, periods;
+	int opened;
+	int running;
+	int pcm_open_flag;
+	int ac97_pcm_type;	/* index # of ac97_pcm to access, -1 = not used */
+};
+
+/*
+ * ATI IXP chip
+ */
+struct atiixp_modem {
+	struct snd_card *card;
+	struct pci_dev *pci;
+
+	struct resource *res;		/* memory i/o */
+	unsigned long addr;
+	void __iomem *remap_addr;
+	int irq;
+	
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97 *ac97[NUM_ATI_CODECS];
+
+	spinlock_t reg_lock;
+
+	struct atiixp_dma dmas[NUM_ATI_DMAS];
+	struct ac97_pcm *pcms[NUM_ATI_PCMS];
+	struct snd_pcm *pcmdevs[NUM_ATI_PCMDEVS];
+
+	int max_channels;		/* max. channels for PCM out */
+
+	unsigned int codec_not_ready_bits;	/* for codec detection */
+
+	int spdif_over_aclink;		/* passed from the module option */
+	struct mutex open_mutex;	/* playback open mutex */
+};
+
+
+/*
+ */
+static DEFINE_PCI_DEVICE_TABLE(snd_atiixp_ids) = {
+	{ PCI_VDEVICE(ATI, 0x434d), 0 }, /* SB200 */
+	{ PCI_VDEVICE(ATI, 0x4378), 0 }, /* SB400 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_atiixp_ids);
+
+
+/*
+ * lowlevel functions
+ */
+
+/*
+ * update the bits of the given register.
+ * return 1 if the bits changed.
+ */
+static int snd_atiixp_update_bits(struct atiixp_modem *chip, unsigned int reg,
+				  unsigned int mask, unsigned int value)
+{
+	void __iomem *addr = chip->remap_addr + reg;
+	unsigned int data, old_data;
+	old_data = data = readl(addr);
+	data &= ~mask;
+	data |= value;
+	if (old_data == data)
+		return 0;
+	writel(data, addr);
+	return 1;
+}
+
+/*
+ * macros for easy use
+ */
+#define atiixp_write(chip,reg,value) \
+	writel(value, chip->remap_addr + ATI_REG_##reg)
+#define atiixp_read(chip,reg) \
+	readl(chip->remap_addr + ATI_REG_##reg)
+#define atiixp_update(chip,reg,mask,val) \
+	snd_atiixp_update_bits(chip, ATI_REG_##reg, mask, val)
+
+/*
+ * handling DMA packets
+ *
+ * we allocate a linear buffer for the DMA, and split it to  each packet.
+ * in a future version, a scatter-gather buffer should be implemented.
+ */
+
+#define ATI_DESC_LIST_SIZE \
+	PAGE_ALIGN(ATI_MAX_DESCRIPTORS * sizeof(struct atiixp_dma_desc))
+
+/*
+ * build packets ring for the given buffer size.
+ *
+ * IXP handles the buffer descriptors, which are connected as a linked
+ * list.  although we can change the list dynamically, in this version,
+ * a static RING of buffer descriptors is used.
+ *
+ * the ring is built in this function, and is set up to the hardware. 
+ */
+static int atiixp_build_dma_packets(struct atiixp_modem *chip,
+				    struct atiixp_dma *dma,
+				    struct snd_pcm_substream *substream,
+				    unsigned int periods,
+				    unsigned int period_bytes)
+{
+	unsigned int i;
+	u32 addr, desc_addr;
+	unsigned long flags;
+
+	if (periods > ATI_MAX_DESCRIPTORS)
+		return -ENOMEM;
+
+	if (dma->desc_buf.area == NULL) {
+		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+					ATI_DESC_LIST_SIZE, &dma->desc_buf) < 0)
+			return -ENOMEM;
+		dma->period_bytes = dma->periods = 0; /* clear */
+	}
+
+	if (dma->periods == periods && dma->period_bytes == period_bytes)
+		return 0;
+
+	/* reset DMA before changing the descriptor table */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	writel(0, chip->remap_addr + dma->ops->llp_offset);
+	dma->ops->enable_dma(chip, 0);
+	dma->ops->enable_dma(chip, 1);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	/* fill the entries */
+	addr = (u32)substream->runtime->dma_addr;
+	desc_addr = (u32)dma->desc_buf.addr;
+	for (i = 0; i < periods; i++) {
+		struct atiixp_dma_desc *desc;
+		desc = &((struct atiixp_dma_desc *)dma->desc_buf.area)[i];
+		desc->addr = cpu_to_le32(addr);
+		desc->status = 0;
+		desc->size = period_bytes >> 2; /* in dwords */
+		desc_addr += sizeof(struct atiixp_dma_desc);
+		if (i == periods - 1)
+			desc->next = cpu_to_le32((u32)dma->desc_buf.addr);
+		else
+			desc->next = cpu_to_le32(desc_addr);
+		addr += period_bytes;
+	}
+
+	writel((u32)dma->desc_buf.addr | ATI_REG_LINKPTR_EN,
+	       chip->remap_addr + dma->ops->llp_offset);
+
+	dma->period_bytes = period_bytes;
+	dma->periods = periods;
+
+	return 0;
+}
+
+/*
+ * remove the ring buffer and release it if assigned
+ */
+static void atiixp_clear_dma_packets(struct atiixp_modem *chip,
+				     struct atiixp_dma *dma,
+				     struct snd_pcm_substream *substream)
+{
+	if (dma->desc_buf.area) {
+		writel(0, chip->remap_addr + dma->ops->llp_offset);
+		snd_dma_free_pages(&dma->desc_buf);
+		dma->desc_buf.area = NULL;
+	}
+}
+
+/*
+ * AC97 interface
+ */
+static int snd_atiixp_acquire_codec(struct atiixp_modem *chip)
+{
+	int timeout = 1000;
+
+	while (atiixp_read(chip, PHYS_OUT_ADDR) & ATI_REG_PHYS_OUT_ADDR_EN) {
+		if (! timeout--) {
+			snd_printk(KERN_WARNING "atiixp-modem: codec acquire timeout\n");
+			return -EBUSY;
+		}
+		udelay(1);
+	}
+	return 0;
+}
+
+static unsigned short snd_atiixp_codec_read(struct atiixp_modem *chip,
+					    unsigned short codec,
+					    unsigned short reg)
+{
+	unsigned int data;
+	int timeout;
+
+	if (snd_atiixp_acquire_codec(chip) < 0)
+		return 0xffff;
+	data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) |
+		ATI_REG_PHYS_OUT_ADDR_EN |
+		ATI_REG_PHYS_OUT_RW |
+		codec;
+	atiixp_write(chip, PHYS_OUT_ADDR, data);
+	if (snd_atiixp_acquire_codec(chip) < 0)
+		return 0xffff;
+	timeout = 1000;
+	do {
+		data = atiixp_read(chip, PHYS_IN_ADDR);
+		if (data & ATI_REG_PHYS_IN_READ_FLAG)
+			return data >> ATI_REG_PHYS_IN_DATA_SHIFT;
+		udelay(1);
+	} while (--timeout);
+	/* time out may happen during reset */
+	if (reg < 0x7c)
+		snd_printk(KERN_WARNING "atiixp-modem: codec read timeout (reg %x)\n", reg);
+	return 0xffff;
+}
+
+
+static void snd_atiixp_codec_write(struct atiixp_modem *chip,
+				   unsigned short codec,
+				   unsigned short reg, unsigned short val)
+{
+	unsigned int data;
+    
+	if (snd_atiixp_acquire_codec(chip) < 0)
+		return;
+	data = ((unsigned int)val << ATI_REG_PHYS_OUT_DATA_SHIFT) |
+		((unsigned int)reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) |
+		ATI_REG_PHYS_OUT_ADDR_EN | codec;
+	atiixp_write(chip, PHYS_OUT_ADDR, data);
+}
+
+
+static unsigned short snd_atiixp_ac97_read(struct snd_ac97 *ac97,
+					   unsigned short reg)
+{
+	struct atiixp_modem *chip = ac97->private_data;
+	return snd_atiixp_codec_read(chip, ac97->num, reg);
+    
+}
+
+static void snd_atiixp_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+				  unsigned short val)
+{
+	struct atiixp_modem *chip = ac97->private_data;
+	if (reg == AC97_GPIO_STATUS) {
+		atiixp_write(chip, MODEM_OUT_GPIO,
+			(val << ATI_REG_MODEM_OUT_GPIO_DATA_SHIFT) | ATI_REG_MODEM_OUT_GPIO_EN);
+		return;
+	}
+	snd_atiixp_codec_write(chip, ac97->num, reg, val);
+}
+
+/*
+ * reset AC link
+ */
+static int snd_atiixp_aclink_reset(struct atiixp_modem *chip)
+{
+	int timeout;
+
+	/* reset powerdoewn */
+	if (atiixp_update(chip, CMD, ATI_REG_CMD_POWERDOWN, 0))
+		udelay(10);
+
+	/* perform a software reset */
+	atiixp_update(chip, CMD, ATI_REG_CMD_AC_SOFT_RESET, ATI_REG_CMD_AC_SOFT_RESET);
+	atiixp_read(chip, CMD);
+	udelay(10);
+	atiixp_update(chip, CMD, ATI_REG_CMD_AC_SOFT_RESET, 0);
+    
+	timeout = 10;
+	while (! (atiixp_read(chip, CMD) & ATI_REG_CMD_ACLINK_ACTIVE)) {
+		/* do a hard reset */
+		atiixp_update(chip, CMD, ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET,
+			      ATI_REG_CMD_AC_SYNC);
+		atiixp_read(chip, CMD);
+		msleep(1);
+		atiixp_update(chip, CMD, ATI_REG_CMD_AC_RESET, ATI_REG_CMD_AC_RESET);
+		if (--timeout) {
+			snd_printk(KERN_ERR "atiixp-modem: codec reset timeout\n");
+			break;
+		}
+	}
+
+	/* deassert RESET and assert SYNC to make sure */
+	atiixp_update(chip, CMD, ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET,
+		      ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_atiixp_aclink_down(struct atiixp_modem *chip)
+{
+	// if (atiixp_read(chip, MODEM_MIRROR) & 0x1) /* modem running, too? */
+	//	return -EBUSY;
+	atiixp_update(chip, CMD,
+		     ATI_REG_CMD_POWERDOWN | ATI_REG_CMD_AC_RESET,
+		     ATI_REG_CMD_POWERDOWN);
+	return 0;
+}
+#endif
+
+/*
+ * auto-detection of codecs
+ *
+ * the IXP chip can generate interrupts for the non-existing codecs.
+ * NEW_FRAME interrupt is used to make sure that the interrupt is generated
+ * even if all three codecs are connected.
+ */
+
+#define ALL_CODEC_NOT_READY \
+	    (ATI_REG_ISR_CODEC0_NOT_READY |\
+	     ATI_REG_ISR_CODEC1_NOT_READY |\
+	     ATI_REG_ISR_CODEC2_NOT_READY)
+#define CODEC_CHECK_BITS (ALL_CODEC_NOT_READY|ATI_REG_ISR_NEW_FRAME)
+
+static int snd_atiixp_codec_detect(struct atiixp_modem *chip)
+{
+	int timeout;
+
+	chip->codec_not_ready_bits = 0;
+	atiixp_write(chip, IER, CODEC_CHECK_BITS);
+	/* wait for the interrupts */
+	timeout = 50;
+	while (timeout-- > 0) {
+		msleep(1);
+		if (chip->codec_not_ready_bits)
+			break;
+	}
+	atiixp_write(chip, IER, 0); /* disable irqs */
+
+	if ((chip->codec_not_ready_bits & ALL_CODEC_NOT_READY) == ALL_CODEC_NOT_READY) {
+		snd_printk(KERN_ERR "atiixp-modem: no codec detected!\n");
+		return -ENXIO;
+	}
+	return 0;
+}
+
+
+/*
+ * enable DMA and irqs
+ */
+static int snd_atiixp_chip_start(struct atiixp_modem *chip)
+{
+	unsigned int reg;
+
+	/* set up spdif, enable burst mode */
+	reg = atiixp_read(chip, CMD);
+	reg |= ATI_REG_CMD_BURST_EN;
+	if(!(reg & ATI_REG_CMD_MODEM_PRESENT))
+		reg |= ATI_REG_CMD_MODEM_PRESENT;
+	atiixp_write(chip, CMD, reg);
+
+	/* clear all interrupt source */
+	atiixp_write(chip, ISR, 0xffffffff);
+	/* enable irqs */
+	atiixp_write(chip, IER,
+		     ATI_REG_IER_MODEM_STATUS_EN |
+		     ATI_REG_IER_MODEM_IN_XRUN_EN |
+		     ATI_REG_IER_MODEM_OUT1_XRUN_EN);
+	return 0;
+}
+
+
+/*
+ * disable DMA and IRQs
+ */
+static int snd_atiixp_chip_stop(struct atiixp_modem *chip)
+{
+	/* clear interrupt source */
+	atiixp_write(chip, ISR, atiixp_read(chip, ISR));
+	/* disable irqs */
+	atiixp_write(chip, IER, 0);
+	return 0;
+}
+
+
+/*
+ * PCM section
+ */
+
+/*
+ * pointer callback simplly reads XXX_DMA_DT_CUR register as the current
+ * position.  when SG-buffer is implemented, the offset must be calculated
+ * correctly...
+ */
+static snd_pcm_uframes_t snd_atiixp_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct atiixp_dma *dma = runtime->private_data;
+	unsigned int curptr;
+	int timeout = 1000;
+
+	while (timeout--) {
+		curptr = readl(chip->remap_addr + dma->ops->dt_cur);
+		if (curptr < dma->buf_addr)
+			continue;
+		curptr -= dma->buf_addr;
+		if (curptr >= dma->buf_bytes)
+			continue;
+		return bytes_to_frames(runtime, curptr);
+	}
+	snd_printd("atiixp-modem: invalid DMA pointer read 0x%x (buf=%x)\n",
+		   readl(chip->remap_addr + dma->ops->dt_cur), dma->buf_addr);
+	return 0;
+}
+
+/*
+ * XRUN detected, and stop the PCM substream
+ */
+static void snd_atiixp_xrun_dma(struct atiixp_modem *chip,
+				struct atiixp_dma *dma)
+{
+	if (! dma->substream || ! dma->running)
+		return;
+	snd_printdd("atiixp-modem: XRUN detected (DMA %d)\n", dma->ops->type);
+	snd_pcm_stop(dma->substream, SNDRV_PCM_STATE_XRUN);
+}
+
+/*
+ * the period ack.  update the substream.
+ */
+static void snd_atiixp_update_dma(struct atiixp_modem *chip,
+				  struct atiixp_dma *dma)
+{
+	if (! dma->substream || ! dma->running)
+		return;
+	snd_pcm_period_elapsed(dma->substream);
+}
+
+/* set BUS_BUSY interrupt bit if any DMA is running */
+/* call with spinlock held */
+static void snd_atiixp_check_bus_busy(struct atiixp_modem *chip)
+{
+	unsigned int bus_busy;
+	if (atiixp_read(chip, CMD) & (ATI_REG_CMD_MODEM_SEND1_EN |
+				      ATI_REG_CMD_MODEM_RECEIVE_EN))
+		bus_busy = ATI_REG_IER_MODEM_SET_BUS_BUSY;
+	else
+		bus_busy = 0;
+	atiixp_update(chip, IER, ATI_REG_IER_MODEM_SET_BUS_BUSY, bus_busy);
+}
+
+/* common trigger callback
+ * calling the lowlevel callbacks in it
+ */
+static int snd_atiixp_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+	struct atiixp_dma *dma = substream->runtime->private_data;
+	int err = 0;
+
+	if (snd_BUG_ON(!dma->ops->enable_transfer ||
+		       !dma->ops->flush_dma))
+		return -EINVAL;
+
+	spin_lock(&chip->reg_lock);
+	switch(cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		dma->ops->enable_transfer(chip, 1);
+		dma->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		dma->ops->enable_transfer(chip, 0);
+		dma->running = 0;
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	if (! err) {
+	snd_atiixp_check_bus_busy(chip);
+	if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		dma->ops->flush_dma(chip);
+		snd_atiixp_check_bus_busy(chip);
+	}
+	}
+	spin_unlock(&chip->reg_lock);
+	return err;
+}
+
+
+/*
+ * lowlevel callbacks for each DMA type
+ *
+ * every callback is supposed to be called in chip->reg_lock spinlock
+ */
+
+/* flush FIFO of analog OUT DMA */
+static void atiixp_out_flush_dma(struct atiixp_modem *chip)
+{
+	atiixp_write(chip, MODEM_FIFO_FLUSH, ATI_REG_MODEM_FIFO_OUT1_FLUSH);
+}
+
+/* enable/disable analog OUT DMA */
+static void atiixp_out_enable_dma(struct atiixp_modem *chip, int on)
+{
+	unsigned int data;
+	data = atiixp_read(chip, CMD);
+	if (on) {
+		if (data & ATI_REG_CMD_MODEM_OUT_DMA1_EN)
+			return;
+		atiixp_out_flush_dma(chip);
+		data |= ATI_REG_CMD_MODEM_OUT_DMA1_EN;
+	} else
+		data &= ~ATI_REG_CMD_MODEM_OUT_DMA1_EN;
+	atiixp_write(chip, CMD, data);
+}
+
+/* start/stop transfer over OUT DMA */
+static void atiixp_out_enable_transfer(struct atiixp_modem *chip, int on)
+{
+	atiixp_update(chip, CMD, ATI_REG_CMD_MODEM_SEND1_EN,
+		      on ? ATI_REG_CMD_MODEM_SEND1_EN : 0);
+}
+
+/* enable/disable analog IN DMA */
+static void atiixp_in_enable_dma(struct atiixp_modem *chip, int on)
+{
+	atiixp_update(chip, CMD, ATI_REG_CMD_MODEM_IN_DMA_EN,
+		      on ? ATI_REG_CMD_MODEM_IN_DMA_EN : 0);
+}
+
+/* start/stop analog IN DMA */
+static void atiixp_in_enable_transfer(struct atiixp_modem *chip, int on)
+{
+	if (on) {
+		unsigned int data = atiixp_read(chip, CMD);
+		if (! (data & ATI_REG_CMD_MODEM_RECEIVE_EN)) {
+			data |= ATI_REG_CMD_MODEM_RECEIVE_EN;
+			atiixp_write(chip, CMD, data);
+		}
+	} else
+		atiixp_update(chip, CMD, ATI_REG_CMD_MODEM_RECEIVE_EN, 0);
+}
+
+/* flush FIFO of analog IN DMA */
+static void atiixp_in_flush_dma(struct atiixp_modem *chip)
+{
+	atiixp_write(chip, MODEM_FIFO_FLUSH, ATI_REG_MODEM_FIFO_IN_FLUSH);
+}
+
+/* set up slots and formats for analog OUT */
+static int snd_atiixp_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+	unsigned int data;
+
+	spin_lock_irq(&chip->reg_lock);
+	/* set output threshold */
+	data = atiixp_read(chip, MODEM_OUT_FIFO);
+	data &= ~ATI_REG_MODEM_OUT1_DMA_THRESHOLD_MASK;
+	data |= 0x04 << ATI_REG_MODEM_OUT1_DMA_THRESHOLD_SHIFT;
+	atiixp_write(chip, MODEM_OUT_FIFO, data);
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+/* set up slots and formats for analog IN */
+static int snd_atiixp_capture_prepare(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+/*
+ * hw_params - allocate the buffer and set up buffer descriptors
+ */
+static int snd_atiixp_pcm_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *hw_params)
+{
+	struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+	struct atiixp_dma *dma = substream->runtime->private_data;
+	int err;
+	int i;
+
+	err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	dma->buf_addr = substream->runtime->dma_addr;
+	dma->buf_bytes = params_buffer_bytes(hw_params);
+
+	err = atiixp_build_dma_packets(chip, dma, substream,
+				       params_periods(hw_params),
+				       params_period_bytes(hw_params));
+	if (err < 0)
+		return err;
+
+	/* set up modem rate */
+	for (i = 0; i < NUM_ATI_CODECS; i++) {
+		if (! chip->ac97[i])
+			continue;
+		snd_ac97_write(chip->ac97[i], AC97_LINE1_RATE, params_rate(hw_params));
+		snd_ac97_write(chip->ac97[i], AC97_LINE1_LEVEL, 0);
+	}
+
+	return err;
+}
+
+static int snd_atiixp_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+	struct atiixp_dma *dma = substream->runtime->private_data;
+
+	atiixp_clear_dma_packets(chip, dma, substream);
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+
+/*
+ * pcm hardware definition, identical for all DMA types
+ */
+static struct snd_pcm_hardware snd_atiixp_pcm_hw =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		(SNDRV_PCM_RATE_8000 |
+				 SNDRV_PCM_RATE_16000 |
+				 SNDRV_PCM_RATE_KNOT),
+	.rate_min =		8000,
+	.rate_max =		16000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	256 * 1024,
+	.period_bytes_min =	32,
+	.period_bytes_max =	128 * 1024,
+	.periods_min =		2,
+	.periods_max =		ATI_MAX_DESCRIPTORS,
+};
+
+static int snd_atiixp_pcm_open(struct snd_pcm_substream *substream,
+			       struct atiixp_dma *dma, int pcm_type)
+{
+	struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+	static unsigned int rates[] = { 8000,  9600, 12000, 16000 };
+	static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+		.count = ARRAY_SIZE(rates),
+		.list = rates,
+		.mask = 0,
+	};
+
+	if (snd_BUG_ON(!dma->ops || !dma->ops->enable_dma))
+		return -EINVAL;
+
+	if (dma->opened)
+		return -EBUSY;
+	dma->substream = substream;
+	runtime->hw = snd_atiixp_pcm_hw;
+	dma->ac97_pcm_type = pcm_type;
+	if ((err = snd_pcm_hw_constraint_list(runtime, 0,
+					      SNDRV_PCM_HW_PARAM_RATE,
+					      &hw_constraints_rates)) < 0)
+		return err;
+	if ((err = snd_pcm_hw_constraint_integer(runtime,
+						 SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+	runtime->private_data = dma;
+
+	/* enable DMA bits */
+	spin_lock_irq(&chip->reg_lock);
+	dma->ops->enable_dma(chip, 1);
+	spin_unlock_irq(&chip->reg_lock);
+	dma->opened = 1;
+
+	return 0;
+}
+
+static int snd_atiixp_pcm_close(struct snd_pcm_substream *substream,
+				struct atiixp_dma *dma)
+{
+	struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+	/* disable DMA bits */
+	if (snd_BUG_ON(!dma->ops || !dma->ops->enable_dma))
+		return -EINVAL;
+	spin_lock_irq(&chip->reg_lock);
+	dma->ops->enable_dma(chip, 0);
+	spin_unlock_irq(&chip->reg_lock);
+	dma->substream = NULL;
+	dma->opened = 0;
+	return 0;
+}
+
+/*
+ */
+static int snd_atiixp_playback_open(struct snd_pcm_substream *substream)
+{
+	struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+	int err;
+
+	mutex_lock(&chip->open_mutex);
+	err = snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_PLAYBACK], 0);
+	mutex_unlock(&chip->open_mutex);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_atiixp_playback_close(struct snd_pcm_substream *substream)
+{
+	struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+	int err;
+	mutex_lock(&chip->open_mutex);
+	err = snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_PLAYBACK]);
+	mutex_unlock(&chip->open_mutex);
+	return err;
+}
+
+static int snd_atiixp_capture_open(struct snd_pcm_substream *substream)
+{
+	struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+	return snd_atiixp_pcm_open(substream, &chip->dmas[ATI_DMA_CAPTURE], 1);
+}
+
+static int snd_atiixp_capture_close(struct snd_pcm_substream *substream)
+{
+	struct atiixp_modem *chip = snd_pcm_substream_chip(substream);
+	return snd_atiixp_pcm_close(substream, &chip->dmas[ATI_DMA_CAPTURE]);
+}
+
+
+/* AC97 playback */
+static struct snd_pcm_ops snd_atiixp_playback_ops = {
+	.open =		snd_atiixp_playback_open,
+	.close =	snd_atiixp_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_atiixp_pcm_hw_params,
+	.hw_free =	snd_atiixp_pcm_hw_free,
+	.prepare =	snd_atiixp_playback_prepare,
+	.trigger =	snd_atiixp_pcm_trigger,
+	.pointer =	snd_atiixp_pcm_pointer,
+};
+
+/* AC97 capture */
+static struct snd_pcm_ops snd_atiixp_capture_ops = {
+	.open =		snd_atiixp_capture_open,
+	.close =	snd_atiixp_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_atiixp_pcm_hw_params,
+	.hw_free =	snd_atiixp_pcm_hw_free,
+	.prepare =	snd_atiixp_capture_prepare,
+	.trigger =	snd_atiixp_pcm_trigger,
+	.pointer =	snd_atiixp_pcm_pointer,
+};
+
+static struct atiixp_dma_ops snd_atiixp_playback_dma_ops = {
+	.type = ATI_DMA_PLAYBACK,
+	.llp_offset = ATI_REG_MODEM_OUT_DMA1_LINKPTR,
+	.dt_cur = ATI_REG_MODEM_OUT_DMA1_DT_CUR,
+	.enable_dma = atiixp_out_enable_dma,
+	.enable_transfer = atiixp_out_enable_transfer,
+	.flush_dma = atiixp_out_flush_dma,
+};
+	
+static struct atiixp_dma_ops snd_atiixp_capture_dma_ops = {
+	.type = ATI_DMA_CAPTURE,
+	.llp_offset = ATI_REG_MODEM_IN_DMA_LINKPTR,
+	.dt_cur = ATI_REG_MODEM_IN_DMA_DT_CUR,
+	.enable_dma = atiixp_in_enable_dma,
+	.enable_transfer = atiixp_in_enable_transfer,
+	.flush_dma = atiixp_in_flush_dma,
+};
+
+static int __devinit snd_atiixp_pcm_new(struct atiixp_modem *chip)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	/* initialize constants */
+	chip->dmas[ATI_DMA_PLAYBACK].ops = &snd_atiixp_playback_dma_ops;
+	chip->dmas[ATI_DMA_CAPTURE].ops = &snd_atiixp_capture_dma_ops;
+
+	/* PCM #0: analog I/O */
+	err = snd_pcm_new(chip->card, "ATI IXP MC97", ATI_PCMDEV_ANALOG, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_atiixp_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_atiixp_capture_ops);
+	pcm->dev_class = SNDRV_PCM_CLASS_MODEM;
+	pcm->private_data = chip;
+	strcpy(pcm->name, "ATI IXP MC97");
+	chip->pcmdevs[ATI_PCMDEV_ANALOG] = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci),
+					      64*1024, 128*1024);
+
+	return 0;
+}
+
+
+
+/*
+ * interrupt handler
+ */
+static irqreturn_t snd_atiixp_interrupt(int irq, void *dev_id)
+{
+	struct atiixp_modem *chip = dev_id;
+	unsigned int status;
+
+	status = atiixp_read(chip, ISR);
+
+	if (! status)
+		return IRQ_NONE;
+
+	/* process audio DMA */
+	if (status & ATI_REG_ISR_MODEM_OUT1_XRUN)
+		snd_atiixp_xrun_dma(chip,  &chip->dmas[ATI_DMA_PLAYBACK]);
+	else if (status & ATI_REG_ISR_MODEM_OUT1_STATUS)
+		snd_atiixp_update_dma(chip, &chip->dmas[ATI_DMA_PLAYBACK]);
+	if (status & ATI_REG_ISR_MODEM_IN_XRUN)
+		snd_atiixp_xrun_dma(chip,  &chip->dmas[ATI_DMA_CAPTURE]);
+	else if (status & ATI_REG_ISR_MODEM_IN_STATUS)
+		snd_atiixp_update_dma(chip, &chip->dmas[ATI_DMA_CAPTURE]);
+
+	/* for codec detection */
+	if (status & CODEC_CHECK_BITS) {
+		unsigned int detected;
+		detected = status & CODEC_CHECK_BITS;
+		spin_lock(&chip->reg_lock);
+		chip->codec_not_ready_bits |= detected;
+		atiixp_update(chip, IER, detected, 0); /* disable the detected irqs */
+		spin_unlock(&chip->reg_lock);
+	}
+
+	/* ack */
+	atiixp_write(chip, ISR, status);
+
+	return IRQ_HANDLED;
+}
+
+
+/*
+ * ac97 mixer section
+ */
+
+static int __devinit snd_atiixp_mixer_new(struct atiixp_modem *chip, int clock)
+{
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	int i, err;
+	int codec_count;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_atiixp_ac97_write,
+		.read = snd_atiixp_ac97_read,
+	};
+	static unsigned int codec_skip[NUM_ATI_CODECS] = {
+		ATI_REG_ISR_CODEC0_NOT_READY,
+		ATI_REG_ISR_CODEC1_NOT_READY,
+		ATI_REG_ISR_CODEC2_NOT_READY,
+	};
+
+	if (snd_atiixp_codec_detect(chip) < 0)
+		return -ENXIO;
+
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus)) < 0)
+		return err;
+	pbus->clock = clock;
+	chip->ac97_bus = pbus;
+
+	codec_count = 0;
+	for (i = 0; i < NUM_ATI_CODECS; i++) {
+		if (chip->codec_not_ready_bits & codec_skip[i])
+			continue;
+		memset(&ac97, 0, sizeof(ac97));
+		ac97.private_data = chip;
+		ac97.pci = chip->pci;
+		ac97.num = i;
+		ac97.scaps = AC97_SCAP_SKIP_AUDIO | AC97_SCAP_POWER_SAVE;
+		if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97[i])) < 0) {
+			chip->ac97[i] = NULL; /* to be sure */
+			snd_printdd("atiixp-modem: codec %d not available for modem\n", i);
+			continue;
+		}
+		codec_count++;
+	}
+
+	if (! codec_count) {
+		snd_printk(KERN_ERR "atiixp-modem: no codec available\n");
+		return -ENODEV;
+	}
+
+	/* snd_ac97_tune_hardware(chip->ac97, ac97_quirks); */
+
+	return 0;
+}
+
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+static int snd_atiixp_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct atiixp_modem *chip = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	for (i = 0; i < NUM_ATI_PCMDEVS; i++)
+		snd_pcm_suspend_all(chip->pcmdevs[i]);
+	for (i = 0; i < NUM_ATI_CODECS; i++)
+		snd_ac97_suspend(chip->ac97[i]);
+	snd_atiixp_aclink_down(chip);
+	snd_atiixp_chip_stop(chip);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_atiixp_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct atiixp_modem *chip = card->private_data;
+	int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "atiixp-modem: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_atiixp_aclink_reset(chip);
+	snd_atiixp_chip_start(chip);
+
+	for (i = 0; i < NUM_ATI_CODECS; i++)
+		snd_ac97_resume(chip->ac97[i]);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+
+#ifdef CONFIG_PROC_FS
+/*
+ * proc interface for register dump
+ */
+
+static void snd_atiixp_proc_read(struct snd_info_entry *entry,
+				 struct snd_info_buffer *buffer)
+{
+	struct atiixp_modem *chip = entry->private_data;
+	int i;
+
+	for (i = 0; i < 256; i += 4)
+		snd_iprintf(buffer, "%02x: %08x\n", i, readl(chip->remap_addr + i));
+}
+
+static void __devinit snd_atiixp_proc_init(struct atiixp_modem *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(chip->card, "atiixp-modem", &entry))
+		snd_info_set_text_ops(entry, chip, snd_atiixp_proc_read);
+}
+#else
+#define snd_atiixp_proc_init(chip)
+#endif
+
+
+/*
+ * destructor
+ */
+
+static int snd_atiixp_free(struct atiixp_modem *chip)
+{
+	if (chip->irq < 0)
+		goto __hw_end;
+	snd_atiixp_chip_stop(chip);
+
+      __hw_end:
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	if (chip->remap_addr)
+		iounmap(chip->remap_addr);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_atiixp_dev_free(struct snd_device *device)
+{
+	struct atiixp_modem *chip = device->device_data;
+	return snd_atiixp_free(chip);
+}
+
+/*
+ * constructor for chip instance
+ */
+static int __devinit snd_atiixp_create(struct snd_card *card,
+				       struct pci_dev *pci,
+				       struct atiixp_modem **r_chip)
+{
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_atiixp_dev_free,
+	};
+	struct atiixp_modem *chip;
+	int err;
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&chip->reg_lock);
+	mutex_init(&chip->open_mutex);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	if ((err = pci_request_regions(pci, "ATI IXP MC97")) < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+	chip->addr = pci_resource_start(pci, 0);
+	chip->remap_addr = pci_ioremap_bar(pci, 0);
+	if (chip->remap_addr == NULL) {
+		snd_printk(KERN_ERR "AC'97 space ioremap problem\n");
+		snd_atiixp_free(chip);
+		return -EIO;
+	}
+
+	if (request_irq(pci->irq, snd_atiixp_interrupt, IRQF_SHARED,
+			card->shortname, chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_atiixp_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+	pci_set_master(pci);
+	synchronize_irq(chip->irq);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_atiixp_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*r_chip = chip;
+	return 0;
+}
+
+
+static int __devinit snd_atiixp_probe(struct pci_dev *pci,
+				      const struct pci_device_id *pci_id)
+{
+	struct snd_card *card;
+	struct atiixp_modem *chip;
+	int err;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	strcpy(card->driver, "ATIIXP-MODEM");
+	strcpy(card->shortname, "ATI IXP Modem");
+	if ((err = snd_atiixp_create(card, pci, &chip)) < 0)
+		goto __error;
+	card->private_data = chip;
+
+	if ((err = snd_atiixp_aclink_reset(chip)) < 0)
+		goto __error;
+
+	if ((err = snd_atiixp_mixer_new(chip, ac97_clock)) < 0)
+		goto __error;
+
+	if ((err = snd_atiixp_pcm_new(chip)) < 0)
+		goto __error;
+	
+	snd_atiixp_proc_init(chip);
+
+	snd_atiixp_chip_start(chip);
+
+	sprintf(card->longname, "%s rev %x at 0x%lx, irq %i",
+		card->shortname, pci->revision, chip->addr, chip->irq);
+
+	if ((err = snd_card_register(card)) < 0)
+		goto __error;
+
+	pci_set_drvdata(pci, card);
+	return 0;
+
+ __error:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit snd_atiixp_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "ATI IXP MC97 controller",
+	.id_table = snd_atiixp_ids,
+	.probe = snd_atiixp_probe,
+	.remove = __devexit_p(snd_atiixp_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_atiixp_suspend,
+	.resume = snd_atiixp_resume,
+#endif
+};
+
+
+static int __init alsa_card_atiixp_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_atiixp_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_atiixp_init)
+module_exit(alsa_card_atiixp_exit)
diff --git a/linux/sound/pci/au88x0/Makefile b/linux/sound/pci/au88x0/Makefile
new file mode 100644
index 0000000..d0a66bc
--- /dev/null
+++ b/linux/sound/pci/au88x0/Makefile
@@ -0,0 +1,7 @@
+snd-au8810-objs := au8810.o
+snd-au8820-objs := au8820.o
+snd-au8830-objs := au8830.o
+
+obj-$(CONFIG_SND_AU8810) += snd-au8810.o
+obj-$(CONFIG_SND_AU8820) += snd-au8820.o
+obj-$(CONFIG_SND_AU8830) += snd-au8830.o
diff --git a/linux/sound/pci/au88x0/au8810.c b/linux/sound/pci/au88x0/au8810.c
new file mode 100644
index 0000000..aa51cc7
--- /dev/null
+++ b/linux/sound/pci/au88x0/au8810.c
@@ -0,0 +1,16 @@
+#include "au8810.h"
+#include "au88x0.h"
+static DEFINE_PCI_DEVICE_TABLE(snd_vortex_ids) = {
+	{PCI_VDEVICE(AUREAL, PCI_DEVICE_ID_AUREAL_ADVANTAGE), 1,},
+	{0,}
+};
+
+#include "au88x0_core.c"
+#include "au88x0_pcm.c"
+#include "au88x0_mixer.c"
+#include "au88x0_mpu401.c"
+#include "au88x0_game.c"
+#include "au88x0_eq.c"
+#include "au88x0_a3d.c"
+#include "au88x0_xtalk.c"
+#include "au88x0.c"
diff --git a/linux/sound/pci/au88x0/au8810.h b/linux/sound/pci/au88x0/au8810.h
new file mode 100644
index 0000000..5d69c31
--- /dev/null
+++ b/linux/sound/pci/au88x0/au8810.h
@@ -0,0 +1,224 @@
+/*
+    Aureal Advantage Soundcard driver.
+ */
+
+#define CHIP_AU8810
+
+#define CARD_NAME "Aureal Advantage 3D Sound Processor"
+#define CARD_NAME_SHORT "au8810"
+
+#define NR_ADB		0x10
+#define NR_WT		0x00
+#define NR_SRC		0x10
+#define NR_A3D		0x10
+#define NR_MIXIN	0x20
+#define NR_MIXOUT	0x10
+
+
+/* ADBDMA */
+#define VORTEX_ADBDMA_STAT 0x27e00	/* read only, subbuffer, DMA pos */
+#define		POS_MASK 0x00000fff
+#define     POS_SHIFT 0x0
+#define 	ADB_SUBBUF_MASK 0x00003000	/* ADB only. */
+#define     ADB_SUBBUF_SHIFT 0xc	/* ADB only. */
+#define VORTEX_ADBDMA_CTRL 0x27180	/* write only; format, flags, DMA pos */
+#define		OFFSET_MASK 0x00000fff
+#define     OFFSET_SHIFT 0x0
+#define		IE_MASK 0x00001000	/* interrupt enable. */
+#define     IE_SHIFT 0xc
+#define     DIR_MASK 0x00002000	/* Direction */
+#define     DIR_SHIFT 0xd
+#define		FMT_MASK 0x0003c000
+#define		FMT_SHIFT 0xe
+// The ADB masks and shift also are valid for the wtdma, except if specified otherwise.
+#define VORTEX_ADBDMA_BUFCFG0 0x27100
+#define VORTEX_ADBDMA_BUFCFG1 0x27104
+#define VORTEX_ADBDMA_BUFBASE 0x27000
+#define VORTEX_ADBDMA_START 0x27c00	/* Which subbuffer starts */
+
+#define VORTEX_ADBDMA_STATUS 0x27A90	/* stored at AdbDma->this_10 / 2 DWORD in size. */
+
+/* WTDMA */
+#define VORTEX_WTDMA_CTRL 0x27fd8	/* format, DMA pos */
+#define VORTEX_WTDMA_STAT 0x27fe8	/* DMA subbuf, DMA pos */
+#define     WT_SUBBUF_MASK 0x3
+#define     WT_SUBBUF_SHIFT 0xc
+#define VORTEX_WTDMA_BUFBASE 0x27fc0
+#define VORTEX_WTDMA_BUFCFG0 0x27fd0
+#define VORTEX_WTDMA_BUFCFG1 0x27fd4
+#define VORTEX_WTDMA_START 0x27fe4	/* which subbuffer is first */
+
+/* ADB */
+#define VORTEX_ADB_SR 0x28400	/* Samplerates enable/disable */
+#define VORTEX_ADB_RTBASE 0x28000
+#define VORTEX_ADB_RTBASE_COUNT 173
+#define VORTEX_ADB_CHNBASE 0x282b4
+#define VORTEX_ADB_CHNBASE_COUNT 24
+#define 	ROUTE_MASK	0xffff
+#define		SOURCE_MASK	0xff00
+#define     ADB_MASK   0xff
+#define		ADB_SHIFT 0x8
+
+/* ADB address */
+#define		OFFSET_ADBDMA	0x00
+#define		OFFSET_SRCIN	0x40
+#define		OFFSET_SRCOUT	0x20
+#define		OFFSET_MIXIN	0x50
+#define		OFFSET_MIXOUT	0x30
+#define		OFFSET_CODECIN	0x70
+#define		OFFSET_CODECOUT	0x88
+#define		OFFSET_SPORTIN	0x78	/* ch 0x13 */
+#define		OFFSET_SPORTOUT	0x90
+#define		OFFSET_SPDIFOUT	0x92	/* ch 0x14 check this! */
+#define		OFFSET_EQIN	0xa0
+#define		OFFSET_EQOUT	0x7e	/* 2 routes on ch 0x11 */
+#define		OFFSET_XTALKOUT	0x66	/* crosstalk canceller (source) */
+#define		OFFSET_XTALKIN	0x96	/* crosstalk canceller (sink) */
+#define		OFFSET_A3DIN	0x70	/* ADB sink. */
+#define		OFFSET_A3DOUT	0xA6	/* ADB source. 2 routes per slice = 8 */
+#define		OFFSET_EFXIN	0x80	/* ADB sink. */
+#define		OFFSET_EFXOUT	0x68	/* ADB source. */
+
+/* ADB route translate helper */
+#define ADB_DMA(x) (x)
+#define ADB_SRCOUT(x) (x + OFFSET_SRCOUT)
+#define ADB_SRCIN(x) (x + OFFSET_SRCIN)
+#define ADB_MIXOUT(x) (x + OFFSET_MIXOUT)
+#define ADB_MIXIN(x) (x + OFFSET_MIXIN)
+#define ADB_CODECIN(x) (x + OFFSET_CODECIN)
+#define ADB_CODECOUT(x) (x + OFFSET_CODECOUT)
+#define ADB_SPORTIN(x) (x + OFFSET_SPORTIN)
+#define ADB_SPORTOUT(x) (x + OFFSET_SPORTOUT)
+#define ADB_SPDIFOUT(x)	(x + OFFSET_SPDIFOUT)
+#define ADB_EQIN(x) (x + OFFSET_EQIN)
+#define ADB_EQOUT(x) (x + OFFSET_EQOUT)
+#define ADB_A3DOUT(x) (x + OFFSET_A3DOUT)	/* 0x10 A3D blocks */
+#define ADB_A3DIN(x) (x + OFFSET_A3DIN)
+#define ADB_XTALKIN(x) (x + OFFSET_XTALKIN)
+#define ADB_XTALKOUT(x) (x + OFFSET_XTALKOUT)
+
+#define MIX_OUTL    0xe
+#define MIX_OUTR    0xf
+#define MIX_INL     0x1e
+#define MIX_INR     0x1f
+#define MIX_DEFIGAIN 0x08	/* 0x8 => 6dB */
+#define MIX_DEFOGAIN 0x08
+
+/* MIXER */
+#define VORTEX_MIXER_SR 0x21f00
+#define VORTEX_MIXER_CLIP 0x21f80
+#define VORTEX_MIXER_CHNBASE 0x21e40
+#define VORTEX_MIXER_RTBASE 0x21e00
+#define 	MIXER_RTBASE_SIZE 0x38
+#define VORTEX_MIX_ENIN 0x21a00	/* Input enable bits. 4 bits wide. */
+#define VORTEX_MIX_SMP 0x21c00	/* AU8820: 0x9c00 */
+
+/* MIX */
+#define VORTEX_MIX_INVOL_A 0x21000	/* in? */
+#define VORTEX_MIX_INVOL_B 0x20000	/* out? */
+#define VORTEX_MIX_VOL_A 0x21800
+#define VORTEX_MIX_VOL_B 0x20800
+
+#define 	VOL_MIN 0x80	/* Input volume when muted. */
+#define		VOL_MAX 0x7f	/* FIXME: Not confirmed! Just guessed. */
+
+/* SRC */
+#define VORTEX_SRC_CHNBASE		0x26c40
+#define VORTEX_SRC_RTBASE		0x26c00
+#define VORTEX_SRCBLOCK_SR		0x26cc0
+#define VORTEX_SRC_SOURCE		0x26cc4
+#define VORTEX_SRC_SOURCESIZE	0x26cc8
+/* Params
+	0x26e00	: 1 U0
+	0x26e40	: 2 CR
+	0x26e80	: 3 U3
+	0x26ec0	: 4 DRIFT1
+	0x26f00 : 5 U1
+	0x26f40	: 6 DRIFT2
+	0x26f80	: 7 U2 : Target rate, direction
+*/
+
+#define VORTEX_SRC_CONVRATIO	0x26e40
+#define VORTEX_SRC_DRIFT0		0x26e80
+#define VORTEX_SRC_DRIFT1		0x26ec0
+#define VORTEX_SRC_DRIFT2		0x26f40
+#define VORTEX_SRC_U0			0x26e00
+#define		U0_SLOWLOCK		0x200
+#define VORTEX_SRC_U1			0x26f00
+#define VORTEX_SRC_U2			0x26f80
+#define VORTEX_SRC_DATA			0x26800	/* 0xc800 */
+#define VORTEX_SRC_DATA0		0x26000
+
+/* FIFO */
+#define VORTEX_FIFO_ADBCTRL 0x16100	/* Control bits. */
+#define VORTEX_FIFO_WTCTRL 0x16000
+#define		FIFO_RDONLY	0x00000001
+#define		FIFO_CTRL	0x00000002	/* Allow ctrl. ? */
+#define		FIFO_VALID	0x00000010
+#define 	FIFO_EMPTY	0x00000020
+#define		FIFO_U0		0x00001000	/* Unknown. */
+#define		FIFO_U1		0x00010000
+#define		FIFO_SIZE_BITS 5
+#define		FIFO_SIZE	(1<<FIFO_SIZE_BITS)	// 0x20
+#define 	FIFO_MASK	(FIFO_SIZE-1)	//0x1f    /* at shift left 0xc */
+//#define       FIFO_MASK       0x1f    /* at shift left 0xb */
+//#define               FIFO_SIZE       0x20
+#define 	FIFO_BITS	0x03880000
+#define VORTEX_FIFO_ADBDATA	0x14000
+#define VORTEX_FIFO_WTDATA	0x10000
+
+/* CODEC */
+#define VORTEX_CODEC_CTRL	0x29184
+#define VORTEX_CODEC_EN		0x29190
+#define		EN_CODEC0	0x00000300
+#define 	EN_AC98		0x00000c00 /* Modem AC98 slots. */
+#define		EN_CODEC1	0x00003000
+#define		EN_CODEC	(EN_CODEC0 | EN_CODEC1)
+#define		EN_SPORT	0x00030000
+#define		EN_SPDIF	0x000c0000
+
+#define VORTEX_CODEC_CHN 	0x29080
+#define VORTEX_CODEC_IO		0x29188
+
+/* SPDIF */
+#define VORTEX_SPDIF_FLAGS	0x2205c
+#define VORTEX_SPDIF_CFG0	0x291D0
+#define VORTEX_SPDIF_CFG1	0x291D4
+#define VORTEX_SPDIF_SMPRATE	0x29194
+
+/* Sample timer */
+#define VORTEX_SMP_TIME		0x29198
+
+#define VORTEX_MODEM_CTRL	0x291ac
+
+/* IRQ */
+#define VORTEX_IRQ_SOURCE 0x2a000	/* Interrupt source flags. */
+#define VORTEX_IRQ_CTRL 0x2a004	/* Interrupt source mask. */
+
+#define VORTEX_STAT	0x2a008	/* Status */
+
+#define VORTEX_CTRL		0x2a00c
+#define 	CTRL_MIDI_EN	0x00000001
+#define 	CTRL_MIDI_PORT	0x00000060
+#define 	CTRL_GAME_EN	0x00000008
+#define 	CTRL_GAME_PORT	0x00000e00
+//#define       CTRL_IRQ_ENABLE 0x01004000
+#define 	CTRL_IRQ_ENABLE	0x00004000
+
+/* write: Timer period config / read: TIMER IRQ ack. */
+#define VORTEX_IRQ_STAT		0x2919c
+
+/* DMA */
+#define VORTEX_ENGINE_CTRL	0x27ae8
+#define 	ENGINE_INIT	0x1380000
+
+/* MIDI *//* GAME. */
+#define VORTEX_MIDI_DATA	0x28800
+#define VORTEX_MIDI_CMD		0x28804	/* Write command / Read status */
+
+#define VORTEX_CTRL2		0x2880c
+#define		CTRL2_GAME_ADCMODE 0x40
+#define VORTEX_GAME_LEGACY	0x28808
+#define VORTEX_GAME_AXIS	0x28810
+#define		AXIS_SIZE 4
+#define		AXIS_RANGE 0x1fff
diff --git a/linux/sound/pci/au88x0/au8820.c b/linux/sound/pci/au88x0/au8820.c
new file mode 100644
index 0000000..2f321e7
--- /dev/null
+++ b/linux/sound/pci/au88x0/au8820.c
@@ -0,0 +1,14 @@
+#include "au8820.h"
+#include "au88x0.h"
+static DEFINE_PCI_DEVICE_TABLE(snd_vortex_ids) = {
+	{PCI_VDEVICE(AUREAL, PCI_DEVICE_ID_AUREAL_VORTEX_1), 0,},
+	{0,}
+};
+
+#include "au88x0_synth.c"
+#include "au88x0_core.c"
+#include "au88x0_pcm.c"
+#include "au88x0_mpu401.c"
+#include "au88x0_game.c"
+#include "au88x0_mixer.c"
+#include "au88x0.c"
diff --git a/linux/sound/pci/au88x0/au8820.h b/linux/sound/pci/au88x0/au8820.h
new file mode 100644
index 0000000..abbe85e
--- /dev/null
+++ b/linux/sound/pci/au88x0/au8820.h
@@ -0,0 +1,204 @@
+/*
+    Aureal Vortex Soundcard driver.
+
+    IO addr collected from asp4core.vxd:
+    function    address
+    0005D5A0    13004
+    00080674    14004
+    00080AFF    12818
+
+ */
+
+#define CHIP_AU8820
+
+#define CARD_NAME "Aureal Vortex 3D Sound Processor"
+#define CARD_NAME_SHORT "au8820"
+
+/* Number of ADB and WT channels */
+#define NR_ADB		0x10
+#define NR_WT		0x20
+#define NR_SRC		0x10
+#define NR_A3D		0x00
+#define NR_MIXIN	0x10
+#define NR_MIXOUT 	0x10
+
+
+/* ADBDMA */
+#define VORTEX_ADBDMA_STAT 0x105c0	/* read only, subbuffer, DMA pos */
+#define		POS_MASK 0x00000fff
+#define     POS_SHIFT 0x0
+#define 	ADB_SUBBUF_MASK 0x00003000	/* ADB only. */
+#define     ADB_SUBBUF_SHIFT 0xc	/* ADB only. */
+#define VORTEX_ADBDMA_CTRL 0x10580	/* write only, format, flags, DMA pos */
+#define		OFFSET_MASK 0x00000fff
+#define     OFFSET_SHIFT 0x0
+#define		IE_MASK 0x00001000	/* interrupt enable. */
+#define     IE_SHIFT 0xc
+#define     DIR_MASK 0x00002000	/* Direction. */
+#define     DIR_SHIFT 0xd
+#define		FMT_MASK 0x0003c000
+#define		FMT_SHIFT 0xe
+// The masks and shift also work for the wtdma, if not specified otherwise.
+#define VORTEX_ADBDMA_BUFCFG0 0x10400
+#define VORTEX_ADBDMA_BUFCFG1 0x10404
+#define VORTEX_ADBDMA_BUFBASE 0x10200
+#define VORTEX_ADBDMA_START 0x106c0	/* Which subbuffer starts */
+#define VORTEX_ADBDMA_STATUS 0x10600	/* stored at AdbDma->this_10 / 2 DWORD in size. */
+
+/* ADB */
+#define VORTEX_ADB_SR 0x10a00	/* Samplerates enable/disable */
+#define VORTEX_ADB_RTBASE 0x10800
+#define VORTEX_ADB_RTBASE_COUNT 103
+#define VORTEX_ADB_CHNBASE 0x1099c
+#define VORTEX_ADB_CHNBASE_COUNT 22
+#define 	ROUTE_MASK	0x3fff
+#define     ADB_MASK   0x7f
+#define		ADB_SHIFT 0x7
+//#define     ADB_MIX_MASK 0xf
+/* ADB address */
+#define		OFFSET_ADBDMA	0x00
+#define		OFFSET_SRCOUT	0x10	/* on channel 0x11 */
+#define		OFFSET_SRCIN	0x10	/* on channel < 0x11 */
+#define		OFFSET_MIXOUT	0x20	/* source */
+#define		OFFSET_MIXIN	0x30	/* sink */
+#define		OFFSET_CODECIN	0x48	/* ADB source */
+#define		OFFSET_CODECOUT	0x58	/* ADB sink/target */
+#define		OFFSET_SPORTOUT	0x60	/* sink */
+#define		OFFSET_SPORTIN	0x50	/* source */
+#define		OFFSET_EFXOUT	0x50	/* sink */
+#define		OFFSET_EFXIN	0x40	/* source */
+#define		OFFSET_A3DOUT	0x00	/* This card has no HRTF :( */
+#define		OFFSET_A3DIN	0x00
+#define		OFFSET_WTOUT	0x58	/*  */
+
+/* ADB route translate helper */
+#define ADB_DMA(x) (x + OFFSET_ADBDMA)
+#define ADB_SRCOUT(x) (x + OFFSET_SRCOUT)
+#define ADB_SRCIN(x) (x + OFFSET_SRCIN)
+#define ADB_MIXOUT(x) (x + OFFSET_MIXOUT)
+#define ADB_MIXIN(x) (x + OFFSET_MIXIN)
+#define ADB_CODECIN(x) (x + OFFSET_CODECIN)
+#define ADB_CODECOUT(x) (x + OFFSET_CODECOUT)
+#define ADB_SPORTOUT(x) (x + OFFSET_SPORTOUT)
+#define ADB_SPORTIN(x) (x + OFFSET_SPORTIN)	/*  */
+#define ADB_A3DOUT(x) (x + OFFSET_A3DOUT)	/* 8 A3D blocks */
+#define ADB_A3DIN(x) (x + OFFSET_A3DIN)
+#define ADB_WTOUT(x,y) (y + OFFSET_WTOUT)
+
+/* WTDMA */
+#define VORTEX_WTDMA_CTRL 0x10500	/* format, DMA pos */
+#define VORTEX_WTDMA_STAT 0x10500	/* DMA subbuf, DMA pos */
+#define     WT_SUBBUF_MASK (0x3 << WT_SUBBUF_SHIFT)
+#define     WT_SUBBUF_SHIFT 0x15
+#define VORTEX_WTDMA_BUFBASE 0x10000
+#define VORTEX_WTDMA_BUFCFG0 0x10300
+#define VORTEX_WTDMA_BUFCFG1 0x10304
+#define VORTEX_WTDMA_START 0x10640	/* which subbuffer is first */
+
+#define VORTEX_WT_BASE 0x9000
+
+/* MIXER */
+#define VORTEX_MIXER_SR 0x9f00
+#define VORTEX_MIXER_CLIP 0x9f80
+#define VORTEX_MIXER_CHNBASE 0x9e40
+#define VORTEX_MIXER_RTBASE 0x9e00
+#define 	MIXER_RTBASE_SIZE 0x26
+#define VORTEX_MIX_ENIN 0x9a00	/* Input enable bits. 4 bits wide. */
+#define VORTEX_MIX_SMP 0x9c00
+
+/* MIX */
+#define VORTEX_MIX_INVOL_A 0x9000	/* in? */
+#define VORTEX_MIX_INVOL_B 0x8000	/* out? */
+#define VORTEX_MIX_VOL_A 0x9800
+#define VORTEX_MIX_VOL_B 0x8800
+
+#define 	VOL_MIN 0x80	/* Input volume when muted. */
+#define		VOL_MAX 0x7f	/* FIXME: Not confirmed! Just guessed. */
+
+//#define MIX_OUTL    0xe
+//#define MIX_OUTR    0xf
+//#define MIX_INL     0xe
+//#define MIX_INR     0xf
+#define MIX_DEFIGAIN 0x08	/* 0x8 => 6dB */
+#define MIX_DEFOGAIN 0x08
+
+/* SRC */
+#define VORTEX_SRCBLOCK_SR	0xccc0
+#define VORTEX_SRC_CHNBASE	0xcc40
+#define VORTEX_SRC_RTBASE	0xcc00
+#define VORTEX_SRC_SOURCE	0xccc4
+#define VORTEX_SRC_SOURCESIZE 0xccc8
+#define VORTEX_SRC_U0		0xce00
+#define VORTEX_SRC_DRIFT0	0xce80
+#define VORTEX_SRC_DRIFT1	0xcec0
+#define VORTEX_SRC_U1		0xcf00
+#define VORTEX_SRC_DRIFT2	0xcf40
+#define VORTEX_SRC_U2		0xcf80
+#define VORTEX_SRC_DATA		0xc800
+#define VORTEX_SRC_DATA0	0xc000
+#define VORTEX_SRC_CONVRATIO 0xce40
+//#define     SRC_RATIO(x) ((((x<<15)/48000) + 1)/2) /* Playback */
+//#define     SRC_RATIO2(x) ((((48000<<15)/x) + 1)/2) /* Recording */
+
+/* FIFO */
+#define VORTEX_FIFO_ADBCTRL 0xf800	/* Control bits. */
+#define VORTEX_FIFO_WTCTRL 0xf840
+#define		FIFO_RDONLY	0x00000001
+#define		FIFO_CTRL	0x00000002	/* Allow ctrl. ? */
+#define		FIFO_VALID	0x00000010
+#define 	FIFO_EMPTY	0x00000020
+#define		FIFO_U0		0x00001000	/* Unknown. */
+#define		FIFO_U1		0x00010000
+#define		FIFO_SIZE_BITS 5
+#define		FIFO_SIZE	(1<<FIFO_SIZE_BITS)	// 0x20
+#define 	FIFO_MASK	(FIFO_SIZE-1)	//0x1f    /* at shift left 0xc */
+#define VORTEX_FIFO_ADBDATA 0xe000
+#define VORTEX_FIFO_WTDATA 0xe800
+
+/* CODEC */
+#define VORTEX_CODEC_CTRL 0x11984
+#define VORTEX_CODEC_EN 0x11990
+#define		EN_CODEC	0x00000300
+#define		EN_SPORT	0x00030000
+#define		EN_SPDIF	0x000c0000
+#define VORTEX_CODEC_CHN 0x11880
+#define VORTEX_CODEC_IO 0x11988
+
+#define VORTEX_SPDIF_FLAGS		0x1005c	/* FIXME */
+#define VORTEX_SPDIF_CFG0		0x119D0
+#define VORTEX_SPDIF_CFG1		0x119D4
+#define VORTEX_SPDIF_SMPRATE	0x11994
+
+/* Sample timer */
+#define VORTEX_SMP_TIME 0x11998
+
+/* IRQ */
+#define VORTEX_IRQ_SOURCE 0x12800	/* Interrupt source flags. */
+#define VORTEX_IRQ_CTRL 0x12804	/* Interrupt source mask. */
+
+#define VORTEX_STAT		0x12808	/* ?? */
+
+#define VORTEX_CTRL 0x1280c
+#define 	CTRL_MIDI_EN 0x00000001
+#define 	CTRL_MIDI_PORT 0x00000060
+#define 	CTRL_GAME_EN 0x00000008
+#define 	CTRL_GAME_PORT 0x00000e00
+#define 	CTRL_IRQ_ENABLE 0x4000
+
+/* write: Timer period config / read: TIMER IRQ ack. */
+#define VORTEX_IRQ_STAT 0x1199c
+
+/* DMA */
+#define VORTEX_DMA_BUFFER 0x10200
+#define VORTEX_ENGINE_CTRL 0x1060c
+#define 	ENGINE_INIT 0x0L
+
+		     /* MIDI *//* GAME. */
+#define VORTEX_MIDI_DATA 0x11000
+#define VORTEX_MIDI_CMD 0x11004	/* Write command / Read status */
+#define VORTEX_GAME_LEGACY 0x11008
+#define VORTEX_CTRL2 0x1100c
+#define 	CTRL2_GAME_ADCMODE 0x40
+#define VORTEX_GAME_AXIS 0x11010
+#define 	AXIS_SIZE 4
+#define		AXIS_RANGE 0x1fff
diff --git a/linux/sound/pci/au88x0/au8830.c b/linux/sound/pci/au88x0/au8830.c
new file mode 100644
index 0000000..279b78f
--- /dev/null
+++ b/linux/sound/pci/au88x0/au8830.c
@@ -0,0 +1,17 @@
+#include "au8830.h"
+#include "au88x0.h"
+static DEFINE_PCI_DEVICE_TABLE(snd_vortex_ids) = {
+	{PCI_VDEVICE(AUREAL, PCI_DEVICE_ID_AUREAL_VORTEX_2), 0,},
+	{0,}
+};
+
+#include "au88x0_synth.c"
+#include "au88x0_core.c"
+#include "au88x0_pcm.c"
+#include "au88x0_mixer.c"
+#include "au88x0_mpu401.c"
+#include "au88x0_game.c"
+#include "au88x0_eq.c"
+#include "au88x0_a3d.c"
+#include "au88x0_xtalk.c"
+#include "au88x0.c"
diff --git a/linux/sound/pci/au88x0/au8830.h b/linux/sound/pci/au88x0/au8830.h
new file mode 100644
index 0000000..04ece1b
--- /dev/null
+++ b/linux/sound/pci/au88x0/au8830.h
@@ -0,0 +1,251 @@
+/*
+    Aureal Vortex Soundcard driver.
+
+    IO addr collected from asp4core.vxd:
+    function    address
+    0005D5A0    13004
+    00080674    14004
+    00080AFF    12818
+
+ */
+
+#define CHIP_AU8830
+
+#define CARD_NAME "Aureal Vortex 2 3D Sound Processor"
+#define CARD_NAME_SHORT "au8830"
+
+#define NR_ADB 0x20
+#define NR_SRC 0x10
+#define NR_A3D 0x10
+#define NR_MIXIN 0x20
+#define NR_MIXOUT 0x10
+#define NR_WT 0x40
+
+/* ADBDMA */
+#define VORTEX_ADBDMA_STAT 0x27e00	/* read only, subbuffer, DMA pos */
+#define		POS_MASK 0x00000fff
+#define     POS_SHIFT 0x0
+#define 	ADB_SUBBUF_MASK 0x00003000	/* ADB only. */
+#define     ADB_SUBBUF_SHIFT 0xc	/* ADB only. */
+#define VORTEX_ADBDMA_CTRL 0x27a00	/* write only; format, flags, DMA pos */
+#define		OFFSET_MASK 0x00000fff
+#define     OFFSET_SHIFT 0x0
+#define		IE_MASK 0x00001000	/* interrupt enable. */
+#define     IE_SHIFT 0xc
+#define     DIR_MASK 0x00002000	/* Direction. */
+#define     DIR_SHIFT 0xd
+#define		FMT_MASK 0x0003c000
+#define		FMT_SHIFT 0xe
+#define		ADB_FIFO_EN_SHIFT	0x15
+#define		ADB_FIFO_EN			(1 << 0x15)
+// The ADB masks and shift also are valid for the wtdma, except if specified otherwise.
+#define VORTEX_ADBDMA_BUFCFG0 0x27800
+#define VORTEX_ADBDMA_BUFCFG1 0x27804
+#define VORTEX_ADBDMA_BUFBASE 0x27400
+#define VORTEX_ADBDMA_START 0x27c00	/* Which subbuffer starts */
+
+#define VORTEX_ADBDMA_STATUS 0x27A90	/* stored at AdbDma->this_10 / 2 DWORD in size. */
+/* Starting at the MSB, each pair of bits seem to be the current DMA page. */
+/* This current page bits are consistent (same value) with VORTEX_ADBDMA_STAT) */
+
+/* DMA */
+#define VORTEX_ENGINE_CTRL 0x27ae8
+#define 	ENGINE_INIT 0x1380000
+
+/* WTDMA */
+#define VORTEX_WTDMA_CTRL 0x27900	/* format, DMA pos */
+#define VORTEX_WTDMA_STAT 0x27d00	/* DMA subbuf, DMA pos */
+#define     WT_SUBBUF_MASK 0x3
+#define     WT_SUBBUF_SHIFT 0xc
+#define VORTEX_WTDMA_BUFBASE 0x27000
+#define VORTEX_WTDMA_BUFCFG0 0x27600
+#define VORTEX_WTDMA_BUFCFG1 0x27604
+#define VORTEX_WTDMA_START 0x27b00	/* which subbuffer is first */
+
+/* ADB */
+#define VORTEX_ADB_SR 0x28400	/* Samplerates enable/disable */
+#define VORTEX_ADB_RTBASE 0x28000
+#define VORTEX_ADB_RTBASE_COUNT 173
+#define VORTEX_ADB_CHNBASE 0x282b4
+#define VORTEX_ADB_CHNBASE_COUNT 24
+#define 	ROUTE_MASK	0xffff
+#define		SOURCE_MASK	0xff00
+#define     ADB_MASK   0xff
+#define		ADB_SHIFT 0x8
+/* ADB address */
+#define		OFFSET_ADBDMA	0x00
+#define		OFFSET_ADBDMAB	0x20
+#define		OFFSET_SRCIN	0x40
+#define		OFFSET_SRCOUT	0x20	/* ch 0x11 */
+#define		OFFSET_MIXIN	0x50	/* ch 0x11 */
+#define		OFFSET_MIXOUT	0x30	/* ch 0x11 */
+#define		OFFSET_CODECIN	0x70 /* ch 0x11 */	/* adb source */
+#define		OFFSET_CODECOUT	0x88 /* ch 0x11 */	/* adb target */
+#define		OFFSET_SPORTIN	0x78	/* ch 0x13 ADB source. 2 routes. */
+#define		OFFSET_SPORTOUT	0x90	/* ch 0x13 ADB sink. 2 routes. */
+#define		OFFSET_SPDIFIN	0x7A	/* ch 0x14 ADB source. */
+#define		OFFSET_SPDIFOUT	0x92	/* ch 0x14 ADB sink. */
+#define		OFFSET_AC98IN	0x7c	/* ch 0x14 ADB source. */
+#define		OFFSET_AC98OUT	0x94	/* ch 0x14 ADB sink. */
+#define		OFFSET_EQIN		0xa0	/* ch 0x11 */
+#define		OFFSET_EQOUT	0x7e /* ch 0x11 */	/* 2 routes on ch 0x11 */
+#define		OFFSET_A3DIN	0x70	/* ADB sink. */
+#define		OFFSET_A3DOUT	0xA6	/* ADB source. 2 routes per slice = 8 */
+#define		OFFSET_WT0		0x40	/* WT bank 0 output. 0x40 - 0x65 */
+#define		OFFSET_WT1		0x80	/* WT bank 1 output. 0x80 - 0xA5 */
+/* WT sources offset : 0x00-0x1f Direct stream. */
+/* WT sources offset : 0x20-0x25 Mixed Output. */
+#define		OFFSET_XTALKOUT	0x66	/* crosstalk canceller (source) 2 routes */
+#define		OFFSET_XTALKIN	0x96	/* crosstalk canceller (sink). 10 routes */
+#define		OFFSET_EFXOUT	0x68	/* ADB source. 8 routes. */
+#define		OFFSET_EFXIN	0x80	/* ADB sink. 8 routes. */
+
+/* ADB route translate helper */
+#define ADB_DMA(x) (x)
+#define ADB_SRCOUT(x) (x + OFFSET_SRCOUT)
+#define ADB_SRCIN(x) (x + OFFSET_SRCIN)
+#define ADB_MIXOUT(x) (x + OFFSET_MIXOUT)
+#define ADB_MIXIN(x) (x + OFFSET_MIXIN)
+#define ADB_CODECIN(x) (x + OFFSET_CODECIN)
+#define ADB_CODECOUT(x) (x + OFFSET_CODECOUT)
+#define ADB_SPORTIN(x) (x + OFFSET_SPORTIN)
+#define ADB_SPORTOUT(x) (x + OFFSET_SPORTOUT)
+#define ADB_SPDIFIN(x)	(x + OFFSET_SPDIFIN)
+#define ADB_SPDIFOUT(x)	(x + OFFSET_SPDIFOUT)
+#define ADB_EQIN(x) (x + OFFSET_EQIN)
+#define ADB_EQOUT(x) (x + OFFSET_EQOUT)
+#define ADB_A3DOUT(x) (x + OFFSET_A3DOUT)	/* 0x10 A3D blocks */
+#define ADB_A3DIN(x) (x + OFFSET_A3DIN)
+//#define ADB_WTOUT(x) ((x<x20)?(x + OFFSET_WT0):(x + OFFSET_WT1))
+#define ADB_WTOUT(x,y) (((x)==0)?((y) + OFFSET_WT0):((y) + OFFSET_WT1))
+#define ADB_XTALKIN(x) ((x) + OFFSET_XTALKIN)
+#define ADB_XTALKOUT(x) ((x) + OFFSET_XTALKOUT)
+
+#define MIX_DEFIGAIN 0x08
+#define MIX_DEFOGAIN 0x08	/* 0x8->6dB  (6dB = x4) 16 to 18 bit conversion? */
+
+/* MIXER */
+#define VORTEX_MIXER_SR 0x21f00
+#define VORTEX_MIXER_CLIP 0x21f80
+#define VORTEX_MIXER_CHNBASE 0x21e40
+#define VORTEX_MIXER_RTBASE 0x21e00
+#define 	MIXER_RTBASE_SIZE 0x38
+#define VORTEX_MIX_ENIN 0x21a00	/* Input enable bits. 4 bits wide. */
+#define VORTEX_MIX_SMP 0x21c00	/* wave data buffers. AU8820: 0x9c00 */
+
+/* MIX */
+#define VORTEX_MIX_INVOL_B 0x20000	/* Input volume current */
+#define VORTEX_MIX_VOL_B 0x20800	/* Output Volume current */
+#define VORTEX_MIX_INVOL_A 0x21000	/* Input Volume target */
+#define VORTEX_MIX_VOL_A 0x21800	/* Output Volume target */
+
+#define 	VOL_MIN 0x80	/* Input volume when muted. */
+#define		VOL_MAX 0x7f	/* FIXME: Not confirmed! Just guessed. */
+
+/* SRC */
+#define VORTEX_SRC_CHNBASE		0x26c40
+#define VORTEX_SRC_RTBASE		0x26c00
+#define VORTEX_SRCBLOCK_SR		0x26cc0
+#define VORTEX_SRC_SOURCE		0x26cc4
+#define VORTEX_SRC_SOURCESIZE	0x26cc8
+/* Params
+	0x26e00	: 1 U0
+	0x26e40	: 2 CR
+	0x26e80	: 3 U3
+	0x26ec0	: 4 DRIFT1
+	0x26f00 : 5 U1
+	0x26f40	: 6 DRIFT2
+	0x26f80	: 7 U2 : Target rate, direction
+*/
+
+#define VORTEX_SRC_CONVRATIO	0x26e40
+#define VORTEX_SRC_DRIFT0		0x26e80
+#define VORTEX_SRC_DRIFT1		0x26ec0
+#define VORTEX_SRC_DRIFT2		0x26f40
+#define VORTEX_SRC_U0			0x26e00
+#define		U0_SLOWLOCK		0x200
+#define VORTEX_SRC_U1			0x26f00
+#define VORTEX_SRC_U2			0x26f80
+#define VORTEX_SRC_DATA			0x26800	/* 0xc800 */
+#define VORTEX_SRC_DATA0		0x26000
+
+/* FIFO */
+#define VORTEX_FIFO_ADBCTRL 0x16100	/* Control bits. */
+#define VORTEX_FIFO_WTCTRL 0x16000
+#define		FIFO_RDONLY	0x00000001
+#define		FIFO_CTRL	0x00000002	/* Allow ctrl. ? */
+#define		FIFO_VALID	0x00000010
+#define 	FIFO_EMPTY	0x00000020
+#define		FIFO_U0		0x00002000	/* Unknown. */
+#define		FIFO_U1		0x00040000
+#define		FIFO_SIZE_BITS 6
+#define		FIFO_SIZE	(1<<(FIFO_SIZE_BITS))	// 0x40
+#define 	FIFO_MASK	(FIFO_SIZE-1)	//0x3f    /* at shift left 0xc */
+#define 	FIFO_BITS	0x1c400000
+#define VORTEX_FIFO_ADBDATA 0x14000
+#define VORTEX_FIFO_WTDATA 0x10000
+
+#define VORTEX_FIFO_GIRT	0x17000	/* wt0, wt1, adb */
+#define		GIRT_COUNT	3
+
+/* CODEC */
+
+#define VORTEX_CODEC_CHN 0x29080	/* The name "CHN" is wrong. */
+
+#define VORTEX_CODEC_CTRL 0x29184
+#define VORTEX_CODEC_IO 0x29188
+
+#define VORTEX_CODEC_SPORTCTRL 0x2918c
+
+#define VORTEX_CODEC_EN 0x29190
+#define		EN_AUDIO0		0x00000300
+#define		EN_MODEM		0x00000c00
+#define		EN_AUDIO1		0x00003000
+#define		EN_SPORT		0x00030000
+#define		EN_SPDIF		0x000c0000
+#define		EN_CODEC		(EN_AUDIO1 | EN_AUDIO0)
+
+#define VORTEX_SPDIF_SMPRATE	0x29194
+
+#define VORTEX_SPDIF_FLAGS		0x2205c
+#define VORTEX_SPDIF_CFG0		0x291D0	/* status data */
+#define VORTEX_SPDIF_CFG1		0x291D4
+
+#define VORTEX_SMP_TIME			0x29198	/* Sample counter/timer */
+#define VORTEX_SMP_TIMER		0x2919c
+#define VORTEX_CODEC2_CTRL		0x291a0
+
+#define VORTEX_MODEM_CTRL		0x291ac
+
+/* IRQ */
+#define VORTEX_IRQ_SOURCE 0x2a000	/* Interrupt source flags. */
+#define VORTEX_IRQ_CTRL 0x2a004	/* Interrupt source mask. */
+
+//#define VORTEX_IRQ_U0 0x2a008 /* ?? */
+#define VORTEX_STAT		0x2a008	/* Some sort of status */
+#define 	STAT_IRQ	0x00000001	/* This bitis set if the IRQ is valid. */
+
+#define VORTEX_CTRL		0x2a00c
+#define 	CTRL_MIDI_EN	0x00000001
+#define 	CTRL_MIDI_PORT	0x00000060
+#define 	CTRL_GAME_EN	0x00000008
+#define 	CTRL_GAME_PORT	0x00000e00
+#define 	CTRL_IRQ_ENABLE	0x00004000
+#define		CTRL_SPDIF		0x00000000	/* unknown. Please find this value */
+#define 	CTRL_SPORT		0x00200000
+#define 	CTRL_RST		0x00800000
+#define 	CTRL_UNKNOWN	0x01000000
+
+/* write: Timer period config / read: TIMER IRQ ack. */
+#define VORTEX_IRQ_STAT 0x2919c
+
+		     /* MIDI *//* GAME. */
+#define VORTEX_MIDI_DATA 0x28800
+#define VORTEX_MIDI_CMD 0x28804	/* Write command / Read status */
+
+#define VORTEX_GAME_LEGACY 0x28808
+#define VORTEX_CTRL2 0x2880c
+#define		CTRL2_GAME_ADCMODE 0x40
+#define VORTEX_GAME_AXIS 0x28810	/* Axis base register. 4 axis's */
+#define		AXIS_SIZE 4
+#define		AXIS_RANGE 0x1fff
diff --git a/linux/sound/pci/au88x0/au88x0.c b/linux/sound/pci/au88x0/au88x0.c
new file mode 100644
index 0000000..7b72c88
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0.c
@@ -0,0 +1,397 @@
+/*
+ * ALSA driver for the Aureal Vortex family of soundprocessors.
+ * Author: Manuel Jander (mjander@embedded.cl)
+ *
+ *   This driver is the result of the OpenVortex Project from Savannah
+ * (savannah.nongnu.org/projects/openvortex). I would like to thank
+ * the developers of OpenVortex, Jeff Muizelaar and Kester Maddock, from
+ * whom i got plenty of help, and their codebase was invaluable.
+ *   Thanks to the ALSA developers, they helped a lot working out
+ * the ALSA part.
+ *   Thanks also to Sourceforge for maintaining the old binary drivers,
+ * and the forum, where developers could comunicate.
+ *
+ * Now at least i can play Legacy DOOM with MIDI music :-)
+ */
+
+#include "au88x0.h"
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <linux/dma-mapping.h>
+#include <sound/initval.h>
+
+// module parameters (see "Module Parameters")
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+static int pcifix[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 255 };
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+module_param_array(pcifix, int, NULL, 0444);
+MODULE_PARM_DESC(pcifix, "Enable VIA-workaround for " CARD_NAME " soundcard.");
+
+MODULE_DESCRIPTION("Aureal vortex");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Aureal Semiconductor Inc., Aureal Vortex Sound Processor}}");
+
+MODULE_DEVICE_TABLE(pci, snd_vortex_ids);
+
+static void vortex_fix_latency(struct pci_dev *vortex)
+{
+	int rc;
+	if (!(rc = pci_write_config_byte(vortex, 0x40, 0xff))) {
+			printk(KERN_INFO CARD_NAME
+			       ": vortex latency is 0xff\n");
+	} else {
+		printk(KERN_WARNING CARD_NAME
+				": could not set vortex latency: pci error 0x%x\n", rc);
+	}
+}
+
+static void vortex_fix_agp_bridge(struct pci_dev *via)
+{
+	int rc;
+	u8 value;
+
+	/*
+	 * only set the bit (Extend PCI#2 Internal Master for
+	 * Efficient Handling of Dummy Requests) if the can
+	 * read the config and it is not already set
+	 */
+
+	if (!(rc = pci_read_config_byte(via, 0x42, &value))
+			&& ((value & 0x10)
+				|| !(rc = pci_write_config_byte(via, 0x42, value | 0x10)))) {
+		printk(KERN_INFO CARD_NAME
+				": bridge config is 0x%x\n", value | 0x10);
+	} else {
+		printk(KERN_WARNING CARD_NAME
+				": could not set vortex latency: pci error 0x%x\n", rc);
+	}
+}
+
+static void __devinit snd_vortex_workaround(struct pci_dev *vortex, int fix)
+{
+	struct pci_dev *via = NULL;
+
+	/* autodetect if workarounds are required */
+	if (fix == 255) {
+		/* VIA KT133 */
+		via = pci_get_device(PCI_VENDOR_ID_VIA,
+			PCI_DEVICE_ID_VIA_8365_1, NULL);
+		/* VIA Apollo */
+		if (via == NULL) {
+			via = pci_get_device(PCI_VENDOR_ID_VIA,
+				PCI_DEVICE_ID_VIA_82C598_1, NULL);
+			/* AMD Irongate */
+			if (via == NULL)
+				via = pci_get_device(PCI_VENDOR_ID_AMD,
+					PCI_DEVICE_ID_AMD_FE_GATE_7007, NULL);
+		}
+		if (via) {
+			printk(KERN_INFO CARD_NAME ": Activating latency workaround...\n");
+			vortex_fix_latency(vortex);
+			vortex_fix_agp_bridge(via);
+		}
+	} else {
+		if (fix & 0x1)
+			vortex_fix_latency(vortex);
+		if ((fix & 0x2) && (via = pci_get_device(PCI_VENDOR_ID_VIA,
+				PCI_DEVICE_ID_VIA_8365_1, NULL)))
+			vortex_fix_agp_bridge(via);
+		if ((fix & 0x4) && (via = pci_get_device(PCI_VENDOR_ID_VIA,
+				PCI_DEVICE_ID_VIA_82C598_1, NULL)))
+			vortex_fix_agp_bridge(via);
+		if ((fix & 0x8) && (via = pci_get_device(PCI_VENDOR_ID_AMD,
+				PCI_DEVICE_ID_AMD_FE_GATE_7007, NULL)))
+			vortex_fix_agp_bridge(via);
+	}
+	pci_dev_put(via);
+}
+
+// component-destructor
+// (see "Management of Cards and Components")
+static int snd_vortex_dev_free(struct snd_device *device)
+{
+	vortex_t *vortex = device->device_data;
+
+	vortex_gameport_unregister(vortex);
+	vortex_core_shutdown(vortex);
+	// Take down PCI interface.
+	free_irq(vortex->irq, vortex);
+	iounmap(vortex->mmio);
+	pci_release_regions(vortex->pci_dev);
+	pci_disable_device(vortex->pci_dev);
+	kfree(vortex);
+
+	return 0;
+}
+
+// chip-specific constructor
+// (see "Management of Cards and Components")
+static int __devinit
+snd_vortex_create(struct snd_card *card, struct pci_dev *pci, vortex_t ** rchip)
+{
+	vortex_t *chip;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_vortex_dev_free,
+	};
+
+	*rchip = NULL;
+
+	// check PCI availability (DMA).
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(32)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32)) < 0) {
+		printk(KERN_ERR "error to set DMA mask\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	chip->card = card;
+
+	// initialize the stuff
+	chip->pci_dev = pci;
+	chip->io = pci_resource_start(pci, 0);
+	chip->vendor = pci->vendor;
+	chip->device = pci->device;
+	chip->card = card;
+	chip->irq = -1;
+
+	// (1) PCI resource allocation
+	// Get MMIO area
+	//
+	if ((err = pci_request_regions(pci, CARD_NAME_SHORT)) != 0)
+		goto regions_out;
+
+	chip->mmio = pci_ioremap_bar(pci, 0);
+	if (!chip->mmio) {
+		printk(KERN_ERR "MMIO area remap failed.\n");
+		err = -ENOMEM;
+		goto ioremap_out;
+	}
+
+	/* Init audio core.
+	 * This must be done before we do request_irq otherwise we can get spurious
+	 * interrupts that we do not handle properly and make a mess of things */
+	if ((err = vortex_core_init(chip)) != 0) {
+		printk(KERN_ERR "hw core init failed\n");
+		goto core_out;
+	}
+
+	if ((err = request_irq(pci->irq, vortex_interrupt,
+	                       IRQF_SHARED, CARD_NAME_SHORT,
+	                       chip)) != 0) {
+		printk(KERN_ERR "cannot grab irq\n");
+		goto irq_out;
+	}
+	chip->irq = pci->irq;
+
+	pci_set_master(pci);
+	// End of PCI setup.
+
+	// Register alsa root device.
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		goto alloc_out;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rchip = chip;
+
+	return 0;
+
+      alloc_out:
+	free_irq(chip->irq, chip);
+      irq_out:
+	vortex_core_shutdown(chip);
+      core_out:
+	iounmap(chip->mmio);
+      ioremap_out:
+	pci_release_regions(chip->pci_dev);
+      regions_out:
+	pci_disable_device(chip->pci_dev);
+	//FIXME: this not the right place to unregister the gameport
+	vortex_gameport_unregister(chip);
+	kfree(chip);
+	return err;
+}
+
+// constructor -- see "Constructor" sub-section
+static int __devinit
+snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	vortex_t *chip;
+	int err;
+
+	// (1)
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+	// (2)
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	// (3)
+	if ((err = snd_vortex_create(card, pci, &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_vortex_workaround(pci, pcifix[dev]);
+
+	// Card details needed in snd_vortex_midi
+	strcpy(card->driver, CARD_NAME_SHORT);
+	sprintf(card->shortname, "Aureal Vortex %s", CARD_NAME_SHORT);
+	sprintf(card->longname, "%s at 0x%lx irq %i",
+		card->shortname, chip->io, chip->irq);
+
+	// (4) Alloc components.
+	// ADB pcm.
+	if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_ADB, NR_ADB)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+#ifndef CHIP_AU8820
+	// ADB SPDIF
+	if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_SPDIF, 1)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	// A3D
+	if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_A3D, NR_A3D)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+#endif
+	/*
+	   // ADB I2S
+	   if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_I2S, 1)) < 0) {
+	   snd_card_free(card);
+	   return err;
+	   }
+	 */
+#ifndef CHIP_AU8810
+	// WT pcm.
+	if ((err = snd_vortex_new_pcm(chip, VORTEX_PCM_WT, NR_WT)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+#endif
+	// snd_ac97_mixer and Vortex mixer.
+	if ((err = snd_vortex_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_vortex_midi(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	vortex_gameport_register(chip);
+
+#if 0
+	if (snd_seq_device_new(card, 1, SNDRV_SEQ_DEV_ID_VORTEX_SYNTH,
+			       sizeof(snd_vortex_synth_arg_t), &wave) < 0
+	    || wave == NULL) {
+		snd_printk(KERN_ERR "Can't initialize Aureal wavetable synth\n");
+	} else {
+		snd_vortex_synth_arg_t *arg;
+
+		arg = SNDRV_SEQ_DEVICE_ARGPTR(wave);
+		strcpy(wave->name, "Aureal Synth");
+		arg->hwptr = vortex;
+		arg->index = 1;
+		arg->seq_ports = seq_ports[dev];
+		arg->max_voices = max_synth_voices[dev];
+	}
+#endif
+
+	// (5)
+	if ((err = pci_read_config_word(pci, PCI_DEVICE_ID,
+				  &(chip->device))) < 0) {
+		snd_card_free(card);
+		return err;
+	}	
+	if ((err = pci_read_config_word(pci, PCI_VENDOR_ID,
+				  &(chip->vendor))) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	chip->rev = pci->revision;
+#ifdef CHIP_AU8830
+	if ((chip->rev) != 0xfe && (chip->rev) != 0xfa) {
+		printk(KERN_ALERT
+		       "vortex: The revision (%x) of your card has not been seen before.\n",
+		       chip->rev);
+		printk(KERN_ALERT
+		       "vortex: Please email the results of 'lspci -vv' to openvortex-dev@nongnu.org.\n");
+		snd_card_free(card);
+		err = -ENODEV;
+		return err;
+	}
+#endif
+
+	// (6)
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	// (7)
+	pci_set_drvdata(pci, card);
+	dev++;
+	vortex_connect_default(chip, 1);
+	vortex_enable_int(chip);
+	return 0;
+}
+
+// destructor -- see "Destructor" sub-section
+static void __devexit snd_vortex_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+// pci_driver definition
+static struct pci_driver driver = {
+	.name = CARD_NAME_SHORT,
+	.id_table = snd_vortex_ids,
+	.probe = snd_vortex_probe,
+	.remove = __devexit_p(snd_vortex_remove),
+};
+
+// initialization of the module
+static int __init alsa_card_vortex_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+// clean up the module
+static void __exit alsa_card_vortex_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_vortex_init)
+module_exit(alsa_card_vortex_exit)
diff --git a/linux/sound/pci/au88x0/au88x0.h b/linux/sound/pci/au88x0/au88x0.h
new file mode 100644
index 0000000..cf46bba
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0.h
@@ -0,0 +1,285 @@
+/*
+ *  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 Library 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 __SOUND_AU88X0_H
+#define __SOUND_AU88X0_H
+
+#ifdef __KERNEL__
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/mpu401.h>
+#include <sound/hwdep.h>
+#include <sound/ac97_codec.h>
+
+#endif
+
+#ifndef CHIP_AU8820
+#include "au88x0_eq.h"
+#include "au88x0_a3d.h"
+#endif
+#ifndef CHIP_AU8810
+#include "au88x0_wt.h"
+#endif
+
+#define	hwread(x,y) readl((x)+(y))
+#define	hwwrite(x,y,z) writel((z),(x)+(y))
+
+/* Vortex MPU401 defines. */
+#define	MIDI_CLOCK_DIV		0x61
+/* Standart MPU401 defines. */
+#define	MPU401_RESET		0xff
+#define	MPU401_ENTER_UART	0x3f
+#define	MPU401_ACK		0xfe
+
+// Get src register value to convert from x to y.
+#define	SRC_RATIO(x,y)		((((x<<15)/y) + 1)/2)
+
+/* FIFO software state constants. */
+#define FIFO_STOP 0
+#define FIFO_START 1
+#define FIFO_PAUSE 2
+
+/* IRQ flags */
+#define IRQ_ERR_MASK	0x00ff
+#define IRQ_FATAL	0x0001
+#define IRQ_PARITY	0x0002
+#define IRQ_REG		0x0004
+#define IRQ_FIFO	0x0008
+#define IRQ_DMA		0x0010
+#define IRQ_PCMOUT	0x0020	/* PCM OUT page crossing */
+#define IRQ_TIMER	0x1000
+#define IRQ_MIDI	0x2000
+#define IRQ_MODEM	0x4000
+
+/* ADB Resource */
+#define VORTEX_RESOURCE_DMA	0x00000000
+#define VORTEX_RESOURCE_SRC	0x00000001
+#define VORTEX_RESOURCE_MIXIN	0x00000002
+#define VORTEX_RESOURCE_MIXOUT	0x00000003
+#define VORTEX_RESOURCE_A3D	0x00000004
+#define VORTEX_RESOURCE_LAST	0x00000005
+
+/* codec io: VORTEX_CODEC_IO bits */
+#define VORTEX_CODEC_ID_SHIFT	24
+#define VORTEX_CODEC_WRITE	0x00800000
+#define VORTEX_CODEC_ADDSHIFT 	16
+#define VORTEX_CODEC_ADDMASK	0x7f0000
+#define VORTEX_CODEC_DATSHIFT	0
+#define VORTEX_CODEC_DATMASK	0xffff
+
+/* Check for SDAC bit in "Extended audio ID" AC97 register */
+//#define VORTEX_IS_QUAD(x) (((x)->codec == NULL) ?  0 : ((x)->codec->ext_id&0x80))
+#define VORTEX_IS_QUAD(x) ((x)->isquad)
+/* Check if chip has bug. */
+#define IS_BAD_CHIP(x) (\
+	(x->rev == 0xfe && x->device == PCI_DEVICE_ID_AUREAL_VORTEX_2) || \
+	(x->rev == 0xfe && x->device == PCI_DEVICE_ID_AUREAL_ADVANTAGE))
+
+
+/* PCM devices */
+#define VORTEX_PCM_ADB		0
+#define VORTEX_PCM_SPDIF	1
+#define VORTEX_PCM_A3D		2
+#define VORTEX_PCM_WT		3
+#define VORTEX_PCM_I2S		4
+#define VORTEX_PCM_LAST		5
+
+#define MIX_CAPT(x) (vortex->mixcapt[x])
+#define MIX_PLAYB(x) (vortex->mixplayb[x])
+#define MIX_SPDIF(x) (vortex->mixspdif[x])
+
+#define NR_WTPB 0x20		/* WT channels per eahc bank. */
+
+/* Structs */
+typedef struct {
+	//int this_08;          /* Still unknown */
+	int fifo_enabled;	/* this_24 */
+	int fifo_status;	/* this_1c */
+	u32 dma_ctrl;		/* this_78 (ADB), this_7c (WT) */
+	int dma_unknown;	/* this_74 (ADB), this_78 (WT). WDM: +8 */
+	int cfg0;
+	int cfg1;
+
+	int nr_ch;		/* Nr of PCM channels in use */
+	int type;		/* Output type (ac97, a3d, spdif, i2s, dsp) */
+	int dma;		/* Hardware DMA index. */
+	int dir;		/* Stream Direction. */
+	u32 resources[5];
+
+	/* Virtual page extender stuff */
+	int nr_periods;
+	int period_bytes;
+	int period_real;
+	int period_virt;
+
+	struct snd_pcm_substream *substream;
+} stream_t;
+
+typedef struct snd_vortex vortex_t;
+struct snd_vortex {
+	/* ALSA structs. */
+	struct snd_card *card;
+	struct snd_pcm *pcm[VORTEX_PCM_LAST];
+
+	struct snd_rawmidi *rmidi;	/* Legacy Midi interface. */
+	struct snd_ac97 *codec;
+
+	/* Stream structs. */
+	stream_t dma_adb[NR_ADB];
+	int spdif_sr;
+#ifndef CHIP_AU8810
+	stream_t dma_wt[NR_WT];
+	wt_voice_t wt_voice[NR_WT];	/* WT register cache. */
+	char mixwt[(NR_WT / NR_WTPB) * 6];	/* WT mixin objects */
+#endif
+
+	/* Global resources */
+	s8 mixcapt[2];
+	s8 mixplayb[4];
+#ifndef CHIP_AU8820
+	s8 mixspdif[2];
+	s8 mixa3d[2];	/* mixers which collect all a3d streams. */
+	s8 mixxtlk[2];	/* crosstalk canceler mixer inputs. */
+#endif
+	u32 fixed_res[5];
+
+#ifndef CHIP_AU8820
+	/* Hardware equalizer structs */
+	eqlzr_t eq;
+	/* A3D structs */
+	a3dsrc_t a3d[NR_A3D];
+	/* Xtalk canceler */
+	int xt_mode;		/* 1: speakers, 0:headphones. */
+#endif
+
+	int isquad;		/* cache of extended ID codec flag. */
+
+	/* Gameport stuff. */
+	struct gameport *gameport;
+
+	/* PCI hardware resources */
+	unsigned long io;
+	void __iomem *mmio;
+	unsigned int irq;
+	spinlock_t lock;
+
+	/* PCI device */
+	struct pci_dev *pci_dev;
+	u16 vendor;
+	u16 device;
+	u8 rev;
+};
+
+/* Functions. */
+
+/* SRC */
+static void vortex_adb_setsrc(vortex_t * vortex, int adbdma,
+			      unsigned int cvrt, int dir);
+
+/* DMA Engines. */
+static void vortex_adbdma_setbuffers(vortex_t * vortex, int adbdma,
+				     int size, int count);
+static void vortex_adbdma_setmode(vortex_t * vortex, int adbdma, int ie,
+				  int dir, int fmt, int d,
+				  u32 offset);
+static void vortex_adbdma_setstartbuffer(vortex_t * vortex, int adbdma, int sb);
+#ifndef CHIP_AU8810
+static void vortex_wtdma_setbuffers(vortex_t * vortex, int wtdma,
+				    int size, int count);
+static void vortex_wtdma_setmode(vortex_t * vortex, int wtdma, int ie, int fmt, int d,	/*int e, */
+				 u32 offset);
+static void vortex_wtdma_setstartbuffer(vortex_t * vortex, int wtdma, int sb);
+#endif
+
+static void vortex_adbdma_startfifo(vortex_t * vortex, int adbdma);
+//static void vortex_adbdma_stopfifo(vortex_t *vortex, int adbdma);
+static void vortex_adbdma_pausefifo(vortex_t * vortex, int adbdma);
+static void vortex_adbdma_resumefifo(vortex_t * vortex, int adbdma);
+static int inline vortex_adbdma_getlinearpos(vortex_t * vortex, int adbdma);
+static void vortex_adbdma_resetup(vortex_t *vortex, int adbdma);
+
+#ifndef CHIP_AU8810
+static void vortex_wtdma_startfifo(vortex_t * vortex, int wtdma);
+static void vortex_wtdma_stopfifo(vortex_t * vortex, int wtdma);
+static void vortex_wtdma_pausefifo(vortex_t * vortex, int wtdma);
+static void vortex_wtdma_resumefifo(vortex_t * vortex, int wtdma);
+static int inline vortex_wtdma_getlinearpos(vortex_t * vortex, int wtdma);
+#endif
+
+/* global stuff. */
+static void vortex_codec_init(vortex_t * vortex);
+static void vortex_codec_write(struct snd_ac97 * codec, unsigned short addr,
+			       unsigned short data);
+static unsigned short vortex_codec_read(struct snd_ac97 * codec, unsigned short addr);
+static void vortex_spdif_init(vortex_t * vortex, int spdif_sr, int spdif_mode);
+
+static int vortex_core_init(vortex_t * card);
+static int vortex_core_shutdown(vortex_t * card);
+static void vortex_enable_int(vortex_t * card);
+static irqreturn_t vortex_interrupt(int irq, void *dev_id);
+static int vortex_alsafmt_aspfmt(int alsafmt);
+
+/* Connection  stuff. */
+static void vortex_connect_default(vortex_t * vortex, int en);
+static int vortex_adb_allocroute(vortex_t * vortex, int dma, int nr_ch,
+				 int dir, int type);
+static char vortex_adb_checkinout(vortex_t * vortex, int resmap[], int out,
+				  int restype);
+#ifndef CHIP_AU8810
+static int vortex_wt_allocroute(vortex_t * vortex, int dma, int nr_ch);
+static void vortex_wt_connect(vortex_t * vortex, int en);
+static void vortex_wt_init(vortex_t * vortex);
+#endif
+
+static void vortex_route(vortex_t * vortex, int en, unsigned char channel,
+			 unsigned char source, unsigned char dest);
+#if 0
+static void vortex_routes(vortex_t * vortex, int en, unsigned char channel,
+			  unsigned char source, unsigned char dest0,
+			  unsigned char dest1);
+#endif
+static void vortex_connection_mixin_mix(vortex_t * vortex, int en,
+					unsigned char mixin,
+					unsigned char mix, int a);
+static void vortex_mix_setinputvolumebyte(vortex_t * vortex,
+					  unsigned char mix, int mixin,
+					  unsigned char vol);
+static void vortex_mix_setvolumebyte(vortex_t * vortex, unsigned char mix,
+				     unsigned char vol);
+
+/* A3D functions. */
+#ifndef CHIP_AU8820
+static void vortex_Vort3D_enable(vortex_t * v);
+static void vortex_Vort3D_disable(vortex_t * v);
+static void vortex_Vort3D_connect(vortex_t * vortex, int en);
+static void vortex_Vort3D_InitializeSource(a3dsrc_t * a, int en);
+#endif
+
+/* Driver stuff. */
+static int vortex_gameport_register(vortex_t * card);
+static void vortex_gameport_unregister(vortex_t * card);
+#ifndef CHIP_AU8820
+static int vortex_eq_init(vortex_t * vortex);
+static int vortex_eq_free(vortex_t * vortex);
+#endif
+/* ALSA stuff. */
+static int snd_vortex_new_pcm(vortex_t * vortex, int idx, int nr);
+static int snd_vortex_mixer(vortex_t * vortex);
+static int snd_vortex_midi(vortex_t * vortex);
+#endif
diff --git a/linux/sound/pci/au88x0/au88x0_a3d.c b/linux/sound/pci/au88x0/au88x0_a3d.c
new file mode 100644
index 0000000..f4aa8ff
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_a3d.c
@@ -0,0 +1,914 @@
+/***************************************************************************
+ *            au88x0_a3d.c
+ *
+ *  Fri Jul 18 14:16:22 2003
+ *  Copyright  2003  mjander
+ *  mjander@users.sourceforge.net
+ *
+ * A3D. You may think i'm crazy, but this may work someday. Who knows...
+ ****************************************************************************/
+
+/*
+ *  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 Library 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 "au88x0_a3d.h"
+#include "au88x0_a3ddata.c"
+#include "au88x0_xtalk.h"
+#include "au88x0.h"
+
+static void
+a3dsrc_SetTimeConsts(a3dsrc_t * a, short HrtfTrack, short ItdTrack,
+		     short GTrack, short CTrack)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio,
+		a3d_addrA(a->slice, a->source, A3D_A_HrtfTrackTC), HrtfTrack);
+	hwwrite(vortex->mmio,
+		a3d_addrA(a->slice, a->source, A3D_A_ITDTrackTC), ItdTrack);
+	hwwrite(vortex->mmio,
+		a3d_addrA(a->slice, a->source, A3D_A_GainTrackTC), GTrack);
+	hwwrite(vortex->mmio,
+		a3d_addrA(a->slice, a->source, A3D_A_CoeffTrackTC), CTrack);
+}
+
+#if 0
+static void
+a3dsrc_GetTimeConsts(a3dsrc_t * a, short *HrtfTrack, short *ItdTrack,
+		     short *GTrack, short *CTrack)
+{
+	// stub!
+}
+
+#endif
+/* Atmospheric absorbtion. */
+
+static void
+a3dsrc_SetAtmosTarget(a3dsrc_t * a, short aa, short b, short c, short d,
+		      short e)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio,
+		a3d_addrB(a->slice, a->source, A3D_B_A21Target),
+		(e << 0x10) | d);
+	hwwrite(vortex->mmio,
+		a3d_addrB(a->slice, a->source, A3D_B_B10Target),
+		(b << 0x10) | aa);
+	hwwrite(vortex->mmio,
+		a3d_addrB(a->slice, a->source, A3D_B_B2Target), c);
+}
+
+static void
+a3dsrc_SetAtmosCurrent(a3dsrc_t * a, short aa, short b, short c, short d,
+		       short e)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio,
+		a3d_addrB(a->slice, a->source, A3D_B_A12Current),
+		(e << 0x10) | d);
+	hwwrite(vortex->mmio,
+		a3d_addrB(a->slice, a->source, A3D_B_B01Current),
+		(b << 0x10) | aa);
+	hwwrite(vortex->mmio,
+		a3d_addrB(a->slice, a->source, A3D_B_B2Current), c);
+}
+
+static void
+a3dsrc_SetAtmosState(a3dsrc_t * a, short x1, short x2, short y1, short y2)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_x1), x1);
+	hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_x2), x2);
+	hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_y1), y1);
+	hwwrite(vortex->mmio, a3d_addrA(a->slice, a->source, A3D_A_y2), y2);
+}
+
+#if 0
+static void
+a3dsrc_GetAtmosTarget(a3dsrc_t * a, short *aa, short *b, short *c,
+		      short *d, short *e)
+{
+}
+static void
+a3dsrc_GetAtmosCurrent(a3dsrc_t * a, short *bb01, short *ab01, short *b2,
+		       short *aa12, short *ba12)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	*aa12 =
+	    hwread(vortex->mmio,
+		   a3d_addrA(a->slice, a->source, A3D_A_A12Current));
+	*ba12 =
+	    hwread(vortex->mmio,
+		   a3d_addrB(a->slice, a->source, A3D_B_A12Current));
+	*ab01 =
+	    hwread(vortex->mmio,
+		   a3d_addrA(a->slice, a->source, A3D_A_B01Current));
+	*bb01 =
+	    hwread(vortex->mmio,
+		   a3d_addrB(a->slice, a->source, A3D_B_B01Current));
+	*b2 =
+	    hwread(vortex->mmio,
+		   a3d_addrA(a->slice, a->source, A3D_A_B2Current));
+}
+
+static void
+a3dsrc_GetAtmosState(a3dsrc_t * a, short *x1, short *x2, short *y1, short *y2)
+{
+
+}
+
+#endif
+/* HRTF */
+
+static void
+a3dsrc_SetHrtfTarget(a3dsrc_t * a, a3d_Hrtf_t const aa, a3d_Hrtf_t const b)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	int i;
+
+	for (i = 0; i < HRTF_SZ; i++)
+		hwwrite(vortex->mmio,
+			a3d_addrB(a->slice, a->source,
+				  A3D_B_HrtfTarget) + (i << 2),
+			(b[i] << 0x10) | aa[i]);
+}
+
+static void
+a3dsrc_SetHrtfCurrent(a3dsrc_t * a, a3d_Hrtf_t const aa, a3d_Hrtf_t const b)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	int i;
+
+	for (i = 0; i < HRTF_SZ; i++)
+		hwwrite(vortex->mmio,
+			a3d_addrB(a->slice, a->source,
+				  A3D_B_HrtfCurrent) + (i << 2),
+			(b[i] << 0x10) | aa[i]);
+}
+
+static void
+a3dsrc_SetHrtfState(a3dsrc_t * a, a3d_Hrtf_t const aa, a3d_Hrtf_t const b)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	int i;
+
+	for (i = 0; i < HRTF_SZ; i++)
+		hwwrite(vortex->mmio,
+			a3d_addrB(a->slice, a->source,
+				  A3D_B_HrtfDelayLine) + (i << 2),
+			(b[i] << 0x10) | aa[i]);
+}
+
+static void a3dsrc_SetHrtfOutput(a3dsrc_t * a, short left, short right)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio,
+		a3d_addrA(a->slice, a->source, A3D_A_HrtfOutL), left);
+	hwwrite(vortex->mmio,
+		a3d_addrA(a->slice, a->source, A3D_A_HrtfOutR), right);
+}
+
+#if 0
+static void a3dsrc_GetHrtfTarget(a3dsrc_t * a, a3d_Hrtf_t aa, a3d_Hrtf_t b)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	int i;
+
+	for (i = 0; i < HRTF_SZ; i++)
+		aa[i] =
+		    hwread(vortex->mmio,
+			   a3d_addrA(a->slice, a->source,
+				     A3D_A_HrtfTarget + (i << 2)));
+	for (i = 0; i < HRTF_SZ; i++)
+		b[i] =
+		    hwread(vortex->mmio,
+			   a3d_addrB(a->slice, a->source,
+				     A3D_B_HrtfTarget + (i << 2)));
+}
+
+static void a3dsrc_GetHrtfCurrent(a3dsrc_t * a, a3d_Hrtf_t aa, a3d_Hrtf_t b)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	int i;
+
+	for (i = 0; i < HRTF_SZ; i++)
+		aa[i] =
+		    hwread(vortex->mmio,
+			   a3d_addrA(a->slice, a->source,
+				     A3D_A_HrtfCurrent + (i << 2)));
+	for (i = 0; i < HRTF_SZ; i++)
+		b[i] =
+		    hwread(vortex->mmio,
+			   a3d_addrB(a->slice, a->source,
+				     A3D_B_HrtfCurrent + (i << 2)));
+}
+
+static void a3dsrc_GetHrtfState(a3dsrc_t * a, a3d_Hrtf_t aa, a3d_Hrtf_t b)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	int i;
+	// FIXME: verify this!
+	for (i = 0; i < HRTF_SZ; i++)
+		aa[i] =
+		    hwread(vortex->mmio,
+			   a3d_addrA(a->slice, a->source,
+				     A3D_A_HrtfDelayLine + (i << 2)));
+	for (i = 0; i < HRTF_SZ; i++)
+		b[i] =
+		    hwread(vortex->mmio,
+			   a3d_addrB(a->slice, a->source,
+				     A3D_B_HrtfDelayLine + (i << 2)));
+}
+
+static void a3dsrc_GetHrtfOutput(a3dsrc_t * a, short *left, short *right)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	*left =
+	    hwread(vortex->mmio,
+		   a3d_addrA(a->slice, a->source, A3D_A_HrtfOutL));
+	*right =
+	    hwread(vortex->mmio,
+		   a3d_addrA(a->slice, a->source, A3D_A_HrtfOutR));
+}
+
+#endif
+
+/* Interaural Time Difference. 
+ * "The other main clue that humans use to locate sounds, is called 
+ * Interaural Time Difference (ITD). The differences in distance from 
+ * the sound source to a listeners ears means  that the sound will 
+ * reach one ear slightly before the other....", found somewhere with google.*/
+static void a3dsrc_SetItdTarget(a3dsrc_t * a, short litd, short ritd)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+
+	if (litd < 0)
+		litd = 0;
+	if (litd > 0x57FF)
+		litd = 0x57FF;
+	if (ritd < 0)
+		ritd = 0;
+	if (ritd > 0x57FF)
+		ritd = 0x57FF;
+	hwwrite(vortex->mmio,
+		a3d_addrB(a->slice, a->source, A3D_B_ITDTarget),
+		(ritd << 0x10) | litd);
+	//hwwrite(vortex->mmio, addr(0x191DF+5, this04, this08), (ritd<<0x10)|litd);
+}
+
+static void a3dsrc_SetItdCurrent(a3dsrc_t * a, short litd, short ritd)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+
+	if (litd < 0)
+		litd = 0;
+	if (litd > 0x57FF)
+		litd = 0x57FF;
+	if (ritd < 0)
+		ritd = 0;
+	if (ritd > 0x57FF)
+		ritd = 0x57FF;
+	hwwrite(vortex->mmio,
+		a3d_addrB(a->slice, a->source, A3D_B_ITDCurrent),
+		(ritd << 0x10) | litd);
+	//hwwrite(vortex->mmio, addr(0x191DF+1, this04, this08), (ritd<<0x10)|litd);
+}
+
+static void a3dsrc_SetItdDline(a3dsrc_t * a, a3d_ItdDline_t const dline)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	int i;
+	/* 45 != 40 -> Check this ! */
+	for (i = 0; i < DLINE_SZ; i++)
+		hwwrite(vortex->mmio,
+			a3d_addrA(a->slice, a->source,
+				  A3D_A_ITDDelayLine) + (i << 2), dline[i]);
+}
+
+#if 0
+static void a3dsrc_GetItdTarget(a3dsrc_t * a, short *litd, short *ritd)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	*ritd =
+	    hwread(vortex->mmio,
+		   a3d_addrA(a->slice, a->source, A3D_A_ITDTarget));
+	*litd =
+	    hwread(vortex->mmio,
+		   a3d_addrB(a->slice, a->source, A3D_B_ITDTarget));
+}
+
+static void a3dsrc_GetItdCurrent(a3dsrc_t * a, short *litd, short *ritd)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+
+	*ritd =
+	    hwread(vortex->mmio,
+		   a3d_addrA(a->slice, a->source, A3D_A_ITDCurrent));
+	*litd =
+	    hwread(vortex->mmio,
+		   a3d_addrB(a->slice, a->source, A3D_B_ITDCurrent));
+}
+
+static void a3dsrc_GetItdDline(a3dsrc_t * a, a3d_ItdDline_t dline)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	int i;
+
+	for (i = 0; i < DLINE_SZ; i++)
+		dline[i] =
+		    hwread(vortex->mmio,
+			   a3d_addrA(a->slice, a->source,
+				     A3D_A_ITDDelayLine + (i << 2)));
+}
+
+#endif
+/* This is may be used for ILD Interaural Level Difference. */
+
+static void a3dsrc_SetGainTarget(a3dsrc_t * a, short left, short right)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio,
+		a3d_addrB(a->slice, a->source, A3D_B_GainTarget),
+		(right << 0x10) | left);
+}
+
+static void a3dsrc_SetGainCurrent(a3dsrc_t * a, short left, short right)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio,
+		a3d_addrB(a->slice, a->source, A3D_B_GainCurrent),
+		(right << 0x10) | left);
+}
+
+#if 0
+static void a3dsrc_GetGainTarget(a3dsrc_t * a, short *left, short *right)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	*right =
+	    hwread(vortex->mmio,
+		   a3d_addrA(a->slice, a->source, A3D_A_GainTarget));
+	*left =
+	    hwread(vortex->mmio,
+		   a3d_addrB(a->slice, a->source, A3D_B_GainTarget));
+}
+
+static void a3dsrc_GetGainCurrent(a3dsrc_t * a, short *left, short *right)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	*right =
+	    hwread(vortex->mmio,
+		   a3d_addrA(a->slice, a->source, A3D_A_GainCurrent));
+	*left =
+	    hwread(vortex->mmio,
+		   a3d_addrB(a->slice, a->source, A3D_B_GainCurrent));
+}
+
+/* CA3dIO this func seems to be inlined all over this place. */
+static void CA3dIO_WriteReg(a3dsrc_t * a, unsigned long addr, short aa, short b)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio, addr, (aa << 0x10) | b);
+}
+
+#endif
+/* Generic A3D stuff */
+
+static void a3dsrc_SetA3DSampleRate(a3dsrc_t * a, int sr)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	int esp0 = 0;
+
+	esp0 = (((esp0 & 0x7fffffff) | 0xB8000000) & 0x7) | ((sr & 0x1f) << 3);
+	hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd), esp0);
+	//hwwrite(vortex->mmio, 0x19C38 + (this08<<0xd), esp0);
+}
+
+static void a3dsrc_EnableA3D(a3dsrc_t * a)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd),
+		0xF0000001);
+	//hwwrite(vortex->mmio, 0x19C38 + (this08<<0xd), 0xF0000001);
+}
+
+static void a3dsrc_DisableA3D(a3dsrc_t * a)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd),
+		0xF0000000);
+}
+
+static void a3dsrc_SetA3DControlReg(a3dsrc_t * a, unsigned long ctrl)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd), ctrl);
+}
+
+static void a3dsrc_SetA3DPointerReg(a3dsrc_t * a, unsigned long ptr)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	hwwrite(vortex->mmio, A3D_SLICE_Pointers + ((a->slice) << 0xd), ptr);
+}
+
+#if 0
+static void a3dsrc_GetA3DSampleRate(a3dsrc_t * a, int *sr)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	*sr = ((hwread(vortex->mmio, A3D_SLICE_Control + (a->slice << 0xd))
+		>> 3) & 0x1f);
+	//*sr = ((hwread(vortex->mmio, 0x19C38 + (this08<<0xd))>>3)&0x1f);
+}
+
+static void a3dsrc_GetA3DControlReg(a3dsrc_t * a, unsigned long *ctrl)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	*ctrl = hwread(vortex->mmio, A3D_SLICE_Control + ((a->slice) << 0xd));
+}
+
+static void a3dsrc_GetA3DPointerReg(a3dsrc_t * a, unsigned long *ptr)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	*ptr = hwread(vortex->mmio, A3D_SLICE_Pointers + ((a->slice) << 0xd));
+}
+
+#endif
+static void a3dsrc_ZeroSliceIO(a3dsrc_t * a)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+	int i;
+
+	for (i = 0; i < 8; i++)
+		hwwrite(vortex->mmio,
+			A3D_SLICE_VDBDest +
+			((((a->slice) << 0xb) + i) << 2), 0);
+	for (i = 0; i < 4; i++)
+		hwwrite(vortex->mmio,
+			A3D_SLICE_VDBSource +
+			((((a->slice) << 0xb) + i) << 2), 0);
+}
+
+/* Reset Single A3D source. */
+static void a3dsrc_ZeroState(a3dsrc_t * a)
+{
+	/*
+	printk(KERN_DEBUG "vortex: ZeroState slice: %d, source %d\n",
+	       a->slice, a->source);
+	*/
+	a3dsrc_SetAtmosState(a, 0, 0, 0, 0);
+	a3dsrc_SetHrtfState(a, A3dHrirZeros, A3dHrirZeros);
+	a3dsrc_SetItdDline(a, A3dItdDlineZeros);
+	a3dsrc_SetHrtfOutput(a, 0, 0);
+	a3dsrc_SetTimeConsts(a, 0, 0, 0, 0);
+
+	a3dsrc_SetAtmosCurrent(a, 0, 0, 0, 0, 0);
+	a3dsrc_SetAtmosTarget(a, 0, 0, 0, 0, 0);
+	a3dsrc_SetItdCurrent(a, 0, 0);
+	a3dsrc_SetItdTarget(a, 0, 0);
+	a3dsrc_SetGainCurrent(a, 0, 0);
+	a3dsrc_SetGainTarget(a, 0, 0);
+
+	a3dsrc_SetHrtfCurrent(a, A3dHrirZeros, A3dHrirZeros);
+	a3dsrc_SetHrtfTarget(a, A3dHrirZeros, A3dHrirZeros);
+}
+
+/* Reset entire A3D engine */
+static void a3dsrc_ZeroStateA3D(a3dsrc_t * a)
+{
+	int i, var, var2;
+
+	if ((a->vortex) == NULL) {
+		printk(KERN_ERR "vortex: ZeroStateA3D: ERROR: a->vortex is NULL\n");
+		return;
+	}
+
+	a3dsrc_SetA3DControlReg(a, 0);
+	a3dsrc_SetA3DPointerReg(a, 0);
+
+	var = a->slice;
+	var2 = a->source;
+	for (i = 0; i < 4; i++) {
+		a->slice = i;
+		a3dsrc_ZeroSliceIO(a);
+		//a3dsrc_ZeroState(a);
+	}
+	a->source = var2;
+	a->slice = var;
+}
+
+/* Program A3D block as pass through */
+static void a3dsrc_ProgramPipe(a3dsrc_t * a)
+{
+	a3dsrc_SetTimeConsts(a, 0, 0, 0, 0);
+	a3dsrc_SetAtmosCurrent(a, 0, 0x4000, 0, 0, 0);
+	a3dsrc_SetAtmosTarget(a, 0x4000, 0, 0, 0, 0);
+	a3dsrc_SetItdCurrent(a, 0, 0);
+	a3dsrc_SetItdTarget(a, 0, 0);
+	a3dsrc_SetGainCurrent(a, 0x7fff, 0x7fff);
+	a3dsrc_SetGainTarget(a, 0x7fff, 0x7fff);
+
+	/* SET HRTF HERE */
+
+	/* Single spike leads to identity transfer function. */
+	a3dsrc_SetHrtfCurrent(a, A3dHrirImpulse, A3dHrirImpulse);
+	a3dsrc_SetHrtfTarget(a, A3dHrirImpulse, A3dHrirImpulse);
+
+	/* Test: Sounds saturated. */
+	//a3dsrc_SetHrtfCurrent(a, A3dHrirSatTest, A3dHrirSatTest);
+	//a3dsrc_SetHrtfTarget(a, A3dHrirSatTest, A3dHrirSatTest);      
+}
+
+/* VDB = Vortex audio Dataflow Bus */
+#if 0
+static void a3dsrc_ClearVDBData(a3dsrc_t * a, unsigned long aa)
+{
+	vortex_t *vortex = (vortex_t *) (a->vortex);
+
+	// ((aa >> 2) << 8) - (aa >> 2)
+	hwwrite(vortex->mmio,
+		a3d_addrS(a->slice, A3D_SLICE_VDBDest) + (a->source << 2), 0);
+	hwwrite(vortex->mmio,
+		a3d_addrS(a->slice,
+			  A3D_SLICE_VDBDest + 4) + (a->source << 2), 0);
+	/*
+	   hwwrite(vortex->mmio, 0x19c00 + (((aa>>2)*255*4)+aa)*8, 0);
+	   hwwrite(vortex->mmio, 0x19c04 + (((aa>>2)*255*4)+aa)*8, 0);
+	 */
+}
+#endif
+
+/* A3D HwSource stuff. */
+
+static void vortex_A3dSourceHw_Initialize(vortex_t * v, int source, int slice)
+{
+	a3dsrc_t *a3dsrc = &(v->a3d[source + (slice * 4)]);
+	//a3dsrc_t *a3dsrc = &(v->a3d[source + (slice*4)]);
+
+	a3dsrc->vortex = (void *)v;
+	a3dsrc->source = source;	/* source */
+	a3dsrc->slice = slice;	/* slice */
+	a3dsrc_ZeroState(a3dsrc);
+	/* Added by me. */
+	a3dsrc_SetA3DSampleRate(a3dsrc, 0x11);
+}
+
+static int Vort3DRend_Initialize(vortex_t * v, unsigned short mode)
+{
+	v->xt_mode = mode;	/* this_14 */
+
+	vortex_XtalkHw_init(v);
+	vortex_XtalkHw_SetGainsAllChan(v);
+	switch (v->xt_mode) {
+	case XT_SPEAKER0:
+		vortex_XtalkHw_ProgramXtalkNarrow(v);
+		break;
+	case XT_SPEAKER1:
+		vortex_XtalkHw_ProgramXtalkWide(v);
+		break;
+	default:
+	case XT_HEADPHONE:
+		vortex_XtalkHw_ProgramPipe(v);
+		break;
+	case XT_DIAMOND:
+		vortex_XtalkHw_ProgramDiamondXtalk(v);
+		break;
+	}
+	vortex_XtalkHw_SetSampleRate(v, 0x11);
+	vortex_XtalkHw_Enable(v);
+	return 0;
+}
+
+/* 3D Sound entry points. */
+
+static int vortex_a3d_register_controls(vortex_t * vortex);
+static void vortex_a3d_unregister_controls(vortex_t * vortex);
+/* A3D base support init/shudown */
+static void __devinit vortex_Vort3D_enable(vortex_t * v)
+{
+	int i;
+
+	Vort3DRend_Initialize(v, XT_HEADPHONE);
+	for (i = 0; i < NR_A3D; i++) {
+		vortex_A3dSourceHw_Initialize(v, i % 4, i >> 2);
+		a3dsrc_ZeroStateA3D(&(v->a3d[0]));
+	}
+	/* Register ALSA controls */
+	vortex_a3d_register_controls(v);
+}
+
+static void vortex_Vort3D_disable(vortex_t * v)
+{
+	vortex_XtalkHw_Disable(v);
+	vortex_a3d_unregister_controls(v);
+}
+
+/* Make A3D subsystem connections. */
+static void vortex_Vort3D_connect(vortex_t * v, int en)
+{
+	int i;
+	
+// Disable AU8810 routes, since they seem to be wrong (in au8810.h).
+#ifdef CHIP_AU8810
+	return;
+#endif
+	
+#if 1
+	/* Alloc Xtalk mixin resources */
+	v->mixxtlk[0] =
+	    vortex_adb_checkinout(v, v->fixed_res, en, VORTEX_RESOURCE_MIXIN);
+	if (v->mixxtlk[0] < 0) {
+		printk
+		    ("vortex: vortex_Vort3D: ERROR: not enough free mixer resources.\n");
+		return;
+	}
+	v->mixxtlk[1] =
+	    vortex_adb_checkinout(v, v->fixed_res, en, VORTEX_RESOURCE_MIXIN);
+	if (v->mixxtlk[1] < 0) {
+		printk
+		    ("vortex: vortex_Vort3D: ERROR: not enough free mixer resources.\n");
+		return;
+	}
+#endif
+
+	/* Connect A3D -> XTALK */
+	for (i = 0; i < 4; i++) {
+		// 2 outputs per each A3D slice. 
+		vortex_route(v, en, 0x11, ADB_A3DOUT(i * 2), ADB_XTALKIN(i));
+		vortex_route(v, en, 0x11, ADB_A3DOUT(i * 2) + 1, ADB_XTALKIN(5 + i));
+	}
+#if 0
+	vortex_route(v, en, 0x11, ADB_XTALKOUT(0), ADB_EQIN(2));
+	vortex_route(v, en, 0x11, ADB_XTALKOUT(1), ADB_EQIN(3));
+#else
+	/* Connect XTalk -> mixer */
+	vortex_route(v, en, 0x11, ADB_XTALKOUT(0), ADB_MIXIN(v->mixxtlk[0]));
+	vortex_route(v, en, 0x11, ADB_XTALKOUT(1), ADB_MIXIN(v->mixxtlk[1]));
+	vortex_connection_mixin_mix(v, en, v->mixxtlk[0], v->mixplayb[0], 0);
+	vortex_connection_mixin_mix(v, en, v->mixxtlk[1], v->mixplayb[1], 0);
+	vortex_mix_setinputvolumebyte(v, v->mixplayb[0], v->mixxtlk[0],
+				      en ? MIX_DEFIGAIN : VOL_MIN);
+	vortex_mix_setinputvolumebyte(v, v->mixplayb[1], v->mixxtlk[1],
+				      en ? MIX_DEFIGAIN : VOL_MIN);
+	if (VORTEX_IS_QUAD(v)) {
+		vortex_connection_mixin_mix(v, en, v->mixxtlk[0],
+					    v->mixplayb[2], 0);
+		vortex_connection_mixin_mix(v, en, v->mixxtlk[1],
+					    v->mixplayb[3], 0);
+		vortex_mix_setinputvolumebyte(v, v->mixplayb[2],
+					      v->mixxtlk[0],
+					      en ? MIX_DEFIGAIN : VOL_MIN);
+		vortex_mix_setinputvolumebyte(v, v->mixplayb[3],
+					      v->mixxtlk[1],
+					      en ? MIX_DEFIGAIN : VOL_MIN);
+	}
+#endif
+}
+
+/* Initialize one single A3D source. */
+static void vortex_Vort3D_InitializeSource(a3dsrc_t * a, int en)
+{
+	if (a->vortex == NULL) {
+		printk
+		    ("vortex: Vort3D_InitializeSource: A3D source not initialized\n");
+		return;
+	}
+	if (en) {
+		a3dsrc_ProgramPipe(a);
+		a3dsrc_SetA3DSampleRate(a, 0x11);
+		a3dsrc_SetTimeConsts(a, HrtfTCDefault,
+				     ItdTCDefault, GainTCDefault,
+				     CoefTCDefault);
+		/* Remark: zero gain is muted. */
+		//a3dsrc_SetGainTarget(a,0,0);
+		//a3dsrc_SetGainCurrent(a,0,0);
+		a3dsrc_EnableA3D(a);
+	} else {
+		a3dsrc_DisableA3D(a);
+		a3dsrc_ZeroState(a);
+	}
+}
+
+/* Conversion of coordinates into 3D parameters. */
+
+static void vortex_a3d_coord2hrtf(a3d_Hrtf_t hrtf, int *coord)
+{
+	/* FIXME: implement this. */
+
+}
+static void vortex_a3d_coord2itd(a3d_Itd_t itd, int *coord)
+{
+	/* FIXME: implement this. */
+
+}
+static void vortex_a3d_coord2ild(a3d_LRGains_t ild, int left, int right)
+{
+	/* FIXME: implement this. */
+
+}
+static void vortex_a3d_translate_filter(a3d_atmos_t filter, int *params)
+{
+	/* FIXME: implement this. */
+
+}
+
+/* ALSA control interface.  */
+
+static int
+snd_vortex_a3d_hrtf_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 6;
+	uinfo->value.integer.min = 0x00000000;
+	uinfo->value.integer.max = 0xffffffff;
+	return 0;
+}
+static int
+snd_vortex_a3d_itd_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0x00000000;
+	uinfo->value.integer.max = 0xffffffff;
+	return 0;
+}
+static int
+snd_vortex_a3d_ild_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0x00000000;
+	uinfo->value.integer.max = 0xffffffff;
+	return 0;
+}
+static int
+snd_vortex_a3d_filter_info(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 4;
+	uinfo->value.integer.min = 0x00000000;
+	uinfo->value.integer.max = 0xffffffff;
+	return 0;
+}
+
+static int
+snd_vortex_a3d_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	//a3dsrc_t *a = kcontrol->private_data;
+	/* No read yet. Would this be really useable/needed ? */
+
+	return 0;
+}
+
+static int
+snd_vortex_a3d_hrtf_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	a3dsrc_t *a = kcontrol->private_data;
+	int changed = 1, i;
+	int coord[6];
+	for (i = 0; i < 6; i++)
+		coord[i] = ucontrol->value.integer.value[i];
+	/* Translate orientation coordinates to a3d params. */
+	vortex_a3d_coord2hrtf(a->hrtf[0], coord);
+	vortex_a3d_coord2hrtf(a->hrtf[1], coord);
+	a3dsrc_SetHrtfTarget(a, a->hrtf[0], a->hrtf[1]);
+	a3dsrc_SetHrtfCurrent(a, a->hrtf[0], a->hrtf[1]);
+	return changed;
+}
+
+static int
+snd_vortex_a3d_itd_put(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	a3dsrc_t *a = kcontrol->private_data;
+	int coord[6];
+	int i, changed = 1;
+	for (i = 0; i < 6; i++)
+		coord[i] = ucontrol->value.integer.value[i];
+	/* Translate orientation coordinates to a3d params. */
+	vortex_a3d_coord2itd(a->hrtf[0], coord);
+	vortex_a3d_coord2itd(a->hrtf[1], coord);
+	/* Inter aural time difference. */
+	a3dsrc_SetItdTarget(a, a->itd[0], a->itd[1]);
+	a3dsrc_SetItdCurrent(a, a->itd[0], a->itd[1]);
+	a3dsrc_SetItdDline(a, a->dline);
+	return changed;
+}
+
+static int
+snd_vortex_a3d_ild_put(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	a3dsrc_t *a = kcontrol->private_data;
+	int changed = 1;
+	int l, r;
+	/* There may be some scale tranlation needed here. */
+	l = ucontrol->value.integer.value[0];
+	r = ucontrol->value.integer.value[1];
+	vortex_a3d_coord2ild(a->ild, l, r);
+	/* Left Right panning. */
+	a3dsrc_SetGainTarget(a, l, r);
+	a3dsrc_SetGainCurrent(a, l, r);
+	return changed;
+}
+
+static int
+snd_vortex_a3d_filter_put(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	a3dsrc_t *a = kcontrol->private_data;
+	int i, changed = 1;
+	int params[6];
+	for (i = 0; i < 6; i++)
+		params[i] = ucontrol->value.integer.value[i];
+	/* Translate generic filter params to a3d filter params. */
+	vortex_a3d_translate_filter(a->filter, params);
+	/* Atmospheric absorbtion and filtering. */
+	a3dsrc_SetAtmosTarget(a, a->filter[0],
+			      a->filter[1], a->filter[2],
+			      a->filter[3], a->filter[4]);
+	a3dsrc_SetAtmosCurrent(a, a->filter[0],
+			       a->filter[1], a->filter[2],
+			       a->filter[3], a->filter[4]);
+	return changed;
+}
+
+static struct snd_kcontrol_new vortex_a3d_kcontrol __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name = "Playback PCM advanced processing",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = snd_vortex_a3d_hrtf_info,
+	.get = snd_vortex_a3d_get,
+	.put = snd_vortex_a3d_hrtf_put,
+};
+
+/* Control (un)registration. */
+static int __devinit vortex_a3d_register_controls(vortex_t * vortex)
+{
+	struct snd_kcontrol *kcontrol;
+	int err, i;
+	/* HRTF controls. */
+	for (i = 0; i < NR_A3D; i++) {
+		if ((kcontrol =
+		     snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
+			return -ENOMEM;
+		kcontrol->id.numid = CTRLID_HRTF;
+		kcontrol->info = snd_vortex_a3d_hrtf_info;
+		kcontrol->put = snd_vortex_a3d_hrtf_put;
+		if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+			return err;
+	}
+	/* ITD controls. */
+	for (i = 0; i < NR_A3D; i++) {
+		if ((kcontrol =
+		     snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
+			return -ENOMEM;
+		kcontrol->id.numid = CTRLID_ITD;
+		kcontrol->info = snd_vortex_a3d_itd_info;
+		kcontrol->put = snd_vortex_a3d_itd_put;
+		if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+			return err;
+	}
+	/* ILD (gains) controls. */
+	for (i = 0; i < NR_A3D; i++) {
+		if ((kcontrol =
+		     snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
+			return -ENOMEM;
+		kcontrol->id.numid = CTRLID_GAINS;
+		kcontrol->info = snd_vortex_a3d_ild_info;
+		kcontrol->put = snd_vortex_a3d_ild_put;
+		if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+			return err;
+	}
+	/* Filter controls. */
+	for (i = 0; i < NR_A3D; i++) {
+		if ((kcontrol =
+		     snd_ctl_new1(&vortex_a3d_kcontrol, &vortex->a3d[i])) == NULL)
+			return -ENOMEM;
+		kcontrol->id.numid = CTRLID_FILTER;
+		kcontrol->info = snd_vortex_a3d_filter_info;
+		kcontrol->put = snd_vortex_a3d_filter_put;
+		if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+			return err;
+	}
+	return 0;
+}
+
+static void vortex_a3d_unregister_controls(vortex_t * vortex)
+{
+
+}
+
+/* End of File*/
diff --git a/linux/sound/pci/au88x0/au88x0_a3d.h b/linux/sound/pci/au88x0/au88x0_a3d.h
new file mode 100644
index 0000000..0584c65
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_a3d.h
@@ -0,0 +1,123 @@
+/***************************************************************************
+ *            au88x0_a3d.h
+ *
+ *  Fri Jul 18 14:16:03 2003
+ *  Copyright  2003  mjander
+ *  mjander@users.sourceforge.net
+ ****************************************************************************/
+
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library 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 _AU88X0_A3D_H
+#define _AU88X0_A3D_H
+
+//#include <openal.h>
+
+#define HRTF_SZ 0x38
+#define DLINE_SZ 0x28
+
+#define CTRLID_HRTF		1
+#define CTRLID_ITD		2
+#define CTRLID_ILD		4
+#define CTRLID_FILTER	8
+#define CTRLID_GAINS	16
+
+/* 3D parameter structs */
+typedef unsigned short int a3d_Hrtf_t[HRTF_SZ];
+typedef unsigned short int a3d_ItdDline_t[DLINE_SZ];
+typedef unsigned short int a3d_atmos_t[5];
+typedef unsigned short int a3d_LRGains_t[2];
+typedef unsigned short int a3d_Itd_t[2];
+typedef unsigned short int a3d_Ild_t[2];
+
+typedef struct {
+	void *vortex;		// Formerly CAsp4HwIO*, now vortex_t*.
+	unsigned int source;	/* this_04 */
+	unsigned int slice;	/* this_08 */
+	a3d_Hrtf_t hrtf[2];
+	a3d_Itd_t itd;
+	a3d_Ild_t ild;
+	a3d_ItdDline_t dline;
+	a3d_atmos_t filter;
+} a3dsrc_t;
+
+/* First Register bank */
+
+#define A3D_A_HrtfCurrent	0x18000	/* 56 ULONG */
+#define A3D_A_GainCurrent	0x180E0
+#define A3D_A_GainTarget	0x180E4
+#define A3D_A_A12Current	0x180E8	/* Atmospheric current. */
+#define A3D_A_A21Target		0x180EC	/* Atmospheric target */
+#define A3D_A_B01Current	0x180F0	/* Atmospheric current */
+#define A3D_A_B10Target		0x180F4	/* Atmospheric target */
+#define A3D_A_B2Current		0x180F8	/* Atmospheric current */
+#define A3D_A_B2Target		0x180FC	/* Atmospheric target */
+#define A3D_A_HrtfTarget	0x18100	/* 56 ULONG */
+#define A3D_A_ITDCurrent	0x181E0
+#define A3D_A_ITDTarget		0x181E4
+#define A3D_A_HrtfDelayLine	0x181E8	/* 56 ULONG */
+#define A3D_A_ITDDelayLine	0x182C8	/* 40/45 ULONG */
+#define A3D_A_HrtfTrackTC	0x1837C	/* Time Constants */
+#define A3D_A_GainTrackTC	0x18380
+#define A3D_A_CoeffTrackTC	0x18384
+#define A3D_A_ITDTrackTC	0x18388
+#define A3D_A_x1			0x1838C
+#define A3D_A_x2			0x18390
+#define A3D_A_y1			0x18394
+#define A3D_A_y2			0x18398
+#define A3D_A_HrtfOutL		0x1839C
+#define A3D_A_HrtfOutR		0x183A0
+#define 	A3D_A_TAIL		0x183A4
+
+/* Second register bank */
+#define A3D_B_HrtfCurrent	0x19000	/* 56 ULONG */
+#define A3D_B_GainCurrent	0x190E0
+#define A3D_B_GainTarget	0x190E4
+#define A3D_B_A12Current	0x190E8
+#define A3D_B_A21Target		0x190EC
+#define A3D_B_B01Current	0x190F0
+#define A3D_B_B10Target		0x190F4
+#define A3D_B_B2Current		0x190F8
+#define A3D_B_B2Target		0x190FC
+#define A3D_B_HrtfTarget	0x19100	/* 56 ULONG */
+#define A3D_B_ITDCurrent	0x191E0
+#define A3D_B_ITDTarget		0x191E4
+#define A3D_B_HrtfDelayLine	0x191E8	/* 56 ULONG */
+#define 	A3D_B_TAIL		0x192C8
+
+/* There are 4 slices, 4 a3d each = 16 a3d sources. */
+#define A3D_SLICE_BANK_A		0x18000	/* 4 sources */
+#define A3D_SLICE_BANK_B		0x19000	/* 4 sources */
+#define A3D_SLICE_VDBDest		0x19C00	/* 8 ULONG */
+#define A3D_SLICE_VDBSource		0x19C20	/* 4 ULONG */
+#define A3D_SLICE_ABReg			0x19C30
+#define A3D_SLICE_CReg			0x19C34
+#define A3D_SLICE_Control		0x19C38
+#define A3D_SLICE_DebugReserved	0x19C3c	/* Dangerous! */
+#define A3D_SLICE_Pointers		0x19C40
+#define 	A3D_SLICE_TAIL		0x1A000
+
+// Slice size: 0x2000
+// Source size: 0x3A4, 0x2C8
+
+/* Address generator macro. */
+#define a3d_addrA(slice,source,reg) (((slice)<<0xd)+((source)*0x3A4)+(reg))
+#define a3d_addrB(slice,source,reg) (((slice)<<0xd)+((source)*0x2C8)+(reg))
+#define a3d_addrS(slice,reg) (((slice)<<0xd)+(reg))
+//#define a3d_addr(slice,source,reg) (((reg)>=0x19000) ? a3d_addr2((slice),(source),(reg)) : a3d_addr1((slice),(source),(reg)))
+
+#endif				/* _AU88X0_A3D_H */
diff --git a/linux/sound/pci/au88x0/au88x0_a3ddata.c b/linux/sound/pci/au88x0/au88x0_a3ddata.c
new file mode 100644
index 0000000..6fab4bb
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_a3ddata.c
@@ -0,0 +1,91 @@
+/***************************************************************************
+ *            au88x0_a3ddata.c
+ *
+ *  Wed Nov 19 21:11:32 2003
+ *  Copyright  2003  mjander
+ *  mjander@users.sourceforge.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 Library 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.
+ */
+
+/* Constant initializer values. */
+
+static const a3d_Hrtf_t A3dHrirZeros = {
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0,
+	0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0,
+	0, 0, 0
+};
+
+static const a3d_Hrtf_t A3dHrirImpulse = {
+	0x7fff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0,
+	0, 0, 0
+};
+
+static const a3d_Hrtf_t A3dHrirOnes = {
+	0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
+	0x7fff,
+	0x7fff,
+	0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
+	0x7fff,
+	0x7fff,
+	0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
+	0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
+	0x7fff,
+	0x7fff,
+	0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
+	0x7fff,
+	0x7fff,
+	0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff
+};
+
+static const a3d_Hrtf_t A3dHrirSatTest = {
+	0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
+	0x7fff,
+	0x7fff,
+	0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
+	0x8001,
+	0x8001,
+	0x7fff, 0x0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static const a3d_Hrtf_t A3dHrirDImpulse = {
+	0, 0x7fff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0,
+	0, 0, 0
+};
+
+static const a3d_ItdDline_t A3dItdDlineZeros = {
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static short const GainTCDefault = 0x300;
+static short const ItdTCDefault = 0x0C8;
+static short const HrtfTCDefault = 0x147;
+static short const CoefTCDefault = 0x300;
diff --git a/linux/sound/pci/au88x0/au88x0_core.c b/linux/sound/pci/au88x0/au88x0_core.c
new file mode 100644
index 0000000..23f49f3
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_core.c
@@ -0,0 +1,2846 @@
+/*
+ *  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 Library 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.
+ */
+
+/*
+    Vortex core low level functions.
+	
+ Author: Manuel Jander (mjander@users.sourceforge.cl)
+ These functions are mainly the result of translations made
+ from the original disassembly of the au88x0 binary drivers,
+ written by Aureal before they went down.
+ Many thanks to the Jeff Muizelaar, Kester Maddock, and whoever
+ contributed to the OpenVortex project.
+ The author of this file, put the few available pieces together
+ and translated the rest of the riddle (Mix, Src and connection stuff).
+ Some things are still to be discovered, and their meanings are unclear.
+
+ Some of these functions aren't intended to be really used, rather
+ to help to understand how does the AU88X0 chips work. Keep them in, because
+ they could be used somewhere in the future.
+
+ This code hasn't been tested or proof read thoroughly. If you wanna help,
+ take a look at the AU88X0 assembly and check if this matches.
+ Functions tested ok so far are (they show the desired effect
+ at least):
+   vortex_routes(); (1 bug fixed).
+   vortex_adb_addroute();
+   vortex_adb_addroutes();
+   vortex_connect_codecplay();
+   vortex_src_flushbuffers();
+   vortex_adbdma_setmode();  note: still some unknown arguments!
+   vortex_adbdma_startfifo();
+   vortex_adbdma_stopfifo();
+   vortex_fifo_setadbctrl(); note: still some unknown arguments!
+   vortex_mix_setinputvolumebyte();
+   vortex_mix_enableinput();
+   vortex_mixer_addWTD(); (fixed)
+   vortex_connection_adbdma_src_src();
+   vortex_connection_adbdma_src();
+   vortex_src_change_convratio();
+   vortex_src_addWTD(); (fixed)
+
+ History:
+
+ 01-03-2003 First revision.
+ 01-21-2003 Some bug fixes.
+ 17-02-2003 many bugfixes after a big versioning mess.
+ 18-02-2003 JAAAAAHHHUUUUUU!!!! The mixer works !! I'm just so happy !
+			 (2 hours later...) I cant believe it! Im really lucky today.
+			 Now the SRC is working too! Yeah! XMMS works !
+ 20-02-2003 First steps into the ALSA world.
+ 28-02-2003 As my birthday present, i discovered how the DMA buffer pages really
+            work :-). It was all wrong.
+ 12-03-2003 ALSA driver starts working (2 channels).
+ 16-03-2003 More srcblock_setupchannel discoveries.
+ 12-04-2003 AU8830 playback support. Recording in the works.
+ 17-04-2003 vortex_route() and vortex_routes() bug fixes. AU8830 recording
+ 			works now, but chipn' dale effect is still there.
+ 16-05-2003 SrcSetupChannel cleanup. Moved the Src setup stuff entirely
+            into au88x0_pcm.c .
+ 06-06-2003 Buffer shifter bugfix. Mixer volume fix.
+ 07-12-2003 A3D routing finally fixed. Believed to be OK.
+ 25-03-2004 Many thanks to Claudia, for such valuable bug reports.
+ 
+*/
+
+#include "au88x0.h"
+#include "au88x0_a3d.h"
+#include <linux/delay.h>
+
+/*  MIXER (CAsp4Mix.s and CAsp4Mixer.s) */
+
+// FIXME: get rid of this.
+static int mchannels[NR_MIXIN];
+static int rampchs[NR_MIXIN];
+
+static void vortex_mixer_en_sr(vortex_t * vortex, int channel)
+{
+	hwwrite(vortex->mmio, VORTEX_MIXER_SR,
+		hwread(vortex->mmio, VORTEX_MIXER_SR) | (0x1 << channel));
+}
+static void vortex_mixer_dis_sr(vortex_t * vortex, int channel)
+{
+	hwwrite(vortex->mmio, VORTEX_MIXER_SR,
+		hwread(vortex->mmio, VORTEX_MIXER_SR) & ~(0x1 << channel));
+}
+
+#if 0
+static void
+vortex_mix_muteinputgain(vortex_t * vortex, unsigned char mix,
+			 unsigned char channel)
+{
+	hwwrite(vortex->mmio, VORTEX_MIX_INVOL_A + ((mix << 5) + channel),
+		0x80);
+	hwwrite(vortex->mmio, VORTEX_MIX_INVOL_B + ((mix << 5) + channel),
+		0x80);
+}
+
+static int vortex_mix_getvolume(vortex_t * vortex, unsigned char mix)
+{
+	int a;
+	a = hwread(vortex->mmio, VORTEX_MIX_VOL_A + (mix << 2)) & 0xff;
+	//FP2LinearFrac(a);
+	return (a);
+}
+
+static int
+vortex_mix_getinputvolume(vortex_t * vortex, unsigned char mix,
+			  int channel, int *vol)
+{
+	int a;
+	if (!(mchannels[mix] & (1 << channel)))
+		return 0;
+	a = hwread(vortex->mmio,
+		   VORTEX_MIX_INVOL_A + (((mix << 5) + channel) << 2));
+	/*
+	   if (rampchs[mix] == 0)
+	   a = FP2LinearFrac(a);
+	   else
+	   a = FP2LinearFracWT(a);
+	 */
+	*vol = a;
+	return (0);
+}
+
+static unsigned int vortex_mix_boost6db(unsigned char vol)
+{
+	return (vol + 8);	/* WOW! what a complex function! */
+}
+
+static void vortex_mix_rampvolume(vortex_t * vortex, int mix)
+{
+	int ch;
+	char a;
+	// This function is intended for ramping down only (see vortex_disableinput()).
+	for (ch = 0; ch < 0x20; ch++) {
+		if (((1 << ch) & rampchs[mix]) == 0)
+			continue;
+		a = hwread(vortex->mmio,
+			   VORTEX_MIX_INVOL_B + (((mix << 5) + ch) << 2));
+		if (a > -126) {
+			a -= 2;
+			hwwrite(vortex->mmio,
+				VORTEX_MIX_INVOL_A +
+				(((mix << 5) + ch) << 2), a);
+			hwwrite(vortex->mmio,
+				VORTEX_MIX_INVOL_B +
+				(((mix << 5) + ch) << 2), a);
+		} else
+			vortex_mix_killinput(vortex, mix, ch);
+	}
+}
+
+static int
+vortex_mix_getenablebit(vortex_t * vortex, unsigned char mix, int mixin)
+{
+	int addr, temp;
+	if (mixin >= 0)
+		addr = mixin;
+	else
+		addr = mixin + 3;
+	addr = ((mix << 3) + (addr >> 2)) << 2;
+	temp = hwread(vortex->mmio, VORTEX_MIX_ENIN + addr);
+	return ((temp >> (mixin & 3)) & 1);
+}
+#endif
+static void
+vortex_mix_setvolumebyte(vortex_t * vortex, unsigned char mix,
+			 unsigned char vol)
+{
+	int temp;
+	hwwrite(vortex->mmio, VORTEX_MIX_VOL_A + (mix << 2), vol);
+	if (1) {		/*if (this_10) */
+		temp = hwread(vortex->mmio, VORTEX_MIX_VOL_B + (mix << 2));
+		if ((temp != 0x80) || (vol == 0x80))
+			return;
+	}
+	hwwrite(vortex->mmio, VORTEX_MIX_VOL_B + (mix << 2), vol);
+}
+
+static void
+vortex_mix_setinputvolumebyte(vortex_t * vortex, unsigned char mix,
+			      int mixin, unsigned char vol)
+{
+	int temp;
+
+	hwwrite(vortex->mmio,
+		VORTEX_MIX_INVOL_A + (((mix << 5) + mixin) << 2), vol);
+	if (1) {		/* this_10, initialized to 1. */
+		temp =
+		    hwread(vortex->mmio,
+			   VORTEX_MIX_INVOL_B + (((mix << 5) + mixin) << 2));
+		if ((temp != 0x80) || (vol == 0x80))
+			return;
+	}
+	hwwrite(vortex->mmio,
+		VORTEX_MIX_INVOL_B + (((mix << 5) + mixin) << 2), vol);
+}
+
+static void
+vortex_mix_setenablebit(vortex_t * vortex, unsigned char mix, int mixin, int en)
+{
+	int temp, addr;
+
+	if (mixin < 0)
+		addr = (mixin + 3);
+	else
+		addr = mixin;
+	addr = ((mix << 3) + (addr >> 2)) << 2;
+	temp = hwread(vortex->mmio, VORTEX_MIX_ENIN + addr);
+	if (en)
+		temp |= (1 << (mixin & 3));
+	else
+		temp &= ~(1 << (mixin & 3));
+	/* Mute input. Astatic void crackling? */
+	hwwrite(vortex->mmio,
+		VORTEX_MIX_INVOL_B + (((mix << 5) + mixin) << 2), 0x80);
+	/* Looks like clear buffer. */
+	hwwrite(vortex->mmio, VORTEX_MIX_SMP + (mixin << 2), 0x0);
+	hwwrite(vortex->mmio, VORTEX_MIX_SMP + 4 + (mixin << 2), 0x0);
+	/* Write enable bit. */
+	hwwrite(vortex->mmio, VORTEX_MIX_ENIN + addr, temp);
+}
+
+static void
+vortex_mix_killinput(vortex_t * vortex, unsigned char mix, int mixin)
+{
+	rampchs[mix] &= ~(1 << mixin);
+	vortex_mix_setinputvolumebyte(vortex, mix, mixin, 0x80);
+	mchannels[mix] &= ~(1 << mixin);
+	vortex_mix_setenablebit(vortex, mix, mixin, 0);
+}
+
+static void
+vortex_mix_enableinput(vortex_t * vortex, unsigned char mix, int mixin)
+{
+	vortex_mix_killinput(vortex, mix, mixin);
+	if ((mchannels[mix] & (1 << mixin)) == 0) {
+		vortex_mix_setinputvolumebyte(vortex, mix, mixin, 0x80);	/*0x80 : mute */
+		mchannels[mix] |= (1 << mixin);
+	}
+	vortex_mix_setenablebit(vortex, mix, mixin, 1);
+}
+
+static void
+vortex_mix_disableinput(vortex_t * vortex, unsigned char mix, int channel,
+			int ramp)
+{
+	if (ramp) {
+		rampchs[mix] |= (1 << channel);
+		// Register callback.
+		//vortex_mix_startrampvolume(vortex);
+		vortex_mix_killinput(vortex, mix, channel);
+	} else
+		vortex_mix_killinput(vortex, mix, channel);
+}
+
+static int
+vortex_mixer_addWTD(vortex_t * vortex, unsigned char mix, unsigned char ch)
+{
+	int temp, lifeboat = 0, prev;
+
+	temp = hwread(vortex->mmio, VORTEX_MIXER_SR);
+	if ((temp & (1 << ch)) == 0) {
+		hwwrite(vortex->mmio, VORTEX_MIXER_CHNBASE + (ch << 2), mix);
+		vortex_mixer_en_sr(vortex, ch);
+		return 1;
+	}
+	prev = VORTEX_MIXER_CHNBASE + (ch << 2);
+	temp = hwread(vortex->mmio, prev);
+	while (temp & 0x10) {
+		prev = VORTEX_MIXER_RTBASE + ((temp & 0xf) << 2);
+		temp = hwread(vortex->mmio, prev);
+		//printk(KERN_INFO "vortex: mixAddWTD: while addr=%x, val=%x\n", prev, temp);
+		if ((++lifeboat) > 0xf) {
+			printk(KERN_ERR
+			       "vortex_mixer_addWTD: lifeboat overflow\n");
+			return 0;
+		}
+	}
+	hwwrite(vortex->mmio, VORTEX_MIXER_RTBASE + ((temp & 0xf) << 2), mix);
+	hwwrite(vortex->mmio, prev, (temp & 0xf) | 0x10);
+	return 1;
+}
+
+static int
+vortex_mixer_delWTD(vortex_t * vortex, unsigned char mix, unsigned char ch)
+{
+	int esp14 = -1, esp18, eax, ebx, edx, ebp, esi = 0;
+	//int esp1f=edi(while)=src, esp10=ch;
+
+	eax = hwread(vortex->mmio, VORTEX_MIXER_SR);
+	if (((1 << ch) & eax) == 0) {
+		printk(KERN_ERR "mix ALARM %x\n", eax);
+		return 0;
+	}
+	ebp = VORTEX_MIXER_CHNBASE + (ch << 2);
+	esp18 = hwread(vortex->mmio, ebp);
+	if (esp18 & 0x10) {
+		ebx = (esp18 & 0xf);
+		if (mix == ebx) {
+			ebx = VORTEX_MIXER_RTBASE + (mix << 2);
+			edx = hwread(vortex->mmio, ebx);
+			//7b60
+			hwwrite(vortex->mmio, ebp, edx);
+			hwwrite(vortex->mmio, ebx, 0);
+		} else {
+			//7ad3
+			edx =
+			    hwread(vortex->mmio,
+				   VORTEX_MIXER_RTBASE + (ebx << 2));
+			//printk(KERN_INFO "vortex: mixdelWTD: 1 addr=%x, val=%x, src=%x\n", ebx, edx, src);
+			while ((edx & 0xf) != mix) {
+				if ((esi) > 0xf) {
+					printk(KERN_ERR
+					       "vortex: mixdelWTD: error lifeboat overflow\n");
+					return 0;
+				}
+				esp14 = ebx;
+				ebx = edx & 0xf;
+				ebp = ebx << 2;
+				edx =
+				    hwread(vortex->mmio,
+					   VORTEX_MIXER_RTBASE + ebp);
+				//printk(KERN_INFO "vortex: mixdelWTD: while addr=%x, val=%x\n", ebp, edx);
+				esi++;
+			}
+			//7b30
+			ebp = ebx << 2;
+			if (edx & 0x10) {	/* Delete entry in between others */
+				ebx = VORTEX_MIXER_RTBASE + ((edx & 0xf) << 2);
+				edx = hwread(vortex->mmio, ebx);
+				//7b60
+				hwwrite(vortex->mmio,
+					VORTEX_MIXER_RTBASE + ebp, edx);
+				hwwrite(vortex->mmio, ebx, 0);
+				//printk(KERN_INFO "vortex mixdelWTD between addr= 0x%x, val= 0x%x\n", ebp, edx);
+			} else {	/* Delete last entry */
+				//7b83
+				if (esp14 == -1)
+					hwwrite(vortex->mmio,
+						VORTEX_MIXER_CHNBASE +
+						(ch << 2), esp18 & 0xef);
+				else {
+					ebx = (0xffffffe0 & edx) | (0xf & ebx);
+					hwwrite(vortex->mmio,
+						VORTEX_MIXER_RTBASE +
+						(esp14 << 2), ebx);
+					//printk(KERN_INFO "vortex mixdelWTD last addr= 0x%x, val= 0x%x\n", esp14, ebx);
+				}
+				hwwrite(vortex->mmio,
+					VORTEX_MIXER_RTBASE + ebp, 0);
+				return 1;
+			}
+		}
+	} else {
+		//printk(KERN_INFO "removed last mix\n");
+		//7be0
+		vortex_mixer_dis_sr(vortex, ch);
+		hwwrite(vortex->mmio, ebp, 0);
+	}
+	return 1;
+}
+
+static void vortex_mixer_init(vortex_t * vortex)
+{
+	u32 addr;
+	int x;
+
+	// FIXME: get rid of this crap.
+	memset(mchannels, 0, NR_MIXOUT * sizeof(int));
+	memset(rampchs, 0, NR_MIXOUT * sizeof(int));
+
+	addr = VORTEX_MIX_SMP + 0x17c;
+	for (x = 0x5f; x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, 0);
+		addr -= 4;
+	}
+	addr = VORTEX_MIX_ENIN + 0x1fc;
+	for (x = 0x7f; x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, 0);
+		addr -= 4;
+	}
+	addr = VORTEX_MIX_SMP + 0x17c;
+	for (x = 0x5f; x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, 0);
+		addr -= 4;
+	}
+	addr = VORTEX_MIX_INVOL_A + 0x7fc;
+	for (x = 0x1ff; x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, 0x80);
+		addr -= 4;
+	}
+	addr = VORTEX_MIX_VOL_A + 0x3c;
+	for (x = 0xf; x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, 0x80);
+		addr -= 4;
+	}
+	addr = VORTEX_MIX_INVOL_B + 0x7fc;
+	for (x = 0x1ff; x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, 0x80);
+		addr -= 4;
+	}
+	addr = VORTEX_MIX_VOL_B + 0x3c;
+	for (x = 0xf; x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, 0x80);
+		addr -= 4;
+	}
+	addr = VORTEX_MIXER_RTBASE + (MIXER_RTBASE_SIZE - 1) * 4;
+	for (x = (MIXER_RTBASE_SIZE - 1); x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, 0x0);
+		addr -= 4;
+	}
+	hwwrite(vortex->mmio, VORTEX_MIXER_SR, 0);
+
+	/* Set clipping ceiling (this may be all wrong). */
+	/*
+	for (x = 0; x < 0x80; x++) {
+		hwwrite(vortex->mmio, VORTEX_MIXER_CLIP + (x << 2), 0x3ffff);
+	}
+	*/
+	/*
+	   call CAsp4Mix__Initialize_CAsp4HwIO____CAsp4Mixer____
+	   Register ISR callback for volume smooth fade out.
+	   Maybe this avoids clicks when press "stop" ?
+	 */
+}
+
+/*  SRC (CAsp4Src.s and CAsp4SrcBlock) */
+
+static void vortex_src_en_sr(vortex_t * vortex, int channel)
+{
+	hwwrite(vortex->mmio, VORTEX_SRCBLOCK_SR,
+		hwread(vortex->mmio, VORTEX_SRCBLOCK_SR) | (0x1 << channel));
+}
+
+static void vortex_src_dis_sr(vortex_t * vortex, int channel)
+{
+	hwwrite(vortex->mmio, VORTEX_SRCBLOCK_SR,
+		hwread(vortex->mmio, VORTEX_SRCBLOCK_SR) & ~(0x1 << channel));
+}
+
+static void vortex_src_flushbuffers(vortex_t * vortex, unsigned char src)
+{
+	int i;
+
+	for (i = 0x1f; i >= 0; i--)
+		hwwrite(vortex->mmio,
+			VORTEX_SRC_DATA0 + (src << 7) + (i << 2), 0);
+	hwwrite(vortex->mmio, VORTEX_SRC_DATA + (src << 3), 0);
+	hwwrite(vortex->mmio, VORTEX_SRC_DATA + (src << 3) + 4, 0);
+}
+
+static void vortex_src_cleardrift(vortex_t * vortex, unsigned char src)
+{
+	hwwrite(vortex->mmio, VORTEX_SRC_DRIFT0 + (src << 2), 0);
+	hwwrite(vortex->mmio, VORTEX_SRC_DRIFT1 + (src << 2), 0);
+	hwwrite(vortex->mmio, VORTEX_SRC_DRIFT2 + (src << 2), 1);
+}
+
+static void
+vortex_src_set_throttlesource(vortex_t * vortex, unsigned char src, int en)
+{
+	int temp;
+
+	temp = hwread(vortex->mmio, VORTEX_SRC_SOURCE);
+	if (en)
+		temp |= 1 << src;
+	else
+		temp &= ~(1 << src);
+	hwwrite(vortex->mmio, VORTEX_SRC_SOURCE, temp);
+}
+
+static int
+vortex_src_persist_convratio(vortex_t * vortex, unsigned char src, int ratio)
+{
+	int temp, lifeboat = 0;
+
+	do {
+		hwwrite(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2), ratio);
+		temp = hwread(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2));
+		if ((++lifeboat) > 0x9) {
+			printk(KERN_ERR "Vortex: Src cvr fail\n");
+			break;
+		}
+	}
+	while (temp != ratio);
+	return temp;
+}
+
+#if 0
+static void vortex_src_slowlock(vortex_t * vortex, unsigned char src)
+{
+	int temp;
+
+	hwwrite(vortex->mmio, VORTEX_SRC_DRIFT2 + (src << 2), 1);
+	hwwrite(vortex->mmio, VORTEX_SRC_DRIFT0 + (src << 2), 0);
+	temp = hwread(vortex->mmio, VORTEX_SRC_U0 + (src << 2));
+	if (temp & 0x200)
+		hwwrite(vortex->mmio, VORTEX_SRC_U0 + (src << 2),
+			temp & ~0x200L);
+}
+
+static void
+vortex_src_change_convratio(vortex_t * vortex, unsigned char src, int ratio)
+{
+	int temp, a;
+
+	if ((ratio & 0x10000) && (ratio != 0x10000)) {
+		if (ratio & 0x3fff)
+			a = (0x11 - ((ratio >> 0xe) & 0x3)) - 1;
+		else
+			a = (0x11 - ((ratio >> 0xe) & 0x3)) - 2;
+	} else
+		a = 0xc;
+	temp = hwread(vortex->mmio, VORTEX_SRC_U0 + (src << 2));
+	if (((temp >> 4) & 0xf) != a)
+		hwwrite(vortex->mmio, VORTEX_SRC_U0 + (src << 2),
+			(temp & 0xf) | ((a & 0xf) << 4));
+
+	vortex_src_persist_convratio(vortex, src, ratio);
+}
+
+static int
+vortex_src_checkratio(vortex_t * vortex, unsigned char src,
+		      unsigned int desired_ratio)
+{
+	int hw_ratio, lifeboat = 0;
+
+	hw_ratio = hwread(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2));
+
+	while (hw_ratio != desired_ratio) {
+		hwwrite(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2), desired_ratio);
+
+		if ((lifeboat++) > 15) {
+			printk(KERN_ERR "Vortex: could not set src-%d from %d to %d\n",
+			       src, hw_ratio, desired_ratio);
+			break;
+		}
+	}
+
+	return hw_ratio;
+}
+
+#endif
+/*
+ Objective: Set samplerate for given SRC module.
+ Arguments:
+	card:	pointer to vortex_t strcut.
+	src:	Integer index of the SRC module.
+	cr:		Current sample rate conversion factor.
+	b:		unknown 16 bit value.
+	sweep:	Enable Samplerate fade from cr toward tr flag.
+	dirplay: 1: playback, 0: recording.
+	sl:		Slow Lock flag.
+	tr:		Target samplerate conversion.
+	thsource: Throttle source flag (no idea what that means).
+*/
+static void vortex_src_setupchannel(vortex_t * card, unsigned char src,
+			unsigned int cr, unsigned int b, int sweep, int d,
+			int dirplay, int sl, unsigned int tr, int thsource)
+{
+	// noplayback: d=2,4,7,0xa,0xb when using first 2 src's.
+	// c: enables pitch sweep.
+	// looks like g is c related. Maybe g is a sweep parameter ?
+	// g = cvr
+	// dirplay: 0 = recording, 1 = playback
+	// d = src hw index.
+
+	int esi, ebp = 0, esp10;
+
+	vortex_src_flushbuffers(card, src);
+
+	if (sweep) {
+		if ((tr & 0x10000) && (tr != 0x10000)) {
+			tr = 0;
+			esi = 0x7;
+		} else {
+			if ((((short)tr) < 0) && (tr != 0x8000)) {
+				tr = 0;
+				esi = 0x8;
+			} else {
+				tr = 1;
+				esi = 0xc;
+			}
+		}
+	} else {
+		if ((cr & 0x10000) && (cr != 0x10000)) {
+			tr = 0;	/*ebx = 0 */
+			esi = 0x11 - ((cr >> 0xe) & 7);
+			if (cr & 0x3fff)
+				esi -= 1;
+			else
+				esi -= 2;
+		} else {
+			tr = 1;
+			esi = 0xc;
+		}
+	}
+	vortex_src_cleardrift(card, src);
+	vortex_src_set_throttlesource(card, src, thsource);
+
+	if ((dirplay == 0) && (sweep == 0)) {
+		if (tr)
+			esp10 = 0xf;
+		else
+			esp10 = 0xc;
+		ebp = 0;
+	} else {
+		if (tr)
+			ebp = 0xf;
+		else
+			ebp = 0xc;
+		esp10 = 0;
+	}
+	hwwrite(card->mmio, VORTEX_SRC_U0 + (src << 2),
+		(sl << 0x9) | (sweep << 0x8) | ((esi & 0xf) << 4) | d);
+	/* 0xc0   esi=0xc c=f=0 d=0 */
+	vortex_src_persist_convratio(card, src, cr);
+	hwwrite(card->mmio, VORTEX_SRC_U1 + (src << 2), b & 0xffff);
+	/* 0   b=0 */
+	hwwrite(card->mmio, VORTEX_SRC_U2 + (src << 2),
+		(tr << 0x11) | (dirplay << 0x10) | (ebp << 0x8) | esp10);
+	/* 0x30f00 e=g=1 esp10=0 ebp=f */
+	//printk(KERN_INFO "vortex: SRC %d, d=0x%x, esi=0x%x, esp10=0x%x, ebp=0x%x\n", src, d, esi, esp10, ebp);
+}
+
+static void vortex_srcblock_init(vortex_t * vortex)
+{
+	u32 addr;
+	int x;
+	hwwrite(vortex->mmio, VORTEX_SRC_SOURCESIZE, 0x1ff);
+	/*
+	   for (x=0; x<0x10; x++) {
+	   vortex_src_init(&vortex_src[x], x);
+	   }
+	 */
+	//addr = 0xcc3c;
+	//addr = 0x26c3c;
+	addr = VORTEX_SRC_RTBASE + 0x3c;
+	for (x = 0xf; x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, 0);
+		addr -= 4;
+	}
+	//addr = 0xcc94;
+	//addr = 0x26c94;
+	addr = VORTEX_SRC_CHNBASE + 0x54;
+	for (x = 0x15; x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, 0);
+		addr -= 4;
+	}
+}
+
+static int
+vortex_src_addWTD(vortex_t * vortex, unsigned char src, unsigned char ch)
+{
+	int temp, lifeboat = 0, prev;
+	// esp13 = src
+
+	temp = hwread(vortex->mmio, VORTEX_SRCBLOCK_SR);
+	if ((temp & (1 << ch)) == 0) {
+		hwwrite(vortex->mmio, VORTEX_SRC_CHNBASE + (ch << 2), src);
+		vortex_src_en_sr(vortex, ch);
+		return 1;
+	}
+	prev = VORTEX_SRC_CHNBASE + (ch << 2);	/*ebp */
+	temp = hwread(vortex->mmio, prev);
+	//while (temp & NR_SRC) {
+	while (temp & 0x10) {
+		prev = VORTEX_SRC_RTBASE + ((temp & 0xf) << 2);	/*esp12 */
+		//prev = VORTEX_SRC_RTBASE + ((temp & (NR_SRC-1)) << 2); /*esp12*/
+		temp = hwread(vortex->mmio, prev);
+		//printk(KERN_INFO "vortex: srcAddWTD: while addr=%x, val=%x\n", prev, temp);
+		if ((++lifeboat) > 0xf) {
+			printk(KERN_ERR
+			       "vortex_src_addWTD: lifeboat overflow\n");
+			return 0;
+		}
+	}
+	hwwrite(vortex->mmio, VORTEX_SRC_RTBASE + ((temp & 0xf) << 2), src);
+	//hwwrite(vortex->mmio, prev, (temp & (NR_SRC-1)) | NR_SRC);
+	hwwrite(vortex->mmio, prev, (temp & 0xf) | 0x10);
+	return 1;
+}
+
+static int
+vortex_src_delWTD(vortex_t * vortex, unsigned char src, unsigned char ch)
+{
+	int esp14 = -1, esp18, eax, ebx, edx, ebp, esi = 0;
+	//int esp1f=edi(while)=src, esp10=ch;
+
+	eax = hwread(vortex->mmio, VORTEX_SRCBLOCK_SR);
+	if (((1 << ch) & eax) == 0) {
+		printk(KERN_ERR "src alarm\n");
+		return 0;
+	}
+	ebp = VORTEX_SRC_CHNBASE + (ch << 2);
+	esp18 = hwread(vortex->mmio, ebp);
+	if (esp18 & 0x10) {
+		ebx = (esp18 & 0xf);
+		if (src == ebx) {
+			ebx = VORTEX_SRC_RTBASE + (src << 2);
+			edx = hwread(vortex->mmio, ebx);
+			//7b60
+			hwwrite(vortex->mmio, ebp, edx);
+			hwwrite(vortex->mmio, ebx, 0);
+		} else {
+			//7ad3
+			edx =
+			    hwread(vortex->mmio,
+				   VORTEX_SRC_RTBASE + (ebx << 2));
+			//printk(KERN_INFO "vortex: srcdelWTD: 1 addr=%x, val=%x, src=%x\n", ebx, edx, src);
+			while ((edx & 0xf) != src) {
+				if ((esi) > 0xf) {
+					printk
+					    ("vortex: srcdelWTD: error, lifeboat overflow\n");
+					return 0;
+				}
+				esp14 = ebx;
+				ebx = edx & 0xf;
+				ebp = ebx << 2;
+				edx =
+				    hwread(vortex->mmio,
+					   VORTEX_SRC_RTBASE + ebp);
+				//printk(KERN_INFO "vortex: srcdelWTD: while addr=%x, val=%x\n", ebp, edx);
+				esi++;
+			}
+			//7b30
+			ebp = ebx << 2;
+			if (edx & 0x10) {	/* Delete entry in between others */
+				ebx = VORTEX_SRC_RTBASE + ((edx & 0xf) << 2);
+				edx = hwread(vortex->mmio, ebx);
+				//7b60
+				hwwrite(vortex->mmio,
+					VORTEX_SRC_RTBASE + ebp, edx);
+				hwwrite(vortex->mmio, ebx, 0);
+				//printk(KERN_INFO "vortex srcdelWTD between addr= 0x%x, val= 0x%x\n", ebp, edx);
+			} else {	/* Delete last entry */
+				//7b83
+				if (esp14 == -1)
+					hwwrite(vortex->mmio,
+						VORTEX_SRC_CHNBASE +
+						(ch << 2), esp18 & 0xef);
+				else {
+					ebx = (0xffffffe0 & edx) | (0xf & ebx);
+					hwwrite(vortex->mmio,
+						VORTEX_SRC_RTBASE +
+						(esp14 << 2), ebx);
+					//printk(KERN_INFO"vortex srcdelWTD last addr= 0x%x, val= 0x%x\n", esp14, ebx);
+				}
+				hwwrite(vortex->mmio,
+					VORTEX_SRC_RTBASE + ebp, 0);
+				return 1;
+			}
+		}
+	} else {
+		//7be0
+		vortex_src_dis_sr(vortex, ch);
+		hwwrite(vortex->mmio, ebp, 0);
+	}
+	return 1;
+}
+
+ /*FIFO*/ 
+
+static void
+vortex_fifo_clearadbdata(vortex_t * vortex, int fifo, int x)
+{
+	for (x--; x >= 0; x--)
+		hwwrite(vortex->mmio,
+			VORTEX_FIFO_ADBDATA +
+			(((fifo << FIFO_SIZE_BITS) + x) << 2), 0);
+}
+
+#if 0
+static void vortex_fifo_adbinitialize(vortex_t * vortex, int fifo, int j)
+{
+	vortex_fifo_clearadbdata(vortex, fifo, FIFO_SIZE);
+#ifdef CHIP_AU8820
+	hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2),
+		(FIFO_U1 | ((j & FIFO_MASK) << 0xb)));
+#else
+	hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2),
+		(FIFO_U1 | ((j & FIFO_MASK) << 0xc)));
+#endif
+}
+#endif
+static void vortex_fifo_setadbvalid(vortex_t * vortex, int fifo, int en)
+{
+	hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2),
+		(hwread(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2)) &
+		 0xffffffef) | ((1 & en) << 4) | FIFO_U1);
+}
+
+static void
+vortex_fifo_setadbctrl(vortex_t * vortex, int fifo, int b, int priority,
+		       int empty, int valid, int f)
+{
+	int temp, lifeboat = 0;
+	//int this_8[NR_ADB] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; /* position */
+	int this_4 = 0x2;
+	/* f seems priority related.
+	 * CAsp4AdbDma::SetPriority is the only place that calls SetAdbCtrl with f set to 1
+	 * every where else it is set to 0. It seems, however, that CAsp4AdbDma::SetPriority
+	 * is never called, thus the f related bits remain a mystery for now.
+	 */
+	do {
+		temp = hwread(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2));
+		if (lifeboat++ > 0xbb8) {
+			printk(KERN_ERR
+			       "Vortex: vortex_fifo_setadbctrl fail\n");
+			break;
+		}
+	}
+	while (temp & FIFO_RDONLY);
+
+	// AU8830 semes to take some special care about fifo content (data).
+	// But i'm just to lazy to translate that :)
+	if (valid) {
+		if ((temp & FIFO_VALID) == 0) {
+			//this_8[fifo] = 0;
+			vortex_fifo_clearadbdata(vortex, fifo, FIFO_SIZE);	// this_4
+#ifdef CHIP_AU8820
+			temp = (this_4 & 0x1f) << 0xb;
+#else
+			temp = (this_4 & 0x3f) << 0xc;
+#endif
+			temp = (temp & 0xfffffffd) | ((b & 1) << 1);
+			temp = (temp & 0xfffffff3) | ((priority & 3) << 2);
+			temp = (temp & 0xffffffef) | ((valid & 1) << 4);
+			temp |= FIFO_U1;
+			temp = (temp & 0xffffffdf) | ((empty & 1) << 5);
+#ifdef CHIP_AU8820
+			temp = (temp & 0xfffbffff) | ((f & 1) << 0x12);
+#endif
+#ifdef CHIP_AU8830
+			temp = (temp & 0xf7ffffff) | ((f & 1) << 0x1b);
+			temp = (temp & 0xefffffff) | ((f & 1) << 0x1c);
+#endif
+#ifdef CHIP_AU8810
+			temp = (temp & 0xfeffffff) | ((f & 1) << 0x18);
+			temp = (temp & 0xfdffffff) | ((f & 1) << 0x19);
+#endif
+		}
+	} else {
+		if (temp & FIFO_VALID) {
+#ifdef CHIP_AU8820
+			temp = ((f & 1) << 0x12) | (temp & 0xfffbffef);
+#endif
+#ifdef CHIP_AU8830
+			temp =
+			    ((f & 1) << 0x1b) | (temp & 0xe7ffffef) | FIFO_BITS;
+#endif
+#ifdef CHIP_AU8810
+			temp =
+			    ((f & 1) << 0x18) | (temp & 0xfcffffef) | FIFO_BITS;
+#endif
+		} else
+			/*if (this_8[fifo]) */
+			vortex_fifo_clearadbdata(vortex, fifo, FIFO_SIZE);
+	}
+	hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2), temp);
+	hwread(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2));
+}
+
+#ifndef CHIP_AU8810
+static void vortex_fifo_clearwtdata(vortex_t * vortex, int fifo, int x)
+{
+	if (x < 1)
+		return;
+	for (x--; x >= 0; x--)
+		hwwrite(vortex->mmio,
+			VORTEX_FIFO_WTDATA +
+			(((fifo << FIFO_SIZE_BITS) + x) << 2), 0);
+}
+
+static void vortex_fifo_wtinitialize(vortex_t * vortex, int fifo, int j)
+{
+	vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE);
+#ifdef CHIP_AU8820
+	hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2),
+		(FIFO_U1 | ((j & FIFO_MASK) << 0xb)));
+#else
+	hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2),
+		(FIFO_U1 | ((j & FIFO_MASK) << 0xc)));
+#endif
+}
+
+static void vortex_fifo_setwtvalid(vortex_t * vortex, int fifo, int en)
+{
+	hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2),
+		(hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2)) &
+		 0xffffffef) | ((en & 1) << 4) | FIFO_U1);
+}
+
+static void
+vortex_fifo_setwtctrl(vortex_t * vortex, int fifo, int ctrl, int priority,
+		      int empty, int valid, int f)
+{
+	int temp = 0, lifeboat = 0;
+	int this_4 = 2;
+
+	do {
+		temp = hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2));
+		if (lifeboat++ > 0xbb8) {
+			printk(KERN_ERR "Vortex: vortex_fifo_setwtctrl fail\n");
+			break;
+		}
+	}
+	while (temp & FIFO_RDONLY);
+
+	if (valid) {
+		if ((temp & FIFO_VALID) == 0) {
+			vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE);	// this_4
+#ifdef CHIP_AU8820
+			temp = (this_4 & 0x1f) << 0xb;
+#else
+			temp = (this_4 & 0x3f) << 0xc;
+#endif
+			temp = (temp & 0xfffffffd) | ((ctrl & 1) << 1);
+			temp = (temp & 0xfffffff3) | ((priority & 3) << 2);
+			temp = (temp & 0xffffffef) | ((valid & 1) << 4);
+			temp |= FIFO_U1;
+			temp = (temp & 0xffffffdf) | ((empty & 1) << 5);
+#ifdef CHIP_AU8820
+			temp = (temp & 0xfffbffff) | ((f & 1) << 0x12);
+#endif
+#ifdef CHIP_AU8830
+			temp = (temp & 0xf7ffffff) | ((f & 1) << 0x1b);
+			temp = (temp & 0xefffffff) | ((f & 1) << 0x1c);
+#endif
+#ifdef CHIP_AU8810
+			temp = (temp & 0xfeffffff) | ((f & 1) << 0x18);
+			temp = (temp & 0xfdffffff) | ((f & 1) << 0x19);
+#endif
+		}
+	} else {
+		if (temp & FIFO_VALID) {
+#ifdef CHIP_AU8820
+			temp = ((f & 1) << 0x12) | (temp & 0xfffbffef);
+#endif
+#ifdef CHIP_AU8830
+			temp =
+			    ((f & 1) << 0x1b) | (temp & 0xe7ffffef) | FIFO_BITS;
+#endif
+#ifdef CHIP_AU8810
+			temp =
+			    ((f & 1) << 0x18) | (temp & 0xfcffffef) | FIFO_BITS;
+#endif
+		} else
+			/*if (this_8[fifo]) */
+			vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE);
+	}
+	hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp);
+	hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2));
+
+/*	
+    do {
+		temp = hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2));
+		if (lifeboat++ > 0xbb8) {
+			printk(KERN_ERR "Vortex: vortex_fifo_setwtctrl fail (hanging)\n");
+			break;
+		}
+    } while ((temp & FIFO_RDONLY)&&(temp & FIFO_VALID)&&(temp != 0xFFFFFFFF));
+	
+	
+	if (valid) {
+		if (temp & FIFO_VALID) {
+			temp = 0x40000;
+			//temp |= 0x08000000;
+			//temp |= 0x10000000;
+			//temp |= 0x04000000;
+			//temp |= 0x00400000;
+			temp |= 0x1c400000;
+			temp &= 0xFFFFFFF3;
+			temp &= 0xFFFFFFEF;
+			temp |= (valid & 1) << 4;
+			hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp);
+			return;
+		} else {
+			vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE);
+			return;
+		}
+	} else {
+		temp &= 0xffffffef;
+		temp |= 0x08000000;
+		temp |= 0x10000000;
+		temp |= 0x04000000;
+		temp |= 0x00400000;
+		hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp);
+		temp = hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2));
+		//((temp >> 6) & 0x3f) 
+		
+		priority = 0;
+		if (((temp & 0x0fc0) ^ ((temp >> 6) & 0x0fc0)) & 0FFFFFFC0)
+			vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE);
+		valid = 0xfb;
+		temp = (temp & 0xfffffffd) | ((ctrl & 1) << 1);
+		temp = (temp & 0xfffdffff) | ((f & 1) << 0x11);
+		temp = (temp & 0xfffffff3) | ((priority & 3) << 2);
+		temp = (temp & 0xffffffef) | ((valid & 1) << 4);
+		temp = (temp & 0xffffffdf) | ((empty & 1) << 5);
+		hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp);
+	}
+	
+	*/
+
+	/*
+	   temp = (temp & 0xfffffffd) | ((ctrl & 1) << 1);
+	   temp = (temp & 0xfffdffff) | ((f & 1) << 0x11);
+	   temp = (temp & 0xfffffff3) | ((priority & 3) << 2);
+	   temp = (temp & 0xffffffef) | ((valid & 1) << 4);
+	   temp = (temp & 0xffffffdf) | ((empty & 1) << 5);
+	   #ifdef FIFO_BITS
+	   temp = temp | FIFO_BITS | 40000;
+	   #endif
+	   // 0x1c440010, 0x1c400000
+	   hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp);
+	 */
+}
+
+#endif
+static void vortex_fifo_init(vortex_t * vortex)
+{
+	int x;
+	u32 addr;
+
+	/* ADB DMA channels fifos. */
+	addr = VORTEX_FIFO_ADBCTRL + ((NR_ADB - 1) * 4);
+	for (x = NR_ADB - 1; x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, (FIFO_U0 | FIFO_U1));
+		if (hwread(vortex->mmio, addr) != (FIFO_U0 | FIFO_U1))
+			printk(KERN_ERR "bad adb fifo reset!");
+		vortex_fifo_clearadbdata(vortex, x, FIFO_SIZE);
+		addr -= 4;
+	}
+
+#ifndef CHIP_AU8810
+	/* WT DMA channels fifos. */
+	addr = VORTEX_FIFO_WTCTRL + ((NR_WT - 1) * 4);
+	for (x = NR_WT - 1; x >= 0; x--) {
+		hwwrite(vortex->mmio, addr, FIFO_U0);
+		if (hwread(vortex->mmio, addr) != FIFO_U0)
+			printk(KERN_ERR
+			       "bad wt fifo reset (0x%08x, 0x%08x)!\n",
+			       addr, hwread(vortex->mmio, addr));
+		vortex_fifo_clearwtdata(vortex, x, FIFO_SIZE);
+		addr -= 4;
+	}
+#endif
+	/* trigger... */
+#ifdef CHIP_AU8820
+	hwwrite(vortex->mmio, 0xf8c0, 0xd03);	//0x0843 0xd6b
+#else
+#ifdef CHIP_AU8830
+	hwwrite(vortex->mmio, 0x17000, 0x61);	/* wt a */
+	hwwrite(vortex->mmio, 0x17004, 0x61);	/* wt b */
+#endif
+	hwwrite(vortex->mmio, 0x17008, 0x61);	/* adb */
+#endif
+}
+
+/* ADBDMA */
+
+static void vortex_adbdma_init(vortex_t * vortex)
+{
+}
+
+static void vortex_adbdma_setfirstbuffer(vortex_t * vortex, int adbdma)
+{
+	stream_t *dma = &vortex->dma_adb[adbdma];
+
+	hwwrite(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2),
+		dma->dma_ctrl);
+}
+
+static void vortex_adbdma_setstartbuffer(vortex_t * vortex, int adbdma, int sb)
+{
+	stream_t *dma = &vortex->dma_adb[adbdma];
+	//hwwrite(vortex->mmio, VORTEX_ADBDMA_START + (adbdma << 2), sb << (((NR_ADB-1)-((adbdma&0xf)*2))));
+	hwwrite(vortex->mmio, VORTEX_ADBDMA_START + (adbdma << 2),
+		sb << ((0xf - (adbdma & 0xf)) * 2));
+	dma->period_real = dma->period_virt = sb;
+}
+
+static void
+vortex_adbdma_setbuffers(vortex_t * vortex, int adbdma,
+			 int psize, int count)
+{
+	stream_t *dma = &vortex->dma_adb[adbdma];
+
+	dma->period_bytes = psize;
+	dma->nr_periods = count;
+
+	dma->cfg0 = 0;
+	dma->cfg1 = 0;
+	switch (count) {
+		/* Four or more pages */
+	default:
+	case 4:
+		dma->cfg1 |= 0x88000000 | 0x44000000 | 0x30000000 | (psize - 1);
+		hwwrite(vortex->mmio,
+			VORTEX_ADBDMA_BUFBASE + (adbdma << 4) + 0xc,
+			snd_pcm_sgbuf_get_addr(dma->substream, psize * 3));
+		/* 3 pages */
+	case 3:
+		dma->cfg0 |= 0x12000000;
+		dma->cfg1 |= 0x80000000 | 0x40000000 | ((psize - 1) << 0xc);
+		hwwrite(vortex->mmio,
+			VORTEX_ADBDMA_BUFBASE + (adbdma << 4) + 0x8,
+			snd_pcm_sgbuf_get_addr(dma->substream, psize * 2));
+		/* 2 pages */
+	case 2:
+		dma->cfg0 |= 0x88000000 | 0x44000000 | 0x10000000 | (psize - 1);
+		hwwrite(vortex->mmio,
+			VORTEX_ADBDMA_BUFBASE + (adbdma << 4) + 0x4,
+			snd_pcm_sgbuf_get_addr(dma->substream, psize));
+		/* 1 page */
+	case 1:
+		dma->cfg0 |= 0x80000000 | 0x40000000 | ((psize - 1) << 0xc);
+		hwwrite(vortex->mmio,
+			VORTEX_ADBDMA_BUFBASE + (adbdma << 4),
+			snd_pcm_sgbuf_get_addr(dma->substream, 0));
+		break;
+	}
+	/*
+	printk(KERN_DEBUG "vortex: cfg0 = 0x%x\nvortex: cfg1=0x%x\n",
+	       dma->cfg0, dma->cfg1);
+	*/
+	hwwrite(vortex->mmio, VORTEX_ADBDMA_BUFCFG0 + (adbdma << 3), dma->cfg0);
+	hwwrite(vortex->mmio, VORTEX_ADBDMA_BUFCFG1 + (adbdma << 3), dma->cfg1);
+
+	vortex_adbdma_setfirstbuffer(vortex, adbdma);
+	vortex_adbdma_setstartbuffer(vortex, adbdma, 0);
+}
+
+static void
+vortex_adbdma_setmode(vortex_t * vortex, int adbdma, int ie, int dir,
+		      int fmt, int d, u32 offset)
+{
+	stream_t *dma = &vortex->dma_adb[adbdma];
+
+	dma->dma_unknown = d;
+	dma->dma_ctrl =
+	    ((offset & OFFSET_MASK) | (dma->dma_ctrl & ~OFFSET_MASK));
+	/* Enable PCMOUT interrupts. */
+	dma->dma_ctrl =
+	    (dma->dma_ctrl & ~IE_MASK) | ((ie << IE_SHIFT) & IE_MASK);
+
+	dma->dma_ctrl =
+	    (dma->dma_ctrl & ~DIR_MASK) | ((dir << DIR_SHIFT) & DIR_MASK);
+	dma->dma_ctrl =
+	    (dma->dma_ctrl & ~FMT_MASK) | ((fmt << FMT_SHIFT) & FMT_MASK);
+
+	hwwrite(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2),
+		dma->dma_ctrl);
+	hwread(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2));
+}
+
+static int vortex_adbdma_bufshift(vortex_t * vortex, int adbdma)
+{
+	stream_t *dma = &vortex->dma_adb[adbdma];
+	int page, p, pp, delta, i;
+
+	page =
+	    (hwread(vortex->mmio, VORTEX_ADBDMA_STAT + (adbdma << 2)) &
+	     ADB_SUBBUF_MASK) >> ADB_SUBBUF_SHIFT;
+	if (dma->nr_periods >= 4)
+		delta = (page - dma->period_real) & 3;
+	else {
+		delta = (page - dma->period_real);
+		if (delta < 0)
+			delta += dma->nr_periods;
+	}
+	if (delta == 0)
+		return 0;
+
+	/* refresh hw page table */
+	if (dma->nr_periods > 4) {
+		for (i = 0; i < delta; i++) {
+			/* p: audio buffer page index */
+			p = dma->period_virt + i + 4;
+			if (p >= dma->nr_periods)
+				p -= dma->nr_periods;
+			/* pp: hardware DMA page index. */
+			pp = dma->period_real + i;
+			if (pp >= 4)
+				pp -= 4;
+			//hwwrite(vortex->mmio, VORTEX_ADBDMA_BUFBASE+(((adbdma << 2)+pp) << 2), dma->table[p].addr);
+			hwwrite(vortex->mmio,
+				VORTEX_ADBDMA_BUFBASE + (((adbdma << 2) + pp) << 2),
+				snd_pcm_sgbuf_get_addr(dma->substream,
+				dma->period_bytes * p));
+			/* Force write thru cache. */
+			hwread(vortex->mmio, VORTEX_ADBDMA_BUFBASE +
+			       (((adbdma << 2) + pp) << 2));
+		}
+	}
+	dma->period_virt += delta;
+	dma->period_real = page;
+	if (dma->period_virt >= dma->nr_periods)
+		dma->period_virt -= dma->nr_periods;
+	if (delta != 1)
+		printk(KERN_INFO "vortex: %d virt=%d, real=%d, delta=%d\n",
+		       adbdma, dma->period_virt, dma->period_real, delta);
+
+	return delta;
+}
+
+
+static void vortex_adbdma_resetup(vortex_t *vortex, int adbdma) {
+	stream_t *dma = &vortex->dma_adb[adbdma];
+	int p, pp, i;
+
+	/* refresh hw page table */
+	for (i=0 ; i < 4 && i < dma->nr_periods; i++) {
+		/* p: audio buffer page index */
+		p = dma->period_virt + i;
+		if (p >= dma->nr_periods)
+			p -= dma->nr_periods;
+		/* pp: hardware DMA page index. */
+		pp = dma->period_real + i;
+		if (dma->nr_periods < 4) {
+			if (pp >= dma->nr_periods)
+				pp -= dma->nr_periods;
+		}
+		else {
+			if (pp >= 4)
+				pp -= 4;
+		}
+		hwwrite(vortex->mmio,
+			VORTEX_ADBDMA_BUFBASE + (((adbdma << 2) + pp) << 2),
+			snd_pcm_sgbuf_get_addr(dma->substream,
+					       dma->period_bytes * p));
+		/* Force write thru cache. */
+		hwread(vortex->mmio, VORTEX_ADBDMA_BUFBASE + (((adbdma << 2)+pp) << 2));
+	}
+}
+
+static int inline vortex_adbdma_getlinearpos(vortex_t * vortex, int adbdma)
+{
+	stream_t *dma = &vortex->dma_adb[adbdma];
+	int temp;
+
+	temp = hwread(vortex->mmio, VORTEX_ADBDMA_STAT + (adbdma << 2));
+	temp = (dma->period_virt * dma->period_bytes) + (temp & (dma->period_bytes - 1));
+	return temp;
+}
+
+static void vortex_adbdma_startfifo(vortex_t * vortex, int adbdma)
+{
+	int this_8 = 0 /*empty */ , this_4 = 0 /*priority */ ;
+	stream_t *dma = &vortex->dma_adb[adbdma];
+
+	switch (dma->fifo_status) {
+	case FIFO_START:
+		vortex_fifo_setadbvalid(vortex, adbdma,
+					dma->fifo_enabled ? 1 : 0);
+		break;
+	case FIFO_STOP:
+		this_8 = 1;
+		hwwrite(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2),
+			dma->dma_ctrl);
+		vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown,
+				       this_4, this_8,
+				       dma->fifo_enabled ? 1 : 0, 0);
+		break;
+	case FIFO_PAUSE:
+		vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown,
+				       this_4, this_8,
+				       dma->fifo_enabled ? 1 : 0, 0);
+		break;
+	}
+	dma->fifo_status = FIFO_START;
+}
+
+static void vortex_adbdma_resumefifo(vortex_t * vortex, int adbdma)
+{
+	stream_t *dma = &vortex->dma_adb[adbdma];
+
+	int this_8 = 1, this_4 = 0;
+	switch (dma->fifo_status) {
+	case FIFO_STOP:
+		hwwrite(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2),
+			dma->dma_ctrl);
+		vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown,
+				       this_4, this_8,
+				       dma->fifo_enabled ? 1 : 0, 0);
+		break;
+	case FIFO_PAUSE:
+		vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown,
+				       this_4, this_8,
+				       dma->fifo_enabled ? 1 : 0, 0);
+		break;
+	}
+	dma->fifo_status = FIFO_START;
+}
+
+static void vortex_adbdma_pausefifo(vortex_t * vortex, int adbdma)
+{
+	stream_t *dma = &vortex->dma_adb[adbdma];
+
+	int this_8 = 0, this_4 = 0;
+	switch (dma->fifo_status) {
+	case FIFO_START:
+		vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown,
+				       this_4, this_8, 0, 0);
+		break;
+	case FIFO_STOP:
+		hwwrite(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2),
+			dma->dma_ctrl);
+		vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown,
+				       this_4, this_8, 0, 0);
+		break;
+	}
+	dma->fifo_status = FIFO_PAUSE;
+}
+
+#if 0				// Using pause instead
+static void vortex_adbdma_stopfifo(vortex_t * vortex, int adbdma)
+{
+	stream_t *dma = &vortex->dma_adb[adbdma];
+
+	int this_4 = 0, this_8 = 0;
+	if (dma->fifo_status == FIFO_START)
+		vortex_fifo_setadbctrl(vortex, adbdma, dma->dma_unknown,
+				       this_4, this_8, 0, 0);
+	else if (dma->fifo_status == FIFO_STOP)
+		return;
+	dma->fifo_status = FIFO_STOP;
+	dma->fifo_enabled = 0;
+}
+
+#endif
+/* WTDMA */
+
+#ifndef CHIP_AU8810
+static void vortex_wtdma_setfirstbuffer(vortex_t * vortex, int wtdma)
+{
+	//int this_7c=dma_ctrl;
+	stream_t *dma = &vortex->dma_wt[wtdma];
+
+	hwwrite(vortex->mmio, VORTEX_WTDMA_CTRL + (wtdma << 2), dma->dma_ctrl);
+}
+
+static void vortex_wtdma_setstartbuffer(vortex_t * vortex, int wtdma, int sb)
+{
+	stream_t *dma = &vortex->dma_wt[wtdma];
+	//hwwrite(vortex->mmio, VORTEX_WTDMA_START + (wtdma << 2), sb << ((0x1f-(wtdma&0xf)*2)));
+	hwwrite(vortex->mmio, VORTEX_WTDMA_START + (wtdma << 2),
+		sb << ((0xf - (wtdma & 0xf)) * 2));
+	dma->period_real = dma->period_virt = sb;
+}
+
+static void
+vortex_wtdma_setbuffers(vortex_t * vortex, int wtdma,
+			int psize, int count)
+{
+	stream_t *dma = &vortex->dma_wt[wtdma];
+
+	dma->period_bytes = psize;
+	dma->nr_periods = count;
+
+	dma->cfg0 = 0;
+	dma->cfg1 = 0;
+	switch (count) {
+		/* Four or more pages */
+	default:
+	case 4:
+		dma->cfg1 |= 0x88000000 | 0x44000000 | 0x30000000 | (psize-1);
+		hwwrite(vortex->mmio, VORTEX_WTDMA_BUFBASE + (wtdma << 4) + 0xc,
+			snd_pcm_sgbuf_get_addr(dma->substream, psize * 3));
+		/* 3 pages */
+	case 3:
+		dma->cfg0 |= 0x12000000;
+		dma->cfg1 |= 0x80000000 | 0x40000000 | ((psize-1) << 0xc);
+		hwwrite(vortex->mmio, VORTEX_WTDMA_BUFBASE + (wtdma << 4)  + 0x8,
+			snd_pcm_sgbuf_get_addr(dma->substream, psize * 2));
+		/* 2 pages */
+	case 2:
+		dma->cfg0 |= 0x88000000 | 0x44000000 | 0x10000000 | (psize-1);
+		hwwrite(vortex->mmio, VORTEX_WTDMA_BUFBASE + (wtdma << 4) + 0x4,
+			snd_pcm_sgbuf_get_addr(dma->substream, psize));
+		/* 1 page */
+	case 1:
+		dma->cfg0 |= 0x80000000 | 0x40000000 | ((psize-1) << 0xc);
+		hwwrite(vortex->mmio, VORTEX_WTDMA_BUFBASE + (wtdma << 4),
+			snd_pcm_sgbuf_get_addr(dma->substream, 0));
+		break;
+	}
+	hwwrite(vortex->mmio, VORTEX_WTDMA_BUFCFG0 + (wtdma << 3), dma->cfg0);
+	hwwrite(vortex->mmio, VORTEX_WTDMA_BUFCFG1 + (wtdma << 3), dma->cfg1);
+
+	vortex_wtdma_setfirstbuffer(vortex, wtdma);
+	vortex_wtdma_setstartbuffer(vortex, wtdma, 0);
+}
+
+static void
+vortex_wtdma_setmode(vortex_t * vortex, int wtdma, int ie, int fmt, int d,
+		     /*int e, */ u32 offset)
+{
+	stream_t *dma = &vortex->dma_wt[wtdma];
+
+	//dma->this_08 = e;
+	dma->dma_unknown = d;
+	dma->dma_ctrl = 0;
+	dma->dma_ctrl =
+	    ((offset & OFFSET_MASK) | (dma->dma_ctrl & ~OFFSET_MASK));
+	/* PCMOUT interrupt */
+	dma->dma_ctrl =
+	    (dma->dma_ctrl & ~IE_MASK) | ((ie << IE_SHIFT) & IE_MASK);
+	/* Always playback. */
+	dma->dma_ctrl |= (1 << DIR_SHIFT);
+	/* Audio Format */
+	dma->dma_ctrl =
+	    (dma->dma_ctrl & FMT_MASK) | ((fmt << FMT_SHIFT) & FMT_MASK);
+	/* Write into hardware */
+	hwwrite(vortex->mmio, VORTEX_WTDMA_CTRL + (wtdma << 2), dma->dma_ctrl);
+}
+
+static int vortex_wtdma_bufshift(vortex_t * vortex, int wtdma)
+{
+	stream_t *dma = &vortex->dma_wt[wtdma];
+	int page, p, pp, delta, i;
+
+	page =
+	    (hwread(vortex->mmio, VORTEX_WTDMA_STAT + (wtdma << 2)) &
+	     WT_SUBBUF_MASK)
+	    >> WT_SUBBUF_SHIFT;
+	if (dma->nr_periods >= 4)
+		delta = (page - dma->period_real) & 3;
+	else {
+		delta = (page - dma->period_real);
+		if (delta < 0)
+			delta += dma->nr_periods;
+	}
+	if (delta == 0)
+		return 0;
+
+	/* refresh hw page table */
+	if (dma->nr_periods > 4) {
+		for (i = 0; i < delta; i++) {
+			/* p: audio buffer page index */
+			p = dma->period_virt + i + 4;
+			if (p >= dma->nr_periods)
+				p -= dma->nr_periods;
+			/* pp: hardware DMA page index. */
+			pp = dma->period_real + i;
+			if (pp >= 4)
+				pp -= 4;
+			hwwrite(vortex->mmio,
+				VORTEX_WTDMA_BUFBASE +
+				(((wtdma << 2) + pp) << 2),
+				snd_pcm_sgbuf_get_addr(dma->substream,
+						       dma->period_bytes * p));
+			/* Force write thru cache. */
+			hwread(vortex->mmio, VORTEX_WTDMA_BUFBASE +
+			       (((wtdma << 2) + pp) << 2));
+		}
+	}
+	dma->period_virt += delta;
+	if (dma->period_virt >= dma->nr_periods)
+		dma->period_virt -= dma->nr_periods;
+	dma->period_real = page;
+
+	if (delta != 1)
+		printk(KERN_WARNING "vortex: wt virt = %d, delta = %d\n",
+		       dma->period_virt, delta);
+
+	return delta;
+}
+
+#if 0
+static void
+vortex_wtdma_getposition(vortex_t * vortex, int wtdma, int *subbuf, int *pos)
+{
+	int temp;
+	temp = hwread(vortex->mmio, VORTEX_WTDMA_STAT + (wtdma << 2));
+	*subbuf = (temp >> WT_SUBBUF_SHIFT) & WT_SUBBUF_MASK;
+	*pos = temp & POS_MASK;
+}
+
+static int vortex_wtdma_getcursubuffer(vortex_t * vortex, int wtdma)
+{
+	return ((hwread(vortex->mmio, VORTEX_WTDMA_STAT + (wtdma << 2)) >>
+		 POS_SHIFT) & POS_MASK);
+}
+#endif
+static int inline vortex_wtdma_getlinearpos(vortex_t * vortex, int wtdma)
+{
+	stream_t *dma = &vortex->dma_wt[wtdma];
+	int temp;
+
+	temp = hwread(vortex->mmio, VORTEX_WTDMA_STAT + (wtdma << 2));
+	temp = (dma->period_virt * dma->period_bytes) + (temp & (dma->period_bytes - 1));
+	return temp;
+}
+
+static void vortex_wtdma_startfifo(vortex_t * vortex, int wtdma)
+{
+	stream_t *dma = &vortex->dma_wt[wtdma];
+	int this_8 = 0, this_4 = 0;
+
+	switch (dma->fifo_status) {
+	case FIFO_START:
+		vortex_fifo_setwtvalid(vortex, wtdma,
+				       dma->fifo_enabled ? 1 : 0);
+		break;
+	case FIFO_STOP:
+		this_8 = 1;
+		hwwrite(vortex->mmio, VORTEX_WTDMA_CTRL + (wtdma << 2),
+			dma->dma_ctrl);
+		vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown,
+				      this_4, this_8,
+				      dma->fifo_enabled ? 1 : 0, 0);
+		break;
+	case FIFO_PAUSE:
+		vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown,
+				      this_4, this_8,
+				      dma->fifo_enabled ? 1 : 0, 0);
+		break;
+	}
+	dma->fifo_status = FIFO_START;
+}
+
+static void vortex_wtdma_resumefifo(vortex_t * vortex, int wtdma)
+{
+	stream_t *dma = &vortex->dma_wt[wtdma];
+
+	int this_8 = 0, this_4 = 0;
+	switch (dma->fifo_status) {
+	case FIFO_STOP:
+		hwwrite(vortex->mmio, VORTEX_WTDMA_CTRL + (wtdma << 2),
+			dma->dma_ctrl);
+		vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown,
+				      this_4, this_8,
+				      dma->fifo_enabled ? 1 : 0, 0);
+		break;
+	case FIFO_PAUSE:
+		vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown,
+				      this_4, this_8,
+				      dma->fifo_enabled ? 1 : 0, 0);
+		break;
+	}
+	dma->fifo_status = FIFO_START;
+}
+
+static void vortex_wtdma_pausefifo(vortex_t * vortex, int wtdma)
+{
+	stream_t *dma = &vortex->dma_wt[wtdma];
+
+	int this_8 = 0, this_4 = 0;
+	switch (dma->fifo_status) {
+	case FIFO_START:
+		vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown,
+				      this_4, this_8, 0, 0);
+		break;
+	case FIFO_STOP:
+		hwwrite(vortex->mmio, VORTEX_WTDMA_CTRL + (wtdma << 2),
+			dma->dma_ctrl);
+		vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown,
+				      this_4, this_8, 0, 0);
+		break;
+	}
+	dma->fifo_status = FIFO_PAUSE;
+}
+
+static void vortex_wtdma_stopfifo(vortex_t * vortex, int wtdma)
+{
+	stream_t *dma = &vortex->dma_wt[wtdma];
+
+	int this_4 = 0, this_8 = 0;
+	if (dma->fifo_status == FIFO_START)
+		vortex_fifo_setwtctrl(vortex, wtdma, dma->dma_unknown,
+				      this_4, this_8, 0, 0);
+	else if (dma->fifo_status == FIFO_STOP)
+		return;
+	dma->fifo_status = FIFO_STOP;
+	dma->fifo_enabled = 0;
+}
+
+#endif
+/* ADB Routes */
+
+typedef int ADBRamLink;
+static void vortex_adb_init(vortex_t * vortex)
+{
+	int i;
+	/* it looks like we are writing more than we need to...
+	 * if we write what we are supposed to it breaks things... */
+	hwwrite(vortex->mmio, VORTEX_ADB_SR, 0);
+	for (i = 0; i < VORTEX_ADB_RTBASE_COUNT; i++)
+		hwwrite(vortex->mmio, VORTEX_ADB_RTBASE + (i << 2),
+			hwread(vortex->mmio,
+			       VORTEX_ADB_RTBASE + (i << 2)) | ROUTE_MASK);
+	for (i = 0; i < VORTEX_ADB_CHNBASE_COUNT; i++) {
+		hwwrite(vortex->mmio, VORTEX_ADB_CHNBASE + (i << 2),
+			hwread(vortex->mmio,
+			       VORTEX_ADB_CHNBASE + (i << 2)) | ROUTE_MASK);
+	}
+}
+
+static void vortex_adb_en_sr(vortex_t * vortex, int channel)
+{
+	hwwrite(vortex->mmio, VORTEX_ADB_SR,
+		hwread(vortex->mmio, VORTEX_ADB_SR) | (0x1 << channel));
+}
+
+static void vortex_adb_dis_sr(vortex_t * vortex, int channel)
+{
+	hwwrite(vortex->mmio, VORTEX_ADB_SR,
+		hwread(vortex->mmio, VORTEX_ADB_SR) & ~(0x1 << channel));
+}
+
+static void
+vortex_adb_addroutes(vortex_t * vortex, unsigned char channel,
+		     ADBRamLink * route, int rnum)
+{
+	int temp, prev, lifeboat = 0;
+
+	if ((rnum <= 0) || (route == NULL))
+		return;
+	/* Write last routes. */
+	rnum--;
+	hwwrite(vortex->mmio,
+		VORTEX_ADB_RTBASE + ((route[rnum] & ADB_MASK) << 2),
+		ROUTE_MASK);
+	while (rnum > 0) {
+		hwwrite(vortex->mmio,
+			VORTEX_ADB_RTBASE +
+			((route[rnum - 1] & ADB_MASK) << 2), route[rnum]);
+		rnum--;
+	}
+	/* Write first route. */
+	temp =
+	    hwread(vortex->mmio,
+		   VORTEX_ADB_CHNBASE + (channel << 2)) & ADB_MASK;
+	if (temp == ADB_MASK) {
+		/* First entry on this channel. */
+		hwwrite(vortex->mmio, VORTEX_ADB_CHNBASE + (channel << 2),
+			route[0]);
+		vortex_adb_en_sr(vortex, channel);
+		return;
+	}
+	/* Not first entry on this channel. Need to link. */
+	do {
+		prev = temp;
+		temp =
+		    hwread(vortex->mmio,
+			   VORTEX_ADB_RTBASE + (temp << 2)) & ADB_MASK;
+		if ((lifeboat++) > ADB_MASK) {
+			printk(KERN_ERR
+			       "vortex_adb_addroutes: unending route! 0x%x\n",
+			       *route);
+			return;
+		}
+	}
+	while (temp != ADB_MASK);
+	hwwrite(vortex->mmio, VORTEX_ADB_RTBASE + (prev << 2), route[0]);
+}
+
+static void
+vortex_adb_delroutes(vortex_t * vortex, unsigned char channel,
+		     ADBRamLink route0, ADBRamLink route1)
+{
+	int temp, lifeboat = 0, prev;
+
+	/* Find route. */
+	temp =
+	    hwread(vortex->mmio,
+		   VORTEX_ADB_CHNBASE + (channel << 2)) & ADB_MASK;
+	if (temp == (route0 & ADB_MASK)) {
+		temp =
+		    hwread(vortex->mmio,
+			   VORTEX_ADB_RTBASE + ((route1 & ADB_MASK) << 2));
+		if ((temp & ADB_MASK) == ADB_MASK)
+			vortex_adb_dis_sr(vortex, channel);
+		hwwrite(vortex->mmio, VORTEX_ADB_CHNBASE + (channel << 2),
+			temp);
+		return;
+	}
+	do {
+		prev = temp;
+		temp =
+		    hwread(vortex->mmio,
+			   VORTEX_ADB_RTBASE + (prev << 2)) & ADB_MASK;
+		if (((lifeboat++) > ADB_MASK) || (temp == ADB_MASK)) {
+			printk(KERN_ERR
+			       "vortex_adb_delroutes: route not found! 0x%x\n",
+			       route0);
+			return;
+		}
+	}
+	while (temp != (route0 & ADB_MASK));
+	temp = hwread(vortex->mmio, VORTEX_ADB_RTBASE + (temp << 2));
+	if ((temp & ADB_MASK) == route1)
+		temp = hwread(vortex->mmio, VORTEX_ADB_RTBASE + (temp << 2));
+	/* Make bridge over deleted route. */
+	hwwrite(vortex->mmio, VORTEX_ADB_RTBASE + (prev << 2), temp);
+}
+
+static void
+vortex_route(vortex_t * vortex, int en, unsigned char channel,
+	     unsigned char source, unsigned char dest)
+{
+	ADBRamLink route;
+
+	route = ((source & ADB_MASK) << ADB_SHIFT) | (dest & ADB_MASK);
+	if (en) {
+		vortex_adb_addroutes(vortex, channel, &route, 1);
+		if ((source < (OFFSET_SRCOUT + NR_SRC))
+		    && (source >= OFFSET_SRCOUT))
+			vortex_src_addWTD(vortex, (source - OFFSET_SRCOUT),
+					  channel);
+		else if ((source < (OFFSET_MIXOUT + NR_MIXOUT))
+			 && (source >= OFFSET_MIXOUT))
+			vortex_mixer_addWTD(vortex,
+					    (source - OFFSET_MIXOUT), channel);
+	} else {
+		vortex_adb_delroutes(vortex, channel, route, route);
+		if ((source < (OFFSET_SRCOUT + NR_SRC))
+		    && (source >= OFFSET_SRCOUT))
+			vortex_src_delWTD(vortex, (source - OFFSET_SRCOUT),
+					  channel);
+		else if ((source < (OFFSET_MIXOUT + NR_MIXOUT))
+			 && (source >= OFFSET_MIXOUT))
+			vortex_mixer_delWTD(vortex,
+					    (source - OFFSET_MIXOUT), channel);
+	}
+}
+
+#if 0
+static void
+vortex_routes(vortex_t * vortex, int en, unsigned char channel,
+	      unsigned char source, unsigned char dest0, unsigned char dest1)
+{
+	ADBRamLink route[2];
+
+	route[0] = ((source & ADB_MASK) << ADB_SHIFT) | (dest0 & ADB_MASK);
+	route[1] = ((source & ADB_MASK) << ADB_SHIFT) | (dest1 & ADB_MASK);
+
+	if (en) {
+		vortex_adb_addroutes(vortex, channel, route, 2);
+		if ((source < (OFFSET_SRCOUT + NR_SRC))
+		    && (source >= (OFFSET_SRCOUT)))
+			vortex_src_addWTD(vortex, (source - OFFSET_SRCOUT),
+					  channel);
+		else if ((source < (OFFSET_MIXOUT + NR_MIXOUT))
+			 && (source >= (OFFSET_MIXOUT)))
+			vortex_mixer_addWTD(vortex,
+					    (source - OFFSET_MIXOUT), channel);
+	} else {
+		vortex_adb_delroutes(vortex, channel, route[0], route[1]);
+		if ((source < (OFFSET_SRCOUT + NR_SRC))
+		    && (source >= (OFFSET_SRCOUT)))
+			vortex_src_delWTD(vortex, (source - OFFSET_SRCOUT),
+					  channel);
+		else if ((source < (OFFSET_MIXOUT + NR_MIXOUT))
+			 && (source >= (OFFSET_MIXOUT)))
+			vortex_mixer_delWTD(vortex,
+					    (source - OFFSET_MIXOUT), channel);
+	}
+}
+
+#endif
+/* Route two sources to same target. Sources must be of same class !!! */
+static void
+vortex_routeLRT(vortex_t * vortex, int en, unsigned char ch,
+		unsigned char source0, unsigned char source1,
+		unsigned char dest)
+{
+	ADBRamLink route[2];
+
+	route[0] = ((source0 & ADB_MASK) << ADB_SHIFT) | (dest & ADB_MASK);
+	route[1] = ((source1 & ADB_MASK) << ADB_SHIFT) | (dest & ADB_MASK);
+
+	if (dest < 0x10)
+		route[1] = (route[1] & ~ADB_MASK) | (dest + 0x20);	/* fifo A */
+
+	if (en) {
+		vortex_adb_addroutes(vortex, ch, route, 2);
+		if ((source0 < (OFFSET_SRCOUT + NR_SRC))
+		    && (source0 >= OFFSET_SRCOUT)) {
+			vortex_src_addWTD(vortex,
+					  (source0 - OFFSET_SRCOUT), ch);
+			vortex_src_addWTD(vortex,
+					  (source1 - OFFSET_SRCOUT), ch);
+		} else if ((source0 < (OFFSET_MIXOUT + NR_MIXOUT))
+			   && (source0 >= OFFSET_MIXOUT)) {
+			vortex_mixer_addWTD(vortex,
+					    (source0 - OFFSET_MIXOUT), ch);
+			vortex_mixer_addWTD(vortex,
+					    (source1 - OFFSET_MIXOUT), ch);
+		}
+	} else {
+		vortex_adb_delroutes(vortex, ch, route[0], route[1]);
+		if ((source0 < (OFFSET_SRCOUT + NR_SRC))
+		    && (source0 >= OFFSET_SRCOUT)) {
+			vortex_src_delWTD(vortex,
+					  (source0 - OFFSET_SRCOUT), ch);
+			vortex_src_delWTD(vortex,
+					  (source1 - OFFSET_SRCOUT), ch);
+		} else if ((source0 < (OFFSET_MIXOUT + NR_MIXOUT))
+			   && (source0 >= OFFSET_MIXOUT)) {
+			vortex_mixer_delWTD(vortex,
+					    (source0 - OFFSET_MIXOUT), ch);
+			vortex_mixer_delWTD(vortex,
+					    (source1 - OFFSET_MIXOUT), ch);
+		}
+	}
+}
+
+/* Connection stuff */
+
+// Connect adbdma to src('s).
+static void
+vortex_connection_adbdma_src(vortex_t * vortex, int en, unsigned char ch,
+			     unsigned char adbdma, unsigned char src)
+{
+	vortex_route(vortex, en, ch, ADB_DMA(adbdma), ADB_SRCIN(src));
+}
+
+// Connect SRC to mixin.
+static void
+vortex_connection_src_mixin(vortex_t * vortex, int en,
+			    unsigned char channel, unsigned char src,
+			    unsigned char mixin)
+{
+	vortex_route(vortex, en, channel, ADB_SRCOUT(src), ADB_MIXIN(mixin));
+}
+
+// Connect mixin with mix output.
+static void
+vortex_connection_mixin_mix(vortex_t * vortex, int en, unsigned char mixin,
+			    unsigned char mix, int a)
+{
+	if (en) {
+		vortex_mix_enableinput(vortex, mix, mixin);
+		vortex_mix_setinputvolumebyte(vortex, mix, mixin, MIX_DEFIGAIN);	// added to original code.
+	} else
+		vortex_mix_disableinput(vortex, mix, mixin, a);
+}
+
+// Connect absolut address to mixin.
+static void
+vortex_connection_adb_mixin(vortex_t * vortex, int en,
+			    unsigned char channel, unsigned char source,
+			    unsigned char mixin)
+{
+	vortex_route(vortex, en, channel, source, ADB_MIXIN(mixin));
+}
+
+static void
+vortex_connection_src_adbdma(vortex_t * vortex, int en, unsigned char ch,
+			     unsigned char src, unsigned char adbdma)
+{
+	vortex_route(vortex, en, ch, ADB_SRCOUT(src), ADB_DMA(adbdma));
+}
+
+static void
+vortex_connection_src_src_adbdma(vortex_t * vortex, int en,
+				 unsigned char ch, unsigned char src0,
+				 unsigned char src1, unsigned char adbdma)
+{
+
+	vortex_routeLRT(vortex, en, ch, ADB_SRCOUT(src0), ADB_SRCOUT(src1),
+			ADB_DMA(adbdma));
+}
+
+// mix to absolut address.
+static void
+vortex_connection_mix_adb(vortex_t * vortex, int en, unsigned char ch,
+			  unsigned char mix, unsigned char dest)
+{
+	vortex_route(vortex, en, ch, ADB_MIXOUT(mix), dest);
+	vortex_mix_setvolumebyte(vortex, mix, MIX_DEFOGAIN);	// added to original code.
+}
+
+// mixer to src.
+static void
+vortex_connection_mix_src(vortex_t * vortex, int en, unsigned char ch,
+			  unsigned char mix, unsigned char src)
+{
+	vortex_route(vortex, en, ch, ADB_MIXOUT(mix), ADB_SRCIN(src));
+	vortex_mix_setvolumebyte(vortex, mix, MIX_DEFOGAIN);	// added to original code.
+}
+
+#if 0
+static void
+vortex_connection_adbdma_src_src(vortex_t * vortex, int en,
+				 unsigned char channel,
+				 unsigned char adbdma, unsigned char src0,
+				 unsigned char src1)
+{
+	vortex_routes(vortex, en, channel, ADB_DMA(adbdma),
+		      ADB_SRCIN(src0), ADB_SRCIN(src1));
+}
+
+// Connect two mix to AdbDma.
+static void
+vortex_connection_mix_mix_adbdma(vortex_t * vortex, int en,
+				 unsigned char ch, unsigned char mix0,
+				 unsigned char mix1, unsigned char adbdma)
+{
+
+	ADBRamLink routes[2];
+	routes[0] =
+	    (((mix0 +
+	       OFFSET_MIXOUT) & ADB_MASK) << ADB_SHIFT) | (adbdma & ADB_MASK);
+	routes[1] =
+	    (((mix1 + OFFSET_MIXOUT) & ADB_MASK) << ADB_SHIFT) | ((adbdma +
+								   0x20) &
+								  ADB_MASK);
+	if (en) {
+		vortex_adb_addroutes(vortex, ch, routes, 0x2);
+		vortex_mixer_addWTD(vortex, mix0, ch);
+		vortex_mixer_addWTD(vortex, mix1, ch);
+	} else {
+		vortex_adb_delroutes(vortex, ch, routes[0], routes[1]);
+		vortex_mixer_delWTD(vortex, mix0, ch);
+		vortex_mixer_delWTD(vortex, mix1, ch);
+	}
+}
+#endif
+
+/* CODEC connect. */
+
+static void
+vortex_connect_codecplay(vortex_t * vortex, int en, unsigned char mixers[])
+{
+#ifdef CHIP_AU8820
+	vortex_connection_mix_adb(vortex, en, 0x11, mixers[0], ADB_CODECOUT(0));
+	vortex_connection_mix_adb(vortex, en, 0x11, mixers[1], ADB_CODECOUT(1));
+#else
+#if 1
+	// Connect front channels through EQ.
+	vortex_connection_mix_adb(vortex, en, 0x11, mixers[0], ADB_EQIN(0));
+	vortex_connection_mix_adb(vortex, en, 0x11, mixers[1], ADB_EQIN(1));
+	/* Lower volume, since EQ has some gain. */
+	vortex_mix_setvolumebyte(vortex, mixers[0], 0);
+	vortex_mix_setvolumebyte(vortex, mixers[1], 0);
+	vortex_route(vortex, en, 0x11, ADB_EQOUT(0), ADB_CODECOUT(0));
+	vortex_route(vortex, en, 0x11, ADB_EQOUT(1), ADB_CODECOUT(1));
+
+	/* Check if reg 0x28 has SDAC bit set. */
+	if (VORTEX_IS_QUAD(vortex)) {
+		/* Rear channel. Note: ADB_CODECOUT(0+2) and (1+2) is for AC97 modem */
+		vortex_connection_mix_adb(vortex, en, 0x11, mixers[2],
+					  ADB_CODECOUT(0 + 4));
+		vortex_connection_mix_adb(vortex, en, 0x11, mixers[3],
+					  ADB_CODECOUT(1 + 4));
+		/* printk(KERN_DEBUG "SDAC detected "); */
+	}
+#else
+	// Use plain direct output to codec.
+	vortex_connection_mix_adb(vortex, en, 0x11, mixers[0], ADB_CODECOUT(0));
+	vortex_connection_mix_adb(vortex, en, 0x11, mixers[1], ADB_CODECOUT(1));
+#endif
+#endif
+}
+
+static void
+vortex_connect_codecrec(vortex_t * vortex, int en, unsigned char mixin0,
+			unsigned char mixin1)
+{
+	/*
+	   Enable: 0x1, 0x1
+	   Channel: 0x11, 0x11
+	   ADB Source address: 0x48, 0x49
+	   Destination Asp4Topology_0x9c,0x98
+	 */
+	vortex_connection_adb_mixin(vortex, en, 0x11, ADB_CODECIN(0), mixin0);
+	vortex_connection_adb_mixin(vortex, en, 0x11, ADB_CODECIN(1), mixin1);
+}
+
+// Higher level ADB audio path (de)allocator.
+
+/* Resource manager */
+static int resnum[VORTEX_RESOURCE_LAST] =
+    { NR_ADB, NR_SRC, NR_MIXIN, NR_MIXOUT, NR_A3D };
+/*
+ Checkout/Checkin resource of given type. 
+ resmap: resource map to be used. If NULL means that we want to allocate
+ a DMA resource (root of all other resources of a dma channel).
+ out: Mean checkout if != 0. Else mean Checkin resource.
+ restype: Indicates type of resource to be checked in or out.
+*/
+static char
+vortex_adb_checkinout(vortex_t * vortex, int resmap[], int out, int restype)
+{
+	int i, qty = resnum[restype], resinuse = 0;
+
+	if (out) {
+		/* Gather used resources by all streams. */
+		for (i = 0; i < NR_ADB; i++) {
+			resinuse |= vortex->dma_adb[i].resources[restype];
+		}
+		resinuse |= vortex->fixed_res[restype];
+		/* Find and take free resource. */
+		for (i = 0; i < qty; i++) {
+			if ((resinuse & (1 << i)) == 0) {
+				if (resmap != NULL)
+					resmap[restype] |= (1 << i);
+				else
+					vortex->dma_adb[i].resources[restype] |= (1 << i);
+				/*
+				printk(KERN_DEBUG
+				       "vortex: ResManager: type %d out %d\n",
+				       restype, i);
+				*/
+				return i;
+			}
+		}
+	} else {
+		if (resmap == NULL)
+			return -EINVAL;
+		/* Checkin first resource of type restype. */
+		for (i = 0; i < qty; i++) {
+			if (resmap[restype] & (1 << i)) {
+				resmap[restype] &= ~(1 << i);
+				/*
+				printk(KERN_DEBUG
+				       "vortex: ResManager: type %d in %d\n",
+				       restype, i);
+				*/
+				return i;
+			}
+		}
+	}
+	printk(KERN_ERR "vortex: FATAL: ResManager: resource type %d exhausted.\n", restype);
+	return -ENOMEM;
+}
+
+/* Default Connections  */
+static int
+vortex_adb_allocroute(vortex_t * vortex, int dma, int nr_ch, int dir, int type);
+
+static void vortex_connect_default(vortex_t * vortex, int en)
+{
+	// Connect AC97 codec.
+	vortex->mixplayb[0] = vortex_adb_checkinout(vortex, vortex->fixed_res, en,
+				  VORTEX_RESOURCE_MIXOUT);
+	vortex->mixplayb[1] = vortex_adb_checkinout(vortex, vortex->fixed_res, en,
+				  VORTEX_RESOURCE_MIXOUT);
+	if (VORTEX_IS_QUAD(vortex)) {
+		vortex->mixplayb[2] = vortex_adb_checkinout(vortex, vortex->fixed_res, en,
+					  VORTEX_RESOURCE_MIXOUT);
+		vortex->mixplayb[3] = vortex_adb_checkinout(vortex, vortex->fixed_res, en,
+					  VORTEX_RESOURCE_MIXOUT);
+	}
+	vortex_connect_codecplay(vortex, en, vortex->mixplayb);
+
+	vortex->mixcapt[0] = vortex_adb_checkinout(vortex, vortex->fixed_res, en,
+				  VORTEX_RESOURCE_MIXIN);
+	vortex->mixcapt[1] = vortex_adb_checkinout(vortex, vortex->fixed_res, en,
+				  VORTEX_RESOURCE_MIXIN);
+	vortex_connect_codecrec(vortex, en, MIX_CAPT(0), MIX_CAPT(1));
+
+	// Connect SPDIF
+#ifndef CHIP_AU8820
+	vortex->mixspdif[0] = vortex_adb_checkinout(vortex, vortex->fixed_res, en,
+				  VORTEX_RESOURCE_MIXOUT);
+	vortex->mixspdif[1] = vortex_adb_checkinout(vortex, vortex->fixed_res, en,
+				  VORTEX_RESOURCE_MIXOUT);
+	vortex_connection_mix_adb(vortex, en, 0x14, vortex->mixspdif[0],
+				  ADB_SPDIFOUT(0));
+	vortex_connection_mix_adb(vortex, en, 0x14, vortex->mixspdif[1],
+				  ADB_SPDIFOUT(1));
+#endif
+	// Connect WT
+#ifndef CHIP_AU8810
+	vortex_wt_connect(vortex, en);
+#endif
+	// A3D (crosstalk canceler and A3D slices). AU8810 disabled for now.
+#ifndef CHIP_AU8820
+	vortex_Vort3D_connect(vortex, en);
+#endif
+	// Connect I2S
+
+	// Connect DSP interface for SQ3500 turbo (not here i think...)
+
+	// Connect AC98 modem codec
+	
+}
+
+/*
+  Allocate nr_ch pcm audio routes if dma < 0. If dma >= 0, existing routes
+  are deallocated.
+  dma: DMA engine routes to be deallocated when dma >= 0.
+  nr_ch: Number of channels to be de/allocated.
+  dir: direction of stream. Uses same values as substream->stream.
+  type: Type of audio output/source (codec, spdif, i2s, dsp, etc)
+  Return: Return allocated DMA or same DMA passed as "dma" when dma >= 0.
+*/
+static int
+vortex_adb_allocroute(vortex_t * vortex, int dma, int nr_ch, int dir, int type)
+{
+	stream_t *stream;
+	int i, en;
+	
+	if ((nr_ch == 3)
+	    || ((dir == SNDRV_PCM_STREAM_CAPTURE) && (nr_ch > 2)))
+		return -EBUSY;
+
+	if (dma >= 0) {
+		en = 0;
+		vortex_adb_checkinout(vortex,
+				      vortex->dma_adb[dma].resources, en,
+				      VORTEX_RESOURCE_DMA);
+	} else {
+		en = 1;
+		if ((dma =
+		     vortex_adb_checkinout(vortex, NULL, en,
+					   VORTEX_RESOURCE_DMA)) < 0)
+			return -EBUSY;
+	}
+
+	stream = &vortex->dma_adb[dma];
+	stream->dma = dma;
+	stream->dir = dir;
+	stream->type = type;
+
+	/* PLAYBACK ROUTES. */
+	if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
+		int src[4], mix[4], ch_top;
+#ifndef CHIP_AU8820
+		int a3d = 0;
+#endif
+		/* Get SRC and MIXER hardware resources. */
+		if (stream->type != VORTEX_PCM_SPDIF) {
+			for (i = 0; i < nr_ch; i++) {
+				if ((src[i] = vortex_adb_checkinout(vortex,
+							   stream->resources, en,
+							   VORTEX_RESOURCE_SRC)) < 0) {
+					memset(stream->resources, 0,
+					       sizeof(unsigned char) *
+					       VORTEX_RESOURCE_LAST);
+					return -EBUSY;
+				}
+				if (stream->type != VORTEX_PCM_A3D) {
+					if ((mix[i] = vortex_adb_checkinout(vortex,
+								   stream->resources,
+								   en,
+								   VORTEX_RESOURCE_MIXIN)) < 0) {
+						memset(stream->resources,
+						       0,
+						       sizeof(unsigned char) * VORTEX_RESOURCE_LAST);
+						return -EBUSY;
+					}
+				}
+			}
+		}
+#ifndef CHIP_AU8820
+		if (stream->type == VORTEX_PCM_A3D) {
+			if ((a3d =
+			     vortex_adb_checkinout(vortex,
+						   stream->resources, en,
+						   VORTEX_RESOURCE_A3D)) < 0) {
+				memset(stream->resources, 0,
+				       sizeof(unsigned char) *
+				       VORTEX_RESOURCE_LAST);
+				printk(KERN_ERR "vortex: out of A3D sources. Sorry\n");
+				return -EBUSY;
+			}
+			/* (De)Initialize A3D hardware source. */
+			vortex_Vort3D_InitializeSource(&(vortex->a3d[a3d]), en);
+		}
+		/* Make SPDIF out exclusive to "spdif" device when in use. */
+		if ((stream->type == VORTEX_PCM_SPDIF) && (en)) {
+			vortex_route(vortex, 0, 0x14,
+				     ADB_MIXOUT(vortex->mixspdif[0]),
+				     ADB_SPDIFOUT(0));
+			vortex_route(vortex, 0, 0x14,
+				     ADB_MIXOUT(vortex->mixspdif[1]),
+				     ADB_SPDIFOUT(1));
+		}
+#endif
+		/* Make playback routes. */
+		for (i = 0; i < nr_ch; i++) {
+			if (stream->type == VORTEX_PCM_ADB) {
+				vortex_connection_adbdma_src(vortex, en,
+							     src[nr_ch - 1],
+							     dma,
+							     src[i]);
+				vortex_connection_src_mixin(vortex, en,
+							    0x11, src[i],
+							    mix[i]);
+				vortex_connection_mixin_mix(vortex, en,
+							    mix[i],
+							    MIX_PLAYB(i), 0);
+#ifndef CHIP_AU8820
+				vortex_connection_mixin_mix(vortex, en,
+							    mix[i],
+							    MIX_SPDIF(i % 2), 0);
+				vortex_mix_setinputvolumebyte(vortex,
+							      MIX_SPDIF(i % 2),
+							      mix[i],
+							      MIX_DEFIGAIN);
+#endif
+			}
+#ifndef CHIP_AU8820
+			if (stream->type == VORTEX_PCM_A3D) {
+				vortex_connection_adbdma_src(vortex, en,
+							     src[nr_ch - 1], 
+								 dma,
+							     src[i]);
+				vortex_route(vortex, en, 0x11, ADB_SRCOUT(src[i]), ADB_A3DIN(a3d));
+				/* XTalk test. */
+				//vortex_route(vortex, en, 0x11, dma, ADB_XTALKIN(i?9:4));
+				//vortex_route(vortex, en, 0x11, ADB_SRCOUT(src[i]), ADB_XTALKIN(i?4:9));
+			}
+			if (stream->type == VORTEX_PCM_SPDIF)
+				vortex_route(vortex, en, 0x14,
+					     ADB_DMA(stream->dma),
+					     ADB_SPDIFOUT(i));
+#endif
+		}
+		if (stream->type != VORTEX_PCM_SPDIF && stream->type != VORTEX_PCM_A3D) {
+			ch_top = (VORTEX_IS_QUAD(vortex) ? 4 : 2);
+			for (i = nr_ch; i < ch_top; i++) {
+				vortex_connection_mixin_mix(vortex, en,
+							    mix[i % nr_ch],
+							    MIX_PLAYB(i), 0);
+#ifndef CHIP_AU8820
+				vortex_connection_mixin_mix(vortex, en,
+							    mix[i % nr_ch],
+							    MIX_SPDIF(i % 2),
+								0);
+				vortex_mix_setinputvolumebyte(vortex,
+							      MIX_SPDIF(i % 2),
+							      mix[i % nr_ch],
+							      MIX_DEFIGAIN);
+#endif
+			}
+		}
+#ifndef CHIP_AU8820
+		else {
+			if (nr_ch == 1 && stream->type == VORTEX_PCM_SPDIF)
+				vortex_route(vortex, en, 0x14,
+					     ADB_DMA(stream->dma),
+					     ADB_SPDIFOUT(1));
+		}
+		/* Reconnect SPDIF out when "spdif" device is down. */
+		if ((stream->type == VORTEX_PCM_SPDIF) && (!en)) {
+			vortex_route(vortex, 1, 0x14,
+				     ADB_MIXOUT(vortex->mixspdif[0]),
+				     ADB_SPDIFOUT(0));
+			vortex_route(vortex, 1, 0x14,
+				     ADB_MIXOUT(vortex->mixspdif[1]),
+				     ADB_SPDIFOUT(1));
+		}
+#endif
+	/* CAPTURE ROUTES. */
+	} else {
+		int src[2], mix[2];
+
+		/* Get SRC and MIXER hardware resources. */
+		for (i = 0; i < nr_ch; i++) {
+			if ((mix[i] =
+			     vortex_adb_checkinout(vortex,
+						   stream->resources, en,
+						   VORTEX_RESOURCE_MIXOUT))
+			    < 0) {
+				memset(stream->resources, 0,
+				       sizeof(unsigned char) *
+				       VORTEX_RESOURCE_LAST);
+				return -EBUSY;
+			}
+			if ((src[i] =
+			     vortex_adb_checkinout(vortex,
+						   stream->resources, en,
+						   VORTEX_RESOURCE_SRC)) < 0) {
+				memset(stream->resources, 0,
+				       sizeof(unsigned char) *
+				       VORTEX_RESOURCE_LAST);
+				return -EBUSY;
+			}
+		}
+
+		/* Make capture routes. */
+		vortex_connection_mixin_mix(vortex, en, MIX_CAPT(0), mix[0], 0);
+		vortex_connection_mix_src(vortex, en, 0x11, mix[0], src[0]);
+		if (nr_ch == 1) {
+			vortex_connection_mixin_mix(vortex, en,
+						    MIX_CAPT(1), mix[0], 0);
+			vortex_connection_src_adbdma(vortex, en,
+						     src[0],
+						     src[0], dma);
+		} else {
+			vortex_connection_mixin_mix(vortex, en,
+						    MIX_CAPT(1), mix[1], 0);
+			vortex_connection_mix_src(vortex, en, 0x11, mix[1],
+						  src[1]);
+			vortex_connection_src_src_adbdma(vortex, en,
+							 src[1], src[0],
+							 src[1], dma);
+		}
+	}
+	vortex->dma_adb[dma].nr_ch = nr_ch;
+
+#if 0
+	/* AC97 Codec channel setup. FIXME: this has no effect on some cards !! */
+	if (nr_ch < 4) {
+		/* Copy stereo to rear channel (surround) */
+		snd_ac97_write_cache(vortex->codec,
+				     AC97_SIGMATEL_DAC2INVERT,
+				     snd_ac97_read(vortex->codec,
+						   AC97_SIGMATEL_DAC2INVERT)
+				     | 4);
+	} else {
+		/* Allow separate front and rear channels. */
+		snd_ac97_write_cache(vortex->codec,
+				     AC97_SIGMATEL_DAC2INVERT,
+				     snd_ac97_read(vortex->codec,
+						   AC97_SIGMATEL_DAC2INVERT)
+				     & ~((u32)
+					 4));
+	}
+#endif
+	return dma;
+}
+
+/*
+ Set the SampleRate of the SRC's attached to the given DMA engine.
+ */
+static void
+vortex_adb_setsrc(vortex_t * vortex, int adbdma, unsigned int rate, int dir)
+{
+	stream_t *stream = &(vortex->dma_adb[adbdma]);
+	int i, cvrt;
+
+	/* dir=1:play ; dir=0:rec */
+	if (dir)
+		cvrt = SRC_RATIO(rate, 48000);
+	else
+		cvrt = SRC_RATIO(48000, rate);
+
+	/* Setup SRC's */
+	for (i = 0; i < NR_SRC; i++) {
+		if (stream->resources[VORTEX_RESOURCE_SRC] & (1 << i))
+			vortex_src_setupchannel(vortex, i, cvrt, 0, 0, i, dir, 1, cvrt, dir);
+	}
+}
+
+// Timer and ISR functions.
+
+static void vortex_settimer(vortex_t * vortex, int period)
+{
+	//set the timer period to <period> 48000ths of a second.
+	hwwrite(vortex->mmio, VORTEX_IRQ_STAT, period);
+}
+
+#if 0
+static void vortex_enable_timer_int(vortex_t * card)
+{
+	hwwrite(card->mmio, VORTEX_IRQ_CTRL,
+		hwread(card->mmio, VORTEX_IRQ_CTRL) | IRQ_TIMER | 0x60);
+}
+
+static void vortex_disable_timer_int(vortex_t * card)
+{
+	hwwrite(card->mmio, VORTEX_IRQ_CTRL,
+		hwread(card->mmio, VORTEX_IRQ_CTRL) & ~IRQ_TIMER);
+}
+
+#endif
+static void vortex_enable_int(vortex_t * card)
+{
+	// CAsp4ISR__EnableVortexInt_void_
+	hwwrite(card->mmio, VORTEX_CTRL,
+		hwread(card->mmio, VORTEX_CTRL) | CTRL_IRQ_ENABLE);
+	hwwrite(card->mmio, VORTEX_IRQ_CTRL,
+		(hwread(card->mmio, VORTEX_IRQ_CTRL) & 0xffffefc0) | 0x24);
+}
+
+static void vortex_disable_int(vortex_t * card)
+{
+	hwwrite(card->mmio, VORTEX_CTRL,
+		hwread(card->mmio, VORTEX_CTRL) & ~CTRL_IRQ_ENABLE);
+}
+
+static irqreturn_t vortex_interrupt(int irq, void *dev_id)
+{
+	vortex_t *vortex = dev_id;
+	int i, handled;
+	u32 source;
+
+	//check if the interrupt is ours.
+	if (!(hwread(vortex->mmio, VORTEX_STAT) & 0x1))
+		return IRQ_NONE;
+
+	// This is the Interrupt Enable flag we set before (consistency check).
+	if (!(hwread(vortex->mmio, VORTEX_CTRL) & CTRL_IRQ_ENABLE))
+		return IRQ_NONE;
+
+	source = hwread(vortex->mmio, VORTEX_IRQ_SOURCE);
+	// Reset IRQ flags.
+	hwwrite(vortex->mmio, VORTEX_IRQ_SOURCE, source);
+	hwread(vortex->mmio, VORTEX_IRQ_SOURCE);
+	// Is at least one IRQ flag set?
+	if (source == 0) {
+		printk(KERN_ERR "vortex: missing irq source\n");
+		return IRQ_NONE;
+	}
+
+	handled = 0;
+	// Attend every interrupt source.
+	if (unlikely(source & IRQ_ERR_MASK)) {
+		if (source & IRQ_FATAL) {
+			printk(KERN_ERR "vortex: IRQ fatal error\n");
+		}
+		if (source & IRQ_PARITY) {
+			printk(KERN_ERR "vortex: IRQ parity error\n");
+		}
+		if (source & IRQ_REG) {
+			printk(KERN_ERR "vortex: IRQ reg error\n");
+		}
+		if (source & IRQ_FIFO) {
+			printk(KERN_ERR "vortex: IRQ fifo error\n");
+		}
+		if (source & IRQ_DMA) {
+			printk(KERN_ERR "vortex: IRQ dma error\n");
+		}
+		handled = 1;
+	}
+	if (source & IRQ_PCMOUT) {
+		/* ALSA period acknowledge. */
+		spin_lock(&vortex->lock);
+		for (i = 0; i < NR_ADB; i++) {
+			if (vortex->dma_adb[i].fifo_status == FIFO_START) {
+				if (!vortex_adbdma_bufshift(vortex, i))
+					continue;
+				spin_unlock(&vortex->lock);
+				snd_pcm_period_elapsed(vortex->dma_adb[i].
+						       substream);
+				spin_lock(&vortex->lock);
+			}
+		}
+#ifndef CHIP_AU8810
+		for (i = 0; i < NR_WT; i++) {
+			if (vortex->dma_wt[i].fifo_status == FIFO_START) {
+				if (vortex_wtdma_bufshift(vortex, i)) ;
+				spin_unlock(&vortex->lock);
+				snd_pcm_period_elapsed(vortex->dma_wt[i].
+						       substream);
+				spin_lock(&vortex->lock);
+			}
+		}
+#endif
+		spin_unlock(&vortex->lock);
+		handled = 1;
+	}
+	//Acknowledge the Timer interrupt
+	if (source & IRQ_TIMER) {
+		hwread(vortex->mmio, VORTEX_IRQ_STAT);
+		handled = 1;
+	}
+	if (source & IRQ_MIDI) {
+		snd_mpu401_uart_interrupt(vortex->irq,
+					  vortex->rmidi->private_data);
+		handled = 1;
+	}
+
+	if (!handled) {
+		printk(KERN_ERR "vortex: unknown irq source %x\n", source);
+	}
+	return IRQ_RETVAL(handled);
+}
+
+/* Codec */
+
+#define POLL_COUNT 1000
+static void vortex_codec_init(vortex_t * vortex)
+{
+	int i;
+
+	for (i = 0; i < 32; i++) {
+		/* the windows driver writes -i, so we write -i */
+		hwwrite(vortex->mmio, (VORTEX_CODEC_CHN + (i << 2)), -i);
+		msleep(2);
+	}
+	if (0) {
+		hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x8068);
+		msleep(1);
+		hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x00e8);
+		msleep(1);
+	} else {
+		hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x00a8);
+		msleep(2);
+		hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x80a8);
+		msleep(2);
+		hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x80e8);
+		msleep(2);
+		hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x80a8);
+		msleep(2);
+		hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x00a8);
+		msleep(2);
+		hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0x00e8);
+	}
+	for (i = 0; i < 32; i++) {
+		hwwrite(vortex->mmio, (VORTEX_CODEC_CHN + (i << 2)), -i);
+		msleep(5);
+	}
+	hwwrite(vortex->mmio, VORTEX_CODEC_CTRL, 0xe8);
+	msleep(1);
+	/* Enable codec channels 0 and 1. */
+	hwwrite(vortex->mmio, VORTEX_CODEC_EN,
+		hwread(vortex->mmio, VORTEX_CODEC_EN) | EN_CODEC);
+}
+
+static void
+vortex_codec_write(struct snd_ac97 * codec, unsigned short addr, unsigned short data)
+{
+
+	vortex_t *card = (vortex_t *) codec->private_data;
+	unsigned int lifeboat = 0;
+
+	/* wait for transactions to clear */
+	while (!(hwread(card->mmio, VORTEX_CODEC_CTRL) & 0x100)) {
+		udelay(100);
+		if (lifeboat++ > POLL_COUNT) {
+			printk(KERN_ERR "vortex: ac97 codec stuck busy\n");
+			return;
+		}
+	}
+	/* write register */
+	hwwrite(card->mmio, VORTEX_CODEC_IO,
+		((addr << VORTEX_CODEC_ADDSHIFT) & VORTEX_CODEC_ADDMASK) |
+		((data << VORTEX_CODEC_DATSHIFT) & VORTEX_CODEC_DATMASK) |
+		VORTEX_CODEC_WRITE |
+		(codec->num << VORTEX_CODEC_ID_SHIFT) );
+
+	/* Flush Caches. */
+	hwread(card->mmio, VORTEX_CODEC_IO);
+}
+
+static unsigned short vortex_codec_read(struct snd_ac97 * codec, unsigned short addr)
+{
+
+	vortex_t *card = (vortex_t *) codec->private_data;
+	u32 read_addr, data;
+	unsigned lifeboat = 0;
+
+	/* wait for transactions to clear */
+	while (!(hwread(card->mmio, VORTEX_CODEC_CTRL) & 0x100)) {
+		udelay(100);
+		if (lifeboat++ > POLL_COUNT) {
+			printk(KERN_ERR "vortex: ac97 codec stuck busy\n");
+			return 0xffff;
+		}
+	}
+	/* set up read address */
+	read_addr = ((addr << VORTEX_CODEC_ADDSHIFT) & VORTEX_CODEC_ADDMASK) |
+		(codec->num << VORTEX_CODEC_ID_SHIFT) ;
+	hwwrite(card->mmio, VORTEX_CODEC_IO, read_addr);
+
+	/* wait for address */
+	do {
+		udelay(100);
+		data = hwread(card->mmio, VORTEX_CODEC_IO);
+		if (lifeboat++ > POLL_COUNT) {
+			printk(KERN_ERR "vortex: ac97 address never arrived\n");
+			return 0xffff;
+		}
+	} while ((data & VORTEX_CODEC_ADDMASK) !=
+		 (addr << VORTEX_CODEC_ADDSHIFT));
+
+	/* return data. */
+	return (u16) (data & VORTEX_CODEC_DATMASK);
+}
+
+/* SPDIF support  */
+
+static void vortex_spdif_init(vortex_t * vortex, int spdif_sr, int spdif_mode)
+{
+	int i, this_38 = 0, this_04 = 0, this_08 = 0, this_0c = 0;
+
+	/* CAsp4Spdif::InitializeSpdifHardware(void) */
+	hwwrite(vortex->mmio, VORTEX_SPDIF_FLAGS,
+		hwread(vortex->mmio, VORTEX_SPDIF_FLAGS) & 0xfff3fffd);
+	//for (i=0x291D4; i<0x29200; i+=4)
+	for (i = 0; i < 11; i++)
+		hwwrite(vortex->mmio, VORTEX_SPDIF_CFG1 + (i << 2), 0);
+	//hwwrite(vortex->mmio, 0x29190, hwread(vortex->mmio, 0x29190) | 0xc0000);
+	hwwrite(vortex->mmio, VORTEX_CODEC_EN,
+		hwread(vortex->mmio, VORTEX_CODEC_EN) | EN_SPDIF);
+
+	/* CAsp4Spdif::ProgramSRCInHardware(enum  SPDIF_SR,enum  SPDIFMODE) */
+	if (this_04 && this_08) {
+		int edi;
+
+		i = (((0x5DC00000 / spdif_sr) + 1) >> 1);
+		if (i > 0x800) {
+			if (i < 0x1ffff)
+				edi = (i >> 1);
+			else
+				edi = 0x1ffff;
+		} else {
+			i = edi = 0x800;
+		}
+		/* this_04 and this_08 are the CASp4Src's (samplerate converters) */
+		vortex_src_setupchannel(vortex, this_04, edi, 0, 1,
+					this_0c, 1, 0, edi, 1);
+		vortex_src_setupchannel(vortex, this_08, edi, 0, 1,
+					this_0c, 1, 0, edi, 1);
+	}
+
+	i = spdif_sr;
+	spdif_sr |= 0x8c;
+	switch (i) {
+	case 32000:
+		this_38 &= 0xFFFFFFFE;
+		this_38 &= 0xFFFFFFFD;
+		this_38 &= 0xF3FFFFFF;
+		this_38 |= 0x03000000;	/* set 32khz samplerate */
+		this_38 &= 0xFFFFFF3F;
+		spdif_sr &= 0xFFFFFFFD;
+		spdif_sr |= 1;
+		break;
+	case 44100:
+		this_38 &= 0xFFFFFFFE;
+		this_38 &= 0xFFFFFFFD;
+		this_38 &= 0xF0FFFFFF;
+		this_38 |= 0x03000000;
+		this_38 &= 0xFFFFFF3F;
+		spdif_sr &= 0xFFFFFFFC;
+		break;
+	case 48000:
+		if (spdif_mode == 1) {
+			this_38 &= 0xFFFFFFFE;
+			this_38 &= 0xFFFFFFFD;
+			this_38 &= 0xF2FFFFFF;
+			this_38 |= 0x02000000;	/* set 48khz samplerate */
+			this_38 &= 0xFFFFFF3F;
+		} else {
+			/* J. Gordon Wolfe: I think this stuff is for AC3 */
+			this_38 |= 0x00000003;
+			this_38 &= 0xFFFFFFBF;
+			this_38 |= 0x80;
+		}
+		spdif_sr |= 2;
+		spdif_sr &= 0xFFFFFFFE;
+		break;
+
+	}
+	/* looks like the next 2 lines transfer a 16-bit value into 2 8-bit 
+	   registers. seems to be for the standard IEC/SPDIF initialization 
+	   stuff */
+	hwwrite(vortex->mmio, VORTEX_SPDIF_CFG0, this_38 & 0xffff);
+	hwwrite(vortex->mmio, VORTEX_SPDIF_CFG1, this_38 >> 0x10);
+	hwwrite(vortex->mmio, VORTEX_SPDIF_SMPRATE, spdif_sr);
+}
+
+/* Initialization */
+
+static int __devinit vortex_core_init(vortex_t * vortex)
+{
+
+	printk(KERN_INFO "Vortex: init.... ");
+	/* Hardware Init. */
+	hwwrite(vortex->mmio, VORTEX_CTRL, 0xffffffff);
+	msleep(5);
+	hwwrite(vortex->mmio, VORTEX_CTRL,
+		hwread(vortex->mmio, VORTEX_CTRL) & 0xffdfffff);
+	msleep(5);
+	/* Reset IRQ flags */
+	hwwrite(vortex->mmio, VORTEX_IRQ_SOURCE, 0xffffffff);
+	hwread(vortex->mmio, VORTEX_IRQ_STAT);
+
+	vortex_codec_init(vortex);
+
+#ifdef CHIP_AU8830
+	hwwrite(vortex->mmio, VORTEX_CTRL,
+		hwread(vortex->mmio, VORTEX_CTRL) | 0x1000000);
+#endif
+
+	/* Init audio engine. */
+	vortex_adbdma_init(vortex);
+	hwwrite(vortex->mmio, VORTEX_ENGINE_CTRL, 0x0);	//, 0xc83c7e58, 0xc5f93e58
+	vortex_adb_init(vortex);
+	/* Init processing blocks. */
+	vortex_fifo_init(vortex);
+	vortex_mixer_init(vortex);
+	vortex_srcblock_init(vortex);
+#ifndef CHIP_AU8820
+	vortex_eq_init(vortex);
+	vortex_spdif_init(vortex, 48000, 1);
+	vortex_Vort3D_enable(vortex);
+#endif
+#ifndef CHIP_AU8810
+	vortex_wt_init(vortex);
+#endif
+	// Moved to au88x0.c
+	//vortex_connect_default(vortex, 1);
+
+	vortex_settimer(vortex, 0x90);
+	// Enable Interrupts.
+	// vortex_enable_int() must be first !!
+	//  hwwrite(vortex->mmio, VORTEX_IRQ_CTRL, 0);
+	// vortex_enable_int(vortex);
+	//vortex_enable_timer_int(vortex);
+	//vortex_disable_timer_int(vortex);
+
+	printk(KERN_INFO "done.\n");
+	spin_lock_init(&vortex->lock);
+
+	return 0;
+}
+
+static int vortex_core_shutdown(vortex_t * vortex)
+{
+
+	printk(KERN_INFO "Vortex: shutdown...");
+#ifndef CHIP_AU8820
+	vortex_eq_free(vortex);
+	vortex_Vort3D_disable(vortex);
+#endif
+	//vortex_disable_timer_int(vortex);
+	vortex_disable_int(vortex);
+	vortex_connect_default(vortex, 0);
+	/* Reset all DMA fifos. */
+	vortex_fifo_init(vortex);
+	/* Erase all audio routes. */
+	vortex_adb_init(vortex);
+
+	/* Disable MPU401 */
+	//hwwrite(vortex->mmio, VORTEX_IRQ_CTRL, hwread(vortex->mmio, VORTEX_IRQ_CTRL) & ~IRQ_MIDI);
+	//hwwrite(vortex->mmio, VORTEX_CTRL, hwread(vortex->mmio, VORTEX_CTRL) & ~CTRL_MIDI_EN);
+
+	hwwrite(vortex->mmio, VORTEX_IRQ_CTRL, 0);
+	hwwrite(vortex->mmio, VORTEX_CTRL, 0);
+	msleep(5);
+	hwwrite(vortex->mmio, VORTEX_IRQ_SOURCE, 0xffff);
+
+	printk(KERN_INFO "done.\n");
+	return 0;
+}
+
+/* Alsa support. */
+
+static int vortex_alsafmt_aspfmt(int alsafmt)
+{
+	int fmt;
+
+	switch (alsafmt) {
+	case SNDRV_PCM_FORMAT_U8:
+		fmt = 0x1;
+		break;
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		fmt = 0x2;
+		break;
+	case SNDRV_PCM_FORMAT_A_LAW:
+		fmt = 0x3;
+		break;
+	case SNDRV_PCM_FORMAT_SPECIAL:
+		fmt = 0x4;	/* guess. */
+		break;
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
+		fmt = 0x5;	/* guess. */
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		fmt = 0x8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE:
+		fmt = 0x9;	/* check this... */
+		break;
+	default:
+		fmt = 0x8;
+		printk(KERN_ERR "vortex: format unsupported %d\n", alsafmt);
+		break;
+	}
+	return fmt;
+}
+
+/* Some not yet useful translations. */
+#if 0
+typedef enum {
+	ASPFMTLINEAR16 = 0,	/* 0x8 */
+	ASPFMTLINEAR8,		/* 0x1 */
+	ASPFMTULAW,		/* 0x2 */
+	ASPFMTALAW,		/* 0x3 */
+	ASPFMTSPORT,		/* ? */
+	ASPFMTSPDIF,		/* ? */
+} ASPENCODING;
+
+static int
+vortex_translateformat(vortex_t * vortex, char bits, char nch, int encod)
+{
+	int a, this_194;
+
+	if ((bits != 8) && (bits != 16))
+		return -1;
+
+	switch (encod) {
+	case 0:
+		if (bits == 0x10)
+			a = 8;	// 16 bit
+		break;
+	case 1:
+		if (bits == 8)
+			a = 1;	// 8 bit
+		break;
+	case 2:
+		a = 2;		// U_LAW
+		break;
+	case 3:
+		a = 3;		// A_LAW
+		break;
+	}
+	switch (nch) {
+	case 1:
+		this_194 = 0;
+		break;
+	case 2:
+		this_194 = 1;
+		break;
+	case 4:
+		this_194 = 1;
+		break;
+	case 6:
+		this_194 = 1;
+		break;
+	}
+	return (a);
+}
+
+static void vortex_cdmacore_setformat(vortex_t * vortex, int bits, int nch)
+{
+	short int d, this_148;
+
+	d = ((bits >> 3) * nch);
+	this_148 = 0xbb80 / d;
+}
+#endif
diff --git a/linux/sound/pci/au88x0/au88x0_eq.c b/linux/sound/pci/au88x0/au88x0_eq.c
new file mode 100644
index 0000000..38602b8
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_eq.c
@@ -0,0 +1,928 @@
+/***************************************************************************
+ *            au88x0_eq.c
+ *  Aureal Vortex Hardware EQ control/access.
+ *
+ *  Sun Jun  8 18:19:19 2003
+ *  2003  Manuel Jander (mjander@users.sourceforge.net)
+ *  
+ *  02 July 2003: First time something works :)
+ *  November 2003: A3D Bypass code completed but untested.
+ *
+ *  TODO:
+ *     - Debug (testing)
+ *     - Test peak visualization support.
+ *
+ ****************************************************************************/
+
+/*
+ *  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 Library 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.
+ */
+
+/*
+ The Aureal Hardware EQ is found on AU8810 and AU8830 chips only.
+ it has 4 inputs (2 for general mix, 2 for A3D) and 2 outputs (supposed 
+ to be routed to the codec).
+*/
+
+#include "au88x0.h"
+#include "au88x0_eq.h"
+#include "au88x0_eqdata.c"
+
+#define VORTEX_EQ_BASE	 0x2b000
+#define VORTEX_EQ_DEST   (VORTEX_EQ_BASE + 0x410)
+#define VORTEX_EQ_SOURCE (VORTEX_EQ_BASE + 0x430)
+#define VORTEX_EQ_CTRL   (VORTEX_EQ_BASE + 0x440)
+
+#define VORTEX_BAND_COEFF_SIZE 0x30
+
+/* CEqHw.s */
+static void vortex_EqHw_SetTimeConsts(vortex_t * vortex, u16 gain, u16 level)
+{
+	hwwrite(vortex->mmio, 0x2b3c4, gain);
+	hwwrite(vortex->mmio, 0x2b3c8, level);
+}
+
+static inline u16 sign_invert(u16 a)
+{
+	/* -(-32768) -> -32768 so we do -(-32768) -> 32767 to make the result positive */
+	if (a == (u16)-32768)
+		return 32767;
+	else
+		return -a;
+}
+
+static void vortex_EqHw_SetLeftCoefs(vortex_t * vortex, u16 coefs[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int i = 0, n /*esp2c */;
+
+	for (n = 0; n < eqhw->this04; n++) {
+		hwwrite(vortex->mmio, 0x2b000 + n * 0x30, coefs[i + 0]);
+		hwwrite(vortex->mmio, 0x2b004 + n * 0x30, coefs[i + 1]);
+
+		if (eqhw->this08 == 0) {
+			hwwrite(vortex->mmio, 0x2b008 + n * 0x30, coefs[i + 2]);
+			hwwrite(vortex->mmio, 0x2b00c + n * 0x30, coefs[i + 3]);
+			hwwrite(vortex->mmio, 0x2b010 + n * 0x30, coefs[i + 4]);
+		} else {
+			hwwrite(vortex->mmio, 0x2b008 + n * 0x30, sign_invert(coefs[2 + i]));
+			hwwrite(vortex->mmio, 0x2b00c + n * 0x30, sign_invert(coefs[3 + i]));
+		        hwwrite(vortex->mmio, 0x2b010 + n * 0x30, sign_invert(coefs[4 + i]));
+		}
+		i += 5;
+	}
+}
+
+static void vortex_EqHw_SetRightCoefs(vortex_t * vortex, u16 coefs[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int i = 0, n /*esp2c */;
+
+	for (n = 0; n < eqhw->this04; n++) {
+		hwwrite(vortex->mmio, 0x2b1e0 + n * 0x30, coefs[0 + i]);
+		hwwrite(vortex->mmio, 0x2b1e4 + n * 0x30, coefs[1 + i]);
+
+		if (eqhw->this08 == 0) {
+			hwwrite(vortex->mmio, 0x2b1e8 + n * 0x30, coefs[2 + i]);
+			hwwrite(vortex->mmio, 0x2b1ec + n * 0x30, coefs[3 + i]);
+			hwwrite(vortex->mmio, 0x2b1f0 + n * 0x30, coefs[4 + i]);
+		} else {
+			hwwrite(vortex->mmio, 0x2b1e8 + n * 0x30, sign_invert(coefs[2 + i]));
+			hwwrite(vortex->mmio, 0x2b1ec + n * 0x30, sign_invert(coefs[3 + i]));
+			hwwrite(vortex->mmio, 0x2b1f0 + n * 0x30, sign_invert(coefs[4 + i]));
+		}
+		i += 5;
+	}
+
+}
+
+static void vortex_EqHw_SetLeftStates(vortex_t * vortex, u16 a[], u16 b[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int i = 0, ebx;
+
+	hwwrite(vortex->mmio, 0x2b3fc, a[0]);
+	hwwrite(vortex->mmio, 0x2b400, a[1]);
+
+	for (ebx = 0; ebx < eqhw->this04; ebx++) {
+		hwwrite(vortex->mmio, 0x2b014 + (i * 0xc), b[i]);
+		hwwrite(vortex->mmio, 0x2b018 + (i * 0xc), b[1 + i]);
+		hwwrite(vortex->mmio, 0x2b01c + (i * 0xc), b[2 + i]);
+		hwwrite(vortex->mmio, 0x2b020 + (i * 0xc), b[3 + i]);
+		i += 4;
+	}
+}
+
+static void vortex_EqHw_SetRightStates(vortex_t * vortex, u16 a[], u16 b[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int i = 0, ebx;
+
+	hwwrite(vortex->mmio, 0x2b404, a[0]);
+	hwwrite(vortex->mmio, 0x2b408, a[1]);
+
+	for (ebx = 0; ebx < eqhw->this04; ebx++) {
+		hwwrite(vortex->mmio, 0x2b1f4 + (i * 0xc), b[i]);
+		hwwrite(vortex->mmio, 0x2b1f8 + (i * 0xc), b[1 + i]);
+		hwwrite(vortex->mmio, 0x2b1fc + (i * 0xc), b[2 + i]);
+		hwwrite(vortex->mmio, 0x2b200 + (i * 0xc), b[3 + i]);
+		i += 4;
+	}
+}
+
+#if 0
+static void vortex_EqHw_GetTimeConsts(vortex_t * vortex, u16 * a, u16 * b)
+{
+	*a = hwread(vortex->mmio, 0x2b3c4);
+	*b = hwread(vortex->mmio, 0x2b3c8);
+}
+
+static void vortex_EqHw_GetLeftCoefs(vortex_t * vortex, u16 a[])
+{
+
+}
+
+static void vortex_EqHw_GetRightCoefs(vortex_t * vortex, u16 a[])
+{
+
+}
+
+static void vortex_EqHw_GetLeftStates(vortex_t * vortex, u16 * a, u16 b[])
+{
+
+}
+
+static void vortex_EqHw_GetRightStates(vortex_t * vortex, u16 * a, u16 b[])
+{
+
+}
+
+#endif
+/* Mix Gains */
+static void vortex_EqHw_SetBypassGain(vortex_t * vortex, u16 a, u16 b)
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	if (eqhw->this08 == 0) {
+		hwwrite(vortex->mmio, 0x2b3d4, a);
+		hwwrite(vortex->mmio, 0x2b3ec, b);
+	} else {
+		hwwrite(vortex->mmio, 0x2b3d4, sign_invert(a));
+		hwwrite(vortex->mmio, 0x2b3ec, sign_invert(b));
+	}
+}
+
+static void vortex_EqHw_SetA3DBypassGain(vortex_t * vortex, u16 a, u16 b)
+{
+
+	hwwrite(vortex->mmio, 0x2b3e0, a);
+	hwwrite(vortex->mmio, 0x2b3f8, b);
+}
+
+#if 0
+static void vortex_EqHw_SetCurrBypassGain(vortex_t * vortex, u16 a, u16 b)
+{
+
+	hwwrite(vortex->mmio, 0x2b3d0, a);
+	hwwrite(vortex->mmio, 0x2b3e8, b);
+}
+
+static void vortex_EqHw_SetCurrA3DBypassGain(vortex_t * vortex, u16 a, u16 b)
+{
+
+	hwwrite(vortex->mmio, 0x2b3dc, a);
+	hwwrite(vortex->mmio, 0x2b3f4, b);
+}
+
+#endif
+static void
+vortex_EqHw_SetLeftGainsSingleTarget(vortex_t * vortex, u16 index, u16 b)
+{
+	hwwrite(vortex->mmio, 0x2b02c + (index * 0x30), b);
+}
+
+static void
+vortex_EqHw_SetRightGainsSingleTarget(vortex_t * vortex, u16 index, u16 b)
+{
+	hwwrite(vortex->mmio, 0x2b20c + (index * 0x30), b);
+}
+
+static void vortex_EqHw_SetLeftGainsTarget(vortex_t * vortex, u16 a[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int ebx;
+
+	for (ebx = 0; ebx < eqhw->this04; ebx++) {
+		hwwrite(vortex->mmio, 0x2b02c + ebx * 0x30, a[ebx]);
+	}
+}
+
+static void vortex_EqHw_SetRightGainsTarget(vortex_t * vortex, u16 a[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int ebx;
+
+	for (ebx = 0; ebx < eqhw->this04; ebx++) {
+		hwwrite(vortex->mmio, 0x2b20c + ebx * 0x30, a[ebx]);
+	}
+}
+
+static void vortex_EqHw_SetLeftGainsCurrent(vortex_t * vortex, u16 a[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int ebx;
+
+	for (ebx = 0; ebx < eqhw->this04; ebx++) {
+		hwwrite(vortex->mmio, 0x2b028 + ebx * 0x30, a[ebx]);
+	}
+}
+
+static void vortex_EqHw_SetRightGainsCurrent(vortex_t * vortex, u16 a[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int ebx;
+
+	for (ebx = 0; ebx < eqhw->this04; ebx++) {
+		hwwrite(vortex->mmio, 0x2b208 + ebx * 0x30, a[ebx]);
+	}
+}
+
+#if 0
+static void vortex_EqHw_GetLeftGainsTarget(vortex_t * vortex, u16 a[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int ebx = 0;
+
+	if (eqhw->this04 < 0)
+		return;
+
+	do {
+		a[ebx] = hwread(vortex->mmio, 0x2b02c + ebx * 0x30);
+		ebx++;
+	}
+	while (ebx < eqhw->this04);
+}
+
+static void vortex_EqHw_GetRightGainsTarget(vortex_t * vortex, u16 a[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int ebx = 0;
+
+	if (eqhw->this04 < 0)
+		return;
+
+	do {
+		a[ebx] = hwread(vortex->mmio, 0x2b20c + ebx * 0x30);
+		ebx++;
+	}
+	while (ebx < eqhw->this04);
+}
+
+static void vortex_EqHw_GetLeftGainsCurrent(vortex_t * vortex, u16 a[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int ebx = 0;
+
+	if (eqhw->this04 < 0)
+		return;
+
+	do {
+		a[ebx] = hwread(vortex->mmio, 0x2b028 + ebx * 0x30);
+		ebx++;
+	}
+	while (ebx < eqhw->this04);
+}
+
+static void vortex_EqHw_GetRightGainsCurrent(vortex_t * vortex, u16 a[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int ebx = 0;
+
+	if (eqhw->this04 < 0)
+		return;
+
+	do {
+		a[ebx] = hwread(vortex->mmio, 0x2b208 + ebx * 0x30);
+		ebx++;
+	}
+	while (ebx < eqhw->this04);
+}
+
+#endif
+/* EQ band levels settings */
+static void vortex_EqHw_SetLevels(vortex_t * vortex, u16 peaks[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int i;
+
+	/* set left peaks */
+	for (i = 0; i < eqhw->this04; i++) {
+		hwwrite(vortex->mmio, 0x2b024 + i * VORTEX_BAND_COEFF_SIZE, peaks[i]);
+	}
+
+	hwwrite(vortex->mmio, 0x2b3cc, peaks[eqhw->this04]);
+	hwwrite(vortex->mmio, 0x2b3d8, peaks[eqhw->this04 + 1]);
+
+	/* set right peaks */
+	for (i = 0; i < eqhw->this04; i++) {
+		hwwrite(vortex->mmio, 0x2b204 + i * VORTEX_BAND_COEFF_SIZE,
+			peaks[i + (eqhw->this04 + 2)]);
+	}
+
+	hwwrite(vortex->mmio, 0x2b3e4, peaks[2 + (eqhw->this04 * 2)]);
+	hwwrite(vortex->mmio, 0x2b3f0, peaks[3 + (eqhw->this04 * 2)]);
+}
+
+#if 0
+static void vortex_EqHw_GetLevels(vortex_t * vortex, u16 a[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int ebx;
+
+	if (eqhw->this04 < 0)
+		return;
+
+	ebx = 0;
+	do {
+		a[ebx] = hwread(vortex->mmio, 0x2b024 + ebx * 0x30);
+		ebx++;
+	}
+	while (ebx < eqhw->this04);
+
+	a[eqhw->this04] = hwread(vortex->mmio, 0x2b3cc);
+	a[eqhw->this04 + 1] = hwread(vortex->mmio, 0x2b3d8);
+
+	ebx = 0;
+	do {
+		a[ebx + (eqhw->this04 + 2)] =
+		    hwread(vortex->mmio, 0x2b204 + ebx * 0x30);
+		ebx++;
+	}
+	while (ebx < eqhw->this04);
+
+	a[2 + (eqhw->this04 * 2)] = hwread(vortex->mmio, 0x2b3e4);
+	a[3 + (eqhw->this04 * 2)] = hwread(vortex->mmio, 0x2b3f0);
+}
+
+#endif
+/* Global Control */
+static void vortex_EqHw_SetControlReg(vortex_t * vortex, u32 reg)
+{
+	hwwrite(vortex->mmio, 0x2b440, reg);
+}
+
+static void vortex_EqHw_SetSampleRate(vortex_t * vortex, u32 sr)
+{
+	hwwrite(vortex->mmio, 0x2b440, ((sr & 0x1f) << 3) | 0xb800);
+}
+
+#if 0
+static void vortex_EqHw_GetControlReg(vortex_t * vortex, u32 *reg)
+{
+	*reg = hwread(vortex->mmio, 0x2b440);
+}
+
+static void vortex_EqHw_GetSampleRate(vortex_t * vortex, u32 *sr)
+{
+	*sr = (hwread(vortex->mmio, 0x2b440) >> 3) & 0x1f;
+}
+
+#endif
+static void vortex_EqHw_Enable(vortex_t * vortex)
+{
+	hwwrite(vortex->mmio, VORTEX_EQ_CTRL, 0xf001);
+}
+
+static void vortex_EqHw_Disable(vortex_t * vortex)
+{
+	hwwrite(vortex->mmio, VORTEX_EQ_CTRL, 0xf000);
+}
+
+/* Reset (zero) buffers */
+static void vortex_EqHw_ZeroIO(vortex_t * vortex)
+{
+	int i;
+	for (i = 0; i < 0x8; i++)
+		hwwrite(vortex->mmio, VORTEX_EQ_DEST + (i << 2), 0x0);
+	for (i = 0; i < 0x4; i++)
+		hwwrite(vortex->mmio, VORTEX_EQ_SOURCE + (i << 2), 0x0);
+}
+
+static void vortex_EqHw_ZeroA3DIO(vortex_t * vortex)
+{
+	int i;
+	for (i = 0; i < 0x4; i++)
+		hwwrite(vortex->mmio, VORTEX_EQ_DEST + (i << 2), 0x0);
+}
+
+static void vortex_EqHw_ZeroState(vortex_t * vortex)
+{
+
+	vortex_EqHw_SetControlReg(vortex, 0);
+	vortex_EqHw_ZeroIO(vortex);
+	hwwrite(vortex->mmio, 0x2b3c0, 0);
+
+	vortex_EqHw_SetTimeConsts(vortex, 0, 0);
+
+	vortex_EqHw_SetLeftCoefs(vortex, asEqCoefsZeros);
+	vortex_EqHw_SetRightCoefs(vortex, asEqCoefsZeros);
+
+	vortex_EqHw_SetLeftGainsCurrent(vortex, eq_gains_zero);
+	vortex_EqHw_SetRightGainsCurrent(vortex, eq_gains_zero);
+	vortex_EqHw_SetLeftGainsTarget(vortex, eq_gains_zero);
+	vortex_EqHw_SetRightGainsTarget(vortex, eq_gains_zero);
+
+	vortex_EqHw_SetBypassGain(vortex, 0, 0);
+	//vortex_EqHw_SetCurrBypassGain(vortex, 0, 0);
+	vortex_EqHw_SetA3DBypassGain(vortex, 0, 0);
+	//vortex_EqHw_SetCurrA3DBypassGain(vortex, 0, 0);
+	vortex_EqHw_SetLeftStates(vortex, eq_states_zero, asEqOutStateZeros);
+	vortex_EqHw_SetRightStates(vortex, eq_states_zero, asEqOutStateZeros);
+	vortex_EqHw_SetLevels(vortex, (u16 *) eq_levels);
+}
+
+/* Program coeficients as pass through */
+static void vortex_EqHw_ProgramPipe(vortex_t * vortex)
+{
+	vortex_EqHw_SetTimeConsts(vortex, 0, 0);
+
+	vortex_EqHw_SetLeftCoefs(vortex, asEqCoefsPipes);
+	vortex_EqHw_SetRightCoefs(vortex, asEqCoefsPipes);
+
+	vortex_EqHw_SetLeftGainsCurrent(vortex, eq_gains_current);
+	vortex_EqHw_SetRightGainsCurrent(vortex, eq_gains_current);
+	vortex_EqHw_SetLeftGainsTarget(vortex, eq_gains_current);
+	vortex_EqHw_SetRightGainsTarget(vortex, eq_gains_current);
+}
+
+/* Program EQ block as 10 band Equalizer */
+static void
+vortex_EqHw_Program10Band(vortex_t * vortex, auxxEqCoeffSet_t * coefset)
+{
+
+	vortex_EqHw_SetTimeConsts(vortex, 0xc, 0x7fe0);
+
+	vortex_EqHw_SetLeftCoefs(vortex, coefset->LeftCoefs);
+	vortex_EqHw_SetRightCoefs(vortex, coefset->RightCoefs);
+
+	vortex_EqHw_SetLeftGainsCurrent(vortex, coefset->LeftGains);
+
+	vortex_EqHw_SetRightGainsTarget(vortex, coefset->RightGains);
+	vortex_EqHw_SetLeftGainsTarget(vortex, coefset->LeftGains);
+
+	vortex_EqHw_SetRightGainsCurrent(vortex, coefset->RightGains);
+}
+
+/* Read all EQ peaks. (think VU meter) */
+static void vortex_EqHw_GetTenBandLevels(vortex_t * vortex, u16 peaks[])
+{
+	eqhw_t *eqhw = &(vortex->eq.this04);
+	int i;
+
+	if (eqhw->this04 <= 0)
+		return;
+
+	for (i = 0; i < eqhw->this04; i++)
+		peaks[i] = hwread(vortex->mmio, 0x2B024 + i * 0x30);
+	for (i = 0; i < eqhw->this04; i++)
+		peaks[i + eqhw->this04] =
+		    hwread(vortex->mmio, 0x2B204 + i * 0x30);
+}
+
+/* CEqlzr.s */
+
+static int vortex_Eqlzr_GetLeftGain(vortex_t * vortex, u16 index, u16 * gain)
+{
+	eqlzr_t *eq = &(vortex->eq);
+
+	if (eq->this28) {
+		*gain = eq->this130[index];
+		return 0;
+	}
+	return 1;
+}
+
+static void vortex_Eqlzr_SetLeftGain(vortex_t * vortex, u16 index, u16 gain)
+{
+	eqlzr_t *eq = &(vortex->eq);
+
+	if (eq->this28 == 0)
+		return;
+
+	eq->this130[index] = gain;
+	if (eq->this54)
+		return;
+
+	vortex_EqHw_SetLeftGainsSingleTarget(vortex, index, gain);
+}
+
+static int vortex_Eqlzr_GetRightGain(vortex_t * vortex, u16 index, u16 * gain)
+{
+	eqlzr_t *eq = &(vortex->eq);
+
+	if (eq->this28) {
+		*gain = eq->this130[index + eq->this10];
+		return 0;
+	}
+	return 1;
+}
+
+static void vortex_Eqlzr_SetRightGain(vortex_t * vortex, u16 index, u16 gain)
+{
+	eqlzr_t *eq = &(vortex->eq);
+
+	if (eq->this28 == 0)
+		return;
+
+	eq->this130[index + eq->this10] = gain;
+	if (eq->this54)
+		return;
+
+	vortex_EqHw_SetRightGainsSingleTarget(vortex, index, gain);
+}
+
+#if 0
+static int
+vortex_Eqlzr_GetAllBands(vortex_t * vortex, u16 * gains, s32 *cnt)
+{
+	eqlzr_t *eq = &(vortex->eq);
+	int si = 0;
+
+	if (eq->this10 == 0)
+		return 1;
+
+	{
+		if (vortex_Eqlzr_GetLeftGain(vortex, si, &gains[si]))
+			return 1;
+		if (vortex_Eqlzr_GetRightGain
+		    (vortex, si, &gains[si + eq->this10]))
+			return 1;
+		si++;
+	}
+	while (eq->this10 > si) ;
+	*cnt = si * 2;
+	return 0;
+}
+#endif
+static int vortex_Eqlzr_SetAllBandsFromActiveCoeffSet(vortex_t * vortex)
+{
+	eqlzr_t *eq = &(vortex->eq);
+
+	vortex_EqHw_SetLeftGainsTarget(vortex, eq->this130);
+	vortex_EqHw_SetRightGainsTarget(vortex, &(eq->this130[eq->this10]));
+
+	return 0;
+}
+
+static int
+vortex_Eqlzr_SetAllBands(vortex_t * vortex, u16 gains[], s32 count)
+{
+	eqlzr_t *eq = &(vortex->eq);
+	int i;
+
+	if (((eq->this10) * 2 != count) || (eq->this28 == 0))
+		return 1;
+
+	for (i = 0; i < count; i++) {
+		eq->this130[i] = gains[i];
+	}
+	
+	if (eq->this54)
+		return 0;
+	return vortex_Eqlzr_SetAllBandsFromActiveCoeffSet(vortex);
+}
+
+static void
+vortex_Eqlzr_SetA3dBypassGain(vortex_t * vortex, u32 a, u32 b)
+{
+	eqlzr_t *eq = &(vortex->eq);
+	u32 eax, ebx;
+
+	eq->this58 = a;
+	eq->this5c = b;
+	if (eq->this54)
+		eax = eq->this0e;
+	else
+		eax = eq->this0a;
+	ebx = (eax * eq->this58) >> 0x10;
+	eax = (eax * eq->this5c) >> 0x10;
+	vortex_EqHw_SetA3DBypassGain(vortex, ebx, eax);
+}
+
+static void vortex_Eqlzr_ProgramA3dBypassGain(vortex_t * vortex)
+{
+	eqlzr_t *eq = &(vortex->eq);
+	u32 eax, ebx;
+
+	if (eq->this54)
+		eax = eq->this0e;
+	else
+		eax = eq->this0a;
+	ebx = (eax * eq->this58) >> 0x10;
+	eax = (eax * eq->this5c) >> 0x10;
+	vortex_EqHw_SetA3DBypassGain(vortex, ebx, eax);
+}
+
+static void vortex_Eqlzr_ShutDownA3d(vortex_t * vortex)
+{
+	if (vortex != NULL)
+		vortex_EqHw_ZeroA3DIO(vortex);
+}
+
+static void vortex_Eqlzr_SetBypass(vortex_t * vortex, u32 bp)
+{
+	eqlzr_t *eq = &(vortex->eq);
+	
+	if ((eq->this28) && (bp == 0)) {
+		/* EQ enabled */
+		vortex_Eqlzr_SetAllBandsFromActiveCoeffSet(vortex);
+		vortex_EqHw_SetBypassGain(vortex, eq->this08, eq->this08);
+	} else {
+		/* EQ disabled. */
+		vortex_EqHw_SetLeftGainsTarget(vortex, eq->this14_array);
+		vortex_EqHw_SetRightGainsTarget(vortex, eq->this14_array);
+		vortex_EqHw_SetBypassGain(vortex, eq->this0c, eq->this0c);
+	}
+	vortex_Eqlzr_ProgramA3dBypassGain(vortex);
+}
+
+static void vortex_Eqlzr_ReadAndSetActiveCoefSet(vortex_t * vortex)
+{
+	eqlzr_t *eq = &(vortex->eq);
+
+	/* Set EQ BiQuad filter coeficients */
+	memcpy(&(eq->coefset), &asEqCoefsNormal, sizeof(auxxEqCoeffSet_t));
+	/* Set EQ Band gain levels and dump into hardware registers. */
+	vortex_Eqlzr_SetAllBands(vortex, eq_gains_normal, eq->this10 * 2);
+}
+
+static int vortex_Eqlzr_GetAllPeaks(vortex_t * vortex, u16 * peaks, int *count)
+{
+	eqlzr_t *eq = &(vortex->eq);
+
+	if (eq->this10 == 0)
+		return 1;
+	*count = eq->this10 * 2;
+	vortex_EqHw_GetTenBandLevels(vortex, peaks);
+	return 0;
+}
+
+#if 0
+static auxxEqCoeffSet_t *vortex_Eqlzr_GetActiveCoefSet(vortex_t * vortex)
+{
+	eqlzr_t *eq = &(vortex->eq);
+
+	return (&(eq->coefset));
+}
+#endif
+static void vortex_Eqlzr_init(vortex_t * vortex)
+{
+	eqlzr_t *eq = &(vortex->eq);
+
+	/* Object constructor */
+	//eq->this04 = 0;
+	eq->this08 = 0;		/* Bypass gain with EQ in use. */
+	eq->this0a = 0x5999;
+	eq->this0c = 0x5999;	/* Bypass gain with EQ disabled. */
+	eq->this0e = 0x5999;
+
+	eq->this10 = 0xa;	/* 10 eq frequency bands. */
+	eq->this04.this04 = eq->this10;
+	eq->this28 = 0x1;	/* if 1 => Allow read access to this130 (gains) */
+	eq->this54 = 0x0;	/* if 1 => Dont Allow access to hardware (gains) */
+	eq->this58 = 0xffff;
+	eq->this5c = 0xffff;
+
+	/* Set gains. */
+	memset(eq->this14_array, 0, sizeof(eq->this14_array));
+
+	/* Actual init. */
+	vortex_EqHw_ZeroState(vortex);
+	vortex_EqHw_SetSampleRate(vortex, 0x11);
+	vortex_Eqlzr_ReadAndSetActiveCoefSet(vortex);
+
+	vortex_EqHw_Program10Band(vortex, &(eq->coefset));
+	vortex_Eqlzr_SetBypass(vortex, eq->this54);
+	vortex_Eqlzr_SetA3dBypassGain(vortex, 0, 0);
+	vortex_EqHw_Enable(vortex);
+}
+
+static void vortex_Eqlzr_shutdown(vortex_t * vortex)
+{
+	vortex_Eqlzr_ShutDownA3d(vortex);
+	vortex_EqHw_ProgramPipe(vortex);
+	vortex_EqHw_Disable(vortex);
+}
+
+/* ALSA interface */
+
+/* Control interface */
+#define snd_vortex_eqtoggle_info	snd_ctl_boolean_mono_info
+
+static int
+snd_vortex_eqtoggle_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	vortex_t *vortex = snd_kcontrol_chip(kcontrol);
+	eqlzr_t *eq = &(vortex->eq);
+	//int i = kcontrol->private_value;
+
+	ucontrol->value.integer.value[0] = eq->this54 ? 0 : 1;
+
+	return 0;
+}
+
+static int
+snd_vortex_eqtoggle_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	vortex_t *vortex = snd_kcontrol_chip(kcontrol);
+	eqlzr_t *eq = &(vortex->eq);
+	//int i = kcontrol->private_value;
+
+	eq->this54 = ucontrol->value.integer.value[0] ? 0 : 1;
+	vortex_Eqlzr_SetBypass(vortex, eq->this54);
+
+	return 1;		/* Allways changes */
+}
+
+static struct snd_kcontrol_new vortex_eqtoggle_kcontrol __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "EQ Enable",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value = 0,
+	.info = snd_vortex_eqtoggle_info,
+	.get = snd_vortex_eqtoggle_get,
+	.put = snd_vortex_eqtoggle_put
+};
+
+static int
+snd_vortex_eq_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0x0000;
+	uinfo->value.integer.max = 0x7fff;
+	return 0;
+}
+
+static int
+snd_vortex_eq_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	vortex_t *vortex = snd_kcontrol_chip(kcontrol);
+	int i = kcontrol->private_value;
+	u16 gainL = 0, gainR = 0;
+
+	vortex_Eqlzr_GetLeftGain(vortex, i, &gainL);
+	vortex_Eqlzr_GetRightGain(vortex, i, &gainR);
+	ucontrol->value.integer.value[0] = gainL;
+	ucontrol->value.integer.value[1] = gainR;
+	return 0;
+}
+
+static int
+snd_vortex_eq_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	vortex_t *vortex = snd_kcontrol_chip(kcontrol);
+	int changed = 0, i = kcontrol->private_value;
+	u16 gainL = 0, gainR = 0;
+
+	vortex_Eqlzr_GetLeftGain(vortex, i, &gainL);
+	vortex_Eqlzr_GetRightGain(vortex, i, &gainR);
+
+	if (gainL != ucontrol->value.integer.value[0]) {
+		vortex_Eqlzr_SetLeftGain(vortex, i,
+					 ucontrol->value.integer.value[0]);
+		changed = 1;
+	}
+	if (gainR != ucontrol->value.integer.value[1]) {
+		vortex_Eqlzr_SetRightGain(vortex, i,
+					  ucontrol->value.integer.value[1]);
+		changed = 1;
+	}
+	return changed;
+}
+
+static struct snd_kcontrol_new vortex_eq_kcontrol __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "                        .",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value = 0,
+	.info = snd_vortex_eq_info,
+	.get = snd_vortex_eq_get,
+	.put = snd_vortex_eq_put
+};
+
+static int
+snd_vortex_peaks_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 20;
+	uinfo->value.integer.min = 0x0000;
+	uinfo->value.integer.max = 0x7fff;
+	return 0;
+}
+
+static int
+snd_vortex_peaks_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	vortex_t *vortex = snd_kcontrol_chip(kcontrol);
+	int i, count = 0;
+	u16 peaks[20];
+
+	vortex_Eqlzr_GetAllPeaks(vortex, peaks, &count);
+	if (count != 20) {
+		printk(KERN_ERR "vortex: peak count error 20 != %d \n", count);
+		return -1;
+	}
+	for (i = 0; i < 20; i++)
+		ucontrol->value.integer.value[i] = peaks[i];
+
+	return 0;
+}
+
+static struct snd_kcontrol_new vortex_levels_kcontrol __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "EQ Peaks",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info = snd_vortex_peaks_info,
+	.get = snd_vortex_peaks_get,
+};
+
+/* EQ band gain labels. */
+static char *EqBandLabels[10] __devinitdata = {
+	"EQ0 31Hz\0",
+	"EQ1 63Hz\0",
+	"EQ2 125Hz\0",
+	"EQ3 250Hz\0",
+	"EQ4 500Hz\0",
+	"EQ5 1KHz\0",
+	"EQ6 2KHz\0",
+	"EQ7 4KHz\0",
+	"EQ8 8KHz\0",
+	"EQ9 16KHz\0",
+};
+
+/* ALSA driver entry points. Init and exit. */
+static int __devinit vortex_eq_init(vortex_t * vortex)
+{
+	struct snd_kcontrol *kcontrol;
+	int err, i;
+
+	vortex_Eqlzr_init(vortex);
+
+	if ((kcontrol =
+	     snd_ctl_new1(&vortex_eqtoggle_kcontrol, vortex)) == NULL)
+		return -ENOMEM;
+	kcontrol->private_value = 0;
+	if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+		return err;
+
+	/* EQ gain controls */
+	for (i = 0; i < 10; i++) {
+		if ((kcontrol =
+		     snd_ctl_new1(&vortex_eq_kcontrol, vortex)) == NULL)
+			return -ENOMEM;
+		strcpy(kcontrol->id.name, EqBandLabels[i]);
+		kcontrol->private_value = i;
+		if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+			return err;
+		//vortex->eqctrl[i] = kcontrol;
+	}
+	/* EQ band levels */
+	if ((kcontrol = snd_ctl_new1(&vortex_levels_kcontrol, vortex)) == NULL)
+		return -ENOMEM;
+	if ((err = snd_ctl_add(vortex->card, kcontrol)) < 0)
+		return err;
+
+	return 0;
+}
+
+static int vortex_eq_free(vortex_t * vortex)
+{
+	/*
+	   //FIXME: segfault because vortex->eqctrl[i] == 4
+	   int i;
+	   for (i=0; i<10; i++) {
+	   if (vortex->eqctrl[i])
+	   snd_ctl_remove(vortex->card, vortex->eqctrl[i]);
+	   }
+	 */
+	vortex_Eqlzr_shutdown(vortex);
+	return 0;
+}
+
+/* End */
diff --git a/linux/sound/pci/au88x0/au88x0_eq.h b/linux/sound/pci/au88x0/au88x0_eq.h
new file mode 100644
index 0000000..474cd00
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_eq.h
@@ -0,0 +1,43 @@
+#ifndef AU88X0_EQ_H
+#define AU88X0_EQ_H
+
+/***************************************************************************
+ *            au88x0_eq.h
+ *
+ *  Definitions and constant data for the Aureal Hardware EQ.
+ *
+ *  Sun Jun  8 18:23:38 2003
+ *  Author: Manuel Jander (mjander@users.sourceforge.net)
+ ****************************************************************************/
+
+typedef struct {
+	u16 LeftCoefs[50];	//0x4
+	u16 RightCoefs[50];	// 0x68
+	u16 LeftGains[10];	//0xd0
+	u16 RightGains[10];	//0xe4
+} auxxEqCoeffSet_t;
+
+typedef struct {
+	s32 this04;		/* How many filters for each side (default = 10) */
+	s32 this08;		/* inited to cero. Stereo flag? */
+} eqhw_t;
+
+typedef struct {
+	eqhw_t this04;		/* CHwEq */
+	u16 this08;		/* Bad codec flag ? SetBypassGain: bypass gain */
+	u16 this0a;
+	u16 this0c;		/* SetBypassGain: bypass gain when this28 is not set. */
+	u16 this0e;
+
+	s32 this10;		/* How many gains are used for each side (right or left). */
+	u16 this14_array[10];	/* SetLeftGainsTarget: Left (and right?) EQ gains  */
+	s32 this28;		/* flag related to EQ enabled or not. Gang flag ? */
+	s32 this54;		/* SetBypass */
+	s32 this58;
+	s32 this5c;
+	/*0x60 */ auxxEqCoeffSet_t coefset;
+	/* 50 u16 word each channel. */
+	u16 this130[20];	/* Left and Right gains */
+} eqlzr_t;
+
+#endif
diff --git a/linux/sound/pci/au88x0/au88x0_eqdata.c b/linux/sound/pci/au88x0/au88x0_eqdata.c
new file mode 100644
index 0000000..ce8dca8
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_eqdata.c
@@ -0,0 +1,116 @@
+/* Data structs */
+
+static u16 asEqCoefsZeros[50] = {
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+};
+
+static u16 asEqCoefsPipes[64] = {
+	0x0000, 0x0000,
+	0x0000, 0x0666, 0x0000, 0x0000, 0x0666,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0666, 0x0000, 0x0000, 0x0666,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0666, 0x0000, 0x0000, 0x0666,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0666, 0x0000, 0x0000, 0x0666,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0666, 0x0000, 0x0000, 0x066a,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000
+};
+
+/* More coef sets can be found in the win2k "inf" file. */
+static auxxEqCoeffSet_t asEqCoefsNormal = {
+	.LeftCoefs = {
+		      0x7e60, 0xc19e, 0x0001, 0x0002, 0x0001,
+		      0x7fa0, 0xc05f, 0x004f, 0x0000, 0xffb1,
+		      0x7f3f, 0xc0bc, 0x00c2, 0x0000, 0xff3e,
+		      0x7e78, 0xc177, 0x011f, 0x0000, 0xfee1,
+		      0x7cd6, 0xc2e5, 0x025c, 0x0000, 0xfda4,
+		      0x7949, 0xc5aa, 0x0467, 0x0000, 0xfb99,
+		      0x7120, 0xcadf, 0x0864, 0x0000, 0xf79c,
+		      0x5d33, 0xd430, 0x0f7e, 0x0000, 0xf082,
+		      0x2beb, 0xe3ca, 0x1bd3, 0x0000, 0xe42d,
+		      0xd740, 0xf01d, 0x2ac5, 0x0000, 0xd53b},
+
+	.RightCoefs = {
+		       0x7e60, 0xc19e, 0x0001, 0x0002, 0x0001,
+		       0x7fa0, 0xc05f, 0x004f, 0x0000, 0xffb1,
+		       0x7f3f, 0xc0bc, 0x00c2, 0x0000, 0xff3e,
+		       0x7e78, 0xc177, 0x011f, 0x0000, 0xfee1,
+		       0x7cd6, 0xc2e5, 0x025c, 0x0000, 0xfda4,
+		       0x7949, 0xc5aa, 0x0467, 0x0000, 0xfb99,
+		       0x7120, 0xcadf, 0x0864, 0x0000, 0xf79c,
+		       0x5d33, 0xd430, 0x0f7e, 0x0000, 0xf082,
+		       0x2beb, 0xe3ca, 0x1bd3, 0x0000, 0xe42d,
+		       0xd740, 0xf01d, 0x2ac5, 0x0000, 0xd53b},
+
+	.LeftGains = {
+		      0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96,
+		      0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96},
+	.RightGains = {
+		       0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96,
+		       0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96}
+};
+
+static u16 eq_gains_normal[20] = {
+	0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96,
+	0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96,
+	0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96,
+	0x3e96, 0x3e96, 0x3e96, 0x3e96, 0x3e96
+};
+
+/* _rodatab60 */
+static u16 eq_gains_zero[10] = {
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000
+};
+
+/* _rodatab7c:  ProgramPipe */
+static u16 eq_gains_current[12] = {
+	0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff,
+	0x7fff,
+	0x7fff, 0x7fff, 0x7fff
+};
+
+/* _rodatab78 */
+static u16 eq_states_zero[2] = { 0x0000, 0x0000 };
+
+static u16 asEqOutStateZeros[48] = {
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000
+};
+
+/*_rodataba0:*/
+static u16 eq_levels[64] = {
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
+};
diff --git a/linux/sound/pci/au88x0/au88x0_game.c b/linux/sound/pci/au88x0/au88x0_game.c
new file mode 100644
index 0000000..e291aa5
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_game.c
@@ -0,0 +1,132 @@
+/*
+ *  Manuel Jander.
+ *
+ *  Based on the work of:
+ *  Vojtech Pavlik
+ *  Raymond Ingles
+ *
+ * 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
+ *
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to <vojtech@suse.cz>, or by paper mail:
+ * Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00 Czech Republic
+ *
+ * Based 90% on Vojtech Pavlik pcigame driver.
+ * Merged and modified by Manuel Jander, for the OpenVortex
+ * driver. (email: mjander@embedded.cl).
+ */
+
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include "au88x0.h"
+#include <linux/gameport.h>
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+
+#define VORTEX_GAME_DWAIT	20	/* 20 ms */
+
+static unsigned char vortex_game_read(struct gameport *gameport)
+{
+	vortex_t *vortex = gameport_get_port_data(gameport);
+	return hwread(vortex->mmio, VORTEX_GAME_LEGACY);
+}
+
+static void vortex_game_trigger(struct gameport *gameport)
+{
+	vortex_t *vortex = gameport_get_port_data(gameport);
+	hwwrite(vortex->mmio, VORTEX_GAME_LEGACY, 0xff);
+}
+
+static int
+vortex_game_cooked_read(struct gameport *gameport, int *axes, int *buttons)
+{
+	vortex_t *vortex = gameport_get_port_data(gameport);
+	int i;
+
+	*buttons = (~hwread(vortex->mmio, VORTEX_GAME_LEGACY) >> 4) & 0xf;
+
+	for (i = 0; i < 4; i++) {
+		axes[i] =
+		    hwread(vortex->mmio, VORTEX_GAME_AXIS + (i * AXIS_SIZE));
+		if (axes[i] == AXIS_RANGE)
+			axes[i] = -1;
+	}
+	return 0;
+}
+
+static int vortex_game_open(struct gameport *gameport, int mode)
+{
+	vortex_t *vortex = gameport_get_port_data(gameport);
+
+	switch (mode) {
+	case GAMEPORT_MODE_COOKED:
+		hwwrite(vortex->mmio, VORTEX_CTRL2,
+			hwread(vortex->mmio,
+			       VORTEX_CTRL2) | CTRL2_GAME_ADCMODE);
+		msleep(VORTEX_GAME_DWAIT);
+		return 0;
+	case GAMEPORT_MODE_RAW:
+		hwwrite(vortex->mmio, VORTEX_CTRL2,
+			hwread(vortex->mmio,
+			       VORTEX_CTRL2) & ~CTRL2_GAME_ADCMODE);
+		return 0;
+	default:
+		return -1;
+	}
+
+	return 0;
+}
+
+static int __devinit vortex_gameport_register(vortex_t * vortex)
+{
+	struct gameport *gp;
+
+	vortex->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "vortex: cannot allocate memory for gameport\n");
+		return -ENOMEM;
+	};
+
+	gameport_set_name(gp, "AU88x0 Gameport");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(vortex->pci_dev));
+	gameport_set_dev_parent(gp, &vortex->pci_dev->dev);
+
+	gp->read = vortex_game_read;
+	gp->trigger = vortex_game_trigger;
+	gp->cooked_read = vortex_game_cooked_read;
+	gp->open = vortex_game_open;
+
+	gameport_set_port_data(gp, vortex);
+	gp->fuzz = 64;
+
+	gameport_register_port(gp);
+
+	return 0;
+}
+
+static void vortex_gameport_unregister(vortex_t * vortex)
+{
+	if (vortex->gameport) {
+		gameport_unregister_port(vortex->gameport);
+		vortex->gameport = NULL;
+	}
+}
+
+#else
+static inline int vortex_gameport_register(vortex_t * vortex) { return -ENOSYS; }
+static inline void vortex_gameport_unregister(vortex_t * vortex) { }
+#endif
diff --git a/linux/sound/pci/au88x0/au88x0_mixer.c b/linux/sound/pci/au88x0/au88x0_mixer.c
new file mode 100644
index 0000000..557c782
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_mixer.c
@@ -0,0 +1,32 @@
+/*
+ * Vortex Mixer support.
+ *
+ * There is much more than just the AC97 mixer...
+ *
+ */
+
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include "au88x0.h"
+
+static int __devinit snd_vortex_mixer(vortex_t * vortex)
+{
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = vortex_codec_write,
+		.read = vortex_codec_read,
+	};
+
+	if ((err = snd_ac97_bus(vortex->card, 0, &ops, NULL, &pbus)) < 0)
+		return err;
+	memset(&ac97, 0, sizeof(ac97));
+	// Initialize AC97 codec stuff.
+	ac97.private_data = vortex;
+	ac97.scaps = AC97_SCAP_NO_SPDIF;
+	err = snd_ac97_mixer(pbus, &ac97, &vortex->codec);
+	vortex->isquad = ((vortex->codec == NULL) ?  0 : (vortex->codec->ext_id&0x80));
+	return err;
+}
diff --git a/linux/sound/pci/au88x0/au88x0_mpu401.c b/linux/sound/pci/au88x0/au88x0_mpu401.c
new file mode 100644
index 0000000..0dc8d25
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_mpu401.c
@@ -0,0 +1,112 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for control of MPU-401 in UART mode
+ *
+ *   Modified for the Aureal Vortex based Soundcards
+ *   by Manuel Jander (mjande@embedded.cl).
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/mpu401.h>
+#include "au88x0.h"
+
+/* Check for mpu401 mmio support. */
+/* MPU401 legacy support is only provided as a emergency fallback *
+ * for older versions of ALSA. Its usage is strongly discouraged. */
+#ifndef MPU401_HW_AUREAL
+#define VORTEX_MPU401_LEGACY
+#endif
+
+/* Vortex MPU401 defines. */
+#define MIDI_CLOCK_DIV      0x61
+/* Standart MPU401 defines. */
+#define MPU401_RESET		0xff
+#define MPU401_ENTER_UART	0x3f
+#define MPU401_ACK		    0xfe
+
+static int __devinit snd_vortex_midi(vortex_t * vortex)
+{
+	struct snd_rawmidi *rmidi;
+	int temp, mode;
+	struct snd_mpu401 *mpu;
+	unsigned long port;
+
+#ifdef VORTEX_MPU401_LEGACY
+	/* EnableHardCodedMPU401Port() */
+	/* Enable Legacy MIDI Interface port. */
+	port = (0x03 << 5);	/* FIXME: static address. 0x330 */
+	temp =
+	    (hwread(vortex->mmio, VORTEX_CTRL) & ~CTRL_MIDI_PORT) |
+	    CTRL_MIDI_EN | port;
+	hwwrite(vortex->mmio, VORTEX_CTRL, temp);
+#else
+	/* Disable Legacy MIDI Interface port. */
+	temp =
+	    (hwread(vortex->mmio, VORTEX_CTRL) & ~CTRL_MIDI_PORT) &
+	    ~CTRL_MIDI_EN;
+	hwwrite(vortex->mmio, VORTEX_CTRL, temp);
+#endif
+	/* Mpu401UartInit() */
+	mode = 1;
+	temp = hwread(vortex->mmio, VORTEX_CTRL2) & 0xffff00cf;
+	temp |= (MIDI_CLOCK_DIV << 8) | ((mode >> 24) & 0xff) << 4;
+	hwwrite(vortex->mmio, VORTEX_CTRL2, temp);
+	hwwrite(vortex->mmio, VORTEX_MIDI_CMD, MPU401_RESET);
+
+	/* Check if anything is OK. */
+	temp = hwread(vortex->mmio, VORTEX_MIDI_DATA);
+	if (temp != MPU401_ACK /*0xfe */ ) {
+		printk(KERN_ERR "midi port doesn't acknowledge!\n");
+		return -ENODEV;
+	}
+	/* Enable MPU401 interrupts. */
+	hwwrite(vortex->mmio, VORTEX_IRQ_CTRL,
+		hwread(vortex->mmio, VORTEX_IRQ_CTRL) | IRQ_MIDI);
+
+	/* Create MPU401 instance. */
+#ifdef VORTEX_MPU401_LEGACY
+	if ((temp =
+	     snd_mpu401_uart_new(vortex->card, 0, MPU401_HW_MPU401, 0x330,
+				 0, 0, 0, &rmidi)) != 0) {
+		hwwrite(vortex->mmio, VORTEX_CTRL,
+			(hwread(vortex->mmio, VORTEX_CTRL) &
+			 ~CTRL_MIDI_PORT) & ~CTRL_MIDI_EN);
+		return temp;
+	}
+#else
+	port = (unsigned long)(vortex->mmio + VORTEX_MIDI_DATA);
+	if ((temp =
+	     snd_mpu401_uart_new(vortex->card, 0, MPU401_HW_AUREAL, port,
+				 MPU401_INFO_INTEGRATED | MPU401_INFO_MMIO,
+				 0, 0, &rmidi)) != 0) {
+		hwwrite(vortex->mmio, VORTEX_CTRL,
+			(hwread(vortex->mmio, VORTEX_CTRL) &
+			 ~CTRL_MIDI_PORT) & ~CTRL_MIDI_EN);
+		return temp;
+	}
+	mpu = rmidi->private_data;
+	mpu->cport = (unsigned long)(vortex->mmio + VORTEX_MIDI_CMD);
+#endif
+	/* Overwrite MIDI name */
+	snprintf(rmidi->name, sizeof(rmidi->name), "%s MIDI %d", CARD_NAME_SHORT , vortex->card->number);
+
+	vortex->rmidi = rmidi;
+	return 0;
+}
diff --git a/linux/sound/pci/au88x0/au88x0_pcm.c b/linux/sound/pci/au88x0/au88x0_pcm.c
new file mode 100644
index 0000000..b9d2f20
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_pcm.c
@@ -0,0 +1,539 @@
+/*
+ *  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 Library 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.
+ */
+ 
+/*
+ * Vortex PCM ALSA driver.
+ *
+ * Supports ADB and WT DMA. Unfortunately, WT channels do not run yet.
+ * It remains stuck,and DMA transfers do not happen. 
+ */
+#include <sound/asoundef.h>
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "au88x0.h"
+
+#define VORTEX_PCM_TYPE(x) (x->name[40])
+
+/* hardware definition */
+static struct snd_pcm_hardware snd_vortex_playback_hw_adb = {
+	.info =
+	    (SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */
+	     SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED |
+	     SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =
+	    SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 |
+	    SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW,
+	.rates = SNDRV_PCM_RATE_CONTINUOUS,
+	.rate_min = 5000,
+	.rate_max = 48000,
+	.channels_min = 1,
+#ifdef CHIP_AU8830
+	.channels_max = 4,
+#else
+	.channels_max = 2,
+#endif
+	.buffer_bytes_max = 0x10000,
+	.period_bytes_min = 0x1,
+	.period_bytes_max = 0x1000,
+	.periods_min = 2,
+	.periods_max = 32,
+};
+
+#ifndef CHIP_AU8820
+static struct snd_pcm_hardware snd_vortex_playback_hw_a3d = {
+	.info =
+	    (SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */
+	     SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED |
+	     SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =
+	    SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 |
+	    SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW,
+	.rates = SNDRV_PCM_RATE_CONTINUOUS,
+	.rate_min = 5000,
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 1,
+	.buffer_bytes_max = 0x10000,
+	.period_bytes_min = 0x100,
+	.period_bytes_max = 0x1000,
+	.periods_min = 2,
+	.periods_max = 64,
+};
+#endif
+static struct snd_pcm_hardware snd_vortex_playback_hw_spdif = {
+	.info =
+	    (SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */
+	     SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED |
+	     SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =
+	    SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 |
+	    SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE | SNDRV_PCM_FMTBIT_MU_LAW |
+	    SNDRV_PCM_FMTBIT_A_LAW,
+	.rates =
+	    SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+	.rate_min = 32000,
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = 0x10000,
+	.period_bytes_min = 0x100,
+	.period_bytes_max = 0x1000,
+	.periods_min = 2,
+	.periods_max = 64,
+};
+
+#ifndef CHIP_AU8810
+static struct snd_pcm_hardware snd_vortex_playback_hw_wt = {
+	.info = (SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS,	// SNDRV_PCM_RATE_48000,
+	.rate_min = 8000,
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = 0x10000,
+	.period_bytes_min = 0x0400,
+	.period_bytes_max = 0x1000,
+	.periods_min = 2,
+	.periods_max = 64,
+};
+#endif
+/* open callback */
+static int snd_vortex_pcm_open(struct snd_pcm_substream *substream)
+{
+	vortex_t *vortex = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+	
+	/* Force equal size periods */
+	if ((err =
+	     snd_pcm_hw_constraint_integer(runtime,
+					   SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+	/* Avoid PAGE_SIZE boundary to fall inside of a period. */
+	if ((err =
+	     snd_pcm_hw_constraint_pow2(runtime, 0,
+					SNDRV_PCM_HW_PARAM_PERIOD_BYTES)) < 0)
+		return err;
+
+	if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
+#ifndef CHIP_AU8820
+		if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_A3D) {
+			runtime->hw = snd_vortex_playback_hw_a3d;
+		}
+#endif
+		if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_SPDIF) {
+			runtime->hw = snd_vortex_playback_hw_spdif;
+			switch (vortex->spdif_sr) {
+			case 32000:
+				runtime->hw.rates = SNDRV_PCM_RATE_32000;
+				break;
+			case 44100:
+				runtime->hw.rates = SNDRV_PCM_RATE_44100;
+				break;
+			case 48000:
+				runtime->hw.rates = SNDRV_PCM_RATE_48000;
+				break;
+			}
+		}
+		if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB
+		    || VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_I2S)
+			runtime->hw = snd_vortex_playback_hw_adb;
+		substream->runtime->private_data = NULL;
+	}
+#ifndef CHIP_AU8810
+	else {
+		runtime->hw = snd_vortex_playback_hw_wt;
+		substream->runtime->private_data = NULL;
+	}
+#endif
+	return 0;
+}
+
+/* close callback */
+static int snd_vortex_pcm_close(struct snd_pcm_substream *substream)
+{
+	//vortex_t *chip = snd_pcm_substream_chip(substream);
+	stream_t *stream = (stream_t *) substream->runtime->private_data;
+
+	// the hardware-specific codes will be here
+	if (stream != NULL) {
+		stream->substream = NULL;
+		stream->nr_ch = 0;
+	}
+	substream->runtime->private_data = NULL;
+	return 0;
+}
+
+/* hw_params callback */
+static int
+snd_vortex_pcm_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *hw_params)
+{
+	vortex_t *chip = snd_pcm_substream_chip(substream);
+	stream_t *stream = (stream_t *) (substream->runtime->private_data);
+	int err;
+
+	// Alloc buffer memory.
+	err =
+	    snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+	if (err < 0) {
+		printk(KERN_ERR "Vortex: pcm page alloc failed!\n");
+		return err;
+	}
+	/*
+	   printk(KERN_INFO "Vortex: periods %d, period_bytes %d, channels = %d\n", params_periods(hw_params),
+	   params_period_bytes(hw_params), params_channels(hw_params));
+	 */
+	spin_lock_irq(&chip->lock);
+	// Make audio routes and config buffer DMA.
+	if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
+		int dma, type = VORTEX_PCM_TYPE(substream->pcm);
+		/* Dealloc any routes. */
+		if (stream != NULL)
+			vortex_adb_allocroute(chip, stream->dma,
+					      stream->nr_ch, stream->dir,
+					      stream->type);
+		/* Alloc routes. */
+		dma =
+		    vortex_adb_allocroute(chip, -1,
+					  params_channels(hw_params),
+					  substream->stream, type);
+		if (dma < 0) {
+			spin_unlock_irq(&chip->lock);
+			return dma;
+		}
+		stream = substream->runtime->private_data = &chip->dma_adb[dma];
+		stream->substream = substream;
+		/* Setup Buffers. */
+		vortex_adbdma_setbuffers(chip, dma,
+					 params_period_bytes(hw_params),
+					 params_periods(hw_params));
+	}
+#ifndef CHIP_AU8810
+	else {
+		/* if (stream != NULL)
+		   vortex_wt_allocroute(chip, substream->number, 0); */
+		vortex_wt_allocroute(chip, substream->number,
+				     params_channels(hw_params));
+		stream = substream->runtime->private_data =
+		    &chip->dma_wt[substream->number];
+		stream->dma = substream->number;
+		stream->substream = substream;
+		vortex_wtdma_setbuffers(chip, substream->number,
+					params_period_bytes(hw_params),
+					params_periods(hw_params));
+	}
+#endif
+	spin_unlock_irq(&chip->lock);
+	return 0;
+}
+
+/* hw_free callback */
+static int snd_vortex_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	vortex_t *chip = snd_pcm_substream_chip(substream);
+	stream_t *stream = (stream_t *) (substream->runtime->private_data);
+
+	spin_lock_irq(&chip->lock);
+	// Delete audio routes.
+	if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
+		if (stream != NULL)
+			vortex_adb_allocroute(chip, stream->dma,
+					      stream->nr_ch, stream->dir,
+					      stream->type);
+	}
+#ifndef CHIP_AU8810
+	else {
+		if (stream != NULL)
+			vortex_wt_allocroute(chip, stream->dma, 0);
+	}
+#endif
+	substream->runtime->private_data = NULL;
+	spin_unlock_irq(&chip->lock);
+
+	return snd_pcm_lib_free_pages(substream);
+}
+
+/* prepare callback */
+static int snd_vortex_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	vortex_t *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	stream_t *stream = (stream_t *) substream->runtime->private_data;
+	int dma = stream->dma, fmt, dir;
+
+	// set up the hardware with the current configuration.
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dir = 1;
+	else
+		dir = 0;
+	fmt = vortex_alsafmt_aspfmt(runtime->format);
+	spin_lock_irq(&chip->lock);
+	if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
+		vortex_adbdma_setmode(chip, dma, 1, dir, fmt, 0 /*? */ ,
+				      0);
+		vortex_adbdma_setstartbuffer(chip, dma, 0);
+		if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_SPDIF)
+			vortex_adb_setsrc(chip, dma, runtime->rate, dir);
+	}
+#ifndef CHIP_AU8810
+	else {
+		vortex_wtdma_setmode(chip, dma, 1, fmt, 0, 0);
+		// FIXME: Set rate (i guess using vortex_wt_writereg() somehow).
+		vortex_wtdma_setstartbuffer(chip, dma, 0);
+	}
+#endif
+	spin_unlock_irq(&chip->lock);
+	return 0;
+}
+
+/* trigger callback */
+static int snd_vortex_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	vortex_t *chip = snd_pcm_substream_chip(substream);
+	stream_t *stream = (stream_t *) substream->runtime->private_data;
+	int dma = stream->dma;
+
+	spin_lock(&chip->lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		// do something to start the PCM engine
+		//printk(KERN_INFO "vortex: start %d\n", dma);
+		stream->fifo_enabled = 1;
+		if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
+			vortex_adbdma_resetup(chip, dma);
+			vortex_adbdma_startfifo(chip, dma);
+		}
+#ifndef CHIP_AU8810
+		else {
+			printk(KERN_INFO "vortex: wt start %d\n", dma);
+			vortex_wtdma_startfifo(chip, dma);
+		}
+#endif
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		// do something to stop the PCM engine
+		//printk(KERN_INFO "vortex: stop %d\n", dma);
+		stream->fifo_enabled = 0;
+		if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
+			vortex_adbdma_pausefifo(chip, dma);
+		//vortex_adbdma_stopfifo(chip, dma);
+#ifndef CHIP_AU8810
+		else {
+			printk(KERN_INFO "vortex: wt stop %d\n", dma);
+			vortex_wtdma_stopfifo(chip, dma);
+		}
+#endif
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		//printk(KERN_INFO "vortex: pause %d\n", dma);
+		if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
+			vortex_adbdma_pausefifo(chip, dma);
+#ifndef CHIP_AU8810
+		else
+			vortex_wtdma_pausefifo(chip, dma);
+#endif
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		//printk(KERN_INFO "vortex: resume %d\n", dma);
+		if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
+			vortex_adbdma_resumefifo(chip, dma);
+#ifndef CHIP_AU8810
+		else
+			vortex_wtdma_resumefifo(chip, dma);
+#endif
+		break;
+	default:
+		spin_unlock(&chip->lock);
+		return -EINVAL;
+	}
+	spin_unlock(&chip->lock);
+	return 0;
+}
+
+/* pointer callback */
+static snd_pcm_uframes_t snd_vortex_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	vortex_t *chip = snd_pcm_substream_chip(substream);
+	stream_t *stream = (stream_t *) substream->runtime->private_data;
+	int dma = stream->dma;
+	snd_pcm_uframes_t current_ptr = 0;
+
+	spin_lock(&chip->lock);
+	if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
+		current_ptr = vortex_adbdma_getlinearpos(chip, dma);
+#ifndef CHIP_AU8810
+	else
+		current_ptr = vortex_wtdma_getlinearpos(chip, dma);
+#endif
+	//printk(KERN_INFO "vortex: pointer = 0x%x\n", current_ptr);
+	spin_unlock(&chip->lock);
+	return (bytes_to_frames(substream->runtime, current_ptr));
+}
+
+/* operators */
+static struct snd_pcm_ops snd_vortex_playback_ops = {
+	.open = snd_vortex_pcm_open,
+	.close = snd_vortex_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_vortex_pcm_hw_params,
+	.hw_free = snd_vortex_pcm_hw_free,
+	.prepare = snd_vortex_pcm_prepare,
+	.trigger = snd_vortex_pcm_trigger,
+	.pointer = snd_vortex_pcm_pointer,
+	.page = snd_pcm_sgbuf_ops_page,
+};
+
+/*
+*  definitions of capture are omitted here...
+*/
+
+static char *vortex_pcm_prettyname[VORTEX_PCM_LAST] = {
+	"AU88x0 ADB",
+	"AU88x0 SPDIF",
+	"AU88x0 A3D",
+	"AU88x0 WT",
+	"AU88x0 I2S",
+};
+static char *vortex_pcm_name[VORTEX_PCM_LAST] = {
+	"adb",
+	"spdif",
+	"a3d",
+	"wt",
+	"i2s",
+};
+
+/* SPDIF kcontrol */
+
+static int snd_vortex_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_vortex_spdif_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS;
+	return 0;
+}
+
+static int snd_vortex_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	vortex_t *vortex = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.iec958.status[0] = 0x00;
+	ucontrol->value.iec958.status[1] = IEC958_AES1_CON_ORIGINAL|IEC958_AES1_CON_DIGDIGCONV_ID;
+	ucontrol->value.iec958.status[2] = 0x00;
+	switch (vortex->spdif_sr) {
+	case 32000: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_32000; break;
+	case 44100: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_44100; break;
+	case 48000: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_48000; break;
+	}
+	return 0;
+}
+
+static int snd_vortex_spdif_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	vortex_t *vortex = snd_kcontrol_chip(kcontrol);
+	int spdif_sr = 48000;
+	switch (ucontrol->value.iec958.status[3] & IEC958_AES3_CON_FS) {
+	case IEC958_AES3_CON_FS_32000: spdif_sr = 32000; break;
+	case IEC958_AES3_CON_FS_44100: spdif_sr = 44100; break;
+	case IEC958_AES3_CON_FS_48000: spdif_sr = 48000; break;
+	}
+	if (spdif_sr == vortex->spdif_sr)
+		return 0;
+	vortex->spdif_sr = spdif_sr;
+	vortex_spdif_init(vortex, vortex->spdif_sr, 1);
+	return 1;
+}
+
+/* spdif controls */
+static struct snd_kcontrol_new snd_vortex_mixer_spdif[] __devinitdata = {
+	{
+		.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+		.info =		snd_vortex_spdif_info,
+		.get =		snd_vortex_spdif_get,
+		.put =		snd_vortex_spdif_put,
+	},
+	{
+		.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
+		.info =		snd_vortex_spdif_info,
+		.get =		snd_vortex_spdif_mask_get
+	},
+};
+
+/* create a pcm device */
+static int __devinit snd_vortex_new_pcm(vortex_t *chip, int idx, int nr)
+{
+	struct snd_pcm *pcm;
+	struct snd_kcontrol *kctl;
+	int i;
+	int err, nr_capt;
+
+	if (!chip || idx < 0 || idx >= VORTEX_PCM_LAST)
+		return -ENODEV;
+
+	/* idx indicates which kind of PCM device. ADB, SPDIF, I2S and A3D share the 
+	 * same dma engine. WT uses it own separate dma engine whcih cant capture. */
+	if (idx == VORTEX_PCM_ADB)
+		nr_capt = nr;
+	else
+		nr_capt = 0;
+	err = snd_pcm_new(chip->card, vortex_pcm_prettyname[idx], idx, nr,
+			  nr_capt, &pcm);
+	if (err < 0)
+		return err;
+	strcpy(pcm->name, vortex_pcm_name[idx]);
+	chip->pcm[idx] = pcm;
+	// This is an evil hack, but it saves a lot of duplicated code.
+	VORTEX_PCM_TYPE(pcm) = idx;
+	pcm->private_data = chip;
+	/* set operators */
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_vortex_playback_ops);
+	if (idx == VORTEX_PCM_ADB)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&snd_vortex_playback_ops);
+	
+	/* pre-allocation of Scatter-Gather buffers */
+	
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+					      snd_dma_pci_data(chip->pci_dev),
+					      0x10000, 0x10000);
+
+	if (VORTEX_PCM_TYPE(pcm) == VORTEX_PCM_SPDIF) {
+		for (i = 0; i < ARRAY_SIZE(snd_vortex_mixer_spdif); i++) {
+			kctl = snd_ctl_new1(&snd_vortex_mixer_spdif[i], chip);
+			if (!kctl)
+				return -ENOMEM;
+			if ((err = snd_ctl_add(chip->card, kctl)) < 0)
+				return err;
+		}
+	}
+	return 0;
+}
diff --git a/linux/sound/pci/au88x0/au88x0_synth.c b/linux/sound/pci/au88x0/au88x0_synth.c
new file mode 100644
index 0000000..2805e34
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_synth.c
@@ -0,0 +1,418 @@
+/*
+ *  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 Library 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.
+ */
+
+/*
+ * Someday its supposed to make use of the WT DMA engine
+ * for a Wavetable synthesizer.
+ */
+
+#include "au88x0.h"
+#include "au88x0_wt.h"
+
+static void vortex_fifo_setwtvalid(vortex_t * vortex, int fifo, int en);
+static void vortex_connection_adb_mixin(vortex_t * vortex, int en,
+					unsigned char channel,
+					unsigned char source,
+					unsigned char mixin);
+static void vortex_connection_mixin_mix(vortex_t * vortex, int en,
+					unsigned char mixin,
+					unsigned char mix, int a);
+static void vortex_fifo_wtinitialize(vortex_t * vortex, int fifo, int j);
+static int vortex_wt_SetReg(vortex_t * vortex, unsigned char reg, int wt,
+			    u32 val);
+
+/* WT */
+
+/* Put 2 WT channels together for one stereo interlaced channel. */
+static void vortex_wt_setstereo(vortex_t * vortex, u32 wt, u32 stereo)
+{
+	int temp;
+
+	//temp = hwread(vortex->mmio, 0x80 + ((wt >> 0x5)<< 0xf) + (((wt & 0x1f) >> 1) << 2));
+	temp = hwread(vortex->mmio, WT_STEREO(wt));
+	temp = (temp & 0xfe) | (stereo & 1);
+	//hwwrite(vortex->mmio, 0x80 + ((wt >> 0x5)<< 0xf) + (((wt & 0x1f) >> 1) << 2), temp);
+	hwwrite(vortex->mmio, WT_STEREO(wt), temp);
+}
+
+/* Join to mixdown route. */
+static void vortex_wt_setdsout(vortex_t * vortex, u32 wt, int en)
+{
+	int temp;
+
+	/* There is one DSREG register for each bank (32 voices each). */
+	temp = hwread(vortex->mmio, WT_DSREG((wt >= 0x20) ? 1 : 0));
+	if (en)
+		temp |= (1 << (wt & 0x1f));
+	else
+		temp &= (1 << ~(wt & 0x1f));
+	hwwrite(vortex->mmio, WT_DSREG((wt >= 0x20) ? 1 : 0), temp);
+}
+
+/* Setup WT route. */
+static int vortex_wt_allocroute(vortex_t * vortex, int wt, int nr_ch)
+{
+	wt_voice_t *voice = &(vortex->wt_voice[wt]);
+	int temp;
+
+	//FIXME: WT audio routing.
+	if (nr_ch) {
+		vortex_fifo_wtinitialize(vortex, wt, 1);
+		vortex_fifo_setwtvalid(vortex, wt, 1);
+		vortex_wt_setstereo(vortex, wt, nr_ch - 1);
+	} else
+		vortex_fifo_setwtvalid(vortex, wt, 0);
+	
+	/* Set mixdown mode. */
+	vortex_wt_setdsout(vortex, wt, 1);
+	/* Set other parameter registers. */
+	hwwrite(vortex->mmio, WT_SRAMP(0), 0x880000);
+	//hwwrite(vortex->mmio, WT_GMODE(0), 0xffffffff);
+#ifdef CHIP_AU8830
+	hwwrite(vortex->mmio, WT_SRAMP(1), 0x880000);
+	//hwwrite(vortex->mmio, WT_GMODE(1), 0xffffffff);
+#endif
+	hwwrite(vortex->mmio, WT_PARM(wt, 0), 0);
+	hwwrite(vortex->mmio, WT_PARM(wt, 1), 0);
+	hwwrite(vortex->mmio, WT_PARM(wt, 2), 0);
+
+	temp = hwread(vortex->mmio, WT_PARM(wt, 3));
+	printk(KERN_DEBUG "vortex: WT PARM3: %x\n", temp);
+	//hwwrite(vortex->mmio, WT_PARM(wt, 3), temp);
+
+	hwwrite(vortex->mmio, WT_DELAY(wt, 0), 0);
+	hwwrite(vortex->mmio, WT_DELAY(wt, 1), 0);
+	hwwrite(vortex->mmio, WT_DELAY(wt, 2), 0);
+	hwwrite(vortex->mmio, WT_DELAY(wt, 3), 0);
+
+	printk(KERN_DEBUG "vortex: WT GMODE: %x\n", hwread(vortex->mmio, WT_GMODE(wt)));
+
+	hwwrite(vortex->mmio, WT_PARM(wt, 2), 0xffffffff);
+	hwwrite(vortex->mmio, WT_PARM(wt, 3), 0xcff1c810);
+
+	voice->parm0 = voice->parm1 = 0xcfb23e2f;
+	hwwrite(vortex->mmio, WT_PARM(wt, 0), voice->parm0);
+	hwwrite(vortex->mmio, WT_PARM(wt, 1), voice->parm1);
+	printk(KERN_DEBUG "vortex: WT GMODE 2 : %x\n", hwread(vortex->mmio, WT_GMODE(wt)));
+	return 0;
+}
+
+
+static void vortex_wt_connect(vortex_t * vortex, int en)
+{
+	int i, ii, mix;
+
+#define NR_WTROUTES 6
+#ifdef CHIP_AU8830
+#define NR_WTBLOCKS 2
+#else
+#define NR_WTBLOCKS 1
+#endif
+
+	for (i = 0; i < NR_WTBLOCKS; i++) {
+		for (ii = 0; ii < NR_WTROUTES; ii++) {
+			mix =
+			    vortex_adb_checkinout(vortex,
+						  vortex->fixed_res, en,
+						  VORTEX_RESOURCE_MIXIN);
+			vortex->mixwt[(i * NR_WTROUTES) + ii] = mix;
+
+			vortex_route(vortex, en, 0x11,
+				     ADB_WTOUT(i, ii + 0x20), ADB_MIXIN(mix));
+
+			vortex_connection_mixin_mix(vortex, en, mix,
+						    vortex->mixplayb[ii % 2], 0);
+			if (VORTEX_IS_QUAD(vortex))
+				vortex_connection_mixin_mix(vortex, en,
+							    mix,
+							    vortex->mixplayb[2 +
+								     (ii % 2)], 0);
+		}
+	}
+	for (i = 0; i < NR_WT; i++) {
+		hwwrite(vortex->mmio, WT_RUN(i), 1);
+	}
+}
+
+/* Read WT Register */
+#if 0
+static int vortex_wt_GetReg(vortex_t * vortex, char reg, int wt)
+{
+	//int eax, esi;
+
+	if (reg == 4) {
+		return hwread(vortex->mmio, WT_PARM(wt, 3));
+	}
+	if (reg == 7) {
+		return hwread(vortex->mmio, WT_GMODE(wt));
+	}
+
+	return 0;
+}
+
+/* WT hardware abstraction layer generic register interface. */
+static int
+vortex_wt_SetReg2(vortex_t * vortex, unsigned char reg, int wt,
+		  u16 val)
+{
+	/*
+	   int eax, edx;
+
+	   if (wt >= NR_WT)  // 0x40 -> NR_WT
+	   return 0;
+
+	   if ((reg - 0x20) > 0) {
+	   if ((reg - 0x21) != 0) 
+	   return 0;
+	   eax = ((((b & 0xff) << 0xb) + (edx & 0xff)) << 4) + 0x208; // param 2
+	   } else {
+	   eax = ((((b & 0xff) << 0xb) + (edx & 0xff)) << 4) + 0x20a; // param 3
+	   }
+	   hwwrite(vortex->mmio, eax, c);
+	 */
+	return 1;
+}
+
+/*public: static void __thiscall CWTHal::SetReg(unsigned char,int,unsigned long) */
+#endif
+static int
+vortex_wt_SetReg(vortex_t * vortex, unsigned char reg, int wt,
+		 u32 val)
+{
+	int ecx;
+
+	if ((reg == 5) || ((reg >= 7) && (reg <= 10)) || (reg == 0xc)) {
+		if (wt >= (NR_WT / NR_WT_PB)) {
+			printk
+			    ("vortex: WT SetReg: bank out of range. reg=0x%x, wt=%d\n",
+			     reg, wt);
+			return 0;
+		}
+	} else {
+		if (wt >= NR_WT) {
+			printk(KERN_ERR "vortex: WT SetReg: voice out of range\n");
+			return 0;
+		}
+	}
+	if (reg > 0xc)
+		return 0;
+
+	switch (reg) {
+		/* Voice specific parameters */
+	case 0:		/* running */
+		/*
+		printk(KERN_DEBUG "vortex: WT SetReg(0x%x) = 0x%08x\n",
+		       WT_RUN(wt), (int)val);
+		*/
+		hwwrite(vortex->mmio, WT_RUN(wt), val);
+		return 0xc;
+		break;
+	case 1:		/* param 0 */
+		/*
+		printk(KERN_DEBUG "vortex: WT SetReg(0x%x) = 0x%08x\n",
+		       WT_PARM(wt,0), (int)val);
+		*/
+		hwwrite(vortex->mmio, WT_PARM(wt, 0), val);
+		return 0xc;
+		break;
+	case 2:		/* param 1 */
+		/*
+		printk(KERN_DEBUG "vortex: WT SetReg(0x%x) = 0x%08x\n",
+		       WT_PARM(wt,1), (int)val);
+		*/
+		hwwrite(vortex->mmio, WT_PARM(wt, 1), val);
+		return 0xc;
+		break;
+	case 3:		/* param 2 */
+		/*
+		printk(KERN_DEBUG "vortex: WT SetReg(0x%x) = 0x%08x\n",
+		       WT_PARM(wt,2), (int)val);
+		*/
+		hwwrite(vortex->mmio, WT_PARM(wt, 2), val);
+		return 0xc;
+		break;
+	case 4:		/* param 3 */
+		/*
+		printk(KERN_DEBUG "vortex: WT SetReg(0x%x) = 0x%08x\n",
+		       WT_PARM(wt,3), (int)val);
+		*/
+		hwwrite(vortex->mmio, WT_PARM(wt, 3), val);
+		return 0xc;
+		break;
+	case 6:		/* mute */
+		/*
+		printk(KERN_DEBUG "vortex: WT SetReg(0x%x) = 0x%08x\n",
+		       WT_MUTE(wt), (int)val);
+		*/
+		hwwrite(vortex->mmio, WT_MUTE(wt), val);
+		return 0xc;
+		break;
+	case 0xb:
+		{		/* delay */
+			/*
+			printk(KERN_DEBUG "vortex: WT SetReg(0x%x) = 0x%08x\n",
+			       WT_DELAY(wt,0), (int)val);
+			*/
+			hwwrite(vortex->mmio, WT_DELAY(wt, 3), val);
+			hwwrite(vortex->mmio, WT_DELAY(wt, 2), val);
+			hwwrite(vortex->mmio, WT_DELAY(wt, 1), val);
+			hwwrite(vortex->mmio, WT_DELAY(wt, 0), val);
+			return 0xc;
+		}
+		break;
+		/* Global WT block parameters */
+	case 5:		/* sramp */
+		ecx = WT_SRAMP(wt);
+		break;
+	case 8:		/* aramp */
+		ecx = WT_ARAMP(wt);
+		break;
+	case 9:		/* mramp */
+		ecx = WT_MRAMP(wt);
+		break;
+	case 0xa:		/* ctrl */
+		ecx = WT_CTRL(wt);
+		break;
+	case 0xc:		/* ds_reg */
+		ecx = WT_DSREG(wt);
+		break;
+	default:
+		return 0;
+		break;
+	}
+	/*
+	printk(KERN_DEBUG "vortex: WT SetReg(0x%x) = 0x%08x\n", ecx, (int)val);
+	*/
+	hwwrite(vortex->mmio, ecx, val);
+	return 1;
+}
+
+static void vortex_wt_init(vortex_t * vortex)
+{
+	u32 var4, var8, varc, var10 = 0, edi;
+
+	var10 &= 0xFFFFFFE3;
+	var10 |= 0x22;
+	var10 &= 0xFFFFFEBF;
+	var10 |= 0x80;
+	var10 |= 0x200;
+	var10 &= 0xfffffffe;
+	var10 &= 0xfffffbff;
+	var10 |= 0x1800;
+	// var10 = 0x1AA2
+	var4 = 0x10000000;
+	varc = 0x00830000;
+	var8 = 0x00830000;
+
+	/* Init Bank registers. */
+	for (edi = 0; edi < (NR_WT / NR_WT_PB); edi++) {
+		vortex_wt_SetReg(vortex, 0xc, edi, 0);	/* ds_reg */
+		vortex_wt_SetReg(vortex, 0xa, edi, var10);	/* ctrl  */
+		vortex_wt_SetReg(vortex, 0x9, edi, var4);	/* mramp */
+		vortex_wt_SetReg(vortex, 0x8, edi, varc);	/* aramp */
+		vortex_wt_SetReg(vortex, 0x5, edi, var8);	/* sramp */
+	}
+	/* Init Voice registers. */
+	for (edi = 0; edi < NR_WT; edi++) {
+		vortex_wt_SetReg(vortex, 0x4, edi, 0);	/* param 3 0x20c */
+		vortex_wt_SetReg(vortex, 0x3, edi, 0);	/* param 2 0x208 */
+		vortex_wt_SetReg(vortex, 0x2, edi, 0);	/* param 1 0x204 */
+		vortex_wt_SetReg(vortex, 0x1, edi, 0);	/* param 0 0x200 */
+		vortex_wt_SetReg(vortex, 0xb, edi, 0);	/* delay 0x400 - 0x40c */
+	}
+	var10 |= 1;
+	for (edi = 0; edi < (NR_WT / NR_WT_PB); edi++)
+		vortex_wt_SetReg(vortex, 0xa, edi, var10);	/* ctrl */
+}
+
+/* Extract of CAdbTopology::SetVolume(struct _ASPVOLUME *) */
+#if 0
+static void vortex_wt_SetVolume(vortex_t * vortex, int wt, int vol[])
+{
+	wt_voice_t *voice = &(vortex->wt_voice[wt]);
+	int ecx = vol[1], eax = vol[0];
+
+	/* This is pure guess */
+	voice->parm0 &= 0xff00ffff;
+	voice->parm0 |= (vol[0] & 0xff) << 0x10;
+	voice->parm1 &= 0xff00ffff;
+	voice->parm1 |= (vol[1] & 0xff) << 0x10;
+
+	/* This is real */
+	hwwrite(vortex, WT_PARM(wt, 0), voice->parm0);
+	hwwrite(vortex, WT_PARM(wt, 1), voice->parm0);
+
+	if (voice->this_1D0 & 4) {
+		eax >>= 8;
+		ecx = eax;
+		if (ecx < 0x80)
+			ecx = 0x7f;
+		voice->parm3 &= 0xFFFFC07F;
+		voice->parm3 |= (ecx & 0x7f) << 7;
+		voice->parm3 &= 0xFFFFFF80;
+		voice->parm3 |= (eax & 0x7f);
+	} else {
+		voice->parm3 &= 0xFFE03FFF;
+		voice->parm3 |= (eax & 0xFE00) << 5;
+	}
+
+	hwwrite(vortex, WT_PARM(wt, 3), voice->parm3);
+}
+
+/* Extract of CAdbTopology::SetFrequency(unsigned long arg_0) */
+static void vortex_wt_SetFrequency(vortex_t * vortex, int wt, unsigned int sr)
+{
+	wt_voice_t *voice = &(vortex->wt_voice[wt]);
+	u32 eax, edx;
+
+	//FIXME: 64 bit operation.
+	eax = ((sr << 0xf) * 0x57619F1) & 0xffffffff;
+	edx = (((sr << 0xf) * 0x57619F1)) >> 0x20;
+
+	edx >>= 0xa;
+	edx <<= 1;
+	if (edx) {
+		if (edx & 0x0FFF80000)
+			eax = 0x7fff;
+		else {
+			edx <<= 0xd;
+			eax = 7;
+			while ((edx & 0x80000000) == 0) {
+				edx <<= 1;
+				eax--;
+				if (eax == 0)
+					break;
+			}
+			if (eax)
+				edx <<= 1;
+			eax <<= 0xc;
+			edx >>= 0x14;
+			eax |= edx;
+		}
+	} else
+		eax = 0;
+	voice->parm0 &= 0xffff0001;
+	voice->parm0 |= (eax & 0x7fff) << 1;
+	voice->parm1 = voice->parm0 | 1;
+	// Wt: this_1D4
+	//AuWt::WriteReg((ulong)(this_1DC<<4)+0x200, (ulong)this_1E4);
+	//AuWt::WriteReg((ulong)(this_1DC<<4)+0x204, (ulong)this_1E8);
+	hwwrite(vortex->mmio, WT_PARM(wt, 0), voice->parm0);
+	hwwrite(vortex->mmio, WT_PARM(wt, 1), voice->parm1);
+}
+#endif
+
+/* End of File */
diff --git a/linux/sound/pci/au88x0/au88x0_wt.h b/linux/sound/pci/au88x0/au88x0_wt.h
new file mode 100644
index 0000000..38d98f8
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_wt.h
@@ -0,0 +1,65 @@
+/***************************************************************************
+ *           WT register offsets.
+ *
+ *  Wed Oct 22 13:50:20 2003
+ *  Copyright  2003  mjander
+ *  mjander@users.sourceforge.org
+ ****************************************************************************/
+#ifndef _AU88X0_WT_H
+#define _AU88X0_WT_H
+
+/* WT channels are grouped in banks. Each bank has 0x20 channels. */
+/* Bank register address boundary is 0x8000 */
+
+#define NR_WT_PB 0x20
+
+/* WT bank base register (as dword address). */
+#define WT_BAR(x) (((x)&0xffe0)<<0x8)
+#define WT_BANK(x) (x>>5)
+/* WT Bank registers */
+#define WT_CTRL(bank)	(((((bank)&1)<<0xd) + 0x00)<<2)	/* 0x0000 */
+#define WT_SRAMP(bank)	(((((bank)&1)<<0xd) + 0x01)<<2)	/* 0x0004 */
+#define WT_DSREG(bank)	(((((bank)&1)<<0xd) + 0x02)<<2)	/* 0x0008 */
+#define WT_MRAMP(bank)	(((((bank)&1)<<0xd) + 0x03)<<2)	/* 0x000c */
+#define WT_GMODE(bank)	(((((bank)&1)<<0xd) + 0x04)<<2)	/* 0x0010 */
+#define WT_ARAMP(bank)	(((((bank)&1)<<0xd) + 0x05)<<2)	/* 0x0014 */
+/* WT Voice registers */
+#define WT_STEREO(voice)	((WT_BAR(voice)+ 0x20 +(((voice)&0x1f)>>1))<<2)	/* 0x0080 */
+#define WT_MUTE(voice)		((WT_BAR(voice)+ 0x40 +((voice)&0x1f))<<2)	/* 0x0100 */
+#define WT_RUN(voice)		((WT_BAR(voice)+ 0x60 +((voice)&0x1f))<<2)	/* 0x0180 */
+/* Some kind of parameters. */
+/* PARM0, PARM1 : Filter (0xFF000000), SampleRate (0x0000FFFF) */
+/* PARM2, PARM3 : Still unknown */
+#define WT_PARM(x,y)	(((WT_BAR(x))+ 0x80 +(((x)&0x1f)<<2)+(y))<<2)	/* 0x0200 */
+#define WT_DELAY(x,y)	(((WT_BAR(x))+ 0x100 +(((x)&0x1f)<<2)+(y))<<2)	/* 0x0400 */
+
+/* Numeric indexes used by SetReg() and GetReg() */
+#if 0
+enum {
+	run = 0,		/* 0  W 1:run 0:stop */
+	parm0,			/* 1  W filter, samplerate */
+	parm1,			/* 2  W filter, samplerate */
+	parm2,			/* 3  W  */
+	parm3,			/* 4  RW volume. This value is calculated using floating point ops. */
+	sramp,			/* 5  W */
+	mute,			/* 6  W 1:mute, 0:unmute */
+	gmode,			/* 7  RO Looks like only bit0 is used. */
+	aramp,			/* 8  W */
+	mramp,			/* 9  W */
+	ctrl,			/* a  W */
+	delay,			/* b  W All 4 values are written at once with same value. */
+	dsreg,			/* c  (R)W */
+} wt_reg;
+#endif
+
+typedef struct {
+	u32 parm0;	/* this_1E4 */
+	u32 parm1;	/* this_1E8 */
+	u32 parm2;	/* this_1EC */
+	u32 parm3;	/* this_1F0 */
+	u32 this_1D0;
+} wt_voice_t;
+
+#endif				/* _AU88X0_WT_H */
+
+/* End of file */
diff --git a/linux/sound/pci/au88x0/au88x0_xtalk.c b/linux/sound/pci/au88x0/au88x0_xtalk.c
new file mode 100644
index 0000000..b4151e2
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_xtalk.c
@@ -0,0 +1,770 @@
+/***************************************************************************
+ *            au88x0_cxtalk.c
+ *
+ *  Wed Nov 19 16:29:47 2003
+ *  Copyright  2003  mjander
+ *  mjander@users.sourceforge.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 Library 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 "au88x0_xtalk.h"
+
+/* Data (a whole lot of data.... ) */
+
+static short const sXtalkWideKLeftEq = 0x269C;
+static short const sXtalkWideKRightEq = 0x269C;
+static short const sXtalkWideKLeftXt = 0xF25E;
+static short const sXtalkWideKRightXt = 0xF25E;
+static short const sXtalkWideShiftLeftEq = 1;
+static short const sXtalkWideShiftRightEq = 1;
+static short const sXtalkWideShiftLeftXt = 0;
+static short const sXtalkWideShiftRightXt = 0;
+static unsigned short const wXtalkWideLeftDelay = 0xd;
+static unsigned short const wXtalkWideRightDelay = 0xd;
+static short const sXtalkNarrowKLeftEq = 0x468D;
+static short const sXtalkNarrowKRightEq = 0x468D;
+static short const sXtalkNarrowKLeftXt = 0xF82E;
+static short const sXtalkNarrowKRightXt = 0xF82E;
+static short const sXtalkNarrowShiftLeftEq = 0x3;
+static short const sXtalkNarrowShiftRightEq = 0x3;
+static short const sXtalkNarrowShiftLeftXt = 0;
+static short const sXtalkNarrowShiftRightXt = 0;
+static unsigned short const wXtalkNarrowLeftDelay = 0x7;
+static unsigned short const wXtalkNarrowRightDelay = 0x7;
+
+static xtalk_gains_t const asXtalkGainsDefault = {
+	0x4000, 0x4000, 4000, 0x4000, 4000, 0x4000, 4000, 0x4000, 4000,
+	0x4000
+};
+
+static xtalk_gains_t const asXtalkGainsTest = {
+	0x8000, 0x7FFF, 0, 0xFFFF, 0x0001, 0xC000, 0x4000, 0xFFFE, 0x0002,
+	0
+};
+static xtalk_gains_t const asXtalkGains1Chan = {
+	0x7FFF, 0, 0, 0, 0x7FFF, 0, 0, 0, 0, 0
+};
+
+// Input gain for 4 A3D slices. One possible input pair is left zero.
+static xtalk_gains_t const asXtalkGainsAllChan = {
+	0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
+	0
+	    //0x7FFF,0x7FFF,0x7FFF,0x7FFF,0x7fff,0x7FFF,0x7FFF,0x7FFF,0x7FFF,0x7fff
+};
+static xtalk_gains_t const asXtalkGainsZeros;
+
+static xtalk_dline_t const alXtalkDlineZeros;
+static xtalk_dline_t const alXtalkDlineTest = {
+	0xFC18, 0x03E8FFFF, 0x186A0, 0x7960FFFE, 1, 0xFFFFFFFF,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0
+};
+
+static xtalk_instate_t const asXtalkInStateZeros;
+static xtalk_instate_t const asXtalkInStateTest =
+    { 0xFF80, 0x0080, 0xFFFF, 0x0001 };
+static xtalk_state_t const asXtalkOutStateZeros;
+
+static short const sDiamondKLeftEq = 0x401d;
+static short const sDiamondKRightEq = 0x401d;
+static short const sDiamondKLeftXt = 0xF90E;
+static short const sDiamondKRightXt = 0xF90E;
+static short const sDiamondShiftLeftEq = 1;	/* 0xF90E Is this a bug ??? */
+static short const sDiamondShiftRightEq = 1;
+static short const sDiamondShiftLeftXt = 0;
+static short const sDiamondShiftRightXt = 0;
+static unsigned short const wDiamondLeftDelay = 0xb;
+static unsigned short const wDiamondRightDelay = 0xb;
+
+static xtalk_coefs_t const asXtalkWideCoefsLeftEq = {
+	{0xEC4C, 0xDCE9, 0xFDC2, 0xFEEC, 0},
+	{0x5F60, 0xCBCB, 0xFC26, 0x0305, 0},
+	{0x340B, 0xf504, 0x6CE8, 0x0D23, 0x00E4},
+	{0xD500, 0x8D76, 0xACC7, 0x5B05, 0x00FA},
+	{0x7F04, 0xC0FA, 0x0263, 0xFDA2, 0}
+};
+static xtalk_coefs_t const asXtalkWideCoefsRightEq = {
+	{0xEC4C, 0xDCE9, 0xFDC2, 0xFEEC, 0},
+	{0x5F60, 0xCBCB, 0xFC26, 0x0305, 0},
+	{0x340B, 0xF504, 0x6CE8, 0x0D23, 0x00E4},
+	{0xD500, 0x8D76, 0xACC7, 0x5B05, 0x00FA},
+	{0x7F04, 0xC0FA, 0x0263, 0xFDA2, 0}
+};
+static xtalk_coefs_t const asXtalkWideCoefsLeftXt = {
+	{0x86C3, 0x7B55, 0x89C3, 0x005B, 0x0047},
+	{0x6000, 0x206A, 0xC6CA, 0x40FF, 0},
+	{0x1100, 0x1164, 0xA1D7, 0x90FC, 0x0001},
+	{0xDC00, 0x9E77, 0xB8C7, 0x0AFF, 0},
+	{0, 0, 0, 0, 0}
+};
+static xtalk_coefs_t const asXtalkWideCoefsRightXt = {
+	{0x86C3, 0x7B55, 0x89C3, 0x005B, 0x0047},
+	{0x6000, 0x206A, 0xC6CA, 0x40FF, 0},
+	{0x1100, 0x1164, 0xA1D7, 0x90FC, 0x0001},
+	{0xDC00, 0x9E77, 0xB8C7, 0x0AFF, 0},
+	{0, 0, 0, 0, 0}
+};
+static xtalk_coefs_t const asXtalkNarrowCoefsLeftEq = {
+	{0x50B5, 0xD07C, 0x026D, 0xFD21, 0},
+	{0x460F, 0xE44F, 0xF75E, 0xEFA6, 0},
+	{0x556D, 0xDCAB, 0x2098, 0xF0F2, 0},
+	{0x7E03, 0xC1F0, 0x007D, 0xFF89, 0},
+	{0x383E, 0xFD9D, 0xB278, 0x4547, 0}
+};
+
+static xtalk_coefs_t const asXtalkNarrowCoefsRightEq = {
+	{0x50B5, 0xD07C, 0x026D, 0xFD21, 0},
+	{0x460F, 0xE44F, 0xF75E, 0xEFA6, 0},
+	{0x556D, 0xDCAB, 0x2098, 0xF0F2, 0},
+	{0x7E03, 0xC1F0, 0x007D, 0xFF89, 0},
+	{0x383E, 0xFD9D, 0xB278, 0x4547, 0}
+};
+
+static xtalk_coefs_t const asXtalkNarrowCoefsLeftXt = {
+	{0x3CB2, 0xDF49, 0xF6EA, 0x095B, 0},
+	{0x6777, 0xC915, 0xFEAF, 0x00B1, 0},
+	{0x7762, 0xC7D9, 0x025B, 0xFDA6, 0},
+	{0x6B7A, 0xD2AA, 0xF2FB, 0x0B64, 0},
+	{0, 0, 0, 0, 0}
+};
+
+static xtalk_coefs_t const asXtalkNarrowCoefsRightXt = {
+	{0x3CB2, 0xDF49, 0xF6EA, 0x095B, 0},
+	{0x6777, 0xC915, 0xFEAF, 0x00B1, 0},
+	{0x7762, 0xC7D9, 0x025B, 0xFDA6, 0},
+	{0x6B7A, 0xD2AA, 0xF2FB, 0x0B64, 0},
+	{0, 0, 0, 0, 0}
+};
+
+static xtalk_coefs_t const asXtalkCoefsZeros;
+static xtalk_coefs_t const asXtalkCoefsPipe = {
+	{0, 0, 0x0FA0, 0, 0},
+	{0, 0, 0x0FA0, 0, 0},
+	{0, 0, 0x0FA0, 0, 0},
+	{0, 0, 0x0FA0, 0, 0},
+	{0, 0, 0x1180, 0, 0},
+};
+static xtalk_coefs_t const asXtalkCoefsNegPipe = {
+	{0, 0, 0xF380, 0, 0},
+	{0, 0, 0xF380, 0, 0},
+	{0, 0, 0xF380, 0, 0},
+	{0, 0, 0xF380, 0, 0},
+	{0, 0, 0xF200, 0, 0}
+};
+
+static xtalk_coefs_t const asXtalkCoefsNumTest = {
+	{0, 0, 0xF380, 0x8000, 0x6D60},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0}
+};
+
+static xtalk_coefs_t const asXtalkCoefsDenTest = {
+	{0xC000, 0x2000, 0x4000, 0, 0},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0}
+};
+
+static xtalk_state_t const asXtalkOutStateTest = {
+	{0x7FFF, 0x0004, 0xFFFC, 0},
+	{0xFE00, 0x0008, 0xFFF8, 0x4000},
+	{0x200, 0x0010, 0xFFF0, 0xC000},
+	{0x8000, 0x0020, 0xFFE0, 0},
+	{0, 0, 0, 0}
+};
+
+static xtalk_coefs_t const asDiamondCoefsLeftEq = {
+	{0x0F1E, 0x2D05, 0xF8E3, 0x07C8, 0},
+	{0x45E2, 0xCA51, 0x0448, 0xFCE7, 0},
+	{0xA93E, 0xDBD5, 0x022C, 0x028A, 0},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0}
+};
+
+static xtalk_coefs_t const asDiamondCoefsRightEq = {
+	{0x0F1E, 0x2D05, 0xF8E3, 0x07C8, 0},
+	{0x45E2, 0xCA51, 0x0448, 0xFCE7, 0},
+	{0xA93E, 0xDBD5, 0x022C, 0x028A, 0},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0}
+};
+
+static xtalk_coefs_t const asDiamondCoefsLeftXt = {
+	{0x3B50, 0xFE08, 0xF959, 0x0060, 0},
+	{0x9FCB, 0xD8F1, 0x00A2, 0x003A, 0},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0}
+};
+
+static xtalk_coefs_t const asDiamondCoefsRightXt = {
+	{0x3B50, 0xFE08, 0xF959, 0x0060, 0},
+	{0x9FCB, 0xD8F1, 0x00A2, 0x003A, 0},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0},
+	{0, 0, 0, 0, 0}
+};
+
+ /**/
+/* XTalk EQ and XT */
+static void
+vortex_XtalkHw_SetLeftEQ(vortex_t * vortex, short arg_0, short arg_4,
+			 xtalk_coefs_t const coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		hwwrite(vortex->mmio, 0x24200 + i * 0x24, coefs[i][0]);
+		hwwrite(vortex->mmio, 0x24204 + i * 0x24, coefs[i][1]);
+		hwwrite(vortex->mmio, 0x24208 + i * 0x24, coefs[i][2]);
+		hwwrite(vortex->mmio, 0x2420c + i * 0x24, coefs[i][3]);
+		hwwrite(vortex->mmio, 0x24210 + i * 0x24, coefs[i][4]);
+	}
+	hwwrite(vortex->mmio, 0x24538, arg_0 & 0xffff);
+	hwwrite(vortex->mmio, 0x2453C, arg_4 & 0xffff);
+}
+
+static void
+vortex_XtalkHw_SetRightEQ(vortex_t * vortex, short arg_0, short arg_4,
+			  xtalk_coefs_t const coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		hwwrite(vortex->mmio, 0x242b4 + i * 0x24, coefs[i][0]);
+		hwwrite(vortex->mmio, 0x242b8 + i * 0x24, coefs[i][1]);
+		hwwrite(vortex->mmio, 0x242bc + i * 0x24, coefs[i][2]);
+		hwwrite(vortex->mmio, 0x242c0 + i * 0x24, coefs[i][3]);
+		hwwrite(vortex->mmio, 0x242c4 + i * 0x24, coefs[i][4]);
+	}
+	hwwrite(vortex->mmio, 0x24540, arg_0 & 0xffff);
+	hwwrite(vortex->mmio, 0x24544, arg_4 & 0xffff);
+}
+
+static void
+vortex_XtalkHw_SetLeftXT(vortex_t * vortex, short arg_0, short arg_4,
+			 xtalk_coefs_t const coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		hwwrite(vortex->mmio, 0x24368 + i * 0x24, coefs[i][0]);
+		hwwrite(vortex->mmio, 0x2436c + i * 0x24, coefs[i][1]);
+		hwwrite(vortex->mmio, 0x24370 + i * 0x24, coefs[i][2]);
+		hwwrite(vortex->mmio, 0x24374 + i * 0x24, coefs[i][3]);
+		hwwrite(vortex->mmio, 0x24378 + i * 0x24, coefs[i][4]);
+	}
+	hwwrite(vortex->mmio, 0x24548, arg_0 & 0xffff);
+	hwwrite(vortex->mmio, 0x2454C, arg_4 & 0xffff);
+}
+
+static void
+vortex_XtalkHw_SetRightXT(vortex_t * vortex, short arg_0, short arg_4,
+			  xtalk_coefs_t const coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		hwwrite(vortex->mmio, 0x2441C + i * 0x24, coefs[i][0]);
+		hwwrite(vortex->mmio, 0x24420 + i * 0x24, coefs[i][1]);
+		hwwrite(vortex->mmio, 0x24424 + i * 0x24, coefs[i][2]);
+		hwwrite(vortex->mmio, 0x24428 + i * 0x24, coefs[i][3]);
+		hwwrite(vortex->mmio, 0x2442C + i * 0x24, coefs[i][4]);
+	}
+	hwwrite(vortex->mmio, 0x24550, arg_0 & 0xffff);
+	hwwrite(vortex->mmio, 0x24554, arg_4 & 0xffff);
+}
+
+static void
+vortex_XtalkHw_SetLeftEQStates(vortex_t * vortex,
+			       xtalk_instate_t const arg_0,
+			       xtalk_state_t const coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		hwwrite(vortex->mmio, 0x24214 + i * 0x24, coefs[i][0]);
+		hwwrite(vortex->mmio, 0x24218 + i * 0x24, coefs[i][1]);
+		hwwrite(vortex->mmio, 0x2421C + i * 0x24, coefs[i][2]);
+		hwwrite(vortex->mmio, 0x24220 + i * 0x24, coefs[i][3]);
+	}
+	hwwrite(vortex->mmio, 0x244F8 + i * 0x24, arg_0[0]);
+	hwwrite(vortex->mmio, 0x244FC + i * 0x24, arg_0[1]);
+	hwwrite(vortex->mmio, 0x24500 + i * 0x24, arg_0[2]);
+	hwwrite(vortex->mmio, 0x24504 + i * 0x24, arg_0[3]);
+}
+
+static void
+vortex_XtalkHw_SetRightEQStates(vortex_t * vortex,
+				xtalk_instate_t const arg_0,
+				xtalk_state_t const coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		hwwrite(vortex->mmio, 0x242C8 + i * 0x24, coefs[i][0]);
+		hwwrite(vortex->mmio, 0x242CC + i * 0x24, coefs[i][1]);
+		hwwrite(vortex->mmio, 0x242D0 + i * 0x24, coefs[i][2]);
+		hwwrite(vortex->mmio, 0x244D4 + i * 0x24, coefs[i][3]);
+	}
+	hwwrite(vortex->mmio, 0x24508 + i * 0x24, arg_0[0]);
+	hwwrite(vortex->mmio, 0x2450C + i * 0x24, arg_0[1]);
+	hwwrite(vortex->mmio, 0x24510 + i * 0x24, arg_0[2]);
+	hwwrite(vortex->mmio, 0x24514 + i * 0x24, arg_0[3]);
+}
+
+static void
+vortex_XtalkHw_SetLeftXTStates(vortex_t * vortex,
+			       xtalk_instate_t const arg_0,
+			       xtalk_state_t const coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		hwwrite(vortex->mmio, 0x2437C + i * 0x24, coefs[i][0]);
+		hwwrite(vortex->mmio, 0x24380 + i * 0x24, coefs[i][1]);
+		hwwrite(vortex->mmio, 0x24384 + i * 0x24, coefs[i][2]);
+		hwwrite(vortex->mmio, 0x24388 + i * 0x24, coefs[i][3]);
+	}
+	hwwrite(vortex->mmio, 0x24518 + i * 0x24, arg_0[0]);
+	hwwrite(vortex->mmio, 0x2451C + i * 0x24, arg_0[1]);
+	hwwrite(vortex->mmio, 0x24520 + i * 0x24, arg_0[2]);
+	hwwrite(vortex->mmio, 0x24524 + i * 0x24, arg_0[3]);
+}
+
+static void
+vortex_XtalkHw_SetRightXTStates(vortex_t * vortex,
+				xtalk_instate_t const arg_0,
+				xtalk_state_t const coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		hwwrite(vortex->mmio, 0x24430 + i * 0x24, coefs[i][0]);
+		hwwrite(vortex->mmio, 0x24434 + i * 0x24, coefs[i][1]);
+		hwwrite(vortex->mmio, 0x24438 + i * 0x24, coefs[i][2]);
+		hwwrite(vortex->mmio, 0x2443C + i * 0x24, coefs[i][3]);
+	}
+	hwwrite(vortex->mmio, 0x24528 + i * 0x24, arg_0[0]);
+	hwwrite(vortex->mmio, 0x2452C + i * 0x24, arg_0[1]);
+	hwwrite(vortex->mmio, 0x24530 + i * 0x24, arg_0[2]);
+	hwwrite(vortex->mmio, 0x24534 + i * 0x24, arg_0[3]);
+}
+
+#if 0
+static void
+vortex_XtalkHw_GetLeftEQ(vortex_t * vortex, short *arg_0, short *arg_4,
+			 xtalk_coefs_t coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		coefs[i][0] = hwread(vortex->mmio, 0x24200 + i * 0x24);
+		coefs[i][1] = hwread(vortex->mmio, 0x24204 + i * 0x24);
+		coefs[i][2] = hwread(vortex->mmio, 0x24208 + i * 0x24);
+		coefs[i][3] = hwread(vortex->mmio, 0x2420c + i * 0x24);
+		coefs[i][4] = hwread(vortex->mmio, 0x24210 + i * 0x24);
+	}
+	*arg_0 = hwread(vortex->mmio, 0x24538) & 0xffff;
+	*arg_4 = hwread(vortex->mmio, 0x2453c) & 0xffff;
+}
+
+static void
+vortex_XtalkHw_GetRightEQ(vortex_t * vortex, short *arg_0, short *arg_4,
+			  xtalk_coefs_t coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		coefs[i][0] = hwread(vortex->mmio, 0x242b4 + i * 0x24);
+		coefs[i][1] = hwread(vortex->mmio, 0x242b8 + i * 0x24);
+		coefs[i][2] = hwread(vortex->mmio, 0x242bc + i * 0x24);
+		coefs[i][3] = hwread(vortex->mmio, 0x242c0 + i * 0x24);
+		coefs[i][4] = hwread(vortex->mmio, 0x242c4 + i * 0x24);
+	}
+	*arg_0 = hwread(vortex->mmio, 0x24540) & 0xffff;
+	*arg_4 = hwread(vortex->mmio, 0x24544) & 0xffff;
+}
+
+static void
+vortex_XtalkHw_GetLeftXT(vortex_t * vortex, short *arg_0, short *arg_4,
+			 xtalk_coefs_t coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		coefs[i][0] = hwread(vortex->mmio, 0x24368 + i * 0x24);
+		coefs[i][1] = hwread(vortex->mmio, 0x2436C + i * 0x24);
+		coefs[i][2] = hwread(vortex->mmio, 0x24370 + i * 0x24);
+		coefs[i][3] = hwread(vortex->mmio, 0x24374 + i * 0x24);
+		coefs[i][4] = hwread(vortex->mmio, 0x24378 + i * 0x24);
+	}
+	*arg_0 = hwread(vortex->mmio, 0x24548) & 0xffff;
+	*arg_4 = hwread(vortex->mmio, 0x2454C) & 0xffff;
+}
+
+static void
+vortex_XtalkHw_GetRightXT(vortex_t * vortex, short *arg_0, short *arg_4,
+			  xtalk_coefs_t coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		coefs[i][0] = hwread(vortex->mmio, 0x2441C + i * 0x24);
+		coefs[i][1] = hwread(vortex->mmio, 0x24420 + i * 0x24);
+		coefs[i][2] = hwread(vortex->mmio, 0x24424 + i * 0x24);
+		coefs[i][3] = hwread(vortex->mmio, 0x24428 + i * 0x24);
+		coefs[i][4] = hwread(vortex->mmio, 0x2442C + i * 0x24);
+	}
+	*arg_0 = hwread(vortex->mmio, 0x24550) & 0xffff;
+	*arg_4 = hwread(vortex->mmio, 0x24554) & 0xffff;
+}
+
+static void
+vortex_XtalkHw_GetLeftEQStates(vortex_t * vortex, xtalk_instate_t arg_0,
+			       xtalk_state_t coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		coefs[i][0] = hwread(vortex->mmio, 0x24214 + i * 0x24);
+		coefs[i][1] = hwread(vortex->mmio, 0x24218 + i * 0x24);
+		coefs[i][2] = hwread(vortex->mmio, 0x2421C + i * 0x24);
+		coefs[i][3] = hwread(vortex->mmio, 0x24220 + i * 0x24);
+	}
+	arg_0[0] = hwread(vortex->mmio, 0x244F8 + i * 0x24);
+	arg_0[1] = hwread(vortex->mmio, 0x244FC + i * 0x24);
+	arg_0[2] = hwread(vortex->mmio, 0x24500 + i * 0x24);
+	arg_0[3] = hwread(vortex->mmio, 0x24504 + i * 0x24);
+}
+
+static void
+vortex_XtalkHw_GetRightEQStates(vortex_t * vortex, xtalk_instate_t arg_0,
+				xtalk_state_t coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		coefs[i][0] = hwread(vortex->mmio, 0x242C8 + i * 0x24);
+		coefs[i][1] = hwread(vortex->mmio, 0x242CC + i * 0x24);
+		coefs[i][2] = hwread(vortex->mmio, 0x242D0 + i * 0x24);
+		coefs[i][3] = hwread(vortex->mmio, 0x242D4 + i * 0x24);
+	}
+	arg_0[0] = hwread(vortex->mmio, 0x24508 + i * 0x24);
+	arg_0[1] = hwread(vortex->mmio, 0x2450C + i * 0x24);
+	arg_0[2] = hwread(vortex->mmio, 0x24510 + i * 0x24);
+	arg_0[3] = hwread(vortex->mmio, 0x24514 + i * 0x24);
+}
+
+static void
+vortex_XtalkHw_GetLeftXTStates(vortex_t * vortex, xtalk_instate_t arg_0,
+			       xtalk_state_t coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		coefs[i][0] = hwread(vortex->mmio, 0x2437C + i * 0x24);
+		coefs[i][1] = hwread(vortex->mmio, 0x24380 + i * 0x24);
+		coefs[i][2] = hwread(vortex->mmio, 0x24384 + i * 0x24);
+		coefs[i][3] = hwread(vortex->mmio, 0x24388 + i * 0x24);
+	}
+	arg_0[0] = hwread(vortex->mmio, 0x24518 + i * 0x24);
+	arg_0[1] = hwread(vortex->mmio, 0x2451C + i * 0x24);
+	arg_0[2] = hwread(vortex->mmio, 0x24520 + i * 0x24);
+	arg_0[3] = hwread(vortex->mmio, 0x24524 + i * 0x24);
+}
+
+static void
+vortex_XtalkHw_GetRightXTStates(vortex_t * vortex, xtalk_instate_t arg_0,
+				xtalk_state_t coefs)
+{
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		coefs[i][0] = hwread(vortex->mmio, 0x24430 + i * 0x24);
+		coefs[i][1] = hwread(vortex->mmio, 0x24434 + i * 0x24);
+		coefs[i][2] = hwread(vortex->mmio, 0x24438 + i * 0x24);
+		coefs[i][3] = hwread(vortex->mmio, 0x2443C + i * 0x24);
+	}
+	arg_0[0] = hwread(vortex->mmio, 0x24528 + i * 0x24);
+	arg_0[1] = hwread(vortex->mmio, 0x2452C + i * 0x24);
+	arg_0[2] = hwread(vortex->mmio, 0x24530 + i * 0x24);
+	arg_0[3] = hwread(vortex->mmio, 0x24534 + i * 0x24);
+}
+
+#endif
+/* Gains */
+
+static void
+vortex_XtalkHw_SetGains(vortex_t * vortex, xtalk_gains_t const gains)
+{
+	int i;
+
+	for (i = 0; i < XTGAINS_SZ; i++) {
+		hwwrite(vortex->mmio, 0x244D0 + (i * 4), gains[i]);
+	}
+}
+
+static void
+vortex_XtalkHw_SetGainsAllChan(vortex_t * vortex)
+{
+	vortex_XtalkHw_SetGains(vortex, asXtalkGainsAllChan);
+}
+
+#if 0
+static void vortex_XtalkHw_GetGains(vortex_t * vortex, xtalk_gains_t gains)
+{
+	int i;
+
+	for (i = 0; i < XTGAINS_SZ; i++)
+		gains[i] = hwread(vortex->mmio, 0x244D0 + i * 4);
+}
+
+#endif
+/* Delay parameters */
+
+static void
+vortex_XtalkHw_SetDelay(vortex_t * vortex, unsigned short right,
+			unsigned short left)
+{
+	u32 esp0 = 0;
+
+	esp0 &= 0x1FFFFFFF;
+	esp0 |= 0xA0000000;
+	esp0 = (esp0 & 0xffffE0ff) | ((right & 0x1F) << 8);
+	esp0 = (esp0 & 0xfffc1fff) | ((left & 0x1F) << 0xd);
+
+	hwwrite(vortex->mmio, 0x24660, esp0);
+}
+
+static void
+vortex_XtalkHw_SetLeftDline(vortex_t * vortex, xtalk_dline_t const dline)
+{
+	int i;
+
+	for (i = 0; i < 0x20; i++) {
+		hwwrite(vortex->mmio, 0x24000 + (i << 2), dline[i] & 0xffff);
+		hwwrite(vortex->mmio, 0x24080 + (i << 2), dline[i] >> 0x10);
+	}
+}
+
+static void
+vortex_XtalkHw_SetRightDline(vortex_t * vortex, xtalk_dline_t const dline)
+{
+	int i;
+
+	for (i = 0; i < 0x20; i++) {
+		hwwrite(vortex->mmio, 0x24100 + (i << 2), dline[i] & 0xffff);
+		hwwrite(vortex->mmio, 0x24180 + (i << 2), dline[i] >> 0x10);
+	}
+}
+
+#if 0
+static void
+vortex_XtalkHw_GetDelay(vortex_t * vortex, unsigned short *right,
+			unsigned short *left)
+{
+	int esp0;
+
+	esp0 = hwread(vortex->mmio, 0x24660);
+	*right = (esp0 >> 8) & 0x1f;
+	*left = (esp0 >> 0xd) & 0x1f;
+}
+
+static void vortex_XtalkHw_GetLeftDline(vortex_t * vortex, xtalk_dline_t dline)
+{
+	int i;
+
+	for (i = 0; i < 0x20; i++) {
+		dline[i] =
+		    (hwread(vortex->mmio, 0x24000 + (i << 2)) & 0xffff) |
+		    (hwread(vortex->mmio, 0x24080 + (i << 2)) << 0x10);
+	}
+}
+
+static void vortex_XtalkHw_GetRightDline(vortex_t * vortex, xtalk_dline_t dline)
+{
+	int i;
+
+	for (i = 0; i < 0x20; i++) {
+		dline[i] =
+		    (hwread(vortex->mmio, 0x24100 + (i << 2)) & 0xffff) |
+		    (hwread(vortex->mmio, 0x24180 + (i << 2)) << 0x10);
+	}
+}
+
+#endif
+/* Control/Global stuff */
+
+#if 0
+static void vortex_XtalkHw_SetControlReg(vortex_t * vortex, u32 ctrl)
+{
+	hwwrite(vortex->mmio, 0x24660, ctrl);
+}
+static void vortex_XtalkHw_GetControlReg(vortex_t * vortex, u32 *ctrl)
+{
+	*ctrl = hwread(vortex->mmio, 0x24660);
+}
+#endif
+static void vortex_XtalkHw_SetSampleRate(vortex_t * vortex, u32 sr)
+{
+	u32 temp;
+
+	temp = (hwread(vortex->mmio, 0x24660) & 0x1FFFFFFF) | 0xC0000000;
+	temp = (temp & 0xffffff07) | ((sr & 0x1f) << 3);
+	hwwrite(vortex->mmio, 0x24660, temp);
+}
+
+#if 0
+static void vortex_XtalkHw_GetSampleRate(vortex_t * vortex, u32 *sr)
+{
+	*sr = (hwread(vortex->mmio, 0x24660) >> 3) & 0x1f;
+}
+
+#endif
+static void vortex_XtalkHw_Enable(vortex_t * vortex)
+{
+	u32 temp;
+
+	temp = (hwread(vortex->mmio, 0x24660) & 0x1FFFFFFF) | 0xC0000000;
+	temp |= 1;
+	hwwrite(vortex->mmio, 0x24660, temp);
+
+}
+
+static void vortex_XtalkHw_Disable(vortex_t * vortex)
+{
+	u32 temp;
+
+	temp = (hwread(vortex->mmio, 0x24660) & 0x1FFFFFFF) | 0xC0000000;
+	temp &= 0xfffffffe;
+	hwwrite(vortex->mmio, 0x24660, temp);
+
+}
+
+static void vortex_XtalkHw_ZeroIO(vortex_t * vortex)
+{
+	int i;
+
+	for (i = 0; i < 20; i++)
+		hwwrite(vortex->mmio, 0x24600 + (i << 2), 0);
+	for (i = 0; i < 4; i++)
+		hwwrite(vortex->mmio, 0x24650 + (i << 2), 0);
+}
+
+static void vortex_XtalkHw_ZeroState(vortex_t * vortex)
+{
+	vortex_XtalkHw_ZeroIO(vortex);	// inlined
+
+	vortex_XtalkHw_SetLeftEQ(vortex, 0, 0, asXtalkCoefsZeros);
+	vortex_XtalkHw_SetRightEQ(vortex, 0, 0, asXtalkCoefsZeros);
+
+	vortex_XtalkHw_SetLeftXT(vortex, 0, 0, asXtalkCoefsZeros);
+	vortex_XtalkHw_SetRightXT(vortex, 0, 0, asXtalkCoefsZeros);
+
+	vortex_XtalkHw_SetGains(vortex, asXtalkGainsZeros);	// inlined
+
+	vortex_XtalkHw_SetDelay(vortex, 0, 0);	// inlined
+
+	vortex_XtalkHw_SetLeftDline(vortex, alXtalkDlineZeros);	// inlined
+	vortex_XtalkHw_SetRightDline(vortex, alXtalkDlineZeros);	// inlined
+	vortex_XtalkHw_SetLeftDline(vortex, alXtalkDlineZeros);	// inlined
+	vortex_XtalkHw_SetRightDline(vortex, alXtalkDlineZeros);	// inlined
+
+	vortex_XtalkHw_SetLeftEQStates(vortex, asXtalkInStateZeros,
+				       asXtalkOutStateZeros);
+	vortex_XtalkHw_SetRightEQStates(vortex, asXtalkInStateZeros,
+					asXtalkOutStateZeros);
+	vortex_XtalkHw_SetLeftXTStates(vortex, asXtalkInStateZeros,
+				       asXtalkOutStateZeros);
+	vortex_XtalkHw_SetRightXTStates(vortex, asXtalkInStateZeros,
+					asXtalkOutStateZeros);
+}
+
+static void vortex_XtalkHw_ProgramPipe(vortex_t * vortex)
+{
+
+	vortex_XtalkHw_SetLeftEQ(vortex, 0, 1, asXtalkCoefsPipe);
+	vortex_XtalkHw_SetRightEQ(vortex, 0, 1, asXtalkCoefsPipe);
+	vortex_XtalkHw_SetLeftXT(vortex, 0, 0, asXtalkCoefsZeros);
+	vortex_XtalkHw_SetRightXT(vortex, 0, 0, asXtalkCoefsZeros);
+
+	vortex_XtalkHw_SetDelay(vortex, 0, 0);	// inlined
+}
+
+static void vortex_XtalkHw_ProgramXtalkWide(vortex_t * vortex)
+{
+
+	vortex_XtalkHw_SetLeftEQ(vortex, sXtalkWideKLeftEq,
+				 sXtalkWideShiftLeftEq, asXtalkWideCoefsLeftEq);
+	vortex_XtalkHw_SetRightEQ(vortex, sXtalkWideKRightEq,
+				  sXtalkWideShiftRightEq,
+				  asXtalkWideCoefsRightEq);
+	vortex_XtalkHw_SetLeftXT(vortex, sXtalkWideKLeftXt,
+				 sXtalkWideShiftLeftXt, asXtalkWideCoefsLeftXt);
+	vortex_XtalkHw_SetRightXT(vortex, sXtalkWideKLeftXt,
+				  sXtalkWideShiftLeftXt,
+				  asXtalkWideCoefsLeftXt);
+
+	vortex_XtalkHw_SetDelay(vortex, wXtalkWideRightDelay, wXtalkWideLeftDelay);	// inlined
+}
+
+static void vortex_XtalkHw_ProgramXtalkNarrow(vortex_t * vortex)
+{
+
+	vortex_XtalkHw_SetLeftEQ(vortex, sXtalkNarrowKLeftEq,
+				 sXtalkNarrowShiftLeftEq,
+				 asXtalkNarrowCoefsLeftEq);
+	vortex_XtalkHw_SetRightEQ(vortex, sXtalkNarrowKRightEq,
+				  sXtalkNarrowShiftRightEq,
+				  asXtalkNarrowCoefsRightEq);
+	vortex_XtalkHw_SetLeftXT(vortex, sXtalkNarrowKLeftXt,
+				 sXtalkNarrowShiftLeftXt,
+				 asXtalkNarrowCoefsLeftXt);
+	vortex_XtalkHw_SetRightXT(vortex, sXtalkNarrowKLeftXt,
+				  sXtalkNarrowShiftLeftXt,
+				  asXtalkNarrowCoefsLeftXt);
+
+	vortex_XtalkHw_SetDelay(vortex, wXtalkNarrowRightDelay, wXtalkNarrowLeftDelay);	// inlined
+}
+
+static void vortex_XtalkHw_ProgramDiamondXtalk(vortex_t * vortex)
+{
+
+	//sDiamondKLeftEq,sDiamondKRightXt,asDiamondCoefsLeftEq
+	vortex_XtalkHw_SetLeftEQ(vortex, sDiamondKLeftEq,
+				 sDiamondShiftLeftEq, asDiamondCoefsLeftEq);
+	vortex_XtalkHw_SetRightEQ(vortex, sDiamondKRightEq,
+				  sDiamondShiftRightEq, asDiamondCoefsRightEq);
+	vortex_XtalkHw_SetLeftXT(vortex, sDiamondKLeftXt,
+				 sDiamondShiftLeftXt, asDiamondCoefsLeftXt);
+	vortex_XtalkHw_SetRightXT(vortex, sDiamondKLeftXt,
+				  sDiamondShiftLeftXt, asDiamondCoefsLeftXt);
+
+	vortex_XtalkHw_SetDelay(vortex, wDiamondRightDelay, wDiamondLeftDelay);	// inlined
+}
+
+static void vortex_XtalkHw_init(vortex_t * vortex)
+{
+	vortex_XtalkHw_ZeroState(vortex);
+}
+
+/* End of file */
diff --git a/linux/sound/pci/au88x0/au88x0_xtalk.h b/linux/sound/pci/au88x0/au88x0_xtalk.h
new file mode 100644
index 0000000..7f4534b
--- /dev/null
+++ b/linux/sound/pci/au88x0/au88x0_xtalk.h
@@ -0,0 +1,61 @@
+/***************************************************************************
+ *            au88x0_cxtalk.h
+ *
+ *  Wed Nov 19 19:07:17 2003
+ *  Copyright  2003  mjander
+ *  mjander@users.sourceforge.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 Library 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.
+ */
+
+/* The crosstalk canceler supports 5 stereo input channels. The result is 
+   available at one single output route pair (stereo). */
+
+#ifndef _AU88X0_CXTALK_H
+#define _AU88X0_CXTALK_H
+
+#include "au88x0.h"
+
+#define XTDLINE_SZ 32
+#define XTGAINS_SZ 10
+#define XTINST_SZ 4
+
+#define XT_HEADPHONE	1
+#define XT_SPEAKER0		2
+#define XT_SPEAKER1		3
+#define XT_DIAMOND		4
+
+typedef u32 xtalk_dline_t[XTDLINE_SZ];
+typedef u16 xtalk_gains_t[XTGAINS_SZ];
+typedef u16 xtalk_instate_t[XTINST_SZ];
+typedef u16 xtalk_coefs_t[5][5];
+typedef u16 xtalk_state_t[5][4];
+
+static void vortex_XtalkHw_SetGains(vortex_t * vortex,
+				    xtalk_gains_t const gains);
+static void vortex_XtalkHw_SetGainsAllChan(vortex_t * vortex);
+static void vortex_XtalkHw_SetSampleRate(vortex_t * vortex, u32 sr);
+static void vortex_XtalkHw_ProgramPipe(vortex_t * vortex);
+static void vortex_XtalkHw_ProgramPipe(vortex_t * vortex);
+static void vortex_XtalkHw_ProgramXtalkWide(vortex_t * vortex);
+static void vortex_XtalkHw_ProgramXtalkNarrow(vortex_t * vortex);
+static void vortex_XtalkHw_ProgramDiamondXtalk(vortex_t * vortex);
+static void vortex_XtalkHw_Enable(vortex_t * vortex);
+static void vortex_XtalkHw_Disable(vortex_t * vortex);
+static void vortex_XtalkHw_init(vortex_t * vortex);
+
+#endif				/* _AU88X0_CXTALK_H */
diff --git a/linux/sound/pci/aw2/Makefile b/linux/sound/pci/aw2/Makefile
new file mode 100644
index 0000000..842335d
--- /dev/null
+++ b/linux/sound/pci/aw2/Makefile
@@ -0,0 +1,3 @@
+snd-aw2-objs := aw2-alsa.o aw2-saa7146.o
+
+obj-$(CONFIG_SND_AW2) += snd-aw2.o
diff --git a/linux/sound/pci/aw2/aw2-alsa.c b/linux/sound/pci/aw2/aw2-alsa.c
new file mode 100644
index 0000000..c150022
--- /dev/null
+++ b/linux/sound/pci/aw2/aw2-alsa.c
@@ -0,0 +1,791 @@
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ * The Audiowerk2 ALSA driver 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; version 2.
+ *
+ * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ *
+ *****************************************************************************/
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+#include "saa7146.h"
+#include "aw2-saa7146.h"
+
+MODULE_AUTHOR("Cedric Bregardis <cedric.bregardis@free.fr>, "
+	      "Jean-Christian Hassler <jhassler@free.fr>");
+MODULE_DESCRIPTION("Emagic Audiowerk 2 sound driver");
+MODULE_LICENSE("GPL");
+
+/*********************************
+ * DEFINES
+ ********************************/
+#define CTL_ROUTE_ANALOG 0
+#define CTL_ROUTE_DIGITAL 1
+
+/*********************************
+ * TYPEDEFS
+ ********************************/
+  /* hardware definition */
+static struct snd_pcm_hardware snd_aw2_playback_hw = {
+	.info = (SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rates = SNDRV_PCM_RATE_44100,
+	.rate_min = 44100,
+	.rate_max = 44100,
+	.channels_min = 2,
+	.channels_max = 4,
+	.buffer_bytes_max = 32768,
+	.period_bytes_min = 4096,
+	.period_bytes_max = 32768,
+	.periods_min = 1,
+	.periods_max = 1024,
+};
+
+static struct snd_pcm_hardware snd_aw2_capture_hw = {
+	.info = (SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rates = SNDRV_PCM_RATE_44100,
+	.rate_min = 44100,
+	.rate_max = 44100,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = 32768,
+	.period_bytes_min = 4096,
+	.period_bytes_max = 32768,
+	.periods_min = 1,
+	.periods_max = 1024,
+};
+
+struct aw2_pcm_device {
+	struct snd_pcm *pcm;
+	unsigned int stream_number;
+	struct aw2 *chip;
+};
+
+struct aw2 {
+	struct snd_aw2_saa7146 saa7146;
+
+	struct pci_dev *pci;
+	int irq;
+	spinlock_t reg_lock;
+	struct mutex mtx;
+
+	unsigned long iobase_phys;
+	void __iomem *iobase_virt;
+
+	struct snd_card *card;
+
+	struct aw2_pcm_device device_playback[NB_STREAM_PLAYBACK];
+	struct aw2_pcm_device device_capture[NB_STREAM_CAPTURE];
+};
+
+/*********************************
+ * FUNCTION DECLARATIONS
+ ********************************/
+static int __init alsa_card_aw2_init(void);
+static void __exit alsa_card_aw2_exit(void);
+static int snd_aw2_dev_free(struct snd_device *device);
+static int __devinit snd_aw2_create(struct snd_card *card,
+				    struct pci_dev *pci, struct aw2 **rchip);
+static int __devinit snd_aw2_probe(struct pci_dev *pci,
+				   const struct pci_device_id *pci_id);
+static void __devexit snd_aw2_remove(struct pci_dev *pci);
+static int snd_aw2_pcm_playback_open(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_playback_close(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_capture_open(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_capture_close(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params);
+static int snd_aw2_pcm_hw_free(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_prepare_playback(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_prepare_capture(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_trigger_playback(struct snd_pcm_substream *substream,
+					int cmd);
+static int snd_aw2_pcm_trigger_capture(struct snd_pcm_substream *substream,
+				       int cmd);
+static snd_pcm_uframes_t snd_aw2_pcm_pointer_playback(struct snd_pcm_substream
+						      *substream);
+static snd_pcm_uframes_t snd_aw2_pcm_pointer_capture(struct snd_pcm_substream
+						     *substream);
+static int __devinit snd_aw2_new_pcm(struct aw2 *chip);
+
+static int snd_aw2_control_switch_capture_info(struct snd_kcontrol *kcontrol,
+					       struct snd_ctl_elem_info *uinfo);
+static int snd_aw2_control_switch_capture_get(struct snd_kcontrol *kcontrol,
+					      struct snd_ctl_elem_value
+					      *ucontrol);
+static int snd_aw2_control_switch_capture_put(struct snd_kcontrol *kcontrol,
+					      struct snd_ctl_elem_value
+					      *ucontrol);
+
+/*********************************
+ * VARIABLES
+ ********************************/
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Audiowerk2 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the Audiowerk2 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Audiowerk2 soundcard.");
+
+static DEFINE_PCI_DEVICE_TABLE(snd_aw2_ids) = {
+	{PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, 0, 0,
+	 0, 0, 0},
+	{0}
+};
+
+MODULE_DEVICE_TABLE(pci, snd_aw2_ids);
+
+/* pci_driver definition */
+static struct pci_driver driver = {
+	.name = "Emagic Audiowerk 2",
+	.id_table = snd_aw2_ids,
+	.probe = snd_aw2_probe,
+	.remove = __devexit_p(snd_aw2_remove),
+};
+
+/* operators for playback PCM alsa interface */
+static struct snd_pcm_ops snd_aw2_playback_ops = {
+	.open = snd_aw2_pcm_playback_open,
+	.close = snd_aw2_pcm_playback_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_aw2_pcm_hw_params,
+	.hw_free = snd_aw2_pcm_hw_free,
+	.prepare = snd_aw2_pcm_prepare_playback,
+	.trigger = snd_aw2_pcm_trigger_playback,
+	.pointer = snd_aw2_pcm_pointer_playback,
+};
+
+/* operators for capture PCM alsa interface */
+static struct snd_pcm_ops snd_aw2_capture_ops = {
+	.open = snd_aw2_pcm_capture_open,
+	.close = snd_aw2_pcm_capture_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_aw2_pcm_hw_params,
+	.hw_free = snd_aw2_pcm_hw_free,
+	.prepare = snd_aw2_pcm_prepare_capture,
+	.trigger = snd_aw2_pcm_trigger_capture,
+	.pointer = snd_aw2_pcm_pointer_capture,
+};
+
+static struct snd_kcontrol_new aw2_control __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PCM Capture Route",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value = 0xffff,
+	.info = snd_aw2_control_switch_capture_info,
+	.get = snd_aw2_control_switch_capture_get,
+	.put = snd_aw2_control_switch_capture_put
+};
+
+/*********************************
+ * FUNCTION IMPLEMENTATIONS
+ ********************************/
+
+/* initialization of the module */
+static int __init alsa_card_aw2_init(void)
+{
+	snd_printdd(KERN_DEBUG "aw2: Load aw2 module\n");
+	return pci_register_driver(&driver);
+}
+
+/* clean up the module */
+static void __exit alsa_card_aw2_exit(void)
+{
+	snd_printdd(KERN_DEBUG "aw2: Unload aw2 module\n");
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_aw2_init);
+module_exit(alsa_card_aw2_exit);
+
+/* component-destructor */
+static int snd_aw2_dev_free(struct snd_device *device)
+{
+	struct aw2 *chip = device->device_data;
+
+	/* Free hardware */
+	snd_aw2_saa7146_free(&chip->saa7146);
+
+	/* release the irq */
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *)chip);
+	/* release the i/o ports & memory */
+	if (chip->iobase_virt)
+		iounmap(chip->iobase_virt);
+
+	pci_release_regions(chip->pci);
+	/* disable the PCI entry */
+	pci_disable_device(chip->pci);
+	/* release the data */
+	kfree(chip);
+
+	return 0;
+}
+
+/* chip-specific constructor */
+static int __devinit snd_aw2_create(struct snd_card *card,
+				    struct pci_dev *pci, struct aw2 **rchip)
+{
+	struct aw2 *chip;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_aw2_dev_free,
+	};
+
+	*rchip = NULL;
+
+	/* initialize the PCI entry */
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+	pci_set_master(pci);
+
+	/* check PCI availability (32bit DMA) */
+	if ((pci_set_dma_mask(pci, DMA_BIT_MASK(32)) < 0) ||
+	    (pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32)) < 0)) {
+		printk(KERN_ERR "aw2: Impossible to set 32bit mask DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	/* initialize the stuff */
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	/* (1) PCI resource allocation */
+	err = pci_request_regions(pci, "Audiowerk2");
+	if (err < 0) {
+		pci_disable_device(pci);
+		kfree(chip);
+		return err;
+	}
+	chip->iobase_phys = pci_resource_start(pci, 0);
+	chip->iobase_virt =
+		ioremap_nocache(chip->iobase_phys,
+				pci_resource_len(pci, 0));
+
+	if (chip->iobase_virt == NULL) {
+		printk(KERN_ERR "aw2: unable to remap memory region");
+		pci_release_regions(pci);
+		pci_disable_device(pci);
+		kfree(chip);
+		return -ENOMEM;
+	}
+
+	/* (2) initialization of the chip hardware */
+	snd_aw2_saa7146_setup(&chip->saa7146, chip->iobase_virt);
+
+	if (request_irq(pci->irq, snd_aw2_saa7146_interrupt,
+			IRQF_SHARED, "Audiowerk2", chip)) {
+		printk(KERN_ERR "aw2: Cannot grab irq %d\n", pci->irq);
+
+		iounmap(chip->iobase_virt);
+		pci_release_regions(chip->pci);
+		pci_disable_device(chip->pci);
+		kfree(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		free_irq(chip->irq, (void *)chip);
+		iounmap(chip->iobase_virt);
+		pci_release_regions(chip->pci);
+		pci_disable_device(chip->pci);
+		kfree(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+	*rchip = chip;
+
+	printk(KERN_INFO
+	       "Audiowerk 2 sound card (saa7146 chipset) detected and "
+	       "managed\n");
+	return 0;
+}
+
+/* constructor */
+static int __devinit snd_aw2_probe(struct pci_dev *pci,
+				   const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct aw2 *chip;
+	int err;
+
+	/* (1) Continue if device is not enabled, else inc dev */
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	/* (2) Create card instance */
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	/* (3) Create main component */
+	err = snd_aw2_create(card, pci, &chip);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	/* initialize mutex */
+	mutex_init(&chip->mtx);
+	/* init spinlock */
+	spin_lock_init(&chip->reg_lock);
+	/* (4) Define driver ID and name string */
+	strcpy(card->driver, "aw2");
+	strcpy(card->shortname, "Audiowerk2");
+
+	sprintf(card->longname, "%s with SAA7146 irq %i",
+		card->shortname, chip->irq);
+
+	/* (5) Create other components */
+	snd_aw2_new_pcm(chip);
+
+	/* (6) Register card instance */
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	/* (7) Set PCI driver data */
+	pci_set_drvdata(pci, card);
+
+	dev++;
+	return 0;
+}
+
+/* destructor */
+static void __devexit snd_aw2_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+/* open callback */
+static int snd_aw2_pcm_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	snd_printdd(KERN_DEBUG "aw2: Playback_open\n");
+	runtime->hw = snd_aw2_playback_hw;
+	return 0;
+}
+
+/* close callback */
+static int snd_aw2_pcm_playback_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+
+}
+
+static int snd_aw2_pcm_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	snd_printdd(KERN_DEBUG "aw2: Capture_open\n");
+	runtime->hw = snd_aw2_capture_hw;
+	return 0;
+}
+
+/* close callback */
+static int snd_aw2_pcm_capture_close(struct snd_pcm_substream *substream)
+{
+	/* TODO: something to do ? */
+	return 0;
+}
+
+ /* hw_params callback */
+static int snd_aw2_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+/* hw_free callback */
+static int snd_aw2_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+/* prepare callback for playback */
+static int snd_aw2_pcm_prepare_playback(struct snd_pcm_substream *substream)
+{
+	struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+	struct aw2 *chip = pcm_device->chip;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long period_size, buffer_size;
+
+	mutex_lock(&chip->mtx);
+
+	period_size = snd_pcm_lib_period_bytes(substream);
+	buffer_size = snd_pcm_lib_buffer_bytes(substream);
+
+	snd_aw2_saa7146_pcm_init_playback(&chip->saa7146,
+					  pcm_device->stream_number,
+					  runtime->dma_addr, period_size,
+					  buffer_size);
+
+	/* Define Interrupt callback */
+	snd_aw2_saa7146_define_it_playback_callback(pcm_device->stream_number,
+						    (snd_aw2_saa7146_it_cb)
+						    snd_pcm_period_elapsed,
+						    (void *)substream);
+
+	mutex_unlock(&chip->mtx);
+
+	return 0;
+}
+
+/* prepare callback for capture */
+static int snd_aw2_pcm_prepare_capture(struct snd_pcm_substream *substream)
+{
+	struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+	struct aw2 *chip = pcm_device->chip;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long period_size, buffer_size;
+
+	mutex_lock(&chip->mtx);
+
+	period_size = snd_pcm_lib_period_bytes(substream);
+	buffer_size = snd_pcm_lib_buffer_bytes(substream);
+
+	snd_aw2_saa7146_pcm_init_capture(&chip->saa7146,
+					 pcm_device->stream_number,
+					 runtime->dma_addr, period_size,
+					 buffer_size);
+
+	/* Define Interrupt callback */
+	snd_aw2_saa7146_define_it_capture_callback(pcm_device->stream_number,
+						   (snd_aw2_saa7146_it_cb)
+						   snd_pcm_period_elapsed,
+						   (void *)substream);
+
+	mutex_unlock(&chip->mtx);
+
+	return 0;
+}
+
+/* playback trigger callback */
+static int snd_aw2_pcm_trigger_playback(struct snd_pcm_substream *substream,
+					int cmd)
+{
+	int status = 0;
+	struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+	struct aw2 *chip = pcm_device->chip;
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_aw2_saa7146_pcm_trigger_start_playback(&chip->saa7146,
+							   pcm_device->
+							   stream_number);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_aw2_saa7146_pcm_trigger_stop_playback(&chip->saa7146,
+							  pcm_device->
+							  stream_number);
+		break;
+	default:
+		status = -EINVAL;
+	}
+	spin_unlock(&chip->reg_lock);
+	return status;
+}
+
+/* capture trigger callback */
+static int snd_aw2_pcm_trigger_capture(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	int status = 0;
+	struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+	struct aw2 *chip = pcm_device->chip;
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_aw2_saa7146_pcm_trigger_start_capture(&chip->saa7146,
+							  pcm_device->
+							  stream_number);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_aw2_saa7146_pcm_trigger_stop_capture(&chip->saa7146,
+							 pcm_device->
+							 stream_number);
+		break;
+	default:
+		status = -EINVAL;
+	}
+	spin_unlock(&chip->reg_lock);
+	return status;
+}
+
+/* playback pointer callback */
+static snd_pcm_uframes_t snd_aw2_pcm_pointer_playback(struct snd_pcm_substream
+						      *substream)
+{
+	struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+	struct aw2 *chip = pcm_device->chip;
+	unsigned int current_ptr;
+
+	/* get the current hardware pointer */
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	current_ptr =
+		snd_aw2_saa7146_get_hw_ptr_playback(&chip->saa7146,
+						    pcm_device->stream_number,
+						    runtime->dma_area,
+						    runtime->buffer_size);
+
+	return bytes_to_frames(substream->runtime, current_ptr);
+}
+
+/* capture pointer callback */
+static snd_pcm_uframes_t snd_aw2_pcm_pointer_capture(struct snd_pcm_substream
+						     *substream)
+{
+	struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+	struct aw2 *chip = pcm_device->chip;
+	unsigned int current_ptr;
+
+	/* get the current hardware pointer */
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	current_ptr =
+		snd_aw2_saa7146_get_hw_ptr_capture(&chip->saa7146,
+						   pcm_device->stream_number,
+						   runtime->dma_area,
+						   runtime->buffer_size);
+
+	return bytes_to_frames(substream->runtime, current_ptr);
+}
+
+/* create a pcm device */
+static int __devinit snd_aw2_new_pcm(struct aw2 *chip)
+{
+	struct snd_pcm *pcm_playback_ana;
+	struct snd_pcm *pcm_playback_num;
+	struct snd_pcm *pcm_capture;
+	struct aw2_pcm_device *pcm_device;
+	int err = 0;
+
+	/* Create new Alsa PCM device */
+
+	err = snd_pcm_new(chip->card, "Audiowerk2 analog playback", 0, 1, 0,
+			  &pcm_playback_ana);
+	if (err < 0) {
+		printk(KERN_ERR "aw2: snd_pcm_new error (0x%X)\n", err);
+		return err;
+	}
+
+	/* Creation ok */
+	pcm_device = &chip->device_playback[NUM_STREAM_PLAYBACK_ANA];
+
+	/* Set PCM device name */
+	strcpy(pcm_playback_ana->name, "Analog playback");
+	/* Associate private data to PCM device */
+	pcm_playback_ana->private_data = pcm_device;
+	/* set operators of PCM device */
+	snd_pcm_set_ops(pcm_playback_ana, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_aw2_playback_ops);
+	/* store PCM device */
+	pcm_device->pcm = pcm_playback_ana;
+	/* give base chip pointer to our internal pcm device
+	   structure */
+	pcm_device->chip = chip;
+	/* Give stream number to PCM device */
+	pcm_device->stream_number = NUM_STREAM_PLAYBACK_ANA;
+
+	/* pre-allocation of buffers */
+	/* Preallocate continuous pages. */
+	err = snd_pcm_lib_preallocate_pages_for_all(pcm_playback_ana,
+						    SNDRV_DMA_TYPE_DEV,
+						    snd_dma_pci_data
+						    (chip->pci),
+						    64 * 1024, 64 * 1024);
+	if (err)
+		printk(KERN_ERR "aw2: snd_pcm_lib_preallocate_pages_for_all "
+		       "error (0x%X)\n", err);
+
+	err = snd_pcm_new(chip->card, "Audiowerk2 digital playback", 1, 1, 0,
+			  &pcm_playback_num);
+
+	if (err < 0) {
+		printk(KERN_ERR "aw2: snd_pcm_new error (0x%X)\n", err);
+		return err;
+	}
+	/* Creation ok */
+	pcm_device = &chip->device_playback[NUM_STREAM_PLAYBACK_DIG];
+
+	/* Set PCM device name */
+	strcpy(pcm_playback_num->name, "Digital playback");
+	/* Associate private data to PCM device */
+	pcm_playback_num->private_data = pcm_device;
+	/* set operators of PCM device */
+	snd_pcm_set_ops(pcm_playback_num, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_aw2_playback_ops);
+	/* store PCM device */
+	pcm_device->pcm = pcm_playback_num;
+	/* give base chip pointer to our internal pcm device
+	   structure */
+	pcm_device->chip = chip;
+	/* Give stream number to PCM device */
+	pcm_device->stream_number = NUM_STREAM_PLAYBACK_DIG;
+
+	/* pre-allocation of buffers */
+	/* Preallocate continuous pages. */
+	err = snd_pcm_lib_preallocate_pages_for_all(pcm_playback_num,
+						    SNDRV_DMA_TYPE_DEV,
+						    snd_dma_pci_data
+						    (chip->pci),
+						    64 * 1024, 64 * 1024);
+	if (err)
+		printk(KERN_ERR
+		       "aw2: snd_pcm_lib_preallocate_pages_for_all error "
+		       "(0x%X)\n", err);
+
+
+
+	err = snd_pcm_new(chip->card, "Audiowerk2 capture", 2, 0, 1,
+			  &pcm_capture);
+
+	if (err < 0) {
+		printk(KERN_ERR "aw2: snd_pcm_new error (0x%X)\n", err);
+		return err;
+	}
+
+	/* Creation ok */
+	pcm_device = &chip->device_capture[NUM_STREAM_CAPTURE_ANA];
+
+	/* Set PCM device name */
+	strcpy(pcm_capture->name, "Capture");
+	/* Associate private data to PCM device */
+	pcm_capture->private_data = pcm_device;
+	/* set operators of PCM device */
+	snd_pcm_set_ops(pcm_capture, SNDRV_PCM_STREAM_CAPTURE,
+			&snd_aw2_capture_ops);
+	/* store PCM device */
+	pcm_device->pcm = pcm_capture;
+	/* give base chip pointer to our internal pcm device
+	   structure */
+	pcm_device->chip = chip;
+	/* Give stream number to PCM device */
+	pcm_device->stream_number = NUM_STREAM_CAPTURE_ANA;
+
+	/* pre-allocation of buffers */
+	/* Preallocate continuous pages. */
+	err = snd_pcm_lib_preallocate_pages_for_all(pcm_capture,
+						    SNDRV_DMA_TYPE_DEV,
+						    snd_dma_pci_data
+						    (chip->pci),
+						    64 * 1024, 64 * 1024);
+	if (err)
+		printk(KERN_ERR
+		       "aw2: snd_pcm_lib_preallocate_pages_for_all error "
+		       "(0x%X)\n", err);
+
+
+	/* Create control */
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&aw2_control, chip));
+	if (err < 0) {
+		printk(KERN_ERR "aw2: snd_ctl_add error (0x%X)\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static int snd_aw2_control_switch_capture_info(struct snd_kcontrol *kcontrol,
+					       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = {
+		"Analog", "Digital"
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) {
+		uinfo->value.enumerated.item =
+		    uinfo->value.enumerated.items - 1;
+	}
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_aw2_control_switch_capture_get(struct snd_kcontrol *kcontrol,
+					      struct snd_ctl_elem_value
+					      *ucontrol)
+{
+	struct aw2 *chip = snd_kcontrol_chip(kcontrol);
+	if (snd_aw2_saa7146_is_using_digital_input(&chip->saa7146))
+		ucontrol->value.enumerated.item[0] = CTL_ROUTE_DIGITAL;
+	else
+		ucontrol->value.enumerated.item[0] = CTL_ROUTE_ANALOG;
+	return 0;
+}
+
+static int snd_aw2_control_switch_capture_put(struct snd_kcontrol *kcontrol,
+					      struct snd_ctl_elem_value
+					      *ucontrol)
+{
+	struct aw2 *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int is_disgital =
+	    snd_aw2_saa7146_is_using_digital_input(&chip->saa7146);
+
+	if (((ucontrol->value.integer.value[0] == CTL_ROUTE_DIGITAL)
+	     && !is_disgital)
+	    || ((ucontrol->value.integer.value[0] == CTL_ROUTE_ANALOG)
+		&& is_disgital)) {
+		snd_aw2_saa7146_use_digital_input(&chip->saa7146, !is_disgital);
+		changed = 1;
+	}
+	return changed;
+}
diff --git a/linux/sound/pci/aw2/aw2-saa7146.c b/linux/sound/pci/aw2/aw2-saa7146.c
new file mode 100644
index 0000000..8afd8b5
--- /dev/null
+++ b/linux/sound/pci/aw2/aw2-saa7146.c
@@ -0,0 +1,464 @@
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ * The Audiowerk2 ALSA driver 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; version 2.
+ *
+ * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ *
+ *****************************************************************************/
+
+#define AW2_SAA7146_M
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "saa7146.h"
+#include "aw2-saa7146.h"
+
+#include "aw2-tsl.c"
+
+#define WRITEREG(value, addr) writel((value), chip->base_addr + (addr))
+#define READREG(addr) readl(chip->base_addr + (addr))
+
+static struct snd_aw2_saa7146_cb_param
+ arr_substream_it_playback_cb[NB_STREAM_PLAYBACK];
+static struct snd_aw2_saa7146_cb_param
+ arr_substream_it_capture_cb[NB_STREAM_CAPTURE];
+
+static int snd_aw2_saa7146_get_limit(int size);
+
+/* chip-specific destructor */
+int snd_aw2_saa7146_free(struct snd_aw2_saa7146 *chip)
+{
+	/* disable all irqs */
+	WRITEREG(0, IER);
+
+	/* reset saa7146 */
+	WRITEREG((MRST_N << 16), MC1);
+
+	/* Unset base addr */
+	chip->base_addr = NULL;
+
+	return 0;
+}
+
+void snd_aw2_saa7146_setup(struct snd_aw2_saa7146 *chip,
+			   void __iomem *pci_base_addr)
+{
+	/* set PCI burst/threshold
+
+	   Burst length definition
+	   VALUE    BURST LENGTH
+	   000      1 Dword
+	   001      2 Dwords
+	   010      4 Dwords
+	   011      8 Dwords
+	   100      16 Dwords
+	   101      32 Dwords
+	   110      64 Dwords
+	   111      128 Dwords
+
+	   Threshold definition
+	   VALUE    WRITE MODE              READ MODE
+	   00       1 Dword of valid data   1 empty Dword
+	   01       4 Dwords of valid data  4 empty Dwords
+	   10       8 Dwords of valid data  8 empty Dwords
+	   11       16 Dwords of valid data 16 empty Dwords */
+
+	unsigned int acon2;
+	unsigned int acon1 = 0;
+	int i;
+
+	/* Set base addr */
+	chip->base_addr = pci_base_addr;
+
+	/* disable all irqs */
+	WRITEREG(0, IER);
+
+	/* reset saa7146 */
+	WRITEREG((MRST_N << 16), MC1);
+
+	/* enable audio interface */
+#ifdef __BIG_ENDIAN
+	acon1 |= A1_SWAP;
+	acon1 |= A2_SWAP;
+#endif
+	/* WS0_CTRL, WS0_SYNC: input TSL1, I2S */
+
+	/* At initialization WS1 and WS2 are disabled (configured as input) */
+	acon1 |= 0 * WS1_CTRL;
+	acon1 |= 0 * WS2_CTRL;
+
+	/* WS4 is not used. So it must not restart A2.
+	   This is why it is configured as output (force to low) */
+	acon1 |= 3 * WS4_CTRL;
+
+	/* WS3_CTRL, WS3_SYNC: output TSL2, I2S */
+	acon1 |= 2 * WS3_CTRL;
+
+	/* A1 and A2 are active and asynchronous */
+	acon1 |= 3 * AUDIO_MODE;
+	WRITEREG(acon1, ACON1);
+
+	/* The following comes from original windows driver.
+	   It is needed to have a correct behavior of input and output
+	   simultenously, but I don't know why ! */
+	WRITEREG(3 * (BurstA1_in) + 3 * (ThreshA1_in) +
+		 3 * (BurstA1_out) + 3 * (ThreshA1_out) +
+		 3 * (BurstA2_out) + 3 * (ThreshA2_out), PCI_BT_A);
+
+	/* enable audio port pins */
+	WRITEREG((EAP << 16) | EAP, MC1);
+
+	/* enable I2C */
+	WRITEREG((EI2C << 16) | EI2C, MC1);
+	/* enable interrupts */
+	WRITEREG(A1_out | A2_out | A1_in | IIC_S | IIC_E, IER);
+
+	/* audio configuration */
+	acon2 = A2_CLKSRC | BCLK1_OEN;
+	WRITEREG(acon2, ACON2);
+
+	/* By default use analog input */
+	snd_aw2_saa7146_use_digital_input(chip, 0);
+
+	/* TSL setup */
+	for (i = 0; i < 8; ++i) {
+		WRITEREG(tsl1[i], TSL1 + (i * 4));
+		WRITEREG(tsl2[i], TSL2 + (i * 4));
+	}
+
+}
+
+void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip,
+				       int stream_number,
+				       unsigned long dma_addr,
+				       unsigned long period_size,
+				       unsigned long buffer_size)
+{
+	unsigned long dw_page, dw_limit;
+
+	/* Configure DMA for substream
+	   Configuration informations: ALSA has allocated continuous memory
+	   pages. So we don't need to use MMU of saa7146.
+	 */
+
+	/* No MMU -> nothing to do with PageA1, we only configure the limit of
+	   PageAx_out register */
+	/* Disable MMU */
+	dw_page = (0L << 11);
+
+	/* Configure Limit for DMA access.
+	   The limit register defines an address limit, which generates
+	   an interrupt if passed by the actual PCI address pointer.
+	   '0001' means an interrupt will be generated if the lower
+	   6 bits (64 bytes) of the PCI address are zero. '0010'
+	   defines a limit of 128 bytes, '0011' one of 256 bytes, and
+	   so on up to 1 Mbyte defined by '1111'. This interrupt range
+	   can be calculated as follows:
+	   Range = 2^(5 + Limit) bytes.
+	 */
+	dw_limit = snd_aw2_saa7146_get_limit(period_size);
+	dw_page |= (dw_limit << 4);
+
+	if (stream_number == 0) {
+		WRITEREG(dw_page, PageA2_out);
+
+		/* Base address for DMA transfert. */
+		/* This address has been reserved by ALSA. */
+		/* This is a physical address */
+		WRITEREG(dma_addr, BaseA2_out);
+
+		/* Define upper limit for DMA access */
+		WRITEREG(dma_addr + buffer_size, ProtA2_out);
+
+	} else if (stream_number == 1) {
+		WRITEREG(dw_page, PageA1_out);
+
+		/* Base address for DMA transfert. */
+		/* This address has been reserved by ALSA. */
+		/* This is a physical address */
+		WRITEREG(dma_addr, BaseA1_out);
+
+		/* Define upper limit for DMA access */
+		WRITEREG(dma_addr + buffer_size, ProtA1_out);
+	} else {
+		printk(KERN_ERR
+		       "aw2: snd_aw2_saa7146_pcm_init_playback: "
+		       "Substream number is not 0 or 1 -> not managed\n");
+	}
+}
+
+void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip,
+				      int stream_number, unsigned long dma_addr,
+				      unsigned long period_size,
+				      unsigned long buffer_size)
+{
+	unsigned long dw_page, dw_limit;
+
+	/* Configure DMA for substream
+	   Configuration informations: ALSA has allocated continuous memory
+	   pages. So we don't need to use MMU of saa7146.
+	 */
+
+	/* No MMU -> nothing to do with PageA1, we only configure the limit of
+	   PageAx_out register */
+	/* Disable MMU */
+	dw_page = (0L << 11);
+
+	/* Configure Limit for DMA access.
+	   The limit register defines an address limit, which generates
+	   an interrupt if passed by the actual PCI address pointer.
+	   '0001' means an interrupt will be generated if the lower
+	   6 bits (64 bytes) of the PCI address are zero. '0010'
+	   defines a limit of 128 bytes, '0011' one of 256 bytes, and
+	   so on up to 1 Mbyte defined by '1111'. This interrupt range
+	   can be calculated as follows:
+	   Range = 2^(5 + Limit) bytes.
+	 */
+	dw_limit = snd_aw2_saa7146_get_limit(period_size);
+	dw_page |= (dw_limit << 4);
+
+	if (stream_number == 0) {
+		WRITEREG(dw_page, PageA1_in);
+
+		/* Base address for DMA transfert. */
+		/* This address has been reserved by ALSA. */
+		/* This is a physical address */
+		WRITEREG(dma_addr, BaseA1_in);
+
+		/* Define upper limit for DMA access  */
+		WRITEREG(dma_addr + buffer_size, ProtA1_in);
+	} else {
+		printk(KERN_ERR
+		       "aw2: snd_aw2_saa7146_pcm_init_capture: "
+		       "Substream number is not 0 -> not managed\n");
+	}
+}
+
+void snd_aw2_saa7146_define_it_playback_callback(unsigned int stream_number,
+						 snd_aw2_saa7146_it_cb
+						 p_it_callback,
+						 void *p_callback_param)
+{
+	if (stream_number < NB_STREAM_PLAYBACK) {
+		arr_substream_it_playback_cb[stream_number].p_it_callback =
+		    (snd_aw2_saa7146_it_cb) p_it_callback;
+		arr_substream_it_playback_cb[stream_number].p_callback_param =
+		    (void *)p_callback_param;
+	}
+}
+
+void snd_aw2_saa7146_define_it_capture_callback(unsigned int stream_number,
+						snd_aw2_saa7146_it_cb
+						p_it_callback,
+						void *p_callback_param)
+{
+	if (stream_number < NB_STREAM_CAPTURE) {
+		arr_substream_it_capture_cb[stream_number].p_it_callback =
+		    (snd_aw2_saa7146_it_cb) p_it_callback;
+		arr_substream_it_capture_cb[stream_number].p_callback_param =
+		    (void *)p_callback_param;
+	}
+}
+
+void snd_aw2_saa7146_pcm_trigger_start_playback(struct snd_aw2_saa7146 *chip,
+						int stream_number)
+{
+	unsigned int acon1 = 0;
+	/* In aw8 driver, dma transfert is always active. It is
+	   started and stopped in a larger "space" */
+	acon1 = READREG(ACON1);
+	if (stream_number == 0) {
+		WRITEREG((TR_E_A2_OUT << 16) | TR_E_A2_OUT, MC1);
+
+		/* WS2_CTRL, WS2_SYNC: output TSL2, I2S */
+		acon1 |= 2 * WS2_CTRL;
+		WRITEREG(acon1, ACON1);
+
+	} else if (stream_number == 1) {
+		WRITEREG((TR_E_A1_OUT << 16) | TR_E_A1_OUT, MC1);
+
+		/* WS1_CTRL, WS1_SYNC: output TSL1, I2S */
+		acon1 |= 1 * WS1_CTRL;
+		WRITEREG(acon1, ACON1);
+	}
+}
+
+void snd_aw2_saa7146_pcm_trigger_stop_playback(struct snd_aw2_saa7146 *chip,
+					       int stream_number)
+{
+	unsigned int acon1 = 0;
+	acon1 = READREG(ACON1);
+	if (stream_number == 0) {
+		/* WS2_CTRL, WS2_SYNC: output TSL2, I2S */
+		acon1 &= ~(3 * WS2_CTRL);
+		WRITEREG(acon1, ACON1);
+
+		WRITEREG((TR_E_A2_OUT << 16), MC1);
+	} else if (stream_number == 1) {
+		/* WS1_CTRL, WS1_SYNC: output TSL1, I2S */
+		acon1 &= ~(3 * WS1_CTRL);
+		WRITEREG(acon1, ACON1);
+
+		WRITEREG((TR_E_A1_OUT << 16), MC1);
+	}
+}
+
+void snd_aw2_saa7146_pcm_trigger_start_capture(struct snd_aw2_saa7146 *chip,
+					       int stream_number)
+{
+	/* In aw8 driver, dma transfert is always active. It is
+	   started and stopped in a larger "space" */
+	if (stream_number == 0)
+		WRITEREG((TR_E_A1_IN << 16) | TR_E_A1_IN, MC1);
+}
+
+void snd_aw2_saa7146_pcm_trigger_stop_capture(struct snd_aw2_saa7146 *chip,
+					      int stream_number)
+{
+	if (stream_number == 0)
+		WRITEREG((TR_E_A1_IN << 16), MC1);
+}
+
+irqreturn_t snd_aw2_saa7146_interrupt(int irq, void *dev_id)
+{
+	unsigned int isr;
+	unsigned int iicsta;
+	struct snd_aw2_saa7146 *chip = dev_id;
+
+	isr = READREG(ISR);
+	if (!isr)
+		return IRQ_NONE;
+
+	WRITEREG(isr, ISR);
+
+	if (isr & (IIC_S | IIC_E)) {
+		iicsta = READREG(IICSTA);
+		WRITEREG(0x100, IICSTA);
+	}
+
+	if (isr & A1_out) {
+		if (arr_substream_it_playback_cb[1].p_it_callback != NULL) {
+			arr_substream_it_playback_cb[1].
+			    p_it_callback(arr_substream_it_playback_cb[1].
+					  p_callback_param);
+		}
+	}
+	if (isr & A2_out) {
+		if (arr_substream_it_playback_cb[0].p_it_callback != NULL) {
+			arr_substream_it_playback_cb[0].
+			    p_it_callback(arr_substream_it_playback_cb[0].
+					  p_callback_param);
+		}
+
+	}
+	if (isr & A1_in) {
+		if (arr_substream_it_capture_cb[0].p_it_callback != NULL) {
+			arr_substream_it_capture_cb[0].
+			    p_it_callback(arr_substream_it_capture_cb[0].
+					  p_callback_param);
+		}
+	}
+	return IRQ_HANDLED;
+}
+
+unsigned int snd_aw2_saa7146_get_hw_ptr_playback(struct snd_aw2_saa7146 *chip,
+						 int stream_number,
+						 unsigned char *start_addr,
+						 unsigned int buffer_size)
+{
+	long pci_adp = 0;
+	size_t ptr = 0;
+
+	if (stream_number == 0) {
+		pci_adp = READREG(PCI_ADP3);
+		ptr = pci_adp - (long)start_addr;
+
+		if (ptr == buffer_size)
+			ptr = 0;
+	}
+	if (stream_number == 1) {
+		pci_adp = READREG(PCI_ADP1);
+		ptr = pci_adp - (size_t) start_addr;
+
+		if (ptr == buffer_size)
+			ptr = 0;
+	}
+	return ptr;
+}
+
+unsigned int snd_aw2_saa7146_get_hw_ptr_capture(struct snd_aw2_saa7146 *chip,
+						int stream_number,
+						unsigned char *start_addr,
+						unsigned int buffer_size)
+{
+	size_t pci_adp = 0;
+	size_t ptr = 0;
+	if (stream_number == 0) {
+		pci_adp = READREG(PCI_ADP2);
+		ptr = pci_adp - (size_t) start_addr;
+
+		if (ptr == buffer_size)
+			ptr = 0;
+	}
+	return ptr;
+}
+
+void snd_aw2_saa7146_use_digital_input(struct snd_aw2_saa7146 *chip,
+				       int use_digital)
+{
+	/* FIXME: switch between analog and digital input does not always work.
+	   It can produce a kind of white noise. It seams that received data
+	   are inverted sometime (endian inversion). Why ? I don't know, maybe
+	   a problem of synchronization... However for the time being I have
+	   not found the problem. Workaround: switch again (and again) between
+	   digital and analog input until it works. */
+	if (use_digital)
+		WRITEREG(0x40, GPIO_CTRL);
+	else
+		WRITEREG(0x50, GPIO_CTRL);
+}
+
+int snd_aw2_saa7146_is_using_digital_input(struct snd_aw2_saa7146 *chip)
+{
+	unsigned int reg_val = READREG(GPIO_CTRL);
+	if ((reg_val & 0xFF) == 0x40)
+		return 1;
+	else
+		return 0;
+}
+
+
+static int snd_aw2_saa7146_get_limit(int size)
+{
+	int limitsize = 32;
+	int limit = 0;
+	while (limitsize < size) {
+		limitsize *= 2;
+		limit++;
+	}
+	return limit;
+}
diff --git a/linux/sound/pci/aw2/aw2-saa7146.h b/linux/sound/pci/aw2/aw2-saa7146.h
new file mode 100644
index 0000000..5b35e35
--- /dev/null
+++ b/linux/sound/pci/aw2/aw2-saa7146.h
@@ -0,0 +1,105 @@
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ * The Audiowerk2 ALSA driver 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; version 2.
+ *
+ * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ *
+ *****************************************************************************/
+
+#ifndef AW2_SAA7146_H
+#define AW2_SAA7146_H
+
+#define NB_STREAM_PLAYBACK 2
+#define NB_STREAM_CAPTURE 1
+
+#define NUM_STREAM_PLAYBACK_ANA 0
+#define NUM_STREAM_PLAYBACK_DIG 1
+
+#define NUM_STREAM_CAPTURE_ANA 0
+
+typedef void (*snd_aw2_saa7146_it_cb) (void *);
+
+struct snd_aw2_saa7146_cb_param {
+	snd_aw2_saa7146_it_cb p_it_callback;
+	void *p_callback_param;
+};
+
+/* definition of the chip-specific record */
+
+struct snd_aw2_saa7146 {
+	void __iomem *base_addr;
+};
+
+extern void snd_aw2_saa7146_setup(struct snd_aw2_saa7146 *chip,
+				  void __iomem *pci_base_addr);
+extern int snd_aw2_saa7146_free(struct snd_aw2_saa7146 *chip);
+
+extern void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip,
+					      int stream_number,
+					      unsigned long dma_addr,
+					      unsigned long period_size,
+					      unsigned long buffer_size);
+extern void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip,
+					     int stream_number,
+					     unsigned long dma_addr,
+					     unsigned long period_size,
+					     unsigned long buffer_size);
+extern void snd_aw2_saa7146_define_it_playback_callback(unsigned int
+							stream_number,
+							snd_aw2_saa7146_it_cb
+							p_it_callback,
+							void *p_callback_param);
+extern void snd_aw2_saa7146_define_it_capture_callback(unsigned int
+						       stream_number,
+						       snd_aw2_saa7146_it_cb
+						       p_it_callback,
+						       void *p_callback_param);
+extern void snd_aw2_saa7146_pcm_trigger_start_capture(struct snd_aw2_saa7146
+						      *chip, int stream_number);
+extern void snd_aw2_saa7146_pcm_trigger_stop_capture(struct snd_aw2_saa7146
+						     *chip, int stream_number);
+
+extern void snd_aw2_saa7146_pcm_trigger_start_playback(struct snd_aw2_saa7146
+						       *chip,
+						       int stream_number);
+extern void snd_aw2_saa7146_pcm_trigger_stop_playback(struct snd_aw2_saa7146
+						      *chip, int stream_number);
+
+extern irqreturn_t snd_aw2_saa7146_interrupt(int irq, void *dev_id);
+extern unsigned int snd_aw2_saa7146_get_hw_ptr_playback(struct snd_aw2_saa7146
+							*chip,
+							int stream_number,
+							unsigned char
+							*start_addr,
+							unsigned int
+							buffer_size);
+extern unsigned int snd_aw2_saa7146_get_hw_ptr_capture(struct snd_aw2_saa7146
+						       *chip,
+						       int stream_number,
+						       unsigned char
+						       *start_addr,
+						       unsigned int
+						       buffer_size);
+
+extern void snd_aw2_saa7146_use_digital_input(struct snd_aw2_saa7146 *chip,
+					      int use_digital);
+
+extern int snd_aw2_saa7146_is_using_digital_input(struct snd_aw2_saa7146
+						  *chip);
+
+#endif
diff --git a/linux/sound/pci/aw2/aw2-tsl.c b/linux/sound/pci/aw2/aw2-tsl.c
new file mode 100644
index 0000000..459b031
--- /dev/null
+++ b/linux/sound/pci/aw2/aw2-tsl.c
@@ -0,0 +1,110 @@
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ * Copyright 1998 Emagic Soft- und Hardware GmbH
+ * Copyright 2002 Martijn Sipkema
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ * The Audiowerk2 ALSA driver 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; version 2.
+ *
+ * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ *
+ *****************************************************************************/
+
+#define TSL_WS0		(1UL << 31)
+#define	TSL_WS1		(1UL << 30)
+#define	TSL_WS2		(1UL << 29)
+#define TSL_WS3		(1UL << 28)
+#define TSL_WS4		(1UL << 27)
+#define	TSL_DIS_A1	(1UL << 24)
+#define TSL_SDW_A1	(1UL << 23)
+#define TSL_SIB_A1	(1UL << 22)
+#define TSL_SF_A1	(1UL << 21)
+#define	TSL_LF_A1	(1UL << 20)
+#define TSL_BSEL_A1	(1UL << 17)
+#define TSL_DOD_A1	(1UL << 15)
+#define TSL_LOW_A1	(1UL << 14)
+#define TSL_DIS_A2	(1UL << 11)
+#define TSL_SDW_A2	(1UL << 10)
+#define TSL_SIB_A2	(1UL << 9)
+#define TSL_SF_A2	(1UL << 8)
+#define TSL_LF_A2	(1UL << 7)
+#define TSL_BSEL_A2	(1UL << 4)
+#define TSL_DOD_A2	(1UL << 2)
+#define TSL_LOW_A2	(1UL << 1)
+#define TSL_EOS		(1UL << 0)
+
+    /* Audiowerk8 hardware setup: */
+    /*      WS0, SD4, TSL1  - Analog/ digital in */
+    /*      WS1, SD0, TSL1  - Analog out #1, digital out */
+    /*      WS2, SD2, TSL1  - Analog out #2 */
+    /*      WS3, SD1, TSL2  - Analog out #3 */
+    /*      WS4, SD3, TSL2  - Analog out #4 */
+
+    /* Audiowerk8 timing: */
+    /*      Timeslot:     | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ... */
+
+    /*      A1_INPUT: */
+    /*      SD4:          <_ADC-L_>-------<_ADC-R_>-------< */
+    /*      WS0:          _______________/---------------\_ */
+
+    /*      A1_OUTPUT: */
+    /*      SD0:          <_1-L___>-------<_1-R___>-------< */
+    /*      WS1:          _______________/---------------\_ */
+    /*      SD2:          >-------<_2-L___>-------<_2-R___> */
+    /*      WS2:          -------\_______________/--------- */
+
+    /*      A2_OUTPUT: */
+    /*      SD1:          <_3-L___>-------<_3-R___>-------< */
+    /*      WS3:          _______________/---------------\_ */
+    /*      SD3:          >-------<_4-L___>-------<_4-R___> */
+    /*      WS4:          -------\_______________/--------- */
+
+static int tsl1[8] = {
+	1 * TSL_SDW_A1 | 3 * TSL_BSEL_A1 |
+	0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_LF_A1,
+
+	1 * TSL_SDW_A1 | 2 * TSL_BSEL_A1 |
+	0 * TSL_DIS_A1 | 0 * TSL_DOD_A1,
+
+	0 * TSL_SDW_A1 | 3 * TSL_BSEL_A1 |
+	0 * TSL_DIS_A1 | 0 * TSL_DOD_A1,
+
+	0 * TSL_SDW_A1 | 2 * TSL_BSEL_A1 |
+	0 * TSL_DIS_A1 | 0 * TSL_DOD_A1,
+
+	1 * TSL_SDW_A1 | 1 * TSL_BSEL_A1 |
+	0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0,
+
+	1 * TSL_SDW_A1 | 0 * TSL_BSEL_A1 |
+	0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0,
+
+	0 * TSL_SDW_A1 | 1 * TSL_BSEL_A1 |
+	0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0,
+
+	0 * TSL_SDW_A1 | 0 * TSL_BSEL_A1 | 0 * TSL_DIS_A1 |
+	0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0 | TSL_SF_A1 | TSL_EOS,
+};
+
+static int tsl2[8] = {
+	0 * TSL_SDW_A2 | 3 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_LF_A2,
+	0 * TSL_SDW_A2 | 2 * TSL_BSEL_A2 | 2 * TSL_DOD_A2,
+	0 * TSL_SDW_A2 | 3 * TSL_BSEL_A2 | 2 * TSL_DOD_A2,
+	0 * TSL_SDW_A2 | 2 * TSL_BSEL_A2 | 2 * TSL_DOD_A2,
+	0 * TSL_SDW_A2 | 1 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2,
+	0 * TSL_SDW_A2 | 0 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2,
+	0 * TSL_SDW_A2 | 1 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2,
+	0 * TSL_SDW_A2 | 0 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2 | TSL_EOS
+};
diff --git a/linux/sound/pci/aw2/saa7146.h b/linux/sound/pci/aw2/saa7146.h
new file mode 100644
index 0000000..ce0ab5f
--- /dev/null
+++ b/linux/sound/pci/aw2/saa7146.h
@@ -0,0 +1,168 @@
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ * The Audiowerk2 ALSA driver 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; version 2.
+ *
+ * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ *
+ *****************************************************************************/
+
+/* SAA7146 registers */
+#define PCI_BT_A	0x4C
+#define IICTFR		0x8C
+#define IICSTA		0x90
+#define BaseA1_in	0x94
+#define ProtA1_in	0x98
+#define PageA1_in	0x9C
+#define BaseA1_out	0xA0
+#define ProtA1_out	0xA4
+#define PageA1_out	0xA8
+#define BaseA2_in	0xAC
+#define ProtA2_in	0xB0
+#define PageA2_in	0xB4
+#define BaseA2_out	0xB8
+#define ProtA2_out	0xBC
+#define PageA2_out	0xC0
+#define IER		0xDC
+#define GPIO_CTRL	0xE0
+#define ACON1		0xF4
+#define ACON2		0xF8
+#define MC1		0xFC
+#define MC2		0x100
+#define ISR		0x10C
+#define PSR		0x110
+#define SSR		0x114
+#define PCI_ADP1	0x12C
+#define PCI_ADP2	0x130
+#define PCI_ADP3	0x134
+#define PCI_ADP4	0x138
+#define LEVEL_REP	0x140
+#define FB_BUFFER1	0x144
+#define FB_BUFFER2	0x148
+#define TSL1		0x180
+#define TSL2		0x1C0
+
+#define ME	(1UL << 11)
+#define LIMIT	(1UL << 4)
+#define PV	(1UL << 3)
+
+/* PSR/ISR/IER */
+#define PPEF		(1UL << 31)
+#define PABO		(1UL << 30)
+#define IIC_S		(1UL << 17)
+#define IIC_E		(1UL << 16)
+#define A2_in		(1UL << 15)
+#define A2_out		(1UL << 14)
+#define A1_in		(1UL << 13)
+#define A1_out		(1UL << 12)
+#define AFOU		(1UL << 11)
+#define PIN3		(1UL << 6)
+#define PIN2		(1UL << 5)
+#define PIN1		(1UL << 4)
+#define PIN0		(1UL << 3)
+#define ECS		(1UL << 2)
+#define EC3S		(1UL << 1)
+#define EC0S		(1UL << 0)
+
+/* SSR */
+#define PRQ		(1UL << 31)
+#define PMA		(1UL << 30)
+#define IIC_EA		(1UL << 21)
+#define IIC_EW		(1UL << 20)
+#define IIC_ER		(1UL << 19)
+#define IIC_EL		(1UL << 18)
+#define IIC_EF		(1UL << 17)
+#define AF2_in		(1UL << 10)
+#define AF2_out		(1UL << 9)
+#define AF1_in		(1UL << 8)
+#define AF1_out		(1UL << 7)
+#define EC5S		(1UL << 3)
+#define EC4S		(1UL << 2)
+#define EC2S		(1UL << 1)
+#define EC1S		(1UL << 0)
+
+/* PCI_BT_A */
+#define BurstA1_in	(1UL << 26)
+#define ThreshA1_in	(1UL << 24)
+#define BurstA1_out	(1UL << 18)
+#define ThreshA1_out	(1UL << 16)
+#define BurstA2_in	(1UL << 10)
+#define ThreshA2_in	(1UL << 8)
+#define BurstA2_out	(1UL << 2)
+#define ThreshA2_out	(1UL << 0)
+
+/* MC1 */
+#define MRST_N		(1UL << 15)
+#define EAP		(1UL << 9)
+#define EI2C		(1UL << 8)
+#define TR_E_A2_OUT	(1UL << 3)
+#define TR_E_A2_IN	(1UL << 2)
+#define TR_E_A1_OUT	(1UL << 1)
+#define TR_E_A1_IN	(1UL << 0)
+
+/* MC2 */
+#define UPLD_IIC	(1UL << 0)
+
+/* ACON1 */
+#define AUDIO_MODE	(1UL << 29)
+#define MAXLEVEL	(1UL << 22)
+#define A1_SWAP		(1UL << 21)
+#define A2_SWAP		(1UL << 20)
+#define WS0_CTRL	(1UL << 18)
+#define WS0_SYNC	(1UL << 16)
+#define WS1_CTRL	(1UL << 14)
+#define WS1_SYNC	(1UL << 12)
+#define WS2_CTRL	(1UL << 10)
+#define WS2_SYNC	(1UL << 8)
+#define WS3_CTRL	(1UL << 6)
+#define WS3_SYNC	(1UL << 4)
+#define WS4_CTRL	(1UL << 2)
+#define WS4_SYNC	(1UL << 0)
+
+/* ACON2 */
+#define A1_CLKSRC	(1UL << 27)
+#define A2_CLKSRC	(1UL << 22)
+#define INVERT_BCLK1	(1UL << 21)
+#define INVERT_BCLK2	(1UL << 20)
+#define BCLK1_OEN	(1UL << 19)
+#define BCLK2_OEN	(1UL << 18)
+
+/* IICSTA */
+#define IICCC		(1UL << 8)
+#define ABORT		(1UL << 7)
+#define SPERR		(1UL << 6)
+#define APERR		(1UL << 5)
+#define DTERR		(1UL << 4)
+#define DRERR		(1UL << 3)
+#define AL		(1UL << 2)
+#define ERR		(1UL << 1)
+#define BUSY		(1UL << 0)
+
+/* IICTFR */
+#define BYTE2		(1UL << 24)
+#define BYTE1		(1UL << 16)
+#define BYTE0		(1UL << 8)
+#define ATRR2		(1UL << 6)
+#define ATRR1		(1UL << 4)
+#define ATRR0		(1UL << 2)
+#define ERR		(1UL << 1)
+#define BUSY		(1UL << 0)
+
+#define START	3
+#define CONT	2
+#define STOP	1
+#define NOP	0
diff --git a/linux/sound/pci/azt3328.c b/linux/sound/pci/azt3328.c
new file mode 100644
index 0000000..2f3cacb
--- /dev/null
+++ b/linux/sound/pci/azt3328.c
@@ -0,0 +1,2540 @@
+/*
+ *  azt3328.c - driver for Aztech AZF3328 based soundcards (e.g. PCI168).
+ *  Copyright (C) 2002, 2005 - 2009 by Andreas Mohr <andi AT lisas.de>
+ *
+ *  Framework borrowed from Bart Hartgers's als4000.c.
+ *  Driver developed on PCI168 AP(W) version (PCI rev. 10, subsystem ID 1801),
+ *  found in a Fujitsu-Siemens PC ("Cordant", aluminum case).
+ *  Other versions are:
+ *  PCI168 A(W), sub ID 1800
+ *  PCI168 A/AP, sub ID 8000
+ *  Please give me feedback in case you try my driver with one of these!!
+ *
+ *  Keywords: Windows XP Vista 168nt4-125.zip 168win95-125.zip PCI 168 download
+ *  (XP/Vista do not support this card at all but every Linux distribution
+ *   has very good support out of the box;
+ *   just to make sure that the right people hit this and get to know that,
+ *   despite the high level of Internet ignorance - as usual :-P -
+ *   about very good support for this card - on Linux!)
+ *
+ * GPL LICENSE
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * NOTES
+ *  Since Aztech does not provide any chipset documentation,
+ *  even on repeated request to various addresses,
+ *  and the answer that was finally given was negative
+ *  (and I was stupid enough to manage to get hold of a PCI168 soundcard
+ *  in the first place >:-P}),
+ *  I was forced to base this driver on reverse engineering
+ *  (3 weeks' worth of evenings filled with driver work).
+ *  (and no, I did NOT go the easy way: to pick up a SB PCI128 for 9 Euros)
+ *
+ *  It is quite likely that the AZF3328 chip is the PCI cousin of the
+ *  AZF3318 ("azt1020 pnp", "MM Pro 16") ISA chip, given very similar specs.
+ *
+ *  The AZF3328 chip (note: AZF3328, *not* AZT3328, that's just the driver name
+ *  for compatibility reasons) from Azfin (joint-venture of Aztech and Fincitec,
+ *  Fincitec acquired by National Semiconductor in 2002, together with the
+ *  Fincitec-related company ARSmikro) has the following features:
+ *
+ *  - compatibility & compliance:
+ *    - Microsoft PC 97 ("PC 97 Hardware Design Guide",
+ *                       http://www.microsoft.com/whdc/archive/pcguides.mspx)
+ *    - Microsoft PC 98 Baseline Audio
+ *    - MPU401 UART
+ *    - Sound Blaster Emulation (DOS Box)
+ *  - builtin AC97 conformant codec (SNR over 80dB)
+ *    Note that "conformant" != "compliant"!! this chip's mixer register layout
+ *    *differs* from the standard AC97 layout:
+ *    they chose to not implement the headphone register (which is not a
+ *    problem since it's merely optional), yet when doing this, they committed
+ *    the grave sin of letting other registers follow immediately instead of
+ *    keeping a headphone dummy register, thereby shifting the mixer register
+ *    addresses illegally. So far unfortunately it looks like the very flexible
+ *    ALSA AC97 support is still not enough to easily compensate for such a
+ *    grave layout violation despite all tweaks and quirks mechanisms it offers.
+ *  - builtin genuine OPL3 - verified to work fine, 20080506
+ *  - full duplex 16bit playback/record at independent sampling rate
+ *  - MPU401 (+ legacy address support, claimed by one official spec sheet)
+ *    FIXME: how to enable legacy addr??
+ *  - game port (legacy address support)
+ *  - builtin DirectInput support, helps reduce CPU overhead (interrupt-driven
+ *    features supported). - See common term "Digital Enhanced Game Port"...
+ *    (probably DirectInput 3.0 spec - confirm)
+ *  - builtin 3D enhancement (said to be YAMAHA Ymersion)
+ *  - built-in General DirectX timer having a 20 bits counter
+ *    with 1us resolution (see below!)
+ *  - I2S serial output port for external DAC
+ *    [FIXME: 3.3V or 5V level? maximum rate is 66.2kHz right?]
+ *  - supports 33MHz PCI spec 2.1, PCI power management 1.0, compliant with ACPI
+ *  - supports hardware volume control
+ *  - single chip low cost solution (128 pin QFP)
+ *  - supports programmable Sub-vendor and Sub-system ID [24C02 SEEPROM chip]
+ *    required for Microsoft's logo compliance (FIXME: where?)
+ *    At least the Trident 4D Wave DX has one bit somewhere
+ *    to enable writes to PCI subsystem VID registers, that should be it.
+ *    This might easily be in extended PCI reg space, since PCI168 also has
+ *    some custom data starting at 0x80. What kind of config settings
+ *    are located in our extended PCI space anyway??
+ *  - PCI168 AP(W) card: power amplifier with 4 Watts/channel at 4 Ohms
+ *    [TDA1517P chip]
+ *
+ *  Note that this driver now is actually *better* than the Windows driver,
+ *  since it additionally supports the card's 1MHz DirectX timer - just try
+ *  the following snd-seq module parameters etc.:
+ *  - options snd-seq seq_default_timer_class=2 seq_default_timer_sclass=0
+ *    seq_default_timer_card=0 seq_client_load=1 seq_default_timer_device=0
+ *    seq_default_timer_subdevice=0 seq_default_timer_resolution=1000000
+ *  - "timidity -iAv -B2,8 -Os -EFreverb=0"
+ *  - "pmidi -p 128:0 jazz.mid"
+ *
+ *  OPL3 hardware playback testing, try something like:
+ *  cat /proc/asound/hwdep
+ *  and
+ *  aconnect -o
+ *  Then use
+ *  sbiload -Dhw:x,y --opl3 /usr/share/sounds/opl3/std.o3 ......./drums.o3
+ *  where x,y is the xx-yy number as given in hwdep.
+ *  Then try
+ *  pmidi -p a:b jazz.mid
+ *  where a:b is the client number plus 0 usually, as given by aconnect above.
+ *  Oh, and make sure to unmute the FM mixer control (doh!)
+ *  NOTE: power use during OPL3 playback is _VERY_ high (70W --> 90W!)
+ *  despite no CPU activity, possibly due to hindering ACPI idling somehow.
+ *  Shouldn't be a problem of the AZF3328 chip itself, I'd hope.
+ *  Higher PCM / FM mixer levels seem to conflict (causes crackling),
+ *  at least sometimes.   Maybe even use with hardware sequencer timer above :)
+ *  adplay/adplug-utils might soon offer hardware-based OPL3 playback, too.
+ *
+ *  Certain PCI versions of this card are susceptible to DMA traffic underruns
+ *  in some systems (resulting in sound crackling/clicking/popping),
+ *  probably because they don't have a DMA FIFO buffer or so.
+ *  Overview (PCI ID/PCI subID/PCI rev.):
+ *  - no DMA crackling on SiS735: 0x50DC/0x1801/16
+ *  - unknown performance: 0x50DC/0x1801/10
+ *    (well, it's not bad on an Athlon 1800 with now very optimized IRQ handler)
+ *
+ *  Crackling happens with VIA chipsets or, in my case, an SiS735, which is
+ *  supposed to be very fast and supposed to get rid of crackling much
+ *  better than a VIA, yet ironically I still get crackling, like many other
+ *  people with the same chipset.
+ *  Possible remedies:
+ *  - use speaker (amplifier) output instead of headphone output
+ *    (in case crackling is due to overloaded output clipping)
+ *  - plug card into a different PCI slot, preferrably one that isn't shared
+ *    too much (this helps a lot, but not completely!)
+ *  - get rid of PCI VGA card, use AGP instead
+ *  - upgrade or downgrade BIOS
+ *  - fiddle with PCI latency settings (setpci -v -s BUSID latency_timer=XX)
+ *    Not too helpful.
+ *  - Disable ACPI/power management/"Auto Detect RAM/PCI Clk" in BIOS
+ *
+ * BUGS
+ *  - full-duplex might *still* be problematic, however a recent test was fine
+ *  - (non-bug) "Bass/Treble or 3D settings don't work" - they do get evaluated
+ *    if you set PCM output switch to "pre 3D" instead of "post 3D".
+ *    If this can't be set, then get a mixer application that Isn't Stupid (tm)
+ *    (e.g. kmix, gamix) - unfortunately several are!!
+ *  - locking is not entirely clean, especially the audio stream activity
+ *    ints --> may be racy
+ *  - an _unconnected_ secondary joystick at the gameport will be reported
+ *    to be "active" (floating values, not precisely -1) due to the way we need
+ *    to read the Digital Enhanced Game Port. Not sure whether it is fixable.
+ *
+ * TODO
+ *  - use PCI_VDEVICE
+ *  - verify driver status on x86_64
+ *  - test multi-card driver operation
+ *  - (ab)use 1MHz DirectX timer as kernel clocksource
+ *  - test MPU401 MIDI playback etc.
+ *  - add more power micro-management (disable various units of the card
+ *    as long as they're unused, to improve audio quality and save power).
+ *    However this requires more I/O ports which I haven't figured out yet
+ *    and which thus might not even exist...
+ *    The standard suspend/resume functionality could probably make use of
+ *    some improvement, too...
+ *  - figure out what all unknown port bits are responsible for
+ *  - figure out some cleverly evil scheme to possibly make ALSA AC97 code
+ *    fully accept our quite incompatible ""AC97"" mixer and thus save some
+ *    code (but I'm not too optimistic that doing this is possible at all)
+ *  - use MMIO (memory-mapped I/O)? Slightly faster access, e.g. for gameport.
+ */
+
+#include <asm/io.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/moduleparam.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+#include "azt3328.h"
+
+MODULE_AUTHOR("Andreas Mohr <andi AT lisas.de>");
+MODULE_DESCRIPTION("Aztech AZF3328 (PCI168)");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Aztech,AZF3328}}");
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+#define SUPPORT_GAMEPORT 1
+#endif
+
+/* === Debug settings ===
+  Further diagnostic functionality than the settings below
+  does not need to be provided, since one can easily write a bash script
+  to dump the card's I/O ports (those listed in lspci -v -v):
+  function dump()
+  {
+    local descr=$1; local addr=$2; local count=$3
+
+    echo "${descr}: ${count} @ ${addr}:"
+    dd if=/dev/port skip=$[${addr}] count=${count} bs=1 2>/dev/null| hexdump -C
+  }
+  and then use something like
+  "dump joy200 0x200 8", "dump mpu388 0x388 4", "dump joy 0xb400 8",
+  "dump codec00 0xa800 32", "dump mixer 0xb800 64", "dump synth 0xbc00 8",
+  possibly within a "while true; do ... sleep 1; done" loop.
+  Tweaking ports could be done using
+  VALSTRING="`printf "%02x" $value`"
+  printf "\x""$VALSTRING"|dd of=/dev/port seek=$[${addr}] bs=1 2>/dev/null
+*/
+
+#define DEBUG_MISC	0
+#define DEBUG_CALLS	0
+#define DEBUG_MIXER	0
+#define DEBUG_CODEC	0
+#define DEBUG_IO	0
+#define DEBUG_TIMER	0
+#define DEBUG_GAME	0
+#define DEBUG_PM	0
+#define MIXER_TESTING	0
+
+#if DEBUG_MISC
+#define snd_azf3328_dbgmisc(format, args...) printk(KERN_DEBUG format, ##args)
+#else
+#define snd_azf3328_dbgmisc(format, args...)
+#endif
+
+#if DEBUG_CALLS
+#define snd_azf3328_dbgcalls(format, args...) printk(format, ##args)
+#define snd_azf3328_dbgcallenter() printk(KERN_DEBUG "--> %s\n", __func__)
+#define snd_azf3328_dbgcallleave() printk(KERN_DEBUG "<-- %s\n", __func__)
+#else
+#define snd_azf3328_dbgcalls(format, args...)
+#define snd_azf3328_dbgcallenter()
+#define snd_azf3328_dbgcallleave()
+#endif
+
+#if DEBUG_MIXER
+#define snd_azf3328_dbgmixer(format, args...) printk(KERN_DEBUG format, ##args)
+#else
+#define snd_azf3328_dbgmixer(format, args...)
+#endif
+
+#if DEBUG_CODEC
+#define snd_azf3328_dbgcodec(format, args...) printk(KERN_DEBUG format, ##args)
+#else
+#define snd_azf3328_dbgcodec(format, args...)
+#endif
+
+#if DEBUG_MISC
+#define snd_azf3328_dbgtimer(format, args...) printk(KERN_DEBUG format, ##args)
+#else
+#define snd_azf3328_dbgtimer(format, args...)
+#endif
+
+#if DEBUG_GAME
+#define snd_azf3328_dbggame(format, args...) printk(KERN_DEBUG format, ##args)
+#else
+#define snd_azf3328_dbggame(format, args...)
+#endif
+
+#if DEBUG_PM
+#define snd_azf3328_dbgpm(format, args...) printk(KERN_DEBUG format, ##args)
+#else
+#define snd_azf3328_dbgpm(format, args...)
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for AZF3328 soundcard.");
+
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for AZF3328 soundcard.");
+
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable AZF3328 soundcard.");
+
+static int seqtimer_scaling = 128;
+module_param(seqtimer_scaling, int, 0444);
+MODULE_PARM_DESC(seqtimer_scaling, "Set 1024000Hz sequencer timer scale factor (lockup danger!). Default 128.");
+
+struct snd_azf3328_codec_data {
+	unsigned long io_base;
+	struct snd_pcm_substream *substream;
+	bool running;
+	const char *name;
+};
+
+enum snd_azf3328_codec_type {
+  AZF_CODEC_PLAYBACK = 0,
+  AZF_CODEC_CAPTURE = 1,
+  AZF_CODEC_I2S_OUT = 2,
+};
+
+struct snd_azf3328 {
+	/* often-used fields towards beginning, then grouped */
+
+	unsigned long ctrl_io; /* usually 0xb000, size 128 */
+	unsigned long game_io;  /* usually 0xb400, size 8 */
+	unsigned long mpu_io;   /* usually 0xb800, size 4 */
+	unsigned long opl3_io; /* usually 0xbc00, size 8 */
+	unsigned long mixer_io; /* usually 0xc000, size 64 */
+
+	spinlock_t reg_lock;
+
+	struct snd_timer *timer;
+
+	struct snd_pcm *pcm[3];
+
+	/* playback, recording and I2S out codecs */
+	struct snd_azf3328_codec_data codecs[3];
+
+	struct snd_card *card;
+	struct snd_rawmidi *rmidi;
+
+#ifdef SUPPORT_GAMEPORT
+	struct gameport *gameport;
+	u16 axes[4];
+#endif
+
+	struct pci_dev *pci;
+	int irq;
+
+	/* register 0x6a is write-only, thus need to remember setting.
+	 * If we need to add more registers here, then we might try to fold this
+	 * into some transparent combined shadow register handling with
+	 * CONFIG_PM register storage below, but that's slightly difficult. */
+	u16 shadow_reg_ctrl_6AH;
+
+#ifdef CONFIG_PM
+	/* register value containers for power management
+	 * Note: not always full I/O range preserved (similar to Win driver!) */
+	u32 saved_regs_ctrl[AZF_ALIGN(AZF_IO_SIZE_CTRL_PM) / 4];
+	u32 saved_regs_game[AZF_ALIGN(AZF_IO_SIZE_GAME_PM) / 4];
+	u32 saved_regs_mpu[AZF_ALIGN(AZF_IO_SIZE_MPU_PM) / 4];
+	u32 saved_regs_opl3[AZF_ALIGN(AZF_IO_SIZE_OPL3_PM) / 4];
+	u32 saved_regs_mixer[AZF_ALIGN(AZF_IO_SIZE_MIXER_PM) / 4];
+#endif
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_azf3328_ids) = {
+	{ 0x122D, 0x50DC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },   /* PCI168/3328 */
+	{ 0x122D, 0x80DA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },   /* 3328 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_azf3328_ids);
+
+
+static int
+snd_azf3328_io_reg_setb(unsigned reg, u8 mask, bool do_set)
+{
+	u8 prev = inb(reg), new;
+
+	new = (do_set) ? (prev|mask) : (prev & ~mask);
+	/* we need to always write the new value no matter whether it differs
+	 * or not, since some register bits don't indicate their setting */
+	outb(new, reg);
+	if (new != prev)
+		return 1;
+
+	return 0;
+}
+
+static inline void
+snd_azf3328_codec_outb(const struct snd_azf3328_codec_data *codec,
+		       unsigned reg,
+		       u8 value
+)
+{
+	outb(value, codec->io_base + reg);
+}
+
+static inline u8
+snd_azf3328_codec_inb(const struct snd_azf3328_codec_data *codec, unsigned reg)
+{
+	return inb(codec->io_base + reg);
+}
+
+static inline void
+snd_azf3328_codec_outw(const struct snd_azf3328_codec_data *codec,
+		       unsigned reg,
+		       u16 value
+)
+{
+	outw(value, codec->io_base + reg);
+}
+
+static inline u16
+snd_azf3328_codec_inw(const struct snd_azf3328_codec_data *codec, unsigned reg)
+{
+	return inw(codec->io_base + reg);
+}
+
+static inline void
+snd_azf3328_codec_outl(const struct snd_azf3328_codec_data *codec,
+		       unsigned reg,
+		       u32 value
+)
+{
+	outl(value, codec->io_base + reg);
+}
+
+static inline u32
+snd_azf3328_codec_inl(const struct snd_azf3328_codec_data *codec, unsigned reg)
+{
+	return inl(codec->io_base + reg);
+}
+
+static inline void
+snd_azf3328_ctrl_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value)
+{
+	outb(value, chip->ctrl_io + reg);
+}
+
+static inline u8
+snd_azf3328_ctrl_inb(const struct snd_azf3328 *chip, unsigned reg)
+{
+	return inb(chip->ctrl_io + reg);
+}
+
+static inline void
+snd_azf3328_ctrl_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
+{
+	outw(value, chip->ctrl_io + reg);
+}
+
+static inline void
+snd_azf3328_ctrl_outl(const struct snd_azf3328 *chip, unsigned reg, u32 value)
+{
+	outl(value, chip->ctrl_io + reg);
+}
+
+static inline void
+snd_azf3328_game_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value)
+{
+	outb(value, chip->game_io + reg);
+}
+
+static inline void
+snd_azf3328_game_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
+{
+	outw(value, chip->game_io + reg);
+}
+
+static inline u8
+snd_azf3328_game_inb(const struct snd_azf3328 *chip, unsigned reg)
+{
+	return inb(chip->game_io + reg);
+}
+
+static inline u16
+snd_azf3328_game_inw(const struct snd_azf3328 *chip, unsigned reg)
+{
+	return inw(chip->game_io + reg);
+}
+
+static inline void
+snd_azf3328_mixer_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
+{
+	outw(value, chip->mixer_io + reg);
+}
+
+static inline u16
+snd_azf3328_mixer_inw(const struct snd_azf3328 *chip, unsigned reg)
+{
+	return inw(chip->mixer_io + reg);
+}
+
+#define AZF_MUTE_BIT 0x80
+
+static bool
+snd_azf3328_mixer_set_mute(const struct snd_azf3328 *chip,
+			   unsigned reg, bool do_mute
+)
+{
+	unsigned long portbase = chip->mixer_io + reg + 1;
+	bool updated;
+
+	/* the mute bit is on the *second* (i.e. right) register of a
+	 * left/right channel setting */
+	updated = snd_azf3328_io_reg_setb(portbase, AZF_MUTE_BIT, do_mute);
+
+	/* indicate whether it was muted before */
+	return (do_mute) ? !updated : updated;
+}
+
+static void
+snd_azf3328_mixer_write_volume_gradually(const struct snd_azf3328 *chip,
+					 unsigned reg,
+					 unsigned char dst_vol_left,
+					 unsigned char dst_vol_right,
+					 int chan_sel, int delay
+)
+{
+	unsigned long portbase = chip->mixer_io + reg;
+	unsigned char curr_vol_left = 0, curr_vol_right = 0;
+	int left_change = 0, right_change = 0;
+
+	snd_azf3328_dbgcallenter();
+
+	if (chan_sel & SET_CHAN_LEFT) {
+		curr_vol_left  = inb(portbase + 1);
+
+		/* take care of muting flag contained in left channel */
+		if (curr_vol_left & AZF_MUTE_BIT)
+			dst_vol_left |= AZF_MUTE_BIT;
+		else
+			dst_vol_left &= ~AZF_MUTE_BIT;
+
+		left_change = (curr_vol_left > dst_vol_left) ? -1 : 1;
+	}
+
+	if (chan_sel & SET_CHAN_RIGHT) {
+		curr_vol_right = inb(portbase + 0);
+
+		right_change = (curr_vol_right > dst_vol_right) ? -1 : 1;
+	}
+
+	do {
+		if (left_change) {
+			if (curr_vol_left != dst_vol_left) {
+				curr_vol_left += left_change;
+				outb(curr_vol_left, portbase + 1);
+			} else
+			    left_change = 0;
+		}
+		if (right_change) {
+			if (curr_vol_right != dst_vol_right) {
+				curr_vol_right += right_change;
+
+			/* during volume change, the right channel is crackling
+			 * somewhat more than the left channel, unfortunately.
+			 * This seems to be a hardware issue. */
+				outb(curr_vol_right, portbase + 0);
+			} else
+			    right_change = 0;
+		}
+		if (delay)
+			mdelay(delay);
+	} while ((left_change) || (right_change));
+	snd_azf3328_dbgcallleave();
+}
+
+/*
+ * general mixer element
+ */
+struct azf3328_mixer_reg {
+	unsigned reg;
+	unsigned int lchan_shift, rchan_shift;
+	unsigned int mask;
+	unsigned int invert: 1;
+	unsigned int stereo: 1;
+	unsigned int enum_c: 4;
+};
+
+#define COMPOSE_MIXER_REG(reg,lchan_shift,rchan_shift,mask,invert,stereo,enum_c) \
+ ((reg) | (lchan_shift << 8) | (rchan_shift << 12) | \
+  (mask << 16) | \
+  (invert << 24) | \
+  (stereo << 25) | \
+  (enum_c << 26))
+
+static void snd_azf3328_mixer_reg_decode(struct azf3328_mixer_reg *r, unsigned long val)
+{
+	r->reg = val & 0xff;
+	r->lchan_shift = (val >> 8) & 0x0f;
+	r->rchan_shift = (val >> 12) & 0x0f;
+	r->mask = (val >> 16) & 0xff;
+	r->invert = (val >> 24) & 1;
+	r->stereo = (val >> 25) & 1;
+	r->enum_c = (val >> 26) & 0x0f;
+}
+
+/*
+ * mixer switches/volumes
+ */
+
+#define AZF3328_MIXER_SWITCH(xname, reg, shift, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_azf3328_info_mixer, \
+  .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \
+  .private_value = COMPOSE_MIXER_REG(reg, shift, 0, 0x1, invert, 0, 0), \
+}
+
+#define AZF3328_MIXER_VOL_STEREO(xname, reg, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_azf3328_info_mixer, \
+  .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \
+  .private_value = COMPOSE_MIXER_REG(reg, 8, 0, mask, invert, 1, 0), \
+}
+
+#define AZF3328_MIXER_VOL_MONO(xname, reg, mask, is_right_chan) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_azf3328_info_mixer, \
+  .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \
+  .private_value = COMPOSE_MIXER_REG(reg, is_right_chan ? 0 : 8, 0, mask, 1, 0, 0), \
+}
+
+#define AZF3328_MIXER_VOL_SPECIAL(xname, reg, mask, shift, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_azf3328_info_mixer, \
+  .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \
+  .private_value = COMPOSE_MIXER_REG(reg, shift, 0, mask, invert, 0, 0), \
+}
+
+#define AZF3328_MIXER_ENUM(xname, reg, enum_c, shift) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_azf3328_info_mixer_enum, \
+  .get = snd_azf3328_get_mixer_enum, .put = snd_azf3328_put_mixer_enum, \
+  .private_value = COMPOSE_MIXER_REG(reg, shift, 0, 0, 0, 0, enum_c), \
+}
+
+static int
+snd_azf3328_info_mixer(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_info *uinfo)
+{
+	struct azf3328_mixer_reg reg;
+
+	snd_azf3328_dbgcallenter();
+	snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
+	uinfo->type = reg.mask == 1 ?
+		SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = reg.stereo + 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = reg.mask;
+	snd_azf3328_dbgcallleave();
+	return 0;
+}
+
+static int
+snd_azf3328_get_mixer(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
+	struct azf3328_mixer_reg reg;
+	u16 oreg, val;
+
+	snd_azf3328_dbgcallenter();
+	snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
+
+	oreg = snd_azf3328_mixer_inw(chip, reg.reg);
+	val = (oreg >> reg.lchan_shift) & reg.mask;
+	if (reg.invert)
+		val = reg.mask - val;
+	ucontrol->value.integer.value[0] = val;
+	if (reg.stereo) {
+		val = (oreg >> reg.rchan_shift) & reg.mask;
+		if (reg.invert)
+			val = reg.mask - val;
+		ucontrol->value.integer.value[1] = val;
+	}
+	snd_azf3328_dbgmixer("get: %02x is %04x -> vol %02lx|%02lx "
+			     "(shift %02d|%02d, mask %02x, inv. %d, stereo %d)\n",
+		reg.reg, oreg,
+		ucontrol->value.integer.value[0], ucontrol->value.integer.value[1],
+		reg.lchan_shift, reg.rchan_shift, reg.mask, reg.invert, reg.stereo);
+	snd_azf3328_dbgcallleave();
+	return 0;
+}
+
+static int
+snd_azf3328_put_mixer(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
+	struct azf3328_mixer_reg reg;
+	u16 oreg, nreg, val;
+
+	snd_azf3328_dbgcallenter();
+	snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
+	oreg = snd_azf3328_mixer_inw(chip, reg.reg);
+	val = ucontrol->value.integer.value[0] & reg.mask;
+	if (reg.invert)
+		val = reg.mask - val;
+	nreg = oreg & ~(reg.mask << reg.lchan_shift);
+	nreg |= (val << reg.lchan_shift);
+	if (reg.stereo) {
+		val = ucontrol->value.integer.value[1] & reg.mask;
+		if (reg.invert)
+			val = reg.mask - val;
+		nreg &= ~(reg.mask << reg.rchan_shift);
+		nreg |= (val << reg.rchan_shift);
+	}
+	if (reg.mask >= 0x07) /* it's a volume control, so better take care */
+		snd_azf3328_mixer_write_volume_gradually(
+			chip, reg.reg, nreg >> 8, nreg & 0xff,
+			/* just set both channels, doesn't matter */
+			SET_CHAN_LEFT|SET_CHAN_RIGHT,
+			0);
+	else
+        	snd_azf3328_mixer_outw(chip, reg.reg, nreg);
+
+	snd_azf3328_dbgmixer("put: %02x to %02lx|%02lx, "
+			     "oreg %04x; shift %02d|%02d -> nreg %04x; after: %04x\n",
+		reg.reg, ucontrol->value.integer.value[0], ucontrol->value.integer.value[1],
+		oreg, reg.lchan_shift, reg.rchan_shift,
+		nreg, snd_azf3328_mixer_inw(chip, reg.reg));
+	snd_azf3328_dbgcallleave();
+	return (nreg != oreg);
+}
+
+static int
+snd_azf3328_info_mixer_enum(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts1[] = {
+		"Mic1", "Mic2"
+	};
+	static const char * const texts2[] = {
+		"Mix", "Mic"
+	};
+	static const char * const texts3[] = {
+		"Mic", "CD", "Video", "Aux",
+		"Line", "Mix", "Mix Mono", "Phone"
+        };
+	static const char * const texts4[] = {
+		"pre 3D", "post 3D"
+        };
+	struct azf3328_mixer_reg reg;
+	const char * const *p = NULL;
+
+	snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
+        uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+        uinfo->count = (reg.reg == IDX_MIXER_REC_SELECT) ? 2 : 1;
+        uinfo->value.enumerated.items = reg.enum_c;
+        if (uinfo->value.enumerated.item > reg.enum_c - 1U)
+                uinfo->value.enumerated.item = reg.enum_c - 1U;
+	if (reg.reg == IDX_MIXER_ADVCTL2) {
+		switch(reg.lchan_shift) {
+		case 8: /* modem out sel */
+			p = texts1;
+			break;
+		case 9: /* mono sel source */
+			p = texts2;
+			break;
+		case 15: /* PCM Out Path */
+			p = texts4;
+			break;
+		}
+	} else
+	if (reg.reg == IDX_MIXER_REC_SELECT)
+		p = texts3;
+
+	strcpy(uinfo->value.enumerated.name, p[uinfo->value.enumerated.item]);
+        return 0;
+}
+
+static int
+snd_azf3328_get_mixer_enum(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+        struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
+	struct azf3328_mixer_reg reg;
+        unsigned short val;
+
+	snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
+	val = snd_azf3328_mixer_inw(chip, reg.reg);
+	if (reg.reg == IDX_MIXER_REC_SELECT) {
+        	ucontrol->value.enumerated.item[0] = (val >> 8) & (reg.enum_c - 1);
+        	ucontrol->value.enumerated.item[1] = (val >> 0) & (reg.enum_c - 1);
+	} else
+        	ucontrol->value.enumerated.item[0] = (val >> reg.lchan_shift) & (reg.enum_c - 1);
+
+	snd_azf3328_dbgmixer("get_enum: %02x is %04x -> %d|%d (shift %02d, enum_c %d)\n",
+		reg.reg, val, ucontrol->value.enumerated.item[0], ucontrol->value.enumerated.item[1],
+		reg.lchan_shift, reg.enum_c);
+        return 0;
+}
+
+static int
+snd_azf3328_put_mixer_enum(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+        struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
+	struct azf3328_mixer_reg reg;
+	u16 oreg, nreg, val;
+
+	snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
+	oreg = snd_azf3328_mixer_inw(chip, reg.reg);
+	val = oreg;
+	if (reg.reg == IDX_MIXER_REC_SELECT) {
+        	if (ucontrol->value.enumerated.item[0] > reg.enum_c - 1U ||
+            	ucontrol->value.enumerated.item[1] > reg.enum_c - 1U)
+                	return -EINVAL;
+        	val = (ucontrol->value.enumerated.item[0] << 8) |
+        	      (ucontrol->value.enumerated.item[1] << 0);
+	} else {
+        	if (ucontrol->value.enumerated.item[0] > reg.enum_c - 1U)
+                	return -EINVAL;
+		val &= ~((reg.enum_c - 1) << reg.lchan_shift);
+        	val |= (ucontrol->value.enumerated.item[0] << reg.lchan_shift);
+	}
+	snd_azf3328_mixer_outw(chip, reg.reg, val);
+	nreg = val;
+
+	snd_azf3328_dbgmixer("put_enum: %02x to %04x, oreg %04x\n", reg.reg, val, oreg);
+	return (nreg != oreg);
+}
+
+static struct snd_kcontrol_new snd_azf3328_mixer_controls[] __devinitdata = {
+	AZF3328_MIXER_SWITCH("Master Playback Switch", IDX_MIXER_PLAY_MASTER, 15, 1),
+	AZF3328_MIXER_VOL_STEREO("Master Playback Volume", IDX_MIXER_PLAY_MASTER, 0x1f, 1),
+	AZF3328_MIXER_SWITCH("PCM Playback Switch", IDX_MIXER_WAVEOUT, 15, 1),
+	AZF3328_MIXER_VOL_STEREO("PCM Playback Volume",
+					IDX_MIXER_WAVEOUT, 0x1f, 1),
+	AZF3328_MIXER_SWITCH("PCM 3D Bypass Playback Switch",
+					IDX_MIXER_ADVCTL2, 7, 1),
+	AZF3328_MIXER_SWITCH("FM Playback Switch", IDX_MIXER_FMSYNTH, 15, 1),
+	AZF3328_MIXER_VOL_STEREO("FM Playback Volume", IDX_MIXER_FMSYNTH, 0x1f, 1),
+	AZF3328_MIXER_SWITCH("CD Playback Switch", IDX_MIXER_CDAUDIO, 15, 1),
+	AZF3328_MIXER_VOL_STEREO("CD Playback Volume", IDX_MIXER_CDAUDIO, 0x1f, 1),
+	AZF3328_MIXER_SWITCH("Capture Switch", IDX_MIXER_REC_VOLUME, 15, 1),
+	AZF3328_MIXER_VOL_STEREO("Capture Volume", IDX_MIXER_REC_VOLUME, 0x0f, 0),
+	AZF3328_MIXER_ENUM("Capture Source", IDX_MIXER_REC_SELECT, 8, 0),
+	AZF3328_MIXER_SWITCH("Mic Playback Switch", IDX_MIXER_MIC, 15, 1),
+	AZF3328_MIXER_VOL_MONO("Mic Playback Volume", IDX_MIXER_MIC, 0x1f, 1),
+	AZF3328_MIXER_SWITCH("Mic Boost (+20dB)", IDX_MIXER_MIC, 6, 0),
+	AZF3328_MIXER_SWITCH("Line Playback Switch", IDX_MIXER_LINEIN, 15, 1),
+	AZF3328_MIXER_VOL_STEREO("Line Playback Volume", IDX_MIXER_LINEIN, 0x1f, 1),
+	AZF3328_MIXER_SWITCH("Beep Playback Switch", IDX_MIXER_PCBEEP, 15, 1),
+	AZF3328_MIXER_VOL_SPECIAL("Beep Playback Volume", IDX_MIXER_PCBEEP, 0x0f, 1, 1),
+	AZF3328_MIXER_SWITCH("Video Playback Switch", IDX_MIXER_VIDEO, 15, 1),
+	AZF3328_MIXER_VOL_STEREO("Video Playback Volume", IDX_MIXER_VIDEO, 0x1f, 1),
+	AZF3328_MIXER_SWITCH("Aux Playback Switch", IDX_MIXER_AUX, 15, 1),
+	AZF3328_MIXER_VOL_STEREO("Aux Playback Volume", IDX_MIXER_AUX, 0x1f, 1),
+	AZF3328_MIXER_SWITCH("Modem Playback Switch", IDX_MIXER_MODEMOUT, 15, 1),
+	AZF3328_MIXER_VOL_MONO("Modem Playback Volume", IDX_MIXER_MODEMOUT, 0x1f, 1),
+	AZF3328_MIXER_SWITCH("Modem Capture Switch", IDX_MIXER_MODEMIN, 15, 1),
+	AZF3328_MIXER_VOL_MONO("Modem Capture Volume", IDX_MIXER_MODEMIN, 0x1f, 1),
+	AZF3328_MIXER_ENUM("Mic Select", IDX_MIXER_ADVCTL2, 2, 8),
+	AZF3328_MIXER_ENUM("Mono Output Select", IDX_MIXER_ADVCTL2, 2, 9),
+	AZF3328_MIXER_ENUM("PCM Output Route", IDX_MIXER_ADVCTL2, 2, 15), /* PCM Out Path, place in front since it controls *both* 3D and Bass/Treble! */
+	AZF3328_MIXER_VOL_SPECIAL("Tone Control - Treble", IDX_MIXER_BASSTREBLE, 0x07, 1, 0),
+	AZF3328_MIXER_VOL_SPECIAL("Tone Control - Bass", IDX_MIXER_BASSTREBLE, 0x07, 9, 0),
+	AZF3328_MIXER_SWITCH("3D Control - Switch", IDX_MIXER_ADVCTL2, 13, 0),
+	AZF3328_MIXER_VOL_SPECIAL("3D Control - Width", IDX_MIXER_ADVCTL1, 0x07, 1, 0), /* "3D Width" */
+	AZF3328_MIXER_VOL_SPECIAL("3D Control - Depth", IDX_MIXER_ADVCTL1, 0x03, 8, 0), /* "Hifi 3D" */
+#if MIXER_TESTING
+	AZF3328_MIXER_SWITCH("0", IDX_MIXER_ADVCTL2, 0, 0),
+	AZF3328_MIXER_SWITCH("1", IDX_MIXER_ADVCTL2, 1, 0),
+	AZF3328_MIXER_SWITCH("2", IDX_MIXER_ADVCTL2, 2, 0),
+	AZF3328_MIXER_SWITCH("3", IDX_MIXER_ADVCTL2, 3, 0),
+	AZF3328_MIXER_SWITCH("4", IDX_MIXER_ADVCTL2, 4, 0),
+	AZF3328_MIXER_SWITCH("5", IDX_MIXER_ADVCTL2, 5, 0),
+	AZF3328_MIXER_SWITCH("6", IDX_MIXER_ADVCTL2, 6, 0),
+	AZF3328_MIXER_SWITCH("7", IDX_MIXER_ADVCTL2, 7, 0),
+	AZF3328_MIXER_SWITCH("8", IDX_MIXER_ADVCTL2, 8, 0),
+	AZF3328_MIXER_SWITCH("9", IDX_MIXER_ADVCTL2, 9, 0),
+	AZF3328_MIXER_SWITCH("10", IDX_MIXER_ADVCTL2, 10, 0),
+	AZF3328_MIXER_SWITCH("11", IDX_MIXER_ADVCTL2, 11, 0),
+	AZF3328_MIXER_SWITCH("12", IDX_MIXER_ADVCTL2, 12, 0),
+	AZF3328_MIXER_SWITCH("13", IDX_MIXER_ADVCTL2, 13, 0),
+	AZF3328_MIXER_SWITCH("14", IDX_MIXER_ADVCTL2, 14, 0),
+	AZF3328_MIXER_SWITCH("15", IDX_MIXER_ADVCTL2, 15, 0),
+#endif
+};
+
+static u16 __devinitdata snd_azf3328_init_values[][2] = {
+        { IDX_MIXER_PLAY_MASTER,	MIXER_MUTE_MASK|0x1f1f },
+        { IDX_MIXER_MODEMOUT,		MIXER_MUTE_MASK|0x1f1f },
+	{ IDX_MIXER_BASSTREBLE,		0x0000 },
+	{ IDX_MIXER_PCBEEP,		MIXER_MUTE_MASK|0x1f1f },
+	{ IDX_MIXER_MODEMIN,		MIXER_MUTE_MASK|0x1f1f },
+	{ IDX_MIXER_MIC,		MIXER_MUTE_MASK|0x001f },
+	{ IDX_MIXER_LINEIN,		MIXER_MUTE_MASK|0x1f1f },
+	{ IDX_MIXER_CDAUDIO,		MIXER_MUTE_MASK|0x1f1f },
+	{ IDX_MIXER_VIDEO,		MIXER_MUTE_MASK|0x1f1f },
+	{ IDX_MIXER_AUX,		MIXER_MUTE_MASK|0x1f1f },
+        { IDX_MIXER_WAVEOUT,		MIXER_MUTE_MASK|0x1f1f },
+        { IDX_MIXER_FMSYNTH,		MIXER_MUTE_MASK|0x1f1f },
+        { IDX_MIXER_REC_VOLUME,		MIXER_MUTE_MASK|0x0707 },
+};
+
+static int __devinit
+snd_azf3328_mixer_new(struct snd_azf3328 *chip)
+{
+	struct snd_card *card;
+	const struct snd_kcontrol_new *sw;
+	unsigned int idx;
+	int err;
+
+	snd_azf3328_dbgcallenter();
+	if (snd_BUG_ON(!chip || !chip->card))
+		return -EINVAL;
+
+	card = chip->card;
+
+	/* mixer reset */
+	snd_azf3328_mixer_outw(chip, IDX_MIXER_RESET, 0x0000);
+
+	/* mute and zero volume channels */
+	for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_init_values); ++idx) {
+		snd_azf3328_mixer_outw(chip,
+			snd_azf3328_init_values[idx][0],
+			snd_azf3328_init_values[idx][1]);
+	}
+
+	/* add mixer controls */
+	sw = snd_azf3328_mixer_controls;
+	for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_mixer_controls);
+			++idx, ++sw) {
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(sw, chip))) < 0)
+			return err;
+	}
+	snd_component_add(card, "AZF3328 mixer");
+	strcpy(card->mixername, "AZF3328 mixer");
+
+	snd_azf3328_dbgcallleave();
+	return 0;
+}
+
+static int
+snd_azf3328_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	int res;
+	snd_azf3328_dbgcallenter();
+	res = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+	snd_azf3328_dbgcallleave();
+	return res;
+}
+
+static int
+snd_azf3328_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_azf3328_dbgcallenter();
+	snd_pcm_lib_free_pages(substream);
+	snd_azf3328_dbgcallleave();
+	return 0;
+}
+
+static void
+snd_azf3328_codec_setfmt(struct snd_azf3328 *chip,
+			       enum snd_azf3328_codec_type codec_type,
+			       enum azf_freq_t bitrate,
+			       unsigned int format_width,
+			       unsigned int channels
+)
+{
+	unsigned long flags;
+	const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type];
+	u16 val = 0xff00;
+
+	snd_azf3328_dbgcallenter();
+	switch (bitrate) {
+	case AZF_FREQ_4000:  val |= SOUNDFORMAT_FREQ_SUSPECTED_4000; break;
+	case AZF_FREQ_4800:  val |= SOUNDFORMAT_FREQ_SUSPECTED_4800; break;
+	case AZF_FREQ_5512:
+		/* the AZF3328 names it "5510" for some strange reason */
+			     val |= SOUNDFORMAT_FREQ_5510; break;
+	case AZF_FREQ_6620:  val |= SOUNDFORMAT_FREQ_6620; break;
+	case AZF_FREQ_8000:  val |= SOUNDFORMAT_FREQ_8000; break;
+	case AZF_FREQ_9600:  val |= SOUNDFORMAT_FREQ_9600; break;
+	case AZF_FREQ_11025: val |= SOUNDFORMAT_FREQ_11025; break;
+	case AZF_FREQ_13240: val |= SOUNDFORMAT_FREQ_SUSPECTED_13240; break;
+	case AZF_FREQ_16000: val |= SOUNDFORMAT_FREQ_16000; break;
+	case AZF_FREQ_22050: val |= SOUNDFORMAT_FREQ_22050; break;
+	case AZF_FREQ_32000: val |= SOUNDFORMAT_FREQ_32000; break;
+	default:
+		snd_printk(KERN_WARNING "unknown bitrate %d, assuming 44.1kHz!\n", bitrate);
+		/* fall-through */
+	case AZF_FREQ_44100: val |= SOUNDFORMAT_FREQ_44100; break;
+	case AZF_FREQ_48000: val |= SOUNDFORMAT_FREQ_48000; break;
+	case AZF_FREQ_66200: val |= SOUNDFORMAT_FREQ_SUSPECTED_66200; break;
+	}
+	/* val = 0xff07; 3m27.993s (65301Hz; -> 64000Hz???) hmm, 66120, 65967, 66123 */
+	/* val = 0xff09; 17m15.098s (13123,478Hz; -> 12000Hz???) hmm, 13237.2Hz? */
+	/* val = 0xff0a; 47m30.599s (4764,891Hz; -> 4800Hz???) yup, 4803Hz */
+	/* val = 0xff0c; 57m0.510s (4010,263Hz; -> 4000Hz???) yup, 4003Hz */
+	/* val = 0xff05; 5m11.556s (... -> 44100Hz) */
+	/* val = 0xff03; 10m21.529s (21872,463Hz; -> 22050Hz???) */
+	/* val = 0xff0f; 20m41.883s (10937,993Hz; -> 11025Hz???) */
+	/* val = 0xff0d; 41m23.135s (5523,600Hz; -> 5512Hz???) */
+	/* val = 0xff0e; 28m30.777s (8017Hz; -> 8000Hz???) */
+
+	if (channels == 2)
+		val |= SOUNDFORMAT_FLAG_2CHANNELS;
+
+	if (format_width == 16)
+		val |= SOUNDFORMAT_FLAG_16BIT;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	/* set bitrate/format */
+	snd_azf3328_codec_outw(codec, IDX_IO_CODEC_SOUNDFORMAT, val);
+
+	/* changing the bitrate/format settings switches off the
+	 * audio output with an annoying click in case of 8/16bit format change
+	 * (maybe shutting down DAC/ADC?), thus immediately
+	 * do some tweaking to reenable it and get rid of the clicking
+	 * (FIXME: yes, it works, but what exactly am I doing here?? :)
+	 * FIXME: does this have some side effects for full-duplex
+	 * or other dramatic side effects? */
+	if (codec_type == AZF_CODEC_PLAYBACK) /* only do it for playback */
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+			snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS) |
+			DMA_RUN_SOMETHING1 |
+			DMA_RUN_SOMETHING2 |
+			SOMETHING_ALMOST_ALWAYS_SET |
+			DMA_EPILOGUE_SOMETHING |
+			DMA_SOMETHING_ELSE
+		);
+
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_azf3328_dbgcallleave();
+}
+
+static inline void
+snd_azf3328_codec_setfmt_lowpower(struct snd_azf3328 *chip,
+			    enum snd_azf3328_codec_type codec_type
+)
+{
+	/* choose lowest frequency for low power consumption.
+	 * While this will cause louder noise due to rather coarse frequency,
+	 * it should never matter since output should always
+	 * get disabled properly when idle anyway. */
+	snd_azf3328_codec_setfmt(chip, codec_type, AZF_FREQ_4000, 8, 1);
+}
+
+static void
+snd_azf3328_ctrl_reg_6AH_update(struct snd_azf3328 *chip,
+					unsigned bitmask,
+					bool enable
+)
+{
+	bool do_mask = !enable;
+	if (do_mask)
+		chip->shadow_reg_ctrl_6AH |= bitmask;
+	else
+		chip->shadow_reg_ctrl_6AH &= ~bitmask;
+	snd_azf3328_dbgcodec("6AH_update mask 0x%04x do_mask %d: val 0x%04x\n",
+			bitmask, do_mask, chip->shadow_reg_ctrl_6AH);
+	snd_azf3328_ctrl_outw(chip, IDX_IO_6AH, chip->shadow_reg_ctrl_6AH);
+}
+
+static inline void
+snd_azf3328_ctrl_enable_codecs(struct snd_azf3328 *chip, bool enable)
+{
+	snd_azf3328_dbgcodec("codec_enable %d\n", enable);
+	/* no idea what exactly is being done here, but I strongly assume it's
+	 * PM related */
+	snd_azf3328_ctrl_reg_6AH_update(
+		chip, IO_6A_PAUSE_PLAYBACK_BIT8, enable
+	);
+}
+
+static void
+snd_azf3328_ctrl_codec_activity(struct snd_azf3328 *chip,
+				enum snd_azf3328_codec_type codec_type,
+				bool enable
+)
+{
+	struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type];
+	bool need_change = (codec->running != enable);
+
+	snd_azf3328_dbgcodec(
+		"codec_activity: %s codec, enable %d, need_change %d\n",
+				codec->name, enable, need_change
+	);
+	if (need_change) {
+		static const struct {
+			enum snd_azf3328_codec_type other1;
+			enum snd_azf3328_codec_type other2;
+		} peer_codecs[3] =
+			{ { AZF_CODEC_CAPTURE, AZF_CODEC_I2S_OUT },
+			  { AZF_CODEC_PLAYBACK, AZF_CODEC_I2S_OUT },
+			  { AZF_CODEC_PLAYBACK, AZF_CODEC_CAPTURE } };
+		bool call_function;
+
+		if (enable)
+			/* if enable codec, call enable_codecs func
+			   to enable codec supply... */
+			call_function = 1;
+		else {
+			/* ...otherwise call enable_codecs func
+			   (which globally shuts down operation of codecs)
+			   only in case the other codecs are currently
+			   not active either! */
+			call_function =
+				((!chip->codecs[peer_codecs[codec_type].other1]
+					.running)
+			     &&  (!chip->codecs[peer_codecs[codec_type].other2]
+					.running));
+		 }
+		 if (call_function)
+			snd_azf3328_ctrl_enable_codecs(chip, enable);
+
+		/* ...and adjust clock, too
+		 * (reduce noise and power consumption) */
+		if (!enable)
+			snd_azf3328_codec_setfmt_lowpower(
+				chip,
+				codec_type
+			);
+		codec->running = enable;
+	}
+}
+
+static void
+snd_azf3328_codec_setdmaa(struct snd_azf3328 *chip,
+				enum snd_azf3328_codec_type codec_type,
+				unsigned long addr,
+				unsigned int count,
+				unsigned int size
+)
+{
+	const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type];
+	snd_azf3328_dbgcallenter();
+	if (!codec->running) {
+		/* AZF3328 uses a two buffer pointer DMA transfer approach */
+
+		unsigned long flags, addr_area2;
+
+		/* width 32bit (prevent overflow): */
+		u32 count_areas, lengths;
+
+		count_areas = size/2;
+		addr_area2 = addr+count_areas;
+		snd_azf3328_dbgcodec("setdma: buffers %08lx[%u] / %08lx[%u]\n",
+				addr, count_areas, addr_area2, count_areas);
+
+		count_areas--; /* max. index */
+
+		/* build combined I/O buffer length word */
+		lengths = (count_areas << 16) | (count_areas);
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		snd_azf3328_codec_outl(codec, IDX_IO_CODEC_DMA_START_1, addr);
+		snd_azf3328_codec_outl(codec, IDX_IO_CODEC_DMA_START_2,
+								addr_area2);
+		snd_azf3328_codec_outl(codec, IDX_IO_CODEC_DMA_LENGTHS,
+								lengths);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	}
+	snd_azf3328_dbgcallleave();
+}
+
+static int
+snd_azf3328_codec_prepare(struct snd_pcm_substream *substream)
+{
+#if 0
+	struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+        unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+#endif
+
+	snd_azf3328_dbgcallenter();
+#if 0
+	snd_azf3328_codec_setfmt(chip, AZF_CODEC_...,
+		runtime->rate,
+		snd_pcm_format_width(runtime->format),
+		runtime->channels);
+	snd_azf3328_codec_setdmaa(chip, AZF_CODEC_...,
+					runtime->dma_addr, count, size);
+#endif
+	snd_azf3328_dbgcallleave();
+	return 0;
+}
+
+static int
+snd_azf3328_codec_trigger(enum snd_azf3328_codec_type codec_type,
+			struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
+	const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type];
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int result = 0;
+	u16 flags1;
+	bool previously_muted = 0;
+	bool is_playback_codec = (AZF_CODEC_PLAYBACK == codec_type);
+
+	snd_azf3328_dbgcalls("snd_azf3328_codec_trigger cmd %d\n", cmd);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_azf3328_dbgcodec("START %s\n", codec->name);
+
+		if (is_playback_codec) {
+			/* mute WaveOut (avoid clicking during setup) */
+			previously_muted =
+				snd_azf3328_mixer_set_mute(
+						chip, IDX_MIXER_WAVEOUT, 1
+				);
+		}
+
+		snd_azf3328_codec_setfmt(chip, codec_type,
+			runtime->rate,
+			snd_pcm_format_width(runtime->format),
+			runtime->channels);
+
+		spin_lock(&chip->reg_lock);
+		/* first, remember current value: */
+		flags1 = snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS);
+
+		/* stop transfer */
+		flags1 &= ~DMA_RESUME;
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
+
+		/* FIXME: clear interrupts or what??? */
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_IRQTYPE, 0xffff);
+		spin_unlock(&chip->reg_lock);
+
+		snd_azf3328_codec_setdmaa(chip, codec_type, runtime->dma_addr,
+			snd_pcm_lib_period_bytes(substream),
+			snd_pcm_lib_buffer_bytes(substream)
+		);
+
+		spin_lock(&chip->reg_lock);
+#ifdef WIN9X
+		/* FIXME: enable playback/recording??? */
+		flags1 |= DMA_RUN_SOMETHING1 | DMA_RUN_SOMETHING2;
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
+
+		/* start transfer again */
+		/* FIXME: what is this value (0x0010)??? */
+		flags1 |= DMA_RESUME | DMA_EPILOGUE_SOMETHING;
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
+#else /* NT4 */
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+			0x0000);
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+			DMA_RUN_SOMETHING1);
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+			DMA_RUN_SOMETHING1 |
+			DMA_RUN_SOMETHING2);
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+			DMA_RESUME |
+			SOMETHING_ALMOST_ALWAYS_SET |
+			DMA_EPILOGUE_SOMETHING |
+			DMA_SOMETHING_ELSE);
+#endif
+		spin_unlock(&chip->reg_lock);
+		snd_azf3328_ctrl_codec_activity(chip, codec_type, 1);
+
+		if (is_playback_codec) {
+			/* now unmute WaveOut */
+			if (!previously_muted)
+				snd_azf3328_mixer_set_mute(
+						chip, IDX_MIXER_WAVEOUT, 0
+				);
+		}
+
+		snd_azf3328_dbgcodec("STARTED %s\n", codec->name);
+		break;
+	case SNDRV_PCM_TRIGGER_RESUME:
+		snd_azf3328_dbgcodec("RESUME %s\n", codec->name);
+		/* resume codec if we were active */
+		spin_lock(&chip->reg_lock);
+		if (codec->running)
+			snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+				snd_azf3328_codec_inw(
+					codec, IDX_IO_CODEC_DMA_FLAGS
+				) | DMA_RESUME
+			);
+		spin_unlock(&chip->reg_lock);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_azf3328_dbgcodec("STOP %s\n", codec->name);
+
+		if (is_playback_codec) {
+			/* mute WaveOut (avoid clicking during setup) */
+			previously_muted =
+				snd_azf3328_mixer_set_mute(
+						chip, IDX_MIXER_WAVEOUT, 1
+				);
+		}
+
+		spin_lock(&chip->reg_lock);
+		/* first, remember current value: */
+		flags1 = snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS);
+
+		/* stop transfer */
+		flags1 &= ~DMA_RESUME;
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
+
+		/* hmm, is this really required? we're resetting the same bit
+		 * immediately thereafter... */
+		flags1 |= DMA_RUN_SOMETHING1;
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
+
+		flags1 &= ~DMA_RUN_SOMETHING1;
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
+		spin_unlock(&chip->reg_lock);
+		snd_azf3328_ctrl_codec_activity(chip, codec_type, 0);
+
+		if (is_playback_codec) {
+			/* now unmute WaveOut */
+			if (!previously_muted)
+				snd_azf3328_mixer_set_mute(
+						chip, IDX_MIXER_WAVEOUT, 0
+				);
+		}
+
+		snd_azf3328_dbgcodec("STOPPED %s\n", codec->name);
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		snd_azf3328_dbgcodec("SUSPEND %s\n", codec->name);
+		/* make sure codec is stopped */
+		snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
+			snd_azf3328_codec_inw(
+				codec, IDX_IO_CODEC_DMA_FLAGS
+			) & ~DMA_RESUME
+		);
+		break;
+        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_PUSH NIY!\n");
+                break;
+        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_RELEASE NIY!\n");
+                break;
+        default:
+		snd_printk(KERN_ERR "FIXME: unknown trigger mode!\n");
+                return -EINVAL;
+	}
+
+	snd_azf3328_dbgcallleave();
+	return result;
+}
+
+static int
+snd_azf3328_codec_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	return snd_azf3328_codec_trigger(AZF_CODEC_PLAYBACK, substream, cmd);
+}
+
+static int
+snd_azf3328_codec_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	return snd_azf3328_codec_trigger(AZF_CODEC_CAPTURE, substream, cmd);
+}
+
+static int
+snd_azf3328_codec_i2s_out_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	return snd_azf3328_codec_trigger(AZF_CODEC_I2S_OUT, substream, cmd);
+}
+
+static snd_pcm_uframes_t
+snd_azf3328_codec_pointer(struct snd_pcm_substream *substream,
+			  enum snd_azf3328_codec_type codec_type
+)
+{
+	const struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
+	const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type];
+	unsigned long bufptr, result;
+	snd_pcm_uframes_t frmres;
+
+#ifdef QUERY_HARDWARE
+	bufptr = snd_azf3328_codec_inl(codec, IDX_IO_CODEC_DMA_START_1);
+#else
+	bufptr = substream->runtime->dma_addr;
+#endif
+	result = snd_azf3328_codec_inl(codec, IDX_IO_CODEC_DMA_CURRPOS);
+
+	/* calculate offset */
+	result -= bufptr;
+	frmres = bytes_to_frames( substream->runtime, result);
+	snd_azf3328_dbgcodec("%s @ 0x%8lx, frames %8ld\n",
+				codec->name, result, frmres);
+	return frmres;
+}
+
+static snd_pcm_uframes_t
+snd_azf3328_codec_playback_pointer(struct snd_pcm_substream *substream)
+{
+	return snd_azf3328_codec_pointer(substream, AZF_CODEC_PLAYBACK);
+}
+
+static snd_pcm_uframes_t
+snd_azf3328_codec_capture_pointer(struct snd_pcm_substream *substream)
+{
+	return snd_azf3328_codec_pointer(substream, AZF_CODEC_CAPTURE);
+}
+
+static snd_pcm_uframes_t
+snd_azf3328_codec_i2s_out_pointer(struct snd_pcm_substream *substream)
+{
+	return snd_azf3328_codec_pointer(substream, AZF_CODEC_I2S_OUT);
+}
+
+/******************************************************************/
+
+#ifdef SUPPORT_GAMEPORT
+static inline void
+snd_azf3328_gameport_irq_enable(struct snd_azf3328 *chip,
+				bool enable
+)
+{
+	snd_azf3328_io_reg_setb(
+		chip->game_io+IDX_GAME_HWCONFIG,
+		GAME_HWCFG_IRQ_ENABLE,
+		enable
+	);
+}
+
+static inline void
+snd_azf3328_gameport_legacy_address_enable(struct snd_azf3328 *chip,
+					   bool enable
+)
+{
+	snd_azf3328_io_reg_setb(
+		chip->game_io+IDX_GAME_HWCONFIG,
+		GAME_HWCFG_LEGACY_ADDRESS_ENABLE,
+		enable
+	);
+}
+
+static void
+snd_azf3328_gameport_set_counter_frequency(struct snd_azf3328 *chip,
+					   unsigned int freq_cfg
+)
+{
+	snd_azf3328_io_reg_setb(
+		chip->game_io+IDX_GAME_HWCONFIG,
+		0x02,
+		(freq_cfg & 1) != 0
+	);
+	snd_azf3328_io_reg_setb(
+		chip->game_io+IDX_GAME_HWCONFIG,
+		0x04,
+		(freq_cfg & 2) != 0
+	);
+}
+
+static inline void
+snd_azf3328_gameport_axis_circuit_enable(struct snd_azf3328 *chip, bool enable)
+{
+	snd_azf3328_ctrl_reg_6AH_update(
+		chip, IO_6A_SOMETHING2_GAMEPORT, enable
+	);
+}
+
+static inline void
+snd_azf3328_gameport_interrupt(struct snd_azf3328 *chip)
+{
+	/*
+	 * skeleton handler only
+	 * (we do not want axis reading in interrupt handler - too much load!)
+	 */
+	snd_azf3328_dbggame("gameport irq\n");
+
+	 /* this should ACK the gameport IRQ properly, hopefully. */
+	snd_azf3328_game_inw(chip, IDX_GAME_AXIS_VALUE);
+}
+
+static int
+snd_azf3328_gameport_open(struct gameport *gameport, int mode)
+{
+	struct snd_azf3328 *chip = gameport_get_port_data(gameport);
+	int res;
+
+	snd_azf3328_dbggame("gameport_open, mode %d\n", mode);
+	switch (mode) {
+	case GAMEPORT_MODE_COOKED:
+	case GAMEPORT_MODE_RAW:
+		res = 0;
+		break;
+	default:
+		res = -1;
+		break;
+	}
+
+	snd_azf3328_gameport_set_counter_frequency(chip,
+				GAME_HWCFG_ADC_COUNTER_FREQ_STD);
+	snd_azf3328_gameport_axis_circuit_enable(chip, (res == 0));
+
+	return res;
+}
+
+static void
+snd_azf3328_gameport_close(struct gameport *gameport)
+{
+	struct snd_azf3328 *chip = gameport_get_port_data(gameport);
+
+	snd_azf3328_dbggame("gameport_close\n");
+	snd_azf3328_gameport_set_counter_frequency(chip,
+				GAME_HWCFG_ADC_COUNTER_FREQ_1_200);
+	snd_azf3328_gameport_axis_circuit_enable(chip, 0);
+}
+
+static int
+snd_azf3328_gameport_cooked_read(struct gameport *gameport,
+				 int *axes,
+				 int *buttons
+)
+{
+	struct snd_azf3328 *chip = gameport_get_port_data(gameport);
+	int i;
+	u8 val;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!chip))
+		return 0;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = snd_azf3328_game_inb(chip, IDX_GAME_LEGACY_COMPATIBLE);
+	*buttons = (~(val) >> 4) & 0xf;
+
+	/* ok, this one is a bit dirty: cooked_read is being polled by a timer,
+	 * thus we're atomic and cannot actively wait in here
+	 * (which would be useful for us since it probably would be better
+	 * to trigger a measurement in here, then wait a short amount of
+	 * time until it's finished, then read values of _this_ measurement).
+	 *
+	 * Thus we simply resort to reading values if they're available already
+	 * and trigger the next measurement.
+	 */
+
+	val = snd_azf3328_game_inb(chip, IDX_GAME_AXES_CONFIG);
+	if (val & GAME_AXES_SAMPLING_READY) {
+		for (i = 0; i < ARRAY_SIZE(chip->axes); ++i) {
+			/* configure the axis to read */
+			val = (i << 4) | 0x0f;
+			snd_azf3328_game_outb(chip, IDX_GAME_AXES_CONFIG, val);
+
+			chip->axes[i] = snd_azf3328_game_inw(
+						chip, IDX_GAME_AXIS_VALUE
+					);
+		}
+	}
+
+	/* trigger next axes sampling, to be evaluated the next time we
+	 * enter this function */
+
+	/* for some very, very strange reason we cannot enable
+	 * Measurement Ready monitoring for all axes here,
+	 * at least not when only one joystick connected */
+	val = 0x03; /* we're able to monitor axes 1 and 2 only */
+	snd_azf3328_game_outb(chip, IDX_GAME_AXES_CONFIG, val);
+
+	snd_azf3328_game_outw(chip, IDX_GAME_AXIS_VALUE, 0xffff);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	for (i = 0; i < ARRAY_SIZE(chip->axes); i++) {
+		axes[i] = chip->axes[i];
+		if (axes[i] == 0xffff)
+			axes[i] = -1;
+	}
+
+	snd_azf3328_dbggame("cooked_read: axes %d %d %d %d buttons %d\n",
+		axes[0], axes[1], axes[2], axes[3], *buttons
+	);
+
+	return 0;
+}
+
+static int __devinit
+snd_azf3328_gameport(struct snd_azf3328 *chip, int dev)
+{
+	struct gameport *gp;
+
+	chip->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "azt3328: cannot alloc memory for gameport\n");
+		return -ENOMEM;
+	}
+
+	gameport_set_name(gp, "AZF3328 Gameport");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
+	gameport_set_dev_parent(gp, &chip->pci->dev);
+	gp->io = chip->game_io;
+	gameport_set_port_data(gp, chip);
+
+	gp->open = snd_azf3328_gameport_open;
+	gp->close = snd_azf3328_gameport_close;
+	gp->fuzz = 16; /* seems ok */
+	gp->cooked_read = snd_azf3328_gameport_cooked_read;
+
+	/* DISABLE legacy address: we don't need it! */
+	snd_azf3328_gameport_legacy_address_enable(chip, 0);
+
+	snd_azf3328_gameport_set_counter_frequency(chip,
+				GAME_HWCFG_ADC_COUNTER_FREQ_1_200);
+	snd_azf3328_gameport_axis_circuit_enable(chip, 0);
+
+	gameport_register_port(chip->gameport);
+
+	return 0;
+}
+
+static void
+snd_azf3328_gameport_free(struct snd_azf3328 *chip)
+{
+	if (chip->gameport) {
+		gameport_unregister_port(chip->gameport);
+		chip->gameport = NULL;
+	}
+	snd_azf3328_gameport_irq_enable(chip, 0);
+}
+#else
+static inline int
+snd_azf3328_gameport(struct snd_azf3328 *chip, int dev) { return -ENOSYS; }
+static inline void
+snd_azf3328_gameport_free(struct snd_azf3328 *chip) { }
+static inline void
+snd_azf3328_gameport_interrupt(struct snd_azf3328 *chip)
+{
+	printk(KERN_WARNING "huh, game port IRQ occurred!?\n");
+}
+#endif /* SUPPORT_GAMEPORT */
+
+/******************************************************************/
+
+static inline void
+snd_azf3328_irq_log_unknown_type(u8 which)
+{
+	snd_azf3328_dbgcodec(
+	"azt3328: unknown IRQ type (%x) occurred, please report!\n",
+		which
+	);
+}
+
+static inline void
+snd_azf3328_codec_interrupt(struct snd_azf3328 *chip, u8 status)
+{
+	u8 which;
+	enum snd_azf3328_codec_type codec_type;
+	const struct snd_azf3328_codec_data *codec;
+
+	for (codec_type = AZF_CODEC_PLAYBACK;
+		 codec_type <= AZF_CODEC_I2S_OUT;
+			 ++codec_type) {
+
+		/* skip codec if there's no interrupt for it */
+		if (!(status & (1 << codec_type)))
+			continue;
+
+		codec = &chip->codecs[codec_type];
+
+		spin_lock(&chip->reg_lock);
+		which = snd_azf3328_codec_inb(codec, IDX_IO_CODEC_IRQTYPE);
+		/* ack all IRQ types immediately */
+		snd_azf3328_codec_outb(codec, IDX_IO_CODEC_IRQTYPE, which);
+		spin_unlock(&chip->reg_lock);
+
+		if ((chip->pcm[codec_type]) && (codec->substream)) {
+			snd_pcm_period_elapsed(codec->substream);
+			snd_azf3328_dbgcodec("%s period done (#%x), @ %x\n",
+				codec->name,
+				which,
+				snd_azf3328_codec_inl(
+					codec, IDX_IO_CODEC_DMA_CURRPOS
+				)
+			);
+		} else
+			printk(KERN_WARNING "azt3328: irq handler problem!\n");
+		if (which & IRQ_SOMETHING)
+			snd_azf3328_irq_log_unknown_type(which);
+	}
+}
+
+static irqreturn_t
+snd_azf3328_interrupt(int irq, void *dev_id)
+{
+	struct snd_azf3328 *chip = dev_id;
+	u8 status;
+#if DEBUG_CODEC
+	static unsigned long irq_count;
+#endif
+
+	status = snd_azf3328_ctrl_inb(chip, IDX_IO_IRQSTATUS);
+
+        /* fast path out, to ease interrupt sharing */
+	if (!(status &
+		(IRQ_PLAYBACK|IRQ_RECORDING|IRQ_I2S_OUT
+		|IRQ_GAMEPORT|IRQ_MPU401|IRQ_TIMER)
+	))
+		return IRQ_NONE; /* must be interrupt for another device */
+
+	snd_azf3328_dbgcodec(
+		"irq_count %ld! IDX_IO_IRQSTATUS %04x\n",
+			irq_count++ /* debug-only */,
+			status
+	);
+
+	if (status & IRQ_TIMER) {
+		/* snd_azf3328_dbgcodec("timer %ld\n",
+			snd_azf3328_codec_inl(chip, IDX_IO_TIMER_VALUE)
+				& TIMER_VALUE_MASK
+		); */
+		if (chip->timer)
+			snd_timer_interrupt(chip->timer, chip->timer->sticks);
+		/* ACK timer */
+                spin_lock(&chip->reg_lock);
+		snd_azf3328_ctrl_outb(chip, IDX_IO_TIMER_VALUE + 3, 0x07);
+		spin_unlock(&chip->reg_lock);
+		snd_azf3328_dbgcodec("azt3328: timer IRQ\n");
+	}
+
+	if (status & (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_I2S_OUT))
+		snd_azf3328_codec_interrupt(chip, status);
+
+	if (status & IRQ_GAMEPORT)
+		snd_azf3328_gameport_interrupt(chip);
+
+	/* MPU401 has less critical IRQ requirements
+	 * than timer and playback/recording, right? */
+	if (status & IRQ_MPU401) {
+		snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+
+		/* hmm, do we have to ack the IRQ here somehow?
+		 * If so, then I don't know how yet... */
+		snd_azf3328_dbgcodec("azt3328: MPU401 IRQ\n");
+	}
+	return IRQ_HANDLED;
+}
+
+/*****************************************************************/
+
+/* as long as we think we have identical snd_pcm_hardware parameters
+   for playback, capture and i2s out, we can use the same physical struct
+   since the struct is simply being copied into a member.
+*/
+static const struct snd_pcm_hardware snd_azf3328_hardware =
+{
+	/* FIXME!! Correct? */
+	.info =			SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_MMAP_VALID,
+	.formats =		SNDRV_PCM_FMTBIT_S8 |
+				SNDRV_PCM_FMTBIT_U8 |
+				SNDRV_PCM_FMTBIT_S16_LE |
+				SNDRV_PCM_FMTBIT_U16_LE,
+	.rates =		SNDRV_PCM_RATE_5512 |
+				SNDRV_PCM_RATE_8000_48000 |
+				SNDRV_PCM_RATE_KNOT,
+	.rate_min =		AZF_FREQ_4000,
+	.rate_max =		AZF_FREQ_66200,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(64*1024),
+	.period_bytes_min =	1024,
+	.period_bytes_max =	(32*1024),
+	/* We simply have two DMA areas (instead of a list of descriptors
+	   such as other cards); I believe that this is a fixed hardware
+	   attribute and there isn't much driver magic to be done to expand it.
+	   Thus indicate that we have at least and at most 2 periods. */
+	.periods_min =		2,
+	.periods_max =		2,
+	/* FIXME: maybe that card actually has a FIFO?
+	 * Hmm, it seems newer revisions do have one, but we still don't know
+	 * its size... */
+	.fifo_size =		0,
+};
+
+
+static unsigned int snd_azf3328_fixed_rates[] = {
+	AZF_FREQ_4000,
+	AZF_FREQ_4800,
+	AZF_FREQ_5512,
+	AZF_FREQ_6620,
+	AZF_FREQ_8000,
+	AZF_FREQ_9600,
+	AZF_FREQ_11025,
+	AZF_FREQ_13240,
+	AZF_FREQ_16000,
+	AZF_FREQ_22050,
+	AZF_FREQ_32000,
+	AZF_FREQ_44100,
+	AZF_FREQ_48000,
+	AZF_FREQ_66200
+};
+
+static struct snd_pcm_hw_constraint_list snd_azf3328_hw_constraints_rates = {
+	.count = ARRAY_SIZE(snd_azf3328_fixed_rates),
+	.list = snd_azf3328_fixed_rates,
+	.mask = 0,
+};
+
+/*****************************************************************/
+
+static int
+snd_azf3328_pcm_open(struct snd_pcm_substream *substream,
+		     enum snd_azf3328_codec_type codec_type
+)
+{
+	struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	snd_azf3328_dbgcallenter();
+	chip->codecs[codec_type].substream = substream;
+
+	/* same parameters for all our codecs - at least we think so... */
+	runtime->hw = snd_azf3328_hardware;
+
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				   &snd_azf3328_hw_constraints_rates);
+	snd_azf3328_dbgcallleave();
+	return 0;
+}
+
+static int
+snd_azf3328_playback_open(struct snd_pcm_substream *substream)
+{
+	return snd_azf3328_pcm_open(substream, AZF_CODEC_PLAYBACK);
+}
+
+static int
+snd_azf3328_capture_open(struct snd_pcm_substream *substream)
+{
+	return snd_azf3328_pcm_open(substream, AZF_CODEC_CAPTURE);
+}
+
+static int
+snd_azf3328_i2s_out_open(struct snd_pcm_substream *substream)
+{
+	return snd_azf3328_pcm_open(substream, AZF_CODEC_I2S_OUT);
+}
+
+static int
+snd_azf3328_pcm_close(struct snd_pcm_substream *substream,
+		      enum snd_azf3328_codec_type codec_type
+)
+{
+	struct snd_azf3328 *chip = snd_pcm_substream_chip(substream);
+
+	snd_azf3328_dbgcallenter();
+	chip->codecs[codec_type].substream = NULL;
+	snd_azf3328_dbgcallleave();
+	return 0;
+}
+
+static int
+snd_azf3328_playback_close(struct snd_pcm_substream *substream)
+{
+	return snd_azf3328_pcm_close(substream, AZF_CODEC_PLAYBACK);
+}
+
+static int
+snd_azf3328_capture_close(struct snd_pcm_substream *substream)
+{
+	return snd_azf3328_pcm_close(substream, AZF_CODEC_CAPTURE);
+}
+
+static int
+snd_azf3328_i2s_out_close(struct snd_pcm_substream *substream)
+{
+	return snd_azf3328_pcm_close(substream, AZF_CODEC_I2S_OUT);
+}
+
+/******************************************************************/
+
+static struct snd_pcm_ops snd_azf3328_playback_ops = {
+	.open =		snd_azf3328_playback_open,
+	.close =	snd_azf3328_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_azf3328_hw_params,
+	.hw_free =	snd_azf3328_hw_free,
+	.prepare =	snd_azf3328_codec_prepare,
+	.trigger =	snd_azf3328_codec_playback_trigger,
+	.pointer =	snd_azf3328_codec_playback_pointer
+};
+
+static struct snd_pcm_ops snd_azf3328_capture_ops = {
+	.open =		snd_azf3328_capture_open,
+	.close =	snd_azf3328_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_azf3328_hw_params,
+	.hw_free =	snd_azf3328_hw_free,
+	.prepare =	snd_azf3328_codec_prepare,
+	.trigger =	snd_azf3328_codec_capture_trigger,
+	.pointer =	snd_azf3328_codec_capture_pointer
+};
+
+static struct snd_pcm_ops snd_azf3328_i2s_out_ops = {
+	.open =		snd_azf3328_i2s_out_open,
+	.close =	snd_azf3328_i2s_out_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_azf3328_hw_params,
+	.hw_free =	snd_azf3328_hw_free,
+	.prepare =	snd_azf3328_codec_prepare,
+	.trigger =	snd_azf3328_codec_i2s_out_trigger,
+	.pointer =	snd_azf3328_codec_i2s_out_pointer
+};
+
+static int __devinit
+snd_azf3328_pcm(struct snd_azf3328 *chip)
+{
+enum { AZF_PCMDEV_STD, AZF_PCMDEV_I2S_OUT, NUM_AZF_PCMDEVS }; /* pcm devices */
+
+	struct snd_pcm *pcm;
+	int err;
+
+	snd_azf3328_dbgcallenter();
+
+	err = snd_pcm_new(chip->card, "AZF3328 DSP", AZF_PCMDEV_STD,
+								1, 1, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+						&snd_azf3328_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+						&snd_azf3328_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, chip->card->shortname);
+	/* same pcm object for playback/capture (see snd_pcm_new() above) */
+	chip->pcm[AZF_CODEC_PLAYBACK] = pcm;
+	chip->pcm[AZF_CODEC_CAPTURE] = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						snd_dma_pci_data(chip->pci),
+							64*1024, 64*1024);
+
+	err = snd_pcm_new(chip->card, "AZF3328 I2S OUT", AZF_PCMDEV_I2S_OUT,
+								1, 0, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+						&snd_azf3328_i2s_out_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, chip->card->shortname);
+	chip->pcm[AZF_CODEC_I2S_OUT] = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						snd_dma_pci_data(chip->pci),
+							64*1024, 64*1024);
+
+	snd_azf3328_dbgcallleave();
+	return 0;
+}
+
+/******************************************************************/
+
+/*** NOTE: the physical timer resolution actually is 1024000 ticks per second
+ *** (probably derived from main crystal via a divider of 24),
+ *** but announcing those attributes to user-space would make programs
+ *** configure the timer to a 1 tick value, resulting in an absolutely fatal
+ *** timer IRQ storm.
+ *** Thus I chose to announce a down-scaled virtual timer to the outside and
+ *** calculate real timer countdown values internally.
+ *** (the scale factor can be set via module parameter "seqtimer_scaling").
+ ***/
+
+static int
+snd_azf3328_timer_start(struct snd_timer *timer)
+{
+	struct snd_azf3328 *chip;
+	unsigned long flags;
+	unsigned int delay;
+
+	snd_azf3328_dbgcallenter();
+	chip = snd_timer_chip(timer);
+	delay = ((timer->sticks * seqtimer_scaling) - 1) & TIMER_VALUE_MASK;
+	if (delay < 49) {
+		/* uhoh, that's not good, since user-space won't know about
+		 * this timing tweak
+		 * (we need to do it to avoid a lockup, though) */
+
+		snd_azf3328_dbgtimer("delay was too low (%d)!\n", delay);
+		delay = 49; /* minimum time is 49 ticks */
+	}
+	snd_azf3328_dbgtimer("setting timer countdown value %d, add COUNTDOWN|IRQ\n", delay);
+	delay |= TIMER_COUNTDOWN_ENABLE | TIMER_IRQ_ENABLE;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_azf3328_ctrl_outl(chip, IDX_IO_TIMER_VALUE, delay);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_azf3328_dbgcallleave();
+	return 0;
+}
+
+static int
+snd_azf3328_timer_stop(struct snd_timer *timer)
+{
+	struct snd_azf3328 *chip;
+	unsigned long flags;
+
+	snd_azf3328_dbgcallenter();
+	chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	/* disable timer countdown and interrupt */
+	/* Hmm, should we write TIMER_IRQ_ACK here?
+	   YES indeed, otherwise a rogue timer operation - which prompts
+	   ALSA(?) to call repeated stop() in vain, but NOT start() -
+	   will never end (value 0x03 is kept shown in control byte).
+	   Simply manually poking 0x04 _once_ immediately successfully stops
+	   the hardware/ALSA interrupt activity. */
+	snd_azf3328_ctrl_outb(chip, IDX_IO_TIMER_VALUE + 3, 0x04);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	snd_azf3328_dbgcallleave();
+	return 0;
+}
+
+
+static int
+snd_azf3328_timer_precise_resolution(struct snd_timer *timer,
+					       unsigned long *num, unsigned long *den)
+{
+	snd_azf3328_dbgcallenter();
+	*num = 1;
+	*den = 1024000 / seqtimer_scaling;
+	snd_azf3328_dbgcallleave();
+	return 0;
+}
+
+static struct snd_timer_hardware snd_azf3328_timer_hw = {
+	.flags = SNDRV_TIMER_HW_AUTO,
+	.resolution = 977, /* 1000000/1024000 = 0.9765625us */
+	.ticks = 1024000, /* max tick count, defined by the value register; actually it's not 1024000, but 1048576, but we don't care */
+	.start = snd_azf3328_timer_start,
+	.stop = snd_azf3328_timer_stop,
+	.precise_resolution = snd_azf3328_timer_precise_resolution,
+};
+
+static int __devinit
+snd_azf3328_timer(struct snd_azf3328 *chip, int device)
+{
+	struct snd_timer *timer = NULL;
+	struct snd_timer_id tid;
+	int err;
+
+	snd_azf3328_dbgcallenter();
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = chip->card->number;
+	tid.device = device;
+	tid.subdevice = 0;
+
+	snd_azf3328_timer_hw.resolution *= seqtimer_scaling;
+	snd_azf3328_timer_hw.ticks /= seqtimer_scaling;
+
+	err = snd_timer_new(chip->card, "AZF3328", &tid, &timer);
+	if (err < 0)
+		goto out;
+
+	strcpy(timer->name, "AZF3328 timer");
+	timer->private_data = chip;
+	timer->hw = snd_azf3328_timer_hw;
+
+	chip->timer = timer;
+
+	snd_azf3328_timer_stop(timer);
+
+	err = 0;
+
+out:
+	snd_azf3328_dbgcallleave();
+	return err;
+}
+
+/******************************************************************/
+
+static int
+snd_azf3328_free(struct snd_azf3328 *chip)
+{
+	if (chip->irq < 0)
+		goto __end_hw;
+
+	/* reset (close) mixer:
+	 * first mute master volume, then reset
+	 */
+	snd_azf3328_mixer_set_mute(chip, IDX_MIXER_PLAY_MASTER, 1);
+	snd_azf3328_mixer_outw(chip, IDX_MIXER_RESET, 0x0000);
+
+	snd_azf3328_timer_stop(chip->timer);
+	snd_azf3328_gameport_free(chip);
+
+	if (chip->irq >= 0)
+		synchronize_irq(chip->irq);
+__end_hw:
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+
+	kfree(chip);
+	return 0;
+}
+
+static int
+snd_azf3328_dev_free(struct snd_device *device)
+{
+	struct snd_azf3328 *chip = device->device_data;
+	return snd_azf3328_free(chip);
+}
+
+#if 0
+/* check whether a bit can be modified */
+static void
+snd_azf3328_test_bit(unsigned unsigned reg, int bit)
+{
+	unsigned char val, valoff, valon;
+
+	val = inb(reg);
+
+	outb(val & ~(1 << bit), reg);
+	valoff = inb(reg);
+
+	outb(val|(1 << bit), reg);
+	valon = inb(reg);
+
+	outb(val, reg);
+
+	printk(KERN_DEBUG "reg %04x bit %d: %02x %02x %02x\n",
+				reg, bit, val, valoff, valon
+	);
+}
+#endif
+
+static inline void
+snd_azf3328_debug_show_ports(const struct snd_azf3328 *chip)
+{
+#if DEBUG_MISC
+	u16 tmp;
+
+	snd_azf3328_dbgmisc(
+		"ctrl_io 0x%lx, game_io 0x%lx, mpu_io 0x%lx, "
+		"opl3_io 0x%lx, mixer_io 0x%lx, irq %d\n",
+		chip->ctrl_io, chip->game_io, chip->mpu_io,
+		chip->opl3_io, chip->mixer_io, chip->irq
+	);
+
+	snd_azf3328_dbgmisc("game %02x %02x %02x %02x %02x %02x\n",
+		snd_azf3328_game_inb(chip, 0),
+		snd_azf3328_game_inb(chip, 1),
+		snd_azf3328_game_inb(chip, 2),
+		snd_azf3328_game_inb(chip, 3),
+		snd_azf3328_game_inb(chip, 4),
+		snd_azf3328_game_inb(chip, 5)
+	);
+
+	for (tmp = 0; tmp < 0x07; tmp += 1)
+		snd_azf3328_dbgmisc("mpu_io 0x%04x\n", inb(chip->mpu_io + tmp));
+
+	for (tmp = 0; tmp <= 0x07; tmp += 1)
+		snd_azf3328_dbgmisc("0x%02x: game200 0x%04x, game208 0x%04x\n",
+			tmp, inb(0x200 + tmp), inb(0x208 + tmp));
+
+	for (tmp = 0; tmp <= 0x01; tmp += 1)
+		snd_azf3328_dbgmisc(
+			"0x%02x: mpu300 0x%04x, mpu310 0x%04x, mpu320 0x%04x, "
+			"mpu330 0x%04x opl388 0x%04x opl38c 0x%04x\n",
+				tmp,
+				inb(0x300 + tmp),
+				inb(0x310 + tmp),
+				inb(0x320 + tmp),
+				inb(0x330 + tmp),
+				inb(0x388 + tmp),
+				inb(0x38c + tmp)
+		);
+
+	for (tmp = 0; tmp < AZF_IO_SIZE_CTRL; tmp += 2)
+		snd_azf3328_dbgmisc("ctrl 0x%02x: 0x%04x\n",
+			tmp, snd_azf3328_ctrl_inw(chip, tmp)
+		);
+
+	for (tmp = 0; tmp < AZF_IO_SIZE_MIXER; tmp += 2)
+		snd_azf3328_dbgmisc("mixer 0x%02x: 0x%04x\n",
+			tmp, snd_azf3328_mixer_inw(chip, tmp)
+		);
+#endif /* DEBUG_MISC */
+}
+
+static int __devinit
+snd_azf3328_create(struct snd_card *card,
+		   struct pci_dev *pci,
+		   unsigned long device_type,
+		   struct snd_azf3328 **rchip)
+{
+	struct snd_azf3328 *chip;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =     snd_azf3328_dev_free,
+	};
+	u8 dma_init;
+	enum snd_azf3328_codec_type codec_type;
+
+	*rchip = NULL;
+
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		err = -ENOMEM;
+		goto out_err;
+	}
+	spin_lock_init(&chip->reg_lock);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	/* check if we can restrict PCI DMA transfers to 24 bits */
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(24)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(24)) < 0) {
+		snd_printk(KERN_ERR "architecture does not support "
+					"24bit PCI busmaster DMA\n"
+		);
+		err = -ENXIO;
+		goto out_err;
+	}
+
+	err = pci_request_regions(pci, "Aztech AZF3328");
+	if (err < 0)
+		goto out_err;
+
+	chip->ctrl_io  = pci_resource_start(pci, 0);
+	chip->game_io  = pci_resource_start(pci, 1);
+	chip->mpu_io   = pci_resource_start(pci, 2);
+	chip->opl3_io  = pci_resource_start(pci, 3);
+	chip->mixer_io = pci_resource_start(pci, 4);
+
+	chip->codecs[AZF_CODEC_PLAYBACK].io_base =
+				chip->ctrl_io + AZF_IO_OFFS_CODEC_PLAYBACK;
+	chip->codecs[AZF_CODEC_PLAYBACK].name = "PLAYBACK";
+	chip->codecs[AZF_CODEC_CAPTURE].io_base =
+				chip->ctrl_io + AZF_IO_OFFS_CODEC_CAPTURE;
+	chip->codecs[AZF_CODEC_CAPTURE].name = "CAPTURE";
+	chip->codecs[AZF_CODEC_I2S_OUT].io_base =
+				chip->ctrl_io + AZF_IO_OFFS_CODEC_I2S_OUT;
+	chip->codecs[AZF_CODEC_I2S_OUT].name = "I2S_OUT";
+
+	if (request_irq(pci->irq, snd_azf3328_interrupt,
+			IRQF_SHARED, card->shortname, chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		err = -EBUSY;
+		goto out_err;
+	}
+	chip->irq = pci->irq;
+	pci_set_master(pci);
+	synchronize_irq(chip->irq);
+
+	snd_azf3328_debug_show_ports(chip);
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0)
+		goto out_err;
+
+	/* create mixer interface & switches */
+	err = snd_azf3328_mixer_new(chip);
+	if (err < 0)
+		goto out_err;
+
+	/* standard codec init stuff */
+		/* default DMA init value */
+	dma_init = DMA_RUN_SOMETHING2|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE;
+
+	for (codec_type = AZF_CODEC_PLAYBACK;
+		codec_type <= AZF_CODEC_I2S_OUT; ++codec_type) {
+		struct snd_azf3328_codec_data *codec =
+			 &chip->codecs[codec_type];
+
+		/* shutdown codecs to save power */
+			/* have ...ctrl_codec_activity() act properly */
+		codec->running = 1;
+		snd_azf3328_ctrl_codec_activity(chip, codec_type, 0);
+
+		spin_lock_irq(&chip->reg_lock);
+		snd_azf3328_codec_outb(codec, IDX_IO_CODEC_DMA_FLAGS,
+						 dma_init);
+		spin_unlock_irq(&chip->reg_lock);
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rchip = chip;
+
+	err = 0;
+	goto out;
+
+out_err:
+	if (chip)
+		snd_azf3328_free(chip);
+	pci_disable_device(pci);
+
+out:
+	return err;
+}
+
+static int __devinit
+snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_azf3328 *chip;
+	struct snd_opl3 *opl3;
+	int err;
+
+	snd_azf3328_dbgcallenter();
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	strcpy(card->driver, "AZF3328");
+	strcpy(card->shortname, "Aztech AZF3328 (PCI168)");
+
+	err = snd_azf3328_create(card, pci, pci_id->driver_data, &chip);
+	if (err < 0)
+		goto out_err;
+
+	card->private_data = chip;
+
+	/* chose to use MPU401_HW_AZT2320 ID instead of MPU401_HW_MPU401,
+	   since our hardware ought to be similar, thus use same ID. */
+	err = snd_mpu401_uart_new(
+		card, 0,
+		MPU401_HW_AZT2320, chip->mpu_io, MPU401_INFO_INTEGRATED,
+		pci->irq, 0, &chip->rmidi
+	);
+	if (err < 0) {
+		snd_printk(KERN_ERR "azf3328: no MPU-401 device at 0x%lx?\n",
+				chip->mpu_io
+		);
+		goto out_err;
+	}
+
+	err = snd_azf3328_timer(chip, 0);
+	if (err < 0)
+		goto out_err;
+
+	err = snd_azf3328_pcm(chip);
+	if (err < 0)
+		goto out_err;
+
+	if (snd_opl3_create(card, chip->opl3_io, chip->opl3_io+2,
+			    OPL3_HW_AUTO, 1, &opl3) < 0) {
+		snd_printk(KERN_ERR "azf3328: no OPL3 device at 0x%lx-0x%lx?\n",
+			   chip->opl3_io, chip->opl3_io+2
+		);
+	} else {
+		/* need to use IDs 1, 2 since ID 0 is snd_azf3328_timer above */
+		err = snd_opl3_timer_new(opl3, 1, 2);
+		if (err < 0)
+			goto out_err;
+		err = snd_opl3_hwdep_new(opl3, 0, 1, NULL);
+		if (err < 0)
+			goto out_err;
+	}
+
+	opl3->private_data = chip;
+
+	sprintf(card->longname, "%s at 0x%lx, irq %i",
+		card->shortname, chip->ctrl_io, chip->irq);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto out_err;
+
+#ifdef MODULE
+	printk(KERN_INFO
+"azt3328: Sound driver for Aztech AZF3328-based soundcards such as PCI168.\n"
+"azt3328: Hardware was completely undocumented, unfortunately.\n"
+"azt3328: Feel free to contact andi AT lisas.de for bug reports etc.!\n"
+"azt3328: User-scalable sequencer timer set to %dHz (1024000Hz / %d).\n",
+	1024000 / seqtimer_scaling, seqtimer_scaling);
+#endif
+
+	snd_azf3328_gameport(chip, dev);
+
+	pci_set_drvdata(pci, card);
+	dev++;
+
+	err = 0;
+	goto out;
+
+out_err:
+	snd_printk(KERN_ERR "azf3328: something failed, exiting\n");
+	snd_card_free(card);
+
+out:
+	snd_azf3328_dbgcallleave();
+	return err;
+}
+
+static void __devexit
+snd_azf3328_remove(struct pci_dev *pci)
+{
+	snd_azf3328_dbgcallenter();
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+	snd_azf3328_dbgcallleave();
+}
+
+#ifdef CONFIG_PM
+static inline void
+snd_azf3328_suspend_regs(unsigned long io_addr, unsigned count, u32 *saved_regs)
+{
+	unsigned reg;
+
+	for (reg = 0; reg < count; ++reg) {
+		*saved_regs = inl(io_addr);
+		snd_azf3328_dbgpm("suspend: io 0x%04lx: 0x%08x\n",
+			io_addr, *saved_regs);
+		++saved_regs;
+		io_addr += sizeof(*saved_regs);
+	}
+}
+
+static int
+snd_azf3328_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_azf3328 *chip = card->private_data;
+	u16 *saved_regs_ctrl_u16;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+	snd_pcm_suspend_all(chip->pcm[AZF_CODEC_PLAYBACK]);
+	snd_pcm_suspend_all(chip->pcm[AZF_CODEC_I2S_OUT]);
+
+	snd_azf3328_suspend_regs(chip->mixer_io,
+		ARRAY_SIZE(chip->saved_regs_mixer), chip->saved_regs_mixer);
+
+	/* make sure to disable master volume etc. to prevent looping sound */
+	snd_azf3328_mixer_set_mute(chip, IDX_MIXER_PLAY_MASTER, 1);
+	snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1);
+
+	snd_azf3328_suspend_regs(chip->ctrl_io,
+		ARRAY_SIZE(chip->saved_regs_ctrl), chip->saved_regs_ctrl);
+
+	/* manually store the one currently relevant write-only reg, too */
+	saved_regs_ctrl_u16 = (u16 *)chip->saved_regs_ctrl;
+	saved_regs_ctrl_u16[IDX_IO_6AH / 2] = chip->shadow_reg_ctrl_6AH;
+
+	snd_azf3328_suspend_regs(chip->game_io,
+		ARRAY_SIZE(chip->saved_regs_game), chip->saved_regs_game);
+	snd_azf3328_suspend_regs(chip->mpu_io,
+		ARRAY_SIZE(chip->saved_regs_mpu), chip->saved_regs_mpu);
+	snd_azf3328_suspend_regs(chip->opl3_io,
+		ARRAY_SIZE(chip->saved_regs_opl3), chip->saved_regs_opl3);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static inline void
+snd_azf3328_resume_regs(const u32 *saved_regs,
+			unsigned long io_addr,
+			unsigned count
+)
+{
+	unsigned reg;
+
+	for (reg = 0; reg < count; ++reg) {
+		outl(*saved_regs, io_addr);
+		snd_azf3328_dbgpm("resume: io 0x%04lx: 0x%08x --> 0x%08x\n",
+			io_addr, *saved_regs, inl(io_addr));
+		++saved_regs;
+		io_addr += sizeof(*saved_regs);
+	}
+}
+
+static int
+snd_azf3328_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	const struct snd_azf3328 *chip = card->private_data;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "azt3328: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_azf3328_resume_regs(chip->saved_regs_game, chip->game_io,
+					ARRAY_SIZE(chip->saved_regs_game));
+	snd_azf3328_resume_regs(chip->saved_regs_mpu, chip->mpu_io,
+					ARRAY_SIZE(chip->saved_regs_mpu));
+	snd_azf3328_resume_regs(chip->saved_regs_opl3, chip->opl3_io,
+					ARRAY_SIZE(chip->saved_regs_opl3));
+
+	snd_azf3328_resume_regs(chip->saved_regs_mixer, chip->mixer_io,
+					ARRAY_SIZE(chip->saved_regs_mixer));
+
+	/* unfortunately with 32bit transfers, IDX_MIXER_PLAY_MASTER (0x02)
+	   and IDX_MIXER_RESET (offset 0x00) get touched at the same time,
+	   resulting in a mixer reset condition persisting until _after_
+	   master vol was restored. Thus master vol needs an extra restore. */
+	outw(((u16 *)chip->saved_regs_mixer)[1], chip->mixer_io + 2);
+
+	snd_azf3328_resume_regs(chip->saved_regs_ctrl, chip->ctrl_io,
+					ARRAY_SIZE(chip->saved_regs_ctrl));
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+
+static struct pci_driver driver = {
+	.name = "AZF3328",
+	.id_table = snd_azf3328_ids,
+	.probe = snd_azf3328_probe,
+	.remove = __devexit_p(snd_azf3328_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_azf3328_suspend,
+	.resume = snd_azf3328_resume,
+#endif
+};
+
+static int __init
+alsa_card_azf3328_init(void)
+{
+	int err;
+	snd_azf3328_dbgcallenter();
+	err = pci_register_driver(&driver);
+	snd_azf3328_dbgcallleave();
+	return err;
+}
+
+static void __exit
+alsa_card_azf3328_exit(void)
+{
+	snd_azf3328_dbgcallenter();
+	pci_unregister_driver(&driver);
+	snd_azf3328_dbgcallleave();
+}
+
+module_init(alsa_card_azf3328_init)
+module_exit(alsa_card_azf3328_exit)
diff --git a/linux/sound/pci/azt3328.h b/linux/sound/pci/azt3328.h
new file mode 100644
index 0000000..6f46b97
--- /dev/null
+++ b/linux/sound/pci/azt3328.h
@@ -0,0 +1,342 @@
+#ifndef __SOUND_AZT3328_H
+#define __SOUND_AZT3328_H
+
+/* "PU" == "power-up value", as tested on PCI168 PCI rev. 10
+ * "WRITE_ONLY"  == register does not indicate actual bit values */
+
+/*** main I/O area port indices ***/
+/* (only 0x70 of 0x80 bytes saved/restored by Windows driver) */
+#define AZF_IO_SIZE_CTRL	0x80
+#define AZF_IO_SIZE_CTRL_PM	0x70
+
+/* the driver initialisation suggests a layout of 4 areas
+ * within the main card control I/O:
+ * from 0x00 (playback codec), from 0x20 (recording codec)
+ * and from 0x40 (most certainly I2S out codec).
+ * And another area from 0x60 to 0x6f (DirectX timer, IRQ management,
+ * power management etc.???). */
+
+#define AZF_IO_OFFS_CODEC_PLAYBACK	0x00
+#define AZF_IO_OFFS_CODEC_CAPTURE	0x20
+#define AZF_IO_OFFS_CODEC_I2S_OUT	0x40
+
+#define IDX_IO_CODEC_DMA_FLAGS       0x00 /* PU:0x0000 */
+     /* able to reactivate output after output muting due to 8/16bit
+      * output change, just like 0x0002.
+      * 0x0001 is the only bit that's able to start the DMA counter */
+  #define DMA_RESUME			0x0001 /* paused if cleared? */
+     /* 0x0002 *temporarily* set during DMA stopping. hmm
+      * both 0x0002 and 0x0004 set in playback setup. */
+     /* able to reactivate output after output muting due to 8/16bit
+      * output change, just like 0x0001. */
+  #define DMA_RUN_SOMETHING1		0x0002 /* \ alternated (toggled) */
+     /* 0x0004: NOT able to reactivate output */
+  #define DMA_RUN_SOMETHING2		0x0004 /* / bits */
+  #define SOMETHING_ALMOST_ALWAYS_SET	0x0008 /* ???; can be modified */
+  #define DMA_EPILOGUE_SOMETHING	0x0010
+  #define DMA_SOMETHING_ELSE		0x0020 /* ??? */
+  #define SOMETHING_UNMODIFIABLE	0xffc0 /* unused? not modifiable */
+#define IDX_IO_CODEC_IRQTYPE     0x02 /* PU:0x0001 */
+  /* write back to flags in case flags are set, in order to ACK IRQ in handler
+   * (bit 1 of port 0x64 indicates interrupt for one of these three types)
+   * sometimes in this case it just writes 0xffff to globally ACK all IRQs
+   * settings written are not reflected when reading back, though.
+   * seems to be IRQ, too (frequently used: port |= 0x07 !), but who knows? */
+  #define IRQ_SOMETHING			0x0001 /* something & ACK */
+  #define IRQ_FINISHED_DMABUF_1		0x0002 /* 1st dmabuf finished & ACK */
+  #define IRQ_FINISHED_DMABUF_2		0x0004 /* 2nd dmabuf finished & ACK */
+  #define IRQMASK_SOME_STATUS_1		0x0008 /* \ related bits */
+  #define IRQMASK_SOME_STATUS_2		0x0010 /* / (checked together in loop) */
+  #define IRQMASK_UNMODIFIABLE		0xffe0 /* unused? not modifiable */
+  /* start address of 1st DMA transfer area, PU:0x00000000 */
+#define IDX_IO_CODEC_DMA_START_1 0x04
+  /* start address of 2nd DMA transfer area, PU:0x00000000 */
+#define IDX_IO_CODEC_DMA_START_2 0x08
+  /* both lengths of DMA transfer areas, PU:0x00000000
+     length1: offset 0x0c, length2: offset 0x0e */
+#define IDX_IO_CODEC_DMA_LENGTHS 0x0c
+#define IDX_IO_CODEC_DMA_CURRPOS 0x10 /* current DMA position, PU:0x00000000 */
+  /* offset within current DMA transfer area, PU:0x0000 */
+#define IDX_IO_CODEC_DMA_CURROFS 0x14
+#define IDX_IO_CODEC_SOUNDFORMAT 0x16 /* PU:0x0010 */
+  /* all unspecified bits can't be modified */
+  #define SOUNDFORMAT_FREQUENCY_MASK	0x000f
+  #define SOUNDFORMAT_XTAL1		0x00
+  #define SOUNDFORMAT_XTAL2		0x01
+    /* all _SUSPECTED_ values are not used by Windows drivers, so we don't
+     * have any hard facts, only rough measurements.
+     * All we know is that the crystal used on the board has 24.576MHz,
+     * like many soundcards (which results in the frequencies below when
+     * using certain divider values selected by the values below) */
+    #define SOUNDFORMAT_FREQ_SUSPECTED_4000	0x0c | SOUNDFORMAT_XTAL1
+    #define SOUNDFORMAT_FREQ_SUSPECTED_4800	0x0a | SOUNDFORMAT_XTAL1
+    #define SOUNDFORMAT_FREQ_5510		0x0c | SOUNDFORMAT_XTAL2
+    #define SOUNDFORMAT_FREQ_6620		0x0a | SOUNDFORMAT_XTAL2
+    #define SOUNDFORMAT_FREQ_8000		0x00 | SOUNDFORMAT_XTAL1 /* also 0x0e | SOUNDFORMAT_XTAL1? */
+    #define SOUNDFORMAT_FREQ_9600		0x08 | SOUNDFORMAT_XTAL1
+    #define SOUNDFORMAT_FREQ_11025		0x00 | SOUNDFORMAT_XTAL2 /* also 0x0e | SOUNDFORMAT_XTAL2? */
+    #define SOUNDFORMAT_FREQ_SUSPECTED_13240	0x08 | SOUNDFORMAT_XTAL2 /* seems to be 6620 *2 */
+    #define SOUNDFORMAT_FREQ_16000		0x02 | SOUNDFORMAT_XTAL1
+    #define SOUNDFORMAT_FREQ_22050		0x02 | SOUNDFORMAT_XTAL2
+    #define SOUNDFORMAT_FREQ_32000		0x04 | SOUNDFORMAT_XTAL1
+    #define SOUNDFORMAT_FREQ_44100		0x04 | SOUNDFORMAT_XTAL2
+    #define SOUNDFORMAT_FREQ_48000		0x06 | SOUNDFORMAT_XTAL1
+    #define SOUNDFORMAT_FREQ_SUSPECTED_66200	0x06 | SOUNDFORMAT_XTAL2 /* 66200 (13240 * 5); 64000 may have been nicer :-\ */
+  #define SOUNDFORMAT_FLAG_16BIT	0x0010
+  #define SOUNDFORMAT_FLAG_2CHANNELS	0x0020
+
+
+/* define frequency helpers, for maximum value safety */
+enum azf_freq_t {
+#define AZF_FREQ(rate) AZF_FREQ_##rate = rate
+  AZF_FREQ(4000),
+  AZF_FREQ(4800),
+  AZF_FREQ(5512),
+  AZF_FREQ(6620),
+  AZF_FREQ(8000),
+  AZF_FREQ(9600),
+  AZF_FREQ(11025),
+  AZF_FREQ(13240),
+  AZF_FREQ(16000),
+  AZF_FREQ(22050),
+  AZF_FREQ(32000),
+  AZF_FREQ(44100),
+  AZF_FREQ(48000),
+  AZF_FREQ(66200),
+#undef AZF_FREQ
+};
+
+/** DirectX timer, main interrupt area (FIXME: and something else?) **/ 
+#define IDX_IO_TIMER_VALUE	0x60 /* found this timer area by pure luck :-) */
+  /* timer countdown value; triggers IRQ when timer is finished */
+  #define TIMER_VALUE_MASK		0x000fffffUL
+  /* activate timer countdown */
+  #define TIMER_COUNTDOWN_ENABLE	0x01000000UL
+  /* trigger timer IRQ on zero transition */
+  #define TIMER_IRQ_ENABLE		0x02000000UL
+  /* being set in IRQ handler in case port 0x00 (hmm, not port 0x64!?!?)
+   * had 0x0020 set upon IRQ handler */
+  #define TIMER_IRQ_ACK			0x04000000UL
+#define IDX_IO_IRQSTATUS        0x64
+  /* some IRQ bit in here might also be used to signal a power-management timer
+   * timeout, to request shutdown of the chip (e.g. AD1815JS has such a thing).
+   * OPL3 hardware contains several timers which confusingly in most cases
+   * are NOT routed to an IRQ, but some designs (e.g. LM4560) DO support that,
+   * so I wouldn't be surprised at all to discover that AZF3328
+   * supports that thing as well... */
+
+  #define IRQ_PLAYBACK	0x0001
+  #define IRQ_RECORDING	0x0002
+  #define IRQ_I2S_OUT	0x0004 /* this IS I2S, right!? (untested) */
+  #define IRQ_GAMEPORT	0x0008 /* Interrupt of Digital(ly) Enhanced Game Port */
+  #define IRQ_MPU401	0x0010
+  #define IRQ_TIMER	0x0020 /* DirectX timer */
+  #define IRQ_UNKNOWN2	0x0040 /* probably unused, or possibly OPL3 timer? */
+  #define IRQ_UNKNOWN3	0x0080 /* probably unused, or possibly OPL3 timer? */
+#define IDX_IO_66H		0x66    /* writing 0xffff returns 0x0000 */
+  /* this is set to e.g. 0x3ff or 0x300, and writable;
+   * maybe some buffer limit, but I couldn't find out more, PU:0x00ff: */
+#define IDX_IO_SOME_VALUE	0x68
+  #define IO_68_RANDOM_TOGGLE1	0x0100	/* toggles randomly */
+  #define IO_68_RANDOM_TOGGLE2	0x0200	/* toggles randomly */
+  /* umm, nope, behaviour of these bits changes depending on what we wrote
+   * to 0x6b!!
+   * And they change upon playback/stop, too:
+   * Writing a value to 0x68 will display this exact value during playback,
+   * too but when stopped it can fall back to a rather different
+   * seemingly random value). Hmm, possibly this is a register which
+   * has a remote shadow which needs proper device supply which only exists
+   * in case playback is active? Or is this driver-induced?
+   */
+
+/* this WORD can be set to have bits 0x0028 activated (FIXME: correct??);
+ * actually inhibits PCM playback!!! maybe power management??: */
+#define IDX_IO_6AH		0x6A /* WRITE_ONLY! */
+  /* bit 5: enabling this will activate permanent counting of bytes 2/3
+   * at gameport I/O (0xb402/3) (equal values each) and cause
+   * gameport legacy I/O at 0x0200 to be _DISABLED_!
+   * Is this Digital Enhanced Game Port Enable??? Or maybe it's Testmode
+   * for Enhanced Digital Gameport (see 4D Wave DX card): */
+  #define IO_6A_SOMETHING1_GAMEPORT	0x0020
+  /* bit 8; sure, this _pauses_ playback (later resumes at same spot!),
+   * but what the heck is this really about??: */
+  #define IO_6A_PAUSE_PLAYBACK_BIT8	0x0100
+  /* bit 9; sure, this _pauses_ playback (later resumes at same spot!),
+   * but what the heck is this really about??: */
+  #define IO_6A_PAUSE_PLAYBACK_BIT9	0x0200
+	/* BIT8 and BIT9 are _NOT_ able to affect OPL3 MIDI playback,
+	 * thus it suggests influence on PCM only!!
+	 * However OTOH there seems to be no bit anywhere around here
+	 * which is able to disable OPL3... */
+  /* bit 10: enabling this actually changes values at legacy gameport
+   * I/O address (0x200); is this enabling of the Digital Enhanced Game Port???
+   * Or maybe this simply switches off the NE558 circuit, since enabling this
+   * still lets us evaluate button states, but not axis states */
+  #define IO_6A_SOMETHING2_GAMEPORT      0x0400
+	/* writing 0x0300: causes quite some crackling during
+	 * PC activity such as switching windows (PCI traffic??
+	 * --> FIFO/timing settings???) */
+	/* writing 0x0100 plus/or 0x0200 inhibits playback */
+	/* since the Windows .INF file has Flag_Enable_JoyStick and
+	 * Flag_Enable_SB_DOS_Emulation directly together, it stands to reason
+	 * that some other bit in this same register might be responsible
+	 * for SB DOS Emulation activation (note that the file did NOT define
+	 * a switch for OPL3!) */
+#define IDX_IO_6CH		0x6C	/* unknown; fully read-writable */
+#define IDX_IO_6EH		0x6E
+	/* writing 0xffff returns 0x83fe (or 0x03fe only).
+	 * writing 0x83 (and only 0x83!!) to 0x6f will cause 0x6c to switch
+	 * from 0000 to ffff. */
+
+/* further I/O indices not saved/restored and not readable after writing,
+ * so probably not used */
+
+
+/*** Gameport area port indices ***/
+/* (only 0x06 of 0x08 bytes saved/restored by Windows driver) */ 
+#define AZF_IO_SIZE_GAME		0x08
+#define AZF_IO_SIZE_GAME_PM		0x06
+
+enum {
+	AZF_GAME_LEGACY_IO_PORT = 0x200
+};
+
+#define IDX_GAME_LEGACY_COMPATIBLE	0x00
+	/* in some operation mode, writing anything to this port
+	 * triggers an interrupt:
+	 * yup, that's in case IDX_GAME_01H has one of the
+	 * axis measurement bits enabled
+	 * (and of course one needs to have GAME_HWCFG_IRQ_ENABLE, too) */
+
+#define IDX_GAME_AXES_CONFIG            0x01
+	/* NOTE: layout of this register awfully similar (read: "identical??")
+	 * to AD1815JS.pdf (p.29) */
+
+  /* enables axis 1 (X axis) measurement: */
+  #define GAME_AXES_ENABLE_1		0x01
+  /* enables axis 2 (Y axis) measurement: */
+  #define GAME_AXES_ENABLE_2		0x02
+  /* enables axis 3 (X axis) measurement: */
+  #define GAME_AXES_ENABLE_3		0x04
+  /* enables axis 4 (Y axis) measurement: */
+  #define GAME_AXES_ENABLE_4		0x08
+  /* selects the current axis to read the measured value of
+   * (at IDX_GAME_AXIS_VALUE):
+   * 00 = axis 1, 01 = axis 2, 10 = axis 3, 11 = axis 4: */
+  #define GAME_AXES_READ_MASK		0x30
+  /* enable to have the latch continuously accept ADC values
+   * (and continuously cause interrupts in case interrupts are enabled);
+   * AD1815JS.pdf says it's ~16ms interval there: */
+  #define GAME_AXES_LATCH_ENABLE	0x40
+  /* joystick data (measured axes) ready for reading: */
+  #define GAME_AXES_SAMPLING_READY	0x80
+
+  /* NOTE: other card specs (SiS960 and others!) state that the
+   * game position latches should be frozen when reading and be freed
+   * (== reset?) after reading!!!
+   * Freezing most likely means disabling 0x40 (GAME_AXES_LATCH_ENABLE),
+   *  but how to free the value? */
+  /* An internet search for "gameport latch ADC" should provide some insight
+   * into how to program such a gameport system. */
+
+  /* writing 0xf0 to 01H once reset both counters to 0, in some special mode!?
+   * yup, in case 6AH 0x20 is not enabled
+   * (and 0x40 is sufficient, 0xf0 is not needed) */
+
+#define IDX_GAME_AXIS_VALUE	0x02
+	/* R: value of currently configured axis (word value!);
+	 * W: trigger axis measurement */
+
+#define IDX_GAME_HWCONFIG	0x04
+	/* note: bits 4 to 7 are never set (== 0) when reading!
+	 * --> reserved bits? */
+  /* enables IRQ notification upon axes measurement ready: */
+  #define GAME_HWCFG_IRQ_ENABLE			0x01
+  /* these bits choose a different frequency for the
+   *  internal ADC counter increment.
+   * hmm, seems to be a combo of bits:
+   * 00 --> standard frequency
+   * 10 --> 1/2
+   * 01 --> 1/20
+   * 11 --> 1/200: */
+  #define GAME_HWCFG_ADC_COUNTER_FREQ_MASK	0x06
+
+  /* FIXME: these values might be reversed... */
+  #define GAME_HWCFG_ADC_COUNTER_FREQ_STD	0
+  #define GAME_HWCFG_ADC_COUNTER_FREQ_1_2	1
+  #define GAME_HWCFG_ADC_COUNTER_FREQ_1_20	2
+  #define GAME_HWCFG_ADC_COUNTER_FREQ_1_200	3
+
+  /* enable gameport legacy I/O address (0x200)
+   * I was unable to locate any configurability for a different address: */
+  #define GAME_HWCFG_LEGACY_ADDRESS_ENABLE	0x08
+
+/*** MPU401 ***/
+#define AZF_IO_SIZE_MPU		0x04
+#define AZF_IO_SIZE_MPU_PM	0x04
+
+/*** OPL3 synth ***/
+/* (only 0x06 of 0x08 bytes saved/restored by Windows driver) */
+#define AZF_IO_SIZE_OPL3	0x08
+#define AZF_IO_SIZE_OPL3_PM	0x06
+/* hmm, given that a standard OPL3 has 4 registers only,
+ * there might be some enhanced functionality lurking at the end
+ * (especially since register 0x04 has a "non-empty" value 0xfe) */
+
+/*** mixer I/O area port indices ***/
+/* (only 0x22 of 0x40 bytes saved/restored by Windows driver)
+ * UNFORTUNATELY azf3328 is NOT truly AC97 compliant: see main file intro */
+#define AZF_IO_SIZE_MIXER	0x40
+#define AZF_IO_SIZE_MIXER_PM	0x22
+
+  #define MIXER_VOLUME_RIGHT_MASK	0x001f
+  #define MIXER_VOLUME_LEFT_MASK	0x1f00
+  #define MIXER_MUTE_MASK		0x8000
+#define IDX_MIXER_RESET		0x00 /* does NOT seem to have AC97 ID bits */
+#define IDX_MIXER_PLAY_MASTER   0x02
+#define IDX_MIXER_MODEMOUT      0x04
+#define IDX_MIXER_BASSTREBLE    0x06
+  #define MIXER_BASSTREBLE_TREBLE_VOLUME_MASK	0x000e
+  #define MIXER_BASSTREBLE_BASS_VOLUME_MASK	0x0e00
+#define IDX_MIXER_PCBEEP        0x08
+#define IDX_MIXER_MODEMIN       0x0a
+#define IDX_MIXER_MIC           0x0c
+  #define MIXER_MIC_MICGAIN_20DB_ENHANCEMENT_MASK	0x0040
+#define IDX_MIXER_LINEIN        0x0e
+#define IDX_MIXER_CDAUDIO       0x10
+#define IDX_MIXER_VIDEO         0x12
+#define IDX_MIXER_AUX           0x14
+#define IDX_MIXER_WAVEOUT       0x16
+#define IDX_MIXER_FMSYNTH       0x18
+#define IDX_MIXER_REC_SELECT    0x1a
+  #define MIXER_REC_SELECT_MIC		0x00
+  #define MIXER_REC_SELECT_CD		0x01
+  #define MIXER_REC_SELECT_VIDEO	0x02
+  #define MIXER_REC_SELECT_AUX		0x03
+  #define MIXER_REC_SELECT_LINEIN	0x04
+  #define MIXER_REC_SELECT_MIXSTEREO	0x05
+  #define MIXER_REC_SELECT_MIXMONO	0x06
+  #define MIXER_REC_SELECT_MONOIN	0x07
+#define IDX_MIXER_REC_VOLUME    0x1c
+#define IDX_MIXER_ADVCTL1       0x1e
+  /* unlisted bits are unmodifiable */
+  #define MIXER_ADVCTL1_3DWIDTH_MASK	0x000e
+  #define MIXER_ADVCTL1_HIFI3D_MASK	0x0300 /* yup, this is missing the high bit that official AC97 contains, plus it doesn't have linear bit value range behaviour but instead acts weirdly (possibly we're dealing with two *different* 3D settings here??) */
+#define IDX_MIXER_ADVCTL2       0x20 /* subset of AC97_GENERAL_PURPOSE reg! */
+  /* unlisted bits are unmodifiable */
+  #define MIXER_ADVCTL2_LPBK		0x0080 /* Loopback mode -- Win driver: "WaveOut3DBypass"? mutes WaveOut at LineOut */
+  #define MIXER_ADVCTL2_MS		0x0100 /* Mic Select 0=Mic1, 1=Mic2 -- Win driver: "ModemOutSelect"?? */
+  #define MIXER_ADVCTL2_MIX		0x0200 /* Mono output select 0=Mix, 1=Mic; Win driver: "MonoSelectSource"?? */
+  #define MIXER_ADVCTL2_3D		0x2000 /* 3D Enhancement 1=on */
+  #define MIXER_ADVCTL2_POP		0x8000 /* Pcm Out Path, 0=pre 3D, 1=post 3D */
+  
+#define IDX_MIXER_SOMETHING30H	0x30 /* used, but unknown??? */
+
+/* driver internal flags */
+#define SET_CHAN_LEFT	1
+#define SET_CHAN_RIGHT	2
+
+/* helper macro to align I/O port ranges to 32bit I/O width */
+#define AZF_ALIGN(x) (((x) + 3) & (~3))
+
+#endif /* __SOUND_AZT3328_H  */
diff --git a/linux/sound/pci/bt87x.c b/linux/sound/pci/bt87x.c
new file mode 100644
index 0000000..37e1b5d
--- /dev/null
+++ b/linux/sound/pci/bt87x.c
@@ -0,0 +1,993 @@
+/*
+ * bt87x.c - Brooktree Bt878/Bt879 driver for ALSA
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * based on btaudio.c by Gerd Knorr <kraxel@bytesex.org>
+ *
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/bitops.h>
+#include <asm/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("Brooktree Bt87x audio driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Brooktree,Bt878},"
+		"{Brooktree,Bt879}}");
+
+static int index[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -2}; /* Exclude the first card */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static int digital_rate[SNDRV_CARDS];	/* digital input rate */
+static int load_all;	/* allow to load the non-whitelisted cards */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Bt87x soundcard");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Bt87x soundcard");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Bt87x soundcard");
+module_param_array(digital_rate, int, NULL, 0444);
+MODULE_PARM_DESC(digital_rate, "Digital input rate for Bt87x soundcard");
+module_param(load_all, bool, 0444);
+MODULE_PARM_DESC(load_all, "Allow to load the non-whitelisted cards");
+
+
+/* register offsets */
+#define REG_INT_STAT		0x100	/* interrupt status */
+#define REG_INT_MASK		0x104	/* interrupt mask */
+#define REG_GPIO_DMA_CTL	0x10c	/* audio control */
+#define REG_PACKET_LEN		0x110	/* audio packet lengths */
+#define REG_RISC_STRT_ADD	0x114	/* RISC program start address */
+#define REG_RISC_COUNT		0x120	/* RISC program counter */
+
+/* interrupt bits */
+#define INT_OFLOW	(1 <<  3)	/* audio A/D overflow */
+#define INT_RISCI	(1 << 11)	/* RISC instruction IRQ bit set */
+#define INT_FBUS	(1 << 12)	/* FIFO overrun due to bus access latency */
+#define INT_FTRGT	(1 << 13)	/* FIFO overrun due to target latency */
+#define INT_FDSR	(1 << 14)	/* FIFO data stream resynchronization */
+#define INT_PPERR	(1 << 15)	/* PCI parity error */
+#define INT_RIPERR	(1 << 16)	/* RISC instruction parity error */
+#define INT_PABORT	(1 << 17)	/* PCI master or target abort */
+#define INT_OCERR	(1 << 18)	/* invalid opcode */
+#define INT_SCERR	(1 << 19)	/* sync counter overflow */
+#define INT_RISC_EN	(1 << 27)	/* DMA controller running */
+#define INT_RISCS_SHIFT	      28	/* RISC status bits */
+
+/* audio control bits */
+#define CTL_FIFO_ENABLE		(1 <<  0)	/* enable audio data FIFO */
+#define CTL_RISC_ENABLE		(1 <<  1)	/* enable audio DMA controller */
+#define CTL_PKTP_4		(0 <<  2)	/* packet mode FIFO trigger point - 4 DWORDs */
+#define CTL_PKTP_8		(1 <<  2)	/* 8 DWORDs */
+#define CTL_PKTP_16		(2 <<  2)	/* 16 DWORDs */
+#define CTL_ACAP_EN		(1 <<  4)	/* enable audio capture */
+#define CTL_DA_APP		(1 <<  5)	/* GPIO input */
+#define CTL_DA_IOM_AFE		(0 <<  6)	/* audio A/D input */
+#define CTL_DA_IOM_DA		(1 <<  6)	/* digital audio input */
+#define CTL_DA_SDR_SHIFT	       8	/* DDF first stage decimation rate */
+#define CTL_DA_SDR_MASK		(0xf<< 8)
+#define CTL_DA_LMT		(1 << 12)	/* limit audio data values */
+#define CTL_DA_ES2		(1 << 13)	/* enable DDF stage 2 */
+#define CTL_DA_SBR		(1 << 14)	/* samples rounded to 8 bits */
+#define CTL_DA_DPM		(1 << 15)	/* data packet mode */
+#define CTL_DA_LRD_SHIFT	      16	/* ALRCK delay */
+#define CTL_DA_MLB		(1 << 21)	/* MSB/LSB format */
+#define CTL_DA_LRI		(1 << 22)	/* left/right indication */
+#define CTL_DA_SCE		(1 << 23)	/* sample clock edge */
+#define CTL_A_SEL_STV		(0 << 24)	/* TV tuner audio input */
+#define CTL_A_SEL_SFM		(1 << 24)	/* FM audio input */
+#define CTL_A_SEL_SML		(2 << 24)	/* mic/line audio input */
+#define CTL_A_SEL_SMXC		(3 << 24)	/* MUX bypass */
+#define CTL_A_SEL_SHIFT		      24
+#define CTL_A_SEL_MASK		(3 << 24)
+#define CTL_A_PWRDN		(1 << 26)	/* analog audio power-down */
+#define CTL_A_G2X		(1 << 27)	/* audio gain boost */
+#define CTL_A_GAIN_SHIFT	      28	/* audio input gain */
+#define CTL_A_GAIN_MASK		(0xf<<28)
+
+/* RISC instruction opcodes */
+#define RISC_WRITE	(0x1 << 28)	/* write FIFO data to memory at address */
+#define RISC_WRITEC	(0x5 << 28)	/* write FIFO data to memory at current address */
+#define RISC_SKIP	(0x2 << 28)	/* skip FIFO data */
+#define RISC_JUMP	(0x7 << 28)	/* jump to address */
+#define RISC_SYNC	(0x8 << 28)	/* synchronize with FIFO */
+
+/* RISC instruction bits */
+#define RISC_BYTES_ENABLE	(0xf << 12)	/* byte enable bits */
+#define RISC_RESYNC		(  1 << 15)	/* disable FDSR errors */
+#define RISC_SET_STATUS_SHIFT	        16	/* set status bits */
+#define RISC_RESET_STATUS_SHIFT	        20	/* clear status bits */
+#define RISC_IRQ		(  1 << 24)	/* interrupt */
+#define RISC_EOL		(  1 << 26)	/* end of line */
+#define RISC_SOL		(  1 << 27)	/* start of line */
+
+/* SYNC status bits values */
+#define RISC_SYNC_FM1	0x6
+#define RISC_SYNC_VRO	0xc
+
+#define ANALOG_CLOCK 1792000
+#ifdef CONFIG_SND_BT87X_OVERCLOCK
+#define CLOCK_DIV_MIN 1
+#else
+#define CLOCK_DIV_MIN 4
+#endif
+#define CLOCK_DIV_MAX 15
+
+#define ERROR_INTERRUPTS (INT_FBUS | INT_FTRGT | INT_PPERR | \
+			  INT_RIPERR | INT_PABORT | INT_OCERR)
+#define MY_INTERRUPTS (INT_RISCI | ERROR_INTERRUPTS)
+
+/* SYNC, one WRITE per line, one extra WRITE per page boundary, SYNC, JUMP */
+#define MAX_RISC_SIZE ((1 + 255 + (PAGE_ALIGN(255 * 4092) / PAGE_SIZE - 1) + 1 + 1) * 8)
+
+/* Cards with configuration information */
+enum snd_bt87x_boardid {
+	SND_BT87X_BOARD_UNKNOWN,
+	SND_BT87X_BOARD_GENERIC,	/* both an & dig interfaces, 32kHz */
+	SND_BT87X_BOARD_ANALOG,		/* board with no external A/D */
+	SND_BT87X_BOARD_OSPREY2x0,
+	SND_BT87X_BOARD_OSPREY440,
+	SND_BT87X_BOARD_AVPHONE98,
+};
+
+/* Card configuration */
+struct snd_bt87x_board {
+	int dig_rate;		/* Digital input sampling rate */
+	u32 digital_fmt;	/* Register settings for digital input */
+	unsigned no_analog:1;	/* No analog input */
+	unsigned no_digital:1;	/* No digital input */
+};
+
+static __devinitdata struct snd_bt87x_board snd_bt87x_boards[] = {
+	[SND_BT87X_BOARD_UNKNOWN] = {
+		.dig_rate = 32000, /* just a guess */
+	},
+	[SND_BT87X_BOARD_GENERIC] = {
+		.dig_rate = 32000,
+	},
+	[SND_BT87X_BOARD_ANALOG] = {
+		.no_digital = 1,
+	},
+	[SND_BT87X_BOARD_OSPREY2x0] = {
+		.dig_rate = 44100,
+		.digital_fmt = CTL_DA_LRI | (1 << CTL_DA_LRD_SHIFT),
+	},
+	[SND_BT87X_BOARD_OSPREY440] = {
+		.dig_rate = 32000,
+		.digital_fmt = CTL_DA_LRI | (1 << CTL_DA_LRD_SHIFT),
+		.no_analog = 1,
+	},
+	[SND_BT87X_BOARD_AVPHONE98] = {
+		.dig_rate = 48000,
+	},
+};
+
+struct snd_bt87x {
+	struct snd_card *card;
+	struct pci_dev *pci;
+	struct snd_bt87x_board board;
+
+	void __iomem *mmio;
+	int irq;
+
+	spinlock_t reg_lock;
+	unsigned long opened;
+	struct snd_pcm_substream *substream;
+
+	struct snd_dma_buffer dma_risc;
+	unsigned int line_bytes;
+	unsigned int lines;
+
+	u32 reg_control;
+	u32 interrupt_mask;
+
+	int current_line;
+
+	int pci_parity_errors;
+};
+
+enum { DEVICE_DIGITAL, DEVICE_ANALOG };
+
+static inline u32 snd_bt87x_readl(struct snd_bt87x *chip, u32 reg)
+{
+	return readl(chip->mmio + reg);
+}
+
+static inline void snd_bt87x_writel(struct snd_bt87x *chip, u32 reg, u32 value)
+{
+	writel(value, chip->mmio + reg);
+}
+
+static int snd_bt87x_create_risc(struct snd_bt87x *chip, struct snd_pcm_substream *substream,
+			       	 unsigned int periods, unsigned int period_bytes)
+{
+	unsigned int i, offset;
+	u32 *risc;
+
+	if (chip->dma_risc.area == NULL) {
+		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+					PAGE_ALIGN(MAX_RISC_SIZE), &chip->dma_risc) < 0)
+			return -ENOMEM;
+	}
+	risc = (u32 *)chip->dma_risc.area;
+	offset = 0;
+	*risc++ = cpu_to_le32(RISC_SYNC | RISC_SYNC_FM1);
+	*risc++ = cpu_to_le32(0);
+	for (i = 0; i < periods; ++i) {
+		u32 rest;
+
+		rest = period_bytes;
+		do {
+			u32 cmd, len;
+			unsigned int addr;
+
+			len = PAGE_SIZE - (offset % PAGE_SIZE);
+			if (len > rest)
+				len = rest;
+			cmd = RISC_WRITE | len;
+			if (rest == period_bytes) {
+				u32 block = i * 16 / periods;
+				cmd |= RISC_SOL;
+				cmd |= block << RISC_SET_STATUS_SHIFT;
+				cmd |= (~block & 0xf) << RISC_RESET_STATUS_SHIFT;
+			}
+			if (len == rest)
+				cmd |= RISC_EOL | RISC_IRQ;
+			*risc++ = cpu_to_le32(cmd);
+			addr = snd_pcm_sgbuf_get_addr(substream, offset);
+			*risc++ = cpu_to_le32(addr);
+			offset += len;
+			rest -= len;
+		} while (rest > 0);
+	}
+	*risc++ = cpu_to_le32(RISC_SYNC | RISC_SYNC_VRO);
+	*risc++ = cpu_to_le32(0);
+	*risc++ = cpu_to_le32(RISC_JUMP);
+	*risc++ = cpu_to_le32(chip->dma_risc.addr);
+	chip->line_bytes = period_bytes;
+	chip->lines = periods;
+	return 0;
+}
+
+static void snd_bt87x_free_risc(struct snd_bt87x *chip)
+{
+	if (chip->dma_risc.area) {
+		snd_dma_free_pages(&chip->dma_risc);
+		chip->dma_risc.area = NULL;
+	}
+}
+
+static void snd_bt87x_pci_error(struct snd_bt87x *chip, unsigned int status)
+{
+	u16 pci_status;
+
+	pci_read_config_word(chip->pci, PCI_STATUS, &pci_status);
+	pci_status &= PCI_STATUS_PARITY | PCI_STATUS_SIG_TARGET_ABORT |
+		PCI_STATUS_REC_TARGET_ABORT | PCI_STATUS_REC_MASTER_ABORT |
+		PCI_STATUS_SIG_SYSTEM_ERROR | PCI_STATUS_DETECTED_PARITY;
+	pci_write_config_word(chip->pci, PCI_STATUS, pci_status);
+	if (pci_status != PCI_STATUS_DETECTED_PARITY)
+		snd_printk(KERN_ERR "Aieee - PCI error! status %#08x, PCI status %#04x\n",
+			   status & ERROR_INTERRUPTS, pci_status);
+	else {
+		snd_printk(KERN_ERR "Aieee - PCI parity error detected!\n");
+		/* error 'handling' similar to aic7xxx_pci.c: */
+		chip->pci_parity_errors++;
+		if (chip->pci_parity_errors > 20) {
+			snd_printk(KERN_ERR "Too many PCI parity errors observed.\n");
+			snd_printk(KERN_ERR "Some device on this bus is generating bad parity.\n");
+			snd_printk(KERN_ERR "This is an error *observed by*, not *generated by*, this card.\n");
+			snd_printk(KERN_ERR "PCI parity error checking has been disabled.\n");
+			chip->interrupt_mask &= ~(INT_PPERR | INT_RIPERR);
+			snd_bt87x_writel(chip, REG_INT_MASK, chip->interrupt_mask);
+		}
+	}
+}
+
+static irqreturn_t snd_bt87x_interrupt(int irq, void *dev_id)
+{
+	struct snd_bt87x *chip = dev_id;
+	unsigned int status, irq_status;
+
+	status = snd_bt87x_readl(chip, REG_INT_STAT);
+	irq_status = status & chip->interrupt_mask;
+	if (!irq_status)
+		return IRQ_NONE;
+	snd_bt87x_writel(chip, REG_INT_STAT, irq_status);
+
+	if (irq_status & ERROR_INTERRUPTS) {
+		if (irq_status & (INT_FBUS | INT_FTRGT))
+			snd_printk(KERN_WARNING "FIFO overrun, status %#08x\n", status);
+		if (irq_status & INT_OCERR)
+			snd_printk(KERN_ERR "internal RISC error, status %#08x\n", status);
+		if (irq_status & (INT_PPERR | INT_RIPERR | INT_PABORT))
+			snd_bt87x_pci_error(chip, irq_status);
+	}
+	if ((irq_status & INT_RISCI) && (chip->reg_control & CTL_ACAP_EN)) {
+		int current_block, irq_block;
+
+		/* assume that exactly one line has been recorded */
+		chip->current_line = (chip->current_line + 1) % chip->lines;
+		/* but check if some interrupts have been skipped */
+		current_block = chip->current_line * 16 / chip->lines;
+		irq_block = status >> INT_RISCS_SHIFT;
+		if (current_block != irq_block)
+			chip->current_line = (irq_block * chip->lines + 15) / 16;
+
+		snd_pcm_period_elapsed(chip->substream);
+	}
+	return IRQ_HANDLED;
+}
+
+static struct snd_pcm_hardware snd_bt87x_digital_hw = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BATCH,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rates = 0, /* set at runtime */
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = 255 * 4092,
+	.period_bytes_min = 32,
+	.period_bytes_max = 4092,
+	.periods_min = 2,
+	.periods_max = 255,
+};
+
+static struct snd_pcm_hardware snd_bt87x_analog_hw = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BATCH,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+	.rates = SNDRV_PCM_RATE_KNOT,
+	.rate_min = ANALOG_CLOCK / CLOCK_DIV_MAX,
+	.rate_max = ANALOG_CLOCK / CLOCK_DIV_MIN,
+	.channels_min = 1,
+	.channels_max = 1,
+	.buffer_bytes_max = 255 * 4092,
+	.period_bytes_min = 32,
+	.period_bytes_max = 4092,
+	.periods_min = 2,
+	.periods_max = 255,
+};
+
+static int snd_bt87x_set_digital_hw(struct snd_bt87x *chip, struct snd_pcm_runtime *runtime)
+{
+	chip->reg_control |= CTL_DA_IOM_DA | CTL_A_PWRDN;
+	runtime->hw = snd_bt87x_digital_hw;
+	runtime->hw.rates = snd_pcm_rate_to_rate_bit(chip->board.dig_rate);
+	runtime->hw.rate_min = chip->board.dig_rate;
+	runtime->hw.rate_max = chip->board.dig_rate;
+	return 0;
+}
+
+static int snd_bt87x_set_analog_hw(struct snd_bt87x *chip, struct snd_pcm_runtime *runtime)
+{
+	static struct snd_ratnum analog_clock = {
+		.num = ANALOG_CLOCK,
+		.den_min = CLOCK_DIV_MIN,
+		.den_max = CLOCK_DIV_MAX,
+		.den_step = 1
+	};
+	static struct snd_pcm_hw_constraint_ratnums constraint_rates = {
+		.nrats = 1,
+		.rats = &analog_clock
+	};
+
+	chip->reg_control &= ~(CTL_DA_IOM_DA | CTL_A_PWRDN);
+	runtime->hw = snd_bt87x_analog_hw;
+	return snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+					     &constraint_rates);
+}
+
+static int snd_bt87x_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	if (test_and_set_bit(0, &chip->opened))
+		return -EBUSY;
+
+	if (substream->pcm->device == DEVICE_DIGITAL)
+		err = snd_bt87x_set_digital_hw(chip, runtime);
+	else
+		err = snd_bt87x_set_analog_hw(chip, runtime);
+	if (err < 0)
+		goto _error;
+
+	err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0)
+		goto _error;
+
+	chip->substream = substream;
+	return 0;
+
+_error:
+	clear_bit(0, &chip->opened);
+	smp_mb__after_clear_bit();
+	return err;
+}
+
+static int snd_bt87x_close(struct snd_pcm_substream *substream)
+{
+	struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&chip->reg_lock);
+	chip->reg_control |= CTL_A_PWRDN;
+	snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
+	spin_unlock_irq(&chip->reg_lock);
+
+	chip->substream = NULL;
+	clear_bit(0, &chip->opened);
+	smp_mb__after_clear_bit();
+	return 0;
+}
+
+static int snd_bt87x_hw_params(struct snd_pcm_substream *substream,
+			       struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	return snd_bt87x_create_risc(chip, substream,
+				     params_periods(hw_params),
+				     params_period_bytes(hw_params));
+}
+
+static int snd_bt87x_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
+
+	snd_bt87x_free_risc(chip);
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_bt87x_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int decimation;
+
+	spin_lock_irq(&chip->reg_lock);
+	chip->reg_control &= ~(CTL_DA_SDR_MASK | CTL_DA_SBR);
+	decimation = (ANALOG_CLOCK + runtime->rate / 4) / runtime->rate;
+	chip->reg_control |= decimation << CTL_DA_SDR_SHIFT;
+	if (runtime->format == SNDRV_PCM_FORMAT_S8)
+		chip->reg_control |= CTL_DA_SBR;
+	snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_bt87x_start(struct snd_bt87x *chip)
+{
+	spin_lock(&chip->reg_lock);
+	chip->current_line = 0;
+	chip->reg_control |= CTL_FIFO_ENABLE | CTL_RISC_ENABLE | CTL_ACAP_EN;
+	snd_bt87x_writel(chip, REG_RISC_STRT_ADD, chip->dma_risc.addr);
+	snd_bt87x_writel(chip, REG_PACKET_LEN,
+			 chip->line_bytes | (chip->lines << 16));
+	snd_bt87x_writel(chip, REG_INT_MASK, chip->interrupt_mask);
+	snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
+	spin_unlock(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_bt87x_stop(struct snd_bt87x *chip)
+{
+	spin_lock(&chip->reg_lock);
+	chip->reg_control &= ~(CTL_FIFO_ENABLE | CTL_RISC_ENABLE | CTL_ACAP_EN);
+	snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
+	snd_bt87x_writel(chip, REG_INT_MASK, 0);
+	snd_bt87x_writel(chip, REG_INT_STAT, MY_INTERRUPTS);
+	spin_unlock(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_bt87x_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		return snd_bt87x_start(chip);
+	case SNDRV_PCM_TRIGGER_STOP:
+		return snd_bt87x_stop(chip);
+	default:
+		return -EINVAL;
+	}
+}
+
+static snd_pcm_uframes_t snd_bt87x_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_bt87x *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	return (snd_pcm_uframes_t)bytes_to_frames(runtime, chip->current_line * chip->line_bytes);
+}
+
+static struct snd_pcm_ops snd_bt87x_pcm_ops = {
+	.open = snd_bt87x_pcm_open,
+	.close = snd_bt87x_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_bt87x_hw_params,
+	.hw_free = snd_bt87x_hw_free,
+	.prepare = snd_bt87x_prepare,
+	.trigger = snd_bt87x_trigger,
+	.pointer = snd_bt87x_pointer,
+	.page = snd_pcm_sgbuf_ops_page,
+};
+
+static int snd_bt87x_capture_volume_info(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 1;
+	info->value.integer.min = 0;
+	info->value.integer.max = 15;
+	return 0;
+}
+
+static int snd_bt87x_capture_volume_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *value)
+{
+	struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
+
+	value->value.integer.value[0] = (chip->reg_control & CTL_A_GAIN_MASK) >> CTL_A_GAIN_SHIFT;
+	return 0;
+}
+
+static int snd_bt87x_capture_volume_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *value)
+{
+	struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
+	u32 old_control;
+	int changed;
+
+	spin_lock_irq(&chip->reg_lock);
+	old_control = chip->reg_control;
+	chip->reg_control = (chip->reg_control & ~CTL_A_GAIN_MASK)
+		| (value->value.integer.value[0] << CTL_A_GAIN_SHIFT);
+	snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
+	changed = old_control != chip->reg_control;
+	spin_unlock_irq(&chip->reg_lock);
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_bt87x_capture_volume = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Volume",
+	.info = snd_bt87x_capture_volume_info,
+	.get = snd_bt87x_capture_volume_get,
+	.put = snd_bt87x_capture_volume_put,
+};
+
+#define snd_bt87x_capture_boost_info	snd_ctl_boolean_mono_info
+
+static int snd_bt87x_capture_boost_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *value)
+{
+	struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
+
+	value->value.integer.value[0] = !! (chip->reg_control & CTL_A_G2X);
+	return 0;
+}
+
+static int snd_bt87x_capture_boost_put(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *value)
+{
+	struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
+	u32 old_control;
+	int changed;
+
+	spin_lock_irq(&chip->reg_lock);
+	old_control = chip->reg_control;
+	chip->reg_control = (chip->reg_control & ~CTL_A_G2X)
+		| (value->value.integer.value[0] ? CTL_A_G2X : 0);
+	snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
+	changed = chip->reg_control != old_control;
+	spin_unlock_irq(&chip->reg_lock);
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_bt87x_capture_boost = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Boost",
+	.info = snd_bt87x_capture_boost_info,
+	.get = snd_bt87x_capture_boost_get,
+	.put = snd_bt87x_capture_boost_put,
+};
+
+static int snd_bt87x_capture_source_info(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_info *info)
+{
+	static char *texts[3] = {"TV Tuner", "FM", "Mic/Line"};
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 3;
+	if (info->value.enumerated.item > 2)
+		info->value.enumerated.item = 2;
+	strcpy(info->value.enumerated.name, texts[info->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_bt87x_capture_source_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *value)
+{
+	struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
+
+	value->value.enumerated.item[0] = (chip->reg_control & CTL_A_SEL_MASK) >> CTL_A_SEL_SHIFT;
+	return 0;
+}
+
+static int snd_bt87x_capture_source_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *value)
+{
+	struct snd_bt87x *chip = snd_kcontrol_chip(kcontrol);
+	u32 old_control;
+	int changed;
+
+	spin_lock_irq(&chip->reg_lock);
+	old_control = chip->reg_control;
+	chip->reg_control = (chip->reg_control & ~CTL_A_SEL_MASK)
+		| (value->value.enumerated.item[0] << CTL_A_SEL_SHIFT);
+	snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
+	changed = chip->reg_control != old_control;
+	spin_unlock_irq(&chip->reg_lock);
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_bt87x_capture_source = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_bt87x_capture_source_info,
+	.get = snd_bt87x_capture_source_get,
+	.put = snd_bt87x_capture_source_put,
+};
+
+static int snd_bt87x_free(struct snd_bt87x *chip)
+{
+	if (chip->mmio)
+		snd_bt87x_stop(chip);
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	if (chip->mmio)
+		iounmap(chip->mmio);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_bt87x_dev_free(struct snd_device *device)
+{
+	struct snd_bt87x *chip = device->device_data;
+	return snd_bt87x_free(chip);
+}
+
+static int __devinit snd_bt87x_pcm(struct snd_bt87x *chip, int device, char *name)
+{
+	int err;
+	struct snd_pcm *pcm;
+
+	err = snd_pcm_new(chip->card, name, device, 0, 1, &pcm);
+	if (err < 0)
+		return err;
+	pcm->private_data = chip;
+	strcpy(pcm->name, name);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_bt87x_pcm_ops);
+	return snd_pcm_lib_preallocate_pages_for_all(pcm,
+						     SNDRV_DMA_TYPE_DEV_SG,
+						     snd_dma_pci_data(chip->pci),
+							128 * 1024,
+							ALIGN(255 * 4092, 1024));
+}
+
+static int __devinit snd_bt87x_create(struct snd_card *card,
+				      struct pci_dev *pci,
+				      struct snd_bt87x **rchip)
+{
+	struct snd_bt87x *chip;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_bt87x_dev_free
+	};
+
+	*rchip = NULL;
+
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	spin_lock_init(&chip->reg_lock);
+
+	if ((err = pci_request_regions(pci, "Bt87x audio")) < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+	chip->mmio = pci_ioremap_bar(pci, 0);
+	if (!chip->mmio) {
+		snd_printk(KERN_ERR "cannot remap io memory\n");
+		err = -ENOMEM;
+		goto fail;
+	}
+
+	chip->reg_control = CTL_A_PWRDN | CTL_DA_ES2 |
+			    CTL_PKTP_16 | (15 << CTL_DA_SDR_SHIFT);
+	chip->interrupt_mask = MY_INTERRUPTS;
+	snd_bt87x_writel(chip, REG_GPIO_DMA_CTL, chip->reg_control);
+	snd_bt87x_writel(chip, REG_INT_MASK, 0);
+	snd_bt87x_writel(chip, REG_INT_STAT, MY_INTERRUPTS);
+
+	err = request_irq(pci->irq, snd_bt87x_interrupt, IRQF_SHARED,
+			  "Bt87x audio", chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR "cannot grab irq %d\n", pci->irq);
+		goto fail;
+	}
+	chip->irq = pci->irq;
+	pci_set_master(pci);
+	synchronize_irq(chip->irq);
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0)
+		goto fail;
+
+	snd_card_set_dev(card, &pci->dev);
+	*rchip = chip;
+	return 0;
+
+fail:
+	snd_bt87x_free(chip);
+	return err;
+}
+
+#define BT_DEVICE(chip, subvend, subdev, id) \
+	{ .vendor = PCI_VENDOR_ID_BROOKTREE, \
+	  .device = chip, \
+	  .subvendor = subvend, .subdevice = subdev, \
+	  .driver_data = SND_BT87X_BOARD_ ## id }
+/* driver_data is the card id for that device */
+
+static DEFINE_PCI_DEVICE_TABLE(snd_bt87x_ids) = {
+	/* Hauppauge WinTV series */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x0070, 0x13eb, GENERIC),
+	/* Hauppauge WinTV series */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_879, 0x0070, 0x13eb, GENERIC),
+	/* Viewcast Osprey 200 */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x0070, 0xff01, OSPREY2x0),
+	/* Viewcast Osprey 440 (rate is configurable via gpio) */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x0070, 0xff07, OSPREY440),
+	/* ATI TV-Wonder */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x1002, 0x0001, GENERIC),
+	/* Leadtek Winfast tv 2000xp delux */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x107d, 0x6606, GENERIC),
+	/* Pinnacle PCTV */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x11bd, 0x0012, GENERIC),
+	/* Voodoo TV 200 */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x121a, 0x3000, GENERIC),
+	/* Askey Computer Corp. MagicTView'99 */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x144f, 0x3000, GENERIC),
+	/* AVerMedia Studio No. 103, 203, ...? */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x1461, 0x0003, AVPHONE98),
+	/* Prolink PixelView PV-M4900 */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0x1554, 0x4011, GENERIC),
+	/* Pinnacle  Studio PCTV rave */
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, 0xbd11, 0x1200, GENERIC),
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, snd_bt87x_ids);
+
+/* cards known not to have audio
+ * (DVB cards use the audio function to transfer MPEG data) */
+static struct {
+	unsigned short subvendor, subdevice;
+} blacklist[] __devinitdata = {
+	{0x0071, 0x0101}, /* Nebula Electronics DigiTV */
+	{0x11bd, 0x001c}, /* Pinnacle PCTV Sat */
+	{0x11bd, 0x0026}, /* Pinnacle PCTV SAT CI */
+	{0x1461, 0x0761}, /* AVermedia AverTV DVB-T */
+	{0x1461, 0x0771}, /* AVermedia DVB-T 771 */
+	{0x1822, 0x0001}, /* Twinhan VisionPlus DVB-T */
+	{0x18ac, 0xd500}, /* DVICO FusionHDTV 5 Lite */
+	{0x18ac, 0xdb10}, /* DVICO FusionHDTV DVB-T Lite */
+	{0x18ac, 0xdb11}, /* Ultraview DVB-T Lite */
+	{0x270f, 0xfc00}, /* Chaintech Digitop DST-1000 DVB-S */
+	{0x7063, 0x2000}, /* pcHDTV HD-2000 TV */
+};
+
+static struct pci_driver driver;
+
+/* return the id of the card, or a negative value if it's blacklisted */
+static int __devinit snd_bt87x_detect_card(struct pci_dev *pci)
+{
+	int i;
+	const struct pci_device_id *supported;
+
+	supported = pci_match_id(snd_bt87x_ids, pci);
+	if (supported && supported->driver_data > 0)
+		return supported->driver_data;
+
+	for (i = 0; i < ARRAY_SIZE(blacklist); ++i)
+		if (blacklist[i].subvendor == pci->subsystem_vendor &&
+		    blacklist[i].subdevice == pci->subsystem_device) {
+			snd_printdd(KERN_INFO "card %#04x-%#04x:%#04x has no audio\n",
+				    pci->device, pci->subsystem_vendor, pci->subsystem_device);
+			return -EBUSY;
+		}
+
+	snd_printk(KERN_INFO "unknown card %#04x-%#04x:%#04x\n",
+		   pci->device, pci->subsystem_vendor, pci->subsystem_device);
+	snd_printk(KERN_DEBUG "please mail id, board name, and, "
+		   "if it works, the correct digital_rate option to "
+		   "<alsa-devel@alsa-project.org>\n");
+	return SND_BT87X_BOARD_UNKNOWN;
+}
+
+static int __devinit snd_bt87x_probe(struct pci_dev *pci,
+				     const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_bt87x *chip;
+	int err;
+	enum snd_bt87x_boardid boardid;
+
+	if (!pci_id->driver_data) {
+		err = snd_bt87x_detect_card(pci);
+		if (err < 0)
+			return -ENODEV;
+		boardid = err;
+	} else
+		boardid = pci_id->driver_data;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		++dev;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	err = snd_bt87x_create(card, pci, &chip);
+	if (err < 0)
+		goto _error;
+
+	memcpy(&chip->board, &snd_bt87x_boards[boardid], sizeof(chip->board));
+
+	if (!chip->board.no_digital) {
+		if (digital_rate[dev] > 0)
+			chip->board.dig_rate = digital_rate[dev];
+
+		chip->reg_control |= chip->board.digital_fmt;
+
+		err = snd_bt87x_pcm(chip, DEVICE_DIGITAL, "Bt87x Digital");
+		if (err < 0)
+			goto _error;
+	}
+	if (!chip->board.no_analog) {
+		err = snd_bt87x_pcm(chip, DEVICE_ANALOG, "Bt87x Analog");
+		if (err < 0)
+			goto _error;
+		err = snd_ctl_add(card, snd_ctl_new1(
+				  &snd_bt87x_capture_volume, chip));
+		if (err < 0)
+			goto _error;
+		err = snd_ctl_add(card, snd_ctl_new1(
+				  &snd_bt87x_capture_boost, chip));
+		if (err < 0)
+			goto _error;
+		err = snd_ctl_add(card, snd_ctl_new1(
+				  &snd_bt87x_capture_source, chip));
+		if (err < 0)
+			goto _error;
+	}
+	snd_printk(KERN_INFO "bt87x%d: Using board %d, %sanalog, %sdigital "
+		   "(rate %d Hz)\n", dev, boardid,
+		   chip->board.no_analog ? "no " : "",
+		   chip->board.no_digital ? "no " : "", chip->board.dig_rate);
+
+	strcpy(card->driver, "Bt87x");
+	sprintf(card->shortname, "Brooktree Bt%x", pci->device);
+	sprintf(card->longname, "%s at %#llx, irq %i",
+		card->shortname, (unsigned long long)pci_resource_start(pci, 0),
+		chip->irq);
+	strcpy(card->mixername, "Bt87x");
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto _error;
+
+	pci_set_drvdata(pci, card);
+	++dev;
+	return 0;
+
+_error:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit snd_bt87x_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+/* default entries for all Bt87x cards - it's not exported */
+/* driver_data is set to 0 to call detection */
+static DEFINE_PCI_DEVICE_TABLE(snd_bt87x_default_ids) = {
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_878, PCI_ANY_ID, PCI_ANY_ID, UNKNOWN),
+	BT_DEVICE(PCI_DEVICE_ID_BROOKTREE_879, PCI_ANY_ID, PCI_ANY_ID, UNKNOWN),
+	{ }
+};
+
+static struct pci_driver driver = {
+	.name = "Bt87x",
+	.id_table = snd_bt87x_ids,
+	.probe = snd_bt87x_probe,
+	.remove = __devexit_p(snd_bt87x_remove),
+};
+
+static int __init alsa_card_bt87x_init(void)
+{
+	if (load_all)
+		driver.id_table = snd_bt87x_default_ids;
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_bt87x_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_bt87x_init)
+module_exit(alsa_card_bt87x_exit)
diff --git a/linux/sound/pci/ca0106/Makefile b/linux/sound/pci/ca0106/Makefile
new file mode 100644
index 0000000..dcbae7b
--- /dev/null
+++ b/linux/sound/pci/ca0106/Makefile
@@ -0,0 +1,3 @@
+snd-ca0106-objs := ca0106_main.o ca0106_proc.o ca0106_mixer.o ca_midi.o
+
+obj-$(CONFIG_SND_CA0106) += snd-ca0106.o
diff --git a/linux/sound/pci/ca0106/ca0106.h b/linux/sound/pci/ca0106/ca0106.h
new file mode 100644
index 0000000..f19c110
--- /dev/null
+++ b/linux/sound/pci/ca0106/ca0106.h
@@ -0,0 +1,742 @@
+/*
+ *  Copyright (c) 2004 James Courtier-Dutton <James@superbug.demon.co.uk>
+ *  Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit
+ *  Version: 0.0.22
+ *
+ *  FEATURES currently supported:
+ *    See ca0106_main.c for features.
+ * 
+ *  Changelog:
+ *    Support interrupts per period.
+ *    Removed noise from Center/LFE channel when in Analog mode.
+ *    Rename and remove mixer controls.
+ *  0.0.6
+ *    Use separate card based DMA buffer for periods table list.
+ *  0.0.7
+ *    Change remove and rename ctrls into lists.
+ *  0.0.8
+ *    Try to fix capture sources.
+ *  0.0.9
+ *    Fix AC3 output.
+ *    Enable S32_LE format support.
+ *  0.0.10
+ *    Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".)
+ *  0.0.11
+ *    Add Model name recognition.
+ *  0.0.12
+ *    Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period.
+ *    Remove redundent "voice" handling.
+ *  0.0.13
+ *    Single trigger call for multi channels.
+ *  0.0.14
+ *    Set limits based on what the sound card hardware can do.
+ *    playback periods_min=2, periods_max=8
+ *    capture hw constraints require period_size = n * 64 bytes.
+ *    playback hw constraints require period_size = n * 64 bytes.
+ *  0.0.15
+ *    Separated ca0106.c into separate functional .c files.
+ *  0.0.16
+ *    Implement 192000 sample rate.
+ *  0.0.17
+ *    Add support for SB0410 and SB0413.
+ *  0.0.18
+ *    Modified Copyright message.
+ *  0.0.19
+ *    Added I2C and SPI registers. Filled in interrupt enable.
+ *  0.0.20
+ *    Added GPIO info for SB Live 24bit.
+ *  0.0.21
+ *   Implement support for Line-in capture on SB Live 24bit.
+ *  0.0.22
+ *    Add support for mute control on SB Live 24bit (cards w/ SPI DAC)
+ *
+ *
+ *  This code was initally based on code from ALSA's emu10k1x.c which is:
+ *  Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ *
+ *   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
+ *
+ */
+
+/************************************************************************************************/
+/* PCI function 0 registers, address = <val> + PCIBASE0						*/
+/************************************************************************************************/
+
+#define PTR			0x00		/* Indexed register set pointer register	*/
+						/* NOTE: The CHANNELNUM and ADDRESS words can	*/
+						/* be modified independently of each other.	*/
+						/* CNL[1:0], ADDR[27:16]                        */
+
+#define DATA			0x04		/* Indexed register set data register		*/
+						/* DATA[31:0]					*/
+
+#define IPR			0x08		/* Global interrupt pending register		*/
+						/* Clear pending interrupts by writing a 1 to	*/
+						/* the relevant bits and zero to the other bits	*/
+#define IPR_MIDI_RX_B		0x00020000	/* MIDI UART-B Receive buffer non-empty		*/
+#define IPR_MIDI_TX_B		0x00010000	/* MIDI UART-B Transmit buffer empty		*/
+#define IPR_SPDIF_IN_USER	0x00004000      /* SPDIF input user data has 16 more bits	*/
+#define IPR_SPDIF_OUT_USER	0x00002000      /* SPDIF output user data needs 16 more bits	*/
+#define IPR_SPDIF_OUT_FRAME	0x00001000      /* SPDIF frame about to start			*/
+#define IPR_SPI			0x00000800      /* SPI transaction completed			*/
+#define IPR_I2C_EEPROM		0x00000400      /* I2C EEPROM transaction completed		*/
+#define IPR_I2C_DAC		0x00000200      /* I2C DAC transaction completed		*/
+#define IPR_AI			0x00000100      /* Audio pending register changed. See PTR reg 0x76	*/
+#define IPR_GPI			0x00000080      /* General Purpose input changed		*/
+#define IPR_SRC_LOCKED          0x00000040      /* SRC lock status changed			*/
+#define IPR_SPDIF_STATUS        0x00000020      /* SPDIF status changed				*/
+#define IPR_TIMER2              0x00000010      /* 192000Hz Timer				*/
+#define IPR_TIMER1              0x00000008      /* 44100Hz Timer				*/
+#define IPR_MIDI_RX_A		0x00000004	/* MIDI UART-A Receive buffer non-empty		*/
+#define IPR_MIDI_TX_A		0x00000002	/* MIDI UART-A Transmit buffer empty		*/
+#define IPR_PCI			0x00000001	/* PCI Bus error				*/
+
+#define INTE			0x0c		/* Interrupt enable register			*/
+
+#define INTE_MIDI_RX_B		0x00020000	/* MIDI UART-B Receive buffer non-empty		*/
+#define INTE_MIDI_TX_B		0x00010000	/* MIDI UART-B Transmit buffer empty		*/
+#define INTE_SPDIF_IN_USER	0x00004000      /* SPDIF input user data has 16 more bits	*/
+#define INTE_SPDIF_OUT_USER	0x00002000      /* SPDIF output user data needs 16 more bits	*/
+#define INTE_SPDIF_OUT_FRAME	0x00001000      /* SPDIF frame about to start			*/
+#define INTE_SPI		0x00000800      /* SPI transaction completed			*/
+#define INTE_I2C_EEPROM		0x00000400      /* I2C EEPROM transaction completed		*/
+#define INTE_I2C_DAC		0x00000200      /* I2C DAC transaction completed		*/
+#define INTE_AI			0x00000100      /* Audio pending register changed. See PTR reg 0x75 */
+#define INTE_GPI		0x00000080      /* General Purpose input changed		*/
+#define INTE_SRC_LOCKED         0x00000040      /* SRC lock status changed			*/
+#define INTE_SPDIF_STATUS       0x00000020      /* SPDIF status changed				*/
+#define INTE_TIMER2             0x00000010      /* 192000Hz Timer				*/
+#define INTE_TIMER1             0x00000008      /* 44100Hz Timer				*/
+#define INTE_MIDI_RX_A		0x00000004	/* MIDI UART-A Receive buffer non-empty		*/
+#define INTE_MIDI_TX_A		0x00000002	/* MIDI UART-A Transmit buffer empty		*/
+#define INTE_PCI		0x00000001	/* PCI Bus error				*/
+
+#define UNKNOWN10		0x10		/* Unknown ??. Defaults to 0 */
+#define HCFG			0x14		/* Hardware config register			*/
+						/* 0x1000 causes AC3 to fails. It adds a dither bit. */
+
+#define HCFG_STAC		0x10000000	/* Special mode for STAC9460 Codec. */
+#define HCFG_CAPTURE_I2S_BYPASS	0x08000000	/* 1 = bypass I2S input async SRC. */
+#define HCFG_CAPTURE_SPDIF_BYPASS 0x04000000	/* 1 = bypass SPDIF input async SRC. */
+#define HCFG_PLAYBACK_I2S_BYPASS 0x02000000	/* 0 = I2S IN mixer output, 1 = I2S IN1. */
+#define HCFG_FORCE_LOCK		0x01000000	/* For test only. Force input SRC tracker to lock. */
+#define HCFG_PLAYBACK_ATTENUATION 0x00006000	/* Playback attenuation mask. 0 = 0dB, 1 = 6dB, 2 = 12dB, 3 = Mute. */
+#define HCFG_PLAYBACK_DITHER	0x00001000	/* 1 = Add dither bit to all playback channels. */
+#define HCFG_PLAYBACK_S32_LE	0x00000800	/* 1 = S32_LE, 0 = S16_LE                       */
+#define HCFG_CAPTURE_S32_LE	0x00000400	/* 1 = S32_LE, 0 = S16_LE (S32_LE current not working)	*/
+#define HCFG_8_CHANNEL_PLAY	0x00000200	/* 1 = 8 channels, 0 = 2 channels per substream.*/
+#define HCFG_8_CHANNEL_CAPTURE	0x00000100	/* 1 = 8 channels, 0 = 2 channels per substream.*/
+#define HCFG_MONO		0x00000080	/* 1 = I2S Input mono                           */
+#define HCFG_I2S_OUTPUT		0x00000010	/* 1 = I2S Output disabled                      */
+#define HCFG_AC97		0x00000008	/* 0 = AC97 1.0, 1 = AC97 2.0                   */
+#define HCFG_LOCK_PLAYBACK_CACHE 0x00000004	/* 1 = Cancel bustmaster accesses to soundcache */
+						/* NOTE: This should generally never be used.  	*/
+#define HCFG_LOCK_CAPTURE_CACHE	0x00000002	/* 1 = Cancel bustmaster accesses to soundcache */
+						/* NOTE: This should generally never be used.  	*/
+#define HCFG_AUDIOENABLE	0x00000001	/* 0 = CODECs transmit zero-valued samples	*/
+						/* Should be set to 1 when the EMU10K1 is	*/
+						/* completely initialized.			*/
+#define GPIO			0x18		/* Defaults: 005f03a3-Analog, 005f02a2-SPDIF.   */
+						/* Here pins 0,1,2,3,4,,6 are output. 5,7 are input */
+						/* For the Audigy LS, pin 0 (or bit 8) controls the SPDIF/Analog jack. */
+						/* SB Live 24bit:
+						 * bit 8 0 = SPDIF in and out / 1 = Analog (Mic or Line)-in.
+						 * bit 9 0 = Mute / 1 = Analog out.
+						 * bit 10 0 = Line-in / 1 = Mic-in.
+						 * bit 11 0 = ? / 1 = ?
+						 * bit 12 0 = 48 Khz / 1 = 96 Khz Analog out on SB Live 24bit.
+						 * bit 13 0 = ? / 1 = ?
+						 * bit 14 0 = Mute / 1 = Analog out
+						 * bit 15 0 = ? / 1 = ?
+						 * Both bit 9 and bit 14 have to be set for analog sound to work on the SB Live 24bit.
+						 */
+						/* 8 general purpose programmable In/Out pins.
+						 * GPI [8:0] Read only. Default 0.
+						 * GPO [15:8] Default 0x9. (Default to SPDIF jack enabled for SPDIF)
+						 * GPO Enable [23:16] Default 0x0f. Setting a bit to 1, causes the pin to be an output pin.
+						 */
+#define AC97DATA		0x1c		/* AC97 register set data register (16 bit)	*/
+
+#define AC97ADDRESS		0x1e		/* AC97 register set address register (8 bit)	*/
+
+/********************************************************************************************************/
+/* CA0106 pointer-offset register set, accessed through the PTR and DATA registers                     */
+/********************************************************************************************************/
+                                                                                                                           
+/* Initally all registers from 0x00 to 0x3f have zero contents. */
+#define PLAYBACK_LIST_ADDR	0x00		/* Base DMA address of a list of pointers to each period/size */
+						/* One list entry: 4 bytes for DMA address, 
+						 * 4 bytes for period_size << 16.
+						 * One list entry is 8 bytes long.
+						 * One list entry for each period in the buffer.
+						 */
+						/* ADDR[31:0], Default: 0x0 */
+#define PLAYBACK_LIST_SIZE	0x01		/* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000  */
+						/* SIZE[21:16], Default: 0x8 */
+#define PLAYBACK_LIST_PTR	0x02		/* Pointer to the current period being played */
+						/* PTR[5:0], Default: 0x0 */
+#define PLAYBACK_UNKNOWN3	0x03		/* Not used ?? */
+#define PLAYBACK_DMA_ADDR	0x04		/* Playback DMA addresss */
+						/* DMA[31:0], Default: 0x0 */
+#define PLAYBACK_PERIOD_SIZE	0x05		/* Playback period size. win2000 uses 0x04000000 */
+						/* SIZE[31:16], Default: 0x0 */
+#define PLAYBACK_POINTER	0x06		/* Playback period pointer. Used with PLAYBACK_LIST_PTR to determine buffer position currently in DAC */
+						/* POINTER[15:0], Default: 0x0 */
+#define PLAYBACK_PERIOD_END_ADDR 0x07		/* Playback fifo end address */
+						/* END_ADDR[15:0], FLAG[16] 0 = don't stop, 1 = stop */
+#define PLAYBACK_FIFO_OFFSET_ADDRESS	0x08	/* Current fifo offset address [21:16] */
+						/* Cache size valid [5:0] */
+#define PLAYBACK_UNKNOWN9	0x09		/* 0x9 to 0xf Unused */
+#define CAPTURE_DMA_ADDR	0x10		/* Capture DMA address */
+						/* DMA[31:0], Default: 0x0 */
+#define CAPTURE_BUFFER_SIZE	0x11		/* Capture buffer size */
+						/* SIZE[31:16], Default: 0x0 */
+#define CAPTURE_POINTER		0x12		/* Capture buffer pointer. Sample currently in ADC */
+						/* POINTER[15:0], Default: 0x0 */
+#define CAPTURE_FIFO_OFFSET_ADDRESS	0x13	/* Current fifo offset address [21:16] */
+						/* Cache size valid [5:0] */
+#define PLAYBACK_LAST_SAMPLE    0x20		/* The sample currently being played */
+/* 0x21 - 0x3f unused */
+#define BASIC_INTERRUPT         0x40		/* Used by both playback and capture interrupt handler */
+						/* Playback (0x1<<channel_id) */
+						/* Capture  (0x100<<channel_id) */
+						/* Playback sample rate 96000 = 0x20000 */
+						/* Start Playback [3:0] (one bit per channel)
+						 * Start Capture [11:8] (one bit per channel)
+						 * Playback rate [23:16] (2 bits per channel) (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz)
+						 * Playback mixer in enable [27:24] (one bit per channel)
+						 * Playback mixer out enable [31:28] (one bit per channel)
+						 */
+/* The Digital out jack is shared with the Center/LFE Analogue output. 
+ * The jack has 4 poles. I will call 1 - Tip, 2 - Next to 1, 3 - Next to 2, 4 - Next to 3
+ * For Analogue: 1 -> Center Speaker, 2 -> Sub Woofer, 3 -> Ground, 4 -> Ground
+ * For Digital: 1 -> Front SPDIF, 2 -> Rear SPDIF, 3 -> Center/Subwoofer SPDIF, 4 -> Ground.
+ * Standard 4 pole Video A/V cable with RCA outputs: 1 -> White, 2 -> Yellow, 3 -> Sheild on all three, 4 -> Red.
+ * So, from this you can see that you cannot use a Standard 4 pole Video A/V cable with the SB Audigy LS card.
+ */
+/* The Front SPDIF PCM gets mixed with samples from the AC97 codec, so can only work for Stereo PCM and not AC3/DTS
+ * The Rear SPDIF can be used for Stereo PCM and also AC3/DTS
+ * The Center/LFE SPDIF cannot be used for AC3/DTS, but can be used for Stereo PCM.
+ * Summary: For ALSA we use the Rear channel for SPDIF Digital AC3/DTS output
+ */
+/* A standard 2 pole mono mini-jack to RCA plug can be used for SPDIF Stereo PCM output from the Front channel.
+ * A standard 3 pole stereo mini-jack to 2 RCA plugs can be used for SPDIF AC3/DTS and Stereo PCM output utilising the Rear channel and just one of the RCA plugs. 
+ */
+#define SPCS0			0x41		/* SPDIF output Channel Status 0 register. For Rear. default=0x02108004, non-audio=0x02108006	*/
+#define SPCS1			0x42		/* SPDIF output Channel Status 1 register. For Front */
+#define SPCS2			0x43		/* SPDIF output Channel Status 2 register. For Center/LFE */
+#define SPCS3			0x44		/* SPDIF output Channel Status 3 register. Unknown */
+						/* When Channel set to 0: */
+#define SPCS_CLKACCYMASK	0x30000000	/* Clock accuracy				*/
+#define SPCS_CLKACCY_1000PPM	0x00000000	/* 1000 parts per million			*/
+#define SPCS_CLKACCY_50PPM	0x10000000	/* 50 parts per million				*/
+#define SPCS_CLKACCY_VARIABLE	0x20000000	/* Variable accuracy				*/
+#define SPCS_SAMPLERATEMASK	0x0f000000	/* Sample rate					*/
+#define SPCS_SAMPLERATE_44	0x00000000	/* 44.1kHz sample rate				*/
+#define SPCS_SAMPLERATE_48	0x02000000	/* 48kHz sample rate				*/
+#define SPCS_SAMPLERATE_32	0x03000000	/* 32kHz sample rate				*/
+#define SPCS_CHANNELNUMMASK	0x00f00000	/* Channel number				*/
+#define SPCS_CHANNELNUM_UNSPEC	0x00000000	/* Unspecified channel number			*/
+#define SPCS_CHANNELNUM_LEFT	0x00100000	/* Left channel					*/
+#define SPCS_CHANNELNUM_RIGHT	0x00200000	/* Right channel				*/
+#define SPCS_SOURCENUMMASK	0x000f0000	/* Source number				*/
+#define SPCS_SOURCENUM_UNSPEC	0x00000000	/* Unspecified source number			*/
+#define SPCS_GENERATIONSTATUS	0x00008000	/* Originality flag (see IEC-958 spec)		*/
+#define SPCS_CATEGORYCODEMASK	0x00007f00	/* Category code (see IEC-958 spec)		*/
+#define SPCS_MODEMASK		0x000000c0	/* Mode (see IEC-958 spec)			*/
+#define SPCS_EMPHASISMASK	0x00000038	/* Emphasis					*/
+#define SPCS_EMPHASIS_NONE	0x00000000	/* No emphasis					*/
+#define SPCS_EMPHASIS_50_15	0x00000008	/* 50/15 usec 2 channel				*/
+#define SPCS_COPYRIGHT		0x00000004	/* Copyright asserted flag -- do not modify	*/
+#define SPCS_NOTAUDIODATA	0x00000002	/* 0 = Digital audio, 1 = not audio		*/
+#define SPCS_PROFESSIONAL	0x00000001	/* 0 = Consumer (IEC-958), 1 = pro (AES3-1992)	*/
+
+						/* When Channel set to 1: */
+#define SPCS_WORD_LENGTH_MASK	0x0000000f	/* Word Length Mask				*/
+#define SPCS_WORD_LENGTH_16	0x00000008	/* Word Length 16 bit				*/
+#define SPCS_WORD_LENGTH_17	0x00000006	/* Word Length 17 bit				*/
+#define SPCS_WORD_LENGTH_18	0x00000004	/* Word Length 18 bit				*/
+#define SPCS_WORD_LENGTH_19	0x00000002	/* Word Length 19 bit				*/
+#define SPCS_WORD_LENGTH_20A	0x0000000a	/* Word Length 20 bit				*/
+#define SPCS_WORD_LENGTH_20	0x00000009	/* Word Length 20 bit (both 0xa and 0x9 are 20 bit) */
+#define SPCS_WORD_LENGTH_21	0x00000007	/* Word Length 21 bit				*/
+#define SPCS_WORD_LENGTH_22	0x00000005	/* Word Length 22 bit				*/
+#define SPCS_WORD_LENGTH_23	0x00000003	/* Word Length 23 bit				*/
+#define SPCS_WORD_LENGTH_24	0x0000000b	/* Word Length 24 bit				*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_MASK	0x000000f0 /* Original Sample rate			*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_NONE	0x00000000 /* Original Sample rate not indicated	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_16000	0x00000010 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_RES1	0x00000020 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_32000	0x00000030 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_12000	0x00000040 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_11025	0x00000050 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_8000	0x00000060 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_RES2	0x00000070 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_192000 0x00000080 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_24000	0x00000090 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_96000	0x000000a0 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_48000	0x000000b0 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_176400 0x000000c0 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_22050	0x000000d0 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_88200	0x000000e0 /* Original Sample rate	*/
+#define SPCS_ORIGINAL_SAMPLE_RATE_44100	0x000000f0 /* Original Sample rate	*/
+
+#define SPDIF_SELECT1		0x45		/* Enables SPDIF or Analogue outputs 0-SPDIF, 0xf00-Analogue */
+						/* 0x100 - Front, 0x800 - Rear, 0x200 - Center/LFE.
+						 * But as the jack is shared, use 0xf00.
+						 * The Windows2000 driver uses 0x0000000f for both digital and analog.
+						 * 0xf00 introduces interesting noises onto the Center/LFE.
+						 * If you turn the volume up, you hear computer noise,
+						 * e.g. mouse moving, changing between app windows etc.
+						 * So, I am going to set this to 0x0000000f all the time now,
+						 * same as the windows driver does.
+						 * Use register SPDIF_SELECT2(0x72) to switch between SPDIF and Analog.
+						 */
+						/* When Channel = 0:
+						 * Wide SPDIF format [3:0] (one bit for each channel) (0=20bit, 1=24bit)
+						 * Tristate SPDIF Output [11:8] (one bit for each channel) (0=Not tristate, 1=Tristate)
+						 * SPDIF Bypass enable [19:16] (one bit for each channel) (0=Not bypass, 1=Bypass)
+						 */
+						/* When Channel = 1:
+						 * SPDIF 0 User data [7:0]
+						 * SPDIF 1 User data [15:8]
+						 * SPDIF 0 User data [23:16]
+						 * SPDIF 0 User data [31:24]
+						 * User data can be sent by using the SPDIF output frame pending and SPDIF output user bit interrupts.
+						 */
+#define WATERMARK		0x46		/* Test bit to indicate cache usage level */
+#define SPDIF_INPUT_STATUS	0x49		/* SPDIF Input status register. Bits the same as SPCS.
+						 * When Channel = 0: Bits the same as SPCS channel 0.
+						 * When Channel = 1: Bits the same as SPCS channel 1.
+						 * When Channel = 2:
+						 * SPDIF Input User data [16:0]
+						 * SPDIF Input Frame count [21:16]
+						 */
+#define CAPTURE_CACHE_DATA	0x50		/* 0x50-0x5f Recorded samples. */
+#define CAPTURE_SOURCE          0x60            /* Capture Source 0 = MIC */
+#define CAPTURE_SOURCE_CHANNEL0 0xf0000000	/* Mask for selecting the Capture sources */
+#define CAPTURE_SOURCE_CHANNEL1 0x0f000000	/* 0 - SPDIF mixer output. */
+#define CAPTURE_SOURCE_CHANNEL2 0x00f00000      /* 1 - What you hear or . 2 - ?? */
+#define CAPTURE_SOURCE_CHANNEL3 0x000f0000	/* 3 - Mic in, Line in, TAD in, Aux in. */
+#define CAPTURE_SOURCE_RECORD_MAP 0x0000ffff	/* Default 0x00e4 */
+						/* Record Map [7:0] (2 bits per channel) 0=mapped to channel 0, 1=mapped to channel 1, 2=mapped to channel2, 3=mapped to channel3 
+						 * Record source select for channel 0 [18:16]
+						 * Record source select for channel 1 [22:20]
+						 * Record source select for channel 2 [26:24]
+						 * Record source select for channel 3 [30:28]
+						 * 0 - SPDIF mixer output.
+						 * 1 - i2s mixer output.
+						 * 2 - SPDIF input.
+						 * 3 - i2s input.
+						 * 4 - AC97 capture.
+						 * 5 - SRC output.
+						 */
+#define CAPTURE_VOLUME1         0x61            /* Capture  volume per channel 0-3 */
+#define CAPTURE_VOLUME2         0x62            /* Capture  volume per channel 4-7 */
+
+#define PLAYBACK_ROUTING1       0x63            /* Playback routing of channels 0-7. Effects AC3 output. Default 0x32765410 */
+#define ROUTING1_REAR           0x77000000      /* Channel_id 0 sends to 10, Channel_id 1 sends to 32 */
+#define ROUTING1_NULL           0x00770000      /* Channel_id 2 sends to 54, Channel_id 3 sends to 76 */
+#define ROUTING1_CENTER_LFE     0x00007700      /* 0x32765410 means, send Channel_id 0 to FRONT, Channel_id 1 to REAR */
+#define ROUTING1_FRONT          0x00000077	/* Channel_id 2 to CENTER_LFE, Channel_id 3 to NULL. */
+						/* Channel_id's handle stereo channels. Channel X is a single mono channel */
+						/* Host is input from the PCI bus. */
+						/* Host channel 0 [2:0] -> SPDIF Mixer/Router channel 0-7.
+						 * Host channel 1 [6:4] -> SPDIF Mixer/Router channel 0-7.
+						 * Host channel 2 [10:8] -> SPDIF Mixer/Router channel 0-7.
+						 * Host channel 3 [14:12] -> SPDIF Mixer/Router channel 0-7.
+						 * Host channel 4 [18:16] -> SPDIF Mixer/Router channel 0-7.
+						 * Host channel 5 [22:20] -> SPDIF Mixer/Router channel 0-7.
+						 * Host channel 6 [26:24] -> SPDIF Mixer/Router channel 0-7.
+						 * Host channel 7 [30:28] -> SPDIF Mixer/Router channel 0-7.
+						 */
+
+#define PLAYBACK_ROUTING2       0x64            /* Playback Routing . Feeding Capture channels back into Playback. Effects AC3 output. Default 0x76767676 */
+						/* SRC is input from the capture inputs. */
+						/* SRC channel 0 [2:0] -> SPDIF Mixer/Router channel 0-7.
+						 * SRC channel 1 [6:4] -> SPDIF Mixer/Router channel 0-7.
+						 * SRC channel 2 [10:8] -> SPDIF Mixer/Router channel 0-7.
+						 * SRC channel 3 [14:12] -> SPDIF Mixer/Router channel 0-7.
+						 * SRC channel 4 [18:16] -> SPDIF Mixer/Router channel 0-7.
+						 * SRC channel 5 [22:20] -> SPDIF Mixer/Router channel 0-7.
+						 * SRC channel 6 [26:24] -> SPDIF Mixer/Router channel 0-7.
+						 * SRC channel 7 [30:28] -> SPDIF Mixer/Router channel 0-7.
+						 */
+
+#define PLAYBACK_MUTE           0x65            /* Unknown. While playing 0x0, while silent 0x00fc0000 */
+						/* SPDIF Mixer input control:
+						 * Invert SRC to SPDIF Mixer [7-0] (One bit per channel)
+						 * Invert Host to SPDIF Mixer [15:8] (One bit per channel)
+						 * SRC to SPDIF Mixer disable [23:16] (One bit per channel)
+						 * Host to SPDIF Mixer disable [31:24] (One bit per channel)
+						 */
+#define PLAYBACK_VOLUME1        0x66            /* Playback SPDIF volume per channel. Set to the same PLAYBACK_VOLUME(0x6a) */
+						/* PLAYBACK_VOLUME1 must be set to 30303030 for SPDIF AC3 Playback */
+						/* SPDIF mixer input volume. 0=12dB, 0x30=0dB, 0xFE=-51.5dB, 0xff=Mute */
+						/* One register for each of the 4 stereo streams. */
+						/* SRC Right volume [7:0]
+						 * SRC Left  volume [15:8]
+						 * Host Right volume [23:16]
+						 * Host Left  volume [31:24]
+						 */
+#define CAPTURE_ROUTING1        0x67            /* Capture Routing. Default 0x32765410 */
+						/* Similar to register 0x63, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */
+#define CAPTURE_ROUTING2        0x68            /* Unknown Routing. Default 0x76767676 */
+						/* Similar to register 0x64, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */
+#define CAPTURE_MUTE            0x69            /* Unknown. While capturing 0x0, while silent 0x00fc0000 */
+						/* Similar to register 0x65, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */
+#define PLAYBACK_VOLUME2        0x6a            /* Playback Analog volume per channel. Does not effect AC3 output */
+						/* Similar to register 0x66, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */
+#define UNKNOWN6b               0x6b            /* Unknown. Readonly. Default 00400000 00400000 00400000 00400000 */
+#define MIDI_UART_A_DATA		0x6c            /* Midi Uart A Data */
+#define MIDI_UART_A_CMD		0x6d            /* Midi Uart A Command/Status */
+#define MIDI_UART_B_DATA		0x6e            /* Midi Uart B Data (currently unused) */
+#define MIDI_UART_B_CMD		0x6f            /* Midi Uart B Command/Status (currently unused) */
+
+/* unique channel identifier for midi->channel */
+
+#define CA0106_MIDI_CHAN_A		0x1
+#define CA0106_MIDI_CHAN_B		0x2
+
+/* from mpu401 */
+
+#define CA0106_MIDI_INPUT_AVAIL 	0x80
+#define CA0106_MIDI_OUTPUT_READY	0x40
+#define CA0106_MPU401_RESET		0xff
+#define CA0106_MPU401_ENTER_UART	0x3f
+#define CA0106_MPU401_ACK		0xfe
+
+#define SAMPLE_RATE_TRACKER_STATUS 0x70         /* Readonly. Default 00108000 00108000 00500000 00500000 */
+						/* Estimated sample rate [19:0] Relative to 48kHz. 0x8000 =  1.0
+						 * Rate Locked [20]
+						 * SPDIF Locked [21] For SPDIF channel only.
+						 * Valid Audio [22] For SPDIF channel only.
+						 */
+#define CAPTURE_CONTROL         0x71            /* Some sort of routing. default = 40c81000 30303030 30300000 00700000 */
+						/* Channel_id 0: 0x40c81000 must be changed to 0x40c80000 for SPDIF AC3 input or output. */
+						/* Channel_id 1: 0xffffffff(mute) 0x30303030(max) controls CAPTURE feedback into PLAYBACK. */
+						/* Sample rate output control register Channel=0
+						 * Sample output rate [1:0] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz)
+						 * Sample input rate [3:2] (0=48kHz, 1=Not available, 2=96kHz, 3=192Khz)
+						 * SRC input source select [4] 0=Audio from digital mixer, 1=Audio from analog source.
+						 * Record rate [9:8] (0=48kHz, 1=Not available, 2=96kHz, 3=192Khz)
+						 * Record mixer output enable [12:10] 
+						 * I2S input rate master mode [15:14] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz)
+						 * I2S output rate [17:16] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz)
+						 * I2S output source select [18] (0=Audio from host, 1=Audio from SRC)
+						 * Record mixer I2S enable [20:19] (enable/disable i2sin1 and i2sin0)
+						 * I2S output master clock select [21] (0=256*I2S output rate, 1=512*I2S output rate.)
+						 * I2S input master clock select [22] (0=256*I2S input rate, 1=512*I2S input rate.)
+						 * I2S input mode [23] (0=Slave, 1=Master)
+						 * SPDIF output rate [25:24] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz)
+						 * SPDIF output source select [26] (0=host, 1=SRC)
+						 * Not used [27]
+						 * Record Source 0 input [29:28] (0=SPDIF in, 1=I2S in, 2=AC97 Mic, 3=AC97 PCM)
+						 * Record Source 1 input [31:30] (0=SPDIF in, 1=I2S in, 2=AC97 Mic, 3=AC97 PCM)
+						 */ 
+						/* Sample rate output control register Channel=1
+						 * I2S Input 0 volume Right [7:0]
+						 * I2S Input 0 volume Left [15:8]
+						 * I2S Input 1 volume Right [23:16]
+						 * I2S Input 1 volume Left [31:24]
+						 */
+						/* Sample rate output control register Channel=2
+						 * SPDIF Input volume Right [23:16]
+						 * SPDIF Input volume Left [31:24]
+						 */
+						/* Sample rate output control register Channel=3
+						 * No used
+						 */
+#define SPDIF_SELECT2           0x72            /* Some sort of routing. Channel_id 0 only. default = 0x0f0f003f. Analog 0x000b0000, Digital 0x0b000000 */
+#define ROUTING2_FRONT_MASK     0x00010000      /* Enable for Front speakers. */
+#define ROUTING2_CENTER_LFE_MASK 0x00020000     /* Enable for Center/LFE speakers. */
+#define ROUTING2_REAR_MASK      0x00080000      /* Enable for Rear speakers. */
+						/* Audio output control
+						 * AC97 output enable [5:0]
+						 * I2S output enable [19:16]
+						 * SPDIF output enable [27:24]
+						 */ 
+#define UNKNOWN73               0x73            /* Unknown. Readonly. Default 0x0 */
+#define CHIP_VERSION            0x74            /* P17 Chip version. Channel_id 0 only. Default 00000071 */
+#define EXTENDED_INT_MASK       0x75            /* Used by both playback and capture interrupt handler */
+						/* Sets which Interrupts are enabled. */
+						/* 0x00000001 = Half period. Playback.
+						 * 0x00000010 = Full period. Playback.
+						 * 0x00000100 = Half buffer. Playback.
+						 * 0x00001000 = Full buffer. Playback.
+						 * 0x00010000 = Half buffer. Capture.
+						 * 0x00100000 = Full buffer. Capture.
+						 * Capture can only do 2 periods.
+						 * 0x01000000 = End audio. Playback.
+						 * 0x40000000 = Half buffer Playback,Caputre xrun.
+						 * 0x80000000 = Full buffer Playback,Caputre xrun.
+						 */
+#define EXTENDED_INT            0x76            /* Used by both playback and capture interrupt handler */
+						/* Shows which interrupts are active at the moment. */
+						/* Same bit layout as EXTENDED_INT_MASK */
+#define COUNTER77               0x77		/* Counter range 0 to 0x3fffff, 192000 counts per second. */
+#define COUNTER78               0x78		/* Counter range 0 to 0x3fffff, 44100 counts per second. */
+#define EXTENDED_INT_TIMER      0x79            /* Channel_id 0 only. Used by both playback and capture interrupt handler */
+						/* Causes interrupts based on timer intervals. */
+#define SPI			0x7a		/* SPI: Serial Interface Register */
+#define I2C_A			0x7b		/* I2C Address. 32 bit */
+#define I2C_D0			0x7c		/* I2C Data Port 0. 32 bit */
+#define I2C_D1			0x7d		/* I2C Data Port 1. 32 bit */
+//I2C values
+#define I2C_A_ADC_ADD_MASK	0x000000fe	//The address is a 7 bit address
+#define I2C_A_ADC_RW_MASK	0x00000001	//bit mask for R/W
+#define I2C_A_ADC_TRANS_MASK	0x00000010  	//Bit mask for I2c address DAC value
+#define I2C_A_ADC_ABORT_MASK	0x00000020	//Bit mask for I2C transaction abort flag
+#define I2C_A_ADC_LAST_MASK	0x00000040	//Bit mask for Last word transaction
+#define I2C_A_ADC_BYTE_MASK	0x00000080	//Bit mask for Byte Mode
+
+#define I2C_A_ADC_ADD		0x00000034	//This is the Device address for ADC 
+#define I2C_A_ADC_READ		0x00000001	//To perform a read operation
+#define I2C_A_ADC_START		0x00000100	//Start I2C transaction
+#define I2C_A_ADC_ABORT		0x00000200	//I2C transaction abort
+#define I2C_A_ADC_LAST		0x00000400	//I2C last transaction
+#define I2C_A_ADC_BYTE		0x00000800	//I2C one byte mode
+
+#define I2C_D_ADC_REG_MASK	0xfe000000  	//ADC address register 
+#define I2C_D_ADC_DAT_MASK	0x01ff0000  	//ADC data register
+
+#define ADC_TIMEOUT		0x00000007	//ADC Timeout Clock Disable
+#define ADC_IFC_CTRL		0x0000000b	//ADC Interface Control
+#define ADC_MASTER		0x0000000c	//ADC Master Mode Control
+#define ADC_POWER		0x0000000d	//ADC PowerDown Control
+#define ADC_ATTEN_ADCL		0x0000000e	//ADC Attenuation ADCL
+#define ADC_ATTEN_ADCR		0x0000000f	//ADC Attenuation ADCR
+#define ADC_ALC_CTRL1		0x00000010	//ADC ALC Control 1
+#define ADC_ALC_CTRL2		0x00000011	//ADC ALC Control 2
+#define ADC_ALC_CTRL3		0x00000012	//ADC ALC Control 3
+#define ADC_NOISE_CTRL		0x00000013	//ADC Noise Gate Control
+#define ADC_LIMIT_CTRL		0x00000014	//ADC Limiter Control
+#define ADC_MUX			0x00000015  	//ADC Mux offset
+
+#if 0
+/* FIXME: Not tested yet. */
+#define ADC_GAIN_MASK		0x000000ff	//Mask for ADC Gain
+#define ADC_ZERODB		0x000000cf	//Value to set ADC to 0dB
+#define ADC_MUTE_MASK		0x000000c0	//Mask for ADC mute
+#define ADC_MUTE		0x000000c0	//Value to mute ADC
+#define ADC_OSR			0x00000008	//Mask for ADC oversample rate select
+#define ADC_TIMEOUT_DISABLE	0x00000008	//Value and mask to disable Timeout clock
+#define ADC_HPF_DISABLE		0x00000100	//Value and mask to disable High pass filter
+#define ADC_TRANWIN_MASK	0x00000070	//Mask for Length of Transient Window
+#endif
+
+#define ADC_MUX_MASK		0x0000000f	//Mask for ADC Mux
+#define ADC_MUX_PHONE		0x00000001	//Value to select TAD at ADC Mux (Not used)
+#define ADC_MUX_MIC		0x00000002	//Value to select Mic at ADC Mux
+#define ADC_MUX_LINEIN		0x00000004	//Value to select LineIn at ADC Mux
+#define ADC_MUX_AUX		0x00000008	//Value to select Aux at ADC Mux
+
+#define SET_CHANNEL 0  /* Testing channel outputs 0=Front, 1=Center/LFE, 2=Unknown, 3=Rear */
+#define PCM_FRONT_CHANNEL 0
+#define PCM_REAR_CHANNEL 1
+#define PCM_CENTER_LFE_CHANNEL 2
+#define PCM_UNKNOWN_CHANNEL 3
+#define CONTROL_FRONT_CHANNEL 0
+#define CONTROL_REAR_CHANNEL 3
+#define CONTROL_CENTER_LFE_CHANNEL 1
+#define CONTROL_UNKNOWN_CHANNEL 2
+
+
+/* Based on WM8768 Datasheet Rev 4.2 page 32 */
+#define SPI_REG_MASK	0x1ff	/* 16-bit SPI writes have a 7-bit address */
+#define SPI_REG_SHIFT	9	/* followed by 9 bits of data */
+
+#define SPI_LDA1_REG		0	/* digital attenuation */
+#define SPI_RDA1_REG		1
+#define SPI_LDA2_REG		4
+#define SPI_RDA2_REG		5
+#define SPI_LDA3_REG		6
+#define SPI_RDA3_REG		7
+#define SPI_LDA4_REG		13
+#define SPI_RDA4_REG		14
+#define SPI_MASTDA_REG		8
+
+#define SPI_DA_BIT_UPDATE	(1<<8)	/* update attenuation values */
+#define SPI_DA_BIT_0dB		0xff	/* 0 dB */
+#define SPI_DA_BIT_infdB	0x00	/* inf dB attenuation (mute) */
+
+#define SPI_PL_REG		2
+#define SPI_PL_BIT_L_M		(0<<5)	/* left channel = mute */
+#define SPI_PL_BIT_L_L		(1<<5)	/* left channel = left */
+#define SPI_PL_BIT_L_R		(2<<5)	/* left channel = right */
+#define SPI_PL_BIT_L_C		(3<<5)	/* left channel = (L+R)/2 */
+#define SPI_PL_BIT_R_M		(0<<7)	/* right channel = mute */
+#define SPI_PL_BIT_R_L		(1<<7)	/* right channel = left */
+#define SPI_PL_BIT_R_R		(2<<7)	/* right channel = right */
+#define SPI_PL_BIT_R_C		(3<<7)	/* right channel = (L+R)/2 */
+#define SPI_IZD_REG		2
+#define SPI_IZD_BIT		(1<<4)	/* infinite zero detect */
+
+#define SPI_FMT_REG		3
+#define SPI_FMT_BIT_RJ		(0<<0)	/* right justified mode */
+#define SPI_FMT_BIT_LJ		(1<<0)	/* left justified mode */
+#define SPI_FMT_BIT_I2S		(2<<0)	/* I2S mode */
+#define SPI_FMT_BIT_DSP		(3<<0)	/* DSP Modes A or B */
+#define SPI_LRP_REG		3
+#define SPI_LRP_BIT		(1<<2)	/* invert LRCLK polarity */
+#define SPI_BCP_REG		3
+#define SPI_BCP_BIT		(1<<3)	/* invert BCLK polarity */
+#define SPI_IWL_REG		3
+#define SPI_IWL_BIT_16		(0<<4)	/* 16-bit world length */
+#define SPI_IWL_BIT_20		(1<<4)	/* 20-bit world length */
+#define SPI_IWL_BIT_24		(2<<4)	/* 24-bit world length */
+#define SPI_IWL_BIT_32		(3<<4)	/* 32-bit world length */
+
+#define SPI_MS_REG		10
+#define SPI_MS_BIT		(1<<5)	/* master mode */
+#define SPI_RATE_REG		10	/* only applies in master mode */
+#define SPI_RATE_BIT_128	(0<<6)	/* MCLK = LRCLK * 128 */
+#define SPI_RATE_BIT_192	(1<<6)
+#define SPI_RATE_BIT_256	(2<<6)
+#define SPI_RATE_BIT_384	(3<<6)
+#define SPI_RATE_BIT_512	(4<<6)
+#define SPI_RATE_BIT_768	(5<<6)
+
+/* They really do label the bit for the 4th channel "4" and not "3" */
+#define SPI_DMUTE0_REG		9
+#define SPI_DMUTE1_REG		9
+#define SPI_DMUTE2_REG		9
+#define SPI_DMUTE4_REG		15
+#define SPI_DMUTE0_BIT		(1<<3)
+#define SPI_DMUTE1_BIT		(1<<4)
+#define SPI_DMUTE2_BIT		(1<<5)
+#define SPI_DMUTE4_BIT		(1<<2)
+
+#define SPI_PHASE0_REG		3
+#define SPI_PHASE1_REG		3
+#define SPI_PHASE2_REG		3
+#define SPI_PHASE4_REG		15
+#define SPI_PHASE0_BIT		(1<<6)
+#define SPI_PHASE1_BIT		(1<<7)
+#define SPI_PHASE2_BIT		(1<<8)
+#define SPI_PHASE4_BIT		(1<<3)
+
+#define SPI_PDWN_REG		2	/* power down all DACs */
+#define SPI_PDWN_BIT		(1<<2)
+#define SPI_DACD0_REG		10	/* power down individual DACs */
+#define SPI_DACD1_REG		10
+#define SPI_DACD2_REG		10
+#define SPI_DACD4_REG		15
+#define SPI_DACD0_BIT		(1<<1)
+#define SPI_DACD1_BIT		(1<<2)
+#define SPI_DACD2_BIT		(1<<3)
+#define SPI_DACD4_BIT		(1<<0)	/* datasheet error says it's 1 */
+
+#define SPI_PWRDNALL_REG	10	/* power down everything */
+#define SPI_PWRDNALL_BIT	(1<<4)
+
+#include "ca_midi.h"
+
+struct snd_ca0106;
+
+struct snd_ca0106_channel {
+	struct snd_ca0106 *emu;
+	int number;
+	int use;
+	void (*interrupt)(struct snd_ca0106 *emu, struct snd_ca0106_channel *channel);
+	struct snd_ca0106_pcm *epcm;
+};
+
+struct snd_ca0106_pcm {
+	struct snd_ca0106 *emu;
+	struct snd_pcm_substream *substream;
+        int channel_id;
+	unsigned short running;
+};
+
+struct snd_ca0106_details {
+        u32 serial;
+        char * name;
+	int ac97;	/* ac97 = 0 -> Select MIC, Line in, TAD in, AUX in.
+			   ac97 = 1 -> Default to AC97 in. */
+	int gpio_type;	/* gpio_type = 1 -> shared mic-in/line-in
+			   gpio_type = 2 -> shared side-out/line-in. */
+	int i2c_adc;	/* with i2c_adc=1, the driver adds some capture volume
+			   controls, phone, mic, line-in and aux. */
+	u16 spi_dac;	/* spi_dac = 0 -> no spi interface for DACs
+			   spi_dac = 0x<front><rear><center-lfe><side>
+			   -> specifies DAC id for each channel pair. */
+};
+
+// definition of the chip-specific record
+struct snd_ca0106 {
+	struct snd_card *card;
+	struct snd_ca0106_details *details;
+	struct pci_dev *pci;
+
+	unsigned long port;
+	struct resource *res_port;
+	int irq;
+
+	unsigned int serial;            /* serial number */
+	unsigned short model;		/* subsystem id */
+
+	spinlock_t emu_lock;
+
+	struct snd_ac97 *ac97;
+	struct snd_pcm *pcm[4];
+
+	struct snd_ca0106_channel playback_channels[4];
+	struct snd_ca0106_channel capture_channels[4];
+	u32 spdif_bits[4];             /* s/pdif out default setup */
+	u32 spdif_str_bits[4];         /* s/pdif out per-stream setup */
+	int spdif_enable;
+	int capture_source;
+	int i2c_capture_source;
+	u8 i2c_capture_volume[4][2];
+	int capture_mic_line_in;
+
+	struct snd_dma_buffer buffer;
+
+	struct snd_ca_midi midi;
+	struct snd_ca_midi midi2;
+
+	u16 spi_dac_reg[16];
+
+#ifdef CONFIG_PM
+#define NUM_SAVED_VOLUMES	9
+	unsigned int saved_vol[NUM_SAVED_VOLUMES];
+#endif
+};
+
+int snd_ca0106_mixer(struct snd_ca0106 *emu);
+int snd_ca0106_proc_init(struct snd_ca0106 * emu);
+
+unsigned int snd_ca0106_ptr_read(struct snd_ca0106 * emu, 
+				 unsigned int reg, 
+				 unsigned int chn);
+
+void snd_ca0106_ptr_write(struct snd_ca0106 *emu, 
+			  unsigned int reg, 
+			  unsigned int chn, 
+			  unsigned int data);
+
+int snd_ca0106_i2c_write(struct snd_ca0106 *emu, u32 reg, u32 value);
+
+int snd_ca0106_spi_write(struct snd_ca0106 * emu,
+				   unsigned int data);
+
+#ifdef CONFIG_PM
+void snd_ca0106_mixer_suspend(struct snd_ca0106 *chip);
+void snd_ca0106_mixer_resume(struct snd_ca0106 *chip);
+#else
+#define snd_ca0106_mixer_suspend(chip)	do { } while (0)
+#define snd_ca0106_mixer_resume(chip)	do { } while (0)
+#endif
diff --git a/linux/sound/pci/ca0106/ca0106_main.c b/linux/sound/pci/ca0106/ca0106_main.c
new file mode 100644
index 0000000..d2d12c0
--- /dev/null
+++ b/linux/sound/pci/ca0106/ca0106_main.c
@@ -0,0 +1,1959 @@
+/*
+ *  Copyright (c) 2004 James Courtier-Dutton <James@superbug.demon.co.uk>
+ *  Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit
+ *  Version: 0.0.25
+ *
+ *  FEATURES currently supported:
+ *    Front, Rear and Center/LFE.
+ *    Surround40 and Surround51.
+ *    Capture from MIC an LINE IN input.
+ *    SPDIF digital playback of PCM stereo and AC3/DTS works.
+ *    (One can use a standard mono mini-jack to one RCA plugs cable.
+ *     or one can use a standard stereo mini-jack to two RCA plugs cable.
+ *     Plug one of the RCA plugs into the Coax input of the external decoder/receiver.)
+ *    ( In theory one could output 3 different AC3 streams at once, to 3 different SPDIF outputs. )
+ *    Notes on how to capture sound:
+ *      The AC97 is used in the PLAYBACK direction.
+ *      The output from the AC97 chip, instead of reaching the speakers, is fed into the Philips 1361T ADC.
+ *      So, to record from the MIC, set the MIC Playback volume to max,
+ *      unmute the MIC and turn up the MASTER Playback volume.
+ *      So, to prevent feedback when capturing, minimise the "Capture feedback into Playback" volume.
+ *   
+ *    The only playback controls that currently do anything are: -
+ *    Analog Front
+ *    Analog Rear
+ *    Analog Center/LFE
+ *    SPDIF Front
+ *    SPDIF Rear
+ *    SPDIF Center/LFE
+ *   
+ *    For capture from Mic in or Line in.
+ *    Digital/Analog ( switch must be in Analog mode for CAPTURE. )
+ * 
+ *    CAPTURE feedback into PLAYBACK
+ * 
+ *  Changelog:
+ *    Support interrupts per period.
+ *    Removed noise from Center/LFE channel when in Analog mode.
+ *    Rename and remove mixer controls.
+ *  0.0.6
+ *    Use separate card based DMA buffer for periods table list.
+ *  0.0.7
+ *    Change remove and rename ctrls into lists.
+ *  0.0.8
+ *    Try to fix capture sources.
+ *  0.0.9
+ *    Fix AC3 output.
+ *    Enable S32_LE format support.
+ *  0.0.10
+ *    Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".)
+ *  0.0.11
+ *    Add Model name recognition.
+ *  0.0.12
+ *    Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period.
+ *    Remove redundent "voice" handling.
+ *  0.0.13
+ *    Single trigger call for multi channels.
+ *  0.0.14
+ *    Set limits based on what the sound card hardware can do.
+ *    playback periods_min=2, periods_max=8
+ *    capture hw constraints require period_size = n * 64 bytes.
+ *    playback hw constraints require period_size = n * 64 bytes.
+ *  0.0.15
+ *    Minor updates.
+ *  0.0.16
+ *    Implement 192000 sample rate.
+ *  0.0.17
+ *    Add support for SB0410 and SB0413.
+ *  0.0.18
+ *    Modified Copyright message.
+ *  0.0.19
+ *    Finally fix support for SB Live 24 bit. SB0410 and SB0413.
+ *    The output codec needs resetting, otherwise all output is muted.
+ *  0.0.20
+ *    Merge "pci_disable_device(pci);" fixes.
+ *  0.0.21
+ *    Add 4 capture channels. (SPDIF only comes in on channel 0. )
+ *    Add SPDIF capture using optional digital I/O module for SB Live 24bit. (Analog capture does not yet work.)
+ *  0.0.22
+ *    Add support for MSI K8N Diamond Motherboard with onboard SB Live 24bit without AC97. From kiksen, bug #901
+ *  0.0.23
+ *    Implement support for Line-in capture on SB Live 24bit.
+ *  0.0.24
+ *    Add support for mute control on SB Live 24bit (cards w/ SPI DAC)
+ *  0.0.25
+ *    Powerdown SPI DAC channels when not in use
+ *
+ *  BUGS:
+ *    Some stability problems when unloading the snd-ca0106 kernel module.
+ *    --
+ *
+ *  TODO:
+ *    4 Capture channels, only one implemented so far.
+ *    Other capture rates apart from 48khz not implemented.
+ *    MIDI
+ *    --
+ *  GENERAL INFO:
+ *    Model: SB0310
+ *    P17 Chip: CA0106-DAT
+ *    AC97 Codec: STAC 9721
+ *    ADC: Philips 1361T (Stereo 24bit)
+ *    DAC: WM8746EDS (6-channel, 24bit, 192Khz)
+ *
+ *  GENERAL INFO:
+ *    Model: SB0410
+ *    P17 Chip: CA0106-DAT
+ *    AC97 Codec: None
+ *    ADC: WM8775EDS (4 Channel)
+ *    DAC: CS4382 (114 dB, 24-Bit, 192 kHz, 8-Channel D/A Converter with DSD Support)
+ *    SPDIF Out control switches between Mic in and SPDIF out.
+ *    No sound out or mic input working yet.
+ * 
+ *  GENERAL INFO:
+ *    Model: SB0413
+ *    P17 Chip: CA0106-DAT
+ *    AC97 Codec: None.
+ *    ADC: Unknown
+ *    DAC: Unknown
+ *    Trying to handle it like the SB0410.
+ *
+ *  This code was initally based on code from ALSA's emu10k1x.c which is:
+ *  Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+
+MODULE_AUTHOR("James Courtier-Dutton <James@superbug.demon.co.uk>");
+MODULE_DESCRIPTION("CA0106");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Creative,SB CA0106 chip}}");
+
+// module parameters (see "Module Parameters")
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+static uint subsystem[SNDRV_CARDS]; /* Force card subsystem model */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the CA0106 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the CA0106 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable the CA0106 soundcard.");
+module_param_array(subsystem, uint, NULL, 0444);
+MODULE_PARM_DESC(subsystem, "Force card subsystem model.");
+
+#include "ca0106.h"
+
+static struct snd_ca0106_details ca0106_chip_details[] = {
+	 /* Sound Blaster X-Fi Extreme Audio. This does not have an AC97. 53SB079000000 */
+	 /* It is really just a normal SB Live 24bit. */
+	 /* Tested:
+	  * See ALSA bug#3251
+	  */
+	 { .serial = 0x10131102,
+	   .name   = "X-Fi Extreme Audio [SBxxxx]",
+	   .gpio_type = 1,
+	   .i2c_adc = 1 } ,
+	 /* Sound Blaster X-Fi Extreme Audio. This does not have an AC97. 53SB079000000 */
+	 /* It is really just a normal SB Live 24bit. */
+	 /*
+ 	  * CTRL:CA0111-WTLF
+	  * ADC: WM8775SEDS
+	  * DAC: CS4382-KQZ
+	  */
+	 /* Tested:
+	  * Playback on front, rear, center/lfe speakers
+	  * Capture from Mic in.
+	  * Not-Tested:
+	  * Capture from Line in.
+	  * Playback to digital out.
+	  */
+	 { .serial = 0x10121102,
+	   .name   = "X-Fi Extreme Audio [SB0790]",
+	   .gpio_type = 1,
+	   .i2c_adc = 1 } ,
+	 /* New Dell Sound Blaster Live! 7.1 24bit. This does not have an AC97.  */
+	 /* AudigyLS[SB0310] */
+	 { .serial = 0x10021102,
+	   .name   = "AudigyLS [SB0310]",
+	   .ac97   = 1 } , 
+	 /* Unknown AudigyLS that also says SB0310 on it */
+	 { .serial = 0x10051102,
+	   .name   = "AudigyLS [SB0310b]",
+	   .ac97   = 1 } ,
+	 /* New Sound Blaster Live! 7.1 24bit. This does not have an AC97. 53SB041000001 */
+	 { .serial = 0x10061102,
+	   .name   = "Live! 7.1 24bit [SB0410]",
+	   .gpio_type = 1,
+	   .i2c_adc = 1 } ,
+	 /* New Dell Sound Blaster Live! 7.1 24bit. This does not have an AC97.  */
+	 { .serial = 0x10071102,
+	   .name   = "Live! 7.1 24bit [SB0413]",
+	   .gpio_type = 1,
+	   .i2c_adc = 1 } ,
+	 /* New Audigy SE. Has a different DAC. */
+	 /* SB0570:
+	  * CTRL:CA0106-DAT
+	  * ADC: WM8775EDS
+	  * DAC: WM8768GEDS
+	  */
+	 { .serial = 0x100a1102,
+	   .name   = "Audigy SE [SB0570]",
+	   .gpio_type = 1,
+	   .i2c_adc = 1,
+	   .spi_dac = 0x4021 } ,
+	 /* New Audigy LS. Has a different DAC. */
+	 /* SB0570:
+	  * CTRL:CA0106-DAT
+	  * ADC: WM8775EDS
+	  * DAC: WM8768GEDS
+	  */
+	 { .serial = 0x10111102,
+	   .name   = "Audigy SE OEM [SB0570a]",
+	   .gpio_type = 1,
+	   .i2c_adc = 1,
+	   .spi_dac = 0x4021 } ,
+	/* Sound Blaster 5.1vx
+	 * Tested: Playback on front, rear, center/lfe speakers
+	 * Not-Tested: Capture
+	 */
+	{ .serial = 0x10041102,
+	  .name   = "Sound Blaster 5.1vx [SB1070]",
+	  .gpio_type = 1,
+	  .i2c_adc = 0,
+	  .spi_dac = 0x0124
+	 } ,
+	 /* MSI K8N Diamond Motherboard with onboard SB Live 24bit without AC97 */
+	 /* SB0438
+	  * CTRL:CA0106-DAT
+	  * ADC: WM8775SEDS
+	  * DAC: CS4382-KQZ
+	  */
+	 { .serial = 0x10091462,
+	   .name   = "MSI K8N Diamond MB [SB0438]",
+	   .gpio_type = 2,
+	   .i2c_adc = 1 } ,
+	 /* MSI K8N Diamond PLUS MB */
+	 { .serial = 0x10091102,
+	   .name   = "MSI K8N Diamond MB",
+	   .gpio_type = 2,
+	   .i2c_adc = 1,
+	   .spi_dac = 0x4021 } ,
+	/* Giga-byte GA-G1975X mobo
+	 * Novell bnc#395807
+	 */
+	/* FIXME: the GPIO and I2C setting aren't tested well */
+	{ .serial = 0x1458a006,
+	  .name = "Giga-byte GA-G1975X",
+	  .gpio_type = 1,
+	  .i2c_adc = 1 },
+	 /* Shuttle XPC SD31P which has an onboard Creative Labs
+	  * Sound Blaster Live! 24-bit EAX
+	  * high-definition 7.1 audio processor".
+	  * Added using info from andrewvegan in alsa bug #1298
+	  */
+	 { .serial = 0x30381297,
+	   .name   = "Shuttle XPC SD31P [SD31P]",
+	   .gpio_type = 1,
+	   .i2c_adc = 1 } ,
+	/* Shuttle XPC SD11G5 which has an onboard Creative Labs
+	 * Sound Blaster Live! 24-bit EAX
+	 * high-definition 7.1 audio processor".
+	 * Fixes ALSA bug#1600
+         */
+	{ .serial = 0x30411297,
+	  .name = "Shuttle XPC SD11G5 [SD11G5]",
+	  .gpio_type = 1,
+	  .i2c_adc = 1 } ,
+	 { .serial = 0,
+	   .name   = "AudigyLS [Unknown]" }
+};
+
+/* hardware definition */
+static struct snd_pcm_hardware snd_ca0106_playback_hw = {
+	.info =			SNDRV_PCM_INFO_MMAP | 
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_SYNC_START,
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
+	.rates =		(SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+				 SNDRV_PCM_RATE_192000),
+	.rate_min =		48000,
+	.rate_max =		192000,
+	.channels_min =		2,  //1,
+	.channels_max =		2,  //6,
+	.buffer_bytes_max =	((65536 - 64) * 8),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(65536 - 64),
+	.periods_min =		2,
+	.periods_max =		8,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_ca0106_capture_hw = {
+	.info =			(SNDRV_PCM_INFO_MMAP | 
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
+#if 0 /* FIXME: looks like 44.1kHz capture causes noisy output on 48kHz */
+	.rates =		(SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+				 SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000),
+	.rate_min =		44100,
+#else
+	.rates =		(SNDRV_PCM_RATE_48000 |
+				 SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000),
+	.rate_min =		48000,
+#endif /* FIXME */
+	.rate_max =		192000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	65536 - 128,
+	.period_bytes_min =	64,
+	.period_bytes_max =	32768 - 64,
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0,
+};
+
+unsigned int snd_ca0106_ptr_read(struct snd_ca0106 * emu, 
+					  unsigned int reg, 
+					  unsigned int chn)
+{
+	unsigned long flags;
+	unsigned int regptr, val;
+  
+	regptr = (reg << 16) | chn;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(regptr, emu->port + PTR);
+	val = inl(emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+	return val;
+}
+
+void snd_ca0106_ptr_write(struct snd_ca0106 *emu, 
+				   unsigned int reg, 
+				   unsigned int chn, 
+				   unsigned int data)
+{
+	unsigned int regptr;
+	unsigned long flags;
+
+	regptr = (reg << 16) | chn;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(regptr, emu->port + PTR);
+	outl(data, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+int snd_ca0106_spi_write(struct snd_ca0106 * emu,
+				   unsigned int data)
+{
+	unsigned int reset, set;
+	unsigned int reg, tmp;
+	int n, result;
+	reg = SPI;
+	if (data > 0xffff) /* Only 16bit values allowed */
+		return 1;
+	tmp = snd_ca0106_ptr_read(emu, reg, 0);
+	reset = (tmp & ~0x3ffff) | 0x20000; /* Set xxx20000 */
+	set = reset | 0x10000; /* Set xxx1xxxx */
+	snd_ca0106_ptr_write(emu, reg, 0, reset | data);
+	tmp = snd_ca0106_ptr_read(emu, reg, 0); /* write post */
+	snd_ca0106_ptr_write(emu, reg, 0, set | data);
+	result = 1;
+	/* Wait for status bit to return to 0 */
+	for (n = 0; n < 100; n++) {
+		udelay(10);
+		tmp = snd_ca0106_ptr_read(emu, reg, 0);
+		if (!(tmp & 0x10000)) {
+			result = 0;
+			break;
+		}
+	}
+	if (result) /* Timed out */
+		return 1;
+	snd_ca0106_ptr_write(emu, reg, 0, reset | data);
+	tmp = snd_ca0106_ptr_read(emu, reg, 0); /* Write post */
+	return 0;
+}
+
+/* The ADC does not support i2c read, so only write is implemented */
+int snd_ca0106_i2c_write(struct snd_ca0106 *emu,
+				u32 reg,
+				u32 value)
+{
+	u32 tmp;
+	int timeout = 0;
+	int status;
+	int retry;
+	if ((reg > 0x7f) || (value > 0x1ff)) {
+		snd_printk(KERN_ERR "i2c_write: invalid values.\n");
+		return -EINVAL;
+	}
+
+	tmp = reg << 25 | value << 16;
+	/*
+	snd_printk(KERN_DEBUG "I2C-write:reg=0x%x, value=0x%x\n", reg, value);
+	*/
+	/* Not sure what this I2C channel controls. */
+	/* snd_ca0106_ptr_write(emu, I2C_D0, 0, tmp); */
+
+	/* This controls the I2C connected to the WM8775 ADC Codec */
+	snd_ca0106_ptr_write(emu, I2C_D1, 0, tmp);
+
+	for (retry = 0; retry < 10; retry++) {
+		/* Send the data to i2c */
+		//tmp = snd_ca0106_ptr_read(emu, I2C_A, 0);
+		//tmp = tmp & ~(I2C_A_ADC_READ|I2C_A_ADC_LAST|I2C_A_ADC_START|I2C_A_ADC_ADD_MASK);
+		tmp = 0;
+		tmp = tmp | (I2C_A_ADC_LAST|I2C_A_ADC_START|I2C_A_ADC_ADD);
+		snd_ca0106_ptr_write(emu, I2C_A, 0, tmp);
+
+		/* Wait till the transaction ends */
+		while (1) {
+			status = snd_ca0106_ptr_read(emu, I2C_A, 0);
+			/*snd_printk(KERN_DEBUG "I2C:status=0x%x\n", status);*/
+			timeout++;
+			if ((status & I2C_A_ADC_START) == 0)
+				break;
+
+			if (timeout > 1000)
+				break;
+		}
+		//Read back and see if the transaction is successful
+		if ((status & I2C_A_ADC_ABORT) == 0)
+			break;
+	}
+
+	if (retry == 10) {
+		snd_printk(KERN_ERR "Writing to ADC failed!\n");
+		return -EINVAL;
+	}
+    
+    	return 0;
+}
+
+
+static void snd_ca0106_intr_enable(struct snd_ca0106 *emu, unsigned int intrenb)
+{
+	unsigned long flags;
+	unsigned int intr_enable;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	intr_enable = inl(emu->port + INTE) | intrenb;
+	outl(intr_enable, emu->port + INTE);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_ca0106_intr_disable(struct snd_ca0106 *emu, unsigned int intrenb)
+{
+	unsigned long flags;
+	unsigned int intr_enable;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	intr_enable = inl(emu->port + INTE) & ~intrenb;
+	outl(intr_enable, emu->port + INTE);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+
+static void snd_ca0106_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+	kfree(runtime->private_data);
+}
+
+static const int spi_dacd_reg[] = {
+	SPI_DACD0_REG,
+	SPI_DACD1_REG,
+	SPI_DACD2_REG,
+	0,
+	SPI_DACD4_REG,
+};
+static const int spi_dacd_bit[] = {
+	SPI_DACD0_BIT,
+	SPI_DACD1_BIT,
+	SPI_DACD2_BIT,
+	0,
+	SPI_DACD4_BIT,
+};
+
+static void restore_spdif_bits(struct snd_ca0106 *chip, int idx)
+{
+	if (chip->spdif_str_bits[idx] != chip->spdif_bits[idx]) {
+		chip->spdif_str_bits[idx] = chip->spdif_bits[idx];
+		snd_ca0106_ptr_write(chip, SPCS0 + idx, 0,
+				     chip->spdif_str_bits[idx]);
+	}
+}
+
+static int snd_ca0106_channel_dac(struct snd_ca0106_details *details,
+				  int channel_id)
+{
+	switch (channel_id) {
+	case PCM_FRONT_CHANNEL:
+		return (details->spi_dac & 0xf000) >> (4 * 3);
+	case PCM_REAR_CHANNEL:
+		return (details->spi_dac & 0x0f00) >> (4 * 2);
+	case PCM_CENTER_LFE_CHANNEL:
+		return (details->spi_dac & 0x00f0) >> (4 * 1);
+	case PCM_UNKNOWN_CHANNEL:
+		return (details->spi_dac & 0x000f) >> (4 * 0);
+	default:
+		snd_printk(KERN_DEBUG "ca0106: unknown channel_id %d\n",
+			   channel_id);
+	}
+	return 0;
+}
+
+static int snd_ca0106_pcm_power_dac(struct snd_ca0106 *chip, int channel_id,
+				    int power)
+{
+	if (chip->details->spi_dac) {
+		const int dac = snd_ca0106_channel_dac(chip->details,
+						       channel_id);
+		const int reg = spi_dacd_reg[dac];
+		const int bit = spi_dacd_bit[dac];
+
+		if (power)
+			/* Power up */
+			chip->spi_dac_reg[reg] &= ~bit;
+		else
+			/* Power down */
+			chip->spi_dac_reg[reg] |= bit;
+		return snd_ca0106_spi_write(chip, chip->spi_dac_reg[reg]);
+	}
+	return 0;
+}
+
+/* open_playback callback */
+static int snd_ca0106_pcm_open_playback_channel(struct snd_pcm_substream *substream,
+						int channel_id)
+{
+	struct snd_ca0106 *chip = snd_pcm_substream_chip(substream);
+        struct snd_ca0106_channel *channel = &(chip->playback_channels[channel_id]);
+	struct snd_ca0106_pcm *epcm;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+
+	if (epcm == NULL)
+		return -ENOMEM;
+	epcm->emu = chip;
+	epcm->substream = substream;
+        epcm->channel_id=channel_id;
+  
+	runtime->private_data = epcm;
+	runtime->private_free = snd_ca0106_pcm_free_substream;
+  
+	runtime->hw = snd_ca0106_playback_hw;
+
+        channel->emu = chip;
+        channel->number = channel_id;
+
+	channel->use = 1;
+	/*
+	printk(KERN_DEBUG "open:channel_id=%d, chip=%p, channel=%p\n",
+	       channel_id, chip, channel);
+	*/
+        //channel->interrupt = snd_ca0106_pcm_channel_interrupt;
+	channel->epcm = epcm;
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+                return err;
+	if ((err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64)) < 0)
+                return err;
+	snd_pcm_set_sync(substream);
+
+	/* Front channel dac should already be on */
+	if (channel_id != PCM_FRONT_CHANNEL) {
+		err = snd_ca0106_pcm_power_dac(chip, channel_id, 1);
+		if (err < 0)
+			return err;
+	}
+
+	restore_spdif_bits(chip, channel_id);
+
+	return 0;
+}
+
+/* close callback */
+static int snd_ca0106_pcm_close_playback(struct snd_pcm_substream *substream)
+{
+	struct snd_ca0106 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+        struct snd_ca0106_pcm *epcm = runtime->private_data;
+	chip->playback_channels[epcm->channel_id].use = 0;
+
+	restore_spdif_bits(chip, epcm->channel_id);
+
+	/* Front channel dac should stay on */
+	if (epcm->channel_id != PCM_FRONT_CHANNEL) {
+		int err;
+		err = snd_ca0106_pcm_power_dac(chip, epcm->channel_id, 0);
+		if (err < 0)
+			return err;
+	}
+
+	/* FIXME: maybe zero others */
+	return 0;
+}
+
+static int snd_ca0106_pcm_open_playback_front(struct snd_pcm_substream *substream)
+{
+	return snd_ca0106_pcm_open_playback_channel(substream, PCM_FRONT_CHANNEL);
+}
+
+static int snd_ca0106_pcm_open_playback_center_lfe(struct snd_pcm_substream *substream)
+{
+	return snd_ca0106_pcm_open_playback_channel(substream, PCM_CENTER_LFE_CHANNEL);
+}
+
+static int snd_ca0106_pcm_open_playback_unknown(struct snd_pcm_substream *substream)
+{
+	return snd_ca0106_pcm_open_playback_channel(substream, PCM_UNKNOWN_CHANNEL);
+}
+
+static int snd_ca0106_pcm_open_playback_rear(struct snd_pcm_substream *substream)
+{
+	return snd_ca0106_pcm_open_playback_channel(substream, PCM_REAR_CHANNEL);
+}
+
+/* open_capture callback */
+static int snd_ca0106_pcm_open_capture_channel(struct snd_pcm_substream *substream,
+					       int channel_id)
+{
+	struct snd_ca0106 *chip = snd_pcm_substream_chip(substream);
+        struct snd_ca0106_channel *channel = &(chip->capture_channels[channel_id]);
+	struct snd_ca0106_pcm *epcm;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+	if (epcm == NULL) {
+		snd_printk(KERN_ERR "open_capture_channel: failed epcm alloc\n");
+		return -ENOMEM;
+        }
+	epcm->emu = chip;
+	epcm->substream = substream;
+        epcm->channel_id=channel_id;
+  
+	runtime->private_data = epcm;
+	runtime->private_free = snd_ca0106_pcm_free_substream;
+  
+	runtime->hw = snd_ca0106_capture_hw;
+
+        channel->emu = chip;
+        channel->number = channel_id;
+
+	channel->use = 1;
+	/*
+        printk(KERN_DEBUG "open:channel_id=%d, chip=%p, channel=%p\n",
+	       channel_id, chip, channel);
+	*/
+        //channel->interrupt = snd_ca0106_pcm_channel_interrupt;
+        channel->epcm = epcm;
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+                return err;
+	//snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hw_constraints_capture_period_sizes);
+	if ((err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64)) < 0)
+                return err;
+	return 0;
+}
+
+/* close callback */
+static int snd_ca0106_pcm_close_capture(struct snd_pcm_substream *substream)
+{
+	struct snd_ca0106 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+        struct snd_ca0106_pcm *epcm = runtime->private_data;
+	chip->capture_channels[epcm->channel_id].use = 0;
+	/* FIXME: maybe zero others */
+	return 0;
+}
+
+static int snd_ca0106_pcm_open_0_capture(struct snd_pcm_substream *substream)
+{
+	return snd_ca0106_pcm_open_capture_channel(substream, 0);
+}
+
+static int snd_ca0106_pcm_open_1_capture(struct snd_pcm_substream *substream)
+{
+	return snd_ca0106_pcm_open_capture_channel(substream, 1);
+}
+
+static int snd_ca0106_pcm_open_2_capture(struct snd_pcm_substream *substream)
+{
+	return snd_ca0106_pcm_open_capture_channel(substream, 2);
+}
+
+static int snd_ca0106_pcm_open_3_capture(struct snd_pcm_substream *substream)
+{
+	return snd_ca0106_pcm_open_capture_channel(substream, 3);
+}
+
+/* hw_params callback */
+static int snd_ca0106_pcm_hw_params_playback(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+/* hw_free callback */
+static int snd_ca0106_pcm_hw_free_playback(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+/* hw_params callback */
+static int snd_ca0106_pcm_hw_params_capture(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+/* hw_free callback */
+static int snd_ca0106_pcm_hw_free_capture(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+/* prepare playback callback */
+static int snd_ca0106_pcm_prepare_playback(struct snd_pcm_substream *substream)
+{
+	struct snd_ca0106 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ca0106_pcm *epcm = runtime->private_data;
+	int channel = epcm->channel_id;
+	u32 *table_base = (u32 *)(emu->buffer.area+(8*16*channel));
+	u32 period_size_bytes = frames_to_bytes(runtime, runtime->period_size);
+	u32 hcfg_mask = HCFG_PLAYBACK_S32_LE;
+	u32 hcfg_set = 0x00000000;
+	u32 hcfg;
+	u32 reg40_mask = 0x30000 << (channel<<1);
+	u32 reg40_set = 0;
+	u32 reg40;
+	/* FIXME: Depending on mixer selection of SPDIF out or not, select the spdif rate or the DAC rate. */
+	u32 reg71_mask = 0x03030000 ; /* Global. Set SPDIF rate. We only support 44100 to spdif, not to DAC. */
+	u32 reg71_set = 0;
+	u32 reg71;
+	int i;
+	
+#if 0 /* debug */
+	snd_printk(KERN_DEBUG
+		   "prepare:channel_number=%d, rate=%d, format=0x%x, "
+		   "channels=%d, buffer_size=%ld, period_size=%ld, "
+		   "periods=%u, frames_to_bytes=%d\n",
+		   channel, runtime->rate, runtime->format,
+		   runtime->channels, runtime->buffer_size,
+		   runtime->period_size, runtime->periods,
+		   frames_to_bytes(runtime, 1));
+	snd_printk(KERN_DEBUG "dma_addr=%x, dma_area=%p, table_base=%p\n",
+		   runtime->dma_addr, runtime->dma_area, table_base);
+	snd_printk(KERN_DEBUG "dma_addr=%x, dma_area=%p, dma_bytes(size)=%x\n",
+		   emu->buffer.addr, emu->buffer.area, emu->buffer.bytes);
+#endif /* debug */
+	/* Rate can be set per channel. */
+	/* reg40 control host to fifo */
+	/* reg71 controls DAC rate. */
+	switch (runtime->rate) {
+	case 44100:
+		reg40_set = 0x10000 << (channel<<1);
+		reg71_set = 0x01010000; 
+		break;
+        case 48000:
+		reg40_set = 0;
+		reg71_set = 0; 
+		break;
+	case 96000:
+		reg40_set = 0x20000 << (channel<<1);
+		reg71_set = 0x02020000; 
+		break;
+	case 192000:
+		reg40_set = 0x30000 << (channel<<1);
+		reg71_set = 0x03030000; 
+		break;
+	default:
+		reg40_set = 0;
+		reg71_set = 0; 
+		break;
+	}
+	/* Format is a global setting */
+	/* FIXME: Only let the first channel accessed set this. */
+	switch (runtime->format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		hcfg_set = 0;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		hcfg_set = HCFG_PLAYBACK_S32_LE;
+		break;
+	default:
+		hcfg_set = 0;
+		break;
+	}
+	hcfg = inl(emu->port + HCFG) ;
+	hcfg = (hcfg & ~hcfg_mask) | hcfg_set;
+	outl(hcfg, emu->port + HCFG);
+	reg40 = snd_ca0106_ptr_read(emu, 0x40, 0);
+	reg40 = (reg40 & ~reg40_mask) | reg40_set;
+	snd_ca0106_ptr_write(emu, 0x40, 0, reg40);
+	reg71 = snd_ca0106_ptr_read(emu, 0x71, 0);
+	reg71 = (reg71 & ~reg71_mask) | reg71_set;
+	snd_ca0106_ptr_write(emu, 0x71, 0, reg71);
+
+	/* FIXME: Check emu->buffer.size before actually writing to it. */
+        for(i=0; i < runtime->periods; i++) {
+		table_base[i*2] = runtime->dma_addr + (i * period_size_bytes);
+		table_base[i*2+1] = period_size_bytes << 16;
+	}
+ 
+	snd_ca0106_ptr_write(emu, PLAYBACK_LIST_ADDR, channel, emu->buffer.addr+(8*16*channel));
+	snd_ca0106_ptr_write(emu, PLAYBACK_LIST_SIZE, channel, (runtime->periods - 1) << 19);
+	snd_ca0106_ptr_write(emu, PLAYBACK_LIST_PTR, channel, 0);
+	snd_ca0106_ptr_write(emu, PLAYBACK_DMA_ADDR, channel, runtime->dma_addr);
+	snd_ca0106_ptr_write(emu, PLAYBACK_PERIOD_SIZE, channel, frames_to_bytes(runtime, runtime->period_size)<<16); // buffer size in bytes
+	/* FIXME  test what 0 bytes does. */
+	snd_ca0106_ptr_write(emu, PLAYBACK_PERIOD_SIZE, channel, 0); // buffer size in bytes
+	snd_ca0106_ptr_write(emu, PLAYBACK_POINTER, channel, 0);
+	snd_ca0106_ptr_write(emu, 0x07, channel, 0x0);
+	snd_ca0106_ptr_write(emu, 0x08, channel, 0);
+        snd_ca0106_ptr_write(emu, PLAYBACK_MUTE, 0x0, 0x0); /* Unmute output */
+#if 0
+	snd_ca0106_ptr_write(emu, SPCS0, 0,
+			       SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+			       SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+			       SPCS_GENERATIONSTATUS | 0x00001200 |
+			       0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT );
+#endif
+
+	return 0;
+}
+
+/* prepare capture callback */
+static int snd_ca0106_pcm_prepare_capture(struct snd_pcm_substream *substream)
+{
+	struct snd_ca0106 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ca0106_pcm *epcm = runtime->private_data;
+	int channel = epcm->channel_id;
+	u32 hcfg_mask = HCFG_CAPTURE_S32_LE;
+	u32 hcfg_set = 0x00000000;
+	u32 hcfg;
+	u32 over_sampling=0x2;
+	u32 reg71_mask = 0x0000c000 ; /* Global. Set ADC rate. */
+	u32 reg71_set = 0;
+	u32 reg71;
+	
+#if 0 /* debug */
+	snd_printk(KERN_DEBUG
+		   "prepare:channel_number=%d, rate=%d, format=0x%x, "
+		   "channels=%d, buffer_size=%ld, period_size=%ld, "
+		   "periods=%u, frames_to_bytes=%d\n",
+		   channel, runtime->rate, runtime->format,
+		   runtime->channels, runtime->buffer_size,
+		   runtime->period_size, runtime->periods,
+		   frames_to_bytes(runtime, 1));
+        snd_printk(KERN_DEBUG "dma_addr=%x, dma_area=%p, table_base=%p\n",
+		   runtime->dma_addr, runtime->dma_area, table_base);
+	snd_printk(KERN_DEBUG "dma_addr=%x, dma_area=%p, dma_bytes(size)=%x\n",
+		   emu->buffer.addr, emu->buffer.area, emu->buffer.bytes);
+#endif /* debug */
+	/* reg71 controls ADC rate. */
+	switch (runtime->rate) {
+	case 44100:
+		reg71_set = 0x00004000;
+		break;
+        case 48000:
+		reg71_set = 0; 
+		break;
+	case 96000:
+		reg71_set = 0x00008000;
+		over_sampling=0xa;
+		break;
+	case 192000:
+		reg71_set = 0x0000c000; 
+		over_sampling=0xa;
+		break;
+	default:
+		reg71_set = 0; 
+		break;
+	}
+	/* Format is a global setting */
+	/* FIXME: Only let the first channel accessed set this. */
+	switch (runtime->format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		hcfg_set = 0;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		hcfg_set = HCFG_CAPTURE_S32_LE;
+		break;
+	default:
+		hcfg_set = 0;
+		break;
+	}
+	hcfg = inl(emu->port + HCFG) ;
+	hcfg = (hcfg & ~hcfg_mask) | hcfg_set;
+	outl(hcfg, emu->port + HCFG);
+	reg71 = snd_ca0106_ptr_read(emu, 0x71, 0);
+	reg71 = (reg71 & ~reg71_mask) | reg71_set;
+	snd_ca0106_ptr_write(emu, 0x71, 0, reg71);
+        if (emu->details->i2c_adc == 1) { /* The SB0410 and SB0413 use I2C to control ADC. */
+	        snd_ca0106_i2c_write(emu, ADC_MASTER, over_sampling); /* Adjust the over sampler to better suit the capture rate. */
+	}
+
+
+	/*
+	printk(KERN_DEBUG
+	       "prepare:channel_number=%d, rate=%d, format=0x%x, channels=%d, "
+	       "buffer_size=%ld, period_size=%ld, frames_to_bytes=%d\n",
+	       channel, runtime->rate, runtime->format, runtime->channels,
+	       runtime->buffer_size, runtime->period_size,
+	       frames_to_bytes(runtime, 1));
+	*/
+	snd_ca0106_ptr_write(emu, 0x13, channel, 0);
+	snd_ca0106_ptr_write(emu, CAPTURE_DMA_ADDR, channel, runtime->dma_addr);
+	snd_ca0106_ptr_write(emu, CAPTURE_BUFFER_SIZE, channel, frames_to_bytes(runtime, runtime->buffer_size)<<16); // buffer size in bytes
+	snd_ca0106_ptr_write(emu, CAPTURE_POINTER, channel, 0);
+
+	return 0;
+}
+
+/* trigger_playback callback */
+static int snd_ca0106_pcm_trigger_playback(struct snd_pcm_substream *substream,
+				    int cmd)
+{
+	struct snd_ca0106 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime;
+	struct snd_ca0106_pcm *epcm;
+	int channel;
+	int result = 0;
+        struct snd_pcm_substream *s;
+	u32 basic = 0;
+	u32 extended = 0;
+	u32 bits;
+	int running = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	default:
+		running = 0;
+		break;
+	}
+        snd_pcm_group_for_each_entry(s, substream) {
+		if (snd_pcm_substream_chip(s) != emu ||
+		    s->stream != SNDRV_PCM_STREAM_PLAYBACK)
+			continue;
+		runtime = s->runtime;
+		epcm = runtime->private_data;
+		channel = epcm->channel_id;
+		/* snd_printk(KERN_DEBUG "channel=%d\n", channel); */
+		epcm->running = running;
+		basic |= (0x1 << channel);
+		extended |= (0x10 << channel);
+                snd_pcm_trigger_done(s, substream);
+        }
+	/* snd_printk(KERN_DEBUG "basic=0x%x, extended=0x%x\n",basic, extended); */
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		bits = snd_ca0106_ptr_read(emu, EXTENDED_INT_MASK, 0);
+		bits |= extended;
+		snd_ca0106_ptr_write(emu, EXTENDED_INT_MASK, 0, bits);
+		bits = snd_ca0106_ptr_read(emu, BASIC_INTERRUPT, 0);
+		bits |= basic;
+		snd_ca0106_ptr_write(emu, BASIC_INTERRUPT, 0, bits);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		bits = snd_ca0106_ptr_read(emu, BASIC_INTERRUPT, 0);
+		bits &= ~basic;
+		snd_ca0106_ptr_write(emu, BASIC_INTERRUPT, 0, bits);
+		bits = snd_ca0106_ptr_read(emu, EXTENDED_INT_MASK, 0);
+		bits &= ~extended;
+		snd_ca0106_ptr_write(emu, EXTENDED_INT_MASK, 0, bits);
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	return result;
+}
+
+/* trigger_capture callback */
+static int snd_ca0106_pcm_trigger_capture(struct snd_pcm_substream *substream,
+				    int cmd)
+{
+	struct snd_ca0106 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ca0106_pcm *epcm = runtime->private_data;
+	int channel = epcm->channel_id;
+	int result = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_ca0106_ptr_write(emu, EXTENDED_INT_MASK, 0, snd_ca0106_ptr_read(emu, EXTENDED_INT_MASK, 0) | (0x110000<<channel));
+		snd_ca0106_ptr_write(emu, BASIC_INTERRUPT, 0, snd_ca0106_ptr_read(emu, BASIC_INTERRUPT, 0)|(0x100<<channel));
+		epcm->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_ca0106_ptr_write(emu, BASIC_INTERRUPT, 0, snd_ca0106_ptr_read(emu, BASIC_INTERRUPT, 0) & ~(0x100<<channel));
+		snd_ca0106_ptr_write(emu, EXTENDED_INT_MASK, 0, snd_ca0106_ptr_read(emu, EXTENDED_INT_MASK, 0) & ~(0x110000<<channel));
+		epcm->running = 0;
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	return result;
+}
+
+/* pointer_playback callback */
+static snd_pcm_uframes_t
+snd_ca0106_pcm_pointer_playback(struct snd_pcm_substream *substream)
+{
+	struct snd_ca0106 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ca0106_pcm *epcm = runtime->private_data;
+	unsigned int ptr, prev_ptr;
+	int channel = epcm->channel_id;
+	int timeout = 10;
+
+	if (!epcm->running)
+		return 0;
+
+	prev_ptr = -1;
+	do {
+		ptr = snd_ca0106_ptr_read(emu, PLAYBACK_LIST_PTR, channel);
+		ptr = (ptr >> 3) * runtime->period_size;
+		ptr += bytes_to_frames(runtime,
+			snd_ca0106_ptr_read(emu, PLAYBACK_POINTER, channel));
+		if (ptr >= runtime->buffer_size)
+			ptr -= runtime->buffer_size;
+		if (prev_ptr == ptr)
+			return ptr;
+		prev_ptr = ptr;
+	} while (--timeout);
+	snd_printk(KERN_WARNING "ca0106: unstable DMA pointer!\n");
+	return 0;
+}
+
+/* pointer_capture callback */
+static snd_pcm_uframes_t
+snd_ca0106_pcm_pointer_capture(struct snd_pcm_substream *substream)
+{
+	struct snd_ca0106 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ca0106_pcm *epcm = runtime->private_data;
+	snd_pcm_uframes_t ptr, ptr1, ptr2 = 0;
+	int channel = channel=epcm->channel_id;
+
+	if (!epcm->running)
+		return 0;
+
+	ptr1 = snd_ca0106_ptr_read(emu, CAPTURE_POINTER, channel);
+	ptr2 = bytes_to_frames(runtime, ptr1);
+	ptr=ptr2;
+        if (ptr >= runtime->buffer_size)
+		ptr -= runtime->buffer_size;
+	/*
+	printk(KERN_DEBUG "ptr1 = 0x%lx, ptr2=0x%lx, ptr=0x%lx, "
+	       "buffer_size = 0x%x, period_size = 0x%x, bits=%d, rate=%d\n",
+	       ptr1, ptr2, ptr, (int)runtime->buffer_size,
+	       (int)runtime->period_size, (int)runtime->frame_bits,
+	       (int)runtime->rate);
+	*/
+	return ptr;
+}
+
+/* operators */
+static struct snd_pcm_ops snd_ca0106_playback_front_ops = {
+	.open =        snd_ca0106_pcm_open_playback_front,
+	.close =       snd_ca0106_pcm_close_playback,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_ca0106_pcm_hw_params_playback,
+	.hw_free =     snd_ca0106_pcm_hw_free_playback,
+	.prepare =     snd_ca0106_pcm_prepare_playback,
+	.trigger =     snd_ca0106_pcm_trigger_playback,
+	.pointer =     snd_ca0106_pcm_pointer_playback,
+};
+
+static struct snd_pcm_ops snd_ca0106_capture_0_ops = {
+	.open =        snd_ca0106_pcm_open_0_capture,
+	.close =       snd_ca0106_pcm_close_capture,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_ca0106_pcm_hw_params_capture,
+	.hw_free =     snd_ca0106_pcm_hw_free_capture,
+	.prepare =     snd_ca0106_pcm_prepare_capture,
+	.trigger =     snd_ca0106_pcm_trigger_capture,
+	.pointer =     snd_ca0106_pcm_pointer_capture,
+};
+
+static struct snd_pcm_ops snd_ca0106_capture_1_ops = {
+	.open =        snd_ca0106_pcm_open_1_capture,
+	.close =       snd_ca0106_pcm_close_capture,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_ca0106_pcm_hw_params_capture,
+	.hw_free =     snd_ca0106_pcm_hw_free_capture,
+	.prepare =     snd_ca0106_pcm_prepare_capture,
+	.trigger =     snd_ca0106_pcm_trigger_capture,
+	.pointer =     snd_ca0106_pcm_pointer_capture,
+};
+
+static struct snd_pcm_ops snd_ca0106_capture_2_ops = {
+	.open =        snd_ca0106_pcm_open_2_capture,
+	.close =       snd_ca0106_pcm_close_capture,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_ca0106_pcm_hw_params_capture,
+	.hw_free =     snd_ca0106_pcm_hw_free_capture,
+	.prepare =     snd_ca0106_pcm_prepare_capture,
+	.trigger =     snd_ca0106_pcm_trigger_capture,
+	.pointer =     snd_ca0106_pcm_pointer_capture,
+};
+
+static struct snd_pcm_ops snd_ca0106_capture_3_ops = {
+	.open =        snd_ca0106_pcm_open_3_capture,
+	.close =       snd_ca0106_pcm_close_capture,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_ca0106_pcm_hw_params_capture,
+	.hw_free =     snd_ca0106_pcm_hw_free_capture,
+	.prepare =     snd_ca0106_pcm_prepare_capture,
+	.trigger =     snd_ca0106_pcm_trigger_capture,
+	.pointer =     snd_ca0106_pcm_pointer_capture,
+};
+
+static struct snd_pcm_ops snd_ca0106_playback_center_lfe_ops = {
+        .open =         snd_ca0106_pcm_open_playback_center_lfe,
+        .close =        snd_ca0106_pcm_close_playback,
+        .ioctl =        snd_pcm_lib_ioctl,
+        .hw_params =    snd_ca0106_pcm_hw_params_playback,
+        .hw_free =      snd_ca0106_pcm_hw_free_playback,
+        .prepare =      snd_ca0106_pcm_prepare_playback,     
+        .trigger =      snd_ca0106_pcm_trigger_playback,  
+        .pointer =      snd_ca0106_pcm_pointer_playback, 
+};
+
+static struct snd_pcm_ops snd_ca0106_playback_unknown_ops = {
+        .open =         snd_ca0106_pcm_open_playback_unknown,
+        .close =        snd_ca0106_pcm_close_playback,
+        .ioctl =        snd_pcm_lib_ioctl,
+        .hw_params =    snd_ca0106_pcm_hw_params_playback,
+        .hw_free =      snd_ca0106_pcm_hw_free_playback,
+        .prepare =      snd_ca0106_pcm_prepare_playback,     
+        .trigger =      snd_ca0106_pcm_trigger_playback,  
+        .pointer =      snd_ca0106_pcm_pointer_playback, 
+};
+
+static struct snd_pcm_ops snd_ca0106_playback_rear_ops = {
+        .open =         snd_ca0106_pcm_open_playback_rear,
+        .close =        snd_ca0106_pcm_close_playback,
+        .ioctl =        snd_pcm_lib_ioctl,
+        .hw_params =    snd_ca0106_pcm_hw_params_playback,
+		.hw_free =      snd_ca0106_pcm_hw_free_playback,
+        .prepare =      snd_ca0106_pcm_prepare_playback,     
+        .trigger =      snd_ca0106_pcm_trigger_playback,  
+        .pointer =      snd_ca0106_pcm_pointer_playback, 
+};
+
+
+static unsigned short snd_ca0106_ac97_read(struct snd_ac97 *ac97,
+					     unsigned short reg)
+{
+	struct snd_ca0106 *emu = ac97->private_data;
+	unsigned long flags;
+	unsigned short val;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outb(reg, emu->port + AC97ADDRESS);
+	val = inw(emu->port + AC97DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+	return val;
+}
+
+static void snd_ca0106_ac97_write(struct snd_ac97 *ac97,
+				    unsigned short reg, unsigned short val)
+{
+	struct snd_ca0106 *emu = ac97->private_data;
+	unsigned long flags;
+  
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outb(reg, emu->port + AC97ADDRESS);
+	outw(val, emu->port + AC97DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static int snd_ca0106_ac97(struct snd_ca0106 *chip)
+{
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_ca0106_ac97_write,
+		.read = snd_ca0106_ac97_read,
+	};
+  
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus)) < 0)
+		return err;
+	pbus->no_vra = 1; /* we don't need VRA */
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.scaps = AC97_SCAP_NO_SPDIF;
+	return snd_ac97_mixer(pbus, &ac97, &chip->ac97);
+}
+
+static void ca0106_stop_chip(struct snd_ca0106 *chip);
+
+static int snd_ca0106_free(struct snd_ca0106 *chip)
+{
+	if (chip->res_port != NULL) {
+		/* avoid access to already used hardware */
+		ca0106_stop_chip(chip);
+	}
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	// release the data
+#if 1
+	if (chip->buffer.area)
+		snd_dma_free_pages(&chip->buffer);
+#endif
+
+	// release the i/o port
+	release_and_free_resource(chip->res_port);
+
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_ca0106_dev_free(struct snd_device *device)
+{
+	struct snd_ca0106 *chip = device->device_data;
+	return snd_ca0106_free(chip);
+}
+
+static irqreturn_t snd_ca0106_interrupt(int irq, void *dev_id)
+{
+	unsigned int status;
+
+	struct snd_ca0106 *chip = dev_id;
+	int i;
+	int mask;
+        unsigned int stat76;
+	struct snd_ca0106_channel *pchannel;
+
+	status = inl(chip->port + IPR);
+	if (! status)
+		return IRQ_NONE;
+
+        stat76 = snd_ca0106_ptr_read(chip, EXTENDED_INT, 0);
+	/*
+	snd_printk(KERN_DEBUG "interrupt status = 0x%08x, stat76=0x%08x\n",
+		   status, stat76);
+	snd_printk(KERN_DEBUG "ptr=0x%08x\n",
+		   snd_ca0106_ptr_read(chip, PLAYBACK_POINTER, 0));
+	*/
+        mask = 0x11; /* 0x1 for one half, 0x10 for the other half period. */
+	for(i = 0; i < 4; i++) {
+		pchannel = &(chip->playback_channels[i]);
+		if (stat76 & mask) {
+/* FIXME: Select the correct substream for period elapsed */
+			if(pchannel->use) {
+				snd_pcm_period_elapsed(pchannel->epcm->substream);
+				//printk(KERN_INFO "interrupt [%d] used\n", i);
+                        }
+		}
+	        //printk(KERN_INFO "channel=%p\n",pchannel);
+	        //printk(KERN_INFO "interrupt stat76[%d] = %08x, use=%d, channel=%d\n", i, stat76, pchannel->use, pchannel->number);
+		mask <<= 1;
+	}
+        mask = 0x110000; /* 0x1 for one half, 0x10 for the other half period. */
+	for(i = 0; i < 4; i++) {
+		pchannel = &(chip->capture_channels[i]);
+		if (stat76 & mask) {
+/* FIXME: Select the correct substream for period elapsed */
+			if(pchannel->use) {
+				snd_pcm_period_elapsed(pchannel->epcm->substream);
+				//printk(KERN_INFO "interrupt [%d] used\n", i);
+                        }
+		}
+	        //printk(KERN_INFO "channel=%p\n",pchannel);
+	        //printk(KERN_INFO "interrupt stat76[%d] = %08x, use=%d, channel=%d\n", i, stat76, pchannel->use, pchannel->number);
+		mask <<= 1;
+	}
+
+        snd_ca0106_ptr_write(chip, EXTENDED_INT, 0, stat76);
+
+	if (chip->midi.dev_id &&
+	    (status & (chip->midi.ipr_tx|chip->midi.ipr_rx))) {
+		if (chip->midi.interrupt)
+			chip->midi.interrupt(&chip->midi, status);
+		else
+			chip->midi.interrupt_disable(&chip->midi, chip->midi.tx_enable | chip->midi.rx_enable);
+	}
+
+	// acknowledge the interrupt if necessary
+	outl(status, chip->port+IPR);
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit snd_ca0106_pcm(struct snd_ca0106 *emu, int device)
+{
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+	int err;
+  
+	err = snd_pcm_new(emu->card, "ca0106", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+  
+	pcm->private_data = emu;
+
+	switch (device) {
+	case 0:
+	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_front_ops);
+	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_0_ops);
+          break;
+	case 1:
+	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_rear_ops);
+	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_1_ops);
+          break;
+	case 2:
+	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_center_lfe_ops);
+	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_2_ops);
+          break;
+	case 3:
+	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_unknown_ops);
+	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_3_ops);
+          break;
+        }
+
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "CA0106");
+
+	for(substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; 
+	    substream; 
+	    substream = substream->next) {
+		if ((err = snd_pcm_lib_preallocate_pages(substream, 
+							 SNDRV_DMA_TYPE_DEV, 
+							 snd_dma_pci_data(emu->pci), 
+							 64*1024, 64*1024)) < 0) /* FIXME: 32*1024 for sound buffer, between 32and64 for Periods table. */
+			return err;
+	}
+
+	for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; 
+	      substream; 
+	      substream = substream->next) {
+ 		if ((err = snd_pcm_lib_preallocate_pages(substream, 
+	                                           SNDRV_DMA_TYPE_DEV, 
+	                                           snd_dma_pci_data(emu->pci), 
+	                                           64*1024, 64*1024)) < 0)
+			return err;
+	}
+  
+	emu->pcm[device] = pcm;
+  
+	return 0;
+}
+
+#define SPI_REG(reg, value)	(((reg) << SPI_REG_SHIFT) | (value))
+static unsigned int spi_dac_init[] = {
+	SPI_REG(SPI_LDA1_REG,	SPI_DA_BIT_0dB), /* 0dB dig. attenuation */
+	SPI_REG(SPI_RDA1_REG,	SPI_DA_BIT_0dB),
+	SPI_REG(SPI_PL_REG,	SPI_PL_BIT_L_L | SPI_PL_BIT_R_R | SPI_IZD_BIT),
+	SPI_REG(SPI_FMT_REG,	SPI_FMT_BIT_I2S | SPI_IWL_BIT_24),
+	SPI_REG(SPI_LDA2_REG,	SPI_DA_BIT_0dB),
+	SPI_REG(SPI_RDA2_REG,	SPI_DA_BIT_0dB),
+	SPI_REG(SPI_LDA3_REG,	SPI_DA_BIT_0dB),
+	SPI_REG(SPI_RDA3_REG,	SPI_DA_BIT_0dB),
+	SPI_REG(SPI_MASTDA_REG,	SPI_DA_BIT_0dB),
+	SPI_REG(9,		0x00),
+	SPI_REG(SPI_MS_REG,	SPI_DACD0_BIT | SPI_DACD1_BIT | SPI_DACD2_BIT),
+	SPI_REG(12,		0x00),
+	SPI_REG(SPI_LDA4_REG,	SPI_DA_BIT_0dB),
+	SPI_REG(SPI_RDA4_REG,	SPI_DA_BIT_0dB | SPI_DA_BIT_UPDATE),
+	SPI_REG(SPI_DACD4_REG,	SPI_DACD4_BIT),
+};
+
+static unsigned int i2c_adc_init[][2] = {
+	{ 0x17, 0x00 }, /* Reset */
+	{ 0x07, 0x00 }, /* Timeout */
+	{ 0x0b, 0x22 },  /* Interface control */
+	{ 0x0c, 0x22 },  /* Master mode control */
+	{ 0x0d, 0x08 },  /* Powerdown control */
+	{ 0x0e, 0xcf },  /* Attenuation Left  0x01 = -103dB, 0xff = 24dB */
+	{ 0x0f, 0xcf },  /* Attenuation Right 0.5dB steps */
+	{ 0x10, 0x7b },  /* ALC Control 1 */
+	{ 0x11, 0x00 },  /* ALC Control 2 */
+	{ 0x12, 0x32 },  /* ALC Control 3 */
+	{ 0x13, 0x00 },  /* Noise gate control */
+	{ 0x14, 0xa6 },  /* Limiter control */
+	{ 0x15, ADC_MUX_LINEIN },  /* ADC Mixer control */
+};
+
+static void ca0106_init_chip(struct snd_ca0106 *chip, int resume)
+{
+	int ch;
+	unsigned int def_bits;
+
+	outl(0, chip->port + INTE);
+
+	/*
+	 *  Init to 0x02109204 :
+	 *  Clock accuracy    = 0     (1000ppm)
+	 *  Sample Rate       = 2     (48kHz)
+	 *  Audio Channel     = 1     (Left of 2)
+	 *  Source Number     = 0     (Unspecified)
+	 *  Generation Status = 1     (Original for Cat Code 12)
+	 *  Cat Code          = 12    (Digital Signal Mixer)
+	 *  Mode              = 0     (Mode 0)
+	 *  Emphasis          = 0     (None)
+	 *  CP                = 1     (Copyright unasserted)
+	 *  AN                = 0     (Audio data)
+	 *  P                 = 0     (Consumer)
+	 */
+	def_bits =
+		SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+		SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+		SPCS_GENERATIONSTATUS | 0x00001200 |
+		0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT;
+	if (!resume) {
+		chip->spdif_str_bits[0] = chip->spdif_bits[0] = def_bits;
+		chip->spdif_str_bits[1] = chip->spdif_bits[1] = def_bits;
+		chip->spdif_str_bits[2] = chip->spdif_bits[2] = def_bits;
+		chip->spdif_str_bits[3] = chip->spdif_bits[3] = def_bits;
+	}
+	/* Only SPCS1 has been tested */
+	snd_ca0106_ptr_write(chip, SPCS1, 0, chip->spdif_str_bits[1]);
+	snd_ca0106_ptr_write(chip, SPCS0, 0, chip->spdif_str_bits[0]);
+	snd_ca0106_ptr_write(chip, SPCS2, 0, chip->spdif_str_bits[2]);
+	snd_ca0106_ptr_write(chip, SPCS3, 0, chip->spdif_str_bits[3]);
+
+        snd_ca0106_ptr_write(chip, PLAYBACK_MUTE, 0, 0x00fc0000);
+        snd_ca0106_ptr_write(chip, CAPTURE_MUTE, 0, 0x00fc0000);
+
+        /* Write 0x8000 to AC97_REC_GAIN to mute it. */
+        outb(AC97_REC_GAIN, chip->port + AC97ADDRESS);
+        outw(0x8000, chip->port + AC97DATA);
+#if 0 /* FIXME: what are these? */
+	snd_ca0106_ptr_write(chip, SPCS0, 0, 0x2108006);
+	snd_ca0106_ptr_write(chip, 0x42, 0, 0x2108006);
+	snd_ca0106_ptr_write(chip, 0x43, 0, 0x2108006);
+	snd_ca0106_ptr_write(chip, 0x44, 0, 0x2108006);
+#endif
+
+	/* OSS drivers set this. */
+	/* snd_ca0106_ptr_write(chip, SPDIF_SELECT2, 0, 0xf0f003f); */
+
+	/* Analog or Digital output */
+	snd_ca0106_ptr_write(chip, SPDIF_SELECT1, 0, 0xf);
+	/* 0x0b000000 for digital, 0x000b0000 for analog, from win2000 drivers.
+	 * Use 0x000f0000 for surround71
+	 */
+	snd_ca0106_ptr_write(chip, SPDIF_SELECT2, 0, 0x000f0000);
+
+	chip->spdif_enable = 0; /* Set digital SPDIF output off */
+	/*snd_ca0106_ptr_write(chip, 0x45, 0, 0);*/ /* Analogue out */
+	/*snd_ca0106_ptr_write(chip, 0x45, 0, 0xf00);*/ /* Digital out */
+
+	/* goes to 0x40c80000 when doing SPDIF IN/OUT */
+	snd_ca0106_ptr_write(chip, CAPTURE_CONTROL, 0, 0x40c81000);
+	/* (Mute) CAPTURE feedback into PLAYBACK volume.
+	 * Only lower 16 bits matter.
+	 */
+	snd_ca0106_ptr_write(chip, CAPTURE_CONTROL, 1, 0xffffffff);
+	/* SPDIF IN Volume */
+	snd_ca0106_ptr_write(chip, CAPTURE_CONTROL, 2, 0x30300000);
+	/* SPDIF IN Volume, 0x70 = (vol & 0x3f) | 0x40 */
+	snd_ca0106_ptr_write(chip, CAPTURE_CONTROL, 3, 0x00700000);
+
+	snd_ca0106_ptr_write(chip, PLAYBACK_ROUTING1, 0, 0x32765410);
+	snd_ca0106_ptr_write(chip, PLAYBACK_ROUTING2, 0, 0x76767676);
+	snd_ca0106_ptr_write(chip, CAPTURE_ROUTING1, 0, 0x32765410);
+	snd_ca0106_ptr_write(chip, CAPTURE_ROUTING2, 0, 0x76767676);
+
+	for (ch = 0; ch < 4; ch++) {
+		/* Only high 16 bits matter */
+		snd_ca0106_ptr_write(chip, CAPTURE_VOLUME1, ch, 0x30303030);
+		snd_ca0106_ptr_write(chip, CAPTURE_VOLUME2, ch, 0x30303030);
+#if 0 /* Mute */
+		snd_ca0106_ptr_write(chip, PLAYBACK_VOLUME1, ch, 0x40404040);
+		snd_ca0106_ptr_write(chip, PLAYBACK_VOLUME2, ch, 0x40404040);
+		snd_ca0106_ptr_write(chip, PLAYBACK_VOLUME1, ch, 0xffffffff);
+		snd_ca0106_ptr_write(chip, PLAYBACK_VOLUME2, ch, 0xffffffff);
+#endif
+	}
+	if (chip->details->i2c_adc == 1) {
+	        /* Select MIC, Line in, TAD in, AUX in */
+	        snd_ca0106_ptr_write(chip, CAPTURE_SOURCE, 0x0, 0x333300e4);
+		/* Default to CAPTURE_SOURCE to i2s in */
+		if (!resume)
+			chip->capture_source = 3;
+	} else if (chip->details->ac97 == 1) {
+	        /* Default to AC97 in */
+	        snd_ca0106_ptr_write(chip, CAPTURE_SOURCE, 0x0, 0x444400e4);
+		/* Default to CAPTURE_SOURCE to AC97 in */
+		if (!resume)
+			chip->capture_source = 4;
+	} else {
+	        /* Select MIC, Line in, TAD in, AUX in */
+	        snd_ca0106_ptr_write(chip, CAPTURE_SOURCE, 0x0, 0x333300e4);
+		/* Default to Set CAPTURE_SOURCE to i2s in */
+		if (!resume)
+			chip->capture_source = 3;
+	}
+
+	if (chip->details->gpio_type == 2) {
+		/* The SB0438 use GPIO differently. */
+		/* FIXME: Still need to find out what the other GPIO bits do.
+		 * E.g. For digital spdif out.
+		 */
+		outl(0x0, chip->port+GPIO);
+		/* outl(0x00f0e000, chip->port+GPIO); */ /* Analog */
+		outl(0x005f5301, chip->port+GPIO); /* Analog */
+	} else if (chip->details->gpio_type == 1) {
+		/* The SB0410 and SB0413 use GPIO differently. */
+		/* FIXME: Still need to find out what the other GPIO bits do.
+		 * E.g. For digital spdif out.
+		 */
+		outl(0x0, chip->port+GPIO);
+		/* outl(0x00f0e000, chip->port+GPIO); */ /* Analog */
+		outl(0x005f5301, chip->port+GPIO); /* Analog */
+	} else {
+		outl(0x0, chip->port+GPIO);
+		outl(0x005f03a3, chip->port+GPIO); /* Analog */
+		/* outl(0x005f02a2, chip->port+GPIO); */ /* SPDIF */
+	}
+	snd_ca0106_intr_enable(chip, 0x105); /* Win2000 uses 0x1e0 */
+
+	/* outl(HCFG_LOCKSOUNDCACHE|HCFG_AUDIOENABLE, chip->port+HCFG); */
+	/* 0x1000 causes AC3 to fails. Maybe it effects 24 bit output. */
+	/* outl(0x00001409, chip->port+HCFG); */
+	/* outl(0x00000009, chip->port+HCFG); */
+	/* AC97 2.0, Enable outputs. */
+	outl(HCFG_AC97 | HCFG_AUDIOENABLE, chip->port+HCFG);
+
+	if (chip->details->i2c_adc == 1) {
+		/* The SB0410 and SB0413 use I2C to control ADC. */
+		int size, n;
+
+		size = ARRAY_SIZE(i2c_adc_init);
+		/* snd_printk(KERN_DEBUG "I2C:array size=0x%x\n", size); */
+		for (n = 0; n < size; n++)
+			snd_ca0106_i2c_write(chip, i2c_adc_init[n][0],
+					     i2c_adc_init[n][1]);
+		for (n = 0; n < 4; n++) {
+			chip->i2c_capture_volume[n][0] = 0xcf;
+			chip->i2c_capture_volume[n][1] = 0xcf;
+		}
+		chip->i2c_capture_source = 2; /* Line in */
+		/* Enable Line-in capture. MIC in currently untested. */
+		/* snd_ca0106_i2c_write(chip, ADC_MUX, ADC_MUX_LINEIN); */
+	}
+
+	if (chip->details->spi_dac) {
+		/* The SB0570 use SPI to control DAC. */
+		int size, n;
+
+		size = ARRAY_SIZE(spi_dac_init);
+		for (n = 0; n < size; n++) {
+			int reg = spi_dac_init[n] >> SPI_REG_SHIFT;
+
+			snd_ca0106_spi_write(chip, spi_dac_init[n]);
+			if (reg < ARRAY_SIZE(chip->spi_dac_reg))
+				chip->spi_dac_reg[reg] = spi_dac_init[n];
+		}
+
+		/* Enable front dac only */
+		snd_ca0106_pcm_power_dac(chip, PCM_FRONT_CHANNEL, 1);
+	}
+}
+
+static void ca0106_stop_chip(struct snd_ca0106 *chip)
+{
+	/* disable interrupts */
+	snd_ca0106_ptr_write(chip, BASIC_INTERRUPT, 0, 0);
+	outl(0, chip->port + INTE);
+	snd_ca0106_ptr_write(chip, EXTENDED_INT_MASK, 0, 0);
+	udelay(1000);
+	/* disable audio */
+	/* outl(HCFG_LOCKSOUNDCACHE, chip->port + HCFG); */
+	outl(0, chip->port + HCFG);
+	/* FIXME: We need to stop and DMA transfers here.
+	 *        But as I am not sure how yet, we cannot from the dma pages.
+	 * So we can fix: snd-malloc: Memory leak?  pages not freed = 8
+	 */
+}
+
+static int __devinit snd_ca0106_create(int dev, struct snd_card *card,
+					 struct pci_dev *pci,
+					 struct snd_ca0106 **rchip)
+{
+	struct snd_ca0106 *chip;
+	struct snd_ca0106_details *c;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_ca0106_dev_free,
+	};
+
+	*rchip = NULL;
+
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(32)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32)) < 0) {
+		printk(KERN_ERR "error to set 32bit mask DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	spin_lock_init(&chip->emu_lock);
+
+	chip->port = pci_resource_start(pci, 0);
+	chip->res_port = request_region(chip->port, 0x20, "snd_ca0106");
+	if (!chip->res_port) {
+		snd_ca0106_free(chip);
+		printk(KERN_ERR "cannot allocate the port\n");
+		return -EBUSY;
+	}
+
+	if (request_irq(pci->irq, snd_ca0106_interrupt,
+			IRQF_SHARED, "snd_ca0106", chip)) {
+		snd_ca0106_free(chip);
+		printk(KERN_ERR "cannot grab irq\n");
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+
+	/* This stores the periods table. */
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				1024, &chip->buffer) < 0) {
+		snd_ca0106_free(chip);
+		return -ENOMEM;
+	}
+
+	pci_set_master(pci);
+	/* read serial */
+	pci_read_config_dword(pci, PCI_SUBSYSTEM_VENDOR_ID, &chip->serial);
+	pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &chip->model);
+	printk(KERN_INFO "snd-ca0106: Model %04x Rev %08x Serial %08x\n",
+	       chip->model, pci->revision, chip->serial);
+	strcpy(card->driver, "CA0106");
+	strcpy(card->shortname, "CA0106");
+
+	for (c = ca0106_chip_details; c->serial; c++) {
+		if (subsystem[dev]) {
+			if (c->serial == subsystem[dev])
+				break;
+		} else if (c->serial == chip->serial)
+			break;
+	}
+	chip->details = c;
+	if (subsystem[dev]) {
+		printk(KERN_INFO "snd-ca0106: Sound card name=%s, "
+		       "subsystem=0x%x. Forced to subsystem=0x%x\n",
+		       c->name, chip->serial, subsystem[dev]);
+	}
+
+	sprintf(card->longname, "%s at 0x%lx irq %i",
+		c->name, chip->port, chip->irq);
+
+	ca0106_init_chip(chip, 0);
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		snd_ca0106_free(chip);
+		return err;
+	}
+	*rchip = chip;
+	return 0;
+}
+
+
+static void ca0106_midi_interrupt_enable(struct snd_ca_midi *midi, int intr)
+{
+	snd_ca0106_intr_enable((struct snd_ca0106 *)(midi->dev_id), intr);
+}
+
+static void ca0106_midi_interrupt_disable(struct snd_ca_midi *midi, int intr)
+{
+	snd_ca0106_intr_disable((struct snd_ca0106 *)(midi->dev_id), intr);
+}
+
+static unsigned char ca0106_midi_read(struct snd_ca_midi *midi, int idx)
+{
+	return (unsigned char)snd_ca0106_ptr_read((struct snd_ca0106 *)(midi->dev_id),
+						  midi->port + idx, 0);
+}
+
+static void ca0106_midi_write(struct snd_ca_midi *midi, int data, int idx)
+{
+	snd_ca0106_ptr_write((struct snd_ca0106 *)(midi->dev_id), midi->port + idx, 0, data);
+}
+
+static struct snd_card *ca0106_dev_id_card(void *dev_id)
+{
+	return ((struct snd_ca0106 *)dev_id)->card;
+}
+
+static int ca0106_dev_id_port(void *dev_id)
+{
+	return ((struct snd_ca0106 *)dev_id)->port;
+}
+
+static int __devinit snd_ca0106_midi(struct snd_ca0106 *chip, unsigned int channel)
+{
+	struct snd_ca_midi *midi;
+	char *name;
+	int err;
+
+	if (channel == CA0106_MIDI_CHAN_B) {
+		name = "CA0106 MPU-401 (UART) B";
+		midi =  &chip->midi2;
+		midi->tx_enable = INTE_MIDI_TX_B;
+		midi->rx_enable = INTE_MIDI_RX_B;
+		midi->ipr_tx = IPR_MIDI_TX_B;
+		midi->ipr_rx = IPR_MIDI_RX_B;
+		midi->port = MIDI_UART_B_DATA;
+	} else {
+		name = "CA0106 MPU-401 (UART)";
+		midi =  &chip->midi;
+		midi->tx_enable = INTE_MIDI_TX_A;
+		midi->rx_enable = INTE_MIDI_TX_B;
+		midi->ipr_tx = IPR_MIDI_TX_A;
+		midi->ipr_rx = IPR_MIDI_RX_A;
+		midi->port = MIDI_UART_A_DATA;
+	}
+
+	midi->reset = CA0106_MPU401_RESET;
+	midi->enter_uart = CA0106_MPU401_ENTER_UART;
+	midi->ack = CA0106_MPU401_ACK;
+
+	midi->input_avail = CA0106_MIDI_INPUT_AVAIL;
+	midi->output_ready = CA0106_MIDI_OUTPUT_READY;
+
+	midi->channel = channel;
+
+	midi->interrupt_enable = ca0106_midi_interrupt_enable;
+	midi->interrupt_disable = ca0106_midi_interrupt_disable;
+
+	midi->read = ca0106_midi_read;
+	midi->write = ca0106_midi_write;
+
+	midi->get_dev_id_card = ca0106_dev_id_card;
+	midi->get_dev_id_port = ca0106_dev_id_port;
+
+	midi->dev_id = chip;
+	
+	if ((err = ca_midi_init(chip, midi, 0, name)) < 0)
+		return err;
+
+	return 0;
+}
+
+
+static int __devinit snd_ca0106_probe(struct pci_dev *pci,
+					const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_ca0106 *chip;
+	int i, err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	err = snd_ca0106_create(dev, card, pci, &chip);
+	if (err < 0)
+		goto error;
+	card->private_data = chip;
+
+	for (i = 0; i < 4; i++) {
+		err = snd_ca0106_pcm(chip, i);
+		if (err < 0)
+			goto error;
+	}
+
+	if (chip->details->ac97 == 1) {
+		/* The SB0410 and SB0413 do not have an AC97 chip. */
+		err = snd_ca0106_ac97(chip);
+		if (err < 0)
+			goto error;
+	}
+	err = snd_ca0106_mixer(chip);
+	if (err < 0)
+		goto error;
+
+	snd_printdd("ca0106: probe for MIDI channel A ...");
+	err = snd_ca0106_midi(chip, CA0106_MIDI_CHAN_A);
+	if (err < 0)
+		goto error;
+	snd_printdd(" done.\n");
+
+#ifdef CONFIG_PROC_FS
+	snd_ca0106_proc_init(chip);
+#endif
+
+	snd_card_set_dev(card, &pci->dev);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto error;
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+
+ error:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit snd_ca0106_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_ca0106_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_ca0106 *chip = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	for (i = 0; i < 4; i++)
+		snd_pcm_suspend_all(chip->pcm[i]);
+	if (chip->details->ac97)
+		snd_ac97_suspend(chip->ac97);
+	snd_ca0106_mixer_suspend(chip);
+
+	ca0106_stop_chip(chip);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_ca0106_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_ca0106 *chip = card->private_data;
+	int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+
+	if (pci_enable_device(pci) < 0) {
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+
+	pci_set_master(pci);
+
+	ca0106_init_chip(chip, 1);
+
+	if (chip->details->ac97)
+		snd_ac97_resume(chip->ac97);
+	snd_ca0106_mixer_resume(chip);
+	if (chip->details->spi_dac) {
+		for (i = 0; i < ARRAY_SIZE(chip->spi_dac_reg); i++)
+			snd_ca0106_spi_write(chip, chip->spi_dac_reg[i]);
+	}
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+// PCI IDs
+static DEFINE_PCI_DEVICE_TABLE(snd_ca0106_ids) = {
+	{ PCI_VDEVICE(CREATIVE, 0x0007), 0 },	/* Audigy LS or Live 24bit */
+	{ 0, }
+};
+MODULE_DEVICE_TABLE(pci, snd_ca0106_ids);
+
+// pci_driver definition
+static struct pci_driver driver = {
+	.name = "CA0106",
+	.id_table = snd_ca0106_ids,
+	.probe = snd_ca0106_probe,
+	.remove = __devexit_p(snd_ca0106_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_ca0106_suspend,
+	.resume = snd_ca0106_resume,
+#endif
+};
+
+// initialization of the module
+static int __init alsa_card_ca0106_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+// clean up the module
+static void __exit alsa_card_ca0106_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_ca0106_init)
+module_exit(alsa_card_ca0106_exit)
diff --git a/linux/sound/pci/ca0106/ca0106_mixer.c b/linux/sound/pci/ca0106/ca0106_mixer.c
new file mode 100644
index 0000000..630aa49
--- /dev/null
+++ b/linux/sound/pci/ca0106/ca0106_mixer.c
@@ -0,0 +1,956 @@
+/*
+ *  Copyright (c) 2004 James Courtier-Dutton <James@superbug.demon.co.uk>
+ *  Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit
+ *  Version: 0.0.18
+ *
+ *  FEATURES currently supported:
+ *    See ca0106_main.c for features.
+ * 
+ *  Changelog:
+ *    Support interrupts per period.
+ *    Removed noise from Center/LFE channel when in Analog mode.
+ *    Rename and remove mixer controls.
+ *  0.0.6
+ *    Use separate card based DMA buffer for periods table list.
+ *  0.0.7
+ *    Change remove and rename ctrls into lists.
+ *  0.0.8
+ *    Try to fix capture sources.
+ *  0.0.9
+ *    Fix AC3 output.
+ *    Enable S32_LE format support.
+ *  0.0.10
+ *    Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".)
+ *  0.0.11
+ *    Add Model name recognition.
+ *  0.0.12
+ *    Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period.
+ *    Remove redundent "voice" handling.
+ *  0.0.13
+ *    Single trigger call for multi channels.
+ *  0.0.14
+ *    Set limits based on what the sound card hardware can do.
+ *    playback periods_min=2, periods_max=8
+ *    capture hw constraints require period_size = n * 64 bytes.
+ *    playback hw constraints require period_size = n * 64 bytes.
+ *  0.0.15
+ *    Separated ca0106.c into separate functional .c files.
+ *  0.0.16
+ *    Modified Copyright message.
+ *  0.0.17
+ *    Implement Mic and Line in Capture.
+ *  0.0.18
+ *    Add support for mute control on SB Live 24bit (cards w/ SPI DAC)
+ *
+ *  This code was initally based on code from ALSA's emu10k1x.c which is:
+ *  Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+#include <asm/io.h>
+
+#include "ca0106.h"
+
+static void ca0106_spdif_enable(struct snd_ca0106 *emu)
+{
+	unsigned int val;
+
+	if (emu->spdif_enable) {
+		/* Digital */
+		snd_ca0106_ptr_write(emu, SPDIF_SELECT1, 0, 0xf);
+		snd_ca0106_ptr_write(emu, SPDIF_SELECT2, 0, 0x0b000000);
+		val = snd_ca0106_ptr_read(emu, CAPTURE_CONTROL, 0) & ~0x1000;
+		snd_ca0106_ptr_write(emu, CAPTURE_CONTROL, 0, val);
+		val = inl(emu->port + GPIO) & ~0x101;
+		outl(val, emu->port + GPIO);
+
+	} else {
+		/* Analog */
+		snd_ca0106_ptr_write(emu, SPDIF_SELECT1, 0, 0xf);
+		snd_ca0106_ptr_write(emu, SPDIF_SELECT2, 0, 0x000f0000);
+		val = snd_ca0106_ptr_read(emu, CAPTURE_CONTROL, 0) | 0x1000;
+		snd_ca0106_ptr_write(emu, CAPTURE_CONTROL, 0, val);
+		val = inl(emu->port + GPIO) | 0x101;
+		outl(val, emu->port + GPIO);
+	}
+}
+
+static void ca0106_set_capture_source(struct snd_ca0106 *emu)
+{
+	unsigned int val = emu->capture_source;
+	unsigned int source, mask;
+	source = (val << 28) | (val << 24) | (val << 20) | (val << 16);
+	mask = snd_ca0106_ptr_read(emu, CAPTURE_SOURCE, 0) & 0xffff;
+	snd_ca0106_ptr_write(emu, CAPTURE_SOURCE, 0, source | mask);
+}
+
+static void ca0106_set_i2c_capture_source(struct snd_ca0106 *emu,
+					  unsigned int val, int force)
+{
+	unsigned int ngain, ogain;
+	u32 source;
+
+	snd_ca0106_i2c_write(emu, ADC_MUX, 0); /* Mute input */
+	ngain = emu->i2c_capture_volume[val][0]; /* Left */
+	ogain = emu->i2c_capture_volume[emu->i2c_capture_source][0]; /* Left */
+	if (force || ngain != ogain)
+		snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCL, ngain & 0xff);
+	ngain = emu->i2c_capture_volume[val][1]; /* Right */
+	ogain = emu->i2c_capture_volume[emu->i2c_capture_source][1]; /* Right */
+	if (force || ngain != ogain)
+		snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCR, ngain & 0xff);
+	source = 1 << val;
+	snd_ca0106_i2c_write(emu, ADC_MUX, source); /* Set source */
+	emu->i2c_capture_source = val;
+}
+
+static void ca0106_set_capture_mic_line_in(struct snd_ca0106 *emu)
+{
+	u32 tmp;
+
+	if (emu->capture_mic_line_in) {
+		/* snd_ca0106_i2c_write(emu, ADC_MUX, 0); */ /* Mute input */
+		tmp = inl(emu->port+GPIO) & ~0x400;
+		tmp = tmp | 0x400;
+		outl(tmp, emu->port+GPIO);
+		/* snd_ca0106_i2c_write(emu, ADC_MUX, ADC_MUX_MIC); */
+	} else {
+		/* snd_ca0106_i2c_write(emu, ADC_MUX, 0); */ /* Mute input */
+		tmp = inl(emu->port+GPIO) & ~0x400;
+		outl(tmp, emu->port+GPIO);
+		/* snd_ca0106_i2c_write(emu, ADC_MUX, ADC_MUX_LINEIN); */
+	}
+}
+
+static void ca0106_set_spdif_bits(struct snd_ca0106 *emu, int idx)
+{
+	snd_ca0106_ptr_write(emu, SPCS0 + idx, 0, emu->spdif_str_bits[idx]);
+}
+
+/*
+ */
+static const DECLARE_TLV_DB_SCALE(snd_ca0106_db_scale1, -5175, 25, 1);
+static const DECLARE_TLV_DB_SCALE(snd_ca0106_db_scale2, -10350, 50, 1);
+
+#define snd_ca0106_shared_spdif_info	snd_ctl_boolean_mono_info
+
+static int snd_ca0106_shared_spdif_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = emu->spdif_enable;
+	return 0;
+}
+
+static int snd_ca0106_shared_spdif_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change = 0;
+
+	val = !!ucontrol->value.integer.value[0];
+	change = (emu->spdif_enable != val);
+	if (change) {
+		emu->spdif_enable = val;
+		ca0106_spdif_enable(emu);
+	}
+        return change;
+}
+
+static int snd_ca0106_capture_source_info(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[6] = {
+		"IEC958 out", "i2s mixer out", "IEC958 in", "i2s in", "AC97 in", "SRC out"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 6;
+	if (uinfo->value.enumerated.item > 5)
+                uinfo->value.enumerated.item = 5;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ca0106_capture_source_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = emu->capture_source;
+	return 0;
+}
+
+static int snd_ca0106_capture_source_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change = 0;
+
+	val = ucontrol->value.enumerated.item[0] ;
+	if (val >= 6)
+		return -EINVAL;
+	change = (emu->capture_source != val);
+	if (change) {
+		emu->capture_source = val;
+		ca0106_set_capture_source(emu);
+	}
+        return change;
+}
+
+static int snd_ca0106_i2c_capture_source_info(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[6] = {
+		"Phone", "Mic", "Line in", "Aux"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3)
+                uinfo->value.enumerated.item = 3;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ca0106_i2c_capture_source_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = emu->i2c_capture_source;
+	return 0;
+}
+
+static int snd_ca0106_i2c_capture_source_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int source_id;
+	int change = 0;
+	/* If the capture source has changed,
+	 * update the capture volume from the cached value
+	 * for the particular source.
+	 */
+	source_id = ucontrol->value.enumerated.item[0] ;
+	if (source_id >= 4)
+		return -EINVAL;
+	change = (emu->i2c_capture_source != source_id);
+	if (change) {
+		ca0106_set_i2c_capture_source(emu, source_id, 0);
+	}
+        return change;
+}
+
+static int snd_ca0106_capture_line_in_side_out_info(struct snd_kcontrol *kcontrol,
+					       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = { "Side out", "Line in" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+                uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ca0106_capture_mic_line_in_info(struct snd_kcontrol *kcontrol,
+					       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = { "Line in", "Mic in" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+                uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ca0106_capture_mic_line_in_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = emu->capture_mic_line_in;
+	return 0;
+}
+
+static int snd_ca0106_capture_mic_line_in_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change = 0;
+
+	val = ucontrol->value.enumerated.item[0] ;
+	if (val > 1)
+		return -EINVAL;
+	change = (emu->capture_mic_line_in != val);
+	if (change) {
+		emu->capture_mic_line_in = val;
+		ca0106_set_capture_mic_line_in(emu);
+	}
+        return change;
+}
+
+static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Shared Mic/Line in Capture Switch",
+	.info =		snd_ca0106_capture_mic_line_in_info,
+	.get =		snd_ca0106_capture_mic_line_in_get,
+	.put =		snd_ca0106_capture_mic_line_in_put
+};
+
+static struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Shared Line in/Side out Capture Switch",
+	.info =		snd_ca0106_capture_line_in_side_out_info,
+	.get =		snd_ca0106_capture_mic_line_in_get,
+	.put =		snd_ca0106_capture_mic_line_in_put
+};
+
+
+static int snd_ca0106_spdif_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static void decode_spdif_bits(unsigned char *status, unsigned int bits)
+{
+	status[0] = (bits >> 0) & 0xff;
+	status[1] = (bits >> 8) & 0xff;
+	status[2] = (bits >> 16) & 0xff;
+	status[3] = (bits >> 24) & 0xff;
+}
+
+static int snd_ca0106_spdif_get_default(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	decode_spdif_bits(ucontrol->value.iec958.status,
+			  emu->spdif_bits[idx]);
+        return 0;
+}
+
+static int snd_ca0106_spdif_get_stream(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	decode_spdif_bits(ucontrol->value.iec958.status,
+			  emu->spdif_str_bits[idx]);
+        return 0;
+}
+
+static int snd_ca0106_spdif_get_mask(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0xff;
+        return 0;
+}
+
+static unsigned int encode_spdif_bits(unsigned char *status)
+{
+	return ((unsigned int)status[0] << 0) |
+		((unsigned int)status[1] << 8) |
+		((unsigned int)status[2] << 16) |
+		((unsigned int)status[3] << 24);
+}
+
+static int snd_ca0106_spdif_put_default(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	unsigned int val;
+
+	val = encode_spdif_bits(ucontrol->value.iec958.status);
+	if (val != emu->spdif_bits[idx]) {
+		emu->spdif_bits[idx] = val;
+		/* FIXME: this isn't safe, but needed to keep the compatibility
+		 * with older alsa-lib config
+		 */
+		emu->spdif_str_bits[idx] = val;
+		ca0106_set_spdif_bits(emu, idx);
+		return 1;
+	}
+	return 0;
+}
+
+static int snd_ca0106_spdif_put_stream(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	unsigned int val;
+
+	val = encode_spdif_bits(ucontrol->value.iec958.status);
+	if (val != emu->spdif_str_bits[idx]) {
+		emu->spdif_str_bits[idx] = val;
+		ca0106_set_spdif_bits(emu, idx);
+		return 1;
+	}
+        return 0;
+}
+
+static int snd_ca0106_volume_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+        uinfo->count = 2;
+        uinfo->value.integer.min = 0;
+        uinfo->value.integer.max = 255;
+        return 0;
+}
+
+static int snd_ca0106_volume_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+        struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+        unsigned int value;
+	int channel_id, reg;
+
+	channel_id = (kcontrol->private_value >> 8) & 0xff;
+	reg = kcontrol->private_value & 0xff;
+
+        value = snd_ca0106_ptr_read(emu, reg, channel_id);
+        ucontrol->value.integer.value[0] = 0xff - ((value >> 24) & 0xff); /* Left */
+        ucontrol->value.integer.value[1] = 0xff - ((value >> 16) & 0xff); /* Right */
+        return 0;
+}
+
+static int snd_ca0106_volume_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+        struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+        unsigned int oval, nval;
+	int channel_id, reg;
+
+	channel_id = (kcontrol->private_value >> 8) & 0xff;
+	reg = kcontrol->private_value & 0xff;
+
+	oval = snd_ca0106_ptr_read(emu, reg, channel_id);
+	nval = ((0xff - ucontrol->value.integer.value[0]) << 24) |
+		((0xff - ucontrol->value.integer.value[1]) << 16);
+        nval |= ((0xff - ucontrol->value.integer.value[0]) << 8) |
+		((0xff - ucontrol->value.integer.value[1]) );
+	if (oval == nval)
+		return 0;
+	snd_ca0106_ptr_write(emu, reg, channel_id, nval);
+	return 1;
+}
+
+static int snd_ca0106_i2c_volume_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+        uinfo->count = 2;
+        uinfo->value.integer.min = 0;
+        uinfo->value.integer.max = 255;
+        return 0;
+}
+
+static int snd_ca0106_i2c_volume_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+        struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+	int source_id;
+
+	source_id = kcontrol->private_value;
+
+        ucontrol->value.integer.value[0] = emu->i2c_capture_volume[source_id][0];
+        ucontrol->value.integer.value[1] = emu->i2c_capture_volume[source_id][1];
+        return 0;
+}
+
+static int snd_ca0106_i2c_volume_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+        struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+        unsigned int ogain;
+        unsigned int ngain;
+	int source_id;
+	int change = 0;
+
+	source_id = kcontrol->private_value;
+	ogain = emu->i2c_capture_volume[source_id][0]; /* Left */
+	ngain = ucontrol->value.integer.value[0];
+	if (ngain > 0xff)
+		return -EINVAL;
+	if (ogain != ngain) {
+		if (emu->i2c_capture_source == source_id)
+			snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCL, ((ngain) & 0xff) );
+		emu->i2c_capture_volume[source_id][0] = ucontrol->value.integer.value[0];
+		change = 1;
+	}
+	ogain = emu->i2c_capture_volume[source_id][1]; /* Right */
+	ngain = ucontrol->value.integer.value[1];
+	if (ngain > 0xff)
+		return -EINVAL;
+	if (ogain != ngain) {
+		if (emu->i2c_capture_source == source_id)
+			snd_ca0106_i2c_write(emu, ADC_ATTEN_ADCR, ((ngain) & 0xff));
+		emu->i2c_capture_volume[source_id][1] = ucontrol->value.integer.value[1];
+		change = 1;
+	}
+
+	return change;
+}
+
+#define spi_mute_info	snd_ctl_boolean_mono_info
+
+static int spi_mute_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = kcontrol->private_value >> SPI_REG_SHIFT;
+	unsigned int bit = kcontrol->private_value & SPI_REG_MASK;
+
+	ucontrol->value.integer.value[0] = !(emu->spi_dac_reg[reg] & bit);
+	return 0;
+}
+
+static int spi_mute_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ca0106 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = kcontrol->private_value >> SPI_REG_SHIFT;
+	unsigned int bit = kcontrol->private_value & SPI_REG_MASK;
+	int ret;
+
+	ret = emu->spi_dac_reg[reg] & bit;
+	if (ucontrol->value.integer.value[0]) {
+		if (!ret)	/* bit already cleared, do nothing */
+			return 0;
+		emu->spi_dac_reg[reg] &= ~bit;
+	} else {
+		if (ret)	/* bit already set, do nothing */
+			return 0;
+		emu->spi_dac_reg[reg] |= bit;
+	}
+
+	ret = snd_ca0106_spi_write(emu, emu->spi_dac_reg[reg]);
+	return ret ? -EINVAL : 1;
+}
+
+#define CA_VOLUME(xname,chid,reg) \
+{								\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,	\
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |		\
+	          SNDRV_CTL_ELEM_ACCESS_TLV_READ,		\
+	.info =	 snd_ca0106_volume_info,			\
+	.get =   snd_ca0106_volume_get,				\
+	.put =   snd_ca0106_volume_put,				\
+	.tlv = { .p = snd_ca0106_db_scale1 },			\
+	.private_value = ((chid) << 8) | (reg)			\
+}
+
+static struct snd_kcontrol_new snd_ca0106_volume_ctls[] __devinitdata = {
+	CA_VOLUME("Analog Front Playback Volume",
+		  CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME2),
+        CA_VOLUME("Analog Rear Playback Volume",
+		  CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME2),
+	CA_VOLUME("Analog Center/LFE Playback Volume",
+		  CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME2),
+        CA_VOLUME("Analog Side Playback Volume",
+		  CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME2),
+
+        CA_VOLUME("IEC958 Front Playback Volume",
+		  CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME1),
+	CA_VOLUME("IEC958 Rear Playback Volume",
+		  CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME1),
+	CA_VOLUME("IEC958 Center/LFE Playback Volume",
+		  CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME1),
+	CA_VOLUME("IEC958 Unknown Playback Volume",
+		  CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME1),
+
+        CA_VOLUME("CAPTURE feedback Playback Volume",
+		  1, CAPTURE_CONTROL),
+
+	{
+		.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+		.count =	4,
+		.info =         snd_ca0106_spdif_info,
+		.get =          snd_ca0106_spdif_get_mask
+	},
+	{
+		.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =		"IEC958 Playback Switch",
+		.info =		snd_ca0106_shared_spdif_info,
+		.get =		snd_ca0106_shared_spdif_get,
+		.put =		snd_ca0106_shared_spdif_put
+	},
+	{
+		.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =		"Digital Source Capture Enum",
+		.info =		snd_ca0106_capture_source_info,
+		.get =		snd_ca0106_capture_source_get,
+		.put =		snd_ca0106_capture_source_put
+	},
+	{
+		.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =		"Analog Source Capture Enum",
+		.info =		snd_ca0106_i2c_capture_source_info,
+		.get =		snd_ca0106_i2c_capture_source_get,
+		.put =		snd_ca0106_i2c_capture_source_put
+	},
+	{
+		.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+		.count =	4,
+		.info =         snd_ca0106_spdif_info,
+		.get =          snd_ca0106_spdif_get_default,
+		.put =          snd_ca0106_spdif_put_default
+	},
+	{
+		.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+		.count =	4,
+		.info =         snd_ca0106_spdif_info,
+		.get =          snd_ca0106_spdif_get_stream,
+		.put =          snd_ca0106_spdif_put_stream
+	},
+};
+
+#define I2C_VOLUME(xname,chid) \
+{								\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,	\
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |		\
+	          SNDRV_CTL_ELEM_ACCESS_TLV_READ,		\
+	.info =  snd_ca0106_i2c_volume_info,			\
+	.get =   snd_ca0106_i2c_volume_get,			\
+	.put =   snd_ca0106_i2c_volume_put,			\
+	.tlv = { .p = snd_ca0106_db_scale2 },			\
+	.private_value = chid					\
+}
+
+static struct snd_kcontrol_new snd_ca0106_volume_i2c_adc_ctls[] __devinitdata = {
+        I2C_VOLUME("Phone Capture Volume", 0),
+        I2C_VOLUME("Mic Capture Volume", 1),
+        I2C_VOLUME("Line in Capture Volume", 2),
+        I2C_VOLUME("Aux Capture Volume", 3),
+};
+
+static const int spi_dmute_reg[] = {
+	SPI_DMUTE0_REG,
+	SPI_DMUTE1_REG,
+	SPI_DMUTE2_REG,
+	0,
+	SPI_DMUTE4_REG,
+};
+static const int spi_dmute_bit[] = {
+	SPI_DMUTE0_BIT,
+	SPI_DMUTE1_BIT,
+	SPI_DMUTE2_BIT,
+	0,
+	SPI_DMUTE4_BIT,
+};
+
+static struct snd_kcontrol_new __devinit
+snd_ca0106_volume_spi_dac_ctl(struct snd_ca0106_details *details,
+			      int channel_id)
+{
+	struct snd_kcontrol_new spi_switch = {0};
+	int reg, bit;
+	int dac_id;
+
+	spi_switch.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	spi_switch.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+	spi_switch.info = spi_mute_info;
+	spi_switch.get = spi_mute_get;
+	spi_switch.put = spi_mute_put;
+
+	switch (channel_id) {
+	case PCM_FRONT_CHANNEL:
+		spi_switch.name = "Analog Front Playback Switch";
+		dac_id = (details->spi_dac & 0xf000) >> (4 * 3);
+		break;
+	case PCM_REAR_CHANNEL:
+		spi_switch.name = "Analog Rear Playback Switch";
+		dac_id = (details->spi_dac & 0x0f00) >> (4 * 2);
+		break;
+	case PCM_CENTER_LFE_CHANNEL:
+		spi_switch.name = "Analog Center/LFE Playback Switch";
+		dac_id = (details->spi_dac & 0x00f0) >> (4 * 1);
+		break;
+	case PCM_UNKNOWN_CHANNEL:
+		spi_switch.name = "Analog Side Playback Switch";
+		dac_id = (details->spi_dac & 0x000f) >> (4 * 0);
+		break;
+	default:
+		/* Unused channel */
+		spi_switch.name = NULL;
+		dac_id = 0;
+	}
+	reg = spi_dmute_reg[dac_id];
+	bit = spi_dmute_bit[dac_id];
+
+	spi_switch.private_value = (reg << SPI_REG_SHIFT) | bit;
+
+	return spi_switch;
+}
+
+static int __devinit remove_ctl(struct snd_card *card, const char *name)
+{
+	struct snd_ctl_elem_id id;
+	memset(&id, 0, sizeof(id));
+	strcpy(id.name, name);
+	id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	return snd_ctl_remove_id(card, &id);
+}
+
+static struct snd_kcontrol __devinit *ctl_find(struct snd_card *card, const char *name)
+{
+	struct snd_ctl_elem_id sid;
+	memset(&sid, 0, sizeof(sid));
+	/* FIXME: strcpy is bad. */
+	strcpy(sid.name, name);
+	sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	return snd_ctl_find_id(card, &sid);
+}
+
+static int __devinit rename_ctl(struct snd_card *card, const char *src, const char *dst)
+{
+	struct snd_kcontrol *kctl = ctl_find(card, src);
+	if (kctl) {
+		strcpy(kctl->id.name, dst);
+		return 0;
+	}
+	return -ENOENT;
+}
+
+#define ADD_CTLS(emu, ctls)						\
+	do {								\
+		int i, _err;						\
+		for (i = 0; i < ARRAY_SIZE(ctls); i++) {		\
+			_err = snd_ctl_add(card, snd_ctl_new1(&ctls[i], emu)); \
+			if (_err < 0)					\
+				return _err;				\
+		}							\
+	} while (0)
+
+static __devinitdata
+DECLARE_TLV_DB_SCALE(snd_ca0106_master_db_scale, -6375, 25, 1);
+
+static char *slave_vols[] __devinitdata = {
+	"Analog Front Playback Volume",
+        "Analog Rear Playback Volume",
+	"Analog Center/LFE Playback Volume",
+        "Analog Side Playback Volume",
+        "IEC958 Front Playback Volume",
+	"IEC958 Rear Playback Volume",
+	"IEC958 Center/LFE Playback Volume",
+	"IEC958 Unknown Playback Volume",
+        "CAPTURE feedback Playback Volume",
+	NULL
+};
+
+static char *slave_sws[] __devinitdata = {
+	"Analog Front Playback Switch",
+	"Analog Rear Playback Switch",
+	"Analog Center/LFE Playback Switch",
+	"Analog Side Playback Switch",
+	"IEC958 Playback Switch",
+	NULL
+};
+
+static void __devinit add_slaves(struct snd_card *card,
+				 struct snd_kcontrol *master, char **list)
+{
+	for (; *list; list++) {
+		struct snd_kcontrol *slave = ctl_find(card, *list);
+		if (slave)
+			snd_ctl_add_slave(master, slave);
+	}
+}
+
+int __devinit snd_ca0106_mixer(struct snd_ca0106 *emu)
+{
+	int err;
+        struct snd_card *card = emu->card;
+	char **c;
+	struct snd_kcontrol *vmaster;
+	static char *ca0106_remove_ctls[] = {
+		"Master Mono Playback Switch",
+		"Master Mono Playback Volume",
+		"3D Control - Switch",
+		"3D Control Sigmatel - Depth",
+		"PCM Playback Switch",
+		"PCM Playback Volume",
+		"CD Playback Switch",
+		"CD Playback Volume",
+		"Phone Playback Switch",
+		"Phone Playback Volume",
+		"Video Playback Switch",
+		"Video Playback Volume",
+		"Beep Playback Switch",
+		"Beep Playback Volume",
+		"Mono Output Select",
+		"Capture Source",
+		"Capture Switch",
+		"Capture Volume",
+		"External Amplifier",
+		"Sigmatel 4-Speaker Stereo Playback Switch",
+		"Surround Phase Inversion Playback Switch",
+		NULL
+	};
+	static char *ca0106_rename_ctls[] = {
+		"Master Playback Switch", "Capture Switch",
+		"Master Playback Volume", "Capture Volume",
+		"Line Playback Switch", "AC97 Line Capture Switch",
+		"Line Playback Volume", "AC97 Line Capture Volume",
+		"Aux Playback Switch", "AC97 Aux Capture Switch",
+		"Aux Playback Volume", "AC97 Aux Capture Volume",
+		"Mic Playback Switch", "AC97 Mic Capture Switch",
+		"Mic Playback Volume", "AC97 Mic Capture Volume",
+		"Mic Select", "AC97 Mic Select",
+		"Mic Boost (+20dB)", "AC97 Mic Boost (+20dB)",
+		NULL
+	};
+#if 1
+	for (c = ca0106_remove_ctls; *c; c++)
+		remove_ctl(card, *c);
+	for (c = ca0106_rename_ctls; *c; c += 2)
+		rename_ctl(card, c[0], c[1]);
+#endif
+
+	ADD_CTLS(emu, snd_ca0106_volume_ctls);
+	if (emu->details->i2c_adc == 1) {
+		ADD_CTLS(emu, snd_ca0106_volume_i2c_adc_ctls);
+		if (emu->details->gpio_type == 1)
+			err = snd_ctl_add(card, snd_ctl_new1(&snd_ca0106_capture_mic_line_in, emu));
+		else  /* gpio_type == 2 */
+			err = snd_ctl_add(card, snd_ctl_new1(&snd_ca0106_capture_line_in_side_out, emu));
+		if (err < 0)
+			return err;
+	}
+	if (emu->details->spi_dac) {
+		int i;
+		for (i = 0;; i++) {
+			struct snd_kcontrol_new ctl;
+			ctl = snd_ca0106_volume_spi_dac_ctl(emu->details, i);
+			if (!ctl.name)
+				break;
+			err = snd_ctl_add(card, snd_ctl_new1(&ctl, emu));
+			if (err < 0)
+				return err;
+		}
+	}
+
+	/* Create virtual master controls */
+	vmaster = snd_ctl_make_virtual_master("Master Playback Volume",
+					      snd_ca0106_master_db_scale);
+	if (!vmaster)
+		return -ENOMEM;
+	err = snd_ctl_add(card, vmaster);
+	if (err < 0)
+		return err;
+	add_slaves(card, vmaster, slave_vols);
+
+	if (emu->details->spi_dac) {
+		vmaster = snd_ctl_make_virtual_master("Master Playback Switch",
+						      NULL);
+		if (!vmaster)
+			return -ENOMEM;
+		err = snd_ctl_add(card, vmaster);
+		if (err < 0)
+			return err;
+		add_slaves(card, vmaster, slave_sws);
+	}
+
+	strcpy(card->mixername, "CA0106");
+        return 0;
+}
+
+#ifdef CONFIG_PM
+struct ca0106_vol_tbl {
+	unsigned int channel_id;
+	unsigned int reg;
+};
+
+static struct ca0106_vol_tbl saved_volumes[NUM_SAVED_VOLUMES] = {
+	{ CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME2 },
+	{ CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME2 },
+	{ CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME2 },
+	{ CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME2 },
+	{ CONTROL_FRONT_CHANNEL, PLAYBACK_VOLUME1 },
+	{ CONTROL_REAR_CHANNEL, PLAYBACK_VOLUME1 },
+	{ CONTROL_CENTER_LFE_CHANNEL, PLAYBACK_VOLUME1 },
+	{ CONTROL_UNKNOWN_CHANNEL, PLAYBACK_VOLUME1 },
+	{ 1, CAPTURE_CONTROL },
+};
+
+void snd_ca0106_mixer_suspend(struct snd_ca0106 *chip)
+{
+	int i;
+
+	/* save volumes */
+	for (i = 0; i < NUM_SAVED_VOLUMES; i++)
+		chip->saved_vol[i] =
+			snd_ca0106_ptr_read(chip, saved_volumes[i].reg,
+					    saved_volumes[i].channel_id);
+}
+
+void snd_ca0106_mixer_resume(struct snd_ca0106  *chip)
+{
+	int i;
+
+	for (i = 0; i < NUM_SAVED_VOLUMES; i++)
+		snd_ca0106_ptr_write(chip, saved_volumes[i].reg,
+				     saved_volumes[i].channel_id,
+				     chip->saved_vol[i]);
+
+	ca0106_spdif_enable(chip);
+	ca0106_set_capture_source(chip);
+	ca0106_set_i2c_capture_source(chip, chip->i2c_capture_source, 1);
+	for (i = 0; i < 4; i++)
+		ca0106_set_spdif_bits(chip, i);
+	if (chip->details->i2c_adc)
+		ca0106_set_capture_mic_line_in(chip);
+}
+#endif /* CONFIG_PM */
diff --git a/linux/sound/pci/ca0106/ca0106_proc.c b/linux/sound/pci/ca0106/ca0106_proc.c
new file mode 100644
index 0000000..ba96428
--- /dev/null
+++ b/linux/sound/pci/ca0106/ca0106_proc.c
@@ -0,0 +1,457 @@
+/*
+ *  Copyright (c) 2004 James Courtier-Dutton <James@superbug.demon.co.uk>
+ *  Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit
+ *  Version: 0.0.18
+ *
+ *  FEATURES currently supported:
+ *    See ca0106_main.c for features.
+ * 
+ *  Changelog:
+ *    Support interrupts per period.
+ *    Removed noise from Center/LFE channel when in Analog mode.
+ *    Rename and remove mixer controls.
+ *  0.0.6
+ *    Use separate card based DMA buffer for periods table list.
+ *  0.0.7
+ *    Change remove and rename ctrls into lists.
+ *  0.0.8
+ *    Try to fix capture sources.
+ *  0.0.9
+ *    Fix AC3 output.
+ *    Enable S32_LE format support.
+ *  0.0.10
+ *    Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".)
+ *  0.0.11
+ *    Add Model name recognition.
+ *  0.0.12
+ *    Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period.
+ *    Remove redundent "voice" handling.
+ *  0.0.13
+ *    Single trigger call for multi channels.
+ *  0.0.14
+ *    Set limits based on what the sound card hardware can do.
+ *    playback periods_min=2, periods_max=8
+ *    capture hw constraints require period_size = n * 64 bytes.
+ *    playback hw constraints require period_size = n * 64 bytes.
+ *  0.0.15
+ *    Separate ca0106.c into separate functional .c files.
+ *  0.0.16
+ *    Modified Copyright message.
+ *  0.0.17
+ *    Add iec958 file in proc file system to show status of SPDIF in.
+ *  0.0.18
+ *    Implement support for Line-in capture on SB Live 24bit.
+ *
+ *  This code was initally based on code from ALSA's emu10k1x.c which is:
+ *  Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/asoundef.h>
+#include <asm/io.h>
+
+#include "ca0106.h"
+
+
+#ifdef CONFIG_PROC_FS
+
+struct snd_ca0106_category_str {
+	int val;
+	const char *name;
+};
+
+static struct snd_ca0106_category_str snd_ca0106_con_category[] = {
+	{ IEC958_AES1_CON_DAT, "DAT" },
+	{ IEC958_AES1_CON_VCR, "VCR" },
+	{ IEC958_AES1_CON_MICROPHONE, "microphone" },
+	{ IEC958_AES1_CON_SYNTHESIZER, "synthesizer" },
+	{ IEC958_AES1_CON_RATE_CONVERTER, "rate converter" },
+	{ IEC958_AES1_CON_MIXER, "mixer" },
+	{ IEC958_AES1_CON_SAMPLER, "sampler" },
+	{ IEC958_AES1_CON_PCM_CODER, "PCM coder" },
+	{ IEC958_AES1_CON_IEC908_CD, "CD" },
+	{ IEC958_AES1_CON_NON_IEC908_CD, "non-IEC908 CD" },
+	{ IEC958_AES1_CON_GENERAL, "general" },
+};
+
+
+static void snd_ca0106_proc_dump_iec958( struct snd_info_buffer *buffer, u32 value)
+{
+	int i;
+	u32 status[4];
+	status[0] = value & 0xff;
+	status[1] = (value >> 8) & 0xff;
+	status[2] = (value >> 16)  & 0xff;
+	status[3] = (value >> 24)  & 0xff;
+	
+	if (! (status[0] & IEC958_AES0_PROFESSIONAL)) {
+		/* consumer */
+		snd_iprintf(buffer, "Mode: consumer\n");
+		snd_iprintf(buffer, "Data: ");
+		if (!(status[0] & IEC958_AES0_NONAUDIO)) {
+			snd_iprintf(buffer, "audio\n");
+		} else {
+			snd_iprintf(buffer, "non-audio\n");
+		}
+		snd_iprintf(buffer, "Rate: ");
+		switch (status[3] & IEC958_AES3_CON_FS) {
+		case IEC958_AES3_CON_FS_44100:
+			snd_iprintf(buffer, "44100 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_48000:
+			snd_iprintf(buffer, "48000 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_32000:
+			snd_iprintf(buffer, "32000 Hz\n");
+			break;
+		default:
+			snd_iprintf(buffer, "unknown\n");
+			break;
+		}
+		snd_iprintf(buffer, "Copyright: ");
+		if (status[0] & IEC958_AES0_CON_NOT_COPYRIGHT) {
+			snd_iprintf(buffer, "permitted\n");
+		} else {
+			snd_iprintf(buffer, "protected\n");
+		}
+		snd_iprintf(buffer, "Emphasis: ");
+		if ((status[0] & IEC958_AES0_CON_EMPHASIS) != IEC958_AES0_CON_EMPHASIS_5015) {
+			snd_iprintf(buffer, "none\n");
+		} else {
+			snd_iprintf(buffer, "50/15us\n");
+		}
+		snd_iprintf(buffer, "Category: ");
+		for (i = 0; i < ARRAY_SIZE(snd_ca0106_con_category); i++) {
+			if ((status[1] & IEC958_AES1_CON_CATEGORY) == snd_ca0106_con_category[i].val) {
+				snd_iprintf(buffer, "%s\n", snd_ca0106_con_category[i].name);
+				break;
+			}
+		}
+		if (i >= ARRAY_SIZE(snd_ca0106_con_category)) {
+			snd_iprintf(buffer, "unknown 0x%x\n", status[1] & IEC958_AES1_CON_CATEGORY);
+		}
+		snd_iprintf(buffer, "Original: ");
+		if (status[1] & IEC958_AES1_CON_ORIGINAL) {
+			snd_iprintf(buffer, "original\n");
+		} else {
+			snd_iprintf(buffer, "1st generation\n");
+		}
+		snd_iprintf(buffer, "Clock: ");
+		switch (status[3] & IEC958_AES3_CON_CLOCK) {
+		case IEC958_AES3_CON_CLOCK_1000PPM:
+			snd_iprintf(buffer, "1000 ppm\n");
+			break;
+		case IEC958_AES3_CON_CLOCK_50PPM:
+			snd_iprintf(buffer, "50 ppm\n");
+			break;
+		case IEC958_AES3_CON_CLOCK_VARIABLE:
+			snd_iprintf(buffer, "variable pitch\n");
+			break;
+		default:
+			snd_iprintf(buffer, "unknown\n");
+			break;
+		}
+	} else {
+		snd_iprintf(buffer, "Mode: professional\n");
+		snd_iprintf(buffer, "Data: ");
+		if (!(status[0] & IEC958_AES0_NONAUDIO)) {
+			snd_iprintf(buffer, "audio\n");
+		} else {
+			snd_iprintf(buffer, "non-audio\n");
+		}
+		snd_iprintf(buffer, "Rate: ");
+		switch (status[0] & IEC958_AES0_PRO_FS) {
+		case IEC958_AES0_PRO_FS_44100:
+			snd_iprintf(buffer, "44100 Hz\n");
+			break;
+		case IEC958_AES0_PRO_FS_48000:
+			snd_iprintf(buffer, "48000 Hz\n");
+			break;
+		case IEC958_AES0_PRO_FS_32000:
+			snd_iprintf(buffer, "32000 Hz\n");
+			break;
+		default:
+			snd_iprintf(buffer, "unknown\n");
+			break;
+		}
+		snd_iprintf(buffer, "Rate Locked: ");
+		if (status[0] & IEC958_AES0_PRO_FREQ_UNLOCKED)
+			snd_iprintf(buffer, "no\n");
+		else
+			snd_iprintf(buffer, "yes\n");
+		snd_iprintf(buffer, "Emphasis: ");
+		switch (status[0] & IEC958_AES0_PRO_EMPHASIS) {
+		case IEC958_AES0_PRO_EMPHASIS_CCITT:
+			snd_iprintf(buffer, "CCITT J.17\n");
+			break;
+		case IEC958_AES0_PRO_EMPHASIS_NONE:
+			snd_iprintf(buffer, "none\n");
+			break;
+		case IEC958_AES0_PRO_EMPHASIS_5015:
+			snd_iprintf(buffer, "50/15us\n");
+			break;
+		case IEC958_AES0_PRO_EMPHASIS_NOTID:
+		default:
+			snd_iprintf(buffer, "unknown\n");
+			break;
+		}
+		snd_iprintf(buffer, "Stereophonic: ");
+		if ((status[1] & IEC958_AES1_PRO_MODE) == IEC958_AES1_PRO_MODE_STEREOPHONIC) {
+			snd_iprintf(buffer, "stereo\n");
+		} else {
+			snd_iprintf(buffer, "not indicated\n");
+		}
+		snd_iprintf(buffer, "Userbits: ");
+		switch (status[1] & IEC958_AES1_PRO_USERBITS) {
+		case IEC958_AES1_PRO_USERBITS_192:
+			snd_iprintf(buffer, "192bit\n");
+			break;
+		case IEC958_AES1_PRO_USERBITS_UDEF:
+			snd_iprintf(buffer, "user-defined\n");
+			break;
+		default:
+			snd_iprintf(buffer, "unknown\n");
+			break;
+		}
+		snd_iprintf(buffer, "Sample Bits: ");
+		switch (status[2] & IEC958_AES2_PRO_SBITS) {
+		case IEC958_AES2_PRO_SBITS_20:
+			snd_iprintf(buffer, "20 bit\n");
+			break;
+		case IEC958_AES2_PRO_SBITS_24:
+			snd_iprintf(buffer, "24 bit\n");
+			break;
+		case IEC958_AES2_PRO_SBITS_UDEF:
+			snd_iprintf(buffer, "user defined\n");
+			break;
+		default:
+			snd_iprintf(buffer, "unknown\n");
+			break;
+		}
+		snd_iprintf(buffer, "Word Length: ");
+		switch (status[2] & IEC958_AES2_PRO_WORDLEN) {
+		case IEC958_AES2_PRO_WORDLEN_22_18:
+			snd_iprintf(buffer, "22 bit or 18 bit\n");
+			break;
+		case IEC958_AES2_PRO_WORDLEN_23_19:
+			snd_iprintf(buffer, "23 bit or 19 bit\n");
+			break;
+		case IEC958_AES2_PRO_WORDLEN_24_20:
+			snd_iprintf(buffer, "24 bit or 20 bit\n");
+			break;
+		case IEC958_AES2_PRO_WORDLEN_20_16:
+			snd_iprintf(buffer, "20 bit or 16 bit\n");
+			break;
+		default:
+			snd_iprintf(buffer, "unknown\n");
+			break;
+		}
+	}
+}
+
+static void snd_ca0106_proc_iec958(struct snd_info_entry *entry, 
+				       struct snd_info_buffer *buffer)
+{
+	struct snd_ca0106 *emu = entry->private_data;
+	u32 value;
+
+        value = snd_ca0106_ptr_read(emu, SAMPLE_RATE_TRACKER_STATUS, 0);
+	snd_iprintf(buffer, "Status: %s, %s, %s\n",
+		  (value & 0x100000) ? "Rate Locked" : "Not Rate Locked",
+		  (value & 0x200000) ? "SPDIF Locked" : "No SPDIF Lock",
+		  (value & 0x400000) ? "Audio Valid" : "No valid audio" );
+	snd_iprintf(buffer, "Estimated sample rate: %u\n", 
+		  ((value & 0xfffff) * 48000) / 0x8000 );
+	if (value & 0x200000) {
+		snd_iprintf(buffer, "IEC958/SPDIF input status:\n");
+        	value = snd_ca0106_ptr_read(emu, SPDIF_INPUT_STATUS, 0);
+		snd_ca0106_proc_dump_iec958(buffer, value);
+	}
+
+	snd_iprintf(buffer, "\n");
+}
+
+static void snd_ca0106_proc_reg_write32(struct snd_info_entry *entry, 
+				       struct snd_info_buffer *buffer)
+{
+	struct snd_ca0106 *emu = entry->private_data;
+	unsigned long flags;
+        char line[64];
+        u32 reg, val;
+        while (!snd_info_get_line(buffer, line, sizeof(line))) {
+                if (sscanf(line, "%x %x", &reg, &val) != 2)
+                        continue;
+		if (reg < 0x40 && val <= 0xffffffff) {
+			spin_lock_irqsave(&emu->emu_lock, flags);
+			outl(val, emu->port + (reg & 0xfffffffc));
+			spin_unlock_irqrestore(&emu->emu_lock, flags);
+		}
+        }
+}
+
+static void snd_ca0106_proc_reg_read32(struct snd_info_entry *entry, 
+				       struct snd_info_buffer *buffer)
+{
+	struct snd_ca0106 *emu = entry->private_data;
+	unsigned long value;
+	unsigned long flags;
+	int i;
+	snd_iprintf(buffer, "Registers:\n\n");
+	for(i = 0; i < 0x20; i+=4) {
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		value = inl(emu->port + i);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+		snd_iprintf(buffer, "Register %02X: %08lX\n", i, value);
+	}
+}
+
+static void snd_ca0106_proc_reg_read16(struct snd_info_entry *entry, 
+				       struct snd_info_buffer *buffer)
+{
+	struct snd_ca0106 *emu = entry->private_data;
+        unsigned int value;
+	unsigned long flags;
+	int i;
+	snd_iprintf(buffer, "Registers:\n\n");
+	for(i = 0; i < 0x20; i+=2) {
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		value = inw(emu->port + i);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+		snd_iprintf(buffer, "Register %02X: %04X\n", i, value);
+	}
+}
+
+static void snd_ca0106_proc_reg_read8(struct snd_info_entry *entry, 
+				       struct snd_info_buffer *buffer)
+{
+	struct snd_ca0106 *emu = entry->private_data;
+	unsigned int value;
+	unsigned long flags;
+	int i;
+	snd_iprintf(buffer, "Registers:\n\n");
+	for(i = 0; i < 0x20; i+=1) {
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		value = inb(emu->port + i);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+		snd_iprintf(buffer, "Register %02X: %02X\n", i, value);
+	}
+}
+
+static void snd_ca0106_proc_reg_read1(struct snd_info_entry *entry, 
+				       struct snd_info_buffer *buffer)
+{
+	struct snd_ca0106 *emu = entry->private_data;
+	unsigned long value;
+	int i,j;
+
+	snd_iprintf(buffer, "Registers\n");
+	for(i = 0; i < 0x40; i++) {
+		snd_iprintf(buffer, "%02X: ",i);
+		for (j = 0; j < 4; j++) {
+                  value = snd_ca0106_ptr_read(emu, i, j);
+		  snd_iprintf(buffer, "%08lX ", value);
+                }
+	        snd_iprintf(buffer, "\n");
+	}
+}
+
+static void snd_ca0106_proc_reg_read2(struct snd_info_entry *entry, 
+				       struct snd_info_buffer *buffer)
+{
+	struct snd_ca0106 *emu = entry->private_data;
+	unsigned long value;
+	int i,j;
+
+	snd_iprintf(buffer, "Registers\n");
+	for(i = 0x40; i < 0x80; i++) {
+		snd_iprintf(buffer, "%02X: ",i);
+		for (j = 0; j < 4; j++) {
+                  value = snd_ca0106_ptr_read(emu, i, j);
+		  snd_iprintf(buffer, "%08lX ", value);
+                }
+	        snd_iprintf(buffer, "\n");
+	}
+}
+
+static void snd_ca0106_proc_reg_write(struct snd_info_entry *entry, 
+				       struct snd_info_buffer *buffer)
+{
+	struct snd_ca0106 *emu = entry->private_data;
+        char line[64];
+        unsigned int reg, channel_id , val;
+        while (!snd_info_get_line(buffer, line, sizeof(line))) {
+                if (sscanf(line, "%x %x %x", &reg, &channel_id, &val) != 3)
+                        continue;
+		if (reg < 0x80 && val <= 0xffffffff && channel_id <= 3)
+                        snd_ca0106_ptr_write(emu, reg, channel_id, val);
+        }
+}
+
+static void snd_ca0106_proc_i2c_write(struct snd_info_entry *entry, 
+				       struct snd_info_buffer *buffer)
+{
+	struct snd_ca0106 *emu = entry->private_data;
+        char line[64];
+        unsigned int reg, val;
+        while (!snd_info_get_line(buffer, line, sizeof(line))) {
+                if (sscanf(line, "%x %x", &reg, &val) != 2)
+                        continue;
+                if ((reg <= 0x7f) || (val <= 0x1ff)) {
+                        snd_ca0106_i2c_write(emu, reg, val);
+		}
+        }
+}
+
+int __devinit snd_ca0106_proc_init(struct snd_ca0106 * emu)
+{
+	struct snd_info_entry *entry;
+	
+	if(! snd_card_proc_new(emu->card, "iec958", &entry))
+		snd_info_set_text_ops(entry, emu, snd_ca0106_proc_iec958);
+	if(! snd_card_proc_new(emu->card, "ca0106_reg32", &entry)) {
+		snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read32);
+		entry->c.text.write = snd_ca0106_proc_reg_write32;
+		entry->mode |= S_IWUSR;
+	}
+	if(! snd_card_proc_new(emu->card, "ca0106_reg16", &entry))
+		snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read16);
+	if(! snd_card_proc_new(emu->card, "ca0106_reg8", &entry))
+		snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read8);
+	if(! snd_card_proc_new(emu->card, "ca0106_regs1", &entry)) {
+		snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read1);
+		entry->c.text.write = snd_ca0106_proc_reg_write;
+		entry->mode |= S_IWUSR;
+	}
+	if(! snd_card_proc_new(emu->card, "ca0106_i2c", &entry)) {
+		entry->c.text.write = snd_ca0106_proc_i2c_write;
+		entry->private_data = emu;
+		entry->mode |= S_IWUSR;
+	}
+	if(! snd_card_proc_new(emu->card, "ca0106_regs2", &entry)) 
+		snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read2);
+	return 0;
+}
+
+#endif /* CONFIG_PROC_FS */
diff --git a/linux/sound/pci/ca0106/ca_midi.c b/linux/sound/pci/ca0106/ca_midi.c
new file mode 100644
index 0000000..c788511
--- /dev/null
+++ b/linux/sound/pci/ca0106/ca_midi.c
@@ -0,0 +1,316 @@
+/* 
+ *  Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de>
+ *  Creative Audio MIDI, for the CA0106 Driver
+ *  Version: 0.0.1
+ *
+ *  Changelog:
+ *    Implementation is based on mpu401 and emu10k1x and
+ *    tested with ca0106.
+ *    mpu401: Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *    emu10k1x: Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ *
+ *   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/spinlock.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+
+#include "ca_midi.h"
+
+#define ca_midi_write_data(midi, data)	midi->write(midi, data, 0)
+#define ca_midi_write_cmd(midi, data)	midi->write(midi, data, 1)
+#define ca_midi_read_data(midi)		midi->read(midi, 0)
+#define ca_midi_read_stat(midi)		midi->read(midi, 1)
+#define ca_midi_input_avail(midi)	(!(ca_midi_read_stat(midi) & midi->input_avail))
+#define ca_midi_output_ready(midi)	(!(ca_midi_read_stat(midi) & midi->output_ready))
+
+static void ca_midi_clear_rx(struct snd_ca_midi *midi)
+{
+	int timeout = 100000;
+	for (; timeout > 0 && ca_midi_input_avail(midi); timeout--)
+		ca_midi_read_data(midi);
+#ifdef CONFIG_SND_DEBUG
+	if (timeout <= 0)
+		snd_printk(KERN_ERR "ca_midi_clear_rx: timeout (status = 0x%x)\n",
+			   ca_midi_read_stat(midi));
+#endif
+}
+
+static void ca_midi_interrupt(struct snd_ca_midi *midi, unsigned int status)
+{
+	unsigned char byte;
+
+	if (midi->rmidi == NULL) {
+		midi->interrupt_disable(midi,midi->tx_enable | midi->rx_enable);
+		return;
+	}
+
+	spin_lock(&midi->input_lock);
+	if ((status & midi->ipr_rx) && ca_midi_input_avail(midi)) {
+		if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
+			ca_midi_clear_rx(midi);
+		} else {
+			byte = ca_midi_read_data(midi);
+			if(midi->substream_input)
+				snd_rawmidi_receive(midi->substream_input, &byte, 1);
+
+
+		}
+	}
+	spin_unlock(&midi->input_lock);
+
+	spin_lock(&midi->output_lock);
+	if ((status & midi->ipr_tx) && ca_midi_output_ready(midi)) {
+		if (midi->substream_output &&
+		    snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) {
+			ca_midi_write_data(midi, byte);
+		} else {
+			midi->interrupt_disable(midi,midi->tx_enable);
+		}
+	}
+	spin_unlock(&midi->output_lock);
+
+}
+
+static void ca_midi_cmd(struct snd_ca_midi *midi, unsigned char cmd, int ack)
+{
+	unsigned long flags;
+	int timeout, ok;
+
+	spin_lock_irqsave(&midi->input_lock, flags);
+	ca_midi_write_data(midi, 0x00);
+	/* ca_midi_clear_rx(midi); */
+
+	ca_midi_write_cmd(midi, cmd);
+	if (ack) {
+		ok = 0;
+		timeout = 10000;
+		while (!ok && timeout-- > 0) {
+			if (ca_midi_input_avail(midi)) {
+				if (ca_midi_read_data(midi) == midi->ack)
+					ok = 1;
+			}
+		}
+		if (!ok && ca_midi_read_data(midi) == midi->ack)
+			ok = 1;
+	} else {
+		ok = 1;
+	}
+	spin_unlock_irqrestore(&midi->input_lock, flags);
+	if (!ok)
+		snd_printk(KERN_ERR "ca_midi_cmd: 0x%x failed at 0x%x (status = 0x%x, data = 0x%x)!!!\n",
+			   cmd,
+			   midi->get_dev_id_port(midi->dev_id),
+			   ca_midi_read_stat(midi),
+			   ca_midi_read_data(midi));
+}
+
+static int ca_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_ca_midi *midi = substream->rmidi->private_data;
+	unsigned long flags;
+	
+	if (snd_BUG_ON(!midi->dev_id))
+		return -ENXIO;
+	spin_lock_irqsave(&midi->open_lock, flags);
+	midi->midi_mode |= CA_MIDI_MODE_INPUT;
+	midi->substream_input = substream;
+	if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		ca_midi_cmd(midi, midi->reset, 1);
+		ca_midi_cmd(midi, midi->enter_uart, 1);
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return 0;
+}
+
+static int ca_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_ca_midi *midi = substream->rmidi->private_data;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!midi->dev_id))
+		return -ENXIO;
+	spin_lock_irqsave(&midi->open_lock, flags);
+	midi->midi_mode |= CA_MIDI_MODE_OUTPUT;
+	midi->substream_output = substream;
+	if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		ca_midi_cmd(midi, midi->reset, 1);
+		ca_midi_cmd(midi, midi->enter_uart, 1);
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return 0;
+}
+
+static int ca_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_ca_midi *midi = substream->rmidi->private_data;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!midi->dev_id))
+		return -ENXIO;
+	spin_lock_irqsave(&midi->open_lock, flags);
+	midi->interrupt_disable(midi,midi->rx_enable);
+	midi->midi_mode &= ~CA_MIDI_MODE_INPUT;
+	midi->substream_input = NULL;
+	if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		ca_midi_cmd(midi, midi->reset, 0);
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return 0;
+}
+
+static int ca_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_ca_midi *midi = substream->rmidi->private_data;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!midi->dev_id))
+		return -ENXIO;
+	
+	spin_lock_irqsave(&midi->open_lock, flags);
+
+	midi->interrupt_disable(midi,midi->tx_enable);
+	midi->midi_mode &= ~CA_MIDI_MODE_OUTPUT;
+	midi->substream_output = NULL;
+	
+	if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		ca_midi_cmd(midi, midi->reset, 0);
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return 0;
+}
+
+static void ca_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_ca_midi *midi = substream->rmidi->private_data;
+
+	if (snd_BUG_ON(!midi->dev_id))
+		return;
+
+	if (up) {
+		midi->interrupt_enable(midi,midi->rx_enable);
+	} else {
+		midi->interrupt_disable(midi, midi->rx_enable);
+	}
+}
+
+static void ca_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_ca_midi *midi = substream->rmidi->private_data;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!midi->dev_id))
+		return;
+
+	if (up) {
+		int max = 4;
+		unsigned char byte;
+
+		spin_lock_irqsave(&midi->output_lock, flags);
+	
+		/* try to send some amount of bytes here before interrupts */
+		while (max > 0) {
+			if (ca_midi_output_ready(midi)) {
+				if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT) ||
+				    snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+					/* no more data */
+					spin_unlock_irqrestore(&midi->output_lock, flags);
+					return;
+				}
+				ca_midi_write_data(midi, byte);
+				max--;
+			} else {
+				break;
+			}
+		}
+
+		spin_unlock_irqrestore(&midi->output_lock, flags);
+		midi->interrupt_enable(midi,midi->tx_enable);
+
+	} else {
+		midi->interrupt_disable(midi,midi->tx_enable);
+	}
+}
+
+static struct snd_rawmidi_ops ca_midi_output =
+{
+	.open =		ca_midi_output_open,
+	.close =	ca_midi_output_close,
+	.trigger =	ca_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops ca_midi_input =
+{
+	.open =		ca_midi_input_open,
+	.close =	ca_midi_input_close,
+	.trigger =	ca_midi_input_trigger,
+};
+
+static void ca_midi_free(struct snd_ca_midi *midi)
+{
+	midi->interrupt = NULL;
+	midi->interrupt_enable = NULL;
+	midi->interrupt_disable = NULL;
+	midi->read = NULL;
+	midi->write = NULL;
+	midi->get_dev_id_card = NULL;
+	midi->get_dev_id_port = NULL;
+	midi->rmidi = NULL;
+}
+
+static void ca_rmidi_free(struct snd_rawmidi *rmidi)
+{
+	ca_midi_free(rmidi->private_data);
+}
+
+int __devinit ca_midi_init(void *dev_id, struct snd_ca_midi *midi, int device, char *name)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	if ((err = snd_rawmidi_new(midi->get_dev_id_card(midi->dev_id), name, device, 1, 1, &rmidi)) < 0)
+		return err;
+
+	midi->dev_id = dev_id;
+	midi->interrupt = ca_midi_interrupt;
+
+	spin_lock_init(&midi->open_lock);
+	spin_lock_init(&midi->input_lock);
+	spin_lock_init(&midi->output_lock);
+
+	strcpy(rmidi->name, name);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &ca_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &ca_midi_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+	                     SNDRV_RAWMIDI_INFO_INPUT |
+	                     SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = midi;
+	rmidi->private_free = ca_rmidi_free;
+	
+	midi->rmidi = rmidi;
+	return 0;
+}
+
diff --git a/linux/sound/pci/ca0106/ca_midi.h b/linux/sound/pci/ca0106/ca_midi.h
new file mode 100644
index 0000000..922ed3e
--- /dev/null
+++ b/linux/sound/pci/ca0106/ca_midi.h
@@ -0,0 +1,66 @@
+/* 
+ *  Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de>
+ *  Creative Audio MIDI, for the CA0106 Driver
+ *  Version: 0.0.1
+ *
+ *  Changelog:
+ *    See ca_midi.c
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/spinlock.h>
+#include <sound/rawmidi.h>
+#include <sound/mpu401.h>
+
+#define CA_MIDI_MODE_INPUT	MPU401_MODE_INPUT
+#define CA_MIDI_MODE_OUTPUT	MPU401_MODE_OUTPUT
+
+struct snd_ca_midi {
+
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *substream_input;
+	struct snd_rawmidi_substream *substream_output;
+
+	void *dev_id;
+
+	spinlock_t input_lock;
+	spinlock_t output_lock;
+	spinlock_t open_lock;
+
+	unsigned int channel;
+
+	unsigned int midi_mode;
+	int port;
+	int tx_enable, rx_enable;
+	int ipr_tx, ipr_rx;            
+	
+	int input_avail, output_ready;
+	int ack, reset, enter_uart;
+
+	void (*interrupt)(struct snd_ca_midi *midi, unsigned int status);
+	void (*interrupt_enable)(struct snd_ca_midi *midi, int intr);
+	void (*interrupt_disable)(struct snd_ca_midi *midi, int intr);
+
+	unsigned char (*read)(struct snd_ca_midi *midi, int idx);
+	void (*write)(struct snd_ca_midi *midi, int data, int idx);
+
+	/* get info from dev_id */
+	struct snd_card *(*get_dev_id_card)(void *dev_id);
+	int (*get_dev_id_port)(void *dev_id);
+};
+
+int ca_midi_init(void *card, struct snd_ca_midi *midi, int device, char *name);
diff --git a/linux/sound/pci/cmipci.c b/linux/sound/pci/cmipci.c
new file mode 100644
index 0000000..329968e
--- /dev/null
+++ b/linux/sound/pci/cmipci.c
@@ -0,0 +1,3429 @@
+/*
+ * Driver for C-Media CMI8338 and 8738 PCI soundcards.
+ * Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+ 
+/* Does not work. Warning may block system in capture mode */
+/* #define USE_VAR48KRATE */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/sb.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("C-Media CMI8x38 PCI");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{C-Media,CMI8738},"
+		"{C-Media,CMI8738B},"
+		"{C-Media,CMI8338A},"
+		"{C-Media,CMI8338B}}");
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+#define SUPPORT_JOYSTICK 1
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable switches */
+static long mpu_port[SNDRV_CARDS];
+static long fm_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)]=1};
+static int soft_ac3[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)]=1};
+#ifdef SUPPORT_JOYSTICK
+static int joystick_port[SNDRV_CARDS];
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for C-Media PCI soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for C-Media PCI soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable C-Media PCI soundcard.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port.");
+module_param_array(soft_ac3, bool, NULL, 0444);
+MODULE_PARM_DESC(soft_ac3, "Sofware-conversion of raw SPDIF packets (model 033 only).");
+#ifdef SUPPORT_JOYSTICK
+module_param_array(joystick_port, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_port, "Joystick port address.");
+#endif
+
+/*
+ * CM8x38 registers definition
+ */
+
+#define CM_REG_FUNCTRL0		0x00
+#define CM_RST_CH1		0x00080000
+#define CM_RST_CH0		0x00040000
+#define CM_CHEN1		0x00020000	/* ch1: enable */
+#define CM_CHEN0		0x00010000	/* ch0: enable */
+#define CM_PAUSE1		0x00000008	/* ch1: pause */
+#define CM_PAUSE0		0x00000004	/* ch0: pause */
+#define CM_CHADC1		0x00000002	/* ch1, 0:playback, 1:record */
+#define CM_CHADC0		0x00000001	/* ch0, 0:playback, 1:record */
+
+#define CM_REG_FUNCTRL1		0x04
+#define CM_DSFC_MASK		0x0000E000	/* channel 1 (DAC?) sampling frequency */
+#define CM_DSFC_SHIFT		13
+#define CM_ASFC_MASK		0x00001C00	/* channel 0 (ADC?) sampling frequency */
+#define CM_ASFC_SHIFT		10
+#define CM_SPDF_1		0x00000200	/* SPDIF IN/OUT at channel B */
+#define CM_SPDF_0		0x00000100	/* SPDIF OUT only channel A */
+#define CM_SPDFLOOP		0x00000080	/* ext. SPDIIF/IN -> OUT loopback */
+#define CM_SPDO2DAC		0x00000040	/* SPDIF/OUT can be heard from internal DAC */
+#define CM_INTRM		0x00000020	/* master control block (MCB) interrupt enabled */
+#define CM_BREQ			0x00000010	/* bus master enabled */
+#define CM_VOICE_EN		0x00000008	/* legacy voice (SB16,FM) */
+#define CM_UART_EN		0x00000004	/* legacy UART */
+#define CM_JYSTK_EN		0x00000002	/* legacy joystick */
+#define CM_ZVPORT		0x00000001	/* ZVPORT */
+
+#define CM_REG_CHFORMAT		0x08
+
+#define CM_CHB3D5C		0x80000000	/* 5,6 channels */
+#define CM_FMOFFSET2		0x40000000	/* initial FM PCM offset 2 when Fmute=1 */
+#define CM_CHB3D		0x20000000	/* 4 channels */
+
+#define CM_CHIP_MASK1		0x1f000000
+#define CM_CHIP_037		0x01000000
+#define CM_SETLAT48		0x00800000	/* set latency timer 48h */
+#define CM_EDGEIRQ		0x00400000	/* emulated edge trigger legacy IRQ */
+#define CM_SPD24SEL39		0x00200000	/* 24-bit spdif: model 039 */
+#define CM_AC3EN1		0x00100000	/* enable AC3: model 037 */
+#define CM_SPDIF_SELECT1	0x00080000	/* for model <= 037 ? */
+#define CM_SPD24SEL		0x00020000	/* 24bit spdif: model 037 */
+/* #define CM_SPDIF_INVERSE	0x00010000 */ /* ??? */
+
+#define CM_ADCBITLEN_MASK	0x0000C000	
+#define CM_ADCBITLEN_16		0x00000000
+#define CM_ADCBITLEN_15		0x00004000
+#define CM_ADCBITLEN_14		0x00008000
+#define CM_ADCBITLEN_13		0x0000C000
+
+#define CM_ADCDACLEN_MASK	0x00003000	/* model 037 */
+#define CM_ADCDACLEN_060	0x00000000
+#define CM_ADCDACLEN_066	0x00001000
+#define CM_ADCDACLEN_130	0x00002000
+#define CM_ADCDACLEN_280	0x00003000
+
+#define CM_ADCDLEN_MASK		0x00003000	/* model 039 */
+#define CM_ADCDLEN_ORIGINAL	0x00000000
+#define CM_ADCDLEN_EXTRA	0x00001000
+#define CM_ADCDLEN_24K		0x00002000
+#define CM_ADCDLEN_WEIGHT	0x00003000
+
+#define CM_CH1_SRATE_176K	0x00000800
+#define CM_CH1_SRATE_96K	0x00000800	/* model 055? */
+#define CM_CH1_SRATE_88K	0x00000400
+#define CM_CH0_SRATE_176K	0x00000200
+#define CM_CH0_SRATE_96K	0x00000200	/* model 055? */
+#define CM_CH0_SRATE_88K	0x00000100
+#define CM_CH0_SRATE_128K	0x00000300
+#define CM_CH0_SRATE_MASK	0x00000300
+
+#define CM_SPDIF_INVERSE2	0x00000080	/* model 055? */
+#define CM_DBLSPDS		0x00000040	/* double SPDIF sample rate 88.2/96 */
+#define CM_POLVALID		0x00000020	/* inverse SPDIF/IN valid bit */
+#define CM_SPDLOCKED		0x00000010
+
+#define CM_CH1FMT_MASK		0x0000000C	/* bit 3: 16 bits, bit 2: stereo */
+#define CM_CH1FMT_SHIFT		2
+#define CM_CH0FMT_MASK		0x00000003	/* bit 1: 16 bits, bit 0: stereo */
+#define CM_CH0FMT_SHIFT		0
+
+#define CM_REG_INT_HLDCLR	0x0C
+#define CM_CHIP_MASK2		0xff000000
+#define CM_CHIP_8768		0x20000000
+#define CM_CHIP_055		0x08000000
+#define CM_CHIP_039		0x04000000
+#define CM_CHIP_039_6CH		0x01000000
+#define CM_UNKNOWN_INT_EN	0x00080000	/* ? */
+#define CM_TDMA_INT_EN		0x00040000
+#define CM_CH1_INT_EN		0x00020000
+#define CM_CH0_INT_EN		0x00010000
+
+#define CM_REG_INT_STATUS	0x10
+#define CM_INTR			0x80000000
+#define CM_VCO			0x08000000	/* Voice Control? CMI8738 */
+#define CM_MCBINT		0x04000000	/* Master Control Block abort cond.? */
+#define CM_UARTINT		0x00010000
+#define CM_LTDMAINT		0x00008000
+#define CM_HTDMAINT		0x00004000
+#define CM_XDO46		0x00000080	/* Modell 033? Direct programming EEPROM (read data register) */
+#define CM_LHBTOG		0x00000040	/* High/Low status from DMA ctrl register */
+#define CM_LEG_HDMA		0x00000020	/* Legacy is in High DMA channel */
+#define CM_LEG_STEREO		0x00000010	/* Legacy is in Stereo mode */
+#define CM_CH1BUSY		0x00000008
+#define CM_CH0BUSY		0x00000004
+#define CM_CHINT1		0x00000002
+#define CM_CHINT0		0x00000001
+
+#define CM_REG_LEGACY_CTRL	0x14
+#define CM_NXCHG		0x80000000	/* don't map base reg dword->sample */
+#define CM_VMPU_MASK		0x60000000	/* MPU401 i/o port address */
+#define CM_VMPU_330		0x00000000
+#define CM_VMPU_320		0x20000000
+#define CM_VMPU_310		0x40000000
+#define CM_VMPU_300		0x60000000
+#define CM_ENWR8237		0x10000000	/* enable bus master to write 8237 base reg */
+#define CM_VSBSEL_MASK		0x0C000000	/* SB16 base address */
+#define CM_VSBSEL_220		0x00000000
+#define CM_VSBSEL_240		0x04000000
+#define CM_VSBSEL_260		0x08000000
+#define CM_VSBSEL_280		0x0C000000
+#define CM_FMSEL_MASK		0x03000000	/* FM OPL3 base address */
+#define CM_FMSEL_388		0x00000000
+#define CM_FMSEL_3C8		0x01000000
+#define CM_FMSEL_3E0		0x02000000
+#define CM_FMSEL_3E8		0x03000000
+#define CM_ENSPDOUT		0x00800000	/* enable XSPDIF/OUT to I/O interface */
+#define CM_SPDCOPYRHT		0x00400000	/* spdif in/out copyright bit */
+#define CM_DAC2SPDO		0x00200000	/* enable wave+fm_midi -> SPDIF/OUT */
+#define CM_INVIDWEN		0x00100000	/* internal vendor ID write enable, model 039? */
+#define CM_SETRETRY		0x00100000	/* 0: legacy i/o wait (default), 1: legacy i/o bus retry */
+#define CM_C_EEACCESS		0x00080000	/* direct programming eeprom regs */
+#define CM_C_EECS		0x00040000
+#define CM_C_EEDI46		0x00020000
+#define CM_C_EECK46		0x00010000
+#define CM_CHB3D6C		0x00008000	/* 5.1 channels support */
+#define CM_CENTR2LIN		0x00004000	/* line-in as center out */
+#define CM_BASE2LIN		0x00002000	/* line-in as bass out */
+#define CM_EXBASEN		0x00001000	/* external bass input enable */
+
+#define CM_REG_MISC_CTRL	0x18
+#define CM_PWD			0x80000000	/* power down */
+#define CM_RESET		0x40000000
+#define CM_SFIL_MASK		0x30000000	/* filter control at front end DAC, model 037? */
+#define CM_VMGAIN		0x10000000	/* analog master amp +6dB, model 039? */
+#define CM_TXVX			0x08000000	/* model 037? */
+#define CM_N4SPK3D		0x04000000	/* copy front to rear */
+#define CM_SPDO5V		0x02000000	/* 5V spdif output (1 = 0.5v (coax)) */
+#define CM_SPDIF48K		0x01000000	/* write */
+#define CM_SPATUS48K		0x01000000	/* read */
+#define CM_ENDBDAC		0x00800000	/* enable double dac */
+#define CM_XCHGDAC		0x00400000	/* 0: front=ch0, 1: front=ch1 */
+#define CM_SPD32SEL		0x00200000	/* 0: 16bit SPDIF, 1: 32bit */
+#define CM_SPDFLOOPI		0x00100000	/* int. SPDIF-OUT -> int. IN */
+#define CM_FM_EN		0x00080000	/* enable legacy FM */
+#define CM_AC3EN2		0x00040000	/* enable AC3: model 039 */
+#define CM_ENWRASID		0x00010000	/* choose writable internal SUBID (audio) */
+#define CM_VIDWPDSB		0x00010000	/* model 037? */
+#define CM_SPDF_AC97		0x00008000	/* 0: SPDIF/OUT 44.1K, 1: 48K */
+#define CM_MASK_EN		0x00004000	/* activate channel mask on legacy DMA */
+#define CM_ENWRMSID		0x00002000	/* choose writable internal SUBID (modem) */
+#define CM_VIDWPPRT		0x00002000	/* model 037? */
+#define CM_SFILENB		0x00001000	/* filter stepping at front end DAC, model 037? */
+#define CM_MMODE_MASK		0x00000E00	/* model DAA interface mode */
+#define CM_SPDIF_SELECT2	0x00000100	/* for model > 039 ? */
+#define CM_ENCENTER		0x00000080
+#define CM_FLINKON		0x00000040	/* force modem link detection on, model 037 */
+#define CM_MUTECH1		0x00000040	/* mute PCI ch1 to DAC */
+#define CM_FLINKOFF		0x00000020	/* force modem link detection off, model 037 */
+#define CM_MIDSMP		0x00000010	/* 1/2 interpolation at front end DAC */
+#define CM_UPDDMA_MASK		0x0000000C	/* TDMA position update notification */
+#define CM_UPDDMA_2048		0x00000000
+#define CM_UPDDMA_1024		0x00000004
+#define CM_UPDDMA_512		0x00000008
+#define CM_UPDDMA_256		0x0000000C		
+#define CM_TWAIT_MASK		0x00000003	/* model 037 */
+#define CM_TWAIT1		0x00000002	/* FM i/o cycle, 0: 48, 1: 64 PCICLKs */
+#define CM_TWAIT0		0x00000001	/* i/o cycle, 0: 4, 1: 6 PCICLKs */
+
+#define CM_REG_TDMA_POSITION	0x1C
+#define CM_TDMA_CNT_MASK	0xFFFF0000	/* current byte/word count */
+#define CM_TDMA_ADR_MASK	0x0000FFFF	/* current address */
+
+	/* byte */
+#define CM_REG_MIXER0		0x20
+#define CM_REG_SBVR		0x20		/* write: sb16 version */
+#define CM_REG_DEV		0x20		/* read: hardware device version */
+
+#define CM_REG_MIXER21		0x21
+#define CM_UNKNOWN_21_MASK	0x78		/* ? */
+#define CM_X_ADPCM		0x04		/* SB16 ADPCM enable */
+#define CM_PROINV		0x02		/* SBPro left/right channel switching */
+#define CM_X_SB16		0x01		/* SB16 compatible */
+
+#define CM_REG_SB16_DATA	0x22
+#define CM_REG_SB16_ADDR	0x23
+
+#define CM_REFFREQ_XIN		(315*1000*1000)/22	/* 14.31818 Mhz reference clock frequency pin XIN */
+#define CM_ADCMULT_XIN		512			/* Guessed (487 best for 44.1kHz, not for 88/176kHz) */
+#define CM_TOLERANCE_RATE	0.001			/* Tolerance sample rate pitch (1000ppm) */
+#define CM_MAXIMUM_RATE		80000000		/* Note more than 80MHz */
+
+#define CM_REG_MIXER1		0x24
+#define CM_FMMUTE		0x80	/* mute FM */
+#define CM_FMMUTE_SHIFT		7
+#define CM_WSMUTE		0x40	/* mute PCM */
+#define CM_WSMUTE_SHIFT		6
+#define CM_REAR2LIN		0x20	/* lin-in -> rear line out */
+#define CM_REAR2LIN_SHIFT	5
+#define CM_REAR2FRONT		0x10	/* exchange rear/front */
+#define CM_REAR2FRONT_SHIFT	4
+#define CM_WAVEINL		0x08	/* digital wave rec. left chan */
+#define CM_WAVEINL_SHIFT	3
+#define CM_WAVEINR		0x04	/* digical wave rec. right */
+#define CM_WAVEINR_SHIFT	2
+#define CM_X3DEN		0x02	/* 3D surround enable */
+#define CM_X3DEN_SHIFT		1
+#define CM_CDPLAY		0x01	/* enable SPDIF/IN PCM -> DAC */
+#define CM_CDPLAY_SHIFT		0
+
+#define CM_REG_MIXER2		0x25
+#define CM_RAUXREN		0x80	/* AUX right capture */
+#define CM_RAUXREN_SHIFT	7
+#define CM_RAUXLEN		0x40	/* AUX left capture */
+#define CM_RAUXLEN_SHIFT	6
+#define CM_VAUXRM		0x20	/* AUX right mute */
+#define CM_VAUXRM_SHIFT		5
+#define CM_VAUXLM		0x10	/* AUX left mute */
+#define CM_VAUXLM_SHIFT		4
+#define CM_VADMIC_MASK		0x0e	/* mic gain level (0-3) << 1 */
+#define CM_VADMIC_SHIFT		1
+#define CM_MICGAINZ		0x01	/* mic boost */
+#define CM_MICGAINZ_SHIFT	0
+
+#define CM_REG_MIXER3		0x24
+#define CM_REG_AUX_VOL		0x26
+#define CM_VAUXL_MASK		0xf0
+#define CM_VAUXR_MASK		0x0f
+
+#define CM_REG_MISC		0x27
+#define CM_UNKNOWN_27_MASK	0xd8	/* ? */
+#define CM_XGPO1		0x20
+// #define CM_XGPBIO		0x04
+#define CM_MIC_CENTER_LFE	0x04	/* mic as center/lfe out? (model 039 or later?) */
+#define CM_SPDIF_INVERSE	0x04	/* spdif input phase inverse (model 037) */
+#define CM_SPDVALID		0x02	/* spdif input valid check */
+#define CM_DMAUTO		0x01	/* SB16 DMA auto detect */
+
+#define CM_REG_AC97		0x28	/* hmmm.. do we have ac97 link? */
+/*
+ * For CMI-8338 (0x28 - 0x2b) .. is this valid for CMI-8738
+ * or identical with AC97 codec?
+ */
+#define CM_REG_EXTERN_CODEC	CM_REG_AC97
+
+/*
+ * MPU401 pci port index address 0x40 - 0x4f (CMI-8738 spec ver. 0.6)
+ */
+#define CM_REG_MPU_PCI		0x40
+
+/*
+ * FM pci port index address 0x50 - 0x5f (CMI-8738 spec ver. 0.6)
+ */
+#define CM_REG_FM_PCI		0x50
+
+/*
+ * access from SB-mixer port
+ */
+#define CM_REG_EXTENT_IND	0xf0
+#define CM_VPHONE_MASK		0xe0	/* Phone volume control (0-3) << 5 */
+#define CM_VPHONE_SHIFT		5
+#define CM_VPHOM		0x10	/* Phone mute control */
+#define CM_VSPKM		0x08	/* Speaker mute control, default high */
+#define CM_RLOOPREN		0x04    /* Rec. R-channel enable */
+#define CM_RLOOPLEN		0x02	/* Rec. L-channel enable */
+#define CM_VADMIC3		0x01	/* Mic record boost */
+
+/*
+ * CMI-8338 spec ver 0.5 (this is not valid for CMI-8738):
+ * the 8 registers 0xf8 - 0xff are used for programming m/n counter by the PLL
+ * unit (readonly?).
+ */
+#define CM_REG_PLL		0xf8
+
+/*
+ * extended registers
+ */
+#define CM_REG_CH0_FRAME1	0x80	/* write: base address */
+#define CM_REG_CH0_FRAME2	0x84	/* read: current address */
+#define CM_REG_CH1_FRAME1	0x88	/* 0-15: count of samples at bus master; buffer size */
+#define CM_REG_CH1_FRAME2	0x8C	/* 16-31: count of samples at codec; fragment size */
+
+#define CM_REG_EXT_MISC		0x90
+#define CM_ADC48K44K		0x10000000	/* ADC parameters group, 0: 44k, 1: 48k */
+#define CM_CHB3D8C		0x00200000	/* 7.1 channels support */
+#define CM_SPD32FMT		0x00100000	/* SPDIF/IN 32k sample rate */
+#define CM_ADC2SPDIF		0x00080000	/* ADC output to SPDIF/OUT */
+#define CM_SHAREADC		0x00040000	/* DAC in ADC as Center/LFE */
+#define CM_REALTCMP		0x00020000	/* monitor the CMPL/CMPR of ADC */
+#define CM_INVLRCK		0x00010000	/* invert ZVPORT's LRCK */
+#define CM_UNKNOWN_90_MASK	0x0000FFFF	/* ? */
+
+/*
+ * size of i/o region
+ */
+#define CM_EXTENT_CODEC	  0x100
+#define CM_EXTENT_MIDI	  0x2
+#define CM_EXTENT_SYNTH	  0x4
+
+
+/*
+ * channels for playback / capture
+ */
+#define CM_CH_PLAY	0
+#define CM_CH_CAPT	1
+
+/*
+ * flags to check device open/close
+ */
+#define CM_OPEN_NONE	0
+#define CM_OPEN_CH_MASK	0x01
+#define CM_OPEN_DAC	0x10
+#define CM_OPEN_ADC	0x20
+#define CM_OPEN_SPDIF	0x40
+#define CM_OPEN_MCHAN	0x80
+#define CM_OPEN_PLAYBACK	(CM_CH_PLAY | CM_OPEN_DAC)
+#define CM_OPEN_PLAYBACK2	(CM_CH_CAPT | CM_OPEN_DAC)
+#define CM_OPEN_PLAYBACK_MULTI	(CM_CH_PLAY | CM_OPEN_DAC | CM_OPEN_MCHAN)
+#define CM_OPEN_CAPTURE		(CM_CH_CAPT | CM_OPEN_ADC)
+#define CM_OPEN_SPDIF_PLAYBACK	(CM_CH_PLAY | CM_OPEN_DAC | CM_OPEN_SPDIF)
+#define CM_OPEN_SPDIF_CAPTURE	(CM_CH_CAPT | CM_OPEN_ADC | CM_OPEN_SPDIF)
+
+
+#if CM_CH_PLAY == 1
+#define CM_PLAYBACK_SRATE_176K	CM_CH1_SRATE_176K
+#define CM_PLAYBACK_SPDF	CM_SPDF_1
+#define CM_CAPTURE_SPDF		CM_SPDF_0
+#else
+#define CM_PLAYBACK_SRATE_176K CM_CH0_SRATE_176K
+#define CM_PLAYBACK_SPDF	CM_SPDF_0
+#define CM_CAPTURE_SPDF		CM_SPDF_1
+#endif
+
+
+/*
+ * driver data
+ */
+
+struct cmipci_pcm {
+	struct snd_pcm_substream *substream;
+	u8 running;		/* dac/adc running? */
+	u8 fmt;			/* format bits */
+	u8 is_dac;
+	u8 needs_silencing;
+	unsigned int dma_size;	/* in frames */
+	unsigned int shift;
+	unsigned int ch;	/* channel (0/1) */
+	unsigned int offset;	/* physical address of the buffer */
+};
+
+/* mixer elements toggled/resumed during ac3 playback */
+struct cmipci_mixer_auto_switches {
+	const char *name;	/* switch to toggle */
+	int toggle_on;		/* value to change when ac3 mode */
+};
+static const struct cmipci_mixer_auto_switches cm_saved_mixer[] = {
+	{"PCM Playback Switch", 0},
+	{"IEC958 Output Switch", 1},
+	{"IEC958 Mix Analog", 0},
+	// {"IEC958 Out To DAC", 1}, // no longer used
+	{"IEC958 Loop", 0},
+};
+#define CM_SAVED_MIXERS		ARRAY_SIZE(cm_saved_mixer)
+
+struct cmipci {
+	struct snd_card *card;
+
+	struct pci_dev *pci;
+	unsigned int device;	/* device ID */
+	int irq;
+
+	unsigned long iobase;
+	unsigned int ctrl;	/* FUNCTRL0 current value */
+
+	struct snd_pcm *pcm;		/* DAC/ADC PCM */
+	struct snd_pcm *pcm2;	/* 2nd DAC */
+	struct snd_pcm *pcm_spdif;	/* SPDIF */
+
+	int chip_version;
+	int max_channels;
+	unsigned int can_ac3_sw: 1;
+	unsigned int can_ac3_hw: 1;
+	unsigned int can_multi_ch: 1;
+	unsigned int can_96k: 1;	/* samplerate above 48k */
+	unsigned int do_soft_ac3: 1;
+
+	unsigned int spdif_playback_avail: 1;	/* spdif ready? */
+	unsigned int spdif_playback_enabled: 1;	/* spdif switch enabled? */
+	int spdif_counter;	/* for software AC3 */
+
+	unsigned int dig_status;
+	unsigned int dig_pcm_status;
+
+	struct snd_pcm_hardware *hw_info[3]; /* for playbacks */
+
+	int opened[2];	/* open mode */
+	struct mutex open_mutex;
+
+	unsigned int mixer_insensitive: 1;
+	struct snd_kcontrol *mixer_res_ctl[CM_SAVED_MIXERS];
+	int mixer_res_status[CM_SAVED_MIXERS];
+
+	struct cmipci_pcm channel[2];	/* ch0 - DAC, ch1 - ADC or 2nd DAC */
+
+	/* external MIDI */
+	struct snd_rawmidi *rmidi;
+
+#ifdef SUPPORT_JOYSTICK
+	struct gameport *gameport;
+#endif
+
+	spinlock_t reg_lock;
+
+#ifdef CONFIG_PM
+	unsigned int saved_regs[0x20];
+	unsigned char saved_mixers[0x20];
+#endif
+};
+
+
+/* read/write operations for dword register */
+static inline void snd_cmipci_write(struct cmipci *cm, unsigned int cmd, unsigned int data)
+{
+	outl(data, cm->iobase + cmd);
+}
+
+static inline unsigned int snd_cmipci_read(struct cmipci *cm, unsigned int cmd)
+{
+	return inl(cm->iobase + cmd);
+}
+
+/* read/write operations for word register */
+static inline void snd_cmipci_write_w(struct cmipci *cm, unsigned int cmd, unsigned short data)
+{
+	outw(data, cm->iobase + cmd);
+}
+
+static inline unsigned short snd_cmipci_read_w(struct cmipci *cm, unsigned int cmd)
+{
+	return inw(cm->iobase + cmd);
+}
+
+/* read/write operations for byte register */
+static inline void snd_cmipci_write_b(struct cmipci *cm, unsigned int cmd, unsigned char data)
+{
+	outb(data, cm->iobase + cmd);
+}
+
+static inline unsigned char snd_cmipci_read_b(struct cmipci *cm, unsigned int cmd)
+{
+	return inb(cm->iobase + cmd);
+}
+
+/* bit operations for dword register */
+static int snd_cmipci_set_bit(struct cmipci *cm, unsigned int cmd, unsigned int flag)
+{
+	unsigned int val, oval;
+	val = oval = inl(cm->iobase + cmd);
+	val |= flag;
+	if (val == oval)
+		return 0;
+	outl(val, cm->iobase + cmd);
+	return 1;
+}
+
+static int snd_cmipci_clear_bit(struct cmipci *cm, unsigned int cmd, unsigned int flag)
+{
+	unsigned int val, oval;
+	val = oval = inl(cm->iobase + cmd);
+	val &= ~flag;
+	if (val == oval)
+		return 0;
+	outl(val, cm->iobase + cmd);
+	return 1;
+}
+
+/* bit operations for byte register */
+static int snd_cmipci_set_bit_b(struct cmipci *cm, unsigned int cmd, unsigned char flag)
+{
+	unsigned char val, oval;
+	val = oval = inb(cm->iobase + cmd);
+	val |= flag;
+	if (val == oval)
+		return 0;
+	outb(val, cm->iobase + cmd);
+	return 1;
+}
+
+static int snd_cmipci_clear_bit_b(struct cmipci *cm, unsigned int cmd, unsigned char flag)
+{
+	unsigned char val, oval;
+	val = oval = inb(cm->iobase + cmd);
+	val &= ~flag;
+	if (val == oval)
+		return 0;
+	outb(val, cm->iobase + cmd);
+	return 1;
+}
+
+
+/*
+ * PCM interface
+ */
+
+/*
+ * calculate frequency
+ */
+
+static unsigned int rates[] = { 5512, 11025, 22050, 44100, 8000, 16000, 32000, 48000 };
+
+static unsigned int snd_cmipci_rate_freq(unsigned int rate)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(rates); i++) {
+		if (rates[i] == rate)
+			return i;
+	}
+	snd_BUG();
+	return 0;
+}
+
+#ifdef USE_VAR48KRATE
+/*
+ * Determine PLL values for frequency setup, maybe the CMI8338 (CMI8738???)
+ * does it this way .. maybe not.  Never get any information from C-Media about
+ * that <werner@suse.de>.
+ */
+static int snd_cmipci_pll_rmn(unsigned int rate, unsigned int adcmult, int *r, int *m, int *n)
+{
+	unsigned int delta, tolerance;
+	int xm, xn, xr;
+
+	for (*r = 0; rate < CM_MAXIMUM_RATE/adcmult; *r += (1<<5))
+		rate <<= 1;
+	*n = -1;
+	if (*r > 0xff)
+		goto out;
+	tolerance = rate*CM_TOLERANCE_RATE;
+
+	for (xn = (1+2); xn < (0x1f+2); xn++) {
+		for (xm = (1+2); xm < (0xff+2); xm++) {
+			xr = ((CM_REFFREQ_XIN/adcmult) * xm) / xn;
+
+			if (xr < rate)
+				delta = rate - xr;
+			else
+				delta = xr - rate;
+
+			/*
+			 * If we found one, remember this,
+			 * and try to find a closer one
+			 */
+			if (delta < tolerance) {
+				tolerance = delta;
+				*m = xm - 2;
+				*n = xn - 2;
+			}
+		}
+	}
+out:
+	return (*n > -1);
+}
+
+/*
+ * Program pll register bits, I assume that the 8 registers 0xf8 upto 0xff
+ * are mapped onto the 8 ADC/DAC sampling frequency which can be choosen
+ * at the register CM_REG_FUNCTRL1 (0x04).
+ * Problem: other ways are also possible (any information about that?)
+ */
+static void snd_cmipci_set_pll(struct cmipci *cm, unsigned int rate, unsigned int slot)
+{
+	unsigned int reg = CM_REG_PLL + slot;
+	/*
+	 * Guess that this programs at reg. 0x04 the pos 15:13/12:10
+	 * for DSFC/ASFC (000 upto 111).
+	 */
+
+	/* FIXME: Init (Do we've to set an other register first before programming?) */
+
+	/* FIXME: Is this correct? Or shouldn't the m/n/r values be used for that? */
+	snd_cmipci_write_b(cm, reg, rate>>8);
+	snd_cmipci_write_b(cm, reg, rate&0xff);
+
+	/* FIXME: Setup (Do we've to set an other register first to enable this?) */
+}
+#endif /* USE_VAR48KRATE */
+
+static int snd_cmipci_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_cmipci_playback2_hw_params(struct snd_pcm_substream *substream,
+					  struct snd_pcm_hw_params *hw_params)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	if (params_channels(hw_params) > 2) {
+		mutex_lock(&cm->open_mutex);
+		if (cm->opened[CM_CH_PLAY]) {
+			mutex_unlock(&cm->open_mutex);
+			return -EBUSY;
+		}
+		/* reserve the channel A */
+		cm->opened[CM_CH_PLAY] = CM_OPEN_PLAYBACK_MULTI;
+		mutex_unlock(&cm->open_mutex);
+	}
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static void snd_cmipci_ch_reset(struct cmipci *cm, int ch)
+{
+	int reset = CM_RST_CH0 << (cm->channel[ch].ch);
+	snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl | reset);
+	snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl & ~reset);
+	udelay(10);
+}
+
+static int snd_cmipci_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+
+/*
+ */
+
+static unsigned int hw_channels[] = {1, 2, 4, 6, 8};
+static struct snd_pcm_hw_constraint_list hw_constraints_channels_4 = {
+	.count = 3,
+	.list = hw_channels,
+	.mask = 0,
+};
+static struct snd_pcm_hw_constraint_list hw_constraints_channels_6 = {
+	.count = 4,
+	.list = hw_channels,
+	.mask = 0,
+};
+static struct snd_pcm_hw_constraint_list hw_constraints_channels_8 = {
+	.count = 5,
+	.list = hw_channels,
+	.mask = 0,
+};
+
+static int set_dac_channels(struct cmipci *cm, struct cmipci_pcm *rec, int channels)
+{
+	if (channels > 2) {
+		if (!cm->can_multi_ch || !rec->ch)
+			return -EINVAL;
+		if (rec->fmt != 0x03) /* stereo 16bit only */
+			return -EINVAL;
+	}
+
+	if (cm->can_multi_ch) {
+		spin_lock_irq(&cm->reg_lock);
+		if (channels > 2) {
+			snd_cmipci_set_bit(cm, CM_REG_LEGACY_CTRL, CM_NXCHG);
+			snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_XCHGDAC);
+		} else {
+			snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_NXCHG);
+			snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_XCHGDAC);
+		}
+		if (channels == 8)
+			snd_cmipci_set_bit(cm, CM_REG_EXT_MISC, CM_CHB3D8C);
+		else
+			snd_cmipci_clear_bit(cm, CM_REG_EXT_MISC, CM_CHB3D8C);
+		if (channels == 6) {
+			snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_CHB3D5C);
+			snd_cmipci_set_bit(cm, CM_REG_LEGACY_CTRL, CM_CHB3D6C);
+		} else {
+			snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_CHB3D5C);
+			snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_CHB3D6C);
+		}
+		if (channels == 4)
+			snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_CHB3D);
+		else
+			snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_CHB3D);
+		spin_unlock_irq(&cm->reg_lock);
+	}
+	return 0;
+}
+
+
+/*
+ * prepare playback/capture channel
+ * channel to be used must have been set in rec->ch.
+ */
+static int snd_cmipci_pcm_prepare(struct cmipci *cm, struct cmipci_pcm *rec,
+				 struct snd_pcm_substream *substream)
+{
+	unsigned int reg, freq, freq_ext, val;
+	unsigned int period_size;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	rec->fmt = 0;
+	rec->shift = 0;
+	if (snd_pcm_format_width(runtime->format) >= 16) {
+		rec->fmt |= 0x02;
+		if (snd_pcm_format_width(runtime->format) > 16)
+			rec->shift++; /* 24/32bit */
+	}
+	if (runtime->channels > 1)
+		rec->fmt |= 0x01;
+	if (rec->is_dac && set_dac_channels(cm, rec, runtime->channels) < 0) {
+		snd_printd("cannot set dac channels\n");
+		return -EINVAL;
+	}
+
+	rec->offset = runtime->dma_addr;
+	/* buffer and period sizes in frame */
+	rec->dma_size = runtime->buffer_size << rec->shift;
+	period_size = runtime->period_size << rec->shift;
+	if (runtime->channels > 2) {
+		/* multi-channels */
+		rec->dma_size = (rec->dma_size * runtime->channels) / 2;
+		period_size = (period_size * runtime->channels) / 2;
+	}
+
+	spin_lock_irq(&cm->reg_lock);
+
+	/* set buffer address */
+	reg = rec->ch ? CM_REG_CH1_FRAME1 : CM_REG_CH0_FRAME1;
+	snd_cmipci_write(cm, reg, rec->offset);
+	/* program sample counts */
+	reg = rec->ch ? CM_REG_CH1_FRAME2 : CM_REG_CH0_FRAME2;
+	snd_cmipci_write_w(cm, reg, rec->dma_size - 1);
+	snd_cmipci_write_w(cm, reg + 2, period_size - 1);
+
+	/* set adc/dac flag */
+	val = rec->ch ? CM_CHADC1 : CM_CHADC0;
+	if (rec->is_dac)
+		cm->ctrl &= ~val;
+	else
+		cm->ctrl |= val;
+	snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl);
+	//snd_printd("cmipci: functrl0 = %08x\n", cm->ctrl);
+
+	/* set sample rate */
+	freq = 0;
+	freq_ext = 0;
+	if (runtime->rate > 48000)
+		switch (runtime->rate) {
+		case 88200:  freq_ext = CM_CH0_SRATE_88K; break;
+		case 96000:  freq_ext = CM_CH0_SRATE_96K; break;
+		case 128000: freq_ext = CM_CH0_SRATE_128K; break;
+		default:     snd_BUG(); break;
+		}
+	else
+		freq = snd_cmipci_rate_freq(runtime->rate);
+	val = snd_cmipci_read(cm, CM_REG_FUNCTRL1);
+	if (rec->ch) {
+		val &= ~CM_DSFC_MASK;
+		val |= (freq << CM_DSFC_SHIFT) & CM_DSFC_MASK;
+	} else {
+		val &= ~CM_ASFC_MASK;
+		val |= (freq << CM_ASFC_SHIFT) & CM_ASFC_MASK;
+	}
+	snd_cmipci_write(cm, CM_REG_FUNCTRL1, val);
+	//snd_printd("cmipci: functrl1 = %08x\n", val);
+
+	/* set format */
+	val = snd_cmipci_read(cm, CM_REG_CHFORMAT);
+	if (rec->ch) {
+		val &= ~CM_CH1FMT_MASK;
+		val |= rec->fmt << CM_CH1FMT_SHIFT;
+	} else {
+		val &= ~CM_CH0FMT_MASK;
+		val |= rec->fmt << CM_CH0FMT_SHIFT;
+	}
+	if (cm->can_96k) {
+		val &= ~(CM_CH0_SRATE_MASK << (rec->ch * 2));
+		val |= freq_ext << (rec->ch * 2);
+	}
+	snd_cmipci_write(cm, CM_REG_CHFORMAT, val);
+	//snd_printd("cmipci: chformat = %08x\n", val);
+
+	if (!rec->is_dac && cm->chip_version) {
+		if (runtime->rate > 44100)
+			snd_cmipci_set_bit(cm, CM_REG_EXT_MISC, CM_ADC48K44K);
+		else
+			snd_cmipci_clear_bit(cm, CM_REG_EXT_MISC, CM_ADC48K44K);
+	}
+
+	rec->running = 0;
+	spin_unlock_irq(&cm->reg_lock);
+
+	return 0;
+}
+
+/*
+ * PCM trigger/stop
+ */
+static int snd_cmipci_pcm_trigger(struct cmipci *cm, struct cmipci_pcm *rec,
+				  int cmd)
+{
+	unsigned int inthld, chen, reset, pause;
+	int result = 0;
+
+	inthld = CM_CH0_INT_EN << rec->ch;
+	chen = CM_CHEN0 << rec->ch;
+	reset = CM_RST_CH0 << rec->ch;
+	pause = CM_PAUSE0 << rec->ch;
+
+	spin_lock(&cm->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		rec->running = 1;
+		/* set interrupt */
+		snd_cmipci_set_bit(cm, CM_REG_INT_HLDCLR, inthld);
+		cm->ctrl |= chen;
+		/* enable channel */
+		snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl);
+		//snd_printd("cmipci: functrl0 = %08x\n", cm->ctrl);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		rec->running = 0;
+		/* disable interrupt */
+		snd_cmipci_clear_bit(cm, CM_REG_INT_HLDCLR, inthld);
+		/* reset */
+		cm->ctrl &= ~chen;
+		snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl | reset);
+		snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl & ~reset);
+		rec->needs_silencing = rec->is_dac;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		cm->ctrl |= pause;
+		snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		cm->ctrl &= ~pause;
+		snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl);
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	spin_unlock(&cm->reg_lock);
+	return result;
+}
+
+/*
+ * return the current pointer
+ */
+static snd_pcm_uframes_t snd_cmipci_pcm_pointer(struct cmipci *cm, struct cmipci_pcm *rec,
+						struct snd_pcm_substream *substream)
+{
+	size_t ptr;
+	unsigned int reg, rem, tries;
+
+	if (!rec->running)
+		return 0;
+#if 1 // this seems better..
+	reg = rec->ch ? CM_REG_CH1_FRAME2 : CM_REG_CH0_FRAME2;
+	for (tries = 0; tries < 3; tries++) {
+		rem = snd_cmipci_read_w(cm, reg);
+		if (rem < rec->dma_size)
+			goto ok;
+	} 
+	printk(KERN_ERR "cmipci: invalid PCM pointer: %#x\n", rem);
+	return SNDRV_PCM_POS_XRUN;
+ok:
+	ptr = (rec->dma_size - (rem + 1)) >> rec->shift;
+#else
+	reg = rec->ch ? CM_REG_CH1_FRAME1 : CM_REG_CH0_FRAME1;
+	ptr = snd_cmipci_read(cm, reg) - rec->offset;
+	ptr = bytes_to_frames(substream->runtime, ptr);
+#endif
+	if (substream->runtime->channels > 2)
+		ptr = (ptr * 2) / substream->runtime->channels;
+	return ptr;
+}
+
+/*
+ * playback
+ */
+
+static int snd_cmipci_playback_trigger(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	return snd_cmipci_pcm_trigger(cm, &cm->channel[CM_CH_PLAY], cmd);
+}
+
+static snd_pcm_uframes_t snd_cmipci_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	return snd_cmipci_pcm_pointer(cm, &cm->channel[CM_CH_PLAY], substream);
+}
+
+
+
+/*
+ * capture
+ */
+
+static int snd_cmipci_capture_trigger(struct snd_pcm_substream *substream,
+				     int cmd)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	return snd_cmipci_pcm_trigger(cm, &cm->channel[CM_CH_CAPT], cmd);
+}
+
+static snd_pcm_uframes_t snd_cmipci_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	return snd_cmipci_pcm_pointer(cm, &cm->channel[CM_CH_CAPT], substream);
+}
+
+
+/*
+ * hw preparation for spdif
+ */
+
+static int snd_cmipci_spdif_default_info(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_cmipci_spdif_default_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *chip = snd_kcontrol_chip(kcontrol);
+	int i;
+
+	spin_lock_irq(&chip->reg_lock);
+	for (i = 0; i < 4; i++)
+		ucontrol->value.iec958.status[i] = (chip->dig_status >> (i * 8)) & 0xff;
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_cmipci_spdif_default_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *chip = snd_kcontrol_chip(kcontrol);
+	int i, change;
+	unsigned int val;
+
+	val = 0;
+	spin_lock_irq(&chip->reg_lock);
+	for (i = 0; i < 4; i++)
+		val |= (unsigned int)ucontrol->value.iec958.status[i] << (i * 8);
+	change = val != chip->dig_status;
+	chip->dig_status = val;
+	spin_unlock_irq(&chip->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_cmipci_spdif_default __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =		snd_cmipci_spdif_default_info,
+	.get =		snd_cmipci_spdif_default_get,
+	.put =		snd_cmipci_spdif_default_put
+};
+
+static int snd_cmipci_spdif_mask_info(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_cmipci_spdif_mask_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0xff;
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_cmipci_spdif_mask __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
+	.info =		snd_cmipci_spdif_mask_info,
+	.get =		snd_cmipci_spdif_mask_get,
+};
+
+static int snd_cmipci_spdif_stream_info(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_cmipci_spdif_stream_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *chip = snd_kcontrol_chip(kcontrol);
+	int i;
+
+	spin_lock_irq(&chip->reg_lock);
+	for (i = 0; i < 4; i++)
+		ucontrol->value.iec958.status[i] = (chip->dig_pcm_status >> (i * 8)) & 0xff;
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_cmipci_spdif_stream_put(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *chip = snd_kcontrol_chip(kcontrol);
+	int i, change;
+	unsigned int val;
+
+	val = 0;
+	spin_lock_irq(&chip->reg_lock);
+	for (i = 0; i < 4; i++)
+		val |= (unsigned int)ucontrol->value.iec958.status[i] << (i * 8);
+	change = val != chip->dig_pcm_status;
+	chip->dig_pcm_status = val;
+	spin_unlock_irq(&chip->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_cmipci_spdif_stream __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+	.info =		snd_cmipci_spdif_stream_info,
+	.get =		snd_cmipci_spdif_stream_get,
+	.put =		snd_cmipci_spdif_stream_put
+};
+
+/*
+ */
+
+/* save mixer setting and mute for AC3 playback */
+static int save_mixer_state(struct cmipci *cm)
+{
+	if (! cm->mixer_insensitive) {
+		struct snd_ctl_elem_value *val;
+		unsigned int i;
+
+		val = kmalloc(sizeof(*val), GFP_ATOMIC);
+		if (!val)
+			return -ENOMEM;
+		for (i = 0; i < CM_SAVED_MIXERS; i++) {
+			struct snd_kcontrol *ctl = cm->mixer_res_ctl[i];
+			if (ctl) {
+				int event;
+				memset(val, 0, sizeof(*val));
+				ctl->get(ctl, val);
+				cm->mixer_res_status[i] = val->value.integer.value[0];
+				val->value.integer.value[0] = cm_saved_mixer[i].toggle_on;
+				event = SNDRV_CTL_EVENT_MASK_INFO;
+				if (cm->mixer_res_status[i] != val->value.integer.value[0]) {
+					ctl->put(ctl, val); /* toggle */
+					event |= SNDRV_CTL_EVENT_MASK_VALUE;
+				}
+				ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+				snd_ctl_notify(cm->card, event, &ctl->id);
+			}
+		}
+		kfree(val);
+		cm->mixer_insensitive = 1;
+	}
+	return 0;
+}
+
+
+/* restore the previously saved mixer status */
+static void restore_mixer_state(struct cmipci *cm)
+{
+	if (cm->mixer_insensitive) {
+		struct snd_ctl_elem_value *val;
+		unsigned int i;
+
+		val = kmalloc(sizeof(*val), GFP_KERNEL);
+		if (!val)
+			return;
+		cm->mixer_insensitive = 0; /* at first clear this;
+					      otherwise the changes will be ignored */
+		for (i = 0; i < CM_SAVED_MIXERS; i++) {
+			struct snd_kcontrol *ctl = cm->mixer_res_ctl[i];
+			if (ctl) {
+				int event;
+
+				memset(val, 0, sizeof(*val));
+				ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+				ctl->get(ctl, val);
+				event = SNDRV_CTL_EVENT_MASK_INFO;
+				if (val->value.integer.value[0] != cm->mixer_res_status[i]) {
+					val->value.integer.value[0] = cm->mixer_res_status[i];
+					ctl->put(ctl, val);
+					event |= SNDRV_CTL_EVENT_MASK_VALUE;
+				}
+				snd_ctl_notify(cm->card, event, &ctl->id);
+			}
+		}
+		kfree(val);
+	}
+}
+
+/* spinlock held! */
+static void setup_ac3(struct cmipci *cm, struct snd_pcm_substream *subs, int do_ac3, int rate)
+{
+	if (do_ac3) {
+		/* AC3EN for 037 */
+		snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_AC3EN1);
+		/* AC3EN for 039 */
+		snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_AC3EN2);
+	
+		if (cm->can_ac3_hw) {
+			/* SPD24SEL for 037, 0x02 */
+			/* SPD24SEL for 039, 0x20, but cannot be set */
+			snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_SPD24SEL);
+			snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL);
+		} else { /* can_ac3_sw */
+			/* SPD32SEL for 037 & 039, 0x20 */
+			snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL);
+			/* set 176K sample rate to fix 033 HW bug */
+			if (cm->chip_version == 33) {
+				if (rate >= 48000) {
+					snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_PLAYBACK_SRATE_176K);
+				} else {
+					snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_PLAYBACK_SRATE_176K);
+				}
+			}
+		}
+
+	} else {
+		snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_AC3EN1);
+		snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_AC3EN2);
+
+		if (cm->can_ac3_hw) {
+			/* chip model >= 37 */
+			if (snd_pcm_format_width(subs->runtime->format) > 16) {
+				snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL);
+				snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_SPD24SEL);
+			} else {
+				snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL);
+				snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_SPD24SEL);
+			}
+		} else {
+			snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL);
+			snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_SPD24SEL);
+			snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_PLAYBACK_SRATE_176K);
+		}
+	}
+}
+
+static int setup_spdif_playback(struct cmipci *cm, struct snd_pcm_substream *subs, int up, int do_ac3)
+{
+	int rate, err;
+
+	rate = subs->runtime->rate;
+
+	if (up && do_ac3)
+		if ((err = save_mixer_state(cm)) < 0)
+			return err;
+
+	spin_lock_irq(&cm->reg_lock);
+	cm->spdif_playback_avail = up;
+	if (up) {
+		/* they are controlled via "IEC958 Output Switch" */
+		/* snd_cmipci_set_bit(cm, CM_REG_LEGACY_CTRL, CM_ENSPDOUT); */
+		/* snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_SPDO2DAC); */
+		if (cm->spdif_playback_enabled)
+			snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_PLAYBACK_SPDF);
+		setup_ac3(cm, subs, do_ac3, rate);
+
+		if (rate == 48000 || rate == 96000)
+			snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_SPDIF48K | CM_SPDF_AC97);
+		else
+			snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPDIF48K | CM_SPDF_AC97);
+		if (rate > 48000)
+			snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_DBLSPDS);
+		else
+			snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_DBLSPDS);
+	} else {
+		/* they are controlled via "IEC958 Output Switch" */
+		/* snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_ENSPDOUT); */
+		/* snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, CM_SPDO2DAC); */
+		snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_DBLSPDS);
+		snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, CM_PLAYBACK_SPDF);
+		setup_ac3(cm, subs, 0, 0);
+	}
+	spin_unlock_irq(&cm->reg_lock);
+	return 0;
+}
+
+
+/*
+ * preparation
+ */
+
+/* playback - enable spdif only on the certain condition */
+static int snd_cmipci_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	int rate = substream->runtime->rate;
+	int err, do_spdif, do_ac3 = 0;
+
+	do_spdif = (rate >= 44100 && rate <= 96000 &&
+		    substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE &&
+		    substream->runtime->channels == 2);
+	if (do_spdif && cm->can_ac3_hw) 
+		do_ac3 = cm->dig_pcm_status & IEC958_AES0_NONAUDIO;
+	if ((err = setup_spdif_playback(cm, substream, do_spdif, do_ac3)) < 0)
+		return err;
+	return snd_cmipci_pcm_prepare(cm, &cm->channel[CM_CH_PLAY], substream);
+}
+
+/* playback  (via device #2) - enable spdif always */
+static int snd_cmipci_playback_spdif_prepare(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	int err, do_ac3;
+
+	if (cm->can_ac3_hw) 
+		do_ac3 = cm->dig_pcm_status & IEC958_AES0_NONAUDIO;
+	else
+		do_ac3 = 1; /* doesn't matter */
+	if ((err = setup_spdif_playback(cm, substream, 1, do_ac3)) < 0)
+		return err;
+	return snd_cmipci_pcm_prepare(cm, &cm->channel[CM_CH_PLAY], substream);
+}
+
+/*
+ * Apparently, the samples last played on channel A stay in some buffer, even
+ * after the channel is reset, and get added to the data for the rear DACs when
+ * playing a multichannel stream on channel B.  This is likely to generate
+ * wraparounds and thus distortions.
+ * To avoid this, we play at least one zero sample after the actual stream has
+ * stopped.
+ */
+static void snd_cmipci_silence_hack(struct cmipci *cm, struct cmipci_pcm *rec)
+{
+	struct snd_pcm_runtime *runtime = rec->substream->runtime;
+	unsigned int reg, val;
+
+	if (rec->needs_silencing && runtime && runtime->dma_area) {
+		/* set up a small silence buffer */
+		memset(runtime->dma_area, 0, PAGE_SIZE);
+		reg = rec->ch ? CM_REG_CH1_FRAME2 : CM_REG_CH0_FRAME2;
+		val = ((PAGE_SIZE / 4) - 1) | (((PAGE_SIZE / 4) / 2 - 1) << 16);
+		snd_cmipci_write(cm, reg, val);
+	
+		/* configure for 16 bits, 2 channels, 8 kHz */
+		if (runtime->channels > 2)
+			set_dac_channels(cm, rec, 2);
+		spin_lock_irq(&cm->reg_lock);
+		val = snd_cmipci_read(cm, CM_REG_FUNCTRL1);
+		val &= ~(CM_ASFC_MASK << (rec->ch * 3));
+		val |= (4 << CM_ASFC_SHIFT) << (rec->ch * 3);
+		snd_cmipci_write(cm, CM_REG_FUNCTRL1, val);
+		val = snd_cmipci_read(cm, CM_REG_CHFORMAT);
+		val &= ~(CM_CH0FMT_MASK << (rec->ch * 2));
+		val |= (3 << CM_CH0FMT_SHIFT) << (rec->ch * 2);
+		if (cm->can_96k)
+			val &= ~(CM_CH0_SRATE_MASK << (rec->ch * 2));
+		snd_cmipci_write(cm, CM_REG_CHFORMAT, val);
+	
+		/* start stream (we don't need interrupts) */
+		cm->ctrl |= CM_CHEN0 << rec->ch;
+		snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl);
+		spin_unlock_irq(&cm->reg_lock);
+
+		msleep(1);
+
+		/* stop and reset stream */
+		spin_lock_irq(&cm->reg_lock);
+		cm->ctrl &= ~(CM_CHEN0 << rec->ch);
+		val = CM_RST_CH0 << rec->ch;
+		snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl | val);
+		snd_cmipci_write(cm, CM_REG_FUNCTRL0, cm->ctrl & ~val);
+		spin_unlock_irq(&cm->reg_lock);
+
+		rec->needs_silencing = 0;
+	}
+}
+
+static int snd_cmipci_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	setup_spdif_playback(cm, substream, 0, 0);
+	restore_mixer_state(cm);
+	snd_cmipci_silence_hack(cm, &cm->channel[0]);
+	return snd_cmipci_hw_free(substream);
+}
+
+static int snd_cmipci_playback2_hw_free(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	snd_cmipci_silence_hack(cm, &cm->channel[1]);
+	return snd_cmipci_hw_free(substream);
+}
+
+/* capture */
+static int snd_cmipci_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	return snd_cmipci_pcm_prepare(cm, &cm->channel[CM_CH_CAPT], substream);
+}
+
+/* capture with spdif (via device #2) */
+static int snd_cmipci_capture_spdif_prepare(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&cm->reg_lock);
+	snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_CAPTURE_SPDF);
+	if (cm->can_96k) {
+		if (substream->runtime->rate > 48000)
+			snd_cmipci_set_bit(cm, CM_REG_CHFORMAT, CM_DBLSPDS);
+		else
+			snd_cmipci_clear_bit(cm, CM_REG_CHFORMAT, CM_DBLSPDS);
+	}
+	if (snd_pcm_format_width(substream->runtime->format) > 16)
+		snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL);
+	else
+		snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL);
+
+	spin_unlock_irq(&cm->reg_lock);
+
+	return snd_cmipci_pcm_prepare(cm, &cm->channel[CM_CH_CAPT], substream);
+}
+
+static int snd_cmipci_capture_spdif_hw_free(struct snd_pcm_substream *subs)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(subs);
+
+	spin_lock_irq(&cm->reg_lock);
+	snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, CM_CAPTURE_SPDF);
+	snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_SPD32SEL);
+	spin_unlock_irq(&cm->reg_lock);
+
+	return snd_cmipci_hw_free(subs);
+}
+
+
+/*
+ * interrupt handler
+ */
+static irqreturn_t snd_cmipci_interrupt(int irq, void *dev_id)
+{
+	struct cmipci *cm = dev_id;
+	unsigned int status, mask = 0;
+	
+	/* fastpath out, to ease interrupt sharing */
+	status = snd_cmipci_read(cm, CM_REG_INT_STATUS);
+	if (!(status & CM_INTR))
+		return IRQ_NONE;
+
+	/* acknowledge interrupt */
+	spin_lock(&cm->reg_lock);
+	if (status & CM_CHINT0)
+		mask |= CM_CH0_INT_EN;
+	if (status & CM_CHINT1)
+		mask |= CM_CH1_INT_EN;
+	snd_cmipci_clear_bit(cm, CM_REG_INT_HLDCLR, mask);
+	snd_cmipci_set_bit(cm, CM_REG_INT_HLDCLR, mask);
+	spin_unlock(&cm->reg_lock);
+
+	if (cm->rmidi && (status & CM_UARTINT))
+		snd_mpu401_uart_interrupt(irq, cm->rmidi->private_data);
+
+	if (cm->pcm) {
+		if ((status & CM_CHINT0) && cm->channel[0].running)
+			snd_pcm_period_elapsed(cm->channel[0].substream);
+		if ((status & CM_CHINT1) && cm->channel[1].running)
+			snd_pcm_period_elapsed(cm->channel[1].substream);
+	}
+	return IRQ_HANDLED;
+}
+
+/*
+ * h/w infos
+ */
+
+/* playback on channel A */
+static struct snd_pcm_hardware snd_cmipci_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5512,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/* capture on channel B */
+static struct snd_pcm_hardware snd_cmipci_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5512,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/* playback on channel B - stereo 16bit only? */
+static struct snd_pcm_hardware snd_cmipci_playback2 =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5512,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/* spdif playback on channel A */
+static struct snd_pcm_hardware snd_cmipci_playback_spdif =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+	.rate_min =		44100,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/* spdif playback on channel A (32bit, IEC958 subframes) */
+static struct snd_pcm_hardware snd_cmipci_playback_iec958_subframe =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
+	.rates =		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+	.rate_min =		44100,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/* spdif capture on channel B */
+static struct snd_pcm_hardware snd_cmipci_capture_spdif =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =	        SNDRV_PCM_FMTBIT_S16_LE |
+				SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
+	.rates =		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+	.rate_min =		44100,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static unsigned int rate_constraints[] = { 5512, 8000, 11025, 16000, 22050,
+			32000, 44100, 48000, 88200, 96000, 128000 };
+static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+		.count = ARRAY_SIZE(rate_constraints),
+		.list = rate_constraints,
+		.mask = 0,
+};
+
+/*
+ * check device open/close
+ */
+static int open_device_check(struct cmipci *cm, int mode, struct snd_pcm_substream *subs)
+{
+	int ch = mode & CM_OPEN_CH_MASK;
+
+	/* FIXME: a file should wait until the device becomes free
+	 * when it's opened on blocking mode.  however, since the current
+	 * pcm framework doesn't pass file pointer before actually opened,
+	 * we can't know whether blocking mode or not in open callback..
+	 */
+	mutex_lock(&cm->open_mutex);
+	if (cm->opened[ch]) {
+		mutex_unlock(&cm->open_mutex);
+		return -EBUSY;
+	}
+	cm->opened[ch] = mode;
+	cm->channel[ch].substream = subs;
+	if (! (mode & CM_OPEN_DAC)) {
+		/* disable dual DAC mode */
+		cm->channel[ch].is_dac = 0;
+		spin_lock_irq(&cm->reg_lock);
+		snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_ENDBDAC);
+		spin_unlock_irq(&cm->reg_lock);
+	}
+	mutex_unlock(&cm->open_mutex);
+	return 0;
+}
+
+static void close_device_check(struct cmipci *cm, int mode)
+{
+	int ch = mode & CM_OPEN_CH_MASK;
+
+	mutex_lock(&cm->open_mutex);
+	if (cm->opened[ch] == mode) {
+		if (cm->channel[ch].substream) {
+			snd_cmipci_ch_reset(cm, ch);
+			cm->channel[ch].running = 0;
+			cm->channel[ch].substream = NULL;
+		}
+		cm->opened[ch] = 0;
+		if (! cm->channel[ch].is_dac) {
+			/* enable dual DAC mode again */
+			cm->channel[ch].is_dac = 1;
+			spin_lock_irq(&cm->reg_lock);
+			snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_ENDBDAC);
+			spin_unlock_irq(&cm->reg_lock);
+		}
+	}
+	mutex_unlock(&cm->open_mutex);
+}
+
+/*
+ */
+
+static int snd_cmipci_playback_open(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	if ((err = open_device_check(cm, CM_OPEN_PLAYBACK, substream)) < 0)
+		return err;
+	runtime->hw = snd_cmipci_playback;
+	if (cm->chip_version == 68) {
+		runtime->hw.rates |= SNDRV_PCM_RATE_88200 |
+				     SNDRV_PCM_RATE_96000;
+		runtime->hw.rate_max = 96000;
+	} else if (cm->chip_version == 55) {
+		err = snd_pcm_hw_constraint_list(runtime, 0,
+			SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+		if (err < 0)
+			return err;
+		runtime->hw.rates |= SNDRV_PCM_RATE_KNOT;
+		runtime->hw.rate_max = 128000;
+	}
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 0x10000);
+	cm->dig_pcm_status = cm->dig_status;
+	return 0;
+}
+
+static int snd_cmipci_capture_open(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	if ((err = open_device_check(cm, CM_OPEN_CAPTURE, substream)) < 0)
+		return err;
+	runtime->hw = snd_cmipci_capture;
+	if (cm->chip_version == 68) {	// 8768 only supports 44k/48k recording
+		runtime->hw.rate_min = 41000;
+		runtime->hw.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000;
+	} else if (cm->chip_version == 55) {
+		err = snd_pcm_hw_constraint_list(runtime, 0,
+			SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+		if (err < 0)
+			return err;
+		runtime->hw.rates |= SNDRV_PCM_RATE_KNOT;
+		runtime->hw.rate_max = 128000;
+	}
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 0x10000);
+	return 0;
+}
+
+static int snd_cmipci_playback2_open(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	if ((err = open_device_check(cm, CM_OPEN_PLAYBACK2, substream)) < 0) /* use channel B */
+		return err;
+	runtime->hw = snd_cmipci_playback2;
+	mutex_lock(&cm->open_mutex);
+	if (! cm->opened[CM_CH_PLAY]) {
+		if (cm->can_multi_ch) {
+			runtime->hw.channels_max = cm->max_channels;
+			if (cm->max_channels == 4)
+				snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &hw_constraints_channels_4);
+			else if (cm->max_channels == 6)
+				snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &hw_constraints_channels_6);
+			else if (cm->max_channels == 8)
+				snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &hw_constraints_channels_8);
+		}
+	}
+	mutex_unlock(&cm->open_mutex);
+	if (cm->chip_version == 68) {
+		runtime->hw.rates |= SNDRV_PCM_RATE_88200 |
+				     SNDRV_PCM_RATE_96000;
+		runtime->hw.rate_max = 96000;
+	} else if (cm->chip_version == 55) {
+		err = snd_pcm_hw_constraint_list(runtime, 0,
+			SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+		if (err < 0)
+			return err;
+		runtime->hw.rates |= SNDRV_PCM_RATE_KNOT;
+		runtime->hw.rate_max = 128000;
+	}
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 0x10000);
+	return 0;
+}
+
+static int snd_cmipci_playback_spdif_open(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	if ((err = open_device_check(cm, CM_OPEN_SPDIF_PLAYBACK, substream)) < 0) /* use channel A */
+		return err;
+	if (cm->can_ac3_hw) {
+		runtime->hw = snd_cmipci_playback_spdif;
+		if (cm->chip_version >= 37) {
+			runtime->hw.formats |= SNDRV_PCM_FMTBIT_S32_LE;
+			snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+		}
+		if (cm->can_96k) {
+			runtime->hw.rates |= SNDRV_PCM_RATE_88200 |
+					     SNDRV_PCM_RATE_96000;
+			runtime->hw.rate_max = 96000;
+		}
+	} else {
+		runtime->hw = snd_cmipci_playback_iec958_subframe;
+	}
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 0x40000);
+	cm->dig_pcm_status = cm->dig_status;
+	return 0;
+}
+
+static int snd_cmipci_capture_spdif_open(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	if ((err = open_device_check(cm, CM_OPEN_SPDIF_CAPTURE, substream)) < 0) /* use channel B */
+		return err;
+	runtime->hw = snd_cmipci_capture_spdif;
+	if (cm->can_96k && !(cm->chip_version == 68)) {
+		runtime->hw.rates |= SNDRV_PCM_RATE_88200 |
+				     SNDRV_PCM_RATE_96000;
+		runtime->hw.rate_max = 96000;
+	}
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 0x40000);
+	return 0;
+}
+
+
+/*
+ */
+
+static int snd_cmipci_playback_close(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	close_device_check(cm, CM_OPEN_PLAYBACK);
+	return 0;
+}
+
+static int snd_cmipci_capture_close(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	close_device_check(cm, CM_OPEN_CAPTURE);
+	return 0;
+}
+
+static int snd_cmipci_playback2_close(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	close_device_check(cm, CM_OPEN_PLAYBACK2);
+	close_device_check(cm, CM_OPEN_PLAYBACK_MULTI);
+	return 0;
+}
+
+static int snd_cmipci_playback_spdif_close(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	close_device_check(cm, CM_OPEN_SPDIF_PLAYBACK);
+	return 0;
+}
+
+static int snd_cmipci_capture_spdif_close(struct snd_pcm_substream *substream)
+{
+	struct cmipci *cm = snd_pcm_substream_chip(substream);
+	close_device_check(cm, CM_OPEN_SPDIF_CAPTURE);
+	return 0;
+}
+
+
+/*
+ */
+
+static struct snd_pcm_ops snd_cmipci_playback_ops = {
+	.open =		snd_cmipci_playback_open,
+	.close =	snd_cmipci_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cmipci_hw_params,
+	.hw_free =	snd_cmipci_playback_hw_free,
+	.prepare =	snd_cmipci_playback_prepare,
+	.trigger =	snd_cmipci_playback_trigger,
+	.pointer =	snd_cmipci_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_cmipci_capture_ops = {
+	.open =		snd_cmipci_capture_open,
+	.close =	snd_cmipci_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cmipci_hw_params,
+	.hw_free =	snd_cmipci_hw_free,
+	.prepare =	snd_cmipci_capture_prepare,
+	.trigger =	snd_cmipci_capture_trigger,
+	.pointer =	snd_cmipci_capture_pointer,
+};
+
+static struct snd_pcm_ops snd_cmipci_playback2_ops = {
+	.open =		snd_cmipci_playback2_open,
+	.close =	snd_cmipci_playback2_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cmipci_playback2_hw_params,
+	.hw_free =	snd_cmipci_playback2_hw_free,
+	.prepare =	snd_cmipci_capture_prepare,	/* channel B */
+	.trigger =	snd_cmipci_capture_trigger,	/* channel B */
+	.pointer =	snd_cmipci_capture_pointer,	/* channel B */
+};
+
+static struct snd_pcm_ops snd_cmipci_playback_spdif_ops = {
+	.open =		snd_cmipci_playback_spdif_open,
+	.close =	snd_cmipci_playback_spdif_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cmipci_hw_params,
+	.hw_free =	snd_cmipci_playback_hw_free,
+	.prepare =	snd_cmipci_playback_spdif_prepare,	/* set up rate */
+	.trigger =	snd_cmipci_playback_trigger,
+	.pointer =	snd_cmipci_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_cmipci_capture_spdif_ops = {
+	.open =		snd_cmipci_capture_spdif_open,
+	.close =	snd_cmipci_capture_spdif_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cmipci_hw_params,
+	.hw_free =	snd_cmipci_capture_spdif_hw_free,
+	.prepare =	snd_cmipci_capture_spdif_prepare,
+	.trigger =	snd_cmipci_capture_trigger,
+	.pointer =	snd_cmipci_capture_pointer,
+};
+
+
+/*
+ */
+
+static int __devinit snd_cmipci_pcm_new(struct cmipci *cm, int device)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(cm->card, cm->card->driver, device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cmipci_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cmipci_capture_ops);
+
+	pcm->private_data = cm;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "C-Media PCI DAC/ADC");
+	cm->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(cm->pci), 64*1024, 128*1024);
+
+	return 0;
+}
+
+static int __devinit snd_cmipci_pcm2_new(struct cmipci *cm, int device)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(cm->card, cm->card->driver, device, 1, 0, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cmipci_playback2_ops);
+
+	pcm->private_data = cm;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "C-Media PCI 2nd DAC");
+	cm->pcm2 = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(cm->pci), 64*1024, 128*1024);
+
+	return 0;
+}
+
+static int __devinit snd_cmipci_pcm_spdif_new(struct cmipci *cm, int device)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(cm->card, cm->card->driver, device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cmipci_playback_spdif_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cmipci_capture_spdif_ops);
+
+	pcm->private_data = cm;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "C-Media PCI IEC958");
+	cm->pcm_spdif = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(cm->pci), 64*1024, 128*1024);
+
+	return 0;
+}
+
+/*
+ * mixer interface:
+ * - CM8338/8738 has a compatible mixer interface with SB16, but
+ *   lack of some elements like tone control, i/o gain and AGC.
+ * - Access to native registers:
+ *   - A 3D switch
+ *   - Output mute switches
+ */
+
+static void snd_cmipci_mixer_write(struct cmipci *s, unsigned char idx, unsigned char data)
+{
+	outb(idx, s->iobase + CM_REG_SB16_ADDR);
+	outb(data, s->iobase + CM_REG_SB16_DATA);
+}
+
+static unsigned char snd_cmipci_mixer_read(struct cmipci *s, unsigned char idx)
+{
+	unsigned char v;
+
+	outb(idx, s->iobase + CM_REG_SB16_ADDR);
+	v = inb(s->iobase + CM_REG_SB16_DATA);
+	return v;
+}
+
+/*
+ * general mixer element
+ */
+struct cmipci_sb_reg {
+	unsigned int left_reg, right_reg;
+	unsigned int left_shift, right_shift;
+	unsigned int mask;
+	unsigned int invert: 1;
+	unsigned int stereo: 1;
+};
+
+#define COMPOSE_SB_REG(lreg,rreg,lshift,rshift,mask,invert,stereo) \
+ ((lreg) | ((rreg) << 8) | (lshift << 16) | (rshift << 19) | (mask << 24) | (invert << 22) | (stereo << 23))
+
+#define CMIPCI_DOUBLE(xname, left_reg, right_reg, left_shift, right_shift, mask, invert, stereo) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_cmipci_info_volume, \
+  .get = snd_cmipci_get_volume, .put = snd_cmipci_put_volume, \
+  .private_value = COMPOSE_SB_REG(left_reg, right_reg, left_shift, right_shift, mask, invert, stereo), \
+}
+
+#define CMIPCI_SB_VOL_STEREO(xname,reg,shift,mask) CMIPCI_DOUBLE(xname, reg, reg+1, shift, shift, mask, 0, 1)
+#define CMIPCI_SB_VOL_MONO(xname,reg,shift,mask) CMIPCI_DOUBLE(xname, reg, reg, shift, shift, mask, 0, 0)
+#define CMIPCI_SB_SW_STEREO(xname,lshift,rshift) CMIPCI_DOUBLE(xname, SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, lshift, rshift, 1, 0, 1)
+#define CMIPCI_SB_SW_MONO(xname,shift) CMIPCI_DOUBLE(xname, SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, shift, shift, 1, 0, 0)
+
+static void cmipci_sb_reg_decode(struct cmipci_sb_reg *r, unsigned long val)
+{
+	r->left_reg = val & 0xff;
+	r->right_reg = (val >> 8) & 0xff;
+	r->left_shift = (val >> 16) & 0x07;
+	r->right_shift = (val >> 19) & 0x07;
+	r->invert = (val >> 22) & 1;
+	r->stereo = (val >> 23) & 1;
+	r->mask = (val >> 24) & 0xff;
+}
+
+static int snd_cmipci_info_volume(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct cmipci_sb_reg reg;
+
+	cmipci_sb_reg_decode(&reg, kcontrol->private_value);
+	uinfo->type = reg.mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = reg.stereo + 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = reg.mask;
+	return 0;
+}
+ 
+static int snd_cmipci_get_volume(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	struct cmipci_sb_reg reg;
+	int val;
+
+	cmipci_sb_reg_decode(&reg, kcontrol->private_value);
+	spin_lock_irq(&cm->reg_lock);
+	val = (snd_cmipci_mixer_read(cm, reg.left_reg) >> reg.left_shift) & reg.mask;
+	if (reg.invert)
+		val = reg.mask - val;
+	ucontrol->value.integer.value[0] = val;
+	if (reg.stereo) {
+		val = (snd_cmipci_mixer_read(cm, reg.right_reg) >> reg.right_shift) & reg.mask;
+		if (reg.invert)
+			val = reg.mask - val;
+		 ucontrol->value.integer.value[1] = val;
+	}
+	spin_unlock_irq(&cm->reg_lock);
+	return 0;
+}
+
+static int snd_cmipci_put_volume(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	struct cmipci_sb_reg reg;
+	int change;
+	int left, right, oleft, oright;
+
+	cmipci_sb_reg_decode(&reg, kcontrol->private_value);
+	left = ucontrol->value.integer.value[0] & reg.mask;
+	if (reg.invert)
+		left = reg.mask - left;
+	left <<= reg.left_shift;
+	if (reg.stereo) {
+		right = ucontrol->value.integer.value[1] & reg.mask;
+		if (reg.invert)
+			right = reg.mask - right;
+		right <<= reg.right_shift;
+	} else
+		right = 0;
+	spin_lock_irq(&cm->reg_lock);
+	oleft = snd_cmipci_mixer_read(cm, reg.left_reg);
+	left |= oleft & ~(reg.mask << reg.left_shift);
+	change = left != oleft;
+	if (reg.stereo) {
+		if (reg.left_reg != reg.right_reg) {
+			snd_cmipci_mixer_write(cm, reg.left_reg, left);
+			oright = snd_cmipci_mixer_read(cm, reg.right_reg);
+		} else
+			oright = left;
+		right |= oright & ~(reg.mask << reg.right_shift);
+		change |= right != oright;
+		snd_cmipci_mixer_write(cm, reg.right_reg, right);
+	} else
+		snd_cmipci_mixer_write(cm, reg.left_reg, left);
+	spin_unlock_irq(&cm->reg_lock);
+	return change;
+}
+
+/*
+ * input route (left,right) -> (left,right)
+ */
+#define CMIPCI_SB_INPUT_SW(xname, left_shift, right_shift) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_cmipci_info_input_sw, \
+  .get = snd_cmipci_get_input_sw, .put = snd_cmipci_put_input_sw, \
+  .private_value = COMPOSE_SB_REG(SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, left_shift, right_shift, 1, 0, 1), \
+}
+
+static int snd_cmipci_info_input_sw(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 4;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+ 
+static int snd_cmipci_get_input_sw(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	struct cmipci_sb_reg reg;
+	int val1, val2;
+
+	cmipci_sb_reg_decode(&reg, kcontrol->private_value);
+	spin_lock_irq(&cm->reg_lock);
+	val1 = snd_cmipci_mixer_read(cm, reg.left_reg);
+	val2 = snd_cmipci_mixer_read(cm, reg.right_reg);
+	spin_unlock_irq(&cm->reg_lock);
+	ucontrol->value.integer.value[0] = (val1 >> reg.left_shift) & 1;
+	ucontrol->value.integer.value[1] = (val2 >> reg.left_shift) & 1;
+	ucontrol->value.integer.value[2] = (val1 >> reg.right_shift) & 1;
+	ucontrol->value.integer.value[3] = (val2 >> reg.right_shift) & 1;
+	return 0;
+}
+
+static int snd_cmipci_put_input_sw(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	struct cmipci_sb_reg reg;
+	int change;
+	int val1, val2, oval1, oval2;
+
+	cmipci_sb_reg_decode(&reg, kcontrol->private_value);
+	spin_lock_irq(&cm->reg_lock);
+	oval1 = snd_cmipci_mixer_read(cm, reg.left_reg);
+	oval2 = snd_cmipci_mixer_read(cm, reg.right_reg);
+	val1 = oval1 & ~((1 << reg.left_shift) | (1 << reg.right_shift));
+	val2 = oval2 & ~((1 << reg.left_shift) | (1 << reg.right_shift));
+	val1 |= (ucontrol->value.integer.value[0] & 1) << reg.left_shift;
+	val2 |= (ucontrol->value.integer.value[1] & 1) << reg.left_shift;
+	val1 |= (ucontrol->value.integer.value[2] & 1) << reg.right_shift;
+	val2 |= (ucontrol->value.integer.value[3] & 1) << reg.right_shift;
+	change = val1 != oval1 || val2 != oval2;
+	snd_cmipci_mixer_write(cm, reg.left_reg, val1);
+	snd_cmipci_mixer_write(cm, reg.right_reg, val2);
+	spin_unlock_irq(&cm->reg_lock);
+	return change;
+}
+
+/*
+ * native mixer switches/volumes
+ */
+
+#define CMIPCI_MIXER_SW_STEREO(xname, reg, lshift, rshift, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_cmipci_info_native_mixer, \
+  .get = snd_cmipci_get_native_mixer, .put = snd_cmipci_put_native_mixer, \
+  .private_value = COMPOSE_SB_REG(reg, reg, lshift, rshift, 1, invert, 1), \
+}
+
+#define CMIPCI_MIXER_SW_MONO(xname, reg, shift, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_cmipci_info_native_mixer, \
+  .get = snd_cmipci_get_native_mixer, .put = snd_cmipci_put_native_mixer, \
+  .private_value = COMPOSE_SB_REG(reg, reg, shift, shift, 1, invert, 0), \
+}
+
+#define CMIPCI_MIXER_VOL_STEREO(xname, reg, lshift, rshift, mask) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_cmipci_info_native_mixer, \
+  .get = snd_cmipci_get_native_mixer, .put = snd_cmipci_put_native_mixer, \
+  .private_value = COMPOSE_SB_REG(reg, reg, lshift, rshift, mask, 0, 1), \
+}
+
+#define CMIPCI_MIXER_VOL_MONO(xname, reg, shift, mask) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+  .info = snd_cmipci_info_native_mixer, \
+  .get = snd_cmipci_get_native_mixer, .put = snd_cmipci_put_native_mixer, \
+  .private_value = COMPOSE_SB_REG(reg, reg, shift, shift, mask, 0, 0), \
+}
+
+static int snd_cmipci_info_native_mixer(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	struct cmipci_sb_reg reg;
+
+	cmipci_sb_reg_decode(&reg, kcontrol->private_value);
+	uinfo->type = reg.mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = reg.stereo + 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = reg.mask;
+	return 0;
+
+}
+
+static int snd_cmipci_get_native_mixer(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	struct cmipci_sb_reg reg;
+	unsigned char oreg, val;
+
+	cmipci_sb_reg_decode(&reg, kcontrol->private_value);
+	spin_lock_irq(&cm->reg_lock);
+	oreg = inb(cm->iobase + reg.left_reg);
+	val = (oreg >> reg.left_shift) & reg.mask;
+	if (reg.invert)
+		val = reg.mask - val;
+	ucontrol->value.integer.value[0] = val;
+	if (reg.stereo) {
+		val = (oreg >> reg.right_shift) & reg.mask;
+		if (reg.invert)
+			val = reg.mask - val;
+		ucontrol->value.integer.value[1] = val;
+	}
+	spin_unlock_irq(&cm->reg_lock);
+	return 0;
+}
+
+static int snd_cmipci_put_native_mixer(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	struct cmipci_sb_reg reg;
+	unsigned char oreg, nreg, val;
+
+	cmipci_sb_reg_decode(&reg, kcontrol->private_value);
+	spin_lock_irq(&cm->reg_lock);
+	oreg = inb(cm->iobase + reg.left_reg);
+	val = ucontrol->value.integer.value[0] & reg.mask;
+	if (reg.invert)
+		val = reg.mask - val;
+	nreg = oreg & ~(reg.mask << reg.left_shift);
+	nreg |= (val << reg.left_shift);
+	if (reg.stereo) {
+		val = ucontrol->value.integer.value[1] & reg.mask;
+		if (reg.invert)
+			val = reg.mask - val;
+		nreg &= ~(reg.mask << reg.right_shift);
+		nreg |= (val << reg.right_shift);
+	}
+	outb(nreg, cm->iobase + reg.left_reg);
+	spin_unlock_irq(&cm->reg_lock);
+	return (nreg != oreg);
+}
+
+/*
+ * special case - check mixer sensitivity
+ */
+static int snd_cmipci_get_native_mixer_sensitive(struct snd_kcontrol *kcontrol,
+						 struct snd_ctl_elem_value *ucontrol)
+{
+	//struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	return snd_cmipci_get_native_mixer(kcontrol, ucontrol);
+}
+
+static int snd_cmipci_put_native_mixer_sensitive(struct snd_kcontrol *kcontrol,
+						 struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	if (cm->mixer_insensitive) {
+		/* ignored */
+		return 0;
+	}
+	return snd_cmipci_put_native_mixer(kcontrol, ucontrol);
+}
+
+
+static struct snd_kcontrol_new snd_cmipci_mixers[] __devinitdata = {
+	CMIPCI_SB_VOL_STEREO("Master Playback Volume", SB_DSP4_MASTER_DEV, 3, 31),
+	CMIPCI_MIXER_SW_MONO("3D Control - Switch", CM_REG_MIXER1, CM_X3DEN_SHIFT, 0),
+	CMIPCI_SB_VOL_STEREO("PCM Playback Volume", SB_DSP4_PCM_DEV, 3, 31),
+	//CMIPCI_MIXER_SW_MONO("PCM Playback Switch", CM_REG_MIXER1, CM_WSMUTE_SHIFT, 1),
+	{ /* switch with sensitivity */
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "PCM Playback Switch",
+		.info = snd_cmipci_info_native_mixer,
+		.get = snd_cmipci_get_native_mixer_sensitive,
+		.put = snd_cmipci_put_native_mixer_sensitive,
+		.private_value = COMPOSE_SB_REG(CM_REG_MIXER1, CM_REG_MIXER1, CM_WSMUTE_SHIFT, CM_WSMUTE_SHIFT, 1, 1, 0),
+	},
+	CMIPCI_MIXER_SW_STEREO("PCM Capture Switch", CM_REG_MIXER1, CM_WAVEINL_SHIFT, CM_WAVEINR_SHIFT, 0),
+	CMIPCI_SB_VOL_STEREO("Synth Playback Volume", SB_DSP4_SYNTH_DEV, 3, 31),
+	CMIPCI_MIXER_SW_MONO("Synth Playback Switch", CM_REG_MIXER1, CM_FMMUTE_SHIFT, 1),
+	CMIPCI_SB_INPUT_SW("Synth Capture Route", 6, 5),
+	CMIPCI_SB_VOL_STEREO("CD Playback Volume", SB_DSP4_CD_DEV, 3, 31),
+	CMIPCI_SB_SW_STEREO("CD Playback Switch", 2, 1),
+	CMIPCI_SB_INPUT_SW("CD Capture Route", 2, 1),
+	CMIPCI_SB_VOL_STEREO("Line Playback Volume", SB_DSP4_LINE_DEV, 3, 31),
+	CMIPCI_SB_SW_STEREO("Line Playback Switch", 4, 3),
+	CMIPCI_SB_INPUT_SW("Line Capture Route", 4, 3),
+	CMIPCI_SB_VOL_MONO("Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31),
+	CMIPCI_SB_SW_MONO("Mic Playback Switch", 0),
+	CMIPCI_DOUBLE("Mic Capture Switch", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 0, 0, 1, 0, 0),
+	CMIPCI_SB_VOL_MONO("Beep Playback Volume", SB_DSP4_SPEAKER_DEV, 6, 3),
+	CMIPCI_MIXER_VOL_STEREO("Aux Playback Volume", CM_REG_AUX_VOL, 4, 0, 15),
+	CMIPCI_MIXER_SW_STEREO("Aux Playback Switch", CM_REG_MIXER2, CM_VAUXLM_SHIFT, CM_VAUXRM_SHIFT, 0),
+	CMIPCI_MIXER_SW_STEREO("Aux Capture Switch", CM_REG_MIXER2, CM_RAUXLEN_SHIFT, CM_RAUXREN_SHIFT, 0),
+	CMIPCI_MIXER_SW_MONO("Mic Boost Playback Switch", CM_REG_MIXER2, CM_MICGAINZ_SHIFT, 1),
+	CMIPCI_MIXER_VOL_MONO("Mic Capture Volume", CM_REG_MIXER2, CM_VADMIC_SHIFT, 7),
+	CMIPCI_SB_VOL_MONO("Phone Playback Volume", CM_REG_EXTENT_IND, 5, 7),
+	CMIPCI_DOUBLE("Phone Playback Switch", CM_REG_EXTENT_IND, CM_REG_EXTENT_IND, 4, 4, 1, 0, 0),
+	CMIPCI_DOUBLE("Beep Playback Switch", CM_REG_EXTENT_IND, CM_REG_EXTENT_IND, 3, 3, 1, 0, 0),
+	CMIPCI_DOUBLE("Mic Boost Capture Switch", CM_REG_EXTENT_IND, CM_REG_EXTENT_IND, 0, 0, 1, 0, 0),
+};
+
+/*
+ * other switches
+ */
+
+struct cmipci_switch_args {
+	int reg;		/* register index */
+	unsigned int mask;	/* mask bits */
+	unsigned int mask_on;	/* mask bits to turn on */
+	unsigned int is_byte: 1;		/* byte access? */
+	unsigned int ac3_sensitive: 1;	/* access forbidden during
+					 * non-audio operation?
+					 */
+};
+
+#define snd_cmipci_uswitch_info		snd_ctl_boolean_mono_info
+
+static int _snd_cmipci_uswitch_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol,
+				   struct cmipci_switch_args *args)
+{
+	unsigned int val;
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&cm->reg_lock);
+	if (args->ac3_sensitive && cm->mixer_insensitive) {
+		ucontrol->value.integer.value[0] = 0;
+		spin_unlock_irq(&cm->reg_lock);
+		return 0;
+	}
+	if (args->is_byte)
+		val = inb(cm->iobase + args->reg);
+	else
+		val = snd_cmipci_read(cm, args->reg);
+	ucontrol->value.integer.value[0] = ((val & args->mask) == args->mask_on) ? 1 : 0;
+	spin_unlock_irq(&cm->reg_lock);
+	return 0;
+}
+
+static int snd_cmipci_uswitch_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci_switch_args *args;
+	args = (struct cmipci_switch_args *)kcontrol->private_value;
+	if (snd_BUG_ON(!args))
+		return -EINVAL;
+	return _snd_cmipci_uswitch_get(kcontrol, ucontrol, args);
+}
+
+static int _snd_cmipci_uswitch_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol,
+				   struct cmipci_switch_args *args)
+{
+	unsigned int val;
+	int change;
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&cm->reg_lock);
+	if (args->ac3_sensitive && cm->mixer_insensitive) {
+		/* ignored */
+		spin_unlock_irq(&cm->reg_lock);
+		return 0;
+	}
+	if (args->is_byte)
+		val = inb(cm->iobase + args->reg);
+	else
+		val = snd_cmipci_read(cm, args->reg);
+	change = (val & args->mask) != (ucontrol->value.integer.value[0] ? 
+			args->mask_on : (args->mask & ~args->mask_on));
+	if (change) {
+		val &= ~args->mask;
+		if (ucontrol->value.integer.value[0])
+			val |= args->mask_on;
+		else
+			val |= (args->mask & ~args->mask_on);
+		if (args->is_byte)
+			outb((unsigned char)val, cm->iobase + args->reg);
+		else
+			snd_cmipci_write(cm, args->reg, val);
+	}
+	spin_unlock_irq(&cm->reg_lock);
+	return change;
+}
+
+static int snd_cmipci_uswitch_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci_switch_args *args;
+	args = (struct cmipci_switch_args *)kcontrol->private_value;
+	if (snd_BUG_ON(!args))
+		return -EINVAL;
+	return _snd_cmipci_uswitch_put(kcontrol, ucontrol, args);
+}
+
+#define DEFINE_SWITCH_ARG(sname, xreg, xmask, xmask_on, xis_byte, xac3) \
+static struct cmipci_switch_args cmipci_switch_arg_##sname = { \
+  .reg = xreg, \
+  .mask = xmask, \
+  .mask_on = xmask_on, \
+  .is_byte = xis_byte, \
+  .ac3_sensitive = xac3, \
+}
+	
+#define DEFINE_BIT_SWITCH_ARG(sname, xreg, xmask, xis_byte, xac3) \
+	DEFINE_SWITCH_ARG(sname, xreg, xmask, xmask, xis_byte, xac3)
+
+#if 0 /* these will be controlled in pcm device */
+DEFINE_BIT_SWITCH_ARG(spdif_in, CM_REG_FUNCTRL1, CM_SPDF_1, 0, 0);
+DEFINE_BIT_SWITCH_ARG(spdif_out, CM_REG_FUNCTRL1, CM_SPDF_0, 0, 0);
+#endif
+DEFINE_BIT_SWITCH_ARG(spdif_in_sel1, CM_REG_CHFORMAT, CM_SPDIF_SELECT1, 0, 0);
+DEFINE_BIT_SWITCH_ARG(spdif_in_sel2, CM_REG_MISC_CTRL, CM_SPDIF_SELECT2, 0, 0);
+DEFINE_BIT_SWITCH_ARG(spdif_enable, CM_REG_LEGACY_CTRL, CM_ENSPDOUT, 0, 0);
+DEFINE_BIT_SWITCH_ARG(spdo2dac, CM_REG_FUNCTRL1, CM_SPDO2DAC, 0, 1);
+DEFINE_BIT_SWITCH_ARG(spdi_valid, CM_REG_MISC, CM_SPDVALID, 1, 0);
+DEFINE_BIT_SWITCH_ARG(spdif_copyright, CM_REG_LEGACY_CTRL, CM_SPDCOPYRHT, 0, 0);
+DEFINE_BIT_SWITCH_ARG(spdif_dac_out, CM_REG_LEGACY_CTRL, CM_DAC2SPDO, 0, 1);
+DEFINE_SWITCH_ARG(spdo_5v, CM_REG_MISC_CTRL, CM_SPDO5V, 0, 0, 0); /* inverse: 0 = 5V */
+// DEFINE_BIT_SWITCH_ARG(spdo_48k, CM_REG_MISC_CTRL, CM_SPDF_AC97|CM_SPDIF48K, 0, 1);
+DEFINE_BIT_SWITCH_ARG(spdif_loop, CM_REG_FUNCTRL1, CM_SPDFLOOP, 0, 1);
+DEFINE_BIT_SWITCH_ARG(spdi_monitor, CM_REG_MIXER1, CM_CDPLAY, 1, 0);
+/* DEFINE_BIT_SWITCH_ARG(spdi_phase, CM_REG_CHFORMAT, CM_SPDIF_INVERSE, 0, 0); */
+DEFINE_BIT_SWITCH_ARG(spdi_phase, CM_REG_MISC, CM_SPDIF_INVERSE, 1, 0);
+DEFINE_BIT_SWITCH_ARG(spdi_phase2, CM_REG_CHFORMAT, CM_SPDIF_INVERSE2, 0, 0);
+#if CM_CH_PLAY == 1
+DEFINE_SWITCH_ARG(exchange_dac, CM_REG_MISC_CTRL, CM_XCHGDAC, 0, 0, 0); /* reversed */
+#else
+DEFINE_SWITCH_ARG(exchange_dac, CM_REG_MISC_CTRL, CM_XCHGDAC, CM_XCHGDAC, 0, 0);
+#endif
+DEFINE_BIT_SWITCH_ARG(fourch, CM_REG_MISC_CTRL, CM_N4SPK3D, 0, 0);
+// DEFINE_BIT_SWITCH_ARG(line_rear, CM_REG_MIXER1, CM_REAR2LIN, 1, 0);
+// DEFINE_BIT_SWITCH_ARG(line_bass, CM_REG_LEGACY_CTRL, CM_CENTR2LIN|CM_BASE2LIN, 0, 0);
+// DEFINE_BIT_SWITCH_ARG(joystick, CM_REG_FUNCTRL1, CM_JYSTK_EN, 0, 0); /* now module option */
+DEFINE_SWITCH_ARG(modem, CM_REG_MISC_CTRL, CM_FLINKON|CM_FLINKOFF, CM_FLINKON, 0, 0);
+
+#define DEFINE_SWITCH(sname, stype, sarg) \
+{ .name = sname, \
+  .iface = stype, \
+  .info = snd_cmipci_uswitch_info, \
+  .get = snd_cmipci_uswitch_get, \
+  .put = snd_cmipci_uswitch_put, \
+  .private_value = (unsigned long)&cmipci_switch_arg_##sarg,\
+}
+
+#define DEFINE_CARD_SWITCH(sname, sarg) DEFINE_SWITCH(sname, SNDRV_CTL_ELEM_IFACE_CARD, sarg)
+#define DEFINE_MIXER_SWITCH(sname, sarg) DEFINE_SWITCH(sname, SNDRV_CTL_ELEM_IFACE_MIXER, sarg)
+
+
+/*
+ * callbacks for spdif output switch
+ * needs toggle two registers..
+ */
+static int snd_cmipci_spdout_enable_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	int changed;
+	changed = _snd_cmipci_uswitch_get(kcontrol, ucontrol, &cmipci_switch_arg_spdif_enable);
+	changed |= _snd_cmipci_uswitch_get(kcontrol, ucontrol, &cmipci_switch_arg_spdo2dac);
+	return changed;
+}
+
+static int snd_cmipci_spdout_enable_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *chip = snd_kcontrol_chip(kcontrol);
+	int changed;
+	changed = _snd_cmipci_uswitch_put(kcontrol, ucontrol, &cmipci_switch_arg_spdif_enable);
+	changed |= _snd_cmipci_uswitch_put(kcontrol, ucontrol, &cmipci_switch_arg_spdo2dac);
+	if (changed) {
+		if (ucontrol->value.integer.value[0]) {
+			if (chip->spdif_playback_avail)
+				snd_cmipci_set_bit(chip, CM_REG_FUNCTRL1, CM_PLAYBACK_SPDF);
+		} else {
+			if (chip->spdif_playback_avail)
+				snd_cmipci_clear_bit(chip, CM_REG_FUNCTRL1, CM_PLAYBACK_SPDF);
+		}
+	}
+	chip->spdif_playback_enabled = ucontrol->value.integer.value[0];
+	return changed;
+}
+
+
+static int snd_cmipci_line_in_mode_info(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	static char *texts[3] = { "Line-In", "Rear Output", "Bass Output" };
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = cm->chip_version >= 39 ? 3 : 2;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static inline unsigned int get_line_in_mode(struct cmipci *cm)
+{
+	unsigned int val;
+	if (cm->chip_version >= 39) {
+		val = snd_cmipci_read(cm, CM_REG_LEGACY_CTRL);
+		if (val & (CM_CENTR2LIN | CM_BASE2LIN))
+			return 2;
+	}
+	val = snd_cmipci_read_b(cm, CM_REG_MIXER1);
+	if (val & CM_REAR2LIN)
+		return 1;
+	return 0;
+}
+
+static int snd_cmipci_line_in_mode_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&cm->reg_lock);
+	ucontrol->value.enumerated.item[0] = get_line_in_mode(cm);
+	spin_unlock_irq(&cm->reg_lock);
+	return 0;
+}
+
+static int snd_cmipci_line_in_mode_put(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	int change;
+
+	spin_lock_irq(&cm->reg_lock);
+	if (ucontrol->value.enumerated.item[0] == 2)
+		change = snd_cmipci_set_bit(cm, CM_REG_LEGACY_CTRL, CM_CENTR2LIN | CM_BASE2LIN);
+	else
+		change = snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_CENTR2LIN | CM_BASE2LIN);
+	if (ucontrol->value.enumerated.item[0] == 1)
+		change |= snd_cmipci_set_bit_b(cm, CM_REG_MIXER1, CM_REAR2LIN);
+	else
+		change |= snd_cmipci_clear_bit_b(cm, CM_REG_MIXER1, CM_REAR2LIN);
+	spin_unlock_irq(&cm->reg_lock);
+	return change;
+}
+
+static int snd_cmipci_mic_in_mode_info(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = { "Mic-In", "Center/LFE Output" };
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_cmipci_mic_in_mode_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	/* same bit as spdi_phase */
+	spin_lock_irq(&cm->reg_lock);
+	ucontrol->value.enumerated.item[0] = 
+		(snd_cmipci_read_b(cm, CM_REG_MISC) & CM_SPDIF_INVERSE) ? 1 : 0;
+	spin_unlock_irq(&cm->reg_lock);
+	return 0;
+}
+
+static int snd_cmipci_mic_in_mode_put(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct cmipci *cm = snd_kcontrol_chip(kcontrol);
+	int change;
+
+	spin_lock_irq(&cm->reg_lock);
+	if (ucontrol->value.enumerated.item[0])
+		change = snd_cmipci_set_bit_b(cm, CM_REG_MISC, CM_SPDIF_INVERSE);
+	else
+		change = snd_cmipci_clear_bit_b(cm, CM_REG_MISC, CM_SPDIF_INVERSE);
+	spin_unlock_irq(&cm->reg_lock);
+	return change;
+}
+
+/* both for CM8338/8738 */
+static struct snd_kcontrol_new snd_cmipci_mixer_switches[] __devinitdata = {
+	DEFINE_MIXER_SWITCH("Four Channel Mode", fourch),
+	{
+		.name = "Line-In Mode",
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.info = snd_cmipci_line_in_mode_info,
+		.get = snd_cmipci_line_in_mode_get,
+		.put = snd_cmipci_line_in_mode_put,
+	},
+};
+
+/* for non-multichannel chips */
+static struct snd_kcontrol_new snd_cmipci_nomulti_switch __devinitdata =
+DEFINE_MIXER_SWITCH("Exchange DAC", exchange_dac);
+
+/* only for CM8738 */
+static struct snd_kcontrol_new snd_cmipci_8738_mixer_switches[] __devinitdata = {
+#if 0 /* controlled in pcm device */
+	DEFINE_MIXER_SWITCH("IEC958 In Record", spdif_in),
+	DEFINE_MIXER_SWITCH("IEC958 Out", spdif_out),
+	DEFINE_MIXER_SWITCH("IEC958 Out To DAC", spdo2dac),
+#endif
+	// DEFINE_MIXER_SWITCH("IEC958 Output Switch", spdif_enable),
+	{ .name = "IEC958 Output Switch",
+	  .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .info = snd_cmipci_uswitch_info,
+	  .get = snd_cmipci_spdout_enable_get,
+	  .put = snd_cmipci_spdout_enable_put,
+	},
+	DEFINE_MIXER_SWITCH("IEC958 In Valid", spdi_valid),
+	DEFINE_MIXER_SWITCH("IEC958 Copyright", spdif_copyright),
+	DEFINE_MIXER_SWITCH("IEC958 5V", spdo_5v),
+//	DEFINE_MIXER_SWITCH("IEC958 In/Out 48KHz", spdo_48k),
+	DEFINE_MIXER_SWITCH("IEC958 Loop", spdif_loop),
+	DEFINE_MIXER_SWITCH("IEC958 In Monitor", spdi_monitor),
+};
+
+/* only for model 033/037 */
+static struct snd_kcontrol_new snd_cmipci_old_mixer_switches[] __devinitdata = {
+	DEFINE_MIXER_SWITCH("IEC958 Mix Analog", spdif_dac_out),
+	DEFINE_MIXER_SWITCH("IEC958 In Phase Inverse", spdi_phase),
+	DEFINE_MIXER_SWITCH("IEC958 In Select", spdif_in_sel1),
+};
+
+/* only for model 039 or later */
+static struct snd_kcontrol_new snd_cmipci_extra_mixer_switches[] __devinitdata = {
+	DEFINE_MIXER_SWITCH("IEC958 In Select", spdif_in_sel2),
+	DEFINE_MIXER_SWITCH("IEC958 In Phase Inverse", spdi_phase2),
+	{
+		.name = "Mic-In Mode",
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.info = snd_cmipci_mic_in_mode_info,
+		.get = snd_cmipci_mic_in_mode_get,
+		.put = snd_cmipci_mic_in_mode_put,
+	}
+};
+
+/* card control switches */
+static struct snd_kcontrol_new snd_cmipci_modem_switch __devinitdata =
+DEFINE_CARD_SWITCH("Modem", modem);
+
+
+static int __devinit snd_cmipci_mixer_new(struct cmipci *cm, int pcm_spdif_device)
+{
+	struct snd_card *card;
+	struct snd_kcontrol_new *sw;
+	struct snd_kcontrol *kctl;
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!cm || !cm->card))
+		return -EINVAL;
+
+	card = cm->card;
+
+	strcpy(card->mixername, "CMedia PCI");
+
+	spin_lock_irq(&cm->reg_lock);
+	snd_cmipci_mixer_write(cm, 0x00, 0x00);		/* mixer reset */
+	spin_unlock_irq(&cm->reg_lock);
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_cmipci_mixers); idx++) {
+		if (cm->chip_version == 68) {	// 8768 has no PCM volume
+			if (!strcmp(snd_cmipci_mixers[idx].name,
+				"PCM Playback Volume"))
+				continue;
+		}
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cmipci_mixers[idx], cm))) < 0)
+			return err;
+	}
+
+	/* mixer switches */
+	sw = snd_cmipci_mixer_switches;
+	for (idx = 0; idx < ARRAY_SIZE(snd_cmipci_mixer_switches); idx++, sw++) {
+		err = snd_ctl_add(cm->card, snd_ctl_new1(sw, cm));
+		if (err < 0)
+			return err;
+	}
+	if (! cm->can_multi_ch) {
+		err = snd_ctl_add(cm->card, snd_ctl_new1(&snd_cmipci_nomulti_switch, cm));
+		if (err < 0)
+			return err;
+	}
+	if (cm->device == PCI_DEVICE_ID_CMEDIA_CM8738 ||
+	    cm->device == PCI_DEVICE_ID_CMEDIA_CM8738B) {
+		sw = snd_cmipci_8738_mixer_switches;
+		for (idx = 0; idx < ARRAY_SIZE(snd_cmipci_8738_mixer_switches); idx++, sw++) {
+			err = snd_ctl_add(cm->card, snd_ctl_new1(sw, cm));
+			if (err < 0)
+				return err;
+		}
+		if (cm->can_ac3_hw) {
+			if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_cmipci_spdif_default, cm))) < 0)
+				return err;
+			kctl->id.device = pcm_spdif_device;
+			if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_cmipci_spdif_mask, cm))) < 0)
+				return err;
+			kctl->id.device = pcm_spdif_device;
+			if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_cmipci_spdif_stream, cm))) < 0)
+				return err;
+			kctl->id.device = pcm_spdif_device;
+		}
+		if (cm->chip_version <= 37) {
+			sw = snd_cmipci_old_mixer_switches;
+			for (idx = 0; idx < ARRAY_SIZE(snd_cmipci_old_mixer_switches); idx++, sw++) {
+				err = snd_ctl_add(cm->card, snd_ctl_new1(sw, cm));
+				if (err < 0)
+					return err;
+			}
+		}
+	}
+	if (cm->chip_version >= 39) {
+		sw = snd_cmipci_extra_mixer_switches;
+		for (idx = 0; idx < ARRAY_SIZE(snd_cmipci_extra_mixer_switches); idx++, sw++) {
+			err = snd_ctl_add(cm->card, snd_ctl_new1(sw, cm));
+			if (err < 0)
+				return err;
+		}
+	}
+
+	/* card switches */
+	/*
+	 * newer chips don't have the register bits to force modem link
+	 * detection; the bit that was FLINKON now mutes CH1
+	 */
+	if (cm->chip_version < 39) {
+		err = snd_ctl_add(cm->card,
+				  snd_ctl_new1(&snd_cmipci_modem_switch, cm));
+		if (err < 0)
+			return err;
+	}
+
+	for (idx = 0; idx < CM_SAVED_MIXERS; idx++) {
+		struct snd_ctl_elem_id elem_id;
+		struct snd_kcontrol *ctl;
+		memset(&elem_id, 0, sizeof(elem_id));
+		elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+		strcpy(elem_id.name, cm_saved_mixer[idx].name);
+		ctl = snd_ctl_find_id(cm->card, &elem_id);
+		if (ctl)
+			cm->mixer_res_ctl[idx] = ctl;
+	}
+
+	return 0;
+}
+
+
+/*
+ * proc interface
+ */
+
+#ifdef CONFIG_PROC_FS
+static void snd_cmipci_proc_read(struct snd_info_entry *entry, 
+				 struct snd_info_buffer *buffer)
+{
+	struct cmipci *cm = entry->private_data;
+	int i, v;
+	
+	snd_iprintf(buffer, "%s\n", cm->card->longname);
+	for (i = 0; i < 0x94; i++) {
+		if (i == 0x28)
+			i = 0x90;
+		v = inb(cm->iobase + i);
+		if (i % 4 == 0)
+			snd_iprintf(buffer, "\n%02x:", i);
+		snd_iprintf(buffer, " %02x", v);
+	}
+	snd_iprintf(buffer, "\n");
+}
+
+static void __devinit snd_cmipci_proc_init(struct cmipci *cm)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(cm->card, "cmipci", &entry))
+		snd_info_set_text_ops(entry, cm, snd_cmipci_proc_read);
+}
+#else /* !CONFIG_PROC_FS */
+static inline void snd_cmipci_proc_init(struct cmipci *cm) {}
+#endif
+
+
+static DEFINE_PCI_DEVICE_TABLE(snd_cmipci_ids) = {
+	{PCI_VDEVICE(CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8338A), 0},
+	{PCI_VDEVICE(CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8338B), 0},
+	{PCI_VDEVICE(CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8738), 0},
+	{PCI_VDEVICE(CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8738B), 0},
+	{PCI_VDEVICE(AL, PCI_DEVICE_ID_CMEDIA_CM8738), 0},
+	{0,},
+};
+
+
+/*
+ * check chip version and capabilities
+ * driver name is modified according to the chip model
+ */
+static void __devinit query_chip(struct cmipci *cm)
+{
+	unsigned int detect;
+
+	/* check reg 0Ch, bit 24-31 */
+	detect = snd_cmipci_read(cm, CM_REG_INT_HLDCLR) & CM_CHIP_MASK2;
+	if (! detect) {
+		/* check reg 08h, bit 24-28 */
+		detect = snd_cmipci_read(cm, CM_REG_CHFORMAT) & CM_CHIP_MASK1;
+		switch (detect) {
+		case 0:
+			cm->chip_version = 33;
+			if (cm->do_soft_ac3)
+				cm->can_ac3_sw = 1;
+			else
+				cm->can_ac3_hw = 1;
+			break;
+		case CM_CHIP_037:
+			cm->chip_version = 37;
+			cm->can_ac3_hw = 1;
+			break;
+		default:
+			cm->chip_version = 39;
+			cm->can_ac3_hw = 1;
+			break;
+		}
+		cm->max_channels = 2;
+	} else {
+		if (detect & CM_CHIP_039) {
+			cm->chip_version = 39;
+			if (detect & CM_CHIP_039_6CH) /* 4 or 6 channels */
+				cm->max_channels = 6;
+			else
+				cm->max_channels = 4;
+		} else if (detect & CM_CHIP_8768) {
+			cm->chip_version = 68;
+			cm->max_channels = 8;
+			cm->can_96k = 1;
+		} else {
+			cm->chip_version = 55;
+			cm->max_channels = 6;
+			cm->can_96k = 1;
+		}
+		cm->can_ac3_hw = 1;
+		cm->can_multi_ch = 1;
+	}
+}
+
+#ifdef SUPPORT_JOYSTICK
+static int __devinit snd_cmipci_create_gameport(struct cmipci *cm, int dev)
+{
+	static int ports[] = { 0x201, 0x200, 0 }; /* FIXME: majority is 0x201? */
+	struct gameport *gp;
+	struct resource *r = NULL;
+	int i, io_port = 0;
+
+	if (joystick_port[dev] == 0)
+		return -ENODEV;
+
+	if (joystick_port[dev] == 1) { /* auto-detect */
+		for (i = 0; ports[i]; i++) {
+			io_port = ports[i];
+			r = request_region(io_port, 1, "CMIPCI gameport");
+			if (r)
+				break;
+		}
+	} else {
+		io_port = joystick_port[dev];
+		r = request_region(io_port, 1, "CMIPCI gameport");
+	}
+
+	if (!r) {
+		printk(KERN_WARNING "cmipci: cannot reserve joystick ports\n");
+		return -EBUSY;
+	}
+
+	cm->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "cmipci: cannot allocate memory for gameport\n");
+		release_and_free_resource(r);
+		return -ENOMEM;
+	}
+	gameport_set_name(gp, "C-Media Gameport");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(cm->pci));
+	gameport_set_dev_parent(gp, &cm->pci->dev);
+	gp->io = io_port;
+	gameport_set_port_data(gp, r);
+
+	snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_JYSTK_EN);
+
+	gameport_register_port(cm->gameport);
+
+	return 0;
+}
+
+static void snd_cmipci_free_gameport(struct cmipci *cm)
+{
+	if (cm->gameport) {
+		struct resource *r = gameport_get_port_data(cm->gameport);
+
+		gameport_unregister_port(cm->gameport);
+		cm->gameport = NULL;
+
+		snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, CM_JYSTK_EN);
+		release_and_free_resource(r);
+	}
+}
+#else
+static inline int snd_cmipci_create_gameport(struct cmipci *cm, int dev) { return -ENOSYS; }
+static inline void snd_cmipci_free_gameport(struct cmipci *cm) { }
+#endif
+
+static int snd_cmipci_free(struct cmipci *cm)
+{
+	if (cm->irq >= 0) {
+		snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_FM_EN);
+		snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_ENSPDOUT);
+		snd_cmipci_write(cm, CM_REG_INT_HLDCLR, 0);  /* disable ints */
+		snd_cmipci_ch_reset(cm, CM_CH_PLAY);
+		snd_cmipci_ch_reset(cm, CM_CH_CAPT);
+		snd_cmipci_write(cm, CM_REG_FUNCTRL0, 0); /* disable channels */
+		snd_cmipci_write(cm, CM_REG_FUNCTRL1, 0);
+
+		/* reset mixer */
+		snd_cmipci_mixer_write(cm, 0, 0);
+
+		free_irq(cm->irq, cm);
+	}
+
+	snd_cmipci_free_gameport(cm);
+	pci_release_regions(cm->pci);
+	pci_disable_device(cm->pci);
+	kfree(cm);
+	return 0;
+}
+
+static int snd_cmipci_dev_free(struct snd_device *device)
+{
+	struct cmipci *cm = device->device_data;
+	return snd_cmipci_free(cm);
+}
+
+static int __devinit snd_cmipci_create_fm(struct cmipci *cm, long fm_port)
+{
+	long iosynth;
+	unsigned int val;
+	struct snd_opl3 *opl3;
+	int err;
+
+	if (!fm_port)
+		goto disable_fm;
+
+	if (cm->chip_version >= 39) {
+		/* first try FM regs in PCI port range */
+		iosynth = cm->iobase + CM_REG_FM_PCI;
+		err = snd_opl3_create(cm->card, iosynth, iosynth + 2,
+				      OPL3_HW_OPL3, 1, &opl3);
+	} else {
+		err = -EIO;
+	}
+	if (err < 0) {
+		/* then try legacy ports */
+		val = snd_cmipci_read(cm, CM_REG_LEGACY_CTRL) & ~CM_FMSEL_MASK;
+		iosynth = fm_port;
+		switch (iosynth) {
+		case 0x3E8: val |= CM_FMSEL_3E8; break;
+		case 0x3E0: val |= CM_FMSEL_3E0; break;
+		case 0x3C8: val |= CM_FMSEL_3C8; break;
+		case 0x388: val |= CM_FMSEL_388; break;
+		default:
+			goto disable_fm;
+		}
+		snd_cmipci_write(cm, CM_REG_LEGACY_CTRL, val);
+		/* enable FM */
+		snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_FM_EN);
+
+		if (snd_opl3_create(cm->card, iosynth, iosynth + 2,
+				    OPL3_HW_OPL3, 0, &opl3) < 0) {
+			printk(KERN_ERR "cmipci: no OPL device at %#lx, "
+			       "skipping...\n", iosynth);
+			goto disable_fm;
+		}
+	}
+	if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+		printk(KERN_ERR "cmipci: cannot create OPL3 hwdep\n");
+		return err;
+	}
+	return 0;
+
+ disable_fm:
+	snd_cmipci_clear_bit(cm, CM_REG_LEGACY_CTRL, CM_FMSEL_MASK);
+	snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_FM_EN);
+	return 0;
+}
+
+static int __devinit snd_cmipci_create(struct snd_card *card, struct pci_dev *pci,
+				       int dev, struct cmipci **rcmipci)
+{
+	struct cmipci *cm;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_cmipci_dev_free,
+	};
+	unsigned int val;
+	long iomidi = 0;
+	int integrated_midi = 0;
+	char modelstr[16];
+	int pcm_index, pcm_spdif_index;
+	static DEFINE_PCI_DEVICE_TABLE(intel_82437vx) = {
+		{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82437VX) },
+		{ },
+	};
+
+	*rcmipci = NULL;
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	cm = kzalloc(sizeof(*cm), GFP_KERNEL);
+	if (cm == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&cm->reg_lock);
+	mutex_init(&cm->open_mutex);
+	cm->device = pci->device;
+	cm->card = card;
+	cm->pci = pci;
+	cm->irq = -1;
+	cm->channel[0].ch = 0;
+	cm->channel[1].ch = 1;
+	cm->channel[0].is_dac = cm->channel[1].is_dac = 1; /* dual DAC mode */
+
+	if ((err = pci_request_regions(pci, card->driver)) < 0) {
+		kfree(cm);
+		pci_disable_device(pci);
+		return err;
+	}
+	cm->iobase = pci_resource_start(pci, 0);
+
+	if (request_irq(pci->irq, snd_cmipci_interrupt,
+			IRQF_SHARED, card->driver, cm)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_cmipci_free(cm);
+		return -EBUSY;
+	}
+	cm->irq = pci->irq;
+
+	pci_set_master(cm->pci);
+
+	/*
+	 * check chip version, max channels and capabilities
+	 */
+
+	cm->chip_version = 0;
+	cm->max_channels = 2;
+	cm->do_soft_ac3 = soft_ac3[dev];
+
+	if (pci->device != PCI_DEVICE_ID_CMEDIA_CM8338A &&
+	    pci->device != PCI_DEVICE_ID_CMEDIA_CM8338B)
+		query_chip(cm);
+	/* added -MCx suffix for chip supporting multi-channels */
+	if (cm->can_multi_ch)
+		sprintf(cm->card->driver + strlen(cm->card->driver),
+			"-MC%d", cm->max_channels);
+	else if (cm->can_ac3_sw)
+		strcpy(cm->card->driver + strlen(cm->card->driver), "-SWIEC");
+
+	cm->dig_status = SNDRV_PCM_DEFAULT_CON_SPDIF;
+	cm->dig_pcm_status = SNDRV_PCM_DEFAULT_CON_SPDIF;
+
+#if CM_CH_PLAY == 1
+	cm->ctrl = CM_CHADC0;	/* default FUNCNTRL0 */
+#else
+	cm->ctrl = CM_CHADC1;	/* default FUNCNTRL0 */
+#endif
+
+	/* initialize codec registers */
+	snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_RESET);
+	snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_RESET);
+	snd_cmipci_write(cm, CM_REG_INT_HLDCLR, 0);	/* disable ints */
+	snd_cmipci_ch_reset(cm, CM_CH_PLAY);
+	snd_cmipci_ch_reset(cm, CM_CH_CAPT);
+	snd_cmipci_write(cm, CM_REG_FUNCTRL0, 0);	/* disable channels */
+	snd_cmipci_write(cm, CM_REG_FUNCTRL1, 0);
+
+	snd_cmipci_write(cm, CM_REG_CHFORMAT, 0);
+	snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_ENDBDAC|CM_N4SPK3D);
+#if CM_CH_PLAY == 1
+	snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_XCHGDAC);
+#else
+	snd_cmipci_clear_bit(cm, CM_REG_MISC_CTRL, CM_XCHGDAC);
+#endif
+	if (cm->chip_version) {
+		snd_cmipci_write_b(cm, CM_REG_EXT_MISC, 0x20); /* magic */
+		snd_cmipci_write_b(cm, CM_REG_EXT_MISC + 1, 0x09); /* more magic */
+	}
+	/* Set Bus Master Request */
+	snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_BREQ);
+
+	/* Assume TX and compatible chip set (Autodetection required for VX chip sets) */
+	switch (pci->device) {
+	case PCI_DEVICE_ID_CMEDIA_CM8738:
+	case PCI_DEVICE_ID_CMEDIA_CM8738B:
+		if (!pci_dev_present(intel_82437vx)) 
+			snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_TXVX);
+		break;
+	default:
+		break;
+	}
+
+	if (cm->chip_version < 68) {
+		val = pci->device < 0x110 ? 8338 : 8738;
+	} else {
+		switch (snd_cmipci_read_b(cm, CM_REG_INT_HLDCLR + 3) & 0x03) {
+		case 0:
+			val = 8769;
+			break;
+		case 2:
+			val = 8762;
+			break;
+		default:
+			switch ((pci->subsystem_vendor << 16) |
+				pci->subsystem_device) {
+			case 0x13f69761:
+			case 0x584d3741:
+			case 0x584d3751:
+			case 0x584d3761:
+			case 0x584d3771:
+			case 0x72848384:
+				val = 8770;
+				break;
+			default:
+				val = 8768;
+				break;
+			}
+		}
+	}
+	sprintf(card->shortname, "C-Media CMI%d", val);
+	if (cm->chip_version < 68)
+		sprintf(modelstr, " (model %d)", cm->chip_version);
+	else
+		modelstr[0] = '\0';
+	sprintf(card->longname, "%s%s at %#lx, irq %i",
+		card->shortname, modelstr, cm->iobase, cm->irq);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, cm, &ops)) < 0) {
+		snd_cmipci_free(cm);
+		return err;
+	}
+
+	if (cm->chip_version >= 39) {
+		val = snd_cmipci_read_b(cm, CM_REG_MPU_PCI + 1);
+		if (val != 0x00 && val != 0xff) {
+			iomidi = cm->iobase + CM_REG_MPU_PCI;
+			integrated_midi = 1;
+		}
+	}
+	if (!integrated_midi) {
+		val = 0;
+		iomidi = mpu_port[dev];
+		switch (iomidi) {
+		case 0x320: val = CM_VMPU_320; break;
+		case 0x310: val = CM_VMPU_310; break;
+		case 0x300: val = CM_VMPU_300; break;
+		case 0x330: val = CM_VMPU_330; break;
+		default:
+			    iomidi = 0; break;
+		}
+		if (iomidi > 0) {
+			snd_cmipci_write(cm, CM_REG_LEGACY_CTRL, val);
+			/* enable UART */
+			snd_cmipci_set_bit(cm, CM_REG_FUNCTRL1, CM_UART_EN);
+			if (inb(iomidi + 1) == 0xff) {
+				snd_printk(KERN_ERR "cannot enable MPU-401 port"
+					   " at %#lx\n", iomidi);
+				snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1,
+						     CM_UART_EN);
+				iomidi = 0;
+			}
+		}
+	}
+
+	if (cm->chip_version < 68) {
+		err = snd_cmipci_create_fm(cm, fm_port[dev]);
+		if (err < 0)
+			return err;
+	}
+
+	/* reset mixer */
+	snd_cmipci_mixer_write(cm, 0, 0);
+
+	snd_cmipci_proc_init(cm);
+
+	/* create pcm devices */
+	pcm_index = pcm_spdif_index = 0;
+	if ((err = snd_cmipci_pcm_new(cm, pcm_index)) < 0)
+		return err;
+	pcm_index++;
+	if ((err = snd_cmipci_pcm2_new(cm, pcm_index)) < 0)
+		return err;
+	pcm_index++;
+	if (cm->can_ac3_hw || cm->can_ac3_sw) {
+		pcm_spdif_index = pcm_index;
+		if ((err = snd_cmipci_pcm_spdif_new(cm, pcm_index)) < 0)
+			return err;
+	}
+
+	/* create mixer interface & switches */
+	if ((err = snd_cmipci_mixer_new(cm, pcm_spdif_index)) < 0)
+		return err;
+
+	if (iomidi > 0) {
+		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_CMIPCI,
+					       iomidi,
+					       (integrated_midi ?
+						MPU401_INFO_INTEGRATED : 0),
+					       cm->irq, 0, &cm->rmidi)) < 0) {
+			printk(KERN_ERR "cmipci: no UART401 device at 0x%lx\n", iomidi);
+		}
+	}
+
+#ifdef USE_VAR48KRATE
+	for (val = 0; val < ARRAY_SIZE(rates); val++)
+		snd_cmipci_set_pll(cm, rates[val], val);
+
+	/*
+	 * (Re-)Enable external switch spdo_48k
+	 */
+	snd_cmipci_set_bit(cm, CM_REG_MISC_CTRL, CM_SPDIF48K|CM_SPDF_AC97);
+#endif /* USE_VAR48KRATE */
+
+	if (snd_cmipci_create_gameport(cm, dev) < 0)
+		snd_cmipci_clear_bit(cm, CM_REG_FUNCTRL1, CM_JYSTK_EN);
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rcmipci = cm;
+	return 0;
+}
+
+/*
+ */
+
+MODULE_DEVICE_TABLE(pci, snd_cmipci_ids);
+
+static int __devinit snd_cmipci_probe(struct pci_dev *pci,
+				      const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct cmipci *cm;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (! enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+	
+	switch (pci->device) {
+	case PCI_DEVICE_ID_CMEDIA_CM8738:
+	case PCI_DEVICE_ID_CMEDIA_CM8738B:
+		strcpy(card->driver, "CMI8738");
+		break;
+	case PCI_DEVICE_ID_CMEDIA_CM8338A:
+	case PCI_DEVICE_ID_CMEDIA_CM8338B:
+		strcpy(card->driver, "CMI8338");
+		break;
+	default:
+		strcpy(card->driver, "CMIPCI");
+		break;
+	}
+
+	if ((err = snd_cmipci_create(card, pci, dev, &cm)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = cm;
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+
+}
+
+static void __devexit snd_cmipci_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+static unsigned char saved_regs[] = {
+	CM_REG_FUNCTRL1, CM_REG_CHFORMAT, CM_REG_LEGACY_CTRL, CM_REG_MISC_CTRL,
+	CM_REG_MIXER0, CM_REG_MIXER1, CM_REG_MIXER2, CM_REG_MIXER3, CM_REG_PLL,
+	CM_REG_CH0_FRAME1, CM_REG_CH0_FRAME2,
+	CM_REG_CH1_FRAME1, CM_REG_CH1_FRAME2, CM_REG_EXT_MISC,
+	CM_REG_INT_STATUS, CM_REG_INT_HLDCLR, CM_REG_FUNCTRL0,
+};
+
+static unsigned char saved_mixers[] = {
+	SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1,
+	SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1,
+	SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1,
+	SB_DSP4_CD_DEV, SB_DSP4_CD_DEV + 1,
+	SB_DSP4_LINE_DEV, SB_DSP4_LINE_DEV + 1,
+	SB_DSP4_MIC_DEV, SB_DSP4_SPEAKER_DEV,
+	CM_REG_EXTENT_IND, SB_DSP4_OUTPUT_SW,
+	SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT,
+};
+
+static int snd_cmipci_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct cmipci *cm = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	
+	snd_pcm_suspend_all(cm->pcm);
+	snd_pcm_suspend_all(cm->pcm2);
+	snd_pcm_suspend_all(cm->pcm_spdif);
+
+	/* save registers */
+	for (i = 0; i < ARRAY_SIZE(saved_regs); i++)
+		cm->saved_regs[i] = snd_cmipci_read(cm, saved_regs[i]);
+	for (i = 0; i < ARRAY_SIZE(saved_mixers); i++)
+		cm->saved_mixers[i] = snd_cmipci_mixer_read(cm, saved_mixers[i]);
+
+	/* disable ints */
+	snd_cmipci_write(cm, CM_REG_INT_HLDCLR, 0);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_cmipci_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct cmipci *cm = card->private_data;
+	int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "cmipci: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	/* reset / initialize to a sane state */
+	snd_cmipci_write(cm, CM_REG_INT_HLDCLR, 0);
+	snd_cmipci_ch_reset(cm, CM_CH_PLAY);
+	snd_cmipci_ch_reset(cm, CM_CH_CAPT);
+	snd_cmipci_mixer_write(cm, 0, 0);
+
+	/* restore registers */
+	for (i = 0; i < ARRAY_SIZE(saved_regs); i++)
+		snd_cmipci_write(cm, saved_regs[i], cm->saved_regs[i]);
+	for (i = 0; i < ARRAY_SIZE(saved_mixers); i++)
+		snd_cmipci_mixer_write(cm, saved_mixers[i], cm->saved_mixers[i]);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static struct pci_driver driver = {
+	.name = "C-Media PCI",
+	.id_table = snd_cmipci_ids,
+	.probe = snd_cmipci_probe,
+	.remove = __devexit_p(snd_cmipci_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_cmipci_suspend,
+	.resume = snd_cmipci_resume,
+#endif
+};
+	
+static int __init alsa_card_cmipci_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_cmipci_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_cmipci_init)
+module_exit(alsa_card_cmipci_exit)
diff --git a/linux/sound/pci/cs4281.c b/linux/sound/pci/cs4281.c
new file mode 100644
index 0000000..6772070
--- /dev/null
+++ b/linux/sound/pci/cs4281.c
@@ -0,0 +1,2109 @@
+/*
+ *  Driver for Cirrus Logic CS4281 based PCI soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/ac97_codec.h>
+#include <sound/tlv.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Cirrus Logic CS4281");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Cirrus Logic,CS4281}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable switches */
+static int dual_codec[SNDRV_CARDS];	/* dual codec */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for CS4281 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for CS4281 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable CS4281 soundcard.");
+module_param_array(dual_codec, bool, NULL, 0444);
+MODULE_PARM_DESC(dual_codec, "Secondary Codec ID (0 = disabled).");
+
+/*
+ *  Direct registers
+ */
+
+#define CS4281_BA0_SIZE		0x1000
+#define CS4281_BA1_SIZE		0x10000
+
+/*
+ *  BA0 registers
+ */
+#define BA0_HISR		0x0000	/* Host Interrupt Status Register */
+#define BA0_HISR_INTENA		(1<<31)	/* Internal Interrupt Enable Bit */
+#define BA0_HISR_MIDI		(1<<22)	/* MIDI port interrupt */
+#define BA0_HISR_FIFOI		(1<<20)	/* FIFO polled interrupt */
+#define BA0_HISR_DMAI		(1<<18)	/* DMA interrupt (half or end) */
+#define BA0_HISR_FIFO(c)	(1<<(12+(c))) /* FIFO channel interrupt */
+#define BA0_HISR_DMA(c)		(1<<(8+(c)))  /* DMA channel interrupt */
+#define BA0_HISR_GPPI		(1<<5)	/* General Purpose Input (Primary chip) */
+#define BA0_HISR_GPSI		(1<<4)	/* General Purpose Input (Secondary chip) */
+#define BA0_HISR_GP3I		(1<<3)	/* GPIO3 pin Interrupt */
+#define BA0_HISR_GP1I		(1<<2)	/* GPIO1 pin Interrupt */
+#define BA0_HISR_VUPI		(1<<1)	/* VOLUP pin Interrupt */
+#define BA0_HISR_VDNI		(1<<0)	/* VOLDN pin Interrupt */
+
+#define BA0_HICR		0x0008	/* Host Interrupt Control Register */
+#define BA0_HICR_CHGM		(1<<1)	/* INTENA Change Mask */
+#define BA0_HICR_IEV		(1<<0)	/* INTENA Value */
+#define BA0_HICR_EOI		(3<<0)	/* End of Interrupt command */
+
+#define BA0_HIMR		0x000c	/* Host Interrupt Mask Register */
+					/* Use same contants as for BA0_HISR */
+
+#define BA0_IIER		0x0010	/* ISA Interrupt Enable Register */
+
+#define BA0_HDSR0		0x00f0	/* Host DMA Engine 0 Status Register */
+#define BA0_HDSR1		0x00f4	/* Host DMA Engine 1 Status Register */
+#define BA0_HDSR2		0x00f8	/* Host DMA Engine 2 Status Register */
+#define BA0_HDSR3		0x00fc	/* Host DMA Engine 3 Status Register */
+
+#define BA0_HDSR_CH1P		(1<<25)	/* Channel 1 Pending */
+#define BA0_HDSR_CH2P		(1<<24)	/* Channel 2 Pending */
+#define BA0_HDSR_DHTC		(1<<17)	/* DMA Half Terminal Count */
+#define BA0_HDSR_DTC		(1<<16)	/* DMA Terminal Count */
+#define BA0_HDSR_DRUN		(1<<15)	/* DMA Running */
+#define BA0_HDSR_RQ		(1<<7)	/* Pending Request */
+
+#define BA0_DCA0		0x0110	/* Host DMA Engine 0 Current Address */
+#define BA0_DCC0		0x0114	/* Host DMA Engine 0 Current Count */
+#define BA0_DBA0		0x0118	/* Host DMA Engine 0 Base Address */
+#define BA0_DBC0		0x011c	/* Host DMA Engine 0 Base Count */
+#define BA0_DCA1		0x0120	/* Host DMA Engine 1 Current Address */
+#define BA0_DCC1		0x0124	/* Host DMA Engine 1 Current Count */
+#define BA0_DBA1		0x0128	/* Host DMA Engine 1 Base Address */
+#define BA0_DBC1		0x012c	/* Host DMA Engine 1 Base Count */
+#define BA0_DCA2		0x0130	/* Host DMA Engine 2 Current Address */
+#define BA0_DCC2		0x0134	/* Host DMA Engine 2 Current Count */
+#define BA0_DBA2		0x0138	/* Host DMA Engine 2 Base Address */
+#define BA0_DBC2		0x013c	/* Host DMA Engine 2 Base Count */
+#define BA0_DCA3		0x0140	/* Host DMA Engine 3 Current Address */
+#define BA0_DCC3		0x0144	/* Host DMA Engine 3 Current Count */
+#define BA0_DBA3		0x0148	/* Host DMA Engine 3 Base Address */
+#define BA0_DBC3		0x014c	/* Host DMA Engine 3 Base Count */
+#define BA0_DMR0		0x0150	/* Host DMA Engine 0 Mode */
+#define BA0_DCR0		0x0154	/* Host DMA Engine 0 Command */
+#define BA0_DMR1		0x0158	/* Host DMA Engine 1 Mode */
+#define BA0_DCR1		0x015c	/* Host DMA Engine 1 Command */
+#define BA0_DMR2		0x0160	/* Host DMA Engine 2 Mode */
+#define BA0_DCR2		0x0164	/* Host DMA Engine 2 Command */
+#define BA0_DMR3		0x0168	/* Host DMA Engine 3 Mode */
+#define BA0_DCR3		0x016c	/* Host DMA Engine 3 Command */
+
+#define BA0_DMR_DMA		(1<<29)	/* Enable DMA mode */
+#define BA0_DMR_POLL		(1<<28)	/* Enable poll mode */
+#define BA0_DMR_TBC		(1<<25)	/* Transfer By Channel */
+#define BA0_DMR_CBC		(1<<24)	/* Count By Channel (0 = frame resolution) */
+#define BA0_DMR_SWAPC		(1<<22)	/* Swap Left/Right Channels */
+#define BA0_DMR_SIZE20		(1<<20)	/* Sample is 20-bit */
+#define BA0_DMR_USIGN		(1<<19)	/* Unsigned */
+#define BA0_DMR_BEND		(1<<18)	/* Big Endian */
+#define BA0_DMR_MONO		(1<<17)	/* Mono */
+#define BA0_DMR_SIZE8		(1<<16)	/* Sample is 8-bit */
+#define BA0_DMR_TYPE_DEMAND	(0<<6)
+#define BA0_DMR_TYPE_SINGLE	(1<<6)
+#define BA0_DMR_TYPE_BLOCK	(2<<6)
+#define BA0_DMR_TYPE_CASCADE	(3<<6)	/* Not supported */
+#define BA0_DMR_DEC		(1<<5)	/* Access Increment (0) or Decrement (1) */
+#define BA0_DMR_AUTO		(1<<4)	/* Auto-Initialize */
+#define BA0_DMR_TR_VERIFY	(0<<2)	/* Verify Transfer */
+#define BA0_DMR_TR_WRITE	(1<<2)	/* Write Transfer */
+#define BA0_DMR_TR_READ		(2<<2)	/* Read Transfer */
+
+#define BA0_DCR_HTCIE		(1<<17)	/* Half Terminal Count Interrupt */
+#define BA0_DCR_TCIE		(1<<16)	/* Terminal Count Interrupt */
+#define BA0_DCR_MSK		(1<<0)	/* DMA Mask bit */
+
+#define BA0_FCR0		0x0180	/* FIFO Control 0 */
+#define BA0_FCR1		0x0184	/* FIFO Control 1 */
+#define BA0_FCR2		0x0188	/* FIFO Control 2 */
+#define BA0_FCR3		0x018c	/* FIFO Control 3 */
+
+#define BA0_FCR_FEN		(1<<31)	/* FIFO Enable bit */
+#define BA0_FCR_DACZ		(1<<30)	/* DAC Zero */
+#define BA0_FCR_PSH		(1<<29)	/* Previous Sample Hold */
+#define BA0_FCR_RS(x)		(((x)&0x1f)<<24) /* Right Slot Mapping */
+#define BA0_FCR_LS(x)		(((x)&0x1f)<<16) /* Left Slot Mapping */
+#define BA0_FCR_SZ(x)		(((x)&0x7f)<<8)	/* FIFO buffer size (in samples) */
+#define BA0_FCR_OF(x)		(((x)&0x7f)<<0)	/* FIFO starting offset (in samples) */
+
+#define BA0_FPDR0		0x0190	/* FIFO Polled Data 0 */
+#define BA0_FPDR1		0x0194	/* FIFO Polled Data 1 */
+#define BA0_FPDR2		0x0198	/* FIFO Polled Data 2 */
+#define BA0_FPDR3		0x019c	/* FIFO Polled Data 3 */
+
+#define BA0_FCHS		0x020c	/* FIFO Channel Status */
+#define BA0_FCHS_RCO(x)		(1<<(7+(((x)&3)<<3))) /* Right Channel Out */
+#define BA0_FCHS_LCO(x)		(1<<(6+(((x)&3)<<3))) /* Left Channel Out */
+#define BA0_FCHS_MRP(x)		(1<<(5+(((x)&3)<<3))) /* Move Read Pointer */
+#define BA0_FCHS_FE(x)		(1<<(4+(((x)&3)<<3))) /* FIFO Empty */
+#define BA0_FCHS_FF(x)		(1<<(3+(((x)&3)<<3))) /* FIFO Full */
+#define BA0_FCHS_IOR(x)		(1<<(2+(((x)&3)<<3))) /* Internal Overrun Flag */
+#define BA0_FCHS_RCI(x)		(1<<(1+(((x)&3)<<3))) /* Right Channel In */
+#define BA0_FCHS_LCI(x)		(1<<(0+(((x)&3)<<3))) /* Left Channel In */
+
+#define BA0_FSIC0		0x0210	/* FIFO Status and Interrupt Control 0 */
+#define BA0_FSIC1		0x0214	/* FIFO Status and Interrupt Control 1 */
+#define BA0_FSIC2		0x0218	/* FIFO Status and Interrupt Control 2 */
+#define BA0_FSIC3		0x021c	/* FIFO Status and Interrupt Control 3 */
+
+#define BA0_FSIC_FIC(x)		(((x)&0x7f)<<24) /* FIFO Interrupt Count */
+#define BA0_FSIC_FORIE		(1<<23) /* FIFO OverRun Interrupt Enable */
+#define BA0_FSIC_FURIE		(1<<22) /* FIFO UnderRun Interrupt Enable */
+#define BA0_FSIC_FSCIE		(1<<16)	/* FIFO Sample Count Interrupt Enable */
+#define BA0_FSIC_FSC(x)		(((x)&0x7f)<<8) /* FIFO Sample Count */
+#define BA0_FSIC_FOR		(1<<7)	/* FIFO OverRun */
+#define BA0_FSIC_FUR		(1<<6)	/* FIFO UnderRun */
+#define BA0_FSIC_FSCR		(1<<0)	/* FIFO Sample Count Reached */
+
+#define BA0_PMCS		0x0344	/* Power Management Control/Status */
+#define BA0_CWPR		0x03e0	/* Configuration Write Protect */
+
+#define BA0_EPPMC		0x03e4	/* Extended PCI Power Management Control */
+#define BA0_EPPMC_FPDN		(1<<14) /* Full Power DowN */
+
+#define BA0_GPIOR		0x03e8	/* GPIO Pin Interface Register */
+
+#define BA0_SPMC		0x03ec	/* Serial Port Power Management Control (& ASDIN2 enable) */
+#define BA0_SPMC_GIPPEN		(1<<15)	/* GP INT Primary PME# Enable */
+#define BA0_SPMC_GISPEN		(1<<14)	/* GP INT Secondary PME# Enable */
+#define BA0_SPMC_EESPD		(1<<9)	/* EEPROM Serial Port Disable */
+#define BA0_SPMC_ASDI2E		(1<<8)	/* ASDIN2 Enable */
+#define BA0_SPMC_ASDO		(1<<7)	/* Asynchronous ASDOUT Assertion */
+#define BA0_SPMC_WUP2		(1<<3)	/* Wakeup for Secondary Input */
+#define BA0_SPMC_WUP1		(1<<2)	/* Wakeup for Primary Input */
+#define BA0_SPMC_ASYNC		(1<<1)	/* Asynchronous ASYNC Assertion */
+#define BA0_SPMC_RSTN		(1<<0)	/* Reset Not! */
+
+#define BA0_CFLR		0x03f0	/* Configuration Load Register (EEPROM or BIOS) */
+#define BA0_CFLR_DEFAULT	0x00000001 /* CFLR must be in AC97 link mode */
+#define BA0_IISR		0x03f4	/* ISA Interrupt Select */
+#define BA0_TMS			0x03f8	/* Test Register */
+#define BA0_SSVID		0x03fc	/* Subsystem ID register */
+
+#define BA0_CLKCR1		0x0400	/* Clock Control Register 1 */
+#define BA0_CLKCR1_CLKON	(1<<25)	/* Read Only */
+#define BA0_CLKCR1_DLLRDY	(1<<24)	/* DLL Ready */
+#define BA0_CLKCR1_DLLOS	(1<<6)	/* DLL Output Select */
+#define BA0_CLKCR1_SWCE		(1<<5)	/* Clock Enable */
+#define BA0_CLKCR1_DLLP		(1<<4)	/* DLL PowerUp */
+#define BA0_CLKCR1_DLLSS	(((x)&3)<<3) /* DLL Source Select */
+
+#define BA0_FRR			0x0410	/* Feature Reporting Register */
+#define BA0_SLT12O		0x041c	/* Slot 12 GPIO Output Register for AC-Link */
+
+#define BA0_SERMC		0x0420	/* Serial Port Master Control */
+#define BA0_SERMC_FCRN		(1<<27)	/* Force Codec Ready Not */
+#define BA0_SERMC_ODSEN2	(1<<25)	/* On-Demand Support Enable ASDIN2 */
+#define BA0_SERMC_ODSEN1	(1<<24)	/* On-Demand Support Enable ASDIN1 */
+#define BA0_SERMC_SXLB		(1<<21)	/* ASDIN2 to ASDOUT Loopback */
+#define BA0_SERMC_SLB		(1<<20)	/* ASDOUT to ASDIN2 Loopback */
+#define BA0_SERMC_LOVF		(1<<19)	/* Loopback Output Valid Frame bit */
+#define BA0_SERMC_TCID(x)	(((x)&3)<<16) /* Target Secondary Codec ID */
+#define BA0_SERMC_PXLB		(5<<1)	/* Primary Port External Loopback */
+#define BA0_SERMC_PLB		(4<<1)	/* Primary Port Internal Loopback */
+#define BA0_SERMC_PTC		(7<<1)	/* Port Timing Configuration */
+#define BA0_SERMC_PTC_AC97	(1<<1)	/* AC97 mode */
+#define BA0_SERMC_MSPE		(1<<0)	/* Master Serial Port Enable */
+
+#define BA0_SERC1		0x0428	/* Serial Port Configuration 1 */
+#define BA0_SERC1_SO1F(x)	(((x)&7)>>1) /* Primary Output Port Format */
+#define BA0_SERC1_AC97		(1<<1)
+#define BA0_SERC1_SO1EN		(1<<0)	/* Primary Output Port Enable */
+
+#define BA0_SERC2		0x042c	/* Serial Port Configuration 2 */
+#define BA0_SERC2_SI1F(x)	(((x)&7)>>1) /* Primary Input Port Format */
+#define BA0_SERC2_AC97		(1<<1)
+#define BA0_SERC2_SI1EN		(1<<0)	/* Primary Input Port Enable */
+
+#define BA0_SLT12M		0x045c	/* Slot 12 Monitor Register for Primary AC-Link */
+
+#define BA0_ACCTL		0x0460	/* AC'97 Control */
+#define BA0_ACCTL_TC		(1<<6)	/* Target Codec */
+#define BA0_ACCTL_CRW		(1<<4)	/* 0=Write, 1=Read Command */
+#define BA0_ACCTL_DCV		(1<<3)	/* Dynamic Command Valid */
+#define BA0_ACCTL_VFRM		(1<<2)	/* Valid Frame */
+#define BA0_ACCTL_ESYN		(1<<1)	/* Enable Sync */
+
+#define BA0_ACSTS		0x0464	/* AC'97 Status */
+#define BA0_ACSTS_VSTS		(1<<1)	/* Valid Status */
+#define BA0_ACSTS_CRDY		(1<<0)	/* Codec Ready */
+
+#define BA0_ACOSV		0x0468	/* AC'97 Output Slot Valid */
+#define BA0_ACOSV_SLV(x)	(1<<((x)-3))
+
+#define BA0_ACCAD		0x046c	/* AC'97 Command Address */
+#define BA0_ACCDA		0x0470	/* AC'97 Command Data */
+
+#define BA0_ACISV		0x0474	/* AC'97 Input Slot Valid */
+#define BA0_ACISV_SLV(x)	(1<<((x)-3))
+
+#define BA0_ACSAD		0x0478	/* AC'97 Status Address */
+#define BA0_ACSDA		0x047c	/* AC'97 Status Data */
+#define BA0_JSPT		0x0480	/* Joystick poll/trigger */
+#define BA0_JSCTL		0x0484	/* Joystick control */
+#define BA0_JSC1		0x0488	/* Joystick control */
+#define BA0_JSC2		0x048c	/* Joystick control */
+#define BA0_JSIO		0x04a0
+
+#define BA0_MIDCR		0x0490	/* MIDI Control */
+#define BA0_MIDCR_MRST		(1<<5)	/* Reset MIDI Interface */
+#define BA0_MIDCR_MLB		(1<<4)	/* MIDI Loop Back Enable */
+#define BA0_MIDCR_TIE		(1<<3)	/* MIDI Transmuit Interrupt Enable */
+#define BA0_MIDCR_RIE		(1<<2)	/* MIDI Receive Interrupt Enable */
+#define BA0_MIDCR_RXE		(1<<1)	/* MIDI Receive Enable */
+#define BA0_MIDCR_TXE		(1<<0)	/* MIDI Transmit Enable */
+
+#define BA0_MIDCMD		0x0494	/* MIDI Command (wo) */
+
+#define BA0_MIDSR		0x0494	/* MIDI Status (ro) */
+#define BA0_MIDSR_RDA		(1<<15)	/* Sticky bit (RBE 1->0) */
+#define BA0_MIDSR_TBE		(1<<14) /* Sticky bit (TBF 0->1) */
+#define BA0_MIDSR_RBE		(1<<7)	/* Receive Buffer Empty */
+#define BA0_MIDSR_TBF		(1<<6)	/* Transmit Buffer Full */
+
+#define BA0_MIDWP		0x0498	/* MIDI Write */
+#define BA0_MIDRP		0x049c	/* MIDI Read (ro) */
+
+#define BA0_AODSD1		0x04a8	/* AC'97 On-Demand Slot Disable for primary link (ro) */
+#define BA0_AODSD1_NDS(x)	(1<<((x)-3))
+
+#define BA0_AODSD2		0x04ac	/* AC'97 On-Demand Slot Disable for secondary link (ro) */
+#define BA0_AODSD2_NDS(x)	(1<<((x)-3))
+
+#define BA0_CFGI		0x04b0	/* Configure Interface (EEPROM interface) */
+#define BA0_SLT12M2		0x04dc	/* Slot 12 Monitor Register 2 for secondary AC-link */
+#define BA0_ACSTS2		0x04e4	/* AC'97 Status Register 2 */
+#define BA0_ACISV2		0x04f4	/* AC'97 Input Slot Valid Register 2 */
+#define BA0_ACSAD2		0x04f8	/* AC'97 Status Address Register 2 */
+#define BA0_ACSDA2		0x04fc	/* AC'97 Status Data Register 2 */
+#define BA0_FMSR		0x0730	/* FM Synthesis Status (ro) */
+#define BA0_B0AP		0x0730	/* FM Bank 0 Address Port (wo) */
+#define BA0_FMDP		0x0734	/* FM Data Port */
+#define BA0_B1AP		0x0738	/* FM Bank 1 Address Port */
+#define BA0_B1DP		0x073c	/* FM Bank 1 Data Port */
+
+#define BA0_SSPM		0x0740	/* Sound System Power Management */
+#define BA0_SSPM_MIXEN		(1<<6)	/* Playback SRC + FM/Wavetable MIX */
+#define BA0_SSPM_CSRCEN		(1<<5)	/* Capture Sample Rate Converter Enable */
+#define BA0_SSPM_PSRCEN		(1<<4)	/* Playback Sample Rate Converter Enable */
+#define BA0_SSPM_JSEN		(1<<3)	/* Joystick Enable */
+#define BA0_SSPM_ACLEN		(1<<2)	/* Serial Port Engine and AC-Link Enable */
+#define BA0_SSPM_FMEN		(1<<1)	/* FM Synthesis Block Enable */
+
+#define BA0_DACSR		0x0744	/* DAC Sample Rate - Playback SRC */
+#define BA0_ADCSR		0x0748	/* ADC Sample Rate - Capture SRC */
+
+#define BA0_SSCR		0x074c	/* Sound System Control Register */
+#define BA0_SSCR_HVS1		(1<<23)	/* Hardwave Volume Step (0=1,1=2) */
+#define BA0_SSCR_MVCS		(1<<19)	/* Master Volume Codec Select */
+#define BA0_SSCR_MVLD		(1<<18)	/* Master Volume Line Out Disable */
+#define BA0_SSCR_MVAD		(1<<17)	/* Master Volume Alternate Out Disable */
+#define BA0_SSCR_MVMD		(1<<16)	/* Master Volume Mono Out Disable */
+#define BA0_SSCR_XLPSRC		(1<<8)	/* External SRC Loopback Mode */
+#define BA0_SSCR_LPSRC		(1<<7)	/* SRC Loopback Mode */
+#define BA0_SSCR_CDTX		(1<<5)	/* CD Transfer Data */
+#define BA0_SSCR_HVC		(1<<3)	/* Harware Volume Control Enable */
+
+#define BA0_FMLVC		0x0754	/* FM Synthesis Left Volume Control */
+#define BA0_FMRVC		0x0758	/* FM Synthesis Right Volume Control */
+#define BA0_SRCSA		0x075c	/* SRC Slot Assignments */
+#define BA0_PPLVC		0x0760	/* PCM Playback Left Volume Control */
+#define BA0_PPRVC		0x0764	/* PCM Playback Right Volume Control */
+#define BA0_PASR		0x0768	/* playback sample rate */
+#define BA0_CASR		0x076C	/* capture sample rate */
+
+/* Source Slot Numbers - Playback */
+#define SRCSLOT_LEFT_PCM_PLAYBACK		0
+#define SRCSLOT_RIGHT_PCM_PLAYBACK		1
+#define SRCSLOT_PHONE_LINE_1_DAC		2
+#define SRCSLOT_CENTER_PCM_PLAYBACK		3
+#define SRCSLOT_LEFT_SURROUND_PCM_PLAYBACK	4
+#define SRCSLOT_RIGHT_SURROUND_PCM_PLAYBACK	5
+#define SRCSLOT_LFE_PCM_PLAYBACK		6
+#define SRCSLOT_PHONE_LINE_2_DAC		7
+#define SRCSLOT_HEADSET_DAC			8
+#define SRCSLOT_LEFT_WT				29  /* invalid for BA0_SRCSA */
+#define SRCSLOT_RIGHT_WT			30  /* invalid for BA0_SRCSA */
+
+/* Source Slot Numbers - Capture */
+#define SRCSLOT_LEFT_PCM_RECORD			10
+#define SRCSLOT_RIGHT_PCM_RECORD		11
+#define SRCSLOT_PHONE_LINE_1_ADC		12
+#define SRCSLOT_MIC_ADC				13
+#define SRCSLOT_PHONE_LINE_2_ADC		17
+#define SRCSLOT_HEADSET_ADC			18
+#define SRCSLOT_SECONDARY_LEFT_PCM_RECORD	20
+#define SRCSLOT_SECONDARY_RIGHT_PCM_RECORD	21
+#define SRCSLOT_SECONDARY_PHONE_LINE_1_ADC	22
+#define SRCSLOT_SECONDARY_MIC_ADC		23
+#define SRCSLOT_SECONDARY_PHONE_LINE_2_ADC	27
+#define SRCSLOT_SECONDARY_HEADSET_ADC		28
+
+/* Source Slot Numbers - Others */
+#define SRCSLOT_POWER_DOWN			31
+
+/* MIDI modes */
+#define CS4281_MODE_OUTPUT		(1<<0)
+#define CS4281_MODE_INPUT		(1<<1)
+
+/* joystick bits */
+/* Bits for JSPT */
+#define JSPT_CAX                                0x00000001
+#define JSPT_CAY                                0x00000002
+#define JSPT_CBX                                0x00000004
+#define JSPT_CBY                                0x00000008
+#define JSPT_BA1                                0x00000010
+#define JSPT_BA2                                0x00000020
+#define JSPT_BB1                                0x00000040
+#define JSPT_BB2                                0x00000080
+
+/* Bits for JSCTL */
+#define JSCTL_SP_MASK                           0x00000003
+#define JSCTL_SP_SLOW                           0x00000000
+#define JSCTL_SP_MEDIUM_SLOW                    0x00000001
+#define JSCTL_SP_MEDIUM_FAST                    0x00000002
+#define JSCTL_SP_FAST                           0x00000003
+#define JSCTL_ARE                               0x00000004
+
+/* Data register pairs masks */
+#define JSC1_Y1V_MASK                           0x0000FFFF
+#define JSC1_X1V_MASK                           0xFFFF0000
+#define JSC1_Y1V_SHIFT                          0
+#define JSC1_X1V_SHIFT                          16
+#define JSC2_Y2V_MASK                           0x0000FFFF
+#define JSC2_X2V_MASK                           0xFFFF0000
+#define JSC2_Y2V_SHIFT                          0
+#define JSC2_X2V_SHIFT                          16
+
+/* JS GPIO */
+#define JSIO_DAX                                0x00000001
+#define JSIO_DAY                                0x00000002
+#define JSIO_DBX                                0x00000004
+#define JSIO_DBY                                0x00000008
+#define JSIO_AXOE                               0x00000010
+#define JSIO_AYOE                               0x00000020
+#define JSIO_BXOE                               0x00000040
+#define JSIO_BYOE                               0x00000080
+
+/*
+ *
+ */
+
+struct cs4281_dma {
+	struct snd_pcm_substream *substream;
+	unsigned int regDBA;		/* offset to DBA register */
+	unsigned int regDCA;		/* offset to DCA register */
+	unsigned int regDBC;		/* offset to DBC register */
+	unsigned int regDCC;		/* offset to DCC register */
+	unsigned int regDMR;		/* offset to DMR register */
+	unsigned int regDCR;		/* offset to DCR register */
+	unsigned int regHDSR;		/* offset to HDSR register */
+	unsigned int regFCR;		/* offset to FCR register */
+	unsigned int regFSIC;		/* offset to FSIC register */
+	unsigned int valDMR;		/* DMA mode */
+	unsigned int valDCR;		/* DMA command */
+	unsigned int valFCR;		/* FIFO control */
+	unsigned int fifo_offset;	/* FIFO offset within BA1 */
+	unsigned char left_slot;	/* FIFO left slot */
+	unsigned char right_slot;	/* FIFO right slot */
+	int frag;			/* period number */
+};
+
+#define SUSPEND_REGISTERS	20
+
+struct cs4281 {
+	int irq;
+
+	void __iomem *ba0;		/* virtual (accessible) address */
+	void __iomem *ba1;		/* virtual (accessible) address */
+	unsigned long ba0_addr;
+	unsigned long ba1_addr;
+
+	int dual_codec;
+
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97 *ac97;
+	struct snd_ac97 *ac97_secondary;
+
+	struct pci_dev *pci;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *midi_input;
+	struct snd_rawmidi_substream *midi_output;
+
+	struct cs4281_dma dma[4];
+
+	unsigned char src_left_play_slot;
+	unsigned char src_right_play_slot;
+	unsigned char src_left_rec_slot;
+	unsigned char src_right_rec_slot;
+
+	unsigned int spurious_dhtc_irq;
+	unsigned int spurious_dtc_irq;
+
+	spinlock_t reg_lock;
+	unsigned int midcr;
+	unsigned int uartm;
+
+	struct gameport *gameport;
+
+#ifdef CONFIG_PM
+	u32 suspend_regs[SUSPEND_REGISTERS];
+#endif
+
+};
+
+static irqreturn_t snd_cs4281_interrupt(int irq, void *dev_id);
+
+static DEFINE_PCI_DEVICE_TABLE(snd_cs4281_ids) = {
+	{ PCI_VDEVICE(CIRRUS, 0x6005), 0, },	/* CS4281 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_cs4281_ids);
+
+/*
+ *  constants
+ */
+
+#define CS4281_FIFO_SIZE	32
+
+/*
+ *  common I/O routines
+ */
+
+static inline void snd_cs4281_pokeBA0(struct cs4281 *chip, unsigned long offset,
+				      unsigned int val)
+{
+        writel(val, chip->ba0 + offset);
+}
+
+static inline unsigned int snd_cs4281_peekBA0(struct cs4281 *chip, unsigned long offset)
+{
+        return readl(chip->ba0 + offset);
+}
+
+static void snd_cs4281_ac97_write(struct snd_ac97 *ac97,
+				  unsigned short reg, unsigned short val)
+{
+	/*
+	 *  1. Write ACCAD = Command Address Register = 46Ch for AC97 register address
+	 *  2. Write ACCDA = Command Data Register = 470h    for data to write to AC97
+	 *  3. Write ACCTL = Control Register = 460h for initiating the write
+	 *  4. Read ACCTL = 460h, DCV should be reset by now and 460h = 07h
+	 *  5. if DCV not cleared, break and return error
+	 */
+	struct cs4281 *chip = ac97->private_data;
+	int count;
+
+	/*
+	 *  Setup the AC97 control registers on the CS461x to send the
+	 *  appropriate command to the AC97 to perform the read.
+	 *  ACCAD = Command Address Register = 46Ch
+	 *  ACCDA = Command Data Register = 470h
+	 *  ACCTL = Control Register = 460h
+	 *  set DCV - will clear when process completed
+	 *  reset CRW - Write command
+	 *  set VFRM - valid frame enabled
+	 *  set ESYN - ASYNC generation enabled
+	 *  set RSTN - ARST# inactive, AC97 codec not reset
+         */
+	snd_cs4281_pokeBA0(chip, BA0_ACCAD, reg);
+	snd_cs4281_pokeBA0(chip, BA0_ACCDA, val);
+	snd_cs4281_pokeBA0(chip, BA0_ACCTL, BA0_ACCTL_DCV | BA0_ACCTL_VFRM |
+				            BA0_ACCTL_ESYN | (ac97->num ? BA0_ACCTL_TC : 0));
+	for (count = 0; count < 2000; count++) {
+		/*
+		 *  First, we want to wait for a short time.
+		 */
+		udelay(10);
+		/*
+		 *  Now, check to see if the write has completed.
+		 *  ACCTL = 460h, DCV should be reset by now and 460h = 07h
+		 */
+		if (!(snd_cs4281_peekBA0(chip, BA0_ACCTL) & BA0_ACCTL_DCV)) {
+			return;
+		}
+	}
+	snd_printk(KERN_ERR "AC'97 write problem, reg = 0x%x, val = 0x%x\n", reg, val);
+}
+
+static unsigned short snd_cs4281_ac97_read(struct snd_ac97 *ac97,
+					   unsigned short reg)
+{
+	struct cs4281 *chip = ac97->private_data;
+	int count;
+	unsigned short result;
+	// FIXME: volatile is necessary in the following due to a bug of
+	// some gcc versions
+	volatile int ac97_num = ((volatile struct snd_ac97 *)ac97)->num;
+
+	/*
+	 *  1. Write ACCAD = Command Address Register = 46Ch for AC97 register address
+	 *  2. Write ACCDA = Command Data Register = 470h    for data to write to AC97 
+	 *  3. Write ACCTL = Control Register = 460h for initiating the write
+	 *  4. Read ACCTL = 460h, DCV should be reset by now and 460h = 17h
+	 *  5. if DCV not cleared, break and return error
+	 *  6. Read ACSTS = Status Register = 464h, check VSTS bit
+	 */
+
+	snd_cs4281_peekBA0(chip, ac97_num ? BA0_ACSDA2 : BA0_ACSDA);
+
+	/*
+	 *  Setup the AC97 control registers on the CS461x to send the
+	 *  appropriate command to the AC97 to perform the read.
+	 *  ACCAD = Command Address Register = 46Ch
+	 *  ACCDA = Command Data Register = 470h
+	 *  ACCTL = Control Register = 460h
+	 *  set DCV - will clear when process completed
+	 *  set CRW - Read command
+	 *  set VFRM - valid frame enabled
+	 *  set ESYN - ASYNC generation enabled
+	 *  set RSTN - ARST# inactive, AC97 codec not reset
+	 */
+
+	snd_cs4281_pokeBA0(chip, BA0_ACCAD, reg);
+	snd_cs4281_pokeBA0(chip, BA0_ACCDA, 0);
+	snd_cs4281_pokeBA0(chip, BA0_ACCTL, BA0_ACCTL_DCV | BA0_ACCTL_CRW |
+					    BA0_ACCTL_VFRM | BA0_ACCTL_ESYN |
+			   (ac97_num ? BA0_ACCTL_TC : 0));
+
+
+	/*
+	 *  Wait for the read to occur.
+	 */
+	for (count = 0; count < 500; count++) {
+		/*
+		 *  First, we want to wait for a short time.
+	 	 */
+		udelay(10);
+		/*
+		 *  Now, check to see if the read has completed.
+		 *  ACCTL = 460h, DCV should be reset by now and 460h = 17h
+		 */
+		if (!(snd_cs4281_peekBA0(chip, BA0_ACCTL) & BA0_ACCTL_DCV))
+			goto __ok1;
+	}
+
+	snd_printk(KERN_ERR "AC'97 read problem (ACCTL_DCV), reg = 0x%x\n", reg);
+	result = 0xffff;
+	goto __end;
+	
+      __ok1:
+	/*
+	 *  Wait for the valid status bit to go active.
+	 */
+	for (count = 0; count < 100; count++) {
+		/*
+		 *  Read the AC97 status register.
+		 *  ACSTS = Status Register = 464h
+		 *  VSTS - Valid Status
+		 */
+		if (snd_cs4281_peekBA0(chip, ac97_num ? BA0_ACSTS2 : BA0_ACSTS) & BA0_ACSTS_VSTS)
+			goto __ok2;
+		udelay(10);
+	}
+	
+	snd_printk(KERN_ERR "AC'97 read problem (ACSTS_VSTS), reg = 0x%x\n", reg);
+	result = 0xffff;
+	goto __end;
+
+      __ok2:
+	/*
+	 *  Read the data returned from the AC97 register.
+	 *  ACSDA = Status Data Register = 474h
+	 */
+	result = snd_cs4281_peekBA0(chip, ac97_num ? BA0_ACSDA2 : BA0_ACSDA);
+
+      __end:
+	return result;
+}
+
+/*
+ *  PCM part
+ */
+
+static int snd_cs4281_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct cs4281_dma *dma = substream->runtime->private_data;
+	struct cs4281 *chip = snd_pcm_substream_chip(substream);
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		dma->valDCR |= BA0_DCR_MSK;
+		dma->valFCR |= BA0_FCR_FEN;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		dma->valDCR &= ~BA0_DCR_MSK;
+		dma->valFCR &= ~BA0_FCR_FEN;
+		break;
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		snd_cs4281_pokeBA0(chip, dma->regDMR, dma->valDMR & ~BA0_DMR_DMA);
+		dma->valDMR |= BA0_DMR_DMA;
+		dma->valDCR &= ~BA0_DCR_MSK;
+		dma->valFCR |= BA0_FCR_FEN;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		dma->valDMR &= ~(BA0_DMR_DMA|BA0_DMR_POLL);
+		dma->valDCR |= BA0_DCR_MSK;
+		dma->valFCR &= ~BA0_FCR_FEN;
+		/* Leave wave playback FIFO enabled for FM */
+		if (dma->regFCR != BA0_FCR0)
+			dma->valFCR &= ~BA0_FCR_FEN;
+		break;
+	default:
+		spin_unlock(&chip->reg_lock);
+		return -EINVAL;
+	}
+	snd_cs4281_pokeBA0(chip, dma->regDMR, dma->valDMR);
+	snd_cs4281_pokeBA0(chip, dma->regFCR, dma->valFCR);
+	snd_cs4281_pokeBA0(chip, dma->regDCR, dma->valDCR);
+	spin_unlock(&chip->reg_lock);
+	return 0;
+}
+
+static unsigned int snd_cs4281_rate(unsigned int rate, unsigned int *real_rate)
+{
+	unsigned int val = ~0;
+	
+	if (real_rate)
+		*real_rate = rate;
+	/* special "hardcoded" rates */
+	switch (rate) {
+	case 8000:	return 5;
+	case 11025:	return 4;
+	case 16000:	return 3;
+	case 22050:	return 2;
+	case 44100:	return 1;
+	case 48000:	return 0;
+	default:
+		goto __variable;
+	}
+      __variable:
+	val = 1536000 / rate;
+	if (real_rate)
+		*real_rate = 1536000 / val;
+	return val;
+}
+
+static void snd_cs4281_mode(struct cs4281 *chip, struct cs4281_dma *dma,
+			    struct snd_pcm_runtime *runtime,
+			    int capture, int src)
+{
+	int rec_mono;
+
+	dma->valDMR = BA0_DMR_TYPE_SINGLE | BA0_DMR_AUTO |
+		      (capture ? BA0_DMR_TR_WRITE : BA0_DMR_TR_READ);
+	if (runtime->channels == 1)
+		dma->valDMR |= BA0_DMR_MONO;
+	if (snd_pcm_format_unsigned(runtime->format) > 0)
+		dma->valDMR |= BA0_DMR_USIGN;
+	if (snd_pcm_format_big_endian(runtime->format) > 0)
+		dma->valDMR |= BA0_DMR_BEND;
+	switch (snd_pcm_format_width(runtime->format)) {
+	case 8: dma->valDMR |= BA0_DMR_SIZE8;
+		if (runtime->channels == 1)
+			dma->valDMR |= BA0_DMR_SWAPC;
+		break;
+	case 32: dma->valDMR |= BA0_DMR_SIZE20; break;
+	}
+	dma->frag = 0;	/* for workaround */
+	dma->valDCR = BA0_DCR_TCIE | BA0_DCR_MSK;
+	if (runtime->buffer_size != runtime->period_size)
+		dma->valDCR |= BA0_DCR_HTCIE;
+	/* Initialize DMA */
+	snd_cs4281_pokeBA0(chip, dma->regDBA, runtime->dma_addr);
+	snd_cs4281_pokeBA0(chip, dma->regDBC, runtime->buffer_size - 1);
+	rec_mono = (chip->dma[1].valDMR & BA0_DMR_MONO) == BA0_DMR_MONO;
+	snd_cs4281_pokeBA0(chip, BA0_SRCSA, (chip->src_left_play_slot << 0) |
+					    (chip->src_right_play_slot << 8) |
+					    (chip->src_left_rec_slot << 16) |
+					    ((rec_mono ? 31 : chip->src_right_rec_slot) << 24));
+	if (!src)
+		goto __skip_src;
+	if (!capture) {
+		if (dma->left_slot == chip->src_left_play_slot) {
+			unsigned int val = snd_cs4281_rate(runtime->rate, NULL);
+			snd_BUG_ON(dma->right_slot != chip->src_right_play_slot);
+			snd_cs4281_pokeBA0(chip, BA0_DACSR, val);
+		}
+	} else {
+		if (dma->left_slot == chip->src_left_rec_slot) {
+			unsigned int val = snd_cs4281_rate(runtime->rate, NULL);
+			snd_BUG_ON(dma->right_slot != chip->src_right_rec_slot);
+			snd_cs4281_pokeBA0(chip, BA0_ADCSR, val);
+		}
+	}
+      __skip_src:
+	/* Deactivate wave playback FIFO before changing slot assignments */
+	if (dma->regFCR == BA0_FCR0)
+		snd_cs4281_pokeBA0(chip, dma->regFCR, snd_cs4281_peekBA0(chip, dma->regFCR) & ~BA0_FCR_FEN);
+	/* Initialize FIFO */
+	dma->valFCR = BA0_FCR_LS(dma->left_slot) |
+		      BA0_FCR_RS(capture && (dma->valDMR & BA0_DMR_MONO) ? 31 : dma->right_slot) |
+		      BA0_FCR_SZ(CS4281_FIFO_SIZE) |
+		      BA0_FCR_OF(dma->fifo_offset);
+	snd_cs4281_pokeBA0(chip, dma->regFCR, dma->valFCR | (capture ? BA0_FCR_PSH : 0));
+	/* Activate FIFO again for FM playback */
+	if (dma->regFCR == BA0_FCR0)
+		snd_cs4281_pokeBA0(chip, dma->regFCR, dma->valFCR | BA0_FCR_FEN);
+	/* Clear FIFO Status and Interrupt Control Register */
+	snd_cs4281_pokeBA0(chip, dma->regFSIC, 0);
+}
+
+static int snd_cs4281_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_cs4281_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_cs4281_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct cs4281_dma *dma = runtime->private_data;
+	struct cs4281 *chip = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&chip->reg_lock);
+	snd_cs4281_mode(chip, dma, runtime, 0, 1);
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_cs4281_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct cs4281_dma *dma = runtime->private_data;
+	struct cs4281 *chip = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&chip->reg_lock);
+	snd_cs4281_mode(chip, dma, runtime, 1, 1);
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_cs4281_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct cs4281_dma *dma = runtime->private_data;
+	struct cs4281 *chip = snd_pcm_substream_chip(substream);
+
+	/*
+	printk(KERN_DEBUG "DCC = 0x%x, buffer_size = 0x%x, jiffies = %li\n",
+	       snd_cs4281_peekBA0(chip, dma->regDCC), runtime->buffer_size,
+	       jiffies);
+	*/
+	return runtime->buffer_size -
+	       snd_cs4281_peekBA0(chip, dma->regDCC) - 1;
+}
+
+static struct snd_pcm_hardware snd_cs4281_playback =
+{
+	.info =			SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_PAUSE |
+				SNDRV_PCM_INFO_RESUME,
+	.formats =		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_U32_LE | SNDRV_PCM_FMTBIT_S32_LE |
+				SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(512*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(512*1024),
+	.periods_min =		1,
+	.periods_max =		2,
+	.fifo_size =		CS4281_FIFO_SIZE,
+};
+
+static struct snd_pcm_hardware snd_cs4281_capture =
+{
+	.info =			SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_PAUSE |
+				SNDRV_PCM_INFO_RESUME,
+	.formats =		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_U32_LE | SNDRV_PCM_FMTBIT_S32_LE |
+				SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(512*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(512*1024),
+	.periods_min =		1,
+	.periods_max =		2,
+	.fifo_size =		CS4281_FIFO_SIZE,
+};
+
+static int snd_cs4281_playback_open(struct snd_pcm_substream *substream)
+{
+	struct cs4281 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct cs4281_dma *dma;
+
+	dma = &chip->dma[0];
+	dma->substream = substream;
+	dma->left_slot = 0;
+	dma->right_slot = 1;
+	runtime->private_data = dma;
+	runtime->hw = snd_cs4281_playback;
+	/* should be detected from the AC'97 layer, but it seems
+	   that although CS4297A rev B reports 18-bit ADC resolution,
+	   samples are 20-bit */
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 20);
+	return 0;
+}
+
+static int snd_cs4281_capture_open(struct snd_pcm_substream *substream)
+{
+	struct cs4281 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct cs4281_dma *dma;
+
+	dma = &chip->dma[1];
+	dma->substream = substream;
+	dma->left_slot = 10;
+	dma->right_slot = 11;
+	runtime->private_data = dma;
+	runtime->hw = snd_cs4281_capture;
+	/* should be detected from the AC'97 layer, but it seems
+	   that although CS4297A rev B reports 18-bit ADC resolution,
+	   samples are 20-bit */
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 20);
+	return 0;
+}
+
+static int snd_cs4281_playback_close(struct snd_pcm_substream *substream)
+{
+	struct cs4281_dma *dma = substream->runtime->private_data;
+
+	dma->substream = NULL;
+	return 0;
+}
+
+static int snd_cs4281_capture_close(struct snd_pcm_substream *substream)
+{
+	struct cs4281_dma *dma = substream->runtime->private_data;
+
+	dma->substream = NULL;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_cs4281_playback_ops = {
+	.open =		snd_cs4281_playback_open,
+	.close =	snd_cs4281_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cs4281_hw_params,
+	.hw_free =	snd_cs4281_hw_free,
+	.prepare =	snd_cs4281_playback_prepare,
+	.trigger =	snd_cs4281_trigger,
+	.pointer =	snd_cs4281_pointer,
+};
+
+static struct snd_pcm_ops snd_cs4281_capture_ops = {
+	.open =		snd_cs4281_capture_open,
+	.close =	snd_cs4281_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cs4281_hw_params,
+	.hw_free =	snd_cs4281_hw_free,
+	.prepare =	snd_cs4281_capture_prepare,
+	.trigger =	snd_cs4281_trigger,
+	.pointer =	snd_cs4281_pointer,
+};
+
+static int __devinit snd_cs4281_pcm(struct cs4281 * chip, int device,
+				    struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	err = snd_pcm_new(chip->card, "CS4281", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs4281_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cs4281_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "CS4281");
+	chip->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci), 64*1024, 512*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+/*
+ *  Mixer section
+ */
+
+#define CS_VOL_MASK	0x1f
+
+static int snd_cs4281_info_volume(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type              = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count             = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = CS_VOL_MASK;
+	return 0;
+}
+ 
+static int snd_cs4281_get_volume(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct cs4281 *chip = snd_kcontrol_chip(kcontrol);
+	int regL = (kcontrol->private_value >> 16) & 0xffff;
+	int regR = kcontrol->private_value & 0xffff;
+	int volL, volR;
+
+	volL = CS_VOL_MASK - (snd_cs4281_peekBA0(chip, regL) & CS_VOL_MASK);
+	volR = CS_VOL_MASK - (snd_cs4281_peekBA0(chip, regR) & CS_VOL_MASK);
+
+	ucontrol->value.integer.value[0] = volL;
+	ucontrol->value.integer.value[1] = volR;
+	return 0;
+}
+
+static int snd_cs4281_put_volume(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct cs4281 *chip = snd_kcontrol_chip(kcontrol);
+	int change = 0;
+	int regL = (kcontrol->private_value >> 16) & 0xffff;
+	int regR = kcontrol->private_value & 0xffff;
+	int volL, volR;
+
+	volL = CS_VOL_MASK - (snd_cs4281_peekBA0(chip, regL) & CS_VOL_MASK);
+	volR = CS_VOL_MASK - (snd_cs4281_peekBA0(chip, regR) & CS_VOL_MASK);
+
+	if (ucontrol->value.integer.value[0] != volL) {
+		volL = CS_VOL_MASK - (ucontrol->value.integer.value[0] & CS_VOL_MASK);
+		snd_cs4281_pokeBA0(chip, regL, volL);
+		change = 1;
+	}
+	if (ucontrol->value.integer.value[1] != volR) {
+		volR = CS_VOL_MASK - (ucontrol->value.integer.value[1] & CS_VOL_MASK);
+		snd_cs4281_pokeBA0(chip, regR, volR);
+		change = 1;
+	}
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_dsp, -4650, 150, 0);
+
+static struct snd_kcontrol_new snd_cs4281_fm_vol = 
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Synth Playback Volume",
+	.info = snd_cs4281_info_volume, 
+	.get = snd_cs4281_get_volume,
+	.put = snd_cs4281_put_volume, 
+	.private_value = ((BA0_FMLVC << 16) | BA0_FMRVC),
+	.tlv = { .p = db_scale_dsp },
+};
+
+static struct snd_kcontrol_new snd_cs4281_pcm_vol = 
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PCM Stream Playback Volume",
+	.info = snd_cs4281_info_volume, 
+	.get = snd_cs4281_get_volume,
+	.put = snd_cs4281_put_volume, 
+	.private_value = ((BA0_PPLVC << 16) | BA0_PPRVC),
+	.tlv = { .p = db_scale_dsp },
+};
+
+static void snd_cs4281_mixer_free_ac97_bus(struct snd_ac97_bus *bus)
+{
+	struct cs4281 *chip = bus->private_data;
+	chip->ac97_bus = NULL;
+}
+
+static void snd_cs4281_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+	struct cs4281 *chip = ac97->private_data;
+	if (ac97->num)
+		chip->ac97_secondary = NULL;
+	else
+		chip->ac97 = NULL;
+}
+
+static int __devinit snd_cs4281_mixer(struct cs4281 * chip)
+{
+	struct snd_card *card = chip->card;
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_cs4281_ac97_write,
+		.read = snd_cs4281_ac97_read,
+	};
+
+	if ((err = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus)) < 0)
+		return err;
+	chip->ac97_bus->private_free = snd_cs4281_mixer_free_ac97_bus;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.private_free = snd_cs4281_mixer_free_ac97;
+	if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97)) < 0)
+		return err;
+	if (chip->dual_codec) {
+		ac97.num = 1;
+		if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97_secondary)) < 0)
+			return err;
+	}
+	if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4281_fm_vol, chip))) < 0)
+		return err;
+	if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4281_pcm_vol, chip))) < 0)
+		return err;
+	return 0;
+}
+
+
+/*
+ * proc interface
+ */
+
+static void snd_cs4281_proc_read(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer)
+{
+	struct cs4281 *chip = entry->private_data;
+
+	snd_iprintf(buffer, "Cirrus Logic CS4281\n\n");
+	snd_iprintf(buffer, "Spurious half IRQs   : %u\n", chip->spurious_dhtc_irq);
+	snd_iprintf(buffer, "Spurious end IRQs    : %u\n", chip->spurious_dtc_irq);
+}
+
+static ssize_t snd_cs4281_BA0_read(struct snd_info_entry *entry,
+				   void *file_private_data,
+				   struct file *file, char __user *buf,
+				   size_t count, loff_t pos)
+{
+	struct cs4281 *chip = entry->private_data;
+	
+	if (copy_to_user_fromio(buf, chip->ba0 + pos, count))
+		return -EFAULT;
+	return count;
+}
+
+static ssize_t snd_cs4281_BA1_read(struct snd_info_entry *entry,
+				   void *file_private_data,
+				   struct file *file, char __user *buf,
+				   size_t count, loff_t pos)
+{
+	struct cs4281 *chip = entry->private_data;
+	
+	if (copy_to_user_fromio(buf, chip->ba1 + pos, count))
+		return -EFAULT;
+	return count;
+}
+
+static struct snd_info_entry_ops snd_cs4281_proc_ops_BA0 = {
+	.read = snd_cs4281_BA0_read,
+};
+
+static struct snd_info_entry_ops snd_cs4281_proc_ops_BA1 = {
+	.read = snd_cs4281_BA1_read,
+};
+
+static void __devinit snd_cs4281_proc_init(struct cs4281 * chip)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(chip->card, "cs4281", &entry))
+		snd_info_set_text_ops(entry, chip, snd_cs4281_proc_read);
+	if (! snd_card_proc_new(chip->card, "cs4281_BA0", &entry)) {
+		entry->content = SNDRV_INFO_CONTENT_DATA;
+		entry->private_data = chip;
+		entry->c.ops = &snd_cs4281_proc_ops_BA0;
+		entry->size = CS4281_BA0_SIZE;
+	}
+	if (! snd_card_proc_new(chip->card, "cs4281_BA1", &entry)) {
+		entry->content = SNDRV_INFO_CONTENT_DATA;
+		entry->private_data = chip;
+		entry->c.ops = &snd_cs4281_proc_ops_BA1;
+		entry->size = CS4281_BA1_SIZE;
+	}
+}
+
+/*
+ * joystick support
+ */
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+
+static void snd_cs4281_gameport_trigger(struct gameport *gameport)
+{
+	struct cs4281 *chip = gameport_get_port_data(gameport);
+
+	if (snd_BUG_ON(!chip))
+		return;
+	snd_cs4281_pokeBA0(chip, BA0_JSPT, 0xff);
+}
+
+static unsigned char snd_cs4281_gameport_read(struct gameport *gameport)
+{
+	struct cs4281 *chip = gameport_get_port_data(gameport);
+
+	if (snd_BUG_ON(!chip))
+		return 0;
+	return snd_cs4281_peekBA0(chip, BA0_JSPT);
+}
+
+#ifdef COOKED_MODE
+static int snd_cs4281_gameport_cooked_read(struct gameport *gameport,
+					   int *axes, int *buttons)
+{
+	struct cs4281 *chip = gameport_get_port_data(gameport);
+	unsigned js1, js2, jst;
+	
+	if (snd_BUG_ON(!chip))
+		return 0;
+
+	js1 = snd_cs4281_peekBA0(chip, BA0_JSC1);
+	js2 = snd_cs4281_peekBA0(chip, BA0_JSC2);
+	jst = snd_cs4281_peekBA0(chip, BA0_JSPT);
+	
+	*buttons = (~jst >> 4) & 0x0F; 
+	
+	axes[0] = ((js1 & JSC1_Y1V_MASK) >> JSC1_Y1V_SHIFT) & 0xFFFF;
+	axes[1] = ((js1 & JSC1_X1V_MASK) >> JSC1_X1V_SHIFT) & 0xFFFF;
+	axes[2] = ((js2 & JSC2_Y2V_MASK) >> JSC2_Y2V_SHIFT) & 0xFFFF;
+	axes[3] = ((js2 & JSC2_X2V_MASK) >> JSC2_X2V_SHIFT) & 0xFFFF;
+
+	for (jst = 0; jst < 4; ++jst)
+		if (axes[jst] == 0xFFFF) axes[jst] = -1;
+	return 0;
+}
+#else
+#define snd_cs4281_gameport_cooked_read	NULL
+#endif
+
+static int snd_cs4281_gameport_open(struct gameport *gameport, int mode)
+{
+	switch (mode) {
+#ifdef COOKED_MODE
+	case GAMEPORT_MODE_COOKED:
+		return 0;
+#endif
+	case GAMEPORT_MODE_RAW:
+		return 0;
+	default:
+		return -1;
+	}
+	return 0;
+}
+
+static int __devinit snd_cs4281_create_gameport(struct cs4281 *chip)
+{
+	struct gameport *gp;
+
+	chip->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "cs4281: cannot allocate memory for gameport\n");
+		return -ENOMEM;
+	}
+
+	gameport_set_name(gp, "CS4281 Gameport");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
+	gameport_set_dev_parent(gp, &chip->pci->dev);
+	gp->open = snd_cs4281_gameport_open;
+	gp->read = snd_cs4281_gameport_read;
+	gp->trigger = snd_cs4281_gameport_trigger;
+	gp->cooked_read = snd_cs4281_gameport_cooked_read;
+	gameport_set_port_data(gp, chip);
+
+	snd_cs4281_pokeBA0(chip, BA0_JSIO, 0xFF); // ?
+	snd_cs4281_pokeBA0(chip, BA0_JSCTL, JSCTL_SP_MEDIUM_SLOW);
+
+	gameport_register_port(gp);
+
+	return 0;
+}
+
+static void snd_cs4281_free_gameport(struct cs4281 *chip)
+{
+	if (chip->gameport) {
+		gameport_unregister_port(chip->gameport);
+		chip->gameport = NULL;
+	}
+}
+#else
+static inline int snd_cs4281_create_gameport(struct cs4281 *chip) { return -ENOSYS; }
+static inline void snd_cs4281_free_gameport(struct cs4281 *chip) { }
+#endif /* CONFIG_GAMEPORT || (MODULE && CONFIG_GAMEPORT_MODULE) */
+
+static int snd_cs4281_free(struct cs4281 *chip)
+{
+	snd_cs4281_free_gameport(chip);
+
+	if (chip->irq >= 0)
+		synchronize_irq(chip->irq);
+
+	/* Mask interrupts */
+	snd_cs4281_pokeBA0(chip, BA0_HIMR, 0x7fffffff);
+	/* Stop the DLL Clock logic. */
+	snd_cs4281_pokeBA0(chip, BA0_CLKCR1, 0);
+	/* Sound System Power Management - Turn Everything OFF */
+	snd_cs4281_pokeBA0(chip, BA0_SSPM, 0);
+	/* PCI interface - D3 state */
+	pci_set_power_state(chip->pci, 3);
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	if (chip->ba0)
+		iounmap(chip->ba0);
+	if (chip->ba1)
+		iounmap(chip->ba1);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+
+	kfree(chip);
+	return 0;
+}
+
+static int snd_cs4281_dev_free(struct snd_device *device)
+{
+	struct cs4281 *chip = device->device_data;
+	return snd_cs4281_free(chip);
+}
+
+static int snd_cs4281_chip_init(struct cs4281 *chip); /* defined below */
+
+static int __devinit snd_cs4281_create(struct snd_card *card,
+				       struct pci_dev *pci,
+				       struct cs4281 ** rchip,
+				       int dual_codec)
+{
+	struct cs4281 *chip;
+	unsigned int tmp;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_cs4281_dev_free,
+	};
+
+	*rchip = NULL;
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	spin_lock_init(&chip->reg_lock);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	pci_set_master(pci);
+	if (dual_codec < 0 || dual_codec > 3) {
+		snd_printk(KERN_ERR "invalid dual_codec option %d\n", dual_codec);
+		dual_codec = 0;
+	}
+	chip->dual_codec = dual_codec;
+
+	if ((err = pci_request_regions(pci, "CS4281")) < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+	chip->ba0_addr = pci_resource_start(pci, 0);
+	chip->ba1_addr = pci_resource_start(pci, 1);
+
+	chip->ba0 = pci_ioremap_bar(pci, 0);
+	chip->ba1 = pci_ioremap_bar(pci, 1);
+	if (!chip->ba0 || !chip->ba1) {
+		snd_cs4281_free(chip);
+		return -ENOMEM;
+	}
+	
+	if (request_irq(pci->irq, snd_cs4281_interrupt, IRQF_SHARED,
+			"CS4281", chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_cs4281_free(chip);
+		return -ENOMEM;
+	}
+	chip->irq = pci->irq;
+
+	tmp = snd_cs4281_chip_init(chip);
+	if (tmp) {
+		snd_cs4281_free(chip);
+		return tmp;
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_cs4281_free(chip);
+		return err;
+	}
+
+	snd_cs4281_proc_init(chip);
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rchip = chip;
+	return 0;
+}
+
+static int snd_cs4281_chip_init(struct cs4281 *chip)
+{
+	unsigned int tmp;
+	unsigned long end_time;
+	int retry_count = 2;
+
+	/* Having EPPMC.FPDN=1 prevent proper chip initialisation */
+	tmp = snd_cs4281_peekBA0(chip, BA0_EPPMC);
+	if (tmp & BA0_EPPMC_FPDN)
+		snd_cs4281_pokeBA0(chip, BA0_EPPMC, tmp & ~BA0_EPPMC_FPDN);
+
+      __retry:
+	tmp = snd_cs4281_peekBA0(chip, BA0_CFLR);
+	if (tmp != BA0_CFLR_DEFAULT) {
+		snd_cs4281_pokeBA0(chip, BA0_CFLR, BA0_CFLR_DEFAULT);
+		tmp = snd_cs4281_peekBA0(chip, BA0_CFLR);
+		if (tmp != BA0_CFLR_DEFAULT) {
+			snd_printk(KERN_ERR "CFLR setup failed (0x%x)\n", tmp);
+			return -EIO;
+		}
+	}
+
+	/* Set the 'Configuration Write Protect' register
+	 * to 4281h.  Allows vendor-defined configuration
+         * space between 0e4h and 0ffh to be written. */	
+	snd_cs4281_pokeBA0(chip, BA0_CWPR, 0x4281);
+	
+	if ((tmp = snd_cs4281_peekBA0(chip, BA0_SERC1)) != (BA0_SERC1_SO1EN | BA0_SERC1_AC97)) {
+		snd_printk(KERN_ERR "SERC1 AC'97 check failed (0x%x)\n", tmp);
+		return -EIO;
+	}
+	if ((tmp = snd_cs4281_peekBA0(chip, BA0_SERC2)) != (BA0_SERC2_SI1EN | BA0_SERC2_AC97)) {
+		snd_printk(KERN_ERR "SERC2 AC'97 check failed (0x%x)\n", tmp);
+		return -EIO;
+	}
+
+	/* Sound System Power Management */
+	snd_cs4281_pokeBA0(chip, BA0_SSPM, BA0_SSPM_MIXEN | BA0_SSPM_CSRCEN |
+				           BA0_SSPM_PSRCEN | BA0_SSPM_JSEN |
+				           BA0_SSPM_ACLEN | BA0_SSPM_FMEN);
+
+	/* Serial Port Power Management */
+ 	/* Blast the clock control register to zero so that the
+         * PLL starts out in a known state, and blast the master serial
+         * port control register to zero so that the serial ports also
+         * start out in a known state. */
+	snd_cs4281_pokeBA0(chip, BA0_CLKCR1, 0);
+	snd_cs4281_pokeBA0(chip, BA0_SERMC, 0);
+
+        /* Make ESYN go to zero to turn off
+         * the Sync pulse on the AC97 link. */
+	snd_cs4281_pokeBA0(chip, BA0_ACCTL, 0);
+	udelay(50);
+                
+	/*  Drive the ARST# pin low for a minimum of 1uS (as defined in the AC97
+	 *  spec) and then drive it high.  This is done for non AC97 modes since
+	 *  there might be logic external to the CS4281 that uses the ARST# line
+	 *  for a reset. */
+	snd_cs4281_pokeBA0(chip, BA0_SPMC, 0);
+	udelay(50);
+	snd_cs4281_pokeBA0(chip, BA0_SPMC, BA0_SPMC_RSTN);
+	msleep(50);
+
+	if (chip->dual_codec)
+		snd_cs4281_pokeBA0(chip, BA0_SPMC, BA0_SPMC_RSTN | BA0_SPMC_ASDI2E);
+
+	/*
+	 *  Set the serial port timing configuration.
+	 */
+	snd_cs4281_pokeBA0(chip, BA0_SERMC,
+			   (chip->dual_codec ? BA0_SERMC_TCID(chip->dual_codec) : BA0_SERMC_TCID(1)) |
+			   BA0_SERMC_PTC_AC97 | BA0_SERMC_MSPE);
+
+	/*
+	 *  Start the DLL Clock logic.
+	 */
+	snd_cs4281_pokeBA0(chip, BA0_CLKCR1, BA0_CLKCR1_DLLP);
+	msleep(50);
+	snd_cs4281_pokeBA0(chip, BA0_CLKCR1, BA0_CLKCR1_SWCE | BA0_CLKCR1_DLLP);
+
+	/*
+	 * Wait for the DLL ready signal from the clock logic.
+	 */
+	end_time = jiffies + HZ;
+	do {
+		/*
+		 *  Read the AC97 status register to see if we've seen a CODEC
+		 *  signal from the AC97 codec.
+		 */
+		if (snd_cs4281_peekBA0(chip, BA0_CLKCR1) & BA0_CLKCR1_DLLRDY)
+			goto __ok0;
+		schedule_timeout_uninterruptible(1);
+	} while (time_after_eq(end_time, jiffies));
+
+	snd_printk(KERN_ERR "DLLRDY not seen\n");
+	return -EIO;
+
+      __ok0:
+
+	/*
+	 *  The first thing we do here is to enable sync generation.  As soon
+	 *  as we start receiving bit clock, we'll start producing the SYNC
+	 *  signal.
+	 */
+	snd_cs4281_pokeBA0(chip, BA0_ACCTL, BA0_ACCTL_ESYN);
+
+	/*
+	 * Wait for the codec ready signal from the AC97 codec.
+	 */
+	end_time = jiffies + HZ;
+	do {
+		/*
+		 *  Read the AC97 status register to see if we've seen a CODEC
+		 *  signal from the AC97 codec.
+		 */
+		if (snd_cs4281_peekBA0(chip, BA0_ACSTS) & BA0_ACSTS_CRDY)
+			goto __ok1;
+		schedule_timeout_uninterruptible(1);
+	} while (time_after_eq(end_time, jiffies));
+
+	snd_printk(KERN_ERR "never read codec ready from AC'97 (0x%x)\n", snd_cs4281_peekBA0(chip, BA0_ACSTS));
+	return -EIO;
+
+      __ok1:
+	if (chip->dual_codec) {
+		end_time = jiffies + HZ;
+		do {
+			if (snd_cs4281_peekBA0(chip, BA0_ACSTS2) & BA0_ACSTS_CRDY)
+				goto __codec2_ok;
+			schedule_timeout_uninterruptible(1);
+		} while (time_after_eq(end_time, jiffies));
+		snd_printk(KERN_INFO "secondary codec doesn't respond. disable it...\n");
+		chip->dual_codec = 0;
+	__codec2_ok: ;
+	}
+
+	/*
+	 *  Assert the valid frame signal so that we can start sending commands
+	 *  to the AC97 codec.
+	 */
+
+	snd_cs4281_pokeBA0(chip, BA0_ACCTL, BA0_ACCTL_VFRM | BA0_ACCTL_ESYN);
+
+	/*
+	 *  Wait until we've sampled input slots 3 and 4 as valid, meaning that
+	 *  the codec is pumping ADC data across the AC-link.
+	 */
+
+	end_time = jiffies + HZ;
+	do {
+		/*
+		 *  Read the input slot valid register and see if input slots 3
+		 *  4 are valid yet.
+		 */
+                if ((snd_cs4281_peekBA0(chip, BA0_ACISV) & (BA0_ACISV_SLV(3) | BA0_ACISV_SLV(4))) == (BA0_ACISV_SLV(3) | BA0_ACISV_SLV(4)))
+                        goto __ok2;
+		schedule_timeout_uninterruptible(1);
+	} while (time_after_eq(end_time, jiffies));
+
+	if (--retry_count > 0)
+		goto __retry;
+	snd_printk(KERN_ERR "never read ISV3 and ISV4 from AC'97\n");
+	return -EIO;
+
+      __ok2:
+
+	/*
+	 *  Now, assert valid frame and the slot 3 and 4 valid bits.  This will
+	 *  commense the transfer of digital audio data to the AC97 codec.
+	 */
+	snd_cs4281_pokeBA0(chip, BA0_ACOSV, BA0_ACOSV_SLV(3) | BA0_ACOSV_SLV(4));
+
+	/*
+	 *  Initialize DMA structures
+	 */
+	for (tmp = 0; tmp < 4; tmp++) {
+		struct cs4281_dma *dma = &chip->dma[tmp];
+		dma->regDBA = BA0_DBA0 + (tmp * 0x10);
+		dma->regDCA = BA0_DCA0 + (tmp * 0x10);
+		dma->regDBC = BA0_DBC0 + (tmp * 0x10);
+		dma->regDCC = BA0_DCC0 + (tmp * 0x10);
+		dma->regDMR = BA0_DMR0 + (tmp * 8);
+		dma->regDCR = BA0_DCR0 + (tmp * 8);
+		dma->regHDSR = BA0_HDSR0 + (tmp * 4);
+		dma->regFCR = BA0_FCR0 + (tmp * 4);
+		dma->regFSIC = BA0_FSIC0 + (tmp * 4);
+		dma->fifo_offset = tmp * CS4281_FIFO_SIZE;
+		snd_cs4281_pokeBA0(chip, dma->regFCR,
+				   BA0_FCR_LS(31) |
+				   BA0_FCR_RS(31) |
+				   BA0_FCR_SZ(CS4281_FIFO_SIZE) |
+				   BA0_FCR_OF(dma->fifo_offset));
+	}
+
+	chip->src_left_play_slot = 0;	/* AC'97 left PCM playback (3) */
+	chip->src_right_play_slot = 1;	/* AC'97 right PCM playback (4) */
+	chip->src_left_rec_slot = 10;	/* AC'97 left PCM record (3) */
+	chip->src_right_rec_slot = 11;	/* AC'97 right PCM record (4) */
+
+	/* Activate wave playback FIFO for FM playback */
+	chip->dma[0].valFCR = BA0_FCR_FEN | BA0_FCR_LS(0) |
+		              BA0_FCR_RS(1) |
+ 	  	              BA0_FCR_SZ(CS4281_FIFO_SIZE) |
+		              BA0_FCR_OF(chip->dma[0].fifo_offset);
+	snd_cs4281_pokeBA0(chip, chip->dma[0].regFCR, chip->dma[0].valFCR);
+	snd_cs4281_pokeBA0(chip, BA0_SRCSA, (chip->src_left_play_slot << 0) |
+					    (chip->src_right_play_slot << 8) |
+					    (chip->src_left_rec_slot << 16) |
+					    (chip->src_right_rec_slot << 24));
+
+	/* Initialize digital volume */
+	snd_cs4281_pokeBA0(chip, BA0_PPLVC, 0);
+	snd_cs4281_pokeBA0(chip, BA0_PPRVC, 0);
+
+	/* Enable IRQs */
+	snd_cs4281_pokeBA0(chip, BA0_HICR, BA0_HICR_EOI);
+	/* Unmask interrupts */
+	snd_cs4281_pokeBA0(chip, BA0_HIMR, 0x7fffffff & ~(
+					BA0_HISR_MIDI |
+					BA0_HISR_DMAI |
+					BA0_HISR_DMA(0) |
+					BA0_HISR_DMA(1) |
+					BA0_HISR_DMA(2) |
+					BA0_HISR_DMA(3)));
+	synchronize_irq(chip->irq);
+
+	return 0;
+}
+
+/*
+ *  MIDI section
+ */
+
+static void snd_cs4281_midi_reset(struct cs4281 *chip)
+{
+	snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr | BA0_MIDCR_MRST);
+	udelay(100);
+	snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+}
+
+static int snd_cs4281_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct cs4281 *chip = substream->rmidi->private_data;
+
+	spin_lock_irq(&chip->reg_lock);
+ 	chip->midcr |= BA0_MIDCR_RXE;
+	chip->midi_input = substream;
+	if (!(chip->uartm & CS4281_MODE_OUTPUT)) {
+		snd_cs4281_midi_reset(chip);
+	} else {
+		snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+	}
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_cs4281_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct cs4281 *chip = substream->rmidi->private_data;
+
+	spin_lock_irq(&chip->reg_lock);
+	chip->midcr &= ~(BA0_MIDCR_RXE | BA0_MIDCR_RIE);
+	chip->midi_input = NULL;
+	if (!(chip->uartm & CS4281_MODE_OUTPUT)) {
+		snd_cs4281_midi_reset(chip);
+	} else {
+		snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+	}
+	chip->uartm &= ~CS4281_MODE_INPUT;
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_cs4281_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct cs4281 *chip = substream->rmidi->private_data;
+
+	spin_lock_irq(&chip->reg_lock);
+	chip->uartm |= CS4281_MODE_OUTPUT;
+	chip->midcr |= BA0_MIDCR_TXE;
+	chip->midi_output = substream;
+	if (!(chip->uartm & CS4281_MODE_INPUT)) {
+		snd_cs4281_midi_reset(chip);
+	} else {
+		snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+	}
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_cs4281_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct cs4281 *chip = substream->rmidi->private_data;
+
+	spin_lock_irq(&chip->reg_lock);
+	chip->midcr &= ~(BA0_MIDCR_TXE | BA0_MIDCR_TIE);
+	chip->midi_output = NULL;
+	if (!(chip->uartm & CS4281_MODE_INPUT)) {
+		snd_cs4281_midi_reset(chip);
+	} else {
+		snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+	}
+	chip->uartm &= ~CS4281_MODE_OUTPUT;
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static void snd_cs4281_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct cs4281 *chip = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (up) {
+		if ((chip->midcr & BA0_MIDCR_RIE) == 0) {
+			chip->midcr |= BA0_MIDCR_RIE;
+			snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+		}
+	} else {
+		if (chip->midcr & BA0_MIDCR_RIE) {
+			chip->midcr &= ~BA0_MIDCR_RIE;
+			snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+		}
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs4281_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct cs4281 *chip = substream->rmidi->private_data;
+	unsigned char byte;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (up) {
+		if ((chip->midcr & BA0_MIDCR_TIE) == 0) {
+			chip->midcr |= BA0_MIDCR_TIE;
+			/* fill UART FIFO buffer at first, and turn Tx interrupts only if necessary */
+			while ((chip->midcr & BA0_MIDCR_TIE) &&
+			       (snd_cs4281_peekBA0(chip, BA0_MIDSR) & BA0_MIDSR_TBF) == 0) {
+				if (snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+					chip->midcr &= ~BA0_MIDCR_TIE;
+				} else {
+					snd_cs4281_pokeBA0(chip, BA0_MIDWP, byte);
+				}
+			}
+			snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+		}
+	} else {
+		if (chip->midcr & BA0_MIDCR_TIE) {
+			chip->midcr &= ~BA0_MIDCR_TIE;
+			snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+		}
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static struct snd_rawmidi_ops snd_cs4281_midi_output =
+{
+	.open =		snd_cs4281_midi_output_open,
+	.close =	snd_cs4281_midi_output_close,
+	.trigger =	snd_cs4281_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_cs4281_midi_input =
+{
+	.open = 	snd_cs4281_midi_input_open,
+	.close =	snd_cs4281_midi_input_close,
+	.trigger =	snd_cs4281_midi_input_trigger,
+};
+
+static int __devinit snd_cs4281_midi(struct cs4281 * chip, int device,
+				     struct snd_rawmidi **rrawmidi)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	if ((err = snd_rawmidi_new(chip->card, "CS4281", device, 1, 1, &rmidi)) < 0)
+		return err;
+	strcpy(rmidi->name, "CS4281");
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_cs4281_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_cs4281_midi_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = chip;
+	chip->rmidi = rmidi;
+	if (rrawmidi)
+		*rrawmidi = rmidi;
+	return 0;
+}
+
+/*
+ *  Interrupt handler
+ */
+
+static irqreturn_t snd_cs4281_interrupt(int irq, void *dev_id)
+{
+	struct cs4281 *chip = dev_id;
+	unsigned int status, dma, val;
+	struct cs4281_dma *cdma;
+
+	if (chip == NULL)
+		return IRQ_NONE;
+	status = snd_cs4281_peekBA0(chip, BA0_HISR);
+	if ((status & 0x7fffffff) == 0) {
+		snd_cs4281_pokeBA0(chip, BA0_HICR, BA0_HICR_EOI);
+		return IRQ_NONE;
+	}
+
+	if (status & (BA0_HISR_DMA(0)|BA0_HISR_DMA(1)|BA0_HISR_DMA(2)|BA0_HISR_DMA(3))) {
+		for (dma = 0; dma < 4; dma++)
+			if (status & BA0_HISR_DMA(dma)) {
+				cdma = &chip->dma[dma];
+				spin_lock(&chip->reg_lock);
+				/* ack DMA IRQ */
+				val = snd_cs4281_peekBA0(chip, cdma->regHDSR);
+				/* workaround, sometimes CS4281 acknowledges */
+				/* end or middle transfer position twice */
+				cdma->frag++;
+				if ((val & BA0_HDSR_DHTC) && !(cdma->frag & 1)) {
+					cdma->frag--;
+					chip->spurious_dhtc_irq++;
+					spin_unlock(&chip->reg_lock);
+					continue;
+				}
+				if ((val & BA0_HDSR_DTC) && (cdma->frag & 1)) {
+					cdma->frag--;
+					chip->spurious_dtc_irq++;
+					spin_unlock(&chip->reg_lock);
+					continue;
+				}
+				spin_unlock(&chip->reg_lock);
+				snd_pcm_period_elapsed(cdma->substream);
+			}
+	}
+
+	if ((status & BA0_HISR_MIDI) && chip->rmidi) {
+		unsigned char c;
+		
+		spin_lock(&chip->reg_lock);
+		while ((snd_cs4281_peekBA0(chip, BA0_MIDSR) & BA0_MIDSR_RBE) == 0) {
+			c = snd_cs4281_peekBA0(chip, BA0_MIDRP);
+			if ((chip->midcr & BA0_MIDCR_RIE) == 0)
+				continue;
+			snd_rawmidi_receive(chip->midi_input, &c, 1);
+		}
+		while ((snd_cs4281_peekBA0(chip, BA0_MIDSR) & BA0_MIDSR_TBF) == 0) {
+			if ((chip->midcr & BA0_MIDCR_TIE) == 0)
+				break;
+			if (snd_rawmidi_transmit(chip->midi_output, &c, 1) != 1) {
+				chip->midcr &= ~BA0_MIDCR_TIE;
+				snd_cs4281_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+				break;
+			}
+			snd_cs4281_pokeBA0(chip, BA0_MIDWP, c);
+		}
+		spin_unlock(&chip->reg_lock);
+	}
+
+	/* EOI to the PCI part... reenables interrupts */
+	snd_cs4281_pokeBA0(chip, BA0_HICR, BA0_HICR_EOI);
+
+	return IRQ_HANDLED;
+}
+
+
+/*
+ * OPL3 command
+ */
+static void snd_cs4281_opl3_command(struct snd_opl3 *opl3, unsigned short cmd,
+				    unsigned char val)
+{
+	unsigned long flags;
+	struct cs4281 *chip = opl3->private_data;
+	void __iomem *port;
+
+	if (cmd & OPL3_RIGHT)
+		port = chip->ba0 + BA0_B1AP; /* right port */
+	else
+		port = chip->ba0 + BA0_B0AP; /* left port */
+
+	spin_lock_irqsave(&opl3->reg_lock, flags);
+
+	writel((unsigned int)cmd, port);
+	udelay(10);
+
+	writel((unsigned int)val, port + 4);
+	udelay(30);
+
+	spin_unlock_irqrestore(&opl3->reg_lock, flags);
+}
+
+static int __devinit snd_cs4281_probe(struct pci_dev *pci,
+				      const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct cs4281 *chip;
+	struct snd_opl3 *opl3;
+	int err;
+
+        if (dev >= SNDRV_CARDS)
+                return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	if ((err = snd_cs4281_create(card, pci, &chip, dual_codec[dev])) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = chip;
+
+	if ((err = snd_cs4281_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4281_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs4281_midi(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_opl3_new(card, OPL3_HW_OPL3_CS4281, &opl3)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	opl3->private_data = chip;
+	opl3->command = snd_cs4281_opl3_command;
+	snd_opl3_init(opl3);
+	if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_cs4281_create_gameport(chip);
+	strcpy(card->driver, "CS4281");
+	strcpy(card->shortname, "Cirrus Logic CS4281");
+	sprintf(card->longname, "%s at 0x%lx, irq %d",
+		card->shortname,
+		chip->ba0_addr,
+		chip->irq);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_cs4281_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+/*
+ * Power Management
+ */
+#ifdef CONFIG_PM
+
+static int saved_regs[SUSPEND_REGISTERS] = {
+	BA0_JSCTL,
+	BA0_GPIOR,
+	BA0_SSCR,
+	BA0_MIDCR,
+	BA0_SRCSA,
+	BA0_PASR,
+	BA0_CASR,
+	BA0_DACSR,
+	BA0_ADCSR,
+	BA0_FMLVC,
+	BA0_FMRVC,
+	BA0_PPLVC,
+	BA0_PPRVC,
+};
+
+#define CLKCR1_CKRA                             0x00010000L
+
+static int cs4281_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct cs4281 *chip = card->private_data;
+	u32 ulCLK;
+	unsigned int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+
+	snd_ac97_suspend(chip->ac97);
+	snd_ac97_suspend(chip->ac97_secondary);
+
+	ulCLK = snd_cs4281_peekBA0(chip, BA0_CLKCR1);
+	ulCLK |= CLKCR1_CKRA;
+	snd_cs4281_pokeBA0(chip, BA0_CLKCR1, ulCLK);
+
+	/* Disable interrupts. */
+	snd_cs4281_pokeBA0(chip, BA0_HICR, BA0_HICR_CHGM);
+
+	/* remember the status registers */
+	for (i = 0; i < ARRAY_SIZE(saved_regs); i++)
+		if (saved_regs[i])
+			chip->suspend_regs[i] = snd_cs4281_peekBA0(chip, saved_regs[i]);
+
+	/* Turn off the serial ports. */
+	snd_cs4281_pokeBA0(chip, BA0_SERMC, 0);
+
+	/* Power off FM, Joystick, AC link, */
+	snd_cs4281_pokeBA0(chip, BA0_SSPM, 0);
+
+	/* DLL off. */
+	snd_cs4281_pokeBA0(chip, BA0_CLKCR1, 0);
+
+	/* AC link off. */
+	snd_cs4281_pokeBA0(chip, BA0_SPMC, 0);
+
+	ulCLK = snd_cs4281_peekBA0(chip, BA0_CLKCR1);
+	ulCLK &= ~CLKCR1_CKRA;
+	snd_cs4281_pokeBA0(chip, BA0_CLKCR1, ulCLK);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int cs4281_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct cs4281 *chip = card->private_data;
+	unsigned int i;
+	u32 ulCLK;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "cs4281: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	ulCLK = snd_cs4281_peekBA0(chip, BA0_CLKCR1);
+	ulCLK |= CLKCR1_CKRA;
+	snd_cs4281_pokeBA0(chip, BA0_CLKCR1, ulCLK);
+
+	snd_cs4281_chip_init(chip);
+
+	/* restore the status registers */
+	for (i = 0; i < ARRAY_SIZE(saved_regs); i++)
+		if (saved_regs[i])
+			snd_cs4281_pokeBA0(chip, saved_regs[i], chip->suspend_regs[i]);
+
+	snd_ac97_resume(chip->ac97);
+	snd_ac97_resume(chip->ac97_secondary);
+
+	ulCLK = snd_cs4281_peekBA0(chip, BA0_CLKCR1);
+	ulCLK &= ~CLKCR1_CKRA;
+	snd_cs4281_pokeBA0(chip, BA0_CLKCR1, ulCLK);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static struct pci_driver driver = {
+	.name = "CS4281",
+	.id_table = snd_cs4281_ids,
+	.probe = snd_cs4281_probe,
+	.remove = __devexit_p(snd_cs4281_remove),
+#ifdef CONFIG_PM
+	.suspend = cs4281_suspend,
+	.resume = cs4281_resume,
+#endif
+};
+	
+static int __init alsa_card_cs4281_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_cs4281_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_cs4281_init)
+module_exit(alsa_card_cs4281_exit)
diff --git a/linux/sound/pci/cs46xx/Makefile b/linux/sound/pci/cs46xx/Makefile
new file mode 100644
index 0000000..67e811e
--- /dev/null
+++ b/linux/sound/pci/cs46xx/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-cs46xx-y := cs46xx.o cs46xx_lib.o
+snd-cs46xx-$(CONFIG_SND_CS46XX_NEW_DSP) += dsp_spos.o dsp_spos_scb_lib.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_CS46XX) += snd-cs46xx.o
diff --git a/linux/sound/pci/cs46xx/cs46xx.c b/linux/sound/pci/cs46xx/cs46xx.c
new file mode 100644
index 0000000..767fa7f
--- /dev/null
+++ b/linux/sound/pci/cs46xx/cs46xx.c
@@ -0,0 +1,186 @@
+/*
+ *  The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+  NOTES:
+  - sometimes the sound is metallic and sibilant, unloading and 
+    reloading the module may solve this.
+*/
+
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/cs46xx.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Cirrus Logic Sound Fusion CS46XX");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Cirrus Logic,Sound Fusion (CS4280)},"
+		"{Cirrus Logic,Sound Fusion (CS4610)},"
+		"{Cirrus Logic,Sound Fusion (CS4612)},"
+		"{Cirrus Logic,Sound Fusion (CS4615)},"
+		"{Cirrus Logic,Sound Fusion (CS4622)},"
+		"{Cirrus Logic,Sound Fusion (CS4624)},"
+		"{Cirrus Logic,Sound Fusion (CS4630)}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static int external_amp[SNDRV_CARDS];
+static int thinkpad[SNDRV_CARDS];
+static int mmap_valid[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the CS46xx soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the CS46xx soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable CS46xx soundcard.");
+module_param_array(external_amp, bool, NULL, 0444);
+MODULE_PARM_DESC(external_amp, "Force to enable external amplifer.");
+module_param_array(thinkpad, bool, NULL, 0444);
+MODULE_PARM_DESC(thinkpad, "Force to enable Thinkpad's CLKRUN control.");
+module_param_array(mmap_valid, bool, NULL, 0444);
+MODULE_PARM_DESC(mmap_valid, "Support OSS mmap.");
+
+static DEFINE_PCI_DEVICE_TABLE(snd_cs46xx_ids) = {
+	{ PCI_VDEVICE(CIRRUS, 0x6001), 0, },   /* CS4280 */
+	{ PCI_VDEVICE(CIRRUS, 0x6003), 0, },   /* CS4612 */
+	{ PCI_VDEVICE(CIRRUS, 0x6004), 0, },   /* CS4615 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_cs46xx_ids);
+
+static int __devinit snd_card_cs46xx_probe(struct pci_dev *pci,
+					   const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_cs46xx *chip;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_cs46xx_create(card, pci,
+				     external_amp[dev], thinkpad[dev],
+				     &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = chip;
+	chip->accept_valid = mmap_valid[dev];
+	if ((err = snd_cs46xx_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	if ((err = snd_cs46xx_pcm_rear(chip,1, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs46xx_pcm_iec958(chip,2,NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+#endif
+	if ((err = snd_cs46xx_mixer(chip, 2)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	if (chip->nr_ac97_codecs ==2) {
+		if ((err = snd_cs46xx_pcm_center_lfe(chip,3,NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+#endif
+	if ((err = snd_cs46xx_midi(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_cs46xx_start_dsp(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+
+	snd_cs46xx_gameport(chip);
+
+	strcpy(card->driver, "CS46xx");
+	strcpy(card->shortname, "Sound Fusion CS46xx");
+	sprintf(card->longname, "%s at 0x%lx/0x%lx, irq %i",
+		card->shortname,
+		chip->ba0_addr,
+		chip->ba1_addr,
+		chip->irq);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_card_cs46xx_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "Sound Fusion CS46xx",
+	.id_table = snd_cs46xx_ids,
+	.probe = snd_card_cs46xx_probe,
+	.remove = __devexit_p(snd_card_cs46xx_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_cs46xx_suspend,
+	.resume = snd_cs46xx_resume,
+#endif
+};
+
+static int __init alsa_card_cs46xx_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_cs46xx_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_cs46xx_init)
+module_exit(alsa_card_cs46xx_exit)
diff --git a/linux/sound/pci/cs46xx/cs46xx_image.h b/linux/sound/pci/cs46xx/cs46xx_image.h
new file mode 100644
index 0000000..dc93f62
--- /dev/null
+++ b/linux/sound/pci/cs46xx/cs46xx_image.h
@@ -0,0 +1,3468 @@
+struct BA1struct {
+	struct {
+		unsigned long offset;
+		unsigned long size;
+	} memory[BA1_MEMORY_COUNT];
+	u32 map[BA1_DWORD_SIZE];
+};
+
+
+static struct BA1struct BA1Struct = {
+{{ 0x00000000, 0x00003000 },{ 0x00010000, 0x00003800 },{ 0x00020000, 0x00007000 }},
+{0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000163,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00200040,0x00008010,0x00000000,
+0x00000000,0x80000001,0x00000001,0x00060000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00900080,0x00000173,0x00000000,
+0x00000000,0x00000010,0x00800000,0x00900000,
+0xf2c0000f,0x00000200,0x00000000,0x00010600,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000163,0x330300c2,
+0x06000000,0x00000000,0x80008000,0x80008000,
+0x3fc0000f,0x00000301,0x00010400,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00b00000,0x00d0806d,0x330480c3,
+0x04800000,0x00000001,0x00800001,0x0000ffff,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x066a0600,0x06350070,0x0000929d,0x929d929d,
+0x00000000,0x0000735a,0x00000600,0x00000000,
+0x929d735a,0x8734abfe,0x00010000,0x735a735a,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x0000804f,0x000000c3,
+0x05000000,0x00a00010,0x00000000,0x80008000,
+0x00000000,0x00000000,0x00000700,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000080,0x00a00000,0x0000809a,0x000000c2,
+0x07400000,0x00000000,0x80008000,0xffffffff,
+0x00c80028,0x00005555,0x00000000,0x000107a0,
+0x00c80028,0x000000c2,0x06800000,0x00000000,
+0x06e00080,0x00300000,0x000080bb,0x000000c9,
+0x07a00000,0x04000000,0x80008000,0xffffffff,
+0x00c80028,0x00005555,0x00000000,0x00000780,
+0x00c80028,0x000000c5,0xff800000,0x00000000,
+0x00640080,0x00c00000,0x00008197,0x000000c9,
+0x07800000,0x04000000,0x80008000,0xffffffff,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x0000805e,0x000000c1,
+0x00000000,0x00800000,0x80008000,0x80008000,
+0x00020000,0x0000ffff,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x929d0600,0x929d929d,0x929d929d,0x929d0000,
+0x929d929d,0x929d929d,0x929d929d,0x929d929d,
+0x929d929d,0x00100635,0x060b013f,0x00000004,
+0x00000001,0x007a0002,0x00000000,0x066e0610,
+0x0105929d,0x929d929d,0x929d929d,0x929d929d,
+0x929d929d,0xa431ac75,0x0001735a,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0x735a0051,
+0x00000000,0x929d929d,0x929d929d,0x929d929d,
+0x929d929d,0x929d929d,0x929d929d,0x929d929d,
+0x929d929d,0x929d929d,0x00000000,0x06400136,
+0x0000270f,0x00010000,0x007a0000,0x00000000,
+0x068e0645,0x0105929d,0x929d929d,0x929d929d,
+0x929d929d,0x929d929d,0xa431ac75,0x0001735a,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0xa431ac75,0xa431ac75,0xa431ac75,0xa431ac75,
+0x735a0100,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00010004,
+0x00040730,0x00001002,0x000f619e,0x00001003,
+0x00001705,0x00001400,0x000a411e,0x00001003,
+0x00040730,0x00001002,0x000f619e,0x00001003,
+0x00009705,0x00001400,0x000a411e,0x00001003,
+0x00040730,0x00001002,0x000f619e,0x00001003,
+0x00011705,0x00001400,0x000a411e,0x00001003,
+0x00040730,0x00001002,0x000f619e,0x00001003,
+0x00019705,0x00001400,0x000a411e,0x00001003,
+0x00040730,0x00001002,0x000f619e,0x00001003,
+0x00021705,0x00001400,0x000a411e,0x00001003,
+0x00040730,0x00001002,0x000f619e,0x00001003,
+0x00029705,0x00001400,0x000a411e,0x00001003,
+0x00040730,0x00001002,0x000f619e,0x00001003,
+0x00031705,0x00001400,0x000a411e,0x00001003,
+0x00040730,0x00001002,0x000f619e,0x00001003,
+0x00039705,0x00001400,0x000a411e,0x00001003,
+0x000fe19e,0x00001003,0x0009c730,0x00001003,
+0x0008e19c,0x00001003,0x000083c1,0x00093040,
+0x00098730,0x00001002,0x000ee19e,0x00001003,
+0x00009705,0x00001400,0x000a211e,0x00001003,
+0x00098730,0x00001002,0x000ee19e,0x00001003,
+0x00011705,0x00001400,0x000a211e,0x00001003,
+0x00098730,0x00001002,0x000ee19e,0x00001003,
+0x00019705,0x00001400,0x000a211e,0x00001003,
+0x00098730,0x00001002,0x000ee19e,0x00001003,
+0x00021705,0x00001400,0x000a211e,0x00001003,
+0x00098730,0x00001002,0x000ee19e,0x00001003,
+0x00029705,0x00001400,0x000a211e,0x00001003,
+0x00098730,0x00001002,0x000ee19e,0x00001003,
+0x00031705,0x00001400,0x000a211e,0x00001003,
+0x00098730,0x00001002,0x000ee19e,0x00001003,
+0x00039705,0x00001400,0x000a211e,0x00001003,
+0x0000a730,0x00001008,0x000e2730,0x00001002,
+0x0000a731,0x00001002,0x0000a731,0x00001002,
+0x0000a731,0x00001002,0x0000a731,0x00001002,
+0x0000a731,0x00001002,0x0000a731,0x00001002,
+0x00000000,0x00000000,0x000f619c,0x00001003,
+0x0007f801,0x000c0000,0x00000037,0x00001000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x000c0000,0x00000000,0x00000000,
+0x0000373c,0x00001000,0x00000000,0x00000000,
+0x000ee19c,0x00001003,0x0007f801,0x000c0000,
+0x00000037,0x00001000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x0000273c,0x00001000,
+0x00000033,0x00001000,0x000e679e,0x00001003,
+0x00007705,0x00001400,0x000ac71e,0x00001003,
+0x00087fc1,0x000c3be0,0x0007f801,0x000c0000,
+0x00000037,0x00001000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x0000a730,0x00001003,
+0x00000033,0x00001000,0x0007f801,0x000c0000,
+0x00000037,0x00001000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x000c0000,
+0x00000032,0x00001000,0x0000273d,0x00001000,
+0x0004a730,0x00001003,0x00000f41,0x00097140,
+0x0000a841,0x0009b240,0x0000a0c1,0x0009f040,
+0x0001c641,0x00093540,0x0001cec1,0x0009b5c0,
+0x00000000,0x00000000,0x0001bf05,0x0003fc40,
+0x00002725,0x000aa400,0x00013705,0x00093a00,
+0x0000002e,0x0009d6c0,0x00038630,0x00001004,
+0x0004ef0a,0x000eb785,0x0003fc8a,0x00000000,
+0x00000000,0x000c70e0,0x0007d182,0x0002c640,
+0x00000630,0x00001004,0x000799b8,0x0002c6c0,
+0x00031705,0x00092240,0x00039f05,0x000932c0,
+0x0003520a,0x00000000,0x00040731,0x0000100b,
+0x00010705,0x000b20c0,0x00000000,0x000eba44,
+0x00032108,0x000c60c4,0x00065208,0x000c2917,
+0x000406b0,0x00001007,0x00012f05,0x00036880,
+0x0002818e,0x000c0000,0x0004410a,0x00000000,
+0x00040630,0x00001007,0x00029705,0x000c0000,
+0x00000000,0x00000000,0x00003fc1,0x0003fc40,
+0x000037c1,0x00091b40,0x00003fc1,0x000911c0,
+0x000037c1,0x000957c0,0x00003fc1,0x000951c0,
+0x000037c1,0x00000000,0x00003fc1,0x000991c0,
+0x000037c1,0x00000000,0x00003fc1,0x0009d1c0,
+0x000037c1,0x00000000,0x0001ccc1,0x000915c0,
+0x0001c441,0x0009d800,0x0009cdc1,0x00091240,
+0x0001c541,0x00091d00,0x0009cfc1,0x00095240,
+0x0001c741,0x00095c80,0x000e8ca9,0x00099240,
+0x000e85ad,0x00095640,0x00069ca9,0x00099d80,
+0x000e952d,0x00099640,0x000eaca9,0x0009d6c0,
+0x000ea5ad,0x00091a40,0x0006bca9,0x0009de80,
+0x000eb52d,0x00095a40,0x000ecca9,0x00099ac0,
+0x000ec5ad,0x0009da40,0x000edca9,0x0009d300,
+0x000a6e0a,0x00001000,0x000ed52d,0x00091e40,
+0x000eeca9,0x00095ec0,0x000ee5ad,0x00099e40,
+0x0006fca9,0x00002500,0x000fb208,0x000c59a0,
+0x000ef52d,0x0009de40,0x00068ca9,0x000912c1,
+0x000683ad,0x00095241,0x00020f05,0x000991c1,
+0x00000000,0x00000000,0x00086f88,0x00001000,
+0x0009cf81,0x000b5340,0x0009c701,0x000b92c0,
+0x0009de81,0x000bd300,0x0009d601,0x000b1700,
+0x0001fd81,0x000b9d80,0x0009f501,0x000b57c0,
+0x000a0f81,0x000bd740,0x00020701,0x000b5c80,
+0x000a1681,0x000b97c0,0x00021601,0x00002500,
+0x000a0701,0x000b9b40,0x000a0f81,0x000b1bc0,
+0x00021681,0x00002d00,0x00020f81,0x000bd800,
+0x000a0701,0x000b5bc0,0x00021601,0x00003500,
+0x000a0f81,0x000b5f40,0x000a0701,0x000bdbc0,
+0x00021681,0x00003d00,0x00020f81,0x000b1d00,
+0x000a0701,0x000b1fc0,0x00021601,0x00020500,
+0x00020f81,0x000b1341,0x000a0701,0x000b9fc0,
+0x00021681,0x00020d00,0x00020f81,0x000bde80,
+0x000a0701,0x000bdfc0,0x00021601,0x00021500,
+0x00020f81,0x000b9341,0x00020701,0x000b53c1,
+0x00021681,0x00021d00,0x000a0f81,0x000d0380,
+0x0000b601,0x000b15c0,0x00007b01,0x00000000,
+0x00007b81,0x000bd1c0,0x00007b01,0x00000000,
+0x00007b81,0x000b91c0,0x00007b01,0x000b57c0,
+0x00007b81,0x000b51c0,0x00007b01,0x000b1b40,
+0x00007b81,0x000b11c0,0x00087b01,0x000c3dc0,
+0x0007e488,0x000d7e45,0x00000000,0x000d7a44,
+0x0007e48a,0x00000000,0x00011f05,0x00084080,
+0x00000000,0x00000000,0x00001705,0x000b3540,
+0x00008a01,0x000bf040,0x00007081,0x000bb5c0,
+0x00055488,0x00000000,0x0000d482,0x0003fc40,
+0x0003fc88,0x00000000,0x0001e401,0x000b3a00,
+0x0001ec81,0x000bd6c0,0x0004ef08,0x000eb784,
+0x000c86b0,0x00001007,0x00008281,0x000bb240,
+0x0000b801,0x000b7140,0x00007888,0x00000000,
+0x0000073c,0x00001000,0x0007f188,0x000c0000,
+0x00000000,0x00000000,0x00055288,0x000c555c,
+0x0005528a,0x000c0000,0x0009fa88,0x000c5d00,
+0x0000fa88,0x00000000,0x00000032,0x00001000,
+0x0000073d,0x00001000,0x0007f188,0x000c0000,
+0x00000000,0x00000000,0x0008c01c,0x00001003,
+0x00002705,0x00001008,0x0008b201,0x000c1392,
+0x0000ba01,0x00000000,0x00008731,0x00001400,
+0x0004c108,0x000fe0c4,0x00057488,0x00000000,
+0x000a6388,0x00001001,0x0008b334,0x000bc141,
+0x0003020e,0x00000000,0x000886b0,0x00001008,
+0x00003625,0x000c5dfa,0x000a638a,0x00001001,
+0x0008020e,0x00001002,0x0008a6b0,0x00001008,
+0x0007f301,0x00000000,0x00000000,0x00000000,
+0x00002725,0x000a8c40,0x000000ae,0x00000000,
+0x000d8630,0x00001008,0x00000000,0x000c74e0,
+0x0007d182,0x0002d640,0x000a8630,0x00001008,
+0x000799b8,0x0002d6c0,0x0000748a,0x000c3ec5,
+0x0007420a,0x000c0000,0x00062208,0x000c4117,
+0x00070630,0x00001009,0x00000000,0x000c0000,
+0x0001022e,0x00000000,0x0003a630,0x00001009,
+0x00000000,0x000c0000,0x00000036,0x00001000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x0002a730,0x00001008,0x0007f801,0x000c0000,
+0x00000037,0x00001000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x0002a730,0x00001008,
+0x00000033,0x00001000,0x0002a705,0x00001008,
+0x00007a01,0x000c0000,0x000e6288,0x000d550a,
+0x0006428a,0x00000000,0x00060730,0x0000100a,
+0x00000000,0x000c0000,0x00000000,0x00000000,
+0x0007aab0,0x00034880,0x00078fb0,0x0000100b,
+0x00057488,0x00000000,0x00033b94,0x00081140,
+0x000183ae,0x00000000,0x000786b0,0x0000100b,
+0x00022f05,0x000c3545,0x0000eb8a,0x00000000,
+0x00042731,0x00001003,0x0007aab0,0x00034880,
+0x00048fb0,0x0000100a,0x00057488,0x00000000,
+0x00033b94,0x00081140,0x000183ae,0x00000000,
+0x000806b0,0x0000100b,0x00022f05,0x00000000,
+0x00007401,0x00091140,0x00048f05,0x000951c0,
+0x00042731,0x00001003,0x0000473d,0x00001000,
+0x000f19b0,0x000bbc47,0x00080000,0x000bffc7,
+0x000fe19e,0x00001003,0x00000000,0x00000000,
+0x0008e19c,0x00001003,0x000083c1,0x00093040,
+0x00000f41,0x00097140,0x0000a841,0x0009b240,
+0x0000a0c1,0x0009f040,0x0001c641,0x00093540,
+0x0001cec1,0x0009b5c0,0x00000000,0x000fdc44,
+0x00055208,0x00000000,0x00010705,0x000a2880,
+0x0000a23a,0x00093a00,0x0003fc8a,0x000df6c5,
+0x0004ef0a,0x000c0000,0x00012f05,0x00036880,
+0x00065308,0x000c2997,0x000d86b0,0x0000100a,
+0x0004410a,0x000d40c7,0x00000000,0x00000000,
+0x00080730,0x00001004,0x00056f0a,0x000ea105,
+0x00000000,0x00000000,0x0000473d,0x00001000,
+0x000f19b0,0x000bbc47,0x00080000,0x000bffc7,
+0x0000273d,0x00001000,0x00000000,0x000eba44,
+0x00048f05,0x0000f440,0x00007401,0x0000f7c0,
+0x00000734,0x00001000,0x00010705,0x000a6880,
+0x00006a88,0x000c75c4,0x00000000,0x000e5084,
+0x00000000,0x000eba44,0x00087401,0x000e4782,
+0x00000734,0x00001000,0x00010705,0x000a6880,
+0x00006a88,0x000c75c4,0x0007c108,0x000c0000,
+0x0007e721,0x000bed40,0x00005f25,0x000badc0,
+0x0003ba97,0x000beb80,0x00065590,0x000b2e00,
+0x00033217,0x00003ec0,0x00065590,0x000b8e40,
+0x0003ed80,0x000491c0,0x00073fb0,0x00074c80,
+0x000283a0,0x0000100c,0x000ee388,0x00042970,
+0x00008301,0x00021ef2,0x000b8f14,0x0000000f,
+0x000c4d8d,0x0000001b,0x000d6dc2,0x000e06c6,
+0x000032ac,0x000c3916,0x0004edc2,0x00074c80,
+0x00078898,0x00001000,0x00038894,0x00000032,
+0x000c4d8d,0x00092e1b,0x000d6dc2,0x000e06c6,
+0x0004edc2,0x000c1956,0x0000722c,0x00034a00,
+0x00041705,0x0009ed40,0x00058730,0x00001400,
+0x000d7488,0x000c3a00,0x00048f05,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000,
+0x00000000,0x00000000,0x00000000,0x00000000}
+ };
diff --git a/linux/sound/pci/cs46xx/cs46xx_lib.c b/linux/sound/pci/cs46xx/cs46xx_lib.c
new file mode 100644
index 0000000..aad3708
--- /dev/null
+++ b/linux/sound/pci/cs46xx/cs46xx_lib.c
@@ -0,0 +1,3881 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *                   Abramo Bagnara <abramo@alsa-project.org>
+ *                   Cirrus Logic, Inc.
+ *  Routines for control of Cirrus Logic CS461x chips
+ *
+ *  KNOWN BUGS:
+ *    - Sometimes the SPDIF input DSP tasks get's unsynchronized
+ *      and the SPDIF get somewhat "distorcionated", or/and left right channel
+ *      are swapped. To get around this problem when it happens, mute and unmute 
+ *      the SPDIF input mixer control.
+ *    - On the Hercules Game Theater XP the amplifier are sometimes turned
+ *      off on inadecuate moments which causes distorcions on sound.
+ *
+ *  TODO:
+ *    - Secondary CODEC on some soundcards
+ *    - SPDIF input support for other sample rates then 48khz
+ *    - Posibility to mix the SPDIF output with analog sources.
+ *    - PCM channels for Center and LFE on secondary codec
+ *
+ *  NOTE: with CONFIG_SND_CS46XX_NEW_DSP unset uses old DSP image (which
+ *        is default configuration), no SPDIF, no secondary codec, no
+ *        multi channel PCM.  But known to work.
+ *
+ *  FINALLY: A credit to the developers Tom and Jordan 
+ *           at Cirrus for have helping me out with the DSP, however we
+ *           still don't have sufficient documentation and technical
+ *           references to be able to implement all fancy feutures
+ *           supported by the cs46xx DSP's. 
+ *           Benny <benny@hostmobility.com>
+ *                
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/mutex.h>
+
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/cs46xx.h>
+
+#include <asm/io.h>
+
+#include "cs46xx_lib.h"
+#include "dsp_spos.h"
+
+static void amp_voyetra(struct snd_cs46xx *chip, int change);
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+static struct snd_pcm_ops snd_cs46xx_playback_rear_ops;
+static struct snd_pcm_ops snd_cs46xx_playback_indirect_rear_ops;
+static struct snd_pcm_ops snd_cs46xx_playback_clfe_ops;
+static struct snd_pcm_ops snd_cs46xx_playback_indirect_clfe_ops;
+static struct snd_pcm_ops snd_cs46xx_playback_iec958_ops;
+static struct snd_pcm_ops snd_cs46xx_playback_indirect_iec958_ops;
+#endif
+
+static struct snd_pcm_ops snd_cs46xx_playback_ops;
+static struct snd_pcm_ops snd_cs46xx_playback_indirect_ops;
+static struct snd_pcm_ops snd_cs46xx_capture_ops;
+static struct snd_pcm_ops snd_cs46xx_capture_indirect_ops;
+
+static unsigned short snd_cs46xx_codec_read(struct snd_cs46xx *chip,
+					    unsigned short reg,
+					    int codec_index)
+{
+	int count;
+	unsigned short result,tmp;
+	u32 offset = 0;
+
+	if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX &&
+		       codec_index != CS46XX_SECONDARY_CODEC_INDEX))
+		return -EINVAL;
+
+	chip->active_ctrl(chip, 1);
+
+	if (codec_index == CS46XX_SECONDARY_CODEC_INDEX)
+		offset = CS46XX_SECONDARY_CODEC_OFFSET;
+
+	/*
+	 *  1. Write ACCAD = Command Address Register = 46Ch for AC97 register address
+	 *  2. Write ACCDA = Command Data Register = 470h    for data to write to AC97 
+	 *  3. Write ACCTL = Control Register = 460h for initiating the write7---55
+	 *  4. Read ACCTL = 460h, DCV should be reset by now and 460h = 17h
+	 *  5. if DCV not cleared, break and return error
+	 *  6. Read ACSTS = Status Register = 464h, check VSTS bit
+	 */
+
+	snd_cs46xx_peekBA0(chip, BA0_ACSDA + offset);
+
+	tmp = snd_cs46xx_peekBA0(chip, BA0_ACCTL);
+	if ((tmp & ACCTL_VFRM) == 0) {
+		snd_printk(KERN_WARNING  "cs46xx: ACCTL_VFRM not set 0x%x\n",tmp);
+		snd_cs46xx_pokeBA0(chip, BA0_ACCTL, (tmp & (~ACCTL_ESYN)) | ACCTL_VFRM );
+		msleep(50);
+		tmp = snd_cs46xx_peekBA0(chip, BA0_ACCTL + offset);
+		snd_cs46xx_pokeBA0(chip, BA0_ACCTL, tmp | ACCTL_ESYN | ACCTL_VFRM );
+
+	}
+
+	/*
+	 *  Setup the AC97 control registers on the CS461x to send the
+	 *  appropriate command to the AC97 to perform the read.
+	 *  ACCAD = Command Address Register = 46Ch
+	 *  ACCDA = Command Data Register = 470h
+	 *  ACCTL = Control Register = 460h
+	 *  set DCV - will clear when process completed
+	 *  set CRW - Read command
+	 *  set VFRM - valid frame enabled
+	 *  set ESYN - ASYNC generation enabled
+	 *  set RSTN - ARST# inactive, AC97 codec not reset
+	 */
+
+	snd_cs46xx_pokeBA0(chip, BA0_ACCAD, reg);
+	snd_cs46xx_pokeBA0(chip, BA0_ACCDA, 0);
+	if (codec_index == CS46XX_PRIMARY_CODEC_INDEX) {
+		snd_cs46xx_pokeBA0(chip, BA0_ACCTL,/* clear ACCTL_DCV */ ACCTL_CRW | 
+				   ACCTL_VFRM | ACCTL_ESYN |
+				   ACCTL_RSTN);
+		snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_CRW |
+				   ACCTL_VFRM | ACCTL_ESYN |
+				   ACCTL_RSTN);
+	} else {
+		snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_TC |
+				   ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN |
+				   ACCTL_RSTN);
+	}
+
+	/*
+	 *  Wait for the read to occur.
+	 */
+	for (count = 0; count < 1000; count++) {
+		/*
+		 *  First, we want to wait for a short time.
+	 	 */
+		udelay(10);
+		/*
+		 *  Now, check to see if the read has completed.
+		 *  ACCTL = 460h, DCV should be reset by now and 460h = 17h
+		 */
+		if (!(snd_cs46xx_peekBA0(chip, BA0_ACCTL) & ACCTL_DCV))
+			goto ok1;
+	}
+
+	snd_printk(KERN_ERR "AC'97 read problem (ACCTL_DCV), reg = 0x%x\n", reg);
+	result = 0xffff;
+	goto end;
+	
+ ok1:
+	/*
+	 *  Wait for the valid status bit to go active.
+	 */
+	for (count = 0; count < 100; count++) {
+		/*
+		 *  Read the AC97 status register.
+		 *  ACSTS = Status Register = 464h
+		 *  VSTS - Valid Status
+		 */
+		if (snd_cs46xx_peekBA0(chip, BA0_ACSTS + offset) & ACSTS_VSTS)
+			goto ok2;
+		udelay(10);
+	}
+	
+	snd_printk(KERN_ERR "AC'97 read problem (ACSTS_VSTS), codec_index %d, reg = 0x%x\n", codec_index, reg);
+	result = 0xffff;
+	goto end;
+
+ ok2:
+	/*
+	 *  Read the data returned from the AC97 register.
+	 *  ACSDA = Status Data Register = 474h
+	 */
+#if 0
+	printk(KERN_DEBUG "e) reg = 0x%x, val = 0x%x, BA0_ACCAD = 0x%x\n", reg,
+			snd_cs46xx_peekBA0(chip, BA0_ACSDA),
+			snd_cs46xx_peekBA0(chip, BA0_ACCAD));
+#endif
+
+	//snd_cs46xx_peekBA0(chip, BA0_ACCAD);
+	result = snd_cs46xx_peekBA0(chip, BA0_ACSDA + offset);
+ end:
+	chip->active_ctrl(chip, -1);
+	return result;
+}
+
+static unsigned short snd_cs46xx_ac97_read(struct snd_ac97 * ac97,
+					    unsigned short reg)
+{
+	struct snd_cs46xx *chip = ac97->private_data;
+	unsigned short val;
+	int codec_index = ac97->num;
+
+	if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX &&
+		       codec_index != CS46XX_SECONDARY_CODEC_INDEX))
+		return 0xffff;
+
+	val = snd_cs46xx_codec_read(chip, reg, codec_index);
+
+	return val;
+}
+
+
+static void snd_cs46xx_codec_write(struct snd_cs46xx *chip,
+				   unsigned short reg,
+				   unsigned short val,
+				   int codec_index)
+{
+	int count;
+
+	if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX &&
+		       codec_index != CS46XX_SECONDARY_CODEC_INDEX))
+		return;
+
+	chip->active_ctrl(chip, 1);
+
+	/*
+	 *  1. Write ACCAD = Command Address Register = 46Ch for AC97 register address
+	 *  2. Write ACCDA = Command Data Register = 470h    for data to write to AC97
+	 *  3. Write ACCTL = Control Register = 460h for initiating the write
+	 *  4. Read ACCTL = 460h, DCV should be reset by now and 460h = 07h
+	 *  5. if DCV not cleared, break and return error
+	 */
+
+	/*
+	 *  Setup the AC97 control registers on the CS461x to send the
+	 *  appropriate command to the AC97 to perform the read.
+	 *  ACCAD = Command Address Register = 46Ch
+	 *  ACCDA = Command Data Register = 470h
+	 *  ACCTL = Control Register = 460h
+	 *  set DCV - will clear when process completed
+	 *  reset CRW - Write command
+	 *  set VFRM - valid frame enabled
+	 *  set ESYN - ASYNC generation enabled
+	 *  set RSTN - ARST# inactive, AC97 codec not reset
+         */
+	snd_cs46xx_pokeBA0(chip, BA0_ACCAD , reg);
+	snd_cs46xx_pokeBA0(chip, BA0_ACCDA , val);
+	snd_cs46xx_peekBA0(chip, BA0_ACCTL);
+
+	if (codec_index == CS46XX_PRIMARY_CODEC_INDEX) {
+		snd_cs46xx_pokeBA0(chip, BA0_ACCTL, /* clear ACCTL_DCV */ ACCTL_VFRM |
+				   ACCTL_ESYN | ACCTL_RSTN);
+		snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM |
+				   ACCTL_ESYN | ACCTL_RSTN);
+	} else {
+		snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_TC |
+				   ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN);
+	}
+
+	for (count = 0; count < 4000; count++) {
+		/*
+		 *  First, we want to wait for a short time.
+		 */
+		udelay(10);
+		/*
+		 *  Now, check to see if the write has completed.
+		 *  ACCTL = 460h, DCV should be reset by now and 460h = 07h
+		 */
+		if (!(snd_cs46xx_peekBA0(chip, BA0_ACCTL) & ACCTL_DCV)) {
+			goto end;
+		}
+	}
+	snd_printk(KERN_ERR "AC'97 write problem, codec_index = %d, reg = 0x%x, val = 0x%x\n", codec_index, reg, val);
+ end:
+	chip->active_ctrl(chip, -1);
+}
+
+static void snd_cs46xx_ac97_write(struct snd_ac97 *ac97,
+				   unsigned short reg,
+				   unsigned short val)
+{
+	struct snd_cs46xx *chip = ac97->private_data;
+	int codec_index = ac97->num;
+
+	if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX &&
+		       codec_index != CS46XX_SECONDARY_CODEC_INDEX))
+		return;
+
+	snd_cs46xx_codec_write(chip, reg, val, codec_index);
+}
+
+
+/*
+ *  Chip initialization
+ */
+
+int snd_cs46xx_download(struct snd_cs46xx *chip,
+			u32 *src,
+                        unsigned long offset,
+                        unsigned long len)
+{
+	void __iomem *dst;
+	unsigned int bank = offset >> 16;
+	offset = offset & 0xffff;
+
+	if (snd_BUG_ON((offset & 3) || (len & 3)))
+		return -EINVAL;
+	dst = chip->region.idx[bank+1].remap_addr + offset;
+	len /= sizeof(u32);
+
+	/* writel already converts 32-bit value to right endianess */
+	while (len-- > 0) {
+		writel(*src++, dst);
+		dst += sizeof(u32);
+	}
+	return 0;
+}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+
+#include "imgs/cwc4630.h"
+#include "imgs/cwcasync.h"
+#include "imgs/cwcsnoop.h"
+#include "imgs/cwcbinhack.h"
+#include "imgs/cwcdma.h"
+
+int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip,
+                         unsigned long offset,
+                         unsigned long len) 
+{
+	void __iomem *dst;
+	unsigned int bank = offset >> 16;
+	offset = offset & 0xffff;
+
+	if (snd_BUG_ON((offset & 3) || (len & 3)))
+		return -EINVAL;
+	dst = chip->region.idx[bank+1].remap_addr + offset;
+	len /= sizeof(u32);
+
+	/* writel already converts 32-bit value to right endianess */
+	while (len-- > 0) {
+		writel(0, dst);
+		dst += sizeof(u32);
+	}
+	return 0;
+}
+
+#else /* old DSP image */
+
+#include "cs46xx_image.h"
+
+int snd_cs46xx_download_image(struct snd_cs46xx *chip)
+{
+	int idx, err;
+	unsigned long offset = 0;
+
+	for (idx = 0; idx < BA1_MEMORY_COUNT; idx++) {
+		if ((err = snd_cs46xx_download(chip,
+					       &BA1Struct.map[offset],
+					       BA1Struct.memory[idx].offset,
+					       BA1Struct.memory[idx].size)) < 0)
+			return err;
+		offset += BA1Struct.memory[idx].size >> 2;
+	}	
+	return 0;
+}
+#endif /* CONFIG_SND_CS46XX_NEW_DSP */
+
+/*
+ *  Chip reset
+ */
+
+static void snd_cs46xx_reset(struct snd_cs46xx *chip)
+{
+	int idx;
+
+	/*
+	 *  Write the reset bit of the SP control register.
+	 */
+	snd_cs46xx_poke(chip, BA1_SPCR, SPCR_RSTSP);
+
+	/*
+	 *  Write the control register.
+	 */
+	snd_cs46xx_poke(chip, BA1_SPCR, SPCR_DRQEN);
+
+	/*
+	 *  Clear the trap registers.
+	 */
+	for (idx = 0; idx < 8; idx++) {
+		snd_cs46xx_poke(chip, BA1_DREG, DREG_REGID_TRAP_SELECT + idx);
+		snd_cs46xx_poke(chip, BA1_TWPR, 0xFFFF);
+	}
+	snd_cs46xx_poke(chip, BA1_DREG, 0);
+
+	/*
+	 *  Set the frame timer to reflect the number of cycles per frame.
+	 */
+	snd_cs46xx_poke(chip, BA1_FRMT, 0xadf);
+}
+
+static int cs46xx_wait_for_fifo(struct snd_cs46xx * chip,int retry_timeout) 
+{
+	u32 i, status = 0;
+	/*
+	 * Make sure the previous FIFO write operation has completed.
+	 */
+	for(i = 0; i < 50; i++){
+		status = snd_cs46xx_peekBA0(chip, BA0_SERBST);
+    
+		if( !(status & SERBST_WBSY) )
+			break;
+
+		mdelay(retry_timeout);
+	}
+  
+	if(status & SERBST_WBSY) {
+		snd_printk(KERN_ERR "cs46xx: failure waiting for "
+			   "FIFO command to complete\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void snd_cs46xx_clear_serial_FIFOs(struct snd_cs46xx *chip)
+{
+	int idx, powerdown = 0;
+	unsigned int tmp;
+
+	/*
+	 *  See if the devices are powered down.  If so, we must power them up first
+	 *  or they will not respond.
+	 */
+	tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1);
+	if (!(tmp & CLKCR1_SWCE)) {
+		snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp | CLKCR1_SWCE);
+		powerdown = 1;
+	}
+
+	/*
+	 *  We want to clear out the serial port FIFOs so we don't end up playing
+	 *  whatever random garbage happens to be in them.  We fill the sample FIFOS
+	 *  with zero (silence).
+	 */
+	snd_cs46xx_pokeBA0(chip, BA0_SERBWP, 0);
+
+	/*
+	 *  Fill all 256 sample FIFO locations.
+	 */
+	for (idx = 0; idx < 0xFF; idx++) {
+		/*
+		 *  Make sure the previous FIFO write operation has completed.
+		 */
+		if (cs46xx_wait_for_fifo(chip,1)) {
+			snd_printdd ("failed waiting for FIFO at addr (%02X)\n",idx);
+
+			if (powerdown)
+				snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp);
+          
+			break;
+		}
+		/*
+		 *  Write the serial port FIFO index.
+		 */
+		snd_cs46xx_pokeBA0(chip, BA0_SERBAD, idx);
+		/*
+		 *  Tell the serial port to load the new value into the FIFO location.
+		 */
+		snd_cs46xx_pokeBA0(chip, BA0_SERBCM, SERBCM_WRC);
+	}
+	/*
+	 *  Now, if we powered up the devices, then power them back down again.
+	 *  This is kinda ugly, but should never happen.
+	 */
+	if (powerdown)
+		snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp);
+}
+
+static void snd_cs46xx_proc_start(struct snd_cs46xx *chip)
+{
+	int cnt;
+
+	/*
+	 *  Set the frame timer to reflect the number of cycles per frame.
+	 */
+	snd_cs46xx_poke(chip, BA1_FRMT, 0xadf);
+	/*
+	 *  Turn on the run, run at frame, and DMA enable bits in the local copy of
+	 *  the SP control register.
+	 */
+	snd_cs46xx_poke(chip, BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN);
+	/*
+	 *  Wait until the run at frame bit resets itself in the SP control
+	 *  register.
+	 */
+	for (cnt = 0; cnt < 25; cnt++) {
+		udelay(50);
+		if (!(snd_cs46xx_peek(chip, BA1_SPCR) & SPCR_RUNFR))
+			break;
+	}
+
+	if (snd_cs46xx_peek(chip, BA1_SPCR) & SPCR_RUNFR)
+		snd_printk(KERN_ERR "SPCR_RUNFR never reset\n");
+}
+
+static void snd_cs46xx_proc_stop(struct snd_cs46xx *chip)
+{
+	/*
+	 *  Turn off the run, run at frame, and DMA enable bits in the local copy of
+	 *  the SP control register.
+	 */
+	snd_cs46xx_poke(chip, BA1_SPCR, 0);
+}
+
+/*
+ *  Sample rate routines
+ */
+
+#define GOF_PER_SEC 200
+
+static void snd_cs46xx_set_play_sample_rate(struct snd_cs46xx *chip, unsigned int rate)
+{
+	unsigned long flags;
+	unsigned int tmp1, tmp2;
+	unsigned int phiIncr;
+	unsigned int correctionPerGOF, correctionPerSec;
+
+	/*
+	 *  Compute the values used to drive the actual sample rate conversion.
+	 *  The following formulas are being computed, using inline assembly
+	 *  since we need to use 64 bit arithmetic to compute the values:
+	 *
+	 *  phiIncr = floor((Fs,in * 2^26) / Fs,out)
+	 *  correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) /
+         *                                   GOF_PER_SEC)
+         *  ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr -M
+         *                       GOF_PER_SEC * correctionPerGOF
+	 *
+	 *  i.e.
+	 *
+	 *  phiIncr:other = dividend:remainder((Fs,in * 2^26) / Fs,out)
+	 *  correctionPerGOF:correctionPerSec =
+	 *      dividend:remainder(ulOther / GOF_PER_SEC)
+	 */
+	tmp1 = rate << 16;
+	phiIncr = tmp1 / 48000;
+	tmp1 -= phiIncr * 48000;
+	tmp1 <<= 10;
+	phiIncr <<= 10;
+	tmp2 = tmp1 / 48000;
+	phiIncr += tmp2;
+	tmp1 -= tmp2 * 48000;
+	correctionPerGOF = tmp1 / GOF_PER_SEC;
+	tmp1 -= correctionPerGOF * GOF_PER_SEC;
+	correctionPerSec = tmp1;
+
+	/*
+	 *  Fill in the SampleRateConverter control block.
+	 */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_cs46xx_poke(chip, BA1_PSRC,
+	  ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF));
+	snd_cs46xx_poke(chip, BA1_PPI, phiIncr);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs46xx_set_capture_sample_rate(struct snd_cs46xx *chip, unsigned int rate)
+{
+	unsigned long flags;
+	unsigned int phiIncr, coeffIncr, tmp1, tmp2;
+	unsigned int correctionPerGOF, correctionPerSec, initialDelay;
+	unsigned int frameGroupLength, cnt;
+
+	/*
+	 *  We can only decimate by up to a factor of 1/9th the hardware rate.
+	 *  Correct the value if an attempt is made to stray outside that limit.
+	 */
+	if ((rate * 9) < 48000)
+		rate = 48000 / 9;
+
+	/*
+	 *  We can not capture at at rate greater than the Input Rate (48000).
+	 *  Return an error if an attempt is made to stray outside that limit.
+	 */
+	if (rate > 48000)
+		rate = 48000;
+
+	/*
+	 *  Compute the values used to drive the actual sample rate conversion.
+	 *  The following formulas are being computed, using inline assembly
+	 *  since we need to use 64 bit arithmetic to compute the values:
+	 *
+	 *     coeffIncr = -floor((Fs,out * 2^23) / Fs,in)
+	 *     phiIncr = floor((Fs,in * 2^26) / Fs,out)
+	 *     correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) /
+	 *                                GOF_PER_SEC)
+	 *     correctionPerSec = Fs,in * 2^26 - Fs,out * phiIncr -
+	 *                          GOF_PER_SEC * correctionPerGOF
+	 *     initialDelay = ceil((24 * Fs,in) / Fs,out)
+	 *
+	 * i.e.
+	 *
+	 *     coeffIncr = neg(dividend((Fs,out * 2^23) / Fs,in))
+	 *     phiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out)
+	 *     correctionPerGOF:correctionPerSec =
+	 * 	    dividend:remainder(ulOther / GOF_PER_SEC)
+	 *     initialDelay = dividend(((24 * Fs,in) + Fs,out - 1) / Fs,out)
+	 */
+
+	tmp1 = rate << 16;
+	coeffIncr = tmp1 / 48000;
+	tmp1 -= coeffIncr * 48000;
+	tmp1 <<= 7;
+	coeffIncr <<= 7;
+	coeffIncr += tmp1 / 48000;
+	coeffIncr ^= 0xFFFFFFFF;
+	coeffIncr++;
+	tmp1 = 48000 << 16;
+	phiIncr = tmp1 / rate;
+	tmp1 -= phiIncr * rate;
+	tmp1 <<= 10;
+	phiIncr <<= 10;
+	tmp2 = tmp1 / rate;
+	phiIncr += tmp2;
+	tmp1 -= tmp2 * rate;
+	correctionPerGOF = tmp1 / GOF_PER_SEC;
+	tmp1 -= correctionPerGOF * GOF_PER_SEC;
+	correctionPerSec = tmp1;
+	initialDelay = ((48000 * 24) + rate - 1) / rate;
+
+	/*
+	 *  Fill in the VariDecimate control block.
+	 */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_cs46xx_poke(chip, BA1_CSRC,
+		((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF));
+	snd_cs46xx_poke(chip, BA1_CCI, coeffIncr);
+	snd_cs46xx_poke(chip, BA1_CD,
+		(((BA1_VARIDEC_BUF_1 + (initialDelay << 2)) << 16) & 0xFFFF0000) | 0x80);
+	snd_cs46xx_poke(chip, BA1_CPI, phiIncr);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	/*
+	 *  Figure out the frame group length for the write back task.  Basically,
+	 *  this is just the factors of 24000 (2^6*3*5^3) that are not present in
+	 *  the output sample rate.
+	 */
+	frameGroupLength = 1;
+	for (cnt = 2; cnt <= 64; cnt *= 2) {
+		if (((rate / cnt) * cnt) != rate)
+			frameGroupLength *= 2;
+	}
+	if (((rate / 3) * 3) != rate) {
+		frameGroupLength *= 3;
+	}
+	for (cnt = 5; cnt <= 125; cnt *= 5) {
+		if (((rate / cnt) * cnt) != rate) 
+			frameGroupLength *= 5;
+        }
+
+	/*
+	 * Fill in the WriteBack control block.
+	 */
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_cs46xx_poke(chip, BA1_CFG1, frameGroupLength);
+	snd_cs46xx_poke(chip, BA1_CFG2, (0x00800000 | frameGroupLength));
+	snd_cs46xx_poke(chip, BA1_CCST, 0x0000FFFF);
+	snd_cs46xx_poke(chip, BA1_CSPB, ((65536 * rate) / 24000));
+	snd_cs46xx_poke(chip, (BA1_CSPB + 4), 0x0000FFFF);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/*
+ *  PCM part
+ */
+
+static void snd_cs46xx_pb_trans_copy(struct snd_pcm_substream *substream,
+				     struct snd_pcm_indirect *rec, size_t bytes)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_cs46xx_pcm * cpcm = runtime->private_data;
+	memcpy(cpcm->hw_buf.area + rec->hw_data, runtime->dma_area + rec->sw_data, bytes);
+}
+
+static int snd_cs46xx_playback_transfer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_cs46xx_pcm * cpcm = runtime->private_data;
+	snd_pcm_indirect_playback_transfer(substream, &cpcm->pcm_rec, snd_cs46xx_pb_trans_copy);
+	return 0;
+}
+
+static void snd_cs46xx_cp_trans_copy(struct snd_pcm_substream *substream,
+				     struct snd_pcm_indirect *rec, size_t bytes)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	memcpy(runtime->dma_area + rec->sw_data,
+	       chip->capt.hw_buf.area + rec->hw_data, bytes);
+}
+
+static int snd_cs46xx_capture_transfer(struct snd_pcm_substream *substream)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_indirect_capture_transfer(substream, &chip->capt.pcm_rec, snd_cs46xx_cp_trans_copy);
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_cs46xx_playback_direct_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	struct snd_cs46xx_pcm *cpcm = substream->runtime->private_data;
+
+	if (snd_BUG_ON(!cpcm->pcm_channel))
+		return -ENXIO;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	ptr = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 2) << 2);
+#else
+	ptr = snd_cs46xx_peek(chip, BA1_PBA);
+#endif
+	ptr -= cpcm->hw_buf.addr;
+	return ptr >> cpcm->shift;
+}
+
+static snd_pcm_uframes_t snd_cs46xx_playback_indirect_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	struct snd_cs46xx_pcm *cpcm = substream->runtime->private_data;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	if (snd_BUG_ON(!cpcm->pcm_channel))
+		return -ENXIO;
+	ptr = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 2) << 2);
+#else
+	ptr = snd_cs46xx_peek(chip, BA1_PBA);
+#endif
+	ptr -= cpcm->hw_buf.addr;
+	return snd_pcm_indirect_playback_pointer(substream, &cpcm->pcm_rec, ptr);
+}
+
+static snd_pcm_uframes_t snd_cs46xx_capture_direct_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	size_t ptr = snd_cs46xx_peek(chip, BA1_CBA) - chip->capt.hw_buf.addr;
+	return ptr >> chip->capt.shift;
+}
+
+static snd_pcm_uframes_t snd_cs46xx_capture_indirect_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	size_t ptr = snd_cs46xx_peek(chip, BA1_CBA) - chip->capt.hw_buf.addr;
+	return snd_pcm_indirect_capture_pointer(substream, &chip->capt.pcm_rec, ptr);
+}
+
+static int snd_cs46xx_playback_trigger(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	/*struct snd_pcm_runtime *runtime = substream->runtime;*/
+	int result = 0;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	struct snd_cs46xx_pcm *cpcm = substream->runtime->private_data;
+	if (! cpcm->pcm_channel) {
+		return -ENXIO;
+	}
+#endif
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+		/* magic value to unmute PCM stream  playback volume */
+		snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 
+				       SCBVolumeCtrl) << 2, 0x80008000);
+
+		if (cpcm->pcm_channel->unlinked)
+			cs46xx_dsp_pcm_link(chip,cpcm->pcm_channel);
+
+		if (substream->runtime->periods != CS46XX_FRAGS)
+			snd_cs46xx_playback_transfer(substream);
+#else
+		spin_lock(&chip->reg_lock);
+		if (substream->runtime->periods != CS46XX_FRAGS)
+			snd_cs46xx_playback_transfer(substream);
+		{ unsigned int tmp;
+		tmp = snd_cs46xx_peek(chip, BA1_PCTL);
+		tmp &= 0x0000ffff;
+		snd_cs46xx_poke(chip, BA1_PCTL, chip->play_ctl | tmp);
+		}
+		spin_unlock(&chip->reg_lock);
+#endif
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+		/* magic mute channel */
+		snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 
+				       SCBVolumeCtrl) << 2, 0xffffffff);
+
+		if (!cpcm->pcm_channel->unlinked)
+			cs46xx_dsp_pcm_unlink(chip,cpcm->pcm_channel);
+#else
+		spin_lock(&chip->reg_lock);
+		{ unsigned int tmp;
+		tmp = snd_cs46xx_peek(chip, BA1_PCTL);
+		tmp &= 0x0000ffff;
+		snd_cs46xx_poke(chip, BA1_PCTL, tmp);
+		}
+		spin_unlock(&chip->reg_lock);
+#endif
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+
+	return result;
+}
+
+static int snd_cs46xx_capture_trigger(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	unsigned int tmp;
+	int result = 0;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		tmp = snd_cs46xx_peek(chip, BA1_CCTL);
+		tmp &= 0xffff0000;
+		snd_cs46xx_poke(chip, BA1_CCTL, chip->capt.ctl | tmp);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		tmp = snd_cs46xx_peek(chip, BA1_CCTL);
+		tmp &= 0xffff0000;
+		snd_cs46xx_poke(chip, BA1_CCTL, tmp);
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	spin_unlock(&chip->reg_lock);
+
+	return result;
+}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+static int _cs46xx_adjust_sample_rate (struct snd_cs46xx *chip, struct snd_cs46xx_pcm *cpcm,
+				       int sample_rate) 
+{
+
+	/* If PCMReaderSCB and SrcTaskSCB not created yet ... */
+	if ( cpcm->pcm_channel == NULL) {
+		cpcm->pcm_channel = cs46xx_dsp_create_pcm_channel (chip, sample_rate, 
+								   cpcm, cpcm->hw_buf.addr,cpcm->pcm_channel_id);
+		if (cpcm->pcm_channel == NULL) {
+			snd_printk(KERN_ERR "cs46xx: failed to create virtual PCM channel\n");
+			return -ENOMEM;
+		}
+		cpcm->pcm_channel->sample_rate = sample_rate;
+	} else
+	/* if sample rate is changed */
+	if ((int)cpcm->pcm_channel->sample_rate != sample_rate) {
+		int unlinked = cpcm->pcm_channel->unlinked;
+		cs46xx_dsp_destroy_pcm_channel (chip,cpcm->pcm_channel);
+
+		if ( (cpcm->pcm_channel = cs46xx_dsp_create_pcm_channel (chip, sample_rate, cpcm, 
+									 cpcm->hw_buf.addr,
+									 cpcm->pcm_channel_id)) == NULL) {
+			snd_printk(KERN_ERR "cs46xx: failed to re-create virtual PCM channel\n");
+			return -ENOMEM;
+		}
+
+		if (!unlinked) cs46xx_dsp_pcm_link (chip,cpcm->pcm_channel);
+		cpcm->pcm_channel->sample_rate = sample_rate;
+	}
+
+	return 0;
+}
+#endif
+
+
+static int snd_cs46xx_playback_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_cs46xx_pcm *cpcm;
+	int err;
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	int sample_rate = params_rate(hw_params);
+	int period_size = params_period_bytes(hw_params);
+#endif
+	cpcm = runtime->private_data;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	if (snd_BUG_ON(!sample_rate))
+		return -ENXIO;
+
+	mutex_lock(&chip->spos_mutex);
+
+	if (_cs46xx_adjust_sample_rate (chip,cpcm,sample_rate)) {
+		mutex_unlock(&chip->spos_mutex);
+		return -ENXIO;
+	}
+
+	snd_BUG_ON(!cpcm->pcm_channel);
+	if (!cpcm->pcm_channel) {
+		mutex_unlock(&chip->spos_mutex);
+		return -ENXIO;
+	}
+
+
+	if (cs46xx_dsp_pcm_channel_set_period (chip,cpcm->pcm_channel,period_size)) {
+		 mutex_unlock(&chip->spos_mutex);
+		 return -EINVAL;
+	 }
+
+	snd_printdd ("period_size (%d), periods (%d) buffer_size(%d)\n",
+		     period_size, params_periods(hw_params),
+		     params_buffer_bytes(hw_params));
+#endif
+
+	if (params_periods(hw_params) == CS46XX_FRAGS) {
+		if (runtime->dma_area != cpcm->hw_buf.area)
+			snd_pcm_lib_free_pages(substream);
+		runtime->dma_area = cpcm->hw_buf.area;
+		runtime->dma_addr = cpcm->hw_buf.addr;
+		runtime->dma_bytes = cpcm->hw_buf.bytes;
+
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+		if (cpcm->pcm_channel_id == DSP_PCM_MAIN_CHANNEL) {
+			substream->ops = &snd_cs46xx_playback_ops;
+		} else if (cpcm->pcm_channel_id == DSP_PCM_REAR_CHANNEL) {
+			substream->ops = &snd_cs46xx_playback_rear_ops;
+		} else if (cpcm->pcm_channel_id == DSP_PCM_CENTER_LFE_CHANNEL) {
+			substream->ops = &snd_cs46xx_playback_clfe_ops;
+		} else if (cpcm->pcm_channel_id == DSP_IEC958_CHANNEL) {
+			substream->ops = &snd_cs46xx_playback_iec958_ops;
+		} else {
+			snd_BUG();
+		}
+#else
+		substream->ops = &snd_cs46xx_playback_ops;
+#endif
+
+	} else {
+		if (runtime->dma_area == cpcm->hw_buf.area) {
+			runtime->dma_area = NULL;
+			runtime->dma_addr = 0;
+			runtime->dma_bytes = 0;
+		}
+		if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) {
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+			mutex_unlock(&chip->spos_mutex);
+#endif
+			return err;
+		}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+		if (cpcm->pcm_channel_id == DSP_PCM_MAIN_CHANNEL) {
+			substream->ops = &snd_cs46xx_playback_indirect_ops;
+		} else if (cpcm->pcm_channel_id == DSP_PCM_REAR_CHANNEL) {
+			substream->ops = &snd_cs46xx_playback_indirect_rear_ops;
+		} else if (cpcm->pcm_channel_id == DSP_PCM_CENTER_LFE_CHANNEL) {
+			substream->ops = &snd_cs46xx_playback_indirect_clfe_ops;
+		} else if (cpcm->pcm_channel_id == DSP_IEC958_CHANNEL) {
+			substream->ops = &snd_cs46xx_playback_indirect_iec958_ops;
+		} else {
+			snd_BUG();
+		}
+#else
+		substream->ops = &snd_cs46xx_playback_indirect_ops;
+#endif
+
+	}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	mutex_unlock(&chip->spos_mutex);
+#endif
+
+	return 0;
+}
+
+static int snd_cs46xx_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	/*struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);*/
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_cs46xx_pcm *cpcm;
+
+	cpcm = runtime->private_data;
+
+	/* if play_back open fails, then this function
+	   is called and cpcm can actually be NULL here */
+	if (!cpcm) return -ENXIO;
+
+	if (runtime->dma_area != cpcm->hw_buf.area)
+		snd_pcm_lib_free_pages(substream);
+    
+	runtime->dma_area = NULL;
+	runtime->dma_addr = 0;
+	runtime->dma_bytes = 0;
+
+	return 0;
+}
+
+static int snd_cs46xx_playback_prepare(struct snd_pcm_substream *substream)
+{
+	unsigned int tmp;
+	unsigned int pfie;
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_cs46xx_pcm *cpcm;
+
+	cpcm = runtime->private_data;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	if (snd_BUG_ON(!cpcm->pcm_channel))
+		return -ENXIO;
+
+	pfie = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 1) << 2 );
+	pfie &= ~0x0000f03f;
+#else
+	/* old dsp */
+	pfie = snd_cs46xx_peek(chip, BA1_PFIE);
+ 	pfie &= ~0x0000f03f;
+#endif
+
+	cpcm->shift = 2;
+	/* if to convert from stereo to mono */
+	if (runtime->channels == 1) {
+		cpcm->shift--;
+		pfie |= 0x00002000;
+	}
+	/* if to convert from 8 bit to 16 bit */
+	if (snd_pcm_format_width(runtime->format) == 8) {
+		cpcm->shift--;
+		pfie |= 0x00001000;
+	}
+	/* if to convert to unsigned */
+	if (snd_pcm_format_unsigned(runtime->format))
+		pfie |= 0x00008000;
+
+	/* Never convert byte order when sample stream is 8 bit */
+	if (snd_pcm_format_width(runtime->format) != 8) {
+		/* convert from big endian to little endian */
+		if (snd_pcm_format_big_endian(runtime->format))
+			pfie |= 0x00004000;
+	}
+	
+	memset(&cpcm->pcm_rec, 0, sizeof(cpcm->pcm_rec));
+	cpcm->pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+	cpcm->pcm_rec.hw_buffer_size = runtime->period_size * CS46XX_FRAGS << cpcm->shift;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+
+	tmp = snd_cs46xx_peek(chip, (cpcm->pcm_channel->pcm_reader_scb->address) << 2);
+	tmp &= ~0x000003ff;
+	tmp |= (4 << cpcm->shift) - 1;
+	/* playback transaction count register */
+	snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address) << 2, tmp);
+
+	/* playback format && interrupt enable */
+	snd_cs46xx_poke(chip, (cpcm->pcm_channel->pcm_reader_scb->address + 1) << 2, pfie | cpcm->pcm_channel->pcm_slot);
+#else
+	snd_cs46xx_poke(chip, BA1_PBA, cpcm->hw_buf.addr);
+	tmp = snd_cs46xx_peek(chip, BA1_PDTC);
+	tmp &= ~0x000003ff;
+	tmp |= (4 << cpcm->shift) - 1;
+	snd_cs46xx_poke(chip, BA1_PDTC, tmp);
+	snd_cs46xx_poke(chip, BA1_PFIE, pfie);
+	snd_cs46xx_set_play_sample_rate(chip, runtime->rate);
+#endif
+
+	return 0;
+}
+
+static int snd_cs46xx_capture_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	cs46xx_dsp_pcm_ostream_set_period (chip, params_period_bytes(hw_params));
+#endif
+	if (runtime->periods == CS46XX_FRAGS) {
+		if (runtime->dma_area != chip->capt.hw_buf.area)
+			snd_pcm_lib_free_pages(substream);
+		runtime->dma_area = chip->capt.hw_buf.area;
+		runtime->dma_addr = chip->capt.hw_buf.addr;
+		runtime->dma_bytes = chip->capt.hw_buf.bytes;
+		substream->ops = &snd_cs46xx_capture_ops;
+	} else {
+		if (runtime->dma_area == chip->capt.hw_buf.area) {
+			runtime->dma_area = NULL;
+			runtime->dma_addr = 0;
+			runtime->dma_bytes = 0;
+		}
+		if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+			return err;
+		substream->ops = &snd_cs46xx_capture_indirect_ops;
+	}
+
+	return 0;
+}
+
+static int snd_cs46xx_capture_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (runtime->dma_area != chip->capt.hw_buf.area)
+		snd_pcm_lib_free_pages(substream);
+	runtime->dma_area = NULL;
+	runtime->dma_addr = 0;
+	runtime->dma_bytes = 0;
+
+	return 0;
+}
+
+static int snd_cs46xx_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	snd_cs46xx_poke(chip, BA1_CBA, chip->capt.hw_buf.addr);
+	chip->capt.shift = 2;
+	memset(&chip->capt.pcm_rec, 0, sizeof(chip->capt.pcm_rec));
+	chip->capt.pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+	chip->capt.pcm_rec.hw_buffer_size = runtime->period_size * CS46XX_FRAGS << 2;
+	snd_cs46xx_set_capture_sample_rate(chip, runtime->rate);
+
+	return 0;
+}
+
+static irqreturn_t snd_cs46xx_interrupt(int irq, void *dev_id)
+{
+	struct snd_cs46xx *chip = dev_id;
+	u32 status1;
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	u32 status2;
+	int i;
+	struct snd_cs46xx_pcm *cpcm = NULL;
+#endif
+
+	/*
+	 *  Read the Interrupt Status Register to clear the interrupt
+	 */
+	status1 = snd_cs46xx_peekBA0(chip, BA0_HISR);
+	if ((status1 & 0x7fffffff) == 0) {
+		snd_cs46xx_pokeBA0(chip, BA0_HICR, HICR_CHGM | HICR_IEV);
+		return IRQ_NONE;
+	}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	status2 = snd_cs46xx_peekBA0(chip, BA0_HSR0);
+
+	for (i = 0; i < DSP_MAX_PCM_CHANNELS; ++i) {
+		if (i <= 15) {
+			if ( status1 & (1 << i) ) {
+				if (i == CS46XX_DSP_CAPTURE_CHANNEL) {
+					if (chip->capt.substream)
+						snd_pcm_period_elapsed(chip->capt.substream);
+				} else {
+					if (ins->pcm_channels[i].active &&
+					    ins->pcm_channels[i].private_data &&
+					    !ins->pcm_channels[i].unlinked) {
+						cpcm = ins->pcm_channels[i].private_data;
+						snd_pcm_period_elapsed(cpcm->substream);
+					}
+				}
+			}
+		} else {
+			if ( status2 & (1 << (i - 16))) {
+				if (ins->pcm_channels[i].active && 
+				    ins->pcm_channels[i].private_data &&
+				    !ins->pcm_channels[i].unlinked) {
+					cpcm = ins->pcm_channels[i].private_data;
+					snd_pcm_period_elapsed(cpcm->substream);
+				}
+			}
+		}
+	}
+
+#else
+	/* old dsp */
+	if ((status1 & HISR_VC0) && chip->playback_pcm) {
+		if (chip->playback_pcm->substream)
+			snd_pcm_period_elapsed(chip->playback_pcm->substream);
+	}
+	if ((status1 & HISR_VC1) && chip->pcm) {
+		if (chip->capt.substream)
+			snd_pcm_period_elapsed(chip->capt.substream);
+	}
+#endif
+
+	if ((status1 & HISR_MIDI) && chip->rmidi) {
+		unsigned char c;
+		
+		spin_lock(&chip->reg_lock);
+		while ((snd_cs46xx_peekBA0(chip, BA0_MIDSR) & MIDSR_RBE) == 0) {
+			c = snd_cs46xx_peekBA0(chip, BA0_MIDRP);
+			if ((chip->midcr & MIDCR_RIE) == 0)
+				continue;
+			snd_rawmidi_receive(chip->midi_input, &c, 1);
+		}
+		while ((snd_cs46xx_peekBA0(chip, BA0_MIDSR) & MIDSR_TBF) == 0) {
+			if ((chip->midcr & MIDCR_TIE) == 0)
+				break;
+			if (snd_rawmidi_transmit(chip->midi_output, &c, 1) != 1) {
+				chip->midcr &= ~MIDCR_TIE;
+				snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+				break;
+			}
+			snd_cs46xx_pokeBA0(chip, BA0_MIDWP, c);
+		}
+		spin_unlock(&chip->reg_lock);
+	}
+	/*
+	 *  EOI to the PCI part....reenables interrupts
+	 */
+	snd_cs46xx_pokeBA0(chip, BA0_HICR, HICR_CHGM | HICR_IEV);
+
+	return IRQ_HANDLED;
+}
+
+static struct snd_pcm_hardware snd_cs46xx_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_INTERLEAVED | 
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER /*|*/
+				 /*SNDRV_PCM_INFO_RESUME*/),
+	.formats =		(SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+				 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
+				 SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5500,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(256 * 1024),
+	.period_bytes_min =	CS46XX_MIN_PERIOD_SIZE,
+	.period_bytes_max =	CS46XX_MAX_PERIOD_SIZE,
+	.periods_min =		CS46XX_FRAGS,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_cs46xx_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER /*|*/
+				 /*SNDRV_PCM_INFO_RESUME*/),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5500,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(256 * 1024),
+	.period_bytes_min =	CS46XX_MIN_PERIOD_SIZE,
+	.period_bytes_max =	CS46XX_MAX_PERIOD_SIZE,
+	.periods_min =		CS46XX_FRAGS,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+
+static unsigned int period_sizes[] = { 32, 64, 128, 256, 512, 1024, 2048 };
+
+static struct snd_pcm_hw_constraint_list hw_constraints_period_sizes = {
+	.count = ARRAY_SIZE(period_sizes),
+	.list = period_sizes,
+	.mask = 0
+};
+
+#endif
+
+static void snd_cs46xx_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+	kfree(runtime->private_data);
+}
+
+static int _cs46xx_playback_open_channel (struct snd_pcm_substream *substream,int pcm_channel_id)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	struct snd_cs46xx_pcm * cpcm;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	cpcm = kzalloc(sizeof(*cpcm), GFP_KERNEL);
+	if (cpcm == NULL)
+		return -ENOMEM;
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+				PAGE_SIZE, &cpcm->hw_buf) < 0) {
+		kfree(cpcm);
+		return -ENOMEM;
+	}
+
+	runtime->hw = snd_cs46xx_playback;
+	runtime->private_data = cpcm;
+	runtime->private_free = snd_cs46xx_pcm_free_substream;
+
+	cpcm->substream = substream;
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	mutex_lock(&chip->spos_mutex);
+	cpcm->pcm_channel = NULL; 
+	cpcm->pcm_channel_id = pcm_channel_id;
+
+
+	snd_pcm_hw_constraint_list(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 
+				   &hw_constraints_period_sizes);
+
+	mutex_unlock(&chip->spos_mutex);
+#else
+	chip->playback_pcm = cpcm; /* HACK */
+#endif
+
+	if (chip->accept_valid)
+		substream->runtime->hw.info |= SNDRV_PCM_INFO_MMAP_VALID;
+	chip->active_ctrl(chip, 1);
+
+	return 0;
+}
+
+static int snd_cs46xx_playback_open(struct snd_pcm_substream *substream)
+{
+	snd_printdd("open front channel\n");
+	return _cs46xx_playback_open_channel(substream,DSP_PCM_MAIN_CHANNEL);
+}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+static int snd_cs46xx_playback_open_rear(struct snd_pcm_substream *substream)
+{
+	snd_printdd("open rear channel\n");
+
+	return _cs46xx_playback_open_channel(substream,DSP_PCM_REAR_CHANNEL);
+}
+
+static int snd_cs46xx_playback_open_clfe(struct snd_pcm_substream *substream)
+{
+	snd_printdd("open center - LFE channel\n");
+
+	return _cs46xx_playback_open_channel(substream,DSP_PCM_CENTER_LFE_CHANNEL);
+}
+
+static int snd_cs46xx_playback_open_iec958(struct snd_pcm_substream *substream)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+
+	snd_printdd("open raw iec958 channel\n");
+
+	mutex_lock(&chip->spos_mutex);
+	cs46xx_iec958_pre_open (chip);
+	mutex_unlock(&chip->spos_mutex);
+
+	return _cs46xx_playback_open_channel(substream,DSP_IEC958_CHANNEL);
+}
+
+static int snd_cs46xx_playback_close(struct snd_pcm_substream *substream);
+
+static int snd_cs46xx_playback_close_iec958(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+  
+	snd_printdd("close raw iec958 channel\n");
+
+	err = snd_cs46xx_playback_close(substream);
+
+	mutex_lock(&chip->spos_mutex);
+	cs46xx_iec958_post_close (chip);
+	mutex_unlock(&chip->spos_mutex);
+
+	return err;
+}
+#endif
+
+static int snd_cs46xx_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+				PAGE_SIZE, &chip->capt.hw_buf) < 0)
+		return -ENOMEM;
+	chip->capt.substream = substream;
+	substream->runtime->hw = snd_cs46xx_capture;
+
+	if (chip->accept_valid)
+		substream->runtime->hw.info |= SNDRV_PCM_INFO_MMAP_VALID;
+
+	chip->active_ctrl(chip, 1);
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				   SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 
+				   &hw_constraints_period_sizes);
+#endif
+	return 0;
+}
+
+static int snd_cs46xx_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_cs46xx_pcm * cpcm;
+
+	cpcm = runtime->private_data;
+
+	/* when playback_open fails, then cpcm can be NULL */
+	if (!cpcm) return -ENXIO;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	mutex_lock(&chip->spos_mutex);
+	if (cpcm->pcm_channel) {
+		cs46xx_dsp_destroy_pcm_channel(chip,cpcm->pcm_channel);
+		cpcm->pcm_channel = NULL;
+	}
+	mutex_unlock(&chip->spos_mutex);
+#else
+	chip->playback_pcm = NULL;
+#endif
+
+	cpcm->substream = NULL;
+	snd_dma_free_pages(&cpcm->hw_buf);
+	chip->active_ctrl(chip, -1);
+
+	return 0;
+}
+
+static int snd_cs46xx_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_cs46xx *chip = snd_pcm_substream_chip(substream);
+
+	chip->capt.substream = NULL;
+	snd_dma_free_pages(&chip->capt.hw_buf);
+	chip->active_ctrl(chip, -1);
+
+	return 0;
+}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+static struct snd_pcm_ops snd_cs46xx_playback_rear_ops = {
+	.open =			snd_cs46xx_playback_open_rear,
+	.close =		snd_cs46xx_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_cs46xx_playback_hw_params,
+	.hw_free =		snd_cs46xx_playback_hw_free,
+	.prepare =		snd_cs46xx_playback_prepare,
+	.trigger =		snd_cs46xx_playback_trigger,
+	.pointer =		snd_cs46xx_playback_direct_pointer,
+};
+
+static struct snd_pcm_ops snd_cs46xx_playback_indirect_rear_ops = {
+	.open =			snd_cs46xx_playback_open_rear,
+	.close =		snd_cs46xx_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_cs46xx_playback_hw_params,
+	.hw_free =		snd_cs46xx_playback_hw_free,
+	.prepare =		snd_cs46xx_playback_prepare,
+	.trigger =		snd_cs46xx_playback_trigger,
+	.pointer =		snd_cs46xx_playback_indirect_pointer,
+	.ack =			snd_cs46xx_playback_transfer,
+};
+
+static struct snd_pcm_ops snd_cs46xx_playback_clfe_ops = {
+	.open =			snd_cs46xx_playback_open_clfe,
+	.close =		snd_cs46xx_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_cs46xx_playback_hw_params,
+	.hw_free =		snd_cs46xx_playback_hw_free,
+	.prepare =		snd_cs46xx_playback_prepare,
+	.trigger =		snd_cs46xx_playback_trigger,
+	.pointer =		snd_cs46xx_playback_direct_pointer,
+};
+
+static struct snd_pcm_ops snd_cs46xx_playback_indirect_clfe_ops = {
+	.open =			snd_cs46xx_playback_open_clfe,
+	.close =		snd_cs46xx_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_cs46xx_playback_hw_params,
+	.hw_free =		snd_cs46xx_playback_hw_free,
+	.prepare =		snd_cs46xx_playback_prepare,
+	.trigger =		snd_cs46xx_playback_trigger,
+	.pointer =		snd_cs46xx_playback_indirect_pointer,
+	.ack =			snd_cs46xx_playback_transfer,
+};
+
+static struct snd_pcm_ops snd_cs46xx_playback_iec958_ops = {
+	.open =			snd_cs46xx_playback_open_iec958,
+	.close =		snd_cs46xx_playback_close_iec958,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_cs46xx_playback_hw_params,
+	.hw_free =		snd_cs46xx_playback_hw_free,
+	.prepare =		snd_cs46xx_playback_prepare,
+	.trigger =		snd_cs46xx_playback_trigger,
+	.pointer =		snd_cs46xx_playback_direct_pointer,
+};
+
+static struct snd_pcm_ops snd_cs46xx_playback_indirect_iec958_ops = {
+	.open =			snd_cs46xx_playback_open_iec958,
+	.close =		snd_cs46xx_playback_close_iec958,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_cs46xx_playback_hw_params,
+	.hw_free =		snd_cs46xx_playback_hw_free,
+	.prepare =		snd_cs46xx_playback_prepare,
+	.trigger =		snd_cs46xx_playback_trigger,
+	.pointer =		snd_cs46xx_playback_indirect_pointer,
+	.ack =			snd_cs46xx_playback_transfer,
+};
+
+#endif
+
+static struct snd_pcm_ops snd_cs46xx_playback_ops = {
+	.open =			snd_cs46xx_playback_open,
+	.close =		snd_cs46xx_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_cs46xx_playback_hw_params,
+	.hw_free =		snd_cs46xx_playback_hw_free,
+	.prepare =		snd_cs46xx_playback_prepare,
+	.trigger =		snd_cs46xx_playback_trigger,
+	.pointer =		snd_cs46xx_playback_direct_pointer,
+};
+
+static struct snd_pcm_ops snd_cs46xx_playback_indirect_ops = {
+	.open =			snd_cs46xx_playback_open,
+	.close =		snd_cs46xx_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_cs46xx_playback_hw_params,
+	.hw_free =		snd_cs46xx_playback_hw_free,
+	.prepare =		snd_cs46xx_playback_prepare,
+	.trigger =		snd_cs46xx_playback_trigger,
+	.pointer =		snd_cs46xx_playback_indirect_pointer,
+	.ack =			snd_cs46xx_playback_transfer,
+};
+
+static struct snd_pcm_ops snd_cs46xx_capture_ops = {
+	.open =			snd_cs46xx_capture_open,
+	.close =		snd_cs46xx_capture_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_cs46xx_capture_hw_params,
+	.hw_free =		snd_cs46xx_capture_hw_free,
+	.prepare =		snd_cs46xx_capture_prepare,
+	.trigger =		snd_cs46xx_capture_trigger,
+	.pointer =		snd_cs46xx_capture_direct_pointer,
+};
+
+static struct snd_pcm_ops snd_cs46xx_capture_indirect_ops = {
+	.open =			snd_cs46xx_capture_open,
+	.close =		snd_cs46xx_capture_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_cs46xx_capture_hw_params,
+	.hw_free =		snd_cs46xx_capture_hw_free,
+	.prepare =		snd_cs46xx_capture_prepare,
+	.trigger =		snd_cs46xx_capture_trigger,
+	.pointer =		snd_cs46xx_capture_indirect_pointer,
+	.ack =			snd_cs46xx_capture_transfer,
+};
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+#define MAX_PLAYBACK_CHANNELS	(DSP_MAX_PCM_CHANNELS - 1)
+#else
+#define MAX_PLAYBACK_CHANNELS	1
+#endif
+
+int __devinit snd_cs46xx_pcm(struct snd_cs46xx *chip, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(chip->card, "CS46xx", device, MAX_PLAYBACK_CHANNELS, 1, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cs46xx_capture_ops);
+
+	/* global setup */
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "CS46xx");
+	chip->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci), 64*1024, 256*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+
+	return 0;
+}
+
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+int __devinit snd_cs46xx_pcm_rear(struct snd_cs46xx *chip, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+
+	if ((err = snd_pcm_new(chip->card, "CS46xx - Rear", device, MAX_PLAYBACK_CHANNELS, 0, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_rear_ops);
+
+	/* global setup */
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "CS46xx - Rear");
+	chip->pcm_rear = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci), 64*1024, 256*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+
+	return 0;
+}
+
+int __devinit snd_cs46xx_pcm_center_lfe(struct snd_cs46xx *chip, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+
+	if ((err = snd_pcm_new(chip->card, "CS46xx - Center LFE", device, MAX_PLAYBACK_CHANNELS, 0, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_clfe_ops);
+
+	/* global setup */
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "CS46xx - Center LFE");
+	chip->pcm_center_lfe = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci), 64*1024, 256*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+
+	return 0;
+}
+
+int __devinit snd_cs46xx_pcm_iec958(struct snd_cs46xx *chip, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+
+	if ((err = snd_pcm_new(chip->card, "CS46xx - IEC958", device, 1, 0, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs46xx_playback_iec958_ops);
+
+	/* global setup */
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "CS46xx - IEC958");
+	chip->pcm_rear = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci), 64*1024, 256*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+
+	return 0;
+}
+#endif
+
+/*
+ *  Mixer routines
+ */
+static void snd_cs46xx_mixer_free_ac97_bus(struct snd_ac97_bus *bus)
+{
+	struct snd_cs46xx *chip = bus->private_data;
+
+	chip->ac97_bus = NULL;
+}
+
+static void snd_cs46xx_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+	struct snd_cs46xx *chip = ac97->private_data;
+
+	if (snd_BUG_ON(ac97 != chip->ac97[CS46XX_PRIMARY_CODEC_INDEX] &&
+		       ac97 != chip->ac97[CS46XX_SECONDARY_CODEC_INDEX]))
+		return;
+
+	if (ac97 == chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]) {
+		chip->ac97[CS46XX_PRIMARY_CODEC_INDEX] = NULL;
+		chip->eapd_switch = NULL;
+	}
+	else
+		chip->ac97[CS46XX_SECONDARY_CODEC_INDEX] = NULL;
+}
+
+static int snd_cs46xx_vol_info(struct snd_kcontrol *kcontrol, 
+			       struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0x7fff;
+	return 0;
+}
+
+static int snd_cs46xx_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value;
+	unsigned int val = snd_cs46xx_peek(chip, reg);
+	ucontrol->value.integer.value[0] = 0xffff - (val >> 16);
+	ucontrol->value.integer.value[1] = 0xffff - (val & 0xffff);
+	return 0;
+}
+
+static int snd_cs46xx_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value;
+	unsigned int val = ((0xffff - ucontrol->value.integer.value[0]) << 16 | 
+			    (0xffff - ucontrol->value.integer.value[1]));
+	unsigned int old = snd_cs46xx_peek(chip, reg);
+	int change = (old != val);
+
+	if (change) {
+		snd_cs46xx_poke(chip, reg, val);
+	}
+
+	return change;
+}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+
+static int snd_cs46xx_vol_dac_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = chip->dsp_spos_instance->dac_volume_left;
+	ucontrol->value.integer.value[1] = chip->dsp_spos_instance->dac_volume_right;
+
+	return 0;
+}
+
+static int snd_cs46xx_vol_dac_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	int change = 0;
+
+	if (chip->dsp_spos_instance->dac_volume_right != ucontrol->value.integer.value[0] ||
+	    chip->dsp_spos_instance->dac_volume_left != ucontrol->value.integer.value[1]) {
+		cs46xx_dsp_set_dac_volume(chip,
+					  ucontrol->value.integer.value[0],
+					  ucontrol->value.integer.value[1]);
+		change = 1;
+	}
+
+	return change;
+}
+
+#if 0
+static int snd_cs46xx_vol_iec958_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = chip->dsp_spos_instance->spdif_input_volume_left;
+	ucontrol->value.integer.value[1] = chip->dsp_spos_instance->spdif_input_volume_right;
+	return 0;
+}
+
+static int snd_cs46xx_vol_iec958_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	int change = 0;
+
+	if (chip->dsp_spos_instance->spdif_input_volume_left  != ucontrol->value.integer.value[0] ||
+	    chip->dsp_spos_instance->spdif_input_volume_right!= ucontrol->value.integer.value[1]) {
+		cs46xx_dsp_set_iec958_volume (chip,
+					      ucontrol->value.integer.value[0],
+					      ucontrol->value.integer.value[1]);
+		change = 1;
+	}
+
+	return change;
+}
+#endif
+
+#define snd_mixer_boolean_info		snd_ctl_boolean_mono_info
+
+static int snd_cs46xx_iec958_get(struct snd_kcontrol *kcontrol, 
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value;
+
+	if (reg == CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT)
+		ucontrol->value.integer.value[0] = (chip->dsp_spos_instance->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED);
+	else
+		ucontrol->value.integer.value[0] = chip->dsp_spos_instance->spdif_status_in;
+
+	return 0;
+}
+
+static int snd_cs46xx_iec958_put(struct snd_kcontrol *kcontrol, 
+                                  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	int change, res;
+
+	switch (kcontrol->private_value) {
+	case CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT:
+		mutex_lock(&chip->spos_mutex);
+		change = (chip->dsp_spos_instance->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED);
+		if (ucontrol->value.integer.value[0] && !change) 
+			cs46xx_dsp_enable_spdif_out(chip);
+		else if (change && !ucontrol->value.integer.value[0])
+			cs46xx_dsp_disable_spdif_out(chip);
+
+		res = (change != (chip->dsp_spos_instance->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED));
+		mutex_unlock(&chip->spos_mutex);
+		break;
+	case CS46XX_MIXER_SPDIF_INPUT_ELEMENT:
+		change = chip->dsp_spos_instance->spdif_status_in;
+		if (ucontrol->value.integer.value[0] && !change) {
+			cs46xx_dsp_enable_spdif_in(chip);
+			/* restore volume */
+		}
+		else if (change && !ucontrol->value.integer.value[0])
+			cs46xx_dsp_disable_spdif_in(chip);
+		
+		res = (change != chip->dsp_spos_instance->spdif_status_in);
+		break;
+	default:
+		res = -EINVAL;
+		snd_BUG(); /* should never happen ... */
+	}
+
+	return res;
+}
+
+static int snd_cs46xx_adc_capture_get(struct snd_kcontrol *kcontrol, 
+                                      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if (ins->adc_input != NULL) 
+		ucontrol->value.integer.value[0] = 1;
+	else 
+		ucontrol->value.integer.value[0] = 0;
+	
+	return 0;
+}
+
+static int snd_cs46xx_adc_capture_put(struct snd_kcontrol *kcontrol, 
+                                      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	int change = 0;
+
+	if (ucontrol->value.integer.value[0] && !ins->adc_input) {
+		cs46xx_dsp_enable_adc_capture(chip);
+		change = 1;
+	} else  if (!ucontrol->value.integer.value[0] && ins->adc_input) {
+		cs46xx_dsp_disable_adc_capture(chip);
+		change = 1;
+	}
+	return change;
+}
+
+static int snd_cs46xx_pcm_capture_get(struct snd_kcontrol *kcontrol, 
+                                      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if (ins->pcm_input != NULL) 
+		ucontrol->value.integer.value[0] = 1;
+	else 
+		ucontrol->value.integer.value[0] = 0;
+
+	return 0;
+}
+
+
+static int snd_cs46xx_pcm_capture_put(struct snd_kcontrol *kcontrol, 
+                                      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	int change = 0;
+
+	if (ucontrol->value.integer.value[0] && !ins->pcm_input) {
+		cs46xx_dsp_enable_pcm_capture(chip);
+		change = 1;
+	} else  if (!ucontrol->value.integer.value[0] && ins->pcm_input) {
+		cs46xx_dsp_disable_pcm_capture(chip);
+		change = 1;
+	}
+
+	return change;
+}
+
+static int snd_herc_spdif_select_get(struct snd_kcontrol *kcontrol, 
+                                     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+
+	int val1 = snd_cs46xx_peekBA0(chip, BA0_EGPIODR);
+
+	if (val1 & EGPIODR_GPOE0)
+		ucontrol->value.integer.value[0] = 1;
+	else
+		ucontrol->value.integer.value[0] = 0;
+
+	return 0;
+}
+
+/*
+ *	Game Theatre XP card - EGPIO[0] is used to select SPDIF input optical or coaxial.
+ */ 
+static int snd_herc_spdif_select_put(struct snd_kcontrol *kcontrol, 
+                                       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	int val1 = snd_cs46xx_peekBA0(chip, BA0_EGPIODR);
+	int val2 = snd_cs46xx_peekBA0(chip, BA0_EGPIOPTR);
+
+	if (ucontrol->value.integer.value[0]) {
+		/* optical is default */
+		snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, 
+				   EGPIODR_GPOE0 | val1);  /* enable EGPIO0 output */
+		snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, 
+				   EGPIOPTR_GPPT0 | val2); /* open-drain on output */
+	} else {
+		/* coaxial */
+		snd_cs46xx_pokeBA0(chip, BA0_EGPIODR,  val1 & ~EGPIODR_GPOE0); /* disable */
+		snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, val2 & ~EGPIOPTR_GPPT0); /* disable */
+	}
+
+	/* checking diff from the EGPIO direction register 
+	   should be enough */
+	return (val1 != (int)snd_cs46xx_peekBA0(chip, BA0_EGPIODR));
+}
+
+
+static int snd_cs46xx_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_cs46xx_spdif_default_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	mutex_lock(&chip->spos_mutex);
+	ucontrol->value.iec958.status[0] = _wrap_all_bits((ins->spdif_csuv_default >> 24) & 0xff);
+	ucontrol->value.iec958.status[1] = _wrap_all_bits((ins->spdif_csuv_default >> 16) & 0xff);
+	ucontrol->value.iec958.status[2] = 0;
+	ucontrol->value.iec958.status[3] = _wrap_all_bits((ins->spdif_csuv_default) & 0xff);
+	mutex_unlock(&chip->spos_mutex);
+
+	return 0;
+}
+
+static int snd_cs46xx_spdif_default_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx * chip = snd_kcontrol_chip(kcontrol);
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	unsigned int val;
+	int change;
+
+	mutex_lock(&chip->spos_mutex);
+	val = ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[0]) << 24) |
+		((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[2]) << 16) |
+		((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[3]))  |
+		/* left and right validity bit */
+		(1 << 13) | (1 << 12);
+
+
+	change = (unsigned int)ins->spdif_csuv_default != val;
+	ins->spdif_csuv_default = val;
+
+	if ( !(ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN) )
+		cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV,val);
+
+	mutex_unlock(&chip->spos_mutex);
+
+	return change;
+}
+
+static int snd_cs46xx_spdif_mask_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0x00;
+	ucontrol->value.iec958.status[3] = 0xff;
+	return 0;
+}
+
+static int snd_cs46xx_spdif_stream_get(struct snd_kcontrol *kcontrol,
+                                         struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	mutex_lock(&chip->spos_mutex);
+	ucontrol->value.iec958.status[0] = _wrap_all_bits((ins->spdif_csuv_stream >> 24) & 0xff);
+	ucontrol->value.iec958.status[1] = _wrap_all_bits((ins->spdif_csuv_stream >> 16) & 0xff);
+	ucontrol->value.iec958.status[2] = 0;
+	ucontrol->value.iec958.status[3] = _wrap_all_bits((ins->spdif_csuv_stream) & 0xff);
+	mutex_unlock(&chip->spos_mutex);
+
+	return 0;
+}
+
+static int snd_cs46xx_spdif_stream_put(struct snd_kcontrol *kcontrol,
+                                        struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx * chip = snd_kcontrol_chip(kcontrol);
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	unsigned int val;
+	int change;
+
+	mutex_lock(&chip->spos_mutex);
+	val = ((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[0]) << 24) |
+		((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[1]) << 16) |
+		((unsigned int)_wrap_all_bits(ucontrol->value.iec958.status[3])) |
+		/* left and right validity bit */
+		(1 << 13) | (1 << 12);
+
+
+	change = ins->spdif_csuv_stream != val;
+	ins->spdif_csuv_stream = val;
+
+	if ( ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN )
+		cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV,val);
+
+	mutex_unlock(&chip->spos_mutex);
+
+	return change;
+}
+
+#endif /* CONFIG_SND_CS46XX_NEW_DSP */
+
+
+static struct snd_kcontrol_new snd_cs46xx_controls[] __devinitdata = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DAC Volume",
+	.info = snd_cs46xx_vol_info,
+#ifndef CONFIG_SND_CS46XX_NEW_DSP
+	.get = snd_cs46xx_vol_get,
+	.put = snd_cs46xx_vol_put,
+	.private_value = BA1_PVOL,
+#else
+	.get = snd_cs46xx_vol_dac_get,
+	.put = snd_cs46xx_vol_dac_put,
+#endif
+},
+
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "ADC Volume",
+	.info = snd_cs46xx_vol_info,
+	.get = snd_cs46xx_vol_get,
+	.put = snd_cs46xx_vol_put,
+#ifndef CONFIG_SND_CS46XX_NEW_DSP
+	.private_value = BA1_CVOL,
+#else
+	.private_value = (VARIDECIMATE_SCB_ADDR + 0xE) << 2,
+#endif
+},
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "ADC Capture Switch",
+	.info = snd_mixer_boolean_info,
+	.get = snd_cs46xx_adc_capture_get,
+	.put = snd_cs46xx_adc_capture_put
+},
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DAC Capture Switch",
+	.info = snd_mixer_boolean_info,
+	.get = snd_cs46xx_pcm_capture_get,
+	.put = snd_cs46xx_pcm_capture_put
+},
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH),
+	.info = snd_mixer_boolean_info,
+	.get = snd_cs46xx_iec958_get,
+	.put = snd_cs46xx_iec958_put,
+	.private_value = CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT,
+},
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = SNDRV_CTL_NAME_IEC958("Input ",NONE,SWITCH),
+	.info = snd_mixer_boolean_info,
+	.get = snd_cs46xx_iec958_get,
+	.put = snd_cs46xx_iec958_put,
+	.private_value = CS46XX_MIXER_SPDIF_INPUT_ELEMENT,
+},
+#if 0
+/* Input IEC958 volume does not work for the moment. (Benny) */
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = SNDRV_CTL_NAME_IEC958("Input ",NONE,VOLUME),
+	.info = snd_cs46xx_vol_info,
+	.get = snd_cs46xx_vol_iec958_get,
+	.put = snd_cs46xx_vol_iec958_put,
+	.private_value = (ASYNCRX_SCB_ADDR + 0xE) << 2,
+},
+#endif
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =  SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =	 snd_cs46xx_spdif_info,
+	.get =	 snd_cs46xx_spdif_default_get,
+	.put =   snd_cs46xx_spdif_default_put,
+},
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =	 SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+	.info =	 snd_cs46xx_spdif_info,
+        .get =	 snd_cs46xx_spdif_mask_get,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ
+},
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =	 SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+	.info =	 snd_cs46xx_spdif_info,
+	.get =	 snd_cs46xx_spdif_stream_get,
+	.put =	 snd_cs46xx_spdif_stream_put
+},
+
+#endif
+};
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+/* set primary cs4294 codec into Extended Audio Mode */
+static int snd_cs46xx_front_dup_get(struct snd_kcontrol *kcontrol, 
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+	val = snd_ac97_read(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX], AC97_CSR_ACMODE);
+	ucontrol->value.integer.value[0] = (val & 0x200) ? 0 : 1;
+	return 0;
+}
+
+static int snd_cs46xx_front_dup_put(struct snd_kcontrol *kcontrol, 
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
+	return snd_ac97_update_bits(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX],
+				    AC97_CSR_ACMODE, 0x200,
+				    ucontrol->value.integer.value[0] ? 0 : 0x200);
+}
+
+static struct snd_kcontrol_new snd_cs46xx_front_dup_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Duplicate Front",
+	.info = snd_mixer_boolean_info,
+	.get = snd_cs46xx_front_dup_get,
+	.put = snd_cs46xx_front_dup_put,
+};
+#endif
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+/* Only available on the Hercules Game Theater XP soundcard */
+static struct snd_kcontrol_new snd_hercules_controls[] = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Optical/Coaxial SPDIF Input Switch",
+	.info = snd_mixer_boolean_info,
+	.get = snd_herc_spdif_select_get,
+	.put = snd_herc_spdif_select_put,
+},
+};
+
+
+static void snd_cs46xx_codec_reset (struct snd_ac97 * ac97)
+{
+	unsigned long end_time;
+	int err;
+
+	/* reset to defaults */
+	snd_ac97_write(ac97, AC97_RESET, 0);	
+
+	/* set the desired CODEC mode */
+	if (ac97->num == CS46XX_PRIMARY_CODEC_INDEX) {
+		snd_printdd("cs46xx: CODEC1 mode %04x\n", 0x0);
+		snd_cs46xx_ac97_write(ac97, AC97_CSR_ACMODE, 0x0);
+	} else if (ac97->num == CS46XX_SECONDARY_CODEC_INDEX) {
+		snd_printdd("cs46xx: CODEC2 mode %04x\n", 0x3);
+		snd_cs46xx_ac97_write(ac97, AC97_CSR_ACMODE, 0x3);
+	} else {
+		snd_BUG(); /* should never happen ... */
+	}
+
+	udelay(50);
+
+	/* it's necessary to wait awhile until registers are accessible after RESET */
+	/* because the PCM or MASTER volume registers can be modified, */
+	/* the REC_GAIN register is used for tests */
+	end_time = jiffies + HZ;
+	do {
+		unsigned short ext_mid;
+    
+		/* use preliminary reads to settle the communication */
+		snd_ac97_read(ac97, AC97_RESET);
+		snd_ac97_read(ac97, AC97_VENDOR_ID1);
+		snd_ac97_read(ac97, AC97_VENDOR_ID2);
+		/* modem? */
+		ext_mid = snd_ac97_read(ac97, AC97_EXTENDED_MID);
+		if (ext_mid != 0xffff && (ext_mid & 1) != 0)
+			return;
+
+		/* test if we can write to the record gain volume register */
+		snd_ac97_write(ac97, AC97_REC_GAIN, 0x8a05);
+		if ((err = snd_ac97_read(ac97, AC97_REC_GAIN)) == 0x8a05)
+			return;
+
+		msleep(10);
+	} while (time_after_eq(end_time, jiffies));
+
+	snd_printk(KERN_ERR "CS46xx secondary codec doesn't respond!\n");  
+}
+#endif
+
+static int __devinit cs46xx_detect_codec(struct snd_cs46xx *chip, int codec)
+{
+	int idx, err;
+	struct snd_ac97_template ac97;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.private_free = snd_cs46xx_mixer_free_ac97;
+	ac97.num = codec;
+	if (chip->amplifier_ctrl == amp_voyetra)
+		ac97.scaps = AC97_SCAP_INV_EAPD;
+
+	if (codec == CS46XX_SECONDARY_CODEC_INDEX) {
+		snd_cs46xx_codec_write(chip, AC97_RESET, 0, codec);
+		udelay(10);
+		if (snd_cs46xx_codec_read(chip, AC97_RESET, codec) & 0x8000) {
+			snd_printdd("snd_cs46xx: seconadry codec not present\n");
+			return -ENXIO;
+		}
+	}
+
+	snd_cs46xx_codec_write(chip, AC97_MASTER, 0x8000, codec);
+	for (idx = 0; idx < 100; ++idx) {
+		if (snd_cs46xx_codec_read(chip, AC97_MASTER, codec) == 0x8000) {
+			err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97[codec]);
+			return err;
+		}
+		msleep(10);
+	}
+	snd_printdd("snd_cs46xx: codec %d detection timeout\n", codec);
+	return -ENXIO;
+}
+
+int __devinit snd_cs46xx_mixer(struct snd_cs46xx *chip, int spdif_device)
+{
+	struct snd_card *card = chip->card;
+	struct snd_ctl_elem_id id;
+	int err;
+	unsigned int idx;
+	static struct snd_ac97_bus_ops ops = {
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+		.reset = snd_cs46xx_codec_reset,
+#endif
+		.write = snd_cs46xx_ac97_write,
+		.read = snd_cs46xx_ac97_read,
+	};
+
+	/* detect primary codec */
+	chip->nr_ac97_codecs = 0;
+	snd_printdd("snd_cs46xx: detecting primary codec\n");
+	if ((err = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus)) < 0)
+		return err;
+	chip->ac97_bus->private_free = snd_cs46xx_mixer_free_ac97_bus;
+
+	if (cs46xx_detect_codec(chip, CS46XX_PRIMARY_CODEC_INDEX) < 0)
+		return -ENXIO;
+	chip->nr_ac97_codecs = 1;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	snd_printdd("snd_cs46xx: detecting seconadry codec\n");
+	/* try detect a secondary codec */
+	if (! cs46xx_detect_codec(chip, CS46XX_SECONDARY_CODEC_INDEX))
+		chip->nr_ac97_codecs = 2;
+#endif /* CONFIG_SND_CS46XX_NEW_DSP */
+
+	/* add cs4630 mixer controls */
+	for (idx = 0; idx < ARRAY_SIZE(snd_cs46xx_controls); idx++) {
+		struct snd_kcontrol *kctl;
+		kctl = snd_ctl_new1(&snd_cs46xx_controls[idx], chip);
+		if (kctl && kctl->id.iface == SNDRV_CTL_ELEM_IFACE_PCM)
+			kctl->id.device = spdif_device;
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			return err;
+	}
+
+	/* get EAPD mixer switch (for voyetra hack) */
+	memset(&id, 0, sizeof(id));
+	id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(id.name, "External Amplifier");
+	chip->eapd_switch = snd_ctl_find_id(chip->card, &id);
+    
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	if (chip->nr_ac97_codecs == 1) {
+		unsigned int id2 = chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]->id & 0xffff;
+		if (id2 == 0x592b || id2 == 0x592d) {
+			err = snd_ctl_add(card, snd_ctl_new1(&snd_cs46xx_front_dup_ctl, chip));
+			if (err < 0)
+				return err;
+			snd_ac97_write_cache(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX],
+					     AC97_CSR_ACMODE, 0x200);
+		}
+	}
+	/* do soundcard specific mixer setup */
+	if (chip->mixer_init) {
+		snd_printdd ("calling chip->mixer_init(chip);\n");
+		chip->mixer_init(chip);
+	}
+#endif
+
+ 	/* turn on amplifier */
+	chip->amplifier_ctrl(chip, 1);
+    
+	return 0;
+}
+
+/*
+ *  RawMIDI interface
+ */
+
+static void snd_cs46xx_midi_reset(struct snd_cs46xx *chip)
+{
+	snd_cs46xx_pokeBA0(chip, BA0_MIDCR, MIDCR_MRST);
+	udelay(100);
+	snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+}
+
+static int snd_cs46xx_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_cs46xx *chip = substream->rmidi->private_data;
+
+	chip->active_ctrl(chip, 1);
+	spin_lock_irq(&chip->reg_lock);
+	chip->uartm |= CS46XX_MODE_INPUT;
+	chip->midcr |= MIDCR_RXE;
+	chip->midi_input = substream;
+	if (!(chip->uartm & CS46XX_MODE_OUTPUT)) {
+		snd_cs46xx_midi_reset(chip);
+	} else {
+		snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+	}
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_cs46xx_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_cs46xx *chip = substream->rmidi->private_data;
+
+	spin_lock_irq(&chip->reg_lock);
+	chip->midcr &= ~(MIDCR_RXE | MIDCR_RIE);
+	chip->midi_input = NULL;
+	if (!(chip->uartm & CS46XX_MODE_OUTPUT)) {
+		snd_cs46xx_midi_reset(chip);
+	} else {
+		snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+	}
+	chip->uartm &= ~CS46XX_MODE_INPUT;
+	spin_unlock_irq(&chip->reg_lock);
+	chip->active_ctrl(chip, -1);
+	return 0;
+}
+
+static int snd_cs46xx_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_cs46xx *chip = substream->rmidi->private_data;
+
+	chip->active_ctrl(chip, 1);
+
+	spin_lock_irq(&chip->reg_lock);
+	chip->uartm |= CS46XX_MODE_OUTPUT;
+	chip->midcr |= MIDCR_TXE;
+	chip->midi_output = substream;
+	if (!(chip->uartm & CS46XX_MODE_INPUT)) {
+		snd_cs46xx_midi_reset(chip);
+	} else {
+		snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+	}
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_cs46xx_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_cs46xx *chip = substream->rmidi->private_data;
+
+	spin_lock_irq(&chip->reg_lock);
+	chip->midcr &= ~(MIDCR_TXE | MIDCR_TIE);
+	chip->midi_output = NULL;
+	if (!(chip->uartm & CS46XX_MODE_INPUT)) {
+		snd_cs46xx_midi_reset(chip);
+	} else {
+		snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+	}
+	chip->uartm &= ~CS46XX_MODE_OUTPUT;
+	spin_unlock_irq(&chip->reg_lock);
+	chip->active_ctrl(chip, -1);
+	return 0;
+}
+
+static void snd_cs46xx_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct snd_cs46xx *chip = substream->rmidi->private_data;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (up) {
+		if ((chip->midcr & MIDCR_RIE) == 0) {
+			chip->midcr |= MIDCR_RIE;
+			snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+		}
+	} else {
+		if (chip->midcr & MIDCR_RIE) {
+			chip->midcr &= ~MIDCR_RIE;
+			snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+		}
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_cs46xx_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct snd_cs46xx *chip = substream->rmidi->private_data;
+	unsigned char byte;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (up) {
+		if ((chip->midcr & MIDCR_TIE) == 0) {
+			chip->midcr |= MIDCR_TIE;
+			/* fill UART FIFO buffer at first, and turn Tx interrupts only if necessary */
+			while ((chip->midcr & MIDCR_TIE) &&
+			       (snd_cs46xx_peekBA0(chip, BA0_MIDSR) & MIDSR_TBF) == 0) {
+				if (snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+					chip->midcr &= ~MIDCR_TIE;
+				} else {
+					snd_cs46xx_pokeBA0(chip, BA0_MIDWP, byte);
+				}
+			}
+			snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+		}
+	} else {
+		if (chip->midcr & MIDCR_TIE) {
+			chip->midcr &= ~MIDCR_TIE;
+			snd_cs46xx_pokeBA0(chip, BA0_MIDCR, chip->midcr);
+		}
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static struct snd_rawmidi_ops snd_cs46xx_midi_output =
+{
+	.open =		snd_cs46xx_midi_output_open,
+	.close =	snd_cs46xx_midi_output_close,
+	.trigger =	snd_cs46xx_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_cs46xx_midi_input =
+{
+	.open =		snd_cs46xx_midi_input_open,
+	.close =	snd_cs46xx_midi_input_close,
+	.trigger =	snd_cs46xx_midi_input_trigger,
+};
+
+int __devinit snd_cs46xx_midi(struct snd_cs46xx *chip, int device, struct snd_rawmidi **rrawmidi)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	if ((err = snd_rawmidi_new(chip->card, "CS46XX", device, 1, 1, &rmidi)) < 0)
+		return err;
+	strcpy(rmidi->name, "CS46XX");
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_cs46xx_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_cs46xx_midi_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = chip;
+	chip->rmidi = rmidi;
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	return 0;
+}
+
+
+/*
+ * gameport interface
+ */
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+
+static void snd_cs46xx_gameport_trigger(struct gameport *gameport)
+{
+	struct snd_cs46xx *chip = gameport_get_port_data(gameport);
+
+	if (snd_BUG_ON(!chip))
+		return;
+	snd_cs46xx_pokeBA0(chip, BA0_JSPT, 0xFF);  //outb(gameport->io, 0xFF);
+}
+
+static unsigned char snd_cs46xx_gameport_read(struct gameport *gameport)
+{
+	struct snd_cs46xx *chip = gameport_get_port_data(gameport);
+
+	if (snd_BUG_ON(!chip))
+		return 0;
+	return snd_cs46xx_peekBA0(chip, BA0_JSPT); //inb(gameport->io);
+}
+
+static int snd_cs46xx_gameport_cooked_read(struct gameport *gameport, int *axes, int *buttons)
+{
+	struct snd_cs46xx *chip = gameport_get_port_data(gameport);
+	unsigned js1, js2, jst;
+
+	if (snd_BUG_ON(!chip))
+		return 0;
+
+	js1 = snd_cs46xx_peekBA0(chip, BA0_JSC1);
+	js2 = snd_cs46xx_peekBA0(chip, BA0_JSC2);
+	jst = snd_cs46xx_peekBA0(chip, BA0_JSPT);
+	
+	*buttons = (~jst >> 4) & 0x0F; 
+	
+	axes[0] = ((js1 & JSC1_Y1V_MASK) >> JSC1_Y1V_SHIFT) & 0xFFFF;
+	axes[1] = ((js1 & JSC1_X1V_MASK) >> JSC1_X1V_SHIFT) & 0xFFFF;
+	axes[2] = ((js2 & JSC2_Y2V_MASK) >> JSC2_Y2V_SHIFT) & 0xFFFF;
+	axes[3] = ((js2 & JSC2_X2V_MASK) >> JSC2_X2V_SHIFT) & 0xFFFF;
+
+	for(jst=0;jst<4;++jst)
+		if(axes[jst]==0xFFFF) axes[jst] = -1;
+	return 0;
+}
+
+static int snd_cs46xx_gameport_open(struct gameport *gameport, int mode)
+{
+	switch (mode) {
+	case GAMEPORT_MODE_COOKED:
+		return 0;
+	case GAMEPORT_MODE_RAW:
+		return 0;
+	default:
+		return -1;
+	}
+	return 0;
+}
+
+int __devinit snd_cs46xx_gameport(struct snd_cs46xx *chip)
+{
+	struct gameport *gp;
+
+	chip->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "cs46xx: cannot allocate memory for gameport\n");
+		return -ENOMEM;
+	}
+
+	gameport_set_name(gp, "CS46xx Gameport");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
+	gameport_set_dev_parent(gp, &chip->pci->dev);
+	gameport_set_port_data(gp, chip);
+
+	gp->open = snd_cs46xx_gameport_open;
+	gp->read = snd_cs46xx_gameport_read;
+	gp->trigger = snd_cs46xx_gameport_trigger;
+	gp->cooked_read = snd_cs46xx_gameport_cooked_read;
+
+	snd_cs46xx_pokeBA0(chip, BA0_JSIO, 0xFF); // ?
+	snd_cs46xx_pokeBA0(chip, BA0_JSCTL, JSCTL_SP_MEDIUM_SLOW);
+
+	gameport_register_port(gp);
+
+	return 0;
+}
+
+static inline void snd_cs46xx_remove_gameport(struct snd_cs46xx *chip)
+{
+	if (chip->gameport) {
+		gameport_unregister_port(chip->gameport);
+		chip->gameport = NULL;
+	}
+}
+#else
+int __devinit snd_cs46xx_gameport(struct snd_cs46xx *chip) { return -ENOSYS; }
+static inline void snd_cs46xx_remove_gameport(struct snd_cs46xx *chip) { }
+#endif /* CONFIG_GAMEPORT */
+
+#ifdef CONFIG_PROC_FS
+/*
+ *  proc interface
+ */
+
+static ssize_t snd_cs46xx_io_read(struct snd_info_entry *entry,
+				  void *file_private_data,
+				  struct file *file, char __user *buf,
+				  size_t count, loff_t pos)
+{
+	struct snd_cs46xx_region *region = entry->private_data;
+	
+	if (copy_to_user_fromio(buf, region->remap_addr + pos, count))
+		return -EFAULT;
+	return count;
+}
+
+static struct snd_info_entry_ops snd_cs46xx_proc_io_ops = {
+	.read = snd_cs46xx_io_read,
+};
+
+static int __devinit snd_cs46xx_proc_init(struct snd_card *card, struct snd_cs46xx *chip)
+{
+	struct snd_info_entry *entry;
+	int idx;
+	
+	for (idx = 0; idx < 5; idx++) {
+		struct snd_cs46xx_region *region = &chip->region.idx[idx];
+		if (! snd_card_proc_new(card, region->name, &entry)) {
+			entry->content = SNDRV_INFO_CONTENT_DATA;
+			entry->private_data = chip;
+			entry->c.ops = &snd_cs46xx_proc_io_ops;
+			entry->size = region->size;
+			entry->mode = S_IFREG | S_IRUSR;
+		}
+	}
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	cs46xx_dsp_proc_init(card, chip);
+#endif
+	return 0;
+}
+
+static int snd_cs46xx_proc_done(struct snd_cs46xx *chip)
+{
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	cs46xx_dsp_proc_done(chip);
+#endif
+	return 0;
+}
+#else /* !CONFIG_PROC_FS */
+#define snd_cs46xx_proc_init(card, chip)
+#define snd_cs46xx_proc_done(chip)
+#endif
+
+/*
+ * stop the h/w
+ */
+static void snd_cs46xx_hw_stop(struct snd_cs46xx *chip)
+{
+	unsigned int tmp;
+
+	tmp = snd_cs46xx_peek(chip, BA1_PFIE);
+	tmp &= ~0x0000f03f;
+	tmp |=  0x00000010;
+	snd_cs46xx_poke(chip, BA1_PFIE, tmp);	/* playback interrupt disable */
+
+	tmp = snd_cs46xx_peek(chip, BA1_CIE);
+	tmp &= ~0x0000003f;
+	tmp |=  0x00000011;
+	snd_cs46xx_poke(chip, BA1_CIE, tmp);	/* capture interrupt disable */
+
+	/*
+         *  Stop playback DMA.
+	 */
+	tmp = snd_cs46xx_peek(chip, BA1_PCTL);
+	snd_cs46xx_poke(chip, BA1_PCTL, tmp & 0x0000ffff);
+
+	/*
+         *  Stop capture DMA.
+	 */
+	tmp = snd_cs46xx_peek(chip, BA1_CCTL);
+	snd_cs46xx_poke(chip, BA1_CCTL, tmp & 0xffff0000);
+
+	/*
+         *  Reset the processor.
+         */
+	snd_cs46xx_reset(chip);
+
+	snd_cs46xx_proc_stop(chip);
+
+	/*
+	 *  Power down the PLL.
+	 */
+	snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, 0);
+
+	/*
+	 *  Turn off the Processor by turning off the software clock enable flag in 
+	 *  the clock control register.
+	 */
+	tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1) & ~CLKCR1_SWCE;
+	snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp);
+}
+
+
+static int snd_cs46xx_free(struct snd_cs46xx *chip)
+{
+	int idx;
+
+	if (snd_BUG_ON(!chip))
+		return -EINVAL;
+
+	if (chip->active_ctrl)
+		chip->active_ctrl(chip, 1);
+
+	snd_cs46xx_remove_gameport(chip);
+
+	if (chip->amplifier_ctrl)
+		chip->amplifier_ctrl(chip, -chip->amplifier); /* force to off */
+	
+	snd_cs46xx_proc_done(chip);
+
+	if (chip->region.idx[0].resource)
+		snd_cs46xx_hw_stop(chip);
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+
+	if (chip->active_ctrl)
+		chip->active_ctrl(chip, -chip->amplifier);
+
+	for (idx = 0; idx < 5; idx++) {
+		struct snd_cs46xx_region *region = &chip->region.idx[idx];
+		if (region->remap_addr)
+			iounmap(region->remap_addr);
+		release_and_free_resource(region->resource);
+	}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	if (chip->dsp_spos_instance) {
+		cs46xx_dsp_spos_destroy(chip);
+		chip->dsp_spos_instance = NULL;
+	}
+#endif
+	
+#ifdef CONFIG_PM
+	kfree(chip->saved_regs);
+#endif
+
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_cs46xx_dev_free(struct snd_device *device)
+{
+	struct snd_cs46xx *chip = device->device_data;
+	return snd_cs46xx_free(chip);
+}
+
+/*
+ *  initialize chip
+ */
+static int snd_cs46xx_chip_init(struct snd_cs46xx *chip)
+{
+	int timeout;
+
+	/* 
+	 *  First, blast the clock control register to zero so that the PLL starts
+         *  out in a known state, and blast the master serial port control register
+         *  to zero so that the serial ports also start out in a known state.
+         */
+        snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, 0);
+        snd_cs46xx_pokeBA0(chip, BA0_SERMC1, 0);
+
+	/*
+	 *  If we are in AC97 mode, then we must set the part to a host controlled
+         *  AC-link.  Otherwise, we won't be able to bring up the link.
+         */        
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	snd_cs46xx_pokeBA0(chip, BA0_SERACC, SERACC_HSP | SERACC_CHIP_TYPE_2_0 | 
+			   SERACC_TWO_CODECS);	/* 2.00 dual codecs */
+	/* snd_cs46xx_pokeBA0(chip, BA0_SERACC, SERACC_HSP | SERACC_CHIP_TYPE_2_0); */ /* 2.00 codec */
+#else
+	snd_cs46xx_pokeBA0(chip, BA0_SERACC, SERACC_HSP | SERACC_CHIP_TYPE_1_03); /* 1.03 codec */
+#endif
+
+        /*
+         *  Drive the ARST# pin low for a minimum of 1uS (as defined in the AC97
+         *  spec) and then drive it high.  This is done for non AC97 modes since
+         *  there might be logic external to the CS461x that uses the ARST# line
+         *  for a reset.
+         */
+	snd_cs46xx_pokeBA0(chip, BA0_ACCTL, 0);
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, 0);
+#endif
+	udelay(50);
+	snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_RSTN);
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, ACCTL_RSTN);
+#endif
+    
+	/*
+	 *  The first thing we do here is to enable sync generation.  As soon
+	 *  as we start receiving bit clock, we'll start producing the SYNC
+	 *  signal.
+	 */
+	snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_ESYN | ACCTL_RSTN);
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, ACCTL_ESYN | ACCTL_RSTN);
+#endif
+
+	/*
+	 *  Now wait for a short while to allow the AC97 part to start
+	 *  generating bit clock (so we don't try to start the PLL without an
+	 *  input clock).
+	 */
+	mdelay(10);
+
+	/*
+	 *  Set the serial port timing configuration, so that
+	 *  the clock control circuit gets its clock from the correct place.
+	 */
+	snd_cs46xx_pokeBA0(chip, BA0_SERMC1, SERMC1_PTC_AC97);
+
+	/*
+	 *  Write the selected clock control setup to the hardware.  Do not turn on
+	 *  SWCE yet (if requested), so that the devices clocked by the output of
+	 *  PLL are not clocked until the PLL is stable.
+	 */
+	snd_cs46xx_pokeBA0(chip, BA0_PLLCC, PLLCC_LPF_1050_2780_KHZ | PLLCC_CDR_73_104_MHZ);
+	snd_cs46xx_pokeBA0(chip, BA0_PLLM, 0x3a);
+	snd_cs46xx_pokeBA0(chip, BA0_CLKCR2, CLKCR2_PDIVS_8);
+
+	/*
+	 *  Power up the PLL.
+	 */
+	snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, CLKCR1_PLLP);
+
+	/*
+         *  Wait until the PLL has stabilized.
+	 */
+	msleep(100);
+
+	/*
+	 *  Turn on clocking of the core so that we can setup the serial ports.
+	 */
+	snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, CLKCR1_PLLP | CLKCR1_SWCE);
+
+	/*
+	 * Enable FIFO  Host Bypass
+	 */
+	snd_cs46xx_pokeBA0(chip, BA0_SERBCF, SERBCF_HBP);
+
+	/*
+	 *  Fill the serial port FIFOs with silence.
+	 */
+	snd_cs46xx_clear_serial_FIFOs(chip);
+
+	/*
+	 *  Set the serial port FIFO pointer to the first sample in the FIFO.
+	 */
+	/* snd_cs46xx_pokeBA0(chip, BA0_SERBSP, 0); */
+
+	/*
+	 *  Write the serial port configuration to the part.  The master
+	 *  enable bit is not set until all other values have been written.
+	 */
+	snd_cs46xx_pokeBA0(chip, BA0_SERC1, SERC1_SO1F_AC97 | SERC1_SO1EN);
+	snd_cs46xx_pokeBA0(chip, BA0_SERC2, SERC2_SI1F_AC97 | SERC1_SO1EN);
+	snd_cs46xx_pokeBA0(chip, BA0_SERMC1, SERMC1_PTC_AC97 | SERMC1_MSPE);
+
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	snd_cs46xx_pokeBA0(chip, BA0_SERC7, SERC7_ASDI2EN);
+	snd_cs46xx_pokeBA0(chip, BA0_SERC3, 0);
+	snd_cs46xx_pokeBA0(chip, BA0_SERC4, 0);
+	snd_cs46xx_pokeBA0(chip, BA0_SERC5, 0);
+	snd_cs46xx_pokeBA0(chip, BA0_SERC6, 1);
+#endif
+
+	mdelay(5);
+
+
+	/*
+	 * Wait for the codec ready signal from the AC97 codec.
+	 */
+	timeout = 150;
+	while (timeout-- > 0) {
+		/*
+		 *  Read the AC97 status register to see if we've seen a CODEC READY
+		 *  signal from the AC97 codec.
+		 */
+		if (snd_cs46xx_peekBA0(chip, BA0_ACSTS) & ACSTS_CRDY)
+			goto ok1;
+		msleep(10);
+	}
+
+
+	snd_printk(KERN_ERR "create - never read codec ready from AC'97\n");
+	snd_printk(KERN_ERR "it is not probably bug, try to use CS4236 driver\n");
+	return -EIO;
+ ok1:
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	{
+		int count;
+		for (count = 0; count < 150; count++) {
+			/* First, we want to wait for a short time. */
+			udelay(25);
+        
+			if (snd_cs46xx_peekBA0(chip, BA0_ACSTS2) & ACSTS_CRDY)
+				break;
+		}
+
+		/*
+		 *  Make sure CODEC is READY.
+		 */
+		if (!(snd_cs46xx_peekBA0(chip, BA0_ACSTS2) & ACSTS_CRDY))
+			snd_printdd("cs46xx: never read card ready from secondary AC'97\n");
+	}
+#endif
+
+	/*
+	 *  Assert the vaid frame signal so that we can start sending commands
+	 *  to the AC97 codec.
+	 */
+	snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN);
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	snd_cs46xx_pokeBA0(chip, BA0_ACCTL2, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN);
+#endif
+
+
+	/*
+	 *  Wait until we've sampled input slots 3 and 4 as valid, meaning that
+	 *  the codec is pumping ADC data across the AC-link.
+	 */
+	timeout = 150;
+	while (timeout-- > 0) {
+		/*
+		 *  Read the input slot valid register and see if input slots 3 and
+		 *  4 are valid yet.
+		 */
+		if ((snd_cs46xx_peekBA0(chip, BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) == (ACISV_ISV3 | ACISV_ISV4))
+			goto ok2;
+		msleep(10);
+	}
+
+#ifndef CONFIG_SND_CS46XX_NEW_DSP
+	snd_printk(KERN_ERR "create - never read ISV3 & ISV4 from AC'97\n");
+	return -EIO;
+#else
+	/* This may happen on a cold boot with a Terratec SiXPack 5.1.
+	   Reloading the driver may help, if there's other soundcards 
+	   with the same problem I would like to know. (Benny) */
+
+	snd_printk(KERN_ERR "ERROR: snd-cs46xx: never read ISV3 & ISV4 from AC'97\n");
+	snd_printk(KERN_ERR "       Try reloading the ALSA driver, if you find something\n");
+        snd_printk(KERN_ERR "       broken or not working on your soundcard upon\n");
+	snd_printk(KERN_ERR "       this message please report to alsa-devel@alsa-project.org\n");
+
+	return -EIO;
+#endif
+ ok2:
+
+	/*
+	 *  Now, assert valid frame and the slot 3 and 4 valid bits.  This will
+	 *  commense the transfer of digital audio data to the AC97 codec.
+	 */
+
+	snd_cs46xx_pokeBA0(chip, BA0_ACOSV, ACOSV_SLV3 | ACOSV_SLV4);
+
+
+	/*
+	 *  Power down the DAC and ADC.  We will power them up (if) when we need
+	 *  them.
+	 */
+	/* snd_cs46xx_pokeBA0(chip, BA0_AC97_POWERDOWN, 0x300); */
+
+	/*
+	 *  Turn off the Processor by turning off the software clock enable flag in 
+	 *  the clock control register.
+	 */
+	/* tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1) & ~CLKCR1_SWCE; */
+	/* snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp); */
+
+	return 0;
+}
+
+/*
+ *  start and load DSP 
+ */
+
+static void cs46xx_enable_stream_irqs(struct snd_cs46xx *chip)
+{
+	unsigned int tmp;
+
+	snd_cs46xx_pokeBA0(chip, BA0_HICR, HICR_IEV | HICR_CHGM);
+        
+	tmp = snd_cs46xx_peek(chip, BA1_PFIE);
+	tmp &= ~0x0000f03f;
+	snd_cs46xx_poke(chip, BA1_PFIE, tmp);	/* playback interrupt enable */
+
+	tmp = snd_cs46xx_peek(chip, BA1_CIE);
+	tmp &= ~0x0000003f;
+	tmp |=  0x00000001;
+	snd_cs46xx_poke(chip, BA1_CIE, tmp);	/* capture interrupt enable */
+}
+
+int __devinit snd_cs46xx_start_dsp(struct snd_cs46xx *chip)
+{	
+	unsigned int tmp;
+	/*
+	 *  Reset the processor.
+	 */
+	snd_cs46xx_reset(chip);
+	/*
+	 *  Download the image to the processor.
+	 */
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+#if 0
+	if (cs46xx_dsp_load_module(chip, &cwcemb80_module) < 0) {
+		snd_printk(KERN_ERR "image download error\n");
+		return -EIO;
+	}
+#endif
+
+	if (cs46xx_dsp_load_module(chip, &cwc4630_module) < 0) {
+		snd_printk(KERN_ERR "image download error [cwc4630]\n");
+		return -EIO;
+	}
+
+	if (cs46xx_dsp_load_module(chip, &cwcasync_module) < 0) {
+		snd_printk(KERN_ERR "image download error [cwcasync]\n");
+		return -EIO;
+	}
+
+	if (cs46xx_dsp_load_module(chip, &cwcsnoop_module) < 0) {
+		snd_printk(KERN_ERR "image download error [cwcsnoop]\n");
+		return -EIO;
+	}
+
+	if (cs46xx_dsp_load_module(chip, &cwcbinhack_module) < 0) {
+		snd_printk(KERN_ERR "image download error [cwcbinhack]\n");
+		return -EIO;
+	}
+
+	if (cs46xx_dsp_load_module(chip, &cwcdma_module) < 0) {
+		snd_printk(KERN_ERR "image download error [cwcdma]\n");
+		return -EIO;
+	}
+
+	if (cs46xx_dsp_scb_and_task_init(chip) < 0)
+		return -EIO;
+#else
+	/* old image */
+	if (snd_cs46xx_download_image(chip) < 0) {
+		snd_printk(KERN_ERR "image download error\n");
+		return -EIO;
+	}
+
+	/*
+         *  Stop playback DMA.
+	 */
+	tmp = snd_cs46xx_peek(chip, BA1_PCTL);
+	chip->play_ctl = tmp & 0xffff0000;
+	snd_cs46xx_poke(chip, BA1_PCTL, tmp & 0x0000ffff);
+#endif
+
+	/*
+         *  Stop capture DMA.
+	 */
+	tmp = snd_cs46xx_peek(chip, BA1_CCTL);
+	chip->capt.ctl = tmp & 0x0000ffff;
+	snd_cs46xx_poke(chip, BA1_CCTL, tmp & 0xffff0000);
+
+	mdelay(5);
+
+	snd_cs46xx_set_play_sample_rate(chip, 8000);
+	snd_cs46xx_set_capture_sample_rate(chip, 8000);
+
+	snd_cs46xx_proc_start(chip);
+
+	cs46xx_enable_stream_irqs(chip);
+	
+#ifndef CONFIG_SND_CS46XX_NEW_DSP
+	/* set the attenuation to 0dB */ 
+	snd_cs46xx_poke(chip, BA1_PVOL, 0x80008000);
+	snd_cs46xx_poke(chip, BA1_CVOL, 0x80008000);
+#endif
+
+	return 0;
+}
+
+
+/*
+ *	AMP control - null AMP
+ */
+ 
+static void amp_none(struct snd_cs46xx *chip, int change)
+{	
+}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+static int voyetra_setup_eapd_slot(struct snd_cs46xx *chip)
+{
+	
+	u32 idx, valid_slots,tmp,powerdown = 0;
+	u16 modem_power,pin_config,logic_type;
+
+	snd_printdd ("cs46xx: cs46xx_setup_eapd_slot()+\n");
+
+	/*
+	 *  See if the devices are powered down.  If so, we must power them up first
+	 *  or they will not respond.
+	 */
+	tmp = snd_cs46xx_peekBA0(chip, BA0_CLKCR1);
+
+	if (!(tmp & CLKCR1_SWCE)) {
+		snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp | CLKCR1_SWCE);
+		powerdown = 1;
+	}
+
+	/*
+	 * Clear PRA.  The Bonzo chip will be used for GPIO not for modem
+	 * stuff.
+	 */
+	if(chip->nr_ac97_codecs != 2) {
+		snd_printk (KERN_ERR "cs46xx: cs46xx_setup_eapd_slot() - no secondary codec configured\n");
+		return -EINVAL;
+	}
+
+	modem_power = snd_cs46xx_codec_read (chip, 
+					     AC97_EXTENDED_MSTATUS,
+					     CS46XX_SECONDARY_CODEC_INDEX);
+	modem_power &=0xFEFF;
+
+	snd_cs46xx_codec_write(chip, 
+			       AC97_EXTENDED_MSTATUS, modem_power,
+			       CS46XX_SECONDARY_CODEC_INDEX);
+
+	/*
+	 * Set GPIO pin's 7 and 8 so that they are configured for output.
+	 */
+	pin_config = snd_cs46xx_codec_read (chip, 
+					    AC97_GPIO_CFG,
+					    CS46XX_SECONDARY_CODEC_INDEX);
+	pin_config &=0x27F;
+
+	snd_cs46xx_codec_write(chip, 
+			       AC97_GPIO_CFG, pin_config,
+			       CS46XX_SECONDARY_CODEC_INDEX);
+    
+	/*
+	 * Set GPIO pin's 7 and 8 so that they are compatible with CMOS logic.
+	 */
+
+	logic_type = snd_cs46xx_codec_read(chip, AC97_GPIO_POLARITY,
+					   CS46XX_SECONDARY_CODEC_INDEX);
+	logic_type &=0x27F; 
+
+	snd_cs46xx_codec_write (chip, AC97_GPIO_POLARITY, logic_type,
+				CS46XX_SECONDARY_CODEC_INDEX);
+
+	valid_slots = snd_cs46xx_peekBA0(chip, BA0_ACOSV);
+	valid_slots |= 0x200;
+	snd_cs46xx_pokeBA0(chip, BA0_ACOSV, valid_slots);
+
+	if ( cs46xx_wait_for_fifo(chip,1) ) {
+	  snd_printdd("FIFO is busy\n");
+	  
+	  return -EINVAL;
+	}
+
+	/*
+	 * Fill slots 12 with the correct value for the GPIO pins. 
+	 */
+	for(idx = 0x90; idx <= 0x9F; idx++) {
+		/*
+		 * Initialize the fifo so that bits 7 and 8 are on.
+		 *
+		 * Remember that the GPIO pins in bonzo are shifted by 4 bits to
+		 * the left.  0x1800 corresponds to bits 7 and 8.
+		 */
+		snd_cs46xx_pokeBA0(chip, BA0_SERBWP, 0x1800);
+
+		/*
+		 * Wait for command to complete
+		 */
+		if ( cs46xx_wait_for_fifo(chip,200) ) {
+			snd_printdd("failed waiting for FIFO at addr (%02X)\n",idx);
+
+			return -EINVAL;
+		}
+            
+		/*
+		 * Write the serial port FIFO index.
+		 */
+		snd_cs46xx_pokeBA0(chip, BA0_SERBAD, idx);
+      
+		/*
+		 * Tell the serial port to load the new value into the FIFO location.
+		 */
+		snd_cs46xx_pokeBA0(chip, BA0_SERBCM, SERBCM_WRC);
+	}
+
+	/* wait for last command to complete */
+	cs46xx_wait_for_fifo(chip,200);
+
+	/*
+	 *  Now, if we powered up the devices, then power them back down again.
+	 *  This is kinda ugly, but should never happen.
+	 */
+	if (powerdown)
+		snd_cs46xx_pokeBA0(chip, BA0_CLKCR1, tmp);
+
+	return 0;
+}
+#endif
+
+/*
+ *	Crystal EAPD mode
+ */
+ 
+static void amp_voyetra(struct snd_cs46xx *chip, int change)
+{
+	/* Manage the EAPD bit on the Crystal 4297 
+	   and the Analog AD1885 */
+	   
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	int old = chip->amplifier;
+#endif
+	int oval, val;
+	
+	chip->amplifier += change;
+	oval = snd_cs46xx_codec_read(chip, AC97_POWERDOWN,
+				     CS46XX_PRIMARY_CODEC_INDEX);
+	val = oval;
+	if (chip->amplifier) {
+		/* Turn the EAPD amp on */
+		val |= 0x8000;
+	} else {
+		/* Turn the EAPD amp off */
+		val &= ~0x8000;
+	}
+	if (val != oval) {
+		snd_cs46xx_codec_write(chip, AC97_POWERDOWN, val,
+				       CS46XX_PRIMARY_CODEC_INDEX);
+		if (chip->eapd_switch)
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->eapd_switch->id);
+	}
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	if (chip->amplifier && !old) {
+		voyetra_setup_eapd_slot(chip);
+	}
+#endif
+}
+
+static void hercules_init(struct snd_cs46xx *chip) 
+{
+	/* default: AMP off, and SPDIF input optical */
+	snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, EGPIODR_GPOE0);
+	snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, EGPIODR_GPOE0);
+}
+
+
+/*
+ *	Game Theatre XP card - EGPIO[2] is used to enable the external amp.
+ */ 
+static void amp_hercules(struct snd_cs46xx *chip, int change)
+{
+	int old = chip->amplifier;
+	int val1 = snd_cs46xx_peekBA0(chip, BA0_EGPIODR);
+	int val2 = snd_cs46xx_peekBA0(chip, BA0_EGPIOPTR);
+
+	chip->amplifier += change;
+	if (chip->amplifier && !old) {
+		snd_printdd ("Hercules amplifier ON\n");
+
+		snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, 
+				   EGPIODR_GPOE2 | val1);     /* enable EGPIO2 output */
+		snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, 
+				   EGPIOPTR_GPPT2 | val2);   /* open-drain on output */
+	} else if (old && !chip->amplifier) {
+		snd_printdd ("Hercules amplifier OFF\n");
+		snd_cs46xx_pokeBA0(chip, BA0_EGPIODR,  val1 & ~EGPIODR_GPOE2); /* disable */
+		snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, val2 & ~EGPIOPTR_GPPT2); /* disable */
+	}
+}
+
+static void voyetra_mixer_init (struct snd_cs46xx *chip)
+{
+	snd_printdd ("initializing Voyetra mixer\n");
+
+	/* Enable SPDIF out */
+	snd_cs46xx_pokeBA0(chip, BA0_EGPIODR, EGPIODR_GPOE0);
+	snd_cs46xx_pokeBA0(chip, BA0_EGPIOPTR, EGPIODR_GPOE0);
+}
+
+static void hercules_mixer_init (struct snd_cs46xx *chip)
+{
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	unsigned int idx;
+	int err;
+	struct snd_card *card = chip->card;
+#endif
+
+	/* set EGPIO to default */
+	hercules_init(chip);
+
+	snd_printdd ("initializing Hercules mixer\n");
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	if (chip->in_suspend)
+		return;
+
+	for (idx = 0 ; idx < ARRAY_SIZE(snd_hercules_controls); idx++) {
+		struct snd_kcontrol *kctl;
+
+		kctl = snd_ctl_new1(&snd_hercules_controls[idx], chip);
+		if ((err = snd_ctl_add(card, kctl)) < 0) {
+			printk (KERN_ERR "cs46xx: failed to initialize Hercules mixer (%d)\n",err);
+			break;
+		}
+	}
+#endif
+}
+
+
+#if 0
+/*
+ *	Untested
+ */
+ 
+static void amp_voyetra_4294(struct snd_cs46xx *chip, int change)
+{
+	chip->amplifier += change;
+
+	if (chip->amplifier) {
+		/* Switch the GPIO pins 7 and 8 to open drain */
+		snd_cs46xx_codec_write(chip, 0x4C,
+				       snd_cs46xx_codec_read(chip, 0x4C) & 0xFE7F);
+		snd_cs46xx_codec_write(chip, 0x4E,
+				       snd_cs46xx_codec_read(chip, 0x4E) | 0x0180);
+		/* Now wake the AMP (this might be backwards) */
+		snd_cs46xx_codec_write(chip, 0x54,
+				       snd_cs46xx_codec_read(chip, 0x54) & ~0x0180);
+	} else {
+		snd_cs46xx_codec_write(chip, 0x54,
+				       snd_cs46xx_codec_read(chip, 0x54) | 0x0180);
+	}
+}
+#endif
+
+
+/*
+ *	Handle the CLKRUN on a thinkpad. We must disable CLKRUN support
+ *	whenever we need to beat on the chip.
+ *
+ *	The original idea and code for this hack comes from David Kaiser at
+ *	Linuxcare. Perhaps one day Crystal will document their chips well
+ *	enough to make them useful.
+ */
+ 
+static void clkrun_hack(struct snd_cs46xx *chip, int change)
+{
+	u16 control, nval;
+	
+	if (!chip->acpi_port)
+		return;
+
+	chip->amplifier += change;
+	
+	/* Read ACPI port */	
+	nval = control = inw(chip->acpi_port + 0x10);
+
+	/* Flip CLKRUN off while running */
+	if (! chip->amplifier)
+		nval |= 0x2000;
+	else
+		nval &= ~0x2000;
+	if (nval != control)
+		outw(nval, chip->acpi_port + 0x10);
+}
+
+	
+/*
+ * detect intel piix4
+ */
+static void clkrun_init(struct snd_cs46xx *chip)
+{
+	struct pci_dev *pdev;
+	u8 pp;
+
+	chip->acpi_port = 0;
+	
+	pdev = pci_get_device(PCI_VENDOR_ID_INTEL,
+		PCI_DEVICE_ID_INTEL_82371AB_3, NULL);
+	if (pdev == NULL)
+		return;		/* Not a thinkpad thats for sure */
+
+	/* Find the control port */		
+	pci_read_config_byte(pdev, 0x41, &pp);
+	chip->acpi_port = pp << 8;
+	pci_dev_put(pdev);
+}
+
+
+/*
+ * Card subid table
+ */
+ 
+struct cs_card_type
+{
+	u16 vendor;
+	u16 id;
+	char *name;
+	void (*init)(struct snd_cs46xx *);
+	void (*amp)(struct snd_cs46xx *, int);
+	void (*active)(struct snd_cs46xx *, int);
+	void (*mixer_init)(struct snd_cs46xx *);
+};
+
+static struct cs_card_type __devinitdata cards[] = {
+	{
+		.vendor = 0x1489,
+		.id = 0x7001,
+		.name = "Genius Soundmaker 128 value",
+		/* nothing special */
+	},
+	{
+		.vendor = 0x5053,
+		.id = 0x3357,
+		.name = "Voyetra",
+		.amp = amp_voyetra,
+		.mixer_init = voyetra_mixer_init,
+	},
+	{
+		.vendor = 0x1071,
+		.id = 0x6003,
+		.name = "Mitac MI6020/21",
+		.amp = amp_voyetra,
+	},
+	/* Hercules Game Theatre XP */
+	{
+		.vendor = 0x14af, /* Guillemot Corporation */
+		.id = 0x0050,
+		.name = "Hercules Game Theatre XP",
+		.amp = amp_hercules,
+		.mixer_init = hercules_mixer_init,
+	},
+	{
+		.vendor = 0x1681,
+		.id = 0x0050,
+		.name = "Hercules Game Theatre XP",
+		.amp = amp_hercules,
+		.mixer_init = hercules_mixer_init,
+	},
+	{
+		.vendor = 0x1681,
+		.id = 0x0051,
+		.name = "Hercules Game Theatre XP",
+		.amp = amp_hercules,
+		.mixer_init = hercules_mixer_init,
+
+	},
+	{
+		.vendor = 0x1681,
+		.id = 0x0052,
+		.name = "Hercules Game Theatre XP",
+		.amp = amp_hercules,
+		.mixer_init = hercules_mixer_init,
+	},
+	{
+		.vendor = 0x1681,
+		.id = 0x0053,
+		.name = "Hercules Game Theatre XP",
+		.amp = amp_hercules,
+		.mixer_init = hercules_mixer_init,
+	},
+	{
+		.vendor = 0x1681,
+		.id = 0x0054,
+		.name = "Hercules Game Theatre XP",
+		.amp = amp_hercules,
+		.mixer_init = hercules_mixer_init,
+	},
+	/* Herculess Fortissimo */
+	{
+		.vendor = 0x1681,
+		.id = 0xa010,
+		.name = "Hercules Gamesurround Fortissimo II",
+	},
+	{
+		.vendor = 0x1681,
+		.id = 0xa011,
+		.name = "Hercules Gamesurround Fortissimo III 7.1",
+	},
+	/* Teratec */
+	{
+		.vendor = 0x153b,
+		.id = 0x112e,
+		.name = "Terratec DMX XFire 1024",
+	},
+	{
+		.vendor = 0x153b,
+		.id = 0x1136,
+		.name = "Terratec SiXPack 5.1",
+	},
+	/* Not sure if the 570 needs the clkrun hack */
+	{
+		.vendor = PCI_VENDOR_ID_IBM,
+		.id = 0x0132,
+		.name = "Thinkpad 570",
+		.init = clkrun_init,
+		.active = clkrun_hack,
+	},
+	{
+		.vendor = PCI_VENDOR_ID_IBM,
+		.id = 0x0153,
+		.name = "Thinkpad 600X/A20/T20",
+		.init = clkrun_init,
+		.active = clkrun_hack,
+	},
+	{
+		.vendor = PCI_VENDOR_ID_IBM,
+		.id = 0x1010,
+		.name = "Thinkpad 600E (unsupported)",
+	},
+	{} /* terminator */
+};
+
+
+/*
+ * APM support
+ */
+#ifdef CONFIG_PM
+static unsigned int saved_regs[] = {
+	BA0_ACOSV,
+	/*BA0_ASER_FADDR,*/
+	BA0_ASER_MASTER,
+	BA1_PVOL,
+	BA1_CVOL,
+};
+
+int snd_cs46xx_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_cs46xx *chip = card->private_data;
+	int i, amp_saved;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	chip->in_suspend = 1;
+	snd_pcm_suspend_all(chip->pcm);
+	// chip->ac97_powerdown = snd_cs46xx_codec_read(chip, AC97_POWER_CONTROL);
+	// chip->ac97_general_purpose = snd_cs46xx_codec_read(chip, BA0_AC97_GENERAL_PURPOSE);
+
+	snd_ac97_suspend(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]);
+	snd_ac97_suspend(chip->ac97[CS46XX_SECONDARY_CODEC_INDEX]);
+
+	/* save some registers */
+	for (i = 0; i < ARRAY_SIZE(saved_regs); i++)
+		chip->saved_regs[i] = snd_cs46xx_peekBA0(chip, saved_regs[i]);
+
+	amp_saved = chip->amplifier;
+	/* turn off amp */
+	chip->amplifier_ctrl(chip, -chip->amplifier);
+	snd_cs46xx_hw_stop(chip);
+	/* disable CLKRUN */
+	chip->active_ctrl(chip, -chip->amplifier);
+	chip->amplifier = amp_saved; /* restore the status */
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+int snd_cs46xx_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_cs46xx *chip = card->private_data;
+	int amp_saved;
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	int i;
+#endif
+	unsigned int tmp;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "cs46xx: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	amp_saved = chip->amplifier;
+	chip->amplifier = 0;
+	chip->active_ctrl(chip, 1); /* force to on */
+
+	snd_cs46xx_chip_init(chip);
+
+	snd_cs46xx_reset(chip);
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	cs46xx_dsp_resume(chip);
+	/* restore some registers */
+	for (i = 0; i < ARRAY_SIZE(saved_regs); i++)
+		snd_cs46xx_pokeBA0(chip, saved_regs[i], chip->saved_regs[i]);
+#else
+	snd_cs46xx_download_image(chip);
+#endif
+
+#if 0
+	snd_cs46xx_codec_write(chip, BA0_AC97_GENERAL_PURPOSE, 
+			       chip->ac97_general_purpose);
+	snd_cs46xx_codec_write(chip, AC97_POWER_CONTROL, 
+			       chip->ac97_powerdown);
+	mdelay(10);
+	snd_cs46xx_codec_write(chip, BA0_AC97_POWERDOWN,
+			       chip->ac97_powerdown);
+	mdelay(5);
+#endif
+
+	snd_ac97_resume(chip->ac97[CS46XX_PRIMARY_CODEC_INDEX]);
+	snd_ac97_resume(chip->ac97[CS46XX_SECONDARY_CODEC_INDEX]);
+
+	/*
+         *  Stop capture DMA.
+	 */
+	tmp = snd_cs46xx_peek(chip, BA1_CCTL);
+	chip->capt.ctl = tmp & 0x0000ffff;
+	snd_cs46xx_poke(chip, BA1_CCTL, tmp & 0xffff0000);
+
+	mdelay(5);
+
+	/* reset playback/capture */
+	snd_cs46xx_set_play_sample_rate(chip, 8000);
+	snd_cs46xx_set_capture_sample_rate(chip, 8000);
+	snd_cs46xx_proc_start(chip);
+
+	cs46xx_enable_stream_irqs(chip);
+
+	if (amp_saved)
+		chip->amplifier_ctrl(chip, 1); /* turn amp on */
+	else
+		chip->active_ctrl(chip, -1); /* disable CLKRUN */
+	chip->amplifier = amp_saved;
+	chip->in_suspend = 0;
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+
+/*
+ */
+
+int __devinit snd_cs46xx_create(struct snd_card *card,
+		      struct pci_dev * pci,
+		      int external_amp, int thinkpad,
+		      struct snd_cs46xx ** rchip)
+{
+	struct snd_cs46xx *chip;
+	int err, idx;
+	struct snd_cs46xx_region *region;
+	struct cs_card_type *cp;
+	u16 ss_card, ss_vendor;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_cs46xx_dev_free,
+	};
+	
+	*rchip = NULL;
+
+	/* enable PCI device */
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	spin_lock_init(&chip->reg_lock);
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	mutex_init(&chip->spos_mutex);
+#endif
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	chip->ba0_addr = pci_resource_start(pci, 0);
+	chip->ba1_addr = pci_resource_start(pci, 1);
+	if (chip->ba0_addr == 0 || chip->ba0_addr == (unsigned long)~0 ||
+	    chip->ba1_addr == 0 || chip->ba1_addr == (unsigned long)~0) {
+	    	snd_printk(KERN_ERR "wrong address(es) - ba0 = 0x%lx, ba1 = 0x%lx\n",
+			   chip->ba0_addr, chip->ba1_addr);
+	    	snd_cs46xx_free(chip);
+	    	return -ENOMEM;
+	}
+
+	region = &chip->region.name.ba0;
+	strcpy(region->name, "CS46xx_BA0");
+	region->base = chip->ba0_addr;
+	region->size = CS46XX_BA0_SIZE;
+
+	region = &chip->region.name.data0;
+	strcpy(region->name, "CS46xx_BA1_data0");
+	region->base = chip->ba1_addr + BA1_SP_DMEM0;
+	region->size = CS46XX_BA1_DATA0_SIZE;
+
+	region = &chip->region.name.data1;
+	strcpy(region->name, "CS46xx_BA1_data1");
+	region->base = chip->ba1_addr + BA1_SP_DMEM1;
+	region->size = CS46XX_BA1_DATA1_SIZE;
+
+	region = &chip->region.name.pmem;
+	strcpy(region->name, "CS46xx_BA1_pmem");
+	region->base = chip->ba1_addr + BA1_SP_PMEM;
+	region->size = CS46XX_BA1_PRG_SIZE;
+
+	region = &chip->region.name.reg;
+	strcpy(region->name, "CS46xx_BA1_reg");
+	region->base = chip->ba1_addr + BA1_SP_REG;
+	region->size = CS46XX_BA1_REG_SIZE;
+
+	/* set up amp and clkrun hack */
+	pci_read_config_word(pci, PCI_SUBSYSTEM_VENDOR_ID, &ss_vendor);
+	pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &ss_card);
+
+	for (cp = &cards[0]; cp->name; cp++) {
+		if (cp->vendor == ss_vendor && cp->id == ss_card) {
+			snd_printdd ("hack for %s enabled\n", cp->name);
+
+			chip->amplifier_ctrl = cp->amp;
+			chip->active_ctrl = cp->active;
+			chip->mixer_init = cp->mixer_init;
+
+			if (cp->init)
+				cp->init(chip);
+			break;
+		}
+	}
+
+	if (external_amp) {
+		snd_printk(KERN_INFO "Crystal EAPD support forced on.\n");
+		chip->amplifier_ctrl = amp_voyetra;
+	}
+
+	if (thinkpad) {
+		snd_printk(KERN_INFO "Activating CLKRUN hack for Thinkpad.\n");
+		chip->active_ctrl = clkrun_hack;
+		clkrun_init(chip);
+	}
+	
+	if (chip->amplifier_ctrl == NULL)
+		chip->amplifier_ctrl = amp_none;
+	if (chip->active_ctrl == NULL)
+		chip->active_ctrl = amp_none;
+
+	chip->active_ctrl(chip, 1); /* enable CLKRUN */
+
+	pci_set_master(pci);
+
+	for (idx = 0; idx < 5; idx++) {
+		region = &chip->region.idx[idx];
+		if ((region->resource = request_mem_region(region->base, region->size,
+							   region->name)) == NULL) {
+			snd_printk(KERN_ERR "unable to request memory region 0x%lx-0x%lx\n",
+				   region->base, region->base + region->size - 1);
+			snd_cs46xx_free(chip);
+			return -EBUSY;
+		}
+		region->remap_addr = ioremap_nocache(region->base, region->size);
+		if (region->remap_addr == NULL) {
+			snd_printk(KERN_ERR "%s ioremap problem\n", region->name);
+			snd_cs46xx_free(chip);
+			return -ENOMEM;
+		}
+	}
+
+	if (request_irq(pci->irq, snd_cs46xx_interrupt, IRQF_SHARED,
+			"CS46XX", chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_cs46xx_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+	chip->dsp_spos_instance = cs46xx_dsp_spos_create(chip);
+	if (chip->dsp_spos_instance == NULL) {
+		snd_cs46xx_free(chip);
+		return -ENOMEM;
+	}
+#endif
+
+	err = snd_cs46xx_chip_init(chip);
+	if (err < 0) {
+		snd_cs46xx_free(chip);
+		return err;
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_cs46xx_free(chip);
+		return err;
+	}
+	
+	snd_cs46xx_proc_init(card, chip);
+
+#ifdef CONFIG_PM
+	chip->saved_regs = kmalloc(sizeof(*chip->saved_regs) *
+				   ARRAY_SIZE(saved_regs), GFP_KERNEL);
+	if (!chip->saved_regs) {
+		snd_cs46xx_free(chip);
+		return -ENOMEM;
+	}
+#endif
+
+	chip->active_ctrl(chip, -1); /* disable CLKRUN */
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rchip = chip;
+	return 0;
+}
diff --git a/linux/sound/pci/cs46xx/cs46xx_lib.h b/linux/sound/pci/cs46xx/cs46xx_lib.h
new file mode 100644
index 0000000..b518949
--- /dev/null
+++ b/linux/sound/pci/cs46xx/cs46xx_lib.h
@@ -0,0 +1,210 @@
+/*
+ *  The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __CS46XX_LIB_H__
+#define __CS46XX_LIB_H__
+
+/*
+ *  constants
+ */
+
+#define CS46XX_BA0_SIZE		  0x1000
+#define CS46XX_BA1_DATA0_SIZE 0x3000
+#define CS46XX_BA1_DATA1_SIZE 0x3800
+#define CS46XX_BA1_PRG_SIZE	  0x7000
+#define CS46XX_BA1_REG_SIZE	  0x0100
+
+
+
+#ifdef CONFIG_SND_CS46XX_NEW_DSP
+#define CS46XX_MIN_PERIOD_SIZE 64
+#define CS46XX_MAX_PERIOD_SIZE 1024*1024
+#else
+#define CS46XX_MIN_PERIOD_SIZE 2048
+#define CS46XX_MAX_PERIOD_SIZE 2048
+#endif
+
+#define CS46XX_FRAGS 2
+/* #define CS46XX_BUFFER_SIZE CS46XX_MAX_PERIOD_SIZE * CS46XX_FRAGS */
+
+#define SCB_NO_PARENT             0
+#define SCB_ON_PARENT_NEXT_SCB    1
+#define SCB_ON_PARENT_SUBLIST_SCB 2
+
+/* 3*1024 parameter, 3.5*1024 sample, 2*3.5*1024 code */
+#define BA1_DWORD_SIZE		(13 * 1024 + 512)
+#define BA1_MEMORY_COUNT	3
+
+/*
+ *  common I/O routines
+ */
+
+static inline void snd_cs46xx_poke(struct snd_cs46xx *chip, unsigned long reg, unsigned int val)
+{
+	unsigned int bank = reg >> 16;
+	unsigned int offset = reg & 0xffff;
+
+	/*
+	if (bank == 0)
+		printk(KERN_DEBUG "snd_cs46xx_poke: %04X - %08X\n",
+		       reg >> 2,val);
+	*/
+	writel(val, chip->region.idx[bank+1].remap_addr + offset);
+}
+
+static inline unsigned int snd_cs46xx_peek(struct snd_cs46xx *chip, unsigned long reg)
+{
+	unsigned int bank = reg >> 16;
+	unsigned int offset = reg & 0xffff;
+	return readl(chip->region.idx[bank+1].remap_addr + offset);
+}
+
+static inline void snd_cs46xx_pokeBA0(struct snd_cs46xx *chip, unsigned long offset, unsigned int val)
+{
+	writel(val, chip->region.name.ba0.remap_addr + offset);
+}
+
+static inline unsigned int snd_cs46xx_peekBA0(struct snd_cs46xx *chip, unsigned long offset)
+{
+	return readl(chip->region.name.ba0.remap_addr + offset);
+}
+
+struct dsp_spos_instance *cs46xx_dsp_spos_create (struct snd_cs46xx * chip);
+void cs46xx_dsp_spos_destroy (struct snd_cs46xx * chip);
+int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * module);
+#ifdef CONFIG_PM
+int cs46xx_dsp_resume(struct snd_cs46xx * chip);
+#endif
+struct dsp_symbol_entry *cs46xx_dsp_lookup_symbol (struct snd_cs46xx * chip, char * symbol_name,
+						   int symbol_type);
+#ifdef CONFIG_PROC_FS
+int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip);
+int cs46xx_dsp_proc_done (struct snd_cs46xx *chip);
+#else
+#define cs46xx_dsp_proc_init(card, chip)
+#define cs46xx_dsp_proc_done(chip)
+#endif
+int cs46xx_dsp_scb_and_task_init (struct snd_cs46xx *chip);
+int snd_cs46xx_download (struct snd_cs46xx *chip, u32 *src, unsigned long offset,
+			 unsigned long len);
+int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip, unsigned long offset, unsigned long len);
+int cs46xx_dsp_enable_spdif_out (struct snd_cs46xx *chip);
+int cs46xx_dsp_enable_spdif_hw (struct snd_cs46xx *chip);
+int cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip);
+int cs46xx_dsp_enable_spdif_in (struct snd_cs46xx *chip);
+int cs46xx_dsp_disable_spdif_in (struct snd_cs46xx *chip);
+int cs46xx_dsp_enable_pcm_capture (struct snd_cs46xx *chip);
+int cs46xx_dsp_disable_pcm_capture (struct snd_cs46xx *chip);
+int cs46xx_dsp_enable_adc_capture (struct snd_cs46xx *chip);
+int cs46xx_dsp_disable_adc_capture (struct snd_cs46xx *chip);
+int cs46xx_poke_via_dsp (struct snd_cs46xx *chip, u32 address, u32 data);
+struct dsp_scb_descriptor * cs46xx_dsp_create_scb (struct snd_cs46xx *chip, char * name,
+						   u32 * scb_data, u32 dest);
+#ifdef CONFIG_PROC_FS
+void cs46xx_dsp_proc_free_scb_desc (struct dsp_scb_descriptor * scb);
+void cs46xx_dsp_proc_register_scb_desc (struct snd_cs46xx *chip,
+					struct dsp_scb_descriptor * scb);
+#else
+#define cs46xx_dsp_proc_free_scb_desc(scb)
+#define cs46xx_dsp_proc_register_scb_desc(chip, scb)
+#endif
+struct dsp_scb_descriptor * cs46xx_dsp_create_timing_master_scb (struct snd_cs46xx *chip);
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_codec_out_scb(struct snd_cs46xx * chip,
+				char * codec_name, u16 channel_disp, u16 fifo_addr,
+				u16 child_scb_addr, u32 dest,
+				struct dsp_scb_descriptor * parent_scb,
+				int scb_child_type);
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_codec_in_scb(struct snd_cs46xx * chip, char * codec_name,
+			       u16 channel_disp, u16 fifo_addr,
+			       u16 sample_buffer_addr, u32 dest,
+			       struct dsp_scb_descriptor * parent_scb,
+			       int scb_child_type);
+void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip,
+			    struct dsp_scb_descriptor * scb);
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_codec_in_scb(struct snd_cs46xx * chip, char * codec_name,
+			       u16 channel_disp, u16 fifo_addr,
+			       u16 sample_buffer_addr, u32 dest,
+			       struct dsp_scb_descriptor * parent_scb,
+			       int scb_child_type);
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_src_task_scb(struct snd_cs46xx * chip, char * scb_name,
+			       int sample_rate, u16 src_buffer_addr,
+			       u16 src_delay_buffer_addr, u32 dest,
+			       struct dsp_scb_descriptor * parent_scb,
+			       int scb_child_type, int pass_through);
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_mix_only_scb(struct snd_cs46xx * chip, char * scb_name,
+			       u16 mix_buffer_addr, u32 dest,
+			       struct dsp_scb_descriptor * parent_scb,
+			       int scb_child_type);
+
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_vari_decimate_scb(struct snd_cs46xx * chip, char * scb_name,
+				    u16 vari_buffer_addr0, u16 vari_buffer_addr1, u32 dest,
+				    struct dsp_scb_descriptor * parent_scb,
+				    int scb_child_type);
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_asynch_fg_rx_scb(struct snd_cs46xx * chip, char * scb_name,
+				   u32 dest, u16 hfg_scb_address, u16 asynch_buffer_address,
+				   struct dsp_scb_descriptor * parent_scb,
+				   int scb_child_type);
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_spio_write_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest,
+				 struct dsp_scb_descriptor * parent_scb,
+				 int scb_child_type);
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_mix_to_ostream_scb(struct snd_cs46xx * chip, char * scb_name,
+				     u16 mix_buffer_addr, u16 writeback_spb, u32 dest,
+				     struct dsp_scb_descriptor * parent_scb,
+				     int scb_child_type);
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_magic_snoop_scb(struct snd_cs46xx * chip, char * scb_name,
+				  u32 dest, u16 snoop_buffer_address,
+				  struct dsp_scb_descriptor * snoop_scb,
+				  struct dsp_scb_descriptor * parent_scb,
+				  int scb_child_type);
+struct dsp_pcm_channel_descriptor *
+cs46xx_dsp_create_pcm_channel (struct snd_cs46xx * chip, u32 sample_rate,
+			       void * private_data, u32 hw_dma_addr,
+			       int pcm_channel_id);
+void cs46xx_dsp_destroy_pcm_channel (struct snd_cs46xx * chip,
+				     struct dsp_pcm_channel_descriptor * pcm_channel);
+int cs46xx_dsp_pcm_unlink (struct snd_cs46xx * chip,
+			   struct dsp_pcm_channel_descriptor * pcm_channel);
+int cs46xx_dsp_pcm_link (struct snd_cs46xx * chip,
+			 struct dsp_pcm_channel_descriptor * pcm_channel);
+struct dsp_scb_descriptor *
+cs46xx_add_record_source (struct snd_cs46xx *chip, struct dsp_scb_descriptor * source,
+			  u16 addr, char * scb_name);
+int cs46xx_src_unlink(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src);
+int cs46xx_src_link(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src);
+int cs46xx_iec958_pre_open (struct snd_cs46xx *chip);
+int cs46xx_iec958_post_close (struct snd_cs46xx *chip);
+int cs46xx_dsp_pcm_channel_set_period (struct snd_cs46xx * chip,
+				       struct dsp_pcm_channel_descriptor * pcm_channel,
+				       int period_size);
+int cs46xx_dsp_pcm_ostream_set_period (struct snd_cs46xx * chip, int period_size);
+int cs46xx_dsp_set_dac_volume (struct snd_cs46xx * chip, u16 left, u16 right);
+int cs46xx_dsp_set_iec958_volume (struct snd_cs46xx * chip, u16 left, u16 right);
+#endif /* __CS46XX_LIB_H__ */
diff --git a/linux/sound/pci/cs46xx/dsp_spos.c b/linux/sound/pci/cs46xx/dsp_spos.c
new file mode 100644
index 0000000..e377287
--- /dev/null
+++ b/linux/sound/pci/cs46xx/dsp_spos.c
@@ -0,0 +1,2017 @@
+/*
+ *   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
+ *
+ */
+
+/*
+ * 2002-07 Benny Sjostrand benny@hostmobility.com
+ */
+
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/asoundef.h>
+#include <sound/cs46xx.h>
+
+#include "cs46xx_lib.h"
+#include "dsp_spos.h"
+
+static int cs46xx_dsp_async_init (struct snd_cs46xx *chip,
+				  struct dsp_scb_descriptor * fg_entry);
+
+static enum wide_opcode wide_opcodes[] = { 
+	WIDE_FOR_BEGIN_LOOP,
+	WIDE_FOR_BEGIN_LOOP2,
+	WIDE_COND_GOTO_ADDR,
+	WIDE_COND_GOTO_CALL,
+	WIDE_TBEQ_COND_GOTO_ADDR,
+	WIDE_TBEQ_COND_CALL_ADDR,
+	WIDE_TBEQ_NCOND_GOTO_ADDR,
+	WIDE_TBEQ_NCOND_CALL_ADDR,
+	WIDE_TBEQ_COND_GOTO1_ADDR,
+	WIDE_TBEQ_COND_CALL1_ADDR,
+	WIDE_TBEQ_NCOND_GOTOI_ADDR,
+	WIDE_TBEQ_NCOND_CALL1_ADDR
+};
+
+static int shadow_and_reallocate_code (struct snd_cs46xx * chip, u32 * data, u32 size,
+				       u32 overlay_begin_address)
+{
+	unsigned int i = 0, j, nreallocated = 0;
+	u32 hival,loval,address;
+	u32 mop_operands,mop_type,wide_op;
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if (snd_BUG_ON(size %2))
+		return -EINVAL;
+  
+	while (i < size) {
+		loval = data[i++];
+		hival = data[i++];
+
+		if (ins->code.offset > 0) {
+			mop_operands = (hival >> 6) & 0x03fff;
+			mop_type = mop_operands >> 10;
+      
+			/* check for wide type instruction */
+			if (mop_type == 0 &&
+			    (mop_operands & WIDE_LADD_INSTR_MASK) == 0 &&
+			    (mop_operands & WIDE_INSTR_MASK) != 0) {
+				wide_op = loval & 0x7f;
+				for (j = 0;j < ARRAY_SIZE(wide_opcodes); ++j) {
+					if (wide_opcodes[j] == wide_op) {
+						/* need to reallocate instruction */
+						address  = (hival & 0x00FFF) << 5;
+						address |=  loval >> 15;
+            
+						snd_printdd("handle_wideop[1]: %05x:%05x addr %04x\n",hival,loval,address);
+            
+						if ( !(address & 0x8000) ) {
+							address += (ins->code.offset / 2) - overlay_begin_address;
+						} else {
+							snd_printdd("handle_wideop[1]: ROM symbol not reallocated\n");
+						}
+            
+						hival &= 0xFF000;
+						loval &= 0x07FFF;
+            
+						hival |= ( (address >> 5)  & 0x00FFF);
+						loval |= ( (address << 15) & 0xF8000);
+            
+						address  = (hival & 0x00FFF) << 5;
+						address |=  loval >> 15;
+            
+						snd_printdd("handle_wideop:[2] %05x:%05x addr %04x\n",hival,loval,address);            
+						nreallocated ++;
+					} /* wide_opcodes[j] == wide_op */
+				} /* for */
+			} /* mod_type == 0 ... */
+		} /* ins->code.offset > 0 */
+
+		ins->code.data[ins->code.size++] = loval;
+		ins->code.data[ins->code.size++] = hival;
+	}
+
+	snd_printdd("dsp_spos: %d instructions reallocated\n",nreallocated);
+	return nreallocated;
+}
+
+static struct dsp_segment_desc * get_segment_desc (struct dsp_module_desc * module, int seg_type)
+{
+	int i;
+	for (i = 0;i < module->nsegments; ++i) {
+		if (module->segments[i].segment_type == seg_type) {
+			return (module->segments + i);
+		}
+	}
+
+	return NULL;
+};
+
+static int find_free_symbol_index (struct dsp_spos_instance * ins)
+{
+	int index = ins->symbol_table.nsymbols,i;
+
+	for (i = ins->symbol_table.highest_frag_index; i < ins->symbol_table.nsymbols; ++i) {
+		if (ins->symbol_table.symbols[i].deleted) {
+			index = i;
+			break;
+		}
+	}
+
+	return index;
+}
+
+static int add_symbols (struct snd_cs46xx * chip, struct dsp_module_desc * module)
+{
+	int i;
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if (module->symbol_table.nsymbols > 0) {
+		if (!strcmp(module->symbol_table.symbols[0].symbol_name, "OVERLAYBEGINADDRESS") &&
+		    module->symbol_table.symbols[0].symbol_type == SYMBOL_CONSTANT ) {
+			module->overlay_begin_address = module->symbol_table.symbols[0].address;
+		}
+	}
+
+	for (i = 0;i < module->symbol_table.nsymbols; ++i) {
+		if (ins->symbol_table.nsymbols == (DSP_MAX_SYMBOLS - 1)) {
+			snd_printk(KERN_ERR "dsp_spos: symbol table is full\n");
+			return -ENOMEM;
+		}
+
+
+		if (cs46xx_dsp_lookup_symbol(chip,
+					     module->symbol_table.symbols[i].symbol_name,
+					     module->symbol_table.symbols[i].symbol_type) == NULL) {
+
+			ins->symbol_table.symbols[ins->symbol_table.nsymbols] = module->symbol_table.symbols[i];
+			ins->symbol_table.symbols[ins->symbol_table.nsymbols].address += ((ins->code.offset / 2) - module->overlay_begin_address);
+			ins->symbol_table.symbols[ins->symbol_table.nsymbols].module = module;
+			ins->symbol_table.symbols[ins->symbol_table.nsymbols].deleted = 0;
+
+			if (ins->symbol_table.nsymbols > ins->symbol_table.highest_frag_index) 
+				ins->symbol_table.highest_frag_index = ins->symbol_table.nsymbols;
+
+			ins->symbol_table.nsymbols++;
+		} else {
+          /* if (0) printk ("dsp_spos: symbol <%s> duplicated, probably nothing wrong with that (Cirrus?)\n",
+                             module->symbol_table.symbols[i].symbol_name); */
+		}
+	}
+
+	return 0;
+}
+
+static struct dsp_symbol_entry *
+add_symbol (struct snd_cs46xx * chip, char * symbol_name, u32 address, int type)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_symbol_entry * symbol = NULL;
+	int index;
+
+	if (ins->symbol_table.nsymbols == (DSP_MAX_SYMBOLS - 1)) {
+		snd_printk(KERN_ERR "dsp_spos: symbol table is full\n");
+		return NULL;
+	}
+  
+	if (cs46xx_dsp_lookup_symbol(chip,
+				     symbol_name,
+				     type) != NULL) {
+		snd_printk(KERN_ERR "dsp_spos: symbol <%s> duplicated\n", symbol_name);
+		return NULL;
+	}
+
+	index = find_free_symbol_index (ins);
+
+	strcpy (ins->symbol_table.symbols[index].symbol_name, symbol_name);
+	ins->symbol_table.symbols[index].address = address;
+	ins->symbol_table.symbols[index].symbol_type = type;
+	ins->symbol_table.symbols[index].module = NULL;
+	ins->symbol_table.symbols[index].deleted = 0;
+	symbol = (ins->symbol_table.symbols + index);
+
+	if (index > ins->symbol_table.highest_frag_index) 
+		ins->symbol_table.highest_frag_index = index;
+
+	if (index == ins->symbol_table.nsymbols)
+		ins->symbol_table.nsymbols++; /* no frag. in list */
+
+	return symbol;
+}
+
+struct dsp_spos_instance *cs46xx_dsp_spos_create (struct snd_cs46xx * chip)
+{
+	struct dsp_spos_instance * ins = kzalloc(sizeof(struct dsp_spos_instance), GFP_KERNEL);
+
+	if (ins == NULL)
+		return NULL;
+
+	/* better to use vmalloc for this big table */
+	ins->symbol_table.symbols = vmalloc(sizeof(struct dsp_symbol_entry) *
+					    DSP_MAX_SYMBOLS);
+	ins->code.data = kmalloc(DSP_CODE_BYTE_SIZE, GFP_KERNEL);
+	ins->modules = kmalloc(sizeof(struct dsp_module_desc) * DSP_MAX_MODULES, GFP_KERNEL);
+	if (!ins->symbol_table.symbols || !ins->code.data || !ins->modules) {
+		cs46xx_dsp_spos_destroy(chip);
+		goto error;
+	}
+	ins->symbol_table.nsymbols = 0;
+	ins->symbol_table.highest_frag_index = 0;
+	ins->code.offset = 0;
+	ins->code.size = 0;
+	ins->nscb = 0;
+	ins->ntask = 0;
+	ins->nmodules = 0;
+
+	/* default SPDIF input sample rate
+	   to 48000 khz */
+	ins->spdif_in_sample_rate = 48000;
+
+	/* maximize volume */
+	ins->dac_volume_right = 0x8000;
+	ins->dac_volume_left = 0x8000;
+	ins->spdif_input_volume_right = 0x8000;
+	ins->spdif_input_volume_left = 0x8000;
+
+	/* set left and right validity bits and
+	   default channel status */
+	ins->spdif_csuv_default =
+		ins->spdif_csuv_stream =
+	 /* byte 0 */  ((unsigned int)_wrap_all_bits(  (SNDRV_PCM_DEFAULT_CON_SPDIF        & 0xff)) << 24) |
+	 /* byte 1 */  ((unsigned int)_wrap_all_bits( ((SNDRV_PCM_DEFAULT_CON_SPDIF >> 8) & 0xff)) << 16) |
+	 /* byte 3 */   (unsigned int)_wrap_all_bits(  (SNDRV_PCM_DEFAULT_CON_SPDIF >> 24) & 0xff) |
+	 /* left and right validity bits */ (1 << 13) | (1 << 12);
+
+	return ins;
+
+error:
+	kfree(ins->modules);
+	kfree(ins->code.data);
+	vfree(ins->symbol_table.symbols);
+	kfree(ins);
+	return NULL;
+}
+
+void  cs46xx_dsp_spos_destroy (struct snd_cs46xx * chip)
+{
+	int i;
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if (snd_BUG_ON(!ins))
+		return;
+
+	mutex_lock(&chip->spos_mutex);
+	for (i = 0; i < ins->nscb; ++i) {
+		if (ins->scbs[i].deleted) continue;
+
+		cs46xx_dsp_proc_free_scb_desc ( (ins->scbs + i) );
+#ifdef CONFIG_PM
+		kfree(ins->scbs[i].data);
+#endif
+	}
+
+	kfree(ins->code.data);
+	vfree(ins->symbol_table.symbols);
+	kfree(ins->modules);
+	kfree(ins);
+	mutex_unlock(&chip->spos_mutex);
+}
+
+static int dsp_load_parameter(struct snd_cs46xx *chip,
+			      struct dsp_segment_desc *parameter)
+{
+	u32 doffset, dsize;
+
+	if (!parameter) {
+		snd_printdd("dsp_spos: module got no parameter segment\n");
+		return 0;
+	}
+
+	doffset = (parameter->offset * 4 + DSP_PARAMETER_BYTE_OFFSET);
+	dsize   = parameter->size * 4;
+
+	snd_printdd("dsp_spos: "
+		    "downloading parameter data to chip (%08x-%08x)\n",
+		    doffset,doffset + dsize);
+	if (snd_cs46xx_download (chip, parameter->data, doffset, dsize)) {
+		snd_printk(KERN_ERR "dsp_spos: "
+			   "failed to download parameter data to DSP\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int dsp_load_sample(struct snd_cs46xx *chip,
+			   struct dsp_segment_desc *sample)
+{
+	u32 doffset, dsize;
+
+	if (!sample) {
+		snd_printdd("dsp_spos: module got no sample segment\n");
+		return 0;
+	}
+
+	doffset = (sample->offset * 4  + DSP_SAMPLE_BYTE_OFFSET);
+	dsize   =  sample->size * 4;
+
+	snd_printdd("dsp_spos: downloading sample data to chip (%08x-%08x)\n",
+		    doffset,doffset + dsize);
+
+	if (snd_cs46xx_download (chip,sample->data,doffset,dsize)) {
+		snd_printk(KERN_ERR "dsp_spos: failed to sample data to DSP\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * module)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_segment_desc * code = get_segment_desc (module,SEGTYPE_SP_PROGRAM);
+	u32 doffset, dsize;
+	int err;
+
+	if (ins->nmodules == DSP_MAX_MODULES - 1) {
+		snd_printk(KERN_ERR "dsp_spos: to many modules loaded into DSP\n");
+		return -ENOMEM;
+	}
+
+	snd_printdd("dsp_spos: loading module %s into DSP\n", module->module_name);
+  
+	if (ins->nmodules == 0) {
+		snd_printdd("dsp_spos: clearing parameter area\n");
+		snd_cs46xx_clear_BA1(chip, DSP_PARAMETER_BYTE_OFFSET, DSP_PARAMETER_BYTE_SIZE);
+	}
+  
+	err = dsp_load_parameter(chip, get_segment_desc(module,
+							SEGTYPE_SP_PARAMETER));
+	if (err < 0)
+		return err;
+
+	if (ins->nmodules == 0) {
+		snd_printdd("dsp_spos: clearing sample area\n");
+		snd_cs46xx_clear_BA1(chip, DSP_SAMPLE_BYTE_OFFSET, DSP_SAMPLE_BYTE_SIZE);
+	}
+
+	err = dsp_load_sample(chip, get_segment_desc(module,
+						     SEGTYPE_SP_SAMPLE));
+	if (err < 0)
+		return err;
+
+	if (ins->nmodules == 0) {
+		snd_printdd("dsp_spos: clearing code area\n");
+		snd_cs46xx_clear_BA1(chip, DSP_CODE_BYTE_OFFSET, DSP_CODE_BYTE_SIZE);
+	}
+
+	if (code == NULL) {
+		snd_printdd("dsp_spos: module got no code segment\n");
+	} else {
+		if (ins->code.offset + code->size > DSP_CODE_BYTE_SIZE) {
+			snd_printk(KERN_ERR "dsp_spos: no space available in DSP\n");
+			return -ENOMEM;
+		}
+
+		module->load_address = ins->code.offset;
+		module->overlay_begin_address = 0x000;
+
+		/* if module has a code segment it must have
+		   symbol table */
+		if (snd_BUG_ON(!module->symbol_table.symbols))
+			return -ENOMEM;
+		if (add_symbols(chip,module)) {
+			snd_printk(KERN_ERR "dsp_spos: failed to load symbol table\n");
+			return -ENOMEM;
+		}
+    
+		doffset = (code->offset * 4 + ins->code.offset * 4 + DSP_CODE_BYTE_OFFSET);
+		dsize   = code->size * 4;
+		snd_printdd("dsp_spos: downloading code to chip (%08x-%08x)\n",
+			    doffset,doffset + dsize);   
+
+		module->nfixups = shadow_and_reallocate_code(chip,code->data,code->size,module->overlay_begin_address);
+
+		if (snd_cs46xx_download (chip,(ins->code.data + ins->code.offset),doffset,dsize)) {
+			snd_printk(KERN_ERR "dsp_spos: failed to download code to DSP\n");
+			return -EINVAL;
+		}
+
+		ins->code.offset += code->size;
+	}
+
+	/* NOTE: module segments and symbol table must be
+	   statically allocated. Case that module data is
+	   not generated by the ospparser */
+	ins->modules[ins->nmodules] = *module;
+	ins->nmodules++;
+
+	return 0;
+}
+
+struct dsp_symbol_entry *
+cs46xx_dsp_lookup_symbol (struct snd_cs46xx * chip, char * symbol_name, int symbol_type)
+{
+	int i;
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	for ( i = 0; i < ins->symbol_table.nsymbols; ++i ) {
+
+		if (ins->symbol_table.symbols[i].deleted)
+			continue;
+
+		if (!strcmp(ins->symbol_table.symbols[i].symbol_name,symbol_name) &&
+		    ins->symbol_table.symbols[i].symbol_type == symbol_type) {
+			return (ins->symbol_table.symbols + i);
+		}
+	}
+
+#if 0
+	printk ("dsp_spos: symbol <%s> type %02x not found\n",
+		symbol_name,symbol_type);
+#endif
+
+	return NULL;
+}
+
+
+#ifdef CONFIG_PROC_FS
+static struct dsp_symbol_entry *
+cs46xx_dsp_lookup_symbol_addr (struct snd_cs46xx * chip, u32 address, int symbol_type)
+{
+	int i;
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	for ( i = 0; i < ins->symbol_table.nsymbols; ++i ) {
+
+		if (ins->symbol_table.symbols[i].deleted)
+			continue;
+
+		if (ins->symbol_table.symbols[i].address == address &&
+		    ins->symbol_table.symbols[i].symbol_type == symbol_type) {
+			return (ins->symbol_table.symbols + i);
+		}
+	}
+
+
+	return NULL;
+}
+
+
+static void cs46xx_dsp_proc_symbol_table_read (struct snd_info_entry *entry,
+					       struct snd_info_buffer *buffer)
+{
+	struct snd_cs46xx *chip = entry->private_data;
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	int i;
+
+	snd_iprintf(buffer, "SYMBOLS:\n");
+	for ( i = 0; i < ins->symbol_table.nsymbols; ++i ) {
+		char *module_str = "system";
+
+		if (ins->symbol_table.symbols[i].deleted)
+			continue;
+
+		if (ins->symbol_table.symbols[i].module != NULL) {
+			module_str = ins->symbol_table.symbols[i].module->module_name;
+		}
+
+    
+		snd_iprintf(buffer, "%04X <%02X> %s [%s]\n",
+			    ins->symbol_table.symbols[i].address,
+			    ins->symbol_table.symbols[i].symbol_type,
+			    ins->symbol_table.symbols[i].symbol_name,
+			    module_str);    
+	}
+}
+
+
+static void cs46xx_dsp_proc_modules_read (struct snd_info_entry *entry,
+					  struct snd_info_buffer *buffer)
+{
+	struct snd_cs46xx *chip = entry->private_data;
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	int i,j;
+
+	mutex_lock(&chip->spos_mutex);
+	snd_iprintf(buffer, "MODULES:\n");
+	for ( i = 0; i < ins->nmodules; ++i ) {
+		snd_iprintf(buffer, "\n%s:\n", ins->modules[i].module_name);
+		snd_iprintf(buffer, "   %d symbols\n", ins->modules[i].symbol_table.nsymbols);
+		snd_iprintf(buffer, "   %d fixups\n", ins->modules[i].nfixups);
+
+		for (j = 0; j < ins->modules[i].nsegments; ++ j) {
+			struct dsp_segment_desc * desc = (ins->modules[i].segments + j);
+			snd_iprintf(buffer, "   segment %02x offset %08x size %08x\n",
+				    desc->segment_type,desc->offset, desc->size);
+		}
+	}
+	mutex_unlock(&chip->spos_mutex);
+}
+
+static void cs46xx_dsp_proc_task_tree_read (struct snd_info_entry *entry,
+					    struct snd_info_buffer *buffer)
+{
+	struct snd_cs46xx *chip = entry->private_data;
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	int i, j, col;
+	void __iomem *dst = chip->region.idx[1].remap_addr + DSP_PARAMETER_BYTE_OFFSET;
+
+	mutex_lock(&chip->spos_mutex);
+	snd_iprintf(buffer, "TASK TREES:\n");
+	for ( i = 0; i < ins->ntask; ++i) {
+		snd_iprintf(buffer,"\n%04x %s:\n",ins->tasks[i].address,ins->tasks[i].task_name);
+
+		for (col = 0,j = 0;j < ins->tasks[i].size; j++,col++) {
+			u32 val;
+			if (col == 4) {
+				snd_iprintf(buffer,"\n");
+				col = 0;
+			}
+			val = readl(dst + (ins->tasks[i].address + j) * sizeof(u32));
+			snd_iprintf(buffer,"%08x ",val);
+		}
+	}
+
+	snd_iprintf(buffer,"\n");  
+	mutex_unlock(&chip->spos_mutex);
+}
+
+static void cs46xx_dsp_proc_scb_read (struct snd_info_entry *entry,
+				      struct snd_info_buffer *buffer)
+{
+	struct snd_cs46xx *chip = entry->private_data;
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	int i;
+
+	mutex_lock(&chip->spos_mutex);
+	snd_iprintf(buffer, "SCB's:\n");
+	for ( i = 0; i < ins->nscb; ++i) {
+		if (ins->scbs[i].deleted)
+			continue;
+		snd_iprintf(buffer,"\n%04x %s:\n\n",ins->scbs[i].address,ins->scbs[i].scb_name);
+
+		if (ins->scbs[i].parent_scb_ptr != NULL) {
+			snd_iprintf(buffer,"parent [%s:%04x] ", 
+				    ins->scbs[i].parent_scb_ptr->scb_name,
+				    ins->scbs[i].parent_scb_ptr->address);
+		} else snd_iprintf(buffer,"parent [none] ");
+
+		snd_iprintf(buffer,"sub_list_ptr [%s:%04x]\nnext_scb_ptr [%s:%04x]  task_entry [%s:%04x]\n",
+			    ins->scbs[i].sub_list_ptr->scb_name,
+			    ins->scbs[i].sub_list_ptr->address,
+			    ins->scbs[i].next_scb_ptr->scb_name,
+			    ins->scbs[i].next_scb_ptr->address,
+			    ins->scbs[i].task_entry->symbol_name,
+			    ins->scbs[i].task_entry->address);
+	}
+
+	snd_iprintf(buffer,"\n");
+	mutex_unlock(&chip->spos_mutex);
+}
+
+static void cs46xx_dsp_proc_parameter_dump_read (struct snd_info_entry *entry,
+						 struct snd_info_buffer *buffer)
+{
+	struct snd_cs46xx *chip = entry->private_data;
+	/*struct dsp_spos_instance * ins = chip->dsp_spos_instance; */
+	unsigned int i, col = 0;
+	void __iomem *dst = chip->region.idx[1].remap_addr + DSP_PARAMETER_BYTE_OFFSET;
+	struct dsp_symbol_entry * symbol; 
+
+	for (i = 0;i < DSP_PARAMETER_BYTE_SIZE; i += sizeof(u32),col ++) {
+		if (col == 4) {
+			snd_iprintf(buffer,"\n");
+			col = 0;
+		}
+
+		if ( (symbol = cs46xx_dsp_lookup_symbol_addr (chip,i / sizeof(u32), SYMBOL_PARAMETER)) != NULL) {
+			col = 0;
+			snd_iprintf (buffer,"\n%s:\n",symbol->symbol_name);
+		}
+
+		if (col == 0) {
+			snd_iprintf(buffer, "%04X ", i / (unsigned int)sizeof(u32));
+		}
+
+		snd_iprintf(buffer,"%08X ",readl(dst + i));
+	}
+}
+
+static void cs46xx_dsp_proc_sample_dump_read (struct snd_info_entry *entry,
+					      struct snd_info_buffer *buffer)
+{
+	struct snd_cs46xx *chip = entry->private_data;
+	int i,col = 0;
+	void __iomem *dst = chip->region.idx[2].remap_addr;
+
+	snd_iprintf(buffer,"PCMREADER:\n");
+	for (i = PCM_READER_BUF1;i < PCM_READER_BUF1 + 0x30; i += sizeof(u32),col ++) {
+		if (col == 4) {
+			snd_iprintf(buffer,"\n");
+			col = 0;
+		}
+
+		if (col == 0) {
+			snd_iprintf(buffer, "%04X ",i);
+		}
+
+		snd_iprintf(buffer,"%08X ",readl(dst + i));
+	}
+
+	snd_iprintf(buffer,"\nMIX_SAMPLE_BUF1:\n");
+
+	col = 0;
+	for (i = MIX_SAMPLE_BUF1;i < MIX_SAMPLE_BUF1 + 0x40; i += sizeof(u32),col ++) {
+		if (col == 4) {
+			snd_iprintf(buffer,"\n");
+			col = 0;
+		}
+
+		if (col == 0) {
+			snd_iprintf(buffer, "%04X ",i);
+		}
+
+		snd_iprintf(buffer,"%08X ",readl(dst + i));
+	}
+
+	snd_iprintf(buffer,"\nSRC_TASK_SCB1:\n");
+	col = 0;
+	for (i = 0x2480 ; i < 0x2480 + 0x40 ; i += sizeof(u32),col ++) {
+		if (col == 4) {
+			snd_iprintf(buffer,"\n");
+			col = 0;
+		}
+		
+		if (col == 0) {
+			snd_iprintf(buffer, "%04X ",i);
+		}
+
+		snd_iprintf(buffer,"%08X ",readl(dst + i));
+	}
+
+
+	snd_iprintf(buffer,"\nSPDIFO_BUFFER:\n");
+	col = 0;
+	for (i = SPDIFO_IP_OUTPUT_BUFFER1;i < SPDIFO_IP_OUTPUT_BUFFER1 + 0x30; i += sizeof(u32),col ++) {
+		if (col == 4) {
+			snd_iprintf(buffer,"\n");
+			col = 0;
+		}
+
+		if (col == 0) {
+			snd_iprintf(buffer, "%04X ",i);
+		}
+
+		snd_iprintf(buffer,"%08X ",readl(dst + i));
+	}
+
+	snd_iprintf(buffer,"\n...\n");
+	col = 0;
+
+	for (i = SPDIFO_IP_OUTPUT_BUFFER1+0xD0;i < SPDIFO_IP_OUTPUT_BUFFER1 + 0x110; i += sizeof(u32),col ++) {
+		if (col == 4) {
+			snd_iprintf(buffer,"\n");
+			col = 0;
+		}
+
+		if (col == 0) {
+			snd_iprintf(buffer, "%04X ",i);
+		}
+
+		snd_iprintf(buffer,"%08X ",readl(dst + i));
+	}
+
+
+	snd_iprintf(buffer,"\nOUTPUT_SNOOP:\n");
+	col = 0;
+	for (i = OUTPUT_SNOOP_BUFFER;i < OUTPUT_SNOOP_BUFFER + 0x40; i += sizeof(u32),col ++) {
+		if (col == 4) {
+			snd_iprintf(buffer,"\n");
+			col = 0;
+		}
+
+		if (col == 0) {
+			snd_iprintf(buffer, "%04X ",i);
+		}
+
+		snd_iprintf(buffer,"%08X ",readl(dst + i));
+	}
+
+	snd_iprintf(buffer,"\nCODEC_INPUT_BUF1: \n");
+	col = 0;
+	for (i = CODEC_INPUT_BUF1;i < CODEC_INPUT_BUF1 + 0x40; i += sizeof(u32),col ++) {
+		if (col == 4) {
+			snd_iprintf(buffer,"\n");
+			col = 0;
+		}
+
+		if (col == 0) {
+			snd_iprintf(buffer, "%04X ",i);
+		}
+
+		snd_iprintf(buffer,"%08X ",readl(dst + i));
+	}
+#if 0
+	snd_iprintf(buffer,"\nWRITE_BACK_BUF1: \n");
+	col = 0;
+	for (i = WRITE_BACK_BUF1;i < WRITE_BACK_BUF1 + 0x40; i += sizeof(u32),col ++) {
+		if (col == 4) {
+			snd_iprintf(buffer,"\n");
+			col = 0;
+		}
+
+		if (col == 0) {
+			snd_iprintf(buffer, "%04X ",i);
+		}
+
+		snd_iprintf(buffer,"%08X ",readl(dst + i));
+	}
+#endif
+
+	snd_iprintf(buffer,"\nSPDIFI_IP_OUTPUT_BUFFER1: \n");
+	col = 0;
+	for (i = SPDIFI_IP_OUTPUT_BUFFER1;i < SPDIFI_IP_OUTPUT_BUFFER1 + 0x80; i += sizeof(u32),col ++) {
+		if (col == 4) {
+			snd_iprintf(buffer,"\n");
+			col = 0;
+		}
+
+		if (col == 0) {
+			snd_iprintf(buffer, "%04X ",i);
+		}
+		
+		snd_iprintf(buffer,"%08X ",readl(dst + i));
+	}
+	snd_iprintf(buffer,"\n");
+}
+
+int cs46xx_dsp_proc_init (struct snd_card *card, struct snd_cs46xx *chip)
+{
+	struct snd_info_entry *entry;
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	int i;
+
+	ins->snd_card = card;
+
+	if ((entry = snd_info_create_card_entry(card, "dsp", card->proc_root)) != NULL) {
+		entry->content = SNDRV_INFO_CONTENT_TEXT;
+		entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+      
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+
+	ins->proc_dsp_dir = entry;
+
+	if (!ins->proc_dsp_dir)
+		return -ENOMEM;
+
+	if ((entry = snd_info_create_card_entry(card, "spos_symbols", ins->proc_dsp_dir)) != NULL) {
+		entry->content = SNDRV_INFO_CONTENT_TEXT;
+		entry->private_data = chip;
+		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+		entry->c.text.read = cs46xx_dsp_proc_symbol_table_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	ins->proc_sym_info_entry = entry;
+    
+	if ((entry = snd_info_create_card_entry(card, "spos_modules", ins->proc_dsp_dir)) != NULL) {
+		entry->content = SNDRV_INFO_CONTENT_TEXT;
+		entry->private_data = chip;
+		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+		entry->c.text.read = cs46xx_dsp_proc_modules_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	ins->proc_modules_info_entry = entry;
+
+	if ((entry = snd_info_create_card_entry(card, "parameter", ins->proc_dsp_dir)) != NULL) {
+		entry->content = SNDRV_INFO_CONTENT_TEXT;
+		entry->private_data = chip;
+		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+		entry->c.text.read = cs46xx_dsp_proc_parameter_dump_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	ins->proc_parameter_dump_info_entry = entry;
+
+	if ((entry = snd_info_create_card_entry(card, "sample", ins->proc_dsp_dir)) != NULL) {
+		entry->content = SNDRV_INFO_CONTENT_TEXT;
+		entry->private_data = chip;
+		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+		entry->c.text.read = cs46xx_dsp_proc_sample_dump_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	ins->proc_sample_dump_info_entry = entry;
+
+	if ((entry = snd_info_create_card_entry(card, "task_tree", ins->proc_dsp_dir)) != NULL) {
+		entry->content = SNDRV_INFO_CONTENT_TEXT;
+		entry->private_data = chip;
+		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+		entry->c.text.read = cs46xx_dsp_proc_task_tree_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	ins->proc_task_info_entry = entry;
+
+	if ((entry = snd_info_create_card_entry(card, "scb_info", ins->proc_dsp_dir)) != NULL) {
+		entry->content = SNDRV_INFO_CONTENT_TEXT;
+		entry->private_data = chip;
+		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+		entry->c.text.read = cs46xx_dsp_proc_scb_read;
+		if (snd_info_register(entry) < 0) {
+			snd_info_free_entry(entry);
+			entry = NULL;
+		}
+	}
+	ins->proc_scb_info_entry = entry;
+
+	mutex_lock(&chip->spos_mutex);
+	/* register/update SCB's entries on proc */
+	for (i = 0; i < ins->nscb; ++i) {
+		if (ins->scbs[i].deleted) continue;
+
+		cs46xx_dsp_proc_register_scb_desc (chip, (ins->scbs + i));
+	}
+	mutex_unlock(&chip->spos_mutex);
+
+	return 0;
+}
+
+int cs46xx_dsp_proc_done (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	int i;
+
+	snd_info_free_entry(ins->proc_sym_info_entry);
+	ins->proc_sym_info_entry = NULL;
+
+	snd_info_free_entry(ins->proc_modules_info_entry);
+	ins->proc_modules_info_entry = NULL;
+
+	snd_info_free_entry(ins->proc_parameter_dump_info_entry);
+	ins->proc_parameter_dump_info_entry = NULL;
+
+	snd_info_free_entry(ins->proc_sample_dump_info_entry);
+	ins->proc_sample_dump_info_entry = NULL;
+
+	snd_info_free_entry(ins->proc_scb_info_entry);
+	ins->proc_scb_info_entry = NULL;
+
+	snd_info_free_entry(ins->proc_task_info_entry);
+	ins->proc_task_info_entry = NULL;
+
+	mutex_lock(&chip->spos_mutex);
+	for (i = 0; i < ins->nscb; ++i) {
+		if (ins->scbs[i].deleted) continue;
+		cs46xx_dsp_proc_free_scb_desc ( (ins->scbs + i) );
+	}
+	mutex_unlock(&chip->spos_mutex);
+
+	snd_info_free_entry(ins->proc_dsp_dir);
+	ins->proc_dsp_dir = NULL;
+
+	return 0;
+}
+#endif /* CONFIG_PROC_FS */
+
+static int debug_tree;
+static void _dsp_create_task_tree (struct snd_cs46xx *chip, u32 * task_data,
+				   u32  dest, int size)
+{
+	void __iomem *spdst = chip->region.idx[1].remap_addr + 
+		DSP_PARAMETER_BYTE_OFFSET + dest * sizeof(u32);
+	int i;
+
+	for (i = 0; i < size; ++i) {
+		if (debug_tree) printk ("addr %p, val %08x\n",spdst,task_data[i]);
+		writel(task_data[i],spdst);
+		spdst += sizeof(u32);
+	}
+}
+
+static int debug_scb;
+static void _dsp_create_scb (struct snd_cs46xx *chip, u32 * scb_data, u32 dest)
+{
+	void __iomem *spdst = chip->region.idx[1].remap_addr + 
+		DSP_PARAMETER_BYTE_OFFSET + dest * sizeof(u32);
+	int i;
+
+	for (i = 0; i < 0x10; ++i) {
+		if (debug_scb) printk ("addr %p, val %08x\n",spdst,scb_data[i]);
+		writel(scb_data[i],spdst);
+		spdst += sizeof(u32);
+	}
+}
+
+static int find_free_scb_index (struct dsp_spos_instance * ins)
+{
+	int index = ins->nscb, i;
+
+	for (i = ins->scb_highest_frag_index; i < ins->nscb; ++i) {
+		if (ins->scbs[i].deleted) {
+			index = i;
+			break;
+		}
+	}
+
+	return index;
+}
+
+static struct dsp_scb_descriptor * _map_scb (struct snd_cs46xx *chip, char * name, u32 dest)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_scb_descriptor * desc = NULL;
+	int index;
+
+	if (ins->nscb == DSP_MAX_SCB_DESC - 1) {
+		snd_printk(KERN_ERR "dsp_spos: got no place for other SCB\n");
+		return NULL;
+	}
+
+	index = find_free_scb_index (ins);
+
+	memset(&ins->scbs[index], 0, sizeof(ins->scbs[index]));
+	strcpy(ins->scbs[index].scb_name, name);
+	ins->scbs[index].address = dest;
+	ins->scbs[index].index = index;
+	ins->scbs[index].ref_count = 1;
+
+	desc = (ins->scbs + index);
+	ins->scbs[index].scb_symbol = add_symbol (chip, name, dest, SYMBOL_PARAMETER);
+
+	if (index > ins->scb_highest_frag_index)
+		ins->scb_highest_frag_index = index;
+
+	if (index == ins->nscb)
+		ins->nscb++;
+
+	return desc;
+}
+
+static struct dsp_task_descriptor *
+_map_task_tree (struct snd_cs46xx *chip, char * name, u32 dest, u32 size)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_task_descriptor * desc = NULL;
+
+	if (ins->ntask == DSP_MAX_TASK_DESC - 1) {
+		snd_printk(KERN_ERR "dsp_spos: got no place for other TASK\n");
+		return NULL;
+	}
+
+	if (name)
+		strcpy(ins->tasks[ins->ntask].task_name, name);
+	else
+		strcpy(ins->tasks[ins->ntask].task_name, "(NULL)");
+	ins->tasks[ins->ntask].address = dest;
+	ins->tasks[ins->ntask].size = size;
+
+	/* quick find in list */
+	ins->tasks[ins->ntask].index = ins->ntask;
+	desc = (ins->tasks + ins->ntask);
+	ins->ntask++;
+
+	if (name)
+		add_symbol (chip,name,dest,SYMBOL_PARAMETER);
+	return desc;
+}
+
+#define SCB_BYTES	(0x10 * 4)
+
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data, u32 dest)
+{
+	struct dsp_scb_descriptor * desc;
+
+#ifdef CONFIG_PM
+	/* copy the data for resume */
+	scb_data = kmemdup(scb_data, SCB_BYTES, GFP_KERNEL);
+	if (!scb_data)
+		return NULL;
+#endif
+
+	desc = _map_scb (chip,name,dest);
+	if (desc) {
+		desc->data = scb_data;
+		_dsp_create_scb(chip,scb_data,dest);
+	} else {
+		snd_printk(KERN_ERR "dsp_spos: failed to map SCB\n");
+#ifdef CONFIG_PM
+		kfree(scb_data);
+#endif
+	}
+
+	return desc;
+}
+
+
+static struct dsp_task_descriptor *
+cs46xx_dsp_create_task_tree (struct snd_cs46xx *chip, char * name, u32 * task_data,
+			     u32 dest, int size)
+{
+	struct dsp_task_descriptor * desc;
+
+	desc = _map_task_tree (chip,name,dest,size);
+	if (desc) {
+		desc->data = task_data;
+		_dsp_create_task_tree(chip,task_data,dest,size);
+	} else {
+		snd_printk(KERN_ERR "dsp_spos: failed to map TASK\n");
+	}
+
+	return desc;
+}
+
+int cs46xx_dsp_scb_and_task_init (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_symbol_entry * fg_task_tree_header_code;
+	struct dsp_symbol_entry * task_tree_header_code;
+	struct dsp_symbol_entry * task_tree_thread;
+	struct dsp_symbol_entry * null_algorithm;
+	struct dsp_symbol_entry * magic_snoop_task;
+
+	struct dsp_scb_descriptor * timing_master_scb;
+	struct dsp_scb_descriptor * codec_out_scb;
+	struct dsp_scb_descriptor * codec_in_scb;
+	struct dsp_scb_descriptor * src_task_scb;
+	struct dsp_scb_descriptor * master_mix_scb;
+	struct dsp_scb_descriptor * rear_mix_scb;
+	struct dsp_scb_descriptor * record_mix_scb;
+	struct dsp_scb_descriptor * write_back_scb;
+	struct dsp_scb_descriptor * vari_decimate_scb;
+	struct dsp_scb_descriptor * rear_codec_out_scb;
+	struct dsp_scb_descriptor * clfe_codec_out_scb;
+	struct dsp_scb_descriptor * magic_snoop_scb;
+	
+	int fifo_addr, fifo_span, valid_slots;
+
+	static struct dsp_spos_control_block sposcb = {
+		/* 0 */ HFG_TREE_SCB,HFG_STACK,
+		/* 1 */ SPOSCB_ADDR,BG_TREE_SCB_ADDR,
+		/* 2 */ DSP_SPOS_DC,0,
+		/* 3 */ DSP_SPOS_DC,DSP_SPOS_DC,
+		/* 4 */ 0,0,
+		/* 5 */ DSP_SPOS_UU,0,
+		/* 6 */ FG_TASK_HEADER_ADDR,0,
+		/* 7 */ 0,0,
+		/* 8 */ DSP_SPOS_UU,DSP_SPOS_DC,
+		/* 9 */ 0,
+		/* A */ 0,HFG_FIRST_EXECUTE_MODE,
+		/* B */ DSP_SPOS_UU,DSP_SPOS_UU,
+		/* C */ DSP_SPOS_DC_DC,
+		/* D */ DSP_SPOS_DC_DC,
+		/* E */ DSP_SPOS_DC_DC,
+		/* F */ DSP_SPOS_DC_DC
+	};
+
+	cs46xx_dsp_create_task_tree(chip, "sposCB", (u32 *)&sposcb, SPOSCB_ADDR, 0x10);
+
+	null_algorithm  = cs46xx_dsp_lookup_symbol(chip, "NULLALGORITHM", SYMBOL_CODE);
+	if (null_algorithm == NULL) {
+		snd_printk(KERN_ERR "dsp_spos: symbol NULLALGORITHM not found\n");
+		return -EIO;
+	}
+
+	fg_task_tree_header_code = cs46xx_dsp_lookup_symbol(chip, "FGTASKTREEHEADERCODE", SYMBOL_CODE);  
+	if (fg_task_tree_header_code == NULL) {
+		snd_printk(KERN_ERR "dsp_spos: symbol FGTASKTREEHEADERCODE not found\n");
+		return -EIO;
+	}
+
+	task_tree_header_code = cs46xx_dsp_lookup_symbol(chip, "TASKTREEHEADERCODE", SYMBOL_CODE);  
+	if (task_tree_header_code == NULL) {
+		snd_printk(KERN_ERR "dsp_spos: symbol TASKTREEHEADERCODE not found\n");
+		return -EIO;
+	}
+  
+	task_tree_thread = cs46xx_dsp_lookup_symbol(chip, "TASKTREETHREAD", SYMBOL_CODE);
+	if (task_tree_thread == NULL) {
+		snd_printk(KERN_ERR "dsp_spos: symbol TASKTREETHREAD not found\n");
+		return -EIO;
+	}
+
+	magic_snoop_task = cs46xx_dsp_lookup_symbol(chip, "MAGICSNOOPTASK", SYMBOL_CODE);
+	if (magic_snoop_task == NULL) {
+		snd_printk(KERN_ERR "dsp_spos: symbol MAGICSNOOPTASK not found\n");
+		return -EIO;
+	}
+  
+	{
+		/* create the null SCB */
+		static struct dsp_generic_scb null_scb = {
+			{ 0, 0, 0, 0 },
+			{ 0, 0, 0, 0, 0 },
+			NULL_SCB_ADDR, NULL_SCB_ADDR,
+			0, 0, 0, 0, 0,
+			{
+				0,0,
+				0,0,
+			}
+		};
+
+		null_scb.entry_point = null_algorithm->address;
+		ins->the_null_scb = cs46xx_dsp_create_scb(chip, "nullSCB", (u32 *)&null_scb, NULL_SCB_ADDR);
+		ins->the_null_scb->task_entry = null_algorithm;
+		ins->the_null_scb->sub_list_ptr = ins->the_null_scb;
+		ins->the_null_scb->next_scb_ptr = ins->the_null_scb;
+		ins->the_null_scb->parent_scb_ptr = NULL;
+		cs46xx_dsp_proc_register_scb_desc (chip,ins->the_null_scb);
+	}
+
+	{
+		/* setup foreground task tree */
+		static struct dsp_task_tree_control_block fg_task_tree_hdr =  {
+			{ FG_TASK_HEADER_ADDR | (DSP_SPOS_DC << 0x10),
+			  DSP_SPOS_DC_DC,
+			  DSP_SPOS_DC_DC,
+			  0x0000,DSP_SPOS_DC,
+			  DSP_SPOS_DC, DSP_SPOS_DC,
+			  DSP_SPOS_DC_DC,
+			  DSP_SPOS_DC_DC,
+			  DSP_SPOS_DC_DC,
+			  DSP_SPOS_DC,DSP_SPOS_DC },
+    
+			{
+				BG_TREE_SCB_ADDR,TIMINGMASTER_SCB_ADDR, 
+				0,
+				FG_TASK_HEADER_ADDR + TCBData,                  
+			},
+
+			{    
+				4,0,
+				1,0,
+				2,SPOSCB_ADDR + HFGFlags,
+				0,0,
+				FG_TASK_HEADER_ADDR + TCBContextBlk,FG_STACK
+			},
+
+			{
+				DSP_SPOS_DC,0,
+				DSP_SPOS_DC,DSP_SPOS_DC,
+				DSP_SPOS_DC,DSP_SPOS_DC,
+				DSP_SPOS_DC,DSP_SPOS_DC,
+				DSP_SPOS_DC,DSP_SPOS_DC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_UU,1,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC 
+			},                                               
+			{ 
+				FG_INTERVAL_TIMER_PERIOD,DSP_SPOS_UU,
+				0,0
+			}
+		};
+
+		fg_task_tree_hdr.links.entry_point = fg_task_tree_header_code->address;
+		fg_task_tree_hdr.context_blk.stack0 = task_tree_thread->address;
+		cs46xx_dsp_create_task_tree(chip,"FGtaskTreeHdr",(u32 *)&fg_task_tree_hdr,FG_TASK_HEADER_ADDR,0x35);
+	}
+
+
+	{
+		/* setup foreground task tree */
+		static struct dsp_task_tree_control_block bg_task_tree_hdr =  {
+			{ DSP_SPOS_DC_DC,
+			  DSP_SPOS_DC_DC,
+			  DSP_SPOS_DC_DC,
+			  DSP_SPOS_DC, DSP_SPOS_DC,
+			  DSP_SPOS_DC, DSP_SPOS_DC,
+			  DSP_SPOS_DC_DC,
+			  DSP_SPOS_DC_DC,
+			  DSP_SPOS_DC_DC,
+			  DSP_SPOS_DC,DSP_SPOS_DC },
+    
+			{
+				NULL_SCB_ADDR,NULL_SCB_ADDR,  /* Set up the background to do nothing */
+				0,
+				BG_TREE_SCB_ADDR + TCBData,
+			},
+
+			{    
+				9999,0,
+				0,1,
+				0,SPOSCB_ADDR + HFGFlags,
+				0,0,
+				BG_TREE_SCB_ADDR + TCBContextBlk,BG_STACK
+			},
+
+			{
+				DSP_SPOS_DC,0,
+				DSP_SPOS_DC,DSP_SPOS_DC,
+				DSP_SPOS_DC,DSP_SPOS_DC,
+				DSP_SPOS_DC,DSP_SPOS_DC,
+				DSP_SPOS_DC,DSP_SPOS_DC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_UU,1,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC,
+				DSP_SPOS_DCDC 
+			},                                               
+			{ 
+				BG_INTERVAL_TIMER_PERIOD,DSP_SPOS_UU,
+				0,0
+			}
+		};
+
+		bg_task_tree_hdr.links.entry_point = task_tree_header_code->address;
+		bg_task_tree_hdr.context_blk.stack0 = task_tree_thread->address;
+		cs46xx_dsp_create_task_tree(chip,"BGtaskTreeHdr",(u32 *)&bg_task_tree_hdr,BG_TREE_SCB_ADDR,0x35);
+	}
+
+	/* create timing master SCB */
+	timing_master_scb = cs46xx_dsp_create_timing_master_scb(chip);
+
+	/* create the CODEC output task */
+	codec_out_scb = cs46xx_dsp_create_codec_out_scb(chip,"CodecOutSCB_I",0x0010,0x0000,
+							MASTERMIX_SCB_ADDR,
+							CODECOUT_SCB_ADDR,timing_master_scb,
+							SCB_ON_PARENT_SUBLIST_SCB);
+
+	if (!codec_out_scb) goto _fail_end;
+	/* create the master mix SCB */
+	master_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"MasterMixSCB",
+							MIX_SAMPLE_BUF1,MASTERMIX_SCB_ADDR,
+							codec_out_scb,
+							SCB_ON_PARENT_SUBLIST_SCB);
+	ins->master_mix_scb = master_mix_scb;
+
+	if (!master_mix_scb) goto _fail_end;
+
+	/* create codec in */
+	codec_in_scb = cs46xx_dsp_create_codec_in_scb(chip,"CodecInSCB",0x0010,0x00A0,
+						      CODEC_INPUT_BUF1,
+						      CODECIN_SCB_ADDR,codec_out_scb,
+						      SCB_ON_PARENT_NEXT_SCB);
+	if (!codec_in_scb) goto _fail_end;
+	ins->codec_in_scb = codec_in_scb;
+
+	/* create write back scb */
+	write_back_scb = cs46xx_dsp_create_mix_to_ostream_scb(chip,"WriteBackSCB",
+							      WRITE_BACK_BUF1,WRITE_BACK_SPB,
+							      WRITEBACK_SCB_ADDR,
+							      timing_master_scb,
+							      SCB_ON_PARENT_NEXT_SCB);
+	if (!write_back_scb) goto _fail_end;
+
+	{
+		static struct dsp_mix2_ostream_spb mix2_ostream_spb = {
+			0x00020000,
+			0x0000ffff
+		};
+    
+		if (!cs46xx_dsp_create_task_tree(chip, NULL,
+						 (u32 *)&mix2_ostream_spb,
+						 WRITE_BACK_SPB, 2))
+			goto _fail_end;
+	}
+
+	/* input sample converter */
+	vari_decimate_scb = cs46xx_dsp_create_vari_decimate_scb(chip,"VariDecimateSCB",
+								VARI_DECIMATE_BUF0,
+								VARI_DECIMATE_BUF1,
+								VARIDECIMATE_SCB_ADDR,
+								write_back_scb,
+								SCB_ON_PARENT_SUBLIST_SCB);
+	if (!vari_decimate_scb) goto _fail_end;
+
+	/* create the record mixer SCB */
+	record_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"RecordMixerSCB",
+							MIX_SAMPLE_BUF2,
+							RECORD_MIXER_SCB_ADDR,
+							vari_decimate_scb,
+							SCB_ON_PARENT_SUBLIST_SCB);
+	ins->record_mixer_scb = record_mix_scb;
+
+	if (!record_mix_scb) goto _fail_end;
+
+	valid_slots = snd_cs46xx_peekBA0(chip, BA0_ACOSV);
+
+	if (snd_BUG_ON(chip->nr_ac97_codecs != 1 && chip->nr_ac97_codecs != 2))
+		goto _fail_end;
+
+	if (chip->nr_ac97_codecs == 1) {
+		/* output on slot 5 and 11 
+		   on primary CODEC */
+		fifo_addr = 0x20;
+		fifo_span = 0x60;
+
+		/* enable slot 5 and 11 */
+		valid_slots |= ACOSV_SLV5 | ACOSV_SLV11;
+	} else {
+		/* output on slot 7 and 8 
+		   on secondary CODEC */
+		fifo_addr = 0x40;
+		fifo_span = 0x10;
+
+		/* enable slot 7 and 8 */
+		valid_slots |= ACOSV_SLV7 | ACOSV_SLV8;
+	}
+	/* create CODEC tasklet for rear speakers output*/
+	rear_codec_out_scb = cs46xx_dsp_create_codec_out_scb(chip,"CodecOutSCB_Rear",fifo_span,fifo_addr,
+							     REAR_MIXER_SCB_ADDR,
+							     REAR_CODECOUT_SCB_ADDR,codec_in_scb,
+							     SCB_ON_PARENT_NEXT_SCB);
+	if (!rear_codec_out_scb) goto _fail_end;
+	
+	
+	/* create the rear PCM channel  mixer SCB */
+	rear_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"RearMixerSCB",
+						      MIX_SAMPLE_BUF3,
+						      REAR_MIXER_SCB_ADDR,
+						      rear_codec_out_scb,
+						      SCB_ON_PARENT_SUBLIST_SCB);
+	ins->rear_mix_scb = rear_mix_scb;
+	if (!rear_mix_scb) goto _fail_end;
+	
+	if (chip->nr_ac97_codecs == 2) {
+		/* create CODEC tasklet for rear Center/LFE output 
+		   slot 6 and 9 on seconadry CODEC */
+		clfe_codec_out_scb = cs46xx_dsp_create_codec_out_scb(chip,"CodecOutSCB_CLFE",0x0030,0x0030,
+								     CLFE_MIXER_SCB_ADDR,
+								     CLFE_CODEC_SCB_ADDR,
+								     rear_codec_out_scb,
+								     SCB_ON_PARENT_NEXT_SCB);
+		if (!clfe_codec_out_scb) goto _fail_end;
+		
+		
+		/* create the rear PCM channel  mixer SCB */
+		ins->center_lfe_mix_scb = cs46xx_dsp_create_mix_only_scb(chip,"CLFEMixerSCB",
+									 MIX_SAMPLE_BUF4,
+									 CLFE_MIXER_SCB_ADDR,
+									 clfe_codec_out_scb,
+									 SCB_ON_PARENT_SUBLIST_SCB);
+		if (!ins->center_lfe_mix_scb) goto _fail_end;
+
+		/* enable slot 6 and 9 */
+		valid_slots |= ACOSV_SLV6 | ACOSV_SLV9;
+	} else {
+		clfe_codec_out_scb = rear_codec_out_scb;
+		ins->center_lfe_mix_scb = rear_mix_scb;
+	}
+
+	/* enable slots depending on CODEC configuration */
+	snd_cs46xx_pokeBA0(chip, BA0_ACOSV, valid_slots);
+
+	/* the magic snooper */
+	magic_snoop_scb = cs46xx_dsp_create_magic_snoop_scb (chip,"MagicSnoopSCB_I",OUTPUTSNOOP_SCB_ADDR,
+							     OUTPUT_SNOOP_BUFFER,
+							     codec_out_scb,
+							     clfe_codec_out_scb,
+							     SCB_ON_PARENT_NEXT_SCB);
+
+    
+	if (!magic_snoop_scb) goto _fail_end;
+	ins->ref_snoop_scb = magic_snoop_scb;
+
+	/* SP IO access */
+	if (!cs46xx_dsp_create_spio_write_scb(chip,"SPIOWriteSCB",SPIOWRITE_SCB_ADDR,
+					      magic_snoop_scb,
+					      SCB_ON_PARENT_NEXT_SCB))
+		goto _fail_end;
+
+	/* SPDIF input sampel rate converter */
+	src_task_scb = cs46xx_dsp_create_src_task_scb(chip,"SrcTaskSCB_SPDIFI",
+						      ins->spdif_in_sample_rate,
+						      SRC_OUTPUT_BUF1,
+						      SRC_DELAY_BUF1,SRCTASK_SCB_ADDR,
+						      master_mix_scb,
+						      SCB_ON_PARENT_SUBLIST_SCB,1);
+
+	if (!src_task_scb) goto _fail_end;
+	cs46xx_src_unlink(chip,src_task_scb);
+
+	/* NOTE: when we now how to detect the SPDIF input
+	   sample rate we will use this SRC to adjust it */
+	ins->spdif_in_src = src_task_scb;
+
+	cs46xx_dsp_async_init(chip,timing_master_scb);
+	return 0;
+
+ _fail_end:
+	snd_printk(KERN_ERR "dsp_spos: failed to setup SCB's in DSP\n");
+	return -EINVAL;
+}
+
+static int cs46xx_dsp_async_init (struct snd_cs46xx *chip,
+				  struct dsp_scb_descriptor * fg_entry)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_symbol_entry * s16_async_codec_input_task;
+	struct dsp_symbol_entry * spdifo_task;
+	struct dsp_symbol_entry * spdifi_task;
+	struct dsp_scb_descriptor * spdifi_scb_desc, * spdifo_scb_desc, * async_codec_scb_desc;
+
+	s16_async_codec_input_task = cs46xx_dsp_lookup_symbol(chip, "S16_ASYNCCODECINPUTTASK", SYMBOL_CODE);
+	if (s16_async_codec_input_task == NULL) {
+		snd_printk(KERN_ERR "dsp_spos: symbol S16_ASYNCCODECINPUTTASK not found\n");
+		return -EIO;
+	}
+	spdifo_task = cs46xx_dsp_lookup_symbol(chip, "SPDIFOTASK", SYMBOL_CODE);
+	if (spdifo_task == NULL) {
+		snd_printk(KERN_ERR "dsp_spos: symbol SPDIFOTASK not found\n");
+		return -EIO;
+	}
+
+	spdifi_task = cs46xx_dsp_lookup_symbol(chip, "SPDIFITASK", SYMBOL_CODE);
+	if (spdifi_task == NULL) {
+		snd_printk(KERN_ERR "dsp_spos: symbol SPDIFITASK not found\n");
+		return -EIO;
+	}
+
+	{
+		/* 0xBC0 */
+		struct dsp_spdifoscb spdifo_scb = {
+			/* 0 */ DSP_SPOS_UUUU,
+			{
+				/* 1 */ 0xb0, 
+				/* 2 */ 0, 
+				/* 3 */ 0, 
+				/* 4 */ 0, 
+			},
+			/* NOTE: the SPDIF output task read samples in mono
+			   format, the AsynchFGTxSCB task writes to buffer
+			   in stereo format
+			*/
+			/* 5 */ RSCONFIG_SAMPLE_16MONO + RSCONFIG_MODULO_256,
+			/* 6 */ ( SPDIFO_IP_OUTPUT_BUFFER1 << 0x10 )  |  0xFFFC,
+			/* 7 */ 0,0, 
+			/* 8 */ 0, 
+			/* 9 */ FG_TASK_HEADER_ADDR, NULL_SCB_ADDR, 
+			/* A */ spdifo_task->address,
+			SPDIFO_SCB_INST + SPDIFOFIFOPointer,
+			{
+				/* B */ 0x0040, /*DSP_SPOS_UUUU,*/
+				/* C */ 0x20ff, /*DSP_SPOS_UUUU,*/
+			},
+			/* D */ 0x804c,0,							  /* SPDIFOFIFOPointer:SPDIFOStatRegAddr; */
+			/* E */ 0x0108,0x0001,					  /* SPDIFOStMoFormat:SPDIFOFIFOBaseAddr; */
+			/* F */ DSP_SPOS_UUUU	  			          /* SPDIFOFree; */
+		};
+
+		/* 0xBB0 */
+		struct dsp_spdifiscb spdifi_scb = {
+			/* 0 */ DSP_SPOS_UULO,DSP_SPOS_UUHI,
+			/* 1 */ 0,
+			/* 2 */ 0,
+			/* 3 */ 1,4000,        /* SPDIFICountLimit SPDIFICount */ 
+			/* 4 */ DSP_SPOS_UUUU, /* SPDIFIStatusData */
+			/* 5 */ 0,DSP_SPOS_UUHI, /* StatusData, Free4 */
+			/* 6 */ DSP_SPOS_UUUU,  /* Free3 */
+			/* 7 */ DSP_SPOS_UU,DSP_SPOS_DC,  /* Free2 BitCount*/
+			/* 8 */ DSP_SPOS_UUUU,	/* TempStatus */
+			/* 9 */ SPDIFO_SCB_INST, NULL_SCB_ADDR,
+			/* A */ spdifi_task->address,
+			SPDIFI_SCB_INST + SPDIFIFIFOPointer,
+			/* NOTE: The SPDIF input task write the sample in mono
+			   format from the HW FIFO, the AsynchFGRxSCB task  reads 
+			   them in stereo 
+			*/
+			/* B */ RSCONFIG_SAMPLE_16MONO + RSCONFIG_MODULO_128,
+			/* C */ (SPDIFI_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC,
+			/* D */ 0x8048,0,
+			/* E */ 0x01f0,0x0001,
+			/* F */ DSP_SPOS_UUUU /* SPDIN_STATUS monitor */
+		};
+
+		/* 0xBA0 */
+		struct dsp_async_codec_input_scb async_codec_input_scb = {
+			/* 0 */ DSP_SPOS_UUUU,
+			/* 1 */ 0,
+			/* 2 */ 0,
+			/* 3 */ 1,4000,
+			/* 4 */ 0x0118,0x0001,
+			/* 5 */ RSCONFIG_SAMPLE_16MONO + RSCONFIG_MODULO_64,
+			/* 6 */ (ASYNC_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC,
+			/* 7 */ DSP_SPOS_UU,0x3,
+			/* 8 */ DSP_SPOS_UUUU,
+			/* 9 */ SPDIFI_SCB_INST,NULL_SCB_ADDR,
+			/* A */ s16_async_codec_input_task->address,
+			HFG_TREE_SCB + AsyncCIOFIFOPointer,
+              
+			/* B */ RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64,
+			/* C */ (ASYNC_IP_OUTPUT_BUFFER1 << 0x10),  /*(ASYNC_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC,*/
+      
+#ifdef UseASER1Input
+			/* short AsyncCIFIFOPointer:AsyncCIStatRegAddr;	       
+			   Init. 0000:8042: for ASER1
+			   0000:8044: for ASER2 */
+			/* D */ 0x8042,0,
+      
+			/* short AsyncCIStMoFormat:AsyncCIFIFOBaseAddr;
+			   Init 1 stero:8050 ASER1
+			   Init 0  mono:8070 ASER2
+			   Init 1 Stereo : 0100 ASER1 (Set by script) */
+			/* E */ 0x0100,0x0001,
+      
+#endif
+      
+#ifdef UseASER2Input
+			/* short AsyncCIFIFOPointer:AsyncCIStatRegAddr;
+			   Init. 0000:8042: for ASER1
+			   0000:8044: for ASER2 */
+			/* D */ 0x8044,0,
+      
+			/* short AsyncCIStMoFormat:AsyncCIFIFOBaseAddr;
+			   Init 1 stero:8050 ASER1
+			   Init 0  mono:8070 ASER2
+			   Init 1 Stereo : 0100 ASER1 (Set by script) */
+			/* E */ 0x0110,0x0001,
+      
+#endif
+      
+			/* short AsyncCIOutputBufModulo:AsyncCIFree;
+			   AsyncCIOutputBufModulo: The modulo size for   
+			   the output buffer of this task */
+			/* F */ 0, /* DSP_SPOS_UUUU */
+		};
+
+		spdifo_scb_desc = cs46xx_dsp_create_scb(chip,"SPDIFOSCB",(u32 *)&spdifo_scb,SPDIFO_SCB_INST);
+
+		if (snd_BUG_ON(!spdifo_scb_desc))
+			return -EIO;
+		spdifi_scb_desc = cs46xx_dsp_create_scb(chip,"SPDIFISCB",(u32 *)&spdifi_scb,SPDIFI_SCB_INST);
+		if (snd_BUG_ON(!spdifi_scb_desc))
+			return -EIO;
+		async_codec_scb_desc = cs46xx_dsp_create_scb(chip,"AsynCodecInputSCB",(u32 *)&async_codec_input_scb, HFG_TREE_SCB);
+		if (snd_BUG_ON(!async_codec_scb_desc))
+			return -EIO;
+
+		async_codec_scb_desc->parent_scb_ptr = NULL;
+		async_codec_scb_desc->next_scb_ptr = spdifi_scb_desc;
+		async_codec_scb_desc->sub_list_ptr = ins->the_null_scb;
+		async_codec_scb_desc->task_entry = s16_async_codec_input_task;
+
+		spdifi_scb_desc->parent_scb_ptr = async_codec_scb_desc;
+		spdifi_scb_desc->next_scb_ptr = spdifo_scb_desc;
+		spdifi_scb_desc->sub_list_ptr = ins->the_null_scb;
+		spdifi_scb_desc->task_entry = spdifi_task;
+
+		spdifo_scb_desc->parent_scb_ptr = spdifi_scb_desc;
+		spdifo_scb_desc->next_scb_ptr = fg_entry;
+		spdifo_scb_desc->sub_list_ptr = ins->the_null_scb;
+		spdifo_scb_desc->task_entry = spdifo_task;
+
+		/* this one is faked, as the parnet of SPDIFO task
+		   is the FG task tree */
+		fg_entry->parent_scb_ptr = spdifo_scb_desc;
+
+		/* for proc fs */
+		cs46xx_dsp_proc_register_scb_desc (chip,spdifo_scb_desc);
+		cs46xx_dsp_proc_register_scb_desc (chip,spdifi_scb_desc);
+		cs46xx_dsp_proc_register_scb_desc (chip,async_codec_scb_desc);
+
+		/* Async MASTER ENABLE, affects both SPDIF input and output */
+		snd_cs46xx_pokeBA0(chip, BA0_ASER_MASTER, 0x1 );
+	}
+
+	return 0;
+}
+
+static void cs46xx_dsp_disable_spdif_hw (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	/* set SPDIF output FIFO slot */
+	snd_cs46xx_pokeBA0(chip, BA0_ASER_FADDR, 0);
+
+	/* SPDIF output MASTER ENABLE */
+	cs46xx_poke_via_dsp (chip,SP_SPDOUT_CONTROL, 0);
+
+	/* right and left validate bit */
+	/*cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_default);*/
+	cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, 0x0);
+
+	/* clear fifo pointer */
+	cs46xx_poke_via_dsp (chip,SP_SPDIN_FIFOPTR, 0x0);
+
+	/* monitor state */
+	ins->spdif_status_out &= ~DSP_SPDIF_STATUS_HW_ENABLED;
+}
+
+int cs46xx_dsp_enable_spdif_hw (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	/* if hw-ctrl already enabled, turn off to reset logic ... */
+	cs46xx_dsp_disable_spdif_hw (chip);
+	udelay(50);
+
+	/* set SPDIF output FIFO slot */
+	snd_cs46xx_pokeBA0(chip, BA0_ASER_FADDR, ( 0x8000 | ((SP_SPDOUT_FIFO >> 4) << 4) ));
+
+	/* SPDIF output MASTER ENABLE */
+	cs46xx_poke_via_dsp (chip,SP_SPDOUT_CONTROL, 0x80000000);
+
+	/* right and left validate bit */
+	cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_default);
+
+	/* monitor state */
+	ins->spdif_status_out |= DSP_SPDIF_STATUS_HW_ENABLED;
+
+	return 0;
+}
+
+int cs46xx_dsp_enable_spdif_in (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	/* turn on amplifier */
+	chip->active_ctrl(chip, 1);
+	chip->amplifier_ctrl(chip, 1);
+
+	if (snd_BUG_ON(ins->asynch_rx_scb))
+		return -EINVAL;
+	if (snd_BUG_ON(!ins->spdif_in_src))
+		return -EINVAL;
+
+	mutex_lock(&chip->spos_mutex);
+
+	if ( ! (ins->spdif_status_out & DSP_SPDIF_STATUS_INPUT_CTRL_ENABLED) ) {
+		/* time countdown enable */
+		cs46xx_poke_via_dsp (chip,SP_ASER_COUNTDOWN, 0x80000005);
+		/* NOTE: 80000005 value is just magic. With all values
+		   that I've tested this one seem to give the best result.
+		   Got no explication why. (Benny) */
+
+		/* SPDIF input MASTER ENABLE */
+		cs46xx_poke_via_dsp (chip,SP_SPDIN_CONTROL, 0x800003ff);
+
+		ins->spdif_status_out |= DSP_SPDIF_STATUS_INPUT_CTRL_ENABLED;
+	}
+
+	/* create and start the asynchronous receiver SCB */
+	ins->asynch_rx_scb = cs46xx_dsp_create_asynch_fg_rx_scb(chip,"AsynchFGRxSCB",
+								ASYNCRX_SCB_ADDR,
+								SPDIFI_SCB_INST,
+								SPDIFI_IP_OUTPUT_BUFFER1,
+								ins->spdif_in_src,
+								SCB_ON_PARENT_SUBLIST_SCB);
+
+	spin_lock_irq(&chip->reg_lock);
+
+	/* reset SPDIF input sample buffer pointer */
+	/*snd_cs46xx_poke (chip, (SPDIFI_SCB_INST + 0x0c) << 2,
+	  (SPDIFI_IP_OUTPUT_BUFFER1 << 0x10) | 0xFFFC);*/
+
+	/* reset FIFO ptr */
+	/*cs46xx_poke_via_dsp (chip,SP_SPDIN_FIFOPTR, 0x0);*/
+	cs46xx_src_link(chip,ins->spdif_in_src);
+
+	/* unmute SRC volume */
+	cs46xx_dsp_scb_set_volume (chip,ins->spdif_in_src,0x7fff,0x7fff);
+
+	spin_unlock_irq(&chip->reg_lock);
+
+	/* set SPDIF input sample rate and unmute
+	   NOTE: only 48khz support for SPDIF input this time */
+	/* cs46xx_dsp_set_src_sample_rate(chip,ins->spdif_in_src,48000); */
+
+	/* monitor state */
+	ins->spdif_status_in = 1;
+	mutex_unlock(&chip->spos_mutex);
+
+	return 0;
+}
+
+int cs46xx_dsp_disable_spdif_in (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if (snd_BUG_ON(!ins->asynch_rx_scb))
+		return -EINVAL;
+	if (snd_BUG_ON(!ins->spdif_in_src))
+		return -EINVAL;
+
+	mutex_lock(&chip->spos_mutex);
+
+	/* Remove the asynchronous receiver SCB */
+	cs46xx_dsp_remove_scb (chip,ins->asynch_rx_scb);
+	ins->asynch_rx_scb = NULL;
+
+	cs46xx_src_unlink(chip,ins->spdif_in_src);
+
+	/* monitor state */
+	ins->spdif_status_in = 0;
+	mutex_unlock(&chip->spos_mutex);
+
+	/* restore amplifier */
+	chip->active_ctrl(chip, -1);
+	chip->amplifier_ctrl(chip, -1);
+
+	return 0;
+}
+
+int cs46xx_dsp_enable_pcm_capture (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if (snd_BUG_ON(ins->pcm_input))
+		return -EINVAL;
+	if (snd_BUG_ON(!ins->ref_snoop_scb))
+		return -EINVAL;
+
+	mutex_lock(&chip->spos_mutex);
+	ins->pcm_input = cs46xx_add_record_source(chip,ins->ref_snoop_scb,PCMSERIALIN_PCM_SCB_ADDR,
+                                                  "PCMSerialInput_Wave");
+	mutex_unlock(&chip->spos_mutex);
+
+	return 0;
+}
+
+int cs46xx_dsp_disable_pcm_capture (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if (snd_BUG_ON(!ins->pcm_input))
+		return -EINVAL;
+
+	mutex_lock(&chip->spos_mutex);
+	cs46xx_dsp_remove_scb (chip,ins->pcm_input);
+	ins->pcm_input = NULL;
+	mutex_unlock(&chip->spos_mutex);
+
+	return 0;
+}
+
+int cs46xx_dsp_enable_adc_capture (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if (snd_BUG_ON(ins->adc_input))
+		return -EINVAL;
+	if (snd_BUG_ON(!ins->codec_in_scb))
+		return -EINVAL;
+
+	mutex_lock(&chip->spos_mutex);
+	ins->adc_input = cs46xx_add_record_source(chip,ins->codec_in_scb,PCMSERIALIN_SCB_ADDR,
+						  "PCMSerialInput_ADC");
+	mutex_unlock(&chip->spos_mutex);
+
+	return 0;
+}
+
+int cs46xx_dsp_disable_adc_capture (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if (snd_BUG_ON(!ins->adc_input))
+		return -EINVAL;
+
+	mutex_lock(&chip->spos_mutex);
+	cs46xx_dsp_remove_scb (chip,ins->adc_input);
+	ins->adc_input = NULL;
+	mutex_unlock(&chip->spos_mutex);
+
+	return 0;
+}
+
+int cs46xx_poke_via_dsp (struct snd_cs46xx *chip, u32 address, u32 data)
+{
+	u32 temp;
+	int  i;
+
+	/* santiy check the parameters.  (These numbers are not 100% correct.  They are
+	   a rough guess from looking at the controller spec.) */
+	if (address < 0x8000 || address >= 0x9000)
+		return -EINVAL;
+        
+	/* initialize the SP_IO_WRITE SCB with the data. */
+	temp = ( address << 16 ) | ( address & 0x0000FFFF);   /* offset 0 <-- address2 : address1 */
+
+	snd_cs46xx_poke(chip,( SPIOWRITE_SCB_ADDR      << 2), temp);
+	snd_cs46xx_poke(chip,((SPIOWRITE_SCB_ADDR + 1) << 2), data); /* offset 1 <-- data1 */
+	snd_cs46xx_poke(chip,((SPIOWRITE_SCB_ADDR + 2) << 2), data); /* offset 1 <-- data2 */
+    
+	/* Poke this location to tell the task to start */
+	snd_cs46xx_poke(chip,((SPIOWRITE_SCB_ADDR + 6) << 2), SPIOWRITE_SCB_ADDR << 0x10);
+
+	/* Verify that the task ran */
+	for (i=0; i<25; i++) {
+		udelay(125);
+
+		temp =  snd_cs46xx_peek(chip,((SPIOWRITE_SCB_ADDR + 6) << 2));
+		if (temp == 0x00000000)
+			break;
+	}
+
+	if (i == 25) {
+		snd_printk(KERN_ERR "dsp_spos: SPIOWriteTask not responding\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+int cs46xx_dsp_set_dac_volume (struct snd_cs46xx * chip, u16 left, u16 right)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_scb_descriptor * scb; 
+
+	mutex_lock(&chip->spos_mutex);
+	
+	/* main output */
+	scb = ins->master_mix_scb->sub_list_ptr;
+	while (scb != ins->the_null_scb) {
+		cs46xx_dsp_scb_set_volume (chip,scb,left,right);
+		scb = scb->next_scb_ptr;
+	}
+
+	/* rear output */
+	scb = ins->rear_mix_scb->sub_list_ptr;
+	while (scb != ins->the_null_scb) {
+		cs46xx_dsp_scb_set_volume (chip,scb,left,right);
+		scb = scb->next_scb_ptr;
+	}
+
+	ins->dac_volume_left = left;
+	ins->dac_volume_right = right;
+
+	mutex_unlock(&chip->spos_mutex);
+
+	return 0;
+}
+
+int cs46xx_dsp_set_iec958_volume (struct snd_cs46xx * chip, u16 left, u16 right)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	mutex_lock(&chip->spos_mutex);
+
+	if (ins->asynch_rx_scb != NULL)
+		cs46xx_dsp_scb_set_volume (chip,ins->asynch_rx_scb,
+					   left,right);
+
+	ins->spdif_input_volume_left = left;
+	ins->spdif_input_volume_right = right;
+
+	mutex_unlock(&chip->spos_mutex);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+int cs46xx_dsp_resume(struct snd_cs46xx * chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	int i, err;
+
+	/* clear parameter, sample and code areas */
+	snd_cs46xx_clear_BA1(chip, DSP_PARAMETER_BYTE_OFFSET,
+			     DSP_PARAMETER_BYTE_SIZE);
+	snd_cs46xx_clear_BA1(chip, DSP_SAMPLE_BYTE_OFFSET,
+			     DSP_SAMPLE_BYTE_SIZE);
+	snd_cs46xx_clear_BA1(chip, DSP_CODE_BYTE_OFFSET, DSP_CODE_BYTE_SIZE);
+
+	for (i = 0; i < ins->nmodules; i++) {
+		struct dsp_module_desc *module = &ins->modules[i];
+		struct dsp_segment_desc *seg;
+		u32 doffset, dsize;
+
+		seg = get_segment_desc(module, SEGTYPE_SP_PARAMETER);
+		err = dsp_load_parameter(chip, seg);
+		if (err < 0)
+			return err;
+
+		seg = get_segment_desc(module, SEGTYPE_SP_SAMPLE);
+		err = dsp_load_sample(chip, seg);
+		if (err < 0)
+			return err;
+
+		seg = get_segment_desc(module, SEGTYPE_SP_PROGRAM);
+		if (!seg)
+			continue;
+
+		doffset = seg->offset * 4 + module->load_address * 4
+			+ DSP_CODE_BYTE_OFFSET;
+		dsize   = seg->size * 4;
+		err = snd_cs46xx_download(chip,
+					  ins->code.data + module->load_address,
+					  doffset, dsize);
+		if (err < 0)
+			return err;
+	}
+
+	for (i = 0; i < ins->ntask; i++) {
+		struct dsp_task_descriptor *t = &ins->tasks[i];
+		_dsp_create_task_tree(chip, t->data, t->address, t->size);
+	}
+
+	for (i = 0; i < ins->nscb; i++) {
+		struct dsp_scb_descriptor *s = &ins->scbs[i];
+		if (s->deleted)
+			continue;
+		_dsp_create_scb(chip, s->data, s->address);
+	}
+	for (i = 0; i < ins->nscb; i++) {
+		struct dsp_scb_descriptor *s = &ins->scbs[i];
+		if (s->deleted)
+			continue;
+		if (s->updated)
+			cs46xx_dsp_spos_update_scb(chip, s);
+		if (s->volume_set)
+			cs46xx_dsp_scb_set_volume(chip, s,
+						  s->volume[0], s->volume[1]);
+	}
+	if (ins->spdif_status_out & DSP_SPDIF_STATUS_HW_ENABLED) {
+		cs46xx_dsp_enable_spdif_hw(chip);
+		snd_cs46xx_poke(chip, (ins->ref_snoop_scb->address + 2) << 2,
+				(OUTPUT_SNOOP_BUFFER + 0x10) << 0x10);
+		if (ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN)
+			cs46xx_poke_via_dsp(chip, SP_SPDOUT_CSUV,
+					    ins->spdif_csuv_stream);
+	}
+	if (chip->dsp_spos_instance->spdif_status_in) {
+		cs46xx_poke_via_dsp(chip, SP_ASER_COUNTDOWN, 0x80000005);
+		cs46xx_poke_via_dsp(chip, SP_SPDIN_CONTROL, 0x800003ff);
+	}
+	return 0;
+}
+#endif
diff --git a/linux/sound/pci/cs46xx/dsp_spos.h b/linux/sound/pci/cs46xx/dsp_spos.h
new file mode 100644
index 0000000..ca47a81
--- /dev/null
+++ b/linux/sound/pci/cs46xx/dsp_spos.h
@@ -0,0 +1,231 @@
+/*
+ *  The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ * 2002-07 Benny Sjostrand benny@hostmobility.com
+ */
+
+#ifdef  CONFIG_SND_CS46XX_NEW_DSP /* hack ... */
+#ifndef __DSP_SPOS_H__
+#define __DSP_SPOS_H__
+
+#define DSP_MAX_SYMBOLS 1024
+#define DSP_MAX_MODULES 64
+
+#define DSP_CODE_BYTE_SIZE             0x00007000UL
+#define DSP_PARAMETER_BYTE_SIZE        0x00003000UL
+#define DSP_SAMPLE_BYTE_SIZE           0x00003800UL
+#define DSP_PARAMETER_BYTE_OFFSET      0x00000000UL
+#define DSP_SAMPLE_BYTE_OFFSET         0x00010000UL
+#define DSP_CODE_BYTE_OFFSET           0x00020000UL
+
+#define WIDE_INSTR_MASK       0x0040
+#define WIDE_LADD_INSTR_MASK  0x0380
+
+/* this instruction types
+   needs to be reallocated when load
+   code into DSP */
+enum wide_opcode {
+	WIDE_FOR_BEGIN_LOOP = 0x20,
+	WIDE_FOR_BEGIN_LOOP2,
+
+	WIDE_COND_GOTO_ADDR = 0x30,
+	WIDE_COND_GOTO_CALL,
+
+	WIDE_TBEQ_COND_GOTO_ADDR = 0x70,
+	WIDE_TBEQ_COND_CALL_ADDR,
+	WIDE_TBEQ_NCOND_GOTO_ADDR,
+	WIDE_TBEQ_NCOND_CALL_ADDR,
+	WIDE_TBEQ_COND_GOTO1_ADDR,
+	WIDE_TBEQ_COND_CALL1_ADDR,
+	WIDE_TBEQ_NCOND_GOTOI_ADDR,
+	WIDE_TBEQ_NCOND_CALL1_ADDR,
+};
+
+/* SAMPLE segment */
+#define VARI_DECIMATE_BUF1       0x0000
+#define WRITE_BACK_BUF1          0x0400
+#define CODEC_INPUT_BUF1         0x0500
+#define PCM_READER_BUF1          0x0600
+#define SRC_DELAY_BUF1           0x0680
+#define VARI_DECIMATE_BUF0       0x0780
+#define SRC_OUTPUT_BUF1          0x07A0
+#define ASYNC_IP_OUTPUT_BUFFER1  0x0A00
+#define OUTPUT_SNOOP_BUFFER      0x0B00
+#define SPDIFI_IP_OUTPUT_BUFFER1 0x0E00
+#define SPDIFO_IP_OUTPUT_BUFFER1 0x1000
+#define MIX_SAMPLE_BUF1          0x1400
+#define MIX_SAMPLE_BUF2          0x2E80
+#define MIX_SAMPLE_BUF3          0x2F00
+#define MIX_SAMPLE_BUF4          0x2F80
+#define MIX_SAMPLE_BUF5          0x3000
+
+/* Task stack address */
+#define HFG_STACK                0x066A
+#define FG_STACK                 0x066E
+#define BG_STACK                 0x068E
+
+/* SCB's addresses */
+#define SPOSCB_ADDR              0x070
+#define BG_TREE_SCB_ADDR         0x635
+#define NULL_SCB_ADDR            0x000
+#define TIMINGMASTER_SCB_ADDR    0x010
+#define CODECOUT_SCB_ADDR        0x020
+#define PCMREADER_SCB_ADDR       0x030
+#define WRITEBACK_SCB_ADDR       0x040
+#define CODECIN_SCB_ADDR         0x080
+#define MASTERMIX_SCB_ADDR       0x090
+#define SRCTASK_SCB_ADDR         0x0A0
+#define VARIDECIMATE_SCB_ADDR    0x0B0
+#define PCMSERIALIN_SCB_ADDR     0x0C0
+#define FG_TASK_HEADER_ADDR      0x600
+#define ASYNCTX_SCB_ADDR         0x0E0
+#define ASYNCRX_SCB_ADDR         0x0F0
+#define SRCTASKII_SCB_ADDR       0x100
+#define OUTPUTSNOOP_SCB_ADDR     0x110
+#define PCMSERIALINII_SCB_ADDR   0x120
+#define SPIOWRITE_SCB_ADDR       0x130
+#define REAR_CODECOUT_SCB_ADDR   0x140
+#define OUTPUTSNOOPII_SCB_ADDR   0x150
+#define PCMSERIALIN_PCM_SCB_ADDR 0x160
+#define RECORD_MIXER_SCB_ADDR    0x170
+#define REAR_MIXER_SCB_ADDR      0x180
+#define CLFE_MIXER_SCB_ADDR      0x190
+#define CLFE_CODEC_SCB_ADDR      0x1A0
+
+/* hyperforground SCB's*/
+#define HFG_TREE_SCB             0xBA0
+#define SPDIFI_SCB_INST          0xBB0
+#define SPDIFO_SCB_INST          0xBC0
+#define WRITE_BACK_SPB           0x0D0
+
+/* offsets */
+#define AsyncCIOFIFOPointer  0xd
+#define SPDIFOFIFOPointer    0xd
+#define SPDIFIFIFOPointer    0xd
+#define TCBData              0xb
+#define HFGFlags             0xa
+#define TCBContextBlk        0x10
+#define AFGTxAccumPhi        0x4
+#define SCBsubListPtr        0x9
+#define SCBfuncEntryPtr      0xA
+#define SRCCorPerGof         0x2
+#define SRCPhiIncr6Int26Frac 0xd
+#define SCBVolumeCtrl        0xe
+
+/* conf */
+#define UseASER1Input 1
+
+
+
+/*
+ * The following defines are for the flags in the rsConfig01/23 registers of
+ * the SP.
+ */
+
+#define RSCONFIG_MODULO_SIZE_MASK               0x0000000FL
+#define RSCONFIG_MODULO_16                      0x00000001L
+#define RSCONFIG_MODULO_32                      0x00000002L
+#define RSCONFIG_MODULO_64                      0x00000003L
+#define RSCONFIG_MODULO_128                     0x00000004L
+#define RSCONFIG_MODULO_256                     0x00000005L
+#define RSCONFIG_MODULO_512                     0x00000006L
+#define RSCONFIG_MODULO_1024                    0x00000007L
+#define RSCONFIG_MODULO_4                       0x00000008L
+#define RSCONFIG_MODULO_8                       0x00000009L
+#define RSCONFIG_SAMPLE_SIZE_MASK               0x000000C0L
+#define RSCONFIG_SAMPLE_8MONO                   0x00000000L
+#define RSCONFIG_SAMPLE_8STEREO                 0x00000040L
+#define RSCONFIG_SAMPLE_16MONO                  0x00000080L
+#define RSCONFIG_SAMPLE_16STEREO                0x000000C0L
+#define RSCONFIG_UNDERRUN_ZERO                  0x00004000L
+#define RSCONFIG_DMA_TO_HOST                    0x00008000L
+#define RSCONFIG_STREAM_NUM_MASK                0x00FF0000L
+#define RSCONFIG_MAX_DMA_SIZE_MASK              0x1F000000L
+#define RSCONFIG_DMA_ENABLE                     0x20000000L
+#define RSCONFIG_PRIORITY_MASK                  0xC0000000L
+#define RSCONFIG_PRIORITY_HIGH                  0x00000000L
+#define RSCONFIG_PRIORITY_MEDIUM_HIGH           0x40000000L
+#define RSCONFIG_PRIORITY_MEDIUM_LOW            0x80000000L
+#define RSCONFIG_PRIORITY_LOW                   0xC0000000L
+#define RSCONFIG_STREAM_NUM_SHIFT               16L
+#define RSCONFIG_MAX_DMA_SIZE_SHIFT             24L
+
+/* SP constants */
+#define FG_INTERVAL_TIMER_PERIOD                0x0051
+#define BG_INTERVAL_TIMER_PERIOD                0x0100
+
+
+/* Only SP accessible registers */
+#define SP_ASER_COUNTDOWN 0x8040
+#define SP_SPDOUT_FIFO    0x0108
+#define SP_SPDIN_MI_FIFO  0x01E0
+#define SP_SPDIN_D_FIFO   0x01F0
+#define SP_SPDIN_STATUS   0x8048
+#define SP_SPDIN_CONTROL  0x8049
+#define SP_SPDIN_FIFOPTR  0x804A
+#define SP_SPDOUT_STATUS  0x804C
+#define SP_SPDOUT_CONTROL 0x804D
+#define SP_SPDOUT_CSUV    0x808E
+
+static inline u8 _wrap_all_bits (u8 val)
+{
+	u8 wrapped;
+	
+	/* wrap all 8 bits */
+	wrapped = 
+		((val & 0x1 ) << 7) |
+		((val & 0x2 ) << 5) |
+		((val & 0x4 ) << 3) |
+		((val & 0x8 ) << 1) |
+		((val & 0x10) >> 1) |
+		((val & 0x20) >> 3) |
+		((val & 0x40) >> 5) |
+		((val & 0x80) >> 7);
+
+	return wrapped;
+}
+
+static inline void cs46xx_dsp_spos_update_scb (struct snd_cs46xx * chip,
+					       struct dsp_scb_descriptor * scb) 
+{
+	/* update nextSCB and subListPtr in SCB */
+	snd_cs46xx_poke(chip,
+			(scb->address + SCBsubListPtr) << 2,
+			(scb->sub_list_ptr->address << 0x10) |
+			(scb->next_scb_ptr->address));	
+	scb->updated = 1;
+}
+
+static inline void cs46xx_dsp_scb_set_volume (struct snd_cs46xx * chip,
+					      struct dsp_scb_descriptor * scb,
+					      u16 left, u16 right)
+{
+	unsigned int val = ((0xffff - left) << 16 | (0xffff - right));
+
+	snd_cs46xx_poke(chip, (scb->address + SCBVolumeCtrl) << 2, val);
+	snd_cs46xx_poke(chip, (scb->address + SCBVolumeCtrl + 1) << 2, val);
+	scb->volume_set = 1;
+	scb->volume[0] = left;
+	scb->volume[1] = right;
+}
+#endif /* __DSP_SPOS_H__ */
+#endif /* CONFIG_SND_CS46XX_NEW_DSP  */
diff --git a/linux/sound/pci/cs46xx/dsp_spos_scb_lib.c b/linux/sound/pci/cs46xx/dsp_spos_scb_lib.c
new file mode 100644
index 0000000..00b148a
--- /dev/null
+++ b/linux/sound/pci/cs46xx/dsp_spos_scb_lib.c
@@ -0,0 +1,1784 @@
+/*
+ *
+ *   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
+ *
+ */
+
+/*
+ * 2002-07 Benny Sjostrand benny@hostmobility.com
+ */
+
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/cs46xx.h>
+
+#include "cs46xx_lib.h"
+#include "dsp_spos.h"
+
+struct proc_scb_info {
+	struct dsp_scb_descriptor * scb_desc;
+	struct snd_cs46xx *chip;
+};
+
+static void remove_symbol (struct snd_cs46xx * chip, struct dsp_symbol_entry * symbol)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	int symbol_index = (int)(symbol - ins->symbol_table.symbols);
+
+	if (snd_BUG_ON(ins->symbol_table.nsymbols <= 0))
+		return;
+	if (snd_BUG_ON(symbol_index < 0 ||
+		       symbol_index >= ins->symbol_table.nsymbols))
+		return;
+
+	ins->symbol_table.symbols[symbol_index].deleted = 1;
+
+	if (symbol_index < ins->symbol_table.highest_frag_index) {
+		ins->symbol_table.highest_frag_index = symbol_index;
+	}
+  
+	if (symbol_index == ins->symbol_table.nsymbols - 1)
+		ins->symbol_table.nsymbols --;
+
+	if (ins->symbol_table.highest_frag_index > ins->symbol_table.nsymbols) {
+		ins->symbol_table.highest_frag_index = ins->symbol_table.nsymbols;
+	}
+
+}
+
+#ifdef CONFIG_PROC_FS
+static void cs46xx_dsp_proc_scb_info_read (struct snd_info_entry *entry,
+					   struct snd_info_buffer *buffer)
+{
+	struct proc_scb_info * scb_info  = entry->private_data;
+	struct dsp_scb_descriptor * scb = scb_info->scb_desc;
+	struct dsp_spos_instance * ins;
+	struct snd_cs46xx *chip = scb_info->chip;
+	int j,col;
+	void __iomem *dst = chip->region.idx[1].remap_addr + DSP_PARAMETER_BYTE_OFFSET;
+
+	ins = chip->dsp_spos_instance;
+
+	mutex_lock(&chip->spos_mutex);
+	snd_iprintf(buffer,"%04x %s:\n",scb->address,scb->scb_name);
+
+	for (col = 0,j = 0;j < 0x10; j++,col++) {
+		if (col == 4) {
+			snd_iprintf(buffer,"\n");
+			col = 0;
+		}
+		snd_iprintf(buffer,"%08x ",readl(dst + (scb->address + j) * sizeof(u32)));
+	}
+  
+	snd_iprintf(buffer,"\n");
+
+	if (scb->parent_scb_ptr != NULL) {
+		snd_iprintf(buffer,"parent [%s:%04x] ", 
+			    scb->parent_scb_ptr->scb_name,
+			    scb->parent_scb_ptr->address);
+	} else snd_iprintf(buffer,"parent [none] ");
+  
+	snd_iprintf(buffer,"sub_list_ptr [%s:%04x]\nnext_scb_ptr [%s:%04x]  task_entry [%s:%04x]\n",
+		    scb->sub_list_ptr->scb_name,
+		    scb->sub_list_ptr->address,
+		    scb->next_scb_ptr->scb_name,
+		    scb->next_scb_ptr->address,
+		    scb->task_entry->symbol_name,
+		    scb->task_entry->address);
+
+	snd_iprintf(buffer,"index [%d] ref_count [%d]\n",scb->index,scb->ref_count);  
+	mutex_unlock(&chip->spos_mutex);
+}
+#endif
+
+static void _dsp_unlink_scb (struct snd_cs46xx *chip, struct dsp_scb_descriptor * scb)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if ( scb->parent_scb_ptr ) {
+		/* unlink parent SCB */
+		if (snd_BUG_ON(scb->parent_scb_ptr->sub_list_ptr != scb &&
+			       scb->parent_scb_ptr->next_scb_ptr != scb))
+			return;
+  
+		if (scb->parent_scb_ptr->sub_list_ptr == scb) {
+
+			if (scb->next_scb_ptr == ins->the_null_scb) {
+				/* last and only node in parent sublist */
+				scb->parent_scb_ptr->sub_list_ptr = scb->sub_list_ptr;
+
+				if (scb->sub_list_ptr != ins->the_null_scb) {
+					scb->sub_list_ptr->parent_scb_ptr = scb->parent_scb_ptr;
+				}
+				scb->sub_list_ptr = ins->the_null_scb;
+			} else {
+				/* first node in parent sublist */
+				scb->parent_scb_ptr->sub_list_ptr = scb->next_scb_ptr;
+
+				if (scb->next_scb_ptr != ins->the_null_scb) {
+					/* update next node parent ptr. */
+					scb->next_scb_ptr->parent_scb_ptr = scb->parent_scb_ptr;
+				}
+				scb->next_scb_ptr = ins->the_null_scb;
+			}
+		} else {
+			scb->parent_scb_ptr->next_scb_ptr = scb->next_scb_ptr;
+
+			if (scb->next_scb_ptr != ins->the_null_scb) {
+				/* update next node parent ptr. */
+				scb->next_scb_ptr->parent_scb_ptr = scb->parent_scb_ptr;
+			}
+			scb->next_scb_ptr = ins->the_null_scb;
+		}
+
+		/* update parent first entry in DSP RAM */
+		cs46xx_dsp_spos_update_scb(chip,scb->parent_scb_ptr);
+
+		/* then update entry in DSP RAM */
+		cs46xx_dsp_spos_update_scb(chip,scb);
+
+		scb->parent_scb_ptr = NULL;
+	}
+}
+
+static void _dsp_clear_sample_buffer (struct snd_cs46xx *chip, u32 sample_buffer_addr,
+				      int dword_count) 
+{
+	void __iomem *dst = chip->region.idx[2].remap_addr + sample_buffer_addr;
+	int i;
+  
+	for (i = 0; i < dword_count ; ++i ) {
+		writel(0, dst);
+		dst += 4;
+	}  
+}
+
+void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip, struct dsp_scb_descriptor * scb)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	unsigned long flags;
+
+	/* check integrety */
+	if (snd_BUG_ON(scb->index < 0 ||
+		       scb->index >= ins->nscb ||
+		       (ins->scbs + scb->index) != scb))
+		return;
+
+#if 0
+	/* can't remove a SCB with childs before 
+	   removing childs first  */
+	if (snd_BUG_ON(scb->sub_list_ptr != ins->the_null_scb ||
+		       scb->next_scb_ptr != ins->the_null_scb))
+		goto _end;
+#endif
+
+	spin_lock_irqsave(&chip->reg_lock, flags);    
+	_dsp_unlink_scb (chip,scb);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	cs46xx_dsp_proc_free_scb_desc(scb);
+	if (snd_BUG_ON(!scb->scb_symbol))
+		return;
+	remove_symbol (chip,scb->scb_symbol);
+
+	ins->scbs[scb->index].deleted = 1;
+#ifdef CONFIG_PM
+	kfree(ins->scbs[scb->index].data);
+	ins->scbs[scb->index].data = NULL;
+#endif
+
+	if (scb->index < ins->scb_highest_frag_index)
+		ins->scb_highest_frag_index = scb->index;
+
+	if (scb->index == ins->nscb - 1) {
+		ins->nscb --;
+	}
+
+	if (ins->scb_highest_frag_index > ins->nscb) {
+		ins->scb_highest_frag_index = ins->nscb;
+	}
+
+#if 0
+	/* !!!! THIS IS A PIECE OF SHIT MADE BY ME !!! */
+	for(i = scb->index + 1;i < ins->nscb; ++i) {
+		ins->scbs[i - 1].index = i - 1;
+	}
+#endif
+}
+
+
+#ifdef CONFIG_PROC_FS
+void cs46xx_dsp_proc_free_scb_desc (struct dsp_scb_descriptor * scb)
+{
+	if (scb->proc_info) {
+		struct proc_scb_info * scb_info = scb->proc_info->private_data;
+
+		snd_printdd("cs46xx_dsp_proc_free_scb_desc: freeing %s\n",scb->scb_name);
+
+		snd_info_free_entry(scb->proc_info);
+		scb->proc_info = NULL;
+
+		kfree (scb_info);
+	}
+}
+
+void cs46xx_dsp_proc_register_scb_desc (struct snd_cs46xx *chip,
+					struct dsp_scb_descriptor * scb)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct snd_info_entry * entry;
+	struct proc_scb_info * scb_info;
+
+	/* register to proc */
+	if (ins->snd_card != NULL && ins->proc_dsp_dir != NULL &&
+	    scb->proc_info == NULL) {
+  
+		if ((entry = snd_info_create_card_entry(ins->snd_card, scb->scb_name, 
+							ins->proc_dsp_dir)) != NULL) {
+			scb_info = kmalloc(sizeof(struct proc_scb_info), GFP_KERNEL);
+			if (!scb_info) {
+				snd_info_free_entry(entry);
+				entry = NULL;
+				goto out;
+			}
+
+			scb_info->chip = chip;
+			scb_info->scb_desc = scb;
+      
+			entry->content = SNDRV_INFO_CONTENT_TEXT;
+			entry->private_data = scb_info;
+			entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
+      
+			entry->c.text.read = cs46xx_dsp_proc_scb_info_read;
+      
+			if (snd_info_register(entry) < 0) {
+				snd_info_free_entry(entry);
+				kfree (scb_info);
+				entry = NULL;
+			}
+		}
+out:
+		scb->proc_info = entry;
+	}
+}
+#endif /* CONFIG_PROC_FS */
+
+static struct dsp_scb_descriptor * 
+_dsp_create_generic_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data, u32 dest,
+                         struct dsp_symbol_entry * task_entry,
+                         struct dsp_scb_descriptor * parent_scb,
+                         int scb_child_type)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_scb_descriptor * scb;
+  
+	unsigned long flags;
+
+	if (snd_BUG_ON(!ins->the_null_scb))
+		return NULL;
+
+	/* fill the data that will be wroten to DSP */
+	scb_data[SCBsubListPtr] = 
+		(ins->the_null_scb->address << 0x10) | ins->the_null_scb->address;
+
+	scb_data[SCBfuncEntryPtr] &= 0xFFFF0000;
+	scb_data[SCBfuncEntryPtr] |= task_entry->address;
+
+	snd_printdd("dsp_spos: creating SCB <%s>\n",name);
+
+	scb = cs46xx_dsp_create_scb(chip,name,scb_data,dest);
+
+
+	scb->sub_list_ptr = ins->the_null_scb;
+	scb->next_scb_ptr = ins->the_null_scb;
+
+	scb->parent_scb_ptr = parent_scb;
+	scb->task_entry = task_entry;
+
+  
+	/* update parent SCB */
+	if (scb->parent_scb_ptr) {
+#if 0
+		printk ("scb->parent_scb_ptr = %s\n",scb->parent_scb_ptr->scb_name);
+		printk ("scb->parent_scb_ptr->next_scb_ptr = %s\n",scb->parent_scb_ptr->next_scb_ptr->scb_name);
+		printk ("scb->parent_scb_ptr->sub_list_ptr = %s\n",scb->parent_scb_ptr->sub_list_ptr->scb_name);
+#endif
+		/* link to  parent SCB */
+		if (scb_child_type == SCB_ON_PARENT_NEXT_SCB) {
+			if (snd_BUG_ON(scb->parent_scb_ptr->next_scb_ptr !=
+				       ins->the_null_scb))
+				return NULL;
+
+			scb->parent_scb_ptr->next_scb_ptr = scb;
+
+		} else if (scb_child_type == SCB_ON_PARENT_SUBLIST_SCB) {
+			if (snd_BUG_ON(scb->parent_scb_ptr->sub_list_ptr !=
+				       ins->the_null_scb))
+				return NULL;
+
+			scb->parent_scb_ptr->sub_list_ptr = scb;
+		} else {
+			snd_BUG();
+		}
+
+		spin_lock_irqsave(&chip->reg_lock, flags);
+
+		/* update entry in DSP RAM */
+		cs46xx_dsp_spos_update_scb(chip,scb->parent_scb_ptr);
+
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+	}
+
+
+	cs46xx_dsp_proc_register_scb_desc (chip,scb);
+
+	return scb;
+}
+
+static struct dsp_scb_descriptor * 
+cs46xx_dsp_create_generic_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data,
+			       u32 dest, char * task_entry_name,
+                               struct dsp_scb_descriptor * parent_scb,
+                               int scb_child_type)
+{
+	struct dsp_symbol_entry * task_entry;
+
+	task_entry = cs46xx_dsp_lookup_symbol (chip,task_entry_name,
+					       SYMBOL_CODE);
+  
+	if (task_entry == NULL) {
+		snd_printk (KERN_ERR "dsp_spos: symbol %s not found\n",task_entry_name);
+		return NULL;
+	}
+  
+	return _dsp_create_generic_scb (chip,name,scb_data,dest,task_entry,
+					parent_scb,scb_child_type);
+}
+
+struct dsp_scb_descriptor * 
+cs46xx_dsp_create_timing_master_scb (struct snd_cs46xx *chip)
+{
+	struct dsp_scb_descriptor * scb;
+  
+	struct dsp_timing_master_scb timing_master_scb = {
+		{ 0,
+		  0,
+		  0,
+		  0
+		},
+		{ 0,
+		  0,
+		  0,
+		  0,
+		  0
+		},
+		0,0,
+		0,NULL_SCB_ADDR,
+		0,0,             /* extraSampleAccum:TMreserved */
+		0,0,             /* codecFIFOptr:codecFIFOsyncd */
+		0x0001,0x8000,   /* fracSampAccumQm1:TMfrmsLeftInGroup */
+		0x0001,0x0000,   /* fracSampCorrectionQm1:TMfrmGroupLength */
+		0x00060000       /* nSampPerFrmQ15 */
+	};    
+  
+	scb = cs46xx_dsp_create_generic_scb(chip,"TimingMasterSCBInst",(u32 *)&timing_master_scb,
+					    TIMINGMASTER_SCB_ADDR,
+					    "TIMINGMASTER",NULL,SCB_NO_PARENT);
+
+	return scb;
+}
+
+
+struct dsp_scb_descriptor * 
+cs46xx_dsp_create_codec_out_scb(struct snd_cs46xx * chip, char * codec_name,
+                                u16 channel_disp, u16 fifo_addr, u16 child_scb_addr,
+                                u32 dest, struct dsp_scb_descriptor * parent_scb,
+                                int scb_child_type)
+{
+	struct dsp_scb_descriptor * scb;
+  
+	struct dsp_codec_output_scb codec_out_scb = {
+		{ 0,
+		  0,
+		  0,
+		  0
+		},
+		{
+			0,
+			0,
+			0,
+			0,
+			0
+		},
+		0,0,
+		0,NULL_SCB_ADDR,
+		0,                      /* COstrmRsConfig */
+		0,                      /* COstrmBufPtr */
+		channel_disp,fifo_addr, /* leftChanBaseIOaddr:rightChanIOdisp */
+		0x0000,0x0080,          /* (!AC97!) COexpVolChangeRate:COscaleShiftCount */
+		0,child_scb_addr        /* COreserved - need child scb to work with rom code */
+	};
+  
+  
+	scb = cs46xx_dsp_create_generic_scb(chip,codec_name,(u32 *)&codec_out_scb,
+					    dest,"S16_CODECOUTPUTTASK",parent_scb,
+					    scb_child_type);
+  
+	return scb;
+}
+
+struct dsp_scb_descriptor * 
+cs46xx_dsp_create_codec_in_scb(struct snd_cs46xx * chip, char * codec_name,
+			       u16 channel_disp, u16 fifo_addr, u16 sample_buffer_addr,
+			       u32 dest, struct dsp_scb_descriptor * parent_scb,
+			       int scb_child_type)
+{
+
+	struct dsp_scb_descriptor * scb;
+	struct dsp_codec_input_scb codec_input_scb = {
+		{ 0,
+		  0,
+		  0,
+		  0
+		},
+		{
+			0,
+			0,
+			0,
+			0,
+			0
+		},
+    
+#if 0  /* cs4620 */
+		SyncIOSCB,NULL_SCB_ADDR
+#else
+		0 , 0,
+#endif
+		0,0,
+
+		RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64,  /* strmRsConfig */
+		sample_buffer_addr << 0x10,       /* strmBufPtr; defined as a dword ptr, used as a byte ptr */
+		channel_disp,fifo_addr,           /* (!AC97!) leftChanBaseINaddr=AC97primary 
+						     link input slot 3 :rightChanINdisp=""slot 4 */
+		0x0000,0x0000,                    /* (!AC97!) ????:scaleShiftCount; no shift needed 
+						     because AC97 is already 20 bits */
+		0x80008000                        /* ??clw cwcgame.scb has 0 */
+	};
+  
+	scb = cs46xx_dsp_create_generic_scb(chip,codec_name,(u32 *)&codec_input_scb,
+					    dest,"S16_CODECINPUTTASK",parent_scb,
+					    scb_child_type);
+	return scb;
+}
+
+
+static struct dsp_scb_descriptor * 
+cs46xx_dsp_create_pcm_reader_scb(struct snd_cs46xx * chip, char * scb_name,
+                                 u16 sample_buffer_addr, u32 dest,
+                                 int virtual_channel, u32 playback_hw_addr,
+                                 struct dsp_scb_descriptor * parent_scb,
+                                 int scb_child_type)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_scb_descriptor * scb;
+  
+	struct dsp_generic_scb pcm_reader_scb = {
+    
+		/*
+		  Play DMA Task xfers data from host buffer to SP buffer
+		  init/runtime variables:
+		  PlayAC: Play Audio Data Conversion - SCB loc: 2nd dword, mask: 0x0000F000L
+		  DATA_FMT_16BIT_ST_LTLEND(0x00000000L)   from 16-bit stereo, little-endian
+		  DATA_FMT_8_BIT_ST_SIGNED(0x00001000L)   from 8-bit stereo, signed
+		  DATA_FMT_16BIT_MN_LTLEND(0x00002000L)   from 16-bit mono, little-endian
+		  DATA_FMT_8_BIT_MN_SIGNED(0x00003000L)   from 8-bit mono, signed
+		  DATA_FMT_16BIT_ST_BIGEND(0x00004000L)   from 16-bit stereo, big-endian
+		  DATA_FMT_16BIT_MN_BIGEND(0x00006000L)   from 16-bit mono, big-endian
+		  DATA_FMT_8_BIT_ST_UNSIGNED(0x00009000L) from 8-bit stereo, unsigned
+		  DATA_FMT_8_BIT_MN_UNSIGNED(0x0000b000L) from 8-bit mono, unsigned
+		  ? Other combinations possible from:
+		  DMA_RQ_C2_AUDIO_CONVERT_MASK    0x0000F000L
+		  DMA_RQ_C2_AC_NONE               0x00000000L
+		  DMA_RQ_C2_AC_8_TO_16_BIT        0x00001000L
+		  DMA_RQ_C2_AC_MONO_TO_STEREO     0x00002000L
+		  DMA_RQ_C2_AC_ENDIAN_CONVERT     0x00004000L
+		  DMA_RQ_C2_AC_SIGNED_CONVERT     0x00008000L
+        
+		  HostBuffAddr: Host Buffer Physical Byte Address - SCB loc:3rd dword, Mask: 0xFFFFFFFFL
+		  aligned to dword boundary
+		*/
+		/* Basic (non scatter/gather) DMA requestor (4 ints) */
+		{ DMA_RQ_C1_SOURCE_ON_HOST +        /* source buffer is on the host */
+		  DMA_RQ_C1_SOURCE_MOD1024 +        /* source buffer is 1024 dwords (4096 bytes) */
+		  DMA_RQ_C1_DEST_MOD32 +            /* dest buffer(PCMreaderBuf) is 32 dwords*/
+		  DMA_RQ_C1_WRITEBACK_SRC_FLAG +    /* ?? */
+		  DMA_RQ_C1_WRITEBACK_DEST_FLAG +   /* ?? */
+		  15,                             /* DwordCount-1: picked 16 for DwordCount because Jim */
+		  /*        Barnette said that is what we should use since */
+		  /*        we are not running in optimized mode? */
+		  DMA_RQ_C2_AC_NONE +
+		  DMA_RQ_C2_SIGNAL_SOURCE_PINGPONG + /* set play interrupt (bit0) in HISR when source */
+		  /*   buffer (on host) crosses half-way point */
+		  virtual_channel,                   /* Play DMA channel arbitrarily set to 0 */
+		  playback_hw_addr,                  /* HostBuffAddr (source) */
+		  DMA_RQ_SD_SP_SAMPLE_ADDR +         /* destination buffer is in SP Sample Memory */
+		  sample_buffer_addr                 /* SP Buffer Address (destination) */
+		},
+		/* Scatter/gather DMA requestor extension   (5 ints) */
+		{
+			0,
+			0,
+			0,
+			0,
+			0 
+		},
+		/* Sublist pointer & next stream control block (SCB) link. */
+		NULL_SCB_ADDR,NULL_SCB_ADDR,
+		/* Pointer to this tasks parameter block & stream function pointer */
+		0,NULL_SCB_ADDR,
+		/* rsConfig register for stream buffer (rsDMA reg. is loaded from basicReq.daw */
+		/*   for incoming streams, or basicReq.saw, for outgoing streams) */
+		RSCONFIG_DMA_ENABLE +                 /* enable DMA */
+		(19 << RSCONFIG_MAX_DMA_SIZE_SHIFT) + /* MAX_DMA_SIZE picked to be 19 since SPUD  */
+		/*  uses it for some reason */
+		((dest >> 4) << RSCONFIG_STREAM_NUM_SHIFT) + /* stream number = SCBaddr/16 */
+		RSCONFIG_SAMPLE_16STEREO +
+		RSCONFIG_MODULO_32,             /* dest buffer(PCMreaderBuf) is 32 dwords (256 bytes) */
+		/* Stream sample pointer & MAC-unit mode for this stream */
+		(sample_buffer_addr << 0x10),
+		/* Fractional increment per output sample in the input sample buffer */
+		0, 
+		{
+			/* Standard stereo volume control
+			   default muted */
+			0xffff,0xffff,
+			0xffff,0xffff
+		}
+	};
+
+	if (ins->null_algorithm == NULL) {
+		ins->null_algorithm =  cs46xx_dsp_lookup_symbol (chip,"NULLALGORITHM",
+								 SYMBOL_CODE);
+    
+		if (ins->null_algorithm == NULL) {
+			snd_printk (KERN_ERR "dsp_spos: symbol NULLALGORITHM not found\n");
+			return NULL;
+		}    
+	}
+
+	scb = _dsp_create_generic_scb(chip,scb_name,(u32 *)&pcm_reader_scb,
+				      dest,ins->null_algorithm,parent_scb,
+				      scb_child_type);
+  
+	return scb;
+}
+
+#define GOF_PER_SEC 200
+
+struct dsp_scb_descriptor * 
+cs46xx_dsp_create_src_task_scb(struct snd_cs46xx * chip, char * scb_name,
+			       int rate,
+                               u16 src_buffer_addr,
+                               u16 src_delay_buffer_addr, u32 dest,
+                               struct dsp_scb_descriptor * parent_scb,
+                               int scb_child_type,
+	                       int pass_through)
+{
+
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_scb_descriptor * scb;
+	unsigned int tmp1, tmp2;
+	unsigned int phiIncr;
+	unsigned int correctionPerGOF, correctionPerSec;
+
+	snd_printdd( "dsp_spos: setting %s rate to %u\n",scb_name,rate);
+
+	/*
+	 *  Compute the values used to drive the actual sample rate conversion.
+	 *  The following formulas are being computed, using inline assembly
+	 *  since we need to use 64 bit arithmetic to compute the values:
+	 *
+	 *  phiIncr = floor((Fs,in * 2^26) / Fs,out)
+	 *  correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) /
+	 *                                   GOF_PER_SEC)
+	 *  ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr -M
+	 *                       GOF_PER_SEC * correctionPerGOF
+	 *
+	 *  i.e.
+	 *
+	 *  phiIncr:other = dividend:remainder((Fs,in * 2^26) / Fs,out)
+	 *  correctionPerGOF:correctionPerSec =
+	 *      dividend:remainder(ulOther / GOF_PER_SEC)
+	 */
+	tmp1 = rate << 16;
+	phiIncr = tmp1 / 48000;
+	tmp1 -= phiIncr * 48000;
+	tmp1 <<= 10;
+	phiIncr <<= 10;
+	tmp2 = tmp1 / 48000;
+	phiIncr += tmp2;
+	tmp1 -= tmp2 * 48000;
+	correctionPerGOF = tmp1 / GOF_PER_SEC;
+	tmp1 -= correctionPerGOF * GOF_PER_SEC;
+	correctionPerSec = tmp1;
+
+	{
+		struct dsp_src_task_scb src_task_scb = {
+			0x0028,0x00c8,
+			0x5555,0x0000,
+			0x0000,0x0000,
+			src_buffer_addr,1,
+			correctionPerGOF,correctionPerSec,
+			RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_32,  
+			0x0000,src_delay_buffer_addr,                  
+			0x0,                                            
+			0x080,(src_delay_buffer_addr + (24 * 4)),
+			0,0, /* next_scb, sub_list_ptr */
+			0,0, /* entry, this_spb */
+			RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_8,
+			src_buffer_addr << 0x10,
+			phiIncr,
+			{ 
+				0xffff - ins->dac_volume_right,0xffff - ins->dac_volume_left,
+				0xffff - ins->dac_volume_right,0xffff - ins->dac_volume_left
+			}
+		};
+		
+		if (ins->s16_up == NULL) {
+			ins->s16_up =  cs46xx_dsp_lookup_symbol (chip,"S16_UPSRC",
+								 SYMBOL_CODE);
+			
+			if (ins->s16_up == NULL) {
+				snd_printk (KERN_ERR "dsp_spos: symbol S16_UPSRC not found\n");
+				return NULL;
+			}    
+		}
+		
+		/* clear buffers */
+		_dsp_clear_sample_buffer (chip,src_buffer_addr,8);
+		_dsp_clear_sample_buffer (chip,src_delay_buffer_addr,32);
+				
+		if (pass_through) {
+			/* wont work with any other rate than
+			   the native DSP rate */
+			snd_BUG_ON(rate != 48000);
+
+			scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&src_task_scb,
+							    dest,"DMAREADER",parent_scb,
+							    scb_child_type);
+		} else {
+			scb = _dsp_create_generic_scb(chip,scb_name,(u32 *)&src_task_scb,
+						      dest,ins->s16_up,parent_scb,
+						      scb_child_type);
+		}
+
+
+	}
+
+	return scb;
+}
+
+#if 0 /* not used */
+struct dsp_scb_descriptor * 
+cs46xx_dsp_create_filter_scb(struct snd_cs46xx * chip, char * scb_name,
+			     u16 buffer_addr, u32 dest,
+			     struct dsp_scb_descriptor * parent_scb,
+			     int scb_child_type) {
+	struct dsp_scb_descriptor * scb;
+	
+	struct dsp_filter_scb filter_scb = {
+		.a0_right            = 0x41a9,
+		.a0_left             = 0x41a9,
+		.a1_right            = 0xb8e4,
+		.a1_left             = 0xb8e4,
+		.a2_right            = 0x3e55,
+		.a2_left             = 0x3e55,
+		
+		.filter_unused3      = 0x0000,
+		.filter_unused2      = 0x0000,
+
+		.output_buf_ptr      = buffer_addr,
+		.init                = 0x000,
+
+		.prev_sample_output1 = 0x00000000,
+		.prev_sample_output2 = 0x00000000,
+
+		.prev_sample_input1  = 0x00000000,
+		.prev_sample_input2  = 0x00000000,
+
+		.next_scb_ptr        = 0x0000,
+		.sub_list_ptr        = 0x0000,
+
+		.entry_point         = 0x0000,
+		.spb_ptr             = 0x0000,
+
+		.b0_right            = 0x0e38,
+		.b0_left             = 0x0e38,
+		.b1_right            = 0x1c71,
+		.b1_left             = 0x1c71,
+		.b2_right            = 0x0e38,
+		.b2_left             = 0x0e38,
+	};
+
+
+	scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&filter_scb,
+					    dest,"FILTERTASK",parent_scb,
+					    scb_child_type);
+
+ 	return scb;
+}
+#endif /* not used */
+
+struct dsp_scb_descriptor * 
+cs46xx_dsp_create_mix_only_scb(struct snd_cs46xx * chip, char * scb_name,
+                               u16 mix_buffer_addr, u32 dest,
+                               struct dsp_scb_descriptor * parent_scb,
+                               int scb_child_type)
+{
+	struct dsp_scb_descriptor * scb;
+  
+	struct dsp_mix_only_scb master_mix_scb = {
+		/* 0 */ { 0,
+			  /* 1 */   0,
+			  /* 2 */  mix_buffer_addr,
+			  /* 3 */  0
+			  /*   */ },
+		{
+			/* 4 */  0,
+			/* 5 */  0,
+			/* 6 */  0,
+			/* 7 */  0,
+			/* 8 */  0x00000080
+		},
+		/* 9 */ 0,0,
+		/* A */ 0,0,
+		/* B */ RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_32,
+		/* C */ (mix_buffer_addr  + (16 * 4)) << 0x10, 
+		/* D */ 0,
+		{
+			/* E */ 0x8000,0x8000,
+			/* F */ 0x8000,0x8000
+		}
+	};
+
+
+	scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&master_mix_scb,
+					    dest,"S16_MIX",parent_scb,
+					    scb_child_type);
+	return scb;
+}
+
+
+struct dsp_scb_descriptor * 
+cs46xx_dsp_create_mix_to_ostream_scb(struct snd_cs46xx * chip, char * scb_name,
+                                     u16 mix_buffer_addr, u16 writeback_spb, u32 dest,
+                                     struct dsp_scb_descriptor * parent_scb,
+                                     int scb_child_type)
+{
+	struct dsp_scb_descriptor * scb;
+
+	struct dsp_mix2_ostream_scb mix2_ostream_scb = {
+		/* Basic (non scatter/gather) DMA requestor (4 ints) */
+		{ 
+			DMA_RQ_C1_SOURCE_MOD64 +
+			DMA_RQ_C1_DEST_ON_HOST +
+			DMA_RQ_C1_DEST_MOD1024 +
+			DMA_RQ_C1_WRITEBACK_SRC_FLAG + 
+			DMA_RQ_C1_WRITEBACK_DEST_FLAG +
+			15,                            
+      
+			DMA_RQ_C2_AC_NONE +
+			DMA_RQ_C2_SIGNAL_DEST_PINGPONG + 
+      
+			CS46XX_DSP_CAPTURE_CHANNEL,                                 
+			DMA_RQ_SD_SP_SAMPLE_ADDR + 
+			mix_buffer_addr, 
+			0x0                   
+		},
+    
+		{ 0, 0, 0, 0, 0, },
+		0,0,
+		0,writeback_spb,
+    
+		RSCONFIG_DMA_ENABLE + 
+		(19 << RSCONFIG_MAX_DMA_SIZE_SHIFT) + 
+    
+		((dest >> 4) << RSCONFIG_STREAM_NUM_SHIFT) +
+		RSCONFIG_DMA_TO_HOST + 
+		RSCONFIG_SAMPLE_16STEREO +
+		RSCONFIG_MODULO_64,    
+		(mix_buffer_addr + (32 * 4)) << 0x10,
+		1,0,            
+		0x0001,0x0080,
+		0xFFFF,0
+	};
+
+
+	scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&mix2_ostream_scb,
+				
+	    dest,"S16_MIX_TO_OSTREAM",parent_scb,
+					    scb_child_type);
+  
+	return scb;
+}
+
+
+struct dsp_scb_descriptor * 
+cs46xx_dsp_create_vari_decimate_scb(struct snd_cs46xx * chip,char * scb_name,
+                                    u16 vari_buffer_addr0,
+                                    u16 vari_buffer_addr1,
+                                    u32 dest,
+                                    struct dsp_scb_descriptor * parent_scb,
+                                    int scb_child_type)
+{
+
+	struct dsp_scb_descriptor * scb;
+  
+	struct dsp_vari_decimate_scb vari_decimate_scb = {
+		0x0028,0x00c8,
+		0x5555,0x0000,
+		0x0000,0x0000,
+		vari_buffer_addr0,vari_buffer_addr1,
+    
+		0x0028,0x00c8,
+		RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_256, 
+    
+		0xFF800000,   
+		0,
+		0x0080,vari_buffer_addr1 + (25 * 4), 
+    
+		0,0, 
+		0,0,
+
+		RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_8,
+		vari_buffer_addr0 << 0x10,   
+		0x04000000,                   
+		{
+			0x8000,0x8000, 
+			0xFFFF,0xFFFF
+		}
+	};
+
+	scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&vari_decimate_scb,
+					    dest,"VARIDECIMATE",parent_scb,
+					    scb_child_type);
+  
+	return scb;
+}
+
+
+static struct dsp_scb_descriptor * 
+cs46xx_dsp_create_pcm_serial_input_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest,
+                                       struct dsp_scb_descriptor * input_scb,
+                                       struct dsp_scb_descriptor * parent_scb,
+                                       int scb_child_type)
+{
+
+	struct dsp_scb_descriptor * scb;
+
+
+	struct dsp_pcm_serial_input_scb pcm_serial_input_scb = {
+		{ 0,
+		  0,
+		  0,
+		  0
+		},
+		{
+			0,
+			0,
+			0,
+			0,
+			0
+		},
+
+		0,0,
+		0,0,
+
+		RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_16,
+		0,
+      /* 0xD */ 0,input_scb->address,
+		{
+      /* 0xE */   0x8000,0x8000,
+      /* 0xF */	  0x8000,0x8000
+		}
+	};
+
+	scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&pcm_serial_input_scb,
+					    dest,"PCMSERIALINPUTTASK",parent_scb,
+					    scb_child_type);
+	return scb;
+}
+
+
+static struct dsp_scb_descriptor * 
+cs46xx_dsp_create_asynch_fg_tx_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest,
+                                   u16 hfg_scb_address,
+                                   u16 asynch_buffer_address,
+                                   struct dsp_scb_descriptor * parent_scb,
+                                   int scb_child_type)
+{
+
+	struct dsp_scb_descriptor * scb;
+
+	struct dsp_asynch_fg_tx_scb asynch_fg_tx_scb = {
+		0xfc00,0x03ff,      /*  Prototype sample buffer size of 256 dwords */
+		0x0058,0x0028,      /* Min Delta 7 dwords == 28 bytes */
+		/* : Max delta 25 dwords == 100 bytes */
+		0,hfg_scb_address,  /* Point to HFG task SCB */
+		0,0,		    /* Initialize current Delta and Consumer ptr adjustment count */
+		0,                  /* Initialize accumulated Phi to 0 */
+		0,0x2aab,           /* Const 1/3 */
+    
+		{
+			0,         /* Define the unused elements */
+			0,
+			0
+		},
+    
+		0,0,
+		0,dest + AFGTxAccumPhi,
+    
+		RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_256, /* Stereo, 256 dword */
+		(asynch_buffer_address) << 0x10,  /* This should be automagically synchronized
+                                                     to the producer pointer */
+    
+		/* There is no correct initial value, it will depend upon the detected
+		   rate etc  */
+		0x18000000,                     /* Phi increment for approx 32k operation */
+		0x8000,0x8000,                  /* Volume controls are unused at this time */
+		0x8000,0x8000
+	};
+  
+	scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&asynch_fg_tx_scb,
+					    dest,"ASYNCHFGTXCODE",parent_scb,
+					    scb_child_type);
+
+	return scb;
+}
+
+
+struct dsp_scb_descriptor * 
+cs46xx_dsp_create_asynch_fg_rx_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest,
+                                   u16 hfg_scb_address,
+                                   u16 asynch_buffer_address,
+                                   struct dsp_scb_descriptor * parent_scb,
+                                   int scb_child_type)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_scb_descriptor * scb;
+
+	struct dsp_asynch_fg_rx_scb asynch_fg_rx_scb = {
+		0xfe00,0x01ff,      /*  Prototype sample buffer size of 128 dwords */
+		0x0064,0x001c,      /* Min Delta 7 dwords == 28 bytes */
+		                    /* : Max delta 25 dwords == 100 bytes */
+		0,hfg_scb_address,  /* Point to HFG task SCB */
+		0,0,				/* Initialize current Delta and Consumer ptr adjustment count */
+		{
+			0,                /* Define the unused elements */
+			0,
+			0,
+			0,
+			0
+		},
+      
+		0,0,
+		0,dest,
+    
+		RSCONFIG_MODULO_128 |
+        RSCONFIG_SAMPLE_16STEREO,                         /* Stereo, 128 dword */
+		( (asynch_buffer_address + (16 * 4))  << 0x10),   /* This should be automagically 
+							                                  synchrinized to the producer pointer */
+    
+		/* There is no correct initial value, it will depend upon the detected
+		   rate etc  */
+		0x18000000,         
+
+		/* Set IEC958 input volume */
+		0xffff - ins->spdif_input_volume_right,0xffff - ins->spdif_input_volume_left,
+		0xffff - ins->spdif_input_volume_right,0xffff - ins->spdif_input_volume_left,
+	};
+
+	scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&asynch_fg_rx_scb,
+					    dest,"ASYNCHFGRXCODE",parent_scb,
+					    scb_child_type);
+
+	return scb;
+}
+
+
+#if 0 /* not used */
+struct dsp_scb_descriptor * 
+cs46xx_dsp_create_output_snoop_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest,
+                                   u16 snoop_buffer_address,
+                                   struct dsp_scb_descriptor * snoop_scb,
+                                   struct dsp_scb_descriptor * parent_scb,
+                                   int scb_child_type)
+{
+
+	struct dsp_scb_descriptor * scb;
+  
+	struct dsp_output_snoop_scb output_snoop_scb = {
+		{ 0,	/*  not used.  Zero */
+		  0,
+		  0,
+		  0,
+		},
+		{
+			0, /* not used.  Zero */
+			0,
+			0,
+			0,
+			0
+		},
+    
+		0,0,
+		0,0,
+    
+		RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64,
+		snoop_buffer_address << 0x10,  
+		0,0,
+		0,
+		0,snoop_scb->address
+	};
+  
+	scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&output_snoop_scb,
+					    dest,"OUTPUTSNOOP",parent_scb,
+					    scb_child_type);
+	return scb;
+}
+#endif /* not used */
+
+
+struct dsp_scb_descriptor * 
+cs46xx_dsp_create_spio_write_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest,
+                                 struct dsp_scb_descriptor * parent_scb,
+                                 int scb_child_type)
+{
+	struct dsp_scb_descriptor * scb;
+  
+	struct dsp_spio_write_scb spio_write_scb = {
+		0,0,         /*   SPIOWAddress2:SPIOWAddress1; */
+		0,           /*   SPIOWData1; */
+		0,           /*   SPIOWData2; */
+		0,0,         /*   SPIOWAddress4:SPIOWAddress3; */
+		0,           /*   SPIOWData3; */
+		0,           /*   SPIOWData4; */
+		0,0,         /*   SPIOWDataPtr:Unused1; */
+		{ 0,0 },     /*   Unused2[2]; */
+    
+		0,0,	     /*   SPIOWChildPtr:SPIOWSiblingPtr; */
+		0,0,         /*   SPIOWThisPtr:SPIOWEntryPoint; */
+    
+		{ 
+			0,
+			0,
+			0,
+			0,
+			0          /*   Unused3[5];  */
+		}
+	};
+
+	scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&spio_write_scb,
+					    dest,"SPIOWRITE",parent_scb,
+					    scb_child_type);
+
+	return scb;
+}
+
+struct dsp_scb_descriptor *
+cs46xx_dsp_create_magic_snoop_scb(struct snd_cs46xx * chip, char * scb_name, u32 dest,
+				  u16 snoop_buffer_address,
+				  struct dsp_scb_descriptor * snoop_scb,
+				  struct dsp_scb_descriptor * parent_scb,
+				  int scb_child_type)
+{
+	struct dsp_scb_descriptor * scb;
+  
+	struct dsp_magic_snoop_task magic_snoop_scb = {
+		/* 0 */ 0, /* i0 */
+		/* 1 */ 0, /* i1 */
+		/* 2 */ snoop_buffer_address << 0x10,
+		/* 3 */ 0,snoop_scb->address,
+		/* 4 */ 0, /* i3 */
+		/* 5 */ 0, /* i4 */
+		/* 6 */ 0, /* i5 */
+		/* 7 */ 0, /* i6 */
+		/* 8 */ 0, /* i7 */
+		/* 9 */ 0,0, /* next_scb, sub_list_ptr */
+		/* A */ 0,0, /* entry_point, this_ptr */
+		/* B */ RSCONFIG_SAMPLE_16STEREO + RSCONFIG_MODULO_64,
+		/* C */ snoop_buffer_address  << 0x10,
+		/* D */ 0,
+		/* E */ { 0x8000,0x8000,
+	        /* F */   0xffff,0xffff
+		}
+	};
+
+	scb = cs46xx_dsp_create_generic_scb(chip,scb_name,(u32 *)&magic_snoop_scb,
+					    dest,"MAGICSNOOPTASK",parent_scb,
+					    scb_child_type);
+
+	return scb;
+}
+
+static struct dsp_scb_descriptor *
+find_next_free_scb (struct snd_cs46xx * chip, struct dsp_scb_descriptor * from)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_scb_descriptor * scb = from;
+
+	while (scb->next_scb_ptr != ins->the_null_scb) {
+		if (snd_BUG_ON(!scb->next_scb_ptr))
+			return NULL;
+
+		scb = scb->next_scb_ptr;
+	}
+
+	return scb;
+}
+
+static u32 pcm_reader_buffer_addr[DSP_MAX_PCM_CHANNELS] = {
+	0x0600, /* 1 */
+	0x1500, /* 2 */
+	0x1580, /* 3 */
+	0x1600, /* 4 */
+	0x1680, /* 5 */
+	0x1700, /* 6 */
+	0x1780, /* 7 */
+	0x1800, /* 8 */
+	0x1880, /* 9 */
+	0x1900, /* 10 */
+	0x1980, /* 11 */
+	0x1A00, /* 12 */
+	0x1A80, /* 13 */
+	0x1B00, /* 14 */
+	0x1B80, /* 15 */
+	0x1C00, /* 16 */
+	0x1C80, /* 17 */
+	0x1D00, /* 18 */
+	0x1D80, /* 19 */
+	0x1E00, /* 20 */
+	0x1E80, /* 21 */
+	0x1F00, /* 22 */
+	0x1F80, /* 23 */
+	0x2000, /* 24 */
+	0x2080, /* 25 */
+	0x2100, /* 26 */
+	0x2180, /* 27 */
+	0x2200, /* 28 */
+	0x2280, /* 29 */
+	0x2300, /* 30 */
+	0x2380, /* 31 */
+	0x2400, /* 32 */
+};
+
+static u32 src_output_buffer_addr[DSP_MAX_SRC_NR] = {
+	0x2B80,
+	0x2BA0,
+	0x2BC0,
+	0x2BE0,
+	0x2D00,  
+	0x2D20,  
+	0x2D40,  
+	0x2D60,
+	0x2D80,
+	0x2DA0,
+	0x2DC0,
+	0x2DE0,
+	0x2E00,
+	0x2E20
+};
+
+static u32 src_delay_buffer_addr[DSP_MAX_SRC_NR] = {
+	0x2480,
+	0x2500,
+	0x2580,
+	0x2600,
+	0x2680,
+	0x2700,
+	0x2780,
+	0x2800,
+	0x2880,
+	0x2900,
+	0x2980,
+	0x2A00,
+	0x2A80,
+	0x2B00
+};
+
+struct dsp_pcm_channel_descriptor *
+cs46xx_dsp_create_pcm_channel (struct snd_cs46xx * chip,
+			       u32 sample_rate, void * private_data, 
+			       u32 hw_dma_addr,
+			       int pcm_channel_id)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_scb_descriptor * src_scb = NULL, * pcm_scb, * mixer_scb = NULL;
+	struct dsp_scb_descriptor * src_parent_scb = NULL;
+
+	/* struct dsp_scb_descriptor * pcm_parent_scb; */
+	char scb_name[DSP_MAX_SCB_NAME];
+	int i, pcm_index = -1, insert_point, src_index = -1, pass_through = 0;
+	unsigned long flags;
+
+	switch (pcm_channel_id) {
+	case DSP_PCM_MAIN_CHANNEL:
+		mixer_scb = ins->master_mix_scb;
+		break;
+	case DSP_PCM_REAR_CHANNEL:
+		mixer_scb = ins->rear_mix_scb;
+		break;
+	case DSP_PCM_CENTER_LFE_CHANNEL:
+		mixer_scb = ins->center_lfe_mix_scb;
+		break;
+	case DSP_PCM_S71_CHANNEL:
+		/* TODO */
+		snd_BUG();
+		break;
+	case DSP_IEC958_CHANNEL:
+		if (snd_BUG_ON(!ins->asynch_tx_scb))
+			return NULL;
+		mixer_scb = ins->asynch_tx_scb;
+
+		/* if sample rate is set to 48khz we pass
+		   the Sample Rate Converted (which could
+		   alter the raw data stream ...) */
+		if (sample_rate == 48000) {
+			snd_printdd ("IEC958 pass through\n");
+			/* Hack to bypass creating a new SRC */
+			pass_through = 1;
+		}
+		break;
+	default:
+		snd_BUG();
+		return NULL;
+	}
+	/* default sample rate is 44100 */
+	if (!sample_rate) sample_rate = 44100;
+
+	/* search for a already created SRC SCB with the same sample rate */
+	for (i = 0; i < DSP_MAX_PCM_CHANNELS && 
+		     (pcm_index == -1 || src_scb == NULL); ++i) {
+
+		/* virtual channel reserved 
+		   for capture */
+		if (i == CS46XX_DSP_CAPTURE_CHANNEL) continue;
+
+		if (ins->pcm_channels[i].active) {
+			if (!src_scb && 
+			    ins->pcm_channels[i].sample_rate == sample_rate &&
+			    ins->pcm_channels[i].mixer_scb == mixer_scb) {
+				src_scb = ins->pcm_channels[i].src_scb;
+				ins->pcm_channels[i].src_scb->ref_count ++;
+				src_index = ins->pcm_channels[i].src_slot;
+			}
+		} else if (pcm_index == -1) {
+			pcm_index = i;
+		}
+	}
+
+	if (pcm_index == -1) {
+		snd_printk (KERN_ERR "dsp_spos: no free PCM channel\n");
+		return NULL;
+	}
+
+	if (src_scb == NULL) {
+		if (ins->nsrc_scb >= DSP_MAX_SRC_NR) {
+			snd_printk(KERN_ERR "dsp_spos: to many SRC instances\n!");
+			return NULL;
+		}
+
+		/* find a free slot */
+		for (i = 0; i < DSP_MAX_SRC_NR; ++i) {
+			if (ins->src_scb_slots[i] == 0) {
+				src_index = i;
+				ins->src_scb_slots[i] = 1;
+				break;
+			}
+		}
+		if (snd_BUG_ON(src_index == -1))
+			return NULL;
+
+		/* we need to create a new SRC SCB */
+		if (mixer_scb->sub_list_ptr == ins->the_null_scb) {
+			src_parent_scb = mixer_scb;
+			insert_point = SCB_ON_PARENT_SUBLIST_SCB;
+		} else {
+			src_parent_scb = find_next_free_scb(chip,mixer_scb->sub_list_ptr);
+			insert_point = SCB_ON_PARENT_NEXT_SCB;
+		}
+
+		snprintf (scb_name,DSP_MAX_SCB_NAME,"SrcTask_SCB%d",src_index);
+		
+		snd_printdd( "dsp_spos: creating SRC \"%s\"\n",scb_name);
+		src_scb = cs46xx_dsp_create_src_task_scb(chip,scb_name,
+							 sample_rate,
+							 src_output_buffer_addr[src_index],
+							 src_delay_buffer_addr[src_index],
+							 /* 0x400 - 0x600 source SCBs */
+							 0x400 + (src_index * 0x10) ,
+							 src_parent_scb,
+							 insert_point,
+							 pass_through);
+
+		if (!src_scb) {
+			snd_printk (KERN_ERR "dsp_spos: failed to create SRCtaskSCB\n");
+			return NULL;
+		}
+
+		/* cs46xx_dsp_set_src_sample_rate(chip,src_scb,sample_rate); */
+
+		ins->nsrc_scb ++;
+	} 
+  
+  
+	snprintf (scb_name,DSP_MAX_SCB_NAME,"PCMReader_SCB%d",pcm_index);
+
+	snd_printdd( "dsp_spos: creating PCM \"%s\" (%d)\n",scb_name,
+                 pcm_channel_id);
+
+	pcm_scb = cs46xx_dsp_create_pcm_reader_scb(chip,scb_name,
+						   pcm_reader_buffer_addr[pcm_index],
+						   /* 0x200 - 400 PCMreader SCBs */
+						   (pcm_index * 0x10) + 0x200,
+						   pcm_index,    /* virtual channel 0-31 */
+						   hw_dma_addr,  /* pcm hw addr */
+                           NULL,         /* parent SCB ptr */
+                           0             /* insert point */ 
+                           );
+
+	if (!pcm_scb) {
+		snd_printk (KERN_ERR "dsp_spos: failed to create PCMreaderSCB\n");
+		return NULL;
+	}
+	
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	ins->pcm_channels[pcm_index].sample_rate = sample_rate;
+	ins->pcm_channels[pcm_index].pcm_reader_scb = pcm_scb;
+	ins->pcm_channels[pcm_index].src_scb = src_scb;
+	ins->pcm_channels[pcm_index].unlinked = 1;
+	ins->pcm_channels[pcm_index].private_data = private_data;
+	ins->pcm_channels[pcm_index].src_slot = src_index;
+	ins->pcm_channels[pcm_index].active = 1;
+	ins->pcm_channels[pcm_index].pcm_slot = pcm_index;
+	ins->pcm_channels[pcm_index].mixer_scb = mixer_scb;
+	ins->npcm_channels ++;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	return (ins->pcm_channels + pcm_index);
+}
+
+int cs46xx_dsp_pcm_channel_set_period (struct snd_cs46xx * chip,
+				       struct dsp_pcm_channel_descriptor * pcm_channel,
+				       int period_size)
+{
+	u32 temp = snd_cs46xx_peek (chip,pcm_channel->pcm_reader_scb->address << 2);
+	temp &= ~DMA_RQ_C1_SOURCE_SIZE_MASK;
+
+	switch (period_size) {
+	case 2048:
+		temp |= DMA_RQ_C1_SOURCE_MOD1024;
+		break;
+	case 1024:
+		temp |= DMA_RQ_C1_SOURCE_MOD512;
+		break;
+	case 512:
+		temp |= DMA_RQ_C1_SOURCE_MOD256;
+		break;
+	case 256:
+		temp |= DMA_RQ_C1_SOURCE_MOD128;
+		break;
+	case 128:
+		temp |= DMA_RQ_C1_SOURCE_MOD64;
+		break;
+	case 64:
+		temp |= DMA_RQ_C1_SOURCE_MOD32;
+		break;		      
+	case 32:
+		temp |= DMA_RQ_C1_SOURCE_MOD16;
+		break; 
+	default:
+		snd_printdd ("period size (%d) not supported by HW\n", period_size);
+		return -EINVAL;
+	}
+
+	snd_cs46xx_poke (chip,pcm_channel->pcm_reader_scb->address << 2,temp);
+
+	return 0;
+}
+
+int cs46xx_dsp_pcm_ostream_set_period (struct snd_cs46xx * chip,
+				       int period_size)
+{
+	u32 temp = snd_cs46xx_peek (chip,WRITEBACK_SCB_ADDR << 2);
+	temp &= ~DMA_RQ_C1_DEST_SIZE_MASK;
+
+	switch (period_size) {
+	case 2048:
+		temp |= DMA_RQ_C1_DEST_MOD1024;
+		break;
+	case 1024:
+		temp |= DMA_RQ_C1_DEST_MOD512;
+		break;
+	case 512:
+		temp |= DMA_RQ_C1_DEST_MOD256;
+		break;
+	case 256:
+		temp |= DMA_RQ_C1_DEST_MOD128;
+		break;
+	case 128:
+		temp |= DMA_RQ_C1_DEST_MOD64;
+		break;
+	case 64:
+		temp |= DMA_RQ_C1_DEST_MOD32;
+		break;		      
+	case 32:
+		temp |= DMA_RQ_C1_DEST_MOD16;
+		break; 
+	default:
+		snd_printdd ("period size (%d) not supported by HW\n", period_size);
+		return -EINVAL;
+	}
+
+	snd_cs46xx_poke (chip,WRITEBACK_SCB_ADDR << 2,temp);
+
+	return 0;
+}
+
+void cs46xx_dsp_destroy_pcm_channel (struct snd_cs46xx * chip,
+				     struct dsp_pcm_channel_descriptor * pcm_channel)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!pcm_channel->active ||
+		       ins->npcm_channels <= 0 ||
+		       pcm_channel->src_scb->ref_count <= 0))
+		return;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	pcm_channel->unlinked = 1;
+	pcm_channel->active = 0;
+	pcm_channel->private_data = NULL;
+	pcm_channel->src_scb->ref_count --;
+	ins->npcm_channels --;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	cs46xx_dsp_remove_scb(chip,pcm_channel->pcm_reader_scb);
+
+	if (!pcm_channel->src_scb->ref_count) {
+		cs46xx_dsp_remove_scb(chip,pcm_channel->src_scb);
+
+		if (snd_BUG_ON(pcm_channel->src_slot < 0 ||
+			       pcm_channel->src_slot >= DSP_MAX_SRC_NR))
+			return;
+
+		ins->src_scb_slots[pcm_channel->src_slot] = 0;
+		ins->nsrc_scb --;
+	}
+}
+
+int cs46xx_dsp_pcm_unlink (struct snd_cs46xx * chip,
+			   struct dsp_pcm_channel_descriptor * pcm_channel)
+{
+	unsigned long flags;
+
+	if (snd_BUG_ON(!pcm_channel->active ||
+		       chip->dsp_spos_instance->npcm_channels <= 0))
+		return -EIO;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (pcm_channel->unlinked) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return -EIO;
+	}
+
+	pcm_channel->unlinked = 1;
+
+	_dsp_unlink_scb (chip,pcm_channel->pcm_reader_scb);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	return 0;
+}
+
+int cs46xx_dsp_pcm_link (struct snd_cs46xx * chip,
+			 struct dsp_pcm_channel_descriptor * pcm_channel)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_scb_descriptor * parent_scb;
+	struct dsp_scb_descriptor * src_scb = pcm_channel->src_scb;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	if (pcm_channel->unlinked == 0) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return -EIO;
+	}
+
+	parent_scb = src_scb;
+
+	if (src_scb->sub_list_ptr != ins->the_null_scb) {
+		src_scb->sub_list_ptr->parent_scb_ptr = pcm_channel->pcm_reader_scb;
+		pcm_channel->pcm_reader_scb->next_scb_ptr = src_scb->sub_list_ptr;
+	}
+
+	src_scb->sub_list_ptr = pcm_channel->pcm_reader_scb;
+
+	snd_BUG_ON(pcm_channel->pcm_reader_scb->parent_scb_ptr);
+	pcm_channel->pcm_reader_scb->parent_scb_ptr = parent_scb;
+
+	/* update SCB entry in DSP RAM */
+	cs46xx_dsp_spos_update_scb(chip,pcm_channel->pcm_reader_scb);
+
+	/* update parent SCB entry */
+	cs46xx_dsp_spos_update_scb(chip,parent_scb);
+
+	pcm_channel->unlinked = 0;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+struct dsp_scb_descriptor *
+cs46xx_add_record_source (struct snd_cs46xx *chip, struct dsp_scb_descriptor * source,
+			  u16 addr, char * scb_name)
+{
+  	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_scb_descriptor * parent;
+	struct dsp_scb_descriptor * pcm_input;
+	int insert_point;
+
+	if (snd_BUG_ON(!ins->record_mixer_scb))
+		return NULL;
+
+	if (ins->record_mixer_scb->sub_list_ptr != ins->the_null_scb) {
+		parent = find_next_free_scb (chip,ins->record_mixer_scb->sub_list_ptr);
+		insert_point = SCB_ON_PARENT_NEXT_SCB;
+	} else {
+		parent = ins->record_mixer_scb;
+		insert_point = SCB_ON_PARENT_SUBLIST_SCB;
+	}
+
+	pcm_input = cs46xx_dsp_create_pcm_serial_input_scb(chip,scb_name,addr,
+							   source, parent,
+							   insert_point);
+
+	return pcm_input;
+}
+
+int cs46xx_src_unlink(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src)
+{
+	unsigned long flags;
+
+	if (snd_BUG_ON(!src->parent_scb_ptr))
+		return -EINVAL;
+
+	/* mute SCB */
+	cs46xx_dsp_scb_set_volume (chip,src,0,0);
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	_dsp_unlink_scb (chip,src);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	return 0;
+}
+
+int cs46xx_src_link(struct snd_cs46xx *chip, struct dsp_scb_descriptor * src)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+	struct dsp_scb_descriptor * parent_scb;
+
+	if (snd_BUG_ON(src->parent_scb_ptr))
+		return -EINVAL;
+	if (snd_BUG_ON(!ins->master_mix_scb))
+		return -EINVAL;
+
+	if (ins->master_mix_scb->sub_list_ptr != ins->the_null_scb) {
+		parent_scb = find_next_free_scb (chip,ins->master_mix_scb->sub_list_ptr);
+		parent_scb->next_scb_ptr = src;
+	} else {
+		parent_scb = ins->master_mix_scb;
+		parent_scb->sub_list_ptr = src;
+	}
+
+	src->parent_scb_ptr = parent_scb;
+
+	/* update entry in DSP RAM */
+	cs46xx_dsp_spos_update_scb(chip,parent_scb);
+  
+	return 0;
+}
+
+int cs46xx_dsp_enable_spdif_out (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if ( ! (ins->spdif_status_out & DSP_SPDIF_STATUS_HW_ENABLED) ) {
+		cs46xx_dsp_enable_spdif_hw (chip);
+	}
+
+	/* dont touch anything if SPDIF is open */
+	if ( ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN) {
+		/* when cs46xx_iec958_post_close(...) is called it
+		   will call this function if necessary depending on
+		   this bit */
+		ins->spdif_status_out |= DSP_SPDIF_STATUS_OUTPUT_ENABLED;
+
+		return -EBUSY;
+	}
+
+	if (snd_BUG_ON(ins->asynch_tx_scb))
+		return -EINVAL;
+	if (snd_BUG_ON(ins->master_mix_scb->next_scb_ptr !=
+		       ins->the_null_scb))
+		return -EINVAL;
+
+	/* reset output snooper sample buffer pointer */
+	snd_cs46xx_poke (chip, (ins->ref_snoop_scb->address + 2) << 2,
+			 (OUTPUT_SNOOP_BUFFER + 0x10) << 0x10 );
+	
+	/* The asynch. transfer task */
+	ins->asynch_tx_scb = cs46xx_dsp_create_asynch_fg_tx_scb(chip,"AsynchFGTxSCB",ASYNCTX_SCB_ADDR,
+								SPDIFO_SCB_INST,
+								SPDIFO_IP_OUTPUT_BUFFER1,
+								ins->master_mix_scb,
+								SCB_ON_PARENT_NEXT_SCB);
+	if (!ins->asynch_tx_scb) return -ENOMEM;
+
+	ins->spdif_pcm_input_scb = cs46xx_dsp_create_pcm_serial_input_scb(chip,"PCMSerialInput_II",
+									  PCMSERIALINII_SCB_ADDR,
+									  ins->ref_snoop_scb,
+									  ins->asynch_tx_scb,
+									  SCB_ON_PARENT_SUBLIST_SCB);
+  
+	
+	if (!ins->spdif_pcm_input_scb) return -ENOMEM;
+
+	/* monitor state */
+	ins->spdif_status_out |= DSP_SPDIF_STATUS_OUTPUT_ENABLED;
+
+	return 0;
+}
+
+int  cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	/* dont touch anything if SPDIF is open */
+	if ( ins->spdif_status_out & DSP_SPDIF_STATUS_PLAYBACK_OPEN) {
+		ins->spdif_status_out &= ~DSP_SPDIF_STATUS_OUTPUT_ENABLED;
+		return -EBUSY;
+	}
+
+	/* check integrety */
+	if (snd_BUG_ON(!ins->asynch_tx_scb))
+		return -EINVAL;
+	if (snd_BUG_ON(!ins->spdif_pcm_input_scb))
+		return -EINVAL;
+	if (snd_BUG_ON(ins->master_mix_scb->next_scb_ptr != ins->asynch_tx_scb))
+		return -EINVAL;
+	if (snd_BUG_ON(ins->asynch_tx_scb->parent_scb_ptr !=
+		       ins->master_mix_scb))
+		return -EINVAL;
+
+	cs46xx_dsp_remove_scb (chip,ins->spdif_pcm_input_scb);
+	cs46xx_dsp_remove_scb (chip,ins->asynch_tx_scb);
+
+	ins->spdif_pcm_input_scb = NULL;
+	ins->asynch_tx_scb = NULL;
+
+	/* clear buffer to prevent any undesired noise */
+	_dsp_clear_sample_buffer(chip,SPDIFO_IP_OUTPUT_BUFFER1,256);
+
+	/* monitor state */
+	ins->spdif_status_out  &= ~DSP_SPDIF_STATUS_OUTPUT_ENABLED;
+
+
+	return 0;
+}
+
+int cs46xx_iec958_pre_open (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if ( ins->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED ) {
+		/* remove AsynchFGTxSCB and and PCMSerialInput_II */
+		cs46xx_dsp_disable_spdif_out (chip);
+
+		/* save state */
+		ins->spdif_status_out |= DSP_SPDIF_STATUS_OUTPUT_ENABLED;
+	}
+	
+	/* if not enabled already */
+	if ( !(ins->spdif_status_out & DSP_SPDIF_STATUS_HW_ENABLED) ) {
+		cs46xx_dsp_enable_spdif_hw (chip);
+	}
+
+	/* Create the asynch. transfer task  for playback */
+	ins->asynch_tx_scb = cs46xx_dsp_create_asynch_fg_tx_scb(chip,"AsynchFGTxSCB",ASYNCTX_SCB_ADDR,
+								SPDIFO_SCB_INST,
+								SPDIFO_IP_OUTPUT_BUFFER1,
+								ins->master_mix_scb,
+								SCB_ON_PARENT_NEXT_SCB);
+
+
+	/* set spdif channel status value for streaming */
+	cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_stream);
+
+	ins->spdif_status_out  |= DSP_SPDIF_STATUS_PLAYBACK_OPEN;
+
+	return 0;
+}
+
+int cs46xx_iec958_post_close (struct snd_cs46xx *chip)
+{
+	struct dsp_spos_instance * ins = chip->dsp_spos_instance;
+
+	if (snd_BUG_ON(!ins->asynch_tx_scb))
+		return -EINVAL;
+
+	ins->spdif_status_out  &= ~DSP_SPDIF_STATUS_PLAYBACK_OPEN;
+
+	/* restore settings */
+	cs46xx_poke_via_dsp (chip,SP_SPDOUT_CSUV, ins->spdif_csuv_default);
+	
+	/* deallocate stuff */
+	if (ins->spdif_pcm_input_scb != NULL) {
+		cs46xx_dsp_remove_scb (chip,ins->spdif_pcm_input_scb);
+		ins->spdif_pcm_input_scb = NULL;
+	}
+
+	cs46xx_dsp_remove_scb (chip,ins->asynch_tx_scb);
+	ins->asynch_tx_scb = NULL;
+
+	/* clear buffer to prevent any undesired noise */
+	_dsp_clear_sample_buffer(chip,SPDIFO_IP_OUTPUT_BUFFER1,256);
+
+	/* restore state */
+	if ( ins->spdif_status_out & DSP_SPDIF_STATUS_OUTPUT_ENABLED ) {
+		cs46xx_dsp_enable_spdif_out (chip);
+	}
+	
+	return 0;
+}
diff --git a/linux/sound/pci/cs46xx/imgs/cwc4630.h b/linux/sound/pci/cs46xx/imgs/cwc4630.h
new file mode 100644
index 0000000..37c4f13
--- /dev/null
+++ b/linux/sound/pci/cs46xx/imgs/cwc4630.h
@@ -0,0 +1,320 @@
+/* generated from cwc4630.osp DO NOT MODIFY */
+
+#ifndef __HEADER_cwc4630_H__
+#define __HEADER_cwc4630_H__
+
+static struct dsp_symbol_entry cwc4630_symbols[] = {
+  { 0x0000, "BEGINADDRESS",0x00 },
+  { 0x8000, "EXECCHILD",0x03 },
+  { 0x8001, "EXECCHILD_98",0x03 },
+  { 0x8003, "EXECCHILD_PUSH1IND",0x03 },
+  { 0x8008, "EXECSIBLING",0x03 },
+  { 0x800a, "EXECSIBLING_298",0x03 },
+  { 0x800b, "EXECSIBLING_2IND1",0x03 },
+  { 0x8010, "TIMINGMASTER",0x03 },
+  { 0x804f, "S16_CODECINPUTTASK",0x03 },
+  { 0x805e, "PCMSERIALINPUTTASK",0x03 },
+  { 0x806d, "S16_MIX_TO_OSTREAM",0x03 },
+  { 0x809a, "S16_MIX",0x03 },
+  { 0x80bb, "S16_UPSRC",0x03 },
+  { 0x813b, "MIX3_EXP",0x03 },
+  { 0x8164, "DECIMATEBYPOW2",0x03 },
+  { 0x8197, "VARIDECIMATE",0x03 },
+  { 0x81f2, "_3DINPUTTASK",0x03 },
+  { 0x820a, "_3DPRLGCINPTASK",0x03 },
+  { 0x8227, "_3DSTEREOINPUTTASK",0x03 },
+  { 0x8242, "_3DOUTPUTTASK",0x03 },
+  { 0x82c4, "HRTF_MORPH_TASK",0x03 },
+  { 0x82c6, "WAIT4DATA",0x03 },
+  { 0x82fa, "PROLOGIC",0x03 },
+  { 0x8496, "DECORRELATOR",0x03 },
+  { 0x84a4, "STEREO2MONO",0x03 },
+  { 0x0070, "SPOSCB",0x02 },
+  { 0x0107, "TASKTREETHREAD",0x03 },
+  { 0x013c, "TASKTREEHEADERCODE",0x03 },
+  { 0x0145, "FGTASKTREEHEADERCODE",0x03 },
+  { 0x0169, "NULLALGORITHM",0x03 },
+  { 0x016d, "HFGEXECCHILD",0x03 },
+  { 0x016e, "HFGEXECCHILD_98",0x03 },
+  { 0x0170, "HFGEXECCHILD_PUSH1IND",0x03 },
+  { 0x0173, "HFGEXECSIBLING",0x03 },
+  { 0x0175, "HFGEXECSIBLING_298",0x03 },
+  { 0x0176, "HFGEXECSIBLING_2IND1",0x03 },
+  { 0x0179, "S16_CODECOUTPUTTASK",0x03 },
+  { 0x0194, "#CODE_END",0x00 },
+}; /* cwc4630 symbols */
+
+static u32 cwc4630_code[] = {
+/* BEGINADDRESS */
+/* 0000 */ 0x00040730,0x00001002,0x000f619e,0x00001003,
+/* 0002 */ 0x00001705,0x00001400,0x000a411e,0x00001003,
+/* 0004 */ 0x00040730,0x00001002,0x000f619e,0x00001003,
+/* 0006 */ 0x00009705,0x00001400,0x000a411e,0x00001003,
+/* 0008 */ 0x00040730,0x00001002,0x000f619e,0x00001003,
+/* 000A */ 0x00011705,0x00001400,0x000a411e,0x00001003,
+/* 000C */ 0x00040730,0x00001002,0x000f619e,0x00001003,
+/* 000E */ 0x00019705,0x00001400,0x000a411e,0x00001003,
+/* 0010 */ 0x00040730,0x00001002,0x000f619e,0x00001003,
+/* 0012 */ 0x00021705,0x00001400,0x000a411e,0x00001003,
+/* 0014 */ 0x00040730,0x00001002,0x000f619e,0x00001003,
+/* 0016 */ 0x00029705,0x00001400,0x000a411e,0x00001003,
+/* 0018 */ 0x00040730,0x00001002,0x000f619e,0x00001003,
+/* 001A */ 0x00031705,0x00001400,0x000a411e,0x00001003,
+/* 001C */ 0x00040730,0x00001002,0x000f619e,0x00001003,
+/* 001E */ 0x00039705,0x00001400,0x000a411e,0x00001003,
+/* 0020 */ 0x000fe19e,0x00001003,0x0009c730,0x00001003,
+/* 0022 */ 0x0008e19c,0x00001003,0x000083c1,0x00093040,
+/* 0024 */ 0x00098730,0x00001002,0x000ee19e,0x00001003,
+/* 0026 */ 0x00009705,0x00001400,0x000a211e,0x00001003,
+/* 0028 */ 0x00098730,0x00001002,0x000ee19e,0x00001003,
+/* 002A */ 0x00011705,0x00001400,0x000a211e,0x00001003,
+/* 002C */ 0x00098730,0x00001002,0x000ee19e,0x00001003,
+/* 002E */ 0x00019705,0x00001400,0x000a211e,0x00001003,
+/* 0030 */ 0x00098730,0x00001002,0x000ee19e,0x00001003,
+/* 0032 */ 0x00021705,0x00001400,0x000a211e,0x00001003,
+/* 0034 */ 0x00098730,0x00001002,0x000ee19e,0x00001003,
+/* 0036 */ 0x00029705,0x00001400,0x000a211e,0x00001003,
+/* 0038 */ 0x00098730,0x00001002,0x000ee19e,0x00001003,
+/* 003A */ 0x00031705,0x00001400,0x000a211e,0x00001003,
+/* 003C */ 0x00098730,0x00001002,0x000ee19e,0x00001003,
+/* 003E */ 0x00039705,0x00001400,0x000a211e,0x00001003,
+/* 0040 */ 0x0001a730,0x00001008,0x000e2730,0x00001002,
+/* 0042 */ 0x0000a731,0x00001002,0x0000a731,0x00001002,
+/* 0044 */ 0x0000a731,0x00001002,0x0000a731,0x00001002,
+/* 0046 */ 0x0000a731,0x00001002,0x0000a731,0x00001002,
+/* 0048 */ 0x00000000,0x00000000,0x000f619c,0x00001003,
+/* 004A */ 0x0007f801,0x000c0000,0x00000037,0x00001000,
+/* 004C */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 004E */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0050 */ 0x00000000,0x000c0000,0x00000000,0x00000000,
+/* 0052 */ 0x0000373c,0x00001000,0x00000000,0x00000000,
+/* 0054 */ 0x000ee19c,0x00001003,0x0007f801,0x000c0000,
+/* 0056 */ 0x00000037,0x00001000,0x00000000,0x00000000,
+/* 0058 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 005A */ 0x00000000,0x00000000,0x0000273c,0x00001000,
+/* 005C */ 0x00000033,0x00001000,0x000e679e,0x00001003,
+/* 005E */ 0x00007705,0x00001400,0x000ac71e,0x00001003,
+/* 0060 */ 0x00087fc1,0x000c3be0,0x0007f801,0x000c0000,
+/* 0062 */ 0x00000037,0x00001000,0x00000000,0x00000000,
+/* 0064 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0066 */ 0x00000000,0x00000000,0x0000a730,0x00001003,
+/* 0068 */ 0x00000033,0x00001000,0x0007f801,0x000c0000,
+/* 006A */ 0x00000037,0x00001000,0x00000000,0x00000000,
+/* 006C */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 006E */ 0x00000000,0x00000000,0x00000000,0x000c0000,
+/* 0070 */ 0x00000032,0x00001000,0x0000273d,0x00001000,
+/* 0072 */ 0x0004a730,0x00001003,0x00000f41,0x00097140,
+/* 0074 */ 0x0000a841,0x0009b240,0x0000a0c1,0x0009f040,
+/* 0076 */ 0x0001c641,0x00093540,0x0001cec1,0x0009b5c0,
+/* 0078 */ 0x00000000,0x00000000,0x0001bf05,0x0003fc40,
+/* 007A */ 0x00002725,0x000aa400,0x00013705,0x00093a00,
+/* 007C */ 0x0000002e,0x0009d6c0,0x0002ef8a,0x00000000,
+/* 007E */ 0x00040630,0x00001004,0x0004ef0a,0x000eb785,
+/* 0080 */ 0x0003fc8a,0x00000000,0x00000000,0x000c70e0,
+/* 0082 */ 0x0007d182,0x0002c640,0x00008630,0x00001004,
+/* 0084 */ 0x000799b8,0x0002c6c0,0x00031705,0x00092240,
+/* 0086 */ 0x00039f05,0x000932c0,0x0003520a,0x00000000,
+/* 0088 */ 0x00070731,0x0000100b,0x00010705,0x000b20c0,
+/* 008A */ 0x00000000,0x000eba44,0x00032108,0x000c60c4,
+/* 008C */ 0x00065208,0x000c2917,0x000486b0,0x00001007,
+/* 008E */ 0x00012f05,0x00036880,0x0002818e,0x000c0000,
+/* 0090 */ 0x0004410a,0x00000000,0x00048630,0x00001007,
+/* 0092 */ 0x00029705,0x000c0000,0x00000000,0x00000000,
+/* 0094 */ 0x00003fc1,0x0003fc40,0x000037c1,0x00091b40,
+/* 0096 */ 0x00003fc1,0x000911c0,0x000037c1,0x000957c0,
+/* 0098 */ 0x00003fc1,0x000951c0,0x000037c1,0x00000000,
+/* 009A */ 0x00003fc1,0x000991c0,0x000037c1,0x00000000,
+/* 009C */ 0x00003fc1,0x0009d1c0,0x000037c1,0x00000000,
+/* 009E */ 0x0001ccc1,0x000915c0,0x0001c441,0x0009d800,
+/* 00A0 */ 0x0009cdc1,0x00091240,0x0001c541,0x00091d00,
+/* 00A2 */ 0x0009cfc1,0x00095240,0x0001c741,0x00095c80,
+/* 00A4 */ 0x000e8ca9,0x00099240,0x000e85ad,0x00095640,
+/* 00A6 */ 0x00069ca9,0x00099d80,0x000e952d,0x00099640,
+/* 00A8 */ 0x000eaca9,0x0009d6c0,0x000ea5ad,0x00091a40,
+/* 00AA */ 0x0006bca9,0x0009de80,0x000eb52d,0x00095a40,
+/* 00AC */ 0x000ecca9,0x00099ac0,0x000ec5ad,0x0009da40,
+/* 00AE */ 0x000edca9,0x0009d300,0x000a6e0a,0x00001000,
+/* 00B0 */ 0x000ed52d,0x00091e40,0x000eeca9,0x00095ec0,
+/* 00B2 */ 0x000ee5ad,0x00099e40,0x0006fca9,0x00002500,
+/* 00B4 */ 0x000fb208,0x000c59a0,0x000ef52d,0x0009de40,
+/* 00B6 */ 0x00068ca9,0x000912c1,0x000683ad,0x00095241,
+/* 00B8 */ 0x00020f05,0x000991c1,0x00000000,0x00000000,
+/* 00BA */ 0x00086f88,0x00001000,0x0009cf81,0x000b5340,
+/* 00BC */ 0x0009c701,0x000b92c0,0x0009de81,0x000bd300,
+/* 00BE */ 0x0009d601,0x000b1700,0x0001fd81,0x000b9d80,
+/* 00C0 */ 0x0009f501,0x000b57c0,0x000a0f81,0x000bd740,
+/* 00C2 */ 0x00020701,0x000b5c80,0x000a1681,0x000b97c0,
+/* 00C4 */ 0x00021601,0x00002500,0x000a0701,0x000b9b40,
+/* 00C6 */ 0x000a0f81,0x000b1bc0,0x00021681,0x00002d00,
+/* 00C8 */ 0x00020f81,0x000bd800,0x000a0701,0x000b5bc0,
+/* 00CA */ 0x00021601,0x00003500,0x000a0f81,0x000b5f40,
+/* 00CC */ 0x000a0701,0x000bdbc0,0x00021681,0x00003d00,
+/* 00CE */ 0x00020f81,0x000b1d00,0x000a0701,0x000b1fc0,
+/* 00D0 */ 0x00021601,0x00020500,0x00020f81,0x000b1341,
+/* 00D2 */ 0x000a0701,0x000b9fc0,0x00021681,0x00020d00,
+/* 00D4 */ 0x00020f81,0x000bde80,0x000a0701,0x000bdfc0,
+/* 00D6 */ 0x00021601,0x00021500,0x00020f81,0x000b9341,
+/* 00D8 */ 0x00020701,0x000b53c1,0x00021681,0x00021d00,
+/* 00DA */ 0x000a0f81,0x000d0380,0x0000b601,0x000b15c0,
+/* 00DC */ 0x00007b01,0x00000000,0x00007b81,0x000bd1c0,
+/* 00DE */ 0x00007b01,0x00000000,0x00007b81,0x000b91c0,
+/* 00E0 */ 0x00007b01,0x000b57c0,0x00007b81,0x000b51c0,
+/* 00E2 */ 0x00007b01,0x000b1b40,0x00007b81,0x000b11c0,
+/* 00E4 */ 0x00087b01,0x000c3dc0,0x0007e488,0x000d7e45,
+/* 00E6 */ 0x00000000,0x000d7a44,0x0007e48a,0x00000000,
+/* 00E8 */ 0x00011f05,0x00084080,0x00000000,0x00000000,
+/* 00EA */ 0x00001705,0x000b3540,0x00008a01,0x000bf040,
+/* 00EC */ 0x00007081,0x000bb5c0,0x00055488,0x00000000,
+/* 00EE */ 0x0000d482,0x0003fc40,0x0003fc88,0x00000000,
+/* 00F0 */ 0x0001e401,0x000b3a00,0x0001ec81,0x000bd6c0,
+/* 00F2 */ 0x0002ef88,0x000e7784,0x00056f08,0x00000000,
+/* 00F4 */ 0x000d86b0,0x00001007,0x00008281,0x000bb240,
+/* 00F6 */ 0x0000b801,0x000b7140,0x00007888,0x00000000,
+/* 00F8 */ 0x0000073c,0x00001000,0x0007f188,0x000c0000,
+/* 00FA */ 0x00000000,0x00000000,0x00055288,0x000c555c,
+/* 00FC */ 0x0005528a,0x000c0000,0x0009fa88,0x000c5d00,
+/* 00FE */ 0x0000fa88,0x00000000,0x00000032,0x00001000,
+/* 0100 */ 0x0000073d,0x00001000,0x0007f188,0x000c0000,
+/* 0102 */ 0x00000000,0x00000000,0x0008c01c,0x00001003,
+/* 0104 */ 0x00002705,0x00001008,0x0008b201,0x000c1392,
+/* 0106 */ 0x0000ba01,0x00000000,
+/* TASKTREETHREAD */
+/* 0107 */ 0x00008731,0x00001400,0x0004c108,0x000fe0c4,
+/* 0109 */ 0x00057488,0x00000000,0x000a6388,0x00001001,
+/* 010B */ 0x0008b334,0x000bc141,0x0003020e,0x00000000,
+/* 010D */ 0x000986b0,0x00001008,0x00003625,0x000c5dfa,
+/* 010F */ 0x000a638a,0x00001001,0x0008020e,0x00001002,
+/* 0111 */ 0x0009a6b0,0x00001008,0x0007f301,0x00000000,
+/* 0113 */ 0x00000000,0x00000000,0x00002725,0x000a8c40,
+/* 0115 */ 0x000000ae,0x00000000,0x000e8630,0x00001008,
+/* 0117 */ 0x00000000,0x000c74e0,0x0007d182,0x0002d640,
+/* 0119 */ 0x000b8630,0x00001008,0x000799b8,0x0002d6c0,
+/* 011B */ 0x0000748a,0x000c3ec5,0x0007420a,0x000c0000,
+/* 011D */ 0x00062208,0x000c4117,0x000a0630,0x00001009,
+/* 011F */ 0x00000000,0x000c0000,0x0001022e,0x00000000,
+/* 0121 */ 0x0006a630,0x00001009,0x00000032,0x00001000,
+/* 0123 */ 0x000ca21c,0x00001003,0x00005a02,0x00000000,
+/* 0125 */ 0x0001a630,0x00001009,0x00000000,0x000c0000,
+/* 0127 */ 0x00000036,0x00001000,0x00000000,0x00000000,
+/* 0129 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 012B */ 0x00000000,0x00000000,0x0003a730,0x00001008,
+/* 012D */ 0x0007f801,0x000c0000,0x00000037,0x00001000,
+/* 012F */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0131 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0133 */ 0x0003a730,0x00001008,0x00000033,0x00001000,
+/* 0135 */ 0x0003a705,0x00001008,0x00007a01,0x000c0000,
+/* 0137 */ 0x000e6288,0x000d550a,0x0006428a,0x00000000,
+/* 0139 */ 0x00090730,0x0000100a,0x00000000,0x000c0000,
+/* 013B */ 0x00000000,0x00000000,
+/* TASKTREEHEADERCODE */
+/* 013C */ 0x0007aab0,0x00034880,0x000a8fb0,0x0000100b,
+/* 013E */ 0x00057488,0x00000000,0x00033b94,0x00081140,
+/* 0140 */ 0x000183ae,0x00000000,0x000a86b0,0x0000100b,
+/* 0142 */ 0x00022f05,0x000c3545,0x0000eb8a,0x00000000,
+/* 0144 */ 0x00042731,0x00001003,
+/* FGTASKTREEHEADERCODE */
+/* 0145 */ 0x0007aab0,0x00034880,0x00078fb0,0x0000100a,
+/* 0147 */ 0x00057488,0x00000000,0x00033b94,0x00081140,
+/* 0149 */ 0x000183ae,0x00000000,0x000b06b0,0x0000100b,
+/* 014B */ 0x00022f05,0x00000000,0x00007401,0x00091140,
+/* 014D */ 0x00048f05,0x000951c0,0x00042731,0x00001003,
+/* 014F */ 0x0000473d,0x00001000,0x000f19b0,0x000bbc47,
+/* 0151 */ 0x00080000,0x000bffc7,0x000fe19e,0x00001003,
+/* 0153 */ 0x00000000,0x00000000,0x0008e19c,0x00001003,
+/* 0155 */ 0x000083c1,0x00093040,0x00000f41,0x00097140,
+/* 0157 */ 0x0000a841,0x0009b240,0x0000a0c1,0x0009f040,
+/* 0159 */ 0x0001c641,0x00093540,0x0001cec1,0x0009b5c0,
+/* 015B */ 0x00000000,0x000fdc44,0x00055208,0x00000000,
+/* 015D */ 0x00010705,0x000a2880,0x0000a23a,0x00093a00,
+/* 015F */ 0x0003fc8a,0x000df6c5,0x0004ef0a,0x000c0000,
+/* 0161 */ 0x00012f05,0x00036880,0x00065308,0x000c2997,
+/* 0163 */ 0x000086b0,0x0000100b,0x0004410a,0x000d40c7,
+/* 0165 */ 0x00000000,0x00000000,0x00088730,0x00001004,
+/* 0167 */ 0x00056f0a,0x000ea105,0x00000000,0x00000000,
+/* NULLALGORITHM */
+/* 0169 */ 0x0000473d,0x00001000,0x000f19b0,0x000bbc47,
+/* 016B */ 0x00080000,0x000bffc7,0x0000273d,0x00001000,
+/* HFGEXECCHILD */
+/* 016D */ 0x00000000,0x000eba44,
+/* HFGEXECCHILD_98 */
+/* 016E */ 0x00048f05,0x0000f440,0x00007401,0x0000f7c0,
+/* HFGEXECCHILD_PUSH1IND */
+/* 0170 */ 0x00000734,0x00001000,0x00010705,0x000a6880,
+/* 0172 */ 0x00006a88,0x000c75c4,
+/* HFGEXECSIBLING */
+/* 0173 */ 0x00000000,0x000e5084,0x00000000,0x000eba44,
+/* HFGEXECSIBLING_298 */
+/* 0175 */ 0x00087401,0x000e4782,
+/* HFGEXECSIBLING_2IND1 */
+/* 0176 */ 0x00000734,0x00001000,0x00010705,0x000a6880,
+/* 0178 */ 0x00006a88,0x000c75c4,
+/* S16_CODECOUTPUTTASK */
+/* 0179 */ 0x0007c108,0x000c0000,0x0007e721,0x000bed40,
+/* 017B */ 0x00005f25,0x000badc0,0x0003ba97,0x000beb80,
+/* 017D */ 0x00065590,0x000b2e00,0x00033217,0x00003ec0,
+/* 017F */ 0x00065590,0x000b8e40,0x0003ed80,0x000491c0,
+/* 0181 */ 0x00073fb0,0x00074c80,0x000583a0,0x0000100c,
+/* 0183 */ 0x000ee388,0x00042970,0x00008301,0x00021ef2,
+/* 0185 */ 0x000b8f14,0x0000000f,0x000c4d8d,0x0000001b,
+/* 0187 */ 0x000d6dc2,0x000e06c6,0x000032ac,0x000c3916,
+/* 0189 */ 0x0004edc2,0x00074c80,0x00078898,0x00001000,
+/* 018B */ 0x00038894,0x00000032,0x000c4d8d,0x00092e1b,
+/* 018D */ 0x000d6dc2,0x000e06c6,0x0004edc2,0x000c1956,
+/* 018F */ 0x0000722c,0x00034a00,0x00041705,0x0009ed40,
+/* 0191 */ 0x00058730,0x00001400,0x000d7488,0x000c3a00,
+/* 0193 */ 0x00048f05,0x00000000
+};
+/* #CODE_END */
+
+static u32 cwc4630_parameter[] = {
+/* 0000 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0004 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0008 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 000C */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0010 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0014 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0018 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 001C */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0020 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0024 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0028 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 002C */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0030 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0034 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0038 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 003C */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0040 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0044 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0048 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 004C */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0050 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0054 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0058 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 005C */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0060 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0064 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0068 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 006C */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0070 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0074 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 0078 */ 0x00000000,0x00000000,0x00000000,0x00000000,
+/* 007C */ 0x00000000,0x00000000,0x00000000,0x00000000
+}; /* #PARAMETER_END */
+
+
+static struct dsp_segment_desc cwc4630_segments[] = {
+  { SEGTYPE_SP_PROGRAM, 0x00000000, 0x00000328, cwc4630_code },
+  { SEGTYPE_SP_PARAMETER, 0x00000000, 0x00000080, cwc4630_parameter },
+};
+
+static struct dsp_module_desc cwc4630_module = {
+  "cwc4630",
+  {
+    38,
+    cwc4630_symbols
+  },
+  2,
+  cwc4630_segments,
+};
+
+#endif /* __HEADER_cwc4630_H__ */
diff --git a/linux/sound/pci/cs46xx/imgs/cwcasync.h b/linux/sound/pci/cs46xx/imgs/cwcasync.h
new file mode 100644
index 0000000..70e63e1
--- /dev/null
+++ b/linux/sound/pci/cs46xx/imgs/cwcasync.h
@@ -0,0 +1,176 @@
+/* generated from cwcasync.osp DO NOT MODIFY */
+
+#ifndef __HEADER_cwcasync_H__
+#define __HEADER_cwcasync_H__
+
+static struct dsp_symbol_entry cwcasync_symbols[] = {
+  { 0x8000, "EXECCHILD",0x03 },
+  { 0x8001, "EXECCHILD_98",0x03 },
+  { 0x8003, "EXECCHILD_PUSH1IND",0x03 },
+  { 0x8008, "EXECSIBLING",0x03 },
+  { 0x800a, "EXECSIBLING_298",0x03 },
+  { 0x800b, "EXECSIBLING_2IND1",0x03 },
+  { 0x8010, "TIMINGMASTER",0x03 },
+  { 0x804f, "S16_CODECINPUTTASK",0x03 },
+  { 0x805e, "PCMSERIALINPUTTASK",0x03 },
+  { 0x806d, "S16_MIX_TO_OSTREAM",0x03 },
+  { 0x809a, "S16_MIX",0x03 },
+  { 0x80bb, "S16_UPSRC",0x03 },
+  { 0x813b, "MIX3_EXP",0x03 },
+  { 0x8164, "DECIMATEBYPOW2",0x03 },
+  { 0x8197, "VARIDECIMATE",0x03 },
+  { 0x81f2, "_3DINPUTTASK",0x03 },
+  { 0x820a, "_3DPRLGCINPTASK",0x03 },
+  { 0x8227, "_3DSTEREOINPUTTASK",0x03 },
+  { 0x8242, "_3DOUTPUTTASK",0x03 },
+  { 0x82c4, "HRTF_MORPH_TASK",0x03 },
+  { 0x82c6, "WAIT4DATA",0x03 },
+  { 0x82fa, "PROLOGIC",0x03 },
+  { 0x8496, "DECORRELATOR",0x03 },
+  { 0x84a4, "STEREO2MONO",0x03 },
+  { 0x0000, "OVERLAYBEGINADDRESS",0x00 },
+  { 0x0000, "SPIOWRITE",0x03 },
+  { 0x000d, "S16_ASYNCCODECINPUTTASK",0x03 },
+  { 0x0043, "SPDIFITASK",0x03 },
+  { 0x007b, "SPDIFOTASK",0x03 },
+  { 0x0097, "ASYNCHFGTXCODE",0x03 },
+  { 0x00be, "ASYNCHFGRXCODE",0x03 },
+  { 0x00db, "#CODE_END",0x00 },
+}; /* cwcasync symbols */
+
+static u32 cwcasync_code[] = {
+/* OVERLAYBEGINADDRESS */
+/* 0000 */ 0x00002731,0x00001400,0x00003725,0x000a8440,
+/* 0002 */ 0x000000ae,0x00000000,0x00060630,0x00001000,
+/* 0004 */ 0x00000000,0x000c7560,0x00075282,0x0002d640,
+/* 0006 */ 0x00021705,0x00000000,0x00072ab8,0x0002d6c0,
+/* 0008 */ 0x00020630,0x00001000,0x000c74c2,0x000d4b82,
+/* 000A */ 0x000475c2,0x00000000,0x0003430a,0x000c0000,
+/* 000C */ 0x00042730,0x00001400,
+/* S16_ASYNCCODECINPUTTASK */
+/* 000D */ 0x0006a108,0x000cf2c4,0x0004f4c0,0x00000000,
+/* 000F */ 0x000fa418,0x0000101f,0x0005d402,0x0001c500,
+/* 0011 */ 0x000f0630,0x00001000,0x00004418,0x00001380,
+/* 0013 */ 0x000e243d,0x000d394a,0x00049705,0x00000000,
+/* 0015 */ 0x0007d530,0x000b4240,0x000e00f2,0x00001000,
+/* 0017 */ 0x00009134,0x000ca20a,0x00004c90,0x00001000,
+/* 0019 */ 0x0005d705,0x00000000,0x00004f25,0x00098240,
+/* 001B */ 0x00004725,0x00000000,0x0000e48a,0x00000000,
+/* 001D */ 0x00027295,0x0009c2c0,0x0003df25,0x00000000,
+/* 001F */ 0x000e8030,0x00001001,0x0005f718,0x000ac600,
+/* 0021 */ 0x0007cf30,0x000c2a01,0x00082630,0x00001001,
+/* 0023 */ 0x000504a0,0x00001001,0x00029314,0x000bcb80,
+/* 0025 */ 0x0003cf25,0x000b0e00,0x0004f5c0,0x00000000,
+/* 0027 */ 0x00049118,0x000d888a,0x0007dd02,0x000c6efa,
+/* 0029 */ 0x00000000,0x00000000,0x0004f5c0,0x00069c80,
+/* 002B */ 0x0000d402,0x00000000,0x000e8630,0x00001001,
+/* 002D */ 0x00079130,0x00000000,0x00049118,0x00090e00,
+/* 002F */ 0x0006c10a,0x00000000,0x00000000,0x000c0000,
+/* 0031 */ 0x0007cf30,0x00030580,0x00005725,0x00000000,
+/* 0033 */ 0x000d84a0,0x00001001,0x00029314,0x000b4780,
+/* 0035 */ 0x0003cf25,0x000b8600,0x00000000,0x00000000,
+/* 0037 */ 0x00000000,0x000c0000,0x00000000,0x00042c80,
+/* 0039 */ 0x0001dec1,0x000e488c,0x00031114,0x00000000,
+/* 003B */ 0x0004f5c2,0x00000000,0x0003640a,0x00000000,
+/* 003D */ 0x00000000,0x000e5084,0x00000000,0x000eb844,
+/* 003F */ 0x00007001,0x00000000,0x00000734,0x00001000,
+/* 0041 */ 0x00010705,0x000a6880,0x00006a88,0x000c75c4,
+/* SPDIFITASK */
+/* 0043 */ 0x0006a108,0x000cf2c4,0x0004f4c0,0x000d5384,
+/* 0045 */ 0x0007e48a,0x00000000,0x00067718,0x00001000,
+/* 0047 */ 0x0007a418,0x00001000,0x0007221a,0x00000000,
+/* 0049 */ 0x0005d402,0x00014500,0x000b8630,0x00001002,
+/* 004B */ 0x00004418,0x00001780,0x000e243d,0x000d394a,
+/* 004D */ 0x00049705,0x00000000,0x0007d530,0x000b4240,
+/* 004F */ 0x000ac0f2,0x00001002,0x00014414,0x00000000,
+/* 0051 */ 0x00004c90,0x00001000,0x0005d705,0x00000000,
+/* 0053 */ 0x00004f25,0x00098240,0x00004725,0x00000000,
+/* 0055 */ 0x0000e48a,0x00000000,0x00027295,0x0009c2c0,
+/* 0057 */ 0x0007df25,0x00000000,0x000ac030,0x00001003,
+/* 0059 */ 0x0005f718,0x000fe798,0x00029314,0x000bcb80,
+/* 005B */ 0x00000930,0x000b0e00,0x0004f5c0,0x000de204,
+/* 005D */ 0x000884a0,0x00001003,0x0007cf25,0x000e3560,
+/* 005F */ 0x00049118,0x00000000,0x00049118,0x000d888a,
+/* 0061 */ 0x0007dd02,0x000c6efa,0x0000c434,0x00030040,
+/* 0063 */ 0x000fda82,0x000c2312,0x000fdc0e,0x00001001,
+/* 0065 */ 0x00083402,0x000c2b92,0x000706b0,0x00001003,
+/* 0067 */ 0x00075a82,0x00000000,0x0000d625,0x000b0940,
+/* 0069 */ 0x0000840e,0x00001002,0x0000aabc,0x000c511e,
+/* 006B */ 0x00078730,0x00001003,0x0000aaf4,0x000e910a,
+/* 006D */ 0x0004628a,0x00000000,0x00006aca,0x00000000,
+/* 006F */ 0x00000930,0x00000000,0x0004f5c0,0x00069c80,
+/* 0071 */ 0x00046ac0,0x00000000,0x0003c40a,0x000fc898,
+/* 0073 */ 0x00049118,0x00090e00,0x0006c10a,0x00000000,
+/* 0075 */ 0x00000000,0x000e5084,0x00000000,0x000eb844,
+/* 0077 */ 0x00007001,0x00000000,0x00000734,0x00001000,
+/* 0079 */ 0x00010705,0x000a6880,0x00006a88,0x000c75c4,
+/* SPDIFOTASK */
+/* 007B */ 0x0006a108,0x000c0000,0x0004f4c0,0x000c3245,
+/* 007D */ 0x0000a418,0x00001000,0x0003a20a,0x00000000,
+/* 007F */ 0x00004418,0x00001380,0x000e243d,0x000d394a,
+/* 0081 */ 0x000c9705,0x000def92,0x0008c030,0x00001004,
+/* 0083 */ 0x0005f718,0x000fe798,0x00000000,0x000c0000,
+/* 0085 */ 0x00005725,0x00000000,0x000704a0,0x00001004,
+/* 0087 */ 0x00029314,0x000b4780,0x0003cf25,0x000b8600,
+/* 0089 */ 0x00000000,0x00000000,0x00000000,0x000c0000,
+/* 008B */ 0x00000000,0x00042c80,0x0001dec1,0x000e488c,
+/* 008D */ 0x00031114,0x00000000,0x0004f5c2,0x00000000,
+/* 008F */ 0x0004a918,0x00098600,0x0006c28a,0x00000000,
+/* 0091 */ 0x00000000,0x000e5084,0x00000000,0x000eb844,
+/* 0093 */ 0x00007001,0x00000000,0x00000734,0x00001000,
+/* 0095 */ 0x00010705,0x000a6880,0x00006a88,0x000c75c4,
+/* ASYNCHFGTXCODE */
+/* 0097 */ 0x0002a880,0x000b4e40,0x00042214,0x000e5548,
+/* 0099 */ 0x000542bf,0x00000000,0x00000000,0x000481c0,
+/* 009B */ 0x00000000,0x00000000,0x00000000,0x00000030,
+/* 009D */ 0x0000072d,0x000fbf8a,0x00077f94,0x000ea7df,
+/* 009F */ 0x0002ac95,0x000d3145,0x00002731,0x00001400,
+/* 00A1 */ 0x00006288,0x000c71c4,0x00014108,0x000e6044,
+/* 00A3 */ 0x00035408,0x00000000,0x00025418,0x000a0ec0,
+/* 00A5 */ 0x0001443d,0x000ca21e,0x00046595,0x000d730c,
+/* 00A7 */ 0x0006538e,0x00000000,0x00064630,0x00001005,
+/* 00A9 */ 0x000e7b0e,0x000df782,0x000746b0,0x00001005,
+/* 00AB */ 0x00036f05,0x000c0000,0x00043695,0x000d598c,
+/* 00AD */ 0x0005331a,0x000f2185,0x00000000,0x00000000,
+/* 00AF */ 0x000007ae,0x000bdb00,0x00040630,0x00001400,
+/* 00B1 */ 0x0005e708,0x000c0000,0x0007ef30,0x000b1c00,
+/* 00B3 */ 0x000d86a0,0x00001005,0x00066408,0x000c0000,
+/* 00B5 */ 0x00000000,0x00000000,0x00021843,0x00000000,
+/* 00B7 */ 0x00000cac,0x00062c00,0x00001dac,0x00063400,
+/* 00B9 */ 0x00002cac,0x0006cc80,0x000db943,0x000e5ca1,
+/* 00BB */ 0x00000000,0x00000000,0x0006680a,0x000f3205,
+/* 00BD */ 0x00042730,0x00001400,
+/* ASYNCHFGRXCODE */
+/* 00BE */ 0x00014108,0x000f2204,0x00025418,0x000a2ec0,
+/* 00C0 */ 0x00015dbd,0x00038100,0x00015dbc,0x00000000,
+/* 00C2 */ 0x0005e415,0x00034880,0x0001258a,0x000d730c,
+/* 00C4 */ 0x0006538e,0x000baa40,0x00060630,0x00001006,
+/* 00C6 */ 0x00067b0e,0x000ac380,0x0003ef05,0x00000000,
+/* 00C8 */ 0x0000f734,0x0001c300,0x000586b0,0x00001400,
+/* 00CA */ 0x000b6f05,0x000c3a00,0x00048f05,0x00000000,
+/* 00CC */ 0x0005b695,0x0008c380,0x0002058e,0x00000000,
+/* 00CE */ 0x000500b0,0x00001400,0x0002b318,0x000e998d,
+/* 00D0 */ 0x0006430a,0x00000000,0x00000000,0x000ef384,
+/* 00D2 */ 0x00004725,0x000c0000,0x00000000,0x000f3204,
+/* 00D4 */ 0x00004f25,0x000c0000,0x00080000,0x000e5ca1,
+/* 00D6 */ 0x000cb943,0x000e5ca1,0x0004b943,0x00000000,
+/* 00D8 */ 0x00040730,0x00001400,0x000cb943,0x000e5ca1,
+/* 00DA */ 0x0004b943,0x00000000
+};
+/* #CODE_END */
+
+static struct dsp_segment_desc cwcasync_segments[] = {
+  { SEGTYPE_SP_PROGRAM, 0x00000000, 0x000001b6, cwcasync_code },
+};
+
+static struct dsp_module_desc cwcasync_module = {
+  "cwcasync",
+  {
+    32,
+    cwcasync_symbols
+  },
+  1,
+  cwcasync_segments,
+};
+
+#endif /* __HEADER_cwcasync_H__ */
diff --git a/linux/sound/pci/cs46xx/imgs/cwcbinhack.h b/linux/sound/pci/cs46xx/imgs/cwcbinhack.h
new file mode 100644
index 0000000..f4d9368
--- /dev/null
+++ b/linux/sound/pci/cs46xx/imgs/cwcbinhack.h
@@ -0,0 +1,48 @@
+/* generated by Benny 
+   MODIFY ON YOUR OWN RISK */
+
+#ifndef __HEADER_cwcbinhack_H__
+#define __HEADER_cwcbinhack_H__
+
+static struct dsp_symbol_entry cwcbinhack_symbols[] = {
+  { 0x02c8, "OVERLAYBEGINADDRESS",0x00 },
+  { 0x02c8, "MAGICSNOOPTASK",0x03 },
+  { 0x0308, "#CODE_END",0x00 },
+}; /* cwcbinhack symbols */
+
+static u32 cwcbinhack_code[] = {
+  /* 0x02c8 */
+  0x0007bfb0,0x000bc240,0x00000c2e,0x000c6084, /* 1 */
+  0x000b8630,0x00001016,0x00006408,0x000efb84, /* 2 */
+  0x00016008,0x00000000,0x0001c088,0x000c0000, /* 3 */
+  0x000fc908,0x000e3392,0x0005f488,0x000efb84, /* 4 */
+  0x0001d402,0x000b2e00,0x0003d418,0x00001000, /* 5 */
+  0x0008d574,0x000c4293,0x00065625,0x000ea30e, /* 6 */
+  0x00096c01,0x000c6f92,0x0001a58a,0x000c6085, /* 7 */
+  0x00002f43,0x00000000,0x000e03a0,0x00001016, /* 8 */
+  0x0005e608,0x000c0000,0x00000000,0x00000000, /* 9 */
+  0x000ca108,0x000dcca1,0x00003bac,0x000c3205, /* 10 */
+  0x00073843,0x00000000,0x00010730,0x00001017, /* 11 */
+  0x0001600a,0x000c0000,0x00057488,0x00000000, /* 12 */
+  0x00000000,0x000e5084,0x00000000,0x000eba44, /* 13 */
+  0x00087401,0x000e4782,0x00000734,0x00001000, /* 14 */
+  0x00010705,0x000a6880,0x00006a88,0x000c75c4, /* 15 */
+  0x00000000,0x00000000,0x00000000,0x00000000, /* 16 */
+};
+/* #CODE_END */
+
+static struct dsp_segment_desc cwcbinhack_segments[] = {
+  { SEGTYPE_SP_PROGRAM, 0x00000000, 64, cwcbinhack_code },
+};
+
+static struct dsp_module_desc cwcbinhack_module = {
+  "cwcbinhack",
+  {
+    3,
+    cwcbinhack_symbols
+  },
+  1,
+  cwcbinhack_segments,
+};
+
+#endif /* __HEADER_cwcbinhack_H__ */
diff --git a/linux/sound/pci/cs46xx/imgs/cwcdma.asp b/linux/sound/pci/cs46xx/imgs/cwcdma.asp
new file mode 100644
index 0000000..a65e119
--- /dev/null
+++ b/linux/sound/pci/cs46xx/imgs/cwcdma.asp
@@ -0,0 +1,170 @@
+// 
+//  Copyright(c) by Benny Sjostrand (benny@hostmobility.com)
+//
+//  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 code runs inside the DSP (cs4610, cs4612, cs4624, or cs4630),
+// to compile it you need a tool named SPASM 3.0 and DSP code owned by 
+// Cirrus Logic(R). The SPASM program will generate a object file (cwcdma.osp),
+// the "ospparser"  tool will genereate the cwcdma.h file it's included from
+// the cs46xx_lib.c file.
+//
+//
+// The purpose of this code is very simple: make it possible to tranfser
+// the samples 'as they are' with no alteration from a PCMreader
+// SCB (DMA from host) to any other SCB. This is useful for AC3 through SPDIF.
+// SRC (source rate converters) task always alters the samples in somehow,
+// however it's from 48khz -> 48khz.
+// The alterations are not audible, but AC3 wont work. 
+//
+//        ...
+//         |
+// +---------------+
+// | AsynchFGTxSCB |
+// +---------------+
+//        |
+//    subListPtr
+//        |
+// +--------------+
+// |   DMAReader  |
+// +--------------+
+//        |
+//    subListPtr
+//        |
+// +-------------+
+// | PCMReader   |
+// +-------------+
+// (DMA from host)
+//
+
+struct dmaSCB
+  {
+    long  dma_reserved1[3];
+
+    short dma_reserved2:dma_outBufPtr;
+
+    short dma_unused1:dma_unused2;
+
+    long  dma_reserved3[4];
+
+    short dma_subListPtr:dma_nextSCB;
+    short dma_SPBptr:dma_entryPoint;
+
+    long  dma_strmRsConfig;
+    long  dma_strmBufPtr;
+
+    long  dma_reserved4;
+
+    VolumeControl s2m_volume;
+  };
+
+#export DMAReader
+void DMAReader()
+{
+  execChild();
+  r2 = r0->dma_subListPtr;
+  r1 = r0->nextSCB;
+	
+  rsConfig01 = r2->strmRsConfig;
+  // Load rsConfig for input buffer
+
+  rsDMA01 = r2->basicReq.daw,       ,                   tb = Z(0 - rf);
+  // Load rsDMA in case input buffer is a DMA buffer    Test to see if there is any data to transfer
+
+  if (tb) goto execSibling_2ind1 after {
+      r5 = rf + (-1);
+      r6 = r1->dma_entryPoint;           // r6 = entry point of sibling task
+      r1 = r1->dma_SPBptr,               // r1 = pointer to sibling task's SPB
+          ,   ind = r6;                  // Load entry point of sibling task
+  }
+
+  rsConfig23 = r0->dma_strmRsConfig;
+  // Load rsConfig for output buffer (never a DMA buffer)
+
+  r4 = r0->dma_outBufPtr;
+
+  rsa0 = r2->strmBufPtr;
+  // rsa0 = input buffer pointer                        
+
+  for (i = r5; i >= 0; --i)
+    after {
+      rsa2 = r4;
+      // rsa2 = output buffer pointer
+
+      nop;
+      nop;
+    }
+  //*****************************
+  // TODO: cycles to this point *
+  //*****************************
+    {
+      acc0 =  (rsd0 = *rsa0++1);
+      // get sample
+
+      nop;  // Those "nop"'s are really uggly, but there's
+      nop;  // something with DSP's pipelines which I don't
+      nop;  // understand, resulting this code to fail without
+            // having those "nop"'s (Benny)
+
+      rsa0?reqDMA = r2;
+      // Trigger DMA transfer on input stream, 
+      // if needed to replenish input buffer
+
+      nop;
+      // Yet another magic "nop" to make stuff work
+
+      ,,r98 = acc0 $+>> 0;
+      // store sample in ALU
+
+      nop;
+      // latency on load register.
+      // (this one is understandable)
+
+      *rsa2++1 = r98;
+      // store sample in output buffer
+
+      nop; // The same story
+      nop; // as above again ...
+      nop;
+    }
+  // TODO: cycles per loop iteration
+
+  r2->strmBufPtr = rsa0,,   ;
+  // Update the modified buffer pointers
+
+  r4 = rsa2;
+  // Load output pointer position into r4
+
+  r2 = r0->nextSCB;
+  // Sibling task
+
+  goto execSibling_2ind1 // takes 6 cycles
+    after {
+      r98 = r2->thisSPB:entryPoint;
+      // Load child routine entry and data address 
+
+      r1 = r9;
+      // r9 is r2->thisSPB
+
+      r0->dma_outBufPtr = r4,,
+      // Store updated output buffer pointer
+
+      ind = r8;
+      // r8 is r2->entryPoint
+    }
+}
diff --git a/linux/sound/pci/cs46xx/imgs/cwcdma.h b/linux/sound/pci/cs46xx/imgs/cwcdma.h
new file mode 100644
index 0000000..7ff0d45
--- /dev/null
+++ b/linux/sound/pci/cs46xx/imgs/cwcdma.h
@@ -0,0 +1,68 @@
+/* generated from cwcdma.osp DO NOT MODIFY */
+
+#ifndef __HEADER_cwcdma_H__
+#define __HEADER_cwcdma_H__
+
+static struct dsp_symbol_entry cwcdma_symbols[] = {
+  { 0x8000, "EXECCHILD",0x03 },
+  { 0x8001, "EXECCHILD_98",0x03 },
+  { 0x8003, "EXECCHILD_PUSH1IND",0x03 },
+  { 0x8008, "EXECSIBLING",0x03 },
+  { 0x800a, "EXECSIBLING_298",0x03 },
+  { 0x800b, "EXECSIBLING_2IND1",0x03 },
+  { 0x8010, "TIMINGMASTER",0x03 },
+  { 0x804f, "S16_CODECINPUTTASK",0x03 },
+  { 0x805e, "PCMSERIALINPUTTASK",0x03 },
+  { 0x806d, "S16_MIX_TO_OSTREAM",0x03 },
+  { 0x809a, "S16_MIX",0x03 },
+  { 0x80bb, "S16_UPSRC",0x03 },
+  { 0x813b, "MIX3_EXP",0x03 },
+  { 0x8164, "DECIMATEBYPOW2",0x03 },
+  { 0x8197, "VARIDECIMATE",0x03 },
+  { 0x81f2, "_3DINPUTTASK",0x03 },
+  { 0x820a, "_3DPRLGCINPTASK",0x03 },
+  { 0x8227, "_3DSTEREOINPUTTASK",0x03 },
+  { 0x8242, "_3DOUTPUTTASK",0x03 },
+  { 0x82c4, "HRTF_MORPH_TASK",0x03 },
+  { 0x82c6, "WAIT4DATA",0x03 },
+  { 0x82fa, "PROLOGIC",0x03 },
+  { 0x8496, "DECORRELATOR",0x03 },
+  { 0x84a4, "STEREO2MONO",0x03 },
+  { 0x0000, "OVERLAYBEGINADDRESS",0x00 },
+  { 0x0000, "DMAREADER",0x03 },
+  { 0x0018, "#CODE_END",0x00 },
+}; /* cwcdma symbols */
+
+static u32 cwcdma_code[] = {
+/* OVERLAYBEGINADDRESS */
+/* 0000 */ 0x00002731,0x00001400,0x0004c108,0x000e5044,
+/* 0002 */ 0x0005f608,0x00000000,0x000007ae,0x000be300,
+/* 0004 */ 0x00058630,0x00001400,0x0007afb0,0x000e9584,
+/* 0006 */ 0x00007301,0x000a9840,0x0005e708,0x000cd104,
+/* 0008 */ 0x00067008,0x00000000,0x000902a0,0x00001000,
+/* 000A */ 0x00012a01,0x000c0000,0x00000000,0x00000000,
+/* 000C */ 0x00021843,0x000c0000,0x00000000,0x000c0000,
+/* 000E */ 0x0000e101,0x000c0000,0x00000cac,0x00000000,
+/* 0010 */ 0x00080000,0x000e5ca1,0x00000000,0x000c0000,
+/* 0012 */ 0x00000000,0x00000000,0x00000000,0x00092c00,
+/* 0014 */ 0x000122c1,0x000e5084,0x00058730,0x00001400,
+/* 0016 */ 0x000d7488,0x000e4782,0x00007401,0x0001c100
+};
+
+/* #CODE_END */
+
+static struct dsp_segment_desc cwcdma_segments[] = {
+  { SEGTYPE_SP_PROGRAM, 0x00000000, 0x00000030, cwcdma_code },
+};
+
+static struct dsp_module_desc cwcdma_module = {
+  "cwcdma",
+  {
+    27,
+    cwcdma_symbols
+  },
+  1,
+  cwcdma_segments,
+};
+
+#endif /* __HEADER_cwcdma_H__ */
diff --git a/linux/sound/pci/cs46xx/imgs/cwcsnoop.h b/linux/sound/pci/cs46xx/imgs/cwcsnoop.h
new file mode 100644
index 0000000..6929d0a
--- /dev/null
+++ b/linux/sound/pci/cs46xx/imgs/cwcsnoop.h
@@ -0,0 +1,46 @@
+/* generated from cwcsnoop.osp DO NOT MODIFY */
+
+#ifndef __HEADER_cwcsnoop_H__
+#define __HEADER_cwcsnoop_H__
+
+static struct dsp_symbol_entry cwcsnoop_symbols[] = {
+  { 0x0500, "OVERLAYBEGINADDRESS",0x00 },
+  { 0x0500, "OUTPUTSNOOP",0x03 },
+  { 0x051f, "#CODE_END",0x00 },
+}; /* cwcsnoop symbols */
+
+static u32 cwcsnoop_code[] = {
+/* 0000 */ 0x0007bfb0,0x000b4e40,0x0007c088,0x000c0617,
+/* 0002 */ 0x00049705,0x00000000,0x00080630,0x00001028,
+/* 0004 */ 0x00076408,0x000efb84,0x00066008,0x00000000,
+/* 0006 */ 0x0007c908,0x000c0000,0x00046725,0x000efa44,
+/* 0008 */ 0x0005f708,0x00000000,0x0001d402,0x000b2e00,
+/* 000A */ 0x0003d418,0x00001000,0x0008d574,0x000c4293,
+/* 000C */ 0x00065625,0x000ea30e,0x00096c01,0x000c6f92,
+/* 000E */ 0x0006a58a,0x000f6085,0x00002f43,0x00000000,
+/* 0010 */ 0x000a83a0,0x00001028,0x0005e608,0x000c0000,
+/* 0012 */ 0x00000000,0x00000000,0x000ca108,0x000dcca1,
+/* 0014 */ 0x00003bac,0x000fb205,0x00073843,0x00000000,
+/* 0016 */ 0x000d8730,0x00001028,0x0006600a,0x000c0000,
+/* 0018 */ 0x00057488,0x00000000,0x00000000,0x000e5084,
+/* 001A */ 0x00000000,0x000eba44,0x00087401,0x000e4782,
+/* 001C */ 0x00000734,0x00001000,0x00010705,0x000a6880,
+/* 001E */ 0x00006a88,0x000c75c4
+};
+/* #CODE_END */
+
+static struct dsp_segment_desc cwcsnoop_segments[] = {
+  { SEGTYPE_SP_PROGRAM, 0x00000000, 0x0000003e, cwcsnoop_code },
+};
+
+static struct dsp_module_desc cwcsnoop_module = {
+  "cwcsnoop",
+  {
+    3,
+    cwcsnoop_symbols
+  },
+  1,
+  cwcsnoop_segments,
+};
+
+#endif /* __HEADER_cwcsnoop_H__ */
diff --git a/linux/sound/pci/cs5530.c b/linux/sound/pci/cs5530.c
new file mode 100644
index 0000000..bc07e27
--- /dev/null
+++ b/linux/sound/pci/cs5530.c
@@ -0,0 +1,306 @@
+/*
+ * cs5530.c - Initialisation code for Cyrix/NatSemi VSA1 softaudio
+ *
+ * 	(C) Copyright 2007 Ash Willis <ashwillis@programmer.net>
+ *	(C) Copyright 2003 Red Hat Inc <alan@lxorguk.ukuu.org.uk>
+ *
+ * This driver was ported (shamelessly ripped ;) from oss/kahlua.c but I did
+ * mess with it a bit. The chip seems to have to have trouble with full duplex
+ * mode. If we're recording in 8bit 8000kHz, say, and we then attempt to
+ * simultaneously play back audio at 16bit 44100kHz, the device actually plays
+ * back in the same format in which it is capturing. By forcing the chip to
+ * always play/capture in 16/44100, we can let alsa-lib convert the samples and
+ * that way we can hack up some full duplex audio. 
+ * 
+ * XpressAudio(tm) is used on the Cyrix MediaGX (now NatSemi Geode) systems.
+ * The older version (VSA1) provides fairly good soundblaster emulation
+ * although there are a couple of bugs: large DMA buffers break record,
+ * and the MPU event handling seems suspect. VSA2 allows the native driver
+ * to control the AC97 audio engine directly and requires a different driver.
+ *
+ * Thanks to National Semiconductor for providing the needed information
+ * on the XpressAudio(tm) internals.
+ *
+ * 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, 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.
+ *
+ * TO DO:
+ *	Investigate whether we can portably support Cognac (5520) in the
+ *	same manner.
+ */
+
+#include <linux/delay.h>
+#include <linux/moduleparam.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/sb.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Ash Willis");
+MODULE_DESCRIPTION("CS5530 Audio");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+struct snd_cs5530 {
+	struct snd_card *card;
+	struct pci_dev *pci;
+	struct snd_sb *sb;
+	unsigned long pci_base;
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_cs5530_ids) = {
+	{PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO, PCI_ANY_ID,
+							PCI_ANY_ID, 0, 0},
+	{0,}
+};
+
+MODULE_DEVICE_TABLE(pci, snd_cs5530_ids);
+
+static int snd_cs5530_free(struct snd_cs5530 *chip)
+{
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_cs5530_dev_free(struct snd_device *device)
+{
+	struct snd_cs5530 *chip = device->device_data;
+	return snd_cs5530_free(chip);
+}
+
+static void __devexit snd_cs5530_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static u8 __devinit snd_cs5530_mixer_read(unsigned long io, u8 reg)
+{
+	outb(reg, io + 4);
+	udelay(20);
+	reg = inb(io + 5);
+	udelay(20);
+	return reg;
+}
+
+static int __devinit snd_cs5530_create(struct snd_card *card,
+				       struct pci_dev *pci,
+				       struct snd_cs5530 **rchip)
+{
+	struct snd_cs5530 *chip;
+	unsigned long sb_base;
+	u8 irq, dma8, dma16 = 0;
+	u16 map;
+	void __iomem *mem;
+	int err;
+
+	static struct snd_device_ops ops = {
+		.dev_free = snd_cs5530_dev_free,
+	};
+	*rchip = NULL;
+
+	err = pci_enable_device(pci);
+ 	if (err < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	chip->card = card;
+	chip->pci = pci;
+
+	err = pci_request_regions(pci, "CS5530");
+	if (err < 0) {
+		kfree(chip); 
+		pci_disable_device(pci);
+		return err;
+	}
+	chip->pci_base = pci_resource_start(pci, 0);
+
+	mem = pci_ioremap_bar(pci, 0);
+	if (mem == NULL) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return -EBUSY;
+	}
+
+	map = readw(mem + 0x18);
+	iounmap(mem);
+
+	/* Map bits
+		0:1	* 0x20 + 0x200 = sb base
+		2	sb enable
+		3	adlib enable
+		5	MPU enable 0x330
+		6	MPU enable 0x300
+
+	   The other bits may be used internally so must be masked */
+
+	sb_base = 0x220 + 0x20 * (map & 3);
+
+	if (map & (1<<2))
+		printk(KERN_INFO "CS5530: XpressAudio at 0x%lx\n", sb_base);
+	else {
+		printk(KERN_ERR "Could not find XpressAudio!\n");
+		snd_cs5530_free(chip);
+		return -ENODEV;
+	}
+
+	if (map & (1<<5))
+		printk(KERN_INFO "CS5530: MPU at 0x300\n");
+	else if (map & (1<<6))
+		printk(KERN_INFO "CS5530: MPU at 0x330\n");
+
+	irq = snd_cs5530_mixer_read(sb_base, 0x80) & 0x0F;
+	dma8 = snd_cs5530_mixer_read(sb_base, 0x81);
+
+	if (dma8 & 0x20)
+		dma16 = 5;
+	else if (dma8 & 0x40)
+		dma16 = 6;
+	else if (dma8 & 0x80)
+		dma16 = 7;
+	else {
+		printk(KERN_ERR "CS5530: No 16bit DMA enabled\n");
+		snd_cs5530_free(chip);
+		return -ENODEV;
+	}
+
+	if (dma8 & 0x01)
+		dma8 = 0;
+	else if (dma8 & 02)
+		dma8 = 1;
+	else if (dma8 & 0x08)
+		dma8 = 3;
+	else {
+		printk(KERN_ERR "CS5530: No 8bit DMA enabled\n");
+		snd_cs5530_free(chip);
+		return -ENODEV;
+	}
+
+	if (irq & 1)
+		irq = 9;
+	else if (irq & 2)
+		irq = 5;
+	else if (irq & 4)
+		irq = 7;
+	else if (irq & 8)
+		irq = 10;
+	else {
+		printk(KERN_ERR "CS5530: SoundBlaster IRQ not set\n");
+		snd_cs5530_free(chip);
+		return -ENODEV;
+	}
+
+	printk(KERN_INFO "CS5530: IRQ: %d DMA8: %d DMA16: %d\n", irq, dma8, 
+									dma16);
+
+	err = snd_sbdsp_create(card, sb_base, irq, snd_sb16dsp_interrupt, dma8,
+						dma16, SB_HW_CS5530, &chip->sb);
+	if (err < 0) {
+		printk(KERN_ERR "CS5530: Could not create SoundBlaster\n");
+		snd_cs5530_free(chip);
+		return err;
+	}
+
+	err = snd_sb16dsp_pcm(chip->sb, 0, &chip->sb->pcm);
+	if (err < 0) {
+		printk(KERN_ERR "CS5530: Could not create PCM\n");
+		snd_cs5530_free(chip);
+		return err;
+	}
+
+	err = snd_sbmixer_new(chip->sb);
+	if (err < 0) {
+		printk(KERN_ERR "CS5530: Could not create Mixer\n");
+		snd_cs5530_free(chip);
+		return err;
+	}
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		snd_cs5530_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+	*rchip = chip;
+	return 0;
+}
+
+static int __devinit snd_cs5530_probe(struct pci_dev *pci,
+					const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_cs5530 *chip = NULL;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+
+	if (err < 0)
+		return err;
+
+	err = snd_cs5530_create(card, pci, &chip);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "CS5530");
+	strcpy(card->shortname, "CS5530 Audio");
+	sprintf(card->longname, "%s at 0x%lx", card->shortname, chip->pci_base);
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static struct pci_driver driver = {
+	.name = "CS5530_Audio",
+	.id_table = snd_cs5530_ids,
+	.probe = snd_cs5530_probe,
+	.remove = __devexit_p(snd_cs5530_remove),
+};
+
+static int __init alsa_card_cs5530_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_cs5530_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_cs5530_init)
+module_exit(alsa_card_cs5530_exit)
+
diff --git a/linux/sound/pci/cs5535audio/Makefile b/linux/sound/pci/cs5535audio/Makefile
new file mode 100644
index 0000000..ccc6422
--- /dev/null
+++ b/linux/sound/pci/cs5535audio/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for cs5535audio
+#
+
+snd-cs5535audio-y := cs5535audio.o cs5535audio_pcm.o
+snd-cs5535audio-$(CONFIG_PM) += cs5535audio_pm.o
+snd-cs5535audio-$(CONFIG_OLPC) += cs5535audio_olpc.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_CS5535AUDIO) += snd-cs5535audio.o
diff --git a/linux/sound/pci/cs5535audio/cs5535audio.c b/linux/sound/pci/cs5535audio/cs5535audio.c
new file mode 100644
index 0000000..afb8037
--- /dev/null
+++ b/linux/sound/pci/cs5535audio/cs5535audio.c
@@ -0,0 +1,424 @@
+/*
+ * Driver for audio on multifunction CS5535/6 companion device
+ * Copyright (C) Jaya Kumar
+ *
+ * Based on Jaroslav Kysela and Takashi Iwai's examples.
+ * This work was sponsored by CIS(M) Sdn Bhd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <asm/io.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+#include "cs5535audio.h"
+
+#define DRIVER_NAME "cs5535audio"
+
+static char *ac97_quirk;
+module_param(ac97_quirk, charp, 0444);
+MODULE_PARM_DESC(ac97_quirk, "AC'97 board specific workarounds.");
+
+static struct ac97_quirk ac97_quirks[] __devinitdata = {
+#if 0 /* Not yet confirmed if all 5536 boards are HP only */
+	{
+		.subvendor = PCI_VENDOR_ID_AMD, 
+		.subdevice = PCI_DEVICE_ID_AMD_CS5536_AUDIO, 
+		.name = "AMD RDK",     
+		.type = AC97_TUNE_HP_ONLY
+	},
+#endif
+	{}
+};
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " DRIVER_NAME);
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " DRIVER_NAME);
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " DRIVER_NAME);
+
+static DEFINE_PCI_DEVICE_TABLE(snd_cs5535audio_ids) = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_AUDIO) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_AUDIO) },
+	{}
+};
+
+MODULE_DEVICE_TABLE(pci, snd_cs5535audio_ids);
+
+static void wait_till_cmd_acked(struct cs5535audio *cs5535au, unsigned long timeout)
+{
+	unsigned int tmp;
+	do {
+		tmp = cs_readl(cs5535au, ACC_CODEC_CNTL);
+		if (!(tmp & CMD_NEW))
+			break;
+		udelay(1);
+	} while (--timeout);
+	if (!timeout)
+		snd_printk(KERN_ERR "Failure writing to cs5535 codec\n");
+}
+
+static unsigned short snd_cs5535audio_codec_read(struct cs5535audio *cs5535au,
+						 unsigned short reg)
+{
+	unsigned int regdata;
+	unsigned int timeout;
+	unsigned int val;
+
+	regdata = ((unsigned int) reg) << 24;
+	regdata |= ACC_CODEC_CNTL_RD_CMD;
+	regdata |= CMD_NEW;
+
+	cs_writel(cs5535au, ACC_CODEC_CNTL, regdata);
+	wait_till_cmd_acked(cs5535au, 50);
+
+	timeout = 50;
+	do {
+		val = cs_readl(cs5535au, ACC_CODEC_STATUS);
+		if ((val & STS_NEW) && reg == (val >> 24))
+			break;
+		udelay(1);
+	} while (--timeout);
+	if (!timeout)
+		snd_printk(KERN_ERR "Failure reading codec reg 0x%x,"
+					"Last value=0x%x\n", reg, val);
+
+	return (unsigned short) val;
+}
+
+static void snd_cs5535audio_codec_write(struct cs5535audio *cs5535au,
+					unsigned short reg, unsigned short val)
+{
+	unsigned int regdata;
+
+	regdata = ((unsigned int) reg) << 24;
+	regdata |= val;
+	regdata &= CMD_MASK;
+	regdata |= CMD_NEW;
+	regdata &= ACC_CODEC_CNTL_WR_CMD;
+
+	cs_writel(cs5535au, ACC_CODEC_CNTL, regdata);
+	wait_till_cmd_acked(cs5535au, 50);
+}
+
+static void snd_cs5535audio_ac97_codec_write(struct snd_ac97 *ac97,
+					     unsigned short reg, unsigned short val)
+{
+	struct cs5535audio *cs5535au = ac97->private_data;
+	snd_cs5535audio_codec_write(cs5535au, reg, val);
+}
+
+static unsigned short snd_cs5535audio_ac97_codec_read(struct snd_ac97 *ac97,
+						      unsigned short reg)
+{
+	struct cs5535audio *cs5535au = ac97->private_data;
+	return snd_cs5535audio_codec_read(cs5535au, reg);
+}
+
+static int __devinit snd_cs5535audio_mixer(struct cs5535audio *cs5535au)
+{
+	struct snd_card *card = cs5535au->card;
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_cs5535audio_ac97_codec_write,
+		.read = snd_cs5535audio_ac97_codec_read,
+	};
+
+	if ((err = snd_ac97_bus(card, 0, &ops, NULL, &pbus)) < 0)
+		return err;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.scaps = AC97_SCAP_AUDIO | AC97_SCAP_SKIP_MODEM
+			| AC97_SCAP_POWER_SAVE;
+	ac97.private_data = cs5535au;
+	ac97.pci = cs5535au->pci;
+
+	/* set any OLPC-specific scaps */
+	olpc_prequirks(card, &ac97);
+
+	if ((err = snd_ac97_mixer(pbus, &ac97, &cs5535au->ac97)) < 0) {
+		snd_printk(KERN_ERR "mixer failed\n");
+		return err;
+	}
+
+	snd_ac97_tune_hardware(cs5535au->ac97, ac97_quirks, ac97_quirk);
+
+	err = olpc_quirks(card, cs5535au->ac97);
+	if (err < 0) {
+		snd_printk(KERN_ERR "olpc quirks failed\n");
+		return err;
+	}
+
+	return 0;
+}
+
+static void process_bm0_irq(struct cs5535audio *cs5535au)
+{
+	u8 bm_stat;
+	spin_lock(&cs5535au->reg_lock);
+	bm_stat = cs_readb(cs5535au, ACC_BM0_STATUS);
+	spin_unlock(&cs5535au->reg_lock);
+	if (bm_stat & EOP) {
+		struct cs5535audio_dma *dma;
+		dma = cs5535au->playback_substream->runtime->private_data;
+		snd_pcm_period_elapsed(cs5535au->playback_substream);
+	} else {
+		snd_printk(KERN_ERR "unexpected bm0 irq src, bm_stat=%x\n",
+					bm_stat);
+	}
+}
+
+static void process_bm1_irq(struct cs5535audio *cs5535au)
+{
+	u8 bm_stat;
+	spin_lock(&cs5535au->reg_lock);
+	bm_stat = cs_readb(cs5535au, ACC_BM1_STATUS);
+	spin_unlock(&cs5535au->reg_lock);
+	if (bm_stat & EOP) {
+		struct cs5535audio_dma *dma;
+		dma = cs5535au->capture_substream->runtime->private_data;
+		snd_pcm_period_elapsed(cs5535au->capture_substream);
+	}
+}
+
+static irqreturn_t snd_cs5535audio_interrupt(int irq, void *dev_id)
+{
+	u16 acc_irq_stat;
+	unsigned char count;
+	struct cs5535audio *cs5535au = dev_id;
+
+	if (cs5535au == NULL)
+		return IRQ_NONE;
+
+	acc_irq_stat = cs_readw(cs5535au, ACC_IRQ_STATUS);
+
+	if (!acc_irq_stat)
+		return IRQ_NONE;
+	for (count = 0; count < 4; count++) {
+		if (acc_irq_stat & (1 << count)) {
+			switch (count) {
+			case IRQ_STS:
+				cs_readl(cs5535au, ACC_GPIO_STATUS);
+				break;
+			case WU_IRQ_STS:
+				cs_readl(cs5535au, ACC_GPIO_STATUS);
+				break;
+			case BM0_IRQ_STS:
+				process_bm0_irq(cs5535au);
+				break;
+			case BM1_IRQ_STS:
+				process_bm1_irq(cs5535au);
+				break;
+			default:
+				snd_printk(KERN_ERR "Unexpected irq src: "
+						"0x%x\n", acc_irq_stat);
+				break;
+			}
+		}
+	}
+	return IRQ_HANDLED;
+}
+
+static int snd_cs5535audio_free(struct cs5535audio *cs5535au)
+{
+	synchronize_irq(cs5535au->irq);
+	pci_set_power_state(cs5535au->pci, 3);
+
+	if (cs5535au->irq >= 0)
+		free_irq(cs5535au->irq, cs5535au);
+
+	pci_release_regions(cs5535au->pci);
+	pci_disable_device(cs5535au->pci);
+	kfree(cs5535au);
+	return 0;
+}
+
+static int snd_cs5535audio_dev_free(struct snd_device *device)
+{
+	struct cs5535audio *cs5535au = device->device_data;
+	return snd_cs5535audio_free(cs5535au);
+}
+
+static int __devinit snd_cs5535audio_create(struct snd_card *card,
+					    struct pci_dev *pci,
+					    struct cs5535audio **rcs5535au)
+{
+	struct cs5535audio *cs5535au;
+
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_cs5535audio_dev_free,
+	};
+
+	*rcs5535au = NULL;
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(32)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32)) < 0) {
+		printk(KERN_WARNING "unable to get 32bit dma\n");
+		err = -ENXIO;
+		goto pcifail;
+	}
+
+	cs5535au = kzalloc(sizeof(*cs5535au), GFP_KERNEL);
+	if (cs5535au == NULL) {
+		err = -ENOMEM;
+		goto pcifail;
+	}
+
+	spin_lock_init(&cs5535au->reg_lock);
+	cs5535au->card = card;
+	cs5535au->pci = pci;
+	cs5535au->irq = -1;
+
+	if ((err = pci_request_regions(pci, "CS5535 Audio")) < 0) {
+		kfree(cs5535au);
+		goto pcifail;
+	}
+
+	cs5535au->port = pci_resource_start(pci, 0);
+
+	if (request_irq(pci->irq, snd_cs5535audio_interrupt,
+			IRQF_SHARED, "CS5535 Audio", cs5535au)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		err = -EBUSY;
+		goto sndfail;
+	}
+
+	cs5535au->irq = pci->irq;
+	pci_set_master(pci);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+				  cs5535au, &ops)) < 0)
+		goto sndfail;
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rcs5535au = cs5535au;
+	return 0;
+
+sndfail: /* leave the device alive, just kill the snd */
+	snd_cs5535audio_free(cs5535au);
+	return err;
+
+pcifail:
+	pci_disable_device(pci);
+	return err;
+}
+
+static int __devinit snd_cs5535audio_probe(struct pci_dev *pci,
+					   const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct cs5535audio *cs5535au;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	if ((err = snd_cs5535audio_create(card, pci, &cs5535au)) < 0)
+		goto probefail_out;
+
+	card->private_data = cs5535au;
+
+	if ((err = snd_cs5535audio_mixer(cs5535au)) < 0)
+		goto probefail_out;
+
+	if ((err = snd_cs5535audio_pcm(cs5535au)) < 0)
+		goto probefail_out;
+
+	strcpy(card->driver, DRIVER_NAME);
+
+	strcpy(card->shortname, "CS5535 Audio");
+	sprintf(card->longname, "%s %s at 0x%lx, irq %i",
+		card->shortname, card->driver,
+		cs5535au->port, cs5535au->irq);
+
+	if ((err = snd_card_register(card)) < 0)
+		goto probefail_out;
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+
+probefail_out:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit snd_cs5535audio_remove(struct pci_dev *pci)
+{
+	olpc_quirks_cleanup();
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = DRIVER_NAME,
+	.id_table = snd_cs5535audio_ids,
+	.probe = snd_cs5535audio_probe,
+	.remove = __devexit_p(snd_cs5535audio_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_cs5535audio_suspend,
+	.resume = snd_cs5535audio_resume,
+#endif
+};
+
+static int __init alsa_card_cs5535audio_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_cs5535audio_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_cs5535audio_init)
+module_exit(alsa_card_cs5535audio_exit)
+
+MODULE_AUTHOR("Jaya Kumar");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("CS5535 Audio");
+MODULE_SUPPORTED_DEVICE("CS5535 Audio");
diff --git a/linux/sound/pci/cs5535audio/cs5535audio.h b/linux/sound/pci/cs5535audio/cs5535audio.h
new file mode 100644
index 0000000..51966d7
--- /dev/null
+++ b/linux/sound/pci/cs5535audio/cs5535audio.h
@@ -0,0 +1,142 @@
+#ifndef __SOUND_CS5535AUDIO_H
+#define __SOUND_CS5535AUDIO_H
+
+#define cs_writel(cs5535au, reg, val)	outl(val, (cs5535au)->port + reg)
+#define cs_writeb(cs5535au, reg, val)	outb(val, (cs5535au)->port + reg)
+#define cs_readl(cs5535au, reg)		inl((cs5535au)->port + reg)
+#define cs_readw(cs5535au, reg)		inw((cs5535au)->port + reg)
+#define cs_readb(cs5535au, reg)		inb((cs5535au)->port + reg)
+
+#define CS5535AUDIO_MAX_DESCRIPTORS	128
+
+/* acc_codec bar0 reg addrs */
+#define ACC_GPIO_STATUS			0x00
+#define ACC_CODEC_STATUS		0x08
+#define ACC_CODEC_CNTL			0x0C
+#define ACC_IRQ_STATUS			0x12
+#define ACC_BM0_CMD			0x20
+#define ACC_BM1_CMD			0x28
+#define ACC_BM0_PRD			0x24
+#define ACC_BM1_PRD			0x2C
+#define ACC_BM0_STATUS			0x21
+#define ACC_BM1_STATUS			0x29
+#define ACC_BM0_PNTR			0x60
+#define ACC_BM1_PNTR			0x64
+
+/* acc_codec bar0 reg bits */
+/* ACC_IRQ_STATUS */
+#define IRQ_STS 			0
+#define WU_IRQ_STS 			1
+#define BM0_IRQ_STS 			2
+#define BM1_IRQ_STS 			3
+/* ACC_BMX_STATUS */
+#define EOP				(1<<0)
+#define BM_EOP_ERR			(1<<1)
+/* ACC_BMX_CTL */
+#define BM_CTL_EN			0x01
+#define BM_CTL_PAUSE			0x03
+#define BM_CTL_DIS			0x00
+#define BM_CTL_BYTE_ORD_LE		0x00
+#define BM_CTL_BYTE_ORD_BE		0x04
+/* cs5535 specific ac97 codec register defines */
+#define CMD_MASK			0xFF00FFFF
+#define CMD_NEW				0x00010000
+#define STS_NEW				0x00020000
+#define PRM_RDY_STS			0x00800000
+#define ACC_CODEC_CNTL_WR_CMD		(~0x80000000)
+#define ACC_CODEC_CNTL_RD_CMD		0x80000000
+#define ACC_CODEC_CNTL_LNK_SHUTDOWN	0x00040000
+#define ACC_CODEC_CNTL_LNK_WRM_RST	0x00020000
+#define PRD_JMP				0x2000
+#define PRD_EOP				0x4000
+#define PRD_EOT				0x8000
+
+enum { CS5535AUDIO_DMA_PLAYBACK, CS5535AUDIO_DMA_CAPTURE, NUM_CS5535AUDIO_DMAS };
+
+struct cs5535audio;
+
+struct cs5535audio_dma_ops {
+	int type;
+	void (*enable_dma)(struct cs5535audio *cs5535au);
+	void (*disable_dma)(struct cs5535audio *cs5535au);
+	void (*pause_dma)(struct cs5535audio *cs5535au);
+	void (*setup_prd)(struct cs5535audio *cs5535au, u32 prd_addr);
+	u32 (*read_prd)(struct cs5535audio *cs5535au);
+	u32 (*read_dma_pntr)(struct cs5535audio *cs5535au);
+};
+
+struct cs5535audio_dma_desc {
+	u32 addr;
+	u16 size;
+	u16 ctlreserved;
+};
+
+struct cs5535audio_dma {
+	const struct cs5535audio_dma_ops *ops;
+	struct snd_dma_buffer desc_buf;
+	struct snd_pcm_substream *substream;
+	unsigned int buf_addr, buf_bytes;
+	unsigned int period_bytes, periods;
+	u32 saved_prd;
+	int pcm_open_flag;
+};
+
+struct cs5535audio {
+	struct snd_card *card;
+	struct snd_ac97 *ac97;
+	struct snd_pcm *pcm;
+	int irq;
+	struct pci_dev *pci;
+	unsigned long port;
+	spinlock_t reg_lock;
+	struct snd_pcm_substream *playback_substream;
+	struct snd_pcm_substream *capture_substream;
+	struct cs5535audio_dma dmas[NUM_CS5535AUDIO_DMAS];
+};
+
+#ifdef CONFIG_PM
+int snd_cs5535audio_suspend(struct pci_dev *pci, pm_message_t state);
+int snd_cs5535audio_resume(struct pci_dev *pci);
+#endif
+
+#ifdef CONFIG_OLPC
+void __devinit olpc_prequirks(struct snd_card *card,
+		struct snd_ac97_template *ac97);
+int __devinit olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97);
+void __devexit olpc_quirks_cleanup(void);
+void olpc_analog_input(struct snd_ac97 *ac97, int on);
+void olpc_mic_bias(struct snd_ac97 *ac97, int on);
+
+static inline void olpc_capture_open(struct snd_ac97 *ac97)
+{
+	/* default to Analog Input off */
+	olpc_analog_input(ac97, 0);
+	/* enable MIC Bias for recording */
+	olpc_mic_bias(ac97, 1);
+}
+
+static inline void olpc_capture_close(struct snd_ac97 *ac97)
+{
+	/* disable Analog Input */
+	olpc_analog_input(ac97, 0);
+	/* disable the MIC Bias (so the recording LED turns off) */
+	olpc_mic_bias(ac97, 0);
+}
+#else
+static inline void olpc_prequirks(struct snd_card *card,
+		struct snd_ac97_template *ac97) { }
+static inline int olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97)
+{
+	return 0;
+}
+static inline void olpc_quirks_cleanup(void) { }
+static inline void olpc_analog_input(struct snd_ac97 *ac97, int on) { }
+static inline void olpc_mic_bias(struct snd_ac97 *ac97, int on) { }
+static inline void olpc_capture_open(struct snd_ac97 *ac97) { }
+static inline void olpc_capture_close(struct snd_ac97 *ac97) { }
+#endif
+
+int __devinit snd_cs5535audio_pcm(struct cs5535audio *cs5535audio);
+
+#endif /* __SOUND_CS5535AUDIO_H */
+
diff --git a/linux/sound/pci/cs5535audio/cs5535audio_olpc.c b/linux/sound/pci/cs5535audio/cs5535audio_olpc.c
new file mode 100644
index 0000000..50da49b
--- /dev/null
+++ b/linux/sound/pci/cs5535audio/cs5535audio_olpc.c
@@ -0,0 +1,191 @@
+/*
+ * OLPC XO-1 additional sound features
+ *
+ * Copyright © 2006  Jaya Kumar <jayakumar.lkml@gmail.com>
+ * Copyright © 2007-2008  Andres Salomon <dilinger@debian.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.
+ */
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include <linux/gpio.h>
+
+#include <asm/olpc.h>
+#include "cs5535audio.h"
+
+#define DRV_NAME "cs5535audio-olpc"
+
+/*
+ * OLPC has an additional feature on top of the regular AD1888 codec features.
+ * It has an Analog Input mode that is switched into (after disabling the
+ * High Pass Filter) via GPIO.  It is supported on B2 and later models.
+ */
+void olpc_analog_input(struct snd_ac97 *ac97, int on)
+{
+	int err;
+
+	if (!machine_is_olpc())
+		return;
+
+	/* update the High Pass Filter (via AC97_AD_TEST2) */
+	err = snd_ac97_update_bits(ac97, AC97_AD_TEST2,
+			1 << AC97_AD_HPFD_SHIFT, on << AC97_AD_HPFD_SHIFT);
+	if (err < 0) {
+		snd_printk(KERN_ERR "setting High Pass Filter - %d\n", err);
+		return;
+	}
+
+	/* set Analog Input through GPIO */
+	gpio_set_value(OLPC_GPIO_MIC_AC, on);
+}
+
+/*
+ * OLPC XO-1's V_REFOUT is a mic bias enable.
+ */
+void olpc_mic_bias(struct snd_ac97 *ac97, int on)
+{
+	int err;
+
+	if (!machine_is_olpc())
+		return;
+
+	on = on ? 0 : 1;
+	err = snd_ac97_update_bits(ac97, AC97_AD_MISC,
+			1 << AC97_AD_VREFD_SHIFT, on << AC97_AD_VREFD_SHIFT);
+	if (err < 0)
+		snd_printk(KERN_ERR "setting MIC Bias - %d\n", err);
+}
+
+static int olpc_dc_info(struct snd_kcontrol *kctl,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int olpc_dc_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+	v->value.integer.value[0] = gpio_get_value(OLPC_GPIO_MIC_AC);
+	return 0;
+}
+
+static int olpc_dc_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+	struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl);
+
+	olpc_analog_input(cs5535au->ac97, v->value.integer.value[0]);
+	return 1;
+}
+
+static int olpc_mic_info(struct snd_kcontrol *kctl,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int olpc_mic_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+	struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl);
+	struct snd_ac97 *ac97 = cs5535au->ac97;
+	int i;
+
+	i = (snd_ac97_read(ac97, AC97_AD_MISC) >> AC97_AD_VREFD_SHIFT) & 0x1;
+	v->value.integer.value[0] = i ? 0 : 1;
+	return 0;
+}
+
+static int olpc_mic_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+	struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl);
+
+	olpc_mic_bias(cs5535au->ac97, v->value.integer.value[0]);
+	return 1;
+}
+
+static struct snd_kcontrol_new olpc_cs5535audio_ctls[] __devinitdata = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DC Mode Enable",
+	.info = olpc_dc_info,
+	.get = olpc_dc_get,
+	.put = olpc_dc_put,
+	.private_value = 0,
+},
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "MIC Bias Enable",
+	.info = olpc_mic_info,
+	.get = olpc_mic_get,
+	.put = olpc_mic_put,
+	.private_value = 0,
+},
+};
+
+void __devinit olpc_prequirks(struct snd_card *card,
+		struct snd_ac97_template *ac97)
+{
+	if (!machine_is_olpc())
+		return;
+
+	/* invert EAPD if on an OLPC B3 or higher */
+	if (olpc_board_at_least(olpc_board_pre(0xb3)))
+		ac97->scaps |= AC97_SCAP_INV_EAPD;
+}
+
+int __devinit olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97)
+{
+	struct snd_ctl_elem_id elem;
+	int i, err;
+
+	if (!machine_is_olpc())
+		return 0;
+
+	if (gpio_request(OLPC_GPIO_MIC_AC, DRV_NAME)) {
+		printk(KERN_ERR DRV_NAME ": unable to allocate MIC GPIO\n");
+		return -EIO;
+	}
+	gpio_direction_output(OLPC_GPIO_MIC_AC, 0);
+
+	/* drop the original AD1888 HPF control */
+	memset(&elem, 0, sizeof(elem));
+	elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strncpy(elem.name, "High Pass Filter Enable", sizeof(elem.name));
+	snd_ctl_remove_id(card, &elem);
+
+	/* drop the original V_REFOUT control */
+	memset(&elem, 0, sizeof(elem));
+	elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strncpy(elem.name, "V_REFOUT Enable", sizeof(elem.name));
+	snd_ctl_remove_id(card, &elem);
+
+	/* add the OLPC-specific controls */
+	for (i = 0; i < ARRAY_SIZE(olpc_cs5535audio_ctls); i++) {
+		err = snd_ctl_add(card, snd_ctl_new1(&olpc_cs5535audio_ctls[i],
+				ac97->private_data));
+		if (err < 0) {
+			gpio_free(OLPC_GPIO_MIC_AC);
+			return err;
+		}
+	}
+
+	/* turn off the mic by default */
+	olpc_mic_bias(ac97, 0);
+	return 0;
+}
+
+void __devexit olpc_quirks_cleanup(void)
+{
+	gpio_free(OLPC_GPIO_MIC_AC);
+}
diff --git a/linux/sound/pci/cs5535audio/cs5535audio_pcm.c b/linux/sound/pci/cs5535audio/cs5535audio_pcm.c
new file mode 100644
index 0000000..f16bc8a
--- /dev/null
+++ b/linux/sound/pci/cs5535audio/cs5535audio_pcm.c
@@ -0,0 +1,454 @@
+/*
+ * Driver for audio on multifunction CS5535 companion device
+ * Copyright (C) Jaya Kumar
+ *
+ * Based on Jaroslav Kysela and Takashi Iwai's examples.
+ * This work was sponsored by CIS(M) Sdn Bhd.
+ *
+ * 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
+ *
+ * todo: add be fmt support, spdif, pm
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/ac97_codec.h>
+#include "cs5535audio.h"
+
+static struct snd_pcm_hardware snd_cs5535audio_playback =
+{
+	.info =			(
+				SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+		 		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 		SNDRV_PCM_INFO_MMAP_VALID |
+		 		SNDRV_PCM_INFO_PAUSE |
+				SNDRV_PCM_INFO_RESUME
+				),
+	.formats =		(
+				SNDRV_PCM_FMTBIT_S16_LE
+				),
+	.rates =		(
+				SNDRV_PCM_RATE_CONTINUOUS |
+				SNDRV_PCM_RATE_8000_48000
+				),
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(64*1024 - 16),
+	.periods_min =		1,
+	.periods_max =		CS5535AUDIO_MAX_DESCRIPTORS,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_cs5535audio_capture =
+{
+	.info =			(
+				SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+		 		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 		SNDRV_PCM_INFO_MMAP_VALID
+				),
+	.formats =		(
+				SNDRV_PCM_FMTBIT_S16_LE
+				),
+	.rates =		(
+				SNDRV_PCM_RATE_CONTINUOUS |
+				SNDRV_PCM_RATE_8000_48000
+				),
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(64*1024 - 16),
+	.periods_min =		1,
+	.periods_max =		CS5535AUDIO_MAX_DESCRIPTORS,
+	.fifo_size =		0,
+};
+
+static int snd_cs5535audio_playback_open(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = snd_cs5535audio_playback;
+	runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_FRONT_DAC];
+	snd_pcm_limit_hw_rates(runtime);
+	cs5535au->playback_substream = substream;
+	runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK]);
+	if ((err = snd_pcm_hw_constraint_integer(runtime,
+				SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+
+	return 0;
+}
+
+static int snd_cs5535audio_playback_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+#define CS5535AUDIO_DESC_LIST_SIZE \
+	PAGE_ALIGN(CS5535AUDIO_MAX_DESCRIPTORS * sizeof(struct cs5535audio_dma_desc))
+
+static int cs5535audio_build_dma_packets(struct cs5535audio *cs5535au,
+					 struct cs5535audio_dma *dma,
+					 struct snd_pcm_substream *substream,
+					 unsigned int periods,
+					 unsigned int period_bytes)
+{
+	unsigned int i;
+	u32 addr, desc_addr, jmpprd_addr;
+	struct cs5535audio_dma_desc *lastdesc;
+
+	if (periods > CS5535AUDIO_MAX_DESCRIPTORS)
+		return -ENOMEM;
+
+	if (dma->desc_buf.area == NULL) {
+		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+					snd_dma_pci_data(cs5535au->pci),
+					CS5535AUDIO_DESC_LIST_SIZE+1,
+					&dma->desc_buf) < 0)
+			return -ENOMEM;
+		dma->period_bytes = dma->periods = 0;
+	}
+
+	if (dma->periods == periods && dma->period_bytes == period_bytes)
+		return 0;
+
+	/* the u32 cast is okay because in snd*create we successfully told
+   	   pci alloc that we're only 32 bit capable so the uppper will be 0 */
+	addr = (u32) substream->runtime->dma_addr;
+	desc_addr = (u32) dma->desc_buf.addr;
+	for (i = 0; i < periods; i++) {
+		struct cs5535audio_dma_desc *desc =
+			&((struct cs5535audio_dma_desc *) dma->desc_buf.area)[i];
+		desc->addr = cpu_to_le32(addr);
+		desc->size = cpu_to_le32(period_bytes);
+		desc->ctlreserved = cpu_to_le32(PRD_EOP);
+		desc_addr += sizeof(struct cs5535audio_dma_desc);
+		addr += period_bytes;
+	}
+	/* we reserved one dummy descriptor at the end to do the PRD jump */
+	lastdesc = &((struct cs5535audio_dma_desc *) dma->desc_buf.area)[periods];
+	lastdesc->addr = cpu_to_le32((u32) dma->desc_buf.addr);
+	lastdesc->size = 0;
+	lastdesc->ctlreserved = cpu_to_le32(PRD_JMP);
+	jmpprd_addr = cpu_to_le32(lastdesc->addr +
+				  (sizeof(struct cs5535audio_dma_desc)*periods));
+
+	dma->substream = substream;
+	dma->period_bytes = period_bytes;
+	dma->periods = periods;
+	spin_lock_irq(&cs5535au->reg_lock);
+	dma->ops->disable_dma(cs5535au);
+	dma->ops->setup_prd(cs5535au, jmpprd_addr);
+	spin_unlock_irq(&cs5535au->reg_lock);
+	return 0;
+}
+
+static void cs5535audio_playback_enable_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_EN);
+}
+
+static void cs5535audio_playback_disable_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM0_CMD, 0);
+}
+
+static void cs5535audio_playback_pause_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_PAUSE);
+}
+
+static void cs5535audio_playback_setup_prd(struct cs5535audio *cs5535au,
+					   u32 prd_addr)
+{
+	cs_writel(cs5535au, ACC_BM0_PRD, prd_addr);
+}
+
+static u32 cs5535audio_playback_read_prd(struct cs5535audio *cs5535au)
+{
+	return cs_readl(cs5535au, ACC_BM0_PRD);
+}
+
+static u32 cs5535audio_playback_read_dma_pntr(struct cs5535audio *cs5535au)
+{
+	return cs_readl(cs5535au, ACC_BM0_PNTR);
+}
+
+static void cs5535audio_capture_enable_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_EN);
+}
+
+static void cs5535audio_capture_disable_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM1_CMD, 0);
+}
+
+static void cs5535audio_capture_pause_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_PAUSE);
+}
+
+static void cs5535audio_capture_setup_prd(struct cs5535audio *cs5535au,
+					  u32 prd_addr)
+{
+	cs_writel(cs5535au, ACC_BM1_PRD, prd_addr);
+}
+
+static u32 cs5535audio_capture_read_prd(struct cs5535audio *cs5535au)
+{
+	return cs_readl(cs5535au, ACC_BM1_PRD);
+}
+
+static u32 cs5535audio_capture_read_dma_pntr(struct cs5535audio *cs5535au)
+{
+	return cs_readl(cs5535au, ACC_BM1_PNTR);
+}
+
+static void cs5535audio_clear_dma_packets(struct cs5535audio *cs5535au,
+					  struct cs5535audio_dma *dma,
+					  struct snd_pcm_substream *substream)
+{
+	snd_dma_free_pages(&dma->desc_buf);
+	dma->desc_buf.area = NULL;
+	dma->substream = NULL;
+}
+
+static int snd_cs5535audio_hw_params(struct snd_pcm_substream *substream,
+				     struct snd_pcm_hw_params *hw_params)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	struct cs5535audio_dma *dma = substream->runtime->private_data;
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	dma->buf_addr = substream->runtime->dma_addr;
+	dma->buf_bytes = params_buffer_bytes(hw_params);
+
+	err = cs5535audio_build_dma_packets(cs5535au, dma, substream,
+					    params_periods(hw_params),
+					    params_period_bytes(hw_params));
+	if (!err)
+		dma->pcm_open_flag = 1;
+
+	return err;
+}
+
+static int snd_cs5535audio_hw_free(struct snd_pcm_substream *substream)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	struct cs5535audio_dma *dma = substream->runtime->private_data;
+
+	if (dma->pcm_open_flag) {
+		if (substream == cs5535au->playback_substream)
+			snd_ac97_update_power(cs5535au->ac97,
+					AC97_PCM_FRONT_DAC_RATE, 0);
+		else
+			snd_ac97_update_power(cs5535au->ac97,
+					AC97_PCM_LR_ADC_RATE, 0);
+		dma->pcm_open_flag = 0;
+	}
+	cs5535audio_clear_dma_packets(cs5535au, dma, substream);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_cs5535audio_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_FRONT_DAC_RATE,
+				 substream->runtime->rate);
+}
+
+static int snd_cs5535audio_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	struct cs5535audio_dma *dma = substream->runtime->private_data;
+	int err = 0;
+
+	spin_lock(&cs5535au->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		dma->ops->pause_dma(cs5535au);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		dma->ops->enable_dma(cs5535au);
+		break;
+	case SNDRV_PCM_TRIGGER_START:
+		dma->ops->enable_dma(cs5535au);
+		break;
+	case SNDRV_PCM_TRIGGER_RESUME:
+		dma->ops->enable_dma(cs5535au);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		dma->ops->disable_dma(cs5535au);
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		dma->ops->disable_dma(cs5535au);
+		break;
+	default:
+		snd_printk(KERN_ERR "unhandled trigger\n");
+		err = -EINVAL;
+		break;
+	}
+	spin_unlock(&cs5535au->reg_lock);
+	return err;
+}
+
+static snd_pcm_uframes_t snd_cs5535audio_pcm_pointer(struct snd_pcm_substream
+							*substream)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	u32 curdma;
+	struct cs5535audio_dma *dma;
+
+	dma = substream->runtime->private_data;
+	curdma = dma->ops->read_dma_pntr(cs5535au);
+	if (curdma < dma->buf_addr) {
+		snd_printk(KERN_ERR "curdma=%x < %x bufaddr.\n",
+					curdma, dma->buf_addr);
+		return 0;
+	}
+	curdma -= dma->buf_addr;
+	if (curdma >= dma->buf_bytes) {
+		snd_printk(KERN_ERR "diff=%x >= %x buf_bytes.\n",
+					curdma, dma->buf_bytes);
+		return 0;
+	}
+	return bytes_to_frames(substream->runtime, curdma);
+}
+
+static int snd_cs5535audio_capture_open(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = snd_cs5535audio_capture;
+	runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_ADC];
+	snd_pcm_limit_hw_rates(runtime);
+	cs5535au->capture_substream = substream;
+	runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE]);
+	if ((err = snd_pcm_hw_constraint_integer(runtime,
+					 SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+	olpc_capture_open(cs5535au->ac97);
+	return 0;
+}
+
+static int snd_cs5535audio_capture_close(struct snd_pcm_substream *substream)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	olpc_capture_close(cs5535au->ac97);
+	return 0;
+}
+
+static int snd_cs5535audio_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_LR_ADC_RATE,
+				 substream->runtime->rate);
+}
+
+static struct snd_pcm_ops snd_cs5535audio_playback_ops = {
+	.open =		snd_cs5535audio_playback_open,
+	.close =	snd_cs5535audio_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cs5535audio_hw_params,
+	.hw_free =	snd_cs5535audio_hw_free,
+	.prepare =	snd_cs5535audio_playback_prepare,
+	.trigger =	snd_cs5535audio_trigger,
+	.pointer =	snd_cs5535audio_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_cs5535audio_capture_ops = {
+	.open =		snd_cs5535audio_capture_open,
+	.close =	snd_cs5535audio_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cs5535audio_hw_params,
+	.hw_free =	snd_cs5535audio_hw_free,
+	.prepare =	snd_cs5535audio_capture_prepare,
+	.trigger =	snd_cs5535audio_trigger,
+	.pointer =	snd_cs5535audio_pcm_pointer,
+};
+
+static struct cs5535audio_dma_ops snd_cs5535audio_playback_dma_ops = {
+        .type = CS5535AUDIO_DMA_PLAYBACK,
+        .enable_dma = cs5535audio_playback_enable_dma,
+        .disable_dma = cs5535audio_playback_disable_dma,
+        .setup_prd = cs5535audio_playback_setup_prd,
+        .read_prd = cs5535audio_playback_read_prd,
+        .pause_dma = cs5535audio_playback_pause_dma,
+        .read_dma_pntr = cs5535audio_playback_read_dma_pntr,
+};
+
+static struct cs5535audio_dma_ops snd_cs5535audio_capture_dma_ops = {
+        .type = CS5535AUDIO_DMA_CAPTURE,
+        .enable_dma = cs5535audio_capture_enable_dma,
+        .disable_dma = cs5535audio_capture_disable_dma,
+        .setup_prd = cs5535audio_capture_setup_prd,
+        .read_prd = cs5535audio_capture_read_prd,
+        .pause_dma = cs5535audio_capture_pause_dma,
+        .read_dma_pntr = cs5535audio_capture_read_dma_pntr,
+};
+
+int __devinit snd_cs5535audio_pcm(struct cs5535audio *cs5535au)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(cs5535au->card, "CS5535 Audio", 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK].ops =
+					&snd_cs5535audio_playback_dma_ops;
+	cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE].ops =
+					&snd_cs5535audio_capture_dma_ops;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					&snd_cs5535audio_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+					&snd_cs5535audio_capture_ops);
+
+	pcm->private_data = cs5535au;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "CS5535 Audio");
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					snd_dma_pci_data(cs5535au->pci),
+					64*1024, 128*1024);
+	cs5535au->pcm = pcm;
+
+	return 0;
+}
+
diff --git a/linux/sound/pci/cs5535audio/cs5535audio_pm.c b/linux/sound/pci/cs5535audio/cs5535audio_pm.c
new file mode 100644
index 0000000..a3301cc
--- /dev/null
+++ b/linux/sound/pci/cs5535audio/cs5535audio_pm.c
@@ -0,0 +1,136 @@
+/*
+ * Power management for audio on multifunction CS5535 companion device
+ * Copyright (C) Jaya Kumar
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include "cs5535audio.h"
+
+static void snd_cs5535audio_stop_hardware(struct cs5535audio *cs5535au)
+{
+	/* 
+	we depend on snd_ac97_suspend to tell the
+	AC97 codec to shutdown. the amd spec suggests
+	that the LNK_SHUTDOWN be done at the same time
+	that the codec power-down is issued. instead,
+	we do it just after rather than at the same 
+	time. excluding codec specific build_ops->suspend
+	ac97 powerdown hits:
+	0x8000 EAPD 
+	0x4000 Headphone amplifier 
+	0x0300 ADC & DAC 
+	0x0400 Analog Mixer powerdown (Vref on) 
+	I am not sure if this is the best that we can do.
+	The remainder to be investigated are:
+	- analog mixer (vref off) 0x0800
+	- AC-link powerdown 0x1000
+	- codec internal clock 0x2000
+	*/
+
+	/* set LNK_SHUTDOWN to shutdown AC link */
+	cs_writel(cs5535au, ACC_CODEC_CNTL, ACC_CODEC_CNTL_LNK_SHUTDOWN);
+
+}
+
+int snd_cs5535audio_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct cs5535audio *cs5535au = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(cs5535au->pcm);
+	snd_ac97_suspend(cs5535au->ac97);
+	for (i = 0; i < NUM_CS5535AUDIO_DMAS; i++) {
+		struct cs5535audio_dma *dma = &cs5535au->dmas[i];
+		if (dma && dma->substream)
+			dma->saved_prd = dma->ops->read_prd(cs5535au);
+	}
+	/* save important regs, then disable aclink in hw */
+	snd_cs5535audio_stop_hardware(cs5535au);
+
+	if (pci_save_state(pci)) {
+		printk(KERN_ERR "cs5535audio: pci_save_state failed!\n");
+		return -EIO;
+	}
+	pci_disable_device(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+int snd_cs5535audio_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct cs5535audio *cs5535au = card->private_data;
+	u32 tmp;
+	int timeout;
+	int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	if (pci_restore_state(pci) < 0) {
+		printk(KERN_ERR "cs5535audio: pci_restore_state failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "cs5535audio: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	/* set LNK_WRM_RST to reset AC link */
+	cs_writel(cs5535au, ACC_CODEC_CNTL, ACC_CODEC_CNTL_LNK_WRM_RST);
+
+	timeout = 50;
+	do {
+		tmp = cs_readl(cs5535au, ACC_CODEC_STATUS);
+		if (tmp & PRM_RDY_STS)
+			break;
+		udelay(1);
+	} while (--timeout);
+
+	if (!timeout)
+		snd_printk(KERN_ERR "Failure getting AC Link ready\n");
+
+	/* set up rate regs, dma. actual initiation is done in trig */
+	for (i = 0; i < NUM_CS5535AUDIO_DMAS; i++) {
+		struct cs5535audio_dma *dma = &cs5535au->dmas[i];
+		if (dma && dma->substream) {
+			dma->substream->ops->prepare(dma->substream);
+			dma->ops->setup_prd(cs5535au, dma->saved_prd);
+		}
+	}
+
+	/* we depend on ac97 to perform the codec power up */
+	snd_ac97_resume(cs5535au->ac97);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+
+	return 0;
+}
+
diff --git a/linux/sound/pci/ctxfi/Makefile b/linux/sound/pci/ctxfi/Makefile
new file mode 100644
index 0000000..15075f8
--- /dev/null
+++ b/linux/sound/pci/ctxfi/Makefile
@@ -0,0 +1,5 @@
+snd-ctxfi-objs := xfi.o ctatc.o ctvmem.o ctpcm.o ctmixer.o ctresource.o \
+	ctsrc.o ctamixer.o ctdaio.o ctimap.o cthardware.o cttimer.o \
+	cthw20k2.o cthw20k1.o
+
+obj-$(CONFIG_SND_CTXFI) += snd-ctxfi.o
diff --git a/linux/sound/pci/ctxfi/ct20k1reg.h b/linux/sound/pci/ctxfi/ct20k1reg.h
new file mode 100644
index 0000000..f2e34e3
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ct20k1reg.h
@@ -0,0 +1,636 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ */
+
+#ifndef CT20K1REG_H
+#define CT20k1REG_H
+
+/* 20k1 registers */
+#define 	DSPXRAM_START 			0x000000
+#define 	DSPXRAM_END 			0x013FFC
+#define 	DSPAXRAM_START 			0x020000
+#define 	DSPAXRAM_END 			0x023FFC
+#define 	DSPYRAM_START 			0x040000
+#define 	DSPYRAM_END 			0x04FFFC
+#define 	DSPAYRAM_START 			0x020000
+#define 	DSPAYRAM_END 			0x063FFC
+#define 	DSPMICRO_START 			0x080000
+#define 	DSPMICRO_END 			0x0B3FFC
+#define 	DSP0IO_START 			0x100000
+#define 	DSP0IO_END	 		0x101FFC
+#define 	AUDIORINGIPDSP0_START 		0x100000
+#define 	AUDIORINGIPDSP0_END 		0x1003FC
+#define 	AUDIORINGOPDSP0_START 		0x100400
+#define 	AUDIORINGOPDSP0_END 		0x1007FC
+#define 	AUDPARARINGIODSP0_START 	0x100800
+#define 	AUDPARARINGIODSP0_END	 	0x100BFC
+#define 	DSP0LOCALHWREG_START 		0x100C00
+#define 	DSP0LOCALHWREG_END	 	0x100C3C
+#define 	DSP0XYRAMAGINDEX_START 		0x100C40
+#define 	DSP0XYRAMAGINDEX_END	 	0x100C5C
+#define 	DSP0XYRAMAGMDFR_START 		0x100C60
+#define 	DSP0XYRAMAGMDFR_END	 	0x100C7C
+#define 	DSP0INTCONTLVEC_START 		0x100C80
+#define 	DSP0INTCONTLVEC_END	 	0x100CD8
+#define 	INTCONTLGLOBALREG_START 	0x100D1C
+#define 	INTCONTLGLOBALREG_END	 	0x100D3C
+#define		HOSTINTFPORTADDRCONTDSP0	0x100D40
+#define		HOSTINTFPORTDATADSP0		0x100D44
+#define		TIME0PERENBDSP0			0x100D60
+#define		TIME0COUNTERDSP0		0x100D64
+#define		TIME1PERENBDSP0			0x100D68
+#define		TIME1COUNTERDSP0		0x100D6C
+#define		TIME2PERENBDSP0			0x100D70
+#define		TIME2COUNTERDSP0		0x100D74
+#define		TIME3PERENBDSP0			0x100D78
+#define		TIME3COUNTERDSP0		0x100D7C
+#define 	XRAMINDOPERREFNOUP_STARTDSP0 	0x100D80
+#define 	XRAMINDOPERREFNOUP_ENDDSP0	0x100D9C
+#define 	XRAMINDOPERREFUP_STARTDSP0	0x100DA0
+#define 	XRAMINDOPERREFUP_ENDDSP0	0x100DBC
+#define 	YRAMINDOPERREFNOUP_STARTDSP0 	0x100DC0
+#define 	YRAMINDOPERREFNOUP_ENDDSP0 	0x100DDC
+#define 	YRAMINDOPERREFUP_STARTDSP0	0x100DE0
+#define 	YRAMINDOPERREFUP_ENDDSP0 	0x100DFC
+#define 	DSP0CONDCODE 			0x100E00
+#define 	DSP0STACKFLAG 			0x100E04
+#define 	DSP0PROGCOUNTSTACKPTREG 	0x100E08
+#define 	DSP0PROGCOUNTSTACKDATAREG 	0x100E0C
+#define 	DSP0CURLOOPADDRREG 		0x100E10
+#define 	DSP0CURLOOPCOUNT 		0x100E14
+#define 	DSP0TOPLOOPCOUNTSTACK 		0x100E18
+#define 	DSP0TOPLOOPADDRSTACK 		0x100E1C
+#define 	DSP0LOOPSTACKPTR 		0x100E20
+#define 	DSP0STASSTACKDATAREG 		0x100E24
+#define 	DSP0STASSTACKPTR 		0x100E28
+#define 	DSP0PROGCOUNT 			0x100E2C
+#define  	GLOBDSPDEBGREG			0x100E30
+#define  	GLOBDSPBREPTRREG		0x100E30
+#define 	DSP0XYRAMBASE_START 		0x100EA0
+#define 	DSP0XYRAMBASE_END 		0x100EBC
+#define 	DSP0XYRAMLENG_START 		0x100EC0
+#define 	DSP0XYRAMLENG_END 		0x100EDC
+#define		SEMAPHOREREGDSP0		0x100EE0
+#define		DSP0INTCONTMASKREG		0x100EE4
+#define		DSP0INTCONTPENDREG		0x100EE8
+#define		DSP0INTCONTSERVINT		0x100EEC
+#define		DSPINTCONTEXTINTMODREG		0x100EEC
+#define		GPIODSP0			0x100EFC
+#define 	DMADSPBASEADDRREG_STARTDSP0	0x100F00
+#define 	DMADSPBASEADDRREG_ENDDSP0	0x100F1C
+#define 	DMAHOSTBASEADDRREG_STARTDSP0	0x100F20
+#define 	DMAHOSTBASEADDRREG_ENDDSP0	0x100F3C
+#define 	DMADSPCURADDRREG_STARTDSP0	0x100F40
+#define 	DMADSPCURADDRREG_ENDDSP0	0x100F5C
+#define 	DMAHOSTCURADDRREG_STARTDSP0	0x100F60
+#define 	DMAHOSTCURADDRREG_ENDDSP0	0x100F7C
+#define 	DMATANXCOUNTREG_STARTDSP0	0x100F80
+#define 	DMATANXCOUNTREG_ENDDSP0		0x100F9C
+#define 	DMATIMEBUGREG_STARTDSP0		0x100FA0
+#define 	DMATIMEBUGREG_ENDDSP0		0x100FAC
+#define 	DMACNTLMODFREG_STARTDSP0	0x100FA0
+#define 	DMACNTLMODFREG_ENDDSP0		0x100FAC
+
+#define 	DMAGLOBSTATSREGDSP0		0x100FEC
+#define 	DSP0XGPRAM_START 		0x101000
+#define 	DSP0XGPRAM_END 			0x1017FC
+#define 	DSP0YGPRAM_START 		0x101800
+#define 	DSP0YGPRAM_END 			0x101FFC
+
+
+
+
+#define 	AUDIORINGIPDSP1_START 		0x102000
+#define 	AUDIORINGIPDSP1_END	 	0x1023FC
+#define 	AUDIORINGOPDSP1_START 		0x102400
+#define 	AUDIORINGOPDSP1_END	 	0x1027FC
+#define 	AUDPARARINGIODSP1_START 	0x102800
+#define 	AUDPARARINGIODSP1_END	 	0x102BFC
+#define 	DSP1LOCALHWREG_START 		0x102C00
+#define 	DSP1LOCALHWREG_END	 	0x102C3C
+#define 	DSP1XYRAMAGINDEX_START 		0x102C40
+#define 	DSP1XYRAMAGINDEX_END	 	0x102C5C
+#define 	DSP1XYRAMAGMDFR_START 		0x102C60
+#define 	DSP1XYRAMAGMDFR_END	 	0x102C7C
+#define 	DSP1INTCONTLVEC_START 		0x102C80
+#define 	DSP1INTCONTLVEC_END	 	0x102CD8
+#define		HOSTINTFPORTADDRCONTDSP1	0x102D40
+#define		HOSTINTFPORTDATADSP1		0x102D44
+#define		TIME0PERENBDSP1			0x102D60
+#define		TIME0COUNTERDSP1		0x102D64
+#define		TIME1PERENBDSP1			0x102D68
+#define		TIME1COUNTERDSP1		0x102D6C
+#define		TIME2PERENBDSP1			0x102D70
+#define		TIME2COUNTERDSP1		0x102D74
+#define		TIME3PERENBDSP1			0x102D78
+#define		TIME3COUNTERDSP1		0x102D7C
+#define 	XRAMINDOPERREFNOUP_STARTDSP1 	0x102D80
+#define 	XRAMINDOPERREFNOUP_ENDDSP1	0x102D9C
+#define 	XRAMINDOPERREFUP_STARTDSP1	0x102DA0
+#define 	XRAMINDOPERREFUP_ENDDSP1	0x102DBC
+#define 	YRAMINDOPERREFNOUP_STARTDSP1 	0x102DC0
+#define 	YRAMINDOPERREFNOUP_ENDDSP1	0x102DDC
+#define 	YRAMINDOPERREFUP_STARTDSP1	0x102DE0
+#define 	YRAMINDOPERREFUP_ENDDSP1	0x102DFC
+
+#define 	DSP1CONDCODE 			0x102E00
+#define 	DSP1STACKFLAG 			0x102E04
+#define 	DSP1PROGCOUNTSTACKPTREG 	0x102E08
+#define 	DSP1PROGCOUNTSTACKDATAREG 	0x102E0C
+#define 	DSP1CURLOOPADDRREG 		0x102E10
+#define 	DSP1CURLOOPCOUNT 		0x102E14
+#define 	DSP1TOPLOOPCOUNTSTACK 		0x102E18
+#define 	DSP1TOPLOOPADDRSTACK 		0x102E1C
+#define 	DSP1LOOPSTACKPTR 		0x102E20
+#define 	DSP1STASSTACKDATAREG 		0x102E24
+#define 	DSP1STASSTACKPTR 		0x102E28
+#define 	DSP1PROGCOUNT 			0x102E2C
+#define 	DSP1XYRAMBASE_START 		0x102EA0
+#define 	DSP1XYRAMBASE_END 		0x102EBC
+#define 	DSP1XYRAMLENG_START 		0x102EC0
+#define 	DSP1XYRAMLENG_END 		0x102EDC
+#define		SEMAPHOREREGDSP1		0x102EE0
+#define		DSP1INTCONTMASKREG		0x102EE4
+#define		DSP1INTCONTPENDREG		0x102EE8
+#define		DSP1INTCONTSERVINT		0x102EEC
+#define		GPIODSP1			0x102EFC
+#define 	DMADSPBASEADDRREG_STARTDSP1	0x102F00
+#define 	DMADSPBASEADDRREG_ENDDSP1	0x102F1C
+#define 	DMAHOSTBASEADDRREG_STARTDSP1	0x102F20
+#define 	DMAHOSTBASEADDRREG_ENDDSP1	0x102F3C
+#define 	DMADSPCURADDRREG_STARTDSP1	0x102F40
+#define 	DMADSPCURADDRREG_ENDDSP1	0x102F5C
+#define 	DMAHOSTCURADDRREG_STARTDSP1	0x102F60
+#define 	DMAHOSTCURADDRREG_ENDDSP1	0x102F7C
+#define 	DMATANXCOUNTREG_STARTDSP1	0x102F80
+#define 	DMATANXCOUNTREG_ENDDSP1		0x102F9C
+#define 	DMATIMEBUGREG_STARTDSP1		0x102FA0
+#define 	DMATIMEBUGREG_ENDDSP1		0x102FAC
+#define 	DMACNTLMODFREG_STARTDSP1	0x102FA0
+#define 	DMACNTLMODFREG_ENDDSP1		0x102FAC
+
+#define 	DMAGLOBSTATSREGDSP1		0x102FEC
+#define 	DSP1XGPRAM_START 		0x103000
+#define 	DSP1XGPRAM_END 			0x1033FC
+#define 	DSP1YGPRAM_START 		0x103400
+#define 	DSP1YGPRAM_END 			0x1037FC
+
+
+
+#define 	AUDIORINGIPDSP2_START 		0x104000
+#define 	AUDIORINGIPDSP2_END	 	0x1043FC
+#define 	AUDIORINGOPDSP2_START 		0x104400
+#define 	AUDIORINGOPDSP2_END	 	0x1047FC
+#define 	AUDPARARINGIODSP2_START 	0x104800
+#define 	AUDPARARINGIODSP2_END	 	0x104BFC
+#define 	DSP2LOCALHWREG_START 		0x104C00
+#define 	DSP2LOCALHWREG_END	 	0x104C3C
+#define 	DSP2XYRAMAGINDEX_START 		0x104C40
+#define 	DSP2XYRAMAGINDEX_END	 	0x104C5C
+#define 	DSP2XYRAMAGMDFR_START 		0x104C60
+#define 	DSP2XYRAMAGMDFR_END		0x104C7C
+#define 	DSP2INTCONTLVEC_START 		0x104C80
+#define 	DSP2INTCONTLVEC_END	 	0x104CD8
+#define		HOSTINTFPORTADDRCONTDSP2	0x104D40
+#define		HOSTINTFPORTDATADSP2		0x104D44
+#define		TIME0PERENBDSP2			0x104D60
+#define		TIME0COUNTERDSP2		0x104D64
+#define		TIME1PERENBDSP2			0x104D68
+#define		TIME1COUNTERDSP2		0x104D6C
+#define		TIME2PERENBDSP2			0x104D70
+#define		TIME2COUNTERDSP2		0x104D74
+#define		TIME3PERENBDSP2			0x104D78
+#define		TIME3COUNTERDSP2		0x104D7C
+#define 	XRAMINDOPERREFNOUP_STARTDSP2 	0x104D80
+#define 	XRAMINDOPERREFNOUP_ENDDSP2	0x104D9C
+#define 	XRAMINDOPERREFUP_STARTDSP2	0x104DA0
+#define 	XRAMINDOPERREFUP_ENDDSP2	0x104DBC
+#define 	YRAMINDOPERREFNOUP_STARTDSP2 	0x104DC0
+#define 	YRAMINDOPERREFNOUP_ENDDSP2	0x104DDC
+#define 	YRAMINDOPERREFUP_STARTDSP2	0x104DE0
+#define 	YRAMINDOPERREFUP_ENDDSP2 	0x104DFC
+#define 	DSP2CONDCODE 			0x104E00
+#define 	DSP2STACKFLAG 			0x104E04
+#define 	DSP2PROGCOUNTSTACKPTREG 	0x104E08
+#define 	DSP2PROGCOUNTSTACKDATAREG 	0x104E0C
+#define 	DSP2CURLOOPADDRREG 		0x104E10
+#define 	DSP2CURLOOPCOUNT 		0x104E14
+#define 	DSP2TOPLOOPCOUNTSTACK 		0x104E18
+#define 	DSP2TOPLOOPADDRSTACK 		0x104E1C
+#define 	DSP2LOOPSTACKPTR 		0x104E20
+#define 	DSP2STASSTACKDATAREG 		0x104E24
+#define 	DSP2STASSTACKPTR 		0x104E28
+#define 	DSP2PROGCOUNT 			0x104E2C
+#define 	DSP2XYRAMBASE_START 		0x104EA0
+#define 	DSP2XYRAMBASE_END 		0x104EBC
+#define 	DSP2XYRAMLENG_START 		0x104EC0
+#define 	DSP2XYRAMLENG_END 		0x104EDC
+#define		SEMAPHOREREGDSP2		0x104EE0
+#define		DSP2INTCONTMASKREG		0x104EE4
+#define		DSP2INTCONTPENDREG		0x104EE8
+#define		DSP2INTCONTSERVINT		0x104EEC
+#define		GPIODSP2			0x104EFC
+#define 	DMADSPBASEADDRREG_STARTDSP2	0x104F00
+#define 	DMADSPBASEADDRREG_ENDDSP2	0x104F1C
+#define 	DMAHOSTBASEADDRREG_STARTDSP2	0x104F20
+#define 	DMAHOSTBASEADDRREG_ENDDSP2	0x104F3C
+#define 	DMADSPCURADDRREG_STARTDSP2	0x104F40
+#define 	DMADSPCURADDRREG_ENDDSP2	0x104F5C
+#define 	DMAHOSTCURADDRREG_STARTDSP2	0x104F60
+#define 	DMAHOSTCURADDRREG_ENDDSP2	0x104F7C
+#define 	DMATANXCOUNTREG_STARTDSP2	0x104F80
+#define 	DMATANXCOUNTREG_ENDDSP2		0x104F9C
+#define 	DMATIMEBUGREG_STARTDSP2		0x104FA0
+#define 	DMATIMEBUGREG_ENDDSP2		0x104FAC
+#define 	DMACNTLMODFREG_STARTDSP2	0x104FA0
+#define 	DMACNTLMODFREG_ENDDSP2		0x104FAC
+
+#define 	DMAGLOBSTATSREGDSP2		0x104FEC
+#define 	DSP2XGPRAM_START 		0x105000
+#define 	DSP2XGPRAM_END 			0x1051FC
+#define 	DSP2YGPRAM_START 		0x105800
+#define 	DSP2YGPRAM_END 			0x1059FC
+
+
+
+#define 	AUDIORINGIPDSP3_START 		0x106000
+#define 	AUDIORINGIPDSP3_END	 	0x1063FC
+#define 	AUDIORINGOPDSP3_START 		0x106400
+#define 	AUDIORINGOPDSP3_END	 	0x1067FC
+#define 	AUDPARARINGIODSP3_START 	0x106800
+#define 	AUDPARARINGIODSP3_END	 	0x106BFC
+#define 	DSP3LOCALHWREG_START 		0x106C00
+#define 	DSP3LOCALHWREG_END	 	0x106C3C
+#define 	DSP3XYRAMAGINDEX_START 		0x106C40
+#define 	DSP3XYRAMAGINDEX_END	 	0x106C5C
+#define 	DSP3XYRAMAGMDFR_START 		0x106C60
+#define 	DSP3XYRAMAGMDFR_END		0x106C7C
+#define 	DSP3INTCONTLVEC_START 		0x106C80
+#define 	DSP3INTCONTLVEC_END	 	0x106CD8
+#define		HOSTINTFPORTADDRCONTDSP3	0x106D40
+#define		HOSTINTFPORTDATADSP3		0x106D44
+#define		TIME0PERENBDSP3			0x106D60
+#define		TIME0COUNTERDSP3		0x106D64
+#define		TIME1PERENBDSP3			0x106D68
+#define		TIME1COUNTERDSP3		0x106D6C
+#define		TIME2PERENBDSP3			0x106D70
+#define		TIME2COUNTERDSP3		0x106D74
+#define		TIME3PERENBDSP3			0x106D78
+#define		TIME3COUNTERDSP3		0x106D7C
+#define 	XRAMINDOPERREFNOUP_STARTDSP3 	0x106D80
+#define 	XRAMINDOPERREFNOUP_ENDDSP3	0x106D9C
+#define 	XRAMINDOPERREFUP_STARTDSP3	0x106DA0
+#define 	XRAMINDOPERREFUP_ENDDSP3	0x106DBC
+#define 	YRAMINDOPERREFNOUP_STARTDSP3 	0x106DC0
+#define 	YRAMINDOPERREFNOUP_ENDDSP3	0x106DDC
+#define 	YRAMINDOPERREFUP_STARTDSP3	0x106DE0
+#define 	YRAMINDOPERREFUP_ENDDSP3	0x100DFC
+
+#define 	DSP3CONDCODE 			0x106E00
+#define 	DSP3STACKFLAG 			0x106E04
+#define 	DSP3PROGCOUNTSTACKPTREG 	0x106E08
+#define 	DSP3PROGCOUNTSTACKDATAREG 	0x106E0C
+#define 	DSP3CURLOOPADDRREG 		0x106E10
+#define 	DSP3CURLOOPCOUNT 		0x106E14
+#define 	DSP3TOPLOOPCOUNTSTACK 		0x106E18
+#define 	DSP3TOPLOOPADDRSTACK 		0x106E1C
+#define 	DSP3LOOPSTACKPTR 		0x106E20
+#define 	DSP3STASSTACKDATAREG 		0x106E24
+#define 	DSP3STASSTACKPTR 		0x106E28
+#define 	DSP3PROGCOUNT 			0x106E2C
+#define 	DSP3XYRAMBASE_START 		0x106EA0
+#define 	DSP3XYRAMBASE_END 		0x106EBC
+#define 	DSP3XYRAMLENG_START 		0x106EC0
+#define 	DSP3XYRAMLENG_END 		0x106EDC
+#define		SEMAPHOREREGDSP3		0x106EE0
+#define		DSP3INTCONTMASKREG		0x106EE4
+#define		DSP3INTCONTPENDREG		0x106EE8
+#define		DSP3INTCONTSERVINT		0x106EEC
+#define		GPIODSP3			0x106EFC
+#define 	DMADSPBASEADDRREG_STARTDSP3	0x106F00
+#define 	DMADSPBASEADDRREG_ENDDSP3	0x106F1C
+#define 	DMAHOSTBASEADDRREG_STARTDSP3	0x106F20
+#define 	DMAHOSTBASEADDRREG_ENDDSP3	0x106F3C
+#define 	DMADSPCURADDRREG_STARTDSP3	0x106F40
+#define 	DMADSPCURADDRREG_ENDDSP3	0x106F5C
+#define 	DMAHOSTCURADDRREG_STARTDSP3	0x106F60
+#define 	DMAHOSTCURADDRREG_ENDDSP3	0x106F7C
+#define 	DMATANXCOUNTREG_STARTDSP3	0x106F80
+#define 	DMATANXCOUNTREG_ENDDSP3		0x106F9C
+#define 	DMATIMEBUGREG_STARTDSP3		0x106FA0
+#define 	DMATIMEBUGREG_ENDDSP3		0x106FAC
+#define 	DMACNTLMODFREG_STARTDSP3	0x106FA0
+#define 	DMACNTLMODFREG_ENDDSP3		0x106FAC
+
+#define 	DMAGLOBSTATSREGDSP3		0x106FEC
+#define 	DSP3XGPRAM_START 		0x107000
+#define 	DSP3XGPRAM_END 			0x1071FC
+#define 	DSP3YGPRAM_START 		0x107800
+#define 	DSP3YGPRAM_END 			0x1079FC
+
+/* end of DSP reg definitions */
+
+#define  	DSPAIMAP_START			0x108000
+#define  	DSPAIMAP_END			0x1083FC
+#define  	DSPPIMAP_START			0x108400
+#define  	DSPPIMAP_END			0x1087FC
+#define  	DSPPOMAP_START			0x108800
+#define  	DSPPOMAP_END			0x108BFC
+#define  	DSPPOCTL			0x108C00
+#define 	TKCTL_START			0x110000
+#define 	TKCTL_END			0x110FFC
+#define 	TKCC_START			0x111000
+#define 	TKCC_END			0x111FFC
+#define 	TKIMAP_START			0x112000
+#define 	TKIMAP_END			0x112FFC
+#define		TKDCTR16			0x113000
+#define		TKPB16				0x113004
+#define		TKBS16				0x113008
+#define		TKDCTR32			0x11300C
+#define		TKPB32				0x113010
+#define		TKBS32				0x113014
+#define		ICDCTR16			0x113018
+#define		ITBS16				0x11301C
+#define		ICDCTR32			0x113020
+#define		ITBS32				0x113024
+#define		ITSTART				0x113028
+#define		TKSQ				0x11302C
+
+#define		TKSCCTL_START			0x114000
+#define		TKSCCTL_END			0x11403C
+#define		TKSCADR_START			0x114100
+#define		TKSCADR_END			0x11413C
+#define		TKSCDATAX_START			0x114800
+#define		TKSCDATAX_END			0x1149FC
+#define		TKPCDATAX_START			0x120000
+#define		TKPCDATAX_END			0x12FFFC
+
+#define		MALSA				0x130000
+#define		MAPPHA				0x130004
+#define		MAPPLA				0x130008
+#define		MALSB				0x130010
+#define		MAPPHB				0x130014
+#define		MAPPLB				0x130018
+
+#define 	TANSPORTMAPABREGS_START		0x130020
+#define 	TANSPORTMAPABREGS_END		0x13A2FC
+
+#define		PTPAHX				0x13B000
+#define		PTPALX				0x13B004
+
+#define		TANSPPAGETABLEPHYADDR015_START	0x13B008
+#define		TANSPPAGETABLEPHYADDR015_END	0x13B07C
+#define		TRNQADRX_START			0x13B100
+#define		TRNQADRX_END			0x13B13C
+#define		TRNQTIMX_START			0x13B200
+#define		TRNQTIMX_END			0x13B23C
+#define		TRNQAPARMX_START		0x13B300
+#define		TRNQAPARMX_END			0x13B33C
+
+#define		TRNQCNT				0x13B400
+#define		TRNCTL				0x13B404
+#define		TRNIS				0x13B408
+#define		TRNCURTS			0x13B40C
+
+#define		AMOP_START			0x140000
+#define		AMOPLO				0x140000
+#define		AMOPHI				0x140004
+#define		AMOP_END			0x147FFC
+#define		PMOP_START			0x148000
+#define		PMOPLO				0x148000
+#define		PMOPHI				0x148004
+#define		PMOP_END			0x14FFFC
+#define		PCURR_START			0x150000
+#define		PCURR_END			0x153FFC
+#define		PTRAG_START			0x154000
+#define		PTRAG_END			0x157FFC
+#define		PSR_START			0x158000
+#define		PSR_END				0x15BFFC
+
+#define		PFSTAT4SEG_START		0x160000
+#define		PFSTAT4SEG_END			0x160BFC
+#define		PFSTAT2SEG_START		0x160C00
+#define		PFSTAT2SEG_END			0x1617FC
+#define		PFTARG4SEG_START		0x164000
+#define		PFTARG4SEG_END			0x164BFC
+#define		PFTARG2SEG_START		0x164C00
+#define		PFTARG2SEG_END			0x1657FC
+#define		PFSR4SEG_START			0x168000
+#define		PFSR4SEG_END			0x168BFC
+#define		PFSR2SEG_START			0x168C00
+#define		PFSR2SEG_END			0x1697FC
+#define		PCURRMS4SEG_START		0x16C000
+#define		PCURRMS4SEG_END			0x16CCFC
+#define		PCURRMS2SEG_START		0x16CC00
+#define		PCURRMS2SEG_END			0x16D7FC
+#define		PTARGMS4SEG_START		0x170000
+#define		PTARGMS4SEG_END			0x172FFC
+#define		PTARGMS2SEG_START		0x173000
+#define		PTARGMS2SEG_END			0x1747FC
+#define		PSRMS4SEG_START			0x170000
+#define		PSRMS4SEG_END			0x172FFC
+#define		PSRMS2SEG_START			0x173000
+#define		PSRMS2SEG_END			0x1747FC
+
+#define		PRING_LO_START			0x190000
+#define		PRING_LO_END			0x193FFC
+#define		PRING_HI_START			0x194000
+#define		PRING_HI_END			0x197FFC
+#define		PRING_LO_HI_START		0x198000
+#define		PRING_LO_HI			0x198000
+#define		PRING_LO_HI_END			0x19BFFC
+
+#define		PINTFIFO			0x1A0000
+#define		SRCCTL				0x1B0000
+#define		SRCCCR				0x1B0004
+#define		SRCIMAP				0x1B0008
+#define		SRCODDC				0x1B000C
+#define		SRCCA				0x1B0010
+#define		SRCCF				0x1B0014
+#define		SRCSA				0x1B0018
+#define		SRCLA				0x1B001C
+#define		SRCCTLSWR			0x1B0020
+
+/* SRC HERE */
+#define		SRCALBA				0x1B002C
+#define		SRCMCTL				0x1B012C
+#define		SRCCERR				0x1B022C
+#define		SRCITB				0x1B032C
+#define		SRCIPM				0x1B082C
+#define		SRCIP				0x1B102C
+#define		SRCENBSTAT			0x1B202C
+#define		SRCENBLO			0x1B212C
+#define		SRCENBHI			0x1B222C
+#define		SRCENBS				0x1B232C
+#define		SRCENB				0x1B282C
+#define		SRCENB07			0x1B282C
+#define		SRCENBS07			0x1B302C
+
+#define		SRCDN0Z				0x1B0030
+#define		SRCDN0Z0			0x1B0030
+#define		SRCDN0Z1			0x1B0034
+#define		SRCDN0Z2			0x1B0038
+#define		SRCDN0Z3			0x1B003C
+#define		SRCDN1Z				0x1B0040
+#define		SRCDN1Z0			0x1B0040
+#define		SRCDN1Z1			0x1B0044
+#define		SRCDN1Z2			0x1B0048
+#define		SRCDN1Z3			0x1B004C
+#define		SRCDN1Z4			0x1B0050
+#define		SRCDN1Z5			0x1B0054
+#define		SRCDN1Z6			0x1B0058
+#define		SRCDN1Z7			0x1B005C
+#define		SRCUPZ				0x1B0060
+#define		SRCUPZ0				0x1B0060
+#define		SRCUPZ1				0x1B0064
+#define		SRCUPZ2				0x1B0068
+#define		SRCUPZ3				0x1B006C
+#define		SRCUPZ4				0x1B0070
+#define		SRCUPZ5				0x1B0074
+#define		SRCUPZ6				0x1B0078
+#define		SRCUPZ7				0x1B007C
+#define		SRCCD0				0x1B0080
+#define		SRCCD1				0x1B0084
+#define		SRCCD2				0x1B0088
+#define		SRCCD3				0x1B008C
+#define		SRCCD4				0x1B0090
+#define		SRCCD5				0x1B0094
+#define		SRCCD6				0x1B0098
+#define		SRCCD7				0x1B009C
+#define		SRCCD8				0x1B00A0
+#define		SRCCD9				0x1B00A4
+#define		SRCCDA				0x1B00A8
+#define		SRCCDB				0x1B00AC
+#define		SRCCDC				0x1B00B0
+#define		SRCCDD				0x1B00B4
+#define		SRCCDE				0x1B00B8
+#define		SRCCDF				0x1B00BC
+#define		SRCCD10				0x1B00C0
+#define		SRCCD11				0x1B00C4
+#define		SRCCD12				0x1B00C8
+#define		SRCCD13				0x1B00CC
+#define		SRCCD14				0x1B00D0
+#define		SRCCD15				0x1B00D4
+#define		SRCCD16				0x1B00D8
+#define		SRCCD17				0x1B00DC
+#define		SRCCD18				0x1B00E0
+#define		SRCCD19				0x1B00E4
+#define		SRCCD1A				0x1B00E8
+#define		SRCCD1B				0x1B00EC
+#define		SRCCD1C				0x1B00F0
+#define		SRCCD1D				0x1B00F4
+#define		SRCCD1E				0x1B00F8
+#define		SRCCD1F				0x1B00FC
+
+#define		SRCCONTRBLOCK_START		0x1B0100
+#define		SRCCONTRBLOCK_END		0x1BFFFC
+#define		FILTOP_START	0x1C0000
+#define		FILTOP_END	0x1C05FC
+#define		FILTIMAP_START	0x1C0800
+#define		FILTIMAP_END	0x1C0DFC
+#define		FILTZ1_START	0x1C1000
+#define		FILTZ1_END	0x1C15FC
+#define		FILTZ2_START	0x1C1800
+#define		FILTZ2_END	0x1C1DFC
+#define		DAOIMAP_START	0x1C5000
+#define		DAOIMAP		0x1C5000
+#define		DAOIMAP_END	0x1C5124
+
+#define		AC97D		0x1C5400
+#define		AC97A		0x1C5404
+#define		AC97CTL		0x1C5408
+#define		I2SCTL		0x1C5420
+
+#define		SPOS		0x1C5440
+#define		SPOSA		0x1C5440
+#define		SPOSB		0x1C5444
+#define		SPOSC		0x1C5448
+#define		SPOSD		0x1C544C
+
+#define		SPISA		0x1C5450
+#define		SPISB		0x1C5454
+#define		SPISC		0x1C5458
+#define		SPISD		0x1C545C
+
+#define		SPFSCTL		0x1C5460
+
+#define		SPFS0		0x1C5468
+#define		SPFS1		0x1C546C
+#define		SPFS2		0x1C5470
+#define		SPFS3		0x1C5474
+#define		SPFS4		0x1C5478
+#define		SPFS5		0x1C547C
+
+#define		SPOCTL		0x1C5480
+#define		SPICTL		0x1C5484
+#define		SPISTS		0x1C5488
+#define		SPINTP		0x1C548C
+#define		SPINTE		0x1C5490
+#define		SPUTCTLAB	0x1C5494
+#define		SPUTCTLCD	0x1C5498
+
+#define		SRTSPA		0x1C54C0
+#define		SRTSPB		0x1C54C4
+#define		SRTSPC		0x1C54C8
+#define		SRTSPD		0x1C54CC
+
+#define		SRTSCTL		0x1C54D0
+#define		SRTSCTLA	0x1C54D0
+#define		SRTSCTLB	0x1C54D4
+#define		SRTSCTLC	0x1C54D8
+#define		SRTSCTLD	0x1C54DC
+
+#define		SRTI2S		0x1C54E0
+#define		SRTICTL		0x1C54F0
+
+#define		WC		0x1C6000
+#define		TIMR		0x1C6004
+# define	TIMR_IE		(1<<15)
+# define	TIMR_IP		(1<<14)
+
+#define		GIP		0x1C6010
+#define		GIE		0x1C6014
+#define		DIE		0x1C6018
+#define		DIC		0x1C601C
+#define		GPIO		0x1C6020
+#define		GPIOCTL		0x1C6024
+#define		GPIP		0x1C6028
+#define		GPIE		0x1C602C
+#define		DSPINT0		0x1C6030
+#define		DSPEIOC		0x1C6034
+#define		MUADAT		0x1C6040
+#define		MUACMD		0x1C6044
+#define 	MUASTAT		0x1C6044
+#define		MUBDAT		0x1C6048
+#define		MUBCMD		0x1C604C
+#define		MUBSTAT		0x1C604C
+#define		UARTCMA		0x1C6050
+#define		UARTCMB		0x1C6054
+#define		UARTIP		0x1C6058
+#define		UARTIE		0x1C605C
+#define		PLLCTL		0x1C6060
+#define		PLLDCD		0x1C6064
+#define		GCTL		0x1C6070
+#define		ID0		0x1C6080
+#define		ID1		0x1C6084
+#define		ID2		0x1C6088
+#define		ID3		0x1C608C
+#define		SDRCTL		0x1C7000
+
+
+#define I2SA_L    0x0L
+#define I2SA_R    0x1L
+#define I2SB_L    0x8L
+#define I2SB_R    0x9L
+#define I2SC_L    0x10L
+#define I2SC_R    0x11L
+#define I2SD_L    0x18L
+#define I2SD_R    0x19L
+
+#endif /* CT20K1REG_H */
+
+
diff --git a/linux/sound/pci/ctxfi/ct20k2reg.h b/linux/sound/pci/ctxfi/ct20k2reg.h
new file mode 100644
index 0000000..e0394e3
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ct20k2reg.h
@@ -0,0 +1,88 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ */
+
+#ifndef _20K2REGISTERS_H_
+#define _20K2REGISTERS_H_
+
+
+/* Timer Registers */
+#define WC		0x1b7000
+#define TIMR		0x1b7004
+# define	TIMR_IE		(1<<15)
+# define	TIMR_IP		(1<<14)
+#define GIP		0x1b7010
+#define GIE		0x1b7014
+
+/* I2C Registers */
+#define I2C_IF_ADDRESS   0x1B9000
+#define I2C_IF_WDATA     0x1B9004
+#define I2C_IF_RDATA     0x1B9008
+#define I2C_IF_STATUS    0x1B900C
+#define I2C_IF_WLOCK     0x1B9010
+
+/* Global Control Registers */
+#define GLOBAL_CNTL_GCTL    0x1B7090
+
+/* PLL Registers */
+#define PLL_CTL 		0x1B7080
+#define PLL_STAT		0x1B7084
+#define PLL_ENB			0x1B7088
+
+/* SRC Registers */
+#define SRC_CTL             0x1A0000 /* 0x1A0000 + (256 * Chn) */
+#define SRC_CCR             0x1A0004 /* 0x1A0004 + (256 * Chn) */
+#define SRC_IMAP            0x1A0008 /* 0x1A0008 + (256 * Chn) */
+#define SRC_CA              0x1A0010 /* 0x1A0010 + (256 * Chn) */
+#define SRC_CF              0x1A0014 /* 0x1A0014 + (256 * Chn) */
+#define SRC_SA              0x1A0018 /* 0x1A0018 + (256 * Chn) */
+#define SRC_LA              0x1A001C /* 0x1A001C + (256 * Chn) */
+#define SRC_CTLSWR	    0x1A0020 /* 0x1A0020 + (256 * Chn) */
+#define SRC_CD		    0x1A0080 /* 0x1A0080 + (256 * Chn) + (4 * Regn) */
+#define SRC_MCTL		0x1A012C
+#define SRC_IP			0x1A102C /* 0x1A102C + (256 * Regn) */
+#define SRC_ENB			0x1A282C /* 0x1A282C + (256 * Regn) */
+#define SRC_ENBSTAT		0x1A202C
+#define SRC_ENBSA		0x1A232C
+#define SRC_DN0Z		0x1A0030
+#define SRC_DN1Z		0x1A0040
+#define SRC_UPZ			0x1A0060
+
+/* GPIO Registers */
+#define GPIO_DATA           0x1B7020
+#define GPIO_CTRL           0x1B7024
+
+/* Virtual memory registers */
+#define VMEM_PTPAL          0x1C6300 /* 0x1C6300 + (16 * Chn) */
+#define VMEM_PTPAH          0x1C6304 /* 0x1C6304 + (16 * Chn) */
+#define VMEM_CTL            0x1C7000
+
+/* Transport Registers */
+#define TRANSPORT_ENB       0x1B6000
+#define TRANSPORT_CTL       0x1B6004
+#define TRANSPORT_INT       0x1B6008
+
+/* Audio IO */
+#define AUDIO_IO_AIM        0x1B5000 /* 0x1B5000 + (0x04 * Chn) */
+#define AUDIO_IO_TX_CTL     0x1B5400 /* 0x1B5400 + (0x40 * Chn) */
+#define AUDIO_IO_TX_CSTAT_L 0x1B5408 /* 0x1B5408 + (0x40 * Chn) */
+#define AUDIO_IO_TX_CSTAT_H 0x1B540C /* 0x1B540C + (0x40 * Chn) */
+#define AUDIO_IO_RX_CTL     0x1B5410 /* 0x1B5410 + (0x40 * Chn) */
+#define AUDIO_IO_RX_SRT_CTL 0x1B5420 /* 0x1B5420 + (0x40 * Chn) */
+#define AUDIO_IO_MCLK       0x1B5600
+#define AUDIO_IO_TX_BLRCLK  0x1B5604
+#define AUDIO_IO_RX_BLRCLK  0x1B5608
+
+/* Mixer */
+#define MIXER_AMOPLO		0x130000 /* 0x130000 + (8 * Chn) [4095 : 0] */
+#define MIXER_AMOPHI		0x130004 /* 0x130004 + (8 * Chn) [4095 : 0] */
+#define MIXER_PRING_LO_HI	0x188000 /* 0x188000 + (4 * Chn) [4095 : 0] */
+#define MIXER_PMOPLO		0x138000 /* 0x138000 + (8 * Chn) [4095 : 0] */
+#define MIXER_PMOPHI		0x138004 /* 0x138004 + (8 * Chn) [4095 : 0] */
+#define MIXER_AR_ENABLE		0x19000C
+
+#endif
diff --git a/linux/sound/pci/ctxfi/ctamixer.c b/linux/sound/pci/ctxfi/ctamixer.c
new file mode 100644
index 0000000..fee35cf
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctamixer.c
@@ -0,0 +1,486 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctamixer.c
+ *
+ * @Brief
+ * This file contains the implementation of the Audio Mixer
+ * resource management object.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 21 2008
+ *
+ */
+
+#include "ctamixer.h"
+#include "cthardware.h"
+#include <linux/slab.h>
+
+#define AMIXER_RESOURCE_NUM	256
+#define SUM_RESOURCE_NUM	256
+
+#define AMIXER_Y_IMMEDIATE	1
+
+#define BLANK_SLOT		4094
+
+static int amixer_master(struct rsc *rsc)
+{
+	rsc->conj = 0;
+	return rsc->idx = container_of(rsc, struct amixer, rsc)->idx[0];
+}
+
+static int amixer_next_conj(struct rsc *rsc)
+{
+	rsc->conj++;
+	return container_of(rsc, struct amixer, rsc)->idx[rsc->conj];
+}
+
+static int amixer_index(const struct rsc *rsc)
+{
+	return container_of(rsc, struct amixer, rsc)->idx[rsc->conj];
+}
+
+static int amixer_output_slot(const struct rsc *rsc)
+{
+	return (amixer_index(rsc) << 4) + 0x4;
+}
+
+static struct rsc_ops amixer_basic_rsc_ops = {
+	.master		= amixer_master,
+	.next_conj	= amixer_next_conj,
+	.index		= amixer_index,
+	.output_slot	= amixer_output_slot,
+};
+
+static int amixer_set_input(struct amixer *amixer, struct rsc *rsc)
+{
+	struct hw *hw;
+
+	hw = amixer->rsc.hw;
+	hw->amixer_set_mode(amixer->rsc.ctrl_blk, AMIXER_Y_IMMEDIATE);
+	amixer->input = rsc;
+	if (!rsc)
+		hw->amixer_set_x(amixer->rsc.ctrl_blk, BLANK_SLOT);
+	else
+		hw->amixer_set_x(amixer->rsc.ctrl_blk,
+					rsc->ops->output_slot(rsc));
+
+	return 0;
+}
+
+/* y is a 14-bit immediate constant */
+static int amixer_set_y(struct amixer *amixer, unsigned int y)
+{
+	struct hw *hw;
+
+	hw = amixer->rsc.hw;
+	hw->amixer_set_y(amixer->rsc.ctrl_blk, y);
+
+	return 0;
+}
+
+static int amixer_set_invalid_squash(struct amixer *amixer, unsigned int iv)
+{
+	struct hw *hw;
+
+	hw = amixer->rsc.hw;
+	hw->amixer_set_iv(amixer->rsc.ctrl_blk, iv);
+
+	return 0;
+}
+
+static int amixer_set_sum(struct amixer *amixer, struct sum *sum)
+{
+	struct hw *hw;
+
+	hw = amixer->rsc.hw;
+	amixer->sum = sum;
+	if (!sum) {
+		hw->amixer_set_se(amixer->rsc.ctrl_blk, 0);
+	} else {
+		hw->amixer_set_se(amixer->rsc.ctrl_blk, 1);
+		hw->amixer_set_sadr(amixer->rsc.ctrl_blk,
+					sum->rsc.ops->index(&sum->rsc));
+	}
+
+	return 0;
+}
+
+static int amixer_commit_write(struct amixer *amixer)
+{
+	struct hw *hw;
+	unsigned int index;
+	int i;
+	struct rsc *input;
+	struct sum *sum;
+
+	hw = amixer->rsc.hw;
+	input = amixer->input;
+	sum = amixer->sum;
+
+	/* Program master and conjugate resources */
+	amixer->rsc.ops->master(&amixer->rsc);
+	if (input)
+		input->ops->master(input);
+
+	if (sum)
+		sum->rsc.ops->master(&sum->rsc);
+
+	for (i = 0; i < amixer->rsc.msr; i++) {
+		hw->amixer_set_dirty_all(amixer->rsc.ctrl_blk);
+		if (input) {
+			hw->amixer_set_x(amixer->rsc.ctrl_blk,
+						input->ops->output_slot(input));
+			input->ops->next_conj(input);
+		}
+		if (sum) {
+			hw->amixer_set_sadr(amixer->rsc.ctrl_blk,
+						sum->rsc.ops->index(&sum->rsc));
+			sum->rsc.ops->next_conj(&sum->rsc);
+		}
+		index = amixer->rsc.ops->output_slot(&amixer->rsc);
+		hw->amixer_commit_write(hw, index, amixer->rsc.ctrl_blk);
+		amixer->rsc.ops->next_conj(&amixer->rsc);
+	}
+	amixer->rsc.ops->master(&amixer->rsc);
+	if (input)
+		input->ops->master(input);
+
+	if (sum)
+		sum->rsc.ops->master(&sum->rsc);
+
+	return 0;
+}
+
+static int amixer_commit_raw_write(struct amixer *amixer)
+{
+	struct hw *hw;
+	unsigned int index;
+
+	hw = amixer->rsc.hw;
+	index = amixer->rsc.ops->output_slot(&amixer->rsc);
+	hw->amixer_commit_write(hw, index, amixer->rsc.ctrl_blk);
+
+	return 0;
+}
+
+static int amixer_get_y(struct amixer *amixer)
+{
+	struct hw *hw;
+
+	hw = amixer->rsc.hw;
+	return hw->amixer_get_y(amixer->rsc.ctrl_blk);
+}
+
+static int amixer_setup(struct amixer *amixer, struct rsc *input,
+			unsigned int scale, struct sum *sum)
+{
+	amixer_set_input(amixer, input);
+	amixer_set_y(amixer, scale);
+	amixer_set_sum(amixer, sum);
+	amixer_commit_write(amixer);
+	return 0;
+}
+
+static struct amixer_rsc_ops amixer_ops = {
+	.set_input		= amixer_set_input,
+	.set_invalid_squash	= amixer_set_invalid_squash,
+	.set_scale		= amixer_set_y,
+	.set_sum		= amixer_set_sum,
+	.commit_write		= amixer_commit_write,
+	.commit_raw_write	= amixer_commit_raw_write,
+	.setup			= amixer_setup,
+	.get_scale		= amixer_get_y,
+};
+
+static int amixer_rsc_init(struct amixer *amixer,
+			   const struct amixer_desc *desc,
+			   struct amixer_mgr *mgr)
+{
+	int err;
+
+	err = rsc_init(&amixer->rsc, amixer->idx[0],
+			AMIXER, desc->msr, mgr->mgr.hw);
+	if (err)
+		return err;
+
+	/* Set amixer specific operations */
+	amixer->rsc.ops = &amixer_basic_rsc_ops;
+	amixer->ops = &amixer_ops;
+	amixer->input = NULL;
+	amixer->sum = NULL;
+
+	amixer_setup(amixer, NULL, 0, NULL);
+
+	return 0;
+}
+
+static int amixer_rsc_uninit(struct amixer *amixer)
+{
+	amixer_setup(amixer, NULL, 0, NULL);
+	rsc_uninit(&amixer->rsc);
+	amixer->ops = NULL;
+	amixer->input = NULL;
+	amixer->sum = NULL;
+	return 0;
+}
+
+static int get_amixer_rsc(struct amixer_mgr *mgr,
+			  const struct amixer_desc *desc,
+			  struct amixer **ramixer)
+{
+	int err, i;
+	unsigned int idx;
+	struct amixer *amixer;
+	unsigned long flags;
+
+	*ramixer = NULL;
+
+	/* Allocate mem for amixer resource */
+	amixer = kzalloc(sizeof(*amixer), GFP_KERNEL);
+	if (!amixer)
+		return -ENOMEM;
+
+	/* Check whether there are sufficient
+	 * amixer resources to meet request. */
+	err = 0;
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	for (i = 0; i < desc->msr; i++) {
+		err = mgr_get_resource(&mgr->mgr, 1, &idx);
+		if (err)
+			break;
+
+		amixer->idx[i] = idx;
+	}
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	if (err) {
+		printk(KERN_ERR "ctxfi: Can't meet AMIXER resource request!\n");
+		goto error;
+	}
+
+	err = amixer_rsc_init(amixer, desc, mgr);
+	if (err)
+		goto error;
+
+	*ramixer = amixer;
+
+	return 0;
+
+error:
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	for (i--; i >= 0; i--)
+		mgr_put_resource(&mgr->mgr, 1, amixer->idx[i]);
+
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	kfree(amixer);
+	return err;
+}
+
+static int put_amixer_rsc(struct amixer_mgr *mgr, struct amixer *amixer)
+{
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	for (i = 0; i < amixer->rsc.msr; i++)
+		mgr_put_resource(&mgr->mgr, 1, amixer->idx[i]);
+
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	amixer_rsc_uninit(amixer);
+	kfree(amixer);
+
+	return 0;
+}
+
+int amixer_mgr_create(void *hw, struct amixer_mgr **ramixer_mgr)
+{
+	int err;
+	struct amixer_mgr *amixer_mgr;
+
+	*ramixer_mgr = NULL;
+	amixer_mgr = kzalloc(sizeof(*amixer_mgr), GFP_KERNEL);
+	if (!amixer_mgr)
+		return -ENOMEM;
+
+	err = rsc_mgr_init(&amixer_mgr->mgr, AMIXER, AMIXER_RESOURCE_NUM, hw);
+	if (err)
+		goto error;
+
+	spin_lock_init(&amixer_mgr->mgr_lock);
+
+	amixer_mgr->get_amixer = get_amixer_rsc;
+	amixer_mgr->put_amixer = put_amixer_rsc;
+
+	*ramixer_mgr = amixer_mgr;
+
+	return 0;
+
+error:
+	kfree(amixer_mgr);
+	return err;
+}
+
+int amixer_mgr_destroy(struct amixer_mgr *amixer_mgr)
+{
+	rsc_mgr_uninit(&amixer_mgr->mgr);
+	kfree(amixer_mgr);
+	return 0;
+}
+
+/* SUM resource management */
+
+static int sum_master(struct rsc *rsc)
+{
+	rsc->conj = 0;
+	return rsc->idx = container_of(rsc, struct sum, rsc)->idx[0];
+}
+
+static int sum_next_conj(struct rsc *rsc)
+{
+	rsc->conj++;
+	return container_of(rsc, struct sum, rsc)->idx[rsc->conj];
+}
+
+static int sum_index(const struct rsc *rsc)
+{
+	return container_of(rsc, struct sum, rsc)->idx[rsc->conj];
+}
+
+static int sum_output_slot(const struct rsc *rsc)
+{
+	return (sum_index(rsc) << 4) + 0xc;
+}
+
+static struct rsc_ops sum_basic_rsc_ops = {
+	.master		= sum_master,
+	.next_conj	= sum_next_conj,
+	.index		= sum_index,
+	.output_slot	= sum_output_slot,
+};
+
+static int sum_rsc_init(struct sum *sum,
+			const struct sum_desc *desc,
+			struct sum_mgr *mgr)
+{
+	int err;
+
+	err = rsc_init(&sum->rsc, sum->idx[0], SUM, desc->msr, mgr->mgr.hw);
+	if (err)
+		return err;
+
+	sum->rsc.ops = &sum_basic_rsc_ops;
+
+	return 0;
+}
+
+static int sum_rsc_uninit(struct sum *sum)
+{
+	rsc_uninit(&sum->rsc);
+	return 0;
+}
+
+static int get_sum_rsc(struct sum_mgr *mgr,
+		       const struct sum_desc *desc,
+		       struct sum **rsum)
+{
+	int err, i;
+	unsigned int idx;
+	struct sum *sum;
+	unsigned long flags;
+
+	*rsum = NULL;
+
+	/* Allocate mem for sum resource */
+	sum = kzalloc(sizeof(*sum), GFP_KERNEL);
+	if (!sum)
+		return -ENOMEM;
+
+	/* Check whether there are sufficient sum resources to meet request. */
+	err = 0;
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	for (i = 0; i < desc->msr; i++) {
+		err = mgr_get_resource(&mgr->mgr, 1, &idx);
+		if (err)
+			break;
+
+		sum->idx[i] = idx;
+	}
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	if (err) {
+		printk(KERN_ERR "ctxfi: Can't meet SUM resource request!\n");
+		goto error;
+	}
+
+	err = sum_rsc_init(sum, desc, mgr);
+	if (err)
+		goto error;
+
+	*rsum = sum;
+
+	return 0;
+
+error:
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	for (i--; i >= 0; i--)
+		mgr_put_resource(&mgr->mgr, 1, sum->idx[i]);
+
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	kfree(sum);
+	return err;
+}
+
+static int put_sum_rsc(struct sum_mgr *mgr, struct sum *sum)
+{
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	for (i = 0; i < sum->rsc.msr; i++)
+		mgr_put_resource(&mgr->mgr, 1, sum->idx[i]);
+
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	sum_rsc_uninit(sum);
+	kfree(sum);
+
+	return 0;
+}
+
+int sum_mgr_create(void *hw, struct sum_mgr **rsum_mgr)
+{
+	int err;
+	struct sum_mgr *sum_mgr;
+
+	*rsum_mgr = NULL;
+	sum_mgr = kzalloc(sizeof(*sum_mgr), GFP_KERNEL);
+	if (!sum_mgr)
+		return -ENOMEM;
+
+	err = rsc_mgr_init(&sum_mgr->mgr, SUM, SUM_RESOURCE_NUM, hw);
+	if (err)
+		goto error;
+
+	spin_lock_init(&sum_mgr->mgr_lock);
+
+	sum_mgr->get_sum = get_sum_rsc;
+	sum_mgr->put_sum = put_sum_rsc;
+
+	*rsum_mgr = sum_mgr;
+
+	return 0;
+
+error:
+	kfree(sum_mgr);
+	return err;
+}
+
+int sum_mgr_destroy(struct sum_mgr *sum_mgr)
+{
+	rsc_mgr_uninit(&sum_mgr->mgr);
+	kfree(sum_mgr);
+	return 0;
+}
+
diff --git a/linux/sound/pci/ctxfi/ctamixer.h b/linux/sound/pci/ctxfi/ctamixer.h
new file mode 100644
index 0000000..cc49e5a
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctamixer.h
@@ -0,0 +1,96 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctamixer.h
+ *
+ * @Brief
+ * This file contains the definition of the Audio Mixer
+ * resource management object.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 21 2008
+ *
+ */
+
+#ifndef CTAMIXER_H
+#define CTAMIXER_H
+
+#include "ctresource.h"
+#include <linux/spinlock.h>
+
+/* Define the descriptor of a summation node resource */
+struct sum {
+	struct rsc rsc;		/* Basic resource info */
+	unsigned char idx[8];
+};
+
+/* Define sum resource request description info */
+struct sum_desc {
+	unsigned int msr;
+};
+
+struct sum_mgr {
+	struct rsc_mgr mgr;	/* Basic resource manager info */
+	spinlock_t mgr_lock;
+
+	 /* request one sum resource */
+	int (*get_sum)(struct sum_mgr *mgr,
+			const struct sum_desc *desc, struct sum **rsum);
+	/* return one sum resource */
+	int (*put_sum)(struct sum_mgr *mgr, struct sum *sum);
+};
+
+/* Constructor and destructor of daio resource manager */
+int sum_mgr_create(void *hw, struct sum_mgr **rsum_mgr);
+int sum_mgr_destroy(struct sum_mgr *sum_mgr);
+
+/* Define the descriptor of a amixer resource */
+struct amixer_rsc_ops;
+
+struct amixer {
+	struct rsc rsc;		/* Basic resource info */
+	unsigned char idx[8];
+	struct rsc *input;	/* pointer to a resource acting as source */
+	struct sum *sum;	/* Put amixer output to this summation node */
+	struct amixer_rsc_ops *ops;	/* AMixer specific operations */
+};
+
+struct amixer_rsc_ops {
+	int (*set_input)(struct amixer *amixer, struct rsc *rsc);
+	int (*set_scale)(struct amixer *amixer, unsigned int scale);
+	int (*set_invalid_squash)(struct amixer *amixer, unsigned int iv);
+	int (*set_sum)(struct amixer *amixer, struct sum *sum);
+	int (*commit_write)(struct amixer *amixer);
+	/* Only for interleaved recording */
+	int (*commit_raw_write)(struct amixer *amixer);
+	int (*setup)(struct amixer *amixer, struct rsc *input,
+			unsigned int scale, struct sum *sum);
+	int (*get_scale)(struct amixer *amixer);
+};
+
+/* Define amixer resource request description info */
+struct amixer_desc {
+	unsigned int msr;
+};
+
+struct amixer_mgr {
+	struct rsc_mgr mgr;	/* Basic resource manager info */
+	spinlock_t mgr_lock;
+
+	 /* request one amixer resource */
+	int (*get_amixer)(struct amixer_mgr *mgr,
+			  const struct amixer_desc *desc,
+			  struct amixer **ramixer);
+	/* return one amixer resource */
+	int (*put_amixer)(struct amixer_mgr *mgr, struct amixer *amixer);
+};
+
+/* Constructor and destructor of amixer resource manager */
+int amixer_mgr_create(void *hw, struct amixer_mgr **ramixer_mgr);
+int amixer_mgr_destroy(struct amixer_mgr *amixer_mgr);
+
+#endif /* CTAMIXER_H */
diff --git a/linux/sound/pci/ctxfi/ctatc.c b/linux/sound/pci/ctxfi/ctatc.c
new file mode 100644
index 0000000..1bff80c
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctatc.c
@@ -0,0 +1,1709 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File    ctatc.c
+ *
+ * @Brief
+ * This file contains the implementation of the device resource management
+ * object.
+ *
+ * @Author Liu Chun
+ * @Date Mar 28 2008
+ */
+
+#include "ctatc.h"
+#include "ctpcm.h"
+#include "ctmixer.h"
+#include "cthardware.h"
+#include "ctsrc.h"
+#include "ctamixer.h"
+#include "ctdaio.h"
+#include "cttimer.h"
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/asoundef.h>
+
+#define MONO_SUM_SCALE	0x19a8	/* 2^(-0.5) in 14-bit floating format */
+#define DAIONUM		7
+#define MAX_MULTI_CHN	8
+
+#define IEC958_DEFAULT_CON ((IEC958_AES0_NONAUDIO \
+			    | IEC958_AES0_CON_NOT_COPYRIGHT) \
+			    | ((IEC958_AES1_CON_MIXER \
+			    | IEC958_AES1_CON_ORIGINAL) << 8) \
+			    | (0x10 << 16) \
+			    | ((IEC958_AES3_CON_FS_48000) << 24))
+
+static struct snd_pci_quirk __devinitdata subsys_20k1_list[] = {
+	SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x0022, "SB055x", CTSB055X),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x002f, "SB055x", CTSB055X),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x0029, "SB073x", CTSB073X),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, 0x0031, "SB073x", CTSB073X),
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_CREATIVE, 0xf000, 0x6000,
+			   "UAA", CTUAA),
+	{ } /* terminator */
+};
+
+static struct snd_pci_quirk __devinitdata subsys_20k2_list[] = {
+	SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB0760,
+		      "SB0760", CTSB0760),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB08801,
+		      "SB0880", CTSB0880),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB08802,
+		      "SB0880", CTSB0880),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_CREATIVE, PCI_SUBDEVICE_ID_CREATIVE_SB08803,
+		      "SB0880", CTSB0880),
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_CREATIVE, 0xf000,
+			   PCI_SUBDEVICE_ID_CREATIVE_HENDRIX, "HENDRIX",
+			   CTHENDRIX),
+	{ } /* terminator */
+};
+
+static const char *ct_subsys_name[NUM_CTCARDS] = {
+	/* 20k1 models */
+	[CTSB055X]	= "SB055x",
+	[CTSB073X]	= "SB073x",
+	[CTUAA]		= "UAA",
+	[CT20K1_UNKNOWN] = "Unknown",
+	/* 20k2 models */
+	[CTSB0760]	= "SB076x",
+	[CTHENDRIX]	= "Hendrix",
+	[CTSB0880]	= "SB0880",
+	[CT20K2_UNKNOWN] = "Unknown",
+};
+
+static struct {
+	int (*create)(struct ct_atc *atc,
+			enum CTALSADEVS device, const char *device_name);
+	int (*destroy)(void *alsa_dev);
+	const char *public_name;
+} alsa_dev_funcs[NUM_CTALSADEVS] = {
+	[FRONT]		= { .create = ct_alsa_pcm_create,
+			    .destroy = NULL,
+			    .public_name = "Front/WaveIn"},
+	[SURROUND]	= { .create = ct_alsa_pcm_create,
+			    .destroy = NULL,
+			    .public_name = "Surround"},
+	[CLFE]		= { .create = ct_alsa_pcm_create,
+			    .destroy = NULL,
+			    .public_name = "Center/LFE"},
+	[SIDE]		= { .create = ct_alsa_pcm_create,
+			    .destroy = NULL,
+			    .public_name = "Side"},
+	[IEC958]	= { .create = ct_alsa_pcm_create,
+			    .destroy = NULL,
+			    .public_name = "IEC958 Non-audio"},
+
+	[MIXER]		= { .create = ct_alsa_mix_create,
+			    .destroy = NULL,
+			    .public_name = "Mixer"}
+};
+
+typedef int (*create_t)(void *, void **);
+typedef int (*destroy_t)(void *);
+
+static struct {
+	int (*create)(void *hw, void **rmgr);
+	int (*destroy)(void *mgr);
+} rsc_mgr_funcs[NUM_RSCTYP] = {
+	[SRC] 		= { .create 	= (create_t)src_mgr_create,
+			    .destroy 	= (destroy_t)src_mgr_destroy	},
+	[SRCIMP] 	= { .create 	= (create_t)srcimp_mgr_create,
+			    .destroy 	= (destroy_t)srcimp_mgr_destroy	},
+	[AMIXER]	= { .create	= (create_t)amixer_mgr_create,
+			    .destroy	= (destroy_t)amixer_mgr_destroy	},
+	[SUM]		= { .create	= (create_t)sum_mgr_create,
+			    .destroy	= (destroy_t)sum_mgr_destroy	},
+	[DAIO]		= { .create	= (create_t)daio_mgr_create,
+			    .destroy	= (destroy_t)daio_mgr_destroy	}
+};
+
+static int
+atc_pcm_release_resources(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+
+/* *
+ * Only mono and interleaved modes are supported now.
+ * Always allocates a contiguous channel block.
+ * */
+
+static int ct_map_audio_buffer(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct snd_pcm_runtime *runtime;
+	struct ct_vm *vm;
+
+	if (!apcm->substream)
+		return 0;
+
+	runtime = apcm->substream->runtime;
+	vm = atc->vm;
+
+	apcm->vm_block = vm->map(vm, apcm->substream, runtime->dma_bytes);
+
+	if (!apcm->vm_block)
+		return -ENOENT;
+
+	return 0;
+}
+
+static void ct_unmap_audio_buffer(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct ct_vm *vm;
+
+	if (!apcm->vm_block)
+		return;
+
+	vm = atc->vm;
+
+	vm->unmap(vm, apcm->vm_block);
+
+	apcm->vm_block = NULL;
+}
+
+static unsigned long atc_get_ptp_phys(struct ct_atc *atc, int index)
+{
+	return atc->vm->get_ptp_phys(atc->vm, index);
+}
+
+static unsigned int convert_format(snd_pcm_format_t snd_format)
+{
+	switch (snd_format) {
+	case SNDRV_PCM_FORMAT_U8:
+		return SRC_SF_U8;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		return SRC_SF_S16;
+	case SNDRV_PCM_FORMAT_S24_3LE:
+		return SRC_SF_S24;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		return SRC_SF_S32;
+	case SNDRV_PCM_FORMAT_FLOAT_LE:
+		return SRC_SF_F32;
+	default:
+		printk(KERN_ERR "ctxfi: not recognized snd format is %d \n",
+			snd_format);
+		return SRC_SF_S16;
+	}
+}
+
+static unsigned int
+atc_get_pitch(unsigned int input_rate, unsigned int output_rate)
+{
+	unsigned int pitch;
+	int b;
+
+	/* get pitch and convert to fixed-point 8.24 format. */
+	pitch = (input_rate / output_rate) << 24;
+	input_rate %= output_rate;
+	input_rate /= 100;
+	output_rate /= 100;
+	for (b = 31; ((b >= 0) && !(input_rate >> b)); )
+		b--;
+
+	if (b >= 0) {
+		input_rate <<= (31 - b);
+		input_rate /= output_rate;
+		b = 24 - (31 - b);
+		if (b >= 0)
+			input_rate <<= b;
+		else
+			input_rate >>= -b;
+
+		pitch |= input_rate;
+	}
+
+	return pitch;
+}
+
+static int select_rom(unsigned int pitch)
+{
+	if (pitch > 0x00428f5c && pitch < 0x01b851ec) {
+		/* 0.26 <= pitch <= 1.72 */
+		return 1;
+	} else if (pitch == 0x01d66666 || pitch == 0x01d66667) {
+		/* pitch == 1.8375 */
+		return 2;
+	} else if (pitch == 0x02000000) {
+		/* pitch == 2 */
+		return 3;
+	} else if (pitch <= 0x08000000) {
+		/* 0 <= pitch <= 8 */
+		return 0;
+	} else {
+		return -ENOENT;
+	}
+}
+
+static int atc_pcm_playback_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct src_mgr *src_mgr = atc->rsc_mgrs[SRC];
+	struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER];
+	struct src_desc desc = {0};
+	struct amixer_desc mix_dsc = {0};
+	struct src *src;
+	struct amixer *amixer;
+	int err;
+	int n_amixer = apcm->substream->runtime->channels, i = 0;
+	int device = apcm->substream->pcm->device;
+	unsigned int pitch;
+
+	/* first release old resources */
+	atc_pcm_release_resources(atc, apcm);
+
+	/* Get SRC resource */
+	desc.multi = apcm->substream->runtime->channels;
+	desc.msr = atc->msr;
+	desc.mode = MEMRD;
+	err = src_mgr->get_src(src_mgr, &desc, (struct src **)&apcm->src);
+	if (err)
+		goto error1;
+
+	pitch = atc_get_pitch(apcm->substream->runtime->rate,
+						(atc->rsr * atc->msr));
+	src = apcm->src;
+	src->ops->set_pitch(src, pitch);
+	src->ops->set_rom(src, select_rom(pitch));
+	src->ops->set_sf(src, convert_format(apcm->substream->runtime->format));
+	src->ops->set_pm(src, (src->ops->next_interleave(src) != NULL));
+
+	/* Get AMIXER resource */
+	n_amixer = (n_amixer < 2) ? 2 : n_amixer;
+	apcm->amixers = kzalloc(sizeof(void *)*n_amixer, GFP_KERNEL);
+	if (!apcm->amixers) {
+		err = -ENOMEM;
+		goto error1;
+	}
+	mix_dsc.msr = atc->msr;
+	for (i = 0, apcm->n_amixer = 0; i < n_amixer; i++) {
+		err = amixer_mgr->get_amixer(amixer_mgr, &mix_dsc,
+					(struct amixer **)&apcm->amixers[i]);
+		if (err)
+			goto error1;
+
+		apcm->n_amixer++;
+	}
+
+	/* Set up device virtual mem map */
+	err = ct_map_audio_buffer(atc, apcm);
+	if (err < 0)
+		goto error1;
+
+	/* Connect resources */
+	src = apcm->src;
+	for (i = 0; i < n_amixer; i++) {
+		amixer = apcm->amixers[i];
+		mutex_lock(&atc->atc_mutex);
+		amixer->ops->setup(amixer, &src->rsc,
+					INIT_VOL, atc->pcm[i+device*2]);
+		mutex_unlock(&atc->atc_mutex);
+		src = src->ops->next_interleave(src);
+		if (!src)
+			src = apcm->src;
+	}
+
+	ct_timer_prepare(apcm->timer);
+
+	return 0;
+
+error1:
+	atc_pcm_release_resources(atc, apcm);
+	return err;
+}
+
+static int
+atc_pcm_release_resources(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct src_mgr *src_mgr = atc->rsc_mgrs[SRC];
+	struct srcimp_mgr *srcimp_mgr = atc->rsc_mgrs[SRCIMP];
+	struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER];
+	struct sum_mgr *sum_mgr = atc->rsc_mgrs[SUM];
+	struct srcimp *srcimp;
+	int i;
+
+	if (apcm->srcimps) {
+		for (i = 0; i < apcm->n_srcimp; i++) {
+			srcimp = apcm->srcimps[i];
+			srcimp->ops->unmap(srcimp);
+			srcimp_mgr->put_srcimp(srcimp_mgr, srcimp);
+			apcm->srcimps[i] = NULL;
+		}
+		kfree(apcm->srcimps);
+		apcm->srcimps = NULL;
+	}
+
+	if (apcm->srccs) {
+		for (i = 0; i < apcm->n_srcc; i++) {
+			src_mgr->put_src(src_mgr, apcm->srccs[i]);
+			apcm->srccs[i] = NULL;
+		}
+		kfree(apcm->srccs);
+		apcm->srccs = NULL;
+	}
+
+	if (apcm->amixers) {
+		for (i = 0; i < apcm->n_amixer; i++) {
+			amixer_mgr->put_amixer(amixer_mgr, apcm->amixers[i]);
+			apcm->amixers[i] = NULL;
+		}
+		kfree(apcm->amixers);
+		apcm->amixers = NULL;
+	}
+
+	if (apcm->mono) {
+		sum_mgr->put_sum(sum_mgr, apcm->mono);
+		apcm->mono = NULL;
+	}
+
+	if (apcm->src) {
+		src_mgr->put_src(src_mgr, apcm->src);
+		apcm->src = NULL;
+	}
+
+	if (apcm->vm_block) {
+		/* Undo device virtual mem map */
+		ct_unmap_audio_buffer(atc, apcm);
+		apcm->vm_block = NULL;
+	}
+
+	return 0;
+}
+
+static int atc_pcm_playback_start(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	unsigned int max_cisz;
+	struct src *src = apcm->src;
+
+	if (apcm->started)
+		return 0;
+	apcm->started = 1;
+
+	max_cisz = src->multi * src->rsc.msr;
+	max_cisz = 0x80 * (max_cisz < 8 ? max_cisz : 8);
+
+	src->ops->set_sa(src, apcm->vm_block->addr);
+	src->ops->set_la(src, apcm->vm_block->addr + apcm->vm_block->size);
+	src->ops->set_ca(src, apcm->vm_block->addr + max_cisz);
+	src->ops->set_cisz(src, max_cisz);
+
+	src->ops->set_bm(src, 1);
+	src->ops->set_state(src, SRC_STATE_INIT);
+	src->ops->commit_write(src);
+
+	ct_timer_start(apcm->timer);
+	return 0;
+}
+
+static int atc_pcm_stop(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct src *src;
+	int i;
+
+	ct_timer_stop(apcm->timer);
+
+	src = apcm->src;
+	src->ops->set_bm(src, 0);
+	src->ops->set_state(src, SRC_STATE_OFF);
+	src->ops->commit_write(src);
+
+	if (apcm->srccs) {
+		for (i = 0; i < apcm->n_srcc; i++) {
+			src = apcm->srccs[i];
+			src->ops->set_bm(src, 0);
+			src->ops->set_state(src, SRC_STATE_OFF);
+			src->ops->commit_write(src);
+		}
+	}
+
+	apcm->started = 0;
+
+	return 0;
+}
+
+static int
+atc_pcm_playback_position(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct src *src = apcm->src;
+	u32 size, max_cisz;
+	int position;
+
+	if (!src)
+		return 0;
+	position = src->ops->get_ca(src);
+
+	size = apcm->vm_block->size;
+	max_cisz = src->multi * src->rsc.msr;
+	max_cisz = 128 * (max_cisz < 8 ? max_cisz : 8);
+
+	return (position + size - max_cisz - apcm->vm_block->addr) % size;
+}
+
+struct src_node_conf_t {
+	unsigned int pitch;
+	unsigned int msr:8;
+	unsigned int mix_msr:8;
+	unsigned int imp_msr:8;
+	unsigned int vo:1;
+};
+
+static void setup_src_node_conf(struct ct_atc *atc, struct ct_atc_pcm *apcm,
+				struct src_node_conf_t *conf, int *n_srcc)
+{
+	unsigned int pitch;
+
+	/* get pitch and convert to fixed-point 8.24 format. */
+	pitch = atc_get_pitch((atc->rsr * atc->msr),
+				apcm->substream->runtime->rate);
+	*n_srcc = 0;
+
+	if (1 == atc->msr) {
+		*n_srcc = apcm->substream->runtime->channels;
+		conf[0].pitch = pitch;
+		conf[0].mix_msr = conf[0].imp_msr = conf[0].msr = 1;
+		conf[0].vo = 1;
+	} else if (2 == atc->msr) {
+		if (0x8000000 < pitch) {
+			/* Need two-stage SRCs, SRCIMPs and
+			 * AMIXERs for converting format */
+			conf[0].pitch = (atc->msr << 24);
+			conf[0].msr = conf[0].mix_msr = 1;
+			conf[0].imp_msr = atc->msr;
+			conf[0].vo = 0;
+			conf[1].pitch = atc_get_pitch(atc->rsr,
+					apcm->substream->runtime->rate);
+			conf[1].msr = conf[1].mix_msr = conf[1].imp_msr = 1;
+			conf[1].vo = 1;
+			*n_srcc = apcm->substream->runtime->channels * 2;
+		} else if (0x1000000 < pitch) {
+			/* Need one-stage SRCs, SRCIMPs and
+			 * AMIXERs for converting format */
+			conf[0].pitch = pitch;
+			conf[0].msr = conf[0].mix_msr
+				    = conf[0].imp_msr = atc->msr;
+			conf[0].vo = 1;
+			*n_srcc = apcm->substream->runtime->channels;
+		}
+	}
+}
+
+static int
+atc_pcm_capture_get_resources(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct src_mgr *src_mgr = atc->rsc_mgrs[SRC];
+	struct srcimp_mgr *srcimp_mgr = atc->rsc_mgrs[SRCIMP];
+	struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER];
+	struct sum_mgr *sum_mgr = atc->rsc_mgrs[SUM];
+	struct src_desc src_dsc = {0};
+	struct src *src;
+	struct srcimp_desc srcimp_dsc = {0};
+	struct srcimp *srcimp;
+	struct amixer_desc mix_dsc = {0};
+	struct sum_desc sum_dsc = {0};
+	unsigned int pitch;
+	int multi, err, i;
+	int n_srcimp, n_amixer, n_srcc, n_sum;
+	struct src_node_conf_t src_node_conf[2] = {{0} };
+
+	/* first release old resources */
+	atc_pcm_release_resources(atc, apcm);
+
+	/* The numbers of converting SRCs and SRCIMPs should be determined
+	 * by pitch value. */
+
+	multi = apcm->substream->runtime->channels;
+
+	/* get pitch and convert to fixed-point 8.24 format. */
+	pitch = atc_get_pitch((atc->rsr * atc->msr),
+				apcm->substream->runtime->rate);
+
+	setup_src_node_conf(atc, apcm, src_node_conf, &n_srcc);
+	n_sum = (1 == multi) ? 1 : 0;
+	n_amixer = n_sum * 2 + n_srcc;
+	n_srcimp = n_srcc;
+	if ((multi > 1) && (0x8000000 >= pitch)) {
+		/* Need extra AMIXERs and SRCIMPs for special treatment
+		 * of interleaved recording of conjugate channels */
+		n_amixer += multi * atc->msr;
+		n_srcimp += multi * atc->msr;
+	} else {
+		n_srcimp += multi;
+	}
+
+	if (n_srcc) {
+		apcm->srccs = kzalloc(sizeof(void *)*n_srcc, GFP_KERNEL);
+		if (!apcm->srccs)
+			return -ENOMEM;
+	}
+	if (n_amixer) {
+		apcm->amixers = kzalloc(sizeof(void *)*n_amixer, GFP_KERNEL);
+		if (!apcm->amixers) {
+			err = -ENOMEM;
+			goto error1;
+		}
+	}
+	apcm->srcimps = kzalloc(sizeof(void *)*n_srcimp, GFP_KERNEL);
+	if (!apcm->srcimps) {
+		err = -ENOMEM;
+		goto error1;
+	}
+
+	/* Allocate SRCs for sample rate conversion if needed */
+	src_dsc.multi = 1;
+	src_dsc.mode = ARCRW;
+	for (i = 0, apcm->n_srcc = 0; i < n_srcc; i++) {
+		src_dsc.msr = src_node_conf[i/multi].msr;
+		err = src_mgr->get_src(src_mgr, &src_dsc,
+					(struct src **)&apcm->srccs[i]);
+		if (err)
+			goto error1;
+
+		src = apcm->srccs[i];
+		pitch = src_node_conf[i/multi].pitch;
+		src->ops->set_pitch(src, pitch);
+		src->ops->set_rom(src, select_rom(pitch));
+		src->ops->set_vo(src, src_node_conf[i/multi].vo);
+
+		apcm->n_srcc++;
+	}
+
+	/* Allocate AMIXERs for routing SRCs of conversion if needed */
+	for (i = 0, apcm->n_amixer = 0; i < n_amixer; i++) {
+		if (i < (n_sum*2))
+			mix_dsc.msr = atc->msr;
+		else if (i < (n_sum*2+n_srcc))
+			mix_dsc.msr = src_node_conf[(i-n_sum*2)/multi].mix_msr;
+		else
+			mix_dsc.msr = 1;
+
+		err = amixer_mgr->get_amixer(amixer_mgr, &mix_dsc,
+					(struct amixer **)&apcm->amixers[i]);
+		if (err)
+			goto error1;
+
+		apcm->n_amixer++;
+	}
+
+	/* Allocate a SUM resource to mix all input channels together */
+	sum_dsc.msr = atc->msr;
+	err = sum_mgr->get_sum(sum_mgr, &sum_dsc, (struct sum **)&apcm->mono);
+	if (err)
+		goto error1;
+
+	pitch = atc_get_pitch((atc->rsr * atc->msr),
+				apcm->substream->runtime->rate);
+	/* Allocate SRCIMP resources */
+	for (i = 0, apcm->n_srcimp = 0; i < n_srcimp; i++) {
+		if (i < (n_srcc))
+			srcimp_dsc.msr = src_node_conf[i/multi].imp_msr;
+		else if (1 == multi)
+			srcimp_dsc.msr = (pitch <= 0x8000000) ? atc->msr : 1;
+		else
+			srcimp_dsc.msr = 1;
+
+		err = srcimp_mgr->get_srcimp(srcimp_mgr, &srcimp_dsc, &srcimp);
+		if (err)
+			goto error1;
+
+		apcm->srcimps[i] = srcimp;
+		apcm->n_srcimp++;
+	}
+
+	/* Allocate a SRC for writing data to host memory */
+	src_dsc.multi = apcm->substream->runtime->channels;
+	src_dsc.msr = 1;
+	src_dsc.mode = MEMWR;
+	err = src_mgr->get_src(src_mgr, &src_dsc, (struct src **)&apcm->src);
+	if (err)
+		goto error1;
+
+	src = apcm->src;
+	src->ops->set_pitch(src, pitch);
+
+	/* Set up device virtual mem map */
+	err = ct_map_audio_buffer(atc, apcm);
+	if (err < 0)
+		goto error1;
+
+	return 0;
+
+error1:
+	atc_pcm_release_resources(atc, apcm);
+	return err;
+}
+
+static int atc_pcm_capture_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct src *src;
+	struct amixer *amixer;
+	struct srcimp *srcimp;
+	struct ct_mixer *mixer = atc->mixer;
+	struct sum *mono;
+	struct rsc *out_ports[8] = {NULL};
+	int err, i, j, n_sum, multi;
+	unsigned int pitch;
+	int mix_base = 0, imp_base = 0;
+
+	atc_pcm_release_resources(atc, apcm);
+
+	/* Get needed resources. */
+	err = atc_pcm_capture_get_resources(atc, apcm);
+	if (err)
+		return err;
+
+	/* Connect resources */
+	mixer->get_output_ports(mixer, MIX_PCMO_FRONT,
+				&out_ports[0], &out_ports[1]);
+
+	multi = apcm->substream->runtime->channels;
+	if (1 == multi) {
+		mono = apcm->mono;
+		for (i = 0; i < 2; i++) {
+			amixer = apcm->amixers[i];
+			amixer->ops->setup(amixer, out_ports[i],
+						MONO_SUM_SCALE, mono);
+		}
+		out_ports[0] = &mono->rsc;
+		n_sum = 1;
+		mix_base = n_sum * 2;
+	}
+
+	for (i = 0; i < apcm->n_srcc; i++) {
+		src = apcm->srccs[i];
+		srcimp = apcm->srcimps[imp_base+i];
+		amixer = apcm->amixers[mix_base+i];
+		srcimp->ops->map(srcimp, src, out_ports[i%multi]);
+		amixer->ops->setup(amixer, &src->rsc, INIT_VOL, NULL);
+		out_ports[i%multi] = &amixer->rsc;
+	}
+
+	pitch = atc_get_pitch((atc->rsr * atc->msr),
+				apcm->substream->runtime->rate);
+
+	if ((multi > 1) && (pitch <= 0x8000000)) {
+		/* Special connection for interleaved
+		 * recording with conjugate channels */
+		for (i = 0; i < multi; i++) {
+			out_ports[i]->ops->master(out_ports[i]);
+			for (j = 0; j < atc->msr; j++) {
+				amixer = apcm->amixers[apcm->n_srcc+j*multi+i];
+				amixer->ops->set_input(amixer, out_ports[i]);
+				amixer->ops->set_scale(amixer, INIT_VOL);
+				amixer->ops->set_sum(amixer, NULL);
+				amixer->ops->commit_raw_write(amixer);
+				out_ports[i]->ops->next_conj(out_ports[i]);
+
+				srcimp = apcm->srcimps[apcm->n_srcc+j*multi+i];
+				srcimp->ops->map(srcimp, apcm->src,
+							&amixer->rsc);
+			}
+		}
+	} else {
+		for (i = 0; i < multi; i++) {
+			srcimp = apcm->srcimps[apcm->n_srcc+i];
+			srcimp->ops->map(srcimp, apcm->src, out_ports[i]);
+		}
+	}
+
+	ct_timer_prepare(apcm->timer);
+
+	return 0;
+}
+
+static int atc_pcm_capture_start(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct src *src;
+	struct src_mgr *src_mgr = atc->rsc_mgrs[SRC];
+	int i, multi;
+
+	if (apcm->started)
+		return 0;
+
+	apcm->started = 1;
+	multi = apcm->substream->runtime->channels;
+	/* Set up converting SRCs */
+	for (i = 0; i < apcm->n_srcc; i++) {
+		src = apcm->srccs[i];
+		src->ops->set_pm(src, ((i%multi) != (multi-1)));
+		src_mgr->src_disable(src_mgr, src);
+	}
+
+	/*  Set up recording SRC */
+	src = apcm->src;
+	src->ops->set_sf(src, convert_format(apcm->substream->runtime->format));
+	src->ops->set_sa(src, apcm->vm_block->addr);
+	src->ops->set_la(src, apcm->vm_block->addr + apcm->vm_block->size);
+	src->ops->set_ca(src, apcm->vm_block->addr);
+	src_mgr->src_disable(src_mgr, src);
+
+	/* Disable relevant SRCs firstly */
+	src_mgr->commit_write(src_mgr);
+
+	/* Enable SRCs respectively */
+	for (i = 0; i < apcm->n_srcc; i++) {
+		src = apcm->srccs[i];
+		src->ops->set_state(src, SRC_STATE_RUN);
+		src->ops->commit_write(src);
+		src_mgr->src_enable_s(src_mgr, src);
+	}
+	src = apcm->src;
+	src->ops->set_bm(src, 1);
+	src->ops->set_state(src, SRC_STATE_RUN);
+	src->ops->commit_write(src);
+	src_mgr->src_enable_s(src_mgr, src);
+
+	/* Enable relevant SRCs synchronously */
+	src_mgr->commit_write(src_mgr);
+
+	ct_timer_start(apcm->timer);
+	return 0;
+}
+
+static int
+atc_pcm_capture_position(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct src *src = apcm->src;
+
+	if (!src)
+		return 0;
+	return src->ops->get_ca(src) - apcm->vm_block->addr;
+}
+
+static int spdif_passthru_playback_get_resources(struct ct_atc *atc,
+						 struct ct_atc_pcm *apcm)
+{
+	struct src_mgr *src_mgr = atc->rsc_mgrs[SRC];
+	struct amixer_mgr *amixer_mgr = atc->rsc_mgrs[AMIXER];
+	struct src_desc desc = {0};
+	struct amixer_desc mix_dsc = {0};
+	struct src *src;
+	int err;
+	int n_amixer = apcm->substream->runtime->channels, i;
+	unsigned int pitch, rsr = atc->pll_rate;
+
+	/* first release old resources */
+	atc_pcm_release_resources(atc, apcm);
+
+	/* Get SRC resource */
+	desc.multi = apcm->substream->runtime->channels;
+	desc.msr = 1;
+	while (apcm->substream->runtime->rate > (rsr * desc.msr))
+		desc.msr <<= 1;
+
+	desc.mode = MEMRD;
+	err = src_mgr->get_src(src_mgr, &desc, (struct src **)&apcm->src);
+	if (err)
+		goto error1;
+
+	pitch = atc_get_pitch(apcm->substream->runtime->rate, (rsr * desc.msr));
+	src = apcm->src;
+	src->ops->set_pitch(src, pitch);
+	src->ops->set_rom(src, select_rom(pitch));
+	src->ops->set_sf(src, convert_format(apcm->substream->runtime->format));
+	src->ops->set_pm(src, (src->ops->next_interleave(src) != NULL));
+	src->ops->set_bp(src, 1);
+
+	/* Get AMIXER resource */
+	n_amixer = (n_amixer < 2) ? 2 : n_amixer;
+	apcm->amixers = kzalloc(sizeof(void *)*n_amixer, GFP_KERNEL);
+	if (!apcm->amixers) {
+		err = -ENOMEM;
+		goto error1;
+	}
+	mix_dsc.msr = desc.msr;
+	for (i = 0, apcm->n_amixer = 0; i < n_amixer; i++) {
+		err = amixer_mgr->get_amixer(amixer_mgr, &mix_dsc,
+					(struct amixer **)&apcm->amixers[i]);
+		if (err)
+			goto error1;
+
+		apcm->n_amixer++;
+	}
+
+	/* Set up device virtual mem map */
+	err = ct_map_audio_buffer(atc, apcm);
+	if (err < 0)
+		goto error1;
+
+	return 0;
+
+error1:
+	atc_pcm_release_resources(atc, apcm);
+	return err;
+}
+
+static int atc_pll_init(struct ct_atc *atc, int rate)
+{
+	struct hw *hw = atc->hw;
+	int err;
+	err = hw->pll_init(hw, rate);
+	atc->pll_rate = err ? 0 : rate;
+	return err;
+}
+
+static int
+spdif_passthru_playback_setup(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct dao *dao = container_of(atc->daios[SPDIFOO], struct dao, daio);
+	unsigned int rate = apcm->substream->runtime->rate;
+	unsigned int status;
+	int err = 0;
+	unsigned char iec958_con_fs;
+
+	switch (rate) {
+	case 48000:
+		iec958_con_fs = IEC958_AES3_CON_FS_48000;
+		break;
+	case 44100:
+		iec958_con_fs = IEC958_AES3_CON_FS_44100;
+		break;
+	case 32000:
+		iec958_con_fs = IEC958_AES3_CON_FS_32000;
+		break;
+	default:
+		return -ENOENT;
+	}
+
+	mutex_lock(&atc->atc_mutex);
+	dao->ops->get_spos(dao, &status);
+	if (((status >> 24) & IEC958_AES3_CON_FS) != iec958_con_fs) {
+		status &= ((~IEC958_AES3_CON_FS) << 24);
+		status |= (iec958_con_fs << 24);
+		dao->ops->set_spos(dao, status);
+		dao->ops->commit_write(dao);
+	}
+	if ((rate != atc->pll_rate) && (32000 != rate))
+		err = atc_pll_init(atc, rate);
+	mutex_unlock(&atc->atc_mutex);
+
+	return err;
+}
+
+static int
+spdif_passthru_playback_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm)
+{
+	struct src *src;
+	struct amixer *amixer;
+	struct dao *dao;
+	int err;
+	int i;
+
+	atc_pcm_release_resources(atc, apcm);
+
+	/* Configure SPDIFOO and PLL to passthrough mode;
+	 * determine pll_rate. */
+	err = spdif_passthru_playback_setup(atc, apcm);
+	if (err)
+		return err;
+
+	/* Get needed resources. */
+	err = spdif_passthru_playback_get_resources(atc, apcm);
+	if (err)
+		return err;
+
+	/* Connect resources */
+	src = apcm->src;
+	for (i = 0; i < apcm->n_amixer; i++) {
+		amixer = apcm->amixers[i];
+		amixer->ops->setup(amixer, &src->rsc, INIT_VOL, NULL);
+		src = src->ops->next_interleave(src);
+		if (!src)
+			src = apcm->src;
+	}
+	/* Connect to SPDIFOO */
+	mutex_lock(&atc->atc_mutex);
+	dao = container_of(atc->daios[SPDIFOO], struct dao, daio);
+	amixer = apcm->amixers[0];
+	dao->ops->set_left_input(dao, &amixer->rsc);
+	amixer = apcm->amixers[1];
+	dao->ops->set_right_input(dao, &amixer->rsc);
+	mutex_unlock(&atc->atc_mutex);
+
+	ct_timer_prepare(apcm->timer);
+
+	return 0;
+}
+
+static int atc_select_line_in(struct ct_atc *atc)
+{
+	struct hw *hw = atc->hw;
+	struct ct_mixer *mixer = atc->mixer;
+	struct src *src;
+
+	if (hw->is_adc_source_selected(hw, ADC_LINEIN))
+		return 0;
+
+	mixer->set_input_left(mixer, MIX_MIC_IN, NULL);
+	mixer->set_input_right(mixer, MIX_MIC_IN, NULL);
+
+	hw->select_adc_source(hw, ADC_LINEIN);
+
+	src = atc->srcs[2];
+	mixer->set_input_left(mixer, MIX_LINE_IN, &src->rsc);
+	src = atc->srcs[3];
+	mixer->set_input_right(mixer, MIX_LINE_IN, &src->rsc);
+
+	return 0;
+}
+
+static int atc_select_mic_in(struct ct_atc *atc)
+{
+	struct hw *hw = atc->hw;
+	struct ct_mixer *mixer = atc->mixer;
+	struct src *src;
+
+	if (hw->is_adc_source_selected(hw, ADC_MICIN))
+		return 0;
+
+	mixer->set_input_left(mixer, MIX_LINE_IN, NULL);
+	mixer->set_input_right(mixer, MIX_LINE_IN, NULL);
+
+	hw->select_adc_source(hw, ADC_MICIN);
+
+	src = atc->srcs[2];
+	mixer->set_input_left(mixer, MIX_MIC_IN, &src->rsc);
+	src = atc->srcs[3];
+	mixer->set_input_right(mixer, MIX_MIC_IN, &src->rsc);
+
+	return 0;
+}
+
+static int atc_have_digit_io_switch(struct ct_atc *atc)
+{
+	struct hw *hw = atc->hw;
+
+	return hw->have_digit_io_switch(hw);
+}
+
+static int atc_select_digit_io(struct ct_atc *atc)
+{
+	struct hw *hw = atc->hw;
+
+	if (hw->is_adc_source_selected(hw, ADC_NONE))
+		return 0;
+
+	hw->select_adc_source(hw, ADC_NONE);
+
+	return 0;
+}
+
+static int atc_daio_unmute(struct ct_atc *atc, unsigned char state, int type)
+{
+	struct daio_mgr *daio_mgr = atc->rsc_mgrs[DAIO];
+
+	if (state)
+		daio_mgr->daio_enable(daio_mgr, atc->daios[type]);
+	else
+		daio_mgr->daio_disable(daio_mgr, atc->daios[type]);
+
+	daio_mgr->commit_write(daio_mgr);
+
+	return 0;
+}
+
+static int
+atc_dao_get_status(struct ct_atc *atc, unsigned int *status, int type)
+{
+	struct dao *dao = container_of(atc->daios[type], struct dao, daio);
+	return dao->ops->get_spos(dao, status);
+}
+
+static int
+atc_dao_set_status(struct ct_atc *atc, unsigned int status, int type)
+{
+	struct dao *dao = container_of(atc->daios[type], struct dao, daio);
+
+	dao->ops->set_spos(dao, status);
+	dao->ops->commit_write(dao);
+	return 0;
+}
+
+static int atc_line_front_unmute(struct ct_atc *atc, unsigned char state)
+{
+	return atc_daio_unmute(atc, state, LINEO1);
+}
+
+static int atc_line_surround_unmute(struct ct_atc *atc, unsigned char state)
+{
+	return atc_daio_unmute(atc, state, LINEO2);
+}
+
+static int atc_line_clfe_unmute(struct ct_atc *atc, unsigned char state)
+{
+	return atc_daio_unmute(atc, state, LINEO3);
+}
+
+static int atc_line_rear_unmute(struct ct_atc *atc, unsigned char state)
+{
+	return atc_daio_unmute(atc, state, LINEO4);
+}
+
+static int atc_line_in_unmute(struct ct_atc *atc, unsigned char state)
+{
+	return atc_daio_unmute(atc, state, LINEIM);
+}
+
+static int atc_spdif_out_unmute(struct ct_atc *atc, unsigned char state)
+{
+	return atc_daio_unmute(atc, state, SPDIFOO);
+}
+
+static int atc_spdif_in_unmute(struct ct_atc *atc, unsigned char state)
+{
+	return atc_daio_unmute(atc, state, SPDIFIO);
+}
+
+static int atc_spdif_out_get_status(struct ct_atc *atc, unsigned int *status)
+{
+	return atc_dao_get_status(atc, status, SPDIFOO);
+}
+
+static int atc_spdif_out_set_status(struct ct_atc *atc, unsigned int status)
+{
+	return atc_dao_set_status(atc, status, SPDIFOO);
+}
+
+static int atc_spdif_out_passthru(struct ct_atc *atc, unsigned char state)
+{
+	struct dao_desc da_dsc = {0};
+	struct dao *dao;
+	int err;
+	struct ct_mixer *mixer = atc->mixer;
+	struct rsc *rscs[2] = {NULL};
+	unsigned int spos = 0;
+
+	mutex_lock(&atc->atc_mutex);
+	dao = container_of(atc->daios[SPDIFOO], struct dao, daio);
+	da_dsc.msr = state ? 1 : atc->msr;
+	da_dsc.passthru = state ? 1 : 0;
+	err = dao->ops->reinit(dao, &da_dsc);
+	if (state) {
+		spos = IEC958_DEFAULT_CON;
+	} else {
+		mixer->get_output_ports(mixer, MIX_SPDIF_OUT,
+					&rscs[0], &rscs[1]);
+		dao->ops->set_left_input(dao, rscs[0]);
+		dao->ops->set_right_input(dao, rscs[1]);
+		/* Restore PLL to atc->rsr if needed. */
+		if (atc->pll_rate != atc->rsr)
+			err = atc_pll_init(atc, atc->rsr);
+	}
+	dao->ops->set_spos(dao, spos);
+	dao->ops->commit_write(dao);
+	mutex_unlock(&atc->atc_mutex);
+
+	return err;
+}
+
+static int atc_release_resources(struct ct_atc *atc)
+{
+	int i;
+	struct daio_mgr *daio_mgr = NULL;
+	struct dao *dao = NULL;
+	struct dai *dai = NULL;
+	struct daio *daio = NULL;
+	struct sum_mgr *sum_mgr = NULL;
+	struct src_mgr *src_mgr = NULL;
+	struct srcimp_mgr *srcimp_mgr = NULL;
+	struct srcimp *srcimp = NULL;
+	struct ct_mixer *mixer = NULL;
+
+	/* disconnect internal mixer objects */
+	if (atc->mixer) {
+		mixer = atc->mixer;
+		mixer->set_input_left(mixer, MIX_LINE_IN, NULL);
+		mixer->set_input_right(mixer, MIX_LINE_IN, NULL);
+		mixer->set_input_left(mixer, MIX_MIC_IN, NULL);
+		mixer->set_input_right(mixer, MIX_MIC_IN, NULL);
+		mixer->set_input_left(mixer, MIX_SPDIF_IN, NULL);
+		mixer->set_input_right(mixer, MIX_SPDIF_IN, NULL);
+	}
+
+	if (atc->daios) {
+		daio_mgr = (struct daio_mgr *)atc->rsc_mgrs[DAIO];
+		for (i = 0; i < atc->n_daio; i++) {
+			daio = atc->daios[i];
+			if (daio->type < LINEIM) {
+				dao = container_of(daio, struct dao, daio);
+				dao->ops->clear_left_input(dao);
+				dao->ops->clear_right_input(dao);
+			} else {
+				dai = container_of(daio, struct dai, daio);
+				/* some thing to do for dai ... */
+			}
+			daio_mgr->put_daio(daio_mgr, daio);
+		}
+		kfree(atc->daios);
+		atc->daios = NULL;
+	}
+
+	if (atc->pcm) {
+		sum_mgr = atc->rsc_mgrs[SUM];
+		for (i = 0; i < atc->n_pcm; i++)
+			sum_mgr->put_sum(sum_mgr, atc->pcm[i]);
+
+		kfree(atc->pcm);
+		atc->pcm = NULL;
+	}
+
+	if (atc->srcs) {
+		src_mgr = atc->rsc_mgrs[SRC];
+		for (i = 0; i < atc->n_src; i++)
+			src_mgr->put_src(src_mgr, atc->srcs[i]);
+
+		kfree(atc->srcs);
+		atc->srcs = NULL;
+	}
+
+	if (atc->srcimps) {
+		srcimp_mgr = atc->rsc_mgrs[SRCIMP];
+		for (i = 0; i < atc->n_srcimp; i++) {
+			srcimp = atc->srcimps[i];
+			srcimp->ops->unmap(srcimp);
+			srcimp_mgr->put_srcimp(srcimp_mgr, atc->srcimps[i]);
+		}
+		kfree(atc->srcimps);
+		atc->srcimps = NULL;
+	}
+
+	return 0;
+}
+
+static int ct_atc_destroy(struct ct_atc *atc)
+{
+	int i = 0;
+
+	if (!atc)
+		return 0;
+
+	if (atc->timer) {
+		ct_timer_free(atc->timer);
+		atc->timer = NULL;
+	}
+
+	atc_release_resources(atc);
+
+	/* Destroy internal mixer objects */
+	if (atc->mixer)
+		ct_mixer_destroy(atc->mixer);
+
+	for (i = 0; i < NUM_RSCTYP; i++) {
+		if (rsc_mgr_funcs[i].destroy && atc->rsc_mgrs[i])
+			rsc_mgr_funcs[i].destroy(atc->rsc_mgrs[i]);
+
+	}
+
+	if (atc->hw)
+		destroy_hw_obj((struct hw *)atc->hw);
+
+	/* Destroy device virtual memory manager object */
+	if (atc->vm) {
+		ct_vm_destroy(atc->vm);
+		atc->vm = NULL;
+	}
+
+	kfree(atc);
+
+	return 0;
+}
+
+static int atc_dev_free(struct snd_device *dev)
+{
+	struct ct_atc *atc = dev->device_data;
+	return ct_atc_destroy(atc);
+}
+
+static int __devinit atc_identify_card(struct ct_atc *atc, unsigned int ssid)
+{
+	const struct snd_pci_quirk *p;
+	const struct snd_pci_quirk *list;
+	u16 vendor_id, device_id;
+
+	switch (atc->chip_type) {
+	case ATC20K1:
+		atc->chip_name = "20K1";
+		list = subsys_20k1_list;
+		break;
+	case ATC20K2:
+		atc->chip_name = "20K2";
+		list = subsys_20k2_list;
+		break;
+	default:
+		return -ENOENT;
+	}
+	if (ssid) {
+		vendor_id = ssid >> 16;
+		device_id = ssid & 0xffff;
+	} else {
+		vendor_id = atc->pci->subsystem_vendor;
+		device_id = atc->pci->subsystem_device;
+	}
+	p = snd_pci_quirk_lookup_id(vendor_id, device_id, list);
+	if (p) {
+		if (p->value < 0) {
+			printk(KERN_ERR "ctxfi: "
+			       "Device %04x:%04x is black-listed\n",
+			       vendor_id, device_id);
+			return -ENOENT;
+		}
+		atc->model = p->value;
+	} else {
+		if (atc->chip_type == ATC20K1)
+			atc->model = CT20K1_UNKNOWN;
+		else
+			atc->model = CT20K2_UNKNOWN;
+	}
+	atc->model_name = ct_subsys_name[atc->model];
+	snd_printd("ctxfi: chip %s model %s (%04x:%04x) is found\n",
+		   atc->chip_name, atc->model_name,
+		   vendor_id, device_id);
+	return 0;
+}
+
+int __devinit ct_atc_create_alsa_devs(struct ct_atc *atc)
+{
+	enum CTALSADEVS i;
+	int err;
+
+	alsa_dev_funcs[MIXER].public_name = atc->chip_name;
+
+	for (i = 0; i < NUM_CTALSADEVS; i++) {
+		if (!alsa_dev_funcs[i].create)
+			continue;
+
+		err = alsa_dev_funcs[i].create(atc, i,
+				alsa_dev_funcs[i].public_name);
+		if (err) {
+			printk(KERN_ERR "ctxfi: "
+			       "Creating alsa device %d failed!\n", i);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static int __devinit atc_create_hw_devs(struct ct_atc *atc)
+{
+	struct hw *hw;
+	struct card_conf info = {0};
+	int i, err;
+
+	err = create_hw_obj(atc->pci, atc->chip_type, atc->model, &hw);
+	if (err) {
+		printk(KERN_ERR "Failed to create hw obj!!!\n");
+		return err;
+	}
+	atc->hw = hw;
+
+	/* Initialize card hardware. */
+	info.rsr = atc->rsr;
+	info.msr = atc->msr;
+	info.vm_pgt_phys = atc_get_ptp_phys(atc, 0);
+	err = hw->card_init(hw, &info);
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < NUM_RSCTYP; i++) {
+		if (!rsc_mgr_funcs[i].create)
+			continue;
+
+		err = rsc_mgr_funcs[i].create(atc->hw, &atc->rsc_mgrs[i]);
+		if (err) {
+			printk(KERN_ERR "ctxfi: "
+			       "Failed to create rsc_mgr %d!!!\n", i);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static int atc_get_resources(struct ct_atc *atc)
+{
+	struct daio_desc da_desc = {0};
+	struct daio_mgr *daio_mgr;
+	struct src_desc src_dsc = {0};
+	struct src_mgr *src_mgr;
+	struct srcimp_desc srcimp_dsc = {0};
+	struct srcimp_mgr *srcimp_mgr;
+	struct sum_desc sum_dsc = {0};
+	struct sum_mgr *sum_mgr;
+	int err, i;
+
+	atc->daios = kzalloc(sizeof(void *)*(DAIONUM), GFP_KERNEL);
+	if (!atc->daios)
+		return -ENOMEM;
+
+	atc->srcs = kzalloc(sizeof(void *)*(2*2), GFP_KERNEL);
+	if (!atc->srcs)
+		return -ENOMEM;
+
+	atc->srcimps = kzalloc(sizeof(void *)*(2*2), GFP_KERNEL);
+	if (!atc->srcimps)
+		return -ENOMEM;
+
+	atc->pcm = kzalloc(sizeof(void *)*(2*4), GFP_KERNEL);
+	if (!atc->pcm)
+		return -ENOMEM;
+
+	daio_mgr = (struct daio_mgr *)atc->rsc_mgrs[DAIO];
+	da_desc.msr = atc->msr;
+	for (i = 0, atc->n_daio = 0; i < DAIONUM-1; i++) {
+		da_desc.type = i;
+		err = daio_mgr->get_daio(daio_mgr, &da_desc,
+					(struct daio **)&atc->daios[i]);
+		if (err) {
+			printk(KERN_ERR "ctxfi: Failed to get DAIO "
+					"resource %d!!!\n", i);
+			return err;
+		}
+		atc->n_daio++;
+	}
+	if (atc->model == CTSB073X)
+		da_desc.type = SPDIFI1;
+	else
+		da_desc.type = SPDIFIO;
+	err = daio_mgr->get_daio(daio_mgr, &da_desc,
+				(struct daio **)&atc->daios[i]);
+	if (err) {
+		printk(KERN_ERR "ctxfi: Failed to get S/PDIF-in resource!!!\n");
+		return err;
+	}
+	atc->n_daio++;
+
+	src_mgr = atc->rsc_mgrs[SRC];
+	src_dsc.multi = 1;
+	src_dsc.msr = atc->msr;
+	src_dsc.mode = ARCRW;
+	for (i = 0, atc->n_src = 0; i < (2*2); i++) {
+		err = src_mgr->get_src(src_mgr, &src_dsc,
+					(struct src **)&atc->srcs[i]);
+		if (err)
+			return err;
+
+		atc->n_src++;
+	}
+
+	srcimp_mgr = atc->rsc_mgrs[SRCIMP];
+	srcimp_dsc.msr = 8; /* SRCIMPs for S/PDIFIn SRT */
+	for (i = 0, atc->n_srcimp = 0; i < (2*1); i++) {
+		err = srcimp_mgr->get_srcimp(srcimp_mgr, &srcimp_dsc,
+					(struct srcimp **)&atc->srcimps[i]);
+		if (err)
+			return err;
+
+		atc->n_srcimp++;
+	}
+	srcimp_dsc.msr = 8; /* SRCIMPs for LINE/MICIn SRT */
+	for (i = 0; i < (2*1); i++) {
+		err = srcimp_mgr->get_srcimp(srcimp_mgr, &srcimp_dsc,
+				(struct srcimp **)&atc->srcimps[2*1+i]);
+		if (err)
+			return err;
+
+		atc->n_srcimp++;
+	}
+
+	sum_mgr = atc->rsc_mgrs[SUM];
+	sum_dsc.msr = atc->msr;
+	for (i = 0, atc->n_pcm = 0; i < (2*4); i++) {
+		err = sum_mgr->get_sum(sum_mgr, &sum_dsc,
+					(struct sum **)&atc->pcm[i]);
+		if (err)
+			return err;
+
+		atc->n_pcm++;
+	}
+
+	return 0;
+}
+
+static void
+atc_connect_dai(struct src_mgr *src_mgr, struct dai *dai,
+		struct src **srcs, struct srcimp **srcimps)
+{
+	struct rsc *rscs[2] = {NULL};
+	struct src *src;
+	struct srcimp *srcimp;
+	int i = 0;
+
+	rscs[0] = &dai->daio.rscl;
+	rscs[1] = &dai->daio.rscr;
+	for (i = 0; i < 2; i++) {
+		src = srcs[i];
+		srcimp = srcimps[i];
+		srcimp->ops->map(srcimp, src, rscs[i]);
+		src_mgr->src_disable(src_mgr, src);
+	}
+
+	src_mgr->commit_write(src_mgr); /* Actually disable SRCs */
+
+	src = srcs[0];
+	src->ops->set_pm(src, 1);
+	for (i = 0; i < 2; i++) {
+		src = srcs[i];
+		src->ops->set_state(src, SRC_STATE_RUN);
+		src->ops->commit_write(src);
+		src_mgr->src_enable_s(src_mgr, src);
+	}
+
+	dai->ops->set_srt_srcl(dai, &(srcs[0]->rsc));
+	dai->ops->set_srt_srcr(dai, &(srcs[1]->rsc));
+
+	dai->ops->set_enb_src(dai, 1);
+	dai->ops->set_enb_srt(dai, 1);
+	dai->ops->commit_write(dai);
+
+	src_mgr->commit_write(src_mgr); /* Synchronously enable SRCs */
+}
+
+static void atc_connect_resources(struct ct_atc *atc)
+{
+	struct dai *dai;
+	struct dao *dao;
+	struct src *src;
+	struct sum *sum;
+	struct ct_mixer *mixer;
+	struct rsc *rscs[2] = {NULL};
+	int i, j;
+
+	mixer = atc->mixer;
+
+	for (i = MIX_WAVE_FRONT, j = LINEO1; i <= MIX_SPDIF_OUT; i++, j++) {
+		mixer->get_output_ports(mixer, i, &rscs[0], &rscs[1]);
+		dao = container_of(atc->daios[j], struct dao, daio);
+		dao->ops->set_left_input(dao, rscs[0]);
+		dao->ops->set_right_input(dao, rscs[1]);
+	}
+
+	dai = container_of(atc->daios[LINEIM], struct dai, daio);
+	atc_connect_dai(atc->rsc_mgrs[SRC], dai,
+			(struct src **)&atc->srcs[2],
+			(struct srcimp **)&atc->srcimps[2]);
+	src = atc->srcs[2];
+	mixer->set_input_left(mixer, MIX_LINE_IN, &src->rsc);
+	src = atc->srcs[3];
+	mixer->set_input_right(mixer, MIX_LINE_IN, &src->rsc);
+
+	dai = container_of(atc->daios[SPDIFIO], struct dai, daio);
+	atc_connect_dai(atc->rsc_mgrs[SRC], dai,
+			(struct src **)&atc->srcs[0],
+			(struct srcimp **)&atc->srcimps[0]);
+
+	src = atc->srcs[0];
+	mixer->set_input_left(mixer, MIX_SPDIF_IN, &src->rsc);
+	src = atc->srcs[1];
+	mixer->set_input_right(mixer, MIX_SPDIF_IN, &src->rsc);
+
+	for (i = MIX_PCMI_FRONT, j = 0; i <= MIX_PCMI_SURROUND; i++, j += 2) {
+		sum = atc->pcm[j];
+		mixer->set_input_left(mixer, i, &sum->rsc);
+		sum = atc->pcm[j+1];
+		mixer->set_input_right(mixer, i, &sum->rsc);
+	}
+}
+
+#ifdef CONFIG_PM
+static int atc_suspend(struct ct_atc *atc, pm_message_t state)
+{
+	int i;
+	struct hw *hw = atc->hw;
+
+	snd_power_change_state(atc->card, SNDRV_CTL_POWER_D3hot);
+
+	for (i = FRONT; i < NUM_PCMS; i++) {
+		if (!atc->pcms[i])
+			continue;
+
+		snd_pcm_suspend_all(atc->pcms[i]);
+	}
+
+	atc_release_resources(atc);
+
+	hw->suspend(hw, state);
+
+	return 0;
+}
+
+static int atc_hw_resume(struct ct_atc *atc)
+{
+	struct hw *hw = atc->hw;
+	struct card_conf info = {0};
+
+	/* Re-initialize card hardware. */
+	info.rsr = atc->rsr;
+	info.msr = atc->msr;
+	info.vm_pgt_phys = atc_get_ptp_phys(atc, 0);
+	return hw->resume(hw, &info);
+}
+
+static int atc_resources_resume(struct ct_atc *atc)
+{
+	struct ct_mixer *mixer;
+	int err = 0;
+
+	/* Get resources */
+	err = atc_get_resources(atc);
+	if (err < 0) {
+		atc_release_resources(atc);
+		return err;
+	}
+
+	/* Build topology */
+	atc_connect_resources(atc);
+
+	mixer = atc->mixer;
+	mixer->resume(mixer);
+
+	return 0;
+}
+
+static int atc_resume(struct ct_atc *atc)
+{
+	int err = 0;
+
+	/* Do hardware resume. */
+	err = atc_hw_resume(atc);
+	if (err < 0) {
+		printk(KERN_ERR "ctxfi: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(atc->card);
+		return err;
+	}
+
+	err = atc_resources_resume(atc);
+	if (err < 0)
+		return err;
+
+	snd_power_change_state(atc->card, SNDRV_CTL_POWER_D0);
+
+	return 0;
+}
+#endif
+
+static struct ct_atc atc_preset __devinitdata = {
+	.map_audio_buffer = ct_map_audio_buffer,
+	.unmap_audio_buffer = ct_unmap_audio_buffer,
+	.pcm_playback_prepare = atc_pcm_playback_prepare,
+	.pcm_release_resources = atc_pcm_release_resources,
+	.pcm_playback_start = atc_pcm_playback_start,
+	.pcm_playback_stop = atc_pcm_stop,
+	.pcm_playback_position = atc_pcm_playback_position,
+	.pcm_capture_prepare = atc_pcm_capture_prepare,
+	.pcm_capture_start = atc_pcm_capture_start,
+	.pcm_capture_stop = atc_pcm_stop,
+	.pcm_capture_position = atc_pcm_capture_position,
+	.spdif_passthru_playback_prepare = spdif_passthru_playback_prepare,
+	.get_ptp_phys = atc_get_ptp_phys,
+	.select_line_in = atc_select_line_in,
+	.select_mic_in = atc_select_mic_in,
+	.select_digit_io = atc_select_digit_io,
+	.line_front_unmute = atc_line_front_unmute,
+	.line_surround_unmute = atc_line_surround_unmute,
+	.line_clfe_unmute = atc_line_clfe_unmute,
+	.line_rear_unmute = atc_line_rear_unmute,
+	.line_in_unmute = atc_line_in_unmute,
+	.spdif_out_unmute = atc_spdif_out_unmute,
+	.spdif_in_unmute = atc_spdif_in_unmute,
+	.spdif_out_get_status = atc_spdif_out_get_status,
+	.spdif_out_set_status = atc_spdif_out_set_status,
+	.spdif_out_passthru = atc_spdif_out_passthru,
+	.have_digit_io_switch = atc_have_digit_io_switch,
+#ifdef CONFIG_PM
+	.suspend = atc_suspend,
+	.resume = atc_resume,
+#endif
+};
+
+/**
+ *  ct_atc_create - create and initialize a hardware manager
+ *  @card: corresponding alsa card object
+ *  @pci: corresponding kernel pci device object
+ *  @ratc: return created object address in it
+ *
+ *  Creates and initializes a hardware manager.
+ *
+ *  Creates kmallocated ct_atc structure. Initializes hardware.
+ *  Returns 0 if suceeds, or negative error code if fails.
+ */
+
+int __devinit ct_atc_create(struct snd_card *card, struct pci_dev *pci,
+			    unsigned int rsr, unsigned int msr,
+			    int chip_type, unsigned int ssid,
+			    struct ct_atc **ratc)
+{
+	struct ct_atc *atc;
+	static struct snd_device_ops ops = {
+		.dev_free = atc_dev_free,
+	};
+	int err;
+
+	*ratc = NULL;
+
+	atc = kzalloc(sizeof(*atc), GFP_KERNEL);
+	if (!atc)
+		return -ENOMEM;
+
+	/* Set operations */
+	*atc = atc_preset;
+
+	atc->card = card;
+	atc->pci = pci;
+	atc->rsr = rsr;
+	atc->msr = msr;
+	atc->chip_type = chip_type;
+
+	mutex_init(&atc->atc_mutex);
+
+	/* Find card model */
+	err = atc_identify_card(atc, ssid);
+	if (err < 0) {
+		printk(KERN_ERR "ctatc: Card not recognised\n");
+		goto error1;
+	}
+
+	/* Set up device virtual memory management object */
+	err = ct_vm_create(&atc->vm, pci);
+	if (err < 0)
+		goto error1;
+
+	/* Create all atc hw devices */
+	err = atc_create_hw_devs(atc);
+	if (err < 0)
+		goto error1;
+
+	err = ct_mixer_create(atc, (struct ct_mixer **)&atc->mixer);
+	if (err) {
+		printk(KERN_ERR "ctxfi: Failed to create mixer obj!!!\n");
+		goto error1;
+	}
+
+	/* Get resources */
+	err = atc_get_resources(atc);
+	if (err < 0)
+		goto error1;
+
+	/* Build topology */
+	atc_connect_resources(atc);
+
+	atc->timer = ct_timer_new(atc);
+	if (!atc->timer)
+		goto error1;
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, atc, &ops);
+	if (err < 0)
+		goto error1;
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*ratc = atc;
+	return 0;
+
+error1:
+	ct_atc_destroy(atc);
+	printk(KERN_ERR "ctxfi: Something wrong!!!\n");
+	return err;
+}
diff --git a/linux/sound/pci/ctxfi/ctatc.h b/linux/sound/pci/ctxfi/ctatc.h
new file mode 100644
index 0000000..7167c01
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctatc.h
@@ -0,0 +1,154 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctatc.h
+ *
+ * @Brief
+ * This file contains the definition of the device resource management object.
+ *
+ * @Author	Liu Chun
+ * @Date 	Mar 28 2008
+ *
+ */
+
+#ifndef CTATC_H
+#define CTATC_H
+
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/timer.h>
+#include <sound/core.h>
+
+#include "ctvmem.h"
+#include "ctresource.h"
+
+enum CTALSADEVS {		/* Types of alsa devices */
+	FRONT,
+	SURROUND,
+	CLFE,
+	SIDE,
+	IEC958,
+	MIXER,
+	NUM_CTALSADEVS		/* This should always be the last */
+};
+
+struct ct_atc_chip_sub_details {
+	u16 subsys;
+	const char *nm_model;
+};
+
+struct ct_atc_chip_details {
+	u16 vendor;
+	u16 device;
+	const struct ct_atc_chip_sub_details *sub_details;
+	const char *nm_card;
+};
+
+struct ct_atc;
+struct ct_timer;
+struct ct_timer_instance;
+
+/* alsa pcm stream descriptor */
+struct ct_atc_pcm {
+	struct snd_pcm_substream *substream;
+	void (*interrupt)(struct ct_atc_pcm *apcm);
+	struct ct_timer_instance *timer;
+	unsigned int started:1;
+
+	/* Only mono and interleaved modes are supported now. */
+	struct ct_vm_block *vm_block;
+	void *src;		/* SRC for interacting with host memory */
+	void **srccs;		/* SRCs for sample rate conversion */
+	void **srcimps;		/* SRC Input Mappers */
+	void **amixers;		/* AMIXERs for routing converted data */
+	void *mono;		/* A SUM resource for mixing chs to one */
+	unsigned char n_srcc;	/* Number of converting SRCs */
+	unsigned char n_srcimp;	/* Number of SRC Input Mappers */
+	unsigned char n_amixer;	/* Number of AMIXERs */
+};
+
+/* Chip resource management object */
+struct ct_atc {
+	struct pci_dev *pci;
+	struct snd_card *card;
+	unsigned int rsr; /* reference sample rate in Hz */
+	unsigned int msr; /* master sample rate in rsr */
+	unsigned int pll_rate; /* current rate of Phase Lock Loop */
+
+	int chip_type;
+	int model;
+	const char *chip_name;
+	const char *model_name;
+
+	struct ct_vm *vm; /* device virtual memory manager for this card */
+	int (*map_audio_buffer)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+	void (*unmap_audio_buffer)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+	unsigned long (*get_ptp_phys)(struct ct_atc *atc, int index);
+
+	struct mutex atc_mutex;
+
+	int (*pcm_playback_prepare)(struct ct_atc *atc,
+				    struct ct_atc_pcm *apcm);
+	int (*pcm_playback_start)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+	int (*pcm_playback_stop)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+	int (*pcm_playback_position)(struct ct_atc *atc,
+				     struct ct_atc_pcm *apcm);
+	int (*spdif_passthru_playback_prepare)(struct ct_atc *atc,
+					       struct ct_atc_pcm *apcm);
+	int (*pcm_capture_prepare)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+	int (*pcm_capture_start)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+	int (*pcm_capture_stop)(struct ct_atc *atc, struct ct_atc_pcm *apcm);
+	int (*pcm_capture_position)(struct ct_atc *atc,
+				    struct ct_atc_pcm *apcm);
+	int (*pcm_release_resources)(struct ct_atc *atc,
+				     struct ct_atc_pcm *apcm);
+	int (*select_line_in)(struct ct_atc *atc);
+	int (*select_mic_in)(struct ct_atc *atc);
+	int (*select_digit_io)(struct ct_atc *atc);
+	int (*line_front_unmute)(struct ct_atc *atc, unsigned char state);
+	int (*line_surround_unmute)(struct ct_atc *atc, unsigned char state);
+	int (*line_clfe_unmute)(struct ct_atc *atc, unsigned char state);
+	int (*line_rear_unmute)(struct ct_atc *atc, unsigned char state);
+	int (*line_in_unmute)(struct ct_atc *atc, unsigned char state);
+	int (*spdif_out_unmute)(struct ct_atc *atc, unsigned char state);
+	int (*spdif_in_unmute)(struct ct_atc *atc, unsigned char state);
+	int (*spdif_out_get_status)(struct ct_atc *atc, unsigned int *status);
+	int (*spdif_out_set_status)(struct ct_atc *atc, unsigned int status);
+	int (*spdif_out_passthru)(struct ct_atc *atc, unsigned char state);
+	int (*have_digit_io_switch)(struct ct_atc *atc);
+
+	/* Don't touch! Used for internal object. */
+	void *rsc_mgrs[NUM_RSCTYP]; /* chip resource managers */
+	void *mixer;		/* internal mixer object */
+	void *hw;		/* chip specific hardware access object */
+	void **daios;		/* digital audio io resources */
+	void **pcm;		/* SUMs for collecting all pcm stream */
+	void **srcs;		/* Sample Rate Converters for input signal */
+	void **srcimps;		/* input mappers for SRCs */
+	unsigned char n_daio;
+	unsigned char n_src;
+	unsigned char n_srcimp;
+	unsigned char n_pcm;
+
+	struct ct_timer *timer;
+
+#ifdef CONFIG_PM
+	int (*suspend)(struct ct_atc *atc, pm_message_t state);
+	int (*resume)(struct ct_atc *atc);
+#define NUM_PCMS (NUM_CTALSADEVS - 1)
+	struct snd_pcm *pcms[NUM_PCMS];
+#endif
+};
+
+
+int __devinit ct_atc_create(struct snd_card *card, struct pci_dev *pci,
+			    unsigned int rsr, unsigned int msr, int chip_type,
+			    unsigned int subsysid, struct ct_atc **ratc);
+int __devinit ct_atc_create_alsa_devs(struct ct_atc *atc);
+
+#endif /* CTATC_H */
diff --git a/linux/sound/pci/ctxfi/ctdaio.c b/linux/sound/pci/ctxfi/ctdaio.c
new file mode 100644
index 0000000..af56eb9
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctdaio.c
@@ -0,0 +1,769 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctdaio.c
+ *
+ * @Brief
+ * This file contains the implementation of Digital Audio Input Output
+ * resource management object.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 23 2008
+ *
+ */
+
+#include "ctdaio.h"
+#include "cthardware.h"
+#include "ctimap.h"
+#include <linux/slab.h>
+#include <linux/kernel.h>
+
+#define DAIO_RESOURCE_NUM	NUM_DAIOTYP
+#define DAIO_OUT_MAX		SPDIFOO
+
+union daio_usage {
+	struct {
+		unsigned short lineo1:1;
+		unsigned short lineo2:1;
+		unsigned short lineo3:1;
+		unsigned short lineo4:1;
+		unsigned short spdifoo:1;
+		unsigned short lineim:1;
+		unsigned short spdifio:1;
+		unsigned short spdifi1:1;
+	} bf;
+	unsigned short data;
+};
+
+struct daio_rsc_idx {
+	unsigned short left;
+	unsigned short right;
+};
+
+struct daio_rsc_idx idx_20k1[NUM_DAIOTYP] = {
+	[LINEO1] = {.left = 0x00, .right = 0x01},
+	[LINEO2] = {.left = 0x18, .right = 0x19},
+	[LINEO3] = {.left = 0x08, .right = 0x09},
+	[LINEO4] = {.left = 0x10, .right = 0x11},
+	[LINEIM] = {.left = 0x1b5, .right = 0x1bd},
+	[SPDIFOO] = {.left = 0x20, .right = 0x21},
+	[SPDIFIO] = {.left = 0x15, .right = 0x1d},
+	[SPDIFI1] = {.left = 0x95, .right = 0x9d},
+};
+
+struct daio_rsc_idx idx_20k2[NUM_DAIOTYP] = {
+	[LINEO1] = {.left = 0x40, .right = 0x41},
+	[LINEO2] = {.left = 0x60, .right = 0x61},
+	[LINEO3] = {.left = 0x50, .right = 0x51},
+	[LINEO4] = {.left = 0x70, .right = 0x71},
+	[LINEIM] = {.left = 0x45, .right = 0xc5},
+	[SPDIFOO] = {.left = 0x00, .right = 0x01},
+	[SPDIFIO] = {.left = 0x05, .right = 0x85},
+};
+
+static int daio_master(struct rsc *rsc)
+{
+	/* Actually, this is not the resource index of DAIO.
+	 * For DAO, it is the input mapper index. And, for DAI,
+	 * it is the output time-slot index. */
+	return rsc->conj = rsc->idx;
+}
+
+static int daio_index(const struct rsc *rsc)
+{
+	return rsc->conj;
+}
+
+static int daio_out_next_conj(struct rsc *rsc)
+{
+	return rsc->conj += 2;
+}
+
+static int daio_in_next_conj_20k1(struct rsc *rsc)
+{
+	return rsc->conj += 0x200;
+}
+
+static int daio_in_next_conj_20k2(struct rsc *rsc)
+{
+	return rsc->conj += 0x100;
+}
+
+static struct rsc_ops daio_out_rsc_ops = {
+	.master		= daio_master,
+	.next_conj	= daio_out_next_conj,
+	.index		= daio_index,
+	.output_slot	= NULL,
+};
+
+static struct rsc_ops daio_in_rsc_ops_20k1 = {
+	.master		= daio_master,
+	.next_conj	= daio_in_next_conj_20k1,
+	.index		= NULL,
+	.output_slot	= daio_index,
+};
+
+static struct rsc_ops daio_in_rsc_ops_20k2 = {
+	.master		= daio_master,
+	.next_conj	= daio_in_next_conj_20k2,
+	.index		= NULL,
+	.output_slot	= daio_index,
+};
+
+static unsigned int daio_device_index(enum DAIOTYP type, struct hw *hw)
+{
+	switch (hw->chip_type) {
+	case ATC20K1:
+		switch (type) {
+		case SPDIFOO:	return 0;
+		case SPDIFIO:	return 0;
+		case SPDIFI1:	return 1;
+		case LINEO1:	return 4;
+		case LINEO2:	return 7;
+		case LINEO3:	return 5;
+		case LINEO4:	return 6;
+		case LINEIM:	return 7;
+		default:	return -EINVAL;
+		}
+	case ATC20K2:
+		switch (type) {
+		case SPDIFOO:	return 0;
+		case SPDIFIO:	return 0;
+		case LINEO1:	return 4;
+		case LINEO2:	return 7;
+		case LINEO3:	return 5;
+		case LINEO4:	return 6;
+		case LINEIM:	return 4;
+		default:	return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static int dao_rsc_reinit(struct dao *dao, const struct dao_desc *desc);
+
+static int dao_spdif_get_spos(struct dao *dao, unsigned int *spos)
+{
+	((struct hw *)dao->hw)->dao_get_spos(dao->ctrl_blk, spos);
+	return 0;
+}
+
+static int dao_spdif_set_spos(struct dao *dao, unsigned int spos)
+{
+	((struct hw *)dao->hw)->dao_set_spos(dao->ctrl_blk, spos);
+	return 0;
+}
+
+static int dao_commit_write(struct dao *dao)
+{
+	((struct hw *)dao->hw)->dao_commit_write(dao->hw,
+		daio_device_index(dao->daio.type, dao->hw), dao->ctrl_blk);
+	return 0;
+}
+
+static int dao_set_left_input(struct dao *dao, struct rsc *input)
+{
+	struct imapper *entry;
+	struct daio *daio = &dao->daio;
+	int i;
+
+	entry = kzalloc((sizeof(*entry) * daio->rscl.msr), GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+
+	/* Program master and conjugate resources */
+	input->ops->master(input);
+	daio->rscl.ops->master(&daio->rscl);
+	for (i = 0; i < daio->rscl.msr; i++, entry++) {
+		entry->slot = input->ops->output_slot(input);
+		entry->user = entry->addr = daio->rscl.ops->index(&daio->rscl);
+		dao->mgr->imap_add(dao->mgr, entry);
+		dao->imappers[i] = entry;
+
+		input->ops->next_conj(input);
+		daio->rscl.ops->next_conj(&daio->rscl);
+	}
+	input->ops->master(input);
+	daio->rscl.ops->master(&daio->rscl);
+
+	return 0;
+}
+
+static int dao_set_right_input(struct dao *dao, struct rsc *input)
+{
+	struct imapper *entry;
+	struct daio *daio = &dao->daio;
+	int i;
+
+	entry = kzalloc((sizeof(*entry) * daio->rscr.msr), GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+
+	/* Program master and conjugate resources */
+	input->ops->master(input);
+	daio->rscr.ops->master(&daio->rscr);
+	for (i = 0; i < daio->rscr.msr; i++, entry++) {
+		entry->slot = input->ops->output_slot(input);
+		entry->user = entry->addr = daio->rscr.ops->index(&daio->rscr);
+		dao->mgr->imap_add(dao->mgr, entry);
+		dao->imappers[daio->rscl.msr + i] = entry;
+
+		input->ops->next_conj(input);
+		daio->rscr.ops->next_conj(&daio->rscr);
+	}
+	input->ops->master(input);
+	daio->rscr.ops->master(&daio->rscr);
+
+	return 0;
+}
+
+static int dao_clear_left_input(struct dao *dao)
+{
+	struct imapper *entry;
+	struct daio *daio = &dao->daio;
+	int i;
+
+	if (!dao->imappers[0])
+		return 0;
+
+	entry = dao->imappers[0];
+	dao->mgr->imap_delete(dao->mgr, entry);
+	/* Program conjugate resources */
+	for (i = 1; i < daio->rscl.msr; i++) {
+		entry = dao->imappers[i];
+		dao->mgr->imap_delete(dao->mgr, entry);
+		dao->imappers[i] = NULL;
+	}
+
+	kfree(dao->imappers[0]);
+	dao->imappers[0] = NULL;
+
+	return 0;
+}
+
+static int dao_clear_right_input(struct dao *dao)
+{
+	struct imapper *entry;
+	struct daio *daio = &dao->daio;
+	int i;
+
+	if (!dao->imappers[daio->rscl.msr])
+		return 0;
+
+	entry = dao->imappers[daio->rscl.msr];
+	dao->mgr->imap_delete(dao->mgr, entry);
+	/* Program conjugate resources */
+	for (i = 1; i < daio->rscr.msr; i++) {
+		entry = dao->imappers[daio->rscl.msr + i];
+		dao->mgr->imap_delete(dao->mgr, entry);
+		dao->imappers[daio->rscl.msr + i] = NULL;
+	}
+
+	kfree(dao->imappers[daio->rscl.msr]);
+	dao->imappers[daio->rscl.msr] = NULL;
+
+	return 0;
+}
+
+static struct dao_rsc_ops dao_ops = {
+	.set_spos		= dao_spdif_set_spos,
+	.commit_write		= dao_commit_write,
+	.get_spos		= dao_spdif_get_spos,
+	.reinit			= dao_rsc_reinit,
+	.set_left_input		= dao_set_left_input,
+	.set_right_input	= dao_set_right_input,
+	.clear_left_input	= dao_clear_left_input,
+	.clear_right_input	= dao_clear_right_input,
+};
+
+static int dai_set_srt_srcl(struct dai *dai, struct rsc *src)
+{
+	src->ops->master(src);
+	((struct hw *)dai->hw)->dai_srt_set_srcm(dai->ctrl_blk,
+						src->ops->index(src));
+	return 0;
+}
+
+static int dai_set_srt_srcr(struct dai *dai, struct rsc *src)
+{
+	src->ops->master(src);
+	((struct hw *)dai->hw)->dai_srt_set_srco(dai->ctrl_blk,
+						src->ops->index(src));
+	return 0;
+}
+
+static int dai_set_srt_msr(struct dai *dai, unsigned int msr)
+{
+	unsigned int rsr;
+
+	for (rsr = 0; msr > 1; msr >>= 1)
+		rsr++;
+
+	((struct hw *)dai->hw)->dai_srt_set_rsr(dai->ctrl_blk, rsr);
+	return 0;
+}
+
+static int dai_set_enb_src(struct dai *dai, unsigned int enb)
+{
+	((struct hw *)dai->hw)->dai_srt_set_ec(dai->ctrl_blk, enb);
+	return 0;
+}
+
+static int dai_set_enb_srt(struct dai *dai, unsigned int enb)
+{
+	((struct hw *)dai->hw)->dai_srt_set_et(dai->ctrl_blk, enb);
+	return 0;
+}
+
+static int dai_commit_write(struct dai *dai)
+{
+	((struct hw *)dai->hw)->dai_commit_write(dai->hw,
+		daio_device_index(dai->daio.type, dai->hw), dai->ctrl_blk);
+	return 0;
+}
+
+static struct dai_rsc_ops dai_ops = {
+	.set_srt_srcl		= dai_set_srt_srcl,
+	.set_srt_srcr		= dai_set_srt_srcr,
+	.set_srt_msr		= dai_set_srt_msr,
+	.set_enb_src		= dai_set_enb_src,
+	.set_enb_srt		= dai_set_enb_srt,
+	.commit_write		= dai_commit_write,
+};
+
+static int daio_rsc_init(struct daio *daio,
+			 const struct daio_desc *desc,
+			 void *hw)
+{
+	int err;
+	unsigned int idx_l, idx_r;
+
+	switch (((struct hw *)hw)->chip_type) {
+	case ATC20K1:
+		idx_l = idx_20k1[desc->type].left;
+		idx_r = idx_20k1[desc->type].right;
+		break;
+	case ATC20K2:
+		idx_l = idx_20k2[desc->type].left;
+		idx_r = idx_20k2[desc->type].right;
+		break;
+	default:
+		return -EINVAL;
+	}
+	err = rsc_init(&daio->rscl, idx_l, DAIO, desc->msr, hw);
+	if (err)
+		return err;
+
+	err = rsc_init(&daio->rscr, idx_r, DAIO, desc->msr, hw);
+	if (err)
+		goto error1;
+
+	/* Set daio->rscl/r->ops to daio specific ones */
+	if (desc->type <= DAIO_OUT_MAX) {
+		daio->rscl.ops = daio->rscr.ops = &daio_out_rsc_ops;
+	} else {
+		switch (((struct hw *)hw)->chip_type) {
+		case ATC20K1:
+			daio->rscl.ops = daio->rscr.ops = &daio_in_rsc_ops_20k1;
+			break;
+		case ATC20K2:
+			daio->rscl.ops = daio->rscr.ops = &daio_in_rsc_ops_20k2;
+			break;
+		default:
+			break;
+		}
+	}
+	daio->type = desc->type;
+
+	return 0;
+
+error1:
+	rsc_uninit(&daio->rscl);
+	return err;
+}
+
+static int daio_rsc_uninit(struct daio *daio)
+{
+	rsc_uninit(&daio->rscl);
+	rsc_uninit(&daio->rscr);
+
+	return 0;
+}
+
+static int dao_rsc_init(struct dao *dao,
+			const struct daio_desc *desc,
+			struct daio_mgr *mgr)
+{
+	struct hw *hw = mgr->mgr.hw;
+	unsigned int conf;
+	int err;
+
+	err = daio_rsc_init(&dao->daio, desc, mgr->mgr.hw);
+	if (err)
+		return err;
+
+	dao->imappers = kzalloc(sizeof(void *)*desc->msr*2, GFP_KERNEL);
+	if (!dao->imappers) {
+		err = -ENOMEM;
+		goto error1;
+	}
+	dao->ops = &dao_ops;
+	dao->mgr = mgr;
+	dao->hw = hw;
+	err = hw->dao_get_ctrl_blk(&dao->ctrl_blk);
+	if (err)
+		goto error2;
+
+	hw->daio_mgr_dsb_dao(mgr->mgr.ctrl_blk,
+			daio_device_index(dao->daio.type, hw));
+	hw->daio_mgr_commit_write(hw, mgr->mgr.ctrl_blk);
+
+	conf = (desc->msr & 0x7) | (desc->passthru << 3);
+	hw->daio_mgr_dao_init(mgr->mgr.ctrl_blk,
+			daio_device_index(dao->daio.type, hw), conf);
+	hw->daio_mgr_enb_dao(mgr->mgr.ctrl_blk,
+			daio_device_index(dao->daio.type, hw));
+	hw->daio_mgr_commit_write(hw, mgr->mgr.ctrl_blk);
+
+	return 0;
+
+error2:
+	kfree(dao->imappers);
+	dao->imappers = NULL;
+error1:
+	daio_rsc_uninit(&dao->daio);
+	return err;
+}
+
+static int dao_rsc_uninit(struct dao *dao)
+{
+	if (dao->imappers) {
+		if (dao->imappers[0])
+			dao_clear_left_input(dao);
+
+		if (dao->imappers[dao->daio.rscl.msr])
+			dao_clear_right_input(dao);
+
+		kfree(dao->imappers);
+		dao->imappers = NULL;
+	}
+	((struct hw *)dao->hw)->dao_put_ctrl_blk(dao->ctrl_blk);
+	dao->hw = dao->ctrl_blk = NULL;
+	daio_rsc_uninit(&dao->daio);
+
+	return 0;
+}
+
+static int dao_rsc_reinit(struct dao *dao, const struct dao_desc *desc)
+{
+	struct daio_mgr *mgr = dao->mgr;
+	struct daio_desc dsc = {0};
+
+	dsc.type = dao->daio.type;
+	dsc.msr = desc->msr;
+	dsc.passthru = desc->passthru;
+	dao_rsc_uninit(dao);
+	return dao_rsc_init(dao, &dsc, mgr);
+}
+
+static int dai_rsc_init(struct dai *dai,
+			const struct daio_desc *desc,
+			struct daio_mgr *mgr)
+{
+	int err;
+	struct hw *hw = mgr->mgr.hw;
+	unsigned int rsr, msr;
+
+	err = daio_rsc_init(&dai->daio, desc, mgr->mgr.hw);
+	if (err)
+		return err;
+
+	dai->ops = &dai_ops;
+	dai->hw = mgr->mgr.hw;
+	err = hw->dai_get_ctrl_blk(&dai->ctrl_blk);
+	if (err)
+		goto error1;
+
+	for (rsr = 0, msr = desc->msr; msr > 1; msr >>= 1)
+		rsr++;
+
+	hw->dai_srt_set_rsr(dai->ctrl_blk, rsr);
+	hw->dai_srt_set_drat(dai->ctrl_blk, 0);
+	/* default to disabling control of a SRC */
+	hw->dai_srt_set_ec(dai->ctrl_blk, 0);
+	hw->dai_srt_set_et(dai->ctrl_blk, 0); /* default to disabling SRT */
+	hw->dai_commit_write(hw,
+		daio_device_index(dai->daio.type, dai->hw), dai->ctrl_blk);
+
+	return 0;
+
+error1:
+	daio_rsc_uninit(&dai->daio);
+	return err;
+}
+
+static int dai_rsc_uninit(struct dai *dai)
+{
+	((struct hw *)dai->hw)->dai_put_ctrl_blk(dai->ctrl_blk);
+	dai->hw = dai->ctrl_blk = NULL;
+	daio_rsc_uninit(&dai->daio);
+	return 0;
+}
+
+static int daio_mgr_get_rsc(struct rsc_mgr *mgr, enum DAIOTYP type)
+{
+	if (((union daio_usage *)mgr->rscs)->data & (0x1 << type))
+		return -ENOENT;
+
+	((union daio_usage *)mgr->rscs)->data |= (0x1 << type);
+
+	return 0;
+}
+
+static int daio_mgr_put_rsc(struct rsc_mgr *mgr, enum DAIOTYP type)
+{
+	((union daio_usage *)mgr->rscs)->data &= ~(0x1 << type);
+
+	return 0;
+}
+
+static int get_daio_rsc(struct daio_mgr *mgr,
+			const struct daio_desc *desc,
+			struct daio **rdaio)
+{
+	int err;
+	struct dai *dai = NULL;
+	struct dao *dao = NULL;
+	unsigned long flags;
+
+	*rdaio = NULL;
+
+	/* Check whether there are sufficient daio resources to meet request. */
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	err = daio_mgr_get_rsc(&mgr->mgr, desc->type);
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	if (err) {
+		printk(KERN_ERR "Can't meet DAIO resource request!\n");
+		return err;
+	}
+
+	/* Allocate mem for daio resource */
+	if (desc->type <= DAIO_OUT_MAX) {
+		dao = kzalloc(sizeof(*dao), GFP_KERNEL);
+		if (!dao) {
+			err = -ENOMEM;
+			goto error;
+		}
+		err = dao_rsc_init(dao, desc, mgr);
+		if (err)
+			goto error;
+
+		*rdaio = &dao->daio;
+	} else {
+		dai = kzalloc(sizeof(*dai), GFP_KERNEL);
+		if (!dai) {
+			err = -ENOMEM;
+			goto error;
+		}
+		err = dai_rsc_init(dai, desc, mgr);
+		if (err)
+			goto error;
+
+		*rdaio = &dai->daio;
+	}
+
+	mgr->daio_enable(mgr, *rdaio);
+	mgr->commit_write(mgr);
+
+	return 0;
+
+error:
+	if (dao)
+		kfree(dao);
+	else if (dai)
+		kfree(dai);
+
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	daio_mgr_put_rsc(&mgr->mgr, desc->type);
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	return err;
+}
+
+static int put_daio_rsc(struct daio_mgr *mgr, struct daio *daio)
+{
+	unsigned long flags;
+
+	mgr->daio_disable(mgr, daio);
+	mgr->commit_write(mgr);
+
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	daio_mgr_put_rsc(&mgr->mgr, daio->type);
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+
+	if (daio->type <= DAIO_OUT_MAX) {
+		dao_rsc_uninit(container_of(daio, struct dao, daio));
+		kfree(container_of(daio, struct dao, daio));
+	} else {
+		dai_rsc_uninit(container_of(daio, struct dai, daio));
+		kfree(container_of(daio, struct dai, daio));
+	}
+
+	return 0;
+}
+
+static int daio_mgr_enb_daio(struct daio_mgr *mgr, struct daio *daio)
+{
+	struct hw *hw = mgr->mgr.hw;
+
+	if (DAIO_OUT_MAX >= daio->type) {
+		hw->daio_mgr_enb_dao(mgr->mgr.ctrl_blk,
+				daio_device_index(daio->type, hw));
+	} else {
+		hw->daio_mgr_enb_dai(mgr->mgr.ctrl_blk,
+				daio_device_index(daio->type, hw));
+	}
+	return 0;
+}
+
+static int daio_mgr_dsb_daio(struct daio_mgr *mgr, struct daio *daio)
+{
+	struct hw *hw = mgr->mgr.hw;
+
+	if (DAIO_OUT_MAX >= daio->type) {
+		hw->daio_mgr_dsb_dao(mgr->mgr.ctrl_blk,
+				daio_device_index(daio->type, hw));
+	} else {
+		hw->daio_mgr_dsb_dai(mgr->mgr.ctrl_blk,
+				daio_device_index(daio->type, hw));
+	}
+	return 0;
+}
+
+static int daio_map_op(void *data, struct imapper *entry)
+{
+	struct rsc_mgr *mgr = &((struct daio_mgr *)data)->mgr;
+	struct hw *hw = mgr->hw;
+
+	hw->daio_mgr_set_imaparc(mgr->ctrl_blk, entry->slot);
+	hw->daio_mgr_set_imapnxt(mgr->ctrl_blk, entry->next);
+	hw->daio_mgr_set_imapaddr(mgr->ctrl_blk, entry->addr);
+	hw->daio_mgr_commit_write(mgr->hw, mgr->ctrl_blk);
+
+	return 0;
+}
+
+static int daio_imap_add(struct daio_mgr *mgr, struct imapper *entry)
+{
+	unsigned long flags;
+	int err;
+
+	spin_lock_irqsave(&mgr->imap_lock, flags);
+	if (!entry->addr && mgr->init_imap_added) {
+		input_mapper_delete(&mgr->imappers, mgr->init_imap,
+							daio_map_op, mgr);
+		mgr->init_imap_added = 0;
+	}
+	err = input_mapper_add(&mgr->imappers, entry, daio_map_op, mgr);
+	spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+	return err;
+}
+
+static int daio_imap_delete(struct daio_mgr *mgr, struct imapper *entry)
+{
+	unsigned long flags;
+	int err;
+
+	spin_lock_irqsave(&mgr->imap_lock, flags);
+	err = input_mapper_delete(&mgr->imappers, entry, daio_map_op, mgr);
+	if (list_empty(&mgr->imappers)) {
+		input_mapper_add(&mgr->imappers, mgr->init_imap,
+							daio_map_op, mgr);
+		mgr->init_imap_added = 1;
+	}
+	spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+	return err;
+}
+
+static int daio_mgr_commit_write(struct daio_mgr *mgr)
+{
+	struct hw *hw = mgr->mgr.hw;
+
+	hw->daio_mgr_commit_write(hw, mgr->mgr.ctrl_blk);
+	return 0;
+}
+
+int daio_mgr_create(void *hw, struct daio_mgr **rdaio_mgr)
+{
+	int err, i;
+	struct daio_mgr *daio_mgr;
+	struct imapper *entry;
+
+	*rdaio_mgr = NULL;
+	daio_mgr = kzalloc(sizeof(*daio_mgr), GFP_KERNEL);
+	if (!daio_mgr)
+		return -ENOMEM;
+
+	err = rsc_mgr_init(&daio_mgr->mgr, DAIO, DAIO_RESOURCE_NUM, hw);
+	if (err)
+		goto error1;
+
+	spin_lock_init(&daio_mgr->mgr_lock);
+	spin_lock_init(&daio_mgr->imap_lock);
+	INIT_LIST_HEAD(&daio_mgr->imappers);
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry) {
+		err = -ENOMEM;
+		goto error2;
+	}
+	entry->slot = entry->addr = entry->next = entry->user = 0;
+	list_add(&entry->list, &daio_mgr->imappers);
+	daio_mgr->init_imap = entry;
+	daio_mgr->init_imap_added = 1;
+
+	daio_mgr->get_daio = get_daio_rsc;
+	daio_mgr->put_daio = put_daio_rsc;
+	daio_mgr->daio_enable = daio_mgr_enb_daio;
+	daio_mgr->daio_disable = daio_mgr_dsb_daio;
+	daio_mgr->imap_add = daio_imap_add;
+	daio_mgr->imap_delete = daio_imap_delete;
+	daio_mgr->commit_write = daio_mgr_commit_write;
+
+	for (i = 0; i < 8; i++) {
+		((struct hw *)hw)->daio_mgr_dsb_dao(daio_mgr->mgr.ctrl_blk, i);
+		((struct hw *)hw)->daio_mgr_dsb_dai(daio_mgr->mgr.ctrl_blk, i);
+	}
+	((struct hw *)hw)->daio_mgr_commit_write(hw, daio_mgr->mgr.ctrl_blk);
+
+	*rdaio_mgr = daio_mgr;
+
+	return 0;
+
+error2:
+	rsc_mgr_uninit(&daio_mgr->mgr);
+error1:
+	kfree(daio_mgr);
+	return err;
+}
+
+int daio_mgr_destroy(struct daio_mgr *daio_mgr)
+{
+	unsigned long flags;
+
+	/* free daio input mapper list */
+	spin_lock_irqsave(&daio_mgr->imap_lock, flags);
+	free_input_mapper_list(&daio_mgr->imappers);
+	spin_unlock_irqrestore(&daio_mgr->imap_lock, flags);
+
+	rsc_mgr_uninit(&daio_mgr->mgr);
+	kfree(daio_mgr);
+
+	return 0;
+}
+
diff --git a/linux/sound/pci/ctxfi/ctdaio.h b/linux/sound/pci/ctxfi/ctdaio.h
new file mode 100644
index 0000000..0f52ce5
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctdaio.h
@@ -0,0 +1,122 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctdaio.h
+ *
+ * @Brief
+ * This file contains the definition of Digital Audio Input Output
+ * resource management object.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 23 2008
+ *
+ */
+
+#ifndef CTDAIO_H
+#define CTDAIO_H
+
+#include "ctresource.h"
+#include "ctimap.h"
+#include <linux/spinlock.h>
+#include <linux/list.h>
+
+/* Define the descriptor of a daio resource */
+enum DAIOTYP {
+	LINEO1,
+	LINEO2,
+	LINEO3,
+	LINEO4,
+	SPDIFOO,	/* S/PDIF Out (Flexijack/Optical) */
+	LINEIM,
+	SPDIFIO,	/* S/PDIF In (Flexijack/Optical) on the card */
+	SPDIFI1,	/* S/PDIF In on internal Drive Bay */
+	NUM_DAIOTYP
+};
+
+struct dao_rsc_ops;
+struct dai_rsc_ops;
+struct daio_mgr;
+
+struct daio {
+	struct rsc rscl;	/* Basic resource info for left TX/RX */
+	struct rsc rscr;	/* Basic resource info for right TX/RX */
+	enum DAIOTYP type;
+};
+
+struct dao {
+	struct daio daio;
+	struct dao_rsc_ops *ops;	/* DAO specific operations */
+	struct imapper **imappers;
+	struct daio_mgr *mgr;
+	void *hw;
+	void *ctrl_blk;
+};
+
+struct dai {
+	struct daio daio;
+	struct dai_rsc_ops *ops;	/* DAI specific operations */
+	void *hw;
+	void *ctrl_blk;
+};
+
+struct dao_desc {
+	unsigned int msr:4;
+	unsigned int passthru:1;
+};
+
+struct dao_rsc_ops {
+	int (*set_spos)(struct dao *dao, unsigned int spos);
+	int (*commit_write)(struct dao *dao);
+	int (*get_spos)(struct dao *dao, unsigned int *spos);
+	int (*reinit)(struct dao *dao, const struct dao_desc *desc);
+	int (*set_left_input)(struct dao *dao, struct rsc *input);
+	int (*set_right_input)(struct dao *dao, struct rsc *input);
+	int (*clear_left_input)(struct dao *dao);
+	int (*clear_right_input)(struct dao *dao);
+};
+
+struct dai_rsc_ops {
+	int (*set_srt_srcl)(struct dai *dai, struct rsc *src);
+	int (*set_srt_srcr)(struct dai *dai, struct rsc *src);
+	int (*set_srt_msr)(struct dai *dai, unsigned int msr);
+	int (*set_enb_src)(struct dai *dai, unsigned int enb);
+	int (*set_enb_srt)(struct dai *dai, unsigned int enb);
+	int (*commit_write)(struct dai *dai);
+};
+
+/* Define daio resource request description info */
+struct daio_desc {
+	unsigned int type:4;
+	unsigned int msr:4;
+	unsigned int passthru:1;
+};
+
+struct daio_mgr {
+	struct rsc_mgr mgr;	/* Basic resource manager info */
+	spinlock_t mgr_lock;
+	spinlock_t imap_lock;
+	struct list_head imappers;
+	struct imapper *init_imap;
+	unsigned int init_imap_added;
+
+	 /* request one daio resource */
+	int (*get_daio)(struct daio_mgr *mgr,
+			const struct daio_desc *desc, struct daio **rdaio);
+	/* return one daio resource */
+	int (*put_daio)(struct daio_mgr *mgr, struct daio *daio);
+	int (*daio_enable)(struct daio_mgr *mgr, struct daio *daio);
+	int (*daio_disable)(struct daio_mgr *mgr, struct daio *daio);
+	int (*imap_add)(struct daio_mgr *mgr, struct imapper *entry);
+	int (*imap_delete)(struct daio_mgr *mgr, struct imapper *entry);
+	int (*commit_write)(struct daio_mgr *mgr);
+};
+
+/* Constructor and destructor of daio resource manager */
+int daio_mgr_create(void *hw, struct daio_mgr **rdaio_mgr);
+int daio_mgr_destroy(struct daio_mgr *daio_mgr);
+
+#endif /* CTDAIO_H */
diff --git a/linux/sound/pci/ctxfi/cthardware.c b/linux/sound/pci/ctxfi/cthardware.c
new file mode 100644
index 0000000..8e64f48
--- /dev/null
+++ b/linux/sound/pci/ctxfi/cthardware.c
@@ -0,0 +1,91 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	cthardware.c
+ *
+ * @Brief
+ * This file contains the implementation of hardware access methord.
+ *
+ * @Author	Liu Chun
+ * @Date 	Jun 26 2008
+ *
+ */
+
+#include "cthardware.h"
+#include "cthw20k1.h"
+#include "cthw20k2.h"
+#include <linux/bug.h>
+
+int __devinit create_hw_obj(struct pci_dev *pci, enum CHIPTYP chip_type,
+			    enum CTCARDS model, struct hw **rhw)
+{
+	int err;
+
+	switch (chip_type) {
+	case ATC20K1:
+		err = create_20k1_hw_obj(rhw);
+		break;
+	case ATC20K2:
+		err = create_20k2_hw_obj(rhw);
+		break;
+	default:
+		err = -ENODEV;
+		break;
+	}
+	if (err)
+		return err;
+
+	(*rhw)->pci = pci;
+	(*rhw)->chip_type = chip_type;
+	(*rhw)->model = model;
+
+	return 0;
+}
+
+int destroy_hw_obj(struct hw *hw)
+{
+	int err;
+
+	switch (hw->pci->device) {
+	case 0x0005:	/* 20k1 device */
+		err = destroy_20k1_hw_obj(hw);
+		break;
+	case 0x000B:	/* 20k2 device */
+		err = destroy_20k2_hw_obj(hw);
+		break;
+	default:
+		err = -ENODEV;
+		break;
+	}
+
+	return err;
+}
+
+unsigned int get_field(unsigned int data, unsigned int field)
+{
+	int i;
+
+	BUG_ON(!field);
+	/* @field should always be greater than 0 */
+	for (i = 0; !(field & (1 << i)); )
+		i++;
+
+	return (data & field) >> i;
+}
+
+void set_field(unsigned int *data, unsigned int field, unsigned int value)
+{
+	int i;
+
+	BUG_ON(!field);
+	/* @field should always be greater than 0 */
+	for (i = 0; !(field & (1 << i)); )
+		i++;
+
+	*data = (*data & (~field)) | ((value << i) & field);
+}
+
diff --git a/linux/sound/pci/ctxfi/cthardware.h b/linux/sound/pci/ctxfi/cthardware.h
new file mode 100644
index 0000000..af55405
--- /dev/null
+++ b/linux/sound/pci/ctxfi/cthardware.h
@@ -0,0 +1,203 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	cthardware.h
+ *
+ * @Brief
+ * This file contains the definition of hardware access methord.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 13 2008
+ *
+ */
+
+#ifndef CTHARDWARE_H
+#define CTHARDWARE_H
+
+#include <linux/types.h>
+#include <linux/pci.h>
+
+enum CHIPTYP {
+	ATC20K1,
+	ATC20K2,
+	ATCNONE
+};
+
+enum CTCARDS {
+	/* 20k1 models */
+	CTSB055X,
+	CT20K1_MODEL_FIRST = CTSB055X,
+	CTSB073X,
+	CTUAA,
+	CT20K1_UNKNOWN,
+	/* 20k2 models */
+	CTSB0760,
+	CT20K2_MODEL_FIRST = CTSB0760,
+	CTHENDRIX,
+	CTSB0880,
+	CT20K2_UNKNOWN,
+	NUM_CTCARDS		/* This should always be the last */
+};
+
+/* Type of input source for ADC */
+enum ADCSRC{
+	ADC_MICIN,
+	ADC_LINEIN,
+	ADC_VIDEO,
+	ADC_AUX,
+	ADC_NONE	/* Switch to digital input */
+};
+
+struct card_conf {
+	/* device virtual mem page table page physical addr
+	 * (supporting one page table page now) */
+	unsigned long vm_pgt_phys;
+	unsigned int rsr;	/* reference sample rate in Hzs*/
+	unsigned int msr;	/* master sample rate in rsrs */
+};
+
+struct hw {
+	int (*card_init)(struct hw *hw, struct card_conf *info);
+	int (*card_stop)(struct hw *hw);
+	int (*pll_init)(struct hw *hw, unsigned int rsr);
+#ifdef CONFIG_PM
+	int (*suspend)(struct hw *hw, pm_message_t state);
+	int (*resume)(struct hw *hw, struct card_conf *info);
+#endif
+	int (*is_adc_source_selected)(struct hw *hw, enum ADCSRC source);
+	int (*select_adc_source)(struct hw *hw, enum ADCSRC source);
+	int (*have_digit_io_switch)(struct hw *hw);
+
+	/* SRC operations */
+	int (*src_rsc_get_ctrl_blk)(void **rblk);
+	int (*src_rsc_put_ctrl_blk)(void *blk);
+	int (*src_set_state)(void *blk, unsigned int state);
+	int (*src_set_bm)(void *blk, unsigned int bm);
+	int (*src_set_rsr)(void *blk, unsigned int rsr);
+	int (*src_set_sf)(void *blk, unsigned int sf);
+	int (*src_set_wr)(void *blk, unsigned int wr);
+	int (*src_set_pm)(void *blk, unsigned int pm);
+	int (*src_set_rom)(void *blk, unsigned int rom);
+	int (*src_set_vo)(void *blk, unsigned int vo);
+	int (*src_set_st)(void *blk, unsigned int st);
+	int (*src_set_ie)(void *blk, unsigned int ie);
+	int (*src_set_ilsz)(void *blk, unsigned int ilsz);
+	int (*src_set_bp)(void *blk, unsigned int bp);
+	int (*src_set_cisz)(void *blk, unsigned int cisz);
+	int (*src_set_ca)(void *blk, unsigned int ca);
+	int (*src_set_sa)(void *blk, unsigned int sa);
+	int (*src_set_la)(void *blk, unsigned int la);
+	int (*src_set_pitch)(void *blk, unsigned int pitch);
+	int (*src_set_clear_zbufs)(void *blk, unsigned int clear);
+	int (*src_set_dirty)(void *blk, unsigned int flags);
+	int (*src_set_dirty_all)(void *blk);
+	int (*src_commit_write)(struct hw *hw, unsigned int idx, void *blk);
+	int (*src_get_ca)(struct hw *hw, unsigned int idx, void *blk);
+	unsigned int (*src_get_dirty)(void *blk);
+	unsigned int (*src_dirty_conj_mask)(void);
+	int (*src_mgr_get_ctrl_blk)(void **rblk);
+	int (*src_mgr_put_ctrl_blk)(void *blk);
+	/* syncly enable src @idx */
+	int (*src_mgr_enbs_src)(void *blk, unsigned int idx);
+	/* enable src @idx */
+	int (*src_mgr_enb_src)(void *blk, unsigned int idx);
+	/* disable src @idx */
+	int (*src_mgr_dsb_src)(void *blk, unsigned int idx);
+	int (*src_mgr_commit_write)(struct hw *hw, void *blk);
+
+	/* SRC Input Mapper operations */
+	int (*srcimp_mgr_get_ctrl_blk)(void **rblk);
+	int (*srcimp_mgr_put_ctrl_blk)(void *blk);
+	int (*srcimp_mgr_set_imaparc)(void *blk, unsigned int slot);
+	int (*srcimp_mgr_set_imapuser)(void *blk, unsigned int user);
+	int (*srcimp_mgr_set_imapnxt)(void *blk, unsigned int next);
+	int (*srcimp_mgr_set_imapaddr)(void *blk, unsigned int addr);
+	int (*srcimp_mgr_commit_write)(struct hw *hw, void *blk);
+
+	/* AMIXER operations */
+	int (*amixer_rsc_get_ctrl_blk)(void **rblk);
+	int (*amixer_rsc_put_ctrl_blk)(void *blk);
+	int (*amixer_mgr_get_ctrl_blk)(void **rblk);
+	int (*amixer_mgr_put_ctrl_blk)(void *blk);
+	int (*amixer_set_mode)(void *blk, unsigned int mode);
+	int (*amixer_set_iv)(void *blk, unsigned int iv);
+	int (*amixer_set_x)(void *blk, unsigned int x);
+	int (*amixer_set_y)(void *blk, unsigned int y);
+	int (*amixer_set_sadr)(void *blk, unsigned int sadr);
+	int (*amixer_set_se)(void *blk, unsigned int se);
+	int (*amixer_set_dirty)(void *blk, unsigned int flags);
+	int (*amixer_set_dirty_all)(void *blk);
+	int (*amixer_commit_write)(struct hw *hw, unsigned int idx, void *blk);
+	int (*amixer_get_y)(void *blk);
+	unsigned int (*amixer_get_dirty)(void *blk);
+
+	/* DAIO operations */
+	int (*dai_get_ctrl_blk)(void **rblk);
+	int (*dai_put_ctrl_blk)(void *blk);
+	int (*dai_srt_set_srco)(void *blk, unsigned int src);
+	int (*dai_srt_set_srcm)(void *blk, unsigned int src);
+	int (*dai_srt_set_rsr)(void *blk, unsigned int rsr);
+	int (*dai_srt_set_drat)(void *blk, unsigned int drat);
+	int (*dai_srt_set_ec)(void *blk, unsigned int ec);
+	int (*dai_srt_set_et)(void *blk, unsigned int et);
+	int (*dai_commit_write)(struct hw *hw, unsigned int idx, void *blk);
+	int (*dao_get_ctrl_blk)(void **rblk);
+	int (*dao_put_ctrl_blk)(void *blk);
+	int (*dao_set_spos)(void *blk, unsigned int spos);
+	int (*dao_commit_write)(struct hw *hw, unsigned int idx, void *blk);
+	int (*dao_get_spos)(void *blk, unsigned int *spos);
+
+	int (*daio_mgr_get_ctrl_blk)(struct hw *hw, void **rblk);
+	int (*daio_mgr_put_ctrl_blk)(void *blk);
+	int (*daio_mgr_enb_dai)(void *blk, unsigned int idx);
+	int (*daio_mgr_dsb_dai)(void *blk, unsigned int idx);
+	int (*daio_mgr_enb_dao)(void *blk, unsigned int idx);
+	int (*daio_mgr_dsb_dao)(void *blk, unsigned int idx);
+	int (*daio_mgr_dao_init)(void *blk, unsigned int idx,
+						unsigned int conf);
+	int (*daio_mgr_set_imaparc)(void *blk, unsigned int slot);
+	int (*daio_mgr_set_imapnxt)(void *blk, unsigned int next);
+	int (*daio_mgr_set_imapaddr)(void *blk, unsigned int addr);
+	int (*daio_mgr_commit_write)(struct hw *hw, void *blk);
+
+	int (*set_timer_irq)(struct hw *hw, int enable);
+	int (*set_timer_tick)(struct hw *hw, unsigned int tick);
+	unsigned int (*get_wc)(struct hw *hw);
+
+	void (*irq_callback)(void *data, unsigned int bit);
+	void *irq_callback_data;
+
+	struct pci_dev *pci;	/* the pci kernel structure of this card */
+	int irq;
+	unsigned long io_base;
+	unsigned long mem_base;
+
+	enum CHIPTYP chip_type;
+	enum CTCARDS model;
+};
+
+int create_hw_obj(struct pci_dev *pci, enum CHIPTYP chip_type,
+		  enum CTCARDS model, struct hw **rhw);
+int destroy_hw_obj(struct hw *hw);
+
+unsigned int get_field(unsigned int data, unsigned int field);
+void set_field(unsigned int *data, unsigned int field, unsigned int value);
+
+/* IRQ bits */
+#define	PLL_INT		(1 << 10) /* PLL input-clock out-of-range */
+#define FI_INT		(1 << 9)  /* forced interrupt */
+#define IT_INT		(1 << 8)  /* timer interrupt */
+#define PCI_INT		(1 << 7)  /* PCI bus error pending */
+#define URT_INT		(1 << 6)  /* UART Tx/Rx */
+#define GPI_INT		(1 << 5)  /* GPI pin */
+#define MIX_INT		(1 << 4)  /* mixer parameter segment FIFO channels */
+#define DAI_INT		(1 << 3)  /* DAI (SR-tracker or SPDIF-receiver) */
+#define TP_INT		(1 << 2)  /* transport priority queue */
+#define DSP_INT		(1 << 1)  /* DSP */
+#define SRC_INT		(1 << 0)  /* SRC channels */
+
+#endif /* CTHARDWARE_H */
diff --git a/linux/sound/pci/ctxfi/cthw20k1.c b/linux/sound/pci/ctxfi/cthw20k1.c
new file mode 100644
index 0000000..0cf400f
--- /dev/null
+++ b/linux/sound/pci/ctxfi/cthw20k1.c
@@ -0,0 +1,2297 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	cthw20k1.c
+ *
+ * @Brief
+ * This file contains the implementation of hardware access methord for 20k1.
+ *
+ * @Author	Liu Chun
+ * @Date 	Jun 24 2008
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include "cthw20k1.h"
+#include "ct20k1reg.h"
+
+#if BITS_PER_LONG == 32
+#define CT_XFI_DMA_MASK		DMA_BIT_MASK(32) /* 32 bit PTE */
+#else
+#define CT_XFI_DMA_MASK		DMA_BIT_MASK(64) /* 64 bit PTE */
+#endif
+
+struct hw20k1 {
+	struct hw hw;
+	spinlock_t reg_20k1_lock;
+	spinlock_t reg_pci_lock;
+};
+
+static u32 hw_read_20kx(struct hw *hw, u32 reg);
+static void hw_write_20kx(struct hw *hw, u32 reg, u32 data);
+static u32 hw_read_pci(struct hw *hw, u32 reg);
+static void hw_write_pci(struct hw *hw, u32 reg, u32 data);
+
+/*
+ * Type definition block.
+ * The layout of control structures can be directly applied on 20k2 chip.
+ */
+
+/*
+ * SRC control block definitions.
+ */
+
+/* SRC resource control block */
+#define SRCCTL_STATE	0x00000007
+#define SRCCTL_BM	0x00000008
+#define SRCCTL_RSR	0x00000030
+#define SRCCTL_SF	0x000001C0
+#define SRCCTL_WR	0x00000200
+#define SRCCTL_PM	0x00000400
+#define SRCCTL_ROM	0x00001800
+#define SRCCTL_VO	0x00002000
+#define SRCCTL_ST	0x00004000
+#define SRCCTL_IE	0x00008000
+#define SRCCTL_ILSZ	0x000F0000
+#define SRCCTL_BP	0x00100000
+
+#define SRCCCR_CISZ	0x000007FF
+#define SRCCCR_CWA	0x001FF800
+#define SRCCCR_D	0x00200000
+#define SRCCCR_RS	0x01C00000
+#define SRCCCR_NAL	0x3E000000
+#define SRCCCR_RA	0xC0000000
+
+#define SRCCA_CA	0x03FFFFFF
+#define SRCCA_RS	0x1C000000
+#define SRCCA_NAL	0xE0000000
+
+#define SRCSA_SA	0x03FFFFFF
+
+#define SRCLA_LA	0x03FFFFFF
+
+/* Mixer Parameter Ring ram Low and Hight register.
+ * Fixed-point value in 8.24 format for parameter channel */
+#define MPRLH_PITCH	0xFFFFFFFF
+
+/* SRC resource register dirty flags */
+union src_dirty {
+	struct {
+		u16 ctl:1;
+		u16 ccr:1;
+		u16 sa:1;
+		u16 la:1;
+		u16 ca:1;
+		u16 mpr:1;
+		u16 czbfs:1;	/* Clear Z-Buffers */
+		u16 rsv:9;
+	} bf;
+	u16 data;
+};
+
+struct src_rsc_ctrl_blk {
+	unsigned int	ctl;
+	unsigned int 	ccr;
+	unsigned int	ca;
+	unsigned int	sa;
+	unsigned int	la;
+	unsigned int	mpr;
+	union src_dirty	dirty;
+};
+
+/* SRC manager control block */
+union src_mgr_dirty {
+	struct {
+		u16 enb0:1;
+		u16 enb1:1;
+		u16 enb2:1;
+		u16 enb3:1;
+		u16 enb4:1;
+		u16 enb5:1;
+		u16 enb6:1;
+		u16 enb7:1;
+		u16 enbsa:1;
+		u16 rsv:7;
+	} bf;
+	u16 data;
+};
+
+struct src_mgr_ctrl_blk {
+	unsigned int		enbsa;
+	unsigned int		enb[8];
+	union src_mgr_dirty	dirty;
+};
+
+/* SRCIMP manager control block */
+#define SRCAIM_ARC	0x00000FFF
+#define SRCAIM_NXT	0x00FF0000
+#define SRCAIM_SRC	0xFF000000
+
+struct srcimap {
+	unsigned int srcaim;
+	unsigned int idx;
+};
+
+/* SRCIMP manager register dirty flags */
+union srcimp_mgr_dirty {
+	struct {
+		u16 srcimap:1;
+		u16 rsv:15;
+	} bf;
+	u16 data;
+};
+
+struct srcimp_mgr_ctrl_blk {
+	struct srcimap		srcimap;
+	union srcimp_mgr_dirty	dirty;
+};
+
+/*
+ * Function implementation block.
+ */
+
+static int src_get_rsc_ctrl_blk(void **rblk)
+{
+	struct src_rsc_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int src_put_rsc_ctrl_blk(void *blk)
+{
+	kfree((struct src_rsc_ctrl_blk *)blk);
+
+	return 0;
+}
+
+static int src_set_state(void *blk, unsigned int state)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_STATE, state);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_bm(void *blk, unsigned int bm)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_BM, bm);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_rsr(void *blk, unsigned int rsr)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_RSR, rsr);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_sf(void *blk, unsigned int sf)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_SF, sf);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_wr(void *blk, unsigned int wr)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_WR, wr);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_pm(void *blk, unsigned int pm)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_PM, pm);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_rom(void *blk, unsigned int rom)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_ROM, rom);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_vo(void *blk, unsigned int vo)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_VO, vo);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_st(void *blk, unsigned int st)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_ST, st);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_ie(void *blk, unsigned int ie)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_IE, ie);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_ilsz(void *blk, unsigned int ilsz)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_ILSZ, ilsz);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_bp(void *blk, unsigned int bp)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_BP, bp);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_cisz(void *blk, unsigned int cisz)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ccr, SRCCCR_CISZ, cisz);
+	ctl->dirty.bf.ccr = 1;
+	return 0;
+}
+
+static int src_set_ca(void *blk, unsigned int ca)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ca, SRCCA_CA, ca);
+	ctl->dirty.bf.ca = 1;
+	return 0;
+}
+
+static int src_set_sa(void *blk, unsigned int sa)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->sa, SRCSA_SA, sa);
+	ctl->dirty.bf.sa = 1;
+	return 0;
+}
+
+static int src_set_la(void *blk, unsigned int la)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->la, SRCLA_LA, la);
+	ctl->dirty.bf.la = 1;
+	return 0;
+}
+
+static int src_set_pitch(void *blk, unsigned int pitch)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->mpr, MPRLH_PITCH, pitch);
+	ctl->dirty.bf.mpr = 1;
+	return 0;
+}
+
+static int src_set_clear_zbufs(void *blk, unsigned int clear)
+{
+	((struct src_rsc_ctrl_blk *)blk)->dirty.bf.czbfs = (clear ? 1 : 0);
+	return 0;
+}
+
+static int src_set_dirty(void *blk, unsigned int flags)
+{
+	((struct src_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff);
+	return 0;
+}
+
+static int src_set_dirty_all(void *blk)
+{
+	((struct src_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0);
+	return 0;
+}
+
+#define AR_SLOT_SIZE		4096
+#define AR_SLOT_BLOCK_SIZE	16
+#define AR_PTS_PITCH		6
+#define AR_PARAM_SRC_OFFSET	0x60
+
+static unsigned int src_param_pitch_mixer(unsigned int src_idx)
+{
+	return ((src_idx << 4) + AR_PTS_PITCH + AR_SLOT_SIZE
+			- AR_PARAM_SRC_OFFSET) % AR_SLOT_SIZE;
+
+}
+
+static int src_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+	int i;
+
+	if (ctl->dirty.bf.czbfs) {
+		/* Clear Z-Buffer registers */
+		for (i = 0; i < 8; i++)
+			hw_write_20kx(hw, SRCUPZ+idx*0x100+i*0x4, 0);
+
+		for (i = 0; i < 4; i++)
+			hw_write_20kx(hw, SRCDN0Z+idx*0x100+i*0x4, 0);
+
+		for (i = 0; i < 8; i++)
+			hw_write_20kx(hw, SRCDN1Z+idx*0x100+i*0x4, 0);
+
+		ctl->dirty.bf.czbfs = 0;
+	}
+	if (ctl->dirty.bf.mpr) {
+		/* Take the parameter mixer resource in the same group as that
+		 * the idx src is in for simplicity. Unlike src, all conjugate
+		 * parameter mixer resources must be programmed for
+		 * corresponding conjugate src resources. */
+		unsigned int pm_idx = src_param_pitch_mixer(idx);
+		hw_write_20kx(hw, PRING_LO_HI+4*pm_idx, ctl->mpr);
+		hw_write_20kx(hw, PMOPLO+8*pm_idx, 0x3);
+		hw_write_20kx(hw, PMOPHI+8*pm_idx, 0x0);
+		ctl->dirty.bf.mpr = 0;
+	}
+	if (ctl->dirty.bf.sa) {
+		hw_write_20kx(hw, SRCSA+idx*0x100, ctl->sa);
+		ctl->dirty.bf.sa = 0;
+	}
+	if (ctl->dirty.bf.la) {
+		hw_write_20kx(hw, SRCLA+idx*0x100, ctl->la);
+		ctl->dirty.bf.la = 0;
+	}
+	if (ctl->dirty.bf.ca) {
+		hw_write_20kx(hw, SRCCA+idx*0x100, ctl->ca);
+		ctl->dirty.bf.ca = 0;
+	}
+
+	/* Write srccf register */
+	hw_write_20kx(hw, SRCCF+idx*0x100, 0x0);
+
+	if (ctl->dirty.bf.ccr) {
+		hw_write_20kx(hw, SRCCCR+idx*0x100, ctl->ccr);
+		ctl->dirty.bf.ccr = 0;
+	}
+	if (ctl->dirty.bf.ctl) {
+		hw_write_20kx(hw, SRCCTL+idx*0x100, ctl->ctl);
+		ctl->dirty.bf.ctl = 0;
+	}
+
+	return 0;
+}
+
+static int src_get_ca(struct hw *hw, unsigned int idx, void *blk)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	ctl->ca = hw_read_20kx(hw, SRCCA+idx*0x100);
+	ctl->dirty.bf.ca = 0;
+
+	return get_field(ctl->ca, SRCCA_CA);
+}
+
+static unsigned int src_get_dirty(void *blk)
+{
+	return ((struct src_rsc_ctrl_blk *)blk)->dirty.data;
+}
+
+static unsigned int src_dirty_conj_mask(void)
+{
+	return 0x20;
+}
+
+static int src_mgr_enbs_src(void *blk, unsigned int idx)
+{
+	((struct src_mgr_ctrl_blk *)blk)->enbsa = ~(0x0);
+	((struct src_mgr_ctrl_blk *)blk)->dirty.bf.enbsa = 1;
+	((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32));
+	return 0;
+}
+
+static int src_mgr_enb_src(void *blk, unsigned int idx)
+{
+	((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32));
+	((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32));
+	return 0;
+}
+
+static int src_mgr_dsb_src(void *blk, unsigned int idx)
+{
+	((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] &= ~(0x1 << (idx%32));
+	((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32));
+	return 0;
+}
+
+static int src_mgr_commit_write(struct hw *hw, void *blk)
+{
+	struct src_mgr_ctrl_blk *ctl = blk;
+	int i;
+	unsigned int ret;
+
+	if (ctl->dirty.bf.enbsa) {
+		do {
+			ret = hw_read_20kx(hw, SRCENBSTAT);
+		} while (ret & 0x1);
+		hw_write_20kx(hw, SRCENBS, ctl->enbsa);
+		ctl->dirty.bf.enbsa = 0;
+	}
+	for (i = 0; i < 8; i++) {
+		if ((ctl->dirty.data & (0x1 << i))) {
+			hw_write_20kx(hw, SRCENB+(i*0x100), ctl->enb[i]);
+			ctl->dirty.data &= ~(0x1 << i);
+		}
+	}
+
+	return 0;
+}
+
+static int src_mgr_get_ctrl_blk(void **rblk)
+{
+	struct src_mgr_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int src_mgr_put_ctrl_blk(void *blk)
+{
+	kfree((struct src_mgr_ctrl_blk *)blk);
+
+	return 0;
+}
+
+static int srcimp_mgr_get_ctrl_blk(void **rblk)
+{
+	struct srcimp_mgr_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int srcimp_mgr_put_ctrl_blk(void *blk)
+{
+	kfree((struct srcimp_mgr_ctrl_blk *)blk);
+
+	return 0;
+}
+
+static int srcimp_mgr_set_imaparc(void *blk, unsigned int slot)
+{
+	struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srcimap.srcaim, SRCAIM_ARC, slot);
+	ctl->dirty.bf.srcimap = 1;
+	return 0;
+}
+
+static int srcimp_mgr_set_imapuser(void *blk, unsigned int user)
+{
+	struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srcimap.srcaim, SRCAIM_SRC, user);
+	ctl->dirty.bf.srcimap = 1;
+	return 0;
+}
+
+static int srcimp_mgr_set_imapnxt(void *blk, unsigned int next)
+{
+	struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srcimap.srcaim, SRCAIM_NXT, next);
+	ctl->dirty.bf.srcimap = 1;
+	return 0;
+}
+
+static int srcimp_mgr_set_imapaddr(void *blk, unsigned int addr)
+{
+	struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+	ctl->srcimap.idx = addr;
+	ctl->dirty.bf.srcimap = 1;
+	return 0;
+}
+
+static int srcimp_mgr_commit_write(struct hw *hw, void *blk)
+{
+	struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+	if (ctl->dirty.bf.srcimap) {
+		hw_write_20kx(hw, SRCIMAP+ctl->srcimap.idx*0x100,
+						ctl->srcimap.srcaim);
+		ctl->dirty.bf.srcimap = 0;
+	}
+
+	return 0;
+}
+
+/*
+ * AMIXER control block definitions.
+ */
+
+#define AMOPLO_M	0x00000003
+#define AMOPLO_X	0x0003FFF0
+#define AMOPLO_Y	0xFFFC0000
+
+#define AMOPHI_SADR	0x000000FF
+#define AMOPHI_SE	0x80000000
+
+/* AMIXER resource register dirty flags */
+union amixer_dirty {
+	struct {
+		u16 amoplo:1;
+		u16 amophi:1;
+		u16 rsv:14;
+	} bf;
+	u16 data;
+};
+
+/* AMIXER resource control block */
+struct amixer_rsc_ctrl_blk {
+	unsigned int		amoplo;
+	unsigned int		amophi;
+	union amixer_dirty	dirty;
+};
+
+static int amixer_set_mode(void *blk, unsigned int mode)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->amoplo, AMOPLO_M, mode);
+	ctl->dirty.bf.amoplo = 1;
+	return 0;
+}
+
+static int amixer_set_iv(void *blk, unsigned int iv)
+{
+	/* 20k1 amixer does not have this field */
+	return 0;
+}
+
+static int amixer_set_x(void *blk, unsigned int x)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->amoplo, AMOPLO_X, x);
+	ctl->dirty.bf.amoplo = 1;
+	return 0;
+}
+
+static int amixer_set_y(void *blk, unsigned int y)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->amoplo, AMOPLO_Y, y);
+	ctl->dirty.bf.amoplo = 1;
+	return 0;
+}
+
+static int amixer_set_sadr(void *blk, unsigned int sadr)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->amophi, AMOPHI_SADR, sadr);
+	ctl->dirty.bf.amophi = 1;
+	return 0;
+}
+
+static int amixer_set_se(void *blk, unsigned int se)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->amophi, AMOPHI_SE, se);
+	ctl->dirty.bf.amophi = 1;
+	return 0;
+}
+
+static int amixer_set_dirty(void *blk, unsigned int flags)
+{
+	((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff);
+	return 0;
+}
+
+static int amixer_set_dirty_all(void *blk)
+{
+	((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0);
+	return 0;
+}
+
+static int amixer_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	if (ctl->dirty.bf.amoplo || ctl->dirty.bf.amophi) {
+		hw_write_20kx(hw, AMOPLO+idx*8, ctl->amoplo);
+		ctl->dirty.bf.amoplo = 0;
+		hw_write_20kx(hw, AMOPHI+idx*8, ctl->amophi);
+		ctl->dirty.bf.amophi = 0;
+	}
+
+	return 0;
+}
+
+static int amixer_get_y(void *blk)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	return get_field(ctl->amoplo, AMOPLO_Y);
+}
+
+static unsigned int amixer_get_dirty(void *blk)
+{
+	return ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data;
+}
+
+static int amixer_rsc_get_ctrl_blk(void **rblk)
+{
+	struct amixer_rsc_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int amixer_rsc_put_ctrl_blk(void *blk)
+{
+	kfree((struct amixer_rsc_ctrl_blk *)blk);
+
+	return 0;
+}
+
+static int amixer_mgr_get_ctrl_blk(void **rblk)
+{
+	/*amixer_mgr_ctrl_blk_t *blk;*/
+
+	*rblk = NULL;
+	/*blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;*/
+
+	return 0;
+}
+
+static int amixer_mgr_put_ctrl_blk(void *blk)
+{
+	/*kfree((amixer_mgr_ctrl_blk_t *)blk);*/
+
+	return 0;
+}
+
+/*
+ * DAIO control block definitions.
+ */
+
+/* Receiver Sample Rate Tracker Control register */
+#define SRTCTL_SRCR	0x000000FF
+#define SRTCTL_SRCL	0x0000FF00
+#define SRTCTL_RSR	0x00030000
+#define SRTCTL_DRAT	0x000C0000
+#define SRTCTL_RLE	0x10000000
+#define SRTCTL_RLP	0x20000000
+#define SRTCTL_EC	0x40000000
+#define SRTCTL_ET	0x80000000
+
+/* DAIO Receiver register dirty flags */
+union dai_dirty {
+	struct {
+		u16 srtctl:1;
+		u16 rsv:15;
+	} bf;
+	u16 data;
+};
+
+/* DAIO Receiver control block */
+struct dai_ctrl_blk {
+	unsigned int	srtctl;
+	union dai_dirty	dirty;
+};
+
+/* S/PDIF Transmitter register dirty flags */
+union dao_dirty {
+	struct {
+		u16 spos:1;
+		u16 rsv:15;
+	} bf;
+	u16 data;
+};
+
+/* S/PDIF Transmitter control block */
+struct dao_ctrl_blk {
+	unsigned int 	spos; /* S/PDIF Output Channel Status Register */
+	union dao_dirty	dirty;
+};
+
+/* Audio Input Mapper RAM */
+#define AIM_ARC		0x00000FFF
+#define AIM_NXT		0x007F0000
+
+struct daoimap {
+	unsigned int aim;
+	unsigned int idx;
+};
+
+/* I2S Transmitter/Receiver Control register */
+#define I2SCTL_EA	0x00000004
+#define I2SCTL_EI	0x00000010
+
+/* S/PDIF Transmitter Control register */
+#define SPOCTL_OE	0x00000001
+#define SPOCTL_OS	0x0000000E
+#define SPOCTL_RIV	0x00000010
+#define SPOCTL_LIV	0x00000020
+#define SPOCTL_SR	0x000000C0
+
+/* S/PDIF Receiver Control register */
+#define SPICTL_EN	0x00000001
+#define SPICTL_I24	0x00000002
+#define SPICTL_IB	0x00000004
+#define SPICTL_SM	0x00000008
+#define SPICTL_VM	0x00000010
+
+/* DAIO manager register dirty flags */
+union daio_mgr_dirty {
+	struct {
+		u32 i2soctl:4;
+		u32 i2sictl:4;
+		u32 spoctl:4;
+		u32 spictl:4;
+		u32 daoimap:1;
+		u32 rsv:15;
+	} bf;
+	u32 data;
+};
+
+/* DAIO manager control block */
+struct daio_mgr_ctrl_blk {
+	unsigned int		i2sctl;
+	unsigned int		spoctl;
+	unsigned int		spictl;
+	struct daoimap		daoimap;
+	union daio_mgr_dirty	dirty;
+};
+
+static int dai_srt_set_srcr(void *blk, unsigned int src)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srtctl, SRTCTL_SRCR, src);
+	ctl->dirty.bf.srtctl = 1;
+	return 0;
+}
+
+static int dai_srt_set_srcl(void *blk, unsigned int src)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srtctl, SRTCTL_SRCL, src);
+	ctl->dirty.bf.srtctl = 1;
+	return 0;
+}
+
+static int dai_srt_set_rsr(void *blk, unsigned int rsr)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srtctl, SRTCTL_RSR, rsr);
+	ctl->dirty.bf.srtctl = 1;
+	return 0;
+}
+
+static int dai_srt_set_drat(void *blk, unsigned int drat)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srtctl, SRTCTL_DRAT, drat);
+	ctl->dirty.bf.srtctl = 1;
+	return 0;
+}
+
+static int dai_srt_set_ec(void *blk, unsigned int ec)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srtctl, SRTCTL_EC, ec ? 1 : 0);
+	ctl->dirty.bf.srtctl = 1;
+	return 0;
+}
+
+static int dai_srt_set_et(void *blk, unsigned int et)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srtctl, SRTCTL_ET, et ? 1 : 0);
+	ctl->dirty.bf.srtctl = 1;
+	return 0;
+}
+
+static int dai_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	if (ctl->dirty.bf.srtctl) {
+		if (idx < 4) {
+			/* S/PDIF SRTs */
+			hw_write_20kx(hw, SRTSCTL+0x4*idx, ctl->srtctl);
+		} else {
+			/* I2S SRT */
+			hw_write_20kx(hw, SRTICTL, ctl->srtctl);
+		}
+		ctl->dirty.bf.srtctl = 0;
+	}
+
+	return 0;
+}
+
+static int dai_get_ctrl_blk(void **rblk)
+{
+	struct dai_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int dai_put_ctrl_blk(void *blk)
+{
+	kfree((struct dai_ctrl_blk *)blk);
+
+	return 0;
+}
+
+static int dao_set_spos(void *blk, unsigned int spos)
+{
+	((struct dao_ctrl_blk *)blk)->spos = spos;
+	((struct dao_ctrl_blk *)blk)->dirty.bf.spos = 1;
+	return 0;
+}
+
+static int dao_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+	struct dao_ctrl_blk *ctl = blk;
+
+	if (ctl->dirty.bf.spos) {
+		if (idx < 4) {
+			/* S/PDIF SPOSx */
+			hw_write_20kx(hw, SPOS+0x4*idx, ctl->spos);
+		}
+		ctl->dirty.bf.spos = 0;
+	}
+
+	return 0;
+}
+
+static int dao_get_spos(void *blk, unsigned int *spos)
+{
+	*spos = ((struct dao_ctrl_blk *)blk)->spos;
+	return 0;
+}
+
+static int dao_get_ctrl_blk(void **rblk)
+{
+	struct dao_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int dao_put_ctrl_blk(void *blk)
+{
+	kfree((struct dao_ctrl_blk *)blk);
+
+	return 0;
+}
+
+static int daio_mgr_enb_dai(void *blk, unsigned int idx)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	if (idx < 4) {
+		/* S/PDIF input */
+		set_field(&ctl->spictl, SPICTL_EN << (idx*8), 1);
+		ctl->dirty.bf.spictl |= (0x1 << idx);
+	} else {
+		/* I2S input */
+		idx %= 4;
+		set_field(&ctl->i2sctl, I2SCTL_EI << (idx*8), 1);
+		ctl->dirty.bf.i2sictl |= (0x1 << idx);
+	}
+	return 0;
+}
+
+static int daio_mgr_dsb_dai(void *blk, unsigned int idx)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	if (idx < 4) {
+		/* S/PDIF input */
+		set_field(&ctl->spictl, SPICTL_EN << (idx*8), 0);
+		ctl->dirty.bf.spictl |= (0x1 << idx);
+	} else {
+		/* I2S input */
+		idx %= 4;
+		set_field(&ctl->i2sctl, I2SCTL_EI << (idx*8), 0);
+		ctl->dirty.bf.i2sictl |= (0x1 << idx);
+	}
+	return 0;
+}
+
+static int daio_mgr_enb_dao(void *blk, unsigned int idx)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	if (idx < 4) {
+		/* S/PDIF output */
+		set_field(&ctl->spoctl, SPOCTL_OE << (idx*8), 1);
+		ctl->dirty.bf.spoctl |= (0x1 << idx);
+	} else {
+		/* I2S output */
+		idx %= 4;
+		set_field(&ctl->i2sctl, I2SCTL_EA << (idx*8), 1);
+		ctl->dirty.bf.i2soctl |= (0x1 << idx);
+	}
+	return 0;
+}
+
+static int daio_mgr_dsb_dao(void *blk, unsigned int idx)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	if (idx < 4) {
+		/* S/PDIF output */
+		set_field(&ctl->spoctl, SPOCTL_OE << (idx*8), 0);
+		ctl->dirty.bf.spoctl |= (0x1 << idx);
+	} else {
+		/* I2S output */
+		idx %= 4;
+		set_field(&ctl->i2sctl, I2SCTL_EA << (idx*8), 0);
+		ctl->dirty.bf.i2soctl |= (0x1 << idx);
+	}
+	return 0;
+}
+
+static int daio_mgr_dao_init(void *blk, unsigned int idx, unsigned int conf)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	if (idx < 4) {
+		/* S/PDIF output */
+		switch ((conf & 0x7)) {
+		case 0:
+			set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 3);
+			break; /* CDIF */
+		case 1:
+			set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 0);
+			break;
+		case 2:
+			set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 1);
+			break;
+		case 4:
+			set_field(&ctl->spoctl, SPOCTL_SR << (idx*8), 2);
+			break;
+		default:
+			break;
+		}
+		set_field(&ctl->spoctl, SPOCTL_LIV << (idx*8),
+			  (conf >> 4) & 0x1); /* Non-audio */
+		set_field(&ctl->spoctl, SPOCTL_RIV << (idx*8),
+			  (conf >> 4) & 0x1); /* Non-audio */
+		set_field(&ctl->spoctl, SPOCTL_OS << (idx*8),
+			  ((conf >> 3) & 0x1) ? 2 : 2); /* Raw */
+
+		ctl->dirty.bf.spoctl |= (0x1 << idx);
+	} else {
+		/* I2S output */
+		/*idx %= 4; */
+	}
+	return 0;
+}
+
+static int daio_mgr_set_imaparc(void *blk, unsigned int slot)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->daoimap.aim, AIM_ARC, slot);
+	ctl->dirty.bf.daoimap = 1;
+	return 0;
+}
+
+static int daio_mgr_set_imapnxt(void *blk, unsigned int next)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->daoimap.aim, AIM_NXT, next);
+	ctl->dirty.bf.daoimap = 1;
+	return 0;
+}
+
+static int daio_mgr_set_imapaddr(void *blk, unsigned int addr)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	ctl->daoimap.idx = addr;
+	ctl->dirty.bf.daoimap = 1;
+	return 0;
+}
+
+static int daio_mgr_commit_write(struct hw *hw, void *blk)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+	int i;
+
+	if (ctl->dirty.bf.i2sictl || ctl->dirty.bf.i2soctl) {
+		for (i = 0; i < 4; i++) {
+			if ((ctl->dirty.bf.i2sictl & (0x1 << i)))
+				ctl->dirty.bf.i2sictl &= ~(0x1 << i);
+
+			if ((ctl->dirty.bf.i2soctl & (0x1 << i)))
+				ctl->dirty.bf.i2soctl &= ~(0x1 << i);
+		}
+		hw_write_20kx(hw, I2SCTL, ctl->i2sctl);
+		mdelay(1);
+	}
+	if (ctl->dirty.bf.spoctl) {
+		for (i = 0; i < 4; i++) {
+			if ((ctl->dirty.bf.spoctl & (0x1 << i)))
+				ctl->dirty.bf.spoctl &= ~(0x1 << i);
+		}
+		hw_write_20kx(hw, SPOCTL, ctl->spoctl);
+		mdelay(1);
+	}
+	if (ctl->dirty.bf.spictl) {
+		for (i = 0; i < 4; i++) {
+			if ((ctl->dirty.bf.spictl & (0x1 << i)))
+				ctl->dirty.bf.spictl &= ~(0x1 << i);
+		}
+		hw_write_20kx(hw, SPICTL, ctl->spictl);
+		mdelay(1);
+	}
+	if (ctl->dirty.bf.daoimap) {
+		hw_write_20kx(hw, DAOIMAP+ctl->daoimap.idx*4,
+					ctl->daoimap.aim);
+		ctl->dirty.bf.daoimap = 0;
+	}
+
+	return 0;
+}
+
+static int daio_mgr_get_ctrl_blk(struct hw *hw, void **rblk)
+{
+	struct daio_mgr_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	blk->i2sctl = hw_read_20kx(hw, I2SCTL);
+	blk->spoctl = hw_read_20kx(hw, SPOCTL);
+	blk->spictl = hw_read_20kx(hw, SPICTL);
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int daio_mgr_put_ctrl_blk(void *blk)
+{
+	kfree((struct daio_mgr_ctrl_blk *)blk);
+
+	return 0;
+}
+
+/* Timer interrupt */
+static int set_timer_irq(struct hw *hw, int enable)
+{
+	hw_write_20kx(hw, GIE, enable ? IT_INT : 0);
+	return 0;
+}
+
+static int set_timer_tick(struct hw *hw, unsigned int ticks)
+{
+	if (ticks)
+		ticks |= TIMR_IE | TIMR_IP;
+	hw_write_20kx(hw, TIMR, ticks);
+	return 0;
+}
+
+static unsigned int get_wc(struct hw *hw)
+{
+	return hw_read_20kx(hw, WC);
+}
+
+/* Card hardware initialization block */
+struct dac_conf {
+	unsigned int msr; /* master sample rate in rsrs */
+};
+
+struct adc_conf {
+	unsigned int msr; 	/* master sample rate in rsrs */
+	unsigned char input; 	/* the input source of ADC */
+	unsigned char mic20db; 	/* boost mic by 20db if input is microphone */
+};
+
+struct daio_conf {
+	unsigned int msr; /* master sample rate in rsrs */
+};
+
+struct trn_conf {
+	unsigned long vm_pgt_phys;
+};
+
+static int hw_daio_init(struct hw *hw, const struct daio_conf *info)
+{
+	u32 i2sorg;
+	u32 spdorg;
+
+	/* Read I2S CTL.  Keep original value. */
+	/*i2sorg = hw_read_20kx(hw, I2SCTL);*/
+	i2sorg = 0x94040404; /* enable all audio out and I2S-D input */
+	/* Program I2S with proper master sample rate and enable
+	 * the correct I2S channel. */
+	i2sorg &= 0xfffffffc;
+
+	/* Enable S/PDIF-out-A in fixed 24-bit data
+	 * format and default to 48kHz. */
+	/* Disable all before doing any changes. */
+	hw_write_20kx(hw, SPOCTL, 0x0);
+	spdorg = 0x05;
+
+	switch (info->msr) {
+	case 1:
+		i2sorg |= 1;
+		spdorg |= (0x0 << 6);
+		break;
+	case 2:
+		i2sorg |= 2;
+		spdorg |= (0x1 << 6);
+		break;
+	case 4:
+		i2sorg |= 3;
+		spdorg |= (0x2 << 6);
+		break;
+	default:
+		i2sorg |= 1;
+		break;
+	}
+
+	hw_write_20kx(hw, I2SCTL, i2sorg);
+	hw_write_20kx(hw, SPOCTL, spdorg);
+
+	/* Enable S/PDIF-in-A in fixed 24-bit data format. */
+	/* Disable all before doing any changes. */
+	hw_write_20kx(hw, SPICTL, 0x0);
+	mdelay(1);
+	spdorg = 0x0a0a0a0a;
+	hw_write_20kx(hw, SPICTL, spdorg);
+	mdelay(1);
+
+	return 0;
+}
+
+/* TRANSPORT operations */
+static int hw_trn_init(struct hw *hw, const struct trn_conf *info)
+{
+	u32 trnctl;
+	u32 ptp_phys_low, ptp_phys_high;
+
+	/* Set up device page table */
+	if ((~0UL) == info->vm_pgt_phys) {
+		printk(KERN_ERR "Wrong device page table page address!\n");
+		return -1;
+	}
+
+	trnctl = 0x13;  /* 32-bit, 4k-size page */
+	ptp_phys_low = (u32)info->vm_pgt_phys;
+	ptp_phys_high = upper_32_bits(info->vm_pgt_phys);
+	if (sizeof(void *) == 8) /* 64bit address */
+		trnctl |= (1 << 2);
+#if 0 /* Only 4k h/w pages for simplicitiy */
+#if PAGE_SIZE == 8192
+	trnctl |= (1<<5);
+#endif
+#endif
+	hw_write_20kx(hw, PTPALX, ptp_phys_low);
+	hw_write_20kx(hw, PTPAHX, ptp_phys_high);
+	hw_write_20kx(hw, TRNCTL, trnctl);
+	hw_write_20kx(hw, TRNIS, 0x200c01); /* realy needed? */
+
+	return 0;
+}
+
+/* Card initialization */
+#define GCTL_EAC	0x00000001
+#define GCTL_EAI	0x00000002
+#define GCTL_BEP	0x00000004
+#define GCTL_BES	0x00000008
+#define GCTL_DSP	0x00000010
+#define GCTL_DBP	0x00000020
+#define GCTL_ABP	0x00000040
+#define GCTL_TBP	0x00000080
+#define GCTL_SBP	0x00000100
+#define GCTL_FBP	0x00000200
+#define GCTL_XA		0x00000400
+#define GCTL_ET		0x00000800
+#define GCTL_PR		0x00001000
+#define GCTL_MRL	0x00002000
+#define GCTL_SDE	0x00004000
+#define GCTL_SDI	0x00008000
+#define GCTL_SM		0x00010000
+#define GCTL_SR		0x00020000
+#define GCTL_SD		0x00040000
+#define GCTL_SE		0x00080000
+#define GCTL_AID	0x00100000
+
+static int hw_pll_init(struct hw *hw, unsigned int rsr)
+{
+	unsigned int pllctl;
+	int i;
+
+	pllctl = (48000 == rsr) ? 0x1480a001 : 0x1480a731;
+	for (i = 0; i < 3; i++) {
+		if (hw_read_20kx(hw, PLLCTL) == pllctl)
+			break;
+
+		hw_write_20kx(hw, PLLCTL, pllctl);
+		mdelay(40);
+	}
+	if (i >= 3) {
+		printk(KERN_ALERT "PLL initialization failed!!!\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static int hw_auto_init(struct hw *hw)
+{
+	unsigned int gctl;
+	int i;
+
+	gctl = hw_read_20kx(hw, GCTL);
+	set_field(&gctl, GCTL_EAI, 0);
+	hw_write_20kx(hw, GCTL, gctl);
+	set_field(&gctl, GCTL_EAI, 1);
+	hw_write_20kx(hw, GCTL, gctl);
+	mdelay(10);
+	for (i = 0; i < 400000; i++) {
+		gctl = hw_read_20kx(hw, GCTL);
+		if (get_field(gctl, GCTL_AID))
+			break;
+	}
+	if (!get_field(gctl, GCTL_AID)) {
+		printk(KERN_ALERT "Card Auto-init failed!!!\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static int i2c_unlock(struct hw *hw)
+{
+	if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa)
+		return 0;
+
+	hw_write_pci(hw, 0xcc, 0x8c);
+	hw_write_pci(hw, 0xcc, 0x0e);
+	if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa)
+		return 0;
+
+	hw_write_pci(hw, 0xcc, 0xee);
+	hw_write_pci(hw, 0xcc, 0xaa);
+	if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa)
+		return 0;
+
+	return -1;
+}
+
+static void i2c_lock(struct hw *hw)
+{
+	if ((hw_read_pci(hw, 0xcc) & 0xff) == 0xaa)
+		hw_write_pci(hw, 0xcc, 0x00);
+}
+
+static void i2c_write(struct hw *hw, u32 device, u32 addr, u32 data)
+{
+	unsigned int ret;
+
+	do {
+		ret = hw_read_pci(hw, 0xEC);
+	} while (!(ret & 0x800000));
+	hw_write_pci(hw, 0xE0, device);
+	hw_write_pci(hw, 0xE4, (data << 8) | (addr & 0xff));
+}
+
+/* DAC operations */
+
+static int hw_reset_dac(struct hw *hw)
+{
+	u32 i;
+	u16 gpioorg;
+	unsigned int ret;
+
+	if (i2c_unlock(hw))
+		return -1;
+
+	do {
+		ret = hw_read_pci(hw, 0xEC);
+	} while (!(ret & 0x800000));
+	hw_write_pci(hw, 0xEC, 0x05);  /* write to i2c status control */
+
+	/* To be effective, need to reset the DAC twice. */
+	for (i = 0; i < 2;  i++) {
+		/* set gpio */
+		mdelay(100);
+		gpioorg = (u16)hw_read_20kx(hw, GPIO);
+		gpioorg &= 0xfffd;
+		hw_write_20kx(hw, GPIO, gpioorg);
+		mdelay(1);
+		hw_write_20kx(hw, GPIO, gpioorg | 0x2);
+	}
+
+	i2c_write(hw, 0x00180080, 0x01, 0x80);
+	i2c_write(hw, 0x00180080, 0x02, 0x10);
+
+	i2c_lock(hw);
+
+	return 0;
+}
+
+static int hw_dac_init(struct hw *hw, const struct dac_conf *info)
+{
+	u32 data;
+	u16 gpioorg;
+	unsigned int ret;
+
+	if (hw->model == CTSB055X) {
+		/* SB055x, unmute outputs */
+		gpioorg = (u16)hw_read_20kx(hw, GPIO);
+		gpioorg &= 0xffbf;	/* set GPIO6 to low */
+		gpioorg |= 2;		/* set GPIO1 to high */
+		hw_write_20kx(hw, GPIO, gpioorg);
+		return 0;
+	}
+
+	/* mute outputs */
+	gpioorg = (u16)hw_read_20kx(hw, GPIO);
+	gpioorg &= 0xffbf;
+	hw_write_20kx(hw, GPIO, gpioorg);
+
+	hw_reset_dac(hw);
+
+	if (i2c_unlock(hw))
+		return -1;
+
+	hw_write_pci(hw, 0xEC, 0x05);  /* write to i2c status control */
+	do {
+		ret = hw_read_pci(hw, 0xEC);
+	} while (!(ret & 0x800000));
+
+	switch (info->msr) {
+	case 1:
+		data = 0x24;
+		break;
+	case 2:
+		data = 0x25;
+		break;
+	case 4:
+		data = 0x26;
+		break;
+	default:
+		data = 0x24;
+		break;
+	}
+
+	i2c_write(hw, 0x00180080, 0x06, data);
+	i2c_write(hw, 0x00180080, 0x09, data);
+	i2c_write(hw, 0x00180080, 0x0c, data);
+	i2c_write(hw, 0x00180080, 0x0f, data);
+
+	i2c_lock(hw);
+
+	/* unmute outputs */
+	gpioorg = (u16)hw_read_20kx(hw, GPIO);
+	gpioorg = gpioorg | 0x40;
+	hw_write_20kx(hw, GPIO, gpioorg);
+
+	return 0;
+}
+
+/* ADC operations */
+
+static int is_adc_input_selected_SB055x(struct hw *hw, enum ADCSRC type)
+{
+	return 0;
+}
+
+static int is_adc_input_selected_SBx(struct hw *hw, enum ADCSRC type)
+{
+	u32 data;
+
+	data = hw_read_20kx(hw, GPIO);
+	switch (type) {
+	case ADC_MICIN:
+		data = ((data & (0x1<<7)) && (data & (0x1<<8)));
+		break;
+	case ADC_LINEIN:
+		data = (!(data & (0x1<<7)) && (data & (0x1<<8)));
+		break;
+	case ADC_NONE: /* Digital I/O */
+		data = (!(data & (0x1<<8)));
+		break;
+	default:
+		data = 0;
+	}
+	return data;
+}
+
+static int is_adc_input_selected_hendrix(struct hw *hw, enum ADCSRC type)
+{
+	u32 data;
+
+	data = hw_read_20kx(hw, GPIO);
+	switch (type) {
+	case ADC_MICIN:
+		data = (data & (0x1 << 7)) ? 1 : 0;
+		break;
+	case ADC_LINEIN:
+		data = (data & (0x1 << 7)) ? 0 : 1;
+		break;
+	default:
+		data = 0;
+	}
+	return data;
+}
+
+static int hw_is_adc_input_selected(struct hw *hw, enum ADCSRC type)
+{
+	switch (hw->model) {
+	case CTSB055X:
+		return is_adc_input_selected_SB055x(hw, type);
+	case CTSB073X:
+		return is_adc_input_selected_hendrix(hw, type);
+	case CTUAA:
+		return is_adc_input_selected_hendrix(hw, type);
+	default:
+		return is_adc_input_selected_SBx(hw, type);
+	}
+}
+
+static int
+adc_input_select_SB055x(struct hw *hw, enum ADCSRC type, unsigned char boost)
+{
+	u32 data;
+
+	/*
+	 * check and set the following GPIO bits accordingly
+	 * ADC_Gain		= GPIO2
+	 * DRM_off		= GPIO3
+	 * Mic_Pwr_on		= GPIO7
+	 * Digital_IO_Sel	= GPIO8
+	 * Mic_Sw		= GPIO9
+	 * Aux/MicLine_Sw	= GPIO12
+	 */
+	data = hw_read_20kx(hw, GPIO);
+	data &= 0xec73;
+	switch (type) {
+	case ADC_MICIN:
+		data |= (0x1<<7) | (0x1<<8) | (0x1<<9) ;
+		data |= boost ? (0x1<<2) : 0;
+		break;
+	case ADC_LINEIN:
+		data |= (0x1<<8);
+		break;
+	case ADC_AUX:
+		data |= (0x1<<8) | (0x1<<12);
+		break;
+	case ADC_NONE:
+		data |= (0x1<<12);  /* set to digital */
+		break;
+	default:
+		return -1;
+	}
+
+	hw_write_20kx(hw, GPIO, data);
+
+	return 0;
+}
+
+
+static int
+adc_input_select_SBx(struct hw *hw, enum ADCSRC type, unsigned char boost)
+{
+	u32 data;
+	u32 i2c_data;
+	unsigned int ret;
+
+	if (i2c_unlock(hw))
+		return -1;
+
+	do {
+		ret = hw_read_pci(hw, 0xEC);
+	} while (!(ret & 0x800000)); /* i2c ready poll */
+	/* set i2c access mode as Direct Control */
+	hw_write_pci(hw, 0xEC, 0x05);
+
+	data = hw_read_20kx(hw, GPIO);
+	switch (type) {
+	case ADC_MICIN:
+		data |= ((0x1 << 7) | (0x1 << 8));
+		i2c_data = 0x1;  /* Mic-in */
+		break;
+	case ADC_LINEIN:
+		data &= ~(0x1 << 7);
+		data |= (0x1 << 8);
+		i2c_data = 0x2; /* Line-in */
+		break;
+	case ADC_NONE:
+		data &= ~(0x1 << 8);
+		i2c_data = 0x0; /* set to Digital */
+		break;
+	default:
+		i2c_lock(hw);
+		return -1;
+	}
+	hw_write_20kx(hw, GPIO, data);
+	i2c_write(hw, 0x001a0080, 0x2a, i2c_data);
+	if (boost) {
+		i2c_write(hw, 0x001a0080, 0x1c, 0xe7); /* +12dB boost */
+		i2c_write(hw, 0x001a0080, 0x1e, 0xe7); /* +12dB boost */
+	} else {
+		i2c_write(hw, 0x001a0080, 0x1c, 0xcf); /* No boost */
+		i2c_write(hw, 0x001a0080, 0x1e, 0xcf); /* No boost */
+	}
+
+	i2c_lock(hw);
+
+	return 0;
+}
+
+static int
+adc_input_select_hendrix(struct hw *hw, enum ADCSRC type, unsigned char boost)
+{
+	u32 data;
+	u32 i2c_data;
+	unsigned int ret;
+
+	if (i2c_unlock(hw))
+		return -1;
+
+	do {
+		ret = hw_read_pci(hw, 0xEC);
+	} while (!(ret & 0x800000)); /* i2c ready poll */
+	/* set i2c access mode as Direct Control */
+	hw_write_pci(hw, 0xEC, 0x05);
+
+	data = hw_read_20kx(hw, GPIO);
+	switch (type) {
+	case ADC_MICIN:
+		data |= (0x1 << 7);
+		i2c_data = 0x1;  /* Mic-in */
+		break;
+	case ADC_LINEIN:
+		data &= ~(0x1 << 7);
+		i2c_data = 0x2; /* Line-in */
+		break;
+	default:
+		i2c_lock(hw);
+		return -1;
+	}
+	hw_write_20kx(hw, GPIO, data);
+	i2c_write(hw, 0x001a0080, 0x2a, i2c_data);
+	if (boost) {
+		i2c_write(hw, 0x001a0080, 0x1c, 0xe7); /* +12dB boost */
+		i2c_write(hw, 0x001a0080, 0x1e, 0xe7); /* +12dB boost */
+	} else {
+		i2c_write(hw, 0x001a0080, 0x1c, 0xcf); /* No boost */
+		i2c_write(hw, 0x001a0080, 0x1e, 0xcf); /* No boost */
+	}
+
+	i2c_lock(hw);
+
+	return 0;
+}
+
+static int hw_adc_input_select(struct hw *hw, enum ADCSRC type)
+{
+	int state = type == ADC_MICIN;
+
+	switch (hw->model) {
+	case CTSB055X:
+		return adc_input_select_SB055x(hw, type, state);
+	case CTSB073X:
+		return adc_input_select_hendrix(hw, type, state);
+	case CTUAA:
+		return adc_input_select_hendrix(hw, type, state);
+	default:
+		return adc_input_select_SBx(hw, type, state);
+	}
+}
+
+static int adc_init_SB055x(struct hw *hw, int input, int mic20db)
+{
+	return adc_input_select_SB055x(hw, input, mic20db);
+}
+
+static int adc_init_SBx(struct hw *hw, int input, int mic20db)
+{
+	u16 gpioorg;
+	u16 input_source;
+	u32 adcdata;
+	unsigned int ret;
+
+	input_source = 0x100;  /* default to analog */
+	switch (input) {
+	case ADC_MICIN:
+		adcdata = 0x1;
+		input_source = 0x180;  /* set GPIO7 to select Mic */
+		break;
+	case ADC_LINEIN:
+		adcdata = 0x2;
+		break;
+	case ADC_VIDEO:
+		adcdata = 0x4;
+		break;
+	case ADC_AUX:
+		adcdata = 0x8;
+		break;
+	case ADC_NONE:
+		adcdata = 0x0;
+		input_source = 0x0;  /* set to Digital */
+		break;
+	default:
+		adcdata = 0x0;
+		break;
+	}
+
+	if (i2c_unlock(hw))
+		return -1;
+
+	do {
+		ret = hw_read_pci(hw, 0xEC);
+	} while (!(ret & 0x800000)); /* i2c ready poll */
+	hw_write_pci(hw, 0xEC, 0x05);  /* write to i2c status control */
+
+	i2c_write(hw, 0x001a0080, 0x0e, 0x08);
+	i2c_write(hw, 0x001a0080, 0x18, 0x0a);
+	i2c_write(hw, 0x001a0080, 0x28, 0x86);
+	i2c_write(hw, 0x001a0080, 0x2a, adcdata);
+
+	if (mic20db) {
+		i2c_write(hw, 0x001a0080, 0x1c, 0xf7);
+		i2c_write(hw, 0x001a0080, 0x1e, 0xf7);
+	} else {
+		i2c_write(hw, 0x001a0080, 0x1c, 0xcf);
+		i2c_write(hw, 0x001a0080, 0x1e, 0xcf);
+	}
+
+	if (!(hw_read_20kx(hw, ID0) & 0x100))
+		i2c_write(hw, 0x001a0080, 0x16, 0x26);
+
+	i2c_lock(hw);
+
+	gpioorg = (u16)hw_read_20kx(hw,  GPIO);
+	gpioorg &= 0xfe7f;
+	gpioorg |= input_source;
+	hw_write_20kx(hw, GPIO, gpioorg);
+
+	return 0;
+}
+
+static int hw_adc_init(struct hw *hw, const struct adc_conf *info)
+{
+	if (hw->model == CTSB055X)
+		return adc_init_SB055x(hw, info->input, info->mic20db);
+	else
+		return adc_init_SBx(hw, info->input, info->mic20db);
+}
+
+static int hw_have_digit_io_switch(struct hw *hw)
+{
+	/* SB073x and Vista compatible cards have no digit IO switch */
+	return !(hw->model == CTSB073X || hw->model == CTUAA);
+}
+
+#define CTLBITS(a, b, c, d)	(((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
+
+#define UAA_CFG_PWRSTATUS	0x44
+#define UAA_CFG_SPACE_FLAG	0xA0
+#define UAA_CORE_CHANGE		0x3FFC
+static int uaa_to_xfi(struct pci_dev *pci)
+{
+	unsigned int bar0, bar1, bar2, bar3, bar4, bar5;
+	unsigned int cmd, irq, cl_size, l_timer, pwr;
+	unsigned int is_uaa;
+	unsigned int data[4] = {0};
+	unsigned int io_base;
+	void *mem_base;
+	int i;
+	const u32 CTLX = CTLBITS('C', 'T', 'L', 'X');
+	const u32 CTL_ = CTLBITS('C', 'T', 'L', '-');
+	const u32 CTLF = CTLBITS('C', 'T', 'L', 'F');
+	const u32 CTLi = CTLBITS('C', 'T', 'L', 'i');
+	const u32 CTLA = CTLBITS('C', 'T', 'L', 'A');
+	const u32 CTLZ = CTLBITS('C', 'T', 'L', 'Z');
+	const u32 CTLL = CTLBITS('C', 'T', 'L', 'L');
+
+	/* By default, Hendrix card UAA Bar0 should be using memory... */
+	io_base = pci_resource_start(pci, 0);
+	mem_base = ioremap(io_base, pci_resource_len(pci, 0));
+	if (!mem_base)
+		return -ENOENT;
+
+	/* Read current mode from Mode Change Register */
+	for (i = 0; i < 4; i++)
+		data[i] = readl(mem_base + UAA_CORE_CHANGE);
+
+	/* Determine current mode... */
+	if (data[0] == CTLA) {
+		is_uaa = ((data[1] == CTLZ && data[2] == CTLL
+			  && data[3] == CTLA) || (data[1] == CTLA
+			  && data[2] == CTLZ && data[3] == CTLL));
+	} else if (data[0] == CTLZ) {
+		is_uaa = (data[1] == CTLL
+				&& data[2] == CTLA && data[3] == CTLA);
+	} else if (data[0] == CTLL) {
+		is_uaa = (data[1] == CTLA
+				&& data[2] == CTLA && data[3] == CTLZ);
+	} else {
+		is_uaa = 0;
+	}
+
+	if (!is_uaa) {
+		/* Not in UAA mode currently. Return directly. */
+		iounmap(mem_base);
+		return 0;
+	}
+
+	pci_read_config_dword(pci, PCI_BASE_ADDRESS_0, &bar0);
+	pci_read_config_dword(pci, PCI_BASE_ADDRESS_1, &bar1);
+	pci_read_config_dword(pci, PCI_BASE_ADDRESS_2, &bar2);
+	pci_read_config_dword(pci, PCI_BASE_ADDRESS_3, &bar3);
+	pci_read_config_dword(pci, PCI_BASE_ADDRESS_4, &bar4);
+	pci_read_config_dword(pci, PCI_BASE_ADDRESS_5, &bar5);
+	pci_read_config_dword(pci, PCI_INTERRUPT_LINE, &irq);
+	pci_read_config_dword(pci, PCI_CACHE_LINE_SIZE, &cl_size);
+	pci_read_config_dword(pci, PCI_LATENCY_TIMER, &l_timer);
+	pci_read_config_dword(pci, UAA_CFG_PWRSTATUS, &pwr);
+	pci_read_config_dword(pci, PCI_COMMAND, &cmd);
+
+	/* Set up X-Fi core PCI configuration space. */
+	/* Switch to X-Fi config space with BAR0 exposed. */
+	pci_write_config_dword(pci, UAA_CFG_SPACE_FLAG, 0x87654321);
+	/* Copy UAA's BAR5 into X-Fi BAR0 */
+	pci_write_config_dword(pci, PCI_BASE_ADDRESS_0, bar5);
+	/* Switch to X-Fi config space without BAR0 exposed. */
+	pci_write_config_dword(pci, UAA_CFG_SPACE_FLAG, 0x12345678);
+	pci_write_config_dword(pci, PCI_BASE_ADDRESS_1, bar1);
+	pci_write_config_dword(pci, PCI_BASE_ADDRESS_2, bar2);
+	pci_write_config_dword(pci, PCI_BASE_ADDRESS_3, bar3);
+	pci_write_config_dword(pci, PCI_BASE_ADDRESS_4, bar4);
+	pci_write_config_dword(pci, PCI_INTERRUPT_LINE, irq);
+	pci_write_config_dword(pci, PCI_CACHE_LINE_SIZE, cl_size);
+	pci_write_config_dword(pci, PCI_LATENCY_TIMER, l_timer);
+	pci_write_config_dword(pci, UAA_CFG_PWRSTATUS, pwr);
+	pci_write_config_dword(pci, PCI_COMMAND, cmd);
+
+	/* Switch to X-Fi mode */
+	writel(CTLX, (mem_base + UAA_CORE_CHANGE));
+	writel(CTL_, (mem_base + UAA_CORE_CHANGE));
+	writel(CTLF, (mem_base + UAA_CORE_CHANGE));
+	writel(CTLi, (mem_base + UAA_CORE_CHANGE));
+
+	iounmap(mem_base);
+
+	return 0;
+}
+
+static irqreturn_t ct_20k1_interrupt(int irq, void *dev_id)
+{
+	struct hw *hw = dev_id;
+	unsigned int status;
+
+	status = hw_read_20kx(hw, GIP);
+	if (!status)
+		return IRQ_NONE;
+
+	if (hw->irq_callback)
+		hw->irq_callback(hw->irq_callback_data, status);
+
+	hw_write_20kx(hw, GIP, status);
+	return IRQ_HANDLED;
+}
+
+static int hw_card_start(struct hw *hw)
+{
+	int err;
+	struct pci_dev *pci = hw->pci;
+
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+
+	/* Set DMA transfer mask */
+	if (pci_set_dma_mask(pci, CT_XFI_DMA_MASK) < 0 ||
+	    pci_set_consistent_dma_mask(pci, CT_XFI_DMA_MASK) < 0) {
+		printk(KERN_ERR "architecture does not support PCI "
+				"busmaster DMA with mask 0x%llx\n",
+		       CT_XFI_DMA_MASK);
+		err = -ENXIO;
+		goto error1;
+	}
+
+	if (!hw->io_base) {
+		err = pci_request_regions(pci, "XFi");
+		if (err < 0)
+			goto error1;
+
+		if (hw->model == CTUAA)
+			hw->io_base = pci_resource_start(pci, 5);
+		else
+			hw->io_base = pci_resource_start(pci, 0);
+
+	}
+
+	/* Switch to X-Fi mode from UAA mode if neeeded */
+	if (hw->model == CTUAA) {
+		err = uaa_to_xfi(pci);
+		if (err)
+			goto error2;
+
+	}
+
+	if (hw->irq < 0) {
+		err = request_irq(pci->irq, ct_20k1_interrupt, IRQF_SHARED,
+				  "ctxfi", hw);
+		if (err < 0) {
+			printk(KERN_ERR "XFi: Cannot get irq %d\n", pci->irq);
+			goto error2;
+		}
+		hw->irq = pci->irq;
+	}
+
+	pci_set_master(pci);
+
+	return 0;
+
+error2:
+	pci_release_regions(pci);
+	hw->io_base = 0;
+error1:
+	pci_disable_device(pci);
+	return err;
+}
+
+static int hw_card_stop(struct hw *hw)
+{
+	unsigned int data;
+
+	/* disable transport bus master and queueing of request */
+	hw_write_20kx(hw, TRNCTL, 0x00);
+
+	/* disable pll */
+	data = hw_read_20kx(hw, PLLCTL);
+	hw_write_20kx(hw, PLLCTL, (data & (~(0x0F<<12))));
+
+	/* TODO: Disable interrupt and so on... */
+	if (hw->irq >= 0)
+		synchronize_irq(hw->irq);
+	return 0;
+}
+
+static int hw_card_shutdown(struct hw *hw)
+{
+	if (hw->irq >= 0)
+		free_irq(hw->irq, hw);
+
+	hw->irq	= -1;
+
+	if (hw->mem_base)
+		iounmap((void *)hw->mem_base);
+
+	hw->mem_base = (unsigned long)NULL;
+
+	if (hw->io_base)
+		pci_release_regions(hw->pci);
+
+	hw->io_base = 0;
+
+	pci_disable_device(hw->pci);
+
+	return 0;
+}
+
+static int hw_card_init(struct hw *hw, struct card_conf *info)
+{
+	int err;
+	unsigned int gctl;
+	u32 data;
+	struct dac_conf dac_info = {0};
+	struct adc_conf adc_info = {0};
+	struct daio_conf daio_info = {0};
+	struct trn_conf trn_info = {0};
+
+	/* Get PCI io port base address and do Hendrix switch if needed. */
+	err = hw_card_start(hw);
+	if (err)
+		return err;
+
+	/* PLL init */
+	err = hw_pll_init(hw, info->rsr);
+	if (err < 0)
+		return err;
+
+	/* kick off auto-init */
+	err = hw_auto_init(hw);
+	if (err < 0)
+		return err;
+
+	/* Enable audio ring */
+	gctl = hw_read_20kx(hw, GCTL);
+	set_field(&gctl, GCTL_EAC, 1);
+	set_field(&gctl, GCTL_DBP, 1);
+	set_field(&gctl, GCTL_TBP, 1);
+	set_field(&gctl, GCTL_FBP, 1);
+	set_field(&gctl, GCTL_ET, 1);
+	hw_write_20kx(hw, GCTL, gctl);
+	mdelay(10);
+
+	/* Reset all global pending interrupts */
+	hw_write_20kx(hw, GIE, 0);
+	/* Reset all SRC pending interrupts */
+	hw_write_20kx(hw, SRCIP, 0);
+	mdelay(30);
+
+	/* Detect the card ID and configure GPIO accordingly. */
+	switch (hw->model) {
+	case CTSB055X:
+		hw_write_20kx(hw, GPIOCTL, 0x13fe);
+		break;
+	case CTSB073X:
+		hw_write_20kx(hw, GPIOCTL, 0x00e6);
+		break;
+	case CTUAA:
+		hw_write_20kx(hw, GPIOCTL, 0x00c2);
+		break;
+	default:
+		hw_write_20kx(hw, GPIOCTL, 0x01e6);
+		break;
+	}
+
+	trn_info.vm_pgt_phys = info->vm_pgt_phys;
+	err = hw_trn_init(hw, &trn_info);
+	if (err < 0)
+		return err;
+
+	daio_info.msr = info->msr;
+	err = hw_daio_init(hw, &daio_info);
+	if (err < 0)
+		return err;
+
+	dac_info.msr = info->msr;
+	err = hw_dac_init(hw, &dac_info);
+	if (err < 0)
+		return err;
+
+	adc_info.msr = info->msr;
+	adc_info.input = ADC_LINEIN;
+	adc_info.mic20db = 0;
+	err = hw_adc_init(hw, &adc_info);
+	if (err < 0)
+		return err;
+
+	data = hw_read_20kx(hw, SRCMCTL);
+	data |= 0x1; /* Enables input from the audio ring */
+	hw_write_20kx(hw, SRCMCTL, data);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int hw_suspend(struct hw *hw, pm_message_t state)
+{
+	struct pci_dev *pci = hw->pci;
+
+	hw_card_stop(hw);
+
+	if (hw->model == CTUAA) {
+		/* Switch to UAA config space. */
+		pci_write_config_dword(pci, UAA_CFG_SPACE_FLAG, 0x0);
+	}
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+
+	return 0;
+}
+
+static int hw_resume(struct hw *hw, struct card_conf *info)
+{
+	struct pci_dev *pci = hw->pci;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+
+	/* Re-initialize card hardware. */
+	return hw_card_init(hw, info);
+}
+#endif
+
+static u32 hw_read_20kx(struct hw *hw, u32 reg)
+{
+	u32 value;
+	unsigned long flags;
+
+	spin_lock_irqsave(
+		&container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+	outl(reg, hw->io_base + 0x0);
+	value = inl(hw->io_base + 0x4);
+	spin_unlock_irqrestore(
+		&container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+
+	return value;
+}
+
+static void hw_write_20kx(struct hw *hw, u32 reg, u32 data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(
+		&container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+	outl(reg, hw->io_base + 0x0);
+	outl(data, hw->io_base + 0x4);
+	spin_unlock_irqrestore(
+		&container_of(hw, struct hw20k1, hw)->reg_20k1_lock, flags);
+
+}
+
+static u32 hw_read_pci(struct hw *hw, u32 reg)
+{
+	u32 value;
+	unsigned long flags;
+
+	spin_lock_irqsave(
+		&container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+	outl(reg, hw->io_base + 0x10);
+	value = inl(hw->io_base + 0x14);
+	spin_unlock_irqrestore(
+		&container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+
+	return value;
+}
+
+static void hw_write_pci(struct hw *hw, u32 reg, u32 data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(
+		&container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+	outl(reg, hw->io_base + 0x10);
+	outl(data, hw->io_base + 0x14);
+	spin_unlock_irqrestore(
+		&container_of(hw, struct hw20k1, hw)->reg_pci_lock, flags);
+}
+
+static struct hw ct20k1_preset __devinitdata = {
+	.irq = -1,
+
+	.card_init = hw_card_init,
+	.card_stop = hw_card_stop,
+	.pll_init = hw_pll_init,
+	.is_adc_source_selected = hw_is_adc_input_selected,
+	.select_adc_source = hw_adc_input_select,
+	.have_digit_io_switch = hw_have_digit_io_switch,
+#ifdef CONFIG_PM
+	.suspend = hw_suspend,
+	.resume = hw_resume,
+#endif
+
+	.src_rsc_get_ctrl_blk = src_get_rsc_ctrl_blk,
+	.src_rsc_put_ctrl_blk = src_put_rsc_ctrl_blk,
+	.src_mgr_get_ctrl_blk = src_mgr_get_ctrl_blk,
+	.src_mgr_put_ctrl_blk = src_mgr_put_ctrl_blk,
+	.src_set_state = src_set_state,
+	.src_set_bm = src_set_bm,
+	.src_set_rsr = src_set_rsr,
+	.src_set_sf = src_set_sf,
+	.src_set_wr = src_set_wr,
+	.src_set_pm = src_set_pm,
+	.src_set_rom = src_set_rom,
+	.src_set_vo = src_set_vo,
+	.src_set_st = src_set_st,
+	.src_set_ie = src_set_ie,
+	.src_set_ilsz = src_set_ilsz,
+	.src_set_bp = src_set_bp,
+	.src_set_cisz = src_set_cisz,
+	.src_set_ca = src_set_ca,
+	.src_set_sa = src_set_sa,
+	.src_set_la = src_set_la,
+	.src_set_pitch = src_set_pitch,
+	.src_set_dirty = src_set_dirty,
+	.src_set_clear_zbufs = src_set_clear_zbufs,
+	.src_set_dirty_all = src_set_dirty_all,
+	.src_commit_write = src_commit_write,
+	.src_get_ca = src_get_ca,
+	.src_get_dirty = src_get_dirty,
+	.src_dirty_conj_mask = src_dirty_conj_mask,
+	.src_mgr_enbs_src = src_mgr_enbs_src,
+	.src_mgr_enb_src = src_mgr_enb_src,
+	.src_mgr_dsb_src = src_mgr_dsb_src,
+	.src_mgr_commit_write = src_mgr_commit_write,
+
+	.srcimp_mgr_get_ctrl_blk = srcimp_mgr_get_ctrl_blk,
+	.srcimp_mgr_put_ctrl_blk = srcimp_mgr_put_ctrl_blk,
+	.srcimp_mgr_set_imaparc = srcimp_mgr_set_imaparc,
+	.srcimp_mgr_set_imapuser = srcimp_mgr_set_imapuser,
+	.srcimp_mgr_set_imapnxt = srcimp_mgr_set_imapnxt,
+	.srcimp_mgr_set_imapaddr = srcimp_mgr_set_imapaddr,
+	.srcimp_mgr_commit_write = srcimp_mgr_commit_write,
+
+	.amixer_rsc_get_ctrl_blk = amixer_rsc_get_ctrl_blk,
+	.amixer_rsc_put_ctrl_blk = amixer_rsc_put_ctrl_blk,
+	.amixer_mgr_get_ctrl_blk = amixer_mgr_get_ctrl_blk,
+	.amixer_mgr_put_ctrl_blk = amixer_mgr_put_ctrl_blk,
+	.amixer_set_mode = amixer_set_mode,
+	.amixer_set_iv = amixer_set_iv,
+	.amixer_set_x = amixer_set_x,
+	.amixer_set_y = amixer_set_y,
+	.amixer_set_sadr = amixer_set_sadr,
+	.amixer_set_se = amixer_set_se,
+	.amixer_set_dirty = amixer_set_dirty,
+	.amixer_set_dirty_all = amixer_set_dirty_all,
+	.amixer_commit_write = amixer_commit_write,
+	.amixer_get_y = amixer_get_y,
+	.amixer_get_dirty = amixer_get_dirty,
+
+	.dai_get_ctrl_blk = dai_get_ctrl_blk,
+	.dai_put_ctrl_blk = dai_put_ctrl_blk,
+	.dai_srt_set_srco = dai_srt_set_srcr,
+	.dai_srt_set_srcm = dai_srt_set_srcl,
+	.dai_srt_set_rsr = dai_srt_set_rsr,
+	.dai_srt_set_drat = dai_srt_set_drat,
+	.dai_srt_set_ec = dai_srt_set_ec,
+	.dai_srt_set_et = dai_srt_set_et,
+	.dai_commit_write = dai_commit_write,
+
+	.dao_get_ctrl_blk = dao_get_ctrl_blk,
+	.dao_put_ctrl_blk = dao_put_ctrl_blk,
+	.dao_set_spos = dao_set_spos,
+	.dao_commit_write = dao_commit_write,
+	.dao_get_spos = dao_get_spos,
+
+	.daio_mgr_get_ctrl_blk = daio_mgr_get_ctrl_blk,
+	.daio_mgr_put_ctrl_blk = daio_mgr_put_ctrl_blk,
+	.daio_mgr_enb_dai = daio_mgr_enb_dai,
+	.daio_mgr_dsb_dai = daio_mgr_dsb_dai,
+	.daio_mgr_enb_dao = daio_mgr_enb_dao,
+	.daio_mgr_dsb_dao = daio_mgr_dsb_dao,
+	.daio_mgr_dao_init = daio_mgr_dao_init,
+	.daio_mgr_set_imaparc = daio_mgr_set_imaparc,
+	.daio_mgr_set_imapnxt = daio_mgr_set_imapnxt,
+	.daio_mgr_set_imapaddr = daio_mgr_set_imapaddr,
+	.daio_mgr_commit_write = daio_mgr_commit_write,
+
+	.set_timer_irq = set_timer_irq,
+	.set_timer_tick = set_timer_tick,
+	.get_wc = get_wc,
+};
+
+int __devinit create_20k1_hw_obj(struct hw **rhw)
+{
+	struct hw20k1 *hw20k1;
+
+	*rhw = NULL;
+	hw20k1 = kzalloc(sizeof(*hw20k1), GFP_KERNEL);
+	if (!hw20k1)
+		return -ENOMEM;
+
+	spin_lock_init(&hw20k1->reg_20k1_lock);
+	spin_lock_init(&hw20k1->reg_pci_lock);
+
+	hw20k1->hw = ct20k1_preset;
+
+	*rhw = &hw20k1->hw;
+
+	return 0;
+}
+
+int destroy_20k1_hw_obj(struct hw *hw)
+{
+	if (hw->io_base)
+		hw_card_shutdown(hw);
+
+	kfree(container_of(hw, struct hw20k1, hw));
+	return 0;
+}
diff --git a/linux/sound/pci/ctxfi/cthw20k1.h b/linux/sound/pci/ctxfi/cthw20k1.h
new file mode 100644
index 0000000..02f72fb
--- /dev/null
+++ b/linux/sound/pci/ctxfi/cthw20k1.h
@@ -0,0 +1,26 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	cthw20k1.h
+ *
+ * @Brief
+ * This file contains the definition of hardware access methord.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 13 2008
+ *
+ */
+
+#ifndef CTHW20K1_H
+#define CTHW20K1_H
+
+#include "cthardware.h"
+
+int create_20k1_hw_obj(struct hw **rhw);
+int destroy_20k1_hw_obj(struct hw *hw);
+
+#endif /* CTHW20K1_H */
diff --git a/linux/sound/pci/ctxfi/cthw20k2.c b/linux/sound/pci/ctxfi/cthw20k2.c
new file mode 100644
index 0000000..b6b11bf
--- /dev/null
+++ b/linux/sound/pci/ctxfi/cthw20k2.c
@@ -0,0 +1,2219 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	cthw20k2.c
+ *
+ * @Brief
+ * This file contains the implementation of hardware access methord for 20k2.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 14 2008
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include "cthw20k2.h"
+#include "ct20k2reg.h"
+
+#if BITS_PER_LONG == 32
+#define CT_XFI_DMA_MASK		DMA_BIT_MASK(32) /* 32 bit PTE */
+#else
+#define CT_XFI_DMA_MASK		DMA_BIT_MASK(64) /* 64 bit PTE */
+#endif
+
+struct hw20k2 {
+	struct hw hw;
+	/* for i2c */
+	unsigned char dev_id;
+	unsigned char addr_size;
+	unsigned char data_size;
+};
+
+static u32 hw_read_20kx(struct hw *hw, u32 reg);
+static void hw_write_20kx(struct hw *hw, u32 reg, u32 data);
+
+/*
+ * Type definition block.
+ * The layout of control structures can be directly applied on 20k2 chip.
+ */
+
+/*
+ * SRC control block definitions.
+ */
+
+/* SRC resource control block */
+#define SRCCTL_STATE	0x00000007
+#define SRCCTL_BM	0x00000008
+#define SRCCTL_RSR	0x00000030
+#define SRCCTL_SF	0x000001C0
+#define SRCCTL_WR	0x00000200
+#define SRCCTL_PM	0x00000400
+#define SRCCTL_ROM	0x00001800
+#define SRCCTL_VO	0x00002000
+#define SRCCTL_ST	0x00004000
+#define SRCCTL_IE	0x00008000
+#define SRCCTL_ILSZ	0x000F0000
+#define SRCCTL_BP	0x00100000
+
+#define SRCCCR_CISZ	0x000007FF
+#define SRCCCR_CWA	0x001FF800
+#define SRCCCR_D	0x00200000
+#define SRCCCR_RS	0x01C00000
+#define SRCCCR_NAL	0x3E000000
+#define SRCCCR_RA	0xC0000000
+
+#define SRCCA_CA	0x0FFFFFFF
+#define SRCCA_RS	0xE0000000
+
+#define SRCSA_SA	0x0FFFFFFF
+
+#define SRCLA_LA	0x0FFFFFFF
+
+/* Mixer Parameter Ring ram Low and Hight register.
+ * Fixed-point value in 8.24 format for parameter channel */
+#define MPRLH_PITCH	0xFFFFFFFF
+
+/* SRC resource register dirty flags */
+union src_dirty {
+	struct {
+		u16 ctl:1;
+		u16 ccr:1;
+		u16 sa:1;
+		u16 la:1;
+		u16 ca:1;
+		u16 mpr:1;
+		u16 czbfs:1;	/* Clear Z-Buffers */
+		u16 rsv:9;
+	} bf;
+	u16 data;
+};
+
+struct src_rsc_ctrl_blk {
+	unsigned int	ctl;
+	unsigned int 	ccr;
+	unsigned int	ca;
+	unsigned int	sa;
+	unsigned int	la;
+	unsigned int	mpr;
+	union src_dirty	dirty;
+};
+
+/* SRC manager control block */
+union src_mgr_dirty {
+	struct {
+		u16 enb0:1;
+		u16 enb1:1;
+		u16 enb2:1;
+		u16 enb3:1;
+		u16 enb4:1;
+		u16 enb5:1;
+		u16 enb6:1;
+		u16 enb7:1;
+		u16 enbsa:1;
+		u16 rsv:7;
+	} bf;
+	u16 data;
+};
+
+struct src_mgr_ctrl_blk {
+	unsigned int		enbsa;
+	unsigned int		enb[8];
+	union src_mgr_dirty	dirty;
+};
+
+/* SRCIMP manager control block */
+#define SRCAIM_ARC	0x00000FFF
+#define SRCAIM_NXT	0x00FF0000
+#define SRCAIM_SRC	0xFF000000
+
+struct srcimap {
+	unsigned int srcaim;
+	unsigned int idx;
+};
+
+/* SRCIMP manager register dirty flags */
+union srcimp_mgr_dirty {
+	struct {
+		u16 srcimap:1;
+		u16 rsv:15;
+	} bf;
+	u16 data;
+};
+
+struct srcimp_mgr_ctrl_blk {
+	struct srcimap		srcimap;
+	union srcimp_mgr_dirty	dirty;
+};
+
+/*
+ * Function implementation block.
+ */
+
+static int src_get_rsc_ctrl_blk(void **rblk)
+{
+	struct src_rsc_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int src_put_rsc_ctrl_blk(void *blk)
+{
+	kfree(blk);
+
+	return 0;
+}
+
+static int src_set_state(void *blk, unsigned int state)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_STATE, state);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_bm(void *blk, unsigned int bm)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_BM, bm);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_rsr(void *blk, unsigned int rsr)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_RSR, rsr);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_sf(void *blk, unsigned int sf)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_SF, sf);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_wr(void *blk, unsigned int wr)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_WR, wr);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_pm(void *blk, unsigned int pm)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_PM, pm);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_rom(void *blk, unsigned int rom)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_ROM, rom);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_vo(void *blk, unsigned int vo)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_VO, vo);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_st(void *blk, unsigned int st)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_ST, st);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_ie(void *blk, unsigned int ie)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_IE, ie);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_ilsz(void *blk, unsigned int ilsz)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_ILSZ, ilsz);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_bp(void *blk, unsigned int bp)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ctl, SRCCTL_BP, bp);
+	ctl->dirty.bf.ctl = 1;
+	return 0;
+}
+
+static int src_set_cisz(void *blk, unsigned int cisz)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ccr, SRCCCR_CISZ, cisz);
+	ctl->dirty.bf.ccr = 1;
+	return 0;
+}
+
+static int src_set_ca(void *blk, unsigned int ca)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->ca, SRCCA_CA, ca);
+	ctl->dirty.bf.ca = 1;
+	return 0;
+}
+
+static int src_set_sa(void *blk, unsigned int sa)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->sa, SRCSA_SA, sa);
+	ctl->dirty.bf.sa = 1;
+	return 0;
+}
+
+static int src_set_la(void *blk, unsigned int la)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->la, SRCLA_LA, la);
+	ctl->dirty.bf.la = 1;
+	return 0;
+}
+
+static int src_set_pitch(void *blk, unsigned int pitch)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->mpr, MPRLH_PITCH, pitch);
+	ctl->dirty.bf.mpr = 1;
+	return 0;
+}
+
+static int src_set_clear_zbufs(void *blk, unsigned int clear)
+{
+	((struct src_rsc_ctrl_blk *)blk)->dirty.bf.czbfs = (clear ? 1 : 0);
+	return 0;
+}
+
+static int src_set_dirty(void *blk, unsigned int flags)
+{
+	((struct src_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff);
+	return 0;
+}
+
+static int src_set_dirty_all(void *blk)
+{
+	((struct src_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0);
+	return 0;
+}
+
+#define AR_SLOT_SIZE		4096
+#define AR_SLOT_BLOCK_SIZE	16
+#define AR_PTS_PITCH		6
+#define AR_PARAM_SRC_OFFSET	0x60
+
+static unsigned int src_param_pitch_mixer(unsigned int src_idx)
+{
+	return ((src_idx << 4) + AR_PTS_PITCH + AR_SLOT_SIZE
+			- AR_PARAM_SRC_OFFSET) % AR_SLOT_SIZE;
+
+}
+
+static int src_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+	int i;
+
+	if (ctl->dirty.bf.czbfs) {
+		/* Clear Z-Buffer registers */
+		for (i = 0; i < 8; i++)
+			hw_write_20kx(hw, SRC_UPZ+idx*0x100+i*0x4, 0);
+
+		for (i = 0; i < 4; i++)
+			hw_write_20kx(hw, SRC_DN0Z+idx*0x100+i*0x4, 0);
+
+		for (i = 0; i < 8; i++)
+			hw_write_20kx(hw, SRC_DN1Z+idx*0x100+i*0x4, 0);
+
+		ctl->dirty.bf.czbfs = 0;
+	}
+	if (ctl->dirty.bf.mpr) {
+		/* Take the parameter mixer resource in the same group as that
+		 * the idx src is in for simplicity. Unlike src, all conjugate
+		 * parameter mixer resources must be programmed for
+		 * corresponding conjugate src resources. */
+		unsigned int pm_idx = src_param_pitch_mixer(idx);
+		hw_write_20kx(hw, MIXER_PRING_LO_HI+4*pm_idx, ctl->mpr);
+		hw_write_20kx(hw, MIXER_PMOPLO+8*pm_idx, 0x3);
+		hw_write_20kx(hw, MIXER_PMOPHI+8*pm_idx, 0x0);
+		ctl->dirty.bf.mpr = 0;
+	}
+	if (ctl->dirty.bf.sa) {
+		hw_write_20kx(hw, SRC_SA+idx*0x100, ctl->sa);
+		ctl->dirty.bf.sa = 0;
+	}
+	if (ctl->dirty.bf.la) {
+		hw_write_20kx(hw, SRC_LA+idx*0x100, ctl->la);
+		ctl->dirty.bf.la = 0;
+	}
+	if (ctl->dirty.bf.ca) {
+		hw_write_20kx(hw, SRC_CA+idx*0x100, ctl->ca);
+		ctl->dirty.bf.ca = 0;
+	}
+
+	/* Write srccf register */
+	hw_write_20kx(hw, SRC_CF+idx*0x100, 0x0);
+
+	if (ctl->dirty.bf.ccr) {
+		hw_write_20kx(hw, SRC_CCR+idx*0x100, ctl->ccr);
+		ctl->dirty.bf.ccr = 0;
+	}
+	if (ctl->dirty.bf.ctl) {
+		hw_write_20kx(hw, SRC_CTL+idx*0x100, ctl->ctl);
+		ctl->dirty.bf.ctl = 0;
+	}
+
+	return 0;
+}
+
+static int src_get_ca(struct hw *hw, unsigned int idx, void *blk)
+{
+	struct src_rsc_ctrl_blk *ctl = blk;
+
+	ctl->ca = hw_read_20kx(hw, SRC_CA+idx*0x100);
+	ctl->dirty.bf.ca = 0;
+
+	return get_field(ctl->ca, SRCCA_CA);
+}
+
+static unsigned int src_get_dirty(void *blk)
+{
+	return ((struct src_rsc_ctrl_blk *)blk)->dirty.data;
+}
+
+static unsigned int src_dirty_conj_mask(void)
+{
+	return 0x20;
+}
+
+static int src_mgr_enbs_src(void *blk, unsigned int idx)
+{
+	((struct src_mgr_ctrl_blk *)blk)->enbsa |= (0x1 << ((idx%128)/4));
+	((struct src_mgr_ctrl_blk *)blk)->dirty.bf.enbsa = 1;
+	((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32));
+	return 0;
+}
+
+static int src_mgr_enb_src(void *blk, unsigned int idx)
+{
+	((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] |= (0x1 << (idx%32));
+	((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32));
+	return 0;
+}
+
+static int src_mgr_dsb_src(void *blk, unsigned int idx)
+{
+	((struct src_mgr_ctrl_blk *)blk)->enb[idx/32] &= ~(0x1 << (idx%32));
+	((struct src_mgr_ctrl_blk *)blk)->dirty.data |= (0x1 << (idx/32));
+	return 0;
+}
+
+static int src_mgr_commit_write(struct hw *hw, void *blk)
+{
+	struct src_mgr_ctrl_blk *ctl = blk;
+	int i;
+	unsigned int ret;
+
+	if (ctl->dirty.bf.enbsa) {
+		do {
+			ret = hw_read_20kx(hw, SRC_ENBSTAT);
+		} while (ret & 0x1);
+		hw_write_20kx(hw, SRC_ENBSA, ctl->enbsa);
+		ctl->dirty.bf.enbsa = 0;
+	}
+	for (i = 0; i < 8; i++) {
+		if ((ctl->dirty.data & (0x1 << i))) {
+			hw_write_20kx(hw, SRC_ENB+(i*0x100), ctl->enb[i]);
+			ctl->dirty.data &= ~(0x1 << i);
+		}
+	}
+
+	return 0;
+}
+
+static int src_mgr_get_ctrl_blk(void **rblk)
+{
+	struct src_mgr_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int src_mgr_put_ctrl_blk(void *blk)
+{
+	kfree(blk);
+
+	return 0;
+}
+
+static int srcimp_mgr_get_ctrl_blk(void **rblk)
+{
+	struct srcimp_mgr_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int srcimp_mgr_put_ctrl_blk(void *blk)
+{
+	kfree(blk);
+
+	return 0;
+}
+
+static int srcimp_mgr_set_imaparc(void *blk, unsigned int slot)
+{
+	struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srcimap.srcaim, SRCAIM_ARC, slot);
+	ctl->dirty.bf.srcimap = 1;
+	return 0;
+}
+
+static int srcimp_mgr_set_imapuser(void *blk, unsigned int user)
+{
+	struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srcimap.srcaim, SRCAIM_SRC, user);
+	ctl->dirty.bf.srcimap = 1;
+	return 0;
+}
+
+static int srcimp_mgr_set_imapnxt(void *blk, unsigned int next)
+{
+	struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srcimap.srcaim, SRCAIM_NXT, next);
+	ctl->dirty.bf.srcimap = 1;
+	return 0;
+}
+
+static int srcimp_mgr_set_imapaddr(void *blk, unsigned int addr)
+{
+	((struct srcimp_mgr_ctrl_blk *)blk)->srcimap.idx = addr;
+	((struct srcimp_mgr_ctrl_blk *)blk)->dirty.bf.srcimap = 1;
+	return 0;
+}
+
+static int srcimp_mgr_commit_write(struct hw *hw, void *blk)
+{
+	struct srcimp_mgr_ctrl_blk *ctl = blk;
+
+	if (ctl->dirty.bf.srcimap) {
+		hw_write_20kx(hw, SRC_IMAP+ctl->srcimap.idx*0x100,
+						ctl->srcimap.srcaim);
+		ctl->dirty.bf.srcimap = 0;
+	}
+
+	return 0;
+}
+
+/*
+ * AMIXER control block definitions.
+ */
+
+#define AMOPLO_M	0x00000003
+#define AMOPLO_IV	0x00000004
+#define AMOPLO_X	0x0003FFF0
+#define AMOPLO_Y	0xFFFC0000
+
+#define AMOPHI_SADR	0x000000FF
+#define AMOPHI_SE	0x80000000
+
+/* AMIXER resource register dirty flags */
+union amixer_dirty {
+	struct {
+		u16 amoplo:1;
+		u16 amophi:1;
+		u16 rsv:14;
+	} bf;
+	u16 data;
+};
+
+/* AMIXER resource control block */
+struct amixer_rsc_ctrl_blk {
+	unsigned int		amoplo;
+	unsigned int		amophi;
+	union amixer_dirty	dirty;
+};
+
+static int amixer_set_mode(void *blk, unsigned int mode)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->amoplo, AMOPLO_M, mode);
+	ctl->dirty.bf.amoplo = 1;
+	return 0;
+}
+
+static int amixer_set_iv(void *blk, unsigned int iv)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->amoplo, AMOPLO_IV, iv);
+	ctl->dirty.bf.amoplo = 1;
+	return 0;
+}
+
+static int amixer_set_x(void *blk, unsigned int x)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->amoplo, AMOPLO_X, x);
+	ctl->dirty.bf.amoplo = 1;
+	return 0;
+}
+
+static int amixer_set_y(void *blk, unsigned int y)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->amoplo, AMOPLO_Y, y);
+	ctl->dirty.bf.amoplo = 1;
+	return 0;
+}
+
+static int amixer_set_sadr(void *blk, unsigned int sadr)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->amophi, AMOPHI_SADR, sadr);
+	ctl->dirty.bf.amophi = 1;
+	return 0;
+}
+
+static int amixer_set_se(void *blk, unsigned int se)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->amophi, AMOPHI_SE, se);
+	ctl->dirty.bf.amophi = 1;
+	return 0;
+}
+
+static int amixer_set_dirty(void *blk, unsigned int flags)
+{
+	((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = (flags & 0xffff);
+	return 0;
+}
+
+static int amixer_set_dirty_all(void *blk)
+{
+	((struct amixer_rsc_ctrl_blk *)blk)->dirty.data = ~(0x0);
+	return 0;
+}
+
+static int amixer_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	if (ctl->dirty.bf.amoplo || ctl->dirty.bf.amophi) {
+		hw_write_20kx(hw, MIXER_AMOPLO+idx*8, ctl->amoplo);
+		ctl->dirty.bf.amoplo = 0;
+		hw_write_20kx(hw, MIXER_AMOPHI+idx*8, ctl->amophi);
+		ctl->dirty.bf.amophi = 0;
+	}
+
+	return 0;
+}
+
+static int amixer_get_y(void *blk)
+{
+	struct amixer_rsc_ctrl_blk *ctl = blk;
+
+	return get_field(ctl->amoplo, AMOPLO_Y);
+}
+
+static unsigned int amixer_get_dirty(void *blk)
+{
+	return ((struct amixer_rsc_ctrl_blk *)blk)->dirty.data;
+}
+
+static int amixer_rsc_get_ctrl_blk(void **rblk)
+{
+	struct amixer_rsc_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int amixer_rsc_put_ctrl_blk(void *blk)
+{
+	kfree(blk);
+
+	return 0;
+}
+
+static int amixer_mgr_get_ctrl_blk(void **rblk)
+{
+	*rblk = NULL;
+
+	return 0;
+}
+
+static int amixer_mgr_put_ctrl_blk(void *blk)
+{
+	return 0;
+}
+
+/*
+ * DAIO control block definitions.
+ */
+
+/* Receiver Sample Rate Tracker Control register */
+#define SRTCTL_SRCO	0x000000FF
+#define SRTCTL_SRCM	0x0000FF00
+#define SRTCTL_RSR	0x00030000
+#define SRTCTL_DRAT	0x00300000
+#define SRTCTL_EC	0x01000000
+#define SRTCTL_ET	0x10000000
+
+/* DAIO Receiver register dirty flags */
+union dai_dirty {
+	struct {
+		u16 srt:1;
+		u16 rsv:15;
+	} bf;
+	u16 data;
+};
+
+/* DAIO Receiver control block */
+struct dai_ctrl_blk {
+	unsigned int	srt;
+	union dai_dirty	dirty;
+};
+
+/* Audio Input Mapper RAM */
+#define AIM_ARC		0x00000FFF
+#define AIM_NXT		0x007F0000
+
+struct daoimap {
+	unsigned int aim;
+	unsigned int idx;
+};
+
+/* Audio Transmitter Control and Status register */
+#define ATXCTL_EN	0x00000001
+#define ATXCTL_MODE	0x00000010
+#define ATXCTL_CD	0x00000020
+#define ATXCTL_RAW	0x00000100
+#define ATXCTL_MT	0x00000200
+#define ATXCTL_NUC	0x00003000
+#define ATXCTL_BEN	0x00010000
+#define ATXCTL_BMUX	0x00700000
+#define ATXCTL_B24	0x01000000
+#define ATXCTL_CPF	0x02000000
+#define ATXCTL_RIV	0x10000000
+#define ATXCTL_LIV	0x20000000
+#define ATXCTL_RSAT	0x40000000
+#define ATXCTL_LSAT	0x80000000
+
+/* XDIF Transmitter register dirty flags */
+union dao_dirty {
+	struct {
+		u16 atxcsl:1;
+		u16 rsv:15;
+	} bf;
+	u16 data;
+};
+
+/* XDIF Transmitter control block */
+struct dao_ctrl_blk {
+	/* XDIF Transmitter Channel Status Low Register */
+	unsigned int	atxcsl;
+	union dao_dirty	dirty;
+};
+
+/* Audio Receiver Control register */
+#define ARXCTL_EN	0x00000001
+
+/* DAIO manager register dirty flags */
+union daio_mgr_dirty {
+	struct {
+		u32 atxctl:8;
+		u32 arxctl:8;
+		u32 daoimap:1;
+		u32 rsv:15;
+	} bf;
+	u32 data;
+};
+
+/* DAIO manager control block */
+struct daio_mgr_ctrl_blk {
+	struct daoimap		daoimap;
+	unsigned int		txctl[8];
+	unsigned int		rxctl[8];
+	union daio_mgr_dirty	dirty;
+};
+
+static int dai_srt_set_srco(void *blk, unsigned int src)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srt, SRTCTL_SRCO, src);
+	ctl->dirty.bf.srt = 1;
+	return 0;
+}
+
+static int dai_srt_set_srcm(void *blk, unsigned int src)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srt, SRTCTL_SRCM, src);
+	ctl->dirty.bf.srt = 1;
+	return 0;
+}
+
+static int dai_srt_set_rsr(void *blk, unsigned int rsr)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srt, SRTCTL_RSR, rsr);
+	ctl->dirty.bf.srt = 1;
+	return 0;
+}
+
+static int dai_srt_set_drat(void *blk, unsigned int drat)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srt, SRTCTL_DRAT, drat);
+	ctl->dirty.bf.srt = 1;
+	return 0;
+}
+
+static int dai_srt_set_ec(void *blk, unsigned int ec)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srt, SRTCTL_EC, ec ? 1 : 0);
+	ctl->dirty.bf.srt = 1;
+	return 0;
+}
+
+static int dai_srt_set_et(void *blk, unsigned int et)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->srt, SRTCTL_ET, et ? 1 : 0);
+	ctl->dirty.bf.srt = 1;
+	return 0;
+}
+
+static int dai_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+	struct dai_ctrl_blk *ctl = blk;
+
+	if (ctl->dirty.bf.srt) {
+		hw_write_20kx(hw, AUDIO_IO_RX_SRT_CTL+0x40*idx, ctl->srt);
+		ctl->dirty.bf.srt = 0;
+	}
+
+	return 0;
+}
+
+static int dai_get_ctrl_blk(void **rblk)
+{
+	struct dai_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int dai_put_ctrl_blk(void *blk)
+{
+	kfree(blk);
+
+	return 0;
+}
+
+static int dao_set_spos(void *blk, unsigned int spos)
+{
+	((struct dao_ctrl_blk *)blk)->atxcsl = spos;
+	((struct dao_ctrl_blk *)blk)->dirty.bf.atxcsl = 1;
+	return 0;
+}
+
+static int dao_commit_write(struct hw *hw, unsigned int idx, void *blk)
+{
+	struct dao_ctrl_blk *ctl = blk;
+
+	if (ctl->dirty.bf.atxcsl) {
+		if (idx < 4) {
+			/* S/PDIF SPOSx */
+			hw_write_20kx(hw, AUDIO_IO_TX_CSTAT_L+0x40*idx,
+							ctl->atxcsl);
+		}
+		ctl->dirty.bf.atxcsl = 0;
+	}
+
+	return 0;
+}
+
+static int dao_get_spos(void *blk, unsigned int *spos)
+{
+	*spos = ((struct dao_ctrl_blk *)blk)->atxcsl;
+	return 0;
+}
+
+static int dao_get_ctrl_blk(void **rblk)
+{
+	struct dao_ctrl_blk *blk;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int dao_put_ctrl_blk(void *blk)
+{
+	kfree(blk);
+
+	return 0;
+}
+
+static int daio_mgr_enb_dai(void *blk, unsigned int idx)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->rxctl[idx], ARXCTL_EN, 1);
+	ctl->dirty.bf.arxctl |= (0x1 << idx);
+	return 0;
+}
+
+static int daio_mgr_dsb_dai(void *blk, unsigned int idx)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->rxctl[idx], ARXCTL_EN, 0);
+
+	ctl->dirty.bf.arxctl |= (0x1 << idx);
+	return 0;
+}
+
+static int daio_mgr_enb_dao(void *blk, unsigned int idx)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->txctl[idx], ATXCTL_EN, 1);
+	ctl->dirty.bf.atxctl |= (0x1 << idx);
+	return 0;
+}
+
+static int daio_mgr_dsb_dao(void *blk, unsigned int idx)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->txctl[idx], ATXCTL_EN, 0);
+	ctl->dirty.bf.atxctl |= (0x1 << idx);
+	return 0;
+}
+
+static int daio_mgr_dao_init(void *blk, unsigned int idx, unsigned int conf)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	if (idx < 4) {
+		/* S/PDIF output */
+		switch ((conf & 0x7)) {
+		case 1:
+			set_field(&ctl->txctl[idx], ATXCTL_NUC, 0);
+			break;
+		case 2:
+			set_field(&ctl->txctl[idx], ATXCTL_NUC, 1);
+			break;
+		case 4:
+			set_field(&ctl->txctl[idx], ATXCTL_NUC, 2);
+			break;
+		case 8:
+			set_field(&ctl->txctl[idx], ATXCTL_NUC, 3);
+			break;
+		default:
+			break;
+		}
+		/* CDIF */
+		set_field(&ctl->txctl[idx], ATXCTL_CD, (!(conf & 0x7)));
+		/* Non-audio */
+		set_field(&ctl->txctl[idx], ATXCTL_LIV, (conf >> 4) & 0x1);
+		/* Non-audio */
+		set_field(&ctl->txctl[idx], ATXCTL_RIV, (conf >> 4) & 0x1);
+		set_field(&ctl->txctl[idx], ATXCTL_RAW,
+			  ((conf >> 3) & 0x1) ? 0 : 0);
+		ctl->dirty.bf.atxctl |= (0x1 << idx);
+	} else {
+		/* I2S output */
+		/*idx %= 4; */
+	}
+	return 0;
+}
+
+static int daio_mgr_set_imaparc(void *blk, unsigned int slot)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->daoimap.aim, AIM_ARC, slot);
+	ctl->dirty.bf.daoimap = 1;
+	return 0;
+}
+
+static int daio_mgr_set_imapnxt(void *blk, unsigned int next)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+
+	set_field(&ctl->daoimap.aim, AIM_NXT, next);
+	ctl->dirty.bf.daoimap = 1;
+	return 0;
+}
+
+static int daio_mgr_set_imapaddr(void *blk, unsigned int addr)
+{
+	((struct daio_mgr_ctrl_blk *)blk)->daoimap.idx = addr;
+	((struct daio_mgr_ctrl_blk *)blk)->dirty.bf.daoimap = 1;
+	return 0;
+}
+
+static int daio_mgr_commit_write(struct hw *hw, void *blk)
+{
+	struct daio_mgr_ctrl_blk *ctl = blk;
+	unsigned int data;
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		if ((ctl->dirty.bf.atxctl & (0x1 << i))) {
+			data = ctl->txctl[i];
+			hw_write_20kx(hw, (AUDIO_IO_TX_CTL+(0x40*i)), data);
+			ctl->dirty.bf.atxctl &= ~(0x1 << i);
+			mdelay(1);
+		}
+		if ((ctl->dirty.bf.arxctl & (0x1 << i))) {
+			data = ctl->rxctl[i];
+			hw_write_20kx(hw, (AUDIO_IO_RX_CTL+(0x40*i)), data);
+			ctl->dirty.bf.arxctl &= ~(0x1 << i);
+			mdelay(1);
+		}
+	}
+	if (ctl->dirty.bf.daoimap) {
+		hw_write_20kx(hw, AUDIO_IO_AIM+ctl->daoimap.idx*4,
+						ctl->daoimap.aim);
+		ctl->dirty.bf.daoimap = 0;
+	}
+
+	return 0;
+}
+
+static int daio_mgr_get_ctrl_blk(struct hw *hw, void **rblk)
+{
+	struct daio_mgr_ctrl_blk *blk;
+	int i;
+
+	*rblk = NULL;
+	blk = kzalloc(sizeof(*blk), GFP_KERNEL);
+	if (!blk)
+		return -ENOMEM;
+
+	for (i = 0; i < 8; i++) {
+		blk->txctl[i] = hw_read_20kx(hw, AUDIO_IO_TX_CTL+(0x40*i));
+		blk->rxctl[i] = hw_read_20kx(hw, AUDIO_IO_RX_CTL+(0x40*i));
+	}
+
+	*rblk = blk;
+
+	return 0;
+}
+
+static int daio_mgr_put_ctrl_blk(void *blk)
+{
+	kfree(blk);
+
+	return 0;
+}
+
+/* Timer interrupt */
+static int set_timer_irq(struct hw *hw, int enable)
+{
+	hw_write_20kx(hw, GIE, enable ? IT_INT : 0);
+	return 0;
+}
+
+static int set_timer_tick(struct hw *hw, unsigned int ticks)
+{
+	if (ticks)
+		ticks |= TIMR_IE | TIMR_IP;
+	hw_write_20kx(hw, TIMR, ticks);
+	return 0;
+}
+
+static unsigned int get_wc(struct hw *hw)
+{
+	return hw_read_20kx(hw, WC);
+}
+
+/* Card hardware initialization block */
+struct dac_conf {
+	unsigned int msr; /* master sample rate in rsrs */
+};
+
+struct adc_conf {
+	unsigned int msr; 	/* master sample rate in rsrs */
+	unsigned char input; 	/* the input source of ADC */
+	unsigned char mic20db; 	/* boost mic by 20db if input is microphone */
+};
+
+struct daio_conf {
+	unsigned int msr; /* master sample rate in rsrs */
+};
+
+struct trn_conf {
+	unsigned long vm_pgt_phys;
+};
+
+static int hw_daio_init(struct hw *hw, const struct daio_conf *info)
+{
+	u32 data;
+	int i;
+
+	/* Program I2S with proper sample rate and enable the correct I2S
+	 * channel. ED(0/8/16/24): Enable all I2S/I2X master clock output */
+	if (1 == info->msr) {
+		hw_write_20kx(hw, AUDIO_IO_MCLK, 0x01010101);
+		hw_write_20kx(hw, AUDIO_IO_TX_BLRCLK, 0x01010101);
+		hw_write_20kx(hw, AUDIO_IO_RX_BLRCLK, 0);
+	} else if (2 == info->msr) {
+		hw_write_20kx(hw, AUDIO_IO_MCLK, 0x11111111);
+		/* Specify all playing 96khz
+		 * EA [0]	- Enabled
+		 * RTA [4:5]	- 96kHz
+		 * EB [8]	- Enabled
+		 * RTB [12:13]	- 96kHz
+		 * EC [16]	- Enabled
+		 * RTC [20:21]	- 96kHz
+		 * ED [24]	- Enabled
+		 * RTD [28:29]	- 96kHz */
+		hw_write_20kx(hw, AUDIO_IO_TX_BLRCLK, 0x11111111);
+		hw_write_20kx(hw, AUDIO_IO_RX_BLRCLK, 0);
+	} else {
+		printk(KERN_ALERT "ctxfi: ERROR!!! Invalid sampling rate!!!\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < 8; i++) {
+		if (i <= 3) {
+			/* 1st 3 channels are SPDIFs (SB0960) */
+			if (i == 3)
+				data = 0x1001001;
+			else
+				data = 0x1000001;
+
+			hw_write_20kx(hw, (AUDIO_IO_TX_CTL+(0x40*i)), data);
+			hw_write_20kx(hw, (AUDIO_IO_RX_CTL+(0x40*i)), data);
+
+			/* Initialize the SPDIF Out Channel status registers.
+			 * The value specified here is based on the typical
+			 * values provided in the specification, namely: Clock
+			 * Accuracy of 1000ppm, Sample Rate of 48KHz,
+			 * unspecified source number, Generation status = 1,
+			 * Category code = 0x12 (Digital Signal Mixer),
+			 * Mode = 0, Emph = 0, Copy Permitted, AN = 0
+			 * (indicating that we're transmitting digital audio,
+			 * and the Professional Use bit is 0. */
+
+			hw_write_20kx(hw, AUDIO_IO_TX_CSTAT_L+(0x40*i),
+					0x02109204); /* Default to 48kHz */
+
+			hw_write_20kx(hw, AUDIO_IO_TX_CSTAT_H+(0x40*i), 0x0B);
+		} else {
+			/* Next 5 channels are I2S (SB0960) */
+			data = 0x11;
+			hw_write_20kx(hw, AUDIO_IO_RX_CTL+(0x40*i), data);
+			if (2 == info->msr) {
+				/* Four channels per sample period */
+				data |= 0x1000;
+			}
+			hw_write_20kx(hw, AUDIO_IO_TX_CTL+(0x40*i), data);
+		}
+	}
+
+	return 0;
+}
+
+/* TRANSPORT operations */
+static int hw_trn_init(struct hw *hw, const struct trn_conf *info)
+{
+	u32 vmctl, data;
+	u32 ptp_phys_low, ptp_phys_high;
+	int i;
+
+	/* Set up device page table */
+	if ((~0UL) == info->vm_pgt_phys) {
+		printk(KERN_ALERT "ctxfi: "
+		       "Wrong device page table page address!!!\n");
+		return -1;
+	}
+
+	vmctl = 0x80000C0F;  /* 32-bit, 4k-size page */
+	ptp_phys_low = (u32)info->vm_pgt_phys;
+	ptp_phys_high = upper_32_bits(info->vm_pgt_phys);
+	if (sizeof(void *) == 8) /* 64bit address */
+		vmctl |= (3 << 8);
+	/* Write page table physical address to all PTPAL registers */
+	for (i = 0; i < 64; i++) {
+		hw_write_20kx(hw, VMEM_PTPAL+(16*i), ptp_phys_low);
+		hw_write_20kx(hw, VMEM_PTPAH+(16*i), ptp_phys_high);
+	}
+	/* Enable virtual memory transfer */
+	hw_write_20kx(hw, VMEM_CTL, vmctl);
+	/* Enable transport bus master and queueing of request */
+	hw_write_20kx(hw, TRANSPORT_CTL, 0x03);
+	hw_write_20kx(hw, TRANSPORT_INT, 0x200c01);
+	/* Enable transport ring */
+	data = hw_read_20kx(hw, TRANSPORT_ENB);
+	hw_write_20kx(hw, TRANSPORT_ENB, (data | 0x03));
+
+	return 0;
+}
+
+/* Card initialization */
+#define GCTL_AIE	0x00000001
+#define GCTL_UAA	0x00000002
+#define GCTL_DPC	0x00000004
+#define GCTL_DBP	0x00000008
+#define GCTL_ABP	0x00000010
+#define GCTL_TBP	0x00000020
+#define GCTL_SBP	0x00000040
+#define GCTL_FBP	0x00000080
+#define GCTL_ME		0x00000100
+#define GCTL_AID	0x00001000
+
+#define PLLCTL_SRC	0x00000007
+#define PLLCTL_SPE	0x00000008
+#define PLLCTL_RD	0x000000F0
+#define PLLCTL_FD	0x0001FF00
+#define PLLCTL_OD	0x00060000
+#define PLLCTL_B	0x00080000
+#define PLLCTL_AS	0x00100000
+#define PLLCTL_LF	0x03E00000
+#define PLLCTL_SPS	0x1C000000
+#define PLLCTL_AD	0x60000000
+
+#define PLLSTAT_CCS	0x00000007
+#define PLLSTAT_SPL	0x00000008
+#define PLLSTAT_CRD	0x000000F0
+#define PLLSTAT_CFD	0x0001FF00
+#define PLLSTAT_SL	0x00020000
+#define PLLSTAT_FAS	0x00040000
+#define PLLSTAT_B	0x00080000
+#define PLLSTAT_PD	0x00100000
+#define PLLSTAT_OCA	0x00200000
+#define PLLSTAT_NCA	0x00400000
+
+static int hw_pll_init(struct hw *hw, unsigned int rsr)
+{
+	unsigned int pllenb;
+	unsigned int pllctl;
+	unsigned int pllstat;
+	int i;
+
+	pllenb = 0xB;
+	hw_write_20kx(hw, PLL_ENB, pllenb);
+	pllctl = 0x20D00000;
+	set_field(&pllctl, PLLCTL_FD, 16 - 4);
+	hw_write_20kx(hw, PLL_CTL, pllctl);
+	mdelay(40);
+	pllctl = hw_read_20kx(hw, PLL_CTL);
+	set_field(&pllctl, PLLCTL_B, 0);
+	if (48000 == rsr) {
+		set_field(&pllctl, PLLCTL_FD, 16 - 2);
+		set_field(&pllctl, PLLCTL_RD, 1 - 1);
+	} else { /* 44100 */
+		set_field(&pllctl, PLLCTL_FD, 147 - 2);
+		set_field(&pllctl, PLLCTL_RD, 10 - 1);
+	}
+	hw_write_20kx(hw, PLL_CTL, pllctl);
+	mdelay(40);
+	for (i = 0; i < 1000; i++) {
+		pllstat = hw_read_20kx(hw, PLL_STAT);
+		if (get_field(pllstat, PLLSTAT_PD))
+			continue;
+
+		if (get_field(pllstat, PLLSTAT_B) !=
+					get_field(pllctl, PLLCTL_B))
+			continue;
+
+		if (get_field(pllstat, PLLSTAT_CCS) !=
+					get_field(pllctl, PLLCTL_SRC))
+			continue;
+
+		if (get_field(pllstat, PLLSTAT_CRD) !=
+					get_field(pllctl, PLLCTL_RD))
+			continue;
+
+		if (get_field(pllstat, PLLSTAT_CFD) !=
+					get_field(pllctl, PLLCTL_FD))
+			continue;
+
+		break;
+	}
+	if (i >= 1000) {
+		printk(KERN_ALERT "ctxfi: PLL initialization failed!!!\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static int hw_auto_init(struct hw *hw)
+{
+	unsigned int gctl;
+	int i;
+
+	gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL);
+	set_field(&gctl, GCTL_AIE, 0);
+	hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl);
+	set_field(&gctl, GCTL_AIE, 1);
+	hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl);
+	mdelay(10);
+	for (i = 0; i < 400000; i++) {
+		gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL);
+		if (get_field(gctl, GCTL_AID))
+			break;
+	}
+	if (!get_field(gctl, GCTL_AID)) {
+		printk(KERN_ALERT "ctxfi: Card Auto-init failed!!!\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+/* DAC operations */
+
+#define CS4382_MC1 		0x1
+#define CS4382_MC2 		0x2
+#define CS4382_MC3		0x3
+#define CS4382_FC		0x4
+#define CS4382_IC		0x5
+#define CS4382_XC1		0x6
+#define CS4382_VCA1 		0x7
+#define CS4382_VCB1 		0x8
+#define CS4382_XC2		0x9
+#define CS4382_VCA2 		0xA
+#define CS4382_VCB2 		0xB
+#define CS4382_XC3		0xC
+#define CS4382_VCA3		0xD
+#define CS4382_VCB3		0xE
+#define CS4382_XC4 		0xF
+#define CS4382_VCA4 		0x10
+#define CS4382_VCB4 		0x11
+#define CS4382_CREV 		0x12
+
+/* I2C status */
+#define STATE_LOCKED		0x00
+#define STATE_UNLOCKED		0xAA
+#define DATA_READY		0x800000    /* Used with I2C_IF_STATUS */
+#define DATA_ABORT		0x10000     /* Used with I2C_IF_STATUS */
+
+#define I2C_STATUS_DCM	0x00000001
+#define I2C_STATUS_BC	0x00000006
+#define I2C_STATUS_APD	0x00000008
+#define I2C_STATUS_AB	0x00010000
+#define I2C_STATUS_DR	0x00800000
+
+#define I2C_ADDRESS_PTAD	0x0000FFFF
+#define I2C_ADDRESS_SLAD	0x007F0000
+
+struct regs_cs4382 {
+	u32 mode_control_1;
+	u32 mode_control_2;
+	u32 mode_control_3;
+
+	u32 filter_control;
+	u32 invert_control;
+
+	u32 mix_control_P1;
+	u32 vol_control_A1;
+	u32 vol_control_B1;
+
+	u32 mix_control_P2;
+	u32 vol_control_A2;
+	u32 vol_control_B2;
+
+	u32 mix_control_P3;
+	u32 vol_control_A3;
+	u32 vol_control_B3;
+
+	u32 mix_control_P4;
+	u32 vol_control_A4;
+	u32 vol_control_B4;
+};
+
+static int hw20k2_i2c_unlock_full_access(struct hw *hw)
+{
+	u8 UnlockKeySequence_FLASH_FULLACCESS_MODE[2] =  {0xB3, 0xD4};
+
+	/* Send keys for forced BIOS mode */
+	hw_write_20kx(hw, I2C_IF_WLOCK,
+			UnlockKeySequence_FLASH_FULLACCESS_MODE[0]);
+	hw_write_20kx(hw, I2C_IF_WLOCK,
+			UnlockKeySequence_FLASH_FULLACCESS_MODE[1]);
+	/* Check whether the chip is unlocked */
+	if (hw_read_20kx(hw, I2C_IF_WLOCK) == STATE_UNLOCKED)
+		return 0;
+
+	return -1;
+}
+
+static int hw20k2_i2c_lock_chip(struct hw *hw)
+{
+	/* Write twice */
+	hw_write_20kx(hw, I2C_IF_WLOCK, STATE_LOCKED);
+	hw_write_20kx(hw, I2C_IF_WLOCK, STATE_LOCKED);
+	if (hw_read_20kx(hw, I2C_IF_WLOCK) == STATE_LOCKED)
+		return 0;
+
+	return -1;
+}
+
+static int hw20k2_i2c_init(struct hw *hw, u8 dev_id, u8 addr_size, u8 data_size)
+{
+	struct hw20k2 *hw20k2 = (struct hw20k2 *)hw;
+	int err;
+	unsigned int i2c_status;
+	unsigned int i2c_addr;
+
+	err = hw20k2_i2c_unlock_full_access(hw);
+	if (err < 0)
+		return err;
+
+	hw20k2->addr_size = addr_size;
+	hw20k2->data_size = data_size;
+	hw20k2->dev_id = dev_id;
+
+	i2c_addr = 0;
+	set_field(&i2c_addr, I2C_ADDRESS_SLAD, dev_id);
+
+	hw_write_20kx(hw, I2C_IF_ADDRESS, i2c_addr);
+
+	i2c_status = hw_read_20kx(hw, I2C_IF_STATUS);
+
+	set_field(&i2c_status, I2C_STATUS_DCM, 1); /* Direct control mode */
+
+	hw_write_20kx(hw, I2C_IF_STATUS, i2c_status);
+
+	return 0;
+}
+
+static int hw20k2_i2c_uninit(struct hw *hw)
+{
+	unsigned int i2c_status;
+	unsigned int i2c_addr;
+
+	i2c_addr = 0;
+	set_field(&i2c_addr, I2C_ADDRESS_SLAD, 0x57); /* I2C id */
+
+	hw_write_20kx(hw, I2C_IF_ADDRESS, i2c_addr);
+
+	i2c_status = hw_read_20kx(hw, I2C_IF_STATUS);
+
+	set_field(&i2c_status, I2C_STATUS_DCM, 0); /* I2C mode */
+
+	hw_write_20kx(hw, I2C_IF_STATUS, i2c_status);
+
+	return hw20k2_i2c_lock_chip(hw);
+}
+
+static int hw20k2_i2c_wait_data_ready(struct hw *hw)
+{
+	int i = 0x400000;
+	unsigned int ret;
+
+	do {
+		ret = hw_read_20kx(hw, I2C_IF_STATUS);
+	} while ((!(ret & DATA_READY)) && --i);
+
+	return i;
+}
+
+static int hw20k2_i2c_read(struct hw *hw, u16 addr, u32 *datap)
+{
+	struct hw20k2 *hw20k2 = (struct hw20k2 *)hw;
+	unsigned int i2c_status;
+
+	i2c_status = hw_read_20kx(hw, I2C_IF_STATUS);
+	set_field(&i2c_status, I2C_STATUS_BC,
+		  (4 == hw20k2->addr_size) ? 0 : hw20k2->addr_size);
+	hw_write_20kx(hw, I2C_IF_STATUS, i2c_status);
+	if (!hw20k2_i2c_wait_data_ready(hw))
+		return -1;
+
+	hw_write_20kx(hw, I2C_IF_WDATA, addr);
+	if (!hw20k2_i2c_wait_data_ready(hw))
+		return -1;
+
+	/* Force a read operation */
+	hw_write_20kx(hw, I2C_IF_RDATA, 0);
+	if (!hw20k2_i2c_wait_data_ready(hw))
+		return -1;
+
+	*datap = hw_read_20kx(hw, I2C_IF_RDATA);
+
+	return 0;
+}
+
+static int hw20k2_i2c_write(struct hw *hw, u16 addr, u32 data)
+{
+	struct hw20k2 *hw20k2 = (struct hw20k2 *)hw;
+	unsigned int i2c_data = (data << (hw20k2->addr_size * 8)) | addr;
+	unsigned int i2c_status;
+
+	i2c_status = hw_read_20kx(hw, I2C_IF_STATUS);
+
+	set_field(&i2c_status, I2C_STATUS_BC,
+		  (4 == (hw20k2->addr_size + hw20k2->data_size)) ?
+		  0 : (hw20k2->addr_size + hw20k2->data_size));
+
+	hw_write_20kx(hw, I2C_IF_STATUS, i2c_status);
+	hw20k2_i2c_wait_data_ready(hw);
+	/* Dummy write to trigger the write oprtation */
+	hw_write_20kx(hw, I2C_IF_WDATA, 0);
+	hw20k2_i2c_wait_data_ready(hw);
+
+	/* This is the real data */
+	hw_write_20kx(hw, I2C_IF_WDATA, i2c_data);
+	hw20k2_i2c_wait_data_ready(hw);
+
+	return 0;
+}
+
+static int hw_dac_init(struct hw *hw, const struct dac_conf *info)
+{
+	int err;
+	u32 data;
+	int i;
+	struct regs_cs4382 cs_read = {0};
+	struct regs_cs4382 cs_def = {
+				   0x00000001,  /* Mode Control 1 */
+				   0x00000000,  /* Mode Control 2 */
+				   0x00000084,  /* Mode Control 3 */
+				   0x00000000,  /* Filter Control */
+				   0x00000000,  /* Invert Control */
+				   0x00000024,  /* Mixing Control Pair 1 */
+				   0x00000000,  /* Vol Control A1 */
+				   0x00000000,  /* Vol Control B1 */
+				   0x00000024,  /* Mixing Control Pair 2 */
+				   0x00000000,  /* Vol Control A2 */
+				   0x00000000,  /* Vol Control B2 */
+				   0x00000024,  /* Mixing Control Pair 3 */
+				   0x00000000,  /* Vol Control A3 */
+				   0x00000000,  /* Vol Control B3 */
+				   0x00000024,  /* Mixing Control Pair 4 */
+				   0x00000000,  /* Vol Control A4 */
+				   0x00000000   /* Vol Control B4 */
+				 };
+
+	/* Set DAC reset bit as output */
+	data = hw_read_20kx(hw, GPIO_CTRL);
+	data |= 0x02;
+	hw_write_20kx(hw, GPIO_CTRL, data);
+
+	err = hw20k2_i2c_init(hw, 0x18, 1, 1);
+	if (err < 0)
+		goto End;
+
+	for (i = 0; i < 2; i++) {
+		/* Reset DAC twice just in-case the chip
+		 * didn't initialized properly */
+		data = hw_read_20kx(hw, GPIO_DATA);
+		/* GPIO data bit 1 */
+		data &= 0xFFFFFFFD;
+		hw_write_20kx(hw, GPIO_DATA, data);
+		mdelay(10);
+		data |= 0x2;
+		hw_write_20kx(hw, GPIO_DATA, data);
+		mdelay(50);
+
+		/* Reset the 2nd time */
+		data &= 0xFFFFFFFD;
+		hw_write_20kx(hw, GPIO_DATA, data);
+		mdelay(10);
+		data |= 0x2;
+		hw_write_20kx(hw, GPIO_DATA, data);
+		mdelay(50);
+
+		if (hw20k2_i2c_read(hw, CS4382_MC1,  &cs_read.mode_control_1))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_MC2,  &cs_read.mode_control_2))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_MC3,  &cs_read.mode_control_3))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_FC,   &cs_read.filter_control))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_IC,   &cs_read.invert_control))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_XC1,  &cs_read.mix_control_P1))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_VCA1, &cs_read.vol_control_A1))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_VCB1, &cs_read.vol_control_B1))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_XC2,  &cs_read.mix_control_P2))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_VCA2, &cs_read.vol_control_A2))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_VCB2, &cs_read.vol_control_B2))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_XC3,  &cs_read.mix_control_P3))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_VCA3, &cs_read.vol_control_A3))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_VCB3, &cs_read.vol_control_B3))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_XC4,  &cs_read.mix_control_P4))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_VCA4, &cs_read.vol_control_A4))
+			continue;
+
+		if (hw20k2_i2c_read(hw, CS4382_VCB4, &cs_read.vol_control_B4))
+			continue;
+
+		if (memcmp(&cs_read, &cs_def, sizeof(cs_read)))
+			continue;
+		else
+			break;
+	}
+
+	if (i >= 2)
+		goto End;
+
+	/* Note: Every I2C write must have some delay.
+	 * This is not a requirement but the delay works here... */
+	hw20k2_i2c_write(hw, CS4382_MC1, 0x80);
+	hw20k2_i2c_write(hw, CS4382_MC2, 0x10);
+	if (1 == info->msr) {
+		hw20k2_i2c_write(hw, CS4382_XC1, 0x24);
+		hw20k2_i2c_write(hw, CS4382_XC2, 0x24);
+		hw20k2_i2c_write(hw, CS4382_XC3, 0x24);
+		hw20k2_i2c_write(hw, CS4382_XC4, 0x24);
+	} else if (2 == info->msr) {
+		hw20k2_i2c_write(hw, CS4382_XC1, 0x25);
+		hw20k2_i2c_write(hw, CS4382_XC2, 0x25);
+		hw20k2_i2c_write(hw, CS4382_XC3, 0x25);
+		hw20k2_i2c_write(hw, CS4382_XC4, 0x25);
+	} else {
+		hw20k2_i2c_write(hw, CS4382_XC1, 0x26);
+		hw20k2_i2c_write(hw, CS4382_XC2, 0x26);
+		hw20k2_i2c_write(hw, CS4382_XC3, 0x26);
+		hw20k2_i2c_write(hw, CS4382_XC4, 0x26);
+	}
+
+	return 0;
+End:
+
+	hw20k2_i2c_uninit(hw);
+	return -1;
+}
+
+/* ADC operations */
+#define MAKE_WM8775_ADDR(addr, data)	(u32)(((addr<<1)&0xFE)|((data>>8)&0x1))
+#define MAKE_WM8775_DATA(data)	(u32)(data&0xFF)
+
+#define WM8775_IC       0x0B
+#define WM8775_MMC      0x0C
+#define WM8775_AADCL    0x0E
+#define WM8775_AADCR    0x0F
+#define WM8775_ADCMC    0x15
+#define WM8775_RESET    0x17
+
+static int hw_is_adc_input_selected(struct hw *hw, enum ADCSRC type)
+{
+	u32 data;
+
+	data = hw_read_20kx(hw, GPIO_DATA);
+	switch (type) {
+	case ADC_MICIN:
+		data = (data & (0x1 << 14)) ? 1 : 0;
+		break;
+	case ADC_LINEIN:
+		data = (data & (0x1 << 14)) ? 0 : 1;
+		break;
+	default:
+		data = 0;
+	}
+	return data;
+}
+
+static int hw_adc_input_select(struct hw *hw, enum ADCSRC type)
+{
+	u32 data;
+
+	data = hw_read_20kx(hw, GPIO_DATA);
+	switch (type) {
+	case ADC_MICIN:
+		data |= (0x1 << 14);
+		hw_write_20kx(hw, GPIO_DATA, data);
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_ADCMC, 0x101),
+				MAKE_WM8775_DATA(0x101)); /* Mic-in */
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCL, 0xE7),
+				MAKE_WM8775_DATA(0xE7)); /* +12dB boost */
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCR, 0xE7),
+				MAKE_WM8775_DATA(0xE7)); /* +12dB boost */
+		break;
+	case ADC_LINEIN:
+		data &= ~(0x1 << 14);
+		hw_write_20kx(hw, GPIO_DATA, data);
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_ADCMC, 0x102),
+				MAKE_WM8775_DATA(0x102)); /* Line-in */
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCL, 0xCF),
+				MAKE_WM8775_DATA(0xCF)); /* No boost */
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCR, 0xCF),
+				MAKE_WM8775_DATA(0xCF)); /* No boost */
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int hw_adc_init(struct hw *hw, const struct adc_conf *info)
+{
+	int err;
+	u32 mux = 2, data, ctl;
+
+	/*  Set ADC reset bit as output */
+	data = hw_read_20kx(hw, GPIO_CTRL);
+	data |= (0x1 << 15);
+	hw_write_20kx(hw, GPIO_CTRL, data);
+
+	/* Initialize I2C */
+	err = hw20k2_i2c_init(hw, 0x1A, 1, 1);
+	if (err < 0) {
+		printk(KERN_ALERT "ctxfi: Failure to acquire I2C!!!\n");
+		goto error;
+	}
+
+	/* Make ADC in normal operation */
+	data = hw_read_20kx(hw, GPIO_DATA);
+	data &= ~(0x1 << 15);
+	mdelay(10);
+	data |= (0x1 << 15);
+	hw_write_20kx(hw, GPIO_DATA, data);
+	mdelay(50);
+
+	/* Set the master mode (256fs) */
+	if (1 == info->msr) {
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_MMC, 0x02),
+						MAKE_WM8775_DATA(0x02));
+	} else if (2 == info->msr) {
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_MMC, 0x0A),
+						MAKE_WM8775_DATA(0x0A));
+	} else {
+		printk(KERN_ALERT "ctxfi: Invalid master sampling "
+				  "rate (msr %d)!!!\n", info->msr);
+		err = -EINVAL;
+		goto error;
+	}
+
+	/* Configure GPIO bit 14 change to line-in/mic-in */
+	ctl = hw_read_20kx(hw, GPIO_CTRL);
+	ctl |= 0x1 << 14;
+	hw_write_20kx(hw, GPIO_CTRL, ctl);
+
+	/* Check using Mic-in or Line-in */
+	data = hw_read_20kx(hw, GPIO_DATA);
+
+	if (mux == 1) {
+		/* Configures GPIO data to select Mic-in */
+		data |= 0x1 << 14;
+		hw_write_20kx(hw, GPIO_DATA, data);
+
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_ADCMC, 0x101),
+				MAKE_WM8775_DATA(0x101)); /* Mic-in */
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCL, 0xE7),
+				MAKE_WM8775_DATA(0xE7)); /* +12dB boost */
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCR, 0xE7),
+				MAKE_WM8775_DATA(0xE7)); /* +12dB boost */
+	} else if (mux == 2) {
+		/* Configures GPIO data to select Line-in */
+		data &= ~(0x1 << 14);
+		hw_write_20kx(hw, GPIO_DATA, data);
+
+		/* Setup ADC */
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_ADCMC, 0x102),
+				MAKE_WM8775_DATA(0x102)); /* Line-in */
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCL, 0xCF),
+				MAKE_WM8775_DATA(0xCF)); /* No boost */
+		hw20k2_i2c_write(hw, MAKE_WM8775_ADDR(WM8775_AADCR, 0xCF),
+				MAKE_WM8775_DATA(0xCF)); /* No boost */
+	} else {
+		printk(KERN_ALERT "ctxfi: ERROR!!! Invalid input mux!!!\n");
+		err = -EINVAL;
+		goto error;
+	}
+
+	return 0;
+
+error:
+	hw20k2_i2c_uninit(hw);
+	return err;
+}
+
+static int hw_have_digit_io_switch(struct hw *hw)
+{
+	return 0;
+}
+
+static irqreturn_t ct_20k2_interrupt(int irq, void *dev_id)
+{
+	struct hw *hw = dev_id;
+	unsigned int status;
+
+	status = hw_read_20kx(hw, GIP);
+	if (!status)
+		return IRQ_NONE;
+
+	if (hw->irq_callback)
+		hw->irq_callback(hw->irq_callback_data, status);
+
+	hw_write_20kx(hw, GIP, status);
+	return IRQ_HANDLED;
+}
+
+static int hw_card_start(struct hw *hw)
+{
+	int err = 0;
+	struct pci_dev *pci = hw->pci;
+	unsigned int gctl;
+
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+
+	/* Set DMA transfer mask */
+	if (pci_set_dma_mask(pci, CT_XFI_DMA_MASK) < 0 ||
+	    pci_set_consistent_dma_mask(pci, CT_XFI_DMA_MASK) < 0) {
+		printk(KERN_ERR "ctxfi: architecture does not support PCI "
+		"busmaster DMA with mask 0x%llx\n", CT_XFI_DMA_MASK);
+		err = -ENXIO;
+		goto error1;
+	}
+
+	if (!hw->io_base) {
+		err = pci_request_regions(pci, "XFi");
+		if (err < 0)
+			goto error1;
+
+		hw->io_base = pci_resource_start(hw->pci, 2);
+		hw->mem_base = (unsigned long)ioremap(hw->io_base,
+					pci_resource_len(hw->pci, 2));
+		if (!hw->mem_base) {
+			err = -ENOENT;
+			goto error2;
+		}
+	}
+
+	/* Switch to 20k2 mode from UAA mode. */
+	gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL);
+	set_field(&gctl, GCTL_UAA, 0);
+	hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl);
+
+	if (hw->irq < 0) {
+		err = request_irq(pci->irq, ct_20k2_interrupt, IRQF_SHARED,
+				  "ctxfi", hw);
+		if (err < 0) {
+			printk(KERN_ERR "XFi: Cannot get irq %d\n", pci->irq);
+			goto error2;
+		}
+		hw->irq = pci->irq;
+	}
+
+	pci_set_master(pci);
+
+	return 0;
+
+/*error3:
+	iounmap((void *)hw->mem_base);
+	hw->mem_base = (unsigned long)NULL;*/
+error2:
+	pci_release_regions(pci);
+	hw->io_base = 0;
+error1:
+	pci_disable_device(pci);
+	return err;
+}
+
+static int hw_card_stop(struct hw *hw)
+{
+	unsigned int data;
+
+	/* disable transport bus master and queueing of request */
+	hw_write_20kx(hw, TRANSPORT_CTL, 0x00);
+
+	/* disable pll */
+	data = hw_read_20kx(hw, PLL_ENB);
+	hw_write_20kx(hw, PLL_ENB, (data & (~0x07)));
+
+	/* TODO: Disable interrupt and so on... */
+	return 0;
+}
+
+static int hw_card_shutdown(struct hw *hw)
+{
+	if (hw->irq >= 0)
+		free_irq(hw->irq, hw);
+
+	hw->irq	= -1;
+
+	if (hw->mem_base)
+		iounmap((void *)hw->mem_base);
+
+	hw->mem_base = (unsigned long)NULL;
+
+	if (hw->io_base)
+		pci_release_regions(hw->pci);
+
+	hw->io_base = 0;
+
+	pci_disable_device(hw->pci);
+
+	return 0;
+}
+
+static int hw_card_init(struct hw *hw, struct card_conf *info)
+{
+	int err;
+	unsigned int gctl;
+	u32 data = 0;
+	struct dac_conf dac_info = {0};
+	struct adc_conf adc_info = {0};
+	struct daio_conf daio_info = {0};
+	struct trn_conf trn_info = {0};
+
+	/* Get PCI io port/memory base address and
+	 * do 20kx core switch if needed. */
+	err = hw_card_start(hw);
+	if (err)
+		return err;
+
+	/* PLL init */
+	err = hw_pll_init(hw, info->rsr);
+	if (err < 0)
+		return err;
+
+	/* kick off auto-init */
+	err = hw_auto_init(hw);
+	if (err < 0)
+		return err;
+
+	gctl = hw_read_20kx(hw, GLOBAL_CNTL_GCTL);
+	set_field(&gctl, GCTL_DBP, 1);
+	set_field(&gctl, GCTL_TBP, 1);
+	set_field(&gctl, GCTL_FBP, 1);
+	set_field(&gctl, GCTL_DPC, 0);
+	hw_write_20kx(hw, GLOBAL_CNTL_GCTL, gctl);
+
+	/* Reset all global pending interrupts */
+	hw_write_20kx(hw, GIE, 0);
+	/* Reset all SRC pending interrupts */
+	hw_write_20kx(hw, SRC_IP, 0);
+
+	/* TODO: detect the card ID and configure GPIO accordingly. */
+	/* Configures GPIO (0xD802 0x98028) */
+	/*hw_write_20kx(hw, GPIO_CTRL, 0x7F07);*/
+	/* Configures GPIO (SB0880) */
+	/*hw_write_20kx(hw, GPIO_CTRL, 0xFF07);*/
+	hw_write_20kx(hw, GPIO_CTRL, 0xD802);
+
+	/* Enable audio ring */
+	hw_write_20kx(hw, MIXER_AR_ENABLE, 0x01);
+
+	trn_info.vm_pgt_phys = info->vm_pgt_phys;
+	err = hw_trn_init(hw, &trn_info);
+	if (err < 0)
+		return err;
+
+	daio_info.msr = info->msr;
+	err = hw_daio_init(hw, &daio_info);
+	if (err < 0)
+		return err;
+
+	dac_info.msr = info->msr;
+	err = hw_dac_init(hw, &dac_info);
+	if (err < 0)
+		return err;
+
+	adc_info.msr = info->msr;
+	adc_info.input = ADC_LINEIN;
+	adc_info.mic20db = 0;
+	err = hw_adc_init(hw, &adc_info);
+	if (err < 0)
+		return err;
+
+	data = hw_read_20kx(hw, SRC_MCTL);
+	data |= 0x1; /* Enables input from the audio ring */
+	hw_write_20kx(hw, SRC_MCTL, data);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int hw_suspend(struct hw *hw, pm_message_t state)
+{
+	struct pci_dev *pci = hw->pci;
+
+	hw_card_stop(hw);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+
+	return 0;
+}
+
+static int hw_resume(struct hw *hw, struct card_conf *info)
+{
+	struct pci_dev *pci = hw->pci;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+
+	/* Re-initialize card hardware. */
+	return hw_card_init(hw, info);
+}
+#endif
+
+static u32 hw_read_20kx(struct hw *hw, u32 reg)
+{
+	return readl((void *)(hw->mem_base + reg));
+}
+
+static void hw_write_20kx(struct hw *hw, u32 reg, u32 data)
+{
+	writel(data, (void *)(hw->mem_base + reg));
+}
+
+static struct hw ct20k2_preset __devinitdata = {
+	.irq = -1,
+
+	.card_init = hw_card_init,
+	.card_stop = hw_card_stop,
+	.pll_init = hw_pll_init,
+	.is_adc_source_selected = hw_is_adc_input_selected,
+	.select_adc_source = hw_adc_input_select,
+	.have_digit_io_switch = hw_have_digit_io_switch,
+#ifdef CONFIG_PM
+	.suspend = hw_suspend,
+	.resume = hw_resume,
+#endif
+
+	.src_rsc_get_ctrl_blk = src_get_rsc_ctrl_blk,
+	.src_rsc_put_ctrl_blk = src_put_rsc_ctrl_blk,
+	.src_mgr_get_ctrl_blk = src_mgr_get_ctrl_blk,
+	.src_mgr_put_ctrl_blk = src_mgr_put_ctrl_blk,
+	.src_set_state = src_set_state,
+	.src_set_bm = src_set_bm,
+	.src_set_rsr = src_set_rsr,
+	.src_set_sf = src_set_sf,
+	.src_set_wr = src_set_wr,
+	.src_set_pm = src_set_pm,
+	.src_set_rom = src_set_rom,
+	.src_set_vo = src_set_vo,
+	.src_set_st = src_set_st,
+	.src_set_ie = src_set_ie,
+	.src_set_ilsz = src_set_ilsz,
+	.src_set_bp = src_set_bp,
+	.src_set_cisz = src_set_cisz,
+	.src_set_ca = src_set_ca,
+	.src_set_sa = src_set_sa,
+	.src_set_la = src_set_la,
+	.src_set_pitch = src_set_pitch,
+	.src_set_dirty = src_set_dirty,
+	.src_set_clear_zbufs = src_set_clear_zbufs,
+	.src_set_dirty_all = src_set_dirty_all,
+	.src_commit_write = src_commit_write,
+	.src_get_ca = src_get_ca,
+	.src_get_dirty = src_get_dirty,
+	.src_dirty_conj_mask = src_dirty_conj_mask,
+	.src_mgr_enbs_src = src_mgr_enbs_src,
+	.src_mgr_enb_src = src_mgr_enb_src,
+	.src_mgr_dsb_src = src_mgr_dsb_src,
+	.src_mgr_commit_write = src_mgr_commit_write,
+
+	.srcimp_mgr_get_ctrl_blk = srcimp_mgr_get_ctrl_blk,
+	.srcimp_mgr_put_ctrl_blk = srcimp_mgr_put_ctrl_blk,
+	.srcimp_mgr_set_imaparc = srcimp_mgr_set_imaparc,
+	.srcimp_mgr_set_imapuser = srcimp_mgr_set_imapuser,
+	.srcimp_mgr_set_imapnxt = srcimp_mgr_set_imapnxt,
+	.srcimp_mgr_set_imapaddr = srcimp_mgr_set_imapaddr,
+	.srcimp_mgr_commit_write = srcimp_mgr_commit_write,
+
+	.amixer_rsc_get_ctrl_blk = amixer_rsc_get_ctrl_blk,
+	.amixer_rsc_put_ctrl_blk = amixer_rsc_put_ctrl_blk,
+	.amixer_mgr_get_ctrl_blk = amixer_mgr_get_ctrl_blk,
+	.amixer_mgr_put_ctrl_blk = amixer_mgr_put_ctrl_blk,
+	.amixer_set_mode = amixer_set_mode,
+	.amixer_set_iv = amixer_set_iv,
+	.amixer_set_x = amixer_set_x,
+	.amixer_set_y = amixer_set_y,
+	.amixer_set_sadr = amixer_set_sadr,
+	.amixer_set_se = amixer_set_se,
+	.amixer_set_dirty = amixer_set_dirty,
+	.amixer_set_dirty_all = amixer_set_dirty_all,
+	.amixer_commit_write = amixer_commit_write,
+	.amixer_get_y = amixer_get_y,
+	.amixer_get_dirty = amixer_get_dirty,
+
+	.dai_get_ctrl_blk = dai_get_ctrl_blk,
+	.dai_put_ctrl_blk = dai_put_ctrl_blk,
+	.dai_srt_set_srco = dai_srt_set_srco,
+	.dai_srt_set_srcm = dai_srt_set_srcm,
+	.dai_srt_set_rsr = dai_srt_set_rsr,
+	.dai_srt_set_drat = dai_srt_set_drat,
+	.dai_srt_set_ec = dai_srt_set_ec,
+	.dai_srt_set_et = dai_srt_set_et,
+	.dai_commit_write = dai_commit_write,
+
+	.dao_get_ctrl_blk = dao_get_ctrl_blk,
+	.dao_put_ctrl_blk = dao_put_ctrl_blk,
+	.dao_set_spos = dao_set_spos,
+	.dao_commit_write = dao_commit_write,
+	.dao_get_spos = dao_get_spos,
+
+	.daio_mgr_get_ctrl_blk = daio_mgr_get_ctrl_blk,
+	.daio_mgr_put_ctrl_blk = daio_mgr_put_ctrl_blk,
+	.daio_mgr_enb_dai = daio_mgr_enb_dai,
+	.daio_mgr_dsb_dai = daio_mgr_dsb_dai,
+	.daio_mgr_enb_dao = daio_mgr_enb_dao,
+	.daio_mgr_dsb_dao = daio_mgr_dsb_dao,
+	.daio_mgr_dao_init = daio_mgr_dao_init,
+	.daio_mgr_set_imaparc = daio_mgr_set_imaparc,
+	.daio_mgr_set_imapnxt = daio_mgr_set_imapnxt,
+	.daio_mgr_set_imapaddr = daio_mgr_set_imapaddr,
+	.daio_mgr_commit_write = daio_mgr_commit_write,
+
+	.set_timer_irq = set_timer_irq,
+	.set_timer_tick = set_timer_tick,
+	.get_wc = get_wc,
+};
+
+int __devinit create_20k2_hw_obj(struct hw **rhw)
+{
+	struct hw20k2 *hw20k2;
+
+	*rhw = NULL;
+	hw20k2 = kzalloc(sizeof(*hw20k2), GFP_KERNEL);
+	if (!hw20k2)
+		return -ENOMEM;
+
+	hw20k2->hw = ct20k2_preset;
+	*rhw = &hw20k2->hw;
+
+	return 0;
+}
+
+int destroy_20k2_hw_obj(struct hw *hw)
+{
+	if (hw->io_base)
+		hw_card_shutdown(hw);
+
+	kfree(hw);
+	return 0;
+}
diff --git a/linux/sound/pci/ctxfi/cthw20k2.h b/linux/sound/pci/ctxfi/cthw20k2.h
new file mode 100644
index 0000000..d2b7daa
--- /dev/null
+++ b/linux/sound/pci/ctxfi/cthw20k2.h
@@ -0,0 +1,26 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	cthw20k2.h
+ *
+ * @Brief
+ * This file contains the definition of hardware access methord.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 13 2008
+ *
+ */
+
+#ifndef CTHW20K2_H
+#define CTHW20K2_H
+
+#include "cthardware.h"
+
+int create_20k2_hw_obj(struct hw **rhw);
+int destroy_20k2_hw_obj(struct hw *hw);
+
+#endif /* CTHW20K2_H */
diff --git a/linux/sound/pci/ctxfi/ctimap.c b/linux/sound/pci/ctxfi/ctimap.c
new file mode 100644
index 0000000..0b73368
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctimap.c
@@ -0,0 +1,112 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctimap.c
+ *
+ * @Brief
+ * This file contains the implementation of generic input mapper operations
+ * for input mapper management.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 23 2008
+ *
+ */
+
+#include "ctimap.h"
+#include <linux/slab.h>
+
+int input_mapper_add(struct list_head *mappers, struct imapper *entry,
+		     int (*map_op)(void *, struct imapper *), void *data)
+{
+	struct list_head *pos, *pre, *head;
+	struct imapper *pre_ent, *pos_ent;
+
+	head = mappers;
+
+	if (list_empty(head)) {
+		entry->next = entry->addr;
+		map_op(data, entry);
+		list_add(&entry->list, head);
+		return 0;
+	}
+
+	list_for_each(pos, head) {
+		pos_ent = list_entry(pos, struct imapper, list);
+		if (pos_ent->slot > entry->slot) {
+			/* found a position in list */
+			break;
+		}
+	}
+
+	if (pos != head) {
+		pre = pos->prev;
+		if (pre == head)
+			pre = head->prev;
+
+		__list_add(&entry->list, pos->prev, pos);
+	} else {
+		pre = head->prev;
+		pos = head->next;
+		list_add_tail(&entry->list, head);
+	}
+
+	pre_ent = list_entry(pre, struct imapper, list);
+	pos_ent = list_entry(pos, struct imapper, list);
+
+	entry->next = pos_ent->addr;
+	map_op(data, entry);
+	pre_ent->next = entry->addr;
+	map_op(data, pre_ent);
+
+	return 0;
+}
+
+int input_mapper_delete(struct list_head *mappers, struct imapper *entry,
+		     int (*map_op)(void *, struct imapper *), void *data)
+{
+	struct list_head *next, *pre, *head;
+	struct imapper *pre_ent, *next_ent;
+
+	head = mappers;
+
+	if (list_empty(head))
+		return 0;
+
+	pre = (entry->list.prev == head) ? head->prev : entry->list.prev;
+	next = (entry->list.next == head) ? head->next : entry->list.next;
+
+	if (pre == &entry->list) {
+		/* entry is the only one node in mappers list */
+		entry->next = entry->addr = entry->user = entry->slot = 0;
+		map_op(data, entry);
+		list_del(&entry->list);
+		return 0;
+	}
+
+	pre_ent = list_entry(pre, struct imapper, list);
+	next_ent = list_entry(next, struct imapper, list);
+
+	pre_ent->next = next_ent->addr;
+	map_op(data, pre_ent);
+	list_del(&entry->list);
+
+	return 0;
+}
+
+void free_input_mapper_list(struct list_head *head)
+{
+	struct imapper *entry;
+	struct list_head *pos;
+
+	while (!list_empty(head)) {
+		pos = head->next;
+		list_del(pos);
+		entry = list_entry(pos, struct imapper, list);
+		kfree(entry);
+	}
+}
+
diff --git a/linux/sound/pci/ctxfi/ctimap.h b/linux/sound/pci/ctxfi/ctimap.h
new file mode 100644
index 0000000..53ccf9b
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctimap.h
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctimap.h
+ *
+ * @Brief
+ * This file contains the definition of generic input mapper operations
+ * for input mapper management.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 23 2008
+ *
+ */
+
+#ifndef CTIMAP_H
+#define CTIMAP_H
+
+#include <linux/list.h>
+
+struct imapper {
+	unsigned short slot; /* the id of the slot containing input data */
+	unsigned short user; /* the id of the user resource consuming data */
+	unsigned short addr; /* the input mapper ram id */
+	unsigned short next; /* the next input mapper ram id */
+	struct list_head	list;
+};
+
+int input_mapper_add(struct list_head *mappers, struct imapper *entry,
+		     int (*map_op)(void *, struct imapper *), void *data);
+
+int input_mapper_delete(struct list_head *mappers, struct imapper *entry,
+		     int (*map_op)(void *, struct imapper *), void *data);
+
+void free_input_mapper_list(struct list_head *mappers);
+
+#endif /* CTIMAP_H */
diff --git a/linux/sound/pci/ctxfi/ctmixer.c b/linux/sound/pci/ctxfi/ctmixer.c
new file mode 100644
index 0000000..15c1e72
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctmixer.c
@@ -0,0 +1,1157 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctmixer.c
+ *
+ * @Brief
+ * This file contains the implementation of alsa mixer device functions.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 28 2008
+ *
+ */
+
+
+#include "ctmixer.h"
+#include "ctamixer.h"
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/asoundef.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+
+enum CT_SUM_CTL {
+	SUM_IN_F,
+	SUM_IN_R,
+	SUM_IN_C,
+	SUM_IN_S,
+	SUM_IN_F_C,
+
+	NUM_CT_SUMS
+};
+
+enum CT_AMIXER_CTL {
+	/* volume control mixers */
+	AMIXER_MASTER_F,
+	AMIXER_MASTER_R,
+	AMIXER_MASTER_C,
+	AMIXER_MASTER_S,
+	AMIXER_PCM_F,
+	AMIXER_PCM_R,
+	AMIXER_PCM_C,
+	AMIXER_PCM_S,
+	AMIXER_SPDIFI,
+	AMIXER_LINEIN,
+	AMIXER_MIC,
+	AMIXER_SPDIFO,
+	AMIXER_WAVE_F,
+	AMIXER_WAVE_R,
+	AMIXER_WAVE_C,
+	AMIXER_WAVE_S,
+	AMIXER_MASTER_F_C,
+	AMIXER_PCM_F_C,
+	AMIXER_SPDIFI_C,
+	AMIXER_LINEIN_C,
+	AMIXER_MIC_C,
+
+	/* this should always be the last one */
+	NUM_CT_AMIXERS
+};
+
+enum CTALSA_MIXER_CTL {
+	/* volume control mixers */
+	MIXER_MASTER_P,
+	MIXER_PCM_P,
+	MIXER_LINEIN_P,
+	MIXER_MIC_P,
+	MIXER_SPDIFI_P,
+	MIXER_SPDIFO_P,
+	MIXER_WAVEF_P,
+	MIXER_WAVER_P,
+	MIXER_WAVEC_P,
+	MIXER_WAVES_P,
+	MIXER_MASTER_C,
+	MIXER_PCM_C,
+	MIXER_LINEIN_C,
+	MIXER_MIC_C,
+	MIXER_SPDIFI_C,
+
+	/* switch control mixers */
+	MIXER_PCM_C_S,
+	MIXER_LINEIN_C_S,
+	MIXER_MIC_C_S,
+	MIXER_SPDIFI_C_S,
+	MIXER_LINEIN_P_S,
+	MIXER_SPDIFO_P_S,
+	MIXER_SPDIFI_P_S,
+	MIXER_WAVEF_P_S,
+	MIXER_WAVER_P_S,
+	MIXER_WAVEC_P_S,
+	MIXER_WAVES_P_S,
+	MIXER_DIGITAL_IO_S,
+	MIXER_IEC958_MASK,
+	MIXER_IEC958_DEFAULT,
+	MIXER_IEC958_STREAM,
+
+	/* this should always be the last one */
+	NUM_CTALSA_MIXERS
+};
+
+#define VOL_MIXER_START		MIXER_MASTER_P
+#define VOL_MIXER_END		MIXER_SPDIFI_C
+#define VOL_MIXER_NUM		(VOL_MIXER_END - VOL_MIXER_START + 1)
+#define SWH_MIXER_START		MIXER_PCM_C_S
+#define SWH_MIXER_END		MIXER_DIGITAL_IO_S
+#define SWH_CAPTURE_START	MIXER_PCM_C_S
+#define SWH_CAPTURE_END		MIXER_SPDIFI_C_S
+
+#define CHN_NUM		2
+
+struct ct_kcontrol_init {
+	unsigned char ctl;
+	char *name;
+};
+
+static struct ct_kcontrol_init
+ct_kcontrol_init_table[NUM_CTALSA_MIXERS] = {
+	[MIXER_MASTER_P] = {
+		.ctl = 1,
+		.name = "Master Playback Volume",
+	},
+	[MIXER_MASTER_C] = {
+		.ctl = 1,
+		.name = "Master Capture Volume",
+	},
+	[MIXER_PCM_P] = {
+		.ctl = 1,
+		.name = "PCM Playback Volume",
+	},
+	[MIXER_PCM_C] = {
+		.ctl = 1,
+		.name = "PCM Capture Volume",
+	},
+	[MIXER_LINEIN_P] = {
+		.ctl = 1,
+		.name = "Line-in Playback Volume",
+	},
+	[MIXER_LINEIN_C] = {
+		.ctl = 1,
+		.name = "Line-in Capture Volume",
+	},
+	[MIXER_MIC_P] = {
+		.ctl = 1,
+		.name = "Mic Playback Volume",
+	},
+	[MIXER_MIC_C] = {
+		.ctl = 1,
+		.name = "Mic Capture Volume",
+	},
+	[MIXER_SPDIFI_P] = {
+		.ctl = 1,
+		.name = "S/PDIF-in Playback Volume",
+	},
+	[MIXER_SPDIFI_C] = {
+		.ctl = 1,
+		.name = "S/PDIF-in Capture Volume",
+	},
+	[MIXER_SPDIFO_P] = {
+		.ctl = 1,
+		.name = "S/PDIF-out Playback Volume",
+	},
+	[MIXER_WAVEF_P] = {
+		.ctl = 1,
+		.name = "Front Playback Volume",
+	},
+	[MIXER_WAVES_P] = {
+		.ctl = 1,
+		.name = "Side Playback Volume",
+	},
+	[MIXER_WAVEC_P] = {
+		.ctl = 1,
+		.name = "Center/LFE Playback Volume",
+	},
+	[MIXER_WAVER_P] = {
+		.ctl = 1,
+		.name = "Surround Playback Volume",
+	},
+
+	[MIXER_PCM_C_S] = {
+		.ctl = 1,
+		.name = "PCM Capture Switch",
+	},
+	[MIXER_LINEIN_C_S] = {
+		.ctl = 1,
+		.name = "Line-in Capture Switch",
+	},
+	[MIXER_MIC_C_S] = {
+		.ctl = 1,
+		.name = "Mic Capture Switch",
+	},
+	[MIXER_SPDIFI_C_S] = {
+		.ctl = 1,
+		.name = "S/PDIF-in Capture Switch",
+	},
+	[MIXER_LINEIN_P_S] = {
+		.ctl = 1,
+		.name = "Line-in Playback Switch",
+	},
+	[MIXER_SPDIFO_P_S] = {
+		.ctl = 1,
+		.name = "S/PDIF-out Playback Switch",
+	},
+	[MIXER_SPDIFI_P_S] = {
+		.ctl = 1,
+		.name = "S/PDIF-in Playback Switch",
+	},
+	[MIXER_WAVEF_P_S] = {
+		.ctl = 1,
+		.name = "Front Playback Switch",
+	},
+	[MIXER_WAVES_P_S] = {
+		.ctl = 1,
+		.name = "Side Playback Switch",
+	},
+	[MIXER_WAVEC_P_S] = {
+		.ctl = 1,
+		.name = "Center/LFE Playback Switch",
+	},
+	[MIXER_WAVER_P_S] = {
+		.ctl = 1,
+		.name = "Surround Playback Switch",
+	},
+	[MIXER_DIGITAL_IO_S] = {
+		.ctl = 0,
+		.name = "Digit-IO Playback Switch",
+	},
+};
+
+static void
+ct_mixer_recording_select(struct ct_mixer *mixer, enum CT_AMIXER_CTL type);
+
+static void
+ct_mixer_recording_unselect(struct ct_mixer *mixer, enum CT_AMIXER_CTL type);
+
+static struct snd_kcontrol *kctls[2] = {NULL};
+
+static enum CT_AMIXER_CTL get_amixer_index(enum CTALSA_MIXER_CTL alsa_index)
+{
+	switch (alsa_index) {
+	case MIXER_MASTER_P:	return AMIXER_MASTER_F;
+	case MIXER_MASTER_C:	return AMIXER_MASTER_F_C;
+	case MIXER_PCM_P:	return AMIXER_PCM_F;
+	case MIXER_PCM_C:
+	case MIXER_PCM_C_S:	return AMIXER_PCM_F_C;
+	case MIXER_LINEIN_P:	return AMIXER_LINEIN;
+	case MIXER_LINEIN_C:
+	case MIXER_LINEIN_C_S:	return AMIXER_LINEIN_C;
+	case MIXER_MIC_P:	return AMIXER_MIC;
+	case MIXER_MIC_C:
+	case MIXER_MIC_C_S:	return AMIXER_MIC_C;
+	case MIXER_SPDIFI_P:	return AMIXER_SPDIFI;
+	case MIXER_SPDIFI_C:
+	case MIXER_SPDIFI_C_S:	return AMIXER_SPDIFI_C;
+	case MIXER_SPDIFO_P:	return AMIXER_SPDIFO;
+	case MIXER_WAVEF_P:	return AMIXER_WAVE_F;
+	case MIXER_WAVES_P:	return AMIXER_WAVE_S;
+	case MIXER_WAVEC_P:	return AMIXER_WAVE_C;
+	case MIXER_WAVER_P:	return AMIXER_WAVE_R;
+	default:		return NUM_CT_AMIXERS;
+	}
+}
+
+static enum CT_AMIXER_CTL get_recording_amixer(enum CT_AMIXER_CTL index)
+{
+	switch (index) {
+	case AMIXER_MASTER_F:	return AMIXER_MASTER_F_C;
+	case AMIXER_PCM_F:	return AMIXER_PCM_F_C;
+	case AMIXER_SPDIFI:	return AMIXER_SPDIFI_C;
+	case AMIXER_LINEIN:	return AMIXER_LINEIN_C;
+	case AMIXER_MIC:	return AMIXER_MIC_C;
+	default:		return NUM_CT_AMIXERS;
+	}
+}
+
+static unsigned char
+get_switch_state(struct ct_mixer *mixer, enum CTALSA_MIXER_CTL type)
+{
+	return (mixer->switch_state & (0x1 << (type - SWH_MIXER_START)))
+		? 1 : 0;
+}
+
+static void
+set_switch_state(struct ct_mixer *mixer,
+		 enum CTALSA_MIXER_CTL type, unsigned char state)
+{
+	if (state)
+		mixer->switch_state |= (0x1 << (type - SWH_MIXER_START));
+	else
+		mixer->switch_state &= ~(0x1 << (type - SWH_MIXER_START));
+}
+
+#if 0 /* not used */
+/* Map integer value ranging from 0 to 65535 to 14-bit float value ranging
+ * from 2^-6 to (1+1023/1024) */
+static unsigned int uint16_to_float14(unsigned int x)
+{
+	unsigned int i;
+
+	if (x < 17)
+		return 0;
+
+	x *= 2031;
+	x /= 65535;
+	x += 16;
+
+	/* i <= 6 */
+	for (i = 0; !(x & 0x400); i++)
+		x <<= 1;
+
+	x = (((7 - i) & 0x7) << 10) | (x & 0x3ff);
+
+	return x;
+}
+
+static unsigned int float14_to_uint16(unsigned int x)
+{
+	unsigned int e;
+
+	if (!x)
+		return x;
+
+	e = (x >> 10) & 0x7;
+	x &= 0x3ff;
+	x += 1024;
+	x >>= (7 - e);
+	x -= 16;
+	x *= 65535;
+	x /= 2031;
+
+	return x;
+}
+#endif /* not used */
+
+#define VOL_SCALE	0x1c
+#define VOL_MAX		0x100
+
+static const DECLARE_TLV_DB_SCALE(ct_vol_db_scale, -6400, 25, 1);
+
+static int ct_alsa_mix_volume_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = VOL_MAX;
+
+	return 0;
+}
+
+static int ct_alsa_mix_volume_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+	enum CT_AMIXER_CTL type = get_amixer_index(kcontrol->private_value);
+	struct amixer *amixer;
+	int i, val;
+
+	for (i = 0; i < 2; i++) {
+		amixer = ((struct ct_mixer *)atc->mixer)->
+						amixers[type*CHN_NUM+i];
+		val = amixer->ops->get_scale(amixer) / VOL_SCALE;
+		if (val < 0)
+			val = 0;
+		else if (val > VOL_MAX)
+			val = VOL_MAX;
+		ucontrol->value.integer.value[i] = val;
+	}
+
+	return 0;
+}
+
+static int ct_alsa_mix_volume_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+	struct ct_mixer *mixer = atc->mixer;
+	enum CT_AMIXER_CTL type = get_amixer_index(kcontrol->private_value);
+	struct amixer *amixer;
+	int i, j, val, oval, change = 0;
+
+	for (i = 0; i < 2; i++) {
+		val = ucontrol->value.integer.value[i];
+		if (val < 0)
+			val = 0;
+		else if (val > VOL_MAX)
+			val = VOL_MAX;
+		val *= VOL_SCALE;
+		amixer = mixer->amixers[type*CHN_NUM+i];
+		oval = amixer->ops->get_scale(amixer);
+		if (val != oval) {
+			amixer->ops->set_scale(amixer, val);
+			amixer->ops->commit_write(amixer);
+			change = 1;
+			/* Synchronize Master/PCM playback AMIXERs. */
+			if (AMIXER_MASTER_F == type || AMIXER_PCM_F == type) {
+				for (j = 1; j < 4; j++) {
+					amixer = mixer->
+						amixers[(type+j)*CHN_NUM+i];
+					amixer->ops->set_scale(amixer, val);
+					amixer->ops->commit_write(amixer);
+				}
+			}
+		}
+	}
+
+	return change;
+}
+
+static struct snd_kcontrol_new vol_ctl = {
+	.access		= SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.iface		= SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info		= ct_alsa_mix_volume_info,
+	.get		= ct_alsa_mix_volume_get,
+	.put		= ct_alsa_mix_volume_put,
+	.tlv		= { .p =  ct_vol_db_scale },
+};
+
+static void
+do_line_mic_switch(struct ct_atc *atc, enum CTALSA_MIXER_CTL type)
+{
+
+	if (MIXER_LINEIN_C_S == type) {
+		atc->select_line_in(atc);
+		set_switch_state(atc->mixer, MIXER_MIC_C_S, 0);
+		snd_ctl_notify(atc->card, SNDRV_CTL_EVENT_MASK_VALUE,
+							&kctls[1]->id);
+	} else if (MIXER_MIC_C_S == type) {
+		atc->select_mic_in(atc);
+		set_switch_state(atc->mixer, MIXER_LINEIN_C_S, 0);
+		snd_ctl_notify(atc->card, SNDRV_CTL_EVENT_MASK_VALUE,
+							&kctls[0]->id);
+	}
+}
+
+static void
+do_digit_io_switch(struct ct_atc *atc, int state)
+{
+	struct ct_mixer *mixer = atc->mixer;
+
+	if (state) {
+		atc->select_digit_io(atc);
+		atc->spdif_out_unmute(atc,
+				get_switch_state(mixer, MIXER_SPDIFO_P_S));
+		atc->spdif_in_unmute(atc, 1);
+		atc->line_in_unmute(atc, 0);
+		return;
+	}
+
+	if (get_switch_state(mixer, MIXER_LINEIN_C_S))
+		atc->select_line_in(atc);
+	else if (get_switch_state(mixer, MIXER_MIC_C_S))
+		atc->select_mic_in(atc);
+
+	atc->spdif_out_unmute(atc, 0);
+	atc->spdif_in_unmute(atc, 0);
+	atc->line_in_unmute(atc, 1);
+	return;
+}
+
+static void do_switch(struct ct_atc *atc, enum CTALSA_MIXER_CTL type, int state)
+{
+	struct ct_mixer *mixer = atc->mixer;
+
+	/* Do changes in mixer. */
+	if ((SWH_CAPTURE_START <= type) && (SWH_CAPTURE_END >= type)) {
+		if (state) {
+			ct_mixer_recording_select(mixer,
+						  get_amixer_index(type));
+		} else {
+			ct_mixer_recording_unselect(mixer,
+						    get_amixer_index(type));
+		}
+	}
+	/* Do changes out of mixer. */
+	if (state && (MIXER_LINEIN_C_S == type || MIXER_MIC_C_S == type))
+		do_line_mic_switch(atc, type);
+	else if (MIXER_WAVEF_P_S == type)
+		atc->line_front_unmute(atc, state);
+	else if (MIXER_WAVES_P_S == type)
+		atc->line_surround_unmute(atc, state);
+	else if (MIXER_WAVEC_P_S == type)
+		atc->line_clfe_unmute(atc, state);
+	else if (MIXER_WAVER_P_S == type)
+		atc->line_rear_unmute(atc, state);
+	else if (MIXER_LINEIN_P_S == type)
+		atc->line_in_unmute(atc, state);
+	else if (MIXER_SPDIFO_P_S == type)
+		atc->spdif_out_unmute(atc, state);
+	else if (MIXER_SPDIFI_P_S == type)
+		atc->spdif_in_unmute(atc, state);
+	else if (MIXER_DIGITAL_IO_S == type)
+		do_digit_io_switch(atc, state);
+
+	return;
+}
+
+static int ct_alsa_mix_switch_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	uinfo->value.integer.step = 1;
+
+	return 0;
+}
+
+static int ct_alsa_mix_switch_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct ct_mixer *mixer =
+		((struct ct_atc *)snd_kcontrol_chip(kcontrol))->mixer;
+	enum CTALSA_MIXER_CTL type = kcontrol->private_value;
+
+	ucontrol->value.integer.value[0] = get_switch_state(mixer, type);
+	return 0;
+}
+
+static int ct_alsa_mix_switch_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+	struct ct_mixer *mixer = atc->mixer;
+	enum CTALSA_MIXER_CTL type = kcontrol->private_value;
+	int state;
+
+	state = ucontrol->value.integer.value[0];
+	if (get_switch_state(mixer, type) == state)
+		return 0;
+
+	set_switch_state(mixer, type, state);
+	do_switch(atc, type, state);
+
+	return 1;
+}
+
+static struct snd_kcontrol_new swh_ctl = {
+	.access		= SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.iface		= SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info		= ct_alsa_mix_switch_info,
+	.get		= ct_alsa_mix_switch_get,
+	.put		= ct_alsa_mix_switch_put
+};
+
+static int ct_spdif_info(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int ct_spdif_get_mask(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0xff;
+	return 0;
+}
+
+static int ct_spdif_default_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned int status = SNDRV_PCM_DEFAULT_CON_SPDIF;
+
+	ucontrol->value.iec958.status[0] = (status >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (status >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (status >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (status >> 24) & 0xff;
+
+	return 0;
+}
+
+static int ct_spdif_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+	unsigned int status;
+
+	atc->spdif_out_get_status(atc, &status);
+	ucontrol->value.iec958.status[0] = (status >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (status >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (status >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (status >> 24) & 0xff;
+
+	return 0;
+}
+
+static int ct_spdif_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct ct_atc *atc = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int status, old_status;
+
+	status = (ucontrol->value.iec958.status[0] << 0) |
+		 (ucontrol->value.iec958.status[1] << 8) |
+		 (ucontrol->value.iec958.status[2] << 16) |
+		 (ucontrol->value.iec958.status[3] << 24);
+
+	atc->spdif_out_get_status(atc, &old_status);
+	change = (old_status != status);
+	if (change)
+		atc->spdif_out_set_status(atc, status);
+
+	return change;
+}
+
+static struct snd_kcontrol_new iec958_mask_ctl = {
+	.access		= SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface		= SNDRV_CTL_ELEM_IFACE_PCM,
+	.name		= SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
+	.count		= 1,
+	.info		= ct_spdif_info,
+	.get		= ct_spdif_get_mask,
+	.private_value	= MIXER_IEC958_MASK
+};
+
+static struct snd_kcontrol_new iec958_default_ctl = {
+	.iface		= SNDRV_CTL_ELEM_IFACE_PCM,
+	.name		= SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+	.count		= 1,
+	.info		= ct_spdif_info,
+	.get		= ct_spdif_default_get,
+	.put		= ct_spdif_put,
+	.private_value	= MIXER_IEC958_DEFAULT
+};
+
+static struct snd_kcontrol_new iec958_ctl = {
+	.access		= SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.iface		= SNDRV_CTL_ELEM_IFACE_PCM,
+	.name		= SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
+	.count		= 1,
+	.info		= ct_spdif_info,
+	.get		= ct_spdif_get,
+	.put		= ct_spdif_put,
+	.private_value	= MIXER_IEC958_STREAM
+};
+
+#define NUM_IEC958_CTL 3
+
+static int
+ct_mixer_kcontrol_new(struct ct_mixer *mixer, struct snd_kcontrol_new *new)
+{
+	struct snd_kcontrol *kctl;
+	int err;
+
+	kctl = snd_ctl_new1(new, mixer->atc);
+	if (!kctl)
+		return -ENOMEM;
+
+	if (SNDRV_CTL_ELEM_IFACE_PCM == kctl->id.iface)
+		kctl->id.device = IEC958;
+
+	err = snd_ctl_add(mixer->atc->card, kctl);
+	if (err)
+		return err;
+
+	switch (new->private_value) {
+	case MIXER_LINEIN_C_S:
+		kctls[0] = kctl; break;
+	case MIXER_MIC_C_S:
+		kctls[1] = kctl; break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int ct_mixer_kcontrols_create(struct ct_mixer *mixer)
+{
+	enum CTALSA_MIXER_CTL type;
+	struct ct_atc *atc = mixer->atc;
+	int err;
+
+	/* Create snd kcontrol instances on demand */
+	for (type = VOL_MIXER_START; type <= VOL_MIXER_END; type++) {
+		if (ct_kcontrol_init_table[type].ctl) {
+			vol_ctl.name = ct_kcontrol_init_table[type].name;
+			vol_ctl.private_value = (unsigned long)type;
+			err = ct_mixer_kcontrol_new(mixer, &vol_ctl);
+			if (err)
+				return err;
+		}
+	}
+
+	ct_kcontrol_init_table[MIXER_DIGITAL_IO_S].ctl =
+					atc->have_digit_io_switch(atc);
+	for (type = SWH_MIXER_START; type <= SWH_MIXER_END; type++) {
+		if (ct_kcontrol_init_table[type].ctl) {
+			swh_ctl.name = ct_kcontrol_init_table[type].name;
+			swh_ctl.private_value = (unsigned long)type;
+			err = ct_mixer_kcontrol_new(mixer, &swh_ctl);
+			if (err)
+				return err;
+		}
+	}
+
+	err = ct_mixer_kcontrol_new(mixer, &iec958_mask_ctl);
+	if (err)
+		return err;
+
+	err = ct_mixer_kcontrol_new(mixer, &iec958_default_ctl);
+	if (err)
+		return err;
+
+	err = ct_mixer_kcontrol_new(mixer, &iec958_ctl);
+	if (err)
+		return err;
+
+	atc->line_front_unmute(atc, 1);
+	set_switch_state(mixer, MIXER_WAVEF_P_S, 1);
+	atc->line_surround_unmute(atc, 0);
+	set_switch_state(mixer, MIXER_WAVES_P_S, 0);
+	atc->line_clfe_unmute(atc, 0);
+	set_switch_state(mixer, MIXER_WAVEC_P_S, 0);
+	atc->line_rear_unmute(atc, 0);
+	set_switch_state(mixer, MIXER_WAVER_P_S, 0);
+	atc->spdif_out_unmute(atc, 0);
+	set_switch_state(mixer, MIXER_SPDIFO_P_S, 0);
+	atc->line_in_unmute(atc, 0);
+	set_switch_state(mixer, MIXER_LINEIN_P_S, 0);
+	atc->spdif_in_unmute(atc, 0);
+	set_switch_state(mixer, MIXER_SPDIFI_P_S, 0);
+
+	set_switch_state(mixer, MIXER_PCM_C_S, 1);
+	set_switch_state(mixer, MIXER_LINEIN_C_S, 1);
+	set_switch_state(mixer, MIXER_SPDIFI_C_S, 1);
+
+	return 0;
+}
+
+static void
+ct_mixer_recording_select(struct ct_mixer *mixer, enum CT_AMIXER_CTL type)
+{
+	struct amixer *amix_d;
+	struct sum *sum_c;
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		amix_d = mixer->amixers[type*CHN_NUM+i];
+		sum_c = mixer->sums[SUM_IN_F_C*CHN_NUM+i];
+		amix_d->ops->set_sum(amix_d, sum_c);
+		amix_d->ops->commit_write(amix_d);
+	}
+}
+
+static void
+ct_mixer_recording_unselect(struct ct_mixer *mixer, enum CT_AMIXER_CTL type)
+{
+	struct amixer *amix_d;
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		amix_d = mixer->amixers[type*CHN_NUM+i];
+		amix_d->ops->set_sum(amix_d, NULL);
+		amix_d->ops->commit_write(amix_d);
+	}
+}
+
+static int ct_mixer_get_resources(struct ct_mixer *mixer)
+{
+	struct sum_mgr *sum_mgr;
+	struct sum *sum;
+	struct sum_desc sum_desc = {0};
+	struct amixer_mgr *amixer_mgr;
+	struct amixer *amixer;
+	struct amixer_desc am_desc = {0};
+	int err;
+	int i;
+
+	/* Allocate sum resources for mixer obj */
+	sum_mgr = (struct sum_mgr *)mixer->atc->rsc_mgrs[SUM];
+	sum_desc.msr = mixer->atc->msr;
+	for (i = 0; i < (NUM_CT_SUMS * CHN_NUM); i++) {
+		err = sum_mgr->get_sum(sum_mgr, &sum_desc, &sum);
+		if (err) {
+			printk(KERN_ERR "ctxfi:Failed to get sum resources for "
+					  "front output!\n");
+			break;
+		}
+		mixer->sums[i] = sum;
+	}
+	if (err)
+		goto error1;
+
+	/* Allocate amixer resources for mixer obj */
+	amixer_mgr = (struct amixer_mgr *)mixer->atc->rsc_mgrs[AMIXER];
+	am_desc.msr = mixer->atc->msr;
+	for (i = 0; i < (NUM_CT_AMIXERS * CHN_NUM); i++) {
+		err = amixer_mgr->get_amixer(amixer_mgr, &am_desc, &amixer);
+		if (err) {
+			printk(KERN_ERR "ctxfi:Failed to get amixer resources "
+			       "for mixer obj!\n");
+			break;
+		}
+		mixer->amixers[i] = amixer;
+	}
+	if (err)
+		goto error2;
+
+	return 0;
+
+error2:
+	for (i = 0; i < (NUM_CT_AMIXERS * CHN_NUM); i++) {
+		if (NULL != mixer->amixers[i]) {
+			amixer = mixer->amixers[i];
+			amixer_mgr->put_amixer(amixer_mgr, amixer);
+			mixer->amixers[i] = NULL;
+		}
+	}
+error1:
+	for (i = 0; i < (NUM_CT_SUMS * CHN_NUM); i++) {
+		if (NULL != mixer->sums[i]) {
+			sum_mgr->put_sum(sum_mgr, (struct sum *)mixer->sums[i]);
+			mixer->sums[i] = NULL;
+		}
+	}
+
+	return err;
+}
+
+static int ct_mixer_get_mem(struct ct_mixer **rmixer)
+{
+	struct ct_mixer *mixer;
+	int err;
+
+	*rmixer = NULL;
+	/* Allocate mem for mixer obj */
+	mixer = kzalloc(sizeof(*mixer), GFP_KERNEL);
+	if (!mixer)
+		return -ENOMEM;
+
+	mixer->amixers = kzalloc(sizeof(void *)*(NUM_CT_AMIXERS*CHN_NUM),
+				 GFP_KERNEL);
+	if (!mixer->amixers) {
+		err = -ENOMEM;
+		goto error1;
+	}
+	mixer->sums = kzalloc(sizeof(void *)*(NUM_CT_SUMS*CHN_NUM), GFP_KERNEL);
+	if (!mixer->sums) {
+		err = -ENOMEM;
+		goto error2;
+	}
+
+	*rmixer = mixer;
+	return 0;
+
+error2:
+	kfree(mixer->amixers);
+error1:
+	kfree(mixer);
+	return err;
+}
+
+static int ct_mixer_topology_build(struct ct_mixer *mixer)
+{
+	struct sum *sum;
+	struct amixer *amix_d, *amix_s;
+	enum CT_AMIXER_CTL i, j;
+
+	/* Build topology from destination to source */
+
+	/* Set up Master mixer */
+	for (i = AMIXER_MASTER_F, j = SUM_IN_F;
+					i <= AMIXER_MASTER_S; i++, j++) {
+		amix_d = mixer->amixers[i*CHN_NUM];
+		sum = mixer->sums[j*CHN_NUM];
+		amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL);
+		amix_d = mixer->amixers[i*CHN_NUM+1];
+		sum = mixer->sums[j*CHN_NUM+1];
+		amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL);
+	}
+
+	/* Set up Wave-out mixer */
+	for (i = AMIXER_WAVE_F, j = AMIXER_MASTER_F;
+					i <= AMIXER_WAVE_S; i++, j++) {
+		amix_d = mixer->amixers[i*CHN_NUM];
+		amix_s = mixer->amixers[j*CHN_NUM];
+		amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL);
+		amix_d = mixer->amixers[i*CHN_NUM+1];
+		amix_s = mixer->amixers[j*CHN_NUM+1];
+		amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL);
+	}
+
+	/* Set up S/PDIF-out mixer */
+	amix_d = mixer->amixers[AMIXER_SPDIFO*CHN_NUM];
+	amix_s = mixer->amixers[AMIXER_MASTER_F*CHN_NUM];
+	amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL);
+	amix_d = mixer->amixers[AMIXER_SPDIFO*CHN_NUM+1];
+	amix_s = mixer->amixers[AMIXER_MASTER_F*CHN_NUM+1];
+	amix_d->ops->setup(amix_d, &amix_s->rsc, INIT_VOL, NULL);
+
+	/* Set up PCM-in mixer */
+	for (i = AMIXER_PCM_F, j = SUM_IN_F; i <= AMIXER_PCM_S; i++, j++) {
+		amix_d = mixer->amixers[i*CHN_NUM];
+		sum = mixer->sums[j*CHN_NUM];
+		amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+		amix_d = mixer->amixers[i*CHN_NUM+1];
+		sum = mixer->sums[j*CHN_NUM+1];
+		amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+	}
+
+	/* Set up Line-in mixer */
+	amix_d = mixer->amixers[AMIXER_LINEIN*CHN_NUM];
+	sum = mixer->sums[SUM_IN_F*CHN_NUM];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+	amix_d = mixer->amixers[AMIXER_LINEIN*CHN_NUM+1];
+	sum = mixer->sums[SUM_IN_F*CHN_NUM+1];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+	/* Set up Mic-in mixer */
+	amix_d = mixer->amixers[AMIXER_MIC*CHN_NUM];
+	sum = mixer->sums[SUM_IN_F*CHN_NUM];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+	amix_d = mixer->amixers[AMIXER_MIC*CHN_NUM+1];
+	sum = mixer->sums[SUM_IN_F*CHN_NUM+1];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+	/* Set up S/PDIF-in mixer */
+	amix_d = mixer->amixers[AMIXER_SPDIFI*CHN_NUM];
+	sum = mixer->sums[SUM_IN_F*CHN_NUM];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+	amix_d = mixer->amixers[AMIXER_SPDIFI*CHN_NUM+1];
+	sum = mixer->sums[SUM_IN_F*CHN_NUM+1];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+	/* Set up Master recording mixer */
+	amix_d = mixer->amixers[AMIXER_MASTER_F_C*CHN_NUM];
+	sum = mixer->sums[SUM_IN_F_C*CHN_NUM];
+	amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL);
+	amix_d = mixer->amixers[AMIXER_MASTER_F_C*CHN_NUM+1];
+	sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1];
+	amix_d->ops->setup(amix_d, &sum->rsc, INIT_VOL, NULL);
+
+	/* Set up PCM-in recording mixer */
+	amix_d = mixer->amixers[AMIXER_PCM_F_C*CHN_NUM];
+	sum = mixer->sums[SUM_IN_F_C*CHN_NUM];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+	amix_d = mixer->amixers[AMIXER_PCM_F_C*CHN_NUM+1];
+	sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+	/* Set up Line-in recording mixer */
+	amix_d = mixer->amixers[AMIXER_LINEIN_C*CHN_NUM];
+	sum = mixer->sums[SUM_IN_F_C*CHN_NUM];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+	amix_d = mixer->amixers[AMIXER_LINEIN_C*CHN_NUM+1];
+	sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+	/* Set up Mic-in recording mixer */
+	amix_d = mixer->amixers[AMIXER_MIC_C*CHN_NUM];
+	sum = mixer->sums[SUM_IN_F_C*CHN_NUM];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+	amix_d = mixer->amixers[AMIXER_MIC_C*CHN_NUM+1];
+	sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+	/* Set up S/PDIF-in recording mixer */
+	amix_d = mixer->amixers[AMIXER_SPDIFI_C*CHN_NUM];
+	sum = mixer->sums[SUM_IN_F_C*CHN_NUM];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+	amix_d = mixer->amixers[AMIXER_SPDIFI_C*CHN_NUM+1];
+	sum = mixer->sums[SUM_IN_F_C*CHN_NUM+1];
+	amix_d->ops->setup(amix_d, NULL, INIT_VOL, sum);
+
+	return 0;
+}
+
+static int mixer_set_input_port(struct amixer *amixer, struct rsc *rsc)
+{
+	amixer->ops->set_input(amixer, rsc);
+	amixer->ops->commit_write(amixer);
+
+	return 0;
+}
+
+static enum CT_AMIXER_CTL port_to_amixer(enum MIXER_PORT_T type)
+{
+	switch (type) {
+	case MIX_WAVE_FRONT:	return AMIXER_WAVE_F;
+	case MIX_WAVE_SURROUND:	return AMIXER_WAVE_S;
+	case MIX_WAVE_CENTLFE:	return AMIXER_WAVE_C;
+	case MIX_WAVE_REAR:	return AMIXER_WAVE_R;
+	case MIX_PCMO_FRONT:	return AMIXER_MASTER_F_C;
+	case MIX_SPDIF_OUT:	return AMIXER_SPDIFO;
+	case MIX_LINE_IN:	return AMIXER_LINEIN;
+	case MIX_MIC_IN:	return AMIXER_MIC;
+	case MIX_SPDIF_IN:	return AMIXER_SPDIFI;
+	case MIX_PCMI_FRONT:	return AMIXER_PCM_F;
+	case MIX_PCMI_SURROUND:	return AMIXER_PCM_S;
+	case MIX_PCMI_CENTLFE:	return AMIXER_PCM_C;
+	case MIX_PCMI_REAR:	return AMIXER_PCM_R;
+	default: 		return 0;
+	}
+}
+
+static int mixer_get_output_ports(struct ct_mixer *mixer,
+				  enum MIXER_PORT_T type,
+				  struct rsc **rleft, struct rsc **rright)
+{
+	enum CT_AMIXER_CTL amix = port_to_amixer(type);
+
+	if (NULL != rleft)
+		*rleft = &((struct amixer *)mixer->amixers[amix*CHN_NUM])->rsc;
+
+	if (NULL != rright)
+		*rright =
+			&((struct amixer *)mixer->amixers[amix*CHN_NUM+1])->rsc;
+
+	return 0;
+}
+
+static int mixer_set_input_left(struct ct_mixer *mixer,
+				enum MIXER_PORT_T type, struct rsc *rsc)
+{
+	enum CT_AMIXER_CTL amix = port_to_amixer(type);
+
+	mixer_set_input_port(mixer->amixers[amix*CHN_NUM], rsc);
+	amix = get_recording_amixer(amix);
+	if (amix < NUM_CT_AMIXERS)
+		mixer_set_input_port(mixer->amixers[amix*CHN_NUM], rsc);
+
+	return 0;
+}
+
+static int
+mixer_set_input_right(struct ct_mixer *mixer,
+		      enum MIXER_PORT_T type, struct rsc *rsc)
+{
+	enum CT_AMIXER_CTL amix = port_to_amixer(type);
+
+	mixer_set_input_port(mixer->amixers[amix*CHN_NUM+1], rsc);
+	amix = get_recording_amixer(amix);
+	if (amix < NUM_CT_AMIXERS)
+		mixer_set_input_port(mixer->amixers[amix*CHN_NUM+1], rsc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int mixer_resume(struct ct_mixer *mixer)
+{
+	int i, state;
+	struct amixer *amixer;
+
+	/* resume topology and volume gain. */
+	for (i = 0; i < NUM_CT_AMIXERS*CHN_NUM; i++) {
+		amixer = mixer->amixers[i];
+		amixer->ops->commit_write(amixer);
+	}
+
+	/* resume switch state. */
+	for (i = SWH_MIXER_START; i <= SWH_MIXER_END; i++) {
+		state = get_switch_state(mixer, i);
+		do_switch(mixer->atc, i, state);
+	}
+
+	return 0;
+}
+#endif
+
+int ct_mixer_destroy(struct ct_mixer *mixer)
+{
+	struct sum_mgr *sum_mgr = (struct sum_mgr *)mixer->atc->rsc_mgrs[SUM];
+	struct amixer_mgr *amixer_mgr =
+			(struct amixer_mgr *)mixer->atc->rsc_mgrs[AMIXER];
+	struct amixer *amixer;
+	int i = 0;
+
+	/* Release amixer resources */
+	for (i = 0; i < (NUM_CT_AMIXERS * CHN_NUM); i++) {
+		if (NULL != mixer->amixers[i]) {
+			amixer = mixer->amixers[i];
+			amixer_mgr->put_amixer(amixer_mgr, amixer);
+		}
+	}
+
+	/* Release sum resources */
+	for (i = 0; i < (NUM_CT_SUMS * CHN_NUM); i++) {
+		if (NULL != mixer->sums[i])
+			sum_mgr->put_sum(sum_mgr, (struct sum *)mixer->sums[i]);
+	}
+
+	/* Release mem assigned to mixer object */
+	kfree(mixer->sums);
+	kfree(mixer->amixers);
+	kfree(mixer);
+
+	return 0;
+}
+
+int ct_mixer_create(struct ct_atc *atc, struct ct_mixer **rmixer)
+{
+	struct ct_mixer *mixer;
+	int err;
+
+	*rmixer = NULL;
+
+	/* Allocate mem for mixer obj */
+	err = ct_mixer_get_mem(&mixer);
+	if (err)
+		return err;
+
+	mixer->switch_state = 0;
+	mixer->atc = atc;
+	/* Set operations */
+	mixer->get_output_ports = mixer_get_output_ports;
+	mixer->set_input_left = mixer_set_input_left;
+	mixer->set_input_right = mixer_set_input_right;
+#ifdef CONFIG_PM
+	mixer->resume = mixer_resume;
+#endif
+
+	/* Allocate chip resources for mixer obj */
+	err = ct_mixer_get_resources(mixer);
+	if (err)
+		goto error;
+
+	/* Build internal mixer topology */
+	ct_mixer_topology_build(mixer);
+
+	*rmixer = mixer;
+
+	return 0;
+
+error:
+	ct_mixer_destroy(mixer);
+	return err;
+}
+
+int ct_alsa_mix_create(struct ct_atc *atc,
+		       enum CTALSADEVS device,
+		       const char *device_name)
+{
+	int err;
+
+	/* Create snd kcontrol instances on demand */
+	/* vol_ctl.device = swh_ctl.device = device; */ /* better w/ device 0 */
+	err = ct_mixer_kcontrols_create((struct ct_mixer *)atc->mixer);
+	if (err)
+		return err;
+
+	strcpy(atc->card->mixername, device_name);
+
+	return 0;
+}
diff --git a/linux/sound/pci/ctxfi/ctmixer.h b/linux/sound/pci/ctxfi/ctmixer.h
new file mode 100644
index 0000000..b009e98
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctmixer.h
@@ -0,0 +1,70 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctmixer.h
+ *
+ * @Brief
+ * This file contains the definition of the mixer device functions.
+ *
+ * @Author	Liu Chun
+ * @Date 	Mar 28 2008
+ *
+ */
+
+#ifndef CTMIXER_H
+#define CTMIXER_H
+
+#include "ctatc.h"
+#include "ctresource.h"
+
+#define INIT_VOL	0x1c00
+
+enum MIXER_PORT_T {
+	MIX_WAVE_FRONT,
+	MIX_WAVE_REAR,
+	MIX_WAVE_CENTLFE,
+	MIX_WAVE_SURROUND,
+	MIX_SPDIF_OUT,
+	MIX_PCMO_FRONT,
+	MIX_MIC_IN,
+	MIX_LINE_IN,
+	MIX_SPDIF_IN,
+	MIX_PCMI_FRONT,
+	MIX_PCMI_REAR,
+	MIX_PCMI_CENTLFE,
+	MIX_PCMI_SURROUND,
+
+	NUM_MIX_PORTS
+};
+
+/* alsa mixer descriptor */
+struct ct_mixer {
+	struct ct_atc *atc;
+
+	void **amixers;		/* amixer resources for volume control */
+	void **sums;		/* sum resources for signal collection */
+	unsigned int switch_state; /* A bit-map to indicate state of switches */
+
+	int (*get_output_ports)(struct ct_mixer *mixer, enum MIXER_PORT_T type,
+				  struct rsc **rleft, struct rsc **rright);
+
+	int (*set_input_left)(struct ct_mixer *mixer,
+			      enum MIXER_PORT_T type, struct rsc *rsc);
+	int (*set_input_right)(struct ct_mixer *mixer,
+			       enum MIXER_PORT_T type, struct rsc *rsc);
+#ifdef CONFIG_PM
+	int (*resume)(struct ct_mixer *mixer);
+#endif
+};
+
+int ct_alsa_mix_create(struct ct_atc *atc,
+		       enum CTALSADEVS device,
+		       const char *device_name);
+int ct_mixer_create(struct ct_atc *atc, struct ct_mixer **rmixer);
+int ct_mixer_destroy(struct ct_mixer *mixer);
+
+#endif /* CTMIXER_H */
diff --git a/linux/sound/pci/ctxfi/ctpcm.c b/linux/sound/pci/ctxfi/ctpcm.c
new file mode 100644
index 0000000..457d211
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctpcm.c
@@ -0,0 +1,435 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctpcm.c
+ *
+ * @Brief
+ * This file contains the definition of the pcm device functions.
+ *
+ * @Author	Liu Chun
+ * @Date 	Apr 2 2008
+ *
+ */
+
+#include "ctpcm.h"
+#include "cttimer.h"
+#include <linux/slab.h>
+#include <sound/pcm.h>
+
+/* Hardware descriptions for playback */
+static struct snd_pcm_hardware ct_pcm_playback_hw = {
+	.info			= (SNDRV_PCM_INFO_MMAP |
+				   SNDRV_PCM_INFO_INTERLEAVED |
+				   SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				   SNDRV_PCM_INFO_MMAP_VALID |
+				   SNDRV_PCM_INFO_PAUSE),
+	.formats		= (SNDRV_PCM_FMTBIT_U8 |
+				   SNDRV_PCM_FMTBIT_S16_LE |
+				   SNDRV_PCM_FMTBIT_S24_3LE |
+				   SNDRV_PCM_FMTBIT_S32_LE |
+				   SNDRV_PCM_FMTBIT_FLOAT_LE),
+	.rates			= (SNDRV_PCM_RATE_CONTINUOUS |
+				   SNDRV_PCM_RATE_8000_192000),
+	.rate_min		= 8000,
+	.rate_max		= 192000,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= (128*1024),
+	.period_bytes_min	= (64),
+	.period_bytes_max	= (128*1024),
+	.periods_min		= 2,
+	.periods_max		= 1024,
+	.fifo_size		= 0,
+};
+
+static struct snd_pcm_hardware ct_spdif_passthru_playback_hw = {
+	.info			= (SNDRV_PCM_INFO_MMAP |
+				   SNDRV_PCM_INFO_INTERLEAVED |
+				   SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				   SNDRV_PCM_INFO_MMAP_VALID |
+				   SNDRV_PCM_INFO_PAUSE),
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE,
+	.rates			= (SNDRV_PCM_RATE_48000 |
+				   SNDRV_PCM_RATE_44100 |
+				   SNDRV_PCM_RATE_32000),
+	.rate_min		= 32000,
+	.rate_max		= 48000,
+	.channels_min		= 2,
+	.channels_max		= 2,
+	.buffer_bytes_max	= (128*1024),
+	.period_bytes_min	= (64),
+	.period_bytes_max	= (128*1024),
+	.periods_min		= 2,
+	.periods_max		= 1024,
+	.fifo_size		= 0,
+};
+
+/* Hardware descriptions for capture */
+static struct snd_pcm_hardware ct_pcm_capture_hw = {
+	.info			= (SNDRV_PCM_INFO_MMAP |
+				   SNDRV_PCM_INFO_INTERLEAVED |
+				   SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				   SNDRV_PCM_INFO_PAUSE |
+				   SNDRV_PCM_INFO_MMAP_VALID),
+	.formats		= (SNDRV_PCM_FMTBIT_U8 |
+				   SNDRV_PCM_FMTBIT_S16_LE |
+				   SNDRV_PCM_FMTBIT_S24_3LE |
+				   SNDRV_PCM_FMTBIT_S32_LE |
+				   SNDRV_PCM_FMTBIT_FLOAT_LE),
+	.rates			= (SNDRV_PCM_RATE_CONTINUOUS |
+				   SNDRV_PCM_RATE_8000_96000),
+	.rate_min		= 8000,
+	.rate_max		= 96000,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= (128*1024),
+	.period_bytes_min	= (384),
+	.period_bytes_max	= (64*1024),
+	.periods_min		= 2,
+	.periods_max		= 1024,
+	.fifo_size		= 0,
+};
+
+static void ct_atc_pcm_interrupt(struct ct_atc_pcm *atc_pcm)
+{
+	struct ct_atc_pcm *apcm = atc_pcm;
+
+	if (!apcm->substream)
+		return;
+
+	snd_pcm_period_elapsed(apcm->substream);
+}
+
+static void ct_atc_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+	struct ct_atc_pcm *apcm = runtime->private_data;
+	struct ct_atc *atc = snd_pcm_substream_chip(apcm->substream);
+
+	atc->pcm_release_resources(atc, apcm);
+	ct_timer_instance_free(apcm->timer);
+	kfree(apcm);
+	runtime->private_data = NULL;
+}
+
+/* pcm playback operations */
+static int ct_pcm_playback_open(struct snd_pcm_substream *substream)
+{
+	struct ct_atc *atc = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ct_atc_pcm *apcm;
+	int err;
+
+	apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
+	if (!apcm)
+		return -ENOMEM;
+
+	apcm->substream = substream;
+	apcm->interrupt = ct_atc_pcm_interrupt;
+	if (IEC958 == substream->pcm->device) {
+		runtime->hw = ct_spdif_passthru_playback_hw;
+		atc->spdif_out_passthru(atc, 1);
+	} else {
+		runtime->hw = ct_pcm_playback_hw;
+		if (FRONT == substream->pcm->device)
+			runtime->hw.channels_max = 8;
+	}
+
+	err = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0) {
+		kfree(apcm);
+		return err;
+	}
+	err = snd_pcm_hw_constraint_minmax(runtime,
+					   SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+					   1024, UINT_MAX);
+	if (err < 0) {
+		kfree(apcm);
+		return err;
+	}
+
+	apcm->timer = ct_timer_instance_new(atc->timer, apcm);
+	if (!apcm->timer) {
+		kfree(apcm);
+		return -ENOMEM;
+	}
+	runtime->private_data = apcm;
+	runtime->private_free = ct_atc_pcm_free_substream;
+
+	return 0;
+}
+
+static int ct_pcm_playback_close(struct snd_pcm_substream *substream)
+{
+	struct ct_atc *atc = snd_pcm_substream_chip(substream);
+
+	/* TODO: Notify mixer inactive. */
+	if (IEC958 == substream->pcm->device)
+		atc->spdif_out_passthru(atc, 0);
+
+	/* The ct_atc_pcm object will be freed by runtime->private_free */
+
+	return 0;
+}
+
+static int ct_pcm_hw_params(struct snd_pcm_substream *substream,
+				     struct snd_pcm_hw_params *hw_params)
+{
+	struct ct_atc *atc = snd_pcm_substream_chip(substream);
+	struct ct_atc_pcm *apcm = substream->runtime->private_data;
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	/* clear previous resources */
+	atc->pcm_release_resources(atc, apcm);
+	return err;
+}
+
+static int ct_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct ct_atc *atc = snd_pcm_substream_chip(substream);
+	struct ct_atc_pcm *apcm = substream->runtime->private_data;
+
+	/* clear previous resources */
+	atc->pcm_release_resources(atc, apcm);
+	/* Free snd-allocated pages */
+	return snd_pcm_lib_free_pages(substream);
+}
+
+
+static int ct_pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct ct_atc *atc = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ct_atc_pcm *apcm = runtime->private_data;
+
+	if (IEC958 == substream->pcm->device)
+		err = atc->spdif_passthru_playback_prepare(atc, apcm);
+	else
+		err = atc->pcm_playback_prepare(atc, apcm);
+
+	if (err < 0) {
+		printk(KERN_ERR "ctxfi: Preparing pcm playback failed!!!\n");
+		return err;
+	}
+
+	return 0;
+}
+
+static int
+ct_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct ct_atc *atc = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ct_atc_pcm *apcm = runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		atc->pcm_playback_start(atc, apcm);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		atc->pcm_playback_stop(atc, apcm);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t
+ct_pcm_playback_pointer(struct snd_pcm_substream *substream)
+{
+	unsigned long position;
+	struct ct_atc *atc = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ct_atc_pcm *apcm = runtime->private_data;
+
+	/* Read out playback position */
+	position = atc->pcm_playback_position(atc, apcm);
+	position = bytes_to_frames(runtime, position);
+	if (position >= runtime->buffer_size)
+		position = 0;
+	return position;
+}
+
+/* pcm capture operations */
+static int ct_pcm_capture_open(struct snd_pcm_substream *substream)
+{
+	struct ct_atc *atc = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ct_atc_pcm *apcm;
+	int err;
+
+	apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
+	if (!apcm)
+		return -ENOMEM;
+
+	apcm->started = 0;
+	apcm->substream = substream;
+	apcm->interrupt = ct_atc_pcm_interrupt;
+	runtime->hw = ct_pcm_capture_hw;
+	runtime->hw.rate_max = atc->rsr * atc->msr;
+
+	err = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0) {
+		kfree(apcm);
+		return err;
+	}
+	err = snd_pcm_hw_constraint_minmax(runtime,
+					   SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+					   1024, UINT_MAX);
+	if (err < 0) {
+		kfree(apcm);
+		return err;
+	}
+
+	apcm->timer = ct_timer_instance_new(atc->timer, apcm);
+	if (!apcm->timer) {
+		kfree(apcm);
+		return -ENOMEM;
+	}
+	runtime->private_data = apcm;
+	runtime->private_free = ct_atc_pcm_free_substream;
+
+	return 0;
+}
+
+static int ct_pcm_capture_close(struct snd_pcm_substream *substream)
+{
+	/* The ct_atc_pcm object will be freed by runtime->private_free */
+	/* TODO: Notify mixer inactive. */
+	return 0;
+}
+
+static int ct_pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct ct_atc *atc = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ct_atc_pcm *apcm = runtime->private_data;
+
+	err = atc->pcm_capture_prepare(atc, apcm);
+	if (err < 0) {
+		printk(KERN_ERR "ctxfi: Preparing pcm capture failed!!!\n");
+		return err;
+	}
+
+	return 0;
+}
+
+static int
+ct_pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct ct_atc *atc = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ct_atc_pcm *apcm = runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		atc->pcm_capture_start(atc, apcm);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		atc->pcm_capture_stop(atc, apcm);
+		break;
+	default:
+		atc->pcm_capture_stop(atc, apcm);
+		break;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t
+ct_pcm_capture_pointer(struct snd_pcm_substream *substream)
+{
+	unsigned long position;
+	struct ct_atc *atc = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ct_atc_pcm *apcm = runtime->private_data;
+
+	/* Read out playback position */
+	position = atc->pcm_capture_position(atc, apcm);
+	position = bytes_to_frames(runtime, position);
+	if (position >= runtime->buffer_size)
+		position = 0;
+	return position;
+}
+
+/* PCM operators for playback */
+static struct snd_pcm_ops ct_pcm_playback_ops = {
+	.open	 	= ct_pcm_playback_open,
+	.close		= ct_pcm_playback_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= ct_pcm_hw_params,
+	.hw_free	= ct_pcm_hw_free,
+	.prepare	= ct_pcm_playback_prepare,
+	.trigger	= ct_pcm_playback_trigger,
+	.pointer	= ct_pcm_playback_pointer,
+	.page		= snd_pcm_sgbuf_ops_page,
+};
+
+/* PCM operators for capture */
+static struct snd_pcm_ops ct_pcm_capture_ops = {
+	.open	 	= ct_pcm_capture_open,
+	.close		= ct_pcm_capture_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= ct_pcm_hw_params,
+	.hw_free	= ct_pcm_hw_free,
+	.prepare	= ct_pcm_capture_prepare,
+	.trigger	= ct_pcm_capture_trigger,
+	.pointer	= ct_pcm_capture_pointer,
+	.page		= snd_pcm_sgbuf_ops_page,
+};
+
+/* Create ALSA pcm device */
+int ct_alsa_pcm_create(struct ct_atc *atc,
+		       enum CTALSADEVS device,
+		       const char *device_name)
+{
+	struct snd_pcm *pcm;
+	int err;
+	int playback_count, capture_count;
+
+	playback_count = (IEC958 == device) ? 1 : 8;
+	capture_count = (FRONT == device) ? 1 : 0;
+	err = snd_pcm_new(atc->card, "ctxfi", device,
+			  playback_count, capture_count, &pcm);
+	if (err < 0) {
+		printk(KERN_ERR "ctxfi: snd_pcm_new failed!! Err=%d\n", err);
+		return err;
+	}
+
+	pcm->private_data = atc;
+	pcm->info_flags = 0;
+	pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+	strlcpy(pcm->name, device_name, sizeof(pcm->name));
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ct_pcm_playback_ops);
+
+	if (FRONT == device)
+		snd_pcm_set_ops(pcm,
+				SNDRV_PCM_STREAM_CAPTURE, &ct_pcm_capture_ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+			snd_dma_pci_data(atc->pci), 128*1024, 128*1024);
+
+#ifdef CONFIG_PM
+	atc->pcms[device] = pcm;
+#endif
+
+	return 0;
+}
diff --git a/linux/sound/pci/ctxfi/ctpcm.h b/linux/sound/pci/ctxfi/ctpcm.h
new file mode 100644
index 0000000..178da0d
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctpcm.h
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctpcm.h
+ *
+ * @Brief
+ * This file contains the definition of the pcm device functions.
+ *
+ * @Author	Liu Chun
+ * @Date 	Mar 28 2008
+ *
+ */
+
+#ifndef CTPCM_H
+#define CTPCM_H
+
+#include "ctatc.h"
+
+int ct_alsa_pcm_create(struct ct_atc *atc,
+		       enum CTALSADEVS device,
+		       const char *device_name);
+
+#endif /* CTPCM_H */
diff --git a/linux/sound/pci/ctxfi/ctresource.c b/linux/sound/pci/ctxfi/ctresource.c
new file mode 100644
index 0000000..7dfaf67
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctresource.c
@@ -0,0 +1,301 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctresource.c
+ *
+ * @Brief
+ * This file contains the implementation of some generic helper functions.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 15 2008
+ *
+ */
+
+#include "ctresource.h"
+#include "cthardware.h"
+#include <linux/err.h>
+#include <linux/slab.h>
+
+#define AUDIO_SLOT_BLOCK_NUM 	256
+
+/* Resource allocation based on bit-map management mechanism */
+static int
+get_resource(u8 *rscs, unsigned int amount,
+	     unsigned int multi, unsigned int *ridx)
+{
+	int i, j, k, n;
+
+	/* Check whether there are sufficient resources to meet request. */
+	for (i = 0, n = multi; i < amount; i++) {
+		j = i / 8;
+		k = i % 8;
+		if (rscs[j] & ((u8)1 << k)) {
+			n = multi;
+			continue;
+		}
+		if (!(--n))
+			break; /* found sufficient contiguous resources */
+	}
+
+	if (i >= amount) {
+		/* Can not find sufficient contiguous resources */
+		return -ENOENT;
+	}
+
+	/* Mark the contiguous bits in resource bit-map as used */
+	for (n = multi; n > 0; n--) {
+		j = i / 8;
+		k = i % 8;
+		rscs[j] |= ((u8)1 << k);
+		i--;
+	}
+
+	*ridx = i + 1;
+
+	return 0;
+}
+
+static int put_resource(u8 *rscs, unsigned int multi, unsigned int idx)
+{
+	unsigned int i, j, k, n;
+
+	/* Mark the contiguous bits in resource bit-map as used */
+	for (n = multi, i = idx; n > 0; n--) {
+		j = i / 8;
+		k = i % 8;
+		rscs[j] &= ~((u8)1 << k);
+		i++;
+	}
+
+	return 0;
+}
+
+int mgr_get_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int *ridx)
+{
+	int err;
+
+	if (n > mgr->avail)
+		return -ENOENT;
+
+	err = get_resource(mgr->rscs, mgr->amount, n, ridx);
+	if (!err)
+		mgr->avail -= n;
+
+	return err;
+}
+
+int mgr_put_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int idx)
+{
+	put_resource(mgr->rscs, n, idx);
+	mgr->avail += n;
+
+	return 0;
+}
+
+static unsigned char offset_in_audio_slot_block[NUM_RSCTYP] = {
+	/* SRC channel is at Audio Ring slot 1 every 16 slots. */
+	[SRC]		= 0x1,
+	[AMIXER]	= 0x4,
+	[SUM]		= 0xc,
+};
+
+static int rsc_index(const struct rsc *rsc)
+{
+    return rsc->conj;
+}
+
+static int audio_ring_slot(const struct rsc *rsc)
+{
+    return (rsc->conj << 4) + offset_in_audio_slot_block[rsc->type];
+}
+
+static int rsc_next_conj(struct rsc *rsc)
+{
+	unsigned int i;
+	for (i = 0; (i < 8) && (!(rsc->msr & (0x1 << i))); )
+		i++;
+	rsc->conj += (AUDIO_SLOT_BLOCK_NUM >> i);
+	return rsc->conj;
+}
+
+static int rsc_master(struct rsc *rsc)
+{
+	return rsc->conj = rsc->idx;
+}
+
+static struct rsc_ops rsc_generic_ops = {
+	.index		= rsc_index,
+	.output_slot	= audio_ring_slot,
+	.master		= rsc_master,
+	.next_conj	= rsc_next_conj,
+};
+
+int rsc_init(struct rsc *rsc, u32 idx, enum RSCTYP type, u32 msr, void *hw)
+{
+	int err = 0;
+
+	rsc->idx = idx;
+	rsc->conj = idx;
+	rsc->type = type;
+	rsc->msr = msr;
+	rsc->hw = hw;
+	rsc->ops = &rsc_generic_ops;
+	if (!hw) {
+		rsc->ctrl_blk = NULL;
+		return 0;
+	}
+
+	switch (type) {
+	case SRC:
+		err = ((struct hw *)hw)->src_rsc_get_ctrl_blk(&rsc->ctrl_blk);
+		break;
+	case AMIXER:
+		err = ((struct hw *)hw)->
+				amixer_rsc_get_ctrl_blk(&rsc->ctrl_blk);
+		break;
+	case SRCIMP:
+	case SUM:
+	case DAIO:
+		break;
+	default:
+		printk(KERN_ERR
+		       "ctxfi: Invalid resource type value %d!\n", type);
+		return -EINVAL;
+	}
+
+	if (err) {
+		printk(KERN_ERR
+		       "ctxfi: Failed to get resource control block!\n");
+		return err;
+	}
+
+	return 0;
+}
+
+int rsc_uninit(struct rsc *rsc)
+{
+	if ((NULL != rsc->hw) && (NULL != rsc->ctrl_blk)) {
+		switch (rsc->type) {
+		case SRC:
+			((struct hw *)rsc->hw)->
+				src_rsc_put_ctrl_blk(rsc->ctrl_blk);
+			break;
+		case AMIXER:
+			((struct hw *)rsc->hw)->
+				amixer_rsc_put_ctrl_blk(rsc->ctrl_blk);
+			break;
+		case SUM:
+		case DAIO:
+			break;
+		default:
+			printk(KERN_ERR "ctxfi: "
+			       "Invalid resource type value %d!\n", rsc->type);
+			break;
+		}
+
+		rsc->hw = rsc->ctrl_blk = NULL;
+	}
+
+	rsc->idx = rsc->conj = 0;
+	rsc->type = NUM_RSCTYP;
+	rsc->msr = 0;
+
+	return 0;
+}
+
+int rsc_mgr_init(struct rsc_mgr *mgr, enum RSCTYP type,
+		 unsigned int amount, void *hw_obj)
+{
+	int err = 0;
+	struct hw *hw = hw_obj;
+
+	mgr->type = NUM_RSCTYP;
+
+	mgr->rscs = kzalloc(((amount + 8 - 1) / 8), GFP_KERNEL);
+	if (!mgr->rscs)
+		return -ENOMEM;
+
+	switch (type) {
+	case SRC:
+		err = hw->src_mgr_get_ctrl_blk(&mgr->ctrl_blk);
+		break;
+	case SRCIMP:
+		err = hw->srcimp_mgr_get_ctrl_blk(&mgr->ctrl_blk);
+		break;
+	case AMIXER:
+		err = hw->amixer_mgr_get_ctrl_blk(&mgr->ctrl_blk);
+		break;
+	case DAIO:
+		err = hw->daio_mgr_get_ctrl_blk(hw, &mgr->ctrl_blk);
+		break;
+	case SUM:
+		break;
+	default:
+		printk(KERN_ERR
+		       "ctxfi: Invalid resource type value %d!\n", type);
+		err = -EINVAL;
+		goto error;
+	}
+
+	if (err) {
+		printk(KERN_ERR
+		       "ctxfi: Failed to get manager control block!\n");
+		goto error;
+	}
+
+	mgr->type = type;
+	mgr->avail = mgr->amount = amount;
+	mgr->hw = hw;
+
+	return 0;
+
+error:
+	kfree(mgr->rscs);
+	return err;
+}
+
+int rsc_mgr_uninit(struct rsc_mgr *mgr)
+{
+	if (NULL != mgr->rscs) {
+		kfree(mgr->rscs);
+		mgr->rscs = NULL;
+	}
+
+	if ((NULL != mgr->hw) && (NULL != mgr->ctrl_blk)) {
+		switch (mgr->type) {
+		case SRC:
+			((struct hw *)mgr->hw)->
+				src_mgr_put_ctrl_blk(mgr->ctrl_blk);
+			break;
+		case SRCIMP:
+			((struct hw *)mgr->hw)->
+				srcimp_mgr_put_ctrl_blk(mgr->ctrl_blk);
+			break;
+		case AMIXER:
+			((struct hw *)mgr->hw)->
+				amixer_mgr_put_ctrl_blk(mgr->ctrl_blk);
+			break;
+		case DAIO:
+			((struct hw *)mgr->hw)->
+				daio_mgr_put_ctrl_blk(mgr->ctrl_blk);
+			break;
+		case SUM:
+			break;
+		default:
+			printk(KERN_ERR "ctxfi: "
+			       "Invalid resource type value %d!\n", mgr->type);
+			break;
+		}
+
+		mgr->hw = mgr->ctrl_blk = NULL;
+	}
+
+	mgr->type = NUM_RSCTYP;
+	mgr->avail = mgr->amount = 0;
+
+	return 0;
+}
diff --git a/linux/sound/pci/ctxfi/ctresource.h b/linux/sound/pci/ctxfi/ctresource.h
new file mode 100644
index 0000000..0838c2e
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctresource.h
@@ -0,0 +1,72 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctresource.h
+ *
+ * @Brief
+ * This file contains the definition of generic hardware resources for
+ * resource management.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 13 2008
+ *
+ */
+
+#ifndef CTRESOURCE_H
+#define CTRESOURCE_H
+
+#include <linux/types.h>
+
+enum RSCTYP {
+	SRC,
+	SRCIMP,
+	AMIXER,
+	SUM,
+	DAIO,
+	NUM_RSCTYP	/* This must be the last one and less than 16 */
+};
+
+struct rsc_ops;
+
+struct rsc {
+	u32 idx:12;	/* The index of a resource */
+	u32 type:4;	/* The type (RSCTYP) of a resource */
+	u32 conj:12;	/* Current conjugate index */
+	u32 msr:4;	/* The Master Sample Rate a resource working on */
+	void *ctrl_blk;	/* Chip specific control info block for a resource */
+	void *hw;	/* Chip specific object for hardware access means */
+	struct rsc_ops *ops;	/* Generic resource operations */
+};
+
+struct rsc_ops {
+	int (*master)(struct rsc *rsc);	/* Move to master resource */
+	int (*next_conj)(struct rsc *rsc); /* Move to next conjugate resource */
+	int (*index)(const struct rsc *rsc); /* Return the index of resource */
+	/* Return the output slot number */
+	int (*output_slot)(const struct rsc *rsc);
+};
+
+int rsc_init(struct rsc *rsc, u32 idx, enum RSCTYP type, u32 msr, void *hw);
+int rsc_uninit(struct rsc *rsc);
+
+struct rsc_mgr {
+	enum RSCTYP type; /* The type (RSCTYP) of resource to manage */
+	unsigned int amount; /* The total amount of a kind of resource */
+	unsigned int avail; /* The amount of currently available resources */
+	unsigned char *rscs; /* The bit-map for resource allocation */
+	void *ctrl_blk; /* Chip specific control info block */
+	void *hw; /* Chip specific object for hardware access */
+};
+
+/* Resource management is based on bit-map mechanism */
+int rsc_mgr_init(struct rsc_mgr *mgr, enum RSCTYP type,
+		 unsigned int amount, void *hw);
+int rsc_mgr_uninit(struct rsc_mgr *mgr);
+int mgr_get_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int *ridx);
+int mgr_put_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int idx);
+
+#endif /* CTRESOURCE_H */
diff --git a/linux/sound/pci/ctxfi/ctsrc.c b/linux/sound/pci/ctxfi/ctsrc.c
new file mode 100644
index 0000000..c749fa7
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctsrc.c
@@ -0,0 +1,885 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctsrc.c
+ *
+ * @Brief
+ * This file contains the implementation of the Sample Rate Convertor
+ * resource management object.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 13 2008
+ *
+ */
+
+#include "ctsrc.h"
+#include "cthardware.h"
+#include <linux/slab.h>
+
+#define SRC_RESOURCE_NUM	64
+#define SRCIMP_RESOURCE_NUM	256
+
+static unsigned int conj_mask;
+
+static int src_default_config_memrd(struct src *src);
+static int src_default_config_memwr(struct src *src);
+static int src_default_config_arcrw(struct src *src);
+
+static int (*src_default_config[3])(struct src *) = {
+	[MEMRD] = src_default_config_memrd,
+	[MEMWR] = src_default_config_memwr,
+	[ARCRW] = src_default_config_arcrw
+};
+
+static int src_set_state(struct src *src, unsigned int state)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_state(src->rsc.ctrl_blk, state);
+
+	return 0;
+}
+
+static int src_set_bm(struct src *src, unsigned int bm)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_bm(src->rsc.ctrl_blk, bm);
+
+	return 0;
+}
+
+static int src_set_sf(struct src *src, unsigned int sf)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_sf(src->rsc.ctrl_blk, sf);
+
+	return 0;
+}
+
+static int src_set_pm(struct src *src, unsigned int pm)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_pm(src->rsc.ctrl_blk, pm);
+
+	return 0;
+}
+
+static int src_set_rom(struct src *src, unsigned int rom)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_rom(src->rsc.ctrl_blk, rom);
+
+	return 0;
+}
+
+static int src_set_vo(struct src *src, unsigned int vo)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_vo(src->rsc.ctrl_blk, vo);
+
+	return 0;
+}
+
+static int src_set_st(struct src *src, unsigned int st)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_st(src->rsc.ctrl_blk, st);
+
+	return 0;
+}
+
+static int src_set_bp(struct src *src, unsigned int bp)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_bp(src->rsc.ctrl_blk, bp);
+
+	return 0;
+}
+
+static int src_set_cisz(struct src *src, unsigned int cisz)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_cisz(src->rsc.ctrl_blk, cisz);
+
+	return 0;
+}
+
+static int src_set_ca(struct src *src, unsigned int ca)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_ca(src->rsc.ctrl_blk, ca);
+
+	return 0;
+}
+
+static int src_set_sa(struct src *src, unsigned int sa)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_sa(src->rsc.ctrl_blk, sa);
+
+	return 0;
+}
+
+static int src_set_la(struct src *src, unsigned int la)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_la(src->rsc.ctrl_blk, la);
+
+	return 0;
+}
+
+static int src_set_pitch(struct src *src, unsigned int pitch)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_pitch(src->rsc.ctrl_blk, pitch);
+
+	return 0;
+}
+
+static int src_set_clear_zbufs(struct src *src)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1);
+
+	return 0;
+}
+
+static int src_commit_write(struct src *src)
+{
+	struct hw *hw;
+	int i;
+	unsigned int dirty = 0;
+
+	hw = src->rsc.hw;
+	src->rsc.ops->master(&src->rsc);
+	if (src->rsc.msr > 1) {
+		/* Save dirty flags for conjugate resource programming */
+		dirty = hw->src_get_dirty(src->rsc.ctrl_blk) & conj_mask;
+	}
+	hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+						src->rsc.ctrl_blk);
+
+	/* Program conjugate parameter mixer resources */
+	if (MEMWR == src->mode)
+		return 0;
+
+	for (i = 1; i < src->rsc.msr; i++) {
+		src->rsc.ops->next_conj(&src->rsc);
+		hw->src_set_dirty(src->rsc.ctrl_blk, dirty);
+		hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+							src->rsc.ctrl_blk);
+	}
+	src->rsc.ops->master(&src->rsc);
+
+	return 0;
+}
+
+static int src_get_ca(struct src *src)
+{
+	struct hw *hw;
+
+	hw = src->rsc.hw;
+	return hw->src_get_ca(hw, src->rsc.ops->index(&src->rsc),
+						src->rsc.ctrl_blk);
+}
+
+static int src_init(struct src *src)
+{
+	src_default_config[src->mode](src);
+
+	return 0;
+}
+
+static struct src *src_next_interleave(struct src *src)
+{
+	return src->intlv;
+}
+
+static int src_default_config_memrd(struct src *src)
+{
+	struct hw *hw = src->rsc.hw;
+	unsigned int rsr, msr;
+
+	hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF);
+	hw->src_set_bm(src->rsc.ctrl_blk, 1);
+	for (rsr = 0, msr = src->rsc.msr; msr > 1; msr >>= 1)
+		rsr++;
+
+	hw->src_set_rsr(src->rsc.ctrl_blk, rsr);
+	hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_S16);
+	hw->src_set_wr(src->rsc.ctrl_blk, 0);
+	hw->src_set_pm(src->rsc.ctrl_blk, 0);
+	hw->src_set_rom(src->rsc.ctrl_blk, 0);
+	hw->src_set_vo(src->rsc.ctrl_blk, 0);
+	hw->src_set_st(src->rsc.ctrl_blk, 0);
+	hw->src_set_ilsz(src->rsc.ctrl_blk, src->multi - 1);
+	hw->src_set_cisz(src->rsc.ctrl_blk, 0x80);
+	hw->src_set_sa(src->rsc.ctrl_blk, 0x0);
+	hw->src_set_la(src->rsc.ctrl_blk, 0x1000);
+	hw->src_set_ca(src->rsc.ctrl_blk, 0x80);
+	hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000);
+	hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1);
+
+	src->rsc.ops->master(&src->rsc);
+	hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+						src->rsc.ctrl_blk);
+
+	for (msr = 1; msr < src->rsc.msr; msr++) {
+		src->rsc.ops->next_conj(&src->rsc);
+		hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000);
+		hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+							src->rsc.ctrl_blk);
+	}
+	src->rsc.ops->master(&src->rsc);
+
+	return 0;
+}
+
+static int src_default_config_memwr(struct src *src)
+{
+	struct hw *hw = src->rsc.hw;
+
+	hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF);
+	hw->src_set_bm(src->rsc.ctrl_blk, 1);
+	hw->src_set_rsr(src->rsc.ctrl_blk, 0);
+	hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_S16);
+	hw->src_set_wr(src->rsc.ctrl_blk, 1);
+	hw->src_set_pm(src->rsc.ctrl_blk, 0);
+	hw->src_set_rom(src->rsc.ctrl_blk, 0);
+	hw->src_set_vo(src->rsc.ctrl_blk, 0);
+	hw->src_set_st(src->rsc.ctrl_blk, 0);
+	hw->src_set_ilsz(src->rsc.ctrl_blk, 0);
+	hw->src_set_cisz(src->rsc.ctrl_blk, 0x80);
+	hw->src_set_sa(src->rsc.ctrl_blk, 0x0);
+	hw->src_set_la(src->rsc.ctrl_blk, 0x1000);
+	hw->src_set_ca(src->rsc.ctrl_blk, 0x80);
+	hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000);
+	hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1);
+
+	src->rsc.ops->master(&src->rsc);
+	hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+						src->rsc.ctrl_blk);
+
+	return 0;
+}
+
+static int src_default_config_arcrw(struct src *src)
+{
+	struct hw *hw = src->rsc.hw;
+	unsigned int rsr, msr;
+	unsigned int dirty;
+
+	hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF);
+	hw->src_set_bm(src->rsc.ctrl_blk, 0);
+	for (rsr = 0, msr = src->rsc.msr; msr > 1; msr >>= 1)
+		rsr++;
+
+	hw->src_set_rsr(src->rsc.ctrl_blk, rsr);
+	hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_F32);
+	hw->src_set_wr(src->rsc.ctrl_blk, 0);
+	hw->src_set_pm(src->rsc.ctrl_blk, 0);
+	hw->src_set_rom(src->rsc.ctrl_blk, 0);
+	hw->src_set_vo(src->rsc.ctrl_blk, 0);
+	hw->src_set_st(src->rsc.ctrl_blk, 0);
+	hw->src_set_ilsz(src->rsc.ctrl_blk, 0);
+	hw->src_set_cisz(src->rsc.ctrl_blk, 0x80);
+	hw->src_set_sa(src->rsc.ctrl_blk, 0x0);
+	/*hw->src_set_sa(src->rsc.ctrl_blk, 0x100);*/
+	hw->src_set_la(src->rsc.ctrl_blk, 0x1000);
+	/*hw->src_set_la(src->rsc.ctrl_blk, 0x03ffffe0);*/
+	hw->src_set_ca(src->rsc.ctrl_blk, 0x80);
+	hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000);
+	hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1);
+
+	dirty = hw->src_get_dirty(src->rsc.ctrl_blk);
+	src->rsc.ops->master(&src->rsc);
+	for (msr = 0; msr < src->rsc.msr; msr++) {
+		hw->src_set_dirty(src->rsc.ctrl_blk, dirty);
+		hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc),
+							src->rsc.ctrl_blk);
+		src->rsc.ops->next_conj(&src->rsc);
+	}
+	src->rsc.ops->master(&src->rsc);
+
+	return 0;
+}
+
+static struct src_rsc_ops src_rsc_ops = {
+	.set_state		= src_set_state,
+	.set_bm			= src_set_bm,
+	.set_sf			= src_set_sf,
+	.set_pm			= src_set_pm,
+	.set_rom		= src_set_rom,
+	.set_vo			= src_set_vo,
+	.set_st			= src_set_st,
+	.set_bp			= src_set_bp,
+	.set_cisz		= src_set_cisz,
+	.set_ca			= src_set_ca,
+	.set_sa			= src_set_sa,
+	.set_la			= src_set_la,
+	.set_pitch		= src_set_pitch,
+	.set_clr_zbufs		= src_set_clear_zbufs,
+	.commit_write		= src_commit_write,
+	.get_ca			= src_get_ca,
+	.init			= src_init,
+	.next_interleave	= src_next_interleave,
+};
+
+static int
+src_rsc_init(struct src *src, u32 idx,
+	     const struct src_desc *desc, struct src_mgr *mgr)
+{
+	int err;
+	int i, n;
+	struct src *p;
+
+	n = (MEMRD == desc->mode) ? desc->multi : 1;
+	for (i = 0, p = src; i < n; i++, p++) {
+		err = rsc_init(&p->rsc, idx + i, SRC, desc->msr, mgr->mgr.hw);
+		if (err)
+			goto error1;
+
+		/* Initialize src specific rsc operations */
+		p->ops = &src_rsc_ops;
+		p->multi = (0 == i) ? desc->multi : 1;
+		p->mode = desc->mode;
+		src_default_config[desc->mode](p);
+		mgr->src_enable(mgr, p);
+		p->intlv = p + 1;
+	}
+	(--p)->intlv = NULL;	/* Set @intlv of the last SRC to NULL */
+
+	mgr->commit_write(mgr);
+
+	return 0;
+
+error1:
+	for (i--, p--; i >= 0; i--, p--) {
+		mgr->src_disable(mgr, p);
+		rsc_uninit(&p->rsc);
+	}
+	mgr->commit_write(mgr);
+	return err;
+}
+
+static int src_rsc_uninit(struct src *src, struct src_mgr *mgr)
+{
+	int i, n;
+	struct src *p;
+
+	n = (MEMRD == src->mode) ? src->multi : 1;
+	for (i = 0, p = src; i < n; i++, p++) {
+		mgr->src_disable(mgr, p);
+		rsc_uninit(&p->rsc);
+		p->multi = 0;
+		p->ops = NULL;
+		p->mode = NUM_SRCMODES;
+		p->intlv = NULL;
+	}
+	mgr->commit_write(mgr);
+
+	return 0;
+}
+
+static int
+get_src_rsc(struct src_mgr *mgr, const struct src_desc *desc, struct src **rsrc)
+{
+	unsigned int idx = SRC_RESOURCE_NUM;
+	int err;
+	struct src *src;
+	unsigned long flags;
+
+	*rsrc = NULL;
+
+	/* Check whether there are sufficient src resources to meet request. */
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	if (MEMRD == desc->mode)
+		err = mgr_get_resource(&mgr->mgr, desc->multi, &idx);
+	else
+		err = mgr_get_resource(&mgr->mgr, 1, &idx);
+
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	if (err) {
+		printk(KERN_ERR "ctxfi: Can't meet SRC resource request!\n");
+		return err;
+	}
+
+	/* Allocate mem for master src resource */
+	if (MEMRD == desc->mode)
+		src = kzalloc(sizeof(*src)*desc->multi, GFP_KERNEL);
+	else
+		src = kzalloc(sizeof(*src), GFP_KERNEL);
+
+	if (!src) {
+		err = -ENOMEM;
+		goto error1;
+	}
+
+	err = src_rsc_init(src, idx, desc, mgr);
+	if (err)
+		goto error2;
+
+	*rsrc = src;
+
+	return 0;
+
+error2:
+	kfree(src);
+error1:
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	if (MEMRD == desc->mode)
+		mgr_put_resource(&mgr->mgr, desc->multi, idx);
+	else
+		mgr_put_resource(&mgr->mgr, 1, idx);
+
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	return err;
+}
+
+static int put_src_rsc(struct src_mgr *mgr, struct src *src)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	src->rsc.ops->master(&src->rsc);
+	if (MEMRD == src->mode)
+		mgr_put_resource(&mgr->mgr, src->multi,
+				 src->rsc.ops->index(&src->rsc));
+	else
+		mgr_put_resource(&mgr->mgr, 1, src->rsc.ops->index(&src->rsc));
+
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	src_rsc_uninit(src, mgr);
+	kfree(src);
+
+	return 0;
+}
+
+static int src_enable_s(struct src_mgr *mgr, struct src *src)
+{
+	struct hw *hw = mgr->mgr.hw;
+	int i;
+
+	src->rsc.ops->master(&src->rsc);
+	for (i = 0; i < src->rsc.msr; i++) {
+		hw->src_mgr_enbs_src(mgr->mgr.ctrl_blk,
+				     src->rsc.ops->index(&src->rsc));
+		src->rsc.ops->next_conj(&src->rsc);
+	}
+	src->rsc.ops->master(&src->rsc);
+
+	return 0;
+}
+
+static int src_enable(struct src_mgr *mgr, struct src *src)
+{
+	struct hw *hw = mgr->mgr.hw;
+	int i;
+
+	src->rsc.ops->master(&src->rsc);
+	for (i = 0; i < src->rsc.msr; i++) {
+		hw->src_mgr_enb_src(mgr->mgr.ctrl_blk,
+				    src->rsc.ops->index(&src->rsc));
+		src->rsc.ops->next_conj(&src->rsc);
+	}
+	src->rsc.ops->master(&src->rsc);
+
+	return 0;
+}
+
+static int src_disable(struct src_mgr *mgr, struct src *src)
+{
+	struct hw *hw = mgr->mgr.hw;
+	int i;
+
+	src->rsc.ops->master(&src->rsc);
+	for (i = 0; i < src->rsc.msr; i++) {
+		hw->src_mgr_dsb_src(mgr->mgr.ctrl_blk,
+				    src->rsc.ops->index(&src->rsc));
+		src->rsc.ops->next_conj(&src->rsc);
+	}
+	src->rsc.ops->master(&src->rsc);
+
+	return 0;
+}
+
+static int src_mgr_commit_write(struct src_mgr *mgr)
+{
+	struct hw *hw = mgr->mgr.hw;
+
+	hw->src_mgr_commit_write(hw, mgr->mgr.ctrl_blk);
+
+	return 0;
+}
+
+int src_mgr_create(void *hw, struct src_mgr **rsrc_mgr)
+{
+	int err, i;
+	struct src_mgr *src_mgr;
+
+	*rsrc_mgr = NULL;
+	src_mgr = kzalloc(sizeof(*src_mgr), GFP_KERNEL);
+	if (!src_mgr)
+		return -ENOMEM;
+
+	err = rsc_mgr_init(&src_mgr->mgr, SRC, SRC_RESOURCE_NUM, hw);
+	if (err)
+		goto error1;
+
+	spin_lock_init(&src_mgr->mgr_lock);
+	conj_mask = ((struct hw *)hw)->src_dirty_conj_mask();
+
+	src_mgr->get_src = get_src_rsc;
+	src_mgr->put_src = put_src_rsc;
+	src_mgr->src_enable_s = src_enable_s;
+	src_mgr->src_enable = src_enable;
+	src_mgr->src_disable = src_disable;
+	src_mgr->commit_write = src_mgr_commit_write;
+
+	/* Disable all SRC resources. */
+	for (i = 0; i < 256; i++)
+		((struct hw *)hw)->src_mgr_dsb_src(src_mgr->mgr.ctrl_blk, i);
+
+	((struct hw *)hw)->src_mgr_commit_write(hw, src_mgr->mgr.ctrl_blk);
+
+	*rsrc_mgr = src_mgr;
+
+	return 0;
+
+error1:
+	kfree(src_mgr);
+	return err;
+}
+
+int src_mgr_destroy(struct src_mgr *src_mgr)
+{
+	rsc_mgr_uninit(&src_mgr->mgr);
+	kfree(src_mgr);
+
+	return 0;
+}
+
+/* SRCIMP resource manager operations */
+
+static int srcimp_master(struct rsc *rsc)
+{
+	rsc->conj = 0;
+	return rsc->idx = container_of(rsc, struct srcimp, rsc)->idx[0];
+}
+
+static int srcimp_next_conj(struct rsc *rsc)
+{
+	rsc->conj++;
+	return container_of(rsc, struct srcimp, rsc)->idx[rsc->conj];
+}
+
+static int srcimp_index(const struct rsc *rsc)
+{
+	return container_of(rsc, struct srcimp, rsc)->idx[rsc->conj];
+}
+
+static struct rsc_ops srcimp_basic_rsc_ops = {
+	.master		= srcimp_master,
+	.next_conj	= srcimp_next_conj,
+	.index		= srcimp_index,
+	.output_slot	= NULL,
+};
+
+static int srcimp_map(struct srcimp *srcimp, struct src *src, struct rsc *input)
+{
+	struct imapper *entry;
+	int i;
+
+	srcimp->rsc.ops->master(&srcimp->rsc);
+	src->rsc.ops->master(&src->rsc);
+	input->ops->master(input);
+
+	/* Program master and conjugate resources */
+	for (i = 0; i < srcimp->rsc.msr; i++) {
+		entry = &srcimp->imappers[i];
+		entry->slot = input->ops->output_slot(input);
+		entry->user = src->rsc.ops->index(&src->rsc);
+		entry->addr = srcimp->rsc.ops->index(&srcimp->rsc);
+		srcimp->mgr->imap_add(srcimp->mgr, entry);
+		srcimp->mapped |= (0x1 << i);
+
+		srcimp->rsc.ops->next_conj(&srcimp->rsc);
+		input->ops->next_conj(input);
+	}
+
+	srcimp->rsc.ops->master(&srcimp->rsc);
+	input->ops->master(input);
+
+	return 0;
+}
+
+static int srcimp_unmap(struct srcimp *srcimp)
+{
+	int i;
+
+	/* Program master and conjugate resources */
+	for (i = 0; i < srcimp->rsc.msr; i++) {
+		if (srcimp->mapped & (0x1 << i)) {
+			srcimp->mgr->imap_delete(srcimp->mgr,
+						 &srcimp->imappers[i]);
+			srcimp->mapped &= ~(0x1 << i);
+		}
+	}
+
+	return 0;
+}
+
+static struct srcimp_rsc_ops srcimp_ops = {
+	.map = srcimp_map,
+	.unmap = srcimp_unmap
+};
+
+static int srcimp_rsc_init(struct srcimp *srcimp,
+			   const struct srcimp_desc *desc,
+			   struct srcimp_mgr *mgr)
+{
+	int err;
+
+	err = rsc_init(&srcimp->rsc, srcimp->idx[0],
+		       SRCIMP, desc->msr, mgr->mgr.hw);
+	if (err)
+		return err;
+
+	/* Reserve memory for imapper nodes */
+	srcimp->imappers = kzalloc(sizeof(struct imapper)*desc->msr,
+				   GFP_KERNEL);
+	if (!srcimp->imappers) {
+		err = -ENOMEM;
+		goto error1;
+	}
+
+	/* Set srcimp specific operations */
+	srcimp->rsc.ops = &srcimp_basic_rsc_ops;
+	srcimp->ops = &srcimp_ops;
+	srcimp->mgr = mgr;
+
+	srcimp->rsc.ops->master(&srcimp->rsc);
+
+	return 0;
+
+error1:
+	rsc_uninit(&srcimp->rsc);
+	return err;
+}
+
+static int srcimp_rsc_uninit(struct srcimp *srcimp)
+{
+	if (NULL != srcimp->imappers) {
+		kfree(srcimp->imappers);
+		srcimp->imappers = NULL;
+	}
+	srcimp->ops = NULL;
+	srcimp->mgr = NULL;
+	rsc_uninit(&srcimp->rsc);
+
+	return 0;
+}
+
+static int get_srcimp_rsc(struct srcimp_mgr *mgr,
+			  const struct srcimp_desc *desc,
+			  struct srcimp **rsrcimp)
+{
+	int err, i;
+	unsigned int idx;
+	struct srcimp *srcimp;
+	unsigned long flags;
+
+	*rsrcimp = NULL;
+
+	/* Allocate mem for SRCIMP resource */
+	srcimp = kzalloc(sizeof(*srcimp), GFP_KERNEL);
+	if (!srcimp)
+		return -ENOMEM;
+
+	/* Check whether there are sufficient SRCIMP resources. */
+	err = 0;
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	for (i = 0; i < desc->msr; i++) {
+		err = mgr_get_resource(&mgr->mgr, 1, &idx);
+		if (err)
+			break;
+
+		srcimp->idx[i] = idx;
+	}
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	if (err) {
+		printk(KERN_ERR "ctxfi: Can't meet SRCIMP resource request!\n");
+		goto error1;
+	}
+
+	err = srcimp_rsc_init(srcimp, desc, mgr);
+	if (err)
+		goto error1;
+
+	*rsrcimp = srcimp;
+
+	return 0;
+
+error1:
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	for (i--; i >= 0; i--)
+		mgr_put_resource(&mgr->mgr, 1, srcimp->idx[i]);
+
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	kfree(srcimp);
+	return err;
+}
+
+static int put_srcimp_rsc(struct srcimp_mgr *mgr, struct srcimp *srcimp)
+{
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&mgr->mgr_lock, flags);
+	for (i = 0; i < srcimp->rsc.msr; i++)
+		mgr_put_resource(&mgr->mgr, 1, srcimp->idx[i]);
+
+	spin_unlock_irqrestore(&mgr->mgr_lock, flags);
+	srcimp_rsc_uninit(srcimp);
+	kfree(srcimp);
+
+	return 0;
+}
+
+static int srcimp_map_op(void *data, struct imapper *entry)
+{
+	struct rsc_mgr *mgr = &((struct srcimp_mgr *)data)->mgr;
+	struct hw *hw = mgr->hw;
+
+	hw->srcimp_mgr_set_imaparc(mgr->ctrl_blk, entry->slot);
+	hw->srcimp_mgr_set_imapuser(mgr->ctrl_blk, entry->user);
+	hw->srcimp_mgr_set_imapnxt(mgr->ctrl_blk, entry->next);
+	hw->srcimp_mgr_set_imapaddr(mgr->ctrl_blk, entry->addr);
+	hw->srcimp_mgr_commit_write(mgr->hw, mgr->ctrl_blk);
+
+	return 0;
+}
+
+static int srcimp_imap_add(struct srcimp_mgr *mgr, struct imapper *entry)
+{
+	unsigned long flags;
+	int err;
+
+	spin_lock_irqsave(&mgr->imap_lock, flags);
+	if ((0 == entry->addr) && (mgr->init_imap_added)) {
+		input_mapper_delete(&mgr->imappers,
+				    mgr->init_imap, srcimp_map_op, mgr);
+		mgr->init_imap_added = 0;
+	}
+	err = input_mapper_add(&mgr->imappers, entry, srcimp_map_op, mgr);
+	spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+	return err;
+}
+
+static int srcimp_imap_delete(struct srcimp_mgr *mgr, struct imapper *entry)
+{
+	unsigned long flags;
+	int err;
+
+	spin_lock_irqsave(&mgr->imap_lock, flags);
+	err = input_mapper_delete(&mgr->imappers, entry, srcimp_map_op, mgr);
+	if (list_empty(&mgr->imappers)) {
+		input_mapper_add(&mgr->imappers, mgr->init_imap,
+				 srcimp_map_op, mgr);
+		mgr->init_imap_added = 1;
+	}
+	spin_unlock_irqrestore(&mgr->imap_lock, flags);
+
+	return err;
+}
+
+int srcimp_mgr_create(void *hw, struct srcimp_mgr **rsrcimp_mgr)
+{
+	int err;
+	struct srcimp_mgr *srcimp_mgr;
+	struct imapper *entry;
+
+	*rsrcimp_mgr = NULL;
+	srcimp_mgr = kzalloc(sizeof(*srcimp_mgr), GFP_KERNEL);
+	if (!srcimp_mgr)
+		return -ENOMEM;
+
+	err = rsc_mgr_init(&srcimp_mgr->mgr, SRCIMP, SRCIMP_RESOURCE_NUM, hw);
+	if (err)
+		goto error1;
+
+	spin_lock_init(&srcimp_mgr->mgr_lock);
+	spin_lock_init(&srcimp_mgr->imap_lock);
+	INIT_LIST_HEAD(&srcimp_mgr->imappers);
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry) {
+		err = -ENOMEM;
+		goto error2;
+	}
+	entry->slot = entry->addr = entry->next = entry->user = 0;
+	list_add(&entry->list, &srcimp_mgr->imappers);
+	srcimp_mgr->init_imap = entry;
+	srcimp_mgr->init_imap_added = 1;
+
+	srcimp_mgr->get_srcimp = get_srcimp_rsc;
+	srcimp_mgr->put_srcimp = put_srcimp_rsc;
+	srcimp_mgr->imap_add = srcimp_imap_add;
+	srcimp_mgr->imap_delete = srcimp_imap_delete;
+
+	*rsrcimp_mgr = srcimp_mgr;
+
+	return 0;
+
+error2:
+	rsc_mgr_uninit(&srcimp_mgr->mgr);
+error1:
+	kfree(srcimp_mgr);
+	return err;
+}
+
+int srcimp_mgr_destroy(struct srcimp_mgr *srcimp_mgr)
+{
+	unsigned long flags;
+
+	/* free src input mapper list */
+	spin_lock_irqsave(&srcimp_mgr->imap_lock, flags);
+	free_input_mapper_list(&srcimp_mgr->imappers);
+	spin_unlock_irqrestore(&srcimp_mgr->imap_lock, flags);
+
+	rsc_mgr_uninit(&srcimp_mgr->mgr);
+	kfree(srcimp_mgr);
+
+	return 0;
+}
diff --git a/linux/sound/pci/ctxfi/ctsrc.h b/linux/sound/pci/ctxfi/ctsrc.h
new file mode 100644
index 0000000..259366a
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctsrc.h
@@ -0,0 +1,149 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File	ctsrc.h
+ *
+ * @Brief
+ * This file contains the definition of the Sample Rate Convertor
+ * resource management object.
+ *
+ * @Author	Liu Chun
+ * @Date 	May 13 2008
+ *
+ */
+
+#ifndef CTSRC_H
+#define CTSRC_H
+
+#include "ctresource.h"
+#include "ctimap.h"
+#include <linux/spinlock.h>
+#include <linux/list.h>
+
+#define SRC_STATE_OFF	0x0
+#define SRC_STATE_INIT	0x4
+#define SRC_STATE_RUN	0x5
+
+#define SRC_SF_U8	0x0
+#define SRC_SF_S16	0x1
+#define SRC_SF_S24	0x2
+#define SRC_SF_S32	0x3
+#define SRC_SF_F32	0x4
+
+/* Define the descriptor of a src resource */
+enum SRCMODE {
+	MEMRD,		/* Read data from host memory */
+	MEMWR,		/* Write data to host memory */
+	ARCRW,		/* Read from and write to audio ring channel */
+	NUM_SRCMODES
+};
+
+struct src_rsc_ops;
+
+struct src {
+	struct rsc rsc; /* Basic resource info */
+	struct src *intlv; /* Pointer to next interleaved SRC in a series */
+	struct src_rsc_ops *ops; /* SRC specific operations */
+	/* Number of contiguous srcs for interleaved usage */
+	unsigned char multi;
+	unsigned char mode; /* Working mode of this SRC resource */
+};
+
+struct src_rsc_ops {
+	int (*set_state)(struct src *src, unsigned int state);
+	int (*set_bm)(struct src *src, unsigned int bm);
+	int (*set_sf)(struct src *src, unsigned int sf);
+	int (*set_pm)(struct src *src, unsigned int pm);
+	int (*set_rom)(struct src *src, unsigned int rom);
+	int (*set_vo)(struct src *src, unsigned int vo);
+	int (*set_st)(struct src *src, unsigned int st);
+	int (*set_bp)(struct src *src, unsigned int bp);
+	int (*set_cisz)(struct src *src, unsigned int cisz);
+	int (*set_ca)(struct src *src, unsigned int ca);
+	int (*set_sa)(struct src *src, unsigned int sa);
+	int (*set_la)(struct src *src, unsigned int la);
+	int (*set_pitch)(struct src *src, unsigned int pitch);
+	int (*set_clr_zbufs)(struct src *src);
+	int (*commit_write)(struct src *src);
+	int (*get_ca)(struct src *src);
+	int (*init)(struct src *src);
+	struct src* (*next_interleave)(struct src *src);
+};
+
+/* Define src resource request description info */
+struct src_desc {
+	/* Number of contiguous master srcs for interleaved usage */
+	unsigned char multi;
+	unsigned char msr;
+	unsigned char mode; /* Working mode of the requested srcs */
+};
+
+/* Define src manager object */
+struct src_mgr {
+	struct rsc_mgr mgr;	/* Basic resource manager info */
+	spinlock_t mgr_lock;
+
+	 /* request src resource */
+	int (*get_src)(struct src_mgr *mgr,
+		       const struct src_desc *desc, struct src **rsrc);
+	/* return src resource */
+	int (*put_src)(struct src_mgr *mgr, struct src *src);
+	int (*src_enable_s)(struct src_mgr *mgr, struct src *src);
+	int (*src_enable)(struct src_mgr *mgr, struct src *src);
+	int (*src_disable)(struct src_mgr *mgr, struct src *src);
+	int (*commit_write)(struct src_mgr *mgr);
+};
+
+/* Define the descriptor of a SRC Input Mapper resource */
+struct srcimp_mgr;
+struct srcimp_rsc_ops;
+
+struct srcimp {
+	struct rsc rsc;
+	unsigned char idx[8];
+	struct imapper *imappers;
+	unsigned int mapped; /* A bit-map indicating which conj rsc is mapped */
+	struct srcimp_mgr *mgr;
+	struct srcimp_rsc_ops *ops;
+};
+
+struct srcimp_rsc_ops {
+	int (*map)(struct srcimp *srcimp, struct src *user, struct rsc *input);
+	int (*unmap)(struct srcimp *srcimp);
+};
+
+/* Define SRCIMP resource request description info */
+struct srcimp_desc {
+	unsigned int msr;
+};
+
+struct srcimp_mgr {
+	struct rsc_mgr mgr;	/* Basic resource manager info */
+	spinlock_t mgr_lock;
+	spinlock_t imap_lock;
+	struct list_head imappers;
+	struct imapper *init_imap;
+	unsigned int init_imap_added;
+
+	 /* request srcimp resource */
+	int (*get_srcimp)(struct srcimp_mgr *mgr,
+			  const struct srcimp_desc *desc,
+			  struct srcimp **rsrcimp);
+	/* return srcimp resource */
+	int (*put_srcimp)(struct srcimp_mgr *mgr, struct srcimp *srcimp);
+	int (*imap_add)(struct srcimp_mgr *mgr, struct imapper *entry);
+	int (*imap_delete)(struct srcimp_mgr *mgr, struct imapper *entry);
+};
+
+/* Constructor and destructor of SRC resource manager */
+int src_mgr_create(void *hw, struct src_mgr **rsrc_mgr);
+int src_mgr_destroy(struct src_mgr *src_mgr);
+/* Constructor and destructor of SRCIMP resource manager */
+int srcimp_mgr_create(void *hw, struct srcimp_mgr **rsrc_mgr);
+int srcimp_mgr_destroy(struct srcimp_mgr *srcimp_mgr);
+
+#endif /* CTSRC_H */
diff --git a/linux/sound/pci/ctxfi/cttimer.c b/linux/sound/pci/ctxfi/cttimer.c
new file mode 100644
index 0000000..93b0aed
--- /dev/null
+++ b/linux/sound/pci/ctxfi/cttimer.c
@@ -0,0 +1,443 @@
+/*
+ * PCM timer handling on ctxfi
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ */
+
+#include <linux/slab.h>
+#include <linux/math64.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "ctatc.h"
+#include "cthardware.h"
+#include "cttimer.h"
+
+static int use_system_timer;
+MODULE_PARM_DESC(use_system_timer, "Foce to use system-timer");
+module_param(use_system_timer, bool, S_IRUGO);
+
+struct ct_timer_ops {
+	void (*init)(struct ct_timer_instance *);
+	void (*prepare)(struct ct_timer_instance *);
+	void (*start)(struct ct_timer_instance *);
+	void (*stop)(struct ct_timer_instance *);
+	void (*free_instance)(struct ct_timer_instance *);
+	void (*interrupt)(struct ct_timer *);
+	void (*free_global)(struct ct_timer *);
+};
+
+/* timer instance -- assigned to each PCM stream */
+struct ct_timer_instance {
+	spinlock_t lock;
+	struct ct_timer *timer_base;
+	struct ct_atc_pcm *apcm;
+	struct snd_pcm_substream *substream;
+	struct timer_list timer;
+	struct list_head instance_list;
+	struct list_head running_list;
+	unsigned int position;
+	unsigned int frag_count;
+	unsigned int running:1;
+	unsigned int need_update:1;
+};
+
+/* timer instance manager */
+struct ct_timer {
+	spinlock_t lock;		/* global timer lock (for xfitimer) */
+	spinlock_t list_lock;		/* lock for instance list */
+	struct ct_atc *atc;
+	struct ct_timer_ops *ops;
+	struct list_head instance_head;
+	struct list_head running_head;
+	unsigned int wc;		/* current wallclock */
+	unsigned int irq_handling:1;	/* in IRQ handling */
+	unsigned int reprogram:1;	/* need to reprogram the internval */
+	unsigned int running:1;		/* global timer running */
+};
+
+
+/*
+ * system-timer-based updates
+ */
+
+static void ct_systimer_callback(unsigned long data)
+{
+	struct ct_timer_instance *ti = (struct ct_timer_instance *)data;
+	struct snd_pcm_substream *substream = ti->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ct_atc_pcm *apcm = ti->apcm;
+	unsigned int period_size = runtime->period_size;
+	unsigned int buffer_size = runtime->buffer_size;
+	unsigned long flags;
+	unsigned int position, dist, interval;
+
+	position = substream->ops->pointer(substream);
+	dist = (position + buffer_size - ti->position) % buffer_size;
+	if (dist >= period_size ||
+	    position / period_size != ti->position / period_size) {
+		apcm->interrupt(apcm);
+		ti->position = position;
+	}
+	/* Add extra HZ*5/1000 to avoid overrun issue when recording
+	 * at 8kHz in 8-bit format or at 88kHz in 24-bit format. */
+	interval = ((period_size - (position % period_size))
+		   * HZ + (runtime->rate - 1)) / runtime->rate + HZ * 5 / 1000;
+	spin_lock_irqsave(&ti->lock, flags);
+	if (ti->running)
+		mod_timer(&ti->timer, jiffies + interval);
+	spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+static void ct_systimer_init(struct ct_timer_instance *ti)
+{
+	setup_timer(&ti->timer, ct_systimer_callback,
+		    (unsigned long)ti);
+}
+
+static void ct_systimer_start(struct ct_timer_instance *ti)
+{
+	struct snd_pcm_runtime *runtime = ti->substream->runtime;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ti->lock, flags);
+	ti->running = 1;
+	mod_timer(&ti->timer,
+		  jiffies + (runtime->period_size * HZ +
+			     (runtime->rate - 1)) / runtime->rate);
+	spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+static void ct_systimer_stop(struct ct_timer_instance *ti)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ti->lock, flags);
+	ti->running = 0;
+	del_timer(&ti->timer);
+	spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+static void ct_systimer_prepare(struct ct_timer_instance *ti)
+{
+	ct_systimer_stop(ti);
+	try_to_del_timer_sync(&ti->timer);
+}
+
+#define ct_systimer_free	ct_systimer_prepare
+
+static struct ct_timer_ops ct_systimer_ops = {
+	.init = ct_systimer_init,
+	.free_instance = ct_systimer_free,
+	.prepare = ct_systimer_prepare,
+	.start = ct_systimer_start,
+	.stop = ct_systimer_stop,
+};
+
+
+/*
+ * Handling multiple streams using a global emu20k1 timer irq
+ */
+
+#define CT_TIMER_FREQ	48000
+#define MIN_TICKS	1
+#define MAX_TICKS	((1 << 13) - 1)
+
+static void ct_xfitimer_irq_rearm(struct ct_timer *atimer, int ticks)
+{
+	struct hw *hw = atimer->atc->hw;
+	if (ticks > MAX_TICKS)
+		ticks = MAX_TICKS;
+	hw->set_timer_tick(hw, ticks);
+	if (!atimer->running)
+		hw->set_timer_irq(hw, 1);
+	atimer->running = 1;
+}
+
+static void ct_xfitimer_irq_stop(struct ct_timer *atimer)
+{
+	if (atimer->running) {
+		struct hw *hw = atimer->atc->hw;
+		hw->set_timer_irq(hw, 0);
+		hw->set_timer_tick(hw, 0);
+		atimer->running = 0;
+	}
+}
+
+static inline unsigned int ct_xfitimer_get_wc(struct ct_timer *atimer)
+{
+	struct hw *hw = atimer->atc->hw;
+	return hw->get_wc(hw);
+}
+
+/*
+ * reprogram the timer interval;
+ * checks the running instance list and determines the next timer interval.
+ * also updates the each stream position, returns the number of streams
+ * to call snd_pcm_period_elapsed() appropriately
+ *
+ * call this inside the lock and irq disabled
+ */
+static int ct_xfitimer_reprogram(struct ct_timer *atimer, int can_update)
+{
+	struct ct_timer_instance *ti;
+	unsigned int min_intr = (unsigned int)-1;
+	int updates = 0;
+	unsigned int wc, diff;
+
+	if (list_empty(&atimer->running_head)) {
+		ct_xfitimer_irq_stop(atimer);
+		atimer->reprogram = 0; /* clear flag */
+		return 0;
+	}
+
+	wc = ct_xfitimer_get_wc(atimer);
+	diff = wc - atimer->wc;
+	atimer->wc = wc;
+	list_for_each_entry(ti, &atimer->running_head, running_list) {
+		if (ti->frag_count > diff)
+			ti->frag_count -= diff;
+		else {
+			unsigned int pos;
+			unsigned int period_size, rate;
+
+			period_size = ti->substream->runtime->period_size;
+			rate = ti->substream->runtime->rate;
+			pos = ti->substream->ops->pointer(ti->substream);
+			if (pos / period_size != ti->position / period_size) {
+				ti->need_update = 1;
+				ti->position = pos;
+				updates++;
+			}
+			pos %= period_size;
+			pos = period_size - pos;
+			ti->frag_count = div_u64((u64)pos * CT_TIMER_FREQ +
+						 rate - 1, rate);
+		}
+		if (ti->need_update && !can_update)
+			min_intr = 0; /* pending to the next irq */
+		if (ti->frag_count < min_intr)
+			min_intr = ti->frag_count;
+	}
+
+	if (min_intr < MIN_TICKS)
+		min_intr = MIN_TICKS;
+	ct_xfitimer_irq_rearm(atimer, min_intr);
+	atimer->reprogram = 0; /* clear flag */
+	return updates;
+}
+
+/* look through the instance list and call period_elapsed if needed */
+static void ct_xfitimer_check_period(struct ct_timer *atimer)
+{
+	struct ct_timer_instance *ti;
+	unsigned long flags;
+
+	spin_lock_irqsave(&atimer->list_lock, flags);
+	list_for_each_entry(ti, &atimer->instance_head, instance_list) {
+		if (ti->running && ti->need_update) {
+			ti->need_update = 0;
+			ti->apcm->interrupt(ti->apcm);
+		}
+	}
+	spin_unlock_irqrestore(&atimer->list_lock, flags);
+}
+
+/* Handle timer-interrupt */
+static void ct_xfitimer_callback(struct ct_timer *atimer)
+{
+	int update;
+	unsigned long flags;
+
+	spin_lock_irqsave(&atimer->lock, flags);
+	atimer->irq_handling = 1;
+	do {
+		update = ct_xfitimer_reprogram(atimer, 1);
+		spin_unlock(&atimer->lock);
+		if (update)
+			ct_xfitimer_check_period(atimer);
+		spin_lock(&atimer->lock);
+	} while (atimer->reprogram);
+	atimer->irq_handling = 0;
+	spin_unlock_irqrestore(&atimer->lock, flags);
+}
+
+static void ct_xfitimer_prepare(struct ct_timer_instance *ti)
+{
+	ti->frag_count = ti->substream->runtime->period_size;
+	ti->running = 0;
+	ti->need_update = 0;
+}
+
+
+/* start/stop the timer */
+static void ct_xfitimer_update(struct ct_timer *atimer)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&atimer->lock, flags);
+	if (atimer->irq_handling) {
+		/* reached from IRQ handler; let it handle later */
+		atimer->reprogram = 1;
+		spin_unlock_irqrestore(&atimer->lock, flags);
+		return;
+	}
+
+	ct_xfitimer_irq_stop(atimer);
+	ct_xfitimer_reprogram(atimer, 0);
+	spin_unlock_irqrestore(&atimer->lock, flags);
+}
+
+static void ct_xfitimer_start(struct ct_timer_instance *ti)
+{
+	struct ct_timer *atimer = ti->timer_base;
+	unsigned long flags;
+
+	spin_lock_irqsave(&atimer->lock, flags);
+	if (list_empty(&ti->running_list))
+		atimer->wc = ct_xfitimer_get_wc(atimer);
+	ti->running = 1;
+	ti->need_update = 0;
+	list_add(&ti->running_list, &atimer->running_head);
+	spin_unlock_irqrestore(&atimer->lock, flags);
+	ct_xfitimer_update(atimer);
+}
+
+static void ct_xfitimer_stop(struct ct_timer_instance *ti)
+{
+	struct ct_timer *atimer = ti->timer_base;
+	unsigned long flags;
+
+	spin_lock_irqsave(&atimer->lock, flags);
+	list_del_init(&ti->running_list);
+	ti->running = 0;
+	spin_unlock_irqrestore(&atimer->lock, flags);
+	ct_xfitimer_update(atimer);
+}
+
+static void ct_xfitimer_free_global(struct ct_timer *atimer)
+{
+	ct_xfitimer_irq_stop(atimer);
+}
+
+static struct ct_timer_ops ct_xfitimer_ops = {
+	.prepare = ct_xfitimer_prepare,
+	.start = ct_xfitimer_start,
+	.stop = ct_xfitimer_stop,
+	.interrupt = ct_xfitimer_callback,
+	.free_global = ct_xfitimer_free_global,
+};
+
+/*
+ * timer instance
+ */
+
+struct ct_timer_instance *
+ct_timer_instance_new(struct ct_timer *atimer, struct ct_atc_pcm *apcm)
+{
+	struct ct_timer_instance *ti;
+
+	ti = kzalloc(sizeof(*ti), GFP_KERNEL);
+	if (!ti)
+		return NULL;
+	spin_lock_init(&ti->lock);
+	INIT_LIST_HEAD(&ti->instance_list);
+	INIT_LIST_HEAD(&ti->running_list);
+	ti->timer_base = atimer;
+	ti->apcm = apcm;
+	ti->substream = apcm->substream;
+	if (atimer->ops->init)
+		atimer->ops->init(ti);
+
+	spin_lock_irq(&atimer->list_lock);
+	list_add(&ti->instance_list, &atimer->instance_head);
+	spin_unlock_irq(&atimer->list_lock);
+
+	return ti;
+}
+
+void ct_timer_prepare(struct ct_timer_instance *ti)
+{
+	if (ti->timer_base->ops->prepare)
+		ti->timer_base->ops->prepare(ti);
+	ti->position = 0;
+	ti->running = 0;
+}
+
+void ct_timer_start(struct ct_timer_instance *ti)
+{
+	struct ct_timer *atimer = ti->timer_base;
+	atimer->ops->start(ti);
+}
+
+void ct_timer_stop(struct ct_timer_instance *ti)
+{
+	struct ct_timer *atimer = ti->timer_base;
+	atimer->ops->stop(ti);
+}
+
+void ct_timer_instance_free(struct ct_timer_instance *ti)
+{
+	struct ct_timer *atimer = ti->timer_base;
+
+	atimer->ops->stop(ti); /* to be sure */
+	if (atimer->ops->free_instance)
+		atimer->ops->free_instance(ti);
+
+	spin_lock_irq(&atimer->list_lock);
+	list_del(&ti->instance_list);
+	spin_unlock_irq(&atimer->list_lock);
+
+	kfree(ti);
+}
+
+/*
+ * timer manager
+ */
+
+static void ct_timer_interrupt(void *data, unsigned int status)
+{
+	struct ct_timer *timer = data;
+
+	/* Interval timer interrupt */
+	if ((status & IT_INT) && timer->ops->interrupt)
+		timer->ops->interrupt(timer);
+}
+
+struct ct_timer *ct_timer_new(struct ct_atc *atc)
+{
+	struct ct_timer *atimer;
+	struct hw *hw;
+
+	atimer = kzalloc(sizeof(*atimer), GFP_KERNEL);
+	if (!atimer)
+		return NULL;
+	spin_lock_init(&atimer->lock);
+	spin_lock_init(&atimer->list_lock);
+	INIT_LIST_HEAD(&atimer->instance_head);
+	INIT_LIST_HEAD(&atimer->running_head);
+	atimer->atc = atc;
+	hw = atc->hw;
+	if (!use_system_timer && hw->set_timer_irq) {
+		snd_printd(KERN_INFO "ctxfi: Use xfi-native timer\n");
+		atimer->ops = &ct_xfitimer_ops;
+		hw->irq_callback_data = atimer;
+		hw->irq_callback = ct_timer_interrupt;
+	} else {
+		snd_printd(KERN_INFO "ctxfi: Use system timer\n");
+		atimer->ops = &ct_systimer_ops;
+	}
+	return atimer;
+}
+
+void ct_timer_free(struct ct_timer *atimer)
+{
+	struct hw *hw = atimer->atc->hw;
+	hw->irq_callback = NULL;
+	if (atimer->ops->free_global)
+		atimer->ops->free_global(atimer);
+	kfree(atimer);
+}
+
diff --git a/linux/sound/pci/ctxfi/cttimer.h b/linux/sound/pci/ctxfi/cttimer.h
new file mode 100644
index 0000000..9793482
--- /dev/null
+++ b/linux/sound/pci/ctxfi/cttimer.h
@@ -0,0 +1,29 @@
+/*
+ * Timer handling
+ */
+
+#ifndef __CTTIMER_H
+#define __CTTIMER_H
+
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+
+struct snd_pcm_substream;
+struct ct_atc;
+struct ct_atc_pcm;
+
+struct ct_timer;
+struct ct_timer_instance;
+
+struct ct_timer *ct_timer_new(struct ct_atc *atc);
+void ct_timer_free(struct ct_timer *atimer);
+
+struct ct_timer_instance *
+ct_timer_instance_new(struct ct_timer *atimer, struct ct_atc_pcm *apcm);
+void ct_timer_instance_free(struct ct_timer_instance *ti);
+void ct_timer_start(struct ct_timer_instance *ti);
+void ct_timer_stop(struct ct_timer_instance *ti);
+void ct_timer_prepare(struct ct_timer_instance *ti);
+
+#endif /* __CTTIMER_H */
diff --git a/linux/sound/pci/ctxfi/ctvmem.c b/linux/sound/pci/ctxfi/ctvmem.c
new file mode 100644
index 0000000..65da6e4
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctvmem.c
@@ -0,0 +1,248 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File    ctvmem.c
+ *
+ * @Brief
+ * This file contains the implementation of virtual memory management object
+ * for card device.
+ *
+ * @Author Liu Chun
+ * @Date Apr 1 2008
+ */
+
+#include "ctvmem.h"
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <sound/pcm.h>
+
+#define CT_PTES_PER_PAGE (CT_PAGE_SIZE / sizeof(void *))
+#define CT_ADDRS_PER_PAGE (CT_PTES_PER_PAGE * CT_PAGE_SIZE)
+
+/* *
+ * Find or create vm block based on requested @size.
+ * @size must be page aligned.
+ * */
+static struct ct_vm_block *
+get_vm_block(struct ct_vm *vm, unsigned int size)
+{
+	struct ct_vm_block *block = NULL, *entry;
+	struct list_head *pos;
+
+	size = CT_PAGE_ALIGN(size);
+	if (size > vm->size) {
+		printk(KERN_ERR "ctxfi: Fail! No sufficient device virtural "
+				  "memory space available!\n");
+		return NULL;
+	}
+
+	mutex_lock(&vm->lock);
+	list_for_each(pos, &vm->unused) {
+		entry = list_entry(pos, struct ct_vm_block, list);
+		if (entry->size >= size)
+			break; /* found a block that is big enough */
+	}
+	if (pos == &vm->unused)
+		goto out;
+
+	if (entry->size == size) {
+		/* Move the vm node from unused list to used list directly */
+		list_del(&entry->list);
+		list_add(&entry->list, &vm->used);
+		vm->size -= size;
+		block = entry;
+		goto out;
+	}
+
+	block = kzalloc(sizeof(*block), GFP_KERNEL);
+	if (!block)
+		goto out;
+
+	block->addr = entry->addr;
+	block->size = size;
+	list_add(&block->list, &vm->used);
+	entry->addr += size;
+	entry->size -= size;
+	vm->size -= size;
+
+ out:
+	mutex_unlock(&vm->lock);
+	return block;
+}
+
+static void put_vm_block(struct ct_vm *vm, struct ct_vm_block *block)
+{
+	struct ct_vm_block *entry, *pre_ent;
+	struct list_head *pos, *pre;
+
+	block->size = CT_PAGE_ALIGN(block->size);
+
+	mutex_lock(&vm->lock);
+	list_del(&block->list);
+	vm->size += block->size;
+
+	list_for_each(pos, &vm->unused) {
+		entry = list_entry(pos, struct ct_vm_block, list);
+		if (entry->addr >= (block->addr + block->size))
+			break; /* found a position */
+	}
+	if (pos == &vm->unused) {
+		list_add_tail(&block->list, &vm->unused);
+		entry = block;
+	} else {
+		if ((block->addr + block->size) == entry->addr) {
+			entry->addr = block->addr;
+			entry->size += block->size;
+			kfree(block);
+		} else {
+			__list_add(&block->list, pos->prev, pos);
+			entry = block;
+		}
+	}
+
+	pos = &entry->list;
+	pre = pos->prev;
+	while (pre != &vm->unused) {
+		entry = list_entry(pos, struct ct_vm_block, list);
+		pre_ent = list_entry(pre, struct ct_vm_block, list);
+		if ((pre_ent->addr + pre_ent->size) > entry->addr)
+			break;
+
+		pre_ent->size += entry->size;
+		list_del(pos);
+		kfree(entry);
+		pos = pre;
+		pre = pos->prev;
+	}
+	mutex_unlock(&vm->lock);
+}
+
+/* Map host addr (kmalloced/vmalloced) to device logical addr. */
+static struct ct_vm_block *
+ct_vm_map(struct ct_vm *vm, struct snd_pcm_substream *substream, int size)
+{
+	struct ct_vm_block *block;
+	unsigned int pte_start;
+	unsigned i, pages;
+	unsigned long *ptp;
+
+	block = get_vm_block(vm, size);
+	if (block == NULL) {
+		printk(KERN_ERR "ctxfi: No virtual memory block that is big "
+				  "enough to allocate!\n");
+		return NULL;
+	}
+
+	ptp = (unsigned long *)vm->ptp[0].area;
+	pte_start = (block->addr >> CT_PAGE_SHIFT);
+	pages = block->size >> CT_PAGE_SHIFT;
+	for (i = 0; i < pages; i++) {
+		unsigned long addr;
+		addr = snd_pcm_sgbuf_get_addr(substream, i << CT_PAGE_SHIFT);
+		ptp[pte_start + i] = addr;
+	}
+
+	block->size = size;
+	return block;
+}
+
+static void ct_vm_unmap(struct ct_vm *vm, struct ct_vm_block *block)
+{
+	/* do unmapping */
+	put_vm_block(vm, block);
+}
+
+/* *
+ * return the host physical addr of the @index-th device
+ * page table page on success, or ~0UL on failure.
+ * The first returned ~0UL indicates the termination.
+ * */
+static dma_addr_t
+ct_get_ptp_phys(struct ct_vm *vm, int index)
+{
+	dma_addr_t addr;
+
+	addr = (index >= CT_PTP_NUM) ? ~0UL : vm->ptp[index].addr;
+
+	return addr;
+}
+
+int ct_vm_create(struct ct_vm **rvm, struct pci_dev *pci)
+{
+	struct ct_vm *vm;
+	struct ct_vm_block *block;
+	int i, err = 0;
+
+	*rvm = NULL;
+
+	vm = kzalloc(sizeof(*vm), GFP_KERNEL);
+	if (!vm)
+		return -ENOMEM;
+
+	mutex_init(&vm->lock);
+
+	/* Allocate page table pages */
+	for (i = 0; i < CT_PTP_NUM; i++) {
+		err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+					  snd_dma_pci_data(pci),
+					  PAGE_SIZE, &vm->ptp[i]);
+		if (err < 0)
+			break;
+	}
+	if (err < 0) {
+		/* no page table pages are allocated */
+		ct_vm_destroy(vm);
+		return -ENOMEM;
+	}
+	vm->size = CT_ADDRS_PER_PAGE * i;
+	vm->map = ct_vm_map;
+	vm->unmap = ct_vm_unmap;
+	vm->get_ptp_phys = ct_get_ptp_phys;
+	INIT_LIST_HEAD(&vm->unused);
+	INIT_LIST_HEAD(&vm->used);
+	block = kzalloc(sizeof(*block), GFP_KERNEL);
+	if (NULL != block) {
+		block->addr = 0;
+		block->size = vm->size;
+		list_add(&block->list, &vm->unused);
+	}
+
+	*rvm = vm;
+	return 0;
+}
+
+/* The caller must ensure no mapping pages are being used
+ * by hardware before calling this function */
+void ct_vm_destroy(struct ct_vm *vm)
+{
+	int i;
+	struct list_head *pos;
+	struct ct_vm_block *entry;
+
+	/* free used and unused list nodes */
+	while (!list_empty(&vm->used)) {
+		pos = vm->used.next;
+		list_del(pos);
+		entry = list_entry(pos, struct ct_vm_block, list);
+		kfree(entry);
+	}
+	while (!list_empty(&vm->unused)) {
+		pos = vm->unused.next;
+		list_del(pos);
+		entry = list_entry(pos, struct ct_vm_block, list);
+		kfree(entry);
+	}
+
+	/* free allocated page table pages */
+	for (i = 0; i < CT_PTP_NUM; i++)
+		snd_dma_free_pages(&vm->ptp[i]);
+
+	vm->size = 0;
+
+	kfree(vm);
+}
diff --git a/linux/sound/pci/ctxfi/ctvmem.h b/linux/sound/pci/ctxfi/ctvmem.h
new file mode 100644
index 0000000..b23adfc
--- /dev/null
+++ b/linux/sound/pci/ctxfi/ctvmem.h
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File    ctvmem.h
+ *
+ * @Brief
+ * This file contains the definition of virtual memory management object
+ * for card device.
+ *
+ * @Author Liu Chun
+ * @Date Mar 28 2008
+ */
+
+#ifndef CTVMEM_H
+#define CTVMEM_H
+
+#define CT_PTP_NUM	1	/* num of device page table pages */
+
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <sound/memalloc.h>
+
+/* The chip can handle the page table of 4k pages
+ * (emu20k1 can handle even 8k pages, but we don't use it right now)
+ */
+#define CT_PAGE_SIZE	4096
+#define CT_PAGE_SHIFT	12
+#define CT_PAGE_MASK	(~(PAGE_SIZE - 1))
+#define CT_PAGE_ALIGN(addr)	ALIGN(addr, CT_PAGE_SIZE)
+
+struct ct_vm_block {
+	unsigned int addr;	/* starting logical addr of this block */
+	unsigned int size;	/* size of this device virtual mem block */
+	struct list_head list;
+};
+
+struct snd_pcm_substream;
+
+/* Virtual memory management object for card device */
+struct ct_vm {
+	struct snd_dma_buffer ptp[CT_PTP_NUM];	/* Device page table pages */
+	unsigned int size;		/* Available addr space in bytes */
+	struct list_head unused;	/* List of unused blocks */
+	struct list_head used;		/* List of used blocks */
+	struct mutex lock;
+
+	/* Map host addr (kmalloced/vmalloced) to device logical addr. */
+	struct ct_vm_block *(*map)(struct ct_vm *, struct snd_pcm_substream *,
+				   int size);
+	/* Unmap device logical addr area. */
+	void (*unmap)(struct ct_vm *, struct ct_vm_block *block);
+	dma_addr_t (*get_ptp_phys)(struct ct_vm *vm, int index);
+};
+
+int ct_vm_create(struct ct_vm **rvm, struct pci_dev *pci);
+void ct_vm_destroy(struct ct_vm *vm);
+
+#endif /* CTVMEM_H */
diff --git a/linux/sound/pci/ctxfi/xfi.c b/linux/sound/pci/ctxfi/xfi.c
new file mode 100644
index 0000000..f42e7e1
--- /dev/null
+++ b/linux/sound/pci/ctxfi/xfi.c
@@ -0,0 +1,167 @@
+/*
+ * xfi linux driver.
+ *
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/pci_ids.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include "ctatc.h"
+#include "cthardware.h"
+
+MODULE_AUTHOR("Creative Technology Ltd");
+MODULE_DESCRIPTION("X-Fi driver version 1.03");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs, Sound Blaster X-Fi}");
+
+static unsigned int reference_rate = 48000;
+static unsigned int multiple = 2;
+MODULE_PARM_DESC(reference_rate, "Reference rate (default=48000)");
+module_param(reference_rate, uint, S_IRUGO);
+MODULE_PARM_DESC(multiple, "Rate multiplier (default=2)");
+module_param(multiple, uint, S_IRUGO);
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+static unsigned int subsystem[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Creative X-Fi driver");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Creative X-Fi driver");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Creative X-Fi driver");
+module_param_array(subsystem, int, NULL, 0444);
+MODULE_PARM_DESC(subsystem, "Override subsystem ID for Creative X-Fi driver");
+
+static DEFINE_PCI_DEVICE_TABLE(ct_pci_dev_ids) = {
+	/* only X-Fi is supported, so... */
+	{ PCI_DEVICE(PCI_VENDOR_ID_CREATIVE, PCI_DEVICE_ID_CREATIVE_20K1),
+	  .driver_data = ATC20K1,
+	},
+	{ PCI_DEVICE(PCI_VENDOR_ID_CREATIVE, PCI_DEVICE_ID_CREATIVE_20K2),
+	  .driver_data = ATC20K2,
+	},
+	{ 0, }
+};
+MODULE_DEVICE_TABLE(pci, ct_pci_dev_ids);
+
+static int __devinit
+ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct ct_atc *atc;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err)
+		return err;
+	if ((reference_rate != 48000) && (reference_rate != 44100)) {
+		printk(KERN_ERR "ctxfi: Invalid reference_rate value %u!!!\n",
+		       reference_rate);
+		printk(KERN_ERR "ctxfi: The valid values for reference_rate "
+		       "are 48000 and 44100, Value 48000 is assumed.\n");
+		reference_rate = 48000;
+	}
+	if ((multiple != 1) && (multiple != 2)) {
+		printk(KERN_ERR "ctxfi: Invalid multiple value %u!!!\n",
+		       multiple);
+		printk(KERN_ERR "ctxfi: The valid values for multiple are "
+		       "1 and 2, Value 2 is assumed.\n");
+		multiple = 2;
+	}
+	err = ct_atc_create(card, pci, reference_rate, multiple,
+			    pci_id->driver_data, subsystem[dev], &atc);
+	if (err < 0)
+		goto error;
+
+	card->private_data = atc;
+
+	/* Create alsa devices supported by this card */
+	err = ct_atc_create_alsa_devs(atc);
+	if (err < 0)
+		goto error;
+
+	strcpy(card->driver, "SB-XFi");
+	strcpy(card->shortname, "Creative X-Fi");
+	snprintf(card->longname, sizeof(card->longname), "%s %s %s",
+		 card->shortname, atc->chip_name, atc->model_name);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto error;
+
+	pci_set_drvdata(pci, card);
+	dev++;
+
+	return 0;
+
+error:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit ct_card_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+#ifdef CONFIG_PM
+static int ct_card_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct ct_atc *atc = card->private_data;
+
+	return atc->suspend(atc, state);
+}
+
+static int ct_card_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct ct_atc *atc = card->private_data;
+
+	return atc->resume(atc);
+}
+#endif
+
+static struct pci_driver ct_driver = {
+	.name = "SB-XFi",
+	.id_table = ct_pci_dev_ids,
+	.probe = ct_card_probe,
+	.remove = __devexit_p(ct_card_remove),
+#ifdef CONFIG_PM
+	.suspend = ct_card_suspend,
+	.resume = ct_card_resume,
+#endif
+};
+
+static int __init ct_card_init(void)
+{
+	return pci_register_driver(&ct_driver);
+}
+
+static void __exit ct_card_exit(void)
+{
+	pci_unregister_driver(&ct_driver);
+}
+
+module_init(ct_card_init)
+module_exit(ct_card_exit)
diff --git a/linux/sound/pci/echoaudio/Makefile b/linux/sound/pci/echoaudio/Makefile
new file mode 100644
index 0000000..1361de7
--- /dev/null
+++ b/linux/sound/pci/echoaudio/Makefile
@@ -0,0 +1,34 @@
+#
+# Makefile for ALSA Echoaudio soundcard drivers
+# Copyright (c) 2003 by Giuliano Pochini <pochini@shiny.it>
+#
+
+snd-darla20-objs := darla20.o
+snd-gina20-objs := gina20.o
+snd-layla20-objs := layla20.o
+snd-darla24-objs := darla24.o
+snd-gina24-objs := gina24.o
+snd-layla24-objs := layla24.o
+snd-mona-objs := mona.o
+snd-mia-objs := mia.o
+snd-echo3g-objs := echo3g.o
+snd-indigo-objs := indigo.o
+snd-indigoio-objs := indigoio.o
+snd-indigodj-objs := indigodj.o
+snd-indigoiox-objs := indigoiox.o
+snd-indigodjx-objs := indigodjx.o
+
+obj-$(CONFIG_SND_DARLA20) += snd-darla20.o
+obj-$(CONFIG_SND_GINA20) += snd-gina20.o
+obj-$(CONFIG_SND_LAYLA20) += snd-layla20.o
+obj-$(CONFIG_SND_DARLA24) += snd-darla24.o
+obj-$(CONFIG_SND_GINA24) += snd-gina24.o
+obj-$(CONFIG_SND_LAYLA24) += snd-layla24.o
+obj-$(CONFIG_SND_MONA) += snd-mona.o
+obj-$(CONFIG_SND_MIA) += snd-mia.o
+obj-$(CONFIG_SND_ECHO3G) += snd-echo3g.o
+obj-$(CONFIG_SND_INDIGO) += snd-indigo.o
+obj-$(CONFIG_SND_INDIGOIO) += snd-indigoio.o
+obj-$(CONFIG_SND_INDIGODJ) += snd-indigodj.o
+obj-$(CONFIG_SND_INDIGOIOX) += snd-indigoiox.o
+obj-$(CONFIG_SND_INDIGODJX) += snd-indigodjx.o
diff --git a/linux/sound/pci/echoaudio/darla20.c b/linux/sound/pci/echoaudio/darla20.c
new file mode 100644
index 0000000..fe7ad64
--- /dev/null
+++ b/linux/sound/pci/echoaudio/darla20.c
@@ -0,0 +1,101 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define ECHOGALS_FAMILY
+#define ECHOCARD_DARLA20
+#define ECHOCARD_NAME "Darla20"
+#define ECHOCARD_HAS_MONITOR
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 8 */
+#define PX_DIGITAL_OUT	8	/* 0 */
+#define PX_ANALOG_IN	8	/* 2 */
+#define PX_DIGITAL_IN	10	/* 0 */
+#define PX_NUM		10
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 8 */
+#define BX_DIGITAL_OUT	8	/* 0 */
+#define BX_ANALOG_IN	8	/* 2 */
+#define BX_DIGITAL_IN	10	/* 0 */
+#define BX_NUM		10
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/darla20_dsp.fw");
+
+#define FW_DARLA20_DSP	0
+
+static const struct firmware card_fw[] = {
+	{0, "darla20_dsp.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x1801, 0xECC0, 0x0010, 0, 0, 0},	/* DSP 56301 Darla20 rev.0 */
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+	.rate_min = 44100,
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+	/* One page (4k) contains 512 instructions. I don't know if the hw
+	supports lists longer than this. In this case periods_max=220 is a
+	safe limit to make sure the list never exceeds 512 instructions. */
+};
+
+
+#include "darla20_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio.c"
diff --git a/linux/sound/pci/echoaudio/darla20_dsp.c b/linux/sound/pci/echoaudio/darla20_dsp.c
new file mode 100644
index 0000000..20c7cbc
--- /dev/null
+++ b/linux/sound/pci/echoaudio/darla20_dsp.c
@@ -0,0 +1,130 @@
+/***************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Darla20\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != DARLA20))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->dsp_code_to_load = FW_DARLA20_DSP;
+	chip->spdif_status = GD_SPDIF_STATUS_UNDEF;
+	chip->clock_state = GD_CLOCK_UNDEF;
+	/* Since this card has no ASIC, mark it as loaded so everything
+	   works OK */
+	chip->asic_loaded = TRUE;
+	chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL;
+
+	if ((err = load_firmware(chip)) < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	return init_line_levels(chip);
+}
+
+
+
+/* The Darla20 has no external clock sources */
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	return ECHO_CLOCK_BIT_INTERNAL;
+}
+
+
+
+/* The Darla20 has no ASIC. Just do nothing */
+static int load_asic(struct echoaudio *chip)
+{
+	return 0;
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	u8 clock_state, spdif_status;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	switch (rate) {
+	case 44100:
+		clock_state = GD_CLOCK_44;
+		spdif_status = GD_SPDIF_STATUS_44;
+		break;
+	case 48000:
+		clock_state = GD_CLOCK_48;
+		spdif_status = GD_SPDIF_STATUS_48;
+		break;
+	default:
+		clock_state = GD_CLOCK_NOCHANGE;
+		spdif_status = GD_SPDIF_STATUS_NOCHANGE;
+		break;
+	}
+
+	if (chip->clock_state == clock_state)
+		clock_state = GD_CLOCK_NOCHANGE;
+	if (spdif_status == chip->spdif_status)
+		spdif_status = GD_SPDIF_STATUS_NOCHANGE;
+
+	chip->comm_page->sample_rate = cpu_to_le32(rate);
+	chip->comm_page->gd_clock_state = clock_state;
+	chip->comm_page->gd_spdif_status = spdif_status;
+	chip->comm_page->gd_resampler_state = 3;	/* magic number - should always be 3 */
+
+	/* Save the new audio state if it changed */
+	if (clock_state != GD_CLOCK_NOCHANGE)
+		chip->clock_state = clock_state;
+	if (spdif_status != GD_SPDIF_STATUS_NOCHANGE)
+		chip->spdif_status = spdif_status;
+	chip->sample_rate = rate;
+
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_SET_GD_AUDIO_STATE);
+}
diff --git a/linux/sound/pci/echoaudio/darla24.c b/linux/sound/pci/echoaudio/darla24.c
new file mode 100644
index 0000000..d1fd34b
--- /dev/null
+++ b/linux/sound/pci/echoaudio/darla24.c
@@ -0,0 +1,108 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define ECHOGALS_FAMILY
+#define ECHOCARD_DARLA24
+#define ECHOCARD_NAME "Darla24"
+#define ECHOCARD_HAS_MONITOR
+#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL
+#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL
+#define ECHOCARD_HAS_EXTERNAL_CLOCK
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 8 */
+#define PX_DIGITAL_OUT	8	/* 0 */
+#define PX_ANALOG_IN	8	/* 2 */
+#define PX_DIGITAL_IN	10	/* 0 */
+#define PX_NUM		10
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 8 */
+#define BX_DIGITAL_OUT	8	/* 0 */
+#define BX_ANALOG_IN	8	/* 2 */
+#define BX_DIGITAL_IN	10	/* 0 */
+#define BX_NUM		10
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/darla24_dsp.fw");
+
+#define FW_DARLA24_DSP	0
+
+static const struct firmware card_fw[] = {
+	{0, "darla24_dsp.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x1801, 0xECC0, 0x0040, 0, 0, 0},	/* DSP 56301 Darla24 rev.0 */
+	{0x1057, 0x1801, 0xECC0, 0x0041, 0, 0, 0},	/* DSP 56301 Darla24 rev.1 */
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates =	SNDRV_PCM_RATE_8000_48000 |
+			SNDRV_PCM_RATE_88200 |
+			SNDRV_PCM_RATE_96000,
+	.rate_min = 8000,
+	.rate_max = 96000,
+	.channels_min = 1,
+	.channels_max = 8,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+	/* One page (4k) contains 512 instructions. I don't know if the hw
+	supports lists longer than this. In this case periods_max=220 is a
+	safe limit to make sure the list never exceeds 512 instructions. */
+};
+
+
+#include "darla24_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio.c"
diff --git a/linux/sound/pci/echoaudio/darla24_dsp.c b/linux/sound/pci/echoaudio/darla24_dsp.c
new file mode 100644
index 0000000..6da6663
--- /dev/null
+++ b/linux/sound/pci/echoaudio/darla24_dsp.c
@@ -0,0 +1,162 @@
+/***************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Darla24\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != DARLA24))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->dsp_code_to_load = FW_DARLA24_DSP;
+	/* Since this card has no ASIC, mark it as loaded so everything
+	   works OK */
+	chip->asic_loaded = TRUE;
+	chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL |
+		ECHO_CLOCK_BIT_ESYNC;
+
+	if ((err = load_firmware(chip)) < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	return init_line_levels(chip);
+}
+
+
+
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	u32 clocks_from_dsp, clock_bits;
+
+	/* Map the DSP clock detect bits to the generic driver clock
+	   detect bits */
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	clock_bits = ECHO_CLOCK_BIT_INTERNAL;
+
+	if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_ESYNC)
+		clock_bits |= ECHO_CLOCK_BIT_ESYNC;
+
+	return clock_bits;
+}
+
+
+
+/* The Darla24 has no ASIC. Just do nothing */
+static int load_asic(struct echoaudio *chip)
+{
+	return 0;
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	u8 clock;
+
+	switch (rate) {
+	case 96000:
+		clock = GD24_96000;
+		break;
+	case 88200:
+		clock = GD24_88200;
+		break;
+	case 48000:
+		clock = GD24_48000;
+		break;
+	case 44100:
+		clock = GD24_44100;
+		break;
+	case 32000:
+		clock = GD24_32000;
+		break;
+	case 22050:
+		clock = GD24_22050;
+		break;
+	case 16000:
+		clock = GD24_16000;
+		break;
+	case 11025:
+		clock = GD24_11025;
+		break;
+	case 8000:
+		clock = GD24_8000;
+		break;
+	default:
+		DE_ACT(("set_sample_rate: Error, invalid sample rate %d\n",
+			rate));
+		return -EINVAL;
+	}
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	DE_ACT(("set_sample_rate: %d clock %d\n", rate, clock));
+	chip->sample_rate = rate;
+
+	/* Override the sample rate if this card is set to Echo sync. */
+	if (chip->input_clock == ECHO_CLOCK_ESYNC)
+		clock = GD24_EXT_SYNC;
+
+	chip->comm_page->sample_rate = cpu_to_le32(rate);	/* ignored by the DSP ? */
+	chip->comm_page->gd_clock_state = clock;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_SET_GD_AUDIO_STATE);
+}
+
+
+
+static int set_input_clock(struct echoaudio *chip, u16 clock)
+{
+	if (snd_BUG_ON(clock != ECHO_CLOCK_INTERNAL &&
+		       clock != ECHO_CLOCK_ESYNC))
+		return -EINVAL;
+	chip->input_clock = clock;
+	return set_sample_rate(chip, chip->sample_rate);
+}
+
diff --git a/linux/sound/pci/echoaudio/echo3g.c b/linux/sound/pci/echoaudio/echo3g.c
new file mode 100644
index 0000000..1dffdc5
--- /dev/null
+++ b/linux/sound/pci/echoaudio/echo3g.c
@@ -0,0 +1,122 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define ECHO3G_FAMILY
+#define ECHOCARD_ECHO3G
+#define ECHOCARD_NAME "Echo3G"
+#define ECHOCARD_HAS_MONITOR
+#define ECHOCARD_HAS_ASIC
+#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL
+#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+#define ECHOCARD_HAS_DIGITAL_IO
+#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH
+#define ECHOCARD_HAS_ADAT	6
+#define ECHOCARD_HAS_EXTERNAL_CLOCK
+#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+#define ECHOCARD_HAS_MIDI
+#define ECHOCARD_HAS_PHANTOM_POWER
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0
+#define PX_DIGITAL_OUT	chip->px_digital_out
+#define PX_ANALOG_IN	chip->px_analog_in
+#define PX_DIGITAL_IN	chip->px_digital_in
+#define PX_NUM		chip->px_num
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0
+#define BX_DIGITAL_OUT	chip->bx_digital_out
+#define BX_ANALOG_IN	chip->bx_analog_in
+#define BX_DIGITAL_IN	chip->bx_digital_in
+#define BX_NUM		chip->bx_num
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/loader_dsp.fw");
+MODULE_FIRMWARE("ea/echo3g_dsp.fw");
+MODULE_FIRMWARE("ea/3g_asic.fw");
+
+#define FW_361_LOADER	0
+#define FW_ECHO3G_DSP	1
+#define FW_3G_ASIC	2
+
+static const struct firmware card_fw[] = {
+	{0, "loader_dsp.fw"},
+	{0, "echo3g_dsp.fw"},
+	{0, "3g_asic.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x3410, 0xECC0, 0x0100, 0, 0, 0},	/* Echo 3G */
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = 	SNDRV_PCM_RATE_32000 |
+			SNDRV_PCM_RATE_44100 |
+			SNDRV_PCM_RATE_48000 |
+			SNDRV_PCM_RATE_88200 |
+			SNDRV_PCM_RATE_96000 |
+			SNDRV_PCM_RATE_CONTINUOUS,
+	.rate_min = 32000,
+	.rate_max = 100000,
+	.channels_min = 1,
+	.channels_max = 8,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+};
+
+#include "echo3g_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio_3g.c"
+#include "echoaudio.c"
+#include "midi.c"
diff --git a/linux/sound/pci/echoaudio/echo3g_dsp.c b/linux/sound/pci/echoaudio/echo3g_dsp.c
new file mode 100644
index 0000000..3cdc2ee
--- /dev/null
+++ b/linux/sound/pci/echoaudio/echo3g_dsp.c
@@ -0,0 +1,132 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+static int load_asic(struct echoaudio *chip);
+static int dsp_set_digital_mode(struct echoaudio *chip, u8 mode);
+static int set_digital_mode(struct echoaudio *chip, u8 mode);
+static int check_asic_status(struct echoaudio *chip);
+static int set_sample_rate(struct echoaudio *chip, u32 rate);
+static int set_input_clock(struct echoaudio *chip, u16 clock);
+static int set_professional_spdif(struct echoaudio *chip, char prof);
+static int set_phantom_power(struct echoaudio *chip, char on);
+static int write_control_reg(struct echoaudio *chip, u32 ctl, u32 frq,
+			     char force);
+
+#include <linux/interrupt.h>
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	local_irq_enable();
+	DE_INIT(("init_hw() - Echo3G\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != ECHO3G))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->comm_page->e3g_frq_register =
+		cpu_to_le32((E3G_MAGIC_NUMBER / 48000) - 2);
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->has_midi = TRUE;
+	chip->dsp_code_to_load = FW_ECHO3G_DSP;
+
+	/* Load the DSP code and the ASIC on the PCI card and get
+	what type of external box is attached */
+	err = load_firmware(chip);
+
+	if (err < 0) {
+		return err;
+	} else if (err == E3G_GINA3G_BOX_TYPE) {
+		chip->input_clock_types =	ECHO_CLOCK_BIT_INTERNAL |
+						ECHO_CLOCK_BIT_SPDIF |
+						ECHO_CLOCK_BIT_ADAT;
+		chip->card_name = "Gina3G";
+		chip->px_digital_out = chip->bx_digital_out = 6;
+		chip->px_analog_in = chip->bx_analog_in = 14;
+		chip->px_digital_in = chip->bx_digital_in = 16;
+		chip->px_num = chip->bx_num = 24;
+		chip->has_phantom_power = TRUE;
+		chip->hasnt_input_nominal_level = TRUE;
+	} else if (err == E3G_LAYLA3G_BOX_TYPE) {
+		chip->input_clock_types =	ECHO_CLOCK_BIT_INTERNAL |
+						ECHO_CLOCK_BIT_SPDIF |
+						ECHO_CLOCK_BIT_ADAT |
+						ECHO_CLOCK_BIT_WORD;
+		chip->card_name = "Layla3G";
+		chip->px_digital_out = chip->bx_digital_out = 8;
+		chip->px_analog_in = chip->bx_analog_in = 16;
+		chip->px_digital_in = chip->bx_digital_in = 24;
+		chip->px_num = chip->bx_num = 32;
+	} else {
+		return -ENODEV;
+	}
+
+	chip->digital_modes =	ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA |
+				ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL |
+				ECHOCAPS_HAS_DIGITAL_MODE_ADAT;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	chip->digital_mode = DIGITAL_MODE_SPDIF_RCA;
+	chip->professional_spdif = FALSE;
+	chip->non_audio_spdif = FALSE;
+	chip->bad_board = FALSE;
+	chip->phantom_power = FALSE;
+	return init_line_levels(chip);
+}
+
+
+
+static int set_phantom_power(struct echoaudio *chip, char on)
+{
+	u32 control_reg = le32_to_cpu(chip->comm_page->control_register);
+
+	if (on)
+		control_reg |= E3G_PHANTOM_POWER;
+	else
+		control_reg &= ~E3G_PHANTOM_POWER;
+
+	chip->phantom_power = on;
+	return write_control_reg(chip, control_reg,
+				 le32_to_cpu(chip->comm_page->e3g_frq_register),
+				 0);
+}
diff --git a/linux/sound/pci/echoaudio/echoaudio.c b/linux/sound/pci/echoaudio/echoaudio.c
new file mode 100644
index 0000000..20763dd
--- /dev/null
+++ b/linux/sound/pci/echoaudio/echoaudio.c
@@ -0,0 +1,2358 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  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.
+ */
+
+MODULE_AUTHOR("Giuliano Pochini <pochini@shiny.it>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Echoaudio " ECHOCARD_NAME " soundcards driver");
+MODULE_SUPPORTED_DEVICE("{{Echoaudio," ECHOCARD_NAME "}}");
+MODULE_DEVICE_TABLE(pci, snd_echo_ids);
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " ECHOCARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " ECHOCARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " ECHOCARD_NAME " soundcard.");
+
+static unsigned int channels_list[10] = {1, 2, 4, 6, 8, 10, 12, 14, 16, 999999};
+static const DECLARE_TLV_DB_SCALE(db_scale_output_gain, -12800, 100, 1);
+
+
+
+static int get_firmware(const struct firmware **fw_entry,
+			struct echoaudio *chip, const short fw_index)
+{
+	int err;
+	char name[30];
+
+#ifdef CONFIG_PM
+	if (chip->fw_cache[fw_index]) {
+		DE_ACT(("firmware requested: %s is cached\n", card_fw[fw_index].data));
+		*fw_entry = chip->fw_cache[fw_index];
+		return 0;
+	}
+#endif
+
+	DE_ACT(("firmware requested: %s\n", card_fw[fw_index].data));
+	snprintf(name, sizeof(name), "ea/%s", card_fw[fw_index].data);
+	err = request_firmware(fw_entry, name, pci_device(chip));
+	if (err < 0)
+		snd_printk(KERN_ERR "get_firmware(): Firmware not available (%d)\n", err);
+#ifdef CONFIG_PM
+	else
+		chip->fw_cache[fw_index] = *fw_entry;
+#endif
+	return err;
+}
+
+
+
+static void free_firmware(const struct firmware *fw_entry)
+{
+#ifdef CONFIG_PM
+	DE_ACT(("firmware not released (kept in cache)\n"));
+#else
+	release_firmware(fw_entry);
+	DE_ACT(("firmware released\n"));
+#endif
+}
+
+
+
+static void free_firmware_cache(struct echoaudio *chip)
+{
+#ifdef CONFIG_PM
+	int i;
+
+	for (i = 0; i < 8 ; i++)
+		if (chip->fw_cache[i]) {
+			release_firmware(chip->fw_cache[i]);
+			DE_ACT(("release_firmware(%d)\n", i));
+		}
+
+	DE_ACT(("firmware_cache released\n"));
+#endif
+}
+
+
+
+/******************************************************************************
+	PCM interface
+******************************************************************************/
+
+static void audiopipe_free(struct snd_pcm_runtime *runtime)
+{
+	struct audiopipe *pipe = runtime->private_data;
+
+	if (pipe->sgpage.area)
+		snd_dma_free_pages(&pipe->sgpage);
+	kfree(pipe);
+}
+
+
+
+static int hw_rule_capture_format_by_channels(struct snd_pcm_hw_params *params,
+					      struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *c = hw_param_interval(params,
+						   SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	struct snd_mask fmt;
+
+	snd_mask_any(&fmt);
+
+#ifndef ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+	/* >=2 channels cannot be S32_BE */
+	if (c->min == 2) {
+		fmt.bits[0] &= ~SNDRV_PCM_FMTBIT_S32_BE;
+		return snd_mask_refine(f, &fmt);
+	}
+#endif
+	/* > 2 channels cannot be U8 and S32_BE */
+	if (c->min > 2) {
+		fmt.bits[0] &= ~(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_BE);
+		return snd_mask_refine(f, &fmt);
+	}
+	/* Mono is ok with any format */
+	return 0;
+}
+
+
+
+static int hw_rule_capture_channels_by_format(struct snd_pcm_hw_params *params,
+					      struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *c = hw_param_interval(params,
+						   SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	struct snd_interval ch;
+
+	snd_interval_any(&ch);
+
+	/* S32_BE is mono (and stereo) only */
+	if (f->bits[0] == SNDRV_PCM_FMTBIT_S32_BE) {
+		ch.min = 1;
+#ifdef ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+		ch.max = 2;
+#else
+		ch.max = 1;
+#endif
+		ch.integer = 1;
+		return snd_interval_refine(c, &ch);
+	}
+	/* U8 can be only mono or stereo */
+	if (f->bits[0] == SNDRV_PCM_FMTBIT_U8) {
+		ch.min = 1;
+		ch.max = 2;
+		ch.integer = 1;
+		return snd_interval_refine(c, &ch);
+	}
+	/* S16_LE, S24_3LE and S32_LE support any number of channels. */
+	return 0;
+}
+
+
+
+static int hw_rule_playback_format_by_channels(struct snd_pcm_hw_params *params,
+					       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *c = hw_param_interval(params,
+						   SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	struct snd_mask fmt;
+	u64 fmask;
+	snd_mask_any(&fmt);
+
+	fmask = fmt.bits[0] + ((u64)fmt.bits[1] << 32);
+
+	/* >2 channels must be S16_LE, S24_3LE or S32_LE */
+	if (c->min > 2) {
+		fmask &= SNDRV_PCM_FMTBIT_S16_LE |
+			 SNDRV_PCM_FMTBIT_S24_3LE |
+			 SNDRV_PCM_FMTBIT_S32_LE;
+	/* 1 channel must be S32_BE or S32_LE */
+	} else if (c->max == 1)
+		fmask &= SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE;
+#ifndef ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+	/* 2 channels cannot be S32_BE */
+	else if (c->min == 2 && c->max == 2)
+		fmask &= ~SNDRV_PCM_FMTBIT_S32_BE;
+#endif
+	else
+		return 0;
+
+	fmt.bits[0] &= (u32)fmask;
+	fmt.bits[1] &= (u32)(fmask >> 32);
+	return snd_mask_refine(f, &fmt);
+}
+
+
+
+static int hw_rule_playback_channels_by_format(struct snd_pcm_hw_params *params,
+					       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *c = hw_param_interval(params,
+						   SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	struct snd_interval ch;
+	u64 fmask;
+
+	snd_interval_any(&ch);
+	ch.integer = 1;
+	fmask = f->bits[0] + ((u64)f->bits[1] << 32);
+
+	/* S32_BE is mono (and stereo) only */
+	if (fmask == SNDRV_PCM_FMTBIT_S32_BE) {
+		ch.min = 1;
+#ifdef ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+		ch.max = 2;
+#else
+		ch.max = 1;
+#endif
+	/* U8 is stereo only */
+	} else if (fmask == SNDRV_PCM_FMTBIT_U8)
+		ch.min = ch.max = 2;
+	/* S16_LE and S24_3LE must be at least stereo */
+	else if (!(fmask & ~(SNDRV_PCM_FMTBIT_S16_LE |
+			       SNDRV_PCM_FMTBIT_S24_3LE)))
+		ch.min = 2;
+	else
+		return 0;
+
+	return snd_interval_refine(c, &ch);
+}
+
+
+
+/* Since the sample rate is a global setting, do allow the user to change the
+sample rate only if there is only one pcm device open. */
+static int hw_rule_sample_rate(struct snd_pcm_hw_params *params,
+			       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *rate = hw_param_interval(params,
+						      SNDRV_PCM_HW_PARAM_RATE);
+	struct echoaudio *chip = rule->private;
+	struct snd_interval fixed;
+
+	if (!chip->can_set_rate) {
+		snd_interval_any(&fixed);
+		fixed.min = fixed.max = chip->sample_rate;
+		return snd_interval_refine(rate, &fixed);
+	}
+	return 0;
+}
+
+
+static int pcm_open(struct snd_pcm_substream *substream,
+		    signed char max_channels)
+{
+	struct echoaudio *chip;
+	struct snd_pcm_runtime *runtime;
+	struct audiopipe *pipe;
+	int err, i;
+
+	if (max_channels <= 0)
+		return -EAGAIN;
+
+	chip = snd_pcm_substream_chip(substream);
+	runtime = substream->runtime;
+
+	pipe = kzalloc(sizeof(struct audiopipe), GFP_KERNEL);
+	if (!pipe)
+		return -ENOMEM;
+	pipe->index = -1;		/* Not configured yet */
+
+	/* Set up hw capabilities and contraints */
+	memcpy(&pipe->hw, &pcm_hardware_skel, sizeof(struct snd_pcm_hardware));
+	DE_HWP(("max_channels=%d\n", max_channels));
+	pipe->constr.list = channels_list;
+	pipe->constr.mask = 0;
+	for (i = 0; channels_list[i] <= max_channels; i++);
+	pipe->constr.count = i;
+	if (pipe->hw.channels_max > max_channels)
+		pipe->hw.channels_max = max_channels;
+	if (chip->digital_mode == DIGITAL_MODE_ADAT) {
+		pipe->hw.rate_max = 48000;
+		pipe->hw.rates &= SNDRV_PCM_RATE_8000_48000;
+	}
+
+	runtime->hw = pipe->hw;
+	runtime->private_data = pipe;
+	runtime->private_free = audiopipe_free;
+	snd_pcm_set_sync(substream);
+
+	/* Only mono and any even number of channels are allowed */
+	if ((err = snd_pcm_hw_constraint_list(runtime, 0,
+					      SNDRV_PCM_HW_PARAM_CHANNELS,
+					      &pipe->constr)) < 0)
+		return err;
+
+	/* All periods should have the same size */
+	if ((err = snd_pcm_hw_constraint_integer(runtime,
+						 SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+
+	/* The hw accesses memory in chunks 32 frames long and they should be
+	32-bytes-aligned. It's not a requirement, but it seems that IRQs are
+	generated with a resolution of 32 frames. Thus we need the following */
+	if ((err = snd_pcm_hw_constraint_step(runtime, 0,
+					      SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+					      32)) < 0)
+		return err;
+	if ((err = snd_pcm_hw_constraint_step(runtime, 0,
+					      SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+					      32)) < 0)
+		return err;
+
+	if ((err = snd_pcm_hw_rule_add(substream->runtime, 0,
+				       SNDRV_PCM_HW_PARAM_RATE,
+					hw_rule_sample_rate, chip,
+				       SNDRV_PCM_HW_PARAM_RATE, -1)) < 0)
+		return err;
+
+	/* Finally allocate a page for the scatter-gather list */
+	if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+				       snd_dma_pci_data(chip->pci),
+				       PAGE_SIZE, &pipe->sgpage)) < 0) {
+		DE_HWP(("s-g list allocation failed\n"));
+		return err;
+	}
+
+	return 0;
+}
+
+
+
+static int pcm_analog_in_open(struct snd_pcm_substream *substream)
+{
+	struct echoaudio *chip = snd_pcm_substream_chip(substream);
+	int err;
+
+	DE_ACT(("pcm_analog_in_open\n"));
+	if ((err = pcm_open(substream, num_analog_busses_in(chip) -
+			    substream->number)) < 0)
+		return err;
+	if ((err = snd_pcm_hw_rule_add(substream->runtime, 0,
+				       SNDRV_PCM_HW_PARAM_CHANNELS,
+				       hw_rule_capture_channels_by_format, NULL,
+				       SNDRV_PCM_HW_PARAM_FORMAT, -1)) < 0)
+		return err;
+	if ((err = snd_pcm_hw_rule_add(substream->runtime, 0,
+				       SNDRV_PCM_HW_PARAM_FORMAT,
+				       hw_rule_capture_format_by_channels, NULL,
+				       SNDRV_PCM_HW_PARAM_CHANNELS, -1)) < 0)
+		return err;
+	atomic_inc(&chip->opencount);
+	if (atomic_read(&chip->opencount) > 1 && chip->rate_set)
+		chip->can_set_rate=0;
+	DE_HWP(("pcm_analog_in_open  cs=%d  oc=%d  r=%d\n",
+		chip->can_set_rate, atomic_read(&chip->opencount),
+		chip->sample_rate));
+	return 0;
+}
+
+
+
+static int pcm_analog_out_open(struct snd_pcm_substream *substream)
+{
+	struct echoaudio *chip = snd_pcm_substream_chip(substream);
+	int max_channels, err;
+
+#ifdef ECHOCARD_HAS_VMIXER
+	max_channels = num_pipes_out(chip);
+#else
+	max_channels = num_analog_busses_out(chip);
+#endif
+	DE_ACT(("pcm_analog_out_open\n"));
+	if ((err = pcm_open(substream, max_channels - substream->number)) < 0)
+		return err;
+	if ((err = snd_pcm_hw_rule_add(substream->runtime, 0,
+				       SNDRV_PCM_HW_PARAM_CHANNELS,
+				       hw_rule_playback_channels_by_format,
+				       NULL,
+				       SNDRV_PCM_HW_PARAM_FORMAT, -1)) < 0)
+		return err;
+	if ((err = snd_pcm_hw_rule_add(substream->runtime, 0,
+				       SNDRV_PCM_HW_PARAM_FORMAT,
+				       hw_rule_playback_format_by_channels,
+				       NULL,
+				       SNDRV_PCM_HW_PARAM_CHANNELS, -1)) < 0)
+		return err;
+	atomic_inc(&chip->opencount);
+	if (atomic_read(&chip->opencount) > 1 && chip->rate_set)
+		chip->can_set_rate=0;
+	DE_HWP(("pcm_analog_out_open  cs=%d  oc=%d  r=%d\n",
+		chip->can_set_rate, atomic_read(&chip->opencount),
+		chip->sample_rate));
+	return 0;
+}
+
+
+
+#ifdef ECHOCARD_HAS_DIGITAL_IO
+
+static int pcm_digital_in_open(struct snd_pcm_substream *substream)
+{
+	struct echoaudio *chip = snd_pcm_substream_chip(substream);
+	int err, max_channels;
+
+	DE_ACT(("pcm_digital_in_open\n"));
+	max_channels = num_digital_busses_in(chip) - substream->number;
+	mutex_lock(&chip->mode_mutex);
+	if (chip->digital_mode == DIGITAL_MODE_ADAT)
+		err = pcm_open(substream, max_channels);
+	else	/* If the card has ADAT, subtract the 6 channels
+		 * that S/PDIF doesn't have
+		 */
+		err = pcm_open(substream, max_channels - ECHOCARD_HAS_ADAT);
+
+	if (err < 0)
+		goto din_exit;
+
+	if ((err = snd_pcm_hw_rule_add(substream->runtime, 0,
+				       SNDRV_PCM_HW_PARAM_CHANNELS,
+				       hw_rule_capture_channels_by_format, NULL,
+				       SNDRV_PCM_HW_PARAM_FORMAT, -1)) < 0)
+		goto din_exit;
+	if ((err = snd_pcm_hw_rule_add(substream->runtime, 0,
+				       SNDRV_PCM_HW_PARAM_FORMAT,
+				       hw_rule_capture_format_by_channels, NULL,
+				       SNDRV_PCM_HW_PARAM_CHANNELS, -1)) < 0)
+		goto din_exit;
+
+	atomic_inc(&chip->opencount);
+	if (atomic_read(&chip->opencount) > 1 && chip->rate_set)
+		chip->can_set_rate=0;
+
+din_exit:
+	mutex_unlock(&chip->mode_mutex);
+	return err;
+}
+
+
+
+#ifndef ECHOCARD_HAS_VMIXER	/* See the note in snd_echo_new_pcm() */
+
+static int pcm_digital_out_open(struct snd_pcm_substream *substream)
+{
+	struct echoaudio *chip = snd_pcm_substream_chip(substream);
+	int err, max_channels;
+
+	DE_ACT(("pcm_digital_out_open\n"));
+	max_channels = num_digital_busses_out(chip) - substream->number;
+	mutex_lock(&chip->mode_mutex);
+	if (chip->digital_mode == DIGITAL_MODE_ADAT)
+		err = pcm_open(substream, max_channels);
+	else	/* If the card has ADAT, subtract the 6 channels
+		 * that S/PDIF doesn't have
+		 */
+		err = pcm_open(substream, max_channels - ECHOCARD_HAS_ADAT);
+
+	if (err < 0)
+		goto dout_exit;
+
+	if ((err = snd_pcm_hw_rule_add(substream->runtime, 0,
+				       SNDRV_PCM_HW_PARAM_CHANNELS,
+				       hw_rule_playback_channels_by_format,
+				       NULL, SNDRV_PCM_HW_PARAM_FORMAT,
+				       -1)) < 0)
+		goto dout_exit;
+	if ((err = snd_pcm_hw_rule_add(substream->runtime, 0,
+				       SNDRV_PCM_HW_PARAM_FORMAT,
+				       hw_rule_playback_format_by_channels,
+				       NULL, SNDRV_PCM_HW_PARAM_CHANNELS,
+				       -1)) < 0)
+		goto dout_exit;
+	atomic_inc(&chip->opencount);
+	if (atomic_read(&chip->opencount) > 1 && chip->rate_set)
+		chip->can_set_rate=0;
+dout_exit:
+	mutex_unlock(&chip->mode_mutex);
+	return err;
+}
+
+#endif /* !ECHOCARD_HAS_VMIXER */
+
+#endif /* ECHOCARD_HAS_DIGITAL_IO */
+
+
+
+static int pcm_close(struct snd_pcm_substream *substream)
+{
+	struct echoaudio *chip = snd_pcm_substream_chip(substream);
+	int oc;
+
+	/* Nothing to do here. Audio is already off and pipe will be
+	 * freed by its callback
+	 */
+	DE_ACT(("pcm_close\n"));
+
+	atomic_dec(&chip->opencount);
+	oc = atomic_read(&chip->opencount);
+	DE_ACT(("pcm_close  oc=%d  cs=%d  rs=%d\n", oc,
+		chip->can_set_rate, chip->rate_set));
+	if (oc < 2)
+		chip->can_set_rate = 1;
+	if (oc == 0)
+		chip->rate_set = 0;
+	DE_ACT(("pcm_close2 oc=%d  cs=%d  rs=%d\n", oc,
+		chip->can_set_rate,chip->rate_set));
+
+	return 0;
+}
+
+
+
+/* Channel allocation and scatter-gather list setup */
+static int init_engine(struct snd_pcm_substream *substream,
+		       struct snd_pcm_hw_params *hw_params,
+		       int pipe_index, int interleave)
+{
+	struct echoaudio *chip;
+	int err, per, rest, page, edge, offs;
+	struct audiopipe *pipe;
+
+	chip = snd_pcm_substream_chip(substream);
+	pipe = (struct audiopipe *) substream->runtime->private_data;
+
+	/* Sets up che hardware. If it's already initialized, reset and
+	 * redo with the new parameters
+	 */
+	spin_lock_irq(&chip->lock);
+	if (pipe->index >= 0) {
+		DE_HWP(("hwp_ie free(%d)\n", pipe->index));
+		err = free_pipes(chip, pipe);
+		snd_BUG_ON(err);
+		chip->substream[pipe->index] = NULL;
+	}
+
+	err = allocate_pipes(chip, pipe, pipe_index, interleave);
+	if (err < 0) {
+		spin_unlock_irq(&chip->lock);
+		DE_ACT((KERN_NOTICE "allocate_pipes(%d) err=%d\n",
+			pipe_index, err));
+		return err;
+	}
+	spin_unlock_irq(&chip->lock);
+	DE_ACT((KERN_NOTICE "allocate_pipes()=%d\n", pipe_index));
+
+	DE_HWP(("pcm_hw_params (bufsize=%dB periods=%d persize=%dB)\n",
+		params_buffer_bytes(hw_params), params_periods(hw_params),
+		params_period_bytes(hw_params)));
+	err = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+	if (err < 0) {
+		snd_printk(KERN_ERR "malloc_pages err=%d\n", err);
+		spin_lock_irq(&chip->lock);
+		free_pipes(chip, pipe);
+		spin_unlock_irq(&chip->lock);
+		pipe->index = -1;
+		return err;
+	}
+
+	sglist_init(chip, pipe);
+	edge = PAGE_SIZE;
+	for (offs = page = per = 0; offs < params_buffer_bytes(hw_params);
+	     per++) {
+		rest = params_period_bytes(hw_params);
+		if (offs + rest > params_buffer_bytes(hw_params))
+			rest = params_buffer_bytes(hw_params) - offs;
+		while (rest) {
+			dma_addr_t addr;
+			addr = snd_pcm_sgbuf_get_addr(substream, offs);
+			if (rest <= edge - offs) {
+				sglist_add_mapping(chip, pipe, addr, rest);
+				sglist_add_irq(chip, pipe);
+				offs += rest;
+				rest = 0;
+			} else {
+				sglist_add_mapping(chip, pipe, addr,
+						   edge - offs);
+				rest -= edge - offs;
+				offs = edge;
+			}
+			if (offs == edge) {
+				edge += PAGE_SIZE;
+				page++;
+			}
+		}
+	}
+
+	/* Close the ring buffer */
+	sglist_wrap(chip, pipe);
+
+	/* This stuff is used by the irq handler, so it must be
+	 * initialized before chip->substream
+	 */
+	chip->last_period[pipe_index] = 0;
+	pipe->last_counter = 0;
+	pipe->position = 0;
+	smp_wmb();
+	chip->substream[pipe_index] = substream;
+	chip->rate_set = 1;
+	spin_lock_irq(&chip->lock);
+	set_sample_rate(chip, hw_params->rate_num / hw_params->rate_den);
+	spin_unlock_irq(&chip->lock);
+	DE_HWP(("pcm_hw_params ok\n"));
+	return 0;
+}
+
+
+
+static int pcm_analog_in_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *hw_params)
+{
+	struct echoaudio *chip = snd_pcm_substream_chip(substream);
+
+	return init_engine(substream, hw_params, px_analog_in(chip) +
+			substream->number, params_channels(hw_params));
+}
+
+
+
+static int pcm_analog_out_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *hw_params)
+{
+	return init_engine(substream, hw_params, substream->number,
+			   params_channels(hw_params));
+}
+
+
+
+#ifdef ECHOCARD_HAS_DIGITAL_IO
+
+static int pcm_digital_in_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *hw_params)
+{
+	struct echoaudio *chip = snd_pcm_substream_chip(substream);
+
+	return init_engine(substream, hw_params, px_digital_in(chip) +
+			substream->number, params_channels(hw_params));
+}
+
+
+
+#ifndef ECHOCARD_HAS_VMIXER	/* See the note in snd_echo_new_pcm() */
+static int pcm_digital_out_hw_params(struct snd_pcm_substream *substream,
+				     struct snd_pcm_hw_params *hw_params)
+{
+	struct echoaudio *chip = snd_pcm_substream_chip(substream);
+
+	return init_engine(substream, hw_params, px_digital_out(chip) +
+			substream->number, params_channels(hw_params));
+}
+#endif /* !ECHOCARD_HAS_VMIXER */
+
+#endif /* ECHOCARD_HAS_DIGITAL_IO */
+
+
+
+static int pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct echoaudio *chip;
+	struct audiopipe *pipe;
+
+	chip = snd_pcm_substream_chip(substream);
+	pipe = (struct audiopipe *) substream->runtime->private_data;
+
+	spin_lock_irq(&chip->lock);
+	if (pipe->index >= 0) {
+		DE_HWP(("pcm_hw_free(%d)\n", pipe->index));
+		free_pipes(chip, pipe);
+		chip->substream[pipe->index] = NULL;
+		pipe->index = -1;
+	}
+	spin_unlock_irq(&chip->lock);
+
+	DE_HWP(("pcm_hw_freed\n"));
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+
+
+static int pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct echoaudio *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audioformat format;
+	int pipe_index = ((struct audiopipe *)runtime->private_data)->index;
+
+	DE_HWP(("Prepare rate=%d format=%d channels=%d\n",
+		runtime->rate, runtime->format, runtime->channels));
+	format.interleave = runtime->channels;
+	format.data_are_bigendian = 0;
+	format.mono_to_stereo = 0;
+	switch (runtime->format) {
+	case SNDRV_PCM_FORMAT_U8:
+		format.bits_per_sample = 8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		format.bits_per_sample = 16;
+		break;
+	case SNDRV_PCM_FORMAT_S24_3LE:
+		format.bits_per_sample = 24;
+		break;
+	case SNDRV_PCM_FORMAT_S32_BE:
+		format.data_are_bigendian = 1;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		format.bits_per_sample = 32;
+		break;
+	default:
+		DE_HWP(("Prepare error: unsupported format %d\n",
+			runtime->format));
+		return -EINVAL;
+	}
+
+	if (snd_BUG_ON(pipe_index >= px_num(chip)))
+		return -EINVAL;
+	if (snd_BUG_ON(!is_pipe_allocated(chip, pipe_index)))
+		return -EINVAL;
+	set_audio_format(chip, pipe_index, &format);
+	return 0;
+}
+
+
+
+static int pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct echoaudio *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audiopipe *pipe = runtime->private_data;
+	int i, err;
+	u32 channelmask = 0;
+	struct snd_pcm_substream *s;
+
+	snd_pcm_group_for_each_entry(s, substream) {
+		for (i = 0; i < DSP_MAXPIPES; i++) {
+			if (s == chip->substream[i]) {
+				channelmask |= 1 << i;
+				snd_pcm_trigger_done(s, substream);
+			}
+		}
+	}
+
+	spin_lock(&chip->lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_RESUME:
+		DE_ACT(("pcm_trigger resume\n"));
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		DE_ACT(("pcm_trigger start\n"));
+		for (i = 0; i < DSP_MAXPIPES; i++) {
+			if (channelmask & (1 << i)) {
+				pipe = chip->substream[i]->runtime->private_data;
+				switch (pipe->state) {
+				case PIPE_STATE_STOPPED:
+					chip->last_period[i] = 0;
+					pipe->last_counter = 0;
+					pipe->position = 0;
+					*pipe->dma_counter = 0;
+				case PIPE_STATE_PAUSED:
+					pipe->state = PIPE_STATE_STARTED;
+					break;
+				case PIPE_STATE_STARTED:
+					break;
+				}
+			}
+		}
+		err = start_transport(chip, channelmask,
+				      chip->pipe_cyclic_mask);
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		DE_ACT(("pcm_trigger suspend\n"));
+	case SNDRV_PCM_TRIGGER_STOP:
+		DE_ACT(("pcm_trigger stop\n"));
+		for (i = 0; i < DSP_MAXPIPES; i++) {
+			if (channelmask & (1 << i)) {
+				pipe = chip->substream[i]->runtime->private_data;
+				pipe->state = PIPE_STATE_STOPPED;
+			}
+		}
+		err = stop_transport(chip, channelmask);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		DE_ACT(("pcm_trigger pause\n"));
+		for (i = 0; i < DSP_MAXPIPES; i++) {
+			if (channelmask & (1 << i)) {
+				pipe = chip->substream[i]->runtime->private_data;
+				pipe->state = PIPE_STATE_PAUSED;
+			}
+		}
+		err = pause_transport(chip, channelmask);
+		break;
+	default:
+		err = -EINVAL;
+	}
+	spin_unlock(&chip->lock);
+	return err;
+}
+
+
+
+static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audiopipe *pipe = runtime->private_data;
+	size_t cnt, bufsize, pos;
+
+	cnt = le32_to_cpu(*pipe->dma_counter);
+	pipe->position += cnt - pipe->last_counter;
+	pipe->last_counter = cnt;
+	bufsize = substream->runtime->buffer_size;
+	pos = bytes_to_frames(substream->runtime, pipe->position);
+
+	while (pos >= bufsize) {
+		pipe->position -= frames_to_bytes(substream->runtime, bufsize);
+		pos -= bufsize;
+	}
+	return pos;
+}
+
+
+
+/* pcm *_ops structures */
+static struct snd_pcm_ops analog_playback_ops = {
+	.open = pcm_analog_out_open,
+	.close = pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = pcm_analog_out_hw_params,
+	.hw_free = pcm_hw_free,
+	.prepare = pcm_prepare,
+	.trigger = pcm_trigger,
+	.pointer = pcm_pointer,
+	.page = snd_pcm_sgbuf_ops_page,
+};
+static struct snd_pcm_ops analog_capture_ops = {
+	.open = pcm_analog_in_open,
+	.close = pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = pcm_analog_in_hw_params,
+	.hw_free = pcm_hw_free,
+	.prepare = pcm_prepare,
+	.trigger = pcm_trigger,
+	.pointer = pcm_pointer,
+	.page = snd_pcm_sgbuf_ops_page,
+};
+#ifdef ECHOCARD_HAS_DIGITAL_IO
+#ifndef ECHOCARD_HAS_VMIXER
+static struct snd_pcm_ops digital_playback_ops = {
+	.open = pcm_digital_out_open,
+	.close = pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = pcm_digital_out_hw_params,
+	.hw_free = pcm_hw_free,
+	.prepare = pcm_prepare,
+	.trigger = pcm_trigger,
+	.pointer = pcm_pointer,
+	.page = snd_pcm_sgbuf_ops_page,
+};
+#endif /* !ECHOCARD_HAS_VMIXER */
+static struct snd_pcm_ops digital_capture_ops = {
+	.open = pcm_digital_in_open,
+	.close = pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = pcm_digital_in_hw_params,
+	.hw_free = pcm_hw_free,
+	.prepare = pcm_prepare,
+	.trigger = pcm_trigger,
+	.pointer = pcm_pointer,
+	.page = snd_pcm_sgbuf_ops_page,
+};
+#endif /* ECHOCARD_HAS_DIGITAL_IO */
+
+
+
+/* Preallocate memory only for the first substream because it's the most
+ * used one
+ */
+static int snd_echo_preallocate_pages(struct snd_pcm *pcm, struct device *dev)
+{
+	struct snd_pcm_substream *ss;
+	int stream, err;
+
+	for (stream = 0; stream < 2; stream++)
+		for (ss = pcm->streams[stream].substream; ss; ss = ss->next) {
+			err = snd_pcm_lib_preallocate_pages(ss, SNDRV_DMA_TYPE_DEV_SG,
+							    dev,
+							    ss->number ? 0 : 128<<10,
+							    256<<10);
+			if (err < 0)
+				return err;
+		}
+	return 0;
+}
+
+
+
+/*<--snd_echo_probe() */
+static int __devinit snd_echo_new_pcm(struct echoaudio *chip)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+#ifdef ECHOCARD_HAS_VMIXER
+	/* This card has a Vmixer, that is there is no direct mapping from PCM
+	streams to physical outputs. The user can mix the streams as he wishes
+	via control interface and it's possible to send any stream to any
+	output, thus it makes no sense to keep analog and digital outputs
+	separated */
+
+	/* PCM#0 Virtual outputs and analog inputs */
+	if ((err = snd_pcm_new(chip->card, "PCM", 0, num_pipes_out(chip),
+				num_analog_busses_in(chip), &pcm)) < 0)
+		return err;
+	pcm->private_data = chip;
+	chip->analog_pcm = pcm;
+	strcpy(pcm->name, chip->card->shortname);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &analog_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &analog_capture_ops);
+	if ((err = snd_echo_preallocate_pages(pcm, snd_dma_pci_data(chip->pci))) < 0)
+		return err;
+	DE_INIT(("Analog PCM ok\n"));
+
+#ifdef ECHOCARD_HAS_DIGITAL_IO
+	/* PCM#1 Digital inputs, no outputs */
+	if ((err = snd_pcm_new(chip->card, "Digital PCM", 1, 0,
+			       num_digital_busses_in(chip), &pcm)) < 0)
+		return err;
+	pcm->private_data = chip;
+	chip->digital_pcm = pcm;
+	strcpy(pcm->name, chip->card->shortname);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &digital_capture_ops);
+	if ((err = snd_echo_preallocate_pages(pcm, snd_dma_pci_data(chip->pci))) < 0)
+		return err;
+	DE_INIT(("Digital PCM ok\n"));
+#endif /* ECHOCARD_HAS_DIGITAL_IO */
+
+#else /* ECHOCARD_HAS_VMIXER */
+
+	/* The card can manage substreams formed by analog and digital channels
+	at the same time, but I prefer to keep analog and digital channels
+	separated, because that mixed thing is confusing and useless. So we
+	register two PCM devices: */
+
+	/* PCM#0 Analog i/o */
+	if ((err = snd_pcm_new(chip->card, "Analog PCM", 0,
+			       num_analog_busses_out(chip),
+			       num_analog_busses_in(chip), &pcm)) < 0)
+		return err;
+	pcm->private_data = chip;
+	chip->analog_pcm = pcm;
+	strcpy(pcm->name, chip->card->shortname);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &analog_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &analog_capture_ops);
+	if ((err = snd_echo_preallocate_pages(pcm, snd_dma_pci_data(chip->pci))) < 0)
+		return err;
+	DE_INIT(("Analog PCM ok\n"));
+
+#ifdef ECHOCARD_HAS_DIGITAL_IO
+	/* PCM#1 Digital i/o */
+	if ((err = snd_pcm_new(chip->card, "Digital PCM", 1,
+			       num_digital_busses_out(chip),
+			       num_digital_busses_in(chip), &pcm)) < 0)
+		return err;
+	pcm->private_data = chip;
+	chip->digital_pcm = pcm;
+	strcpy(pcm->name, chip->card->shortname);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &digital_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &digital_capture_ops);
+	if ((err = snd_echo_preallocate_pages(pcm, snd_dma_pci_data(chip->pci))) < 0)
+		return err;
+	DE_INIT(("Digital PCM ok\n"));
+#endif /* ECHOCARD_HAS_DIGITAL_IO */
+
+#endif /* ECHOCARD_HAS_VMIXER */
+
+	return 0;
+}
+
+
+
+
+/******************************************************************************
+	Control interface
+******************************************************************************/
+
+#if !defined(ECHOCARD_HAS_VMIXER) || defined(ECHOCARD_HAS_LINE_OUT_GAIN)
+
+/******************* PCM output volume *******************/
+static int snd_echo_output_gain_info(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = num_busses_out(chip);
+	uinfo->value.integer.min = ECHOGAIN_MINOUT;
+	uinfo->value.integer.max = ECHOGAIN_MAXOUT;
+	return 0;
+}
+
+static int snd_echo_output_gain_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int c;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	for (c = 0; c < num_busses_out(chip); c++)
+		ucontrol->value.integer.value[c] = chip->output_gain[c];
+	return 0;
+}
+
+static int snd_echo_output_gain_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int c, changed, gain;
+
+	changed = 0;
+	chip = snd_kcontrol_chip(kcontrol);
+	spin_lock_irq(&chip->lock);
+	for (c = 0; c < num_busses_out(chip); c++) {
+		gain = ucontrol->value.integer.value[c];
+		/* Ignore out of range values */
+		if (gain < ECHOGAIN_MINOUT || gain > ECHOGAIN_MAXOUT)
+			continue;
+		if (chip->output_gain[c] != gain) {
+			set_output_gain(chip, c, gain);
+			changed = 1;
+		}
+	}
+	if (changed)
+		update_output_line_level(chip);
+	spin_unlock_irq(&chip->lock);
+	return changed;
+}
+
+#ifdef ECHOCARD_HAS_LINE_OUT_GAIN
+/* On the Mia this one controls the line-out volume */
+static struct snd_kcontrol_new snd_echo_line_output_gain __devinitdata = {
+	.name = "Line Playback Volume",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.info = snd_echo_output_gain_info,
+	.get = snd_echo_output_gain_get,
+	.put = snd_echo_output_gain_put,
+	.tlv = {.p = db_scale_output_gain},
+};
+#else
+static struct snd_kcontrol_new snd_echo_pcm_output_gain __devinitdata = {
+	.name = "PCM Playback Volume",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.info = snd_echo_output_gain_info,
+	.get = snd_echo_output_gain_get,
+	.put = snd_echo_output_gain_put,
+	.tlv = {.p = db_scale_output_gain},
+};
+#endif
+
+#endif /* !ECHOCARD_HAS_VMIXER || ECHOCARD_HAS_LINE_OUT_GAIN */
+
+
+
+#ifdef ECHOCARD_HAS_INPUT_GAIN
+
+/******************* Analog input volume *******************/
+static int snd_echo_input_gain_info(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = num_analog_busses_in(chip);
+	uinfo->value.integer.min = ECHOGAIN_MININP;
+	uinfo->value.integer.max = ECHOGAIN_MAXINP;
+	return 0;
+}
+
+static int snd_echo_input_gain_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int c;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	for (c = 0; c < num_analog_busses_in(chip); c++)
+		ucontrol->value.integer.value[c] = chip->input_gain[c];
+	return 0;
+}
+
+static int snd_echo_input_gain_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int c, gain, changed;
+
+	changed = 0;
+	chip = snd_kcontrol_chip(kcontrol);
+	spin_lock_irq(&chip->lock);
+	for (c = 0; c < num_analog_busses_in(chip); c++) {
+		gain = ucontrol->value.integer.value[c];
+		/* Ignore out of range values */
+		if (gain < ECHOGAIN_MININP || gain > ECHOGAIN_MAXINP)
+			continue;
+		if (chip->input_gain[c] != gain) {
+			set_input_gain(chip, c, gain);
+			changed = 1;
+		}
+	}
+	if (changed)
+		update_input_line_level(chip);
+	spin_unlock_irq(&chip->lock);
+	return changed;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_input_gain, -2500, 50, 0);
+
+static struct snd_kcontrol_new snd_echo_line_input_gain __devinitdata = {
+	.name = "Line Capture Volume",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.info = snd_echo_input_gain_info,
+	.get = snd_echo_input_gain_get,
+	.put = snd_echo_input_gain_put,
+	.tlv = {.p = db_scale_input_gain},
+};
+
+#endif /* ECHOCARD_HAS_INPUT_GAIN */
+
+
+
+#ifdef ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL
+
+/************ Analog output nominal level (+4dBu / -10dBV) ***************/
+static int snd_echo_output_nominal_info (struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_info *uinfo)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = num_analog_busses_out(chip);
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_echo_output_nominal_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int c;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	for (c = 0; c < num_analog_busses_out(chip); c++)
+		ucontrol->value.integer.value[c] = chip->nominal_level[c];
+	return 0;
+}
+
+static int snd_echo_output_nominal_put(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int c, changed;
+
+	changed = 0;
+	chip = snd_kcontrol_chip(kcontrol);
+	spin_lock_irq(&chip->lock);
+	for (c = 0; c < num_analog_busses_out(chip); c++) {
+		if (chip->nominal_level[c] != ucontrol->value.integer.value[c]) {
+			set_nominal_level(chip, c,
+					  ucontrol->value.integer.value[c]);
+			changed = 1;
+		}
+	}
+	if (changed)
+		update_output_line_level(chip);
+	spin_unlock_irq(&chip->lock);
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_echo_output_nominal_level __devinitdata = {
+	.name = "Line Playback Switch (-10dBV)",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info = snd_echo_output_nominal_info,
+	.get = snd_echo_output_nominal_get,
+	.put = snd_echo_output_nominal_put,
+};
+
+#endif /* ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL */
+
+
+
+#ifdef ECHOCARD_HAS_INPUT_NOMINAL_LEVEL
+
+/*************** Analog input nominal level (+4dBu / -10dBV) ***************/
+static int snd_echo_input_nominal_info(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = num_analog_busses_in(chip);
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_echo_input_nominal_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int c;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	for (c = 0; c < num_analog_busses_in(chip); c++)
+		ucontrol->value.integer.value[c] =
+			chip->nominal_level[bx_analog_in(chip) + c];
+	return 0;
+}
+
+static int snd_echo_input_nominal_put(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int c, changed;
+
+	changed = 0;
+	chip = snd_kcontrol_chip(kcontrol);
+	spin_lock_irq(&chip->lock);
+	for (c = 0; c < num_analog_busses_in(chip); c++) {
+		if (chip->nominal_level[bx_analog_in(chip) + c] !=
+		    ucontrol->value.integer.value[c]) {
+			set_nominal_level(chip, bx_analog_in(chip) + c,
+					  ucontrol->value.integer.value[c]);
+			changed = 1;
+		}
+	}
+	if (changed)
+		update_output_line_level(chip);	/* "Output" is not a mistake
+						 * here.
+						 */
+	spin_unlock_irq(&chip->lock);
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_echo_intput_nominal_level __devinitdata = {
+	.name = "Line Capture Switch (-10dBV)",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info = snd_echo_input_nominal_info,
+	.get = snd_echo_input_nominal_get,
+	.put = snd_echo_input_nominal_put,
+};
+
+#endif /* ECHOCARD_HAS_INPUT_NOMINAL_LEVEL */
+
+
+
+#ifdef ECHOCARD_HAS_MONITOR
+
+/******************* Monitor mixer *******************/
+static int snd_echo_mixer_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = ECHOGAIN_MINOUT;
+	uinfo->value.integer.max = ECHOGAIN_MAXOUT;
+	uinfo->dimen.d[0] = num_busses_out(chip);
+	uinfo->dimen.d[1] = num_busses_in(chip);
+	return 0;
+}
+
+static int snd_echo_mixer_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] =
+		chip->monitor_gain[ucontrol->id.index / num_busses_in(chip)]
+			[ucontrol->id.index % num_busses_in(chip)];
+	return 0;
+}
+
+static int snd_echo_mixer_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int changed,  gain;
+	short out, in;
+
+	changed = 0;
+	chip = snd_kcontrol_chip(kcontrol);
+	out = ucontrol->id.index / num_busses_in(chip);
+	in = ucontrol->id.index % num_busses_in(chip);
+	gain = ucontrol->value.integer.value[0];
+	if (gain < ECHOGAIN_MINOUT || gain > ECHOGAIN_MAXOUT)
+		return -EINVAL;
+	if (chip->monitor_gain[out][in] != gain) {
+		spin_lock_irq(&chip->lock);
+		set_monitor_gain(chip, out, in, gain);
+		update_output_line_level(chip);
+		spin_unlock_irq(&chip->lock);
+		changed = 1;
+	}
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_echo_monitor_mixer __devinitdata = {
+	.name = "Monitor Mixer Volume",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.info = snd_echo_mixer_info,
+	.get = snd_echo_mixer_get,
+	.put = snd_echo_mixer_put,
+	.tlv = {.p = db_scale_output_gain},
+};
+
+#endif /* ECHOCARD_HAS_MONITOR */
+
+
+
+#ifdef ECHOCARD_HAS_VMIXER
+
+/******************* Vmixer *******************/
+static int snd_echo_vmixer_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = ECHOGAIN_MINOUT;
+	uinfo->value.integer.max = ECHOGAIN_MAXOUT;
+	uinfo->dimen.d[0] = num_busses_out(chip);
+	uinfo->dimen.d[1] = num_pipes_out(chip);
+	return 0;
+}
+
+static int snd_echo_vmixer_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] =
+		chip->vmixer_gain[ucontrol->id.index / num_pipes_out(chip)]
+			[ucontrol->id.index % num_pipes_out(chip)];
+	return 0;
+}
+
+static int snd_echo_vmixer_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int gain, changed;
+	short vch, out;
+
+	changed = 0;
+	chip = snd_kcontrol_chip(kcontrol);
+	out = ucontrol->id.index / num_pipes_out(chip);
+	vch = ucontrol->id.index % num_pipes_out(chip);
+	gain = ucontrol->value.integer.value[0];
+	if (gain < ECHOGAIN_MINOUT || gain > ECHOGAIN_MAXOUT)
+		return -EINVAL;
+	if (chip->vmixer_gain[out][vch] != ucontrol->value.integer.value[0]) {
+		spin_lock_irq(&chip->lock);
+		set_vmixer_gain(chip, out, vch, ucontrol->value.integer.value[0]);
+		update_vmixer_level(chip);
+		spin_unlock_irq(&chip->lock);
+		changed = 1;
+	}
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_echo_vmixer __devinitdata = {
+	.name = "VMixer Volume",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.info = snd_echo_vmixer_info,
+	.get = snd_echo_vmixer_get,
+	.put = snd_echo_vmixer_put,
+	.tlv = {.p = db_scale_output_gain},
+};
+
+#endif /* ECHOCARD_HAS_VMIXER */
+
+
+
+#ifdef ECHOCARD_HAS_DIGITAL_MODE_SWITCH
+
+/******************* Digital mode switch *******************/
+static int snd_echo_digital_mode_info(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_info *uinfo)
+{
+	static char *names[4] = {
+		"S/PDIF Coaxial", "S/PDIF Optical", "ADAT Optical",
+		"S/PDIF Cdrom"
+	};
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->value.enumerated.items = chip->num_digital_modes;
+	uinfo->count = 1;
+	if (uinfo->value.enumerated.item >= chip->num_digital_modes)
+		uinfo->value.enumerated.item = chip->num_digital_modes - 1;
+	strcpy(uinfo->value.enumerated.name, names[
+			chip->digital_mode_list[uinfo->value.enumerated.item]]);
+	return 0;
+}
+
+static int snd_echo_digital_mode_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int i, mode;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	mode = chip->digital_mode;
+	for (i = chip->num_digital_modes - 1; i >= 0; i--)
+		if (mode == chip->digital_mode_list[i]) {
+			ucontrol->value.enumerated.item[0] = i;
+			break;
+		}
+	return 0;
+}
+
+static int snd_echo_digital_mode_put(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int changed;
+	unsigned short emode, dmode;
+
+	changed = 0;
+	chip = snd_kcontrol_chip(kcontrol);
+
+	emode = ucontrol->value.enumerated.item[0];
+	if (emode >= chip->num_digital_modes)
+		return -EINVAL;
+	dmode = chip->digital_mode_list[emode];
+
+	if (dmode != chip->digital_mode) {
+		/* mode_mutex is required to make this operation atomic wrt
+		pcm_digital_*_open() and set_input_clock() functions. */
+		mutex_lock(&chip->mode_mutex);
+
+		/* Do not allow the user to change the digital mode when a pcm
+		device is open because it also changes the number of channels
+		and the allowed sample rates */
+		if (atomic_read(&chip->opencount)) {
+			changed = -EAGAIN;
+		} else {
+			changed = set_digital_mode(chip, dmode);
+			/* If we had to change the clock source, report it */
+			if (changed > 0 && chip->clock_src_ctl) {
+				snd_ctl_notify(chip->card,
+					       SNDRV_CTL_EVENT_MASK_VALUE,
+					       &chip->clock_src_ctl->id);
+				DE_ACT(("SDM() =%d\n", changed));
+			}
+			if (changed >= 0)
+				changed = 1;	/* No errors */
+		}
+		mutex_unlock(&chip->mode_mutex);
+	}
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_echo_digital_mode_switch __devinitdata = {
+	.name = "Digital mode Switch",
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.info = snd_echo_digital_mode_info,
+	.get = snd_echo_digital_mode_get,
+	.put = snd_echo_digital_mode_put,
+};
+
+#endif /* ECHOCARD_HAS_DIGITAL_MODE_SWITCH */
+
+
+
+#ifdef ECHOCARD_HAS_DIGITAL_IO
+
+/******************* S/PDIF mode switch *******************/
+static int snd_echo_spdif_mode_info(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	static char *names[2] = {"Consumer", "Professional"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->value.enumerated.items = 2;
+	uinfo->count = 1;
+	if (uinfo->value.enumerated.item)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name,
+	       names[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_echo_spdif_mode_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = !!chip->professional_spdif;
+	return 0;
+}
+
+static int snd_echo_spdif_mode_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int mode;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	mode = !!ucontrol->value.enumerated.item[0];
+	if (mode != chip->professional_spdif) {
+		spin_lock_irq(&chip->lock);
+		set_professional_spdif(chip, mode);
+		spin_unlock_irq(&chip->lock);
+		return 1;
+	}
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_echo_spdif_mode_switch __devinitdata = {
+	.name = "S/PDIF mode Switch",
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.info = snd_echo_spdif_mode_info,
+	.get = snd_echo_spdif_mode_get,
+	.put = snd_echo_spdif_mode_put,
+};
+
+#endif /* ECHOCARD_HAS_DIGITAL_IO */
+
+
+
+#ifdef ECHOCARD_HAS_EXTERNAL_CLOCK
+
+/******************* Select input clock source *******************/
+static int snd_echo_clock_source_info(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_info *uinfo)
+{
+	static char *names[8] = {
+		"Internal", "Word", "Super", "S/PDIF", "ADAT", "ESync",
+		"ESync96", "MTC"
+	};
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->value.enumerated.items = chip->num_clock_sources;
+	uinfo->count = 1;
+	if (uinfo->value.enumerated.item >= chip->num_clock_sources)
+		uinfo->value.enumerated.item = chip->num_clock_sources - 1;
+	strcpy(uinfo->value.enumerated.name, names[
+			chip->clock_source_list[uinfo->value.enumerated.item]]);
+	return 0;
+}
+
+static int snd_echo_clock_source_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int i, clock;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	clock = chip->input_clock;
+
+	for (i = 0; i < chip->num_clock_sources; i++)
+		if (clock == chip->clock_source_list[i])
+			ucontrol->value.enumerated.item[0] = i;
+
+	return 0;
+}
+
+static int snd_echo_clock_source_put(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int changed;
+	unsigned int eclock, dclock;
+
+	changed = 0;
+	chip = snd_kcontrol_chip(kcontrol);
+	eclock = ucontrol->value.enumerated.item[0];
+	if (eclock >= chip->input_clock_types)
+		return -EINVAL;
+	dclock = chip->clock_source_list[eclock];
+	if (chip->input_clock != dclock) {
+		mutex_lock(&chip->mode_mutex);
+		spin_lock_irq(&chip->lock);
+		if ((changed = set_input_clock(chip, dclock)) == 0)
+			changed = 1;	/* no errors */
+		spin_unlock_irq(&chip->lock);
+		mutex_unlock(&chip->mode_mutex);
+	}
+
+	if (changed < 0)
+		DE_ACT(("seticlk val%d err 0x%x\n", dclock, changed));
+
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_echo_clock_source_switch __devinitdata = {
+	.name = "Sample Clock Source",
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.info = snd_echo_clock_source_info,
+	.get = snd_echo_clock_source_get,
+	.put = snd_echo_clock_source_put,
+};
+
+#endif /* ECHOCARD_HAS_EXTERNAL_CLOCK */
+
+
+
+#ifdef ECHOCARD_HAS_PHANTOM_POWER
+
+/******************* Phantom power switch *******************/
+#define snd_echo_phantom_power_info	snd_ctl_boolean_mono_info
+
+static int snd_echo_phantom_power_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = chip->phantom_power;
+	return 0;
+}
+
+static int snd_echo_phantom_power_put(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip = snd_kcontrol_chip(kcontrol);
+	int power, changed = 0;
+
+	power = !!ucontrol->value.integer.value[0];
+	if (chip->phantom_power != power) {
+		spin_lock_irq(&chip->lock);
+		changed = set_phantom_power(chip, power);
+		spin_unlock_irq(&chip->lock);
+		if (changed == 0)
+			changed = 1;	/* no errors */
+	}
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_echo_phantom_power_switch __devinitdata = {
+	.name = "Phantom power Switch",
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.info = snd_echo_phantom_power_info,
+	.get = snd_echo_phantom_power_get,
+	.put = snd_echo_phantom_power_put,
+};
+
+#endif /* ECHOCARD_HAS_PHANTOM_POWER */
+
+
+
+#ifdef ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE
+
+/******************* Digital input automute switch *******************/
+#define snd_echo_automute_info		snd_ctl_boolean_mono_info
+
+static int snd_echo_automute_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = chip->digital_in_automute;
+	return 0;
+}
+
+static int snd_echo_automute_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip = snd_kcontrol_chip(kcontrol);
+	int automute, changed = 0;
+
+	automute = !!ucontrol->value.integer.value[0];
+	if (chip->digital_in_automute != automute) {
+		spin_lock_irq(&chip->lock);
+		changed = set_input_auto_mute(chip, automute);
+		spin_unlock_irq(&chip->lock);
+		if (changed == 0)
+			changed = 1;	/* no errors */
+	}
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_echo_automute_switch __devinitdata = {
+	.name = "Digital Capture Switch (automute)",
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.info = snd_echo_automute_info,
+	.get = snd_echo_automute_get,
+	.put = snd_echo_automute_put,
+};
+
+#endif /* ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE */
+
+
+
+/******************* VU-meters switch *******************/
+#define snd_echo_vumeters_switch_info		snd_ctl_boolean_mono_info
+
+static int snd_echo_vumeters_switch_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	spin_lock_irq(&chip->lock);
+	set_meters_on(chip, ucontrol->value.integer.value[0]);
+	spin_unlock_irq(&chip->lock);
+	return 1;
+}
+
+static struct snd_kcontrol_new snd_echo_vumeters_switch __devinitdata = {
+	.name = "VU-meters Switch",
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
+	.info = snd_echo_vumeters_switch_info,
+	.put = snd_echo_vumeters_switch_put,
+};
+
+
+
+/***** Read VU-meters (input, output, analog and digital together) *****/
+static int snd_echo_vumeters_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 96;
+	uinfo->value.integer.min = ECHOGAIN_MINOUT;
+	uinfo->value.integer.max = 0;
+#ifdef ECHOCARD_HAS_VMIXER
+	uinfo->dimen.d[0] = 3;	/* Out, In, Virt */
+#else
+	uinfo->dimen.d[0] = 2;	/* Out, In */
+#endif
+	uinfo->dimen.d[1] = 16;	/* 16 channels */
+	uinfo->dimen.d[2] = 2;	/* 0=level, 1=peak */
+	return 0;
+}
+
+static int snd_echo_vumeters_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	get_audio_meters(chip, ucontrol->value.integer.value);
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_echo_vumeters __devinitdata = {
+	.name = "VU-meters",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ |
+		  SNDRV_CTL_ELEM_ACCESS_VOLATILE |
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.info = snd_echo_vumeters_info,
+	.get = snd_echo_vumeters_get,
+	.tlv = {.p = db_scale_output_gain},
+};
+
+
+
+/*** Channels info - it exports informations about the number of channels ***/
+static int snd_echo_channels_info_info(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	struct echoaudio *chip;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 6;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1 << ECHO_CLOCK_NUMBER;
+	return 0;
+}
+
+static int snd_echo_channels_info_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct echoaudio *chip;
+	int detected, clocks, bit, src;
+
+	chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = num_busses_in(chip);
+	ucontrol->value.integer.value[1] = num_analog_busses_in(chip);
+	ucontrol->value.integer.value[2] = num_busses_out(chip);
+	ucontrol->value.integer.value[3] = num_analog_busses_out(chip);
+	ucontrol->value.integer.value[4] = num_pipes_out(chip);
+
+	/* Compute the bitmask of the currently valid input clocks */
+	detected = detect_input_clocks(chip);
+	clocks = 0;
+	src = chip->num_clock_sources - 1;
+	for (bit = ECHO_CLOCK_NUMBER - 1; bit >= 0; bit--)
+		if (detected & (1 << bit))
+			for (; src >= 0; src--)
+				if (bit == chip->clock_source_list[src]) {
+					clocks |= 1 << src;
+					break;
+				}
+	ucontrol->value.integer.value[5] = clocks;
+
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_echo_channels_info __devinitdata = {
+	.name = "Channels info",
+	.iface = SNDRV_CTL_ELEM_IFACE_HWDEP,
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info = snd_echo_channels_info_info,
+	.get = snd_echo_channels_info_get,
+};
+
+
+
+
+/******************************************************************************
+	IRQ Handler
+******************************************************************************/
+
+static irqreturn_t snd_echo_interrupt(int irq, void *dev_id)
+{
+	struct echoaudio *chip = dev_id;
+	struct snd_pcm_substream *substream;
+	int period, ss, st;
+
+	spin_lock(&chip->lock);
+	st = service_irq(chip);
+	if (st < 0) {
+		spin_unlock(&chip->lock);
+		return IRQ_NONE;
+	}
+	/* The hardware doesn't tell us which substream caused the irq,
+	thus we have to check all running substreams. */
+	for (ss = 0; ss < DSP_MAXPIPES; ss++) {
+		substream = chip->substream[ss];
+		if (substream && ((struct audiopipe *)substream->runtime->
+				private_data)->state == PIPE_STATE_STARTED) {
+			period = pcm_pointer(substream) /
+				substream->runtime->period_size;
+			if (period != chip->last_period[ss]) {
+				chip->last_period[ss] = period;
+				spin_unlock(&chip->lock);
+				snd_pcm_period_elapsed(substream);
+				spin_lock(&chip->lock);
+			}
+		}
+	}
+	spin_unlock(&chip->lock);
+
+#ifdef ECHOCARD_HAS_MIDI
+	if (st > 0 && chip->midi_in) {
+		snd_rawmidi_receive(chip->midi_in, chip->midi_buffer, st);
+		DE_MID(("rawmidi_iread=%d\n", st));
+	}
+#endif
+	return IRQ_HANDLED;
+}
+
+
+
+
+/******************************************************************************
+	Module construction / destruction
+******************************************************************************/
+
+static int snd_echo_free(struct echoaudio *chip)
+{
+	DE_INIT(("Stop DSP...\n"));
+	if (chip->comm_page)
+		rest_in_peace(chip);
+	DE_INIT(("Stopped.\n"));
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+
+	if (chip->comm_page)
+		snd_dma_free_pages(&chip->commpage_dma_buf);
+
+	if (chip->dsp_registers)
+		iounmap(chip->dsp_registers);
+
+	if (chip->iores)
+		release_and_free_resource(chip->iores);
+
+	DE_INIT(("MMIO freed.\n"));
+
+	pci_disable_device(chip->pci);
+
+	/* release chip data */
+	free_firmware_cache(chip);
+	kfree(chip);
+	DE_INIT(("Chip freed.\n"));
+	return 0;
+}
+
+
+
+static int snd_echo_dev_free(struct snd_device *device)
+{
+	struct echoaudio *chip = device->device_data;
+
+	DE_INIT(("snd_echo_dev_free()...\n"));
+	return snd_echo_free(chip);
+}
+
+
+
+/* <--snd_echo_probe() */
+static __devinit int snd_echo_create(struct snd_card *card,
+				     struct pci_dev *pci,
+				     struct echoaudio **rchip)
+{
+	struct echoaudio *chip;
+	int err;
+	size_t sz;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_echo_dev_free,
+	};
+
+	*rchip = NULL;
+
+	pci_write_config_byte(pci, PCI_LATENCY_TIMER, 0xC0);
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	pci_set_master(pci);
+
+	/* Allocate chip if needed */
+	if (!*rchip) {
+		chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+		if (!chip) {
+			pci_disable_device(pci);
+			return -ENOMEM;
+		}
+		DE_INIT(("chip=%p\n", chip));
+		spin_lock_init(&chip->lock);
+		chip->card = card;
+		chip->pci = pci;
+		chip->irq = -1;
+		atomic_set(&chip->opencount, 0);
+		mutex_init(&chip->mode_mutex);
+		chip->can_set_rate = 1;
+	} else {
+		/* If this was called from the resume function, chip is
+		 * already allocated and it contains current card settings.
+		 */
+		chip = *rchip;
+	}
+
+	/* PCI resource allocation */
+	chip->dsp_registers_phys = pci_resource_start(pci, 0);
+	sz = pci_resource_len(pci, 0);
+	if (sz > PAGE_SIZE)
+		sz = PAGE_SIZE;		/* We map only the required part */
+
+	if ((chip->iores = request_mem_region(chip->dsp_registers_phys, sz,
+					      ECHOCARD_NAME)) == NULL) {
+		snd_echo_free(chip);
+		snd_printk(KERN_ERR "cannot get memory region\n");
+		return -EBUSY;
+	}
+	chip->dsp_registers = (volatile u32 __iomem *)
+		ioremap_nocache(chip->dsp_registers_phys, sz);
+
+	if (request_irq(pci->irq, snd_echo_interrupt, IRQF_SHARED,
+			ECHOCARD_NAME, chip)) {
+		snd_echo_free(chip);
+		snd_printk(KERN_ERR "cannot grab irq\n");
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+	DE_INIT(("pci=%p irq=%d subdev=%04x Init hardware...\n",
+		 chip->pci, chip->irq, chip->pci->subsystem_device));
+
+	/* Create the DSP comm page - this is the area of memory used for most
+	of the communication with the DSP, which accesses it via bus mastering */
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+				sizeof(struct comm_page),
+				&chip->commpage_dma_buf) < 0) {
+		snd_echo_free(chip);
+		snd_printk(KERN_ERR "cannot allocate the comm page\n");
+		return -ENOMEM;
+	}
+	chip->comm_page_phys = chip->commpage_dma_buf.addr;
+	chip->comm_page = (struct comm_page *)chip->commpage_dma_buf.area;
+
+	err = init_hw(chip, chip->pci->device, chip->pci->subsystem_device);
+	if (err >= 0)
+		err = set_mixer_defaults(chip);
+	if (err < 0) {
+		DE_INIT(("init_hw err=%d\n", err));
+		snd_echo_free(chip);
+		return err;
+	}
+	DE_INIT(("Card init OK\n"));
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_echo_free(chip);
+		return err;
+	}
+	*rchip = chip;
+	/* Init done ! */
+	return 0;
+}
+
+
+
+/* constructor */
+static int __devinit snd_echo_probe(struct pci_dev *pci,
+				    const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct echoaudio *chip;
+	char *dsp;
+	int i, err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	DE_INIT(("Echoaudio driver starting...\n"));
+	i = 0;
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	snd_card_set_dev(card, &pci->dev);
+
+	chip = NULL;	/* Tells snd_echo_create to allocate chip */
+	if ((err = snd_echo_create(card, pci, &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "Echo_" ECHOCARD_NAME);
+	strcpy(card->shortname, chip->card_name);
+
+	dsp = "56301";
+	if (pci_id->device == 0x3410)
+		dsp = "56361";
+
+	sprintf(card->longname, "%s rev.%d (DSP%s) at 0x%lx irq %i",
+		card->shortname, pci_id->subdevice & 0x000f, dsp,
+		chip->dsp_registers_phys, chip->irq);
+
+	if ((err = snd_echo_new_pcm(chip)) < 0) {
+		snd_printk(KERN_ERR "new pcm error %d\n", err);
+		snd_card_free(card);
+		return err;
+	}
+
+#ifdef ECHOCARD_HAS_MIDI
+	if (chip->has_midi) {	/* Some Mia's do not have midi */
+		if ((err = snd_echo_midi_create(card, chip)) < 0) {
+			snd_printk(KERN_ERR "new midi error %d\n", err);
+			snd_card_free(card);
+			return err;
+		}
+	}
+#endif
+
+#ifdef ECHOCARD_HAS_VMIXER
+	snd_echo_vmixer.count = num_pipes_out(chip) * num_busses_out(chip);
+	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_vmixer, chip))) < 0)
+		goto ctl_error;
+#ifdef ECHOCARD_HAS_LINE_OUT_GAIN
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&snd_echo_line_output_gain, chip));
+	if (err < 0)
+		goto ctl_error;
+#endif
+#else /* ECHOCARD_HAS_VMIXER */
+	err = snd_ctl_add(chip->card,
+			  snd_ctl_new1(&snd_echo_pcm_output_gain, chip));
+	if (err < 0)
+		goto ctl_error;
+#endif /* ECHOCARD_HAS_VMIXER */
+
+#ifdef ECHOCARD_HAS_INPUT_GAIN
+	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_line_input_gain, chip))) < 0)
+		goto ctl_error;
+#endif
+
+#ifdef ECHOCARD_HAS_INPUT_NOMINAL_LEVEL
+	if (!chip->hasnt_input_nominal_level)
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_intput_nominal_level, chip))) < 0)
+			goto ctl_error;
+#endif
+
+#ifdef ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL
+	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_output_nominal_level, chip))) < 0)
+		goto ctl_error;
+#endif
+
+	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_vumeters_switch, chip))) < 0)
+		goto ctl_error;
+
+	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_vumeters, chip))) < 0)
+		goto ctl_error;
+
+#ifdef ECHOCARD_HAS_MONITOR
+	snd_echo_monitor_mixer.count = num_busses_in(chip) * num_busses_out(chip);
+	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_monitor_mixer, chip))) < 0)
+		goto ctl_error;
+#endif
+
+#ifdef ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE
+	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_automute_switch, chip))) < 0)
+		goto ctl_error;
+#endif
+
+	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_channels_info, chip))) < 0)
+		goto ctl_error;
+
+#ifdef ECHOCARD_HAS_DIGITAL_MODE_SWITCH
+	/* Creates a list of available digital modes */
+	chip->num_digital_modes = 0;
+	for (i = 0; i < 6; i++)
+		if (chip->digital_modes & (1 << i))
+			chip->digital_mode_list[chip->num_digital_modes++] = i;
+
+	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_digital_mode_switch, chip))) < 0)
+		goto ctl_error;
+#endif /* ECHOCARD_HAS_DIGITAL_MODE_SWITCH */
+
+#ifdef ECHOCARD_HAS_EXTERNAL_CLOCK
+	/* Creates a list of available clock sources */
+	chip->num_clock_sources = 0;
+	for (i = 0; i < 10; i++)
+		if (chip->input_clock_types & (1 << i))
+			chip->clock_source_list[chip->num_clock_sources++] = i;
+
+	if (chip->num_clock_sources > 1) {
+		chip->clock_src_ctl = snd_ctl_new1(&snd_echo_clock_source_switch, chip);
+		if ((err = snd_ctl_add(chip->card, chip->clock_src_ctl)) < 0)
+			goto ctl_error;
+	}
+#endif /* ECHOCARD_HAS_EXTERNAL_CLOCK */
+
+#ifdef ECHOCARD_HAS_DIGITAL_IO
+	if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_spdif_mode_switch, chip))) < 0)
+		goto ctl_error;
+#endif
+
+#ifdef ECHOCARD_HAS_PHANTOM_POWER
+	if (chip->has_phantom_power)
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_echo_phantom_power_switch, chip))) < 0)
+			goto ctl_error;
+#endif
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto ctl_error;
+	snd_printk(KERN_INFO "Card registered: %s\n", card->longname);
+
+	pci_set_drvdata(pci, chip);
+	dev++;
+	return 0;
+
+ctl_error:
+	snd_printk(KERN_ERR "new control error %d\n", err);
+	snd_card_free(card);
+	return err;
+}
+
+
+
+#if defined(CONFIG_PM)
+
+static int snd_echo_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct echoaudio *chip = pci_get_drvdata(pci);
+
+	DE_INIT(("suspend start\n"));
+	snd_pcm_suspend_all(chip->analog_pcm);
+	snd_pcm_suspend_all(chip->digital_pcm);
+
+#ifdef ECHOCARD_HAS_MIDI
+	/* This call can sleep */
+	if (chip->midi_out)
+		snd_echo_midi_output_trigger(chip->midi_out, 0);
+#endif
+	spin_lock_irq(&chip->lock);
+	if (wait_handshake(chip)) {
+		spin_unlock_irq(&chip->lock);
+		return -EIO;
+	}
+	clear_handshake(chip);
+	if (send_vector(chip, DSP_VC_GO_COMATOSE) < 0) {
+		spin_unlock_irq(&chip->lock);
+		return -EIO;
+	}
+	spin_unlock_irq(&chip->lock);
+
+	chip->dsp_code = NULL;
+	free_irq(chip->irq, chip);
+	chip->irq = -1;
+	pci_save_state(pci);
+	pci_disable_device(pci);
+
+	DE_INIT(("suspend done\n"));
+	return 0;
+}
+
+
+
+static int snd_echo_resume(struct pci_dev *pci)
+{
+	struct echoaudio *chip = pci_get_drvdata(pci);
+	struct comm_page *commpage, *commpage_bak;
+	u32 pipe_alloc_mask;
+	int err;
+
+	DE_INIT(("resume start\n"));
+	pci_restore_state(pci);
+	commpage_bak = kmalloc(sizeof(struct echoaudio), GFP_KERNEL);
+	if (commpage_bak == NULL)
+		return -ENOMEM;
+	commpage = chip->comm_page;
+	memcpy(commpage_bak, commpage, sizeof(struct comm_page));
+
+	err = init_hw(chip, chip->pci->device, chip->pci->subsystem_device);
+	if (err < 0) {
+		kfree(commpage_bak);
+		DE_INIT(("resume init_hw err=%d\n", err));
+		snd_echo_free(chip);
+		return err;
+	}
+	DE_INIT(("resume init OK\n"));
+
+	/* Temporarily set chip->pipe_alloc_mask=0 otherwise
+	 * restore_dsp_settings() fails.
+	 */
+	pipe_alloc_mask = chip->pipe_alloc_mask;
+	chip->pipe_alloc_mask = 0;
+	err = restore_dsp_rettings(chip);
+	chip->pipe_alloc_mask = pipe_alloc_mask;
+	if (err < 0) {
+		kfree(commpage_bak);
+		return err;
+	}
+	DE_INIT(("resume restore OK\n"));
+
+	memcpy(&commpage->audio_format, &commpage_bak->audio_format,
+		sizeof(commpage->audio_format));
+	memcpy(&commpage->sglist_addr, &commpage_bak->sglist_addr,
+		sizeof(commpage->sglist_addr));
+	memcpy(&commpage->midi_output, &commpage_bak->midi_output,
+		sizeof(commpage->midi_output));
+	kfree(commpage_bak);
+
+	if (request_irq(pci->irq, snd_echo_interrupt, IRQF_SHARED,
+			ECHOCARD_NAME, chip)) {
+		snd_echo_free(chip);
+		snd_printk(KERN_ERR "cannot grab irq\n");
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+	DE_INIT(("resume irq=%d\n", chip->irq));
+
+#ifdef ECHOCARD_HAS_MIDI
+	if (chip->midi_input_enabled)
+		enable_midi_input(chip, TRUE);
+	if (chip->midi_out)
+		snd_echo_midi_output_trigger(chip->midi_out, 1);
+#endif
+
+	DE_INIT(("resume done\n"));
+	return 0;
+}
+
+#endif /* CONFIG_PM */
+
+
+
+static void __devexit snd_echo_remove(struct pci_dev *pci)
+{
+	struct echoaudio *chip;
+
+	chip = pci_get_drvdata(pci);
+	if (chip)
+		snd_card_free(chip->card);
+	pci_set_drvdata(pci, NULL);
+}
+
+
+
+/******************************************************************************
+	Everything starts and ends here
+******************************************************************************/
+
+/* pci_driver definition */
+static struct pci_driver driver = {
+	.name = "Echoaudio " ECHOCARD_NAME,
+	.id_table = snd_echo_ids,
+	.probe = snd_echo_probe,
+	.remove = __devexit_p(snd_echo_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_echo_suspend,
+	.resume = snd_echo_resume,
+#endif /* CONFIG_PM */
+};
+
+
+
+/* initialization of the module */
+static int __init alsa_card_echo_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+
+
+/* clean up the module */
+static void __exit alsa_card_echo_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+
+module_init(alsa_card_echo_init)
+module_exit(alsa_card_echo_exit)
diff --git a/linux/sound/pci/echoaudio/echoaudio.h b/linux/sound/pci/echoaudio/echoaudio.h
new file mode 100644
index 0000000..1df974d
--- /dev/null
+++ b/linux/sound/pci/echoaudio/echoaudio.h
@@ -0,0 +1,598 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+ ****************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+ ****************************************************************************
+
+
+   Here's a block diagram of how most of the cards work:
+
+                  +-----------+
+           record |           |<-------------------- Inputs
+          <-------|           |        |
+     PCI          | Transport |        |
+     bus          |  engine   |       \|/
+          ------->|           |    +-------+
+            play  |           |--->|monitor|-------> Outputs
+                  +-----------+    | mixer |
+                                   +-------+
+
+   The lines going to and from the PCI bus represent "pipes".  A pipe performs
+   audio transport - moving audio data to and from buffers on the host via
+   bus mastering.
+
+   The inputs and outputs on the right represent input and output "busses."
+   A bus is a physical, real connection to the outside world.  An example
+   of a bus would be the 1/4" analog connectors on the back of Layla or
+   an RCA S/PDIF connector.
+
+   For most cards, there is a one-to-one correspondence between outputs
+   and busses; that is, each individual pipe is hard-wired to a single bus.
+
+   Cards that work this way are Darla20, Gina20, Layla20, Darla24, Gina24,
+   Layla24, Mona, and Indigo.
+
+
+   Mia has a feature called "virtual outputs."
+
+
+                  +-----------+
+           record |           |<----------------------------- Inputs
+          <-------|           |                  |
+     PCI          | Transport |                  |
+     bus          |  engine   |                 \|/
+          ------->|           |   +------+   +-------+
+            play  |           |-->|vmixer|-->|monitor|-------> Outputs
+                  +-----------+   +------+   | mixer |
+                                             +-------+
+
+
+   Obviously, the difference here is the box labeled "vmixer."  Vmixer is
+   short for "virtual output mixer."  For Mia, pipes are *not* hard-wired
+   to a single bus; the vmixer lets you mix any pipe to any bus in any
+   combination.
+
+   Note, however, that the left-hand side of the diagram is unchanged.
+   Transport works exactly the same way - the difference is in the mixer stage.
+
+
+   Pipes and busses are numbered starting at zero.
+
+
+
+   Pipe index
+   ==========
+
+   A number of calls in CEchoGals refer to a "pipe index".  A pipe index is
+   a unique number for a pipe that unambiguously refers to a playback or record
+   pipe.  Pipe indices are numbered starting with analog outputs, followed by
+   digital outputs, then analog inputs, then digital inputs.
+
+   Take Gina24 as an example:
+
+   Pipe index
+
+   0-7            Analog outputs (0 .. FirstDigitalBusOut-1)
+   8-15           Digital outputs (FirstDigitalBusOut .. NumBussesOut-1)
+   16-17          Analog inputs
+   18-25          Digital inputs
+
+
+   You get the pipe index by calling CEchoGals::OpenAudio; the other transport
+   functions take the pipe index as a parameter.  If you need a pipe index for
+   some other reason, use the handy Makepipe_index method.
+
+
+   Some calls take a CChannelMask parameter; CChannelMask is a handy way to
+   group pipe indices.
+
+
+
+   Digital mode switch
+   ===================
+
+   Some cards (right now, Gina24, Layla24, and Mona) have a Digital Mode Switch
+   or DMS.  Cards with a DMS can be set to one of three mutually exclusive
+   digital modes: S/PDIF RCA, S/PDIF optical, or ADAT optical.
+
+   This may create some confusion since ADAT optical is 8 channels wide and
+   S/PDIF is only two channels wide.  Gina24, Layla24, and Mona handle this
+   by acting as if they always have 8 digital outs and ins.  If you are in
+   either S/PDIF mode, the last 6 channels don't do anything - data sent
+   out these channels is thrown away and you will always record zeros.
+
+   Note that with Gina24, Layla24, and Mona, sample rates above 50 kHz are
+   only available if you have the card configured for S/PDIF optical or S/PDIF
+   RCA.
+
+
+
+   Double speed mode
+   =================
+
+   Some of the cards support 88.2 kHz and 96 kHz sampling (Darla24, Gina24,
+   Layla24, Mona, Mia, and Indigo).  For these cards, the driver sometimes has
+   to worry about "double speed mode"; double speed mode applies whenever the
+   sampling rate is above 50 kHz.
+
+   For instance, Mona and Layla24 support word clock sync.  However, they
+   actually support two different word clock modes - single speed (below
+   50 kHz) and double speed (above 50 kHz).  The hardware detects if a single
+   or double speed word clock signal is present; the generic code uses that
+   information to determine which mode to use.
+
+   The generic code takes care of all this for you.
+*/
+
+
+#ifndef _ECHOAUDIO_H_
+#define _ECHOAUDIO_H_
+
+
+#define TRUE 1
+#define FALSE 0
+
+#include "echoaudio_dsp.h"
+
+
+
+/***********************************************************************
+
+	PCI configuration space
+
+***********************************************************************/
+
+/*
+ * PCI vendor ID and device IDs for the hardware
+ */
+#define VENDOR_ID		0x1057
+#define DEVICE_ID_56301		0x1801
+#define DEVICE_ID_56361		0x3410
+#define SUBVENDOR_ID		0xECC0
+
+
+/*
+ * Valid Echo PCI subsystem card IDs
+ */
+#define DARLA20			0x0010
+#define GINA20			0x0020
+#define LAYLA20			0x0030
+#define DARLA24			0x0040
+#define GINA24			0x0050
+#define LAYLA24			0x0060
+#define MONA			0x0070
+#define MIA			0x0080
+#define INDIGO			0x0090
+#define INDIGO_IO		0x00a0
+#define INDIGO_DJ		0x00b0
+#define DC8			0x00c0
+#define INDIGO_IOX		0x00d0
+#define INDIGO_DJX		0x00e0
+#define ECHO3G			0x0100
+
+
+/************************************************************************
+
+	Array sizes and so forth
+
+***********************************************************************/
+
+/*
+ * Sizes
+ */
+#define ECHO_MAXAUDIOINPUTS	32	/* Max audio input channels */
+#define ECHO_MAXAUDIOOUTPUTS	32	/* Max audio output channels */
+#define ECHO_MAXAUDIOPIPES	32	/* Max number of input and output
+					 * pipes */
+#define E3G_MAX_OUTPUTS		16
+#define ECHO_MAXMIDIJACKS	1	/* Max MIDI ports */
+#define ECHO_MIDI_QUEUE_SZ 	512	/* Max MIDI input queue entries */
+#define ECHO_MTC_QUEUE_SZ	32	/* Max MIDI time code input queue
+					 * entries */
+
+/*
+ * MIDI activity indicator timeout
+ */
+#define MIDI_ACTIVITY_TIMEOUT_USEC	200000
+
+
+/****************************************************************************
+ 
+   Clocks
+
+*****************************************************************************/
+
+/*
+ * Clock numbers
+ */
+#define ECHO_CLOCK_INTERNAL		0
+#define ECHO_CLOCK_WORD			1
+#define ECHO_CLOCK_SUPER		2
+#define ECHO_CLOCK_SPDIF		3
+#define ECHO_CLOCK_ADAT			4
+#define ECHO_CLOCK_ESYNC		5
+#define ECHO_CLOCK_ESYNC96		6
+#define ECHO_CLOCK_MTC			7
+#define ECHO_CLOCK_NUMBER		8
+#define ECHO_CLOCKS			0xffff
+
+/*
+ * Clock bit numbers - used to report capabilities and whatever clocks
+ * are being detected dynamically.
+ */
+#define ECHO_CLOCK_BIT_INTERNAL		(1 << ECHO_CLOCK_INTERNAL)
+#define ECHO_CLOCK_BIT_WORD		(1 << ECHO_CLOCK_WORD)
+#define ECHO_CLOCK_BIT_SUPER		(1 << ECHO_CLOCK_SUPER)
+#define ECHO_CLOCK_BIT_SPDIF		(1 << ECHO_CLOCK_SPDIF)
+#define ECHO_CLOCK_BIT_ADAT		(1 << ECHO_CLOCK_ADAT)
+#define ECHO_CLOCK_BIT_ESYNC		(1 << ECHO_CLOCK_ESYNC)
+#define ECHO_CLOCK_BIT_ESYNC96		(1 << ECHO_CLOCK_ESYNC96)
+#define ECHO_CLOCK_BIT_MTC		(1<<ECHO_CLOCK_MTC)
+
+
+/***************************************************************************
+
+   Digital modes
+
+****************************************************************************/
+
+/*
+ * Digital modes for Mona, Layla24, and Gina24
+ */
+#define DIGITAL_MODE_NONE			0xFF
+#define DIGITAL_MODE_SPDIF_RCA			0
+#define DIGITAL_MODE_SPDIF_OPTICAL		1
+#define DIGITAL_MODE_ADAT			2
+#define DIGITAL_MODE_SPDIF_CDROM		3
+#define DIGITAL_MODES				4
+
+/*
+ * Digital mode capability masks
+ */
+#define ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA	(1 << DIGITAL_MODE_SPDIF_RCA)
+#define ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL	(1 << DIGITAL_MODE_SPDIF_OPTICAL)
+#define ECHOCAPS_HAS_DIGITAL_MODE_ADAT		(1 << DIGITAL_MODE_ADAT)
+#define ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_CDROM	(1 << DIGITAL_MODE_SPDIF_CDROM)
+
+
+#define EXT_3GBOX_NC			0x01	/* 3G box not connected */
+#define EXT_3GBOX_NOT_SET		0x02	/* 3G box not detected yet */
+
+
+#define ECHOGAIN_MUTED		(-128)	/* Minimum possible gain */
+#define ECHOGAIN_MINOUT		(-128)	/* Min output gain (dB) */
+#define ECHOGAIN_MAXOUT		(6)	/* Max output gain (dB) */
+#define ECHOGAIN_MININP		(-50)	/* Min input gain (0.5 dB) */
+#define ECHOGAIN_MAXINP		(50)	/* Max input gain (0.5 dB) */
+
+#define PIPE_STATE_STOPPED	0	/* Pipe has been reset */
+#define PIPE_STATE_PAUSED	1	/* Pipe has been stopped */
+#define PIPE_STATE_STARTED	2	/* Pipe has been started */
+#define PIPE_STATE_PENDING	3	/* Pipe has pending start */
+
+
+/* Debug initialization */
+#ifdef CONFIG_SND_DEBUG
+#define DE_INIT(x) snd_printk x
+#else
+#define DE_INIT(x)
+#endif
+
+/* Debug hw_params callbacks */
+#ifdef CONFIG_SND_DEBUG
+#define DE_HWP(x) snd_printk x
+#else
+#define DE_HWP(x)
+#endif
+
+/* Debug normal activity (open, start, stop...) */
+#ifdef CONFIG_SND_DEBUG
+#define DE_ACT(x) snd_printk x
+#else
+#define DE_ACT(x)
+#endif
+
+/* Debug midi activity */
+#ifdef CONFIG_SND_DEBUG
+#define DE_MID(x) snd_printk x
+#else
+#define DE_MID(x)
+#endif
+
+
+struct audiopipe {
+	volatile u32 *dma_counter;	/* Commpage register that contains
+					 * the current dma position
+					 * (lower 32 bits only)
+					 */
+	u32 last_counter;		/* The last position, which is used
+					 * to compute...
+					 */
+	u32 position;			/* ...the number of bytes tranferred
+					 * by the DMA engine, modulo the
+					 * buffer size
+					 */
+	short index;			/* Index of the first channel or <0
+					 * if hw is not configured yet
+					 */
+	short interleave;
+	struct snd_dma_buffer sgpage;	/* Room for the scatter-gather list */
+	struct snd_pcm_hardware hw;
+	struct snd_pcm_hw_constraint_list constr;
+	short sglist_head;
+	char state;			/* pipe state */
+};
+
+
+struct audioformat {
+	u8 interleave;			/* How the data is arranged in memory:
+					 * mono = 1, stereo = 2, ...
+					 */
+	u8 bits_per_sample;		/* 8, 16, 24, 32 (24 bits left aligned) */
+	char mono_to_stereo;		/* Only used if interleave is 1 and
+					 * if this is an output pipe.
+					 */
+	char data_are_bigendian;	/* 1 = big endian, 0 = little endian */
+};
+
+
+struct echoaudio {
+	spinlock_t lock;
+	struct snd_pcm_substream *substream[DSP_MAXPIPES];
+	int last_period[DSP_MAXPIPES];
+	struct mutex mode_mutex;
+	u16 num_digital_modes, digital_mode_list[6];
+	u16 num_clock_sources, clock_source_list[10];
+	atomic_t opencount;
+	struct snd_kcontrol *clock_src_ctl;
+	struct snd_pcm *analog_pcm, *digital_pcm;
+	struct snd_card *card;
+	const char *card_name;
+	struct pci_dev *pci;
+	unsigned long dsp_registers_phys;
+	struct resource *iores;
+	struct snd_dma_buffer commpage_dma_buf;
+	int irq;
+#ifdef ECHOCARD_HAS_MIDI
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *midi_in, *midi_out;
+#endif
+	struct timer_list timer;
+	char tinuse;				/* Timer in use */
+	char midi_full;				/* MIDI output buffer is full */
+	char can_set_rate;
+	char rate_set;
+
+	/* This stuff is used mainly by the lowlevel code */
+	struct comm_page *comm_page;	/* Virtual address of the memory
+					 * seen by DSP
+					 */
+	u32 pipe_alloc_mask;		/* Bitmask of allocated pipes */
+	u32 pipe_cyclic_mask;		/* Bitmask of pipes with cyclic
+					 * buffers
+					 */
+	u32 sample_rate;		/* Card sample rate in Hz */
+	u8 digital_mode;		/* Current digital mode
+					 * (see DIGITAL_MODE_*)
+					 */
+	u8 spdif_status;		/* Gina20, Darla20, Darla24 - only */
+	u8 clock_state;			/* Gina20, Darla20, Darla24 - only */
+	u8 input_clock;			/* Currently selected sample clock
+					 * source
+					 */
+	u8 output_clock;		/* Layla20 only */
+	char meters_enabled;		/* VU-meters status */
+	char asic_loaded;		/* Set TRUE when ASIC loaded */
+	char bad_board;			/* Set TRUE if DSP won't load */
+	char professional_spdif;	/* 0 = consumer; 1 = professional */
+	char non_audio_spdif;		/* 3G - only */
+	char digital_in_automute;	/* Gina24, Layla24, Mona - only */
+	char has_phantom_power;
+	char hasnt_input_nominal_level;	/* Gina3G */
+	char phantom_power;		/* Gina3G - only */
+	char has_midi;
+	char midi_input_enabled;
+
+#ifdef ECHOCARD_ECHO3G
+	/* External module -dependent pipe and bus indexes */
+	char px_digital_out, px_analog_in, px_digital_in, px_num;
+	char bx_digital_out, bx_analog_in, bx_digital_in, bx_num;
+#endif
+
+	char nominal_level[ECHO_MAXAUDIOPIPES];	/* True == -10dBV
+						 * False == +4dBu */
+	s8 input_gain[ECHO_MAXAUDIOINPUTS];	/* Input level -50..+50
+						 * unit is 0.5dB */
+	s8 output_gain[ECHO_MAXAUDIOOUTPUTS];	/* Output level -128..+6 dB
+						 * (-128=muted) */
+	s8 monitor_gain[ECHO_MAXAUDIOOUTPUTS][ECHO_MAXAUDIOINPUTS];
+		/* -128..+6 dB */
+	s8 vmixer_gain[ECHO_MAXAUDIOOUTPUTS][ECHO_MAXAUDIOOUTPUTS];
+		/* -128..+6 dB */
+
+	u16 digital_modes;		/* Bitmask of supported modes
+					 * (see ECHOCAPS_HAS_DIGITAL_MODE_*) */
+	u16 input_clock_types;		/* Suppoted input clock types */
+	u16 output_clock_types;		/* Suppoted output clock types -
+					 * Layla20 only */
+	u16 device_id, subdevice_id;
+	u16 *dsp_code;			/* Current DSP code loaded,
+					 * NULL if nothing loaded */
+	short dsp_code_to_load;		/* DSP code to load */
+	short asic_code;		/* Current ASIC code */
+	u32 comm_page_phys;			/* Physical address of the
+						 * memory seen by DSP */
+	volatile u32 __iomem *dsp_registers;	/* DSP's register base */
+	u32 active_mask;			/* Chs. active mask or
+						 * punks out */
+#ifdef CONFIG_PM
+	const struct firmware *fw_cache[8];	/* Cached firmwares */
+#endif
+
+#ifdef ECHOCARD_HAS_MIDI
+	u16 mtc_state;				/* State for MIDI input parsing state machine */
+	u8 midi_buffer[MIDI_IN_BUFFER_SIZE];
+#endif
+};
+
+
+static int init_dsp_comm_page(struct echoaudio *chip);
+static int init_line_levels(struct echoaudio *chip);
+static int free_pipes(struct echoaudio *chip, struct audiopipe *pipe);
+static int load_firmware(struct echoaudio *chip);
+static int wait_handshake(struct echoaudio *chip);
+static int send_vector(struct echoaudio *chip, u32 command);
+static int get_firmware(const struct firmware **fw_entry,
+			struct echoaudio *chip, const short fw_index);
+static void free_firmware(const struct firmware *fw_entry);
+
+#ifdef ECHOCARD_HAS_MIDI
+static int enable_midi_input(struct echoaudio *chip, char enable);
+static void snd_echo_midi_output_trigger(
+			struct snd_rawmidi_substream *substream, int up);
+static int midi_service_irq(struct echoaudio *chip);
+static int __devinit snd_echo_midi_create(struct snd_card *card,
+					  struct echoaudio *chip);
+#endif
+
+
+static inline void clear_handshake(struct echoaudio *chip)
+{
+	chip->comm_page->handshake = 0;
+}
+
+static inline u32 get_dsp_register(struct echoaudio *chip, u32 index)
+{
+	return readl(&chip->dsp_registers[index]);
+}
+
+static inline void set_dsp_register(struct echoaudio *chip, u32 index,
+				    u32 value)
+{
+	writel(value, &chip->dsp_registers[index]);
+}
+
+
+/* Pipe and bus indexes. PX_* and BX_* are defined as chip->px_* and chip->bx_*
+for 3G cards because they depend on the external box. They are integer
+constants for all other cards.
+Never use those defines directly, use the following functions instead. */
+
+static inline int px_digital_out(const struct echoaudio *chip)
+{
+	return PX_DIGITAL_OUT;
+}
+
+static inline int px_analog_in(const struct echoaudio *chip)
+{
+	return PX_ANALOG_IN;
+}
+
+static inline int px_digital_in(const struct echoaudio *chip)
+{
+	return PX_DIGITAL_IN;
+}
+
+static inline int px_num(const struct echoaudio *chip)
+{
+	return PX_NUM;
+}
+
+static inline int bx_digital_out(const struct echoaudio *chip)
+{
+	return BX_DIGITAL_OUT;
+}
+
+static inline int bx_analog_in(const struct echoaudio *chip)
+{
+	return BX_ANALOG_IN;
+}
+
+static inline int bx_digital_in(const struct echoaudio *chip)
+{
+	return BX_DIGITAL_IN;
+}
+
+static inline int bx_num(const struct echoaudio *chip)
+{
+	return BX_NUM;
+}
+
+static inline int num_pipes_out(const struct echoaudio *chip)
+{
+	return px_analog_in(chip);
+}
+
+static inline int num_pipes_in(const struct echoaudio *chip)
+{
+	return px_num(chip) - px_analog_in(chip);
+}
+
+static inline int num_busses_out(const struct echoaudio *chip)
+{
+	return bx_analog_in(chip);
+}
+
+static inline int num_busses_in(const struct echoaudio *chip)
+{
+	return bx_num(chip) - bx_analog_in(chip);
+}
+
+static inline int num_analog_busses_out(const struct echoaudio *chip)
+{
+	return bx_digital_out(chip);
+}
+
+static inline int num_analog_busses_in(const struct echoaudio *chip)
+{
+	return bx_digital_in(chip) - bx_analog_in(chip);
+}
+
+static inline int num_digital_busses_out(const struct echoaudio *chip)
+{
+	return num_busses_out(chip) - num_analog_busses_out(chip);
+}
+
+static inline int num_digital_busses_in(const struct echoaudio *chip)
+{
+	return num_busses_in(chip) - num_analog_busses_in(chip);
+}
+
+/* The monitor array is a one-dimensional array; compute the offset
+ * into the array */
+static inline int monitor_index(const struct echoaudio *chip, int out, int in)
+{
+	return out * num_busses_in(chip) + in;
+}
+
+
+#ifndef pci_device
+#define pci_device(chip) (&chip->pci->dev)
+#endif
+
+
+#endif /* _ECHOAUDIO_H_ */
diff --git a/linux/sound/pci/echoaudio/echoaudio_3g.c b/linux/sound/pci/echoaudio/echoaudio_3g.c
new file mode 100644
index 0000000..658db44
--- /dev/null
+++ b/linux/sound/pci/echoaudio/echoaudio_3g.c
@@ -0,0 +1,432 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+
+/* These functions are common for all "3G" cards */
+
+
+static int check_asic_status(struct echoaudio *chip)
+{
+	u32 box_status;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->comm_page->ext_box_status = cpu_to_le32(E3G_ASIC_NOT_LOADED);
+	chip->asic_loaded = FALSE;
+	clear_handshake(chip);
+	send_vector(chip, DSP_VC_TEST_ASIC);
+
+	if (wait_handshake(chip)) {
+		chip->dsp_code = NULL;
+		return -EIO;
+	}
+
+	box_status = le32_to_cpu(chip->comm_page->ext_box_status);
+	DE_INIT(("box_status=%x\n", box_status));
+	if (box_status == E3G_ASIC_NOT_LOADED)
+		return -ENODEV;
+
+	chip->asic_loaded = TRUE;
+	return box_status & E3G_BOX_TYPE_MASK;
+}
+
+
+
+static inline u32 get_frq_reg(struct echoaudio *chip)
+{
+	return le32_to_cpu(chip->comm_page->e3g_frq_register);
+}
+
+
+
+/* Most configuration of 3G cards is accomplished by writing the control
+register. write_control_reg sends the new control register value to the DSP. */
+static int write_control_reg(struct echoaudio *chip, u32 ctl, u32 frq,
+			     char force)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+
+	DE_ACT(("WriteControlReg: Setting 0x%x, 0x%x\n", ctl, frq));
+
+	ctl = cpu_to_le32(ctl);
+	frq = cpu_to_le32(frq);
+
+	if (ctl != chip->comm_page->control_register ||
+	    frq != chip->comm_page->e3g_frq_register || force) {
+		chip->comm_page->e3g_frq_register = frq;
+		chip->comm_page->control_register = ctl;
+		clear_handshake(chip);
+		return send_vector(chip, DSP_VC_WRITE_CONTROL_REG);
+	}
+
+	DE_ACT(("WriteControlReg: not written, no change\n"));
+	return 0;
+}
+
+
+
+/* Set the digital mode - currently for Gina24, Layla24, Mona, 3G */
+static int set_digital_mode(struct echoaudio *chip, u8 mode)
+{
+	u8 previous_mode;
+	int err, i, o;
+
+	/* All audio channels must be closed before changing the digital mode */
+	if (snd_BUG_ON(chip->pipe_alloc_mask))
+		return -EAGAIN;
+
+	if (snd_BUG_ON(!(chip->digital_modes & (1 << mode))))
+		return -EINVAL;
+
+	previous_mode = chip->digital_mode;
+	err = dsp_set_digital_mode(chip, mode);
+
+	/* If we successfully changed the digital mode from or to ADAT,
+	 * then make sure all output, input and monitor levels are
+	 * updated by the DSP comm object. */
+	if (err >= 0 && previous_mode != mode &&
+	    (previous_mode == DIGITAL_MODE_ADAT || mode == DIGITAL_MODE_ADAT)) {
+		spin_lock_irq(&chip->lock);
+		for (o = 0; o < num_busses_out(chip); o++)
+			for (i = 0; i < num_busses_in(chip); i++)
+				set_monitor_gain(chip, o, i,
+						 chip->monitor_gain[o][i]);
+
+#ifdef ECHOCARD_HAS_INPUT_GAIN
+		for (i = 0; i < num_busses_in(chip); i++)
+			set_input_gain(chip, i, chip->input_gain[i]);
+		update_input_line_level(chip);
+#endif
+
+		for (o = 0; o < num_busses_out(chip); o++)
+			set_output_gain(chip, o, chip->output_gain[o]);
+		update_output_line_level(chip);
+		spin_unlock_irq(&chip->lock);
+	}
+
+	return err;
+}
+
+
+
+static u32 set_spdif_bits(struct echoaudio *chip, u32 control_reg, u32 rate)
+{
+	control_reg &= E3G_SPDIF_FORMAT_CLEAR_MASK;
+
+	switch (rate) {
+	case 32000 :
+		control_reg |= E3G_SPDIF_SAMPLE_RATE0 | E3G_SPDIF_SAMPLE_RATE1;
+		break;
+	case 44100 :
+		if (chip->professional_spdif)
+			control_reg |= E3G_SPDIF_SAMPLE_RATE0;
+		break;
+	case 48000 :
+		control_reg |= E3G_SPDIF_SAMPLE_RATE1;
+		break;
+	}
+
+	if (chip->professional_spdif)
+		control_reg |= E3G_SPDIF_PRO_MODE;
+
+	if (chip->non_audio_spdif)
+		control_reg |= E3G_SPDIF_NOT_AUDIO;
+
+	control_reg |= E3G_SPDIF_24_BIT | E3G_SPDIF_TWO_CHANNEL |
+		E3G_SPDIF_COPY_PERMIT;
+
+	return control_reg;
+}
+
+
+
+/* Set the S/PDIF output format */
+static int set_professional_spdif(struct echoaudio *chip, char prof)
+{
+	u32 control_reg;
+
+	control_reg = le32_to_cpu(chip->comm_page->control_register);
+	chip->professional_spdif = prof;
+	control_reg = set_spdif_bits(chip, control_reg, chip->sample_rate);
+	return write_control_reg(chip, control_reg, get_frq_reg(chip), 0);
+}
+
+
+
+/* detect_input_clocks() returns a bitmask consisting of all the input clocks
+currently connected to the hardware; this changes as the user connects and
+disconnects clock inputs. You should use this information to determine which
+clocks the user is allowed to select. */
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	u32 clocks_from_dsp, clock_bits;
+
+	/* Map the DSP clock detect bits to the generic driver clock
+	 * detect bits */
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	clock_bits = ECHO_CLOCK_BIT_INTERNAL;
+
+	if (clocks_from_dsp & E3G_CLOCK_DETECT_BIT_WORD)
+		clock_bits |= ECHO_CLOCK_BIT_WORD;
+
+	switch(chip->digital_mode) {
+	case DIGITAL_MODE_SPDIF_RCA:
+	case DIGITAL_MODE_SPDIF_OPTICAL:
+		if (clocks_from_dsp & E3G_CLOCK_DETECT_BIT_SPDIF)
+			clock_bits |= ECHO_CLOCK_BIT_SPDIF;
+		break;
+	case DIGITAL_MODE_ADAT:
+		if (clocks_from_dsp & E3G_CLOCK_DETECT_BIT_ADAT)
+			clock_bits |= ECHO_CLOCK_BIT_ADAT;
+		break;
+	}
+
+	return clock_bits;
+}
+
+
+
+static int load_asic(struct echoaudio *chip)
+{
+	int box_type, err;
+
+	if (chip->asic_loaded)
+		return 0;
+
+	/* Give the DSP a few milliseconds to settle down */
+	mdelay(2);
+
+	err = load_asic_generic(chip, DSP_FNC_LOAD_3G_ASIC, FW_3G_ASIC);
+	if (err < 0)
+		return err;
+
+	chip->asic_code = FW_3G_ASIC;
+
+	/* Now give the new ASIC some time to set up */
+	msleep(1000);
+	/* See if it worked */
+	box_type = check_asic_status(chip);
+
+	/* Set up the control register if the load succeeded -
+	 * 48 kHz, internal clock, S/PDIF RCA mode */
+	if (box_type >= 0) {
+		err = write_control_reg(chip, E3G_48KHZ,
+					E3G_FREQ_REG_DEFAULT, TRUE);
+		if (err < 0)
+			return err;
+	}
+
+	return box_type;
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	u32 control_reg, clock, base_rate, frq_reg;
+
+	/* Only set the clock for internal mode. */
+	if (chip->input_clock != ECHO_CLOCK_INTERNAL) {
+		DE_ACT(("set_sample_rate: Cannot set sample rate - "
+			"clock not set to CLK_CLOCKININTERNAL\n"));
+		/* Save the rate anyhow */
+		chip->comm_page->sample_rate = cpu_to_le32(rate);
+		chip->sample_rate = rate;
+		set_input_clock(chip, chip->input_clock);
+		return 0;
+	}
+
+	if (snd_BUG_ON(rate >= 50000 &&
+		       chip->digital_mode == DIGITAL_MODE_ADAT))
+		return -EINVAL;
+
+	clock = 0;
+	control_reg = le32_to_cpu(chip->comm_page->control_register);
+	control_reg &= E3G_CLOCK_CLEAR_MASK;
+
+	switch (rate) {
+	case 96000:
+		clock = E3G_96KHZ;
+		break;
+	case 88200:
+		clock = E3G_88KHZ;
+		break;
+	case 48000:
+		clock = E3G_48KHZ;
+		break;
+	case 44100:
+		clock = E3G_44KHZ;
+		break;
+	case 32000:
+		clock = E3G_32KHZ;
+		break;
+	default:
+		clock = E3G_CONTINUOUS_CLOCK;
+		if (rate > 50000)
+			clock |= E3G_DOUBLE_SPEED_MODE;
+		break;
+	}
+
+	control_reg |= clock;
+	control_reg = set_spdif_bits(chip, control_reg, rate);
+
+	base_rate = rate;
+	if (base_rate > 50000)
+		base_rate /= 2;
+	if (base_rate < 32000)
+		base_rate = 32000;
+
+	frq_reg = E3G_MAGIC_NUMBER / base_rate - 2;
+	if (frq_reg > E3G_FREQ_REG_MAX)
+		frq_reg = E3G_FREQ_REG_MAX;
+
+	chip->comm_page->sample_rate = cpu_to_le32(rate);	/* ignored by the DSP */
+	chip->sample_rate = rate;
+	DE_ACT(("SetSampleRate: %d clock %x\n", rate, control_reg));
+
+	/* Tell the DSP about it - DSP reads both control reg & freq reg */
+	return write_control_reg(chip, control_reg, frq_reg, 0);
+}
+
+
+
+/* Set the sample clock source to internal, S/PDIF, ADAT */
+static int set_input_clock(struct echoaudio *chip, u16 clock)
+{
+	u32 control_reg, clocks_from_dsp;
+
+	DE_ACT(("set_input_clock:\n"));
+
+	/* Mask off the clock select bits */
+	control_reg = le32_to_cpu(chip->comm_page->control_register) &
+		E3G_CLOCK_CLEAR_MASK;
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	switch (clock) {
+	case ECHO_CLOCK_INTERNAL:
+		DE_ACT(("Set Echo3G clock to INTERNAL\n"));
+		chip->input_clock = ECHO_CLOCK_INTERNAL;
+		return set_sample_rate(chip, chip->sample_rate);
+	case ECHO_CLOCK_SPDIF:
+		if (chip->digital_mode == DIGITAL_MODE_ADAT)
+			return -EAGAIN;
+		DE_ACT(("Set Echo3G clock to SPDIF\n"));
+		control_reg |= E3G_SPDIF_CLOCK;
+		if (clocks_from_dsp & E3G_CLOCK_DETECT_BIT_SPDIF96)
+			control_reg |= E3G_DOUBLE_SPEED_MODE;
+		else
+			control_reg &= ~E3G_DOUBLE_SPEED_MODE;
+		break;
+	case ECHO_CLOCK_ADAT:
+		if (chip->digital_mode != DIGITAL_MODE_ADAT)
+			return -EAGAIN;
+		DE_ACT(("Set Echo3G clock to ADAT\n"));
+		control_reg |= E3G_ADAT_CLOCK;
+		control_reg &= ~E3G_DOUBLE_SPEED_MODE;
+		break;
+	case ECHO_CLOCK_WORD:
+		DE_ACT(("Set Echo3G clock to WORD\n"));
+		control_reg |= E3G_WORD_CLOCK;
+		if (clocks_from_dsp & E3G_CLOCK_DETECT_BIT_WORD96)
+			control_reg |= E3G_DOUBLE_SPEED_MODE;
+		else
+			control_reg &= ~E3G_DOUBLE_SPEED_MODE;
+		break;
+	default:
+		DE_ACT(("Input clock 0x%x not supported for Echo3G\n", clock));
+		return -EINVAL;
+	}
+
+	chip->input_clock = clock;
+	return write_control_reg(chip, control_reg, get_frq_reg(chip), 1);
+}
+
+
+
+static int dsp_set_digital_mode(struct echoaudio *chip, u8 mode)
+{
+	u32 control_reg;
+	int err, incompatible_clock;
+
+	/* Set clock to "internal" if it's not compatible with the new mode */
+	incompatible_clock = FALSE;
+	switch (mode) {
+	case DIGITAL_MODE_SPDIF_OPTICAL:
+	case DIGITAL_MODE_SPDIF_RCA:
+		if (chip->input_clock == ECHO_CLOCK_ADAT)
+			incompatible_clock = TRUE;
+		break;
+	case DIGITAL_MODE_ADAT:
+		if (chip->input_clock == ECHO_CLOCK_SPDIF)
+			incompatible_clock = TRUE;
+		break;
+	default:
+		DE_ACT(("Digital mode not supported: %d\n", mode));
+		return -EINVAL;
+	}
+
+	spin_lock_irq(&chip->lock);
+
+	if (incompatible_clock) {
+		chip->sample_rate = 48000;
+		set_input_clock(chip, ECHO_CLOCK_INTERNAL);
+	}
+
+	/* Clear the current digital mode */
+	control_reg = le32_to_cpu(chip->comm_page->control_register);
+	control_reg &= E3G_DIGITAL_MODE_CLEAR_MASK;
+
+	/* Tweak the control reg */
+	switch (mode) {
+	case DIGITAL_MODE_SPDIF_OPTICAL:
+		control_reg |= E3G_SPDIF_OPTICAL_MODE;
+		break;
+	case DIGITAL_MODE_SPDIF_RCA:
+		/* E3G_SPDIF_OPTICAL_MODE bit cleared */
+		break;
+	case DIGITAL_MODE_ADAT:
+		control_reg |= E3G_ADAT_MODE;
+		control_reg &= ~E3G_DOUBLE_SPEED_MODE;	/* @@ useless */
+		break;
+	}
+
+	err = write_control_reg(chip, control_reg, get_frq_reg(chip), 1);
+	spin_unlock_irq(&chip->lock);
+	if (err < 0)
+		return err;
+	chip->digital_mode = mode;
+
+	DE_ACT(("set_digital_mode(%d)\n", chip->digital_mode));
+	return incompatible_clock;
+}
diff --git a/linux/sound/pci/echoaudio/echoaudio_dsp.c b/linux/sound/pci/echoaudio/echoaudio_dsp.c
new file mode 100644
index 0000000..64417a7
--- /dev/null
+++ b/linux/sound/pci/echoaudio/echoaudio_dsp.c
@@ -0,0 +1,1151 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+#if PAGE_SIZE < 4096
+#error PAGE_SIZE is < 4k
+#endif
+
+static int restore_dsp_rettings(struct echoaudio *chip);
+
+
+/* Some vector commands involve the DSP reading or writing data to and from the
+comm page; if you send one of these commands to the DSP, it will complete the
+command and then write a non-zero value to the Handshake field in the
+comm page.  This function waits for the handshake to show up. */
+static int wait_handshake(struct echoaudio *chip)
+{
+	int i;
+
+	/* Wait up to 20ms for the handshake from the DSP */
+	for (i = 0; i < HANDSHAKE_TIMEOUT; i++) {
+		/* Look for the handshake value */
+		barrier();
+		if (chip->comm_page->handshake) {
+			return 0;
+		}
+		udelay(1);
+	}
+
+	snd_printk(KERN_ERR "wait_handshake(): Timeout waiting for DSP\n");
+	return -EBUSY;
+}
+
+
+
+/* Much of the interaction between the DSP and the driver is done via vector
+commands; send_vector writes a vector command to the DSP.  Typically, this
+causes the DSP to read or write fields in the comm page.
+PCI posting is not required thanks to the handshake logic. */
+static int send_vector(struct echoaudio *chip, u32 command)
+{
+	int i;
+
+	wmb();	/* Flush all pending writes before sending the command */
+
+	/* Wait up to 100ms for the "vector busy" bit to be off */
+	for (i = 0; i < VECTOR_BUSY_TIMEOUT; i++) {
+		if (!(get_dsp_register(chip, CHI32_VECTOR_REG) &
+		      CHI32_VECTOR_BUSY)) {
+			set_dsp_register(chip, CHI32_VECTOR_REG, command);
+			/*if (i)  DE_ACT(("send_vector time: %d\n", i));*/
+			return 0;
+		}
+		udelay(1);
+	}
+
+	DE_ACT((KERN_ERR "timeout on send_vector\n"));
+	return -EBUSY;
+}
+
+
+
+/* write_dsp writes a 32-bit value to the DSP; this is used almost
+exclusively for loading the DSP. */
+static int write_dsp(struct echoaudio *chip, u32 data)
+{
+	u32 status, i;
+
+	for (i = 0; i < 10000000; i++) {	/* timeout = 10s */
+		status = get_dsp_register(chip, CHI32_STATUS_REG);
+		if ((status & CHI32_STATUS_HOST_WRITE_EMPTY) != 0) {
+			set_dsp_register(chip, CHI32_DATA_REG, data);
+			wmb();			/* write it immediately */
+			return 0;
+		}
+		udelay(1);
+		cond_resched();
+	}
+
+	chip->bad_board = TRUE;		/* Set TRUE until DSP re-loaded */
+	DE_ACT((KERN_ERR "write_dsp: Set bad_board to TRUE\n"));
+	return -EIO;
+}
+
+
+
+/* read_dsp reads a 32-bit value from the DSP; this is used almost
+exclusively for loading the DSP and checking the status of the ASIC. */
+static int read_dsp(struct echoaudio *chip, u32 *data)
+{
+	u32 status, i;
+
+	for (i = 0; i < READ_DSP_TIMEOUT; i++) {
+		status = get_dsp_register(chip, CHI32_STATUS_REG);
+		if ((status & CHI32_STATUS_HOST_READ_FULL) != 0) {
+			*data = get_dsp_register(chip, CHI32_DATA_REG);
+			return 0;
+		}
+		udelay(1);
+		cond_resched();
+	}
+
+	chip->bad_board = TRUE;		/* Set TRUE until DSP re-loaded */
+	DE_INIT((KERN_ERR "read_dsp: Set bad_board to TRUE\n"));
+	return -EIO;
+}
+
+
+
+/****************************************************************************
+	Firmware loading functions
+ ****************************************************************************/
+
+/* This function is used to read back the serial number from the DSP;
+this is triggered by the SET_COMMPAGE_ADDR command.
+Only some early Echogals products have serial numbers in the ROM;
+the serial number is not used, but you still need to do this as
+part of the DSP load process. */
+static int read_sn(struct echoaudio *chip)
+{
+	int i;
+	u32 sn[6];
+
+	for (i = 0; i < 5; i++) {
+		if (read_dsp(chip, &sn[i])) {
+			snd_printk(KERN_ERR "Failed to read serial number\n");
+			return -EIO;
+		}
+	}
+	DE_INIT(("Read serial number %08x %08x %08x %08x %08x\n",
+		 sn[0], sn[1], sn[2], sn[3], sn[4]));
+	return 0;
+}
+
+
+
+#ifndef ECHOCARD_HAS_ASIC
+/* This card has no ASIC, just return ok */
+static inline int check_asic_status(struct echoaudio *chip)
+{
+	chip->asic_loaded = TRUE;
+	return 0;
+}
+
+#endif /* !ECHOCARD_HAS_ASIC */
+
+
+
+#ifdef ECHOCARD_HAS_ASIC
+
+/* Load ASIC code - done after the DSP is loaded */
+static int load_asic_generic(struct echoaudio *chip, u32 cmd, short asic)
+{
+	const struct firmware *fw;
+	int err;
+	u32 i, size;
+	u8 *code;
+
+	err = get_firmware(&fw, chip, asic);
+	if (err < 0) {
+		snd_printk(KERN_WARNING "Firmware not found !\n");
+		return err;
+	}
+
+	code = (u8 *)fw->data;
+	size = fw->size;
+
+	/* Send the "Here comes the ASIC" command */
+	if (write_dsp(chip, cmd) < 0)
+		goto la_error;
+
+	/* Write length of ASIC file in bytes */
+	if (write_dsp(chip, size) < 0)
+		goto la_error;
+
+	for (i = 0; i < size; i++) {
+		if (write_dsp(chip, code[i]) < 0)
+			goto la_error;
+	}
+
+	DE_INIT(("ASIC loaded\n"));
+	free_firmware(fw);
+	return 0;
+
+la_error:
+	DE_INIT(("failed on write_dsp\n"));
+	free_firmware(fw);
+	return -EIO;
+}
+
+#endif /* ECHOCARD_HAS_ASIC */
+
+
+
+#ifdef DSP_56361
+
+/* Install the resident loader for 56361 DSPs;  The resident loader is on
+the EPROM on the board for 56301 DSP. The resident loader is a tiny little
+program that is used to load the real DSP code. */
+static int install_resident_loader(struct echoaudio *chip)
+{
+	u32 address;
+	int index, words, i;
+	u16 *code;
+	u32 status;
+	const struct firmware *fw;
+
+	/* 56361 cards only!  This check is required by the old 56301-based
+	Mona and Gina24 */
+	if (chip->device_id != DEVICE_ID_56361)
+		return 0;
+
+	/* Look to see if the resident loader is present.  If the resident
+	loader is already installed, host flag 5 will be on. */
+	status = get_dsp_register(chip, CHI32_STATUS_REG);
+	if (status & CHI32_STATUS_REG_HF5) {
+		DE_INIT(("Resident loader already installed; status is 0x%x\n",
+			 status));
+		return 0;
+	}
+
+	i = get_firmware(&fw, chip, FW_361_LOADER);
+	if (i < 0) {
+		snd_printk(KERN_WARNING "Firmware not found !\n");
+		return i;
+	}
+
+	/* The DSP code is an array of 16 bit words.  The array is divided up
+	into sections.  The first word of each section is the size in words,
+	followed by the section type.
+	Since DSP addresses and data are 24 bits wide, they each take up two
+	16 bit words in the array.
+	This is a lot like the other loader loop, but it's not a loop, you
+	don't write the memory type, and you don't write a zero at the end. */
+
+	/* Set DSP format bits for 24 bit mode */
+	set_dsp_register(chip, CHI32_CONTROL_REG,
+			 get_dsp_register(chip, CHI32_CONTROL_REG) | 0x900);
+
+	code = (u16 *)fw->data;
+
+	/* Skip the header section; the first word in the array is the size
+	of the first section, so the first real section of code is pointed
+	to by Code[0]. */
+	index = code[0];
+
+	/* Skip the section size, LRS block type, and DSP memory type */
+	index += 3;
+
+	/* Get the number of DSP words to write */
+	words = code[index++];
+
+	/* Get the DSP address for this block; 24 bits, so build from two words */
+	address = ((u32)code[index] << 16) + code[index + 1];
+	index += 2;
+
+	/* Write the count to the DSP */
+	if (write_dsp(chip, words)) {
+		DE_INIT(("install_resident_loader: Failed to write word count!\n"));
+		goto irl_error;
+	}
+	/* Write the DSP address */
+	if (write_dsp(chip, address)) {
+		DE_INIT(("install_resident_loader: Failed to write DSP address!\n"));
+		goto irl_error;
+	}
+	/* Write out this block of code to the DSP */
+	for (i = 0; i < words; i++) {
+		u32 data;
+
+		data = ((u32)code[index] << 16) + code[index + 1];
+		if (write_dsp(chip, data)) {
+			DE_INIT(("install_resident_loader: Failed to write DSP code\n"));
+			goto irl_error;
+		}
+		index += 2;
+	}
+
+	/* Wait for flag 5 to come up */
+	for (i = 0; i < 200; i++) {	/* Timeout is 50us * 200 = 10ms */
+		udelay(50);
+		status = get_dsp_register(chip, CHI32_STATUS_REG);
+		if (status & CHI32_STATUS_REG_HF5)
+			break;
+	}
+
+	if (i == 200) {
+		DE_INIT(("Resident loader failed to set HF5\n"));
+		goto irl_error;
+	}
+
+	DE_INIT(("Resident loader successfully installed\n"));
+	free_firmware(fw);
+	return 0;
+
+irl_error:
+	free_firmware(fw);
+	return -EIO;
+}
+
+#endif /* DSP_56361 */
+
+
+static int load_dsp(struct echoaudio *chip, u16 *code)
+{
+	u32 address, data;
+	int index, words, i;
+
+	if (chip->dsp_code == code) {
+		DE_INIT(("DSP is already loaded!\n"));
+		return 0;
+	}
+	chip->bad_board = TRUE;		/* Set TRUE until DSP loaded */
+	chip->dsp_code = NULL;		/* Current DSP code not loaded */
+	chip->asic_loaded = FALSE;	/* Loading the DSP code will reset the ASIC */
+
+	DE_INIT(("load_dsp: Set bad_board to TRUE\n"));
+
+	/* If this board requires a resident loader, install it. */
+#ifdef DSP_56361
+	if ((i = install_resident_loader(chip)) < 0)
+		return i;
+#endif
+
+	/* Send software reset command */
+	if (send_vector(chip, DSP_VC_RESET) < 0) {
+		DE_INIT(("LoadDsp: send_vector DSP_VC_RESET failed, Critical Failure\n"));
+		return -EIO;
+	}
+	/* Delay 10us */
+	udelay(10);
+
+	/* Wait 10ms for HF3 to indicate that software reset is complete */
+	for (i = 0; i < 1000; i++) {	/* Timeout is 10us * 1000 = 10ms */
+		if (get_dsp_register(chip, CHI32_STATUS_REG) &
+		    CHI32_STATUS_REG_HF3)
+			break;
+		udelay(10);
+	}
+
+	if (i == 1000) {
+		DE_INIT(("load_dsp: Timeout waiting for CHI32_STATUS_REG_HF3\n"));
+		return -EIO;
+	}
+
+	/* Set DSP format bits for 24 bit mode now that soft reset is done */
+	set_dsp_register(chip, CHI32_CONTROL_REG,
+			 get_dsp_register(chip, CHI32_CONTROL_REG) | 0x900);
+
+	/* Main loader loop */
+
+	index = code[0];
+	for (;;) {
+		int block_type, mem_type;
+
+		/* Total Block Size */
+		index++;
+
+		/* Block Type */
+		block_type = code[index];
+		if (block_type == 4)	/* We're finished */
+			break;
+
+		index++;
+
+		/* Memory Type  P=0,X=1,Y=2 */
+		mem_type = code[index++];
+
+		/* Block Code Size */
+		words = code[index++];
+		if (words == 0)		/* We're finished */
+			break;
+
+		/* Start Address */
+		address = ((u32)code[index] << 16) + code[index + 1];
+		index += 2;
+
+		if (write_dsp(chip, words) < 0) {
+			DE_INIT(("load_dsp: failed to write number of DSP words\n"));
+			return -EIO;
+		}
+		if (write_dsp(chip, address) < 0) {
+			DE_INIT(("load_dsp: failed to write DSP address\n"));
+			return -EIO;
+		}
+		if (write_dsp(chip, mem_type) < 0) {
+			DE_INIT(("load_dsp: failed to write DSP memory type\n"));
+			return -EIO;
+		}
+		/* Code */
+		for (i = 0; i < words; i++, index+=2) {
+			data = ((u32)code[index] << 16) + code[index + 1];
+			if (write_dsp(chip, data) < 0) {
+				DE_INIT(("load_dsp: failed to write DSP data\n"));
+				return -EIO;
+			}
+		}
+	}
+
+	if (write_dsp(chip, 0) < 0) {	/* We're done!!! */
+		DE_INIT(("load_dsp: Failed to write final zero\n"));
+		return -EIO;
+	}
+	udelay(10);
+
+	for (i = 0; i < 5000; i++) {	/* Timeout is 100us * 5000 = 500ms */
+		/* Wait for flag 4 - indicates that the DSP loaded OK */
+		if (get_dsp_register(chip, CHI32_STATUS_REG) &
+		    CHI32_STATUS_REG_HF4) {
+			set_dsp_register(chip, CHI32_CONTROL_REG,
+					 get_dsp_register(chip, CHI32_CONTROL_REG) & ~0x1b00);
+
+			if (write_dsp(chip, DSP_FNC_SET_COMMPAGE_ADDR) < 0) {
+				DE_INIT(("load_dsp: Failed to write DSP_FNC_SET_COMMPAGE_ADDR\n"));
+				return -EIO;
+			}
+
+			if (write_dsp(chip, chip->comm_page_phys) < 0) {
+				DE_INIT(("load_dsp: Failed to write comm page address\n"));
+				return -EIO;
+			}
+
+			/* Get the serial number via slave mode.
+			This is triggered by the SET_COMMPAGE_ADDR command.
+			We don't actually use the serial number but we have to
+			get it as part of the DSP init voodoo. */
+			if (read_sn(chip) < 0) {
+				DE_INIT(("load_dsp: Failed to read serial number\n"));
+				return -EIO;
+			}
+
+			chip->dsp_code = code;		/* Show which DSP code loaded */
+			chip->bad_board = FALSE;	/* DSP OK */
+			DE_INIT(("load_dsp: OK!\n"));
+			return 0;
+		}
+		udelay(100);
+	}
+
+	DE_INIT(("load_dsp: DSP load timed out waiting for HF4\n"));
+	return -EIO;
+}
+
+
+
+/* load_firmware takes care of loading the DSP and any ASIC code. */
+static int load_firmware(struct echoaudio *chip)
+{
+	const struct firmware *fw;
+	int box_type, err;
+
+	if (snd_BUG_ON(!chip->dsp_code_to_load || !chip->comm_page))
+		return -EPERM;
+
+	/* See if the ASIC is present and working - only if the DSP is already loaded */
+	if (chip->dsp_code) {
+		if ((box_type = check_asic_status(chip)) >= 0)
+			return box_type;
+		/* ASIC check failed; force the DSP to reload */
+		chip->dsp_code = NULL;
+	}
+
+	err = get_firmware(&fw, chip, chip->dsp_code_to_load);
+	if (err < 0)
+		return err;
+	err = load_dsp(chip, (u16 *)fw->data);
+	free_firmware(fw);
+	if (err < 0)
+		return err;
+
+	if ((box_type = load_asic(chip)) < 0)
+		return box_type;	/* error */
+
+	return box_type;
+}
+
+
+
+/****************************************************************************
+	Mixer functions
+ ****************************************************************************/
+
+#if defined(ECHOCARD_HAS_INPUT_NOMINAL_LEVEL) || \
+	defined(ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL)
+
+/* Set the nominal level for an input or output bus (true = -10dBV, false = +4dBu) */
+static int set_nominal_level(struct echoaudio *chip, u16 index, char consumer)
+{
+	if (snd_BUG_ON(index >= num_busses_out(chip) + num_busses_in(chip)))
+		return -EINVAL;
+
+	/* Wait for the handshake (OK even if ASIC is not loaded) */
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->nominal_level[index] = consumer;
+
+	if (consumer)
+		chip->comm_page->nominal_level_mask |= cpu_to_le32(1 << index);
+	else
+		chip->comm_page->nominal_level_mask &= ~cpu_to_le32(1 << index);
+
+	return 0;
+}
+
+#endif /* ECHOCARD_HAS_*_NOMINAL_LEVEL */
+
+
+
+/* Set the gain for a single physical output channel (dB). */
+static int set_output_gain(struct echoaudio *chip, u16 channel, s8 gain)
+{
+	if (snd_BUG_ON(channel >= num_busses_out(chip)))
+		return -EINVAL;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	/* Save the new value */
+	chip->output_gain[channel] = gain;
+	chip->comm_page->line_out_level[channel] = gain;
+	return 0;
+}
+
+
+
+#ifdef ECHOCARD_HAS_MONITOR
+/* Set the monitor level from an input bus to an output bus. */
+static int set_monitor_gain(struct echoaudio *chip, u16 output, u16 input,
+			    s8 gain)
+{
+	if (snd_BUG_ON(output >= num_busses_out(chip) ||
+		    input >= num_busses_in(chip)))
+		return -EINVAL;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->monitor_gain[output][input] = gain;
+	chip->comm_page->monitors[monitor_index(chip, output, input)] = gain;
+	return 0;
+}
+#endif /* ECHOCARD_HAS_MONITOR */
+
+
+/* Tell the DSP to read and update output, nominal & monitor levels in comm page. */
+static int update_output_line_level(struct echoaudio *chip)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_UPDATE_OUTVOL);
+}
+
+
+
+/* Tell the DSP to read and update input levels in comm page */
+static int update_input_line_level(struct echoaudio *chip)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_UPDATE_INGAIN);
+}
+
+
+
+/* set_meters_on turns the meters on or off.  If meters are turned on, the DSP
+will write the meter and clock detect values to the comm page at about 30Hz */
+static void set_meters_on(struct echoaudio *chip, char on)
+{
+	if (on && !chip->meters_enabled) {
+		send_vector(chip, DSP_VC_METERS_ON);
+		chip->meters_enabled = 1;
+	} else if (!on && chip->meters_enabled) {
+		send_vector(chip, DSP_VC_METERS_OFF);
+		chip->meters_enabled = 0;
+		memset((s8 *)chip->comm_page->vu_meter, ECHOGAIN_MUTED,
+		       DSP_MAXPIPES);
+		memset((s8 *)chip->comm_page->peak_meter, ECHOGAIN_MUTED,
+		       DSP_MAXPIPES);
+	}
+}
+
+
+
+/* Fill out an the given array using the current values in the comm page.
+Meters are written in the comm page by the DSP in this order:
+ Output busses
+ Input busses
+ Output pipes (vmixer cards only)
+
+This function assumes there are no more than 16 in/out busses or pipes
+Meters is an array [3][16][2] of long. */
+static void get_audio_meters(struct echoaudio *chip, long *meters)
+{
+	int i, m, n;
+
+	m = 0;
+	n = 0;
+	for (i = 0; i < num_busses_out(chip); i++, m++) {
+		meters[n++] = chip->comm_page->vu_meter[m];
+		meters[n++] = chip->comm_page->peak_meter[m];
+	}
+	for (; n < 32; n++)
+		meters[n] = 0;
+
+#ifdef ECHOCARD_ECHO3G
+	m = E3G_MAX_OUTPUTS;	/* Skip unused meters */
+#endif
+
+	for (i = 0; i < num_busses_in(chip); i++, m++) {
+		meters[n++] = chip->comm_page->vu_meter[m];
+		meters[n++] = chip->comm_page->peak_meter[m];
+	}
+	for (; n < 64; n++)
+		meters[n] = 0;
+
+#ifdef ECHOCARD_HAS_VMIXER
+	for (i = 0; i < num_pipes_out(chip); i++, m++) {
+		meters[n++] = chip->comm_page->vu_meter[m];
+		meters[n++] = chip->comm_page->peak_meter[m];
+	}
+#endif
+	for (; n < 96; n++)
+		meters[n] = 0;
+}
+
+
+
+static int restore_dsp_rettings(struct echoaudio *chip)
+{
+	int i, o, err;
+	DE_INIT(("restore_dsp_settings\n"));
+
+	if ((err = check_asic_status(chip)) < 0)
+		return err;
+
+	/* Gina20/Darla20 only. Should be harmless for other cards. */
+	chip->comm_page->gd_clock_state = GD_CLOCK_UNDEF;
+	chip->comm_page->gd_spdif_status = GD_SPDIF_STATUS_UNDEF;
+	chip->comm_page->handshake = 0xffffffff;
+
+	/* Restore output busses */
+	for (i = 0; i < num_busses_out(chip); i++) {
+		err = set_output_gain(chip, i, chip->output_gain[i]);
+		if (err < 0)
+			return err;
+	}
+
+#ifdef ECHOCARD_HAS_VMIXER
+	for (i = 0; i < num_pipes_out(chip); i++)
+		for (o = 0; o < num_busses_out(chip); o++) {
+			err = set_vmixer_gain(chip, o, i,
+						chip->vmixer_gain[o][i]);
+			if (err < 0)
+				return err;
+		}
+	if (update_vmixer_level(chip) < 0)
+		return -EIO;
+#endif /* ECHOCARD_HAS_VMIXER */
+
+#ifdef ECHOCARD_HAS_MONITOR
+	for (o = 0; o < num_busses_out(chip); o++)
+		for (i = 0; i < num_busses_in(chip); i++) {
+			err = set_monitor_gain(chip, o, i,
+						chip->monitor_gain[o][i]);
+			if (err < 0)
+				return err;
+		}
+#endif /* ECHOCARD_HAS_MONITOR */
+
+#ifdef ECHOCARD_HAS_INPUT_GAIN
+	for (i = 0; i < num_busses_in(chip); i++) {
+		err = set_input_gain(chip, i, chip->input_gain[i]);
+		if (err < 0)
+			return err;
+	}
+#endif /* ECHOCARD_HAS_INPUT_GAIN */
+
+	err = update_output_line_level(chip);
+	if (err < 0)
+		return err;
+
+	err = update_input_line_level(chip);
+	if (err < 0)
+		return err;
+
+	err = set_sample_rate(chip, chip->sample_rate);
+	if (err < 0)
+		return err;
+
+	if (chip->meters_enabled) {
+		err = send_vector(chip, DSP_VC_METERS_ON);
+		if (err < 0)
+			return err;
+	}
+
+#ifdef ECHOCARD_HAS_DIGITAL_MODE_SWITCH
+	if (set_digital_mode(chip, chip->digital_mode) < 0)
+		return -EIO;
+#endif
+
+#ifdef ECHOCARD_HAS_DIGITAL_IO
+	if (set_professional_spdif(chip, chip->professional_spdif) < 0)
+		return -EIO;
+#endif
+
+#ifdef ECHOCARD_HAS_PHANTOM_POWER
+	if (set_phantom_power(chip, chip->phantom_power) < 0)
+		return -EIO;
+#endif
+
+#ifdef ECHOCARD_HAS_EXTERNAL_CLOCK
+	/* set_input_clock() also restores automute setting */
+	if (set_input_clock(chip, chip->input_clock) < 0)
+		return -EIO;
+#endif
+
+#ifdef ECHOCARD_HAS_OUTPUT_CLOCK_SWITCH
+	if (set_output_clock(chip, chip->output_clock) < 0)
+		return -EIO;
+#endif
+
+	if (wait_handshake(chip) < 0)
+		return -EIO;
+	clear_handshake(chip);
+	if (send_vector(chip, DSP_VC_UPDATE_FLAGS) < 0)
+		return -EIO;
+
+	DE_INIT(("restore_dsp_rettings done\n"));
+	return 0;
+}
+
+
+
+/****************************************************************************
+	Transport functions
+ ****************************************************************************/
+
+/* set_audio_format() sets the format of the audio data in host memory for
+this pipe.  Note that _MS_ (mono-to-stereo) playback modes are not used by ALSA
+but they are here because they are just mono while capturing */
+static void set_audio_format(struct echoaudio *chip, u16 pipe_index,
+			     const struct audioformat *format)
+{
+	u16 dsp_format;
+
+	dsp_format = DSP_AUDIOFORM_SS_16LE;
+
+	/* Look for super-interleave (no big-endian and 8 bits) */
+	if (format->interleave > 2) {
+		switch (format->bits_per_sample) {
+		case 16:
+			dsp_format = DSP_AUDIOFORM_SUPER_INTERLEAVE_16LE;
+			break;
+		case 24:
+			dsp_format = DSP_AUDIOFORM_SUPER_INTERLEAVE_24LE;
+			break;
+		case 32:
+			dsp_format = DSP_AUDIOFORM_SUPER_INTERLEAVE_32LE;
+			break;
+		}
+		dsp_format |= format->interleave;
+	} else if (format->data_are_bigendian) {
+		/* For big-endian data, only 32 bit samples are supported */
+		switch (format->interleave) {
+		case 1:
+			dsp_format = DSP_AUDIOFORM_MM_32BE;
+			break;
+#ifdef ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+		case 2:
+			dsp_format = DSP_AUDIOFORM_SS_32BE;
+			break;
+#endif
+		}
+	} else if (format->interleave == 1 &&
+		   format->bits_per_sample == 32 && !format->mono_to_stereo) {
+		/* 32 bit little-endian mono->mono case */
+		dsp_format = DSP_AUDIOFORM_MM_32LE;
+	} else {
+		/* Handle the other little-endian formats */
+		switch (format->bits_per_sample) {
+		case 8:
+			if (format->interleave == 2)
+				dsp_format = DSP_AUDIOFORM_SS_8;
+			else
+				dsp_format = DSP_AUDIOFORM_MS_8;
+			break;
+		default:
+		case 16:
+			if (format->interleave == 2)
+				dsp_format = DSP_AUDIOFORM_SS_16LE;
+			else
+				dsp_format = DSP_AUDIOFORM_MS_16LE;
+			break;
+		case 24:
+			if (format->interleave == 2)
+				dsp_format = DSP_AUDIOFORM_SS_24LE;
+			else
+				dsp_format = DSP_AUDIOFORM_MS_24LE;
+			break;
+		case 32:
+			if (format->interleave == 2)
+				dsp_format = DSP_AUDIOFORM_SS_32LE;
+			else
+				dsp_format = DSP_AUDIOFORM_MS_32LE;
+			break;
+		}
+	}
+	DE_ACT(("set_audio_format[%d] = %x\n", pipe_index, dsp_format));
+	chip->comm_page->audio_format[pipe_index] = cpu_to_le16(dsp_format);
+}
+
+
+
+/* start_transport starts transport for a set of pipes.
+The bits 1 in channel_mask specify what pipes to start. Only the bit of the
+first channel must be set, regardless its interleave.
+Same thing for pause_ and stop_ -trasport below. */
+static int start_transport(struct echoaudio *chip, u32 channel_mask,
+			   u32 cyclic_mask)
+{
+	DE_ACT(("start_transport %x\n", channel_mask));
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->comm_page->cmd_start |= cpu_to_le32(channel_mask);
+
+	if (chip->comm_page->cmd_start) {
+		clear_handshake(chip);
+		send_vector(chip, DSP_VC_START_TRANSFER);
+		if (wait_handshake(chip))
+			return -EIO;
+		/* Keep track of which pipes are transporting */
+		chip->active_mask |= channel_mask;
+		chip->comm_page->cmd_start = 0;
+		return 0;
+	}
+
+	DE_ACT(("start_transport: No pipes to start!\n"));
+	return -EINVAL;
+}
+
+
+
+static int pause_transport(struct echoaudio *chip, u32 channel_mask)
+{
+	DE_ACT(("pause_transport %x\n", channel_mask));
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->comm_page->cmd_stop |= cpu_to_le32(channel_mask);
+	chip->comm_page->cmd_reset = 0;
+	if (chip->comm_page->cmd_stop) {
+		clear_handshake(chip);
+		send_vector(chip, DSP_VC_STOP_TRANSFER);
+		if (wait_handshake(chip))
+			return -EIO;
+		/* Keep track of which pipes are transporting */
+		chip->active_mask &= ~channel_mask;
+		chip->comm_page->cmd_stop = 0;
+		chip->comm_page->cmd_reset = 0;
+		return 0;
+	}
+
+	DE_ACT(("pause_transport: No pipes to stop!\n"));
+	return 0;
+}
+
+
+
+static int stop_transport(struct echoaudio *chip, u32 channel_mask)
+{
+	DE_ACT(("stop_transport %x\n", channel_mask));
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->comm_page->cmd_stop |= cpu_to_le32(channel_mask);
+	chip->comm_page->cmd_reset |= cpu_to_le32(channel_mask);
+	if (chip->comm_page->cmd_reset) {
+		clear_handshake(chip);
+		send_vector(chip, DSP_VC_STOP_TRANSFER);
+		if (wait_handshake(chip))
+			return -EIO;
+		/* Keep track of which pipes are transporting */
+		chip->active_mask &= ~channel_mask;
+		chip->comm_page->cmd_stop = 0;
+		chip->comm_page->cmd_reset = 0;
+		return 0;
+	}
+
+	DE_ACT(("stop_transport: No pipes to stop!\n"));
+	return 0;
+}
+
+
+
+static inline int is_pipe_allocated(struct echoaudio *chip, u16 pipe_index)
+{
+	return (chip->pipe_alloc_mask & (1 << pipe_index));
+}
+
+
+
+/* Stops everything and turns off the DSP. All pipes should be already
+stopped and unallocated. */
+static int rest_in_peace(struct echoaudio *chip)
+{
+	DE_ACT(("rest_in_peace() open=%x\n", chip->pipe_alloc_mask));
+
+	/* Stops all active pipes (just to be sure) */
+	stop_transport(chip, chip->active_mask);
+
+	set_meters_on(chip, FALSE);
+
+#ifdef ECHOCARD_HAS_MIDI
+	enable_midi_input(chip, FALSE);
+#endif
+
+	/* Go to sleep */
+	if (chip->dsp_code) {
+		/* Make load_firmware do a complete reload */
+		chip->dsp_code = NULL;
+		/* Put the DSP to sleep */
+		return send_vector(chip, DSP_VC_GO_COMATOSE);
+	}
+	return 0;
+}
+
+
+
+/* Fills the comm page with default values */
+static int init_dsp_comm_page(struct echoaudio *chip)
+{
+	/* Check if the compiler added extra padding inside the structure */
+	if (offsetof(struct comm_page, midi_output) != 0xbe0) {
+		DE_INIT(("init_dsp_comm_page() - Invalid struct comm_page structure\n"));
+		return -EPERM;
+	}
+
+	/* Init all the basic stuff */
+	chip->card_name = ECHOCARD_NAME;
+	chip->bad_board = TRUE;	/* Set TRUE until DSP loaded */
+	chip->dsp_code = NULL;	/* Current DSP code not loaded */
+	chip->asic_loaded = FALSE;
+	memset(chip->comm_page, 0, sizeof(struct comm_page));
+
+	/* Init the comm page */
+	chip->comm_page->comm_size =
+		cpu_to_le32(sizeof(struct comm_page));
+	chip->comm_page->handshake = 0xffffffff;
+	chip->comm_page->midi_out_free_count =
+		cpu_to_le32(DSP_MIDI_OUT_FIFO_SIZE);
+	chip->comm_page->sample_rate = cpu_to_le32(44100);
+
+	/* Set line levels so we don't blast any inputs on startup */
+	memset(chip->comm_page->monitors, ECHOGAIN_MUTED, MONITOR_ARRAY_SIZE);
+	memset(chip->comm_page->vmixer, ECHOGAIN_MUTED, VMIXER_ARRAY_SIZE);
+
+	return 0;
+}
+
+
+
+/* This function initializes the chip structure with default values, ie. all
+ * muted and internal clock source. Then it copies the settings to the DSP.
+ * This MUST be called after the DSP is up and running !
+ */
+static int init_line_levels(struct echoaudio *chip)
+{
+	DE_INIT(("init_line_levels\n"));
+	memset(chip->output_gain, ECHOGAIN_MUTED, sizeof(chip->output_gain));
+	memset(chip->input_gain, ECHOGAIN_MUTED, sizeof(chip->input_gain));
+	memset(chip->monitor_gain, ECHOGAIN_MUTED, sizeof(chip->monitor_gain));
+	memset(chip->vmixer_gain, ECHOGAIN_MUTED, sizeof(chip->vmixer_gain));
+	chip->input_clock = ECHO_CLOCK_INTERNAL;
+	chip->output_clock = ECHO_CLOCK_WORD;
+	chip->sample_rate = 44100;
+	return restore_dsp_rettings(chip);
+}
+
+
+
+/* This is low level part of the interrupt handler.
+It returns -1 if the IRQ is not ours, or N>=0 if it is, where N is the number
+of midi data in the input queue. */
+static int service_irq(struct echoaudio *chip)
+{
+	int st;
+
+	/* Read the DSP status register and see if this DSP generated this interrupt */
+	if (get_dsp_register(chip, CHI32_STATUS_REG) & CHI32_STATUS_IRQ) {
+		st = 0;
+#ifdef ECHOCARD_HAS_MIDI
+		/* Get and parse midi data if present */
+		if (chip->comm_page->midi_input[0])	/* The count is at index 0 */
+			st = midi_service_irq(chip);	/* Returns how many midi bytes we received */
+#endif
+		/* Clear the hardware interrupt */
+		chip->comm_page->midi_input[0] = 0;
+		send_vector(chip, DSP_VC_ACK_INT);
+		return st;
+	}
+	return -1;
+}
+
+
+
+
+/******************************************************************************
+	Functions for opening and closing pipes
+ ******************************************************************************/
+
+/* allocate_pipes is used to reserve audio pipes for your exclusive use.
+The call will fail if some pipes are already allocated. */
+static int allocate_pipes(struct echoaudio *chip, struct audiopipe *pipe,
+			  int pipe_index, int interleave)
+{
+	int i;
+	u32 channel_mask;
+	char is_cyclic;
+
+	DE_ACT(("allocate_pipes: ch=%d int=%d\n", pipe_index, interleave));
+
+	if (chip->bad_board)
+		return -EIO;
+
+	is_cyclic = 1;	/* This driver uses cyclic buffers only */
+
+	for (channel_mask = i = 0; i < interleave; i++)
+		channel_mask |= 1 << (pipe_index + i);
+	if (chip->pipe_alloc_mask & channel_mask) {
+		DE_ACT(("allocate_pipes: channel already open\n"));
+		return -EAGAIN;
+	}
+
+	chip->comm_page->position[pipe_index] = 0;
+	chip->pipe_alloc_mask |= channel_mask;
+	if (is_cyclic)
+		chip->pipe_cyclic_mask |= channel_mask;
+	pipe->index = pipe_index;
+	pipe->interleave = interleave;
+	pipe->state = PIPE_STATE_STOPPED;
+
+	/* The counter register is where the DSP writes the 32 bit DMA
+	position for a pipe.  The DSP is constantly updating this value as
+	it moves data. The DMA counter is in units of bytes, not samples. */
+	pipe->dma_counter = &chip->comm_page->position[pipe_index];
+	*pipe->dma_counter = 0;
+	DE_ACT(("allocate_pipes: ok\n"));
+	return pipe_index;
+}
+
+
+
+static int free_pipes(struct echoaudio *chip, struct audiopipe *pipe)
+{
+	u32 channel_mask;
+	int i;
+
+	DE_ACT(("free_pipes: Pipe %d\n", pipe->index));
+	if (snd_BUG_ON(!is_pipe_allocated(chip, pipe->index)))
+		return -EINVAL;
+	if (snd_BUG_ON(pipe->state != PIPE_STATE_STOPPED))
+		return -EINVAL;
+
+	for (channel_mask = i = 0; i < pipe->interleave; i++)
+		channel_mask |= 1 << (pipe->index + i);
+
+	chip->pipe_alloc_mask &= ~channel_mask;
+	chip->pipe_cyclic_mask &= ~channel_mask;
+	return 0;
+}
+
+
+
+/******************************************************************************
+	Functions for managing the scatter-gather list
+******************************************************************************/
+
+static int sglist_init(struct echoaudio *chip, struct audiopipe *pipe)
+{
+	pipe->sglist_head = 0;
+	memset(pipe->sgpage.area, 0, PAGE_SIZE);
+	chip->comm_page->sglist_addr[pipe->index].addr =
+		cpu_to_le32(pipe->sgpage.addr);
+	return 0;
+}
+
+
+
+static int sglist_add_mapping(struct echoaudio *chip, struct audiopipe *pipe,
+				dma_addr_t address, size_t length)
+{
+	int head = pipe->sglist_head;
+	struct sg_entry *list = (struct sg_entry *)pipe->sgpage.area;
+
+	if (head < MAX_SGLIST_ENTRIES - 1) {
+		list[head].addr = cpu_to_le32(address);
+		list[head].size = cpu_to_le32(length);
+		pipe->sglist_head++;
+	} else {
+		DE_ACT(("SGlist: too many fragments\n"));
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+
+
+static inline int sglist_add_irq(struct echoaudio *chip, struct audiopipe *pipe)
+{
+	return sglist_add_mapping(chip, pipe, 0, 0);
+}
+
+
+
+static inline int sglist_wrap(struct echoaudio *chip, struct audiopipe *pipe)
+{
+	return sglist_add_mapping(chip, pipe, pipe->sgpage.addr, 0);
+}
diff --git a/linux/sound/pci/echoaudio/echoaudio_dsp.h b/linux/sound/pci/echoaudio/echoaudio_dsp.h
new file mode 100644
index 0000000..cb7d75a
--- /dev/null
+++ b/linux/sound/pci/echoaudio/echoaudio_dsp.h
@@ -0,0 +1,698 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+#ifndef _ECHO_DSP_
+#define _ECHO_DSP_
+
+
+/**** Echogals: Darla20, Gina20, Layla20, and Darla24 ****/
+#if defined(ECHOGALS_FAMILY)
+
+#define NUM_ASIC_TESTS		5
+#define READ_DSP_TIMEOUT	1000000L	/* one second */
+
+/**** Echo24: Gina24, Layla24, Mona, Mia, Mia-midi ****/
+#elif defined(ECHO24_FAMILY)
+
+#define DSP_56361			/* Some Echo24 cards use the 56361 DSP */
+#define READ_DSP_TIMEOUT	100000L		/* .1 second */
+
+/**** 3G: Gina3G, Layla3G ****/
+#elif defined(ECHO3G_FAMILY)
+
+#define DSP_56361
+#define READ_DSP_TIMEOUT 	100000L		/* .1 second */
+#define MIN_MTC_1X_RATE		32000
+
+/**** Indigo: Indigo, Indigo IO, Indigo DJ ****/
+#elif defined(INDIGO_FAMILY)
+
+#define DSP_56361
+#define READ_DSP_TIMEOUT	100000L		/* .1 second */
+
+#else
+
+#error No family is defined
+
+#endif
+
+
+
+/*
+ *
+ *  Max inputs and outputs
+ *
+ */
+
+#define DSP_MAXAUDIOINPUTS		16	/* Max audio input channels */
+#define DSP_MAXAUDIOOUTPUTS		16	/* Max audio output channels */
+#define DSP_MAXPIPES			32	/* Max total pipes (input + output) */
+
+
+/*
+ *
+ * These are the offsets for the memory-mapped DSP registers; the DSP base
+ * address is treated as the start of a u32 array.
+ */
+
+#define CHI32_CONTROL_REG		4
+#define CHI32_STATUS_REG		5
+#define CHI32_VECTOR_REG		6
+#define CHI32_DATA_REG			7
+
+
+/*
+ *
+ * Interesting bits within the DSP registers
+ *
+ */
+
+#define CHI32_VECTOR_BUSY		0x00000001
+#define CHI32_STATUS_REG_HF3		0x00000008
+#define CHI32_STATUS_REG_HF4		0x00000010
+#define CHI32_STATUS_REG_HF5		0x00000020
+#define CHI32_STATUS_HOST_READ_FULL	0x00000004
+#define CHI32_STATUS_HOST_WRITE_EMPTY	0x00000002
+#define CHI32_STATUS_IRQ		0x00000040
+
+
+/* 
+ *
+ * DSP commands sent via slave mode; these are sent to the DSP by write_dsp()
+ *
+ */
+
+#define DSP_FNC_SET_COMMPAGE_ADDR		0x02
+#define DSP_FNC_LOAD_LAYLA_ASIC			0xa0
+#define DSP_FNC_LOAD_GINA24_ASIC		0xa0
+#define DSP_FNC_LOAD_MONA_PCI_CARD_ASIC		0xa0
+#define DSP_FNC_LOAD_LAYLA24_PCI_CARD_ASIC	0xa0
+#define DSP_FNC_LOAD_MONA_EXTERNAL_ASIC		0xa1
+#define DSP_FNC_LOAD_LAYLA24_EXTERNAL_ASIC	0xa1
+#define DSP_FNC_LOAD_3G_ASIC			0xa0
+
+
+/*
+ *
+ * Defines to handle the MIDI input state engine; these are used to properly
+ * extract MIDI time code bytes and their timestamps from the MIDI input stream.
+ *
+ */
+
+#define MIDI_IN_STATE_NORMAL	0
+#define MIDI_IN_STATE_TS_HIGH	1
+#define MIDI_IN_STATE_TS_LOW	2
+#define MIDI_IN_STATE_F1_DATA 	3
+#define MIDI_IN_SKIP_DATA	(-1)
+
+
+/*----------------------------------------------------------------------------
+
+Setting the sample rates on Layla24 is somewhat schizophrenic.
+
+For standard rates, it works exactly like Mona and Gina24.  That is, for
+8, 11.025, 16, 22.05, 32, 44.1, 48, 88.2, and 96 kHz, you just set the
+appropriate bits in the control register and write the control register.
+
+In order to support MIDI time code sync (and possibly SMPTE LTC sync in
+the future), Layla24 also has "continuous sample rate mode".  In this mode,
+Layla24 can generate any sample rate between 25 and 50 kHz inclusive, or
+50 to 100 kHz inclusive for double speed mode.
+
+To use continuous mode:
+
+-Set the clock select bits in the control register to 0xe (see the #define
+ below)
+
+-Set double-speed mode if you want to use sample rates above 50 kHz
+
+-Write the control register as you would normally
+
+-Now, you need to set the frequency register. First, you need to determine the
+ value for the frequency register.  This is given by the following formula:
+
+frequency_reg = (LAYLA24_MAGIC_NUMBER / sample_rate) - 2
+
+Note the #define below for the magic number
+
+-Wait for the DSP handshake
+-Write the frequency_reg value to the .SampleRate field of the comm page
+-Send the vector command SET_LAYLA24_FREQUENCY_REG (see vmonkey.h)
+
+Once you have set the control register up for continuous mode, you can just
+write the frequency register to change the sample rate.  This could be
+used for MIDI time code sync. For MTC sync, the control register is set for
+continuous mode.  The driver then just keeps writing the
+SET_LAYLA24_FREQUENCY_REG command.
+
+-----------------------------------------------------------------------------*/
+
+#define LAYLA24_MAGIC_NUMBER			677376000
+#define LAYLA24_CONTINUOUS_CLOCK		0x000e
+
+
+/*
+ *
+ * DSP vector commands
+ *
+ */
+
+#define DSP_VC_RESET				0x80ff
+
+#ifndef DSP_56361
+
+#define DSP_VC_ACK_INT				0x8073
+#define DSP_VC_SET_VMIXER_GAIN			0x0000	/* Not used, only for compile */
+#define DSP_VC_START_TRANSFER			0x0075	/* Handshke rqd. */
+#define DSP_VC_METERS_ON			0x0079
+#define DSP_VC_METERS_OFF			0x007b
+#define DSP_VC_UPDATE_OUTVOL			0x007d	/* Handshke rqd. */
+#define DSP_VC_UPDATE_INGAIN			0x007f	/* Handshke rqd. */
+#define DSP_VC_ADD_AUDIO_BUFFER			0x0081	/* Handshke rqd. */
+#define DSP_VC_TEST_ASIC			0x00eb
+#define DSP_VC_UPDATE_CLOCKS			0x00ef	/* Handshke rqd. */
+#define DSP_VC_SET_LAYLA_SAMPLE_RATE		0x00f1	/* Handshke rqd. */
+#define DSP_VC_SET_GD_AUDIO_STATE		0x00f1	/* Handshke rqd. */
+#define DSP_VC_WRITE_CONTROL_REG		0x00f1	/* Handshke rqd. */
+#define DSP_VC_MIDI_WRITE			0x00f5	/* Handshke rqd. */
+#define DSP_VC_STOP_TRANSFER			0x00f7	/* Handshke rqd. */
+#define DSP_VC_UPDATE_FLAGS			0x00fd	/* Handshke rqd. */
+#define DSP_VC_GO_COMATOSE			0x00f9
+
+#else /* !DSP_56361 */
+
+/* Vector commands for families that use either the 56301 or 56361 */
+#define DSP_VC_ACK_INT				0x80F5
+#define DSP_VC_SET_VMIXER_GAIN			0x00DB	/* Handshke rqd. */
+#define DSP_VC_START_TRANSFER			0x00DD	/* Handshke rqd. */
+#define DSP_VC_METERS_ON			0x00EF
+#define DSP_VC_METERS_OFF			0x00F1
+#define DSP_VC_UPDATE_OUTVOL			0x00E3	/* Handshke rqd. */
+#define DSP_VC_UPDATE_INGAIN			0x00E5	/* Handshke rqd. */
+#define DSP_VC_ADD_AUDIO_BUFFER			0x00E1	/* Handshke rqd. */
+#define DSP_VC_TEST_ASIC			0x00ED
+#define DSP_VC_UPDATE_CLOCKS			0x00E9	/* Handshke rqd. */
+#define DSP_VC_SET_LAYLA24_FREQUENCY_REG	0x00E9	/* Handshke rqd. */
+#define DSP_VC_SET_LAYLA_SAMPLE_RATE		0x00EB	/* Handshke rqd. */
+#define DSP_VC_SET_GD_AUDIO_STATE		0x00EB	/* Handshke rqd. */
+#define DSP_VC_WRITE_CONTROL_REG		0x00EB	/* Handshke rqd. */
+#define DSP_VC_MIDI_WRITE			0x00E7	/* Handshke rqd. */
+#define DSP_VC_STOP_TRANSFER			0x00DF	/* Handshke rqd. */
+#define DSP_VC_UPDATE_FLAGS			0x00FB	/* Handshke rqd. */
+#define DSP_VC_GO_COMATOSE			0x00d9
+
+#endif /* !DSP_56361 */
+
+
+/*
+ *
+ * Timeouts
+ *
+ */
+
+#define HANDSHAKE_TIMEOUT		20000	/* send_vector command timeout (20ms) */
+#define VECTOR_BUSY_TIMEOUT		100000	/* 100ms */
+#define MIDI_OUT_DELAY_USEC		2000	/* How long to wait after MIDI fills up */
+
+
+/*
+ *
+ * Flags for .Flags field in the comm page
+ *
+ */
+
+#define DSP_FLAG_MIDI_INPUT		0x0001	/* Enable MIDI input */
+#define DSP_FLAG_SPDIF_NONAUDIO		0x0002	/* Sets the "non-audio" bit
+						 * in the S/PDIF out status
+						 * bits.  Clear this flag for
+						 * audio data;
+						 * set it for AC3 or WMA or
+						 * some such */
+#define DSP_FLAG_PROFESSIONAL_SPDIF	0x0008	/* 1 Professional, 0 Consumer */
+
+
+/*
+ *
+ * Clock detect bits reported by the DSP for Gina20, Layla20, Darla24, and Mia
+ *
+ */
+
+#define GLDM_CLOCK_DETECT_BIT_WORD	0x0002
+#define GLDM_CLOCK_DETECT_BIT_SUPER	0x0004
+#define GLDM_CLOCK_DETECT_BIT_SPDIF	0x0008
+#define GLDM_CLOCK_DETECT_BIT_ESYNC	0x0010
+
+
+/*
+ *
+ * Clock detect bits reported by the DSP for Gina24, Mona, and Layla24
+ *
+ */
+
+#define GML_CLOCK_DETECT_BIT_WORD96	0x0002
+#define GML_CLOCK_DETECT_BIT_WORD48	0x0004
+#define GML_CLOCK_DETECT_BIT_SPDIF48	0x0008
+#define GML_CLOCK_DETECT_BIT_SPDIF96	0x0010
+#define GML_CLOCK_DETECT_BIT_WORD	(GML_CLOCK_DETECT_BIT_WORD96 | GML_CLOCK_DETECT_BIT_WORD48)
+#define GML_CLOCK_DETECT_BIT_SPDIF	(GML_CLOCK_DETECT_BIT_SPDIF48 | GML_CLOCK_DETECT_BIT_SPDIF96)
+#define GML_CLOCK_DETECT_BIT_ESYNC	0x0020
+#define GML_CLOCK_DETECT_BIT_ADAT	0x0040
+
+
+/*
+ *
+ * Layla clock numbers to send to DSP
+ *
+ */
+
+#define LAYLA20_CLOCK_INTERNAL		0
+#define LAYLA20_CLOCK_SPDIF		1
+#define LAYLA20_CLOCK_WORD		2
+#define LAYLA20_CLOCK_SUPER		3
+
+
+/*
+ *
+ * Gina/Darla clock states
+ *
+ */
+
+#define GD_CLOCK_NOCHANGE		0
+#define GD_CLOCK_44			1
+#define GD_CLOCK_48			2
+#define GD_CLOCK_SPDIFIN		3
+#define GD_CLOCK_UNDEF			0xff
+
+
+/*
+ *
+ * Gina/Darla S/PDIF status bits
+ *
+ */
+
+#define GD_SPDIF_STATUS_NOCHANGE	0
+#define GD_SPDIF_STATUS_44		1
+#define GD_SPDIF_STATUS_48		2
+#define GD_SPDIF_STATUS_UNDEF		0xff
+
+
+/*
+ *
+ * Layla20 output clocks
+ *
+ */
+
+#define LAYLA20_OUTPUT_CLOCK_SUPER	0
+#define LAYLA20_OUTPUT_CLOCK_WORD	1
+
+
+/****************************************************************************
+
+   Magic constants for the Darla24 hardware
+
+ ****************************************************************************/
+
+#define GD24_96000	0x0
+#define GD24_48000	0x1
+#define GD24_44100	0x2
+#define GD24_32000	0x3
+#define GD24_22050	0x4
+#define GD24_16000	0x5
+#define GD24_11025	0x6
+#define GD24_8000	0x7
+#define GD24_88200	0x8
+#define GD24_EXT_SYNC	0x9
+
+
+/*
+ *
+ * Return values from the DSP when ASIC is loaded
+ *
+ */
+
+#define ASIC_ALREADY_LOADED	0x1
+#define ASIC_NOT_LOADED		0x0
+
+
+/*
+ *
+ * DSP Audio formats
+ *
+ * These are the audio formats that the DSP can transfer
+ * via input and output pipes.  LE means little-endian,
+ * BE means big-endian.
+ *
+ * DSP_AUDIOFORM_MS_8   
+ *
+ *    8-bit mono unsigned samples.  For playback,
+ *    mono data is duplicated out the left and right channels
+ *    of the output bus.  The "MS" part of the name
+ *    means mono->stereo.
+ *
+ * DSP_AUDIOFORM_MS_16LE
+ *
+ *    16-bit signed little-endian mono samples.  Playback works
+ *    like the previous code.
+ *
+ * DSP_AUDIOFORM_MS_24LE
+ *
+ *    24-bit signed little-endian mono samples.  Data is packed
+ *    three bytes per sample; if you had two samples 0x112233 and 0x445566
+ *    they would be stored in memory like this: 33 22 11 66 55 44.
+ *
+ * DSP_AUDIOFORM_MS_32LE
+ * 
+ *    24-bit signed little-endian mono samples in a 32-bit 
+ *    container.  In other words, each sample is a 32-bit signed 
+ *    integer, where the actual audio data is left-justified 
+ *    in the 32 bits and only the 24 most significant bits are valid.
+ *
+ * DSP_AUDIOFORM_SS_8
+ * DSP_AUDIOFORM_SS_16LE
+ * DSP_AUDIOFORM_SS_24LE
+ * DSP_AUDIOFORM_SS_32LE
+ *
+ *    Like the previous ones, except now with stereo interleaved
+ *    data.  "SS" means stereo->stereo.
+ *
+ * DSP_AUDIOFORM_MM_32LE
+ *
+ *    Similar to DSP_AUDIOFORM_MS_32LE, except that the mono
+ *    data is not duplicated out both the left and right outputs.
+ *    This mode is used by the ASIO driver.  Here, "MM" means
+ *    mono->mono.
+ *
+ * DSP_AUDIOFORM_MM_32BE
+ *
+ *    Just like DSP_AUDIOFORM_MM_32LE, but now the data is
+ *    in big-endian format.
+ *
+ */
+
+#define DSP_AUDIOFORM_MS_8	0	/* 8 bit mono */
+#define DSP_AUDIOFORM_MS_16LE	1	/* 16 bit mono */
+#define DSP_AUDIOFORM_MS_24LE	2	/* 24 bit mono */
+#define DSP_AUDIOFORM_MS_32LE	3	/* 32 bit mono */
+#define DSP_AUDIOFORM_SS_8	4	/* 8 bit stereo */
+#define DSP_AUDIOFORM_SS_16LE	5	/* 16 bit stereo */
+#define DSP_AUDIOFORM_SS_24LE	6	/* 24 bit stereo */
+#define DSP_AUDIOFORM_SS_32LE	7	/* 32 bit stereo */
+#define DSP_AUDIOFORM_MM_32LE	8	/* 32 bit mono->mono little-endian */
+#define DSP_AUDIOFORM_MM_32BE	9	/* 32 bit mono->mono big-endian */
+#define DSP_AUDIOFORM_SS_32BE	10	/* 32 bit stereo big endian */
+#define DSP_AUDIOFORM_INVALID	0xFF	/* Invalid audio format */
+
+
+/*
+ *
+ * Super-interleave is defined as interleaving by 4 or more.  Darla20 and Gina20
+ * do not support super interleave.
+ *
+ * 16 bit, 24 bit, and 32 bit little endian samples are supported for super 
+ * interleave.  The interleave factor must be even.  16 - way interleave is the 
+ * current maximum, so you can interleave by 4, 6, 8, 10, 12, 14, and 16.
+ *
+ * The actual format code is derived by taking the define below and or-ing with
+ * the interleave factor.  So, 32 bit interleave by 6 is 0x86 and
+ * 16 bit interleave by 16 is (0x40 | 0x10) = 0x50.
+ *
+ */
+
+#define DSP_AUDIOFORM_SUPER_INTERLEAVE_16LE	0x40
+#define DSP_AUDIOFORM_SUPER_INTERLEAVE_24LE	0xc0
+#define DSP_AUDIOFORM_SUPER_INTERLEAVE_32LE	0x80
+
+
+/*
+ *
+ * Gina24, Mona, and Layla24 control register defines
+ *
+ */
+
+#define GML_CONVERTER_ENABLE	0x0010
+#define GML_SPDIF_PRO_MODE	0x0020	/* Professional S/PDIF == 1,
+					   consumer == 0 */
+#define GML_SPDIF_SAMPLE_RATE0	0x0040
+#define GML_SPDIF_SAMPLE_RATE1	0x0080
+#define GML_SPDIF_TWO_CHANNEL	0x0100	/* 1 == two channels,
+					   0 == one channel */
+#define GML_SPDIF_NOT_AUDIO	0x0200
+#define GML_SPDIF_COPY_PERMIT	0x0400
+#define GML_SPDIF_24_BIT	0x0800	/* 1 == 24 bit, 0 == 20 bit */
+#define GML_ADAT_MODE		0x1000	/* 1 == ADAT mode, 0 == S/PDIF mode */
+#define GML_SPDIF_OPTICAL_MODE	0x2000	/* 1 == optical mode, 0 == RCA mode */
+#define GML_SPDIF_CDROM_MODE	0x3000	/* 1 == CDROM mode,
+					 * 0 == RCA or optical mode */
+#define GML_DOUBLE_SPEED_MODE	0x4000	/* 1 == double speed,
+					   0 == single speed */
+
+#define GML_DIGITAL_IN_AUTO_MUTE 0x800000
+
+#define GML_96KHZ		(0x0 | GML_DOUBLE_SPEED_MODE)
+#define GML_88KHZ		(0x1 | GML_DOUBLE_SPEED_MODE)
+#define GML_48KHZ		0x2
+#define GML_44KHZ		0x3
+#define GML_32KHZ		0x4
+#define GML_22KHZ		0x5
+#define GML_16KHZ		0x6
+#define GML_11KHZ		0x7
+#define GML_8KHZ		0x8
+#define GML_SPDIF_CLOCK		0x9
+#define GML_ADAT_CLOCK		0xA
+#define GML_WORD_CLOCK		0xB
+#define GML_ESYNC_CLOCK		0xC
+#define GML_ESYNCx2_CLOCK	0xD
+
+#define GML_CLOCK_CLEAR_MASK		0xffffbff0
+#define GML_SPDIF_RATE_CLEAR_MASK	(~(GML_SPDIF_SAMPLE_RATE0|GML_SPDIF_SAMPLE_RATE1))
+#define GML_DIGITAL_MODE_CLEAR_MASK	0xffffcfff
+#define GML_SPDIF_FORMAT_CLEAR_MASK	0xfffff01f
+
+
+/*
+ *
+ * Mia sample rate and clock setting constants
+ *
+ */
+
+#define MIA_32000	0x0040
+#define MIA_44100	0x0042
+#define MIA_48000	0x0041
+#define MIA_88200	0x0142
+#define MIA_96000	0x0141
+
+#define MIA_SPDIF	0x00000044
+#define MIA_SPDIF96	0x00000144
+
+#define MIA_MIDI_REV	1	/* Must be Mia rev 1 for MIDI support */
+
+
+/*
+ *
+ * 3G register bits
+ *
+ */
+
+#define E3G_CONVERTER_ENABLE	0x0010
+#define E3G_SPDIF_PRO_MODE	0x0020	/* Professional S/PDIF == 1,
+					   consumer == 0 */
+#define E3G_SPDIF_SAMPLE_RATE0	0x0040
+#define E3G_SPDIF_SAMPLE_RATE1	0x0080
+#define E3G_SPDIF_TWO_CHANNEL	0x0100	/* 1 == two channels,
+					   0 == one channel */
+#define E3G_SPDIF_NOT_AUDIO	0x0200
+#define E3G_SPDIF_COPY_PERMIT	0x0400
+#define E3G_SPDIF_24_BIT	0x0800	/* 1 == 24 bit, 0 == 20 bit */
+#define E3G_DOUBLE_SPEED_MODE	0x4000	/* 1 == double speed,
+					   0 == single speed */
+#define E3G_PHANTOM_POWER	0x8000	/* 1 == phantom power on,
+					   0 == phantom power off */
+
+#define E3G_96KHZ		(0x0 | E3G_DOUBLE_SPEED_MODE)
+#define E3G_88KHZ		(0x1 | E3G_DOUBLE_SPEED_MODE)
+#define E3G_48KHZ		0x2
+#define E3G_44KHZ		0x3
+#define E3G_32KHZ		0x4
+#define E3G_22KHZ		0x5
+#define E3G_16KHZ		0x6
+#define E3G_11KHZ		0x7
+#define E3G_8KHZ		0x8
+#define E3G_SPDIF_CLOCK		0x9
+#define E3G_ADAT_CLOCK		0xA
+#define E3G_WORD_CLOCK		0xB
+#define E3G_CONTINUOUS_CLOCK	0xE
+
+#define E3G_ADAT_MODE		0x1000
+#define E3G_SPDIF_OPTICAL_MODE	0x2000
+
+#define E3G_CLOCK_CLEAR_MASK		0xbfffbff0
+#define E3G_DIGITAL_MODE_CLEAR_MASK	0xffffcfff
+#define E3G_SPDIF_FORMAT_CLEAR_MASK	0xfffff01f
+
+/* Clock detect bits reported by the DSP */
+#define E3G_CLOCK_DETECT_BIT_WORD96	0x0001
+#define E3G_CLOCK_DETECT_BIT_WORD48	0x0002
+#define E3G_CLOCK_DETECT_BIT_SPDIF48	0x0004
+#define E3G_CLOCK_DETECT_BIT_ADAT	0x0004
+#define E3G_CLOCK_DETECT_BIT_SPDIF96	0x0008
+#define E3G_CLOCK_DETECT_BIT_WORD	(E3G_CLOCK_DETECT_BIT_WORD96|E3G_CLOCK_DETECT_BIT_WORD48)
+#define E3G_CLOCK_DETECT_BIT_SPDIF	(E3G_CLOCK_DETECT_BIT_SPDIF48|E3G_CLOCK_DETECT_BIT_SPDIF96)
+
+/* Frequency control register */
+#define E3G_MAGIC_NUMBER		677376000
+#define E3G_FREQ_REG_DEFAULT		(E3G_MAGIC_NUMBER / 48000 - 2)
+#define E3G_FREQ_REG_MAX		0xffff
+
+/* 3G external box types */
+#define E3G_GINA3G_BOX_TYPE		0x00
+#define E3G_LAYLA3G_BOX_TYPE		0x10
+#define E3G_ASIC_NOT_LOADED		0xffff
+#define E3G_BOX_TYPE_MASK		0xf0
+
+/* Indigo express control register values */
+#define INDIGO_EXPRESS_32000		0x02
+#define INDIGO_EXPRESS_44100		0x01
+#define INDIGO_EXPRESS_48000		0x00
+#define INDIGO_EXPRESS_DOUBLE_SPEED	0x10
+#define INDIGO_EXPRESS_QUAD_SPEED	0x04
+#define INDIGO_EXPRESS_CLOCK_MASK	0x17
+
+
+/*
+ *
+ * Gina20 & Layla20 have input gain controls for the analog inputs;
+ * this is the magic number for the hardware that gives you 0 dB at -10.
+ *
+ */
+
+#define GL20_INPUT_GAIN_MAGIC_NUMBER	0xC8
+
+
+/*
+ *
+ * Defines how much time must pass between DSP load attempts
+ *
+ */
+
+#define DSP_LOAD_ATTEMPT_PERIOD		1000000L	/* One second */
+
+
+/*
+ *
+ * Size of arrays for the comm page.  MAX_PLAY_TAPS and MAX_REC_TAPS are
+ * no longer used, but the sizes must still be right for the DSP to see
+ * the comm page correctly.
+ *
+ */
+
+#define MONITOR_ARRAY_SIZE	0x180
+#define VMIXER_ARRAY_SIZE	0x40
+#define MIDI_OUT_BUFFER_SIZE	32
+#define MIDI_IN_BUFFER_SIZE	256
+#define MAX_PLAY_TAPS		168
+#define MAX_REC_TAPS		192
+#define DSP_MIDI_OUT_FIFO_SIZE	64
+
+
+/* sg_entry is a single entry for the scatter-gather list.  The array of struct
+sg_entry struct is read by the DSP, so all values must be little-endian. */
+
+#define MAX_SGLIST_ENTRIES 512
+
+struct sg_entry {
+	u32 addr;
+	u32 size;
+};
+
+
+/****************************************************************************
+
+  The comm page.  This structure is read and written by the DSP; the
+  DSP code is a firm believer in the byte offsets written in the comments
+  at the end of each line.  This structure should not be changed.
+
+  Any reads from or writes to this structure should be in little-endian format.
+
+ ****************************************************************************/
+
+struct comm_page {		/*				Base	Length*/
+	u32 comm_size;		/* size of this object		0x000	4 */
+	u32 flags;		/* See Appendix A below		0x004	4 */
+	u32 unused;		/* Unused entry			0x008	4 */
+	u32 sample_rate;	/* Card sample rate in Hz	0x00c	4 */
+	u32 handshake;		/* DSP command handshake	0x010	4 */
+	u32 cmd_start;		/* Chs. to start mask		0x014	4 */
+	u32 cmd_stop;		/* Chs. to stop mask		0x018	4 */
+	u32 cmd_reset;		/* Chs. to reset mask		0x01c	4 */
+	u16 audio_format[DSP_MAXPIPES];	/* Chs. audio format	0x020	32*2 */
+	struct sg_entry sglist_addr[DSP_MAXPIPES];
+				/* Chs. Physical sglist addrs	0x060	32*8 */
+	u32 position[DSP_MAXPIPES];
+				/* Positions for ea. ch.	0x160	32*4 */
+	s8 vu_meter[DSP_MAXPIPES];
+				/* VU meters			0x1e0	32*1 */
+	s8 peak_meter[DSP_MAXPIPES];
+				/* Peak meters			0x200	32*1 */
+	s8 line_out_level[DSP_MAXAUDIOOUTPUTS];
+				/* Output gain			0x220	16*1 */
+	s8 line_in_level[DSP_MAXAUDIOINPUTS];
+				/* Input gain			0x230	16*1 */
+	s8 monitors[MONITOR_ARRAY_SIZE];
+				/* Monitor map			0x240	0x180 */
+	u32 play_coeff[MAX_PLAY_TAPS];
+			/* Gina/Darla play filters - obsolete	0x3c0	168*4 */
+	u32 rec_coeff[MAX_REC_TAPS];
+			/* Gina/Darla record filters - obsolete	0x660	192*4 */
+	u16 midi_input[MIDI_IN_BUFFER_SIZE];
+			/* MIDI input data transfer buffer	0x960	256*2 */
+	u8 gd_clock_state;	/* Chg Gina/Darla clock state	0xb60	1 */
+	u8 gd_spdif_status;	/* Chg. Gina/Darla S/PDIF state	0xb61	1 */
+	u8 gd_resampler_state;	/* Should always be 3		0xb62	1 */
+	u8 filler2;		/*				0xb63	1 */
+	u32 nominal_level_mask;	/* -10 level enable mask	0xb64	4 */
+	u16 input_clock;	/* Chg. Input clock state	0xb68	2 */
+	u16 output_clock;	/* Chg. Output clock state	0xb6a	2 */
+	u32 status_clocks;	/* Current Input clock state	0xb6c	4 */
+	u32 ext_box_status;	/* External box status		0xb70	4 */
+	u32 cmd_add_buffer;	/* Pipes to add (obsolete)	0xb74	4 */
+	u32 midi_out_free_count;
+			/* # of bytes free in MIDI output FIFO	0xb78	4 */
+	u32 unused2;		/* Cyclic pipes			0xb7c	4 */
+	u32 control_register;
+			/* Mona, Gina24, Layla24, 3G ctrl reg	0xb80	4 */
+	u32 e3g_frq_register;	/* 3G frequency register	0xb84	4 */
+	u8 filler[24];		/* filler			0xb88	24*1 */
+	s8 vmixer[VMIXER_ARRAY_SIZE];
+				/* Vmixer levels		0xba0	64*1 */
+	u8 midi_output[MIDI_OUT_BUFFER_SIZE];
+				/* MIDI output data		0xbe0	32*1 */
+};
+
+#endif /* _ECHO_DSP_ */
diff --git a/linux/sound/pci/echoaudio/echoaudio_gml.c b/linux/sound/pci/echoaudio/echoaudio_gml.c
new file mode 100644
index 0000000..afa2733
--- /dev/null
+++ b/linux/sound/pci/echoaudio/echoaudio_gml.c
@@ -0,0 +1,200 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+/* These functions are common for Gina24, Layla24 and Mona cards */
+
+
+/* ASIC status check - some cards have one or two ASICs that need to be
+loaded.  Once that load is complete, this function is called to see if
+the load was successful.
+If this load fails, it does not necessarily mean that the hardware is
+defective - the external box may be disconnected or turned off. */
+static int check_asic_status(struct echoaudio *chip)
+{
+	u32 asic_status;
+
+	send_vector(chip, DSP_VC_TEST_ASIC);
+
+	/* The DSP will return a value to indicate whether or not the
+	   ASIC is currently loaded */
+	if (read_dsp(chip, &asic_status) < 0) {
+		DE_INIT(("check_asic_status: failed on read_dsp\n"));
+		chip->asic_loaded = FALSE;
+		return -EIO;
+	}
+
+	chip->asic_loaded = (asic_status == ASIC_ALREADY_LOADED);
+	return chip->asic_loaded ? 0 : -EIO;
+}
+
+
+
+/* Most configuration of Gina24, Layla24, or Mona is accomplished by writing
+the control register.  write_control_reg sends the new control register
+value to the DSP. */
+static int write_control_reg(struct echoaudio *chip, u32 value, char force)
+{
+	/* Handle the digital input auto-mute */
+	if (chip->digital_in_automute)
+		value |= GML_DIGITAL_IN_AUTO_MUTE;
+	else
+		value &= ~GML_DIGITAL_IN_AUTO_MUTE;
+
+	DE_ACT(("write_control_reg: 0x%x\n", value));
+
+	/* Write the control register */
+	value = cpu_to_le32(value);
+	if (value != chip->comm_page->control_register || force) {
+		if (wait_handshake(chip))
+			return -EIO;
+		chip->comm_page->control_register = value;
+		clear_handshake(chip);
+		return send_vector(chip, DSP_VC_WRITE_CONTROL_REG);
+	}
+	return 0;
+}
+
+
+
+/* Gina24, Layla24, and Mona support digital input auto-mute.  If the digital
+input auto-mute is enabled, the DSP will only enable the digital inputs if
+the card is syncing to a valid clock on the ADAT or S/PDIF inputs.
+If the auto-mute is disabled, the digital inputs are enabled regardless of
+what the input clock is set or what is connected. */
+static int set_input_auto_mute(struct echoaudio *chip, int automute)
+{
+	DE_ACT(("set_input_auto_mute %d\n", automute));
+
+	chip->digital_in_automute = automute;
+
+	/* Re-set the input clock to the current value - indirectly causes
+	the auto-mute flag to be sent to the DSP */
+	return set_input_clock(chip, chip->input_clock);
+}
+
+
+
+/* S/PDIF coax / S/PDIF optical / ADAT - switch */
+static int set_digital_mode(struct echoaudio *chip, u8 mode)
+{
+	u8 previous_mode;
+	int err, i, o;
+
+	if (chip->bad_board)
+		return -EIO;
+
+	/* All audio channels must be closed before changing the digital mode */
+	if (snd_BUG_ON(chip->pipe_alloc_mask))
+		return -EAGAIN;
+
+	if (snd_BUG_ON(!(chip->digital_modes & (1 << mode))))
+		return -EINVAL;
+
+	previous_mode = chip->digital_mode;
+	err = dsp_set_digital_mode(chip, mode);
+
+	/* If we successfully changed the digital mode from or to ADAT,
+	   then make sure all output, input and monitor levels are
+	   updated by the DSP comm object. */
+	if (err >= 0 && previous_mode != mode &&
+	    (previous_mode == DIGITAL_MODE_ADAT || mode == DIGITAL_MODE_ADAT)) {
+		spin_lock_irq(&chip->lock);
+		for (o = 0; o < num_busses_out(chip); o++)
+			for (i = 0; i < num_busses_in(chip); i++)
+				set_monitor_gain(chip, o, i,
+						 chip->monitor_gain[o][i]);
+
+#ifdef ECHOCARD_HAS_INPUT_GAIN
+		for (i = 0; i < num_busses_in(chip); i++)
+			set_input_gain(chip, i, chip->input_gain[i]);
+		update_input_line_level(chip);
+#endif
+
+		for (o = 0; o < num_busses_out(chip); o++)
+			set_output_gain(chip, o, chip->output_gain[o]);
+		update_output_line_level(chip);
+		spin_unlock_irq(&chip->lock);
+	}
+
+	return err;
+}
+
+
+
+/* Set the S/PDIF output format */
+static int set_professional_spdif(struct echoaudio *chip, char prof)
+{
+	u32 control_reg;
+	int err;
+
+	/* Clear the current S/PDIF flags */
+	control_reg = le32_to_cpu(chip->comm_page->control_register);
+	control_reg &= GML_SPDIF_FORMAT_CLEAR_MASK;
+
+	/* Set the new S/PDIF flags depending on the mode */
+	control_reg |= GML_SPDIF_TWO_CHANNEL | GML_SPDIF_24_BIT |
+		GML_SPDIF_COPY_PERMIT;
+	if (prof) {
+		/* Professional mode */
+		control_reg |= GML_SPDIF_PRO_MODE;
+
+		switch (chip->sample_rate) {
+		case 32000:
+			control_reg |= GML_SPDIF_SAMPLE_RATE0 |
+				GML_SPDIF_SAMPLE_RATE1;
+			break;
+		case 44100:
+			control_reg |= GML_SPDIF_SAMPLE_RATE0;
+			break;
+		case 48000:
+			control_reg |= GML_SPDIF_SAMPLE_RATE1;
+			break;
+		}
+	} else {
+		/* Consumer mode */
+		switch (chip->sample_rate) {
+		case 32000:
+			control_reg |= GML_SPDIF_SAMPLE_RATE0 |
+				GML_SPDIF_SAMPLE_RATE1;
+			break;
+		case 48000:
+			control_reg |= GML_SPDIF_SAMPLE_RATE1;
+			break;
+		}
+	}
+
+	if ((err = write_control_reg(chip, control_reg, FALSE)))
+		return err;
+	chip->professional_spdif = prof;
+	DE_ACT(("set_professional_spdif to %s\n",
+		prof ? "Professional" : "Consumer"));
+	return 0;
+}
diff --git a/linux/sound/pci/echoaudio/gina20.c b/linux/sound/pci/echoaudio/gina20.c
new file mode 100644
index 0000000..050e54a
--- /dev/null
+++ b/linux/sound/pci/echoaudio/gina20.c
@@ -0,0 +1,105 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define ECHOGALS_FAMILY
+#define ECHOCARD_GINA20
+#define ECHOCARD_NAME "Gina20"
+#define ECHOCARD_HAS_MONITOR
+#define ECHOCARD_HAS_INPUT_GAIN
+#define ECHOCARD_HAS_DIGITAL_IO
+#define ECHOCARD_HAS_EXTERNAL_CLOCK
+#define ECHOCARD_HAS_ADAT	FALSE
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 8 */
+#define PX_DIGITAL_OUT	8	/* 2 */
+#define PX_ANALOG_IN	10	/* 2 */
+#define PX_DIGITAL_IN	12	/* 2 */
+#define PX_NUM		14
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 8 */
+#define BX_DIGITAL_OUT	8	/* 2 */
+#define BX_ANALOG_IN	10	/* 2 */
+#define BX_DIGITAL_IN	12	/* 2 */
+#define BX_NUM		14
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/gina20_dsp.fw");
+
+#define FW_GINA20_DSP	0
+
+static const struct firmware card_fw[] = {
+	{0, "gina20_dsp.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x1801, 0xECC0, 0x0020, 0, 0, 0},	/* DSP 56301 Gina20 rev.0 */
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+	.rate_min = 44100,
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+	/* One page (4k) contains 512 instructions. I don't know if the hw
+	supports lists longer than this. In this case periods_max=220 is a
+	safe limit to make sure the list never exceeds 512 instructions. */
+};
+
+
+#include "gina20_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio.c"
diff --git a/linux/sound/pci/echoaudio/gina20_dsp.c b/linux/sound/pci/echoaudio/gina20_dsp.c
new file mode 100644
index 0000000..d1615a0
--- /dev/null
+++ b/linux/sound/pci/echoaudio/gina20_dsp.c
@@ -0,0 +1,220 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+static int set_professional_spdif(struct echoaudio *chip, char prof);
+static int update_flags(struct echoaudio *chip);
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Gina20\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != GINA20))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->dsp_code_to_load = FW_GINA20_DSP;
+	chip->spdif_status = GD_SPDIF_STATUS_UNDEF;
+	chip->clock_state = GD_CLOCK_UNDEF;
+	/* Since this card has no ASIC, mark it as loaded so everything
+	   works OK */
+	chip->asic_loaded = TRUE;
+	chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL |
+		ECHO_CLOCK_BIT_SPDIF;
+
+	if ((err = load_firmware(chip)) < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	chip->professional_spdif = FALSE;
+	return init_line_levels(chip);
+}
+
+
+
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	u32 clocks_from_dsp, clock_bits;
+
+	/* Map the DSP clock detect bits to the generic driver clock
+	   detect bits */
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	clock_bits = ECHO_CLOCK_BIT_INTERNAL;
+
+	if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_SPDIF)
+		clock_bits |= ECHO_CLOCK_BIT_SPDIF;
+
+	return clock_bits;
+}
+
+
+
+/* The Gina20 has no ASIC. Just do nothing */
+static int load_asic(struct echoaudio *chip)
+{
+	return 0;
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	u8 clock_state, spdif_status;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	switch (rate) {
+	case 44100:
+		clock_state = GD_CLOCK_44;
+		spdif_status = GD_SPDIF_STATUS_44;
+		break;
+	case 48000:
+		clock_state = GD_CLOCK_48;
+		spdif_status = GD_SPDIF_STATUS_48;
+		break;
+	default:
+		clock_state = GD_CLOCK_NOCHANGE;
+		spdif_status = GD_SPDIF_STATUS_NOCHANGE;
+		break;
+	}
+
+	if (chip->clock_state == clock_state)
+		clock_state = GD_CLOCK_NOCHANGE;
+	if (spdif_status == chip->spdif_status)
+		spdif_status = GD_SPDIF_STATUS_NOCHANGE;
+
+	chip->comm_page->sample_rate = cpu_to_le32(rate);
+	chip->comm_page->gd_clock_state = clock_state;
+	chip->comm_page->gd_spdif_status = spdif_status;
+	chip->comm_page->gd_resampler_state = 3;	/* magic number - should always be 3 */
+
+	/* Save the new audio state if it changed */
+	if (clock_state != GD_CLOCK_NOCHANGE)
+		chip->clock_state = clock_state;
+	if (spdif_status != GD_SPDIF_STATUS_NOCHANGE)
+		chip->spdif_status = spdif_status;
+	chip->sample_rate = rate;
+
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_SET_GD_AUDIO_STATE);
+}
+
+
+
+static int set_input_clock(struct echoaudio *chip, u16 clock)
+{
+	DE_ACT(("set_input_clock:\n"));
+
+	switch (clock) {
+	case ECHO_CLOCK_INTERNAL:
+		/* Reset the audio state to unknown (just in case) */
+		chip->clock_state = GD_CLOCK_UNDEF;
+		chip->spdif_status = GD_SPDIF_STATUS_UNDEF;
+		set_sample_rate(chip, chip->sample_rate);
+		chip->input_clock = clock;
+		DE_ACT(("Set Gina clock to INTERNAL\n"));
+		break;
+	case ECHO_CLOCK_SPDIF:
+		chip->comm_page->gd_clock_state = GD_CLOCK_SPDIFIN;
+		chip->comm_page->gd_spdif_status = GD_SPDIF_STATUS_NOCHANGE;
+		clear_handshake(chip);
+		send_vector(chip, DSP_VC_SET_GD_AUDIO_STATE);
+		chip->clock_state = GD_CLOCK_SPDIFIN;
+		DE_ACT(("Set Gina20 clock to SPDIF\n"));
+		chip->input_clock = clock;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+
+
+/* Set input bus gain (one unit is 0.5dB !) */
+static int set_input_gain(struct echoaudio *chip, u16 input, int gain)
+{
+	if (snd_BUG_ON(input >= num_busses_in(chip)))
+		return -EINVAL;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->input_gain[input] = gain;
+	gain += GL20_INPUT_GAIN_MAGIC_NUMBER;
+	chip->comm_page->line_in_level[input] = gain;
+	return 0;
+}
+
+
+
+/* Tell the DSP to reread the flags from the comm page */
+static int update_flags(struct echoaudio *chip)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_UPDATE_FLAGS);
+}
+
+
+
+static int set_professional_spdif(struct echoaudio *chip, char prof)
+{
+	DE_ACT(("set_professional_spdif %d\n", prof));
+	if (prof)
+		chip->comm_page->flags |=
+			cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF);
+	else
+		chip->comm_page->flags &=
+			~cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF);
+	chip->professional_spdif = prof;
+	return update_flags(chip);
+}
diff --git a/linux/sound/pci/echoaudio/gina24.c b/linux/sound/pci/echoaudio/gina24.c
new file mode 100644
index 0000000..5748fc6
--- /dev/null
+++ b/linux/sound/pci/echoaudio/gina24.c
@@ -0,0 +1,129 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define ECHO24_FAMILY
+#define ECHOCARD_GINA24
+#define ECHOCARD_NAME "Gina24"
+#define ECHOCARD_HAS_MONITOR
+#define ECHOCARD_HAS_ASIC
+#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL
+#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+#define ECHOCARD_HAS_DIGITAL_IO
+#define ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE
+#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH
+#define ECHOCARD_HAS_EXTERNAL_CLOCK
+#define ECHOCARD_HAS_ADAT	6
+#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 8 */
+#define PX_DIGITAL_OUT	8	/* 8 */
+#define PX_ANALOG_IN	16	/* 2 */
+#define PX_DIGITAL_IN	18	/* 8 */
+#define PX_NUM		26
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 8 */
+#define BX_DIGITAL_OUT	8	/* 8 */
+#define BX_ANALOG_IN	16	/* 2 */
+#define BX_DIGITAL_IN	18	/* 8 */
+#define BX_NUM		26
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/loader_dsp.fw");
+MODULE_FIRMWARE("ea/gina24_301_dsp.fw");
+MODULE_FIRMWARE("ea/gina24_361_dsp.fw");
+MODULE_FIRMWARE("ea/gina24_301_asic.fw");
+MODULE_FIRMWARE("ea/gina24_361_asic.fw");
+
+#define FW_361_LOADER		0
+#define FW_GINA24_301_DSP	1
+#define FW_GINA24_361_DSP	2
+#define FW_GINA24_301_ASIC	3
+#define FW_GINA24_361_ASIC	4
+
+static const struct firmware card_fw[] = {
+	{0, "loader_dsp.fw"},
+	{0, "gina24_301_dsp.fw"},
+	{0, "gina24_361_dsp.fw"},
+	{0, "gina24_301_asic.fw"},
+	{0, "gina24_361_asic.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x1801, 0xECC0, 0x0050, 0, 0, 0},	/* DSP 56301 Gina24 rev.0 */
+	{0x1057, 0x1801, 0xECC0, 0x0051, 0, 0, 0},	/* DSP 56301 Gina24 rev.1 */
+	{0x1057, 0x3410, 0xECC0, 0x0050, 0, 0, 0},	/* DSP 56361 Gina24 rev.0 */
+	{0x1057, 0x3410, 0xECC0, 0x0051, 0, 0, 0},	/* DSP 56361 Gina24 rev.1 */
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = 	SNDRV_PCM_RATE_8000_48000 |
+			SNDRV_PCM_RATE_88200 |
+			SNDRV_PCM_RATE_96000,
+	.rate_min = 8000,
+	.rate_max = 96000,
+	.channels_min = 1,
+	.channels_max = 8,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+	/* One page (4k) contains 512 instructions. I don't know if the hw
+	supports lists longer than this. In this case periods_max=220 is a
+	safe limit to make sure the list never exceeds 512 instructions.
+	220 ~= (512 - 1 - (BUFFER_BYTES_MAX / PAGE_SIZE)) / 2 */
+};
+
+#include "gina24_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio_gml.c"
+#include "echoaudio.c"
diff --git a/linux/sound/pci/echoaudio/gina24_dsp.c b/linux/sound/pci/echoaudio/gina24_dsp.c
new file mode 100644
index 0000000..98f7cfa
--- /dev/null
+++ b/linux/sound/pci/echoaudio/gina24_dsp.c
@@ -0,0 +1,349 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+static int write_control_reg(struct echoaudio *chip, u32 value, char force);
+static int set_input_clock(struct echoaudio *chip, u16 clock);
+static int set_professional_spdif(struct echoaudio *chip, char prof);
+static int set_digital_mode(struct echoaudio *chip, u8 mode);
+static int load_asic_generic(struct echoaudio *chip, u32 cmd, short asic);
+static int check_asic_status(struct echoaudio *chip);
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Gina24\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != GINA24))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->input_clock_types =
+		ECHO_CLOCK_BIT_INTERNAL | ECHO_CLOCK_BIT_SPDIF |
+		ECHO_CLOCK_BIT_ESYNC | ECHO_CLOCK_BIT_ESYNC96 |
+		ECHO_CLOCK_BIT_ADAT;
+
+	/* Gina24 comes in both '301 and '361 flavors */
+	if (chip->device_id == DEVICE_ID_56361) {
+		chip->dsp_code_to_load = FW_GINA24_361_DSP;
+		chip->digital_modes =
+			ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA |
+			ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL |
+			ECHOCAPS_HAS_DIGITAL_MODE_ADAT;
+	} else {
+		chip->dsp_code_to_load = FW_GINA24_301_DSP;
+		chip->digital_modes =
+			ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA |
+			ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL |
+			ECHOCAPS_HAS_DIGITAL_MODE_ADAT |
+			ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_CDROM;
+	}
+
+	if ((err = load_firmware(chip)) < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	chip->digital_mode = DIGITAL_MODE_SPDIF_RCA;
+	chip->professional_spdif = FALSE;
+	chip->digital_in_automute = TRUE;
+	return init_line_levels(chip);
+}
+
+
+
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	u32 clocks_from_dsp, clock_bits;
+
+	/* Map the DSP clock detect bits to the generic driver clock
+	   detect bits */
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	clock_bits = ECHO_CLOCK_BIT_INTERNAL;
+
+	if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_SPDIF)
+		clock_bits |= ECHO_CLOCK_BIT_SPDIF;
+
+	if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_ADAT)
+		clock_bits |= ECHO_CLOCK_BIT_ADAT;
+
+	if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_ESYNC)
+		clock_bits |= ECHO_CLOCK_BIT_ESYNC | ECHO_CLOCK_BIT_ESYNC96;
+
+	return clock_bits;
+}
+
+
+
+/* Gina24 has an ASIC on the PCI card which must be loaded for anything
+interesting to happen. */
+static int load_asic(struct echoaudio *chip)
+{
+	u32 control_reg;
+	int err;
+	short asic;
+
+	if (chip->asic_loaded)
+		return 1;
+
+	/* Give the DSP a few milliseconds to settle down */
+	mdelay(10);
+
+	/* Pick the correct ASIC for '301 or '361 Gina24 */
+	if (chip->device_id == DEVICE_ID_56361)
+		asic = FW_GINA24_361_ASIC;
+	else
+		asic = FW_GINA24_301_ASIC;
+
+	err = load_asic_generic(chip, DSP_FNC_LOAD_GINA24_ASIC, asic);
+	if (err < 0)
+		return err;
+
+	chip->asic_code = asic;
+
+	/* Now give the new ASIC a little time to set up */
+	mdelay(10);
+	/* See if it worked */
+	err = check_asic_status(chip);
+
+	/* Set up the control register if the load succeeded -
+	   48 kHz, internal clock, S/PDIF RCA mode */
+	if (!err) {
+		control_reg = GML_CONVERTER_ENABLE | GML_48KHZ;
+		err = write_control_reg(chip, control_reg, TRUE);
+	}
+	DE_INIT(("load_asic() done\n"));
+	return err;
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	u32 control_reg, clock;
+
+	if (snd_BUG_ON(rate >= 50000 &&
+		       chip->digital_mode == DIGITAL_MODE_ADAT))
+		return -EINVAL;
+
+	/* Only set the clock for internal mode. */
+	if (chip->input_clock != ECHO_CLOCK_INTERNAL) {
+		DE_ACT(("set_sample_rate: Cannot set sample rate - "
+			"clock not set to CLK_CLOCKININTERNAL\n"));
+		/* Save the rate anyhow */
+		chip->comm_page->sample_rate = cpu_to_le32(rate);
+		chip->sample_rate = rate;
+		return 0;
+	}
+
+	clock = 0;
+
+	control_reg = le32_to_cpu(chip->comm_page->control_register);
+	control_reg &= GML_CLOCK_CLEAR_MASK & GML_SPDIF_RATE_CLEAR_MASK;
+
+	switch (rate) {
+	case 96000:
+		clock = GML_96KHZ;
+		break;
+	case 88200:
+		clock = GML_88KHZ;
+		break;
+	case 48000:
+		clock = GML_48KHZ | GML_SPDIF_SAMPLE_RATE1;
+		break;
+	case 44100:
+		clock = GML_44KHZ;
+		/* Professional mode ? */
+		if (control_reg & GML_SPDIF_PRO_MODE)
+			clock |= GML_SPDIF_SAMPLE_RATE0;
+		break;
+	case 32000:
+		clock = GML_32KHZ | GML_SPDIF_SAMPLE_RATE0 |
+			GML_SPDIF_SAMPLE_RATE1;
+		break;
+	case 22050:
+		clock = GML_22KHZ;
+		break;
+	case 16000:
+		clock = GML_16KHZ;
+		break;
+	case 11025:
+		clock = GML_11KHZ;
+		break;
+	case 8000:
+		clock = GML_8KHZ;
+		break;
+	default:
+		DE_ACT(("set_sample_rate: %d invalid!\n", rate));
+		return -EINVAL;
+	}
+
+	control_reg |= clock;
+
+	chip->comm_page->sample_rate = cpu_to_le32(rate);	/* ignored by the DSP */
+	chip->sample_rate = rate;
+	DE_ACT(("set_sample_rate: %d clock %d\n", rate, clock));
+
+	return write_control_reg(chip, control_reg, FALSE);
+}
+
+
+
+static int set_input_clock(struct echoaudio *chip, u16 clock)
+{
+	u32 control_reg, clocks_from_dsp;
+
+	DE_ACT(("set_input_clock:\n"));
+
+	/* Mask off the clock select bits */
+	control_reg = le32_to_cpu(chip->comm_page->control_register) &
+		GML_CLOCK_CLEAR_MASK;
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	switch (clock) {
+	case ECHO_CLOCK_INTERNAL:
+		DE_ACT(("Set Gina24 clock to INTERNAL\n"));
+		chip->input_clock = ECHO_CLOCK_INTERNAL;
+		return set_sample_rate(chip, chip->sample_rate);
+	case ECHO_CLOCK_SPDIF:
+		if (chip->digital_mode == DIGITAL_MODE_ADAT)
+			return -EAGAIN;
+		DE_ACT(("Set Gina24 clock to SPDIF\n"));
+		control_reg |= GML_SPDIF_CLOCK;
+		if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_SPDIF96)
+			control_reg |= GML_DOUBLE_SPEED_MODE;
+		else
+			control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		break;
+	case ECHO_CLOCK_ADAT:
+		if (chip->digital_mode != DIGITAL_MODE_ADAT)
+			return -EAGAIN;
+		DE_ACT(("Set Gina24 clock to ADAT\n"));
+		control_reg |= GML_ADAT_CLOCK;
+		control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		break;
+	case ECHO_CLOCK_ESYNC:
+		DE_ACT(("Set Gina24 clock to ESYNC\n"));
+		control_reg |= GML_ESYNC_CLOCK;
+		control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		break;
+	case ECHO_CLOCK_ESYNC96:
+		DE_ACT(("Set Gina24 clock to ESYNC96\n"));
+		control_reg |= GML_ESYNC_CLOCK | GML_DOUBLE_SPEED_MODE;
+		break;
+	default:
+		DE_ACT(("Input clock 0x%x not supported for Gina24\n", clock));
+		return -EINVAL;
+	}
+
+	chip->input_clock = clock;
+	return write_control_reg(chip, control_reg, TRUE);
+}
+
+
+
+static int dsp_set_digital_mode(struct echoaudio *chip, u8 mode)
+{
+	u32 control_reg;
+	int err, incompatible_clock;
+
+	/* Set clock to "internal" if it's not compatible with the new mode */
+	incompatible_clock = FALSE;
+	switch (mode) {
+	case DIGITAL_MODE_SPDIF_OPTICAL:
+	case DIGITAL_MODE_SPDIF_CDROM:
+	case DIGITAL_MODE_SPDIF_RCA:
+		if (chip->input_clock == ECHO_CLOCK_ADAT)
+			incompatible_clock = TRUE;
+		break;
+	case DIGITAL_MODE_ADAT:
+		if (chip->input_clock == ECHO_CLOCK_SPDIF)
+			incompatible_clock = TRUE;
+		break;
+	default:
+		DE_ACT(("Digital mode not supported: %d\n", mode));
+		return -EINVAL;
+	}
+
+	spin_lock_irq(&chip->lock);
+
+	if (incompatible_clock) {	/* Switch to 48KHz, internal */
+		chip->sample_rate = 48000;
+		set_input_clock(chip, ECHO_CLOCK_INTERNAL);
+	}
+
+	/* Clear the current digital mode */
+	control_reg = le32_to_cpu(chip->comm_page->control_register);
+	control_reg &= GML_DIGITAL_MODE_CLEAR_MASK;
+
+	/* Tweak the control reg */
+	switch (mode) {
+	case DIGITAL_MODE_SPDIF_OPTICAL:
+		control_reg |= GML_SPDIF_OPTICAL_MODE;
+		break;
+	case DIGITAL_MODE_SPDIF_CDROM:
+		/* '361 Gina24 cards do not have the S/PDIF CD-ROM mode */
+		if (chip->device_id == DEVICE_ID_56301)
+			control_reg |= GML_SPDIF_CDROM_MODE;
+		break;
+	case DIGITAL_MODE_SPDIF_RCA:
+		/* GML_SPDIF_OPTICAL_MODE bit cleared */
+		break;
+	case DIGITAL_MODE_ADAT:
+		control_reg |= GML_ADAT_MODE;
+		control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		break;
+	}
+
+	err = write_control_reg(chip, control_reg, TRUE);
+	spin_unlock_irq(&chip->lock);
+	if (err < 0)
+		return err;
+	chip->digital_mode = mode;
+
+	DE_ACT(("set_digital_mode to %d\n", chip->digital_mode));
+	return incompatible_clock;
+}
diff --git a/linux/sound/pci/echoaudio/indigo.c b/linux/sound/pci/echoaudio/indigo.c
new file mode 100644
index 0000000..4ae5e35
--- /dev/null
+++ b/linux/sound/pci/echoaudio/indigo.c
@@ -0,0 +1,107 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define INDIGO_FAMILY
+#define ECHOCARD_INDIGO
+#define ECHOCARD_NAME "Indigo"
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+#define ECHOCARD_HAS_VMIXER
+#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 8 */
+#define PX_DIGITAL_OUT	8	/* 0 */
+#define PX_ANALOG_IN	8	/* 0 */
+#define PX_DIGITAL_IN	8	/* 0 */
+#define PX_NUM		8
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 2 */
+#define BX_DIGITAL_OUT	2	/* 0 */
+#define BX_ANALOG_IN	2	/* 0 */
+#define BX_DIGITAL_IN	2	/* 0 */
+#define BX_NUM		2
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/loader_dsp.fw");
+MODULE_FIRMWARE("ea/indigo_dsp.fw");
+
+#define FW_361_LOADER	0
+#define FW_INDIGO_DSP	1
+
+static const struct firmware card_fw[] = {
+	{0, "loader_dsp.fw"},
+	{0, "indigo_dsp.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x3410, 0xECC0, 0x0090, 0, 0, 0},	/* Indigo */
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = 	SNDRV_PCM_RATE_32000 |
+			SNDRV_PCM_RATE_44100 |
+			SNDRV_PCM_RATE_48000 |
+			SNDRV_PCM_RATE_88200 |
+			SNDRV_PCM_RATE_96000,
+	.rate_min = 32000,
+	.rate_max = 96000,
+	.channels_min = 1,
+	.channels_max = 8,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+};
+
+#include "indigo_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio.c"
+
diff --git a/linux/sound/pci/echoaudio/indigo_dsp.c b/linux/sound/pci/echoaudio/indigo_dsp.c
new file mode 100644
index 0000000..5e85f14
--- /dev/null
+++ b/linux/sound/pci/echoaudio/indigo_dsp.c
@@ -0,0 +1,164 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe,
+			   int gain);
+static int update_vmixer_level(struct echoaudio *chip);
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Indigo\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != INDIGO))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->dsp_code_to_load = FW_INDIGO_DSP;
+	/* Since this card has no ASIC, mark it as loaded so everything
+	   works OK */
+	chip->asic_loaded = TRUE;
+	chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL;
+
+	if ((err = load_firmware(chip)) < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	return init_line_levels(chip);
+}
+
+
+
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	return ECHO_CLOCK_BIT_INTERNAL;
+}
+
+
+
+/* The Indigo has no ASIC. Just do nothing */
+static int load_asic(struct echoaudio *chip)
+{
+	return 0;
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	u32 control_reg;
+
+	switch (rate) {
+	case 96000:
+		control_reg = MIA_96000;
+		break;
+	case 88200:
+		control_reg = MIA_88200;
+		break;
+	case 48000:
+		control_reg = MIA_48000;
+		break;
+	case 44100:
+		control_reg = MIA_44100;
+		break;
+	case 32000:
+		control_reg = MIA_32000;
+		break;
+	default:
+		DE_ACT(("set_sample_rate: %d invalid!\n", rate));
+		return -EINVAL;
+	}
+
+	/* Set the control register if it has changed */
+	if (control_reg != le32_to_cpu(chip->comm_page->control_register)) {
+		if (wait_handshake(chip))
+			return -EIO;
+
+		chip->comm_page->sample_rate = cpu_to_le32(rate);	/* ignored by the DSP */
+		chip->comm_page->control_register = cpu_to_le32(control_reg);
+		chip->sample_rate = rate;
+
+		clear_handshake(chip);
+		return send_vector(chip, DSP_VC_UPDATE_CLOCKS);
+	}
+	return 0;
+}
+
+
+
+/* This function routes the sound from a virtual channel to a real output */
+static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe,
+			   int gain)
+{
+	int index;
+
+	if (snd_BUG_ON(pipe >= num_pipes_out(chip) ||
+		       output >= num_busses_out(chip)))
+		return -EINVAL;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->vmixer_gain[output][pipe] = gain;
+	index = output * num_pipes_out(chip) + pipe;
+	chip->comm_page->vmixer[index] = gain;
+
+	DE_ACT(("set_vmixer_gain: pipe %d, out %d = %d\n", pipe, output, gain));
+	return 0;
+}
+
+
+
+/* Tell the DSP to read and update virtual mixer levels in comm page. */
+static int update_vmixer_level(struct echoaudio *chip)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_SET_VMIXER_GAIN);
+}
+
diff --git a/linux/sound/pci/echoaudio/indigo_express_dsp.c b/linux/sound/pci/echoaudio/indigo_express_dsp.c
new file mode 100644
index 0000000..2e4ab3e
--- /dev/null
+++ b/linux/sound/pci/echoaudio/indigo_express_dsp.c
@@ -0,0 +1,120 @@
+/************************************************************************
+
+This file is part of Echo Digital Audio's generic driver library.
+Copyright Echo Digital Audio Corporation (c) 1998 - 2005
+All rights reserved
+www.echoaudio.com
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library 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
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser 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
+
+*************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+*************************************************************************/
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	u32 clock, control_reg, old_control_reg;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	old_control_reg = le32_to_cpu(chip->comm_page->control_register);
+	control_reg = old_control_reg & ~INDIGO_EXPRESS_CLOCK_MASK;
+
+	switch (rate) {
+	case 32000:
+		clock = INDIGO_EXPRESS_32000;
+		break;
+	case 44100:
+		clock = INDIGO_EXPRESS_44100;
+		break;
+	case 48000:
+		clock = INDIGO_EXPRESS_48000;
+		break;
+	case 64000:
+		clock = INDIGO_EXPRESS_32000|INDIGO_EXPRESS_DOUBLE_SPEED;
+		break;
+	case 88200:
+		clock = INDIGO_EXPRESS_44100|INDIGO_EXPRESS_DOUBLE_SPEED;
+		break;
+	case 96000:
+		clock = INDIGO_EXPRESS_48000|INDIGO_EXPRESS_DOUBLE_SPEED;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	control_reg |= clock;
+	if (control_reg != old_control_reg) {
+		DE_ACT(("set_sample_rate: %d clock %d\n", rate, clock));
+		chip->comm_page->control_register = cpu_to_le32(control_reg);
+		chip->sample_rate = rate;
+		clear_handshake(chip);
+		return send_vector(chip, DSP_VC_UPDATE_CLOCKS);
+	}
+	return 0;
+}
+
+
+
+/* This function routes the sound from a virtual channel to a real output */
+static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe,
+			   int gain)
+{
+	int index;
+
+	if (snd_BUG_ON(pipe >= num_pipes_out(chip) ||
+		       output >= num_busses_out(chip)))
+		return -EINVAL;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->vmixer_gain[output][pipe] = gain;
+	index = output * num_pipes_out(chip) + pipe;
+	chip->comm_page->vmixer[index] = gain;
+
+	DE_ACT(("set_vmixer_gain: pipe %d, out %d = %d\n", pipe, output, gain));
+	return 0;
+}
+
+
+
+/* Tell the DSP to read and update virtual mixer levels in comm page. */
+static int update_vmixer_level(struct echoaudio *chip)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_SET_VMIXER_GAIN);
+}
+
+
+
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	return ECHO_CLOCK_BIT_INTERNAL;
+}
+
+
+
+/* The IndigoIO has no ASIC. Just do nothing */
+static int load_asic(struct echoaudio *chip)
+{
+	return 0;
+}
diff --git a/linux/sound/pci/echoaudio/indigodj.c b/linux/sound/pci/echoaudio/indigodj.c
new file mode 100644
index 0000000..3550715
--- /dev/null
+++ b/linux/sound/pci/echoaudio/indigodj.c
@@ -0,0 +1,107 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define INDIGO_FAMILY
+#define ECHOCARD_INDIGO_DJ
+#define ECHOCARD_NAME "Indigo DJ"
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+#define ECHOCARD_HAS_VMIXER
+#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 8 */
+#define PX_DIGITAL_OUT	8	/* 0 */
+#define PX_ANALOG_IN	8	/* 0 */
+#define PX_DIGITAL_IN	8	/* 0 */
+#define PX_NUM		8
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 4 */
+#define BX_DIGITAL_OUT	4	/* 0 */
+#define BX_ANALOG_IN	4	/* 0 */
+#define BX_DIGITAL_IN	4	/* 0 */
+#define BX_NUM		4
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/loader_dsp.fw");
+MODULE_FIRMWARE("ea/indigo_dj_dsp.fw");
+
+#define FW_361_LOADER		0
+#define FW_INDIGO_DJ_DSP	1
+
+static const struct firmware card_fw[] = {
+	{0, "loader_dsp.fw"},
+	{0, "indigo_dj_dsp.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x3410, 0xECC0, 0x00B0, 0, 0, 0},	/* Indigo DJ*/
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = 	SNDRV_PCM_RATE_32000 |
+			SNDRV_PCM_RATE_44100 |
+			SNDRV_PCM_RATE_48000 |
+			SNDRV_PCM_RATE_88200 |
+			SNDRV_PCM_RATE_96000,
+	.rate_min = 32000,
+	.rate_max = 96000,
+	.channels_min = 1,
+	.channels_max = 4,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+};
+
+#include "indigodj_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio.c"
+
diff --git a/linux/sound/pci/echoaudio/indigodj_dsp.c b/linux/sound/pci/echoaudio/indigodj_dsp.c
new file mode 100644
index 0000000..68f3c8c
--- /dev/null
+++ b/linux/sound/pci/echoaudio/indigodj_dsp.c
@@ -0,0 +1,164 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe,
+			   int gain);
+static int update_vmixer_level(struct echoaudio *chip);
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Indigo DJ\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != INDIGO_DJ))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->dsp_code_to_load = FW_INDIGO_DJ_DSP;
+	/* Since this card has no ASIC, mark it as loaded so everything
+	   works OK */
+	chip->asic_loaded = TRUE;
+	chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL;
+
+	if ((err = load_firmware(chip)) < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	return init_line_levels(chip);
+}
+
+
+
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	return ECHO_CLOCK_BIT_INTERNAL;
+}
+
+
+
+/* The IndigoDJ has no ASIC. Just do nothing */
+static int load_asic(struct echoaudio *chip)
+{
+	return 0;
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	u32 control_reg;
+
+	switch (rate) {
+	case 96000:
+		control_reg = MIA_96000;
+		break;
+	case 88200:
+		control_reg = MIA_88200;
+		break;
+	case 48000:
+		control_reg = MIA_48000;
+		break;
+	case 44100:
+		control_reg = MIA_44100;
+		break;
+	case 32000:
+		control_reg = MIA_32000;
+		break;
+	default:
+		DE_ACT(("set_sample_rate: %d invalid!\n", rate));
+		return -EINVAL;
+	}
+
+	/* Set the control register if it has changed */
+	if (control_reg != le32_to_cpu(chip->comm_page->control_register)) {
+		if (wait_handshake(chip))
+			return -EIO;
+
+		chip->comm_page->sample_rate = cpu_to_le32(rate);	/* ignored by the DSP */
+		chip->comm_page->control_register = cpu_to_le32(control_reg);
+		chip->sample_rate = rate;
+
+		clear_handshake(chip);
+		return send_vector(chip, DSP_VC_UPDATE_CLOCKS);
+	}
+	return 0;
+}
+
+
+
+/* This function routes the sound from a virtual channel to a real output */
+static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe,
+			   int gain)
+{
+	int index;
+
+	if (snd_BUG_ON(pipe >= num_pipes_out(chip) ||
+		       output >= num_busses_out(chip)))
+		return -EINVAL;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->vmixer_gain[output][pipe] = gain;
+	index = output * num_pipes_out(chip) + pipe;
+	chip->comm_page->vmixer[index] = gain;
+
+	DE_ACT(("set_vmixer_gain: pipe %d, out %d = %d\n", pipe, output, gain));
+	return 0;
+}
+
+
+
+/* Tell the DSP to read and update virtual mixer levels in comm page. */
+static int update_vmixer_level(struct echoaudio *chip)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_SET_VMIXER_GAIN);
+}
+
diff --git a/linux/sound/pci/echoaudio/indigodjx.c b/linux/sound/pci/echoaudio/indigodjx.c
new file mode 100644
index 0000000..19b191f
--- /dev/null
+++ b/linux/sound/pci/echoaudio/indigodjx.c
@@ -0,0 +1,108 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2009 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define INDIGO_FAMILY
+#define ECHOCARD_INDIGO_DJX
+#define ECHOCARD_NAME "Indigo DJx"
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+#define ECHOCARD_HAS_VMIXER
+#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 8 */
+#define PX_DIGITAL_OUT	8	/* 0 */
+#define PX_ANALOG_IN	8	/* 0 */
+#define PX_DIGITAL_IN	8	/* 0 */
+#define PX_NUM		8
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 4 */
+#define BX_DIGITAL_OUT	4	/* 0 */
+#define BX_ANALOG_IN	4	/* 0 */
+#define BX_DIGITAL_IN	4	/* 0 */
+#define BX_NUM		4
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/loader_dsp.fw");
+MODULE_FIRMWARE("ea/indigo_djx_dsp.fw");
+
+#define FW_361_LOADER		0
+#define FW_INDIGO_DJX_DSP	1
+
+static const struct firmware card_fw[] = {
+	{0, "loader_dsp.fw"},
+	{0, "indigo_djx_dsp.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x3410, 0xECC0, 0x00E0, 0, 0, 0},	/* Indigo DJx*/
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = 	SNDRV_PCM_RATE_32000 |
+			SNDRV_PCM_RATE_44100 |
+			SNDRV_PCM_RATE_48000 |
+			SNDRV_PCM_RATE_64000 |
+			SNDRV_PCM_RATE_88200 |
+			SNDRV_PCM_RATE_96000,
+	.rate_min = 32000,
+	.rate_max = 96000,
+	.channels_min = 1,
+	.channels_max = 4,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+};
+
+#include "indigodjx_dsp.c"
+#include "indigo_express_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio.c"
diff --git a/linux/sound/pci/echoaudio/indigodjx_dsp.c b/linux/sound/pci/echoaudio/indigodjx_dsp.c
new file mode 100644
index 0000000..bb9632c
--- /dev/null
+++ b/linux/sound/pci/echoaudio/indigodjx_dsp.c
@@ -0,0 +1,71 @@
+/************************************************************************
+
+This file is part of Echo Digital Audio's generic driver library.
+Copyright Echo Digital Audio Corporation (c) 1998 - 2005
+All rights reserved
+www.echoaudio.com
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library 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
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser 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
+
+*************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+*************************************************************************/
+
+static int update_vmixer_level(struct echoaudio *chip);
+static int set_vmixer_gain(struct echoaudio *chip, u16 output,
+			   u16 pipe, int gain);
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Indigo DJx\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != INDIGO_DJX))
+		return -ENODEV;
+
+	err = init_dsp_comm_page(chip);
+	if (err < 0) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->dsp_code_to_load = FW_INDIGO_DJX_DSP;
+	/* Since this card has no ASIC, mark it as loaded so everything
+	   works OK */
+	chip->asic_loaded = TRUE;
+	chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL;
+
+	err = load_firmware(chip);
+	if (err < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	return init_line_levels(chip);
+}
diff --git a/linux/sound/pci/echoaudio/indigoio.c b/linux/sound/pci/echoaudio/indigoio.c
new file mode 100644
index 0000000..a9fcedf
--- /dev/null
+++ b/linux/sound/pci/echoaudio/indigoio.c
@@ -0,0 +1,108 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define INDIGO_FAMILY
+#define ECHOCARD_INDIGO_IO
+#define ECHOCARD_NAME "Indigo IO"
+#define ECHOCARD_HAS_MONITOR
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+#define ECHOCARD_HAS_VMIXER
+#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 8 */
+#define PX_DIGITAL_OUT	8	/* 0 */
+#define PX_ANALOG_IN	8	/* 2 */
+#define PX_DIGITAL_IN	10	/* 0 */
+#define PX_NUM		10
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 2 */
+#define BX_DIGITAL_OUT	2	/* 0 */
+#define BX_ANALOG_IN	2	/* 2 */
+#define BX_DIGITAL_IN	4	/* 0 */
+#define BX_NUM		4
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/loader_dsp.fw");
+MODULE_FIRMWARE("ea/indigo_io_dsp.fw");
+
+#define FW_361_LOADER		0
+#define FW_INDIGO_IO_DSP	1
+
+static const struct firmware card_fw[] = {
+	{0, "loader_dsp.fw"},
+	{0, "indigo_io_dsp.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x3410, 0xECC0, 0x00A0, 0, 0, 0},	/* Indigo IO*/
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = 	SNDRV_PCM_RATE_32000 |
+			SNDRV_PCM_RATE_44100 |
+			SNDRV_PCM_RATE_48000 |
+			SNDRV_PCM_RATE_88200 |
+			SNDRV_PCM_RATE_96000,
+	.rate_min = 32000,
+	.rate_max = 96000,
+	.channels_min = 1,
+	.channels_max = 8,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+};
+
+#include "indigoio_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio.c"
+
diff --git a/linux/sound/pci/echoaudio/indigoio_dsp.c b/linux/sound/pci/echoaudio/indigoio_dsp.c
new file mode 100644
index 0000000..beb9a5b
--- /dev/null
+++ b/linux/sound/pci/echoaudio/indigoio_dsp.c
@@ -0,0 +1,135 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe,
+			   int gain);
+static int update_vmixer_level(struct echoaudio *chip);
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Indigo IO\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != INDIGO_IO))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->dsp_code_to_load = FW_INDIGO_IO_DSP;
+	/* Since this card has no ASIC, mark it as loaded so everything
+	   works OK */
+	chip->asic_loaded = TRUE;
+	chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL;
+
+	if ((err = load_firmware(chip)) < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	return init_line_levels(chip);
+}
+
+
+
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	return ECHO_CLOCK_BIT_INTERNAL;
+}
+
+
+
+/* The IndigoIO has no ASIC. Just do nothing */
+static int load_asic(struct echoaudio *chip)
+{
+	return 0;
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->sample_rate = rate;
+	chip->comm_page->sample_rate = cpu_to_le32(rate);
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_UPDATE_CLOCKS);
+}
+
+
+
+/* This function routes the sound from a virtual channel to a real output */
+static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe,
+			   int gain)
+{
+	int index;
+
+	if (snd_BUG_ON(pipe >= num_pipes_out(chip) ||
+		       output >= num_busses_out(chip)))
+		return -EINVAL;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->vmixer_gain[output][pipe] = gain;
+	index = output * num_pipes_out(chip) + pipe;
+	chip->comm_page->vmixer[index] = gain;
+
+	DE_ACT(("set_vmixer_gain: pipe %d, out %d = %d\n", pipe, output, gain));
+	return 0;
+}
+
+
+
+/* Tell the DSP to read and update virtual mixer levels in comm page. */
+static int update_vmixer_level(struct echoaudio *chip)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_SET_VMIXER_GAIN);
+}
+
diff --git a/linux/sound/pci/echoaudio/indigoiox.c b/linux/sound/pci/echoaudio/indigoiox.c
new file mode 100644
index 0000000..bcdfac6
--- /dev/null
+++ b/linux/sound/pci/echoaudio/indigoiox.c
@@ -0,0 +1,110 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2009 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define INDIGO_FAMILY
+#define ECHOCARD_INDIGO_IOX
+#define ECHOCARD_NAME "Indigo IOx"
+#define ECHOCARD_HAS_MONITOR
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+#define ECHOCARD_HAS_VMIXER
+#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 8 */
+#define PX_DIGITAL_OUT	8	/* 0 */
+#define PX_ANALOG_IN	8	/* 2 */
+#define PX_DIGITAL_IN	10	/* 0 */
+#define PX_NUM		10
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 2 */
+#define BX_DIGITAL_OUT	2	/* 0 */
+#define BX_ANALOG_IN	2	/* 2 */
+#define BX_DIGITAL_IN	4	/* 0 */
+#define BX_NUM		4
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/loader_dsp.fw");
+MODULE_FIRMWARE("ea/indigo_iox_dsp.fw");
+
+#define FW_361_LOADER		0
+#define FW_INDIGO_IOX_DSP	1
+
+static const struct firmware card_fw[] = {
+	{0, "loader_dsp.fw"},
+	{0, "indigo_iox_dsp.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x3410, 0xECC0, 0x00D0, 0, 0, 0},	/* Indigo IOx */
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = 	SNDRV_PCM_RATE_32000 |
+			SNDRV_PCM_RATE_44100 |
+			SNDRV_PCM_RATE_48000 |
+			SNDRV_PCM_RATE_64000 |
+			SNDRV_PCM_RATE_88200 |
+			SNDRV_PCM_RATE_96000,
+	.rate_min = 32000,
+	.rate_max = 96000,
+	.channels_min = 1,
+	.channels_max = 8,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+};
+
+#include "indigoiox_dsp.c"
+#include "indigo_express_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio.c"
+
diff --git a/linux/sound/pci/echoaudio/indigoiox_dsp.c b/linux/sound/pci/echoaudio/indigoiox_dsp.c
new file mode 100644
index 0000000..394c6e7
--- /dev/null
+++ b/linux/sound/pci/echoaudio/indigoiox_dsp.c
@@ -0,0 +1,71 @@
+/************************************************************************
+
+This file is part of Echo Digital Audio's generic driver library.
+Copyright Echo Digital Audio Corporation (c) 1998 - 2005
+All rights reserved
+www.echoaudio.com
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library 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
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser 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
+
+*************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+*************************************************************************/
+
+static int update_vmixer_level(struct echoaudio *chip);
+static int set_vmixer_gain(struct echoaudio *chip, u16 output,
+			   u16 pipe, int gain);
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Indigo IOx\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != INDIGO_IOX))
+		return -ENODEV;
+
+	err = init_dsp_comm_page(chip);
+	if (err < 0) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->dsp_code_to_load = FW_INDIGO_IOX_DSP;
+	/* Since this card has no ASIC, mark it as loaded so everything
+	   works OK */
+	chip->asic_loaded = TRUE;
+	chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL;
+
+	err = load_firmware(chip);
+	if (err < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	return init_line_levels(chip);
+}
diff --git a/linux/sound/pci/echoaudio/layla20.c b/linux/sound/pci/echoaudio/layla20.c
new file mode 100644
index 0000000..d3a98c5
--- /dev/null
+++ b/linux/sound/pci/echoaudio/layla20.c
@@ -0,0 +1,115 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define ECHOGALS_FAMILY
+#define ECHOCARD_LAYLA20
+#define ECHOCARD_NAME "Layla20"
+#define ECHOCARD_HAS_MONITOR
+#define ECHOCARD_HAS_ASIC
+#define ECHOCARD_HAS_INPUT_GAIN
+#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+#define ECHOCARD_HAS_DIGITAL_IO
+#define ECHOCARD_HAS_EXTERNAL_CLOCK
+#define ECHOCARD_HAS_ADAT	FALSE
+#define ECHOCARD_HAS_OUTPUT_CLOCK_SWITCH
+#define ECHOCARD_HAS_MIDI
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 10 */
+#define PX_DIGITAL_OUT	10	/*  2 */
+#define PX_ANALOG_IN	12	/*  8 */
+#define PX_DIGITAL_IN	20	/*  2 */
+#define PX_NUM		22
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 10 */
+#define BX_DIGITAL_OUT	10	/*  2 */
+#define BX_ANALOG_IN	12	/*  8 */
+#define BX_DIGITAL_IN	20	/*  2 */
+#define BX_NUM		22
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/layla20_dsp.fw");
+MODULE_FIRMWARE("ea/layla20_asic.fw");
+
+#define FW_LAYLA20_DSP	0
+#define FW_LAYLA20_ASIC	1
+
+static const struct firmware card_fw[] = {
+	{0, "layla20_dsp.fw"},
+	{0, "layla20_asic.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x1801, 0xECC0, 0x0030, 0, 0, 0},	/* DSP 56301 Layla20 rev.0 */
+	{0x1057, 0x1801, 0xECC0, 0x0031, 0, 0, 0},	/* DSP 56301 Layla20 rev.1 */
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS,
+	.rate_min = 8000,
+	.rate_max = 50000,
+	.channels_min = 1,
+	.channels_max = 10,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+	/* One page (4k) contains 512 instructions. I don't know if the hw
+	supports lists longer than this. In this case periods_max=220 is a
+	safe limit to make sure the list never exceeds 512 instructions. */
+};
+
+#include "layla20_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio.c"
+#include "midi.c"
diff --git a/linux/sound/pci/echoaudio/layla20_dsp.c b/linux/sound/pci/echoaudio/layla20_dsp.c
new file mode 100644
index 0000000..53ce946
--- /dev/null
+++ b/linux/sound/pci/echoaudio/layla20_dsp.c
@@ -0,0 +1,295 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+static int read_dsp(struct echoaudio *chip, u32 *data);
+static int set_professional_spdif(struct echoaudio *chip, char prof);
+static int load_asic_generic(struct echoaudio *chip, u32 cmd, short asic);
+static int check_asic_status(struct echoaudio *chip);
+static int update_flags(struct echoaudio *chip);
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Layla20\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != LAYLA20))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->has_midi = TRUE;
+	chip->dsp_code_to_load = FW_LAYLA20_DSP;
+	chip->input_clock_types =
+		ECHO_CLOCK_BIT_INTERNAL | ECHO_CLOCK_BIT_SPDIF |
+		ECHO_CLOCK_BIT_WORD | ECHO_CLOCK_BIT_SUPER;
+	chip->output_clock_types =
+		ECHO_CLOCK_BIT_WORD | ECHO_CLOCK_BIT_SUPER;
+
+	if ((err = load_firmware(chip)) < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	chip->professional_spdif = FALSE;
+	return init_line_levels(chip);
+}
+
+
+
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	u32 clocks_from_dsp, clock_bits;
+
+	/* Map the DSP clock detect bits to the generic driver clock detect bits */
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	clock_bits = ECHO_CLOCK_BIT_INTERNAL;
+
+	if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_SPDIF)
+		clock_bits |= ECHO_CLOCK_BIT_SPDIF;
+
+	if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_WORD) {
+		if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_SUPER)
+			clock_bits |= ECHO_CLOCK_BIT_SUPER;
+		else
+			clock_bits |= ECHO_CLOCK_BIT_WORD;
+	}
+
+	return clock_bits;
+}
+
+
+
+/* ASIC status check - some cards have one or two ASICs that need to be
+loaded.  Once that load is complete, this function is called to see if
+the load was successful.
+If this load fails, it does not necessarily mean that the hardware is
+defective - the external box may be disconnected or turned off.
+This routine sometimes fails for Layla20; for Layla20, the loop runs
+5 times and succeeds if it wins on three of the loops. */
+static int check_asic_status(struct echoaudio *chip)
+{
+	u32 asic_status;
+	int goodcnt, i;
+
+	chip->asic_loaded = FALSE;
+	for (i = goodcnt = 0; i < 5; i++) {
+		send_vector(chip, DSP_VC_TEST_ASIC);
+
+		/* The DSP will return a value to indicate whether or not
+		   the ASIC is currently loaded */
+		if (read_dsp(chip, &asic_status) < 0) {
+			DE_ACT(("check_asic_status: failed on read_dsp\n"));
+			return -EIO;
+		}
+
+		if (asic_status == ASIC_ALREADY_LOADED) {
+			if (++goodcnt == 3) {
+				chip->asic_loaded = TRUE;
+				return 0;
+			}
+		}
+	}
+	return -EIO;
+}
+
+
+
+/* Layla20 has an ASIC in the external box */
+static int load_asic(struct echoaudio *chip)
+{
+	int err;
+
+	if (chip->asic_loaded)
+		return 0;
+
+	err = load_asic_generic(chip, DSP_FNC_LOAD_LAYLA_ASIC,
+				FW_LAYLA20_ASIC);
+	if (err < 0)
+		return err;
+
+	/* Check if ASIC is alive and well. */
+	return check_asic_status(chip);
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	if (snd_BUG_ON(rate < 8000 || rate > 50000))
+		return -EINVAL;
+
+	/* Only set the clock for internal mode. Do not return failure,
+	   simply treat it as a non-event. */
+	if (chip->input_clock != ECHO_CLOCK_INTERNAL) {
+		DE_ACT(("set_sample_rate: Cannot set sample rate - "
+			"clock not set to CLK_CLOCKININTERNAL\n"));
+		chip->comm_page->sample_rate = cpu_to_le32(rate);
+		chip->sample_rate = rate;
+		return 0;
+	}
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	DE_ACT(("set_sample_rate(%d)\n", rate));
+	chip->sample_rate = rate;
+	chip->comm_page->sample_rate = cpu_to_le32(rate);
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_SET_LAYLA_SAMPLE_RATE);
+}
+
+
+
+static int set_input_clock(struct echoaudio *chip, u16 clock_source)
+{
+	u16 clock;
+	u32 rate;
+
+	DE_ACT(("set_input_clock:\n"));
+	rate = 0;
+	switch (clock_source) {
+	case ECHO_CLOCK_INTERNAL:
+		DE_ACT(("Set Layla20 clock to INTERNAL\n"));
+		rate = chip->sample_rate;
+		clock = LAYLA20_CLOCK_INTERNAL;
+		break;
+	case ECHO_CLOCK_SPDIF:
+		DE_ACT(("Set Layla20 clock to SPDIF\n"));
+		clock = LAYLA20_CLOCK_SPDIF;
+		break;
+	case ECHO_CLOCK_WORD:
+		DE_ACT(("Set Layla20 clock to WORD\n"));
+		clock = LAYLA20_CLOCK_WORD;
+		break;
+	case ECHO_CLOCK_SUPER:
+		DE_ACT(("Set Layla20 clock to SUPER\n"));
+		clock = LAYLA20_CLOCK_SUPER;
+		break;
+	default:
+		DE_ACT(("Input clock 0x%x not supported for Layla24\n",
+			clock_source));
+		return -EINVAL;
+	}
+	chip->input_clock = clock_source;
+
+	chip->comm_page->input_clock = cpu_to_le16(clock);
+	clear_handshake(chip);
+	send_vector(chip, DSP_VC_UPDATE_CLOCKS);
+
+	if (rate)
+		set_sample_rate(chip, rate);
+
+	return 0;
+}
+
+
+
+static int set_output_clock(struct echoaudio *chip, u16 clock)
+{
+	DE_ACT(("set_output_clock: %d\n", clock));
+	switch (clock) {
+	case ECHO_CLOCK_SUPER:
+		clock = LAYLA20_OUTPUT_CLOCK_SUPER;
+		break;
+	case ECHO_CLOCK_WORD:
+		clock = LAYLA20_OUTPUT_CLOCK_WORD;
+		break;
+	default:
+		DE_ACT(("set_output_clock wrong clock\n"));
+		return -EINVAL;
+	}
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->comm_page->output_clock = cpu_to_le16(clock);
+	chip->output_clock = clock;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_UPDATE_CLOCKS);
+}
+
+
+
+/* Set input bus gain (one unit is 0.5dB !) */
+static int set_input_gain(struct echoaudio *chip, u16 input, int gain)
+{
+	if (snd_BUG_ON(input >= num_busses_in(chip)))
+		return -EINVAL;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->input_gain[input] = gain;
+	gain += GL20_INPUT_GAIN_MAGIC_NUMBER;
+	chip->comm_page->line_in_level[input] = gain;
+	return 0;
+}
+
+
+
+/* Tell the DSP to reread the flags from the comm page */
+static int update_flags(struct echoaudio *chip)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_UPDATE_FLAGS);
+}
+
+
+
+static int set_professional_spdif(struct echoaudio *chip, char prof)
+{
+	DE_ACT(("set_professional_spdif %d\n", prof));
+	if (prof)
+		chip->comm_page->flags |=
+			cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF);
+	else
+		chip->comm_page->flags &=
+			~cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF);
+	chip->professional_spdif = prof;
+	return update_flags(chip);
+}
diff --git a/linux/sound/pci/echoaudio/layla24.c b/linux/sound/pci/echoaudio/layla24.c
new file mode 100644
index 0000000..2a1dca6
--- /dev/null
+++ b/linux/sound/pci/echoaudio/layla24.c
@@ -0,0 +1,127 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define ECHO24_FAMILY
+#define ECHOCARD_LAYLA24
+#define ECHOCARD_NAME "Layla24"
+#define ECHOCARD_HAS_MONITOR
+#define ECHOCARD_HAS_ASIC
+#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL
+#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+#define ECHOCARD_HAS_DIGITAL_IO
+#define ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE
+#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH
+#define ECHOCARD_HAS_EXTERNAL_CLOCK
+#define ECHOCARD_HAS_ADAT	6
+#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+#define ECHOCARD_HAS_MIDI
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 8 */
+#define PX_DIGITAL_OUT	8	/* 8 */
+#define PX_ANALOG_IN	16	/* 8 */
+#define PX_DIGITAL_IN	24	/* 8 */
+#define PX_NUM		32
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 8 */
+#define BX_DIGITAL_OUT	8	/* 8 */
+#define BX_ANALOG_IN	16	/* 8 */
+#define BX_DIGITAL_IN	24	/* 8 */
+#define BX_NUM		32
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/loader_dsp.fw");
+MODULE_FIRMWARE("ea/layla24_dsp.fw");
+MODULE_FIRMWARE("ea/layla24_1_asic.fw");
+MODULE_FIRMWARE("ea/layla24_2A_asic.fw");
+MODULE_FIRMWARE("ea/layla24_2S_asic.fw");
+
+#define FW_361_LOADER		0
+#define FW_LAYLA24_DSP		1
+#define FW_LAYLA24_1_ASIC	2
+#define FW_LAYLA24_2A_ASIC	3
+#define FW_LAYLA24_2S_ASIC	4
+
+static const struct firmware card_fw[] = {
+	{0, "loader_dsp.fw"},
+	{0, "layla24_dsp.fw"},
+	{0, "layla24_1_asic.fw"},
+	{0, "layla24_2A_asic.fw"},
+	{0, "layla24_2S_asic.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x3410, 0xECC0, 0x0060, 0, 0, 0},	/* DSP 56361 Layla24 rev.0 */
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates =	SNDRV_PCM_RATE_8000_96000,
+	.rate_min = 8000,
+	.rate_max = 100000,
+	.channels_min = 1,
+	.channels_max = 8,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+	/* One page (4k) contains 512 instructions. I don't know if the hw
+	supports lists longer than this. In this case periods_max=220 is a
+	safe limit to make sure the list never exceeds 512 instructions. */
+};
+
+
+#include "layla24_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio_gml.c"
+#include "echoaudio.c"
+#include "midi.c"
diff --git a/linux/sound/pci/echoaudio/layla24_dsp.c b/linux/sound/pci/echoaudio/layla24_dsp.c
new file mode 100644
index 0000000..8c04164
--- /dev/null
+++ b/linux/sound/pci/echoaudio/layla24_dsp.c
@@ -0,0 +1,398 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+static int write_control_reg(struct echoaudio *chip, u32 value, char force);
+static int set_input_clock(struct echoaudio *chip, u16 clock);
+static int set_professional_spdif(struct echoaudio *chip, char prof);
+static int set_digital_mode(struct echoaudio *chip, u8 mode);
+static int load_asic_generic(struct echoaudio *chip, u32 cmd, short asic);
+static int check_asic_status(struct echoaudio *chip);
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Layla24\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != LAYLA24))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->has_midi = TRUE;
+	chip->dsp_code_to_load = FW_LAYLA24_DSP;
+	chip->input_clock_types =
+		ECHO_CLOCK_BIT_INTERNAL | ECHO_CLOCK_BIT_SPDIF |
+		ECHO_CLOCK_BIT_WORD | ECHO_CLOCK_BIT_ADAT;
+	chip->digital_modes =
+		ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA |
+		ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL |
+		ECHOCAPS_HAS_DIGITAL_MODE_ADAT;
+
+	if ((err = load_firmware(chip)) < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	if ((err = init_line_levels(chip)) < 0)
+		return err;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	chip->digital_mode = DIGITAL_MODE_SPDIF_RCA;
+	chip->professional_spdif = FALSE;
+	chip->digital_in_automute = TRUE;
+	return init_line_levels(chip);
+}
+
+
+
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	u32 clocks_from_dsp, clock_bits;
+
+	/* Map the DSP clock detect bits to the generic driver clock detect bits */
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	clock_bits = ECHO_CLOCK_BIT_INTERNAL;
+
+	if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_SPDIF)
+		clock_bits |= ECHO_CLOCK_BIT_SPDIF;
+
+	if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_ADAT)
+		clock_bits |= ECHO_CLOCK_BIT_ADAT;
+
+	if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_WORD)
+		clock_bits |= ECHO_CLOCK_BIT_WORD;
+
+	return clock_bits;
+}
+
+
+
+/* Layla24 has an ASIC on the PCI card and another ASIC in the external box;
+both need to be loaded. */
+static int load_asic(struct echoaudio *chip)
+{
+	int err;
+
+	if (chip->asic_loaded)
+		return 1;
+
+	DE_INIT(("load_asic\n"));
+
+	/* Give the DSP a few milliseconds to settle down */
+	mdelay(10);
+
+	/* Load the ASIC for the PCI card */
+	err = load_asic_generic(chip, DSP_FNC_LOAD_LAYLA24_PCI_CARD_ASIC,
+				FW_LAYLA24_1_ASIC);
+	if (err < 0)
+		return err;
+
+	chip->asic_code = FW_LAYLA24_2S_ASIC;
+
+	/* Now give the new ASIC a little time to set up */
+	mdelay(10);
+
+	/* Do the external one */
+	err = load_asic_generic(chip, DSP_FNC_LOAD_LAYLA24_EXTERNAL_ASIC,
+				FW_LAYLA24_2S_ASIC);
+	if (err < 0)
+		return FALSE;
+
+	/* Now give the external ASIC a little time to set up */
+	mdelay(10);
+
+	/* See if it worked */
+	err = check_asic_status(chip);
+
+	/* Set up the control register if the load succeeded -
+	   48 kHz, internal clock, S/PDIF RCA mode */
+	if (!err)
+		err = write_control_reg(chip, GML_CONVERTER_ENABLE | GML_48KHZ,
+					TRUE);
+	
+	DE_INIT(("load_asic() done\n"));
+	return err;
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	u32 control_reg, clock, base_rate;
+
+	if (snd_BUG_ON(rate >= 50000 &&
+		       chip->digital_mode == DIGITAL_MODE_ADAT))
+		return -EINVAL;
+
+	/* Only set the clock for internal mode. */
+	if (chip->input_clock != ECHO_CLOCK_INTERNAL) {
+		DE_ACT(("set_sample_rate: Cannot set sample rate - "
+			"clock not set to CLK_CLOCKININTERNAL\n"));
+		/* Save the rate anyhow */
+		chip->comm_page->sample_rate = cpu_to_le32(rate);
+		chip->sample_rate = rate;
+		return 0;
+	}
+
+	/* Get the control register & clear the appropriate bits */
+	control_reg = le32_to_cpu(chip->comm_page->control_register);
+	control_reg &= GML_CLOCK_CLEAR_MASK & GML_SPDIF_RATE_CLEAR_MASK;
+
+	clock = 0;
+
+	switch (rate) {
+	case 96000:
+		clock = GML_96KHZ;
+		break;
+	case 88200:
+		clock = GML_88KHZ;
+		break;
+	case 48000:
+		clock = GML_48KHZ | GML_SPDIF_SAMPLE_RATE1;
+		break;
+	case 44100:
+		clock = GML_44KHZ;
+		/* Professional mode */
+		if (control_reg & GML_SPDIF_PRO_MODE)
+			clock |= GML_SPDIF_SAMPLE_RATE0;
+		break;
+	case 32000:
+		clock = GML_32KHZ | GML_SPDIF_SAMPLE_RATE0 |
+			GML_SPDIF_SAMPLE_RATE1;
+		break;
+	case 22050:
+		clock = GML_22KHZ;
+		break;
+	case 16000:
+		clock = GML_16KHZ;
+		break;
+	case 11025:
+		clock = GML_11KHZ;
+		break;
+	case 8000:
+		clock = GML_8KHZ;
+		break;
+	default:
+		/* If this is a non-standard rate, then the driver needs to
+		use Layla24's special "continuous frequency" mode */
+		clock = LAYLA24_CONTINUOUS_CLOCK;
+		if (rate > 50000) {
+			base_rate = rate >> 1;
+			control_reg |= GML_DOUBLE_SPEED_MODE;
+		} else {
+			base_rate = rate;
+		}
+
+		if (base_rate < 25000)
+			base_rate = 25000;
+
+		if (wait_handshake(chip))
+			return -EIO;
+
+		chip->comm_page->sample_rate =
+			cpu_to_le32(LAYLA24_MAGIC_NUMBER / base_rate - 2);
+
+		clear_handshake(chip);
+		send_vector(chip, DSP_VC_SET_LAYLA24_FREQUENCY_REG);
+	}
+
+	control_reg |= clock;
+
+	chip->comm_page->sample_rate = cpu_to_le32(rate);	/* ignored by the DSP ? */
+	chip->sample_rate = rate;
+	DE_ACT(("set_sample_rate: %d clock %d\n", rate, control_reg));
+
+	return write_control_reg(chip, control_reg, FALSE);
+}
+
+
+
+static int set_input_clock(struct echoaudio *chip, u16 clock)
+{
+	u32 control_reg, clocks_from_dsp;
+
+	/* Mask off the clock select bits */
+	control_reg = le32_to_cpu(chip->comm_page->control_register) &
+		GML_CLOCK_CLEAR_MASK;
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	/* Pick the new clock */
+	switch (clock) {
+	case ECHO_CLOCK_INTERNAL:
+		DE_ACT(("Set Layla24 clock to INTERNAL\n"));
+		chip->input_clock = ECHO_CLOCK_INTERNAL;
+		return set_sample_rate(chip, chip->sample_rate);
+	case ECHO_CLOCK_SPDIF:
+		if (chip->digital_mode == DIGITAL_MODE_ADAT)
+			return -EAGAIN;
+		control_reg |= GML_SPDIF_CLOCK;
+		/* Layla24 doesn't support 96KHz S/PDIF */
+		control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		DE_ACT(("Set Layla24 clock to SPDIF\n"));
+		break;
+	case ECHO_CLOCK_WORD:
+		control_reg |= GML_WORD_CLOCK;
+		if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_WORD96)
+			control_reg |= GML_DOUBLE_SPEED_MODE;
+		else
+			control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		DE_ACT(("Set Layla24 clock to WORD\n"));
+		break;
+	case ECHO_CLOCK_ADAT:
+		if (chip->digital_mode != DIGITAL_MODE_ADAT)
+			return -EAGAIN;
+		control_reg |= GML_ADAT_CLOCK;
+		control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		DE_ACT(("Set Layla24 clock to ADAT\n"));
+		break;
+	default:
+		DE_ACT(("Input clock 0x%x not supported for Layla24\n", clock));
+		return -EINVAL;
+	}
+
+	chip->input_clock = clock;
+	return write_control_reg(chip, control_reg, TRUE);
+}
+
+
+
+/* Depending on what digital mode you want, Layla24 needs different ASICs
+loaded.  This function checks the ASIC needed for the new mode and sees
+if it matches the one already loaded. */
+static int switch_asic(struct echoaudio *chip, short asic)
+{
+	s8 *monitors;
+
+	/*  Check to see if this is already loaded */
+	if (asic != chip->asic_code) {
+		monitors = kmemdup(chip->comm_page->monitors,
+					MONITOR_ARRAY_SIZE, GFP_KERNEL);
+		if (! monitors)
+			return -ENOMEM;
+
+		memset(chip->comm_page->monitors, ECHOGAIN_MUTED,
+		       MONITOR_ARRAY_SIZE);
+
+		/* Load the desired ASIC */
+		if (load_asic_generic(chip, DSP_FNC_LOAD_LAYLA24_EXTERNAL_ASIC,
+				      asic) < 0) {
+			memcpy(chip->comm_page->monitors, monitors,
+			       MONITOR_ARRAY_SIZE);
+			kfree(monitors);
+			return -EIO;
+		}
+		chip->asic_code = asic;
+		memcpy(chip->comm_page->monitors, monitors, MONITOR_ARRAY_SIZE);
+		kfree(monitors);
+	}
+
+	return 0;
+}
+
+
+
+static int dsp_set_digital_mode(struct echoaudio *chip, u8 mode)
+{
+	u32 control_reg;
+	int err, incompatible_clock;
+	short asic;
+
+	/* Set clock to "internal" if it's not compatible with the new mode */
+	incompatible_clock = FALSE;
+	switch (mode) {
+	case DIGITAL_MODE_SPDIF_OPTICAL:
+	case DIGITAL_MODE_SPDIF_RCA:
+		if (chip->input_clock == ECHO_CLOCK_ADAT)
+			incompatible_clock = TRUE;
+		asic = FW_LAYLA24_2S_ASIC;
+		break;
+	case DIGITAL_MODE_ADAT:
+		if (chip->input_clock == ECHO_CLOCK_SPDIF)
+			incompatible_clock = TRUE;
+		asic = FW_LAYLA24_2A_ASIC;
+		break;
+	default:
+		DE_ACT(("Digital mode not supported: %d\n", mode));
+		return -EINVAL;
+	}
+
+	if (incompatible_clock) {	/* Switch to 48KHz, internal */
+		chip->sample_rate = 48000;
+		spin_lock_irq(&chip->lock);
+		set_input_clock(chip, ECHO_CLOCK_INTERNAL);
+		spin_unlock_irq(&chip->lock);
+	}
+
+	/* switch_asic() can sleep */
+	if (switch_asic(chip, asic) < 0)
+		return -EIO;
+
+	spin_lock_irq(&chip->lock);
+
+	/* Tweak the control register */
+	control_reg = le32_to_cpu(chip->comm_page->control_register);
+	control_reg &= GML_DIGITAL_MODE_CLEAR_MASK;
+
+	switch (mode) {
+	case DIGITAL_MODE_SPDIF_OPTICAL:
+		control_reg |= GML_SPDIF_OPTICAL_MODE;
+		break;
+	case DIGITAL_MODE_SPDIF_RCA:
+		/* GML_SPDIF_OPTICAL_MODE bit cleared */
+		break;
+	case DIGITAL_MODE_ADAT:
+		control_reg |= GML_ADAT_MODE;
+		control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		break;
+	}
+
+	err = write_control_reg(chip, control_reg, TRUE);
+	spin_unlock_irq(&chip->lock);
+	if (err < 0)
+		return err;
+	chip->digital_mode = mode;
+
+	DE_ACT(("set_digital_mode to %d\n", mode));
+	return incompatible_clock;
+}
diff --git a/linux/sound/pci/echoaudio/mia.c b/linux/sound/pci/echoaudio/mia.c
new file mode 100644
index 0000000..9cdf14c
--- /dev/null
+++ b/linux/sound/pci/echoaudio/mia.c
@@ -0,0 +1,121 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define ECHO24_FAMILY
+#define ECHOCARD_MIA
+#define ECHOCARD_NAME "Mia"
+#define ECHOCARD_HAS_MONITOR
+#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL
+#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+#define ECHOCARD_HAS_VMIXER
+#define ECHOCARD_HAS_DIGITAL_IO
+#define ECHOCARD_HAS_EXTERNAL_CLOCK
+#define ECHOCARD_HAS_ADAT	FALSE
+#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+#define ECHOCARD_HAS_MIDI
+#define ECHOCARD_HAS_LINE_OUT_GAIN
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 8 */
+#define PX_DIGITAL_OUT	8	/* 0 */
+#define PX_ANALOG_IN	8	/* 2 */
+#define PX_DIGITAL_IN	10	/* 2 */
+#define PX_NUM		12
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 2 */
+#define BX_DIGITAL_OUT	2	/* 2 */
+#define BX_ANALOG_IN	4	/* 2 */
+#define BX_DIGITAL_IN	6	/* 2 */
+#define BX_NUM		8
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/loader_dsp.fw");
+MODULE_FIRMWARE("ea/mia_dsp.fw");
+
+#define FW_361_LOADER	0
+#define FW_MIA_DSP	1
+
+static const struct firmware card_fw[] = {
+	{0, "loader_dsp.fw"},
+	{0, "mia_dsp.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x3410, 0xECC0, 0x0080, 0, 0, 0},	/* DSP 56361 Mia rev.0 */
+	{0x1057, 0x3410, 0xECC0, 0x0081, 0, 0, 0},	/* DSP 56361 Mia rev.1 */
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = 	SNDRV_PCM_RATE_32000 |
+			SNDRV_PCM_RATE_44100 |
+			SNDRV_PCM_RATE_48000 |
+			SNDRV_PCM_RATE_88200 |
+			SNDRV_PCM_RATE_96000,
+	.rate_min = 8000,
+	.rate_max = 96000,
+	.channels_min = 1,
+	.channels_max = 8,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+	/* One page (4k) contains 512 instructions. I don't know if the hw
+	supports lists longer than this. In this case periods_max=220 is a
+	safe limit to make sure the list never exceeds 512 instructions. */
+};
+
+
+#include "mia_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio.c"
+#include "midi.c"
diff --git a/linux/sound/pci/echoaudio/mia_dsp.c b/linux/sound/pci/echoaudio/mia_dsp.c
new file mode 100644
index 0000000..6ebfa6e
--- /dev/null
+++ b/linux/sound/pci/echoaudio/mia_dsp.c
@@ -0,0 +1,224 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+static int set_input_clock(struct echoaudio *chip, u16 clock);
+static int set_professional_spdif(struct echoaudio *chip, char prof);
+static int update_flags(struct echoaudio *chip);
+static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe,
+			   int gain);
+static int update_vmixer_level(struct echoaudio *chip);
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Mia\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != MIA))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->dsp_code_to_load = FW_MIA_DSP;
+	/* Since this card has no ASIC, mark it as loaded so everything
+	   works OK */
+	chip->asic_loaded = TRUE;
+	if ((subdevice_id & 0x0000f) == MIA_MIDI_REV)
+		chip->has_midi = TRUE;
+	chip->input_clock_types = ECHO_CLOCK_BIT_INTERNAL |
+		ECHO_CLOCK_BIT_SPDIF;
+
+	if ((err = load_firmware(chip)) < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	return init_line_levels(chip);
+}
+
+
+
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	u32 clocks_from_dsp, clock_bits;
+
+	/* Map the DSP clock detect bits to the generic driver clock
+	   detect bits */
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	clock_bits = ECHO_CLOCK_BIT_INTERNAL;
+
+	if (clocks_from_dsp & GLDM_CLOCK_DETECT_BIT_SPDIF)
+		clock_bits |= ECHO_CLOCK_BIT_SPDIF;
+
+	return clock_bits;
+}
+
+
+
+/* The Mia has no ASIC. Just do nothing */
+static int load_asic(struct echoaudio *chip)
+{
+	return 0;
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	u32 control_reg;
+
+	switch (rate) {
+	case 96000:
+		control_reg = MIA_96000;
+		break;
+	case 88200:
+		control_reg = MIA_88200;
+		break;
+	case 48000:
+		control_reg = MIA_48000;
+		break;
+	case 44100:
+		control_reg = MIA_44100;
+		break;
+	case 32000:
+		control_reg = MIA_32000;
+		break;
+	default:
+		DE_ACT(("set_sample_rate: %d invalid!\n", rate));
+		return -EINVAL;
+	}
+
+	/* Override the clock setting if this Mia is set to S/PDIF clock */
+	if (chip->input_clock == ECHO_CLOCK_SPDIF)
+		control_reg |= MIA_SPDIF;
+
+	/* Set the control register if it has changed */
+	if (control_reg != le32_to_cpu(chip->comm_page->control_register)) {
+		if (wait_handshake(chip))
+			return -EIO;
+
+		chip->comm_page->sample_rate = cpu_to_le32(rate);	/* ignored by the DSP */
+		chip->comm_page->control_register = cpu_to_le32(control_reg);
+		chip->sample_rate = rate;
+
+		clear_handshake(chip);
+		return send_vector(chip, DSP_VC_UPDATE_CLOCKS);
+	}
+	return 0;
+}
+
+
+
+static int set_input_clock(struct echoaudio *chip, u16 clock)
+{
+	DE_ACT(("set_input_clock(%d)\n", clock));
+	if (snd_BUG_ON(clock != ECHO_CLOCK_INTERNAL &&
+		       clock != ECHO_CLOCK_SPDIF))
+		return -EINVAL;
+
+	chip->input_clock = clock;
+	return set_sample_rate(chip, chip->sample_rate);
+}
+
+
+
+/* This function routes the sound from a virtual channel to a real output */
+static int set_vmixer_gain(struct echoaudio *chip, u16 output, u16 pipe,
+			   int gain)
+{
+	int index;
+
+	if (snd_BUG_ON(pipe >= num_pipes_out(chip) ||
+		       output >= num_busses_out(chip)))
+		return -EINVAL;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	chip->vmixer_gain[output][pipe] = gain;
+	index = output * num_pipes_out(chip) + pipe;
+	chip->comm_page->vmixer[index] = gain;
+
+	DE_ACT(("set_vmixer_gain: pipe %d, out %d = %d\n", pipe, output, gain));
+	return 0;
+}
+
+
+
+/* Tell the DSP to read and update virtual mixer levels in comm page. */
+static int update_vmixer_level(struct echoaudio *chip)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_SET_VMIXER_GAIN);
+}
+
+
+
+/* Tell the DSP to reread the flags from the comm page */
+static int update_flags(struct echoaudio *chip)
+{
+	if (wait_handshake(chip))
+		return -EIO;
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_UPDATE_FLAGS);
+}
+
+
+
+static int set_professional_spdif(struct echoaudio *chip, char prof)
+{
+	DE_ACT(("set_professional_spdif %d\n", prof));
+	if (prof)
+		chip->comm_page->flags |=
+			cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF);
+	else
+		chip->comm_page->flags &=
+			~cpu_to_le32(DSP_FLAG_PROFESSIONAL_SPDIF);
+	chip->professional_spdif = prof;
+	return update_flags(chip);
+}
+
diff --git a/linux/sound/pci/echoaudio/midi.c b/linux/sound/pci/echoaudio/midi.c
new file mode 100644
index 0000000..a953d14
--- /dev/null
+++ b/linux/sound/pci/echoaudio/midi.c
@@ -0,0 +1,331 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+/******************************************************************************
+	MIDI lowlevel code
+******************************************************************************/
+
+/* Start and stop Midi input */
+static int enable_midi_input(struct echoaudio *chip, char enable)
+{
+	DE_MID(("enable_midi_input(%d)\n", enable));
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	if (enable) {
+		chip->mtc_state = MIDI_IN_STATE_NORMAL;
+		chip->comm_page->flags |=
+			cpu_to_le32(DSP_FLAG_MIDI_INPUT);
+	} else
+		chip->comm_page->flags &=
+			~cpu_to_le32(DSP_FLAG_MIDI_INPUT);
+
+	clear_handshake(chip);
+	return send_vector(chip, DSP_VC_UPDATE_FLAGS);
+}
+
+
+
+/* Send a buffer full of MIDI data to the DSP
+Returns how many actually written or < 0 on error */
+static int write_midi(struct echoaudio *chip, u8 *data, int bytes)
+{
+	if (snd_BUG_ON(bytes <= 0 || bytes >= MIDI_OUT_BUFFER_SIZE))
+		return -EINVAL;
+
+	if (wait_handshake(chip))
+		return -EIO;
+
+	/* HF4 indicates that it is safe to write MIDI output data */
+	if (! (get_dsp_register(chip, CHI32_STATUS_REG) & CHI32_STATUS_REG_HF4))
+		return 0;
+
+	chip->comm_page->midi_output[0] = bytes;
+	memcpy(&chip->comm_page->midi_output[1], data, bytes);
+	chip->comm_page->midi_out_free_count = 0;
+	clear_handshake(chip);
+	send_vector(chip, DSP_VC_MIDI_WRITE);
+	DE_MID(("write_midi: %d\n", bytes));
+	return bytes;
+}
+
+
+
+/* Run the state machine for MIDI input data
+MIDI time code sync isn't supported by this code right now, but you still need
+this state machine to parse the incoming MIDI data stream.  Every time the DSP
+sees a 0xF1 byte come in, it adds the DSP sample position to the MIDI data
+stream. The DSP sample position is represented as a 32 bit unsigned value,
+with the high 16 bits first, followed by the low 16 bits. Since these aren't
+real MIDI bytes, the following logic is needed to skip them. */
+static inline int mtc_process_data(struct echoaudio *chip, short midi_byte)
+{
+	switch (chip->mtc_state) {
+	case MIDI_IN_STATE_NORMAL:
+		if (midi_byte == 0xF1)
+			chip->mtc_state = MIDI_IN_STATE_TS_HIGH;
+		break;
+	case MIDI_IN_STATE_TS_HIGH:
+		chip->mtc_state = MIDI_IN_STATE_TS_LOW;
+		return MIDI_IN_SKIP_DATA;
+		break;
+	case MIDI_IN_STATE_TS_LOW:
+		chip->mtc_state = MIDI_IN_STATE_F1_DATA;
+		return MIDI_IN_SKIP_DATA;
+		break;
+	case MIDI_IN_STATE_F1_DATA:
+		chip->mtc_state = MIDI_IN_STATE_NORMAL;
+		break;
+	}
+	return 0;
+}
+
+
+
+/* This function is called from the IRQ handler and it reads the midi data
+from the DSP's buffer.  It returns the number of bytes received. */
+static int midi_service_irq(struct echoaudio *chip)
+{
+	short int count, midi_byte, i, received;
+
+	/* The count is at index 0, followed by actual data */
+	count = le16_to_cpu(chip->comm_page->midi_input[0]);
+
+	if (snd_BUG_ON(count >= MIDI_IN_BUFFER_SIZE))
+		return 0;
+
+	/* Get the MIDI data from the comm page */
+	i = 1;
+	received = 0;
+	for (i = 1; i <= count; i++) {
+		/* Get the MIDI byte */
+		midi_byte = le16_to_cpu(chip->comm_page->midi_input[i]);
+
+		/* Parse the incoming MIDI stream. The incoming MIDI data
+		consists of MIDI bytes and timestamps for the MIDI time code
+		0xF1 bytes. mtc_process_data() is a little state machine that
+		parses the stream. If you get MIDI_IN_SKIP_DATA back, then
+		this is a timestamp byte, not a MIDI byte, so don't store it
+		in the MIDI input buffer. */
+		if (mtc_process_data(chip, midi_byte) == MIDI_IN_SKIP_DATA)
+			continue;
+
+		chip->midi_buffer[received++] = (u8)midi_byte;
+	}
+
+	return received;
+}
+
+
+
+
+/******************************************************************************
+	MIDI interface
+******************************************************************************/
+
+static int snd_echo_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct echoaudio *chip = substream->rmidi->private_data;
+
+	chip->midi_in = substream;
+	DE_MID(("rawmidi_iopen\n"));
+	return 0;
+}
+
+
+
+static void snd_echo_midi_input_trigger(struct snd_rawmidi_substream *substream,
+					int up)
+{
+	struct echoaudio *chip = substream->rmidi->private_data;
+
+	if (up != chip->midi_input_enabled) {
+		spin_lock_irq(&chip->lock);
+		enable_midi_input(chip, up);
+		spin_unlock_irq(&chip->lock);
+		chip->midi_input_enabled = up;
+	}
+}
+
+
+
+static int snd_echo_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct echoaudio *chip = substream->rmidi->private_data;
+
+	chip->midi_in = NULL;
+	DE_MID(("rawmidi_iclose\n"));
+	return 0;
+}
+
+
+
+static int snd_echo_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct echoaudio *chip = substream->rmidi->private_data;
+
+	chip->tinuse = 0;
+	chip->midi_full = 0;
+	chip->midi_out = substream;
+	DE_MID(("rawmidi_oopen\n"));
+	return 0;
+}
+
+
+
+static void snd_echo_midi_output_write(unsigned long data)
+{
+	struct echoaudio *chip = (struct echoaudio *)data;
+	unsigned long flags;
+	int bytes, sent, time;
+	unsigned char buf[MIDI_OUT_BUFFER_SIZE - 1];
+
+	DE_MID(("snd_echo_midi_output_write\n"));
+	/* No interrupts are involved: we have to check at regular intervals
+	if the card's output buffer has room for new data. */
+	sent = bytes = 0;
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->midi_full = 0;
+	if (!snd_rawmidi_transmit_empty(chip->midi_out)) {
+		bytes = snd_rawmidi_transmit_peek(chip->midi_out, buf,
+						  MIDI_OUT_BUFFER_SIZE - 1);
+		DE_MID(("Try to send %d bytes...\n", bytes));
+		sent = write_midi(chip, buf, bytes);
+		if (sent < 0) {
+			snd_printk(KERN_ERR "write_midi() error %d\n", sent);
+			/* retry later */
+			sent = 9000;
+			chip->midi_full = 1;
+		} else if (sent > 0) {
+			DE_MID(("%d bytes sent\n", sent));
+			snd_rawmidi_transmit_ack(chip->midi_out, sent);
+		} else {
+			/* Buffer is full. DSP's internal buffer is 64 (128 ?)
+			bytes long. Let's wait until half of them are sent */
+			DE_MID(("Full\n"));
+			sent = 32;
+			chip->midi_full = 1;
+		}
+	}
+
+	/* We restart the timer only if there is some data left to send */
+	if (!snd_rawmidi_transmit_empty(chip->midi_out) && chip->tinuse) {
+		/* The timer will expire slightly after the data has been
+		   sent */
+		time = (sent << 3) / 25 + 1;	/* 8/25=0.32ms to send a byte */
+		mod_timer(&chip->timer, jiffies + (time * HZ + 999) / 1000);
+		DE_MID(("Timer armed(%d)\n", ((time * HZ + 999) / 1000)));
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+
+static void snd_echo_midi_output_trigger(struct snd_rawmidi_substream *substream,
+					 int up)
+{
+	struct echoaudio *chip = substream->rmidi->private_data;
+
+	DE_MID(("snd_echo_midi_output_trigger(%d)\n", up));
+	spin_lock_irq(&chip->lock);
+	if (up) {
+		if (!chip->tinuse) {
+			init_timer(&chip->timer);
+			chip->timer.function = snd_echo_midi_output_write;
+			chip->timer.data = (unsigned long)chip;
+			chip->tinuse = 1;
+		}
+	} else {
+		if (chip->tinuse) {
+			chip->tinuse = 0;
+			spin_unlock_irq(&chip->lock);
+			del_timer_sync(&chip->timer);
+			DE_MID(("Timer removed\n"));
+			return;
+		}
+	}
+	spin_unlock_irq(&chip->lock);
+
+	if (up && !chip->midi_full)
+		snd_echo_midi_output_write((unsigned long)chip);
+}
+
+
+
+static int snd_echo_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct echoaudio *chip = substream->rmidi->private_data;
+
+	chip->midi_out = NULL;
+	DE_MID(("rawmidi_oclose\n"));
+	return 0;
+}
+
+
+
+static struct snd_rawmidi_ops snd_echo_midi_input = {
+	.open = snd_echo_midi_input_open,
+	.close = snd_echo_midi_input_close,
+	.trigger = snd_echo_midi_input_trigger,
+};
+
+static struct snd_rawmidi_ops snd_echo_midi_output = {
+	.open = snd_echo_midi_output_open,
+	.close = snd_echo_midi_output_close,
+	.trigger = snd_echo_midi_output_trigger,
+};
+
+
+
+/* <--snd_echo_probe() */
+static int __devinit snd_echo_midi_create(struct snd_card *card,
+					  struct echoaudio *chip)
+{
+	int err;
+
+	if ((err = snd_rawmidi_new(card, card->shortname, 0, 1, 1,
+				   &chip->rmidi)) < 0)
+		return err;
+
+	strcpy(chip->rmidi->name, card->shortname);
+	chip->rmidi->private_data = chip;
+
+	snd_rawmidi_set_ops(chip->rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+			    &snd_echo_midi_input);
+	snd_rawmidi_set_ops(chip->rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+			    &snd_echo_midi_output);
+
+	chip->rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+		SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
+	DE_INIT(("MIDI ok\n"));
+	return 0;
+}
diff --git a/linux/sound/pci/echoaudio/mona.c b/linux/sound/pci/echoaudio/mona.c
new file mode 100644
index 0000000..1047be4
--- /dev/null
+++ b/linux/sound/pci/echoaudio/mona.c
@@ -0,0 +1,138 @@
+/*
+ *  ALSA driver for Echoaudio soundcards.
+ *  Copyright (C) 2003-2004 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define ECHO24_FAMILY
+#define ECHOCARD_MONA
+#define ECHOCARD_NAME "Mona"
+#define ECHOCARD_HAS_MONITOR
+#define ECHOCARD_HAS_ASIC
+#define ECHOCARD_HAS_SUPER_INTERLEAVE
+#define ECHOCARD_HAS_DIGITAL_IO
+#define ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE
+#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH
+#define ECHOCARD_HAS_EXTERNAL_CLOCK
+#define ECHOCARD_HAS_ADAT	6
+#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32
+
+/* Pipe indexes */
+#define PX_ANALOG_OUT	0	/* 6 */
+#define PX_DIGITAL_OUT	6	/* 8 */
+#define PX_ANALOG_IN	14	/* 4 */
+#define PX_DIGITAL_IN	18	/* 8 */
+#define PX_NUM		26
+
+/* Bus indexes */
+#define BX_ANALOG_OUT	0	/* 6 */
+#define BX_DIGITAL_OUT	6	/* 8 */
+#define BX_ANALOG_IN	14	/* 4 */
+#define BX_DIGITAL_IN	18	/* 8 */
+#define BX_NUM		26
+
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+#include "echoaudio.h"
+
+MODULE_FIRMWARE("ea/loader_dsp.fw");
+MODULE_FIRMWARE("ea/mona_301_dsp.fw");
+MODULE_FIRMWARE("ea/mona_361_dsp.fw");
+MODULE_FIRMWARE("ea/mona_301_1_asic_48.fw");
+MODULE_FIRMWARE("ea/mona_301_1_asic_96.fw");
+MODULE_FIRMWARE("ea/mona_361_1_asic_48.fw");
+MODULE_FIRMWARE("ea/mona_361_1_asic_96.fw");
+MODULE_FIRMWARE("ea/mona_2_asic.fw");
+
+#define FW_361_LOADER		0
+#define FW_MONA_301_DSP		1
+#define FW_MONA_361_DSP		2
+#define FW_MONA_301_1_ASIC48	3
+#define FW_MONA_301_1_ASIC96	4
+#define FW_MONA_361_1_ASIC48	5
+#define FW_MONA_361_1_ASIC96	6
+#define FW_MONA_2_ASIC		7
+
+static const struct firmware card_fw[] = {
+	{0, "loader_dsp.fw"},
+	{0, "mona_301_dsp.fw"},
+	{0, "mona_361_dsp.fw"},
+	{0, "mona_301_1_asic_48.fw"},
+	{0, "mona_301_1_asic_96.fw"},
+	{0, "mona_361_1_asic_48.fw"},
+	{0, "mona_361_1_asic_96.fw"},
+	{0, "mona_2_asic.fw"}
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_echo_ids) = {
+	{0x1057, 0x1801, 0xECC0, 0x0070, 0, 0, 0},	/* DSP 56301 Mona rev.0 */
+	{0x1057, 0x1801, 0xECC0, 0x0071, 0, 0, 0},	/* DSP 56301 Mona rev.1 */
+	{0x1057, 0x1801, 0xECC0, 0x0072, 0, 0, 0},	/* DSP 56301 Mona rev.2 */
+	{0x1057, 0x3410, 0xECC0, 0x0070, 0, 0, 0},	/* DSP 56361 Mona rev.0 */
+	{0x1057, 0x3410, 0xECC0, 0x0071, 0, 0, 0},	/* DSP 56361 Mona rev.1 */
+	{0x1057, 0x3410, 0xECC0, 0x0072, 0, 0, 0},	/* DSP 56361 Mona rev.2 */
+	{0,}
+};
+
+static struct snd_pcm_hardware pcm_hardware_skel = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats =	SNDRV_PCM_FMTBIT_U8 |
+			SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_3LE |
+			SNDRV_PCM_FMTBIT_S32_LE |
+			SNDRV_PCM_FMTBIT_S32_BE,
+	.rates = 	SNDRV_PCM_RATE_8000_48000 |
+			SNDRV_PCM_RATE_88200 |
+			SNDRV_PCM_RATE_96000,
+	.rate_min = 8000,
+	.rate_max = 96000,
+	.channels_min = 1,
+	.channels_max = 8,
+	.buffer_bytes_max = 262144,
+	.period_bytes_min = 32,
+	.period_bytes_max = 131072,
+	.periods_min = 2,
+	.periods_max = 220,
+	/* One page (4k) contains 512 instructions. I don't know if the hw
+	supports lists longer than this. In this case periods_max=220 is a
+	safe limit to make sure the list never exceeds 512 instructions. */
+};
+
+
+#include "mona_dsp.c"
+#include "echoaudio_dsp.c"
+#include "echoaudio_gml.c"
+#include "echoaudio.c"
diff --git a/linux/sound/pci/echoaudio/mona_dsp.c b/linux/sound/pci/echoaudio/mona_dsp.c
new file mode 100644
index 0000000..6e6a7eb
--- /dev/null
+++ b/linux/sound/pci/echoaudio/mona_dsp.c
@@ -0,0 +1,427 @@
+/****************************************************************************
+
+   Copyright Echo Digital Audio Corporation (c) 1998 - 2004
+   All rights reserved
+   www.echoaudio.com
+
+   This file is part of Echo Digital Audio's generic driver library.
+
+   Echo Digital Audio's generic driver library 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.
+
+   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.
+
+   *************************************************************************
+
+ Translation from C++ and adaptation for use in ALSA-Driver
+ were made by Giuliano Pochini <pochini@shiny.it>
+
+****************************************************************************/
+
+
+static int write_control_reg(struct echoaudio *chip, u32 value, char force);
+static int set_input_clock(struct echoaudio *chip, u16 clock);
+static int set_professional_spdif(struct echoaudio *chip, char prof);
+static int set_digital_mode(struct echoaudio *chip, u8 mode);
+static int load_asic_generic(struct echoaudio *chip, u32 cmd, short asic);
+static int check_asic_status(struct echoaudio *chip);
+
+
+static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id)
+{
+	int err;
+
+	DE_INIT(("init_hw() - Mona\n"));
+	if (snd_BUG_ON((subdevice_id & 0xfff0) != MONA))
+		return -ENODEV;
+
+	if ((err = init_dsp_comm_page(chip))) {
+		DE_INIT(("init_hw - could not initialize DSP comm page\n"));
+		return err;
+	}
+
+	chip->device_id = device_id;
+	chip->subdevice_id = subdevice_id;
+	chip->bad_board = TRUE;
+	chip->input_clock_types =
+		ECHO_CLOCK_BIT_INTERNAL | ECHO_CLOCK_BIT_SPDIF |
+		ECHO_CLOCK_BIT_WORD | ECHO_CLOCK_BIT_ADAT;
+	chip->digital_modes =
+		ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_RCA |
+		ECHOCAPS_HAS_DIGITAL_MODE_SPDIF_OPTICAL |
+		ECHOCAPS_HAS_DIGITAL_MODE_ADAT;
+
+	/* Mona comes in both '301 and '361 flavors */
+	if (chip->device_id == DEVICE_ID_56361)
+		chip->dsp_code_to_load = FW_MONA_361_DSP;
+	else
+		chip->dsp_code_to_load = FW_MONA_301_DSP;
+
+	if ((err = load_firmware(chip)) < 0)
+		return err;
+	chip->bad_board = FALSE;
+
+	DE_INIT(("init_hw done\n"));
+	return err;
+}
+
+
+
+static int set_mixer_defaults(struct echoaudio *chip)
+{
+	chip->digital_mode = DIGITAL_MODE_SPDIF_RCA;
+	chip->professional_spdif = FALSE;
+	chip->digital_in_automute = TRUE;
+	return init_line_levels(chip);
+}
+
+
+
+static u32 detect_input_clocks(const struct echoaudio *chip)
+{
+	u32 clocks_from_dsp, clock_bits;
+
+	/* Map the DSP clock detect bits to the generic driver clock
+	   detect bits */
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	clock_bits = ECHO_CLOCK_BIT_INTERNAL;
+
+	if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_SPDIF)
+		clock_bits |= ECHO_CLOCK_BIT_SPDIF;
+
+	if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_ADAT)
+		clock_bits |= ECHO_CLOCK_BIT_ADAT;
+
+	if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_WORD)
+		clock_bits |= ECHO_CLOCK_BIT_WORD;
+
+	return clock_bits;
+}
+
+
+
+/* Mona has an ASIC on the PCI card and another ASIC in the external box; 
+both need to be loaded. */
+static int load_asic(struct echoaudio *chip)
+{
+	u32 control_reg;
+	int err;
+	short asic;
+
+	if (chip->asic_loaded)
+		return 0;
+
+	mdelay(10);
+
+	if (chip->device_id == DEVICE_ID_56361)
+		asic = FW_MONA_361_1_ASIC48;
+	else
+		asic = FW_MONA_301_1_ASIC48;
+
+	err = load_asic_generic(chip, DSP_FNC_LOAD_MONA_PCI_CARD_ASIC, asic);
+	if (err < 0)
+		return err;
+
+	chip->asic_code = asic;
+	mdelay(10);
+
+	/* Do the external one */
+	err = load_asic_generic(chip, DSP_FNC_LOAD_MONA_EXTERNAL_ASIC,
+				FW_MONA_2_ASIC);
+	if (err < 0)
+		return err;
+
+	mdelay(10);
+	err = check_asic_status(chip);
+
+	/* Set up the control register if the load succeeded -
+	   48 kHz, internal clock, S/PDIF RCA mode */
+	if (!err) {
+		control_reg = GML_CONVERTER_ENABLE | GML_48KHZ;
+		err = write_control_reg(chip, control_reg, TRUE);
+	}
+
+	return err;
+}
+
+
+
+/* Depending on what digital mode you want, Mona needs different ASICs
+loaded.  This function checks the ASIC needed for the new mode and sees
+if it matches the one already loaded. */
+static int switch_asic(struct echoaudio *chip, char double_speed)
+{
+	int err;
+	short asic;
+
+	/* Check the clock detect bits to see if this is
+	a single-speed clock or a double-speed clock; load
+	a new ASIC if necessary. */
+	if (chip->device_id == DEVICE_ID_56361) {
+		if (double_speed)
+			asic = FW_MONA_361_1_ASIC96;
+		else
+			asic = FW_MONA_361_1_ASIC48;
+	} else {
+		if (double_speed)
+			asic = FW_MONA_301_1_ASIC96;
+		else
+			asic = FW_MONA_301_1_ASIC48;
+	}
+
+	if (asic != chip->asic_code) {
+		/* Load the desired ASIC */
+		err = load_asic_generic(chip, DSP_FNC_LOAD_MONA_PCI_CARD_ASIC,
+					asic);
+		if (err < 0)
+			return err;
+		chip->asic_code = asic;
+	}
+
+	return 0;
+}
+
+
+
+static int set_sample_rate(struct echoaudio *chip, u32 rate)
+{
+	u32 control_reg, clock;
+	short asic;
+	char force_write;
+
+	/* Only set the clock for internal mode. */
+	if (chip->input_clock != ECHO_CLOCK_INTERNAL) {
+		DE_ACT(("set_sample_rate: Cannot set sample rate - "
+			"clock not set to CLK_CLOCKININTERNAL\n"));
+		/* Save the rate anyhow */
+		chip->comm_page->sample_rate = cpu_to_le32(rate);
+		chip->sample_rate = rate;
+		return 0;
+	}
+
+	/* Now, check to see if the required ASIC is loaded */
+	if (rate >= 88200) {
+		if (chip->digital_mode == DIGITAL_MODE_ADAT)
+			return -EINVAL;
+		if (chip->device_id == DEVICE_ID_56361)
+			asic = FW_MONA_361_1_ASIC96;
+		else
+			asic = FW_MONA_301_1_ASIC96;
+	} else {
+		if (chip->device_id == DEVICE_ID_56361)
+			asic = FW_MONA_361_1_ASIC48;
+		else
+			asic = FW_MONA_301_1_ASIC48;
+	}
+
+	force_write = 0;
+	if (asic != chip->asic_code) {
+		int err;
+		/* Load the desired ASIC (load_asic_generic() can sleep) */
+		spin_unlock_irq(&chip->lock);
+		err = load_asic_generic(chip, DSP_FNC_LOAD_MONA_PCI_CARD_ASIC,
+					asic);
+		spin_lock_irq(&chip->lock);
+
+		if (err < 0)
+			return err;
+		chip->asic_code = asic;
+		force_write = 1;
+	}
+
+	/* Compute the new control register value */
+	clock = 0;
+	control_reg = le32_to_cpu(chip->comm_page->control_register);
+	control_reg &= GML_CLOCK_CLEAR_MASK;
+	control_reg &= GML_SPDIF_RATE_CLEAR_MASK;
+
+	switch (rate) {
+	case 96000:
+		clock = GML_96KHZ;
+		break;
+	case 88200:
+		clock = GML_88KHZ;
+		break;
+	case 48000:
+		clock = GML_48KHZ | GML_SPDIF_SAMPLE_RATE1;
+		break;
+	case 44100:
+		clock = GML_44KHZ;
+		/* Professional mode */
+		if (control_reg & GML_SPDIF_PRO_MODE)
+			clock |= GML_SPDIF_SAMPLE_RATE0;
+		break;
+	case 32000:
+		clock = GML_32KHZ | GML_SPDIF_SAMPLE_RATE0 |
+			GML_SPDIF_SAMPLE_RATE1;
+		break;
+	case 22050:
+		clock = GML_22KHZ;
+		break;
+	case 16000:
+		clock = GML_16KHZ;
+		break;
+	case 11025:
+		clock = GML_11KHZ;
+		break;
+	case 8000:
+		clock = GML_8KHZ;
+		break;
+	default:
+		DE_ACT(("set_sample_rate: %d invalid!\n", rate));
+		return -EINVAL;
+	}
+
+	control_reg |= clock;
+
+	chip->comm_page->sample_rate = cpu_to_le32(rate);	/* ignored by the DSP */
+	chip->sample_rate = rate;
+	DE_ACT(("set_sample_rate: %d clock %d\n", rate, clock));
+
+	return write_control_reg(chip, control_reg, force_write);
+}
+
+
+
+static int set_input_clock(struct echoaudio *chip, u16 clock)
+{
+	u32 control_reg, clocks_from_dsp;
+	int err;
+
+	DE_ACT(("set_input_clock:\n"));
+
+	/* Prevent two simultaneous calls to switch_asic() */
+	if (atomic_read(&chip->opencount))
+		return -EAGAIN;
+
+	/* Mask off the clock select bits */
+	control_reg = le32_to_cpu(chip->comm_page->control_register) &
+		GML_CLOCK_CLEAR_MASK;
+	clocks_from_dsp = le32_to_cpu(chip->comm_page->status_clocks);
+
+	switch (clock) {
+	case ECHO_CLOCK_INTERNAL:
+		DE_ACT(("Set Mona clock to INTERNAL\n"));
+		chip->input_clock = ECHO_CLOCK_INTERNAL;
+		return set_sample_rate(chip, chip->sample_rate);
+	case ECHO_CLOCK_SPDIF:
+		if (chip->digital_mode == DIGITAL_MODE_ADAT)
+			return -EAGAIN;
+		spin_unlock_irq(&chip->lock);
+		err = switch_asic(chip, clocks_from_dsp &
+				  GML_CLOCK_DETECT_BIT_SPDIF96);
+		spin_lock_irq(&chip->lock);
+		if (err < 0)
+			return err;
+		DE_ACT(("Set Mona clock to SPDIF\n"));
+		control_reg |= GML_SPDIF_CLOCK;
+		if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_SPDIF96)
+			control_reg |= GML_DOUBLE_SPEED_MODE;
+		else
+			control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		break;
+	case ECHO_CLOCK_WORD:
+		DE_ACT(("Set Mona clock to WORD\n"));
+		spin_unlock_irq(&chip->lock);
+		err = switch_asic(chip, clocks_from_dsp &
+				  GML_CLOCK_DETECT_BIT_WORD96);
+		spin_lock_irq(&chip->lock);
+		if (err < 0)
+			return err;
+		control_reg |= GML_WORD_CLOCK;
+		if (clocks_from_dsp & GML_CLOCK_DETECT_BIT_WORD96)
+			control_reg |= GML_DOUBLE_SPEED_MODE;
+		else
+			control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		break;
+	case ECHO_CLOCK_ADAT:
+		DE_ACT(("Set Mona clock to ADAT\n"));
+		if (chip->digital_mode != DIGITAL_MODE_ADAT)
+			return -EAGAIN;
+		control_reg |= GML_ADAT_CLOCK;
+		control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		break;
+	default:
+		DE_ACT(("Input clock 0x%x not supported for Mona\n", clock));
+		return -EINVAL;
+	}
+
+	chip->input_clock = clock;
+	return write_control_reg(chip, control_reg, TRUE);
+}
+
+
+
+static int dsp_set_digital_mode(struct echoaudio *chip, u8 mode)
+{
+	u32 control_reg;
+	int err, incompatible_clock;
+
+	/* Set clock to "internal" if it's not compatible with the new mode */
+	incompatible_clock = FALSE;
+	switch (mode) {
+	case DIGITAL_MODE_SPDIF_OPTICAL:
+	case DIGITAL_MODE_SPDIF_RCA:
+		if (chip->input_clock == ECHO_CLOCK_ADAT)
+			incompatible_clock = TRUE;
+		break;
+	case DIGITAL_MODE_ADAT:
+		if (chip->input_clock == ECHO_CLOCK_SPDIF)
+			incompatible_clock = TRUE;
+		break;
+	default:
+		DE_ACT(("Digital mode not supported: %d\n", mode));
+		return -EINVAL;
+	}
+
+	spin_lock_irq(&chip->lock);
+
+	if (incompatible_clock) {	/* Switch to 48KHz, internal */
+		chip->sample_rate = 48000;
+		set_input_clock(chip, ECHO_CLOCK_INTERNAL);
+	}
+
+	/* Clear the current digital mode */
+	control_reg = le32_to_cpu(chip->comm_page->control_register);
+	control_reg &= GML_DIGITAL_MODE_CLEAR_MASK;
+
+	/* Tweak the control reg */
+	switch (mode) {
+	case DIGITAL_MODE_SPDIF_OPTICAL:
+		control_reg |= GML_SPDIF_OPTICAL_MODE;
+		break;
+	case DIGITAL_MODE_SPDIF_RCA:
+		/* GML_SPDIF_OPTICAL_MODE bit cleared */
+		break;
+	case DIGITAL_MODE_ADAT:
+		/* If the current ASIC is the 96KHz ASIC, switch the ASIC
+		   and set to 48 KHz */
+		if (chip->asic_code == FW_MONA_361_1_ASIC96 ||
+		    chip->asic_code == FW_MONA_301_1_ASIC96) {
+			set_sample_rate(chip, 48000);
+		}
+		control_reg |= GML_ADAT_MODE;
+		control_reg &= ~GML_DOUBLE_SPEED_MODE;
+		break;
+	}
+
+	err = write_control_reg(chip, control_reg, FALSE);
+	spin_unlock_irq(&chip->lock);
+	if (err < 0)
+		return err;
+	chip->digital_mode = mode;
+
+	DE_ACT(("set_digital_mode to %d\n", mode));
+	return incompatible_clock;
+}
diff --git a/linux/sound/pci/emu10k1/Makefile b/linux/sound/pci/emu10k1/Makefile
new file mode 100644
index 0000000..fc5591e
--- /dev/null
+++ b/linux/sound/pci/emu10k1/Makefile
@@ -0,0 +1,15 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-emu10k1-objs := emu10k1.o emu10k1_main.o \
+		    irq.o memory.o voice.o emumpu401.o emupcm.o io.o \
+		    emuproc.o emumixer.o emufx.o timer.o p16v.o
+snd-emu10k1-synth-objs := emu10k1_synth.o emu10k1_callback.o emu10k1_patch.o
+snd-emu10k1x-objs := emu10k1x.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_EMU10K1) += snd-emu10k1.o
+obj-$(CONFIG_SND_EMU10K1_SEQ) += snd-emu10k1-synth.o
+obj-$(CONFIG_SND_EMU10K1X) += snd-emu10k1x.o
diff --git a/linux/sound/pci/emu10k1/emu10k1.c b/linux/sound/pci/emu10k1/emu10k1.c
new file mode 100644
index 0000000..aff8387
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emu10k1.c
@@ -0,0 +1,288 @@
+/*
+ *  The driver for the EMU10K1 (SB Live!) based soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *  Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ *      Added support for Audigy 2 Value.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * 
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("EMU10K1");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB Live!/PCI512/E-mu APS},"
+	       "{Creative Labs,SB Audigy}}");
+
+#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
+#define ENABLE_SYNTH
+#include <sound/emu10k1_synth.h>
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static int extin[SNDRV_CARDS];
+static int extout[SNDRV_CARDS];
+static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
+static int max_synth_voices[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 64};
+static int max_buffer_size[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 128};
+static int enable_ir[SNDRV_CARDS];
+static uint subsystem[SNDRV_CARDS]; /* Force card subsystem model */
+static uint delay_pcm_irq[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the EMU10K1 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the EMU10K1 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable the EMU10K1 soundcard.");
+module_param_array(extin, int, NULL, 0444);
+MODULE_PARM_DESC(extin, "Available external inputs for FX8010. Zero=default.");
+module_param_array(extout, int, NULL, 0444);
+MODULE_PARM_DESC(extout, "Available external outputs for FX8010. Zero=default.");
+module_param_array(seq_ports, int, NULL, 0444);
+MODULE_PARM_DESC(seq_ports, "Allocated sequencer ports for internal synthesizer.");
+module_param_array(max_synth_voices, int, NULL, 0444);
+MODULE_PARM_DESC(max_synth_voices, "Maximum number of voices for WaveTable.");
+module_param_array(max_buffer_size, int, NULL, 0444);
+MODULE_PARM_DESC(max_buffer_size, "Maximum sample buffer size in MB.");
+module_param_array(enable_ir, bool, NULL, 0444);
+MODULE_PARM_DESC(enable_ir, "Enable IR.");
+module_param_array(subsystem, uint, NULL, 0444);
+MODULE_PARM_DESC(subsystem, "Force card subsystem model.");
+module_param_array(delay_pcm_irq, uint, NULL, 0444);
+MODULE_PARM_DESC(delay_pcm_irq, "Delay PCM interrupt by specified number of samples (default 0).");
+/*
+ * Class 0401: 1102:0008 (rev 00) Subsystem: 1102:1001 -> Audigy2 Value  Model:SB0400
+ */
+static DEFINE_PCI_DEVICE_TABLE(snd_emu10k1_ids) = {
+	{ PCI_VDEVICE(CREATIVE, 0x0002), 0 },	/* EMU10K1 */
+	{ PCI_VDEVICE(CREATIVE, 0x0004), 1 },	/* Audigy */
+	{ PCI_VDEVICE(CREATIVE, 0x0008), 1 },	/* Audigy 2 Value SB0400 */
+	{ 0, }
+};
+
+/*
+ * Audigy 2 Value notes:
+ * A_IOCFG Input (GPIO)
+ * 0x400  = Front analog jack plugged in. (Green socket)
+ * 0x1000 = Read analog jack plugged in. (Black socket)
+ * 0x2000 = Center/LFE analog jack plugged in. (Orange socket)
+ * A_IOCFG Output (GPIO)
+ * 0x60 = Sound out of front Left.
+ * Win sets it to 0xXX61
+ */
+
+MODULE_DEVICE_TABLE(pci, snd_emu10k1_ids);
+
+static int __devinit snd_card_emu10k1_probe(struct pci_dev *pci,
+					    const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_emu10k1 *emu;
+#ifdef ENABLE_SYNTH
+	struct snd_seq_device *wave = NULL;
+#endif
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+        	return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+	if (max_buffer_size[dev] < 32)
+		max_buffer_size[dev] = 32;
+	else if (max_buffer_size[dev] > 1024)
+		max_buffer_size[dev] = 1024;
+	if ((err = snd_emu10k1_create(card, pci, extin[dev], extout[dev],
+				      (long)max_buffer_size[dev] * 1024 * 1024,
+				      enable_ir[dev], subsystem[dev],
+				      &emu)) < 0)
+		goto error;
+	card->private_data = emu;
+	emu->delay_pcm_irq = delay_pcm_irq[dev] & 0x1f;
+	if ((err = snd_emu10k1_pcm(emu, 0, NULL)) < 0)
+		goto error;
+	if ((err = snd_emu10k1_pcm_mic(emu, 1, NULL)) < 0)
+		goto error;
+	if ((err = snd_emu10k1_pcm_efx(emu, 2, NULL)) < 0)
+		goto error;
+	/* This stores the periods table. */
+	if (emu->card_capabilities->ca0151_chip) { /* P16V */	
+		if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+					       1024, &emu->p16v_buffer)) < 0)
+			goto error;
+	}
+
+	if ((err = snd_emu10k1_mixer(emu, 0, 3)) < 0)
+		goto error;
+	
+	if ((err = snd_emu10k1_timer(emu, 0)) < 0)
+		goto error;
+
+	if ((err = snd_emu10k1_pcm_multi(emu, 3, NULL)) < 0)
+		goto error;
+	if (emu->card_capabilities->ca0151_chip) { /* P16V */
+		if ((err = snd_p16v_pcm(emu, 4, NULL)) < 0)
+			goto error;
+	}
+	if (emu->audigy) {
+		if ((err = snd_emu10k1_audigy_midi(emu)) < 0)
+			goto error;
+	} else {
+		if ((err = snd_emu10k1_midi(emu)) < 0)
+			goto error;
+	}
+	if ((err = snd_emu10k1_fx8010_new(emu, 0, NULL)) < 0)
+		goto error;
+#ifdef ENABLE_SYNTH
+	if (snd_seq_device_new(card, 1, SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH,
+			       sizeof(struct snd_emu10k1_synth_arg), &wave) < 0 ||
+	    wave == NULL) {
+		snd_printk(KERN_WARNING "can't initialize Emu10k1 wavetable synth\n");
+	} else {
+		struct snd_emu10k1_synth_arg *arg;
+		arg = SNDRV_SEQ_DEVICE_ARGPTR(wave);
+		strcpy(wave->name, "Emu-10k1 Synth");
+		arg->hwptr = emu;
+		arg->index = 1;
+		arg->seq_ports = seq_ports[dev];
+		arg->max_voices = max_synth_voices[dev];
+	}
+#endif
+ 
+	strcpy(card->driver, emu->card_capabilities->driver);
+	strcpy(card->shortname, emu->card_capabilities->name);
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s (rev.%d, serial:0x%x) at 0x%lx, irq %i",
+		 card->shortname, emu->revision, emu->serial, emu->port, emu->irq);
+
+	if ((err = snd_card_register(card)) < 0)
+		goto error;
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+
+ error:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit snd_card_emu10k1_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+
+#ifdef CONFIG_PM
+static int snd_emu10k1_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_emu10k1 *emu = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+	snd_pcm_suspend_all(emu->pcm);
+	snd_pcm_suspend_all(emu->pcm_mic);
+	snd_pcm_suspend_all(emu->pcm_efx);
+	snd_pcm_suspend_all(emu->pcm_multi);
+	snd_pcm_suspend_all(emu->pcm_p16v);
+
+	snd_ac97_suspend(emu->ac97);
+
+	snd_emu10k1_efx_suspend(emu);
+	snd_emu10k1_suspend_regs(emu);
+	if (emu->card_capabilities->ca0151_chip)
+		snd_p16v_suspend(emu);
+
+	snd_emu10k1_done(emu);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_emu10k1_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_emu10k1 *emu = card->private_data;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "emu10k1: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_emu10k1_resume_init(emu);
+	snd_emu10k1_efx_resume(emu);
+	snd_ac97_resume(emu->ac97);
+	snd_emu10k1_resume_regs(emu);
+
+	if (emu->card_capabilities->ca0151_chip)
+		snd_p16v_resume(emu);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct pci_driver driver = {
+	.name = "EMU10K1_Audigy",
+	.id_table = snd_emu10k1_ids,
+	.probe = snd_card_emu10k1_probe,
+	.remove = __devexit_p(snd_card_emu10k1_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_emu10k1_suspend,
+	.resume = snd_emu10k1_resume,
+#endif
+};
+
+static int __init alsa_card_emu10k1_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_emu10k1_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_emu10k1_init)
+module_exit(alsa_card_emu10k1_exit)
diff --git a/linux/sound/pci/emu10k1/emu10k1_callback.c b/linux/sound/pci/emu10k1/emu10k1_callback.c
new file mode 100644
index 0000000..7ef949d
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emu10k1_callback.c
@@ -0,0 +1,551 @@
+/*
+ *  synth callback routines for Emu10k1
+ *
+ *  Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "emu10k1_synth_local.h"
+#include <sound/asoundef.h>
+
+/* voice status */
+enum {
+	V_FREE=0, V_OFF, V_RELEASED, V_PLAYING, V_END
+};
+
+/* Keeps track of what we are finding */
+struct best_voice {
+	unsigned int time;
+	int voice;
+};
+
+/*
+ * prototypes
+ */
+static void lookup_voices(struct snd_emux *emux, struct snd_emu10k1 *hw,
+			  struct best_voice *best, int active_only);
+static struct snd_emux_voice *get_voice(struct snd_emux *emux,
+					struct snd_emux_port *port);
+static int start_voice(struct snd_emux_voice *vp);
+static void trigger_voice(struct snd_emux_voice *vp);
+static void release_voice(struct snd_emux_voice *vp);
+static void update_voice(struct snd_emux_voice *vp, int update);
+static void terminate_voice(struct snd_emux_voice *vp);
+static void free_voice(struct snd_emux_voice *vp);
+static void set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp);
+static void set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp);
+static void set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp);
+
+/*
+ * Ensure a value is between two points
+ * macro evaluates its args more than once, so changed to upper-case.
+ */
+#define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0)
+#define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0)
+
+
+/*
+ * set up operators
+ */
+static struct snd_emux_operators emu10k1_ops = {
+	.owner =	THIS_MODULE,
+	.get_voice =	get_voice,
+	.prepare =	start_voice,
+	.trigger =	trigger_voice,
+	.release =	release_voice,
+	.update =	update_voice,
+	.terminate =	terminate_voice,
+	.free_voice =	free_voice,
+	.sample_new =	snd_emu10k1_sample_new,
+	.sample_free =	snd_emu10k1_sample_free,
+};
+
+void
+snd_emu10k1_ops_setup(struct snd_emux *emux)
+{
+	emux->ops = emu10k1_ops;
+}
+
+
+/*
+ * get more voice for pcm
+ *
+ * terminate most inactive voice and give it as a pcm voice.
+ */
+int
+snd_emu10k1_synth_get_voice(struct snd_emu10k1 *hw)
+{
+	struct snd_emux *emu;
+	struct snd_emux_voice *vp;
+	struct best_voice best[V_END];
+	unsigned long flags;
+	int i;
+
+	emu = hw->synth;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	lookup_voices(emu, hw, best, 1); /* no OFF voices */
+	for (i = 0; i < V_END; i++) {
+		if (best[i].voice >= 0) {
+			int ch;
+			vp = &emu->voices[best[i].voice];
+			if ((ch = vp->ch) < 0) {
+				/*
+				printk(KERN_WARNING
+				       "synth_get_voice: ch < 0 (%d) ??", i);
+				*/
+				continue;
+			}
+			vp->emu->num_voices--;
+			vp->ch = -1;
+			vp->state = SNDRV_EMUX_ST_OFF;
+			spin_unlock_irqrestore(&emu->voice_lock, flags);
+			return ch;
+		}
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+
+	/* not found */
+	return -ENOMEM;
+}
+
+
+/*
+ * turn off the voice (not terminated)
+ */
+static void
+release_voice(struct snd_emux_voice *vp)
+{
+	int dcysusv;
+	struct snd_emu10k1 *hw;
+	
+	hw = vp->hw;
+	dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease;
+	snd_emu10k1_ptr_write(hw, DCYSUSM, vp->ch, dcysusv);
+	dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease | DCYSUSV_CHANNELENABLE_MASK;
+	snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, dcysusv);
+}
+
+
+/*
+ * terminate the voice
+ */
+static void
+terminate_voice(struct snd_emux_voice *vp)
+{
+	struct snd_emu10k1 *hw;
+	
+	if (snd_BUG_ON(!vp))
+		return;
+	hw = vp->hw;
+	snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK);
+	if (vp->block) {
+		struct snd_emu10k1_memblk *emem;
+		emem = (struct snd_emu10k1_memblk *)vp->block;
+		if (emem->map_locked > 0)
+			emem->map_locked--;
+	}
+}
+
+/*
+ * release the voice to system
+ */
+static void
+free_voice(struct snd_emux_voice *vp)
+{
+	struct snd_emu10k1 *hw;
+	
+	hw = vp->hw;
+	/* FIXME: emu10k1_synth is broken. */
+	/* This can get called with hw == 0 */
+	/* Problem apparent on plug, unplug then plug */
+	/* on the Audigy 2 ZS Notebook. */
+	if (hw && (vp->ch >= 0)) {
+		snd_emu10k1_ptr_write(hw, IFATN, vp->ch, 0xff00);
+		snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK);
+		// snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0);
+		snd_emu10k1_ptr_write(hw, VTFT, vp->ch, 0xffff);
+		snd_emu10k1_ptr_write(hw, CVCF, vp->ch, 0xffff);
+		snd_emu10k1_voice_free(hw, &hw->voices[vp->ch]);
+		vp->emu->num_voices--;
+		vp->ch = -1;
+	}
+}
+
+
+/*
+ * update registers
+ */
+static void
+update_voice(struct snd_emux_voice *vp, int update)
+{
+	struct snd_emu10k1 *hw;
+	
+	hw = vp->hw;
+	if (update & SNDRV_EMUX_UPDATE_VOLUME)
+		snd_emu10k1_ptr_write(hw, IFATN_ATTENUATION, vp->ch, vp->avol);
+	if (update & SNDRV_EMUX_UPDATE_PITCH)
+		snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch);
+	if (update & SNDRV_EMUX_UPDATE_PAN) {
+		snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_A, vp->ch, vp->apan);
+		snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_B, vp->ch, vp->aaux);
+	}
+	if (update & SNDRV_EMUX_UPDATE_FMMOD)
+		set_fmmod(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_TREMFREQ)
+		snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq);
+	if (update & SNDRV_EMUX_UPDATE_FM2FRQ2)
+		set_fm2frq2(hw, vp);
+	if (update & SNDRV_EMUX_UPDATE_Q)
+		set_filterQ(hw, vp);
+}
+
+
+/*
+ * look up voice table - get the best voice in order of preference
+ */
+/* spinlock held! */
+static void
+lookup_voices(struct snd_emux *emu, struct snd_emu10k1 *hw,
+	      struct best_voice *best, int active_only)
+{
+	struct snd_emux_voice *vp;
+	struct best_voice *bp;
+	int  i;
+
+	for (i = 0; i < V_END; i++) {
+		best[i].time = (unsigned int)-1; /* XXX MAX_?INT really */;
+		best[i].voice = -1;
+	}
+
+	/*
+	 * Go through them all and get a best one to use.
+	 * NOTE: could also look at volume and pick the quietest one.
+	 */
+	for (i = 0; i < emu->max_voices; i++) {
+		int state, val;
+
+		vp = &emu->voices[i];
+		state = vp->state;
+		if (state == SNDRV_EMUX_ST_OFF) {
+			if (vp->ch < 0) {
+				if (active_only)
+					continue;
+				bp = best + V_FREE;
+			} else
+				bp = best + V_OFF;
+		}
+		else if (state == SNDRV_EMUX_ST_RELEASED ||
+			 state == SNDRV_EMUX_ST_PENDING) {
+			bp = best + V_RELEASED;
+#if 1
+			val = snd_emu10k1_ptr_read(hw, CVCF_CURRENTVOL, vp->ch);
+			if (! val)
+				bp = best + V_OFF;
+#endif
+		}
+		else if (state == SNDRV_EMUX_ST_STANDBY)
+			continue;
+		else if (state & SNDRV_EMUX_ST_ON)
+			bp = best + V_PLAYING;
+		else
+			continue;
+
+		/* check if sample is finished playing (non-looping only) */
+		if (bp != best + V_OFF && bp != best + V_FREE &&
+		    (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) {
+			val = snd_emu10k1_ptr_read(hw, CCCA_CURRADDR, vp->ch);
+			if (val >= vp->reg.loopstart)
+				bp = best + V_OFF;
+		}
+
+		if (vp->time < bp->time) {
+			bp->time = vp->time;
+			bp->voice = i;
+		}
+	}
+}
+
+/*
+ * get an empty voice
+ *
+ * emu->voice_lock is already held.
+ */
+static struct snd_emux_voice *
+get_voice(struct snd_emux *emu, struct snd_emux_port *port)
+{
+	struct snd_emu10k1 *hw;
+	struct snd_emux_voice *vp;
+	struct best_voice best[V_END];
+	int i;
+
+	hw = emu->hw;
+
+	lookup_voices(emu, hw, best, 0);
+	for (i = 0; i < V_END; i++) {
+		if (best[i].voice >= 0) {
+			vp = &emu->voices[best[i].voice];
+			if (vp->ch < 0) {
+				/* allocate a voice */
+				struct snd_emu10k1_voice *hwvoice;
+				if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, &hwvoice) < 0 || hwvoice == NULL)
+					continue;
+				vp->ch = hwvoice->number;
+				emu->num_voices++;
+			}
+			return vp;
+		}
+	}
+
+	/* not found */
+	return NULL;
+}
+
+/*
+ * prepare envelopes and LFOs
+ */
+static int
+start_voice(struct snd_emux_voice *vp)
+{
+	unsigned int temp;
+	int ch;
+	unsigned int addr, mapped_offset;
+	struct snd_midi_channel *chan;
+	struct snd_emu10k1 *hw;
+	struct snd_emu10k1_memblk *emem;
+	
+	hw = vp->hw;
+	ch = vp->ch;
+	if (snd_BUG_ON(ch < 0))
+		return -EINVAL;
+	chan = vp->chan;
+
+	emem = (struct snd_emu10k1_memblk *)vp->block;
+	if (emem == NULL)
+		return -EINVAL;
+	emem->map_locked++;
+	if (snd_emu10k1_memblk_map(hw, emem) < 0) {
+		/* printk(KERN_ERR "emu: cannot map!\n"); */
+		return -ENOMEM;
+	}
+	mapped_offset = snd_emu10k1_memblk_offset(emem) >> 1;
+	vp->reg.start += mapped_offset;
+	vp->reg.end += mapped_offset;
+	vp->reg.loopstart += mapped_offset;
+	vp->reg.loopend += mapped_offset;
+
+	/* set channel routing */
+	/* A = left(0), B = right(1), C = reverb(c), D = chorus(d) */
+	if (hw->audigy) {
+		temp = FXBUS_MIDI_LEFT | (FXBUS_MIDI_RIGHT << 8) | 
+			(FXBUS_MIDI_REVERB << 16) | (FXBUS_MIDI_CHORUS << 24);
+		snd_emu10k1_ptr_write(hw, A_FXRT1, ch, temp);
+	} else {
+		temp = (FXBUS_MIDI_LEFT << 16) | (FXBUS_MIDI_RIGHT << 20) | 
+			(FXBUS_MIDI_REVERB << 24) | (FXBUS_MIDI_CHORUS << 28);
+		snd_emu10k1_ptr_write(hw, FXRT, ch, temp);
+	}
+
+	/* channel to be silent and idle */
+	snd_emu10k1_ptr_write(hw, DCYSUSV, ch, 0x0000);
+	snd_emu10k1_ptr_write(hw, VTFT, ch, 0x0000FFFF);
+	snd_emu10k1_ptr_write(hw, CVCF, ch, 0x0000FFFF);
+	snd_emu10k1_ptr_write(hw, PTRX, ch, 0);
+	snd_emu10k1_ptr_write(hw, CPF, ch, 0);
+
+	/* set pitch offset */
+	snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch);
+
+	/* set envelope parameters */
+	snd_emu10k1_ptr_write(hw, ENVVAL, ch, vp->reg.parm.moddelay);
+	snd_emu10k1_ptr_write(hw, ATKHLDM, ch, vp->reg.parm.modatkhld);
+	snd_emu10k1_ptr_write(hw, DCYSUSM, ch, vp->reg.parm.moddcysus);
+	snd_emu10k1_ptr_write(hw, ENVVOL, ch, vp->reg.parm.voldelay);
+	snd_emu10k1_ptr_write(hw, ATKHLDV, ch, vp->reg.parm.volatkhld);
+	/* decay/sustain parameter for volume envelope is used
+	   for triggerg the voice */
+
+	/* cutoff and volume */
+	temp = (unsigned int)vp->acutoff << 8 | (unsigned char)vp->avol;
+	snd_emu10k1_ptr_write(hw, IFATN, vp->ch, temp);
+
+	/* modulation envelope heights */
+	snd_emu10k1_ptr_write(hw, PEFE, ch, vp->reg.parm.pefe);
+
+	/* lfo1/2 delay */
+	snd_emu10k1_ptr_write(hw, LFOVAL1, ch, vp->reg.parm.lfo1delay);
+	snd_emu10k1_ptr_write(hw, LFOVAL2, ch, vp->reg.parm.lfo2delay);
+
+	/* lfo1 pitch & cutoff shift */
+	set_fmmod(hw, vp);
+	/* lfo1 volume & freq */
+	snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq);
+	/* lfo2 pitch & freq */
+	set_fm2frq2(hw, vp);
+
+	/* reverb and loop start (reverb 8bit, MSB) */
+	temp = vp->reg.parm.reverb;
+	temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10;
+	LIMITMAX(temp, 255);
+	addr = vp->reg.loopstart;
+	snd_emu10k1_ptr_write(hw, PSST, vp->ch, (temp << 24) | addr);
+
+	/* chorus & loop end (chorus 8bit, MSB) */
+	addr = vp->reg.loopend;
+	temp = vp->reg.parm.chorus;
+	temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10;
+	LIMITMAX(temp, 255);
+	temp = (temp <<24) | addr;
+	snd_emu10k1_ptr_write(hw, DSL, ch, temp);
+
+	/* clear filter delay memory */
+	snd_emu10k1_ptr_write(hw, Z1, ch, 0);
+	snd_emu10k1_ptr_write(hw, Z2, ch, 0);
+
+	/* invalidate maps */
+	temp = (hw->silent_page.addr << 1) | MAP_PTI_MASK;
+	snd_emu10k1_ptr_write(hw, MAPA, ch, temp);
+	snd_emu10k1_ptr_write(hw, MAPB, ch, temp);
+#if 0
+	/* cache */
+	{
+		unsigned int val, sample;
+		val = 32;
+		if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS)
+			sample = 0x80808080;
+		else {
+			sample = 0;
+			val *= 2;
+		}
+
+		/* cache */
+		snd_emu10k1_ptr_write(hw, CCR, ch, 0x1c << 16);
+		snd_emu10k1_ptr_write(hw, CDE, ch, sample);
+		snd_emu10k1_ptr_write(hw, CDF, ch, sample);
+
+		/* invalidate maps */
+		temp = ((unsigned int)hw->silent_page.addr << 1) | MAP_PTI_MASK;
+		snd_emu10k1_ptr_write(hw, MAPA, ch, temp);
+		snd_emu10k1_ptr_write(hw, MAPB, ch, temp);
+		
+		/* fill cache */
+		val -= 4;
+		val <<= 25;
+		val |= 0x1c << 16;
+		snd_emu10k1_ptr_write(hw, CCR, ch, val);
+	}
+#endif
+
+	/* Q & current address (Q 4bit value, MSB) */
+	addr = vp->reg.start;
+	temp = vp->reg.parm.filterQ;
+	temp = (temp<<28) | addr;
+	if (vp->apitch < 0xe400)
+		temp |= CCCA_INTERPROM_0;
+	else {
+		unsigned int shift = (vp->apitch - 0xe000) >> 10;
+		temp |= shift << 25;
+	}
+	if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS)
+		temp |= CCCA_8BITSELECT;
+	snd_emu10k1_ptr_write(hw, CCCA, ch, temp);
+
+	/* reset volume */
+	temp = (unsigned int)vp->vtarget << 16;
+	snd_emu10k1_ptr_write(hw, VTFT, ch, temp | vp->ftarget);
+	snd_emu10k1_ptr_write(hw, CVCF, ch, temp | 0xff00);
+	return 0;
+}
+
+/*
+ * Start envelope
+ */
+static void
+trigger_voice(struct snd_emux_voice *vp)
+{
+	unsigned int temp, ptarget;
+	struct snd_emu10k1 *hw;
+	struct snd_emu10k1_memblk *emem;
+	
+	hw = vp->hw;
+
+	emem = (struct snd_emu10k1_memblk *)vp->block;
+	if (! emem || emem->mapped_page < 0)
+		return; /* not mapped */
+
+#if 0
+	ptarget = (unsigned int)vp->ptarget << 16;
+#else
+	ptarget = IP_TO_CP(vp->apitch);
+#endif
+	/* set pitch target and pan (volume) */
+	temp = ptarget | (vp->apan << 8) | vp->aaux;
+	snd_emu10k1_ptr_write(hw, PTRX, vp->ch, temp);
+
+	/* pitch target */
+	snd_emu10k1_ptr_write(hw, CPF, vp->ch, ptarget);
+
+	/* trigger voice */
+	snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, vp->reg.parm.voldcysus|DCYSUSV_CHANNELENABLE_MASK);
+}
+
+#define MOD_SENSE 18
+
+/* set lfo1 modulation height and cutoff */
+static void
+set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp)
+{
+	unsigned short fmmod;
+	short pitch;
+	unsigned char cutoff;
+	int modulation;
+
+	pitch = (char)(vp->reg.parm.fmmod>>8);
+	cutoff = (vp->reg.parm.fmmod & 0xff);
+	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+	pitch += (MOD_SENSE * modulation) / 1200;
+	LIMITVALUE(pitch, -128, 127);
+	fmmod = ((unsigned char)pitch<<8) | cutoff;
+	snd_emu10k1_ptr_write(hw, FMMOD, vp->ch, fmmod);
+}
+
+/* set lfo2 pitch & frequency */
+static void
+set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp)
+{
+	unsigned short fm2frq2;
+	short pitch;
+	unsigned char freq;
+	int modulation;
+
+	pitch = (char)(vp->reg.parm.fm2frq2>>8);
+	freq = vp->reg.parm.fm2frq2 & 0xff;
+	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure;
+	pitch += (MOD_SENSE * modulation) / 1200;
+	LIMITVALUE(pitch, -128, 127);
+	fm2frq2 = ((unsigned char)pitch<<8) | freq;
+	snd_emu10k1_ptr_write(hw, FM2FRQ2, vp->ch, fm2frq2);
+}
+
+/* set filterQ */
+static void
+set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp)
+{
+	unsigned int val;
+	val = snd_emu10k1_ptr_read(hw, CCCA, vp->ch) & ~CCCA_RESONANCE;
+	val |= (vp->reg.parm.filterQ << 28);
+	snd_emu10k1_ptr_write(hw, CCCA, vp->ch, val);
+}
diff --git a/linux/sound/pci/emu10k1/emu10k1_main.c b/linux/sound/pci/emu10k1/emu10k1_main.c
new file mode 100644
index 0000000..66c7fb3
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emu10k1_main.c
@@ -0,0 +1,2080 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *                   Creative Labs, Inc.
+ *  Routines for control of EMU10K1 chips
+ *
+ *  Copyright (c) by James Courtier-Dutton <James@superbug.co.uk>
+ *      Added support for Audigy 2 Value.
+ *  	Added EMU 1010 support.
+ *  	General bug fixes and enhancements.
+ *
+ *
+ *  BUGS:
+ *    --
+ *
+ *  TODO:
+ *    --
+ *
+ *   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/sched.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mutex.h>
+
+
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#include <linux/firmware.h>
+#include "p16v.h"
+#include "tina2.h"
+#include "p17v.h"
+
+
+#define HANA_FILENAME "emu/hana.fw"
+#define DOCK_FILENAME "emu/audio_dock.fw"
+#define EMU1010B_FILENAME "emu/emu1010b.fw"
+#define MICRO_DOCK_FILENAME "emu/micro_dock.fw"
+#define EMU0404_FILENAME "emu/emu0404.fw"
+#define EMU1010_NOTEBOOK_FILENAME "emu/emu1010_notebook.fw"
+
+MODULE_FIRMWARE(HANA_FILENAME);
+MODULE_FIRMWARE(DOCK_FILENAME);
+MODULE_FIRMWARE(EMU1010B_FILENAME);
+MODULE_FIRMWARE(MICRO_DOCK_FILENAME);
+MODULE_FIRMWARE(EMU0404_FILENAME);
+MODULE_FIRMWARE(EMU1010_NOTEBOOK_FILENAME);
+
+
+/*************************************************************************
+ * EMU10K1 init / done
+ *************************************************************************/
+
+void snd_emu10k1_voice_init(struct snd_emu10k1 *emu, int ch)
+{
+	snd_emu10k1_ptr_write(emu, DCYSUSV, ch, 0);
+	snd_emu10k1_ptr_write(emu, IP, ch, 0);
+	snd_emu10k1_ptr_write(emu, VTFT, ch, 0xffff);
+	snd_emu10k1_ptr_write(emu, CVCF, ch, 0xffff);
+	snd_emu10k1_ptr_write(emu, PTRX, ch, 0);
+	snd_emu10k1_ptr_write(emu, CPF, ch, 0);
+	snd_emu10k1_ptr_write(emu, CCR, ch, 0);
+
+	snd_emu10k1_ptr_write(emu, PSST, ch, 0);
+	snd_emu10k1_ptr_write(emu, DSL, ch, 0x10);
+	snd_emu10k1_ptr_write(emu, CCCA, ch, 0);
+	snd_emu10k1_ptr_write(emu, Z1, ch, 0);
+	snd_emu10k1_ptr_write(emu, Z2, ch, 0);
+	snd_emu10k1_ptr_write(emu, FXRT, ch, 0x32100000);
+
+	snd_emu10k1_ptr_write(emu, ATKHLDM, ch, 0);
+	snd_emu10k1_ptr_write(emu, DCYSUSM, ch, 0);
+	snd_emu10k1_ptr_write(emu, IFATN, ch, 0xffff);
+	snd_emu10k1_ptr_write(emu, PEFE, ch, 0);
+	snd_emu10k1_ptr_write(emu, FMMOD, ch, 0);
+	snd_emu10k1_ptr_write(emu, TREMFRQ, ch, 24);	/* 1 Hz */
+	snd_emu10k1_ptr_write(emu, FM2FRQ2, ch, 24);	/* 1 Hz */
+	snd_emu10k1_ptr_write(emu, TEMPENV, ch, 0);
+
+	/*** these are last so OFF prevents writing ***/
+	snd_emu10k1_ptr_write(emu, LFOVAL2, ch, 0);
+	snd_emu10k1_ptr_write(emu, LFOVAL1, ch, 0);
+	snd_emu10k1_ptr_write(emu, ATKHLDV, ch, 0);
+	snd_emu10k1_ptr_write(emu, ENVVOL, ch, 0);
+	snd_emu10k1_ptr_write(emu, ENVVAL, ch, 0);
+
+	/* Audigy extra stuffs */
+	if (emu->audigy) {
+		snd_emu10k1_ptr_write(emu, 0x4c, ch, 0); /* ?? */
+		snd_emu10k1_ptr_write(emu, 0x4d, ch, 0); /* ?? */
+		snd_emu10k1_ptr_write(emu, 0x4e, ch, 0); /* ?? */
+		snd_emu10k1_ptr_write(emu, 0x4f, ch, 0); /* ?? */
+		snd_emu10k1_ptr_write(emu, A_FXRT1, ch, 0x03020100);
+		snd_emu10k1_ptr_write(emu, A_FXRT2, ch, 0x3f3f3f3f);
+		snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, ch, 0);
+	}
+}
+
+static unsigned int spi_dac_init[] = {
+		0x00ff,
+		0x02ff,
+		0x0400,
+		0x0520,
+		0x0600,
+		0x08ff,
+		0x0aff,
+		0x0cff,
+		0x0eff,
+		0x10ff,
+		0x1200,
+		0x1400,
+		0x1480,
+		0x1800,
+		0x1aff,
+		0x1cff,
+		0x1e00,
+		0x0530,
+		0x0602,
+		0x0622,
+		0x1400,
+};
+
+static unsigned int i2c_adc_init[][2] = {
+	{ 0x17, 0x00 }, /* Reset */
+	{ 0x07, 0x00 }, /* Timeout */
+	{ 0x0b, 0x22 },  /* Interface control */
+	{ 0x0c, 0x22 },  /* Master mode control */
+	{ 0x0d, 0x08 },  /* Powerdown control */
+	{ 0x0e, 0xcf },  /* Attenuation Left  0x01 = -103dB, 0xff = 24dB */
+	{ 0x0f, 0xcf },  /* Attenuation Right 0.5dB steps */
+	{ 0x10, 0x7b },  /* ALC Control 1 */
+	{ 0x11, 0x00 },  /* ALC Control 2 */
+	{ 0x12, 0x32 },  /* ALC Control 3 */
+	{ 0x13, 0x00 },  /* Noise gate control */
+	{ 0x14, 0xa6 },  /* Limiter control */
+	{ 0x15, ADC_MUX_2 },  /* ADC Mixer control. Mic for A2ZS Notebook */
+};
+
+static int snd_emu10k1_init(struct snd_emu10k1 *emu, int enable_ir, int resume)
+{
+	unsigned int silent_page;
+	int ch;
+	u32 tmp;
+
+	/* disable audio and lock cache */
+	outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK |
+		HCFG_MUTEBUTTONENABLE, emu->port + HCFG);
+
+	/* reset recording buffers */
+	snd_emu10k1_ptr_write(emu, MICBS, 0, ADCBS_BUFSIZE_NONE);
+	snd_emu10k1_ptr_write(emu, MICBA, 0, 0);
+	snd_emu10k1_ptr_write(emu, FXBS, 0, ADCBS_BUFSIZE_NONE);
+	snd_emu10k1_ptr_write(emu, FXBA, 0, 0);
+	snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE);
+	snd_emu10k1_ptr_write(emu, ADCBA, 0, 0);
+
+	/* disable channel interrupt */
+	outl(0, emu->port + INTE);
+	snd_emu10k1_ptr_write(emu, CLIEL, 0, 0);
+	snd_emu10k1_ptr_write(emu, CLIEH, 0, 0);
+	snd_emu10k1_ptr_write(emu, SOLEL, 0, 0);
+	snd_emu10k1_ptr_write(emu, SOLEH, 0, 0);
+
+	if (emu->audigy) {
+		/* set SPDIF bypass mode */
+		snd_emu10k1_ptr_write(emu, SPBYPASS, 0, SPBYPASS_FORMAT);
+		/* enable rear left + rear right AC97 slots */
+		snd_emu10k1_ptr_write(emu, AC97SLOT, 0, AC97SLOT_REAR_RIGHT |
+				      AC97SLOT_REAR_LEFT);
+	}
+
+	/* init envelope engine */
+	for (ch = 0; ch < NUM_G; ch++)
+		snd_emu10k1_voice_init(emu, ch);
+
+	snd_emu10k1_ptr_write(emu, SPCS0, 0, emu->spdif_bits[0]);
+	snd_emu10k1_ptr_write(emu, SPCS1, 0, emu->spdif_bits[1]);
+	snd_emu10k1_ptr_write(emu, SPCS2, 0, emu->spdif_bits[2]);
+
+	if (emu->card_capabilities->ca0151_chip) { /* audigy2 */
+		/* Hacks for Alice3 to work independent of haP16V driver */
+		/* Setup SRCMulti_I2S SamplingRate */
+		tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
+		tmp &= 0xfffff1ff;
+		tmp |= (0x2<<9);
+		snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp);
+
+		/* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
+		snd_emu10k1_ptr20_write(emu, SRCSel, 0, 0x14);
+		/* Setup SRCMulti Input Audio Enable */
+		/* Use 0xFFFFFFFF to enable P16V sounds. */
+		snd_emu10k1_ptr20_write(emu, SRCMULTI_ENABLE, 0, 0xFFFFFFFF);
+
+		/* Enabled Phased (8-channel) P16V playback */
+		outl(0x0201, emu->port + HCFG2);
+		/* Set playback routing. */
+		snd_emu10k1_ptr20_write(emu, CAPTURE_P16V_SOURCE, 0, 0x78e4);
+	}
+	if (emu->card_capabilities->ca0108_chip) { /* audigy2 Value */
+		/* Hacks for Alice3 to work independent of haP16V driver */
+		snd_printk(KERN_INFO "Audigy2 value: Special config.\n");
+		/* Setup SRCMulti_I2S SamplingRate */
+		tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
+		tmp &= 0xfffff1ff;
+		tmp |= (0x2<<9);
+		snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp);
+
+		/* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
+		outl(0x600000, emu->port + 0x20);
+		outl(0x14, emu->port + 0x24);
+
+		/* Setup SRCMulti Input Audio Enable */
+		outl(0x7b0000, emu->port + 0x20);
+		outl(0xFF000000, emu->port + 0x24);
+
+		/* Setup SPDIF Out Audio Enable */
+		/* The Audigy 2 Value has a separate SPDIF out,
+		 * so no need for a mixer switch
+		 */
+		outl(0x7a0000, emu->port + 0x20);
+		outl(0xFF000000, emu->port + 0x24);
+		tmp = inl(emu->port + A_IOCFG) & ~0x8; /* Clear bit 3 */
+		outl(tmp, emu->port + A_IOCFG);
+	}
+	if (emu->card_capabilities->spi_dac) { /* Audigy 2 ZS Notebook with DAC Wolfson WM8768/WM8568 */
+		int size, n;
+
+		size = ARRAY_SIZE(spi_dac_init);
+		for (n = 0; n < size; n++)
+			snd_emu10k1_spi_write(emu, spi_dac_init[n]);
+
+		snd_emu10k1_ptr20_write(emu, 0x60, 0, 0x10);
+		/* Enable GPIOs
+		 * GPIO0: Unknown
+		 * GPIO1: Speakers-enabled.
+		 * GPIO2: Unknown
+		 * GPIO3: Unknown
+		 * GPIO4: IEC958 Output on.
+		 * GPIO5: Unknown
+		 * GPIO6: Unknown
+		 * GPIO7: Unknown
+		 */
+		outl(0x76, emu->port + A_IOCFG); /* Windows uses 0x3f76 */
+	}
+	if (emu->card_capabilities->i2c_adc) { /* Audigy 2 ZS Notebook with ADC Wolfson WM8775 */
+		int size, n;
+
+		snd_emu10k1_ptr20_write(emu, P17V_I2S_SRC_SEL, 0, 0x2020205f);
+		tmp = inl(emu->port + A_IOCFG);
+		outl(tmp | 0x4, emu->port + A_IOCFG);  /* Set bit 2 for mic input */
+		tmp = inl(emu->port + A_IOCFG);
+		size = ARRAY_SIZE(i2c_adc_init);
+		for (n = 0; n < size; n++)
+			snd_emu10k1_i2c_write(emu, i2c_adc_init[n][0], i2c_adc_init[n][1]);
+		for (n = 0; n < 4; n++) {
+			emu->i2c_capture_volume[n][0] = 0xcf;
+			emu->i2c_capture_volume[n][1] = 0xcf;
+		}
+	}
+
+
+	snd_emu10k1_ptr_write(emu, PTB, 0, emu->ptb_pages.addr);
+	snd_emu10k1_ptr_write(emu, TCB, 0, 0);	/* taken from original driver */
+	snd_emu10k1_ptr_write(emu, TCBS, 0, 4);	/* taken from original driver */
+
+	silent_page = (emu->silent_page.addr << 1) | MAP_PTI_MASK;
+	for (ch = 0; ch < NUM_G; ch++) {
+		snd_emu10k1_ptr_write(emu, MAPA, ch, silent_page);
+		snd_emu10k1_ptr_write(emu, MAPB, ch, silent_page);
+	}
+
+	if (emu->card_capabilities->emu_model) {
+		outl(HCFG_AUTOMUTE_ASYNC |
+			HCFG_EMU32_SLAVE |
+			HCFG_AUDIOENABLE, emu->port + HCFG);
+	/*
+	 *  Hokay, setup HCFG
+	 *   Mute Disable Audio = 0
+	 *   Lock Tank Memory = 1
+	 *   Lock Sound Memory = 0
+	 *   Auto Mute = 1
+	 */
+	} else if (emu->audigy) {
+		if (emu->revision == 4) /* audigy2 */
+			outl(HCFG_AUDIOENABLE |
+			     HCFG_AC3ENABLE_CDSPDIF |
+			     HCFG_AC3ENABLE_GPSPDIF |
+			     HCFG_AUTOMUTE | HCFG_JOYENABLE, emu->port + HCFG);
+		else
+			outl(HCFG_AUTOMUTE | HCFG_JOYENABLE, emu->port + HCFG);
+	/* FIXME: Remove all these emu->model and replace it with a card recognition parameter,
+	 * e.g. card_capabilities->joystick */
+	} else if (emu->model == 0x20 ||
+	    emu->model == 0xc400 ||
+	    (emu->model == 0x21 && emu->revision < 6))
+		outl(HCFG_LOCKTANKCACHE_MASK | HCFG_AUTOMUTE, emu->port + HCFG);
+	else
+		/* With on-chip joystick */
+		outl(HCFG_LOCKTANKCACHE_MASK | HCFG_AUTOMUTE | HCFG_JOYENABLE, emu->port + HCFG);
+
+	if (enable_ir) {	/* enable IR for SB Live */
+		if (emu->card_capabilities->emu_model) {
+			;  /* Disable all access to A_IOCFG for the emu1010 */
+		} else if (emu->card_capabilities->i2c_adc) {
+			;  /* Disable A_IOCFG for Audigy 2 ZS Notebook */
+		} else if (emu->audigy) {
+			unsigned int reg = inl(emu->port + A_IOCFG);
+			outl(reg | A_IOCFG_GPOUT2, emu->port + A_IOCFG);
+			udelay(500);
+			outl(reg | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2, emu->port + A_IOCFG);
+			udelay(100);
+			outl(reg, emu->port + A_IOCFG);
+		} else {
+			unsigned int reg = inl(emu->port + HCFG);
+			outl(reg | HCFG_GPOUT2, emu->port + HCFG);
+			udelay(500);
+			outl(reg | HCFG_GPOUT1 | HCFG_GPOUT2, emu->port + HCFG);
+			udelay(100);
+			outl(reg, emu->port + HCFG);
+		}
+	}
+
+	if (emu->card_capabilities->emu_model) {
+		;  /* Disable all access to A_IOCFG for the emu1010 */
+	} else if (emu->card_capabilities->i2c_adc) {
+		;  /* Disable A_IOCFG for Audigy 2 ZS Notebook */
+	} else if (emu->audigy) {	/* enable analog output */
+		unsigned int reg = inl(emu->port + A_IOCFG);
+		outl(reg | A_IOCFG_GPOUT0, emu->port + A_IOCFG);
+	}
+
+	return 0;
+}
+
+static void snd_emu10k1_audio_enable(struct snd_emu10k1 *emu)
+{
+	/*
+	 *  Enable the audio bit
+	 */
+	outl(inl(emu->port + HCFG) | HCFG_AUDIOENABLE, emu->port + HCFG);
+
+	/* Enable analog/digital outs on audigy */
+	if (emu->card_capabilities->emu_model) {
+		;  /* Disable all access to A_IOCFG for the emu1010 */
+	} else if (emu->card_capabilities->i2c_adc) {
+		;  /* Disable A_IOCFG for Audigy 2 ZS Notebook */
+	} else if (emu->audigy) {
+		outl(inl(emu->port + A_IOCFG) & ~0x44, emu->port + A_IOCFG);
+
+		if (emu->card_capabilities->ca0151_chip) { /* audigy2 */
+			/* Unmute Analog now.  Set GPO6 to 1 for Apollo.
+			 * This has to be done after init ALice3 I2SOut beyond 48KHz.
+			 * So, sequence is important. */
+			outl(inl(emu->port + A_IOCFG) | 0x0040, emu->port + A_IOCFG);
+		} else if (emu->card_capabilities->ca0108_chip) { /* audigy2 value */
+			/* Unmute Analog now. */
+			outl(inl(emu->port + A_IOCFG) | 0x0060, emu->port + A_IOCFG);
+		} else {
+			/* Disable routing from AC97 line out to Front speakers */
+			outl(inl(emu->port + A_IOCFG) | 0x0080, emu->port + A_IOCFG);
+		}
+	}
+
+#if 0
+	{
+	unsigned int tmp;
+	/* FIXME: the following routine disables LiveDrive-II !! */
+	/* TOSLink detection */
+	emu->tos_link = 0;
+	tmp = inl(emu->port + HCFG);
+	if (tmp & (HCFG_GPINPUT0 | HCFG_GPINPUT1)) {
+		outl(tmp|0x800, emu->port + HCFG);
+		udelay(50);
+		if (tmp != (inl(emu->port + HCFG) & ~0x800)) {
+			emu->tos_link = 1;
+			outl(tmp, emu->port + HCFG);
+		}
+	}
+	}
+#endif
+
+	snd_emu10k1_intr_enable(emu, INTE_PCIERRORENABLE);
+}
+
+int snd_emu10k1_done(struct snd_emu10k1 *emu)
+{
+	int ch;
+
+	outl(0, emu->port + INTE);
+
+	/*
+	 *  Shutdown the chip
+	 */
+	for (ch = 0; ch < NUM_G; ch++)
+		snd_emu10k1_ptr_write(emu, DCYSUSV, ch, 0);
+	for (ch = 0; ch < NUM_G; ch++) {
+		snd_emu10k1_ptr_write(emu, VTFT, ch, 0);
+		snd_emu10k1_ptr_write(emu, CVCF, ch, 0);
+		snd_emu10k1_ptr_write(emu, PTRX, ch, 0);
+		snd_emu10k1_ptr_write(emu, CPF, ch, 0);
+	}
+
+	/* reset recording buffers */
+	snd_emu10k1_ptr_write(emu, MICBS, 0, 0);
+	snd_emu10k1_ptr_write(emu, MICBA, 0, 0);
+	snd_emu10k1_ptr_write(emu, FXBS, 0, 0);
+	snd_emu10k1_ptr_write(emu, FXBA, 0, 0);
+	snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
+	snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE);
+	snd_emu10k1_ptr_write(emu, ADCBA, 0, 0);
+	snd_emu10k1_ptr_write(emu, TCBS, 0, TCBS_BUFFSIZE_16K);
+	snd_emu10k1_ptr_write(emu, TCB, 0, 0);
+	if (emu->audigy)
+		snd_emu10k1_ptr_write(emu, A_DBG, 0, A_DBG_SINGLE_STEP);
+	else
+		snd_emu10k1_ptr_write(emu, DBG, 0, EMU10K1_DBG_SINGLE_STEP);
+
+	/* disable channel interrupt */
+	snd_emu10k1_ptr_write(emu, CLIEL, 0, 0);
+	snd_emu10k1_ptr_write(emu, CLIEH, 0, 0);
+	snd_emu10k1_ptr_write(emu, SOLEL, 0, 0);
+	snd_emu10k1_ptr_write(emu, SOLEH, 0, 0);
+
+	/* disable audio and lock cache */
+	outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, emu->port + HCFG);
+	snd_emu10k1_ptr_write(emu, PTB, 0, 0);
+
+	return 0;
+}
+
+/*************************************************************************
+ * ECARD functional implementation
+ *************************************************************************/
+
+/* In A1 Silicon, these bits are in the HC register */
+#define HOOKN_BIT		(1L << 12)
+#define HANDN_BIT		(1L << 11)
+#define PULSEN_BIT		(1L << 10)
+
+#define EC_GDI1			(1 << 13)
+#define EC_GDI0			(1 << 14)
+
+#define EC_NUM_CONTROL_BITS	20
+
+#define EC_AC3_DATA_SELN	0x0001L
+#define EC_EE_DATA_SEL		0x0002L
+#define EC_EE_CNTRL_SELN	0x0004L
+#define EC_EECLK		0x0008L
+#define EC_EECS			0x0010L
+#define EC_EESDO		0x0020L
+#define EC_TRIM_CSN		0x0040L
+#define EC_TRIM_SCLK		0x0080L
+#define EC_TRIM_SDATA		0x0100L
+#define EC_TRIM_MUTEN		0x0200L
+#define EC_ADCCAL		0x0400L
+#define EC_ADCRSTN		0x0800L
+#define EC_DACCAL		0x1000L
+#define EC_DACMUTEN		0x2000L
+#define EC_LEDN			0x4000L
+
+#define EC_SPDIF0_SEL_SHIFT	15
+#define EC_SPDIF1_SEL_SHIFT	17
+#define EC_SPDIF0_SEL_MASK	(0x3L << EC_SPDIF0_SEL_SHIFT)
+#define EC_SPDIF1_SEL_MASK	(0x7L << EC_SPDIF1_SEL_SHIFT)
+#define EC_SPDIF0_SELECT(_x)	(((_x) << EC_SPDIF0_SEL_SHIFT) & EC_SPDIF0_SEL_MASK)
+#define EC_SPDIF1_SELECT(_x)	(((_x) << EC_SPDIF1_SEL_SHIFT) & EC_SPDIF1_SEL_MASK)
+#define EC_CURRENT_PROM_VERSION 0x01	/* Self-explanatory.  This should
+					 * be incremented any time the EEPROM's
+					 * format is changed.  */
+
+#define EC_EEPROM_SIZE		0x40	/* ECARD EEPROM has 64 16-bit words */
+
+/* Addresses for special values stored in to EEPROM */
+#define EC_PROM_VERSION_ADDR	0x20	/* Address of the current prom version */
+#define EC_BOARDREV0_ADDR	0x21	/* LSW of board rev */
+#define EC_BOARDREV1_ADDR	0x22	/* MSW of board rev */
+
+#define EC_LAST_PROMFILE_ADDR	0x2f
+
+#define EC_SERIALNUM_ADDR	0x30	/* First word of serial number.  The
+					 * can be up to 30 characters in length
+					 * and is stored as a NULL-terminated
+					 * ASCII string.  Any unused bytes must be
+					 * filled with zeros */
+#define EC_CHECKSUM_ADDR	0x3f	/* Location at which checksum is stored */
+
+
+/* Most of this stuff is pretty self-evident.  According to the hardware
+ * dudes, we need to leave the ADCCAL bit low in order to avoid a DC
+ * offset problem.  Weird.
+ */
+#define EC_RAW_RUN_MODE		(EC_DACMUTEN | EC_ADCRSTN | EC_TRIM_MUTEN | \
+				 EC_TRIM_CSN)
+
+
+#define EC_DEFAULT_ADC_GAIN	0xC4C4
+#define EC_DEFAULT_SPDIF0_SEL	0x0
+#define EC_DEFAULT_SPDIF1_SEL	0x4
+
+/**************************************************************************
+ * @func Clock bits into the Ecard's control latch.  The Ecard uses a
+ *  control latch will is loaded bit-serially by toggling the Modem control
+ *  lines from function 2 on the E8010.  This function hides these details
+ *  and presents the illusion that we are actually writing to a distinct
+ *  register.
+ */
+
+static void snd_emu10k1_ecard_write(struct snd_emu10k1 *emu, unsigned int value)
+{
+	unsigned short count;
+	unsigned int data;
+	unsigned long hc_port;
+	unsigned int hc_value;
+
+	hc_port = emu->port + HCFG;
+	hc_value = inl(hc_port) & ~(HOOKN_BIT | HANDN_BIT | PULSEN_BIT);
+	outl(hc_value, hc_port);
+
+	for (count = 0; count < EC_NUM_CONTROL_BITS; count++) {
+
+		/* Set up the value */
+		data = ((value & 0x1) ? PULSEN_BIT : 0);
+		value >>= 1;
+
+		outl(hc_value | data, hc_port);
+
+		/* Clock the shift register */
+		outl(hc_value | data | HANDN_BIT, hc_port);
+		outl(hc_value | data, hc_port);
+	}
+
+	/* Latch the bits */
+	outl(hc_value | HOOKN_BIT, hc_port);
+	outl(hc_value, hc_port);
+}
+
+/**************************************************************************
+ * @func Set the gain of the ECARD's CS3310 Trim/gain controller.  The
+ * trim value consists of a 16bit value which is composed of two
+ * 8 bit gain/trim values, one for the left channel and one for the
+ * right channel.  The following table maps from the Gain/Attenuation
+ * value in decibels into the corresponding bit pattern for a single
+ * channel.
+ */
+
+static void snd_emu10k1_ecard_setadcgain(struct snd_emu10k1 *emu,
+					 unsigned short gain)
+{
+	unsigned int bit;
+
+	/* Enable writing to the TRIM registers */
+	snd_emu10k1_ecard_write(emu, emu->ecard_ctrl & ~EC_TRIM_CSN);
+
+	/* Do it again to insure that we meet hold time requirements */
+	snd_emu10k1_ecard_write(emu, emu->ecard_ctrl & ~EC_TRIM_CSN);
+
+	for (bit = (1 << 15); bit; bit >>= 1) {
+		unsigned int value;
+
+		value = emu->ecard_ctrl & ~(EC_TRIM_CSN | EC_TRIM_SDATA);
+
+		if (gain & bit)
+			value |= EC_TRIM_SDATA;
+
+		/* Clock the bit */
+		snd_emu10k1_ecard_write(emu, value);
+		snd_emu10k1_ecard_write(emu, value | EC_TRIM_SCLK);
+		snd_emu10k1_ecard_write(emu, value);
+	}
+
+	snd_emu10k1_ecard_write(emu, emu->ecard_ctrl);
+}
+
+static int snd_emu10k1_ecard_init(struct snd_emu10k1 *emu)
+{
+	unsigned int hc_value;
+
+	/* Set up the initial settings */
+	emu->ecard_ctrl = EC_RAW_RUN_MODE |
+			  EC_SPDIF0_SELECT(EC_DEFAULT_SPDIF0_SEL) |
+			  EC_SPDIF1_SELECT(EC_DEFAULT_SPDIF1_SEL);
+
+	/* Step 0: Set the codec type in the hardware control register
+	 * and enable audio output */
+	hc_value = inl(emu->port + HCFG);
+	outl(hc_value | HCFG_AUDIOENABLE | HCFG_CODECFORMAT_I2S, emu->port + HCFG);
+	inl(emu->port + HCFG);
+
+	/* Step 1: Turn off the led and deassert TRIM_CS */
+	snd_emu10k1_ecard_write(emu, EC_ADCCAL | EC_LEDN | EC_TRIM_CSN);
+
+	/* Step 2: Calibrate the ADC and DAC */
+	snd_emu10k1_ecard_write(emu, EC_DACCAL | EC_LEDN | EC_TRIM_CSN);
+
+	/* Step 3: Wait for awhile;   XXX We can't get away with this
+	 * under a real operating system; we'll need to block and wait that
+	 * way. */
+	snd_emu10k1_wait(emu, 48000);
+
+	/* Step 4: Switch off the DAC and ADC calibration.  Note
+	 * That ADC_CAL is actually an inverted signal, so we assert
+	 * it here to stop calibration.  */
+	snd_emu10k1_ecard_write(emu, EC_ADCCAL | EC_LEDN | EC_TRIM_CSN);
+
+	/* Step 4: Switch into run mode */
+	snd_emu10k1_ecard_write(emu, emu->ecard_ctrl);
+
+	/* Step 5: Set the analog input gain */
+	snd_emu10k1_ecard_setadcgain(emu, EC_DEFAULT_ADC_GAIN);
+
+	return 0;
+}
+
+static int snd_emu10k1_cardbus_init(struct snd_emu10k1 *emu)
+{
+	unsigned long special_port;
+	unsigned int value;
+
+	/* Special initialisation routine
+	 * before the rest of the IO-Ports become active.
+	 */
+	special_port = emu->port + 0x38;
+	value = inl(special_port);
+	outl(0x00d00000, special_port);
+	value = inl(special_port);
+	outl(0x00d00001, special_port);
+	value = inl(special_port);
+	outl(0x00d0005f, special_port);
+	value = inl(special_port);
+	outl(0x00d0007f, special_port);
+	value = inl(special_port);
+	outl(0x0090007f, special_port);
+	value = inl(special_port);
+
+	snd_emu10k1_ptr20_write(emu, TINA2_VOLUME, 0, 0xfefefefe); /* Defaults to 0x30303030 */
+	/* Delay to give time for ADC chip to switch on. It needs 113ms */
+	msleep(200);
+	return 0;
+}
+
+static int snd_emu1010_load_firmware(struct snd_emu10k1 *emu, const char *filename)
+{
+	int err;
+	int n, i;
+	int reg;
+	int value;
+	unsigned int write_post;
+	unsigned long flags;
+	const struct firmware *fw_entry;
+
+	err = request_firmware(&fw_entry, filename, &emu->pci->dev);
+	if (err != 0) {
+		snd_printk(KERN_ERR "firmware: %s not found. Err = %d\n", filename, err);
+		return err;
+	}
+	snd_printk(KERN_INFO "firmware size = 0x%zx\n", fw_entry->size);
+
+	/* The FPGA is a Xilinx Spartan IIE XC2S50E */
+	/* GPIO7 -> FPGA PGMN
+	 * GPIO6 -> FPGA CCLK
+	 * GPIO5 -> FPGA DIN
+	 * FPGA CONFIG OFF -> FPGA PGMN
+	 */
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(0x00, emu->port + A_IOCFG); /* Set PGMN low for 1uS. */
+	write_post = inl(emu->port + A_IOCFG);
+	udelay(100);
+	outl(0x80, emu->port + A_IOCFG); /* Leave bit 7 set during netlist setup. */
+	write_post = inl(emu->port + A_IOCFG);
+	udelay(100); /* Allow FPGA memory to clean */
+	for (n = 0; n < fw_entry->size; n++) {
+		value = fw_entry->data[n];
+		for (i = 0; i < 8; i++) {
+			reg = 0x80;
+			if (value & 0x1)
+				reg = reg | 0x20;
+			value = value >> 1;
+			outl(reg, emu->port + A_IOCFG);
+			write_post = inl(emu->port + A_IOCFG);
+			outl(reg | 0x40, emu->port + A_IOCFG);
+			write_post = inl(emu->port + A_IOCFG);
+		}
+	}
+	/* After programming, set GPIO bit 4 high again. */
+	outl(0x10, emu->port + A_IOCFG);
+	write_post = inl(emu->port + A_IOCFG);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+
+	release_firmware(fw_entry);
+	return 0;
+}
+
+static int emu1010_firmware_thread(void *data)
+{
+	struct snd_emu10k1 *emu = data;
+	u32 tmp, tmp2, reg;
+	int err;
+
+	for (;;) {
+		/* Delay to allow Audio Dock to settle */
+		msleep_interruptible(1000);
+		if (kthread_should_stop())
+			break;
+		snd_emu1010_fpga_read(emu, EMU_HANA_IRQ_STATUS, &tmp); /* IRQ Status */
+		snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &reg); /* OPTIONS: Which cards are attached to the EMU */
+		if (reg & EMU_HANA_OPTION_DOCK_OFFLINE) {
+			/* Audio Dock attached */
+			/* Return to Audio Dock programming mode */
+			snd_printk(KERN_INFO "emu1010: Loading Audio Dock Firmware\n");
+			snd_emu1010_fpga_write(emu, EMU_HANA_FPGA_CONFIG, EMU_HANA_FPGA_CONFIG_AUDIODOCK);
+			if (emu->card_capabilities->emu_model ==
+			    EMU_MODEL_EMU1010) {
+				err = snd_emu1010_load_firmware(emu, DOCK_FILENAME);
+				if (err != 0)
+					continue;
+			} else if (emu->card_capabilities->emu_model ==
+				   EMU_MODEL_EMU1010B) {
+				err = snd_emu1010_load_firmware(emu, MICRO_DOCK_FILENAME);
+				if (err != 0)
+					continue;
+			} else if (emu->card_capabilities->emu_model ==
+				   EMU_MODEL_EMU1616) {
+				err = snd_emu1010_load_firmware(emu, MICRO_DOCK_FILENAME);
+				if (err != 0)
+					continue;
+			}
+
+			snd_emu1010_fpga_write(emu, EMU_HANA_FPGA_CONFIG, 0);
+			snd_emu1010_fpga_read(emu, EMU_HANA_IRQ_STATUS, &reg);
+			snd_printk(KERN_INFO "emu1010: EMU_HANA+DOCK_IRQ_STATUS = 0x%x\n", reg);
+			/* ID, should read & 0x7f = 0x55 when FPGA programmed. */
+			snd_emu1010_fpga_read(emu, EMU_HANA_ID, &reg);
+			snd_printk(KERN_INFO "emu1010: EMU_HANA+DOCK_ID = 0x%x\n", reg);
+			if ((reg & 0x1f) != 0x15) {
+				/* FPGA failed to be programmed */
+				snd_printk(KERN_INFO "emu1010: Loading Audio Dock Firmware file failed, reg = 0x%x\n", reg);
+				continue;
+			}
+			snd_printk(KERN_INFO "emu1010: Audio Dock Firmware loaded\n");
+			snd_emu1010_fpga_read(emu, EMU_DOCK_MAJOR_REV, &tmp);
+			snd_emu1010_fpga_read(emu, EMU_DOCK_MINOR_REV, &tmp2);
+			snd_printk(KERN_INFO "Audio Dock ver: %u.%u\n",
+				   tmp, tmp2);
+			/* Sync clocking between 1010 and Dock */
+			/* Allow DLL to settle */
+			msleep(10);
+			/* Unmute all. Default is muted after a firmware load */
+			snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE);
+		}
+	}
+	snd_printk(KERN_INFO "emu1010: firmware thread stopping\n");
+	return 0;
+}
+
+/*
+ * EMU-1010 - details found out from this driver, official MS Win drivers,
+ * testing the card:
+ *
+ * Audigy2 (aka Alice2):
+ * ---------------------
+ * 	* communication over PCI
+ * 	* conversion of 32-bit data coming over EMU32 links from HANA FPGA
+ *	  to 2 x 16-bit, using internal DSP instructions
+ * 	* slave mode, clock supplied by HANA
+ * 	* linked to HANA using:
+ * 		32 x 32-bit serial EMU32 output channels
+ * 		16 x EMU32 input channels
+ * 		(?) x I2S I/O channels (?)
+ *
+ * FPGA (aka HANA):
+ * ---------------
+ * 	* provides all (?) physical inputs and outputs of the card
+ * 		(ADC, DAC, SPDIF I/O, ADAT I/O, etc.)
+ * 	* provides clock signal for the card and Alice2
+ * 	* two crystals - for 44.1kHz and 48kHz multiples
+ * 	* provides internal routing of signal sources to signal destinations
+ * 	* inputs/outputs to Alice2 - see above
+ *
+ * Current status of the driver:
+ * ----------------------------
+ * 	* only 44.1/48kHz supported (the MS Win driver supports up to 192 kHz)
+ * 	* PCM device nb. 2:
+ *		16 x 16-bit playback - snd_emu10k1_fx8010_playback_ops
+ * 		16 x 32-bit capture - snd_emu10k1_capture_efx_ops
+ */
+static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu)
+{
+	unsigned int i;
+	u32 tmp, tmp2, reg;
+	int err;
+	const char *filename = NULL;
+
+	snd_printk(KERN_INFO "emu1010: Special config.\n");
+	/* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
+	 * Lock Sound Memory Cache, Lock Tank Memory Cache,
+	 * Mute all codecs.
+	 */
+	outl(0x0005a00c, emu->port + HCFG);
+	/* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
+	 * Lock Tank Memory Cache,
+	 * Mute all codecs.
+	 */
+	outl(0x0005a004, emu->port + HCFG);
+	/* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
+	 * Mute all codecs.
+	 */
+	outl(0x0005a000, emu->port + HCFG);
+	/* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
+	 * Mute all codecs.
+	 */
+	outl(0x0005a000, emu->port + HCFG);
+
+	/* Disable 48Volt power to Audio Dock */
+	snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_PWR, 0);
+
+	/* ID, should read & 0x7f = 0x55. (Bit 7 is the IRQ bit) */
+	snd_emu1010_fpga_read(emu, EMU_HANA_ID, &reg);
+	snd_printdd("reg1 = 0x%x\n", reg);
+	if ((reg & 0x3f) == 0x15) {
+		/* FPGA netlist already present so clear it */
+		/* Return to programming mode */
+
+		snd_emu1010_fpga_write(emu, EMU_HANA_FPGA_CONFIG, 0x02);
+	}
+	snd_emu1010_fpga_read(emu, EMU_HANA_ID, &reg);
+	snd_printdd("reg2 = 0x%x\n", reg);
+	if ((reg & 0x3f) == 0x15) {
+		/* FPGA failed to return to programming mode */
+		snd_printk(KERN_INFO "emu1010: FPGA failed to return to programming mode\n");
+		return -ENODEV;
+	}
+	snd_printk(KERN_INFO "emu1010: EMU_HANA_ID = 0x%x\n", reg);
+	switch (emu->card_capabilities->emu_model) {
+	case EMU_MODEL_EMU1010:
+		filename = HANA_FILENAME;
+		break;
+	case EMU_MODEL_EMU1010B:
+		filename = EMU1010B_FILENAME;
+		break;
+	case EMU_MODEL_EMU1616:
+		filename = EMU1010_NOTEBOOK_FILENAME;
+		break;
+	case EMU_MODEL_EMU0404:
+		filename = EMU0404_FILENAME;
+		break;
+	default:
+		filename = NULL;
+		return -ENODEV;
+		break;
+	}
+	snd_printk(KERN_INFO "emu1010: filename %s testing\n", filename);
+	err = snd_emu1010_load_firmware(emu, filename);
+	if (err != 0) {
+		snd_printk(
+			KERN_INFO "emu1010: Loading Firmware file %s failed\n",
+			filename);
+		return err;
+	}
+
+	/* ID, should read & 0x7f = 0x55 when FPGA programmed. */
+	snd_emu1010_fpga_read(emu, EMU_HANA_ID, &reg);
+	if ((reg & 0x3f) != 0x15) {
+		/* FPGA failed to be programmed */
+		snd_printk(KERN_INFO "emu1010: Loading Hana Firmware file failed, reg = 0x%x\n", reg);
+		return -ENODEV;
+	}
+
+	snd_printk(KERN_INFO "emu1010: Hana Firmware loaded\n");
+	snd_emu1010_fpga_read(emu, EMU_HANA_MAJOR_REV, &tmp);
+	snd_emu1010_fpga_read(emu, EMU_HANA_MINOR_REV, &tmp2);
+	snd_printk(KERN_INFO "emu1010: Hana version: %u.%u\n", tmp, tmp2);
+	/* Enable 48Volt power to Audio Dock */
+	snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_PWR, EMU_HANA_DOCK_PWR_ON);
+
+	snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &reg);
+	snd_printk(KERN_INFO "emu1010: Card options = 0x%x\n", reg);
+	snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &reg);
+	snd_printk(KERN_INFO "emu1010: Card options = 0x%x\n", reg);
+	snd_emu1010_fpga_read(emu, EMU_HANA_OPTICAL_TYPE, &tmp);
+	/* Optical -> ADAT I/O  */
+	/* 0 : SPDIF
+	 * 1 : ADAT
+	 */
+	emu->emu1010.optical_in = 1; /* IN_ADAT */
+	emu->emu1010.optical_out = 1; /* IN_ADAT */
+	tmp = 0;
+	tmp = (emu->emu1010.optical_in ? EMU_HANA_OPTICAL_IN_ADAT : 0) |
+		(emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : 0);
+	snd_emu1010_fpga_write(emu, EMU_HANA_OPTICAL_TYPE, tmp);
+	snd_emu1010_fpga_read(emu, EMU_HANA_ADC_PADS, &tmp);
+	/* Set no attenuation on Audio Dock pads. */
+	snd_emu1010_fpga_write(emu, EMU_HANA_ADC_PADS, 0x00);
+	emu->emu1010.adc_pads = 0x00;
+	snd_emu1010_fpga_read(emu, EMU_HANA_DOCK_MISC, &tmp);
+	/* Unmute Audio dock DACs, Headphone source DAC-4. */
+	snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_MISC, 0x30);
+	snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, 0x12);
+	snd_emu1010_fpga_read(emu, EMU_HANA_DAC_PADS, &tmp);
+	/* DAC PADs. */
+	snd_emu1010_fpga_write(emu, EMU_HANA_DAC_PADS, 0x0f);
+	emu->emu1010.dac_pads = 0x0f;
+	snd_emu1010_fpga_read(emu, EMU_HANA_DOCK_MISC, &tmp);
+	snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_MISC, 0x30);
+	snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &tmp);
+	/* SPDIF Format. Set Consumer mode, 24bit, copy enable */
+	snd_emu1010_fpga_write(emu, EMU_HANA_SPDIF_MODE, 0x10);
+	/* MIDI routing */
+	snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_IN, 0x19);
+	/* Unknown. */
+	snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_OUT, 0x0c);
+	/* IRQ Enable: Alll on */
+	/* snd_emu1010_fpga_write(emu, 0x09, 0x0f ); */
+	/* IRQ Enable: All off */
+	snd_emu1010_fpga_write(emu, EMU_HANA_IRQ_ENABLE, 0x00);
+
+	snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &reg);
+	snd_printk(KERN_INFO "emu1010: Card options3 = 0x%x\n", reg);
+	/* Default WCLK set to 48kHz. */
+	snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, 0x00);
+	/* Word Clock source, Internal 48kHz x1 */
+	snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K);
+	/* snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_4X); */
+	/* Audio Dock LEDs. */
+	snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, 0x12);
+
+#if 0
+	/* For 96kHz */
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_0, EMU_SRC_HAMOA_ADC_LEFT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_1, EMU_SRC_HAMOA_ADC_RIGHT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_4, EMU_SRC_HAMOA_ADC_LEFT2);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_5, EMU_SRC_HAMOA_ADC_RIGHT2);
+#endif
+#if 0
+	/* For 192kHz */
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_0, EMU_SRC_HAMOA_ADC_LEFT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_1, EMU_SRC_HAMOA_ADC_RIGHT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_2, EMU_SRC_HAMOA_ADC_LEFT2);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_3, EMU_SRC_HAMOA_ADC_RIGHT2);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_4, EMU_SRC_HAMOA_ADC_LEFT3);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_5, EMU_SRC_HAMOA_ADC_RIGHT3);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_6, EMU_SRC_HAMOA_ADC_LEFT4);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_7, EMU_SRC_HAMOA_ADC_RIGHT4);
+#endif
+#if 1
+	/* For 48kHz */
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_0, EMU_SRC_DOCK_MIC_A1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_1, EMU_SRC_DOCK_MIC_B1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_2, EMU_SRC_HAMOA_ADC_LEFT2);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_3, EMU_SRC_HAMOA_ADC_LEFT2);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_4, EMU_SRC_DOCK_ADC1_LEFT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_5, EMU_SRC_DOCK_ADC1_RIGHT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_6, EMU_SRC_DOCK_ADC2_LEFT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_7, EMU_SRC_DOCK_ADC2_RIGHT1);
+	/* Pavel Hofman - setting defaults for 8 more capture channels
+	 * Defaults only, users will set their own values anyways, let's
+	 * just copy/paste.
+	 */
+
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_8, EMU_SRC_DOCK_MIC_A1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_9, EMU_SRC_DOCK_MIC_B1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_A, EMU_SRC_HAMOA_ADC_LEFT2);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_B, EMU_SRC_HAMOA_ADC_LEFT2);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_C, EMU_SRC_DOCK_ADC1_LEFT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_D, EMU_SRC_DOCK_ADC1_RIGHT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_E, EMU_SRC_DOCK_ADC2_LEFT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_F, EMU_SRC_DOCK_ADC2_RIGHT1);
+#endif
+#if 0
+	/* Original */
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_4, EMU_SRC_HANA_ADAT);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_5, EMU_SRC_HANA_ADAT + 1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_6, EMU_SRC_HANA_ADAT + 2);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_7, EMU_SRC_HANA_ADAT + 3);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_8, EMU_SRC_HANA_ADAT + 4);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_9, EMU_SRC_HANA_ADAT + 5);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_A, EMU_SRC_HANA_ADAT + 6);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_B, EMU_SRC_HANA_ADAT + 7);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_C, EMU_SRC_DOCK_MIC_A1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_D, EMU_SRC_DOCK_MIC_B1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_E, EMU_SRC_HAMOA_ADC_LEFT2);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE2_EMU32_F, EMU_SRC_HAMOA_ADC_LEFT2);
+#endif
+	for (i = 0; i < 0x20; i++) {
+		/* AudioDock Elink <- Silence */
+		snd_emu1010_fpga_link_dst_src_write(emu, 0x0100 + i, EMU_SRC_SILENCE);
+	}
+	for (i = 0; i < 4; i++) {
+		/* Hana SPDIF Out <- Silence */
+		snd_emu1010_fpga_link_dst_src_write(emu, 0x0200 + i, EMU_SRC_SILENCE);
+	}
+	for (i = 0; i < 7; i++) {
+		/* Hamoa DAC <- Silence */
+		snd_emu1010_fpga_link_dst_src_write(emu, 0x0300 + i, EMU_SRC_SILENCE);
+	}
+	for (i = 0; i < 7; i++) {
+		/* Hana ADAT Out <- Silence */
+		snd_emu1010_fpga_link_dst_src_write(emu, EMU_DST_HANA_ADAT + i, EMU_SRC_SILENCE);
+	}
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE_I2S0_LEFT, EMU_SRC_DOCK_ADC1_LEFT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE_I2S0_RIGHT, EMU_SRC_DOCK_ADC1_RIGHT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE_I2S1_LEFT, EMU_SRC_DOCK_ADC2_LEFT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE_I2S1_RIGHT, EMU_SRC_DOCK_ADC2_RIGHT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE_I2S2_LEFT, EMU_SRC_DOCK_ADC3_LEFT1);
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_ALICE_I2S2_RIGHT, EMU_SRC_DOCK_ADC3_RIGHT1);
+	snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, 0x01); /* Unmute all */
+
+	snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &tmp);
+
+	/* AC97 1.03, Any 32Meg of 2Gig address, Auto-Mute, EMU32 Slave,
+	 * Lock Sound Memory Cache, Lock Tank Memory Cache,
+	 * Mute all codecs.
+	 */
+	outl(0x0000a000, emu->port + HCFG);
+	/* AC97 1.03, Any 32Meg of 2Gig address, Auto-Mute, EMU32 Slave,
+	 * Lock Sound Memory Cache, Lock Tank Memory Cache,
+	 * Un-Mute all codecs.
+	 */
+	outl(0x0000a001, emu->port + HCFG);
+
+	/* Initial boot complete. Now patches */
+
+	snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &tmp);
+	snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_IN, 0x19); /* MIDI Route */
+	snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_OUT, 0x0c); /* Unknown */
+	snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_IN, 0x19); /* MIDI Route */
+	snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_OUT, 0x0c); /* Unknown */
+	snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &tmp);
+	snd_emu1010_fpga_write(emu, EMU_HANA_SPDIF_MODE, 0x10); /* SPDIF Format spdif  (or 0x11 for aes/ebu) */
+
+	/* Start Micro/Audio Dock firmware loader thread */
+	if (!emu->emu1010.firmware_thread) {
+		emu->emu1010.firmware_thread =
+			kthread_create(emu1010_firmware_thread, emu,
+				       "emu1010_firmware");
+		wake_up_process(emu->emu1010.firmware_thread);
+	}
+
+#if 0
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_HAMOA_DAC_LEFT1, EMU_SRC_ALICE_EMU32B + 2); /* ALICE2 bus 0xa2 */
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_HAMOA_DAC_RIGHT1, EMU_SRC_ALICE_EMU32B + 3); /* ALICE2 bus 0xa3 */
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_HANA_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 2); /* ALICE2 bus 0xb2 */
+	snd_emu1010_fpga_link_dst_src_write(emu,
+		EMU_DST_HANA_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 3); /* ALICE2 bus 0xb3 */
+#endif
+	/* Default outputs */
+	if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) {
+		/* 1616(M) cardbus default outputs */
+		/* ALICE2 bus 0xa0 */
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC1_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+		emu->emu1010.output_source[0] = 17;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC1_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+		emu->emu1010.output_source[1] = 18;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC2_LEFT1, EMU_SRC_ALICE_EMU32A + 2);
+		emu->emu1010.output_source[2] = 19;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC2_RIGHT1, EMU_SRC_ALICE_EMU32A + 3);
+		emu->emu1010.output_source[3] = 20;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC3_LEFT1, EMU_SRC_ALICE_EMU32A + 4);
+		emu->emu1010.output_source[4] = 21;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC3_RIGHT1, EMU_SRC_ALICE_EMU32A + 5);
+		emu->emu1010.output_source[5] = 22;
+		/* ALICE2 bus 0xa0 */
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_MANA_DAC_LEFT, EMU_SRC_ALICE_EMU32A + 0);
+		emu->emu1010.output_source[16] = 17;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_MANA_DAC_RIGHT, EMU_SRC_ALICE_EMU32A + 1);
+		emu->emu1010.output_source[17] = 18;
+	} else {
+		/* ALICE2 bus 0xa0 */
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC1_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+		emu->emu1010.output_source[0] = 21;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC1_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+		emu->emu1010.output_source[1] = 22;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC2_LEFT1, EMU_SRC_ALICE_EMU32A + 2);
+		emu->emu1010.output_source[2] = 23;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC2_RIGHT1, EMU_SRC_ALICE_EMU32A + 3);
+		emu->emu1010.output_source[3] = 24;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC3_LEFT1, EMU_SRC_ALICE_EMU32A + 4);
+		emu->emu1010.output_source[4] = 25;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC3_RIGHT1, EMU_SRC_ALICE_EMU32A + 5);
+		emu->emu1010.output_source[5] = 26;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC4_LEFT1, EMU_SRC_ALICE_EMU32A + 6);
+		emu->emu1010.output_source[6] = 27;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_DAC4_RIGHT1, EMU_SRC_ALICE_EMU32A + 7);
+		emu->emu1010.output_source[7] = 28;
+		/* ALICE2 bus 0xa0 */
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_PHONES_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+		emu->emu1010.output_source[8] = 21;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_PHONES_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+		emu->emu1010.output_source[9] = 22;
+		/* ALICE2 bus 0xa0 */
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+		emu->emu1010.output_source[10] = 21;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_DOCK_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+		emu->emu1010.output_source[11] = 22;
+		/* ALICE2 bus 0xa0 */
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HANA_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+		emu->emu1010.output_source[12] = 21;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HANA_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+		emu->emu1010.output_source[13] = 22;
+		/* ALICE2 bus 0xa0 */
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HAMOA_DAC_LEFT1, EMU_SRC_ALICE_EMU32A + 0);
+		emu->emu1010.output_source[14] = 21;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HAMOA_DAC_RIGHT1, EMU_SRC_ALICE_EMU32A + 1);
+		emu->emu1010.output_source[15] = 22;
+		/* ALICE2 bus 0xa0 */
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HANA_ADAT, EMU_SRC_ALICE_EMU32A + 0);
+		emu->emu1010.output_source[16] = 21;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HANA_ADAT + 1, EMU_SRC_ALICE_EMU32A + 1);
+		emu->emu1010.output_source[17] = 22;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HANA_ADAT + 2, EMU_SRC_ALICE_EMU32A + 2);
+		emu->emu1010.output_source[18] = 23;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HANA_ADAT + 3, EMU_SRC_ALICE_EMU32A + 3);
+		emu->emu1010.output_source[19] = 24;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HANA_ADAT + 4, EMU_SRC_ALICE_EMU32A + 4);
+		emu->emu1010.output_source[20] = 25;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HANA_ADAT + 5, EMU_SRC_ALICE_EMU32A + 5);
+		emu->emu1010.output_source[21] = 26;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HANA_ADAT + 6, EMU_SRC_ALICE_EMU32A + 6);
+		emu->emu1010.output_source[22] = 27;
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			EMU_DST_HANA_ADAT + 7, EMU_SRC_ALICE_EMU32A + 7);
+		emu->emu1010.output_source[23] = 28;
+	}
+	/* TEMP: Select SPDIF in/out */
+	/* snd_emu1010_fpga_write(emu, EMU_HANA_OPTICAL_TYPE, 0x0); */ /* Output spdif */
+
+	/* TEMP: Select 48kHz SPDIF out */
+	snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, 0x0); /* Mute all */
+	snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, 0x0); /* Default fallback clock 48kHz */
+	/* Word Clock source, Internal 48kHz x1 */
+	snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K);
+	/* snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_4X); */
+	emu->emu1010.internal_clock = 1; /* 48000 */
+	snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, 0x12); /* Set LEDs on Audio Dock */
+	snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, 0x1); /* Unmute all */
+	/* snd_emu1010_fpga_write(emu, 0x7, 0x0); */ /* Mute all */
+	/* snd_emu1010_fpga_write(emu, 0x7, 0x1); */ /* Unmute all */
+	/* snd_emu1010_fpga_write(emu, 0xe, 0x12); */ /* Set LEDs on Audio Dock */
+
+	return 0;
+}
+/*
+ *  Create the EMU10K1 instance
+ */
+
+#ifdef CONFIG_PM
+static int alloc_pm_buffer(struct snd_emu10k1 *emu);
+static void free_pm_buffer(struct snd_emu10k1 *emu);
+#endif
+
+static int snd_emu10k1_free(struct snd_emu10k1 *emu)
+{
+	if (emu->port) {	/* avoid access to already used hardware */
+		snd_emu10k1_fx8010_tram_setup(emu, 0);
+		snd_emu10k1_done(emu);
+		snd_emu10k1_free_efx(emu);
+	}
+	if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1010) {
+		/* Disable 48Volt power to Audio Dock */
+		snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_PWR, 0);
+	}
+	if (emu->emu1010.firmware_thread)
+		kthread_stop(emu->emu1010.firmware_thread);
+	if (emu->irq >= 0)
+		free_irq(emu->irq, emu);
+	/* remove reserved page */
+	if (emu->reserved_page) {
+		snd_emu10k1_synth_free(emu,
+			(struct snd_util_memblk *)emu->reserved_page);
+		emu->reserved_page = NULL;
+	}
+	if (emu->memhdr)
+		snd_util_memhdr_free(emu->memhdr);
+	if (emu->silent_page.area)
+		snd_dma_free_pages(&emu->silent_page);
+	if (emu->ptb_pages.area)
+		snd_dma_free_pages(&emu->ptb_pages);
+	vfree(emu->page_ptr_table);
+	vfree(emu->page_addr_table);
+#ifdef CONFIG_PM
+	free_pm_buffer(emu);
+#endif
+	if (emu->port)
+		pci_release_regions(emu->pci);
+	if (emu->card_capabilities->ca0151_chip) /* P16V */
+		snd_p16v_free(emu);
+	pci_disable_device(emu->pci);
+	kfree(emu);
+	return 0;
+}
+
+static int snd_emu10k1_dev_free(struct snd_device *device)
+{
+	struct snd_emu10k1 *emu = device->device_data;
+	return snd_emu10k1_free(emu);
+}
+
+static struct snd_emu_chip_details emu_chip_details[] = {
+	/* Audigy4 (Not PRO) SB0610 */
+	/* Tested by James@superbug.co.uk 4th April 2006 */
+	/* A_IOCFG bits
+	 * Output
+	 * 0: ?
+	 * 1: ?
+	 * 2: ?
+	 * 3: 0 - Digital Out, 1 - Line in
+	 * 4: ?
+	 * 5: ?
+	 * 6: ?
+	 * 7: ?
+	 * Input
+	 * 8: ?
+	 * 9: ?
+	 * A: Green jack sense (Front)
+	 * B: ?
+	 * C: Black jack sense (Rear/Side Right)
+	 * D: Yellow jack sense (Center/LFE/Side Left)
+	 * E: ?
+	 * F: ?
+	 *
+	 * Digital Out/Line in switch using A_IOCFG bit 3 (0x08)
+	 * 0 - Digital Out
+	 * 1 - Line in
+	 */
+	/* Mic input not tested.
+	 * Analog CD input not tested
+	 * Digital Out not tested.
+	 * Line in working.
+	 * Audio output 5.1 working. Side outputs not working.
+	 */
+	/* DSP: CA10300-IAT LF
+	 * DAC: Cirrus Logic CS4382-KQZ
+	 * ADC: Philips 1361T
+	 * AC97: Sigmatel STAC9750
+	 * CA0151: None
+	 */
+	{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x10211102,
+	 .driver = "Audigy2", .name = "SB Audigy 4 [SB0610]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0108_chip = 1,
+	 .spk71 = 1,
+	 .adc_1361t = 1,  /* 24 bit capture instead of 16bit */
+	 .ac97_chip = 1} ,
+	/* Audigy 2 Value AC3 out does not work yet.
+	 * Need to find out how to turn off interpolators.
+	 */
+	/* Tested by James@superbug.co.uk 3rd July 2005 */
+	/* DSP: CA0108-IAT
+	 * DAC: CS4382-KQ
+	 * ADC: Philips 1361T
+	 * AC97: STAC9750
+	 * CA0151: None
+	 */
+	{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x10011102,
+	 .driver = "Audigy2", .name = "SB Audigy 2 Value [SB0400]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0108_chip = 1,
+	 .spk71 = 1,
+	 .ac97_chip = 1} ,
+	/* Audigy 2 ZS Notebook Cardbus card.*/
+	/* Tested by James@superbug.co.uk 6th November 2006 */
+	/* Audio output 7.1/Headphones working.
+	 * Digital output working. (AC3 not checked, only PCM)
+	 * Audio Mic/Line inputs working.
+	 * Digital input not tested.
+	 */
+	/* DSP: Tina2
+	 * DAC: Wolfson WM8768/WM8568
+	 * ADC: Wolfson WM8775
+	 * AC97: None
+	 * CA0151: None
+	 */
+	/* Tested by James@superbug.co.uk 4th April 2006 */
+	/* A_IOCFG bits
+	 * Output
+	 * 0: Not Used
+	 * 1: 0 = Mute all the 7.1 channel out. 1 = unmute.
+	 * 2: Analog input 0 = line in, 1 = mic in
+	 * 3: Not Used
+	 * 4: Digital output 0 = off, 1 = on.
+	 * 5: Not Used
+	 * 6: Not Used
+	 * 7: Not Used
+	 * Input
+	 *      All bits 1 (0x3fxx) means nothing plugged in.
+	 * 8-9: 0 = Line in/Mic, 2 = Optical in, 3 = Nothing.
+	 * A-B: 0 = Headphones, 2 = Optical out, 3 = Nothing.
+	 * C-D: 2 = Front/Rear/etc, 3 = nothing.
+	 * E-F: Always 0
+	 *
+	 */
+	{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x20011102,
+	 .driver = "Audigy2", .name = "SB Audigy 2 ZS Notebook [SB0530]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0108_chip = 1,
+	 .ca_cardbus_chip = 1,
+	 .spi_dac = 1,
+	 .i2c_adc = 1,
+	 .spk71 = 1} ,
+	/* Tested by James@superbug.co.uk 4th Nov 2007. */
+	{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x42011102,
+	 .driver = "Audigy2", .name = "E-mu 1010 Notebook [MAEM8950]",
+	 .id = "EMU1010",
+	 .emu10k2_chip = 1,
+	 .ca0108_chip = 1,
+	 .ca_cardbus_chip = 1,
+	 .spk71 = 1 ,
+	 .emu_model = EMU_MODEL_EMU1616},
+	/* Tested by James@superbug.co.uk 4th Nov 2007. */
+	/* This is MAEM8960, 0202 is MAEM 8980 */
+	{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40041102,
+	 .driver = "Audigy2", .name = "E-mu 1010b PCI [MAEM8960]",
+	 .id = "EMU1010",
+	 .emu10k2_chip = 1,
+	 .ca0108_chip = 1,
+	 .spk71 = 1,
+	 .emu_model = EMU_MODEL_EMU1010B}, /* EMU 1010 new revision */
+	/* Tested by James@superbug.co.uk 8th July 2005. */
+	/* This is MAEM8810, 0202 is MAEM8820 */
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x40011102,
+	 .driver = "Audigy2", .name = "E-mu 1010 [MAEM8810]",
+	 .id = "EMU1010",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .spk71 = 1,
+	 .emu_model = EMU_MODEL_EMU1010}, /* EMU 1010 old revision */
+	/* EMU0404b */
+	{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40021102,
+	 .driver = "Audigy2", .name = "E-mu 0404b PCI [MAEM8852]",
+	 .id = "EMU0404",
+	 .emu10k2_chip = 1,
+	 .ca0108_chip = 1,
+	 .spk71 = 1,
+	 .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 new revision */
+	/* Tested by James@superbug.co.uk 20-3-2007. */
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x40021102,
+	 .driver = "Audigy2", .name = "E-mu 0404 [MAEM8850]",
+	 .id = "EMU0404",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .spk71 = 1,
+	 .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 */
+	/* Note that all E-mu cards require kernel 2.6 or newer. */
+	{.vendor = 0x1102, .device = 0x0008,
+	 .driver = "Audigy2", .name = "SB Audigy 2 Value [Unknown]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0108_chip = 1,
+	 .ac97_chip = 1} ,
+	/* Tested by James@superbug.co.uk 3rd July 2005 */
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20071102,
+	 .driver = "Audigy2", .name = "SB Audigy 4 PRO [SB0380]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ca0151_chip = 1,
+	 .spk71 = 1,
+	 .spdif_bug = 1,
+	 .ac97_chip = 1} ,
+	/* Tested by shane-alsa@cm.nu 5th Nov 2005 */
+	/* The 0x20061102 does have SB0350 written on it
+	 * Just like 0x20021102
+	 */
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20061102,
+	 .driver = "Audigy2", .name = "SB Audigy 2 [SB0350b]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ca0151_chip = 1,
+	 .spk71 = 1,
+	 .spdif_bug = 1,
+	 .invert_shared_spdif = 1,	/* digital/analog switch swapped */
+	 .ac97_chip = 1} ,
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20021102,
+	 .driver = "Audigy2", .name = "SB Audigy 2 ZS [SB0350]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ca0151_chip = 1,
+	 .spk71 = 1,
+	 .spdif_bug = 1,
+	 .invert_shared_spdif = 1,	/* digital/analog switch swapped */
+	 .ac97_chip = 1} ,
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x20011102,
+	 .driver = "Audigy2", .name = "SB Audigy 2 ZS [SB0360]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ca0151_chip = 1,
+	 .spk71 = 1,
+	 .spdif_bug = 1,
+	 .invert_shared_spdif = 1,	/* digital/analog switch swapped */
+	 .ac97_chip = 1} ,
+	/* Audigy 2 */
+	/* Tested by James@superbug.co.uk 3rd July 2005 */
+	/* DSP: CA0102-IAT
+	 * DAC: CS4382-KQ
+	 * ADC: Philips 1361T
+	 * AC97: STAC9721
+	 * CA0151: Yes
+	 */
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10071102,
+	 .driver = "Audigy2", .name = "SB Audigy 2 [SB0240]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ca0151_chip = 1,
+	 .spk71 = 1,
+	 .spdif_bug = 1,
+	 .adc_1361t = 1,  /* 24 bit capture instead of 16bit */
+	 .ac97_chip = 1} ,
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10051102,
+	 .driver = "Audigy2", .name = "SB Audigy 2 Platinum EX [SB0280]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ca0151_chip = 1,
+	 .spk71 = 1,
+	 .spdif_bug = 1} ,
+	/* Dell OEM/Creative Labs Audigy 2 ZS */
+	/* See ALSA bug#1365 */
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10031102,
+	 .driver = "Audigy2", .name = "SB Audigy 2 ZS [SB0353]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ca0151_chip = 1,
+	 .spk71 = 1,
+	 .spdif_bug = 1,
+	 .invert_shared_spdif = 1,	/* digital/analog switch swapped */
+	 .ac97_chip = 1} ,
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10021102,
+	 .driver = "Audigy2", .name = "SB Audigy 2 Platinum [SB0240P]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ca0151_chip = 1,
+	 .spk71 = 1,
+	 .spdif_bug = 1,
+	 .invert_shared_spdif = 1,	/* digital/analog switch swapped */
+	 .adc_1361t = 1,  /* 24 bit capture instead of 16bit. Fixes ALSA bug#324 */
+	 .ac97_chip = 1} ,
+	{.vendor = 0x1102, .device = 0x0004, .revision = 0x04,
+	 .driver = "Audigy2", .name = "SB Audigy 2 [Unknown]",
+	 .id = "Audigy2",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ca0151_chip = 1,
+	 .spdif_bug = 1,
+	 .ac97_chip = 1} ,
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x00531102,
+	 .driver = "Audigy", .name = "SB Audigy 1 [SB0092]",
+	 .id = "Audigy",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ac97_chip = 1} ,
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x00521102,
+	 .driver = "Audigy", .name = "SB Audigy 1 ES [SB0160]",
+	 .id = "Audigy",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .spdif_bug = 1,
+	 .ac97_chip = 1} ,
+	{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x00511102,
+	 .driver = "Audigy", .name = "SB Audigy 1 [SB0090]",
+	 .id = "Audigy",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ac97_chip = 1} ,
+	{.vendor = 0x1102, .device = 0x0004,
+	 .driver = "Audigy", .name = "Audigy 1 [Unknown]",
+	 .id = "Audigy",
+	 .emu10k2_chip = 1,
+	 .ca0102_chip = 1,
+	 .ac97_chip = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x100a1102,
+	 .driver = "EMU10K1", .name = "SB Live! 5.1 [SB0220]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x806b1102,
+	 .driver = "EMU10K1", .name = "SB Live! [SB0105]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x806a1102,
+	 .driver = "EMU10K1", .name = "SB Live! Value [SB0103]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80691102,
+	 .driver = "EMU10K1", .name = "SB Live! Value [SB0101]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	/* Tested by ALSA bug#1680 26th December 2005 */
+	/* note: It really has SB0220 written on the card, */
+	/* but it's SB0228 according to kx.inf */
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80661102,
+	 .driver = "EMU10K1", .name = "SB Live! 5.1 Dell OEM [SB0228]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	/* Tested by Thomas Zehetbauer 27th Aug 2005 */
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80651102,
+	 .driver = "EMU10K1", .name = "SB Live! 5.1 [SB0220]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80641102,
+	 .driver = "EMU10K1", .name = "SB Live! 5.1",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	/* Tested by alsa bugtrack user "hus" bug #1297 12th Aug 2005 */
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80611102,
+	 .driver = "EMU10K1", .name = "SB Live! 5.1 [SB0060]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 2, /* ac97 is optional; both SBLive 5.1 and platinum
+			  * share the same IDs!
+			  */
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80511102,
+	 .driver = "EMU10K1", .name = "SB Live! Value [CT4850]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80401102,
+	 .driver = "EMU10K1", .name = "SB Live! Platinum [CT4760P]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80321102,
+	 .driver = "EMU10K1", .name = "SB Live! Value [CT4871]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80311102,
+	 .driver = "EMU10K1", .name = "SB Live! Value [CT4831]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80281102,
+	 .driver = "EMU10K1", .name = "SB Live! Value [CT4870]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	/* Tested by James@superbug.co.uk 3rd July 2005 */
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80271102,
+	 .driver = "EMU10K1", .name = "SB Live! Value [CT4832]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80261102,
+	 .driver = "EMU10K1", .name = "SB Live! Value [CT4830]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80231102,
+	 .driver = "EMU10K1", .name = "SB PCI512 [CT4790]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80221102,
+	 .driver = "EMU10K1", .name = "SB Live! Value [CT4780]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x40011102,
+	 .driver = "EMU10K1", .name = "E-mu APS [PC545]",
+	 .id = "APS",
+	 .emu10k1_chip = 1,
+	 .ecard = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x00211102,
+	 .driver = "EMU10K1", .name = "SB Live! [CT4620]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x00201102,
+	 .driver = "EMU10K1", .name = "SB Live! Value [CT4670]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{.vendor = 0x1102, .device = 0x0002,
+	 .driver = "EMU10K1", .name = "SB Live! [Unknown]",
+	 .id = "Live",
+	 .emu10k1_chip = 1,
+	 .ac97_chip = 1,
+	 .sblive51 = 1} ,
+	{ } /* terminator */
+};
+
+int __devinit snd_emu10k1_create(struct snd_card *card,
+		       struct pci_dev *pci,
+		       unsigned short extin_mask,
+		       unsigned short extout_mask,
+		       long max_cache_bytes,
+		       int enable_ir,
+		       uint subsystem,
+		       struct snd_emu10k1 **remu)
+{
+	struct snd_emu10k1 *emu;
+	int idx, err;
+	int is_audigy;
+	unsigned int silent_page;
+	const struct snd_emu_chip_details *c;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_emu10k1_dev_free,
+	};
+
+	*remu = NULL;
+
+	/* enable PCI device */
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+
+	emu = kzalloc(sizeof(*emu), GFP_KERNEL);
+	if (emu == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	emu->card = card;
+	spin_lock_init(&emu->reg_lock);
+	spin_lock_init(&emu->emu_lock);
+	spin_lock_init(&emu->spi_lock);
+	spin_lock_init(&emu->i2c_lock);
+	spin_lock_init(&emu->voice_lock);
+	spin_lock_init(&emu->synth_lock);
+	spin_lock_init(&emu->memblk_lock);
+	mutex_init(&emu->fx8010.lock);
+	INIT_LIST_HEAD(&emu->mapped_link_head);
+	INIT_LIST_HEAD(&emu->mapped_order_link_head);
+	emu->pci = pci;
+	emu->irq = -1;
+	emu->synth = NULL;
+	emu->get_synth_voice = NULL;
+	/* read revision & serial */
+	emu->revision = pci->revision;
+	pci_read_config_dword(pci, PCI_SUBSYSTEM_VENDOR_ID, &emu->serial);
+	pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &emu->model);
+	snd_printdd("vendor = 0x%x, device = 0x%x, subsystem_vendor_id = 0x%x, subsystem_id = 0x%x\n", pci->vendor, pci->device, emu->serial, emu->model);
+
+	for (c = emu_chip_details; c->vendor; c++) {
+		if (c->vendor == pci->vendor && c->device == pci->device) {
+			if (subsystem) {
+				if (c->subsystem && (c->subsystem == subsystem))
+					break;
+				else
+					continue;
+			} else {
+				if (c->subsystem && (c->subsystem != emu->serial))
+					continue;
+				if (c->revision && c->revision != emu->revision)
+					continue;
+			}
+			break;
+		}
+	}
+	if (c->vendor == 0) {
+		snd_printk(KERN_ERR "emu10k1: Card not recognised\n");
+		kfree(emu);
+		pci_disable_device(pci);
+		return -ENOENT;
+	}
+	emu->card_capabilities = c;
+	if (c->subsystem && !subsystem)
+		snd_printdd("Sound card name = %s\n", c->name);
+	else if (subsystem)
+		snd_printdd("Sound card name = %s, "
+			"vendor = 0x%x, device = 0x%x, subsystem = 0x%x. "
+			"Forced to subsystem = 0x%x\n",	c->name,
+			pci->vendor, pci->device, emu->serial, c->subsystem);
+	else
+		snd_printdd("Sound card name = %s, "
+			"vendor = 0x%x, device = 0x%x, subsystem = 0x%x.\n",
+			c->name, pci->vendor, pci->device,
+			emu->serial);
+
+	if (!*card->id && c->id) {
+		int i, n = 0;
+		strlcpy(card->id, c->id, sizeof(card->id));
+		for (;;) {
+			for (i = 0; i < snd_ecards_limit; i++) {
+				if (snd_cards[i] && !strcmp(snd_cards[i]->id, card->id))
+					break;
+			}
+			if (i >= snd_ecards_limit)
+				break;
+			n++;
+			if (n >= SNDRV_CARDS)
+				break;
+			snprintf(card->id, sizeof(card->id), "%s_%d", c->id, n);
+		}
+	}
+
+	is_audigy = emu->audigy = c->emu10k2_chip;
+
+	/* set the DMA transfer mask */
+	emu->dma_mask = is_audigy ? AUDIGY_DMA_MASK : EMU10K1_DMA_MASK;
+	if (pci_set_dma_mask(pci, emu->dma_mask) < 0 ||
+	    pci_set_consistent_dma_mask(pci, emu->dma_mask) < 0) {
+		snd_printk(KERN_ERR "architecture does not support PCI busmaster DMA with mask 0x%lx\n", emu->dma_mask);
+		kfree(emu);
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+	if (is_audigy)
+		emu->gpr_base = A_FXGPREGBASE;
+	else
+		emu->gpr_base = FXGPREGBASE;
+
+	err = pci_request_regions(pci, "EMU10K1");
+	if (err < 0) {
+		kfree(emu);
+		pci_disable_device(pci);
+		return err;
+	}
+	emu->port = pci_resource_start(pci, 0);
+
+	emu->max_cache_pages = max_cache_bytes >> PAGE_SHIFT;
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				32 * 1024, &emu->ptb_pages) < 0) {
+		err = -ENOMEM;
+		goto error;
+	}
+
+	emu->page_ptr_table = vmalloc(emu->max_cache_pages * sizeof(void *));
+	emu->page_addr_table = vmalloc(emu->max_cache_pages *
+				       sizeof(unsigned long));
+	if (emu->page_ptr_table == NULL || emu->page_addr_table == NULL) {
+		err = -ENOMEM;
+		goto error;
+	}
+
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				EMUPAGESIZE, &emu->silent_page) < 0) {
+		err = -ENOMEM;
+		goto error;
+	}
+	emu->memhdr = snd_util_memhdr_new(emu->max_cache_pages * PAGE_SIZE);
+	if (emu->memhdr == NULL) {
+		err = -ENOMEM;
+		goto error;
+	}
+	emu->memhdr->block_extra_size = sizeof(struct snd_emu10k1_memblk) -
+		sizeof(struct snd_util_memblk);
+
+	pci_set_master(pci);
+
+	emu->fx8010.fxbus_mask = 0x303f;
+	if (extin_mask == 0)
+		extin_mask = 0x3fcf;
+	if (extout_mask == 0)
+		extout_mask = 0x7fff;
+	emu->fx8010.extin_mask = extin_mask;
+	emu->fx8010.extout_mask = extout_mask;
+	emu->enable_ir = enable_ir;
+
+	if (emu->card_capabilities->ca_cardbus_chip) {
+		err = snd_emu10k1_cardbus_init(emu);
+		if (err < 0)
+			goto error;
+	}
+	if (emu->card_capabilities->ecard) {
+		err = snd_emu10k1_ecard_init(emu);
+		if (err < 0)
+			goto error;
+	} else if (emu->card_capabilities->emu_model) {
+		err = snd_emu10k1_emu1010_init(emu);
+		if (err < 0) {
+			snd_emu10k1_free(emu);
+			return err;
+		}
+	} else {
+		/* 5.1: Enable the additional AC97 Slots. If the emu10k1 version
+			does not support this, it shouldn't do any harm */
+		snd_emu10k1_ptr_write(emu, AC97SLOT, 0,
+					AC97SLOT_CNTR|AC97SLOT_LFE);
+	}
+
+	/* initialize TRAM setup */
+	emu->fx8010.itram_size = (16 * 1024)/2;
+	emu->fx8010.etram_pages.area = NULL;
+	emu->fx8010.etram_pages.bytes = 0;
+
+	/* irq handler must be registered after I/O ports are activated */
+	if (request_irq(pci->irq, snd_emu10k1_interrupt, IRQF_SHARED,
+			"EMU10K1", emu)) {
+		err = -EBUSY;
+		goto error;
+	}
+	emu->irq = pci->irq;
+
+	/*
+	 *  Init to 0x02109204 :
+	 *  Clock accuracy    = 0     (1000ppm)
+	 *  Sample Rate       = 2     (48kHz)
+	 *  Audio Channel     = 1     (Left of 2)
+	 *  Source Number     = 0     (Unspecified)
+	 *  Generation Status = 1     (Original for Cat Code 12)
+	 *  Cat Code          = 12    (Digital Signal Mixer)
+	 *  Mode              = 0     (Mode 0)
+	 *  Emphasis          = 0     (None)
+	 *  CP                = 1     (Copyright unasserted)
+	 *  AN                = 0     (Audio data)
+	 *  P                 = 0     (Consumer)
+	 */
+	emu->spdif_bits[0] = emu->spdif_bits[1] =
+		emu->spdif_bits[2] = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+		SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+		SPCS_GENERATIONSTATUS | 0x00001200 |
+		0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT;
+
+	emu->reserved_page = (struct snd_emu10k1_memblk *)
+		snd_emu10k1_synth_alloc(emu, 4096);
+	if (emu->reserved_page)
+		emu->reserved_page->map_locked = 1;
+
+	/* Clear silent pages and set up pointers */
+	memset(emu->silent_page.area, 0, PAGE_SIZE);
+	silent_page = emu->silent_page.addr << 1;
+	for (idx = 0; idx < MAXPAGES; idx++)
+		((u32 *)emu->ptb_pages.area)[idx] = cpu_to_le32(silent_page | idx);
+
+	/* set up voice indices */
+	for (idx = 0; idx < NUM_G; idx++) {
+		emu->voices[idx].emu = emu;
+		emu->voices[idx].number = idx;
+	}
+
+	err = snd_emu10k1_init(emu, enable_ir, 0);
+	if (err < 0)
+		goto error;
+#ifdef CONFIG_PM
+	err = alloc_pm_buffer(emu);
+	if (err < 0)
+		goto error;
+#endif
+
+	/*  Initialize the effect engine */
+	err = snd_emu10k1_init_efx(emu);
+	if (err < 0)
+		goto error;
+	snd_emu10k1_audio_enable(emu);
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, emu, &ops);
+	if (err < 0)
+		goto error;
+
+#ifdef CONFIG_PROC_FS
+	snd_emu10k1_proc_init(emu);
+#endif
+
+	snd_card_set_dev(card, &pci->dev);
+	*remu = emu;
+	return 0;
+
+ error:
+	snd_emu10k1_free(emu);
+	return err;
+}
+
+#ifdef CONFIG_PM
+static unsigned char saved_regs[] = {
+	CPF, PTRX, CVCF, VTFT, Z1, Z2, PSST, DSL, CCCA, CCR, CLP,
+	FXRT, MAPA, MAPB, ENVVOL, ATKHLDV, DCYSUSV, LFOVAL1, ENVVAL,
+	ATKHLDM, DCYSUSM, LFOVAL2, IP, IFATN, PEFE, FMMOD, TREMFRQ, FM2FRQ2,
+	TEMPENV, ADCCR, FXWC, MICBA, ADCBA, FXBA,
+	MICBS, ADCBS, FXBS, CDCS, GPSCS, SPCS0, SPCS1, SPCS2,
+	SPBYPASS, AC97SLOT, CDSRCS, GPSRCS, ZVSRCS, MICIDX, ADCIDX, FXIDX,
+	0xff /* end */
+};
+static unsigned char saved_regs_audigy[] = {
+	A_ADCIDX, A_MICIDX, A_FXWC1, A_FXWC2, A_SAMPLE_RATE,
+	A_FXRT2, A_SENDAMOUNTS, A_FXRT1,
+	0xff /* end */
+};
+
+static int __devinit alloc_pm_buffer(struct snd_emu10k1 *emu)
+{
+	int size;
+
+	size = ARRAY_SIZE(saved_regs);
+	if (emu->audigy)
+		size += ARRAY_SIZE(saved_regs_audigy);
+	emu->saved_ptr = vmalloc(4 * NUM_G * size);
+	if (!emu->saved_ptr)
+		return -ENOMEM;
+	if (snd_emu10k1_efx_alloc_pm_buffer(emu) < 0)
+		return -ENOMEM;
+	if (emu->card_capabilities->ca0151_chip &&
+	    snd_p16v_alloc_pm_buffer(emu) < 0)
+		return -ENOMEM;
+	return 0;
+}
+
+static void free_pm_buffer(struct snd_emu10k1 *emu)
+{
+	vfree(emu->saved_ptr);
+	snd_emu10k1_efx_free_pm_buffer(emu);
+	if (emu->card_capabilities->ca0151_chip)
+		snd_p16v_free_pm_buffer(emu);
+}
+
+void snd_emu10k1_suspend_regs(struct snd_emu10k1 *emu)
+{
+	int i;
+	unsigned char *reg;
+	unsigned int *val;
+
+	val = emu->saved_ptr;
+	for (reg = saved_regs; *reg != 0xff; reg++)
+		for (i = 0; i < NUM_G; i++, val++)
+			*val = snd_emu10k1_ptr_read(emu, *reg, i);
+	if (emu->audigy) {
+		for (reg = saved_regs_audigy; *reg != 0xff; reg++)
+			for (i = 0; i < NUM_G; i++, val++)
+				*val = snd_emu10k1_ptr_read(emu, *reg, i);
+	}
+	if (emu->audigy)
+		emu->saved_a_iocfg = inl(emu->port + A_IOCFG);
+	emu->saved_hcfg = inl(emu->port + HCFG);
+}
+
+void snd_emu10k1_resume_init(struct snd_emu10k1 *emu)
+{
+	if (emu->card_capabilities->ca_cardbus_chip)
+		snd_emu10k1_cardbus_init(emu);
+	if (emu->card_capabilities->ecard)
+		snd_emu10k1_ecard_init(emu);
+	else if (emu->card_capabilities->emu_model)
+		snd_emu10k1_emu1010_init(emu);
+	else
+		snd_emu10k1_ptr_write(emu, AC97SLOT, 0, AC97SLOT_CNTR|AC97SLOT_LFE);
+	snd_emu10k1_init(emu, emu->enable_ir, 1);
+}
+
+void snd_emu10k1_resume_regs(struct snd_emu10k1 *emu)
+{
+	int i;
+	unsigned char *reg;
+	unsigned int *val;
+
+	snd_emu10k1_audio_enable(emu);
+
+	/* resore for spdif */
+	if (emu->audigy)
+		outl(emu->saved_a_iocfg, emu->port + A_IOCFG);
+	outl(emu->saved_hcfg, emu->port + HCFG);
+
+	val = emu->saved_ptr;
+	for (reg = saved_regs; *reg != 0xff; reg++)
+		for (i = 0; i < NUM_G; i++, val++)
+			snd_emu10k1_ptr_write(emu, *reg, i, *val);
+	if (emu->audigy) {
+		for (reg = saved_regs_audigy; *reg != 0xff; reg++)
+			for (i = 0; i < NUM_G; i++, val++)
+				snd_emu10k1_ptr_write(emu, *reg, i, *val);
+	}
+}
+#endif
diff --git a/linux/sound/pci/emu10k1/emu10k1_patch.c b/linux/sound/pci/emu10k1/emu10k1_patch.c
new file mode 100644
index 0000000..e10f027
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emu10k1_patch.c
@@ -0,0 +1,229 @@
+/*
+ *  Patch transfer callback for Emu10k1
+ *
+ *  Copyright (C) 2000 Takashi iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+/*
+ * All the code for loading in a patch.  There is very little that is
+ * chip specific here.  Just the actual writing to the board.
+ */
+
+#include "emu10k1_synth_local.h"
+
+/*
+ */
+#define BLANK_LOOP_START	4
+#define BLANK_LOOP_END		8
+#define BLANK_LOOP_SIZE		12
+#define BLANK_HEAD_SIZE		32
+
+/*
+ * allocate a sample block and copy data from userspace
+ */
+int
+snd_emu10k1_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp,
+		       struct snd_util_memhdr *hdr,
+		       const void __user *data, long count)
+{
+	int offset;
+	int truesize, size, loopsize, blocksize;
+	int loopend, sampleend;
+	unsigned int start_addr;
+	struct snd_emu10k1 *emu;
+
+	emu = rec->hw;
+	if (snd_BUG_ON(!sp || !hdr))
+		return -EINVAL;
+
+	if (sp->v.size == 0) {
+		snd_printd("emu: rom font for sample %d\n", sp->v.sample);
+		return 0;
+	}
+
+	/* recalculate address offset */
+	sp->v.end -= sp->v.start;
+	sp->v.loopstart -= sp->v.start;
+	sp->v.loopend -= sp->v.start;
+	sp->v.start = 0;
+
+	/* some samples have invalid data.  the addresses are corrected in voice info */
+	sampleend = sp->v.end;
+	if (sampleend > sp->v.size)
+		sampleend = sp->v.size;
+	loopend = sp->v.loopend;
+	if (loopend > sampleend)
+		loopend = sampleend;
+
+	/* be sure loop points start < end */
+	if (sp->v.loopstart >= sp->v.loopend) {
+		int tmp = sp->v.loopstart;
+		sp->v.loopstart = sp->v.loopend;
+		sp->v.loopend = tmp;
+	}
+
+	/* compute true data size to be loaded */
+	truesize = sp->v.size + BLANK_HEAD_SIZE;
+	loopsize = 0;
+#if 0 /* not supported */
+	if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
+		loopsize = sp->v.loopend - sp->v.loopstart;
+	truesize += loopsize;
+#endif
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
+		truesize += BLANK_LOOP_SIZE;
+
+	/* try to allocate a memory block */
+	blocksize = truesize;
+	if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+		blocksize *= 2;
+	sp->block = snd_emu10k1_synth_alloc(emu, blocksize);
+	if (sp->block == NULL) {
+		snd_printd("emu10k1: synth malloc failed (size=%d)\n", blocksize);
+		/* not ENOMEM (for compatibility with OSS) */
+		return -ENOSPC;
+	}
+	/* set the total size */
+	sp->v.truesize = blocksize;
+
+	/* write blank samples at head */
+	offset = 0;
+	size = BLANK_HEAD_SIZE;
+	if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+		size *= 2;
+	if (offset + size > blocksize)
+		return -EINVAL;
+	snd_emu10k1_synth_bzero(emu, sp->block, offset, size);
+	offset += size;
+
+	/* copy start->loopend */
+	size = loopend;
+	if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+		size *= 2;
+	if (offset + size > blocksize)
+		return -EINVAL;
+	if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size)) {
+		snd_emu10k1_synth_free(emu, sp->block);
+		sp->block = NULL;
+		return -EFAULT;
+	}
+	offset += size;
+	data += size;
+
+#if 0 /* not suppported yet */
+	/* handle reverse (or bidirectional) loop */
+	if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)) {
+		/* copy loop in reverse */
+		if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS)) {
+			int woffset;
+			unsigned short *wblock = (unsigned short*)block;
+			woffset = offset / 2;
+			if (offset + loopsize * 2 > blocksize)
+				return -EINVAL;
+			for (i = 0; i < loopsize; i++)
+				wblock[woffset + i] = wblock[woffset - i -1];
+			offset += loopsize * 2;
+		} else {
+			if (offset + loopsize > blocksize)
+				return -EINVAL;
+			for (i = 0; i < loopsize; i++)
+				block[offset + i] = block[offset - i -1];
+			offset += loopsize;
+		}
+
+		/* modify loop pointers */
+		if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
+			sp->v.loopend += loopsize;
+		} else {
+			sp->v.loopstart += loopsize;
+			sp->v.loopend += loopsize;
+		}
+		/* add sample pointer */
+		sp->v.end += loopsize;
+	}
+#endif
+
+	/* loopend -> sample end */
+	size = sp->v.size - loopend;
+	if (size < 0)
+		return -EINVAL;
+	if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+		size *= 2;
+	if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size)) {
+		snd_emu10k1_synth_free(emu, sp->block);
+		sp->block = NULL;
+		return -EFAULT;
+	}
+	offset += size;
+
+	/* clear rest of samples (if any) */
+	if (offset < blocksize)
+		snd_emu10k1_synth_bzero(emu, sp->block, offset, blocksize - offset);
+
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
+		/* if no blank loop is attached in the sample, add it */
+		if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) {
+			sp->v.loopstart = sp->v.end + BLANK_LOOP_START;
+			sp->v.loopend = sp->v.end + BLANK_LOOP_END;
+		}
+	}
+
+#if 0 /* not supported yet */
+	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_UNSIGNED) {
+		/* unsigned -> signed */
+		if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS)) {
+			unsigned short *wblock = (unsigned short*)block;
+			for (i = 0; i < truesize; i++)
+				wblock[i] ^= 0x8000;
+		} else {
+			for (i = 0; i < truesize; i++)
+				block[i] ^= 0x80;
+		}
+	}
+#endif
+
+	/* recalculate offset */
+	start_addr = BLANK_HEAD_SIZE * 2;
+	if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
+		start_addr >>= 1;
+	sp->v.start += start_addr;
+	sp->v.end += start_addr;
+	sp->v.loopstart += start_addr;
+	sp->v.loopend += start_addr;
+
+	return 0;
+}
+
+/*
+ * free a sample block
+ */
+int
+snd_emu10k1_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
+			struct snd_util_memhdr *hdr)
+{
+	struct snd_emu10k1 *emu;
+
+	emu = rec->hw;
+	if (snd_BUG_ON(!sp || !hdr))
+		return -EINVAL;
+
+	if (sp->block) {
+		snd_emu10k1_synth_free(emu, sp->block);
+		sp->block = NULL;
+	}
+	return 0;
+}
+
diff --git a/linux/sound/pci/emu10k1/emu10k1_synth.c b/linux/sound/pci/emu10k1/emu10k1_synth.c
new file mode 100644
index 0000000..ad7b714
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emu10k1_synth.c
@@ -0,0 +1,123 @@
+/*
+ *  Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Routines for control of EMU10K1 WaveTable synth
+ *
+ *   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 "emu10k1_synth_local.h"
+#include <linux/init.h>
+
+MODULE_AUTHOR("Takashi Iwai");
+MODULE_DESCRIPTION("Routines for control of EMU10K1 WaveTable synth");
+MODULE_LICENSE("GPL");
+
+/*
+ * create a new hardware dependent device for Emu10k1
+ */
+static int snd_emu10k1_synth_new_device(struct snd_seq_device *dev)
+{
+	struct snd_emux *emux;
+	struct snd_emu10k1 *hw;
+	struct snd_emu10k1_synth_arg *arg;
+	unsigned long flags;
+
+	arg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	if (arg == NULL)
+		return -EINVAL;
+
+	if (arg->seq_ports <= 0)
+		return 0; /* nothing */
+	if (arg->max_voices < 1)
+		arg->max_voices = 1;
+	else if (arg->max_voices > 64)
+		arg->max_voices = 64;
+
+	if (snd_emux_new(&emux) < 0)
+		return -ENOMEM;
+
+	snd_emu10k1_ops_setup(emux);
+	hw = arg->hwptr;
+	emux->hw = hw;
+	emux->max_voices = arg->max_voices;
+	emux->num_ports = arg->seq_ports;
+	emux->pitch_shift = -501;
+	emux->memhdr = hw->memhdr;
+	/* maximum two ports */
+	emux->midi_ports = arg->seq_ports < 2 ? arg->seq_ports : 2;
+	/* audigy has two external midis */
+	emux->midi_devidx = hw->audigy ? 2 : 1;
+	emux->linear_panning = 0;
+	emux->hwdep_idx = 2; /* FIXED */
+
+	if (snd_emux_register(emux, dev->card, arg->index, "Emu10k1") < 0) {
+		snd_emux_free(emux);
+		return -ENOMEM;
+	}
+
+	spin_lock_irqsave(&hw->voice_lock, flags);
+	hw->synth = emux;
+	hw->get_synth_voice = snd_emu10k1_synth_get_voice;
+	spin_unlock_irqrestore(&hw->voice_lock, flags);
+
+	dev->driver_data = emux;
+
+	return 0;
+}
+
+static int snd_emu10k1_synth_delete_device(struct snd_seq_device *dev)
+{
+	struct snd_emux *emux;
+	struct snd_emu10k1 *hw;
+	unsigned long flags;
+
+	if (dev->driver_data == NULL)
+		return 0; /* not registered actually */
+
+	emux = dev->driver_data;
+
+	hw = emux->hw;
+	spin_lock_irqsave(&hw->voice_lock, flags);
+	hw->synth = NULL;
+	hw->get_synth_voice = NULL;
+	spin_unlock_irqrestore(&hw->voice_lock, flags);
+
+	snd_emux_free(emux);
+	return 0;
+}
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_emu10k1_synth_init(void)
+{
+	
+	static struct snd_seq_dev_ops ops = {
+		snd_emu10k1_synth_new_device,
+		snd_emu10k1_synth_delete_device,
+	};
+	return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH, &ops,
+					      sizeof(struct snd_emu10k1_synth_arg));
+}
+
+static void __exit alsa_emu10k1_synth_exit(void)
+{
+	snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH);
+}
+
+module_init(alsa_emu10k1_synth_init)
+module_exit(alsa_emu10k1_synth_exit)
diff --git a/linux/sound/pci/emu10k1/emu10k1_synth_local.h b/linux/sound/pci/emu10k1/emu10k1_synth_local.h
new file mode 100644
index 0000000..25f328f
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emu10k1_synth_local.h
@@ -0,0 +1,42 @@
+#ifndef __EMU10K1_SYNTH_LOCAL_H
+#define __EMU10K1_SYNTH_LOCAL_H
+/*
+ *  Local defininitons for Emu10k1 wavetable
+ *
+ *  Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1_synth.h>
+
+/* emu10k1_patch.c */
+int snd_emu10k1_sample_new(struct snd_emux *private_data,
+			   struct snd_sf_sample *sp,
+			   struct snd_util_memhdr *hdr,
+			   const void __user *_data, long count);
+int snd_emu10k1_sample_free(struct snd_emux *private_data,
+			    struct snd_sf_sample *sp,
+			    struct snd_util_memhdr *hdr);
+int snd_emu10k1_memhdr_init(struct snd_emux *emu);
+
+/* emu10k1_callback.c */
+void snd_emu10k1_ops_setup(struct snd_emux *emu);
+int snd_emu10k1_synth_get_voice(struct snd_emu10k1 *hw);
+
+
+#endif	/* __EMU10K1_SYNTH_LOCAL_H */
diff --git a/linux/sound/pci/emu10k1/emu10k1x.c b/linux/sound/pci/emu10k1/emu10k1x.c
new file mode 100644
index 0000000..df47f73
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emu10k1x.c
@@ -0,0 +1,1635 @@
+/*
+ *  Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ *  Driver EMU10K1X chips
+ *
+ *  Parts of this code were adapted from audigyls.c driver which is
+ *  Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ *
+ *  BUGS:
+ *    --
+ *
+ *  TODO:
+ *
+ *  Chips (SB0200 model):
+ *    - EMU10K1X-DBQ
+ *    - STAC 9708T
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/rawmidi.h>
+
+MODULE_AUTHOR("Francisco Moraes <fmoraes@nc.rr.com>");
+MODULE_DESCRIPTION("EMU10K1X");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Dell Creative Labs,SB Live!}");
+
+// module parameters (see "Module Parameters")
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the EMU10K1X soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the EMU10K1X soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable the EMU10K1X soundcard.");
+
+
+// some definitions were borrowed from emu10k1 driver as they seem to be the same
+/************************************************************************************************/
+/* PCI function 0 registers, address = <val> + PCIBASE0						*/
+/************************************************************************************************/
+
+#define PTR			0x00		/* Indexed register set pointer register	*/
+						/* NOTE: The CHANNELNUM and ADDRESS words can	*/
+						/* be modified independently of each other.	*/
+
+#define DATA			0x04		/* Indexed register set data register		*/
+
+#define IPR			0x08		/* Global interrupt pending register		*/
+						/* Clear pending interrupts by writing a 1 to	*/
+						/* the relevant bits and zero to the other bits	*/
+#define IPR_MIDITRANSBUFEMPTY   0x00000001	/* MIDI UART transmit buffer empty		*/
+#define IPR_MIDIRECVBUFEMPTY    0x00000002	/* MIDI UART receive buffer empty		*/
+#define IPR_CH_0_LOOP           0x00000800      /* Channel 0 loop                               */
+#define IPR_CH_0_HALF_LOOP      0x00000100      /* Channel 0 half loop                          */
+#define IPR_CAP_0_LOOP          0x00080000      /* Channel capture loop                         */
+#define IPR_CAP_0_HALF_LOOP     0x00010000      /* Channel capture half loop                    */
+
+#define INTE			0x0c		/* Interrupt enable register			*/
+#define INTE_MIDITXENABLE       0x00000001	/* Enable MIDI transmit-buffer-empty interrupts	*/
+#define INTE_MIDIRXENABLE       0x00000002	/* Enable MIDI receive-buffer-empty interrupts	*/
+#define INTE_CH_0_LOOP          0x00000800      /* Channel 0 loop                               */
+#define INTE_CH_0_HALF_LOOP     0x00000100      /* Channel 0 half loop                          */
+#define INTE_CAP_0_LOOP         0x00080000      /* Channel capture loop                         */
+#define INTE_CAP_0_HALF_LOOP    0x00010000      /* Channel capture half loop                    */
+
+#define HCFG			0x14		/* Hardware config register			*/
+
+#define HCFG_LOCKSOUNDCACHE	0x00000008	/* 1 = Cancel bustmaster accesses to soundcache */
+						/* NOTE: This should generally never be used.  	*/
+#define HCFG_AUDIOENABLE	0x00000001	/* 0 = CODECs transmit zero-valued samples	*/
+						/* Should be set to 1 when the EMU10K1 is	*/
+						/* completely initialized.			*/
+#define GPIO			0x18		/* Defaults: 00001080-Analog, 00001000-SPDIF.   */
+
+
+#define AC97DATA		0x1c		/* AC97 register set data register (16 bit)	*/
+
+#define AC97ADDRESS		0x1e		/* AC97 register set address register (8 bit)	*/
+
+/********************************************************************************************************/
+/* Emu10k1x pointer-offset register set, accessed through the PTR and DATA registers			*/
+/********************************************************************************************************/
+#define PLAYBACK_LIST_ADDR	0x00		/* Base DMA address of a list of pointers to each period/size */
+						/* One list entry: 4 bytes for DMA address, 
+						 * 4 bytes for period_size << 16.
+						 * One list entry is 8 bytes long.
+						 * One list entry for each period in the buffer.
+						 */
+#define PLAYBACK_LIST_SIZE	0x01		/* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000  */
+#define PLAYBACK_LIST_PTR	0x02		/* Pointer to the current period being played */
+#define PLAYBACK_DMA_ADDR	0x04		/* Playback DMA addresss */
+#define PLAYBACK_PERIOD_SIZE	0x05		/* Playback period size */
+#define PLAYBACK_POINTER	0x06		/* Playback period pointer. Sample currently in DAC */
+#define PLAYBACK_UNKNOWN1       0x07
+#define PLAYBACK_UNKNOWN2       0x08
+
+/* Only one capture channel supported */
+#define CAPTURE_DMA_ADDR	0x10		/* Capture DMA address */
+#define CAPTURE_BUFFER_SIZE	0x11		/* Capture buffer size */
+#define CAPTURE_POINTER		0x12		/* Capture buffer pointer. Sample currently in ADC */
+#define CAPTURE_UNKNOWN         0x13
+
+/* From 0x20 - 0x3f, last samples played on each channel */
+
+#define TRIGGER_CHANNEL         0x40            /* Trigger channel playback                     */
+#define TRIGGER_CHANNEL_0       0x00000001      /* Trigger channel 0                            */
+#define TRIGGER_CHANNEL_1       0x00000002      /* Trigger channel 1                            */
+#define TRIGGER_CHANNEL_2       0x00000004      /* Trigger channel 2                            */
+#define TRIGGER_CAPTURE         0x00000100      /* Trigger capture channel                      */
+
+#define ROUTING                 0x41            /* Setup sound routing ?                        */
+#define ROUTING_FRONT_LEFT      0x00000001
+#define ROUTING_FRONT_RIGHT     0x00000002
+#define ROUTING_REAR_LEFT       0x00000004
+#define ROUTING_REAR_RIGHT      0x00000008
+#define ROUTING_CENTER_LFE      0x00010000
+
+#define SPCS0			0x42		/* SPDIF output Channel Status 0 register	*/
+
+#define SPCS1			0x43		/* SPDIF output Channel Status 1 register	*/
+
+#define SPCS2			0x44		/* SPDIF output Channel Status 2 register	*/
+
+#define SPCS_CLKACCYMASK	0x30000000	/* Clock accuracy				*/
+#define SPCS_CLKACCY_1000PPM	0x00000000	/* 1000 parts per million			*/
+#define SPCS_CLKACCY_50PPM	0x10000000	/* 50 parts per million				*/
+#define SPCS_CLKACCY_VARIABLE	0x20000000	/* Variable accuracy				*/
+#define SPCS_SAMPLERATEMASK	0x0f000000	/* Sample rate					*/
+#define SPCS_SAMPLERATE_44	0x00000000	/* 44.1kHz sample rate				*/
+#define SPCS_SAMPLERATE_48	0x02000000	/* 48kHz sample rate				*/
+#define SPCS_SAMPLERATE_32	0x03000000	/* 32kHz sample rate				*/
+#define SPCS_CHANNELNUMMASK	0x00f00000	/* Channel number				*/
+#define SPCS_CHANNELNUM_UNSPEC	0x00000000	/* Unspecified channel number			*/
+#define SPCS_CHANNELNUM_LEFT	0x00100000	/* Left channel					*/
+#define SPCS_CHANNELNUM_RIGHT	0x00200000	/* Right channel				*/
+#define SPCS_SOURCENUMMASK	0x000f0000	/* Source number				*/
+#define SPCS_SOURCENUM_UNSPEC	0x00000000	/* Unspecified source number			*/
+#define SPCS_GENERATIONSTATUS	0x00008000	/* Originality flag (see IEC-958 spec)		*/
+#define SPCS_CATEGORYCODEMASK	0x00007f00	/* Category code (see IEC-958 spec)		*/
+#define SPCS_MODEMASK		0x000000c0	/* Mode (see IEC-958 spec)			*/
+#define SPCS_EMPHASISMASK	0x00000038	/* Emphasis					*/
+#define SPCS_EMPHASIS_NONE	0x00000000	/* No emphasis					*/
+#define SPCS_EMPHASIS_50_15	0x00000008	/* 50/15 usec 2 channel				*/
+#define SPCS_COPYRIGHT		0x00000004	/* Copyright asserted flag -- do not modify	*/
+#define SPCS_NOTAUDIODATA	0x00000002	/* 0 = Digital audio, 1 = not audio		*/
+#define SPCS_PROFESSIONAL	0x00000001	/* 0 = Consumer (IEC-958), 1 = pro (AES3-1992)	*/
+
+#define SPDIF_SELECT		0x45		/* Enables SPDIF or Analogue outputs 0-Analogue, 0x700-SPDIF */
+
+/* This is the MPU port on the card                      					*/
+#define MUDATA		0x47
+#define MUCMD		0x48
+#define MUSTAT		MUCMD
+
+/* From 0x50 - 0x5f, last samples captured */
+
+/**
+ * The hardware has 3 channels for playback and 1 for capture.
+ *  - channel 0 is the front channel
+ *  - channel 1 is the rear channel
+ *  - channel 2 is the center/lfe channel
+ * Volume is controlled by the AC97 for the front and rear channels by
+ * the PCM Playback Volume, Sigmatel Surround Playback Volume and 
+ * Surround Playback Volume. The Sigmatel 4-Speaker Stereo switch affects
+ * the front/rear channel mixing in the REAR OUT jack. When using the
+ * 4-Speaker Stereo, both front and rear channels will be mixed in the
+ * REAR OUT.
+ * The center/lfe channel has no volume control and cannot be muted during
+ * playback.
+ */
+
+struct emu10k1x_voice {
+	struct emu10k1x *emu;
+	int number;
+	int use;
+  
+	struct emu10k1x_pcm *epcm;
+};
+
+struct emu10k1x_pcm {
+	struct emu10k1x *emu;
+	struct snd_pcm_substream *substream;
+	struct emu10k1x_voice *voice;
+	unsigned short running;
+};
+
+struct emu10k1x_midi {
+	struct emu10k1x *emu;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *substream_input;
+	struct snd_rawmidi_substream *substream_output;
+	unsigned int midi_mode;
+	spinlock_t input_lock;
+	spinlock_t output_lock;
+	spinlock_t open_lock;
+	int tx_enable, rx_enable;
+	int port;
+	int ipr_tx, ipr_rx;
+	void (*interrupt)(struct emu10k1x *emu, unsigned int status);
+};
+
+// definition of the chip-specific record
+struct emu10k1x {
+	struct snd_card *card;
+	struct pci_dev *pci;
+
+	unsigned long port;
+	struct resource *res_port;
+	int irq;
+
+	unsigned char revision;		/* chip revision */
+	unsigned int serial;            /* serial number */
+	unsigned short model;		/* subsystem id */
+
+	spinlock_t emu_lock;
+	spinlock_t voice_lock;
+
+	struct snd_ac97 *ac97;
+	struct snd_pcm *pcm;
+
+	struct emu10k1x_voice voices[3];
+	struct emu10k1x_voice capture_voice;
+	u32 spdif_bits[3]; // SPDIF out setup
+
+	struct snd_dma_buffer dma_buffer;
+
+	struct emu10k1x_midi midi;
+};
+
+/* hardware definition */
+static struct snd_pcm_hardware snd_emu10k1x_playback_hw = {
+	.info =			(SNDRV_PCM_INFO_MMAP | 
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(32*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(16*1024),
+	.periods_min =		2,
+	.periods_max =		8,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_emu10k1x_capture_hw = {
+	.info =			(SNDRV_PCM_INFO_MMAP | 
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(32*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(16*1024),
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0,
+};
+
+static unsigned int snd_emu10k1x_ptr_read(struct emu10k1x * emu, 
+					  unsigned int reg, 
+					  unsigned int chn)
+{
+	unsigned long flags;
+	unsigned int regptr, val;
+  
+	regptr = (reg << 16) | chn;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(regptr, emu->port + PTR);
+	val = inl(emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+	return val;
+}
+
+static void snd_emu10k1x_ptr_write(struct emu10k1x *emu, 
+				   unsigned int reg, 
+				   unsigned int chn, 
+				   unsigned int data)
+{
+	unsigned int regptr;
+	unsigned long flags;
+
+	regptr = (reg << 16) | chn;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(regptr, emu->port + PTR);
+	outl(data, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_emu10k1x_intr_enable(struct emu10k1x *emu, unsigned int intrenb)
+{
+	unsigned long flags;
+	unsigned int intr_enable;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	intr_enable = inl(emu->port + INTE) | intrenb;
+	outl(intr_enable, emu->port + INTE);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_emu10k1x_intr_disable(struct emu10k1x *emu, unsigned int intrenb)
+{
+	unsigned long flags;
+	unsigned int intr_enable;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	intr_enable = inl(emu->port + INTE) & ~intrenb;
+	outl(intr_enable, emu->port + INTE);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_emu10k1x_gpio_write(struct emu10k1x *emu, unsigned int value)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(value, emu->port + GPIO);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_emu10k1x_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+	kfree(runtime->private_data);
+}
+
+static void snd_emu10k1x_pcm_interrupt(struct emu10k1x *emu, struct emu10k1x_voice *voice)
+{
+	struct emu10k1x_pcm *epcm;
+
+	if ((epcm = voice->epcm) == NULL)
+		return;
+	if (epcm->substream == NULL)
+		return;
+#if 0
+	snd_printk(KERN_INFO "IRQ: position = 0x%x, period = 0x%x, size = 0x%x\n",
+		   epcm->substream->ops->pointer(epcm->substream),
+		   snd_pcm_lib_period_bytes(epcm->substream),
+		   snd_pcm_lib_buffer_bytes(epcm->substream));
+#endif
+	snd_pcm_period_elapsed(epcm->substream);
+}
+
+/* open callback */
+static int snd_emu10k1x_playback_open(struct snd_pcm_substream *substream)
+{
+	struct emu10k1x *chip = snd_pcm_substream_chip(substream);
+	struct emu10k1x_pcm *epcm;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) {
+		return err;
+	}
+	if ((err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64)) < 0)
+                return err;
+
+	epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+	if (epcm == NULL)
+		return -ENOMEM;
+	epcm->emu = chip;
+	epcm->substream = substream;
+  
+	runtime->private_data = epcm;
+	runtime->private_free = snd_emu10k1x_pcm_free_substream;
+  
+	runtime->hw = snd_emu10k1x_playback_hw;
+
+	return 0;
+}
+
+/* close callback */
+static int snd_emu10k1x_playback_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+/* hw_params callback */
+static int snd_emu10k1x_pcm_hw_params(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct emu10k1x_pcm *epcm = runtime->private_data;
+
+	if (! epcm->voice) {
+		epcm->voice = &epcm->emu->voices[substream->pcm->device];
+		epcm->voice->use = 1;
+		epcm->voice->epcm = epcm;
+	}
+
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+/* hw_free callback */
+static int snd_emu10k1x_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct emu10k1x_pcm *epcm;
+
+	if (runtime->private_data == NULL)
+		return 0;
+	
+	epcm = runtime->private_data;
+
+	if (epcm->voice) {
+		epcm->voice->use = 0;
+		epcm->voice->epcm = NULL;
+		epcm->voice = NULL;
+	}
+
+	return snd_pcm_lib_free_pages(substream);
+}
+
+/* prepare callback */
+static int snd_emu10k1x_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct emu10k1x *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct emu10k1x_pcm *epcm = runtime->private_data;
+	int voice = epcm->voice->number;
+	u32 *table_base = (u32 *)(emu->dma_buffer.area+1024*voice);
+	u32 period_size_bytes = frames_to_bytes(runtime, runtime->period_size);
+	int i;
+	
+	for(i = 0; i < runtime->periods; i++) {
+		*table_base++=runtime->dma_addr+(i*period_size_bytes);
+		*table_base++=period_size_bytes<<16;
+	}
+
+	snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_ADDR, voice, emu->dma_buffer.addr+1024*voice);
+	snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_SIZE, voice, (runtime->periods - 1) << 19);
+	snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_PTR, voice, 0);
+	snd_emu10k1x_ptr_write(emu, PLAYBACK_POINTER, voice, 0);
+	snd_emu10k1x_ptr_write(emu, PLAYBACK_UNKNOWN1, voice, 0);
+	snd_emu10k1x_ptr_write(emu, PLAYBACK_UNKNOWN2, voice, 0);
+	snd_emu10k1x_ptr_write(emu, PLAYBACK_DMA_ADDR, voice, runtime->dma_addr);
+
+	snd_emu10k1x_ptr_write(emu, PLAYBACK_PERIOD_SIZE, voice, frames_to_bytes(runtime, runtime->period_size)<<16);
+
+	return 0;
+}
+
+/* trigger callback */
+static int snd_emu10k1x_pcm_trigger(struct snd_pcm_substream *substream,
+				    int cmd)
+{
+	struct emu10k1x *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct emu10k1x_pcm *epcm = runtime->private_data;
+	int channel = epcm->voice->number;
+	int result = 0;
+
+//	snd_printk(KERN_INFO "trigger - emu10k1x = 0x%x, cmd = %i, pointer = %d\n", (int)emu, cmd, (int)substream->ops->pointer(substream));
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if(runtime->periods == 2)
+			snd_emu10k1x_intr_enable(emu, (INTE_CH_0_LOOP | INTE_CH_0_HALF_LOOP) << channel);
+		else
+			snd_emu10k1x_intr_enable(emu, INTE_CH_0_LOOP << channel);
+		epcm->running = 1;
+		snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0)|(TRIGGER_CHANNEL_0<<channel));
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		epcm->running = 0;
+		snd_emu10k1x_intr_disable(emu, (INTE_CH_0_LOOP | INTE_CH_0_HALF_LOOP) << channel);
+		snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0) & ~(TRIGGER_CHANNEL_0<<channel));
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	return result;
+}
+
+/* pointer callback */
+static snd_pcm_uframes_t
+snd_emu10k1x_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct emu10k1x *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct emu10k1x_pcm *epcm = runtime->private_data;
+	int channel = epcm->voice->number;
+	snd_pcm_uframes_t ptr = 0, ptr1 = 0, ptr2= 0,ptr3 = 0,ptr4 = 0;
+
+	if (!epcm->running)
+		return 0;
+
+	ptr3 = snd_emu10k1x_ptr_read(emu, PLAYBACK_LIST_PTR, channel);
+	ptr1 = snd_emu10k1x_ptr_read(emu, PLAYBACK_POINTER, channel);
+	ptr4 = snd_emu10k1x_ptr_read(emu, PLAYBACK_LIST_PTR, channel);
+
+	if(ptr4 == 0 && ptr1 == frames_to_bytes(runtime, runtime->buffer_size))
+		return 0;
+	
+	if (ptr3 != ptr4) 
+		ptr1 = snd_emu10k1x_ptr_read(emu, PLAYBACK_POINTER, channel);
+	ptr2 = bytes_to_frames(runtime, ptr1);
+	ptr2 += (ptr4 >> 3) * runtime->period_size;
+	ptr = ptr2;
+
+	if (ptr >= runtime->buffer_size)
+		ptr -= runtime->buffer_size;
+
+	return ptr;
+}
+
+/* operators */
+static struct snd_pcm_ops snd_emu10k1x_playback_ops = {
+	.open =        snd_emu10k1x_playback_open,
+	.close =       snd_emu10k1x_playback_close,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_emu10k1x_pcm_hw_params,
+	.hw_free =     snd_emu10k1x_pcm_hw_free,
+	.prepare =     snd_emu10k1x_pcm_prepare,
+	.trigger =     snd_emu10k1x_pcm_trigger,
+	.pointer =     snd_emu10k1x_pcm_pointer,
+};
+
+/* open_capture callback */
+static int snd_emu10k1x_pcm_open_capture(struct snd_pcm_substream *substream)
+{
+	struct emu10k1x *chip = snd_pcm_substream_chip(substream);
+	struct emu10k1x_pcm *epcm;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+                return err;
+	if ((err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64)) < 0)
+                return err;
+
+	epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+	if (epcm == NULL)
+		return -ENOMEM;
+
+	epcm->emu = chip;
+	epcm->substream = substream;
+
+	runtime->private_data = epcm;
+	runtime->private_free = snd_emu10k1x_pcm_free_substream;
+
+	runtime->hw = snd_emu10k1x_capture_hw;
+
+	return 0;
+}
+
+/* close callback */
+static int snd_emu10k1x_pcm_close_capture(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+/* hw_params callback */
+static int snd_emu10k1x_pcm_hw_params_capture(struct snd_pcm_substream *substream,
+					      struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct emu10k1x_pcm *epcm = runtime->private_data;
+
+	if (! epcm->voice) {
+		if (epcm->emu->capture_voice.use)
+			return -EBUSY;
+		epcm->voice = &epcm->emu->capture_voice;
+		epcm->voice->epcm = epcm;
+		epcm->voice->use = 1;
+	}
+
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+/* hw_free callback */
+static int snd_emu10k1x_pcm_hw_free_capture(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	struct emu10k1x_pcm *epcm;
+
+	if (runtime->private_data == NULL)
+		return 0;
+	epcm = runtime->private_data;
+
+	if (epcm->voice) {
+		epcm->voice->use = 0;
+		epcm->voice->epcm = NULL;
+		epcm->voice = NULL;
+	}
+
+	return snd_pcm_lib_free_pages(substream);
+}
+
+/* prepare capture callback */
+static int snd_emu10k1x_pcm_prepare_capture(struct snd_pcm_substream *substream)
+{
+	struct emu10k1x *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	snd_emu10k1x_ptr_write(emu, CAPTURE_DMA_ADDR, 0, runtime->dma_addr);
+	snd_emu10k1x_ptr_write(emu, CAPTURE_BUFFER_SIZE, 0, frames_to_bytes(runtime, runtime->buffer_size)<<16); // buffer size in bytes
+	snd_emu10k1x_ptr_write(emu, CAPTURE_POINTER, 0, 0);
+	snd_emu10k1x_ptr_write(emu, CAPTURE_UNKNOWN, 0, 0);
+
+	return 0;
+}
+
+/* trigger_capture callback */
+static int snd_emu10k1x_pcm_trigger_capture(struct snd_pcm_substream *substream,
+					    int cmd)
+{
+	struct emu10k1x *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct emu10k1x_pcm *epcm = runtime->private_data;
+	int result = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_emu10k1x_intr_enable(emu, INTE_CAP_0_LOOP | 
+					 INTE_CAP_0_HALF_LOOP);
+		snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0)|TRIGGER_CAPTURE);
+		epcm->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		epcm->running = 0;
+		snd_emu10k1x_intr_disable(emu, INTE_CAP_0_LOOP | 
+					  INTE_CAP_0_HALF_LOOP);
+		snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0) & ~(TRIGGER_CAPTURE));
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	return result;
+}
+
+/* pointer_capture callback */
+static snd_pcm_uframes_t
+snd_emu10k1x_pcm_pointer_capture(struct snd_pcm_substream *substream)
+{
+	struct emu10k1x *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct emu10k1x_pcm *epcm = runtime->private_data;
+	snd_pcm_uframes_t ptr;
+
+	if (!epcm->running)
+		return 0;
+
+	ptr = bytes_to_frames(runtime, snd_emu10k1x_ptr_read(emu, CAPTURE_POINTER, 0));
+	if (ptr >= runtime->buffer_size)
+		ptr -= runtime->buffer_size;
+
+	return ptr;
+}
+
+static struct snd_pcm_ops snd_emu10k1x_capture_ops = {
+	.open =        snd_emu10k1x_pcm_open_capture,
+	.close =       snd_emu10k1x_pcm_close_capture,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_emu10k1x_pcm_hw_params_capture,
+	.hw_free =     snd_emu10k1x_pcm_hw_free_capture,
+	.prepare =     snd_emu10k1x_pcm_prepare_capture,
+	.trigger =     snd_emu10k1x_pcm_trigger_capture,
+	.pointer =     snd_emu10k1x_pcm_pointer_capture,
+};
+
+static unsigned short snd_emu10k1x_ac97_read(struct snd_ac97 *ac97,
+					     unsigned short reg)
+{
+	struct emu10k1x *emu = ac97->private_data;
+	unsigned long flags;
+	unsigned short val;
+  
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outb(reg, emu->port + AC97ADDRESS);
+	val = inw(emu->port + AC97DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+	return val;
+}
+
+static void snd_emu10k1x_ac97_write(struct snd_ac97 *ac97,
+				    unsigned short reg, unsigned short val)
+{
+	struct emu10k1x *emu = ac97->private_data;
+	unsigned long flags;
+  
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outb(reg, emu->port + AC97ADDRESS);
+	outw(val, emu->port + AC97DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static int snd_emu10k1x_ac97(struct emu10k1x *chip)
+{
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_emu10k1x_ac97_write,
+		.read = snd_emu10k1x_ac97_read,
+	};
+  
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus)) < 0)
+		return err;
+	pbus->no_vra = 1; /* we don't need VRA */
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.scaps = AC97_SCAP_NO_SPDIF;
+	return snd_ac97_mixer(pbus, &ac97, &chip->ac97);
+}
+
+static int snd_emu10k1x_free(struct emu10k1x *chip)
+{
+	snd_emu10k1x_ptr_write(chip, TRIGGER_CHANNEL, 0, 0);
+	// disable interrupts
+	outl(0, chip->port + INTE);
+	// disable audio
+	outl(HCFG_LOCKSOUNDCACHE, chip->port + HCFG);
+
+	/* release the irq */
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+
+	// release the i/o port
+	release_and_free_resource(chip->res_port);
+
+	// release the DMA
+	if (chip->dma_buffer.area) {
+		snd_dma_free_pages(&chip->dma_buffer);
+	}
+
+	pci_disable_device(chip->pci);
+
+	// release the data
+	kfree(chip);
+	return 0;
+}
+
+static int snd_emu10k1x_dev_free(struct snd_device *device)
+{
+	struct emu10k1x *chip = device->device_data;
+	return snd_emu10k1x_free(chip);
+}
+
+static irqreturn_t snd_emu10k1x_interrupt(int irq, void *dev_id)
+{
+	unsigned int status;
+
+	struct emu10k1x *chip = dev_id;
+	struct emu10k1x_voice *pvoice = chip->voices;
+	int i;
+	int mask;
+
+	status = inl(chip->port + IPR);
+
+	if (! status)
+		return IRQ_NONE;
+
+	// capture interrupt
+	if (status & (IPR_CAP_0_LOOP | IPR_CAP_0_HALF_LOOP)) {
+		struct emu10k1x_voice *cap_voice = &chip->capture_voice;
+		if (cap_voice->use)
+			snd_emu10k1x_pcm_interrupt(chip, cap_voice);
+		else
+			snd_emu10k1x_intr_disable(chip, 
+						  INTE_CAP_0_LOOP |
+						  INTE_CAP_0_HALF_LOOP);
+	}
+		
+	mask = IPR_CH_0_LOOP|IPR_CH_0_HALF_LOOP;
+	for (i = 0; i < 3; i++) {
+		if (status & mask) {
+			if (pvoice->use)
+				snd_emu10k1x_pcm_interrupt(chip, pvoice);
+			else 
+				snd_emu10k1x_intr_disable(chip, mask);
+		}
+		pvoice++;
+		mask <<= 1;
+	}
+		
+	if (status & (IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY)) {
+		if (chip->midi.interrupt)
+			chip->midi.interrupt(chip, status);
+		else
+			snd_emu10k1x_intr_disable(chip, INTE_MIDITXENABLE|INTE_MIDIRXENABLE);
+	}
+		
+	// acknowledge the interrupt if necessary
+	outl(status, chip->port + IPR);
+
+	// snd_printk(KERN_INFO "interrupt %08x\n", status);
+	return IRQ_HANDLED;
+}
+
+static int __devinit snd_emu10k1x_pcm(struct emu10k1x *emu, int device, struct snd_pcm **rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+	int capture = 0;
+  
+	if (rpcm)
+		*rpcm = NULL;
+	if (device == 0)
+		capture = 1;
+	
+	if ((err = snd_pcm_new(emu->card, "emu10k1x", device, 1, capture, &pcm)) < 0)
+		return err;
+  
+	pcm->private_data = emu;
+	
+	switch(device) {
+	case 0:
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1x_playback_ops);
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1x_capture_ops);
+		break;
+	case 1:
+	case 2:
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1x_playback_ops);
+		break;
+	}
+
+	pcm->info_flags = 0;
+	switch(device) {
+	case 0:
+		strcpy(pcm->name, "EMU10K1X Front");
+		break;
+	case 1:
+		strcpy(pcm->name, "EMU10K1X Rear");
+		break;
+	case 2:
+		strcpy(pcm->name, "EMU10K1X Center/LFE");
+		break;
+	}
+	emu->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(emu->pci), 
+					      32*1024, 32*1024);
+  
+	if (rpcm)
+		*rpcm = pcm;
+  
+	return 0;
+}
+
+static int __devinit snd_emu10k1x_create(struct snd_card *card,
+					 struct pci_dev *pci,
+					 struct emu10k1x **rchip)
+{
+	struct emu10k1x *chip;
+	int err;
+	int ch;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_emu10k1x_dev_free,
+	};
+
+	*rchip = NULL;
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(28)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(28)) < 0) {
+		snd_printk(KERN_ERR "error to set 28bit mask DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	spin_lock_init(&chip->emu_lock);
+	spin_lock_init(&chip->voice_lock);
+  
+	chip->port = pci_resource_start(pci, 0);
+	if ((chip->res_port = request_region(chip->port, 8,
+					     "EMU10K1X")) == NULL) { 
+		snd_printk(KERN_ERR "emu10k1x: cannot allocate the port 0x%lx\n", chip->port);
+		snd_emu10k1x_free(chip);
+		return -EBUSY;
+	}
+
+	if (request_irq(pci->irq, snd_emu10k1x_interrupt,
+			IRQF_SHARED, "EMU10K1X", chip)) {
+		snd_printk(KERN_ERR "emu10k1x: cannot grab irq %d\n", pci->irq);
+		snd_emu10k1x_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+  
+	if(snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+			       4 * 1024, &chip->dma_buffer) < 0) {
+		snd_emu10k1x_free(chip);
+		return -ENOMEM;
+	}
+
+	pci_set_master(pci);
+	/* read revision & serial */
+	chip->revision = pci->revision;
+	pci_read_config_dword(pci, PCI_SUBSYSTEM_VENDOR_ID, &chip->serial);
+	pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &chip->model);
+	snd_printk(KERN_INFO "Model %04x Rev %08x Serial %08x\n", chip->model,
+		   chip->revision, chip->serial);
+
+	outl(0, chip->port + INTE);	
+
+	for(ch = 0; ch < 3; ch++) {
+		chip->voices[ch].emu = chip;
+		chip->voices[ch].number = ch;
+	}
+
+	/*
+	 *  Init to 0x02109204 :
+	 *  Clock accuracy    = 0     (1000ppm)
+	 *  Sample Rate       = 2     (48kHz)
+	 *  Audio Channel     = 1     (Left of 2)
+	 *  Source Number     = 0     (Unspecified)
+	 *  Generation Status = 1     (Original for Cat Code 12)
+	 *  Cat Code          = 12    (Digital Signal Mixer)
+	 *  Mode              = 0     (Mode 0)
+	 *  Emphasis          = 0     (None)
+	 *  CP                = 1     (Copyright unasserted)
+	 *  AN                = 0     (Audio data)
+	 *  P                 = 0     (Consumer)
+	 */
+	snd_emu10k1x_ptr_write(chip, SPCS0, 0,
+			       chip->spdif_bits[0] = 
+			       SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+			       SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+			       SPCS_GENERATIONSTATUS | 0x00001200 |
+			       0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
+	snd_emu10k1x_ptr_write(chip, SPCS1, 0,
+			       chip->spdif_bits[1] = 
+			       SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+			       SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+			       SPCS_GENERATIONSTATUS | 0x00001200 |
+			       0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
+	snd_emu10k1x_ptr_write(chip, SPCS2, 0,
+			       chip->spdif_bits[2] = 
+			       SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+			       SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
+			       SPCS_GENERATIONSTATUS | 0x00001200 |
+			       0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
+
+	snd_emu10k1x_ptr_write(chip, SPDIF_SELECT, 0, 0x700); // disable SPDIF
+	snd_emu10k1x_ptr_write(chip, ROUTING, 0, 0x1003F); // routing
+	snd_emu10k1x_gpio_write(chip, 0x1080); // analog mode
+
+	outl(HCFG_LOCKSOUNDCACHE|HCFG_AUDIOENABLE, chip->port+HCFG);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+				  chip, &ops)) < 0) {
+		snd_emu10k1x_free(chip);
+		return err;
+	}
+	*rchip = chip;
+	return 0;
+}
+
+static void snd_emu10k1x_proc_reg_read(struct snd_info_entry *entry, 
+				       struct snd_info_buffer *buffer)
+{
+	struct emu10k1x *emu = entry->private_data;
+	unsigned long value,value1,value2;
+	unsigned long flags;
+	int i;
+
+	snd_iprintf(buffer, "Registers:\n\n");
+	for(i = 0; i < 0x20; i+=4) {
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		value = inl(emu->port + i);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+		snd_iprintf(buffer, "Register %02X: %08lX\n", i, value);
+	}
+	snd_iprintf(buffer, "\nRegisters\n\n");
+	for(i = 0; i <= 0x48; i++) {
+		value = snd_emu10k1x_ptr_read(emu, i, 0);
+		if(i < 0x10 || (i >= 0x20 && i < 0x40)) {
+			value1 = snd_emu10k1x_ptr_read(emu, i, 1);
+			value2 = snd_emu10k1x_ptr_read(emu, i, 2);
+			snd_iprintf(buffer, "%02X: %08lX %08lX %08lX\n", i, value, value1, value2);
+		} else {
+			snd_iprintf(buffer, "%02X: %08lX\n", i, value);
+		}
+	}
+}
+
+static void snd_emu10k1x_proc_reg_write(struct snd_info_entry *entry, 
+					struct snd_info_buffer *buffer)
+{
+	struct emu10k1x *emu = entry->private_data;
+	char line[64];
+	unsigned int reg, channel_id , val;
+
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		if (sscanf(line, "%x %x %x", &reg, &channel_id, &val) != 3)
+			continue;
+
+		if (reg < 0x49 && val <= 0xffffffff && channel_id <= 2)
+			snd_emu10k1x_ptr_write(emu, reg, channel_id, val);
+	}
+}
+
+static int __devinit snd_emu10k1x_proc_init(struct emu10k1x * emu)
+{
+	struct snd_info_entry *entry;
+	
+	if(! snd_card_proc_new(emu->card, "emu10k1x_regs", &entry)) {
+		snd_info_set_text_ops(entry, emu, snd_emu10k1x_proc_reg_read);
+		entry->c.text.write = snd_emu10k1x_proc_reg_write;
+		entry->mode |= S_IWUSR;
+		entry->private_data = emu;
+	}
+	
+	return 0;
+}
+
+#define snd_emu10k1x_shared_spdif_info	snd_ctl_boolean_mono_info
+
+static int snd_emu10k1x_shared_spdif_get(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct emu10k1x *emu = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = (snd_emu10k1x_ptr_read(emu, SPDIF_SELECT, 0) == 0x700) ? 0 : 1;
+
+	return 0;
+}
+
+static int snd_emu10k1x_shared_spdif_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct emu10k1x *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change = 0;
+
+	val = ucontrol->value.integer.value[0] ;
+
+	if (val) {
+		// enable spdif output
+		snd_emu10k1x_ptr_write(emu, SPDIF_SELECT, 0, 0x000);
+		snd_emu10k1x_ptr_write(emu, ROUTING, 0, 0x700);
+		snd_emu10k1x_gpio_write(emu, 0x1000);
+	} else {
+		// disable spdif output
+		snd_emu10k1x_ptr_write(emu, SPDIF_SELECT, 0, 0x700);
+		snd_emu10k1x_ptr_write(emu, ROUTING, 0, 0x1003F);
+		snd_emu10k1x_gpio_write(emu, 0x1080);
+	}
+	return change;
+}
+
+static struct snd_kcontrol_new snd_emu10k1x_shared_spdif __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Analog/Digital Output Jack",
+	.info =		snd_emu10k1x_shared_spdif_info,
+	.get =		snd_emu10k1x_shared_spdif_get,
+	.put =		snd_emu10k1x_shared_spdif_put
+};
+
+static int snd_emu10k1x_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_emu10k1x_spdif_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct emu10k1x *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	ucontrol->value.iec958.status[0] = (emu->spdif_bits[idx] >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (emu->spdif_bits[idx] >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (emu->spdif_bits[idx] >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (emu->spdif_bits[idx] >> 24) & 0xff;
+	return 0;
+}
+
+static int snd_emu10k1x_spdif_get_mask(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0xff;
+	return 0;
+}
+
+static int snd_emu10k1x_spdif_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct emu10k1x *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	int change;
+	unsigned int val;
+
+	val = (ucontrol->value.iec958.status[0] << 0) |
+		(ucontrol->value.iec958.status[1] << 8) |
+		(ucontrol->value.iec958.status[2] << 16) |
+		(ucontrol->value.iec958.status[3] << 24);
+	change = val != emu->spdif_bits[idx];
+	if (change) {
+		snd_emu10k1x_ptr_write(emu, SPCS0 + idx, 0, val);
+		emu->spdif_bits[idx] = val;
+	}
+	return change;
+}
+
+static struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+	.count =	3,
+	.info =         snd_emu10k1x_spdif_info,
+	.get =          snd_emu10k1x_spdif_get_mask
+};
+
+static struct snd_kcontrol_new snd_emu10k1x_spdif_control =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.count =	3,
+	.info =         snd_emu10k1x_spdif_info,
+	.get =          snd_emu10k1x_spdif_get,
+	.put =          snd_emu10k1x_spdif_put
+};
+
+static int __devinit snd_emu10k1x_mixer(struct emu10k1x *emu)
+{
+	int err;
+	struct snd_kcontrol *kctl;
+	struct snd_card *card = emu->card;
+
+	if ((kctl = snd_ctl_new1(&snd_emu10k1x_spdif_mask_control, emu)) == NULL)
+		return -ENOMEM;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+	if ((kctl = snd_ctl_new1(&snd_emu10k1x_shared_spdif, emu)) == NULL)
+		return -ENOMEM;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+	if ((kctl = snd_ctl_new1(&snd_emu10k1x_spdif_control, emu)) == NULL)
+		return -ENOMEM;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+
+	return 0;
+}
+
+#define EMU10K1X_MIDI_MODE_INPUT	(1<<0)
+#define EMU10K1X_MIDI_MODE_OUTPUT	(1<<1)
+
+static inline unsigned char mpu401_read(struct emu10k1x *emu, struct emu10k1x_midi *mpu, int idx)
+{
+	return (unsigned char)snd_emu10k1x_ptr_read(emu, mpu->port + idx, 0);
+}
+
+static inline void mpu401_write(struct emu10k1x *emu, struct emu10k1x_midi *mpu, int data, int idx)
+{
+	snd_emu10k1x_ptr_write(emu, mpu->port + idx, 0, data);
+}
+
+#define mpu401_write_data(emu, mpu, data)	mpu401_write(emu, mpu, data, 0)
+#define mpu401_write_cmd(emu, mpu, data)	mpu401_write(emu, mpu, data, 1)
+#define mpu401_read_data(emu, mpu)		mpu401_read(emu, mpu, 0)
+#define mpu401_read_stat(emu, mpu)		mpu401_read(emu, mpu, 1)
+
+#define mpu401_input_avail(emu,mpu)	(!(mpu401_read_stat(emu,mpu) & 0x80))
+#define mpu401_output_ready(emu,mpu)	(!(mpu401_read_stat(emu,mpu) & 0x40))
+
+#define MPU401_RESET		0xff
+#define MPU401_ENTER_UART	0x3f
+#define MPU401_ACK		0xfe
+
+static void mpu401_clear_rx(struct emu10k1x *emu, struct emu10k1x_midi *mpu)
+{
+	int timeout = 100000;
+	for (; timeout > 0 && mpu401_input_avail(emu, mpu); timeout--)
+		mpu401_read_data(emu, mpu);
+#ifdef CONFIG_SND_DEBUG
+	if (timeout <= 0)
+		snd_printk(KERN_ERR "cmd: clear rx timeout (status = 0x%x)\n", mpu401_read_stat(emu, mpu));
+#endif
+}
+
+/*
+
+ */
+
+static void do_emu10k1x_midi_interrupt(struct emu10k1x *emu,
+				       struct emu10k1x_midi *midi, unsigned int status)
+{
+	unsigned char byte;
+
+	if (midi->rmidi == NULL) {
+		snd_emu10k1x_intr_disable(emu, midi->tx_enable | midi->rx_enable);
+		return;
+	}
+
+	spin_lock(&midi->input_lock);
+	if ((status & midi->ipr_rx) && mpu401_input_avail(emu, midi)) {
+		if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_INPUT)) {
+			mpu401_clear_rx(emu, midi);
+		} else {
+			byte = mpu401_read_data(emu, midi);
+			if (midi->substream_input)
+				snd_rawmidi_receive(midi->substream_input, &byte, 1);
+		}
+	}
+	spin_unlock(&midi->input_lock);
+
+	spin_lock(&midi->output_lock);
+	if ((status & midi->ipr_tx) && mpu401_output_ready(emu, midi)) {
+		if (midi->substream_output &&
+		    snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) {
+			mpu401_write_data(emu, midi, byte);
+		} else {
+			snd_emu10k1x_intr_disable(emu, midi->tx_enable);
+		}
+	}
+	spin_unlock(&midi->output_lock);
+}
+
+static void snd_emu10k1x_midi_interrupt(struct emu10k1x *emu, unsigned int status)
+{
+	do_emu10k1x_midi_interrupt(emu, &emu->midi, status);
+}
+
+static int snd_emu10k1x_midi_cmd(struct emu10k1x * emu,
+				  struct emu10k1x_midi *midi, unsigned char cmd, int ack)
+{
+	unsigned long flags;
+	int timeout, ok;
+
+	spin_lock_irqsave(&midi->input_lock, flags);
+	mpu401_write_data(emu, midi, 0x00);
+	/* mpu401_clear_rx(emu, midi); */
+
+	mpu401_write_cmd(emu, midi, cmd);
+	if (ack) {
+		ok = 0;
+		timeout = 10000;
+		while (!ok && timeout-- > 0) {
+			if (mpu401_input_avail(emu, midi)) {
+				if (mpu401_read_data(emu, midi) == MPU401_ACK)
+					ok = 1;
+			}
+		}
+		if (!ok && mpu401_read_data(emu, midi) == MPU401_ACK)
+			ok = 1;
+	} else {
+		ok = 1;
+	}
+	spin_unlock_irqrestore(&midi->input_lock, flags);
+	if (!ok) {
+		snd_printk(KERN_ERR "midi_cmd: 0x%x failed at 0x%lx (status = 0x%x, data = 0x%x)!!!\n",
+			   cmd, emu->port,
+			   mpu401_read_stat(emu, midi),
+			   mpu401_read_data(emu, midi));
+		return 1;
+	}
+	return 0;
+}
+
+static int snd_emu10k1x_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct emu10k1x *emu;
+	struct emu10k1x_midi *midi = substream->rmidi->private_data;
+	unsigned long flags;
+	
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return -ENXIO;
+	spin_lock_irqsave(&midi->open_lock, flags);
+	midi->midi_mode |= EMU10K1X_MIDI_MODE_INPUT;
+	midi->substream_input = substream;
+	if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_OUTPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 1))
+			goto error_out;
+		if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_ENTER_UART, 1))
+			goto error_out;
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return 0;
+
+error_out:
+	return -EIO;
+}
+
+static int snd_emu10k1x_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct emu10k1x *emu;
+	struct emu10k1x_midi *midi = substream->rmidi->private_data;
+	unsigned long flags;
+
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return -ENXIO;
+	spin_lock_irqsave(&midi->open_lock, flags);
+	midi->midi_mode |= EMU10K1X_MIDI_MODE_OUTPUT;
+	midi->substream_output = substream;
+	if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_INPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 1))
+			goto error_out;
+		if (snd_emu10k1x_midi_cmd(emu, midi, MPU401_ENTER_UART, 1))
+			goto error_out;
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return 0;
+
+error_out:
+	return -EIO;
+}
+
+static int snd_emu10k1x_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct emu10k1x *emu;
+	struct emu10k1x_midi *midi = substream->rmidi->private_data;
+	unsigned long flags;
+	int err = 0;
+
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return -ENXIO;
+	spin_lock_irqsave(&midi->open_lock, flags);
+	snd_emu10k1x_intr_disable(emu, midi->rx_enable);
+	midi->midi_mode &= ~EMU10K1X_MIDI_MODE_INPUT;
+	midi->substream_input = NULL;
+	if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_OUTPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		err = snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 0);
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return err;
+}
+
+static int snd_emu10k1x_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct emu10k1x *emu;
+	struct emu10k1x_midi *midi = substream->rmidi->private_data;
+	unsigned long flags;
+	int err = 0;
+
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return -ENXIO;
+	spin_lock_irqsave(&midi->open_lock, flags);
+	snd_emu10k1x_intr_disable(emu, midi->tx_enable);
+	midi->midi_mode &= ~EMU10K1X_MIDI_MODE_OUTPUT;
+	midi->substream_output = NULL;
+	if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_INPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		err = snd_emu10k1x_midi_cmd(emu, midi, MPU401_RESET, 0);
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return err;
+}
+
+static void snd_emu10k1x_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct emu10k1x *emu;
+	struct emu10k1x_midi *midi = substream->rmidi->private_data;
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return;
+
+	if (up)
+		snd_emu10k1x_intr_enable(emu, midi->rx_enable);
+	else
+		snd_emu10k1x_intr_disable(emu, midi->rx_enable);
+}
+
+static void snd_emu10k1x_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct emu10k1x *emu;
+	struct emu10k1x_midi *midi = substream->rmidi->private_data;
+	unsigned long flags;
+
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return;
+
+	if (up) {
+		int max = 4;
+		unsigned char byte;
+	
+		/* try to send some amount of bytes here before interrupts */
+		spin_lock_irqsave(&midi->output_lock, flags);
+		while (max > 0) {
+			if (mpu401_output_ready(emu, midi)) {
+				if (!(midi->midi_mode & EMU10K1X_MIDI_MODE_OUTPUT) ||
+				    snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+					/* no more data */
+					spin_unlock_irqrestore(&midi->output_lock, flags);
+					return;
+				}
+				mpu401_write_data(emu, midi, byte);
+				max--;
+			} else {
+				break;
+			}
+		}
+		spin_unlock_irqrestore(&midi->output_lock, flags);
+		snd_emu10k1x_intr_enable(emu, midi->tx_enable);
+	} else {
+		snd_emu10k1x_intr_disable(emu, midi->tx_enable);
+	}
+}
+
+/*
+
+ */
+
+static struct snd_rawmidi_ops snd_emu10k1x_midi_output =
+{
+	.open =		snd_emu10k1x_midi_output_open,
+	.close =	snd_emu10k1x_midi_output_close,
+	.trigger =	snd_emu10k1x_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_emu10k1x_midi_input =
+{
+	.open =		snd_emu10k1x_midi_input_open,
+	.close =	snd_emu10k1x_midi_input_close,
+	.trigger =	snd_emu10k1x_midi_input_trigger,
+};
+
+static void snd_emu10k1x_midi_free(struct snd_rawmidi *rmidi)
+{
+	struct emu10k1x_midi *midi = rmidi->private_data;
+	midi->interrupt = NULL;
+	midi->rmidi = NULL;
+}
+
+static int __devinit emu10k1x_midi_init(struct emu10k1x *emu,
+					struct emu10k1x_midi *midi, int device, char *name)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	if ((err = snd_rawmidi_new(emu->card, name, device, 1, 1, &rmidi)) < 0)
+		return err;
+	midi->emu = emu;
+	spin_lock_init(&midi->open_lock);
+	spin_lock_init(&midi->input_lock);
+	spin_lock_init(&midi->output_lock);
+	strcpy(rmidi->name, name);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_emu10k1x_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_emu10k1x_midi_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+	                     SNDRV_RAWMIDI_INFO_INPUT |
+	                     SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = midi;
+	rmidi->private_free = snd_emu10k1x_midi_free;
+	midi->rmidi = rmidi;
+	return 0;
+}
+
+static int __devinit snd_emu10k1x_midi(struct emu10k1x *emu)
+{
+	struct emu10k1x_midi *midi = &emu->midi;
+	int err;
+
+	if ((err = emu10k1x_midi_init(emu, midi, 0, "EMU10K1X MPU-401 (UART)")) < 0)
+		return err;
+
+	midi->tx_enable = INTE_MIDITXENABLE;
+	midi->rx_enable = INTE_MIDIRXENABLE;
+	midi->port = MUDATA;
+	midi->ipr_tx = IPR_MIDITRANSBUFEMPTY;
+	midi->ipr_rx = IPR_MIDIRECVBUFEMPTY;
+	midi->interrupt = snd_emu10k1x_midi_interrupt;
+	return 0;
+}
+
+static int __devinit snd_emu10k1x_probe(struct pci_dev *pci,
+					const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct emu10k1x *chip;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	if ((err = snd_emu10k1x_create(card, pci, &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_emu10k1x_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_emu10k1x_pcm(chip, 1, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_emu10k1x_pcm(chip, 2, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_emu10k1x_ac97(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_emu10k1x_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	
+	if ((err = snd_emu10k1x_midi(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	snd_emu10k1x_proc_init(chip);
+
+	strcpy(card->driver, "EMU10K1X");
+	strcpy(card->shortname, "Dell Sound Blaster Live!");
+	sprintf(card->longname, "%s at 0x%lx irq %i",
+		card->shortname, chip->port, chip->irq);
+
+	snd_card_set_dev(card, &pci->dev);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_emu10k1x_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+// PCI IDs
+static DEFINE_PCI_DEVICE_TABLE(snd_emu10k1x_ids) = {
+	{ PCI_VDEVICE(CREATIVE, 0x0006), 0 },	/* Dell OEM version (EMU10K1) */
+	{ 0, }
+};
+MODULE_DEVICE_TABLE(pci, snd_emu10k1x_ids);
+
+// pci_driver definition
+static struct pci_driver driver = {
+	.name = "EMU10K1X",
+	.id_table = snd_emu10k1x_ids,
+	.probe = snd_emu10k1x_probe,
+	.remove = __devexit_p(snd_emu10k1x_remove),
+};
+
+// initialization of the module
+static int __init alsa_card_emu10k1x_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+// clean up the module
+static void __exit alsa_card_emu10k1x_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_emu10k1x_init)
+module_exit(alsa_card_emu10k1x_exit)
diff --git a/linux/sound/pci/emu10k1/emufx.c b/linux/sound/pci/emu10k1/emufx.c
new file mode 100644
index 0000000..7a94014
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emufx.c
@@ -0,0 +1,2753 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *                   Creative Labs, Inc.
+ *  Routines for effect processor FX8010
+ *
+ *  Copyright (c) by James Courtier-Dutton <James@superbug.co.uk>
+ *  	Added EMU 1010 support.
+ *
+ *  BUGS:
+ *    --
+ *
+ *  TODO:
+ *    --
+ *
+ *   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/pci.h>
+#include <linux/capability.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/moduleparam.h>
+
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/emu10k1.h>
+
+#if 0		/* for testing purposes - digital out -> capture */
+#define EMU10K1_CAPTURE_DIGITAL_OUT
+#endif
+#if 0		/* for testing purposes - set S/PDIF to AC3 output */
+#define EMU10K1_SET_AC3_IEC958
+#endif
+#if 0		/* for testing purposes - feed the front signal to Center/LFE outputs */
+#define EMU10K1_CENTER_LFE_FROM_FRONT
+#endif
+
+static bool high_res_gpr_volume;
+module_param(high_res_gpr_volume, bool, 0444);
+MODULE_PARM_DESC(high_res_gpr_volume, "GPR mixer controls use 31-bit range.");
+
+/*
+ *  Tables
+ */ 
+
+static char *fxbuses[16] = {
+	/* 0x00 */ "PCM Left",
+	/* 0x01 */ "PCM Right",
+	/* 0x02 */ "PCM Surround Left",
+	/* 0x03 */ "PCM Surround Right",
+	/* 0x04 */ "MIDI Left",
+	/* 0x05 */ "MIDI Right",
+	/* 0x06 */ "Center",
+	/* 0x07 */ "LFE",
+	/* 0x08 */ NULL,
+	/* 0x09 */ NULL,
+	/* 0x0a */ NULL,
+	/* 0x0b */ NULL,
+	/* 0x0c */ "MIDI Reverb",
+	/* 0x0d */ "MIDI Chorus",
+	/* 0x0e */ NULL,
+	/* 0x0f */ NULL
+};
+
+static char *creative_ins[16] = {
+	/* 0x00 */ "AC97 Left",
+	/* 0x01 */ "AC97 Right",
+	/* 0x02 */ "TTL IEC958 Left",
+	/* 0x03 */ "TTL IEC958 Right",
+	/* 0x04 */ "Zoom Video Left",
+	/* 0x05 */ "Zoom Video Right",
+	/* 0x06 */ "Optical IEC958 Left",
+	/* 0x07 */ "Optical IEC958 Right",
+	/* 0x08 */ "Line/Mic 1 Left",
+	/* 0x09 */ "Line/Mic 1 Right",
+	/* 0x0a */ "Coaxial IEC958 Left",
+	/* 0x0b */ "Coaxial IEC958 Right",
+	/* 0x0c */ "Line/Mic 2 Left",
+	/* 0x0d */ "Line/Mic 2 Right",
+	/* 0x0e */ NULL,
+	/* 0x0f */ NULL
+};
+
+static char *audigy_ins[16] = {
+	/* 0x00 */ "AC97 Left",
+	/* 0x01 */ "AC97 Right",
+	/* 0x02 */ "Audigy CD Left",
+	/* 0x03 */ "Audigy CD Right",
+	/* 0x04 */ "Optical IEC958 Left",
+	/* 0x05 */ "Optical IEC958 Right",
+	/* 0x06 */ NULL,
+	/* 0x07 */ NULL,
+	/* 0x08 */ "Line/Mic 2 Left",
+	/* 0x09 */ "Line/Mic 2 Right",
+	/* 0x0a */ "SPDIF Left",
+	/* 0x0b */ "SPDIF Right",
+	/* 0x0c */ "Aux2 Left",
+	/* 0x0d */ "Aux2 Right",
+	/* 0x0e */ NULL,
+	/* 0x0f */ NULL
+};
+
+static char *creative_outs[32] = {
+	/* 0x00 */ "AC97 Left",
+	/* 0x01 */ "AC97 Right",
+	/* 0x02 */ "Optical IEC958 Left",
+	/* 0x03 */ "Optical IEC958 Right",
+	/* 0x04 */ "Center",
+	/* 0x05 */ "LFE",
+	/* 0x06 */ "Headphone Left",
+	/* 0x07 */ "Headphone Right",
+	/* 0x08 */ "Surround Left",
+	/* 0x09 */ "Surround Right",
+	/* 0x0a */ "PCM Capture Left",
+	/* 0x0b */ "PCM Capture Right",
+	/* 0x0c */ "MIC Capture",
+	/* 0x0d */ "AC97 Surround Left",
+	/* 0x0e */ "AC97 Surround Right",
+	/* 0x0f */ NULL,
+	/* 0x10 */ NULL,
+	/* 0x11 */ "Analog Center",
+	/* 0x12 */ "Analog LFE",
+	/* 0x13 */ NULL,
+	/* 0x14 */ NULL,
+	/* 0x15 */ NULL,
+	/* 0x16 */ NULL,
+	/* 0x17 */ NULL,
+	/* 0x18 */ NULL,
+	/* 0x19 */ NULL,
+	/* 0x1a */ NULL,
+	/* 0x1b */ NULL,
+	/* 0x1c */ NULL,
+	/* 0x1d */ NULL,
+	/* 0x1e */ NULL,
+	/* 0x1f */ NULL,
+};
+
+static char *audigy_outs[32] = {
+	/* 0x00 */ "Digital Front Left",
+	/* 0x01 */ "Digital Front Right",
+	/* 0x02 */ "Digital Center",
+	/* 0x03 */ "Digital LEF",
+	/* 0x04 */ "Headphone Left",
+	/* 0x05 */ "Headphone Right",
+	/* 0x06 */ "Digital Rear Left",
+	/* 0x07 */ "Digital Rear Right",
+	/* 0x08 */ "Front Left",
+	/* 0x09 */ "Front Right",
+	/* 0x0a */ "Center",
+	/* 0x0b */ "LFE",
+	/* 0x0c */ NULL,
+	/* 0x0d */ NULL,
+	/* 0x0e */ "Rear Left",
+	/* 0x0f */ "Rear Right",
+	/* 0x10 */ "AC97 Front Left",
+	/* 0x11 */ "AC97 Front Right",
+	/* 0x12 */ "ADC Caputre Left",
+	/* 0x13 */ "ADC Capture Right",
+	/* 0x14 */ NULL,
+	/* 0x15 */ NULL,
+	/* 0x16 */ NULL,
+	/* 0x17 */ NULL,
+	/* 0x18 */ NULL,
+	/* 0x19 */ NULL,
+	/* 0x1a */ NULL,
+	/* 0x1b */ NULL,
+	/* 0x1c */ NULL,
+	/* 0x1d */ NULL,
+	/* 0x1e */ NULL,
+	/* 0x1f */ NULL,
+};
+
+static const u32 bass_table[41][5] = {
+	{ 0x3e4f844f, 0x84ed4cc3, 0x3cc69927, 0x7b03553a, 0xc4da8486 },
+	{ 0x3e69a17a, 0x84c280fb, 0x3cd77cd4, 0x7b2f2a6f, 0xc4b08d1d },
+	{ 0x3e82ff42, 0x849991d5, 0x3ce7466b, 0x7b5917c6, 0xc48863ee },
+	{ 0x3e9bab3c, 0x847267f0, 0x3cf5ffe8, 0x7b813560, 0xc461f22c },
+	{ 0x3eb3b275, 0x844ced29, 0x3d03b295, 0x7ba79a1c, 0xc43d223b },
+	{ 0x3ecb2174, 0x84290c8b, 0x3d106714, 0x7bcc5ba3, 0xc419dfa5 },
+	{ 0x3ee2044b, 0x8406b244, 0x3d1c2561, 0x7bef8e77, 0xc3f8170f },
+	{ 0x3ef86698, 0x83e5cb96, 0x3d26f4d8, 0x7c114600, 0xc3d7b625 },
+	{ 0x3f0e5390, 0x83c646c9, 0x3d30dc39, 0x7c319498, 0xc3b8ab97 },
+	{ 0x3f23d60b, 0x83a81321, 0x3d39e1af, 0x7c508b9c, 0xc39ae704 },
+	{ 0x3f38f884, 0x838b20d2, 0x3d420ad2, 0x7c6e3b75, 0xc37e58f1 },
+	{ 0x3f4dc52c, 0x836f60ef, 0x3d495cab, 0x7c8ab3a6, 0xc362f2be },
+	{ 0x3f6245e8, 0x8354c565, 0x3d4fdbb8, 0x7ca602d6, 0xc348a69b },
+	{ 0x3f76845f, 0x833b40ec, 0x3d558bf0, 0x7cc036df, 0xc32f677c },
+	{ 0x3f8a8a03, 0x8322c6fb, 0x3d5a70c4, 0x7cd95cd7, 0xc317290b },
+	{ 0x3f9e6014, 0x830b4bc3, 0x3d5e8d25, 0x7cf1811a, 0xc2ffdfa5 },
+	{ 0x3fb20fae, 0x82f4c420, 0x3d61e37f, 0x7d08af56, 0xc2e9804a },
+	{ 0x3fc5a1cc, 0x82df2592, 0x3d6475c3, 0x7d1ef294, 0xc2d40096 },
+	{ 0x3fd91f55, 0x82ca6632, 0x3d664564, 0x7d345541, 0xc2bf56b9 },
+	{ 0x3fec9120, 0x82b67cac, 0x3d675356, 0x7d48e138, 0xc2ab796e },
+	{ 0x40000000, 0x82a36037, 0x3d67a012, 0x7d5c9fc9, 0xc2985fee },
+	{ 0x401374c7, 0x8291088a, 0x3d672b93, 0x7d6f99c3, 0xc28601f2 },
+	{ 0x4026f857, 0x827f6dd7, 0x3d65f559, 0x7d81d77c, 0xc27457a3 },
+	{ 0x403a939f, 0x826e88c5, 0x3d63fc63, 0x7d9360d4, 0xc2635996 },
+	{ 0x404e4faf, 0x825e5266, 0x3d613f32, 0x7da43d42, 0xc25300c6 },
+	{ 0x406235ba, 0x824ec434, 0x3d5dbbc3, 0x7db473d7, 0xc243468e },
+	{ 0x40764f1f, 0x823fd80c, 0x3d596f8f, 0x7dc40b44, 0xc23424a2 },
+	{ 0x408aa576, 0x82318824, 0x3d545787, 0x7dd309e2, 0xc2259509 },
+	{ 0x409f4296, 0x8223cf0b, 0x3d4e7012, 0x7de175b5, 0xc2179218 },
+	{ 0x40b430a0, 0x8216a7a1, 0x3d47b505, 0x7def5475, 0xc20a1670 },
+	{ 0x40c97a0a, 0x820a0d12, 0x3d4021a1, 0x7dfcab8d, 0xc1fd1cf5 },
+	{ 0x40df29a6, 0x81fdfad6, 0x3d37b08d, 0x7e098028, 0xc1f0a0ca },
+	{ 0x40f54ab1, 0x81f26ca9, 0x3d2e5bd1, 0x7e15d72b, 0xc1e49d52 },
+	{ 0x410be8da, 0x81e75e89, 0x3d241cce, 0x7e21b544, 0xc1d90e24 },
+	{ 0x41231051, 0x81dcccb3, 0x3d18ec37, 0x7e2d1ee6, 0xc1cdef10 },
+	{ 0x413acdd0, 0x81d2b39e, 0x3d0cc20a, 0x7e38184e, 0xc1c33c13 },
+	{ 0x41532ea7, 0x81c90ffb, 0x3cff9585, 0x7e42a58b, 0xc1b8f15a },
+	{ 0x416c40cd, 0x81bfdeb2, 0x3cf15d21, 0x7e4cca7c, 0xc1af0b3f },
+	{ 0x418612ea, 0x81b71cdc, 0x3ce20e85, 0x7e568ad3, 0xc1a58640 },
+	{ 0x41a0b465, 0x81aec7c5, 0x3cd19e7c, 0x7e5fea1e, 0xc19c5f03 },
+	{ 0x41bc3573, 0x81a6dcea, 0x3cc000e9, 0x7e68ebc2, 0xc1939250 }
+};
+
+static const u32 treble_table[41][5] = {
+	{ 0x0125cba9, 0xfed5debd, 0x00599b6c, 0x0d2506da, 0xfa85b354 },
+	{ 0x0142f67e, 0xfeb03163, 0x0066cd0f, 0x0d14c69d, 0xfa914473 },
+	{ 0x016328bd, 0xfe860158, 0x0075b7f2, 0x0d03eb27, 0xfa9d32d2 },
+	{ 0x0186b438, 0xfe56c982, 0x00869234, 0x0cf27048, 0xfaa97fca },
+	{ 0x01adf358, 0xfe21f5fe, 0x00999842, 0x0ce051c2, 0xfab62ca5 },
+	{ 0x01d949fa, 0xfde6e287, 0x00af0d8d, 0x0ccd8b4a, 0xfac33aa7 },
+	{ 0x02092669, 0xfda4d8bf, 0x00c73d4c, 0x0cba1884, 0xfad0ab07 },
+	{ 0x023e0268, 0xfd5b0e4a, 0x00e27b54, 0x0ca5f509, 0xfade7ef2 },
+	{ 0x0278645c, 0xfd08a2b0, 0x01012509, 0x0c911c63, 0xfaecb788 },
+	{ 0x02b8e091, 0xfcac9d1a, 0x0123a262, 0x0c7b8a14, 0xfafb55df },
+	{ 0x03001a9a, 0xfc45e9ce, 0x014a6709, 0x0c65398f, 0xfb0a5aff },
+	{ 0x034ec6d7, 0xfbd3576b, 0x0175f397, 0x0c4e2643, 0xfb19c7e4 },
+	{ 0x03a5ac15, 0xfb5393ee, 0x01a6d6ed, 0x0c364b94, 0xfb299d7c },
+	{ 0x0405a562, 0xfac52968, 0x01ddafae, 0x0c1da4e2, 0xfb39dca5 },
+	{ 0x046fa3fe, 0xfa267a66, 0x021b2ddd, 0x0c042d8d, 0xfb4a8631 },
+	{ 0x04e4b17f, 0xf975be0f, 0x0260149f, 0x0be9e0f2, 0xfb5b9ae0 },
+	{ 0x0565f220, 0xf8b0fbe5, 0x02ad3c29, 0x0bceba73, 0xfb6d1b60 },
+	{ 0x05f4a745, 0xf7d60722, 0x030393d4, 0x0bb2b578, 0xfb7f084d },
+	{ 0x06923236, 0xf6e279bd, 0x03642465, 0x0b95cd75, 0xfb916233 },
+	{ 0x07401713, 0xf5d3aef9, 0x03d01283, 0x0b77fded, 0xfba42984 },
+	{ 0x08000000, 0xf4a6bd88, 0x0448a161, 0x0b594278, 0xfbb75e9f },
+	{ 0x08d3c097, 0xf3587131, 0x04cf35a4, 0x0b3996c9, 0xfbcb01cb },
+	{ 0x09bd59a2, 0xf1e543f9, 0x05655880, 0x0b18f6b2, 0xfbdf1333 },
+	{ 0x0abefd0f, 0xf04956ca, 0x060cbb12, 0x0af75e2c, 0xfbf392e8 },
+	{ 0x0bdb123e, 0xee806984, 0x06c739fe, 0x0ad4c962, 0xfc0880dd },
+	{ 0x0d143a94, 0xec85d287, 0x0796e150, 0x0ab134b0, 0xfc1ddce5 },
+	{ 0x0e6d5664, 0xea547598, 0x087df0a0, 0x0a8c9cb6, 0xfc33a6ad },
+	{ 0x0fe98a2a, 0xe7e6ba35, 0x097edf83, 0x0a66fe5b, 0xfc49ddc2 },
+	{ 0x118c4421, 0xe536813a, 0x0a9c6248, 0x0a4056d7, 0xfc608185 },
+	{ 0x1359422e, 0xe23d19eb, 0x0bd96efb, 0x0a18a3bf, 0xfc77912c },
+	{ 0x1554982b, 0xdef33645, 0x0d3942bd, 0x09efe312, 0xfc8f0bc1 },
+	{ 0x1782b68a, 0xdb50deb1, 0x0ebf676d, 0x09c6133f, 0xfca6f019 },
+	{ 0x19e8715d, 0xd74d64fd, 0x106fb999, 0x099b3337, 0xfcbf3cd6 },
+	{ 0x1c8b07b8, 0xd2df56ab, 0x124e6ec8, 0x096f4274, 0xfcd7f060 },
+	{ 0x1f702b6d, 0xcdfc6e92, 0x14601c10, 0x0942410b, 0xfcf108e5 },
+	{ 0x229e0933, 0xc89985cd, 0x16a9bcfa, 0x09142fb5, 0xfd0a8451 },
+	{ 0x261b5118, 0xc2aa8409, 0x1930bab6, 0x08e50fdc, 0xfd24604d },
+	{ 0x29ef3f5d, 0xbc224f28, 0x1bfaf396, 0x08b4e3aa, 0xfd3e9a3b },
+	{ 0x2e21a59b, 0xb4f2ba46, 0x1f0ec2d6, 0x0883ae15, 0xfd592f33 },
+	{ 0x32baf44b, 0xad0c7429, 0x227308a3, 0x085172eb, 0xfd741bfd },
+	{ 0x37c4448b, 0xa45ef51d, 0x262f3267, 0x081e36dc, 0xfd8f5d14 }
+};
+
+/* dB gain = (float) 20 * log10( float(db_table_value) / 0x8000000 ) */
+static const u32 db_table[101] = {
+	0x00000000, 0x01571f82, 0x01674b41, 0x01783a1b, 0x0189f540,
+	0x019c8651, 0x01aff763, 0x01c45306, 0x01d9a446, 0x01eff6b8,
+	0x0207567a, 0x021fd03d, 0x0239714c, 0x02544792, 0x027061a1,
+	0x028dcebb, 0x02ac9edc, 0x02cce2bf, 0x02eeabe8, 0x03120cb0,
+	0x0337184e, 0x035de2df, 0x03868173, 0x03b10a18, 0x03dd93e9,
+	0x040c3713, 0x043d0cea, 0x04702ff3, 0x04a5bbf2, 0x04ddcdfb,
+	0x0518847f, 0x0555ff62, 0x05966005, 0x05d9c95d, 0x06206005,
+	0x066a4a52, 0x06b7b067, 0x0708bc4c, 0x075d9a01, 0x07b6779d,
+	0x08138561, 0x0874f5d5, 0x08dafde1, 0x0945d4ed, 0x09b5b4fd,
+	0x0a2adad1, 0x0aa58605, 0x0b25f936, 0x0bac7a24, 0x0c3951d8,
+	0x0ccccccc, 0x0d673b17, 0x0e08f093, 0x0eb24510, 0x0f639481,
+	0x101d3f2d, 0x10dfa9e6, 0x11ab3e3f, 0x12806ac3, 0x135fa333,
+	0x144960c5, 0x153e2266, 0x163e6cfe, 0x174acbb7, 0x1863d04d,
+	0x198a1357, 0x1abe349f, 0x1c00db77, 0x1d52b712, 0x1eb47ee6,
+	0x2026f30f, 0x21aadcb6, 0x23410e7e, 0x24ea64f9, 0x26a7c71d,
+	0x287a26c4, 0x2a62812c, 0x2c61df84, 0x2e795779, 0x30aa0bcf,
+	0x32f52cfe, 0x355bf9d8, 0x37dfc033, 0x3a81dda4, 0x3d43c038,
+	0x4026e73c, 0x432ce40f, 0x46575af8, 0x49a8040f, 0x4d20ac2a,
+	0x50c335d3, 0x54919a57, 0x588dead1, 0x5cba514a, 0x611911ea,
+	0x65ac8c2f, 0x6a773c39, 0x6f7bbc23, 0x74bcc56c, 0x7a3d3272,
+	0x7fffffff,
+};
+
+/* EMU10k1/EMU10k2 DSP control db gain */
+static const DECLARE_TLV_DB_SCALE(snd_emu10k1_db_scale1, -4000, 40, 1);
+static const DECLARE_TLV_DB_LINEAR(snd_emu10k1_db_linear, TLV_DB_GAIN_MUTE, 0);
+
+static const u32 onoff_table[2] = {
+	0x00000000, 0x00000001
+};
+
+/*
+ */
+ 
+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);
+}
+
+/*
+ *   controls
+ */
+
+static int snd_emu10k1_gpr_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_emu10k1_fx8010_ctl *ctl =
+		(struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value;
+
+	if (ctl->min == 0 && ctl->max == 1)
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	else
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = ctl->vcount;
+	uinfo->value.integer.min = ctl->min;
+	uinfo->value.integer.max = ctl->max;
+	return 0;
+}
+
+static int snd_emu10k1_gpr_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	struct snd_emu10k1_fx8010_ctl *ctl =
+		(struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value;
+	unsigned long flags;
+	unsigned int i;
+	
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (i = 0; i < ctl->vcount; i++)
+		ucontrol->value.integer.value[i] = ctl->value[i];
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return 0;
+}
+
+static int snd_emu10k1_gpr_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	struct snd_emu10k1_fx8010_ctl *ctl =
+		(struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value;
+	unsigned long flags;
+	unsigned int nval, val;
+	unsigned int i, j;
+	int change = 0;
+	
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (i = 0; i < ctl->vcount; i++) {
+		nval = ucontrol->value.integer.value[i];
+		if (nval < ctl->min)
+			nval = ctl->min;
+		if (nval > ctl->max)
+			nval = ctl->max;
+		if (nval != ctl->value[i])
+			change = 1;
+		val = ctl->value[i] = nval;
+		switch (ctl->translation) {
+		case EMU10K1_GPR_TRANSLATION_NONE:
+			snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, val);
+			break;
+		case EMU10K1_GPR_TRANSLATION_TABLE100:
+			snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, db_table[val]);
+			break;
+		case EMU10K1_GPR_TRANSLATION_BASS:
+			if ((ctl->count % 5) != 0 || (ctl->count / 5) != ctl->vcount) {
+				change = -EIO;
+				goto __error;
+			}
+			for (j = 0; j < 5; j++)
+				snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[j * ctl->vcount + i], 0, bass_table[val][j]);
+			break;
+		case EMU10K1_GPR_TRANSLATION_TREBLE:
+			if ((ctl->count % 5) != 0 || (ctl->count / 5) != ctl->vcount) {
+				change = -EIO;
+				goto __error;
+			}
+			for (j = 0; j < 5; j++)
+				snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[j * ctl->vcount + i], 0, treble_table[val][j]);
+			break;
+		case EMU10K1_GPR_TRANSLATION_ONOFF:
+			snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, onoff_table[val]);
+			break;
+		}
+	}
+      __error:
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return change;
+}
+
+/*
+ *   Interrupt handler
+ */
+
+static void snd_emu10k1_fx8010_interrupt(struct snd_emu10k1 *emu)
+{
+	struct snd_emu10k1_fx8010_irq *irq, *nirq;
+
+	irq = emu->fx8010.irq_handlers;
+	while (irq) {
+		nirq = irq->next;	/* irq ptr can be removed from list */
+		if (snd_emu10k1_ptr_read(emu, emu->gpr_base + irq->gpr_running, 0) & 0xffff0000) {
+			if (irq->handler)
+				irq->handler(emu, irq->private_data);
+			snd_emu10k1_ptr_write(emu, emu->gpr_base + irq->gpr_running, 0, 1);
+		}
+		irq = nirq;
+	}
+}
+
+int snd_emu10k1_fx8010_register_irq_handler(struct snd_emu10k1 *emu,
+					    snd_fx8010_irq_handler_t *handler,
+					    unsigned char gpr_running,
+					    void *private_data,
+					    struct snd_emu10k1_fx8010_irq **r_irq)
+{
+	struct snd_emu10k1_fx8010_irq *irq;
+	unsigned long flags;
+	
+	irq = kmalloc(sizeof(*irq), GFP_ATOMIC);
+	if (irq == NULL)
+		return -ENOMEM;
+	irq->handler = handler;
+	irq->gpr_running = gpr_running;
+	irq->private_data = private_data;
+	irq->next = NULL;
+	spin_lock_irqsave(&emu->fx8010.irq_lock, flags);
+	if (emu->fx8010.irq_handlers == NULL) {
+		emu->fx8010.irq_handlers = irq;
+		emu->dsp_interrupt = snd_emu10k1_fx8010_interrupt;
+		snd_emu10k1_intr_enable(emu, INTE_FXDSPENABLE);
+	} else {
+		irq->next = emu->fx8010.irq_handlers;
+		emu->fx8010.irq_handlers = irq;
+	}
+	spin_unlock_irqrestore(&emu->fx8010.irq_lock, flags);
+	if (r_irq)
+		*r_irq = irq;
+	return 0;
+}
+
+int snd_emu10k1_fx8010_unregister_irq_handler(struct snd_emu10k1 *emu,
+					      struct snd_emu10k1_fx8010_irq *irq)
+{
+	struct snd_emu10k1_fx8010_irq *tmp;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&emu->fx8010.irq_lock, flags);
+	if ((tmp = emu->fx8010.irq_handlers) == irq) {
+		emu->fx8010.irq_handlers = tmp->next;
+		if (emu->fx8010.irq_handlers == NULL) {
+			snd_emu10k1_intr_disable(emu, INTE_FXDSPENABLE);
+			emu->dsp_interrupt = NULL;
+		}
+	} else {
+		while (tmp && tmp->next != irq)
+			tmp = tmp->next;
+		if (tmp)
+			tmp->next = tmp->next->next;
+	}
+	spin_unlock_irqrestore(&emu->fx8010.irq_lock, flags);
+	kfree(irq);
+	return 0;
+}
+
+/*************************************************************************
+ * EMU10K1 effect manager
+ *************************************************************************/
+
+static void snd_emu10k1_write_op(struct snd_emu10k1_fx8010_code *icode,
+				 unsigned int *ptr,
+				 u32 op, u32 r, u32 a, u32 x, u32 y)
+{
+	u_int32_t *code;
+	if (snd_BUG_ON(*ptr >= 512))
+		return;
+	code = (u_int32_t __force *)icode->code + (*ptr) * 2;
+	set_bit(*ptr, icode->code_valid);
+	code[0] = ((x & 0x3ff) << 10) | (y & 0x3ff);
+	code[1] = ((op & 0x0f) << 20) | ((r & 0x3ff) << 10) | (a & 0x3ff);
+	(*ptr)++;
+}
+
+#define OP(icode, ptr, op, r, a, x, y) \
+	snd_emu10k1_write_op(icode, ptr, op, r, a, x, y)
+
+static void snd_emu10k1_audigy_write_op(struct snd_emu10k1_fx8010_code *icode,
+					unsigned int *ptr,
+					u32 op, u32 r, u32 a, u32 x, u32 y)
+{
+	u_int32_t *code;
+	if (snd_BUG_ON(*ptr >= 1024))
+		return;
+	code = (u_int32_t __force *)icode->code + (*ptr) * 2;
+	set_bit(*ptr, icode->code_valid);
+	code[0] = ((x & 0x7ff) << 12) | (y & 0x7ff);
+	code[1] = ((op & 0x0f) << 24) | ((r & 0x7ff) << 12) | (a & 0x7ff);
+	(*ptr)++;
+}
+
+#define A_OP(icode, ptr, op, r, a, x, y) \
+	snd_emu10k1_audigy_write_op(icode, ptr, op, r, a, x, y)
+
+static void snd_emu10k1_efx_write(struct snd_emu10k1 *emu, unsigned int pc, unsigned int data)
+{
+	pc += emu->audigy ? A_MICROCODEBASE : MICROCODEBASE;
+	snd_emu10k1_ptr_write(emu, pc, 0, data);
+}
+
+unsigned int snd_emu10k1_efx_read(struct snd_emu10k1 *emu, unsigned int pc)
+{
+	pc += emu->audigy ? A_MICROCODEBASE : MICROCODEBASE;
+	return snd_emu10k1_ptr_read(emu, pc, 0);
+}
+
+static int snd_emu10k1_gpr_poke(struct snd_emu10k1 *emu,
+				struct snd_emu10k1_fx8010_code *icode)
+{
+	int gpr;
+	u32 val;
+
+	for (gpr = 0; gpr < (emu->audigy ? 0x200 : 0x100); gpr++) {
+		if (!test_bit(gpr, icode->gpr_valid))
+			continue;
+		if (get_user(val, &icode->gpr_map[gpr]))
+			return -EFAULT;
+		snd_emu10k1_ptr_write(emu, emu->gpr_base + gpr, 0, val);
+	}
+	return 0;
+}
+
+static int snd_emu10k1_gpr_peek(struct snd_emu10k1 *emu,
+				struct snd_emu10k1_fx8010_code *icode)
+{
+	int gpr;
+	u32 val;
+
+	for (gpr = 0; gpr < (emu->audigy ? 0x200 : 0x100); gpr++) {
+		set_bit(gpr, icode->gpr_valid);
+		val = snd_emu10k1_ptr_read(emu, emu->gpr_base + gpr, 0);
+		if (put_user(val, &icode->gpr_map[gpr]))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+static int snd_emu10k1_tram_poke(struct snd_emu10k1 *emu,
+				 struct snd_emu10k1_fx8010_code *icode)
+{
+	int tram;
+	u32 addr, val;
+
+	for (tram = 0; tram < (emu->audigy ? 0x100 : 0xa0); tram++) {
+		if (!test_bit(tram, icode->tram_valid))
+			continue;
+		if (get_user(val, &icode->tram_data_map[tram]) ||
+		    get_user(addr, &icode->tram_addr_map[tram]))
+			return -EFAULT;
+		snd_emu10k1_ptr_write(emu, TANKMEMDATAREGBASE + tram, 0, val);
+		if (!emu->audigy) {
+			snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + tram, 0, addr);
+		} else {
+			snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + tram, 0, addr << 12);
+			snd_emu10k1_ptr_write(emu, A_TANKMEMCTLREGBASE + tram, 0, addr >> 20);
+		}
+	}
+	return 0;
+}
+
+static int snd_emu10k1_tram_peek(struct snd_emu10k1 *emu,
+				 struct snd_emu10k1_fx8010_code *icode)
+{
+	int tram;
+	u32 val, addr;
+
+	memset(icode->tram_valid, 0, sizeof(icode->tram_valid));
+	for (tram = 0; tram < (emu->audigy ? 0x100 : 0xa0); tram++) {
+		set_bit(tram, icode->tram_valid);
+		val = snd_emu10k1_ptr_read(emu, TANKMEMDATAREGBASE + tram, 0);
+		if (!emu->audigy) {
+			addr = snd_emu10k1_ptr_read(emu, TANKMEMADDRREGBASE + tram, 0);
+		} else {
+			addr = snd_emu10k1_ptr_read(emu, TANKMEMADDRREGBASE + tram, 0) >> 12;
+			addr |= snd_emu10k1_ptr_read(emu, A_TANKMEMCTLREGBASE + tram, 0) << 20;
+		}
+		if (put_user(val, &icode->tram_data_map[tram]) ||
+		    put_user(addr, &icode->tram_addr_map[tram]))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+static int snd_emu10k1_code_poke(struct snd_emu10k1 *emu,
+				 struct snd_emu10k1_fx8010_code *icode)
+{
+	u32 pc, lo, hi;
+
+	for (pc = 0; pc < (emu->audigy ? 2*1024 : 2*512); pc += 2) {
+		if (!test_bit(pc / 2, icode->code_valid))
+			continue;
+		if (get_user(lo, &icode->code[pc + 0]) ||
+		    get_user(hi, &icode->code[pc + 1]))
+			return -EFAULT;
+		snd_emu10k1_efx_write(emu, pc + 0, lo);
+		snd_emu10k1_efx_write(emu, pc + 1, hi);
+	}
+	return 0;
+}
+
+static int snd_emu10k1_code_peek(struct snd_emu10k1 *emu,
+				 struct snd_emu10k1_fx8010_code *icode)
+{
+	u32 pc;
+
+	memset(icode->code_valid, 0, sizeof(icode->code_valid));
+	for (pc = 0; pc < (emu->audigy ? 2*1024 : 2*512); pc += 2) {
+		set_bit(pc / 2, icode->code_valid);
+		if (put_user(snd_emu10k1_efx_read(emu, pc + 0), &icode->code[pc + 0]))
+			return -EFAULT;
+		if (put_user(snd_emu10k1_efx_read(emu, pc + 1), &icode->code[pc + 1]))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+static struct snd_emu10k1_fx8010_ctl *
+snd_emu10k1_look_for_ctl(struct snd_emu10k1 *emu, struct snd_ctl_elem_id *id)
+{
+	struct snd_emu10k1_fx8010_ctl *ctl;
+	struct snd_kcontrol *kcontrol;
+
+	list_for_each_entry(ctl, &emu->fx8010.gpr_ctl, list) {
+		kcontrol = ctl->kcontrol;
+		if (kcontrol->id.iface == id->iface &&
+		    !strcmp(kcontrol->id.name, id->name) &&
+		    kcontrol->id.index == id->index)
+			return ctl;
+	}
+	return NULL;
+}
+
+#define MAX_TLV_SIZE	256
+
+static unsigned int *copy_tlv(const unsigned int __user *_tlv)
+{
+	unsigned int data[2];
+	unsigned int *tlv;
+
+	if (!_tlv)
+		return NULL;
+	if (copy_from_user(data, _tlv, sizeof(data)))
+		return NULL;
+	if (data[1] >= MAX_TLV_SIZE)
+		return NULL;
+	tlv = kmalloc(data[1] + sizeof(data), GFP_KERNEL);
+	if (!tlv)
+		return NULL;
+	memcpy(tlv, data, sizeof(data));
+	if (copy_from_user(tlv + 2, _tlv + 2, data[1])) {
+		kfree(tlv);
+		return NULL;
+	}
+	return tlv;
+}
+
+static int copy_gctl(struct snd_emu10k1 *emu,
+		     struct snd_emu10k1_fx8010_control_gpr *gctl,
+		     struct snd_emu10k1_fx8010_control_gpr __user *_gctl,
+		     int idx)
+{
+	struct snd_emu10k1_fx8010_control_old_gpr __user *octl;
+
+	if (emu->support_tlv)
+		return copy_from_user(gctl, &_gctl[idx], sizeof(*gctl));
+	octl = (struct snd_emu10k1_fx8010_control_old_gpr __user *)_gctl;
+	if (copy_from_user(gctl, &octl[idx], sizeof(*octl)))
+		return -EFAULT;
+	gctl->tlv = NULL;
+	return 0;
+}
+
+static int copy_gctl_to_user(struct snd_emu10k1 *emu,
+		     struct snd_emu10k1_fx8010_control_gpr __user *_gctl,
+		     struct snd_emu10k1_fx8010_control_gpr *gctl,
+		     int idx)
+{
+	struct snd_emu10k1_fx8010_control_old_gpr __user *octl;
+
+	if (emu->support_tlv)
+		return copy_to_user(&_gctl[idx], gctl, sizeof(*gctl));
+	
+	octl = (struct snd_emu10k1_fx8010_control_old_gpr __user *)_gctl;
+	return copy_to_user(&octl[idx], gctl, sizeof(*octl));
+}
+
+static int snd_emu10k1_verify_controls(struct snd_emu10k1 *emu,
+				       struct snd_emu10k1_fx8010_code *icode)
+{
+	unsigned int i;
+	struct snd_ctl_elem_id __user *_id;
+	struct snd_ctl_elem_id id;
+	struct snd_emu10k1_fx8010_control_gpr *gctl;
+	int err;
+	
+	for (i = 0, _id = icode->gpr_del_controls;
+	     i < icode->gpr_del_control_count; i++, _id++) {
+	     	if (copy_from_user(&id, _id, sizeof(id)))
+	     		return -EFAULT;
+		if (snd_emu10k1_look_for_ctl(emu, &id) == NULL)
+			return -ENOENT;
+	}
+	gctl = kmalloc(sizeof(*gctl), GFP_KERNEL);
+	if (! gctl)
+		return -ENOMEM;
+	err = 0;
+	for (i = 0; i < icode->gpr_add_control_count; i++) {
+		if (copy_gctl(emu, gctl, icode->gpr_add_controls, i)) {
+			err = -EFAULT;
+			goto __error;
+		}
+		if (snd_emu10k1_look_for_ctl(emu, &gctl->id))
+			continue;
+		down_read(&emu->card->controls_rwsem);
+		if (snd_ctl_find_id(emu->card, &gctl->id) != NULL) {
+			up_read(&emu->card->controls_rwsem);
+			err = -EEXIST;
+			goto __error;
+		}
+		up_read(&emu->card->controls_rwsem);
+		if (gctl->id.iface != SNDRV_CTL_ELEM_IFACE_MIXER &&
+		    gctl->id.iface != SNDRV_CTL_ELEM_IFACE_PCM) {
+			err = -EINVAL;
+			goto __error;
+		}
+	}
+	for (i = 0; i < icode->gpr_list_control_count; i++) {
+	     	/* FIXME: we need to check the WRITE access */
+		if (copy_gctl(emu, gctl, icode->gpr_list_controls, i)) {
+			err = -EFAULT;
+			goto __error;
+		}
+	}
+ __error:
+	kfree(gctl);
+	return err;
+}
+
+static void snd_emu10k1_ctl_private_free(struct snd_kcontrol *kctl)
+{
+	struct snd_emu10k1_fx8010_ctl *ctl;
+	
+	ctl = (struct snd_emu10k1_fx8010_ctl *) kctl->private_value;
+	kctl->private_value = 0;
+	list_del(&ctl->list);
+	kfree(ctl);
+	if (kctl->tlv.p)
+		kfree(kctl->tlv.p);
+}
+
+static int snd_emu10k1_add_controls(struct snd_emu10k1 *emu,
+				    struct snd_emu10k1_fx8010_code *icode)
+{
+	unsigned int i, j;
+	struct snd_emu10k1_fx8010_control_gpr *gctl;
+	struct snd_emu10k1_fx8010_ctl *ctl, *nctl;
+	struct snd_kcontrol_new knew;
+	struct snd_kcontrol *kctl;
+	struct snd_ctl_elem_value *val;
+	int err = 0;
+
+	val = kmalloc(sizeof(*val), GFP_KERNEL);
+	gctl = kmalloc(sizeof(*gctl), GFP_KERNEL);
+	nctl = kmalloc(sizeof(*nctl), GFP_KERNEL);
+	if (!val || !gctl || !nctl) {
+		err = -ENOMEM;
+		goto __error;
+	}
+
+	for (i = 0; i < icode->gpr_add_control_count; i++) {
+		if (copy_gctl(emu, gctl, icode->gpr_add_controls, i)) {
+			err = -EFAULT;
+			goto __error;
+		}
+		if (gctl->id.iface != SNDRV_CTL_ELEM_IFACE_MIXER &&
+		    gctl->id.iface != SNDRV_CTL_ELEM_IFACE_PCM) {
+			err = -EINVAL;
+			goto __error;
+		}
+		if (! gctl->id.name[0]) {
+			err = -EINVAL;
+			goto __error;
+		}
+		ctl = snd_emu10k1_look_for_ctl(emu, &gctl->id);
+		memset(&knew, 0, sizeof(knew));
+		knew.iface = gctl->id.iface;
+		knew.name = gctl->id.name;
+		knew.index = gctl->id.index;
+		knew.device = gctl->id.device;
+		knew.subdevice = gctl->id.subdevice;
+		knew.info = snd_emu10k1_gpr_ctl_info;
+		knew.tlv.p = copy_tlv(gctl->tlv);
+		if (knew.tlv.p)
+			knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+		knew.get = snd_emu10k1_gpr_ctl_get;
+		knew.put = snd_emu10k1_gpr_ctl_put;
+		memset(nctl, 0, sizeof(*nctl));
+		nctl->vcount = gctl->vcount;
+		nctl->count = gctl->count;
+		for (j = 0; j < 32; j++) {
+			nctl->gpr[j] = gctl->gpr[j];
+			nctl->value[j] = ~gctl->value[j];	/* inverted, we want to write new value in gpr_ctl_put() */
+			val->value.integer.value[j] = gctl->value[j];
+		}
+		nctl->min = gctl->min;
+		nctl->max = gctl->max;
+		nctl->translation = gctl->translation;
+		if (ctl == NULL) {
+			ctl = kmalloc(sizeof(*ctl), GFP_KERNEL);
+			if (ctl == NULL) {
+				err = -ENOMEM;
+				kfree(knew.tlv.p);
+				goto __error;
+			}
+			knew.private_value = (unsigned long)ctl;
+			*ctl = *nctl;
+			if ((err = snd_ctl_add(emu->card, kctl = snd_ctl_new1(&knew, emu))) < 0) {
+				kfree(ctl);
+				kfree(knew.tlv.p);
+				goto __error;
+			}
+			kctl->private_free = snd_emu10k1_ctl_private_free;
+			ctl->kcontrol = kctl;
+			list_add_tail(&ctl->list, &emu->fx8010.gpr_ctl);
+		} else {
+			/* overwrite */
+			nctl->list = ctl->list;
+			nctl->kcontrol = ctl->kcontrol;
+			*ctl = *nctl;
+			snd_ctl_notify(emu->card, SNDRV_CTL_EVENT_MASK_VALUE |
+			                          SNDRV_CTL_EVENT_MASK_INFO, &ctl->kcontrol->id);
+		}
+		snd_emu10k1_gpr_ctl_put(ctl->kcontrol, val);
+	}
+      __error:
+	kfree(nctl);
+	kfree(gctl);
+	kfree(val);
+	return err;
+}
+
+static int snd_emu10k1_del_controls(struct snd_emu10k1 *emu,
+				    struct snd_emu10k1_fx8010_code *icode)
+{
+	unsigned int i;
+	struct snd_ctl_elem_id id;
+	struct snd_ctl_elem_id __user *_id;
+	struct snd_emu10k1_fx8010_ctl *ctl;
+	struct snd_card *card = emu->card;
+	
+	for (i = 0, _id = icode->gpr_del_controls;
+	     i < icode->gpr_del_control_count; i++, _id++) {
+	     	if (copy_from_user(&id, _id, sizeof(id)))
+			return -EFAULT;
+		down_write(&card->controls_rwsem);
+		ctl = snd_emu10k1_look_for_ctl(emu, &id);
+		if (ctl)
+			snd_ctl_remove(card, ctl->kcontrol);
+		up_write(&card->controls_rwsem);
+	}
+	return 0;
+}
+
+static int snd_emu10k1_list_controls(struct snd_emu10k1 *emu,
+				     struct snd_emu10k1_fx8010_code *icode)
+{
+	unsigned int i = 0, j;
+	unsigned int total = 0;
+	struct snd_emu10k1_fx8010_control_gpr *gctl;
+	struct snd_emu10k1_fx8010_ctl *ctl;
+	struct snd_ctl_elem_id *id;
+
+	gctl = kmalloc(sizeof(*gctl), GFP_KERNEL);
+	if (! gctl)
+		return -ENOMEM;
+
+	list_for_each_entry(ctl, &emu->fx8010.gpr_ctl, list) {
+		total++;
+		if (icode->gpr_list_controls &&
+		    i < icode->gpr_list_control_count) {
+			memset(gctl, 0, sizeof(*gctl));
+			id = &ctl->kcontrol->id;
+			gctl->id.iface = id->iface;
+			strlcpy(gctl->id.name, id->name, sizeof(gctl->id.name));
+			gctl->id.index = id->index;
+			gctl->id.device = id->device;
+			gctl->id.subdevice = id->subdevice;
+			gctl->vcount = ctl->vcount;
+			gctl->count = ctl->count;
+			for (j = 0; j < 32; j++) {
+				gctl->gpr[j] = ctl->gpr[j];
+				gctl->value[j] = ctl->value[j];
+			}
+			gctl->min = ctl->min;
+			gctl->max = ctl->max;
+			gctl->translation = ctl->translation;
+			if (copy_gctl_to_user(emu, icode->gpr_list_controls,
+					      gctl, i)) {
+				kfree(gctl);
+				return -EFAULT;
+			}
+			i++;
+		}
+	}
+	icode->gpr_list_control_total = total;
+	kfree(gctl);
+	return 0;
+}
+
+static int snd_emu10k1_icode_poke(struct snd_emu10k1 *emu,
+				  struct snd_emu10k1_fx8010_code *icode)
+{
+	int err = 0;
+
+	mutex_lock(&emu->fx8010.lock);
+	if ((err = snd_emu10k1_verify_controls(emu, icode)) < 0)
+		goto __error;
+	strlcpy(emu->fx8010.name, icode->name, sizeof(emu->fx8010.name));
+	/* stop FX processor - this may be dangerous, but it's better to miss
+	   some samples than generate wrong ones - [jk] */
+	if (emu->audigy)
+		snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg | A_DBG_SINGLE_STEP);
+	else
+		snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg | EMU10K1_DBG_SINGLE_STEP);
+	/* ok, do the main job */
+	if ((err = snd_emu10k1_del_controls(emu, icode)) < 0 ||
+	    (err = snd_emu10k1_gpr_poke(emu, icode)) < 0 ||
+	    (err = snd_emu10k1_tram_poke(emu, icode)) < 0 ||
+	    (err = snd_emu10k1_code_poke(emu, icode)) < 0 ||
+	    (err = snd_emu10k1_add_controls(emu, icode)) < 0)
+		goto __error;
+	/* start FX processor when the DSP code is updated */
+	if (emu->audigy)
+		snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg);
+	else
+		snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg);
+      __error:
+	mutex_unlock(&emu->fx8010.lock);
+	return err;
+}
+
+static int snd_emu10k1_icode_peek(struct snd_emu10k1 *emu,
+				  struct snd_emu10k1_fx8010_code *icode)
+{
+	int err;
+
+	mutex_lock(&emu->fx8010.lock);
+	strlcpy(icode->name, emu->fx8010.name, sizeof(icode->name));
+	/* ok, do the main job */
+	err = snd_emu10k1_gpr_peek(emu, icode);
+	if (err >= 0)
+		err = snd_emu10k1_tram_peek(emu, icode);
+	if (err >= 0)
+		err = snd_emu10k1_code_peek(emu, icode);
+	if (err >= 0)
+		err = snd_emu10k1_list_controls(emu, icode);
+	mutex_unlock(&emu->fx8010.lock);
+	return err;
+}
+
+static int snd_emu10k1_ipcm_poke(struct snd_emu10k1 *emu,
+				 struct snd_emu10k1_fx8010_pcm_rec *ipcm)
+{
+	unsigned int i;
+	int err = 0;
+	struct snd_emu10k1_fx8010_pcm *pcm;
+
+	if (ipcm->substream >= EMU10K1_FX8010_PCM_COUNT)
+		return -EINVAL;
+	if (ipcm->channels > 32)
+		return -EINVAL;
+	pcm = &emu->fx8010.pcm[ipcm->substream];
+	mutex_lock(&emu->fx8010.lock);
+	spin_lock_irq(&emu->reg_lock);
+	if (pcm->opened) {
+		err = -EBUSY;
+		goto __error;
+	}
+	if (ipcm->channels == 0) {	/* remove */
+		pcm->valid = 0;
+	} else {
+		/* FIXME: we need to add universal code to the PCM transfer routine */
+		if (ipcm->channels != 2) {
+			err = -EINVAL;
+			goto __error;
+		}
+		pcm->valid = 1;
+		pcm->opened = 0;
+		pcm->channels = ipcm->channels;
+		pcm->tram_start = ipcm->tram_start;
+		pcm->buffer_size = ipcm->buffer_size;
+		pcm->gpr_size = ipcm->gpr_size;
+		pcm->gpr_count = ipcm->gpr_count;
+		pcm->gpr_tmpcount = ipcm->gpr_tmpcount;
+		pcm->gpr_ptr = ipcm->gpr_ptr;
+		pcm->gpr_trigger = ipcm->gpr_trigger;
+		pcm->gpr_running = ipcm->gpr_running;
+		for (i = 0; i < pcm->channels; i++)
+			pcm->etram[i] = ipcm->etram[i];
+	}
+      __error:
+	spin_unlock_irq(&emu->reg_lock);
+	mutex_unlock(&emu->fx8010.lock);
+	return err;
+}
+
+static int snd_emu10k1_ipcm_peek(struct snd_emu10k1 *emu,
+				 struct snd_emu10k1_fx8010_pcm_rec *ipcm)
+{
+	unsigned int i;
+	int err = 0;
+	struct snd_emu10k1_fx8010_pcm *pcm;
+
+	if (ipcm->substream >= EMU10K1_FX8010_PCM_COUNT)
+		return -EINVAL;
+	pcm = &emu->fx8010.pcm[ipcm->substream];
+	mutex_lock(&emu->fx8010.lock);
+	spin_lock_irq(&emu->reg_lock);
+	ipcm->channels = pcm->channels;
+	ipcm->tram_start = pcm->tram_start;
+	ipcm->buffer_size = pcm->buffer_size;
+	ipcm->gpr_size = pcm->gpr_size;
+	ipcm->gpr_ptr = pcm->gpr_ptr;
+	ipcm->gpr_count = pcm->gpr_count;
+	ipcm->gpr_tmpcount = pcm->gpr_tmpcount;
+	ipcm->gpr_trigger = pcm->gpr_trigger;
+	ipcm->gpr_running = pcm->gpr_running;
+	for (i = 0; i < pcm->channels; i++)
+		ipcm->etram[i] = pcm->etram[i];
+	ipcm->res1 = ipcm->res2 = 0;
+	ipcm->pad = 0;
+	spin_unlock_irq(&emu->reg_lock);
+	mutex_unlock(&emu->fx8010.lock);
+	return err;
+}
+
+#define SND_EMU10K1_GPR_CONTROLS	44
+#define SND_EMU10K1_INPUTS		12
+#define SND_EMU10K1_PLAYBACK_CHANNELS	8
+#define SND_EMU10K1_CAPTURE_CHANNELS	4
+
+static void __devinit
+snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl,
+			      const char *name, int gpr, int defval)
+{
+	ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(ctl->id.name, name);
+	ctl->vcount = ctl->count = 1;
+	ctl->gpr[0] = gpr + 0; ctl->value[0] = defval;
+	if (high_res_gpr_volume) {
+		ctl->min = 0;
+		ctl->max = 0x7fffffff;
+		ctl->tlv = snd_emu10k1_db_linear;
+		ctl->translation = EMU10K1_GPR_TRANSLATION_NONE;
+	} else {
+		ctl->min = 0;
+		ctl->max = 100;
+		ctl->tlv = snd_emu10k1_db_scale1;
+		ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100;
+	}
+}
+
+static void __devinit
+snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl,
+				const char *name, int gpr, int defval)
+{
+	ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(ctl->id.name, name);
+	ctl->vcount = ctl->count = 2;
+	ctl->gpr[0] = gpr + 0; ctl->value[0] = defval;
+	ctl->gpr[1] = gpr + 1; ctl->value[1] = defval;
+	if (high_res_gpr_volume) {
+		ctl->min = 0;
+		ctl->max = 0x7fffffff;
+		ctl->tlv = snd_emu10k1_db_linear;
+		ctl->translation = EMU10K1_GPR_TRANSLATION_NONE;
+	} else {
+		ctl->min = 0;
+		ctl->max = 100;
+		ctl->tlv = snd_emu10k1_db_scale1;
+		ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100;
+	}
+}
+
+static void __devinit
+snd_emu10k1_init_mono_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl,
+				    const char *name, int gpr, int defval)
+{
+	ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(ctl->id.name, name);
+	ctl->vcount = ctl->count = 1;
+	ctl->gpr[0] = gpr + 0; ctl->value[0] = defval;
+	ctl->min = 0;
+	ctl->max = 1;
+	ctl->translation = EMU10K1_GPR_TRANSLATION_ONOFF;
+}
+
+static void __devinit
+snd_emu10k1_init_stereo_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl,
+				      const char *name, int gpr, int defval)
+{
+	ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(ctl->id.name, name);
+	ctl->vcount = ctl->count = 2;
+	ctl->gpr[0] = gpr + 0; ctl->value[0] = defval;
+	ctl->gpr[1] = gpr + 1; ctl->value[1] = defval;
+	ctl->min = 0;
+	ctl->max = 1;
+	ctl->translation = EMU10K1_GPR_TRANSLATION_ONOFF;
+}
+
+/*
+ * Used for emu1010 - conversion from 32-bit capture inputs from HANA
+ * to 2 x 16-bit registers in audigy - their values are read via DMA.
+ * Conversion is performed by Audigy DSP instructions of FX8010.
+ */
+static int snd_emu10k1_audigy_dsp_convert_32_to_2x16(
+				struct snd_emu10k1_fx8010_code *icode,
+				u32 *ptr, int tmp, int bit_shifter16,
+				int reg_in, int reg_out)
+{
+	A_OP(icode, ptr, iACC3, A_GPR(tmp + 1), reg_in, A_C_00000000, A_C_00000000);
+	A_OP(icode, ptr, iANDXOR, A_GPR(tmp), A_GPR(tmp + 1), A_GPR(bit_shifter16 - 1), A_C_00000000);
+	A_OP(icode, ptr, iTSTNEG, A_GPR(tmp + 2), A_GPR(tmp), A_C_80000000, A_GPR(bit_shifter16 - 2));
+	A_OP(icode, ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_C_80000000, A_C_00000000);
+	A_OP(icode, ptr, iANDXOR, A_GPR(tmp), A_GPR(tmp), A_GPR(bit_shifter16 - 3), A_C_00000000);
+	A_OP(icode, ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A_GPR(tmp), A_C_00010000);
+	A_OP(icode, ptr, iANDXOR, reg_out, A_GPR(tmp), A_C_ffffffff, A_GPR(tmp + 2));
+	A_OP(icode, ptr, iACC3, reg_out + 1, A_GPR(tmp + 1), A_C_00000000, A_C_00000000);
+	return 1;
+}
+
+/*
+ * initial DSP configuration for Audigy
+ */
+
+static int __devinit _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu)
+{
+	int err, i, z, gpr, nctl;
+	int bit_shifter16;
+	const int playback = 10;
+	const int capture = playback + (SND_EMU10K1_PLAYBACK_CHANNELS * 2); /* we reserve 10 voices */
+	const int stereo_mix = capture + 2;
+	const int tmp = 0x88;
+	u32 ptr;
+	struct snd_emu10k1_fx8010_code *icode = NULL;
+	struct snd_emu10k1_fx8010_control_gpr *controls = NULL, *ctl;
+	u32 *gpr_map;
+	mm_segment_t seg;
+
+	if ((icode = kzalloc(sizeof(*icode), GFP_KERNEL)) == NULL ||
+	    (icode->gpr_map = (u_int32_t __user *)
+	     kcalloc(512 + 256 + 256 + 2 * 1024, sizeof(u_int32_t),
+		     GFP_KERNEL)) == NULL ||
+	    (controls = kcalloc(SND_EMU10K1_GPR_CONTROLS,
+				sizeof(*controls), GFP_KERNEL)) == NULL) {
+		err = -ENOMEM;
+		goto __err;
+	}
+	gpr_map = (u32 __force *)icode->gpr_map;
+
+	icode->tram_data_map = icode->gpr_map + 512;
+	icode->tram_addr_map = icode->tram_data_map + 256;
+	icode->code = icode->tram_addr_map + 256;
+
+	/* clear free GPRs */
+	for (i = 0; i < 512; i++)
+		set_bit(i, icode->gpr_valid);
+		
+	/* clear TRAM data & address lines */
+	for (i = 0; i < 256; i++)
+		set_bit(i, icode->tram_valid);
+
+	strcpy(icode->name, "Audigy DSP code for ALSA");
+	ptr = 0;
+	nctl = 0;
+	gpr = stereo_mix + 10;
+	gpr_map[gpr++] = 0x00007fff;
+	gpr_map[gpr++] = 0x00008000;
+	gpr_map[gpr++] = 0x0000ffff;
+	bit_shifter16 = gpr;
+
+	/* stop FX processor */
+	snd_emu10k1_ptr_write(emu, A_DBG, 0, (emu->fx8010.dbg = 0) | A_DBG_SINGLE_STEP);
+
+#if 1
+	/* PCM front Playback Volume (independent from stereo mix)
+	 * playback = 0 + ( gpr * FXBUS_PCM_LEFT_FRONT >> 31)
+	 * where gpr contains attenuation from corresponding mixer control
+	 * (snd_emu10k1_init_stereo_control)
+	 */
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_FRONT));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_FRONT));
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Front Playback Volume", gpr, 100);
+	gpr += 2;
+
+	/* PCM Surround Playback (independent from stereo mix) */
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+2), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_REAR));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+3), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_REAR));
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Surround Playback Volume", gpr, 100);
+	gpr += 2;
+	
+	/* PCM Side Playback (independent from stereo mix) */
+	if (emu->card_capabilities->spk71) {
+		A_OP(icode, &ptr, iMAC0, A_GPR(playback+6), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_SIDE));
+		A_OP(icode, &ptr, iMAC0, A_GPR(playback+7), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_SIDE));
+		snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Side Playback Volume", gpr, 100);
+		gpr += 2;
+	}
+
+	/* PCM Center Playback (independent from stereo mix) */
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+4), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_CENTER));
+	snd_emu10k1_init_mono_control(&controls[nctl++], "PCM Center Playback Volume", gpr, 100);
+	gpr++;
+
+	/* PCM LFE Playback (independent from stereo mix) */
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+5), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LFE));
+	snd_emu10k1_init_mono_control(&controls[nctl++], "PCM LFE Playback Volume", gpr, 100);
+	gpr++;
+	
+	/*
+	 * Stereo Mix
+	 */
+	/* Wave (PCM) Playback Volume (will be renamed later) */
+	A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT));
+	A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT));
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "Wave Playback Volume", gpr, 100);
+	gpr += 2;
+
+	/* Synth Playback */
+	A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+0), A_GPR(stereo_mix+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT));
+	A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+1), A_GPR(stereo_mix+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT));
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Playback Volume", gpr, 100);
+	gpr += 2;
+
+	/* Wave (PCM) Capture */
+	A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT));
+	A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT));
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Capture Volume", gpr, 0);
+	gpr += 2;
+
+	/* Synth Capture */
+	A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT));
+	A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT));
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Capture Volume", gpr, 0);
+	gpr += 2;
+      
+	/*
+	 * inputs
+	 */
+#define A_ADD_VOLUME_IN(var,vol,input) \
+A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input))
+
+	/* emu1212 DSP 0 and DSP 1 Capture */
+	if (emu->card_capabilities->emu_model) {
+		if (emu->card_capabilities->ca0108_chip) {
+			/* Note:JCD:No longer bit shift lower 16bits to upper 16bits of 32bit value. */
+			A_OP(icode, &ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A3_EMU32IN(0x0), A_C_00000001);
+			A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_GPR(tmp));
+			A_OP(icode, &ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A3_EMU32IN(0x1), A_C_00000001);
+			A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr), A_GPR(tmp));
+		} else {
+			A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_P16VIN(0x0));
+			A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_P16VIN(0x1));
+		}
+		snd_emu10k1_init_stereo_control(&controls[nctl++], "EMU Capture Volume", gpr, 0);
+		gpr += 2;
+	}
+	/* AC'97 Playback Volume - used only for mic (renamed later) */
+	A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L);
+	A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AC97_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "AMic Playback Volume", gpr, 0);
+	gpr += 2;
+	/* AC'97 Capture Volume - used only for mic */
+	A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AC97_L);
+	A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AC97_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "Mic Capture Volume", gpr, 0);
+	gpr += 2;
+
+	/* mic capture buffer */	
+	A_OP(icode, &ptr, iINTERP, A_EXTOUT(A_EXTOUT_MIC_CAP), A_EXTIN(A_EXTIN_AC97_L), 0xcd, A_EXTIN(A_EXTIN_AC97_R));
+
+	/* Audigy CD Playback Volume */
+	A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_SPDIF_CD_L);
+	A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_SPDIF_CD_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++],
+					emu->card_capabilities->ac97_chip ? "Audigy CD Playback Volume" : "CD Playback Volume",
+					gpr, 0);
+	gpr += 2;
+	/* Audigy CD Capture Volume */
+	A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_SPDIF_CD_L);
+	A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_SPDIF_CD_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++],
+					emu->card_capabilities->ac97_chip ? "Audigy CD Capture Volume" : "CD Capture Volume",
+					gpr, 0);
+	gpr += 2;
+
+ 	/* Optical SPDIF Playback Volume */
+	A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_OPT_SPDIF_L);
+	A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_OPT_SPDIF_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",PLAYBACK,VOLUME), gpr, 0);
+	gpr += 2;
+	/* Optical SPDIF Capture Volume */
+	A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_OPT_SPDIF_L);
+	A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_OPT_SPDIF_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",CAPTURE,VOLUME), gpr, 0);
+	gpr += 2;
+
+	/* Line2 Playback Volume */
+	A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_LINE2_L);
+	A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_LINE2_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++],
+					emu->card_capabilities->ac97_chip ? "Line2 Playback Volume" : "Line Playback Volume",
+					gpr, 0);
+	gpr += 2;
+	/* Line2 Capture Volume */
+	A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_LINE2_L);
+	A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_LINE2_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++],
+					emu->card_capabilities->ac97_chip ? "Line2 Capture Volume" : "Line Capture Volume",
+					gpr, 0);
+	gpr += 2;
+        
+	/* Philips ADC Playback Volume */
+	A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_ADC_L);
+	A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_ADC_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Playback Volume", gpr, 0);
+	gpr += 2;
+	/* Philips ADC Capture Volume */
+	A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_ADC_L);
+	A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_ADC_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Capture Volume", gpr, 0);
+	gpr += 2;
+
+	/* Aux2 Playback Volume */
+	A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AUX2_L);
+	A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AUX2_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++],
+					emu->card_capabilities->ac97_chip ? "Aux2 Playback Volume" : "Aux Playback Volume",
+					gpr, 0);
+	gpr += 2;
+	/* Aux2 Capture Volume */
+	A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AUX2_L);
+	A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AUX2_R);
+	snd_emu10k1_init_stereo_control(&controls[nctl++],
+					emu->card_capabilities->ac97_chip ? "Aux2 Capture Volume" : "Aux Capture Volume",
+					gpr, 0);
+	gpr += 2;
+	
+	/* Stereo Mix Front Playback Volume */
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_GPR(playback), A_GPR(gpr), A_GPR(stereo_mix));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+1), A_GPR(playback+1), A_GPR(gpr+1), A_GPR(stereo_mix+1));
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "Front Playback Volume", gpr, 100);
+	gpr += 2;
+	
+	/* Stereo Mix Surround Playback */
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+2), A_GPR(playback+2), A_GPR(gpr), A_GPR(stereo_mix));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+3), A_GPR(playback+3), A_GPR(gpr+1), A_GPR(stereo_mix+1));
+	snd_emu10k1_init_stereo_control(&controls[nctl++], "Surround Playback Volume", gpr, 0);
+	gpr += 2;
+
+	/* Stereo Mix Center Playback */
+	/* Center = sub = Left/2 + Right/2 */
+	A_OP(icode, &ptr, iINTERP, A_GPR(tmp), A_GPR(stereo_mix), 0xcd, A_GPR(stereo_mix+1));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+4), A_GPR(playback+4), A_GPR(gpr), A_GPR(tmp));
+	snd_emu10k1_init_mono_control(&controls[nctl++], "Center Playback Volume", gpr, 0);
+	gpr++;
+
+	/* Stereo Mix LFE Playback */
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+5), A_GPR(playback+5), A_GPR(gpr), A_GPR(tmp));
+	snd_emu10k1_init_mono_control(&controls[nctl++], "LFE Playback Volume", gpr, 0);
+	gpr++;
+	
+	if (emu->card_capabilities->spk71) {
+		/* Stereo Mix Side Playback */
+		A_OP(icode, &ptr, iMAC0, A_GPR(playback+6), A_GPR(playback+6), A_GPR(gpr), A_GPR(stereo_mix));
+		A_OP(icode, &ptr, iMAC0, A_GPR(playback+7), A_GPR(playback+7), A_GPR(gpr+1), A_GPR(stereo_mix+1));
+		snd_emu10k1_init_stereo_control(&controls[nctl++], "Side Playback Volume", gpr, 0);
+		gpr += 2;
+	}
+
+	/*
+	 * outputs
+	 */
+#define A_PUT_OUTPUT(out,src) A_OP(icode, &ptr, iACC3, A_EXTOUT(out), A_C_00000000, A_C_00000000, A_GPR(src))
+#define A_PUT_STEREO_OUTPUT(out1,out2,src) \
+	{A_PUT_OUTPUT(out1,src); A_PUT_OUTPUT(out2,src+1);}
+
+#define _A_SWITCH(icode, ptr, dst, src, sw) \
+	A_OP((icode), ptr, iMACINT0, dst, A_C_00000000, src, sw);
+#define A_SWITCH(icode, ptr, dst, src, sw) \
+		_A_SWITCH(icode, ptr, A_GPR(dst), A_GPR(src), A_GPR(sw))
+#define _A_SWITCH_NEG(icode, ptr, dst, src) \
+	A_OP((icode), ptr, iANDXOR, dst, src, A_C_00000001, A_C_00000001);
+#define A_SWITCH_NEG(icode, ptr, dst, src) \
+		_A_SWITCH_NEG(icode, ptr, A_GPR(dst), A_GPR(src))
+
+
+	/*
+	 *  Process tone control
+	 */
+	A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), A_GPR(playback + 0), A_C_00000000, A_C_00000000); /* left */
+	A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), A_GPR(playback + 1), A_C_00000000, A_C_00000000); /* right */
+	A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2), A_GPR(playback + 2), A_C_00000000, A_C_00000000); /* rear left */
+	A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 3), A_GPR(playback + 3), A_C_00000000, A_C_00000000); /* rear right */
+	A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), A_GPR(playback + 4), A_C_00000000, A_C_00000000); /* center */
+	A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), A_GPR(playback + 5), A_C_00000000, A_C_00000000); /* LFE */
+	if (emu->card_capabilities->spk71) {
+		A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 6), A_GPR(playback + 6), A_C_00000000, A_C_00000000); /* side left */
+		A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 7), A_GPR(playback + 7), A_C_00000000, A_C_00000000); /* side right */
+	}
+	
+
+	ctl = &controls[nctl + 0];
+	ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(ctl->id.name, "Tone Control - Bass");
+	ctl->vcount = 2;
+	ctl->count = 10;
+	ctl->min = 0;
+	ctl->max = 40;
+	ctl->value[0] = ctl->value[1] = 20;
+	ctl->translation = EMU10K1_GPR_TRANSLATION_BASS;
+	ctl = &controls[nctl + 1];
+	ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(ctl->id.name, "Tone Control - Treble");
+	ctl->vcount = 2;
+	ctl->count = 10;
+	ctl->min = 0;
+	ctl->max = 40;
+	ctl->value[0] = ctl->value[1] = 20;
+	ctl->translation = EMU10K1_GPR_TRANSLATION_TREBLE;
+
+#define BASS_GPR	0x8c
+#define TREBLE_GPR	0x96
+
+	for (z = 0; z < 5; z++) {
+		int j;
+		for (j = 0; j < 2; j++) {
+			controls[nctl + 0].gpr[z * 2 + j] = BASS_GPR + z * 2 + j;
+			controls[nctl + 1].gpr[z * 2 + j] = TREBLE_GPR + z * 2 + j;
+		}
+	}
+	for (z = 0; z < 4; z++) {		/* front/rear/center-lfe/side */
+		int j, k, l, d;
+		for (j = 0; j < 2; j++) {	/* left/right */
+			k = 0xb0 + (z * 8) + (j * 4);
+			l = 0xe0 + (z * 8) + (j * 4);
+			d = playback + SND_EMU10K1_PLAYBACK_CHANNELS + z * 2 + j;
+
+			A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(d), A_GPR(BASS_GPR + 0 + j));
+			A_OP(icode, &ptr, iMACMV, A_GPR(k+1), A_GPR(k), A_GPR(k+1), A_GPR(BASS_GPR + 4 + j));
+			A_OP(icode, &ptr, iMACMV, A_GPR(k), A_GPR(d), A_GPR(k), A_GPR(BASS_GPR + 2 + j));
+			A_OP(icode, &ptr, iMACMV, A_GPR(k+3), A_GPR(k+2), A_GPR(k+3), A_GPR(BASS_GPR + 8 + j));
+			A_OP(icode, &ptr, iMAC0, A_GPR(k+2), A_GPR_ACCU, A_GPR(k+2), A_GPR(BASS_GPR + 6 + j));
+			A_OP(icode, &ptr, iACC3, A_GPR(k+2), A_GPR(k+2), A_GPR(k+2), A_C_00000000);
+
+			A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(k+2), A_GPR(TREBLE_GPR + 0 + j));
+			A_OP(icode, &ptr, iMACMV, A_GPR(l+1), A_GPR(l), A_GPR(l+1), A_GPR(TREBLE_GPR + 4 + j));
+			A_OP(icode, &ptr, iMACMV, A_GPR(l), A_GPR(k+2), A_GPR(l), A_GPR(TREBLE_GPR + 2 + j));
+			A_OP(icode, &ptr, iMACMV, A_GPR(l+3), A_GPR(l+2), A_GPR(l+3), A_GPR(TREBLE_GPR + 8 + j));
+			A_OP(icode, &ptr, iMAC0, A_GPR(l+2), A_GPR_ACCU, A_GPR(l+2), A_GPR(TREBLE_GPR + 6 + j));
+			A_OP(icode, &ptr, iMACINT0, A_GPR(l+2), A_C_00000000, A_GPR(l+2), A_C_00000010);
+
+			A_OP(icode, &ptr, iACC3, A_GPR(d), A_GPR(l+2), A_C_00000000, A_C_00000000);
+
+			if (z == 2)	/* center */
+				break;
+		}
+	}
+	nctl += 2;
+
+#undef BASS_GPR
+#undef TREBLE_GPR
+
+	for (z = 0; z < 8; z++) {
+		A_SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr + 0);
+		A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 0);
+		A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1);
+		A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+	}
+	snd_emu10k1_init_stereo_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0);
+	gpr += 2;
+
+	/* Master volume (will be renamed later) */
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+1+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+1+SND_EMU10K1_PLAYBACK_CHANNELS));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+2+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+2+SND_EMU10K1_PLAYBACK_CHANNELS));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+3+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+3+SND_EMU10K1_PLAYBACK_CHANNELS));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+4+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+4+SND_EMU10K1_PLAYBACK_CHANNELS));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+5+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+5+SND_EMU10K1_PLAYBACK_CHANNELS));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+6+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+6+SND_EMU10K1_PLAYBACK_CHANNELS));
+	A_OP(icode, &ptr, iMAC0, A_GPR(playback+7+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+7+SND_EMU10K1_PLAYBACK_CHANNELS));
+	snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0);
+	gpr += 2;
+
+	/* analog speakers */
+	A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS);
+	A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2 + SND_EMU10K1_PLAYBACK_CHANNELS);
+	A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4 + SND_EMU10K1_PLAYBACK_CHANNELS);
+	A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5 + SND_EMU10K1_PLAYBACK_CHANNELS);
+	if (emu->card_capabilities->spk71)
+		A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6 + SND_EMU10K1_PLAYBACK_CHANNELS);
+
+	/* headphone */
+	A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS);
+
+	/* digital outputs */
+	/* A_PUT_STEREO_OUTPUT(A_EXTOUT_FRONT_L, A_EXTOUT_FRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); */
+	if (emu->card_capabilities->emu_model) {
+		/* EMU1010 Outputs from PCM Front, Rear, Center, LFE, Side */
+		snd_printk(KERN_INFO "EMU outputs on\n");
+		for (z = 0; z < 8; z++) {
+			if (emu->card_capabilities->ca0108_chip) {
+				A_OP(icode, &ptr, iACC3, A3_EMU32OUT(z), A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_C_00000000, A_C_00000000);
+			} else {
+				A_OP(icode, &ptr, iACC3, A_EMU32OUTL(z), A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_C_00000000, A_C_00000000);
+			}
+		}
+	}
+
+	/* IEC958 Optical Raw Playback Switch */ 
+	gpr_map[gpr++] = 0;
+	gpr_map[gpr++] = 0x1008;
+	gpr_map[gpr++] = 0xffff0000;
+	for (z = 0; z < 2; z++) {
+		A_OP(icode, &ptr, iMAC0, A_GPR(tmp + 2), A_FXBUS(FXBUS_PT_LEFT + z), A_C_00000000, A_C_00000000);
+		A_OP(icode, &ptr, iSKIP, A_GPR_COND, A_GPR_COND, A_GPR(gpr - 2), A_C_00000001);
+		A_OP(icode, &ptr, iACC3, A_GPR(tmp + 2), A_C_00000000, A_C_00010000, A_GPR(tmp + 2));
+		A_OP(icode, &ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_GPR(gpr - 1), A_C_00000000);
+		A_SWITCH(icode, &ptr, tmp + 0, tmp + 2, gpr + z);
+		A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z);
+		A_SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1);
+		if ((z==1) && (emu->card_capabilities->spdif_bug)) {
+			/* Due to a SPDIF output bug on some Audigy cards, this code delays the Right channel by 1 sample */
+			snd_printk(KERN_INFO "Installing spdif_bug patch: %s\n", emu->card_capabilities->name);
+			A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(gpr - 3), A_C_00000000, A_C_00000000);
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 3), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+		} else {
+			A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+		}
+	}
+	snd_emu10k1_init_stereo_onoff_control(controls + nctl++, SNDRV_CTL_NAME_IEC958("Optical Raw ",PLAYBACK,SWITCH), gpr, 0);
+	gpr += 2;
+	
+	A_PUT_STEREO_OUTPUT(A_EXTOUT_REAR_L, A_EXTOUT_REAR_R, playback+2 + SND_EMU10K1_PLAYBACK_CHANNELS);
+	A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4 + SND_EMU10K1_PLAYBACK_CHANNELS);
+	A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5 + SND_EMU10K1_PLAYBACK_CHANNELS);
+
+	/* ADC buffer */
+#ifdef EMU10K1_CAPTURE_DIGITAL_OUT
+	A_PUT_STEREO_OUTPUT(A_EXTOUT_ADC_CAP_L, A_EXTOUT_ADC_CAP_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS);
+#else
+	A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_L, capture);
+	A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_R, capture+1);
+#endif
+
+	if (emu->card_capabilities->emu_model) {
+		if (emu->card_capabilities->ca0108_chip) {
+			snd_printk(KERN_INFO "EMU2 inputs on\n");
+			for (z = 0; z < 0x10; z++) {
+				snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, 
+									bit_shifter16,
+									A3_EMU32IN(z),
+									A_FXBUS2(z*2) );
+			}
+		} else {
+			snd_printk(KERN_INFO "EMU inputs on\n");
+			/* Capture 16 (originally 8) channels of S32_LE sound */
+
+			/*
+			printk(KERN_DEBUG "emufx.c: gpr=0x%x, tmp=0x%x\n",
+			       gpr, tmp);
+			*/
+			/* For the EMU1010: How to get 32bit values from the DSP. High 16bits into L, low 16bits into R. */
+			/* A_P16VIN(0) is delayed by one sample,
+			 * so all other A_P16VIN channels will need to also be delayed
+			 */
+			/* Left ADC in. 1 of 2 */
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_P16VIN(0x0), A_FXBUS2(0) );
+			/* Right ADC in 1 of 2 */
+			gpr_map[gpr++] = 0x00000000;
+			/* Delaying by one sample: instead of copying the input
+			 * value A_P16VIN to output A_FXBUS2 as in the first channel,
+			 * we use an auxiliary register, delaying the value by one
+			 * sample
+			 */
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(2) );
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x1), A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(4) );
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x2), A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(6) );
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x3), A_C_00000000, A_C_00000000);
+			/* For 96kHz mode */
+			/* Left ADC in. 2 of 2 */
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0x8) );
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x4), A_C_00000000, A_C_00000000);
+			/* Right ADC in 2 of 2 */
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xa) );
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x5), A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xc) );
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x6), A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xe) );
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x7), A_C_00000000, A_C_00000000);
+			/* Pavel Hofman - we still have voices, A_FXBUS2s, and
+			 * A_P16VINs available -
+			 * let's add 8 more capture channels - total of 16
+			 */
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp,
+								  bit_shifter16,
+								  A_GPR(gpr - 1),
+								  A_FXBUS2(0x10));
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x8),
+			     A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp,
+								  bit_shifter16,
+								  A_GPR(gpr - 1),
+								  A_FXBUS2(0x12));
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x9),
+			     A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp,
+								  bit_shifter16,
+								  A_GPR(gpr - 1),
+								  A_FXBUS2(0x14));
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xa),
+			     A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp,
+								  bit_shifter16,
+								  A_GPR(gpr - 1),
+								  A_FXBUS2(0x16));
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xb),
+			     A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp,
+								  bit_shifter16,
+								  A_GPR(gpr - 1),
+								  A_FXBUS2(0x18));
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xc),
+			     A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp,
+								  bit_shifter16,
+								  A_GPR(gpr - 1),
+								  A_FXBUS2(0x1a));
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xd),
+			     A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp,
+								  bit_shifter16,
+								  A_GPR(gpr - 1),
+								  A_FXBUS2(0x1c));
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xe),
+			     A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp,
+								  bit_shifter16,
+								  A_GPR(gpr - 1),
+								  A_FXBUS2(0x1e));
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xf),
+			     A_C_00000000, A_C_00000000);
+		}
+
+#if 0
+		for (z = 4; z < 8; z++) {
+			A_OP(icode, &ptr, iACC3, A_FXBUS2(z), A_C_00000000, A_C_00000000, A_C_00000000);
+		}
+		for (z = 0xc; z < 0x10; z++) {
+			A_OP(icode, &ptr, iACC3, A_FXBUS2(z), A_C_00000000, A_C_00000000, A_C_00000000);
+		}
+#endif
+	} else {
+		/* EFX capture - capture the 16 EXTINs */
+		/* Capture 16 channels of S16_LE sound */
+		for (z = 0; z < 16; z++) {
+			A_OP(icode, &ptr, iACC3, A_FXBUS2(z), A_C_00000000, A_C_00000000, A_EXTIN(z));
+		}
+	}
+	
+#endif /* JCD test */
+	/*
+	 * ok, set up done..
+	 */
+
+	if (gpr > tmp) {
+		snd_BUG();
+		err = -EIO;
+		goto __err;
+	}
+	/* clear remaining instruction memory */
+	while (ptr < 0x400)
+		A_OP(icode, &ptr, 0x0f, 0xc0, 0xc0, 0xcf, 0xc0);
+
+	seg = snd_enter_user();
+	icode->gpr_add_control_count = nctl;
+	icode->gpr_add_controls = (struct snd_emu10k1_fx8010_control_gpr __user *)controls;
+	emu->support_tlv = 1; /* support TLV */
+	err = snd_emu10k1_icode_poke(emu, icode);
+	emu->support_tlv = 0; /* clear again */
+	snd_leave_user(seg);
+
+ __err:
+	kfree(controls);
+	if (icode != NULL) {
+		kfree((void __force *)icode->gpr_map);
+		kfree(icode);
+	}
+	return err;
+}
+
+
+/*
+ * initial DSP configuration for Emu10k1
+ */
+
+/* when volume = max, then copy only to avoid volume modification */
+/* with iMAC0 (negative values) */
+static void __devinit _volume(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol)
+{
+	OP(icode, ptr, iMAC0, dst, C_00000000, src, vol);
+	OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff);
+	OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000001);
+	OP(icode, ptr, iACC3, dst, src, C_00000000, C_00000000);
+}
+static void __devinit _volume_add(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol)
+{
+	OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff);
+	OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002);
+	OP(icode, ptr, iMACINT0, dst, dst, src, C_00000001);
+	OP(icode, ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000001);
+	OP(icode, ptr, iMAC0, dst, dst, src, vol);
+}
+static void __devinit _volume_out(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol)
+{
+	OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff);
+	OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002);
+	OP(icode, ptr, iACC3, dst, src, C_00000000, C_00000000);
+	OP(icode, ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000001);
+	OP(icode, ptr, iMAC0, dst, C_00000000, src, vol);
+}
+
+#define VOLUME(icode, ptr, dst, src, vol) \
+		_volume(icode, ptr, GPR(dst), GPR(src), GPR(vol))
+#define VOLUME_IN(icode, ptr, dst, src, vol) \
+		_volume(icode, ptr, GPR(dst), EXTIN(src), GPR(vol))
+#define VOLUME_ADD(icode, ptr, dst, src, vol) \
+		_volume_add(icode, ptr, GPR(dst), GPR(src), GPR(vol))
+#define VOLUME_ADDIN(icode, ptr, dst, src, vol) \
+		_volume_add(icode, ptr, GPR(dst), EXTIN(src), GPR(vol))
+#define VOLUME_OUT(icode, ptr, dst, src, vol) \
+		_volume_out(icode, ptr, EXTOUT(dst), GPR(src), GPR(vol))
+#define _SWITCH(icode, ptr, dst, src, sw) \
+	OP((icode), ptr, iMACINT0, dst, C_00000000, src, sw);
+#define SWITCH(icode, ptr, dst, src, sw) \
+		_SWITCH(icode, ptr, GPR(dst), GPR(src), GPR(sw))
+#define SWITCH_IN(icode, ptr, dst, src, sw) \
+		_SWITCH(icode, ptr, GPR(dst), EXTIN(src), GPR(sw))
+#define _SWITCH_NEG(icode, ptr, dst, src) \
+	OP((icode), ptr, iANDXOR, dst, src, C_00000001, C_00000001);
+#define SWITCH_NEG(icode, ptr, dst, src) \
+		_SWITCH_NEG(icode, ptr, GPR(dst), GPR(src))
+
+
+static int __devinit _snd_emu10k1_init_efx(struct snd_emu10k1 *emu)
+{
+	int err, i, z, gpr, tmp, playback, capture;
+	u32 ptr;
+	struct snd_emu10k1_fx8010_code *icode;
+	struct snd_emu10k1_fx8010_pcm_rec *ipcm = NULL;
+	struct snd_emu10k1_fx8010_control_gpr *controls = NULL, *ctl;
+	u32 *gpr_map;
+	mm_segment_t seg;
+
+	if ((icode = kzalloc(sizeof(*icode), GFP_KERNEL)) == NULL)
+		return -ENOMEM;
+	if ((icode->gpr_map = (u_int32_t __user *)
+	     kcalloc(256 + 160 + 160 + 2 * 512, sizeof(u_int32_t),
+		     GFP_KERNEL)) == NULL ||
+            (controls = kcalloc(SND_EMU10K1_GPR_CONTROLS,
+				sizeof(struct snd_emu10k1_fx8010_control_gpr),
+				GFP_KERNEL)) == NULL ||
+	    (ipcm = kzalloc(sizeof(*ipcm), GFP_KERNEL)) == NULL) {
+		err = -ENOMEM;
+		goto __err;
+	}
+	gpr_map = (u32 __force *)icode->gpr_map;
+
+	icode->tram_data_map = icode->gpr_map + 256;
+	icode->tram_addr_map = icode->tram_data_map + 160;
+	icode->code = icode->tram_addr_map + 160;
+	
+	/* clear free GPRs */
+	for (i = 0; i < 256; i++)
+		set_bit(i, icode->gpr_valid);
+
+	/* clear TRAM data & address lines */
+	for (i = 0; i < 160; i++)
+		set_bit(i, icode->tram_valid);
+
+	strcpy(icode->name, "SB Live! FX8010 code for ALSA v1.2 by Jaroslav Kysela");
+	ptr = 0; i = 0;
+	/* we have 12 inputs */
+	playback = SND_EMU10K1_INPUTS;
+	/* we have 6 playback channels and tone control doubles */
+	capture = playback + (SND_EMU10K1_PLAYBACK_CHANNELS * 2);
+	gpr = capture + SND_EMU10K1_CAPTURE_CHANNELS;
+	tmp = 0x88;	/* we need 4 temporary GPR */
+	/* from 0x8c to 0xff is the area for tone control */
+
+	/* stop FX processor */
+	snd_emu10k1_ptr_write(emu, DBG, 0, (emu->fx8010.dbg = 0) | EMU10K1_DBG_SINGLE_STEP);
+
+	/*
+	 *  Process FX Buses
+	 */
+	OP(icode, &ptr, iMACINT0, GPR(0), C_00000000, FXBUS(FXBUS_PCM_LEFT), C_00000004);
+	OP(icode, &ptr, iMACINT0, GPR(1), C_00000000, FXBUS(FXBUS_PCM_RIGHT), C_00000004);
+	OP(icode, &ptr, iMACINT0, GPR(2), C_00000000, FXBUS(FXBUS_MIDI_LEFT), C_00000004);
+	OP(icode, &ptr, iMACINT0, GPR(3), C_00000000, FXBUS(FXBUS_MIDI_RIGHT), C_00000004);
+	OP(icode, &ptr, iMACINT0, GPR(4), C_00000000, FXBUS(FXBUS_PCM_LEFT_REAR), C_00000004);
+	OP(icode, &ptr, iMACINT0, GPR(5), C_00000000, FXBUS(FXBUS_PCM_RIGHT_REAR), C_00000004);
+	OP(icode, &ptr, iMACINT0, GPR(6), C_00000000, FXBUS(FXBUS_PCM_CENTER), C_00000004);
+	OP(icode, &ptr, iMACINT0, GPR(7), C_00000000, FXBUS(FXBUS_PCM_LFE), C_00000004);
+	OP(icode, &ptr, iMACINT0, GPR(8), C_00000000, C_00000000, C_00000000);	/* S/PDIF left */
+	OP(icode, &ptr, iMACINT0, GPR(9), C_00000000, C_00000000, C_00000000);	/* S/PDIF right */
+	OP(icode, &ptr, iMACINT0, GPR(10), C_00000000, FXBUS(FXBUS_PCM_LEFT_FRONT), C_00000004);
+	OP(icode, &ptr, iMACINT0, GPR(11), C_00000000, FXBUS(FXBUS_PCM_RIGHT_FRONT), C_00000004);
+
+	/* Raw S/PDIF PCM */
+	ipcm->substream = 0;
+	ipcm->channels = 2;
+	ipcm->tram_start = 0;
+	ipcm->buffer_size = (64 * 1024) / 2;
+	ipcm->gpr_size = gpr++;
+	ipcm->gpr_ptr = gpr++;
+	ipcm->gpr_count = gpr++;
+	ipcm->gpr_tmpcount = gpr++;
+	ipcm->gpr_trigger = gpr++;
+	ipcm->gpr_running = gpr++;
+	ipcm->etram[0] = 0;
+	ipcm->etram[1] = 1;
+
+	gpr_map[gpr + 0] = 0xfffff000;
+	gpr_map[gpr + 1] = 0xffff0000;
+	gpr_map[gpr + 2] = 0x70000000;
+	gpr_map[gpr + 3] = 0x00000007;
+	gpr_map[gpr + 4] = 0x001f << 11;
+	gpr_map[gpr + 5] = 0x001c << 11;
+	gpr_map[gpr + 6] = (0x22  - 0x01) - 1;	/* skip at 01 to 22 */
+	gpr_map[gpr + 7] = (0x22  - 0x06) - 1;	/* skip at 06 to 22 */
+	gpr_map[gpr + 8] = 0x2000000 + (2<<11);
+	gpr_map[gpr + 9] = 0x4000000 + (2<<11);
+	gpr_map[gpr + 10] = 1<<11;
+	gpr_map[gpr + 11] = (0x24 - 0x0a) - 1;	/* skip at 0a to 24 */
+	gpr_map[gpr + 12] = 0;
+
+	/* if the trigger flag is not set, skip */
+	/* 00: */ OP(icode, &ptr, iMAC0, C_00000000, GPR(ipcm->gpr_trigger), C_00000000, C_00000000);
+	/* 01: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_ZERO, GPR(gpr + 6));
+	/* if the running flag is set, we're running */
+	/* 02: */ OP(icode, &ptr, iMAC0, C_00000000, GPR(ipcm->gpr_running), C_00000000, C_00000000);
+	/* 03: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000004);
+	/* wait until ((GPR_DBAC>>11) & 0x1f) == 0x1c) */
+	/* 04: */ OP(icode, &ptr, iANDXOR, GPR(tmp + 0), GPR_DBAC, GPR(gpr + 4), C_00000000);
+	/* 05: */ OP(icode, &ptr, iMACINT0, C_00000000, GPR(tmp + 0), C_ffffffff, GPR(gpr + 5));
+	/* 06: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, GPR(gpr + 7));
+	/* 07: */ OP(icode, &ptr, iACC3, GPR(gpr + 12), C_00000010, C_00000001, C_00000000);
+
+	/* 08: */ OP(icode, &ptr, iANDXOR, GPR(ipcm->gpr_running), GPR(ipcm->gpr_running), C_00000000, C_00000001);
+	/* 09: */ OP(icode, &ptr, iACC3, GPR(gpr + 12), GPR(gpr + 12), C_ffffffff, C_00000000);
+	/* 0a: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, GPR(gpr + 11));
+	/* 0b: */ OP(icode, &ptr, iACC3, GPR(gpr + 12), C_00000001, C_00000000, C_00000000);
+
+	/* 0c: */ OP(icode, &ptr, iANDXOR, GPR(tmp + 0), ETRAM_DATA(ipcm->etram[0]), GPR(gpr + 0), C_00000000);
+	/* 0d: */ OP(icode, &ptr, iLOG, GPR(tmp + 0), GPR(tmp + 0), GPR(gpr + 3), C_00000000);
+	/* 0e: */ OP(icode, &ptr, iANDXOR, GPR(8), GPR(tmp + 0), GPR(gpr + 1), GPR(gpr + 2));
+	/* 0f: */ OP(icode, &ptr, iSKIP, C_00000000, GPR_COND, CC_REG_MINUS, C_00000001);
+	/* 10: */ OP(icode, &ptr, iANDXOR, GPR(8), GPR(8), GPR(gpr + 1), GPR(gpr + 2));
+
+	/* 11: */ OP(icode, &ptr, iANDXOR, GPR(tmp + 0), ETRAM_DATA(ipcm->etram[1]), GPR(gpr + 0), C_00000000);
+	/* 12: */ OP(icode, &ptr, iLOG, GPR(tmp + 0), GPR(tmp + 0), GPR(gpr + 3), C_00000000);
+	/* 13: */ OP(icode, &ptr, iANDXOR, GPR(9), GPR(tmp + 0), GPR(gpr + 1), GPR(gpr + 2));
+	/* 14: */ OP(icode, &ptr, iSKIP, C_00000000, GPR_COND, CC_REG_MINUS, C_00000001);
+	/* 15: */ OP(icode, &ptr, iANDXOR, GPR(9), GPR(9), GPR(gpr + 1), GPR(gpr + 2));
+
+	/* 16: */ OP(icode, &ptr, iACC3, GPR(tmp + 0), GPR(ipcm->gpr_ptr), C_00000001, C_00000000);
+	/* 17: */ OP(icode, &ptr, iMACINT0, C_00000000, GPR(tmp + 0), C_ffffffff, GPR(ipcm->gpr_size));
+	/* 18: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_MINUS, C_00000001);
+	/* 19: */ OP(icode, &ptr, iACC3, GPR(tmp + 0), C_00000000, C_00000000, C_00000000);
+	/* 1a: */ OP(icode, &ptr, iACC3, GPR(ipcm->gpr_ptr), GPR(tmp + 0), C_00000000, C_00000000);
+	
+	/* 1b: */ OP(icode, &ptr, iACC3, GPR(ipcm->gpr_tmpcount), GPR(ipcm->gpr_tmpcount), C_ffffffff, C_00000000);
+	/* 1c: */ OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002);
+	/* 1d: */ OP(icode, &ptr, iACC3, GPR(ipcm->gpr_tmpcount), GPR(ipcm->gpr_count), C_00000000, C_00000000);
+	/* 1e: */ OP(icode, &ptr, iACC3, GPR_IRQ, C_80000000, C_00000000, C_00000000);
+	/* 1f: */ OP(icode, &ptr, iANDXOR, GPR(ipcm->gpr_running), GPR(ipcm->gpr_running), C_00000001, C_00010000);
+
+	/* 20: */ OP(icode, &ptr, iANDXOR, GPR(ipcm->gpr_running), GPR(ipcm->gpr_running), C_00010000, C_00000001);
+	/* 21: */ OP(icode, &ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000002);
+
+	/* 22: */ OP(icode, &ptr, iMACINT1, ETRAM_ADDR(ipcm->etram[0]), GPR(gpr + 8), GPR_DBAC, C_ffffffff);
+	/* 23: */ OP(icode, &ptr, iMACINT1, ETRAM_ADDR(ipcm->etram[1]), GPR(gpr + 9), GPR_DBAC, C_ffffffff);
+
+	/* 24: */
+	gpr += 13;
+
+	/* Wave Playback Volume */
+	for (z = 0; z < 2; z++)
+		VOLUME(icode, &ptr, playback + z, z, gpr + z);
+	snd_emu10k1_init_stereo_control(controls + i++, "Wave Playback Volume", gpr, 100);
+	gpr += 2;
+
+	/* Wave Surround Playback Volume */
+	for (z = 0; z < 2; z++)
+		VOLUME(icode, &ptr, playback + 2 + z, z, gpr + z);
+	snd_emu10k1_init_stereo_control(controls + i++, "Wave Surround Playback Volume", gpr, 0);
+	gpr += 2;
+	
+	/* Wave Center/LFE Playback Volume */
+	OP(icode, &ptr, iACC3, GPR(tmp + 0), FXBUS(FXBUS_PCM_LEFT), FXBUS(FXBUS_PCM_RIGHT), C_00000000);
+	OP(icode, &ptr, iMACINT0, GPR(tmp + 0), C_00000000, GPR(tmp + 0), C_00000002);
+	VOLUME(icode, &ptr, playback + 4, tmp + 0, gpr);
+	snd_emu10k1_init_mono_control(controls + i++, "Wave Center Playback Volume", gpr++, 0);
+	VOLUME(icode, &ptr, playback + 5, tmp + 0, gpr);
+	snd_emu10k1_init_mono_control(controls + i++, "Wave LFE Playback Volume", gpr++, 0);
+
+	/* Wave Capture Volume + Switch */
+	for (z = 0; z < 2; z++) {
+		SWITCH(icode, &ptr, tmp + 0, z, gpr + 2 + z);
+		VOLUME(icode, &ptr, capture + z, tmp + 0, gpr + z);
+	}
+	snd_emu10k1_init_stereo_control(controls + i++, "Wave Capture Volume", gpr, 0);
+	snd_emu10k1_init_stereo_onoff_control(controls + i++, "Wave Capture Switch", gpr + 2, 0);
+	gpr += 4;
+
+	/* Synth Playback Volume */
+	for (z = 0; z < 2; z++)
+		VOLUME_ADD(icode, &ptr, playback + z, 2 + z, gpr + z);
+	snd_emu10k1_init_stereo_control(controls + i++, "Synth Playback Volume", gpr, 100);
+	gpr += 2;
+
+	/* Synth Capture Volume + Switch */
+	for (z = 0; z < 2; z++) {
+		SWITCH(icode, &ptr, tmp + 0, 2 + z, gpr + 2 + z);
+		VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+	}
+	snd_emu10k1_init_stereo_control(controls + i++, "Synth Capture Volume", gpr, 0);
+	snd_emu10k1_init_stereo_onoff_control(controls + i++, "Synth Capture Switch", gpr + 2, 0);
+	gpr += 4;
+
+	/* Surround Digital Playback Volume (renamed later without Digital) */
+	for (z = 0; z < 2; z++)
+		VOLUME_ADD(icode, &ptr, playback + 2 + z, 4 + z, gpr + z);
+	snd_emu10k1_init_stereo_control(controls + i++, "Surround Digital Playback Volume", gpr, 100);
+	gpr += 2;
+
+	/* Surround Capture Volume + Switch */
+	for (z = 0; z < 2; z++) {
+		SWITCH(icode, &ptr, tmp + 0, 4 + z, gpr + 2 + z);
+		VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+	}
+	snd_emu10k1_init_stereo_control(controls + i++, "Surround Capture Volume", gpr, 0);
+	snd_emu10k1_init_stereo_onoff_control(controls + i++, "Surround Capture Switch", gpr + 2, 0);
+	gpr += 4;
+
+	/* Center Playback Volume (renamed later without Digital) */
+	VOLUME_ADD(icode, &ptr, playback + 4, 6, gpr);
+	snd_emu10k1_init_mono_control(controls + i++, "Center Digital Playback Volume", gpr++, 100);
+
+	/* LFE Playback Volume + Switch (renamed later without Digital) */
+	VOLUME_ADD(icode, &ptr, playback + 5, 7, gpr);
+	snd_emu10k1_init_mono_control(controls + i++, "LFE Digital Playback Volume", gpr++, 100);
+
+	/* Front Playback Volume */
+	for (z = 0; z < 2; z++)
+		VOLUME_ADD(icode, &ptr, playback + z, 10 + z, gpr + z);
+	snd_emu10k1_init_stereo_control(controls + i++, "Front Playback Volume", gpr, 100);
+	gpr += 2;
+
+	/* Front Capture Volume + Switch */
+	for (z = 0; z < 2; z++) {
+		SWITCH(icode, &ptr, tmp + 0, 10 + z, gpr + 2);
+		VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+	}
+	snd_emu10k1_init_stereo_control(controls + i++, "Front Capture Volume", gpr, 0);
+	snd_emu10k1_init_mono_onoff_control(controls + i++, "Front Capture Switch", gpr + 2, 0);
+	gpr += 3;
+
+	/*
+	 *  Process inputs
+	 */
+
+	if (emu->fx8010.extin_mask & ((1<<EXTIN_AC97_L)|(1<<EXTIN_AC97_R))) {
+		/* AC'97 Playback Volume */
+		VOLUME_ADDIN(icode, &ptr, playback + 0, EXTIN_AC97_L, gpr); gpr++;
+		VOLUME_ADDIN(icode, &ptr, playback + 1, EXTIN_AC97_R, gpr); gpr++;
+		snd_emu10k1_init_stereo_control(controls + i++, "AC97 Playback Volume", gpr-2, 0);
+		/* AC'97 Capture Volume */
+		VOLUME_ADDIN(icode, &ptr, capture + 0, EXTIN_AC97_L, gpr); gpr++;
+		VOLUME_ADDIN(icode, &ptr, capture + 1, EXTIN_AC97_R, gpr); gpr++;
+		snd_emu10k1_init_stereo_control(controls + i++, "AC97 Capture Volume", gpr-2, 100);
+	}
+	
+	if (emu->fx8010.extin_mask & ((1<<EXTIN_SPDIF_CD_L)|(1<<EXTIN_SPDIF_CD_R))) {
+		/* IEC958 TTL Playback Volume */
+		for (z = 0; z < 2; z++)
+			VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_SPDIF_CD_L + z, gpr + z);
+		snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("TTL ",PLAYBACK,VOLUME), gpr, 0);
+		gpr += 2;
+	
+		/* IEC958 TTL Capture Volume + Switch */
+		for (z = 0; z < 2; z++) {
+			SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_SPDIF_CD_L + z, gpr + 2 + z);
+			VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+		}
+		snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("TTL ",CAPTURE,VOLUME), gpr, 0);
+		snd_emu10k1_init_stereo_onoff_control(controls + i++, SNDRV_CTL_NAME_IEC958("TTL ",CAPTURE,SWITCH), gpr + 2, 0);
+		gpr += 4;
+	}
+	
+	if (emu->fx8010.extin_mask & ((1<<EXTIN_ZOOM_L)|(1<<EXTIN_ZOOM_R))) {
+		/* Zoom Video Playback Volume */
+		for (z = 0; z < 2; z++)
+			VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_ZOOM_L + z, gpr + z);
+		snd_emu10k1_init_stereo_control(controls + i++, "Zoom Video Playback Volume", gpr, 0);
+		gpr += 2;
+	
+		/* Zoom Video Capture Volume + Switch */
+		for (z = 0; z < 2; z++) {
+			SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_ZOOM_L + z, gpr + 2 + z);
+			VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+		}
+		snd_emu10k1_init_stereo_control(controls + i++, "Zoom Video Capture Volume", gpr, 0);
+		snd_emu10k1_init_stereo_onoff_control(controls + i++, "Zoom Video Capture Switch", gpr + 2, 0);
+		gpr += 4;
+	}
+	
+	if (emu->fx8010.extin_mask & ((1<<EXTIN_TOSLINK_L)|(1<<EXTIN_TOSLINK_R))) {
+		/* IEC958 Optical Playback Volume */
+		for (z = 0; z < 2; z++)
+			VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_TOSLINK_L + z, gpr + z);
+		snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("LiveDrive ",PLAYBACK,VOLUME), gpr, 0);
+		gpr += 2;
+	
+		/* IEC958 Optical Capture Volume */
+		for (z = 0; z < 2; z++) {
+			SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_TOSLINK_L + z, gpr + 2 + z);
+			VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+		}
+		snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("LiveDrive ",CAPTURE,VOLUME), gpr, 0);
+		snd_emu10k1_init_stereo_onoff_control(controls + i++, SNDRV_CTL_NAME_IEC958("LiveDrive ",CAPTURE,SWITCH), gpr + 2, 0);
+		gpr += 4;
+	}
+	
+	if (emu->fx8010.extin_mask & ((1<<EXTIN_LINE1_L)|(1<<EXTIN_LINE1_R))) {
+		/* Line LiveDrive Playback Volume */
+		for (z = 0; z < 2; z++)
+			VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_LINE1_L + z, gpr + z);
+		snd_emu10k1_init_stereo_control(controls + i++, "Line LiveDrive Playback Volume", gpr, 0);
+		gpr += 2;
+	
+		/* Line LiveDrive Capture Volume + Switch */
+		for (z = 0; z < 2; z++) {
+			SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_LINE1_L + z, gpr + 2 + z);
+			VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+		}
+		snd_emu10k1_init_stereo_control(controls + i++, "Line LiveDrive Capture Volume", gpr, 0);
+		snd_emu10k1_init_stereo_onoff_control(controls + i++, "Line LiveDrive Capture Switch", gpr + 2, 0);
+		gpr += 4;
+	}
+	
+	if (emu->fx8010.extin_mask & ((1<<EXTIN_COAX_SPDIF_L)|(1<<EXTIN_COAX_SPDIF_R))) {
+		/* IEC958 Coax Playback Volume */
+		for (z = 0; z < 2; z++)
+			VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_COAX_SPDIF_L + z, gpr + z);
+		snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("Coaxial ",PLAYBACK,VOLUME), gpr, 0);
+		gpr += 2;
+	
+		/* IEC958 Coax Capture Volume + Switch */
+		for (z = 0; z < 2; z++) {
+			SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_COAX_SPDIF_L + z, gpr + 2 + z);
+			VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+		}
+		snd_emu10k1_init_stereo_control(controls + i++, SNDRV_CTL_NAME_IEC958("Coaxial ",CAPTURE,VOLUME), gpr, 0);
+		snd_emu10k1_init_stereo_onoff_control(controls + i++, SNDRV_CTL_NAME_IEC958("Coaxial ",CAPTURE,SWITCH), gpr + 2, 0);
+		gpr += 4;
+	}
+	
+	if (emu->fx8010.extin_mask & ((1<<EXTIN_LINE2_L)|(1<<EXTIN_LINE2_R))) {
+		/* Line LiveDrive Playback Volume */
+		for (z = 0; z < 2; z++)
+			VOLUME_ADDIN(icode, &ptr, playback + z, EXTIN_LINE2_L + z, gpr + z);
+		snd_emu10k1_init_stereo_control(controls + i++, "Line2 LiveDrive Playback Volume", gpr, 0);
+		controls[i-1].id.index = 1;
+		gpr += 2;
+	
+		/* Line LiveDrive Capture Volume */
+		for (z = 0; z < 2; z++) {
+			SWITCH_IN(icode, &ptr, tmp + 0, EXTIN_LINE2_L + z, gpr + 2 + z);
+			VOLUME_ADD(icode, &ptr, capture + z, tmp + 0, gpr + z);
+		}
+		snd_emu10k1_init_stereo_control(controls + i++, "Line2 LiveDrive Capture Volume", gpr, 0);
+		controls[i-1].id.index = 1;
+		snd_emu10k1_init_stereo_onoff_control(controls + i++, "Line2 LiveDrive Capture Switch", gpr + 2, 0);
+		controls[i-1].id.index = 1;
+		gpr += 4;
+	}
+
+	/*
+	 *  Process tone control
+	 */
+	OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), GPR(playback + 0), C_00000000, C_00000000); /* left */
+	OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), GPR(playback + 1), C_00000000, C_00000000); /* right */
+	OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2), GPR(playback + 2), C_00000000, C_00000000); /* rear left */
+	OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 3), GPR(playback + 3), C_00000000, C_00000000); /* rear right */
+	OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), GPR(playback + 4), C_00000000, C_00000000); /* center */
+	OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), GPR(playback + 5), C_00000000, C_00000000); /* LFE */
+
+	ctl = &controls[i + 0];
+	ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(ctl->id.name, "Tone Control - Bass");
+	ctl->vcount = 2;
+	ctl->count = 10;
+	ctl->min = 0;
+	ctl->max = 40;
+	ctl->value[0] = ctl->value[1] = 20;
+	ctl->translation = EMU10K1_GPR_TRANSLATION_BASS;
+	ctl = &controls[i + 1];
+	ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(ctl->id.name, "Tone Control - Treble");
+	ctl->vcount = 2;
+	ctl->count = 10;
+	ctl->min = 0;
+	ctl->max = 40;
+	ctl->value[0] = ctl->value[1] = 20;
+	ctl->translation = EMU10K1_GPR_TRANSLATION_TREBLE;
+
+#define BASS_GPR	0x8c
+#define TREBLE_GPR	0x96
+
+	for (z = 0; z < 5; z++) {
+		int j;
+		for (j = 0; j < 2; j++) {
+			controls[i + 0].gpr[z * 2 + j] = BASS_GPR + z * 2 + j;
+			controls[i + 1].gpr[z * 2 + j] = TREBLE_GPR + z * 2 + j;
+		}
+	}
+	for (z = 0; z < 3; z++) {		/* front/rear/center-lfe */
+		int j, k, l, d;
+		for (j = 0; j < 2; j++) {	/* left/right */
+			k = 0xa0 + (z * 8) + (j * 4);
+			l = 0xd0 + (z * 8) + (j * 4);
+			d = playback + SND_EMU10K1_PLAYBACK_CHANNELS + z * 2 + j;
+
+			OP(icode, &ptr, iMAC0, C_00000000, C_00000000, GPR(d), GPR(BASS_GPR + 0 + j));
+			OP(icode, &ptr, iMACMV, GPR(k+1), GPR(k), GPR(k+1), GPR(BASS_GPR + 4 + j));
+			OP(icode, &ptr, iMACMV, GPR(k), GPR(d), GPR(k), GPR(BASS_GPR + 2 + j));
+			OP(icode, &ptr, iMACMV, GPR(k+3), GPR(k+2), GPR(k+3), GPR(BASS_GPR + 8 + j));
+			OP(icode, &ptr, iMAC0, GPR(k+2), GPR_ACCU, GPR(k+2), GPR(BASS_GPR + 6 + j));
+			OP(icode, &ptr, iACC3, GPR(k+2), GPR(k+2), GPR(k+2), C_00000000);
+
+			OP(icode, &ptr, iMAC0, C_00000000, C_00000000, GPR(k+2), GPR(TREBLE_GPR + 0 + j));
+			OP(icode, &ptr, iMACMV, GPR(l+1), GPR(l), GPR(l+1), GPR(TREBLE_GPR + 4 + j));
+			OP(icode, &ptr, iMACMV, GPR(l), GPR(k+2), GPR(l), GPR(TREBLE_GPR + 2 + j));
+			OP(icode, &ptr, iMACMV, GPR(l+3), GPR(l+2), GPR(l+3), GPR(TREBLE_GPR + 8 + j));
+			OP(icode, &ptr, iMAC0, GPR(l+2), GPR_ACCU, GPR(l+2), GPR(TREBLE_GPR + 6 + j));
+			OP(icode, &ptr, iMACINT0, GPR(l+2), C_00000000, GPR(l+2), C_00000010);
+
+			OP(icode, &ptr, iACC3, GPR(d), GPR(l+2), C_00000000, C_00000000);
+
+			if (z == 2)	/* center */
+				break;
+		}
+	}
+	i += 2;
+
+#undef BASS_GPR
+#undef TREBLE_GPR
+
+	for (z = 0; z < 6; z++) {
+		SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr + 0);
+		SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 0);
+		SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1);
+		OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+	}
+	snd_emu10k1_init_stereo_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0);
+	gpr += 2;
+
+	/*
+	 *  Process outputs
+	 */
+	if (emu->fx8010.extout_mask & ((1<<EXTOUT_AC97_L)|(1<<EXTOUT_AC97_R))) {
+		/* AC'97 Playback Volume */
+
+		for (z = 0; z < 2; z++)
+			OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_L + z), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), C_00000000, C_00000000);
+	}
+
+	if (emu->fx8010.extout_mask & ((1<<EXTOUT_TOSLINK_L)|(1<<EXTOUT_TOSLINK_R))) {
+		/* IEC958 Optical Raw Playback Switch */
+
+		for (z = 0; z < 2; z++) {
+			SWITCH(icode, &ptr, tmp + 0, 8 + z, gpr + z);
+			SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z);
+			SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1);
+			OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_TOSLINK_L + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+#ifdef EMU10K1_CAPTURE_DIGITAL_OUT
+	 		OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ADC_CAP_L + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+#endif
+		}
+
+		snd_emu10k1_init_stereo_onoff_control(controls + i++, SNDRV_CTL_NAME_IEC958("Optical Raw ",PLAYBACK,SWITCH), gpr, 0);
+		gpr += 2;
+	}
+
+	if (emu->fx8010.extout_mask & ((1<<EXTOUT_HEADPHONE_L)|(1<<EXTOUT_HEADPHONE_R))) {
+		/* Headphone Playback Volume */
+
+		for (z = 0; z < 2; z++) {
+			SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4 + z, gpr + 2 + z);
+			SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 2 + z);
+			SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1);
+			OP(icode, &ptr, iACC3, GPR(tmp + 0), GPR(tmp + 0), GPR(tmp + 1), C_00000000);
+			VOLUME_OUT(icode, &ptr, EXTOUT_HEADPHONE_L + z, tmp + 0, gpr + z);
+		}
+
+		snd_emu10k1_init_stereo_control(controls + i++, "Headphone Playback Volume", gpr + 0, 0);
+		controls[i-1].id.index = 1;	/* AC'97 can have also Headphone control */
+		snd_emu10k1_init_mono_onoff_control(controls + i++, "Headphone Center Playback Switch", gpr + 2, 0);
+		controls[i-1].id.index = 1;
+		snd_emu10k1_init_mono_onoff_control(controls + i++, "Headphone LFE Playback Switch", gpr + 3, 0);
+		controls[i-1].id.index = 1;
+
+		gpr += 4;
+	}
+	
+	if (emu->fx8010.extout_mask & ((1<<EXTOUT_REAR_L)|(1<<EXTOUT_REAR_R)))
+		for (z = 0; z < 2; z++)
+			OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_REAR_L + z), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2 + z), C_00000000, C_00000000);
+
+	if (emu->fx8010.extout_mask & ((1<<EXTOUT_AC97_REAR_L)|(1<<EXTOUT_AC97_REAR_R)))
+		for (z = 0; z < 2; z++)
+			OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_REAR_L + z), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2 + z), C_00000000, C_00000000);
+
+	if (emu->fx8010.extout_mask & (1<<EXTOUT_AC97_CENTER)) {
+#ifndef EMU10K1_CENTER_LFE_FROM_FRONT
+		OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_CENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), C_00000000, C_00000000);
+		OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ACENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), C_00000000, C_00000000);
+#else
+		OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_CENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), C_00000000, C_00000000);
+		OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ACENTER), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), C_00000000, C_00000000);
+#endif
+	}
+
+	if (emu->fx8010.extout_mask & (1<<EXTOUT_AC97_LFE)) {
+#ifndef EMU10K1_CENTER_LFE_FROM_FRONT
+		OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_LFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), C_00000000, C_00000000);
+		OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ALFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), C_00000000, C_00000000);
+#else
+		OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_LFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), C_00000000, C_00000000);
+		OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ALFE), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), C_00000000, C_00000000);
+#endif
+	}
+	
+#ifndef EMU10K1_CAPTURE_DIGITAL_OUT
+	for (z = 0; z < 2; z++)
+ 		OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_ADC_CAP_L + z), GPR(capture + z), C_00000000, C_00000000);
+#endif
+	
+	if (emu->fx8010.extout_mask & (1<<EXTOUT_MIC_CAP))
+		OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_MIC_CAP), GPR(capture + 2), C_00000000, C_00000000);
+
+	/* EFX capture - capture the 16 EXTINS */
+	if (emu->card_capabilities->sblive51) {
+		/* On the Live! 5.1, FXBUS2(1) and FXBUS(2) are shared with EXTOUT_ACENTER
+		 * and EXTOUT_ALFE, so we can't connect inputs to them for multitrack recording.
+		 *
+		 * Since only 14 of the 16 EXTINs are used, this is not a big problem.  
+		 * We route AC97L and R to FX capture 14 and 15, SPDIF CD in to FX capture 
+		 * 0 and 3, then the rest of the EXTINs to the corresponding FX capture 
+		 * channel.  Multitrack recorders will still see the center/lfe output signal 
+		 * on the second and third channels.
+		 */
+		OP(icode, &ptr, iACC3, FXBUS2(14), C_00000000, C_00000000, EXTIN(0));
+		OP(icode, &ptr, iACC3, FXBUS2(15), C_00000000, C_00000000, EXTIN(1));
+		OP(icode, &ptr, iACC3, FXBUS2(0), C_00000000, C_00000000, EXTIN(2));
+		OP(icode, &ptr, iACC3, FXBUS2(3), C_00000000, C_00000000, EXTIN(3));
+		for (z = 4; z < 14; z++)
+			OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(z));
+	} else {
+		for (z = 0; z < 16; z++)
+			OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(z));
+	}
+	    
+
+	if (gpr > tmp) {
+		snd_BUG();
+		err = -EIO;
+		goto __err;
+	}
+	if (i > SND_EMU10K1_GPR_CONTROLS) {
+		snd_BUG();
+		err = -EIO;
+		goto __err;
+	}
+	
+	/* clear remaining instruction memory */
+	while (ptr < 0x200)
+		OP(icode, &ptr, iACC3, C_00000000, C_00000000, C_00000000, C_00000000);
+
+	if ((err = snd_emu10k1_fx8010_tram_setup(emu, ipcm->buffer_size)) < 0)
+		goto __err;
+	seg = snd_enter_user();
+	icode->gpr_add_control_count = i;
+	icode->gpr_add_controls = (struct snd_emu10k1_fx8010_control_gpr __user *)controls;
+	emu->support_tlv = 1; /* support TLV */
+	err = snd_emu10k1_icode_poke(emu, icode);
+	emu->support_tlv = 0; /* clear again */
+	snd_leave_user(seg);
+	if (err >= 0)
+		err = snd_emu10k1_ipcm_poke(emu, ipcm);
+      __err:
+	kfree(ipcm);
+	kfree(controls);
+	if (icode != NULL) {
+		kfree((void __force *)icode->gpr_map);
+		kfree(icode);
+	}
+	return err;
+}
+
+int __devinit snd_emu10k1_init_efx(struct snd_emu10k1 *emu)
+{
+	spin_lock_init(&emu->fx8010.irq_lock);
+	INIT_LIST_HEAD(&emu->fx8010.gpr_ctl);
+	if (emu->audigy)
+		return _snd_emu10k1_audigy_init_efx(emu);
+	else
+		return _snd_emu10k1_init_efx(emu);
+}
+
+void snd_emu10k1_free_efx(struct snd_emu10k1 *emu)
+{
+	/* stop processor */
+	if (emu->audigy)
+		snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg = A_DBG_SINGLE_STEP);
+	else
+		snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg = EMU10K1_DBG_SINGLE_STEP);
+}
+
+#if 0 /* FIXME: who use them? */
+int snd_emu10k1_fx8010_tone_control_activate(struct snd_emu10k1 *emu, int output)
+{
+	if (output < 0 || output >= 6)
+		return -EINVAL;
+	snd_emu10k1_ptr_write(emu, emu->gpr_base + 0x94 + output, 0, 1);
+	return 0;
+}
+
+int snd_emu10k1_fx8010_tone_control_deactivate(struct snd_emu10k1 *emu, int output)
+{
+	if (output < 0 || output >= 6)
+		return -EINVAL;
+	snd_emu10k1_ptr_write(emu, emu->gpr_base + 0x94 + output, 0, 0);
+	return 0;
+}
+#endif
+
+int snd_emu10k1_fx8010_tram_setup(struct snd_emu10k1 *emu, u32 size)
+{
+	u8 size_reg = 0;
+
+	/* size is in samples */
+	if (size != 0) {
+		size = (size - 1) >> 13;
+
+		while (size) {
+			size >>= 1;
+			size_reg++;
+		}
+		size = 0x2000 << size_reg;
+	}
+	if ((emu->fx8010.etram_pages.bytes / 2) == size)
+		return 0;
+	spin_lock_irq(&emu->emu_lock);
+	outl(HCFG_LOCKTANKCACHE_MASK | inl(emu->port + HCFG), emu->port + HCFG);
+	spin_unlock_irq(&emu->emu_lock);
+	snd_emu10k1_ptr_write(emu, TCB, 0, 0);
+	snd_emu10k1_ptr_write(emu, TCBS, 0, 0);
+	if (emu->fx8010.etram_pages.area != NULL) {
+		snd_dma_free_pages(&emu->fx8010.etram_pages);
+		emu->fx8010.etram_pages.area = NULL;
+		emu->fx8010.etram_pages.bytes = 0;
+	}
+
+	if (size > 0) {
+		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci),
+					size * 2, &emu->fx8010.etram_pages) < 0)
+			return -ENOMEM;
+		memset(emu->fx8010.etram_pages.area, 0, size * 2);
+		snd_emu10k1_ptr_write(emu, TCB, 0, emu->fx8010.etram_pages.addr);
+		snd_emu10k1_ptr_write(emu, TCBS, 0, size_reg);
+		spin_lock_irq(&emu->emu_lock);
+		outl(inl(emu->port + HCFG) & ~HCFG_LOCKTANKCACHE_MASK, emu->port + HCFG);
+		spin_unlock_irq(&emu->emu_lock);
+	}
+
+	return 0;
+}
+
+static int snd_emu10k1_fx8010_open(struct snd_hwdep * hw, struct file *file)
+{
+	return 0;
+}
+
+static void copy_string(char *dst, char *src, char *null, int idx)
+{
+	if (src == NULL)
+		sprintf(dst, "%s %02X", null, idx);
+	else
+		strcpy(dst, src);
+}
+
+static void snd_emu10k1_fx8010_info(struct snd_emu10k1 *emu,
+				   struct snd_emu10k1_fx8010_info *info)
+{
+	char **fxbus, **extin, **extout;
+	unsigned short fxbus_mask, extin_mask, extout_mask;
+	int res;
+
+	info->internal_tram_size = emu->fx8010.itram_size;
+	info->external_tram_size = emu->fx8010.etram_pages.bytes / 2;
+	fxbus = fxbuses;
+	extin = emu->audigy ? audigy_ins : creative_ins;
+	extout = emu->audigy ? audigy_outs : creative_outs;
+	fxbus_mask = emu->fx8010.fxbus_mask;
+	extin_mask = emu->fx8010.extin_mask;
+	extout_mask = emu->fx8010.extout_mask;
+	for (res = 0; res < 16; res++, fxbus++, extin++, extout++) {
+		copy_string(info->fxbus_names[res], fxbus_mask & (1 << res) ? *fxbus : NULL, "FXBUS", res);
+		copy_string(info->extin_names[res], extin_mask & (1 << res) ? *extin : NULL, "Unused", res);
+		copy_string(info->extout_names[res], extout_mask & (1 << res) ? *extout : NULL, "Unused", res);
+	}
+	for (res = 16; res < 32; res++, extout++)
+		copy_string(info->extout_names[res], extout_mask & (1 << res) ? *extout : NULL, "Unused", res);
+	info->gpr_controls = emu->fx8010.gpr_count;
+}
+
+static int snd_emu10k1_fx8010_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct snd_emu10k1 *emu = hw->private_data;
+	struct snd_emu10k1_fx8010_info *info;
+	struct snd_emu10k1_fx8010_code *icode;
+	struct snd_emu10k1_fx8010_pcm_rec *ipcm;
+	unsigned int addr;
+	void __user *argp = (void __user *)arg;
+	int res;
+	
+	switch (cmd) {
+	case SNDRV_EMU10K1_IOCTL_PVERSION:
+		emu->support_tlv = 1;
+		return put_user(SNDRV_EMU10K1_VERSION, (int __user *)argp);
+	case SNDRV_EMU10K1_IOCTL_INFO:
+		info = kmalloc(sizeof(*info), GFP_KERNEL);
+		if (!info)
+			return -ENOMEM;
+		snd_emu10k1_fx8010_info(emu, info);
+		if (copy_to_user(argp, info, sizeof(*info))) {
+			kfree(info);
+			return -EFAULT;
+		}
+		kfree(info);
+		return 0;
+	case SNDRV_EMU10K1_IOCTL_CODE_POKE:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+
+		icode = memdup_user(argp, sizeof(*icode));
+		if (IS_ERR(icode))
+			return PTR_ERR(icode);
+		res = snd_emu10k1_icode_poke(emu, icode);
+		kfree(icode);
+		return res;
+	case SNDRV_EMU10K1_IOCTL_CODE_PEEK:
+		icode = memdup_user(argp, sizeof(*icode));
+		if (IS_ERR(icode))
+			return PTR_ERR(icode);
+		res = snd_emu10k1_icode_peek(emu, icode);
+		if (res == 0 && copy_to_user(argp, icode, sizeof(*icode))) {
+			kfree(icode);
+			return -EFAULT;
+		}
+		kfree(icode);
+		return res;
+	case SNDRV_EMU10K1_IOCTL_PCM_POKE:
+		ipcm = memdup_user(argp, sizeof(*ipcm));
+		if (IS_ERR(ipcm))
+			return PTR_ERR(ipcm);
+		res = snd_emu10k1_ipcm_poke(emu, ipcm);
+		kfree(ipcm);
+		return res;
+	case SNDRV_EMU10K1_IOCTL_PCM_PEEK:
+		ipcm = memdup_user(argp, sizeof(*ipcm));
+		if (IS_ERR(ipcm))
+			return PTR_ERR(ipcm);
+		res = snd_emu10k1_ipcm_peek(emu, ipcm);
+		if (res == 0 && copy_to_user(argp, ipcm, sizeof(*ipcm))) {
+			kfree(ipcm);
+			return -EFAULT;
+		}
+		kfree(ipcm);
+		return res;
+	case SNDRV_EMU10K1_IOCTL_TRAM_SETUP:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		if (get_user(addr, (unsigned int __user *)argp))
+			return -EFAULT;
+		mutex_lock(&emu->fx8010.lock);
+		res = snd_emu10k1_fx8010_tram_setup(emu, addr);
+		mutex_unlock(&emu->fx8010.lock);
+		return res;
+	case SNDRV_EMU10K1_IOCTL_STOP:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		if (emu->audigy)
+			snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP);
+		else
+			snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP);
+		return 0;
+	case SNDRV_EMU10K1_IOCTL_CONTINUE:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		if (emu->audigy)
+			snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg = 0);
+		else
+			snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg = 0);
+		return 0;
+	case SNDRV_EMU10K1_IOCTL_ZERO_TRAM_COUNTER:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		if (emu->audigy)
+			snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg | A_DBG_ZC);
+		else
+			snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg | EMU10K1_DBG_ZC);
+		udelay(10);
+		if (emu->audigy)
+			snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg);
+		else
+			snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg);
+		return 0;
+	case SNDRV_EMU10K1_IOCTL_SINGLE_STEP:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		if (get_user(addr, (unsigned int __user *)argp))
+			return -EFAULT;
+		if (addr > 0x1ff)
+			return -EINVAL;
+		if (emu->audigy)
+			snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP | addr);
+		else
+			snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP | addr);
+		udelay(10);
+		if (emu->audigy)
+			snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP | A_DBG_STEP_ADDR | addr);
+		else
+			snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP | EMU10K1_DBG_STEP | addr);
+		return 0;
+	case SNDRV_EMU10K1_IOCTL_DBG_READ:
+		if (emu->audigy)
+			addr = snd_emu10k1_ptr_read(emu, A_DBG, 0);
+		else
+			addr = snd_emu10k1_ptr_read(emu, DBG, 0);
+		if (put_user(addr, (unsigned int __user *)argp))
+			return -EFAULT;
+		return 0;
+	}
+	return -ENOTTY;
+}
+
+static int snd_emu10k1_fx8010_release(struct snd_hwdep * hw, struct file *file)
+{
+	return 0;
+}
+
+int __devinit snd_emu10k1_fx8010_new(struct snd_emu10k1 *emu, int device, struct snd_hwdep ** rhwdep)
+{
+	struct snd_hwdep *hw;
+	int err;
+	
+	if (rhwdep)
+		*rhwdep = NULL;
+	if ((err = snd_hwdep_new(emu->card, "FX8010", device, &hw)) < 0)
+		return err;
+	strcpy(hw->name, "EMU10K1 (FX8010)");
+	hw->iface = SNDRV_HWDEP_IFACE_EMU10K1;
+	hw->ops.open = snd_emu10k1_fx8010_open;
+	hw->ops.ioctl = snd_emu10k1_fx8010_ioctl;
+	hw->ops.release = snd_emu10k1_fx8010_release;
+	hw->private_data = emu;
+	if (rhwdep)
+		*rhwdep = hw;
+	return 0;
+}
+
+#ifdef CONFIG_PM
+int __devinit snd_emu10k1_efx_alloc_pm_buffer(struct snd_emu10k1 *emu)
+{
+	int len;
+
+	len = emu->audigy ? 0x200 : 0x100;
+	emu->saved_gpr = kmalloc(len * 4, GFP_KERNEL);
+	if (! emu->saved_gpr)
+		return -ENOMEM;
+	len = emu->audigy ? 0x100 : 0xa0;
+	emu->tram_val_saved = kmalloc(len * 4, GFP_KERNEL);
+	emu->tram_addr_saved = kmalloc(len * 4, GFP_KERNEL);
+	if (! emu->tram_val_saved || ! emu->tram_addr_saved)
+		return -ENOMEM;
+	len = emu->audigy ? 2 * 1024 : 2 * 512;
+	emu->saved_icode = vmalloc(len * 4);
+	if (! emu->saved_icode)
+		return -ENOMEM;
+	return 0;
+}
+
+void snd_emu10k1_efx_free_pm_buffer(struct snd_emu10k1 *emu)
+{
+	kfree(emu->saved_gpr);
+	kfree(emu->tram_val_saved);
+	kfree(emu->tram_addr_saved);
+	vfree(emu->saved_icode);
+}
+
+/*
+ * save/restore GPR, TRAM and codes
+ */
+void snd_emu10k1_efx_suspend(struct snd_emu10k1 *emu)
+{
+	int i, len;
+
+	len = emu->audigy ? 0x200 : 0x100;
+	for (i = 0; i < len; i++)
+		emu->saved_gpr[i] = snd_emu10k1_ptr_read(emu, emu->gpr_base + i, 0);
+
+	len = emu->audigy ? 0x100 : 0xa0;
+	for (i = 0; i < len; i++) {
+		emu->tram_val_saved[i] = snd_emu10k1_ptr_read(emu, TANKMEMDATAREGBASE + i, 0);
+		emu->tram_addr_saved[i] = snd_emu10k1_ptr_read(emu, TANKMEMADDRREGBASE + i, 0);
+		if (emu->audigy) {
+			emu->tram_addr_saved[i] >>= 12;
+			emu->tram_addr_saved[i] |=
+				snd_emu10k1_ptr_read(emu, A_TANKMEMCTLREGBASE + i, 0) << 20;
+		}
+	}
+
+	len = emu->audigy ? 2 * 1024 : 2 * 512;
+	for (i = 0; i < len; i++)
+		emu->saved_icode[i] = snd_emu10k1_efx_read(emu, i);
+}
+
+void snd_emu10k1_efx_resume(struct snd_emu10k1 *emu)
+{
+	int i, len;
+
+	/* set up TRAM */
+	if (emu->fx8010.etram_pages.bytes > 0) {
+		unsigned size, size_reg = 0;
+		size = emu->fx8010.etram_pages.bytes / 2;
+		size = (size - 1) >> 13;
+		while (size) {
+			size >>= 1;
+			size_reg++;
+		}
+		outl(HCFG_LOCKTANKCACHE_MASK | inl(emu->port + HCFG), emu->port + HCFG);
+		snd_emu10k1_ptr_write(emu, TCB, 0, emu->fx8010.etram_pages.addr);
+		snd_emu10k1_ptr_write(emu, TCBS, 0, size_reg);
+		outl(inl(emu->port + HCFG) & ~HCFG_LOCKTANKCACHE_MASK, emu->port + HCFG);
+	}
+
+	if (emu->audigy)
+		snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg | A_DBG_SINGLE_STEP);
+	else
+		snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg | EMU10K1_DBG_SINGLE_STEP);
+
+	len = emu->audigy ? 0x200 : 0x100;
+	for (i = 0; i < len; i++)
+		snd_emu10k1_ptr_write(emu, emu->gpr_base + i, 0, emu->saved_gpr[i]);
+
+	len = emu->audigy ? 0x100 : 0xa0;
+	for (i = 0; i < len; i++) {
+		snd_emu10k1_ptr_write(emu, TANKMEMDATAREGBASE + i, 0,
+				      emu->tram_val_saved[i]);
+		if (! emu->audigy)
+			snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + i, 0,
+					      emu->tram_addr_saved[i]);
+		else {
+			snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + i, 0,
+					      emu->tram_addr_saved[i] << 12);
+			snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + i, 0,
+					      emu->tram_addr_saved[i] >> 20);
+		}
+	}
+
+	len = emu->audigy ? 2 * 1024 : 2 * 512;
+	for (i = 0; i < len; i++)
+		snd_emu10k1_efx_write(emu, i, emu->saved_icode[i]);
+
+	/* start FX processor when the DSP code is updated */
+	if (emu->audigy)
+		snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg);
+	else
+		snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg);
+}
+#endif
diff --git a/linux/sound/pci/emu10k1/emumixer.c b/linux/sound/pci/emu10k1/emumixer.c
new file mode 100644
index 0000000..05afe06
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emumixer.c
@@ -0,0 +1,2137 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ *                   Takashi Iwai <tiwai@suse.de>
+ *                   Creative Labs, Inc.
+ *  Routines for control of EMU10K1 chips / mixer routines
+ *  Multichannel PCM support Copyright (c) Lee Revell <rlrevell@joe-job.com>
+ *
+ *  Copyright (c) by James Courtier-Dutton <James@superbug.co.uk>
+ *  	Added EMU 1010 support.
+ *
+ *  BUGS:
+ *    --
+ *
+ *  TODO:
+ *    --
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#include <linux/delay.h>
+#include <sound/tlv.h>
+
+#include "p17v.h"
+
+#define AC97_ID_STAC9758	0x83847658
+
+static const DECLARE_TLV_DB_SCALE(snd_audigy_db_scale2, -10350, 50, 1); /* WM8775 gain scale */
+
+static int snd_emu10k1_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_emu10k1_spdif_get(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	unsigned long flags;
+
+	/* Limit: emu->spdif_bits */
+	if (idx >= 3)
+		return -EINVAL;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	ucontrol->value.iec958.status[0] = (emu->spdif_bits[idx] >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (emu->spdif_bits[idx] >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (emu->spdif_bits[idx] >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (emu->spdif_bits[idx] >> 24) & 0xff;
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return 0;
+}
+
+static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0xff;
+	return 0;
+}
+
+/*
+ * Items labels in enum mixer controls assigning source data to
+ * each destination
+ */
+static char *emu1010_src_texts[] = { 
+	"Silence",
+	"Dock Mic A",
+	"Dock Mic B",
+	"Dock ADC1 Left",
+	"Dock ADC1 Right",
+	"Dock ADC2 Left",
+	"Dock ADC2 Right",
+	"Dock ADC3 Left",
+	"Dock ADC3 Right",
+	"0202 ADC Left",
+	"0202 ADC Right",
+	"0202 SPDIF Left",
+	"0202 SPDIF Right",
+	"ADAT 0",
+	"ADAT 1",
+	"ADAT 2",
+	"ADAT 3",
+	"ADAT 4",
+	"ADAT 5",
+	"ADAT 6",
+	"ADAT 7",
+	"DSP 0",
+	"DSP 1",
+	"DSP 2",
+	"DSP 3",
+	"DSP 4",
+	"DSP 5",
+	"DSP 6",
+	"DSP 7",
+	"DSP 8",
+	"DSP 9",
+	"DSP 10",
+	"DSP 11",
+	"DSP 12",
+	"DSP 13",
+	"DSP 14",
+	"DSP 15",
+	"DSP 16",
+	"DSP 17",
+	"DSP 18",
+	"DSP 19",
+	"DSP 20",
+	"DSP 21",
+	"DSP 22",
+	"DSP 23",
+	"DSP 24",
+	"DSP 25",
+	"DSP 26",
+	"DSP 27",
+	"DSP 28",
+	"DSP 29",
+	"DSP 30",
+	"DSP 31",
+};
+
+/* 1616(m) cardbus */
+
+static char *emu1616_src_texts[] = {
+	"Silence",
+	"Dock Mic A",
+	"Dock Mic B",
+	"Dock ADC1 Left",
+	"Dock ADC1 Right",
+	"Dock ADC2 Left",
+	"Dock ADC2 Right",
+	"Dock SPDIF Left",
+	"Dock SPDIF Right",
+	"ADAT 0",
+	"ADAT 1",
+	"ADAT 2",
+	"ADAT 3",
+	"ADAT 4",
+	"ADAT 5",
+	"ADAT 6",
+	"ADAT 7",
+	"DSP 0",
+	"DSP 1",
+	"DSP 2",
+	"DSP 3",
+	"DSP 4",
+	"DSP 5",
+	"DSP 6",
+	"DSP 7",
+	"DSP 8",
+	"DSP 9",
+	"DSP 10",
+	"DSP 11",
+	"DSP 12",
+	"DSP 13",
+	"DSP 14",
+	"DSP 15",
+	"DSP 16",
+	"DSP 17",
+	"DSP 18",
+	"DSP 19",
+	"DSP 20",
+	"DSP 21",
+	"DSP 22",
+	"DSP 23",
+	"DSP 24",
+	"DSP 25",
+	"DSP 26",
+	"DSP 27",
+	"DSP 28",
+	"DSP 29",
+	"DSP 30",
+	"DSP 31",
+};
+
+
+/*
+ * List of data sources available for each destination
+ */
+static unsigned int emu1010_src_regs[] = {
+	EMU_SRC_SILENCE,/* 0 */
+	EMU_SRC_DOCK_MIC_A1, /* 1 */
+	EMU_SRC_DOCK_MIC_B1, /* 2 */
+	EMU_SRC_DOCK_ADC1_LEFT1, /* 3 */
+	EMU_SRC_DOCK_ADC1_RIGHT1, /* 4 */
+	EMU_SRC_DOCK_ADC2_LEFT1, /* 5 */
+	EMU_SRC_DOCK_ADC2_RIGHT1, /* 6 */
+	EMU_SRC_DOCK_ADC3_LEFT1, /* 7 */
+	EMU_SRC_DOCK_ADC3_RIGHT1, /* 8 */
+	EMU_SRC_HAMOA_ADC_LEFT1, /* 9 */
+	EMU_SRC_HAMOA_ADC_RIGHT1, /* 10 */
+	EMU_SRC_HANA_SPDIF_LEFT1, /* 11 */
+	EMU_SRC_HANA_SPDIF_RIGHT1, /* 12 */
+	EMU_SRC_HANA_ADAT, /* 13 */
+	EMU_SRC_HANA_ADAT+1, /* 14 */
+	EMU_SRC_HANA_ADAT+2, /* 15 */
+	EMU_SRC_HANA_ADAT+3, /* 16 */
+	EMU_SRC_HANA_ADAT+4, /* 17 */
+	EMU_SRC_HANA_ADAT+5, /* 18 */
+	EMU_SRC_HANA_ADAT+6, /* 19 */
+	EMU_SRC_HANA_ADAT+7, /* 20 */
+	EMU_SRC_ALICE_EMU32A, /* 21 */
+	EMU_SRC_ALICE_EMU32A+1, /* 22 */
+	EMU_SRC_ALICE_EMU32A+2, /* 23 */
+	EMU_SRC_ALICE_EMU32A+3, /* 24 */
+	EMU_SRC_ALICE_EMU32A+4, /* 25 */
+	EMU_SRC_ALICE_EMU32A+5, /* 26 */
+	EMU_SRC_ALICE_EMU32A+6, /* 27 */
+	EMU_SRC_ALICE_EMU32A+7, /* 28 */
+	EMU_SRC_ALICE_EMU32A+8, /* 29 */
+	EMU_SRC_ALICE_EMU32A+9, /* 30 */
+	EMU_SRC_ALICE_EMU32A+0xa, /* 31 */
+	EMU_SRC_ALICE_EMU32A+0xb, /* 32 */
+	EMU_SRC_ALICE_EMU32A+0xc, /* 33 */
+	EMU_SRC_ALICE_EMU32A+0xd, /* 34 */
+	EMU_SRC_ALICE_EMU32A+0xe, /* 35 */
+	EMU_SRC_ALICE_EMU32A+0xf, /* 36 */
+	EMU_SRC_ALICE_EMU32B, /* 37 */
+	EMU_SRC_ALICE_EMU32B+1, /* 38 */
+	EMU_SRC_ALICE_EMU32B+2, /* 39 */
+	EMU_SRC_ALICE_EMU32B+3, /* 40 */
+	EMU_SRC_ALICE_EMU32B+4, /* 41 */
+	EMU_SRC_ALICE_EMU32B+5, /* 42 */
+	EMU_SRC_ALICE_EMU32B+6, /* 43 */
+	EMU_SRC_ALICE_EMU32B+7, /* 44 */
+	EMU_SRC_ALICE_EMU32B+8, /* 45 */
+	EMU_SRC_ALICE_EMU32B+9, /* 46 */
+	EMU_SRC_ALICE_EMU32B+0xa, /* 47 */
+	EMU_SRC_ALICE_EMU32B+0xb, /* 48 */
+	EMU_SRC_ALICE_EMU32B+0xc, /* 49 */
+	EMU_SRC_ALICE_EMU32B+0xd, /* 50 */
+	EMU_SRC_ALICE_EMU32B+0xe, /* 51 */
+	EMU_SRC_ALICE_EMU32B+0xf, /* 52 */
+};
+
+/* 1616(m) cardbus */
+static unsigned int emu1616_src_regs[] = {
+	EMU_SRC_SILENCE,
+	EMU_SRC_DOCK_MIC_A1,
+	EMU_SRC_DOCK_MIC_B1,
+	EMU_SRC_DOCK_ADC1_LEFT1,
+	EMU_SRC_DOCK_ADC1_RIGHT1,
+	EMU_SRC_DOCK_ADC2_LEFT1,
+	EMU_SRC_DOCK_ADC2_RIGHT1,
+	EMU_SRC_MDOCK_SPDIF_LEFT1,
+	EMU_SRC_MDOCK_SPDIF_RIGHT1,
+	EMU_SRC_MDOCK_ADAT,
+	EMU_SRC_MDOCK_ADAT+1,
+	EMU_SRC_MDOCK_ADAT+2,
+	EMU_SRC_MDOCK_ADAT+3,
+	EMU_SRC_MDOCK_ADAT+4,
+	EMU_SRC_MDOCK_ADAT+5,
+	EMU_SRC_MDOCK_ADAT+6,
+	EMU_SRC_MDOCK_ADAT+7,
+	EMU_SRC_ALICE_EMU32A,
+	EMU_SRC_ALICE_EMU32A+1,
+	EMU_SRC_ALICE_EMU32A+2,
+	EMU_SRC_ALICE_EMU32A+3,
+	EMU_SRC_ALICE_EMU32A+4,
+	EMU_SRC_ALICE_EMU32A+5,
+	EMU_SRC_ALICE_EMU32A+6,
+	EMU_SRC_ALICE_EMU32A+7,
+	EMU_SRC_ALICE_EMU32A+8,
+	EMU_SRC_ALICE_EMU32A+9,
+	EMU_SRC_ALICE_EMU32A+0xa,
+	EMU_SRC_ALICE_EMU32A+0xb,
+	EMU_SRC_ALICE_EMU32A+0xc,
+	EMU_SRC_ALICE_EMU32A+0xd,
+	EMU_SRC_ALICE_EMU32A+0xe,
+	EMU_SRC_ALICE_EMU32A+0xf,
+	EMU_SRC_ALICE_EMU32B,
+	EMU_SRC_ALICE_EMU32B+1,
+	EMU_SRC_ALICE_EMU32B+2,
+	EMU_SRC_ALICE_EMU32B+3,
+	EMU_SRC_ALICE_EMU32B+4,
+	EMU_SRC_ALICE_EMU32B+5,
+	EMU_SRC_ALICE_EMU32B+6,
+	EMU_SRC_ALICE_EMU32B+7,
+	EMU_SRC_ALICE_EMU32B+8,
+	EMU_SRC_ALICE_EMU32B+9,
+	EMU_SRC_ALICE_EMU32B+0xa,
+	EMU_SRC_ALICE_EMU32B+0xb,
+	EMU_SRC_ALICE_EMU32B+0xc,
+	EMU_SRC_ALICE_EMU32B+0xd,
+	EMU_SRC_ALICE_EMU32B+0xe,
+	EMU_SRC_ALICE_EMU32B+0xf,
+};
+
+/*
+ * Data destinations - physical EMU outputs.
+ * Each destination has an enum mixer control to choose a data source
+ */
+static unsigned int emu1010_output_dst[] = {
+	EMU_DST_DOCK_DAC1_LEFT1, /* 0 */
+	EMU_DST_DOCK_DAC1_RIGHT1, /* 1 */
+	EMU_DST_DOCK_DAC2_LEFT1, /* 2 */
+	EMU_DST_DOCK_DAC2_RIGHT1, /* 3 */
+	EMU_DST_DOCK_DAC3_LEFT1, /* 4 */
+	EMU_DST_DOCK_DAC3_RIGHT1, /* 5 */
+	EMU_DST_DOCK_DAC4_LEFT1, /* 6 */
+	EMU_DST_DOCK_DAC4_RIGHT1, /* 7 */
+	EMU_DST_DOCK_PHONES_LEFT1, /* 8 */
+	EMU_DST_DOCK_PHONES_RIGHT1, /* 9 */
+	EMU_DST_DOCK_SPDIF_LEFT1, /* 10 */
+	EMU_DST_DOCK_SPDIF_RIGHT1, /* 11 */
+	EMU_DST_HANA_SPDIF_LEFT1, /* 12 */
+	EMU_DST_HANA_SPDIF_RIGHT1, /* 13 */
+	EMU_DST_HAMOA_DAC_LEFT1, /* 14 */
+	EMU_DST_HAMOA_DAC_RIGHT1, /* 15 */
+	EMU_DST_HANA_ADAT, /* 16 */
+	EMU_DST_HANA_ADAT+1, /* 17 */
+	EMU_DST_HANA_ADAT+2, /* 18 */
+	EMU_DST_HANA_ADAT+3, /* 19 */
+	EMU_DST_HANA_ADAT+4, /* 20 */
+	EMU_DST_HANA_ADAT+5, /* 21 */
+	EMU_DST_HANA_ADAT+6, /* 22 */
+	EMU_DST_HANA_ADAT+7, /* 23 */
+};
+
+/* 1616(m) cardbus */
+static unsigned int emu1616_output_dst[] = {
+	EMU_DST_DOCK_DAC1_LEFT1,
+	EMU_DST_DOCK_DAC1_RIGHT1,
+	EMU_DST_DOCK_DAC2_LEFT1,
+	EMU_DST_DOCK_DAC2_RIGHT1,
+	EMU_DST_DOCK_DAC3_LEFT1,
+	EMU_DST_DOCK_DAC3_RIGHT1,
+	EMU_DST_MDOCK_SPDIF_LEFT1,
+	EMU_DST_MDOCK_SPDIF_RIGHT1,
+	EMU_DST_MDOCK_ADAT,
+	EMU_DST_MDOCK_ADAT+1,
+	EMU_DST_MDOCK_ADAT+2,
+	EMU_DST_MDOCK_ADAT+3,
+	EMU_DST_MDOCK_ADAT+4,
+	EMU_DST_MDOCK_ADAT+5,
+	EMU_DST_MDOCK_ADAT+6,
+	EMU_DST_MDOCK_ADAT+7,
+	EMU_DST_MANA_DAC_LEFT,
+	EMU_DST_MANA_DAC_RIGHT,
+};
+
+/*
+ * Data destinations - HANA outputs going to Alice2 (audigy) for
+ *   capture (EMU32 + I2S links)
+ * Each destination has an enum mixer control to choose a data source
+ */
+static unsigned int emu1010_input_dst[] = {
+	EMU_DST_ALICE2_EMU32_0,
+	EMU_DST_ALICE2_EMU32_1,
+	EMU_DST_ALICE2_EMU32_2,
+	EMU_DST_ALICE2_EMU32_3,
+	EMU_DST_ALICE2_EMU32_4,
+	EMU_DST_ALICE2_EMU32_5,
+	EMU_DST_ALICE2_EMU32_6,
+	EMU_DST_ALICE2_EMU32_7,
+	EMU_DST_ALICE2_EMU32_8,
+	EMU_DST_ALICE2_EMU32_9,
+	EMU_DST_ALICE2_EMU32_A,
+	EMU_DST_ALICE2_EMU32_B,
+	EMU_DST_ALICE2_EMU32_C,
+	EMU_DST_ALICE2_EMU32_D,
+	EMU_DST_ALICE2_EMU32_E,
+	EMU_DST_ALICE2_EMU32_F,
+	EMU_DST_ALICE_I2S0_LEFT,
+	EMU_DST_ALICE_I2S0_RIGHT,
+	EMU_DST_ALICE_I2S1_LEFT,
+	EMU_DST_ALICE_I2S1_RIGHT,
+	EMU_DST_ALICE_I2S2_LEFT,
+	EMU_DST_ALICE_I2S2_RIGHT,
+};
+
+static int snd_emu1010_input_output_source_info(struct snd_kcontrol *kcontrol,
+						struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	char **items;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) {
+		uinfo->value.enumerated.items = 49;
+		items = emu1616_src_texts;
+	} else {
+		uinfo->value.enumerated.items = 53;
+		items = emu1010_src_texts;
+	}
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+			uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+	       items[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_emu1010_output_source_get(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int channel;
+
+	channel = (kcontrol->private_value) & 0xff;
+	/* Limit: emu1010_output_dst, emu->emu1010.output_source */
+	if (channel >= 24 ||
+	    (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 &&
+	     channel >= 18))
+		return -EINVAL;
+	ucontrol->value.enumerated.item[0] = emu->emu1010.output_source[channel];
+	return 0;
+}
+
+static int snd_emu1010_output_source_put(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	unsigned int channel;
+
+	val = ucontrol->value.enumerated.item[0];
+	if (val >= 53 ||
+	    (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 &&
+	     val >= 49))
+		return -EINVAL;
+	channel = (kcontrol->private_value) & 0xff;
+	/* Limit: emu1010_output_dst, emu->emu1010.output_source */
+	if (channel >= 24 ||
+	    (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 &&
+	     channel >= 18))
+		return -EINVAL;
+	if (emu->emu1010.output_source[channel] == val)
+		return 0;
+	emu->emu1010.output_source[channel] = val;
+	if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616)
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			emu1616_output_dst[channel], emu1616_src_regs[val]);
+	else
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			emu1010_output_dst[channel], emu1010_src_regs[val]);
+	return 1;
+}
+
+static int snd_emu1010_input_source_get(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int channel;
+
+	channel = (kcontrol->private_value) & 0xff;
+	/* Limit: emu1010_input_dst, emu->emu1010.input_source */
+	if (channel >= 22)
+		return -EINVAL;
+	ucontrol->value.enumerated.item[0] = emu->emu1010.input_source[channel];
+	return 0;
+}
+
+static int snd_emu1010_input_source_put(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	unsigned int channel;
+
+	val = ucontrol->value.enumerated.item[0];
+	if (val >= 53 ||
+	    (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 &&
+	     val >= 49))
+		return -EINVAL;
+	channel = (kcontrol->private_value) & 0xff;
+	/* Limit: emu1010_input_dst, emu->emu1010.input_source */
+	if (channel >= 22)
+		return -EINVAL;
+	if (emu->emu1010.input_source[channel] == val)
+		return 0;
+	emu->emu1010.input_source[channel] = val;
+	if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616)
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			emu1010_input_dst[channel], emu1616_src_regs[val]);
+	else
+		snd_emu1010_fpga_link_dst_src_write(emu,
+			emu1010_input_dst[channel], emu1010_src_regs[val]);
+	return 1;
+}
+
+#define EMU1010_SOURCE_OUTPUT(xname,chid) \
+{								\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,	\
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,		\
+	.info =  snd_emu1010_input_output_source_info,		\
+	.get =   snd_emu1010_output_source_get,			\
+	.put =   snd_emu1010_output_source_put,			\
+	.private_value = chid					\
+}
+
+static struct snd_kcontrol_new snd_emu1010_output_enum_ctls[] __devinitdata = {
+	EMU1010_SOURCE_OUTPUT("Dock DAC1 Left Playback Enum", 0),
+	EMU1010_SOURCE_OUTPUT("Dock DAC1 Right Playback Enum", 1),
+	EMU1010_SOURCE_OUTPUT("Dock DAC2 Left Playback Enum", 2),
+	EMU1010_SOURCE_OUTPUT("Dock DAC2 Right Playback Enum", 3),
+	EMU1010_SOURCE_OUTPUT("Dock DAC3 Left Playback Enum", 4),
+	EMU1010_SOURCE_OUTPUT("Dock DAC3 Right Playback Enum", 5),
+	EMU1010_SOURCE_OUTPUT("Dock DAC4 Left Playback Enum", 6),
+	EMU1010_SOURCE_OUTPUT("Dock DAC4 Right Playback Enum", 7),
+	EMU1010_SOURCE_OUTPUT("Dock Phones Left Playback Enum", 8),
+	EMU1010_SOURCE_OUTPUT("Dock Phones Right Playback Enum", 9),
+	EMU1010_SOURCE_OUTPUT("Dock SPDIF Left Playback Enum", 0xa),
+	EMU1010_SOURCE_OUTPUT("Dock SPDIF Right Playback Enum", 0xb),
+	EMU1010_SOURCE_OUTPUT("1010 SPDIF Left Playback Enum", 0xc),
+	EMU1010_SOURCE_OUTPUT("1010 SPDIF Right Playback Enum", 0xd),
+	EMU1010_SOURCE_OUTPUT("0202 DAC Left Playback Enum", 0xe),
+	EMU1010_SOURCE_OUTPUT("0202 DAC Right Playback Enum", 0xf),
+	EMU1010_SOURCE_OUTPUT("1010 ADAT 0 Playback Enum", 0x10),
+	EMU1010_SOURCE_OUTPUT("1010 ADAT 1 Playback Enum", 0x11),
+	EMU1010_SOURCE_OUTPUT("1010 ADAT 2 Playback Enum", 0x12),
+	EMU1010_SOURCE_OUTPUT("1010 ADAT 3 Playback Enum", 0x13),
+	EMU1010_SOURCE_OUTPUT("1010 ADAT 4 Playback Enum", 0x14),
+	EMU1010_SOURCE_OUTPUT("1010 ADAT 5 Playback Enum", 0x15),
+	EMU1010_SOURCE_OUTPUT("1010 ADAT 6 Playback Enum", 0x16),
+	EMU1010_SOURCE_OUTPUT("1010 ADAT 7 Playback Enum", 0x17),
+};
+
+
+/* 1616(m) cardbus */
+static struct snd_kcontrol_new snd_emu1616_output_enum_ctls[] __devinitdata = {
+	EMU1010_SOURCE_OUTPUT("Dock DAC1 Left Playback Enum", 0),
+	EMU1010_SOURCE_OUTPUT("Dock DAC1 Right Playback Enum", 1),
+	EMU1010_SOURCE_OUTPUT("Dock DAC2 Left Playback Enum", 2),
+	EMU1010_SOURCE_OUTPUT("Dock DAC2 Right Playback Enum", 3),
+	EMU1010_SOURCE_OUTPUT("Dock DAC3 Left Playback Enum", 4),
+	EMU1010_SOURCE_OUTPUT("Dock DAC3 Right Playback Enum", 5),
+	EMU1010_SOURCE_OUTPUT("Dock SPDIF Left Playback Enum", 6),
+	EMU1010_SOURCE_OUTPUT("Dock SPDIF Right Playback Enum", 7),
+	EMU1010_SOURCE_OUTPUT("Dock ADAT 0 Playback Enum", 8),
+	EMU1010_SOURCE_OUTPUT("Dock ADAT 1 Playback Enum", 9),
+	EMU1010_SOURCE_OUTPUT("Dock ADAT 2 Playback Enum", 0xa),
+	EMU1010_SOURCE_OUTPUT("Dock ADAT 3 Playback Enum", 0xb),
+	EMU1010_SOURCE_OUTPUT("Dock ADAT 4 Playback Enum", 0xc),
+	EMU1010_SOURCE_OUTPUT("Dock ADAT 5 Playback Enum", 0xd),
+	EMU1010_SOURCE_OUTPUT("Dock ADAT 6 Playback Enum", 0xe),
+	EMU1010_SOURCE_OUTPUT("Dock ADAT 7 Playback Enum", 0xf),
+	EMU1010_SOURCE_OUTPUT("Mana DAC Left Playback Enum", 0x10),
+	EMU1010_SOURCE_OUTPUT("Mana DAC Right Playback Enum", 0x11),
+};
+
+
+#define EMU1010_SOURCE_INPUT(xname,chid) \
+{								\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,	\
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,		\
+	.info =  snd_emu1010_input_output_source_info,		\
+	.get =   snd_emu1010_input_source_get,			\
+	.put =   snd_emu1010_input_source_put,			\
+	.private_value = chid					\
+}
+
+static struct snd_kcontrol_new snd_emu1010_input_enum_ctls[] __devinitdata = {
+	EMU1010_SOURCE_INPUT("DSP 0 Capture Enum", 0),
+	EMU1010_SOURCE_INPUT("DSP 1 Capture Enum", 1),
+	EMU1010_SOURCE_INPUT("DSP 2 Capture Enum", 2),
+	EMU1010_SOURCE_INPUT("DSP 3 Capture Enum", 3),
+	EMU1010_SOURCE_INPUT("DSP 4 Capture Enum", 4),
+	EMU1010_SOURCE_INPUT("DSP 5 Capture Enum", 5),
+	EMU1010_SOURCE_INPUT("DSP 6 Capture Enum", 6),
+	EMU1010_SOURCE_INPUT("DSP 7 Capture Enum", 7),
+	EMU1010_SOURCE_INPUT("DSP 8 Capture Enum", 8),
+	EMU1010_SOURCE_INPUT("DSP 9 Capture Enum", 9),
+	EMU1010_SOURCE_INPUT("DSP A Capture Enum", 0xa),
+	EMU1010_SOURCE_INPUT("DSP B Capture Enum", 0xb),
+	EMU1010_SOURCE_INPUT("DSP C Capture Enum", 0xc),
+	EMU1010_SOURCE_INPUT("DSP D Capture Enum", 0xd),
+	EMU1010_SOURCE_INPUT("DSP E Capture Enum", 0xe),
+	EMU1010_SOURCE_INPUT("DSP F Capture Enum", 0xf),
+	EMU1010_SOURCE_INPUT("DSP 10 Capture Enum", 0x10),
+	EMU1010_SOURCE_INPUT("DSP 11 Capture Enum", 0x11),
+	EMU1010_SOURCE_INPUT("DSP 12 Capture Enum", 0x12),
+	EMU1010_SOURCE_INPUT("DSP 13 Capture Enum", 0x13),
+	EMU1010_SOURCE_INPUT("DSP 14 Capture Enum", 0x14),
+	EMU1010_SOURCE_INPUT("DSP 15 Capture Enum", 0x15),
+};
+
+
+
+#define snd_emu1010_adc_pads_info	snd_ctl_boolean_mono_info
+
+static int snd_emu1010_adc_pads_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int mask = kcontrol->private_value & 0xff;
+	ucontrol->value.integer.value[0] = (emu->emu1010.adc_pads & mask) ? 1 : 0;
+	return 0;
+}
+
+static int snd_emu1010_adc_pads_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int mask = kcontrol->private_value & 0xff;
+	unsigned int val, cache;
+	val = ucontrol->value.integer.value[0];
+	cache = emu->emu1010.adc_pads;
+	if (val == 1) 
+		cache = cache | mask;
+	else
+		cache = cache & ~mask;
+	if (cache != emu->emu1010.adc_pads) {
+		snd_emu1010_fpga_write(emu, EMU_HANA_ADC_PADS, cache );
+	        emu->emu1010.adc_pads = cache;
+	}
+
+	return 0;
+}
+
+
+
+#define EMU1010_ADC_PADS(xname,chid) \
+{								\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,	\
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,		\
+	.info =  snd_emu1010_adc_pads_info,			\
+	.get =   snd_emu1010_adc_pads_get,			\
+	.put =   snd_emu1010_adc_pads_put,			\
+	.private_value = chid					\
+}
+
+static struct snd_kcontrol_new snd_emu1010_adc_pads[] __devinitdata = {
+	EMU1010_ADC_PADS("ADC1 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD1),
+	EMU1010_ADC_PADS("ADC2 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD2),
+	EMU1010_ADC_PADS("ADC3 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD3),
+	EMU1010_ADC_PADS("ADC1 14dB PAD 0202 Capture Switch", EMU_HANA_0202_ADC_PAD1),
+};
+
+#define snd_emu1010_dac_pads_info	snd_ctl_boolean_mono_info
+
+static int snd_emu1010_dac_pads_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int mask = kcontrol->private_value & 0xff;
+	ucontrol->value.integer.value[0] = (emu->emu1010.dac_pads & mask) ? 1 : 0;
+	return 0;
+}
+
+static int snd_emu1010_dac_pads_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int mask = kcontrol->private_value & 0xff;
+	unsigned int val, cache;
+	val = ucontrol->value.integer.value[0];
+	cache = emu->emu1010.dac_pads;
+	if (val == 1) 
+		cache = cache | mask;
+	else
+		cache = cache & ~mask;
+	if (cache != emu->emu1010.dac_pads) {
+		snd_emu1010_fpga_write(emu, EMU_HANA_DAC_PADS, cache );
+	        emu->emu1010.dac_pads = cache;
+	}
+
+	return 0;
+}
+
+
+
+#define EMU1010_DAC_PADS(xname,chid) \
+{								\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,	\
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,		\
+	.info =  snd_emu1010_dac_pads_info,			\
+	.get =   snd_emu1010_dac_pads_get,			\
+	.put =   snd_emu1010_dac_pads_put,			\
+	.private_value = chid					\
+}
+
+static struct snd_kcontrol_new snd_emu1010_dac_pads[] __devinitdata = {
+	EMU1010_DAC_PADS("DAC1 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD1),
+	EMU1010_DAC_PADS("DAC2 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD2),
+	EMU1010_DAC_PADS("DAC3 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD3),
+	EMU1010_DAC_PADS("DAC4 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD4),
+	EMU1010_DAC_PADS("DAC1 0202 14dB PAD Playback Switch", EMU_HANA_0202_DAC_PAD1),
+};
+
+
+static int snd_emu1010_internal_clock_info(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = {
+		"44100", "48000", "SPDIF", "ADAT"
+	};
+		
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+	
+	
+}
+
+static int snd_emu1010_internal_clock_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = emu->emu1010.internal_clock;
+	return 0;
+}
+
+static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change = 0;
+
+	val = ucontrol->value.enumerated.item[0] ;
+	/* Limit: uinfo->value.enumerated.items = 4; */
+	if (val >= 4)
+		return -EINVAL;
+	change = (emu->emu1010.internal_clock != val);
+	if (change) {
+		emu->emu1010.internal_clock = val;
+		switch (val) {
+		case 0:
+			/* 44100 */
+			/* Mute all */
+			snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE );
+			/* Default fallback clock 48kHz */
+			snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_44_1K );
+			/* Word Clock source, Internal 44.1kHz x1 */
+			snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK,
+			EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X );
+			/* Set LEDs on Audio Dock */
+			snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2,
+				EMU_HANA_DOCK_LEDS_2_44K | EMU_HANA_DOCK_LEDS_2_LOCK );
+			/* Allow DLL to settle */
+			msleep(10);
+			/* Unmute all */
+			snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE );
+			break;
+		case 1:
+			/* 48000 */
+			/* Mute all */
+			snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE );
+			/* Default fallback clock 48kHz */
+			snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K );
+			/* Word Clock source, Internal 48kHz x1 */
+			snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK,
+				EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X );
+			/* Set LEDs on Audio Dock */
+			snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2,
+				EMU_HANA_DOCK_LEDS_2_48K | EMU_HANA_DOCK_LEDS_2_LOCK );
+			/* Allow DLL to settle */
+			msleep(10);
+			/* Unmute all */
+			snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE );
+			break;
+			
+		case 2: /* Take clock from S/PDIF IN */
+			/* Mute all */
+			snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE );
+			/* Default fallback clock 48kHz */
+			snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K );
+			/* Word Clock source, sync to S/PDIF input */
+			snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK,
+				EMU_HANA_WCLOCK_HANA_SPDIF_IN | EMU_HANA_WCLOCK_1X );
+			/* Set LEDs on Audio Dock */
+			snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2,
+				EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_LOCK );
+			/* FIXME: We should set EMU_HANA_DOCK_LEDS_2_LOCK only when clock signal is present and valid */	
+			/* Allow DLL to settle */
+			msleep(10);
+			/* Unmute all */
+			snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE );
+			break;
+		
+		case 3: 			
+			/* Take clock from ADAT IN */
+			/* Mute all */
+			snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE );
+			/* Default fallback clock 48kHz */
+			snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K );
+			/* Word Clock source, sync to ADAT input */
+			snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK,
+				EMU_HANA_WCLOCK_HANA_ADAT_IN | EMU_HANA_WCLOCK_1X );
+			/* Set LEDs on Audio Dock */
+			snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_LOCK );
+			/* FIXME: We should set EMU_HANA_DOCK_LEDS_2_LOCK only when clock signal is present and valid */	
+			/* Allow DLL to settle */
+			msleep(10);
+			/*   Unmute all */
+			snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE );
+			 
+			
+			break;		
+		}
+	}
+        return change;
+}
+
+static struct snd_kcontrol_new snd_emu1010_internal_clock =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Clock Internal Rate",
+	.count =	1,
+	.info =         snd_emu1010_internal_clock_info,
+	.get =          snd_emu1010_internal_clock_get,
+	.put =          snd_emu1010_internal_clock_put
+};
+
+static int snd_audigy_i2c_capture_source_info(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_info *uinfo)
+{
+#if 0
+	static char *texts[4] = {
+		"Unknown1", "Unknown2", "Mic", "Line"
+	};
+#endif
+	static char *texts[2] = {
+		"Mic", "Line"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+                uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_audigy_i2c_capture_source_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = emu->i2c_capture_source;
+	return 0;
+}
+
+static int snd_audigy_i2c_capture_source_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int source_id;
+	unsigned int ngain, ogain;
+	u32 gpio;
+	int change = 0;
+	unsigned long flags;
+	u32 source;
+	/* If the capture source has changed,
+	 * update the capture volume from the cached value
+	 * for the particular source.
+	 */
+	source_id = ucontrol->value.enumerated.item[0];
+	/* Limit: uinfo->value.enumerated.items = 2; */
+	/*        emu->i2c_capture_volume */
+	if (source_id >= 2)
+		return -EINVAL;
+	change = (emu->i2c_capture_source != source_id);
+	if (change) {
+		snd_emu10k1_i2c_write(emu, ADC_MUX, 0); /* Mute input */
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		gpio = inl(emu->port + A_IOCFG);
+		if (source_id==0)
+			outl(gpio | 0x4, emu->port + A_IOCFG);
+		else
+			outl(gpio & ~0x4, emu->port + A_IOCFG);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+
+		ngain = emu->i2c_capture_volume[source_id][0]; /* Left */
+		ogain = emu->i2c_capture_volume[emu->i2c_capture_source][0]; /* Left */
+		if (ngain != ogain)
+			snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCL, ((ngain) & 0xff));
+		ngain = emu->i2c_capture_volume[source_id][1]; /* Right */
+		ogain = emu->i2c_capture_volume[emu->i2c_capture_source][1]; /* Right */
+		if (ngain != ogain)
+			snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCR, ((ngain) & 0xff));
+
+		source = 1 << (source_id + 2);
+		snd_emu10k1_i2c_write(emu, ADC_MUX, source); /* Set source */
+		emu->i2c_capture_source = source_id;
+	}
+        return change;
+}
+
+static struct snd_kcontrol_new snd_audigy_i2c_capture_source =
+{
+		.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =		"Capture Source",
+		.info =		snd_audigy_i2c_capture_source_info,
+		.get =		snd_audigy_i2c_capture_source_get,
+		.put =		snd_audigy_i2c_capture_source_put
+};
+
+static int snd_audigy_i2c_volume_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	return 0;
+}
+
+static int snd_audigy_i2c_volume_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int source_id;
+
+	source_id = kcontrol->private_value;
+	/* Limit: emu->i2c_capture_volume */
+        /*        capture_source: uinfo->value.enumerated.items = 2 */
+	if (source_id >= 2)
+		return -EINVAL;
+
+	ucontrol->value.integer.value[0] = emu->i2c_capture_volume[source_id][0];
+	ucontrol->value.integer.value[1] = emu->i2c_capture_volume[source_id][1];
+	return 0;
+}
+
+static int snd_audigy_i2c_volume_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int ogain;
+	unsigned int ngain;
+	unsigned int source_id;
+	int change = 0;
+
+	source_id = kcontrol->private_value;
+	/* Limit: emu->i2c_capture_volume */
+        /*        capture_source: uinfo->value.enumerated.items = 2 */
+	if (source_id >= 2)
+		return -EINVAL;
+	ogain = emu->i2c_capture_volume[source_id][0]; /* Left */
+	ngain = ucontrol->value.integer.value[0];
+	if (ngain > 0xff)
+		return 0;
+	if (ogain != ngain) {
+		if (emu->i2c_capture_source == source_id)
+			snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCL, ((ngain) & 0xff) );
+		emu->i2c_capture_volume[source_id][0] = ngain;
+		change = 1;
+	}
+	ogain = emu->i2c_capture_volume[source_id][1]; /* Right */
+	ngain = ucontrol->value.integer.value[1];
+	if (ngain > 0xff)
+		return 0;
+	if (ogain != ngain) {
+		if (emu->i2c_capture_source == source_id)
+			snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCR, ((ngain) & 0xff));
+		emu->i2c_capture_volume[source_id][1] = ngain;
+		change = 1;
+	}
+
+	return change;
+}
+
+#define I2C_VOLUME(xname,chid) \
+{								\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,	\
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |		\
+	          SNDRV_CTL_ELEM_ACCESS_TLV_READ,		\
+	.info =  snd_audigy_i2c_volume_info,			\
+	.get =   snd_audigy_i2c_volume_get,			\
+	.put =   snd_audigy_i2c_volume_put,			\
+	.tlv = { .p = snd_audigy_db_scale2 },			\
+	.private_value = chid					\
+}
+
+
+static struct snd_kcontrol_new snd_audigy_i2c_volume_ctls[] __devinitdata = {
+	I2C_VOLUME("Mic Capture Volume", 0),
+	I2C_VOLUME("Line Capture Volume", 0)
+};
+
+#if 0
+static int snd_audigy_spdif_output_rate_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {"44100", "48000", "96000"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_audigy_spdif_output_rate_get(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int tmp;
+	unsigned long flags;
+	
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
+	switch (tmp & A_SPDIF_RATE_MASK) {
+	case A_SPDIF_44100:
+		ucontrol->value.enumerated.item[0] = 0;
+		break;
+	case A_SPDIF_48000:
+		ucontrol->value.enumerated.item[0] = 1;
+		break;
+	case A_SPDIF_96000:
+		ucontrol->value.enumerated.item[0] = 2;
+		break;
+	default:
+		ucontrol->value.enumerated.item[0] = 1;
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return 0;
+}
+
+static int snd_audigy_spdif_output_rate_put(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int reg, val, tmp;
+	unsigned long flags;
+
+	switch(ucontrol->value.enumerated.item[0]) {
+	case 0:
+		val = A_SPDIF_44100;
+		break;
+	case 1:
+		val = A_SPDIF_48000;
+		break;
+	case 2:
+		val = A_SPDIF_96000;
+		break;
+	default:
+		val = A_SPDIF_48000;
+		break;
+	}
+
+	
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	reg = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
+	tmp = reg & ~A_SPDIF_RATE_MASK;
+	tmp |= val;
+	if ((change = (tmp != reg)))
+		snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp);
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_audigy_spdif_output_rate =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Audigy SPDIF Output Sample Rate",
+	.count =	1,
+	.info =         snd_audigy_spdif_output_rate_info,
+	.get =          snd_audigy_spdif_output_rate_get,
+	.put =          snd_audigy_spdif_output_rate_put
+};
+#endif
+
+static int snd_emu10k1_spdif_put(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	int change;
+	unsigned int val;
+	unsigned long flags;
+
+	/* Limit: emu->spdif_bits */
+	if (idx >= 3)
+		return -EINVAL;
+	val = (ucontrol->value.iec958.status[0] << 0) |
+	      (ucontrol->value.iec958.status[1] << 8) |
+	      (ucontrol->value.iec958.status[2] << 16) |
+	      (ucontrol->value.iec958.status[3] << 24);
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	change = val != emu->spdif_bits[idx];
+	if (change) {
+		snd_emu10k1_ptr_write(emu, SPCS0 + idx, 0, val);
+		emu->spdif_bits[idx] = val;
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_emu10k1_spdif_mask_control =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+	.count =	3,
+	.info =         snd_emu10k1_spdif_info,
+	.get =          snd_emu10k1_spdif_get_mask
+};
+
+static struct snd_kcontrol_new snd_emu10k1_spdif_control =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.count =	3,
+	.info =         snd_emu10k1_spdif_info,
+	.get =          snd_emu10k1_spdif_get,
+	.put =          snd_emu10k1_spdif_put
+};
+
+
+static void update_emu10k1_fxrt(struct snd_emu10k1 *emu, int voice, unsigned char *route)
+{
+	if (emu->audigy) {
+		snd_emu10k1_ptr_write(emu, A_FXRT1, voice,
+				      snd_emu10k1_compose_audigy_fxrt1(route));
+		snd_emu10k1_ptr_write(emu, A_FXRT2, voice,
+				      snd_emu10k1_compose_audigy_fxrt2(route));
+	} else {
+		snd_emu10k1_ptr_write(emu, FXRT, voice,
+				      snd_emu10k1_compose_send_routing(route));
+	}
+}
+
+static void update_emu10k1_send_volume(struct snd_emu10k1 *emu, int voice, unsigned char *volume)
+{
+	snd_emu10k1_ptr_write(emu, PTRX_FXSENDAMOUNT_A, voice, volume[0]);
+	snd_emu10k1_ptr_write(emu, PTRX_FXSENDAMOUNT_B, voice, volume[1]);
+	snd_emu10k1_ptr_write(emu, PSST_FXSENDAMOUNT_C, voice, volume[2]);
+	snd_emu10k1_ptr_write(emu, DSL_FXSENDAMOUNT_D, voice, volume[3]);
+	if (emu->audigy) {
+		unsigned int val = ((unsigned int)volume[4] << 24) |
+			((unsigned int)volume[5] << 16) |
+			((unsigned int)volume[6] << 8) |
+			(unsigned int)volume[7];
+		snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, val);
+	}
+}
+
+/* PCM stream controls */
+
+static int snd_emu10k1_send_routing_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = emu->audigy ? 3*8 : 3*4;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = emu->audigy ? 0x3f : 0x0f;
+	return 0;
+}
+
+static int snd_emu10k1_send_routing_get(struct snd_kcontrol *kcontrol,
+                                        struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned long flags;
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	struct snd_emu10k1_pcm_mixer *mix =
+		&emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int voice, idx;
+	int num_efx = emu->audigy ? 8 : 4;
+	int mask = emu->audigy ? 0x3f : 0x0f;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (voice = 0; voice < 3; voice++)
+		for (idx = 0; idx < num_efx; idx++)
+			ucontrol->value.integer.value[(voice * num_efx) + idx] = 
+				mix->send_routing[voice][idx] & mask;
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return 0;
+}
+
+static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol,
+                                        struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned long flags;
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	struct snd_emu10k1_pcm_mixer *mix =
+		&emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int change = 0, voice, idx, val;
+	int num_efx = emu->audigy ? 8 : 4;
+	int mask = emu->audigy ? 0x3f : 0x0f;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (voice = 0; voice < 3; voice++)
+		for (idx = 0; idx < num_efx; idx++) {
+			val = ucontrol->value.integer.value[(voice * num_efx) + idx] & mask;
+			if (mix->send_routing[voice][idx] != val) {
+				mix->send_routing[voice][idx] = val;
+				change = 1;
+			}
+		}	
+	if (change && mix->epcm) {
+		if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+			update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number,
+					    &mix->send_routing[1][0]);
+			update_emu10k1_fxrt(emu, mix->epcm->voices[1]->number,
+					    &mix->send_routing[2][0]);
+		} else if (mix->epcm->voices[0]) {
+			update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number,
+					    &mix->send_routing[0][0]);
+		}
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_emu10k1_send_routing_control =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "EMU10K1 PCM Send Routing",
+	.count =	32,
+	.info =         snd_emu10k1_send_routing_info,
+	.get =          snd_emu10k1_send_routing_get,
+	.put =          snd_emu10k1_send_routing_put
+};
+
+static int snd_emu10k1_send_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = emu->audigy ? 3*8 : 3*4;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	return 0;
+}
+
+static int snd_emu10k1_send_volume_get(struct snd_kcontrol *kcontrol,
+                                       struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned long flags;
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	struct snd_emu10k1_pcm_mixer *mix =
+		&emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int idx;
+	int num_efx = emu->audigy ? 8 : 4;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < 3*num_efx; idx++)
+		ucontrol->value.integer.value[idx] = mix->send_volume[idx/num_efx][idx%num_efx];
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return 0;
+}
+
+static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol,
+                                       struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned long flags;
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	struct snd_emu10k1_pcm_mixer *mix =
+		&emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int change = 0, idx, val;
+	int num_efx = emu->audigy ? 8 : 4;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < 3*num_efx; idx++) {
+		val = ucontrol->value.integer.value[idx] & 255;
+		if (mix->send_volume[idx/num_efx][idx%num_efx] != val) {
+			mix->send_volume[idx/num_efx][idx%num_efx] = val;
+			change = 1;
+		}
+	}
+	if (change && mix->epcm) {
+		if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+			update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number,
+						   &mix->send_volume[1][0]);
+			update_emu10k1_send_volume(emu, mix->epcm->voices[1]->number,
+						   &mix->send_volume[2][0]);
+		} else if (mix->epcm->voices[0]) {
+			update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number,
+						   &mix->send_volume[0][0]);
+		}
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_emu10k1_send_volume_control =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "EMU10K1 PCM Send Volume",
+	.count =	32,
+	.info =         snd_emu10k1_send_volume_info,
+	.get =          snd_emu10k1_send_volume_get,
+	.put =          snd_emu10k1_send_volume_put
+};
+
+static int snd_emu10k1_attn_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 3;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0xffff;
+	return 0;
+}
+
+static int snd_emu10k1_attn_get(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	struct snd_emu10k1_pcm_mixer *mix =
+		&emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	unsigned long flags;
+	int idx;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < 3; idx++)
+		ucontrol->value.integer.value[idx] = mix->attn[idx];
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return 0;
+}
+
+static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned long flags;
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	struct snd_emu10k1_pcm_mixer *mix =
+		&emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int change = 0, idx, val;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < 3; idx++) {
+		val = ucontrol->value.integer.value[idx] & 0xffff;
+		if (mix->attn[idx] != val) {
+			mix->attn[idx] = val;
+			change = 1;
+		}
+	}
+	if (change && mix->epcm) {
+		if (mix->epcm->voices[0] && mix->epcm->voices[1]) {
+			snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[1]);
+			snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[1]->number, mix->attn[2]);
+		} else if (mix->epcm->voices[0]) {
+			snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[0]);
+		}
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_emu10k1_attn_control =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "EMU10K1 PCM Volume",
+	.count =	32,
+	.info =         snd_emu10k1_attn_info,
+	.get =          snd_emu10k1_attn_get,
+	.put =          snd_emu10k1_attn_put
+};
+
+/* Mutichannel PCM stream controls */
+
+static int snd_emu10k1_efx_send_routing_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = emu->audigy ? 8 : 4;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = emu->audigy ? 0x3f : 0x0f;
+	return 0;
+}
+
+static int snd_emu10k1_efx_send_routing_get(struct snd_kcontrol *kcontrol,
+                                        struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned long flags;
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	struct snd_emu10k1_pcm_mixer *mix =
+		&emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int idx;
+	int num_efx = emu->audigy ? 8 : 4;
+	int mask = emu->audigy ? 0x3f : 0x0f;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < num_efx; idx++)
+		ucontrol->value.integer.value[idx] = 
+			mix->send_routing[0][idx] & mask;
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return 0;
+}
+
+static int snd_emu10k1_efx_send_routing_put(struct snd_kcontrol *kcontrol,
+                                        struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned long flags;
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[ch];
+	int change = 0, idx, val;
+	int num_efx = emu->audigy ? 8 : 4;
+	int mask = emu->audigy ? 0x3f : 0x0f;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < num_efx; idx++) {
+		val = ucontrol->value.integer.value[idx] & mask;
+		if (mix->send_routing[0][idx] != val) {
+			mix->send_routing[0][idx] = val;
+			change = 1;
+		}
+	}	
+
+	if (change && mix->epcm) {
+		if (mix->epcm->voices[ch]) {
+			update_emu10k1_fxrt(emu, mix->epcm->voices[ch]->number,
+					&mix->send_routing[0][0]);
+		}
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "Multichannel PCM Send Routing",
+	.count =	16,
+	.info =         snd_emu10k1_efx_send_routing_info,
+	.get =          snd_emu10k1_efx_send_routing_get,
+	.put =          snd_emu10k1_efx_send_routing_put
+};
+
+static int snd_emu10k1_efx_send_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = emu->audigy ? 8 : 4;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	return 0;
+}
+
+static int snd_emu10k1_efx_send_volume_get(struct snd_kcontrol *kcontrol,
+                                       struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned long flags;
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	struct snd_emu10k1_pcm_mixer *mix =
+		&emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	int idx;
+	int num_efx = emu->audigy ? 8 : 4;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < num_efx; idx++)
+		ucontrol->value.integer.value[idx] = mix->send_volume[0][idx];
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return 0;
+}
+
+static int snd_emu10k1_efx_send_volume_put(struct snd_kcontrol *kcontrol,
+                                       struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned long flags;
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[ch];
+	int change = 0, idx, val;
+	int num_efx = emu->audigy ? 8 : 4;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	for (idx = 0; idx < num_efx; idx++) {
+		val = ucontrol->value.integer.value[idx] & 255;
+		if (mix->send_volume[0][idx] != val) {
+			mix->send_volume[0][idx] = val;
+			change = 1;
+		}
+	}
+	if (change && mix->epcm) {
+		if (mix->epcm->voices[ch]) {
+			update_emu10k1_send_volume(emu, mix->epcm->voices[ch]->number,
+						   &mix->send_volume[0][0]);
+		}
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return change;
+}
+
+
+static struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "Multichannel PCM Send Volume",
+	.count =	16,
+	.info =         snd_emu10k1_efx_send_volume_info,
+	.get =          snd_emu10k1_efx_send_volume_get,
+	.put =          snd_emu10k1_efx_send_volume_put
+};
+
+static int snd_emu10k1_efx_attn_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0xffff;
+	return 0;
+}
+
+static int snd_emu10k1_efx_attn_get(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	struct snd_emu10k1_pcm_mixer *mix =
+		&emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	ucontrol->value.integer.value[0] = mix->attn[0];
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return 0;
+}
+
+static int snd_emu10k1_efx_attn_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned long flags;
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[ch];
+	int change = 0, val;
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	val = ucontrol->value.integer.value[0] & 0xffff;
+	if (mix->attn[0] != val) {
+		mix->attn[0] = val;
+		change = 1;
+	}
+	if (change && mix->epcm) {
+		if (mix->epcm->voices[ch]) {
+			snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[ch]->number, mix->attn[0]);
+		}
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_emu10k1_efx_attn_control =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =        SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         "Multichannel PCM Volume",
+	.count =	16,
+	.info =         snd_emu10k1_efx_attn_info,
+	.get =          snd_emu10k1_efx_attn_get,
+	.put =          snd_emu10k1_efx_attn_put
+};
+
+#define snd_emu10k1_shared_spdif_info	snd_ctl_boolean_mono_info
+
+static int snd_emu10k1_shared_spdif_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+
+	if (emu->audigy)
+		ucontrol->value.integer.value[0] = inl(emu->port + A_IOCFG) & A_IOCFG_GPOUT0 ? 1 : 0;
+	else
+		ucontrol->value.integer.value[0] = inl(emu->port + HCFG) & HCFG_GPOUT0 ? 1 : 0;
+	if (emu->card_capabilities->invert_shared_spdif)
+		ucontrol->value.integer.value[0] =
+			!ucontrol->value.integer.value[0];
+		
+	return 0;
+}
+
+static int snd_emu10k1_shared_spdif_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned long flags;
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int reg, val, sw;
+	int change = 0;
+
+	sw = ucontrol->value.integer.value[0];
+	if (emu->card_capabilities->invert_shared_spdif)
+		sw = !sw;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	if ( emu->card_capabilities->i2c_adc) {
+		/* Do nothing for Audigy 2 ZS Notebook */
+	} else if (emu->audigy) {
+		reg = inl(emu->port + A_IOCFG);
+		val = sw ? A_IOCFG_GPOUT0 : 0;
+		change = (reg & A_IOCFG_GPOUT0) != val;
+		if (change) {
+			reg &= ~A_IOCFG_GPOUT0;
+			reg |= val;
+			outl(reg | val, emu->port + A_IOCFG);
+		}
+	}
+	reg = inl(emu->port + HCFG);
+	val = sw ? HCFG_GPOUT0 : 0;
+	change |= (reg & HCFG_GPOUT0) != val;
+	if (change) {
+		reg &= ~HCFG_GPOUT0;
+		reg |= val;
+		outl(reg | val, emu->port + HCFG);
+	}
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_emu10k1_shared_spdif __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"SB Live Analog/Digital Output Jack",
+	.info =		snd_emu10k1_shared_spdif_info,
+	.get =		snd_emu10k1_shared_spdif_get,
+	.put =		snd_emu10k1_shared_spdif_put
+};
+
+static struct snd_kcontrol_new snd_audigy_shared_spdif __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Audigy Analog/Digital Output Jack",
+	.info =		snd_emu10k1_shared_spdif_info,
+	.get =		snd_emu10k1_shared_spdif_get,
+	.put =		snd_emu10k1_shared_spdif_put
+};
+
+/* workaround for too low volume on Audigy due to 16bit/24bit conversion */
+
+#define snd_audigy_capture_boost_info	snd_ctl_boolean_mono_info
+
+static int snd_audigy_capture_boost_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+
+	/* FIXME: better to use a cached version */
+	val = snd_ac97_read(emu->ac97, AC97_REC_GAIN);
+	ucontrol->value.integer.value[0] = !!val;
+	return 0;
+}
+
+static int snd_audigy_capture_boost_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+
+	if (ucontrol->value.integer.value[0])
+		val = 0x0f0f;
+	else
+		val = 0;
+	return snd_ac97_update(emu->ac97, AC97_REC_GAIN, val);
+}
+
+static struct snd_kcontrol_new snd_audigy_capture_boost __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Analog Capture Boost",
+	.info =		snd_audigy_capture_boost_info,
+	.get =		snd_audigy_capture_boost_get,
+	.put =		snd_audigy_capture_boost_put
+};
+
+
+/*
+ */
+static void snd_emu10k1_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+	struct snd_emu10k1 *emu = ac97->private_data;
+	emu->ac97 = NULL;
+}
+
+/*
+ */
+static int remove_ctl(struct snd_card *card, const char *name)
+{
+	struct snd_ctl_elem_id id;
+	memset(&id, 0, sizeof(id));
+	strcpy(id.name, name);
+	id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	return snd_ctl_remove_id(card, &id);
+}
+
+static struct snd_kcontrol *ctl_find(struct snd_card *card, const char *name)
+{
+	struct snd_ctl_elem_id sid;
+	memset(&sid, 0, sizeof(sid));
+	strcpy(sid.name, name);
+	sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	return snd_ctl_find_id(card, &sid);
+}
+
+static int rename_ctl(struct snd_card *card, const char *src, const char *dst)
+{
+	struct snd_kcontrol *kctl = ctl_find(card, src);
+	if (kctl) {
+		strcpy(kctl->id.name, dst);
+		return 0;
+	}
+	return -ENOENT;
+}
+
+int __devinit snd_emu10k1_mixer(struct snd_emu10k1 *emu,
+				int pcm_device, int multi_device)
+{
+	int err, pcm;
+	struct snd_kcontrol *kctl;
+	struct snd_card *card = emu->card;
+	char **c;
+	static char *emu10k1_remove_ctls[] = {
+		/* no AC97 mono, surround, center/lfe */
+		"Master Mono Playback Switch",
+		"Master Mono Playback Volume",
+		"PCM Out Path & Mute",
+		"Mono Output Select",
+		"Front Playback Switch",
+		"Front Playback Volume",
+		"Surround Playback Switch",
+		"Surround Playback Volume",
+		"Center Playback Switch",
+		"Center Playback Volume",
+		"LFE Playback Switch",
+		"LFE Playback Volume",
+		NULL
+	};
+	static char *emu10k1_rename_ctls[] = {
+		"Surround Digital Playback Volume", "Surround Playback Volume",
+		"Center Digital Playback Volume", "Center Playback Volume",
+		"LFE Digital Playback Volume", "LFE Playback Volume",
+		NULL
+	};
+	static char *audigy_remove_ctls[] = {
+		/* Master/PCM controls on ac97 of Audigy has no effect */
+		/* On the Audigy2 the AC97 playback is piped into
+		 * the Philips ADC for 24bit capture */
+		"PCM Playback Switch",
+		"PCM Playback Volume",
+		"Master Mono Playback Switch",
+		"Master Mono Playback Volume",
+		"Master Playback Switch",
+		"Master Playback Volume",
+		"PCM Out Path & Mute",
+		"Mono Output Select",
+		/* remove unused AC97 capture controls */
+		"Capture Source",
+		"Capture Switch",
+		"Capture Volume",
+		"Mic Select",
+		"Video Playback Switch",
+		"Video Playback Volume",
+		"Mic Playback Switch",
+		"Mic Playback Volume",
+		NULL
+	};
+	static char *audigy_rename_ctls[] = {
+		/* use conventional names */
+		"Wave Playback Volume", "PCM Playback Volume",
+		/* "Wave Capture Volume", "PCM Capture Volume", */
+		"Wave Master Playback Volume", "Master Playback Volume",
+		"AMic Playback Volume", "Mic Playback Volume",
+		NULL
+	};
+	static char *audigy_rename_ctls_i2c_adc[] = {
+		//"Analog Mix Capture Volume","OLD Analog Mix Capture Volume",
+		"Line Capture Volume", "Analog Mix Capture Volume",
+		"Wave Playback Volume", "OLD PCM Playback Volume",
+		"Wave Master Playback Volume", "Master Playback Volume",
+		"AMic Playback Volume", "Old Mic Playback Volume",
+		"CD Capture Volume", "IEC958 Optical Capture Volume",
+		NULL
+	};
+	static char *audigy_remove_ctls_i2c_adc[] = {
+		/* On the Audigy2 ZS Notebook
+		 * Capture via WM8775  */
+		"Mic Capture Volume",
+		"Analog Mix Capture Volume",
+		"Aux Capture Volume",
+		"IEC958 Optical Capture Volume",
+		NULL
+	};
+	static char *audigy_remove_ctls_1361t_adc[] = {
+		/* On the Audigy2 the AC97 playback is piped into
+		 * the Philips ADC for 24bit capture */
+		"PCM Playback Switch",
+		"PCM Playback Volume",
+		"Master Mono Playback Switch",
+		"Master Mono Playback Volume",
+		"Capture Source",
+		"Capture Switch",
+		"Capture Volume",
+		"Mic Capture Volume",
+		"Headphone Playback Switch",
+		"Headphone Playback Volume",
+		"3D Control - Center",
+		"3D Control - Depth",
+		"3D Control - Switch",
+		"Line2 Playback Volume",
+		"Line2 Capture Volume",
+		NULL
+	};
+	static char *audigy_rename_ctls_1361t_adc[] = {
+		"Master Playback Switch", "Master Capture Switch",
+		"Master Playback Volume", "Master Capture Volume",
+		"Wave Master Playback Volume", "Master Playback Volume",
+		"Beep Playback Switch", "Beep Capture Switch",
+		"Beep Playback Volume", "Beep Capture Volume",
+		"Phone Playback Switch", "Phone Capture Switch",
+		"Phone Playback Volume", "Phone Capture Volume",
+		"Mic Playback Switch", "Mic Capture Switch",
+		"Mic Playback Volume", "Mic Capture Volume",
+		"Line Playback Switch", "Line Capture Switch",
+		"Line Playback Volume", "Line Capture Volume",
+		"CD Playback Switch", "CD Capture Switch",
+		"CD Playback Volume", "CD Capture Volume",
+		"Aux Playback Switch", "Aux Capture Switch",
+		"Aux Playback Volume", "Aux Capture Volume",
+		"Video Playback Switch", "Video Capture Switch",
+		"Video Playback Volume", "Video Capture Volume",
+
+		NULL
+	};
+
+	if (emu->card_capabilities->ac97_chip) {
+		struct snd_ac97_bus *pbus;
+		struct snd_ac97_template ac97;
+		static struct snd_ac97_bus_ops ops = {
+			.write = snd_emu10k1_ac97_write,
+			.read = snd_emu10k1_ac97_read,
+		};
+
+		if ((err = snd_ac97_bus(emu->card, 0, &ops, NULL, &pbus)) < 0)
+			return err;
+		pbus->no_vra = 1; /* we don't need VRA */
+		
+		memset(&ac97, 0, sizeof(ac97));
+		ac97.private_data = emu;
+		ac97.private_free = snd_emu10k1_mixer_free_ac97;
+		ac97.scaps = AC97_SCAP_NO_SPDIF;
+		if ((err = snd_ac97_mixer(pbus, &ac97, &emu->ac97)) < 0) {
+			if (emu->card_capabilities->ac97_chip == 1)
+				return err;
+			snd_printd(KERN_INFO "emu10k1: AC97 is optional on this board\n");
+			snd_printd(KERN_INFO"          Proceeding without ac97 mixers...\n");
+			snd_device_free(emu->card, pbus);
+			goto no_ac97; /* FIXME: get rid of ugly gotos.. */
+		}
+		if (emu->audigy) {
+			/* set master volume to 0 dB */
+			snd_ac97_write_cache(emu->ac97, AC97_MASTER, 0x0000);
+			/* set capture source to mic */
+			snd_ac97_write_cache(emu->ac97, AC97_REC_SEL, 0x0000);
+			if (emu->card_capabilities->adc_1361t)
+				c = audigy_remove_ctls_1361t_adc;
+			else 
+				c = audigy_remove_ctls;
+		} else {
+			/*
+			 * Credits for cards based on STAC9758:
+			 *   James Courtier-Dutton <James@superbug.demon.co.uk>
+			 *   Voluspa <voluspa@comhem.se>
+			 */
+			if (emu->ac97->id == AC97_ID_STAC9758) {
+				emu->rear_ac97 = 1;
+				snd_emu10k1_ptr_write(emu, AC97SLOT, 0, AC97SLOT_CNTR|AC97SLOT_LFE|AC97SLOT_REAR_LEFT|AC97SLOT_REAR_RIGHT);
+				snd_ac97_write_cache(emu->ac97, AC97_HEADPHONE, 0x0202);
+			}
+			/* remove unused AC97 controls */
+			snd_ac97_write_cache(emu->ac97, AC97_SURROUND_MASTER, 0x0202);
+			snd_ac97_write_cache(emu->ac97, AC97_CENTER_LFE_MASTER, 0x0202);
+			c = emu10k1_remove_ctls;
+		}
+		for (; *c; c++)
+			remove_ctl(card, *c);
+	} else if (emu->card_capabilities->i2c_adc) {
+		c = audigy_remove_ctls_i2c_adc;
+		for (; *c; c++)
+			remove_ctl(card, *c);
+	} else {
+	no_ac97:
+		if (emu->card_capabilities->ecard)
+			strcpy(emu->card->mixername, "EMU APS");
+		else if (emu->audigy)
+			strcpy(emu->card->mixername, "SB Audigy");
+		else
+			strcpy(emu->card->mixername, "Emu10k1");
+	}
+
+	if (emu->audigy)
+		if (emu->card_capabilities->adc_1361t)
+			c = audigy_rename_ctls_1361t_adc;
+		else if (emu->card_capabilities->i2c_adc)
+			c = audigy_rename_ctls_i2c_adc;
+		else
+			c = audigy_rename_ctls;
+	else
+		c = emu10k1_rename_ctls;
+	for (; *c; c += 2)
+		rename_ctl(card, c[0], c[1]);
+
+	if (emu->card_capabilities->subsystem == 0x20071102) {  /* Audigy 4 Pro */
+		rename_ctl(card, "Line2 Capture Volume", "Line1/Mic Capture Volume");
+		rename_ctl(card, "Analog Mix Capture Volume", "Line2 Capture Volume");
+		rename_ctl(card, "Aux2 Capture Volume", "Line3 Capture Volume");
+		rename_ctl(card, "Mic Capture Volume", "Unknown1 Capture Volume");
+		remove_ctl(card, "Headphone Playback Switch");
+		remove_ctl(card, "Headphone Playback Volume");
+		remove_ctl(card, "3D Control - Center");
+		remove_ctl(card, "3D Control - Depth");
+		remove_ctl(card, "3D Control - Switch");
+	}
+	if ((kctl = emu->ctl_send_routing = snd_ctl_new1(&snd_emu10k1_send_routing_control, emu)) == NULL)
+		return -ENOMEM;
+	kctl->id.device = pcm_device;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+	if ((kctl = emu->ctl_send_volume = snd_ctl_new1(&snd_emu10k1_send_volume_control, emu)) == NULL)
+		return -ENOMEM;
+	kctl->id.device = pcm_device;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+	if ((kctl = emu->ctl_attn = snd_ctl_new1(&snd_emu10k1_attn_control, emu)) == NULL)
+		return -ENOMEM;
+	kctl->id.device = pcm_device;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+
+	if ((kctl = emu->ctl_efx_send_routing = snd_ctl_new1(&snd_emu10k1_efx_send_routing_control, emu)) == NULL)
+		return -ENOMEM;
+	kctl->id.device = multi_device;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+	
+	if ((kctl = emu->ctl_efx_send_volume = snd_ctl_new1(&snd_emu10k1_efx_send_volume_control, emu)) == NULL)
+		return -ENOMEM;
+	kctl->id.device = multi_device;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+	
+	if ((kctl = emu->ctl_efx_attn = snd_ctl_new1(&snd_emu10k1_efx_attn_control, emu)) == NULL)
+		return -ENOMEM;
+	kctl->id.device = multi_device;
+	if ((err = snd_ctl_add(card, kctl)))
+		return err;
+
+	/* initialize the routing and volume table for each pcm playback stream */
+	for (pcm = 0; pcm < 32; pcm++) {
+		struct snd_emu10k1_pcm_mixer *mix;
+		int v;
+		
+		mix = &emu->pcm_mixer[pcm];
+		mix->epcm = NULL;
+
+		for (v = 0; v < 4; v++)
+			mix->send_routing[0][v] = 
+				mix->send_routing[1][v] = 
+				mix->send_routing[2][v] = v;
+		
+		memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+		mix->send_volume[0][0] = mix->send_volume[0][1] =
+		mix->send_volume[1][0] = mix->send_volume[2][1] = 255;
+		
+		mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff;
+	}
+	
+	/* initialize the routing and volume table for the multichannel playback stream */
+	for (pcm = 0; pcm < NUM_EFX_PLAYBACK; pcm++) {
+		struct snd_emu10k1_pcm_mixer *mix;
+		int v;
+		
+		mix = &emu->efx_pcm_mixer[pcm];
+		mix->epcm = NULL;
+
+		mix->send_routing[0][0] = pcm;
+		mix->send_routing[0][1] = (pcm == 0) ? 1 : 0;
+		for (v = 0; v < 2; v++)
+			mix->send_routing[0][2+v] = 13+v;
+		if (emu->audigy)
+			for (v = 0; v < 4; v++)
+				mix->send_routing[0][4+v] = 60+v;
+		
+		memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+		mix->send_volume[0][0]  = 255;
+		
+		mix->attn[0] = 0xffff;
+	}
+	
+	if (! emu->card_capabilities->ecard) { /* FIXME: APS has these controls? */
+		/* sb live! and audigy */
+		if ((kctl = snd_ctl_new1(&snd_emu10k1_spdif_mask_control, emu)) == NULL)
+			return -ENOMEM;
+		if (!emu->audigy)
+			kctl->id.device = emu->pcm_efx->device;
+		if ((err = snd_ctl_add(card, kctl)))
+			return err;
+		if ((kctl = snd_ctl_new1(&snd_emu10k1_spdif_control, emu)) == NULL)
+			return -ENOMEM;
+		if (!emu->audigy)
+			kctl->id.device = emu->pcm_efx->device;
+		if ((err = snd_ctl_add(card, kctl)))
+			return err;
+	}
+
+	if (emu->card_capabilities->emu_model) {
+		;  /* Disable the snd_audigy_spdif_shared_spdif */
+	} else if (emu->audigy) {
+		if ((kctl = snd_ctl_new1(&snd_audigy_shared_spdif, emu)) == NULL)
+			return -ENOMEM;
+		if ((err = snd_ctl_add(card, kctl)))
+			return err;
+#if 0
+		if ((kctl = snd_ctl_new1(&snd_audigy_spdif_output_rate, emu)) == NULL)
+			return -ENOMEM;
+		if ((err = snd_ctl_add(card, kctl)))
+			return err;
+#endif
+	} else if (! emu->card_capabilities->ecard) {
+		/* sb live! */
+		if ((kctl = snd_ctl_new1(&snd_emu10k1_shared_spdif, emu)) == NULL)
+			return -ENOMEM;
+		if ((err = snd_ctl_add(card, kctl)))
+			return err;
+	}
+	if (emu->card_capabilities->ca0151_chip) { /* P16V */
+		if ((err = snd_p16v_mixer(emu)))
+			return err;
+	}
+
+	if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) {
+		/* 1616(m) cardbus */
+		int i;
+
+		for (i = 0; i < ARRAY_SIZE(snd_emu1616_output_enum_ctls); i++) {
+			err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_emu1616_output_enum_ctls[i],
+					     emu));
+			if (err < 0)
+				return err;
+		}
+		for (i = 0; i < ARRAY_SIZE(snd_emu1010_input_enum_ctls); i++) {
+			err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_emu1010_input_enum_ctls[i],
+					     emu));
+			if (err < 0)
+				return err;
+		}
+		for (i = 0; i < ARRAY_SIZE(snd_emu1010_adc_pads) - 2; i++) {
+			err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_emu1010_adc_pads[i], emu));
+			if (err < 0)
+				return err;
+		}
+		for (i = 0; i < ARRAY_SIZE(snd_emu1010_dac_pads) - 2; i++) {
+			err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_emu1010_dac_pads[i], emu));
+			if (err < 0)
+				return err;
+		}
+		err = snd_ctl_add(card,
+			snd_ctl_new1(&snd_emu1010_internal_clock, emu));
+		if (err < 0)
+			return err;
+
+	} else if (emu->card_capabilities->emu_model) {
+		/* all other e-mu cards for now */
+		int i;
+
+		for (i = 0; i < ARRAY_SIZE(snd_emu1010_output_enum_ctls); i++) {
+			err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_emu1010_output_enum_ctls[i],
+					     emu));
+			if (err < 0)
+				return err;
+		}
+		for (i = 0; i < ARRAY_SIZE(snd_emu1010_input_enum_ctls); i++) {
+			err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_emu1010_input_enum_ctls[i],
+					     emu));
+			if (err < 0)
+				return err;
+		}
+		for (i = 0; i < ARRAY_SIZE(snd_emu1010_adc_pads); i++) {
+			err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_emu1010_adc_pads[i], emu));
+			if (err < 0)
+				return err;
+		}
+		for (i = 0; i < ARRAY_SIZE(snd_emu1010_dac_pads); i++) {
+			err = snd_ctl_add(card,
+				snd_ctl_new1(&snd_emu1010_dac_pads[i], emu));
+			if (err < 0)
+				return err;
+		}
+		err = snd_ctl_add(card,
+			snd_ctl_new1(&snd_emu1010_internal_clock, emu));
+		if (err < 0)
+			return err;
+	}
+
+	if ( emu->card_capabilities->i2c_adc) {
+		int i;
+
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_audigy_i2c_capture_source, emu));
+		if (err < 0)
+			return err;
+
+		for (i = 0; i < ARRAY_SIZE(snd_audigy_i2c_volume_ctls); i++) {
+			err = snd_ctl_add(card, snd_ctl_new1(&snd_audigy_i2c_volume_ctls[i], emu));
+			if (err < 0)
+				return err;
+		}
+	}
+		
+	if (emu->card_capabilities->ac97_chip && emu->audigy) {
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_audigy_capture_boost,
+						     emu));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
diff --git a/linux/sound/pci/emu10k1/emumpu401.c b/linux/sound/pci/emu10k1/emumpu401.c
new file mode 100644
index 0000000..bab5648
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emumpu401.c
@@ -0,0 +1,396 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for control of EMU10K1 MPU-401 in UART mode
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+#define EMU10K1_MIDI_MODE_INPUT		(1<<0)
+#define EMU10K1_MIDI_MODE_OUTPUT	(1<<1)
+
+static inline unsigned char mpu401_read(struct snd_emu10k1 *emu,
+					struct snd_emu10k1_midi *mpu, int idx)
+{
+	if (emu->audigy)
+		return (unsigned char)snd_emu10k1_ptr_read(emu, mpu->port + idx, 0);
+	else
+		return inb(emu->port + mpu->port + idx);
+}
+
+static inline void mpu401_write(struct snd_emu10k1 *emu,
+				struct snd_emu10k1_midi *mpu, int data, int idx)
+{
+	if (emu->audigy)
+		snd_emu10k1_ptr_write(emu, mpu->port + idx, 0, data);
+	else
+		outb(data, emu->port + mpu->port + idx);
+}
+
+#define mpu401_write_data(emu, mpu, data)	mpu401_write(emu, mpu, data, 0)
+#define mpu401_write_cmd(emu, mpu, data)	mpu401_write(emu, mpu, data, 1)
+#define mpu401_read_data(emu, mpu)		mpu401_read(emu, mpu, 0)
+#define mpu401_read_stat(emu, mpu)		mpu401_read(emu, mpu, 1)
+
+#define mpu401_input_avail(emu,mpu)	(!(mpu401_read_stat(emu,mpu) & 0x80))
+#define mpu401_output_ready(emu,mpu)	(!(mpu401_read_stat(emu,mpu) & 0x40))
+
+#define MPU401_RESET		0xff
+#define MPU401_ENTER_UART	0x3f
+#define MPU401_ACK		0xfe
+
+static void mpu401_clear_rx(struct snd_emu10k1 *emu, struct snd_emu10k1_midi *mpu)
+{
+	int timeout = 100000;
+	for (; timeout > 0 && mpu401_input_avail(emu, mpu); timeout--)
+		mpu401_read_data(emu, mpu);
+#ifdef CONFIG_SND_DEBUG
+	if (timeout <= 0)
+		snd_printk(KERN_ERR "cmd: clear rx timeout (status = 0x%x)\n", mpu401_read_stat(emu, mpu));
+#endif
+}
+
+/*
+
+ */
+
+static void do_emu10k1_midi_interrupt(struct snd_emu10k1 *emu, struct snd_emu10k1_midi *midi, unsigned int status)
+{
+	unsigned char byte;
+
+	if (midi->rmidi == NULL) {
+		snd_emu10k1_intr_disable(emu, midi->tx_enable | midi->rx_enable);
+		return;
+	}
+
+	spin_lock(&midi->input_lock);
+	if ((status & midi->ipr_rx) && mpu401_input_avail(emu, midi)) {
+		if (!(midi->midi_mode & EMU10K1_MIDI_MODE_INPUT)) {
+			mpu401_clear_rx(emu, midi);
+		} else {
+			byte = mpu401_read_data(emu, midi);
+			if (midi->substream_input)
+				snd_rawmidi_receive(midi->substream_input, &byte, 1);
+		}
+	}
+	spin_unlock(&midi->input_lock);
+
+	spin_lock(&midi->output_lock);
+	if ((status & midi->ipr_tx) && mpu401_output_ready(emu, midi)) {
+		if (midi->substream_output &&
+		    snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) {
+			mpu401_write_data(emu, midi, byte);
+		} else {
+			snd_emu10k1_intr_disable(emu, midi->tx_enable);
+		}
+	}
+	spin_unlock(&midi->output_lock);
+}
+
+static void snd_emu10k1_midi_interrupt(struct snd_emu10k1 *emu, unsigned int status)
+{
+	do_emu10k1_midi_interrupt(emu, &emu->midi, status);
+}
+
+static void snd_emu10k1_midi_interrupt2(struct snd_emu10k1 *emu, unsigned int status)
+{
+	do_emu10k1_midi_interrupt(emu, &emu->midi2, status);
+}
+
+static int snd_emu10k1_midi_cmd(struct snd_emu10k1 * emu, struct snd_emu10k1_midi *midi, unsigned char cmd, int ack)
+{
+	unsigned long flags;
+	int timeout, ok;
+
+	spin_lock_irqsave(&midi->input_lock, flags);
+	mpu401_write_data(emu, midi, 0x00);
+	/* mpu401_clear_rx(emu, midi); */
+
+	mpu401_write_cmd(emu, midi, cmd);
+	if (ack) {
+		ok = 0;
+		timeout = 10000;
+		while (!ok && timeout-- > 0) {
+			if (mpu401_input_avail(emu, midi)) {
+				if (mpu401_read_data(emu, midi) == MPU401_ACK)
+					ok = 1;
+			}
+		}
+		if (!ok && mpu401_read_data(emu, midi) == MPU401_ACK)
+			ok = 1;
+	} else {
+		ok = 1;
+	}
+	spin_unlock_irqrestore(&midi->input_lock, flags);
+	if (!ok) {
+		snd_printk(KERN_ERR "midi_cmd: 0x%x failed at 0x%lx (status = 0x%x, data = 0x%x)!!!\n",
+			   cmd, emu->port,
+			   mpu401_read_stat(emu, midi),
+			   mpu401_read_data(emu, midi));
+		return 1;
+	}
+	return 0;
+}
+
+static int snd_emu10k1_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_emu10k1 *emu;
+	struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
+	unsigned long flags;
+
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return -ENXIO;
+	spin_lock_irqsave(&midi->open_lock, flags);
+	midi->midi_mode |= EMU10K1_MIDI_MODE_INPUT;
+	midi->substream_input = substream;
+	if (!(midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		if (snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 1))
+			goto error_out;
+		if (snd_emu10k1_midi_cmd(emu, midi, MPU401_ENTER_UART, 1))
+			goto error_out;
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return 0;
+
+error_out:
+	return -EIO;
+}
+
+static int snd_emu10k1_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_emu10k1 *emu;
+	struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
+	unsigned long flags;
+
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return -ENXIO;
+	spin_lock_irqsave(&midi->open_lock, flags);
+	midi->midi_mode |= EMU10K1_MIDI_MODE_OUTPUT;
+	midi->substream_output = substream;
+	if (!(midi->midi_mode & EMU10K1_MIDI_MODE_INPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		if (snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 1))
+			goto error_out;
+		if (snd_emu10k1_midi_cmd(emu, midi, MPU401_ENTER_UART, 1))
+			goto error_out;
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return 0;
+
+error_out:
+	return -EIO;
+}
+
+static int snd_emu10k1_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_emu10k1 *emu;
+	struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
+	unsigned long flags;
+	int err = 0;
+
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return -ENXIO;
+	spin_lock_irqsave(&midi->open_lock, flags);
+	snd_emu10k1_intr_disable(emu, midi->rx_enable);
+	midi->midi_mode &= ~EMU10K1_MIDI_MODE_INPUT;
+	midi->substream_input = NULL;
+	if (!(midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		err = snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 0);
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return err;
+}
+
+static int snd_emu10k1_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_emu10k1 *emu;
+	struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
+	unsigned long flags;
+	int err = 0;
+
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return -ENXIO;
+	spin_lock_irqsave(&midi->open_lock, flags);
+	snd_emu10k1_intr_disable(emu, midi->tx_enable);
+	midi->midi_mode &= ~EMU10K1_MIDI_MODE_OUTPUT;
+	midi->substream_output = NULL;
+	if (!(midi->midi_mode & EMU10K1_MIDI_MODE_INPUT)) {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+		err = snd_emu10k1_midi_cmd(emu, midi, MPU401_RESET, 0);
+	} else {
+		spin_unlock_irqrestore(&midi->open_lock, flags);
+	}
+	return err;
+}
+
+static void snd_emu10k1_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_emu10k1 *emu;
+	struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return;
+
+	if (up)
+		snd_emu10k1_intr_enable(emu, midi->rx_enable);
+	else
+		snd_emu10k1_intr_disable(emu, midi->rx_enable);
+}
+
+static void snd_emu10k1_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_emu10k1 *emu;
+	struct snd_emu10k1_midi *midi = (struct snd_emu10k1_midi *)substream->rmidi->private_data;
+	unsigned long flags;
+
+	emu = midi->emu;
+	if (snd_BUG_ON(!emu))
+		return;
+
+	if (up) {
+		int max = 4;
+		unsigned char byte;
+	
+		/* try to send some amount of bytes here before interrupts */
+		spin_lock_irqsave(&midi->output_lock, flags);
+		while (max > 0) {
+			if (mpu401_output_ready(emu, midi)) {
+				if (!(midi->midi_mode & EMU10K1_MIDI_MODE_OUTPUT) ||
+				    snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+					/* no more data */
+					spin_unlock_irqrestore(&midi->output_lock, flags);
+					return;
+				}
+				mpu401_write_data(emu, midi, byte);
+				max--;
+			} else {
+				break;
+			}
+		}
+		spin_unlock_irqrestore(&midi->output_lock, flags);
+		snd_emu10k1_intr_enable(emu, midi->tx_enable);
+	} else {
+		snd_emu10k1_intr_disable(emu, midi->tx_enable);
+	}
+}
+
+/*
+
+ */
+
+static struct snd_rawmidi_ops snd_emu10k1_midi_output =
+{
+	.open =		snd_emu10k1_midi_output_open,
+	.close =	snd_emu10k1_midi_output_close,
+	.trigger =	snd_emu10k1_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_emu10k1_midi_input =
+{
+	.open =		snd_emu10k1_midi_input_open,
+	.close =	snd_emu10k1_midi_input_close,
+	.trigger =	snd_emu10k1_midi_input_trigger,
+};
+
+static void snd_emu10k1_midi_free(struct snd_rawmidi *rmidi)
+{
+	struct snd_emu10k1_midi *midi = rmidi->private_data;
+	midi->interrupt = NULL;
+	midi->rmidi = NULL;
+}
+
+static int __devinit emu10k1_midi_init(struct snd_emu10k1 *emu, struct snd_emu10k1_midi *midi, int device, char *name)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	if ((err = snd_rawmidi_new(emu->card, name, device, 1, 1, &rmidi)) < 0)
+		return err;
+	midi->emu = emu;
+	spin_lock_init(&midi->open_lock);
+	spin_lock_init(&midi->input_lock);
+	spin_lock_init(&midi->output_lock);
+	strcpy(rmidi->name, name);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_emu10k1_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_emu10k1_midi_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+	                     SNDRV_RAWMIDI_INFO_INPUT |
+	                     SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = midi;
+	rmidi->private_free = snd_emu10k1_midi_free;
+	midi->rmidi = rmidi;
+	return 0;
+}
+
+int __devinit snd_emu10k1_midi(struct snd_emu10k1 *emu)
+{
+	struct snd_emu10k1_midi *midi = &emu->midi;
+	int err;
+
+	if ((err = emu10k1_midi_init(emu, midi, 0, "EMU10K1 MPU-401 (UART)")) < 0)
+		return err;
+
+	midi->tx_enable = INTE_MIDITXENABLE;
+	midi->rx_enable = INTE_MIDIRXENABLE;
+	midi->port = MUDATA;
+	midi->ipr_tx = IPR_MIDITRANSBUFEMPTY;
+	midi->ipr_rx = IPR_MIDIRECVBUFEMPTY;
+	midi->interrupt = snd_emu10k1_midi_interrupt;
+	return 0;
+}
+
+int __devinit snd_emu10k1_audigy_midi(struct snd_emu10k1 *emu)
+{
+	struct snd_emu10k1_midi *midi;
+	int err;
+
+	midi = &emu->midi;
+	if ((err = emu10k1_midi_init(emu, midi, 0, "Audigy MPU-401 (UART)")) < 0)
+		return err;
+
+	midi->tx_enable = INTE_MIDITXENABLE;
+	midi->rx_enable = INTE_MIDIRXENABLE;
+	midi->port = A_MUDATA1;
+	midi->ipr_tx = IPR_MIDITRANSBUFEMPTY;
+	midi->ipr_rx = IPR_MIDIRECVBUFEMPTY;
+	midi->interrupt = snd_emu10k1_midi_interrupt;
+
+	midi = &emu->midi2;
+	if ((err = emu10k1_midi_init(emu, midi, 1, "Audigy MPU-401 #2")) < 0)
+		return err;
+
+	midi->tx_enable = INTE_A_MIDITXENABLE2;
+	midi->rx_enable = INTE_A_MIDIRXENABLE2;
+	midi->port = A_MUDATA2;
+	midi->ipr_tx = IPR_A_MIDITRANSBUFEMPTY2;
+	midi->ipr_rx = IPR_A_MIDIRECVBUFEMPTY2;
+	midi->interrupt = snd_emu10k1_midi_interrupt2;
+	return 0;
+}
diff --git a/linux/sound/pci/emu10k1/emupcm.c b/linux/sound/pci/emu10k1/emupcm.c
new file mode 100644
index 0000000..622bace
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emupcm.c
@@ -0,0 +1,1865 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *                   Creative Labs, Inc.
+ *  Routines for control of EMU10K1 chips / PCM routines
+ *  Multichannel PCM support Copyright (c) Lee Revell <rlrevell@joe-job.com>
+ *
+ *  BUGS:
+ *    --
+ *
+ *  TODO:
+ *    --
+ *
+ *   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/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+static void snd_emu10k1_pcm_interrupt(struct snd_emu10k1 *emu,
+				      struct snd_emu10k1_voice *voice)
+{
+	struct snd_emu10k1_pcm *epcm;
+
+	if ((epcm = voice->epcm) == NULL)
+		return;
+	if (epcm->substream == NULL)
+		return;
+#if 0
+	printk(KERN_DEBUG "IRQ: position = 0x%x, period = 0x%x, size = 0x%x\n",
+			epcm->substream->runtime->hw->pointer(emu, epcm->substream),
+			snd_pcm_lib_period_bytes(epcm->substream),
+			snd_pcm_lib_buffer_bytes(epcm->substream));
+#endif
+	snd_pcm_period_elapsed(epcm->substream);
+}
+
+static void snd_emu10k1_pcm_ac97adc_interrupt(struct snd_emu10k1 *emu,
+					      unsigned int status)
+{
+#if 0
+	if (status & IPR_ADCBUFHALFFULL) {
+		if (emu->pcm_capture_substream->runtime->mode == SNDRV_PCM_MODE_FRAME)
+			return;
+	}
+#endif
+	snd_pcm_period_elapsed(emu->pcm_capture_substream);
+}
+
+static void snd_emu10k1_pcm_ac97mic_interrupt(struct snd_emu10k1 *emu,
+					      unsigned int status)
+{
+#if 0
+	if (status & IPR_MICBUFHALFFULL) {
+		if (emu->pcm_capture_mic_substream->runtime->mode == SNDRV_PCM_MODE_FRAME)
+			return;
+	}
+#endif
+	snd_pcm_period_elapsed(emu->pcm_capture_mic_substream);
+}
+
+static void snd_emu10k1_pcm_efx_interrupt(struct snd_emu10k1 *emu,
+					  unsigned int status)
+{
+#if 0
+	if (status & IPR_EFXBUFHALFFULL) {
+		if (emu->pcm_capture_efx_substream->runtime->mode == SNDRV_PCM_MODE_FRAME)
+			return;
+	}
+#endif
+	snd_pcm_period_elapsed(emu->pcm_capture_efx_substream);
+}	 
+
+static snd_pcm_uframes_t snd_emu10k1_efx_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	unsigned int ptr;
+
+	if (!epcm->running)
+		return 0;
+	ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff;
+	ptr += runtime->buffer_size;
+	ptr -= epcm->ccca_start_addr;
+	ptr %= runtime->buffer_size;
+
+	return ptr;
+}
+
+static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voices)
+{
+	int err, i;
+
+	if (epcm->voices[1] != NULL && voices < 2) {
+		snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
+		epcm->voices[1] = NULL;
+	}
+	for (i = 0; i < voices; i++) {
+		if (epcm->voices[i] == NULL)
+			break;
+	}
+	if (i == voices)
+		return 0; /* already allocated */
+
+	for (i = 0; i < ARRAY_SIZE(epcm->voices); i++) {
+		if (epcm->voices[i]) {
+			snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]);
+			epcm->voices[i] = NULL;
+		}
+	}
+	err = snd_emu10k1_voice_alloc(epcm->emu,
+				      epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX,
+				      voices,
+				      &epcm->voices[0]);
+	
+	if (err < 0)
+		return err;
+	epcm->voices[0]->epcm = epcm;
+	if (voices > 1) {
+		for (i = 1; i < voices; i++) {
+			epcm->voices[i] = &epcm->emu->voices[epcm->voices[0]->number + i];
+			epcm->voices[i]->epcm = epcm;
+		}
+	}
+	if (epcm->extra == NULL) {
+		err = snd_emu10k1_voice_alloc(epcm->emu,
+					      epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX,
+					      1,
+					      &epcm->extra);
+		if (err < 0) {
+			/*
+			printk(KERN_DEBUG "pcm_channel_alloc: "
+			       "failed extra: voices=%d, frame=%d\n",
+			       voices, frame);
+			*/
+			for (i = 0; i < voices; i++) {
+				snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]);
+				epcm->voices[i] = NULL;
+			}
+			return err;
+		}
+		epcm->extra->epcm = epcm;
+		epcm->extra->interrupt = snd_emu10k1_pcm_interrupt;
+	}
+	return 0;
+}
+
+static unsigned int capture_period_sizes[31] = {
+	384,	448,	512,	640,
+	384*2,	448*2,	512*2,	640*2,
+	384*4,	448*4,	512*4,	640*4,
+	384*8,	448*8,	512*8,	640*8,
+	384*16,	448*16,	512*16,	640*16,
+	384*32,	448*32,	512*32,	640*32,
+	384*64,	448*64,	512*64,	640*64,
+	384*128,448*128,512*128
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_capture_period_sizes = {
+	.count = 31,
+	.list = capture_period_sizes,
+	.mask = 0
+};
+
+static unsigned int capture_rates[8] = {
+	8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_capture_rates = {
+	.count = 8,
+	.list = capture_rates,
+	.mask = 0
+};
+
+static unsigned int snd_emu10k1_capture_rate_reg(unsigned int rate)
+{
+	switch (rate) {
+	case 8000:	return ADCCR_SAMPLERATE_8;
+	case 11025:	return ADCCR_SAMPLERATE_11;
+	case 16000:	return ADCCR_SAMPLERATE_16;
+	case 22050:	return ADCCR_SAMPLERATE_22;
+	case 24000:	return ADCCR_SAMPLERATE_24;
+	case 32000:	return ADCCR_SAMPLERATE_32;
+	case 44100:	return ADCCR_SAMPLERATE_44;
+	case 48000:	return ADCCR_SAMPLERATE_48;
+	default:
+			snd_BUG();
+			return ADCCR_SAMPLERATE_8;
+	}
+}
+
+static unsigned int snd_emu10k1_audigy_capture_rate_reg(unsigned int rate)
+{
+	switch (rate) {
+	case 8000:	return A_ADCCR_SAMPLERATE_8;
+	case 11025:	return A_ADCCR_SAMPLERATE_11;
+	case 12000:	return A_ADCCR_SAMPLERATE_12; /* really supported? */
+	case 16000:	return ADCCR_SAMPLERATE_16;
+	case 22050:	return ADCCR_SAMPLERATE_22;
+	case 24000:	return ADCCR_SAMPLERATE_24;
+	case 32000:	return ADCCR_SAMPLERATE_32;
+	case 44100:	return ADCCR_SAMPLERATE_44;
+	case 48000:	return ADCCR_SAMPLERATE_48;
+	default:
+			snd_BUG();
+			return A_ADCCR_SAMPLERATE_8;
+	}
+}
+
+static unsigned int emu10k1_calc_pitch_target(unsigned int rate)
+{
+	unsigned int pitch_target;
+
+	pitch_target = (rate << 8) / 375;
+	pitch_target = (pitch_target >> 1) + (pitch_target & 1);
+	return pitch_target;
+}
+
+#define PITCH_48000 0x00004000
+#define PITCH_96000 0x00008000
+#define PITCH_85000 0x00007155
+#define PITCH_80726 0x00006ba2
+#define PITCH_67882 0x00005a82
+#define PITCH_57081 0x00004c1c
+
+static unsigned int emu10k1_select_interprom(unsigned int pitch_target)
+{
+	if (pitch_target == PITCH_48000)
+		return CCCA_INTERPROM_0;
+	else if (pitch_target < PITCH_48000)
+		return CCCA_INTERPROM_1;
+	else if (pitch_target >= PITCH_96000)
+		return CCCA_INTERPROM_0;
+	else if (pitch_target >= PITCH_85000)
+		return CCCA_INTERPROM_6;
+	else if (pitch_target >= PITCH_80726)
+		return CCCA_INTERPROM_5;
+	else if (pitch_target >= PITCH_67882)
+		return CCCA_INTERPROM_4;
+	else if (pitch_target >= PITCH_57081)
+		return CCCA_INTERPROM_3;
+	else  
+		return CCCA_INTERPROM_2;
+}
+
+/*
+ * calculate cache invalidate size 
+ *
+ * stereo: channel is stereo
+ * w_16: using 16bit samples
+ *
+ * returns: cache invalidate size in samples
+ */
+static inline int emu10k1_ccis(int stereo, int w_16)
+{
+	if (w_16) {
+		return stereo ? 24 : 26;
+	} else {
+		return stereo ? 24*2 : 26*2;
+	}
+}
+
+static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu,
+				       int master, int extra,
+				       struct snd_emu10k1_voice *evoice,
+				       unsigned int start_addr,
+				       unsigned int end_addr,
+				       struct snd_emu10k1_pcm_mixer *mix)
+{
+	struct snd_pcm_substream *substream = evoice->epcm->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int silent_page, tmp;
+	int voice, stereo, w_16;
+	unsigned char attn, send_amount[8];
+	unsigned char send_routing[8];
+	unsigned long flags;
+	unsigned int pitch_target;
+	unsigned int ccis;
+
+	voice = evoice->number;
+	stereo = runtime->channels == 2;
+	w_16 = snd_pcm_format_width(runtime->format) == 16;
+
+	if (!extra && stereo) {
+		start_addr >>= 1;
+		end_addr >>= 1;
+	}
+	if (w_16) {
+		start_addr >>= 1;
+		end_addr >>= 1;
+	}
+
+	spin_lock_irqsave(&emu->reg_lock, flags);
+
+	/* volume parameters */
+	if (extra) {
+		attn = 0;
+		memset(send_routing, 0, sizeof(send_routing));
+		send_routing[0] = 0;
+		send_routing[1] = 1;
+		send_routing[2] = 2;
+		send_routing[3] = 3;
+		memset(send_amount, 0, sizeof(send_amount));
+	} else {
+		/* mono, left, right (master voice = left) */
+		tmp = stereo ? (master ? 1 : 2) : 0;
+		memcpy(send_routing, &mix->send_routing[tmp][0], 8);
+		memcpy(send_amount, &mix->send_volume[tmp][0], 8);
+	}
+
+	ccis = emu10k1_ccis(stereo, w_16);
+	
+	if (master) {
+		evoice->epcm->ccca_start_addr = start_addr + ccis;
+		if (extra) {
+			start_addr += ccis;
+			end_addr += ccis + emu->delay_pcm_irq;
+		}
+		if (stereo && !extra) {
+			snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK);
+			snd_emu10k1_ptr_write(emu, CPF, (voice + 1), CPF_STEREO_MASK);
+		} else {
+			snd_emu10k1_ptr_write(emu, CPF, voice, 0);
+		}
+	}
+
+	/* setup routing */
+	if (emu->audigy) {
+		snd_emu10k1_ptr_write(emu, A_FXRT1, voice,
+				      snd_emu10k1_compose_audigy_fxrt1(send_routing));
+		snd_emu10k1_ptr_write(emu, A_FXRT2, voice,
+				      snd_emu10k1_compose_audigy_fxrt2(send_routing));
+		snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice,
+				      ((unsigned int)send_amount[4] << 24) |
+				      ((unsigned int)send_amount[5] << 16) |
+				      ((unsigned int)send_amount[6] << 8) |
+				      (unsigned int)send_amount[7]);
+	} else
+		snd_emu10k1_ptr_write(emu, FXRT, voice,
+				      snd_emu10k1_compose_send_routing(send_routing));
+	/* Stop CA */
+	/* Assumption that PT is already 0 so no harm overwriting */
+	snd_emu10k1_ptr_write(emu, PTRX, voice, (send_amount[0] << 8) | send_amount[1]);
+	snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24));
+	snd_emu10k1_ptr_write(emu, PSST, voice,
+			(start_addr + (extra ? emu->delay_pcm_irq : 0)) |
+			(send_amount[2] << 24));
+	if (emu->card_capabilities->emu_model)
+		pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */
+	else 
+		pitch_target = emu10k1_calc_pitch_target(runtime->rate);
+	if (extra)
+		snd_emu10k1_ptr_write(emu, CCCA, voice, start_addr |
+			      emu10k1_select_interprom(pitch_target) |
+			      (w_16 ? 0 : CCCA_8BITSELECT));
+	else
+		snd_emu10k1_ptr_write(emu, CCCA, voice, (start_addr + ccis) |
+			      emu10k1_select_interprom(pitch_target) |
+			      (w_16 ? 0 : CCCA_8BITSELECT));
+	/* Clear filter delay memory */
+	snd_emu10k1_ptr_write(emu, Z1, voice, 0);
+	snd_emu10k1_ptr_write(emu, Z2, voice, 0);
+	/* invalidate maps */
+	silent_page = ((unsigned int)emu->silent_page.addr << 1) | MAP_PTI_MASK;
+	snd_emu10k1_ptr_write(emu, MAPA, voice, silent_page);
+	snd_emu10k1_ptr_write(emu, MAPB, voice, silent_page);
+	/* modulation envelope */
+	snd_emu10k1_ptr_write(emu, CVCF, voice, 0xffff);
+	snd_emu10k1_ptr_write(emu, VTFT, voice, 0xffff);
+	snd_emu10k1_ptr_write(emu, ATKHLDM, voice, 0);
+	snd_emu10k1_ptr_write(emu, DCYSUSM, voice, 0x007f);
+	snd_emu10k1_ptr_write(emu, LFOVAL1, voice, 0x8000);
+	snd_emu10k1_ptr_write(emu, LFOVAL2, voice, 0x8000);
+	snd_emu10k1_ptr_write(emu, FMMOD, voice, 0);
+	snd_emu10k1_ptr_write(emu, TREMFRQ, voice, 0);
+	snd_emu10k1_ptr_write(emu, FM2FRQ2, voice, 0);
+	snd_emu10k1_ptr_write(emu, ENVVAL, voice, 0x8000);
+	/* volume envelope */
+	snd_emu10k1_ptr_write(emu, ATKHLDV, voice, 0x7f7f);
+	snd_emu10k1_ptr_write(emu, ENVVOL, voice, 0x0000);
+	/* filter envelope */
+	snd_emu10k1_ptr_write(emu, PEFE_FILTERAMOUNT, voice, 0x7f);
+	/* pitch envelope */
+	snd_emu10k1_ptr_write(emu, PEFE_PITCHAMOUNT, voice, 0);
+
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+}
+
+static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream,
+					  struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	int err;
+
+	if ((err = snd_emu10k1_pcm_channel_alloc(epcm, params_channels(hw_params))) < 0)
+		return err;
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	if (err > 0) {	/* change */
+		int mapped;
+		if (epcm->memblk != NULL)
+			snd_emu10k1_free_pages(emu, epcm->memblk);
+		epcm->memblk = snd_emu10k1_alloc_pages(emu, substream);
+		epcm->start_addr = 0;
+		if (! epcm->memblk)
+			return -ENOMEM;
+		mapped = ((struct snd_emu10k1_memblk *)epcm->memblk)->mapped_page;
+		if (mapped < 0)
+			return -ENOMEM;
+		epcm->start_addr = mapped << PAGE_SHIFT;
+	}
+	return 0;
+}
+
+static int snd_emu10k1_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm;
+
+	if (runtime->private_data == NULL)
+		return 0;
+	epcm = runtime->private_data;
+	if (epcm->extra) {
+		snd_emu10k1_voice_free(epcm->emu, epcm->extra);
+		epcm->extra = NULL;
+	}
+	if (epcm->voices[1]) {
+		snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
+		epcm->voices[1] = NULL;
+	}
+	if (epcm->voices[0]) {
+		snd_emu10k1_voice_free(epcm->emu, epcm->voices[0]);
+		epcm->voices[0] = NULL;
+	}
+	if (epcm->memblk) {
+		snd_emu10k1_free_pages(emu, epcm->memblk);
+		epcm->memblk = NULL;
+		epcm->start_addr = 0;
+	}
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_emu10k1_efx_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm;
+	int i;
+
+	if (runtime->private_data == NULL)
+		return 0;
+	epcm = runtime->private_data;
+	if (epcm->extra) {
+		snd_emu10k1_voice_free(epcm->emu, epcm->extra);
+		epcm->extra = NULL;
+	}
+	for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
+		if (epcm->voices[i]) {
+			snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]);
+			epcm->voices[i] = NULL;
+		}
+	}
+	if (epcm->memblk) {
+		snd_emu10k1_free_pages(emu, epcm->memblk);
+		epcm->memblk = NULL;
+		epcm->start_addr = 0;
+	}
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	unsigned int start_addr, end_addr;
+
+	start_addr = epcm->start_addr;
+	end_addr = snd_pcm_lib_period_bytes(substream);
+	if (runtime->channels == 2) {
+		start_addr >>= 1;
+		end_addr >>= 1;
+	}
+	end_addr += start_addr;
+	snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra,
+				   start_addr, end_addr, NULL);
+	start_addr = epcm->start_addr;
+	end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream);
+	snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0],
+				   start_addr, end_addr,
+				   &emu->pcm_mixer[substream->number]);
+	if (epcm->voices[1])
+		snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1],
+					   start_addr, end_addr,
+					   &emu->pcm_mixer[substream->number]);
+	return 0;
+}
+
+static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	unsigned int start_addr, end_addr;
+	unsigned int channel_size;
+	int i;
+
+	start_addr = epcm->start_addr;
+	end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream);
+
+	/*
+	 * the kX driver leaves some space between voices
+	 */
+	channel_size = ( end_addr - start_addr ) / NUM_EFX_PLAYBACK;
+
+	snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra,
+				   start_addr, start_addr + (channel_size / 2), NULL);
+
+	/* only difference with the master voice is we use it for the pointer */
+	snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0],
+				   start_addr, start_addr + channel_size,
+				   &emu->efx_pcm_mixer[0]);
+
+	start_addr += channel_size;
+	for (i = 1; i < NUM_EFX_PLAYBACK; i++) {
+		snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i],
+					   start_addr, start_addr + channel_size,
+					   &emu->efx_pcm_mixer[i]);
+		start_addr += channel_size;
+	}
+
+	return 0;
+}
+
+static struct snd_pcm_hardware snd_emu10k1_efx_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_NONINTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		NUM_EFX_PLAYBACK,
+	.channels_max =		NUM_EFX_PLAYBACK,
+	.buffer_bytes_max =	(64*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(64*1024),
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0,
+};
+
+static int snd_emu10k1_capture_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_emu10k1_capture_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	int idx;
+
+	/* zeroing the buffer size will stop capture */
+	snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, 0);
+	switch (epcm->type) {
+	case CAPTURE_AC97ADC:
+		snd_emu10k1_ptr_write(emu, ADCCR, 0, 0);
+		break;
+	case CAPTURE_EFX:
+		if (emu->audigy) {
+			snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0);
+			snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0);
+		} else
+			snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
+		break;
+	default:
+		break;
+	}	
+	snd_emu10k1_ptr_write(emu, epcm->capture_ba_reg, 0, runtime->dma_addr);
+	epcm->capture_bufsize = snd_pcm_lib_buffer_bytes(substream);
+	epcm->capture_bs_val = 0;
+	for (idx = 0; idx < 31; idx++) {
+		if (capture_period_sizes[idx] == epcm->capture_bufsize) {
+			epcm->capture_bs_val = idx + 1;
+			break;
+		}
+	}
+	if (epcm->capture_bs_val == 0) {
+		snd_BUG();
+		epcm->capture_bs_val++;
+	}
+	if (epcm->type == CAPTURE_AC97ADC) {
+		epcm->capture_cr_val = emu->audigy ? A_ADCCR_LCHANENABLE : ADCCR_LCHANENABLE;
+		if (runtime->channels > 1)
+			epcm->capture_cr_val |= emu->audigy ? A_ADCCR_RCHANENABLE : ADCCR_RCHANENABLE;
+		epcm->capture_cr_val |= emu->audigy ?
+			snd_emu10k1_audigy_capture_rate_reg(runtime->rate) :
+			snd_emu10k1_capture_rate_reg(runtime->rate);
+	}
+	return 0;
+}
+
+static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int extra, struct snd_emu10k1_voice *evoice)
+{
+	struct snd_pcm_runtime *runtime;
+	unsigned int voice, stereo, i, ccis, cra = 64, cs, sample;
+
+	if (evoice == NULL)
+		return;
+	runtime = evoice->epcm->substream->runtime;
+	voice = evoice->number;
+	stereo = (!extra && runtime->channels == 2);
+	sample = snd_pcm_format_width(runtime->format) == 16 ? 0 : 0x80808080;
+	ccis = emu10k1_ccis(stereo, sample == 0);
+	/* set cs to 2 * number of cache registers beside the invalidated */
+	cs = (sample == 0) ? (32-ccis) : (64-ccis+1) >> 1;
+	if (cs > 16) cs = 16;
+	for (i = 0; i < cs; i++) {
+		snd_emu10k1_ptr_write(emu, CD0 + i, voice, sample);
+		if (stereo) {
+			snd_emu10k1_ptr_write(emu, CD0 + i, voice + 1, sample);
+		}
+	}
+	/* reset cache */
+	snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, 0);
+	snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice, cra);
+	if (stereo) {
+		snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice + 1, 0);
+		snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice + 1, cra);
+	}
+	/* fill cache */
+	snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, ccis);
+	if (stereo) {
+		snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice+1, ccis);
+	}
+}
+
+static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice,
+					       int master, int extra,
+					       struct snd_emu10k1_pcm_mixer *mix)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+	unsigned int attn, vattn;
+	unsigned int voice, tmp;
+
+	if (evoice == NULL)	/* skip second voice for mono */
+		return;
+	substream = evoice->epcm->substream;
+	runtime = substream->runtime;
+	voice = evoice->number;
+
+	attn = extra ? 0 : 0x00ff;
+	tmp = runtime->channels == 2 ? (master ? 1 : 2) : 0;
+	vattn = mix != NULL ? (mix->attn[tmp] << 16) : 0;
+	snd_emu10k1_ptr_write(emu, IFATN, voice, attn);
+	snd_emu10k1_ptr_write(emu, VTFT, voice, vattn | 0xffff);
+	snd_emu10k1_ptr_write(emu, CVCF, voice, vattn | 0xffff);
+	snd_emu10k1_ptr_write(emu, DCYSUSV, voice, 0x7f7f);
+	snd_emu10k1_voice_clear_loop_stop(emu, voice);
+}	
+
+static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, int master, int extra)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+	unsigned int voice, pitch, pitch_target;
+
+	if (evoice == NULL)	/* skip second voice for mono */
+		return;
+	substream = evoice->epcm->substream;
+	runtime = substream->runtime;
+	voice = evoice->number;
+
+	pitch = snd_emu10k1_rate_to_pitch(runtime->rate) >> 8;
+	if (emu->card_capabilities->emu_model)
+		pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */
+	else 
+		pitch_target = emu10k1_calc_pitch_target(runtime->rate);
+	snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target);
+	if (master || evoice->epcm->type == PLAYBACK_EFX)
+		snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target);
+	snd_emu10k1_ptr_write(emu, IP, voice, pitch);
+	if (extra)
+		snd_emu10k1_voice_intr_enable(emu, voice);
+}
+
+static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice)
+{
+	unsigned int voice;
+
+	if (evoice == NULL)
+		return;
+	voice = evoice->number;
+	snd_emu10k1_voice_intr_disable(emu, voice);
+	snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0);
+	snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0);
+	snd_emu10k1_ptr_write(emu, IFATN, voice, 0xffff);
+	snd_emu10k1_ptr_write(emu, VTFT, voice, 0xffff);
+	snd_emu10k1_ptr_write(emu, CVCF, voice, 0xffff);
+	snd_emu10k1_ptr_write(emu, IP, voice, 0);
+}
+
+static inline void snd_emu10k1_playback_mangle_extra(struct snd_emu10k1 *emu,
+		struct snd_emu10k1_pcm *epcm,
+		struct snd_pcm_substream *substream,
+		struct snd_pcm_runtime *runtime)
+{
+	unsigned int ptr, period_pos;
+
+	/* try to sychronize the current position for the interrupt
+	   source voice */
+	period_pos = runtime->status->hw_ptr - runtime->hw_ptr_interrupt;
+	period_pos %= runtime->period_size;
+	ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->extra->number);
+	ptr &= ~0x00ffffff;
+	ptr |= epcm->ccca_start_addr + period_pos;
+	snd_emu10k1_ptr_write(emu, CCCA, epcm->extra->number, ptr);
+}
+
+static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream,
+				        int cmd)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	struct snd_emu10k1_pcm_mixer *mix;
+	int result = 0;
+
+	/*
+	printk(KERN_DEBUG "trigger - emu10k1 = 0x%x, cmd = %i, pointer = %i\n",
+	       (int)emu, cmd, substream->ops->pointer(substream))
+	*/
+	spin_lock(&emu->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_emu10k1_playback_invalidate_cache(emu, 1, epcm->extra);	/* do we need this? */
+		snd_emu10k1_playback_invalidate_cache(emu, 0, epcm->voices[0]);
+		/* follow thru */
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE)
+			snd_emu10k1_playback_mangle_extra(emu, epcm, substream, runtime);
+		mix = &emu->pcm_mixer[substream->number];
+		snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 1, 0, mix);
+		snd_emu10k1_playback_prepare_voice(emu, epcm->voices[1], 0, 0, mix);
+		snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, 1, NULL);
+		snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1, 0);
+		snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0, 0);
+		snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1);
+		epcm->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		epcm->running = 0;
+		snd_emu10k1_playback_stop_voice(emu, epcm->voices[0]);
+		snd_emu10k1_playback_stop_voice(emu, epcm->voices[1]);
+		snd_emu10k1_playback_stop_voice(emu, epcm->extra);
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	spin_unlock(&emu->reg_lock);
+	return result;
+}
+
+static int snd_emu10k1_capture_trigger(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	int result = 0;
+
+	spin_lock(&emu->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		/* hmm this should cause full and half full interrupt to be raised? */
+		outl(epcm->capture_ipr, emu->port + IPR);
+		snd_emu10k1_intr_enable(emu, epcm->capture_inte);
+		/*
+		printk(KERN_DEBUG "adccr = 0x%x, adcbs = 0x%x\n",
+		       epcm->adccr, epcm->adcbs);
+		*/
+		switch (epcm->type) {
+		case CAPTURE_AC97ADC:
+			snd_emu10k1_ptr_write(emu, ADCCR, 0, epcm->capture_cr_val);
+			break;
+		case CAPTURE_EFX:
+			if (emu->audigy) {
+				snd_emu10k1_ptr_write(emu, A_FXWC1, 0, epcm->capture_cr_val);
+				snd_emu10k1_ptr_write(emu, A_FXWC2, 0, epcm->capture_cr_val2);
+				snd_printdd("cr_val=0x%x, cr_val2=0x%x\n", epcm->capture_cr_val, epcm->capture_cr_val2);
+			} else
+				snd_emu10k1_ptr_write(emu, FXWC, 0, epcm->capture_cr_val);
+			break;
+		default:	
+			break;
+		}
+		snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, epcm->capture_bs_val);
+		epcm->running = 1;
+		epcm->first_ptr = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		epcm->running = 0;
+		snd_emu10k1_intr_disable(emu, epcm->capture_inte);
+		outl(epcm->capture_ipr, emu->port + IPR);
+		snd_emu10k1_ptr_write(emu, epcm->capture_bs_reg, 0, 0);
+		switch (epcm->type) {
+		case CAPTURE_AC97ADC:
+			snd_emu10k1_ptr_write(emu, ADCCR, 0, 0);
+			break;
+		case CAPTURE_EFX:
+			if (emu->audigy) {
+				snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0);
+				snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0);
+			} else
+				snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		result = -EINVAL;
+	}
+	spin_unlock(&emu->reg_lock);
+	return result;
+}
+
+static snd_pcm_uframes_t snd_emu10k1_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	unsigned int ptr;
+
+	if (!epcm->running)
+		return 0;
+	ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff;
+#if 0	/* Perex's code */
+	ptr += runtime->buffer_size;
+	ptr -= epcm->ccca_start_addr;
+	ptr %= runtime->buffer_size;
+#else	/* EMU10K1 Open Source code from Creative */
+	if (ptr < epcm->ccca_start_addr)
+		ptr += runtime->buffer_size - epcm->ccca_start_addr;
+	else {
+		ptr -= epcm->ccca_start_addr;
+		if (ptr >= runtime->buffer_size)
+			ptr -= runtime->buffer_size;
+	}
+#endif
+	/*
+	printk(KERN_DEBUG
+	       "ptr = 0x%lx, buffer_size = 0x%lx, period_size = 0x%lx\n",
+	       (long)ptr, (long)runtime->buffer_size,
+	       (long)runtime->period_size);
+	*/
+	return ptr;
+}
+
+
+static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream,
+				        int cmd)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	int i;
+	int result = 0;
+
+	spin_lock(&emu->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* prepare voices */
+		for (i = 0; i < NUM_EFX_PLAYBACK; i++) {	
+			snd_emu10k1_playback_invalidate_cache(emu, 0, epcm->voices[i]);
+		}
+		snd_emu10k1_playback_invalidate_cache(emu, 1, epcm->extra);
+
+		/* follow thru */
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, 1, NULL);
+		snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 0, 0,
+						   &emu->efx_pcm_mixer[0]);
+		for (i = 1; i < NUM_EFX_PLAYBACK; i++)
+			snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, 0,
+							   &emu->efx_pcm_mixer[i]);
+		snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 0, 0);
+		snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1);
+		for (i = 1; i < NUM_EFX_PLAYBACK; i++)
+			snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i], 0, 0);
+		epcm->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		epcm->running = 0;
+		for (i = 0; i < NUM_EFX_PLAYBACK; i++) {	
+			snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]);
+		}
+		snd_emu10k1_playback_stop_voice(emu, epcm->extra);
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	spin_unlock(&emu->reg_lock);
+	return result;
+}
+
+
+static snd_pcm_uframes_t snd_emu10k1_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	unsigned int ptr;
+
+	if (!epcm->running)
+		return 0;
+	if (epcm->first_ptr) {
+		udelay(50);	/* hack, it takes awhile until capture is started */
+		epcm->first_ptr = 0;
+	}
+	ptr = snd_emu10k1_ptr_read(emu, epcm->capture_idx_reg, 0) & 0x0000ffff;
+	return bytes_to_frames(runtime, ptr);
+}
+
+/*
+ *  Playback support device description
+ */
+
+static struct snd_pcm_hardware snd_emu10k1_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_96000,
+	.rate_min =		4000,
+	.rate_max =		96000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *  Capture support device description
+ */
+
+static struct snd_pcm_hardware snd_emu10k1_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		8000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(64*1024),
+	.period_bytes_min =	384,
+	.period_bytes_max =	(64*1024),
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_emu10k1_capture_efx =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | 
+				 SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | 
+				 SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000,
+	.rate_min =		44100,
+	.rate_max =		192000,
+	.channels_min =		8,
+	.channels_max =		8,
+	.buffer_bytes_max =	(64*1024),
+	.period_bytes_min =	384,
+	.period_bytes_max =	(64*1024),
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0,
+};
+
+/*
+ *
+ */
+
+static void snd_emu10k1_pcm_mixer_notify1(struct snd_emu10k1 *emu, struct snd_kcontrol *kctl, int idx, int activate)
+{
+	struct snd_ctl_elem_id id;
+
+	if (! kctl)
+		return;
+	if (activate)
+		kctl->vd[idx].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	else
+		kctl->vd[idx].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(emu->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO,
+		       snd_ctl_build_ioff(&id, kctl, idx));
+}
+
+static void snd_emu10k1_pcm_mixer_notify(struct snd_emu10k1 *emu, int idx, int activate)
+{
+	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_send_routing, idx, activate);
+	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_send_volume, idx, activate);
+	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_attn, idx, activate);
+}
+
+static void snd_emu10k1_pcm_efx_mixer_notify(struct snd_emu10k1 *emu, int idx, int activate)
+{
+	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_send_routing, idx, activate);
+	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_send_volume, idx, activate);
+	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_attn, idx, activate);
+}
+
+static void snd_emu10k1_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+	kfree(runtime->private_data);
+}
+
+static int snd_emu10k1_efx_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_pcm_mixer *mix;
+	int i;
+
+	for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
+		mix = &emu->efx_pcm_mixer[i];
+		mix->epcm = NULL;
+		snd_emu10k1_pcm_efx_mixer_notify(emu, i, 0);
+	}
+	return 0;
+}
+
+static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_pcm *epcm;
+	struct snd_emu10k1_pcm_mixer *mix;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int i;
+
+	epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+	if (epcm == NULL)
+		return -ENOMEM;
+	epcm->emu = emu;
+	epcm->type = PLAYBACK_EFX;
+	epcm->substream = substream;
+	
+	emu->pcm_playback_efx_substream = substream;
+
+	runtime->private_data = epcm;
+	runtime->private_free = snd_emu10k1_pcm_free_substream;
+	runtime->hw = snd_emu10k1_efx_playback;
+	
+	for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
+		mix = &emu->efx_pcm_mixer[i];
+		mix->send_routing[0][0] = i;
+		memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+		mix->send_volume[0][0] = 255;
+		mix->attn[0] = 0xffff;
+		mix->epcm = epcm;
+		snd_emu10k1_pcm_efx_mixer_notify(emu, i, 1);
+	}
+	return 0;
+}
+
+static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_pcm *epcm;
+	struct snd_emu10k1_pcm_mixer *mix;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int i, err;
+
+	epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+	if (epcm == NULL)
+		return -ENOMEM;
+	epcm->emu = emu;
+	epcm->type = PLAYBACK_EMUVOICE;
+	epcm->substream = substream;
+	runtime->private_data = epcm;
+	runtime->private_free = snd_emu10k1_pcm_free_substream;
+	runtime->hw = snd_emu10k1_playback;
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) {
+		kfree(epcm);
+		return err;
+	}
+	if ((err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX)) < 0) {
+		kfree(epcm);
+		return err;
+	}
+	mix = &emu->pcm_mixer[substream->number];
+	for (i = 0; i < 4; i++)
+		mix->send_routing[0][i] = mix->send_routing[1][i] = mix->send_routing[2][i] = i;
+	memset(&mix->send_volume, 0, sizeof(mix->send_volume));
+	mix->send_volume[0][0] = mix->send_volume[0][1] =
+	mix->send_volume[1][0] = mix->send_volume[2][1] = 255;
+	mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff;
+	mix->epcm = epcm;
+	snd_emu10k1_pcm_mixer_notify(emu, substream->number, 1);
+	return 0;
+}
+
+static int snd_emu10k1_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_pcm_mixer *mix = &emu->pcm_mixer[substream->number];
+
+	mix->epcm = NULL;
+	snd_emu10k1_pcm_mixer_notify(emu, substream->number, 0);
+	return 0;
+}
+
+static int snd_emu10k1_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm;
+
+	epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+	if (epcm == NULL)
+		return -ENOMEM;
+	epcm->emu = emu;
+	epcm->type = CAPTURE_AC97ADC;
+	epcm->substream = substream;
+	epcm->capture_ipr = IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL;
+	epcm->capture_inte = INTE_ADCBUFENABLE;
+	epcm->capture_ba_reg = ADCBA;
+	epcm->capture_bs_reg = ADCBS;
+	epcm->capture_idx_reg = emu->audigy ? A_ADCIDX : ADCIDX;
+	runtime->private_data = epcm;
+	runtime->private_free = snd_emu10k1_pcm_free_substream;
+	runtime->hw = snd_emu10k1_capture;
+	emu->capture_interrupt = snd_emu10k1_pcm_ac97adc_interrupt;
+	emu->pcm_capture_substream = substream;
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_capture_rates);
+	return 0;
+}
+
+static int snd_emu10k1_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+
+	emu->capture_interrupt = NULL;
+	emu->pcm_capture_substream = NULL;
+	return 0;
+}
+
+static int snd_emu10k1_capture_mic_open(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_pcm *epcm;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+	if (epcm == NULL)
+		return -ENOMEM;
+	epcm->emu = emu;
+	epcm->type = CAPTURE_AC97MIC;
+	epcm->substream = substream;
+	epcm->capture_ipr = IPR_MICBUFFULL|IPR_MICBUFHALFFULL;
+	epcm->capture_inte = INTE_MICBUFENABLE;
+	epcm->capture_ba_reg = MICBA;
+	epcm->capture_bs_reg = MICBS;
+	epcm->capture_idx_reg = emu->audigy ? A_MICIDX : MICIDX;
+	substream->runtime->private_data = epcm;
+	substream->runtime->private_free = snd_emu10k1_pcm_free_substream;
+	runtime->hw = snd_emu10k1_capture;
+	runtime->hw.rates = SNDRV_PCM_RATE_8000;
+	runtime->hw.rate_min = runtime->hw.rate_max = 8000;
+	runtime->hw.channels_min = 1;
+	emu->capture_mic_interrupt = snd_emu10k1_pcm_ac97mic_interrupt;
+	emu->pcm_capture_mic_substream = substream;
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
+	return 0;
+}
+
+static int snd_emu10k1_capture_mic_close(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+
+	emu->capture_interrupt = NULL;
+	emu->pcm_capture_mic_substream = NULL;
+	return 0;
+}
+
+static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_pcm *epcm;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int nefx = emu->audigy ? 64 : 32;
+	int idx;
+
+	epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+	if (epcm == NULL)
+		return -ENOMEM;
+	epcm->emu = emu;
+	epcm->type = CAPTURE_EFX;
+	epcm->substream = substream;
+	epcm->capture_ipr = IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL;
+	epcm->capture_inte = INTE_EFXBUFENABLE;
+	epcm->capture_ba_reg = FXBA;
+	epcm->capture_bs_reg = FXBS;
+	epcm->capture_idx_reg = FXIDX;
+	substream->runtime->private_data = epcm;
+	substream->runtime->private_free = snd_emu10k1_pcm_free_substream;
+	runtime->hw = snd_emu10k1_capture_efx;
+	runtime->hw.rates = SNDRV_PCM_RATE_48000;
+	runtime->hw.rate_min = runtime->hw.rate_max = 48000;
+	spin_lock_irq(&emu->reg_lock);
+	if (emu->card_capabilities->emu_model) {
+		/*  Nb. of channels has been increased to 16 */
+		/* TODO
+		 * SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE
+		 * SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+		 * SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
+		 * SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000
+		 * rate_min = 44100,
+		 * rate_max = 192000,
+		 * channels_min = 16,
+		 * channels_max = 16,
+		 * Need to add mixer control to fix sample rate
+		 *                 
+		 * There are 32 mono channels of 16bits each.
+		 * 24bit Audio uses 2x channels over 16bit
+		 * 96kHz uses 2x channels over 48kHz
+		 * 192kHz uses 4x channels over 48kHz
+		 * So, for 48kHz 24bit, one has 16 channels
+		 * for 96kHz 24bit, one has 8 channels
+		 * for 192kHz 24bit, one has 4 channels
+		 *
+		 */
+#if 1
+		switch (emu->emu1010.internal_clock) {
+		case 0:
+			/* For 44.1kHz */
+			runtime->hw.rates = SNDRV_PCM_RATE_44100;
+			runtime->hw.rate_min = runtime->hw.rate_max = 44100;
+			runtime->hw.channels_min =
+				runtime->hw.channels_max = 16;
+			break;
+		case 1:
+			/* For 48kHz */
+			runtime->hw.rates = SNDRV_PCM_RATE_48000;
+			runtime->hw.rate_min = runtime->hw.rate_max = 48000;
+			runtime->hw.channels_min =
+				runtime->hw.channels_max = 16;
+			break;
+		};
+#endif
+#if 0
+		/* For 96kHz */
+		runtime->hw.rates = SNDRV_PCM_RATE_96000;
+		runtime->hw.rate_min = runtime->hw.rate_max = 96000;
+		runtime->hw.channels_min = runtime->hw.channels_max = 4;
+#endif
+#if 0
+		/* For 192kHz */
+		runtime->hw.rates = SNDRV_PCM_RATE_192000;
+		runtime->hw.rate_min = runtime->hw.rate_max = 192000;
+		runtime->hw.channels_min = runtime->hw.channels_max = 2;
+#endif
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
+		/* efx_voices_mask[0] is expected to be zero
+ 		 * efx_voices_mask[1] is expected to have 32bits set
+		 */
+	} else {
+		runtime->hw.channels_min = runtime->hw.channels_max = 0;
+		for (idx = 0; idx < nefx; idx++) {
+			if (emu->efx_voices_mask[idx/32] & (1 << (idx%32))) {
+				runtime->hw.channels_min++;
+				runtime->hw.channels_max++;
+			}
+		}
+	}
+	epcm->capture_cr_val = emu->efx_voices_mask[0];
+	epcm->capture_cr_val2 = emu->efx_voices_mask[1];
+	spin_unlock_irq(&emu->reg_lock);
+	emu->capture_efx_interrupt = snd_emu10k1_pcm_efx_interrupt;
+	emu->pcm_capture_efx_substream = substream;
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes);
+	return 0;
+}
+
+static int snd_emu10k1_capture_efx_close(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+
+	emu->capture_interrupt = NULL;
+	emu->pcm_capture_efx_substream = NULL;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_emu10k1_playback_ops = {
+	.open =			snd_emu10k1_playback_open,
+	.close =		snd_emu10k1_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_emu10k1_playback_hw_params,
+	.hw_free =		snd_emu10k1_playback_hw_free,
+	.prepare =		snd_emu10k1_playback_prepare,
+	.trigger =		snd_emu10k1_playback_trigger,
+	.pointer =		snd_emu10k1_playback_pointer,
+	.page =			snd_pcm_sgbuf_ops_page,
+};
+
+static struct snd_pcm_ops snd_emu10k1_capture_ops = {
+	.open =			snd_emu10k1_capture_open,
+	.close =		snd_emu10k1_capture_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_emu10k1_capture_hw_params,
+	.hw_free =		snd_emu10k1_capture_hw_free,
+	.prepare =		snd_emu10k1_capture_prepare,
+	.trigger =		snd_emu10k1_capture_trigger,
+	.pointer =		snd_emu10k1_capture_pointer,
+};
+
+/* EFX playback */
+static struct snd_pcm_ops snd_emu10k1_efx_playback_ops = {
+	.open =			snd_emu10k1_efx_playback_open,
+	.close =		snd_emu10k1_efx_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_emu10k1_playback_hw_params,
+	.hw_free =		snd_emu10k1_efx_playback_hw_free,
+	.prepare =		snd_emu10k1_efx_playback_prepare,
+	.trigger =		snd_emu10k1_efx_playback_trigger,
+	.pointer =		snd_emu10k1_efx_playback_pointer,
+	.page =			snd_pcm_sgbuf_ops_page,
+};
+
+int __devinit snd_emu10k1_pcm(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+
+	if ((err = snd_pcm_new(emu->card, "emu10k1", device, 32, 1, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = emu;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1_capture_ops);
+
+	pcm->info_flags = 0;
+	pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+	strcpy(pcm->name, "ADC Capture/Standard PCM Playback");
+	emu->pcm = pcm;
+
+	for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
+		if ((err = snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG, snd_dma_pci_data(emu->pci), 64*1024, 64*1024)) < 0)
+			return err;
+
+	for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next)
+		snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci), 64*1024, 64*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+
+	return 0;
+}
+
+int __devinit snd_emu10k1_pcm_multi(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+
+	if ((err = snd_pcm_new(emu->card, "emu10k1", device, 1, 0, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = emu;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_efx_playback_ops);
+
+	pcm->info_flags = 0;
+	pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+	strcpy(pcm->name, "Multichannel Playback");
+	emu->pcm_multi = pcm;
+
+	for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
+		if ((err = snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG, snd_dma_pci_data(emu->pci), 64*1024, 64*1024)) < 0)
+			return err;
+
+	if (rpcm)
+		*rpcm = pcm;
+
+	return 0;
+}
+
+
+static struct snd_pcm_ops snd_emu10k1_capture_mic_ops = {
+	.open =			snd_emu10k1_capture_mic_open,
+	.close =		snd_emu10k1_capture_mic_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_emu10k1_capture_hw_params,
+	.hw_free =		snd_emu10k1_capture_hw_free,
+	.prepare =		snd_emu10k1_capture_prepare,
+	.trigger =		snd_emu10k1_capture_trigger,
+	.pointer =		snd_emu10k1_capture_pointer,
+};
+
+int __devinit snd_emu10k1_pcm_mic(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+
+	if ((err = snd_pcm_new(emu->card, "emu10k1 mic", device, 0, 1, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = emu;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1_capture_mic_ops);
+
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "Mic Capture");
+	emu->pcm_mic = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci), 64*1024, 64*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+static int snd_emu10k1_pcm_efx_voices_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	int nefx = emu->audigy ? 64 : 32;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = nefx;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_emu10k1_pcm_efx_voices_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	int nefx = emu->audigy ? 64 : 32;
+	int idx;
+	
+	spin_lock_irq(&emu->reg_lock);
+	for (idx = 0; idx < nefx; idx++)
+		ucontrol->value.integer.value[idx] = (emu->efx_voices_mask[idx / 32] & (1 << (idx % 32))) ? 1 : 0;
+	spin_unlock_irq(&emu->reg_lock);
+	return 0;
+}
+
+static int snd_emu10k1_pcm_efx_voices_mask_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int nval[2], bits;
+	int nefx = emu->audigy ? 64 : 32;
+	int nefxb = emu->audigy ? 7 : 6;
+	int change, idx;
+	
+	nval[0] = nval[1] = 0;
+	for (idx = 0, bits = 0; idx < nefx; idx++)
+		if (ucontrol->value.integer.value[idx]) {
+			nval[idx / 32] |= 1 << (idx % 32);
+			bits++;
+		}
+		
+	for (idx = 0; idx < nefxb; idx++)
+		if (1 << idx == bits)
+			break;
+	
+	if (idx >= nefxb)
+		return -EINVAL;
+
+	spin_lock_irq(&emu->reg_lock);
+	change = (nval[0] != emu->efx_voices_mask[0]) ||
+		(nval[1] != emu->efx_voices_mask[1]);
+	emu->efx_voices_mask[0] = nval[0];
+	emu->efx_voices_mask[1] = nval[1];
+	spin_unlock_irq(&emu->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = {
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name = "Captured FX8010 Outputs",
+	.info = snd_emu10k1_pcm_efx_voices_mask_info,
+	.get = snd_emu10k1_pcm_efx_voices_mask_get,
+	.put = snd_emu10k1_pcm_efx_voices_mask_put
+};
+
+static struct snd_pcm_ops snd_emu10k1_capture_efx_ops = {
+	.open =			snd_emu10k1_capture_efx_open,
+	.close =		snd_emu10k1_capture_efx_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_emu10k1_capture_hw_params,
+	.hw_free =		snd_emu10k1_capture_hw_free,
+	.prepare =		snd_emu10k1_capture_prepare,
+	.trigger =		snd_emu10k1_capture_trigger,
+	.pointer =		snd_emu10k1_capture_pointer,
+};
+
+
+/* EFX playback */
+
+#define INITIAL_TRAM_SHIFT     14
+#define INITIAL_TRAM_POS(size) ((((size) / 2) - INITIAL_TRAM_SHIFT) - 1)
+
+static void snd_emu10k1_fx8010_playback_irq(struct snd_emu10k1 *emu, void *private_data)
+{
+	struct snd_pcm_substream *substream = private_data;
+	snd_pcm_period_elapsed(substream);
+}
+
+static void snd_emu10k1_fx8010_playback_tram_poke1(unsigned short *dst_left,
+						   unsigned short *dst_right,
+						   unsigned short *src,
+						   unsigned int count,
+						   unsigned int tram_shift)
+{
+	/*
+	printk(KERN_DEBUG "tram_poke1: dst_left = 0x%p, dst_right = 0x%p, "
+	       "src = 0x%p, count = 0x%x\n",
+	       dst_left, dst_right, src, count);
+	*/
+	if ((tram_shift & 1) == 0) {
+		while (count--) {
+			*dst_left-- = *src++;
+			*dst_right-- = *src++;
+		}
+	} else {
+		while (count--) {
+			*dst_right-- = *src++;
+			*dst_left-- = *src++;
+		}
+	}
+}
+
+static void fx8010_pb_trans_copy(struct snd_pcm_substream *substream,
+				 struct snd_pcm_indirect *rec, size_t bytes)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number];
+	unsigned int tram_size = pcm->buffer_size;
+	unsigned short *src = (unsigned short *)(substream->runtime->dma_area + rec->sw_data);
+	unsigned int frames = bytes >> 2, count;
+	unsigned int tram_pos = pcm->tram_pos;
+	unsigned int tram_shift = pcm->tram_shift;
+
+	while (frames > tram_pos) {
+		count = tram_pos + 1;
+		snd_emu10k1_fx8010_playback_tram_poke1((unsigned short *)emu->fx8010.etram_pages.area + tram_pos,
+						       (unsigned short *)emu->fx8010.etram_pages.area + tram_pos + tram_size / 2,
+						       src, count, tram_shift);
+		src += count * 2;
+		frames -= count;
+		tram_pos = (tram_size / 2) - 1;
+		tram_shift++;
+	}
+	snd_emu10k1_fx8010_playback_tram_poke1((unsigned short *)emu->fx8010.etram_pages.area + tram_pos,
+					       (unsigned short *)emu->fx8010.etram_pages.area + tram_pos + tram_size / 2,
+					       src, frames, tram_shift);
+	tram_pos -= frames;
+	pcm->tram_pos = tram_pos;
+	pcm->tram_shift = tram_shift;
+}
+
+static int snd_emu10k1_fx8010_playback_transfer(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number];
+
+	snd_pcm_indirect_playback_transfer(substream, &pcm->pcm_rec, fx8010_pb_trans_copy);
+	return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_hw_params(struct snd_pcm_substream *substream,
+						 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_emu10k1_fx8010_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number];
+	unsigned int i;
+
+	for (i = 0; i < pcm->channels; i++)
+		snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + 0x80 + pcm->etram[i], 0, 0);
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number];
+	unsigned int i;
+	
+	/*
+	printk(KERN_DEBUG "prepare: etram_pages = 0x%p, dma_area = 0x%x, "
+	       "buffer_size = 0x%x (0x%x)\n",
+	       emu->fx8010.etram_pages, runtime->dma_area,
+	       runtime->buffer_size, runtime->buffer_size << 2);
+	*/
+	memset(&pcm->pcm_rec, 0, sizeof(pcm->pcm_rec));
+	pcm->pcm_rec.hw_buffer_size = pcm->buffer_size * 2; /* byte size */
+	pcm->pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+	pcm->tram_pos = INITIAL_TRAM_POS(pcm->buffer_size);
+	pcm->tram_shift = 0;
+	snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_running, 0, 0);	/* reset */
+	snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 0);	/* reset */
+	snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_size, 0, runtime->buffer_size);
+	snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_ptr, 0, 0);		/* reset ptr number */
+	snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_count, 0, runtime->period_size);
+	snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_tmpcount, 0, runtime->period_size);
+	for (i = 0; i < pcm->channels; i++)
+		snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + 0x80 + pcm->etram[i], 0, (TANKMEMADDRREG_READ|TANKMEMADDRREG_ALIGN) + i * (runtime->buffer_size / pcm->channels));
+	return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number];
+	int result = 0;
+
+	spin_lock(&emu->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* follow thru */
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+#ifdef EMU10K1_SET_AC3_IEC958
+	{
+		int i;
+		for (i = 0; i < 3; i++) {
+			unsigned int bits;
+			bits = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
+			       SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | SPCS_GENERATIONSTATUS |
+			       0x00001200 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT | SPCS_NOTAUDIODATA;
+			snd_emu10k1_ptr_write(emu, SPCS0 + i, 0, bits);
+		}
+	}
+#endif
+		result = snd_emu10k1_fx8010_register_irq_handler(emu, snd_emu10k1_fx8010_playback_irq, pcm->gpr_running, substream, &pcm->irq);
+		if (result < 0)
+			goto __err;
+		snd_emu10k1_fx8010_playback_transfer(substream);	/* roll the ball */
+		snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 1);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		snd_emu10k1_fx8010_unregister_irq_handler(emu, pcm->irq); pcm->irq = NULL;
+		snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 0);
+		pcm->tram_pos = INITIAL_TRAM_POS(pcm->buffer_size);
+		pcm->tram_shift = 0;
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+      __err:
+	spin_unlock(&emu->reg_lock);
+	return result;
+}
+
+static snd_pcm_uframes_t snd_emu10k1_fx8010_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number];
+	size_t ptr; /* byte pointer */
+
+	if (!snd_emu10k1_ptr_read(emu, emu->gpr_base + pcm->gpr_trigger, 0))
+		return 0;
+	ptr = snd_emu10k1_ptr_read(emu, emu->gpr_base + pcm->gpr_ptr, 0) << 2;
+	return snd_pcm_indirect_playback_pointer(substream, &pcm->pcm_rec, ptr);
+}
+
+static struct snd_pcm_hardware snd_emu10k1_fx8010_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_RESUME |
+				 /* SNDRV_PCM_INFO_MMAP_VALID | */ SNDRV_PCM_INFO_PAUSE),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		1,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	1024,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static int snd_emu10k1_fx8010_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number];
+
+	runtime->hw = snd_emu10k1_fx8010_playback;
+	runtime->hw.channels_min = runtime->hw.channels_max = pcm->channels;
+	runtime->hw.period_bytes_max = (pcm->buffer_size * 2) / 2;
+	spin_lock_irq(&emu->reg_lock);
+	if (pcm->valid == 0) {
+		spin_unlock_irq(&emu->reg_lock);
+		return -ENODEV;
+	}
+	pcm->opened = 1;
+	spin_unlock_irq(&emu->reg_lock);
+	return 0;
+}
+
+static int snd_emu10k1_fx8010_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_fx8010_pcm *pcm = &emu->fx8010.pcm[substream->number];
+
+	spin_lock_irq(&emu->reg_lock);
+	pcm->opened = 0;
+	spin_unlock_irq(&emu->reg_lock);
+	return 0;
+}
+
+static struct snd_pcm_ops snd_emu10k1_fx8010_playback_ops = {
+	.open =			snd_emu10k1_fx8010_playback_open,
+	.close =		snd_emu10k1_fx8010_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_emu10k1_fx8010_playback_hw_params,
+	.hw_free =		snd_emu10k1_fx8010_playback_hw_free,
+	.prepare =		snd_emu10k1_fx8010_playback_prepare,
+	.trigger =		snd_emu10k1_fx8010_playback_trigger,
+	.pointer =		snd_emu10k1_fx8010_playback_pointer,
+	.ack =			snd_emu10k1_fx8010_playback_transfer,
+};
+
+int __devinit snd_emu10k1_pcm_efx(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	struct snd_kcontrol *kctl;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+
+	if ((err = snd_pcm_new(emu->card, "emu10k1 efx", device, 8, 1, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = emu;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_fx8010_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1_capture_efx_ops);
+
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "Multichannel Capture/PT Playback");
+	emu->pcm_efx = pcm;
+	if (rpcm)
+		*rpcm = pcm;
+
+	/* EFX capture - record the "FXBUS2" channels, by default we connect the EXTINs 
+	 * to these
+	 */	
+	
+	/* emu->efx_voices_mask[0] = FXWC_DEFAULTROUTE_C | FXWC_DEFAULTROUTE_A; */
+	if (emu->audigy) {
+		emu->efx_voices_mask[0] = 0;
+		if (emu->card_capabilities->emu_model)
+			/* Pavel Hofman - 32 voices will be used for
+			 * capture (write mode) -
+			 * each bit = corresponding voice
+			 */
+			emu->efx_voices_mask[1] = 0xffffffff;
+		else
+			emu->efx_voices_mask[1] = 0xffff;
+	} else {
+		emu->efx_voices_mask[0] = 0xffff0000;
+		emu->efx_voices_mask[1] = 0;
+	}
+	/* For emu1010, the control has to set 32 upper bits (voices)
+	 * out of the 64 bits (voices) to true for the 16-channels capture
+	 * to work correctly. Correct A_FXWC2 initial value (0xffffffff)
+	 * is already defined but the snd_emu10k1_pcm_efx_voices_mask
+	 * control can override this register's value.
+	 */
+	kctl = snd_ctl_new1(&snd_emu10k1_pcm_efx_voices_mask, emu);
+	if (!kctl)
+		return -ENOMEM;
+	kctl->id.device = device;
+	snd_ctl_add(emu->card, kctl);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci), 64*1024, 64*1024);
+
+	return 0;
+}
diff --git a/linux/sound/pci/emu10k1/emuproc.c b/linux/sound/pci/emu10k1/emuproc.c
new file mode 100644
index 0000000..bc38dd4
--- /dev/null
+++ b/linux/sound/pci/emu10k1/emuproc.c
@@ -0,0 +1,671 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *                   Creative Labs, Inc.
+ *  Routines for control of EMU10K1 chips / proc interface routines
+ *
+ *  Copyright (c) by James Courtier-Dutton <James@superbug.co.uk>
+ *  	Added EMU 1010 support.
+ *
+ *  BUGS:
+ *    --
+ *
+ *  TODO:
+ *    --
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#include "p16v.h"
+
+#ifdef CONFIG_PROC_FS
+static void snd_emu10k1_proc_spdif_status(struct snd_emu10k1 * emu,
+					  struct snd_info_buffer *buffer,
+					  char *title,
+					  int status_reg,
+					  int rate_reg)
+{
+	static char *clkaccy[4] = { "1000ppm", "50ppm", "variable", "unknown" };
+	static int samplerate[16] = { 44100, 1, 48000, 32000, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+	static char *channel[16] = { "unspec", "left", "right", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" };
+	static char *emphasis[8] = { "none", "50/15 usec 2 channel", "2", "3", "4", "5", "6", "7" };
+	unsigned int status, rate = 0;
+	
+	status = snd_emu10k1_ptr_read(emu, status_reg, 0);
+
+	snd_iprintf(buffer, "\n%s\n", title);
+
+	if (status != 0xffffffff) {
+		snd_iprintf(buffer, "Professional Mode     : %s\n", (status & SPCS_PROFESSIONAL) ? "yes" : "no");
+		snd_iprintf(buffer, "Not Audio Data        : %s\n", (status & SPCS_NOTAUDIODATA) ? "yes" : "no");
+		snd_iprintf(buffer, "Copyright             : %s\n", (status & SPCS_COPYRIGHT) ? "yes" : "no");
+		snd_iprintf(buffer, "Emphasis              : %s\n", emphasis[(status & SPCS_EMPHASISMASK) >> 3]);
+		snd_iprintf(buffer, "Mode                  : %i\n", (status & SPCS_MODEMASK) >> 6);
+		snd_iprintf(buffer, "Category Code         : 0x%x\n", (status & SPCS_CATEGORYCODEMASK) >> 8);
+		snd_iprintf(buffer, "Generation Status     : %s\n", status & SPCS_GENERATIONSTATUS ? "original" : "copy");
+		snd_iprintf(buffer, "Source Mask           : %i\n", (status & SPCS_SOURCENUMMASK) >> 16);
+		snd_iprintf(buffer, "Channel Number        : %s\n", channel[(status & SPCS_CHANNELNUMMASK) >> 20]);
+		snd_iprintf(buffer, "Sample Rate           : %iHz\n", samplerate[(status & SPCS_SAMPLERATEMASK) >> 24]);
+		snd_iprintf(buffer, "Clock Accuracy        : %s\n", clkaccy[(status & SPCS_CLKACCYMASK) >> 28]);
+
+		if (rate_reg > 0) {
+			rate = snd_emu10k1_ptr_read(emu, rate_reg, 0);
+			snd_iprintf(buffer, "S/PDIF Valid          : %s\n", rate & SRCS_SPDIFVALID ? "on" : "off");
+			snd_iprintf(buffer, "S/PDIF Locked         : %s\n", rate & SRCS_SPDIFLOCKED ? "on" : "off");
+			snd_iprintf(buffer, "Rate Locked           : %s\n", rate & SRCS_RATELOCKED ? "on" : "off");
+			/* From ((Rate * 48000 ) / 262144); */
+			snd_iprintf(buffer, "Estimated Sample Rate : %d\n", ((rate & 0xFFFFF ) * 375) >> 11); 
+		}
+	} else {
+		snd_iprintf(buffer, "No signal detected.\n");
+	}
+
+}
+
+static void snd_emu10k1_proc_read(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer)
+{
+	/* FIXME - output names are in emufx.c too */
+	static char *creative_outs[32] = {
+		/* 00 */ "AC97 Left",
+		/* 01 */ "AC97 Right",
+		/* 02 */ "Optical IEC958 Left",
+		/* 03 */ "Optical IEC958 Right",
+		/* 04 */ "Center",
+		/* 05 */ "LFE",
+		/* 06 */ "Headphone Left",
+		/* 07 */ "Headphone Right",
+		/* 08 */ "Surround Left",
+		/* 09 */ "Surround Right",
+		/* 10 */ "PCM Capture Left",
+		/* 11 */ "PCM Capture Right",
+		/* 12 */ "MIC Capture",
+		/* 13 */ "AC97 Surround Left",
+		/* 14 */ "AC97 Surround Right",
+		/* 15 */ "???",
+		/* 16 */ "???",
+		/* 17 */ "Analog Center",
+		/* 18 */ "Analog LFE",
+		/* 19 */ "???",
+		/* 20 */ "???",
+		/* 21 */ "???",
+		/* 22 */ "???",
+		/* 23 */ "???",
+		/* 24 */ "???",
+		/* 25 */ "???",
+		/* 26 */ "???",
+		/* 27 */ "???",
+		/* 28 */ "???",
+		/* 29 */ "???",
+		/* 30 */ "???",
+		/* 31 */ "???"
+	};
+
+	static char *audigy_outs[64] = {
+		/* 00 */ "Digital Front Left",
+		/* 01 */ "Digital Front Right",
+		/* 02 */ "Digital Center",
+		/* 03 */ "Digital LEF",
+		/* 04 */ "Headphone Left",
+		/* 05 */ "Headphone Right",
+		/* 06 */ "Digital Rear Left",
+		/* 07 */ "Digital Rear Right",
+		/* 08 */ "Front Left",
+		/* 09 */ "Front Right",
+		/* 10 */ "Center",
+		/* 11 */ "LFE",
+		/* 12 */ "???",
+		/* 13 */ "???",
+		/* 14 */ "Rear Left",
+		/* 15 */ "Rear Right",
+		/* 16 */ "AC97 Front Left",
+		/* 17 */ "AC97 Front Right",
+		/* 18 */ "ADC Caputre Left",
+		/* 19 */ "ADC Capture Right",
+		/* 20 */ "???",
+		/* 21 */ "???",
+		/* 22 */ "???",
+		/* 23 */ "???",
+		/* 24 */ "???",
+		/* 25 */ "???",
+		/* 26 */ "???",
+		/* 27 */ "???",
+		/* 28 */ "???",
+		/* 29 */ "???",
+		/* 30 */ "???",
+		/* 31 */ "???",
+		/* 32 */ "FXBUS2_0",
+		/* 33 */ "FXBUS2_1",
+		/* 34 */ "FXBUS2_2",
+		/* 35 */ "FXBUS2_3",
+		/* 36 */ "FXBUS2_4",
+		/* 37 */ "FXBUS2_5",
+		/* 38 */ "FXBUS2_6",
+		/* 39 */ "FXBUS2_7",
+		/* 40 */ "FXBUS2_8",
+		/* 41 */ "FXBUS2_9",
+		/* 42 */ "FXBUS2_10",
+		/* 43 */ "FXBUS2_11",
+		/* 44 */ "FXBUS2_12",
+		/* 45 */ "FXBUS2_13",
+		/* 46 */ "FXBUS2_14",
+		/* 47 */ "FXBUS2_15",
+		/* 48 */ "FXBUS2_16",
+		/* 49 */ "FXBUS2_17",
+		/* 50 */ "FXBUS2_18",
+		/* 51 */ "FXBUS2_19",
+		/* 52 */ "FXBUS2_20",
+		/* 53 */ "FXBUS2_21",
+		/* 54 */ "FXBUS2_22",
+		/* 55 */ "FXBUS2_23",
+		/* 56 */ "FXBUS2_24",
+		/* 57 */ "FXBUS2_25",
+		/* 58 */ "FXBUS2_26",
+		/* 59 */ "FXBUS2_27",
+		/* 60 */ "FXBUS2_28",
+		/* 61 */ "FXBUS2_29",
+		/* 62 */ "FXBUS2_30",
+		/* 63 */ "FXBUS2_31"
+	};
+
+	struct snd_emu10k1 *emu = entry->private_data;
+	unsigned int val, val1;
+	int nefx = emu->audigy ? 64 : 32;
+	char **outputs = emu->audigy ? audigy_outs : creative_outs;
+	int idx;
+	
+	snd_iprintf(buffer, "EMU10K1\n\n");
+	snd_iprintf(buffer, "Card                  : %s\n",
+		    emu->audigy ? "Audigy" : (emu->card_capabilities->ecard ? "EMU APS" : "Creative"));
+	snd_iprintf(buffer, "Internal TRAM (words) : 0x%x\n", emu->fx8010.itram_size);
+	snd_iprintf(buffer, "External TRAM (words) : 0x%x\n", (int)emu->fx8010.etram_pages.bytes / 2);
+	snd_iprintf(buffer, "\n");
+	snd_iprintf(buffer, "Effect Send Routing   :\n");
+	for (idx = 0; idx < NUM_G; idx++) {
+		val = emu->audigy ?
+			snd_emu10k1_ptr_read(emu, A_FXRT1, idx) :
+			snd_emu10k1_ptr_read(emu, FXRT, idx);
+		val1 = emu->audigy ?
+			snd_emu10k1_ptr_read(emu, A_FXRT2, idx) :
+			0;
+		if (emu->audigy) {
+			snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i, ",
+				idx,
+				val & 0x3f,
+				(val >> 8) & 0x3f,
+				(val >> 16) & 0x3f,
+				(val >> 24) & 0x3f);
+			snd_iprintf(buffer, "E=%i, F=%i, G=%i, H=%i\n",
+				val1 & 0x3f,
+				(val1 >> 8) & 0x3f,
+				(val1 >> 16) & 0x3f,
+				(val1 >> 24) & 0x3f);
+		} else {
+			snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i\n",
+				idx,
+				(val >> 16) & 0x0f,
+				(val >> 20) & 0x0f,
+				(val >> 24) & 0x0f,
+				(val >> 28) & 0x0f);
+		}
+	}
+	snd_iprintf(buffer, "\nCaptured FX Outputs   :\n");
+	for (idx = 0; idx < nefx; idx++) {
+		if (emu->efx_voices_mask[idx/32] & (1 << (idx%32)))
+			snd_iprintf(buffer, "  Output %02i [%s]\n", idx, outputs[idx]);
+	}
+	snd_iprintf(buffer, "\nAll FX Outputs        :\n");
+	for (idx = 0; idx < (emu->audigy ? 64 : 32); idx++)
+		snd_iprintf(buffer, "  Output %02i [%s]\n", idx, outputs[idx]);
+}
+
+static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer)
+{
+	struct snd_emu10k1 *emu = entry->private_data;
+	u32 value;
+	u32 value2;
+	unsigned long flags;
+	u32 rate;
+
+	if (emu->card_capabilities->emu_model) {
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		snd_emu1010_fpga_read(emu, 0x38, &value);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+		if ((value & 0x1) == 0) {
+			spin_lock_irqsave(&emu->emu_lock, flags);
+			snd_emu1010_fpga_read(emu, 0x2a, &value);
+			snd_emu1010_fpga_read(emu, 0x2b, &value2);
+			spin_unlock_irqrestore(&emu->emu_lock, flags);
+			rate = 0x1770000 / (((value << 5) | value2)+1);	
+			snd_iprintf(buffer, "ADAT Locked : %u\n", rate);
+		} else {
+			snd_iprintf(buffer, "ADAT Unlocked\n");
+		}
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		snd_emu1010_fpga_read(emu, 0x20, &value);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+		if ((value & 0x4) == 0) {
+			spin_lock_irqsave(&emu->emu_lock, flags);
+			snd_emu1010_fpga_read(emu, 0x28, &value);
+			snd_emu1010_fpga_read(emu, 0x29, &value2);
+			spin_unlock_irqrestore(&emu->emu_lock, flags);
+			rate = 0x1770000 / (((value << 5) | value2)+1);	
+			snd_iprintf(buffer, "SPDIF Locked : %d\n", rate);
+		} else {
+			snd_iprintf(buffer, "SPDIF Unlocked\n");
+		}
+	} else {
+		snd_emu10k1_proc_spdif_status(emu, buffer, "CD-ROM S/PDIF In", CDCS, CDSRCS);
+		snd_emu10k1_proc_spdif_status(emu, buffer, "Optical or Coax S/PDIF In", GPSCS, GPSRCS);
+	}
+#if 0
+	val = snd_emu10k1_ptr_read(emu, ZVSRCS, 0);
+	snd_iprintf(buffer, "\nZoomed Video\n");
+	snd_iprintf(buffer, "Rate Locked           : %s\n", val & SRCS_RATELOCKED ? "on" : "off");
+	snd_iprintf(buffer, "Estimated Sample Rate : 0x%x\n", val & SRCS_ESTSAMPLERATE);
+#endif
+}
+
+static void snd_emu10k1_proc_rates_read(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer)
+{
+	static int samplerate[8] = { 44100, 48000, 96000, 192000, 4, 5, 6, 7 };
+	struct snd_emu10k1 *emu = entry->private_data;
+	unsigned int val, tmp, n;
+	val = snd_emu10k1_ptr20_read(emu, CAPTURE_RATE_STATUS, 0);
+	tmp = (val >> 16) & 0x8;
+	for (n = 0; n < 4; n++) {
+		tmp = val >> (16 + (n*4));
+		if (tmp & 0x8) snd_iprintf(buffer, "Channel %d: Rate=%d\n", n, samplerate[tmp & 0x7]);
+		else snd_iprintf(buffer, "Channel %d: No input\n", n);
+	}
+}
+
+static void snd_emu10k1_proc_acode_read(struct snd_info_entry *entry, 
+				        struct snd_info_buffer *buffer)
+{
+	u32 pc;
+	struct snd_emu10k1 *emu = entry->private_data;
+
+	snd_iprintf(buffer, "FX8010 Instruction List '%s'\n", emu->fx8010.name);
+	snd_iprintf(buffer, "  Code dump      :\n");
+	for (pc = 0; pc < (emu->audigy ? 1024 : 512); pc++) {
+		u32 low, high;
+			
+		low = snd_emu10k1_efx_read(emu, pc * 2);
+		high = snd_emu10k1_efx_read(emu, pc * 2 + 1);
+		if (emu->audigy)
+			snd_iprintf(buffer, "    OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n",
+				    (high >> 24) & 0x0f,
+				    (high >> 12) & 0x7ff,
+				    (high >> 0) & 0x7ff,
+				    (low >> 12) & 0x7ff,
+				    (low >> 0) & 0x7ff,
+				    pc,
+				    high, low);
+		else
+			snd_iprintf(buffer, "    OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n",
+				    (high >> 20) & 0x0f,
+				    (high >> 10) & 0x3ff,
+				    (high >> 0) & 0x3ff,
+				    (low >> 10) & 0x3ff,
+				    (low >> 0) & 0x3ff,
+				    pc,
+				    high, low);
+	}
+}
+
+#define TOTAL_SIZE_GPR		(0x100*4)
+#define A_TOTAL_SIZE_GPR	(0x200*4)
+#define TOTAL_SIZE_TANKMEM_DATA	(0xa0*4)
+#define TOTAL_SIZE_TANKMEM_ADDR (0xa0*4)
+#define A_TOTAL_SIZE_TANKMEM_DATA (0x100*4)
+#define A_TOTAL_SIZE_TANKMEM_ADDR (0x100*4)
+#define TOTAL_SIZE_CODE		(0x200*8)
+#define A_TOTAL_SIZE_CODE	(0x400*8)
+
+static ssize_t snd_emu10k1_fx8010_read(struct snd_info_entry *entry,
+				       void *file_private_data,
+				       struct file *file, char __user *buf,
+				       size_t count, loff_t pos)
+{
+	struct snd_emu10k1 *emu = entry->private_data;
+	unsigned int offset;
+	int tram_addr = 0;
+	unsigned int *tmp;
+	long res;
+	unsigned int idx;
+	
+	if (!strcmp(entry->name, "fx8010_tram_addr")) {
+		offset = TANKMEMADDRREGBASE;
+		tram_addr = 1;
+	} else if (!strcmp(entry->name, "fx8010_tram_data")) {
+		offset = TANKMEMDATAREGBASE;
+	} else if (!strcmp(entry->name, "fx8010_code")) {
+		offset = emu->audigy ? A_MICROCODEBASE : MICROCODEBASE;
+	} else {
+		offset = emu->audigy ? A_FXGPREGBASE : FXGPREGBASE;
+	}
+
+	tmp = kmalloc(count + 8, GFP_KERNEL);
+	if (!tmp)
+		return -ENOMEM;
+	for (idx = 0; idx < ((pos & 3) + count + 3) >> 2; idx++) {
+		unsigned int val;
+		val = snd_emu10k1_ptr_read(emu, offset + idx + (pos >> 2), 0);
+		if (tram_addr && emu->audigy) {
+			val >>= 11;
+			val |= snd_emu10k1_ptr_read(emu, 0x100 + idx + (pos >> 2), 0) << 20;
+		}
+		tmp[idx] = val;
+	}
+	if (copy_to_user(buf, ((char *)tmp) + (pos & 3), count))
+		res = -EFAULT;
+	else
+		res = count;
+	kfree(tmp);
+	return res;
+}
+
+static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer)
+{
+	struct snd_emu10k1 *emu = entry->private_data;
+	struct snd_emu10k1_voice *voice;
+	int idx;
+	
+	snd_iprintf(buffer, "ch\tuse\tpcm\tefx\tsynth\tmidi\n");
+	for (idx = 0; idx < NUM_G; idx++) {
+		voice = &emu->voices[idx];
+		snd_iprintf(buffer, "%i\t%i\t%i\t%i\t%i\t%i\n",
+			idx,
+			voice->use,
+			voice->pcm,
+			voice->efx,
+			voice->synth,
+			voice->midi);
+	}
+}
+
+#ifdef CONFIG_SND_DEBUG
+static void snd_emu_proc_emu1010_reg_read(struct snd_info_entry *entry,
+				     struct snd_info_buffer *buffer)
+{
+	struct snd_emu10k1 *emu = entry->private_data;
+	u32 value;
+	unsigned long flags;
+	int i;
+	snd_iprintf(buffer, "EMU1010 Registers:\n\n");
+
+	for(i = 0; i < 0x40; i+=1) {
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		snd_emu1010_fpga_read(emu, i, &value);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+		snd_iprintf(buffer, "%02X: %08X, %02X\n", i, value, (value >> 8) & 0x7f);
+	}
+}
+
+static void snd_emu_proc_io_reg_read(struct snd_info_entry *entry,
+				     struct snd_info_buffer *buffer)
+{
+	struct snd_emu10k1 *emu = entry->private_data;
+	unsigned long value;
+	unsigned long flags;
+	int i;
+	snd_iprintf(buffer, "IO Registers:\n\n");
+	for(i = 0; i < 0x40; i+=4) {
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		value = inl(emu->port + i);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+		snd_iprintf(buffer, "%02X: %08lX\n", i, value);
+	}
+}
+
+static void snd_emu_proc_io_reg_write(struct snd_info_entry *entry,
+                                      struct snd_info_buffer *buffer)
+{
+	struct snd_emu10k1 *emu = entry->private_data;
+	unsigned long flags;
+	char line[64];
+	u32 reg, val;
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		if (sscanf(line, "%x %x", &reg, &val) != 2)
+			continue;
+		if (reg < 0x40 && val <= 0xffffffff) {
+			spin_lock_irqsave(&emu->emu_lock, flags);
+			outl(val, emu->port + (reg & 0xfffffffc));
+			spin_unlock_irqrestore(&emu->emu_lock, flags);
+		}
+	}
+}
+
+static unsigned int snd_ptr_read(struct snd_emu10k1 * emu,
+				 unsigned int iobase,
+				 unsigned int reg,
+				 unsigned int chn)
+{
+	unsigned long flags;
+	unsigned int regptr, val;
+
+	regptr = (reg << 16) | chn;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(regptr, emu->port + iobase + PTR);
+	val = inl(emu->port + iobase + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+	return val;
+}
+
+static void snd_ptr_write(struct snd_emu10k1 *emu,
+			  unsigned int iobase,
+			  unsigned int reg,
+			  unsigned int chn,
+			  unsigned int data)
+{
+	unsigned int regptr;
+	unsigned long flags;
+
+	regptr = (reg << 16) | chn;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(regptr, emu->port + iobase + PTR);
+	outl(data, emu->port + iobase + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+
+static void snd_emu_proc_ptr_reg_read(struct snd_info_entry *entry,
+				      struct snd_info_buffer *buffer, int iobase, int offset, int length, int voices)
+{
+	struct snd_emu10k1 *emu = entry->private_data;
+	unsigned long value;
+	int i,j;
+	if (offset+length > 0xa0) {
+		snd_iprintf(buffer, "Input values out of range\n");
+		return;
+	}
+	snd_iprintf(buffer, "Registers 0x%x\n", iobase);
+	for(i = offset; i < offset+length; i++) {
+		snd_iprintf(buffer, "%02X: ",i);
+		for (j = 0; j < voices; j++) {
+			if(iobase == 0)
+                		value = snd_ptr_read(emu, 0, i, j);
+			else
+                		value = snd_ptr_read(emu, 0x20, i, j);
+			snd_iprintf(buffer, "%08lX ", value);
+		}
+		snd_iprintf(buffer, "\n");
+	}
+}
+
+static void snd_emu_proc_ptr_reg_write(struct snd_info_entry *entry,
+				       struct snd_info_buffer *buffer, int iobase)
+{
+	struct snd_emu10k1 *emu = entry->private_data;
+	char line[64];
+	unsigned int reg, channel_id , val;
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		if (sscanf(line, "%x %x %x", &reg, &channel_id, &val) != 3)
+			continue;
+		if (reg < 0xa0 && val <= 0xffffffff && channel_id <= 3)
+			snd_ptr_write(emu, iobase, reg, channel_id, val);
+	}
+}
+
+static void snd_emu_proc_ptr_reg_write00(struct snd_info_entry *entry,
+					 struct snd_info_buffer *buffer)
+{
+	snd_emu_proc_ptr_reg_write(entry, buffer, 0);
+}
+
+static void snd_emu_proc_ptr_reg_write20(struct snd_info_entry *entry,
+					 struct snd_info_buffer *buffer)
+{
+	snd_emu_proc_ptr_reg_write(entry, buffer, 0x20);
+}
+	
+
+static void snd_emu_proc_ptr_reg_read00a(struct snd_info_entry *entry,
+					 struct snd_info_buffer *buffer)
+{
+	snd_emu_proc_ptr_reg_read(entry, buffer, 0, 0, 0x40, 64);
+}
+
+static void snd_emu_proc_ptr_reg_read00b(struct snd_info_entry *entry,
+					 struct snd_info_buffer *buffer)
+{
+	snd_emu_proc_ptr_reg_read(entry, buffer, 0, 0x40, 0x40, 64);
+}
+
+static void snd_emu_proc_ptr_reg_read20a(struct snd_info_entry *entry,
+					 struct snd_info_buffer *buffer)
+{
+	snd_emu_proc_ptr_reg_read(entry, buffer, 0x20, 0, 0x40, 4);
+}
+
+static void snd_emu_proc_ptr_reg_read20b(struct snd_info_entry *entry,
+					 struct snd_info_buffer *buffer)
+{
+	snd_emu_proc_ptr_reg_read(entry, buffer, 0x20, 0x40, 0x40, 4);
+}
+
+static void snd_emu_proc_ptr_reg_read20c(struct snd_info_entry *entry,
+					 struct snd_info_buffer * buffer)
+{
+	snd_emu_proc_ptr_reg_read(entry, buffer, 0x20, 0x80, 0x20, 4);
+}
+#endif
+
+static struct snd_info_entry_ops snd_emu10k1_proc_ops_fx8010 = {
+	.read = snd_emu10k1_fx8010_read,
+};
+
+int __devinit snd_emu10k1_proc_init(struct snd_emu10k1 * emu)
+{
+	struct snd_info_entry *entry;
+#ifdef CONFIG_SND_DEBUG
+	if (emu->card_capabilities->emu_model) {
+		if (! snd_card_proc_new(emu->card, "emu1010_regs", &entry)) 
+			snd_info_set_text_ops(entry, emu, snd_emu_proc_emu1010_reg_read);
+	}
+	if (! snd_card_proc_new(emu->card, "io_regs", &entry)) {
+		snd_info_set_text_ops(entry, emu, snd_emu_proc_io_reg_read);
+		entry->c.text.write = snd_emu_proc_io_reg_write;
+		entry->mode |= S_IWUSR;
+	}
+	if (! snd_card_proc_new(emu->card, "ptr_regs00a", &entry)) {
+		snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read00a);
+		entry->c.text.write = snd_emu_proc_ptr_reg_write00;
+		entry->mode |= S_IWUSR;
+	}
+	if (! snd_card_proc_new(emu->card, "ptr_regs00b", &entry)) {
+		snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read00b);
+		entry->c.text.write = snd_emu_proc_ptr_reg_write00;
+		entry->mode |= S_IWUSR;
+	}
+	if (! snd_card_proc_new(emu->card, "ptr_regs20a", &entry)) {
+		snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20a);
+		entry->c.text.write = snd_emu_proc_ptr_reg_write20;
+		entry->mode |= S_IWUSR;
+	}
+	if (! snd_card_proc_new(emu->card, "ptr_regs20b", &entry)) {
+		snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20b);
+		entry->c.text.write = snd_emu_proc_ptr_reg_write20;
+		entry->mode |= S_IWUSR;
+	}
+	if (! snd_card_proc_new(emu->card, "ptr_regs20c", &entry)) {
+		snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20c);
+		entry->c.text.write = snd_emu_proc_ptr_reg_write20;
+		entry->mode |= S_IWUSR;
+	}
+#endif
+	
+	if (! snd_card_proc_new(emu->card, "emu10k1", &entry))
+		snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_read);
+
+	if (emu->card_capabilities->emu10k2_chip) {
+		if (! snd_card_proc_new(emu->card, "spdif-in", &entry))
+			snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_spdif_read);
+	}
+	if (emu->card_capabilities->ca0151_chip) {
+		if (! snd_card_proc_new(emu->card, "capture-rates", &entry))
+			snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_rates_read);
+	}
+
+	if (! snd_card_proc_new(emu->card, "voices", &entry))
+		snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_voices_read);
+
+	if (! snd_card_proc_new(emu->card, "fx8010_gpr", &entry)) {
+		entry->content = SNDRV_INFO_CONTENT_DATA;
+		entry->private_data = emu;
+		entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
+		entry->size = emu->audigy ? A_TOTAL_SIZE_GPR : TOTAL_SIZE_GPR;
+		entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
+	}
+	if (! snd_card_proc_new(emu->card, "fx8010_tram_data", &entry)) {
+		entry->content = SNDRV_INFO_CONTENT_DATA;
+		entry->private_data = emu;
+		entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
+		entry->size = emu->audigy ? A_TOTAL_SIZE_TANKMEM_DATA : TOTAL_SIZE_TANKMEM_DATA ;
+		entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
+	}
+	if (! snd_card_proc_new(emu->card, "fx8010_tram_addr", &entry)) {
+		entry->content = SNDRV_INFO_CONTENT_DATA;
+		entry->private_data = emu;
+		entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
+		entry->size = emu->audigy ? A_TOTAL_SIZE_TANKMEM_ADDR : TOTAL_SIZE_TANKMEM_ADDR ;
+		entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
+	}
+	if (! snd_card_proc_new(emu->card, "fx8010_code", &entry)) {
+		entry->content = SNDRV_INFO_CONTENT_DATA;
+		entry->private_data = emu;
+		entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
+		entry->size = emu->audigy ? A_TOTAL_SIZE_CODE : TOTAL_SIZE_CODE;
+		entry->c.ops = &snd_emu10k1_proc_ops_fx8010;
+	}
+	if (! snd_card_proc_new(emu->card, "fx8010_acode", &entry)) {
+		entry->content = SNDRV_INFO_CONTENT_TEXT;
+		entry->private_data = emu;
+		entry->mode = S_IFREG | S_IRUGO /*| S_IWUSR*/;
+		entry->c.text.read = snd_emu10k1_proc_acode_read;
+	}
+	return 0;
+}
+#endif /* CONFIG_PROC_FS */
diff --git a/linux/sound/pci/emu10k1/io.c b/linux/sound/pci/emu10k1/io.c
new file mode 100644
index 0000000..5ef7080
--- /dev/null
+++ b/linux/sound/pci/emu10k1/io.c
@@ -0,0 +1,582 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *                   Creative Labs, Inc.
+ *  Routines for control of EMU10K1 chips
+ *
+ *  BUGS:
+ *    --
+ *
+ *  TODO:
+ *    --
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+#include <linux/delay.h>
+#include "p17v.h"
+
+unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn)
+{
+	unsigned long flags;
+	unsigned int regptr, val;
+	unsigned int mask;
+
+	mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK;
+	regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK);
+
+	if (reg & 0xff000000) {
+		unsigned char size, offset;
+		
+		size = (reg >> 24) & 0x3f;
+		offset = (reg >> 16) & 0x1f;
+		mask = ((1 << size) - 1) << offset;
+		
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		outl(regptr, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+		
+		return (val & mask) >> offset;
+	} else {
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		outl(regptr, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+		return val;
+	}
+}
+
+EXPORT_SYMBOL(snd_emu10k1_ptr_read);
+
+void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data)
+{
+	unsigned int regptr;
+	unsigned long flags;
+	unsigned int mask;
+
+	if (!emu) {
+		snd_printk(KERN_ERR "ptr_write: emu is null!\n");
+		dump_stack();
+		return;
+	}
+	mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK;
+	regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK);
+
+	if (reg & 0xff000000) {
+		unsigned char size, offset;
+
+		size = (reg >> 24) & 0x3f;
+		offset = (reg >> 16) & 0x1f;
+		mask = ((1 << size) - 1) << offset;
+		data = (data << offset) & mask;
+
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		outl(regptr, emu->port + PTR);
+		data |= inl(emu->port + DATA) & ~mask;
+		outl(data, emu->port + DATA);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);		
+	} else {
+		spin_lock_irqsave(&emu->emu_lock, flags);
+		outl(regptr, emu->port + PTR);
+		outl(data, emu->port + DATA);
+		spin_unlock_irqrestore(&emu->emu_lock, flags);
+	}
+}
+
+EXPORT_SYMBOL(snd_emu10k1_ptr_write);
+
+unsigned int snd_emu10k1_ptr20_read(struct snd_emu10k1 * emu, 
+					  unsigned int reg, 
+					  unsigned int chn)
+{
+	unsigned long flags;
+	unsigned int regptr, val;
+  
+	regptr = (reg << 16) | chn;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(regptr, emu->port + 0x20 + PTR);
+	val = inl(emu->port + 0x20 + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+	return val;
+}
+
+void snd_emu10k1_ptr20_write(struct snd_emu10k1 *emu, 
+				   unsigned int reg, 
+				   unsigned int chn, 
+				   unsigned int data)
+{
+	unsigned int regptr;
+	unsigned long flags;
+
+	regptr = (reg << 16) | chn;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(regptr, emu->port + 0x20 + PTR);
+	outl(data, emu->port + 0x20 + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+int snd_emu10k1_spi_write(struct snd_emu10k1 * emu,
+				   unsigned int data)
+{
+	unsigned int reset, set;
+	unsigned int reg, tmp;
+	int n, result;
+	int err = 0;
+
+	/* This function is not re-entrant, so protect against it. */
+	spin_lock(&emu->spi_lock);
+	if (emu->card_capabilities->ca0108_chip)
+		reg = 0x3c; /* PTR20, reg 0x3c */
+	else {
+		/* For other chip types the SPI register
+		 * is currently unknown. */
+		err = 1;
+		goto spi_write_exit;
+	}
+	if (data > 0xffff) {
+		/* Only 16bit values allowed */
+		err = 1;
+		goto spi_write_exit;
+	}
+
+	tmp = snd_emu10k1_ptr20_read(emu, reg, 0);
+	reset = (tmp & ~0x3ffff) | 0x20000; /* Set xxx20000 */
+	set = reset | 0x10000; /* Set xxx1xxxx */
+	snd_emu10k1_ptr20_write(emu, reg, 0, reset | data);
+	tmp = snd_emu10k1_ptr20_read(emu, reg, 0); /* write post */
+	snd_emu10k1_ptr20_write(emu, reg, 0, set | data);
+	result = 1;
+	/* Wait for status bit to return to 0 */
+	for (n = 0; n < 100; n++) {
+		udelay(10);
+		tmp = snd_emu10k1_ptr20_read(emu, reg, 0);
+		if (!(tmp & 0x10000)) {
+			result = 0;
+			break;
+		}
+	}
+	if (result) {
+		/* Timed out */
+		err = 1;
+		goto spi_write_exit;
+	}
+	snd_emu10k1_ptr20_write(emu, reg, 0, reset | data);
+	tmp = snd_emu10k1_ptr20_read(emu, reg, 0); /* Write post */
+	err = 0;
+spi_write_exit:
+	spin_unlock(&emu->spi_lock);
+	return err;
+}
+
+/* The ADC does not support i2c read, so only write is implemented */
+int snd_emu10k1_i2c_write(struct snd_emu10k1 *emu,
+				u32 reg,
+				u32 value)
+{
+	u32 tmp;
+	int timeout = 0;
+	int status;
+	int retry;
+	int err = 0;
+
+	if ((reg > 0x7f) || (value > 0x1ff)) {
+		snd_printk(KERN_ERR "i2c_write: invalid values.\n");
+		return -EINVAL;
+	}
+
+	/* This function is not re-entrant, so protect against it. */
+	spin_lock(&emu->i2c_lock);
+
+	tmp = reg << 25 | value << 16;
+
+	/* This controls the I2C connected to the WM8775 ADC Codec */
+	snd_emu10k1_ptr20_write(emu, P17V_I2C_1, 0, tmp);
+	tmp = snd_emu10k1_ptr20_read(emu, P17V_I2C_1, 0); /* write post */
+
+	for (retry = 0; retry < 10; retry++) {
+		/* Send the data to i2c */
+		tmp = 0;
+		tmp = tmp | (I2C_A_ADC_LAST|I2C_A_ADC_START|I2C_A_ADC_ADD);
+		snd_emu10k1_ptr20_write(emu, P17V_I2C_ADDR, 0, tmp);
+
+		/* Wait till the transaction ends */
+		while (1) {
+			mdelay(1);
+			status = snd_emu10k1_ptr20_read(emu, P17V_I2C_ADDR, 0);
+			timeout++;
+			if ((status & I2C_A_ADC_START) == 0)
+				break;
+
+			if (timeout > 1000) {
+                		snd_printk(KERN_WARNING
+					   "emu10k1:I2C:timeout status=0x%x\n",
+					   status);
+				break;
+			}
+		}
+		//Read back and see if the transaction is successful
+		if ((status & I2C_A_ADC_ABORT) == 0)
+			break;
+	}
+
+	if (retry == 10) {
+		snd_printk(KERN_ERR "Writing to ADC failed!\n");
+		snd_printk(KERN_ERR "status=0x%x, reg=%d, value=%d\n",
+			status, reg, value);
+		/* dump_stack(); */
+		err = -EINVAL;
+	}
+    
+	spin_unlock(&emu->i2c_lock);
+	return err;
+}
+
+int snd_emu1010_fpga_write(struct snd_emu10k1 * emu, u32 reg, u32 value)
+{
+	unsigned long flags;
+
+	if (reg > 0x3f)
+		return 1;
+	reg += 0x40; /* 0x40 upwards are registers. */
+	if (value > 0x3f) /* 0 to 0x3f are values */
+		return 1;
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(reg, emu->port + A_IOCFG);
+	udelay(10);
+	outl(reg | 0x80, emu->port + A_IOCFG);  /* High bit clocks the value into the fpga. */
+	udelay(10);
+	outl(value, emu->port + A_IOCFG);
+	udelay(10);
+	outl(value | 0x80 , emu->port + A_IOCFG);  /* High bit clocks the value into the fpga. */
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+
+	return 0;
+}
+
+int snd_emu1010_fpga_read(struct snd_emu10k1 * emu, u32 reg, u32 *value)
+{
+	unsigned long flags;
+	if (reg > 0x3f)
+		return 1;
+	reg += 0x40; /* 0x40 upwards are registers. */
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outl(reg, emu->port + A_IOCFG);
+	udelay(10);
+	outl(reg | 0x80, emu->port + A_IOCFG);  /* High bit clocks the value into the fpga. */
+	udelay(10);
+	*value = ((inl(emu->port + A_IOCFG) >> 8) & 0x7f);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+
+	return 0;
+}
+
+/* Each Destination has one and only one Source,
+ * but one Source can feed any number of Destinations simultaneously.
+ */
+int snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 * emu, u32 dst, u32 src)
+{
+	snd_emu1010_fpga_write(emu, 0x00, ((dst >> 8) & 0x3f) );
+	snd_emu1010_fpga_write(emu, 0x01, (dst & 0x3f) );
+	snd_emu1010_fpga_write(emu, 0x02, ((src >> 8) & 0x3f) );
+	snd_emu1010_fpga_write(emu, 0x03, (src & 0x3f) );
+
+	return 0;
+}
+
+void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb)
+{
+	unsigned long flags;
+	unsigned int enable;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	enable = inl(emu->port + INTE) | intrenb;
+	outl(enable, emu->port + INTE);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb)
+{
+	unsigned long flags;
+	unsigned int enable;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	enable = inl(emu->port + INTE) & ~intrenb;
+	outl(enable, emu->port + INTE);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+	unsigned long flags;
+	unsigned int val;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	/* voice interrupt */
+	if (voicenum >= 32) {
+		outl(CLIEH << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val |= 1 << (voicenum - 32);
+	} else {
+		outl(CLIEL << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val |= 1 << voicenum;
+	}
+	outl(val, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+	unsigned long flags;
+	unsigned int val;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	/* voice interrupt */
+	if (voicenum >= 32) {
+		outl(CLIEH << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val &= ~(1 << (voicenum - 32));
+	} else {
+		outl(CLIEL << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val &= ~(1 << voicenum);
+	}
+	outl(val, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	/* voice interrupt */
+	if (voicenum >= 32) {
+		outl(CLIPH << 16, emu->port + PTR);
+		voicenum = 1 << (voicenum - 32);
+	} else {
+		outl(CLIPL << 16, emu->port + PTR);
+		voicenum = 1 << voicenum;
+	}
+	outl(voicenum, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_half_loop_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+	unsigned long flags;
+	unsigned int val;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	/* voice interrupt */
+	if (voicenum >= 32) {
+		outl(HLIEH << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val |= 1 << (voicenum - 32);
+	} else {
+		outl(HLIEL << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val |= 1 << voicenum;
+	}
+	outl(val, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_half_loop_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+	unsigned long flags;
+	unsigned int val;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	/* voice interrupt */
+	if (voicenum >= 32) {
+		outl(HLIEH << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val &= ~(1 << (voicenum - 32));
+	} else {
+		outl(HLIEL << 16, emu->port + PTR);
+		val = inl(emu->port + DATA);
+		val &= ~(1 << voicenum);
+	}
+	outl(val, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	/* voice interrupt */
+	if (voicenum >= 32) {
+		outl(HLIPH << 16, emu->port + PTR);
+		voicenum = 1 << (voicenum - 32);
+	} else {
+		outl(HLIPL << 16, emu->port + PTR);
+		voicenum = 1 << voicenum;
+	}
+	outl(voicenum, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+	unsigned long flags;
+	unsigned int sol;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	/* voice interrupt */
+	if (voicenum >= 32) {
+		outl(SOLEH << 16, emu->port + PTR);
+		sol = inl(emu->port + DATA);
+		sol |= 1 << (voicenum - 32);
+	} else {
+		outl(SOLEL << 16, emu->port + PTR);
+		sol = inl(emu->port + DATA);
+		sol |= 1 << voicenum;
+	}
+	outl(sol, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum)
+{
+	unsigned long flags;
+	unsigned int sol;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	/* voice interrupt */
+	if (voicenum >= 32) {
+		outl(SOLEH << 16, emu->port + PTR);
+		sol = inl(emu->port + DATA);
+		sol &= ~(1 << (voicenum - 32));
+	} else {
+		outl(SOLEL << 16, emu->port + PTR);
+		sol = inl(emu->port + DATA);
+		sol &= ~(1 << voicenum);
+	}
+	outl(sol, emu->port + DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait)
+{
+	volatile unsigned count;
+	unsigned int newtime = 0, curtime;
+
+	curtime = inl(emu->port + WC) >> 6;
+	while (wait-- > 0) {
+		count = 0;
+		while (count++ < 16384) {
+			newtime = inl(emu->port + WC) >> 6;
+			if (newtime != curtime)
+				break;
+		}
+		if (count > 16384)
+			break;
+		curtime = newtime;
+	}
+}
+
+unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct snd_emu10k1 *emu = ac97->private_data;
+	unsigned long flags;
+	unsigned short val;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outb(reg, emu->port + AC97ADDRESS);
+	val = inw(emu->port + AC97DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+	return val;
+}
+
+void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short data)
+{
+	struct snd_emu10k1 *emu = ac97->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	outb(reg, emu->port + AC97ADDRESS);
+	outw(data, emu->port + AC97DATA);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+/*
+ *  convert rate to pitch
+ */
+
+unsigned int snd_emu10k1_rate_to_pitch(unsigned int rate)
+{
+	static u32 logMagTable[128] = {
+		0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2,
+		0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5,
+		0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081,
+		0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191,
+		0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7,
+		0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829,
+		0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e,
+		0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26,
+		0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d,
+		0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885,
+		0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899,
+		0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c,
+		0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3,
+		0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3,
+		0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83,
+		0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df
+	};
+	static char logSlopeTable[128] = {
+		0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58,
+		0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53,
+		0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f,
+		0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b,
+		0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47,
+		0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44,
+		0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41,
+		0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e,
+		0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c,
+		0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39,
+		0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37,
+		0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35,
+		0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34,
+		0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32,
+		0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30,
+		0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f
+	};
+	int i;
+
+	if (rate == 0)
+		return 0;	/* Bail out if no leading "1" */
+	rate *= 11185;		/* Scale 48000 to 0x20002380 */
+	for (i = 31; i > 0; i--) {
+		if (rate & 0x80000000) {	/* Detect leading "1" */
+			return (((unsigned int) (i - 15) << 20) +
+			       logMagTable[0x7f & (rate >> 24)] +
+					(0x7f & (rate >> 17)) *
+					logSlopeTable[0x7f & (rate >> 24)]);
+		}
+		rate <<= 1;
+	}
+
+	return 0;		/* Should never reach this point */
+}
+
diff --git a/linux/sound/pci/emu10k1/irq.c b/linux/sound/pci/emu10k1/irq.c
new file mode 100644
index 0000000..30bfed6
--- /dev/null
+++ b/linux/sound/pci/emu10k1/irq.c
@@ -0,0 +1,208 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *                   Creative Labs, Inc.
+ *  Routines for IRQ control of EMU10K1 chips
+ *
+ *  BUGS:
+ *    --
+ *
+ *  TODO:
+ *    --
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id)
+{
+	struct snd_emu10k1 *emu = dev_id;
+	unsigned int status, status2, orig_status, orig_status2;
+	int handled = 0;
+	int timeout = 0;
+
+	while (((status = inl(emu->port + IPR)) != 0) && (timeout < 1000)) {
+		timeout++;
+		orig_status = status;
+		handled = 1;
+		if ((status & 0xffffffff) == 0xffffffff) {
+			snd_printk(KERN_INFO "snd-emu10k1: Suspected sound card removal\n");
+			break;
+		}
+		if (status & IPR_PCIERROR) {
+			snd_printk(KERN_ERR "interrupt: PCI error\n");
+			snd_emu10k1_intr_disable(emu, INTE_PCIERRORENABLE);
+			status &= ~IPR_PCIERROR;
+		}
+		if (status & (IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE)) {
+			if (emu->hwvol_interrupt)
+				emu->hwvol_interrupt(emu, status);
+			else
+				snd_emu10k1_intr_disable(emu, INTE_VOLINCRENABLE|INTE_VOLDECRENABLE|INTE_MUTEENABLE);
+			status &= ~(IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE);
+		}
+		if (status & IPR_CHANNELLOOP) {
+			int voice;
+			int voice_max = status & IPR_CHANNELNUMBERMASK;
+			u32 val;
+			struct snd_emu10k1_voice *pvoice = emu->voices;
+
+			val = snd_emu10k1_ptr_read(emu, CLIPL, 0);
+			for (voice = 0; voice <= voice_max; voice++) {
+				if (voice == 0x20)
+					val = snd_emu10k1_ptr_read(emu, CLIPH, 0);
+				if (val & 1) {
+					if (pvoice->use && pvoice->interrupt != NULL) {
+						pvoice->interrupt(emu, pvoice);
+						snd_emu10k1_voice_intr_ack(emu, voice);
+					} else {
+						snd_emu10k1_voice_intr_disable(emu, voice);
+					}
+				}
+				val >>= 1;
+				pvoice++;
+			}
+			val = snd_emu10k1_ptr_read(emu, HLIPL, 0);
+			for (voice = 0; voice <= voice_max; voice++) {
+				if (voice == 0x20)
+					val = snd_emu10k1_ptr_read(emu, HLIPH, 0);
+				if (val & 1) {
+					if (pvoice->use && pvoice->interrupt != NULL) {
+						pvoice->interrupt(emu, pvoice);
+						snd_emu10k1_voice_half_loop_intr_ack(emu, voice);
+					} else {
+						snd_emu10k1_voice_half_loop_intr_disable(emu, voice);
+					}
+				}
+				val >>= 1;
+				pvoice++;
+			}
+			status &= ~IPR_CHANNELLOOP;
+		}
+		status &= ~IPR_CHANNELNUMBERMASK;
+		if (status & (IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL)) {
+			if (emu->capture_interrupt)
+				emu->capture_interrupt(emu, status);
+			else
+				snd_emu10k1_intr_disable(emu, INTE_ADCBUFENABLE);
+			status &= ~(IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL);
+		}
+		if (status & (IPR_MICBUFFULL|IPR_MICBUFHALFFULL)) {
+			if (emu->capture_mic_interrupt)
+				emu->capture_mic_interrupt(emu, status);
+			else
+				snd_emu10k1_intr_disable(emu, INTE_MICBUFENABLE);
+			status &= ~(IPR_MICBUFFULL|IPR_MICBUFHALFFULL);
+		}
+		if (status & (IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL)) {
+			if (emu->capture_efx_interrupt)
+				emu->capture_efx_interrupt(emu, status);
+			else
+				snd_emu10k1_intr_disable(emu, INTE_EFXBUFENABLE);
+			status &= ~(IPR_EFXBUFFULL|IPR_EFXBUFHALFFULL);
+		}
+		if (status & (IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY)) {
+			if (emu->midi.interrupt)
+				emu->midi.interrupt(emu, status);
+			else
+				snd_emu10k1_intr_disable(emu, INTE_MIDITXENABLE|INTE_MIDIRXENABLE);
+			status &= ~(IPR_MIDITRANSBUFEMPTY|IPR_MIDIRECVBUFEMPTY);
+		}
+		if (status & (IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2)) {
+			if (emu->midi2.interrupt)
+				emu->midi2.interrupt(emu, status);
+			else
+				snd_emu10k1_intr_disable(emu, INTE_A_MIDITXENABLE2|INTE_A_MIDIRXENABLE2);
+			status &= ~(IPR_A_MIDITRANSBUFEMPTY2|IPR_A_MIDIRECVBUFEMPTY2);
+		}
+		if (status & IPR_INTERVALTIMER) {
+			if (emu->timer)
+				snd_timer_interrupt(emu->timer, emu->timer->sticks);
+			else
+				snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB);
+			status &= ~IPR_INTERVALTIMER;
+		}
+		if (status & (IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE)) {
+			if (emu->spdif_interrupt)
+				emu->spdif_interrupt(emu, status);
+			else
+				snd_emu10k1_intr_disable(emu, INTE_GPSPDIFENABLE|INTE_CDSPDIFENABLE);
+			status &= ~(IPR_GPSPDIFSTATUSCHANGE|IPR_CDROMSTATUSCHANGE);
+		}
+		if (status & IPR_FXDSP) {
+			if (emu->dsp_interrupt)
+				emu->dsp_interrupt(emu);
+			else
+				snd_emu10k1_intr_disable(emu, INTE_FXDSPENABLE);
+			status &= ~IPR_FXDSP;
+		}
+		if (status & IPR_P16V) {
+			while ((status2 = inl(emu->port + IPR2)) != 0) {
+				u32 mask = INTE2_PLAYBACK_CH_0_LOOP;  /* Full Loop */
+				struct snd_emu10k1_voice *pvoice = &(emu->p16v_voices[0]);
+				struct snd_emu10k1_voice *cvoice = &(emu->p16v_capture_voice);
+
+				//printk(KERN_INFO "status2=0x%x\n", status2);
+				orig_status2 = status2;
+				if(status2 & mask) {
+					if(pvoice->use) {
+						snd_pcm_period_elapsed(pvoice->epcm->substream);
+					} else { 
+						snd_printk(KERN_ERR "p16v: status: 0x%08x, mask=0x%08x, pvoice=%p, use=%d\n", status2, mask, pvoice, pvoice->use);
+					}
+				}
+				if(status2 & 0x110000) {
+					//printk(KERN_INFO "capture int found\n");
+					if(cvoice->use) {
+						//printk(KERN_INFO "capture period_elapsed\n");
+						snd_pcm_period_elapsed(cvoice->epcm->substream);
+					}
+				}
+				outl(orig_status2, emu->port + IPR2); /* ack all */
+			}
+			status &= ~IPR_P16V;
+		}
+
+		if (status) {
+			unsigned int bits;
+			snd_printk(KERN_ERR "emu10k1: unhandled interrupt: 0x%08x\n", status);
+			//make sure any interrupts we don't handle are disabled:
+			bits = INTE_FXDSPENABLE |
+				INTE_PCIERRORENABLE |
+				INTE_VOLINCRENABLE |
+				INTE_VOLDECRENABLE |
+				INTE_MUTEENABLE |
+				INTE_MICBUFENABLE |
+				INTE_ADCBUFENABLE |
+				INTE_EFXBUFENABLE |
+				INTE_GPSPDIFENABLE |
+				INTE_CDSPDIFENABLE |
+				INTE_INTERVALTIMERENB |
+				INTE_MIDITXENABLE |
+				INTE_MIDIRXENABLE;
+			if (emu->audigy)
+				bits |= INTE_A_MIDITXENABLE2 | INTE_A_MIDIRXENABLE2;
+			snd_emu10k1_intr_disable(emu, bits);
+		}
+		outl(orig_status, emu->port + IPR); /* ack all */
+	}
+	if (timeout == 1000)
+		snd_printk(KERN_INFO "emu10k1 irq routine failure\n");
+
+	return IRQ_RETVAL(handled);
+}
diff --git a/linux/sound/pci/emu10k1/memory.c b/linux/sound/pci/emu10k1/memory.c
new file mode 100644
index 0000000..957a311
--- /dev/null
+++ b/linux/sound/pci/emu10k1/memory.c
@@ -0,0 +1,572 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *
+ *  EMU10K1 memory page allocation (PTB area)
+ *
+ *
+ *   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/pci.h>
+#include <linux/gfp.h>
+#include <linux/time.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+/* page arguments of these two macros are Emu page (4096 bytes), not like
+ * aligned pages in others
+ */
+#define __set_ptb_entry(emu,page,addr) \
+	(((u32 *)(emu)->ptb_pages.area)[page] = cpu_to_le32(((addr) << 1) | (page)))
+
+#define UNIT_PAGES		(PAGE_SIZE / EMUPAGESIZE)
+#define MAX_ALIGN_PAGES		(MAXPAGES / UNIT_PAGES)
+/* get aligned page from offset address */
+#define get_aligned_page(offset)	((offset) >> PAGE_SHIFT)
+/* get offset address from aligned page */
+#define aligned_page_offset(page)	((page) << PAGE_SHIFT)
+
+#if PAGE_SIZE == 4096
+/* page size == EMUPAGESIZE */
+/* fill PTB entrie(s) corresponding to page with addr */
+#define set_ptb_entry(emu,page,addr)	__set_ptb_entry(emu,page,addr)
+/* fill PTB entrie(s) corresponding to page with silence pointer */
+#define set_silent_ptb(emu,page)	__set_ptb_entry(emu,page,emu->silent_page.addr)
+#else
+/* fill PTB entries -- we need to fill UNIT_PAGES entries */
+static inline void set_ptb_entry(struct snd_emu10k1 *emu, int page, dma_addr_t addr)
+{
+	int i;
+	page *= UNIT_PAGES;
+	for (i = 0; i < UNIT_PAGES; i++, page++) {
+		__set_ptb_entry(emu, page, addr);
+		addr += EMUPAGESIZE;
+	}
+}
+static inline void set_silent_ptb(struct snd_emu10k1 *emu, int page)
+{
+	int i;
+	page *= UNIT_PAGES;
+	for (i = 0; i < UNIT_PAGES; i++, page++)
+		/* do not increment ptr */
+		__set_ptb_entry(emu, page, emu->silent_page.addr);
+}
+#endif /* PAGE_SIZE */
+
+
+/*
+ */
+static int synth_alloc_pages(struct snd_emu10k1 *hw, struct snd_emu10k1_memblk *blk);
+static int synth_free_pages(struct snd_emu10k1 *hw, struct snd_emu10k1_memblk *blk);
+
+#define get_emu10k1_memblk(l,member)	list_entry(l, struct snd_emu10k1_memblk, member)
+
+
+/* initialize emu10k1 part */
+static void emu10k1_memblk_init(struct snd_emu10k1_memblk *blk)
+{
+	blk->mapped_page = -1;
+	INIT_LIST_HEAD(&blk->mapped_link);
+	INIT_LIST_HEAD(&blk->mapped_order_link);
+	blk->map_locked = 0;
+
+	blk->first_page = get_aligned_page(blk->mem.offset);
+	blk->last_page = get_aligned_page(blk->mem.offset + blk->mem.size - 1);
+	blk->pages = blk->last_page - blk->first_page + 1;
+}
+
+/*
+ * search empty region on PTB with the given size
+ *
+ * if an empty region is found, return the page and store the next mapped block
+ * in nextp
+ * if not found, return a negative error code.
+ */
+static int search_empty_map_area(struct snd_emu10k1 *emu, int npages, struct list_head **nextp)
+{
+	int page = 0, found_page = -ENOMEM;
+	int max_size = npages;
+	int size;
+	struct list_head *candidate = &emu->mapped_link_head;
+	struct list_head *pos;
+
+	list_for_each (pos, &emu->mapped_link_head) {
+		struct snd_emu10k1_memblk *blk = get_emu10k1_memblk(pos, mapped_link);
+		if (blk->mapped_page < 0)
+			continue;
+		size = blk->mapped_page - page;
+		if (size == npages) {
+			*nextp = pos;
+			return page;
+		}
+		else if (size > max_size) {
+			/* we look for the maximum empty hole */
+			max_size = size;
+			candidate = pos;
+			found_page = page;
+		}
+		page = blk->mapped_page + blk->pages;
+	}
+	size = MAX_ALIGN_PAGES - page;
+	if (size >= max_size) {
+		*nextp = pos;
+		return page;
+	}
+	*nextp = candidate;
+	return found_page;
+}
+
+/*
+ * map a memory block onto emu10k1's PTB
+ *
+ * call with memblk_lock held
+ */
+static int map_memblk(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
+{
+	int page, pg;
+	struct list_head *next;
+
+	page = search_empty_map_area(emu, blk->pages, &next);
+	if (page < 0) /* not found */
+		return page;
+	/* insert this block in the proper position of mapped list */
+	list_add_tail(&blk->mapped_link, next);
+	/* append this as a newest block in order list */
+	list_add_tail(&blk->mapped_order_link, &emu->mapped_order_link_head);
+	blk->mapped_page = page;
+	/* fill PTB */
+	for (pg = blk->first_page; pg <= blk->last_page; pg++) {
+		set_ptb_entry(emu, page, emu->page_addr_table[pg]);
+		page++;
+	}
+	return 0;
+}
+
+/*
+ * unmap the block
+ * return the size of resultant empty pages
+ *
+ * call with memblk_lock held
+ */
+static int unmap_memblk(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
+{
+	int start_page, end_page, mpage, pg;
+	struct list_head *p;
+	struct snd_emu10k1_memblk *q;
+
+	/* calculate the expected size of empty region */
+	if ((p = blk->mapped_link.prev) != &emu->mapped_link_head) {
+		q = get_emu10k1_memblk(p, mapped_link);
+		start_page = q->mapped_page + q->pages;
+	} else
+		start_page = 0;
+	if ((p = blk->mapped_link.next) != &emu->mapped_link_head) {
+		q = get_emu10k1_memblk(p, mapped_link);
+		end_page = q->mapped_page;
+	} else
+		end_page = MAX_ALIGN_PAGES;
+
+	/* remove links */
+	list_del(&blk->mapped_link);
+	list_del(&blk->mapped_order_link);
+	/* clear PTB */
+	mpage = blk->mapped_page;
+	for (pg = blk->first_page; pg <= blk->last_page; pg++) {
+		set_silent_ptb(emu, mpage);
+		mpage++;
+	}
+	blk->mapped_page = -1;
+	return end_page - start_page; /* return the new empty size */
+}
+
+/*
+ * search empty pages with the given size, and create a memory block
+ *
+ * unlike synth_alloc the memory block is aligned to the page start
+ */
+static struct snd_emu10k1_memblk *
+search_empty(struct snd_emu10k1 *emu, int size)
+{
+	struct list_head *p;
+	struct snd_emu10k1_memblk *blk;
+	int page, psize;
+
+	psize = get_aligned_page(size + PAGE_SIZE -1);
+	page = 0;
+	list_for_each(p, &emu->memhdr->block) {
+		blk = get_emu10k1_memblk(p, mem.list);
+		if (page + psize <= blk->first_page)
+			goto __found_pages;
+		page = blk->last_page + 1;
+	}
+	if (page + psize > emu->max_cache_pages)
+		return NULL;
+
+__found_pages:
+	/* create a new memory block */
+	blk = (struct snd_emu10k1_memblk *)__snd_util_memblk_new(emu->memhdr, psize << PAGE_SHIFT, p->prev);
+	if (blk == NULL)
+		return NULL;
+	blk->mem.offset = aligned_page_offset(page); /* set aligned offset */
+	emu10k1_memblk_init(blk);
+	return blk;
+}
+
+
+/*
+ * check if the given pointer is valid for pages
+ */
+static int is_valid_page(struct snd_emu10k1 *emu, dma_addr_t addr)
+{
+	if (addr & ~emu->dma_mask) {
+		snd_printk(KERN_ERR "max memory size is 0x%lx (addr = 0x%lx)!!\n", emu->dma_mask, (unsigned long)addr);
+		return 0;
+	}
+	if (addr & (EMUPAGESIZE-1)) {
+		snd_printk(KERN_ERR "page is not aligned\n");
+		return 0;
+	}
+	return 1;
+}
+
+/*
+ * map the given memory block on PTB.
+ * if the block is already mapped, update the link order.
+ * if no empty pages are found, tries to release unsed memory blocks
+ * and retry the mapping.
+ */
+int snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
+{
+	int err;
+	int size;
+	struct list_head *p, *nextp;
+	struct snd_emu10k1_memblk *deleted;
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->memblk_lock, flags);
+	if (blk->mapped_page >= 0) {
+		/* update order link */
+		list_del(&blk->mapped_order_link);
+		list_add_tail(&blk->mapped_order_link, &emu->mapped_order_link_head);
+		spin_unlock_irqrestore(&emu->memblk_lock, flags);
+		return 0;
+	}
+	if ((err = map_memblk(emu, blk)) < 0) {
+		/* no enough page - try to unmap some blocks */
+		/* starting from the oldest block */
+		p = emu->mapped_order_link_head.next;
+		for (; p != &emu->mapped_order_link_head; p = nextp) {
+			nextp = p->next;
+			deleted = get_emu10k1_memblk(p, mapped_order_link);
+			if (deleted->map_locked)
+				continue;
+			size = unmap_memblk(emu, deleted);
+			if (size >= blk->pages) {
+				/* ok the empty region is enough large */
+				err = map_memblk(emu, blk);
+				break;
+			}
+		}
+	}
+	spin_unlock_irqrestore(&emu->memblk_lock, flags);
+	return err;
+}
+
+EXPORT_SYMBOL(snd_emu10k1_memblk_map);
+
+/*
+ * page allocation for DMA
+ */
+struct snd_util_memblk *
+snd_emu10k1_alloc_pages(struct snd_emu10k1 *emu, struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_util_memhdr *hdr;
+	struct snd_emu10k1_memblk *blk;
+	int page, err, idx;
+
+	if (snd_BUG_ON(!emu))
+		return NULL;
+	if (snd_BUG_ON(runtime->dma_bytes <= 0 ||
+		       runtime->dma_bytes >= MAXPAGES * EMUPAGESIZE))
+		return NULL;
+	hdr = emu->memhdr;
+	if (snd_BUG_ON(!hdr))
+		return NULL;
+
+	idx = runtime->period_size >= runtime->buffer_size ?
+					(emu->delay_pcm_irq * 2) : 0;
+	mutex_lock(&hdr->block_mutex);
+	blk = search_empty(emu, runtime->dma_bytes + idx);
+	if (blk == NULL) {
+		mutex_unlock(&hdr->block_mutex);
+		return NULL;
+	}
+	/* fill buffer addresses but pointers are not stored so that
+	 * snd_free_pci_page() is not called in in synth_free()
+	 */
+	idx = 0;
+	for (page = blk->first_page; page <= blk->last_page; page++, idx++) {
+		unsigned long ofs = idx << PAGE_SHIFT;
+		dma_addr_t addr;
+		addr = snd_pcm_sgbuf_get_addr(substream, ofs);
+		if (! is_valid_page(emu, addr)) {
+			printk(KERN_ERR "emu: failure page = %d\n", idx);
+			mutex_unlock(&hdr->block_mutex);
+			return NULL;
+		}
+		emu->page_addr_table[page] = addr;
+		emu->page_ptr_table[page] = NULL;
+	}
+
+	/* set PTB entries */
+	blk->map_locked = 1; /* do not unmap this block! */
+	err = snd_emu10k1_memblk_map(emu, blk);
+	if (err < 0) {
+		__snd_util_mem_free(hdr, (struct snd_util_memblk *)blk);
+		mutex_unlock(&hdr->block_mutex);
+		return NULL;
+	}
+	mutex_unlock(&hdr->block_mutex);
+	return (struct snd_util_memblk *)blk;
+}
+
+
+/*
+ * release DMA buffer from page table
+ */
+int snd_emu10k1_free_pages(struct snd_emu10k1 *emu, struct snd_util_memblk *blk)
+{
+	if (snd_BUG_ON(!emu || !blk))
+		return -EINVAL;
+	return snd_emu10k1_synth_free(emu, blk);
+}
+
+
+/*
+ * memory allocation using multiple pages (for synth)
+ * Unlike the DMA allocation above, non-contiguous pages are assined.
+ */
+
+/*
+ * allocate a synth sample area
+ */
+struct snd_util_memblk *
+snd_emu10k1_synth_alloc(struct snd_emu10k1 *hw, unsigned int size)
+{
+	struct snd_emu10k1_memblk *blk;
+	struct snd_util_memhdr *hdr = hw->memhdr; 
+
+	mutex_lock(&hdr->block_mutex);
+	blk = (struct snd_emu10k1_memblk *)__snd_util_mem_alloc(hdr, size);
+	if (blk == NULL) {
+		mutex_unlock(&hdr->block_mutex);
+		return NULL;
+	}
+	if (synth_alloc_pages(hw, blk)) {
+		__snd_util_mem_free(hdr, (struct snd_util_memblk *)blk);
+		mutex_unlock(&hdr->block_mutex);
+		return NULL;
+	}
+	snd_emu10k1_memblk_map(hw, blk);
+	mutex_unlock(&hdr->block_mutex);
+	return (struct snd_util_memblk *)blk;
+}
+
+EXPORT_SYMBOL(snd_emu10k1_synth_alloc);
+
+/*
+ * free a synth sample area
+ */
+int
+snd_emu10k1_synth_free(struct snd_emu10k1 *emu, struct snd_util_memblk *memblk)
+{
+	struct snd_util_memhdr *hdr = emu->memhdr; 
+	struct snd_emu10k1_memblk *blk = (struct snd_emu10k1_memblk *)memblk;
+	unsigned long flags;
+
+	mutex_lock(&hdr->block_mutex);
+	spin_lock_irqsave(&emu->memblk_lock, flags);
+	if (blk->mapped_page >= 0)
+		unmap_memblk(emu, blk);
+	spin_unlock_irqrestore(&emu->memblk_lock, flags);
+	synth_free_pages(emu, blk);
+	 __snd_util_mem_free(hdr, memblk);
+	mutex_unlock(&hdr->block_mutex);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_emu10k1_synth_free);
+
+/* check new allocation range */
+static void get_single_page_range(struct snd_util_memhdr *hdr,
+				  struct snd_emu10k1_memblk *blk,
+				  int *first_page_ret, int *last_page_ret)
+{
+	struct list_head *p;
+	struct snd_emu10k1_memblk *q;
+	int first_page, last_page;
+	first_page = blk->first_page;
+	if ((p = blk->mem.list.prev) != &hdr->block) {
+		q = get_emu10k1_memblk(p, mem.list);
+		if (q->last_page == first_page)
+			first_page++;  /* first page was already allocated */
+	}
+	last_page = blk->last_page;
+	if ((p = blk->mem.list.next) != &hdr->block) {
+		q = get_emu10k1_memblk(p, mem.list);
+		if (q->first_page == last_page)
+			last_page--; /* last page was already allocated */
+	}
+	*first_page_ret = first_page;
+	*last_page_ret = last_page;
+}
+
+/* release allocated pages */
+static void __synth_free_pages(struct snd_emu10k1 *emu, int first_page,
+			       int last_page)
+{
+	int page;
+
+	for (page = first_page; page <= last_page; page++) {
+		free_page((unsigned long)emu->page_ptr_table[page]);
+		emu->page_addr_table[page] = 0;
+		emu->page_ptr_table[page] = NULL;
+	}
+}
+
+/*
+ * allocate kernel pages
+ */
+static int synth_alloc_pages(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
+{
+	int page, first_page, last_page;
+
+	emu10k1_memblk_init(blk);
+	get_single_page_range(emu->memhdr, blk, &first_page, &last_page);
+	/* allocate kernel pages */
+	for (page = first_page; page <= last_page; page++) {
+		/* first try to allocate from <4GB zone */
+		struct page *p = alloc_page(GFP_KERNEL | GFP_DMA32 |
+					    __GFP_NOWARN);
+		if (!p || (page_to_pfn(p) & ~(emu->dma_mask >> PAGE_SHIFT))) {
+			if (p)
+				__free_page(p);
+			/* try to allocate from <16MB zone */
+			p = alloc_page(GFP_ATOMIC | GFP_DMA |
+				       __GFP_NORETRY | /* no OOM-killer */
+				       __GFP_NOWARN);
+		}
+		if (!p) {
+			__synth_free_pages(emu, first_page, page - 1);
+			return -ENOMEM;
+		}
+		emu->page_addr_table[page] = page_to_phys(p);
+		emu->page_ptr_table[page] = page_address(p);
+	}
+	return 0;
+}
+
+/*
+ * free pages
+ */
+static int synth_free_pages(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk)
+{
+	int first_page, last_page;
+
+	get_single_page_range(emu->memhdr, blk, &first_page, &last_page);
+	__synth_free_pages(emu, first_page, last_page);
+	return 0;
+}
+
+/* calculate buffer pointer from offset address */
+static inline void *offset_ptr(struct snd_emu10k1 *emu, int page, int offset)
+{
+	char *ptr;
+	if (snd_BUG_ON(page < 0 || page >= emu->max_cache_pages))
+		return NULL;
+	ptr = emu->page_ptr_table[page];
+	if (! ptr) {
+		printk(KERN_ERR "emu10k1: access to NULL ptr: page = %d\n", page);
+		return NULL;
+	}
+	ptr += offset & (PAGE_SIZE - 1);
+	return (void*)ptr;
+}
+
+/*
+ * bzero(blk + offset, size)
+ */
+int snd_emu10k1_synth_bzero(struct snd_emu10k1 *emu, struct snd_util_memblk *blk,
+			    int offset, int size)
+{
+	int page, nextofs, end_offset, temp, temp1;
+	void *ptr;
+	struct snd_emu10k1_memblk *p = (struct snd_emu10k1_memblk *)blk;
+
+	offset += blk->offset & (PAGE_SIZE - 1);
+	end_offset = offset + size;
+	page = get_aligned_page(offset);
+	do {
+		nextofs = aligned_page_offset(page + 1);
+		temp = nextofs - offset;
+		temp1 = end_offset - offset;
+		if (temp1 < temp)
+			temp = temp1;
+		ptr = offset_ptr(emu, page + p->first_page, offset);
+		if (ptr)
+			memset(ptr, 0, temp);
+		offset = nextofs;
+		page++;
+	} while (offset < end_offset);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_emu10k1_synth_bzero);
+
+/*
+ * copy_from_user(blk + offset, data, size)
+ */
+int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_memblk *blk,
+				     int offset, const char __user *data, int size)
+{
+	int page, nextofs, end_offset, temp, temp1;
+	void *ptr;
+	struct snd_emu10k1_memblk *p = (struct snd_emu10k1_memblk *)blk;
+
+	offset += blk->offset & (PAGE_SIZE - 1);
+	end_offset = offset + size;
+	page = get_aligned_page(offset);
+	do {
+		nextofs = aligned_page_offset(page + 1);
+		temp = nextofs - offset;
+		temp1 = end_offset - offset;
+		if (temp1 < temp)
+			temp = temp1;
+		ptr = offset_ptr(emu, page + p->first_page, offset);
+		if (ptr && copy_from_user(ptr, data, temp))
+			return -EFAULT;
+		offset = nextofs;
+		data += temp;
+		page++;
+	} while (offset < end_offset);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_emu10k1_synth_copy_from_user);
diff --git a/linux/sound/pci/emu10k1/p16v.c b/linux/sound/pci/emu10k1/p16v.c
new file mode 100644
index 0000000..61b8ab3
--- /dev/null
+++ b/linux/sound/pci/emu10k1/p16v.c
@@ -0,0 +1,934 @@
+/*
+ *  Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ *  Driver p16v chips
+ *  Version: 0.25
+ *
+ *  FEATURES currently supported:
+ *    Output fixed at S32_LE, 2 channel to hw:0,0
+ *    Rates: 44.1, 48, 96, 192.
+ *
+ *  Changelog:
+ *  0.8
+ *    Use separate card based buffer for periods table.
+ *  0.9
+ *    Use 2 channel output streams instead of 8 channel.
+ *       (8 channel output streams might be good for ASIO type output)
+ *    Corrected speaker output, so Front -> Front etc.
+ *  0.10
+ *    Fixed missed interrupts.
+ *  0.11
+ *    Add Sound card model number and names.
+ *    Add Analog volume controls.
+ *  0.12
+ *    Corrected playback interrupts. Now interrupt per period, instead of half period.
+ *  0.13
+ *    Use single trigger for multichannel.
+ *  0.14
+ *    Mic capture now works at fixed: S32_LE, 96000Hz, Stereo.
+ *  0.15
+ *    Force buffer_size / period_size == INTEGER.
+ *  0.16
+ *    Update p16v.c to work with changed alsa api.
+ *  0.17
+ *    Update p16v.c to work with changed alsa api. Removed boot_devs.
+ *  0.18
+ *    Merging with snd-emu10k1 driver.
+ *  0.19
+ *    One stereo channel at 24bit now works.
+ *  0.20
+ *    Added better register defines.
+ *  0.21
+ *    Integrated with snd-emu10k1 driver.
+ *  0.22
+ *    Removed #if 0 ... #endif
+ *  0.23
+ *    Implement different capture rates.
+ *  0.24
+ *    Implement different capture source channels.
+ *    e.g. When HD Capture source is set to SPDIF,
+ *    setting HD Capture channel to 0 captures from CDROM digital input.
+ *    setting HD Capture channel to 1 captures from SPDIF in.
+ *  0.25
+ *    Include capture buffer sizes.
+ *
+ *  BUGS:
+ *    Some stability problems when unloading the snd-p16v kernel module.
+ *    --
+ *
+ *  TODO:
+ *    SPDIF out.
+ *    Find out how to change capture sample rates. E.g. To record SPDIF at 48000Hz.
+ *    Currently capture fixed at 48000Hz.
+ *
+ *    --
+ *  GENERAL INFO:
+ *    Model: SB0240
+ *    P16V Chip: CA0151-DBS
+ *    Audigy 2 Chip: CA0102-IAT
+ *    AC97 Codec: STAC 9721
+ *    ADC: Philips 1361T (Stereo 24bit)
+ *    DAC: CS4382-K (8-channel, 24bit, 192Khz)
+ *
+ *  This code was initally based on code from ALSA's emu10k1x.c which is:
+ *  Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+#include <sound/emu10k1.h>
+#include "p16v.h"
+
+#define SET_CHANNEL 0  /* Testing channel outputs 0=Front, 1=Center/LFE, 2=Unknown, 3=Rear */
+#define PCM_FRONT_CHANNEL 0
+#define PCM_REAR_CHANNEL 1
+#define PCM_CENTER_LFE_CHANNEL 2
+#define PCM_SIDE_CHANNEL 3
+#define CONTROL_FRONT_CHANNEL 0
+#define CONTROL_REAR_CHANNEL 3
+#define CONTROL_CENTER_LFE_CHANNEL 1
+#define CONTROL_SIDE_CHANNEL 2
+
+/* Card IDs:
+ * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2002 -> Audigy2 ZS 7.1 Model:SB0350
+ * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1007 -> Audigy2 6.1    Model:SB0240
+ * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1002 -> Audigy2 Platinum  Model:SB msb0240230009266
+ * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2007 -> Audigy4 Pro Model:SB0380 M1SB0380472001901E
+ *
+ */
+
+ /* hardware definition */
+static struct snd_pcm_hardware snd_p16v_playback_hw = {
+	.info =			SNDRV_PCM_INFO_MMAP | 
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				SNDRV_PCM_INFO_RESUME |
+				SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_SYNC_START,
+	.formats =		SNDRV_PCM_FMTBIT_S32_LE, /* Only supports 24-bit samples padded to 32 bits. */
+	.rates =		SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100, 
+	.rate_min =		44100,
+	.rate_max =		192000,
+	.channels_min =		8, 
+	.channels_max =		8,
+	.buffer_bytes_max =	((65536 - 64) * 8),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(65536 - 64),
+	.periods_min =		2,
+	.periods_max =		8,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_p16v_capture_hw = {
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_S32_LE,
+	.rates =		SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100, 
+	.rate_min =		44100,
+	.rate_max =		192000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(65536 - 64),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(65536 - 128) >> 1,  /* size has to be N*64 bytes */
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0,
+};
+
+static void snd_p16v_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+  
+	if (epcm) {
+        	/* snd_printk(KERN_DEBUG "epcm free: %p\n", epcm); */
+		kfree(epcm);
+	}
+}
+
+/* open_playback callback */
+static int snd_p16v_pcm_open_playback_channel(struct snd_pcm_substream *substream, int channel_id)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+        struct snd_emu10k1_voice *channel = &(emu->p16v_voices[channel_id]);
+	struct snd_emu10k1_pcm *epcm;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+        /* snd_printk(KERN_DEBUG "epcm kcalloc: %p\n", epcm); */
+
+	if (epcm == NULL)
+		return -ENOMEM;
+	epcm->emu = emu;
+	epcm->substream = substream;
+	/*
+	snd_printk(KERN_DEBUG "epcm device=%d, channel_id=%d\n",
+		   substream->pcm->device, channel_id);
+	*/
+	runtime->private_data = epcm;
+	runtime->private_free = snd_p16v_pcm_free_substream;
+  
+	runtime->hw = snd_p16v_playback_hw;
+
+        channel->emu = emu;
+        channel->number = channel_id;
+
+        channel->use=1;
+#if 0 /* debug */
+	snd_printk(KERN_DEBUG
+		   "p16v: open channel_id=%d, channel=%p, use=0x%x\n",
+		   channel_id, channel, channel->use);
+	printk(KERN_DEBUG "open:channel_id=%d, chip=%p, channel=%p\n",
+	       channel_id, chip, channel);
+#endif /* debug */
+	/* channel->interrupt = snd_p16v_pcm_channel_interrupt; */
+	channel->epcm = epcm;
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+                return err;
+
+	runtime->sync.id32[0] = substream->pcm->card->number;
+	runtime->sync.id32[1] = 'P';
+	runtime->sync.id32[2] = 16;
+	runtime->sync.id32[3] = 'V';
+
+	return 0;
+}
+/* open_capture callback */
+static int snd_p16v_pcm_open_capture_channel(struct snd_pcm_substream *substream, int channel_id)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_emu10k1_voice *channel = &(emu->p16v_capture_voice);
+	struct snd_emu10k1_pcm *epcm;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
+	/* snd_printk(KERN_DEBUG "epcm kcalloc: %p\n", epcm); */
+
+	if (epcm == NULL)
+		return -ENOMEM;
+	epcm->emu = emu;
+	epcm->substream = substream;
+	/*
+	snd_printk(KERN_DEBUG "epcm device=%d, channel_id=%d\n",
+		   substream->pcm->device, channel_id);
+	*/
+	runtime->private_data = epcm;
+	runtime->private_free = snd_p16v_pcm_free_substream;
+  
+	runtime->hw = snd_p16v_capture_hw;
+
+	channel->emu = emu;
+	channel->number = channel_id;
+
+	channel->use=1;
+#if 0 /* debug */
+	snd_printk(KERN_DEBUG
+		   "p16v: open channel_id=%d, channel=%p, use=0x%x\n",
+		   channel_id, channel, channel->use);
+	printk(KERN_DEBUG "open:channel_id=%d, chip=%p, channel=%p\n",
+	       channel_id, chip, channel);
+#endif /* debug */
+	/* channel->interrupt = snd_p16v_pcm_channel_interrupt; */
+	channel->epcm = epcm;
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+
+	return 0;
+}
+
+
+/* close callback */
+static int snd_p16v_pcm_close_playback(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	//struct snd_pcm_runtime *runtime = substream->runtime;
+	//struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	emu->p16v_voices[substream->pcm->device - emu->p16v_device_offset].use = 0;
+	/* FIXME: maybe zero others */
+	return 0;
+}
+
+/* close callback */
+static int snd_p16v_pcm_close_capture(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	//struct snd_pcm_runtime *runtime = substream->runtime;
+	//struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	emu->p16v_capture_voice.use = 0;
+	/* FIXME: maybe zero others */
+	return 0;
+}
+
+static int snd_p16v_pcm_open_playback_front(struct snd_pcm_substream *substream)
+{
+	return snd_p16v_pcm_open_playback_channel(substream, PCM_FRONT_CHANNEL);
+}
+
+static int snd_p16v_pcm_open_capture(struct snd_pcm_substream *substream)
+{
+	// Only using channel 0 for now, but the card has 2 channels.
+	return snd_p16v_pcm_open_capture_channel(substream, 0);
+}
+
+/* hw_params callback */
+static int snd_p16v_pcm_hw_params_playback(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *hw_params)
+{
+	int result;
+	result = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	return result;
+}
+
+/* hw_params callback */
+static int snd_p16v_pcm_hw_params_capture(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *hw_params)
+{
+	int result;
+	result = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	return result;
+}
+
+
+/* hw_free callback */
+static int snd_p16v_pcm_hw_free_playback(struct snd_pcm_substream *substream)
+{
+	int result;
+	result = snd_pcm_lib_free_pages(substream);
+	return result;
+}
+
+/* hw_free callback */
+static int snd_p16v_pcm_hw_free_capture(struct snd_pcm_substream *substream)
+{
+	int result;
+	result = snd_pcm_lib_free_pages(substream);
+	return result;
+}
+
+
+/* prepare playback callback */
+static int snd_p16v_pcm_prepare_playback(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int channel = substream->pcm->device - emu->p16v_device_offset;
+	u32 *table_base = (u32 *)(emu->p16v_buffer.area+(8*16*channel));
+	u32 period_size_bytes = frames_to_bytes(runtime, runtime->period_size);
+	int i;
+	u32 tmp;
+	
+#if 0 /* debug */
+	snd_printk(KERN_DEBUG "prepare:channel_number=%d, rate=%d, "
+		   "format=0x%x, channels=%d, buffer_size=%ld, "
+		   "period_size=%ld, periods=%u, frames_to_bytes=%d\n",
+		   channel, runtime->rate, runtime->format, runtime->channels,
+		   runtime->buffer_size, runtime->period_size,
+		   runtime->periods, frames_to_bytes(runtime, 1));
+	snd_printk(KERN_DEBUG "dma_addr=%x, dma_area=%p, table_base=%p\n",
+		   runtime->dma_addr, runtime->dma_area, table_base);
+	snd_printk(KERN_DEBUG "dma_addr=%x, dma_area=%p, dma_bytes(size)=%x\n",
+		   emu->p16v_buffer.addr, emu->p16v_buffer.area,
+		   emu->p16v_buffer.bytes);
+#endif /* debug */
+	tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, channel);
+        switch (runtime->rate) {
+	case 44100:
+	  snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x8080);
+	  break;
+	case 96000:
+	  snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x4040);
+	  break;
+	case 192000:
+	  snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x2020);
+	  break;
+	case 48000:
+	default:
+	  snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0xe0e0) | 0x0000);
+	  break;
+	}
+	/* FIXME: Check emu->buffer.size before actually writing to it. */
+	for(i = 0; i < runtime->periods; i++) {
+		table_base[i*2]=runtime->dma_addr+(i*period_size_bytes);
+		table_base[(i*2)+1]=period_size_bytes<<16;
+	}
+ 
+	snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_ADDR, channel, emu->p16v_buffer.addr+(8*16*channel));
+	snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_SIZE, channel, (runtime->periods - 1) << 19);
+	snd_emu10k1_ptr20_write(emu, PLAYBACK_LIST_PTR, channel, 0);
+	snd_emu10k1_ptr20_write(emu, PLAYBACK_DMA_ADDR, channel, runtime->dma_addr);
+	//snd_emu10k1_ptr20_write(emu, PLAYBACK_PERIOD_SIZE, channel, frames_to_bytes(runtime, runtime->period_size)<<16); // buffer size in bytes
+	snd_emu10k1_ptr20_write(emu, PLAYBACK_PERIOD_SIZE, channel, 0); // buffer size in bytes
+	snd_emu10k1_ptr20_write(emu, PLAYBACK_POINTER, channel, 0);
+	snd_emu10k1_ptr20_write(emu, 0x07, channel, 0x0);
+	snd_emu10k1_ptr20_write(emu, 0x08, channel, 0);
+
+	return 0;
+}
+
+/* prepare capture callback */
+static int snd_p16v_pcm_prepare_capture(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int channel = substream->pcm->device - emu->p16v_device_offset;
+	u32 tmp;
+
+	/*
+	printk(KERN_DEBUG "prepare capture:channel_number=%d, rate=%d, "
+	       "format=0x%x, channels=%d, buffer_size=%ld, period_size=%ld, "
+	       "frames_to_bytes=%d\n",
+	       channel, runtime->rate, runtime->format, runtime->channels,
+	       runtime->buffer_size, runtime->period_size,
+	       frames_to_bytes(runtime, 1));
+	*/
+	tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, channel);
+        switch (runtime->rate) {
+	case 44100:
+	  snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0800);
+	  break;
+	case 96000:
+	  snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0400);
+	  break;
+	case 192000:
+	  snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0200);
+	  break;
+	case 48000:
+	default:
+	  snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, channel, (tmp & ~0x0e00) | 0x0000);
+	  break;
+	}
+	/* FIXME: Check emu->buffer.size before actually writing to it. */
+	snd_emu10k1_ptr20_write(emu, 0x13, channel, 0);
+	snd_emu10k1_ptr20_write(emu, CAPTURE_DMA_ADDR, channel, runtime->dma_addr);
+	snd_emu10k1_ptr20_write(emu, CAPTURE_BUFFER_SIZE, channel, frames_to_bytes(runtime, runtime->buffer_size) << 16); // buffer size in bytes
+	snd_emu10k1_ptr20_write(emu, CAPTURE_POINTER, channel, 0);
+	//snd_emu10k1_ptr20_write(emu, CAPTURE_SOURCE, 0x0, 0x333300e4); /* Select MIC or Line in */
+	//snd_emu10k1_ptr20_write(emu, EXTENDED_INT_MASK, 0, snd_emu10k1_ptr20_read(emu, EXTENDED_INT_MASK, 0) | (0x110000<<channel));
+
+	return 0;
+}
+
+static void snd_p16v_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb)
+{
+	unsigned long flags;
+	unsigned int enable;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	enable = inl(emu->port + INTE2) | intrenb;
+	outl(enable, emu->port + INTE2);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+static void snd_p16v_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb)
+{
+	unsigned long flags;
+	unsigned int disable;
+
+	spin_lock_irqsave(&emu->emu_lock, flags);
+	disable = inl(emu->port + INTE2) & (~intrenb);
+	outl(disable, emu->port + INTE2);
+	spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+/* trigger_playback callback */
+static int snd_p16v_pcm_trigger_playback(struct snd_pcm_substream *substream,
+				    int cmd)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime;
+	struct snd_emu10k1_pcm *epcm;
+	int channel;
+	int result = 0;
+        struct snd_pcm_substream *s;
+	u32 basic = 0;
+	u32 inte = 0;
+	int running = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		running=1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	default:
+		running = 0;
+		break;
+	}
+        snd_pcm_group_for_each_entry(s, substream) {
+		if (snd_pcm_substream_chip(s) != emu ||
+		    s->stream != SNDRV_PCM_STREAM_PLAYBACK)
+			continue;
+		runtime = s->runtime;
+		epcm = runtime->private_data;
+		channel = substream->pcm->device-emu->p16v_device_offset;
+		/* snd_printk(KERN_DEBUG "p16v channel=%d\n", channel); */
+		epcm->running = running;
+		basic |= (0x1<<channel);
+		inte |= (INTE2_PLAYBACK_CH_0_LOOP<<channel);
+                snd_pcm_trigger_done(s, substream);
+        }
+	/* snd_printk(KERN_DEBUG "basic=0x%x, inte=0x%x\n", basic, inte); */
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_p16v_intr_enable(emu, inte);
+		snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0)| (basic));
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & ~(basic));
+		snd_p16v_intr_disable(emu, inte);
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	return result;
+}
+
+/* trigger_capture callback */
+static int snd_p16v_pcm_trigger_capture(struct snd_pcm_substream *substream,
+                                   int cmd)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	int channel = 0;
+	int result = 0;
+	u32 inte = INTE2_CAPTURE_CH_0_LOOP | INTE2_CAPTURE_CH_0_HALF_LOOP;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_p16v_intr_enable(emu, inte);
+		snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0)|(0x100<<channel));
+		epcm->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & ~(0x100<<channel));
+		snd_p16v_intr_disable(emu, inte);
+		//snd_emu10k1_ptr20_write(emu, EXTENDED_INT_MASK, 0, snd_emu10k1_ptr20_read(emu, EXTENDED_INT_MASK, 0) & ~(0x110000<<channel));
+		epcm->running = 0;
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	return result;
+}
+
+/* pointer_playback callback */
+static snd_pcm_uframes_t
+snd_p16v_pcm_pointer_playback(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	snd_pcm_uframes_t ptr, ptr1, ptr2,ptr3,ptr4 = 0;
+	int channel = substream->pcm->device - emu->p16v_device_offset;
+	if (!epcm->running)
+		return 0;
+
+	ptr3 = snd_emu10k1_ptr20_read(emu, PLAYBACK_LIST_PTR, channel);
+	ptr1 = snd_emu10k1_ptr20_read(emu, PLAYBACK_POINTER, channel);
+	ptr4 = snd_emu10k1_ptr20_read(emu, PLAYBACK_LIST_PTR, channel);
+	if (ptr3 != ptr4) ptr1 = snd_emu10k1_ptr20_read(emu, PLAYBACK_POINTER, channel);
+	ptr2 = bytes_to_frames(runtime, ptr1);
+	ptr2+= (ptr4 >> 3) * runtime->period_size;
+	ptr=ptr2;
+        if (ptr >= runtime->buffer_size)
+		ptr -= runtime->buffer_size;
+
+	return ptr;
+}
+
+/* pointer_capture callback */
+static snd_pcm_uframes_t
+snd_p16v_pcm_pointer_capture(struct snd_pcm_substream *substream)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	snd_pcm_uframes_t ptr, ptr1, ptr2 = 0;
+	int channel = 0;
+
+	if (!epcm->running)
+		return 0;
+
+	ptr1 = snd_emu10k1_ptr20_read(emu, CAPTURE_POINTER, channel);
+	ptr2 = bytes_to_frames(runtime, ptr1);
+	ptr=ptr2;
+	if (ptr >= runtime->buffer_size) {
+		ptr -= runtime->buffer_size;
+		printk(KERN_WARNING "buffer capture limited!\n");
+	}
+	/*
+	printk(KERN_DEBUG "ptr1 = 0x%lx, ptr2=0x%lx, ptr=0x%lx, "
+	       "buffer_size = 0x%x, period_size = 0x%x, bits=%d, rate=%d\n",
+	       ptr1, ptr2, ptr, (int)runtime->buffer_size,
+	       (int)runtime->period_size, (int)runtime->frame_bits,
+	       (int)runtime->rate);
+	*/
+	return ptr;
+}
+
+/* operators */
+static struct snd_pcm_ops snd_p16v_playback_front_ops = {
+	.open =        snd_p16v_pcm_open_playback_front,
+	.close =       snd_p16v_pcm_close_playback,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_p16v_pcm_hw_params_playback,
+	.hw_free =     snd_p16v_pcm_hw_free_playback,
+	.prepare =     snd_p16v_pcm_prepare_playback,
+	.trigger =     snd_p16v_pcm_trigger_playback,
+	.pointer =     snd_p16v_pcm_pointer_playback,
+};
+
+static struct snd_pcm_ops snd_p16v_capture_ops = {
+	.open =        snd_p16v_pcm_open_capture,
+	.close =       snd_p16v_pcm_close_capture,
+	.ioctl =       snd_pcm_lib_ioctl,
+	.hw_params =   snd_p16v_pcm_hw_params_capture,
+	.hw_free =     snd_p16v_pcm_hw_free_capture,
+	.prepare =     snd_p16v_pcm_prepare_capture,
+	.trigger =     snd_p16v_pcm_trigger_capture,
+	.pointer =     snd_p16v_pcm_pointer_capture,
+};
+
+
+int snd_p16v_free(struct snd_emu10k1 *chip)
+{
+	// release the data
+	if (chip->p16v_buffer.area) {
+		snd_dma_free_pages(&chip->p16v_buffer);
+		/*
+		snd_printk(KERN_DEBUG "period lables free: %p\n",
+			   &chip->p16v_buffer);
+		*/
+	}
+	return 0;
+}
+
+int __devinit snd_p16v_pcm(struct snd_emu10k1 *emu, int device, struct snd_pcm **rpcm)
+{
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+	int err;
+        int capture=1;
+  
+	/* snd_printk(KERN_DEBUG "snd_p16v_pcm called. device=%d\n", device); */
+	emu->p16v_device_offset = device;
+	if (rpcm)
+		*rpcm = NULL;
+
+	if ((err = snd_pcm_new(emu->card, "p16v", device, 1, capture, &pcm)) < 0)
+		return err;
+  
+	pcm->private_data = emu;
+	// Single playback 8 channel device.
+	// Single capture 2 channel device.
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_p16v_playback_front_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_p16v_capture_ops);
+
+	pcm->info_flags = 0;
+	pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+	strcpy(pcm->name, "p16v");
+	emu->pcm_p16v = pcm;
+
+	for(substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; 
+	    substream; 
+	    substream = substream->next) {
+		if ((err = snd_pcm_lib_preallocate_pages(substream, 
+							 SNDRV_DMA_TYPE_DEV, 
+							 snd_dma_pci_data(emu->pci), 
+							 ((65536 - 64) * 8), ((65536 - 64) * 8))) < 0) 
+			return err;
+		/*
+		snd_printk(KERN_DEBUG
+			   "preallocate playback substream: err=%d\n", err);
+		*/
+	}
+
+	for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; 
+	      substream; 
+	      substream = substream->next) {
+ 		if ((err = snd_pcm_lib_preallocate_pages(substream, 
+	                                           SNDRV_DMA_TYPE_DEV, 
+	                                           snd_dma_pci_data(emu->pci), 
+	                                           65536 - 64, 65536 - 64)) < 0)
+			return err;
+		/*
+		snd_printk(KERN_DEBUG
+			   "preallocate capture substream: err=%d\n", err);
+		*/
+	}
+  
+	if (rpcm)
+		*rpcm = pcm;
+  
+	return 0;
+}
+
+static int snd_p16v_volume_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+        uinfo->count = 2;
+        uinfo->value.integer.min = 0;
+        uinfo->value.integer.max = 255;
+        return 0;
+}
+
+static int snd_p16v_volume_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+        struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	int high_low = (kcontrol->private_value >> 8) & 0xff;
+	int reg = kcontrol->private_value & 0xff;
+	u32 value;
+
+	value = snd_emu10k1_ptr20_read(emu, reg, high_low);
+	if (high_low) {
+		ucontrol->value.integer.value[0] = 0xff - ((value >> 24) & 0xff); /* Left */
+		ucontrol->value.integer.value[1] = 0xff - ((value >> 16) & 0xff); /* Right */
+	} else {
+		ucontrol->value.integer.value[0] = 0xff - ((value >> 8) & 0xff); /* Left */
+		ucontrol->value.integer.value[1] = 0xff - ((value >> 0) & 0xff); /* Right */
+	}
+	return 0;
+}
+
+static int snd_p16v_volume_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+        struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	int high_low = (kcontrol->private_value >> 8) & 0xff;
+	int reg = kcontrol->private_value & 0xff;
+        u32 value, oval;
+
+	oval = value = snd_emu10k1_ptr20_read(emu, reg, 0);
+	if (high_low == 1) {
+		value &= 0xffff;
+		value |= ((0xff - ucontrol->value.integer.value[0]) << 24) |
+			((0xff - ucontrol->value.integer.value[1]) << 16);
+	} else {
+		value &= 0xffff0000;
+		value |= ((0xff - ucontrol->value.integer.value[0]) << 8) |
+			((0xff - ucontrol->value.integer.value[1]) );
+	}
+	if (value != oval) {
+		snd_emu10k1_ptr20_write(emu, reg, 0, value);
+		return 1;
+	}
+	return 0;
+}
+
+static int snd_p16v_capture_source_info(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[8] = {
+		"SPDIF", "I2S", "SRC48", "SRCMulti_SPDIF", "SRCMulti_I2S",
+		"CDIF", "FX", "AC97"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 8;
+	if (uinfo->value.enumerated.item > 7)
+                uinfo->value.enumerated.item = 7;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_p16v_capture_source_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = emu->p16v_capture_source;
+	return 0;
+}
+
+static int snd_p16v_capture_source_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change = 0;
+	u32 mask;
+	u32 source;
+
+	val = ucontrol->value.enumerated.item[0] ;
+	if (val > 7)
+		return -EINVAL;
+	change = (emu->p16v_capture_source != val);
+	if (change) {
+		emu->p16v_capture_source = val;
+		source = (val << 28) | (val << 24) | (val << 20) | (val << 16);
+		mask = snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & 0xffff;
+		snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, source | mask);
+	}
+        return change;
+}
+
+static int snd_p16v_capture_channel_info(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = { "0", "1", "2", "3",  };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3)
+                uinfo->value.enumerated.item = 3;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_p16v_capture_channel_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = emu->p16v_capture_channel;
+	return 0;
+}
+
+static int snd_p16v_capture_channel_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change = 0;
+	u32 tmp;
+
+	val = ucontrol->value.enumerated.item[0] ;
+	if (val > 3)
+		return -EINVAL;
+	change = (emu->p16v_capture_channel != val);
+	if (change) {
+		emu->p16v_capture_channel = val;
+		tmp = snd_emu10k1_ptr20_read(emu, CAPTURE_P16V_SOURCE, 0) & 0xfffc;
+		snd_emu10k1_ptr20_write(emu, CAPTURE_P16V_SOURCE, 0, tmp | val);
+	}
+        return change;
+}
+static const DECLARE_TLV_DB_SCALE(snd_p16v_db_scale1, -5175, 25, 1);
+
+#define P16V_VOL(xname,xreg,xhl) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+        .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |             \
+                  SNDRV_CTL_ELEM_ACCESS_TLV_READ,               \
+	.info = snd_p16v_volume_info, \
+	.get = snd_p16v_volume_get, \
+	.put = snd_p16v_volume_put, \
+	.tlv = { .p = snd_p16v_db_scale1 },	\
+	.private_value = ((xreg) | ((xhl) << 8)) \
+}
+
+static struct snd_kcontrol_new p16v_mixer_controls[] __devinitdata = {
+	P16V_VOL("HD Analog Front Playback Volume", PLAYBACK_VOLUME_MIXER9, 0),
+	P16V_VOL("HD Analog Rear Playback Volume", PLAYBACK_VOLUME_MIXER10, 1),
+	P16V_VOL("HD Analog Center/LFE Playback Volume", PLAYBACK_VOLUME_MIXER9, 1),
+	P16V_VOL("HD Analog Side Playback Volume", PLAYBACK_VOLUME_MIXER10, 0),
+	P16V_VOL("HD SPDIF Front Playback Volume", PLAYBACK_VOLUME_MIXER7, 0),
+	P16V_VOL("HD SPDIF Rear Playback Volume", PLAYBACK_VOLUME_MIXER8, 1),
+	P16V_VOL("HD SPDIF Center/LFE Playback Volume", PLAYBACK_VOLUME_MIXER7, 1),
+	P16V_VOL("HD SPDIF Side Playback Volume", PLAYBACK_VOLUME_MIXER8, 0),
+	{
+		.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =		"HD source Capture",
+		.info =		snd_p16v_capture_source_info,
+		.get =		snd_p16v_capture_source_get,
+		.put =		snd_p16v_capture_source_put
+	},
+	{
+		.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =		"HD channel Capture",
+		.info =		snd_p16v_capture_channel_info,
+		.get =		snd_p16v_capture_channel_get,
+		.put =		snd_p16v_capture_channel_put
+	},
+};
+
+
+int __devinit snd_p16v_mixer(struct snd_emu10k1 *emu)
+{
+	int i, err;
+        struct snd_card *card = emu->card;
+
+	for (i = 0; i < ARRAY_SIZE(p16v_mixer_controls); i++) {
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&p16v_mixer_controls[i],
+							  emu))) < 0)
+			return err;
+	}
+        return 0;
+}
+
+#ifdef CONFIG_PM
+
+#define NUM_CHS	1	/* up to 4, but only first channel is used */
+
+int __devinit snd_p16v_alloc_pm_buffer(struct snd_emu10k1 *emu)
+{
+	emu->p16v_saved = vmalloc(NUM_CHS * 4 * 0x80);
+	if (! emu->p16v_saved)
+		return -ENOMEM;
+	return 0;
+}
+
+void snd_p16v_free_pm_buffer(struct snd_emu10k1 *emu)
+{
+	vfree(emu->p16v_saved);
+}
+
+void snd_p16v_suspend(struct snd_emu10k1 *emu)
+{
+	int i, ch;
+	unsigned int *val;
+
+	val = emu->p16v_saved;
+	for (ch = 0; ch < NUM_CHS; ch++)
+		for (i = 0; i < 0x80; i++, val++)
+			*val = snd_emu10k1_ptr20_read(emu, i, ch);
+}
+
+void snd_p16v_resume(struct snd_emu10k1 *emu)
+{
+	int i, ch;
+	unsigned int *val;
+
+	val = emu->p16v_saved;
+	for (ch = 0; ch < NUM_CHS; ch++)
+		for (i = 0; i < 0x80; i++, val++)
+			snd_emu10k1_ptr20_write(emu, i, ch, *val);
+}
+#endif
diff --git a/linux/sound/pci/emu10k1/p16v.h b/linux/sound/pci/emu10k1/p16v.h
new file mode 100644
index 0000000..1532149
--- /dev/null
+++ b/linux/sound/pci/emu10k1/p16v.h
@@ -0,0 +1,299 @@
+/*
+ *  Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ *  Driver p16v chips
+ *  Version: 0.21
+ *
+ *  FEATURES currently supported:
+ *    Output fixed at S32_LE, 2 channel to hw:0,0
+ *    Rates: 44.1, 48, 96, 192.
+ *
+ *  Changelog:
+ *  0.8
+ *    Use separate card based buffer for periods table.
+ *  0.9
+ *    Use 2 channel output streams instead of 8 channel.
+ *       (8 channel output streams might be good for ASIO type output)
+ *    Corrected speaker output, so Front -> Front etc.
+ *  0.10
+ *    Fixed missed interrupts.
+ *  0.11
+ *    Add Sound card model number and names.
+ *    Add Analog volume controls.
+ *  0.12
+ *    Corrected playback interrupts. Now interrupt per period, instead of half period.
+ *  0.13
+ *    Use single trigger for multichannel.
+ *  0.14
+ *    Mic capture now works at fixed: S32_LE, 96000Hz, Stereo.
+ *  0.15
+ *    Force buffer_size / period_size == INTEGER.
+ *  0.16
+ *    Update p16v.c to work with changed alsa api.
+ *  0.17
+ *    Update p16v.c to work with changed alsa api. Removed boot_devs.
+ *  0.18
+ *    Merging with snd-emu10k1 driver.
+ *  0.19
+ *    One stereo channel at 24bit now works.
+ *  0.20
+ *    Added better register defines.
+ *  0.21
+ *    Split from p16v.c
+ *
+ *
+ *  BUGS:
+ *    Some stability problems when unloading the snd-p16v kernel module.
+ *    --
+ *
+ *  TODO:
+ *    SPDIF out.
+ *    Find out how to change capture sample rates. E.g. To record SPDIF at 48000Hz.
+ *    Currently capture fixed at 48000Hz.
+ *
+ *    --
+ *  GENERAL INFO:
+ *    Model: SB0240
+ *    P16V Chip: CA0151-DBS
+ *    Audigy 2 Chip: CA0102-IAT
+ *    AC97 Codec: STAC 9721
+ *    ADC: Philips 1361T (Stereo 24bit)
+ *    DAC: CS4382-K (8-channel, 24bit, 192Khz)
+ *
+ *  This code was initally based on code from ALSA's emu10k1x.c which is:
+ *  Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
+ *
+ *   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
+ *
+ */
+
+/********************************************************************************************************/
+/* Audigy2 P16V pointer-offset register set, accessed through the PTR2 and DATA2 registers                     */
+/********************************************************************************************************/
+                                                                                                                           
+/* The sample rate of the SPDIF outputs is set by modifying a register in the EMU10K2 PTR register A_SPDIF_SAMPLERATE.
+ * The sample rate is also controlled by the same registers that control the rate of the EMU10K2 sample rate converters.
+ */
+
+/* Initally all registers from 0x00 to 0x3f have zero contents. */
+#define PLAYBACK_LIST_ADDR	0x00		/* Base DMA address of a list of pointers to each period/size */
+						/* One list entry: 4 bytes for DMA address, 
+						 * 4 bytes for period_size << 16.
+						 * One list entry is 8 bytes long.
+						 * One list entry for each period in the buffer.
+						 */
+#define PLAYBACK_LIST_SIZE	0x01		/* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000  */
+#define PLAYBACK_LIST_PTR	0x02		/* Pointer to the current period being played */
+#define PLAYBACK_UNKNOWN3	0x03		/* Not used */
+#define PLAYBACK_DMA_ADDR	0x04		/* Playback DMA addresss */
+#define PLAYBACK_PERIOD_SIZE	0x05		/* Playback period size. win2000 uses 0x04000000 */
+#define PLAYBACK_POINTER	0x06		/* Playback period pointer. Used with PLAYBACK_LIST_PTR to determine buffer position currently in DAC */
+#define PLAYBACK_FIFO_END_ADDRESS	0x07		/* Playback FIFO end address */
+#define PLAYBACK_FIFO_POINTER	0x08		/* Playback FIFO pointer and number of valid sound samples in cache */
+#define PLAYBACK_UNKNOWN9	0x09		/* Not used */
+#define CAPTURE_DMA_ADDR	0x10		/* Capture DMA address */
+#define CAPTURE_BUFFER_SIZE	0x11		/* Capture buffer size */
+#define CAPTURE_POINTER		0x12		/* Capture buffer pointer. Sample currently in ADC */
+#define CAPTURE_FIFO_POINTER	0x13		/* Capture FIFO pointer and number of valid sound samples in cache */
+#define CAPTURE_P16V_VOLUME1	0x14		/* Low: Capture volume 0xXXXX3030 */
+#define CAPTURE_P16V_VOLUME2	0x15		/* High:Has no effect on capture volume */
+#define CAPTURE_P16V_SOURCE     0x16            /* P16V source select. Set to 0x0700E4E5 for AC97 CAPTURE */
+						/* [0:1] Capture input 0 channel select. 0 = Capture output 0.
+						 *                                       1 = Capture output 1.
+						 *                                       2 = Capture output 2.
+						 *                                       3 = Capture output 3.
+						 * [3:2] Capture input 1 channel select. 0 = Capture output 0.
+						 *                                       1 = Capture output 1.
+						 *                                       2 = Capture output 2.
+						 *                                       3 = Capture output 3.
+						 * [5:4] Capture input 2 channel select. 0 = Capture output 0.
+						 *                                       1 = Capture output 1.
+						 *                                       2 = Capture output 2.
+						 *                                       3 = Capture output 3.
+						 * [7:6] Capture input 3 channel select. 0 = Capture output 0.
+						 *                                       1 = Capture output 1.
+						 *                                       2 = Capture output 2.
+						 *                                       3 = Capture output 3.
+						 * [9:8] Playback input 0 channel select. 0 = Play output 0.
+						 *                                        1 = Play output 1.
+						 *                                        2 = Play output 2.
+						 *                                        3 = Play output 3.
+						 * [11:10] Playback input 1 channel select. 0 = Play output 0.
+						 *                                          1 = Play output 1.
+						 *                                          2 = Play output 2.
+						 *                                          3 = Play output 3.
+						 * [13:12] Playback input 2 channel select. 0 = Play output 0.
+						 *                                          1 = Play output 1.
+						 *                                          2 = Play output 2.
+						 *                                          3 = Play output 3.
+						 * [15:14] Playback input 3 channel select. 0 = Play output 0.
+						 *                                          1 = Play output 1.
+						 *                                          2 = Play output 2.
+						 *                                          3 = Play output 3.
+						 * [19:16] Playback mixer output enable. 1 bit per channel.
+						 * [23:20] Capture mixer output enable. 1 bit per channel.
+						 * [26:24] FX engine channel capture 0 = 0x60-0x67.
+						 *                                   1 = 0x68-0x6f.
+						 *                                   2 = 0x70-0x77.
+						 *                                   3 = 0x78-0x7f.
+						 *                                   4 = 0x80-0x87.
+						 *                                   5 = 0x88-0x8f.
+						 *                                   6 = 0x90-0x97.
+						 *                                   7 = 0x98-0x9f.
+						 * [31:27] Not used.
+						 */
+
+						/* 0x1 = capture on.
+						 * 0x100 = capture off.
+						 * 0x200 = capture off.
+						 * 0x1000 = capture off.
+						 */
+#define CAPTURE_RATE_STATUS		0x17		/* Capture sample rate. Read only */
+						/* [15:0] Not used.
+						 * [18:16] Channel 0 Detected sample rate. 0 - 44.1khz
+						 *                               1 - 48 khz
+						 *                               2 - 96 khz
+						 *                               3 - 192 khz
+						 *                               7 - undefined rate.
+						 * [19] Channel 0. 1 - Valid, 0 - Not Valid.
+						 * [22:20] Channel 1 Detected sample rate. 
+						 * [23] Channel 1. 1 - Valid, 0 - Not Valid.
+						 * [26:24] Channel 2 Detected sample rate. 
+						 * [27] Channel 2. 1 - Valid, 0 - Not Valid.
+						 * [30:28] Channel 3 Detected sample rate. 
+						 * [31] Channel 3. 1 - Valid, 0 - Not Valid.
+						 */
+/* 0x18 - 0x1f unused */
+#define PLAYBACK_LAST_SAMPLE    0x20		/* The sample currently being played. Read only */
+/* 0x21 - 0x3f unused */
+#define BASIC_INTERRUPT         0x40		/* Used by both playback and capture interrupt handler */
+						/* Playback (0x1<<channel_id) Don't touch high 16bits. */
+						/* Capture  (0x100<<channel_id). not tested */
+						/* Start Playback [3:0] (one bit per channel)
+						 * Start Capture [11:8] (one bit per channel)
+						 * Record source select for channel 0 [18:16]
+						 * Record source select for channel 1 [22:20]
+						 * Record source select for channel 2 [26:24]
+						 * Record source select for channel 3 [30:28]
+						 * 0 - SPDIF channel.
+						 * 1 - I2S channel.
+						 * 2 - SRC48 channel.
+						 * 3 - SRCMulti_SPDIF channel.
+						 * 4 - SRCMulti_I2S channel.
+						 * 5 - SPDIF channel.
+						 * 6 - fxengine capture.
+						 * 7 - AC97 capture.
+						 */
+						/* Default 41110000.
+						 * Writing 0xffffffff hangs the PC.
+						 * Writing 0xffff0000 -> 77770000 so it must be some sort of route.
+						 * bit 0x1 starts DMA playback on channel_id 0
+						 */
+/* 0x41,42 take values from 0 - 0xffffffff, but have no effect on playback */
+/* 0x43,0x48 do not remember settings */
+/* 0x41-45 unused */
+#define WATERMARK            0x46		/* Test bit to indicate cache level usage */
+						/* Values it can have while playing on channel 0. 
+						 * 0000f000, 0000f004, 0000f008, 0000f00c.
+						 * Readonly.
+						 */
+/* 0x47-0x4f unused */
+/* 0x50-0x5f Capture cache data */
+#define SRCSel			0x60            /* SRCSel. Default 0x4. Bypass P16V 0x14 */
+						/* [0] 0 = 10K2 audio, 1 = SRC48 mixer output.
+						 * [2] 0 = 10K2 audio, 1 = SRCMulti SPDIF mixer output.
+						 * [4] 0 = 10K2 audio, 1 = SRCMulti I2S mixer output.
+						 */
+						/* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */
+						/* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */
+						/* SRC48 and SRCMULTI sample rate select and output select. */
+						/* 0xffffffff -> 0xC0000015
+						 * 0xXXXXXXX4 = Enable Front Left/Right
+						 * Enable PCMs
+						 */
+
+/* 0x61 -> 0x6c are Volume controls */
+#define PLAYBACK_VOLUME_MIXER1  0x61		/* SRC48 Low to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER2  0x62		/* SRC48 High to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER3  0x63		/* SRCMULTI SPDIF Low to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER4  0x64		/* SRCMULTI SPDIF High to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER5  0x65		/* SRCMULTI I2S Low to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER6  0x66		/* SRCMULTI I2S High to mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER7  0x67		/* P16V Low to SRCMULTI SPDIF mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER8  0x68		/* P16V High to SRCMULTI SPDIF mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER9  0x69		/* P16V Low to SRCMULTI I2S mixer input volume control. */
+						/* 0xXXXX3030 = PCM0 Volume (Front).
+						 * 0x3030XXXX = PCM1 Volume (Center)
+						 */
+#define PLAYBACK_VOLUME_MIXER10  0x6a		/* P16V High to SRCMULTI I2S mixer input volume control. */
+						/* 0x3030XXXX = PCM3 Volume (Rear). */
+#define PLAYBACK_VOLUME_MIXER11  0x6b		/* E10K2 Low to SRC48 mixer input volume control. */
+#define PLAYBACK_VOLUME_MIXER12 0x6c		/* E10K2 High to SRC48 mixer input volume control. */
+
+#define SRC48_ENABLE            0x6d            /* SRC48 input audio enable */
+						/* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */
+						/* [23:16] The corresponding P16V channel to SRC48 enabled if == 1.
+						 * [31:24] The corresponding E10K2 channel to SRC48 enabled.
+						 */
+#define SRCMULTI_ENABLE         0x6e            /* SRCMulti input audio enable. Default 0xffffffff */
+						/* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */
+						/* [7:0] The corresponding P16V channel to SRCMulti_I2S enabled if == 1.
+						 * [15:8] The corresponding E10K2 channel to SRCMulti I2S enabled.
+						 * [23:16] The corresponding P16V channel to SRCMulti SPDIF enabled.
+						 * [31:24] The corresponding E10K2 channel to SRCMulti SPDIF enabled.
+						 */
+						/* Bypass P16V 0xff00ff00 
+						 * Bitmap. 0 = Off, 1 = On.
+						 * P16V playback outputs:
+						 * 0xXXXXXXX1 = PCM0 Left. (Front)
+						 * 0xXXXXXXX2 = PCM0 Right.
+						 * 0xXXXXXXX4 = PCM1 Left. (Center/LFE)
+						 * 0xXXXXXXX8 = PCM1 Right.
+						 * 0xXXXXXX1X = PCM2 Left. (Unknown)
+						 * 0xXXXXXX2X = PCM2 Right.
+						 * 0xXXXXXX4X = PCM3 Left. (Rear)
+						 * 0xXXXXXX8X = PCM3 Right.
+						 */
+#define AUDIO_OUT_ENABLE        0x6f            /* Default: 000100FF */
+						/* [3:0] Does something, but not documented. Probably capture enable.
+						 * [7:4] Playback channels enable. not documented.
+						 * [16] AC97 output enable if == 1
+						 * [30] 0 = SRCMulti_I2S input from fxengine 0x68-0x6f.
+						 *      1 = SRCMulti_I2S input from SRC48 output.
+						 * [31] 0 = SRCMulti_SPDIF input from fxengine 0x60-0x67.
+						 *      1 = SRCMulti_SPDIF input from SRC48 output.
+						 */
+						/* 0xffffffff -> C00100FF */
+						/* 0 -> Not playback sound, irq still running */
+						/* 0xXXXXXX10 = PCM0 Left/Right On. (Front)
+						 * 0xXXXXXX20 = PCM1 Left/Right On. (Center/LFE)
+						 * 0xXXXXXX40 = PCM2 Left/Right On. (Unknown)
+						 * 0xXXXXXX80 = PCM3 Left/Right On. (Rear)
+						 */
+#define PLAYBACK_SPDIF_SELECT     0x70          /* Default: 12030F00 */
+						/* 0xffffffff -> 3FF30FFF */
+						/* 0x00000001 pauses stream/irq fail. */
+						/* All other bits do not effect playback */
+#define PLAYBACK_SPDIF_SRC_SELECT 0x71          /* Default: 0000E4E4 */
+						/* 0xffffffff -> F33FFFFF */
+						/* All bits do not effect playback */
+#define PLAYBACK_SPDIF_USER_DATA0 0x72		/* SPDIF out user data 0 */
+#define PLAYBACK_SPDIF_USER_DATA1 0x73		/* SPDIF out user data 1 */
+/* 0x74-0x75 unknown */
+#define CAPTURE_SPDIF_CONTROL	0x76		/* SPDIF in control setting */
+#define CAPTURE_SPDIF_STATUS	0x77		/* SPDIF in status */
+#define CAPURE_SPDIF_USER_DATA0 0x78		/* SPDIF in user data 0 */
+#define CAPURE_SPDIF_USER_DATA1 0x79		/* SPDIF in user data 1 */
+#define CAPURE_SPDIF_USER_DATA2 0x7a		/* SPDIF in user data 2 */
+
diff --git a/linux/sound/pci/emu10k1/p17v.h b/linux/sound/pci/emu10k1/p17v.h
new file mode 100644
index 0000000..4ef5f68
--- /dev/null
+++ b/linux/sound/pci/emu10k1/p17v.h
@@ -0,0 +1,158 @@
+/*
+ *  Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ *  Driver p17v chips
+ *  Version: 0.01
+ *
+ *   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
+ *
+ */
+
+/******************************************************************************/
+/* Audigy2Value Tina (P17V) pointer-offset register set,
+ * accessed through the PTR20 and DATA24 registers  */
+/******************************************************************************/
+
+/* 00 - 07: Not used */
+#define P17V_PLAYBACK_FIFO_PTR	0x08	/* Current playback fifo pointer
+					 * and number of sound samples in cache.
+					 */  
+/* 09 - 12: Not used */
+#define P17V_CAPTURE_FIFO_PTR	0x13	/* Current capture fifo pointer
+					 * and number of sound samples in cache.
+					 */  
+/* 14 - 17: Not used */
+#define P17V_PB_CHN_SEL		0x18	/* P17v playback channel select */
+#define P17V_SE_SLOT_SEL_L	0x19	/* Sound Engine slot select low */
+#define P17V_SE_SLOT_SEL_H	0x1a	/* Sound Engine slot select high */
+/* 1b - 1f: Not used */
+/* 20 - 2f: Not used */
+/* 30 - 3b: Not used */
+#define P17V_SPI		0x3c	/* SPI interface register */
+#define P17V_I2C_ADDR		0x3d	/* I2C Address */
+#define P17V_I2C_0		0x3e	/* I2C Data */
+#define P17V_I2C_1		0x3f	/* I2C Data */
+/* I2C values */
+#define I2C_A_ADC_ADD_MASK	0x000000fe	/*The address is a 7 bit address */
+#define I2C_A_ADC_RW_MASK	0x00000001	/*bit mask for R/W */
+#define I2C_A_ADC_TRANS_MASK	0x00000010  	/*Bit mask for I2c address DAC value  */
+#define I2C_A_ADC_ABORT_MASK	0x00000020	/*Bit mask for I2C transaction abort flag */
+#define I2C_A_ADC_LAST_MASK	0x00000040	/*Bit mask for Last word transaction */
+#define I2C_A_ADC_BYTE_MASK	0x00000080	/*Bit mask for Byte Mode */
+
+#define I2C_A_ADC_ADD		0x00000034	/*This is the Device address for ADC  */
+#define I2C_A_ADC_READ		0x00000001	/*To perform a read operation */
+#define I2C_A_ADC_START		0x00000100	/*Start I2C transaction */
+#define I2C_A_ADC_ABORT		0x00000200	/*I2C transaction abort */
+#define I2C_A_ADC_LAST		0x00000400	/*I2C last transaction */
+#define I2C_A_ADC_BYTE		0x00000800	/*I2C one byte mode */
+
+#define I2C_D_ADC_REG_MASK	0xfe000000  	/*ADC address register */ 
+#define I2C_D_ADC_DAT_MASK	0x01ff0000  	/*ADC data register */
+
+#define ADC_TIMEOUT		0x00000007	/*ADC Timeout Clock Disable */
+#define ADC_IFC_CTRL		0x0000000b	/*ADC Interface Control */
+#define ADC_MASTER		0x0000000c	/*ADC Master Mode Control */
+#define ADC_POWER		0x0000000d	/*ADC PowerDown Control */
+#define ADC_ATTEN_ADCL		0x0000000e	/*ADC Attenuation ADCL */
+#define ADC_ATTEN_ADCR		0x0000000f	/*ADC Attenuation ADCR */
+#define ADC_ALC_CTRL1		0x00000010	/*ADC ALC Control 1 */
+#define ADC_ALC_CTRL2		0x00000011	/*ADC ALC Control 2 */
+#define ADC_ALC_CTRL3		0x00000012	/*ADC ALC Control 3 */
+#define ADC_NOISE_CTRL		0x00000013	/*ADC Noise Gate Control */
+#define ADC_LIMIT_CTRL		0x00000014	/*ADC Limiter Control */
+#define ADC_MUX			0x00000015  	/*ADC Mux offset */
+#if 0
+/* FIXME: Not tested yet. */
+#define ADC_GAIN_MASK		0x000000ff	//Mask for ADC Gain
+#define ADC_ZERODB		0x000000cf	//Value to set ADC to 0dB
+#define ADC_MUTE_MASK		0x000000c0	//Mask for ADC mute
+#define ADC_MUTE		0x000000c0	//Value to mute ADC
+#define ADC_OSR			0x00000008	//Mask for ADC oversample rate select
+#define ADC_TIMEOUT_DISABLE	0x00000008	//Value and mask to disable Timeout clock
+#define ADC_HPF_DISABLE		0x00000100	//Value and mask to disable High pass filter
+#define ADC_TRANWIN_MASK	0x00000070	//Mask for Length of Transient Window
+#endif
+
+#define ADC_MUX_MASK		0x0000000f	//Mask for ADC Mux
+#define ADC_MUX_0		0x00000001	//Value to select Unknown at ADC Mux (Not used)
+#define ADC_MUX_1		0x00000002	//Value to select Unknown at ADC Mux (Not used)
+#define ADC_MUX_2		0x00000004	//Value to select Mic at ADC Mux
+#define ADC_MUX_3		0x00000008	//Value to select Line-In at ADC Mux
+
+#define P17V_START_AUDIO	0x40	/* Start Audio bit */
+/* 41 - 47: Reserved */
+#define P17V_START_CAPTURE	0x48	/* Start Capture bit */
+#define P17V_CAPTURE_FIFO_BASE	0x49	/* Record FIFO base address */
+#define P17V_CAPTURE_FIFO_SIZE	0x4a	/* Record FIFO buffer size */
+#define P17V_CAPTURE_FIFO_INDEX	0x4b	/* Record FIFO capture index */
+#define P17V_CAPTURE_VOL_H	0x4c	/* P17v capture volume control */
+#define P17V_CAPTURE_VOL_L	0x4d	/* P17v capture volume control */
+/* 4e - 4f: Not used */
+/* 50 - 5f: Not used */
+#define P17V_SRCSel		0x60	/* SRC48 and SRCMulti sample rate select
+					 * and output select
+					 */
+#define P17V_MIXER_AC97_10K1_VOL_L	0x61	/* 10K to Mixer_AC97 input volume control */
+#define P17V_MIXER_AC97_10K1_VOL_H	0x62	/* 10K to Mixer_AC97 input volume control */
+#define P17V_MIXER_AC97_P17V_VOL_L	0x63	/* P17V to Mixer_AC97 input volume control */
+#define P17V_MIXER_AC97_P17V_VOL_H	0x64	/* P17V to Mixer_AC97 input volume control */
+#define P17V_MIXER_AC97_SRP_REC_VOL_L	0x65	/* SRP Record to Mixer_AC97 input volume control */
+#define P17V_MIXER_AC97_SRP_REC_VOL_H	0x66	/* SRP Record to Mixer_AC97 input volume control */
+/* 67 - 68: Reserved */
+#define P17V_MIXER_Spdif_10K1_VOL_L	0x69	/* 10K to Mixer_Spdif input volume control */
+#define P17V_MIXER_Spdif_10K1_VOL_H	0x6A	/* 10K to Mixer_Spdif input volume control */
+#define P17V_MIXER_Spdif_P17V_VOL_L	0x6B	/* P17V to Mixer_Spdif input volume control */
+#define P17V_MIXER_Spdif_P17V_VOL_H	0x6C	/* P17V to Mixer_Spdif input volume control */
+#define P17V_MIXER_Spdif_SRP_REC_VOL_L	0x6D	/* SRP Record to Mixer_Spdif input volume control */
+#define P17V_MIXER_Spdif_SRP_REC_VOL_H	0x6E	/* SRP Record to Mixer_Spdif input volume control */
+/* 6f - 70: Reserved */
+#define P17V_MIXER_I2S_10K1_VOL_L	0x71	/* 10K to Mixer_I2S input volume control */
+#define P17V_MIXER_I2S_10K1_VOL_H	0x72	/* 10K to Mixer_I2S input volume control */
+#define P17V_MIXER_I2S_P17V_VOL_L	0x73	/* P17V to Mixer_I2S input volume control */
+#define P17V_MIXER_I2S_P17V_VOL_H	0x74	/* P17V to Mixer_I2S input volume control */
+#define P17V_MIXER_I2S_SRP_REC_VOL_L	0x75	/* SRP Record to Mixer_I2S input volume control */
+#define P17V_MIXER_I2S_SRP_REC_VOL_H	0x76	/* SRP Record to Mixer_I2S input volume control */
+/* 77 - 78: Reserved */
+#define P17V_MIXER_AC97_ENABLE		0x79	/* Mixer AC97 input audio enable */
+#define P17V_MIXER_SPDIF_ENABLE		0x7A	/* Mixer SPDIF input audio enable */
+#define P17V_MIXER_I2S_ENABLE		0x7B	/* Mixer I2S input audio enable */
+#define P17V_AUDIO_OUT_ENABLE		0x7C	/* Audio out enable */
+#define P17V_MIXER_ATT			0x7D	/* SRP Mixer Attenuation Select */
+#define P17V_SRP_RECORD_SRR		0x7E	/* SRP Record channel source Select */
+#define P17V_SOFT_RESET_SRP_MIXER	0x7F	/* SRP and mixer soft reset */
+
+#define P17V_AC97_OUT_MASTER_VOL_L	0x80	/* AC97 Output master volume control */
+#define P17V_AC97_OUT_MASTER_VOL_H	0x81	/* AC97 Output master volume control */
+#define P17V_SPDIF_OUT_MASTER_VOL_L	0x82	/* SPDIF Output master volume control */
+#define P17V_SPDIF_OUT_MASTER_VOL_H	0x83	/* SPDIF Output master volume control */
+#define P17V_I2S_OUT_MASTER_VOL_L	0x84	/* I2S Output master volume control */
+#define P17V_I2S_OUT_MASTER_VOL_H	0x85	/* I2S Output master volume control */
+/* 86 - 87: Not used */
+#define P17V_I2S_CHANNEL_SWAP_PHASE_INVERSE	0x88	/* I2S out mono channel swap
+							 * and phase inverse */
+#define P17V_SPDIF_CHANNEL_SWAP_PHASE_INVERSE	0x89	/* SPDIF out mono channel swap
+							 * and phase inverse */
+/* 8A: Not used */
+#define P17V_SRP_P17V_ESR		0x8B	/* SRP_P17V estimated sample rate and rate lock */
+#define P17V_SRP_REC_ESR		0x8C	/* SRP_REC estimated sample rate and rate lock */
+#define P17V_SRP_BYPASS			0x8D	/* srps channel bypass and srps bypass */
+/* 8E - 92: Not used */
+#define P17V_I2S_SRC_SEL		0x93	/* I2SIN mode sel */
+
+
+
+
+
+
diff --git a/linux/sound/pci/emu10k1/timer.c b/linux/sound/pci/emu10k1/timer.c
new file mode 100644
index 0000000..72321e9
--- /dev/null
+++ b/linux/sound/pci/emu10k1/timer.c
@@ -0,0 +1,96 @@
+/*
+ *  Copyright (c) by Lee Revell <rlrevell@joe-job.com>
+ *                   Clemens Ladisch <clemens@ladisch.de>
+ *  Routines for control of EMU10K1 chips
+ *
+ *  BUGS:
+ *    --
+ *
+ *  TODO:
+ *    --
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+static int snd_emu10k1_timer_start(struct snd_timer *timer)
+{
+	struct snd_emu10k1 *emu;
+	unsigned long flags;
+	unsigned int delay;
+
+	emu = snd_timer_chip(timer);
+	delay = timer->sticks - 1;
+	if (delay < 5 ) /* minimum time is 5 ticks */
+		delay = 5;
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	snd_emu10k1_intr_enable(emu, INTE_INTERVALTIMERENB);
+	outw(delay & TIMER_RATE_MASK, emu->port + TIMER);
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return 0;
+}
+
+static int snd_emu10k1_timer_stop(struct snd_timer *timer)
+{
+	struct snd_emu10k1 *emu;
+	unsigned long flags;
+
+	emu = snd_timer_chip(timer);
+	spin_lock_irqsave(&emu->reg_lock, flags);
+	snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB);
+	spin_unlock_irqrestore(&emu->reg_lock, flags);
+	return 0;
+}
+
+static int snd_emu10k1_timer_precise_resolution(struct snd_timer *timer,
+					       unsigned long *num, unsigned long *den)
+{
+	*num = 1;
+	*den = 48000;
+	return 0;
+}
+
+static struct snd_timer_hardware snd_emu10k1_timer_hw = {
+	.flags = SNDRV_TIMER_HW_AUTO,
+	.resolution = 20833, /* 1 sample @ 48KHZ = 20.833...us */
+	.ticks = 1024,
+	.start = snd_emu10k1_timer_start,
+	.stop = snd_emu10k1_timer_stop,
+	.precise_resolution = snd_emu10k1_timer_precise_resolution,
+};
+
+int __devinit snd_emu10k1_timer(struct snd_emu10k1 *emu, int device)
+{
+	struct snd_timer *timer = NULL;
+	struct snd_timer_id tid;
+	int err;
+
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = emu->card->number;
+	tid.device = device;
+	tid.subdevice = 0;
+	if ((err = snd_timer_new(emu->card, "EMU10K1", &tid, &timer)) >= 0) {
+		strcpy(timer->name, "EMU10K1 timer");
+		timer->private_data = emu;
+		timer->hw = snd_emu10k1_timer_hw;
+	}
+	emu->timer = timer;
+	return err;
+}
diff --git a/linux/sound/pci/emu10k1/tina2.h b/linux/sound/pci/emu10k1/tina2.h
new file mode 100644
index 0000000..f2d8eb6
--- /dev/null
+++ b/linux/sound/pci/emu10k1/tina2.h
@@ -0,0 +1,32 @@
+/*
+ *  Copyright (c) by James Courtier-Dutton <James@superbug.demon.co.uk>
+ *  Driver tina2 chips
+ *  Version: 0.1
+ *
+ *   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
+ *
+ */
+
+/********************************************************************************************************/
+/* Audigy2 Tina2 (notebook) pointer-offset register set, accessed through the PTR2 and DATA2 registers  */
+/********************************************************************************************************/
+
+#define TINA2_VOLUME	0x71	/* Attenuate playback volume to prevent distortion. */
+				/* The windows driver does not use this register,
+				 * so it must use some other attenuation method.
+				 * Without this, the output is 12dB too loud,
+				 * resulting in distortion.
+				 */
+
diff --git a/linux/sound/pci/emu10k1/voice.c b/linux/sound/pci/emu10k1/voice.c
new file mode 100644
index 0000000..20b8da2
--- /dev/null
+++ b/linux/sound/pci/emu10k1/voice.c
@@ -0,0 +1,167 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *                   Creative Labs, Inc.
+ *                   Lee Revell <rlrevell@joe-job.com>
+ *  Routines for control of EMU10K1 chips - voice manager
+ *
+ *  Rewrote voice allocator for multichannel support - rlrevell 12/2004
+ * 
+ *  BUGS:
+ *    --
+ *
+ *  TODO:
+ *    --
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/time.h>
+#include <sound/core.h>
+#include <sound/emu10k1.h>
+
+/* Previously the voice allocator started at 0 every time.  The new voice 
+ * allocator uses a round robin scheme.  The next free voice is tracked in 
+ * the card record and each allocation begins where the last left off.  The 
+ * hardware requires stereo interleaved voices be aligned to an even/odd 
+ * boundary.  For multichannel voice allocation we ensure than the block of 
+ * voices does not cross the 32 voice boundary.  This simplifies the 
+ * multichannel support and ensures we can use a single write to the 
+ * (set|clear)_loop_stop registers.  Otherwise (for example) the voices would 
+ * get out of sync when pausing/resuming a stream.
+ *							--rlrevell
+ */
+
+static int voice_alloc(struct snd_emu10k1 *emu, int type, int number,
+		       struct snd_emu10k1_voice **rvoice)
+{
+	struct snd_emu10k1_voice *voice;
+	int i, j, k, first_voice, last_voice, skip;
+
+	*rvoice = NULL;
+	first_voice = last_voice = 0;
+	for (i = emu->next_free_voice, j = 0; j < NUM_G ; i += number, j += number) {
+		/*
+		printk(KERN_DEBUG "i %d j %d next free %d!\n",
+		       i, j, emu->next_free_voice);
+		*/
+		i %= NUM_G;
+
+		/* stereo voices must be even/odd */
+		if ((number == 2) && (i % 2)) {
+			i++;
+			continue;
+		}
+			
+		skip = 0;
+		for (k = 0; k < number; k++) {
+			voice = &emu->voices[(i+k) % NUM_G];
+			if (voice->use) {
+				skip = 1;
+				break;
+			}
+		}
+		if (!skip) {
+			/* printk(KERN_DEBUG "allocated voice %d\n", i); */
+			first_voice = i;
+			last_voice = (i + number) % NUM_G;
+			emu->next_free_voice = last_voice;
+			break;
+		}
+	}
+	
+	if (first_voice == last_voice)
+		return -ENOMEM;
+	
+	for (i = 0; i < number; i++) {
+		voice = &emu->voices[(first_voice + i) % NUM_G];
+		/*
+		printk(kERN_DEBUG "voice alloc - %i, %i of %i\n",
+		       voice->number, idx-first_voice+1, number);
+		*/
+		voice->use = 1;
+		switch (type) {
+		case EMU10K1_PCM:
+			voice->pcm = 1;
+			break;
+		case EMU10K1_SYNTH:
+			voice->synth = 1;
+			break;
+		case EMU10K1_MIDI:
+			voice->midi = 1;
+			break;
+		case EMU10K1_EFX:
+			voice->efx = 1;
+			break;
+		}
+	}
+	*rvoice = &emu->voices[first_voice];
+	return 0;
+}
+
+int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number,
+			    struct snd_emu10k1_voice **rvoice)
+{
+	unsigned long flags;
+	int result;
+
+	if (snd_BUG_ON(!rvoice))
+		return -EINVAL;
+	if (snd_BUG_ON(!number))
+		return -EINVAL;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (;;) {
+		result = voice_alloc(emu, type, number, rvoice);
+		if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI)
+			break;
+
+		/* free a voice from synth */
+		if (emu->get_synth_voice) {
+			result = emu->get_synth_voice(emu);
+			if (result >= 0) {
+				struct snd_emu10k1_voice *pvoice = &emu->voices[result];
+				pvoice->interrupt = NULL;
+				pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
+				pvoice->epcm = NULL;
+			}
+		}
+		if (result < 0)
+			break;
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+
+	return result;
+}
+
+EXPORT_SYMBOL(snd_emu10k1_voice_alloc);
+
+int snd_emu10k1_voice_free(struct snd_emu10k1 *emu,
+			   struct snd_emu10k1_voice *pvoice)
+{
+	unsigned long flags;
+
+	if (snd_BUG_ON(!pvoice))
+		return -EINVAL;
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	pvoice->interrupt = NULL;
+	pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
+	pvoice->epcm = NULL;
+	snd_emu10k1_voice_init(emu, pvoice->number);
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_emu10k1_voice_free);
diff --git a/linux/sound/pci/ens1370.c b/linux/sound/pci/ens1370.c
new file mode 100644
index 0000000..537cfba
--- /dev/null
+++ b/linux/sound/pci/ens1370.c
@@ -0,0 +1,2498 @@
+/*
+ *  Driver for Ensoniq ES1370/ES1371 AudioPCI soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ *		     Thomas Sailer <sailer@ife.ee.ethz.ch>
+ *
+ *   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
+ *
+ */
+
+/* Power-Management-Code ( CONFIG_PM )
+ * for ens1371 only ( FIXME )
+ * derived from cs4281.c, atiixp.c and via82xx.c
+ * using http://www.alsa-project.org/~tiwai/writing-an-alsa-driver/ 
+ * by Kurt J. Bosch
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#ifdef CHIP1371
+#include <sound/ac97_codec.h>
+#else
+#include <sound/ak4531_codec.h>
+#endif
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+
+#ifndef CHIP1371
+#undef CHIP1370
+#define CHIP1370
+#endif
+
+#ifdef CHIP1370
+#define DRIVER_NAME "ENS1370"
+#else
+#define DRIVER_NAME "ENS1371"
+#endif
+
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Thomas Sailer <sailer@ife.ee.ethz.ch>");
+MODULE_LICENSE("GPL");
+#ifdef CHIP1370
+MODULE_DESCRIPTION("Ensoniq AudioPCI ES1370");
+MODULE_SUPPORTED_DEVICE("{{Ensoniq,AudioPCI-97 ES1370},"
+	        "{Creative Labs,SB PCI64/128 (ES1370)}}");
+#endif
+#ifdef CHIP1371
+MODULE_DESCRIPTION("Ensoniq/Creative AudioPCI ES1371+");
+MODULE_SUPPORTED_DEVICE("{{Ensoniq,AudioPCI ES1371/73},"
+		"{Ensoniq,AudioPCI ES1373},"
+		"{Creative Labs,Ectiva EV1938},"
+		"{Creative Labs,SB PCI64/128 (ES1371/73)},"
+		"{Creative Labs,Vibra PCI128},"
+		"{Ectiva,EV1938}}");
+#endif
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+#define SUPPORT_JOYSTICK
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable switches */
+#ifdef SUPPORT_JOYSTICK
+#ifdef CHIP1371
+static int joystick_port[SNDRV_CARDS];
+#else
+static int joystick[SNDRV_CARDS];
+#endif
+#endif
+#ifdef CHIP1371
+static int spdif[SNDRV_CARDS];
+static int lineio[SNDRV_CARDS];
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Ensoniq AudioPCI soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Ensoniq AudioPCI soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Ensoniq AudioPCI soundcard.");
+#ifdef SUPPORT_JOYSTICK
+#ifdef CHIP1371
+module_param_array(joystick_port, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_port, "Joystick port address.");
+#else
+module_param_array(joystick, bool, NULL, 0444);
+MODULE_PARM_DESC(joystick, "Enable joystick.");
+#endif
+#endif /* SUPPORT_JOYSTICK */
+#ifdef CHIP1371
+module_param_array(spdif, int, NULL, 0444);
+MODULE_PARM_DESC(spdif, "S/PDIF output (-1 = none, 0 = auto, 1 = force).");
+module_param_array(lineio, int, NULL, 0444);
+MODULE_PARM_DESC(lineio, "Line In to Rear Out (0 = auto, 1 = force).");
+#endif
+
+/* ES1371 chip ID */
+/* This is a little confusing because all ES1371 compatible chips have the
+   same DEVICE_ID, the only thing differentiating them is the REV_ID field.
+   This is only significant if you want to enable features on the later parts.
+   Yes, I know it's stupid and why didn't we use the sub IDs?
+*/
+#define ES1371REV_ES1373_A  0x04
+#define ES1371REV_ES1373_B  0x06
+#define ES1371REV_CT5880_A  0x07
+#define CT5880REV_CT5880_C  0x02
+#define CT5880REV_CT5880_D  0x03	/* ??? -jk */
+#define CT5880REV_CT5880_E  0x04	/* mw */
+#define ES1371REV_ES1371_B  0x09
+#define EV1938REV_EV1938_A  0x00
+#define ES1371REV_ES1373_8  0x08
+
+/*
+ * Direct registers
+ */
+
+#define ES_REG(ensoniq, x) ((ensoniq)->port + ES_REG_##x)
+
+#define ES_REG_CONTROL	0x00	/* R/W: Interrupt/Chip select control register */
+#define   ES_1370_ADC_STOP	(1<<31)		/* disable capture buffer transfers */
+#define   ES_1370_XCTL1 	(1<<30)		/* general purpose output bit */
+#define   ES_1373_BYPASS_P1	(1<<31)		/* bypass SRC for PB1 */
+#define   ES_1373_BYPASS_P2	(1<<30)		/* bypass SRC for PB2 */
+#define   ES_1373_BYPASS_R	(1<<29)		/* bypass SRC for REC */
+#define   ES_1373_TEST_BIT	(1<<28)		/* should be set to 0 for normal operation */
+#define   ES_1373_RECEN_B	(1<<27)		/* mix record with playback for I2S/SPDIF out */
+#define   ES_1373_SPDIF_THRU	(1<<26)		/* 0 = SPDIF thru mode, 1 = SPDIF == dig out */
+#define   ES_1371_JOY_ASEL(o)	(((o)&0x03)<<24)/* joystick port mapping */
+#define   ES_1371_JOY_ASELM	(0x03<<24)	/* mask for above */
+#define   ES_1371_JOY_ASELI(i)  (((i)>>24)&0x03)
+#define   ES_1371_GPIO_IN(i)	(((i)>>20)&0x0f)/* GPIO in [3:0] pins - R/O */
+#define   ES_1370_PCLKDIVO(o)	(((o)&0x1fff)<<16)/* clock divide ratio for DAC2 */
+#define   ES_1370_PCLKDIVM	((0x1fff)<<16)	/* mask for above */
+#define   ES_1370_PCLKDIVI(i)	(((i)>>16)&0x1fff)/* clock divide ratio for DAC2 */
+#define   ES_1371_GPIO_OUT(o)	(((o)&0x0f)<<16)/* GPIO out [3:0] pins - W/R */
+#define   ES_1371_GPIO_OUTM     (0x0f<<16)	/* mask for above */
+#define   ES_MSFMTSEL		(1<<15)		/* MPEG serial data format; 0 = SONY, 1 = I2S */
+#define   ES_1370_M_SBB		(1<<14)		/* clock source for DAC - 0 = clock generator; 1 = MPEG clocks */
+#define   ES_1371_SYNC_RES	(1<<14)		/* Warm AC97 reset */
+#define   ES_1370_WTSRSEL(o)	(((o)&0x03)<<12)/* fixed frequency clock for DAC1 */
+#define   ES_1370_WTSRSELM	(0x03<<12)	/* mask for above */
+#define   ES_1371_ADC_STOP	(1<<13)		/* disable CCB transfer capture information */
+#define   ES_1371_PWR_INTRM	(1<<12)		/* power level change interrupts enable */
+#define   ES_1370_DAC_SYNC	(1<<11)		/* DAC's are synchronous */
+#define   ES_1371_M_CB		(1<<11)		/* capture clock source; 0 = AC'97 ADC; 1 = I2S */
+#define   ES_CCB_INTRM		(1<<10)		/* CCB voice interrupts enable */
+#define   ES_1370_M_CB		(1<<9)		/* capture clock source; 0 = ADC; 1 = MPEG */
+#define   ES_1370_XCTL0		(1<<8)		/* generap purpose output bit */
+#define   ES_1371_PDLEV(o)	(((o)&0x03)<<8)	/* current power down level */
+#define   ES_1371_PDLEVM	(0x03<<8)	/* mask for above */
+#define   ES_BREQ		(1<<7)		/* memory bus request enable */
+#define   ES_DAC1_EN		(1<<6)		/* DAC1 playback channel enable */
+#define   ES_DAC2_EN		(1<<5)		/* DAC2 playback channel enable */
+#define   ES_ADC_EN		(1<<4)		/* ADC capture channel enable */
+#define   ES_UART_EN		(1<<3)		/* UART enable */
+#define   ES_JYSTK_EN		(1<<2)		/* Joystick module enable */
+#define   ES_1370_CDC_EN	(1<<1)		/* Codec interface enable */
+#define   ES_1371_XTALCKDIS	(1<<1)		/* Xtal clock disable */
+#define   ES_1370_SERR_DISABLE	(1<<0)		/* PCI serr signal disable */
+#define   ES_1371_PCICLKDIS     (1<<0)		/* PCI clock disable */
+#define ES_REG_STATUS	0x04	/* R/O: Interrupt/Chip select status register */
+#define   ES_INTR               (1<<31)		/* Interrupt is pending */
+#define   ES_1371_ST_AC97_RST	(1<<29)		/* CT5880 AC'97 Reset bit */
+#define   ES_1373_REAR_BIT27	(1<<27)		/* rear bits: 000 - front, 010 - mirror, 101 - separate */
+#define   ES_1373_REAR_BIT26	(1<<26)
+#define   ES_1373_REAR_BIT24	(1<<24)
+#define   ES_1373_GPIO_INT_EN(o)(((o)&0x0f)<<20)/* GPIO [3:0] pins - interrupt enable */
+#define   ES_1373_SPDIF_EN	(1<<18)		/* SPDIF enable */
+#define   ES_1373_SPDIF_TEST	(1<<17)		/* SPDIF test */
+#define   ES_1371_TEST          (1<<16)		/* test ASIC */
+#define   ES_1373_GPIO_INT(i)	(((i)&0x0f)>>12)/* GPIO [3:0] pins - interrupt pending */
+#define   ES_1370_CSTAT		(1<<10)		/* CODEC is busy or register write in progress */
+#define   ES_1370_CBUSY         (1<<9)		/* CODEC is busy */
+#define   ES_1370_CWRIP		(1<<8)		/* CODEC register write in progress */
+#define   ES_1371_SYNC_ERR	(1<<8)		/* CODEC synchronization error occurred */
+#define   ES_1371_VC(i)         (((i)>>6)&0x03)	/* voice code from CCB module */
+#define   ES_1370_VC(i)		(((i)>>5)&0x03)	/* voice code from CCB module */
+#define   ES_1371_MPWR          (1<<5)		/* power level interrupt pending */
+#define   ES_MCCB		(1<<4)		/* CCB interrupt pending */
+#define   ES_UART		(1<<3)		/* UART interrupt pending */
+#define   ES_DAC1		(1<<2)		/* DAC1 channel interrupt pending */
+#define   ES_DAC2		(1<<1)		/* DAC2 channel interrupt pending */
+#define   ES_ADC		(1<<0)		/* ADC channel interrupt pending */
+#define ES_REG_UART_DATA 0x08	/* R/W: UART data register */
+#define ES_REG_UART_STATUS 0x09	/* R/O: UART status register */
+#define   ES_RXINT		(1<<7)		/* RX interrupt occurred */
+#define   ES_TXINT		(1<<2)		/* TX interrupt occurred */
+#define   ES_TXRDY		(1<<1)		/* transmitter ready */
+#define   ES_RXRDY		(1<<0)		/* receiver ready */
+#define ES_REG_UART_CONTROL 0x09	/* W/O: UART control register */
+#define   ES_RXINTEN		(1<<7)		/* RX interrupt enable */
+#define   ES_TXINTENO(o)	(((o)&0x03)<<5)	/* TX interrupt enable */
+#define   ES_TXINTENM		(0x03<<5)	/* mask for above */
+#define   ES_TXINTENI(i)	(((i)>>5)&0x03)
+#define   ES_CNTRL(o)		(((o)&0x03)<<0)	/* control */
+#define   ES_CNTRLM		(0x03<<0)	/* mask for above */
+#define ES_REG_UART_RES	0x0a	/* R/W: UART reserver register */
+#define   ES_TEST_MODE		(1<<0)		/* test mode enabled */
+#define ES_REG_MEM_PAGE	0x0c	/* R/W: Memory page register */
+#define   ES_MEM_PAGEO(o)	(((o)&0x0f)<<0)	/* memory page select - out */
+#define   ES_MEM_PAGEM		(0x0f<<0)	/* mask for above */
+#define   ES_MEM_PAGEI(i)	(((i)>>0)&0x0f) /* memory page select - in */
+#define ES_REG_1370_CODEC 0x10	/* W/O: Codec write register address */
+#define   ES_1370_CODEC_WRITE(a,d) ((((a)&0xff)<<8)|(((d)&0xff)<<0))
+#define ES_REG_1371_CODEC 0x14	/* W/R: Codec Read/Write register address */
+#define   ES_1371_CODEC_RDY	   (1<<31)	/* codec ready */
+#define   ES_1371_CODEC_WIP	   (1<<30)	/* codec register access in progress */
+#define   ES_1371_CODEC_PIRD	   (1<<23)	/* codec read/write select register */
+#define   ES_1371_CODEC_WRITE(a,d) ((((a)&0x7f)<<16)|(((d)&0xffff)<<0))
+#define   ES_1371_CODEC_READS(a)   ((((a)&0x7f)<<16)|ES_1371_CODEC_PIRD)
+#define   ES_1371_CODEC_READ(i)    (((i)>>0)&0xffff)
+
+#define ES_REG_1371_SMPRATE 0x10	/* W/R: Codec rate converter interface register */
+#define   ES_1371_SRC_RAM_ADDRO(o) (((o)&0x7f)<<25)/* address of the sample rate converter */
+#define   ES_1371_SRC_RAM_ADDRM	   (0x7f<<25)	/* mask for above */
+#define   ES_1371_SRC_RAM_ADDRI(i) (((i)>>25)&0x7f)/* address of the sample rate converter */
+#define   ES_1371_SRC_RAM_WE	   (1<<24)	/* R/W: read/write control for sample rate converter */
+#define   ES_1371_SRC_RAM_BUSY     (1<<23)	/* R/O: sample rate memory is busy */
+#define   ES_1371_SRC_DISABLE      (1<<22)	/* sample rate converter disable */
+#define   ES_1371_DIS_P1	   (1<<21)	/* playback channel 1 accumulator update disable */
+#define   ES_1371_DIS_P2	   (1<<20)	/* playback channel 1 accumulator update disable */
+#define   ES_1371_DIS_R1	   (1<<19)	/* capture channel accumulator update disable */
+#define   ES_1371_SRC_RAM_DATAO(o) (((o)&0xffff)<<0)/* current value of the sample rate converter */
+#define   ES_1371_SRC_RAM_DATAM	   (0xffff<<0)	/* mask for above */
+#define   ES_1371_SRC_RAM_DATAI(i) (((i)>>0)&0xffff)/* current value of the sample rate converter */
+
+#define ES_REG_1371_LEGACY 0x18	/* W/R: Legacy control/status register */
+#define   ES_1371_JFAST		(1<<31)		/* fast joystick timing */
+#define   ES_1371_HIB		(1<<30)		/* host interrupt blocking enable */
+#define   ES_1371_VSB		(1<<29)		/* SB; 0 = addr 0x220xH, 1 = 0x22FxH */
+#define   ES_1371_VMPUO(o)	(((o)&0x03)<<27)/* base register address; 0 = 0x320xH; 1 = 0x330xH; 2 = 0x340xH; 3 = 0x350xH */
+#define   ES_1371_VMPUM		(0x03<<27)	/* mask for above */
+#define   ES_1371_VMPUI(i)	(((i)>>27)&0x03)/* base register address */
+#define   ES_1371_VCDCO(o)	(((o)&0x03)<<25)/* CODEC; 0 = 0x530xH; 1 = undefined; 2 = 0xe80xH; 3 = 0xF40xH */
+#define   ES_1371_VCDCM		(0x03<<25)	/* mask for above */
+#define   ES_1371_VCDCI(i)	(((i)>>25)&0x03)/* CODEC address */
+#define   ES_1371_FIRQ		(1<<24)		/* force an interrupt */
+#define   ES_1371_SDMACAP	(1<<23)		/* enable event capture for slave DMA controller */
+#define   ES_1371_SPICAP	(1<<22)		/* enable event capture for slave IRQ controller */
+#define   ES_1371_MDMACAP	(1<<21)		/* enable event capture for master DMA controller */
+#define   ES_1371_MPICAP	(1<<20)		/* enable event capture for master IRQ controller */
+#define   ES_1371_ADCAP		(1<<19)		/* enable event capture for ADLIB register; 0x388xH */
+#define   ES_1371_SVCAP		(1<<18)		/* enable event capture for SB registers */
+#define   ES_1371_CDCCAP	(1<<17)		/* enable event capture for CODEC registers */
+#define   ES_1371_BACAP		(1<<16)		/* enable event capture for SoundScape base address */
+#define   ES_1371_EXI(i)	(((i)>>8)&0x07)	/* event number */
+#define   ES_1371_AI(i)		(((i)>>3)&0x1f)	/* event significant I/O address */
+#define   ES_1371_WR		(1<<2)	/* event capture; 0 = read; 1 = write */
+#define   ES_1371_LEGINT	(1<<0)	/* interrupt for legacy events; 0 = interrupt did occur */
+
+#define ES_REG_CHANNEL_STATUS 0x1c /* R/W: first 32-bits from S/PDIF channel status block, es1373 */
+
+#define ES_REG_SERIAL	0x20	/* R/W: Serial interface control register */
+#define   ES_1371_DAC_TEST	(1<<22)		/* DAC test mode enable */
+#define   ES_P2_END_INCO(o)	(((o)&0x07)<<19)/* binary offset value to increment / loop end */
+#define   ES_P2_END_INCM	(0x07<<19)	/* mask for above */
+#define   ES_P2_END_INCI(i)	(((i)>>16)&0x07)/* binary offset value to increment / loop end */
+#define   ES_P2_ST_INCO(o)	(((o)&0x07)<<16)/* binary offset value to increment / start */
+#define   ES_P2_ST_INCM		(0x07<<16)	/* mask for above */
+#define   ES_P2_ST_INCI(i)	(((i)<<16)&0x07)/* binary offset value to increment / start */
+#define   ES_R1_LOOP_SEL	(1<<15)		/* ADC; 0 - loop mode; 1 = stop mode */
+#define   ES_P2_LOOP_SEL	(1<<14)		/* DAC2; 0 - loop mode; 1 = stop mode */
+#define   ES_P1_LOOP_SEL	(1<<13)		/* DAC1; 0 - loop mode; 1 = stop mode */
+#define   ES_P2_PAUSE		(1<<12)		/* DAC2; 0 - play mode; 1 = pause mode */
+#define   ES_P1_PAUSE		(1<<11)		/* DAC1; 0 - play mode; 1 = pause mode */
+#define   ES_R1_INT_EN		(1<<10)		/* ADC interrupt enable */
+#define   ES_P2_INT_EN		(1<<9)		/* DAC2 interrupt enable */
+#define   ES_P1_INT_EN		(1<<8)		/* DAC1 interrupt enable */
+#define   ES_P1_SCT_RLD		(1<<7)		/* force sample counter reload for DAC1 */
+#define   ES_P2_DAC_SEN		(1<<6)		/* when stop mode: 0 - DAC2 play back zeros; 1 = DAC2 play back last sample */
+#define   ES_R1_MODEO(o)	(((o)&0x03)<<4)	/* ADC mode; 0 = 8-bit mono; 1 = 8-bit stereo; 2 = 16-bit mono; 3 = 16-bit stereo */
+#define   ES_R1_MODEM		(0x03<<4)	/* mask for above */
+#define   ES_R1_MODEI(i)	(((i)>>4)&0x03)
+#define   ES_P2_MODEO(o)	(((o)&0x03)<<2)	/* DAC2 mode; -- '' -- */
+#define   ES_P2_MODEM		(0x03<<2)	/* mask for above */
+#define   ES_P2_MODEI(i)	(((i)>>2)&0x03)
+#define   ES_P1_MODEO(o)	(((o)&0x03)<<0)	/* DAC1 mode; -- '' -- */
+#define   ES_P1_MODEM		(0x03<<0)	/* mask for above */
+#define   ES_P1_MODEI(i)	(((i)>>0)&0x03)
+
+#define ES_REG_DAC1_COUNT 0x24	/* R/W: DAC1 sample count register */
+#define ES_REG_DAC2_COUNT 0x28	/* R/W: DAC2 sample count register */
+#define ES_REG_ADC_COUNT  0x2c	/* R/W: ADC sample count register */
+#define   ES_REG_CURR_COUNT(i)  (((i)>>16)&0xffff)
+#define   ES_REG_COUNTO(o)	(((o)&0xffff)<<0)
+#define   ES_REG_COUNTM		(0xffff<<0)
+#define   ES_REG_COUNTI(i)	(((i)>>0)&0xffff)
+
+#define ES_REG_DAC1_FRAME 0x30	/* R/W: PAGE 0x0c; DAC1 frame address */
+#define ES_REG_DAC1_SIZE  0x34	/* R/W: PAGE 0x0c; DAC1 frame size */
+#define ES_REG_DAC2_FRAME 0x38	/* R/W: PAGE 0x0c; DAC2 frame address */
+#define ES_REG_DAC2_SIZE  0x3c	/* R/W: PAGE 0x0c; DAC2 frame size */
+#define ES_REG_ADC_FRAME  0x30	/* R/W: PAGE 0x0d; ADC frame address */
+#define ES_REG_ADC_SIZE	  0x34	/* R/W: PAGE 0x0d; ADC frame size */
+#define   ES_REG_FCURR_COUNTO(o) (((o)&0xffff)<<16)
+#define   ES_REG_FCURR_COUNTM    (0xffff<<16)
+#define   ES_REG_FCURR_COUNTI(i) (((i)>>14)&0x3fffc)
+#define   ES_REG_FSIZEO(o)	 (((o)&0xffff)<<0)
+#define   ES_REG_FSIZEM		 (0xffff<<0)
+#define   ES_REG_FSIZEI(i)	 (((i)>>0)&0xffff)
+#define ES_REG_PHANTOM_FRAME 0x38 /* R/W: PAGE 0x0d: phantom frame address */
+#define ES_REG_PHANTOM_COUNT 0x3c /* R/W: PAGE 0x0d: phantom frame count */
+
+#define ES_REG_UART_FIFO  0x30	/* R/W: PAGE 0x0e; UART FIFO register */
+#define   ES_REG_UF_VALID	 (1<<8)
+#define   ES_REG_UF_BYTEO(o)	 (((o)&0xff)<<0)
+#define   ES_REG_UF_BYTEM	 (0xff<<0)
+#define   ES_REG_UF_BYTEI(i)	 (((i)>>0)&0xff)
+
+
+/*
+ *  Pages
+ */
+
+#define ES_PAGE_DAC	0x0c
+#define ES_PAGE_ADC	0x0d
+#define ES_PAGE_UART	0x0e
+#define ES_PAGE_UART1	0x0f
+
+/*
+ *  Sample rate converter addresses
+ */
+
+#define ES_SMPREG_DAC1		0x70
+#define ES_SMPREG_DAC2		0x74
+#define ES_SMPREG_ADC		0x78
+#define ES_SMPREG_VOL_ADC	0x6c
+#define ES_SMPREG_VOL_DAC1	0x7c
+#define ES_SMPREG_VOL_DAC2	0x7e
+#define ES_SMPREG_TRUNC_N	0x00
+#define ES_SMPREG_INT_REGS	0x01
+#define ES_SMPREG_ACCUM_FRAC	0x02
+#define ES_SMPREG_VFREQ_FRAC	0x03
+
+/*
+ *  Some contants
+ */
+
+#define ES_1370_SRCLOCK	   1411200
+#define ES_1370_SRTODIV(x) (ES_1370_SRCLOCK/(x)-2)
+
+/*
+ *  Open modes
+ */
+
+#define ES_MODE_PLAY1	0x0001
+#define ES_MODE_PLAY2	0x0002
+#define ES_MODE_CAPTURE	0x0004
+
+#define ES_MODE_OUTPUT	0x0001	/* for MIDI */
+#define ES_MODE_INPUT	0x0002	/* for MIDI */
+
+/*
+
+ */
+
+struct ensoniq {
+	spinlock_t reg_lock;
+	struct mutex src_mutex;
+
+	int irq;
+
+	unsigned long playback1size;
+	unsigned long playback2size;
+	unsigned long capture3size;
+
+	unsigned long port;
+	unsigned int mode;
+	unsigned int uartm;	/* UART mode */
+
+	unsigned int ctrl;	/* control register */
+	unsigned int sctrl;	/* serial control register */
+	unsigned int cssr;	/* control status register */
+	unsigned int uartc;	/* uart control register */
+	unsigned int rev;	/* chip revision */
+
+	union {
+#ifdef CHIP1371
+		struct {
+			struct snd_ac97 *ac97;
+		} es1371;
+#else
+		struct {
+			int pclkdiv_lock;
+			struct snd_ak4531 *ak4531;
+		} es1370;
+#endif
+	} u;
+
+	struct pci_dev *pci;
+	struct snd_card *card;
+	struct snd_pcm *pcm1;	/* DAC1/ADC PCM */
+	struct snd_pcm *pcm2;	/* DAC2 PCM */
+	struct snd_pcm_substream *playback1_substream;
+	struct snd_pcm_substream *playback2_substream;
+	struct snd_pcm_substream *capture_substream;
+	unsigned int p1_dma_size;
+	unsigned int p2_dma_size;
+	unsigned int c_dma_size;
+	unsigned int p1_period_size;
+	unsigned int p2_period_size;
+	unsigned int c_period_size;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *midi_input;
+	struct snd_rawmidi_substream *midi_output;
+
+	unsigned int spdif;
+	unsigned int spdif_default;
+	unsigned int spdif_stream;
+
+#ifdef CHIP1370
+	struct snd_dma_buffer dma_bug;
+#endif
+
+#ifdef SUPPORT_JOYSTICK
+	struct gameport *gameport;
+#endif
+};
+
+static irqreturn_t snd_audiopci_interrupt(int irq, void *dev_id);
+
+static DEFINE_PCI_DEVICE_TABLE(snd_audiopci_ids) = {
+#ifdef CHIP1370
+	{ PCI_VDEVICE(ENSONIQ, 0x5000), 0, },	/* ES1370 */
+#endif
+#ifdef CHIP1371
+	{ PCI_VDEVICE(ENSONIQ, 0x1371), 0, },	/* ES1371 */
+	{ PCI_VDEVICE(ENSONIQ, 0x5880), 0, },	/* ES1373 - CT5880 */
+	{ PCI_VDEVICE(ECTIVA, 0x8938), 0, },	/* Ectiva EV1938 */
+#endif
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_audiopci_ids);
+
+/*
+ *  constants
+ */
+
+#define POLL_COUNT	0xa000
+
+#ifdef CHIP1370
+static unsigned int snd_es1370_fixed_rates[] =
+	{5512, 11025, 22050, 44100};
+static struct snd_pcm_hw_constraint_list snd_es1370_hw_constraints_rates = {
+	.count = 4, 
+	.list = snd_es1370_fixed_rates,
+	.mask = 0,
+};
+static struct snd_ratnum es1370_clock = {
+	.num = ES_1370_SRCLOCK,
+	.den_min = 29, 
+	.den_max = 353,
+	.den_step = 1,
+};
+static struct snd_pcm_hw_constraint_ratnums snd_es1370_hw_constraints_clock = {
+	.nrats = 1,
+	.rats = &es1370_clock,
+};
+#else
+static struct snd_ratden es1371_dac_clock = {
+	.num_min = 3000 * (1 << 15),
+	.num_max = 48000 * (1 << 15),
+	.num_step = 3000,
+	.den = 1 << 15,
+};
+static struct snd_pcm_hw_constraint_ratdens snd_es1371_hw_constraints_dac_clock = {
+	.nrats = 1,
+	.rats = &es1371_dac_clock,
+};
+static struct snd_ratnum es1371_adc_clock = {
+	.num = 48000 << 15,
+	.den_min = 32768, 
+	.den_max = 393216,
+	.den_step = 1,
+};
+static struct snd_pcm_hw_constraint_ratnums snd_es1371_hw_constraints_adc_clock = {
+	.nrats = 1,
+	.rats = &es1371_adc_clock,
+};
+#endif
+static const unsigned int snd_ensoniq_sample_shift[] =
+	{0, 1, 1, 2};
+
+/*
+ *  common I/O routines
+ */
+
+#ifdef CHIP1371
+
+static unsigned int snd_es1371_wait_src_ready(struct ensoniq * ensoniq)
+{
+	unsigned int t, r = 0;
+
+	for (t = 0; t < POLL_COUNT; t++) {
+		r = inl(ES_REG(ensoniq, 1371_SMPRATE));
+		if ((r & ES_1371_SRC_RAM_BUSY) == 0)
+			return r;
+		cond_resched();
+	}
+	snd_printk(KERN_ERR "wait src ready timeout 0x%lx [0x%x]\n",
+		   ES_REG(ensoniq, 1371_SMPRATE), r);
+	return 0;
+}
+
+static unsigned int snd_es1371_src_read(struct ensoniq * ensoniq, unsigned short reg)
+{
+	unsigned int temp, i, orig, r;
+
+	/* wait for ready */
+	temp = orig = snd_es1371_wait_src_ready(ensoniq);
+
+	/* expose the SRC state bits */
+	r = temp & (ES_1371_SRC_DISABLE | ES_1371_DIS_P1 |
+		    ES_1371_DIS_P2 | ES_1371_DIS_R1);
+	r |= ES_1371_SRC_RAM_ADDRO(reg) | 0x10000;
+	outl(r, ES_REG(ensoniq, 1371_SMPRATE));
+
+	/* now, wait for busy and the correct time to read */
+	temp = snd_es1371_wait_src_ready(ensoniq);
+	
+	if ((temp & 0x00870000) != 0x00010000) {
+		/* wait for the right state */
+		for (i = 0; i < POLL_COUNT; i++) {
+			temp = inl(ES_REG(ensoniq, 1371_SMPRATE));
+			if ((temp & 0x00870000) == 0x00010000)
+				break;
+		}
+	}
+
+	/* hide the state bits */	
+	r = orig & (ES_1371_SRC_DISABLE | ES_1371_DIS_P1 |
+		   ES_1371_DIS_P2 | ES_1371_DIS_R1);
+	r |= ES_1371_SRC_RAM_ADDRO(reg);
+	outl(r, ES_REG(ensoniq, 1371_SMPRATE));
+	
+	return temp;
+}
+
+static void snd_es1371_src_write(struct ensoniq * ensoniq,
+				 unsigned short reg, unsigned short data)
+{
+	unsigned int r;
+
+	r = snd_es1371_wait_src_ready(ensoniq) &
+	    (ES_1371_SRC_DISABLE | ES_1371_DIS_P1 |
+	     ES_1371_DIS_P2 | ES_1371_DIS_R1);
+	r |= ES_1371_SRC_RAM_ADDRO(reg) | ES_1371_SRC_RAM_DATAO(data);
+	outl(r | ES_1371_SRC_RAM_WE, ES_REG(ensoniq, 1371_SMPRATE));
+}
+
+#endif /* CHIP1371 */
+
+#ifdef CHIP1370
+
+static void snd_es1370_codec_write(struct snd_ak4531 *ak4531,
+				   unsigned short reg, unsigned short val)
+{
+	struct ensoniq *ensoniq = ak4531->private_data;
+	unsigned long end_time = jiffies + HZ / 10;
+
+#if 0
+	printk(KERN_DEBUG
+	       "CODEC WRITE: reg = 0x%x, val = 0x%x (0x%x), creg = 0x%x\n",
+	       reg, val, ES_1370_CODEC_WRITE(reg, val), ES_REG(ensoniq, 1370_CODEC));
+#endif
+	do {
+		if (!(inl(ES_REG(ensoniq, STATUS)) & ES_1370_CSTAT)) {
+			outw(ES_1370_CODEC_WRITE(reg, val), ES_REG(ensoniq, 1370_CODEC));
+			return;
+		}
+		schedule_timeout_uninterruptible(1);
+	} while (time_after(end_time, jiffies));
+	snd_printk(KERN_ERR "codec write timeout, status = 0x%x\n",
+		   inl(ES_REG(ensoniq, STATUS)));
+}
+
+#endif /* CHIP1370 */
+
+#ifdef CHIP1371
+
+static void snd_es1371_codec_write(struct snd_ac97 *ac97,
+				   unsigned short reg, unsigned short val)
+{
+	struct ensoniq *ensoniq = ac97->private_data;
+	unsigned int t, x;
+
+	mutex_lock(&ensoniq->src_mutex);
+	for (t = 0; t < POLL_COUNT; t++) {
+		if (!(inl(ES_REG(ensoniq, 1371_CODEC)) & ES_1371_CODEC_WIP)) {
+			/* save the current state for latter */
+			x = snd_es1371_wait_src_ready(ensoniq);
+			outl((x & (ES_1371_SRC_DISABLE | ES_1371_DIS_P1 |
+			           ES_1371_DIS_P2 | ES_1371_DIS_R1)) | 0x00010000,
+			     ES_REG(ensoniq, 1371_SMPRATE));
+			/* wait for not busy (state 0) first to avoid
+			   transition states */
+			for (t = 0; t < POLL_COUNT; t++) {
+				if ((inl(ES_REG(ensoniq, 1371_SMPRATE)) & 0x00870000) ==
+				    0x00000000)
+					break;
+			}
+			/* wait for a SAFE time to write addr/data and then do it, dammit */
+			for (t = 0; t < POLL_COUNT; t++) {
+				if ((inl(ES_REG(ensoniq, 1371_SMPRATE)) & 0x00870000) ==
+				    0x00010000)
+					break;
+			}
+			outl(ES_1371_CODEC_WRITE(reg, val), ES_REG(ensoniq, 1371_CODEC));
+			/* restore SRC reg */
+			snd_es1371_wait_src_ready(ensoniq);
+			outl(x, ES_REG(ensoniq, 1371_SMPRATE));
+			mutex_unlock(&ensoniq->src_mutex);
+			return;
+		}
+	}
+	mutex_unlock(&ensoniq->src_mutex);
+	snd_printk(KERN_ERR "codec write timeout at 0x%lx [0x%x]\n",
+		   ES_REG(ensoniq, 1371_CODEC), inl(ES_REG(ensoniq, 1371_CODEC)));
+}
+
+static unsigned short snd_es1371_codec_read(struct snd_ac97 *ac97,
+					    unsigned short reg)
+{
+	struct ensoniq *ensoniq = ac97->private_data;
+	unsigned int t, x, fail = 0;
+
+      __again:
+	mutex_lock(&ensoniq->src_mutex);
+	for (t = 0; t < POLL_COUNT; t++) {
+		if (!(inl(ES_REG(ensoniq, 1371_CODEC)) & ES_1371_CODEC_WIP)) {
+			/* save the current state for latter */
+			x = snd_es1371_wait_src_ready(ensoniq);
+			outl((x & (ES_1371_SRC_DISABLE | ES_1371_DIS_P1 |
+			           ES_1371_DIS_P2 | ES_1371_DIS_R1)) | 0x00010000,
+			     ES_REG(ensoniq, 1371_SMPRATE));
+			/* wait for not busy (state 0) first to avoid
+			   transition states */
+			for (t = 0; t < POLL_COUNT; t++) {
+				if ((inl(ES_REG(ensoniq, 1371_SMPRATE)) & 0x00870000) ==
+				    0x00000000)
+					break;
+			}
+			/* wait for a SAFE time to write addr/data and then do it, dammit */
+			for (t = 0; t < POLL_COUNT; t++) {
+				if ((inl(ES_REG(ensoniq, 1371_SMPRATE)) & 0x00870000) ==
+				    0x00010000)
+					break;
+			}
+			outl(ES_1371_CODEC_READS(reg), ES_REG(ensoniq, 1371_CODEC));
+			/* restore SRC reg */
+			snd_es1371_wait_src_ready(ensoniq);
+			outl(x, ES_REG(ensoniq, 1371_SMPRATE));
+			/* wait for WIP again */
+			for (t = 0; t < POLL_COUNT; t++) {
+				if (!(inl(ES_REG(ensoniq, 1371_CODEC)) & ES_1371_CODEC_WIP))
+					break;		
+			}
+			/* now wait for the stinkin' data (RDY) */
+			for (t = 0; t < POLL_COUNT; t++) {
+				if ((x = inl(ES_REG(ensoniq, 1371_CODEC))) & ES_1371_CODEC_RDY) {
+					mutex_unlock(&ensoniq->src_mutex);
+					return ES_1371_CODEC_READ(x);
+				}
+			}
+			mutex_unlock(&ensoniq->src_mutex);
+			if (++fail > 10) {
+				snd_printk(KERN_ERR "codec read timeout (final) "
+					   "at 0x%lx, reg = 0x%x [0x%x]\n",
+					   ES_REG(ensoniq, 1371_CODEC), reg,
+					   inl(ES_REG(ensoniq, 1371_CODEC)));
+				return 0;
+			}
+			goto __again;
+		}
+	}
+	mutex_unlock(&ensoniq->src_mutex);
+	snd_printk(KERN_ERR "es1371: codec read timeout at 0x%lx [0x%x]\n",
+		   ES_REG(ensoniq, 1371_CODEC), inl(ES_REG(ensoniq, 1371_CODEC)));
+	return 0;
+}
+
+static void snd_es1371_codec_wait(struct snd_ac97 *ac97)
+{
+	msleep(750);
+	snd_es1371_codec_read(ac97, AC97_RESET);
+	snd_es1371_codec_read(ac97, AC97_VENDOR_ID1);
+	snd_es1371_codec_read(ac97, AC97_VENDOR_ID2);
+	msleep(50);
+}
+
+static void snd_es1371_adc_rate(struct ensoniq * ensoniq, unsigned int rate)
+{
+	unsigned int n, truncm, freq, result;
+
+	mutex_lock(&ensoniq->src_mutex);
+	n = rate / 3000;
+	if ((1 << n) & ((1 << 15) | (1 << 13) | (1 << 11) | (1 << 9)))
+		n--;
+	truncm = (21 * n - 1) | 1;
+	freq = ((48000UL << 15) / rate) * n;
+	result = (48000UL << 15) / (freq / n);
+	if (rate >= 24000) {
+		if (truncm > 239)
+			truncm = 239;
+		snd_es1371_src_write(ensoniq, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N,
+				(((239 - truncm) >> 1) << 9) | (n << 4));
+	} else {
+		if (truncm > 119)
+			truncm = 119;
+		snd_es1371_src_write(ensoniq, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N,
+				0x8000 | (((119 - truncm) >> 1) << 9) | (n << 4));
+	}
+	snd_es1371_src_write(ensoniq, ES_SMPREG_ADC + ES_SMPREG_INT_REGS,
+			     (snd_es1371_src_read(ensoniq, ES_SMPREG_ADC +
+						  ES_SMPREG_INT_REGS) & 0x00ff) |
+			     ((freq >> 5) & 0xfc00));
+	snd_es1371_src_write(ensoniq, ES_SMPREG_ADC + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_ADC, n << 8);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_ADC + 1, n << 8);
+	mutex_unlock(&ensoniq->src_mutex);
+}
+
+static void snd_es1371_dac1_rate(struct ensoniq * ensoniq, unsigned int rate)
+{
+	unsigned int freq, r;
+
+	mutex_lock(&ensoniq->src_mutex);
+	freq = ((rate << 15) + 1500) / 3000;
+	r = (snd_es1371_wait_src_ready(ensoniq) & (ES_1371_SRC_DISABLE |
+						   ES_1371_DIS_P2 | ES_1371_DIS_R1)) |
+		ES_1371_DIS_P1;
+	outl(r, ES_REG(ensoniq, 1371_SMPRATE));
+	snd_es1371_src_write(ensoniq, ES_SMPREG_DAC1 + ES_SMPREG_INT_REGS,
+			     (snd_es1371_src_read(ensoniq, ES_SMPREG_DAC1 +
+						  ES_SMPREG_INT_REGS) & 0x00ff) |
+			     ((freq >> 5) & 0xfc00));
+	snd_es1371_src_write(ensoniq, ES_SMPREG_DAC1 + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff);
+	r = (snd_es1371_wait_src_ready(ensoniq) & (ES_1371_SRC_DISABLE |
+						   ES_1371_DIS_P2 | ES_1371_DIS_R1));
+	outl(r, ES_REG(ensoniq, 1371_SMPRATE));
+	mutex_unlock(&ensoniq->src_mutex);
+}
+
+static void snd_es1371_dac2_rate(struct ensoniq * ensoniq, unsigned int rate)
+{
+	unsigned int freq, r;
+
+	mutex_lock(&ensoniq->src_mutex);
+	freq = ((rate << 15) + 1500) / 3000;
+	r = (snd_es1371_wait_src_ready(ensoniq) & (ES_1371_SRC_DISABLE |
+						   ES_1371_DIS_P1 | ES_1371_DIS_R1)) |
+		ES_1371_DIS_P2;
+	outl(r, ES_REG(ensoniq, 1371_SMPRATE));
+	snd_es1371_src_write(ensoniq, ES_SMPREG_DAC2 + ES_SMPREG_INT_REGS,
+			     (snd_es1371_src_read(ensoniq, ES_SMPREG_DAC2 +
+						  ES_SMPREG_INT_REGS) & 0x00ff) |
+			     ((freq >> 5) & 0xfc00));
+	snd_es1371_src_write(ensoniq, ES_SMPREG_DAC2 + ES_SMPREG_VFREQ_FRAC,
+			     freq & 0x7fff);
+	r = (snd_es1371_wait_src_ready(ensoniq) & (ES_1371_SRC_DISABLE |
+						   ES_1371_DIS_P1 | ES_1371_DIS_R1));
+	outl(r, ES_REG(ensoniq, 1371_SMPRATE));
+	mutex_unlock(&ensoniq->src_mutex);
+}
+
+#endif /* CHIP1371 */
+
+static int snd_ensoniq_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	{
+		unsigned int what = 0;
+		struct snd_pcm_substream *s;
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s == ensoniq->playback1_substream) {
+				what |= ES_P1_PAUSE;
+				snd_pcm_trigger_done(s, substream);
+			} else if (s == ensoniq->playback2_substream) {
+				what |= ES_P2_PAUSE;
+				snd_pcm_trigger_done(s, substream);
+			} else if (s == ensoniq->capture_substream)
+				return -EINVAL;
+		}
+		spin_lock(&ensoniq->reg_lock);
+		if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)
+			ensoniq->sctrl |= what;
+		else
+			ensoniq->sctrl &= ~what;
+		outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL));
+		spin_unlock(&ensoniq->reg_lock);
+		break;
+	}
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_STOP:
+	{
+		unsigned int what = 0;
+		struct snd_pcm_substream *s;
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s == ensoniq->playback1_substream) {
+				what |= ES_DAC1_EN;
+				snd_pcm_trigger_done(s, substream);
+			} else if (s == ensoniq->playback2_substream) {
+				what |= ES_DAC2_EN;
+				snd_pcm_trigger_done(s, substream);
+			} else if (s == ensoniq->capture_substream) {
+				what |= ES_ADC_EN;
+				snd_pcm_trigger_done(s, substream);
+			}
+		}
+		spin_lock(&ensoniq->reg_lock);
+		if (cmd == SNDRV_PCM_TRIGGER_START)
+			ensoniq->ctrl |= what;
+		else
+			ensoniq->ctrl &= ~what;
+		outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+		spin_unlock(&ensoniq->reg_lock);
+		break;
+	}
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ *  PCM part
+ */
+
+static int snd_ensoniq_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_ensoniq_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_ensoniq_playback1_prepare(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int mode = 0;
+
+	ensoniq->p1_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	ensoniq->p1_period_size = snd_pcm_lib_period_bytes(substream);
+	if (snd_pcm_format_width(runtime->format) == 16)
+		mode |= 0x02;
+	if (runtime->channels > 1)
+		mode |= 0x01;
+	spin_lock_irq(&ensoniq->reg_lock);
+	ensoniq->ctrl &= ~ES_DAC1_EN;
+#ifdef CHIP1371
+	/* 48k doesn't need SRC (it breaks AC3-passthru) */
+	if (runtime->rate == 48000)
+		ensoniq->ctrl |= ES_1373_BYPASS_P1;
+	else
+		ensoniq->ctrl &= ~ES_1373_BYPASS_P1;
+#endif
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	outl(ES_MEM_PAGEO(ES_PAGE_DAC), ES_REG(ensoniq, MEM_PAGE));
+	outl(runtime->dma_addr, ES_REG(ensoniq, DAC1_FRAME));
+	outl((ensoniq->p1_dma_size >> 2) - 1, ES_REG(ensoniq, DAC1_SIZE));
+	ensoniq->sctrl &= ~(ES_P1_LOOP_SEL | ES_P1_PAUSE | ES_P1_SCT_RLD | ES_P1_MODEM);
+	ensoniq->sctrl |= ES_P1_INT_EN | ES_P1_MODEO(mode);
+	outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL));
+	outl((ensoniq->p1_period_size >> snd_ensoniq_sample_shift[mode]) - 1,
+	     ES_REG(ensoniq, DAC1_COUNT));
+#ifdef CHIP1370
+	ensoniq->ctrl &= ~ES_1370_WTSRSELM;
+	switch (runtime->rate) {
+	case 5512: ensoniq->ctrl |= ES_1370_WTSRSEL(0); break;
+	case 11025: ensoniq->ctrl |= ES_1370_WTSRSEL(1); break;
+	case 22050: ensoniq->ctrl |= ES_1370_WTSRSEL(2); break;
+	case 44100: ensoniq->ctrl |= ES_1370_WTSRSEL(3); break;
+	default: snd_BUG();
+	}
+#endif
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	spin_unlock_irq(&ensoniq->reg_lock);
+#ifndef CHIP1370
+	snd_es1371_dac1_rate(ensoniq, runtime->rate);
+#endif
+	return 0;
+}
+
+static int snd_ensoniq_playback2_prepare(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int mode = 0;
+
+	ensoniq->p2_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	ensoniq->p2_period_size = snd_pcm_lib_period_bytes(substream);
+	if (snd_pcm_format_width(runtime->format) == 16)
+		mode |= 0x02;
+	if (runtime->channels > 1)
+		mode |= 0x01;
+	spin_lock_irq(&ensoniq->reg_lock);
+	ensoniq->ctrl &= ~ES_DAC2_EN;
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	outl(ES_MEM_PAGEO(ES_PAGE_DAC), ES_REG(ensoniq, MEM_PAGE));
+	outl(runtime->dma_addr, ES_REG(ensoniq, DAC2_FRAME));
+	outl((ensoniq->p2_dma_size >> 2) - 1, ES_REG(ensoniq, DAC2_SIZE));
+	ensoniq->sctrl &= ~(ES_P2_LOOP_SEL | ES_P2_PAUSE | ES_P2_DAC_SEN |
+			    ES_P2_END_INCM | ES_P2_ST_INCM | ES_P2_MODEM);
+	ensoniq->sctrl |= ES_P2_INT_EN | ES_P2_MODEO(mode) |
+			  ES_P2_END_INCO(mode & 2 ? 2 : 1) | ES_P2_ST_INCO(0);
+	outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL));
+	outl((ensoniq->p2_period_size >> snd_ensoniq_sample_shift[mode]) - 1,
+	     ES_REG(ensoniq, DAC2_COUNT));
+#ifdef CHIP1370
+	if (!(ensoniq->u.es1370.pclkdiv_lock & ES_MODE_CAPTURE)) {
+		ensoniq->ctrl &= ~ES_1370_PCLKDIVM;
+		ensoniq->ctrl |= ES_1370_PCLKDIVO(ES_1370_SRTODIV(runtime->rate));
+		ensoniq->u.es1370.pclkdiv_lock |= ES_MODE_PLAY2;
+	}
+#endif
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	spin_unlock_irq(&ensoniq->reg_lock);
+#ifndef CHIP1370
+	snd_es1371_dac2_rate(ensoniq, runtime->rate);
+#endif
+	return 0;
+}
+
+static int snd_ensoniq_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int mode = 0;
+
+	ensoniq->c_dma_size = snd_pcm_lib_buffer_bytes(substream);
+	ensoniq->c_period_size = snd_pcm_lib_period_bytes(substream);
+	if (snd_pcm_format_width(runtime->format) == 16)
+		mode |= 0x02;
+	if (runtime->channels > 1)
+		mode |= 0x01;
+	spin_lock_irq(&ensoniq->reg_lock);
+	ensoniq->ctrl &= ~ES_ADC_EN;
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	outl(ES_MEM_PAGEO(ES_PAGE_ADC), ES_REG(ensoniq, MEM_PAGE));
+	outl(runtime->dma_addr, ES_REG(ensoniq, ADC_FRAME));
+	outl((ensoniq->c_dma_size >> 2) - 1, ES_REG(ensoniq, ADC_SIZE));
+	ensoniq->sctrl &= ~(ES_R1_LOOP_SEL | ES_R1_MODEM);
+	ensoniq->sctrl |= ES_R1_INT_EN | ES_R1_MODEO(mode);
+	outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL));
+	outl((ensoniq->c_period_size >> snd_ensoniq_sample_shift[mode]) - 1,
+	     ES_REG(ensoniq, ADC_COUNT));
+#ifdef CHIP1370
+	if (!(ensoniq->u.es1370.pclkdiv_lock & ES_MODE_PLAY2)) {
+		ensoniq->ctrl &= ~ES_1370_PCLKDIVM;
+		ensoniq->ctrl |= ES_1370_PCLKDIVO(ES_1370_SRTODIV(runtime->rate));
+		ensoniq->u.es1370.pclkdiv_lock |= ES_MODE_CAPTURE;
+	}
+#endif
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	spin_unlock_irq(&ensoniq->reg_lock);
+#ifndef CHIP1370
+	snd_es1371_adc_rate(ensoniq, runtime->rate);
+#endif
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_ensoniq_playback1_pointer(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	spin_lock(&ensoniq->reg_lock);
+	if (inl(ES_REG(ensoniq, CONTROL)) & ES_DAC1_EN) {
+		outl(ES_MEM_PAGEO(ES_PAGE_DAC), ES_REG(ensoniq, MEM_PAGE));
+		ptr = ES_REG_FCURR_COUNTI(inl(ES_REG(ensoniq, DAC1_SIZE)));
+		ptr = bytes_to_frames(substream->runtime, ptr);
+	} else {
+		ptr = 0;
+	}
+	spin_unlock(&ensoniq->reg_lock);
+	return ptr;
+}
+
+static snd_pcm_uframes_t snd_ensoniq_playback2_pointer(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	spin_lock(&ensoniq->reg_lock);
+	if (inl(ES_REG(ensoniq, CONTROL)) & ES_DAC2_EN) {
+		outl(ES_MEM_PAGEO(ES_PAGE_DAC), ES_REG(ensoniq, MEM_PAGE));
+		ptr = ES_REG_FCURR_COUNTI(inl(ES_REG(ensoniq, DAC2_SIZE)));
+		ptr = bytes_to_frames(substream->runtime, ptr);
+	} else {
+		ptr = 0;
+	}
+	spin_unlock(&ensoniq->reg_lock);
+	return ptr;
+}
+
+static snd_pcm_uframes_t snd_ensoniq_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	spin_lock(&ensoniq->reg_lock);
+	if (inl(ES_REG(ensoniq, CONTROL)) & ES_ADC_EN) {
+		outl(ES_MEM_PAGEO(ES_PAGE_ADC), ES_REG(ensoniq, MEM_PAGE));
+		ptr = ES_REG_FCURR_COUNTI(inl(ES_REG(ensoniq, ADC_SIZE)));
+		ptr = bytes_to_frames(substream->runtime, ptr);
+	} else {
+		ptr = 0;
+	}
+	spin_unlock(&ensoniq->reg_lock);
+	return ptr;
+}
+
+static struct snd_pcm_hardware snd_ensoniq_playback1 =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =
+#ifndef CHIP1370
+				SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+#else
+				(SNDRV_PCM_RATE_KNOT | 	/* 5512Hz rate */
+				 SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050 | 
+				 SNDRV_PCM_RATE_44100),
+#endif
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_ensoniq_playback2 =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | 
+				 SNDRV_PCM_INFO_SYNC_START),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_ensoniq_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static int snd_ensoniq_playback1_open(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	ensoniq->mode |= ES_MODE_PLAY1;
+	ensoniq->playback1_substream = substream;
+	runtime->hw = snd_ensoniq_playback1;
+	snd_pcm_set_sync(substream);
+	spin_lock_irq(&ensoniq->reg_lock);
+	if (ensoniq->spdif && ensoniq->playback2_substream == NULL)
+		ensoniq->spdif_stream = ensoniq->spdif_default;
+	spin_unlock_irq(&ensoniq->reg_lock);
+#ifdef CHIP1370
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				   &snd_es1370_hw_constraints_rates);
+#else
+	snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &snd_es1371_hw_constraints_dac_clock);
+#endif
+	return 0;
+}
+
+static int snd_ensoniq_playback2_open(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	ensoniq->mode |= ES_MODE_PLAY2;
+	ensoniq->playback2_substream = substream;
+	runtime->hw = snd_ensoniq_playback2;
+	snd_pcm_set_sync(substream);
+	spin_lock_irq(&ensoniq->reg_lock);
+	if (ensoniq->spdif && ensoniq->playback1_substream == NULL)
+		ensoniq->spdif_stream = ensoniq->spdif_default;
+	spin_unlock_irq(&ensoniq->reg_lock);
+#ifdef CHIP1370
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &snd_es1370_hw_constraints_clock);
+#else
+	snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &snd_es1371_hw_constraints_dac_clock);
+#endif
+	return 0;
+}
+
+static int snd_ensoniq_capture_open(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	ensoniq->mode |= ES_MODE_CAPTURE;
+	ensoniq->capture_substream = substream;
+	runtime->hw = snd_ensoniq_capture;
+	snd_pcm_set_sync(substream);
+#ifdef CHIP1370
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &snd_es1370_hw_constraints_clock);
+#else
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &snd_es1371_hw_constraints_adc_clock);
+#endif
+	return 0;
+}
+
+static int snd_ensoniq_playback1_close(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+
+	ensoniq->playback1_substream = NULL;
+	ensoniq->mode &= ~ES_MODE_PLAY1;
+	return 0;
+}
+
+static int snd_ensoniq_playback2_close(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+
+	ensoniq->playback2_substream = NULL;
+	spin_lock_irq(&ensoniq->reg_lock);
+#ifdef CHIP1370
+	ensoniq->u.es1370.pclkdiv_lock &= ~ES_MODE_PLAY2;
+#endif
+	ensoniq->mode &= ~ES_MODE_PLAY2;
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static int snd_ensoniq_capture_close(struct snd_pcm_substream *substream)
+{
+	struct ensoniq *ensoniq = snd_pcm_substream_chip(substream);
+
+	ensoniq->capture_substream = NULL;
+	spin_lock_irq(&ensoniq->reg_lock);
+#ifdef CHIP1370
+	ensoniq->u.es1370.pclkdiv_lock &= ~ES_MODE_CAPTURE;
+#endif
+	ensoniq->mode &= ~ES_MODE_CAPTURE;
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static struct snd_pcm_ops snd_ensoniq_playback1_ops = {
+	.open =		snd_ensoniq_playback1_open,
+	.close =	snd_ensoniq_playback1_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ensoniq_hw_params,
+	.hw_free =	snd_ensoniq_hw_free,
+	.prepare =	snd_ensoniq_playback1_prepare,
+	.trigger =	snd_ensoniq_trigger,
+	.pointer =	snd_ensoniq_playback1_pointer,
+};
+
+static struct snd_pcm_ops snd_ensoniq_playback2_ops = {
+	.open =		snd_ensoniq_playback2_open,
+	.close =	snd_ensoniq_playback2_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ensoniq_hw_params,
+	.hw_free =	snd_ensoniq_hw_free,
+	.prepare =	snd_ensoniq_playback2_prepare,
+	.trigger =	snd_ensoniq_trigger,
+	.pointer =	snd_ensoniq_playback2_pointer,
+};
+
+static struct snd_pcm_ops snd_ensoniq_capture_ops = {
+	.open =		snd_ensoniq_capture_open,
+	.close =	snd_ensoniq_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ensoniq_hw_params,
+	.hw_free =	snd_ensoniq_hw_free,
+	.prepare =	snd_ensoniq_capture_prepare,
+	.trigger =	snd_ensoniq_trigger,
+	.pointer =	snd_ensoniq_capture_pointer,
+};
+
+static int __devinit snd_ensoniq_pcm(struct ensoniq * ensoniq, int device,
+				     struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+#ifdef CHIP1370
+	err = snd_pcm_new(ensoniq->card, "ES1370/1", device, 1, 1, &pcm);
+#else
+	err = snd_pcm_new(ensoniq->card, "ES1371/1", device, 1, 1, &pcm);
+#endif
+	if (err < 0)
+		return err;
+
+#ifdef CHIP1370
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ensoniq_playback2_ops);
+#else
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ensoniq_playback1_ops);
+#endif
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ensoniq_capture_ops);
+
+	pcm->private_data = ensoniq;
+	pcm->info_flags = 0;
+#ifdef CHIP1370
+	strcpy(pcm->name, "ES1370 DAC2/ADC");
+#else
+	strcpy(pcm->name, "ES1371 DAC2/ADC");
+#endif
+	ensoniq->pcm1 = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(ensoniq->pci), 64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+static int __devinit snd_ensoniq_pcm2(struct ensoniq * ensoniq, int device,
+				      struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+#ifdef CHIP1370
+	err = snd_pcm_new(ensoniq->card, "ES1370/2", device, 1, 0, &pcm);
+#else
+	err = snd_pcm_new(ensoniq->card, "ES1371/2", device, 1, 0, &pcm);
+#endif
+	if (err < 0)
+		return err;
+
+#ifdef CHIP1370
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ensoniq_playback1_ops);
+#else
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ensoniq_playback2_ops);
+#endif
+	pcm->private_data = ensoniq;
+	pcm->info_flags = 0;
+#ifdef CHIP1370
+	strcpy(pcm->name, "ES1370 DAC1");
+#else
+	strcpy(pcm->name, "ES1371 DAC1");
+#endif
+	ensoniq->pcm2 = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(ensoniq->pci), 64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+/*
+ *  Mixer section
+ */
+
+/*
+ * ENS1371 mixer (including SPDIF interface)
+ */
+#ifdef CHIP1371
+static int snd_ens1373_spdif_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_ens1373_spdif_default_get(struct snd_kcontrol *kcontrol,
+                                         struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	spin_lock_irq(&ensoniq->reg_lock);
+	ucontrol->value.iec958.status[0] = (ensoniq->spdif_default >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (ensoniq->spdif_default >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (ensoniq->spdif_default >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (ensoniq->spdif_default >> 24) & 0xff;
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static int snd_ens1373_spdif_default_put(struct snd_kcontrol *kcontrol,
+                                         struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+
+	val = ((u32)ucontrol->value.iec958.status[0] << 0) |
+	      ((u32)ucontrol->value.iec958.status[1] << 8) |
+	      ((u32)ucontrol->value.iec958.status[2] << 16) |
+	      ((u32)ucontrol->value.iec958.status[3] << 24);
+	spin_lock_irq(&ensoniq->reg_lock);
+	change = ensoniq->spdif_default != val;
+	ensoniq->spdif_default = val;
+	if (change && ensoniq->playback1_substream == NULL &&
+	    ensoniq->playback2_substream == NULL)
+		outl(val, ES_REG(ensoniq, CHANNEL_STATUS));
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return change;
+}
+
+static int snd_ens1373_spdif_mask_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0xff;
+	return 0;
+}
+
+static int snd_ens1373_spdif_stream_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	spin_lock_irq(&ensoniq->reg_lock);
+	ucontrol->value.iec958.status[0] = (ensoniq->spdif_stream >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (ensoniq->spdif_stream >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (ensoniq->spdif_stream >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (ensoniq->spdif_stream >> 24) & 0xff;
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static int snd_ens1373_spdif_stream_put(struct snd_kcontrol *kcontrol,
+                                        struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+
+	val = ((u32)ucontrol->value.iec958.status[0] << 0) |
+	      ((u32)ucontrol->value.iec958.status[1] << 8) |
+	      ((u32)ucontrol->value.iec958.status[2] << 16) |
+	      ((u32)ucontrol->value.iec958.status[3] << 24);
+	spin_lock_irq(&ensoniq->reg_lock);
+	change = ensoniq->spdif_stream != val;
+	ensoniq->spdif_stream = val;
+	if (change && (ensoniq->playback1_substream != NULL ||
+		       ensoniq->playback2_substream != NULL))
+		outl(val, ES_REG(ensoniq, CHANNEL_STATUS));
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return change;
+}
+
+#define ES1371_SPDIF(xname) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_es1371_spdif_info, \
+  .get = snd_es1371_spdif_get, .put = snd_es1371_spdif_put }
+
+#define snd_es1371_spdif_info		snd_ctl_boolean_mono_info
+
+static int snd_es1371_spdif_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&ensoniq->reg_lock);
+	ucontrol->value.integer.value[0] = ensoniq->ctrl & ES_1373_SPDIF_THRU ? 1 : 0;
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static int snd_es1371_spdif_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	unsigned int nval1, nval2;
+	int change;
+	
+	nval1 = ucontrol->value.integer.value[0] ? ES_1373_SPDIF_THRU : 0;
+	nval2 = ucontrol->value.integer.value[0] ? ES_1373_SPDIF_EN : 0;
+	spin_lock_irq(&ensoniq->reg_lock);
+	change = (ensoniq->ctrl & ES_1373_SPDIF_THRU) != nval1;
+	ensoniq->ctrl &= ~ES_1373_SPDIF_THRU;
+	ensoniq->ctrl |= nval1;
+	ensoniq->cssr &= ~ES_1373_SPDIF_EN;
+	ensoniq->cssr |= nval2;
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	outl(ensoniq->cssr, ES_REG(ensoniq, STATUS));
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return change;
+}
+
+
+/* spdif controls */
+static struct snd_kcontrol_new snd_es1371_mixer_spdif[] __devinitdata = {
+	ES1371_SPDIF(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH)),
+	{
+		.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+		.info =		snd_ens1373_spdif_info,
+		.get =		snd_ens1373_spdif_default_get,
+		.put =		snd_ens1373_spdif_default_put,
+	},
+	{
+		.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+		.info =		snd_ens1373_spdif_info,
+		.get =		snd_ens1373_spdif_mask_get
+	},
+	{
+		.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+		.info =		snd_ens1373_spdif_info,
+		.get =		snd_ens1373_spdif_stream_get,
+		.put =		snd_ens1373_spdif_stream_put
+	},
+};
+
+
+#define snd_es1373_rear_info		snd_ctl_boolean_mono_info
+
+static int snd_es1373_rear_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	int val = 0;
+	
+	spin_lock_irq(&ensoniq->reg_lock);
+	if ((ensoniq->cssr & (ES_1373_REAR_BIT27|ES_1373_REAR_BIT26|
+			      ES_1373_REAR_BIT24)) == ES_1373_REAR_BIT26)
+	    	val = 1;
+	ucontrol->value.integer.value[0] = val;
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static int snd_es1373_rear_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	unsigned int nval1;
+	int change;
+	
+	nval1 = ucontrol->value.integer.value[0] ?
+		ES_1373_REAR_BIT26 : (ES_1373_REAR_BIT27|ES_1373_REAR_BIT24);
+	spin_lock_irq(&ensoniq->reg_lock);
+	change = (ensoniq->cssr & (ES_1373_REAR_BIT27|
+				   ES_1373_REAR_BIT26|ES_1373_REAR_BIT24)) != nval1;
+	ensoniq->cssr &= ~(ES_1373_REAR_BIT27|ES_1373_REAR_BIT26|ES_1373_REAR_BIT24);
+	ensoniq->cssr |= nval1;
+	outl(ensoniq->cssr, ES_REG(ensoniq, STATUS));
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_ens1373_rear __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"AC97 2ch->4ch Copy Switch",
+	.info =		snd_es1373_rear_info,
+	.get =		snd_es1373_rear_get,
+	.put =		snd_es1373_rear_put,
+};
+
+#define snd_es1373_line_info		snd_ctl_boolean_mono_info
+
+static int snd_es1373_line_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	int val = 0;
+	
+	spin_lock_irq(&ensoniq->reg_lock);
+	if ((ensoniq->ctrl & ES_1371_GPIO_OUTM) >= 4)
+	    	val = 1;
+	ucontrol->value.integer.value[0] = val;
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static int snd_es1373_line_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	int changed;
+	unsigned int ctrl;
+	
+	spin_lock_irq(&ensoniq->reg_lock);
+	ctrl = ensoniq->ctrl;
+	if (ucontrol->value.integer.value[0])
+		ensoniq->ctrl |= ES_1371_GPIO_OUT(4);	/* switch line-in -> rear out */
+	else
+		ensoniq->ctrl &= ~ES_1371_GPIO_OUT(4);
+	changed = (ctrl != ensoniq->ctrl);
+	if (changed)
+		outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_ens1373_line __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Line In->Rear Out Switch",
+	.info =		snd_es1373_line_info,
+	.get =		snd_es1373_line_get,
+	.put =		snd_es1373_line_put,
+};
+
+static void snd_ensoniq_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+	struct ensoniq *ensoniq = ac97->private_data;
+	ensoniq->u.es1371.ac97 = NULL;
+}
+
+struct es1371_quirk {
+	unsigned short vid;		/* vendor ID */
+	unsigned short did;		/* device ID */
+	unsigned char rev;		/* revision */
+};
+
+static int es1371_quirk_lookup(struct ensoniq *ensoniq,
+				struct es1371_quirk *list)
+{
+	while (list->vid != (unsigned short)PCI_ANY_ID) {
+		if (ensoniq->pci->vendor == list->vid &&
+		    ensoniq->pci->device == list->did &&
+		    ensoniq->rev == list->rev)
+			return 1;
+		list++;
+	}
+	return 0;
+}
+
+static struct es1371_quirk es1371_spdif_present[] __devinitdata = {
+	{ .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_C },
+	{ .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_D },
+	{ .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_E },
+	{ .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_ES1371, .rev = ES1371REV_CT5880_A },
+	{ .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_ES1371, .rev = ES1371REV_ES1373_8 },
+	{ .vid = PCI_ANY_ID, .did = PCI_ANY_ID }
+};
+
+static struct snd_pci_quirk ens1373_line_quirk[] __devinitdata = {
+	SND_PCI_QUIRK_ID(0x1274, 0x2000), /* GA-7DXR */
+	SND_PCI_QUIRK_ID(0x1458, 0xa000), /* GA-8IEXP */
+	{ } /* end */
+};
+
+static int __devinit snd_ensoniq_1371_mixer(struct ensoniq *ensoniq,
+					    int has_spdif, int has_line)
+{
+	struct snd_card *card = ensoniq->card;
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_es1371_codec_write,
+		.read = snd_es1371_codec_read,
+		.wait = snd_es1371_codec_wait,
+	};
+
+	if ((err = snd_ac97_bus(card, 0, &ops, NULL, &pbus)) < 0)
+		return err;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = ensoniq;
+	ac97.private_free = snd_ensoniq_mixer_free_ac97;
+	ac97.pci = ensoniq->pci;
+	ac97.scaps = AC97_SCAP_AUDIO;
+	if ((err = snd_ac97_mixer(pbus, &ac97, &ensoniq->u.es1371.ac97)) < 0)
+		return err;
+	if (has_spdif > 0 ||
+	    (!has_spdif && es1371_quirk_lookup(ensoniq, es1371_spdif_present))) {
+		struct snd_kcontrol *kctl;
+		int i, is_spdif = 0;
+
+		ensoniq->spdif_default = ensoniq->spdif_stream =
+			SNDRV_PCM_DEFAULT_CON_SPDIF;
+		outl(ensoniq->spdif_default, ES_REG(ensoniq, CHANNEL_STATUS));
+
+		if (ensoniq->u.es1371.ac97->ext_id & AC97_EI_SPDIF)
+			is_spdif++;
+
+		for (i = 0; i < ARRAY_SIZE(snd_es1371_mixer_spdif); i++) {
+			kctl = snd_ctl_new1(&snd_es1371_mixer_spdif[i], ensoniq);
+			if (!kctl)
+				return -ENOMEM;
+			kctl->id.index = is_spdif;
+			err = snd_ctl_add(card, kctl);
+			if (err < 0)
+				return err;
+		}
+	}
+	if (ensoniq->u.es1371.ac97->ext_id & AC97_EI_SDAC) {
+		/* mirror rear to front speakers */
+		ensoniq->cssr &= ~(ES_1373_REAR_BIT27|ES_1373_REAR_BIT24);
+		ensoniq->cssr |= ES_1373_REAR_BIT26;
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_ens1373_rear, ensoniq));
+		if (err < 0)
+			return err;
+	}
+	if (has_line > 0 ||
+	    snd_pci_quirk_lookup(ensoniq->pci, ens1373_line_quirk)) {
+		 err = snd_ctl_add(card, snd_ctl_new1(&snd_ens1373_line,
+						      ensoniq));
+		 if (err < 0)
+			 return err;
+	}
+
+	return 0;
+}
+
+#endif /* CHIP1371 */
+
+/* generic control callbacks for ens1370 */
+#ifdef CHIP1370
+#define ENSONIQ_CONTROL(xname, mask) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = xname, .info = snd_ensoniq_control_info, \
+  .get = snd_ensoniq_control_get, .put = snd_ensoniq_control_put, \
+  .private_value = mask }
+
+#define snd_ensoniq_control_info	snd_ctl_boolean_mono_info
+
+static int snd_ensoniq_control_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	int mask = kcontrol->private_value;
+	
+	spin_lock_irq(&ensoniq->reg_lock);
+	ucontrol->value.integer.value[0] = ensoniq->ctrl & mask ? 1 : 0;
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static int snd_ensoniq_control_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct ensoniq *ensoniq = snd_kcontrol_chip(kcontrol);
+	int mask = kcontrol->private_value;
+	unsigned int nval;
+	int change;
+	
+	nval = ucontrol->value.integer.value[0] ? mask : 0;
+	spin_lock_irq(&ensoniq->reg_lock);
+	change = (ensoniq->ctrl & mask) != nval;
+	ensoniq->ctrl &= ~mask;
+	ensoniq->ctrl |= nval;
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return change;
+}
+
+/*
+ * ENS1370 mixer
+ */
+
+static struct snd_kcontrol_new snd_es1370_controls[2] __devinitdata = {
+ENSONIQ_CONTROL("PCM 0 Output also on Line-In Jack", ES_1370_XCTL0),
+ENSONIQ_CONTROL("Mic +5V bias", ES_1370_XCTL1)
+};
+
+#define ES1370_CONTROLS ARRAY_SIZE(snd_es1370_controls)
+
+static void snd_ensoniq_mixer_free_ak4531(struct snd_ak4531 *ak4531)
+{
+	struct ensoniq *ensoniq = ak4531->private_data;
+	ensoniq->u.es1370.ak4531 = NULL;
+}
+
+static int __devinit snd_ensoniq_1370_mixer(struct ensoniq * ensoniq)
+{
+	struct snd_card *card = ensoniq->card;
+	struct snd_ak4531 ak4531;
+	unsigned int idx;
+	int err;
+
+	/* try reset AK4531 */
+	outw(ES_1370_CODEC_WRITE(AK4531_RESET, 0x02), ES_REG(ensoniq, 1370_CODEC));
+	inw(ES_REG(ensoniq, 1370_CODEC));
+	udelay(100);
+	outw(ES_1370_CODEC_WRITE(AK4531_RESET, 0x03), ES_REG(ensoniq, 1370_CODEC));
+	inw(ES_REG(ensoniq, 1370_CODEC));
+	udelay(100);
+
+	memset(&ak4531, 0, sizeof(ak4531));
+	ak4531.write = snd_es1370_codec_write;
+	ak4531.private_data = ensoniq;
+	ak4531.private_free = snd_ensoniq_mixer_free_ak4531;
+	if ((err = snd_ak4531_mixer(card, &ak4531, &ensoniq->u.es1370.ak4531)) < 0)
+		return err;
+	for (idx = 0; idx < ES1370_CONTROLS; idx++) {
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_es1370_controls[idx], ensoniq));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+#endif /* CHIP1370 */
+
+#ifdef SUPPORT_JOYSTICK
+
+#ifdef CHIP1371
+static int __devinit snd_ensoniq_get_joystick_port(int dev)
+{
+	switch (joystick_port[dev]) {
+	case 0: /* disabled */
+	case 1: /* auto-detect */
+	case 0x200:
+	case 0x208:
+	case 0x210:
+	case 0x218:
+		return joystick_port[dev];
+
+	default:
+		printk(KERN_ERR "ens1371: invalid joystick port %#x", joystick_port[dev]);
+		return 0;
+	}
+}
+#else
+static inline int snd_ensoniq_get_joystick_port(int dev)
+{
+	return joystick[dev] ? 0x200 : 0;
+}
+#endif
+
+static int __devinit snd_ensoniq_create_gameport(struct ensoniq *ensoniq, int dev)
+{
+	struct gameport *gp;
+	int io_port;
+
+	io_port = snd_ensoniq_get_joystick_port(dev);
+
+	switch (io_port) {
+	case 0:
+		return -ENOSYS;
+
+	case 1: /* auto_detect */
+		for (io_port = 0x200; io_port <= 0x218; io_port += 8)
+			if (request_region(io_port, 8, "ens137x: gameport"))
+				break;
+		if (io_port > 0x218) {
+			printk(KERN_WARNING "ens137x: no gameport ports available\n");
+			return -EBUSY;
+		}
+		break;
+
+	default:
+		if (!request_region(io_port, 8, "ens137x: gameport")) {
+			printk(KERN_WARNING "ens137x: gameport io port 0x%#x in use\n",
+			       io_port);
+			return -EBUSY;
+		}
+		break;
+	}
+
+	ensoniq->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "ens137x: cannot allocate memory for gameport\n");
+		release_region(io_port, 8);
+		return -ENOMEM;
+	}
+
+	gameport_set_name(gp, "ES137x");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(ensoniq->pci));
+	gameport_set_dev_parent(gp, &ensoniq->pci->dev);
+	gp->io = io_port;
+
+	ensoniq->ctrl |= ES_JYSTK_EN;
+#ifdef CHIP1371
+	ensoniq->ctrl &= ~ES_1371_JOY_ASELM;
+	ensoniq->ctrl |= ES_1371_JOY_ASEL((io_port - 0x200) / 8);
+#endif
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+
+	gameport_register_port(ensoniq->gameport);
+
+	return 0;
+}
+
+static void snd_ensoniq_free_gameport(struct ensoniq *ensoniq)
+{
+	if (ensoniq->gameport) {
+		int port = ensoniq->gameport->io;
+
+		gameport_unregister_port(ensoniq->gameport);
+		ensoniq->gameport = NULL;
+		ensoniq->ctrl &= ~ES_JYSTK_EN;
+		outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+		release_region(port, 8);
+	}
+}
+#else
+static inline int snd_ensoniq_create_gameport(struct ensoniq *ensoniq, long port) { return -ENOSYS; }
+static inline void snd_ensoniq_free_gameport(struct ensoniq *ensoniq) { }
+#endif /* SUPPORT_JOYSTICK */
+
+/*
+
+ */
+
+static void snd_ensoniq_proc_read(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer)
+{
+	struct ensoniq *ensoniq = entry->private_data;
+
+#ifdef CHIP1370
+	snd_iprintf(buffer, "Ensoniq AudioPCI ES1370\n\n");
+#else
+	snd_iprintf(buffer, "Ensoniq AudioPCI ES1371\n\n");
+#endif
+	snd_iprintf(buffer, "Joystick enable  : %s\n",
+		    ensoniq->ctrl & ES_JYSTK_EN ? "on" : "off");
+#ifdef CHIP1370
+	snd_iprintf(buffer, "MIC +5V bias     : %s\n",
+		    ensoniq->ctrl & ES_1370_XCTL1 ? "on" : "off");
+	snd_iprintf(buffer, "Line In to AOUT  : %s\n",
+		    ensoniq->ctrl & ES_1370_XCTL0 ? "on" : "off");
+#else
+	snd_iprintf(buffer, "Joystick port    : 0x%x\n",
+		    (ES_1371_JOY_ASELI(ensoniq->ctrl) * 8) + 0x200);
+#endif
+}
+
+static void __devinit snd_ensoniq_proc_init(struct ensoniq * ensoniq)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(ensoniq->card, "audiopci", &entry))
+		snd_info_set_text_ops(entry, ensoniq, snd_ensoniq_proc_read);
+}
+
+/*
+
+ */
+
+static int snd_ensoniq_free(struct ensoniq *ensoniq)
+{
+	snd_ensoniq_free_gameport(ensoniq);
+	if (ensoniq->irq < 0)
+		goto __hw_end;
+#ifdef CHIP1370
+	outl(ES_1370_SERR_DISABLE, ES_REG(ensoniq, CONTROL));	/* switch everything off */
+	outl(0, ES_REG(ensoniq, SERIAL));	/* clear serial interface */
+#else
+	outl(0, ES_REG(ensoniq, CONTROL));	/* switch everything off */
+	outl(0, ES_REG(ensoniq, SERIAL));	/* clear serial interface */
+#endif
+	if (ensoniq->irq >= 0)
+		synchronize_irq(ensoniq->irq);
+	pci_set_power_state(ensoniq->pci, 3);
+      __hw_end:
+#ifdef CHIP1370
+	if (ensoniq->dma_bug.area)
+		snd_dma_free_pages(&ensoniq->dma_bug);
+#endif
+	if (ensoniq->irq >= 0)
+		free_irq(ensoniq->irq, ensoniq);
+	pci_release_regions(ensoniq->pci);
+	pci_disable_device(ensoniq->pci);
+	kfree(ensoniq);
+	return 0;
+}
+
+static int snd_ensoniq_dev_free(struct snd_device *device)
+{
+	struct ensoniq *ensoniq = device->device_data;
+	return snd_ensoniq_free(ensoniq);
+}
+
+#ifdef CHIP1371
+static struct snd_pci_quirk es1371_amplifier_hack[] __devinitdata = {
+	SND_PCI_QUIRK_ID(0x107b, 0x2150),	/* Gateway Solo 2150 */
+	SND_PCI_QUIRK_ID(0x13bd, 0x100c),	/* EV1938 on Mebius PC-MJ100V */
+	SND_PCI_QUIRK_ID(0x1102, 0x5938),	/* Targa Xtender300 */
+	SND_PCI_QUIRK_ID(0x1102, 0x8938),	/* IPC Topnote G notebook */
+	{ } /* end */
+};
+
+static struct es1371_quirk es1371_ac97_reset_hack[] = {
+	{ .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_C },
+	{ .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_D },
+	{ .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_CT5880, .rev = CT5880REV_CT5880_E },
+	{ .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_ES1371, .rev = ES1371REV_CT5880_A },
+	{ .vid = PCI_VENDOR_ID_ENSONIQ, .did = PCI_DEVICE_ID_ENSONIQ_ES1371, .rev = ES1371REV_ES1373_8 },
+	{ .vid = PCI_ANY_ID, .did = PCI_ANY_ID }
+};
+#endif
+
+static void snd_ensoniq_chip_init(struct ensoniq *ensoniq)
+{
+#ifdef CHIP1371
+	int idx;
+#endif
+	/* this code was part of snd_ensoniq_create before intruduction
+	  * of suspend/resume
+	  */
+#ifdef CHIP1370
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL));
+	outl(ES_MEM_PAGEO(ES_PAGE_ADC), ES_REG(ensoniq, MEM_PAGE));
+	outl(ensoniq->dma_bug.addr, ES_REG(ensoniq, PHANTOM_FRAME));
+	outl(0, ES_REG(ensoniq, PHANTOM_COUNT));
+#else
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL));
+	outl(0, ES_REG(ensoniq, 1371_LEGACY));
+	if (es1371_quirk_lookup(ensoniq, es1371_ac97_reset_hack)) {
+	    outl(ensoniq->cssr, ES_REG(ensoniq, STATUS));
+	    /* need to delay around 20ms(bleech) to give
+	       some CODECs enough time to wakeup */
+	    msleep(20);
+	}
+	/* AC'97 warm reset to start the bitclk */
+	outl(ensoniq->ctrl | ES_1371_SYNC_RES, ES_REG(ensoniq, CONTROL));
+	inl(ES_REG(ensoniq, CONTROL));
+	udelay(20);
+	outl(ensoniq->ctrl, ES_REG(ensoniq, CONTROL));
+	/* Init the sample rate converter */
+	snd_es1371_wait_src_ready(ensoniq);	
+	outl(ES_1371_SRC_DISABLE, ES_REG(ensoniq, 1371_SMPRATE));
+	for (idx = 0; idx < 0x80; idx++)
+		snd_es1371_src_write(ensoniq, idx, 0);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_DAC1 + ES_SMPREG_TRUNC_N, 16 << 4);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_DAC1 + ES_SMPREG_INT_REGS, 16 << 10);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_DAC2 + ES_SMPREG_TRUNC_N, 16 << 4);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_DAC2 + ES_SMPREG_INT_REGS, 16 << 10);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_ADC, 1 << 12);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_ADC + 1, 1 << 12);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_DAC1, 1 << 12);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_DAC1 + 1, 1 << 12);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_DAC2, 1 << 12);
+	snd_es1371_src_write(ensoniq, ES_SMPREG_VOL_DAC2 + 1, 1 << 12);
+	snd_es1371_adc_rate(ensoniq, 22050);
+	snd_es1371_dac1_rate(ensoniq, 22050);
+	snd_es1371_dac2_rate(ensoniq, 22050);
+	/* WARNING:
+	 * enabling the sample rate converter without properly programming
+	 * its parameters causes the chip to lock up (the SRC busy bit will
+	 * be stuck high, and I've found no way to rectify this other than
+	 * power cycle) - Thomas Sailer
+	 */
+	snd_es1371_wait_src_ready(ensoniq);
+	outl(0, ES_REG(ensoniq, 1371_SMPRATE));
+	/* try reset codec directly */
+	outl(ES_1371_CODEC_WRITE(0, 0), ES_REG(ensoniq, 1371_CODEC));
+#endif
+	outb(ensoniq->uartc = 0x00, ES_REG(ensoniq, UART_CONTROL));
+	outb(0x00, ES_REG(ensoniq, UART_RES));
+	outl(ensoniq->cssr, ES_REG(ensoniq, STATUS));
+	synchronize_irq(ensoniq->irq);
+}
+
+#ifdef CONFIG_PM
+static int snd_ensoniq_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct ensoniq *ensoniq = card->private_data;
+	
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+	snd_pcm_suspend_all(ensoniq->pcm1);
+	snd_pcm_suspend_all(ensoniq->pcm2);
+	
+#ifdef CHIP1371	
+	snd_ac97_suspend(ensoniq->u.es1371.ac97);
+#else
+	/* try to reset AK4531 */
+	outw(ES_1370_CODEC_WRITE(AK4531_RESET, 0x02), ES_REG(ensoniq, 1370_CODEC));
+	inw(ES_REG(ensoniq, 1370_CODEC));
+	udelay(100);
+	outw(ES_1370_CODEC_WRITE(AK4531_RESET, 0x03), ES_REG(ensoniq, 1370_CODEC));
+	inw(ES_REG(ensoniq, 1370_CODEC));
+	udelay(100);
+	snd_ak4531_suspend(ensoniq->u.es1370.ak4531);
+#endif	
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_ensoniq_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct ensoniq *ensoniq = card->private_data;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR DRIVER_NAME ": pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_ensoniq_chip_init(ensoniq);
+
+#ifdef CHIP1371	
+	snd_ac97_resume(ensoniq->u.es1371.ac97);
+#else
+	snd_ak4531_resume(ensoniq->u.es1370.ak4531);
+#endif	
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+
+static int __devinit snd_ensoniq_create(struct snd_card *card,
+				     struct pci_dev *pci,
+				     struct ensoniq ** rensoniq)
+{
+	struct ensoniq *ensoniq;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_ensoniq_dev_free,
+	};
+
+	*rensoniq = NULL;
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	ensoniq = kzalloc(sizeof(*ensoniq), GFP_KERNEL);
+	if (ensoniq == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	spin_lock_init(&ensoniq->reg_lock);
+	mutex_init(&ensoniq->src_mutex);
+	ensoniq->card = card;
+	ensoniq->pci = pci;
+	ensoniq->irq = -1;
+	if ((err = pci_request_regions(pci, "Ensoniq AudioPCI")) < 0) {
+		kfree(ensoniq);
+		pci_disable_device(pci);
+		return err;
+	}
+	ensoniq->port = pci_resource_start(pci, 0);
+	if (request_irq(pci->irq, snd_audiopci_interrupt, IRQF_SHARED,
+			"Ensoniq AudioPCI", ensoniq)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_ensoniq_free(ensoniq);
+		return -EBUSY;
+	}
+	ensoniq->irq = pci->irq;
+#ifdef CHIP1370
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				16, &ensoniq->dma_bug) < 0) {
+		snd_printk(KERN_ERR "unable to allocate space for phantom area - dma_bug\n");
+		snd_ensoniq_free(ensoniq);
+		return -EBUSY;
+	}
+#endif
+	pci_set_master(pci);
+	ensoniq->rev = pci->revision;
+#ifdef CHIP1370
+#if 0
+	ensoniq->ctrl = ES_1370_CDC_EN | ES_1370_SERR_DISABLE |
+		ES_1370_PCLKDIVO(ES_1370_SRTODIV(8000));
+#else	/* get microphone working */
+	ensoniq->ctrl = ES_1370_CDC_EN | ES_1370_PCLKDIVO(ES_1370_SRTODIV(8000));
+#endif
+	ensoniq->sctrl = 0;
+#else
+	ensoniq->ctrl = 0;
+	ensoniq->sctrl = 0;
+	ensoniq->cssr = 0;
+	if (snd_pci_quirk_lookup(pci, es1371_amplifier_hack))
+		ensoniq->ctrl |= ES_1371_GPIO_OUT(1);	/* turn amplifier on */
+
+	if (es1371_quirk_lookup(ensoniq, es1371_ac97_reset_hack))
+		ensoniq->cssr |= ES_1371_ST_AC97_RST;
+#endif
+
+	snd_ensoniq_chip_init(ensoniq);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ensoniq, &ops)) < 0) {
+		snd_ensoniq_free(ensoniq);
+		return err;
+	}
+
+	snd_ensoniq_proc_init(ensoniq);
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rensoniq = ensoniq;
+	return 0;
+}
+
+/*
+ *  MIDI section
+ */
+
+static void snd_ensoniq_midi_interrupt(struct ensoniq * ensoniq)
+{
+	struct snd_rawmidi *rmidi = ensoniq->rmidi;
+	unsigned char status, mask, byte;
+
+	if (rmidi == NULL)
+		return;
+	/* do Rx at first */
+	spin_lock(&ensoniq->reg_lock);
+	mask = ensoniq->uartm & ES_MODE_INPUT ? ES_RXRDY : 0;
+	while (mask) {
+		status = inb(ES_REG(ensoniq, UART_STATUS));
+		if ((status & mask) == 0)
+			break;
+		byte = inb(ES_REG(ensoniq, UART_DATA));
+		snd_rawmidi_receive(ensoniq->midi_input, &byte, 1);
+	}
+	spin_unlock(&ensoniq->reg_lock);
+
+	/* do Tx at second */
+	spin_lock(&ensoniq->reg_lock);
+	mask = ensoniq->uartm & ES_MODE_OUTPUT ? ES_TXRDY : 0;
+	while (mask) {
+		status = inb(ES_REG(ensoniq, UART_STATUS));
+		if ((status & mask) == 0)
+			break;
+		if (snd_rawmidi_transmit(ensoniq->midi_output, &byte, 1) != 1) {
+			ensoniq->uartc &= ~ES_TXINTENM;
+			outb(ensoniq->uartc, ES_REG(ensoniq, UART_CONTROL));
+			mask &= ~ES_TXRDY;
+		} else {
+			outb(byte, ES_REG(ensoniq, UART_DATA));
+		}
+	}
+	spin_unlock(&ensoniq->reg_lock);
+}
+
+static int snd_ensoniq_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct ensoniq *ensoniq = substream->rmidi->private_data;
+
+	spin_lock_irq(&ensoniq->reg_lock);
+	ensoniq->uartm |= ES_MODE_INPUT;
+	ensoniq->midi_input = substream;
+	if (!(ensoniq->uartm & ES_MODE_OUTPUT)) {
+		outb(ES_CNTRL(3), ES_REG(ensoniq, UART_CONTROL));
+		outb(ensoniq->uartc = 0, ES_REG(ensoniq, UART_CONTROL));
+		outl(ensoniq->ctrl |= ES_UART_EN, ES_REG(ensoniq, CONTROL));
+	}
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static int snd_ensoniq_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct ensoniq *ensoniq = substream->rmidi->private_data;
+
+	spin_lock_irq(&ensoniq->reg_lock);
+	if (!(ensoniq->uartm & ES_MODE_OUTPUT)) {
+		outb(ensoniq->uartc = 0, ES_REG(ensoniq, UART_CONTROL));
+		outl(ensoniq->ctrl &= ~ES_UART_EN, ES_REG(ensoniq, CONTROL));
+	} else {
+		outb(ensoniq->uartc &= ~ES_RXINTEN, ES_REG(ensoniq, UART_CONTROL));
+	}
+	ensoniq->midi_input = NULL;
+	ensoniq->uartm &= ~ES_MODE_INPUT;
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static int snd_ensoniq_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct ensoniq *ensoniq = substream->rmidi->private_data;
+
+	spin_lock_irq(&ensoniq->reg_lock);
+	ensoniq->uartm |= ES_MODE_OUTPUT;
+	ensoniq->midi_output = substream;
+	if (!(ensoniq->uartm & ES_MODE_INPUT)) {
+		outb(ES_CNTRL(3), ES_REG(ensoniq, UART_CONTROL));
+		outb(ensoniq->uartc = 0, ES_REG(ensoniq, UART_CONTROL));
+		outl(ensoniq->ctrl |= ES_UART_EN, ES_REG(ensoniq, CONTROL));
+	}
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static int snd_ensoniq_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct ensoniq *ensoniq = substream->rmidi->private_data;
+
+	spin_lock_irq(&ensoniq->reg_lock);
+	if (!(ensoniq->uartm & ES_MODE_INPUT)) {
+		outb(ensoniq->uartc = 0, ES_REG(ensoniq, UART_CONTROL));
+		outl(ensoniq->ctrl &= ~ES_UART_EN, ES_REG(ensoniq, CONTROL));
+	} else {
+		outb(ensoniq->uartc &= ~ES_TXINTENM, ES_REG(ensoniq, UART_CONTROL));
+	}
+	ensoniq->midi_output = NULL;
+	ensoniq->uartm &= ~ES_MODE_OUTPUT;
+	spin_unlock_irq(&ensoniq->reg_lock);
+	return 0;
+}
+
+static void snd_ensoniq_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct ensoniq *ensoniq = substream->rmidi->private_data;
+	int idx;
+
+	spin_lock_irqsave(&ensoniq->reg_lock, flags);
+	if (up) {
+		if ((ensoniq->uartc & ES_RXINTEN) == 0) {
+			/* empty input FIFO */
+			for (idx = 0; idx < 32; idx++)
+				inb(ES_REG(ensoniq, UART_DATA));
+			ensoniq->uartc |= ES_RXINTEN;
+			outb(ensoniq->uartc, ES_REG(ensoniq, UART_CONTROL));
+		}
+	} else {
+		if (ensoniq->uartc & ES_RXINTEN) {
+			ensoniq->uartc &= ~ES_RXINTEN;
+			outb(ensoniq->uartc, ES_REG(ensoniq, UART_CONTROL));
+		}
+	}
+	spin_unlock_irqrestore(&ensoniq->reg_lock, flags);
+}
+
+static void snd_ensoniq_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	unsigned long flags;
+	struct ensoniq *ensoniq = substream->rmidi->private_data;
+	unsigned char byte;
+
+	spin_lock_irqsave(&ensoniq->reg_lock, flags);
+	if (up) {
+		if (ES_TXINTENI(ensoniq->uartc) == 0) {
+			ensoniq->uartc |= ES_TXINTENO(1);
+			/* fill UART FIFO buffer at first, and turn Tx interrupts only if necessary */
+			while (ES_TXINTENI(ensoniq->uartc) == 1 &&
+			       (inb(ES_REG(ensoniq, UART_STATUS)) & ES_TXRDY)) {
+				if (snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+					ensoniq->uartc &= ~ES_TXINTENM;
+				} else {
+					outb(byte, ES_REG(ensoniq, UART_DATA));
+				}
+			}
+			outb(ensoniq->uartc, ES_REG(ensoniq, UART_CONTROL));
+		}
+	} else {
+		if (ES_TXINTENI(ensoniq->uartc) == 1) {
+			ensoniq->uartc &= ~ES_TXINTENM;
+			outb(ensoniq->uartc, ES_REG(ensoniq, UART_CONTROL));
+		}
+	}
+	spin_unlock_irqrestore(&ensoniq->reg_lock, flags);
+}
+
+static struct snd_rawmidi_ops snd_ensoniq_midi_output =
+{
+	.open =		snd_ensoniq_midi_output_open,
+	.close =	snd_ensoniq_midi_output_close,
+	.trigger =	snd_ensoniq_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_ensoniq_midi_input =
+{
+	.open =		snd_ensoniq_midi_input_open,
+	.close =	snd_ensoniq_midi_input_close,
+	.trigger =	snd_ensoniq_midi_input_trigger,
+};
+
+static int __devinit snd_ensoniq_midi(struct ensoniq * ensoniq, int device,
+				      struct snd_rawmidi **rrawmidi)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	if (rrawmidi)
+		*rrawmidi = NULL;
+	if ((err = snd_rawmidi_new(ensoniq->card, "ES1370/1", device, 1, 1, &rmidi)) < 0)
+		return err;
+#ifdef CHIP1370
+	strcpy(rmidi->name, "ES1370");
+#else
+	strcpy(rmidi->name, "ES1371");
+#endif
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_ensoniq_midi_output);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_ensoniq_midi_input);
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT |
+		SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = ensoniq;
+	ensoniq->rmidi = rmidi;
+	if (rrawmidi)
+		*rrawmidi = rmidi;
+	return 0;
+}
+
+/*
+ *  Interrupt handler
+ */
+
+static irqreturn_t snd_audiopci_interrupt(int irq, void *dev_id)
+{
+	struct ensoniq *ensoniq = dev_id;
+	unsigned int status, sctrl;
+
+	if (ensoniq == NULL)
+		return IRQ_NONE;
+
+	status = inl(ES_REG(ensoniq, STATUS));
+	if (!(status & ES_INTR))
+		return IRQ_NONE;
+
+	spin_lock(&ensoniq->reg_lock);
+	sctrl = ensoniq->sctrl;
+	if (status & ES_DAC1)
+		sctrl &= ~ES_P1_INT_EN;
+	if (status & ES_DAC2)
+		sctrl &= ~ES_P2_INT_EN;
+	if (status & ES_ADC)
+		sctrl &= ~ES_R1_INT_EN;
+	outl(sctrl, ES_REG(ensoniq, SERIAL));
+	outl(ensoniq->sctrl, ES_REG(ensoniq, SERIAL));
+	spin_unlock(&ensoniq->reg_lock);
+
+	if (status & ES_UART)
+		snd_ensoniq_midi_interrupt(ensoniq);
+	if ((status & ES_DAC2) && ensoniq->playback2_substream)
+		snd_pcm_period_elapsed(ensoniq->playback2_substream);
+	if ((status & ES_ADC) && ensoniq->capture_substream)
+		snd_pcm_period_elapsed(ensoniq->capture_substream);
+	if ((status & ES_DAC1) && ensoniq->playback1_substream)
+		snd_pcm_period_elapsed(ensoniq->playback1_substream);
+	return IRQ_HANDLED;
+}
+
+static int __devinit snd_audiopci_probe(struct pci_dev *pci,
+					const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct ensoniq *ensoniq;
+	int err, pcm_devs[2];
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	if ((err = snd_ensoniq_create(card, pci, &ensoniq)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = ensoniq;
+
+	pcm_devs[0] = 0; pcm_devs[1] = 1;
+#ifdef CHIP1370
+	if ((err = snd_ensoniq_1370_mixer(ensoniq)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+#endif
+#ifdef CHIP1371
+	if ((err = snd_ensoniq_1371_mixer(ensoniq, spdif[dev], lineio[dev])) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+#endif
+	if ((err = snd_ensoniq_pcm(ensoniq, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_ensoniq_pcm2(ensoniq, 1, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_ensoniq_midi(ensoniq, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	snd_ensoniq_create_gameport(ensoniq, dev);
+
+	strcpy(card->driver, DRIVER_NAME);
+
+	strcpy(card->shortname, "Ensoniq AudioPCI");
+	sprintf(card->longname, "%s %s at 0x%lx, irq %i",
+		card->shortname,
+		card->driver,
+		ensoniq->port,
+		ensoniq->irq);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_audiopci_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = DRIVER_NAME,
+	.id_table = snd_audiopci_ids,
+	.probe = snd_audiopci_probe,
+	.remove = __devexit_p(snd_audiopci_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_ensoniq_suspend,
+	.resume = snd_ensoniq_resume,
+#endif
+};
+	
+static int __init alsa_card_ens137x_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_ens137x_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_ens137x_init)
+module_exit(alsa_card_ens137x_exit)
diff --git a/linux/sound/pci/ens1371.c b/linux/sound/pci/ens1371.c
new file mode 100644
index 0000000..ca0da0a
--- /dev/null
+++ b/linux/sound/pci/ens1371.c
@@ -0,0 +1,2 @@
+#define CHIP1371
+#include "ens1370.c"
diff --git a/linux/sound/pci/es1938.c b/linux/sound/pci/es1938.c
new file mode 100644
index 0000000..553b752
--- /dev/null
+++ b/linux/sound/pci/es1938.c
@@ -0,0 +1,1906 @@
+/*
+ *  Driver for ESS Solo-1 (ES1938, ES1946, ES1969) soundcard
+ *  Copyright (c) by Jaromir Koutek <miri@punknet.cz>,
+ *                   Jaroslav Kysela <perex@perex.cz>,
+ *                   Thomas Sailer <sailer@ife.ee.ethz.ch>,
+ *                   Abramo Bagnara <abramo@alsa-project.org>,
+ *                   Markus Gruber <gruber@eikon.tum.de>
+ * 
+ * Rewritten from sonicvibes.c source.
+ *
+ *  TODO:
+ *    Rewrite better spinlocks
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+  NOTES:
+  - Capture data is written unaligned starting from dma_base + 1 so I need to
+    disable mmap and to add a copy callback.
+  - After several cycle of the following:
+    while : ; do arecord -d1 -f cd -t raw | aplay -f cd ; done
+    a "playback write error (DMA or IRQ trouble?)" may happen.
+    This is due to playback interrupts not generated.
+    I suspect a timing issue.
+  - Sometimes the interrupt handler is invoked wrongly during playback.
+    This generates some harmless "Unexpected hw_pointer: wrong interrupt
+    acknowledge".
+    I've seen that using small period sizes.
+    Reproducible with:
+    mpg123 test.mp3 &
+    hdparm -t -T /dev/hda
+*/
+
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/moduleparam.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/opl3.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <asm/io.h>
+
+MODULE_AUTHOR("Jaromir Koutek <miri@punknet.cz>");
+MODULE_DESCRIPTION("ESS Solo-1");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,ES1938},"
+                "{ESS,ES1946},"
+                "{ESS,ES1969},"
+		"{TerraTec,128i PCI}}");
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+#define SUPPORT_JOYSTICK 1
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ESS Solo-1 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ESS Solo-1 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ESS Solo-1 soundcard.");
+
+#define SLIO_REG(chip, x) ((chip)->io_port + ESSIO_REG_##x)
+
+#define SLDM_REG(chip, x) ((chip)->ddma_port + ESSDM_REG_##x)
+
+#define SLSB_REG(chip, x) ((chip)->sb_port + ESSSB_REG_##x)
+
+#define SL_PCI_LEGACYCONTROL		0x40
+#define SL_PCI_CONFIG			0x50
+#define SL_PCI_DDMACONTROL		0x60
+
+#define ESSIO_REG_AUDIO2DMAADDR		0
+#define ESSIO_REG_AUDIO2DMACOUNT	4
+#define ESSIO_REG_AUDIO2MODE		6
+#define ESSIO_REG_IRQCONTROL		7
+
+#define ESSDM_REG_DMAADDR		0x00
+#define ESSDM_REG_DMACOUNT		0x04
+#define ESSDM_REG_DMACOMMAND		0x08
+#define ESSDM_REG_DMASTATUS		0x08
+#define ESSDM_REG_DMAMODE		0x0b
+#define ESSDM_REG_DMACLEAR		0x0d
+#define ESSDM_REG_DMAMASK		0x0f
+
+#define ESSSB_REG_FMLOWADDR		0x00
+#define ESSSB_REG_FMHIGHADDR		0x02
+#define ESSSB_REG_MIXERADDR		0x04
+#define ESSSB_REG_MIXERDATA		0x05
+
+#define ESSSB_IREG_AUDIO1		0x14
+#define ESSSB_IREG_MICMIX		0x1a
+#define ESSSB_IREG_RECSRC		0x1c
+#define ESSSB_IREG_MASTER		0x32
+#define ESSSB_IREG_FM			0x36
+#define ESSSB_IREG_AUXACD		0x38
+#define ESSSB_IREG_AUXB			0x3a
+#define ESSSB_IREG_PCSPEAKER		0x3c
+#define ESSSB_IREG_LINE			0x3e
+#define ESSSB_IREG_SPATCONTROL		0x50
+#define ESSSB_IREG_SPATLEVEL		0x52
+#define ESSSB_IREG_MASTER_LEFT		0x60
+#define ESSSB_IREG_MASTER_RIGHT		0x62
+#define ESSSB_IREG_MPU401CONTROL	0x64
+#define ESSSB_IREG_MICMIXRECORD		0x68
+#define ESSSB_IREG_AUDIO2RECORD		0x69
+#define ESSSB_IREG_AUXACDRECORD		0x6a
+#define ESSSB_IREG_FMRECORD		0x6b
+#define ESSSB_IREG_AUXBRECORD		0x6c
+#define ESSSB_IREG_MONO			0x6d
+#define ESSSB_IREG_LINERECORD		0x6e
+#define ESSSB_IREG_MONORECORD		0x6f
+#define ESSSB_IREG_AUDIO2SAMPLE		0x70
+#define ESSSB_IREG_AUDIO2MODE		0x71
+#define ESSSB_IREG_AUDIO2FILTER		0x72
+#define ESSSB_IREG_AUDIO2TCOUNTL	0x74
+#define ESSSB_IREG_AUDIO2TCOUNTH	0x76
+#define ESSSB_IREG_AUDIO2CONTROL1	0x78
+#define ESSSB_IREG_AUDIO2CONTROL2	0x7a
+#define ESSSB_IREG_AUDIO2		0x7c
+
+#define ESSSB_REG_RESET			0x06
+
+#define ESSSB_REG_READDATA		0x0a
+#define ESSSB_REG_WRITEDATA		0x0c
+#define ESSSB_REG_READSTATUS		0x0c
+
+#define ESSSB_REG_STATUS		0x0e
+
+#define ESS_CMD_EXTSAMPLERATE		0xa1
+#define ESS_CMD_FILTERDIV		0xa2
+#define ESS_CMD_DMACNTRELOADL		0xa4
+#define ESS_CMD_DMACNTRELOADH		0xa5
+#define ESS_CMD_ANALOGCONTROL		0xa8
+#define ESS_CMD_IRQCONTROL		0xb1
+#define ESS_CMD_DRQCONTROL		0xb2
+#define ESS_CMD_RECLEVEL		0xb4
+#define ESS_CMD_SETFORMAT		0xb6
+#define ESS_CMD_SETFORMAT2		0xb7
+#define ESS_CMD_DMACONTROL		0xb8
+#define ESS_CMD_DMATYPE			0xb9
+#define ESS_CMD_OFFSETLEFT		0xba	
+#define ESS_CMD_OFFSETRIGHT		0xbb
+#define ESS_CMD_READREG			0xc0
+#define ESS_CMD_ENABLEEXT		0xc6
+#define ESS_CMD_PAUSEDMA		0xd0
+#define ESS_CMD_ENABLEAUDIO1		0xd1
+#define ESS_CMD_STOPAUDIO1		0xd3
+#define ESS_CMD_AUDIO1STATUS		0xd8
+#define ESS_CMD_CONTDMA			0xd4
+#define ESS_CMD_TESTIRQ			0xf2
+
+#define ESS_RECSRC_MIC		0
+#define ESS_RECSRC_AUXACD	2
+#define ESS_RECSRC_AUXB		5
+#define ESS_RECSRC_LINE		6
+#define ESS_RECSRC_NONE		7
+
+#define DAC1 0x01
+#define ADC1 0x02
+#define DAC2 0x04
+
+/*
+
+ */
+
+#define SAVED_REG_SIZE	32 /* max. number of registers to save */
+
+struct es1938 {
+	int irq;
+
+	unsigned long io_port;
+	unsigned long sb_port;
+	unsigned long vc_port;
+	unsigned long mpu_port;
+	unsigned long game_port;
+	unsigned long ddma_port;
+
+	unsigned char irqmask;
+	unsigned char revision;
+
+	struct snd_kcontrol *hw_volume;
+	struct snd_kcontrol *hw_switch;
+	struct snd_kcontrol *master_volume;
+	struct snd_kcontrol *master_switch;
+
+	struct pci_dev *pci;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *capture_substream;
+	struct snd_pcm_substream *playback1_substream;
+	struct snd_pcm_substream *playback2_substream;
+	struct snd_rawmidi *rmidi;
+
+	unsigned int dma1_size;
+	unsigned int dma2_size;
+	unsigned int dma1_start;
+	unsigned int dma2_start;
+	unsigned int dma1_shift;
+	unsigned int dma2_shift;
+	unsigned int last_capture_dmaaddr;
+	unsigned int active;
+
+	spinlock_t reg_lock;
+	spinlock_t mixer_lock;
+        struct snd_info_entry *proc_entry;
+
+#ifdef SUPPORT_JOYSTICK
+	struct gameport *gameport;
+#endif
+#ifdef CONFIG_PM
+	unsigned char saved_regs[SAVED_REG_SIZE];
+#endif
+};
+
+static irqreturn_t snd_es1938_interrupt(int irq, void *dev_id);
+
+static DEFINE_PCI_DEVICE_TABLE(snd_es1938_ids) = {
+	{ PCI_VDEVICE(ESS, 0x1969), 0, },   /* Solo-1 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_es1938_ids);
+
+#define RESET_LOOP_TIMEOUT	0x10000
+#define WRITE_LOOP_TIMEOUT	0x10000
+#define GET_LOOP_TIMEOUT	0x01000
+
+#undef REG_DEBUG
+/* -----------------------------------------------------------------
+ * Write to a mixer register
+ * -----------------------------------------------------------------*/
+static void snd_es1938_mixer_write(struct es1938 *chip, unsigned char reg, unsigned char val)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	outb(reg, SLSB_REG(chip, MIXERADDR));
+	outb(val, SLSB_REG(chip, MIXERDATA));
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk(KERN_DEBUG "Mixer reg %02x set to %02x\n", reg, val);
+#endif
+}
+
+/* -----------------------------------------------------------------
+ * Read from a mixer register
+ * -----------------------------------------------------------------*/
+static int snd_es1938_mixer_read(struct es1938 *chip, unsigned char reg)
+{
+	int data;
+	unsigned long flags;
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	outb(reg, SLSB_REG(chip, MIXERADDR));
+	data = inb(SLSB_REG(chip, MIXERDATA));
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk(KERN_DEBUG "Mixer reg %02x now is %02x\n", reg, data);
+#endif
+	return data;
+}
+
+/* -----------------------------------------------------------------
+ * Write to some bits of a mixer register (return old value)
+ * -----------------------------------------------------------------*/
+static int snd_es1938_mixer_bits(struct es1938 *chip, unsigned char reg,
+				 unsigned char mask, unsigned char val)
+{
+	unsigned long flags;
+	unsigned char old, new, oval;
+	spin_lock_irqsave(&chip->mixer_lock, flags);
+	outb(reg, SLSB_REG(chip, MIXERADDR));
+	old = inb(SLSB_REG(chip, MIXERDATA));
+	oval = old & mask;
+	if (val != oval) {
+		new = (old & ~mask) | (val & mask);
+		outb(new, SLSB_REG(chip, MIXERDATA));
+#ifdef REG_DEBUG
+		snd_printk(KERN_DEBUG "Mixer reg %02x was %02x, set to %02x\n",
+			   reg, old, new);
+#endif
+	}
+	spin_unlock_irqrestore(&chip->mixer_lock, flags);
+	return oval;
+}
+
+/* -----------------------------------------------------------------
+ * Write command to Controller Registers
+ * -----------------------------------------------------------------*/
+static void snd_es1938_write_cmd(struct es1938 *chip, unsigned char cmd)
+{
+	int i;
+	unsigned char v;
+	for (i = 0; i < WRITE_LOOP_TIMEOUT; i++) {
+		if (!(v = inb(SLSB_REG(chip, READSTATUS)) & 0x80)) {
+			outb(cmd, SLSB_REG(chip, WRITEDATA));
+			return;
+		}
+	}
+	printk(KERN_ERR "snd_es1938_write_cmd timeout (0x02%x/0x02%x)\n", cmd, v);
+}
+
+/* -----------------------------------------------------------------
+ * Read the Read Data Buffer
+ * -----------------------------------------------------------------*/
+static int snd_es1938_get_byte(struct es1938 *chip)
+{
+	int i;
+	unsigned char v;
+	for (i = GET_LOOP_TIMEOUT; i; i--)
+		if ((v = inb(SLSB_REG(chip, STATUS))) & 0x80)
+			return inb(SLSB_REG(chip, READDATA));
+	snd_printk(KERN_ERR "get_byte timeout: status 0x02%x\n", v);
+	return -ENODEV;
+}
+
+/* -----------------------------------------------------------------
+ * Write value cmd register
+ * -----------------------------------------------------------------*/
+static void snd_es1938_write(struct es1938 *chip, unsigned char reg, unsigned char val)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1938_write_cmd(chip, reg);
+	snd_es1938_write_cmd(chip, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk(KERN_DEBUG "Reg %02x set to %02x\n", reg, val);
+#endif
+}
+
+/* -----------------------------------------------------------------
+ * Read data from cmd register and return it
+ * -----------------------------------------------------------------*/
+static unsigned char snd_es1938_read(struct es1938 *chip, unsigned char reg)
+{
+	unsigned char val;
+	unsigned long flags;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1938_write_cmd(chip, ESS_CMD_READREG);
+	snd_es1938_write_cmd(chip, reg);
+	val = snd_es1938_get_byte(chip);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+#ifdef REG_DEBUG
+	snd_printk(KERN_DEBUG "Reg %02x now is %02x\n", reg, val);
+#endif
+	return val;
+}
+
+/* -----------------------------------------------------------------
+ * Write data to cmd register and return old value
+ * -----------------------------------------------------------------*/
+static int snd_es1938_bits(struct es1938 *chip, unsigned char reg, unsigned char mask,
+			   unsigned char val)
+{
+	unsigned long flags;
+	unsigned char old, new, oval;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_es1938_write_cmd(chip, ESS_CMD_READREG);
+	snd_es1938_write_cmd(chip, reg);
+	old = snd_es1938_get_byte(chip);
+	oval = old & mask;
+	if (val != oval) {
+		snd_es1938_write_cmd(chip, reg);
+		new = (old & ~mask) | (val & mask);
+		snd_es1938_write_cmd(chip, new);
+#ifdef REG_DEBUG
+		snd_printk(KERN_DEBUG "Reg %02x was %02x, set to %02x\n",
+			   reg, old, new);
+#endif
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return oval;
+}
+
+/* --------------------------------------------------------------------
+ * Reset the chip
+ * --------------------------------------------------------------------*/
+static void snd_es1938_reset(struct es1938 *chip)
+{
+	int i;
+
+	outb(3, SLSB_REG(chip, RESET));
+	inb(SLSB_REG(chip, RESET));
+	outb(0, SLSB_REG(chip, RESET));
+	for (i = 0; i < RESET_LOOP_TIMEOUT; i++) {
+		if (inb(SLSB_REG(chip, STATUS)) & 0x80) {
+			if (inb(SLSB_REG(chip, READDATA)) == 0xaa)
+				goto __next;
+		}
+	}
+	snd_printk(KERN_ERR "ESS Solo-1 reset failed\n");
+
+     __next:
+	snd_es1938_write_cmd(chip, ESS_CMD_ENABLEEXT);
+
+	/* Demand transfer DMA: 4 bytes per DMA request */
+	snd_es1938_write(chip, ESS_CMD_DMATYPE, 2);
+
+	/* Change behaviour of register A1
+	   4x oversampling
+	   2nd channel DAC asynchronous */                                                      
+	snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2MODE, 0x32);
+	/* enable/select DMA channel and IRQ channel */
+	snd_es1938_bits(chip, ESS_CMD_IRQCONTROL, 0xf0, 0x50);
+	snd_es1938_bits(chip, ESS_CMD_DRQCONTROL, 0xf0, 0x50);
+	snd_es1938_write_cmd(chip, ESS_CMD_ENABLEAUDIO1);
+	/* Set spatializer parameters to recommended values */
+	snd_es1938_mixer_write(chip, 0x54, 0x8f);
+	snd_es1938_mixer_write(chip, 0x56, 0x95);
+	snd_es1938_mixer_write(chip, 0x58, 0x94);
+	snd_es1938_mixer_write(chip, 0x5a, 0x80);
+}
+
+/* --------------------------------------------------------------------
+ * Reset the FIFOs
+ * --------------------------------------------------------------------*/
+static void snd_es1938_reset_fifo(struct es1938 *chip)
+{
+	outb(2, SLSB_REG(chip, RESET));
+	outb(0, SLSB_REG(chip, RESET));
+}
+
+static struct snd_ratnum clocks[2] = {
+	{
+		.num = 793800,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	},
+	{
+		.num = 768000,
+		.den_min = 1,
+		.den_max = 128,
+		.den_step = 1,
+	}
+};
+
+static struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = {
+	.nrats = 2,
+	.rats = clocks,
+};
+
+
+static void snd_es1938_rate_set(struct es1938 *chip, 
+				struct snd_pcm_substream *substream,
+				int mode)
+{
+	unsigned int bits, div0;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	if (runtime->rate_num == clocks[0].num)
+		bits = 128 - runtime->rate_den;
+	else
+		bits = 256 - runtime->rate_den;
+
+	/* set filter register */
+	div0 = 256 - 7160000*20/(8*82*runtime->rate);
+		
+	if (mode == DAC2) {
+		snd_es1938_mixer_write(chip, 0x70, bits);
+		snd_es1938_mixer_write(chip, 0x72, div0);
+	} else {
+		snd_es1938_write(chip, 0xA1, bits);
+		snd_es1938_write(chip, 0xA2, div0);
+	}
+}
+
+/* --------------------------------------------------------------------
+ * Configure Solo1 builtin DMA Controller
+ * --------------------------------------------------------------------*/
+
+static void snd_es1938_playback1_setdma(struct es1938 *chip)
+{
+	outb(0x00, SLIO_REG(chip, AUDIO2MODE));
+	outl(chip->dma2_start, SLIO_REG(chip, AUDIO2DMAADDR));
+	outw(0, SLIO_REG(chip, AUDIO2DMACOUNT));
+	outw(chip->dma2_size, SLIO_REG(chip, AUDIO2DMACOUNT));
+}
+
+static void snd_es1938_playback2_setdma(struct es1938 *chip)
+{
+	/* Enable DMA controller */
+	outb(0xc4, SLDM_REG(chip, DMACOMMAND));
+	/* 1. Master reset */
+	outb(0, SLDM_REG(chip, DMACLEAR));
+	/* 2. Mask DMA */
+	outb(1, SLDM_REG(chip, DMAMASK));
+	outb(0x18, SLDM_REG(chip, DMAMODE));
+	outl(chip->dma1_start, SLDM_REG(chip, DMAADDR));
+	outw(chip->dma1_size - 1, SLDM_REG(chip, DMACOUNT));
+	/* 3. Unmask DMA */
+	outb(0, SLDM_REG(chip, DMAMASK));
+}
+
+static void snd_es1938_capture_setdma(struct es1938 *chip)
+{
+	/* Enable DMA controller */
+	outb(0xc4, SLDM_REG(chip, DMACOMMAND));
+	/* 1. Master reset */
+	outb(0, SLDM_REG(chip, DMACLEAR));
+	/* 2. Mask DMA */
+	outb(1, SLDM_REG(chip, DMAMASK));
+	outb(0x14, SLDM_REG(chip, DMAMODE));
+	outl(chip->dma1_start, SLDM_REG(chip, DMAADDR));
+	chip->last_capture_dmaaddr = chip->dma1_start;
+	outw(chip->dma1_size - 1, SLDM_REG(chip, DMACOUNT));
+	/* 3. Unmask DMA */
+	outb(0, SLDM_REG(chip, DMAMASK));
+}
+
+/* ----------------------------------------------------------------------
+ *
+ *                           *** PCM part ***
+ */
+
+static int snd_es1938_capture_trigger(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	int val;
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		val = 0x0f;
+		chip->active |= ADC1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		val = 0x00;
+		chip->active &= ~ADC1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_es1938_write(chip, ESS_CMD_DMACONTROL, val);
+	return 0;
+}
+
+static int snd_es1938_playback1_trigger(struct snd_pcm_substream *substream,
+					int cmd)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		/* According to the documentation this should be:
+		   0x13 but that value may randomly swap stereo channels */
+                snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2CONTROL1, 0x92);
+                udelay(10);
+		snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2CONTROL1, 0x93);
+                /* This two stage init gives the FIFO -> DAC connection time to
+                 * settle before first data from DMA flows in.  This should ensure
+                 * no swapping of stereo channels.  Report a bug if otherwise :-) */
+		outb(0x0a, SLIO_REG(chip, AUDIO2MODE));
+		chip->active |= DAC2;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		outb(0, SLIO_REG(chip, AUDIO2MODE));
+		snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2CONTROL1, 0);
+		chip->active &= ~DAC2;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int snd_es1938_playback2_trigger(struct snd_pcm_substream *substream,
+					int cmd)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	int val;
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		val = 5;
+		chip->active |= DAC1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		val = 0;
+		chip->active &= ~DAC1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_es1938_write(chip, ESS_CMD_DMACONTROL, val);
+	return 0;
+}
+
+static int snd_es1938_playback_trigger(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	switch (substream->number) {
+	case 0:
+		return snd_es1938_playback1_trigger(substream, cmd);
+	case 1:
+		return snd_es1938_playback2_trigger(substream, cmd);
+	}
+	snd_BUG();
+	return -EINVAL;
+}
+
+/* --------------------------------------------------------------------
+ * First channel for Extended Mode Audio 1 ADC Operation
+ * --------------------------------------------------------------------*/
+static int snd_es1938_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int u, is8, mono;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma1_size = size;
+	chip->dma1_start = runtime->dma_addr;
+
+	mono = (runtime->channels > 1) ? 0 : 1;
+	is8 = snd_pcm_format_width(runtime->format) == 16 ? 0 : 1;
+	u = snd_pcm_format_unsigned(runtime->format);
+
+	chip->dma1_shift = 2 - mono - is8;
+
+	snd_es1938_reset_fifo(chip);
+	
+	/* program type */
+	snd_es1938_bits(chip, ESS_CMD_ANALOGCONTROL, 0x03, (mono ? 2 : 1));
+
+	/* set clock and counters */
+        snd_es1938_rate_set(chip, substream, ADC1);
+
+	count = 0x10000 - count;
+	snd_es1938_write(chip, ESS_CMD_DMACNTRELOADL, count & 0xff);
+	snd_es1938_write(chip, ESS_CMD_DMACNTRELOADH, count >> 8);
+
+	/* initialize and configure ADC */
+	snd_es1938_write(chip, ESS_CMD_SETFORMAT2, u ? 0x51 : 0x71);
+	snd_es1938_write(chip, ESS_CMD_SETFORMAT2, 0x90 | 
+		       (u ? 0x00 : 0x20) | 
+		       (is8 ? 0x00 : 0x04) | 
+		       (mono ? 0x40 : 0x08));
+
+	//	snd_es1938_reset_fifo(chip);	
+
+	/* 11. configure system interrupt controller and DMA controller */
+	snd_es1938_capture_setdma(chip);
+
+	return 0;
+}
+
+
+/* ------------------------------------------------------------------------------
+ * Second Audio channel DAC Operation
+ * ------------------------------------------------------------------------------*/
+static int snd_es1938_playback1_prepare(struct snd_pcm_substream *substream)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int u, is8, mono;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma2_size = size;
+	chip->dma2_start = runtime->dma_addr;
+
+	mono = (runtime->channels > 1) ? 0 : 1;
+	is8 = snd_pcm_format_width(runtime->format) == 16 ? 0 : 1;
+	u = snd_pcm_format_unsigned(runtime->format);
+
+	chip->dma2_shift = 2 - mono - is8;
+
+        snd_es1938_reset_fifo(chip);
+
+	/* set clock and counters */
+        snd_es1938_rate_set(chip, substream, DAC2);
+
+	count >>= 1;
+	count = 0x10000 - count;
+	snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2TCOUNTL, count & 0xff);
+	snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2TCOUNTH, count >> 8);
+
+	/* initialize and configure Audio 2 DAC */
+	snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2CONTROL2, 0x40 | (u ? 0 : 4) |
+			       (mono ? 0 : 2) | (is8 ? 0 : 1));
+
+	/* program DMA */
+	snd_es1938_playback1_setdma(chip);
+	
+	return 0;
+}
+
+static int snd_es1938_playback2_prepare(struct snd_pcm_substream *substream)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int u, is8, mono;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	chip->dma1_size = size;
+	chip->dma1_start = runtime->dma_addr;
+
+	mono = (runtime->channels > 1) ? 0 : 1;
+	is8 = snd_pcm_format_width(runtime->format) == 16 ? 0 : 1;
+	u = snd_pcm_format_unsigned(runtime->format);
+
+	chip->dma1_shift = 2 - mono - is8;
+
+	count = 0x10000 - count;
+ 
+	/* reset */
+	snd_es1938_reset_fifo(chip);
+	
+	snd_es1938_bits(chip, ESS_CMD_ANALOGCONTROL, 0x03, (mono ? 2 : 1));
+
+	/* set clock and counters */
+        snd_es1938_rate_set(chip, substream, DAC1);
+	snd_es1938_write(chip, ESS_CMD_DMACNTRELOADL, count & 0xff);
+	snd_es1938_write(chip, ESS_CMD_DMACNTRELOADH, count >> 8);
+
+	/* initialized and configure DAC */
+        snd_es1938_write(chip, ESS_CMD_SETFORMAT, u ? 0x80 : 0x00);
+        snd_es1938_write(chip, ESS_CMD_SETFORMAT, u ? 0x51 : 0x71);
+        snd_es1938_write(chip, ESS_CMD_SETFORMAT2, 
+			 0x90 | (mono ? 0x40 : 0x08) |
+			 (is8 ? 0x00 : 0x04) | (u ? 0x00 : 0x20));
+
+	/* program DMA */
+	snd_es1938_playback2_setdma(chip);
+	
+	return 0;
+}
+
+static int snd_es1938_playback_prepare(struct snd_pcm_substream *substream)
+{
+	switch (substream->number) {
+	case 0:
+		return snd_es1938_playback1_prepare(substream);
+	case 1:
+		return snd_es1938_playback2_prepare(substream);
+	}
+	snd_BUG();
+	return -EINVAL;
+}
+
+/* during the incrementing of dma counters the DMA register reads sometimes
+   returns garbage. To ensure a valid hw pointer, the following checks which
+   should be very unlikely to fail are used:
+   - is the current DMA address in the valid DMA range ?
+   - is the sum of DMA address and DMA counter pointing to the last DMA byte ?
+   One can argue this could differ by one byte depending on which register is
+   updated first, so the implementation below allows for that.
+*/
+static snd_pcm_uframes_t snd_es1938_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+#if 0
+	size_t old, new;
+	/* This stuff is *needed*, don't ask why - AB */
+	old = inw(SLDM_REG(chip, DMACOUNT));
+	while ((new = inw(SLDM_REG(chip, DMACOUNT))) != old)
+		old = new;
+	ptr = chip->dma1_size - 1 - new;
+#else
+	size_t count;
+	unsigned int diff;
+
+	ptr = inl(SLDM_REG(chip, DMAADDR));
+	count = inw(SLDM_REG(chip, DMACOUNT));
+	diff = chip->dma1_start + chip->dma1_size - ptr - count;
+
+	if (diff > 3 || ptr < chip->dma1_start
+	      || ptr >= chip->dma1_start+chip->dma1_size)
+	  ptr = chip->last_capture_dmaaddr;            /* bad, use last saved */
+	else
+	  chip->last_capture_dmaaddr = ptr;            /* good, remember it */
+
+	ptr -= chip->dma1_start;
+#endif
+	return ptr >> chip->dma1_shift;
+}
+
+static snd_pcm_uframes_t snd_es1938_playback1_pointer(struct snd_pcm_substream *substream)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+#if 1
+	ptr = chip->dma2_size - inw(SLIO_REG(chip, AUDIO2DMACOUNT));
+#else
+	ptr = inl(SLIO_REG(chip, AUDIO2DMAADDR)) - chip->dma2_start;
+#endif
+	return ptr >> chip->dma2_shift;
+}
+
+static snd_pcm_uframes_t snd_es1938_playback2_pointer(struct snd_pcm_substream *substream)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	size_t old, new;
+#if 1
+	/* This stuff is *needed*, don't ask why - AB */
+	old = inw(SLDM_REG(chip, DMACOUNT));
+	while ((new = inw(SLDM_REG(chip, DMACOUNT))) != old)
+		old = new;
+	ptr = chip->dma1_size - 1 - new;
+#else
+	ptr = inl(SLDM_REG(chip, DMAADDR)) - chip->dma1_start;
+#endif
+	return ptr >> chip->dma1_shift;
+}
+
+static snd_pcm_uframes_t snd_es1938_playback_pointer(struct snd_pcm_substream *substream)
+{
+	switch (substream->number) {
+	case 0:
+		return snd_es1938_playback1_pointer(substream);
+	case 1:
+		return snd_es1938_playback2_pointer(substream);
+	}
+	snd_BUG();
+	return -EINVAL;
+}
+
+static int snd_es1938_capture_copy(struct snd_pcm_substream *substream,
+				   int channel,
+				   snd_pcm_uframes_t pos,
+				   void __user *dst,
+				   snd_pcm_uframes_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	pos <<= chip->dma1_shift;
+	count <<= chip->dma1_shift;
+	if (snd_BUG_ON(pos + count > chip->dma1_size))
+		return -EINVAL;
+	if (pos + count < chip->dma1_size) {
+		if (copy_to_user(dst, runtime->dma_area + pos + 1, count))
+			return -EFAULT;
+	} else {
+		if (copy_to_user(dst, runtime->dma_area + pos + 1, count - 1))
+			return -EFAULT;
+		if (put_user(runtime->dma_area[0], ((unsigned char __user *)dst) + count - 1))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+/*
+ * buffer management
+ */
+static int snd_es1938_pcm_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *hw_params)
+
+{
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	return 0;
+}
+
+static int snd_es1938_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+/* ----------------------------------------------------------------------
+ * Audio1 Capture (ADC)
+ * ----------------------------------------------------------------------*/
+static struct snd_pcm_hardware snd_es1938_capture =
+{
+	.info =			(SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_BLOCK_TRANSFER),
+	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+				 SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		6000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+        .buffer_bytes_max =	0x8000,       /* DMA controller screws on higher values */
+	.period_bytes_min =	64,
+	.period_bytes_max =	0x8000,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		256,
+};
+
+/* -----------------------------------------------------------------------
+ * Audio2 Playback (DAC)
+ * -----------------------------------------------------------------------*/
+static struct snd_pcm_hardware snd_es1938_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+				 SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		6000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+        .buffer_bytes_max =	0x8000,       /* DMA controller screws on higher values */
+	.period_bytes_min =	64,
+	.period_bytes_max =	0x8000,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		256,
+};
+
+static int snd_es1938_capture_open(struct snd_pcm_substream *substream)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (chip->playback2_substream)
+		return -EAGAIN;
+	chip->capture_substream = substream;
+	runtime->hw = snd_es1938_capture;
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &hw_constraints_clocks);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 0, 0xff00);
+	return 0;
+}
+
+static int snd_es1938_playback_open(struct snd_pcm_substream *substream)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	switch (substream->number) {
+	case 0:
+		chip->playback1_substream = substream;
+		break;
+	case 1:
+		if (chip->capture_substream)
+			return -EAGAIN;
+		chip->playback2_substream = substream;
+		break;
+	default:
+		snd_BUG();
+		return -EINVAL;
+	}
+	runtime->hw = snd_es1938_playback;
+	snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &hw_constraints_clocks);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 0, 0xff00);
+	return 0;
+}
+
+static int snd_es1938_capture_close(struct snd_pcm_substream *substream)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	return 0;
+}
+
+static int snd_es1938_playback_close(struct snd_pcm_substream *substream)
+{
+	struct es1938 *chip = snd_pcm_substream_chip(substream);
+
+	switch (substream->number) {
+	case 0:
+		chip->playback1_substream = NULL;
+		break;
+	case 1:
+		chip->playback2_substream = NULL;
+		break;
+	default:
+		snd_BUG();
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct snd_pcm_ops snd_es1938_playback_ops = {
+	.open =		snd_es1938_playback_open,
+	.close =	snd_es1938_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_es1938_pcm_hw_params,
+	.hw_free =	snd_es1938_pcm_hw_free,
+	.prepare =	snd_es1938_playback_prepare,
+	.trigger =	snd_es1938_playback_trigger,
+	.pointer =	snd_es1938_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_es1938_capture_ops = {
+	.open =		snd_es1938_capture_open,
+	.close =	snd_es1938_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_es1938_pcm_hw_params,
+	.hw_free =	snd_es1938_pcm_hw_free,
+	.prepare =	snd_es1938_capture_prepare,
+	.trigger =	snd_es1938_capture_trigger,
+	.pointer =	snd_es1938_capture_pointer,
+	.copy =		snd_es1938_capture_copy,
+};
+
+static int __devinit snd_es1938_new_pcm(struct es1938 *chip, int device)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(chip->card, "es-1938-1946", device, 2, 1, &pcm)) < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es1938_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es1938_capture_ops);
+	
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "ESS Solo-1");
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci), 64*1024, 64*1024);
+
+	chip->pcm = pcm;
+	return 0;
+}
+
+/* -------------------------------------------------------------------
+ * 
+ *                       *** Mixer part ***
+ */
+
+static int snd_es1938_info_mux(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[8] = {
+		"Mic", "Mic Master", "CD", "AOUT",
+		"Mic1", "Mix", "Line", "Master"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 8;
+	if (uinfo->value.enumerated.item > 7)
+		uinfo->value.enumerated.item = 7;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_es1938_get_mux(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct es1938 *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = snd_es1938_mixer_read(chip, 0x1c) & 0x07;
+	return 0;
+}
+
+static int snd_es1938_put_mux(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct es1938 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char val = ucontrol->value.enumerated.item[0];
+	
+	if (val > 7)
+		return -EINVAL;
+	return snd_es1938_mixer_bits(chip, 0x1c, 0x07, val) != val;
+}
+
+#define snd_es1938_info_spatializer_enable	snd_ctl_boolean_mono_info
+
+static int snd_es1938_get_spatializer_enable(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_value *ucontrol)
+{
+	struct es1938 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char val = snd_es1938_mixer_read(chip, 0x50);
+	ucontrol->value.integer.value[0] = !!(val & 8);
+	return 0;
+}
+
+static int snd_es1938_put_spatializer_enable(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_value *ucontrol)
+{
+	struct es1938 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char oval, nval;
+	int change;
+	nval = ucontrol->value.integer.value[0] ? 0x0c : 0x04;
+	oval = snd_es1938_mixer_read(chip, 0x50) & 0x0c;
+	change = nval != oval;
+	if (change) {
+		snd_es1938_mixer_write(chip, 0x50, nval & ~0x04);
+		snd_es1938_mixer_write(chip, 0x50, nval);
+	}
+	return change;
+}
+
+static int snd_es1938_info_hw_volume(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 63;
+	return 0;
+}
+
+static int snd_es1938_get_hw_volume(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct es1938 *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = snd_es1938_mixer_read(chip, 0x61) & 0x3f;
+	ucontrol->value.integer.value[1] = snd_es1938_mixer_read(chip, 0x63) & 0x3f;
+	return 0;
+}
+
+#define snd_es1938_info_hw_switch		snd_ctl_boolean_stereo_info
+
+static int snd_es1938_get_hw_switch(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct es1938 *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = !(snd_es1938_mixer_read(chip, 0x61) & 0x40);
+	ucontrol->value.integer.value[1] = !(snd_es1938_mixer_read(chip, 0x63) & 0x40);
+	return 0;
+}
+
+static void snd_es1938_hwv_free(struct snd_kcontrol *kcontrol)
+{
+	struct es1938 *chip = snd_kcontrol_chip(kcontrol);
+	chip->master_volume = NULL;
+	chip->master_switch = NULL;
+	chip->hw_volume = NULL;
+	chip->hw_switch = NULL;
+}
+
+static int snd_es1938_reg_bits(struct es1938 *chip, unsigned char reg,
+			       unsigned char mask, unsigned char val)
+{
+	if (reg < 0xa0)
+		return snd_es1938_mixer_bits(chip, reg, mask, val);
+	else
+		return snd_es1938_bits(chip, reg, mask, val);
+}
+
+static int snd_es1938_reg_read(struct es1938 *chip, unsigned char reg)
+{
+	if (reg < 0xa0)
+		return snd_es1938_mixer_read(chip, reg);
+	else
+		return snd_es1938_read(chip, reg);
+}
+
+#define ES1938_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv)    \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,\
+  .name = xname, .index = xindex, \
+  .info = snd_es1938_info_single, \
+  .get = snd_es1938_get_single, .put = snd_es1938_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \
+  .tlv = { .p = xtlv } }
+#define ES1938_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_es1938_info_single, \
+  .get = snd_es1938_get_single, .put = snd_es1938_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_es1938_info_single(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_es1938_get_single(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct es1938 *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int val;
+	
+	val = snd_es1938_reg_read(chip, reg);
+	ucontrol->value.integer.value[0] = (val >> shift) & mask;
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_es1938_put_single(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct es1938 *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	unsigned char val;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	mask <<= shift;
+	val <<= shift;
+	return snd_es1938_reg_bits(chip, reg, mask, val) != val;
+}
+
+#define ES1938_DOUBLE_TLV(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,\
+  .name = xname, .index = xindex, \
+  .info = snd_es1938_info_double, \
+  .get = snd_es1938_get_double, .put = snd_es1938_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22), \
+  .tlv = { .p = xtlv } }
+#define ES1938_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_es1938_info_double, \
+  .get = snd_es1938_get_double, .put = snd_es1938_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+static int snd_es1938_info_double(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_es1938_get_double(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct es1938 *chip = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	unsigned char left, right;
+	
+	left = snd_es1938_reg_read(chip, left_reg);
+	if (left_reg != right_reg)
+		right = snd_es1938_reg_read(chip, right_reg);
+	else
+		right = left;
+	ucontrol->value.integer.value[0] = (left >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (right >> shift_right) & mask;
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_es1938_put_double(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct es1938 *chip = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned char val1, val2, mask1, mask2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	mask1 = mask << shift_left;
+	mask2 = mask << shift_right;
+	if (left_reg != right_reg) {
+		change = 0;
+		if (snd_es1938_reg_bits(chip, left_reg, mask1, val1) != val1)
+			change = 1;
+		if (snd_es1938_reg_bits(chip, right_reg, mask2, val2) != val2)
+			change = 1;
+	} else {
+		change = (snd_es1938_reg_bits(chip, left_reg, mask1 | mask2, 
+					      val1 | val2) != (val1 | val2));
+	}
+	return change;
+}
+
+static unsigned int db_scale_master[] = {
+	TLV_DB_RANGE_HEAD(2),
+	0, 54, TLV_DB_SCALE_ITEM(-3600, 50, 1),
+	54, 63, TLV_DB_SCALE_ITEM(-900, 100, 0),
+};
+
+static unsigned int db_scale_audio1[] = {
+	TLV_DB_RANGE_HEAD(2),
+	0, 8, TLV_DB_SCALE_ITEM(-3300, 300, 1),
+	8, 15, TLV_DB_SCALE_ITEM(-900, 150, 0),
+};
+
+static unsigned int db_scale_audio2[] = {
+	TLV_DB_RANGE_HEAD(2),
+	0, 8, TLV_DB_SCALE_ITEM(-3450, 300, 1),
+	8, 15, TLV_DB_SCALE_ITEM(-1050, 150, 0),
+};
+
+static unsigned int db_scale_mic[] = {
+	TLV_DB_RANGE_HEAD(2),
+	0, 8, TLV_DB_SCALE_ITEM(-2400, 300, 1),
+	8, 15, TLV_DB_SCALE_ITEM(0, 150, 0),
+};
+
+static unsigned int db_scale_line[] = {
+	TLV_DB_RANGE_HEAD(2),
+	0, 8, TLV_DB_SCALE_ITEM(-3150, 300, 1),
+	8, 15, TLV_DB_SCALE_ITEM(-750, 150, 0),
+};
+
+static const DECLARE_TLV_DB_SCALE(db_scale_capture, 0, 150, 0);
+
+static struct snd_kcontrol_new snd_es1938_controls[] = {
+ES1938_DOUBLE_TLV("Master Playback Volume", 0, 0x60, 0x62, 0, 0, 63, 0,
+		  db_scale_master),
+ES1938_DOUBLE("Master Playback Switch", 0, 0x60, 0x62, 6, 6, 1, 1),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Hardware Master Playback Volume",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = snd_es1938_info_hw_volume,
+	.get = snd_es1938_get_hw_volume,
+},
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = (SNDRV_CTL_ELEM_ACCESS_READ |
+		   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.name = "Hardware Master Playback Switch",
+	.info = snd_es1938_info_hw_switch,
+	.get = snd_es1938_get_hw_switch,
+	.tlv = { .p = db_scale_master },
+},
+ES1938_SINGLE("Hardware Volume Split", 0, 0x64, 7, 1, 0),
+ES1938_DOUBLE_TLV("Line Playback Volume", 0, 0x3e, 0x3e, 4, 0, 15, 0,
+		  db_scale_line),
+ES1938_DOUBLE("CD Playback Volume", 0, 0x38, 0x38, 4, 0, 15, 0),
+ES1938_DOUBLE_TLV("FM Playback Volume", 0, 0x36, 0x36, 4, 0, 15, 0,
+		  db_scale_mic),
+ES1938_DOUBLE_TLV("Mono Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0,
+		  db_scale_line),
+ES1938_DOUBLE_TLV("Mic Playback Volume", 0, 0x1a, 0x1a, 4, 0, 15, 0,
+		  db_scale_mic),
+ES1938_DOUBLE_TLV("Aux Playback Volume", 0, 0x3a, 0x3a, 4, 0, 15, 0,
+		  db_scale_line),
+ES1938_DOUBLE_TLV("Capture Volume", 0, 0xb4, 0xb4, 4, 0, 15, 0,
+		  db_scale_capture),
+ES1938_SINGLE("Beep Volume", 0, 0x3c, 0, 7, 0),
+ES1938_SINGLE("Record Monitor", 0, 0xa8, 3, 1, 0),
+ES1938_SINGLE("Capture Switch", 0, 0x1c, 4, 1, 1),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.info = snd_es1938_info_mux,
+	.get = snd_es1938_get_mux,
+	.put = snd_es1938_put_mux,
+},
+ES1938_DOUBLE_TLV("Mono Input Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0,
+		  db_scale_line),
+ES1938_DOUBLE_TLV("PCM Capture Volume", 0, 0x69, 0x69, 4, 0, 15, 0,
+		  db_scale_audio2),
+ES1938_DOUBLE_TLV("Mic Capture Volume", 0, 0x68, 0x68, 4, 0, 15, 0,
+		  db_scale_mic),
+ES1938_DOUBLE_TLV("Line Capture Volume", 0, 0x6e, 0x6e, 4, 0, 15, 0,
+		  db_scale_line),
+ES1938_DOUBLE_TLV("FM Capture Volume", 0, 0x6b, 0x6b, 4, 0, 15, 0,
+		  db_scale_mic),
+ES1938_DOUBLE_TLV("Mono Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0,
+		  db_scale_line),
+ES1938_DOUBLE_TLV("CD Capture Volume", 0, 0x6a, 0x6a, 4, 0, 15, 0,
+		  db_scale_line),
+ES1938_DOUBLE_TLV("Aux Capture Volume", 0, 0x6c, 0x6c, 4, 0, 15, 0,
+		  db_scale_line),
+ES1938_DOUBLE_TLV("PCM Playback Volume", 0, 0x7c, 0x7c, 4, 0, 15, 0,
+		  db_scale_audio2),
+ES1938_DOUBLE_TLV("PCM Playback Volume", 1, 0x14, 0x14, 4, 0, 15, 0,
+		  db_scale_audio1),
+ES1938_SINGLE("3D Control - Level", 0, 0x52, 0, 63, 0),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "3D Control - Switch",
+	.info = snd_es1938_info_spatializer_enable,
+	.get = snd_es1938_get_spatializer_enable,
+	.put = snd_es1938_put_spatializer_enable,
+},
+ES1938_SINGLE("Mic Boost (+26dB)", 0, 0x7d, 3, 1, 0)
+};
+
+
+/* ---------------------------------------------------------------------------- */
+/* ---------------------------------------------------------------------------- */
+
+/*
+ * initialize the chip - used by resume callback, too
+ */
+static void snd_es1938_chip_init(struct es1938 *chip)
+{
+	/* reset chip */
+	snd_es1938_reset(chip);
+
+	/* configure native mode */
+
+	/* enable bus master */
+	pci_set_master(chip->pci);
+
+	/* disable legacy audio */
+	pci_write_config_word(chip->pci, SL_PCI_LEGACYCONTROL, 0x805f);
+
+	/* set DDMA base */
+	pci_write_config_word(chip->pci, SL_PCI_DDMACONTROL, chip->ddma_port | 1);
+
+	/* set DMA/IRQ policy */
+	pci_write_config_dword(chip->pci, SL_PCI_CONFIG, 0);
+
+	/* enable Audio 1, Audio 2, MPU401 IRQ and HW volume IRQ*/
+	outb(0xf0, SLIO_REG(chip, IRQCONTROL));
+
+	/* reset DMA */
+	outb(0, SLDM_REG(chip, DMACLEAR));
+}
+
+#ifdef CONFIG_PM
+/*
+ * PM support
+ */
+
+static unsigned char saved_regs[SAVED_REG_SIZE+1] = {
+	0x14, 0x1a, 0x1c, 0x3a, 0x3c, 0x3e, 0x36, 0x38,
+	0x50, 0x52, 0x60, 0x61, 0x62, 0x63, 0x64, 0x68,
+	0x69, 0x6a, 0x6b, 0x6d, 0x6e, 0x6f, 0x7c, 0x7d,
+	0xa8, 0xb4,
+};
+
+
+static int es1938_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct es1938 *chip = card->private_data;
+	unsigned char *s, *d;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+
+	/* save mixer-related registers */
+	for (s = saved_regs, d = chip->saved_regs; *s; s++, d++)
+		*d = snd_es1938_reg_read(chip, *s);
+
+	outb(0x00, SLIO_REG(chip, IRQCONTROL)); /* disable irqs */
+	if (chip->irq >= 0) {
+		free_irq(chip->irq, chip);
+		chip->irq = -1;
+	}
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int es1938_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct es1938 *chip = card->private_data;
+	unsigned char *s, *d;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "es1938: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+
+	if (request_irq(pci->irq, snd_es1938_interrupt,
+			IRQF_SHARED, "ES1938", chip)) {
+		printk(KERN_ERR "es1938: unable to grab IRQ %d, "
+		       "disabling device\n", pci->irq);
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	chip->irq = pci->irq;
+	snd_es1938_chip_init(chip);
+
+	/* restore mixer-related registers */
+	for (s = saved_regs, d = chip->saved_regs; *s; s++, d++) {
+		if (*s < 0xa0)
+			snd_es1938_mixer_write(chip, *s, *d);
+		else
+			snd_es1938_write(chip, *s, *d);
+	}
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef SUPPORT_JOYSTICK
+static int __devinit snd_es1938_create_gameport(struct es1938 *chip)
+{
+	struct gameport *gp;
+
+	chip->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "es1938: cannot allocate memory for gameport\n");
+		return -ENOMEM;
+	}
+
+	gameport_set_name(gp, "ES1938");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
+	gameport_set_dev_parent(gp, &chip->pci->dev);
+	gp->io = chip->game_port;
+
+	gameport_register_port(gp);
+
+	return 0;
+}
+
+static void snd_es1938_free_gameport(struct es1938 *chip)
+{
+	if (chip->gameport) {
+		gameport_unregister_port(chip->gameport);
+		chip->gameport = NULL;
+	}
+}
+#else
+static inline int snd_es1938_create_gameport(struct es1938 *chip) { return -ENOSYS; }
+static inline void snd_es1938_free_gameport(struct es1938 *chip) { }
+#endif /* SUPPORT_JOYSTICK */
+
+static int snd_es1938_free(struct es1938 *chip)
+{
+	/* disable irqs */
+	outb(0x00, SLIO_REG(chip, IRQCONTROL));
+	if (chip->rmidi)
+		snd_es1938_mixer_bits(chip, ESSSB_IREG_MPU401CONTROL, 0x40, 0);
+
+	snd_es1938_free_gameport(chip);
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_es1938_dev_free(struct snd_device *device)
+{
+	struct es1938 *chip = device->device_data;
+	return snd_es1938_free(chip);
+}
+
+static int __devinit snd_es1938_create(struct snd_card *card,
+				    struct pci_dev * pci,
+				    struct es1938 ** rchip)
+{
+	struct es1938 *chip;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_es1938_dev_free,
+	};
+
+	*rchip = NULL;
+
+	/* enable PCI device */
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+        /* check, if we can restrict PCI DMA transfers to 24 bits */
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(24)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(24)) < 0) {
+		snd_printk(KERN_ERR "architecture does not support 24bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+                return -ENXIO;
+        }
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	spin_lock_init(&chip->reg_lock);
+	spin_lock_init(&chip->mixer_lock);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	if ((err = pci_request_regions(pci, "ESS Solo-1")) < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+	chip->io_port = pci_resource_start(pci, 0);
+	chip->sb_port = pci_resource_start(pci, 1);
+	chip->vc_port = pci_resource_start(pci, 2);
+	chip->mpu_port = pci_resource_start(pci, 3);
+	chip->game_port = pci_resource_start(pci, 4);
+	if (request_irq(pci->irq, snd_es1938_interrupt, IRQF_SHARED,
+			"ES1938", chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_es1938_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+#ifdef ES1938_DDEBUG
+	snd_printk(KERN_DEBUG "create: io: 0x%lx, sb: 0x%lx, vc: 0x%lx, mpu: 0x%lx, game: 0x%lx\n",
+		   chip->io_port, chip->sb_port, chip->vc_port, chip->mpu_port, chip->game_port);
+#endif
+
+	chip->ddma_port = chip->vc_port + 0x00;		/* fix from Thomas Sailer */
+
+	snd_es1938_chip_init(chip);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_es1938_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rchip = chip;
+	return 0;
+}
+
+/* --------------------------------------------------------------------
+ * Interrupt handler
+ * -------------------------------------------------------------------- */
+static irqreturn_t snd_es1938_interrupt(int irq, void *dev_id)
+{
+	struct es1938 *chip = dev_id;
+	unsigned char status, audiostatus;
+	int handled = 0;
+
+	status = inb(SLIO_REG(chip, IRQCONTROL));
+#if 0
+	printk(KERN_DEBUG "Es1938debug - interrupt status: =0x%x\n", status);
+#endif
+	
+	/* AUDIO 1 */
+	if (status & 0x10) {
+#if 0
+                printk(KERN_DEBUG
+		       "Es1938debug - AUDIO channel 1 interrupt\n");
+		printk(KERN_DEBUG
+		       "Es1938debug - AUDIO channel 1 DMAC DMA count: %u\n",
+		       inw(SLDM_REG(chip, DMACOUNT)));
+		printk(KERN_DEBUG
+		       "Es1938debug - AUDIO channel 1 DMAC DMA base: %u\n",
+		       inl(SLDM_REG(chip, DMAADDR)));
+		printk(KERN_DEBUG
+		       "Es1938debug - AUDIO channel 1 DMAC DMA status: 0x%x\n",
+		       inl(SLDM_REG(chip, DMASTATUS)));
+#endif
+		/* clear irq */
+		handled = 1;
+		audiostatus = inb(SLSB_REG(chip, STATUS));
+		if (chip->active & ADC1)
+			snd_pcm_period_elapsed(chip->capture_substream);
+		else if (chip->active & DAC1)
+			snd_pcm_period_elapsed(chip->playback2_substream);
+	}
+	
+	/* AUDIO 2 */
+	if (status & 0x20) {
+#if 0
+                printk(KERN_DEBUG
+		       "Es1938debug - AUDIO channel 2 interrupt\n");
+		printk(KERN_DEBUG
+		       "Es1938debug - AUDIO channel 2 DMAC DMA count: %u\n",
+		       inw(SLIO_REG(chip, AUDIO2DMACOUNT)));
+		printk(KERN_DEBUG
+		       "Es1938debug - AUDIO channel 2 DMAC DMA base: %u\n",
+		       inl(SLIO_REG(chip, AUDIO2DMAADDR)));
+
+#endif
+		/* clear irq */
+		handled = 1;
+		snd_es1938_mixer_bits(chip, ESSSB_IREG_AUDIO2CONTROL2, 0x80, 0);
+		if (chip->active & DAC2)
+			snd_pcm_period_elapsed(chip->playback1_substream);
+	}
+
+	/* Hardware volume */
+	if (status & 0x40) {
+		int split = snd_es1938_mixer_read(chip, 0x64) & 0x80;
+		handled = 1;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->hw_switch->id);
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->hw_volume->id);
+		if (!split) {
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->master_switch->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->master_volume->id);
+		}
+		/* ack interrupt */
+		snd_es1938_mixer_write(chip, 0x66, 0x00);
+	}
+
+	/* MPU401 */
+	if (status & 0x80) {
+		// the following line is evil! It switches off MIDI interrupt handling after the first interrupt received.
+		// replacing the last 0 by 0x40 works for ESS-Solo1, but just doing nothing works as well!
+		// andreas@flying-snail.de
+		// snd_es1938_mixer_bits(chip, ESSSB_IREG_MPU401CONTROL, 0x40, 0); /* ack? */
+		if (chip->rmidi) {
+			handled = 1;
+			snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+		}
+	}
+	return IRQ_RETVAL(handled);
+}
+
+#define ES1938_DMA_SIZE 64
+
+static int __devinit snd_es1938_mixer(struct es1938 *chip)
+{
+	struct snd_card *card;
+	unsigned int idx;
+	int err;
+
+	card = chip->card;
+
+	strcpy(card->mixername, "ESS Solo-1");
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_es1938_controls); idx++) {
+		struct snd_kcontrol *kctl;
+		kctl = snd_ctl_new1(&snd_es1938_controls[idx], chip);
+		switch (idx) {
+			case 0:
+				chip->master_volume = kctl;
+				kctl->private_free = snd_es1938_hwv_free;
+				break;
+			case 1:
+				chip->master_switch = kctl;
+				kctl->private_free = snd_es1938_hwv_free;
+				break;
+			case 2:
+				chip->hw_volume = kctl;
+				kctl->private_free = snd_es1938_hwv_free;
+				break;
+			case 3:
+				chip->hw_switch = kctl;
+				kctl->private_free = snd_es1938_hwv_free;
+				break;
+			}
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			return err;
+	}
+	return 0;
+}
+       
+
+static int __devinit snd_es1938_probe(struct pci_dev *pci,
+				      const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct es1938 *chip;
+	struct snd_opl3 *opl3;
+	int idx, err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+	for (idx = 0; idx < 5; idx++) {
+		if (pci_resource_start(pci, idx) == 0 ||
+		    !(pci_resource_flags(pci, idx) & IORESOURCE_IO)) {
+		    	snd_card_free(card);
+		    	return -ENODEV;
+		}
+	}
+	if ((err = snd_es1938_create(card, pci, &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = chip;
+
+	strcpy(card->driver, "ES1938");
+	strcpy(card->shortname, "ESS ES1938 (Solo-1)");
+	sprintf(card->longname, "%s rev %i, irq %i",
+		card->shortname,
+		chip->revision,
+		chip->irq);
+
+	if ((err = snd_es1938_new_pcm(chip, 0)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_es1938_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (snd_opl3_create(card,
+			    SLSB_REG(chip, FMLOWADDR),
+			    SLSB_REG(chip, FMHIGHADDR),
+			    OPL3_HW_OPL3, 1, &opl3) < 0) {
+		printk(KERN_ERR "es1938: OPL3 not detected at 0x%lx\n",
+			   SLSB_REG(chip, FMLOWADDR));
+	} else {
+	        if ((err = snd_opl3_timer_new(opl3, 0, 1)) < 0) {
+	                snd_card_free(card);
+	                return err;
+		}
+	        if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+	                snd_card_free(card);
+	                return err;
+		}
+	}
+	if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+				chip->mpu_port, MPU401_INFO_INTEGRATED,
+				chip->irq, 0, &chip->rmidi) < 0) {
+		printk(KERN_ERR "es1938: unable to initialize MPU-401\n");
+	} else {
+		// this line is vital for MIDI interrupt handling on ess-solo1
+		// andreas@flying-snail.de
+		snd_es1938_mixer_bits(chip, ESSSB_IREG_MPU401CONTROL, 0x40, 0x40);
+	}
+
+	snd_es1938_create_gameport(chip);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_es1938_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "ESS ES1938 (Solo-1)",
+	.id_table = snd_es1938_ids,
+	.probe = snd_es1938_probe,
+	.remove = __devexit_p(snd_es1938_remove),
+#ifdef CONFIG_PM
+	.suspend = es1938_suspend,
+	.resume = es1938_resume,
+#endif
+};
+
+static int __init alsa_card_es1938_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_es1938_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_es1938_init)
+module_exit(alsa_card_es1938_exit)
diff --git a/linux/sound/pci/es1968.c b/linux/sound/pci/es1968.c
new file mode 100644
index 0000000..23a58f0
--- /dev/null
+++ b/linux/sound/pci/es1968.c
@@ -0,0 +1,2871 @@
+/*
+ *  Driver for ESS Maestro 1/2/2E Sound Card (started 21.8.99)
+ *  Copyright (c) by Matze Braun <MatzeBraun@gmx.de>.
+ *                   Takashi Iwai <tiwai@suse.de>
+ *                  
+ *  Most of the driver code comes from Zach Brown(zab@redhat.com)
+ *	Alan Cox OSS Driver
+ *  Rewritted from card-es1938.c source.
+ *
+ *  TODO:
+ *   Perhaps Synth
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *
+ *  Notes from Zach Brown about the driver code
+ *
+ *  Hardware Description
+ *
+ *	A working Maestro setup contains the Maestro chip wired to a 
+ *	codec or 2.  In the Maestro we have the APUs, the ASSP, and the
+ *	Wavecache.  The APUs can be though of as virtual audio routing
+ *	channels.  They can take data from a number of sources and perform
+ *	basic encodings of the data.  The wavecache is a storehouse for
+ *	PCM data.  Typically it deals with PCI and interracts with the
+ *	APUs.  The ASSP is a wacky DSP like device that ESS is loth
+ *	to release docs on.  Thankfully it isn't required on the Maestro
+ *	until you start doing insane things like FM emulation and surround
+ *	encoding.  The codecs are almost always AC-97 compliant codecs, 
+ *	but it appears that early Maestros may have had PT101 (an ESS
+ *	part?) wired to them.  The only real difference in the Maestro
+ *	families is external goop like docking capability, memory for
+ *	the ASSP, and initialization differences.
+ *
+ *  Driver Operation
+ *
+ *	We only drive the APU/Wavecache as typical DACs and drive the
+ *	mixers in the codecs.  There are 64 APUs.  We assign 6 to each
+ *	/dev/dsp? device.  2 channels for output, and 4 channels for
+ *	input.
+ *
+ *	Each APU can do a number of things, but we only really use
+ *	3 basic functions.  For playback we use them to convert PCM
+ *	data fetched over PCI by the wavecahche into analog data that
+ *	is handed to the codec.  One APU for mono, and a pair for stereo.
+ *	When in stereo, the combination of smarts in the APU and Wavecache
+ *	decide which wavecache gets the left or right channel.
+ *
+ *	For record we still use the old overly mono system.  For each in
+ *	coming channel the data comes in from the codec, through a 'input'
+ *	APU, through another rate converter APU, and then into memory via
+ *	the wavecache and PCI.  If its stereo, we mash it back into LRLR in
+ *	software.  The pass between the 2 APUs is supposedly what requires us
+ *	to have a 512 byte buffer sitting around in wavecache/memory.
+ *
+ *	The wavecache makes our life even more fun.  First off, it can
+ *	only address the first 28 bits of PCI address space, making it
+ *	useless on quite a few architectures.  Secondly, its insane.
+ *	It claims to fetch from 4 regions of PCI space, each 4 meg in length.
+ *	But that doesn't really work.  You can only use 1 region.  So all our
+ *	allocations have to be in 4meg of each other.  Booo.  Hiss.
+ *	So we have a module parameter, dsps_order, that is the order of
+ *	the number of dsps to provide.  All their buffer space is allocated
+ *	on open time.  The sonicvibes OSS routines we inherited really want
+ *	power of 2 buffers, so we have all those next to each other, then
+ *	512 byte regions for the recording wavecaches.  This ends up
+ *	wasting quite a bit of memory.  The only fixes I can see would be 
+ *	getting a kernel allocator that could work in zones, or figuring out
+ *	just how to coerce the WP into doing what we want.
+ *
+ *	The indirection of the various registers means we have to spinlock
+ *	nearly all register accesses.  We have the main register indirection
+ *	like the wave cache, maestro registers, etc.  Then we have beasts
+ *	like the APU interface that is indirect registers gotten at through
+ *	the main maestro indirection.  Ouch.  We spinlock around the actual
+ *	ports on a per card basis.  This means spinlock activity at each IO
+ *	operation, but the only IO operation clusters are in non critical 
+ *	paths and it makes the code far easier to follow.  Interrupts are
+ *	blocked while holding the locks because the int handler has to
+ *	get at some of them :(.  The mixer interface doesn't, however.
+ *	We also have an OSS state lock that is thrown around in a few
+ *	places.
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/input.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/mpu401.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+
+#define CARD_NAME "ESS Maestro1/2"
+#define DRIVER_NAME "ES1968"
+
+MODULE_DESCRIPTION("ESS Maestro");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,Maestro 2e},"
+		"{ESS,Maestro 2},"
+		"{ESS,Maestro 1},"
+		"{TerraTec,DMX}}");
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+#define SUPPORT_JOYSTICK 1
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 1-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static int total_bufsize[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1024 };
+static int pcm_substreams_p[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4 };
+static int pcm_substreams_c[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1 };
+static int clock[SNDRV_CARDS];
+static int use_pm[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+static int enable_mpu[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+#ifdef SUPPORT_JOYSTICK
+static int joystick[SNDRV_CARDS];
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+module_param_array(total_bufsize, int, NULL, 0444);
+MODULE_PARM_DESC(total_bufsize, "Total buffer size in kB.");
+module_param_array(pcm_substreams_p, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_substreams_p, "PCM Playback substreams for " CARD_NAME " soundcard.");
+module_param_array(pcm_substreams_c, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_substreams_c, "PCM Capture substreams for " CARD_NAME " soundcard.");
+module_param_array(clock, int, NULL, 0444);
+MODULE_PARM_DESC(clock, "Clock on " CARD_NAME " soundcard.  (0 = auto-detect)");
+module_param_array(use_pm, int, NULL, 0444);
+MODULE_PARM_DESC(use_pm, "Toggle power-management.  (0 = off, 1 = on, 2 = auto)");
+module_param_array(enable_mpu, int, NULL, 0444);
+MODULE_PARM_DESC(enable_mpu, "Enable MPU401.  (0 = off, 1 = on, 2 = auto)");
+#ifdef SUPPORT_JOYSTICK
+module_param_array(joystick, bool, NULL, 0444);
+MODULE_PARM_DESC(joystick, "Enable joystick.");
+#endif
+
+
+#define NR_APUS			64
+#define NR_APU_REGS		16
+
+/* NEC Versas ? */
+#define NEC_VERSA_SUBID1	0x80581033
+#define NEC_VERSA_SUBID2	0x803c1033
+
+/* Mode Flags */
+#define ESS_FMT_STEREO     	0x01
+#define ESS_FMT_16BIT      	0x02
+
+#define DAC_RUNNING		1
+#define ADC_RUNNING		2
+
+/* Values for the ESM_LEGACY_AUDIO_CONTROL */
+
+#define ESS_DISABLE_AUDIO	0x8000
+#define ESS_ENABLE_SERIAL_IRQ	0x4000
+#define IO_ADRESS_ALIAS		0x0020
+#define MPU401_IRQ_ENABLE	0x0010
+#define MPU401_IO_ENABLE	0x0008
+#define GAME_IO_ENABLE		0x0004
+#define FM_IO_ENABLE		0x0002
+#define SB_IO_ENABLE		0x0001
+
+/* Values for the ESM_CONFIG_A */
+
+#define PIC_SNOOP1		0x4000
+#define PIC_SNOOP2		0x2000
+#define SAFEGUARD		0x0800
+#define DMA_CLEAR		0x0700
+#define DMA_DDMA		0x0000
+#define DMA_TDMA		0x0100
+#define DMA_PCPCI		0x0200
+#define POST_WRITE		0x0080
+#define PCI_TIMING		0x0040
+#define SWAP_LR			0x0020
+#define SUBTR_DECODE		0x0002
+
+/* Values for the ESM_CONFIG_B */
+
+#define SPDIF_CONFB		0x0100
+#define HWV_CONFB		0x0080
+#define DEBOUNCE		0x0040
+#define GPIO_CONFB		0x0020
+#define CHI_CONFB		0x0010
+#define IDMA_CONFB		0x0008	/*undoc */
+#define MIDI_FIX		0x0004	/*undoc */
+#define IRQ_TO_ISA		0x0001	/*undoc */
+
+/* Values for Ring Bus Control B */
+#define	RINGB_2CODEC_ID_MASK	0x0003
+#define RINGB_DIS_VALIDATION	0x0008
+#define RINGB_EN_SPDIF		0x0010
+#define	RINGB_EN_2CODEC		0x0020
+#define RINGB_SING_BIT_DUAL	0x0040
+
+/* ****Port Adresses**** */
+
+/*   Write & Read */
+#define ESM_INDEX		0x02
+#define ESM_DATA		0x00
+
+/*   AC97 + RingBus */
+#define ESM_AC97_INDEX		0x30
+#define	ESM_AC97_DATA		0x32
+#define ESM_RING_BUS_DEST	0x34
+#define ESM_RING_BUS_CONTR_A	0x36
+#define ESM_RING_BUS_CONTR_B	0x38
+#define ESM_RING_BUS_SDO	0x3A
+
+/*   WaveCache*/
+#define WC_INDEX		0x10
+#define WC_DATA			0x12
+#define WC_CONTROL		0x14
+
+/*   ASSP*/
+#define ASSP_INDEX		0x80
+#define ASSP_MEMORY		0x82
+#define ASSP_DATA		0x84
+#define ASSP_CONTROL_A		0xA2
+#define ASSP_CONTROL_B		0xA4
+#define ASSP_CONTROL_C		0xA6
+#define ASSP_HOSTW_INDEX	0xA8
+#define ASSP_HOSTW_DATA		0xAA
+#define ASSP_HOSTW_IRQ		0xAC
+/* Midi */
+#define ESM_MPU401_PORT		0x98
+/* Others */
+#define ESM_PORT_HOST_IRQ	0x18
+
+#define IDR0_DATA_PORT		0x00
+#define IDR1_CRAM_POINTER	0x01
+#define IDR2_CRAM_DATA		0x02
+#define IDR3_WAVE_DATA		0x03
+#define IDR4_WAVE_PTR_LOW	0x04
+#define IDR5_WAVE_PTR_HI	0x05
+#define IDR6_TIMER_CTRL		0x06
+#define IDR7_WAVE_ROMRAM	0x07
+
+#define WRITEABLE_MAP		0xEFFFFF
+#define READABLE_MAP		0x64003F
+
+/* PCI Register */
+
+#define ESM_LEGACY_AUDIO_CONTROL 0x40
+#define ESM_ACPI_COMMAND	0x54
+#define ESM_CONFIG_A		0x50
+#define ESM_CONFIG_B		0x52
+#define ESM_DDMA		0x60
+
+/* Bob Bits */
+#define ESM_BOB_ENABLE		0x0001
+#define ESM_BOB_START		0x0001
+
+/* Host IRQ Control Bits */
+#define ESM_RESET_MAESTRO	0x8000
+#define ESM_RESET_DIRECTSOUND   0x4000
+#define ESM_HIRQ_ClkRun		0x0100
+#define ESM_HIRQ_HW_VOLUME	0x0040
+#define ESM_HIRQ_HARPO		0x0030	/* What's that? */
+#define ESM_HIRQ_ASSP		0x0010
+#define	ESM_HIRQ_DSIE		0x0004
+#define ESM_HIRQ_MPU401		0x0002
+#define ESM_HIRQ_SB		0x0001
+
+/* Host IRQ Status Bits */
+#define ESM_MPU401_IRQ		0x02
+#define ESM_SB_IRQ		0x01
+#define ESM_SOUND_IRQ		0x04
+#define	ESM_ASSP_IRQ		0x10
+#define ESM_HWVOL_IRQ		0x40
+
+#define ESS_SYSCLK		50000000
+#define ESM_BOB_FREQ 		200
+#define ESM_BOB_FREQ_MAX	800
+
+#define ESM_FREQ_ESM1  		(49152000L / 1024L)	/* default rate 48000 */
+#define ESM_FREQ_ESM2  		(50000000L / 1024L)
+
+/* APU Modes: reg 0x00, bit 4-7 */
+#define ESM_APU_MODE_SHIFT	4
+#define ESM_APU_MODE_MASK	(0xf << 4)
+#define	ESM_APU_OFF		0x00
+#define	ESM_APU_16BITLINEAR	0x01	/* 16-Bit Linear Sample Player */
+#define	ESM_APU_16BITSTEREO	0x02	/* 16-Bit Stereo Sample Player */
+#define	ESM_APU_8BITLINEAR	0x03	/* 8-Bit Linear Sample Player */
+#define	ESM_APU_8BITSTEREO	0x04	/* 8-Bit Stereo Sample Player */
+#define	ESM_APU_8BITDIFF	0x05	/* 8-Bit Differential Sample Playrer */
+#define	ESM_APU_DIGITALDELAY	0x06	/* Digital Delay Line */
+#define	ESM_APU_DUALTAP		0x07	/* Dual Tap Reader */
+#define	ESM_APU_CORRELATOR	0x08	/* Correlator */
+#define	ESM_APU_INPUTMIXER	0x09	/* Input Mixer */
+#define	ESM_APU_WAVETABLE	0x0A	/* Wave Table Mode */
+#define	ESM_APU_SRCONVERTOR	0x0B	/* Sample Rate Convertor */
+#define	ESM_APU_16BITPINGPONG	0x0C	/* 16-Bit Ping-Pong Sample Player */
+#define	ESM_APU_RESERVED1	0x0D	/* Reserved 1 */
+#define	ESM_APU_RESERVED2	0x0E	/* Reserved 2 */
+#define	ESM_APU_RESERVED3	0x0F	/* Reserved 3 */
+
+/* reg 0x00 */
+#define ESM_APU_FILTER_Q_SHIFT		0
+#define ESM_APU_FILTER_Q_MASK		(3 << 0)
+/* APU Filtey Q Control */
+#define ESM_APU_FILTER_LESSQ	0x00
+#define ESM_APU_FILTER_MOREQ	0x03
+
+#define ESM_APU_FILTER_TYPE_SHIFT	2
+#define ESM_APU_FILTER_TYPE_MASK	(3 << 2)
+#define ESM_APU_ENV_TYPE_SHIFT		8
+#define ESM_APU_ENV_TYPE_MASK		(3 << 8)
+#define ESM_APU_ENV_STATE_SHIFT		10
+#define ESM_APU_ENV_STATE_MASK		(3 << 10)
+#define ESM_APU_END_CURVE		(1 << 12)
+#define ESM_APU_INT_ON_LOOP		(1 << 13)
+#define ESM_APU_DMA_ENABLE		(1 << 14)
+
+/* reg 0x02 */
+#define ESM_APU_SUBMIX_GROUP_SHIRT	0
+#define ESM_APU_SUBMIX_GROUP_MASK	(7 << 0)
+#define ESM_APU_SUBMIX_MODE		(1 << 3)
+#define ESM_APU_6dB			(1 << 4)
+#define ESM_APU_DUAL_EFFECT		(1 << 5)
+#define ESM_APU_EFFECT_CHANNELS_SHIFT	6
+#define ESM_APU_EFFECT_CHANNELS_MASK	(3 << 6)
+
+/* reg 0x03 */
+#define ESM_APU_STEP_SIZE_MASK		0x0fff
+
+/* reg 0x04 */
+#define ESM_APU_PHASE_SHIFT		0
+#define ESM_APU_PHASE_MASK		(0xff << 0)
+#define ESM_APU_WAVE64K_PAGE_SHIFT	8	/* most 8bit of wave start offset */
+#define ESM_APU_WAVE64K_PAGE_MASK	(0xff << 8)
+
+/* reg 0x05 - wave start offset */
+/* reg 0x06 - wave end offset */
+/* reg 0x07 - wave loop length */
+
+/* reg 0x08 */
+#define ESM_APU_EFFECT_GAIN_SHIFT	0
+#define ESM_APU_EFFECT_GAIN_MASK	(0xff << 0)
+#define ESM_APU_TREMOLO_DEPTH_SHIFT	8
+#define ESM_APU_TREMOLO_DEPTH_MASK	(0xf << 8)
+#define ESM_APU_TREMOLO_RATE_SHIFT	12
+#define ESM_APU_TREMOLO_RATE_MASK	(0xf << 12)
+
+/* reg 0x09 */
+/* bit 0-7 amplitude dest? */
+#define ESM_APU_AMPLITUDE_NOW_SHIFT	8
+#define ESM_APU_AMPLITUDE_NOW_MASK	(0xff << 8)
+
+/* reg 0x0a */
+#define ESM_APU_POLAR_PAN_SHIFT		0
+#define ESM_APU_POLAR_PAN_MASK		(0x3f << 0)
+/* Polar Pan Control */
+#define	ESM_APU_PAN_CENTER_CIRCLE		0x00
+#define	ESM_APU_PAN_MIDDLE_RADIUS		0x01
+#define	ESM_APU_PAN_OUTSIDE_RADIUS		0x02
+
+#define ESM_APU_FILTER_TUNING_SHIFT	8
+#define ESM_APU_FILTER_TUNING_MASK	(0xff << 8)
+
+/* reg 0x0b */
+#define ESM_APU_DATA_SRC_A_SHIFT	0
+#define ESM_APU_DATA_SRC_A_MASK		(0x7f << 0)
+#define ESM_APU_INV_POL_A		(1 << 7)
+#define ESM_APU_DATA_SRC_B_SHIFT	8
+#define ESM_APU_DATA_SRC_B_MASK		(0x7f << 8)
+#define ESM_APU_INV_POL_B		(1 << 15)
+
+#define ESM_APU_VIBRATO_RATE_SHIFT	0
+#define ESM_APU_VIBRATO_RATE_MASK	(0xf << 0)
+#define ESM_APU_VIBRATO_DEPTH_SHIFT	4
+#define ESM_APU_VIBRATO_DEPTH_MASK	(0xf << 4)
+#define ESM_APU_VIBRATO_PHASE_SHIFT	8
+#define ESM_APU_VIBRATO_PHASE_MASK	(0xff << 8)
+
+/* reg 0x0c */
+#define ESM_APU_RADIUS_SELECT		(1 << 6)
+
+/* APU Filter Control */
+#define	ESM_APU_FILTER_2POLE_LOPASS	0x00
+#define	ESM_APU_FILTER_2POLE_BANDPASS	0x01
+#define	ESM_APU_FILTER_2POLE_HIPASS	0x02
+#define	ESM_APU_FILTER_1POLE_LOPASS	0x03
+#define	ESM_APU_FILTER_1POLE_HIPASS	0x04
+#define	ESM_APU_FILTER_OFF		0x05
+
+/* APU ATFP Type */
+#define	ESM_APU_ATFP_AMPLITUDE			0x00
+#define	ESM_APU_ATFP_TREMELO			0x01
+#define	ESM_APU_ATFP_FILTER			0x02
+#define	ESM_APU_ATFP_PAN			0x03
+
+/* APU ATFP Flags */
+#define	ESM_APU_ATFP_FLG_OFF			0x00
+#define	ESM_APU_ATFP_FLG_WAIT			0x01
+#define	ESM_APU_ATFP_FLG_DONE			0x02
+#define	ESM_APU_ATFP_FLG_INPROCESS		0x03
+
+
+/* capture mixing buffer size */
+#define ESM_MEM_ALIGN		0x1000
+#define ESM_MIXBUF_SIZE		0x400
+
+#define ESM_MODE_PLAY		0
+#define ESM_MODE_CAPTURE	1
+
+
+/* APU use in the driver */
+enum snd_enum_apu_type {
+	ESM_APU_PCM_PLAY,
+	ESM_APU_PCM_CAPTURE,
+	ESM_APU_PCM_RATECONV,
+	ESM_APU_FREE
+};
+
+/* chip type */
+enum {
+	TYPE_MAESTRO, TYPE_MAESTRO2, TYPE_MAESTRO2E
+};
+
+/* DMA Hack! */
+struct esm_memory {
+	struct snd_dma_buffer buf;
+	int empty;	/* status */
+	struct list_head list;
+};
+
+/* Playback Channel */
+struct esschan {
+	int running;
+
+	u8 apu[4];
+	u8 apu_mode[4];
+
+	/* playback/capture pcm buffer */
+	struct esm_memory *memory;
+	/* capture mixer buffer */
+	struct esm_memory *mixbuf;
+
+	unsigned int hwptr;	/* current hw pointer in bytes */
+	unsigned int count;	/* sample counter in bytes */
+	unsigned int dma_size;	/* total buffer size in bytes */
+	unsigned int frag_size;	/* period size in bytes */
+	unsigned int wav_shift;
+	u16 base[4];		/* offset for ptr */
+
+	/* stereo/16bit flag */
+	unsigned char fmt;
+	int mode;	/* playback / capture */
+
+	int bob_freq;	/* required timer frequency */
+
+	struct snd_pcm_substream *substream;
+
+	/* linked list */
+	struct list_head list;
+
+#ifdef CONFIG_PM
+	u16 wc_map[4];
+#endif
+};
+
+struct es1968 {
+	/* Module Config */
+	int total_bufsize;			/* in bytes */
+
+	int playback_streams, capture_streams;
+
+	unsigned int clock;		/* clock */
+	/* for clock measurement */
+	unsigned int in_measurement: 1;
+	unsigned int measure_apu;
+	unsigned int measure_lastpos;
+	unsigned int measure_count;
+
+	/* buffer */
+	struct snd_dma_buffer dma;
+
+	/* Resources... */
+	int irq;
+	unsigned long io_port;
+	int type;
+	struct pci_dev *pci;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	int do_pm;		/* power-management enabled */
+
+	/* DMA memory block */
+	struct list_head buf_list;
+
+	/* ALSA Stuff */
+	struct snd_ac97 *ac97;
+	struct snd_rawmidi *rmidi;
+
+	spinlock_t reg_lock;
+	unsigned int in_suspend;
+
+	/* Maestro Stuff */
+	u16 maestro_map[32];
+	int bobclient;		/* active timer instancs */
+	int bob_freq;		/* timer frequency */
+	struct mutex memory_mutex;	/* memory lock */
+
+	/* APU states */
+	unsigned char apu[NR_APUS];
+
+	/* active substreams */
+	struct list_head substream_list;
+	spinlock_t substream_lock;
+
+#ifdef CONFIG_PM
+	u16 apu_map[NR_APUS][NR_APU_REGS];
+#endif
+
+#ifdef SUPPORT_JOYSTICK
+	struct gameport *gameport;
+#endif
+
+#ifdef CONFIG_SND_ES1968_INPUT
+	struct input_dev *input_dev;
+	char phys[64];			/* physical device path */
+#else
+	struct snd_kcontrol *master_switch; /* for h/w volume control */
+	struct snd_kcontrol *master_volume;
+	spinlock_t ac97_lock;
+	struct tasklet_struct hwvol_tq;
+#endif
+};
+
+static irqreturn_t snd_es1968_interrupt(int irq, void *dev_id);
+
+static DEFINE_PCI_DEVICE_TABLE(snd_es1968_ids) = {
+	/* Maestro 1 */
+        { 0x1285, 0x0100, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, TYPE_MAESTRO },
+	/* Maestro 2 */
+	{ 0x125d, 0x1968, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, TYPE_MAESTRO2 },
+	/* Maestro 2E */
+        { 0x125d, 0x1978, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, TYPE_MAESTRO2E },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_es1968_ids);
+
+/* *********************
+   * Low Level Funcs!  *
+   *********************/
+
+/* no spinlock */
+static void __maestro_write(struct es1968 *chip, u16 reg, u16 data)
+{
+	outw(reg, chip->io_port + ESM_INDEX);
+	outw(data, chip->io_port + ESM_DATA);
+	chip->maestro_map[reg] = data;
+}
+
+static inline void maestro_write(struct es1968 *chip, u16 reg, u16 data)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	__maestro_write(chip, reg, data);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/* no spinlock */
+static u16 __maestro_read(struct es1968 *chip, u16 reg)
+{
+	if (READABLE_MAP & (1 << reg)) {
+		outw(reg, chip->io_port + ESM_INDEX);
+		chip->maestro_map[reg] = inw(chip->io_port + ESM_DATA);
+	}
+	return chip->maestro_map[reg];
+}
+
+static inline u16 maestro_read(struct es1968 *chip, u16 reg)
+{
+	unsigned long flags;
+	u16 result;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	result = __maestro_read(chip, reg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return result;
+}
+
+/* Wait for the codec bus to be free */
+static int snd_es1968_ac97_wait(struct es1968 *chip)
+{
+	int timeout = 100000;
+
+	while (timeout-- > 0) {
+		if (!(inb(chip->io_port + ESM_AC97_INDEX) & 1))
+			return 0;
+		cond_resched();
+	}
+	snd_printd("es1968: ac97 timeout\n");
+	return 1; /* timeout */
+}
+
+static int snd_es1968_ac97_wait_poll(struct es1968 *chip)
+{
+	int timeout = 100000;
+
+	while (timeout-- > 0) {
+		if (!(inb(chip->io_port + ESM_AC97_INDEX) & 1))
+			return 0;
+	}
+	snd_printd("es1968: ac97 timeout\n");
+	return 1; /* timeout */
+}
+
+static void snd_es1968_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val)
+{
+	struct es1968 *chip = ac97->private_data;
+#ifndef CONFIG_SND_ES1968_INPUT
+	unsigned long flags;
+#endif
+
+	snd_es1968_ac97_wait(chip);
+
+	/* Write the bus */
+#ifndef CONFIG_SND_ES1968_INPUT
+	spin_lock_irqsave(&chip->ac97_lock, flags);
+#endif
+	outw(val, chip->io_port + ESM_AC97_DATA);
+	/*msleep(1);*/
+	outb(reg, chip->io_port + ESM_AC97_INDEX);
+	/*msleep(1);*/
+#ifndef CONFIG_SND_ES1968_INPUT
+	spin_unlock_irqrestore(&chip->ac97_lock, flags);
+#endif
+}
+
+static unsigned short snd_es1968_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	u16 data = 0;
+	struct es1968 *chip = ac97->private_data;
+#ifndef CONFIG_SND_ES1968_INPUT
+	unsigned long flags;
+#endif
+
+	snd_es1968_ac97_wait(chip);
+
+#ifndef CONFIG_SND_ES1968_INPUT
+	spin_lock_irqsave(&chip->ac97_lock, flags);
+#endif
+	outb(reg | 0x80, chip->io_port + ESM_AC97_INDEX);
+	/*msleep(1);*/
+
+	if (!snd_es1968_ac97_wait_poll(chip)) {
+		data = inw(chip->io_port + ESM_AC97_DATA);
+		/*msleep(1);*/
+	}
+#ifndef CONFIG_SND_ES1968_INPUT
+	spin_unlock_irqrestore(&chip->ac97_lock, flags);
+#endif
+
+	return data;
+}
+
+/* no spinlock */
+static void apu_index_set(struct es1968 *chip, u16 index)
+{
+	int i;
+	__maestro_write(chip, IDR1_CRAM_POINTER, index);
+	for (i = 0; i < 1000; i++)
+		if (__maestro_read(chip, IDR1_CRAM_POINTER) == index)
+			return;
+	snd_printd("es1968: APU register select failed. (Timeout)\n");
+}
+
+/* no spinlock */
+static void apu_data_set(struct es1968 *chip, u16 data)
+{
+	int i;
+	for (i = 0; i < 1000; i++) {
+		if (__maestro_read(chip, IDR0_DATA_PORT) == data)
+			return;
+		__maestro_write(chip, IDR0_DATA_PORT, data);
+	}
+	snd_printd("es1968: APU register set probably failed (Timeout)!\n");
+}
+
+/* no spinlock */
+static void __apu_set_register(struct es1968 *chip, u16 channel, u8 reg, u16 data)
+{
+	if (snd_BUG_ON(channel >= NR_APUS))
+		return;
+#ifdef CONFIG_PM
+	chip->apu_map[channel][reg] = data;
+#endif
+	reg |= (channel << 4);
+	apu_index_set(chip, reg);
+	apu_data_set(chip, data);
+}
+
+static void apu_set_register(struct es1968 *chip, u16 channel, u8 reg, u16 data)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	__apu_set_register(chip, channel, reg, data);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static u16 __apu_get_register(struct es1968 *chip, u16 channel, u8 reg)
+{
+	if (snd_BUG_ON(channel >= NR_APUS))
+		return 0;
+	reg |= (channel << 4);
+	apu_index_set(chip, reg);
+	return __maestro_read(chip, IDR0_DATA_PORT);
+}
+
+static u16 apu_get_register(struct es1968 *chip, u16 channel, u8 reg)
+{
+	unsigned long flags;
+	u16 v;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	v = __apu_get_register(chip, channel, reg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return v;
+}
+
+#if 0 /* ASSP is not supported */
+
+static void assp_set_register(struct es1968 *chip, u32 reg, u32 value)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	outl(reg, chip->io_port + ASSP_INDEX);
+	outl(value, chip->io_port + ASSP_DATA);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static u32 assp_get_register(struct es1968 *chip, u32 reg)
+{
+	unsigned long flags;
+	u32 value;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	outl(reg, chip->io_port + ASSP_INDEX);
+	value = inl(chip->io_port + ASSP_DATA);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	return value;
+}
+
+#endif
+
+static void wave_set_register(struct es1968 *chip, u16 reg, u16 value)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	outw(reg, chip->io_port + WC_INDEX);
+	outw(value, chip->io_port + WC_DATA);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static u16 wave_get_register(struct es1968 *chip, u16 reg)
+{
+	unsigned long flags;
+	u16 value;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	outw(reg, chip->io_port + WC_INDEX);
+	value = inw(chip->io_port + WC_DATA);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	return value;
+}
+
+/* *******************
+   * Bob the Timer!  *
+   *******************/
+
+static void snd_es1968_bob_stop(struct es1968 *chip)
+{
+	u16 reg;
+
+	reg = __maestro_read(chip, 0x11);
+	reg &= ~ESM_BOB_ENABLE;
+	__maestro_write(chip, 0x11, reg);
+	reg = __maestro_read(chip, 0x17);
+	reg &= ~ESM_BOB_START;
+	__maestro_write(chip, 0x17, reg);
+}
+
+static void snd_es1968_bob_start(struct es1968 *chip)
+{
+	int prescale;
+	int divide;
+
+	/* compute ideal interrupt frequency for buffer size & play rate */
+	/* first, find best prescaler value to match freq */
+	for (prescale = 5; prescale < 12; prescale++)
+		if (chip->bob_freq > (ESS_SYSCLK >> (prescale + 9)))
+			break;
+
+	/* next, back off prescaler whilst getting divider into optimum range */
+	divide = 1;
+	while ((prescale > 5) && (divide < 32)) {
+		prescale--;
+		divide <<= 1;
+	}
+	divide >>= 1;
+
+	/* now fine-tune the divider for best match */
+	for (; divide < 31; divide++)
+		if (chip->bob_freq >
+		    ((ESS_SYSCLK >> (prescale + 9)) / (divide + 1))) break;
+
+	/* divide = 0 is illegal, but don't let prescale = 4! */
+	if (divide == 0) {
+		divide++;
+		if (prescale > 5)
+			prescale--;
+	} else if (divide > 1)
+		divide--;
+
+	__maestro_write(chip, 6, 0x9000 | (prescale << 5) | divide);	/* set reg */
+
+	/* Now set IDR 11/17 */
+	__maestro_write(chip, 0x11, __maestro_read(chip, 0x11) | 1);
+	__maestro_write(chip, 0x17, __maestro_read(chip, 0x17) | 1);
+}
+
+/* call with substream spinlock */
+static void snd_es1968_bob_inc(struct es1968 *chip, int freq)
+{
+	chip->bobclient++;
+	if (chip->bobclient == 1) {
+		chip->bob_freq = freq;
+		snd_es1968_bob_start(chip);
+	} else if (chip->bob_freq < freq) {
+		snd_es1968_bob_stop(chip);
+		chip->bob_freq = freq;
+		snd_es1968_bob_start(chip);
+	}
+}
+
+/* call with substream spinlock */
+static void snd_es1968_bob_dec(struct es1968 *chip)
+{
+	chip->bobclient--;
+	if (chip->bobclient <= 0)
+		snd_es1968_bob_stop(chip);
+	else if (chip->bob_freq > ESM_BOB_FREQ) {
+		/* check reduction of timer frequency */
+		int max_freq = ESM_BOB_FREQ;
+		struct esschan *es;
+		list_for_each_entry(es, &chip->substream_list, list) {
+			if (max_freq < es->bob_freq)
+				max_freq = es->bob_freq;
+		}
+		if (max_freq != chip->bob_freq) {
+			snd_es1968_bob_stop(chip);
+			chip->bob_freq = max_freq;
+			snd_es1968_bob_start(chip);
+		}
+	}
+}
+
+static int
+snd_es1968_calc_bob_rate(struct es1968 *chip, struct esschan *es,
+			 struct snd_pcm_runtime *runtime)
+{
+	/* we acquire 4 interrupts per period for precise control.. */
+	int freq = runtime->rate * 4;
+	if (es->fmt & ESS_FMT_STEREO)
+		freq <<= 1;
+	if (es->fmt & ESS_FMT_16BIT)
+		freq <<= 1;
+	freq /= es->frag_size;
+	if (freq < ESM_BOB_FREQ)
+		freq = ESM_BOB_FREQ;
+	else if (freq > ESM_BOB_FREQ_MAX)
+		freq = ESM_BOB_FREQ_MAX;
+	return freq;
+}
+
+
+/*************
+ *  PCM Part *
+ *************/
+
+static u32 snd_es1968_compute_rate(struct es1968 *chip, u32 freq)
+{
+	u32 rate = (freq << 16) / chip->clock;
+#if 0 /* XXX: do we need this? */ 
+	if (rate > 0x10000)
+		rate = 0x10000;
+#endif
+	return rate;
+}
+
+/* get current pointer */
+static inline unsigned int
+snd_es1968_get_dma_ptr(struct es1968 *chip, struct esschan *es)
+{
+	unsigned int offset;
+
+	offset = apu_get_register(chip, es->apu[0], 5);
+
+	offset -= es->base[0];
+
+	return (offset & 0xFFFE);	/* hardware is in words */
+}
+
+static void snd_es1968_apu_set_freq(struct es1968 *chip, int apu, int freq)
+{
+	apu_set_register(chip, apu, 2,
+			   (apu_get_register(chip, apu, 2) & 0x00FF) |
+			   ((freq & 0xff) << 8) | 0x10);
+	apu_set_register(chip, apu, 3, freq >> 8);
+}
+
+/* spin lock held */
+static inline void snd_es1968_trigger_apu(struct es1968 *esm, int apu, int mode)
+{
+	/* set the APU mode */
+	__apu_set_register(esm, apu, 0,
+			   (__apu_get_register(esm, apu, 0) & 0xff0f) |
+			   (mode << 4));
+}
+
+static void snd_es1968_pcm_start(struct es1968 *chip, struct esschan *es)
+{
+	spin_lock(&chip->reg_lock);
+	__apu_set_register(chip, es->apu[0], 5, es->base[0]);
+	snd_es1968_trigger_apu(chip, es->apu[0], es->apu_mode[0]);
+	if (es->mode == ESM_MODE_CAPTURE) {
+		__apu_set_register(chip, es->apu[2], 5, es->base[2]);
+		snd_es1968_trigger_apu(chip, es->apu[2], es->apu_mode[2]);
+	}
+	if (es->fmt & ESS_FMT_STEREO) {
+		__apu_set_register(chip, es->apu[1], 5, es->base[1]);
+		snd_es1968_trigger_apu(chip, es->apu[1], es->apu_mode[1]);
+		if (es->mode == ESM_MODE_CAPTURE) {
+			__apu_set_register(chip, es->apu[3], 5, es->base[3]);
+			snd_es1968_trigger_apu(chip, es->apu[3], es->apu_mode[3]);
+		}
+	}
+	spin_unlock(&chip->reg_lock);
+}
+
+static void snd_es1968_pcm_stop(struct es1968 *chip, struct esschan *es)
+{
+	spin_lock(&chip->reg_lock);
+	snd_es1968_trigger_apu(chip, es->apu[0], 0);
+	snd_es1968_trigger_apu(chip, es->apu[1], 0);
+	if (es->mode == ESM_MODE_CAPTURE) {
+		snd_es1968_trigger_apu(chip, es->apu[2], 0);
+		snd_es1968_trigger_apu(chip, es->apu[3], 0);
+	}
+	spin_unlock(&chip->reg_lock);
+}
+
+/* set the wavecache control reg */
+static void snd_es1968_program_wavecache(struct es1968 *chip, struct esschan *es,
+					 int channel, u32 addr, int capture)
+{
+	u32 tmpval = (addr - 0x10) & 0xFFF8;
+
+	if (! capture) {
+		if (!(es->fmt & ESS_FMT_16BIT))
+			tmpval |= 4;	/* 8bit */
+		if (es->fmt & ESS_FMT_STEREO)
+			tmpval |= 2;	/* stereo */
+	}
+
+	/* set the wavecache control reg */
+	wave_set_register(chip, es->apu[channel] << 3, tmpval);
+
+#ifdef CONFIG_PM
+	es->wc_map[channel] = tmpval;
+#endif
+}
+
+
+static void snd_es1968_playback_setup(struct es1968 *chip, struct esschan *es,
+				      struct snd_pcm_runtime *runtime)
+{
+	u32 pa;
+	int high_apu = 0;
+	int channel, apu;
+	int i, size;
+	unsigned long flags;
+	u32 freq;
+
+	size = es->dma_size >> es->wav_shift;
+
+	if (es->fmt & ESS_FMT_STEREO)
+		high_apu++;
+
+	for (channel = 0; channel <= high_apu; channel++) {
+		apu = es->apu[channel];
+
+		snd_es1968_program_wavecache(chip, es, channel, es->memory->buf.addr, 0);
+
+		/* Offset to PCMBAR */
+		pa = es->memory->buf.addr;
+		pa -= chip->dma.addr;
+		pa >>= 1;	/* words */
+
+		pa |= 0x00400000;	/* System RAM (Bit 22) */
+
+		if (es->fmt & ESS_FMT_STEREO) {
+			/* Enable stereo */
+			if (channel)
+				pa |= 0x00800000;	/* (Bit 23) */
+			if (es->fmt & ESS_FMT_16BIT)
+				pa >>= 1;
+		}
+
+		/* base offset of dma calcs when reading the pointer
+		   on this left one */
+		es->base[channel] = pa & 0xFFFF;
+
+		for (i = 0; i < 16; i++)
+			apu_set_register(chip, apu, i, 0x0000);
+
+		/* Load the buffer into the wave engine */
+		apu_set_register(chip, apu, 4, ((pa >> 16) & 0xFF) << 8);
+		apu_set_register(chip, apu, 5, pa & 0xFFFF);
+		apu_set_register(chip, apu, 6, (pa + size) & 0xFFFF);
+		/* setting loop == sample len */
+		apu_set_register(chip, apu, 7, size);
+
+		/* clear effects/env.. */
+		apu_set_register(chip, apu, 8, 0x0000);
+		/* set amp now to 0xd0 (?), low byte is 'amplitude dest'? */
+		apu_set_register(chip, apu, 9, 0xD000);
+
+		/* clear routing stuff */
+		apu_set_register(chip, apu, 11, 0x0000);
+		/* dma on, no envelopes, filter to all 1s) */
+		apu_set_register(chip, apu, 0, 0x400F);
+
+		if (es->fmt & ESS_FMT_16BIT)
+			es->apu_mode[channel] = ESM_APU_16BITLINEAR;
+		else
+			es->apu_mode[channel] = ESM_APU_8BITLINEAR;
+
+		if (es->fmt & ESS_FMT_STEREO) {
+			/* set panning: left or right */
+			/* Check: different panning. On my Canyon 3D Chipset the
+			   Channels are swapped. I don't know, about the output
+			   to the SPDif Link. Perhaps you have to change this
+			   and not the APU Regs 4-5. */
+			apu_set_register(chip, apu, 10,
+					 0x8F00 | (channel ? 0 : 0x10));
+			es->apu_mode[channel] += 1;	/* stereo */
+		} else
+			apu_set_register(chip, apu, 10, 0x8F08);
+	}
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	/* clear WP interrupts */
+	outw(1, chip->io_port + 0x04);
+	/* enable WP ints */
+	outw(inw(chip->io_port + ESM_PORT_HOST_IRQ) | ESM_HIRQ_DSIE, chip->io_port + ESM_PORT_HOST_IRQ);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	freq = runtime->rate;
+	/* set frequency */
+	if (freq > 48000)
+		freq = 48000;
+	if (freq < 4000)
+		freq = 4000;
+
+	/* hmmm.. */
+	if (!(es->fmt & ESS_FMT_16BIT) && !(es->fmt & ESS_FMT_STEREO))
+		freq >>= 1;
+
+	freq = snd_es1968_compute_rate(chip, freq);
+
+	/* Load the frequency, turn on 6dB */
+	snd_es1968_apu_set_freq(chip, es->apu[0], freq);
+	snd_es1968_apu_set_freq(chip, es->apu[1], freq);
+}
+
+
+static void init_capture_apu(struct es1968 *chip, struct esschan *es, int channel,
+			     unsigned int pa, unsigned int bsize,
+			     int mode, int route)
+{
+	int i, apu = es->apu[channel];
+
+	es->apu_mode[channel] = mode;
+
+	/* set the wavecache control reg */
+	snd_es1968_program_wavecache(chip, es, channel, pa, 1);
+
+	/* Offset to PCMBAR */
+	pa -= chip->dma.addr;
+	pa >>= 1;	/* words */
+
+	/* base offset of dma calcs when reading the pointer
+	   on this left one */
+	es->base[channel] = pa & 0xFFFF;
+	pa |= 0x00400000;	/* bit 22 -> System RAM */
+
+	/* Begin loading the APU */
+	for (i = 0; i < 16; i++)
+		apu_set_register(chip, apu, i, 0x0000);
+
+	/* need to enable subgroups.. and we should probably
+	   have different groups for different /dev/dsps..  */
+	apu_set_register(chip, apu, 2, 0x8);
+
+	/* Load the buffer into the wave engine */
+	apu_set_register(chip, apu, 4, ((pa >> 16) & 0xFF) << 8);
+	apu_set_register(chip, apu, 5, pa & 0xFFFF);
+	apu_set_register(chip, apu, 6, (pa + bsize) & 0xFFFF);
+	apu_set_register(chip, apu, 7, bsize);
+	/* clear effects/env.. */
+	apu_set_register(chip, apu, 8, 0x00F0);
+	/* amplitude now?  sure.  why not.  */
+	apu_set_register(chip, apu, 9, 0x0000);
+	/* set filter tune, radius, polar pan */
+	apu_set_register(chip, apu, 10, 0x8F08);
+	/* route input */
+	apu_set_register(chip, apu, 11, route);
+	/* dma on, no envelopes, filter to all 1s) */
+	apu_set_register(chip, apu, 0, 0x400F);
+}
+
+static void snd_es1968_capture_setup(struct es1968 *chip, struct esschan *es,
+				     struct snd_pcm_runtime *runtime)
+{
+	int size;
+	u32 freq;
+	unsigned long flags;
+
+	size = es->dma_size >> es->wav_shift;
+
+	/* APU assignments:
+	   0 = mono/left SRC
+	   1 = right SRC
+	   2 = mono/left Input Mixer
+	   3 = right Input Mixer
+	*/
+	/* data seems to flow from the codec, through an apu into
+	   the 'mixbuf' bit of page, then through the SRC apu
+	   and out to the real 'buffer'.  ok.  sure.  */
+
+	/* input mixer (left/mono) */
+	/* parallel in crap, see maestro reg 0xC [8-11] */
+	init_capture_apu(chip, es, 2,
+			 es->mixbuf->buf.addr, ESM_MIXBUF_SIZE/4, /* in words */
+			 ESM_APU_INPUTMIXER, 0x14);
+	/* SRC (left/mono); get input from inputing apu */
+	init_capture_apu(chip, es, 0, es->memory->buf.addr, size,
+			 ESM_APU_SRCONVERTOR, es->apu[2]);
+	if (es->fmt & ESS_FMT_STEREO) {
+		/* input mixer (right) */
+		init_capture_apu(chip, es, 3,
+				 es->mixbuf->buf.addr + ESM_MIXBUF_SIZE/2,
+				 ESM_MIXBUF_SIZE/4, /* in words */
+				 ESM_APU_INPUTMIXER, 0x15);
+		/* SRC (right) */
+		init_capture_apu(chip, es, 1,
+				 es->memory->buf.addr + size*2, size,
+				 ESM_APU_SRCONVERTOR, es->apu[3]);
+	}
+
+	freq = runtime->rate;
+	/* Sample Rate conversion APUs don't like 0x10000 for their rate */
+	if (freq > 47999)
+		freq = 47999;
+	if (freq < 4000)
+		freq = 4000;
+
+	freq = snd_es1968_compute_rate(chip, freq);
+
+	/* Load the frequency, turn on 6dB */
+	snd_es1968_apu_set_freq(chip, es->apu[0], freq);
+	snd_es1968_apu_set_freq(chip, es->apu[1], freq);
+
+	/* fix mixer rate at 48khz.  and its _must_ be 0x10000. */
+	freq = 0x10000;
+	snd_es1968_apu_set_freq(chip, es->apu[2], freq);
+	snd_es1968_apu_set_freq(chip, es->apu[3], freq);
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	/* clear WP interrupts */
+	outw(1, chip->io_port + 0x04);
+	/* enable WP ints */
+	outw(inw(chip->io_port + ESM_PORT_HOST_IRQ) | ESM_HIRQ_DSIE, chip->io_port + ESM_PORT_HOST_IRQ);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/*******************
+ *  ALSA Interface *
+ *******************/
+
+static int snd_es1968_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct es1968 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct esschan *es = runtime->private_data;
+
+	es->dma_size = snd_pcm_lib_buffer_bytes(substream);
+	es->frag_size = snd_pcm_lib_period_bytes(substream);
+
+	es->wav_shift = 1; /* maestro handles always 16bit */
+	es->fmt = 0;
+	if (snd_pcm_format_width(runtime->format) == 16)
+		es->fmt |= ESS_FMT_16BIT;
+	if (runtime->channels > 1) {
+		es->fmt |= ESS_FMT_STEREO;
+		if (es->fmt & ESS_FMT_16BIT) /* 8bit is already word shifted */
+			es->wav_shift++;
+	}
+	es->bob_freq = snd_es1968_calc_bob_rate(chip, es, runtime);
+
+	switch (es->mode) {
+	case ESM_MODE_PLAY:
+		snd_es1968_playback_setup(chip, es, runtime);
+		break;
+	case ESM_MODE_CAPTURE:
+		snd_es1968_capture_setup(chip, es, runtime);
+		break;
+	}
+
+	return 0;
+}
+
+static int snd_es1968_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct es1968 *chip = snd_pcm_substream_chip(substream);
+	struct esschan *es = substream->runtime->private_data;
+
+	spin_lock(&chip->substream_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (es->running)
+			break;
+		snd_es1968_bob_inc(chip, es->bob_freq);
+		es->count = 0;
+		es->hwptr = 0;
+		snd_es1968_pcm_start(chip, es);
+		es->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (! es->running)
+			break;
+		snd_es1968_pcm_stop(chip, es);
+		es->running = 0;
+		snd_es1968_bob_dec(chip);
+		break;
+	}
+	spin_unlock(&chip->substream_lock);
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_es1968_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct es1968 *chip = snd_pcm_substream_chip(substream);
+	struct esschan *es = substream->runtime->private_data;
+	unsigned int ptr;
+
+	ptr = snd_es1968_get_dma_ptr(chip, es) << es->wav_shift;
+	
+	return bytes_to_frames(substream->runtime, ptr % es->dma_size);
+}
+
+static struct snd_pcm_hardware snd_es1968_playback = {
+	.info =			(SNDRV_PCM_INFO_MMAP |
+               		         SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 /*SNDRV_PCM_INFO_PAUSE |*/
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	256,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_es1968_capture = {
+	.info =			(SNDRV_PCM_INFO_NONINTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 /*SNDRV_PCM_INFO_PAUSE |*/
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		/*SNDRV_PCM_FMTBIT_U8 |*/ SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	65536,
+	.period_bytes_min =	256,
+	.period_bytes_max =	65536,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/* *************************
+   * DMA memory management *
+   *************************/
+
+/* Because the Maestro can only take addresses relative to the PCM base address
+   register :( */
+
+static int calc_available_memory_size(struct es1968 *chip)
+{
+	int max_size = 0;
+	struct esm_memory *buf;
+
+	mutex_lock(&chip->memory_mutex);
+	list_for_each_entry(buf, &chip->buf_list, list) {
+		if (buf->empty && buf->buf.bytes > max_size)
+			max_size = buf->buf.bytes;
+	}
+	mutex_unlock(&chip->memory_mutex);
+	if (max_size >= 128*1024)
+		max_size = 127*1024;
+	return max_size;
+}
+
+/* allocate a new memory chunk with the specified size */
+static struct esm_memory *snd_es1968_new_memory(struct es1968 *chip, int size)
+{
+	struct esm_memory *buf;
+
+	size = ALIGN(size, ESM_MEM_ALIGN);
+	mutex_lock(&chip->memory_mutex);
+	list_for_each_entry(buf, &chip->buf_list, list) {
+		if (buf->empty && buf->buf.bytes >= size)
+			goto __found;
+	}
+	mutex_unlock(&chip->memory_mutex);
+	return NULL;
+
+__found:
+	if (buf->buf.bytes > size) {
+		struct esm_memory *chunk = kmalloc(sizeof(*chunk), GFP_KERNEL);
+		if (chunk == NULL) {
+			mutex_unlock(&chip->memory_mutex);
+			return NULL;
+		}
+		chunk->buf = buf->buf;
+		chunk->buf.bytes -= size;
+		chunk->buf.area += size;
+		chunk->buf.addr += size;
+		chunk->empty = 1;
+		buf->buf.bytes = size;
+		list_add(&chunk->list, &buf->list);
+	}
+	buf->empty = 0;
+	mutex_unlock(&chip->memory_mutex);
+	return buf;
+}
+
+/* free a memory chunk */
+static void snd_es1968_free_memory(struct es1968 *chip, struct esm_memory *buf)
+{
+	struct esm_memory *chunk;
+
+	mutex_lock(&chip->memory_mutex);
+	buf->empty = 1;
+	if (buf->list.prev != &chip->buf_list) {
+		chunk = list_entry(buf->list.prev, struct esm_memory, list);
+		if (chunk->empty) {
+			chunk->buf.bytes += buf->buf.bytes;
+			list_del(&buf->list);
+			kfree(buf);
+			buf = chunk;
+		}
+	}
+	if (buf->list.next != &chip->buf_list) {
+		chunk = list_entry(buf->list.next, struct esm_memory, list);
+		if (chunk->empty) {
+			buf->buf.bytes += chunk->buf.bytes;
+			list_del(&chunk->list);
+			kfree(chunk);
+		}
+	}
+	mutex_unlock(&chip->memory_mutex);
+}
+
+static void snd_es1968_free_dmabuf(struct es1968 *chip)
+{
+	struct list_head *p;
+
+	if (! chip->dma.area)
+		return;
+	snd_dma_reserve_buf(&chip->dma, snd_dma_pci_buf_id(chip->pci));
+	while ((p = chip->buf_list.next) != &chip->buf_list) {
+		struct esm_memory *chunk = list_entry(p, struct esm_memory, list);
+		list_del(p);
+		kfree(chunk);
+	}
+}
+
+static int __devinit
+snd_es1968_init_dmabuf(struct es1968 *chip)
+{
+	int err;
+	struct esm_memory *chunk;
+
+	chip->dma.dev.type = SNDRV_DMA_TYPE_DEV;
+	chip->dma.dev.dev = snd_dma_pci_data(chip->pci);
+	if (! snd_dma_get_reserved_buf(&chip->dma, snd_dma_pci_buf_id(chip->pci))) {
+		err = snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV,
+						   snd_dma_pci_data(chip->pci),
+						   chip->total_bufsize, &chip->dma);
+		if (err < 0 || ! chip->dma.area) {
+			snd_printk(KERN_ERR "es1968: can't allocate dma pages for size %d\n",
+				   chip->total_bufsize);
+			return -ENOMEM;
+		}
+		if ((chip->dma.addr + chip->dma.bytes - 1) & ~((1 << 28) - 1)) {
+			snd_dma_free_pages(&chip->dma);
+			snd_printk(KERN_ERR "es1968: DMA buffer beyond 256MB.\n");
+			return -ENOMEM;
+		}
+	}
+
+	INIT_LIST_HEAD(&chip->buf_list);
+	/* allocate an empty chunk */
+	chunk = kmalloc(sizeof(*chunk), GFP_KERNEL);
+	if (chunk == NULL) {
+		snd_es1968_free_dmabuf(chip);
+		return -ENOMEM;
+	}
+	memset(chip->dma.area, 0, ESM_MEM_ALIGN);
+	chunk->buf = chip->dma;
+	chunk->buf.area += ESM_MEM_ALIGN;
+	chunk->buf.addr += ESM_MEM_ALIGN;
+	chunk->buf.bytes -= ESM_MEM_ALIGN;
+	chunk->empty = 1;
+	list_add(&chunk->list, &chip->buf_list);
+
+	return 0;
+}
+
+/* setup the dma_areas */
+/* buffer is extracted from the pre-allocated memory chunk */
+static int snd_es1968_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *hw_params)
+{
+	struct es1968 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct esschan *chan = runtime->private_data;
+	int size = params_buffer_bytes(hw_params);
+
+	if (chan->memory) {
+		if (chan->memory->buf.bytes >= size) {
+			runtime->dma_bytes = size;
+			return 0;
+		}
+		snd_es1968_free_memory(chip, chan->memory);
+	}
+	chan->memory = snd_es1968_new_memory(chip, size);
+	if (chan->memory == NULL) {
+		// snd_printd("cannot allocate dma buffer: size = %d\n", size);
+		return -ENOMEM;
+	}
+	snd_pcm_set_runtime_buffer(substream, &chan->memory->buf);
+	return 1; /* area was changed */
+}
+
+/* remove dma areas if allocated */
+static int snd_es1968_hw_free(struct snd_pcm_substream *substream)
+{
+	struct es1968 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct esschan *chan;
+	
+	if (runtime->private_data == NULL)
+		return 0;
+	chan = runtime->private_data;
+	if (chan->memory) {
+		snd_es1968_free_memory(chip, chan->memory);
+		chan->memory = NULL;
+	}
+	return 0;
+}
+
+
+/*
+ * allocate APU pair
+ */
+static int snd_es1968_alloc_apu_pair(struct es1968 *chip, int type)
+{
+	int apu;
+
+	for (apu = 0; apu < NR_APUS; apu += 2) {
+		if (chip->apu[apu] == ESM_APU_FREE &&
+		    chip->apu[apu + 1] == ESM_APU_FREE) {
+			chip->apu[apu] = chip->apu[apu + 1] = type;
+			return apu;
+		}
+	}
+	return -EBUSY;
+}
+
+/*
+ * release APU pair
+ */
+static void snd_es1968_free_apu_pair(struct es1968 *chip, int apu)
+{
+	chip->apu[apu] = chip->apu[apu + 1] = ESM_APU_FREE;
+}
+
+
+/******************
+ * PCM open/close *
+ ******************/
+
+static int snd_es1968_playback_open(struct snd_pcm_substream *substream)
+{
+	struct es1968 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct esschan *es;
+	int apu1;
+
+	/* search 2 APUs */
+	apu1 = snd_es1968_alloc_apu_pair(chip, ESM_APU_PCM_PLAY);
+	if (apu1 < 0)
+		return apu1;
+
+	es = kzalloc(sizeof(*es), GFP_KERNEL);
+	if (!es) {
+		snd_es1968_free_apu_pair(chip, apu1);
+		return -ENOMEM;
+	}
+
+	es->apu[0] = apu1;
+	es->apu[1] = apu1 + 1;
+	es->apu_mode[0] = 0;
+	es->apu_mode[1] = 0;
+	es->running = 0;
+	es->substream = substream;
+	es->mode = ESM_MODE_PLAY;
+
+	runtime->private_data = es;
+	runtime->hw = snd_es1968_playback;
+	runtime->hw.buffer_bytes_max = runtime->hw.period_bytes_max =
+		calc_available_memory_size(chip);
+
+	spin_lock_irq(&chip->substream_lock);
+	list_add(&es->list, &chip->substream_list);
+	spin_unlock_irq(&chip->substream_lock);
+
+	return 0;
+}
+
+static int snd_es1968_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct es1968 *chip = snd_pcm_substream_chip(substream);
+	struct esschan *es;
+	int apu1, apu2;
+
+	apu1 = snd_es1968_alloc_apu_pair(chip, ESM_APU_PCM_CAPTURE);
+	if (apu1 < 0)
+		return apu1;
+	apu2 = snd_es1968_alloc_apu_pair(chip, ESM_APU_PCM_RATECONV);
+	if (apu2 < 0) {
+		snd_es1968_free_apu_pair(chip, apu1);
+		return apu2;
+	}
+	
+	es = kzalloc(sizeof(*es), GFP_KERNEL);
+	if (!es) {
+		snd_es1968_free_apu_pair(chip, apu1);
+		snd_es1968_free_apu_pair(chip, apu2);
+		return -ENOMEM;
+	}
+
+	es->apu[0] = apu1;
+	es->apu[1] = apu1 + 1;
+	es->apu[2] = apu2;
+	es->apu[3] = apu2 + 1;
+	es->apu_mode[0] = 0;
+	es->apu_mode[1] = 0;
+	es->apu_mode[2] = 0;
+	es->apu_mode[3] = 0;
+	es->running = 0;
+	es->substream = substream;
+	es->mode = ESM_MODE_CAPTURE;
+
+	/* get mixbuffer */
+	if ((es->mixbuf = snd_es1968_new_memory(chip, ESM_MIXBUF_SIZE)) == NULL) {
+		snd_es1968_free_apu_pair(chip, apu1);
+		snd_es1968_free_apu_pair(chip, apu2);
+		kfree(es);
+                return -ENOMEM;
+        }
+	memset(es->mixbuf->buf.area, 0, ESM_MIXBUF_SIZE);
+
+	runtime->private_data = es;
+	runtime->hw = snd_es1968_capture;
+	runtime->hw.buffer_bytes_max = runtime->hw.period_bytes_max =
+		calc_available_memory_size(chip) - 1024; /* keep MIXBUF size */
+	snd_pcm_hw_constraint_pow2(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES);
+
+	spin_lock_irq(&chip->substream_lock);
+	list_add(&es->list, &chip->substream_list);
+	spin_unlock_irq(&chip->substream_lock);
+
+	return 0;
+}
+
+static int snd_es1968_playback_close(struct snd_pcm_substream *substream)
+{
+	struct es1968 *chip = snd_pcm_substream_chip(substream);
+	struct esschan *es;
+
+	if (substream->runtime->private_data == NULL)
+		return 0;
+	es = substream->runtime->private_data;
+	spin_lock_irq(&chip->substream_lock);
+	list_del(&es->list);
+	spin_unlock_irq(&chip->substream_lock);
+	snd_es1968_free_apu_pair(chip, es->apu[0]);
+	kfree(es);
+
+	return 0;
+}
+
+static int snd_es1968_capture_close(struct snd_pcm_substream *substream)
+{
+	struct es1968 *chip = snd_pcm_substream_chip(substream);
+	struct esschan *es;
+
+	if (substream->runtime->private_data == NULL)
+		return 0;
+	es = substream->runtime->private_data;
+	spin_lock_irq(&chip->substream_lock);
+	list_del(&es->list);
+	spin_unlock_irq(&chip->substream_lock);
+	snd_es1968_free_memory(chip, es->mixbuf);
+	snd_es1968_free_apu_pair(chip, es->apu[0]);
+	snd_es1968_free_apu_pair(chip, es->apu[2]);
+	kfree(es);
+
+	return 0;
+}
+
+static struct snd_pcm_ops snd_es1968_playback_ops = {
+	.open =		snd_es1968_playback_open,
+	.close =	snd_es1968_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_es1968_hw_params,
+	.hw_free =	snd_es1968_hw_free,
+	.prepare =	snd_es1968_pcm_prepare,
+	.trigger =	snd_es1968_pcm_trigger,
+	.pointer =	snd_es1968_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_es1968_capture_ops = {
+	.open =		snd_es1968_capture_open,
+	.close =	snd_es1968_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_es1968_hw_params,
+	.hw_free =	snd_es1968_hw_free,
+	.prepare =	snd_es1968_pcm_prepare,
+	.trigger =	snd_es1968_pcm_trigger,
+	.pointer =	snd_es1968_pcm_pointer,
+};
+
+
+/*
+ * measure clock
+ */
+#define CLOCK_MEASURE_BUFSIZE	16768	/* enough large for a single shot */
+
+static void __devinit es1968_measure_clock(struct es1968 *chip)
+{
+	int i, apu;
+	unsigned int pa, offset, t;
+	struct esm_memory *memory;
+	struct timeval start_time, stop_time;
+
+	if (chip->clock == 0)
+		chip->clock = 48000; /* default clock value */
+
+	/* search 2 APUs (although one apu is enough) */
+	if ((apu = snd_es1968_alloc_apu_pair(chip, ESM_APU_PCM_PLAY)) < 0) {
+		snd_printk(KERN_ERR "Hmm, cannot find empty APU pair!?\n");
+		return;
+	}
+	if ((memory = snd_es1968_new_memory(chip, CLOCK_MEASURE_BUFSIZE)) == NULL) {
+		snd_printk(KERN_ERR "cannot allocate dma buffer - using default clock %d\n", chip->clock);
+		snd_es1968_free_apu_pair(chip, apu);
+		return;
+	}
+
+	memset(memory->buf.area, 0, CLOCK_MEASURE_BUFSIZE);
+
+	wave_set_register(chip, apu << 3, (memory->buf.addr - 0x10) & 0xfff8);
+
+	pa = (unsigned int)((memory->buf.addr - chip->dma.addr) >> 1);
+	pa |= 0x00400000;	/* System RAM (Bit 22) */
+
+	/* initialize apu */
+	for (i = 0; i < 16; i++)
+		apu_set_register(chip, apu, i, 0x0000);
+
+	apu_set_register(chip, apu, 0, 0x400f);
+	apu_set_register(chip, apu, 4, ((pa >> 16) & 0xff) << 8);
+	apu_set_register(chip, apu, 5, pa & 0xffff);
+	apu_set_register(chip, apu, 6, (pa + CLOCK_MEASURE_BUFSIZE/2) & 0xffff);
+	apu_set_register(chip, apu, 7, CLOCK_MEASURE_BUFSIZE/2);
+	apu_set_register(chip, apu, 8, 0x0000);
+	apu_set_register(chip, apu, 9, 0xD000);
+	apu_set_register(chip, apu, 10, 0x8F08);
+	apu_set_register(chip, apu, 11, 0x0000);
+	spin_lock_irq(&chip->reg_lock);
+	outw(1, chip->io_port + 0x04); /* clear WP interrupts */
+	outw(inw(chip->io_port + ESM_PORT_HOST_IRQ) | ESM_HIRQ_DSIE, chip->io_port + ESM_PORT_HOST_IRQ); /* enable WP ints */
+	spin_unlock_irq(&chip->reg_lock);
+
+	snd_es1968_apu_set_freq(chip, apu, ((unsigned int)48000 << 16) / chip->clock); /* 48000 Hz */
+
+	chip->in_measurement = 1;
+	chip->measure_apu = apu;
+	spin_lock_irq(&chip->reg_lock);
+	snd_es1968_bob_inc(chip, ESM_BOB_FREQ);
+	__apu_set_register(chip, apu, 5, pa & 0xffff);
+	snd_es1968_trigger_apu(chip, apu, ESM_APU_16BITLINEAR);
+	do_gettimeofday(&start_time);
+	spin_unlock_irq(&chip->reg_lock);
+	msleep(50);
+	spin_lock_irq(&chip->reg_lock);
+	offset = __apu_get_register(chip, apu, 5);
+	do_gettimeofday(&stop_time);
+	snd_es1968_trigger_apu(chip, apu, 0); /* stop */
+	snd_es1968_bob_dec(chip);
+	chip->in_measurement = 0;
+	spin_unlock_irq(&chip->reg_lock);
+
+	/* check the current position */
+	offset -= (pa & 0xffff);
+	offset &= 0xfffe;
+	offset += chip->measure_count * (CLOCK_MEASURE_BUFSIZE/2);
+
+	t = stop_time.tv_sec - start_time.tv_sec;
+	t *= 1000000;
+	if (stop_time.tv_usec < start_time.tv_usec)
+		t -= start_time.tv_usec - stop_time.tv_usec;
+	else
+		t += stop_time.tv_usec - start_time.tv_usec;
+	if (t == 0) {
+		snd_printk(KERN_ERR "?? calculation error..\n");
+	} else {
+		offset *= 1000;
+		offset = (offset / t) * 1000 + ((offset % t) * 1000) / t;
+		if (offset < 47500 || offset > 48500) {
+			if (offset >= 40000 && offset <= 50000)
+				chip->clock = (chip->clock * offset) / 48000;
+		}
+		printk(KERN_INFO "es1968: clocking to %d\n", chip->clock);
+	}
+	snd_es1968_free_memory(chip, memory);
+	snd_es1968_free_apu_pair(chip, apu);
+}
+
+
+/*
+ */
+
+static void snd_es1968_pcm_free(struct snd_pcm *pcm)
+{
+	struct es1968 *esm = pcm->private_data;
+	snd_es1968_free_dmabuf(esm);
+	esm->pcm = NULL;
+}
+
+static int __devinit
+snd_es1968_pcm(struct es1968 *chip, int device)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	/* get DMA buffer */
+	if ((err = snd_es1968_init_dmabuf(chip)) < 0)
+		return err;
+
+	/* set PCMBAR */
+	wave_set_register(chip, 0x01FC, chip->dma.addr >> 12);
+	wave_set_register(chip, 0x01FD, chip->dma.addr >> 12);
+	wave_set_register(chip, 0x01FE, chip->dma.addr >> 12);
+	wave_set_register(chip, 0x01FF, chip->dma.addr >> 12);
+
+	if ((err = snd_pcm_new(chip->card, "ESS Maestro", device,
+			       chip->playback_streams,
+			       chip->capture_streams, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = chip;
+	pcm->private_free = snd_es1968_pcm_free;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es1968_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es1968_capture_ops);
+
+	pcm->info_flags = 0;
+
+	strcpy(pcm->name, "ESS Maestro");
+
+	chip->pcm = pcm;
+
+	return 0;
+}
+/*
+ * suppress jitter on some maestros when playing stereo
+ */
+static void snd_es1968_suppress_jitter(struct es1968 *chip, struct esschan *es)
+{
+	unsigned int cp1;
+	unsigned int cp2;
+	unsigned int diff;
+
+	cp1 = __apu_get_register(chip, 0, 5);
+	cp2 = __apu_get_register(chip, 1, 5);
+	diff = (cp1 > cp2 ? cp1 - cp2 : cp2 - cp1);
+
+	if (diff > 1)
+		__maestro_write(chip, IDR0_DATA_PORT, cp1);
+}
+
+/*
+ * update pointer
+ */
+static void snd_es1968_update_pcm(struct es1968 *chip, struct esschan *es)
+{
+	unsigned int hwptr;
+	unsigned int diff;
+	struct snd_pcm_substream *subs = es->substream;
+        
+	if (subs == NULL || !es->running)
+		return;
+
+	hwptr = snd_es1968_get_dma_ptr(chip, es) << es->wav_shift;
+	hwptr %= es->dma_size;
+
+	diff = (es->dma_size + hwptr - es->hwptr) % es->dma_size;
+
+	es->hwptr = hwptr;
+	es->count += diff;
+
+	if (es->count > es->frag_size) {
+		spin_unlock(&chip->substream_lock);
+		snd_pcm_period_elapsed(subs);
+		spin_lock(&chip->substream_lock);
+		es->count %= es->frag_size;
+	}
+}
+
+/* The hardware volume works by incrementing / decrementing 2 counters
+   (without wrap around) in response to volume button presses and then
+   generating an interrupt. The pair of counters is stored in bits 1-3 and 5-7
+   of a byte wide register. The meaning of bits 0 and 4 is unknown. */
+static void es1968_update_hw_volume(unsigned long private_data)
+{
+	struct es1968 *chip = (struct es1968 *) private_data;
+	int x, val;
+#ifndef CONFIG_SND_ES1968_INPUT
+	unsigned long flags;
+#endif
+
+	/* Figure out which volume control button was pushed,
+	   based on differences from the default register
+	   values. */
+	x = inb(chip->io_port + 0x1c) & 0xee;
+	/* Reset the volume control registers. */
+	outb(0x88, chip->io_port + 0x1c);
+	outb(0x88, chip->io_port + 0x1d);
+	outb(0x88, chip->io_port + 0x1e);
+	outb(0x88, chip->io_port + 0x1f);
+
+	if (chip->in_suspend)
+		return;
+
+#ifndef CONFIG_SND_ES1968_INPUT
+	if (! chip->master_switch || ! chip->master_volume)
+		return;
+
+	/* FIXME: we can't call snd_ac97_* functions since here is in tasklet. */
+	spin_lock_irqsave(&chip->ac97_lock, flags);
+	val = chip->ac97->regs[AC97_MASTER];
+	switch (x) {
+	case 0x88:
+		/* mute */
+		val ^= 0x8000;
+		chip->ac97->regs[AC97_MASTER] = val;
+		outw(val, chip->io_port + ESM_AC97_DATA);
+		outb(AC97_MASTER, chip->io_port + ESM_AC97_INDEX);
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->master_switch->id);
+		break;
+	case 0xaa:
+		/* volume up */
+		if ((val & 0x7f) > 0)
+			val--;
+		if ((val & 0x7f00) > 0)
+			val -= 0x0100;
+		chip->ac97->regs[AC97_MASTER] = val;
+		outw(val, chip->io_port + ESM_AC97_DATA);
+		outb(AC97_MASTER, chip->io_port + ESM_AC97_INDEX);
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->master_volume->id);
+		break;
+	case 0x66:
+		/* volume down */
+		if ((val & 0x7f) < 0x1f)
+			val++;
+		if ((val & 0x7f00) < 0x1f00)
+			val += 0x0100;
+		chip->ac97->regs[AC97_MASTER] = val;
+		outw(val, chip->io_port + ESM_AC97_DATA);
+		outb(AC97_MASTER, chip->io_port + ESM_AC97_INDEX);
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->master_volume->id);
+		break;
+	}
+	spin_unlock_irqrestore(&chip->ac97_lock, flags);
+#else
+	if (!chip->input_dev)
+		return;
+
+	val = 0;
+	switch (x) {
+	case 0x88:
+		/* The counters have not changed, yet we've received a HV
+		   interrupt. According to tests run by various people this
+		   happens when pressing the mute button. */
+		val = KEY_MUTE;
+		break;
+	case 0xaa:
+		/* counters increased by 1 -> volume up */
+		val = KEY_VOLUMEUP;
+		break;
+	case 0x66:
+		/* counters decreased by 1 -> volume down */
+		val = KEY_VOLUMEDOWN;
+		break;
+	}
+
+	if (val) {
+		input_report_key(chip->input_dev, val, 1);
+		input_sync(chip->input_dev);
+		input_report_key(chip->input_dev, val, 0);
+		input_sync(chip->input_dev);
+	}
+#endif
+}
+
+/*
+ * interrupt handler
+ */
+static irqreturn_t snd_es1968_interrupt(int irq, void *dev_id)
+{
+	struct es1968 *chip = dev_id;
+	u32 event;
+
+	if (!(event = inb(chip->io_port + 0x1A)))
+		return IRQ_NONE;
+
+	outw(inw(chip->io_port + 4) & 1, chip->io_port + 4);
+
+	if (event & ESM_HWVOL_IRQ)
+#ifdef CONFIG_SND_ES1968_INPUT
+		es1968_update_hw_volume((unsigned long)chip);
+#else
+		tasklet_schedule(&chip->hwvol_tq); /* we'll do this later */
+#endif
+
+	/* else ack 'em all, i imagine */
+	outb(0xFF, chip->io_port + 0x1A);
+
+	if ((event & ESM_MPU401_IRQ) && chip->rmidi) {
+		snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+	}
+
+	if (event & ESM_SOUND_IRQ) {
+		struct esschan *es;
+		spin_lock(&chip->substream_lock);
+		list_for_each_entry(es, &chip->substream_list, list) {
+			if (es->running) {
+				snd_es1968_update_pcm(chip, es);
+				if (es->fmt & ESS_FMT_STEREO)
+					snd_es1968_suppress_jitter(chip, es);
+			}
+		}
+		spin_unlock(&chip->substream_lock);
+		if (chip->in_measurement) {
+			unsigned int curp = __apu_get_register(chip, chip->measure_apu, 5);
+			if (curp < chip->measure_lastpos)
+				chip->measure_count++;
+			chip->measure_lastpos = curp;
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+/*
+ *  Mixer stuff
+ */
+
+static int __devinit
+snd_es1968_mixer(struct es1968 *chip)
+{
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+#ifndef CONFIG_SND_ES1968_INPUT
+	struct snd_ctl_elem_id elem_id;
+#endif
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_es1968_ac97_write,
+		.read = snd_es1968_ac97_read,
+	};
+
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus)) < 0)
+		return err;
+	pbus->no_vra = 1; /* ES1968 doesn't need VRA */
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97)) < 0)
+		return err;
+
+#ifndef CONFIG_SND_ES1968_INPUT
+	/* attach master switch / volumes for h/w volume control */
+	memset(&elem_id, 0, sizeof(elem_id));
+	elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(elem_id.name, "Master Playback Switch");
+	chip->master_switch = snd_ctl_find_id(chip->card, &elem_id);
+	memset(&elem_id, 0, sizeof(elem_id));
+	elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(elem_id.name, "Master Playback Volume");
+	chip->master_volume = snd_ctl_find_id(chip->card, &elem_id);
+#endif
+
+	return 0;
+}
+
+/*
+ * reset ac97 codec
+ */
+
+static void snd_es1968_ac97_reset(struct es1968 *chip)
+{
+	unsigned long ioaddr = chip->io_port;
+
+	unsigned short save_ringbus_a;
+	unsigned short save_68;
+	unsigned short w;
+	unsigned int vend;
+
+	/* save configuration */
+	save_ringbus_a = inw(ioaddr + 0x36);
+
+	//outw(inw(ioaddr + 0x38) & 0xfffc, ioaddr + 0x38); /* clear second codec id? */
+	/* set command/status address i/o to 1st codec */
+	outw(inw(ioaddr + 0x3a) & 0xfffc, ioaddr + 0x3a);
+	outw(inw(ioaddr + 0x3c) & 0xfffc, ioaddr + 0x3c);
+
+	/* disable ac link */
+	outw(0x0000, ioaddr + 0x36);
+	save_68 = inw(ioaddr + 0x68);
+	pci_read_config_word(chip->pci, 0x58, &w);	/* something magical with gpio and bus arb. */
+	pci_read_config_dword(chip->pci, PCI_SUBSYSTEM_VENDOR_ID, &vend);
+	if (w & 1)
+		save_68 |= 0x10;
+	outw(0xfffe, ioaddr + 0x64);	/* unmask gpio 0 */
+	outw(0x0001, ioaddr + 0x68);	/* gpio write */
+	outw(0x0000, ioaddr + 0x60);	/* write 0 to gpio 0 */
+	udelay(20);
+	outw(0x0001, ioaddr + 0x60);	/* write 1 to gpio 1 */
+	msleep(20);
+
+	outw(save_68 | 0x1, ioaddr + 0x68);	/* now restore .. */
+	outw((inw(ioaddr + 0x38) & 0xfffc) | 0x1, ioaddr + 0x38);
+	outw((inw(ioaddr + 0x3a) & 0xfffc) | 0x1, ioaddr + 0x3a);
+	outw((inw(ioaddr + 0x3c) & 0xfffc) | 0x1, ioaddr + 0x3c);
+
+	/* now the second codec */
+	/* disable ac link */
+	outw(0x0000, ioaddr + 0x36);
+	outw(0xfff7, ioaddr + 0x64);	/* unmask gpio 3 */
+	save_68 = inw(ioaddr + 0x68);
+	outw(0x0009, ioaddr + 0x68);	/* gpio write 0 & 3 ?? */
+	outw(0x0001, ioaddr + 0x60);	/* write 1 to gpio */
+	udelay(20);
+	outw(0x0009, ioaddr + 0x60);	/* write 9 to gpio */
+	msleep(500);
+	//outw(inw(ioaddr + 0x38) & 0xfffc, ioaddr + 0x38);
+	outw(inw(ioaddr + 0x3a) & 0xfffc, ioaddr + 0x3a);
+	outw(inw(ioaddr + 0x3c) & 0xfffc, ioaddr + 0x3c);
+
+#if 0				/* the loop here needs to be much better if we want it.. */
+	snd_printk(KERN_INFO "trying software reset\n");
+	/* try and do a software reset */
+	outb(0x80 | 0x7c, ioaddr + 0x30);
+	for (w = 0;; w++) {
+		if ((inw(ioaddr + 0x30) & 1) == 0) {
+			if (inb(ioaddr + 0x32) != 0)
+				break;
+
+			outb(0x80 | 0x7d, ioaddr + 0x30);
+			if (((inw(ioaddr + 0x30) & 1) == 0)
+			    && (inb(ioaddr + 0x32) != 0))
+				break;
+			outb(0x80 | 0x7f, ioaddr + 0x30);
+			if (((inw(ioaddr + 0x30) & 1) == 0)
+			    && (inb(ioaddr + 0x32) != 0))
+				break;
+		}
+
+		if (w > 10000) {
+			outb(inb(ioaddr + 0x37) | 0x08, ioaddr + 0x37);	/* do a software reset */
+			msleep(500);	/* oh my.. */
+			outb(inb(ioaddr + 0x37) & ~0x08,
+				ioaddr + 0x37);
+			udelay(1);
+			outw(0x80, ioaddr + 0x30);
+			for (w = 0; w < 10000; w++) {
+				if ((inw(ioaddr + 0x30) & 1) == 0)
+					break;
+			}
+		}
+	}
+#endif
+	if (vend == NEC_VERSA_SUBID1 || vend == NEC_VERSA_SUBID2) {
+		/* turn on external amp? */
+		outw(0xf9ff, ioaddr + 0x64);
+		outw(inw(ioaddr + 0x68) | 0x600, ioaddr + 0x68);
+		outw(0x0209, ioaddr + 0x60);
+	}
+
+	/* restore.. */
+	outw(save_ringbus_a, ioaddr + 0x36);
+
+	/* Turn on the 978 docking chip.
+	   First frob the "master output enable" bit,
+	   then set most of the playback volume control registers to max. */
+	outb(inb(ioaddr+0xc0)|(1<<5), ioaddr+0xc0);
+	outb(0xff, ioaddr+0xc3);
+	outb(0xff, ioaddr+0xc4);
+	outb(0xff, ioaddr+0xc6);
+	outb(0xff, ioaddr+0xc8);
+	outb(0x3f, ioaddr+0xcf);
+	outb(0x3f, ioaddr+0xd0);
+}
+
+static void snd_es1968_reset(struct es1968 *chip)
+{
+	/* Reset */
+	outw(ESM_RESET_MAESTRO | ESM_RESET_DIRECTSOUND,
+	     chip->io_port + ESM_PORT_HOST_IRQ);
+	udelay(10);
+	outw(0x0000, chip->io_port + ESM_PORT_HOST_IRQ);
+	udelay(10);
+}
+
+/*
+ * initialize maestro chip
+ */
+static void snd_es1968_chip_init(struct es1968 *chip)
+{
+	struct pci_dev *pci = chip->pci;
+	int i;
+	unsigned long iobase  = chip->io_port;
+	u16 w;
+	u32 n;
+
+	/* We used to muck around with pci config space that
+	 * we had no business messing with.  We don't know enough
+	 * about the machine to know which DMA mode is appropriate, 
+	 * etc.  We were guessing wrong on some machines and making
+	 * them unhappy.  We now trust in the BIOS to do things right,
+	 * which almost certainly means a new host of problems will
+	 * arise with broken BIOS implementations.  screw 'em. 
+	 * We're already intolerant of machines that don't assign
+	 * IRQs.
+	 */
+	
+	/* Config Reg A */
+	pci_read_config_word(pci, ESM_CONFIG_A, &w);
+
+	w &= ~DMA_CLEAR;	/* Clear DMA bits */
+	w &= ~(PIC_SNOOP1 | PIC_SNOOP2);	/* Clear Pic Snoop Mode Bits */
+	w &= ~SAFEGUARD;	/* Safeguard off */
+	w |= POST_WRITE;	/* Posted write */
+	w |= PCI_TIMING;	/* PCI timing on */
+	/* XXX huh?  claims to be reserved.. */
+	w &= ~SWAP_LR;		/* swap left/right 
+				   seems to only have effect on SB
+				   Emulation */
+	w &= ~SUBTR_DECODE;	/* Subtractive decode off */
+
+	pci_write_config_word(pci, ESM_CONFIG_A, w);
+
+	/* Config Reg B */
+
+	pci_read_config_word(pci, ESM_CONFIG_B, &w);
+
+	w &= ~(1 << 15);	/* Turn off internal clock multiplier */
+	/* XXX how do we know which to use? */
+	w &= ~(1 << 14);	/* External clock */
+
+	w &= ~SPDIF_CONFB;	/* disable S/PDIF output */
+	w |= HWV_CONFB;		/* HWV on */
+	w |= DEBOUNCE;		/* Debounce off: easier to push the HW buttons */
+	w &= ~GPIO_CONFB;	/* GPIO 4:5 */
+	w |= CHI_CONFB;		/* Disconnect from the CHI.  Enabling this made a dell 7500 work. */
+	w &= ~IDMA_CONFB;	/* IDMA off (undocumented) */
+	w &= ~MIDI_FIX;		/* MIDI fix off (undoc) */
+	w &= ~(1 << 1);		/* reserved, always write 0 */
+	w &= ~IRQ_TO_ISA;	/* IRQ to ISA off (undoc) */
+
+	pci_write_config_word(pci, ESM_CONFIG_B, w);
+
+	/* DDMA off */
+
+	pci_read_config_word(pci, ESM_DDMA, &w);
+	w &= ~(1 << 0);
+	pci_write_config_word(pci, ESM_DDMA, w);
+
+	/*
+	 *	Legacy mode
+	 */
+
+	pci_read_config_word(pci, ESM_LEGACY_AUDIO_CONTROL, &w);
+
+	w |= ESS_DISABLE_AUDIO;	/* Disable Legacy Audio */
+	w &= ~ESS_ENABLE_SERIAL_IRQ;	/* Disable SIRQ */
+	w &= ~(0x1f);		/* disable mpu irq/io, game port, fm, SB */
+
+	pci_write_config_word(pci, ESM_LEGACY_AUDIO_CONTROL, w);
+
+	/* Set up 978 docking control chip. */
+	pci_read_config_word(pci, 0x58, &w);
+	w|=1<<2;	/* Enable 978. */
+	w|=1<<3;	/* Turn on 978 hardware volume control. */
+	w&=~(1<<11);	/* Turn on 978 mixer volume control. */
+	pci_write_config_word(pci, 0x58, w);
+	
+	/* Sound Reset */
+
+	snd_es1968_reset(chip);
+
+	/*
+	 *	Ring Bus Setup
+	 */
+
+	/* setup usual 0x34 stuff.. 0x36 may be chip specific */
+	outw(0xC090, iobase + ESM_RING_BUS_DEST); /* direct sound, stereo */
+	udelay(20);
+	outw(0x3000, iobase + ESM_RING_BUS_CONTR_A); /* enable ringbus/serial */
+	udelay(20);
+
+	/*
+	 *	Reset the CODEC
+	 */
+	 
+	snd_es1968_ac97_reset(chip);
+
+	/* Ring Bus Control B */
+
+	n = inl(iobase + ESM_RING_BUS_CONTR_B);
+	n &= ~RINGB_EN_SPDIF;	/* SPDIF off */
+	//w |= RINGB_EN_2CODEC;	/* enable 2nd codec */
+	outl(n, iobase + ESM_RING_BUS_CONTR_B);
+
+	/* Set hardware volume control registers to midpoints.
+	   We can tell which button was pushed based on how they change. */
+	outb(0x88, iobase+0x1c);
+	outb(0x88, iobase+0x1d);
+	outb(0x88, iobase+0x1e);
+	outb(0x88, iobase+0x1f);
+
+	/* it appears some maestros (dell 7500) only work if these are set,
+	   regardless of wether we use the assp or not. */
+
+	outb(0, iobase + ASSP_CONTROL_B);
+	outb(3, iobase + ASSP_CONTROL_A);	/* M: Reserved bits... */
+	outb(0, iobase + ASSP_CONTROL_C);	/* M: Disable ASSP, ASSP IRQ's and FM Port */
+
+	/*
+	 * set up wavecache
+	 */
+	for (i = 0; i < 16; i++) {
+		/* Write 0 into the buffer area 0x1E0->1EF */
+		outw(0x01E0 + i, iobase + WC_INDEX);
+		outw(0x0000, iobase + WC_DATA);
+
+		/* The 1.10 test program seem to write 0 into the buffer area
+		 * 0x1D0-0x1DF too.*/
+		outw(0x01D0 + i, iobase + WC_INDEX);
+		outw(0x0000, iobase + WC_DATA);
+	}
+	wave_set_register(chip, IDR7_WAVE_ROMRAM,
+			  (wave_get_register(chip, IDR7_WAVE_ROMRAM) & 0xFF00));
+	wave_set_register(chip, IDR7_WAVE_ROMRAM,
+			  wave_get_register(chip, IDR7_WAVE_ROMRAM) | 0x100);
+	wave_set_register(chip, IDR7_WAVE_ROMRAM,
+			  wave_get_register(chip, IDR7_WAVE_ROMRAM) & ~0x200);
+	wave_set_register(chip, IDR7_WAVE_ROMRAM,
+			  wave_get_register(chip, IDR7_WAVE_ROMRAM) | ~0x400);
+
+
+	maestro_write(chip, IDR2_CRAM_DATA, 0x0000);
+	/* Now back to the DirectSound stuff */
+	/* audio serial configuration.. ? */
+	maestro_write(chip, 0x08, 0xB004);
+	maestro_write(chip, 0x09, 0x001B);
+	maestro_write(chip, 0x0A, 0x8000);
+	maestro_write(chip, 0x0B, 0x3F37);
+	maestro_write(chip, 0x0C, 0x0098);
+
+	/* parallel in, has something to do with recording :) */
+	maestro_write(chip, 0x0C,
+		      (maestro_read(chip, 0x0C) & ~0xF000) | 0x8000);
+	/* parallel out */
+	maestro_write(chip, 0x0C,
+		      (maestro_read(chip, 0x0C) & ~0x0F00) | 0x0500);
+
+	maestro_write(chip, 0x0D, 0x7632);
+
+	/* Wave cache control on - test off, sg off, 
+	   enable, enable extra chans 1Mb */
+
+	w = inw(iobase + WC_CONTROL);
+
+	w &= ~0xFA00;		/* Seems to be reserved? I don't know */
+	w |= 0xA000;		/* reserved... I don't know */
+	w &= ~0x0200;		/* Channels 56,57,58,59 as Extra Play,Rec Channel enable
+				   Seems to crash the Computer if enabled... */
+	w |= 0x0100;		/* Wave Cache Operation Enabled */
+	w |= 0x0080;		/* Channels 60/61 as Placback/Record enabled */
+	w &= ~0x0060;		/* Clear Wavtable Size */
+	w |= 0x0020;		/* Wavetable Size : 1MB */
+	/* Bit 4 is reserved */
+	w &= ~0x000C;		/* DMA Stuff? I don't understand what the datasheet means */
+	/* Bit 1 is reserved */
+	w &= ~0x0001;		/* Test Mode off */
+
+	outw(w, iobase + WC_CONTROL);
+
+	/* Now clear the APU control ram */
+	for (i = 0; i < NR_APUS; i++) {
+		for (w = 0; w < NR_APU_REGS; w++)
+			apu_set_register(chip, i, w, 0);
+
+	}
+}
+
+/* Enable IRQ's */
+static void snd_es1968_start_irq(struct es1968 *chip)
+{
+	unsigned short w;
+	w = ESM_HIRQ_DSIE | ESM_HIRQ_HW_VOLUME;
+	if (chip->rmidi)
+		w |= ESM_HIRQ_MPU401;
+	outb(w, chip->io_port + 0x1A);
+	outw(w, chip->io_port + ESM_PORT_HOST_IRQ);
+}
+
+#ifdef CONFIG_PM
+/*
+ * PM support
+ */
+static int es1968_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct es1968 *chip = card->private_data;
+
+	if (! chip->do_pm)
+		return 0;
+
+	chip->in_suspend = 1;
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_ac97_suspend(chip->ac97);
+	snd_es1968_bob_stop(chip);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int es1968_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct es1968 *chip = card->private_data;
+	struct esschan *es;
+
+	if (! chip->do_pm)
+		return 0;
+
+	/* restore all our config */
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "es1968: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_es1968_chip_init(chip);
+
+	/* need to restore the base pointers.. */ 
+	if (chip->dma.addr) {
+		/* set PCMBAR */
+		wave_set_register(chip, 0x01FC, chip->dma.addr >> 12);
+	}
+
+	snd_es1968_start_irq(chip);
+
+	/* restore ac97 state */
+	snd_ac97_resume(chip->ac97);
+
+	list_for_each_entry(es, &chip->substream_list, list) {
+		switch (es->mode) {
+		case ESM_MODE_PLAY:
+			snd_es1968_playback_setup(chip, es, es->substream->runtime);
+			break;
+		case ESM_MODE_CAPTURE:
+			snd_es1968_capture_setup(chip, es, es->substream->runtime);
+			break;
+		}
+	}
+
+	/* start timer again */
+	if (chip->bobclient)
+		snd_es1968_bob_start(chip);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	chip->in_suspend = 0;
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef SUPPORT_JOYSTICK
+#define JOYSTICK_ADDR	0x200
+static int __devinit snd_es1968_create_gameport(struct es1968 *chip, int dev)
+{
+	struct gameport *gp;
+	struct resource *r;
+	u16 val;
+
+	if (!joystick[dev])
+		return -ENODEV;
+
+	r = request_region(JOYSTICK_ADDR, 8, "ES1968 gameport");
+	if (!r)
+		return -EBUSY;
+
+	chip->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "es1968: cannot allocate memory for gameport\n");
+		release_and_free_resource(r);
+		return -ENOMEM;
+	}
+
+	pci_read_config_word(chip->pci, ESM_LEGACY_AUDIO_CONTROL, &val);
+	pci_write_config_word(chip->pci, ESM_LEGACY_AUDIO_CONTROL, val | 0x04);
+
+	gameport_set_name(gp, "ES1968 Gameport");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
+	gameport_set_dev_parent(gp, &chip->pci->dev);
+	gp->io = JOYSTICK_ADDR;
+	gameport_set_port_data(gp, r);
+
+	gameport_register_port(gp);
+
+	return 0;
+}
+
+static void snd_es1968_free_gameport(struct es1968 *chip)
+{
+	if (chip->gameport) {
+		struct resource *r = gameport_get_port_data(chip->gameport);
+
+		gameport_unregister_port(chip->gameport);
+		chip->gameport = NULL;
+
+		release_and_free_resource(r);
+	}
+}
+#else
+static inline int snd_es1968_create_gameport(struct es1968 *chip, int dev) { return -ENOSYS; }
+static inline void snd_es1968_free_gameport(struct es1968 *chip) { }
+#endif
+
+#ifdef CONFIG_SND_ES1968_INPUT
+static int __devinit snd_es1968_input_register(struct es1968 *chip)
+{
+	struct input_dev *input_dev;
+	int err;
+
+	input_dev = input_allocate_device();
+	if (!input_dev)
+		return -ENOMEM;
+
+	snprintf(chip->phys, sizeof(chip->phys), "pci-%s/input0",
+		 pci_name(chip->pci));
+
+	input_dev->name = chip->card->driver;
+	input_dev->phys = chip->phys;
+	input_dev->id.bustype = BUS_PCI;
+	input_dev->id.vendor  = chip->pci->vendor;
+	input_dev->id.product = chip->pci->device;
+	input_dev->dev.parent = &chip->pci->dev;
+
+	__set_bit(EV_KEY, input_dev->evbit);
+	__set_bit(KEY_MUTE, input_dev->keybit);
+	__set_bit(KEY_VOLUMEDOWN, input_dev->keybit);
+	__set_bit(KEY_VOLUMEUP, input_dev->keybit);
+
+	err = input_register_device(input_dev);
+	if (err) {
+		input_free_device(input_dev);
+		return err;
+	}
+
+	chip->input_dev = input_dev;
+	return 0;
+}
+#endif /* CONFIG_SND_ES1968_INPUT */
+
+static int snd_es1968_free(struct es1968 *chip)
+{
+#ifdef CONFIG_SND_ES1968_INPUT
+	if (chip->input_dev)
+		input_unregister_device(chip->input_dev);
+#endif
+
+	if (chip->io_port) {
+		if (chip->irq >= 0)
+			synchronize_irq(chip->irq);
+		outw(1, chip->io_port + 0x04); /* clear WP interrupts */
+		outw(0, chip->io_port + ESM_PORT_HOST_IRQ); /* disable IRQ */
+	}
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	snd_es1968_free_gameport(chip);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_es1968_dev_free(struct snd_device *device)
+{
+	struct es1968 *chip = device->device_data;
+	return snd_es1968_free(chip);
+}
+
+struct ess_device_list {
+	unsigned short type;	/* chip type */
+	unsigned short vendor;	/* subsystem vendor id */
+};
+
+static struct ess_device_list pm_whitelist[] __devinitdata = {
+	{ TYPE_MAESTRO2E, 0x0e11 },	/* Compaq Armada */
+	{ TYPE_MAESTRO2E, 0x1028 },
+	{ TYPE_MAESTRO2E, 0x103c },
+	{ TYPE_MAESTRO2E, 0x1179 },
+	{ TYPE_MAESTRO2E, 0x14c0 },	/* HP omnibook 4150 */
+	{ TYPE_MAESTRO2E, 0x1558 },
+};
+
+static struct ess_device_list mpu_blacklist[] __devinitdata = {
+	{ TYPE_MAESTRO2, 0x125d },
+};
+
+static int __devinit snd_es1968_create(struct snd_card *card,
+				       struct pci_dev *pci,
+				       int total_bufsize,
+				       int play_streams,
+				       int capt_streams,
+				       int chip_type,
+				       int do_pm,
+				       struct es1968 **chip_ret)
+{
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_es1968_dev_free,
+	};
+	struct es1968 *chip;
+	int i, err;
+
+	*chip_ret = NULL;
+
+	/* enable PCI device */
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	/* check, if we can restrict PCI DMA transfers to 28 bits */
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(28)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(28)) < 0) {
+		snd_printk(KERN_ERR "architecture does not support 28bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (! chip) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	/* Set Vars */
+	chip->type = chip_type;
+	spin_lock_init(&chip->reg_lock);
+	spin_lock_init(&chip->substream_lock);
+	INIT_LIST_HEAD(&chip->buf_list);
+	INIT_LIST_HEAD(&chip->substream_list);
+	mutex_init(&chip->memory_mutex);
+#ifndef CONFIG_SND_ES1968_INPUT
+	spin_lock_init(&chip->ac97_lock);
+	tasklet_init(&chip->hwvol_tq, es1968_update_hw_volume, (unsigned long)chip);
+#endif
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	chip->total_bufsize = total_bufsize;	/* in bytes */
+	chip->playback_streams = play_streams;
+	chip->capture_streams = capt_streams;
+
+	if ((err = pci_request_regions(pci, "ESS Maestro")) < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+	chip->io_port = pci_resource_start(pci, 0);
+	if (request_irq(pci->irq, snd_es1968_interrupt, IRQF_SHARED,
+			"ESS Maestro", chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_es1968_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+	        
+	/* Clear Maestro_map */
+	for (i = 0; i < 32; i++)
+		chip->maestro_map[i] = 0;
+
+	/* Clear Apu Map */
+	for (i = 0; i < NR_APUS; i++)
+		chip->apu[i] = ESM_APU_FREE;
+
+	/* just to be sure */
+	pci_set_master(pci);
+
+	if (do_pm > 1) {
+		/* disable power-management if not on the whitelist */
+		unsigned short vend;
+		pci_read_config_word(chip->pci, PCI_SUBSYSTEM_VENDOR_ID, &vend);
+		for (i = 0; i < (int)ARRAY_SIZE(pm_whitelist); i++) {
+			if (chip->type == pm_whitelist[i].type &&
+			    vend == pm_whitelist[i].vendor) {
+				do_pm = 1;
+				break;
+			}
+		}
+		if (do_pm > 1) {
+			/* not matched; disabling pm */
+			printk(KERN_INFO "es1968: not attempting power management.\n");
+			do_pm = 0;
+		}
+	}
+	chip->do_pm = do_pm;
+
+	snd_es1968_chip_init(chip);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_es1968_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*chip_ret = chip;
+
+	return 0;
+}
+
+
+/*
+ */
+static int __devinit snd_es1968_probe(struct pci_dev *pci,
+				      const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct es1968 *chip;
+	unsigned int i;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+                
+	if (total_bufsize[dev] < 128)
+		total_bufsize[dev] = 128;
+	if (total_bufsize[dev] > 4096)
+		total_bufsize[dev] = 4096;
+	if ((err = snd_es1968_create(card, pci,
+				     total_bufsize[dev] * 1024, /* in bytes */
+				     pcm_substreams_p[dev], 
+				     pcm_substreams_c[dev],
+				     pci_id->driver_data,
+				     use_pm[dev],
+				     &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = chip;
+
+	switch (chip->type) {
+	case TYPE_MAESTRO2E:
+		strcpy(card->driver, "ES1978");
+		strcpy(card->shortname, "ESS ES1978 (Maestro 2E)");
+		break;
+	case TYPE_MAESTRO2:
+		strcpy(card->driver, "ES1968");
+		strcpy(card->shortname, "ESS ES1968 (Maestro 2)");
+		break;
+	case TYPE_MAESTRO:
+		strcpy(card->driver, "ESM1");
+		strcpy(card->shortname, "ESS Maestro 1");
+		break;
+	}
+
+	if ((err = snd_es1968_pcm(chip, 0)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_es1968_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if (enable_mpu[dev] == 2) {
+		/* check the black list */
+		unsigned short vend;
+		pci_read_config_word(chip->pci, PCI_SUBSYSTEM_VENDOR_ID, &vend);
+		for (i = 0; i < ARRAY_SIZE(mpu_blacklist); i++) {
+			if (chip->type == mpu_blacklist[i].type &&
+			    vend == mpu_blacklist[i].vendor) {
+				enable_mpu[dev] = 0;
+				break;
+			}
+		}
+	}
+	if (enable_mpu[dev]) {
+		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401,
+					       chip->io_port + ESM_MPU401_PORT,
+					       MPU401_INFO_INTEGRATED,
+					       chip->irq, 0, &chip->rmidi)) < 0) {
+			printk(KERN_WARNING "es1968: skipping MPU-401 MIDI support..\n");
+		}
+	}
+
+	snd_es1968_create_gameport(chip, dev);
+
+#ifdef CONFIG_SND_ES1968_INPUT
+	err = snd_es1968_input_register(chip);
+	if (err)
+		snd_printk(KERN_WARNING "Input device registration "
+			"failed with error %i", err);
+#endif
+
+	snd_es1968_start_irq(chip);
+
+	chip->clock = clock[dev];
+	if (! chip->clock)
+		es1968_measure_clock(chip);
+
+	sprintf(card->longname, "%s at 0x%lx, irq %i",
+		card->shortname, chip->io_port, chip->irq);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_es1968_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "ES1968 (ESS Maestro)",
+	.id_table = snd_es1968_ids,
+	.probe = snd_es1968_probe,
+	.remove = __devexit_p(snd_es1968_remove),
+#ifdef CONFIG_PM
+	.suspend = es1968_suspend,
+	.resume = es1968_resume,
+#endif
+};
+
+static int __init alsa_card_es1968_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_es1968_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_es1968_init)
+module_exit(alsa_card_es1968_exit)
diff --git a/linux/sound/pci/fm801.c b/linux/sound/pci/fm801.c
new file mode 100644
index 0000000..e1baad7
--- /dev/null
+++ b/linux/sound/pci/fm801.c
@@ -0,0 +1,1622 @@
+/*
+ *  The driver for the ForteMedia FM801 based soundcards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *  Support FM only card by Andy Shevchenko <andy@smile.org.ua>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#include <sound/ac97_codec.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+
+#ifdef CONFIG_SND_FM801_TEA575X_BOOL
+#include <sound/tea575x-tuner.h>
+#define TEA575X_RADIO 1
+#endif
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ForteMedia FM801");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ForteMedia,FM801},"
+		"{Genius,SoundMaker Live 5.1}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+/*
+ *  Enable TEA575x tuner
+ *    1 = MediaForte 256-PCS
+ *    2 = MediaForte 256-PCPR
+ *    3 = MediaForte 64-PCR
+ *   16 = setup tuner only (this is additional bit), i.e. SF64-PCR FM card
+ *  High 16-bits are video (radio) device number + 1
+ */
+static int tea575x_tuner[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the FM801 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the FM801 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable FM801 soundcard.");
+module_param_array(tea575x_tuner, int, NULL, 0444);
+MODULE_PARM_DESC(tea575x_tuner, "TEA575x tuner access method (1 = SF256-PCS, 2=SF256-PCPR, 3=SF64-PCR, +16=tuner-only).");
+
+#define TUNER_ONLY		(1<<4)
+#define TUNER_TYPE_MASK		(~TUNER_ONLY & 0xFFFF)
+
+/*
+ *  Direct registers
+ */
+
+#define FM801_REG(chip, reg)	(chip->port + FM801_##reg)
+
+#define FM801_PCM_VOL		0x00	/* PCM Output Volume */
+#define FM801_FM_VOL		0x02	/* FM Output Volume */
+#define FM801_I2S_VOL		0x04	/* I2S Volume */
+#define FM801_REC_SRC		0x06	/* Record Source */
+#define FM801_PLY_CTRL		0x08	/* Playback Control */
+#define FM801_PLY_COUNT		0x0a	/* Playback Count */
+#define FM801_PLY_BUF1		0x0c	/* Playback Bufer I */
+#define FM801_PLY_BUF2		0x10	/* Playback Buffer II */
+#define FM801_CAP_CTRL		0x14	/* Capture Control */
+#define FM801_CAP_COUNT		0x16	/* Capture Count */
+#define FM801_CAP_BUF1		0x18	/* Capture Buffer I */
+#define FM801_CAP_BUF2		0x1c	/* Capture Buffer II */
+#define FM801_CODEC_CTRL	0x22	/* Codec Control */
+#define FM801_I2S_MODE		0x24	/* I2S Mode Control */
+#define FM801_VOLUME		0x26	/* Volume Up/Down/Mute Status */
+#define FM801_I2C_CTRL		0x29	/* I2C Control */
+#define FM801_AC97_CMD		0x2a	/* AC'97 Command */
+#define FM801_AC97_DATA		0x2c	/* AC'97 Data */
+#define FM801_MPU401_DATA	0x30	/* MPU401 Data */
+#define FM801_MPU401_CMD	0x31	/* MPU401 Command */
+#define FM801_GPIO_CTRL		0x52	/* General Purpose I/O Control */
+#define FM801_GEN_CTRL		0x54	/* General Control */
+#define FM801_IRQ_MASK		0x56	/* Interrupt Mask */
+#define FM801_IRQ_STATUS	0x5a	/* Interrupt Status */
+#define FM801_OPL3_BANK0	0x68	/* OPL3 Status Read / Bank 0 Write */
+#define FM801_OPL3_DATA0	0x69	/* OPL3 Data 0 Write */
+#define FM801_OPL3_BANK1	0x6a	/* OPL3 Bank 1 Write */
+#define FM801_OPL3_DATA1	0x6b	/* OPL3 Bank 1 Write */
+#define FM801_POWERDOWN		0x70	/* Blocks Power Down Control */
+
+/* codec access */
+#define FM801_AC97_READ		(1<<7)	/* read=1, write=0 */
+#define FM801_AC97_VALID	(1<<8)	/* port valid=1 */
+#define FM801_AC97_BUSY		(1<<9)	/* busy=1 */
+#define FM801_AC97_ADDR_SHIFT	10	/* codec id (2bit) */
+
+/* playback and record control register bits */
+#define FM801_BUF1_LAST		(1<<1)
+#define FM801_BUF2_LAST		(1<<2)
+#define FM801_START		(1<<5)
+#define FM801_PAUSE		(1<<6)
+#define FM801_IMMED_STOP	(1<<7)
+#define FM801_RATE_SHIFT	8
+#define FM801_RATE_MASK		(15 << FM801_RATE_SHIFT)
+#define FM801_CHANNELS_4	(1<<12)	/* playback only */
+#define FM801_CHANNELS_6	(2<<12)	/* playback only */
+#define FM801_CHANNELS_6MS	(3<<12)	/* playback only */
+#define FM801_CHANNELS_MASK	(3<<12)
+#define FM801_16BIT		(1<<14)
+#define FM801_STEREO		(1<<15)
+
+/* IRQ status bits */
+#define FM801_IRQ_PLAYBACK	(1<<8)
+#define FM801_IRQ_CAPTURE	(1<<9)
+#define FM801_IRQ_VOLUME	(1<<14)
+#define FM801_IRQ_MPU		(1<<15)
+
+/* GPIO control register */
+#define FM801_GPIO_GP0		(1<<0)	/* read/write */
+#define FM801_GPIO_GP1		(1<<1)
+#define FM801_GPIO_GP2		(1<<2)
+#define FM801_GPIO_GP3		(1<<3)
+#define FM801_GPIO_GP(x)	(1<<(0+(x)))
+#define FM801_GPIO_GD0		(1<<8)	/* directions: 1 = input, 0 = output*/
+#define FM801_GPIO_GD1		(1<<9)
+#define FM801_GPIO_GD2		(1<<10)
+#define FM801_GPIO_GD3		(1<<11)
+#define FM801_GPIO_GD(x)	(1<<(8+(x)))
+#define FM801_GPIO_GS0		(1<<12)	/* function select: */
+#define FM801_GPIO_GS1		(1<<13)	/*    1 = GPIO */
+#define FM801_GPIO_GS2		(1<<14)	/*    0 = other (S/PDIF, VOL) */
+#define FM801_GPIO_GS3		(1<<15)
+#define FM801_GPIO_GS(x)	(1<<(12+(x)))
+	
+/*
+
+ */
+
+struct fm801 {
+	int irq;
+
+	unsigned long port;	/* I/O port number */
+	unsigned int multichannel: 1,	/* multichannel support */
+		     secondary: 1;	/* secondary codec */
+	unsigned char secondary_addr;	/* address of the secondary codec */
+	unsigned int tea575x_tuner;	/* tuner access method & flags */
+
+	unsigned short ply_ctrl; /* playback control */
+	unsigned short cap_ctrl; /* capture control */
+
+	unsigned long ply_buffer;
+	unsigned int ply_buf;
+	unsigned int ply_count;
+	unsigned int ply_size;
+	unsigned int ply_pos;
+
+	unsigned long cap_buffer;
+	unsigned int cap_buf;
+	unsigned int cap_count;
+	unsigned int cap_size;
+	unsigned int cap_pos;
+
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97 *ac97;
+	struct snd_ac97 *ac97_sec;
+
+	struct pci_dev *pci;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct snd_rawmidi *rmidi;
+	struct snd_pcm_substream *playback_substream;
+	struct snd_pcm_substream *capture_substream;
+	unsigned int p_dma_size;
+	unsigned int c_dma_size;
+
+	spinlock_t reg_lock;
+	struct snd_info_entry *proc_entry;
+
+#ifdef TEA575X_RADIO
+	struct snd_tea575x tea;
+#endif
+
+#ifdef CONFIG_PM
+	u16 saved_regs[0x20];
+#endif
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_fm801_ids) = {
+	{ 0x1319, 0x0801, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0, },   /* FM801 */
+	{ 0x5213, 0x0510, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0, },   /* Gallant Odyssey Sound 4 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_fm801_ids);
+
+/*
+ *  common I/O routines
+ */
+
+static int snd_fm801_update_bits(struct fm801 *chip, unsigned short reg,
+				 unsigned short mask, unsigned short value)
+{
+	int change;
+	unsigned long flags;
+	unsigned short old, new;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	old = inw(chip->port + reg);
+	new = (old & ~mask) | value;
+	change = old != new;
+	if (change)
+		outw(new, chip->port + reg);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return change;
+}
+
+static void snd_fm801_codec_write(struct snd_ac97 *ac97,
+				  unsigned short reg,
+				  unsigned short val)
+{
+	struct fm801 *chip = ac97->private_data;
+	int idx;
+
+	/*
+	 *  Wait until the codec interface is not ready..
+	 */
+	for (idx = 0; idx < 100; idx++) {
+		if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
+			goto ok1;
+		udelay(10);
+	}
+	snd_printk(KERN_ERR "AC'97 interface is busy (1)\n");
+	return;
+
+ ok1:
+	/* write data and address */
+	outw(val, FM801_REG(chip, AC97_DATA));
+	outw(reg | (ac97->addr << FM801_AC97_ADDR_SHIFT), FM801_REG(chip, AC97_CMD));
+	/*
+	 *  Wait until the write command is not completed..
+         */
+	for (idx = 0; idx < 1000; idx++) {
+		if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
+			return;
+		udelay(10);
+	}
+	snd_printk(KERN_ERR "AC'97 interface #%d is busy (2)\n", ac97->num);
+}
+
+static unsigned short snd_fm801_codec_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct fm801 *chip = ac97->private_data;
+	int idx;
+
+	/*
+	 *  Wait until the codec interface is not ready..
+	 */
+	for (idx = 0; idx < 100; idx++) {
+		if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
+			goto ok1;
+		udelay(10);
+	}
+	snd_printk(KERN_ERR "AC'97 interface is busy (1)\n");
+	return 0;
+
+ ok1:
+	/* read command */
+	outw(reg | (ac97->addr << FM801_AC97_ADDR_SHIFT) | FM801_AC97_READ,
+	     FM801_REG(chip, AC97_CMD));
+	for (idx = 0; idx < 100; idx++) {
+		if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY))
+			goto ok2;
+		udelay(10);
+	}
+	snd_printk(KERN_ERR "AC'97 interface #%d is busy (2)\n", ac97->num);
+	return 0;
+
+ ok2:
+	for (idx = 0; idx < 1000; idx++) {
+		if (inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_VALID)
+			goto ok3;
+		udelay(10);
+	}
+	snd_printk(KERN_ERR "AC'97 interface #%d is not valid (2)\n", ac97->num);
+	return 0;
+
+ ok3:
+	return inw(FM801_REG(chip, AC97_DATA));
+}
+
+static unsigned int rates[] = {
+  5500,  8000,  9600, 11025,
+  16000, 19200, 22050, 32000,
+  38400, 44100, 48000
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static unsigned int channels[] = {
+  2, 4, 6
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_channels = {
+	.count = ARRAY_SIZE(channels),
+	.list = channels,
+	.mask = 0,
+};
+
+/*
+ *  Sample rate routines
+ */
+
+static unsigned short snd_fm801_rate_bits(unsigned int rate)
+{
+	unsigned int idx;
+
+	for (idx = 0; idx < ARRAY_SIZE(rates); idx++)
+		if (rates[idx] == rate)
+			return idx;
+	snd_BUG();
+	return ARRAY_SIZE(rates) - 1;
+}
+
+/*
+ *  PCM part
+ */
+
+static int snd_fm801_playback_trigger(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+	struct fm801 *chip = snd_pcm_substream_chip(substream);
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		chip->ply_ctrl &= ~(FM801_BUF1_LAST |
+				     FM801_BUF2_LAST |
+				     FM801_PAUSE);
+		chip->ply_ctrl |= FM801_START |
+				   FM801_IMMED_STOP;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		chip->ply_ctrl &= ~(FM801_START | FM801_PAUSE);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		chip->ply_ctrl |= FM801_PAUSE;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		chip->ply_ctrl &= ~FM801_PAUSE;
+		break;
+	default:
+		spin_unlock(&chip->reg_lock);
+		snd_BUG();
+		return -EINVAL;
+	}
+	outw(chip->ply_ctrl, FM801_REG(chip, PLY_CTRL));
+	spin_unlock(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_fm801_capture_trigger(struct snd_pcm_substream *substream,
+				     int cmd)
+{
+	struct fm801 *chip = snd_pcm_substream_chip(substream);
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		chip->cap_ctrl &= ~(FM801_BUF1_LAST |
+				     FM801_BUF2_LAST |
+				     FM801_PAUSE);
+		chip->cap_ctrl |= FM801_START |
+				   FM801_IMMED_STOP;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		chip->cap_ctrl &= ~(FM801_START | FM801_PAUSE);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		chip->cap_ctrl |= FM801_PAUSE;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		chip->cap_ctrl &= ~FM801_PAUSE;
+		break;
+	default:
+		spin_unlock(&chip->reg_lock);
+		snd_BUG();
+		return -EINVAL;
+	}
+	outw(chip->cap_ctrl, FM801_REG(chip, CAP_CTRL));
+	spin_unlock(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_fm801_hw_params(struct snd_pcm_substream *substream,
+			       struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_fm801_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_fm801_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct fm801 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	chip->ply_size = snd_pcm_lib_buffer_bytes(substream);
+	chip->ply_count = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irq(&chip->reg_lock);
+	chip->ply_ctrl &= ~(FM801_START | FM801_16BIT |
+			     FM801_STEREO | FM801_RATE_MASK |
+			     FM801_CHANNELS_MASK);
+	if (snd_pcm_format_width(runtime->format) == 16)
+		chip->ply_ctrl |= FM801_16BIT;
+	if (runtime->channels > 1) {
+		chip->ply_ctrl |= FM801_STEREO;
+		if (runtime->channels == 4)
+			chip->ply_ctrl |= FM801_CHANNELS_4;
+		else if (runtime->channels == 6)
+			chip->ply_ctrl |= FM801_CHANNELS_6;
+	}
+	chip->ply_ctrl |= snd_fm801_rate_bits(runtime->rate) << FM801_RATE_SHIFT;
+	chip->ply_buf = 0;
+	outw(chip->ply_ctrl, FM801_REG(chip, PLY_CTRL));
+	outw(chip->ply_count - 1, FM801_REG(chip, PLY_COUNT));
+	chip->ply_buffer = runtime->dma_addr;
+	chip->ply_pos = 0;
+	outl(chip->ply_buffer, FM801_REG(chip, PLY_BUF1));
+	outl(chip->ply_buffer + (chip->ply_count % chip->ply_size), FM801_REG(chip, PLY_BUF2));
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_fm801_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct fm801 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	chip->cap_size = snd_pcm_lib_buffer_bytes(substream);
+	chip->cap_count = snd_pcm_lib_period_bytes(substream);
+	spin_lock_irq(&chip->reg_lock);
+	chip->cap_ctrl &= ~(FM801_START | FM801_16BIT |
+			     FM801_STEREO | FM801_RATE_MASK);
+	if (snd_pcm_format_width(runtime->format) == 16)
+		chip->cap_ctrl |= FM801_16BIT;
+	if (runtime->channels > 1)
+		chip->cap_ctrl |= FM801_STEREO;
+	chip->cap_ctrl |= snd_fm801_rate_bits(runtime->rate) << FM801_RATE_SHIFT;
+	chip->cap_buf = 0;
+	outw(chip->cap_ctrl, FM801_REG(chip, CAP_CTRL));
+	outw(chip->cap_count - 1, FM801_REG(chip, CAP_COUNT));
+	chip->cap_buffer = runtime->dma_addr;
+	chip->cap_pos = 0;
+	outl(chip->cap_buffer, FM801_REG(chip, CAP_BUF1));
+	outl(chip->cap_buffer + (chip->cap_count % chip->cap_size), FM801_REG(chip, CAP_BUF2));
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_fm801_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct fm801 *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(chip->ply_ctrl & FM801_START))
+		return 0;
+	spin_lock(&chip->reg_lock);
+	ptr = chip->ply_pos + (chip->ply_count - 1) - inw(FM801_REG(chip, PLY_COUNT));
+	if (inw(FM801_REG(chip, IRQ_STATUS)) & FM801_IRQ_PLAYBACK) {
+		ptr += chip->ply_count;
+		ptr %= chip->ply_size;
+	}
+	spin_unlock(&chip->reg_lock);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_fm801_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct fm801 *chip = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(chip->cap_ctrl & FM801_START))
+		return 0;
+	spin_lock(&chip->reg_lock);
+	ptr = chip->cap_pos + (chip->cap_count - 1) - inw(FM801_REG(chip, CAP_COUNT));
+	if (inw(FM801_REG(chip, IRQ_STATUS)) & FM801_IRQ_CAPTURE) {
+		ptr += chip->cap_count;
+		ptr %= chip->cap_size;
+	}
+	spin_unlock(&chip->reg_lock);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static irqreturn_t snd_fm801_interrupt(int irq, void *dev_id)
+{
+	struct fm801 *chip = dev_id;
+	unsigned short status;
+	unsigned int tmp;
+
+	status = inw(FM801_REG(chip, IRQ_STATUS));
+	status &= FM801_IRQ_PLAYBACK|FM801_IRQ_CAPTURE|FM801_IRQ_MPU|FM801_IRQ_VOLUME;
+	if (! status)
+		return IRQ_NONE;
+	/* ack first */
+	outw(status, FM801_REG(chip, IRQ_STATUS));
+	if (chip->pcm && (status & FM801_IRQ_PLAYBACK) && chip->playback_substream) {
+		spin_lock(&chip->reg_lock);
+		chip->ply_buf++;
+		chip->ply_pos += chip->ply_count;
+		chip->ply_pos %= chip->ply_size;
+		tmp = chip->ply_pos + chip->ply_count;
+		tmp %= chip->ply_size;
+		outl(chip->ply_buffer + tmp,
+				(chip->ply_buf & 1) ?
+					FM801_REG(chip, PLY_BUF1) :
+					FM801_REG(chip, PLY_BUF2));
+		spin_unlock(&chip->reg_lock);
+		snd_pcm_period_elapsed(chip->playback_substream);
+	}
+	if (chip->pcm && (status & FM801_IRQ_CAPTURE) && chip->capture_substream) {
+		spin_lock(&chip->reg_lock);
+		chip->cap_buf++;
+		chip->cap_pos += chip->cap_count;
+		chip->cap_pos %= chip->cap_size;
+		tmp = chip->cap_pos + chip->cap_count;
+		tmp %= chip->cap_size;
+		outl(chip->cap_buffer + tmp,
+				(chip->cap_buf & 1) ?
+					FM801_REG(chip, CAP_BUF1) :
+					FM801_REG(chip, CAP_BUF2));
+		spin_unlock(&chip->reg_lock);
+		snd_pcm_period_elapsed(chip->capture_substream);
+	}
+	if (chip->rmidi && (status & FM801_IRQ_MPU))
+		snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+	if (status & FM801_IRQ_VOLUME)
+		;/* TODO */
+
+	return IRQ_HANDLED;
+}
+
+static struct snd_pcm_hardware snd_fm801_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5500,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_fm801_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		5500,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static int snd_fm801_playback_open(struct snd_pcm_substream *substream)
+{
+	struct fm801 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	chip->playback_substream = substream;
+	runtime->hw = snd_fm801_playback;
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				   &hw_constraints_rates);
+	if (chip->multichannel) {
+		runtime->hw.channels_max = 6;
+		snd_pcm_hw_constraint_list(runtime, 0,
+					   SNDRV_PCM_HW_PARAM_CHANNELS,
+					   &hw_constraints_channels);
+	}
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+	return 0;
+}
+
+static int snd_fm801_capture_open(struct snd_pcm_substream *substream)
+{
+	struct fm801 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	chip->capture_substream = substream;
+	runtime->hw = snd_fm801_capture;
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				   &hw_constraints_rates);
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+	return 0;
+}
+
+static int snd_fm801_playback_close(struct snd_pcm_substream *substream)
+{
+	struct fm801 *chip = snd_pcm_substream_chip(substream);
+
+	chip->playback_substream = NULL;
+	return 0;
+}
+
+static int snd_fm801_capture_close(struct snd_pcm_substream *substream)
+{
+	struct fm801 *chip = snd_pcm_substream_chip(substream);
+
+	chip->capture_substream = NULL;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_fm801_playback_ops = {
+	.open =		snd_fm801_playback_open,
+	.close =	snd_fm801_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_fm801_hw_params,
+	.hw_free =	snd_fm801_hw_free,
+	.prepare =	snd_fm801_playback_prepare,
+	.trigger =	snd_fm801_playback_trigger,
+	.pointer =	snd_fm801_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_fm801_capture_ops = {
+	.open =		snd_fm801_capture_open,
+	.close =	snd_fm801_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_fm801_hw_params,
+	.hw_free =	snd_fm801_hw_free,
+	.prepare =	snd_fm801_capture_prepare,
+	.trigger =	snd_fm801_capture_trigger,
+	.pointer =	snd_fm801_capture_pointer,
+};
+
+static int __devinit snd_fm801_pcm(struct fm801 *chip, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(chip->card, "FM801", device, 1, 1, &pcm)) < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_fm801_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_fm801_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "FM801");
+	chip->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci),
+					      chip->multichannel ? 128*1024 : 64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+/*
+ *  TEA5757 radio
+ */
+
+#ifdef TEA575X_RADIO
+
+/* 256PCS GPIO numbers */
+#define TEA_256PCS_DATA			1
+#define TEA_256PCS_WRITE_ENABLE		2	/* inverted */
+#define TEA_256PCS_BUS_CLOCK		3
+
+static void snd_fm801_tea575x_256pcs_write(struct snd_tea575x *tea, unsigned int val)
+{
+	struct fm801 *chip = tea->private_data;
+	unsigned short reg;
+	int i = 25;
+
+	spin_lock_irq(&chip->reg_lock);
+	reg = inw(FM801_REG(chip, GPIO_CTRL));
+	/* use GPIO lines and set write enable bit */
+	reg |= FM801_GPIO_GS(TEA_256PCS_DATA) |
+	       FM801_GPIO_GS(TEA_256PCS_WRITE_ENABLE) |
+	       FM801_GPIO_GS(TEA_256PCS_BUS_CLOCK);
+	/* all of lines are in the write direction */
+	/* clear data and clock lines */
+	reg &= ~(FM801_GPIO_GD(TEA_256PCS_DATA) |
+	         FM801_GPIO_GD(TEA_256PCS_WRITE_ENABLE) |
+	         FM801_GPIO_GD(TEA_256PCS_BUS_CLOCK) |
+	         FM801_GPIO_GP(TEA_256PCS_DATA) |
+	         FM801_GPIO_GP(TEA_256PCS_BUS_CLOCK) |
+		 FM801_GPIO_GP(TEA_256PCS_WRITE_ENABLE));
+	outw(reg, FM801_REG(chip, GPIO_CTRL));
+	udelay(1);
+
+	while (i--) {
+		if (val & (1 << i))
+			reg |= FM801_GPIO_GP(TEA_256PCS_DATA);
+		else
+			reg &= ~FM801_GPIO_GP(TEA_256PCS_DATA);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+		reg |= FM801_GPIO_GP(TEA_256PCS_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		reg &= ~FM801_GPIO_GP(TEA_256PCS_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+	}
+
+	/* and reset the write enable bit */
+	reg |= FM801_GPIO_GP(TEA_256PCS_WRITE_ENABLE) |
+	       FM801_GPIO_GP(TEA_256PCS_DATA);
+	outw(reg, FM801_REG(chip, GPIO_CTRL));
+	spin_unlock_irq(&chip->reg_lock);
+}
+
+static unsigned int snd_fm801_tea575x_256pcs_read(struct snd_tea575x *tea)
+{
+	struct fm801 *chip = tea->private_data;
+	unsigned short reg;
+	unsigned int val = 0;
+	int i;
+	
+	spin_lock_irq(&chip->reg_lock);
+	reg = inw(FM801_REG(chip, GPIO_CTRL));
+	/* use GPIO lines, set data direction to input */
+	reg |= FM801_GPIO_GS(TEA_256PCS_DATA) |
+	       FM801_GPIO_GS(TEA_256PCS_WRITE_ENABLE) |
+	       FM801_GPIO_GS(TEA_256PCS_BUS_CLOCK) |
+	       FM801_GPIO_GD(TEA_256PCS_DATA) |
+	       FM801_GPIO_GP(TEA_256PCS_DATA) |
+	       FM801_GPIO_GP(TEA_256PCS_WRITE_ENABLE);
+	/* all of lines are in the write direction, except data */
+	/* clear data, write enable and clock lines */
+	reg &= ~(FM801_GPIO_GD(TEA_256PCS_WRITE_ENABLE) |
+	         FM801_GPIO_GD(TEA_256PCS_BUS_CLOCK) |
+	         FM801_GPIO_GP(TEA_256PCS_BUS_CLOCK));
+
+	for (i = 0; i < 24; i++) {
+		reg &= ~FM801_GPIO_GP(TEA_256PCS_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+		reg |= FM801_GPIO_GP(TEA_256PCS_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+		val <<= 1;
+		if (inw(FM801_REG(chip, GPIO_CTRL)) & FM801_GPIO_GP(TEA_256PCS_DATA))
+			val |= 1;
+	}
+
+	spin_unlock_irq(&chip->reg_lock);
+
+	return val;
+}
+
+/* 256PCPR GPIO numbers */
+#define TEA_256PCPR_BUS_CLOCK		0
+#define TEA_256PCPR_DATA		1
+#define TEA_256PCPR_WRITE_ENABLE	2	/* inverted */
+
+static void snd_fm801_tea575x_256pcpr_write(struct snd_tea575x *tea, unsigned int val)
+{
+	struct fm801 *chip = tea->private_data;
+	unsigned short reg;
+	int i = 25;
+
+	spin_lock_irq(&chip->reg_lock);
+	reg = inw(FM801_REG(chip, GPIO_CTRL));
+	/* use GPIO lines and set write enable bit */
+	reg |= FM801_GPIO_GS(TEA_256PCPR_DATA) |
+	       FM801_GPIO_GS(TEA_256PCPR_WRITE_ENABLE) |
+	       FM801_GPIO_GS(TEA_256PCPR_BUS_CLOCK);
+	/* all of lines are in the write direction */
+	/* clear data and clock lines */
+	reg &= ~(FM801_GPIO_GD(TEA_256PCPR_DATA) |
+	         FM801_GPIO_GD(TEA_256PCPR_WRITE_ENABLE) |
+	         FM801_GPIO_GD(TEA_256PCPR_BUS_CLOCK) |
+	         FM801_GPIO_GP(TEA_256PCPR_DATA) |
+	         FM801_GPIO_GP(TEA_256PCPR_BUS_CLOCK) |
+		 FM801_GPIO_GP(TEA_256PCPR_WRITE_ENABLE));
+	outw(reg, FM801_REG(chip, GPIO_CTRL));
+	udelay(1);
+
+	while (i--) {
+		if (val & (1 << i))
+			reg |= FM801_GPIO_GP(TEA_256PCPR_DATA);
+		else
+			reg &= ~FM801_GPIO_GP(TEA_256PCPR_DATA);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+		reg |= FM801_GPIO_GP(TEA_256PCPR_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		reg &= ~FM801_GPIO_GP(TEA_256PCPR_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+	}
+
+	/* and reset the write enable bit */
+	reg |= FM801_GPIO_GP(TEA_256PCPR_WRITE_ENABLE) |
+	       FM801_GPIO_GP(TEA_256PCPR_DATA);
+	outw(reg, FM801_REG(chip, GPIO_CTRL));
+	spin_unlock_irq(&chip->reg_lock);
+}
+
+static unsigned int snd_fm801_tea575x_256pcpr_read(struct snd_tea575x *tea)
+{
+	struct fm801 *chip = tea->private_data;
+	unsigned short reg;
+	unsigned int val = 0;
+	int i;
+	
+	spin_lock_irq(&chip->reg_lock);
+	reg = inw(FM801_REG(chip, GPIO_CTRL));
+	/* use GPIO lines, set data direction to input */
+	reg |= FM801_GPIO_GS(TEA_256PCPR_DATA) |
+	       FM801_GPIO_GS(TEA_256PCPR_WRITE_ENABLE) |
+	       FM801_GPIO_GS(TEA_256PCPR_BUS_CLOCK) |
+	       FM801_GPIO_GD(TEA_256PCPR_DATA) |
+	       FM801_GPIO_GP(TEA_256PCPR_DATA) |
+	       FM801_GPIO_GP(TEA_256PCPR_WRITE_ENABLE);
+	/* all of lines are in the write direction, except data */
+	/* clear data, write enable and clock lines */
+	reg &= ~(FM801_GPIO_GD(TEA_256PCPR_WRITE_ENABLE) |
+	         FM801_GPIO_GD(TEA_256PCPR_BUS_CLOCK) |
+	         FM801_GPIO_GP(TEA_256PCPR_BUS_CLOCK));
+
+	for (i = 0; i < 24; i++) {
+		reg &= ~FM801_GPIO_GP(TEA_256PCPR_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+		reg |= FM801_GPIO_GP(TEA_256PCPR_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+		val <<= 1;
+		if (inw(FM801_REG(chip, GPIO_CTRL)) & FM801_GPIO_GP(TEA_256PCPR_DATA))
+			val |= 1;
+	}
+
+	spin_unlock_irq(&chip->reg_lock);
+
+	return val;
+}
+
+/* 64PCR GPIO numbers */
+#define TEA_64PCR_BUS_CLOCK		0
+#define TEA_64PCR_WRITE_ENABLE		1	/* inverted */
+#define TEA_64PCR_DATA			2
+
+static void snd_fm801_tea575x_64pcr_write(struct snd_tea575x *tea, unsigned int val)
+{
+	struct fm801 *chip = tea->private_data;
+	unsigned short reg;
+	int i = 25;
+
+	spin_lock_irq(&chip->reg_lock);
+	reg = inw(FM801_REG(chip, GPIO_CTRL));
+	/* use GPIO lines and set write enable bit */
+	reg |= FM801_GPIO_GS(TEA_64PCR_DATA) |
+	       FM801_GPIO_GS(TEA_64PCR_WRITE_ENABLE) |
+	       FM801_GPIO_GS(TEA_64PCR_BUS_CLOCK);
+	/* all of lines are in the write direction */
+	/* clear data and clock lines */
+	reg &= ~(FM801_GPIO_GD(TEA_64PCR_DATA) |
+	         FM801_GPIO_GD(TEA_64PCR_WRITE_ENABLE) |
+	         FM801_GPIO_GD(TEA_64PCR_BUS_CLOCK) |
+	         FM801_GPIO_GP(TEA_64PCR_DATA) |
+	         FM801_GPIO_GP(TEA_64PCR_BUS_CLOCK) |
+		 FM801_GPIO_GP(TEA_64PCR_WRITE_ENABLE));
+	outw(reg, FM801_REG(chip, GPIO_CTRL));
+	udelay(1);
+
+	while (i--) {
+		if (val & (1 << i))
+			reg |= FM801_GPIO_GP(TEA_64PCR_DATA);
+		else
+			reg &= ~FM801_GPIO_GP(TEA_64PCR_DATA);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+		reg |= FM801_GPIO_GP(TEA_64PCR_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		reg &= ~FM801_GPIO_GP(TEA_64PCR_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+	}
+
+	/* and reset the write enable bit */
+	reg |= FM801_GPIO_GP(TEA_64PCR_WRITE_ENABLE) |
+	       FM801_GPIO_GP(TEA_64PCR_DATA);
+	outw(reg, FM801_REG(chip, GPIO_CTRL));
+	spin_unlock_irq(&chip->reg_lock);
+}
+
+static unsigned int snd_fm801_tea575x_64pcr_read(struct snd_tea575x *tea)
+{
+	struct fm801 *chip = tea->private_data;
+	unsigned short reg;
+	unsigned int val = 0;
+	int i;
+	
+	spin_lock_irq(&chip->reg_lock);
+	reg = inw(FM801_REG(chip, GPIO_CTRL));
+	/* use GPIO lines, set data direction to input */
+	reg |= FM801_GPIO_GS(TEA_64PCR_DATA) |
+	       FM801_GPIO_GS(TEA_64PCR_WRITE_ENABLE) |
+	       FM801_GPIO_GS(TEA_64PCR_BUS_CLOCK) |
+	       FM801_GPIO_GD(TEA_64PCR_DATA) |
+	       FM801_GPIO_GP(TEA_64PCR_DATA) |
+	       FM801_GPIO_GP(TEA_64PCR_WRITE_ENABLE);
+	/* all of lines are in the write direction, except data */
+	/* clear data, write enable and clock lines */
+	reg &= ~(FM801_GPIO_GD(TEA_64PCR_WRITE_ENABLE) |
+	         FM801_GPIO_GD(TEA_64PCR_BUS_CLOCK) |
+	         FM801_GPIO_GP(TEA_64PCR_BUS_CLOCK));
+
+	for (i = 0; i < 24; i++) {
+		reg &= ~FM801_GPIO_GP(TEA_64PCR_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+		reg |= FM801_GPIO_GP(TEA_64PCR_BUS_CLOCK);
+		outw(reg, FM801_REG(chip, GPIO_CTRL));
+		udelay(1);
+		val <<= 1;
+		if (inw(FM801_REG(chip, GPIO_CTRL)) & FM801_GPIO_GP(TEA_64PCR_DATA))
+			val |= 1;
+	}
+
+	spin_unlock_irq(&chip->reg_lock);
+
+	return val;
+}
+
+static void snd_fm801_tea575x_64pcr_mute(struct snd_tea575x *tea,
+					  unsigned int mute)
+{
+	struct fm801 *chip = tea->private_data;
+	unsigned short reg;
+
+	spin_lock_irq(&chip->reg_lock);
+
+	reg = inw(FM801_REG(chip, GPIO_CTRL));
+	if (mute)
+		/* 0xf800 (mute) */
+		reg &= ~FM801_GPIO_GP(TEA_64PCR_WRITE_ENABLE);
+	else
+		/* 0xf802 (unmute) */
+		reg |= FM801_GPIO_GP(TEA_64PCR_WRITE_ENABLE);
+	outw(reg, FM801_REG(chip, GPIO_CTRL));
+	udelay(1);
+
+	spin_unlock_irq(&chip->reg_lock);
+}
+
+static struct snd_tea575x_ops snd_fm801_tea_ops[3] = {
+	{
+		/* 1 = MediaForte 256-PCS */
+		.write = snd_fm801_tea575x_256pcs_write,
+		.read = snd_fm801_tea575x_256pcs_read,
+	},
+	{
+		/* 2 = MediaForte 256-PCPR */
+		.write = snd_fm801_tea575x_256pcpr_write,
+		.read = snd_fm801_tea575x_256pcpr_read,
+	},
+	{
+		/* 3 = MediaForte 64-PCR */
+		.write = snd_fm801_tea575x_64pcr_write,
+		.read = snd_fm801_tea575x_64pcr_read,
+		.mute = snd_fm801_tea575x_64pcr_mute,
+	}
+};
+#endif
+
+/*
+ *  Mixer routines
+ */
+
+#define FM801_SINGLE(xname, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_fm801_info_single, \
+  .get = snd_fm801_get_single, .put = snd_fm801_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_fm801_info_single(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_fm801_get_single(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct fm801 *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+
+	ucontrol->value.integer.value[0] = (inw(chip->port + reg) >> shift) & mask;
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_fm801_put_single(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct fm801 *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	unsigned short val;
+
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	return snd_fm801_update_bits(chip, reg, mask << shift, val << shift);
+}
+
+#define FM801_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_fm801_info_double, \
+  .get = snd_fm801_get_double, .put = snd_fm801_put_double, \
+  .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24) }
+#define FM801_DOUBLE_TLV(xname, reg, shift_left, shift_right, mask, invert, xtlv) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .name = xname, .info = snd_fm801_info_double, \
+  .get = snd_fm801_get_double, .put = snd_fm801_put_double, \
+  .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24), \
+  .tlv = { .p = (xtlv) } }
+
+static int snd_fm801_info_double(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_fm801_get_double(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct fm801 *chip = snd_kcontrol_chip(kcontrol);
+        int reg = kcontrol->private_value & 0xff;
+	int shift_left = (kcontrol->private_value >> 8) & 0x0f;
+	int shift_right = (kcontrol->private_value >> 12) & 0x0f;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+
+	spin_lock_irq(&chip->reg_lock);
+	ucontrol->value.integer.value[0] = (inw(chip->port + reg) >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (inw(chip->port + reg) >> shift_right) & mask;
+	spin_unlock_irq(&chip->reg_lock);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_fm801_put_double(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct fm801 *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift_left = (kcontrol->private_value >> 8) & 0x0f;
+	int shift_right = (kcontrol->private_value >> 12) & 0x0f;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	unsigned short val1, val2;
+ 
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	return snd_fm801_update_bits(chip, reg,
+				     (mask << shift_left) | (mask << shift_right),
+				     (val1 << shift_left ) | (val2 << shift_right));
+}
+
+static int snd_fm801_info_mux(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[5] = {
+		"AC97 Primary", "FM", "I2S", "PCM", "AC97 Secondary"
+	};
+ 
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 5;
+	if (uinfo->value.enumerated.item > 4)
+		uinfo->value.enumerated.item = 4;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_fm801_get_mux(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct fm801 *chip = snd_kcontrol_chip(kcontrol);
+        unsigned short val;
+ 
+	val = inw(FM801_REG(chip, REC_SRC)) & 7;
+	if (val > 4)
+		val = 4;
+        ucontrol->value.enumerated.item[0] = val;
+        return 0;
+}
+
+static int snd_fm801_put_mux(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct fm801 *chip = snd_kcontrol_chip(kcontrol);
+        unsigned short val;
+ 
+        if ((val = ucontrol->value.enumerated.item[0]) > 4)
+                return -EINVAL;
+	return snd_fm801_update_bits(chip, FM801_REC_SRC, 7, val);
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_dsp, -3450, 150, 0);
+
+#define FM801_CONTROLS ARRAY_SIZE(snd_fm801_controls)
+
+static struct snd_kcontrol_new snd_fm801_controls[] __devinitdata = {
+FM801_DOUBLE_TLV("Wave Playback Volume", FM801_PCM_VOL, 0, 8, 31, 1,
+		 db_scale_dsp),
+FM801_SINGLE("Wave Playback Switch", FM801_PCM_VOL, 15, 1, 1),
+FM801_DOUBLE_TLV("I2S Playback Volume", FM801_I2S_VOL, 0, 8, 31, 1,
+		 db_scale_dsp),
+FM801_SINGLE("I2S Playback Switch", FM801_I2S_VOL, 15, 1, 1),
+FM801_DOUBLE_TLV("FM Playback Volume", FM801_FM_VOL, 0, 8, 31, 1,
+		 db_scale_dsp),
+FM801_SINGLE("FM Playback Switch", FM801_FM_VOL, 15, 1, 1),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Digital Capture Source",
+	.info = snd_fm801_info_mux,
+	.get = snd_fm801_get_mux,
+	.put = snd_fm801_put_mux,
+}
+};
+
+#define FM801_CONTROLS_MULTI ARRAY_SIZE(snd_fm801_controls_multi)
+
+static struct snd_kcontrol_new snd_fm801_controls_multi[] __devinitdata = {
+FM801_SINGLE("AC97 2ch->4ch Copy Switch", FM801_CODEC_CTRL, 7, 1, 0),
+FM801_SINGLE("AC97 18-bit Switch", FM801_CODEC_CTRL, 10, 1, 0),
+FM801_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), FM801_I2S_MODE, 8, 1, 0),
+FM801_SINGLE(SNDRV_CTL_NAME_IEC958("Raw Data ",PLAYBACK,SWITCH), FM801_I2S_MODE, 9, 1, 0),
+FM801_SINGLE(SNDRV_CTL_NAME_IEC958("Raw Data ",CAPTURE,SWITCH), FM801_I2S_MODE, 10, 1, 0),
+FM801_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), FM801_GEN_CTRL, 2, 1, 0),
+};
+
+static void snd_fm801_mixer_free_ac97_bus(struct snd_ac97_bus *bus)
+{
+	struct fm801 *chip = bus->private_data;
+	chip->ac97_bus = NULL;
+}
+
+static void snd_fm801_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+	struct fm801 *chip = ac97->private_data;
+	if (ac97->num == 0) {
+		chip->ac97 = NULL;
+	} else {
+		chip->ac97_sec = NULL;
+	}
+}
+
+static int __devinit snd_fm801_mixer(struct fm801 *chip)
+{
+	struct snd_ac97_template ac97;
+	unsigned int i;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_fm801_codec_write,
+		.read = snd_fm801_codec_read,
+	};
+
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus)) < 0)
+		return err;
+	chip->ac97_bus->private_free = snd_fm801_mixer_free_ac97_bus;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.private_free = snd_fm801_mixer_free_ac97;
+	if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97)) < 0)
+		return err;
+	if (chip->secondary) {
+		ac97.num = 1;
+		ac97.addr = chip->secondary_addr;
+		if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97_sec)) < 0)
+			return err;
+	}
+	for (i = 0; i < FM801_CONTROLS; i++)
+		snd_ctl_add(chip->card, snd_ctl_new1(&snd_fm801_controls[i], chip));
+	if (chip->multichannel) {
+		for (i = 0; i < FM801_CONTROLS_MULTI; i++)
+			snd_ctl_add(chip->card, snd_ctl_new1(&snd_fm801_controls_multi[i], chip));
+	}
+	return 0;
+}
+
+/*
+ *  initialization routines
+ */
+
+static int wait_for_codec(struct fm801 *chip, unsigned int codec_id,
+			  unsigned short reg, unsigned long waits)
+{
+	unsigned long timeout = jiffies + waits;
+
+	outw(FM801_AC97_READ | (codec_id << FM801_AC97_ADDR_SHIFT) | reg,
+	     FM801_REG(chip, AC97_CMD));
+	udelay(5);
+	do {
+		if ((inw(FM801_REG(chip, AC97_CMD)) & (FM801_AC97_VALID|FM801_AC97_BUSY))
+		    == FM801_AC97_VALID)
+			return 0;
+		schedule_timeout_uninterruptible(1);
+	} while (time_after(timeout, jiffies));
+	return -EIO;
+}
+
+static int snd_fm801_chip_init(struct fm801 *chip, int resume)
+{
+	unsigned short cmdw;
+
+	if (chip->tea575x_tuner & TUNER_ONLY)
+		goto __ac97_ok;
+
+	/* codec cold reset + AC'97 warm reset */
+	outw((1<<5) | (1<<6), FM801_REG(chip, CODEC_CTRL));
+	inw(FM801_REG(chip, CODEC_CTRL)); /* flush posting data */
+	udelay(100);
+	outw(0, FM801_REG(chip, CODEC_CTRL));
+
+	if (wait_for_codec(chip, 0, AC97_RESET, msecs_to_jiffies(750)) < 0)
+		if (!resume) {
+			snd_printk(KERN_INFO "Primary AC'97 codec not found, "
+					    "assume SF64-PCR (tuner-only)\n");
+			chip->tea575x_tuner = 3 | TUNER_ONLY;
+			goto __ac97_ok;
+		}
+
+	if (chip->multichannel) {
+		if (chip->secondary_addr) {
+			wait_for_codec(chip, chip->secondary_addr,
+				       AC97_VENDOR_ID1, msecs_to_jiffies(50));
+		} else {
+			/* my card has the secondary codec */
+			/* at address #3, so the loop is inverted */
+			int i;
+			for (i = 3; i > 0; i--) {
+				if (!wait_for_codec(chip, i, AC97_VENDOR_ID1,
+						     msecs_to_jiffies(50))) {
+					cmdw = inw(FM801_REG(chip, AC97_DATA));
+					if (cmdw != 0xffff && cmdw != 0) {
+						chip->secondary = 1;
+						chip->secondary_addr = i;
+						break;
+					}
+				}
+			}
+		}
+
+		/* the recovery phase, it seems that probing for non-existing codec might */
+		/* cause timeout problems */
+		wait_for_codec(chip, 0, AC97_VENDOR_ID1, msecs_to_jiffies(750));
+	}
+
+      __ac97_ok:
+
+	/* init volume */
+	outw(0x0808, FM801_REG(chip, PCM_VOL));
+	outw(0x9f1f, FM801_REG(chip, FM_VOL));
+	outw(0x8808, FM801_REG(chip, I2S_VOL));
+
+	/* I2S control - I2S mode */
+	outw(0x0003, FM801_REG(chip, I2S_MODE));
+
+	/* interrupt setup */
+	cmdw = inw(FM801_REG(chip, IRQ_MASK));
+	if (chip->irq < 0)
+		cmdw |= 0x00c3;		/* mask everything, no PCM nor MPU */
+	else
+		cmdw &= ~0x0083;	/* unmask MPU, PLAYBACK & CAPTURE */
+	outw(cmdw, FM801_REG(chip, IRQ_MASK));
+
+	/* interrupt clear */
+	outw(FM801_IRQ_PLAYBACK|FM801_IRQ_CAPTURE|FM801_IRQ_MPU, FM801_REG(chip, IRQ_STATUS));
+
+	return 0;
+}
+
+
+static int snd_fm801_free(struct fm801 *chip)
+{
+	unsigned short cmdw;
+
+	if (chip->irq < 0)
+		goto __end_hw;
+
+	/* interrupt setup - mask everything */
+	cmdw = inw(FM801_REG(chip, IRQ_MASK));
+	cmdw |= 0x00c3;
+	outw(cmdw, FM801_REG(chip, IRQ_MASK));
+
+      __end_hw:
+#ifdef TEA575X_RADIO
+	snd_tea575x_exit(&chip->tea);
+#endif
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+
+	kfree(chip);
+	return 0;
+}
+
+static int snd_fm801_dev_free(struct snd_device *device)
+{
+	struct fm801 *chip = device->device_data;
+	return snd_fm801_free(chip);
+}
+
+static int __devinit snd_fm801_create(struct snd_card *card,
+				      struct pci_dev * pci,
+				      int tea575x_tuner,
+				      struct fm801 ** rchip)
+{
+	struct fm801 *chip;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_fm801_dev_free,
+	};
+
+	*rchip = NULL;
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	spin_lock_init(&chip->reg_lock);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	chip->tea575x_tuner = tea575x_tuner;
+	if ((err = pci_request_regions(pci, "FM801")) < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+	chip->port = pci_resource_start(pci, 0);
+	if ((tea575x_tuner & TUNER_ONLY) == 0) {
+		if (request_irq(pci->irq, snd_fm801_interrupt, IRQF_SHARED,
+				"FM801", chip)) {
+			snd_printk(KERN_ERR "unable to grab IRQ %d\n", chip->irq);
+			snd_fm801_free(chip);
+			return -EBUSY;
+		}
+		chip->irq = pci->irq;
+		pci_set_master(pci);
+	}
+
+	if (pci->revision >= 0xb1)	/* FM801-AU */
+		chip->multichannel = 1;
+
+	snd_fm801_chip_init(chip, 0);
+	/* init might set tuner access method */
+	tea575x_tuner = chip->tea575x_tuner;
+
+	if (chip->irq >= 0 && (tea575x_tuner & TUNER_ONLY)) {
+		pci_clear_master(pci);
+		free_irq(chip->irq, chip);
+		chip->irq = -1;
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_fm801_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+#ifdef TEA575X_RADIO
+	if ((tea575x_tuner & TUNER_TYPE_MASK) > 0 &&
+	    (tea575x_tuner & TUNER_TYPE_MASK) < 4) {
+		chip->tea.dev_nr = tea575x_tuner >> 16;
+		chip->tea.card = card;
+		chip->tea.freq_fixup = 10700;
+		chip->tea.private_data = chip;
+		chip->tea.ops = &snd_fm801_tea_ops[(tea575x_tuner & TUNER_TYPE_MASK) - 1];
+		snd_tea575x_init(&chip->tea);
+	}
+#endif
+
+	*rchip = chip;
+	return 0;
+}
+
+static int __devinit snd_card_fm801_probe(struct pci_dev *pci,
+					  const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct fm801 *chip;
+	struct snd_opl3 *opl3;
+	int err;
+
+        if (dev >= SNDRV_CARDS)
+                return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+	if ((err = snd_fm801_create(card, pci, tea575x_tuner[dev], &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = chip;
+
+	strcpy(card->driver, "FM801");
+	strcpy(card->shortname, "ForteMedia FM801-");
+	strcat(card->shortname, chip->multichannel ? "AU" : "AS");
+	sprintf(card->longname, "%s at 0x%lx, irq %i",
+		card->shortname, chip->port, chip->irq);
+
+	if (chip->tea575x_tuner & TUNER_ONLY)
+		goto __fm801_tuner_only;
+
+	if ((err = snd_fm801_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_fm801_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_FM801,
+				       FM801_REG(chip, MPU401_DATA),
+				       MPU401_INFO_INTEGRATED,
+				       chip->irq, 0, &chip->rmidi)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_opl3_create(card, FM801_REG(chip, OPL3_BANK0),
+				   FM801_REG(chip, OPL3_BANK1),
+				   OPL3_HW_OPL3_FM801, 1, &opl3)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+      __fm801_tuner_only:
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_card_fm801_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+#ifdef CONFIG_PM
+static unsigned char saved_regs[] = {
+	FM801_PCM_VOL, FM801_I2S_VOL, FM801_FM_VOL, FM801_REC_SRC,
+	FM801_PLY_CTRL, FM801_PLY_COUNT, FM801_PLY_BUF1, FM801_PLY_BUF2,
+	FM801_CAP_CTRL, FM801_CAP_COUNT, FM801_CAP_BUF1, FM801_CAP_BUF2,
+	FM801_CODEC_CTRL, FM801_I2S_MODE, FM801_VOLUME, FM801_GEN_CTRL,
+};
+
+static int snd_fm801_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct fm801 *chip = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_ac97_suspend(chip->ac97);
+	snd_ac97_suspend(chip->ac97_sec);
+	for (i = 0; i < ARRAY_SIZE(saved_regs); i++)
+		chip->saved_regs[i] = inw(chip->port + saved_regs[i]);
+	/* FIXME: tea575x suspend */
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_fm801_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct fm801 *chip = card->private_data;
+	int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "fm801: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_fm801_chip_init(chip, 1);
+	snd_ac97_resume(chip->ac97);
+	snd_ac97_resume(chip->ac97_sec);
+	for (i = 0; i < ARRAY_SIZE(saved_regs); i++)
+		outw(chip->saved_regs[i], chip->port + saved_regs[i]);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct pci_driver driver = {
+	.name = "FM801",
+	.id_table = snd_fm801_ids,
+	.probe = snd_card_fm801_probe,
+	.remove = __devexit_p(snd_card_fm801_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_fm801_suspend,
+	.resume = snd_fm801_resume,
+#endif
+};
+
+static int __init alsa_card_fm801_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_fm801_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_fm801_init)
+module_exit(alsa_card_fm801_exit)
diff --git a/linux/sound/pci/hda/Kconfig b/linux/sound/pci/hda/Kconfig
new file mode 100644
index 0000000..0ea5cc6
--- /dev/null
+++ b/linux/sound/pci/hda/Kconfig
@@ -0,0 +1,220 @@
+menuconfig SND_HDA_INTEL
+	tristate "Intel HD Audio"
+	select SND_PCM
+	select SND_VMASTER
+	help
+	  Say Y here to include support for Intel "High Definition
+	  Audio" (Azalia) and its compatible devices.
+
+	  This option enables the HD-audio controller.  Don't forget
+	  to choose the appropriate codec options below.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-hda-intel.
+
+if SND_HDA_INTEL
+
+config SND_HDA_HWDEP
+	bool "Build hwdep interface for HD-audio driver"
+	select SND_HWDEP
+	help
+	  Say Y here to build a hwdep interface for HD-audio driver.
+	  This interface can be used for out-of-band communication
+	  with codecs for debugging purposes.
+
+config SND_HDA_RECONFIG
+	bool "Allow dynamic codec reconfiguration (EXPERIMENTAL)"
+	depends on SND_HDA_HWDEP && EXPERIMENTAL
+	help
+	  Say Y here to enable the HD-audio codec re-configuration feature.
+	  This adds the sysfs interfaces to allow user to clear the whole
+	  codec configuration, change the codec setup, add extra verbs,
+	  and re-configure the codec dynamically.
+
+config SND_HDA_INPUT_BEEP
+	bool "Support digital beep via input layer"
+	depends on INPUT=y || INPUT=SND_HDA_INTEL
+	help
+	  Say Y here to build a digital beep interface for HD-audio
+	  driver. This interface is used to generate digital beeps.
+
+config SND_HDA_INPUT_BEEP_MODE
+	int "Digital beep registration mode (0=off, 1=on, 2=mute sw on/off)"
+	depends on SND_HDA_INPUT_BEEP=y
+	default "1"
+	range 0 2
+	help
+	  Set 0 to disable the digital beep interface for HD-audio by default.
+	  Set 1 to always enable the digital beep interface for HD-audio by
+	  default. Set 2 to control the beep device registration to input
+	  layer using a "Beep Switch" in mixer applications.
+
+config SND_HDA_INPUT_JACK
+	bool "Support jack plugging notification via input layer"
+	depends on INPUT=y || INPUT=SND
+	select SND_JACK
+	help
+	  Say Y here to enable the jack plugging notification via
+	  input layer.
+
+config SND_HDA_PATCH_LOADER
+	bool "Support initialization patch loading for HD-audio"
+	depends on EXPERIMENTAL
+	select FW_LOADER
+	select SND_HDA_HWDEP
+	select SND_HDA_RECONFIG
+	help
+	  Say Y here to allow the HD-audio driver to load a pseudo
+	  firmware file ("patch") for overriding the BIOS setup at
+	  start up.  The "patch" file can be specified via patch module
+	  option, such as patch=hda-init.
+
+	  This option turns on hwdep and reconfig features automatically.
+
+config SND_HDA_CODEC_REALTEK
+	bool "Build Realtek HD-audio codec support"
+	default y
+	help
+	  Say Y here to include Realtek HD-audio codec support in
+	  snd-hda-intel driver, such as ALC880.
+
+	  When the HD-audio driver is built as a module, the codec
+	  support code is also built as another module,
+	  snd-hda-codec-realtek.
+	  This module is automatically loaded at probing.
+
+config SND_HDA_CODEC_ANALOG
+	bool "Build Analog Device HD-audio codec support"
+	default y
+	help
+	  Say Y here to include Analog Device HD-audio codec support in
+	  snd-hda-intel driver, such as AD1986A.
+
+	  When the HD-audio driver is built as a module, the codec
+	  support code is also built as another module,
+	  snd-hda-codec-analog.
+	  This module is automatically loaded at probing.
+
+config SND_HDA_CODEC_SIGMATEL
+	bool "Build IDT/Sigmatel HD-audio codec support"
+	default y
+	help
+	  Say Y here to include IDT (Sigmatel) HD-audio codec support in
+	  snd-hda-intel driver, such as STAC9200.
+
+	  When the HD-audio driver is built as a module, the codec
+	  support code is also built as another module,
+	  snd-hda-codec-idt.
+	  This module is automatically loaded at probing.
+
+config SND_HDA_CODEC_VIA
+	bool "Build VIA HD-audio codec support"
+	default y
+	help
+	  Say Y here to include VIA HD-audio codec support in
+	  snd-hda-intel driver, such as VT1708.
+
+	  When the HD-audio driver is built as a module, the codec
+	  support code is also built as another module,
+	  snd-hda-codec-via.
+	  This module is automatically loaded at probing.
+
+config SND_HDA_CODEC_HDMI
+	bool "Build HDMI/DisplayPort HD-audio codec support"
+	select SND_DYNAMIC_MINORS
+	default y
+	help
+	  Say Y here to include HDMI and DisplayPort HD-audio codec
+	  support in snd-hda-intel driver.  This includes all AMD/ATI,
+	  Intel and Nvidia HDMI/DisplayPort codecs.
+
+	  When the HD-audio driver is built as a module, the codec
+	  support code is also built as another module,
+	  snd-hda-codec-hdmi.
+	  This module is automatically loaded at probing.
+
+config SND_HDA_CODEC_CIRRUS
+	bool "Build Cirrus Logic codec support"
+	depends on SND_HDA_INTEL
+	default y
+	help
+	  Say Y here to include Cirrus Logic codec support in
+	  snd-hda-intel driver, such as CS4206.
+
+	  When the HD-audio driver is built as a module, the codec
+	  support code is also built as another module,
+	  snd-hda-codec-cirrus.
+	  This module is automatically loaded at probing.
+
+config SND_HDA_CODEC_CONEXANT
+	bool "Build Conexant HD-audio codec support"
+	default y
+	help
+	  Say Y here to include Conexant HD-audio codec support in
+	  snd-hda-intel driver, such as CX20549.
+
+	  When the HD-audio driver is built as a module, the codec
+	  support code is also built as another module,
+	  snd-hda-codec-conexant.
+	  This module is automatically loaded at probing.
+
+config SND_HDA_CODEC_CA0110
+	bool "Build Creative CA0110-IBG codec support"
+	depends on SND_HDA_INTEL
+	default y
+	help
+	  Say Y here to include Creative CA0110-IBG codec support in
+	  snd-hda-intel driver, found on some Creative X-Fi cards.
+
+	  When the HD-audio driver is built as a module, the codec
+	  support code is also built as another module,
+	  snd-hda-codec-ca0110.
+	  This module is automatically loaded at probing.
+
+config SND_HDA_CODEC_CMEDIA
+	bool "Build C-Media HD-audio codec support"
+	default y
+	help
+	  Say Y here to include C-Media HD-audio codec support in
+	  snd-hda-intel driver, such as CMI9880.
+
+	  When the HD-audio driver is built as a module, the codec
+	  support code is also built as another module,
+	  snd-hda-codec-cmedia.
+	  This module is automatically loaded at probing.
+
+config SND_HDA_CODEC_SI3054
+	bool "Build Silicon Labs 3054 HD-modem codec support"
+	default y
+	help
+	  Say Y here to include Silicon Labs 3054 HD-modem codec
+	  (and compatibles) support in snd-hda-intel driver.
+
+	  When the HD-audio driver is built as a module, the codec
+	  support code is also built as another module,
+	  snd-hda-codec-si3054.
+	  This module is automatically loaded at probing.
+
+config SND_HDA_GENERIC
+	bool "Enable generic HD-audio codec parser"
+	default y
+	help
+	  Say Y here to enable the generic HD-audio codec parser
+	  in snd-hda-intel driver.
+
+config SND_HDA_POWER_SAVE
+	bool "Aggressive power-saving on HD-audio"
+	help
+	  Say Y here to enable more aggressive power-saving mode on
+	  HD-audio driver.  The power-saving timeout can be configured
+	  via power_save option or over sysfs on-the-fly.
+
+config SND_HDA_POWER_SAVE_DEFAULT
+	int "Default time-out for HD-audio power-save mode"
+	depends on SND_HDA_POWER_SAVE
+	default 0
+	help
+	  The default time-out value in seconds for HD-audio automatic
+	  power-save mode.  0 means to disable the power-save mode.
+
+endif
diff --git a/linux/sound/pci/hda/Makefile b/linux/sound/pci/hda/Makefile
new file mode 100644
index 0000000..17ef365
--- /dev/null
+++ b/linux/sound/pci/hda/Makefile
@@ -0,0 +1,58 @@
+snd-hda-intel-objs := hda_intel.o
+
+snd-hda-codec-y := hda_codec.o
+snd-hda-codec-$(CONFIG_SND_HDA_GENERIC) += hda_generic.o
+snd-hda-codec-$(CONFIG_PROC_FS) += hda_proc.o
+snd-hda-codec-$(CONFIG_SND_HDA_HWDEP) += hda_hwdep.o
+snd-hda-codec-$(CONFIG_SND_HDA_INPUT_BEEP) += hda_beep.o
+
+snd-hda-codec-realtek-objs :=	patch_realtek.o
+snd-hda-codec-cmedia-objs :=	patch_cmedia.o
+snd-hda-codec-analog-objs :=	patch_analog.o
+snd-hda-codec-idt-objs :=	patch_sigmatel.o
+snd-hda-codec-si3054-objs :=	patch_si3054.o
+snd-hda-codec-cirrus-objs :=	patch_cirrus.o
+snd-hda-codec-ca0110-objs :=	patch_ca0110.o
+snd-hda-codec-conexant-objs :=	patch_conexant.o
+snd-hda-codec-via-objs :=	patch_via.o
+snd-hda-codec-hdmi-objs :=	patch_hdmi.o hda_eld.o
+
+# common driver
+obj-$(CONFIG_SND_HDA_INTEL) := snd-hda-codec.o
+
+# codec drivers (note: CONFIG_SND_HDA_CODEC_XXX are booleans)
+ifdef CONFIG_SND_HDA_CODEC_REALTEK
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-realtek.o
+endif
+ifdef CONFIG_SND_HDA_CODEC_CMEDIA
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-cmedia.o
+endif
+ifdef CONFIG_SND_HDA_CODEC_ANALOG
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-analog.o
+endif
+ifdef CONFIG_SND_HDA_CODEC_SIGMATEL
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-idt.o
+endif
+ifdef CONFIG_SND_HDA_CODEC_SI3054
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-si3054.o
+endif
+ifdef CONFIG_SND_HDA_CODEC_CIRRUS
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-cirrus.o
+endif
+ifdef CONFIG_SND_HDA_CODEC_CA0110
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-ca0110.o
+endif
+ifdef CONFIG_SND_HDA_CODEC_CONEXANT
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-conexant.o
+endif
+ifdef CONFIG_SND_HDA_CODEC_VIA
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-via.o
+endif
+ifdef CONFIG_SND_HDA_CODEC_HDMI
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-hdmi.o
+endif
+
+# this must be the last entry after codec drivers;
+# otherwise the codec patches won't be hooked before the PCI probe
+# when built in kernel
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o
diff --git a/linux/sound/pci/hda/hda_beep.c b/linux/sound/pci/hda/hda_beep.c
new file mode 100644
index 0000000..29714c8
--- /dev/null
+++ b/linux/sound/pci/hda/hda_beep.c
@@ -0,0 +1,267 @@
+/*
+ * Digital Beep Input Interface for HD-audio codec
+ *
+ * Author: Matthew Ranostay <mranostay@embeddedalley.com>
+ * Copyright (c) 2008 Embedded Alley Solutions Inc
+ *
+ *  This driver 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 driver 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/input.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <sound/core.h>
+#include "hda_beep.h"
+#include "hda_local.h"
+
+enum {
+	DIGBEEP_HZ_STEP = 46875,	/* 46.875 Hz */
+	DIGBEEP_HZ_MIN = 93750,		/* 93.750 Hz */
+	DIGBEEP_HZ_MAX = 12000000,	/* 12 KHz */
+};
+
+static void snd_hda_generate_beep(struct work_struct *work)
+{
+	struct hda_beep *beep =
+		container_of(work, struct hda_beep, beep_work);
+	struct hda_codec *codec = beep->codec;
+
+	if (!beep->enabled)
+		return;
+
+	/* generate tone */
+	snd_hda_codec_write(codec, beep->nid, 0,
+			AC_VERB_SET_BEEP_CONTROL, beep->tone);
+}
+
+/* (non-standard) Linear beep tone calculation for IDT/STAC codecs 
+ *
+ * The tone frequency of beep generator on IDT/STAC codecs is
+ * defined from the 8bit tone parameter, in Hz,
+ *    freq = 48000 * (257 - tone) / 1024
+ * that is from 12kHz to 93.75Hz in steps of 46.875 Hz
+ */
+static int beep_linear_tone(struct hda_beep *beep, int hz)
+{
+	if (hz <= 0)
+		return 0;
+	hz *= 1000; /* fixed point */
+	hz = hz - DIGBEEP_HZ_MIN
+		+ DIGBEEP_HZ_STEP / 2; /* round to nearest step */
+	if (hz < 0)
+		hz = 0; /* turn off PC beep*/
+	else if (hz >= (DIGBEEP_HZ_MAX - DIGBEEP_HZ_MIN))
+		hz = 1; /* max frequency */
+	else {
+		hz /= DIGBEEP_HZ_STEP;
+		hz = 255 - hz;
+	}
+	return hz;
+}
+
+/* HD-audio standard beep tone parameter calculation
+ *
+ * The tone frequency in Hz is calculated as
+ *   freq = 48000 / (tone * 4)
+ * from 47Hz to 12kHz
+ */
+static int beep_standard_tone(struct hda_beep *beep, int hz)
+{
+	if (hz <= 0)
+		return 0; /* disabled */
+	hz = 12000 / hz;
+	if (hz > 0xff)
+		return 0xff;
+	if (hz <= 0)
+		return 1;
+	return hz;
+}
+
+static int snd_hda_beep_event(struct input_dev *dev, unsigned int type,
+				unsigned int code, int hz)
+{
+	struct hda_beep *beep = input_get_drvdata(dev);
+
+	switch (code) {
+	case SND_BELL:
+		if (hz)
+			hz = 1000;
+	case SND_TONE:
+		if (beep->linear_tone)
+			beep->tone = beep_linear_tone(beep, hz);
+		else
+			beep->tone = beep_standard_tone(beep, hz);
+		break;
+	default:
+		return -1;
+	}
+
+	/* schedule beep event */
+	schedule_work(&beep->beep_work);
+	return 0;
+}
+
+static void snd_hda_do_detach(struct hda_beep *beep)
+{
+	input_unregister_device(beep->dev);
+	beep->dev = NULL;
+	cancel_work_sync(&beep->beep_work);
+	/* turn off beep for sure */
+	snd_hda_codec_write(beep->codec, beep->nid, 0,
+				  AC_VERB_SET_BEEP_CONTROL, 0);
+}
+
+static int snd_hda_do_attach(struct hda_beep *beep)
+{
+	struct input_dev *input_dev;
+	struct hda_codec *codec = beep->codec;
+	int err;
+
+	input_dev = input_allocate_device();
+	if (!input_dev) {
+		printk(KERN_INFO "hda_beep: unable to allocate input device\n");
+		return -ENOMEM;
+	}
+
+	/* setup digital beep device */
+	input_dev->name = "HDA Digital PCBeep";
+	input_dev->phys = beep->phys;
+	input_dev->id.bustype = BUS_PCI;
+
+	input_dev->id.vendor = codec->vendor_id >> 16;
+	input_dev->id.product = codec->vendor_id & 0xffff;
+	input_dev->id.version = 0x01;
+
+	input_dev->evbit[0] = BIT_MASK(EV_SND);
+	input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+	input_dev->event = snd_hda_beep_event;
+	input_dev->dev.parent = &codec->bus->pci->dev;
+	input_set_drvdata(input_dev, beep);
+
+	err = input_register_device(input_dev);
+	if (err < 0) {
+		input_free_device(input_dev);
+		printk(KERN_INFO "hda_beep: unable to register input device\n");
+		return err;
+	}
+	beep->dev = input_dev;
+	return 0;
+}
+
+static void snd_hda_do_register(struct work_struct *work)
+{
+	struct hda_beep *beep =
+		container_of(work, struct hda_beep, register_work);
+
+	mutex_lock(&beep->mutex);
+	if (beep->enabled && !beep->dev)
+		snd_hda_do_attach(beep);
+	mutex_unlock(&beep->mutex);
+}
+
+static void snd_hda_do_unregister(struct work_struct *work)
+{
+	struct hda_beep *beep =
+		container_of(work, struct hda_beep, unregister_work.work);
+
+	mutex_lock(&beep->mutex);
+	if (!beep->enabled && beep->dev)
+		snd_hda_do_detach(beep);
+	mutex_unlock(&beep->mutex);
+}
+
+int snd_hda_enable_beep_device(struct hda_codec *codec, int enable)
+{
+	struct hda_beep *beep = codec->beep;
+	enable = !!enable;
+	if (beep == NULL)
+		return 0;
+	if (beep->enabled != enable) {
+		beep->enabled = enable;
+		if (!enable) {
+			/* turn off beep */
+			snd_hda_codec_write(beep->codec, beep->nid, 0,
+						  AC_VERB_SET_BEEP_CONTROL, 0);
+		}
+		if (beep->mode == HDA_BEEP_MODE_SWREG) {
+			if (enable) {
+				cancel_delayed_work(&beep->unregister_work);
+				schedule_work(&beep->register_work);
+			} else {
+				schedule_delayed_work(&beep->unregister_work,
+									   HZ);
+			}
+		}
+		return 1;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_enable_beep_device);
+
+int snd_hda_attach_beep_device(struct hda_codec *codec, int nid)
+{
+	struct hda_beep *beep;
+
+	if (!snd_hda_get_bool_hint(codec, "beep"))
+		return 0; /* disabled explicitly by hints */
+	if (codec->beep_mode == HDA_BEEP_MODE_OFF)
+		return 0; /* disabled by module option */
+
+	beep = kzalloc(sizeof(*beep), GFP_KERNEL);
+	if (beep == NULL)
+		return -ENOMEM;
+	snprintf(beep->phys, sizeof(beep->phys),
+		"card%d/codec#%d/beep0", codec->bus->card->number, codec->addr);
+	/* enable linear scale */
+	snd_hda_codec_write(codec, nid, 0,
+		AC_VERB_SET_DIGI_CONVERT_2, 0x01);
+
+	beep->nid = nid;
+	beep->codec = codec;
+	beep->mode = codec->beep_mode;
+	codec->beep = beep;
+
+	INIT_WORK(&beep->register_work, &snd_hda_do_register);
+	INIT_DELAYED_WORK(&beep->unregister_work, &snd_hda_do_unregister);
+	INIT_WORK(&beep->beep_work, &snd_hda_generate_beep);
+	mutex_init(&beep->mutex);
+
+	if (beep->mode == HDA_BEEP_MODE_ON) {
+		int err = snd_hda_do_attach(beep);
+		if (err < 0) {
+			kfree(beep);
+			codec->beep = NULL;
+			return err;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_attach_beep_device);
+
+void snd_hda_detach_beep_device(struct hda_codec *codec)
+{
+	struct hda_beep *beep = codec->beep;
+	if (beep) {
+		cancel_work_sync(&beep->register_work);
+		cancel_delayed_work(&beep->unregister_work);
+		if (beep->dev)
+			snd_hda_do_detach(beep);
+		codec->beep = NULL;
+		kfree(beep);
+	}
+}
+EXPORT_SYMBOL_HDA(snd_hda_detach_beep_device);
diff --git a/linux/sound/pci/hda/hda_beep.h b/linux/sound/pci/hda/hda_beep.h
new file mode 100644
index 0000000..f1de1ba
--- /dev/null
+++ b/linux/sound/pci/hda/hda_beep.h
@@ -0,0 +1,56 @@
+/*
+ * Digital Beep Input Interface for HD-audio codec
+ *
+ * Author: Matthew Ranostay <mranostay@embeddedalley.com>
+ * Copyright (c) 2008 Embedded Alley Solutions Inc
+ *
+ *  This driver 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 driver 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 __SOUND_HDA_BEEP_H
+#define __SOUND_HDA_BEEP_H
+
+#include "hda_codec.h"
+
+#define HDA_BEEP_MODE_OFF	0
+#define HDA_BEEP_MODE_ON	1
+#define HDA_BEEP_MODE_SWREG	2
+
+/* beep information */
+struct hda_beep {
+	struct input_dev *dev;
+	struct hda_codec *codec;
+	unsigned int mode;
+	char phys[32];
+	int tone;
+	hda_nid_t nid;
+	unsigned int enabled:1;
+	unsigned int request_enable:1;
+	unsigned int linear_tone:1;	/* linear tone for IDT/STAC codec */
+	struct work_struct register_work; /* registration work */
+	struct delayed_work unregister_work; /* unregistration work */
+	struct work_struct beep_work; /* scheduled task for beep event */
+	struct mutex mutex;
+};
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+int snd_hda_enable_beep_device(struct hda_codec *codec, int enable);
+int snd_hda_attach_beep_device(struct hda_codec *codec, int nid);
+void snd_hda_detach_beep_device(struct hda_codec *codec);
+#else
+#define snd_hda_attach_beep_device(...)		0
+#define snd_hda_detach_beep_device(...)
+#endif
+#endif
diff --git a/linux/sound/pci/hda/hda_codec.c b/linux/sound/pci/hda/hda_codec.c
new file mode 100644
index 0000000..98b6d02
--- /dev/null
+++ b/linux/sound/pci/hda/hda_codec.c
@@ -0,0 +1,4960 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include <sound/asoundef.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include "hda_local.h"
+#include "hda_beep.h"
+#include <sound/hda_hwdep.h>
+
+/*
+ * vendor / preset table
+ */
+
+struct hda_vendor_id {
+	unsigned int id;
+	const char *name;
+};
+
+/* codec vendor labels */
+static struct hda_vendor_id hda_vendor_ids[] = {
+	{ 0x1002, "ATI" },
+	{ 0x1013, "Cirrus Logic" },
+	{ 0x1057, "Motorola" },
+	{ 0x1095, "Silicon Image" },
+	{ 0x10de, "Nvidia" },
+	{ 0x10ec, "Realtek" },
+	{ 0x1102, "Creative" },
+	{ 0x1106, "VIA" },
+	{ 0x111d, "IDT" },
+	{ 0x11c1, "LSI" },
+	{ 0x11d4, "Analog Devices" },
+	{ 0x13f6, "C-Media" },
+	{ 0x14f1, "Conexant" },
+	{ 0x17e8, "Chrontel" },
+	{ 0x1854, "LG" },
+	{ 0x1aec, "Wolfson Microelectronics" },
+	{ 0x434d, "C-Media" },
+	{ 0x8086, "Intel" },
+	{ 0x8384, "SigmaTel" },
+	{} /* terminator */
+};
+
+static DEFINE_MUTEX(preset_mutex);
+static LIST_HEAD(hda_preset_tables);
+
+int snd_hda_add_codec_preset(struct hda_codec_preset_list *preset)
+{
+	mutex_lock(&preset_mutex);
+	list_add_tail(&preset->list, &hda_preset_tables);
+	mutex_unlock(&preset_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_add_codec_preset);
+
+int snd_hda_delete_codec_preset(struct hda_codec_preset_list *preset)
+{
+	mutex_lock(&preset_mutex);
+	list_del(&preset->list);
+	mutex_unlock(&preset_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_delete_codec_preset);
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static void hda_power_work(struct work_struct *work);
+static void hda_keep_power_on(struct hda_codec *codec);
+#else
+static inline void hda_keep_power_on(struct hda_codec *codec) {}
+#endif
+
+/**
+ * snd_hda_get_jack_location - Give a location string of the jack
+ * @cfg: pin default config value
+ *
+ * Parse the pin default config value and returns the string of the
+ * jack location, e.g. "Rear", "Front", etc.
+ */
+const char *snd_hda_get_jack_location(u32 cfg)
+{
+	static char *bases[7] = {
+		"N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom",
+	};
+	static unsigned char specials_idx[] = {
+		0x07, 0x08,
+		0x17, 0x18, 0x19,
+		0x37, 0x38
+	};
+	static char *specials[] = {
+		"Rear Panel", "Drive Bar",
+		"Riser", "HDMI", "ATAPI",
+		"Mobile-In", "Mobile-Out"
+	};
+	int i;
+	cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;
+	if ((cfg & 0x0f) < 7)
+		return bases[cfg & 0x0f];
+	for (i = 0; i < ARRAY_SIZE(specials_idx); i++) {
+		if (cfg == specials_idx[i])
+			return specials[i];
+	}
+	return "UNKNOWN";
+}
+EXPORT_SYMBOL_HDA(snd_hda_get_jack_location);
+
+/**
+ * snd_hda_get_jack_connectivity - Give a connectivity string of the jack
+ * @cfg: pin default config value
+ *
+ * Parse the pin default config value and returns the string of the
+ * jack connectivity, i.e. external or internal connection.
+ */
+const char *snd_hda_get_jack_connectivity(u32 cfg)
+{
+	static char *jack_locations[4] = { "Ext", "Int", "Sep", "Oth" };
+
+	return jack_locations[(cfg >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3];
+}
+EXPORT_SYMBOL_HDA(snd_hda_get_jack_connectivity);
+
+/**
+ * snd_hda_get_jack_type - Give a type string of the jack
+ * @cfg: pin default config value
+ *
+ * Parse the pin default config value and returns the string of the
+ * jack type, i.e. the purpose of the jack, such as Line-Out or CD.
+ */
+const char *snd_hda_get_jack_type(u32 cfg)
+{
+	static char *jack_types[16] = {
+		"Line Out", "Speaker", "HP Out", "CD",
+		"SPDIF Out", "Digital Out", "Modem Line", "Modem Hand",
+		"Line In", "Aux", "Mic", "Telephony",
+		"SPDIF In", "Digitial In", "Reserved", "Other"
+	};
+
+	return jack_types[(cfg & AC_DEFCFG_DEVICE)
+				>> AC_DEFCFG_DEVICE_SHIFT];
+}
+EXPORT_SYMBOL_HDA(snd_hda_get_jack_type);
+
+/*
+ * Compose a 32bit command word to be sent to the HD-audio controller
+ */
+static inline unsigned int
+make_codec_cmd(struct hda_codec *codec, hda_nid_t nid, int direct,
+	       unsigned int verb, unsigned int parm)
+{
+	u32 val;
+
+	if ((codec->addr & ~0xf) || (direct & ~1) || (nid & ~0x7f) ||
+	    (verb & ~0xfff) || (parm & ~0xffff)) {
+		printk(KERN_ERR "hda-codec: out of range cmd %x:%x:%x:%x:%x\n",
+		       codec->addr, direct, nid, verb, parm);
+		return ~0;
+	}
+
+	val = (u32)codec->addr << 28;
+	val |= (u32)direct << 27;
+	val |= (u32)nid << 20;
+	val |= verb << 8;
+	val |= parm;
+	return val;
+}
+
+/*
+ * Send and receive a verb
+ */
+static int codec_exec_verb(struct hda_codec *codec, unsigned int cmd,
+			   unsigned int *res)
+{
+	struct hda_bus *bus = codec->bus;
+	int err;
+
+	if (cmd == ~0)
+		return -1;
+
+	if (res)
+		*res = -1;
+ again:
+	snd_hda_power_up(codec);
+	mutex_lock(&bus->cmd_mutex);
+	err = bus->ops.command(bus, cmd);
+	if (!err && res)
+		*res = bus->ops.get_response(bus, codec->addr);
+	mutex_unlock(&bus->cmd_mutex);
+	snd_hda_power_down(codec);
+	if (res && *res == -1 && bus->rirb_error) {
+		if (bus->response_reset) {
+			snd_printd("hda_codec: resetting BUS due to "
+				   "fatal communication error\n");
+			bus->ops.bus_reset(bus);
+		}
+		goto again;
+	}
+	/* clear reset-flag when the communication gets recovered */
+	if (!err)
+		bus->response_reset = 0;
+	return err;
+}
+
+/**
+ * snd_hda_codec_read - send a command and get the response
+ * @codec: the HDA codec
+ * @nid: NID to send the command
+ * @direct: direct flag
+ * @verb: the verb to send
+ * @parm: the parameter for the verb
+ *
+ * Send a single command and read the corresponding response.
+ *
+ * Returns the obtained response value, or -1 for an error.
+ */
+unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid,
+				int direct,
+				unsigned int verb, unsigned int parm)
+{
+	unsigned cmd = make_codec_cmd(codec, nid, direct, verb, parm);
+	unsigned int res;
+	codec_exec_verb(codec, cmd, &res);
+	return res;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_read);
+
+/**
+ * snd_hda_codec_write - send a single command without waiting for response
+ * @codec: the HDA codec
+ * @nid: NID to send the command
+ * @direct: direct flag
+ * @verb: the verb to send
+ * @parm: the parameter for the verb
+ *
+ * Send a single command without waiting for response.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int direct,
+			 unsigned int verb, unsigned int parm)
+{
+	unsigned int cmd = make_codec_cmd(codec, nid, direct, verb, parm);
+	unsigned int res;
+	return codec_exec_verb(codec, cmd,
+			       codec->bus->sync_write ? &res : NULL);
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_write);
+
+/**
+ * snd_hda_sequence_write - sequence writes
+ * @codec: the HDA codec
+ * @seq: VERB array to send
+ *
+ * Send the commands sequentially from the given array.
+ * The array must be terminated with NID=0.
+ */
+void snd_hda_sequence_write(struct hda_codec *codec, const struct hda_verb *seq)
+{
+	for (; seq->nid; seq++)
+		snd_hda_codec_write(codec, seq->nid, 0, seq->verb, seq->param);
+}
+EXPORT_SYMBOL_HDA(snd_hda_sequence_write);
+
+/**
+ * snd_hda_get_sub_nodes - get the range of sub nodes
+ * @codec: the HDA codec
+ * @nid: NID to parse
+ * @start_id: the pointer to store the start NID
+ *
+ * Parse the NID and store the start NID of its sub-nodes.
+ * Returns the number of sub-nodes.
+ */
+int snd_hda_get_sub_nodes(struct hda_codec *codec, hda_nid_t nid,
+			  hda_nid_t *start_id)
+{
+	unsigned int parm;
+
+	parm = snd_hda_param_read(codec, nid, AC_PAR_NODE_COUNT);
+	if (parm == -1)
+		return 0;
+	*start_id = (parm >> 16) & 0x7fff;
+	return (int)(parm & 0x7fff);
+}
+EXPORT_SYMBOL_HDA(snd_hda_get_sub_nodes);
+
+/**
+ * snd_hda_get_connections - get connection list
+ * @codec: the HDA codec
+ * @nid: NID to parse
+ * @conn_list: connection list array
+ * @max_conns: max. number of connections to store
+ *
+ * Parses the connection list of the given widget and stores the list
+ * of NIDs.
+ *
+ * Returns the number of connections, or a negative error code.
+ */
+int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
+			    hda_nid_t *conn_list, int max_conns)
+{
+	unsigned int parm;
+	int i, conn_len, conns;
+	unsigned int shift, num_elems, mask;
+	unsigned int wcaps;
+	hda_nid_t prev_nid;
+
+	if (snd_BUG_ON(!conn_list || max_conns <= 0))
+		return -EINVAL;
+
+	wcaps = get_wcaps(codec, nid);
+	if (!(wcaps & AC_WCAP_CONN_LIST) &&
+	    get_wcaps_type(wcaps) != AC_WID_VOL_KNB) {
+		snd_printk(KERN_WARNING "hda_codec: "
+			   "connection list not available for 0x%x\n", nid);
+		return -EINVAL;
+	}
+
+	parm = snd_hda_param_read(codec, nid, AC_PAR_CONNLIST_LEN);
+	if (parm & AC_CLIST_LONG) {
+		/* long form */
+		shift = 16;
+		num_elems = 2;
+	} else {
+		/* short form */
+		shift = 8;
+		num_elems = 4;
+	}
+	conn_len = parm & AC_CLIST_LENGTH;
+	mask = (1 << (shift-1)) - 1;
+
+	if (!conn_len)
+		return 0; /* no connection */
+
+	if (conn_len == 1) {
+		/* single connection */
+		parm = snd_hda_codec_read(codec, nid, 0,
+					  AC_VERB_GET_CONNECT_LIST, 0);
+		if (parm == -1 && codec->bus->rirb_error)
+			return -EIO;
+		conn_list[0] = parm & mask;
+		return 1;
+	}
+
+	/* multi connection */
+	conns = 0;
+	prev_nid = 0;
+	for (i = 0; i < conn_len; i++) {
+		int range_val;
+		hda_nid_t val, n;
+
+		if (i % num_elems == 0) {
+			parm = snd_hda_codec_read(codec, nid, 0,
+						  AC_VERB_GET_CONNECT_LIST, i);
+			if (parm == -1 && codec->bus->rirb_error)
+				return -EIO;
+		}
+		range_val = !!(parm & (1 << (shift-1))); /* ranges */
+		val = parm & mask;
+		if (val == 0) {
+			snd_printk(KERN_WARNING "hda_codec: "
+				   "invalid CONNECT_LIST verb %x[%i]:%x\n",
+				    nid, i, parm);
+			return 0;
+		}
+		parm >>= shift;
+		if (range_val) {
+			/* ranges between the previous and this one */
+			if (!prev_nid || prev_nid >= val) {
+				snd_printk(KERN_WARNING "hda_codec: "
+					   "invalid dep_range_val %x:%x\n",
+					   prev_nid, val);
+				continue;
+			}
+			for (n = prev_nid + 1; n <= val; n++) {
+				if (conns >= max_conns) {
+					snd_printk(KERN_ERR "hda_codec: "
+						   "Too many connections %d for NID 0x%x\n",
+						   conns, nid);
+					return -EINVAL;
+				}
+				conn_list[conns++] = n;
+			}
+		} else {
+			if (conns >= max_conns) {
+				snd_printk(KERN_ERR "hda_codec: "
+					   "Too many connections %d for NID 0x%x\n",
+					   conns, nid);
+				return -EINVAL;
+			}
+			conn_list[conns++] = val;
+		}
+		prev_nid = val;
+	}
+	return conns;
+}
+EXPORT_SYMBOL_HDA(snd_hda_get_connections);
+
+
+/**
+ * snd_hda_queue_unsol_event - add an unsolicited event to queue
+ * @bus: the BUS
+ * @res: unsolicited event (lower 32bit of RIRB entry)
+ * @res_ex: codec addr and flags (upper 32bit or RIRB entry)
+ *
+ * Adds the given event to the queue.  The events are processed in
+ * the workqueue asynchronously.  Call this function in the interrupt
+ * hanlder when RIRB receives an unsolicited event.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex)
+{
+	struct hda_bus_unsolicited *unsol;
+	unsigned int wp;
+
+	unsol = bus->unsol;
+	if (!unsol)
+		return 0;
+
+	wp = (unsol->wp + 1) % HDA_UNSOL_QUEUE_SIZE;
+	unsol->wp = wp;
+
+	wp <<= 1;
+	unsol->queue[wp] = res;
+	unsol->queue[wp + 1] = res_ex;
+
+	queue_work(bus->workq, &unsol->work);
+
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_queue_unsol_event);
+
+/*
+ * process queued unsolicited events
+ */
+static void process_unsol_events(struct work_struct *work)
+{
+	struct hda_bus_unsolicited *unsol =
+		container_of(work, struct hda_bus_unsolicited, work);
+	struct hda_bus *bus = unsol->bus;
+	struct hda_codec *codec;
+	unsigned int rp, caddr, res;
+
+	while (unsol->rp != unsol->wp) {
+		rp = (unsol->rp + 1) % HDA_UNSOL_QUEUE_SIZE;
+		unsol->rp = rp;
+		rp <<= 1;
+		res = unsol->queue[rp];
+		caddr = unsol->queue[rp + 1];
+		if (!(caddr & (1 << 4))) /* no unsolicited event? */
+			continue;
+		codec = bus->caddr_tbl[caddr & 0x0f];
+		if (codec && codec->patch_ops.unsol_event)
+			codec->patch_ops.unsol_event(codec, res);
+	}
+}
+
+/*
+ * initialize unsolicited queue
+ */
+static int init_unsol_queue(struct hda_bus *bus)
+{
+	struct hda_bus_unsolicited *unsol;
+
+	if (bus->unsol) /* already initialized */
+		return 0;
+
+	unsol = kzalloc(sizeof(*unsol), GFP_KERNEL);
+	if (!unsol) {
+		snd_printk(KERN_ERR "hda_codec: "
+			   "can't allocate unsolicited queue\n");
+		return -ENOMEM;
+	}
+	INIT_WORK(&unsol->work, process_unsol_events);
+	unsol->bus = bus;
+	bus->unsol = unsol;
+	return 0;
+}
+
+/*
+ * destructor
+ */
+static void snd_hda_codec_free(struct hda_codec *codec);
+
+static int snd_hda_bus_free(struct hda_bus *bus)
+{
+	struct hda_codec *codec, *n;
+
+	if (!bus)
+		return 0;
+	if (bus->workq)
+		flush_workqueue(bus->workq);
+	if (bus->unsol)
+		kfree(bus->unsol);
+	list_for_each_entry_safe(codec, n, &bus->codec_list, list) {
+		snd_hda_codec_free(codec);
+	}
+	if (bus->ops.private_free)
+		bus->ops.private_free(bus);
+	if (bus->workq)
+		destroy_workqueue(bus->workq);
+	kfree(bus);
+	return 0;
+}
+
+static int snd_hda_bus_dev_free(struct snd_device *device)
+{
+	struct hda_bus *bus = device->device_data;
+	bus->shutdown = 1;
+	return snd_hda_bus_free(bus);
+}
+
+#ifdef CONFIG_SND_HDA_HWDEP
+static int snd_hda_bus_dev_register(struct snd_device *device)
+{
+	struct hda_bus *bus = device->device_data;
+	struct hda_codec *codec;
+	list_for_each_entry(codec, &bus->codec_list, list) {
+		snd_hda_hwdep_add_sysfs(codec);
+		snd_hda_hwdep_add_power_sysfs(codec);
+	}
+	return 0;
+}
+#else
+#define snd_hda_bus_dev_register	NULL
+#endif
+
+/**
+ * snd_hda_bus_new - create a HDA bus
+ * @card: the card entry
+ * @temp: the template for hda_bus information
+ * @busp: the pointer to store the created bus instance
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int /*__devinit*/ snd_hda_bus_new(struct snd_card *card,
+			      const struct hda_bus_template *temp,
+			      struct hda_bus **busp)
+{
+	struct hda_bus *bus;
+	int err;
+	static struct snd_device_ops dev_ops = {
+		.dev_register = snd_hda_bus_dev_register,
+		.dev_free = snd_hda_bus_dev_free,
+	};
+
+	if (snd_BUG_ON(!temp))
+		return -EINVAL;
+	if (snd_BUG_ON(!temp->ops.command || !temp->ops.get_response))
+		return -EINVAL;
+
+	if (busp)
+		*busp = NULL;
+
+	bus = kzalloc(sizeof(*bus), GFP_KERNEL);
+	if (bus == NULL) {
+		snd_printk(KERN_ERR "can't allocate struct hda_bus\n");
+		return -ENOMEM;
+	}
+
+	bus->card = card;
+	bus->private_data = temp->private_data;
+	bus->pci = temp->pci;
+	bus->modelname = temp->modelname;
+	bus->power_save = temp->power_save;
+	bus->ops = temp->ops;
+
+	mutex_init(&bus->cmd_mutex);
+	mutex_init(&bus->prepare_mutex);
+	INIT_LIST_HEAD(&bus->codec_list);
+
+	snprintf(bus->workq_name, sizeof(bus->workq_name),
+		 "hd-audio%d", card->number);
+	bus->workq = create_singlethread_workqueue(bus->workq_name);
+	if (!bus->workq) {
+		snd_printk(KERN_ERR "cannot create workqueue %s\n",
+			   bus->workq_name);
+		kfree(bus);
+		return -ENOMEM;
+	}
+
+	err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops);
+	if (err < 0) {
+		snd_hda_bus_free(bus);
+		return err;
+	}
+	if (busp)
+		*busp = bus;
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_bus_new);
+
+#ifdef CONFIG_SND_HDA_GENERIC
+#define is_generic_config(codec) \
+	(codec->modelname && !strcmp(codec->modelname, "generic"))
+#else
+#define is_generic_config(codec)	0
+#endif
+
+#ifdef MODULE
+#define HDA_MODREQ_MAX_COUNT	2	/* two request_modules()'s */
+#else
+#define HDA_MODREQ_MAX_COUNT	0	/* all presets are statically linked */
+#endif
+
+/*
+ * find a matching codec preset
+ */
+static const struct hda_codec_preset *
+find_codec_preset(struct hda_codec *codec)
+{
+	struct hda_codec_preset_list *tbl;
+	const struct hda_codec_preset *preset;
+	int mod_requested = 0;
+
+	if (is_generic_config(codec))
+		return NULL; /* use the generic parser */
+
+ again:
+	mutex_lock(&preset_mutex);
+	list_for_each_entry(tbl, &hda_preset_tables, list) {
+		if (!try_module_get(tbl->owner)) {
+			snd_printk(KERN_ERR "hda_codec: cannot module_get\n");
+			continue;
+		}
+		for (preset = tbl->preset; preset->id; preset++) {
+			u32 mask = preset->mask;
+			if (preset->afg && preset->afg != codec->afg)
+				continue;
+			if (preset->mfg && preset->mfg != codec->mfg)
+				continue;
+			if (!mask)
+				mask = ~0;
+			if (preset->id == (codec->vendor_id & mask) &&
+			    (!preset->rev ||
+			     preset->rev == codec->revision_id)) {
+				mutex_unlock(&preset_mutex);
+				codec->owner = tbl->owner;
+				return preset;
+			}
+		}
+		module_put(tbl->owner);
+	}
+	mutex_unlock(&preset_mutex);
+
+	if (mod_requested < HDA_MODREQ_MAX_COUNT) {
+		char name[32];
+		if (!mod_requested)
+			snprintf(name, sizeof(name), "snd-hda-codec-id:%08x",
+				 codec->vendor_id);
+		else
+			snprintf(name, sizeof(name), "snd-hda-codec-id:%04x*",
+				 (codec->vendor_id >> 16) & 0xffff);
+		request_module(name);
+		mod_requested++;
+		goto again;
+	}
+	return NULL;
+}
+
+/*
+ * get_codec_name - store the codec name
+ */
+static int get_codec_name(struct hda_codec *codec)
+{
+	const struct hda_vendor_id *c;
+	const char *vendor = NULL;
+	u16 vendor_id = codec->vendor_id >> 16;
+	char tmp[16];
+
+	if (codec->vendor_name)
+		goto get_chip_name;
+
+	for (c = hda_vendor_ids; c->id; c++) {
+		if (c->id == vendor_id) {
+			vendor = c->name;
+			break;
+		}
+	}
+	if (!vendor) {
+		sprintf(tmp, "Generic %04x", vendor_id);
+		vendor = tmp;
+	}
+	codec->vendor_name = kstrdup(vendor, GFP_KERNEL);
+	if (!codec->vendor_name)
+		return -ENOMEM;
+
+ get_chip_name:
+	if (codec->chip_name)
+		return 0;
+
+	if (codec->preset && codec->preset->name)
+		codec->chip_name = kstrdup(codec->preset->name, GFP_KERNEL);
+	else {
+		sprintf(tmp, "ID %x", codec->vendor_id & 0xffff);
+		codec->chip_name = kstrdup(tmp, GFP_KERNEL);
+	}
+	if (!codec->chip_name)
+		return -ENOMEM;
+	return 0;
+}
+
+/*
+ * look for an AFG and MFG nodes
+ */
+static void /*__devinit*/ setup_fg_nodes(struct hda_codec *codec)
+{
+	int i, total_nodes, function_id;
+	hda_nid_t nid;
+
+	total_nodes = snd_hda_get_sub_nodes(codec, AC_NODE_ROOT, &nid);
+	for (i = 0; i < total_nodes; i++, nid++) {
+		function_id = snd_hda_param_read(codec, nid,
+						AC_PAR_FUNCTION_TYPE);
+		switch (function_id & 0xff) {
+		case AC_GRP_AUDIO_FUNCTION:
+			codec->afg = nid;
+			codec->afg_function_id = function_id & 0xff;
+			codec->afg_unsol = (function_id >> 8) & 1;
+			break;
+		case AC_GRP_MODEM_FUNCTION:
+			codec->mfg = nid;
+			codec->mfg_function_id = function_id & 0xff;
+			codec->mfg_unsol = (function_id >> 8) & 1;
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+/*
+ * read widget caps for each widget and store in cache
+ */
+static int read_widget_caps(struct hda_codec *codec, hda_nid_t fg_node)
+{
+	int i;
+	hda_nid_t nid;
+
+	codec->num_nodes = snd_hda_get_sub_nodes(codec, fg_node,
+						 &codec->start_nid);
+	codec->wcaps = kmalloc(codec->num_nodes * 4, GFP_KERNEL);
+	if (!codec->wcaps)
+		return -ENOMEM;
+	nid = codec->start_nid;
+	for (i = 0; i < codec->num_nodes; i++, nid++)
+		codec->wcaps[i] = snd_hda_param_read(codec, nid,
+						     AC_PAR_AUDIO_WIDGET_CAP);
+	return 0;
+}
+
+/* read all pin default configurations and save codec->init_pins */
+static int read_pin_defaults(struct hda_codec *codec)
+{
+	int i;
+	hda_nid_t nid = codec->start_nid;
+
+	for (i = 0; i < codec->num_nodes; i++, nid++) {
+		struct hda_pincfg *pin;
+		unsigned int wcaps = get_wcaps(codec, nid);
+		unsigned int wid_type = get_wcaps_type(wcaps);
+		if (wid_type != AC_WID_PIN)
+			continue;
+		pin = snd_array_new(&codec->init_pins);
+		if (!pin)
+			return -ENOMEM;
+		pin->nid = nid;
+		pin->cfg = snd_hda_codec_read(codec, nid, 0,
+					      AC_VERB_GET_CONFIG_DEFAULT, 0);
+		pin->ctrl = snd_hda_codec_read(codec, nid, 0,
+					       AC_VERB_GET_PIN_WIDGET_CONTROL,
+					       0);
+	}
+	return 0;
+}
+
+/* look up the given pin config list and return the item matching with NID */
+static struct hda_pincfg *look_up_pincfg(struct hda_codec *codec,
+					 struct snd_array *array,
+					 hda_nid_t nid)
+{
+	int i;
+	for (i = 0; i < array->used; i++) {
+		struct hda_pincfg *pin = snd_array_elem(array, i);
+		if (pin->nid == nid)
+			return pin;
+	}
+	return NULL;
+}
+
+/* write a config value for the given NID */
+static void set_pincfg(struct hda_codec *codec, hda_nid_t nid,
+		       unsigned int cfg)
+{
+	int i;
+	for (i = 0; i < 4; i++) {
+		snd_hda_codec_write(codec, nid, 0,
+				    AC_VERB_SET_CONFIG_DEFAULT_BYTES_0 + i,
+				    cfg & 0xff);
+		cfg >>= 8;
+	}
+}
+
+/* set the current pin config value for the given NID.
+ * the value is cached, and read via snd_hda_codec_get_pincfg()
+ */
+int snd_hda_add_pincfg(struct hda_codec *codec, struct snd_array *list,
+		       hda_nid_t nid, unsigned int cfg)
+{
+	struct hda_pincfg *pin;
+	unsigned int oldcfg;
+
+	if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
+		return -EINVAL;
+
+	oldcfg = snd_hda_codec_get_pincfg(codec, nid);
+	pin = look_up_pincfg(codec, list, nid);
+	if (!pin) {
+		pin = snd_array_new(list);
+		if (!pin)
+			return -ENOMEM;
+		pin->nid = nid;
+	}
+	pin->cfg = cfg;
+
+	/* change only when needed; e.g. if the pincfg is already present
+	 * in user_pins[], don't write it
+	 */
+	cfg = snd_hda_codec_get_pincfg(codec, nid);
+	if (oldcfg != cfg)
+		set_pincfg(codec, nid, cfg);
+	return 0;
+}
+
+/**
+ * snd_hda_codec_set_pincfg - Override a pin default configuration
+ * @codec: the HDA codec
+ * @nid: NID to set the pin config
+ * @cfg: the pin default config value
+ *
+ * Override a pin default configuration value in the cache.
+ * This value can be read by snd_hda_codec_get_pincfg() in a higher
+ * priority than the real hardware value.
+ */
+int snd_hda_codec_set_pincfg(struct hda_codec *codec,
+			     hda_nid_t nid, unsigned int cfg)
+{
+	return snd_hda_add_pincfg(codec, &codec->driver_pins, nid, cfg);
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_set_pincfg);
+
+/**
+ * snd_hda_codec_get_pincfg - Obtain a pin-default configuration
+ * @codec: the HDA codec
+ * @nid: NID to get the pin config
+ *
+ * Get the current pin config value of the given pin NID.
+ * If the pincfg value is cached or overridden via sysfs or driver,
+ * returns the cached value.
+ */
+unsigned int snd_hda_codec_get_pincfg(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct hda_pincfg *pin;
+
+#ifdef CONFIG_SND_HDA_HWDEP
+	pin = look_up_pincfg(codec, &codec->user_pins, nid);
+	if (pin)
+		return pin->cfg;
+#endif
+	pin = look_up_pincfg(codec, &codec->driver_pins, nid);
+	if (pin)
+		return pin->cfg;
+	pin = look_up_pincfg(codec, &codec->init_pins, nid);
+	if (pin)
+		return pin->cfg;
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_get_pincfg);
+
+/* restore all current pin configs */
+static void restore_pincfgs(struct hda_codec *codec)
+{
+	int i;
+	for (i = 0; i < codec->init_pins.used; i++) {
+		struct hda_pincfg *pin = snd_array_elem(&codec->init_pins, i);
+		set_pincfg(codec, pin->nid,
+			   snd_hda_codec_get_pincfg(codec, pin->nid));
+	}
+}
+
+/**
+ * snd_hda_shutup_pins - Shut up all pins
+ * @codec: the HDA codec
+ *
+ * Clear all pin controls to shup up before suspend for avoiding click noise.
+ * The controls aren't cached so that they can be resumed properly.
+ */
+void snd_hda_shutup_pins(struct hda_codec *codec)
+{
+	int i;
+	/* don't shut up pins when unloading the driver; otherwise it breaks
+	 * the default pin setup at the next load of the driver
+	 */
+	if (codec->bus->shutdown)
+		return;
+	for (i = 0; i < codec->init_pins.used; i++) {
+		struct hda_pincfg *pin = snd_array_elem(&codec->init_pins, i);
+		/* use read here for syncing after issuing each verb */
+		snd_hda_codec_read(codec, pin->nid, 0,
+				   AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+	}
+	codec->pins_shutup = 1;
+}
+EXPORT_SYMBOL_HDA(snd_hda_shutup_pins);
+
+/* Restore the pin controls cleared previously via snd_hda_shutup_pins() */
+static void restore_shutup_pins(struct hda_codec *codec)
+{
+	int i;
+	if (!codec->pins_shutup)
+		return;
+	if (codec->bus->shutdown)
+		return;
+	for (i = 0; i < codec->init_pins.used; i++) {
+		struct hda_pincfg *pin = snd_array_elem(&codec->init_pins, i);
+		snd_hda_codec_write(codec, pin->nid, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL,
+				    pin->ctrl);
+	}
+	codec->pins_shutup = 0;
+}
+
+static void init_hda_cache(struct hda_cache_rec *cache,
+			   unsigned int record_size);
+static void free_hda_cache(struct hda_cache_rec *cache);
+
+/* restore the initial pin cfgs and release all pincfg lists */
+static void restore_init_pincfgs(struct hda_codec *codec)
+{
+	/* first free driver_pins and user_pins, then call restore_pincfg
+	 * so that only the values in init_pins are restored
+	 */
+	snd_array_free(&codec->driver_pins);
+#ifdef CONFIG_SND_HDA_HWDEP
+	snd_array_free(&codec->user_pins);
+#endif
+	restore_pincfgs(codec);
+	snd_array_free(&codec->init_pins);
+}
+
+/*
+ * audio-converter setup caches
+ */
+struct hda_cvt_setup {
+	hda_nid_t nid;
+	u8 stream_tag;
+	u8 channel_id;
+	u16 format_id;
+	unsigned char active;	/* cvt is currently used */
+	unsigned char dirty;	/* setups should be cleared */
+};
+
+/* get or create a cache entry for the given audio converter NID */
+static struct hda_cvt_setup *
+get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct hda_cvt_setup *p;
+	int i;
+
+	for (i = 0; i < codec->cvt_setups.used; i++) {
+		p = snd_array_elem(&codec->cvt_setups, i);
+		if (p->nid == nid)
+			return p;
+	}
+	p = snd_array_new(&codec->cvt_setups);
+	if (p)
+		p->nid = nid;
+	return p;
+}
+
+/*
+ * codec destructor
+ */
+static void snd_hda_codec_free(struct hda_codec *codec)
+{
+	if (!codec)
+		return;
+	restore_init_pincfgs(codec);
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	cancel_delayed_work(&codec->power_work);
+	flush_workqueue(codec->bus->workq);
+#endif
+	list_del(&codec->list);
+	snd_array_free(&codec->mixers);
+	snd_array_free(&codec->nids);
+	codec->bus->caddr_tbl[codec->addr] = NULL;
+	if (codec->patch_ops.free)
+		codec->patch_ops.free(codec);
+	module_put(codec->owner);
+	free_hda_cache(&codec->amp_cache);
+	free_hda_cache(&codec->cmd_cache);
+	kfree(codec->vendor_name);
+	kfree(codec->chip_name);
+	kfree(codec->modelname);
+	kfree(codec->wcaps);
+	kfree(codec);
+}
+
+static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
+				unsigned int power_state);
+
+/**
+ * snd_hda_codec_new - create a HDA codec
+ * @bus: the bus to assign
+ * @codec_addr: the codec address
+ * @codecp: the pointer to store the generated codec
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus,
+				unsigned int codec_addr,
+				struct hda_codec **codecp)
+{
+	struct hda_codec *codec;
+	char component[31];
+	int err;
+
+	if (snd_BUG_ON(!bus))
+		return -EINVAL;
+	if (snd_BUG_ON(codec_addr > HDA_MAX_CODEC_ADDRESS))
+		return -EINVAL;
+
+	if (bus->caddr_tbl[codec_addr]) {
+		snd_printk(KERN_ERR "hda_codec: "
+			   "address 0x%x is already occupied\n", codec_addr);
+		return -EBUSY;
+	}
+
+	codec = kzalloc(sizeof(*codec), GFP_KERNEL);
+	if (codec == NULL) {
+		snd_printk(KERN_ERR "can't allocate struct hda_codec\n");
+		return -ENOMEM;
+	}
+
+	codec->bus = bus;
+	codec->addr = codec_addr;
+	mutex_init(&codec->spdif_mutex);
+	mutex_init(&codec->control_mutex);
+	init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info));
+	init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head));
+	snd_array_init(&codec->mixers, sizeof(struct hda_nid_item), 32);
+	snd_array_init(&codec->nids, sizeof(struct hda_nid_item), 32);
+	snd_array_init(&codec->init_pins, sizeof(struct hda_pincfg), 16);
+	snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16);
+	snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8);
+	if (codec->bus->modelname) {
+		codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL);
+		if (!codec->modelname) {
+			snd_hda_codec_free(codec);
+			return -ENODEV;
+		}
+	}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	INIT_DELAYED_WORK(&codec->power_work, hda_power_work);
+	/* snd_hda_codec_new() marks the codec as power-up, and leave it as is.
+	 * the caller has to power down appropriatley after initialization
+	 * phase.
+	 */
+	hda_keep_power_on(codec);
+#endif
+
+	list_add_tail(&codec->list, &bus->codec_list);
+	bus->caddr_tbl[codec_addr] = codec;
+
+	codec->vendor_id = snd_hda_param_read(codec, AC_NODE_ROOT,
+					      AC_PAR_VENDOR_ID);
+	if (codec->vendor_id == -1)
+		/* read again, hopefully the access method was corrected
+		 * in the last read...
+		 */
+		codec->vendor_id = snd_hda_param_read(codec, AC_NODE_ROOT,
+						      AC_PAR_VENDOR_ID);
+	codec->subsystem_id = snd_hda_param_read(codec, AC_NODE_ROOT,
+						 AC_PAR_SUBSYSTEM_ID);
+	codec->revision_id = snd_hda_param_read(codec, AC_NODE_ROOT,
+						AC_PAR_REV_ID);
+
+	setup_fg_nodes(codec);
+	if (!codec->afg && !codec->mfg) {
+		snd_printdd("hda_codec: no AFG or MFG node found\n");
+		err = -ENODEV;
+		goto error;
+	}
+
+	err = read_widget_caps(codec, codec->afg ? codec->afg : codec->mfg);
+	if (err < 0) {
+		snd_printk(KERN_ERR "hda_codec: cannot malloc\n");
+		goto error;
+	}
+	err = read_pin_defaults(codec);
+	if (err < 0)
+		goto error;
+
+	if (!codec->subsystem_id) {
+		hda_nid_t nid = codec->afg ? codec->afg : codec->mfg;
+		codec->subsystem_id =
+			snd_hda_codec_read(codec, nid, 0,
+					   AC_VERB_GET_SUBSYSTEM_ID, 0);
+	}
+
+	/* power-up all before initialization */
+	hda_set_power_state(codec,
+			    codec->afg ? codec->afg : codec->mfg,
+			    AC_PWRST_D0);
+
+	snd_hda_codec_proc_new(codec);
+
+	snd_hda_create_hwdep(codec);
+
+	sprintf(component, "HDA:%08x,%08x,%08x", codec->vendor_id,
+		codec->subsystem_id, codec->revision_id);
+	snd_component_add(codec->bus->card, component);
+
+	if (codecp)
+		*codecp = codec;
+	return 0;
+
+ error:
+	snd_hda_codec_free(codec);
+	return err;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_new);
+
+/**
+ * snd_hda_codec_configure - (Re-)configure the HD-audio codec
+ * @codec: the HDA codec
+ *
+ * Start parsing of the given codec tree and (re-)initialize the whole
+ * patch instance.
+ *
+ * Returns 0 if successful or a negative error code.
+ */
+int snd_hda_codec_configure(struct hda_codec *codec)
+{
+	int err;
+
+	codec->preset = find_codec_preset(codec);
+	if (!codec->vendor_name || !codec->chip_name) {
+		err = get_codec_name(codec);
+		if (err < 0)
+			return err;
+	}
+
+	if (is_generic_config(codec)) {
+		err = snd_hda_parse_generic_codec(codec);
+		goto patched;
+	}
+	if (codec->preset && codec->preset->patch) {
+		err = codec->preset->patch(codec);
+		goto patched;
+	}
+
+	/* call the default parser */
+	err = snd_hda_parse_generic_codec(codec);
+	if (err < 0)
+		printk(KERN_ERR "hda-codec: No codec parser is available\n");
+
+ patched:
+	if (!err && codec->patch_ops.unsol_event)
+		err = init_unsol_queue(codec->bus);
+	/* audio codec should override the mixer name */
+	if (!err && (codec->afg || !*codec->bus->card->mixername))
+		snprintf(codec->bus->card->mixername,
+			 sizeof(codec->bus->card->mixername),
+			 "%s %s", codec->vendor_name, codec->chip_name);
+	return err;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_configure);
+
+/**
+ * snd_hda_codec_setup_stream - set up the codec for streaming
+ * @codec: the CODEC to set up
+ * @nid: the NID to set up
+ * @stream_tag: stream tag to pass, it's between 0x1 and 0xf.
+ * @channel_id: channel id to pass, zero based.
+ * @format: stream format.
+ */
+void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
+				u32 stream_tag,
+				int channel_id, int format)
+{
+	struct hda_codec *c;
+	struct hda_cvt_setup *p;
+	unsigned int oldval, newval;
+	int type;
+	int i;
+
+	if (!nid)
+		return;
+
+	snd_printdd("hda_codec_setup_stream: "
+		    "NID=0x%x, stream=0x%x, channel=%d, format=0x%x\n",
+		    nid, stream_tag, channel_id, format);
+	p = get_hda_cvt_setup(codec, nid);
+	if (!p)
+		return;
+	/* update the stream-id if changed */
+	if (p->stream_tag != stream_tag || p->channel_id != channel_id) {
+		oldval = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0);
+		newval = (stream_tag << 4) | channel_id;
+		if (oldval != newval)
+			snd_hda_codec_write(codec, nid, 0,
+					    AC_VERB_SET_CHANNEL_STREAMID,
+					    newval);
+		p->stream_tag = stream_tag;
+		p->channel_id = channel_id;
+	}
+	/* update the format-id if changed */
+	if (p->format_id != format) {
+		oldval = snd_hda_codec_read(codec, nid, 0,
+					    AC_VERB_GET_STREAM_FORMAT, 0);
+		if (oldval != format) {
+			msleep(1);
+			snd_hda_codec_write(codec, nid, 0,
+					    AC_VERB_SET_STREAM_FORMAT,
+					    format);
+		}
+		p->format_id = format;
+	}
+	p->active = 1;
+	p->dirty = 0;
+
+	/* make other inactive cvts with the same stream-tag dirty */
+	type = get_wcaps_type(get_wcaps(codec, nid));
+	list_for_each_entry(c, &codec->bus->codec_list, list) {
+		for (i = 0; i < c->cvt_setups.used; i++) {
+			p = snd_array_elem(&c->cvt_setups, i);
+			if (!p->active && p->stream_tag == stream_tag &&
+			    get_wcaps_type(get_wcaps(codec, p->nid)) == type)
+				p->dirty = 1;
+		}
+	}
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_setup_stream);
+
+static void really_cleanup_stream(struct hda_codec *codec,
+				  struct hda_cvt_setup *q);
+
+/**
+ * __snd_hda_codec_cleanup_stream - clean up the codec for closing
+ * @codec: the CODEC to clean up
+ * @nid: the NID to clean up
+ * @do_now: really clean up the stream instead of clearing the active flag
+ */
+void __snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid,
+				    int do_now)
+{
+	struct hda_cvt_setup *p;
+
+	if (!nid)
+		return;
+
+	if (codec->no_sticky_stream)
+		do_now = 1;
+
+	snd_printdd("hda_codec_cleanup_stream: NID=0x%x\n", nid);
+	p = get_hda_cvt_setup(codec, nid);
+	if (p) {
+		/* here we just clear the active flag when do_now isn't set;
+		 * actual clean-ups will be done later in
+		 * purify_inactive_streams() called from snd_hda_codec_prpapre()
+		 */
+		if (do_now)
+			really_cleanup_stream(codec, p);
+		else
+			p->active = 0;
+	}
+}
+EXPORT_SYMBOL_HDA(__snd_hda_codec_cleanup_stream);
+
+static void really_cleanup_stream(struct hda_codec *codec,
+				  struct hda_cvt_setup *q)
+{
+	hda_nid_t nid = q->nid;
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, 0);
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, 0);
+	memset(q, 0, sizeof(*q));
+	q->nid = nid;
+}
+
+/* clean up the all conflicting obsolete streams */
+static void purify_inactive_streams(struct hda_codec *codec)
+{
+	struct hda_codec *c;
+	int i;
+
+	list_for_each_entry(c, &codec->bus->codec_list, list) {
+		for (i = 0; i < c->cvt_setups.used; i++) {
+			struct hda_cvt_setup *p;
+			p = snd_array_elem(&c->cvt_setups, i);
+			if (p->dirty)
+				really_cleanup_stream(c, p);
+		}
+	}
+}
+
+/* clean up all streams; called from suspend */
+static void hda_cleanup_all_streams(struct hda_codec *codec)
+{
+	int i;
+
+	for (i = 0; i < codec->cvt_setups.used; i++) {
+		struct hda_cvt_setup *p = snd_array_elem(&codec->cvt_setups, i);
+		if (p->stream_tag)
+			really_cleanup_stream(codec, p);
+	}
+}
+
+/*
+ * amp access functions
+ */
+
+/* FIXME: more better hash key? */
+#define HDA_HASH_KEY(nid, dir, idx) (u32)((nid) + ((idx) << 16) + ((dir) << 24))
+#define HDA_HASH_PINCAP_KEY(nid) (u32)((nid) + (0x02 << 24))
+#define HDA_HASH_PARPCM_KEY(nid) (u32)((nid) + (0x03 << 24))
+#define HDA_HASH_PARSTR_KEY(nid) (u32)((nid) + (0x04 << 24))
+#define INFO_AMP_CAPS	(1<<0)
+#define INFO_AMP_VOL(ch)	(1 << (1 + (ch)))
+
+/* initialize the hash table */
+static void /*__devinit*/ init_hda_cache(struct hda_cache_rec *cache,
+				     unsigned int record_size)
+{
+	memset(cache, 0, sizeof(*cache));
+	memset(cache->hash, 0xff, sizeof(cache->hash));
+	snd_array_init(&cache->buf, record_size, 64);
+}
+
+static void free_hda_cache(struct hda_cache_rec *cache)
+{
+	snd_array_free(&cache->buf);
+}
+
+/* query the hash.  allocate an entry if not found. */
+static struct hda_cache_head  *get_hash(struct hda_cache_rec *cache, u32 key)
+{
+	u16 idx = key % (u16)ARRAY_SIZE(cache->hash);
+	u16 cur = cache->hash[idx];
+	struct hda_cache_head *info;
+
+	while (cur != 0xffff) {
+		info = snd_array_elem(&cache->buf, cur);
+		if (info->key == key)
+			return info;
+		cur = info->next;
+	}
+	return NULL;
+}
+
+/* query the hash.  allocate an entry if not found. */
+static struct hda_cache_head  *get_alloc_hash(struct hda_cache_rec *cache,
+					      u32 key)
+{
+	struct hda_cache_head *info = get_hash(cache, key);
+	if (!info) {
+		u16 idx, cur;
+		/* add a new hash entry */
+		info = snd_array_new(&cache->buf);
+		if (!info)
+			return NULL;
+		cur = snd_array_index(&cache->buf, info);
+		info->key = key;
+		info->val = 0;
+		idx = key % (u16)ARRAY_SIZE(cache->hash);
+		info->next = cache->hash[idx];
+		cache->hash[idx] = cur;
+	}
+	return info;
+}
+
+/* query and allocate an amp hash entry */
+static inline struct hda_amp_info *
+get_alloc_amp_hash(struct hda_codec *codec, u32 key)
+{
+	return (struct hda_amp_info *)get_alloc_hash(&codec->amp_cache, key);
+}
+
+/**
+ * query_amp_caps - query AMP capabilities
+ * @codec: the HD-auio codec
+ * @nid: the NID to query
+ * @direction: either #HDA_INPUT or #HDA_OUTPUT
+ *
+ * Query AMP capabilities for the given widget and direction.
+ * Returns the obtained capability bits.
+ *
+ * When cap bits have been already read, this doesn't read again but
+ * returns the cached value.
+ */
+u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
+{
+	struct hda_amp_info *info;
+
+	info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, 0));
+	if (!info)
+		return 0;
+	if (!(info->head.val & INFO_AMP_CAPS)) {
+		if (!(get_wcaps(codec, nid) & AC_WCAP_AMP_OVRD))
+			nid = codec->afg;
+		info->amp_caps = snd_hda_param_read(codec, nid,
+						    direction == HDA_OUTPUT ?
+						    AC_PAR_AMP_OUT_CAP :
+						    AC_PAR_AMP_IN_CAP);
+		if (info->amp_caps)
+			info->head.val |= INFO_AMP_CAPS;
+	}
+	return info->amp_caps;
+}
+EXPORT_SYMBOL_HDA(query_amp_caps);
+
+/**
+ * snd_hda_override_amp_caps - Override the AMP capabilities
+ * @codec: the CODEC to clean up
+ * @nid: the NID to clean up
+ * @direction: either #HDA_INPUT or #HDA_OUTPUT
+ * @caps: the capability bits to set
+ *
+ * Override the cached AMP caps bits value by the given one.
+ * This function is useful if the driver needs to adjust the AMP ranges,
+ * e.g. limit to 0dB, etc.
+ *
+ * Returns zero if successful or a negative error code.
+ */
+int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
+			      unsigned int caps)
+{
+	struct hda_amp_info *info;
+
+	info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, dir, 0));
+	if (!info)
+		return -EINVAL;
+	info->amp_caps = caps;
+	info->head.val |= INFO_AMP_CAPS;
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_override_amp_caps);
+
+static unsigned int
+query_caps_hash(struct hda_codec *codec, hda_nid_t nid, u32 key,
+		unsigned int (*func)(struct hda_codec *, hda_nid_t))
+{
+	struct hda_amp_info *info;
+
+	info = get_alloc_amp_hash(codec, key);
+	if (!info)
+		return 0;
+	if (!info->head.val) {
+		info->head.val |= INFO_AMP_CAPS;
+		info->amp_caps = func(codec, nid);
+	}
+	return info->amp_caps;
+}
+
+static unsigned int read_pin_cap(struct hda_codec *codec, hda_nid_t nid)
+{
+	return snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
+}
+
+/**
+ * snd_hda_query_pin_caps - Query PIN capabilities
+ * @codec: the HD-auio codec
+ * @nid: the NID to query
+ *
+ * Query PIN capabilities for the given widget.
+ * Returns the obtained capability bits.
+ *
+ * When cap bits have been already read, this doesn't read again but
+ * returns the cached value.
+ */
+u32 snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid)
+{
+	return query_caps_hash(codec, nid, HDA_HASH_PINCAP_KEY(nid),
+			       read_pin_cap);
+}
+EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps);
+
+/**
+ * snd_hda_pin_sense - execute pin sense measurement
+ * @codec: the CODEC to sense
+ * @nid: the pin NID to sense
+ *
+ * Execute necessary pin sense measurement and return its Presence Detect,
+ * Impedance, ELD Valid etc. status bits.
+ */
+u32 snd_hda_pin_sense(struct hda_codec *codec, hda_nid_t nid)
+{
+	u32 pincap;
+
+	if (!codec->no_trigger_sense) {
+		pincap = snd_hda_query_pin_caps(codec, nid);
+		if (pincap & AC_PINCAP_TRIG_REQ) /* need trigger? */
+			snd_hda_codec_read(codec, nid, 0,
+					AC_VERB_SET_PIN_SENSE, 0);
+	}
+	return snd_hda_codec_read(codec, nid, 0,
+				  AC_VERB_GET_PIN_SENSE, 0);
+}
+EXPORT_SYMBOL_HDA(snd_hda_pin_sense);
+
+/**
+ * snd_hda_jack_detect - query pin Presence Detect status
+ * @codec: the CODEC to sense
+ * @nid: the pin NID to sense
+ *
+ * Query and return the pin's Presence Detect status.
+ */
+int snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid)
+{
+	u32 sense = snd_hda_pin_sense(codec, nid);
+	return !!(sense & AC_PINSENSE_PRESENCE);
+}
+EXPORT_SYMBOL_HDA(snd_hda_jack_detect);
+
+/*
+ * read the current volume to info
+ * if the cache exists, read the cache value.
+ */
+static unsigned int get_vol_mute(struct hda_codec *codec,
+				 struct hda_amp_info *info, hda_nid_t nid,
+				 int ch, int direction, int index)
+{
+	u32 val, parm;
+
+	if (info->head.val & INFO_AMP_VOL(ch))
+		return info->vol[ch];
+
+	parm = ch ? AC_AMP_GET_RIGHT : AC_AMP_GET_LEFT;
+	parm |= direction == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
+	parm |= index;
+	val = snd_hda_codec_read(codec, nid, 0,
+				 AC_VERB_GET_AMP_GAIN_MUTE, parm);
+	info->vol[ch] = val & 0xff;
+	info->head.val |= INFO_AMP_VOL(ch);
+	return info->vol[ch];
+}
+
+/*
+ * write the current volume in info to the h/w and update the cache
+ */
+static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
+			 hda_nid_t nid, int ch, int direction, int index,
+			 int val)
+{
+	u32 parm;
+
+	parm = ch ? AC_AMP_SET_RIGHT : AC_AMP_SET_LEFT;
+	parm |= direction == HDA_OUTPUT ? AC_AMP_SET_OUTPUT : AC_AMP_SET_INPUT;
+	parm |= index << AC_AMP_SET_INDEX_SHIFT;
+	parm |= val;
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, parm);
+	info->vol[ch] = val;
+}
+
+/**
+ * snd_hda_codec_amp_read - Read AMP value
+ * @codec: HD-audio codec
+ * @nid: NID to read the AMP value
+ * @ch: channel (left=0 or right=1)
+ * @direction: #HDA_INPUT or #HDA_OUTPUT
+ * @index: the index value (only for input direction)
+ *
+ * Read AMP value.  The volume is between 0 to 0x7f, 0x80 = mute bit.
+ */
+int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch,
+			   int direction, int index)
+{
+	struct hda_amp_info *info;
+	info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, index));
+	if (!info)
+		return 0;
+	return get_vol_mute(codec, info, nid, ch, direction, index);
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_amp_read);
+
+/**
+ * snd_hda_codec_amp_update - update the AMP value
+ * @codec: HD-audio codec
+ * @nid: NID to read the AMP value
+ * @ch: channel (left=0 or right=1)
+ * @direction: #HDA_INPUT or #HDA_OUTPUT
+ * @idx: the index value (only for input direction)
+ * @mask: bit mask to set
+ * @val: the bits value to set
+ *
+ * Update the AMP value with a bit mask.
+ * Returns 0 if the value is unchanged, 1 if changed.
+ */
+int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch,
+			     int direction, int idx, int mask, int val)
+{
+	struct hda_amp_info *info;
+
+	info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, idx));
+	if (!info)
+		return 0;
+	if (snd_BUG_ON(mask & ~0xff))
+		mask &= 0xff;
+	val &= mask;
+	val |= get_vol_mute(codec, info, nid, ch, direction, idx) & ~mask;
+	if (info->vol[ch] == val)
+		return 0;
+	put_vol_mute(codec, info, nid, ch, direction, idx, val);
+	return 1;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_amp_update);
+
+/**
+ * snd_hda_codec_amp_stereo - update the AMP stereo values
+ * @codec: HD-audio codec
+ * @nid: NID to read the AMP value
+ * @direction: #HDA_INPUT or #HDA_OUTPUT
+ * @idx: the index value (only for input direction)
+ * @mask: bit mask to set
+ * @val: the bits value to set
+ *
+ * Update the AMP values like snd_hda_codec_amp_update(), but for a
+ * stereo widget with the same mask and value.
+ */
+int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
+			     int direction, int idx, int mask, int val)
+{
+	int ch, ret = 0;
+
+	if (snd_BUG_ON(mask & ~0xff))
+		mask &= 0xff;
+	for (ch = 0; ch < 2; ch++)
+		ret |= snd_hda_codec_amp_update(codec, nid, ch, direction,
+						idx, mask, val);
+	return ret;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_amp_stereo);
+
+#ifdef SND_HDA_NEEDS_RESUME
+/**
+ * snd_hda_codec_resume_amp - Resume all AMP commands from the cache
+ * @codec: HD-audio codec
+ *
+ * Resume the all amp commands from the cache.
+ */
+void snd_hda_codec_resume_amp(struct hda_codec *codec)
+{
+	struct hda_amp_info *buffer = codec->amp_cache.buf.list;
+	int i;
+
+	for (i = 0; i < codec->amp_cache.buf.used; i++, buffer++) {
+		u32 key = buffer->head.key;
+		hda_nid_t nid;
+		unsigned int idx, dir, ch;
+		if (!key)
+			continue;
+		nid = key & 0xff;
+		idx = (key >> 16) & 0xff;
+		dir = (key >> 24) & 0xff;
+		for (ch = 0; ch < 2; ch++) {
+			if (!(buffer->head.val & INFO_AMP_VOL(ch)))
+				continue;
+			put_vol_mute(codec, buffer, nid, ch, dir, idx,
+				     buffer->vol[ch]);
+		}
+	}
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_resume_amp);
+#endif /* SND_HDA_NEEDS_RESUME */
+
+static u32 get_amp_max_value(struct hda_codec *codec, hda_nid_t nid, int dir,
+			     unsigned int ofs)
+{
+	u32 caps = query_amp_caps(codec, nid, dir);
+	/* get num steps */
+	caps = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+	if (ofs < caps)
+		caps -= ofs;
+	return caps;
+}
+
+/**
+ * snd_hda_mixer_amp_volume_info - Info callback for a standard AMP mixer
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	u16 nid = get_amp_nid(kcontrol);
+	u8 chs = get_amp_channels(kcontrol);
+	int dir = get_amp_direction(kcontrol);
+	unsigned int ofs = get_amp_offset(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = chs == 3 ? 2 : 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = get_amp_max_value(codec, nid, dir, ofs);
+	if (!uinfo->value.integer.max) {
+		printk(KERN_WARNING "hda_codec: "
+		       "num_steps = 0 for NID=0x%x (ctl = %s)\n", nid,
+		       kcontrol->id.name);
+		return -EINVAL;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_volume_info);
+
+
+static inline unsigned int
+read_amp_value(struct hda_codec *codec, hda_nid_t nid,
+	       int ch, int dir, int idx, unsigned int ofs)
+{
+	unsigned int val;
+	val = snd_hda_codec_amp_read(codec, nid, ch, dir, idx);
+	val &= HDA_AMP_VOLMASK;
+	if (val >= ofs)
+		val -= ofs;
+	else
+		val = 0;
+	return val;
+}
+
+static inline int
+update_amp_value(struct hda_codec *codec, hda_nid_t nid,
+		 int ch, int dir, int idx, unsigned int ofs,
+		 unsigned int val)
+{
+	unsigned int maxval;
+
+	if (val > 0)
+		val += ofs;
+	/* ofs = 0: raw max value */
+	maxval = get_amp_max_value(codec, nid, dir, 0);
+	if (val > maxval)
+		val = maxval;
+	return snd_hda_codec_amp_update(codec, nid, ch, dir, idx,
+					HDA_AMP_VOLMASK, val);
+}
+
+/**
+ * snd_hda_mixer_amp_volume_get - Get callback for a standard AMP mixer volume
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = get_amp_nid(kcontrol);
+	int chs = get_amp_channels(kcontrol);
+	int dir = get_amp_direction(kcontrol);
+	int idx = get_amp_index(kcontrol);
+	unsigned int ofs = get_amp_offset(kcontrol);
+	long *valp = ucontrol->value.integer.value;
+
+	if (chs & 1)
+		*valp++ = read_amp_value(codec, nid, 0, dir, idx, ofs);
+	if (chs & 2)
+		*valp = read_amp_value(codec, nid, 1, dir, idx, ofs);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_volume_get);
+
+/**
+ * snd_hda_mixer_amp_volume_put - Put callback for a standard AMP mixer volume
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = get_amp_nid(kcontrol);
+	int chs = get_amp_channels(kcontrol);
+	int dir = get_amp_direction(kcontrol);
+	int idx = get_amp_index(kcontrol);
+	unsigned int ofs = get_amp_offset(kcontrol);
+	long *valp = ucontrol->value.integer.value;
+	int change = 0;
+
+	snd_hda_power_up(codec);
+	if (chs & 1) {
+		change = update_amp_value(codec, nid, 0, dir, idx, ofs, *valp);
+		valp++;
+	}
+	if (chs & 2)
+		change |= update_amp_value(codec, nid, 1, dir, idx, ofs, *valp);
+	snd_hda_power_down(codec);
+	return change;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_volume_put);
+
+/**
+ * snd_hda_mixer_amp_volume_put - TLV callback for a standard AMP mixer volume
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+			  unsigned int size, unsigned int __user *_tlv)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = get_amp_nid(kcontrol);
+	int dir = get_amp_direction(kcontrol);
+	unsigned int ofs = get_amp_offset(kcontrol);
+	bool min_mute = get_amp_min_mute(kcontrol);
+	u32 caps, val1, val2;
+
+	if (size < 4 * sizeof(unsigned int))
+		return -ENOMEM;
+	caps = query_amp_caps(codec, nid, dir);
+	val2 = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT;
+	val2 = (val2 + 1) * 25;
+	val1 = -((caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT);
+	val1 += ofs;
+	val1 = ((int)val1) * ((int)val2);
+	if (min_mute)
+		val2 |= TLV_DB_SCALE_MUTE;
+	if (put_user(SNDRV_CTL_TLVT_DB_SCALE, _tlv))
+		return -EFAULT;
+	if (put_user(2 * sizeof(unsigned int), _tlv + 1))
+		return -EFAULT;
+	if (put_user(val1, _tlv + 2))
+		return -EFAULT;
+	if (put_user(val2, _tlv + 3))
+		return -EFAULT;
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_tlv);
+
+/**
+ * snd_hda_set_vmaster_tlv - Set TLV for a virtual master control
+ * @codec: HD-audio codec
+ * @nid: NID of a reference widget
+ * @dir: #HDA_INPUT or #HDA_OUTPUT
+ * @tlv: TLV data to be stored, at least 4 elements
+ *
+ * Set (static) TLV data for a virtual master volume using the AMP caps
+ * obtained from the reference NID.
+ * The volume range is recalculated as if the max volume is 0dB.
+ */
+void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
+			     unsigned int *tlv)
+{
+	u32 caps;
+	int nums, step;
+
+	caps = query_amp_caps(codec, nid, dir);
+	nums = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+	step = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT;
+	step = (step + 1) * 25;
+	tlv[0] = SNDRV_CTL_TLVT_DB_SCALE;
+	tlv[1] = 2 * sizeof(unsigned int);
+	tlv[2] = -nums * step;
+	tlv[3] = step;
+}
+EXPORT_SYMBOL_HDA(snd_hda_set_vmaster_tlv);
+
+/* find a mixer control element with the given name */
+static struct snd_kcontrol *
+_snd_hda_find_mixer_ctl(struct hda_codec *codec,
+			const char *name, int idx)
+{
+	struct snd_ctl_elem_id id;
+	memset(&id, 0, sizeof(id));
+	id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	id.index = idx;
+	if (snd_BUG_ON(strlen(name) >= sizeof(id.name)))
+		return NULL;
+	strcpy(id.name, name);
+	return snd_ctl_find_id(codec->bus->card, &id);
+}
+
+/**
+ * snd_hda_find_mixer_ctl - Find a mixer control element with the given name
+ * @codec: HD-audio codec
+ * @name: ctl id name string
+ *
+ * Get the control element with the given id string and IFACE_MIXER.
+ */
+struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
+					    const char *name)
+{
+	return _snd_hda_find_mixer_ctl(codec, name, 0);
+}
+EXPORT_SYMBOL_HDA(snd_hda_find_mixer_ctl);
+
+static int find_empty_mixer_ctl_idx(struct hda_codec *codec, const char *name)
+{
+	int idx;
+	for (idx = 0; idx < 16; idx++) { /* 16 ctlrs should be large enough */
+		if (!_snd_hda_find_mixer_ctl(codec, name, idx))
+			return idx;
+	}
+	return -EBUSY;
+}
+
+/**
+ * snd_hda_ctl_add - Add a control element and assign to the codec
+ * @codec: HD-audio codec
+ * @nid: corresponding NID (optional)
+ * @kctl: the control element to assign
+ *
+ * Add the given control element to an array inside the codec instance.
+ * All control elements belonging to a codec are supposed to be added
+ * by this function so that a proper clean-up works at the free or
+ * reconfiguration time.
+ *
+ * If non-zero @nid is passed, the NID is assigned to the control element.
+ * The assignment is shown in the codec proc file.
+ *
+ * snd_hda_ctl_add() checks the control subdev id field whether
+ * #HDA_SUBDEV_NID_FLAG bit is set.  If set (and @nid is zero), the lower
+ * bits value is taken as the NID to assign. The #HDA_NID_ITEM_AMP bit
+ * specifies if kctl->private_value is a HDA amplifier value.
+ */
+int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid,
+		    struct snd_kcontrol *kctl)
+{
+	int err;
+	unsigned short flags = 0;
+	struct hda_nid_item *item;
+
+	if (kctl->id.subdevice & HDA_SUBDEV_AMP_FLAG) {
+		flags |= HDA_NID_ITEM_AMP;
+		if (nid == 0)
+			nid = get_amp_nid_(kctl->private_value);
+	}
+	if ((kctl->id.subdevice & HDA_SUBDEV_NID_FLAG) != 0 && nid == 0)
+		nid = kctl->id.subdevice & 0xffff;
+	if (kctl->id.subdevice & (HDA_SUBDEV_NID_FLAG|HDA_SUBDEV_AMP_FLAG))
+		kctl->id.subdevice = 0;
+	err = snd_ctl_add(codec->bus->card, kctl);
+	if (err < 0)
+		return err;
+	item = snd_array_new(&codec->mixers);
+	if (!item)
+		return -ENOMEM;
+	item->kctl = kctl;
+	item->nid = nid;
+	item->flags = flags;
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_ctl_add);
+
+/**
+ * snd_hda_add_nid - Assign a NID to a control element
+ * @codec: HD-audio codec
+ * @nid: corresponding NID (optional)
+ * @kctl: the control element to assign
+ * @index: index to kctl
+ *
+ * Add the given control element to an array inside the codec instance.
+ * This function is used when #snd_hda_ctl_add cannot be used for 1:1
+ * NID:KCTL mapping - for example "Capture Source" selector.
+ */
+int snd_hda_add_nid(struct hda_codec *codec, struct snd_kcontrol *kctl,
+		    unsigned int index, hda_nid_t nid)
+{
+	struct hda_nid_item *item;
+
+	if (nid > 0) {
+		item = snd_array_new(&codec->nids);
+		if (!item)
+			return -ENOMEM;
+		item->kctl = kctl;
+		item->index = index;
+		item->nid = nid;
+		return 0;
+	}
+	printk(KERN_ERR "hda-codec: no NID for mapping control %s:%d:%d\n",
+	       kctl->id.name, kctl->id.index, index);
+	return -EINVAL;
+}
+EXPORT_SYMBOL_HDA(snd_hda_add_nid);
+
+/**
+ * snd_hda_ctls_clear - Clear all controls assigned to the given codec
+ * @codec: HD-audio codec
+ */
+void snd_hda_ctls_clear(struct hda_codec *codec)
+{
+	int i;
+	struct hda_nid_item *items = codec->mixers.list;
+	for (i = 0; i < codec->mixers.used; i++)
+		snd_ctl_remove(codec->bus->card, items[i].kctl);
+	snd_array_free(&codec->mixers);
+	snd_array_free(&codec->nids);
+}
+
+/* pseudo device locking
+ * toggle card->shutdown to allow/disallow the device access (as a hack)
+ */
+static int hda_lock_devices(struct snd_card *card)
+{
+	spin_lock(&card->files_lock);
+	if (card->shutdown) {
+		spin_unlock(&card->files_lock);
+		return -EINVAL;
+	}
+	card->shutdown = 1;
+	spin_unlock(&card->files_lock);
+	return 0;
+}
+
+static void hda_unlock_devices(struct snd_card *card)
+{
+	spin_lock(&card->files_lock);
+	card->shutdown = 0;
+	spin_unlock(&card->files_lock);
+}
+
+/**
+ * snd_hda_codec_reset - Clear all objects assigned to the codec
+ * @codec: HD-audio codec
+ *
+ * This frees the all PCM and control elements assigned to the codec, and
+ * clears the caches and restores the pin default configurations.
+ *
+ * When a device is being used, it returns -EBSY.  If successfully freed,
+ * returns zero.
+ */
+int snd_hda_codec_reset(struct hda_codec *codec)
+{
+	struct snd_card *card = codec->bus->card;
+	int i, pcm;
+
+	if (hda_lock_devices(card) < 0)
+		return -EBUSY;
+	/* check whether the codec isn't used by any mixer or PCM streams */
+	if (!list_empty(&card->ctl_files)) {
+		hda_unlock_devices(card);
+		return -EBUSY;
+	}
+	for (pcm = 0; pcm < codec->num_pcms; pcm++) {
+		struct hda_pcm *cpcm = &codec->pcm_info[pcm];
+		if (!cpcm->pcm)
+			continue;
+		if (cpcm->pcm->streams[0].substream_opened ||
+		    cpcm->pcm->streams[1].substream_opened) {
+			hda_unlock_devices(card);
+			return -EBUSY;
+		}
+	}
+
+	/* OK, let it free */
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	cancel_delayed_work(&codec->power_work);
+	flush_workqueue(codec->bus->workq);
+#endif
+	snd_hda_ctls_clear(codec);
+	/* relase PCMs */
+	for (i = 0; i < codec->num_pcms; i++) {
+		if (codec->pcm_info[i].pcm) {
+			snd_device_free(card, codec->pcm_info[i].pcm);
+			clear_bit(codec->pcm_info[i].device,
+				  codec->bus->pcm_dev_bits);
+		}
+	}
+	if (codec->patch_ops.free)
+		codec->patch_ops.free(codec);
+	codec->proc_widget_hook = NULL;
+	codec->spec = NULL;
+	free_hda_cache(&codec->amp_cache);
+	free_hda_cache(&codec->cmd_cache);
+	init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info));
+	init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head));
+	/* free only driver_pins so that init_pins + user_pins are restored */
+	snd_array_free(&codec->driver_pins);
+	restore_pincfgs(codec);
+	codec->num_pcms = 0;
+	codec->pcm_info = NULL;
+	codec->preset = NULL;
+	memset(&codec->patch_ops, 0, sizeof(codec->patch_ops));
+	codec->slave_dig_outs = NULL;
+	codec->spdif_status_reset = 0;
+	module_put(codec->owner);
+	codec->owner = NULL;
+
+	/* allow device access again */
+	hda_unlock_devices(card);
+	return 0;
+}
+
+/**
+ * snd_hda_add_vmaster - create a virtual master control and add slaves
+ * @codec: HD-audio codec
+ * @name: vmaster control name
+ * @tlv: TLV data (optional)
+ * @slaves: slave control names (optional)
+ *
+ * Create a virtual master control with the given name.  The TLV data
+ * must be either NULL or a valid data.
+ *
+ * @slaves is a NULL-terminated array of strings, each of which is a
+ * slave control name.  All controls with these names are assigned to
+ * the new virtual master control.
+ *
+ * This function returns zero if successful or a negative error code.
+ */
+int snd_hda_add_vmaster(struct hda_codec *codec, char *name,
+			unsigned int *tlv, const char **slaves)
+{
+	struct snd_kcontrol *kctl;
+	const char **s;
+	int err;
+
+	for (s = slaves; *s && !snd_hda_find_mixer_ctl(codec, *s); s++)
+		;
+	if (!*s) {
+		snd_printdd("No slave found for %s\n", name);
+		return 0;
+	}
+	kctl = snd_ctl_make_virtual_master(name, tlv);
+	if (!kctl)
+		return -ENOMEM;
+	err = snd_hda_ctl_add(codec, 0, kctl);
+	if (err < 0)
+		return err;
+
+	for (s = slaves; *s; s++) {
+		struct snd_kcontrol *sctl;
+		int i = 0;
+		for (;;) {
+			sctl = _snd_hda_find_mixer_ctl(codec, *s, i);
+			if (!sctl) {
+				if (!i)
+					snd_printdd("Cannot find slave %s, "
+						    "skipped\n", *s);
+				break;
+			}
+			err = snd_ctl_add_slave(kctl, sctl);
+			if (err < 0)
+				return err;
+			i++;
+		}
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_add_vmaster);
+
+/**
+ * snd_hda_mixer_amp_switch_info - Info callback for a standard AMP mixer switch
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	int chs = get_amp_channels(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = chs == 3 ? 2 : 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_switch_info);
+
+/**
+ * snd_hda_mixer_amp_switch_get - Get callback for a standard AMP mixer switch
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = get_amp_nid(kcontrol);
+	int chs = get_amp_channels(kcontrol);
+	int dir = get_amp_direction(kcontrol);
+	int idx = get_amp_index(kcontrol);
+	long *valp = ucontrol->value.integer.value;
+
+	if (chs & 1)
+		*valp++ = (snd_hda_codec_amp_read(codec, nid, 0, dir, idx) &
+			   HDA_AMP_MUTE) ? 0 : 1;
+	if (chs & 2)
+		*valp = (snd_hda_codec_amp_read(codec, nid, 1, dir, idx) &
+			 HDA_AMP_MUTE) ? 0 : 1;
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_switch_get);
+
+/**
+ * snd_hda_mixer_amp_switch_put - Put callback for a standard AMP mixer switch
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_COMPOSE_AMP_VAL*() or related macros.
+ */
+int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = get_amp_nid(kcontrol);
+	int chs = get_amp_channels(kcontrol);
+	int dir = get_amp_direction(kcontrol);
+	int idx = get_amp_index(kcontrol);
+	long *valp = ucontrol->value.integer.value;
+	int change = 0;
+
+	snd_hda_power_up(codec);
+	if (chs & 1) {
+		change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
+						  HDA_AMP_MUTE,
+						  *valp ? 0 : HDA_AMP_MUTE);
+		valp++;
+	}
+	if (chs & 2)
+		change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx,
+						   HDA_AMP_MUTE,
+						   *valp ? 0 : HDA_AMP_MUTE);
+	hda_call_check_power_status(codec, nid);
+	snd_hda_power_down(codec);
+	return change;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_switch_put);
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+/**
+ * snd_hda_mixer_amp_switch_put_beep - Put callback for a beep AMP switch
+ *
+ * This function calls snd_hda_enable_beep_device(), which behaves differently
+ * depending on beep_mode option.
+ */
+int snd_hda_mixer_amp_switch_put_beep(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	long *valp = ucontrol->value.integer.value;
+
+	snd_hda_enable_beep_device(codec, *valp);
+	return snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_switch_put_beep);
+#endif /* CONFIG_SND_HDA_INPUT_BEEP */
+
+/*
+ * bound volume controls
+ *
+ * bind multiple volumes (# indices, from 0)
+ */
+
+#define AMP_VAL_IDX_SHIFT	19
+#define AMP_VAL_IDX_MASK	(0x0f<<19)
+
+/**
+ * snd_hda_mixer_bind_switch_get - Get callback for a bound volume control
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_BIND_MUTE*() macros.
+ */
+int snd_hda_mixer_bind_switch_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned long pval;
+	int err;
+
+	mutex_lock(&codec->control_mutex);
+	pval = kcontrol->private_value;
+	kcontrol->private_value = pval & ~AMP_VAL_IDX_MASK; /* index 0 */
+	err = snd_hda_mixer_amp_switch_get(kcontrol, ucontrol);
+	kcontrol->private_value = pval;
+	mutex_unlock(&codec->control_mutex);
+	return err;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_switch_get);
+
+/**
+ * snd_hda_mixer_bind_switch_put - Put callback for a bound volume control
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_BIND_MUTE*() macros.
+ */
+int snd_hda_mixer_bind_switch_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned long pval;
+	int i, indices, err = 0, change = 0;
+
+	mutex_lock(&codec->control_mutex);
+	pval = kcontrol->private_value;
+	indices = (pval & AMP_VAL_IDX_MASK) >> AMP_VAL_IDX_SHIFT;
+	for (i = 0; i < indices; i++) {
+		kcontrol->private_value = (pval & ~AMP_VAL_IDX_MASK) |
+			(i << AMP_VAL_IDX_SHIFT);
+		err = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+		if (err < 0)
+			break;
+		change |= err;
+	}
+	kcontrol->private_value = pval;
+	mutex_unlock(&codec->control_mutex);
+	return err < 0 ? err : change;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_switch_put);
+
+/**
+ * snd_hda_mixer_bind_ctls_info - Info callback for a generic bound control
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_BIND_VOL() or HDA_BIND_SW() macros.
+ */
+int snd_hda_mixer_bind_ctls_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct hda_bind_ctls *c;
+	int err;
+
+	mutex_lock(&codec->control_mutex);
+	c = (struct hda_bind_ctls *)kcontrol->private_value;
+	kcontrol->private_value = *c->values;
+	err = c->ops->info(kcontrol, uinfo);
+	kcontrol->private_value = (long)c;
+	mutex_unlock(&codec->control_mutex);
+	return err;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_ctls_info);
+
+/**
+ * snd_hda_mixer_bind_ctls_get - Get callback for a generic bound control
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_BIND_VOL() or HDA_BIND_SW() macros.
+ */
+int snd_hda_mixer_bind_ctls_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct hda_bind_ctls *c;
+	int err;
+
+	mutex_lock(&codec->control_mutex);
+	c = (struct hda_bind_ctls *)kcontrol->private_value;
+	kcontrol->private_value = *c->values;
+	err = c->ops->get(kcontrol, ucontrol);
+	kcontrol->private_value = (long)c;
+	mutex_unlock(&codec->control_mutex);
+	return err;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_ctls_get);
+
+/**
+ * snd_hda_mixer_bind_ctls_put - Put callback for a generic bound control
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_BIND_VOL() or HDA_BIND_SW() macros.
+ */
+int snd_hda_mixer_bind_ctls_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct hda_bind_ctls *c;
+	unsigned long *vals;
+	int err = 0, change = 0;
+
+	mutex_lock(&codec->control_mutex);
+	c = (struct hda_bind_ctls *)kcontrol->private_value;
+	for (vals = c->values; *vals; vals++) {
+		kcontrol->private_value = *vals;
+		err = c->ops->put(kcontrol, ucontrol);
+		if (err < 0)
+			break;
+		change |= err;
+	}
+	kcontrol->private_value = (long)c;
+	mutex_unlock(&codec->control_mutex);
+	return err < 0 ? err : change;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_ctls_put);
+
+/**
+ * snd_hda_mixer_bind_tlv - TLV callback for a generic bound control
+ *
+ * The control element is supposed to have the private_value field
+ * set up via HDA_BIND_VOL() macro.
+ */
+int snd_hda_mixer_bind_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+			   unsigned int size, unsigned int __user *tlv)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct hda_bind_ctls *c;
+	int err;
+
+	mutex_lock(&codec->control_mutex);
+	c = (struct hda_bind_ctls *)kcontrol->private_value;
+	kcontrol->private_value = *c->values;
+	err = c->ops->tlv(kcontrol, op_flag, size, tlv);
+	kcontrol->private_value = (long)c;
+	mutex_unlock(&codec->control_mutex);
+	return err;
+}
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_tlv);
+
+struct hda_ctl_ops snd_hda_bind_vol = {
+	.info = snd_hda_mixer_amp_volume_info,
+	.get = snd_hda_mixer_amp_volume_get,
+	.put = snd_hda_mixer_amp_volume_put,
+	.tlv = snd_hda_mixer_amp_tlv
+};
+EXPORT_SYMBOL_HDA(snd_hda_bind_vol);
+
+struct hda_ctl_ops snd_hda_bind_sw = {
+	.info = snd_hda_mixer_amp_switch_info,
+	.get = snd_hda_mixer_amp_switch_get,
+	.put = snd_hda_mixer_amp_switch_put,
+	.tlv = snd_hda_mixer_amp_tlv
+};
+EXPORT_SYMBOL_HDA(snd_hda_bind_sw);
+
+/*
+ * SPDIF out controls
+ */
+
+static int snd_hda_spdif_mask_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_hda_spdif_cmask_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
+					   IEC958_AES0_NONAUDIO |
+					   IEC958_AES0_CON_EMPHASIS_5015 |
+					   IEC958_AES0_CON_NOT_COPYRIGHT;
+	ucontrol->value.iec958.status[1] = IEC958_AES1_CON_CATEGORY |
+					   IEC958_AES1_CON_ORIGINAL;
+	return 0;
+}
+
+static int snd_hda_spdif_pmask_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
+					   IEC958_AES0_NONAUDIO |
+					   IEC958_AES0_PRO_EMPHASIS_5015;
+	return 0;
+}
+
+static int snd_hda_spdif_default_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.iec958.status[0] = codec->spdif_status & 0xff;
+	ucontrol->value.iec958.status[1] = (codec->spdif_status >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (codec->spdif_status >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (codec->spdif_status >> 24) & 0xff;
+
+	return 0;
+}
+
+/* convert from SPDIF status bits to HDA SPDIF bits
+ * bit 0 (DigEn) is always set zero (to be filled later)
+ */
+static unsigned short convert_from_spdif_status(unsigned int sbits)
+{
+	unsigned short val = 0;
+
+	if (sbits & IEC958_AES0_PROFESSIONAL)
+		val |= AC_DIG1_PROFESSIONAL;
+	if (sbits & IEC958_AES0_NONAUDIO)
+		val |= AC_DIG1_NONAUDIO;
+	if (sbits & IEC958_AES0_PROFESSIONAL) {
+		if ((sbits & IEC958_AES0_PRO_EMPHASIS) ==
+		    IEC958_AES0_PRO_EMPHASIS_5015)
+			val |= AC_DIG1_EMPHASIS;
+	} else {
+		if ((sbits & IEC958_AES0_CON_EMPHASIS) ==
+		    IEC958_AES0_CON_EMPHASIS_5015)
+			val |= AC_DIG1_EMPHASIS;
+		if (!(sbits & IEC958_AES0_CON_NOT_COPYRIGHT))
+			val |= AC_DIG1_COPYRIGHT;
+		if (sbits & (IEC958_AES1_CON_ORIGINAL << 8))
+			val |= AC_DIG1_LEVEL;
+		val |= sbits & (IEC958_AES1_CON_CATEGORY << 8);
+	}
+	return val;
+}
+
+/* convert to SPDIF status bits from HDA SPDIF bits
+ */
+static unsigned int convert_to_spdif_status(unsigned short val)
+{
+	unsigned int sbits = 0;
+
+	if (val & AC_DIG1_NONAUDIO)
+		sbits |= IEC958_AES0_NONAUDIO;
+	if (val & AC_DIG1_PROFESSIONAL)
+		sbits |= IEC958_AES0_PROFESSIONAL;
+	if (sbits & IEC958_AES0_PROFESSIONAL) {
+		if (sbits & AC_DIG1_EMPHASIS)
+			sbits |= IEC958_AES0_PRO_EMPHASIS_5015;
+	} else {
+		if (val & AC_DIG1_EMPHASIS)
+			sbits |= IEC958_AES0_CON_EMPHASIS_5015;
+		if (!(val & AC_DIG1_COPYRIGHT))
+			sbits |= IEC958_AES0_CON_NOT_COPYRIGHT;
+		if (val & AC_DIG1_LEVEL)
+			sbits |= (IEC958_AES1_CON_ORIGINAL << 8);
+		sbits |= val & (0x7f << 8);
+	}
+	return sbits;
+}
+
+/* set digital convert verbs both for the given NID and its slaves */
+static void set_dig_out(struct hda_codec *codec, hda_nid_t nid,
+			int verb, int val)
+{
+	hda_nid_t *d;
+
+	snd_hda_codec_write_cache(codec, nid, 0, verb, val);
+	d = codec->slave_dig_outs;
+	if (!d)
+		return;
+	for (; *d; d++)
+		snd_hda_codec_write_cache(codec, *d, 0, verb, val);
+}
+
+static inline void set_dig_out_convert(struct hda_codec *codec, hda_nid_t nid,
+				       int dig1, int dig2)
+{
+	if (dig1 != -1)
+		set_dig_out(codec, nid, AC_VERB_SET_DIGI_CONVERT_1, dig1);
+	if (dig2 != -1)
+		set_dig_out(codec, nid, AC_VERB_SET_DIGI_CONVERT_2, dig2);
+}
+
+static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value;
+	unsigned short val;
+	int change;
+
+	mutex_lock(&codec->spdif_mutex);
+	codec->spdif_status = ucontrol->value.iec958.status[0] |
+		((unsigned int)ucontrol->value.iec958.status[1] << 8) |
+		((unsigned int)ucontrol->value.iec958.status[2] << 16) |
+		((unsigned int)ucontrol->value.iec958.status[3] << 24);
+	val = convert_from_spdif_status(codec->spdif_status);
+	val |= codec->spdif_ctls & 1;
+	change = codec->spdif_ctls != val;
+	codec->spdif_ctls = val;
+
+	if (change)
+		set_dig_out_convert(codec, nid, val & 0xff, (val >> 8) & 0xff);
+
+	mutex_unlock(&codec->spdif_mutex);
+	return change;
+}
+
+#define snd_hda_spdif_out_switch_info	snd_ctl_boolean_mono_info
+
+static int snd_hda_spdif_out_switch_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = codec->spdif_ctls & AC_DIG1_ENABLE;
+	return 0;
+}
+
+static int snd_hda_spdif_out_switch_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value;
+	unsigned short val;
+	int change;
+
+	mutex_lock(&codec->spdif_mutex);
+	val = codec->spdif_ctls & ~AC_DIG1_ENABLE;
+	if (ucontrol->value.integer.value[0])
+		val |= AC_DIG1_ENABLE;
+	change = codec->spdif_ctls != val;
+	if (change) {
+		codec->spdif_ctls = val;
+		set_dig_out_convert(codec, nid, val & 0xff, -1);
+		/* unmute amp switch (if any) */
+		if ((get_wcaps(codec, nid) & AC_WCAP_OUT_AMP) &&
+		    (val & AC_DIG1_ENABLE))
+			snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0,
+						 HDA_AMP_MUTE, 0);
+	}
+	mutex_unlock(&codec->spdif_mutex);
+	return change;
+}
+
+static struct snd_kcontrol_new dig_mixes[] = {
+	{
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+		.info = snd_hda_spdif_mask_info,
+		.get = snd_hda_spdif_cmask_get,
+	},
+	{
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK),
+		.info = snd_hda_spdif_mask_info,
+		.get = snd_hda_spdif_pmask_get,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+		.info = snd_hda_spdif_mask_info,
+		.get = snd_hda_spdif_default_get,
+		.put = snd_hda_spdif_default_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+		.info = snd_hda_spdif_out_switch_info,
+		.get = snd_hda_spdif_out_switch_get,
+		.put = snd_hda_spdif_out_switch_put,
+	},
+	{ } /* end */
+};
+
+/**
+ * snd_hda_create_spdif_out_ctls - create Output SPDIF-related controls
+ * @codec: the HDA codec
+ * @nid: audio out widget NID
+ *
+ * Creates controls related with the SPDIF output.
+ * Called from each patch supporting the SPDIF out.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_create_spdif_out_ctls(struct hda_codec *codec, hda_nid_t nid)
+{
+	int err;
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_new *dig_mix;
+	int idx;
+
+	idx = find_empty_mixer_ctl_idx(codec, "IEC958 Playback Switch");
+	if (idx < 0) {
+		printk(KERN_ERR "hda_codec: too many IEC958 outputs\n");
+		return -EBUSY;
+	}
+	for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) {
+		kctl = snd_ctl_new1(dig_mix, codec);
+		if (!kctl)
+			return -ENOMEM;
+		kctl->id.index = idx;
+		kctl->private_value = nid;
+		err = snd_hda_ctl_add(codec, nid, kctl);
+		if (err < 0)
+			return err;
+	}
+	codec->spdif_ctls =
+		snd_hda_codec_read(codec, nid, 0,
+				   AC_VERB_GET_DIGI_CONVERT_1, 0);
+	codec->spdif_status = convert_to_spdif_status(codec->spdif_ctls);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_create_spdif_out_ctls);
+
+/*
+ * SPDIF sharing with analog output
+ */
+static int spdif_share_sw_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = mout->share_spdif;
+	return 0;
+}
+
+static int spdif_share_sw_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
+	mout->share_spdif = !!ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static struct snd_kcontrol_new spdif_share_sw = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "IEC958 Default PCM Playback Switch",
+	.info = snd_ctl_boolean_mono_info,
+	.get = spdif_share_sw_get,
+	.put = spdif_share_sw_put,
+};
+
+/**
+ * snd_hda_create_spdif_share_sw - create Default PCM switch
+ * @codec: the HDA codec
+ * @mout: multi-out instance
+ */
+int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
+				  struct hda_multi_out *mout)
+{
+	if (!mout->dig_out_nid)
+		return 0;
+	/* ATTENTION: here mout is passed as private_data, instead of codec */
+	return snd_hda_ctl_add(codec, mout->dig_out_nid,
+			      snd_ctl_new1(&spdif_share_sw, mout));
+}
+EXPORT_SYMBOL_HDA(snd_hda_create_spdif_share_sw);
+
+/*
+ * SPDIF input
+ */
+
+#define snd_hda_spdif_in_switch_info	snd_hda_spdif_out_switch_info
+
+static int snd_hda_spdif_in_switch_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = codec->spdif_in_enable;
+	return 0;
+}
+
+static int snd_hda_spdif_in_switch_put(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value;
+	unsigned int val = !!ucontrol->value.integer.value[0];
+	int change;
+
+	mutex_lock(&codec->spdif_mutex);
+	change = codec->spdif_in_enable != val;
+	if (change) {
+		codec->spdif_in_enable = val;
+		snd_hda_codec_write_cache(codec, nid, 0,
+					  AC_VERB_SET_DIGI_CONVERT_1, val);
+	}
+	mutex_unlock(&codec->spdif_mutex);
+	return change;
+}
+
+static int snd_hda_spdif_in_status_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value;
+	unsigned short val;
+	unsigned int sbits;
+
+	val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DIGI_CONVERT_1, 0);
+	sbits = convert_to_spdif_status(val);
+	ucontrol->value.iec958.status[0] = sbits;
+	ucontrol->value.iec958.status[1] = sbits >> 8;
+	ucontrol->value.iec958.status[2] = sbits >> 16;
+	ucontrol->value.iec958.status[3] = sbits >> 24;
+	return 0;
+}
+
+static struct snd_kcontrol_new dig_in_ctls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, SWITCH),
+		.info = snd_hda_spdif_in_switch_info,
+		.get = snd_hda_spdif_in_switch_get,
+		.put = snd_hda_spdif_in_switch_put,
+	},
+	{
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+		.info = snd_hda_spdif_mask_info,
+		.get = snd_hda_spdif_in_status_get,
+	},
+	{ } /* end */
+};
+
+/**
+ * snd_hda_create_spdif_in_ctls - create Input SPDIF-related controls
+ * @codec: the HDA codec
+ * @nid: audio in widget NID
+ *
+ * Creates controls related with the SPDIF input.
+ * Called from each patch supporting the SPDIF in.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid)
+{
+	int err;
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_new *dig_mix;
+	int idx;
+
+	idx = find_empty_mixer_ctl_idx(codec, "IEC958 Capture Switch");
+	if (idx < 0) {
+		printk(KERN_ERR "hda_codec: too many IEC958 inputs\n");
+		return -EBUSY;
+	}
+	for (dig_mix = dig_in_ctls; dig_mix->name; dig_mix++) {
+		kctl = snd_ctl_new1(dig_mix, codec);
+		if (!kctl)
+			return -ENOMEM;
+		kctl->private_value = nid;
+		err = snd_hda_ctl_add(codec, nid, kctl);
+		if (err < 0)
+			return err;
+	}
+	codec->spdif_in_enable =
+		snd_hda_codec_read(codec, nid, 0,
+				   AC_VERB_GET_DIGI_CONVERT_1, 0) &
+		AC_DIG1_ENABLE;
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_create_spdif_in_ctls);
+
+#ifdef SND_HDA_NEEDS_RESUME
+/*
+ * command cache
+ */
+
+/* build a 32bit cache key with the widget id and the command parameter */
+#define build_cmd_cache_key(nid, verb)	((verb << 8) | nid)
+#define get_cmd_cache_nid(key)		((key) & 0xff)
+#define get_cmd_cache_cmd(key)		(((key) >> 8) & 0xffff)
+
+/**
+ * snd_hda_codec_write_cache - send a single command with caching
+ * @codec: the HDA codec
+ * @nid: NID to send the command
+ * @direct: direct flag
+ * @verb: the verb to send
+ * @parm: the parameter for the verb
+ *
+ * Send a single command without waiting for response.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
+			      int direct, unsigned int verb, unsigned int parm)
+{
+	int err = snd_hda_codec_write(codec, nid, direct, verb, parm);
+	struct hda_cache_head *c;
+	u32 key;
+
+	if (err < 0)
+		return err;
+	/* parm may contain the verb stuff for get/set amp */
+	verb = verb | (parm >> 8);
+	parm &= 0xff;
+	key = build_cmd_cache_key(nid, verb);
+	mutex_lock(&codec->bus->cmd_mutex);
+	c = get_alloc_hash(&codec->cmd_cache, key);
+	if (c)
+		c->val = parm;
+	mutex_unlock(&codec->bus->cmd_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_write_cache);
+
+/**
+ * snd_hda_codec_update_cache - check cache and write the cmd only when needed
+ * @codec: the HDA codec
+ * @nid: NID to send the command
+ * @direct: direct flag
+ * @verb: the verb to send
+ * @parm: the parameter for the verb
+ *
+ * This function works like snd_hda_codec_write_cache(), but it doesn't send
+ * command if the parameter is already identical with the cached value.
+ * If not, it sends the command and refreshes the cache.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_codec_update_cache(struct hda_codec *codec, hda_nid_t nid,
+			       int direct, unsigned int verb, unsigned int parm)
+{
+	struct hda_cache_head *c;
+	u32 key;
+
+	/* parm may contain the verb stuff for get/set amp */
+	verb = verb | (parm >> 8);
+	parm &= 0xff;
+	key = build_cmd_cache_key(nid, verb);
+	mutex_lock(&codec->bus->cmd_mutex);
+	c = get_hash(&codec->cmd_cache, key);
+	if (c && c->val == parm) {
+		mutex_unlock(&codec->bus->cmd_mutex);
+		return 0;
+	}
+	mutex_unlock(&codec->bus->cmd_mutex);
+	return snd_hda_codec_write_cache(codec, nid, direct, verb, parm);
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_update_cache);
+
+/**
+ * snd_hda_codec_resume_cache - Resume the all commands from the cache
+ * @codec: HD-audio codec
+ *
+ * Execute all verbs recorded in the command caches to resume.
+ */
+void snd_hda_codec_resume_cache(struct hda_codec *codec)
+{
+	struct hda_cache_head *buffer = codec->cmd_cache.buf.list;
+	int i;
+
+	for (i = 0; i < codec->cmd_cache.buf.used; i++, buffer++) {
+		u32 key = buffer->key;
+		if (!key)
+			continue;
+		snd_hda_codec_write(codec, get_cmd_cache_nid(key), 0,
+				    get_cmd_cache_cmd(key), buffer->val);
+	}
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_resume_cache);
+
+/**
+ * snd_hda_sequence_write_cache - sequence writes with caching
+ * @codec: the HDA codec
+ * @seq: VERB array to send
+ *
+ * Send the commands sequentially from the given array.
+ * Thte commands are recorded on cache for power-save and resume.
+ * The array must be terminated with NID=0.
+ */
+void snd_hda_sequence_write_cache(struct hda_codec *codec,
+				  const struct hda_verb *seq)
+{
+	for (; seq->nid; seq++)
+		snd_hda_codec_write_cache(codec, seq->nid, 0, seq->verb,
+					  seq->param);
+}
+EXPORT_SYMBOL_HDA(snd_hda_sequence_write_cache);
+#endif /* SND_HDA_NEEDS_RESUME */
+
+/*
+ * set power state of the codec
+ */
+static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
+				unsigned int power_state)
+{
+	hda_nid_t nid;
+	int i;
+
+	/* this delay seems necessary to avoid click noise at power-down */
+	if (power_state == AC_PWRST_D3)
+		msleep(100);
+	snd_hda_codec_read(codec, fg, 0, AC_VERB_SET_POWER_STATE,
+			    power_state);
+	/* partial workaround for "azx_get_response timeout" */
+	if (power_state == AC_PWRST_D0 &&
+	    (codec->vendor_id & 0xffff0000) == 0x14f10000)
+		msleep(10);
+
+	nid = codec->start_nid;
+	for (i = 0; i < codec->num_nodes; i++, nid++) {
+		unsigned int wcaps = get_wcaps(codec, nid);
+		if (wcaps & AC_WCAP_POWER) {
+			unsigned int wid_type = get_wcaps_type(wcaps);
+			if (power_state == AC_PWRST_D3 &&
+			    wid_type == AC_WID_PIN) {
+				unsigned int pincap;
+				/*
+				 * don't power down the widget if it controls
+				 * eapd and EAPD_BTLENABLE is set.
+				 */
+				pincap = snd_hda_query_pin_caps(codec, nid);
+				if (pincap & AC_PINCAP_EAPD) {
+					int eapd = snd_hda_codec_read(codec,
+						nid, 0,
+						AC_VERB_GET_EAPD_BTLENABLE, 0);
+					eapd &= 0x02;
+					if (eapd)
+						continue;
+				}
+			}
+			snd_hda_codec_write(codec, nid, 0,
+					    AC_VERB_SET_POWER_STATE,
+					    power_state);
+		}
+	}
+
+	if (power_state == AC_PWRST_D0) {
+		unsigned long end_time;
+		int state;
+		/* wait until the codec reachs to D0 */
+		end_time = jiffies + msecs_to_jiffies(500);
+		do {
+			state = snd_hda_codec_read(codec, fg, 0,
+						   AC_VERB_GET_POWER_STATE, 0);
+			if (state == power_state)
+				break;
+			msleep(1);
+		} while (time_after_eq(end_time, jiffies));
+	}
+}
+
+#ifdef CONFIG_SND_HDA_HWDEP
+/* execute additional init verbs */
+static void hda_exec_init_verbs(struct hda_codec *codec)
+{
+	if (codec->init_verbs.list)
+		snd_hda_sequence_write(codec, codec->init_verbs.list);
+}
+#else
+static inline void hda_exec_init_verbs(struct hda_codec *codec) {}
+#endif
+
+#ifdef SND_HDA_NEEDS_RESUME
+/*
+ * call suspend and power-down; used both from PM and power-save
+ */
+static void hda_call_codec_suspend(struct hda_codec *codec)
+{
+	if (codec->patch_ops.suspend)
+		codec->patch_ops.suspend(codec, PMSG_SUSPEND);
+	hda_cleanup_all_streams(codec);
+	hda_set_power_state(codec,
+			    codec->afg ? codec->afg : codec->mfg,
+			    AC_PWRST_D3);
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	snd_hda_update_power_acct(codec);
+	cancel_delayed_work(&codec->power_work);
+	codec->power_on = 0;
+	codec->power_transition = 0;
+	codec->power_jiffies = jiffies;
+#endif
+}
+
+/*
+ * kick up codec; used both from PM and power-save
+ */
+static void hda_call_codec_resume(struct hda_codec *codec)
+{
+	hda_set_power_state(codec,
+			    codec->afg ? codec->afg : codec->mfg,
+			    AC_PWRST_D0);
+	restore_pincfgs(codec); /* restore all current pin configs */
+	restore_shutup_pins(codec);
+	hda_exec_init_verbs(codec);
+	if (codec->patch_ops.resume)
+		codec->patch_ops.resume(codec);
+	else {
+		if (codec->patch_ops.init)
+			codec->patch_ops.init(codec);
+		snd_hda_codec_resume_amp(codec);
+		snd_hda_codec_resume_cache(codec);
+	}
+}
+#endif /* SND_HDA_NEEDS_RESUME */
+
+
+/**
+ * snd_hda_build_controls - build mixer controls
+ * @bus: the BUS
+ *
+ * Creates mixer controls for each codec included in the bus.
+ *
+ * Returns 0 if successful, otherwise a negative error code.
+ */
+int /*__devinit*/ snd_hda_build_controls(struct hda_bus *bus)
+{
+	struct hda_codec *codec;
+
+	list_for_each_entry(codec, &bus->codec_list, list) {
+		int err = snd_hda_codec_build_controls(codec);
+		if (err < 0) {
+			printk(KERN_ERR "hda_codec: cannot build controls "
+			       "for #%d (error %d)\n", codec->addr, err);
+			err = snd_hda_codec_reset(codec);
+			if (err < 0) {
+				printk(KERN_ERR
+				       "hda_codec: cannot revert codec\n");
+				return err;
+			}
+		}
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_build_controls);
+
+int snd_hda_codec_build_controls(struct hda_codec *codec)
+{
+	int err = 0;
+	hda_exec_init_verbs(codec);
+	/* continue to initialize... */
+	if (codec->patch_ops.init)
+		err = codec->patch_ops.init(codec);
+	if (!err && codec->patch_ops.build_controls)
+		err = codec->patch_ops.build_controls(codec);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/*
+ * stream formats
+ */
+struct hda_rate_tbl {
+	unsigned int hz;
+	unsigned int alsa_bits;
+	unsigned int hda_fmt;
+};
+
+/* rate = base * mult / div */
+#define HDA_RATE(base, mult, div) \
+	(AC_FMT_BASE_##base##K | (((mult) - 1) << AC_FMT_MULT_SHIFT) | \
+	 (((div) - 1) << AC_FMT_DIV_SHIFT))
+
+static struct hda_rate_tbl rate_bits[] = {
+	/* rate in Hz, ALSA rate bitmask, HDA format value */
+
+	/* autodetected value used in snd_hda_query_supported_pcm */
+	{ 8000, SNDRV_PCM_RATE_8000, HDA_RATE(48, 1, 6) },
+	{ 11025, SNDRV_PCM_RATE_11025, HDA_RATE(44, 1, 4) },
+	{ 16000, SNDRV_PCM_RATE_16000, HDA_RATE(48, 1, 3) },
+	{ 22050, SNDRV_PCM_RATE_22050, HDA_RATE(44, 1, 2) },
+	{ 32000, SNDRV_PCM_RATE_32000, HDA_RATE(48, 2, 3) },
+	{ 44100, SNDRV_PCM_RATE_44100, HDA_RATE(44, 1, 1) },
+	{ 48000, SNDRV_PCM_RATE_48000, HDA_RATE(48, 1, 1) },
+	{ 88200, SNDRV_PCM_RATE_88200, HDA_RATE(44, 2, 1) },
+	{ 96000, SNDRV_PCM_RATE_96000, HDA_RATE(48, 2, 1) },
+	{ 176400, SNDRV_PCM_RATE_176400, HDA_RATE(44, 4, 1) },
+	{ 192000, SNDRV_PCM_RATE_192000, HDA_RATE(48, 4, 1) },
+#define AC_PAR_PCM_RATE_BITS	11
+	/* up to bits 10, 384kHZ isn't supported properly */
+
+	/* not autodetected value */
+	{ 9600, SNDRV_PCM_RATE_KNOT, HDA_RATE(48, 1, 5) },
+
+	{ 0 } /* terminator */
+};
+
+/**
+ * snd_hda_calc_stream_format - calculate format bitset
+ * @rate: the sample rate
+ * @channels: the number of channels
+ * @format: the PCM format (SNDRV_PCM_FORMAT_XXX)
+ * @maxbps: the max. bps
+ *
+ * Calculate the format bitset from the given rate, channels and th PCM format.
+ *
+ * Return zero if invalid.
+ */
+unsigned int snd_hda_calc_stream_format(unsigned int rate,
+					unsigned int channels,
+					unsigned int format,
+					unsigned int maxbps,
+					unsigned short spdif_ctls)
+{
+	int i;
+	unsigned int val = 0;
+
+	for (i = 0; rate_bits[i].hz; i++)
+		if (rate_bits[i].hz == rate) {
+			val = rate_bits[i].hda_fmt;
+			break;
+		}
+	if (!rate_bits[i].hz) {
+		snd_printdd("invalid rate %d\n", rate);
+		return 0;
+	}
+
+	if (channels == 0 || channels > 8) {
+		snd_printdd("invalid channels %d\n", channels);
+		return 0;
+	}
+	val |= channels - 1;
+
+	switch (snd_pcm_format_width(format)) {
+	case 8:
+		val |= AC_FMT_BITS_8;
+		break;
+	case 16:
+		val |= AC_FMT_BITS_16;
+		break;
+	case 20:
+	case 24:
+	case 32:
+		if (maxbps >= 32 || format == SNDRV_PCM_FORMAT_FLOAT_LE)
+			val |= AC_FMT_BITS_32;
+		else if (maxbps >= 24)
+			val |= AC_FMT_BITS_24;
+		else
+			val |= AC_FMT_BITS_20;
+		break;
+	default:
+		snd_printdd("invalid format width %d\n",
+			    snd_pcm_format_width(format));
+		return 0;
+	}
+
+	if (spdif_ctls & AC_DIG1_NONAUDIO)
+		val |= AC_FMT_TYPE_NON_PCM;
+
+	return val;
+}
+EXPORT_SYMBOL_HDA(snd_hda_calc_stream_format);
+
+static unsigned int get_pcm_param(struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int val = 0;
+	if (nid != codec->afg &&
+	    (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD))
+		val = snd_hda_param_read(codec, nid, AC_PAR_PCM);
+	if (!val || val == -1)
+		val = snd_hda_param_read(codec, codec->afg, AC_PAR_PCM);
+	if (!val || val == -1)
+		return 0;
+	return val;
+}
+
+static unsigned int query_pcm_param(struct hda_codec *codec, hda_nid_t nid)
+{
+	return query_caps_hash(codec, nid, HDA_HASH_PARPCM_KEY(nid),
+			       get_pcm_param);
+}
+
+static unsigned int get_stream_param(struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
+	if (!streams || streams == -1)
+		streams = snd_hda_param_read(codec, codec->afg, AC_PAR_STREAM);
+	if (!streams || streams == -1)
+		return 0;
+	return streams;
+}
+
+static unsigned int query_stream_param(struct hda_codec *codec, hda_nid_t nid)
+{
+	return query_caps_hash(codec, nid, HDA_HASH_PARSTR_KEY(nid),
+			       get_stream_param);
+}
+
+/**
+ * snd_hda_query_supported_pcm - query the supported PCM rates and formats
+ * @codec: the HDA codec
+ * @nid: NID to query
+ * @ratesp: the pointer to store the detected rate bitflags
+ * @formatsp: the pointer to store the detected formats
+ * @bpsp: the pointer to store the detected format widths
+ *
+ * Queries the supported PCM rates and formats.  The NULL @ratesp, @formatsp
+ * or @bsps argument is ignored.
+ *
+ * Returns 0 if successful, otherwise a negative error code.
+ */
+static int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
+				u32 *ratesp, u64 *formatsp, unsigned int *bpsp)
+{
+	unsigned int i, val, wcaps;
+
+	wcaps = get_wcaps(codec, nid);
+	val = query_pcm_param(codec, nid);
+
+	if (ratesp) {
+		u32 rates = 0;
+		for (i = 0; i < AC_PAR_PCM_RATE_BITS; i++) {
+			if (val & (1 << i))
+				rates |= rate_bits[i].alsa_bits;
+		}
+		if (rates == 0) {
+			snd_printk(KERN_ERR "hda_codec: rates == 0 "
+				   "(nid=0x%x, val=0x%x, ovrd=%i)\n",
+					nid, val,
+					(wcaps & AC_WCAP_FORMAT_OVRD) ? 1 : 0);
+			return -EIO;
+		}
+		*ratesp = rates;
+	}
+
+	if (formatsp || bpsp) {
+		u64 formats = 0;
+		unsigned int streams, bps;
+
+		streams = query_stream_param(codec, nid);
+		if (!streams)
+			return -EIO;
+
+		bps = 0;
+		if (streams & AC_SUPFMT_PCM) {
+			if (val & AC_SUPPCM_BITS_8) {
+				formats |= SNDRV_PCM_FMTBIT_U8;
+				bps = 8;
+			}
+			if (val & AC_SUPPCM_BITS_16) {
+				formats |= SNDRV_PCM_FMTBIT_S16_LE;
+				bps = 16;
+			}
+			if (wcaps & AC_WCAP_DIGITAL) {
+				if (val & AC_SUPPCM_BITS_32)
+					formats |= SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE;
+				if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24))
+					formats |= SNDRV_PCM_FMTBIT_S32_LE;
+				if (val & AC_SUPPCM_BITS_24)
+					bps = 24;
+				else if (val & AC_SUPPCM_BITS_20)
+					bps = 20;
+			} else if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24|
+					  AC_SUPPCM_BITS_32)) {
+				formats |= SNDRV_PCM_FMTBIT_S32_LE;
+				if (val & AC_SUPPCM_BITS_32)
+					bps = 32;
+				else if (val & AC_SUPPCM_BITS_24)
+					bps = 24;
+				else if (val & AC_SUPPCM_BITS_20)
+					bps = 20;
+			}
+		}
+		if (streams & AC_SUPFMT_FLOAT32) {
+			formats |= SNDRV_PCM_FMTBIT_FLOAT_LE;
+			if (!bps)
+				bps = 32;
+		}
+		if (streams == AC_SUPFMT_AC3) {
+			/* should be exclusive */
+			/* temporary hack: we have still no proper support
+			 * for the direct AC3 stream...
+			 */
+			formats |= SNDRV_PCM_FMTBIT_U8;
+			bps = 8;
+		}
+		if (formats == 0) {
+			snd_printk(KERN_ERR "hda_codec: formats == 0 "
+				   "(nid=0x%x, val=0x%x, ovrd=%i, "
+				   "streams=0x%x)\n",
+					nid, val,
+					(wcaps & AC_WCAP_FORMAT_OVRD) ? 1 : 0,
+					streams);
+			return -EIO;
+		}
+		if (formatsp)
+			*formatsp = formats;
+		if (bpsp)
+			*bpsp = bps;
+	}
+
+	return 0;
+}
+
+/**
+ * snd_hda_is_supported_format - Check the validity of the format
+ * @codec: HD-audio codec
+ * @nid: NID to check
+ * @format: the HD-audio format value to check
+ *
+ * Check whether the given node supports the format value.
+ *
+ * Returns 1 if supported, 0 if not.
+ */
+int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid,
+				unsigned int format)
+{
+	int i;
+	unsigned int val = 0, rate, stream;
+
+	val = query_pcm_param(codec, nid);
+	if (!val)
+		return 0;
+
+	rate = format & 0xff00;
+	for (i = 0; i < AC_PAR_PCM_RATE_BITS; i++)
+		if (rate_bits[i].hda_fmt == rate) {
+			if (val & (1 << i))
+				break;
+			return 0;
+		}
+	if (i >= AC_PAR_PCM_RATE_BITS)
+		return 0;
+
+	stream = query_stream_param(codec, nid);
+	if (!stream)
+		return 0;
+
+	if (stream & AC_SUPFMT_PCM) {
+		switch (format & 0xf0) {
+		case 0x00:
+			if (!(val & AC_SUPPCM_BITS_8))
+				return 0;
+			break;
+		case 0x10:
+			if (!(val & AC_SUPPCM_BITS_16))
+				return 0;
+			break;
+		case 0x20:
+			if (!(val & AC_SUPPCM_BITS_20))
+				return 0;
+			break;
+		case 0x30:
+			if (!(val & AC_SUPPCM_BITS_24))
+				return 0;
+			break;
+		case 0x40:
+			if (!(val & AC_SUPPCM_BITS_32))
+				return 0;
+			break;
+		default:
+			return 0;
+		}
+	} else {
+		/* FIXME: check for float32 and AC3? */
+	}
+
+	return 1;
+}
+EXPORT_SYMBOL_HDA(snd_hda_is_supported_format);
+
+/*
+ * PCM stuff
+ */
+static int hda_pcm_default_open_close(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int hda_pcm_default_prepare(struct hda_pcm_stream *hinfo,
+				   struct hda_codec *codec,
+				   unsigned int stream_tag,
+				   unsigned int format,
+				   struct snd_pcm_substream *substream)
+{
+	snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format);
+	return 0;
+}
+
+static int hda_pcm_default_cleanup(struct hda_pcm_stream *hinfo,
+				   struct hda_codec *codec,
+				   struct snd_pcm_substream *substream)
+{
+	snd_hda_codec_cleanup_stream(codec, hinfo->nid);
+	return 0;
+}
+
+static int set_pcm_default_values(struct hda_codec *codec,
+				  struct hda_pcm_stream *info)
+{
+	int err;
+
+	/* query support PCM information from the given NID */
+	if (info->nid && (!info->rates || !info->formats)) {
+		err = snd_hda_query_supported_pcm(codec, info->nid,
+				info->rates ? NULL : &info->rates,
+				info->formats ? NULL : &info->formats,
+				info->maxbps ? NULL : &info->maxbps);
+		if (err < 0)
+			return err;
+	}
+	if (info->ops.open == NULL)
+		info->ops.open = hda_pcm_default_open_close;
+	if (info->ops.close == NULL)
+		info->ops.close = hda_pcm_default_open_close;
+	if (info->ops.prepare == NULL) {
+		if (snd_BUG_ON(!info->nid))
+			return -EINVAL;
+		info->ops.prepare = hda_pcm_default_prepare;
+	}
+	if (info->ops.cleanup == NULL) {
+		if (snd_BUG_ON(!info->nid))
+			return -EINVAL;
+		info->ops.cleanup = hda_pcm_default_cleanup;
+	}
+	return 0;
+}
+
+/*
+ * codec prepare/cleanup entries
+ */
+int snd_hda_codec_prepare(struct hda_codec *codec,
+			  struct hda_pcm_stream *hinfo,
+			  unsigned int stream,
+			  unsigned int format,
+			  struct snd_pcm_substream *substream)
+{
+	int ret;
+	mutex_lock(&codec->bus->prepare_mutex);
+	ret = hinfo->ops.prepare(hinfo, codec, stream, format, substream);
+	if (ret >= 0)
+		purify_inactive_streams(codec);
+	mutex_unlock(&codec->bus->prepare_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_prepare);
+
+void snd_hda_codec_cleanup(struct hda_codec *codec,
+			   struct hda_pcm_stream *hinfo,
+			   struct snd_pcm_substream *substream)
+{
+	mutex_lock(&codec->bus->prepare_mutex);
+	hinfo->ops.cleanup(hinfo, codec, substream);
+	mutex_unlock(&codec->bus->prepare_mutex);
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_cleanup);
+
+/* global */
+const char *snd_hda_pcm_type_name[HDA_PCM_NTYPES] = {
+	"Audio", "SPDIF", "HDMI", "Modem"
+};
+
+/*
+ * get the empty PCM device number to assign
+ *
+ * note the max device number is limited by HDA_MAX_PCMS, currently 10
+ */
+static int get_empty_pcm_device(struct hda_bus *bus, int type)
+{
+	/* audio device indices; not linear to keep compatibility */
+	static int audio_idx[HDA_PCM_NTYPES][5] = {
+		[HDA_PCM_TYPE_AUDIO] = { 0, 2, 4, 5, -1 },
+		[HDA_PCM_TYPE_SPDIF] = { 1, -1 },
+		[HDA_PCM_TYPE_HDMI]  = { 3, 7, 8, 9, -1 },
+		[HDA_PCM_TYPE_MODEM] = { 6, -1 },
+	};
+	int i;
+
+	if (type >= HDA_PCM_NTYPES) {
+		snd_printk(KERN_WARNING "Invalid PCM type %d\n", type);
+		return -EINVAL;
+	}
+
+	for (i = 0; audio_idx[type][i] >= 0 ; i++)
+		if (!test_and_set_bit(audio_idx[type][i], bus->pcm_dev_bits))
+			return audio_idx[type][i];
+
+	snd_printk(KERN_WARNING "Too many %s devices\n",
+		snd_hda_pcm_type_name[type]);
+	return -EAGAIN;
+}
+
+/*
+ * attach a new PCM stream
+ */
+static int snd_hda_attach_pcm(struct hda_codec *codec, struct hda_pcm *pcm)
+{
+	struct hda_bus *bus = codec->bus;
+	struct hda_pcm_stream *info;
+	int stream, err;
+
+	if (snd_BUG_ON(!pcm->name))
+		return -EINVAL;
+	for (stream = 0; stream < 2; stream++) {
+		info = &pcm->stream[stream];
+		if (info->substreams) {
+			err = set_pcm_default_values(codec, info);
+			if (err < 0)
+				return err;
+		}
+	}
+	return bus->ops.attach_pcm(bus, codec, pcm);
+}
+
+/* assign all PCMs of the given codec */
+int snd_hda_codec_build_pcms(struct hda_codec *codec)
+{
+	unsigned int pcm;
+	int err;
+
+	if (!codec->num_pcms) {
+		if (!codec->patch_ops.build_pcms)
+			return 0;
+		err = codec->patch_ops.build_pcms(codec);
+		if (err < 0) {
+			printk(KERN_ERR "hda_codec: cannot build PCMs"
+			       "for #%d (error %d)\n", codec->addr, err);
+			err = snd_hda_codec_reset(codec);
+			if (err < 0) {
+				printk(KERN_ERR
+				       "hda_codec: cannot revert codec\n");
+				return err;
+			}
+		}
+	}
+	for (pcm = 0; pcm < codec->num_pcms; pcm++) {
+		struct hda_pcm *cpcm = &codec->pcm_info[pcm];
+		int dev;
+
+		if (!cpcm->stream[0].substreams && !cpcm->stream[1].substreams)
+			continue; /* no substreams assigned */
+
+		if (!cpcm->pcm) {
+			dev = get_empty_pcm_device(codec->bus, cpcm->pcm_type);
+			if (dev < 0)
+				continue; /* no fatal error */
+			cpcm->device = dev;
+			err = snd_hda_attach_pcm(codec, cpcm);
+			if (err < 0) {
+				printk(KERN_ERR "hda_codec: cannot attach "
+				       "PCM stream %d for codec #%d\n",
+				       dev, codec->addr);
+				continue; /* no fatal error */
+			}
+		}
+	}
+	return 0;
+}
+
+/**
+ * snd_hda_build_pcms - build PCM information
+ * @bus: the BUS
+ *
+ * Create PCM information for each codec included in the bus.
+ *
+ * The build_pcms codec patch is requested to set up codec->num_pcms and
+ * codec->pcm_info properly.  The array is referred by the top-level driver
+ * to create its PCM instances.
+ * The allocated codec->pcm_info should be released in codec->patch_ops.free
+ * callback.
+ *
+ * At least, substreams, channels_min and channels_max must be filled for
+ * each stream.  substreams = 0 indicates that the stream doesn't exist.
+ * When rates and/or formats are zero, the supported values are queried
+ * from the given nid.  The nid is used also by the default ops.prepare
+ * and ops.cleanup callbacks.
+ *
+ * The driver needs to call ops.open in its open callback.  Similarly,
+ * ops.close is supposed to be called in the close callback.
+ * ops.prepare should be called in the prepare or hw_params callback
+ * with the proper parameters for set up.
+ * ops.cleanup should be called in hw_free for clean up of streams.
+ *
+ * This function returns 0 if successfull, or a negative error code.
+ */
+int __devinit snd_hda_build_pcms(struct hda_bus *bus)
+{
+	struct hda_codec *codec;
+
+	list_for_each_entry(codec, &bus->codec_list, list) {
+		int err = snd_hda_codec_build_pcms(codec);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_build_pcms);
+
+/**
+ * snd_hda_check_board_config - compare the current codec with the config table
+ * @codec: the HDA codec
+ * @num_configs: number of config enums
+ * @models: array of model name strings
+ * @tbl: configuration table, terminated by null entries
+ *
+ * Compares the modelname or PCI subsystem id of the current codec with the
+ * given configuration table.  If a matching entry is found, returns its
+ * config value (supposed to be 0 or positive).
+ *
+ * If no entries are matching, the function returns a negative value.
+ */
+int snd_hda_check_board_config(struct hda_codec *codec,
+			       int num_configs, const char **models,
+			       const struct snd_pci_quirk *tbl)
+{
+	if (codec->modelname && models) {
+		int i;
+		for (i = 0; i < num_configs; i++) {
+			if (models[i] &&
+			    !strcmp(codec->modelname, models[i])) {
+				snd_printd(KERN_INFO "hda_codec: model '%s' is "
+					   "selected\n", models[i]);
+				return i;
+			}
+		}
+	}
+
+	if (!codec->bus->pci || !tbl)
+		return -1;
+
+	tbl = snd_pci_quirk_lookup(codec->bus->pci, tbl);
+	if (!tbl)
+		return -1;
+	if (tbl->value >= 0 && tbl->value < num_configs) {
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+		char tmp[10];
+		const char *model = NULL;
+		if (models)
+			model = models[tbl->value];
+		if (!model) {
+			sprintf(tmp, "#%d", tbl->value);
+			model = tmp;
+		}
+		snd_printdd(KERN_INFO "hda_codec: model '%s' is selected "
+			    "for config %x:%x (%s)\n",
+			    model, tbl->subvendor, tbl->subdevice,
+			    (tbl->name ? tbl->name : "Unknown device"));
+#endif
+		return tbl->value;
+	}
+	return -1;
+}
+EXPORT_SYMBOL_HDA(snd_hda_check_board_config);
+
+/**
+ * snd_hda_check_board_codec_sid_config - compare the current codec
+					subsystem ID with the
+					config table
+
+	   This is important for Gateway notebooks with SB450 HDA Audio
+	   where the vendor ID of the PCI device is:
+		ATI Technologies Inc SB450 HDA Audio [1002:437b]
+	   and the vendor/subvendor are found only at the codec.
+
+ * @codec: the HDA codec
+ * @num_configs: number of config enums
+ * @models: array of model name strings
+ * @tbl: configuration table, terminated by null entries
+ *
+ * Compares the modelname or PCI subsystem id of the current codec with the
+ * given configuration table.  If a matching entry is found, returns its
+ * config value (supposed to be 0 or positive).
+ *
+ * If no entries are matching, the function returns a negative value.
+ */
+int snd_hda_check_board_codec_sid_config(struct hda_codec *codec,
+			       int num_configs, const char **models,
+			       const struct snd_pci_quirk *tbl)
+{
+	const struct snd_pci_quirk *q;
+
+	/* Search for codec ID */
+	for (q = tbl; q->subvendor; q++) {
+		unsigned long vendorid = (q->subdevice) | (q->subvendor << 16);
+
+		if (vendorid == codec->subsystem_id)
+			break;
+	}
+
+	if (!q->subvendor)
+		return -1;
+
+	tbl = q;
+
+	if (tbl->value >= 0 && tbl->value < num_configs) {
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+		char tmp[10];
+		const char *model = NULL;
+		if (models)
+			model = models[tbl->value];
+		if (!model) {
+			sprintf(tmp, "#%d", tbl->value);
+			model = tmp;
+		}
+		snd_printdd(KERN_INFO "hda_codec: model '%s' is selected "
+			    "for config %x:%x (%s)\n",
+			    model, tbl->subvendor, tbl->subdevice,
+			    (tbl->name ? tbl->name : "Unknown device"));
+#endif
+		return tbl->value;
+	}
+	return -1;
+}
+EXPORT_SYMBOL_HDA(snd_hda_check_board_codec_sid_config);
+
+/**
+ * snd_hda_add_new_ctls - create controls from the array
+ * @codec: the HDA codec
+ * @knew: the array of struct snd_kcontrol_new
+ *
+ * This helper function creates and add new controls in the given array.
+ * The array must be terminated with an empty entry as terminator.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew)
+{
+	int err;
+
+	for (; knew->name; knew++) {
+		struct snd_kcontrol *kctl;
+		int addr = 0, idx = 0;
+		if (knew->iface == -1)	/* skip this codec private value */
+			continue;
+		for (;;) {
+			kctl = snd_ctl_new1(knew, codec);
+			if (!kctl)
+				return -ENOMEM;
+			if (addr > 0)
+				kctl->id.device = addr;
+			if (idx > 0)
+				kctl->id.index = idx;
+			err = snd_hda_ctl_add(codec, 0, kctl);
+			if (!err)
+				break;
+			/* try first with another device index corresponding to
+			 * the codec addr; if it still fails (or it's the
+			 * primary codec), then try another control index
+			 */
+			if (!addr && codec->addr)
+				addr = codec->addr;
+			else if (!idx && !knew->index) {
+				idx = find_empty_mixer_ctl_idx(codec,
+							       knew->name);
+				if (idx <= 0)
+					return err;
+			} else
+				return err;
+		}
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_add_new_ctls);
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
+				unsigned int power_state);
+
+static void hda_power_work(struct work_struct *work)
+{
+	struct hda_codec *codec =
+		container_of(work, struct hda_codec, power_work.work);
+	struct hda_bus *bus = codec->bus;
+
+	if (!codec->power_on || codec->power_count) {
+		codec->power_transition = 0;
+		return;
+	}
+
+	hda_call_codec_suspend(codec);
+	if (bus->ops.pm_notify)
+		bus->ops.pm_notify(bus);
+}
+
+static void hda_keep_power_on(struct hda_codec *codec)
+{
+	codec->power_count++;
+	codec->power_on = 1;
+	codec->power_jiffies = jiffies;
+}
+
+/* update the power on/off account with the current jiffies */
+void snd_hda_update_power_acct(struct hda_codec *codec)
+{
+	unsigned long delta = jiffies - codec->power_jiffies;
+	if (codec->power_on)
+		codec->power_on_acct += delta;
+	else
+		codec->power_off_acct += delta;
+	codec->power_jiffies += delta;
+}
+
+/**
+ * snd_hda_power_up - Power-up the codec
+ * @codec: HD-audio codec
+ *
+ * Increment the power-up counter and power up the hardware really when
+ * not turned on yet.
+ */
+void snd_hda_power_up(struct hda_codec *codec)
+{
+	struct hda_bus *bus = codec->bus;
+
+	codec->power_count++;
+	if (codec->power_on || codec->power_transition)
+		return;
+
+	snd_hda_update_power_acct(codec);
+	codec->power_on = 1;
+	codec->power_jiffies = jiffies;
+	if (bus->ops.pm_notify)
+		bus->ops.pm_notify(bus);
+	hda_call_codec_resume(codec);
+	cancel_delayed_work(&codec->power_work);
+	codec->power_transition = 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_power_up);
+
+#define power_save(codec)	\
+	((codec)->bus->power_save ? *(codec)->bus->power_save : 0)
+
+/**
+ * snd_hda_power_down - Power-down the codec
+ * @codec: HD-audio codec
+ *
+ * Decrement the power-up counter and schedules the power-off work if
+ * the counter rearches to zero.
+ */
+void snd_hda_power_down(struct hda_codec *codec)
+{
+	--codec->power_count;
+	if (!codec->power_on || codec->power_count || codec->power_transition)
+		return;
+	if (power_save(codec)) {
+		codec->power_transition = 1; /* avoid reentrance */
+		queue_delayed_work(codec->bus->workq, &codec->power_work,
+				msecs_to_jiffies(power_save(codec) * 1000));
+	}
+}
+EXPORT_SYMBOL_HDA(snd_hda_power_down);
+
+/**
+ * snd_hda_check_amp_list_power - Check the amp list and update the power
+ * @codec: HD-audio codec
+ * @check: the object containing an AMP list and the status
+ * @nid: NID to check / update
+ *
+ * Check whether the given NID is in the amp list.  If it's in the list,
+ * check the current AMP status, and update the the power-status according
+ * to the mute status.
+ *
+ * This function is supposed to be set or called from the check_power_status
+ * patch ops.
+ */
+int snd_hda_check_amp_list_power(struct hda_codec *codec,
+				 struct hda_loopback_check *check,
+				 hda_nid_t nid)
+{
+	struct hda_amp_list *p;
+	int ch, v;
+
+	if (!check->amplist)
+		return 0;
+	for (p = check->amplist; p->nid; p++) {
+		if (p->nid == nid)
+			break;
+	}
+	if (!p->nid)
+		return 0; /* nothing changed */
+
+	for (p = check->amplist; p->nid; p++) {
+		for (ch = 0; ch < 2; ch++) {
+			v = snd_hda_codec_amp_read(codec, p->nid, ch, p->dir,
+						   p->idx);
+			if (!(v & HDA_AMP_MUTE) && v > 0) {
+				if (!check->power_on) {
+					check->power_on = 1;
+					snd_hda_power_up(codec);
+				}
+				return 1;
+			}
+		}
+	}
+	if (check->power_on) {
+		check->power_on = 0;
+		snd_hda_power_down(codec);
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_check_amp_list_power);
+#endif
+
+/*
+ * Channel mode helper
+ */
+
+/**
+ * snd_hda_ch_mode_info - Info callback helper for the channel mode enum
+ */
+int snd_hda_ch_mode_info(struct hda_codec *codec,
+			 struct snd_ctl_elem_info *uinfo,
+			 const struct hda_channel_mode *chmode,
+			 int num_chmodes)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = num_chmodes;
+	if (uinfo->value.enumerated.item >= num_chmodes)
+		uinfo->value.enumerated.item = num_chmodes - 1;
+	sprintf(uinfo->value.enumerated.name, "%dch",
+		chmode[uinfo->value.enumerated.item].channels);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_ch_mode_info);
+
+/**
+ * snd_hda_ch_mode_get - Get callback helper for the channel mode enum
+ */
+int snd_hda_ch_mode_get(struct hda_codec *codec,
+			struct snd_ctl_elem_value *ucontrol,
+			const struct hda_channel_mode *chmode,
+			int num_chmodes,
+			int max_channels)
+{
+	int i;
+
+	for (i = 0; i < num_chmodes; i++) {
+		if (max_channels == chmode[i].channels) {
+			ucontrol->value.enumerated.item[0] = i;
+			break;
+		}
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_ch_mode_get);
+
+/**
+ * snd_hda_ch_mode_put - Put callback helper for the channel mode enum
+ */
+int snd_hda_ch_mode_put(struct hda_codec *codec,
+			struct snd_ctl_elem_value *ucontrol,
+			const struct hda_channel_mode *chmode,
+			int num_chmodes,
+			int *max_channelsp)
+{
+	unsigned int mode;
+
+	mode = ucontrol->value.enumerated.item[0];
+	if (mode >= num_chmodes)
+		return -EINVAL;
+	if (*max_channelsp == chmode[mode].channels)
+		return 0;
+	/* change the current channel setting */
+	*max_channelsp = chmode[mode].channels;
+	if (chmode[mode].sequence)
+		snd_hda_sequence_write_cache(codec, chmode[mode].sequence);
+	return 1;
+}
+EXPORT_SYMBOL_HDA(snd_hda_ch_mode_put);
+
+/*
+ * input MUX helper
+ */
+
+/**
+ * snd_hda_input_mux_info_info - Info callback helper for the input-mux enum
+ */
+int snd_hda_input_mux_info(const struct hda_input_mux *imux,
+			   struct snd_ctl_elem_info *uinfo)
+{
+	unsigned int index;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = imux->num_items;
+	if (!imux->num_items)
+		return 0;
+	index = uinfo->value.enumerated.item;
+	if (index >= imux->num_items)
+		index = imux->num_items - 1;
+	strcpy(uinfo->value.enumerated.name, imux->items[index].label);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_input_mux_info);
+
+/**
+ * snd_hda_input_mux_info_put - Put callback helper for the input-mux enum
+ */
+int snd_hda_input_mux_put(struct hda_codec *codec,
+			  const struct hda_input_mux *imux,
+			  struct snd_ctl_elem_value *ucontrol,
+			  hda_nid_t nid,
+			  unsigned int *cur_val)
+{
+	unsigned int idx;
+
+	if (!imux->num_items)
+		return 0;
+	idx = ucontrol->value.enumerated.item[0];
+	if (idx >= imux->num_items)
+		idx = imux->num_items - 1;
+	if (*cur_val == idx)
+		return 0;
+	snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_CONNECT_SEL,
+				  imux->items[idx].index);
+	*cur_val = idx;
+	return 1;
+}
+EXPORT_SYMBOL_HDA(snd_hda_input_mux_put);
+
+
+/*
+ * Multi-channel / digital-out PCM helper functions
+ */
+
+/* setup SPDIF output stream */
+static void setup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid,
+				 unsigned int stream_tag, unsigned int format)
+{
+	/* turn off SPDIF once; otherwise the IEC958 bits won't be updated */
+	if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE))
+		set_dig_out_convert(codec, nid,
+				    codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff,
+				    -1);
+	snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format);
+	if (codec->slave_dig_outs) {
+		hda_nid_t *d;
+		for (d = codec->slave_dig_outs; *d; d++)
+			snd_hda_codec_setup_stream(codec, *d, stream_tag, 0,
+						   format);
+	}
+	/* turn on again (if needed) */
+	if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE))
+		set_dig_out_convert(codec, nid,
+				    codec->spdif_ctls & 0xff, -1);
+}
+
+static void cleanup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid)
+{
+	snd_hda_codec_cleanup_stream(codec, nid);
+	if (codec->slave_dig_outs) {
+		hda_nid_t *d;
+		for (d = codec->slave_dig_outs; *d; d++)
+			snd_hda_codec_cleanup_stream(codec, *d);
+	}
+}
+
+/**
+ * snd_hda_bus_reboot_notify - call the reboot notifier of each codec
+ * @bus: HD-audio bus
+ */
+void snd_hda_bus_reboot_notify(struct hda_bus *bus)
+{
+	struct hda_codec *codec;
+
+	if (!bus)
+		return;
+	list_for_each_entry(codec, &bus->codec_list, list) {
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+		if (!codec->power_on)
+			continue;
+#endif
+		if (codec->patch_ops.reboot_notify)
+			codec->patch_ops.reboot_notify(codec);
+	}
+}
+EXPORT_SYMBOL_HDA(snd_hda_bus_reboot_notify);
+
+/**
+ * snd_hda_multi_out_dig_open - open the digital out in the exclusive mode
+ */
+int snd_hda_multi_out_dig_open(struct hda_codec *codec,
+			       struct hda_multi_out *mout)
+{
+	mutex_lock(&codec->spdif_mutex);
+	if (mout->dig_out_used == HDA_DIG_ANALOG_DUP)
+		/* already opened as analog dup; reset it once */
+		cleanup_dig_out_stream(codec, mout->dig_out_nid);
+	mout->dig_out_used = HDA_DIG_EXCLUSIVE;
+	mutex_unlock(&codec->spdif_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_dig_open);
+
+/**
+ * snd_hda_multi_out_dig_prepare - prepare the digital out stream
+ */
+int snd_hda_multi_out_dig_prepare(struct hda_codec *codec,
+				  struct hda_multi_out *mout,
+				  unsigned int stream_tag,
+				  unsigned int format,
+				  struct snd_pcm_substream *substream)
+{
+	mutex_lock(&codec->spdif_mutex);
+	setup_dig_out_stream(codec, mout->dig_out_nid, stream_tag, format);
+	mutex_unlock(&codec->spdif_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_dig_prepare);
+
+/**
+ * snd_hda_multi_out_dig_cleanup - clean-up the digital out stream
+ */
+int snd_hda_multi_out_dig_cleanup(struct hda_codec *codec,
+				  struct hda_multi_out *mout)
+{
+	mutex_lock(&codec->spdif_mutex);
+	cleanup_dig_out_stream(codec, mout->dig_out_nid);
+	mutex_unlock(&codec->spdif_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_dig_cleanup);
+
+/**
+ * snd_hda_multi_out_dig_close - release the digital out stream
+ */
+int snd_hda_multi_out_dig_close(struct hda_codec *codec,
+				struct hda_multi_out *mout)
+{
+	mutex_lock(&codec->spdif_mutex);
+	mout->dig_out_used = 0;
+	mutex_unlock(&codec->spdif_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_dig_close);
+
+/**
+ * snd_hda_multi_out_analog_open - open analog outputs
+ *
+ * Open analog outputs and set up the hw-constraints.
+ * If the digital outputs can be opened as slave, open the digital
+ * outputs, too.
+ */
+int snd_hda_multi_out_analog_open(struct hda_codec *codec,
+				  struct hda_multi_out *mout,
+				  struct snd_pcm_substream *substream,
+				  struct hda_pcm_stream *hinfo)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	runtime->hw.channels_max = mout->max_channels;
+	if (mout->dig_out_nid) {
+		if (!mout->analog_rates) {
+			mout->analog_rates = hinfo->rates;
+			mout->analog_formats = hinfo->formats;
+			mout->analog_maxbps = hinfo->maxbps;
+		} else {
+			runtime->hw.rates = mout->analog_rates;
+			runtime->hw.formats = mout->analog_formats;
+			hinfo->maxbps = mout->analog_maxbps;
+		}
+		if (!mout->spdif_rates) {
+			snd_hda_query_supported_pcm(codec, mout->dig_out_nid,
+						    &mout->spdif_rates,
+						    &mout->spdif_formats,
+						    &mout->spdif_maxbps);
+		}
+		mutex_lock(&codec->spdif_mutex);
+		if (mout->share_spdif) {
+			if ((runtime->hw.rates & mout->spdif_rates) &&
+			    (runtime->hw.formats & mout->spdif_formats)) {
+				runtime->hw.rates &= mout->spdif_rates;
+				runtime->hw.formats &= mout->spdif_formats;
+				if (mout->spdif_maxbps < hinfo->maxbps)
+					hinfo->maxbps = mout->spdif_maxbps;
+			} else {
+				mout->share_spdif = 0;
+				/* FIXME: need notify? */
+			}
+		}
+		mutex_unlock(&codec->spdif_mutex);
+	}
+	return snd_pcm_hw_constraint_step(substream->runtime, 0,
+					  SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+}
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_analog_open);
+
+/**
+ * snd_hda_multi_out_analog_prepare - Preapre the analog outputs.
+ *
+ * Set up the i/o for analog out.
+ * When the digital out is available, copy the front out to digital out, too.
+ */
+int snd_hda_multi_out_analog_prepare(struct hda_codec *codec,
+				     struct hda_multi_out *mout,
+				     unsigned int stream_tag,
+				     unsigned int format,
+				     struct snd_pcm_substream *substream)
+{
+	hda_nid_t *nids = mout->dac_nids;
+	int chs = substream->runtime->channels;
+	int i;
+
+	mutex_lock(&codec->spdif_mutex);
+	if (mout->dig_out_nid && mout->share_spdif &&
+	    mout->dig_out_used != HDA_DIG_EXCLUSIVE) {
+		if (chs == 2 &&
+		    snd_hda_is_supported_format(codec, mout->dig_out_nid,
+						format) &&
+		    !(codec->spdif_status & IEC958_AES0_NONAUDIO)) {
+			mout->dig_out_used = HDA_DIG_ANALOG_DUP;
+			setup_dig_out_stream(codec, mout->dig_out_nid,
+					     stream_tag, format);
+		} else {
+			mout->dig_out_used = 0;
+			cleanup_dig_out_stream(codec, mout->dig_out_nid);
+		}
+	}
+	mutex_unlock(&codec->spdif_mutex);
+
+	/* front */
+	snd_hda_codec_setup_stream(codec, nids[HDA_FRONT], stream_tag,
+				   0, format);
+	if (!mout->no_share_stream &&
+	    mout->hp_nid && mout->hp_nid != nids[HDA_FRONT])
+		/* headphone out will just decode front left/right (stereo) */
+		snd_hda_codec_setup_stream(codec, mout->hp_nid, stream_tag,
+					   0, format);
+	/* extra outputs copied from front */
+	for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++)
+		if (!mout->no_share_stream && mout->extra_out_nid[i])
+			snd_hda_codec_setup_stream(codec,
+						   mout->extra_out_nid[i],
+						   stream_tag, 0, format);
+
+	/* surrounds */
+	for (i = 1; i < mout->num_dacs; i++) {
+		if (chs >= (i + 1) * 2) /* independent out */
+			snd_hda_codec_setup_stream(codec, nids[i], stream_tag,
+						   i * 2, format);
+		else if (!mout->no_share_stream) /* copy front */
+			snd_hda_codec_setup_stream(codec, nids[i], stream_tag,
+						   0, format);
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_analog_prepare);
+
+/**
+ * snd_hda_multi_out_analog_cleanup - clean up the setting for analog out
+ */
+int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec,
+				     struct hda_multi_out *mout)
+{
+	hda_nid_t *nids = mout->dac_nids;
+	int i;
+
+	for (i = 0; i < mout->num_dacs; i++)
+		snd_hda_codec_cleanup_stream(codec, nids[i]);
+	if (mout->hp_nid)
+		snd_hda_codec_cleanup_stream(codec, mout->hp_nid);
+	for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++)
+		if (mout->extra_out_nid[i])
+			snd_hda_codec_cleanup_stream(codec,
+						     mout->extra_out_nid[i]);
+	mutex_lock(&codec->spdif_mutex);
+	if (mout->dig_out_nid && mout->dig_out_used == HDA_DIG_ANALOG_DUP) {
+		cleanup_dig_out_stream(codec, mout->dig_out_nid);
+		mout->dig_out_used = 0;
+	}
+	mutex_unlock(&codec->spdif_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_analog_cleanup);
+
+/*
+ * Helper for automatic pin configuration
+ */
+
+static int is_in_nid_list(hda_nid_t nid, hda_nid_t *list)
+{
+	for (; *list; list++)
+		if (*list == nid)
+			return 1;
+	return 0;
+}
+
+
+/*
+ * Sort an associated group of pins according to their sequence numbers.
+ */
+static void sort_pins_by_sequence(hda_nid_t *pins, short *sequences,
+				  int num_pins)
+{
+	int i, j;
+	short seq;
+	hda_nid_t nid;
+
+	for (i = 0; i < num_pins; i++) {
+		for (j = i + 1; j < num_pins; j++) {
+			if (sequences[i] > sequences[j]) {
+				seq = sequences[i];
+				sequences[i] = sequences[j];
+				sequences[j] = seq;
+				nid = pins[i];
+				pins[i] = pins[j];
+				pins[j] = nid;
+			}
+		}
+	}
+}
+
+
+/* add the found input-pin to the cfg->inputs[] table */
+static void add_auto_cfg_input_pin(struct auto_pin_cfg *cfg, hda_nid_t nid,
+				   int type)
+{
+	if (cfg->num_inputs < AUTO_CFG_MAX_INS) {
+		cfg->inputs[cfg->num_inputs].pin = nid;
+		cfg->inputs[cfg->num_inputs].type = type;
+		cfg->num_inputs++;
+	}
+}
+
+/* sort inputs in the order of AUTO_PIN_* type */
+static void sort_autocfg_input_pins(struct auto_pin_cfg *cfg)
+{
+	int i, j;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		for (j = i + 1; j < cfg->num_inputs; j++) {
+			if (cfg->inputs[i].type > cfg->inputs[j].type) {
+				struct auto_pin_cfg_item tmp;
+				tmp = cfg->inputs[i];
+				cfg->inputs[i] = cfg->inputs[j];
+				cfg->inputs[j] = tmp;
+			}
+		}
+	}
+}
+
+/*
+ * Parse all pin widgets and store the useful pin nids to cfg
+ *
+ * The number of line-outs or any primary output is stored in line_outs,
+ * and the corresponding output pins are assigned to line_out_pins[],
+ * in the order of front, rear, CLFE, side, ...
+ *
+ * If more extra outputs (speaker and headphone) are found, the pins are
+ * assisnged to hp_pins[] and speaker_pins[], respectively.  If no line-out jack
+ * is detected, one of speaker of HP pins is assigned as the primary
+ * output, i.e. to line_out_pins[0].  So, line_outs is always positive
+ * if any analog output exists.
+ *
+ * The analog input pins are assigned to inputs array.
+ * The digital input/output pins are assigned to dig_in_pin and dig_out_pin,
+ * respectively.
+ */
+int snd_hda_parse_pin_def_config(struct hda_codec *codec,
+				 struct auto_pin_cfg *cfg,
+				 hda_nid_t *ignore_nids)
+{
+	hda_nid_t nid, end_nid;
+	short seq, assoc_line_out, assoc_speaker;
+	short sequences_line_out[ARRAY_SIZE(cfg->line_out_pins)];
+	short sequences_speaker[ARRAY_SIZE(cfg->speaker_pins)];
+	short sequences_hp[ARRAY_SIZE(cfg->hp_pins)];
+	int i;
+
+	memset(cfg, 0, sizeof(*cfg));
+
+	memset(sequences_line_out, 0, sizeof(sequences_line_out));
+	memset(sequences_speaker, 0, sizeof(sequences_speaker));
+	memset(sequences_hp, 0, sizeof(sequences_hp));
+	assoc_line_out = assoc_speaker = 0;
+
+	end_nid = codec->start_nid + codec->num_nodes;
+	for (nid = codec->start_nid; nid < end_nid; nid++) {
+		unsigned int wid_caps = get_wcaps(codec, nid);
+		unsigned int wid_type = get_wcaps_type(wid_caps);
+		unsigned int def_conf;
+		short assoc, loc;
+
+		/* read all default configuration for pin complex */
+		if (wid_type != AC_WID_PIN)
+			continue;
+		/* ignore the given nids (e.g. pc-beep returns error) */
+		if (ignore_nids && is_in_nid_list(nid, ignore_nids))
+			continue;
+
+		def_conf = snd_hda_codec_get_pincfg(codec, nid);
+		if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE)
+			continue;
+		loc = get_defcfg_location(def_conf);
+		switch (get_defcfg_device(def_conf)) {
+		case AC_JACK_LINE_OUT:
+			seq = get_defcfg_sequence(def_conf);
+			assoc = get_defcfg_association(def_conf);
+
+			if (!(wid_caps & AC_WCAP_STEREO))
+				if (!cfg->mono_out_pin)
+					cfg->mono_out_pin = nid;
+			if (!assoc)
+				continue;
+			if (!assoc_line_out)
+				assoc_line_out = assoc;
+			else if (assoc_line_out != assoc)
+				continue;
+			if (cfg->line_outs >= ARRAY_SIZE(cfg->line_out_pins))
+				continue;
+			cfg->line_out_pins[cfg->line_outs] = nid;
+			sequences_line_out[cfg->line_outs] = seq;
+			cfg->line_outs++;
+			break;
+		case AC_JACK_SPEAKER:
+			seq = get_defcfg_sequence(def_conf);
+			assoc = get_defcfg_association(def_conf);
+			if (!assoc)
+				continue;
+			if (!assoc_speaker)
+				assoc_speaker = assoc;
+			else if (assoc_speaker != assoc)
+				continue;
+			if (cfg->speaker_outs >= ARRAY_SIZE(cfg->speaker_pins))
+				continue;
+			cfg->speaker_pins[cfg->speaker_outs] = nid;
+			sequences_speaker[cfg->speaker_outs] = seq;
+			cfg->speaker_outs++;
+			break;
+		case AC_JACK_HP_OUT:
+			seq = get_defcfg_sequence(def_conf);
+			assoc = get_defcfg_association(def_conf);
+			if (cfg->hp_outs >= ARRAY_SIZE(cfg->hp_pins))
+				continue;
+			cfg->hp_pins[cfg->hp_outs] = nid;
+			sequences_hp[cfg->hp_outs] = (assoc << 4) | seq;
+			cfg->hp_outs++;
+			break;
+		case AC_JACK_MIC_IN:
+			add_auto_cfg_input_pin(cfg, nid, AUTO_PIN_MIC);
+			break;
+		case AC_JACK_LINE_IN:
+			add_auto_cfg_input_pin(cfg, nid, AUTO_PIN_LINE_IN);
+			break;
+		case AC_JACK_CD:
+			add_auto_cfg_input_pin(cfg, nid, AUTO_PIN_CD);
+			break;
+		case AC_JACK_AUX:
+			add_auto_cfg_input_pin(cfg, nid, AUTO_PIN_AUX);
+			break;
+		case AC_JACK_SPDIF_OUT:
+		case AC_JACK_DIG_OTHER_OUT:
+			if (cfg->dig_outs >= ARRAY_SIZE(cfg->dig_out_pins))
+				continue;
+			cfg->dig_out_pins[cfg->dig_outs] = nid;
+			cfg->dig_out_type[cfg->dig_outs] =
+				(loc == AC_JACK_LOC_HDMI) ?
+				HDA_PCM_TYPE_HDMI : HDA_PCM_TYPE_SPDIF;
+			cfg->dig_outs++;
+			break;
+		case AC_JACK_SPDIF_IN:
+		case AC_JACK_DIG_OTHER_IN:
+			cfg->dig_in_pin = nid;
+			if (loc == AC_JACK_LOC_HDMI)
+				cfg->dig_in_type = HDA_PCM_TYPE_HDMI;
+			else
+				cfg->dig_in_type = HDA_PCM_TYPE_SPDIF;
+			break;
+		}
+	}
+
+	/* FIX-UP:
+	 * If no line-out is defined but multiple HPs are found,
+	 * some of them might be the real line-outs.
+	 */
+	if (!cfg->line_outs && cfg->hp_outs > 1) {
+		int i = 0;
+		while (i < cfg->hp_outs) {
+			/* The real HPs should have the sequence 0x0f */
+			if ((sequences_hp[i] & 0x0f) == 0x0f) {
+				i++;
+				continue;
+			}
+			/* Move it to the line-out table */
+			cfg->line_out_pins[cfg->line_outs] = cfg->hp_pins[i];
+			sequences_line_out[cfg->line_outs] = sequences_hp[i];
+			cfg->line_outs++;
+			cfg->hp_outs--;
+			memmove(cfg->hp_pins + i, cfg->hp_pins + i + 1,
+				sizeof(cfg->hp_pins[0]) * (cfg->hp_outs - i));
+			memmove(sequences_hp + i, sequences_hp + i + 1,
+				sizeof(sequences_hp[0]) * (cfg->hp_outs - i));
+		}
+		memset(cfg->hp_pins + cfg->hp_outs, 0,
+		       sizeof(hda_nid_t) * (AUTO_CFG_MAX_OUTS - cfg->hp_outs));
+	}
+
+	/* sort by sequence */
+	sort_pins_by_sequence(cfg->line_out_pins, sequences_line_out,
+			      cfg->line_outs);
+	sort_pins_by_sequence(cfg->speaker_pins, sequences_speaker,
+			      cfg->speaker_outs);
+	sort_pins_by_sequence(cfg->hp_pins, sequences_hp,
+			      cfg->hp_outs);
+
+	/*
+	 * FIX-UP: if no line-outs are detected, try to use speaker or HP pin
+	 * as a primary output
+	 */
+	if (!cfg->line_outs) {
+		if (cfg->speaker_outs) {
+			cfg->line_outs = cfg->speaker_outs;
+			memcpy(cfg->line_out_pins, cfg->speaker_pins,
+			       sizeof(cfg->speaker_pins));
+			cfg->speaker_outs = 0;
+			memset(cfg->speaker_pins, 0, sizeof(cfg->speaker_pins));
+			cfg->line_out_type = AUTO_PIN_SPEAKER_OUT;
+		} else if (cfg->hp_outs) {
+			cfg->line_outs = cfg->hp_outs;
+			memcpy(cfg->line_out_pins, cfg->hp_pins,
+			       sizeof(cfg->hp_pins));
+			cfg->hp_outs = 0;
+			memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins));
+			cfg->line_out_type = AUTO_PIN_HP_OUT;
+		}
+	}
+
+	/* Reorder the surround channels
+	 * ALSA sequence is front/surr/clfe/side
+	 * HDA sequence is:
+	 *    4-ch: front/surr  =>  OK as it is
+	 *    6-ch: front/clfe/surr
+	 *    8-ch: front/clfe/rear/side|fc
+	 */
+	switch (cfg->line_outs) {
+	case 3:
+	case 4:
+		nid = cfg->line_out_pins[1];
+		cfg->line_out_pins[1] = cfg->line_out_pins[2];
+		cfg->line_out_pins[2] = nid;
+		break;
+	}
+
+	sort_autocfg_input_pins(cfg);
+
+	/*
+	 * debug prints of the parsed results
+	 */
+	snd_printd("autoconfig: line_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n",
+		   cfg->line_outs, cfg->line_out_pins[0], cfg->line_out_pins[1],
+		   cfg->line_out_pins[2], cfg->line_out_pins[3],
+		   cfg->line_out_pins[4]);
+	snd_printd("   speaker_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n",
+		   cfg->speaker_outs, cfg->speaker_pins[0],
+		   cfg->speaker_pins[1], cfg->speaker_pins[2],
+		   cfg->speaker_pins[3], cfg->speaker_pins[4]);
+	snd_printd("   hp_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n",
+		   cfg->hp_outs, cfg->hp_pins[0],
+		   cfg->hp_pins[1], cfg->hp_pins[2],
+		   cfg->hp_pins[3], cfg->hp_pins[4]);
+	snd_printd("   mono: mono_out=0x%x\n", cfg->mono_out_pin);
+	if (cfg->dig_outs)
+		snd_printd("   dig-out=0x%x/0x%x\n",
+			   cfg->dig_out_pins[0], cfg->dig_out_pins[1]);
+	snd_printd("   inputs:");
+	for (i = 0; i < cfg->num_inputs; i++) {
+		snd_printdd(" %s=0x%x",
+			    hda_get_autocfg_input_label(codec, cfg, i),
+			    cfg->inputs[i].pin);
+	}
+	snd_printd("\n");
+	if (cfg->dig_in_pin)
+		snd_printd("   dig-in=0x%x\n", cfg->dig_in_pin);
+
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_parse_pin_def_config);
+
+int snd_hda_get_input_pin_attr(unsigned int def_conf)
+{
+	unsigned int loc = get_defcfg_location(def_conf);
+	unsigned int conn = get_defcfg_connect(def_conf);
+	if (conn == AC_JACK_PORT_NONE)
+		return INPUT_PIN_ATTR_UNUSED;
+	/* Windows may claim the internal mic to be BOTH, too */
+	if (conn == AC_JACK_PORT_FIXED || conn == AC_JACK_PORT_BOTH)
+		return INPUT_PIN_ATTR_INT;
+	if ((loc & 0x30) == AC_JACK_LOC_INTERNAL)
+		return INPUT_PIN_ATTR_INT;
+	if ((loc & 0x30) == AC_JACK_LOC_SEPARATE)
+		return INPUT_PIN_ATTR_DOCK;
+	if (loc == AC_JACK_LOC_REAR)
+		return INPUT_PIN_ATTR_REAR;
+	if (loc == AC_JACK_LOC_FRONT)
+		return INPUT_PIN_ATTR_FRONT;
+	return INPUT_PIN_ATTR_NORMAL;
+}
+EXPORT_SYMBOL_HDA(snd_hda_get_input_pin_attr);
+
+/**
+ * hda_get_input_pin_label - Give a label for the given input pin
+ *
+ * When check_location is true, the function checks the pin location
+ * for mic and line-in pins, and set an appropriate prefix like "Front",
+ * "Rear", "Internal".
+ */
+
+const char *hda_get_input_pin_label(struct hda_codec *codec, hda_nid_t pin,
+					int check_location)
+{
+	unsigned int def_conf;
+	static const char *mic_names[] = {
+		"Internal Mic", "Dock Mic", "Mic", "Front Mic", "Rear Mic",
+	};
+	int attr;
+
+	def_conf = snd_hda_codec_get_pincfg(codec, pin);
+
+	switch (get_defcfg_device(def_conf)) {
+	case AC_JACK_MIC_IN:
+		if (!check_location)
+			return "Mic";
+		attr = snd_hda_get_input_pin_attr(def_conf);
+		if (!attr)
+			return "None";
+		return mic_names[attr - 1];
+	case AC_JACK_LINE_IN:
+		if (!check_location)
+			return "Line";
+		attr = snd_hda_get_input_pin_attr(def_conf);
+		if (!attr)
+			return "None";
+		if (attr == INPUT_PIN_ATTR_DOCK)
+			return "Dock Line";
+		return "Line";
+	case AC_JACK_AUX:
+		return "Aux";
+	case AC_JACK_CD:
+		return "CD";
+	case AC_JACK_SPDIF_IN:
+		return "SPDIF In";
+	case AC_JACK_DIG_OTHER_IN:
+		return "Digital In";
+	default:
+		return "Misc";
+	}
+}
+EXPORT_SYMBOL_HDA(hda_get_input_pin_label);
+
+/* Check whether the location prefix needs to be added to the label.
+ * If all mic-jacks are in the same location (e.g. rear panel), we don't
+ * have to put "Front" prefix to each label.  In such a case, returns false.
+ */
+static int check_mic_location_need(struct hda_codec *codec,
+				   const struct auto_pin_cfg *cfg,
+				   int input)
+{
+	unsigned int defc;
+	int i, attr, attr2;
+
+	defc = snd_hda_codec_get_pincfg(codec, cfg->inputs[input].pin);
+	attr = snd_hda_get_input_pin_attr(defc);
+	/* for internal or docking mics, we need locations */
+	if (attr <= INPUT_PIN_ATTR_NORMAL)
+		return 1;
+
+	attr = 0;
+	for (i = 0; i < cfg->num_inputs; i++) {
+		defc = snd_hda_codec_get_pincfg(codec, cfg->inputs[i].pin);
+		attr2 = snd_hda_get_input_pin_attr(defc);
+		if (attr2 >= INPUT_PIN_ATTR_NORMAL) {
+			if (attr && attr != attr2)
+				return 1; /* different locations found */
+			attr = attr2;
+		}
+	}
+	return 0;
+}
+
+/**
+ * hda_get_autocfg_input_label - Get a label for the given input
+ *
+ * Get a label for the given input pin defined by the autocfg item.
+ * Unlike hda_get_input_pin_label(), this function checks all inputs
+ * defined in autocfg and avoids the redundant mic/line prefix as much as
+ * possible.
+ */
+const char *hda_get_autocfg_input_label(struct hda_codec *codec,
+					const struct auto_pin_cfg *cfg,
+					int input)
+{
+	int type = cfg->inputs[input].type;
+	int has_multiple_pins = 0;
+
+	if ((input > 0 && cfg->inputs[input - 1].type == type) ||
+	    (input < cfg->num_inputs - 1 && cfg->inputs[input + 1].type == type))
+		has_multiple_pins = 1;
+	if (has_multiple_pins && type == AUTO_PIN_MIC)
+		has_multiple_pins &= check_mic_location_need(codec, cfg, input);
+	return hda_get_input_pin_label(codec, cfg->inputs[input].pin,
+				       has_multiple_pins);
+}
+EXPORT_SYMBOL_HDA(hda_get_autocfg_input_label);
+
+/**
+ * snd_hda_add_imux_item - Add an item to input_mux
+ *
+ * When the same label is used already in the existing items, the number
+ * suffix is appended to the label.  This label index number is stored
+ * to type_idx when non-NULL pointer is given.
+ */
+int snd_hda_add_imux_item(struct hda_input_mux *imux, const char *label,
+			  int index, int *type_idx)
+{
+	int i, label_idx = 0;
+	if (imux->num_items >= HDA_MAX_NUM_INPUTS) {
+		snd_printd(KERN_ERR "hda_codec: Too many imux items!\n");
+		return -EINVAL;
+	}
+	for (i = 0; i < imux->num_items; i++) {
+		if (!strncmp(label, imux->items[i].label, strlen(label)))
+			label_idx++;
+	}
+	if (type_idx)
+		*type_idx = label_idx;
+	if (label_idx > 0)
+		snprintf(imux->items[imux->num_items].label,
+			 sizeof(imux->items[imux->num_items].label),
+			 "%s %d", label, label_idx);
+	else
+		strlcpy(imux->items[imux->num_items].label, label,
+			sizeof(imux->items[imux->num_items].label));
+	imux->items[imux->num_items].index = index;
+	imux->num_items++;
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_add_imux_item);
+
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+
+/**
+ * snd_hda_suspend - suspend the codecs
+ * @bus: the HDA bus
+ *
+ * Returns 0 if successful.
+ */
+int snd_hda_suspend(struct hda_bus *bus)
+{
+	struct hda_codec *codec;
+
+	list_for_each_entry(codec, &bus->codec_list, list) {
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+		if (!codec->power_on)
+			continue;
+#endif
+		hda_call_codec_suspend(codec);
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_suspend);
+
+/**
+ * snd_hda_resume - resume the codecs
+ * @bus: the HDA bus
+ *
+ * Returns 0 if successful.
+ *
+ * This fucntion is defined only when POWER_SAVE isn't set.
+ * In the power-save mode, the codec is resumed dynamically.
+ */
+int snd_hda_resume(struct hda_bus *bus)
+{
+	struct hda_codec *codec;
+
+	list_for_each_entry(codec, &bus->codec_list, list) {
+		if (snd_hda_codec_needs_resume(codec))
+			hda_call_codec_resume(codec);
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_resume);
+#endif /* CONFIG_PM */
+
+/*
+ * generic arrays
+ */
+
+/**
+ * snd_array_new - get a new element from the given array
+ * @array: the array object
+ *
+ * Get a new element from the given array.  If it exceeds the
+ * pre-allocated array size, re-allocate the array.
+ *
+ * Returns NULL if allocation failed.
+ */
+void *snd_array_new(struct snd_array *array)
+{
+	if (array->used >= array->alloced) {
+		int num = array->alloced + array->alloc_align;
+		void *nlist;
+		if (snd_BUG_ON(num >= 4096))
+			return NULL;
+		nlist = kcalloc(num + 1, array->elem_size, GFP_KERNEL);
+		if (!nlist)
+			return NULL;
+		if (array->list) {
+			memcpy(nlist, array->list,
+			       array->elem_size * array->alloced);
+			kfree(array->list);
+		}
+		array->list = nlist;
+		array->alloced = num;
+	}
+	return snd_array_elem(array, array->used++);
+}
+EXPORT_SYMBOL_HDA(snd_array_new);
+
+/**
+ * snd_array_free - free the given array elements
+ * @array: the array object
+ */
+void snd_array_free(struct snd_array *array)
+{
+	kfree(array->list);
+	array->used = 0;
+	array->alloced = 0;
+	array->list = NULL;
+}
+EXPORT_SYMBOL_HDA(snd_array_free);
+
+/**
+ * snd_print_pcm_rates - Print the supported PCM rates to the string buffer
+ * @pcm: PCM caps bits
+ * @buf: the string buffer to write
+ * @buflen: the max buffer length
+ *
+ * used by hda_proc.c and hda_eld.c
+ */
+void snd_print_pcm_rates(int pcm, char *buf, int buflen)
+{
+	static unsigned int rates[] = {
+		8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200,
+		96000, 176400, 192000, 384000
+	};
+	int i, j;
+
+	for (i = 0, j = 0; i < ARRAY_SIZE(rates); i++)
+		if (pcm & (1 << i))
+			j += snprintf(buf + j, buflen - j,  " %d", rates[i]);
+
+	buf[j] = '\0'; /* necessary when j == 0 */
+}
+EXPORT_SYMBOL_HDA(snd_print_pcm_rates);
+
+/**
+ * snd_print_pcm_bits - Print the supported PCM fmt bits to the string buffer
+ * @pcm: PCM caps bits
+ * @buf: the string buffer to write
+ * @buflen: the max buffer length
+ *
+ * used by hda_proc.c and hda_eld.c
+ */
+void snd_print_pcm_bits(int pcm, char *buf, int buflen)
+{
+	static unsigned int bits[] = { 8, 16, 20, 24, 32 };
+	int i, j;
+
+	for (i = 0, j = 0; i < ARRAY_SIZE(bits); i++)
+		if (pcm & (AC_SUPPCM_BITS_8 << i))
+			j += snprintf(buf + j, buflen - j,  " %d", bits[i]);
+
+	buf[j] = '\0'; /* necessary when j == 0 */
+}
+EXPORT_SYMBOL_HDA(snd_print_pcm_bits);
+
+MODULE_DESCRIPTION("HDA codec core");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/pci/hda/hda_codec.h b/linux/sound/pci/hda/hda_codec.h
new file mode 100644
index 0000000..fdf8d44
--- /dev/null
+++ b/linux/sound/pci/hda/hda_codec.h
@@ -0,0 +1,1048 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Copyright (c) 2004 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 __SOUND_HDA_CODEC_H
+#define __SOUND_HDA_CODEC_H
+
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/hwdep.h>
+
+#if defined(CONFIG_PM) || defined(CONFIG_SND_HDA_POWER_SAVE)
+#define SND_HDA_NEEDS_RESUME	/* resume control code is required */
+#endif
+
+/*
+ * nodes
+ */
+#define	AC_NODE_ROOT		0x00
+
+/*
+ * function group types
+ */
+enum {
+	AC_GRP_AUDIO_FUNCTION = 0x01,
+	AC_GRP_MODEM_FUNCTION = 0x02,
+};
+	
+/*
+ * widget types
+ */
+enum {
+	AC_WID_AUD_OUT,		/* Audio Out */
+	AC_WID_AUD_IN,		/* Audio In */
+	AC_WID_AUD_MIX,		/* Audio Mixer */
+	AC_WID_AUD_SEL,		/* Audio Selector */
+	AC_WID_PIN,		/* Pin Complex */
+	AC_WID_POWER,		/* Power */
+	AC_WID_VOL_KNB,		/* Volume Knob */
+	AC_WID_BEEP,		/* Beep Generator */
+	AC_WID_VENDOR = 0x0f	/* Vendor specific */
+};
+
+/*
+ * GET verbs
+ */
+#define AC_VERB_GET_STREAM_FORMAT		0x0a00
+#define AC_VERB_GET_AMP_GAIN_MUTE		0x0b00
+#define AC_VERB_GET_PROC_COEF			0x0c00
+#define AC_VERB_GET_COEF_INDEX			0x0d00
+#define AC_VERB_PARAMETERS			0x0f00
+#define AC_VERB_GET_CONNECT_SEL			0x0f01
+#define AC_VERB_GET_CONNECT_LIST		0x0f02
+#define AC_VERB_GET_PROC_STATE			0x0f03
+#define AC_VERB_GET_SDI_SELECT			0x0f04
+#define AC_VERB_GET_POWER_STATE			0x0f05
+#define AC_VERB_GET_CONV			0x0f06
+#define AC_VERB_GET_PIN_WIDGET_CONTROL		0x0f07
+#define AC_VERB_GET_UNSOLICITED_RESPONSE	0x0f08
+#define AC_VERB_GET_PIN_SENSE			0x0f09
+#define AC_VERB_GET_BEEP_CONTROL		0x0f0a
+#define AC_VERB_GET_EAPD_BTLENABLE		0x0f0c
+#define AC_VERB_GET_DIGI_CONVERT_1		0x0f0d
+#define AC_VERB_GET_DIGI_CONVERT_2		0x0f0e /* unused */
+#define AC_VERB_GET_VOLUME_KNOB_CONTROL		0x0f0f
+/* f10-f1a: GPIO */
+#define AC_VERB_GET_GPIO_DATA			0x0f15
+#define AC_VERB_GET_GPIO_MASK			0x0f16
+#define AC_VERB_GET_GPIO_DIRECTION		0x0f17
+#define AC_VERB_GET_GPIO_WAKE_MASK		0x0f18
+#define AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK	0x0f19
+#define AC_VERB_GET_GPIO_STICKY_MASK		0x0f1a
+#define AC_VERB_GET_CONFIG_DEFAULT		0x0f1c
+/* f20: AFG/MFG */
+#define AC_VERB_GET_SUBSYSTEM_ID		0x0f20
+#define AC_VERB_GET_CVT_CHAN_COUNT		0x0f2d
+#define AC_VERB_GET_HDMI_DIP_SIZE		0x0f2e
+#define AC_VERB_GET_HDMI_ELDD			0x0f2f
+#define AC_VERB_GET_HDMI_DIP_INDEX		0x0f30
+#define AC_VERB_GET_HDMI_DIP_DATA		0x0f31
+#define AC_VERB_GET_HDMI_DIP_XMIT		0x0f32
+#define AC_VERB_GET_HDMI_CP_CTRL		0x0f33
+#define AC_VERB_GET_HDMI_CHAN_SLOT		0x0f34
+
+/*
+ * SET verbs
+ */
+#define AC_VERB_SET_STREAM_FORMAT		0x200
+#define AC_VERB_SET_AMP_GAIN_MUTE		0x300
+#define AC_VERB_SET_PROC_COEF			0x400
+#define AC_VERB_SET_COEF_INDEX			0x500
+#define AC_VERB_SET_CONNECT_SEL			0x701
+#define AC_VERB_SET_PROC_STATE			0x703
+#define AC_VERB_SET_SDI_SELECT			0x704
+#define AC_VERB_SET_POWER_STATE			0x705
+#define AC_VERB_SET_CHANNEL_STREAMID		0x706
+#define AC_VERB_SET_PIN_WIDGET_CONTROL		0x707
+#define AC_VERB_SET_UNSOLICITED_ENABLE		0x708
+#define AC_VERB_SET_PIN_SENSE			0x709
+#define AC_VERB_SET_BEEP_CONTROL		0x70a
+#define AC_VERB_SET_EAPD_BTLENABLE		0x70c
+#define AC_VERB_SET_DIGI_CONVERT_1		0x70d
+#define AC_VERB_SET_DIGI_CONVERT_2		0x70e
+#define AC_VERB_SET_VOLUME_KNOB_CONTROL		0x70f
+#define AC_VERB_SET_GPIO_DATA			0x715
+#define AC_VERB_SET_GPIO_MASK			0x716
+#define AC_VERB_SET_GPIO_DIRECTION		0x717
+#define AC_VERB_SET_GPIO_WAKE_MASK		0x718
+#define AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK	0x719
+#define AC_VERB_SET_GPIO_STICKY_MASK		0x71a
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_0	0x71c
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_1	0x71d
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_2	0x71e
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_3	0x71f
+#define AC_VERB_SET_EAPD				0x788
+#define AC_VERB_SET_CODEC_RESET			0x7ff
+#define AC_VERB_SET_CVT_CHAN_COUNT		0x72d
+#define AC_VERB_SET_HDMI_DIP_INDEX		0x730
+#define AC_VERB_SET_HDMI_DIP_DATA		0x731
+#define AC_VERB_SET_HDMI_DIP_XMIT		0x732
+#define AC_VERB_SET_HDMI_CP_CTRL		0x733
+#define AC_VERB_SET_HDMI_CHAN_SLOT		0x734
+
+/*
+ * Parameter IDs
+ */
+#define AC_PAR_VENDOR_ID		0x00
+#define AC_PAR_SUBSYSTEM_ID		0x01
+#define AC_PAR_REV_ID			0x02
+#define AC_PAR_NODE_COUNT		0x04
+#define AC_PAR_FUNCTION_TYPE		0x05
+#define AC_PAR_AUDIO_FG_CAP		0x08
+#define AC_PAR_AUDIO_WIDGET_CAP		0x09
+#define AC_PAR_PCM			0x0a
+#define AC_PAR_STREAM			0x0b
+#define AC_PAR_PIN_CAP			0x0c
+#define AC_PAR_AMP_IN_CAP		0x0d
+#define AC_PAR_CONNLIST_LEN		0x0e
+#define AC_PAR_POWER_STATE		0x0f
+#define AC_PAR_PROC_CAP			0x10
+#define AC_PAR_GPIO_CAP			0x11
+#define AC_PAR_AMP_OUT_CAP		0x12
+#define AC_PAR_VOL_KNB_CAP		0x13
+#define AC_PAR_HDMI_LPCM_CAP		0x20
+
+/*
+ * AC_VERB_PARAMETERS results (32bit)
+ */
+
+/* Function Group Type */
+#define AC_FGT_TYPE			(0xff<<0)
+#define AC_FGT_TYPE_SHIFT		0
+#define AC_FGT_UNSOL_CAP		(1<<8)
+
+/* Audio Function Group Capabilities */
+#define AC_AFG_OUT_DELAY		(0xf<<0)
+#define AC_AFG_IN_DELAY			(0xf<<8)
+#define AC_AFG_BEEP_GEN			(1<<16)
+
+/* Audio Widget Capabilities */
+#define AC_WCAP_STEREO			(1<<0)	/* stereo I/O */
+#define AC_WCAP_IN_AMP			(1<<1)	/* AMP-in present */
+#define AC_WCAP_OUT_AMP			(1<<2)	/* AMP-out present */
+#define AC_WCAP_AMP_OVRD		(1<<3)	/* AMP-parameter override */
+#define AC_WCAP_FORMAT_OVRD		(1<<4)	/* format override */
+#define AC_WCAP_STRIPE			(1<<5)	/* stripe */
+#define AC_WCAP_PROC_WID		(1<<6)	/* Proc Widget */
+#define AC_WCAP_UNSOL_CAP		(1<<7)	/* Unsol capable */
+#define AC_WCAP_CONN_LIST		(1<<8)	/* connection list */
+#define AC_WCAP_DIGITAL			(1<<9)	/* digital I/O */
+#define AC_WCAP_POWER			(1<<10)	/* power control */
+#define AC_WCAP_LR_SWAP			(1<<11)	/* L/R swap */
+#define AC_WCAP_CP_CAPS			(1<<12) /* content protection */
+#define AC_WCAP_CHAN_CNT_EXT		(7<<13)	/* channel count ext */
+#define AC_WCAP_DELAY			(0xf<<16)
+#define AC_WCAP_DELAY_SHIFT		16
+#define AC_WCAP_TYPE			(0xf<<20)
+#define AC_WCAP_TYPE_SHIFT		20
+
+/* supported PCM rates and bits */
+#define AC_SUPPCM_RATES			(0xfff << 0)
+#define AC_SUPPCM_BITS_8		(1<<16)
+#define AC_SUPPCM_BITS_16		(1<<17)
+#define AC_SUPPCM_BITS_20		(1<<18)
+#define AC_SUPPCM_BITS_24		(1<<19)
+#define AC_SUPPCM_BITS_32		(1<<20)
+
+/* supported PCM stream format */
+#define AC_SUPFMT_PCM			(1<<0)
+#define AC_SUPFMT_FLOAT32		(1<<1)
+#define AC_SUPFMT_AC3			(1<<2)
+
+/* GP I/O count */
+#define AC_GPIO_IO_COUNT		(0xff<<0)
+#define AC_GPIO_O_COUNT			(0xff<<8)
+#define AC_GPIO_O_COUNT_SHIFT		8
+#define AC_GPIO_I_COUNT			(0xff<<16)
+#define AC_GPIO_I_COUNT_SHIFT		16
+#define AC_GPIO_UNSOLICITED		(1<<30)
+#define AC_GPIO_WAKE			(1<<31)
+
+/* Converter stream, channel */
+#define AC_CONV_CHANNEL			(0xf<<0)
+#define AC_CONV_STREAM			(0xf<<4)
+#define AC_CONV_STREAM_SHIFT		4
+
+/* Input converter SDI select */
+#define AC_SDI_SELECT			(0xf<<0)
+
+/* stream format id */
+#define AC_FMT_CHAN_SHIFT		0
+#define AC_FMT_CHAN_MASK		(0x0f << 0)
+#define AC_FMT_BITS_SHIFT		4
+#define AC_FMT_BITS_MASK		(7 << 4)
+#define AC_FMT_BITS_8			(0 << 4)
+#define AC_FMT_BITS_16			(1 << 4)
+#define AC_FMT_BITS_20			(2 << 4)
+#define AC_FMT_BITS_24			(3 << 4)
+#define AC_FMT_BITS_32			(4 << 4)
+#define AC_FMT_DIV_SHIFT		8
+#define AC_FMT_DIV_MASK			(7 << 8)
+#define AC_FMT_MULT_SHIFT		11
+#define AC_FMT_MULT_MASK		(7 << 11)
+#define AC_FMT_BASE_SHIFT		14
+#define AC_FMT_BASE_48K			(0 << 14)
+#define AC_FMT_BASE_44K			(1 << 14)
+#define AC_FMT_TYPE_SHIFT		15
+#define AC_FMT_TYPE_PCM			(0 << 15)
+#define AC_FMT_TYPE_NON_PCM		(1 << 15)
+
+/* Unsolicited response control */
+#define AC_UNSOL_TAG			(0x3f<<0)
+#define AC_UNSOL_ENABLED		(1<<7)
+#define AC_USRSP_EN			AC_UNSOL_ENABLED
+
+/* Unsolicited responses */
+#define AC_UNSOL_RES_TAG		(0x3f<<26)
+#define AC_UNSOL_RES_TAG_SHIFT		26
+#define AC_UNSOL_RES_SUBTAG		(0x1f<<21)
+#define AC_UNSOL_RES_SUBTAG_SHIFT	21
+#define AC_UNSOL_RES_ELDV		(1<<1)	/* ELD Data valid (for HDMI) */
+#define AC_UNSOL_RES_PD			(1<<0)	/* pinsense detect */
+#define AC_UNSOL_RES_CP_STATE		(1<<1)	/* content protection */
+#define AC_UNSOL_RES_CP_READY		(1<<0)	/* content protection */
+
+/* Pin widget capabilies */
+#define AC_PINCAP_IMP_SENSE		(1<<0)	/* impedance sense capable */
+#define AC_PINCAP_TRIG_REQ		(1<<1)	/* trigger required */
+#define AC_PINCAP_PRES_DETECT		(1<<2)	/* presence detect capable */
+#define AC_PINCAP_HP_DRV		(1<<3)	/* headphone drive capable */
+#define AC_PINCAP_OUT			(1<<4)	/* output capable */
+#define AC_PINCAP_IN			(1<<5)	/* input capable */
+#define AC_PINCAP_BALANCE		(1<<6)	/* balanced I/O capable */
+/* Note: This LR_SWAP pincap is defined in the Realtek ALC883 specification,
+ *       but is marked reserved in the Intel HDA specification.
+ */
+#define AC_PINCAP_LR_SWAP		(1<<7)	/* L/R swap */
+/* Note: The same bit as LR_SWAP is newly defined as HDMI capability
+ *       in HD-audio specification
+ */
+#define AC_PINCAP_HDMI			(1<<7)	/* HDMI pin */
+#define AC_PINCAP_DP			(1<<24)	/* DisplayPort pin, can
+						 * coexist with AC_PINCAP_HDMI
+						 */
+#define AC_PINCAP_VREF			(0x37<<8)
+#define AC_PINCAP_VREF_SHIFT		8
+#define AC_PINCAP_EAPD			(1<<16)	/* EAPD capable */
+#define AC_PINCAP_HBR			(1<<27)	/* High Bit Rate */
+/* Vref status (used in pin cap) */
+#define AC_PINCAP_VREF_HIZ		(1<<0)	/* Hi-Z */
+#define AC_PINCAP_VREF_50		(1<<1)	/* 50% */
+#define AC_PINCAP_VREF_GRD		(1<<2)	/* ground */
+#define AC_PINCAP_VREF_80		(1<<4)	/* 80% */
+#define AC_PINCAP_VREF_100		(1<<5)	/* 100% */
+
+/* Amplifier capabilities */
+#define AC_AMPCAP_OFFSET		(0x7f<<0)  /* 0dB offset */
+#define AC_AMPCAP_OFFSET_SHIFT		0
+#define AC_AMPCAP_NUM_STEPS		(0x7f<<8)  /* number of steps */
+#define AC_AMPCAP_NUM_STEPS_SHIFT	8
+#define AC_AMPCAP_STEP_SIZE		(0x7f<<16) /* step size 0-32dB
+						    * in 0.25dB
+						    */
+#define AC_AMPCAP_STEP_SIZE_SHIFT	16
+#define AC_AMPCAP_MUTE			(1<<31)    /* mute capable */
+#define AC_AMPCAP_MUTE_SHIFT		31
+
+/* Connection list */
+#define AC_CLIST_LENGTH			(0x7f<<0)
+#define AC_CLIST_LONG			(1<<7)
+
+/* Supported power status */
+#define AC_PWRST_D0SUP			(1<<0)
+#define AC_PWRST_D1SUP			(1<<1)
+#define AC_PWRST_D2SUP			(1<<2)
+#define AC_PWRST_D3SUP			(1<<3)
+#define AC_PWRST_D3COLDSUP		(1<<4)
+#define AC_PWRST_S3D3COLDSUP		(1<<29)
+#define AC_PWRST_CLKSTOP		(1<<30)
+#define AC_PWRST_EPSS			(1U<<31)
+
+/* Power state values */
+#define AC_PWRST_SETTING		(0xf<<0)
+#define AC_PWRST_ACTUAL			(0xf<<4)
+#define AC_PWRST_ACTUAL_SHIFT		4
+#define AC_PWRST_D0			0x00
+#define AC_PWRST_D1			0x01
+#define AC_PWRST_D2			0x02
+#define AC_PWRST_D3			0x03
+
+/* Processing capabilies */
+#define AC_PCAP_BENIGN			(1<<0)
+#define AC_PCAP_NUM_COEF		(0xff<<8)
+#define AC_PCAP_NUM_COEF_SHIFT		8
+
+/* Volume knobs capabilities */
+#define AC_KNBCAP_NUM_STEPS		(0x7f<<0)
+#define AC_KNBCAP_DELTA			(1<<7)
+
+/* HDMI LPCM capabilities */
+#define AC_LPCMCAP_48K_CP_CHNS		(0x0f<<0) /* max channels w/ CP-on */	
+#define AC_LPCMCAP_48K_NO_CHNS		(0x0f<<4) /* max channels w/o CP-on */
+#define AC_LPCMCAP_48K_20BIT		(1<<8)	/* 20b bitrate supported */
+#define AC_LPCMCAP_48K_24BIT		(1<<9)	/* 24b bitrate supported */
+#define AC_LPCMCAP_96K_CP_CHNS		(0x0f<<10) /* max channels w/ CP-on */	
+#define AC_LPCMCAP_96K_NO_CHNS		(0x0f<<14) /* max channels w/o CP-on */
+#define AC_LPCMCAP_96K_20BIT		(1<<18)	/* 20b bitrate supported */
+#define AC_LPCMCAP_96K_24BIT		(1<<19)	/* 24b bitrate supported */
+#define AC_LPCMCAP_192K_CP_CHNS		(0x0f<<20) /* max channels w/ CP-on */	
+#define AC_LPCMCAP_192K_NO_CHNS		(0x0f<<24) /* max channels w/o CP-on */
+#define AC_LPCMCAP_192K_20BIT		(1<<28)	/* 20b bitrate supported */
+#define AC_LPCMCAP_192K_24BIT		(1<<29)	/* 24b bitrate supported */
+#define AC_LPCMCAP_44K			(1<<30)	/* 44.1kHz support */
+#define AC_LPCMCAP_44K_MS		(1<<31)	/* 44.1kHz-multiplies support */
+
+/*
+ * Control Parameters
+ */
+
+/* Amp gain/mute */
+#define AC_AMP_MUTE			(1<<7)
+#define AC_AMP_GAIN			(0x7f)
+#define AC_AMP_GET_INDEX		(0xf<<0)
+
+#define AC_AMP_GET_LEFT			(1<<13)
+#define AC_AMP_GET_RIGHT		(0<<13)
+#define AC_AMP_GET_OUTPUT		(1<<15)
+#define AC_AMP_GET_INPUT		(0<<15)
+
+#define AC_AMP_SET_INDEX		(0xf<<8)
+#define AC_AMP_SET_INDEX_SHIFT		8
+#define AC_AMP_SET_RIGHT		(1<<12)
+#define AC_AMP_SET_LEFT			(1<<13)
+#define AC_AMP_SET_INPUT		(1<<14)
+#define AC_AMP_SET_OUTPUT		(1<<15)
+
+/* DIGITAL1 bits */
+#define AC_DIG1_ENABLE			(1<<0)
+#define AC_DIG1_V			(1<<1)
+#define AC_DIG1_VCFG			(1<<2)
+#define AC_DIG1_EMPHASIS		(1<<3)
+#define AC_DIG1_COPYRIGHT		(1<<4)
+#define AC_DIG1_NONAUDIO		(1<<5)
+#define AC_DIG1_PROFESSIONAL		(1<<6)
+#define AC_DIG1_LEVEL			(1<<7)
+
+/* DIGITAL2 bits */
+#define AC_DIG2_CC			(0x7f<<0)
+
+/* Pin widget control - 8bit */
+#define AC_PINCTL_EPT			(0x3<<0)
+#define AC_PINCTL_EPT_NATIVE		0
+#define AC_PINCTL_EPT_HBR		3
+#define AC_PINCTL_VREFEN		(0x7<<0)
+#define AC_PINCTL_VREF_HIZ		0	/* Hi-Z */
+#define AC_PINCTL_VREF_50		1	/* 50% */
+#define AC_PINCTL_VREF_GRD		2	/* ground */
+#define AC_PINCTL_VREF_80		4	/* 80% */
+#define AC_PINCTL_VREF_100		5	/* 100% */
+#define AC_PINCTL_IN_EN			(1<<5)
+#define AC_PINCTL_OUT_EN		(1<<6)
+#define AC_PINCTL_HP_EN			(1<<7)
+
+/* Pin sense - 32bit */
+#define AC_PINSENSE_IMPEDANCE_MASK	(0x7fffffff)
+#define AC_PINSENSE_PRESENCE		(1<<31)
+#define AC_PINSENSE_ELDV		(1<<30)	/* ELD valid (HDMI) */
+
+/* EAPD/BTL enable - 32bit */
+#define AC_EAPDBTL_BALANCED		(1<<0)
+#define AC_EAPDBTL_EAPD			(1<<1)
+#define AC_EAPDBTL_LR_SWAP		(1<<2)
+
+/* HDMI ELD data */
+#define AC_ELDD_ELD_VALID		(1<<31)
+#define AC_ELDD_ELD_DATA		0xff
+
+/* HDMI DIP size */
+#define AC_DIPSIZE_ELD_BUF		(1<<3) /* ELD buf size of packet size */
+#define AC_DIPSIZE_PACK_IDX		(0x07<<0) /* packet index */
+
+/* HDMI DIP index */
+#define AC_DIPIDX_PACK_IDX		(0x07<<5) /* packet idnex */
+#define AC_DIPIDX_BYTE_IDX		(0x1f<<0) /* byte index */
+
+/* HDMI DIP xmit (transmit) control */
+#define AC_DIPXMIT_MASK			(0x3<<6)
+#define AC_DIPXMIT_DISABLE		(0x0<<6) /* disable xmit */
+#define AC_DIPXMIT_ONCE			(0x2<<6) /* xmit once then disable */
+#define AC_DIPXMIT_BEST			(0x3<<6) /* best effort */
+
+/* HDMI content protection (CP) control */
+#define AC_CPCTRL_CES			(1<<9) /* current encryption state */
+#define AC_CPCTRL_READY			(1<<8) /* ready bit */
+#define AC_CPCTRL_SUBTAG		(0x1f<<3) /* subtag for unsol-resp */
+#define AC_CPCTRL_STATE			(3<<0) /* current CP request state */
+
+/* Converter channel <-> HDMI slot mapping */
+#define AC_CVTMAP_HDMI_SLOT		(0xf<<0) /* HDMI slot number */
+#define AC_CVTMAP_CHAN			(0xf<<4) /* converter channel number */
+
+/* configuration default - 32bit */
+#define AC_DEFCFG_SEQUENCE		(0xf<<0)
+#define AC_DEFCFG_DEF_ASSOC		(0xf<<4)
+#define AC_DEFCFG_ASSOC_SHIFT		4
+#define AC_DEFCFG_MISC			(0xf<<8)
+#define AC_DEFCFG_MISC_SHIFT		8
+#define AC_DEFCFG_MISC_NO_PRESENCE	(1<<0)
+#define AC_DEFCFG_COLOR			(0xf<<12)
+#define AC_DEFCFG_COLOR_SHIFT		12
+#define AC_DEFCFG_CONN_TYPE		(0xf<<16)
+#define AC_DEFCFG_CONN_TYPE_SHIFT	16
+#define AC_DEFCFG_DEVICE		(0xf<<20)
+#define AC_DEFCFG_DEVICE_SHIFT		20
+#define AC_DEFCFG_LOCATION		(0x3f<<24)
+#define AC_DEFCFG_LOCATION_SHIFT	24
+#define AC_DEFCFG_PORT_CONN		(0x3<<30)
+#define AC_DEFCFG_PORT_CONN_SHIFT	30
+
+/* device device types (0x0-0xf) */
+enum {
+	AC_JACK_LINE_OUT,
+	AC_JACK_SPEAKER,
+	AC_JACK_HP_OUT,
+	AC_JACK_CD,
+	AC_JACK_SPDIF_OUT,
+	AC_JACK_DIG_OTHER_OUT,
+	AC_JACK_MODEM_LINE_SIDE,
+	AC_JACK_MODEM_HAND_SIDE,
+	AC_JACK_LINE_IN,
+	AC_JACK_AUX,
+	AC_JACK_MIC_IN,
+	AC_JACK_TELEPHONY,
+	AC_JACK_SPDIF_IN,
+	AC_JACK_DIG_OTHER_IN,
+	AC_JACK_OTHER = 0xf,
+};
+
+/* jack connection types (0x0-0xf) */
+enum {
+	AC_JACK_CONN_UNKNOWN,
+	AC_JACK_CONN_1_8,
+	AC_JACK_CONN_1_4,
+	AC_JACK_CONN_ATAPI,
+	AC_JACK_CONN_RCA,
+	AC_JACK_CONN_OPTICAL,
+	AC_JACK_CONN_OTHER_DIGITAL,
+	AC_JACK_CONN_OTHER_ANALOG,
+	AC_JACK_CONN_DIN,
+	AC_JACK_CONN_XLR,
+	AC_JACK_CONN_RJ11,
+	AC_JACK_CONN_COMB,
+	AC_JACK_CONN_OTHER = 0xf,
+};
+
+/* jack colors (0x0-0xf) */
+enum {
+	AC_JACK_COLOR_UNKNOWN,
+	AC_JACK_COLOR_BLACK,
+	AC_JACK_COLOR_GREY,
+	AC_JACK_COLOR_BLUE,
+	AC_JACK_COLOR_GREEN,
+	AC_JACK_COLOR_RED,
+	AC_JACK_COLOR_ORANGE,
+	AC_JACK_COLOR_YELLOW,
+	AC_JACK_COLOR_PURPLE,
+	AC_JACK_COLOR_PINK,
+	AC_JACK_COLOR_WHITE = 0xe,
+	AC_JACK_COLOR_OTHER,
+};
+
+/* Jack location (0x0-0x3f) */
+/* common case */
+enum {
+	AC_JACK_LOC_NONE,
+	AC_JACK_LOC_REAR,
+	AC_JACK_LOC_FRONT,
+	AC_JACK_LOC_LEFT,
+	AC_JACK_LOC_RIGHT,
+	AC_JACK_LOC_TOP,
+	AC_JACK_LOC_BOTTOM,
+};
+/* bits 4-5 */
+enum {
+	AC_JACK_LOC_EXTERNAL = 0x00,
+	AC_JACK_LOC_INTERNAL = 0x10,
+	AC_JACK_LOC_SEPARATE = 0x20,
+	AC_JACK_LOC_OTHER    = 0x30,
+};
+enum {
+	/* external on primary chasis */
+	AC_JACK_LOC_REAR_PANEL = 0x07,
+	AC_JACK_LOC_DRIVE_BAY,
+	/* internal */
+	AC_JACK_LOC_RISER = 0x17,
+	AC_JACK_LOC_HDMI,
+	AC_JACK_LOC_ATAPI,
+	/* others */
+	AC_JACK_LOC_MOBILE_IN = 0x37,
+	AC_JACK_LOC_MOBILE_OUT,
+};
+
+/* Port connectivity (0-3) */
+enum {
+	AC_JACK_PORT_COMPLEX,
+	AC_JACK_PORT_NONE,
+	AC_JACK_PORT_FIXED,
+	AC_JACK_PORT_BOTH,
+};
+
+/* max. connections to a widget */
+#define HDA_MAX_CONNECTIONS	32
+
+/* max. codec address */
+#define HDA_MAX_CODEC_ADDRESS	0x0f
+
+/* max number of PCM devics per card */
+#define HDA_MAX_PCMS		10
+
+/*
+ * generic arrays
+ */
+struct snd_array {
+	unsigned int used;
+	unsigned int alloced;
+	unsigned int elem_size;
+	unsigned int alloc_align;
+	void *list;
+};
+
+void *snd_array_new(struct snd_array *array);
+void snd_array_free(struct snd_array *array);
+static inline void snd_array_init(struct snd_array *array, unsigned int size,
+				  unsigned int align)
+{
+	array->elem_size = size;
+	array->alloc_align = align;
+}
+
+static inline void *snd_array_elem(struct snd_array *array, unsigned int idx)
+{
+	return array->list + idx * array->elem_size;
+}
+
+static inline unsigned int snd_array_index(struct snd_array *array, void *ptr)
+{
+	return (unsigned long)(ptr - array->list) / array->elem_size;
+}
+
+/*
+ * Structures
+ */
+
+struct hda_bus;
+struct hda_beep;
+struct hda_codec;
+struct hda_pcm;
+struct hda_pcm_stream;
+struct hda_bus_unsolicited;
+
+/* NID type */
+typedef u16 hda_nid_t;
+
+/* bus operators */
+struct hda_bus_ops {
+	/* send a single command */
+	int (*command)(struct hda_bus *bus, unsigned int cmd);
+	/* get a response from the last command */
+	unsigned int (*get_response)(struct hda_bus *bus, unsigned int addr);
+	/* free the private data */
+	void (*private_free)(struct hda_bus *);
+	/* attach a PCM stream */
+	int (*attach_pcm)(struct hda_bus *bus, struct hda_codec *codec,
+			  struct hda_pcm *pcm);
+	/* reset bus for retry verb */
+	void (*bus_reset)(struct hda_bus *bus);
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	/* notify power-up/down from codec to controller */
+	void (*pm_notify)(struct hda_bus *bus);
+#endif
+};
+
+/* template to pass to the bus constructor */
+struct hda_bus_template {
+	void *private_data;
+	struct pci_dev *pci;
+	const char *modelname;
+	int *power_save;
+	struct hda_bus_ops ops;
+};
+
+/*
+ * codec bus
+ *
+ * each controller needs to creata a hda_bus to assign the accessor.
+ * A hda_bus contains several codecs in the list codec_list.
+ */
+struct hda_bus {
+	struct snd_card *card;
+
+	/* copied from template */
+	void *private_data;
+	struct pci_dev *pci;
+	const char *modelname;
+	int *power_save;
+	struct hda_bus_ops ops;
+
+	/* codec linked list */
+	struct list_head codec_list;
+	/* link caddr -> codec */
+	struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS + 1];
+
+	struct mutex cmd_mutex;
+	struct mutex prepare_mutex;
+
+	/* unsolicited event queue */
+	struct hda_bus_unsolicited *unsol;
+	char workq_name[16];
+	struct workqueue_struct *workq;	/* common workqueue for codecs */
+
+	/* assigned PCMs */
+	DECLARE_BITMAP(pcm_dev_bits, SNDRV_PCM_DEVICES);
+
+	/* misc op flags */
+	unsigned int needs_damn_long_delay :1;
+	unsigned int allow_bus_reset:1;	/* allow bus reset at fatal error */
+	unsigned int sync_write:1;	/* sync after verb write */
+	/* status for codec/controller */
+	unsigned int shutdown :1;	/* being unloaded */
+	unsigned int rirb_error:1;	/* error in codec communication */
+	unsigned int response_reset:1;	/* controller was reset */
+	unsigned int in_reset:1;	/* during reset operation */
+	unsigned int power_keep_link_on:1; /* don't power off HDA link */
+};
+
+/*
+ * codec preset
+ *
+ * Known codecs have the patch to build and set up the controls/PCMs
+ * better than the generic parser.
+ */
+struct hda_codec_preset {
+	unsigned int id;
+	unsigned int mask;
+	unsigned int subs;
+	unsigned int subs_mask;
+	unsigned int rev;
+	hda_nid_t afg, mfg;
+	const char *name;
+	int (*patch)(struct hda_codec *codec);
+};
+	
+struct hda_codec_preset_list {
+	const struct hda_codec_preset *preset;
+	struct module *owner;
+	struct list_head list;
+};
+
+/* initial hook */
+int snd_hda_add_codec_preset(struct hda_codec_preset_list *preset);
+int snd_hda_delete_codec_preset(struct hda_codec_preset_list *preset);
+
+/* ops set by the preset patch */
+struct hda_codec_ops {
+	int (*build_controls)(struct hda_codec *codec);
+	int (*build_pcms)(struct hda_codec *codec);
+	int (*init)(struct hda_codec *codec);
+	void (*free)(struct hda_codec *codec);
+	void (*unsol_event)(struct hda_codec *codec, unsigned int res);
+#ifdef SND_HDA_NEEDS_RESUME
+	int (*suspend)(struct hda_codec *codec, pm_message_t state);
+	int (*resume)(struct hda_codec *codec);
+#endif
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	int (*check_power_status)(struct hda_codec *codec, hda_nid_t nid);
+#endif
+	void (*reboot_notify)(struct hda_codec *codec);
+};
+
+/* record for amp information cache */
+struct hda_cache_head {
+	u32 key;		/* hash key */
+	u16 val;		/* assigned value */
+	u16 next;		/* next link; -1 = terminal */
+};
+
+struct hda_amp_info {
+	struct hda_cache_head head;
+	u32 amp_caps;		/* amp capabilities */
+	u16 vol[2];		/* current volume & mute */
+};
+
+struct hda_cache_rec {
+	u16 hash[64];			/* hash table for index */
+	struct snd_array buf;		/* record entries */
+};
+
+/* PCM callbacks */
+struct hda_pcm_ops {
+	int (*open)(struct hda_pcm_stream *info, struct hda_codec *codec,
+		    struct snd_pcm_substream *substream);
+	int (*close)(struct hda_pcm_stream *info, struct hda_codec *codec,
+		     struct snd_pcm_substream *substream);
+	int (*prepare)(struct hda_pcm_stream *info, struct hda_codec *codec,
+		       unsigned int stream_tag, unsigned int format,
+		       struct snd_pcm_substream *substream);
+	int (*cleanup)(struct hda_pcm_stream *info, struct hda_codec *codec,
+		       struct snd_pcm_substream *substream);
+};
+
+/* PCM information for each substream */
+struct hda_pcm_stream {
+	unsigned int substreams;	/* number of substreams, 0 = not exist*/
+	unsigned int channels_min;	/* min. number of channels */
+	unsigned int channels_max;	/* max. number of channels */
+	hda_nid_t nid;	/* default NID to query rates/formats/bps, or set up */
+	u32 rates;	/* supported rates */
+	u64 formats;	/* supported formats (SNDRV_PCM_FMTBIT_) */
+	unsigned int maxbps;	/* supported max. bit per sample */
+	struct hda_pcm_ops ops;
+};
+
+/* PCM types */
+enum {
+	HDA_PCM_TYPE_AUDIO,
+	HDA_PCM_TYPE_SPDIF,
+	HDA_PCM_TYPE_HDMI,
+	HDA_PCM_TYPE_MODEM,
+	HDA_PCM_NTYPES
+};
+
+/* for PCM creation */
+struct hda_pcm {
+	char *name;
+	struct hda_pcm_stream stream[2];
+	unsigned int pcm_type;	/* HDA_PCM_TYPE_XXX */
+	int device;		/* device number to assign */
+	struct snd_pcm *pcm;	/* assigned PCM instance */
+};
+
+/* codec information */
+struct hda_codec {
+	struct hda_bus *bus;
+	unsigned int addr;	/* codec addr*/
+	struct list_head list;	/* list point */
+
+	hda_nid_t afg;	/* AFG node id */
+	hda_nid_t mfg;	/* MFG node id */
+
+	/* ids */
+	u8 afg_function_id;
+	u8 mfg_function_id;
+	u8 afg_unsol;
+	u8 mfg_unsol;
+	u32 vendor_id;
+	u32 subsystem_id;
+	u32 revision_id;
+
+	/* detected preset */
+	const struct hda_codec_preset *preset;
+	struct module *owner;
+	const char *vendor_name;	/* codec vendor name */
+	const char *chip_name;		/* codec chip name */
+	const char *modelname;	/* model name for preset */
+
+	/* set by patch */
+	struct hda_codec_ops patch_ops;
+
+	/* PCM to create, set by patch_ops.build_pcms callback */
+	unsigned int num_pcms;
+	struct hda_pcm *pcm_info;
+
+	/* codec specific info */
+	void *spec;
+
+	/* beep device */
+	struct hda_beep *beep;
+	unsigned int beep_mode;
+
+	/* widget capabilities cache */
+	unsigned int num_nodes;
+	hda_nid_t start_nid;
+	u32 *wcaps;
+
+	struct snd_array mixers;	/* list of assigned mixer elements */
+	struct snd_array nids;		/* list of mapped mixer elements */
+
+	struct hda_cache_rec amp_cache;	/* cache for amp access */
+	struct hda_cache_rec cmd_cache;	/* cache for other commands */
+
+	struct mutex spdif_mutex;
+	struct mutex control_mutex;
+	unsigned int spdif_status;	/* IEC958 status bits */
+	unsigned short spdif_ctls;	/* SPDIF control bits */
+	unsigned int spdif_in_enable;	/* SPDIF input enable? */
+	hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */
+	struct snd_array init_pins;	/* initial (BIOS) pin configurations */
+	struct snd_array driver_pins;	/* pin configs set by codec parser */
+	struct snd_array cvt_setups;	/* audio convert setups */
+
+#ifdef CONFIG_SND_HDA_HWDEP
+	struct snd_hwdep *hwdep;	/* assigned hwdep device */
+	struct snd_array init_verbs;	/* additional init verbs */
+	struct snd_array hints;		/* additional hints */
+	struct snd_array user_pins;	/* default pin configs to override */
+#endif
+
+	/* misc flags */
+	unsigned int spdif_status_reset :1; /* needs to toggle SPDIF for each
+					     * status change
+					     * (e.g. Realtek codecs)
+					     */
+	unsigned int pin_amp_workaround:1; /* pin out-amp takes index
+					    * (e.g. Conexant codecs)
+					    */
+	unsigned int no_sticky_stream:1; /* no sticky-PCM stream assignment */
+	unsigned int pins_shutup:1;	/* pins are shut up */
+	unsigned int no_trigger_sense:1; /* don't trigger at pin-sensing */
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	unsigned int power_on :1;	/* current (global) power-state */
+	unsigned int power_transition :1; /* power-state in transition */
+	int power_count;	/* current (global) power refcount */
+	struct delayed_work power_work; /* delayed task for powerdown */
+	unsigned long power_on_acct;
+	unsigned long power_off_acct;
+	unsigned long power_jiffies;
+#endif
+
+	/* codec-specific additional proc output */
+	void (*proc_widget_hook)(struct snd_info_buffer *buffer,
+				 struct hda_codec *codec, hda_nid_t nid);
+};
+
+/* direction */
+enum {
+	HDA_INPUT, HDA_OUTPUT
+};
+
+
+/*
+ * constructors
+ */
+int snd_hda_bus_new(struct snd_card *card, const struct hda_bus_template *temp,
+		    struct hda_bus **busp);
+int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
+		      struct hda_codec **codecp);
+int snd_hda_codec_configure(struct hda_codec *codec);
+
+/*
+ * low level functions
+ */
+unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid,
+				int direct,
+				unsigned int verb, unsigned int parm);
+int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int direct,
+			unsigned int verb, unsigned int parm);
+#define snd_hda_param_read(codec, nid, param) \
+	snd_hda_codec_read(codec, nid, 0, AC_VERB_PARAMETERS, param)
+int snd_hda_get_sub_nodes(struct hda_codec *codec, hda_nid_t nid,
+			  hda_nid_t *start_id);
+int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
+			    hda_nid_t *conn_list, int max_conns);
+
+struct hda_verb {
+	hda_nid_t nid;
+	u32 verb;
+	u32 param;
+};
+
+void snd_hda_sequence_write(struct hda_codec *codec,
+			    const struct hda_verb *seq);
+
+/* unsolicited event */
+int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex);
+
+/* cached write */
+#ifdef SND_HDA_NEEDS_RESUME
+int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
+			      int direct, unsigned int verb, unsigned int parm);
+void snd_hda_sequence_write_cache(struct hda_codec *codec,
+				  const struct hda_verb *seq);
+int snd_hda_codec_update_cache(struct hda_codec *codec, hda_nid_t nid,
+			      int direct, unsigned int verb, unsigned int parm);
+void snd_hda_codec_resume_cache(struct hda_codec *codec);
+#else
+#define snd_hda_codec_write_cache	snd_hda_codec_write
+#define snd_hda_codec_update_cache	snd_hda_codec_write
+#define snd_hda_sequence_write_cache	snd_hda_sequence_write
+#endif
+
+/* the struct for codec->pin_configs */
+struct hda_pincfg {
+	hda_nid_t nid;
+	unsigned char ctrl;	/* current pin control value */
+	unsigned char pad;	/* reserved */
+	unsigned int cfg;	/* default configuration */
+};
+
+unsigned int snd_hda_codec_get_pincfg(struct hda_codec *codec, hda_nid_t nid);
+int snd_hda_codec_set_pincfg(struct hda_codec *codec, hda_nid_t nid,
+			     unsigned int cfg);
+int snd_hda_add_pincfg(struct hda_codec *codec, struct snd_array *list,
+		       hda_nid_t nid, unsigned int cfg); /* for hwdep */
+void snd_hda_shutup_pins(struct hda_codec *codec);
+
+/*
+ * Mixer
+ */
+int snd_hda_build_controls(struct hda_bus *bus);
+int snd_hda_codec_build_controls(struct hda_codec *codec);
+
+/*
+ * PCM
+ */
+int snd_hda_build_pcms(struct hda_bus *bus);
+int snd_hda_codec_build_pcms(struct hda_codec *codec);
+
+int snd_hda_codec_prepare(struct hda_codec *codec,
+			  struct hda_pcm_stream *hinfo,
+			  unsigned int stream,
+			  unsigned int format,
+			  struct snd_pcm_substream *substream);
+void snd_hda_codec_cleanup(struct hda_codec *codec,
+			   struct hda_pcm_stream *hinfo,
+			   struct snd_pcm_substream *substream);
+
+void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
+				u32 stream_tag,
+				int channel_id, int format);
+void __snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid,
+				    int do_now);
+#define snd_hda_codec_cleanup_stream(codec, nid) \
+	__snd_hda_codec_cleanup_stream(codec, nid, 0)
+unsigned int snd_hda_calc_stream_format(unsigned int rate,
+					unsigned int channels,
+					unsigned int format,
+					unsigned int maxbps,
+					unsigned short spdif_ctls);
+int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid,
+				unsigned int format);
+
+/*
+ * Misc
+ */
+void snd_hda_get_codec_name(struct hda_codec *codec, char *name, int namelen);
+void snd_hda_bus_reboot_notify(struct hda_bus *bus);
+
+/*
+ * power management
+ */
+#ifdef CONFIG_PM
+int snd_hda_suspend(struct hda_bus *bus);
+int snd_hda_resume(struct hda_bus *bus);
+#endif
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static inline
+int hda_call_check_power_status(struct hda_codec *codec, hda_nid_t nid)
+{
+	if (codec->patch_ops.check_power_status)
+		return codec->patch_ops.check_power_status(codec, nid);
+	return 0;
+}
+#else	
+#define hda_call_check_power_status(codec, nid)		0
+#endif
+
+/*
+ * get widget information
+ */
+const char *snd_hda_get_jack_connectivity(u32 cfg);
+const char *snd_hda_get_jack_type(u32 cfg);
+const char *snd_hda_get_jack_location(u32 cfg);
+
+/*
+ * power saving
+ */
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+void snd_hda_power_up(struct hda_codec *codec);
+void snd_hda_power_down(struct hda_codec *codec);
+#define snd_hda_codec_needs_resume(codec) codec->power_count
+void snd_hda_update_power_acct(struct hda_codec *codec);
+#else
+static inline void snd_hda_power_up(struct hda_codec *codec) {}
+static inline void snd_hda_power_down(struct hda_codec *codec) {}
+#define snd_hda_codec_needs_resume(codec) 1
+#endif
+
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+/*
+ * patch firmware
+ */
+int snd_hda_load_patch(struct hda_bus *bus, const char *patch);
+#endif
+
+/*
+ * Codec modularization
+ */
+
+/* Export symbols only for communication with codec drivers;
+ * When built in kernel, all HD-audio drivers are supposed to be statically
+ * linked to the kernel.  Thus, the symbols don't have to (or shouldn't) be
+ * exported unless it's built as a module.
+ */
+#ifdef MODULE
+#define EXPORT_SYMBOL_HDA(sym) EXPORT_SYMBOL_GPL(sym)
+#else
+#define EXPORT_SYMBOL_HDA(sym)
+#endif
+
+#endif /* __SOUND_HDA_CODEC_H */
diff --git a/linux/sound/pci/hda/hda_eld.c b/linux/sound/pci/hda/hda_eld.c
new file mode 100644
index 0000000..4a66347
--- /dev/null
+++ b/linux/sound/pci/hda/hda_eld.c
@@ -0,0 +1,637 @@
+/*
+ * Generic routines and proc interface for ELD(EDID Like Data) information
+ *
+ * Copyright(c) 2008 Intel Corporation.
+ *
+ * Authors:
+ * 		Wu Fengguang <wfg@linux.intel.com>
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <asm/unaligned.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+enum eld_versions {
+	ELD_VER_CEA_861D	= 2,
+	ELD_VER_PARTIAL		= 31,
+};
+
+enum cea_edid_versions {
+	CEA_EDID_VER_NONE	= 0,
+	CEA_EDID_VER_CEA861	= 1,
+	CEA_EDID_VER_CEA861A	= 2,
+	CEA_EDID_VER_CEA861BCD	= 3,
+	CEA_EDID_VER_RESERVED	= 4,
+};
+
+static char *cea_speaker_allocation_names[] = {
+	/*  0 */ "FL/FR",
+	/*  1 */ "LFE",
+	/*  2 */ "FC",
+	/*  3 */ "RL/RR",
+	/*  4 */ "RC",
+	/*  5 */ "FLC/FRC",
+	/*  6 */ "RLC/RRC",
+	/*  7 */ "FLW/FRW",
+	/*  8 */ "FLH/FRH",
+	/*  9 */ "TC",
+	/* 10 */ "FCH",
+};
+
+static char *eld_connection_type_names[4] = {
+	"HDMI",
+	"DisplayPort",
+	"2-reserved",
+	"3-reserved"
+};
+
+enum cea_audio_coding_types {
+	AUDIO_CODING_TYPE_REF_STREAM_HEADER	=  0,
+	AUDIO_CODING_TYPE_LPCM			=  1,
+	AUDIO_CODING_TYPE_AC3			=  2,
+	AUDIO_CODING_TYPE_MPEG1			=  3,
+	AUDIO_CODING_TYPE_MP3			=  4,
+	AUDIO_CODING_TYPE_MPEG2			=  5,
+	AUDIO_CODING_TYPE_AACLC			=  6,
+	AUDIO_CODING_TYPE_DTS			=  7,
+	AUDIO_CODING_TYPE_ATRAC			=  8,
+	AUDIO_CODING_TYPE_SACD			=  9,
+	AUDIO_CODING_TYPE_EAC3			= 10,
+	AUDIO_CODING_TYPE_DTS_HD		= 11,
+	AUDIO_CODING_TYPE_MLP			= 12,
+	AUDIO_CODING_TYPE_DST			= 13,
+	AUDIO_CODING_TYPE_WMAPRO		= 14,
+	AUDIO_CODING_TYPE_REF_CXT		= 15,
+	/* also include valid xtypes below */
+	AUDIO_CODING_TYPE_HE_AAC		= 15,
+	AUDIO_CODING_TYPE_HE_AAC2		= 16,
+	AUDIO_CODING_TYPE_MPEG_SURROUND		= 17,
+};
+
+enum cea_audio_coding_xtypes {
+	AUDIO_CODING_XTYPE_HE_REF_CT		= 0,
+	AUDIO_CODING_XTYPE_HE_AAC		= 1,
+	AUDIO_CODING_XTYPE_HE_AAC2		= 2,
+	AUDIO_CODING_XTYPE_MPEG_SURROUND	= 3,
+	AUDIO_CODING_XTYPE_FIRST_RESERVED	= 4,
+};
+
+static char *cea_audio_coding_type_names[] = {
+	/*  0 */ "undefined",
+	/*  1 */ "LPCM",
+	/*  2 */ "AC-3",
+	/*  3 */ "MPEG1",
+	/*  4 */ "MP3",
+	/*  5 */ "MPEG2",
+	/*  6 */ "AAC-LC",
+	/*  7 */ "DTS",
+	/*  8 */ "ATRAC",
+	/*  9 */ "DSD (One Bit Audio)",
+	/* 10 */ "E-AC-3/DD+ (Dolby Digital Plus)",
+	/* 11 */ "DTS-HD",
+	/* 12 */ "MLP (Dolby TrueHD)",
+	/* 13 */ "DST",
+	/* 14 */ "WMAPro",
+	/* 15 */ "HE-AAC",
+	/* 16 */ "HE-AACv2",
+	/* 17 */ "MPEG Surround",
+};
+
+/*
+ * The following two lists are shared between
+ * 	- HDMI audio InfoFrame (source to sink)
+ * 	- CEA E-EDID Extension (sink to source)
+ */
+
+/*
+ * SS1:SS0 index => sample size
+ */
+static int cea_sample_sizes[4] = {
+	0,	 		/* 0: Refer to Stream Header */
+	AC_SUPPCM_BITS_16,	/* 1: 16 bits */
+	AC_SUPPCM_BITS_20,	/* 2: 20 bits */
+	AC_SUPPCM_BITS_24,	/* 3: 24 bits */
+};
+
+/*
+ * SF2:SF1:SF0 index => sampling frequency
+ */
+static int cea_sampling_frequencies[8] = {
+	0,			/* 0: Refer to Stream Header */
+	SNDRV_PCM_RATE_32000,	/* 1:  32000Hz */
+	SNDRV_PCM_RATE_44100,	/* 2:  44100Hz */
+	SNDRV_PCM_RATE_48000,	/* 3:  48000Hz */
+	SNDRV_PCM_RATE_88200,	/* 4:  88200Hz */
+	SNDRV_PCM_RATE_96000,	/* 5:  96000Hz */
+	SNDRV_PCM_RATE_176400,	/* 6: 176400Hz */
+	SNDRV_PCM_RATE_192000,	/* 7: 192000Hz */
+};
+
+static unsigned char hdmi_get_eld_byte(struct hda_codec *codec, hda_nid_t nid,
+					int byte_index)
+{
+	unsigned int val;
+
+	val = snd_hda_codec_read(codec, nid, 0,
+					AC_VERB_GET_HDMI_ELDD, byte_index);
+
+#ifdef BE_PARANOID
+	printk(KERN_INFO "HDMI: ELD data byte %d: 0x%x\n", byte_index, val);
+#endif
+
+	if ((val & AC_ELDD_ELD_VALID) == 0) {
+		snd_printd(KERN_INFO "HDMI: invalid ELD data byte %d\n",
+								byte_index);
+		val = 0;
+	}
+
+	return val & AC_ELDD_ELD_DATA;
+}
+
+#define GRAB_BITS(buf, byte, lowbit, bits) 		\
+({							\
+	BUILD_BUG_ON(lowbit > 7);			\
+	BUILD_BUG_ON(bits > 8);				\
+	BUILD_BUG_ON(bits <= 0);			\
+							\
+	(buf[byte] >> (lowbit)) & ((1 << (bits)) - 1);	\
+})
+
+static void hdmi_update_short_audio_desc(struct cea_sad *a,
+					 const unsigned char *buf)
+{
+	int i;
+	int val;
+
+	val = GRAB_BITS(buf, 1, 0, 7);
+	a->rates = 0;
+	for (i = 0; i < 7; i++)
+		if (val & (1 << i))
+			a->rates |= cea_sampling_frequencies[i + 1];
+
+	a->channels = GRAB_BITS(buf, 0, 0, 3);
+	a->channels++;
+
+	a->sample_bits = 0;
+	a->max_bitrate = 0;
+
+	a->format = GRAB_BITS(buf, 0, 3, 4);
+	switch (a->format) {
+	case AUDIO_CODING_TYPE_REF_STREAM_HEADER:
+		snd_printd(KERN_INFO
+				"HDMI: audio coding type 0 not expected\n");
+		break;
+
+	case AUDIO_CODING_TYPE_LPCM:
+		val = GRAB_BITS(buf, 2, 0, 3);
+		for (i = 0; i < 3; i++)
+			if (val & (1 << i))
+				a->sample_bits |= cea_sample_sizes[i + 1];
+		break;
+
+	case AUDIO_CODING_TYPE_AC3:
+	case AUDIO_CODING_TYPE_MPEG1:
+	case AUDIO_CODING_TYPE_MP3:
+	case AUDIO_CODING_TYPE_MPEG2:
+	case AUDIO_CODING_TYPE_AACLC:
+	case AUDIO_CODING_TYPE_DTS:
+	case AUDIO_CODING_TYPE_ATRAC:
+		a->max_bitrate = GRAB_BITS(buf, 2, 0, 8);
+		a->max_bitrate *= 8000;
+		break;
+
+	case AUDIO_CODING_TYPE_SACD:
+		break;
+
+	case AUDIO_CODING_TYPE_EAC3:
+		break;
+
+	case AUDIO_CODING_TYPE_DTS_HD:
+		break;
+
+	case AUDIO_CODING_TYPE_MLP:
+		break;
+
+	case AUDIO_CODING_TYPE_DST:
+		break;
+
+	case AUDIO_CODING_TYPE_WMAPRO:
+		a->profile = GRAB_BITS(buf, 2, 0, 3);
+		break;
+
+	case AUDIO_CODING_TYPE_REF_CXT:
+		a->format = GRAB_BITS(buf, 2, 3, 5);
+		if (a->format == AUDIO_CODING_XTYPE_HE_REF_CT ||
+		    a->format >= AUDIO_CODING_XTYPE_FIRST_RESERVED) {
+			snd_printd(KERN_INFO
+				"HDMI: audio coding xtype %d not expected\n",
+				a->format);
+			a->format = 0;
+		} else
+			a->format += AUDIO_CODING_TYPE_HE_AAC -
+				     AUDIO_CODING_XTYPE_HE_AAC;
+		break;
+	}
+}
+
+/*
+ * Be careful, ELD buf could be totally rubbish!
+ */
+static int hdmi_update_eld(struct hdmi_eld *e,
+			   const unsigned char *buf, int size)
+{
+	int mnl;
+	int i;
+
+	e->eld_ver = GRAB_BITS(buf, 0, 3, 5);
+	if (e->eld_ver != ELD_VER_CEA_861D &&
+	    e->eld_ver != ELD_VER_PARTIAL) {
+		snd_printd(KERN_INFO "HDMI: Unknown ELD version %d\n",
+								e->eld_ver);
+		goto out_fail;
+	}
+
+	e->eld_size = size;
+	e->baseline_len = GRAB_BITS(buf, 2, 0, 8);
+	mnl		= GRAB_BITS(buf, 4, 0, 5);
+	e->cea_edid_ver	= GRAB_BITS(buf, 4, 5, 3);
+
+	e->support_hdcp	= GRAB_BITS(buf, 5, 0, 1);
+	e->support_ai	= GRAB_BITS(buf, 5, 1, 1);
+	e->conn_type	= GRAB_BITS(buf, 5, 2, 2);
+	e->sad_count	= GRAB_BITS(buf, 5, 4, 4);
+
+	e->aud_synch_delay = GRAB_BITS(buf, 6, 0, 8) * 2;
+	e->spk_alloc	= GRAB_BITS(buf, 7, 0, 7);
+
+	e->port_id	  = get_unaligned_le64(buf + 8);
+
+	/* not specified, but the spec's tendency is little endian */
+	e->manufacture_id = get_unaligned_le16(buf + 16);
+	e->product_id	  = get_unaligned_le16(buf + 18);
+
+	if (mnl > ELD_MAX_MNL) {
+		snd_printd(KERN_INFO "HDMI: MNL is reserved value %d\n", mnl);
+		goto out_fail;
+	} else if (ELD_FIXED_BYTES + mnl > size) {
+		snd_printd(KERN_INFO "HDMI: out of range MNL %d\n", mnl);
+		goto out_fail;
+	} else
+		strlcpy(e->monitor_name, buf + ELD_FIXED_BYTES, mnl);
+
+	for (i = 0; i < e->sad_count; i++) {
+		if (ELD_FIXED_BYTES + mnl + 3 * (i + 1) > size) {
+			snd_printd(KERN_INFO "HDMI: out of range SAD %d\n", i);
+			goto out_fail;
+		}
+		hdmi_update_short_audio_desc(e->sad + i,
+					buf + ELD_FIXED_BYTES + mnl + 3 * i);
+	}
+
+	return 0;
+
+out_fail:
+	e->eld_ver = 0;
+	return -EINVAL;
+}
+
+static int hdmi_eld_valid(struct hda_codec *codec, hda_nid_t nid)
+{
+	int eldv;
+	int present;
+
+	present = snd_hda_pin_sense(codec, nid);
+	eldv    = (present & AC_PINSENSE_ELDV);
+	present = (present & AC_PINSENSE_PRESENCE);
+
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+	printk(KERN_INFO "HDMI: sink_present = %d, eld_valid = %d\n",
+			!!present, !!eldv);
+#endif
+
+	return eldv && present;
+}
+
+int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid)
+{
+	return snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_HDMI_DIP_SIZE,
+						 AC_DIPSIZE_ELD_BUF);
+}
+
+int snd_hdmi_get_eld(struct hdmi_eld *eld,
+		     struct hda_codec *codec, hda_nid_t nid)
+{
+	int i;
+	int ret;
+	int size;
+	unsigned char *buf;
+
+	if (!hdmi_eld_valid(codec, nid))
+		return -ENOENT;
+
+	size = snd_hdmi_get_eld_size(codec, nid);
+	if (size == 0) {
+		/* wfg: workaround for ASUS P5E-VM HDMI board */
+		snd_printd(KERN_INFO "HDMI: ELD buf size is 0, force 128\n");
+		size = 128;
+	}
+	if (size < ELD_FIXED_BYTES || size > PAGE_SIZE) {
+		snd_printd(KERN_INFO "HDMI: invalid ELD buf size %d\n", size);
+		return -ERANGE;
+	}
+
+	buf = kmalloc(size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	for (i = 0; i < size; i++)
+		buf[i] = hdmi_get_eld_byte(codec, nid, i);
+
+	ret = hdmi_update_eld(eld, buf, size);
+
+	kfree(buf);
+	return ret;
+}
+
+static void hdmi_show_short_audio_desc(struct cea_sad *a)
+{
+	char buf[SND_PRINT_RATES_ADVISED_BUFSIZE];
+	char buf2[8 + SND_PRINT_BITS_ADVISED_BUFSIZE] = ", bits =";
+
+	if (!a->format)
+		return;
+
+	snd_print_pcm_rates(a->rates, buf, sizeof(buf));
+
+	if (a->format == AUDIO_CODING_TYPE_LPCM)
+		snd_print_pcm_bits(a->sample_bits, buf2 + 8, sizeof(buf2 - 8));
+	else if (a->max_bitrate)
+		snprintf(buf2, sizeof(buf2),
+				", max bitrate = %d", a->max_bitrate);
+	else
+		buf2[0] = '\0';
+
+	printk(KERN_INFO "HDMI: supports coding type %s:"
+			" channels = %d, rates =%s%s\n",
+			cea_audio_coding_type_names[a->format],
+			a->channels,
+			buf,
+			buf2);
+}
+
+void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen)
+{
+	int i, j;
+
+	for (i = 0, j = 0; i < ARRAY_SIZE(cea_speaker_allocation_names); i++) {
+		if (spk_alloc & (1 << i))
+			j += snprintf(buf + j, buflen - j,  " %s",
+					cea_speaker_allocation_names[i]);
+	}
+	buf[j] = '\0';	/* necessary when j == 0 */
+}
+
+void snd_hdmi_show_eld(struct hdmi_eld *e)
+{
+	int i;
+
+	printk(KERN_INFO "HDMI: detected monitor %s at connection type %s\n",
+			e->monitor_name,
+			eld_connection_type_names[e->conn_type]);
+
+	if (e->spk_alloc) {
+		char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
+		snd_print_channel_allocation(e->spk_alloc, buf, sizeof(buf));
+		printk(KERN_INFO "HDMI: available speakers:%s\n", buf);
+	}
+
+	for (i = 0; i < e->sad_count; i++)
+		hdmi_show_short_audio_desc(e->sad + i);
+}
+
+#ifdef CONFIG_PROC_FS
+
+static void hdmi_print_sad_info(int i, struct cea_sad *a,
+				struct snd_info_buffer *buffer)
+{
+	char buf[SND_PRINT_RATES_ADVISED_BUFSIZE];
+
+	snd_iprintf(buffer, "sad%d_coding_type\t[0x%x] %s\n",
+			i, a->format, cea_audio_coding_type_names[a->format]);
+	snd_iprintf(buffer, "sad%d_channels\t\t%d\n", i, a->channels);
+
+	snd_print_pcm_rates(a->rates, buf, sizeof(buf));
+	snd_iprintf(buffer, "sad%d_rates\t\t[0x%x]%s\n", i, a->rates, buf);
+
+	if (a->format == AUDIO_CODING_TYPE_LPCM) {
+		snd_print_pcm_bits(a->sample_bits, buf, sizeof(buf));
+		snd_iprintf(buffer, "sad%d_bits\t\t[0x%x]%s\n",
+							i, a->sample_bits, buf);
+	}
+
+	if (a->max_bitrate)
+		snd_iprintf(buffer, "sad%d_max_bitrate\t%d\n",
+							i, a->max_bitrate);
+
+	if (a->profile)
+		snd_iprintf(buffer, "sad%d_profile\t\t%d\n", i, a->profile);
+}
+
+static void hdmi_print_eld_info(struct snd_info_entry *entry,
+				struct snd_info_buffer *buffer)
+{
+	struct hdmi_eld *e = entry->private_data;
+	char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
+	int i;
+	static char *eld_versoin_names[32] = {
+		"reserved",
+		"reserved",
+		"CEA-861D or below",
+		[3 ... 30] = "reserved",
+		[31] = "partial"
+	};
+	static char *cea_edid_version_names[8] = {
+		"no CEA EDID Timing Extension block present",
+		"CEA-861",
+		"CEA-861-A",
+		"CEA-861-B, C or D",
+		[4 ... 7] = "reserved"
+	};
+
+	snd_iprintf(buffer, "monitor_present\t\t%d\n", e->monitor_present);
+	snd_iprintf(buffer, "eld_valid\t\t%d\n", e->eld_valid);
+	snd_iprintf(buffer, "monitor_name\t\t%s\n", e->monitor_name);
+	snd_iprintf(buffer, "connection_type\t\t%s\n",
+				eld_connection_type_names[e->conn_type]);
+	snd_iprintf(buffer, "eld_version\t\t[0x%x] %s\n", e->eld_ver,
+					eld_versoin_names[e->eld_ver]);
+	snd_iprintf(buffer, "edid_version\t\t[0x%x] %s\n", e->cea_edid_ver,
+				cea_edid_version_names[e->cea_edid_ver]);
+	snd_iprintf(buffer, "manufacture_id\t\t0x%x\n", e->manufacture_id);
+	snd_iprintf(buffer, "product_id\t\t0x%x\n", e->product_id);
+	snd_iprintf(buffer, "port_id\t\t\t0x%llx\n", (long long)e->port_id);
+	snd_iprintf(buffer, "support_hdcp\t\t%d\n", e->support_hdcp);
+	snd_iprintf(buffer, "support_ai\t\t%d\n", e->support_ai);
+	snd_iprintf(buffer, "audio_sync_delay\t%d\n", e->aud_synch_delay);
+
+	snd_print_channel_allocation(e->spk_alloc, buf, sizeof(buf));
+	snd_iprintf(buffer, "speakers\t\t[0x%x]%s\n", e->spk_alloc, buf);
+
+	snd_iprintf(buffer, "sad_count\t\t%d\n", e->sad_count);
+
+	for (i = 0; i < e->sad_count; i++)
+		hdmi_print_sad_info(i, e->sad + i, buffer);
+}
+
+static void hdmi_write_eld_info(struct snd_info_entry *entry,
+				struct snd_info_buffer *buffer)
+{
+	struct hdmi_eld *e = entry->private_data;
+	char line[64];
+	char name[64];
+	char *sname;
+	long long val;
+	unsigned int n;
+
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		if (sscanf(line, "%s %llx", name, &val) != 2)
+			continue;
+		/*
+		 * We don't allow modification to these fields:
+		 * 	monitor_name manufacture_id product_id
+		 * 	eld_version edid_version
+		 */
+		if (!strcmp(name, "monitor_present"))
+			e->monitor_present = val;
+		else if (!strcmp(name, "eld_valid"))
+			e->eld_valid = val;
+		else if (!strcmp(name, "connection_type"))
+			e->conn_type = val;
+		else if (!strcmp(name, "port_id"))
+			e->port_id = val;
+		else if (!strcmp(name, "support_hdcp"))
+			e->support_hdcp = val;
+		else if (!strcmp(name, "support_ai"))
+			e->support_ai = val;
+		else if (!strcmp(name, "audio_sync_delay"))
+			e->aud_synch_delay = val;
+		else if (!strcmp(name, "speakers"))
+			e->spk_alloc = val;
+		else if (!strcmp(name, "sad_count"))
+			e->sad_count = val;
+		else if (!strncmp(name, "sad", 3)) {
+			sname = name + 4;
+			n = name[3] - '0';
+			if (name[4] >= '0' && name[4] <= '9') {
+				sname++;
+				n = 10 * n + name[4] - '0';
+			}
+			if (n >= ELD_MAX_SAD)
+				continue;
+			if (!strcmp(sname, "_coding_type"))
+				e->sad[n].format = val;
+			else if (!strcmp(sname, "_channels"))
+				e->sad[n].channels = val;
+			else if (!strcmp(sname, "_rates"))
+				e->sad[n].rates = val;
+			else if (!strcmp(sname, "_bits"))
+				e->sad[n].sample_bits = val;
+			else if (!strcmp(sname, "_max_bitrate"))
+				e->sad[n].max_bitrate = val;
+			else if (!strcmp(sname, "_profile"))
+				e->sad[n].profile = val;
+			if (n >= e->sad_count)
+				e->sad_count = n + 1;
+		}
+	}
+}
+
+
+int snd_hda_eld_proc_new(struct hda_codec *codec, struct hdmi_eld *eld,
+			 int index)
+{
+	char name[32];
+	struct snd_info_entry *entry;
+	int err;
+
+	snprintf(name, sizeof(name), "eld#%d.%d", codec->addr, index);
+	err = snd_card_proc_new(codec->bus->card, name, &entry);
+	if (err < 0)
+		return err;
+
+	snd_info_set_text_ops(entry, eld, hdmi_print_eld_info);
+	entry->c.text.write = hdmi_write_eld_info;
+	entry->mode |= S_IWUSR;
+	eld->proc_entry = entry;
+
+	return 0;
+}
+
+void snd_hda_eld_proc_free(struct hda_codec *codec, struct hdmi_eld *eld)
+{
+	if (!codec->bus->shutdown && eld->proc_entry) {
+		snd_device_free(codec->bus->card, eld->proc_entry);
+		eld->proc_entry = NULL;
+	}
+}
+
+#endif /* CONFIG_PROC_FS */
+
+/* update PCM info based on ELD */
+void hdmi_eld_update_pcm_info(struct hdmi_eld *eld, struct hda_pcm_stream *pcm,
+			      struct hda_pcm_stream *codec_pars)
+{
+	int i;
+
+	/* assume basic audio support (the basic audio flag is not in ELD;
+	 * however, all audio capable sinks are required to support basic
+	 * audio) */
+	pcm->rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000;
+	pcm->formats = SNDRV_PCM_FMTBIT_S16_LE;
+	pcm->maxbps = 16;
+	pcm->channels_max = 2;
+	for (i = 0; i < eld->sad_count; i++) {
+		struct cea_sad *a = &eld->sad[i];
+		pcm->rates |= a->rates;
+		if (a->channels > pcm->channels_max)
+			pcm->channels_max = a->channels;
+		if (a->format == AUDIO_CODING_TYPE_LPCM) {
+			if (a->sample_bits & AC_SUPPCM_BITS_20) {
+				pcm->formats |= SNDRV_PCM_FMTBIT_S32_LE;
+				if (pcm->maxbps < 20)
+					pcm->maxbps = 20;
+			}
+			if (a->sample_bits & AC_SUPPCM_BITS_24) {
+				pcm->formats |= SNDRV_PCM_FMTBIT_S32_LE;
+				if (pcm->maxbps < 24)
+					pcm->maxbps = 24;
+			}
+		}
+	}
+
+	if (!codec_pars)
+		return;
+
+	/* restrict the parameters by the values the codec provides */
+	pcm->rates &= codec_pars->rates;
+	pcm->formats &= codec_pars->formats;
+	pcm->channels_max = min(pcm->channels_max, codec_pars->channels_max);
+	pcm->maxbps = min(pcm->maxbps, codec_pars->maxbps);
+}
diff --git a/linux/sound/pci/hda/hda_generic.c b/linux/sound/pci/hda/hda_generic.c
new file mode 100644
index 0000000..fb0582f
--- /dev/null
+++ b/linux/sound/pci/hda/hda_generic.c
@@ -0,0 +1,1083 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Generic widget tree parser
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+/* widget node for parsing */
+struct hda_gnode {
+	hda_nid_t nid;		/* NID of this widget */
+	unsigned short nconns;	/* number of input connections */
+	hda_nid_t *conn_list;
+	hda_nid_t slist[2];	/* temporay list */
+	unsigned int wid_caps;	/* widget capabilities */
+	unsigned char type;	/* widget type */
+	unsigned char pin_ctl;	/* pin controls */
+	unsigned char checked;	/* the flag indicates that the node is already parsed */
+	unsigned int pin_caps;	/* pin widget capabilities */
+	unsigned int def_cfg;	/* default configuration */
+	unsigned int amp_out_caps;	/* AMP out capabilities */
+	unsigned int amp_in_caps;	/* AMP in capabilities */
+	struct list_head list;
+};
+
+/* patch-specific record */
+
+#define MAX_PCM_VOLS	2
+struct pcm_vol {
+	struct hda_gnode *node;	/* Node for PCM volume */
+	unsigned int index;	/* connection of PCM volume */
+};
+
+struct hda_gspec {
+	struct hda_gnode *dac_node[2];	/* DAC node */
+	struct hda_gnode *out_pin_node[2];	/* Output pin (Line-Out) node */
+	struct pcm_vol pcm_vol[MAX_PCM_VOLS];	/* PCM volumes */
+	unsigned int pcm_vol_nodes;	/* number of PCM volumes */
+
+	struct hda_gnode *adc_node;	/* ADC node */
+	struct hda_gnode *cap_vol_node;	/* Node for capture volume */
+	unsigned int cur_cap_src;	/* current capture source */
+	struct hda_input_mux input_mux;
+
+	unsigned int def_amp_in_caps;
+	unsigned int def_amp_out_caps;
+
+	struct hda_pcm pcm_rec;		/* PCM information */
+
+	struct list_head nid_list;	/* list of widgets */
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+#define MAX_LOOPBACK_AMPS	7
+	struct hda_loopback_check loopback;
+	int num_loopbacks;
+	struct hda_amp_list loopback_list[MAX_LOOPBACK_AMPS + 1];
+#endif
+};
+
+/*
+ * retrieve the default device type from the default config value
+ */
+#define defcfg_type(node) (((node)->def_cfg & AC_DEFCFG_DEVICE) >> \
+			   AC_DEFCFG_DEVICE_SHIFT)
+#define defcfg_location(node) (((node)->def_cfg & AC_DEFCFG_LOCATION) >> \
+			       AC_DEFCFG_LOCATION_SHIFT)
+#define defcfg_port_conn(node) (((node)->def_cfg & AC_DEFCFG_PORT_CONN) >> \
+				AC_DEFCFG_PORT_CONN_SHIFT)
+
+/*
+ * destructor
+ */
+static void snd_hda_generic_free(struct hda_codec *codec)
+{
+	struct hda_gspec *spec = codec->spec;
+	struct hda_gnode *node, *n;
+
+	if (! spec)
+		return;
+	/* free all widgets */
+	list_for_each_entry_safe(node, n, &spec->nid_list, list) {
+		if (node->conn_list != node->slist)
+			kfree(node->conn_list);
+		kfree(node);
+	}
+	kfree(spec);
+}
+
+
+/*
+ * add a new widget node and read its attributes
+ */
+static int add_new_node(struct hda_codec *codec, struct hda_gspec *spec, hda_nid_t nid)
+{
+	struct hda_gnode *node;
+	int nconns;
+	hda_nid_t conn_list[HDA_MAX_CONNECTIONS];
+
+	node = kzalloc(sizeof(*node), GFP_KERNEL);
+	if (node == NULL)
+		return -ENOMEM;
+	node->nid = nid;
+	node->wid_caps = get_wcaps(codec, nid);
+	node->type = get_wcaps_type(node->wid_caps);
+	if (node->wid_caps & AC_WCAP_CONN_LIST) {
+		nconns = snd_hda_get_connections(codec, nid, conn_list,
+						 HDA_MAX_CONNECTIONS);
+		if (nconns < 0) {
+			kfree(node);
+			return nconns;
+		}
+	} else {
+		nconns = 0;
+	}
+	if (nconns <= ARRAY_SIZE(node->slist))
+		node->conn_list = node->slist;
+	else {
+		node->conn_list = kmalloc(sizeof(hda_nid_t) * nconns,
+					  GFP_KERNEL);
+		if (! node->conn_list) {
+			snd_printk(KERN_ERR "hda-generic: cannot malloc\n");
+			kfree(node);
+			return -ENOMEM;
+		}
+	}
+	memcpy(node->conn_list, conn_list, nconns * sizeof(hda_nid_t));
+	node->nconns = nconns;
+
+	if (node->type == AC_WID_PIN) {
+		node->pin_caps = snd_hda_query_pin_caps(codec, node->nid);
+		node->pin_ctl = snd_hda_codec_read(codec, node->nid, 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+		node->def_cfg = snd_hda_codec_get_pincfg(codec, node->nid);
+	}
+
+	if (node->wid_caps & AC_WCAP_OUT_AMP) {
+		if (node->wid_caps & AC_WCAP_AMP_OVRD)
+			node->amp_out_caps = snd_hda_param_read(codec, node->nid, AC_PAR_AMP_OUT_CAP);
+		if (! node->amp_out_caps)
+			node->amp_out_caps = spec->def_amp_out_caps;
+	}
+	if (node->wid_caps & AC_WCAP_IN_AMP) {
+		if (node->wid_caps & AC_WCAP_AMP_OVRD)
+			node->amp_in_caps = snd_hda_param_read(codec, node->nid, AC_PAR_AMP_IN_CAP);
+		if (! node->amp_in_caps)
+			node->amp_in_caps = spec->def_amp_in_caps;
+	}
+	list_add_tail(&node->list, &spec->nid_list);
+	return 0;
+}
+
+/*
+ * build the AFG subtree
+ */
+static int build_afg_tree(struct hda_codec *codec)
+{
+	struct hda_gspec *spec = codec->spec;
+	int i, nodes, err;
+	hda_nid_t nid;
+
+	if (snd_BUG_ON(!spec))
+		return -EINVAL;
+
+	spec->def_amp_out_caps = snd_hda_param_read(codec, codec->afg, AC_PAR_AMP_OUT_CAP);
+	spec->def_amp_in_caps = snd_hda_param_read(codec, codec->afg, AC_PAR_AMP_IN_CAP);
+
+	nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid);
+	if (! nid || nodes < 0) {
+		printk(KERN_ERR "Invalid AFG subtree\n");
+		return -EINVAL;
+	}
+
+	/* parse all nodes belonging to the AFG */
+	for (i = 0; i < nodes; i++, nid++) {
+		if ((err = add_new_node(codec, spec, nid)) < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+
+/*
+ * look for the node record for the given NID
+ */
+/* FIXME: should avoid the braindead linear search */
+static struct hda_gnode *hda_get_node(struct hda_gspec *spec, hda_nid_t nid)
+{
+	struct hda_gnode *node;
+
+	list_for_each_entry(node, &spec->nid_list, list) {
+		if (node->nid == nid)
+			return node;
+	}
+	return NULL;
+}
+
+/*
+ * unmute (and set max vol) the output amplifier
+ */
+static int unmute_output(struct hda_codec *codec, struct hda_gnode *node)
+{
+	unsigned int val, ofs;
+	snd_printdd("UNMUTE OUT: NID=0x%x\n", node->nid);
+	val = (node->amp_out_caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+	ofs = (node->amp_out_caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT;
+	if (val >= ofs)
+		val -= ofs;
+	snd_hda_codec_amp_stereo(codec, node->nid, HDA_OUTPUT, 0, 0xff, val);
+	return 0;
+}
+
+/*
+ * unmute (and set max vol) the input amplifier
+ */
+static int unmute_input(struct hda_codec *codec, struct hda_gnode *node, unsigned int index)
+{
+	unsigned int val, ofs;
+	snd_printdd("UNMUTE IN: NID=0x%x IDX=0x%x\n", node->nid, index);
+	val = (node->amp_in_caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+	ofs = (node->amp_in_caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT;
+	if (val >= ofs)
+		val -= ofs;
+	snd_hda_codec_amp_stereo(codec, node->nid, HDA_INPUT, index, 0xff, val);
+	return 0;
+}
+
+/*
+ * select the input connection of the given node.
+ */
+static int select_input_connection(struct hda_codec *codec, struct hda_gnode *node,
+				   unsigned int index)
+{
+	snd_printdd("CONNECT: NID=0x%x IDX=0x%x\n", node->nid, index);
+	return snd_hda_codec_write_cache(codec, node->nid, 0,
+					 AC_VERB_SET_CONNECT_SEL, index);
+}
+
+/*
+ * clear checked flag of each node in the node list
+ */
+static void clear_check_flags(struct hda_gspec *spec)
+{
+	struct hda_gnode *node;
+
+	list_for_each_entry(node, &spec->nid_list, list) {
+		node->checked = 0;
+	}
+}
+
+/*
+ * parse the output path recursively until reach to an audio output widget
+ *
+ * returns 0 if not found, 1 if found, or a negative error code.
+ */
+static int parse_output_path(struct hda_codec *codec, struct hda_gspec *spec,
+			     struct hda_gnode *node, int dac_idx)
+{
+	int i, err;
+	struct hda_gnode *child;
+
+	if (node->checked)
+		return 0;
+
+	node->checked = 1;
+	if (node->type == AC_WID_AUD_OUT) {
+		if (node->wid_caps & AC_WCAP_DIGITAL) {
+			snd_printdd("Skip Digital OUT node %x\n", node->nid);
+			return 0;
+		}
+		snd_printdd("AUD_OUT found %x\n", node->nid);
+		if (spec->dac_node[dac_idx]) {
+			/* already DAC node is assigned, just unmute & connect */
+			return node == spec->dac_node[dac_idx];
+		}
+		spec->dac_node[dac_idx] = node;
+		if ((node->wid_caps & AC_WCAP_OUT_AMP) &&
+		    spec->pcm_vol_nodes < MAX_PCM_VOLS) {
+			spec->pcm_vol[spec->pcm_vol_nodes].node = node;
+			spec->pcm_vol[spec->pcm_vol_nodes].index = 0;
+			spec->pcm_vol_nodes++;
+		}
+		return 1; /* found */
+	}
+
+	for (i = 0; i < node->nconns; i++) {
+		child = hda_get_node(spec, node->conn_list[i]);
+		if (! child)
+			continue;
+		err = parse_output_path(codec, spec, child, dac_idx);
+		if (err < 0)
+			return err;
+		else if (err > 0) {
+			/* found one,
+			 * select the path, unmute both input and output
+			 */
+			if (node->nconns > 1)
+				select_input_connection(codec, node, i);
+			unmute_input(codec, node, i);
+			unmute_output(codec, node);
+			if (spec->dac_node[dac_idx] &&
+			    spec->pcm_vol_nodes < MAX_PCM_VOLS &&
+			    !(spec->dac_node[dac_idx]->wid_caps &
+			      AC_WCAP_OUT_AMP)) {
+				if ((node->wid_caps & AC_WCAP_IN_AMP) ||
+				    (node->wid_caps & AC_WCAP_OUT_AMP)) {
+					int n = spec->pcm_vol_nodes;
+					spec->pcm_vol[n].node = node;
+					spec->pcm_vol[n].index = i;
+					spec->pcm_vol_nodes++;
+				}
+			}
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Look for the output PIN widget with the given jack type
+ * and parse the output path to that PIN.
+ *
+ * Returns the PIN node when the path to DAC is established.
+ */
+static struct hda_gnode *parse_output_jack(struct hda_codec *codec,
+					   struct hda_gspec *spec,
+					   int jack_type)
+{
+	struct hda_gnode *node;
+	int err;
+
+	list_for_each_entry(node, &spec->nid_list, list) {
+		if (node->type != AC_WID_PIN)
+			continue;
+		/* output capable? */
+		if (! (node->pin_caps & AC_PINCAP_OUT))
+			continue;
+		if (defcfg_port_conn(node) == AC_JACK_PORT_NONE)
+			continue; /* unconnected */
+		if (jack_type >= 0) {
+			if (jack_type != defcfg_type(node))
+				continue;
+			if (node->wid_caps & AC_WCAP_DIGITAL)
+				continue; /* skip SPDIF */
+		} else {
+			/* output as default? */
+			if (! (node->pin_ctl & AC_PINCTL_OUT_EN))
+				continue;
+		}
+		clear_check_flags(spec);
+		err = parse_output_path(codec, spec, node, 0);
+		if (err < 0)
+			return NULL;
+		if (! err && spec->out_pin_node[0]) {
+			err = parse_output_path(codec, spec, node, 1);
+			if (err < 0)
+				return NULL;
+		}
+		if (err > 0) {
+			/* unmute the PIN output */
+			unmute_output(codec, node);
+			/* set PIN-Out enable */
+			snd_hda_codec_write_cache(codec, node->nid, 0,
+					    AC_VERB_SET_PIN_WIDGET_CONTROL,
+					    AC_PINCTL_OUT_EN |
+					    ((node->pin_caps & AC_PINCAP_HP_DRV) ?
+					     AC_PINCTL_HP_EN : 0));
+			return node;
+		}
+	}
+	return NULL;
+}
+
+
+/*
+ * parse outputs
+ */
+static int parse_output(struct hda_codec *codec)
+{
+	struct hda_gspec *spec = codec->spec;
+	struct hda_gnode *node;
+
+	/*
+	 * Look for the output PIN widget
+	 */
+	/* first, look for the line-out pin */
+	node = parse_output_jack(codec, spec, AC_JACK_LINE_OUT);
+	if (node) /* found, remember the PIN node */
+		spec->out_pin_node[0] = node;
+	else {
+		/* if no line-out is found, try speaker out */
+		node = parse_output_jack(codec, spec, AC_JACK_SPEAKER);
+		if (node)
+			spec->out_pin_node[0] = node;
+	}
+	/* look for the HP-out pin */
+	node = parse_output_jack(codec, spec, AC_JACK_HP_OUT);
+	if (node) {
+		if (! spec->out_pin_node[0])
+			spec->out_pin_node[0] = node;
+		else
+			spec->out_pin_node[1] = node;
+	}
+
+	if (! spec->out_pin_node[0]) {
+		/* no line-out or HP pins found,
+		 * then choose for the first output pin
+		 */
+		spec->out_pin_node[0] = parse_output_jack(codec, spec, -1);
+		if (! spec->out_pin_node[0])
+			snd_printd("hda_generic: no proper output path found\n");
+	}
+
+	return 0;
+}
+
+/*
+ * input MUX
+ */
+
+/* control callbacks */
+static int capture_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct hda_gspec *spec = codec->spec;
+	return snd_hda_input_mux_info(&spec->input_mux, uinfo);
+}
+
+static int capture_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct hda_gspec *spec = codec->spec;
+
+	ucontrol->value.enumerated.item[0] = spec->cur_cap_src;
+	return 0;
+}
+
+static int capture_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct hda_gspec *spec = codec->spec;
+	return snd_hda_input_mux_put(codec, &spec->input_mux, ucontrol,
+				     spec->adc_node->nid, &spec->cur_cap_src);
+}
+
+/*
+ * return the string name of the given input PIN widget
+ */
+static const char *get_input_type(struct hda_gnode *node, unsigned int *pinctl)
+{
+	unsigned int location = defcfg_location(node);
+	switch (defcfg_type(node)) {
+	case AC_JACK_LINE_IN:
+		if ((location & 0x0f) == AC_JACK_LOC_FRONT)
+			return "Front Line";
+		return "Line";
+	case AC_JACK_CD:
+#if 0
+		if (pinctl)
+			*pinctl |= AC_PINCTL_VREF_GRD;
+#endif
+		return "CD";
+	case AC_JACK_AUX:
+		if ((location & 0x0f) == AC_JACK_LOC_FRONT)
+			return "Front Aux";
+		return "Aux";
+	case AC_JACK_MIC_IN:
+		if (pinctl &&
+		    (node->pin_caps &
+		     (AC_PINCAP_VREF_80 << AC_PINCAP_VREF_SHIFT)))
+			*pinctl |= AC_PINCTL_VREF_80;
+		if ((location & 0x0f) == AC_JACK_LOC_FRONT)
+			return "Front Mic";
+		return "Mic";
+	case AC_JACK_SPDIF_IN:
+		return "SPDIF";
+	case AC_JACK_DIG_OTHER_IN:
+		return "Digital";
+	}
+	return NULL;
+}
+
+/*
+ * parse the nodes recursively until reach to the input PIN
+ *
+ * returns 0 if not found, 1 if found, or a negative error code.
+ */
+static int parse_adc_sub_nodes(struct hda_codec *codec, struct hda_gspec *spec,
+			       struct hda_gnode *node, int idx)
+{
+	int i, err;
+	unsigned int pinctl;
+	const char *type;
+
+	if (node->checked)
+		return 0;
+
+	node->checked = 1;
+	if (node->type != AC_WID_PIN) {
+		for (i = 0; i < node->nconns; i++) {
+			struct hda_gnode *child;
+			child = hda_get_node(spec, node->conn_list[i]);
+			if (! child)
+				continue;
+			err = parse_adc_sub_nodes(codec, spec, child, idx);
+			if (err < 0)
+				return err;
+			if (err > 0) {
+				/* found one,
+				 * select the path, unmute both input and output
+				 */
+				if (node->nconns > 1)
+					select_input_connection(codec, node, i);
+				unmute_input(codec, node, i);
+				unmute_output(codec, node);
+				return err;
+			}
+		}
+		return 0;
+	}
+
+	/* input capable? */
+	if (! (node->pin_caps & AC_PINCAP_IN))
+		return 0;
+
+	if (defcfg_port_conn(node) == AC_JACK_PORT_NONE)
+		return 0; /* unconnected */
+
+	if (node->wid_caps & AC_WCAP_DIGITAL)
+		return 0; /* skip SPDIF */
+
+	if (spec->input_mux.num_items >= HDA_MAX_NUM_INPUTS) {
+		snd_printk(KERN_ERR "hda_generic: Too many items for capture\n");
+		return -EINVAL;
+	}
+
+	pinctl = AC_PINCTL_IN_EN;
+	/* create a proper capture source label */
+	type = get_input_type(node, &pinctl);
+	if (! type) {
+		/* input as default? */
+		if (! (node->pin_ctl & AC_PINCTL_IN_EN))
+			return 0;
+		type = "Input";
+	}
+	snd_hda_add_imux_item(&spec->input_mux, type, idx, NULL);
+
+	/* unmute the PIN external input */
+	unmute_input(codec, node, 0); /* index = 0? */
+	/* set PIN-In enable */
+	snd_hda_codec_write_cache(codec, node->nid, 0,
+				  AC_VERB_SET_PIN_WIDGET_CONTROL, pinctl);
+
+	return 1; /* found */
+}
+
+/*
+ * parse input
+ */
+static int parse_input_path(struct hda_codec *codec, struct hda_gnode *adc_node)
+{
+	struct hda_gspec *spec = codec->spec;
+	struct hda_gnode *node;
+	int i, err;
+
+	snd_printdd("AUD_IN = %x\n", adc_node->nid);
+	clear_check_flags(spec);
+
+	// awk added - fixed no recording due to muted widget
+	unmute_input(codec, adc_node, 0);
+	
+	/*
+	 * check each connection of the ADC
+	 * if it reaches to a proper input PIN, add the path as the
+	 * input path.
+	 */
+	/* first, check the direct connections to PIN widgets */
+	for (i = 0; i < adc_node->nconns; i++) {
+		node = hda_get_node(spec, adc_node->conn_list[i]);
+		if (node && node->type == AC_WID_PIN) {
+			err = parse_adc_sub_nodes(codec, spec, node, i);
+			if (err < 0)
+				return err;
+		}
+	}
+	/* ... then check the rests, more complicated connections */
+	for (i = 0; i < adc_node->nconns; i++) {
+		node = hda_get_node(spec, adc_node->conn_list[i]);
+		if (node && node->type != AC_WID_PIN) {
+			err = parse_adc_sub_nodes(codec, spec, node, i);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	if (! spec->input_mux.num_items)
+		return 0; /* no input path found... */
+
+	snd_printdd("[Capture Source] NID=0x%x, #SRC=%d\n", adc_node->nid, spec->input_mux.num_items);
+	for (i = 0; i < spec->input_mux.num_items; i++)
+		snd_printdd("  [%s] IDX=0x%x\n", spec->input_mux.items[i].label,
+			    spec->input_mux.items[i].index);
+
+	spec->adc_node = adc_node;
+	return 1;
+}
+
+/*
+ * parse input
+ */
+static int parse_input(struct hda_codec *codec)
+{
+	struct hda_gspec *spec = codec->spec;
+	struct hda_gnode *node;
+	int err;
+
+	/*
+	 * At first we look for an audio input widget.
+	 * If it reaches to certain input PINs, we take it as the
+	 * input path.
+	 */
+	list_for_each_entry(node, &spec->nid_list, list) {
+		if (node->wid_caps & AC_WCAP_DIGITAL)
+			continue; /* skip SPDIF */
+		if (node->type == AC_WID_AUD_IN) {
+			err = parse_input_path(codec, node);
+			if (err < 0)
+				return err;
+			else if (err > 0)
+				return 0;
+		}
+	}
+	snd_printd("hda_generic: no proper input path found\n");
+	return 0;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static void add_input_loopback(struct hda_codec *codec, hda_nid_t nid,
+			       int dir, int idx)
+{
+	struct hda_gspec *spec = codec->spec;
+	struct hda_amp_list *p;
+
+	if (spec->num_loopbacks >= MAX_LOOPBACK_AMPS) {
+		snd_printk(KERN_ERR "hda_generic: Too many loopback ctls\n");
+		return;
+	}
+	p = &spec->loopback_list[spec->num_loopbacks++];
+	p->nid = nid;
+	p->dir = dir;
+	p->idx = idx;
+	spec->loopback.amplist = spec->loopback_list;
+}
+#else
+#define add_input_loopback(codec,nid,dir,idx)
+#endif
+
+/*
+ * create mixer controls if possible
+ */
+static int create_mixer(struct hda_codec *codec, struct hda_gnode *node,
+			unsigned int index, const char *type,
+			const char *dir_sfx, int is_loopback)
+{
+	char name[32];
+	int err;
+	int created = 0;
+	struct snd_kcontrol_new knew;
+
+	if (type)
+		sprintf(name, "%s %s Switch", type, dir_sfx);
+	else
+		sprintf(name, "%s Switch", dir_sfx);
+	if ((node->wid_caps & AC_WCAP_IN_AMP) &&
+	    (node->amp_in_caps & AC_AMPCAP_MUTE)) {
+		knew = (struct snd_kcontrol_new)HDA_CODEC_MUTE(name, node->nid, index, HDA_INPUT);
+		if (is_loopback)
+			add_input_loopback(codec, node->nid, HDA_INPUT, index);
+		snd_printdd("[%s] NID=0x%x, DIR=IN, IDX=0x%x\n", name, node->nid, index);
+		err = snd_hda_ctl_add(codec, node->nid,
+					snd_ctl_new1(&knew, codec));
+		if (err < 0)
+			return err;
+		created = 1;
+	} else if ((node->wid_caps & AC_WCAP_OUT_AMP) &&
+		   (node->amp_out_caps & AC_AMPCAP_MUTE)) {
+		knew = (struct snd_kcontrol_new)HDA_CODEC_MUTE(name, node->nid, 0, HDA_OUTPUT);
+		if (is_loopback)
+			add_input_loopback(codec, node->nid, HDA_OUTPUT, 0);
+		snd_printdd("[%s] NID=0x%x, DIR=OUT\n", name, node->nid);
+		err = snd_hda_ctl_add(codec, node->nid,
+					snd_ctl_new1(&knew, codec));
+		if (err < 0)
+			return err;
+		created = 1;
+	}
+
+	if (type)
+		sprintf(name, "%s %s Volume", type, dir_sfx);
+	else
+		sprintf(name, "%s Volume", dir_sfx);
+	if ((node->wid_caps & AC_WCAP_IN_AMP) &&
+	    (node->amp_in_caps & AC_AMPCAP_NUM_STEPS)) {
+		knew = (struct snd_kcontrol_new)HDA_CODEC_VOLUME(name, node->nid, index, HDA_INPUT);
+		snd_printdd("[%s] NID=0x%x, DIR=IN, IDX=0x%x\n", name, node->nid, index);
+		err = snd_hda_ctl_add(codec, node->nid,
+					snd_ctl_new1(&knew, codec));
+		if (err < 0)
+			return err;
+		created = 1;
+	} else if ((node->wid_caps & AC_WCAP_OUT_AMP) &&
+		   (node->amp_out_caps & AC_AMPCAP_NUM_STEPS)) {
+		knew = (struct snd_kcontrol_new)HDA_CODEC_VOLUME(name, node->nid, 0, HDA_OUTPUT);
+		snd_printdd("[%s] NID=0x%x, DIR=OUT\n", name, node->nid);
+		err = snd_hda_ctl_add(codec, node->nid,
+					snd_ctl_new1(&knew, codec));
+		if (err < 0)
+			return err;
+		created = 1;
+	}
+
+	return created;
+}
+
+/*
+ * check whether the controls with the given name and direction suffix already exist
+ */
+static int check_existing_control(struct hda_codec *codec, const char *type, const char *dir)
+{
+	struct snd_ctl_elem_id id;
+	memset(&id, 0, sizeof(id));
+	sprintf(id.name, "%s %s Volume", type, dir);
+	id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	if (snd_ctl_find_id(codec->bus->card, &id))
+		return 1;
+	sprintf(id.name, "%s %s Switch", type, dir);
+	id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	if (snd_ctl_find_id(codec->bus->card, &id))
+		return 1;
+	return 0;
+}
+
+/*
+ * build output mixer controls
+ */
+static int create_output_mixers(struct hda_codec *codec, const char **names)
+{
+	struct hda_gspec *spec = codec->spec;
+	int i, err;
+
+	for (i = 0; i < spec->pcm_vol_nodes; i++) {
+		err = create_mixer(codec, spec->pcm_vol[i].node,
+				   spec->pcm_vol[i].index,
+				   names[i], "Playback", 0);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int build_output_controls(struct hda_codec *codec)
+{
+	struct hda_gspec *spec = codec->spec;
+	static const char *types_speaker[] = { "Speaker", "Headphone" };
+	static const char *types_line[] = { "Front", "Headphone" };
+
+	switch (spec->pcm_vol_nodes) {
+	case 1:
+		return create_mixer(codec, spec->pcm_vol[0].node,
+				    spec->pcm_vol[0].index,
+				    "Master", "Playback", 0);
+	case 2:
+		if (defcfg_type(spec->out_pin_node[0]) == AC_JACK_SPEAKER)
+			return create_output_mixers(codec, types_speaker);
+		else
+			return create_output_mixers(codec, types_line);
+	}
+	return 0;
+}
+
+/* create capture volume/switch */
+static int build_input_controls(struct hda_codec *codec)
+{
+	struct hda_gspec *spec = codec->spec;
+	struct hda_gnode *adc_node = spec->adc_node;
+	int i, err;
+	static struct snd_kcontrol_new cap_sel = {
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = capture_source_info,
+		.get = capture_source_get,
+		.put = capture_source_put,
+	};
+
+	if (! adc_node || ! spec->input_mux.num_items)
+		return 0; /* not found */
+
+	spec->cur_cap_src = 0;
+	select_input_connection(codec, adc_node,
+				spec->input_mux.items[0].index);
+
+	/* create capture volume and switch controls if the ADC has an amp */
+	/* do we have only a single item? */
+	if (spec->input_mux.num_items == 1) {
+		err = create_mixer(codec, adc_node,
+				   spec->input_mux.items[0].index,
+				   NULL, "Capture", 0);
+		if (err < 0)
+			return err;
+		return 0;
+	}
+
+	/* create input MUX if multiple sources are available */
+	err = snd_hda_ctl_add(codec, spec->adc_node->nid,
+			      snd_ctl_new1(&cap_sel, codec));
+	if (err < 0)
+		return err;
+
+	/* no volume control? */
+	if (! (adc_node->wid_caps & AC_WCAP_IN_AMP) ||
+	    ! (adc_node->amp_in_caps & AC_AMPCAP_NUM_STEPS))
+		return 0;
+
+	for (i = 0; i < spec->input_mux.num_items; i++) {
+		struct snd_kcontrol_new knew;
+		char name[32];
+		sprintf(name, "%s Capture Volume",
+			spec->input_mux.items[i].label);
+		knew = (struct snd_kcontrol_new)
+			HDA_CODEC_VOLUME(name, adc_node->nid,
+					 spec->input_mux.items[i].index,
+					 HDA_INPUT);
+		err = snd_hda_ctl_add(codec, adc_node->nid,
+					snd_ctl_new1(&knew, codec));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+
+/*
+ * parse the nodes recursively until reach to the output PIN.
+ *
+ * returns 0 - if not found,
+ *         1 - if found, but no mixer is created
+ *         2 - if found and mixer was already created, (just skip)
+ *         a negative error code
+ */
+static int parse_loopback_path(struct hda_codec *codec, struct hda_gspec *spec,
+			       struct hda_gnode *node, struct hda_gnode *dest_node,
+			       const char *type)
+{
+	int i, err;
+
+	if (node->checked)
+		return 0;
+
+	node->checked = 1;
+	if (node == dest_node) {
+		/* loopback connection found */
+		return 1;
+	}
+
+	for (i = 0; i < node->nconns; i++) {
+		struct hda_gnode *child = hda_get_node(spec, node->conn_list[i]);
+		if (! child)
+			continue;
+		err = parse_loopback_path(codec, spec, child, dest_node, type);
+		if (err < 0)
+			return err;
+		else if (err >= 1) {
+			if (err == 1) {
+				err = create_mixer(codec, node, i, type,
+						   "Playback", 1);
+				if (err < 0)
+					return err;
+				if (err > 0)
+					return 2; /* ok, created */
+				/* not created, maybe in the lower path */
+				err = 1;
+			}
+			/* connect and unmute */
+			if (node->nconns > 1)
+				select_input_connection(codec, node, i);
+			unmute_input(codec, node, i);
+			unmute_output(codec, node);
+			return err;
+		}
+	}
+	return 0;
+}
+
+/*
+ * parse the tree and build the loopback controls
+ */
+static int build_loopback_controls(struct hda_codec *codec)
+{
+	struct hda_gspec *spec = codec->spec;
+	struct hda_gnode *node;
+	int err;
+	const char *type;
+
+	if (! spec->out_pin_node[0])
+		return 0;
+
+	list_for_each_entry(node, &spec->nid_list, list) {
+		if (node->type != AC_WID_PIN)
+			continue;
+		/* input capable? */
+		if (! (node->pin_caps & AC_PINCAP_IN))
+			return 0;
+		type = get_input_type(node, NULL);
+		if (type) {
+			if (check_existing_control(codec, type, "Playback"))
+				continue;
+			clear_check_flags(spec);
+			err = parse_loopback_path(codec, spec,
+						  spec->out_pin_node[0],
+						  node, type);
+			if (err < 0)
+				return err;
+			if (! err)
+				continue;
+		}
+	}
+	return 0;
+}
+
+/*
+ * build mixer controls
+ */
+static int build_generic_controls(struct hda_codec *codec)
+{
+	int err;
+
+	if ((err = build_input_controls(codec)) < 0 ||
+	    (err = build_output_controls(codec)) < 0 ||
+	    (err = build_loopback_controls(codec)) < 0)
+		return err;
+
+	return 0;
+}
+
+/*
+ * PCM
+ */
+static struct hda_pcm_stream generic_pcm_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+};
+
+static int generic_pcm2_prepare(struct hda_pcm_stream *hinfo,
+				struct hda_codec *codec,
+				unsigned int stream_tag,
+				unsigned int format,
+				struct snd_pcm_substream *substream)
+{
+	struct hda_gspec *spec = codec->spec;
+
+	snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format);
+	snd_hda_codec_setup_stream(codec, spec->dac_node[1]->nid,
+				   stream_tag, 0, format);
+	return 0;
+}
+
+static int generic_pcm2_cleanup(struct hda_pcm_stream *hinfo,
+				struct hda_codec *codec,
+				struct snd_pcm_substream *substream)
+{
+	struct hda_gspec *spec = codec->spec;
+
+	snd_hda_codec_cleanup_stream(codec, hinfo->nid);
+	snd_hda_codec_cleanup_stream(codec, spec->dac_node[1]->nid);
+	return 0;
+}
+
+static int build_generic_pcms(struct hda_codec *codec)
+{
+	struct hda_gspec *spec = codec->spec;
+	struct hda_pcm *info = &spec->pcm_rec;
+
+	if (! spec->dac_node[0] && ! spec->adc_node) {
+		snd_printd("hda_generic: no PCM found\n");
+		return 0;
+	}
+
+	codec->num_pcms = 1;
+	codec->pcm_info = info;
+
+	info->name = "HDA Generic";
+	if (spec->dac_node[0]) {
+		info->stream[0] = generic_pcm_playback;
+		info->stream[0].nid = spec->dac_node[0]->nid;
+		if (spec->dac_node[1]) {
+			info->stream[0].ops.prepare = generic_pcm2_prepare;
+			info->stream[0].ops.cleanup = generic_pcm2_cleanup;
+		}
+	}
+	if (spec->adc_node) {
+		info->stream[1] = generic_pcm_playback;
+		info->stream[1].nid = spec->adc_node->nid;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static int generic_check_power_status(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct hda_gspec *spec = codec->spec;
+	return snd_hda_check_amp_list_power(codec, &spec->loopback, nid);
+}
+#endif
+
+
+/*
+ */
+static struct hda_codec_ops generic_patch_ops = {
+	.build_controls = build_generic_controls,
+	.build_pcms = build_generic_pcms,
+	.free = snd_hda_generic_free,
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	.check_power_status = generic_check_power_status,
+#endif
+};
+
+/*
+ * the generic parser
+ */
+int snd_hda_parse_generic_codec(struct hda_codec *codec)
+{
+	struct hda_gspec *spec;
+	int err;
+
+	if(!codec->afg)
+		return 0;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL) {
+		printk(KERN_ERR "hda_generic: can't allocate spec\n");
+		return -ENOMEM;
+	}
+	codec->spec = spec;
+	INIT_LIST_HEAD(&spec->nid_list);
+
+	if ((err = build_afg_tree(codec)) < 0)
+		goto error;
+
+	if ((err = parse_input(codec)) < 0 ||
+	    (err = parse_output(codec)) < 0)
+		goto error;
+
+	codec->patch_ops = generic_patch_ops;
+
+	return 0;
+
+ error:
+	snd_hda_generic_free(codec);
+	return err;
+}
+EXPORT_SYMBOL(snd_hda_parse_generic_codec);
diff --git a/linux/sound/pci/hda/hda_hwdep.c b/linux/sound/pci/hda/hda_hwdep.c
new file mode 100644
index 0000000..bf3ced5
--- /dev/null
+++ b/linux/sound/pci/hda/hda_hwdep.c
@@ -0,0 +1,818 @@
+/*
+ * HWDEP Interface for HD-audio codec
+ *
+ * Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/compat.h>
+#include <linux/mutex.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/firmware.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+#include <sound/hda_hwdep.h>
+#include <sound/minors.h>
+
+/* hint string pair */
+struct hda_hint {
+	const char *key;
+	const char *val;	/* contained in the same alloc as key */
+};
+
+/*
+ * write/read an out-of-bound verb
+ */
+static int verb_write_ioctl(struct hda_codec *codec,
+			    struct hda_verb_ioctl __user *arg)
+{
+	u32 verb, res;
+
+	if (get_user(verb, &arg->verb))
+		return -EFAULT;
+	res = snd_hda_codec_read(codec, verb >> 24, 0,
+				 (verb >> 8) & 0xffff, verb & 0xff);
+	if (put_user(res, &arg->res))
+		return -EFAULT;
+	return 0;
+}
+
+static int get_wcap_ioctl(struct hda_codec *codec,
+			  struct hda_verb_ioctl __user *arg)
+{
+	u32 verb, res;
+	
+	if (get_user(verb, &arg->verb))
+		return -EFAULT;
+	res = get_wcaps(codec, verb >> 24);
+	if (put_user(res, &arg->res))
+		return -EFAULT;
+	return 0;
+}
+
+
+/*
+ */
+static int hda_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
+			   unsigned int cmd, unsigned long arg)
+{
+	struct hda_codec *codec = hw->private_data;
+	void __user *argp = (void __user *)arg;
+
+	switch (cmd) {
+	case HDA_IOCTL_PVERSION:
+		return put_user(HDA_HWDEP_VERSION, (int __user *)argp);
+	case HDA_IOCTL_VERB_WRITE:
+		return verb_write_ioctl(codec, argp);
+	case HDA_IOCTL_GET_WCAP:
+		return get_wcap_ioctl(codec, argp);
+	}
+	return -ENOIOCTLCMD;
+}
+
+#ifdef CONFIG_COMPAT
+static int hda_hwdep_ioctl_compat(struct snd_hwdep *hw, struct file *file,
+				  unsigned int cmd, unsigned long arg)
+{
+	return hda_hwdep_ioctl(hw, file, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+static int hda_hwdep_open(struct snd_hwdep *hw, struct file *file)
+{
+#ifndef CONFIG_SND_DEBUG_VERBOSE
+	if (!capable(CAP_SYS_RAWIO))
+		return -EACCES;
+#endif
+	return 0;
+}
+
+static void clear_hwdep_elements(struct hda_codec *codec)
+{
+	int i;
+
+	/* clear init verbs */
+	snd_array_free(&codec->init_verbs);
+	/* clear hints */
+	for (i = 0; i < codec->hints.used; i++) {
+		struct hda_hint *hint = snd_array_elem(&codec->hints, i);
+		kfree(hint->key); /* we don't need to free hint->val */
+	}
+	snd_array_free(&codec->hints);
+	snd_array_free(&codec->user_pins);
+}
+
+static void hwdep_free(struct snd_hwdep *hwdep)
+{
+	clear_hwdep_elements(hwdep->private_data);
+}
+
+int /*__devinit*/ snd_hda_create_hwdep(struct hda_codec *codec)
+{
+	char hwname[16];
+	struct snd_hwdep *hwdep;
+	int err;
+
+	sprintf(hwname, "HDA Codec %d", codec->addr);
+	err = snd_hwdep_new(codec->bus->card, hwname, codec->addr, &hwdep);
+	if (err < 0)
+		return err;
+	codec->hwdep = hwdep;
+	sprintf(hwdep->name, "HDA Codec %d", codec->addr);
+	hwdep->iface = SNDRV_HWDEP_IFACE_HDA;
+	hwdep->private_data = codec;
+	hwdep->private_free = hwdep_free;
+	hwdep->exclusive = 1;
+
+	hwdep->ops.open = hda_hwdep_open;
+	hwdep->ops.ioctl = hda_hwdep_ioctl;
+#ifdef CONFIG_COMPAT
+	hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat;
+#endif
+
+	snd_array_init(&codec->init_verbs, sizeof(struct hda_verb), 32);
+	snd_array_init(&codec->hints, sizeof(struct hda_hint), 32);
+	snd_array_init(&codec->user_pins, sizeof(struct hda_pincfg), 16);
+
+	return 0;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static ssize_t power_on_acct_show(struct device *dev,
+				  struct device_attribute *attr,
+				  char *buf)
+{
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);
+	struct hda_codec *codec = hwdep->private_data;
+	snd_hda_update_power_acct(codec);
+	return sprintf(buf, "%u\n", jiffies_to_msecs(codec->power_on_acct));
+}
+
+static ssize_t power_off_acct_show(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);
+	struct hda_codec *codec = hwdep->private_data;
+	snd_hda_update_power_acct(codec);
+	return sprintf(buf, "%u\n", jiffies_to_msecs(codec->power_off_acct));
+}
+
+static struct device_attribute power_attrs[] = {
+	__ATTR_RO(power_on_acct),
+	__ATTR_RO(power_off_acct),
+};
+
+int snd_hda_hwdep_add_power_sysfs(struct hda_codec *codec)
+{
+	struct snd_hwdep *hwdep = codec->hwdep;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(power_attrs); i++)
+		snd_add_device_sysfs_file(SNDRV_DEVICE_TYPE_HWDEP, hwdep->card,
+					  hwdep->device, &power_attrs[i]);
+	return 0;
+}
+#endif /* CONFIG_SND_HDA_POWER_SAVE */
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+
+/*
+ * sysfs interface
+ */
+
+static int clear_codec(struct hda_codec *codec)
+{
+	int err;
+
+	err = snd_hda_codec_reset(codec);
+	if (err < 0) {
+		snd_printk(KERN_ERR "The codec is being used, can't free.\n");
+		return err;
+	}
+	clear_hwdep_elements(codec);
+	return 0;
+}
+
+static int reconfig_codec(struct hda_codec *codec)
+{
+	int err;
+
+	snd_hda_power_up(codec);
+	snd_printk(KERN_INFO "hda-codec: reconfiguring\n");
+	err = snd_hda_codec_reset(codec);
+	if (err < 0) {
+		snd_printk(KERN_ERR
+			   "The codec is being used, can't reconfigure.\n");
+		goto error;
+	}
+	err = snd_hda_codec_configure(codec);
+	if (err < 0)
+		goto error;
+	/* rebuild PCMs */
+	err = snd_hda_codec_build_pcms(codec);
+	if (err < 0)
+		goto error;
+	/* rebuild mixers */
+	err = snd_hda_codec_build_controls(codec);
+	if (err < 0)
+		goto error;
+	err = snd_card_register(codec->bus->card);
+ error:
+	snd_hda_power_down(codec);
+	return err;
+}
+
+/*
+ * allocate a string at most len chars, and remove the trailing EOL
+ */
+static char *kstrndup_noeol(const char *src, size_t len)
+{
+	char *s = kstrndup(src, len, GFP_KERNEL);
+	char *p;
+	if (!s)
+		return NULL;
+	p = strchr(s, '\n');
+	if (p)
+		*p = 0;
+	return s;
+}
+
+#define CODEC_INFO_SHOW(type)					\
+static ssize_t type##_show(struct device *dev,			\
+			   struct device_attribute *attr,	\
+			   char *buf)				\
+{								\
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);		\
+	struct hda_codec *codec = hwdep->private_data;		\
+	return sprintf(buf, "0x%x\n", codec->type);		\
+}
+
+#define CODEC_INFO_STR_SHOW(type)				\
+static ssize_t type##_show(struct device *dev,			\
+			     struct device_attribute *attr,	\
+					char *buf)		\
+{								\
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);		\
+	struct hda_codec *codec = hwdep->private_data;		\
+	return sprintf(buf, "%s\n",				\
+		       codec->type ? codec->type : "");		\
+}
+
+CODEC_INFO_SHOW(vendor_id);
+CODEC_INFO_SHOW(subsystem_id);
+CODEC_INFO_SHOW(revision_id);
+CODEC_INFO_SHOW(afg);
+CODEC_INFO_SHOW(mfg);
+CODEC_INFO_STR_SHOW(vendor_name);
+CODEC_INFO_STR_SHOW(chip_name);
+CODEC_INFO_STR_SHOW(modelname);
+
+#define CODEC_INFO_STORE(type)					\
+static ssize_t type##_store(struct device *dev,			\
+			    struct device_attribute *attr,	\
+			    const char *buf, size_t count)	\
+{								\
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);		\
+	struct hda_codec *codec = hwdep->private_data;		\
+	unsigned long val;					\
+	int err = strict_strtoul(buf, 0, &val);			\
+	if (err < 0)						\
+		return err;					\
+	codec->type = val;					\
+	return count;						\
+}
+
+#define CODEC_INFO_STR_STORE(type)				\
+static ssize_t type##_store(struct device *dev,			\
+			    struct device_attribute *attr,	\
+			    const char *buf, size_t count)	\
+{								\
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);		\
+	struct hda_codec *codec = hwdep->private_data;		\
+	char *s = kstrndup_noeol(buf, 64);			\
+	if (!s)							\
+		return -ENOMEM;					\
+	kfree(codec->type);					\
+	codec->type = s;					\
+	return count;						\
+}
+
+CODEC_INFO_STORE(vendor_id);
+CODEC_INFO_STORE(subsystem_id);
+CODEC_INFO_STORE(revision_id);
+CODEC_INFO_STR_STORE(vendor_name);
+CODEC_INFO_STR_STORE(chip_name);
+CODEC_INFO_STR_STORE(modelname);
+
+#define CODEC_ACTION_STORE(type)				\
+static ssize_t type##_store(struct device *dev,			\
+			    struct device_attribute *attr,	\
+			    const char *buf, size_t count)	\
+{								\
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);		\
+	struct hda_codec *codec = hwdep->private_data;		\
+	int err = 0;						\
+	if (*buf)						\
+		err = type##_codec(codec);			\
+	return err < 0 ? err : count;				\
+}
+
+CODEC_ACTION_STORE(reconfig);
+CODEC_ACTION_STORE(clear);
+
+static ssize_t init_verbs_show(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);
+	struct hda_codec *codec = hwdep->private_data;
+	int i, len = 0;
+	for (i = 0; i < codec->init_verbs.used; i++) {
+		struct hda_verb *v = snd_array_elem(&codec->init_verbs, i);
+		len += snprintf(buf + len, PAGE_SIZE - len,
+				"0x%02x 0x%03x 0x%04x\n",
+				v->nid, v->verb, v->param);
+	}
+	return len;
+}
+
+static int parse_init_verbs(struct hda_codec *codec, const char *buf)
+{
+	struct hda_verb *v;
+	int nid, verb, param;
+
+	if (sscanf(buf, "%i %i %i", &nid, &verb, &param) != 3)
+		return -EINVAL;
+	if (!nid || !verb)
+		return -EINVAL;
+	v = snd_array_new(&codec->init_verbs);
+	if (!v)
+		return -ENOMEM;
+	v->nid = nid;
+	v->verb = verb;
+	v->param = param;
+	return 0;
+}
+
+static ssize_t init_verbs_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);
+	struct hda_codec *codec = hwdep->private_data;
+	int err = parse_init_verbs(codec, buf);
+	if (err < 0)
+		return err;
+	return count;
+}
+
+static ssize_t hints_show(struct device *dev,
+			  struct device_attribute *attr,
+			  char *buf)
+{
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);
+	struct hda_codec *codec = hwdep->private_data;
+	int i, len = 0;
+	for (i = 0; i < codec->hints.used; i++) {
+		struct hda_hint *hint = snd_array_elem(&codec->hints, i);
+		len += snprintf(buf + len, PAGE_SIZE - len,
+				"%s = %s\n", hint->key, hint->val);
+	}
+	return len;
+}
+
+static struct hda_hint *get_hint(struct hda_codec *codec, const char *key)
+{
+	int i;
+
+	for (i = 0; i < codec->hints.used; i++) {
+		struct hda_hint *hint = snd_array_elem(&codec->hints, i);
+		if (!strcmp(hint->key, key))
+			return hint;
+	}
+	return NULL;
+}
+
+static void remove_trail_spaces(char *str)
+{
+	char *p;
+	if (!*str)
+		return;
+	p = str + strlen(str) - 1;
+	for (; isspace(*p); p--) {
+		*p = 0;
+		if (p == str)
+			return;
+	}
+}
+
+#define MAX_HINTS	1024
+
+static int parse_hints(struct hda_codec *codec, const char *buf)
+{
+	char *key, *val;
+	struct hda_hint *hint;
+
+	buf = skip_spaces(buf);
+	if (!*buf || *buf == '#' || *buf == '\n')
+		return 0;
+	if (*buf == '=')
+		return -EINVAL;
+	key = kstrndup_noeol(buf, 1024);
+	if (!key)
+		return -ENOMEM;
+	/* extract key and val */
+	val = strchr(key, '=');
+	if (!val) {
+		kfree(key);
+		return -EINVAL;
+	}
+	*val++ = 0;
+	val = skip_spaces(val);
+	remove_trail_spaces(key);
+	remove_trail_spaces(val);
+	hint = get_hint(codec, key);
+	if (hint) {
+		/* replace */
+		kfree(hint->key);
+		hint->key = key;
+		hint->val = val;
+		return 0;
+	}
+	/* allocate a new hint entry */
+	if (codec->hints.used >= MAX_HINTS)
+		hint = NULL;
+	else
+		hint = snd_array_new(&codec->hints);
+	if (!hint) {
+		kfree(key);
+		return -ENOMEM;
+	}
+	hint->key = key;
+	hint->val = val;
+	return 0;
+}
+
+static ssize_t hints_store(struct device *dev,
+			   struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);
+	struct hda_codec *codec = hwdep->private_data;
+	int err = parse_hints(codec, buf);
+	if (err < 0)
+		return err;
+	return count;
+}
+
+static ssize_t pin_configs_show(struct hda_codec *codec,
+				struct snd_array *list,
+				char *buf)
+{
+	int i, len = 0;
+	for (i = 0; i < list->used; i++) {
+		struct hda_pincfg *pin = snd_array_elem(list, i);
+		len += sprintf(buf + len, "0x%02x 0x%08x\n",
+			       pin->nid, pin->cfg);
+	}
+	return len;
+}
+
+static ssize_t init_pin_configs_show(struct device *dev,
+				     struct device_attribute *attr,
+				     char *buf)
+{
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);
+	struct hda_codec *codec = hwdep->private_data;
+	return pin_configs_show(codec, &codec->init_pins, buf);
+}
+
+static ssize_t user_pin_configs_show(struct device *dev,
+				     struct device_attribute *attr,
+				     char *buf)
+{
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);
+	struct hda_codec *codec = hwdep->private_data;
+	return pin_configs_show(codec, &codec->user_pins, buf);
+}
+
+static ssize_t driver_pin_configs_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);
+	struct hda_codec *codec = hwdep->private_data;
+	return pin_configs_show(codec, &codec->driver_pins, buf);
+}
+
+#define MAX_PIN_CONFIGS		32
+
+static int parse_user_pin_configs(struct hda_codec *codec, const char *buf)
+{
+	int nid, cfg;
+
+	if (sscanf(buf, "%i %i", &nid, &cfg) != 2)
+		return -EINVAL;
+	if (!nid)
+		return -EINVAL;
+	return snd_hda_add_pincfg(codec, &codec->user_pins, nid, cfg);
+}
+
+static ssize_t user_pin_configs_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct snd_hwdep *hwdep = dev_get_drvdata(dev);
+	struct hda_codec *codec = hwdep->private_data;
+	int err = parse_user_pin_configs(codec, buf);
+	if (err < 0)
+		return err;
+	return count;
+}
+
+#define CODEC_ATTR_RW(type) \
+	__ATTR(type, 0644, type##_show, type##_store)
+#define CODEC_ATTR_RO(type) \
+	__ATTR_RO(type)
+#define CODEC_ATTR_WO(type) \
+	__ATTR(type, 0200, NULL, type##_store)
+
+static struct device_attribute codec_attrs[] = {
+	CODEC_ATTR_RW(vendor_id),
+	CODEC_ATTR_RW(subsystem_id),
+	CODEC_ATTR_RW(revision_id),
+	CODEC_ATTR_RO(afg),
+	CODEC_ATTR_RO(mfg),
+	CODEC_ATTR_RW(vendor_name),
+	CODEC_ATTR_RW(chip_name),
+	CODEC_ATTR_RW(modelname),
+	CODEC_ATTR_RW(init_verbs),
+	CODEC_ATTR_RW(hints),
+	CODEC_ATTR_RO(init_pin_configs),
+	CODEC_ATTR_RW(user_pin_configs),
+	CODEC_ATTR_RO(driver_pin_configs),
+	CODEC_ATTR_WO(reconfig),
+	CODEC_ATTR_WO(clear),
+};
+
+/*
+ * create sysfs files on hwdep directory
+ */
+int snd_hda_hwdep_add_sysfs(struct hda_codec *codec)
+{
+	struct snd_hwdep *hwdep = codec->hwdep;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(codec_attrs); i++)
+		snd_add_device_sysfs_file(SNDRV_DEVICE_TYPE_HWDEP, hwdep->card,
+					  hwdep->device, &codec_attrs[i]);
+	return 0;
+}
+
+/*
+ * Look for hint string
+ */
+const char *snd_hda_get_hint(struct hda_codec *codec, const char *key)
+{
+	struct hda_hint *hint = get_hint(codec, key);
+	return hint ? hint->val : NULL;
+}
+EXPORT_SYMBOL_HDA(snd_hda_get_hint);
+
+int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key)
+{
+	const char *p = snd_hda_get_hint(codec, key);
+	if (!p || !*p)
+		return -ENOENT;
+	switch (toupper(*p)) {
+	case 'T': /* true */
+	case 'Y': /* yes */
+	case '1':
+		return 1;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_get_bool_hint);
+
+#endif /* CONFIG_SND_HDA_RECONFIG */
+
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+
+/* parser mode */
+enum {
+	LINE_MODE_NONE,
+	LINE_MODE_CODEC,
+	LINE_MODE_MODEL,
+	LINE_MODE_PINCFG,
+	LINE_MODE_VERB,
+	LINE_MODE_HINT,
+	LINE_MODE_VENDOR_ID,
+	LINE_MODE_SUBSYSTEM_ID,
+	LINE_MODE_REVISION_ID,
+	LINE_MODE_CHIP_NAME,
+	NUM_LINE_MODES,
+};
+
+static inline int strmatch(const char *a, const char *b)
+{
+	return strnicmp(a, b, strlen(b)) == 0;
+}
+
+/* parse the contents after the line "[codec]"
+ * accept only the line with three numbers, and assign the current codec
+ */
+static void parse_codec_mode(char *buf, struct hda_bus *bus,
+			     struct hda_codec **codecp)
+{
+	unsigned int vendorid, subid, caddr;
+	struct hda_codec *codec;
+
+	*codecp = NULL;
+	if (sscanf(buf, "%i %i %i", &vendorid, &subid, &caddr) == 3) {
+		list_for_each_entry(codec, &bus->codec_list, list) {
+			if (codec->vendor_id == vendorid &&
+			    codec->subsystem_id == subid &&
+			    codec->addr == caddr) {
+				*codecp = codec;
+				break;
+			}
+		}
+	}
+}
+
+/* parse the contents after the other command tags, [pincfg], [verb],
+ * [vendor_id], [subsystem_id], [revision_id], [chip_name], [hint] and [model]
+ * just pass to the sysfs helper (only when any codec was specified)
+ */
+static void parse_pincfg_mode(char *buf, struct hda_bus *bus,
+			      struct hda_codec **codecp)
+{
+	parse_user_pin_configs(*codecp, buf);
+}
+
+static void parse_verb_mode(char *buf, struct hda_bus *bus,
+			    struct hda_codec **codecp)
+{
+	parse_init_verbs(*codecp, buf);
+}
+
+static void parse_hint_mode(char *buf, struct hda_bus *bus,
+			    struct hda_codec **codecp)
+{
+	parse_hints(*codecp, buf);
+}
+
+static void parse_model_mode(char *buf, struct hda_bus *bus,
+			     struct hda_codec **codecp)
+{
+	kfree((*codecp)->modelname);
+	(*codecp)->modelname = kstrdup(buf, GFP_KERNEL);
+}
+
+static void parse_chip_name_mode(char *buf, struct hda_bus *bus,
+				 struct hda_codec **codecp)
+{
+	kfree((*codecp)->chip_name);
+	(*codecp)->chip_name = kstrdup(buf, GFP_KERNEL);
+}
+
+#define DEFINE_PARSE_ID_MODE(name) \
+static void parse_##name##_mode(char *buf, struct hda_bus *bus, \
+				 struct hda_codec **codecp) \
+{ \
+	unsigned long val; \
+	if (!strict_strtoul(buf, 0, &val)) \
+		(*codecp)->name = val; \
+}
+
+DEFINE_PARSE_ID_MODE(vendor_id);
+DEFINE_PARSE_ID_MODE(subsystem_id);
+DEFINE_PARSE_ID_MODE(revision_id);
+
+
+struct hda_patch_item {
+	const char *tag;
+	void (*parser)(char *buf, struct hda_bus *bus, struct hda_codec **retc);
+	int need_codec;
+};
+
+static struct hda_patch_item patch_items[NUM_LINE_MODES] = {
+	[LINE_MODE_CODEC] = { "[codec]", parse_codec_mode, 0 },
+	[LINE_MODE_MODEL] = { "[model]", parse_model_mode, 1 },
+	[LINE_MODE_VERB] = { "[verb]", parse_verb_mode, 1 },
+	[LINE_MODE_PINCFG] = { "[pincfg]", parse_pincfg_mode, 1 },
+	[LINE_MODE_HINT] = { "[hint]", parse_hint_mode, 1 },
+	[LINE_MODE_VENDOR_ID] = { "[vendor_id]", parse_vendor_id_mode, 1 },
+	[LINE_MODE_SUBSYSTEM_ID] = { "[subsystem_id]", parse_subsystem_id_mode, 1 },
+	[LINE_MODE_REVISION_ID] = { "[revision_id]", parse_revision_id_mode, 1 },
+	[LINE_MODE_CHIP_NAME] = { "[chip_name]", parse_chip_name_mode, 1 },
+};
+
+/* check the line starting with '[' -- change the parser mode accodingly */
+static int parse_line_mode(char *buf, struct hda_bus *bus)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(patch_items); i++) {
+		if (!patch_items[i].tag)
+			continue;
+		if (strmatch(buf, patch_items[i].tag))
+			return i;
+	}
+	return LINE_MODE_NONE;
+}
+
+/* copy one line from the buffer in fw, and update the fields in fw
+ * return zero if it reaches to the end of the buffer, or non-zero
+ * if successfully copied a line
+ *
+ * the spaces at the beginning and the end of the line are stripped
+ */
+static int get_line_from_fw(char *buf, int size, struct firmware *fw)
+{
+	int len;
+	const char *p = fw->data;
+	while (isspace(*p) && fw->size) {
+		p++;
+		fw->size--;
+	}
+	if (!fw->size)
+		return 0;
+	if (size < fw->size)
+		size = fw->size;
+
+	for (len = 0; len < fw->size; len++) {
+		if (!*p)
+			break;
+		if (*p == '\n') {
+			p++;
+			len++;
+			break;
+		}
+		if (len < size)
+			*buf++ = *p++;
+	}
+	*buf = 0;
+	fw->size -= len;
+	fw->data = p;
+	remove_trail_spaces(buf);
+	return 1;
+}
+
+/*
+ * load a "patch" firmware file and parse it
+ */
+int snd_hda_load_patch(struct hda_bus *bus, const char *patch)
+{
+	int err;
+	const struct firmware *fw;
+	struct firmware tmp;
+	char buf[128];
+	struct hda_codec *codec;
+	int line_mode;
+	struct device *dev = bus->card->dev;
+
+	if (snd_BUG_ON(!dev))
+		return -ENODEV;
+	err = request_firmware(&fw, patch, dev);
+	if (err < 0) {
+		printk(KERN_ERR "hda-codec: Cannot load the patch '%s'\n",
+		       patch);
+		return err;
+	}
+
+	tmp = *fw;
+	line_mode = LINE_MODE_NONE;
+	codec = NULL;
+	while (get_line_from_fw(buf, sizeof(buf) - 1, &tmp)) {
+		if (!*buf || *buf == '#' || *buf == '\n')
+			continue;
+		if (*buf == '[')
+			line_mode = parse_line_mode(buf, bus);
+		else if (patch_items[line_mode].parser &&
+			 (codec || !patch_items[line_mode].need_codec))
+			patch_items[line_mode].parser(buf, bus, &codec);
+	}
+	release_firmware(fw);
+	return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_load_patch);
+#endif /* CONFIG_SND_HDA_PATCH_LOADER */
diff --git a/linux/sound/pci/hda/hda_intel.c b/linux/sound/pci/hda/hda_intel.c
new file mode 100644
index 0000000..a1c4008
--- /dev/null
+++ b/linux/sound/pci/hda/hda_intel.c
@@ -0,0 +1,2845 @@
+/*
+ *
+ *  hda_intel.c - Implementation of primary alsa driver code base
+ *                for Intel HD Audio.
+ *
+ *  Copyright(c) 2004 Intel Corporation. All rights reserved.
+ *
+ *  Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *                     PeiSen Hou <pshou@realtek.com.tw>
+ *
+ *  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.
+ *
+ *  CONTACTS:
+ *
+ *  Matt Jared		matt.jared@intel.com
+ *  Andy Kopp		andy.kopp@intel.com
+ *  Dan Kogan		dan.d.kogan@intel.com
+ *
+ *  CHANGES:
+ *
+ *  2004.12.01	Major rewrite by tiwai, merged the work of pshou
+ * 
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/mutex.h>
+#include <linux/reboot.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include "hda_codec.h"
+
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+static char *model[SNDRV_CARDS];
+static int position_fix[SNDRV_CARDS];
+static int bdl_pos_adj[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1};
+static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1};
+static int probe_only[SNDRV_CARDS];
+static int single_cmd;
+static int enable_msi = -1;
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+static char *patch[SNDRV_CARDS];
+#endif
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+static int beep_mode[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] =
+					CONFIG_SND_HDA_INPUT_BEEP_MODE};
+#endif
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Intel HD audio interface.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Intel HD audio interface.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Intel HD audio interface.");
+module_param_array(model, charp, NULL, 0444);
+MODULE_PARM_DESC(model, "Use the given board model.");
+module_param_array(position_fix, int, NULL, 0444);
+MODULE_PARM_DESC(position_fix, "DMA pointer read method."
+		 "(0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO).");
+module_param_array(bdl_pos_adj, int, NULL, 0644);
+MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset.");
+module_param_array(probe_mask, int, NULL, 0444);
+MODULE_PARM_DESC(probe_mask, "Bitmask to probe codecs (default = -1).");
+module_param_array(probe_only, int, NULL, 0444);
+MODULE_PARM_DESC(probe_only, "Only probing and no codec initialization.");
+module_param(single_cmd, bool, 0444);
+MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs "
+		 "(for debugging only).");
+module_param(enable_msi, int, 0444);
+MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)");
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+module_param_array(patch, charp, NULL, 0444);
+MODULE_PARM_DESC(patch, "Patch file for Intel HD audio interface.");
+#endif
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+module_param_array(beep_mode, int, NULL, 0444);
+MODULE_PARM_DESC(beep_mode, "Select HDA Beep registration mode "
+			    "(0=off, 1=on, 2=mute switch on/off) (default=1).");
+#endif
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
+module_param(power_save, int, 0644);
+MODULE_PARM_DESC(power_save, "Automatic power-saving timeout "
+		 "(in second, 0 = disable).");
+
+/* reset the HD-audio controller in power save mode.
+ * this may give more power-saving, but will take longer time to
+ * wake up.
+ */
+static int power_save_controller = 1;
+module_param(power_save_controller, bool, 0644);
+MODULE_PARM_DESC(power_save_controller, "Reset controller in power save mode.");
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Intel, ICH6},"
+			 "{Intel, ICH6M},"
+			 "{Intel, ICH7},"
+			 "{Intel, ESB2},"
+			 "{Intel, ICH8},"
+			 "{Intel, ICH9},"
+			 "{Intel, ICH10},"
+			 "{Intel, PCH},"
+			 "{Intel, CPT},"
+			 "{Intel, PBG},"
+			 "{Intel, SCH},"
+			 "{ATI, SB450},"
+			 "{ATI, SB600},"
+			 "{ATI, RS600},"
+			 "{ATI, RS690},"
+			 "{ATI, RS780},"
+			 "{ATI, R600},"
+			 "{ATI, RV630},"
+			 "{ATI, RV610},"
+			 "{ATI, RV670},"
+			 "{ATI, RV635},"
+			 "{ATI, RV620},"
+			 "{ATI, RV770},"
+			 "{VIA, VT8251},"
+			 "{VIA, VT8237A},"
+			 "{SiS, SIS966},"
+			 "{ULI, M5461}}");
+MODULE_DESCRIPTION("Intel HDA driver");
+
+#ifdef CONFIG_SND_VERBOSE_PRINTK
+#define SFX	/* nop */
+#else
+#define SFX	"hda-intel: "
+#endif
+
+/*
+ * registers
+ */
+#define ICH6_REG_GCAP			0x00
+#define   ICH6_GCAP_64OK	(1 << 0)   /* 64bit address support */
+#define   ICH6_GCAP_NSDO	(3 << 1)   /* # of serial data out signals */
+#define   ICH6_GCAP_BSS		(31 << 3)  /* # of bidirectional streams */
+#define   ICH6_GCAP_ISS		(15 << 8)  /* # of input streams */
+#define   ICH6_GCAP_OSS		(15 << 12) /* # of output streams */
+#define ICH6_REG_VMIN			0x02
+#define ICH6_REG_VMAJ			0x03
+#define ICH6_REG_OUTPAY			0x04
+#define ICH6_REG_INPAY			0x06
+#define ICH6_REG_GCTL			0x08
+#define   ICH6_GCTL_RESET	(1 << 0)   /* controller reset */
+#define   ICH6_GCTL_FCNTRL	(1 << 1)   /* flush control */
+#define   ICH6_GCTL_UNSOL	(1 << 8)   /* accept unsol. response enable */
+#define ICH6_REG_WAKEEN			0x0c
+#define ICH6_REG_STATESTS		0x0e
+#define ICH6_REG_GSTS			0x10
+#define   ICH6_GSTS_FSTS	(1 << 1)   /* flush status */
+#define ICH6_REG_INTCTL			0x20
+#define ICH6_REG_INTSTS			0x24
+#define ICH6_REG_WALLCLK		0x30	/* 24Mhz source */
+#define ICH6_REG_SYNC			0x34	
+#define ICH6_REG_CORBLBASE		0x40
+#define ICH6_REG_CORBUBASE		0x44
+#define ICH6_REG_CORBWP			0x48
+#define ICH6_REG_CORBRP			0x4a
+#define   ICH6_CORBRP_RST	(1 << 15)  /* read pointer reset */
+#define ICH6_REG_CORBCTL		0x4c
+#define   ICH6_CORBCTL_RUN	(1 << 1)   /* enable DMA */
+#define   ICH6_CORBCTL_CMEIE	(1 << 0)   /* enable memory error irq */
+#define ICH6_REG_CORBSTS		0x4d
+#define   ICH6_CORBSTS_CMEI	(1 << 0)   /* memory error indication */
+#define ICH6_REG_CORBSIZE		0x4e
+
+#define ICH6_REG_RIRBLBASE		0x50
+#define ICH6_REG_RIRBUBASE		0x54
+#define ICH6_REG_RIRBWP			0x58
+#define   ICH6_RIRBWP_RST	(1 << 15)  /* write pointer reset */
+#define ICH6_REG_RINTCNT		0x5a
+#define ICH6_REG_RIRBCTL		0x5c
+#define   ICH6_RBCTL_IRQ_EN	(1 << 0)   /* enable IRQ */
+#define   ICH6_RBCTL_DMA_EN	(1 << 1)   /* enable DMA */
+#define   ICH6_RBCTL_OVERRUN_EN	(1 << 2)   /* enable overrun irq */
+#define ICH6_REG_RIRBSTS		0x5d
+#define   ICH6_RBSTS_IRQ	(1 << 0)   /* response irq */
+#define   ICH6_RBSTS_OVERRUN	(1 << 2)   /* overrun irq */
+#define ICH6_REG_RIRBSIZE		0x5e
+
+#define ICH6_REG_IC			0x60
+#define ICH6_REG_IR			0x64
+#define ICH6_REG_IRS			0x68
+#define   ICH6_IRS_VALID	(1<<1)
+#define   ICH6_IRS_BUSY		(1<<0)
+
+#define ICH6_REG_DPLBASE		0x70
+#define ICH6_REG_DPUBASE		0x74
+#define   ICH6_DPLBASE_ENABLE	0x1	/* Enable position buffer */
+
+/* SD offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */
+enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
+
+/* stream register offsets from stream base */
+#define ICH6_REG_SD_CTL			0x00
+#define ICH6_REG_SD_STS			0x03
+#define ICH6_REG_SD_LPIB		0x04
+#define ICH6_REG_SD_CBL			0x08
+#define ICH6_REG_SD_LVI			0x0c
+#define ICH6_REG_SD_FIFOW		0x0e
+#define ICH6_REG_SD_FIFOSIZE		0x10
+#define ICH6_REG_SD_FORMAT		0x12
+#define ICH6_REG_SD_BDLPL		0x18
+#define ICH6_REG_SD_BDLPU		0x1c
+
+/* PCI space */
+#define ICH6_PCIREG_TCSEL	0x44
+
+/*
+ * other constants
+ */
+
+/* max number of SDs */
+/* ICH, ATI and VIA have 4 playback and 4 capture */
+#define ICH6_NUM_CAPTURE	4
+#define ICH6_NUM_PLAYBACK	4
+
+/* ULI has 6 playback and 5 capture */
+#define ULI_NUM_CAPTURE		5
+#define ULI_NUM_PLAYBACK	6
+
+/* ATI HDMI has 1 playback and 0 capture */
+#define ATIHDMI_NUM_CAPTURE	0
+#define ATIHDMI_NUM_PLAYBACK	1
+
+/* TERA has 4 playback and 3 capture */
+#define TERA_NUM_CAPTURE	3
+#define TERA_NUM_PLAYBACK	4
+
+/* this number is statically defined for simplicity */
+#define MAX_AZX_DEV		16
+
+/* max number of fragments - we may use more if allocating more pages for BDL */
+#define BDL_SIZE		4096
+#define AZX_MAX_BDL_ENTRIES	(BDL_SIZE / 16)
+#define AZX_MAX_FRAG		32
+/* max buffer size - no h/w limit, you can increase as you like */
+#define AZX_MAX_BUF_SIZE	(1024*1024*1024)
+
+/* RIRB int mask: overrun[2], response[0] */
+#define RIRB_INT_RESPONSE	0x01
+#define RIRB_INT_OVERRUN	0x04
+#define RIRB_INT_MASK		0x05
+
+/* STATESTS int mask: S3,SD2,SD1,SD0 */
+#define AZX_MAX_CODECS		8
+#define AZX_DEFAULT_CODECS	4
+#define STATESTS_INT_MASK	((1 << AZX_MAX_CODECS) - 1)
+
+/* SD_CTL bits */
+#define SD_CTL_STREAM_RESET	0x01	/* stream reset bit */
+#define SD_CTL_DMA_START	0x02	/* stream DMA start bit */
+#define SD_CTL_STRIPE		(3 << 16)	/* stripe control */
+#define SD_CTL_TRAFFIC_PRIO	(1 << 18)	/* traffic priority */
+#define SD_CTL_DIR		(1 << 19)	/* bi-directional stream */
+#define SD_CTL_STREAM_TAG_MASK	(0xf << 20)
+#define SD_CTL_STREAM_TAG_SHIFT	20
+
+/* SD_CTL and SD_STS */
+#define SD_INT_DESC_ERR		0x10	/* descriptor error interrupt */
+#define SD_INT_FIFO_ERR		0x08	/* FIFO error interrupt */
+#define SD_INT_COMPLETE		0x04	/* completion interrupt */
+#define SD_INT_MASK		(SD_INT_DESC_ERR|SD_INT_FIFO_ERR|\
+				 SD_INT_COMPLETE)
+
+/* SD_STS */
+#define SD_STS_FIFO_READY	0x20	/* FIFO ready */
+
+/* INTCTL and INTSTS */
+#define ICH6_INT_ALL_STREAM	0xff	   /* all stream interrupts */
+#define ICH6_INT_CTRL_EN	0x40000000 /* controller interrupt enable bit */
+#define ICH6_INT_GLOBAL_EN	0x80000000 /* global interrupt enable bit */
+
+/* below are so far hardcoded - should read registers in future */
+#define ICH6_MAX_CORB_ENTRIES	256
+#define ICH6_MAX_RIRB_ENTRIES	256
+
+/* position fix mode */
+enum {
+	POS_FIX_AUTO,
+	POS_FIX_LPIB,
+	POS_FIX_POSBUF,
+	POS_FIX_VIACOMBO,
+};
+
+/* Defines for ATI HD Audio support in SB450 south bridge */
+#define ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR   0x42
+#define ATI_SB450_HDAUDIO_ENABLE_SNOOP      0x02
+
+/* Defines for Nvidia HDA support */
+#define NVIDIA_HDA_TRANSREG_ADDR      0x4e
+#define NVIDIA_HDA_ENABLE_COHBITS     0x0f
+#define NVIDIA_HDA_ISTRM_COH          0x4d
+#define NVIDIA_HDA_OSTRM_COH          0x4c
+#define NVIDIA_HDA_ENABLE_COHBIT      0x01
+
+/* Defines for Intel SCH HDA snoop control */
+#define INTEL_SCH_HDA_DEVC      0x78
+#define INTEL_SCH_HDA_DEVC_NOSNOOP       (0x1<<11)
+
+/* Define IN stream 0 FIFO size offset in VIA controller */
+#define VIA_IN_STREAM0_FIFO_SIZE_OFFSET	0x90
+/* Define VIA HD Audio Device ID*/
+#define VIA_HDAC_DEVICE_ID		0x3288
+
+/* HD Audio class code */
+#define PCI_CLASS_MULTIMEDIA_HD_AUDIO	0x0403
+
+/*
+ */
+
+struct azx_dev {
+	struct snd_dma_buffer bdl; /* BDL buffer */
+	u32 *posbuf;		/* position buffer pointer */
+
+	unsigned int bufsize;	/* size of the play buffer in bytes */
+	unsigned int period_bytes; /* size of the period in bytes */
+	unsigned int frags;	/* number for period in the play buffer */
+	unsigned int fifo_size;	/* FIFO size */
+	unsigned long start_wallclk;	/* start + minimum wallclk */
+	unsigned long period_wallclk;	/* wallclk for period */
+
+	void __iomem *sd_addr;	/* stream descriptor pointer */
+
+	u32 sd_int_sta_mask;	/* stream int status mask */
+
+	/* pcm support */
+	struct snd_pcm_substream *substream;	/* assigned substream,
+						 * set in PCM open
+						 */
+	unsigned int format_val;	/* format value to be set in the
+					 * controller and the codec
+					 */
+	unsigned char stream_tag;	/* assigned stream */
+	unsigned char index;		/* stream index */
+	int device;			/* last device number assigned to */
+
+	unsigned int opened :1;
+	unsigned int running :1;
+	unsigned int irq_pending :1;
+	/*
+	 * For VIA:
+	 *  A flag to ensure DMA position is 0
+	 *  when link position is not greater than FIFO size
+	 */
+	unsigned int insufficient :1;
+};
+
+/* CORB/RIRB */
+struct azx_rb {
+	u32 *buf;		/* CORB/RIRB buffer
+				 * Each CORB entry is 4byte, RIRB is 8byte
+				 */
+	dma_addr_t addr;	/* physical address of CORB/RIRB buffer */
+	/* for RIRB */
+	unsigned short rp, wp;	/* read/write pointers */
+	int cmds[AZX_MAX_CODECS];	/* number of pending requests */
+	u32 res[AZX_MAX_CODECS];	/* last read value */
+};
+
+struct azx {
+	struct snd_card *card;
+	struct pci_dev *pci;
+	int dev_index;
+
+	/* chip type specific */
+	int driver_type;
+	int playback_streams;
+	int playback_index_offset;
+	int capture_streams;
+	int capture_index_offset;
+	int num_streams;
+
+	/* pci resources */
+	unsigned long addr;
+	void __iomem *remap_addr;
+	int irq;
+
+	/* locks */
+	spinlock_t reg_lock;
+	struct mutex open_mutex;
+
+	/* streams (x num_streams) */
+	struct azx_dev *azx_dev;
+
+	/* PCM */
+	struct snd_pcm *pcm[HDA_MAX_PCMS];
+
+	/* HD codec */
+	unsigned short codec_mask;
+	int  codec_probe_mask; /* copied from probe_mask option */
+	struct hda_bus *bus;
+	unsigned int beep_mode;
+
+	/* CORB/RIRB */
+	struct azx_rb corb;
+	struct azx_rb rirb;
+
+	/* CORB/RIRB and position buffers */
+	struct snd_dma_buffer rb;
+	struct snd_dma_buffer posbuf;
+
+	/* flags */
+	int position_fix[2]; /* for both playback/capture streams */
+	int poll_count;
+	unsigned int running :1;
+	unsigned int initialized :1;
+	unsigned int single_cmd :1;
+	unsigned int polling_mode :1;
+	unsigned int msi :1;
+	unsigned int irq_pending_warned :1;
+	unsigned int probing :1; /* codec probing phase */
+
+	/* for debugging */
+	unsigned int last_cmd[AZX_MAX_CODECS];
+
+	/* for pending irqs */
+	struct work_struct irq_pending_work;
+
+	/* reboot notifier (for mysterious hangup problem at power-down) */
+	struct notifier_block reboot_notifier;
+};
+
+/* driver types */
+enum {
+	AZX_DRIVER_ICH,
+	AZX_DRIVER_PCH,
+	AZX_DRIVER_SCH,
+	AZX_DRIVER_ATI,
+	AZX_DRIVER_ATIHDMI,
+	AZX_DRIVER_VIA,
+	AZX_DRIVER_SIS,
+	AZX_DRIVER_ULI,
+	AZX_DRIVER_NVIDIA,
+	AZX_DRIVER_TERA,
+	AZX_DRIVER_CTX,
+	AZX_DRIVER_GENERIC,
+	AZX_NUM_DRIVERS, /* keep this as last entry */
+};
+
+static char *driver_short_names[] __devinitdata = {
+	[AZX_DRIVER_ICH] = "HDA Intel",
+	[AZX_DRIVER_PCH] = "HDA Intel PCH",
+	[AZX_DRIVER_SCH] = "HDA Intel MID",
+	[AZX_DRIVER_ATI] = "HDA ATI SB",
+	[AZX_DRIVER_ATIHDMI] = "HDA ATI HDMI",
+	[AZX_DRIVER_VIA] = "HDA VIA VT82xx",
+	[AZX_DRIVER_SIS] = "HDA SIS966",
+	[AZX_DRIVER_ULI] = "HDA ULI M5461",
+	[AZX_DRIVER_NVIDIA] = "HDA NVidia",
+	[AZX_DRIVER_TERA] = "HDA Teradici", 
+	[AZX_DRIVER_CTX] = "HDA Creative", 
+	[AZX_DRIVER_GENERIC] = "HD-Audio Generic",
+};
+
+/*
+ * macros for easy use
+ */
+#define azx_writel(chip,reg,value) \
+	writel(value, (chip)->remap_addr + ICH6_REG_##reg)
+#define azx_readl(chip,reg) \
+	readl((chip)->remap_addr + ICH6_REG_##reg)
+#define azx_writew(chip,reg,value) \
+	writew(value, (chip)->remap_addr + ICH6_REG_##reg)
+#define azx_readw(chip,reg) \
+	readw((chip)->remap_addr + ICH6_REG_##reg)
+#define azx_writeb(chip,reg,value) \
+	writeb(value, (chip)->remap_addr + ICH6_REG_##reg)
+#define azx_readb(chip,reg) \
+	readb((chip)->remap_addr + ICH6_REG_##reg)
+
+#define azx_sd_writel(dev,reg,value) \
+	writel(value, (dev)->sd_addr + ICH6_REG_##reg)
+#define azx_sd_readl(dev,reg) \
+	readl((dev)->sd_addr + ICH6_REG_##reg)
+#define azx_sd_writew(dev,reg,value) \
+	writew(value, (dev)->sd_addr + ICH6_REG_##reg)
+#define azx_sd_readw(dev,reg) \
+	readw((dev)->sd_addr + ICH6_REG_##reg)
+#define azx_sd_writeb(dev,reg,value) \
+	writeb(value, (dev)->sd_addr + ICH6_REG_##reg)
+#define azx_sd_readb(dev,reg) \
+	readb((dev)->sd_addr + ICH6_REG_##reg)
+
+/* for pcm support */
+#define get_azx_dev(substream) (substream->runtime->private_data)
+
+static int azx_acquire_irq(struct azx *chip, int do_disconnect);
+static int azx_send_cmd(struct hda_bus *bus, unsigned int val);
+/*
+ * Interface for HD codec
+ */
+
+/*
+ * CORB / RIRB interface
+ */
+static int azx_alloc_cmd_io(struct azx *chip)
+{
+	int err;
+
+	/* single page (at least 4096 bytes) must suffice for both ringbuffes */
+	err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+				  snd_dma_pci_data(chip->pci),
+				  PAGE_SIZE, &chip->rb);
+	if (err < 0) {
+		snd_printk(KERN_ERR SFX "cannot allocate CORB/RIRB\n");
+		return err;
+	}
+	return 0;
+}
+
+static void azx_init_cmd_io(struct azx *chip)
+{
+	spin_lock_irq(&chip->reg_lock);
+	/* CORB set up */
+	chip->corb.addr = chip->rb.addr;
+	chip->corb.buf = (u32 *)chip->rb.area;
+	azx_writel(chip, CORBLBASE, (u32)chip->corb.addr);
+	azx_writel(chip, CORBUBASE, upper_32_bits(chip->corb.addr));
+
+	/* set the corb size to 256 entries (ULI requires explicitly) */
+	azx_writeb(chip, CORBSIZE, 0x02);
+	/* set the corb write pointer to 0 */
+	azx_writew(chip, CORBWP, 0);
+	/* reset the corb hw read pointer */
+	azx_writew(chip, CORBRP, ICH6_CORBRP_RST);
+	/* enable corb dma */
+	azx_writeb(chip, CORBCTL, ICH6_CORBCTL_RUN);
+
+	/* RIRB set up */
+	chip->rirb.addr = chip->rb.addr + 2048;
+	chip->rirb.buf = (u32 *)(chip->rb.area + 2048);
+	chip->rirb.wp = chip->rirb.rp = 0;
+	memset(chip->rirb.cmds, 0, sizeof(chip->rirb.cmds));
+	azx_writel(chip, RIRBLBASE, (u32)chip->rirb.addr);
+	azx_writel(chip, RIRBUBASE, upper_32_bits(chip->rirb.addr));
+
+	/* set the rirb size to 256 entries (ULI requires explicitly) */
+	azx_writeb(chip, RIRBSIZE, 0x02);
+	/* reset the rirb hw write pointer */
+	azx_writew(chip, RIRBWP, ICH6_RIRBWP_RST);
+	/* set N=1, get RIRB response interrupt for new entry */
+	if (chip->driver_type == AZX_DRIVER_CTX)
+		azx_writew(chip, RINTCNT, 0xc0);
+	else
+		azx_writew(chip, RINTCNT, 1);
+	/* enable rirb dma and response irq */
+	azx_writeb(chip, RIRBCTL, ICH6_RBCTL_DMA_EN | ICH6_RBCTL_IRQ_EN);
+	spin_unlock_irq(&chip->reg_lock);
+}
+
+static void azx_free_cmd_io(struct azx *chip)
+{
+	spin_lock_irq(&chip->reg_lock);
+	/* disable ringbuffer DMAs */
+	azx_writeb(chip, RIRBCTL, 0);
+	azx_writeb(chip, CORBCTL, 0);
+	spin_unlock_irq(&chip->reg_lock);
+}
+
+static unsigned int azx_command_addr(u32 cmd)
+{
+	unsigned int addr = cmd >> 28;
+
+	if (addr >= AZX_MAX_CODECS) {
+		snd_BUG();
+		addr = 0;
+	}
+
+	return addr;
+}
+
+static unsigned int azx_response_addr(u32 res)
+{
+	unsigned int addr = res & 0xf;
+
+	if (addr >= AZX_MAX_CODECS) {
+		snd_BUG();
+		addr = 0;
+	}
+
+	return addr;
+}
+
+/* send a command */
+static int azx_corb_send_cmd(struct hda_bus *bus, u32 val)
+{
+	struct azx *chip = bus->private_data;
+	unsigned int addr = azx_command_addr(val);
+	unsigned int wp;
+
+	spin_lock_irq(&chip->reg_lock);
+
+	/* add command to corb */
+	wp = azx_readb(chip, CORBWP);
+	wp++;
+	wp %= ICH6_MAX_CORB_ENTRIES;
+
+	chip->rirb.cmds[addr]++;
+	chip->corb.buf[wp] = cpu_to_le32(val);
+	azx_writel(chip, CORBWP, wp);
+
+	spin_unlock_irq(&chip->reg_lock);
+
+	return 0;
+}
+
+#define ICH6_RIRB_EX_UNSOL_EV	(1<<4)
+
+/* retrieve RIRB entry - called from interrupt handler */
+static void azx_update_rirb(struct azx *chip)
+{
+	unsigned int rp, wp;
+	unsigned int addr;
+	u32 res, res_ex;
+
+	wp = azx_readb(chip, RIRBWP);
+	if (wp == chip->rirb.wp)
+		return;
+	chip->rirb.wp = wp;
+
+	while (chip->rirb.rp != wp) {
+		chip->rirb.rp++;
+		chip->rirb.rp %= ICH6_MAX_RIRB_ENTRIES;
+
+		rp = chip->rirb.rp << 1; /* an RIRB entry is 8-bytes */
+		res_ex = le32_to_cpu(chip->rirb.buf[rp + 1]);
+		res = le32_to_cpu(chip->rirb.buf[rp]);
+		addr = azx_response_addr(res_ex);
+		if (res_ex & ICH6_RIRB_EX_UNSOL_EV)
+			snd_hda_queue_unsol_event(chip->bus, res, res_ex);
+		else if (chip->rirb.cmds[addr]) {
+			chip->rirb.res[addr] = res;
+			smp_wmb();
+			chip->rirb.cmds[addr]--;
+		} else
+			snd_printk(KERN_ERR SFX "spurious response %#x:%#x, "
+				   "last cmd=%#08x\n",
+				   res, res_ex,
+				   chip->last_cmd[addr]);
+	}
+}
+
+/* receive a response */
+static unsigned int azx_rirb_get_response(struct hda_bus *bus,
+					  unsigned int addr)
+{
+	struct azx *chip = bus->private_data;
+	unsigned long timeout;
+	int do_poll = 0;
+
+ again:
+	timeout = jiffies + msecs_to_jiffies(1000);
+	for (;;) {
+		if (chip->polling_mode || do_poll) {
+			spin_lock_irq(&chip->reg_lock);
+			azx_update_rirb(chip);
+			spin_unlock_irq(&chip->reg_lock);
+		}
+		if (!chip->rirb.cmds[addr]) {
+			smp_rmb();
+			bus->rirb_error = 0;
+
+			if (!do_poll)
+				chip->poll_count = 0;
+			return chip->rirb.res[addr]; /* the last value */
+		}
+		if (time_after(jiffies, timeout))
+			break;
+		if (bus->needs_damn_long_delay)
+			msleep(2); /* temporary workaround */
+		else {
+			udelay(10);
+			cond_resched();
+		}
+	}
+
+	if (!chip->polling_mode && chip->poll_count < 2) {
+		snd_printdd(SFX "azx_get_response timeout, "
+			   "polling the codec once: last cmd=0x%08x\n",
+			   chip->last_cmd[addr]);
+		do_poll = 1;
+		chip->poll_count++;
+		goto again;
+	}
+
+
+	if (!chip->polling_mode) {
+		snd_printk(KERN_WARNING SFX "azx_get_response timeout, "
+			   "switching to polling mode: last cmd=0x%08x\n",
+			   chip->last_cmd[addr]);
+		chip->polling_mode = 1;
+		goto again;
+	}
+
+	if (chip->msi) {
+		snd_printk(KERN_WARNING SFX "No response from codec, "
+			   "disabling MSI: last cmd=0x%08x\n",
+			   chip->last_cmd[addr]);
+		free_irq(chip->irq, chip);
+		chip->irq = -1;
+		pci_disable_msi(chip->pci);
+		chip->msi = 0;
+		if (azx_acquire_irq(chip, 1) < 0) {
+			bus->rirb_error = 1;
+			return -1;
+		}
+		goto again;
+	}
+
+	if (chip->probing) {
+		/* If this critical timeout happens during the codec probing
+		 * phase, this is likely an access to a non-existing codec
+		 * slot.  Better to return an error and reset the system.
+		 */
+		return -1;
+	}
+
+	/* a fatal communication error; need either to reset or to fallback
+	 * to the single_cmd mode
+	 */
+	bus->rirb_error = 1;
+	if (bus->allow_bus_reset && !bus->response_reset && !bus->in_reset) {
+		bus->response_reset = 1;
+		return -1; /* give a chance to retry */
+	}
+
+	snd_printk(KERN_ERR "hda_intel: azx_get_response timeout, "
+		   "switching to single_cmd mode: last cmd=0x%08x\n",
+		   chip->last_cmd[addr]);
+	chip->single_cmd = 1;
+	bus->response_reset = 0;
+	/* release CORB/RIRB */
+	azx_free_cmd_io(chip);
+	/* disable unsolicited responses */
+	azx_writel(chip, GCTL, azx_readl(chip, GCTL) & ~ICH6_GCTL_UNSOL);
+	return -1;
+}
+
+/*
+ * Use the single immediate command instead of CORB/RIRB for simplicity
+ *
+ * Note: according to Intel, this is not preferred use.  The command was
+ *       intended for the BIOS only, and may get confused with unsolicited
+ *       responses.  So, we shouldn't use it for normal operation from the
+ *       driver.
+ *       I left the codes, however, for debugging/testing purposes.
+ */
+
+/* receive a response */
+static int azx_single_wait_for_response(struct azx *chip, unsigned int addr)
+{
+	int timeout = 50;
+
+	while (timeout--) {
+		/* check IRV busy bit */
+		if (azx_readw(chip, IRS) & ICH6_IRS_VALID) {
+			/* reuse rirb.res as the response return value */
+			chip->rirb.res[addr] = azx_readl(chip, IR);
+			return 0;
+		}
+		udelay(1);
+	}
+	if (printk_ratelimit())
+		snd_printd(SFX "get_response timeout: IRS=0x%x\n",
+			   azx_readw(chip, IRS));
+	chip->rirb.res[addr] = -1;
+	return -EIO;
+}
+
+/* send a command */
+static int azx_single_send_cmd(struct hda_bus *bus, u32 val)
+{
+	struct azx *chip = bus->private_data;
+	unsigned int addr = azx_command_addr(val);
+	int timeout = 50;
+
+	bus->rirb_error = 0;
+	while (timeout--) {
+		/* check ICB busy bit */
+		if (!((azx_readw(chip, IRS) & ICH6_IRS_BUSY))) {
+			/* Clear IRV valid bit */
+			azx_writew(chip, IRS, azx_readw(chip, IRS) |
+				   ICH6_IRS_VALID);
+			azx_writel(chip, IC, val);
+			azx_writew(chip, IRS, azx_readw(chip, IRS) |
+				   ICH6_IRS_BUSY);
+			return azx_single_wait_for_response(chip, addr);
+		}
+		udelay(1);
+	}
+	if (printk_ratelimit())
+		snd_printd(SFX "send_cmd timeout: IRS=0x%x, val=0x%x\n",
+			   azx_readw(chip, IRS), val);
+	return -EIO;
+}
+
+/* receive a response */
+static unsigned int azx_single_get_response(struct hda_bus *bus,
+					    unsigned int addr)
+{
+	struct azx *chip = bus->private_data;
+	return chip->rirb.res[addr];
+}
+
+/*
+ * The below are the main callbacks from hda_codec.
+ *
+ * They are just the skeleton to call sub-callbacks according to the
+ * current setting of chip->single_cmd.
+ */
+
+/* send a command */
+static int azx_send_cmd(struct hda_bus *bus, unsigned int val)
+{
+	struct azx *chip = bus->private_data;
+
+	chip->last_cmd[azx_command_addr(val)] = val;
+	if (chip->single_cmd)
+		return azx_single_send_cmd(bus, val);
+	else
+		return azx_corb_send_cmd(bus, val);
+}
+
+/* get a response */
+static unsigned int azx_get_response(struct hda_bus *bus,
+				     unsigned int addr)
+{
+	struct azx *chip = bus->private_data;
+	if (chip->single_cmd)
+		return azx_single_get_response(bus, addr);
+	else
+		return azx_rirb_get_response(bus, addr);
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static void azx_power_notify(struct hda_bus *bus);
+#endif
+
+/* reset codec link */
+static int azx_reset(struct azx *chip, int full_reset)
+{
+	int count;
+
+	if (!full_reset)
+		goto __skip;
+
+	/* clear STATESTS */
+	azx_writeb(chip, STATESTS, STATESTS_INT_MASK);
+
+	/* reset controller */
+	azx_writel(chip, GCTL, azx_readl(chip, GCTL) & ~ICH6_GCTL_RESET);
+
+	count = 50;
+	while (azx_readb(chip, GCTL) && --count)
+		msleep(1);
+
+	/* delay for >= 100us for codec PLL to settle per spec
+	 * Rev 0.9 section 5.5.1
+	 */
+	msleep(1);
+
+	/* Bring controller out of reset */
+	azx_writeb(chip, GCTL, azx_readb(chip, GCTL) | ICH6_GCTL_RESET);
+
+	count = 50;
+	while (!azx_readb(chip, GCTL) && --count)
+		msleep(1);
+
+	/* Brent Chartrand said to wait >= 540us for codecs to initialize */
+	msleep(1);
+
+      __skip:
+	/* check to see if controller is ready */
+	if (!azx_readb(chip, GCTL)) {
+		snd_printd(SFX "azx_reset: controller not ready!\n");
+		return -EBUSY;
+	}
+
+	/* Accept unsolicited responses */
+	if (!chip->single_cmd)
+		azx_writel(chip, GCTL, azx_readl(chip, GCTL) |
+			   ICH6_GCTL_UNSOL);
+
+	/* detect codecs */
+	if (!chip->codec_mask) {
+		chip->codec_mask = azx_readw(chip, STATESTS);
+		snd_printdd(SFX "codec_mask = 0x%x\n", chip->codec_mask);
+	}
+
+	return 0;
+}
+
+
+/*
+ * Lowlevel interface
+ */  
+
+/* enable interrupts */
+static void azx_int_enable(struct azx *chip)
+{
+	/* enable controller CIE and GIE */
+	azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) |
+		   ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN);
+}
+
+/* disable interrupts */
+static void azx_int_disable(struct azx *chip)
+{
+	int i;
+
+	/* disable interrupts in stream descriptor */
+	for (i = 0; i < chip->num_streams; i++) {
+		struct azx_dev *azx_dev = &chip->azx_dev[i];
+		azx_sd_writeb(azx_dev, SD_CTL,
+			      azx_sd_readb(azx_dev, SD_CTL) & ~SD_INT_MASK);
+	}
+
+	/* disable SIE for all streams */
+	azx_writeb(chip, INTCTL, 0);
+
+	/* disable controller CIE and GIE */
+	azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) &
+		   ~(ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN));
+}
+
+/* clear interrupts */
+static void azx_int_clear(struct azx *chip)
+{
+	int i;
+
+	/* clear stream status */
+	for (i = 0; i < chip->num_streams; i++) {
+		struct azx_dev *azx_dev = &chip->azx_dev[i];
+		azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK);
+	}
+
+	/* clear STATESTS */
+	azx_writeb(chip, STATESTS, STATESTS_INT_MASK);
+
+	/* clear rirb status */
+	azx_writeb(chip, RIRBSTS, RIRB_INT_MASK);
+
+	/* clear int status */
+	azx_writel(chip, INTSTS, ICH6_INT_CTRL_EN | ICH6_INT_ALL_STREAM);
+}
+
+/* start a stream */
+static void azx_stream_start(struct azx *chip, struct azx_dev *azx_dev)
+{
+	/*
+	 * Before stream start, initialize parameter
+	 */
+	azx_dev->insufficient = 1;
+
+	/* enable SIE */
+	azx_writel(chip, INTCTL,
+		   azx_readl(chip, INTCTL) | (1 << azx_dev->index));
+	/* set DMA start and interrupt mask */
+	azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) |
+		      SD_CTL_DMA_START | SD_INT_MASK);
+}
+
+/* stop DMA */
+static void azx_stream_clear(struct azx *chip, struct azx_dev *azx_dev)
+{
+	azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) &
+		      ~(SD_CTL_DMA_START | SD_INT_MASK));
+	azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */
+}
+
+/* stop a stream */
+static void azx_stream_stop(struct azx *chip, struct azx_dev *azx_dev)
+{
+	azx_stream_clear(chip, azx_dev);
+	/* disable SIE */
+	azx_writel(chip, INTCTL,
+		   azx_readl(chip, INTCTL) & ~(1 << azx_dev->index));
+}
+
+
+/*
+ * reset and start the controller registers
+ */
+static void azx_init_chip(struct azx *chip, int full_reset)
+{
+	if (chip->initialized)
+		return;
+
+	/* reset controller */
+	azx_reset(chip, full_reset);
+
+	/* initialize interrupts */
+	azx_int_clear(chip);
+	azx_int_enable(chip);
+
+	/* initialize the codec command I/O */
+	if (!chip->single_cmd)
+		azx_init_cmd_io(chip);
+
+	/* program the position buffer */
+	azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr);
+	azx_writel(chip, DPUBASE, upper_32_bits(chip->posbuf.addr));
+
+	chip->initialized = 1;
+}
+
+/*
+ * initialize the PCI registers
+ */
+/* update bits in a PCI register byte */
+static void update_pci_byte(struct pci_dev *pci, unsigned int reg,
+			    unsigned char mask, unsigned char val)
+{
+	unsigned char data;
+
+	pci_read_config_byte(pci, reg, &data);
+	data &= ~mask;
+	data |= (val & mask);
+	pci_write_config_byte(pci, reg, data);
+}
+
+static void azx_init_pci(struct azx *chip)
+{
+	unsigned short snoop;
+
+	/* Clear bits 0-2 of PCI register TCSEL (at offset 0x44)
+	 * TCSEL == Traffic Class Select Register, which sets PCI express QOS
+	 * Ensuring these bits are 0 clears playback static on some HD Audio
+	 * codecs
+	 */
+	update_pci_byte(chip->pci, ICH6_PCIREG_TCSEL, 0x07, 0);
+
+	switch (chip->driver_type) {
+	case AZX_DRIVER_ATI:
+		/* For ATI SB450 azalia HD audio, we need to enable snoop */
+		update_pci_byte(chip->pci,
+				ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR, 
+				0x07, ATI_SB450_HDAUDIO_ENABLE_SNOOP);
+		break;
+	case AZX_DRIVER_NVIDIA:
+		/* For NVIDIA HDA, enable snoop */
+		update_pci_byte(chip->pci,
+				NVIDIA_HDA_TRANSREG_ADDR,
+				0x0f, NVIDIA_HDA_ENABLE_COHBITS);
+		update_pci_byte(chip->pci,
+				NVIDIA_HDA_ISTRM_COH,
+				0x01, NVIDIA_HDA_ENABLE_COHBIT);
+		update_pci_byte(chip->pci,
+				NVIDIA_HDA_OSTRM_COH,
+				0x01, NVIDIA_HDA_ENABLE_COHBIT);
+		break;
+	case AZX_DRIVER_SCH:
+	case AZX_DRIVER_PCH:
+		pci_read_config_word(chip->pci, INTEL_SCH_HDA_DEVC, &snoop);
+		if (snoop & INTEL_SCH_HDA_DEVC_NOSNOOP) {
+			pci_write_config_word(chip->pci, INTEL_SCH_HDA_DEVC,
+				snoop & (~INTEL_SCH_HDA_DEVC_NOSNOOP));
+			pci_read_config_word(chip->pci,
+				INTEL_SCH_HDA_DEVC, &snoop);
+			snd_printdd(SFX "HDA snoop disabled, enabling ... %s\n",
+				(snoop & INTEL_SCH_HDA_DEVC_NOSNOOP)
+				? "Failed" : "OK");
+		}
+		break;
+
+        }
+}
+
+
+static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev);
+
+/*
+ * interrupt handler
+ */
+static irqreturn_t azx_interrupt(int irq, void *dev_id)
+{
+	struct azx *chip = dev_id;
+	struct azx_dev *azx_dev;
+	u32 status;
+	u8 sd_status;
+	int i, ok;
+
+	spin_lock(&chip->reg_lock);
+
+	status = azx_readl(chip, INTSTS);
+	if (status == 0) {
+		spin_unlock(&chip->reg_lock);
+		return IRQ_NONE;
+	}
+	
+	for (i = 0; i < chip->num_streams; i++) {
+		azx_dev = &chip->azx_dev[i];
+		if (status & azx_dev->sd_int_sta_mask) {
+			sd_status = azx_sd_readb(azx_dev, SD_STS);
+			azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK);
+			if (!azx_dev->substream || !azx_dev->running ||
+			    !(sd_status & SD_INT_COMPLETE))
+				continue;
+			/* check whether this IRQ is really acceptable */
+			ok = azx_position_ok(chip, azx_dev);
+			if (ok == 1) {
+				azx_dev->irq_pending = 0;
+				spin_unlock(&chip->reg_lock);
+				snd_pcm_period_elapsed(azx_dev->substream);
+				spin_lock(&chip->reg_lock);
+			} else if (ok == 0 && chip->bus && chip->bus->workq) {
+				/* bogus IRQ, process it later */
+				azx_dev->irq_pending = 1;
+				queue_work(chip->bus->workq,
+					   &chip->irq_pending_work);
+			}
+		}
+	}
+
+	/* clear rirb int */
+	status = azx_readb(chip, RIRBSTS);
+	if (status & RIRB_INT_MASK) {
+		if (status & RIRB_INT_RESPONSE) {
+			if (chip->driver_type == AZX_DRIVER_CTX)
+				udelay(80);
+			azx_update_rirb(chip);
+		}
+		azx_writeb(chip, RIRBSTS, RIRB_INT_MASK);
+	}
+
+#if 0
+	/* clear state status int */
+	if (azx_readb(chip, STATESTS) & 0x04)
+		azx_writeb(chip, STATESTS, 0x04);
+#endif
+	spin_unlock(&chip->reg_lock);
+	
+	return IRQ_HANDLED;
+}
+
+
+/*
+ * set up a BDL entry
+ */
+static int setup_bdle(struct snd_pcm_substream *substream,
+		      struct azx_dev *azx_dev, u32 **bdlp,
+		      int ofs, int size, int with_ioc)
+{
+	u32 *bdl = *bdlp;
+
+	while (size > 0) {
+		dma_addr_t addr;
+		int chunk;
+
+		if (azx_dev->frags >= AZX_MAX_BDL_ENTRIES)
+			return -EINVAL;
+
+		addr = snd_pcm_sgbuf_get_addr(substream, ofs);
+		/* program the address field of the BDL entry */
+		bdl[0] = cpu_to_le32((u32)addr);
+		bdl[1] = cpu_to_le32(upper_32_bits(addr));
+		/* program the size field of the BDL entry */
+		chunk = snd_pcm_sgbuf_get_chunk_size(substream, ofs, size);
+		bdl[2] = cpu_to_le32(chunk);
+		/* program the IOC to enable interrupt
+		 * only when the whole fragment is processed
+		 */
+		size -= chunk;
+		bdl[3] = (size || !with_ioc) ? 0 : cpu_to_le32(0x01);
+		bdl += 4;
+		azx_dev->frags++;
+		ofs += chunk;
+	}
+	*bdlp = bdl;
+	return ofs;
+}
+
+/*
+ * set up BDL entries
+ */
+static int azx_setup_periods(struct azx *chip,
+			     struct snd_pcm_substream *substream,
+			     struct azx_dev *azx_dev)
+{
+	u32 *bdl;
+	int i, ofs, periods, period_bytes;
+	int pos_adj;
+
+	/* reset BDL address */
+	azx_sd_writel(azx_dev, SD_BDLPL, 0);
+	azx_sd_writel(azx_dev, SD_BDLPU, 0);
+
+	period_bytes = azx_dev->period_bytes;
+	periods = azx_dev->bufsize / period_bytes;
+
+	/* program the initial BDL entries */
+	bdl = (u32 *)azx_dev->bdl.area;
+	ofs = 0;
+	azx_dev->frags = 0;
+	pos_adj = bdl_pos_adj[chip->dev_index];
+	if (pos_adj > 0) {
+		struct snd_pcm_runtime *runtime = substream->runtime;
+		int pos_align = pos_adj;
+		pos_adj = (pos_adj * runtime->rate + 47999) / 48000;
+		if (!pos_adj)
+			pos_adj = pos_align;
+		else
+			pos_adj = ((pos_adj + pos_align - 1) / pos_align) *
+				pos_align;
+		pos_adj = frames_to_bytes(runtime, pos_adj);
+		if (pos_adj >= period_bytes) {
+			snd_printk(KERN_WARNING SFX "Too big adjustment %d\n",
+				   bdl_pos_adj[chip->dev_index]);
+			pos_adj = 0;
+		} else {
+			ofs = setup_bdle(substream, azx_dev,
+					 &bdl, ofs, pos_adj, 1);
+			if (ofs < 0)
+				goto error;
+		}
+	} else
+		pos_adj = 0;
+	for (i = 0; i < periods; i++) {
+		if (i == periods - 1 && pos_adj)
+			ofs = setup_bdle(substream, azx_dev, &bdl, ofs,
+					 period_bytes - pos_adj, 0);
+		else
+			ofs = setup_bdle(substream, azx_dev, &bdl, ofs,
+					 period_bytes, 1);
+		if (ofs < 0)
+			goto error;
+	}
+	return 0;
+
+ error:
+	snd_printk(KERN_ERR SFX "Too many BDL entries: buffer=%d, period=%d\n",
+		   azx_dev->bufsize, period_bytes);
+	return -EINVAL;
+}
+
+/* reset stream */
+static void azx_stream_reset(struct azx *chip, struct azx_dev *azx_dev)
+{
+	unsigned char val;
+	int timeout;
+
+	azx_stream_clear(chip, azx_dev);
+
+	azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) |
+		      SD_CTL_STREAM_RESET);
+	udelay(3);
+	timeout = 300;
+	while (!((val = azx_sd_readb(azx_dev, SD_CTL)) & SD_CTL_STREAM_RESET) &&
+	       --timeout)
+		;
+	val &= ~SD_CTL_STREAM_RESET;
+	azx_sd_writeb(azx_dev, SD_CTL, val);
+	udelay(3);
+
+	timeout = 300;
+	/* waiting for hardware to report that the stream is out of reset */
+	while (((val = azx_sd_readb(azx_dev, SD_CTL)) & SD_CTL_STREAM_RESET) &&
+	       --timeout)
+		;
+
+	/* reset first position - may not be synced with hw at this time */
+	*azx_dev->posbuf = 0;
+}
+
+/*
+ * set up the SD for streaming
+ */
+static int azx_setup_controller(struct azx *chip, struct azx_dev *azx_dev)
+{
+	/* make sure the run bit is zero for SD */
+	azx_stream_clear(chip, azx_dev);
+	/* program the stream_tag */
+	azx_sd_writel(azx_dev, SD_CTL,
+		      (azx_sd_readl(azx_dev, SD_CTL) & ~SD_CTL_STREAM_TAG_MASK)|
+		      (azx_dev->stream_tag << SD_CTL_STREAM_TAG_SHIFT));
+
+	/* program the length of samples in cyclic buffer */
+	azx_sd_writel(azx_dev, SD_CBL, azx_dev->bufsize);
+
+	/* program the stream format */
+	/* this value needs to be the same as the one programmed */
+	azx_sd_writew(azx_dev, SD_FORMAT, azx_dev->format_val);
+
+	/* program the stream LVI (last valid index) of the BDL */
+	azx_sd_writew(azx_dev, SD_LVI, azx_dev->frags - 1);
+
+	/* program the BDL address */
+	/* lower BDL address */
+	azx_sd_writel(azx_dev, SD_BDLPL, (u32)azx_dev->bdl.addr);
+	/* upper BDL address */
+	azx_sd_writel(azx_dev, SD_BDLPU, upper_32_bits(azx_dev->bdl.addr));
+
+	/* enable the position buffer */
+	if (chip->position_fix[0] != POS_FIX_LPIB ||
+	    chip->position_fix[1] != POS_FIX_LPIB) {
+		if (!(azx_readl(chip, DPLBASE) & ICH6_DPLBASE_ENABLE))
+			azx_writel(chip, DPLBASE,
+				(u32)chip->posbuf.addr | ICH6_DPLBASE_ENABLE);
+	}
+
+	/* set the interrupt enable bits in the descriptor control register */
+	azx_sd_writel(azx_dev, SD_CTL,
+		      azx_sd_readl(azx_dev, SD_CTL) | SD_INT_MASK);
+
+	return 0;
+}
+
+/*
+ * Probe the given codec address
+ */
+static int probe_codec(struct azx *chip, int addr)
+{
+	unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) |
+		(AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID;
+	unsigned int res;
+
+	mutex_lock(&chip->bus->cmd_mutex);
+	chip->probing = 1;
+	azx_send_cmd(chip->bus, cmd);
+	res = azx_get_response(chip->bus, addr);
+	chip->probing = 0;
+	mutex_unlock(&chip->bus->cmd_mutex);
+	if (res == -1)
+		return -EIO;
+	snd_printdd(SFX "codec #%d probed OK\n", addr);
+	return 0;
+}
+
+static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec,
+				 struct hda_pcm *cpcm);
+static void azx_stop_chip(struct azx *chip);
+
+static void azx_bus_reset(struct hda_bus *bus)
+{
+	struct azx *chip = bus->private_data;
+
+	bus->in_reset = 1;
+	azx_stop_chip(chip);
+	azx_init_chip(chip, 1);
+#ifdef CONFIG_PM
+	if (chip->initialized) {
+		int i;
+
+		for (i = 0; i < HDA_MAX_PCMS; i++)
+			snd_pcm_suspend_all(chip->pcm[i]);
+		snd_hda_suspend(chip->bus);
+		snd_hda_resume(chip->bus);
+	}
+#endif
+	bus->in_reset = 0;
+}
+
+/*
+ * Codec initialization
+ */
+
+/* number of codec slots for each chipset: 0 = default slots (i.e. 4) */
+static unsigned int azx_max_codecs[AZX_NUM_DRIVERS] __devinitdata = {
+	[AZX_DRIVER_NVIDIA] = 8,
+	[AZX_DRIVER_TERA] = 1,
+};
+
+static int __devinit azx_codec_create(struct azx *chip, const char *model)
+{
+	struct hda_bus_template bus_temp;
+	int c, codecs, err;
+	int max_slots;
+
+	memset(&bus_temp, 0, sizeof(bus_temp));
+	bus_temp.private_data = chip;
+	bus_temp.modelname = model;
+	bus_temp.pci = chip->pci;
+	bus_temp.ops.command = azx_send_cmd;
+	bus_temp.ops.get_response = azx_get_response;
+	bus_temp.ops.attach_pcm = azx_attach_pcm_stream;
+	bus_temp.ops.bus_reset = azx_bus_reset;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	bus_temp.power_save = &power_save;
+	bus_temp.ops.pm_notify = azx_power_notify;
+#endif
+
+	err = snd_hda_bus_new(chip->card, &bus_temp, &chip->bus);
+	if (err < 0)
+		return err;
+
+	if (chip->driver_type == AZX_DRIVER_NVIDIA)
+		chip->bus->needs_damn_long_delay = 1;
+
+	codecs = 0;
+	max_slots = azx_max_codecs[chip->driver_type];
+	if (!max_slots)
+		max_slots = AZX_DEFAULT_CODECS;
+
+	/* First try to probe all given codec slots */
+	for (c = 0; c < max_slots; c++) {
+		if ((chip->codec_mask & (1 << c)) & chip->codec_probe_mask) {
+			if (probe_codec(chip, c) < 0) {
+				/* Some BIOSen give you wrong codec addresses
+				 * that don't exist
+				 */
+				snd_printk(KERN_WARNING SFX
+					   "Codec #%d probe error; "
+					   "disabling it...\n", c);
+				chip->codec_mask &= ~(1 << c);
+				/* More badly, accessing to a non-existing
+				 * codec often screws up the controller chip,
+				 * and disturbs the further communications.
+				 * Thus if an error occurs during probing,
+				 * better to reset the controller chip to
+				 * get back to the sanity state.
+				 */
+				azx_stop_chip(chip);
+				azx_init_chip(chip, 1);
+			}
+		}
+	}
+
+	/* Then create codec instances */
+	for (c = 0; c < max_slots; c++) {
+		if ((chip->codec_mask & (1 << c)) & chip->codec_probe_mask) {
+			struct hda_codec *codec;
+			err = snd_hda_codec_new(chip->bus, c, &codec);
+			if (err < 0)
+				continue;
+			codec->beep_mode = chip->beep_mode;
+			codecs++;
+		}
+	}
+	if (!codecs) {
+		snd_printk(KERN_ERR SFX "no codecs initialized\n");
+		return -ENXIO;
+	}
+	return 0;
+}
+
+/* configure each codec instance */
+static int __devinit azx_codec_configure(struct azx *chip)
+{
+	struct hda_codec *codec;
+	list_for_each_entry(codec, &chip->bus->codec_list, list) {
+		snd_hda_codec_configure(codec);
+	}
+	return 0;
+}
+
+
+/*
+ * PCM support
+ */
+
+/* assign a stream for the PCM */
+static inline struct azx_dev *
+azx_assign_device(struct azx *chip, struct snd_pcm_substream *substream)
+{
+	int dev, i, nums;
+	struct azx_dev *res = NULL;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		dev = chip->playback_index_offset;
+		nums = chip->playback_streams;
+	} else {
+		dev = chip->capture_index_offset;
+		nums = chip->capture_streams;
+	}
+	for (i = 0; i < nums; i++, dev++)
+		if (!chip->azx_dev[dev].opened) {
+			res = &chip->azx_dev[dev];
+			if (res->device == substream->pcm->device)
+				break;
+		}
+	if (res) {
+		res->opened = 1;
+		res->device = substream->pcm->device;
+	}
+	return res;
+}
+
+/* release the assigned stream */
+static inline void azx_release_device(struct azx_dev *azx_dev)
+{
+	azx_dev->opened = 0;
+}
+
+static struct snd_pcm_hardware azx_pcm_hw = {
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 /* No full-resume yet implemented */
+				 /* SNDRV_PCM_INFO_RESUME |*/
+				 SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_SYNC_START),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	AZX_MAX_BUF_SIZE,
+	.period_bytes_min =	128,
+	.period_bytes_max =	AZX_MAX_BUF_SIZE / 2,
+	.periods_min =		2,
+	.periods_max =		AZX_MAX_FRAG,
+	.fifo_size =		0,
+};
+
+struct azx_pcm {
+	struct azx *chip;
+	struct hda_codec *codec;
+	struct hda_pcm_stream *hinfo[2];
+};
+
+static int azx_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+	struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
+	struct azx *chip = apcm->chip;
+	struct azx_dev *azx_dev;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long flags;
+	int err;
+
+	mutex_lock(&chip->open_mutex);
+	azx_dev = azx_assign_device(chip, substream);
+	if (azx_dev == NULL) {
+		mutex_unlock(&chip->open_mutex);
+		return -EBUSY;
+	}
+	runtime->hw = azx_pcm_hw;
+	runtime->hw.channels_min = hinfo->channels_min;
+	runtime->hw.channels_max = hinfo->channels_max;
+	runtime->hw.formats = hinfo->formats;
+	runtime->hw.rates = hinfo->rates;
+	snd_pcm_limit_hw_rates(runtime);
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+				   128);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				   128);
+	snd_hda_power_up(apcm->codec);
+	err = hinfo->ops.open(hinfo, apcm->codec, substream);
+	if (err < 0) {
+		azx_release_device(azx_dev);
+		snd_hda_power_down(apcm->codec);
+		mutex_unlock(&chip->open_mutex);
+		return err;
+	}
+	snd_pcm_limit_hw_rates(runtime);
+	/* sanity check */
+	if (snd_BUG_ON(!runtime->hw.channels_min) ||
+	    snd_BUG_ON(!runtime->hw.channels_max) ||
+	    snd_BUG_ON(!runtime->hw.formats) ||
+	    snd_BUG_ON(!runtime->hw.rates)) {
+		azx_release_device(azx_dev);
+		hinfo->ops.close(hinfo, apcm->codec, substream);
+		snd_hda_power_down(apcm->codec);
+		mutex_unlock(&chip->open_mutex);
+		return -EINVAL;
+	}
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	azx_dev->substream = substream;
+	azx_dev->running = 0;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	runtime->private_data = azx_dev;
+	snd_pcm_set_sync(substream);
+	mutex_unlock(&chip->open_mutex);
+	return 0;
+}
+
+static int azx_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+	struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
+	struct azx *chip = apcm->chip;
+	struct azx_dev *azx_dev = get_azx_dev(substream);
+	unsigned long flags;
+
+	mutex_lock(&chip->open_mutex);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	azx_dev->substream = NULL;
+	azx_dev->running = 0;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	azx_release_device(azx_dev);
+	hinfo->ops.close(hinfo, apcm->codec, substream);
+	snd_hda_power_down(apcm->codec);
+	mutex_unlock(&chip->open_mutex);
+	return 0;
+}
+
+static int azx_pcm_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *hw_params)
+{
+	struct azx_dev *azx_dev = get_azx_dev(substream);
+
+	azx_dev->bufsize = 0;
+	azx_dev->period_bytes = 0;
+	azx_dev->format_val = 0;
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int azx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+	struct azx_dev *azx_dev = get_azx_dev(substream);
+	struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
+
+	/* reset BDL address */
+	azx_sd_writel(azx_dev, SD_BDLPL, 0);
+	azx_sd_writel(azx_dev, SD_BDLPU, 0);
+	azx_sd_writel(azx_dev, SD_CTL, 0);
+	azx_dev->bufsize = 0;
+	azx_dev->period_bytes = 0;
+	azx_dev->format_val = 0;
+
+	snd_hda_codec_cleanup(apcm->codec, hinfo, substream);
+
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int azx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+	struct azx *chip = apcm->chip;
+	struct azx_dev *azx_dev = get_azx_dev(substream);
+	struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int bufsize, period_bytes, format_val, stream_tag;
+	int err;
+
+	azx_stream_reset(chip, azx_dev);
+	format_val = snd_hda_calc_stream_format(runtime->rate,
+						runtime->channels,
+						runtime->format,
+						hinfo->maxbps,
+						apcm->codec->spdif_ctls);
+	if (!format_val) {
+		snd_printk(KERN_ERR SFX
+			   "invalid format_val, rate=%d, ch=%d, format=%d\n",
+			   runtime->rate, runtime->channels, runtime->format);
+		return -EINVAL;
+	}
+
+	bufsize = snd_pcm_lib_buffer_bytes(substream);
+	period_bytes = snd_pcm_lib_period_bytes(substream);
+
+	snd_printdd(SFX "azx_pcm_prepare: bufsize=0x%x, format=0x%x\n",
+		    bufsize, format_val);
+
+	if (bufsize != azx_dev->bufsize ||
+	    period_bytes != azx_dev->period_bytes ||
+	    format_val != azx_dev->format_val) {
+		azx_dev->bufsize = bufsize;
+		azx_dev->period_bytes = period_bytes;
+		azx_dev->format_val = format_val;
+		err = azx_setup_periods(chip, substream, azx_dev);
+		if (err < 0)
+			return err;
+	}
+
+	/* wallclk has 24Mhz clock source */
+	azx_dev->period_wallclk = (((runtime->period_size * 24000) /
+						runtime->rate) * 1000);
+	azx_setup_controller(chip, azx_dev);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		azx_dev->fifo_size = azx_sd_readw(azx_dev, SD_FIFOSIZE) + 1;
+	else
+		azx_dev->fifo_size = 0;
+
+	stream_tag = azx_dev->stream_tag;
+	/* CA-IBG chips need the playback stream starting from 1 */
+	if (chip->driver_type == AZX_DRIVER_CTX &&
+	    stream_tag > chip->capture_streams)
+		stream_tag -= chip->capture_streams;
+	return snd_hda_codec_prepare(apcm->codec, hinfo, stream_tag,
+				     azx_dev->format_val, substream);
+}
+
+static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+	struct azx *chip = apcm->chip;
+	struct azx_dev *azx_dev;
+	struct snd_pcm_substream *s;
+	int rstart = 0, start, nsync = 0, sbits = 0;
+	int nwait, timeout;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		rstart = 1;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		start = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_STOP:
+		start = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_pcm_group_for_each_entry(s, substream) {
+		if (s->pcm->card != substream->pcm->card)
+			continue;
+		azx_dev = get_azx_dev(s);
+		sbits |= 1 << azx_dev->index;
+		nsync++;
+		snd_pcm_trigger_done(s, substream);
+	}
+
+	spin_lock(&chip->reg_lock);
+	if (nsync > 1) {
+		/* first, set SYNC bits of corresponding streams */
+		azx_writel(chip, SYNC, azx_readl(chip, SYNC) | sbits);
+	}
+	snd_pcm_group_for_each_entry(s, substream) {
+		if (s->pcm->card != substream->pcm->card)
+			continue;
+		azx_dev = get_azx_dev(s);
+		if (start) {
+			azx_dev->start_wallclk = azx_readl(chip, WALLCLK);
+			if (!rstart)
+				azx_dev->start_wallclk -=
+						azx_dev->period_wallclk;
+			azx_stream_start(chip, azx_dev);
+		} else {
+			azx_stream_stop(chip, azx_dev);
+		}
+		azx_dev->running = start;
+	}
+	spin_unlock(&chip->reg_lock);
+	if (start) {
+		if (nsync == 1)
+			return 0;
+		/* wait until all FIFOs get ready */
+		for (timeout = 5000; timeout; timeout--) {
+			nwait = 0;
+			snd_pcm_group_for_each_entry(s, substream) {
+				if (s->pcm->card != substream->pcm->card)
+					continue;
+				azx_dev = get_azx_dev(s);
+				if (!(azx_sd_readb(azx_dev, SD_STS) &
+				      SD_STS_FIFO_READY))
+					nwait++;
+			}
+			if (!nwait)
+				break;
+			cpu_relax();
+		}
+	} else {
+		/* wait until all RUN bits are cleared */
+		for (timeout = 5000; timeout; timeout--) {
+			nwait = 0;
+			snd_pcm_group_for_each_entry(s, substream) {
+				if (s->pcm->card != substream->pcm->card)
+					continue;
+				azx_dev = get_azx_dev(s);
+				if (azx_sd_readb(azx_dev, SD_CTL) &
+				    SD_CTL_DMA_START)
+					nwait++;
+			}
+			if (!nwait)
+				break;
+			cpu_relax();
+		}
+	}
+	if (nsync > 1) {
+		spin_lock(&chip->reg_lock);
+		/* reset SYNC bits */
+		azx_writel(chip, SYNC, azx_readl(chip, SYNC) & ~sbits);
+		spin_unlock(&chip->reg_lock);
+	}
+	return 0;
+}
+
+/* get the current DMA position with correction on VIA chips */
+static unsigned int azx_via_get_position(struct azx *chip,
+					 struct azx_dev *azx_dev)
+{
+	unsigned int link_pos, mini_pos, bound_pos;
+	unsigned int mod_link_pos, mod_dma_pos, mod_mini_pos;
+	unsigned int fifo_size;
+
+	link_pos = azx_sd_readl(azx_dev, SD_LPIB);
+	if (azx_dev->index >= 4) {
+		/* Playback, no problem using link position */
+		return link_pos;
+	}
+
+	/* Capture */
+	/* For new chipset,
+	 * use mod to get the DMA position just like old chipset
+	 */
+	mod_dma_pos = le32_to_cpu(*azx_dev->posbuf);
+	mod_dma_pos %= azx_dev->period_bytes;
+
+	/* azx_dev->fifo_size can't get FIFO size of in stream.
+	 * Get from base address + offset.
+	 */
+	fifo_size = readw(chip->remap_addr + VIA_IN_STREAM0_FIFO_SIZE_OFFSET);
+
+	if (azx_dev->insufficient) {
+		/* Link position never gather than FIFO size */
+		if (link_pos <= fifo_size)
+			return 0;
+
+		azx_dev->insufficient = 0;
+	}
+
+	if (link_pos <= fifo_size)
+		mini_pos = azx_dev->bufsize + link_pos - fifo_size;
+	else
+		mini_pos = link_pos - fifo_size;
+
+	/* Find nearest previous boudary */
+	mod_mini_pos = mini_pos % azx_dev->period_bytes;
+	mod_link_pos = link_pos % azx_dev->period_bytes;
+	if (mod_link_pos >= fifo_size)
+		bound_pos = link_pos - mod_link_pos;
+	else if (mod_dma_pos >= mod_mini_pos)
+		bound_pos = mini_pos - mod_mini_pos;
+	else {
+		bound_pos = mini_pos - mod_mini_pos + azx_dev->period_bytes;
+		if (bound_pos >= azx_dev->bufsize)
+			bound_pos = 0;
+	}
+
+	/* Calculate real DMA position we want */
+	return bound_pos + mod_dma_pos;
+}
+
+static unsigned int azx_get_position(struct azx *chip,
+				     struct azx_dev *azx_dev)
+{
+	unsigned int pos;
+	int stream = azx_dev->substream->stream;
+
+	switch (chip->position_fix[stream]) {
+	case POS_FIX_LPIB:
+		/* read LPIB */
+		pos = azx_sd_readl(azx_dev, SD_LPIB);
+		break;
+	case POS_FIX_VIACOMBO:
+		pos = azx_via_get_position(chip, azx_dev);
+		break;
+	default:
+		/* use the position buffer */
+		pos = le32_to_cpu(*azx_dev->posbuf);
+	}
+
+	if (pos >= azx_dev->bufsize)
+		pos = 0;
+	return pos;
+}
+
+static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+	struct azx *chip = apcm->chip;
+	struct azx_dev *azx_dev = get_azx_dev(substream);
+	return bytes_to_frames(substream->runtime,
+			       azx_get_position(chip, azx_dev));
+}
+
+/*
+ * Check whether the current DMA position is acceptable for updating
+ * periods.  Returns non-zero if it's OK.
+ *
+ * Many HD-audio controllers appear pretty inaccurate about
+ * the update-IRQ timing.  The IRQ is issued before actually the
+ * data is processed.  So, we need to process it afterwords in a
+ * workqueue.
+ */
+static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
+{
+	u32 wallclk;
+	unsigned int pos;
+	int stream;
+
+	wallclk = azx_readl(chip, WALLCLK) - azx_dev->start_wallclk;
+	if (wallclk < (azx_dev->period_wallclk * 2) / 3)
+		return -1;	/* bogus (too early) interrupt */
+
+	stream = azx_dev->substream->stream;
+	pos = azx_get_position(chip, azx_dev);
+	if (chip->position_fix[stream] == POS_FIX_AUTO) {
+		if (!pos) {
+			printk(KERN_WARNING
+			       "hda-intel: Invalid position buffer, "
+			       "using LPIB read method instead.\n");
+			chip->position_fix[stream] = POS_FIX_LPIB;
+			pos = azx_get_position(chip, azx_dev);
+		} else
+			chip->position_fix[stream] = POS_FIX_POSBUF;
+	}
+
+	if (WARN_ONCE(!azx_dev->period_bytes,
+		      "hda-intel: zero azx_dev->period_bytes"))
+		return -1; /* this shouldn't happen! */
+	if (wallclk < (azx_dev->period_wallclk * 5) / 4 &&
+	    pos % azx_dev->period_bytes > azx_dev->period_bytes / 2)
+		/* NG - it's below the first next period boundary */
+		return bdl_pos_adj[chip->dev_index] ? 0 : -1;
+	azx_dev->start_wallclk += wallclk;
+	return 1; /* OK, it's fine */
+}
+
+/*
+ * The work for pending PCM period updates.
+ */
+static void azx_irq_pending_work(struct work_struct *work)
+{
+	struct azx *chip = container_of(work, struct azx, irq_pending_work);
+	int i, pending, ok;
+
+	if (!chip->irq_pending_warned) {
+		printk(KERN_WARNING
+		       "hda-intel: IRQ timing workaround is activated "
+		       "for card #%d. Suggest a bigger bdl_pos_adj.\n",
+		       chip->card->number);
+		chip->irq_pending_warned = 1;
+	}
+
+	for (;;) {
+		pending = 0;
+		spin_lock_irq(&chip->reg_lock);
+		for (i = 0; i < chip->num_streams; i++) {
+			struct azx_dev *azx_dev = &chip->azx_dev[i];
+			if (!azx_dev->irq_pending ||
+			    !azx_dev->substream ||
+			    !azx_dev->running)
+				continue;
+			ok = azx_position_ok(chip, azx_dev);
+			if (ok > 0) {
+				azx_dev->irq_pending = 0;
+				spin_unlock(&chip->reg_lock);
+				snd_pcm_period_elapsed(azx_dev->substream);
+				spin_lock(&chip->reg_lock);
+			} else if (ok < 0) {
+				pending = 0;	/* too early */
+			} else
+				pending++;
+		}
+		spin_unlock_irq(&chip->reg_lock);
+		if (!pending)
+			return;
+		msleep(1);
+	}
+}
+
+/* clear irq_pending flags and assure no on-going workq */
+static void azx_clear_irq_pending(struct azx *chip)
+{
+	int i;
+
+	spin_lock_irq(&chip->reg_lock);
+	for (i = 0; i < chip->num_streams; i++)
+		chip->azx_dev[i].irq_pending = 0;
+	spin_unlock_irq(&chip->reg_lock);
+}
+
+static struct snd_pcm_ops azx_pcm_ops = {
+	.open = azx_pcm_open,
+	.close = azx_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = azx_pcm_hw_params,
+	.hw_free = azx_pcm_hw_free,
+	.prepare = azx_pcm_prepare,
+	.trigger = azx_pcm_trigger,
+	.pointer = azx_pcm_pointer,
+	.page = snd_pcm_sgbuf_ops_page,
+};
+
+static void azx_pcm_free(struct snd_pcm *pcm)
+{
+	struct azx_pcm *apcm = pcm->private_data;
+	if (apcm) {
+		apcm->chip->pcm[pcm->device] = NULL;
+		kfree(apcm);
+	}
+}
+
+static int
+azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec,
+		      struct hda_pcm *cpcm)
+{
+	struct azx *chip = bus->private_data;
+	struct snd_pcm *pcm;
+	struct azx_pcm *apcm;
+	int pcm_dev = cpcm->device;
+	int s, err;
+
+	if (pcm_dev >= HDA_MAX_PCMS) {
+		snd_printk(KERN_ERR SFX "Invalid PCM device number %d\n",
+			   pcm_dev);
+		return -EINVAL;
+	}
+	if (chip->pcm[pcm_dev]) {
+		snd_printk(KERN_ERR SFX "PCM %d already exists\n", pcm_dev);
+		return -EBUSY;
+	}
+	err = snd_pcm_new(chip->card, cpcm->name, pcm_dev,
+			  cpcm->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams,
+			  cpcm->stream[SNDRV_PCM_STREAM_CAPTURE].substreams,
+			  &pcm);
+	if (err < 0)
+		return err;
+	strlcpy(pcm->name, cpcm->name, sizeof(pcm->name));
+	apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
+	if (apcm == NULL)
+		return -ENOMEM;
+	apcm->chip = chip;
+	apcm->codec = codec;
+	pcm->private_data = apcm;
+	pcm->private_free = azx_pcm_free;
+	if (cpcm->pcm_type == HDA_PCM_TYPE_MODEM)
+		pcm->dev_class = SNDRV_PCM_CLASS_MODEM;
+	chip->pcm[pcm_dev] = pcm;
+	cpcm->pcm = pcm;
+	for (s = 0; s < 2; s++) {
+		apcm->hinfo[s] = &cpcm->stream[s];
+		if (cpcm->stream[s].substreams)
+			snd_pcm_set_ops(pcm, s, &azx_pcm_ops);
+	}
+	/* buffer pre-allocation */
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+					      snd_dma_pci_data(chip->pci),
+					      1024 * 64, 32 * 1024 * 1024);
+	return 0;
+}
+
+/*
+ * mixer creation - all stuff is implemented in hda module
+ */
+static int __devinit azx_mixer_create(struct azx *chip)
+{
+	return snd_hda_build_controls(chip->bus);
+}
+
+
+/*
+ * initialize SD streams
+ */
+static int __devinit azx_init_stream(struct azx *chip)
+{
+	int i;
+
+	/* initialize each stream (aka device)
+	 * assign the starting bdl address to each stream (device)
+	 * and initialize
+	 */
+	for (i = 0; i < chip->num_streams; i++) {
+		struct azx_dev *azx_dev = &chip->azx_dev[i];
+		azx_dev->posbuf = (u32 __iomem *)(chip->posbuf.area + i * 8);
+		/* offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */
+		azx_dev->sd_addr = chip->remap_addr + (0x20 * i + 0x80);
+		/* int mask: SDI0=0x01, SDI1=0x02, ... SDO3=0x80 */
+		azx_dev->sd_int_sta_mask = 1 << i;
+		/* stream tag: must be non-zero and unique */
+		azx_dev->index = i;
+		azx_dev->stream_tag = i + 1;
+	}
+
+	return 0;
+}
+
+static int azx_acquire_irq(struct azx *chip, int do_disconnect)
+{
+	if (request_irq(chip->pci->irq, azx_interrupt,
+			chip->msi ? 0 : IRQF_SHARED,
+			"hda_intel", chip)) {
+		printk(KERN_ERR "hda-intel: unable to grab IRQ %d, "
+		       "disabling device\n", chip->pci->irq);
+		if (do_disconnect)
+			snd_card_disconnect(chip->card);
+		return -1;
+	}
+	chip->irq = chip->pci->irq;
+	pci_intx(chip->pci, !chip->msi);
+	return 0;
+}
+
+
+static void azx_stop_chip(struct azx *chip)
+{
+	if (!chip->initialized)
+		return;
+
+	/* disable interrupts */
+	azx_int_disable(chip);
+	azx_int_clear(chip);
+
+	/* disable CORB/RIRB */
+	azx_free_cmd_io(chip);
+
+	/* disable position buffer */
+	azx_writel(chip, DPLBASE, 0);
+	azx_writel(chip, DPUBASE, 0);
+
+	chip->initialized = 0;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+/* power-up/down the controller */
+static void azx_power_notify(struct hda_bus *bus)
+{
+	struct azx *chip = bus->private_data;
+	struct hda_codec *c;
+	int power_on = 0;
+
+	list_for_each_entry(c, &bus->codec_list, list) {
+		if (c->power_on) {
+			power_on = 1;
+			break;
+		}
+	}
+	if (power_on)
+		azx_init_chip(chip, 1);
+	else if (chip->running && power_save_controller &&
+		 !bus->power_keep_link_on)
+		azx_stop_chip(chip);
+}
+#endif /* CONFIG_SND_HDA_POWER_SAVE */
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+
+static int snd_hda_codecs_inuse(struct hda_bus *bus)
+{
+	struct hda_codec *codec;
+
+	list_for_each_entry(codec, &bus->codec_list, list) {
+		if (snd_hda_codec_needs_resume(codec))
+			return 1;
+	}
+	return 0;
+}
+
+static int azx_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct azx *chip = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	azx_clear_irq_pending(chip);
+	for (i = 0; i < HDA_MAX_PCMS; i++)
+		snd_pcm_suspend_all(chip->pcm[i]);
+	if (chip->initialized)
+		snd_hda_suspend(chip->bus);
+	azx_stop_chip(chip);
+	if (chip->irq >= 0) {
+		free_irq(chip->irq, chip);
+		chip->irq = -1;
+	}
+	if (chip->msi)
+		pci_disable_msi(chip->pci);
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int azx_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct azx *chip = card->private_data;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "hda-intel: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+	if (chip->msi)
+		if (pci_enable_msi(pci) < 0)
+			chip->msi = 0;
+	if (azx_acquire_irq(chip, 1) < 0)
+		return -EIO;
+	azx_init_pci(chip);
+
+	if (snd_hda_codecs_inuse(chip->bus))
+		azx_init_chip(chip, 1);
+
+	snd_hda_resume(chip->bus);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+
+/*
+ * reboot notifier for hang-up problem at power-down
+ */
+static int azx_halt(struct notifier_block *nb, unsigned long event, void *buf)
+{
+	struct azx *chip = container_of(nb, struct azx, reboot_notifier);
+	snd_hda_bus_reboot_notify(chip->bus);
+	azx_stop_chip(chip);
+	return NOTIFY_OK;
+}
+
+static void azx_notifier_register(struct azx *chip)
+{
+	chip->reboot_notifier.notifier_call = azx_halt;
+	register_reboot_notifier(&chip->reboot_notifier);
+}
+
+static void azx_notifier_unregister(struct azx *chip)
+{
+	if (chip->reboot_notifier.notifier_call)
+		unregister_reboot_notifier(&chip->reboot_notifier);
+}
+
+/*
+ * destructor
+ */
+static int azx_free(struct azx *chip)
+{
+	int i;
+
+	azx_notifier_unregister(chip);
+
+	if (chip->initialized) {
+		azx_clear_irq_pending(chip);
+		for (i = 0; i < chip->num_streams; i++)
+			azx_stream_stop(chip, &chip->azx_dev[i]);
+		azx_stop_chip(chip);
+	}
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void*)chip);
+	if (chip->msi)
+		pci_disable_msi(chip->pci);
+	if (chip->remap_addr)
+		iounmap(chip->remap_addr);
+
+	if (chip->azx_dev) {
+		for (i = 0; i < chip->num_streams; i++)
+			if (chip->azx_dev[i].bdl.area)
+				snd_dma_free_pages(&chip->azx_dev[i].bdl);
+	}
+	if (chip->rb.area)
+		snd_dma_free_pages(&chip->rb);
+	if (chip->posbuf.area)
+		snd_dma_free_pages(&chip->posbuf);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip->azx_dev);
+	kfree(chip);
+
+	return 0;
+}
+
+static int azx_dev_free(struct snd_device *device)
+{
+	return azx_free(device->device_data);
+}
+
+/*
+ * white/black-listing for position_fix
+ */
+static struct snd_pci_quirk position_fix_list[] __devinitdata = {
+	SND_PCI_QUIRK(0x1025, 0x009f, "Acer Aspire 5110", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1025, 0x026f, "Acer Aspire 5538", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1028, 0x01cc, "Dell D820", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1028, 0x01de, "Dell Precision 390", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1028, 0x01f6, "Dell Latitude 131L", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1028, 0x0470, "Dell Inspiron 1120", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x103c, 0x306d, "HP dv3", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1043, 0x813d, "ASUS P5AD2", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1043, 0x81b3, "ASUS", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1043, 0x81e7, "ASUS M2V", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x104d, 0x9069, "Sony VPCS11V9E", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1106, 0x3288, "ASUS M2V-MX SE", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1179, 0xff10, "Toshiba A100-259", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1297, 0x3166, "Shuttle", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1458, 0xa022, "ga-ma770-ud3", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1462, 0x1002, "MSI Wind U115", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1565, 0x820f, "Biostar Microtech", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1565, 0x8218, "Biostar Microtech", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x1849, 0x0888, "775Dual-VSTA", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x8086, 0x2503, "DG965OT AAD63733-203", POS_FIX_LPIB),
+	SND_PCI_QUIRK(0x8086, 0xd601, "eMachines T5212", POS_FIX_LPIB),
+	{}
+};
+
+static int __devinit check_position_fix(struct azx *chip, int fix)
+{
+	const struct snd_pci_quirk *q;
+
+	switch (fix) {
+	case POS_FIX_LPIB:
+	case POS_FIX_POSBUF:
+	case POS_FIX_VIACOMBO:
+		return fix;
+	}
+
+	q = snd_pci_quirk_lookup(chip->pci, position_fix_list);
+	if (q) {
+		printk(KERN_INFO
+		       "hda_intel: position_fix set to %d "
+		       "for device %04x:%04x\n",
+		       q->value, q->subvendor, q->subdevice);
+		return q->value;
+	}
+
+	/* Check VIA/ATI HD Audio Controller exist */
+	switch (chip->driver_type) {
+	case AZX_DRIVER_VIA:
+	case AZX_DRIVER_ATI:
+		/* Use link position directly, avoid any transfer problem. */
+		return POS_FIX_VIACOMBO;
+	}
+
+	return POS_FIX_AUTO;
+}
+
+/*
+ * black-lists for probe_mask
+ */
+static struct snd_pci_quirk probe_mask_list[] __devinitdata = {
+	/* Thinkpad often breaks the controller communication when accessing
+	 * to the non-working (or non-existing) modem codec slot.
+	 */
+	SND_PCI_QUIRK(0x1014, 0x05b7, "Thinkpad Z60", 0x01),
+	SND_PCI_QUIRK(0x17aa, 0x2010, "Thinkpad X/T/R60", 0x01),
+	SND_PCI_QUIRK(0x17aa, 0x20ac, "Thinkpad X/T/R61", 0x01),
+	/* broken BIOS */
+	SND_PCI_QUIRK(0x1028, 0x20ac, "Dell Studio Desktop", 0x01),
+	/* including bogus ALC268 in slot#2 that conflicts with ALC888 */
+	SND_PCI_QUIRK(0x17c0, 0x4085, "Medion MD96630", 0x01),
+	/* forced codec slots */
+	SND_PCI_QUIRK(0x1043, 0x1262, "ASUS W5Fm", 0x103),
+	SND_PCI_QUIRK(0x1046, 0x1262, "ASUS W5F", 0x103),
+	{}
+};
+
+#define AZX_FORCE_CODEC_MASK	0x100
+
+static void __devinit check_probe_mask(struct azx *chip, int dev)
+{
+	const struct snd_pci_quirk *q;
+
+	chip->codec_probe_mask = probe_mask[dev];
+	if (chip->codec_probe_mask == -1) {
+		q = snd_pci_quirk_lookup(chip->pci, probe_mask_list);
+		if (q) {
+			printk(KERN_INFO
+			       "hda_intel: probe_mask set to 0x%x "
+			       "for device %04x:%04x\n",
+			       q->value, q->subvendor, q->subdevice);
+			chip->codec_probe_mask = q->value;
+		}
+	}
+
+	/* check forced option */
+	if (chip->codec_probe_mask != -1 &&
+	    (chip->codec_probe_mask & AZX_FORCE_CODEC_MASK)) {
+		chip->codec_mask = chip->codec_probe_mask & 0xff;
+		printk(KERN_INFO "hda_intel: codec_mask forced to 0x%x\n",
+		       chip->codec_mask);
+	}
+}
+
+/*
+ * white/black-list for enable_msi
+ */
+static struct snd_pci_quirk msi_black_list[] __devinitdata = {
+	SND_PCI_QUIRK(0x1043, 0x81f2, "ASUS", 0), /* Athlon64 X2 + nvidia */
+	SND_PCI_QUIRK(0x1043, 0x81f6, "ASUS", 0), /* nvidia */
+	SND_PCI_QUIRK(0x1043, 0x822d, "ASUS", 0), /* Athlon64 X2 + nvidia MCP55 */
+	SND_PCI_QUIRK(0x1849, 0x0888, "ASRock", 0), /* Athlon64 X2 + nvidia */
+	SND_PCI_QUIRK(0xa0a0, 0x0575, "Aopen MZ915-M", 0), /* ICH6 */
+	{}
+};
+
+static void __devinit check_msi(struct azx *chip)
+{
+	const struct snd_pci_quirk *q;
+
+	if (enable_msi >= 0) {
+		chip->msi = !!enable_msi;
+		return;
+	}
+	chip->msi = 1;	/* enable MSI as default */
+	q = snd_pci_quirk_lookup(chip->pci, msi_black_list);
+	if (q) {
+		printk(KERN_INFO
+		       "hda_intel: msi for device %04x:%04x set to %d\n",
+		       q->subvendor, q->subdevice, q->value);
+		chip->msi = q->value;
+		return;
+	}
+
+	/* NVidia chipsets seem to cause troubles with MSI */
+	if (chip->driver_type == AZX_DRIVER_NVIDIA) {
+		printk(KERN_INFO "hda_intel: Disable MSI for Nvidia chipset\n");
+		chip->msi = 0;
+	}
+}
+
+
+/*
+ * constructor
+ */
+static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci,
+				int dev, int driver_type,
+				struct azx **rchip)
+{
+	struct azx *chip;
+	int i, err;
+	unsigned short gcap;
+	static struct snd_device_ops ops = {
+		.dev_free = azx_dev_free,
+	};
+
+	*rchip = NULL;
+
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip) {
+		snd_printk(KERN_ERR SFX "cannot allocate chip\n");
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&chip->reg_lock);
+	mutex_init(&chip->open_mutex);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	chip->driver_type = driver_type;
+	check_msi(chip);
+	chip->dev_index = dev;
+	INIT_WORK(&chip->irq_pending_work, azx_irq_pending_work);
+
+	chip->position_fix[0] = chip->position_fix[1] =
+		check_position_fix(chip, position_fix[dev]);
+	check_probe_mask(chip, dev);
+
+	chip->single_cmd = single_cmd;
+
+	if (bdl_pos_adj[dev] < 0) {
+		switch (chip->driver_type) {
+		case AZX_DRIVER_ICH:
+		case AZX_DRIVER_PCH:
+			bdl_pos_adj[dev] = 1;
+			break;
+		default:
+			bdl_pos_adj[dev] = 32;
+			break;
+		}
+	}
+
+#if BITS_PER_LONG != 64
+	/* Fix up base address on ULI M5461 */
+	if (chip->driver_type == AZX_DRIVER_ULI) {
+		u16 tmp3;
+		pci_read_config_word(pci, 0x40, &tmp3);
+		pci_write_config_word(pci, 0x40, tmp3 | 0x10);
+		pci_write_config_dword(pci, PCI_BASE_ADDRESS_1, 0);
+	}
+#endif
+
+	err = pci_request_regions(pci, "ICH HD audio");
+	if (err < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+
+	chip->addr = pci_resource_start(pci, 0);
+	chip->remap_addr = pci_ioremap_bar(pci, 0);
+	if (chip->remap_addr == NULL) {
+		snd_printk(KERN_ERR SFX "ioremap error\n");
+		err = -ENXIO;
+		goto errout;
+	}
+
+	if (chip->msi)
+		if (pci_enable_msi(pci) < 0)
+			chip->msi = 0;
+
+	if (azx_acquire_irq(chip, 0) < 0) {
+		err = -EBUSY;
+		goto errout;
+	}
+
+	pci_set_master(pci);
+	synchronize_irq(chip->irq);
+
+	gcap = azx_readw(chip, GCAP);
+	snd_printdd(SFX "chipset global capabilities = 0x%x\n", gcap);
+
+	/* disable SB600 64bit support for safety */
+	if ((chip->driver_type == AZX_DRIVER_ATI) ||
+	    (chip->driver_type == AZX_DRIVER_ATIHDMI)) {
+		struct pci_dev *p_smbus;
+		p_smbus = pci_get_device(PCI_VENDOR_ID_ATI,
+					 PCI_DEVICE_ID_ATI_SBX00_SMBUS,
+					 NULL);
+		if (p_smbus) {
+			if (p_smbus->revision < 0x30)
+				gcap &= ~ICH6_GCAP_64OK;
+			pci_dev_put(p_smbus);
+		}
+	}
+
+	/* disable 64bit DMA address for Teradici */
+	/* it does not work with device 6549:1200 subsys e4a2:040b */
+	if (chip->driver_type == AZX_DRIVER_TERA)
+		gcap &= ~ICH6_GCAP_64OK;
+
+	/* allow 64bit DMA address if supported by H/W */
+	if ((gcap & ICH6_GCAP_64OK) && !pci_set_dma_mask(pci, DMA_BIT_MASK(64)))
+		pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(64));
+	else {
+		pci_set_dma_mask(pci, DMA_BIT_MASK(32));
+		pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32));
+	}
+
+	/* read number of streams from GCAP register instead of using
+	 * hardcoded value
+	 */
+	chip->capture_streams = (gcap >> 8) & 0x0f;
+	chip->playback_streams = (gcap >> 12) & 0x0f;
+	if (!chip->playback_streams && !chip->capture_streams) {
+		/* gcap didn't give any info, switching to old method */
+
+		switch (chip->driver_type) {
+		case AZX_DRIVER_ULI:
+			chip->playback_streams = ULI_NUM_PLAYBACK;
+			chip->capture_streams = ULI_NUM_CAPTURE;
+			break;
+		case AZX_DRIVER_ATIHDMI:
+			chip->playback_streams = ATIHDMI_NUM_PLAYBACK;
+			chip->capture_streams = ATIHDMI_NUM_CAPTURE;
+			break;
+		case AZX_DRIVER_GENERIC:
+		default:
+			chip->playback_streams = ICH6_NUM_PLAYBACK;
+			chip->capture_streams = ICH6_NUM_CAPTURE;
+			break;
+		}
+	}
+	chip->capture_index_offset = 0;
+	chip->playback_index_offset = chip->capture_streams;
+	chip->num_streams = chip->playback_streams + chip->capture_streams;
+	chip->azx_dev = kcalloc(chip->num_streams, sizeof(*chip->azx_dev),
+				GFP_KERNEL);
+	if (!chip->azx_dev) {
+		snd_printk(KERN_ERR SFX "cannot malloc azx_dev\n");
+		goto errout;
+	}
+
+	for (i = 0; i < chip->num_streams; i++) {
+		/* allocate memory for the BDL for each stream */
+		err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+					  snd_dma_pci_data(chip->pci),
+					  BDL_SIZE, &chip->azx_dev[i].bdl);
+		if (err < 0) {
+			snd_printk(KERN_ERR SFX "cannot allocate BDL\n");
+			goto errout;
+		}
+	}
+	/* allocate memory for the position buffer */
+	err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+				  snd_dma_pci_data(chip->pci),
+				  chip->num_streams * 8, &chip->posbuf);
+	if (err < 0) {
+		snd_printk(KERN_ERR SFX "cannot allocate posbuf\n");
+		goto errout;
+	}
+	/* allocate CORB/RIRB */
+	err = azx_alloc_cmd_io(chip);
+	if (err < 0)
+		goto errout;
+
+	/* initialize streams */
+	azx_init_stream(chip);
+
+	/* initialize chip */
+	azx_init_pci(chip);
+	azx_init_chip(chip, (probe_only[dev] & 2) == 0);
+
+	/* codec detection */
+	if (!chip->codec_mask) {
+		snd_printk(KERN_ERR SFX "no codecs found!\n");
+		err = -ENODEV;
+		goto errout;
+	}
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err <0) {
+		snd_printk(KERN_ERR SFX "Error creating device [card]!\n");
+		goto errout;
+	}
+
+	strcpy(card->driver, "HDA-Intel");
+	strlcpy(card->shortname, driver_short_names[chip->driver_type],
+		sizeof(card->shortname));
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s at 0x%lx irq %i",
+		 card->shortname, chip->addr, chip->irq);
+
+	*rchip = chip;
+	return 0;
+
+ errout:
+	azx_free(chip);
+	return err;
+}
+
+static void power_down_all_codecs(struct azx *chip)
+{
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	/* The codecs were powered up in snd_hda_codec_new().
+	 * Now all initialization done, so turn them down if possible
+	 */
+	struct hda_codec *codec;
+	list_for_each_entry(codec, &chip->bus->codec_list, list) {
+		snd_hda_power_down(codec);
+	}
+#endif
+}
+
+static int __devinit azx_probe(struct pci_dev *pci,
+			       const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct azx *chip;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0) {
+		snd_printk(KERN_ERR SFX "Error creating card!\n");
+		return err;
+	}
+
+	/* set this here since it's referred in snd_hda_load_patch() */
+	snd_card_set_dev(card, &pci->dev);
+
+	err = azx_create(card, pci, dev, pci_id->driver_data, &chip);
+	if (err < 0)
+		goto out_free;
+	card->private_data = chip;
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+	chip->beep_mode = beep_mode[dev];
+#endif
+
+	/* create codec instances */
+	err = azx_codec_create(chip, model[dev]);
+	if (err < 0)
+		goto out_free;
+#ifdef CONFIG_SND_HDA_PATCH_LOADER
+	if (patch[dev]) {
+		snd_printk(KERN_ERR SFX "Applying patch firmware '%s'\n",
+			   patch[dev]);
+		err = snd_hda_load_patch(chip->bus, patch[dev]);
+		if (err < 0)
+			goto out_free;
+	}
+#endif
+	if ((probe_only[dev] & 1) == 0) {
+		err = azx_codec_configure(chip);
+		if (err < 0)
+			goto out_free;
+	}
+
+	/* create PCM streams */
+	err = snd_hda_build_pcms(chip->bus);
+	if (err < 0)
+		goto out_free;
+
+	/* create mixer controls */
+	err = azx_mixer_create(chip);
+	if (err < 0)
+		goto out_free;
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto out_free;
+
+	pci_set_drvdata(pci, card);
+	chip->running = 1;
+	power_down_all_codecs(chip);
+	azx_notifier_register(chip);
+
+	dev++;
+	return err;
+out_free:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit azx_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+/* PCI IDs */
+static DEFINE_PCI_DEVICE_TABLE(azx_ids) = {
+	/* CPT */
+	{ PCI_DEVICE(0x8086, 0x1c20), .driver_data = AZX_DRIVER_PCH },
+	/* PBG */
+	{ PCI_DEVICE(0x8086, 0x1d20), .driver_data = AZX_DRIVER_PCH },
+	/* SCH */
+	{ PCI_DEVICE(0x8086, 0x811b), .driver_data = AZX_DRIVER_SCH },
+	/* Generic Intel */
+	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_ANY_ID),
+	  .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+	  .class_mask = 0xffffff,
+	  .driver_data = AZX_DRIVER_ICH },
+	/* ATI SB 450/600 */
+	{ PCI_DEVICE(0x1002, 0x437b), .driver_data = AZX_DRIVER_ATI },
+	{ PCI_DEVICE(0x1002, 0x4383), .driver_data = AZX_DRIVER_ATI },
+	/* ATI HDMI */
+	{ PCI_DEVICE(0x1002, 0x793b), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0x7919), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0x960f), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0x970f), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0xaa00), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0xaa08), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0xaa10), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0xaa18), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0xaa20), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0xaa28), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0xaa30), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0xaa38), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0xaa40), .driver_data = AZX_DRIVER_ATIHDMI },
+	{ PCI_DEVICE(0x1002, 0xaa48), .driver_data = AZX_DRIVER_ATIHDMI },
+	/* VIA VT8251/VT8237A */
+	{ PCI_DEVICE(0x1106, 0x3288), .driver_data = AZX_DRIVER_VIA },
+	/* SIS966 */
+	{ PCI_DEVICE(0x1039, 0x7502), .driver_data = AZX_DRIVER_SIS },
+	/* ULI M5461 */
+	{ PCI_DEVICE(0x10b9, 0x5461), .driver_data = AZX_DRIVER_ULI },
+	/* NVIDIA MCP */
+	{ PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID),
+	  .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+	  .class_mask = 0xffffff,
+	  .driver_data = AZX_DRIVER_NVIDIA },
+	/* Teradici */
+	{ PCI_DEVICE(0x6549, 0x1200), .driver_data = AZX_DRIVER_TERA },
+	/* Creative X-Fi (CA0110-IBG) */
+#if !defined(CONFIG_SND_CTXFI) && !defined(CONFIG_SND_CTXFI_MODULE)
+	/* the following entry conflicts with snd-ctxfi driver,
+	 * as ctxfi driver mutates from HD-audio to native mode with
+	 * a special command sequence.
+	 */
+	{ PCI_DEVICE(PCI_VENDOR_ID_CREATIVE, PCI_ANY_ID),
+	  .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+	  .class_mask = 0xffffff,
+	  .driver_data = AZX_DRIVER_CTX },
+#else
+	/* this entry seems still valid -- i.e. without emu20kx chip */
+	{ PCI_DEVICE(0x1102, 0x0009), .driver_data = AZX_DRIVER_CTX },
+#endif
+	/* Vortex86MX */
+	{ PCI_DEVICE(0x17f3, 0x3010), .driver_data = AZX_DRIVER_GENERIC },
+	/* AMD/ATI Generic, PCI class code and Vendor ID for HD Audio */
+	{ PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_ANY_ID),
+	  .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+	  .class_mask = 0xffffff,
+	  .driver_data = AZX_DRIVER_GENERIC },
+	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_ANY_ID),
+	  .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+	  .class_mask = 0xffffff,
+	  .driver_data = AZX_DRIVER_GENERIC },
+	{ 0, }
+};
+MODULE_DEVICE_TABLE(pci, azx_ids);
+
+/* pci_driver definition */
+static struct pci_driver driver = {
+	.name = "HDA Intel",
+	.id_table = azx_ids,
+	.probe = azx_probe,
+	.remove = __devexit_p(azx_remove),
+#ifdef CONFIG_PM
+	.suspend = azx_suspend,
+	.resume = azx_resume,
+#endif
+};
+
+static int __init alsa_card_azx_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_azx_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_azx_init)
+module_exit(alsa_card_azx_exit)
diff --git a/linux/sound/pci/hda/hda_local.h b/linux/sound/pci/hda/hda_local.h
new file mode 100644
index 0000000..46bbefe
--- /dev/null
+++ b/linux/sound/pci/hda/hda_local.h
@@ -0,0 +1,659 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Local helper functions
+ *
+ * Copyright (c) 2004 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 __SOUND_HDA_LOCAL_H
+#define __SOUND_HDA_LOCAL_H
+
+/* We abuse kcontrol_new.subdev field to pass the NID corresponding to
+ * the given new control.  If id.subdev has a bit flag HDA_SUBDEV_NID_FLAG,
+ * snd_hda_ctl_add() takes the lower-bit subdev value as a valid NID.
+ * 
+ * Note that the subdevice field is cleared again before the real registration
+ * in snd_hda_ctl_add(), so that this value won't appear in the outside.
+ */
+#define HDA_SUBDEV_NID_FLAG	(1U << 31)
+#define HDA_SUBDEV_AMP_FLAG	(1U << 30)
+
+/*
+ * for mixer controls
+ */
+#define HDA_COMPOSE_AMP_VAL_OFS(nid,chs,idx,dir,ofs)		\
+	((nid) | ((chs)<<16) | ((dir)<<18) | ((idx)<<19) | ((ofs)<<23))
+#define HDA_AMP_VAL_MIN_MUTE (1<<29)
+#define HDA_COMPOSE_AMP_VAL(nid,chs,idx,dir) \
+	HDA_COMPOSE_AMP_VAL_OFS(nid, chs, idx, dir, 0)
+/* mono volume with index (index=0,1,...) (channel=1,2) */
+#define HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, channel, xindex, dir, flags) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx,  \
+	  .subdevice = HDA_SUBDEV_AMP_FLAG, \
+	  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+	  	    SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+	  	    SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \
+	  .info = snd_hda_mixer_amp_volume_info, \
+	  .get = snd_hda_mixer_amp_volume_get, \
+	  .put = snd_hda_mixer_amp_volume_put, \
+	  .tlv = { .c = snd_hda_mixer_amp_tlv },		\
+	  .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, dir) | flags }
+/* stereo volume with index */
+#define HDA_CODEC_VOLUME_IDX(xname, xcidx, nid, xindex, direction) \
+	HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, 3, xindex, direction, 0)
+/* mono volume */
+#define HDA_CODEC_VOLUME_MONO(xname, nid, channel, xindex, direction) \
+	HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, channel, xindex, direction, 0)
+/* stereo volume */
+#define HDA_CODEC_VOLUME(xname, nid, xindex, direction) \
+	HDA_CODEC_VOLUME_MONO(xname, nid, 3, xindex, direction)
+/* stereo volume with min=mute */
+#define HDA_CODEC_VOLUME_MIN_MUTE(xname, nid, xindex, direction) \
+	HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, 3, xindex, direction, \
+				  HDA_AMP_VAL_MIN_MUTE)
+/* mono mute switch with index (index=0,1,...) (channel=1,2) */
+#define HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
+	  .subdevice = HDA_SUBDEV_AMP_FLAG, \
+	  .info = snd_hda_mixer_amp_switch_info, \
+	  .get = snd_hda_mixer_amp_switch_get, \
+	  .put = snd_hda_mixer_amp_switch_put, \
+	  .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
+/* stereo mute switch with index */
+#define HDA_CODEC_MUTE_IDX(xname, xcidx, nid, xindex, direction) \
+	HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, 3, xindex, direction)
+/* mono mute switch */
+#define HDA_CODEC_MUTE_MONO(xname, nid, channel, xindex, direction) \
+	HDA_CODEC_MUTE_MONO_IDX(xname, 0, nid, channel, xindex, direction)
+/* stereo mute switch */
+#define HDA_CODEC_MUTE(xname, nid, xindex, direction) \
+	HDA_CODEC_MUTE_MONO(xname, nid, 3, xindex, direction)
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+/* special beep mono mute switch with index (index=0,1,...) (channel=1,2) */
+#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
+	  .subdevice = HDA_SUBDEV_AMP_FLAG, \
+	  .info = snd_hda_mixer_amp_switch_info, \
+	  .get = snd_hda_mixer_amp_switch_get, \
+	  .put = snd_hda_mixer_amp_switch_put_beep, \
+	  .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
+#else
+/* no digital beep - just the standard one */
+#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, ch, xidx, dir) \
+	HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, ch, xidx, dir)
+#endif /* CONFIG_SND_HDA_INPUT_BEEP */
+/* special beep mono mute switch */
+#define HDA_CODEC_MUTE_BEEP_MONO(xname, nid, channel, xindex, direction) \
+	HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, 0, nid, channel, xindex, direction)
+/* special beep stereo mute switch */
+#define HDA_CODEC_MUTE_BEEP(xname, nid, xindex, direction) \
+	HDA_CODEC_MUTE_BEEP_MONO(xname, nid, 3, xindex, direction)
+
+extern const char *snd_hda_pcm_type_name[];
+
+int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo);
+int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+			  unsigned int size, unsigned int __user *tlv);
+int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo);
+int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol);
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+int snd_hda_mixer_amp_switch_put_beep(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol);
+#endif
+/* lowlevel accessor with caching; use carefully */
+int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch,
+			   int direction, int index);
+int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch,
+			     int direction, int idx, int mask, int val);
+int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
+			     int dir, int idx, int mask, int val);
+#ifdef SND_HDA_NEEDS_RESUME
+void snd_hda_codec_resume_amp(struct hda_codec *codec);
+#endif
+
+void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
+			     unsigned int *tlv);
+struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
+					    const char *name);
+int snd_hda_add_vmaster(struct hda_codec *codec, char *name,
+			unsigned int *tlv, const char **slaves);
+int snd_hda_codec_reset(struct hda_codec *codec);
+
+/* amp value bits */
+#define HDA_AMP_MUTE	0x80
+#define HDA_AMP_UNMUTE	0x00
+#define HDA_AMP_VOLMASK	0x7f
+
+/* mono switch binding multiple inputs */
+#define HDA_BIND_MUTE_MONO(xname, nid, channel, indices, direction) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0,  \
+	  .info = snd_hda_mixer_amp_switch_info, \
+	  .get = snd_hda_mixer_bind_switch_get, \
+	  .put = snd_hda_mixer_bind_switch_put, \
+	  .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, indices, direction) }
+
+/* stereo switch binding multiple inputs */
+#define HDA_BIND_MUTE(xname,nid,indices,dir) \
+	HDA_BIND_MUTE_MONO(xname,nid,3,indices,dir)
+
+int snd_hda_mixer_bind_switch_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_bind_switch_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol);
+
+/* more generic bound controls */
+struct hda_ctl_ops {
+	snd_kcontrol_info_t *info;
+	snd_kcontrol_get_t *get;
+	snd_kcontrol_put_t *put;
+	snd_kcontrol_tlv_rw_t *tlv;
+};
+
+extern struct hda_ctl_ops snd_hda_bind_vol;	/* for bind-volume with TLV */
+extern struct hda_ctl_ops snd_hda_bind_sw;	/* for bind-switch */
+
+struct hda_bind_ctls {
+	struct hda_ctl_ops *ops;
+	unsigned long values[];
+};
+
+int snd_hda_mixer_bind_ctls_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo);
+int snd_hda_mixer_bind_ctls_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_bind_ctls_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol);
+int snd_hda_mixer_bind_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+			   unsigned int size, unsigned int __user *tlv);
+
+#define HDA_BIND_VOL(xname, bindrec) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	  .name = xname, \
+	  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\
+			  SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+			  SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,\
+	  .info = snd_hda_mixer_bind_ctls_info,\
+	  .get =  snd_hda_mixer_bind_ctls_get,\
+	  .put = snd_hda_mixer_bind_ctls_put,\
+	  .tlv = { .c = snd_hda_mixer_bind_tlv },\
+	  .private_value = (long) (bindrec) }
+#define HDA_BIND_SW(xname, bindrec) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
+	  .name = xname, \
+	  .info = snd_hda_mixer_bind_ctls_info,\
+	  .get =  snd_hda_mixer_bind_ctls_get,\
+	  .put = snd_hda_mixer_bind_ctls_put,\
+	  .private_value = (long) (bindrec) }
+
+/*
+ * SPDIF I/O
+ */
+int snd_hda_create_spdif_out_ctls(struct hda_codec *codec, hda_nid_t nid);
+int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid);
+
+/*
+ * input MUX helper
+ */
+#define HDA_MAX_NUM_INPUTS	16
+struct hda_input_mux_item {
+	char label[32];
+	unsigned int index;
+};
+struct hda_input_mux {
+	unsigned int num_items;
+	struct hda_input_mux_item items[HDA_MAX_NUM_INPUTS];
+};
+
+int snd_hda_input_mux_info(const struct hda_input_mux *imux,
+			   struct snd_ctl_elem_info *uinfo);
+int snd_hda_input_mux_put(struct hda_codec *codec,
+			  const struct hda_input_mux *imux,
+			  struct snd_ctl_elem_value *ucontrol, hda_nid_t nid,
+			  unsigned int *cur_val);
+
+/*
+ * Channel mode helper
+ */
+struct hda_channel_mode {
+	int channels;
+	const struct hda_verb *sequence;
+};
+
+int snd_hda_ch_mode_info(struct hda_codec *codec,
+			 struct snd_ctl_elem_info *uinfo,
+			 const struct hda_channel_mode *chmode,
+			 int num_chmodes);
+int snd_hda_ch_mode_get(struct hda_codec *codec,
+			struct snd_ctl_elem_value *ucontrol,
+			const struct hda_channel_mode *chmode,
+			int num_chmodes,
+			int max_channels);
+int snd_hda_ch_mode_put(struct hda_codec *codec,
+			struct snd_ctl_elem_value *ucontrol,
+			const struct hda_channel_mode *chmode,
+			int num_chmodes,
+			int *max_channelsp);
+
+/*
+ * Multi-channel / digital-out PCM helper
+ */
+
+enum { HDA_FRONT, HDA_REAR, HDA_CLFE, HDA_SIDE }; /* index for dac_nidx */
+enum { HDA_DIG_NONE, HDA_DIG_EXCLUSIVE, HDA_DIG_ANALOG_DUP }; /* dig_out_used */
+
+struct hda_multi_out {
+	int num_dacs;		/* # of DACs, must be more than 1 */
+	hda_nid_t *dac_nids;	/* DAC list */
+	hda_nid_t hp_nid;	/* optional DAC for HP, 0 when not exists */
+	hda_nid_t extra_out_nid[3];	/* optional DACs, 0 when not exists */
+	hda_nid_t dig_out_nid;	/* digital out audio widget */
+	hda_nid_t *slave_dig_outs;
+	int max_channels;	/* currently supported analog channels */
+	int dig_out_used;	/* current usage of digital out (HDA_DIG_XXX) */
+	int no_share_stream;	/* don't share a stream with multiple pins */
+	int share_spdif;	/* share SPDIF pin */
+	/* PCM information for both analog and SPDIF DACs */
+	unsigned int analog_rates;
+	unsigned int analog_maxbps;
+	u64 analog_formats;
+	unsigned int spdif_rates;
+	unsigned int spdif_maxbps;
+	u64 spdif_formats;
+};
+
+int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
+				  struct hda_multi_out *mout);
+int snd_hda_multi_out_dig_open(struct hda_codec *codec,
+			       struct hda_multi_out *mout);
+int snd_hda_multi_out_dig_close(struct hda_codec *codec,
+				struct hda_multi_out *mout);
+int snd_hda_multi_out_dig_prepare(struct hda_codec *codec,
+				  struct hda_multi_out *mout,
+				  unsigned int stream_tag,
+				  unsigned int format,
+				  struct snd_pcm_substream *substream);
+int snd_hda_multi_out_dig_cleanup(struct hda_codec *codec,
+				  struct hda_multi_out *mout);
+int snd_hda_multi_out_analog_open(struct hda_codec *codec,
+				  struct hda_multi_out *mout,
+				  struct snd_pcm_substream *substream,
+				  struct hda_pcm_stream *hinfo);
+int snd_hda_multi_out_analog_prepare(struct hda_codec *codec,
+				     struct hda_multi_out *mout,
+				     unsigned int stream_tag,
+				     unsigned int format,
+				     struct snd_pcm_substream *substream);
+int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec,
+				     struct hda_multi_out *mout);
+
+/*
+ * generic codec parser
+ */
+#ifdef CONFIG_SND_HDA_GENERIC
+int snd_hda_parse_generic_codec(struct hda_codec *codec);
+#else
+static inline int snd_hda_parse_generic_codec(struct hda_codec *codec)
+{
+	return -ENODEV;
+}
+#endif
+
+/*
+ * generic proc interface
+ */
+#ifdef CONFIG_PROC_FS
+int snd_hda_codec_proc_new(struct hda_codec *codec);
+#else
+static inline int snd_hda_codec_proc_new(struct hda_codec *codec) { return 0; }
+#endif
+
+#define SND_PRINT_RATES_ADVISED_BUFSIZE	80
+void snd_print_pcm_rates(int pcm, char *buf, int buflen);
+
+#define SND_PRINT_BITS_ADVISED_BUFSIZE	16
+void snd_print_pcm_bits(int pcm, char *buf, int buflen);
+
+/*
+ * Misc
+ */
+int snd_hda_check_board_config(struct hda_codec *codec, int num_configs,
+			       const char **modelnames,
+			       const struct snd_pci_quirk *pci_list);
+int snd_hda_check_board_codec_sid_config(struct hda_codec *codec,
+                               int num_configs, const char **models,
+                               const struct snd_pci_quirk *tbl);
+int snd_hda_add_new_ctls(struct hda_codec *codec,
+			 struct snd_kcontrol_new *knew);
+
+/*
+ * unsolicited event handler
+ */
+
+#define HDA_UNSOL_QUEUE_SIZE	64
+
+struct hda_bus_unsolicited {
+	/* ring buffer */
+	u32 queue[HDA_UNSOL_QUEUE_SIZE * 2];
+	unsigned int rp, wp;
+
+	/* workqueue */
+	struct work_struct work;
+	struct hda_bus *bus;
+};
+
+/*
+ * Helper for automatic pin configuration
+ */
+
+enum {
+	AUTO_PIN_MIC,
+	AUTO_PIN_LINE_IN,
+	AUTO_PIN_CD,
+	AUTO_PIN_AUX,
+	AUTO_PIN_LAST
+};
+
+enum {
+	AUTO_PIN_LINE_OUT,
+	AUTO_PIN_SPEAKER_OUT,
+	AUTO_PIN_HP_OUT
+};
+
+#define AUTO_CFG_MAX_OUTS	5
+#define AUTO_CFG_MAX_INS	8
+
+struct auto_pin_cfg_item {
+	hda_nid_t pin;
+	int type;
+};
+
+struct auto_pin_cfg;
+const char *hda_get_input_pin_label(struct hda_codec *codec, hda_nid_t pin,
+				    int check_location);
+const char *hda_get_autocfg_input_label(struct hda_codec *codec,
+					const struct auto_pin_cfg *cfg,
+					int input);
+int snd_hda_add_imux_item(struct hda_input_mux *imux, const char *label,
+			  int index, int *type_index_ret);
+
+enum {
+	INPUT_PIN_ATTR_UNUSED,	/* pin not connected */
+	INPUT_PIN_ATTR_INT,	/* internal mic/line-in */
+	INPUT_PIN_ATTR_DOCK,	/* docking mic/line-in */
+	INPUT_PIN_ATTR_NORMAL,	/* mic/line-in jack */
+	INPUT_PIN_ATTR_FRONT,	/* mic/line-in jack in front */
+	INPUT_PIN_ATTR_REAR,	/* mic/line-in jack in rear */
+};
+
+int snd_hda_get_input_pin_attr(unsigned int def_conf);
+
+struct auto_pin_cfg {
+	int line_outs;
+	/* sorted in the order of Front/Surr/CLFE/Side */
+	hda_nid_t line_out_pins[AUTO_CFG_MAX_OUTS];
+	int speaker_outs;
+	hda_nid_t speaker_pins[AUTO_CFG_MAX_OUTS];
+	int hp_outs;
+	int line_out_type;	/* AUTO_PIN_XXX_OUT */
+	hda_nid_t hp_pins[AUTO_CFG_MAX_OUTS];
+	int num_inputs;
+	struct auto_pin_cfg_item inputs[AUTO_CFG_MAX_INS];
+	int dig_outs;
+	hda_nid_t dig_out_pins[2];
+	hda_nid_t dig_in_pin;
+	hda_nid_t mono_out_pin;
+	int dig_out_type[2]; /* HDA_PCM_TYPE_XXX */
+	int dig_in_type; /* HDA_PCM_TYPE_XXX */
+};
+
+#define get_defcfg_connect(cfg) \
+	((cfg & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT)
+#define get_defcfg_association(cfg) \
+	((cfg & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT)
+#define get_defcfg_location(cfg) \
+	((cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT)
+#define get_defcfg_sequence(cfg) \
+	(cfg & AC_DEFCFG_SEQUENCE)
+#define get_defcfg_device(cfg) \
+	((cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT)
+
+int snd_hda_parse_pin_def_config(struct hda_codec *codec,
+				 struct auto_pin_cfg *cfg,
+				 hda_nid_t *ignore_nids);
+
+/* amp values */
+#define AMP_IN_MUTE(idx)	(0x7080 | ((idx)<<8))
+#define AMP_IN_UNMUTE(idx)	(0x7000 | ((idx)<<8))
+#define AMP_OUT_MUTE		0xb080
+#define AMP_OUT_UNMUTE		0xb000
+#define AMP_OUT_ZERO		0xb000
+/* pinctl values */
+#define PIN_IN			(AC_PINCTL_IN_EN)
+#define PIN_VREFHIZ		(AC_PINCTL_IN_EN | AC_PINCTL_VREF_HIZ)
+#define PIN_VREF50		(AC_PINCTL_IN_EN | AC_PINCTL_VREF_50)
+#define PIN_VREFGRD		(AC_PINCTL_IN_EN | AC_PINCTL_VREF_GRD)
+#define PIN_VREF80		(AC_PINCTL_IN_EN | AC_PINCTL_VREF_80)
+#define PIN_VREF100		(AC_PINCTL_IN_EN | AC_PINCTL_VREF_100)
+#define PIN_OUT			(AC_PINCTL_OUT_EN)
+#define PIN_HP			(AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN)
+#define PIN_HP_AMP		(AC_PINCTL_HP_EN)
+
+/*
+ * get widget capabilities
+ */
+static inline u32 get_wcaps(struct hda_codec *codec, hda_nid_t nid)
+{
+	if (nid < codec->start_nid ||
+	    nid >= codec->start_nid + codec->num_nodes)
+		return 0;
+	return codec->wcaps[nid - codec->start_nid];
+}
+
+/* get the widget type from widget capability bits */
+#define get_wcaps_type(wcaps) (((wcaps) & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT)
+
+static inline unsigned int get_wcaps_channels(u32 wcaps)
+{
+	unsigned int chans;
+
+	chans = (wcaps & AC_WCAP_CHAN_CNT_EXT) >> 13;
+	chans = ((chans << 1) | 1) + 1;
+
+	return chans;
+}
+
+u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction);
+int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
+			      unsigned int caps);
+u32 snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid);
+u32 snd_hda_pin_sense(struct hda_codec *codec, hda_nid_t nid);
+int snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid);
+
+/* flags for hda_nid_item */
+#define HDA_NID_ITEM_AMP	(1<<0)
+
+struct hda_nid_item {
+	struct snd_kcontrol *kctl;
+	unsigned int index;
+	hda_nid_t nid;
+	unsigned short flags;
+};
+
+int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid,
+		    struct snd_kcontrol *kctl);
+int snd_hda_add_nid(struct hda_codec *codec, struct snd_kcontrol *kctl,
+		    unsigned int index, hda_nid_t nid);
+void snd_hda_ctls_clear(struct hda_codec *codec);
+
+/*
+ * hwdep interface
+ */
+#ifdef CONFIG_SND_HDA_HWDEP
+int snd_hda_create_hwdep(struct hda_codec *codec);
+#else
+static inline int snd_hda_create_hwdep(struct hda_codec *codec) { return 0; }
+#endif
+
+#if defined(CONFIG_SND_HDA_POWER_SAVE) && defined(CONFIG_SND_HDA_HWDEP)
+int snd_hda_hwdep_add_power_sysfs(struct hda_codec *codec);
+#else
+static inline int snd_hda_hwdep_add_power_sysfs(struct hda_codec *codec)
+{
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+int snd_hda_hwdep_add_sysfs(struct hda_codec *codec);
+#else
+static inline int snd_hda_hwdep_add_sysfs(struct hda_codec *codec)
+{
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+const char *snd_hda_get_hint(struct hda_codec *codec, const char *key);
+int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key);
+#else
+static inline
+const char *snd_hda_get_hint(struct hda_codec *codec, const char *key)
+{
+	return NULL;
+}
+
+static inline
+int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key)
+{
+	return -ENOENT;
+}
+#endif
+
+/*
+ * power-management
+ */
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+void snd_hda_schedule_power_save(struct hda_codec *codec);
+
+struct hda_amp_list {
+	hda_nid_t nid;
+	unsigned char dir;
+	unsigned char idx;
+};
+
+struct hda_loopback_check {
+	struct hda_amp_list *amplist;
+	int power_on;
+};
+
+int snd_hda_check_amp_list_power(struct hda_codec *codec,
+				 struct hda_loopback_check *check,
+				 hda_nid_t nid);
+#endif /* CONFIG_SND_HDA_POWER_SAVE */
+
+/*
+ * AMP control callbacks
+ */
+/* retrieve parameters from private_value */
+#define get_amp_nid_(pv)	((pv) & 0xffff)
+#define get_amp_nid(kc)		get_amp_nid_((kc)->private_value)
+#define get_amp_channels(kc)	(((kc)->private_value >> 16) & 0x3)
+#define get_amp_direction(kc)	(((kc)->private_value >> 18) & 0x1)
+#define get_amp_index(kc)	(((kc)->private_value >> 19) & 0xf)
+#define get_amp_offset(kc)	(((kc)->private_value >> 23) & 0x3f)
+#define get_amp_min_mute(kc)	(((kc)->private_value >> 29) & 0x1)
+
+/*
+ * CEA Short Audio Descriptor data
+ */
+struct cea_sad {
+	int	channels;
+	int	format;		/* (format == 0) indicates invalid SAD */
+	int	rates;
+	int	sample_bits;	/* for LPCM */
+	int	max_bitrate;	/* for AC3...ATRAC */
+	int	profile;	/* for WMAPRO */
+};
+
+#define ELD_FIXED_BYTES	20
+#define ELD_MAX_MNL	16
+#define ELD_MAX_SAD	16
+
+/*
+ * ELD: EDID Like Data
+ */
+struct hdmi_eld {
+	bool	monitor_present;
+	bool	eld_valid;
+	int	eld_size;
+	int	baseline_len;
+	int	eld_ver;
+	int	cea_edid_ver;
+	char	monitor_name[ELD_MAX_MNL + 1];
+	int	manufacture_id;
+	int	product_id;
+	u64	port_id;
+	int	support_hdcp;
+	int	support_ai;
+	int	conn_type;
+	int	aud_synch_delay;
+	int	spk_alloc;
+	int	sad_count;
+	struct cea_sad sad[ELD_MAX_SAD];
+#ifdef CONFIG_PROC_FS
+	struct snd_info_entry *proc_entry;
+#endif
+};
+
+int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid);
+int snd_hdmi_get_eld(struct hdmi_eld *, struct hda_codec *, hda_nid_t);
+void snd_hdmi_show_eld(struct hdmi_eld *eld);
+void hdmi_eld_update_pcm_info(struct hdmi_eld *eld, struct hda_pcm_stream *pcm,
+			      struct hda_pcm_stream *codec_pars);
+
+#ifdef CONFIG_PROC_FS
+int snd_hda_eld_proc_new(struct hda_codec *codec, struct hdmi_eld *eld,
+			 int index);
+void snd_hda_eld_proc_free(struct hda_codec *codec, struct hdmi_eld *eld);
+#else
+static inline int snd_hda_eld_proc_new(struct hda_codec *codec,
+				       struct hdmi_eld *eld,
+				       int index)
+{
+	return 0;
+}
+static inline void snd_hda_eld_proc_free(struct hda_codec *codec,
+					 struct hdmi_eld *eld)
+{
+}
+#endif
+
+#define SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE 80
+void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen);
+
+#endif /* __SOUND_HDA_LOCAL_H */
diff --git a/linux/sound/pci/hda/hda_proc.c b/linux/sound/pci/hda/hda_proc.c
new file mode 100644
index 0000000..f025200
--- /dev/null
+++ b/linux/sound/pci/hda/hda_proc.c
@@ -0,0 +1,727 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ * 
+ * Generic proc interface
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+static char *bits_names(unsigned int bits, char *names[], int size)
+{
+	int i, n;
+	static char buf[128];
+
+	for (i = 0, n = 0; i < size; i++) {
+		if (bits & (1U<<i) && names[i])
+			n += snprintf(buf + n, sizeof(buf) - n, " %s",
+				      names[i]);
+	}
+	buf[n] = '\0';
+
+	return buf;
+}
+
+static const char *get_wid_type_name(unsigned int wid_value)
+{
+	static char *names[16] = {
+		[AC_WID_AUD_OUT] = "Audio Output",
+		[AC_WID_AUD_IN] = "Audio Input",
+		[AC_WID_AUD_MIX] = "Audio Mixer",
+		[AC_WID_AUD_SEL] = "Audio Selector",
+		[AC_WID_PIN] = "Pin Complex",
+		[AC_WID_POWER] = "Power Widget",
+		[AC_WID_VOL_KNB] = "Volume Knob Widget",
+		[AC_WID_BEEP] = "Beep Generator Widget",
+		[AC_WID_VENDOR] = "Vendor Defined Widget",
+	};
+	wid_value &= 0xf;
+	if (names[wid_value])
+		return names[wid_value];
+	else
+		return "UNKNOWN Widget";
+}
+
+static void print_nid_array(struct snd_info_buffer *buffer,
+			    struct hda_codec *codec, hda_nid_t nid,
+			    struct snd_array *array)
+{
+	int i;
+	struct hda_nid_item *items = array->list, *item;
+	struct snd_kcontrol *kctl;
+	for (i = 0; i < array->used; i++) {
+		item = &items[i];
+		if (item->nid == nid) {
+			kctl = item->kctl;
+			snd_iprintf(buffer,
+			  "  Control: name=\"%s\", index=%i, device=%i\n",
+			  kctl->id.name, kctl->id.index + item->index,
+			  kctl->id.device);
+			if (item->flags & HDA_NID_ITEM_AMP)
+				snd_iprintf(buffer,
+				  "    ControlAmp: chs=%lu, dir=%s, "
+				  "idx=%lu, ofs=%lu\n",
+				  get_amp_channels(kctl),
+				  get_amp_direction(kctl) ? "Out" : "In",
+				  get_amp_index(kctl),
+				  get_amp_offset(kctl));
+		}
+	}
+}
+
+static void print_nid_pcms(struct snd_info_buffer *buffer,
+			   struct hda_codec *codec, hda_nid_t nid)
+{
+	int pcm, type;
+	struct hda_pcm *cpcm;
+	for (pcm = 0; pcm < codec->num_pcms; pcm++) {
+		cpcm = &codec->pcm_info[pcm];
+		for (type = 0; type < 2; type++) {
+			if (cpcm->stream[type].nid != nid || cpcm->pcm == NULL)
+				continue;
+			snd_iprintf(buffer, "  Device: name=\"%s\", "
+				    "type=\"%s\", device=%i\n",
+				    cpcm->name,
+				    snd_hda_pcm_type_name[cpcm->pcm_type],
+				    cpcm->pcm->device);
+		}
+	}
+}
+
+static void print_amp_caps(struct snd_info_buffer *buffer,
+			   struct hda_codec *codec, hda_nid_t nid, int dir)
+{
+	unsigned int caps;
+	caps = snd_hda_param_read(codec, nid,
+				  dir == HDA_OUTPUT ?
+				    AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
+	if (caps == -1 || caps == 0) {
+		snd_iprintf(buffer, "N/A\n");
+		return;
+	}
+	snd_iprintf(buffer, "ofs=0x%02x, nsteps=0x%02x, stepsize=0x%02x, "
+		    "mute=%x\n",
+		    caps & AC_AMPCAP_OFFSET,
+		    (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT,
+		    (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT,
+		    (caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT);
+}
+
+static void print_amp_vals(struct snd_info_buffer *buffer,
+			   struct hda_codec *codec, hda_nid_t nid,
+			   int dir, int stereo, int indices)
+{
+	unsigned int val;
+	int i;
+
+	dir = dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
+	for (i = 0; i < indices; i++) {
+		snd_iprintf(buffer, " [");
+		if (stereo) {
+			val = snd_hda_codec_read(codec, nid, 0,
+						 AC_VERB_GET_AMP_GAIN_MUTE,
+						 AC_AMP_GET_LEFT | dir | i);
+			snd_iprintf(buffer, "0x%02x ", val);
+		}
+		val = snd_hda_codec_read(codec, nid, 0,
+					 AC_VERB_GET_AMP_GAIN_MUTE,
+					 AC_AMP_GET_RIGHT | dir | i);
+		snd_iprintf(buffer, "0x%02x]", val);
+	}
+	snd_iprintf(buffer, "\n");
+}
+
+static void print_pcm_rates(struct snd_info_buffer *buffer, unsigned int pcm)
+{
+	char buf[SND_PRINT_RATES_ADVISED_BUFSIZE];
+
+	pcm &= AC_SUPPCM_RATES;
+	snd_iprintf(buffer, "    rates [0x%x]:", pcm);
+	snd_print_pcm_rates(pcm, buf, sizeof(buf));
+	snd_iprintf(buffer, "%s\n", buf);
+}
+
+static void print_pcm_bits(struct snd_info_buffer *buffer, unsigned int pcm)
+{
+	char buf[SND_PRINT_BITS_ADVISED_BUFSIZE];
+
+	snd_iprintf(buffer, "    bits [0x%x]:", (pcm >> 16) & 0xff);
+	snd_print_pcm_bits(pcm, buf, sizeof(buf));
+	snd_iprintf(buffer, "%s\n", buf);
+}
+
+static void print_pcm_formats(struct snd_info_buffer *buffer,
+			      unsigned int streams)
+{
+	snd_iprintf(buffer, "    formats [0x%x]:", streams & 0xf);
+	if (streams & AC_SUPFMT_PCM)
+		snd_iprintf(buffer, " PCM");
+	if (streams & AC_SUPFMT_FLOAT32)
+		snd_iprintf(buffer, " FLOAT");
+	if (streams & AC_SUPFMT_AC3)
+		snd_iprintf(buffer, " AC3");
+	snd_iprintf(buffer, "\n");
+}
+
+static void print_pcm_caps(struct snd_info_buffer *buffer,
+			   struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int pcm = snd_hda_param_read(codec, nid, AC_PAR_PCM);
+	unsigned int stream = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
+	if (pcm == -1 || stream == -1) {
+		snd_iprintf(buffer, "N/A\n");
+		return;
+	}
+	print_pcm_rates(buffer, pcm);
+	print_pcm_bits(buffer, pcm);
+	print_pcm_formats(buffer, stream);
+}
+
+static const char *get_jack_connection(u32 cfg)
+{
+	static char *names[16] = {
+		"Unknown", "1/8", "1/4", "ATAPI",
+		"RCA", "Optical","Digital", "Analog",
+		"DIN", "XLR", "RJ11", "Comb",
+		NULL, NULL, NULL, "Other"
+	};
+	cfg = (cfg & AC_DEFCFG_CONN_TYPE) >> AC_DEFCFG_CONN_TYPE_SHIFT;
+	if (names[cfg])
+		return names[cfg];
+	else
+		return "UNKNOWN";
+}
+
+static const char *get_jack_color(u32 cfg)
+{
+	static char *names[16] = {
+		"Unknown", "Black", "Grey", "Blue",
+		"Green", "Red", "Orange", "Yellow",
+		"Purple", "Pink", NULL, NULL,
+		NULL, NULL, "White", "Other",
+	};
+	cfg = (cfg & AC_DEFCFG_COLOR) >> AC_DEFCFG_COLOR_SHIFT;
+	if (names[cfg])
+		return names[cfg];
+	else
+		return "UNKNOWN";
+}
+
+static void print_pin_caps(struct snd_info_buffer *buffer,
+			   struct hda_codec *codec, hda_nid_t nid,
+			   int *supports_vref)
+{
+	static char *jack_conns[4] = { "Jack", "N/A", "Fixed", "Both" };
+	unsigned int caps, val;
+
+	caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
+	snd_iprintf(buffer, "  Pincap 0x%08x:", caps);
+	if (caps & AC_PINCAP_IN)
+		snd_iprintf(buffer, " IN");
+	if (caps & AC_PINCAP_OUT)
+		snd_iprintf(buffer, " OUT");
+	if (caps & AC_PINCAP_HP_DRV)
+		snd_iprintf(buffer, " HP");
+	if (caps & AC_PINCAP_EAPD)
+		snd_iprintf(buffer, " EAPD");
+	if (caps & AC_PINCAP_PRES_DETECT)
+		snd_iprintf(buffer, " Detect");
+	if (caps & AC_PINCAP_BALANCE)
+		snd_iprintf(buffer, " Balanced");
+	if (caps & AC_PINCAP_HDMI) {
+		/* Realtek uses this bit as a different meaning */
+		if ((codec->vendor_id >> 16) == 0x10ec)
+			snd_iprintf(buffer, " R/L");
+		else {
+			if (caps & AC_PINCAP_HBR)
+				snd_iprintf(buffer, " HBR");
+			snd_iprintf(buffer, " HDMI");
+		}
+	}
+	if (caps & AC_PINCAP_DP)
+		snd_iprintf(buffer, " DP");
+	if (caps & AC_PINCAP_TRIG_REQ)
+		snd_iprintf(buffer, " Trigger");
+	if (caps & AC_PINCAP_IMP_SENSE)
+		snd_iprintf(buffer, " ImpSense");
+	snd_iprintf(buffer, "\n");
+	if (caps & AC_PINCAP_VREF) {
+		unsigned int vref =
+			(caps & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
+		snd_iprintf(buffer, "    Vref caps:");
+		if (vref & AC_PINCAP_VREF_HIZ)
+			snd_iprintf(buffer, " HIZ");
+		if (vref & AC_PINCAP_VREF_50)
+			snd_iprintf(buffer, " 50");
+		if (vref & AC_PINCAP_VREF_GRD)
+			snd_iprintf(buffer, " GRD");
+		if (vref & AC_PINCAP_VREF_80)
+			snd_iprintf(buffer, " 80");
+		if (vref & AC_PINCAP_VREF_100)
+			snd_iprintf(buffer, " 100");
+		snd_iprintf(buffer, "\n");
+		*supports_vref = 1;
+	} else
+		*supports_vref = 0;
+	if (caps & AC_PINCAP_EAPD) {
+		val = snd_hda_codec_read(codec, nid, 0,
+					 AC_VERB_GET_EAPD_BTLENABLE, 0);
+		snd_iprintf(buffer, "  EAPD 0x%x:", val);
+		if (val & AC_EAPDBTL_BALANCED)
+			snd_iprintf(buffer, " BALANCED");
+		if (val & AC_EAPDBTL_EAPD)
+			snd_iprintf(buffer, " EAPD");
+		if (val & AC_EAPDBTL_LR_SWAP)
+			snd_iprintf(buffer, " R/L");
+		snd_iprintf(buffer, "\n");
+	}
+	caps = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0);
+	snd_iprintf(buffer, "  Pin Default 0x%08x: [%s] %s at %s %s\n", caps,
+		    jack_conns[(caps & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT],
+		    snd_hda_get_jack_type(caps),
+		    snd_hda_get_jack_connectivity(caps),
+		    snd_hda_get_jack_location(caps));
+	snd_iprintf(buffer, "    Conn = %s, Color = %s\n",
+		    get_jack_connection(caps),
+		    get_jack_color(caps));
+	/* Default association and sequence values refer to default grouping
+	 * of pin complexes and their sequence within the group. This is used
+	 * for priority and resource allocation.
+	 */
+	snd_iprintf(buffer, "    DefAssociation = 0x%x, Sequence = 0x%x\n",
+		    (caps & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT,
+		    caps & AC_DEFCFG_SEQUENCE);
+	if (((caps & AC_DEFCFG_MISC) >> AC_DEFCFG_MISC_SHIFT) &
+	    AC_DEFCFG_MISC_NO_PRESENCE) {
+		/* Miscellaneous bit indicates external hardware does not
+		 * support presence detection even if the pin complex
+		 * indicates it is supported.
+		 */
+		snd_iprintf(buffer, "    Misc = NO_PRESENCE\n");
+	}
+}
+
+static void print_pin_ctls(struct snd_info_buffer *buffer,
+			   struct hda_codec *codec, hda_nid_t nid,
+			   int supports_vref)
+{
+	unsigned int pinctls;
+
+	pinctls = snd_hda_codec_read(codec, nid, 0,
+				     AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+	snd_iprintf(buffer, "  Pin-ctls: 0x%02x:", pinctls);
+	if (pinctls & AC_PINCTL_IN_EN)
+		snd_iprintf(buffer, " IN");
+	if (pinctls & AC_PINCTL_OUT_EN)
+		snd_iprintf(buffer, " OUT");
+	if (pinctls & AC_PINCTL_HP_EN)
+		snd_iprintf(buffer, " HP");
+	if (supports_vref) {
+		int vref = pinctls & AC_PINCTL_VREFEN;
+		switch (vref) {
+		case AC_PINCTL_VREF_HIZ:
+			snd_iprintf(buffer, " VREF_HIZ");
+			break;
+		case AC_PINCTL_VREF_50:
+			snd_iprintf(buffer, " VREF_50");
+			break;
+		case AC_PINCTL_VREF_GRD:
+			snd_iprintf(buffer, " VREF_GRD");
+			break;
+		case AC_PINCTL_VREF_80:
+			snd_iprintf(buffer, " VREF_80");
+			break;
+		case AC_PINCTL_VREF_100:
+			snd_iprintf(buffer, " VREF_100");
+			break;
+		}
+	}
+	snd_iprintf(buffer, "\n");
+}
+
+static void print_vol_knob(struct snd_info_buffer *buffer,
+			   struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int cap = snd_hda_param_read(codec, nid,
+					      AC_PAR_VOL_KNB_CAP);
+	snd_iprintf(buffer, "  Volume-Knob: delta=%d, steps=%d, ",
+		    (cap >> 7) & 1, cap & 0x7f);
+	cap = snd_hda_codec_read(codec, nid, 0,
+				 AC_VERB_GET_VOLUME_KNOB_CONTROL, 0);
+	snd_iprintf(buffer, "direct=%d, val=%d\n",
+		    (cap >> 7) & 1, cap & 0x7f);
+}
+
+static void print_audio_io(struct snd_info_buffer *buffer,
+			   struct hda_codec *codec, hda_nid_t nid,
+			   unsigned int wid_type)
+{
+	int conv = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0);
+	snd_iprintf(buffer,
+		    "  Converter: stream=%d, channel=%d\n",
+		    (conv & AC_CONV_STREAM) >> AC_CONV_STREAM_SHIFT,
+		    conv & AC_CONV_CHANNEL);
+
+	if (wid_type == AC_WID_AUD_IN && (conv & AC_CONV_CHANNEL) == 0) {
+		int sdi = snd_hda_codec_read(codec, nid, 0,
+					     AC_VERB_GET_SDI_SELECT, 0);
+		snd_iprintf(buffer, "  SDI-Select: %d\n",
+			    sdi & AC_SDI_SELECT);
+	}
+}
+
+static void print_digital_conv(struct snd_info_buffer *buffer,
+			       struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int digi1 = snd_hda_codec_read(codec, nid, 0,
+						AC_VERB_GET_DIGI_CONVERT_1, 0);
+	snd_iprintf(buffer, "  Digital:");
+	if (digi1 & AC_DIG1_ENABLE)
+		snd_iprintf(buffer, " Enabled");
+	if (digi1 & AC_DIG1_V)
+		snd_iprintf(buffer, " Validity");
+	if (digi1 & AC_DIG1_VCFG)
+		snd_iprintf(buffer, " ValidityCfg");
+	if (digi1 & AC_DIG1_EMPHASIS)
+		snd_iprintf(buffer, " Preemphasis");
+	if (digi1 & AC_DIG1_COPYRIGHT)
+		snd_iprintf(buffer, " Copyright");
+	if (digi1 & AC_DIG1_NONAUDIO)
+		snd_iprintf(buffer, " Non-Audio");
+	if (digi1 & AC_DIG1_PROFESSIONAL)
+		snd_iprintf(buffer, " Pro");
+	if (digi1 & AC_DIG1_LEVEL)
+		snd_iprintf(buffer, " GenLevel");
+	snd_iprintf(buffer, "\n");
+	snd_iprintf(buffer, "  Digital category: 0x%x\n",
+		    (digi1 >> 8) & AC_DIG2_CC);
+}
+
+static const char *get_pwr_state(u32 state)
+{
+	static const char *buf[4] = {
+		"D0", "D1", "D2", "D3"
+	};
+	if (state < 4)
+		return buf[state];
+	return "UNKNOWN";
+}
+
+static void print_power_state(struct snd_info_buffer *buffer,
+			      struct hda_codec *codec, hda_nid_t nid)
+{
+	static char *names[] = {
+		[ilog2(AC_PWRST_D0SUP)]		= "D0",
+		[ilog2(AC_PWRST_D1SUP)]		= "D1",
+		[ilog2(AC_PWRST_D2SUP)]		= "D2",
+		[ilog2(AC_PWRST_D3SUP)]		= "D3",
+		[ilog2(AC_PWRST_D3COLDSUP)]	= "D3cold",
+		[ilog2(AC_PWRST_S3D3COLDSUP)]	= "S3D3cold",
+		[ilog2(AC_PWRST_CLKSTOP)]	= "CLKSTOP",
+		[ilog2(AC_PWRST_EPSS)]		= "EPSS",
+	};
+
+	int sup = snd_hda_param_read(codec, nid, AC_PAR_POWER_STATE);
+	int pwr = snd_hda_codec_read(codec, nid, 0,
+				     AC_VERB_GET_POWER_STATE, 0);
+	if (sup)
+		snd_iprintf(buffer, "  Power states: %s\n",
+			    bits_names(sup, names, ARRAY_SIZE(names)));
+
+	snd_iprintf(buffer, "  Power: setting=%s, actual=%s\n",
+		    get_pwr_state(pwr & AC_PWRST_SETTING),
+		    get_pwr_state((pwr & AC_PWRST_ACTUAL) >>
+				  AC_PWRST_ACTUAL_SHIFT));
+}
+
+static void print_unsol_cap(struct snd_info_buffer *buffer,
+			      struct hda_codec *codec, hda_nid_t nid)
+{
+	int unsol = snd_hda_codec_read(codec, nid, 0,
+				       AC_VERB_GET_UNSOLICITED_RESPONSE, 0);
+	snd_iprintf(buffer,
+		    "  Unsolicited: tag=%02x, enabled=%d\n",
+		    unsol & AC_UNSOL_TAG,
+		    (unsol & AC_UNSOL_ENABLED) ? 1 : 0);
+}
+
+static void print_proc_caps(struct snd_info_buffer *buffer,
+			    struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int proc_caps = snd_hda_param_read(codec, nid,
+						    AC_PAR_PROC_CAP);
+	snd_iprintf(buffer, "  Processing caps: benign=%d, ncoeff=%d\n",
+		    proc_caps & AC_PCAP_BENIGN,
+		    (proc_caps & AC_PCAP_NUM_COEF) >> AC_PCAP_NUM_COEF_SHIFT);
+}
+
+static void print_conn_list(struct snd_info_buffer *buffer,
+			    struct hda_codec *codec, hda_nid_t nid,
+			    unsigned int wid_type, hda_nid_t *conn,
+			    int conn_len)
+{
+	int c, curr = -1;
+
+	if (conn_len > 1 &&
+	    wid_type != AC_WID_AUD_MIX &&
+	    wid_type != AC_WID_VOL_KNB &&
+	    wid_type != AC_WID_POWER)
+		curr = snd_hda_codec_read(codec, nid, 0,
+					  AC_VERB_GET_CONNECT_SEL, 0);
+	snd_iprintf(buffer, "  Connection: %d\n", conn_len);
+	if (conn_len > 0) {
+		snd_iprintf(buffer, "    ");
+		for (c = 0; c < conn_len; c++) {
+			snd_iprintf(buffer, " 0x%02x", conn[c]);
+			if (c == curr)
+				snd_iprintf(buffer, "*");
+		}
+		snd_iprintf(buffer, "\n");
+	}
+}
+
+static void print_gpio(struct snd_info_buffer *buffer,
+		       struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int gpio =
+		snd_hda_param_read(codec, codec->afg, AC_PAR_GPIO_CAP);
+	unsigned int enable, direction, wake, unsol, sticky, data;
+	int i, max;
+	snd_iprintf(buffer, "GPIO: io=%d, o=%d, i=%d, "
+		    "unsolicited=%d, wake=%d\n",
+		    gpio & AC_GPIO_IO_COUNT,
+		    (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT,
+		    (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT,
+		    (gpio & AC_GPIO_UNSOLICITED) ? 1 : 0,
+		    (gpio & AC_GPIO_WAKE) ? 1 : 0);
+	max = gpio & AC_GPIO_IO_COUNT;
+	if (!max || max > 8)
+		return;
+	enable = snd_hda_codec_read(codec, nid, 0,
+				    AC_VERB_GET_GPIO_MASK, 0);
+	direction = snd_hda_codec_read(codec, nid, 0,
+				       AC_VERB_GET_GPIO_DIRECTION, 0);
+	wake = snd_hda_codec_read(codec, nid, 0,
+				  AC_VERB_GET_GPIO_WAKE_MASK, 0);
+	unsol  = snd_hda_codec_read(codec, nid, 0,
+				    AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK, 0);
+	sticky = snd_hda_codec_read(codec, nid, 0,
+				    AC_VERB_GET_GPIO_STICKY_MASK, 0);
+	data = snd_hda_codec_read(codec, nid, 0,
+				  AC_VERB_GET_GPIO_DATA, 0);
+	for (i = 0; i < max; ++i)
+		snd_iprintf(buffer,
+			    "  IO[%d]: enable=%d, dir=%d, wake=%d, "
+			    "sticky=%d, data=%d, unsol=%d\n", i,
+			    (enable & (1<<i)) ? 1 : 0,
+			    (direction & (1<<i)) ? 1 : 0,
+			    (wake & (1<<i)) ? 1 : 0,
+			    (sticky & (1<<i)) ? 1 : 0,
+			    (data & (1<<i)) ? 1 : 0,
+			    (unsol & (1<<i)) ? 1 : 0);
+	/* FIXME: add GPO and GPI pin information */
+	print_nid_array(buffer, codec, nid, &codec->mixers);
+	print_nid_array(buffer, codec, nid, &codec->nids);
+}
+
+static void print_codec_info(struct snd_info_entry *entry,
+			     struct snd_info_buffer *buffer)
+{
+	struct hda_codec *codec = entry->private_data;
+	hda_nid_t nid;
+	int i, nodes;
+
+	snd_iprintf(buffer, "Codec: ");
+	if (codec->vendor_name && codec->chip_name)
+		snd_iprintf(buffer, "%s %s\n",
+			    codec->vendor_name, codec->chip_name);
+	else
+		snd_iprintf(buffer, "Not Set\n");
+	snd_iprintf(buffer, "Address: %d\n", codec->addr);
+	if (codec->afg)
+		snd_iprintf(buffer, "AFG Function Id: 0x%x (unsol %u)\n",
+			codec->afg_function_id, codec->afg_unsol);
+	if (codec->mfg)
+		snd_iprintf(buffer, "MFG Function Id: 0x%x (unsol %u)\n",
+			codec->mfg_function_id, codec->mfg_unsol);
+	snd_iprintf(buffer, "Vendor Id: 0x%08x\n", codec->vendor_id);
+	snd_iprintf(buffer, "Subsystem Id: 0x%08x\n", codec->subsystem_id);
+	snd_iprintf(buffer, "Revision Id: 0x%x\n", codec->revision_id);
+
+	if (codec->mfg)
+		snd_iprintf(buffer, "Modem Function Group: 0x%x\n", codec->mfg);
+	else
+		snd_iprintf(buffer, "No Modem Function Group found\n");
+
+	if (! codec->afg)
+		return;
+	snd_hda_power_up(codec);
+	snd_iprintf(buffer, "Default PCM:\n");
+	print_pcm_caps(buffer, codec, codec->afg);
+	snd_iprintf(buffer, "Default Amp-In caps: ");
+	print_amp_caps(buffer, codec, codec->afg, HDA_INPUT);
+	snd_iprintf(buffer, "Default Amp-Out caps: ");
+	print_amp_caps(buffer, codec, codec->afg, HDA_OUTPUT);
+
+	nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid);
+	if (! nid || nodes < 0) {
+		snd_iprintf(buffer, "Invalid AFG subtree\n");
+		snd_hda_power_down(codec);
+		return;
+	}
+
+	print_gpio(buffer, codec, codec->afg);
+	if (codec->proc_widget_hook)
+		codec->proc_widget_hook(buffer, codec, codec->afg);
+
+	for (i = 0; i < nodes; i++, nid++) {
+		unsigned int wid_caps =
+			snd_hda_param_read(codec, nid,
+					   AC_PAR_AUDIO_WIDGET_CAP);
+		unsigned int wid_type = get_wcaps_type(wid_caps);
+		hda_nid_t conn[HDA_MAX_CONNECTIONS];
+		int conn_len = 0;
+
+		snd_iprintf(buffer, "Node 0x%02x [%s] wcaps 0x%x:", nid,
+			    get_wid_type_name(wid_type), wid_caps);
+		if (wid_caps & AC_WCAP_STEREO) {
+			unsigned int chans = get_wcaps_channels(wid_caps);
+			if (chans == 2)
+				snd_iprintf(buffer, " Stereo");
+			else
+				snd_iprintf(buffer, " %d-Channels", chans);
+		} else
+			snd_iprintf(buffer, " Mono");
+		if (wid_caps & AC_WCAP_DIGITAL)
+			snd_iprintf(buffer, " Digital");
+		if (wid_caps & AC_WCAP_IN_AMP)
+			snd_iprintf(buffer, " Amp-In");
+		if (wid_caps & AC_WCAP_OUT_AMP)
+			snd_iprintf(buffer, " Amp-Out");
+		if (wid_caps & AC_WCAP_STRIPE)
+			snd_iprintf(buffer, " Stripe");
+		if (wid_caps & AC_WCAP_LR_SWAP)
+			snd_iprintf(buffer, " R/L");
+		if (wid_caps & AC_WCAP_CP_CAPS)
+			snd_iprintf(buffer, " CP");
+		snd_iprintf(buffer, "\n");
+
+		print_nid_array(buffer, codec, nid, &codec->mixers);
+		print_nid_array(buffer, codec, nid, &codec->nids);
+		print_nid_pcms(buffer, codec, nid);
+
+		/* volume knob is a special widget that always have connection
+		 * list
+		 */
+		if (wid_type == AC_WID_VOL_KNB)
+			wid_caps |= AC_WCAP_CONN_LIST;
+
+		if (wid_caps & AC_WCAP_CONN_LIST)
+			conn_len = snd_hda_get_connections(codec, nid, conn,
+							   HDA_MAX_CONNECTIONS);
+
+		if (wid_caps & AC_WCAP_IN_AMP) {
+			snd_iprintf(buffer, "  Amp-In caps: ");
+			print_amp_caps(buffer, codec, nid, HDA_INPUT);
+			snd_iprintf(buffer, "  Amp-In vals: ");
+			print_amp_vals(buffer, codec, nid, HDA_INPUT,
+				       wid_caps & AC_WCAP_STEREO,
+				       wid_type == AC_WID_PIN ? 1 : conn_len);
+		}
+		if (wid_caps & AC_WCAP_OUT_AMP) {
+			snd_iprintf(buffer, "  Amp-Out caps: ");
+			print_amp_caps(buffer, codec, nid, HDA_OUTPUT);
+			snd_iprintf(buffer, "  Amp-Out vals: ");
+			if (wid_type == AC_WID_PIN &&
+			    codec->pin_amp_workaround)
+				print_amp_vals(buffer, codec, nid, HDA_OUTPUT,
+					       wid_caps & AC_WCAP_STEREO,
+					       conn_len);
+			else
+				print_amp_vals(buffer, codec, nid, HDA_OUTPUT,
+					       wid_caps & AC_WCAP_STEREO, 1);
+		}
+
+		switch (wid_type) {
+		case AC_WID_PIN: {
+			int supports_vref;
+			print_pin_caps(buffer, codec, nid, &supports_vref);
+			print_pin_ctls(buffer, codec, nid, supports_vref);
+			break;
+		}
+		case AC_WID_VOL_KNB:
+			print_vol_knob(buffer, codec, nid);
+			break;
+		case AC_WID_AUD_OUT:
+		case AC_WID_AUD_IN:
+			print_audio_io(buffer, codec, nid, wid_type);
+			if (wid_caps & AC_WCAP_DIGITAL)
+				print_digital_conv(buffer, codec, nid);
+			if (wid_caps & AC_WCAP_FORMAT_OVRD) {
+				snd_iprintf(buffer, "  PCM:\n");
+				print_pcm_caps(buffer, codec, nid);
+			}
+			break;
+		}
+
+		if (wid_caps & AC_WCAP_UNSOL_CAP)
+			print_unsol_cap(buffer, codec, nid);
+
+		if (wid_caps & AC_WCAP_POWER)
+			print_power_state(buffer, codec, nid);
+
+		if (wid_caps & AC_WCAP_DELAY)
+			snd_iprintf(buffer, "  Delay: %d samples\n",
+				    (wid_caps & AC_WCAP_DELAY) >>
+				    AC_WCAP_DELAY_SHIFT);
+
+		if (wid_caps & AC_WCAP_CONN_LIST)
+			print_conn_list(buffer, codec, nid, wid_type,
+					conn, conn_len);
+
+		if (wid_caps & AC_WCAP_PROC_WID)
+			print_proc_caps(buffer, codec, nid);
+
+		if (codec->proc_widget_hook)
+			codec->proc_widget_hook(buffer, codec, nid);
+	}
+	snd_hda_power_down(codec);
+}
+
+/*
+ * create a proc read
+ */
+int snd_hda_codec_proc_new(struct hda_codec *codec)
+{
+	char name[32];
+	struct snd_info_entry *entry;
+	int err;
+
+	snprintf(name, sizeof(name), "codec#%d", codec->addr);
+	err = snd_card_proc_new(codec->bus->card, name, &entry);
+	if (err < 0)
+		return err;
+
+	snd_info_set_text_ops(entry, codec, print_codec_info);
+	return 0;
+}
+
diff --git a/linux/sound/pci/hda/patch_analog.c b/linux/sound/pci/hda/patch_analog.c
new file mode 100644
index 0000000..f7ff3f7
--- /dev/null
+++ b/linux/sound/pci/hda/patch_analog.c
@@ -0,0 +1,4820 @@
+/*
+ * HD audio interface patch for AD1882, AD1884, AD1981HD, AD1983, AD1984,
+ *   AD1986A, AD1988
+ *
+ * Copyright (c) 2005-2007 Takashi Iwai <tiwai@suse.de>
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+#include "hda_beep.h"
+
+struct ad198x_spec {
+	struct snd_kcontrol_new *mixers[5];
+	int num_mixers;
+	unsigned int beep_amp;	/* beep amp value, set via set_beep_amp() */
+	const struct hda_verb *init_verbs[5];	/* initialization verbs
+						 * don't forget NULL termination!
+						 */
+	unsigned int num_init_verbs;
+
+	/* playback */
+	struct hda_multi_out multiout;	/* playback set-up
+					 * max_channels, dacs must be set
+					 * dig_out_nid and hp_nid are optional
+					 */
+	unsigned int cur_eapd;
+	unsigned int need_dac_fix;
+
+	/* capture */
+	unsigned int num_adc_nids;
+	hda_nid_t *adc_nids;
+	hda_nid_t dig_in_nid;		/* digital-in NID; optional */
+
+	/* capture source */
+	const struct hda_input_mux *input_mux;
+	hda_nid_t *capsrc_nids;
+	unsigned int cur_mux[3];
+
+	/* channel model */
+	const struct hda_channel_mode *channel_mode;
+	int num_channel_mode;
+
+	/* PCM information */
+	struct hda_pcm pcm_rec[3];	/* used in alc_build_pcms() */
+
+	unsigned int spdif_route;
+
+	/* dynamic controls, init_verbs and input_mux */
+	struct auto_pin_cfg autocfg;
+	struct snd_array kctls;
+	struct hda_input_mux private_imux;
+	hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
+
+	unsigned int jack_present: 1;
+	unsigned int inv_jack_detect: 1;/* inverted jack-detection */
+	unsigned int inv_eapd: 1;	/* inverted EAPD implementation */
+	unsigned int analog_beep: 1;	/* analog beep input present */
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	struct hda_loopback_check loopback;
+#endif
+	/* for virtual master */
+	hda_nid_t vmaster_nid;
+	const char **slave_vols;
+	const char **slave_sws;
+};
+
+/*
+ * input MUX handling (common part)
+ */
+static int ad198x_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct ad198x_spec *spec = codec->spec;
+
+	return snd_hda_input_mux_info(spec->input_mux, uinfo);
+}
+
+static int ad198x_mux_enum_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct ad198x_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
+	return 0;
+}
+
+static int ad198x_mux_enum_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct ad198x_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
+				     spec->capsrc_nids[adc_idx],
+				     &spec->cur_mux[adc_idx]);
+}
+
+/*
+ * initialization (common callbacks)
+ */
+static int ad198x_init(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->num_init_verbs; i++)
+		snd_hda_sequence_write(codec, spec->init_verbs[i]);
+	return 0;
+}
+
+static const char *ad_slave_vols[] = {
+	"Front Playback Volume",
+	"Surround Playback Volume",
+	"Center Playback Volume",
+	"LFE Playback Volume",
+	"Side Playback Volume",
+	"Headphone Playback Volume",
+	"Mono Playback Volume",
+	"Speaker Playback Volume",
+	"IEC958 Playback Volume",
+	NULL
+};
+
+static const char *ad_slave_sws[] = {
+	"Front Playback Switch",
+	"Surround Playback Switch",
+	"Center Playback Switch",
+	"LFE Playback Switch",
+	"Side Playback Switch",
+	"Headphone Playback Switch",
+	"Mono Playback Switch",
+	"Speaker Playback Switch",
+	"IEC958 Playback Switch",
+	NULL
+};
+
+static void ad198x_free_kctls(struct hda_codec *codec);
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+/* additional beep mixers; the actual parameters are overwritten at build */
+static struct snd_kcontrol_new ad_beep_mixer[] = {
+	HDA_CODEC_VOLUME("Beep Playback Volume", 0, 0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_BEEP("Beep Playback Switch", 0, 0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad_beep2_mixer[] = {
+	HDA_CODEC_VOLUME("Digital Beep Playback Volume", 0, 0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_BEEP("Digital Beep Playback Switch", 0, 0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+#define set_beep_amp(spec, nid, idx, dir) \
+	((spec)->beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir)) /* mono */
+#else
+#define set_beep_amp(spec, nid, idx, dir) /* NOP */
+#endif
+
+static int ad198x_build_controls(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+	struct snd_kcontrol *kctl;
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < spec->num_mixers; i++) {
+		err = snd_hda_add_new_ctls(codec, spec->mixers[i]);
+		if (err < 0)
+			return err;
+	}
+	if (spec->multiout.dig_out_nid) {
+		err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid);
+		if (err < 0)
+			return err;
+		err = snd_hda_create_spdif_share_sw(codec,
+						    &spec->multiout);
+		if (err < 0)
+			return err;
+		spec->multiout.share_spdif = 1;
+	} 
+	if (spec->dig_in_nid) {
+		err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
+		if (err < 0)
+			return err;
+	}
+
+	/* create beep controls if needed */
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+	if (spec->beep_amp) {
+		struct snd_kcontrol_new *knew;
+		knew = spec->analog_beep ? ad_beep2_mixer : ad_beep_mixer;
+		for ( ; knew->name; knew++) {
+			struct snd_kcontrol *kctl;
+			kctl = snd_ctl_new1(knew, codec);
+			if (!kctl)
+				return -ENOMEM;
+			kctl->private_value = spec->beep_amp;
+			err = snd_hda_ctl_add(codec, 0, kctl);
+			if (err < 0)
+				return err;
+		}
+	}
+#endif
+
+	/* if we have no master control, let's create it */
+	if (!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
+		unsigned int vmaster_tlv[4];
+		snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid,
+					HDA_OUTPUT, vmaster_tlv);
+		err = snd_hda_add_vmaster(codec, "Master Playback Volume",
+					  vmaster_tlv,
+					  (spec->slave_vols ?
+					   spec->slave_vols : ad_slave_vols));
+		if (err < 0)
+			return err;
+	}
+	if (!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
+		err = snd_hda_add_vmaster(codec, "Master Playback Switch",
+					  NULL,
+					  (spec->slave_sws ?
+					   spec->slave_sws : ad_slave_sws));
+		if (err < 0)
+			return err;
+	}
+
+	ad198x_free_kctls(codec); /* no longer needed */
+
+	/* assign Capture Source enums to NID */
+	kctl = snd_hda_find_mixer_ctl(codec, "Capture Source");
+	if (!kctl)
+		kctl = snd_hda_find_mixer_ctl(codec, "Input Source");
+	for (i = 0; kctl && i < kctl->count; i++) {
+		err = snd_hda_add_nid(codec, kctl, i, spec->capsrc_nids[i]);
+		if (err < 0)
+			return err;
+	}
+
+	/* assign IEC958 enums to NID */
+	kctl = snd_hda_find_mixer_ctl(codec,
+			SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source");
+	if (kctl) {
+		err = snd_hda_add_nid(codec, kctl, 0,
+				      spec->multiout.dig_out_nid);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static int ad198x_check_power_status(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct ad198x_spec *spec = codec->spec;
+	return snd_hda_check_amp_list_power(codec, &spec->loopback, nid);
+}
+#endif
+
+/*
+ * Analog playback callbacks
+ */
+static int ad198x_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				    struct hda_codec *codec,
+				    struct snd_pcm_substream *substream)
+{
+	struct ad198x_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
+					     hinfo);
+}
+
+static int ad198x_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       unsigned int stream_tag,
+				       unsigned int format,
+				       struct snd_pcm_substream *substream)
+{
+	struct ad198x_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag,
+						format, substream);
+}
+
+static int ad198x_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       struct snd_pcm_substream *substream)
+{
+	struct ad198x_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Digital out
+ */
+static int ad198x_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+					struct hda_codec *codec,
+					struct snd_pcm_substream *substream)
+{
+	struct ad198x_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int ad198x_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+					 struct hda_codec *codec,
+					 struct snd_pcm_substream *substream)
+{
+	struct ad198x_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int ad198x_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					   struct hda_codec *codec,
+					   unsigned int stream_tag,
+					   unsigned int format,
+					   struct snd_pcm_substream *substream)
+{
+	struct ad198x_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag,
+					     format, substream);
+}
+
+static int ad198x_dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+					   struct hda_codec *codec,
+					   struct snd_pcm_substream *substream)
+{
+	struct ad198x_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Analog capture
+ */
+static int ad198x_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      unsigned int stream_tag,
+				      unsigned int format,
+				      struct snd_pcm_substream *substream)
+{
+	struct ad198x_spec *spec = codec->spec;
+	snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
+				   stream_tag, 0, format);
+	return 0;
+}
+
+static int ad198x_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      struct snd_pcm_substream *substream)
+{
+	struct ad198x_spec *spec = codec->spec;
+	snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]);
+	return 0;
+}
+
+
+/*
+ */
+static struct hda_pcm_stream ad198x_pcm_analog_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 6, /* changed later */
+	.nid = 0, /* fill later */
+	.ops = {
+		.open = ad198x_playback_pcm_open,
+		.prepare = ad198x_playback_pcm_prepare,
+		.cleanup = ad198x_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream ad198x_pcm_analog_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0, /* fill later */
+	.ops = {
+		.prepare = ad198x_capture_pcm_prepare,
+		.cleanup = ad198x_capture_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream ad198x_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0, /* fill later */
+	.ops = {
+		.open = ad198x_dig_playback_pcm_open,
+		.close = ad198x_dig_playback_pcm_close,
+		.prepare = ad198x_dig_playback_pcm_prepare,
+		.cleanup = ad198x_dig_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream ad198x_pcm_digital_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in alc_build_pcms */
+};
+
+static int ad198x_build_pcms(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+	struct hda_pcm *info = spec->pcm_rec;
+
+	codec->num_pcms = 1;
+	codec->pcm_info = info;
+
+	info->name = "AD198x Analog";
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ad198x_pcm_analog_playback;
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = spec->multiout.max_channels;
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dac_nids[0];
+	info->stream[SNDRV_PCM_STREAM_CAPTURE] = ad198x_pcm_analog_capture;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_adc_nids;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
+
+	if (spec->multiout.dig_out_nid) {
+		info++;
+		codec->num_pcms++;
+		info->name = "AD198x Digital";
+		info->pcm_type = HDA_PCM_TYPE_SPDIF;
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ad198x_pcm_digital_playback;
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dig_out_nid;
+		if (spec->dig_in_nid) {
+			info->stream[SNDRV_PCM_STREAM_CAPTURE] = ad198x_pcm_digital_capture;
+			info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid;
+		}
+	}
+
+	return 0;
+}
+
+static inline void ad198x_shutup(struct hda_codec *codec)
+{
+	snd_hda_shutup_pins(codec);
+}
+
+static void ad198x_free_kctls(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+
+	if (spec->kctls.list) {
+		struct snd_kcontrol_new *kctl = spec->kctls.list;
+		int i;
+		for (i = 0; i < spec->kctls.used; i++)
+			kfree(kctl[i].name);
+	}
+	snd_array_free(&spec->kctls);
+}
+
+static void ad198x_power_eapd_write(struct hda_codec *codec, hda_nid_t front,
+				hda_nid_t hp)
+{
+	struct ad198x_spec *spec = codec->spec;
+	snd_hda_codec_write(codec, front, 0, AC_VERB_SET_EAPD_BTLENABLE,
+			    !spec->inv_eapd ? 0x00 : 0x02);
+	snd_hda_codec_write(codec, hp, 0, AC_VERB_SET_EAPD_BTLENABLE,
+			    !spec->inv_eapd ? 0x00 : 0x02);
+}
+
+static void ad198x_power_eapd(struct hda_codec *codec)
+{
+	/* We currently only handle front, HP */
+	switch (codec->vendor_id) {
+	case 0x11d41882:
+	case 0x11d4882a:
+	case 0x11d41884:
+	case 0x11d41984:
+	case 0x11d41883:
+	case 0x11d4184a:
+	case 0x11d4194a:
+	case 0x11d4194b:
+		ad198x_power_eapd_write(codec, 0x12, 0x11);
+		break;
+	case 0x11d41981:
+	case 0x11d41983:
+		ad198x_power_eapd_write(codec, 0x05, 0x06);
+		break;
+	case 0x11d41986:
+		ad198x_power_eapd_write(codec, 0x1b, 0x1a);
+		break;
+	case 0x11d41988:
+	case 0x11d4198b:
+	case 0x11d4989a:
+	case 0x11d4989b:
+		ad198x_power_eapd_write(codec, 0x29, 0x22);
+		break;
+	}
+}
+
+static void ad198x_free(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+
+	if (!spec)
+		return;
+
+	ad198x_shutup(codec);
+	ad198x_free_kctls(codec);
+	kfree(spec);
+	snd_hda_detach_beep_device(codec);
+}
+
+#ifdef SND_HDA_NEEDS_RESUME
+static int ad198x_suspend(struct hda_codec *codec, pm_message_t state)
+{
+	ad198x_shutup(codec);
+	ad198x_power_eapd(codec);
+	return 0;
+}
+#endif
+
+static struct hda_codec_ops ad198x_patch_ops = {
+	.build_controls = ad198x_build_controls,
+	.build_pcms = ad198x_build_pcms,
+	.init = ad198x_init,
+	.free = ad198x_free,
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	.check_power_status = ad198x_check_power_status,
+#endif
+#ifdef SND_HDA_NEEDS_RESUME
+	.suspend = ad198x_suspend,
+#endif
+	.reboot_notify = ad198x_shutup,
+};
+
+
+/*
+ * EAPD control
+ * the private value = nid
+ */
+#define ad198x_eapd_info	snd_ctl_boolean_mono_info
+
+static int ad198x_eapd_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct ad198x_spec *spec = codec->spec;
+	if (spec->inv_eapd)
+		ucontrol->value.integer.value[0] = ! spec->cur_eapd;
+	else
+		ucontrol->value.integer.value[0] = spec->cur_eapd;
+	return 0;
+}
+
+static int ad198x_eapd_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct ad198x_spec *spec = codec->spec;
+	hda_nid_t nid = kcontrol->private_value & 0xff;
+	unsigned int eapd;
+	eapd = !!ucontrol->value.integer.value[0];
+	if (spec->inv_eapd)
+		eapd = !eapd;
+	if (eapd == spec->cur_eapd)
+		return 0;
+	spec->cur_eapd = eapd;
+	snd_hda_codec_write_cache(codec, nid,
+				  0, AC_VERB_SET_EAPD_BTLENABLE,
+				  eapd ? 0x02 : 0x00);
+	return 1;
+}
+
+static int ad198x_ch_mode_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo);
+static int ad198x_ch_mode_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol);
+static int ad198x_ch_mode_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol);
+
+
+/*
+ * AD1986A specific
+ */
+
+#define AD1986A_SPDIF_OUT	0x02
+#define AD1986A_FRONT_DAC	0x03
+#define AD1986A_SURR_DAC	0x04
+#define AD1986A_CLFE_DAC	0x05
+#define AD1986A_ADC		0x06
+
+static hda_nid_t ad1986a_dac_nids[3] = {
+	AD1986A_FRONT_DAC, AD1986A_SURR_DAC, AD1986A_CLFE_DAC
+};
+static hda_nid_t ad1986a_adc_nids[1] = { AD1986A_ADC };
+static hda_nid_t ad1986a_capsrc_nids[1] = { 0x12 };
+
+static struct hda_input_mux ad1986a_capture_source = {
+	.num_items = 7,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "CD", 0x1 },
+		{ "Aux", 0x3 },
+		{ "Line", 0x4 },
+		{ "Mix", 0x5 },
+		{ "Mono", 0x6 },
+		{ "Phone", 0x7 },
+	},
+};
+
+
+static struct hda_bind_ctls ad1986a_bind_pcm_vol = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(AD1986A_SURR_DAC, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(AD1986A_CLFE_DAC, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+static struct hda_bind_ctls ad1986a_bind_pcm_sw = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(AD1986A_SURR_DAC, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(AD1986A_CLFE_DAC, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+/*
+ * mixers
+ */
+static struct snd_kcontrol_new ad1986a_mixers[] = {
+	/*
+	 * bind volumes/mutes of 3 DACs as a single PCM control for simplicity
+	 */
+	HDA_BIND_VOL("PCM Playback Volume", &ad1986a_bind_pcm_vol),
+	HDA_BIND_SW("PCM Playback Switch", &ad1986a_bind_pcm_sw),
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x1c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Surround Playback Switch", 0x1c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x1d, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x1d, 2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x1d, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x1d, 2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x1a, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x17, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x17, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Aux Playback Volume", 0x16, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Aux Playback Switch", 0x16, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mono Playback Volume", 0x1e, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Mono Playback Switch", 0x1e, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x12, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	HDA_CODEC_MUTE("Stereo Downmix Switch", 0x09, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+/* additional mixers for 3stack mode */
+static struct snd_kcontrol_new ad1986a_3st_mixers[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = ad198x_ch_mode_info,
+		.get = ad198x_ch_mode_get,
+		.put = ad198x_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+/* laptop model - 2ch only */
+static hda_nid_t ad1986a_laptop_dac_nids[1] = { AD1986A_FRONT_DAC };
+
+/* master controls both pins 0x1a and 0x1b */
+static struct hda_bind_ctls ad1986a_laptop_master_vol = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x1a, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_OUTPUT),
+		0,
+	},
+};
+
+static struct hda_bind_ctls ad1986a_laptop_master_sw = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x1a, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_OUTPUT),
+		0,
+	},
+};
+
+static struct snd_kcontrol_new ad1986a_laptop_mixers[] = {
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x03, 0x0, HDA_OUTPUT),
+	HDA_BIND_VOL("Master Playback Volume", &ad1986a_laptop_master_vol),
+	HDA_BIND_SW("Master Playback Switch", &ad1986a_laptop_master_sw),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x17, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x17, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Aux Playback Volume", 0x16, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Aux Playback Switch", 0x16, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x0f, 0x0, HDA_OUTPUT),
+	/* 
+	   HDA_CODEC_VOLUME("Mono Playback Volume", 0x1e, 0x0, HDA_OUTPUT),
+	   HDA_CODEC_MUTE("Mono Playback Switch", 0x1e, 0x0, HDA_OUTPUT), */
+	HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x12, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+/* laptop-eapd model - 2ch only */
+
+static struct hda_input_mux ad1986a_laptop_eapd_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Internal Mic", 0x4 },
+		{ "Mix", 0x5 },
+	},
+};
+
+static struct hda_input_mux ad1986a_automic_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Mix", 0x5 },
+	},
+};
+
+static struct snd_kcontrol_new ad1986a_laptop_master_mixers[] = {
+	HDA_BIND_VOL("Master Playback Volume", &ad1986a_laptop_master_vol),
+	HDA_BIND_SW("Master Playback Switch", &ad1986a_laptop_master_sw),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1986a_laptop_eapd_mixers[] = {
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x12, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "External Amplifier",
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x1b,
+		.info = ad198x_eapd_info,
+		.get = ad198x_eapd_get,
+		.put = ad198x_eapd_put,
+		.private_value = 0x1b, /* port-D */
+	},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1986a_laptop_intmic_mixers[] = {
+	HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x17, 0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x17, 0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+/* re-connect the mic boost input according to the jack sensing */
+static void ad1986a_automic(struct hda_codec *codec)
+{
+	unsigned int present;
+	present = snd_hda_jack_detect(codec, 0x1f);
+	/* 0 = 0x1f, 2 = 0x1d, 4 = mixed */
+	snd_hda_codec_write(codec, 0x0f, 0, AC_VERB_SET_CONNECT_SEL,
+			    present ? 0 : 2);
+}
+
+#define AD1986A_MIC_EVENT		0x36
+
+static void ad1986a_automic_unsol_event(struct hda_codec *codec,
+					    unsigned int res)
+{
+	if ((res >> 26) != AD1986A_MIC_EVENT)
+		return;
+	ad1986a_automic(codec);
+}
+
+static int ad1986a_automic_init(struct hda_codec *codec)
+{
+	ad198x_init(codec);
+	ad1986a_automic(codec);
+	return 0;
+}
+
+/* laptop-automute - 2ch only */
+
+static void ad1986a_update_hp(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+	unsigned int mute;
+
+	if (spec->jack_present)
+		mute = HDA_AMP_MUTE; /* mute internal speaker */
+	else
+		/* unmute internal speaker if necessary */
+		mute = snd_hda_codec_amp_read(codec, 0x1a, 0, HDA_OUTPUT, 0);
+	snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, mute);
+}
+
+static void ad1986a_hp_automute(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+
+	spec->jack_present = snd_hda_jack_detect(codec, 0x1a);
+	if (spec->inv_jack_detect)
+		spec->jack_present = !spec->jack_present;
+	ad1986a_update_hp(codec);
+}
+
+#define AD1986A_HP_EVENT		0x37
+
+static void ad1986a_hp_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	if ((res >> 26) != AD1986A_HP_EVENT)
+		return;
+	ad1986a_hp_automute(codec);
+}
+
+static int ad1986a_hp_init(struct hda_codec *codec)
+{
+	ad198x_init(codec);
+	ad1986a_hp_automute(codec);
+	return 0;
+}
+
+/* bind hp and internal speaker mute (with plug check) */
+static int ad1986a_hp_master_sw_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	long *valp = ucontrol->value.integer.value;
+	int change;
+
+	change = snd_hda_codec_amp_update(codec, 0x1a, 0, HDA_OUTPUT, 0,
+					  HDA_AMP_MUTE,
+					  valp[0] ? 0 : HDA_AMP_MUTE);
+	change |= snd_hda_codec_amp_update(codec, 0x1a, 1, HDA_OUTPUT, 0,
+					   HDA_AMP_MUTE,
+					   valp[1] ? 0 : HDA_AMP_MUTE);
+	if (change)
+		ad1986a_update_hp(codec);
+	return change;
+}
+
+static struct snd_kcontrol_new ad1986a_automute_master_mixers[] = {
+	HDA_BIND_VOL("Master Playback Volume", &ad1986a_laptop_master_vol),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.info = snd_hda_mixer_amp_switch_info,
+		.get = snd_hda_mixer_amp_switch_get,
+		.put = ad1986a_hp_master_sw_put,
+		.private_value = HDA_COMPOSE_AMP_VAL(0x1a, 3, 0, HDA_OUTPUT),
+	},
+	{ } /* end */
+};
+
+
+/*
+ * initialization verbs
+ */
+static struct hda_verb ad1986a_init_verbs[] = {
+	/* Front, Surround, CLFE DAC; mute as default */
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	/* Downmix - off */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	/* HP, Line-Out, Surround, CLFE selectors */
+	{0x0a, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x0b, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x0c, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Mono selector */
+	{0x0e, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Mic selector: Mic 1/2 pin */
+	{0x0f, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Line-in selector: Line-in */
+	{0x10, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Mic 1/2 swap */
+	{0x11, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Record selector: mic */
+	{0x12, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Mic, Phone, CD, Aux, Line-In amp; mute as default */
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	/* PC beep */
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* HP, Line-Out, Surround, CLFE, Mono pins; mute as default */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	/* HP Pin */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+	/* Front, Surround, CLFE Pins */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	/* Mono Pin */
+	{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	/* Mic Pin */
+	{0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* Line, Aux, CD, Beep-In Pin */
+	{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	{0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	{0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	{ } /* end */
+};
+
+static struct hda_verb ad1986a_ch2_init[] = {
+	/* Surround out -> Line In */
+	{ 0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+ 	/* Line-in selectors */
+	{ 0x10, AC_VERB_SET_CONNECT_SEL, 0x1 },
+	/* CLFE -> Mic in */
+	{ 0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	/* Mic selector, mix C/LFE (backmic) and Mic (frontmic) */
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x4 },
+	{ } /* end */
+};
+
+static struct hda_verb ad1986a_ch4_init[] = {
+	/* Surround out -> Surround */
+	{ 0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x10, AC_VERB_SET_CONNECT_SEL, 0x0 },
+	/* CLFE -> Mic in */
+	{ 0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x4 },
+	{ } /* end */
+};
+
+static struct hda_verb ad1986a_ch6_init[] = {
+	/* Surround out -> Surround out */
+	{ 0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x10, AC_VERB_SET_CONNECT_SEL, 0x0 },
+	/* CLFE -> CLFE */
+	{ 0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x0 },
+	{ } /* end */
+};
+
+static struct hda_channel_mode ad1986a_modes[3] = {
+	{ 2, ad1986a_ch2_init },
+	{ 4, ad1986a_ch4_init },
+	{ 6, ad1986a_ch6_init },
+};
+
+/* eapd initialization */
+static struct hda_verb ad1986a_eapd_init_verbs[] = {
+	{0x1b, AC_VERB_SET_EAPD_BTLENABLE, 0x00 },
+	{}
+};
+
+static struct hda_verb ad1986a_automic_verbs[] = {
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	/*{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},*/
+	{0x0f, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x1f, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1986A_MIC_EVENT},
+	{}
+};
+
+/* Ultra initialization */
+static struct hda_verb ad1986a_ultra_init[] = {
+	/* eapd initialization */
+	{ 0x1b, AC_VERB_SET_EAPD_BTLENABLE, 0x00 },
+	/* CLFE -> Mic in */
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x2 },
+	{ 0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	{ 0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 },
+	{ } /* end */
+};
+
+/* pin sensing on HP jack */
+static struct hda_verb ad1986a_hp_init_verbs[] = {
+	{0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1986A_HP_EVENT},
+	{}
+};
+
+static void ad1986a_samsung_p50_unsol_event(struct hda_codec *codec,
+					    unsigned int res)
+{
+	switch (res >> 26) {
+	case AD1986A_HP_EVENT:
+		ad1986a_hp_automute(codec);
+		break;
+	case AD1986A_MIC_EVENT:
+		ad1986a_automic(codec);
+		break;
+	}
+}
+
+static int ad1986a_samsung_p50_init(struct hda_codec *codec)
+{
+	ad198x_init(codec);
+	ad1986a_hp_automute(codec);
+	ad1986a_automic(codec);
+	return 0;
+}
+
+
+/* models */
+enum {
+	AD1986A_6STACK,
+	AD1986A_3STACK,
+	AD1986A_LAPTOP,
+	AD1986A_LAPTOP_EAPD,
+	AD1986A_LAPTOP_AUTOMUTE,
+	AD1986A_ULTRA,
+	AD1986A_SAMSUNG,
+	AD1986A_SAMSUNG_P50,
+	AD1986A_MODELS
+};
+
+static const char *ad1986a_models[AD1986A_MODELS] = {
+	[AD1986A_6STACK]	= "6stack",
+	[AD1986A_3STACK]	= "3stack",
+	[AD1986A_LAPTOP]	= "laptop",
+	[AD1986A_LAPTOP_EAPD]	= "laptop-eapd",
+	[AD1986A_LAPTOP_AUTOMUTE] = "laptop-automute",
+	[AD1986A_ULTRA]		= "ultra",
+	[AD1986A_SAMSUNG]	= "samsung",
+	[AD1986A_SAMSUNG_P50]	= "samsung-p50",
+};
+
+static struct snd_pci_quirk ad1986a_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x103c, 0x30af, "HP B2800", AD1986A_LAPTOP_EAPD),
+	SND_PCI_QUIRK(0x1043, 0x1153, "ASUS M9", AD1986A_LAPTOP_EAPD),
+	SND_PCI_QUIRK(0x1043, 0x11f7, "ASUS U5A", AD1986A_LAPTOP_EAPD),
+	SND_PCI_QUIRK(0x1043, 0x1213, "ASUS A6J", AD1986A_LAPTOP_EAPD),
+	SND_PCI_QUIRK(0x1043, 0x1263, "ASUS U5F", AD1986A_LAPTOP_EAPD),
+	SND_PCI_QUIRK(0x1043, 0x1297, "ASUS Z62F", AD1986A_LAPTOP_EAPD),
+	SND_PCI_QUIRK(0x1043, 0x12b3, "ASUS V1j", AD1986A_LAPTOP_EAPD),
+	SND_PCI_QUIRK(0x1043, 0x1302, "ASUS W3j", AD1986A_LAPTOP_EAPD),
+	SND_PCI_QUIRK(0x1043, 0x1443, "ASUS VX1", AD1986A_LAPTOP),
+	SND_PCI_QUIRK(0x1043, 0x1447, "ASUS A8J", AD1986A_3STACK),
+	SND_PCI_QUIRK(0x1043, 0x817f, "ASUS P5", AD1986A_3STACK),
+	SND_PCI_QUIRK(0x1043, 0x818f, "ASUS P5", AD1986A_LAPTOP),
+	SND_PCI_QUIRK(0x1043, 0x81b3, "ASUS P5", AD1986A_3STACK),
+	SND_PCI_QUIRK(0x1043, 0x81cb, "ASUS M2N", AD1986A_3STACK),
+	SND_PCI_QUIRK(0x1043, 0x8234, "ASUS M2N", AD1986A_3STACK),
+	SND_PCI_QUIRK(0x10de, 0xcb84, "ASUS A8N-VM", AD1986A_3STACK),
+	SND_PCI_QUIRK(0x1179, 0xff40, "Toshiba Satellite L40-10Q", AD1986A_3STACK),
+	SND_PCI_QUIRK(0x144d, 0xb03c, "Samsung R55", AD1986A_3STACK),
+	SND_PCI_QUIRK(0x144d, 0xc01e, "FSC V2060", AD1986A_LAPTOP),
+	SND_PCI_QUIRK(0x144d, 0xc024, "Samsung P50", AD1986A_SAMSUNG_P50),
+	SND_PCI_QUIRK(0x144d, 0xc027, "Samsung Q1", AD1986A_ULTRA),
+	SND_PCI_QUIRK_MASK(0x144d, 0xff00, 0xc000, "Samsung", AD1986A_SAMSUNG),
+	SND_PCI_QUIRK(0x144d, 0xc504, "Samsung Q35", AD1986A_3STACK),
+	SND_PCI_QUIRK(0x17aa, 0x1011, "Lenovo M55", AD1986A_LAPTOP),
+	SND_PCI_QUIRK(0x17aa, 0x1017, "Lenovo A60", AD1986A_3STACK),
+	SND_PCI_QUIRK(0x17aa, 0x2066, "Lenovo N100", AD1986A_LAPTOP_AUTOMUTE),
+	SND_PCI_QUIRK(0x17c0, 0x2017, "Samsung M50", AD1986A_LAPTOP),
+	{}
+};
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list ad1986a_loopbacks[] = {
+	{ 0x13, HDA_OUTPUT, 0 }, /* Mic */
+	{ 0x14, HDA_OUTPUT, 0 }, /* Phone */
+	{ 0x15, HDA_OUTPUT, 0 }, /* CD */
+	{ 0x16, HDA_OUTPUT, 0 }, /* Aux */
+	{ 0x17, HDA_OUTPUT, 0 }, /* Line */
+	{ } /* end */
+};
+#endif
+
+static int is_jack_available(struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int conf = snd_hda_codec_get_pincfg(codec, nid);
+	return get_defcfg_connect(conf) != AC_JACK_PORT_NONE;
+}
+
+static int patch_ad1986a(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec;
+	int err, board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	err = snd_hda_attach_beep_device(codec, 0x19);
+	if (err < 0) {
+		ad198x_free(codec);
+		return err;
+	}
+	set_beep_amp(spec, 0x18, 0, HDA_OUTPUT);
+
+	spec->multiout.max_channels = 6;
+	spec->multiout.num_dacs = ARRAY_SIZE(ad1986a_dac_nids);
+	spec->multiout.dac_nids = ad1986a_dac_nids;
+	spec->multiout.dig_out_nid = AD1986A_SPDIF_OUT;
+	spec->num_adc_nids = 1;
+	spec->adc_nids = ad1986a_adc_nids;
+	spec->capsrc_nids = ad1986a_capsrc_nids;
+	spec->input_mux = &ad1986a_capture_source;
+	spec->num_mixers = 1;
+	spec->mixers[0] = ad1986a_mixers;
+	spec->num_init_verbs = 1;
+	spec->init_verbs[0] = ad1986a_init_verbs;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = ad1986a_loopbacks;
+#endif
+	spec->vmaster_nid = 0x1b;
+	spec->inv_eapd = 1; /* AD1986A has the inverted EAPD implementation */
+
+	codec->patch_ops = ad198x_patch_ops;
+
+	/* override some parameters */
+	board_config = snd_hda_check_board_config(codec, AD1986A_MODELS,
+						  ad1986a_models,
+						  ad1986a_cfg_tbl);
+	switch (board_config) {
+	case AD1986A_3STACK:
+		spec->num_mixers = 2;
+		spec->mixers[1] = ad1986a_3st_mixers;
+		spec->num_init_verbs = 2;
+		spec->init_verbs[1] = ad1986a_ch2_init;
+		spec->channel_mode = ad1986a_modes;
+		spec->num_channel_mode = ARRAY_SIZE(ad1986a_modes);
+		spec->need_dac_fix = 1;
+		spec->multiout.max_channels = 2;
+		spec->multiout.num_dacs = 1;
+		break;
+	case AD1986A_LAPTOP:
+		spec->mixers[0] = ad1986a_laptop_mixers;
+		spec->multiout.max_channels = 2;
+		spec->multiout.num_dacs = 1;
+		spec->multiout.dac_nids = ad1986a_laptop_dac_nids;
+		break;
+	case AD1986A_LAPTOP_EAPD:
+		spec->num_mixers = 3;
+		spec->mixers[0] = ad1986a_laptop_master_mixers;
+		spec->mixers[1] = ad1986a_laptop_eapd_mixers;
+		spec->mixers[2] = ad1986a_laptop_intmic_mixers;
+		spec->num_init_verbs = 2;
+		spec->init_verbs[1] = ad1986a_eapd_init_verbs;
+		spec->multiout.max_channels = 2;
+		spec->multiout.num_dacs = 1;
+		spec->multiout.dac_nids = ad1986a_laptop_dac_nids;
+		if (!is_jack_available(codec, 0x25))
+			spec->multiout.dig_out_nid = 0;
+		spec->input_mux = &ad1986a_laptop_eapd_capture_source;
+		break;
+	case AD1986A_SAMSUNG:
+		spec->num_mixers = 2;
+		spec->mixers[0] = ad1986a_laptop_master_mixers;
+		spec->mixers[1] = ad1986a_laptop_eapd_mixers;
+		spec->num_init_verbs = 3;
+		spec->init_verbs[1] = ad1986a_eapd_init_verbs;
+		spec->init_verbs[2] = ad1986a_automic_verbs;
+		spec->multiout.max_channels = 2;
+		spec->multiout.num_dacs = 1;
+		spec->multiout.dac_nids = ad1986a_laptop_dac_nids;
+		if (!is_jack_available(codec, 0x25))
+			spec->multiout.dig_out_nid = 0;
+		spec->input_mux = &ad1986a_automic_capture_source;
+		codec->patch_ops.unsol_event = ad1986a_automic_unsol_event;
+		codec->patch_ops.init = ad1986a_automic_init;
+		break;
+	case AD1986A_SAMSUNG_P50:
+		spec->num_mixers = 2;
+		spec->mixers[0] = ad1986a_automute_master_mixers;
+		spec->mixers[1] = ad1986a_laptop_eapd_mixers;
+		spec->num_init_verbs = 4;
+		spec->init_verbs[1] = ad1986a_eapd_init_verbs;
+		spec->init_verbs[2] = ad1986a_automic_verbs;
+		spec->init_verbs[3] = ad1986a_hp_init_verbs;
+		spec->multiout.max_channels = 2;
+		spec->multiout.num_dacs = 1;
+		spec->multiout.dac_nids = ad1986a_laptop_dac_nids;
+		if (!is_jack_available(codec, 0x25))
+			spec->multiout.dig_out_nid = 0;
+		spec->input_mux = &ad1986a_automic_capture_source;
+		codec->patch_ops.unsol_event = ad1986a_samsung_p50_unsol_event;
+		codec->patch_ops.init = ad1986a_samsung_p50_init;
+		break;
+	case AD1986A_LAPTOP_AUTOMUTE:
+		spec->num_mixers = 3;
+		spec->mixers[0] = ad1986a_automute_master_mixers;
+		spec->mixers[1] = ad1986a_laptop_eapd_mixers;
+		spec->mixers[2] = ad1986a_laptop_intmic_mixers;
+		spec->num_init_verbs = 3;
+		spec->init_verbs[1] = ad1986a_eapd_init_verbs;
+		spec->init_verbs[2] = ad1986a_hp_init_verbs;
+		spec->multiout.max_channels = 2;
+		spec->multiout.num_dacs = 1;
+		spec->multiout.dac_nids = ad1986a_laptop_dac_nids;
+		if (!is_jack_available(codec, 0x25))
+			spec->multiout.dig_out_nid = 0;
+		spec->input_mux = &ad1986a_laptop_eapd_capture_source;
+		codec->patch_ops.unsol_event = ad1986a_hp_unsol_event;
+		codec->patch_ops.init = ad1986a_hp_init;
+		/* Lenovo N100 seems to report the reversed bit
+		 * for HP jack-sensing
+		 */
+		spec->inv_jack_detect = 1;
+		break;
+	case AD1986A_ULTRA:
+		spec->mixers[0] = ad1986a_laptop_eapd_mixers;
+		spec->num_init_verbs = 2;
+		spec->init_verbs[1] = ad1986a_ultra_init;
+		spec->multiout.max_channels = 2;
+		spec->multiout.num_dacs = 1;
+		spec->multiout.dac_nids = ad1986a_laptop_dac_nids;
+		spec->multiout.dig_out_nid = 0;
+		break;
+	}
+
+	/* AD1986A has a hardware problem that it can't share a stream
+	 * with multiple output pins.  The copy of front to surrounds
+	 * causes noisy or silent outputs at a certain timing, e.g.
+	 * changing the volume.
+	 * So, let's disable the shared stream.
+	 */
+	spec->multiout.no_share_stream = 1;
+
+	codec->no_trigger_sense = 1;
+	codec->no_sticky_stream = 1;
+
+	return 0;
+}
+
+/*
+ * AD1983 specific
+ */
+
+#define AD1983_SPDIF_OUT	0x02
+#define AD1983_DAC		0x03
+#define AD1983_ADC		0x04
+
+static hda_nid_t ad1983_dac_nids[1] = { AD1983_DAC };
+static hda_nid_t ad1983_adc_nids[1] = { AD1983_ADC };
+static hda_nid_t ad1983_capsrc_nids[1] = { 0x15 };
+
+static struct hda_input_mux ad1983_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Line", 0x1 },
+		{ "Mix", 0x2 },
+		{ "Mix Mono", 0x3 },
+	},
+};
+
+/*
+ * SPDIF playback route
+ */
+static int ad1983_spdif_route_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "PCM", "ADC" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int ad1983_spdif_route_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct ad198x_spec *spec = codec->spec;
+
+	ucontrol->value.enumerated.item[0] = spec->spdif_route;
+	return 0;
+}
+
+static int ad1983_spdif_route_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct ad198x_spec *spec = codec->spec;
+
+	if (ucontrol->value.enumerated.item[0] > 1)
+		return -EINVAL;
+	if (spec->spdif_route != ucontrol->value.enumerated.item[0]) {
+		spec->spdif_route = ucontrol->value.enumerated.item[0];
+		snd_hda_codec_write_cache(codec, spec->multiout.dig_out_nid, 0,
+					  AC_VERB_SET_CONNECT_SEL,
+					  spec->spdif_route);
+		return 1;
+	}
+	return 0;
+}
+
+static struct snd_kcontrol_new ad1983_mixers[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x05, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x05, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x06, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x06, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x07, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x07, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+		.info = ad1983_spdif_route_info,
+		.get = ad1983_spdif_route_get,
+		.put = ad1983_spdif_route_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb ad1983_init_verbs[] = {
+	/* Front, HP, Mono; mute as default */
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x06, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	/* Beep, PCM, Mic, Line-In: mute */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	/* Front, HP selectors; from Mix */
+	{0x05, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x06, AC_VERB_SET_CONNECT_SEL, 0x01},
+	/* Mono selector; from Mix */
+	{0x0b, AC_VERB_SET_CONNECT_SEL, 0x03},
+	/* Mic selector; Mic */
+	{0x0c, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Line-in selector: Line-in */
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Mic boost: 0dB */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+	/* Record selector: mic */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	/* SPDIF route: PCM */
+	{0x02, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Front Pin */
+	{0x05, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	/* HP Pin */
+	{0x06, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+	/* Mono Pin */
+	{0x07, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	/* Mic Pin */
+	{0x08, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* Line Pin */
+	{0x09, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	{ } /* end */
+};
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list ad1983_loopbacks[] = {
+	{ 0x12, HDA_OUTPUT, 0 }, /* Mic */
+	{ 0x13, HDA_OUTPUT, 0 }, /* Line */
+	{ } /* end */
+};
+#endif
+
+static int patch_ad1983(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	err = snd_hda_attach_beep_device(codec, 0x10);
+	if (err < 0) {
+		ad198x_free(codec);
+		return err;
+	}
+	set_beep_amp(spec, 0x10, 0, HDA_OUTPUT);
+
+	spec->multiout.max_channels = 2;
+	spec->multiout.num_dacs = ARRAY_SIZE(ad1983_dac_nids);
+	spec->multiout.dac_nids = ad1983_dac_nids;
+	spec->multiout.dig_out_nid = AD1983_SPDIF_OUT;
+	spec->num_adc_nids = 1;
+	spec->adc_nids = ad1983_adc_nids;
+	spec->capsrc_nids = ad1983_capsrc_nids;
+	spec->input_mux = &ad1983_capture_source;
+	spec->num_mixers = 1;
+	spec->mixers[0] = ad1983_mixers;
+	spec->num_init_verbs = 1;
+	spec->init_verbs[0] = ad1983_init_verbs;
+	spec->spdif_route = 0;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = ad1983_loopbacks;
+#endif
+	spec->vmaster_nid = 0x05;
+
+	codec->patch_ops = ad198x_patch_ops;
+
+	codec->no_trigger_sense = 1;
+	codec->no_sticky_stream = 1;
+
+	return 0;
+}
+
+
+/*
+ * AD1981 HD specific
+ */
+
+#define AD1981_SPDIF_OUT	0x02
+#define AD1981_DAC		0x03
+#define AD1981_ADC		0x04
+
+static hda_nid_t ad1981_dac_nids[1] = { AD1981_DAC };
+static hda_nid_t ad1981_adc_nids[1] = { AD1981_ADC };
+static hda_nid_t ad1981_capsrc_nids[1] = { 0x15 };
+
+/* 0x0c, 0x09, 0x0e, 0x0f, 0x19, 0x05, 0x18, 0x17 */
+static struct hda_input_mux ad1981_capture_source = {
+	.num_items = 7,
+	.items = {
+		{ "Front Mic", 0x0 },
+		{ "Line", 0x1 },
+		{ "Mix", 0x2 },
+		{ "Mix Mono", 0x3 },
+		{ "CD", 0x4 },
+		{ "Mic", 0x6 },
+		{ "Aux", 0x7 },
+	},
+};
+
+static struct snd_kcontrol_new ad1981_mixers[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x05, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x05, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x06, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x06, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x07, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x07, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Aux Playback Volume", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Aux Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x1c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x1c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x1d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x1d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x08, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	/* identical with AD1983 */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+		.info = ad1983_spdif_route_info,
+		.get = ad1983_spdif_route_get,
+		.put = ad1983_spdif_route_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb ad1981_init_verbs[] = {
+	/* Front, HP, Mono; mute as default */
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x06, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	/* Beep, PCM, Front Mic, Line, Rear Mic, Aux, CD-In: mute */
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	/* Front, HP selectors; from Mix */
+	{0x05, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x06, AC_VERB_SET_CONNECT_SEL, 0x01},
+	/* Mono selector; from Mix */
+	{0x0b, AC_VERB_SET_CONNECT_SEL, 0x03},
+	/* Mic Mixer; select Front Mic */
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+	{0x1f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	/* Mic boost: 0dB */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Record selector: Front mic */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	/* SPDIF route: PCM */
+	{0x02, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Front Pin */
+	{0x05, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	/* HP Pin */
+	{0x06, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+	/* Mono Pin */
+	{0x07, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	/* Front & Rear Mic Pins */
+	{0x08, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* Line Pin */
+	{0x09, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	/* Digital Beep */
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* Line-Out as Input: disabled */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+	{ } /* end */
+};
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list ad1981_loopbacks[] = {
+	{ 0x12, HDA_OUTPUT, 0 }, /* Front Mic */
+	{ 0x13, HDA_OUTPUT, 0 }, /* Line */
+	{ 0x1b, HDA_OUTPUT, 0 }, /* Aux */
+	{ 0x1c, HDA_OUTPUT, 0 }, /* Mic */
+	{ 0x1d, HDA_OUTPUT, 0 }, /* CD */
+	{ } /* end */
+};
+#endif
+
+/*
+ * Patch for HP nx6320
+ *
+ * nx6320 uses EAPD in the reverse way - EAPD-on means the internal
+ * speaker output enabled _and_ mute-LED off.
+ */
+
+#define AD1981_HP_EVENT		0x37
+#define AD1981_MIC_EVENT	0x38
+
+static struct hda_verb ad1981_hp_init_verbs[] = {
+	{0x05, AC_VERB_SET_EAPD_BTLENABLE, 0x00 }, /* default off */
+	/* pin sensing on HP and Mic jacks */
+	{0x06, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1981_HP_EVENT},
+	{0x08, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1981_MIC_EVENT},
+	{}
+};
+
+/* turn on/off EAPD (+ mute HP) as a master switch */
+static int ad1981_hp_master_sw_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct ad198x_spec *spec = codec->spec;
+
+	if (! ad198x_eapd_put(kcontrol, ucontrol))
+		return 0;
+	/* change speaker pin appropriately */
+	snd_hda_codec_write(codec, 0x05, 0,
+			    AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    spec->cur_eapd ? PIN_OUT : 0);
+	/* toggle HP mute appropriately */
+	snd_hda_codec_amp_stereo(codec, 0x06, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE,
+				 spec->cur_eapd ? 0 : HDA_AMP_MUTE);
+	return 1;
+}
+
+/* bind volumes of both NID 0x05 and 0x06 */
+static struct hda_bind_ctls ad1981_hp_bind_master_vol = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x05, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x06, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+/* mute internal speaker if HP is plugged */
+static void ad1981_hp_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+
+	present = snd_hda_jack_detect(codec, 0x06);
+	snd_hda_codec_amp_stereo(codec, 0x05, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+}
+
+/* toggle input of built-in and mic jack appropriately */
+static void ad1981_hp_automic(struct hda_codec *codec)
+{
+	static struct hda_verb mic_jack_on[] = {
+		{0x1f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+		{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+		{}
+	};
+	static struct hda_verb mic_jack_off[] = {
+		{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+		{0x1f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+		{}
+	};
+	unsigned int present;
+
+	present = snd_hda_jack_detect(codec, 0x08);
+	if (present)
+		snd_hda_sequence_write(codec, mic_jack_on);
+	else
+		snd_hda_sequence_write(codec, mic_jack_off);
+}
+
+/* unsolicited event for HP jack sensing */
+static void ad1981_hp_unsol_event(struct hda_codec *codec,
+				  unsigned int res)
+{
+	res >>= 26;
+	switch (res) {
+	case AD1981_HP_EVENT:
+		ad1981_hp_automute(codec);
+		break;
+	case AD1981_MIC_EVENT:
+		ad1981_hp_automic(codec);
+		break;
+	}
+}
+
+static struct hda_input_mux ad1981_hp_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Docking-Station", 0x1 },
+		{ "Mix", 0x2 },
+	},
+};
+
+static struct snd_kcontrol_new ad1981_hp_mixers[] = {
+	HDA_BIND_VOL("Master Playback Volume", &ad1981_hp_bind_master_vol),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x05,
+		.name = "Master Playback Switch",
+		.info = ad198x_eapd_info,
+		.get = ad198x_eapd_get,
+		.put = ad1981_hp_master_sw_put,
+		.private_value = 0x05,
+	},
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x11, 0x0, HDA_OUTPUT),
+#if 0
+	/* FIXME: analog mic/line loopback doesn't work with my tests...
+	 *        (although recording is OK)
+	 */
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Docking-Station Playback Volume", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Docking-Station Playback Switch", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x1c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x1c, 0x0, HDA_OUTPUT),
+	/* FIXME: does this laptop have analog CD connection? */
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x1d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x1d, 0x0, HDA_OUTPUT),
+#endif
+	HDA_CODEC_VOLUME("Mic Boost", 0x08, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Boost", 0x18, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+/* initialize jack-sensing, too */
+static int ad1981_hp_init(struct hda_codec *codec)
+{
+	ad198x_init(codec);
+	ad1981_hp_automute(codec);
+	ad1981_hp_automic(codec);
+	return 0;
+}
+
+/* configuration for Toshiba Laptops */
+static struct hda_verb ad1981_toshiba_init_verbs[] = {
+	{0x05, AC_VERB_SET_EAPD_BTLENABLE, 0x01 }, /* default on */
+	/* pin sensing on HP and Mic jacks */
+	{0x06, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1981_HP_EVENT},
+	{0x08, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1981_MIC_EVENT},
+	{}
+};
+
+static struct snd_kcontrol_new ad1981_toshiba_mixers[] = {
+	HDA_CODEC_VOLUME("Amp Volume", 0x1a, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Amp Switch", 0x1a, 0x0, HDA_OUTPUT),
+	{ }
+};
+
+/* configuration for Lenovo Thinkpad T60 */
+static struct snd_kcontrol_new ad1981_thinkpad_mixers[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x05, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Master Playback Switch", 0x05, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x1d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x1d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x08, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	/* identical with AD1983 */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+		.info = ad1983_spdif_route_info,
+		.get = ad1983_spdif_route_get,
+		.put = ad1983_spdif_route_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_input_mux ad1981_thinkpad_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Mix", 0x2 },
+		{ "CD", 0x4 },
+	},
+};
+
+/* models */
+enum {
+	AD1981_BASIC,
+	AD1981_HP,
+	AD1981_THINKPAD,
+	AD1981_TOSHIBA,
+	AD1981_MODELS
+};
+
+static const char *ad1981_models[AD1981_MODELS] = {
+	[AD1981_HP]		= "hp",
+	[AD1981_THINKPAD]	= "thinkpad",
+	[AD1981_BASIC]		= "basic",
+	[AD1981_TOSHIBA]	= "toshiba"
+};
+
+static struct snd_pci_quirk ad1981_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1014, 0x0597, "Lenovo Z60", AD1981_THINKPAD),
+	SND_PCI_QUIRK(0x1014, 0x05b7, "Lenovo Z60m", AD1981_THINKPAD),
+	/* All HP models */
+	SND_PCI_QUIRK_VENDOR(0x103c, "HP nx", AD1981_HP),
+	SND_PCI_QUIRK(0x1179, 0x0001, "Toshiba U205", AD1981_TOSHIBA),
+	/* Lenovo Thinkpad T60/X60/Z6xx */
+	SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo Thinkpad", AD1981_THINKPAD),
+	/* HP nx6320 (reversed SSID, H/W bug) */
+	SND_PCI_QUIRK(0x30b0, 0x103c, "HP nx6320", AD1981_HP),
+	{}
+};
+
+static int patch_ad1981(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec;
+	int err, board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	err = snd_hda_attach_beep_device(codec, 0x10);
+	if (err < 0) {
+		ad198x_free(codec);
+		return err;
+	}
+	set_beep_amp(spec, 0x0d, 0, HDA_OUTPUT);
+
+	spec->multiout.max_channels = 2;
+	spec->multiout.num_dacs = ARRAY_SIZE(ad1981_dac_nids);
+	spec->multiout.dac_nids = ad1981_dac_nids;
+	spec->multiout.dig_out_nid = AD1981_SPDIF_OUT;
+	spec->num_adc_nids = 1;
+	spec->adc_nids = ad1981_adc_nids;
+	spec->capsrc_nids = ad1981_capsrc_nids;
+	spec->input_mux = &ad1981_capture_source;
+	spec->num_mixers = 1;
+	spec->mixers[0] = ad1981_mixers;
+	spec->num_init_verbs = 1;
+	spec->init_verbs[0] = ad1981_init_verbs;
+	spec->spdif_route = 0;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = ad1981_loopbacks;
+#endif
+	spec->vmaster_nid = 0x05;
+
+	codec->patch_ops = ad198x_patch_ops;
+
+	/* override some parameters */
+	board_config = snd_hda_check_board_config(codec, AD1981_MODELS,
+						  ad1981_models,
+						  ad1981_cfg_tbl);
+	switch (board_config) {
+	case AD1981_HP:
+		spec->mixers[0] = ad1981_hp_mixers;
+		spec->num_init_verbs = 2;
+		spec->init_verbs[1] = ad1981_hp_init_verbs;
+		spec->multiout.dig_out_nid = 0;
+		spec->input_mux = &ad1981_hp_capture_source;
+
+		codec->patch_ops.init = ad1981_hp_init;
+		codec->patch_ops.unsol_event = ad1981_hp_unsol_event;
+		/* set the upper-limit for mixer amp to 0dB for avoiding the
+		 * possible damage by overloading
+		 */
+		snd_hda_override_amp_caps(codec, 0x11, HDA_INPUT,
+					  (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+					  (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+					  (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+					  (1 << AC_AMPCAP_MUTE_SHIFT));
+		break;
+	case AD1981_THINKPAD:
+		spec->mixers[0] = ad1981_thinkpad_mixers;
+		spec->input_mux = &ad1981_thinkpad_capture_source;
+		/* set the upper-limit for mixer amp to 0dB for avoiding the
+		 * possible damage by overloading
+		 */
+		snd_hda_override_amp_caps(codec, 0x11, HDA_INPUT,
+					  (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+					  (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+					  (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+					  (1 << AC_AMPCAP_MUTE_SHIFT));
+		break;
+	case AD1981_TOSHIBA:
+		spec->mixers[0] = ad1981_hp_mixers;
+		spec->mixers[1] = ad1981_toshiba_mixers;
+		spec->num_init_verbs = 2;
+		spec->init_verbs[1] = ad1981_toshiba_init_verbs;
+		spec->multiout.dig_out_nid = 0;
+		spec->input_mux = &ad1981_hp_capture_source;
+		codec->patch_ops.init = ad1981_hp_init;
+		codec->patch_ops.unsol_event = ad1981_hp_unsol_event;
+		break;
+	}
+
+	codec->no_trigger_sense = 1;
+	codec->no_sticky_stream = 1;
+
+	return 0;
+}
+
+
+/*
+ * AD1988
+ *
+ * Output pins and routes
+ *
+ *        Pin               Mix     Sel     DAC (*)
+ * port-A 0x11 (mute/hp) <- 0x22 <- 0x37 <- 03/04/06
+ * port-B 0x14 (mute/hp) <- 0x2b <- 0x30 <- 03/04/06
+ * port-C 0x15 (mute)    <- 0x2c <- 0x31 <- 05/0a
+ * port-D 0x12 (mute/hp) <- 0x29         <- 04
+ * port-E 0x17 (mute/hp) <- 0x26 <- 0x32 <- 05/0a
+ * port-F 0x16 (mute)    <- 0x2a         <- 06
+ * port-G 0x24 (mute)    <- 0x27         <- 05
+ * port-H 0x25 (mute)    <- 0x28         <- 0a
+ * mono   0x13 (mute/amp)<- 0x1e <- 0x36 <- 03/04/06
+ *
+ * DAC0 = 03h, DAC1 = 04h, DAC2 = 05h, DAC3 = 06h, DAC4 = 0ah
+ * (*) DAC2/3/4 are swapped to DAC3/4/2 on AD198A rev.2 due to a h/w bug.
+ *
+ * Input pins and routes
+ *
+ *        pin     boost   mix input # / adc input #
+ * port-A 0x11 -> 0x38 -> mix 2, ADC 0
+ * port-B 0x14 -> 0x39 -> mix 0, ADC 1
+ * port-C 0x15 -> 0x3a -> 33:0 - mix 1, ADC 2
+ * port-D 0x12 -> 0x3d -> mix 3, ADC 8
+ * port-E 0x17 -> 0x3c -> 34:0 - mix 4, ADC 4
+ * port-F 0x16 -> 0x3b -> mix 5, ADC 3
+ * port-G 0x24 -> N/A  -> 33:1 - mix 1, 34:1 - mix 4, ADC 6
+ * port-H 0x25 -> N/A  -> 33:2 - mix 1, 34:2 - mix 4, ADC 7
+ *
+ *
+ * DAC assignment
+ *   6stack - front/surr/CLFE/side/opt DACs - 04/06/05/0a/03
+ *   3stack - front/surr/CLFE/opt DACs - 04/05/0a/03
+ *
+ * Inputs of Analog Mix (0x20)
+ *   0:Port-B (front mic)
+ *   1:Port-C/G/H (line-in)
+ *   2:Port-A
+ *   3:Port-D (line-in/2)
+ *   4:Port-E/G/H (mic-in)
+ *   5:Port-F (mic2-in)
+ *   6:CD
+ *   7:Beep
+ *
+ * ADC selection
+ *   0:Port-A
+ *   1:Port-B (front mic-in)
+ *   2:Port-C (line-in)
+ *   3:Port-F (mic2-in)
+ *   4:Port-E (mic-in)
+ *   5:CD
+ *   6:Port-G
+ *   7:Port-H
+ *   8:Port-D (line-in/2)
+ *   9:Mix
+ *
+ * Proposed pin assignments by the datasheet
+ *
+ * 6-stack
+ * Port-A front headphone
+ *      B front mic-in
+ *      C rear line-in
+ *      D rear front-out
+ *      E rear mic-in
+ *      F rear surround
+ *      G rear CLFE
+ *      H rear side
+ *
+ * 3-stack
+ * Port-A front headphone
+ *      B front mic
+ *      C rear line-in/surround
+ *      D rear front-out
+ *      E rear mic-in/CLFE
+ *
+ * laptop
+ * Port-A headphone
+ *      B mic-in
+ *      C docking station
+ *      D internal speaker (with EAPD)
+ *      E/F quad mic array
+ */
+
+
+/* models */
+enum {
+	AD1988_6STACK,
+	AD1988_6STACK_DIG,
+	AD1988_3STACK,
+	AD1988_3STACK_DIG,
+	AD1988_LAPTOP,
+	AD1988_LAPTOP_DIG,
+	AD1988_AUTO,
+	AD1988_MODEL_LAST,
+};
+
+/* reivision id to check workarounds */
+#define AD1988A_REV2		0x100200
+
+#define is_rev2(codec) \
+	((codec)->vendor_id == 0x11d41988 && \
+	 (codec)->revision_id == AD1988A_REV2)
+
+/*
+ * mixers
+ */
+
+static hda_nid_t ad1988_6stack_dac_nids[4] = {
+	0x04, 0x06, 0x05, 0x0a
+};
+
+static hda_nid_t ad1988_3stack_dac_nids[3] = {
+	0x04, 0x05, 0x0a
+};
+
+/* for AD1988A revision-2, DAC2-4 are swapped */
+static hda_nid_t ad1988_6stack_dac_nids_rev2[4] = {
+	0x04, 0x05, 0x0a, 0x06
+};
+
+static hda_nid_t ad1988_3stack_dac_nids_rev2[3] = {
+	0x04, 0x0a, 0x06
+};
+
+static hda_nid_t ad1988_adc_nids[3] = {
+	0x08, 0x09, 0x0f
+};
+
+static hda_nid_t ad1988_capsrc_nids[3] = {
+	0x0c, 0x0d, 0x0e
+};
+
+#define AD1988_SPDIF_OUT		0x02
+#define AD1988_SPDIF_OUT_HDMI	0x0b
+#define AD1988_SPDIF_IN		0x07
+
+static hda_nid_t ad1989b_slave_dig_outs[] = {
+	AD1988_SPDIF_OUT, AD1988_SPDIF_OUT_HDMI, 0
+};
+
+static struct hda_input_mux ad1988_6stack_capture_source = {
+	.num_items = 5,
+	.items = {
+		{ "Front Mic", 0x1 },	/* port-B */
+		{ "Line", 0x2 },	/* port-C */
+		{ "Mic", 0x4 },		/* port-E */
+		{ "CD", 0x5 },
+		{ "Mix", 0x9 },
+	},
+};
+
+static struct hda_input_mux ad1988_laptop_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic/Line", 0x1 },	/* port-B */
+		{ "CD", 0x5 },
+		{ "Mix", 0x9 },
+	},
+};
+
+/*
+ */
+static int ad198x_ch_mode_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct ad198x_spec *spec = codec->spec;
+	return snd_hda_ch_mode_info(codec, uinfo, spec->channel_mode,
+				    spec->num_channel_mode);
+}
+
+static int ad198x_ch_mode_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct ad198x_spec *spec = codec->spec;
+	return snd_hda_ch_mode_get(codec, ucontrol, spec->channel_mode,
+				   spec->num_channel_mode, spec->multiout.max_channels);
+}
+
+static int ad198x_ch_mode_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct ad198x_spec *spec = codec->spec;
+	int err = snd_hda_ch_mode_put(codec, ucontrol, spec->channel_mode,
+				      spec->num_channel_mode,
+				      &spec->multiout.max_channels);
+	if (err >= 0 && spec->need_dac_fix)
+		spec->multiout.num_dacs = spec->multiout.max_channels / 2;
+	return err;
+}
+
+/* 6-stack mode */
+static struct snd_kcontrol_new ad1988_6stack_mixers1[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x04, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x06, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x05, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x05, 2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Side Playback Volume", 0x0a, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1988_6stack_mixers1_rev2[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x04, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x05, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0a, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0a, 2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Side Playback Volume", 0x06, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1988_6stack_mixers2[] = {
+	HDA_BIND_MUTE("Front Playback Switch", 0x29, 2, HDA_INPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x2a, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x27, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x27, 2, 2, HDA_INPUT),
+	HDA_BIND_MUTE("Side Playback Switch", 0x28, 2, HDA_INPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x22, 2, HDA_INPUT),
+	HDA_BIND_MUTE("Mono Playback Switch", 0x1e, 2, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x6, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x6, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x4, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x4, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Analog Mix Playback Volume", 0x21, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Analog Mix Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x39, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x3c, 0x0, HDA_OUTPUT),
+
+	{ } /* end */
+};
+
+/* 3-stack mode */
+static struct snd_kcontrol_new ad1988_3stack_mixers1[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x04, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0a, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x05, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x05, 2, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1988_3stack_mixers1_rev2[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x04, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0a, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x06, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x06, 2, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1988_3stack_mixers2[] = {
+	HDA_BIND_MUTE("Front Playback Switch", 0x29, 2, HDA_INPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x2c, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x26, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x26, 2, 2, HDA_INPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x22, 2, HDA_INPUT),
+	HDA_BIND_MUTE("Mono Playback Switch", 0x1e, 2, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x6, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x6, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x4, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x4, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Analog Mix Playback Volume", 0x21, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Analog Mix Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x39, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x3c, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = ad198x_ch_mode_info,
+		.get = ad198x_ch_mode_get,
+		.put = ad198x_ch_mode_put,
+	},
+
+	{ } /* end */
+};
+
+/* laptop mode */
+static struct snd_kcontrol_new ad1988_laptop_mixers[] = {
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x04, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x29, 0x0, HDA_INPUT),
+	HDA_BIND_MUTE("Mono Playback Switch", 0x1e, 2, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x6, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x6, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x1, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Analog Mix Playback Volume", 0x21, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Analog Mix Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_VOLUME("Mic Boost", 0x39, 0x0, HDA_OUTPUT),
+
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "External Amplifier",
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x12,
+		.info = ad198x_eapd_info,
+		.get = ad198x_eapd_get,
+		.put = ad198x_eapd_put,
+		.private_value = 0x12, /* port-D */
+	},
+
+	{ } /* end */
+};
+
+/* capture */
+static struct snd_kcontrol_new ad1988_capture_mixers[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 2, 0x0e, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 2, 0x0e, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 3,
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+static int ad1988_spdif_playback_source_info(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {
+		"PCM", "ADC1", "ADC2", "ADC3"
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item >= 4)
+		uinfo->value.enumerated.item = 3;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int ad1988_spdif_playback_source_get(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int sel;
+
+	sel = snd_hda_codec_read(codec, 0x1d, 0, AC_VERB_GET_AMP_GAIN_MUTE,
+				 AC_AMP_GET_INPUT);
+	if (!(sel & 0x80))
+		ucontrol->value.enumerated.item[0] = 0;
+	else {
+		sel = snd_hda_codec_read(codec, 0x0b, 0,
+					 AC_VERB_GET_CONNECT_SEL, 0);
+		if (sel < 3)
+			sel++;
+		else
+			sel = 0;
+		ucontrol->value.enumerated.item[0] = sel;
+	}
+	return 0;
+}
+
+static int ad1988_spdif_playback_source_put(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int val, sel;
+	int change;
+
+	val = ucontrol->value.enumerated.item[0];
+	if (val > 3)
+		return -EINVAL;
+	if (!val) {
+		sel = snd_hda_codec_read(codec, 0x1d, 0,
+					 AC_VERB_GET_AMP_GAIN_MUTE,
+					 AC_AMP_GET_INPUT);
+		change = sel & 0x80;
+		if (change) {
+			snd_hda_codec_write_cache(codec, 0x1d, 0,
+						  AC_VERB_SET_AMP_GAIN_MUTE,
+						  AMP_IN_UNMUTE(0));
+			snd_hda_codec_write_cache(codec, 0x1d, 0,
+						  AC_VERB_SET_AMP_GAIN_MUTE,
+						  AMP_IN_MUTE(1));
+		}
+	} else {
+		sel = snd_hda_codec_read(codec, 0x1d, 0,
+					 AC_VERB_GET_AMP_GAIN_MUTE,
+					 AC_AMP_GET_INPUT | 0x01);
+		change = sel & 0x80;
+		if (change) {
+			snd_hda_codec_write_cache(codec, 0x1d, 0,
+						  AC_VERB_SET_AMP_GAIN_MUTE,
+						  AMP_IN_MUTE(0));
+			snd_hda_codec_write_cache(codec, 0x1d, 0,
+						  AC_VERB_SET_AMP_GAIN_MUTE,
+						  AMP_IN_UNMUTE(1));
+		}
+		sel = snd_hda_codec_read(codec, 0x0b, 0,
+					 AC_VERB_GET_CONNECT_SEL, 0) + 1;
+		change |= sel != val;
+		if (change)
+			snd_hda_codec_write_cache(codec, 0x0b, 0,
+						  AC_VERB_SET_CONNECT_SEL,
+						  val - 1);
+	}
+	return change;
+}
+
+static struct snd_kcontrol_new ad1988_spdif_out_mixers[] = {
+	HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "IEC958 Playback Source",
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x1b,
+		.info = ad1988_spdif_playback_source_info,
+		.get = ad1988_spdif_playback_source_get,
+		.put = ad1988_spdif_playback_source_put,
+	},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1988_spdif_in_mixers[] = {
+	HDA_CODEC_VOLUME("IEC958 Capture Volume", 0x1c, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1989_spdif_out_mixers[] = {
+	HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("HDMI Playback Volume", 0x1d, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+/*
+ * initialization verbs
+ */
+
+/*
+ * for 6-stack (+dig)
+ */
+static struct hda_verb ad1988_6stack_init_verbs[] = {
+	/* Front, Surround, CLFE, side DAC; unmute as default */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Port-A front headphon path */
+	{0x37, AC_VERB_SET_CONNECT_SEL, 0x01}, /* DAC1:04h */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Port-D line-out path */
+	{0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* Port-F surround path */
+	{0x2a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x2a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* Port-G CLFE path */
+	{0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* Port-H side path */
+	{0x28, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x28, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x25, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x25, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* Mono out path */
+	{0x36, AC_VERB_SET_CONNECT_SEL, 0x1}, /* DAC1:04h */
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb01f}, /* unmute, 0dB */
+	/* Port-B front mic-in path */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x39, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* Port-C line-in path */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x3a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x33, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Port-E mic-in path */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x3c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x34, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Analog CD Input */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	/* Analog Mix output amp */
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */
+
+	{ }
+};
+
+static struct hda_verb ad1988_capture_init_verbs[] = {
+	/* mute analog mix */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)},
+	/* select ADCs - front-mic */
+	{0x0c, AC_VERB_SET_CONNECT_SEL, 0x1},
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0x1},
+	{0x0e, AC_VERB_SET_CONNECT_SEL, 0x1},
+
+	{ }
+};
+
+static struct hda_verb ad1988_spdif_init_verbs[] = {
+	/* SPDIF out sel */
+	{0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, /* PCM */
+	{0x0b, AC_VERB_SET_CONNECT_SEL, 0x0}, /* ADC1 */
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* SPDIF out pin */
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */
+
+	{ }
+};
+
+static struct hda_verb ad1988_spdif_in_init_verbs[] = {
+	/* unmute SPDIF input pin */
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{ }
+};
+
+/* AD1989 has no ADC -> SPDIF route */
+static struct hda_verb ad1989_spdif_init_verbs[] = {
+	/* SPDIF-1 out pin */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */
+	/* SPDIF-2/HDMI out pin */
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */
+	{ }
+};
+
+/*
+ * verbs for 3stack (+dig)
+ */
+static struct hda_verb ad1988_3stack_ch2_init[] = {
+	/* set port-C to line-in */
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	/* set port-E to mic-in */
+	{ 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ } /* end */
+};
+
+static struct hda_verb ad1988_3stack_ch6_init[] = {
+	/* set port-C to surround out */
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	/* set port-E to CLFE out */
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ } /* end */
+};
+
+static struct hda_channel_mode ad1988_3stack_modes[2] = {
+	{ 2, ad1988_3stack_ch2_init },
+	{ 6, ad1988_3stack_ch6_init },
+};
+
+static struct hda_verb ad1988_3stack_init_verbs[] = {
+	/* Front, Surround, CLFE, side DAC; unmute as default */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Port-A front headphon path */
+	{0x37, AC_VERB_SET_CONNECT_SEL, 0x01}, /* DAC1:04h */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Port-D line-out path */
+	{0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* Mono out path */
+	{0x36, AC_VERB_SET_CONNECT_SEL, 0x1}, /* DAC1:04h */
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb01f}, /* unmute, 0dB */
+	/* Port-B front mic-in path */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x39, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* Port-C line-in/surround path - 6ch mode as default */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x3a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x31, AC_VERB_SET_CONNECT_SEL, 0x0}, /* output sel: DAC 0x05 */
+	{0x33, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* Port-E mic-in/CLFE path - 6ch mode as default */
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x3c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x32, AC_VERB_SET_CONNECT_SEL, 0x1}, /* output sel: DAC 0x0a */
+	{0x34, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* mute analog mix */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)},
+	/* select ADCs - front-mic */
+	{0x0c, AC_VERB_SET_CONNECT_SEL, 0x1},
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0x1},
+	{0x0e, AC_VERB_SET_CONNECT_SEL, 0x1},
+	/* Analog Mix output amp */
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */
+	{ }
+};
+
+/*
+ * verbs for laptop mode (+dig)
+ */
+static struct hda_verb ad1988_laptop_hp_on[] = {
+	/* unmute port-A and mute port-D */
+	{ 0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ } /* end */
+};
+static struct hda_verb ad1988_laptop_hp_off[] = {
+	/* mute port-A and unmute port-D */
+	{ 0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ } /* end */
+};
+
+#define AD1988_HP_EVENT	0x01
+
+static struct hda_verb ad1988_laptop_init_verbs[] = {
+	/* Front, Surround, CLFE, side DAC; unmute as default */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Port-A front headphon path */
+	{0x37, AC_VERB_SET_CONNECT_SEL, 0x01}, /* DAC1:04h */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* unsolicited event for pin-sense */
+	{0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1988_HP_EVENT },
+	/* Port-D line-out path + EAPD */
+	{0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x12, AC_VERB_SET_EAPD_BTLENABLE, 0x00}, /* EAPD-off */
+	/* Mono out path */
+	{0x36, AC_VERB_SET_CONNECT_SEL, 0x1}, /* DAC1:04h */
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb01f}, /* unmute, 0dB */
+	/* Port-B mic-in path */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x39, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* Port-C docking station - try to output */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x3a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x33, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* mute analog mix */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)},
+	/* select ADCs - mic */
+	{0x0c, AC_VERB_SET_CONNECT_SEL, 0x1},
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0x1},
+	{0x0e, AC_VERB_SET_CONNECT_SEL, 0x1},
+	/* Analog Mix output amp */
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */
+	{ }
+};
+
+static void ad1988_laptop_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	if ((res >> 26) != AD1988_HP_EVENT)
+		return;
+	if (snd_hda_jack_detect(codec, 0x11))
+		snd_hda_sequence_write(codec, ad1988_laptop_hp_on);
+	else
+		snd_hda_sequence_write(codec, ad1988_laptop_hp_off);
+} 
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list ad1988_loopbacks[] = {
+	{ 0x20, HDA_INPUT, 0 }, /* Front Mic */
+	{ 0x20, HDA_INPUT, 1 }, /* Line */
+	{ 0x20, HDA_INPUT, 4 }, /* Mic */
+	{ 0x20, HDA_INPUT, 6 }, /* CD */
+	{ } /* end */
+};
+#endif
+
+/*
+ * Automatic parse of I/O pins from the BIOS configuration
+ */
+
+enum {
+	AD_CTL_WIDGET_VOL,
+	AD_CTL_WIDGET_MUTE,
+	AD_CTL_BIND_MUTE,
+};
+static struct snd_kcontrol_new ad1988_control_templates[] = {
+	HDA_CODEC_VOLUME(NULL, 0, 0, 0),
+	HDA_CODEC_MUTE(NULL, 0, 0, 0),
+	HDA_BIND_MUTE(NULL, 0, 0, 0),
+};
+
+/* add dynamic controls */
+static int add_control(struct ad198x_spec *spec, int type, const char *name,
+		       unsigned long val)
+{
+	struct snd_kcontrol_new *knew;
+
+	snd_array_init(&spec->kctls, sizeof(*knew), 32);
+	knew = snd_array_new(&spec->kctls);
+	if (!knew)
+		return -ENOMEM;
+	*knew = ad1988_control_templates[type];
+	knew->name = kstrdup(name, GFP_KERNEL);
+	if (! knew->name)
+		return -ENOMEM;
+	if (get_amp_nid_(val))
+		knew->subdevice = HDA_SUBDEV_AMP_FLAG;
+	knew->private_value = val;
+	return 0;
+}
+
+#define AD1988_PIN_CD_NID		0x18
+#define AD1988_PIN_BEEP_NID		0x10
+
+static hda_nid_t ad1988_mixer_nids[8] = {
+	/* A     B     C     D     E     F     G     H */
+	0x22, 0x2b, 0x2c, 0x29, 0x26, 0x2a, 0x27, 0x28
+};
+
+static inline hda_nid_t ad1988_idx_to_dac(struct hda_codec *codec, int idx)
+{
+	static hda_nid_t idx_to_dac[8] = {
+		/* A     B     C     D     E     F     G     H */
+		0x04, 0x06, 0x05, 0x04, 0x0a, 0x06, 0x05, 0x0a
+	};
+	static hda_nid_t idx_to_dac_rev2[8] = {
+		/* A     B     C     D     E     F     G     H */
+		0x04, 0x05, 0x0a, 0x04, 0x06, 0x05, 0x0a, 0x06
+	};
+	if (is_rev2(codec))
+		return idx_to_dac_rev2[idx];
+	else
+		return idx_to_dac[idx];
+}
+
+static hda_nid_t ad1988_boost_nids[8] = {
+	0x38, 0x39, 0x3a, 0x3d, 0x3c, 0x3b, 0, 0
+};
+
+static int ad1988_pin_idx(hda_nid_t nid)
+{
+	static hda_nid_t ad1988_io_pins[8] = {
+		0x11, 0x14, 0x15, 0x12, 0x17, 0x16, 0x24, 0x25
+	};
+	int i;
+	for (i = 0; i < ARRAY_SIZE(ad1988_io_pins); i++)
+		if (ad1988_io_pins[i] == nid)
+			return i;
+	return 0; /* should be -1 */
+}
+
+static int ad1988_pin_to_loopback_idx(hda_nid_t nid)
+{
+	static int loopback_idx[8] = {
+		2, 0, 1, 3, 4, 5, 1, 4
+	};
+	switch (nid) {
+	case AD1988_PIN_CD_NID:
+		return 6;
+	default:
+		return loopback_idx[ad1988_pin_idx(nid)];
+	}
+}
+
+static int ad1988_pin_to_adc_idx(hda_nid_t nid)
+{
+	static int adc_idx[8] = {
+		0, 1, 2, 8, 4, 3, 6, 7
+	};
+	switch (nid) {
+	case AD1988_PIN_CD_NID:
+		return 5;
+	default:
+		return adc_idx[ad1988_pin_idx(nid)];
+	}
+}
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int ad1988_auto_fill_dac_nids(struct hda_codec *codec,
+				     const struct auto_pin_cfg *cfg)
+{
+	struct ad198x_spec *spec = codec->spec;
+	int i, idx;
+
+	spec->multiout.dac_nids = spec->private_dac_nids;
+
+	/* check the pins hardwired to audio widget */
+	for (i = 0; i < cfg->line_outs; i++) {
+		idx = ad1988_pin_idx(cfg->line_out_pins[i]);
+		spec->multiout.dac_nids[i] = ad1988_idx_to_dac(codec, idx);
+	}
+	spec->multiout.num_dacs = cfg->line_outs;
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int ad1988_auto_create_multi_out_ctls(struct ad198x_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	char name[32];
+	static const char *chname[4] = { "Front", "Surround", NULL /*CLFE*/, "Side" };
+	hda_nid_t nid;
+	int i, err;
+
+	for (i = 0; i < cfg->line_outs; i++) {
+		hda_nid_t dac = spec->multiout.dac_nids[i];
+		if (! dac)
+			continue;
+		nid = ad1988_mixer_nids[ad1988_pin_idx(cfg->line_out_pins[i])];
+		if (i == 2) {
+			/* Center/LFE */
+			err = add_control(spec, AD_CTL_WIDGET_VOL,
+					  "Center Playback Volume",
+					  HDA_COMPOSE_AMP_VAL(dac, 1, 0, HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = add_control(spec, AD_CTL_WIDGET_VOL,
+					  "LFE Playback Volume",
+					  HDA_COMPOSE_AMP_VAL(dac, 2, 0, HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = add_control(spec, AD_CTL_BIND_MUTE,
+					  "Center Playback Switch",
+					  HDA_COMPOSE_AMP_VAL(nid, 1, 2, HDA_INPUT));
+			if (err < 0)
+				return err;
+			err = add_control(spec, AD_CTL_BIND_MUTE,
+					  "LFE Playback Switch",
+					  HDA_COMPOSE_AMP_VAL(nid, 2, 2, HDA_INPUT));
+			if (err < 0)
+				return err;
+		} else {
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = add_control(spec, AD_CTL_WIDGET_VOL, name,
+					  HDA_COMPOSE_AMP_VAL(dac, 3, 0, HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = add_control(spec, AD_CTL_BIND_MUTE, name,
+					  HDA_COMPOSE_AMP_VAL(nid, 3, 2, HDA_INPUT));
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+/* add playback controls for speaker and HP outputs */
+static int ad1988_auto_create_extra_out(struct hda_codec *codec, hda_nid_t pin,
+					const char *pfx)
+{
+	struct ad198x_spec *spec = codec->spec;
+	hda_nid_t nid;
+	int i, idx, err;
+	char name[32];
+
+	if (! pin)
+		return 0;
+
+	idx = ad1988_pin_idx(pin);
+	nid = ad1988_idx_to_dac(codec, idx);
+	/* check whether the corresponding DAC was already taken */
+	for (i = 0; i < spec->autocfg.line_outs; i++) {
+		hda_nid_t pin = spec->autocfg.line_out_pins[i];
+		hda_nid_t dac = ad1988_idx_to_dac(codec, ad1988_pin_idx(pin));
+		if (dac == nid)
+			break;
+	}
+	if (i >= spec->autocfg.line_outs) {
+		/* specify the DAC as the extra output */
+		if (!spec->multiout.hp_nid)
+			spec->multiout.hp_nid = nid;
+		else
+			spec->multiout.extra_out_nid[0] = nid;
+		/* control HP volume/switch on the output mixer amp */
+		sprintf(name, "%s Playback Volume", pfx);
+		err = add_control(spec, AD_CTL_WIDGET_VOL, name,
+				  HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT));
+		if (err < 0)
+			return err;
+	}
+	nid = ad1988_mixer_nids[idx];
+	sprintf(name, "%s Playback Switch", pfx);
+	if ((err = add_control(spec, AD_CTL_BIND_MUTE, name,
+			       HDA_COMPOSE_AMP_VAL(nid, 3, 2, HDA_INPUT))) < 0)
+		return err;
+	return 0;
+}
+
+/* create input playback/capture controls for the given pin */
+static int new_analog_input(struct ad198x_spec *spec, hda_nid_t pin,
+			    const char *ctlname, int ctlidx, int boost)
+{
+	char name[32];
+	int err, idx;
+
+	sprintf(name, "%s Playback Volume", ctlname);
+	idx = ad1988_pin_to_loopback_idx(pin);
+	if ((err = add_control(spec, AD_CTL_WIDGET_VOL, name,
+			       HDA_COMPOSE_AMP_VAL(0x20, 3, idx, HDA_INPUT))) < 0)
+		return err;
+	sprintf(name, "%s Playback Switch", ctlname);
+	if ((err = add_control(spec, AD_CTL_WIDGET_MUTE, name,
+			       HDA_COMPOSE_AMP_VAL(0x20, 3, idx, HDA_INPUT))) < 0)
+		return err;
+	if (boost) {
+		hda_nid_t bnid;
+		idx = ad1988_pin_idx(pin);
+		bnid = ad1988_boost_nids[idx];
+		if (bnid) {
+			sprintf(name, "%s Boost", ctlname);
+			return add_control(spec, AD_CTL_WIDGET_VOL, name,
+					   HDA_COMPOSE_AMP_VAL(bnid, 3, idx, HDA_OUTPUT));
+
+		}
+	}
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int ad1988_auto_create_analog_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	struct ad198x_spec *spec = codec->spec;
+	struct hda_input_mux *imux = &spec->private_imux;
+	int i, err, type, type_idx;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		const char *label;
+		type = cfg->inputs[i].type;
+		label = hda_get_autocfg_input_label(codec, cfg, i);
+		snd_hda_add_imux_item(imux, label,
+				      ad1988_pin_to_adc_idx(cfg->inputs[i].pin),
+				      &type_idx);
+		err = new_analog_input(spec, cfg->inputs[i].pin,
+				       label, type_idx,
+				       type == AUTO_PIN_MIC);
+		if (err < 0)
+			return err;
+	}
+	snd_hda_add_imux_item(imux, "Mix", 9, NULL);
+
+	if ((err = add_control(spec, AD_CTL_WIDGET_VOL,
+			       "Analog Mix Playback Volume",
+			       HDA_COMPOSE_AMP_VAL(0x21, 3, 0x0, HDA_OUTPUT))) < 0)
+		return err;
+	if ((err = add_control(spec, AD_CTL_WIDGET_MUTE,
+			       "Analog Mix Playback Switch",
+			       HDA_COMPOSE_AMP_VAL(0x21, 3, 0x0, HDA_OUTPUT))) < 0)
+		return err;
+
+	return 0;
+}
+
+static void ad1988_auto_set_output_and_unmute(struct hda_codec *codec,
+					      hda_nid_t nid, int pin_type,
+					      int dac_idx)
+{
+	/* set as output */
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, pin_type);
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE);
+	switch (nid) {
+	case 0x11: /* port-A - DAC 04 */
+		snd_hda_codec_write(codec, 0x37, 0, AC_VERB_SET_CONNECT_SEL, 0x01);
+		break;
+	case 0x14: /* port-B - DAC 06 */
+		snd_hda_codec_write(codec, 0x30, 0, AC_VERB_SET_CONNECT_SEL, 0x02);
+		break;
+	case 0x15: /* port-C - DAC 05 */
+		snd_hda_codec_write(codec, 0x31, 0, AC_VERB_SET_CONNECT_SEL, 0x00);
+		break;
+	case 0x17: /* port-E - DAC 0a */
+		snd_hda_codec_write(codec, 0x32, 0, AC_VERB_SET_CONNECT_SEL, 0x01);
+		break;
+	case 0x13: /* mono - DAC 04 */
+		snd_hda_codec_write(codec, 0x36, 0, AC_VERB_SET_CONNECT_SEL, 0x01);
+		break;
+	}
+}
+
+static void ad1988_auto_init_multi_out(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->autocfg.line_outs; i++) {
+		hda_nid_t nid = spec->autocfg.line_out_pins[i];
+		ad1988_auto_set_output_and_unmute(codec, nid, PIN_OUT, i);
+	}
+}
+
+static void ad1988_auto_init_extra_out(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+	hda_nid_t pin;
+
+	pin = spec->autocfg.speaker_pins[0];
+	if (pin) /* connect to front */
+		ad1988_auto_set_output_and_unmute(codec, pin, PIN_OUT, 0);
+	pin = spec->autocfg.hp_pins[0];
+	if (pin) /* connect to front */
+		ad1988_auto_set_output_and_unmute(codec, pin, PIN_HP, 0);
+}
+
+static void ad1988_auto_init_analog_input(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+	const struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i, idx;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		switch (nid) {
+		case 0x15: /* port-C */
+			snd_hda_codec_write(codec, 0x33, 0, AC_VERB_SET_CONNECT_SEL, 0x0);
+			break;
+		case 0x17: /* port-E */
+			snd_hda_codec_write(codec, 0x34, 0, AC_VERB_SET_CONNECT_SEL, 0x0);
+			break;
+		}
+		snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+				    i == AUTO_PIN_MIC ? PIN_VREF80 : PIN_IN);
+		if (nid != AD1988_PIN_CD_NID)
+			snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
+					    AMP_OUT_MUTE);
+		idx = ad1988_pin_idx(nid);
+		if (ad1988_boost_nids[idx])
+			snd_hda_codec_write(codec, ad1988_boost_nids[idx], 0,
+					    AC_VERB_SET_AMP_GAIN_MUTE,
+					    AMP_OUT_ZERO);
+	}
+}
+
+/* parse the BIOS configuration and set up the alc_spec */
+/* return 1 if successful, 0 if the proper config is not found, or a negative error code */
+static int ad1988_parse_auto_config(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+	int err;
+
+	if ((err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL)) < 0)
+		return err;
+	if ((err = ad1988_auto_fill_dac_nids(codec, &spec->autocfg)) < 0)
+		return err;
+	if (! spec->autocfg.line_outs)
+		return 0; /* can't find valid BIOS pin config */
+	if ((err = ad1988_auto_create_multi_out_ctls(spec, &spec->autocfg)) < 0 ||
+	    (err = ad1988_auto_create_extra_out(codec,
+						spec->autocfg.speaker_pins[0],
+						"Speaker")) < 0 ||
+	    (err = ad1988_auto_create_extra_out(codec, spec->autocfg.hp_pins[0],
+						"Headphone")) < 0 ||
+	    (err = ad1988_auto_create_analog_input_ctls(codec, &spec->autocfg)) < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	if (spec->autocfg.dig_outs)
+		spec->multiout.dig_out_nid = AD1988_SPDIF_OUT;
+	if (spec->autocfg.dig_in_pin)
+		spec->dig_in_nid = AD1988_SPDIF_IN;
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->init_verbs[spec->num_init_verbs++] = ad1988_6stack_init_verbs;
+
+	spec->input_mux = &spec->private_imux;
+
+	return 1;
+}
+
+/* init callback for auto-configuration model -- overriding the default init */
+static int ad1988_auto_init(struct hda_codec *codec)
+{
+	ad198x_init(codec);
+	ad1988_auto_init_multi_out(codec);
+	ad1988_auto_init_extra_out(codec);
+	ad1988_auto_init_analog_input(codec);
+	return 0;
+}
+
+
+/*
+ */
+
+static const char *ad1988_models[AD1988_MODEL_LAST] = {
+	[AD1988_6STACK]		= "6stack",
+	[AD1988_6STACK_DIG]	= "6stack-dig",
+	[AD1988_3STACK]		= "3stack",
+	[AD1988_3STACK_DIG]	= "3stack-dig",
+	[AD1988_LAPTOP]		= "laptop",
+	[AD1988_LAPTOP_DIG]	= "laptop-dig",
+	[AD1988_AUTO]		= "auto",
+};
+
+static struct snd_pci_quirk ad1988_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1043, 0x81ec, "Asus P5B-DLX", AD1988_6STACK_DIG),
+	SND_PCI_QUIRK(0x1043, 0x81f6, "Asus M2N-SLI", AD1988_6STACK_DIG),
+	SND_PCI_QUIRK(0x1043, 0x8277, "Asus P5K-E/WIFI-AP", AD1988_6STACK_DIG),
+	SND_PCI_QUIRK(0x1043, 0x8311, "Asus P5Q-Premium/Pro", AD1988_6STACK_DIG),
+	{}
+};
+
+static int patch_ad1988(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec;
+	int err, board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	if (is_rev2(codec))
+		snd_printk(KERN_INFO "patch_analog: AD1988A rev.2 is detected, enable workarounds\n");
+
+	board_config = snd_hda_check_board_config(codec, AD1988_MODEL_LAST,
+						  ad1988_models, ad1988_cfg_tbl);
+	if (board_config < 0) {
+		printk(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+		       codec->chip_name);
+		board_config = AD1988_AUTO;
+	}
+
+	if (board_config == AD1988_AUTO) {
+		/* automatic parse from the BIOS config */
+		err = ad1988_parse_auto_config(codec);
+		if (err < 0) {
+			ad198x_free(codec);
+			return err;
+		} else if (! err) {
+			printk(KERN_INFO "hda_codec: Cannot set up configuration from BIOS.  Using 6-stack mode...\n");
+			board_config = AD1988_6STACK;
+		}
+	}
+
+	err = snd_hda_attach_beep_device(codec, 0x10);
+	if (err < 0) {
+		ad198x_free(codec);
+		return err;
+	}
+	set_beep_amp(spec, 0x10, 0, HDA_OUTPUT);
+
+	switch (board_config) {
+	case AD1988_6STACK:
+	case AD1988_6STACK_DIG:
+		spec->multiout.max_channels = 8;
+		spec->multiout.num_dacs = 4;
+		if (is_rev2(codec))
+			spec->multiout.dac_nids = ad1988_6stack_dac_nids_rev2;
+		else
+			spec->multiout.dac_nids = ad1988_6stack_dac_nids;
+		spec->input_mux = &ad1988_6stack_capture_source;
+		spec->num_mixers = 2;
+		if (is_rev2(codec))
+			spec->mixers[0] = ad1988_6stack_mixers1_rev2;
+		else
+			spec->mixers[0] = ad1988_6stack_mixers1;
+		spec->mixers[1] = ad1988_6stack_mixers2;
+		spec->num_init_verbs = 1;
+		spec->init_verbs[0] = ad1988_6stack_init_verbs;
+		if (board_config == AD1988_6STACK_DIG) {
+			spec->multiout.dig_out_nid = AD1988_SPDIF_OUT;
+			spec->dig_in_nid = AD1988_SPDIF_IN;
+		}
+		break;
+	case AD1988_3STACK:
+	case AD1988_3STACK_DIG:
+		spec->multiout.max_channels = 6;
+		spec->multiout.num_dacs = 3;
+		if (is_rev2(codec))
+			spec->multiout.dac_nids = ad1988_3stack_dac_nids_rev2;
+		else
+			spec->multiout.dac_nids = ad1988_3stack_dac_nids;
+		spec->input_mux = &ad1988_6stack_capture_source;
+		spec->channel_mode = ad1988_3stack_modes;
+		spec->num_channel_mode = ARRAY_SIZE(ad1988_3stack_modes);
+		spec->num_mixers = 2;
+		if (is_rev2(codec))
+			spec->mixers[0] = ad1988_3stack_mixers1_rev2;
+		else
+			spec->mixers[0] = ad1988_3stack_mixers1;
+		spec->mixers[1] = ad1988_3stack_mixers2;
+		spec->num_init_verbs = 1;
+		spec->init_verbs[0] = ad1988_3stack_init_verbs;
+		if (board_config == AD1988_3STACK_DIG)
+			spec->multiout.dig_out_nid = AD1988_SPDIF_OUT;
+		break;
+	case AD1988_LAPTOP:
+	case AD1988_LAPTOP_DIG:
+		spec->multiout.max_channels = 2;
+		spec->multiout.num_dacs = 1;
+		spec->multiout.dac_nids = ad1988_3stack_dac_nids;
+		spec->input_mux = &ad1988_laptop_capture_source;
+		spec->num_mixers = 1;
+		spec->mixers[0] = ad1988_laptop_mixers;
+		spec->inv_eapd = 1; /* inverted EAPD */
+		spec->num_init_verbs = 1;
+		spec->init_verbs[0] = ad1988_laptop_init_verbs;
+		if (board_config == AD1988_LAPTOP_DIG)
+			spec->multiout.dig_out_nid = AD1988_SPDIF_OUT;
+		break;
+	}
+
+	spec->num_adc_nids = ARRAY_SIZE(ad1988_adc_nids);
+	spec->adc_nids = ad1988_adc_nids;
+	spec->capsrc_nids = ad1988_capsrc_nids;
+	spec->mixers[spec->num_mixers++] = ad1988_capture_mixers;
+	spec->init_verbs[spec->num_init_verbs++] = ad1988_capture_init_verbs;
+	if (spec->multiout.dig_out_nid) {
+		if (codec->vendor_id >= 0x11d4989a) {
+			spec->mixers[spec->num_mixers++] =
+				ad1989_spdif_out_mixers;
+			spec->init_verbs[spec->num_init_verbs++] =
+				ad1989_spdif_init_verbs;
+			codec->slave_dig_outs = ad1989b_slave_dig_outs;
+		} else {
+			spec->mixers[spec->num_mixers++] =
+				ad1988_spdif_out_mixers;
+			spec->init_verbs[spec->num_init_verbs++] =
+				ad1988_spdif_init_verbs;
+		}
+	}
+	if (spec->dig_in_nid && codec->vendor_id < 0x11d4989a) {
+		spec->mixers[spec->num_mixers++] = ad1988_spdif_in_mixers;
+		spec->init_verbs[spec->num_init_verbs++] =
+			ad1988_spdif_in_init_verbs;
+	}
+
+	codec->patch_ops = ad198x_patch_ops;
+	switch (board_config) {
+	case AD1988_AUTO:
+		codec->patch_ops.init = ad1988_auto_init;
+		break;
+	case AD1988_LAPTOP:
+	case AD1988_LAPTOP_DIG:
+		codec->patch_ops.unsol_event = ad1988_laptop_unsol_event;
+		break;
+	}
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = ad1988_loopbacks;
+#endif
+	spec->vmaster_nid = 0x04;
+
+	codec->no_trigger_sense = 1;
+	codec->no_sticky_stream = 1;
+
+	return 0;
+}
+
+
+/*
+ * AD1884 / AD1984
+ *
+ * port-B - front line/mic-in
+ * port-E - aux in/out
+ * port-F - aux in/out
+ * port-C - rear line/mic-in
+ * port-D - rear line/hp-out
+ * port-A - front line/hp-out
+ *
+ * AD1984 = AD1884 + two digital mic-ins
+ *
+ * FIXME:
+ * For simplicity, we share the single DAC for both HP and line-outs
+ * right now.  The inidividual playbacks could be easily implemented,
+ * but no build-up framework is given, so far.
+ */
+
+static hda_nid_t ad1884_dac_nids[1] = {
+	0x04,
+};
+
+static hda_nid_t ad1884_adc_nids[2] = {
+	0x08, 0x09,
+};
+
+static hda_nid_t ad1884_capsrc_nids[2] = {
+	0x0c, 0x0d,
+};
+
+#define AD1884_SPDIF_OUT	0x02
+
+static struct hda_input_mux ad1884_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Front Mic", 0x0 },
+		{ "Mic", 0x1 },
+		{ "CD", 0x2 },
+		{ "Mix", 0x3 },
+	},
+};
+
+static struct snd_kcontrol_new ad1884_base_mixers[] = {
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x04, 0x0, HDA_OUTPUT),
+	/* HDA_CODEC_VOLUME_IDX("PCM Playback Volume", 1, 0x03, 0x0, HDA_OUTPUT), */
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x13, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x13, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 2,
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	/* SPDIF controls */
+	HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+		/* identical with ad1983 */
+		.info = ad1983_spdif_route_info,
+		.get = ad1983_spdif_route_get,
+		.put = ad1983_spdif_route_put,
+	},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1984_dmic_mixers[] = {
+	HDA_CODEC_VOLUME("Digital Mic Capture Volume", 0x05, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Digital Mic Capture Switch", 0x05, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Digital Mic Capture Volume", 1, 0x06, 0x0,
+			     HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Digital Mic Capture Switch", 1, 0x06, 0x0,
+			   HDA_INPUT),
+	{ } /* end */
+};
+
+/*
+ * initialization verbs
+ */
+static struct hda_verb ad1884_init_verbs[] = {
+	/* DACs; mute as default */
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* Port-A (HP) mixer */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Port-A pin */
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* HP selector - select DAC2 */
+	{0x22, AC_VERB_SET_CONNECT_SEL, 0x1},
+	/* Port-D (Line-out) mixer */
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Port-D pin */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Mono-out mixer */
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Mono-out pin */
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Mono selector */
+	{0x0e, AC_VERB_SET_CONNECT_SEL, 0x1},
+	/* Port-B (front mic) pin */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Port-C (rear mic) pin */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Analog mixer; mute as default */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	/* Analog Mix output amp */
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */
+	/* SPDIF output selector */
+	{0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, /* PCM */
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */
+	{ } /* end */
+};
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list ad1884_loopbacks[] = {
+	{ 0x20, HDA_INPUT, 0 }, /* Front Mic */
+	{ 0x20, HDA_INPUT, 1 }, /* Mic */
+	{ 0x20, HDA_INPUT, 2 }, /* CD */
+	{ 0x20, HDA_INPUT, 4 }, /* Docking */
+	{ } /* end */
+};
+#endif
+
+static const char *ad1884_slave_vols[] = {
+	"PCM Playback Volume",
+	"Mic Playback Volume",
+	"Mono Playback Volume",
+	"Front Mic Playback Volume",
+	"Mic Playback Volume",
+	"CD Playback Volume",
+	"Internal Mic Playback Volume",
+	"Docking Mic Playback Volume",
+	/* "Beep Playback Volume", */
+	"IEC958 Playback Volume",
+	NULL
+};
+
+static int patch_ad1884(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	err = snd_hda_attach_beep_device(codec, 0x10);
+	if (err < 0) {
+		ad198x_free(codec);
+		return err;
+	}
+	set_beep_amp(spec, 0x10, 0, HDA_OUTPUT);
+
+	spec->multiout.max_channels = 2;
+	spec->multiout.num_dacs = ARRAY_SIZE(ad1884_dac_nids);
+	spec->multiout.dac_nids = ad1884_dac_nids;
+	spec->multiout.dig_out_nid = AD1884_SPDIF_OUT;
+	spec->num_adc_nids = ARRAY_SIZE(ad1884_adc_nids);
+	spec->adc_nids = ad1884_adc_nids;
+	spec->capsrc_nids = ad1884_capsrc_nids;
+	spec->input_mux = &ad1884_capture_source;
+	spec->num_mixers = 1;
+	spec->mixers[0] = ad1884_base_mixers;
+	spec->num_init_verbs = 1;
+	spec->init_verbs[0] = ad1884_init_verbs;
+	spec->spdif_route = 0;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = ad1884_loopbacks;
+#endif
+	spec->vmaster_nid = 0x04;
+	/* we need to cover all playback volumes */
+	spec->slave_vols = ad1884_slave_vols;
+
+	codec->patch_ops = ad198x_patch_ops;
+
+	codec->no_trigger_sense = 1;
+	codec->no_sticky_stream = 1;
+
+	return 0;
+}
+
+/*
+ * Lenovo Thinkpad T61/X61
+ */
+static struct hda_input_mux ad1984_thinkpad_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Internal Mic", 0x1 },
+		{ "Mix", 0x3 },
+		{ "Docking-Station", 0x4 },
+	},
+};
+
+
+/*
+ * Dell Precision T3400
+ */
+static struct hda_input_mux ad1984_dell_desktop_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Front Mic", 0x0 },
+		{ "Line-In", 0x1 },
+		{ "Mix", 0x3 },
+	},
+};
+
+
+static struct snd_kcontrol_new ad1984_thinkpad_mixers[] = {
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x04, 0x0, HDA_OUTPUT),
+	/* HDA_CODEC_VOLUME_IDX("PCM Playback Volume", 1, 0x03, 0x0, HDA_OUTPUT), */
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Beep Playback Volume", 0x20, 0x03, HDA_INPUT),
+	HDA_CODEC_MUTE("Beep Playback Switch", 0x20, 0x03, HDA_INPUT),
+	HDA_CODEC_VOLUME("Docking Mic Playback Volume", 0x20, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("Docking Mic Playback Switch", 0x20, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Boost", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Docking Mic Boost", 0x25, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 2,
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	/* SPDIF controls */
+	HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+		/* identical with ad1983 */
+		.info = ad1983_spdif_route_info,
+		.get = ad1983_spdif_route_get,
+		.put = ad1983_spdif_route_put,
+	},
+	{ } /* end */
+};
+
+/* additional verbs */
+static struct hda_verb ad1984_thinkpad_init_verbs[] = {
+	/* Port-E (docking station mic) pin */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* docking mic boost */
+	{0x25, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* Analog PC Beeper - allow firmware/ACPI beeps */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3) | 0x1a},
+	/* Analog mixer - docking mic; mute as default */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* enable EAPD bit */
+	{0x12, AC_VERB_SET_EAPD_BTLENABLE, 0x02},
+	{ } /* end */
+};
+
+/*
+ * Dell Precision T3400
+ */
+static struct snd_kcontrol_new ad1984_dell_desktop_mixers[] = {
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x04, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x13, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x13, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line-In Playback Volume", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Line-In Playback Switch", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line-In Boost", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 2,
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+/* Digial MIC ADC NID 0x05 + 0x06 */
+static int ad1984_pcm_dmic_prepare(struct hda_pcm_stream *hinfo,
+				   struct hda_codec *codec,
+				   unsigned int stream_tag,
+				   unsigned int format,
+				   struct snd_pcm_substream *substream)
+{
+	snd_hda_codec_setup_stream(codec, 0x05 + substream->number,
+				   stream_tag, 0, format);
+	return 0;
+}
+
+static int ad1984_pcm_dmic_cleanup(struct hda_pcm_stream *hinfo,
+				   struct hda_codec *codec,
+				   struct snd_pcm_substream *substream)
+{
+	snd_hda_codec_cleanup_stream(codec, 0x05 + substream->number);
+	return 0;
+}
+
+static struct hda_pcm_stream ad1984_pcm_dmic_capture = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x05,
+	.ops = {
+		.prepare = ad1984_pcm_dmic_prepare,
+		.cleanup = ad1984_pcm_dmic_cleanup
+	},
+};
+
+static int ad1984_build_pcms(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec = codec->spec;
+	struct hda_pcm *info;
+	int err;
+
+	err = ad198x_build_pcms(codec);
+	if (err < 0)
+		return err;
+
+	info = spec->pcm_rec + codec->num_pcms;
+	codec->num_pcms++;
+	info->name = "AD1984 Digital Mic";
+	info->stream[SNDRV_PCM_STREAM_CAPTURE] = ad1984_pcm_dmic_capture;
+	return 0;
+}
+
+/* models */
+enum {
+	AD1984_BASIC,
+	AD1984_THINKPAD,
+	AD1984_DELL_DESKTOP,
+	AD1984_MODELS
+};
+
+static const char *ad1984_models[AD1984_MODELS] = {
+	[AD1984_BASIC]		= "basic",
+	[AD1984_THINKPAD]	= "thinkpad",
+	[AD1984_DELL_DESKTOP]	= "dell_desktop",
+};
+
+static struct snd_pci_quirk ad1984_cfg_tbl[] = {
+	/* Lenovo Thinkpad T61/X61 */
+	SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo Thinkpad", AD1984_THINKPAD),
+	SND_PCI_QUIRK(0x1028, 0x0214, "Dell T3400", AD1984_DELL_DESKTOP),
+	SND_PCI_QUIRK(0x1028, 0x0233, "Dell Latitude E6400", AD1984_DELL_DESKTOP),
+	{}
+};
+
+static int patch_ad1984(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec;
+	int board_config, err;
+
+	err = patch_ad1884(codec);
+	if (err < 0)
+		return err;
+	spec = codec->spec;
+	board_config = snd_hda_check_board_config(codec, AD1984_MODELS,
+						  ad1984_models, ad1984_cfg_tbl);
+	switch (board_config) {
+	case AD1984_BASIC:
+		/* additional digital mics */
+		spec->mixers[spec->num_mixers++] = ad1984_dmic_mixers;
+		codec->patch_ops.build_pcms = ad1984_build_pcms;
+		break;
+	case AD1984_THINKPAD:
+		if (codec->subsystem_id == 0x17aa20fb) {
+			/* Thinpad X300 does not have the ability to do SPDIF,
+			   or attach to docking station to use SPDIF */
+			spec->multiout.dig_out_nid = 0;
+		} else
+			spec->multiout.dig_out_nid = AD1884_SPDIF_OUT;
+		spec->input_mux = &ad1984_thinkpad_capture_source;
+		spec->mixers[0] = ad1984_thinkpad_mixers;
+		spec->init_verbs[spec->num_init_verbs++] = ad1984_thinkpad_init_verbs;
+		spec->analog_beep = 1;
+		break;
+	case AD1984_DELL_DESKTOP:
+		spec->multiout.dig_out_nid = 0;
+		spec->input_mux = &ad1984_dell_desktop_capture_source;
+		spec->mixers[0] = ad1984_dell_desktop_mixers;
+		break;
+	}
+	return 0;
+}
+
+
+/*
+ * AD1883 / AD1884A / AD1984A / AD1984B
+ *
+ * port-B (0x14) - front mic-in
+ * port-E (0x1c) - rear mic-in
+ * port-F (0x16) - CD / ext out
+ * port-C (0x15) - rear line-in
+ * port-D (0x12) - rear line-out
+ * port-A (0x11) - front hp-out
+ *
+ * AD1984A = AD1884A + digital-mic
+ * AD1883 = equivalent with AD1984A
+ * AD1984B = AD1984A + extra SPDIF-out
+ *
+ * FIXME:
+ * We share the single DAC for both HP and line-outs (see AD1884/1984).
+ */
+
+static hda_nid_t ad1884a_dac_nids[1] = {
+	0x03,
+};
+
+#define ad1884a_adc_nids	ad1884_adc_nids
+#define ad1884a_capsrc_nids	ad1884_capsrc_nids
+
+#define AD1884A_SPDIF_OUT	0x02
+
+static struct hda_input_mux ad1884a_capture_source = {
+	.num_items = 5,
+	.items = {
+		{ "Front Mic", 0x0 },
+		{ "Mic", 0x4 },
+		{ "Line", 0x1 },
+		{ "CD", 0x2 },
+		{ "Mix", 0x3 },
+	},
+};
+
+static struct snd_kcontrol_new ad1884a_base_mixers[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x21, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Master Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x13, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x13, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x20, 0x5, HDA_INPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x20, 0x5, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Boost", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x25, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 2,
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	/* SPDIF controls */
+	HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+		/* identical with ad1983 */
+		.info = ad1983_spdif_route_info,
+		.get = ad1983_spdif_route_get,
+		.put = ad1983_spdif_route_put,
+	},
+	{ } /* end */
+};
+
+/*
+ * initialization verbs
+ */
+static struct hda_verb ad1884a_init_verbs[] = {
+	/* DACs; unmute as default */
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, 0x27}, /* 0dB */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, 0x27}, /* 0dB */
+	/* Port-A (HP) mixer - route only from analog mixer */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Port-A pin */
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Port-D (Line-out) mixer - route only from analog mixer */
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Port-D pin */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Mono-out mixer - route only from analog mixer */
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Mono-out pin */
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Port-B (front mic) pin */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Port-C (rear line-in) pin */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Port-E (rear mic) pin */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x25, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, /* no boost */
+	/* Port-F (CD) pin */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Analog mixer; mute as default */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, /* aux */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+	/* Analog Mix output amp */
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* capture sources */
+	{0x0c, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* SPDIF output amp */
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */
+	{ } /* end */
+};
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list ad1884a_loopbacks[] = {
+	{ 0x20, HDA_INPUT, 0 }, /* Front Mic */
+	{ 0x20, HDA_INPUT, 1 }, /* Mic */
+	{ 0x20, HDA_INPUT, 2 }, /* CD */
+	{ 0x20, HDA_INPUT, 4 }, /* Docking */
+	{ } /* end */
+};
+#endif
+
+/*
+ * Laptop model
+ *
+ * Port A: Headphone jack
+ * Port B: MIC jack
+ * Port C: Internal MIC
+ * Port D: Dock Line Out (if enabled)
+ * Port E: Dock Line In (if enabled)
+ * Port F: Internal speakers
+ */
+
+static int ad1884a_mobile_master_sw_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	int ret = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+	int mute = (!ucontrol->value.integer.value[0] &&
+		    !ucontrol->value.integer.value[1]);
+	/* toggle GPIO1 according to the mute state */
+	snd_hda_codec_write_cache(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
+			    mute ? 0x02 : 0x0);
+	return ret;
+}
+
+static struct snd_kcontrol_new ad1884a_laptop_mixers[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x21, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.info = snd_hda_mixer_amp_switch_info,
+		.get = snd_hda_mixer_amp_switch_get,
+		.put = ad1884a_mobile_master_sw_put,
+		.private_value = HDA_COMPOSE_AMP_VAL(0x21, 3, 0, HDA_OUTPUT),
+	},
+	HDA_CODEC_MUTE("Dock Playback Switch", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x20, 0x5, HDA_INPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x20, 0x5, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Dock Mic Playback Volume", 0x20, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("Dock Mic Playback Switch", 0x20, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Boost", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Dock Mic Boost", 0x25, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1884a_mobile_mixers[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x21, 0x0, HDA_OUTPUT),
+	/*HDA_CODEC_MUTE("Master Playback Switch", 0x21, 0x0, HDA_OUTPUT),*/
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.info = snd_hda_mixer_amp_switch_info,
+		.get = snd_hda_mixer_amp_switch_get,
+		.put = ad1884a_mobile_master_sw_put,
+		.private_value = HDA_COMPOSE_AMP_VAL(0x21, 3, 0, HDA_OUTPUT),
+	},
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x20, 0x5, HDA_INPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x20, 0x5, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Capture Volume", 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Capture Volume", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+/* mute internal speaker if HP is plugged */
+static void ad1884a_hp_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+
+	present = snd_hda_jack_detect(codec, 0x11);
+	snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+	snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_EAPD_BTLENABLE,
+			    present ? 0x00 : 0x02);
+}
+
+/* switch to external mic if plugged */
+static void ad1884a_hp_automic(struct hda_codec *codec)
+{
+	unsigned int present;
+
+	present = snd_hda_jack_detect(codec, 0x14);
+	snd_hda_codec_write(codec, 0x0c, 0, AC_VERB_SET_CONNECT_SEL,
+			    present ? 0 : 1);
+}
+
+#define AD1884A_HP_EVENT		0x37
+#define AD1884A_MIC_EVENT		0x36
+
+/* unsolicited event for HP jack sensing */
+static void ad1884a_hp_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	switch (res >> 26) {
+	case AD1884A_HP_EVENT:
+		ad1884a_hp_automute(codec);
+		break;
+	case AD1884A_MIC_EVENT:
+		ad1884a_hp_automic(codec);
+		break;
+	}
+}
+
+/* initialize jack-sensing, too */
+static int ad1884a_hp_init(struct hda_codec *codec)
+{
+	ad198x_init(codec);
+	ad1884a_hp_automute(codec);
+	ad1884a_hp_automic(codec);
+	return 0;
+}
+
+/* mute internal speaker if HP or docking HP is plugged */
+static void ad1884a_laptop_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+
+	present = snd_hda_jack_detect(codec, 0x11);
+	if (!present)
+		present = snd_hda_jack_detect(codec, 0x12);
+	snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+	snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_EAPD_BTLENABLE,
+			    present ? 0x00 : 0x02);
+}
+
+/* switch to external mic if plugged */
+static void ad1884a_laptop_automic(struct hda_codec *codec)
+{
+	unsigned int idx;
+
+	if (snd_hda_jack_detect(codec, 0x14))
+		idx = 0;
+	else if (snd_hda_jack_detect(codec, 0x1c))
+		idx = 4;
+	else
+		idx = 1;
+	snd_hda_codec_write(codec, 0x0c, 0, AC_VERB_SET_CONNECT_SEL, idx);
+}
+
+/* unsolicited event for HP jack sensing */
+static void ad1884a_laptop_unsol_event(struct hda_codec *codec,
+				       unsigned int res)
+{
+	switch (res >> 26) {
+	case AD1884A_HP_EVENT:
+		ad1884a_laptop_automute(codec);
+		break;
+	case AD1884A_MIC_EVENT:
+		ad1884a_laptop_automic(codec);
+		break;
+	}
+}
+
+/* initialize jack-sensing, too */
+static int ad1884a_laptop_init(struct hda_codec *codec)
+{
+	ad198x_init(codec);
+	ad1884a_laptop_automute(codec);
+	ad1884a_laptop_automic(codec);
+	return 0;
+}
+
+/* additional verbs for laptop model */
+static struct hda_verb ad1884a_laptop_verbs[] = {
+	/* Port-A (HP) pin - always unmuted */
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Port-F (int speaker) mixer - route only from analog mixer */
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Port-F (int speaker) pin */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* required for compaq 6530s/6531s speaker output */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* Port-C pin - internal mic-in */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x7002}, /* raise mic as default */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0x7002}, /* raise mic as default */
+	/* Port-D (docking line-out) pin - default unmuted */
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* analog mix */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* unsolicited event for pin-sense */
+	{0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_HP_EVENT},
+	{0x12, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_HP_EVENT},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_MIC_EVENT},
+	{0x1c, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_MIC_EVENT},
+	/* allow to touch GPIO1 (for mute control) */
+	{0x01, AC_VERB_SET_GPIO_MASK, 0x02},
+	{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x02},
+	{0x01, AC_VERB_SET_GPIO_DATA, 0x02}, /* first muted */
+	{ } /* end */
+};
+
+static struct hda_verb ad1884a_mobile_verbs[] = {
+	/* DACs; unmute as default */
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, 0x27}, /* 0dB */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, 0x27}, /* 0dB */
+	/* Port-A (HP) mixer - route only from analog mixer */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Port-A pin */
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Port-A (HP) pin - always unmuted */
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Port-B (mic jack) pin */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x7002}, /* raise mic as default */
+	/* Port-C (int mic) pin */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0x7002}, /* raise mic as default */
+	/* Port-F (int speaker) mixer - route only from analog mixer */
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Port-F pin */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Analog mixer; mute as default */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+	/* Analog Mix output amp */
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* capture sources */
+	/* {0x0c, AC_VERB_SET_CONNECT_SEL, 0x0}, */ /* set via unsol */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* unsolicited event for pin-sense */
+	{0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_HP_EVENT},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_MIC_EVENT},
+	/* allow to touch GPIO1 (for mute control) */
+	{0x01, AC_VERB_SET_GPIO_MASK, 0x02},
+	{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x02},
+	{0x01, AC_VERB_SET_GPIO_DATA, 0x02}, /* first muted */
+	{ } /* end */
+};
+
+/*
+ * Thinkpad X300
+ * 0x11 - HP
+ * 0x12 - speaker
+ * 0x14 - mic-in
+ * 0x17 - built-in mic
+ */
+
+static struct hda_verb ad1984a_thinkpad_verbs[] = {
+	/* HP unmute */
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* analog mix */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* turn on EAPD */
+	{0x12, AC_VERB_SET_EAPD_BTLENABLE, 0x02},
+	/* unsolicited event for pin-sense */
+	{0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_HP_EVENT},
+	/* internal mic - dmic */
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	/* set magic COEFs for dmic */
+	{0x01, AC_VERB_SET_COEF_INDEX, 0x13f7},
+	{0x01, AC_VERB_SET_PROC_COEF, 0x08},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1984a_thinkpad_mixers[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x21, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Master Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x20, 0x5, HDA_INPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x20, 0x5, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Boost", 0x17, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_input_mux ad1984a_thinkpad_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Internal Mic", 0x5 },
+		{ "Mix", 0x3 },
+	},
+};
+
+/* mute internal speaker if HP is plugged */
+static void ad1984a_thinkpad_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+
+	present = snd_hda_jack_detect(codec, 0x11);
+	snd_hda_codec_amp_stereo(codec, 0x12, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+}
+
+/* unsolicited event for HP jack sensing */
+static void ad1984a_thinkpad_unsol_event(struct hda_codec *codec,
+					 unsigned int res)
+{
+	if ((res >> 26) != AD1884A_HP_EVENT)
+		return;
+	ad1984a_thinkpad_automute(codec);
+}
+
+/* initialize jack-sensing, too */
+static int ad1984a_thinkpad_init(struct hda_codec *codec)
+{
+	ad198x_init(codec);
+	ad1984a_thinkpad_automute(codec);
+	return 0;
+}
+
+/*
+ * HP Touchsmart
+ * port-A (0x11)      - front hp-out
+ * port-B (0x14)      - unused
+ * port-C (0x15)      - unused
+ * port-D (0x12)      - rear line out
+ * port-E (0x1c)      - front mic-in
+ * port-F (0x16)      - Internal speakers
+ * digital-mic (0x17) - Internal mic
+ */
+
+static struct hda_verb ad1984a_touchsmart_verbs[] = {
+	/* DACs; unmute as default */
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, 0x27}, /* 0dB */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, 0x27}, /* 0dB */
+	/* Port-A (HP) mixer - route only from analog mixer */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Port-A pin */
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Port-A (HP) pin - always unmuted */
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Port-E (int speaker) mixer - route only from analog mixer */
+	{0x25, AC_VERB_SET_AMP_GAIN_MUTE, 0x03},
+	/* Port-E pin */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	/* Port-F (int speaker) mixer - route only from analog mixer */
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Port-F pin */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Analog mixer; mute as default */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+	/* Analog Mix output amp */
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* capture sources */
+	/* {0x0c, AC_VERB_SET_CONNECT_SEL, 0x0}, */ /* set via unsol */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* unsolicited event for pin-sense */
+	{0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_HP_EVENT},
+	{0x1c, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_MIC_EVENT},
+	/* allow to touch GPIO1 (for mute control) */
+	{0x01, AC_VERB_SET_GPIO_MASK, 0x02},
+	{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x02},
+	{0x01, AC_VERB_SET_GPIO_DATA, 0x02}, /* first muted */
+	/* internal mic - dmic */
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	/* set magic COEFs for dmic */
+	{0x01, AC_VERB_SET_COEF_INDEX, 0x13f7},
+	{0x01, AC_VERB_SET_PROC_COEF, 0x08},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1984a_touchsmart_mixers[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x21, 0x0, HDA_OUTPUT),
+/*	HDA_CODEC_MUTE("Master Playback Switch", 0x21, 0x0, HDA_OUTPUT),*/
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.name = "Master Playback Switch",
+		.info = snd_hda_mixer_amp_switch_info,
+		.get = snd_hda_mixer_amp_switch_get,
+		.put = ad1884a_mobile_master_sw_put,
+		.private_value = HDA_COMPOSE_AMP_VAL(0x21, 3, 0, HDA_OUTPUT),
+	},
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x20, 0x5, HDA_INPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x20, 0x5, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x25, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Internal Mic Boost", 0x17, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+/* switch to external mic if plugged */
+static void ad1984a_touchsmart_automic(struct hda_codec *codec)
+{
+	if (snd_hda_jack_detect(codec, 0x1c))
+		snd_hda_codec_write(codec, 0x0c, 0,
+				     AC_VERB_SET_CONNECT_SEL, 0x4);
+	else
+		snd_hda_codec_write(codec, 0x0c, 0,
+				     AC_VERB_SET_CONNECT_SEL, 0x5);
+}
+
+
+/* unsolicited event for HP jack sensing */
+static void ad1984a_touchsmart_unsol_event(struct hda_codec *codec,
+	unsigned int res)
+{
+	switch (res >> 26) {
+	case AD1884A_HP_EVENT:
+		ad1884a_hp_automute(codec);
+		break;
+	case AD1884A_MIC_EVENT:
+		ad1984a_touchsmart_automic(codec);
+		break;
+	}
+}
+
+/* initialize jack-sensing, too */
+static int ad1984a_touchsmart_init(struct hda_codec *codec)
+{
+	ad198x_init(codec);
+	ad1884a_hp_automute(codec);
+	ad1984a_touchsmart_automic(codec);
+	return 0;
+}
+
+
+/*
+ */
+
+enum {
+	AD1884A_DESKTOP,
+	AD1884A_LAPTOP,
+	AD1884A_MOBILE,
+	AD1884A_THINKPAD,
+	AD1984A_TOUCHSMART,
+	AD1884A_MODELS
+};
+
+static const char *ad1884a_models[AD1884A_MODELS] = {
+	[AD1884A_DESKTOP]	= "desktop",
+	[AD1884A_LAPTOP]	= "laptop",
+	[AD1884A_MOBILE]	= "mobile",
+	[AD1884A_THINKPAD]	= "thinkpad",
+	[AD1984A_TOUCHSMART]	= "touchsmart",
+};
+
+static struct snd_pci_quirk ad1884a_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x103c, 0x3030, "HP", AD1884A_MOBILE),
+	SND_PCI_QUIRK(0x103c, 0x3037, "HP 2230s", AD1884A_LAPTOP),
+	SND_PCI_QUIRK(0x103c, 0x3056, "HP", AD1884A_MOBILE),
+	SND_PCI_QUIRK_MASK(0x103c, 0xfff0, 0x3070, "HP", AD1884A_MOBILE),
+	SND_PCI_QUIRK_MASK(0x103c, 0xfff0, 0x30d0, "HP laptop", AD1884A_LAPTOP),
+	SND_PCI_QUIRK_MASK(0x103c, 0xfff0, 0x30e0, "HP laptop", AD1884A_LAPTOP),
+	SND_PCI_QUIRK_MASK(0x103c, 0xff00, 0x3600, "HP laptop", AD1884A_LAPTOP),
+	SND_PCI_QUIRK_MASK(0x103c, 0xfff0, 0x7010, "HP laptop", AD1884A_MOBILE),
+	SND_PCI_QUIRK(0x17aa, 0x20ac, "Thinkpad X300", AD1884A_THINKPAD),
+	SND_PCI_QUIRK(0x103c, 0x2a82, "Touchsmart", AD1984A_TOUCHSMART),
+	{}
+};
+
+static int patch_ad1884a(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec;
+	int err, board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	err = snd_hda_attach_beep_device(codec, 0x10);
+	if (err < 0) {
+		ad198x_free(codec);
+		return err;
+	}
+	set_beep_amp(spec, 0x10, 0, HDA_OUTPUT);
+
+	spec->multiout.max_channels = 2;
+	spec->multiout.num_dacs = ARRAY_SIZE(ad1884a_dac_nids);
+	spec->multiout.dac_nids = ad1884a_dac_nids;
+	spec->multiout.dig_out_nid = AD1884A_SPDIF_OUT;
+	spec->num_adc_nids = ARRAY_SIZE(ad1884a_adc_nids);
+	spec->adc_nids = ad1884a_adc_nids;
+	spec->capsrc_nids = ad1884a_capsrc_nids;
+	spec->input_mux = &ad1884a_capture_source;
+	spec->num_mixers = 1;
+	spec->mixers[0] = ad1884a_base_mixers;
+	spec->num_init_verbs = 1;
+	spec->init_verbs[0] = ad1884a_init_verbs;
+	spec->spdif_route = 0;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = ad1884a_loopbacks;
+#endif
+	codec->patch_ops = ad198x_patch_ops;
+
+	/* override some parameters */
+	board_config = snd_hda_check_board_config(codec, AD1884A_MODELS,
+						  ad1884a_models,
+						  ad1884a_cfg_tbl);
+	switch (board_config) {
+	case AD1884A_LAPTOP:
+		spec->mixers[0] = ad1884a_laptop_mixers;
+		spec->init_verbs[spec->num_init_verbs++] = ad1884a_laptop_verbs;
+		spec->multiout.dig_out_nid = 0;
+		codec->patch_ops.unsol_event = ad1884a_laptop_unsol_event;
+		codec->patch_ops.init = ad1884a_laptop_init;
+		/* set the upper-limit for mixer amp to 0dB for avoiding the
+		 * possible damage by overloading
+		 */
+		snd_hda_override_amp_caps(codec, 0x20, HDA_INPUT,
+					  (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+					  (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+					  (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+					  (1 << AC_AMPCAP_MUTE_SHIFT));
+		break;
+	case AD1884A_MOBILE:
+		spec->mixers[0] = ad1884a_mobile_mixers;
+		spec->init_verbs[0] = ad1884a_mobile_verbs;
+		spec->multiout.dig_out_nid = 0;
+		codec->patch_ops.unsol_event = ad1884a_hp_unsol_event;
+		codec->patch_ops.init = ad1884a_hp_init;
+		/* set the upper-limit for mixer amp to 0dB for avoiding the
+		 * possible damage by overloading
+		 */
+		snd_hda_override_amp_caps(codec, 0x20, HDA_INPUT,
+					  (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+					  (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+					  (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+					  (1 << AC_AMPCAP_MUTE_SHIFT));
+		break;
+	case AD1884A_THINKPAD:
+		spec->mixers[0] = ad1984a_thinkpad_mixers;
+		spec->init_verbs[spec->num_init_verbs++] =
+			ad1984a_thinkpad_verbs;
+		spec->multiout.dig_out_nid = 0;
+		spec->input_mux = &ad1984a_thinkpad_capture_source;
+		codec->patch_ops.unsol_event = ad1984a_thinkpad_unsol_event;
+		codec->patch_ops.init = ad1984a_thinkpad_init;
+		break;
+	case AD1984A_TOUCHSMART:
+		spec->mixers[0] = ad1984a_touchsmart_mixers;
+		spec->init_verbs[0] = ad1984a_touchsmart_verbs;
+		spec->multiout.dig_out_nid = 0;
+		codec->patch_ops.unsol_event = ad1984a_touchsmart_unsol_event;
+		codec->patch_ops.init = ad1984a_touchsmart_init;
+		/* set the upper-limit for mixer amp to 0dB for avoiding the
+		 * possible damage by overloading
+		 */
+		snd_hda_override_amp_caps(codec, 0x20, HDA_INPUT,
+					  (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+					  (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+					  (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+					  (1 << AC_AMPCAP_MUTE_SHIFT));
+		break;
+	}
+
+	codec->no_trigger_sense = 1;
+	codec->no_sticky_stream = 1;
+
+	return 0;
+}
+
+
+/*
+ * AD1882 / AD1882A
+ *
+ * port-A - front hp-out
+ * port-B - front mic-in
+ * port-C - rear line-in, shared surr-out (3stack)
+ * port-D - rear line-out
+ * port-E - rear mic-in, shared clfe-out (3stack)
+ * port-F - rear surr-out (6stack)
+ * port-G - rear clfe-out (6stack)
+ */
+
+static hda_nid_t ad1882_dac_nids[3] = {
+	0x04, 0x03, 0x05
+};
+
+static hda_nid_t ad1882_adc_nids[2] = {
+	0x08, 0x09,
+};
+
+static hda_nid_t ad1882_capsrc_nids[2] = {
+	0x0c, 0x0d,
+};
+
+#define AD1882_SPDIF_OUT	0x02
+
+/* list: 0x11, 0x39, 0x3a, 0x18, 0x3c, 0x3b, 0x12, 0x20 */
+static struct hda_input_mux ad1882_capture_source = {
+	.num_items = 5,
+	.items = {
+		{ "Front Mic", 0x1 },
+		{ "Mic", 0x4 },
+		{ "Line", 0x2 },
+		{ "CD", 0x3 },
+		{ "Mix", 0x7 },
+	},
+};
+
+/* list: 0x11, 0x39, 0x3a, 0x3c, 0x18, 0x1f, 0x12, 0x20 */
+static struct hda_input_mux ad1882a_capture_source = {
+	.num_items = 5,
+	.items = {
+		{ "Front Mic", 0x1 },
+		{ "Mic", 0x4},
+		{ "Line", 0x2 },
+		{ "Digital Mic", 0x06 },
+		{ "Mix", 0x7 },
+	},
+};
+
+static struct snd_kcontrol_new ad1882_base_mixers[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x04, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x05, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x05, 2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x13, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x13, 1, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_VOLUME("Mic Boost", 0x3c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x39, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line-In Boost", 0x3a, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 2,
+		.info = ad198x_mux_enum_info,
+		.get = ad198x_mux_enum_get,
+		.put = ad198x_mux_enum_put,
+	},
+	/* SPDIF controls */
+	HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+		/* identical with ad1983 */
+		.info = ad1983_spdif_route_info,
+		.get = ad1983_spdif_route_get,
+		.put = ad1983_spdif_route_put,
+	},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1882_loopback_mixers[] = {
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x06, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x06, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1882a_loopback_mixers[] = {
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x06, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x06, HDA_INPUT),
+	HDA_CODEC_VOLUME("Digital Mic Boost", 0x1f, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1882_3stack_mixers[] = {
+	HDA_CODEC_MUTE("Surround Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x17, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x17, 2, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = ad198x_ch_mode_info,
+		.get = ad198x_ch_mode_get,
+		.put = ad198x_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new ad1882_6stack_mixers[] = {
+	HDA_CODEC_MUTE("Surround Playback Switch", 0x16, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x24, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x24, 2, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct hda_verb ad1882_ch2_init[] = {
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{ } /* end */
+};
+
+static struct hda_verb ad1882_ch4_init[] = {
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{ } /* end */
+};
+
+static struct hda_verb ad1882_ch6_init[] = {
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{ } /* end */
+};
+
+static struct hda_channel_mode ad1882_modes[3] = {
+	{ 2, ad1882_ch2_init },
+	{ 4, ad1882_ch4_init },
+	{ 6, ad1882_ch6_init },
+};
+
+/*
+ * initialization verbs
+ */
+static struct hda_verb ad1882_init_verbs[] = {
+	/* DACs; mute as default */
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* Port-A (HP) mixer */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Port-A pin */
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* HP selector - select DAC2 */
+	{0x37, AC_VERB_SET_CONNECT_SEL, 0x1},
+	/* Port-D (Line-out) mixer */
+	{0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Port-D pin */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Mono-out mixer */
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Mono-out pin */
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Port-B (front mic) pin */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x39, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, /* boost */
+	/* Port-C (line-in) pin */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x3a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, /* boost */
+	/* Port-C mixer - mute as input */
+	{0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Port-E (mic-in) pin */
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x3c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, /* boost */
+	/* Port-E mixer - mute as input */
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Port-F (surround) */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Port-G (CLFE) */
+	{0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Analog mixer; mute as default */
+	/* list: 0x39, 0x3a, 0x11, 0x12, 0x3c, 0x3b, 0x18, 0x1a */
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)},
+	/* Analog Mix output amp */
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */
+	/* SPDIF output selector */
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */
+	{0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, /* PCM */
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */
+	{ } /* end */
+};
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list ad1882_loopbacks[] = {
+	{ 0x20, HDA_INPUT, 0 }, /* Front Mic */
+	{ 0x20, HDA_INPUT, 1 }, /* Mic */
+	{ 0x20, HDA_INPUT, 4 }, /* Line */
+	{ 0x20, HDA_INPUT, 6 }, /* CD */
+	{ } /* end */
+};
+#endif
+
+/* models */
+enum {
+	AD1882_3STACK,
+	AD1882_6STACK,
+	AD1882_MODELS
+};
+
+static const char *ad1882_models[AD1986A_MODELS] = {
+	[AD1882_3STACK]		= "3stack",
+	[AD1882_6STACK]		= "6stack",
+};
+
+
+static int patch_ad1882(struct hda_codec *codec)
+{
+	struct ad198x_spec *spec;
+	int err, board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	err = snd_hda_attach_beep_device(codec, 0x10);
+	if (err < 0) {
+		ad198x_free(codec);
+		return err;
+	}
+	set_beep_amp(spec, 0x10, 0, HDA_OUTPUT);
+
+	spec->multiout.max_channels = 6;
+	spec->multiout.num_dacs = 3;
+	spec->multiout.dac_nids = ad1882_dac_nids;
+	spec->multiout.dig_out_nid = AD1882_SPDIF_OUT;
+	spec->num_adc_nids = ARRAY_SIZE(ad1882_adc_nids);
+	spec->adc_nids = ad1882_adc_nids;
+	spec->capsrc_nids = ad1882_capsrc_nids;
+	if (codec->vendor_id == 0x11d41882)
+		spec->input_mux = &ad1882_capture_source;
+	else
+		spec->input_mux = &ad1882a_capture_source;
+	spec->num_mixers = 2;
+	spec->mixers[0] = ad1882_base_mixers;
+	if (codec->vendor_id == 0x11d41882)
+		spec->mixers[1] = ad1882_loopback_mixers;
+	else
+		spec->mixers[1] = ad1882a_loopback_mixers;
+	spec->num_init_verbs = 1;
+	spec->init_verbs[0] = ad1882_init_verbs;
+	spec->spdif_route = 0;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = ad1882_loopbacks;
+#endif
+	spec->vmaster_nid = 0x04;
+
+	codec->patch_ops = ad198x_patch_ops;
+
+	/* override some parameters */
+	board_config = snd_hda_check_board_config(codec, AD1882_MODELS,
+						  ad1882_models, NULL);
+	switch (board_config) {
+	default:
+	case AD1882_3STACK:
+		spec->num_mixers = 3;
+		spec->mixers[2] = ad1882_3stack_mixers;
+		spec->channel_mode = ad1882_modes;
+		spec->num_channel_mode = ARRAY_SIZE(ad1882_modes);
+		spec->need_dac_fix = 1;
+		spec->multiout.max_channels = 2;
+		spec->multiout.num_dacs = 1;
+		break;
+	case AD1882_6STACK:
+		spec->num_mixers = 3;
+		spec->mixers[2] = ad1882_6stack_mixers;
+		break;
+	}
+
+	codec->no_trigger_sense = 1;
+	codec->no_sticky_stream = 1;
+
+	return 0;
+}
+
+
+/*
+ * patch entries
+ */
+static struct hda_codec_preset snd_hda_preset_analog[] = {
+	{ .id = 0x11d4184a, .name = "AD1884A", .patch = patch_ad1884a },
+	{ .id = 0x11d41882, .name = "AD1882", .patch = patch_ad1882 },
+	{ .id = 0x11d41883, .name = "AD1883", .patch = patch_ad1884a },
+	{ .id = 0x11d41884, .name = "AD1884", .patch = patch_ad1884 },
+	{ .id = 0x11d4194a, .name = "AD1984A", .patch = patch_ad1884a },
+	{ .id = 0x11d4194b, .name = "AD1984B", .patch = patch_ad1884a },
+	{ .id = 0x11d41981, .name = "AD1981", .patch = patch_ad1981 },
+	{ .id = 0x11d41983, .name = "AD1983", .patch = patch_ad1983 },
+	{ .id = 0x11d41984, .name = "AD1984", .patch = patch_ad1984 },
+	{ .id = 0x11d41986, .name = "AD1986A", .patch = patch_ad1986a },
+	{ .id = 0x11d41988, .name = "AD1988", .patch = patch_ad1988 },
+	{ .id = 0x11d4198b, .name = "AD1988B", .patch = patch_ad1988 },
+	{ .id = 0x11d4882a, .name = "AD1882A", .patch = patch_ad1882 },
+	{ .id = 0x11d4989a, .name = "AD1989A", .patch = patch_ad1988 },
+	{ .id = 0x11d4989b, .name = "AD1989B", .patch = patch_ad1988 },
+	{} /* terminator */
+};
+
+MODULE_ALIAS("snd-hda-codec-id:11d4*");
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Analog Devices HD-audio codec");
+
+static struct hda_codec_preset_list analog_list = {
+	.preset = snd_hda_preset_analog,
+	.owner = THIS_MODULE,
+};
+
+static int __init patch_analog_init(void)
+{
+	return snd_hda_add_codec_preset(&analog_list);
+}
+
+static void __exit patch_analog_exit(void)
+{
+	snd_hda_delete_codec_preset(&analog_list);
+}
+
+module_init(patch_analog_init)
+module_exit(patch_analog_exit)
diff --git a/linux/sound/pci/hda/patch_ca0110.c b/linux/sound/pci/hda/patch_ca0110.c
new file mode 100644
index 0000000..46c8bf4
--- /dev/null
+++ b/linux/sound/pci/hda/patch_ca0110.c
@@ -0,0 +1,572 @@
+/*
+ * HD audio interface patch for Creative X-Fi CA0110-IBG chip
+ *
+ * Copyright (c) 2008 Takashi Iwai <tiwai@suse.de>
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+/*
+ */
+
+struct ca0110_spec {
+	struct auto_pin_cfg autocfg;
+	struct hda_multi_out multiout;
+	hda_nid_t out_pins[AUTO_CFG_MAX_OUTS];
+	hda_nid_t dacs[AUTO_CFG_MAX_OUTS];
+	hda_nid_t hp_dac;
+	hda_nid_t input_pins[AUTO_PIN_LAST];
+	hda_nid_t adcs[AUTO_PIN_LAST];
+	hda_nid_t dig_out;
+	hda_nid_t dig_in;
+	unsigned int num_inputs;
+	const char *input_labels[AUTO_PIN_LAST];
+	struct hda_pcm pcm_rec[2];	/* PCM information */
+};
+
+/*
+ * PCM callbacks
+ */
+static int ca0110_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				    struct hda_codec *codec,
+				    struct snd_pcm_substream *substream)
+{
+	struct ca0110_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
+					     hinfo);
+}
+
+static int ca0110_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       unsigned int stream_tag,
+				       unsigned int format,
+				       struct snd_pcm_substream *substream)
+{
+	struct ca0110_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_prepare(codec, &spec->multiout,
+						stream_tag, format, substream);
+}
+
+static int ca0110_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       struct snd_pcm_substream *substream)
+{
+	struct ca0110_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Digital out
+ */
+static int ca0110_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+					struct hda_codec *codec,
+					struct snd_pcm_substream *substream)
+{
+	struct ca0110_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int ca0110_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+					 struct hda_codec *codec,
+					 struct snd_pcm_substream *substream)
+{
+	struct ca0110_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int ca0110_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					   struct hda_codec *codec,
+					   unsigned int stream_tag,
+					   unsigned int format,
+					   struct snd_pcm_substream *substream)
+{
+	struct ca0110_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag,
+					     format, substream);
+}
+
+/*
+ * Analog capture
+ */
+static int ca0110_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      unsigned int stream_tag,
+				      unsigned int format,
+				      struct snd_pcm_substream *substream)
+{
+	struct ca0110_spec *spec = codec->spec;
+
+	snd_hda_codec_setup_stream(codec, spec->adcs[substream->number],
+				   stream_tag, 0, format);
+	return 0;
+}
+
+static int ca0110_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      struct snd_pcm_substream *substream)
+{
+	struct ca0110_spec *spec = codec->spec;
+
+	snd_hda_codec_cleanup_stream(codec, spec->adcs[substream->number]);
+	return 0;
+}
+
+/*
+ */
+
+static char *dirstr[2] = { "Playback", "Capture" };
+
+static int _add_switch(struct hda_codec *codec, hda_nid_t nid, const char *pfx,
+		       int chan, int dir)
+{
+	char namestr[44];
+	int type = dir ? HDA_INPUT : HDA_OUTPUT;
+	struct snd_kcontrol_new knew =
+		HDA_CODEC_MUTE_MONO(namestr, nid, chan, 0, type);
+	sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]);
+	return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec));
+}
+
+static int _add_volume(struct hda_codec *codec, hda_nid_t nid, const char *pfx,
+		       int chan, int dir)
+{
+	char namestr[44];
+	int type = dir ? HDA_INPUT : HDA_OUTPUT;
+	struct snd_kcontrol_new knew =
+		HDA_CODEC_VOLUME_MONO(namestr, nid, chan, 0, type);
+	sprintf(namestr, "%s %s Volume", pfx, dirstr[dir]);
+	return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec));
+}
+
+#define add_out_switch(codec, nid, pfx)	_add_switch(codec, nid, pfx, 3, 0)
+#define add_out_volume(codec, nid, pfx)	_add_volume(codec, nid, pfx, 3, 0)
+#define add_in_switch(codec, nid, pfx)	_add_switch(codec, nid, pfx, 3, 1)
+#define add_in_volume(codec, nid, pfx)	_add_volume(codec, nid, pfx, 3, 1)
+#define add_mono_switch(codec, nid, pfx, chan) \
+	_add_switch(codec, nid, pfx, chan, 0)
+#define add_mono_volume(codec, nid, pfx, chan) \
+	_add_volume(codec, nid, pfx, chan, 0)
+
+static int ca0110_build_controls(struct hda_codec *codec)
+{
+	struct ca0110_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	static char *prefix[AUTO_CFG_MAX_OUTS] = {
+		"Front", "Surround", NULL, "Side", "Multi"
+	};
+	hda_nid_t mutenid;
+	int i, err;
+
+	for (i = 0; i < spec->multiout.num_dacs; i++) {
+		if (get_wcaps(codec, spec->out_pins[i]) & AC_WCAP_OUT_AMP)
+			mutenid = spec->out_pins[i];
+		else
+			mutenid = spec->multiout.dac_nids[i];
+		if (!prefix[i]) {
+			err = add_mono_switch(codec, mutenid,
+					      "Center", 1);
+			if (err < 0)
+				return err;
+			err = add_mono_switch(codec, mutenid,
+					      "LFE", 1);
+			if (err < 0)
+				return err;
+			err = add_mono_volume(codec, spec->multiout.dac_nids[i],
+					      "Center", 1);
+			if (err < 0)
+				return err;
+			err = add_mono_volume(codec, spec->multiout.dac_nids[i],
+					      "LFE", 1);
+			if (err < 0)
+				return err;
+		} else {
+			err = add_out_switch(codec, mutenid,
+					     prefix[i]);
+			if (err < 0)
+				return err;
+			err = add_out_volume(codec, spec->multiout.dac_nids[i],
+					 prefix[i]);
+			if (err < 0)
+				return err;
+		}
+	}
+	if (cfg->hp_outs) {
+		if (get_wcaps(codec, cfg->hp_pins[0]) & AC_WCAP_OUT_AMP)
+			mutenid = cfg->hp_pins[0];
+		else
+			mutenid = spec->multiout.dac_nids[i];
+
+		err = add_out_switch(codec, mutenid, "Headphone");
+		if (err < 0)
+			return err;
+		if (spec->hp_dac) {
+			err = add_out_volume(codec, spec->hp_dac, "Headphone");
+			if (err < 0)
+				return err;
+		}
+	}
+	for (i = 0; i < spec->num_inputs; i++) {
+		const char *label = spec->input_labels[i];
+		if (get_wcaps(codec, spec->input_pins[i]) & AC_WCAP_IN_AMP)
+			mutenid = spec->input_pins[i];
+		else
+			mutenid = spec->adcs[i];
+		err = add_in_switch(codec, mutenid, label);
+		if (err < 0)
+			return err;
+		err = add_in_volume(codec, spec->adcs[i], label);
+		if (err < 0)
+			return err;
+	}
+
+	if (spec->dig_out) {
+		err = snd_hda_create_spdif_out_ctls(codec, spec->dig_out);
+		if (err < 0)
+			return err;
+		err = snd_hda_create_spdif_share_sw(codec, &spec->multiout);
+		if (err < 0)
+			return err;
+		spec->multiout.share_spdif = 1;
+	}
+	if (spec->dig_in) {
+		err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in);
+		if (err < 0)
+			return err;
+		err = add_in_volume(codec, spec->dig_in, "IEC958");
+	}
+	return 0;
+}
+
+/*
+ */
+static struct hda_pcm_stream ca0110_pcm_analog_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 8,
+	.ops = {
+		.open = ca0110_playback_pcm_open,
+		.prepare = ca0110_playback_pcm_prepare,
+		.cleanup = ca0110_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream ca0110_pcm_analog_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.ops = {
+		.prepare = ca0110_capture_pcm_prepare,
+		.cleanup = ca0110_capture_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream ca0110_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.ops = {
+		.open = ca0110_dig_playback_pcm_open,
+		.close = ca0110_dig_playback_pcm_close,
+		.prepare = ca0110_dig_playback_pcm_prepare
+	},
+};
+
+static struct hda_pcm_stream ca0110_pcm_digital_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+};
+
+static int ca0110_build_pcms(struct hda_codec *codec)
+{
+	struct ca0110_spec *spec = codec->spec;
+	struct hda_pcm *info = spec->pcm_rec;
+
+	codec->pcm_info = info;
+	codec->num_pcms = 0;
+
+	info->name = "CA0110 Analog";
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ca0110_pcm_analog_playback;
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dacs[0];
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
+		spec->multiout.max_channels;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0110_pcm_analog_capture;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_inputs;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[0];
+	codec->num_pcms++;
+
+	if (!spec->dig_out && !spec->dig_in)
+		return 0;
+
+	info++;
+	info->name = "CA0110 Digital";
+	info->pcm_type = HDA_PCM_TYPE_SPDIF;
+	if (spec->dig_out) {
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
+			ca0110_pcm_digital_playback;
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dig_out;
+	}
+	if (spec->dig_in) {
+		info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+			ca0110_pcm_digital_capture;
+		info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in;
+	}
+	codec->num_pcms++;
+
+	return 0;
+}
+
+static void init_output(struct hda_codec *codec, hda_nid_t pin, hda_nid_t dac)
+{
+	if (pin) {
+		snd_hda_codec_write(codec, pin, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP);
+		if (get_wcaps(codec, pin) & AC_WCAP_OUT_AMP)
+			snd_hda_codec_write(codec, pin, 0,
+					    AC_VERB_SET_AMP_GAIN_MUTE,
+					    AMP_OUT_UNMUTE);
+	}
+	if (dac)
+		snd_hda_codec_write(codec, dac, 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO);
+}
+
+static void init_input(struct hda_codec *codec, hda_nid_t pin, hda_nid_t adc)
+{
+	if (pin) {
+		snd_hda_codec_write(codec, pin, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80);
+		if (get_wcaps(codec, pin) & AC_WCAP_IN_AMP)
+			snd_hda_codec_write(codec, pin, 0,
+					    AC_VERB_SET_AMP_GAIN_MUTE,
+					    AMP_IN_UNMUTE(0));
+	}
+	if (adc)
+		snd_hda_codec_write(codec, adc, 0, AC_VERB_SET_AMP_GAIN_MUTE,
+				    AMP_IN_UNMUTE(0));
+}
+
+static int ca0110_init(struct hda_codec *codec)
+{
+	struct ca0110_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	for (i = 0; i < spec->multiout.num_dacs; i++)
+		init_output(codec, spec->out_pins[i],
+			    spec->multiout.dac_nids[i]);
+	init_output(codec, cfg->hp_pins[0], spec->hp_dac);
+	init_output(codec, cfg->dig_out_pins[0], spec->dig_out);
+
+	for (i = 0; i < spec->num_inputs; i++)
+		init_input(codec, spec->input_pins[i], spec->adcs[i]);
+	init_input(codec, cfg->dig_in_pin, spec->dig_in);
+	return 0;
+}
+
+static void ca0110_free(struct hda_codec *codec)
+{
+	kfree(codec->spec);
+}
+
+static struct hda_codec_ops ca0110_patch_ops = {
+	.build_controls = ca0110_build_controls,
+	.build_pcms = ca0110_build_pcms,
+	.init = ca0110_init,
+	.free = ca0110_free,
+};
+
+
+static void parse_line_outs(struct hda_codec *codec)
+{
+	struct ca0110_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i, n;
+	unsigned int def_conf;
+	hda_nid_t nid;
+
+	n = 0;
+	for (i = 0; i < cfg->line_outs; i++) {
+		nid = cfg->line_out_pins[i];
+		def_conf = snd_hda_codec_get_pincfg(codec, nid);
+		if (!def_conf)
+			continue; /* invalid pin */
+		if (snd_hda_get_connections(codec, nid, &spec->dacs[i], 1) != 1)
+			continue;
+		spec->out_pins[n++] = nid;
+	}
+	spec->multiout.dac_nids = spec->dacs;
+	spec->multiout.num_dacs = n;
+	spec->multiout.max_channels = n * 2;
+}
+
+static void parse_hp_out(struct hda_codec *codec)
+{
+	struct ca0110_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+	unsigned int def_conf;
+	hda_nid_t nid, dac;
+
+	if (!cfg->hp_outs)
+		return;
+	nid = cfg->hp_pins[0];
+	def_conf = snd_hda_codec_get_pincfg(codec, nid);
+	if (!def_conf) {
+		cfg->hp_outs = 0;
+		return;
+	}
+	if (snd_hda_get_connections(codec, nid, &dac, 1) != 1)
+		return;
+
+	for (i = 0; i < cfg->line_outs; i++)
+		if (dac == spec->dacs[i])
+			break;
+	if (i >= cfg->line_outs) {
+		spec->hp_dac = dac;
+		spec->multiout.hp_nid = dac;
+	}
+}
+
+static void parse_input(struct hda_codec *codec)
+{
+	struct ca0110_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t nid, pin;
+	int n, i, j;
+
+	n = 0;
+	nid = codec->start_nid;
+	for (i = 0; i < codec->num_nodes; i++, nid++) {
+		unsigned int wcaps = get_wcaps(codec, nid);
+		unsigned int type = get_wcaps_type(wcaps);
+		if (type != AC_WID_AUD_IN)
+			continue;
+		if (snd_hda_get_connections(codec, nid, &pin, 1) != 1)
+			continue;
+		if (pin == cfg->dig_in_pin) {
+			spec->dig_in = nid;
+			continue;
+		}
+		for (j = 0; j < cfg->num_inputs; j++)
+			if (cfg->inputs[j].pin == pin)
+				break;
+		if (j >= cfg->num_inputs)
+			continue;
+		spec->input_pins[n] = pin;
+		spec->input_labels[n] = hda_get_input_pin_label(codec, pin, 1);
+		spec->adcs[n] = nid;
+		n++;
+	}
+	spec->num_inputs = n;
+}
+
+static void parse_digital(struct hda_codec *codec)
+{
+	struct ca0110_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+
+	if (cfg->dig_outs &&
+	    snd_hda_get_connections(codec, cfg->dig_out_pins[0],
+				    &spec->dig_out, 1) == 1)
+		spec->multiout.dig_out_nid = spec->dig_out;
+}
+
+static int ca0110_parse_auto_config(struct hda_codec *codec)
+{
+	struct ca0110_spec *spec = codec->spec;
+	int err;
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+	if (err < 0)
+		return err;
+
+	parse_line_outs(codec);
+	parse_hp_out(codec);
+	parse_digital(codec);
+	parse_input(codec);
+	return 0;
+}
+
+
+static int patch_ca0110(struct hda_codec *codec)
+{
+	struct ca0110_spec *spec;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	codec->spec = spec;
+
+	codec->bus->needs_damn_long_delay = 1;
+
+	err = ca0110_parse_auto_config(codec);
+	if (err < 0)
+		goto error;
+
+	codec->patch_ops = ca0110_patch_ops;
+
+	return 0;
+
+ error:
+	kfree(codec->spec);
+	codec->spec = NULL;
+	return err;
+}
+
+
+/*
+ * patch entries
+ */
+static struct hda_codec_preset snd_hda_preset_ca0110[] = {
+	{ .id = 0x1102000a, .name = "CA0110-IBG", .patch = patch_ca0110 },
+	{ .id = 0x1102000b, .name = "CA0110-IBG", .patch = patch_ca0110 },
+	{ .id = 0x1102000d, .name = "SB0880 X-Fi", .patch = patch_ca0110 },
+	{} /* terminator */
+};
+
+MODULE_ALIAS("snd-hda-codec-id:1102000a");
+MODULE_ALIAS("snd-hda-codec-id:1102000b");
+MODULE_ALIAS("snd-hda-codec-id:1102000d");
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Creative CA0110-IBG HD-audio codec");
+
+static struct hda_codec_preset_list ca0110_list = {
+	.preset = snd_hda_preset_ca0110,
+	.owner = THIS_MODULE,
+};
+
+static int __init patch_ca0110_init(void)
+{
+	return snd_hda_add_codec_preset(&ca0110_list);
+}
+
+static void __exit patch_ca0110_exit(void)
+{
+	snd_hda_delete_codec_preset(&ca0110_list);
+}
+
+module_init(patch_ca0110_init)
+module_exit(patch_ca0110_exit)
diff --git a/linux/sound/pci/hda/patch_cirrus.c b/linux/sound/pci/hda/patch_cirrus.c
new file mode 100644
index 0000000..18af38e
--- /dev/null
+++ b/linux/sound/pci/hda/patch_cirrus.c
@@ -0,0 +1,1312 @@
+/*
+ * HD audio interface patch for Cirrus Logic CS420x chip
+ *
+ * Copyright (c) 2009 Takashi Iwai <tiwai@suse.de>
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+/*
+ */
+
+struct cs_spec {
+	int board_config;
+	struct auto_pin_cfg autocfg;
+	struct hda_multi_out multiout;
+	struct snd_kcontrol *vmaster_sw;
+	struct snd_kcontrol *vmaster_vol;
+
+	hda_nid_t dac_nid[AUTO_CFG_MAX_OUTS];
+	hda_nid_t slave_dig_outs[2];
+
+	unsigned int input_idx[AUTO_PIN_LAST];
+	unsigned int capsrc_idx[AUTO_PIN_LAST];
+	hda_nid_t adc_nid[AUTO_PIN_LAST];
+	unsigned int adc_idx[AUTO_PIN_LAST];
+	unsigned int num_inputs;
+	unsigned int cur_input;
+	unsigned int automic_idx;
+	hda_nid_t cur_adc;
+	unsigned int cur_adc_stream_tag;
+	unsigned int cur_adc_format;
+	hda_nid_t dig_in;
+
+	struct hda_bind_ctls *capture_bind[2];
+
+	unsigned int gpio_mask;
+	unsigned int gpio_dir;
+	unsigned int gpio_data;
+
+	struct hda_pcm pcm_rec[2];	/* PCM information */
+
+	unsigned int hp_detect:1;
+	unsigned int mic_detect:1;
+};
+
+/* available models */
+enum {
+	CS420X_MBP53,
+	CS420X_MBP55,
+	CS420X_IMAC27,
+	CS420X_AUTO,
+	CS420X_MODELS
+};
+
+/* Vendor-specific processing widget */
+#define CS420X_VENDOR_NID	0x11
+#define CS_DIG_OUT1_PIN_NID	0x10
+#define CS_DIG_OUT2_PIN_NID	0x15
+#define CS_DMIC1_PIN_NID	0x12
+#define CS_DMIC2_PIN_NID	0x0e
+
+/* coef indices */
+#define IDX_SPDIF_STAT		0x0000
+#define IDX_SPDIF_CTL		0x0001
+#define IDX_ADC_CFG		0x0002
+/* SZC bitmask, 4 modes below:
+ * 0 = immediate,
+ * 1 = digital immediate, analog zero-cross
+ * 2 = digtail & analog soft-ramp
+ * 3 = digital soft-ramp, analog zero-cross
+ */
+#define   CS_COEF_ADC_SZC_MASK		(3 << 0)
+#define   CS_COEF_ADC_MIC_SZC_MODE	(3 << 0) /* SZC setup for mic */
+#define   CS_COEF_ADC_LI_SZC_MODE	(3 << 0) /* SZC setup for line-in */
+/* PGA mode: 0 = differential, 1 = signle-ended */
+#define   CS_COEF_ADC_MIC_PGA_MODE	(1 << 5) /* PGA setup for mic */
+#define   CS_COEF_ADC_LI_PGA_MODE	(1 << 6) /* PGA setup for line-in */
+#define IDX_DAC_CFG		0x0003
+/* SZC bitmask, 4 modes below:
+ * 0 = Immediate
+ * 1 = zero-cross
+ * 2 = soft-ramp
+ * 3 = soft-ramp on zero-cross
+ */
+#define   CS_COEF_DAC_HP_SZC_MODE	(3 << 0) /* nid 0x02 */
+#define   CS_COEF_DAC_LO_SZC_MODE	(3 << 2) /* nid 0x03 */
+#define   CS_COEF_DAC_SPK_SZC_MODE	(3 << 4) /* nid 0x04 */
+
+#define IDX_BEEP_CFG		0x0004
+/* 0x0008 - test reg key */
+/* 0x0009 - 0x0014 -> 12 test regs */
+/* 0x0015 - visibility reg */
+
+
+static inline int cs_vendor_coef_get(struct hda_codec *codec, unsigned int idx)
+{
+	snd_hda_codec_write(codec, CS420X_VENDOR_NID, 0,
+			    AC_VERB_SET_COEF_INDEX, idx);
+	return snd_hda_codec_read(codec, CS420X_VENDOR_NID, 0,
+				  AC_VERB_GET_PROC_COEF, 0);
+}
+
+static inline void cs_vendor_coef_set(struct hda_codec *codec, unsigned int idx,
+				      unsigned int coef)
+{
+	snd_hda_codec_write(codec, CS420X_VENDOR_NID, 0,
+			    AC_VERB_SET_COEF_INDEX, idx);
+	snd_hda_codec_write(codec, CS420X_VENDOR_NID, 0,
+			    AC_VERB_SET_PROC_COEF, coef);
+}
+
+
+#define HP_EVENT	1
+#define MIC_EVENT	2
+
+/*
+ * PCM callbacks
+ */
+static int cs_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				struct hda_codec *codec,
+				struct snd_pcm_substream *substream)
+{
+	struct cs_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
+					     hinfo);
+}
+
+static int cs_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+				   struct hda_codec *codec,
+				   unsigned int stream_tag,
+				   unsigned int format,
+				   struct snd_pcm_substream *substream)
+{
+	struct cs_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_prepare(codec, &spec->multiout,
+						stream_tag, format, substream);
+}
+
+static int cs_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				   struct hda_codec *codec,
+				   struct snd_pcm_substream *substream)
+{
+	struct cs_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Digital out
+ */
+static int cs_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				    struct hda_codec *codec,
+				    struct snd_pcm_substream *substream)
+{
+	struct cs_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int cs_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+				     struct hda_codec *codec,
+				     struct snd_pcm_substream *substream)
+{
+	struct cs_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int cs_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       unsigned int stream_tag,
+				       unsigned int format,
+				       struct snd_pcm_substream *substream)
+{
+	struct cs_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag,
+					     format, substream);
+}
+
+static int cs_dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       struct snd_pcm_substream *substream)
+{
+	struct cs_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Analog capture
+ */
+static int cs_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+				  struct hda_codec *codec,
+				  unsigned int stream_tag,
+				  unsigned int format,
+				  struct snd_pcm_substream *substream)
+{
+	struct cs_spec *spec = codec->spec;
+	spec->cur_adc = spec->adc_nid[spec->cur_input];
+	spec->cur_adc_stream_tag = stream_tag;
+	spec->cur_adc_format = format;
+	snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format);
+	return 0;
+}
+
+static int cs_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				  struct hda_codec *codec,
+				  struct snd_pcm_substream *substream)
+{
+	struct cs_spec *spec = codec->spec;
+	snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
+	spec->cur_adc = 0;
+	return 0;
+}
+
+/*
+ */
+static struct hda_pcm_stream cs_pcm_analog_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.ops = {
+		.open = cs_playback_pcm_open,
+		.prepare = cs_playback_pcm_prepare,
+		.cleanup = cs_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream cs_pcm_analog_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.ops = {
+		.prepare = cs_capture_pcm_prepare,
+		.cleanup = cs_capture_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream cs_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.ops = {
+		.open = cs_dig_playback_pcm_open,
+		.close = cs_dig_playback_pcm_close,
+		.prepare = cs_dig_playback_pcm_prepare,
+		.cleanup = cs_dig_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream cs_pcm_digital_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+};
+
+static int cs_build_pcms(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	struct hda_pcm *info = spec->pcm_rec;
+
+	codec->pcm_info = info;
+	codec->num_pcms = 0;
+
+	info->name = "Cirrus Analog";
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK] = cs_pcm_analog_playback;
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dac_nid[0];
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
+		spec->multiout.max_channels;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE] = cs_pcm_analog_capture;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE].nid =
+		spec->adc_nid[spec->cur_input];
+	codec->num_pcms++;
+
+	if (!spec->multiout.dig_out_nid && !spec->dig_in)
+		return 0;
+
+	info++;
+	info->name = "Cirrus Digital";
+	info->pcm_type = spec->autocfg.dig_out_type[0];
+	if (!info->pcm_type)
+		info->pcm_type = HDA_PCM_TYPE_SPDIF;
+	if (spec->multiout.dig_out_nid) {
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
+			cs_pcm_digital_playback;
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
+			spec->multiout.dig_out_nid;
+	}
+	if (spec->dig_in) {
+		info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+			cs_pcm_digital_capture;
+		info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in;
+	}
+	codec->num_pcms++;
+
+	return 0;
+}
+
+/*
+ * parse codec topology
+ */
+
+static hda_nid_t get_dac(struct hda_codec *codec, hda_nid_t pin)
+{
+	hda_nid_t dac;
+	if (!pin)
+		return 0;
+	if (snd_hda_get_connections(codec, pin, &dac, 1) != 1)
+		return 0;
+	return dac;
+}
+
+static int is_ext_mic(struct hda_codec *codec, unsigned int idx)
+{
+	struct cs_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t pin = cfg->inputs[idx].pin;
+	unsigned int val = snd_hda_query_pin_caps(codec, pin);
+	if (!(val & AC_PINCAP_PRES_DETECT))
+		return 0;
+	val = snd_hda_codec_get_pincfg(codec, pin);
+	return (snd_hda_get_input_pin_attr(val) != INPUT_PIN_ATTR_INT);
+}
+
+static hda_nid_t get_adc(struct hda_codec *codec, hda_nid_t pin,
+			 unsigned int *idxp)
+{
+	int i;
+	hda_nid_t nid;
+
+	nid = codec->start_nid;
+	for (i = 0; i < codec->num_nodes; i++, nid++) {
+		hda_nid_t pins[2];
+		unsigned int type;
+		int j, nums;
+		type = (get_wcaps(codec, nid) & AC_WCAP_TYPE)
+			>> AC_WCAP_TYPE_SHIFT;
+		if (type != AC_WID_AUD_IN)
+			continue;
+		nums = snd_hda_get_connections(codec, nid, pins,
+					       ARRAY_SIZE(pins));
+		if (nums <= 0)
+			continue;
+		for (j = 0; j < nums; j++) {
+			if (pins[j] == pin) {
+				*idxp = j;
+				return nid;
+			}
+		}
+	}
+	return 0;
+}
+
+static int is_active_pin(struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int val;
+	val = snd_hda_codec_get_pincfg(codec, nid);
+	return (get_defcfg_connect(val) != AC_JACK_PORT_NONE);
+}
+
+static int parse_output(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i, extra_nids;
+	hda_nid_t dac;
+
+	for (i = 0; i < cfg->line_outs; i++) {
+		dac = get_dac(codec, cfg->line_out_pins[i]);
+		if (!dac)
+			break;
+		spec->dac_nid[i] = dac;
+	}
+	spec->multiout.num_dacs = i;
+	spec->multiout.dac_nids = spec->dac_nid;
+	spec->multiout.max_channels = i * 2;
+
+	/* add HP and speakers */
+	extra_nids = 0;
+	for (i = 0; i < cfg->hp_outs; i++) {
+		dac = get_dac(codec, cfg->hp_pins[i]);
+		if (!dac)
+			break;
+		if (!i)
+			spec->multiout.hp_nid = dac;
+		else
+			spec->multiout.extra_out_nid[extra_nids++] = dac;
+	}
+	for (i = 0; i < cfg->speaker_outs; i++) {
+		dac = get_dac(codec, cfg->speaker_pins[i]);
+		if (!dac)
+			break;
+		spec->multiout.extra_out_nid[extra_nids++] = dac;
+	}
+
+	if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) {
+		cfg->speaker_outs = cfg->line_outs;
+		memcpy(cfg->speaker_pins, cfg->line_out_pins,
+		       sizeof(cfg->speaker_pins));
+		cfg->line_outs = 0;
+	}
+
+	return 0;
+}
+
+static int parse_input(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t pin = cfg->inputs[i].pin;
+		spec->input_idx[spec->num_inputs] = i;
+		spec->capsrc_idx[i] = spec->num_inputs++;
+		spec->cur_input = i;
+		spec->adc_nid[i] = get_adc(codec, pin, &spec->adc_idx[i]);
+	}
+	if (!spec->num_inputs)
+		return 0;
+
+	/* check whether the automatic mic switch is available */
+	if (spec->num_inputs == 2 &&
+	    cfg->inputs[0].type == AUTO_PIN_MIC &&
+	    cfg->inputs[1].type == AUTO_PIN_MIC) {
+		if (is_ext_mic(codec, cfg->inputs[0].pin)) {
+			if (!is_ext_mic(codec, cfg->inputs[1].pin)) {
+				spec->mic_detect = 1;
+				spec->automic_idx = 0;
+			}
+		} else {
+			if (is_ext_mic(codec, cfg->inputs[1].pin)) {
+				spec->mic_detect = 1;
+				spec->automic_idx = 1;
+			}
+		}
+	}
+	return 0;
+}
+
+
+static int parse_digital_output(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t nid;
+
+	if (!cfg->dig_outs)
+		return 0;
+	if (snd_hda_get_connections(codec, cfg->dig_out_pins[0], &nid, 1) < 1)
+		return 0;
+	spec->multiout.dig_out_nid = nid;
+	spec->multiout.share_spdif = 1;
+	if (cfg->dig_outs > 1 &&
+	    snd_hda_get_connections(codec, cfg->dig_out_pins[1], &nid, 1) > 0) {
+		spec->slave_dig_outs[0] = nid;
+		codec->slave_dig_outs = spec->slave_dig_outs;
+	}
+	return 0;
+}
+
+static int parse_digital_input(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int idx;
+
+	if (cfg->dig_in_pin)
+		spec->dig_in = get_adc(codec, cfg->dig_in_pin, &idx);
+	return 0;
+}
+
+/*
+ * create mixer controls
+ */
+
+static const char *dir_sfx[2] = { "Playback", "Capture" };
+
+static int add_mute(struct hda_codec *codec, const char *name, int index,
+		    unsigned int pval, int dir, struct snd_kcontrol **kctlp)
+{
+	char tmp[44];
+	struct snd_kcontrol_new knew =
+		HDA_CODEC_MUTE_IDX(tmp, index, 0, 0, HDA_OUTPUT);
+	knew.private_value = pval;
+	snprintf(tmp, sizeof(tmp), "%s %s Switch", name, dir_sfx[dir]);
+	*kctlp = snd_ctl_new1(&knew, codec);
+	(*kctlp)->id.subdevice = HDA_SUBDEV_AMP_FLAG;
+	return snd_hda_ctl_add(codec, 0, *kctlp);
+}
+
+static int add_volume(struct hda_codec *codec, const char *name,
+		      int index, unsigned int pval, int dir,
+		      struct snd_kcontrol **kctlp)
+{
+	char tmp[32];
+	struct snd_kcontrol_new knew =
+		HDA_CODEC_VOLUME_IDX(tmp, index, 0, 0, HDA_OUTPUT);
+	knew.private_value = pval;
+	snprintf(tmp, sizeof(tmp), "%s %s Volume", name, dir_sfx[dir]);
+	*kctlp = snd_ctl_new1(&knew, codec);
+	(*kctlp)->id.subdevice = HDA_SUBDEV_AMP_FLAG;
+	return snd_hda_ctl_add(codec, 0, *kctlp);
+}
+
+static void fix_volume_caps(struct hda_codec *codec, hda_nid_t dac)
+{
+	unsigned int caps;
+
+	/* set the upper-limit for mixer amp to 0dB */
+	caps = query_amp_caps(codec, dac, HDA_OUTPUT);
+	caps &= ~(0x7f << AC_AMPCAP_NUM_STEPS_SHIFT);
+	caps |= ((caps >> AC_AMPCAP_OFFSET_SHIFT) & 0x7f)
+		<< AC_AMPCAP_NUM_STEPS_SHIFT;
+	snd_hda_override_amp_caps(codec, dac, HDA_OUTPUT, caps);
+}
+
+static int add_vmaster(struct hda_codec *codec, hda_nid_t dac)
+{
+	struct cs_spec *spec = codec->spec;
+	unsigned int tlv[4];
+	int err;
+
+	spec->vmaster_sw =
+		snd_ctl_make_virtual_master("Master Playback Switch", NULL);
+	err = snd_hda_ctl_add(codec, dac, spec->vmaster_sw);
+	if (err < 0)
+		return err;
+
+	snd_hda_set_vmaster_tlv(codec, dac, HDA_OUTPUT, tlv);
+	spec->vmaster_vol =
+		snd_ctl_make_virtual_master("Master Playback Volume", tlv);
+	err = snd_hda_ctl_add(codec, dac, spec->vmaster_vol);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int add_output(struct hda_codec *codec, hda_nid_t dac, int idx,
+		      int num_ctls, int type)
+{
+	struct cs_spec *spec = codec->spec;
+	const char *name;
+	int err, index;
+	struct snd_kcontrol *kctl;
+	static char *speakers[] = {
+		"Front Speaker", "Surround Speaker", "Bass Speaker"
+	};
+	static char *line_outs[] = {
+		"Front Line-Out", "Surround Line-Out", "Bass Line-Out"
+	};
+
+	fix_volume_caps(codec, dac);
+	if (!spec->vmaster_sw) {
+		err = add_vmaster(codec, dac);
+		if (err < 0)
+			return err;
+	}
+
+	index = 0;
+	switch (type) {
+	case AUTO_PIN_HP_OUT:
+		name = "Headphone";
+		index = idx;
+		break;
+	case AUTO_PIN_SPEAKER_OUT:
+		if (num_ctls > 1)
+			name = speakers[idx];
+		else
+			name = "Speaker";
+		break;
+	default:
+		if (num_ctls > 1)
+			name = line_outs[idx];
+		else
+			name = "Line-Out";
+		break;
+	}
+
+	err = add_mute(codec, name, index,
+		       HDA_COMPOSE_AMP_VAL(dac, 3, 0, HDA_OUTPUT), 0, &kctl);
+	if (err < 0)
+		return err;
+	err = snd_ctl_add_slave(spec->vmaster_sw, kctl);
+	if (err < 0)
+		return err;
+
+	err = add_volume(codec, name, index,
+			 HDA_COMPOSE_AMP_VAL(dac, 3, 0, HDA_OUTPUT), 0, &kctl);
+	if (err < 0)
+		return err;
+	err = snd_ctl_add_slave(spec->vmaster_vol, kctl);
+	if (err < 0)
+		return err;
+
+	return 0;
+}		
+
+static int build_output(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i, err;
+
+	for (i = 0; i < cfg->line_outs; i++) {
+		err = add_output(codec, get_dac(codec, cfg->line_out_pins[i]),
+				 i, cfg->line_outs, cfg->line_out_type);
+		if (err < 0)
+			return err;
+	}
+	for (i = 0; i < cfg->hp_outs; i++) {
+		err = add_output(codec, get_dac(codec, cfg->hp_pins[i]),
+				 i, cfg->hp_outs, AUTO_PIN_HP_OUT);
+		if (err < 0)
+			return err;
+	}
+	for (i = 0; i < cfg->speaker_outs; i++) {
+		err = add_output(codec, get_dac(codec, cfg->speaker_pins[i]),
+				 i, cfg->speaker_outs, AUTO_PIN_SPEAKER_OUT);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+/*
+ */
+
+static struct snd_kcontrol_new cs_capture_ctls[] = {
+	HDA_BIND_SW("Capture Switch", 0),
+	HDA_BIND_VOL("Capture Volume", 0),
+};
+
+static int change_cur_input(struct hda_codec *codec, unsigned int idx,
+			    int force)
+{
+	struct cs_spec *spec = codec->spec;
+	
+	if (spec->cur_input == idx && !force)
+		return 0;
+	if (spec->cur_adc && spec->cur_adc != spec->adc_nid[idx]) {
+		/* stream is running, let's swap the current ADC */
+		__snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1);
+		spec->cur_adc = spec->adc_nid[idx];
+		snd_hda_codec_setup_stream(codec, spec->cur_adc,
+					   spec->cur_adc_stream_tag, 0,
+					   spec->cur_adc_format);
+	}
+	snd_hda_codec_write(codec, spec->cur_adc, 0,
+			    AC_VERB_SET_CONNECT_SEL,
+			    spec->adc_idx[idx]);
+	spec->cur_input = idx;
+	return 1;
+}
+
+static int cs_capture_source_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct cs_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	unsigned int idx;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = spec->num_inputs;
+	if (uinfo->value.enumerated.item >= spec->num_inputs)
+		uinfo->value.enumerated.item = spec->num_inputs - 1;
+	idx = spec->input_idx[uinfo->value.enumerated.item];
+	strcpy(uinfo->value.enumerated.name,
+	       hda_get_input_pin_label(codec, cfg->inputs[idx].pin, 1));
+	return 0;
+}
+
+static int cs_capture_source_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct cs_spec *spec = codec->spec;
+	ucontrol->value.enumerated.item[0] = spec->capsrc_idx[spec->cur_input];
+	return 0;
+}
+
+static int cs_capture_source_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct cs_spec *spec = codec->spec;
+	unsigned int idx = ucontrol->value.enumerated.item[0];
+
+	if (idx >= spec->num_inputs)
+		return -EINVAL;
+	idx = spec->input_idx[idx];
+	return change_cur_input(codec, idx, 0);
+}
+
+static struct snd_kcontrol_new cs_capture_source = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Capture Source",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = cs_capture_source_info,
+	.get = cs_capture_source_get,
+	.put = cs_capture_source_put,
+};
+
+static struct hda_bind_ctls *make_bind_capture(struct hda_codec *codec,
+					       struct hda_ctl_ops *ops)
+{
+	struct cs_spec *spec = codec->spec;
+	struct hda_bind_ctls *bind;
+	int i, n;
+
+	bind = kzalloc(sizeof(*bind) + sizeof(long) * (spec->num_inputs + 1),
+		       GFP_KERNEL);
+	if (!bind)
+		return NULL;
+	bind->ops = ops;
+	n = 0;
+	for (i = 0; i < AUTO_PIN_LAST; i++) {
+		if (!spec->adc_nid[i])
+			continue;
+		bind->values[n++] =
+			HDA_COMPOSE_AMP_VAL(spec->adc_nid[i], 3,
+					    spec->adc_idx[i], HDA_INPUT);
+	}
+	return bind;
+}
+
+/* add a (input-boost) volume control to the given input pin */
+static int add_input_volume_control(struct hda_codec *codec,
+				    struct auto_pin_cfg *cfg,
+				    int item)
+{
+	hda_nid_t pin = cfg->inputs[item].pin;
+	u32 caps;
+	const char *label;
+	struct snd_kcontrol *kctl;
+		
+	if (!(get_wcaps(codec, pin) & AC_WCAP_IN_AMP))
+		return 0;
+	caps = query_amp_caps(codec, pin, HDA_INPUT);
+	caps = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+	if (caps <= 1)
+		return 0;
+	label = hda_get_autocfg_input_label(codec, cfg, item);
+	return add_volume(codec, label, 0,
+			  HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_INPUT), 1, &kctl);
+}
+
+static int build_input(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	int i, err;
+
+	if (!spec->num_inputs)
+		return 0;
+
+	/* make bind-capture */
+	spec->capture_bind[0] = make_bind_capture(codec, &snd_hda_bind_sw);
+	spec->capture_bind[1] = make_bind_capture(codec, &snd_hda_bind_vol);
+	for (i = 0; i < 2; i++) {
+		struct snd_kcontrol *kctl;
+		int n;
+		if (!spec->capture_bind[i])
+			return -ENOMEM;
+		kctl = snd_ctl_new1(&cs_capture_ctls[i], codec);
+		if (!kctl)
+			return -ENOMEM;
+		kctl->private_value = (long)spec->capture_bind[i];
+		err = snd_hda_ctl_add(codec, 0, kctl);
+		if (err < 0)
+			return err;
+		for (n = 0; n < AUTO_PIN_LAST; n++) {
+			if (!spec->adc_nid[n])
+				continue;
+			err = snd_hda_add_nid(codec, kctl, 0, spec->adc_nid[n]);
+			if (err < 0)
+				return err;
+		}
+	}
+	
+	if (spec->num_inputs > 1 && !spec->mic_detect) {
+		err = snd_hda_ctl_add(codec, 0,
+				      snd_ctl_new1(&cs_capture_source, codec));
+		if (err < 0)
+			return err;
+	}
+
+	for (i = 0; i < spec->num_inputs; i++) {
+		err = add_input_volume_control(codec, &spec->autocfg, i);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+/*
+ */
+
+static int build_digital_output(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	int err;
+
+	if (!spec->multiout.dig_out_nid)
+		return 0;
+
+	err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid);
+	if (err < 0)
+		return err;
+	err = snd_hda_create_spdif_share_sw(codec, &spec->multiout);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int build_digital_input(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	if (spec->dig_in)
+		return snd_hda_create_spdif_in_ctls(codec, spec->dig_in);
+	return 0;
+}
+
+/*
+ * auto-mute and auto-mic switching
+ */
+
+static void cs_automute(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	unsigned int caps, hp_present;
+	hda_nid_t nid;
+	int i;
+
+	hp_present = 0;
+	for (i = 0; i < cfg->hp_outs; i++) {
+		nid = cfg->hp_pins[i];
+		caps = snd_hda_query_pin_caps(codec, nid);
+		if (!(caps & AC_PINCAP_PRES_DETECT))
+			continue;
+		hp_present = snd_hda_jack_detect(codec, nid);
+		if (hp_present)
+			break;
+	}
+	for (i = 0; i < cfg->speaker_outs; i++) {
+		nid = cfg->speaker_pins[i];
+		snd_hda_codec_write(codec, nid, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL,
+				    hp_present ? 0 : PIN_OUT);
+	}
+	if (spec->board_config == CS420X_MBP53 ||
+	    spec->board_config == CS420X_MBP55 ||
+	    spec->board_config == CS420X_IMAC27) {
+		unsigned int gpio = hp_present ? 0x02 : 0x08;
+		snd_hda_codec_write(codec, 0x01, 0,
+				    AC_VERB_SET_GPIO_DATA, gpio);
+	}
+}
+
+static void cs_automic(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t nid;
+	unsigned int present;
+	
+	nid = cfg->inputs[spec->automic_idx].pin;
+	present = snd_hda_jack_detect(codec, nid);
+	if (present)
+		change_cur_input(codec, spec->automic_idx, 0);
+	else
+		change_cur_input(codec, !spec->automic_idx, 0);
+}
+
+/*
+ */
+
+static void init_output(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	/* mute first */
+	for (i = 0; i < spec->multiout.num_dacs; i++)
+		snd_hda_codec_write(codec, spec->multiout.dac_nids[i], 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+	if (spec->multiout.hp_nid)
+		snd_hda_codec_write(codec, spec->multiout.hp_nid, 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+	for (i = 0; i < ARRAY_SIZE(spec->multiout.extra_out_nid); i++) {
+		if (!spec->multiout.extra_out_nid[i])
+			break;
+		snd_hda_codec_write(codec, spec->multiout.extra_out_nid[i], 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+	}
+
+	/* set appropriate pin controls */
+	for (i = 0; i < cfg->line_outs; i++)
+		snd_hda_codec_write(codec, cfg->line_out_pins[i], 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+	for (i = 0; i < cfg->hp_outs; i++) {
+		hda_nid_t nid = cfg->hp_pins[i];
+		snd_hda_codec_write(codec, nid, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP);
+		if (!cfg->speaker_outs)
+			continue;
+		if (get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) {
+			snd_hda_codec_write(codec, nid, 0,
+					    AC_VERB_SET_UNSOLICITED_ENABLE,
+					    AC_USRSP_EN | HP_EVENT);
+			spec->hp_detect = 1;
+		}
+	}
+	for (i = 0; i < cfg->speaker_outs; i++)
+		snd_hda_codec_write(codec, cfg->speaker_pins[i], 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+	if (spec->hp_detect)
+		cs_automute(codec);
+}
+
+static void init_input(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	unsigned int coef;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		unsigned int ctl;
+		hda_nid_t pin = cfg->inputs[i].pin;
+		if (!spec->adc_nid[i])
+			continue;
+		/* set appropriate pin control and mute first */
+		ctl = PIN_IN;
+		if (cfg->inputs[i].type == AUTO_PIN_MIC) {
+			unsigned int caps = snd_hda_query_pin_caps(codec, pin);
+			caps >>= AC_PINCAP_VREF_SHIFT;
+			if (caps & AC_PINCAP_VREF_80)
+				ctl = PIN_VREF80;
+		}
+		snd_hda_codec_write(codec, pin, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, ctl);
+		snd_hda_codec_write(codec, spec->adc_nid[i], 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE,
+				    AMP_IN_MUTE(spec->adc_idx[i]));
+		if (spec->mic_detect && spec->automic_idx == i)
+			snd_hda_codec_write(codec, pin, 0,
+					    AC_VERB_SET_UNSOLICITED_ENABLE,
+					    AC_USRSP_EN | MIC_EVENT);
+	}
+	change_cur_input(codec, spec->cur_input, 1);
+	if (spec->mic_detect)
+		cs_automic(codec);
+
+	coef = 0x000a; /* ADC1/2 - Digital and Analog Soft Ramp */
+	if (is_active_pin(codec, CS_DMIC2_PIN_NID))
+		coef |= 0x0500; /* DMIC2 enable 2 channels, disable GPIO1 */
+	if (is_active_pin(codec, CS_DMIC1_PIN_NID))
+		coef |= 0x1800; /* DMIC1 enable 2 channels, disable GPIO0 
+				 * No effect if SPDIF_OUT2 is selected in 
+				 * IDX_SPDIF_CTL.
+				  */
+	cs_vendor_coef_set(codec, IDX_ADC_CFG, coef);
+}
+
+static struct hda_verb cs_coef_init_verbs[] = {
+	{0x11, AC_VERB_SET_PROC_STATE, 1},
+	{0x11, AC_VERB_SET_COEF_INDEX, IDX_DAC_CFG},
+	{0x11, AC_VERB_SET_PROC_COEF,
+	 (0x002a /* DAC1/2/3 SZCMode Soft Ramp */
+	  | 0x0040 /* Mute DACs on FIFO error */
+	  | 0x1000 /* Enable DACs High Pass Filter */
+	  | 0x0400 /* Disable Coefficient Auto increment */
+	  )},
+	/* Beep */
+	{0x11, AC_VERB_SET_COEF_INDEX, IDX_DAC_CFG},
+	{0x11, AC_VERB_SET_PROC_COEF, 0x0007}, /* Enable Beep thru DAC1/2/3 */
+
+	{} /* terminator */
+};
+
+/* Errata: CS4207 rev C0/C1/C2 Silicon
+ *
+ * http://www.cirrus.com/en/pubs/errata/ER880C3.pdf
+ *
+ * 6. At high temperature (TA > +85°C), the digital supply current (IVD)
+ * may be excessive (up to an additional 200 μA), which is most easily
+ * observed while the part is being held in reset (RESET# active low).
+ *
+ * Root Cause: At initial powerup of the device, the logic that drives
+ * the clock and write enable to the S/PDIF SRC RAMs is not properly
+ * initialized.
+ * Certain random patterns will cause a steady leakage current in those
+ * RAM cells. The issue will resolve once the SRCs are used (turned on).
+ *
+ * Workaround: The following verb sequence briefly turns on the S/PDIF SRC
+ * blocks, which will alleviate the issue.
+ */
+
+static struct hda_verb cs_errata_init_verbs[] = {
+	{0x01, AC_VERB_SET_POWER_STATE, 0x00}, /* AFG: D0 */
+	{0x11, AC_VERB_SET_PROC_STATE, 0x01},  /* VPW: processing on */
+
+	{0x11, AC_VERB_SET_COEF_INDEX, 0x0008},
+	{0x11, AC_VERB_SET_PROC_COEF, 0x9999},
+	{0x11, AC_VERB_SET_COEF_INDEX, 0x0017},
+	{0x11, AC_VERB_SET_PROC_COEF, 0xa412},
+	{0x11, AC_VERB_SET_COEF_INDEX, 0x0001},
+	{0x11, AC_VERB_SET_PROC_COEF, 0x0009},
+
+	{0x07, AC_VERB_SET_POWER_STATE, 0x00}, /* S/PDIF Rx: D0 */
+	{0x08, AC_VERB_SET_POWER_STATE, 0x00}, /* S/PDIF Tx: D0 */
+
+	{0x11, AC_VERB_SET_COEF_INDEX, 0x0017},
+	{0x11, AC_VERB_SET_PROC_COEF, 0x2412},
+	{0x11, AC_VERB_SET_COEF_INDEX, 0x0008},
+	{0x11, AC_VERB_SET_PROC_COEF, 0x0000},
+	{0x11, AC_VERB_SET_COEF_INDEX, 0x0001},
+	{0x11, AC_VERB_SET_PROC_COEF, 0x0008},
+	{0x11, AC_VERB_SET_PROC_STATE, 0x00},
+
+	{0x07, AC_VERB_SET_POWER_STATE, 0x03}, /* S/PDIF Rx: D3 */
+	{0x08, AC_VERB_SET_POWER_STATE, 0x03}, /* S/PDIF Tx: D3 */
+	/*{0x01, AC_VERB_SET_POWER_STATE, 0x03},*/ /* AFG: D3 This is already handled */
+
+	{} /* terminator */
+};
+
+/* SPDIF setup */
+static void init_digital(struct hda_codec *codec)
+{
+	unsigned int coef;
+
+	coef = 0x0002; /* SRC_MUTE soft-mute on SPDIF (if no lock) */
+	coef |= 0x0008; /* Replace with mute on error */
+	if (is_active_pin(codec, CS_DIG_OUT2_PIN_NID))
+		coef |= 0x4000; /* RX to TX1 or TX2 Loopthru / SPDIF2
+				 * SPDIF_OUT2 is shared with GPIO1 and
+				 * DMIC_SDA2.
+				 */
+	cs_vendor_coef_set(codec, IDX_SPDIF_CTL, coef);
+}
+
+static int cs_init(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+
+	/* init_verb sequence for C0/C1/C2 errata*/
+	snd_hda_sequence_write(codec, cs_errata_init_verbs);
+
+	snd_hda_sequence_write(codec, cs_coef_init_verbs);
+
+	if (spec->gpio_mask) {
+		snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK,
+				    spec->gpio_mask);
+		snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION,
+				    spec->gpio_dir);
+		snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
+				    spec->gpio_data);
+	}
+
+	init_output(codec);
+	init_input(codec);
+	init_digital(codec);
+	return 0;
+}
+
+static int cs_build_controls(struct hda_codec *codec)
+{
+	int err;
+
+	err = build_output(codec);
+	if (err < 0)
+		return err;
+	err = build_input(codec);
+	if (err < 0)
+		return err;
+	err = build_digital_output(codec);
+	if (err < 0)
+		return err;
+	err = build_digital_input(codec);
+	if (err < 0)
+		return err;
+	return cs_init(codec);
+}
+
+static void cs_free(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	kfree(spec->capture_bind[0]);
+	kfree(spec->capture_bind[1]);
+	kfree(codec->spec);
+}
+
+static void cs_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	switch ((res >> 26) & 0x7f) {
+	case HP_EVENT:
+		cs_automute(codec);
+		break;
+	case MIC_EVENT:
+		cs_automic(codec);
+		break;
+	}
+}
+
+static struct hda_codec_ops cs_patch_ops = {
+	.build_controls = cs_build_controls,
+	.build_pcms = cs_build_pcms,
+	.init = cs_init,
+	.free = cs_free,
+	.unsol_event = cs_unsol_event,
+};
+
+static int cs_parse_auto_config(struct hda_codec *codec)
+{
+	struct cs_spec *spec = codec->spec;
+	int err;
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+	if (err < 0)
+		return err;
+
+	err = parse_output(codec);
+	if (err < 0)
+		return err;
+	err = parse_input(codec);
+	if (err < 0)
+		return err;
+	err = parse_digital_output(codec);
+	if (err < 0)
+		return err;
+	err = parse_digital_input(codec);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static const char *cs420x_models[CS420X_MODELS] = {
+	[CS420X_MBP53] = "mbp53",
+	[CS420X_MBP55] = "mbp55",
+	[CS420X_IMAC27] = "imac27",
+	[CS420X_AUTO] = "auto",
+};
+
+
+static struct snd_pci_quirk cs420x_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x10de, 0x0ac0, "MacBookPro 5,3", CS420X_MBP53),
+	SND_PCI_QUIRK(0x10de, 0x0d94, "MacBookAir 3,1(2)", CS420X_MBP55),
+	SND_PCI_QUIRK(0x10de, 0xcb79, "MacBookPro 5,5", CS420X_MBP55),
+	SND_PCI_QUIRK(0x10de, 0xcb89, "MacBookPro 7,1", CS420X_MBP55),
+	SND_PCI_QUIRK(0x8086, 0x7270, "IMac 27 Inch", CS420X_IMAC27),
+	{} /* terminator */
+};
+
+struct cs_pincfg {
+	hda_nid_t nid;
+	u32 val;
+};
+
+static struct cs_pincfg mbp53_pincfgs[] = {
+	{ 0x09, 0x012b4050 },
+	{ 0x0a, 0x90100141 },
+	{ 0x0b, 0x90100140 },
+	{ 0x0c, 0x018b3020 },
+	{ 0x0d, 0x90a00110 },
+	{ 0x0e, 0x400000f0 },
+	{ 0x0f, 0x01cbe030 },
+	{ 0x10, 0x014be060 },
+	{ 0x12, 0x400000f0 },
+	{ 0x15, 0x400000f0 },
+	{} /* terminator */
+};
+
+static struct cs_pincfg mbp55_pincfgs[] = {
+	{ 0x09, 0x012b4030 },
+	{ 0x0a, 0x90100121 },
+	{ 0x0b, 0x90100120 },
+	{ 0x0c, 0x400000f0 },
+	{ 0x0d, 0x90a00110 },
+	{ 0x0e, 0x400000f0 },
+	{ 0x0f, 0x400000f0 },
+	{ 0x10, 0x014be040 },
+	{ 0x12, 0x400000f0 },
+	{ 0x15, 0x400000f0 },
+	{} /* terminator */
+};
+
+static struct cs_pincfg imac27_pincfgs[] = {
+	{ 0x09, 0x012b4050 },
+	{ 0x0a, 0x90100140 },
+	{ 0x0b, 0x90100142 },
+	{ 0x0c, 0x018b3020 },
+	{ 0x0d, 0x90a00110 },
+	{ 0x0e, 0x400000f0 },
+	{ 0x0f, 0x01cbe030 },
+	{ 0x10, 0x014be060 },
+	{ 0x12, 0x01ab9070 },
+	{ 0x15, 0x400000f0 },
+	{} /* terminator */
+};
+
+static struct cs_pincfg *cs_pincfgs[CS420X_MODELS] = {
+	[CS420X_MBP53] = mbp53_pincfgs,
+	[CS420X_MBP55] = mbp55_pincfgs,
+	[CS420X_IMAC27] = imac27_pincfgs,
+};
+
+static void fix_pincfg(struct hda_codec *codec, int model)
+{
+	const struct cs_pincfg *cfg = cs_pincfgs[model];
+	if (!cfg)
+		return;
+	for (; cfg->nid; cfg++)
+		snd_hda_codec_set_pincfg(codec, cfg->nid, cfg->val);
+}
+
+
+static int patch_cs420x(struct hda_codec *codec)
+{
+	struct cs_spec *spec;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	codec->spec = spec;
+
+	spec->board_config =
+		snd_hda_check_board_config(codec, CS420X_MODELS,
+					   cs420x_models, cs420x_cfg_tbl);
+	if (spec->board_config >= 0)
+		fix_pincfg(codec, spec->board_config);
+
+	switch (spec->board_config) {
+	case CS420X_IMAC27:
+	case CS420X_MBP53:
+	case CS420X_MBP55:
+		/* GPIO1 = headphones */
+		/* GPIO3 = speakers */
+		spec->gpio_mask = 0x0a;
+		spec->gpio_dir = 0x0a;
+		break;
+	}
+
+	err = cs_parse_auto_config(codec);
+	if (err < 0)
+		goto error;
+
+	codec->patch_ops = cs_patch_ops;
+
+	return 0;
+
+ error:
+	kfree(codec->spec);
+	codec->spec = NULL;
+	return err;
+}
+
+
+/*
+ * patch entries
+ */
+static struct hda_codec_preset snd_hda_preset_cirrus[] = {
+	{ .id = 0x10134206, .name = "CS4206", .patch = patch_cs420x },
+	{ .id = 0x10134207, .name = "CS4207", .patch = patch_cs420x },
+	{} /* terminator */
+};
+
+MODULE_ALIAS("snd-hda-codec-id:10134206");
+MODULE_ALIAS("snd-hda-codec-id:10134207");
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Cirrus Logic HD-audio codec");
+
+static struct hda_codec_preset_list cirrus_list = {
+	.preset = snd_hda_preset_cirrus,
+	.owner = THIS_MODULE,
+};
+
+static int __init patch_cirrus_init(void)
+{
+	return snd_hda_add_codec_preset(&cirrus_list);
+}
+
+static void __exit patch_cirrus_exit(void)
+{
+	snd_hda_delete_codec_preset(&cirrus_list);
+}
+
+module_init(patch_cirrus_init)
+module_exit(patch_cirrus_exit)
diff --git a/linux/sound/pci/hda/patch_cmedia.c b/linux/sound/pci/hda/patch_cmedia.c
new file mode 100644
index 0000000..ff60908
--- /dev/null
+++ b/linux/sound/pci/hda/patch_cmedia.c
@@ -0,0 +1,776 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for C-Media CMI9880
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+#define NUM_PINS	11
+
+
+/* board config type */
+enum {
+	CMI_MINIMAL,	/* back 3-jack */
+	CMI_MIN_FP,	/* back 3-jack + front-panel 2-jack */
+	CMI_FULL,	/* back 6-jack + front-panel 2-jack */
+	CMI_FULL_DIG,	/* back 6-jack + front-panel 2-jack + digital I/O */
+	CMI_ALLOUT,	/* back 5-jack + front-panel 2-jack + digital out */
+	CMI_AUTO,	/* let driver guess it */
+	CMI_MODELS
+};
+
+struct cmi_spec {
+	int board_config;
+	unsigned int no_line_in: 1;	/* no line-in (5-jack) */
+	unsigned int front_panel: 1;	/* has front-panel 2-jack */
+
+	/* playback */
+	struct hda_multi_out multiout;
+	hda_nid_t dac_nids[AUTO_CFG_MAX_OUTS];	/* NID for each DAC */
+	int num_dacs;
+
+	/* capture */
+	hda_nid_t *adc_nids;
+	hda_nid_t dig_in_nid;
+
+	/* capture source */
+	const struct hda_input_mux *input_mux;
+	unsigned int cur_mux[2];
+
+	/* channel mode */
+	int num_channel_modes;
+	const struct hda_channel_mode *channel_modes;
+
+	struct hda_pcm pcm_rec[2];	/* PCM information */
+
+	/* pin default configuration */
+	hda_nid_t pin_nid[NUM_PINS];
+	unsigned int def_conf[NUM_PINS];
+	unsigned int pin_def_confs;
+
+	/* multichannel pins */
+	struct hda_verb multi_init[9];	/* 2 verbs for each pin + terminator */
+};
+
+/*
+ * input MUX
+ */
+static int cmi_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct cmi_spec *spec = codec->spec;
+	return snd_hda_input_mux_info(spec->input_mux, uinfo);
+}
+
+static int cmi_mux_enum_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct cmi_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
+	return 0;
+}
+
+static int cmi_mux_enum_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct cmi_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
+				     spec->adc_nids[adc_idx], &spec->cur_mux[adc_idx]);
+}
+
+/*
+ * shared line-in, mic for surrounds
+ */
+
+/* 3-stack / 2 channel */
+static struct hda_verb cmi9880_ch2_init[] = {
+	/* set line-in PIN for input */
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	/* set mic PIN for input, also enable vref */
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	/* route front PCM (DAC1) to HP */
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	{}
+};
+
+/* 3-stack / 6 channel */
+static struct hda_verb cmi9880_ch6_init[] = {
+	/* set line-in PIN for output */
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	/* set mic PIN for output */
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	/* route front PCM (DAC1) to HP */
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	{}
+};
+
+/* 3-stack+front / 8 channel */
+static struct hda_verb cmi9880_ch8_init[] = {
+	/* set line-in PIN for output */
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	/* set mic PIN for output */
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	/* route rear-surround PCM (DAC4) to HP */
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x03 },
+	{}
+};
+
+static struct hda_channel_mode cmi9880_channel_modes[3] = {
+	{ 2, cmi9880_ch2_init },
+	{ 6, cmi9880_ch6_init },
+	{ 8, cmi9880_ch8_init },
+};
+
+static int cmi_ch_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct cmi_spec *spec = codec->spec;
+	return snd_hda_ch_mode_info(codec, uinfo, spec->channel_modes,
+				    spec->num_channel_modes);
+}
+
+static int cmi_ch_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct cmi_spec *spec = codec->spec;
+	return snd_hda_ch_mode_get(codec, ucontrol, spec->channel_modes,
+				   spec->num_channel_modes, spec->multiout.max_channels);
+}
+
+static int cmi_ch_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct cmi_spec *spec = codec->spec;
+	return snd_hda_ch_mode_put(codec, ucontrol, spec->channel_modes,
+				   spec->num_channel_modes, &spec->multiout.max_channels);
+}
+
+/*
+ */
+static struct snd_kcontrol_new cmi9880_basic_mixer[] = {
+	/* CMI9880 has no playback volumes! */
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x03, 0x0, HDA_OUTPUT), /* front */
+	HDA_CODEC_MUTE("Surround Playback Switch", 0x04, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x05, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x05, 2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Side Playback Switch", 0x06, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 2,
+		.info = cmi_mux_enum_info,
+		.get = cmi_mux_enum_get,
+		.put = cmi_mux_enum_put,
+	},
+	HDA_CODEC_VOLUME("Capture Volume", 0x08, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x08, 0, HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Beep Playback Volume", 0x23, 0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Beep Playback Switch", 0x23, 0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+/*
+ * shared I/O pins
+ */
+static struct snd_kcontrol_new cmi9880_ch_mode_mixer[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = cmi_ch_mode_info,
+		.get = cmi_ch_mode_get,
+		.put = cmi_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+/* AUD-in selections:
+ * 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x1f 0x20
+ */
+static struct hda_input_mux cmi9880_basic_mux = {
+	.num_items = 4,
+	.items = {
+		{ "Front Mic", 0x5 },
+		{ "Rear Mic", 0x2 },
+		{ "Line", 0x1 },
+		{ "CD", 0x7 },
+	}
+};
+
+static struct hda_input_mux cmi9880_no_line_mux = {
+	.num_items = 3,
+	.items = {
+		{ "Front Mic", 0x5 },
+		{ "Rear Mic", 0x2 },
+		{ "CD", 0x7 },
+	}
+};
+
+/* front, rear, clfe, rear_surr */
+static hda_nid_t cmi9880_dac_nids[4] = {
+	0x03, 0x04, 0x05, 0x06
+};
+/* ADC0, ADC1 */
+static hda_nid_t cmi9880_adc_nids[2] = {
+	0x08, 0x09
+};
+
+#define CMI_DIG_OUT_NID	0x07
+#define CMI_DIG_IN_NID	0x0a
+
+/*
+ */
+static struct hda_verb cmi9880_basic_init[] = {
+	/* port-D for line out (rear panel) */
+	{ 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	/* port-E for HP out (front panel) */
+	{ 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	/* route front PCM to HP */
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-A for surround (rear panel) */
+	{ 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	/* port-G for CLFE (rear panel) */
+	{ 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	{ 0x1f, AC_VERB_SET_CONNECT_SEL, 0x02 },
+	/* port-H for side (rear panel) */
+	{ 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	{ 0x20, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	/* port-C for line-in (rear panel) */
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	/* port-B for mic-in (rear panel) with vref */
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	/* port-F for mic-in (front panel) with vref */
+	{ 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	/* CD-in */
+	{ 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	/* route front mic to ADC1/2 */
+	{ 0x08, AC_VERB_SET_CONNECT_SEL, 0x05 },
+	{ 0x09, AC_VERB_SET_CONNECT_SEL, 0x05 },
+	{} /* terminator */
+};
+
+static struct hda_verb cmi9880_allout_init[] = {
+	/* port-D for line out (rear panel) */
+	{ 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	/* port-E for HP out (front panel) */
+	{ 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	/* route front PCM to HP */
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-A for side (rear panel) */
+	{ 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	/* port-G for CLFE (rear panel) */
+	{ 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	{ 0x1f, AC_VERB_SET_CONNECT_SEL, 0x02 },
+	/* port-H for side (rear panel) */
+	{ 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	{ 0x20, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	/* port-C for surround (rear panel) */
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	/* port-B for mic-in (rear panel) with vref */
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	/* port-F for mic-in (front panel) with vref */
+	{ 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	/* CD-in */
+	{ 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	/* route front mic to ADC1/2 */
+	{ 0x08, AC_VERB_SET_CONNECT_SEL, 0x05 },
+	{ 0x09, AC_VERB_SET_CONNECT_SEL, 0x05 },
+	{} /* terminator */
+};
+
+/*
+ */
+static int cmi9880_build_controls(struct hda_codec *codec)
+{
+	struct cmi_spec *spec = codec->spec;
+	struct snd_kcontrol *kctl;
+	int i, err;
+
+	err = snd_hda_add_new_ctls(codec, cmi9880_basic_mixer);
+	if (err < 0)
+		return err;
+	if (spec->channel_modes) {
+		err = snd_hda_add_new_ctls(codec, cmi9880_ch_mode_mixer);
+		if (err < 0)
+			return err;
+	}
+	if (spec->multiout.dig_out_nid) {
+		err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid);
+		if (err < 0)
+			return err;
+		err = snd_hda_create_spdif_share_sw(codec,
+						    &spec->multiout);
+		if (err < 0)
+			return err;
+		spec->multiout.share_spdif = 1;
+	}
+	if (spec->dig_in_nid) {
+		err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
+		if (err < 0)
+			return err;
+	}
+
+	/* assign Capture Source enums to NID */
+	kctl = snd_hda_find_mixer_ctl(codec, "Capture Source");
+	for (i = 0; kctl && i < kctl->count; i++) {
+		err = snd_hda_add_nid(codec, kctl, i, spec->adc_nids[i]);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+/* fill in the multi_dac_nids table, which will decide
+   which audio widget to use for each channel */
+static int cmi9880_fill_multi_dac_nids(struct hda_codec *codec, const struct auto_pin_cfg *cfg)
+{
+	struct cmi_spec *spec = codec->spec;
+	hda_nid_t nid;
+	int assigned[4];
+	int i, j;
+
+	/* clear the table, only one c-media dac assumed here */
+	memset(spec->dac_nids, 0, sizeof(spec->dac_nids));
+	memset(assigned, 0, sizeof(assigned));
+	/* check the pins we found */
+	for (i = 0; i < cfg->line_outs; i++) {
+		nid = cfg->line_out_pins[i];
+		/* nid 0x0b~0x0e is hardwired to audio widget 0x3~0x6 */
+		if (nid >= 0x0b && nid <= 0x0e) {
+			spec->dac_nids[i] = (nid - 0x0b) + 0x03;
+			assigned[nid - 0x0b] = 1;
+		}
+	}
+	/* left pin can be connect to any audio widget */
+	for (i = 0; i < cfg->line_outs; i++) {
+		nid = cfg->line_out_pins[i];
+		if (nid <= 0x0e)
+			continue;
+		/* search for an empty channel */
+		for (j = 0; j < cfg->line_outs; j++) {
+			if (! assigned[j]) {
+				spec->dac_nids[i] = j + 0x03;
+				assigned[j] = 1;
+				break;
+			}
+		}
+	}
+	spec->num_dacs = cfg->line_outs;
+	return 0;
+}
+
+/* create multi_init table, which is used for multichannel initialization */
+static int cmi9880_fill_multi_init(struct hda_codec *codec, const struct auto_pin_cfg *cfg)
+{
+	struct cmi_spec *spec = codec->spec;
+	hda_nid_t nid;
+	int i, j, k, len;
+
+	/* clear the table, only one c-media dac assumed here */
+	memset(spec->multi_init, 0, sizeof(spec->multi_init));
+	for (j = 0, i = 0; i < cfg->line_outs; i++) {
+		hda_nid_t conn[4];
+		nid = cfg->line_out_pins[i];
+		/* set as output */
+		spec->multi_init[j].nid = nid;
+		spec->multi_init[j].verb = AC_VERB_SET_PIN_WIDGET_CONTROL;
+		spec->multi_init[j].param = PIN_OUT;
+		j++;
+		if (nid > 0x0e) {
+			/* set connection */
+			spec->multi_init[j].nid = nid;
+			spec->multi_init[j].verb = AC_VERB_SET_CONNECT_SEL;
+			spec->multi_init[j].param = 0;
+			/* find the index in connect list */
+			len = snd_hda_get_connections(codec, nid, conn, 4);
+			for (k = 0; k < len; k++)
+				if (conn[k] == spec->dac_nids[i]) {
+					spec->multi_init[j].param = k;
+					break;
+				}
+			j++;
+		}
+	}
+	return 0;
+}
+
+static int cmi9880_init(struct hda_codec *codec)
+{
+	struct cmi_spec *spec = codec->spec;
+	if (spec->board_config == CMI_ALLOUT)
+		snd_hda_sequence_write(codec, cmi9880_allout_init);
+	else
+		snd_hda_sequence_write(codec, cmi9880_basic_init);
+	if (spec->board_config == CMI_AUTO)
+		snd_hda_sequence_write(codec, spec->multi_init);
+	return 0;
+}
+
+/*
+ * Analog playback callbacks
+ */
+static int cmi9880_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				     struct hda_codec *codec,
+				     struct snd_pcm_substream *substream)
+{
+	struct cmi_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
+					     hinfo);
+}
+
+static int cmi9880_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					struct hda_codec *codec,
+					unsigned int stream_tag,
+					unsigned int format,
+					struct snd_pcm_substream *substream)
+{
+	struct cmi_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag,
+						format, substream);
+}
+
+static int cmi9880_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       struct snd_pcm_substream *substream)
+{
+	struct cmi_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Digital out
+ */
+static int cmi9880_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+					 struct hda_codec *codec,
+					 struct snd_pcm_substream *substream)
+{
+	struct cmi_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int cmi9880_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+					  struct hda_codec *codec,
+					  struct snd_pcm_substream *substream)
+{
+	struct cmi_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int cmi9880_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					    struct hda_codec *codec,
+					    unsigned int stream_tag,
+					    unsigned int format,
+					    struct snd_pcm_substream *substream)
+{
+	struct cmi_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag,
+					     format, substream);
+}
+
+/*
+ * Analog capture
+ */
+static int cmi9880_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      unsigned int stream_tag,
+				      unsigned int format,
+				      struct snd_pcm_substream *substream)
+{
+	struct cmi_spec *spec = codec->spec;
+
+	snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
+				   stream_tag, 0, format);
+	return 0;
+}
+
+static int cmi9880_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      struct snd_pcm_substream *substream)
+{
+	struct cmi_spec *spec = codec->spec;
+
+	snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]);
+	return 0;
+}
+
+
+/*
+ */
+static struct hda_pcm_stream cmi9880_pcm_analog_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 8,
+	.nid = 0x03, /* NID to query formats and rates */
+	.ops = {
+		.open = cmi9880_playback_pcm_open,
+		.prepare = cmi9880_playback_pcm_prepare,
+		.cleanup = cmi9880_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream cmi9880_pcm_analog_capture = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x08, /* NID to query formats and rates */
+	.ops = {
+		.prepare = cmi9880_capture_pcm_prepare,
+		.cleanup = cmi9880_capture_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream cmi9880_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in cmi9880_build_pcms */
+	.ops = {
+		.open = cmi9880_dig_playback_pcm_open,
+		.close = cmi9880_dig_playback_pcm_close,
+		.prepare = cmi9880_dig_playback_pcm_prepare
+	},
+};
+
+static struct hda_pcm_stream cmi9880_pcm_digital_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in cmi9880_build_pcms */
+};
+
+static int cmi9880_build_pcms(struct hda_codec *codec)
+{
+	struct cmi_spec *spec = codec->spec;
+	struct hda_pcm *info = spec->pcm_rec;
+
+	codec->num_pcms = 1;
+	codec->pcm_info = info;
+
+	info->name = "CMI9880";
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK] = cmi9880_pcm_analog_playback;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE] = cmi9880_pcm_analog_capture;
+
+	if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
+		codec->num_pcms++;
+		info++;
+		info->name = "CMI9880 Digital";
+		info->pcm_type = HDA_PCM_TYPE_SPDIF;
+		if (spec->multiout.dig_out_nid) {
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK] = cmi9880_pcm_digital_playback;
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dig_out_nid;
+		}
+		if (spec->dig_in_nid) {
+			info->stream[SNDRV_PCM_STREAM_CAPTURE] = cmi9880_pcm_digital_capture;
+			info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid;
+		}
+	}
+
+	return 0;
+}
+
+static void cmi9880_free(struct hda_codec *codec)
+{
+	kfree(codec->spec);
+}
+
+/*
+ */
+
+static const char *cmi9880_models[CMI_MODELS] = {
+	[CMI_MINIMAL]	= "minimal",
+	[CMI_MIN_FP]	= "min_fp",
+	[CMI_FULL]	= "full",
+	[CMI_FULL_DIG]	= "full_dig",
+	[CMI_ALLOUT]	= "allout",
+	[CMI_AUTO]	= "auto",
+};
+
+static struct snd_pci_quirk cmi9880_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1043, 0x813d, "ASUS P5AD2", CMI_FULL_DIG),
+	SND_PCI_QUIRK(0x1854, 0x002b, "LG LS75", CMI_MINIMAL),
+	SND_PCI_QUIRK(0x1854, 0x0032, "LG", CMI_FULL_DIG),
+	{} /* terminator */
+};
+
+static struct hda_codec_ops cmi9880_patch_ops = {
+	.build_controls = cmi9880_build_controls,
+	.build_pcms = cmi9880_build_pcms,
+	.init = cmi9880_init,
+	.free = cmi9880_free,
+};
+
+static int patch_cmi9880(struct hda_codec *codec)
+{
+	struct cmi_spec *spec;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+	spec->board_config = snd_hda_check_board_config(codec, CMI_MODELS,
+							cmi9880_models,
+							cmi9880_cfg_tbl);
+	if (spec->board_config < 0) {
+		snd_printdd(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+			    codec->chip_name);
+		spec->board_config = CMI_AUTO; /* try everything */
+	}
+
+	/* copy default DAC NIDs */
+	memcpy(spec->dac_nids, cmi9880_dac_nids, sizeof(spec->dac_nids));
+	spec->num_dacs = 4;
+
+	switch (spec->board_config) {
+	case CMI_MINIMAL:
+	case CMI_MIN_FP:
+		spec->channel_modes = cmi9880_channel_modes;
+		if (spec->board_config == CMI_MINIMAL)
+			spec->num_channel_modes = 2;
+		else {
+			spec->front_panel = 1;
+			spec->num_channel_modes = 3;
+		}
+		spec->multiout.max_channels = cmi9880_channel_modes[0].channels;
+		spec->input_mux = &cmi9880_basic_mux;
+		break;
+	case CMI_FULL:
+	case CMI_FULL_DIG:
+		spec->front_panel = 1;
+		spec->multiout.max_channels = 8;
+		spec->input_mux = &cmi9880_basic_mux;
+		if (spec->board_config == CMI_FULL_DIG) {
+			spec->multiout.dig_out_nid = CMI_DIG_OUT_NID;
+			spec->dig_in_nid = CMI_DIG_IN_NID;
+		}
+		break;
+	case CMI_ALLOUT:
+		spec->front_panel = 1;
+		spec->multiout.max_channels = 8;
+		spec->no_line_in = 1;
+		spec->input_mux = &cmi9880_no_line_mux;
+		spec->multiout.dig_out_nid = CMI_DIG_OUT_NID;
+		break;
+	case CMI_AUTO:
+		{
+		unsigned int port_e, port_f, port_g, port_h;
+		unsigned int port_spdifi, port_spdifo;
+		struct auto_pin_cfg cfg;
+
+		/* collect pin default configuration */
+		port_e = snd_hda_codec_get_pincfg(codec, 0x0f);
+		port_f = snd_hda_codec_get_pincfg(codec, 0x10);
+		spec->front_panel = 1;
+		if (get_defcfg_connect(port_e) == AC_JACK_PORT_NONE ||
+		    get_defcfg_connect(port_f) == AC_JACK_PORT_NONE) {
+			port_g = snd_hda_codec_get_pincfg(codec, 0x1f);
+			port_h = snd_hda_codec_get_pincfg(codec, 0x20);
+			spec->channel_modes = cmi9880_channel_modes;
+			/* no front panel */
+			if (get_defcfg_connect(port_g) == AC_JACK_PORT_NONE ||
+			    get_defcfg_connect(port_h) == AC_JACK_PORT_NONE) {
+				/* no optional rear panel */
+				spec->board_config = CMI_MINIMAL;
+				spec->front_panel = 0;
+				spec->num_channel_modes = 2;
+			} else {
+				spec->board_config = CMI_MIN_FP;
+				spec->num_channel_modes = 3;
+			}
+			spec->input_mux = &cmi9880_basic_mux;
+			spec->multiout.max_channels = cmi9880_channel_modes[0].channels;
+		} else {
+			spec->input_mux = &cmi9880_basic_mux;
+			port_spdifi = snd_hda_codec_get_pincfg(codec, 0x13);
+			port_spdifo = snd_hda_codec_get_pincfg(codec, 0x12);
+			if (get_defcfg_connect(port_spdifo) != AC_JACK_PORT_NONE)
+				spec->multiout.dig_out_nid = CMI_DIG_OUT_NID;
+			if (get_defcfg_connect(port_spdifi) != AC_JACK_PORT_NONE)
+				spec->dig_in_nid = CMI_DIG_IN_NID;
+			spec->multiout.max_channels = 8;
+		}
+		snd_hda_parse_pin_def_config(codec, &cfg, NULL);
+		if (cfg.line_outs) {
+			spec->multiout.max_channels = cfg.line_outs * 2;
+			cmi9880_fill_multi_dac_nids(codec, &cfg);
+			cmi9880_fill_multi_init(codec, &cfg);
+		} else
+			snd_printd("patch_cmedia: cannot detect association in defcfg\n");
+		break;
+		}
+	}
+
+	spec->multiout.num_dacs = spec->num_dacs;
+	spec->multiout.dac_nids = spec->dac_nids;
+
+	spec->adc_nids = cmi9880_adc_nids;
+
+	codec->patch_ops = cmi9880_patch_ops;
+
+	return 0;
+}
+
+/*
+ * patch entries
+ */
+static struct hda_codec_preset snd_hda_preset_cmedia[] = {
+	{ .id = 0x13f69880, .name = "CMI9880", .patch = patch_cmi9880 },
+ 	{ .id = 0x434d4980, .name = "CMI9880", .patch = patch_cmi9880 },
+	{} /* terminator */
+};
+
+MODULE_ALIAS("snd-hda-codec-id:13f69880");
+MODULE_ALIAS("snd-hda-codec-id:434d4980");
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("C-Media HD-audio codec");
+
+static struct hda_codec_preset_list cmedia_list = {
+	.preset = snd_hda_preset_cmedia,
+	.owner = THIS_MODULE,
+};
+
+static int __init patch_cmedia_init(void)
+{
+	return snd_hda_add_codec_preset(&cmedia_list);
+}
+
+static void __exit patch_cmedia_exit(void)
+{
+	snd_hda_delete_codec_preset(&cmedia_list);
+}
+
+module_init(patch_cmedia_init)
+module_exit(patch_cmedia_exit)
diff --git a/linux/sound/pci/hda/patch_conexant.c b/linux/sound/pci/hda/patch_conexant.c
new file mode 100644
index 0000000..76bd58a
--- /dev/null
+++ b/linux/sound/pci/hda/patch_conexant.c
@@ -0,0 +1,3940 @@
+/*
+ * HD audio interface patch for Conexant HDA audio codec
+ *
+ * Copyright (c) 2006 Pototskiy Akex <alex.pototskiy@gmail.com>
+ * 		      Takashi Iwai <tiwai@suse.de>
+ * 		      Tobin Davis  <tdavis@dsl-only.net>
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+
+#include "hda_codec.h"
+#include "hda_local.h"
+#include "hda_beep.h"
+
+#define CXT_PIN_DIR_IN              0x00
+#define CXT_PIN_DIR_OUT             0x01
+#define CXT_PIN_DIR_INOUT           0x02
+#define CXT_PIN_DIR_IN_NOMICBIAS    0x03
+#define CXT_PIN_DIR_INOUT_NOMICBIAS 0x04
+
+#define CONEXANT_HP_EVENT	0x37
+#define CONEXANT_MIC_EVENT	0x38
+
+/* Conexant 5051 specific */
+
+#define CXT5051_SPDIF_OUT	0x12
+#define CXT5051_PORTB_EVENT	0x38
+#define CXT5051_PORTC_EVENT	0x39
+
+#define AUTO_MIC_PORTB		(1 << 1)
+#define AUTO_MIC_PORTC		(1 << 2)
+
+struct conexant_jack {
+
+	hda_nid_t nid;
+	int type;
+	struct snd_jack *jack;
+
+};
+
+struct pin_dac_pair {
+	hda_nid_t pin;
+	hda_nid_t dac;
+	int type;
+};
+
+struct conexant_spec {
+
+	struct snd_kcontrol_new *mixers[5];
+	int num_mixers;
+	hda_nid_t vmaster_nid;
+
+	const struct hda_verb *init_verbs[5];	/* initialization verbs
+						 * don't forget NULL
+						 * termination!
+						 */
+	unsigned int num_init_verbs;
+
+	/* playback */
+	struct hda_multi_out multiout;	/* playback set-up
+					 * max_channels, dacs must be set
+					 * dig_out_nid and hp_nid are optional
+					 */
+	unsigned int cur_eapd;
+	unsigned int hp_present;
+	unsigned int auto_mic;
+	int auto_mic_ext;		/* autocfg.inputs[] index for ext mic */
+	unsigned int need_dac_fix;
+
+	/* capture */
+	unsigned int num_adc_nids;
+	hda_nid_t *adc_nids;
+	hda_nid_t dig_in_nid;		/* digital-in NID; optional */
+
+	unsigned int cur_adc_idx;
+	hda_nid_t cur_adc;
+	unsigned int cur_adc_stream_tag;
+	unsigned int cur_adc_format;
+
+	/* capture source */
+	const struct hda_input_mux *input_mux;
+	hda_nid_t *capsrc_nids;
+	unsigned int cur_mux[3];
+
+	/* channel model */
+	const struct hda_channel_mode *channel_mode;
+	int num_channel_mode;
+
+	/* PCM information */
+	struct hda_pcm pcm_rec[2];	/* used in build_pcms() */
+
+	unsigned int spdif_route;
+
+	/* jack detection */
+	struct snd_array jacks;
+
+	/* dynamic controls, init_verbs and input_mux */
+	struct auto_pin_cfg autocfg;
+	struct hda_input_mux private_imux;
+	hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
+	struct pin_dac_pair dac_info[8];
+	int dac_info_filled;
+
+	unsigned int port_d_mode;
+	unsigned int auto_mute:1;	/* used in auto-parser */
+	unsigned int dell_automute:1;
+	unsigned int dell_vostro:1;
+	unsigned int ideapad:1;
+	unsigned int thinkpad:1;
+	unsigned int hp_laptop:1;
+
+	unsigned int ext_mic_present;
+	unsigned int recording;
+	void (*capture_prepare)(struct hda_codec *codec);
+	void (*capture_cleanup)(struct hda_codec *codec);
+
+	/* OLPC XO-1.5 supports DC input mode (e.g. for use with analog sensors)
+	 * through the microphone jack.
+	 * When the user enables this through a mixer switch, both internal and
+	 * external microphones are disabled. Gain is fixed at 0dB. In this mode,
+	 * we also allow the bias to be configured through a separate mixer
+	 * control. */
+	unsigned int dc_enable;
+	unsigned int dc_input_bias; /* offset into cxt5066_olpc_dc_bias */
+	unsigned int mic_boost; /* offset into cxt5066_analog_mic_boost */
+
+	unsigned int beep_amp;
+};
+
+static int conexant_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
+					     hinfo);
+}
+
+static int conexant_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					 struct hda_codec *codec,
+					 unsigned int stream_tag,
+					 unsigned int format,
+					 struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_prepare(codec, &spec->multiout,
+						stream_tag,
+						format, substream);
+}
+
+static int conexant_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+					 struct hda_codec *codec,
+					 struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Digital out
+ */
+static int conexant_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+					  struct hda_codec *codec,
+					  struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int conexant_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+					 struct hda_codec *codec,
+					 struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int conexant_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					 struct hda_codec *codec,
+					 unsigned int stream_tag,
+					 unsigned int format,
+					 struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
+					     stream_tag,
+					     format, substream);
+}
+
+/*
+ * Analog capture
+ */
+static int conexant_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      unsigned int stream_tag,
+				      unsigned int format,
+				      struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	if (spec->capture_prepare)
+		spec->capture_prepare(codec);
+	snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
+				   stream_tag, 0, format);
+	return 0;
+}
+
+static int conexant_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]);
+	if (spec->capture_cleanup)
+		spec->capture_cleanup(codec);
+	return 0;
+}
+
+
+
+static struct hda_pcm_stream conexant_pcm_analog_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0, /* fill later */
+	.ops = {
+		.open = conexant_playback_pcm_open,
+		.prepare = conexant_playback_pcm_prepare,
+		.cleanup = conexant_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream conexant_pcm_analog_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0, /* fill later */
+	.ops = {
+		.prepare = conexant_capture_pcm_prepare,
+		.cleanup = conexant_capture_pcm_cleanup
+	},
+};
+
+
+static struct hda_pcm_stream conexant_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0, /* fill later */
+	.ops = {
+		.open = conexant_dig_playback_pcm_open,
+		.close = conexant_dig_playback_pcm_close,
+		.prepare = conexant_dig_playback_pcm_prepare
+	},
+};
+
+static struct hda_pcm_stream conexant_pcm_digital_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in alc_build_pcms */
+};
+
+static int cx5051_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      unsigned int stream_tag,
+				      unsigned int format,
+				      struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	spec->cur_adc = spec->adc_nids[spec->cur_adc_idx];
+	spec->cur_adc_stream_tag = stream_tag;
+	spec->cur_adc_format = format;
+	snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format);
+	return 0;
+}
+
+static int cx5051_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      struct snd_pcm_substream *substream)
+{
+	struct conexant_spec *spec = codec->spec;
+	snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
+	spec->cur_adc = 0;
+	return 0;
+}
+
+static struct hda_pcm_stream cx5051_pcm_analog_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0, /* fill later */
+	.ops = {
+		.prepare = cx5051_capture_pcm_prepare,
+		.cleanup = cx5051_capture_pcm_cleanup
+	},
+};
+
+static int conexant_build_pcms(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct hda_pcm *info = spec->pcm_rec;
+
+	codec->num_pcms = 1;
+	codec->pcm_info = info;
+
+	info->name = "CONEXANT Analog";
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK] = conexant_pcm_analog_playback;
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
+		spec->multiout.max_channels;
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
+		spec->multiout.dac_nids[0];
+	if (codec->vendor_id == 0x14f15051)
+		info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+			cx5051_pcm_analog_capture;
+	else
+		info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+			conexant_pcm_analog_capture;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_adc_nids;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
+
+	if (spec->multiout.dig_out_nid) {
+		info++;
+		codec->num_pcms++;
+		info->name = "Conexant Digital";
+		info->pcm_type = HDA_PCM_TYPE_SPDIF;
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
+			conexant_pcm_digital_playback;
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
+			spec->multiout.dig_out_nid;
+		if (spec->dig_in_nid) {
+			info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+				conexant_pcm_digital_capture;
+			info->stream[SNDRV_PCM_STREAM_CAPTURE].nid =
+				spec->dig_in_nid;
+		}
+	}
+
+	return 0;
+}
+
+static int conexant_mux_enum_info(struct snd_kcontrol *kcontrol,
+	       			  struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+
+	return snd_hda_input_mux_info(spec->input_mux, uinfo);
+}
+
+static int conexant_mux_enum_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
+	return 0;
+}
+
+static int conexant_mux_enum_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
+				     spec->capsrc_nids[adc_idx],
+				     &spec->cur_mux[adc_idx]);
+}
+
+#ifdef CONFIG_SND_HDA_INPUT_JACK
+static void conexant_free_jack_priv(struct snd_jack *jack)
+{
+	struct conexant_jack *jacks = jack->private_data;
+	jacks->nid = 0;
+	jacks->jack = NULL;
+}
+
+static int conexant_add_jack(struct hda_codec *codec,
+		hda_nid_t nid, int type)
+{
+	struct conexant_spec *spec;
+	struct conexant_jack *jack;
+	const char *name;
+	int err;
+
+	spec = codec->spec;
+	snd_array_init(&spec->jacks, sizeof(*jack), 32);
+	jack = snd_array_new(&spec->jacks);
+	name = (type == SND_JACK_HEADPHONE) ? "Headphone" : "Mic" ;
+
+	if (!jack)
+		return -ENOMEM;
+
+	jack->nid = nid;
+	jack->type = type;
+
+	err = snd_jack_new(codec->bus->card, name, type, &jack->jack);
+	if (err < 0)
+		return err;
+	jack->jack->private_data = jack;
+	jack->jack->private_free = conexant_free_jack_priv;
+	return 0;
+}
+
+static void conexant_report_jack(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct conexant_jack *jacks = spec->jacks.list;
+
+	if (jacks) {
+		int i;
+		for (i = 0; i < spec->jacks.used; i++) {
+			if (jacks->nid == nid) {
+				unsigned int present;
+				present = snd_hda_jack_detect(codec, nid);
+
+				present = (present) ? jacks->type : 0 ;
+
+				snd_jack_report(jacks->jack,
+						present);
+			}
+			jacks++;
+		}
+	}
+}
+
+static int conexant_init_jacks(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->num_init_verbs; i++) {
+		const struct hda_verb *hv;
+
+		hv = spec->init_verbs[i];
+		while (hv->nid) {
+			int err = 0;
+			switch (hv->param ^ AC_USRSP_EN) {
+			case CONEXANT_HP_EVENT:
+				err = conexant_add_jack(codec, hv->nid,
+						SND_JACK_HEADPHONE);
+				conexant_report_jack(codec, hv->nid);
+				break;
+			case CXT5051_PORTC_EVENT:
+			case CONEXANT_MIC_EVENT:
+				err = conexant_add_jack(codec, hv->nid,
+						SND_JACK_MICROPHONE);
+				conexant_report_jack(codec, hv->nid);
+				break;
+			}
+			if (err < 0)
+				return err;
+			++hv;
+		}
+	}
+	return 0;
+
+}
+#else
+static inline void conexant_report_jack(struct hda_codec *codec, hda_nid_t nid)
+{
+}
+
+static inline int conexant_init_jacks(struct hda_codec *codec)
+{
+	return 0;
+}
+#endif
+
+static int conexant_init(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->num_init_verbs; i++)
+		snd_hda_sequence_write(codec, spec->init_verbs[i]);
+	return 0;
+}
+
+static void conexant_free(struct hda_codec *codec)
+{
+#ifdef CONFIG_SND_HDA_INPUT_JACK
+	struct conexant_spec *spec = codec->spec;
+	if (spec->jacks.list) {
+		struct conexant_jack *jacks = spec->jacks.list;
+		int i;
+		for (i = 0; i < spec->jacks.used; i++, jacks++) {
+			if (jacks->jack)
+				snd_device_free(codec->bus->card, jacks->jack);
+		}
+		snd_array_free(&spec->jacks);
+	}
+#endif
+	snd_hda_detach_beep_device(codec);
+	kfree(codec->spec);
+}
+
+static struct snd_kcontrol_new cxt_capture_mixers[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = conexant_mux_enum_info,
+		.get = conexant_mux_enum_get,
+		.put = conexant_mux_enum_put
+	},
+	{}
+};
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+/* additional beep mixers; the actual parameters are overwritten at build */
+static struct snd_kcontrol_new cxt_beep_mixer[] = {
+	HDA_CODEC_VOLUME_MONO("Beep Playback Volume", 0, 1, 0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_BEEP_MONO("Beep Playback Switch", 0, 1, 0, HDA_OUTPUT),
+	{ } /* end */
+};
+#endif
+
+static const char *slave_vols[] = {
+	"Headphone Playback Volume",
+	"Speaker Playback Volume",
+	NULL
+};
+
+static const char *slave_sws[] = {
+	"Headphone Playback Switch",
+	"Speaker Playback Switch",
+	NULL
+};
+
+static int conexant_build_controls(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < spec->num_mixers; i++) {
+		err = snd_hda_add_new_ctls(codec, spec->mixers[i]);
+		if (err < 0)
+			return err;
+	}
+	if (spec->multiout.dig_out_nid) {
+		err = snd_hda_create_spdif_out_ctls(codec,
+						    spec->multiout.dig_out_nid);
+		if (err < 0)
+			return err;
+		err = snd_hda_create_spdif_share_sw(codec,
+						    &spec->multiout);
+		if (err < 0)
+			return err;
+		spec->multiout.share_spdif = 1;
+	} 
+	if (spec->dig_in_nid) {
+		err = snd_hda_create_spdif_in_ctls(codec,spec->dig_in_nid);
+		if (err < 0)
+			return err;
+	}
+
+	/* if we have no master control, let's create it */
+	if (spec->vmaster_nid &&
+	    !snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
+		unsigned int vmaster_tlv[4];
+		snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid,
+					HDA_OUTPUT, vmaster_tlv);
+		err = snd_hda_add_vmaster(codec, "Master Playback Volume",
+					  vmaster_tlv, slave_vols);
+		if (err < 0)
+			return err;
+	}
+	if (spec->vmaster_nid &&
+	    !snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
+		err = snd_hda_add_vmaster(codec, "Master Playback Switch",
+					  NULL, slave_sws);
+		if (err < 0)
+			return err;
+	}
+
+	if (spec->input_mux) {
+		err = snd_hda_add_new_ctls(codec, cxt_capture_mixers);
+		if (err < 0)
+			return err;
+	}
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+	/* create beep controls if needed */
+	if (spec->beep_amp) {
+		struct snd_kcontrol_new *knew;
+		for (knew = cxt_beep_mixer; knew->name; knew++) {
+			struct snd_kcontrol *kctl;
+			kctl = snd_ctl_new1(knew, codec);
+			if (!kctl)
+				return -ENOMEM;
+			kctl->private_value = spec->beep_amp;
+			err = snd_hda_ctl_add(codec, 0, kctl);
+			if (err < 0)
+				return err;
+		}
+	}
+#endif
+
+	return 0;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static int conexant_suspend(struct hda_codec *codec, pm_message_t state)
+{
+	snd_hda_shutup_pins(codec);
+	return 0;
+}
+#endif
+
+static struct hda_codec_ops conexant_patch_ops = {
+	.build_controls = conexant_build_controls,
+	.build_pcms = conexant_build_pcms,
+	.init = conexant_init,
+	.free = conexant_free,
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	.suspend = conexant_suspend,
+#endif
+	.reboot_notify = snd_hda_shutup_pins,
+};
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+#define set_beep_amp(spec, nid, idx, dir) \
+	((spec)->beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir))
+#else
+#define set_beep_amp(spec, nid, idx, dir) /* NOP */
+#endif
+
+/*
+ * EAPD control
+ * the private value = nid | (invert << 8)
+ */
+
+#define cxt_eapd_info		snd_ctl_boolean_mono_info
+
+static int cxt_eapd_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	int invert = (kcontrol->private_value >> 8) & 1;
+	if (invert)
+		ucontrol->value.integer.value[0] = !spec->cur_eapd;
+	else
+		ucontrol->value.integer.value[0] = spec->cur_eapd;
+	return 0;
+
+}
+
+static int cxt_eapd_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	int invert = (kcontrol->private_value >> 8) & 1;
+	hda_nid_t nid = kcontrol->private_value & 0xff;
+	unsigned int eapd;
+
+	eapd = !!ucontrol->value.integer.value[0];
+	if (invert)
+		eapd = !eapd;
+	if (eapd == spec->cur_eapd)
+		return 0;
+	
+	spec->cur_eapd = eapd;
+	snd_hda_codec_write_cache(codec, nid,
+				  0, AC_VERB_SET_EAPD_BTLENABLE,
+				  eapd ? 0x02 : 0x00);
+	return 1;
+}
+
+/* controls for test mode */
+#ifdef CONFIG_SND_DEBUG
+
+#define CXT_EAPD_SWITCH(xname, nid, mask) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0,  \
+	  .info = cxt_eapd_info, \
+	  .get = cxt_eapd_get, \
+	  .put = cxt_eapd_put, \
+	  .private_value = nid | (mask<<16) }
+
+
+
+static int conexant_ch_mode_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	return snd_hda_ch_mode_info(codec, uinfo, spec->channel_mode,
+				    spec->num_channel_mode);
+}
+
+static int conexant_ch_mode_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	return snd_hda_ch_mode_get(codec, ucontrol, spec->channel_mode,
+				   spec->num_channel_mode,
+				   spec->multiout.max_channels);
+}
+
+static int conexant_ch_mode_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	int err = snd_hda_ch_mode_put(codec, ucontrol, spec->channel_mode,
+				      spec->num_channel_mode,
+				      &spec->multiout.max_channels);
+	if (err >= 0 && spec->need_dac_fix)
+		spec->multiout.num_dacs = spec->multiout.max_channels / 2;
+	return err;
+}
+
+#define CXT_PIN_MODE(xname, nid, dir) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0,  \
+	  .info = conexant_ch_mode_info, \
+	  .get = conexant_ch_mode_get, \
+	  .put = conexant_ch_mode_put, \
+	  .private_value = nid | (dir<<16) }
+
+#endif /* CONFIG_SND_DEBUG */
+
+/* Conexant 5045 specific */
+
+static hda_nid_t cxt5045_dac_nids[1] = { 0x19 };
+static hda_nid_t cxt5045_adc_nids[1] = { 0x1a };
+static hda_nid_t cxt5045_capsrc_nids[1] = { 0x1a };
+#define CXT5045_SPDIF_OUT	0x18
+
+static struct hda_channel_mode cxt5045_modes[1] = {
+	{ 2, NULL },
+};
+
+static struct hda_input_mux cxt5045_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "IntMic", 0x1 },
+		{ "ExtMic", 0x2 },
+	}
+};
+
+static struct hda_input_mux cxt5045_capture_source_benq = {
+	.num_items = 5,
+	.items = {
+		{ "IntMic", 0x1 },
+		{ "ExtMic", 0x2 },
+		{ "LineIn", 0x3 },
+		{ "CD",     0x4 },
+		{ "Mixer",  0x0 },
+	}
+};
+
+static struct hda_input_mux cxt5045_capture_source_hp530 = {
+	.num_items = 2,
+	.items = {
+		{ "ExtMic", 0x1 },
+		{ "IntMic", 0x2 },
+	}
+};
+
+/* turn on/off EAPD (+ mute HP) as a master switch */
+static int cxt5045_hp_master_sw_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	unsigned int bits;
+
+	if (!cxt_eapd_put(kcontrol, ucontrol))
+		return 0;
+
+	/* toggle internal speakers mute depending of presence of
+	 * the headphone jack
+	 */
+	bits = (!spec->hp_present && spec->cur_eapd) ? 0 : HDA_AMP_MUTE;
+	snd_hda_codec_amp_stereo(codec, 0x10, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+
+	bits = spec->cur_eapd ? 0 : HDA_AMP_MUTE;
+	snd_hda_codec_amp_stereo(codec, 0x11, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	return 1;
+}
+
+/* bind volumes of both NID 0x10 and 0x11 */
+static struct hda_bind_ctls cxt5045_hp_bind_master_vol = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x10, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x11, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+/* toggle input of built-in and mic jack appropriately */
+static void cxt5045_hp_automic(struct hda_codec *codec)
+{
+	static struct hda_verb mic_jack_on[] = {
+		{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+		{0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+		{}
+	};
+	static struct hda_verb mic_jack_off[] = {
+		{0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+		{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+		{}
+	};
+	unsigned int present;
+
+	present = snd_hda_jack_detect(codec, 0x12);
+	if (present)
+		snd_hda_sequence_write(codec, mic_jack_on);
+	else
+		snd_hda_sequence_write(codec, mic_jack_off);
+}
+
+
+/* mute internal speaker if HP is plugged */
+static void cxt5045_hp_automute(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	unsigned int bits;
+
+	spec->hp_present = snd_hda_jack_detect(codec, 0x11);
+
+	bits = (spec->hp_present || !spec->cur_eapd) ? HDA_AMP_MUTE : 0; 
+	snd_hda_codec_amp_stereo(codec, 0x10, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+}
+
+/* unsolicited event for HP jack sensing */
+static void cxt5045_hp_unsol_event(struct hda_codec *codec,
+				   unsigned int res)
+{
+	res >>= 26;
+	switch (res) {
+	case CONEXANT_HP_EVENT:
+		cxt5045_hp_automute(codec);
+		break;
+	case CONEXANT_MIC_EVENT:
+		cxt5045_hp_automic(codec);
+		break;
+
+	}
+}
+
+static struct snd_kcontrol_new cxt5045_mixers[] = {
+	HDA_CODEC_VOLUME("Int Mic Capture Volume", 0x1a, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Capture Switch", 0x1a, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Ext Mic Capture Volume", 0x1a, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Ext Mic Capture Switch", 0x1a, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x17, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x17, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x17, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x17, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Ext Mic Playback Volume", 0x17, 0x2, HDA_INPUT),
+	HDA_CODEC_MUTE("Ext Mic Playback Switch", 0x17, 0x2, HDA_INPUT),
+	HDA_BIND_VOL("Master Playback Volume", &cxt5045_hp_bind_master_vol),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = cxt_eapd_info,
+		.get = cxt_eapd_get,
+		.put = cxt5045_hp_master_sw_put,
+		.private_value = 0x10,
+	},
+
+	{}
+};
+
+static struct snd_kcontrol_new cxt5045_benq_mixers[] = {
+	HDA_CODEC_VOLUME("CD Capture Volume", 0x1a, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Capture Switch", 0x1a, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x17, 0x4, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x17, 0x4, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Line In Capture Volume", 0x1a, 0x03, HDA_INPUT),
+	HDA_CODEC_MUTE("Line In Capture Switch", 0x1a, 0x03, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line In Playback Volume", 0x17, 0x3, HDA_INPUT),
+	HDA_CODEC_MUTE("Line In Playback Switch", 0x17, 0x3, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Mixer Capture Volume", 0x1a, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mixer Capture Switch", 0x1a, 0x0, HDA_INPUT),
+
+	{}
+};
+
+static struct snd_kcontrol_new cxt5045_mixers_hp530[] = {
+	HDA_CODEC_VOLUME("Int Mic Capture Volume", 0x1a, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Capture Switch", 0x1a, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Ext Mic Capture Volume", 0x1a, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Ext Mic Capture Switch", 0x1a, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("PCM Playback Volume", 0x17, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("PCM Playback Switch", 0x17, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x17, 0x2, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x17, 0x2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Ext Mic Playback Volume", 0x17, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Ext Mic Playback Switch", 0x17, 0x1, HDA_INPUT),
+	HDA_BIND_VOL("Master Playback Volume", &cxt5045_hp_bind_master_vol),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = cxt_eapd_info,
+		.get = cxt_eapd_get,
+		.put = cxt5045_hp_master_sw_put,
+		.private_value = 0x10,
+	},
+
+	{}
+};
+
+static struct hda_verb cxt5045_init_verbs[] = {
+	/* Line in, Mic */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_80 },
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_80 },
+	/* HP, Amp  */
+	{0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x10, AC_VERB_SET_CONNECT_SEL, 0x1},
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x11, AC_VERB_SET_CONNECT_SEL, 0x1},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* Record selector: Int mic */
+	{0x1a, AC_VERB_SET_CONNECT_SEL,0x1},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE,
+	 AC_AMP_SET_INPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x17},
+	/* SPDIF route: PCM */
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{ 0x13, AC_VERB_SET_CONNECT_SEL, 0x0 },
+	/* EAPD */
+	{0x10, AC_VERB_SET_EAPD_BTLENABLE, 0x2 }, /* default on */ 
+	{ } /* end */
+};
+
+static struct hda_verb cxt5045_benq_init_verbs[] = {
+	/* Int Mic, Mic */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_80 },
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_80 },
+	/* Line In,HP, Amp  */
+	{0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x10, AC_VERB_SET_CONNECT_SEL, 0x1},
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x11, AC_VERB_SET_CONNECT_SEL, 0x1},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* Record selector: Int mic */
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x1},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE,
+	 AC_AMP_SET_INPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x17},
+	/* SPDIF route: PCM */
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x13, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* EAPD */
+	{0x10, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+	{ } /* end */
+};
+
+static struct hda_verb cxt5045_hp_sense_init_verbs[] = {
+	/* pin sensing on HP jack */
+	{0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
+	{ } /* end */
+};
+
+static struct hda_verb cxt5045_mic_sense_init_verbs[] = {
+	/* pin sensing on HP jack */
+	{0x12, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
+	{ } /* end */
+};
+
+#ifdef CONFIG_SND_DEBUG
+/* Test configuration for debugging, modelled after the ALC260 test
+ * configuration.
+ */
+static struct hda_input_mux cxt5045_test_capture_source = {
+	.num_items = 5,
+	.items = {
+		{ "MIXER", 0x0 },
+		{ "MIC1 pin", 0x1 },
+		{ "LINE1 pin", 0x2 },
+		{ "HP-OUT pin", 0x3 },
+		{ "CD pin", 0x4 },
+        },
+};
+
+static struct snd_kcontrol_new cxt5045_test_mixer[] = {
+
+	/* Output controls */
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x10, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x10, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Node 11 Playback Volume", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Node 11 Playback Switch", 0x11, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Node 12 Playback Volume", 0x12, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Node 12 Playback Switch", 0x12, 0x0, HDA_OUTPUT),
+	
+	/* Modes for retasking pin widgets */
+	CXT_PIN_MODE("HP-OUT pin mode", 0x11, CXT_PIN_DIR_INOUT),
+	CXT_PIN_MODE("LINE1 pin mode", 0x12, CXT_PIN_DIR_INOUT),
+
+	/* EAPD Switch Control */
+	CXT_EAPD_SWITCH("External Amplifier", 0x10, 0x0),
+
+	/* Loopback mixer controls */
+
+	HDA_CODEC_VOLUME("Mixer-1 Volume", 0x17, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mixer-1 Switch", 0x17, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mixer-2 Volume", 0x17, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Mixer-2 Switch", 0x17, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mixer-3 Volume", 0x17, 0x2, HDA_INPUT),
+	HDA_CODEC_MUTE("Mixer-3 Switch", 0x17, 0x2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mixer-4 Volume", 0x17, 0x3, HDA_INPUT),
+	HDA_CODEC_MUTE("Mixer-4 Switch", 0x17, 0x3, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mixer-5 Volume", 0x17, 0x4, HDA_INPUT),
+	HDA_CODEC_MUTE("Mixer-5 Switch", 0x17, 0x4, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Input Source",
+		.info = conexant_mux_enum_info,
+		.get = conexant_mux_enum_get,
+		.put = conexant_mux_enum_put,
+	},
+	/* Audio input controls */
+	HDA_CODEC_VOLUME("Input-1 Volume", 0x1a, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Input-1 Switch", 0x1a, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Input-2 Volume", 0x1a, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Input-2 Switch", 0x1a, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Input-3 Volume", 0x1a, 0x2, HDA_INPUT),
+	HDA_CODEC_MUTE("Input-3 Switch", 0x1a, 0x2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Input-4 Volume", 0x1a, 0x3, HDA_INPUT),
+	HDA_CODEC_MUTE("Input-4 Switch", 0x1a, 0x3, HDA_INPUT),
+	HDA_CODEC_VOLUME("Input-5 Volume", 0x1a, 0x4, HDA_INPUT),
+	HDA_CODEC_MUTE("Input-5 Switch", 0x1a, 0x4, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_verb cxt5045_test_init_verbs[] = {
+	/* Set connections */
+	{ 0x10, AC_VERB_SET_CONNECT_SEL, 0x0 },
+	{ 0x11, AC_VERB_SET_CONNECT_SEL, 0x0 },
+	{ 0x12, AC_VERB_SET_CONNECT_SEL, 0x0 },
+	/* Enable retasking pins as output, initially without power amp */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	/* Disable digital (SPDIF) pins initially, but users can enable
+	 * them via a mixer switch.  In the case of SPDIF-out, this initverb
+	 * payload also sets the generation to 0, output to be in "consumer"
+	 * PCM format, copyright asserted, no pre-emphasis and no validity
+	 * control.
+	 */
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x18, AC_VERB_SET_DIGI_CONVERT_1, 0},
+
+	/* Start with output sum widgets muted and their output gains at min */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+
+	/* Unmute retasking pin widget output buffers since the default
+	 * state appears to be output.  As the pin mode is changed by the
+	 * user the pin mode control will take care of enabling the pin's
+	 * input/output buffers as needed.
+	 */
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Mute capture amp left and right */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+
+	/* Set ADC connection select to match default mixer setting (mic1
+	 * pin)
+	 */
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x17, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Mute all inputs to mixer widget (even unconnected ones) */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* Mixer pin */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* Mic1 pin */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* Line pin */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* HP pin */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */
+
+	{ }
+};
+#endif
+
+
+/* initialize jack-sensing, too */
+static int cxt5045_init(struct hda_codec *codec)
+{
+	conexant_init(codec);
+	cxt5045_hp_automute(codec);
+	return 0;
+}
+
+
+enum {
+	CXT5045_LAPTOP_HPSENSE,
+	CXT5045_LAPTOP_MICSENSE,
+	CXT5045_LAPTOP_HPMICSENSE,
+	CXT5045_BENQ,
+	CXT5045_LAPTOP_HP530,
+#ifdef CONFIG_SND_DEBUG
+	CXT5045_TEST,
+#endif
+	CXT5045_MODELS
+};
+
+static const char *cxt5045_models[CXT5045_MODELS] = {
+	[CXT5045_LAPTOP_HPSENSE]	= "laptop-hpsense",
+	[CXT5045_LAPTOP_MICSENSE]	= "laptop-micsense",
+	[CXT5045_LAPTOP_HPMICSENSE]	= "laptop-hpmicsense",
+	[CXT5045_BENQ]			= "benq",
+	[CXT5045_LAPTOP_HP530]		= "laptop-hp530",
+#ifdef CONFIG_SND_DEBUG
+	[CXT5045_TEST]		= "test",
+#endif
+};
+
+static struct snd_pci_quirk cxt5045_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x103c, 0x30d5, "HP 530", CXT5045_LAPTOP_HP530),
+	SND_PCI_QUIRK_MASK(0x103c, 0xff00, 0x3000, "HP DV Series",
+			   CXT5045_LAPTOP_HPSENSE),
+	SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba P105", CXT5045_LAPTOP_MICSENSE),
+	SND_PCI_QUIRK(0x152d, 0x0753, "Benq R55E", CXT5045_BENQ),
+	SND_PCI_QUIRK(0x1734, 0x10ad, "Fujitsu Si1520", CXT5045_LAPTOP_MICSENSE),
+	SND_PCI_QUIRK(0x1734, 0x10cb, "Fujitsu Si3515", CXT5045_LAPTOP_HPMICSENSE),
+	SND_PCI_QUIRK(0x1734, 0x110e, "Fujitsu V5505",
+		      CXT5045_LAPTOP_HPMICSENSE),
+	SND_PCI_QUIRK(0x1509, 0x1e40, "FIC", CXT5045_LAPTOP_HPMICSENSE),
+	SND_PCI_QUIRK(0x1509, 0x2f05, "FIC", CXT5045_LAPTOP_HPMICSENSE),
+	SND_PCI_QUIRK(0x1509, 0x2f06, "FIC", CXT5045_LAPTOP_HPMICSENSE),
+	SND_PCI_QUIRK_MASK(0x1631, 0xff00, 0xc100, "Packard Bell",
+			   CXT5045_LAPTOP_HPMICSENSE),
+	SND_PCI_QUIRK(0x8086, 0x2111, "Conexant Reference board", CXT5045_LAPTOP_HPSENSE),
+	{}
+};
+
+static int patch_cxt5045(struct hda_codec *codec)
+{
+	struct conexant_spec *spec;
+	int board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	codec->spec = spec;
+	codec->pin_amp_workaround = 1;
+
+	spec->multiout.max_channels = 2;
+	spec->multiout.num_dacs = ARRAY_SIZE(cxt5045_dac_nids);
+	spec->multiout.dac_nids = cxt5045_dac_nids;
+	spec->multiout.dig_out_nid = CXT5045_SPDIF_OUT;
+	spec->num_adc_nids = 1;
+	spec->adc_nids = cxt5045_adc_nids;
+	spec->capsrc_nids = cxt5045_capsrc_nids;
+	spec->input_mux = &cxt5045_capture_source;
+	spec->num_mixers = 1;
+	spec->mixers[0] = cxt5045_mixers;
+	spec->num_init_verbs = 1;
+	spec->init_verbs[0] = cxt5045_init_verbs;
+	spec->spdif_route = 0;
+	spec->num_channel_mode = ARRAY_SIZE(cxt5045_modes);
+	spec->channel_mode = cxt5045_modes;
+
+	set_beep_amp(spec, 0x16, 0, 1);
+
+	codec->patch_ops = conexant_patch_ops;
+
+	board_config = snd_hda_check_board_config(codec, CXT5045_MODELS,
+						  cxt5045_models,
+						  cxt5045_cfg_tbl);
+	switch (board_config) {
+	case CXT5045_LAPTOP_HPSENSE:
+		codec->patch_ops.unsol_event = cxt5045_hp_unsol_event;
+		spec->input_mux = &cxt5045_capture_source;
+		spec->num_init_verbs = 2;
+		spec->init_verbs[1] = cxt5045_hp_sense_init_verbs;
+		spec->mixers[0] = cxt5045_mixers;
+		codec->patch_ops.init = cxt5045_init;
+		break;
+	case CXT5045_LAPTOP_MICSENSE:
+		codec->patch_ops.unsol_event = cxt5045_hp_unsol_event;
+		spec->input_mux = &cxt5045_capture_source;
+		spec->num_init_verbs = 2;
+		spec->init_verbs[1] = cxt5045_mic_sense_init_verbs;
+		spec->mixers[0] = cxt5045_mixers;
+		codec->patch_ops.init = cxt5045_init;
+		break;
+	default:
+	case CXT5045_LAPTOP_HPMICSENSE:
+		codec->patch_ops.unsol_event = cxt5045_hp_unsol_event;
+		spec->input_mux = &cxt5045_capture_source;
+		spec->num_init_verbs = 3;
+		spec->init_verbs[1] = cxt5045_hp_sense_init_verbs;
+		spec->init_verbs[2] = cxt5045_mic_sense_init_verbs;
+		spec->mixers[0] = cxt5045_mixers;
+		codec->patch_ops.init = cxt5045_init;
+		break;
+	case CXT5045_BENQ:
+		codec->patch_ops.unsol_event = cxt5045_hp_unsol_event;
+		spec->input_mux = &cxt5045_capture_source_benq;
+		spec->num_init_verbs = 1;
+		spec->init_verbs[0] = cxt5045_benq_init_verbs;
+		spec->mixers[0] = cxt5045_mixers;
+		spec->mixers[1] = cxt5045_benq_mixers;
+		spec->num_mixers = 2;
+		codec->patch_ops.init = cxt5045_init;
+		break;
+	case CXT5045_LAPTOP_HP530:
+		codec->patch_ops.unsol_event = cxt5045_hp_unsol_event;
+		spec->input_mux = &cxt5045_capture_source_hp530;
+		spec->num_init_verbs = 2;
+		spec->init_verbs[1] = cxt5045_hp_sense_init_verbs;
+		spec->mixers[0] = cxt5045_mixers_hp530;
+		codec->patch_ops.init = cxt5045_init;
+		break;
+#ifdef CONFIG_SND_DEBUG
+	case CXT5045_TEST:
+		spec->input_mux = &cxt5045_test_capture_source;
+		spec->mixers[0] = cxt5045_test_mixer;
+		spec->init_verbs[0] = cxt5045_test_init_verbs;
+		break;
+		
+#endif	
+	}
+
+	switch (codec->subsystem_id >> 16) {
+	case 0x103c:
+	case 0x1631:
+	case 0x1734:
+	case 0x17aa:
+		/* HP, Packard Bell, Fujitsu-Siemens & Lenovo laptops have
+		 * really bad sound over 0dB on NID 0x17. Fix max PCM level to
+		 * 0 dB (originally it has 0x2b steps with 0dB offset 0x14)
+		 */
+		snd_hda_override_amp_caps(codec, 0x17, HDA_INPUT,
+					  (0x14 << AC_AMPCAP_OFFSET_SHIFT) |
+					  (0x14 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+					  (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+					  (1 << AC_AMPCAP_MUTE_SHIFT));
+		break;
+	}
+
+	if (spec->beep_amp)
+		snd_hda_attach_beep_device(codec, spec->beep_amp);
+
+	return 0;
+}
+
+
+/* Conexant 5047 specific */
+#define CXT5047_SPDIF_OUT	0x11
+
+static hda_nid_t cxt5047_dac_nids[1] = { 0x10 }; /* 0x1c */
+static hda_nid_t cxt5047_adc_nids[1] = { 0x12 };
+static hda_nid_t cxt5047_capsrc_nids[1] = { 0x1a };
+
+static struct hda_channel_mode cxt5047_modes[1] = {
+	{ 2, NULL },
+};
+
+static struct hda_input_mux cxt5047_toshiba_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "ExtMic", 0x2 },
+		{ "Line-In", 0x1 },
+	}
+};
+
+/* turn on/off EAPD (+ mute HP) as a master switch */
+static int cxt5047_hp_master_sw_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	unsigned int bits;
+
+	if (!cxt_eapd_put(kcontrol, ucontrol))
+		return 0;
+
+	/* toggle internal speakers mute depending of presence of
+	 * the headphone jack
+	 */
+	bits = (!spec->hp_present && spec->cur_eapd) ? 0 : HDA_AMP_MUTE;
+	/* NOTE: Conexat codec needs the index for *OUTPUT* amp of
+	 * pin widgets unlike other codecs.  In this case, we need to
+	 * set index 0x01 for the volume from the mixer amp 0x19.
+	 */
+	snd_hda_codec_amp_stereo(codec, 0x1d, HDA_OUTPUT, 0x01,
+				 HDA_AMP_MUTE, bits);
+	bits = spec->cur_eapd ? 0 : HDA_AMP_MUTE;
+	snd_hda_codec_amp_stereo(codec, 0x13, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	return 1;
+}
+
+/* mute internal speaker if HP is plugged */
+static void cxt5047_hp_automute(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	unsigned int bits;
+
+	spec->hp_present = snd_hda_jack_detect(codec, 0x13);
+
+	bits = (spec->hp_present || !spec->cur_eapd) ? HDA_AMP_MUTE : 0;
+	/* See the note in cxt5047_hp_master_sw_put */
+	snd_hda_codec_amp_stereo(codec, 0x1d, HDA_OUTPUT, 0x01,
+				 HDA_AMP_MUTE, bits);
+}
+
+/* toggle input of built-in and mic jack appropriately */
+static void cxt5047_hp_automic(struct hda_codec *codec)
+{
+	static struct hda_verb mic_jack_on[] = {
+		{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+		{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+		{}
+	};
+	static struct hda_verb mic_jack_off[] = {
+		{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+		{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+		{}
+	};
+	unsigned int present;
+
+	present = snd_hda_jack_detect(codec, 0x15);
+	if (present)
+		snd_hda_sequence_write(codec, mic_jack_on);
+	else
+		snd_hda_sequence_write(codec, mic_jack_off);
+}
+
+/* unsolicited event for HP jack sensing */
+static void cxt5047_hp_unsol_event(struct hda_codec *codec,
+				  unsigned int res)
+{
+	switch (res >> 26) {
+	case CONEXANT_HP_EVENT:
+		cxt5047_hp_automute(codec);
+		break;
+	case CONEXANT_MIC_EVENT:
+		cxt5047_hp_automic(codec);
+		break;
+	}
+}
+
+static struct snd_kcontrol_new cxt5047_base_mixers[] = {
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x19, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x19, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x1a, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x03, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x12, 0x03, HDA_INPUT),
+	HDA_CODEC_VOLUME("PCM Volume", 0x10, 0x00, HDA_OUTPUT),
+	HDA_CODEC_MUTE("PCM Switch", 0x10, 0x00, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = cxt_eapd_info,
+		.get = cxt_eapd_get,
+		.put = cxt5047_hp_master_sw_put,
+		.private_value = 0x13,
+	},
+
+	{}
+};
+
+static struct snd_kcontrol_new cxt5047_hp_spk_mixers[] = {
+	/* See the note in cxt5047_hp_master_sw_put */
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x1d, 0x01, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x13, 0x00, HDA_OUTPUT),
+	{}
+};
+
+static struct snd_kcontrol_new cxt5047_hp_only_mixers[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x13, 0x00, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct hda_verb cxt5047_init_verbs[] = {
+	/* Line in, Mic, Built-in Mic */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_50 },
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_50 },
+	/* HP, Speaker  */
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	{0x13, AC_VERB_SET_CONNECT_SEL, 0x0}, /* mixer(0x19) */
+	{0x1d, AC_VERB_SET_CONNECT_SEL, 0x1}, /* mixer(0x19) */
+	/* Record selector: Mic */
+	{0x12, AC_VERB_SET_CONNECT_SEL,0x03},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE,
+	 AC_AMP_SET_INPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x17},
+	{0x1A, AC_VERB_SET_CONNECT_SEL,0x02},
+	{0x1A, AC_VERB_SET_AMP_GAIN_MUTE,
+	 AC_AMP_SET_OUTPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x00},
+	{0x1A, AC_VERB_SET_AMP_GAIN_MUTE,
+	 AC_AMP_SET_OUTPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x03},
+	/* SPDIF route: PCM */
+	{ 0x18, AC_VERB_SET_CONNECT_SEL, 0x0 },
+	/* Enable unsolicited events */
+	{0x13, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
+	{ } /* end */
+};
+
+/* configuration for Toshiba Laptops */
+static struct hda_verb cxt5047_toshiba_init_verbs[] = {
+	{0x13, AC_VERB_SET_EAPD_BTLENABLE, 0x0}, /* default off */
+	{}
+};
+
+/* Test configuration for debugging, modelled after the ALC260 test
+ * configuration.
+ */
+#ifdef CONFIG_SND_DEBUG
+static struct hda_input_mux cxt5047_test_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "LINE1 pin", 0x0 },
+		{ "MIC1 pin", 0x1 },
+		{ "MIC2 pin", 0x2 },
+		{ "CD pin", 0x3 },
+        },
+};
+
+static struct snd_kcontrol_new cxt5047_test_mixer[] = {
+
+	/* Output only controls */
+	HDA_CODEC_VOLUME("OutAmp-1 Volume", 0x10, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("OutAmp-1 Switch", 0x10,0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("OutAmp-2 Volume", 0x1c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("OutAmp-2 Switch", 0x1c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x1d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x1d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("HeadPhone Playback Volume", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("HeadPhone Playback Switch", 0x13, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line1-Out Playback Volume", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Line1-Out Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line2-Out Playback Volume", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Line2-Out Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+
+	/* Modes for retasking pin widgets */
+	CXT_PIN_MODE("LINE1 pin mode", 0x14, CXT_PIN_DIR_INOUT),
+	CXT_PIN_MODE("MIC1 pin mode", 0x15, CXT_PIN_DIR_INOUT),
+
+	/* EAPD Switch Control */
+	CXT_EAPD_SWITCH("External Amplifier", 0x13, 0x0),
+
+	/* Loopback mixer controls */
+	HDA_CODEC_VOLUME("MIC1 Playback Volume", 0x12, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("MIC1 Playback Switch", 0x12, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("MIC2 Playback Volume", 0x12, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("MIC2 Playback Switch", 0x12, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("LINE Playback Volume", 0x12, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("LINE Playback Switch", 0x12, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x12, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x12, 0x04, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Capture-1 Volume", 0x19, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture-1 Switch", 0x19, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture-2 Volume", 0x19, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture-2 Switch", 0x19, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture-3 Volume", 0x19, 0x2, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture-3 Switch", 0x19, 0x2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture-4 Volume", 0x19, 0x3, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture-4 Switch", 0x19, 0x3, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Input Source",
+		.info = conexant_mux_enum_info,
+		.get = conexant_mux_enum_get,
+		.put = conexant_mux_enum_put,
+	},
+	HDA_CODEC_VOLUME("Mic Boost Volume", 0x1a, 0x0, HDA_OUTPUT),
+
+	{ } /* end */
+};
+
+static struct hda_verb cxt5047_test_init_verbs[] = {
+	/* Enable retasking pins as output, initially without power amp */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	/* Disable digital (SPDIF) pins initially, but users can enable
+	 * them via a mixer switch.  In the case of SPDIF-out, this initverb
+	 * payload also sets the generation to 0, output to be in "consumer"
+	 * PCM format, copyright asserted, no pre-emphasis and no validity
+	 * control.
+	 */
+	{0x18, AC_VERB_SET_DIGI_CONVERT_1, 0},
+
+	/* Ensure mic1, mic2, line1 pin widgets take input from the 
+	 * OUT1 sum bus when acting as an output.
+	 */
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0},
+
+	/* Start with output sum widgets muted and their output gains at min */
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+
+	/* Unmute retasking pin widget output buffers since the default
+	 * state appears to be output.  As the pin mode is changed by the
+	 * user the pin mode control will take care of enabling the pin's
+	 * input/output buffers as needed.
+	 */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Mute capture amp left and right */
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+
+	/* Set ADC connection select to match default mixer setting (mic1
+	 * pin)
+	 */
+	{0x12, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Mute all inputs to mixer widget (even unconnected ones) */
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* line1 pin */
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* line2 pin */
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, /* Beep-gen pin */
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, /* Line-out pin */
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, /* HP-pin pin */
+
+	{ }
+};
+#endif
+
+
+/* initialize jack-sensing, too */
+static int cxt5047_hp_init(struct hda_codec *codec)
+{
+	conexant_init(codec);
+	cxt5047_hp_automute(codec);
+	return 0;
+}
+
+
+enum {
+	CXT5047_LAPTOP,		/* Laptops w/o EAPD support */
+	CXT5047_LAPTOP_HP,	/* Some HP laptops */
+	CXT5047_LAPTOP_EAPD,	/* Laptops with EAPD support */
+#ifdef CONFIG_SND_DEBUG
+	CXT5047_TEST,
+#endif
+	CXT5047_MODELS
+};
+
+static const char *cxt5047_models[CXT5047_MODELS] = {
+	[CXT5047_LAPTOP]	= "laptop",
+	[CXT5047_LAPTOP_HP]	= "laptop-hp",
+	[CXT5047_LAPTOP_EAPD]	= "laptop-eapd",
+#ifdef CONFIG_SND_DEBUG
+	[CXT5047_TEST]		= "test",
+#endif
+};
+
+static struct snd_pci_quirk cxt5047_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x103c, 0x30a5, "HP DV5200T/DV8000T", CXT5047_LAPTOP_HP),
+	SND_PCI_QUIRK_MASK(0x103c, 0xff00, 0x3000, "HP DV Series",
+			   CXT5047_LAPTOP),
+	SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba P100", CXT5047_LAPTOP_EAPD),
+	{}
+};
+
+static int patch_cxt5047(struct hda_codec *codec)
+{
+	struct conexant_spec *spec;
+	int board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	codec->spec = spec;
+	codec->pin_amp_workaround = 1;
+
+	spec->multiout.max_channels = 2;
+	spec->multiout.num_dacs = ARRAY_SIZE(cxt5047_dac_nids);
+	spec->multiout.dac_nids = cxt5047_dac_nids;
+	spec->multiout.dig_out_nid = CXT5047_SPDIF_OUT;
+	spec->num_adc_nids = 1;
+	spec->adc_nids = cxt5047_adc_nids;
+	spec->capsrc_nids = cxt5047_capsrc_nids;
+	spec->num_mixers = 1;
+	spec->mixers[0] = cxt5047_base_mixers;
+	spec->num_init_verbs = 1;
+	spec->init_verbs[0] = cxt5047_init_verbs;
+	spec->spdif_route = 0;
+	spec->num_channel_mode = ARRAY_SIZE(cxt5047_modes),
+	spec->channel_mode = cxt5047_modes,
+
+	codec->patch_ops = conexant_patch_ops;
+
+	board_config = snd_hda_check_board_config(codec, CXT5047_MODELS,
+						  cxt5047_models,
+						  cxt5047_cfg_tbl);
+	switch (board_config) {
+	case CXT5047_LAPTOP:
+		spec->num_mixers = 2;
+		spec->mixers[1] = cxt5047_hp_spk_mixers;
+		codec->patch_ops.unsol_event = cxt5047_hp_unsol_event;
+		break;
+	case CXT5047_LAPTOP_HP:
+		spec->num_mixers = 2;
+		spec->mixers[1] = cxt5047_hp_only_mixers;
+		codec->patch_ops.unsol_event = cxt5047_hp_unsol_event;
+		codec->patch_ops.init = cxt5047_hp_init;
+		break;
+	case CXT5047_LAPTOP_EAPD:
+		spec->input_mux = &cxt5047_toshiba_capture_source;
+		spec->num_mixers = 2;
+		spec->mixers[1] = cxt5047_hp_spk_mixers;
+		spec->num_init_verbs = 2;
+		spec->init_verbs[1] = cxt5047_toshiba_init_verbs;
+		codec->patch_ops.unsol_event = cxt5047_hp_unsol_event;
+		break;
+#ifdef CONFIG_SND_DEBUG
+	case CXT5047_TEST:
+		spec->input_mux = &cxt5047_test_capture_source;
+		spec->mixers[0] = cxt5047_test_mixer;
+		spec->init_verbs[0] = cxt5047_test_init_verbs;
+		codec->patch_ops.unsol_event = cxt5047_hp_unsol_event;
+#endif	
+	}
+	spec->vmaster_nid = 0x13;
+
+	switch (codec->subsystem_id >> 16) {
+	case 0x103c:
+		/* HP laptops have really bad sound over 0 dB on NID 0x10.
+		 * Fix max PCM level to 0 dB (originally it has 0x1e steps
+		 * with 0 dB offset 0x17)
+		 */
+		snd_hda_override_amp_caps(codec, 0x10, HDA_INPUT,
+					  (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+					  (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+					  (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+					  (1 << AC_AMPCAP_MUTE_SHIFT));
+		break;
+	}
+
+	return 0;
+}
+
+/* Conexant 5051 specific */
+static hda_nid_t cxt5051_dac_nids[1] = { 0x10 };
+static hda_nid_t cxt5051_adc_nids[2] = { 0x14, 0x15 };
+
+static struct hda_channel_mode cxt5051_modes[1] = {
+	{ 2, NULL },
+};
+
+static void cxt5051_update_speaker(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	unsigned int pinctl;
+	/* headphone pin */
+	pinctl = (spec->hp_present && spec->cur_eapd) ? PIN_HP : 0;
+	snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    pinctl);
+	/* speaker pin */
+	pinctl = (!spec->hp_present && spec->cur_eapd) ? PIN_OUT : 0;
+	snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    pinctl);
+	/* on ideapad there is an aditional speaker (subwoofer) to mute */
+	if (spec->ideapad)
+		snd_hda_codec_write(codec, 0x1b, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL,
+				    pinctl);
+}
+
+/* turn on/off EAPD (+ mute HP) as a master switch */
+static int cxt5051_hp_master_sw_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	if (!cxt_eapd_put(kcontrol, ucontrol))
+		return 0;
+	cxt5051_update_speaker(codec);
+	return 1;
+}
+
+/* toggle input of built-in and mic jack appropriately */
+static void cxt5051_portb_automic(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	unsigned int present;
+
+	if (!(spec->auto_mic & AUTO_MIC_PORTB))
+		return;
+	present = snd_hda_jack_detect(codec, 0x17);
+	snd_hda_codec_write(codec, 0x14, 0,
+			    AC_VERB_SET_CONNECT_SEL,
+			    present ? 0x01 : 0x00);
+}
+
+/* switch the current ADC according to the jack state */
+static void cxt5051_portc_automic(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	unsigned int present;
+	hda_nid_t new_adc;
+
+	if (!(spec->auto_mic & AUTO_MIC_PORTC))
+		return;
+	present = snd_hda_jack_detect(codec, 0x18);
+	if (present)
+		spec->cur_adc_idx = 1;
+	else
+		spec->cur_adc_idx = 0;
+	new_adc = spec->adc_nids[spec->cur_adc_idx];
+	if (spec->cur_adc && spec->cur_adc != new_adc) {
+		/* stream is running, let's swap the current ADC */
+		__snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1);
+		spec->cur_adc = new_adc;
+		snd_hda_codec_setup_stream(codec, new_adc,
+					   spec->cur_adc_stream_tag, 0,
+					   spec->cur_adc_format);
+	}
+}
+
+/* mute internal speaker if HP is plugged */
+static void cxt5051_hp_automute(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+
+	spec->hp_present = snd_hda_jack_detect(codec, 0x16);
+	cxt5051_update_speaker(codec);
+}
+
+/* unsolicited event for HP jack sensing */
+static void cxt5051_hp_unsol_event(struct hda_codec *codec,
+				   unsigned int res)
+{
+	int nid = (res & AC_UNSOL_RES_SUBTAG) >> 20;
+	switch (res >> 26) {
+	case CONEXANT_HP_EVENT:
+		cxt5051_hp_automute(codec);
+		break;
+	case CXT5051_PORTB_EVENT:
+		cxt5051_portb_automic(codec);
+		break;
+	case CXT5051_PORTC_EVENT:
+		cxt5051_portc_automic(codec);
+		break;
+	}
+	conexant_report_jack(codec, nid);
+}
+
+static struct snd_kcontrol_new cxt5051_playback_mixers[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = cxt_eapd_info,
+		.get = cxt_eapd_get,
+		.put = cxt5051_hp_master_sw_put,
+		.private_value = 0x1a,
+	},
+	{}
+};
+
+static struct snd_kcontrol_new cxt5051_capture_mixers[] = {
+	HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("External Mic Volume", 0x14, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("External Mic Switch", 0x14, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Docking Mic Volume", 0x15, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Docking Mic Switch", 0x15, 0x00, HDA_INPUT),
+	{}
+};
+
+static struct snd_kcontrol_new cxt5051_hp_mixers[] = {
+	HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("External Mic Volume", 0x15, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("External Mic Switch", 0x15, 0x00, HDA_INPUT),
+	{}
+};
+
+static struct snd_kcontrol_new cxt5051_hp_dv6736_mixers[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x14, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x14, 0x00, HDA_INPUT),
+	{}
+};
+
+static struct snd_kcontrol_new cxt5051_f700_mixers[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x14, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x14, 0x01, HDA_INPUT),
+	{}
+};
+
+static struct snd_kcontrol_new cxt5051_toshiba_mixers[] = {
+	HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("External Mic Volume", 0x14, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("External Mic Switch", 0x14, 0x01, HDA_INPUT),
+	{}
+};
+
+static struct hda_verb cxt5051_init_verbs[] = {
+	/* Line in, Mic */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
+	/* SPK  */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* HP, Amp  */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* DAC1 */	
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Record selector: Int mic */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44},
+	/* SPDIF route: PCM */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1c, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* EAPD */
+	{0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ 
+	{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
+	{ } /* end */
+};
+
+static struct hda_verb cxt5051_hp_dv6736_init_verbs[] = {
+	/* Line in, Mic */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0},
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0},
+	/* SPK  */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* HP, Amp  */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* DAC1 */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Record selector: Int mic */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x1},
+	/* SPDIF route: PCM */
+	{0x1c, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* EAPD */
+	{0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+	{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
+	{ } /* end */
+};
+
+static struct hda_verb cxt5051_lenovo_x200_init_verbs[] = {
+	/* Line in, Mic */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
+	/* SPK  */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* HP, Amp  */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* Docking HP */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x19, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* DAC1 */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Record selector: Int mic */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44},
+	/* SPDIF route: PCM */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, /* needed for W500 Advanced Mini Dock 250410 */
+	{0x1c, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* EAPD */
+	{0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+	{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
+	{ } /* end */
+};
+
+static struct hda_verb cxt5051_f700_init_verbs[] = {
+	/* Line in, Mic */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0},
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0},
+	/* SPK  */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* HP, Amp  */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* DAC1 */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Record selector: Int mic */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x1},
+	/* SPDIF route: PCM */
+	{0x1c, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* EAPD */
+	{0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+	{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
+	{ } /* end */
+};
+
+static void cxt5051_init_mic_port(struct hda_codec *codec, hda_nid_t nid,
+				 unsigned int event)
+{
+	snd_hda_codec_write(codec, nid, 0,
+			    AC_VERB_SET_UNSOLICITED_ENABLE,
+			    AC_USRSP_EN | event);
+#ifdef CONFIG_SND_HDA_INPUT_JACK
+	conexant_add_jack(codec, nid, SND_JACK_MICROPHONE);
+	conexant_report_jack(codec, nid);
+#endif
+}
+
+static struct hda_verb cxt5051_ideapad_init_verbs[] = {
+	/* Subwoofer */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{ } /* end */
+};
+
+/* initialize jack-sensing, too */
+static int cxt5051_init(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+
+	conexant_init(codec);
+	conexant_init_jacks(codec);
+
+	if (spec->auto_mic & AUTO_MIC_PORTB)
+		cxt5051_init_mic_port(codec, 0x17, CXT5051_PORTB_EVENT);
+	if (spec->auto_mic & AUTO_MIC_PORTC)
+		cxt5051_init_mic_port(codec, 0x18, CXT5051_PORTC_EVENT);
+
+	if (codec->patch_ops.unsol_event) {
+		cxt5051_hp_automute(codec);
+		cxt5051_portb_automic(codec);
+		cxt5051_portc_automic(codec);
+	}
+	return 0;
+}
+
+
+enum {
+	CXT5051_LAPTOP,	 /* Laptops w/ EAPD support */
+	CXT5051_HP,	/* no docking */
+	CXT5051_HP_DV6736,	/* HP without mic switch */
+	CXT5051_LENOVO_X200,	/* Lenovo X200 laptop, also used for Advanced Mini Dock 250410 */
+	CXT5051_F700,       /* HP Compaq Presario F700 */
+	CXT5051_TOSHIBA,	/* Toshiba M300 & co */
+	CXT5051_IDEAPAD,	/* Lenovo IdeaPad Y430 */
+	CXT5051_MODELS
+};
+
+static const char *cxt5051_models[CXT5051_MODELS] = {
+	[CXT5051_LAPTOP]	= "laptop",
+	[CXT5051_HP]		= "hp",
+	[CXT5051_HP_DV6736]	= "hp-dv6736",
+	[CXT5051_LENOVO_X200]	= "lenovo-x200",
+	[CXT5051_F700]          = "hp-700",
+	[CXT5051_TOSHIBA]	= "toshiba",
+	[CXT5051_IDEAPAD]	= "ideapad",
+};
+
+static struct snd_pci_quirk cxt5051_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x103c, 0x30cf, "HP DV6736", CXT5051_HP_DV6736),
+	SND_PCI_QUIRK(0x103c, 0x360b, "Compaq Presario CQ60", CXT5051_HP),
+	SND_PCI_QUIRK(0x103c, 0x30ea, "Compaq Presario F700", CXT5051_F700),
+	SND_PCI_QUIRK(0x1179, 0xff50, "Toshiba M30x", CXT5051_TOSHIBA),
+	SND_PCI_QUIRK(0x14f1, 0x0101, "Conexant Reference board",
+		      CXT5051_LAPTOP),
+	SND_PCI_QUIRK(0x14f1, 0x5051, "HP Spartan 1.1", CXT5051_HP),
+	SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo X200", CXT5051_LENOVO_X200),
+	SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo IdeaPad", CXT5051_IDEAPAD),
+	{}
+};
+
+static int patch_cxt5051(struct hda_codec *codec)
+{
+	struct conexant_spec *spec;
+	int board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	codec->spec = spec;
+	codec->pin_amp_workaround = 1;
+
+	codec->patch_ops = conexant_patch_ops;
+	codec->patch_ops.init = cxt5051_init;
+
+	spec->multiout.max_channels = 2;
+	spec->multiout.num_dacs = ARRAY_SIZE(cxt5051_dac_nids);
+	spec->multiout.dac_nids = cxt5051_dac_nids;
+	spec->multiout.dig_out_nid = CXT5051_SPDIF_OUT;
+	spec->num_adc_nids = 1; /* not 2; via auto-mic switch */
+	spec->adc_nids = cxt5051_adc_nids;
+	spec->num_mixers = 2;
+	spec->mixers[0] = cxt5051_capture_mixers;
+	spec->mixers[1] = cxt5051_playback_mixers;
+	spec->num_init_verbs = 1;
+	spec->init_verbs[0] = cxt5051_init_verbs;
+	spec->spdif_route = 0;
+	spec->num_channel_mode = ARRAY_SIZE(cxt5051_modes);
+	spec->channel_mode = cxt5051_modes;
+	spec->cur_adc = 0;
+	spec->cur_adc_idx = 0;
+
+	set_beep_amp(spec, 0x13, 0, HDA_OUTPUT);
+
+	codec->patch_ops.unsol_event = cxt5051_hp_unsol_event;
+
+	board_config = snd_hda_check_board_config(codec, CXT5051_MODELS,
+						  cxt5051_models,
+						  cxt5051_cfg_tbl);
+	spec->auto_mic = AUTO_MIC_PORTB | AUTO_MIC_PORTC;
+	switch (board_config) {
+	case CXT5051_HP:
+		spec->mixers[0] = cxt5051_hp_mixers;
+		break;
+	case CXT5051_HP_DV6736:
+		spec->init_verbs[0] = cxt5051_hp_dv6736_init_verbs;
+		spec->mixers[0] = cxt5051_hp_dv6736_mixers;
+		spec->auto_mic = 0;
+		break;
+	case CXT5051_LENOVO_X200:
+		spec->init_verbs[0] = cxt5051_lenovo_x200_init_verbs;
+		/* Thinkpad X301 does not have S/PDIF wired and no ability
+		   to use a docking station. */
+		if (codec->subsystem_id == 0x17aa211f)
+			spec->multiout.dig_out_nid = 0;
+		break;
+	case CXT5051_F700:
+		spec->init_verbs[0] = cxt5051_f700_init_verbs;
+		spec->mixers[0] = cxt5051_f700_mixers;
+		spec->auto_mic = 0;
+		break;
+	case CXT5051_TOSHIBA:
+		spec->mixers[0] = cxt5051_toshiba_mixers;
+		spec->auto_mic = AUTO_MIC_PORTB;
+		break;
+	case CXT5051_IDEAPAD:
+		spec->init_verbs[spec->num_init_verbs++] =
+			cxt5051_ideapad_init_verbs;
+		spec->ideapad = 1;
+		break;
+	}
+
+	if (spec->beep_amp)
+		snd_hda_attach_beep_device(codec, spec->beep_amp);
+
+	return 0;
+}
+
+/* Conexant 5066 specific */
+
+static hda_nid_t cxt5066_dac_nids[1] = { 0x10 };
+static hda_nid_t cxt5066_adc_nids[3] = { 0x14, 0x15, 0x16 };
+static hda_nid_t cxt5066_capsrc_nids[1] = { 0x17 };
+#define CXT5066_SPDIF_OUT	0x21
+
+/* OLPC's microphone port is DC coupled for use with external sensors,
+ * therefore we use a 50% mic bias in order to center the input signal with
+ * the DC input range of the codec. */
+#define CXT5066_OLPC_EXT_MIC_BIAS PIN_VREF50
+
+static struct hda_channel_mode cxt5066_modes[1] = {
+	{ 2, NULL },
+};
+
+static void cxt5066_update_speaker(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	unsigned int pinctl;
+
+	snd_printdd("CXT5066: update speaker, hp_present=%d, cur_eapd=%d\n",
+		    spec->hp_present, spec->cur_eapd);
+
+	/* Port A (HP) */
+	pinctl = ((spec->hp_present & 1) && spec->cur_eapd) ? PIN_HP : 0;
+	snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			pinctl);
+
+	/* Port D (HP/LO) */
+	if (spec->dell_automute) {
+		/* DELL AIO Port Rule: PortA>  PortD>  IntSpk */
+		pinctl = (!(spec->hp_present & 1) && spec->cur_eapd)
+			? PIN_OUT : 0;
+	} else if (spec->thinkpad) {
+		if (spec->cur_eapd)
+			pinctl = spec->port_d_mode;
+		/* Mute dock line-out if Port A (laptop HP) is present */
+		if (spec->hp_present&  1)
+			pinctl = 0;
+	} else {
+		pinctl = ((spec->hp_present & 2) && spec->cur_eapd)
+			? spec->port_d_mode : 0;
+	}
+	snd_hda_codec_write(codec, 0x1c, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			pinctl);
+
+	/* CLASS_D AMP */
+	pinctl = (!spec->hp_present && spec->cur_eapd) ? PIN_OUT : 0;
+	snd_hda_codec_write(codec, 0x1f, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			pinctl);
+}
+
+/* turn on/off EAPD (+ mute HP) as a master switch */
+static int cxt5066_hp_master_sw_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	if (!cxt_eapd_put(kcontrol, ucontrol))
+		return 0;
+
+	cxt5066_update_speaker(codec);
+	return 1;
+}
+
+static const struct hda_input_mux cxt5066_olpc_dc_bias = {
+	.num_items = 3,
+	.items = {
+		{ "Off", PIN_IN },
+		{ "50%", PIN_VREF50 },
+		{ "80%", PIN_VREF80 },
+	},
+};
+
+static int cxt5066_set_olpc_dc_bias(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	/* Even though port F is the DC input, the bias is controlled on port B.
+	 * we also leave that port as an active input (but unselected) in DC mode
+	 * just in case that is necessary to make the bias setting take effect. */
+	return snd_hda_codec_write_cache(codec, 0x1a, 0,
+		AC_VERB_SET_PIN_WIDGET_CONTROL,
+		cxt5066_olpc_dc_bias.items[spec->dc_input_bias].index);
+}
+
+/* OLPC defers mic widget control until when capture is started because the
+ * microphone LED comes on as soon as these settings are put in place. if we
+ * did this before recording, it would give the false indication that recording
+ * is happening when it is not. */
+static void cxt5066_olpc_select_mic(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	if (!spec->recording)
+		return;
+
+	if (spec->dc_enable) {
+		/* in DC mode we ignore presence detection and just use the jack
+		 * through our special DC port */
+		const struct hda_verb enable_dc_mode[] = {
+			/* disble internal mic, port C */
+			{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+			/* enable DC capture, port F */
+			{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+			{},
+		};
+
+		snd_hda_sequence_write(codec, enable_dc_mode);
+		/* port B input disabled (and bias set) through the following call */
+		cxt5066_set_olpc_dc_bias(codec);
+		return;
+	}
+
+	/* disable DC (port F) */
+	snd_hda_codec_write(codec, 0x1e, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+
+	/* external mic, port B */
+	snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+		spec->ext_mic_present ? CXT5066_OLPC_EXT_MIC_BIAS : 0);
+
+	/* internal mic, port C */
+	snd_hda_codec_write(codec, 0x1b, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+		spec->ext_mic_present ? 0 : PIN_VREF80);
+}
+
+/* toggle input of built-in and mic jack appropriately */
+static void cxt5066_olpc_automic(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	unsigned int present;
+
+	if (spec->dc_enable) /* don't do presence detection in DC mode */
+		return;
+
+	present = snd_hda_codec_read(codec, 0x1a, 0,
+				     AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
+	if (present)
+		snd_printdd("CXT5066: external microphone detected\n");
+	else
+		snd_printdd("CXT5066: external microphone absent\n");
+
+	snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_CONNECT_SEL,
+		present ? 0 : 1);
+	spec->ext_mic_present = !!present;
+
+	cxt5066_olpc_select_mic(codec);
+}
+
+/* toggle input of built-in digital mic and mic jack appropriately */
+static void cxt5066_vostro_automic(struct hda_codec *codec)
+{
+	unsigned int present;
+
+	struct hda_verb ext_mic_present[] = {
+		/* enable external mic, port B */
+		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+
+		/* switch to external mic input */
+		{0x17, AC_VERB_SET_CONNECT_SEL, 0},
+		{0x14, AC_VERB_SET_CONNECT_SEL, 0},
+
+		/* disable internal digital mic */
+		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{}
+	};
+	static struct hda_verb ext_mic_absent[] = {
+		/* enable internal mic, port C */
+		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+		/* switch to internal mic input */
+		{0x14, AC_VERB_SET_CONNECT_SEL, 2},
+
+		/* disable external mic, port B */
+		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{}
+	};
+
+	present = snd_hda_jack_detect(codec, 0x1a);
+	if (present) {
+		snd_printdd("CXT5066: external microphone detected\n");
+		snd_hda_sequence_write(codec, ext_mic_present);
+	} else {
+		snd_printdd("CXT5066: external microphone absent\n");
+		snd_hda_sequence_write(codec, ext_mic_absent);
+	}
+}
+
+/* toggle input of built-in digital mic and mic jack appropriately */
+static void cxt5066_ideapad_automic(struct hda_codec *codec)
+{
+	unsigned int present;
+
+	struct hda_verb ext_mic_present[] = {
+		{0x14, AC_VERB_SET_CONNECT_SEL, 0},
+		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{}
+	};
+	static struct hda_verb ext_mic_absent[] = {
+		{0x14, AC_VERB_SET_CONNECT_SEL, 2},
+		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{}
+	};
+
+	present = snd_hda_jack_detect(codec, 0x1b);
+	if (present) {
+		snd_printdd("CXT5066: external microphone detected\n");
+		snd_hda_sequence_write(codec, ext_mic_present);
+	} else {
+		snd_printdd("CXT5066: external microphone absent\n");
+		snd_hda_sequence_write(codec, ext_mic_absent);
+	}
+}
+
+/* toggle input of built-in digital mic and mic jack appropriately */
+static void cxt5066_hp_laptop_automic(struct hda_codec *codec)
+{
+	unsigned int present;
+
+	present = snd_hda_jack_detect(codec, 0x1b);
+	snd_printdd("CXT5066: external microphone present=%d\n", present);
+	snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_CONNECT_SEL,
+			    present ? 1 : 3);
+}
+
+
+/* toggle input of built-in digital mic and mic jack appropriately
+   order is: external mic -> dock mic -> interal mic */
+static void cxt5066_thinkpad_automic(struct hda_codec *codec)
+{
+	unsigned int ext_present, dock_present;
+
+	static struct hda_verb ext_mic_present[] = {
+		{0x14, AC_VERB_SET_CONNECT_SEL, 0},
+		{0x17, AC_VERB_SET_CONNECT_SEL, 1},
+		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{}
+	};
+	static struct hda_verb dock_mic_present[] = {
+		{0x14, AC_VERB_SET_CONNECT_SEL, 0},
+		{0x17, AC_VERB_SET_CONNECT_SEL, 0},
+		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{}
+	};
+	static struct hda_verb ext_mic_absent[] = {
+		{0x14, AC_VERB_SET_CONNECT_SEL, 2},
+		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{}
+	};
+
+	ext_present = snd_hda_jack_detect(codec, 0x1b);
+	dock_present = snd_hda_jack_detect(codec, 0x1a);
+	if (ext_present) {
+		snd_printdd("CXT5066: external microphone detected\n");
+		snd_hda_sequence_write(codec, ext_mic_present);
+	} else if (dock_present) {
+		snd_printdd("CXT5066: dock microphone detected\n");
+		snd_hda_sequence_write(codec, dock_mic_present);
+	} else {
+		snd_printdd("CXT5066: external microphone absent\n");
+		snd_hda_sequence_write(codec, ext_mic_absent);
+	}
+}
+
+/* mute internal speaker if HP is plugged */
+static void cxt5066_hp_automute(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	unsigned int portA, portD;
+
+	/* Port A */
+	portA = snd_hda_jack_detect(codec, 0x19);
+
+	/* Port D */
+	portD = snd_hda_jack_detect(codec, 0x1c);
+
+	spec->hp_present = !!(portA);
+	spec->hp_present |= portD ? 2 : 0;
+	snd_printdd("CXT5066: hp automute portA=%x portD=%x present=%d\n",
+		portA, portD, spec->hp_present);
+	cxt5066_update_speaker(codec);
+}
+
+/* unsolicited event for jack sensing */
+static void cxt5066_olpc_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	struct conexant_spec *spec = codec->spec;
+	snd_printdd("CXT5066: unsol event %x (%x)\n", res, res >> 26);
+	switch (res >> 26) {
+	case CONEXANT_HP_EVENT:
+		cxt5066_hp_automute(codec);
+		break;
+	case CONEXANT_MIC_EVENT:
+		/* ignore mic events in DC mode; we're always using the jack */
+		if (!spec->dc_enable)
+			cxt5066_olpc_automic(codec);
+		break;
+	}
+}
+
+/* unsolicited event for jack sensing */
+static void cxt5066_vostro_event(struct hda_codec *codec, unsigned int res)
+{
+	snd_printdd("CXT5066_vostro: unsol event %x (%x)\n", res, res >> 26);
+	switch (res >> 26) {
+	case CONEXANT_HP_EVENT:
+		cxt5066_hp_automute(codec);
+		break;
+	case CONEXANT_MIC_EVENT:
+		cxt5066_vostro_automic(codec);
+		break;
+	}
+}
+
+/* unsolicited event for jack sensing */
+static void cxt5066_ideapad_event(struct hda_codec *codec, unsigned int res)
+{
+	snd_printdd("CXT5066_ideapad: unsol event %x (%x)\n", res, res >> 26);
+	switch (res >> 26) {
+	case CONEXANT_HP_EVENT:
+		cxt5066_hp_automute(codec);
+		break;
+	case CONEXANT_MIC_EVENT:
+		cxt5066_ideapad_automic(codec);
+		break;
+	}
+}
+
+/* unsolicited event for jack sensing */
+static void cxt5066_hp_laptop_event(struct hda_codec *codec, unsigned int res)
+{
+	snd_printdd("CXT5066_hp_laptop: unsol event %x (%x)\n", res, res >> 26);
+	switch (res >> 26) {
+	case CONEXANT_HP_EVENT:
+		cxt5066_hp_automute(codec);
+		break;
+	case CONEXANT_MIC_EVENT:
+		cxt5066_hp_laptop_automic(codec);
+		break;
+	}
+}
+
+/* unsolicited event for jack sensing */
+static void cxt5066_thinkpad_event(struct hda_codec *codec, unsigned int res)
+{
+	snd_printdd("CXT5066_thinkpad: unsol event %x (%x)\n", res, res >> 26);
+	switch (res >> 26) {
+	case CONEXANT_HP_EVENT:
+		cxt5066_hp_automute(codec);
+		break;
+	case CONEXANT_MIC_EVENT:
+		cxt5066_thinkpad_automic(codec);
+		break;
+	}
+}
+
+static const struct hda_input_mux cxt5066_analog_mic_boost = {
+	.num_items = 5,
+	.items = {
+		{ "0dB",  0 },
+		{ "10dB", 1 },
+		{ "20dB", 2 },
+		{ "30dB", 3 },
+		{ "40dB", 4 },
+	},
+};
+
+static void cxt5066_set_mic_boost(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	snd_hda_codec_write_cache(codec, 0x17, 0,
+		AC_VERB_SET_AMP_GAIN_MUTE,
+		AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_OUTPUT |
+			cxt5066_analog_mic_boost.items[spec->mic_boost].index);
+	if (spec->ideapad || spec->thinkpad) {
+		/* adjust the internal mic as well...it is not through 0x17 */
+		snd_hda_codec_write_cache(codec, 0x23, 0,
+			AC_VERB_SET_AMP_GAIN_MUTE,
+			AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_INPUT |
+				cxt5066_analog_mic_boost.
+					items[spec->mic_boost].index);
+	}
+}
+
+static int cxt5066_mic_boost_mux_enum_info(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	return snd_hda_input_mux_info(&cxt5066_analog_mic_boost, uinfo);
+}
+
+static int cxt5066_mic_boost_mux_enum_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	ucontrol->value.enumerated.item[0] = spec->mic_boost;
+	return 0;
+}
+
+static int cxt5066_mic_boost_mux_enum_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	const struct hda_input_mux *imux = &cxt5066_analog_mic_boost;
+	unsigned int idx;
+	idx = ucontrol->value.enumerated.item[0];
+	if (idx >= imux->num_items)
+		idx = imux->num_items - 1;
+
+	spec->mic_boost = idx;
+	if (!spec->dc_enable)
+		cxt5066_set_mic_boost(codec);
+	return 1;
+}
+
+static void cxt5066_enable_dc(struct hda_codec *codec)
+{
+	const struct hda_verb enable_dc_mode[] = {
+		/* disable gain */
+		{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+		/* switch to DC input */
+		{0x17, AC_VERB_SET_CONNECT_SEL, 3},
+		{}
+	};
+
+	/* configure as input source */
+	snd_hda_sequence_write(codec, enable_dc_mode);
+	cxt5066_olpc_select_mic(codec); /* also sets configured bias */
+}
+
+static void cxt5066_disable_dc(struct hda_codec *codec)
+{
+	/* reconfigure input source */
+	cxt5066_set_mic_boost(codec);
+	/* automic also selects the right mic if we're recording */
+	cxt5066_olpc_automic(codec);
+}
+
+static int cxt5066_olpc_dc_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	ucontrol->value.integer.value[0] = spec->dc_enable;
+	return 0;
+}
+
+static int cxt5066_olpc_dc_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	int dc_enable = !!ucontrol->value.integer.value[0];
+
+	if (dc_enable == spec->dc_enable)
+		return 0;
+
+	spec->dc_enable = dc_enable;
+	if (dc_enable)
+		cxt5066_enable_dc(codec);
+	else
+		cxt5066_disable_dc(codec);
+
+	return 1;
+}
+
+static int cxt5066_olpc_dc_bias_enum_info(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	return snd_hda_input_mux_info(&cxt5066_olpc_dc_bias, uinfo);
+}
+
+static int cxt5066_olpc_dc_bias_enum_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	ucontrol->value.enumerated.item[0] = spec->dc_input_bias;
+	return 0;
+}
+
+static int cxt5066_olpc_dc_bias_enum_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	const struct hda_input_mux *imux = &cxt5066_analog_mic_boost;
+	unsigned int idx;
+
+	idx = ucontrol->value.enumerated.item[0];
+	if (idx >= imux->num_items)
+		idx = imux->num_items - 1;
+
+	spec->dc_input_bias = idx;
+	if (spec->dc_enable)
+		cxt5066_set_olpc_dc_bias(codec);
+	return 1;
+}
+
+static void cxt5066_olpc_capture_prepare(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	/* mark as recording and configure the microphone widget so that the
+	 * recording LED comes on. */
+	spec->recording = 1;
+	cxt5066_olpc_select_mic(codec);
+}
+
+static void cxt5066_olpc_capture_cleanup(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	const struct hda_verb disable_mics[] = {
+		/* disable external mic, port B */
+		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+		/* disble internal mic, port C */
+		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+		/* disable DC capture, port F */
+		{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{},
+	};
+
+	snd_hda_sequence_write(codec, disable_mics);
+	spec->recording = 0;
+}
+
+static struct hda_input_mux cxt5066_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic B", 0 },
+		{ "Mic C", 1 },
+		{ "Mic E", 2 },
+		{ "Mic F", 3 },
+	},
+};
+
+static struct hda_bind_ctls cxt5066_bind_capture_vol_others = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_INPUT),
+		HDA_COMPOSE_AMP_VAL(0x14, 3, 2, HDA_INPUT),
+		0
+	},
+};
+
+static struct hda_bind_ctls cxt5066_bind_capture_sw_others = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_INPUT),
+		HDA_COMPOSE_AMP_VAL(0x14, 3, 2, HDA_INPUT),
+		0
+	},
+};
+
+static struct snd_kcontrol_new cxt5066_mixer_master[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT),
+	{}
+};
+
+static struct snd_kcontrol_new cxt5066_mixer_master_olpc[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Volume",
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				  SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+				  SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.info = snd_hda_mixer_amp_volume_info,
+		.get = snd_hda_mixer_amp_volume_get,
+		.put = snd_hda_mixer_amp_volume_put,
+		.tlv = { .c = snd_hda_mixer_amp_tlv },
+		/* offset by 28 volume steps to limit minimum gain to -46dB */
+		.private_value =
+			HDA_COMPOSE_AMP_VAL_OFS(0x10, 3, 0, HDA_OUTPUT, 28),
+	},
+	{}
+};
+
+static struct snd_kcontrol_new cxt5066_mixer_olpc_dc[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "DC Mode Enable Switch",
+		.info = snd_ctl_boolean_mono_info,
+		.get = cxt5066_olpc_dc_get,
+		.put = cxt5066_olpc_dc_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "DC Input Bias Enum",
+		.info = cxt5066_olpc_dc_bias_enum_info,
+		.get = cxt5066_olpc_dc_bias_enum_get,
+		.put = cxt5066_olpc_dc_bias_enum_put,
+	},
+	{}
+};
+
+static struct snd_kcontrol_new cxt5066_mixers[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = cxt_eapd_info,
+		.get = cxt_eapd_get,
+		.put = cxt5066_hp_master_sw_put,
+		.private_value = 0x1d,
+	},
+
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Mic Boost Capture Enum",
+		.info = cxt5066_mic_boost_mux_enum_info,
+		.get = cxt5066_mic_boost_mux_enum_get,
+		.put = cxt5066_mic_boost_mux_enum_put,
+	},
+
+	HDA_BIND_VOL("Capture Volume", &cxt5066_bind_capture_vol_others),
+	HDA_BIND_SW("Capture Switch", &cxt5066_bind_capture_sw_others),
+	{}
+};
+
+static struct snd_kcontrol_new cxt5066_vostro_mixers[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Int Mic Boost Capture Enum",
+		.info = cxt5066_mic_boost_mux_enum_info,
+		.get = cxt5066_mic_boost_mux_enum_get,
+		.put = cxt5066_mic_boost_mux_enum_put,
+		.private_value = 0x23 | 0x100,
+	},
+	{}
+};
+
+static struct hda_verb cxt5066_init_verbs[] = {
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port B */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port C */
+	{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port F */
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port E */
+
+	/* Speakers  */
+	{0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1f, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	/* HP, Amp  */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1c, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	/* DAC1 */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Node 14 connections: 0x17 0x18 0x23 0x24 0x27 */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x50},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2) | 0x50},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+
+	/* no digital microphone support yet */
+	{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Audio input selector */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x3},
+
+	/* SPDIF route: PCM */
+	{0x20, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x22, AC_VERB_SET_CONNECT_SEL, 0x0},
+
+	{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	/* EAPD */
+	{0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+
+	/* not handling these yet */
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, 0},
+	{0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, 0},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, 0},
+	{0x1c, AC_VERB_SET_UNSOLICITED_ENABLE, 0},
+	{0x1d, AC_VERB_SET_UNSOLICITED_ENABLE, 0},
+	{0x1e, AC_VERB_SET_UNSOLICITED_ENABLE, 0},
+	{0x20, AC_VERB_SET_UNSOLICITED_ENABLE, 0},
+	{0x22, AC_VERB_SET_UNSOLICITED_ENABLE, 0},
+	{ } /* end */
+};
+
+static struct hda_verb cxt5066_init_verbs_olpc[] = {
+	/* Port A: headphones */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	/* Port B: external microphone */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Port C: internal microphone */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Port D: unused */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Port E: unused, but has primary EAPD */
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+
+	/* Port F: external DC input through microphone port */
+	{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Port G: internal speakers */
+	{0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1f, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	/* DAC1 */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* DAC2: unused */
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x50},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+
+	/* Disable digital microphone port */
+	{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Audio input selectors */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x3},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+
+	/* Disable SPDIF */
+	{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* enable unsolicited events for Port A and B */
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
+	{0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
+	{ } /* end */
+};
+
+static struct hda_verb cxt5066_init_verbs_vostro[] = {
+	/* Port A: headphones */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	/* Port B: external microphone */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Port C: unused */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Port D: unused */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Port E: unused, but has primary EAPD */
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+
+	/* Port F: unused */
+	{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Port G: internal speakers */
+	{0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1f, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	/* DAC1 */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* DAC2: unused */
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+
+	/* Digital microphone port */
+	{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	/* Audio input selectors */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x3},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+
+	/* Disable SPDIF */
+	{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* enable unsolicited events for Port A and B */
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
+	{0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
+	{ } /* end */
+};
+
+static struct hda_verb cxt5066_init_verbs_ideapad[] = {
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port B */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port C */
+	{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port F */
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port E */
+
+	/* Speakers  */
+	{0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1f, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	/* HP, Amp  */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1c, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	/* DAC1 */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Node 14 connections: 0x17 0x18 0x23 0x24 0x27 */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x50},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2) | 0x50},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 2},	/* default to internal mic */
+
+	/* Audio input selector */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x2},
+	{0x17, AC_VERB_SET_CONNECT_SEL, 1},	/* route ext mic */
+
+	/* SPDIF route: PCM */
+	{0x20, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x22, AC_VERB_SET_CONNECT_SEL, 0x0},
+
+	{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	/* internal microphone */
+	{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* enable int mic */
+
+	/* EAPD */
+	{0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
+	{ } /* end */
+};
+
+static struct hda_verb cxt5066_init_verbs_thinkpad[] = {
+	{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port F */
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port E */
+
+	/* Port G: internal speakers  */
+	{0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1f, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	/* Port A: HP, Amp  */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	/* Port B: Mic Dock */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Port C: Mic */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+	/* Port D: HP Dock, Amp */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x1c, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
+
+	/* DAC1 */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Node 14 connections: 0x17 0x18 0x23 0x24 0x27 */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x50},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2) | 0x50},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 2},	/* default to internal mic */
+
+	/* Audio input selector */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x2},
+	{0x17, AC_VERB_SET_CONNECT_SEL, 1},	/* route ext mic */
+
+	/* SPDIF route: PCM */
+	{0x20, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x22, AC_VERB_SET_CONNECT_SEL, 0x0},
+
+	{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	/* internal microphone */
+	{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* enable int mic */
+
+	/* EAPD */
+	{0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+
+	/* enable unsolicited events for Port A, B, C and D */
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
+	{0x1c, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
+	{0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
+	{ } /* end */
+};
+
+static struct hda_verb cxt5066_init_verbs_portd_lo[] = {
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{ } /* end */
+};
+
+
+static struct hda_verb cxt5066_init_verbs_hp_laptop[] = {
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x0},
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
+	{ } /* end */
+};
+
+/* initialize jack-sensing, too */
+static int cxt5066_init(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+
+	snd_printdd("CXT5066: init\n");
+	conexant_init(codec);
+	if (codec->patch_ops.unsol_event) {
+		cxt5066_hp_automute(codec);
+		if (spec->dell_vostro)
+			cxt5066_vostro_automic(codec);
+		else if (spec->ideapad)
+			cxt5066_ideapad_automic(codec);
+		else if (spec->thinkpad)
+			cxt5066_thinkpad_automic(codec);
+		else if (spec->hp_laptop)
+			cxt5066_hp_laptop_automic(codec);
+	}
+	cxt5066_set_mic_boost(codec);
+	return 0;
+}
+
+static int cxt5066_olpc_init(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	snd_printdd("CXT5066: init\n");
+	conexant_init(codec);
+	cxt5066_hp_automute(codec);
+	if (!spec->dc_enable) {
+		cxt5066_set_mic_boost(codec);
+		cxt5066_olpc_automic(codec);
+	} else {
+		cxt5066_enable_dc(codec);
+	}
+	return 0;
+}
+
+enum {
+	CXT5066_LAPTOP,		/* Laptops w/ EAPD support */
+	CXT5066_DELL_LAPTOP,	/* Dell Laptop */
+	CXT5066_OLPC_XO_1_5,	/* OLPC XO 1.5 */
+	CXT5066_DELL_VOSTRO,	/* Dell Vostro 1015i */
+	CXT5066_IDEAPAD,	/* Lenovo IdeaPad U150 */
+	CXT5066_THINKPAD,	/* Lenovo ThinkPad T410s, others? */
+	CXT5066_HP_LAPTOP,      /* HP Laptop */
+	CXT5066_MODELS
+};
+
+static const char *cxt5066_models[CXT5066_MODELS] = {
+	[CXT5066_LAPTOP]	= "laptop",
+	[CXT5066_DELL_LAPTOP]	= "dell-laptop",
+	[CXT5066_OLPC_XO_1_5]	= "olpc-xo-1_5",
+	[CXT5066_DELL_VOSTRO]	= "dell-vostro",
+	[CXT5066_IDEAPAD]	= "ideapad",
+	[CXT5066_THINKPAD]	= "thinkpad",
+	[CXT5066_HP_LAPTOP]	= "hp-laptop",
+};
+
+static struct snd_pci_quirk cxt5066_cfg_tbl[] = {
+	SND_PCI_QUIRK_MASK(0x1025, 0xff00, 0x0400, "Acer", CXT5066_IDEAPAD),
+	SND_PCI_QUIRK(0x1028, 0x02d8, "Dell Vostro", CXT5066_DELL_VOSTRO),
+	SND_PCI_QUIRK(0x1028, 0x02f5, "Dell Vostro 320", CXT5066_IDEAPAD),
+	SND_PCI_QUIRK(0x1028, 0x0402, "Dell Vostro", CXT5066_DELL_VOSTRO),
+	SND_PCI_QUIRK(0x1028, 0x0408, "Dell Inspiron One 19T", CXT5066_IDEAPAD),
+	SND_PCI_QUIRK(0x103c, 0x360b, "HP G60", CXT5066_HP_LAPTOP),
+	SND_PCI_QUIRK(0x1043, 0x13f3, "Asus A52J", CXT5066_HP_LAPTOP),
+	SND_PCI_QUIRK(0x1179, 0xff1e, "Toshiba Satellite C650D", CXT5066_IDEAPAD),
+	SND_PCI_QUIRK(0x1179, 0xff50, "Toshiba Satellite P500-PSPGSC-01800T", CXT5066_OLPC_XO_1_5),
+	SND_PCI_QUIRK(0x1179, 0xffe0, "Toshiba Satellite Pro T130-15F", CXT5066_OLPC_XO_1_5),
+	SND_PCI_QUIRK(0x14f1, 0x0101, "Conexant Reference board",
+		      CXT5066_LAPTOP),
+	SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT5066_OLPC_XO_1_5),
+	SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400s", CXT5066_THINKPAD),
+	SND_PCI_QUIRK(0x17aa, 0x21b2, "Thinkpad X100e", CXT5066_IDEAPAD),
+	SND_PCI_QUIRK(0x17aa, 0x21c5, "Thinkpad Edge 13", CXT5066_THINKPAD),
+	SND_PCI_QUIRK(0x17aa, 0x21b3, "Thinkpad Edge 13 (197)", CXT5066_IDEAPAD),
+	SND_PCI_QUIRK(0x17aa, 0x21b4, "Thinkpad Edge", CXT5066_IDEAPAD),
+	SND_PCI_QUIRK(0x17aa, 0x21c8, "Thinkpad Edge 11", CXT5066_IDEAPAD),
+ 	SND_PCI_QUIRK(0x17aa, 0x215e, "Lenovo Thinkpad", CXT5066_THINKPAD),
+ 	SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo G series", CXT5066_IDEAPAD),
+	SND_PCI_QUIRK(0x17aa, 0x390a, "Lenovo S10-3t", CXT5066_IDEAPAD),
+	SND_PCI_QUIRK(0x17aa, 0x3938, "Lenovo G series (AMD)", CXT5066_IDEAPAD),
+	SND_PCI_QUIRK(0x17aa, 0x3a0d, "ideapad", CXT5066_IDEAPAD),
+	{}
+};
+
+static int patch_cxt5066(struct hda_codec *codec)
+{
+	struct conexant_spec *spec;
+	int board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	codec->spec = spec;
+
+	codec->patch_ops = conexant_patch_ops;
+	codec->patch_ops.init = conexant_init;
+
+	spec->dell_automute = 0;
+	spec->multiout.max_channels = 2;
+	spec->multiout.num_dacs = ARRAY_SIZE(cxt5066_dac_nids);
+	spec->multiout.dac_nids = cxt5066_dac_nids;
+	spec->multiout.dig_out_nid = CXT5066_SPDIF_OUT;
+	spec->num_adc_nids = 1;
+	spec->adc_nids = cxt5066_adc_nids;
+	spec->capsrc_nids = cxt5066_capsrc_nids;
+	spec->input_mux = &cxt5066_capture_source;
+
+	spec->port_d_mode = PIN_HP;
+
+	spec->num_init_verbs = 1;
+	spec->init_verbs[0] = cxt5066_init_verbs;
+	spec->num_channel_mode = ARRAY_SIZE(cxt5066_modes);
+	spec->channel_mode = cxt5066_modes;
+	spec->cur_adc = 0;
+	spec->cur_adc_idx = 0;
+
+	set_beep_amp(spec, 0x13, 0, HDA_OUTPUT);
+
+	board_config = snd_hda_check_board_config(codec, CXT5066_MODELS,
+						  cxt5066_models, cxt5066_cfg_tbl);
+	switch (board_config) {
+	default:
+	case CXT5066_LAPTOP:
+		spec->mixers[spec->num_mixers++] = cxt5066_mixer_master;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixers;
+		break;
+	case CXT5066_DELL_LAPTOP:
+		spec->mixers[spec->num_mixers++] = cxt5066_mixer_master;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixers;
+
+		spec->port_d_mode = PIN_OUT;
+		spec->init_verbs[spec->num_init_verbs] = cxt5066_init_verbs_portd_lo;
+		spec->num_init_verbs++;
+		spec->dell_automute = 1;
+		break;
+	case CXT5066_HP_LAPTOP:
+		codec->patch_ops.init = cxt5066_init;
+		codec->patch_ops.unsol_event = cxt5066_hp_laptop_event;
+		spec->init_verbs[spec->num_init_verbs] =
+			cxt5066_init_verbs_hp_laptop;
+		spec->num_init_verbs++;
+		spec->hp_laptop = 1;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixer_master;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixers;
+		/* no S/PDIF out */
+		spec->multiout.dig_out_nid = 0;
+		/* input source automatically selected */
+		spec->input_mux = NULL;
+		spec->port_d_mode = 0;
+		spec->mic_boost = 3; /* default 30dB gain */
+		break;
+
+	case CXT5066_OLPC_XO_1_5:
+		codec->patch_ops.init = cxt5066_olpc_init;
+		codec->patch_ops.unsol_event = cxt5066_olpc_unsol_event;
+		spec->init_verbs[0] = cxt5066_init_verbs_olpc;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixer_master_olpc;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixer_olpc_dc;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixers;
+		spec->port_d_mode = 0;
+		spec->mic_boost = 3; /* default 30dB gain */
+
+		/* no S/PDIF out */
+		spec->multiout.dig_out_nid = 0;
+
+		/* input source automatically selected */
+		spec->input_mux = NULL;
+
+		/* our capture hooks which allow us to turn on the microphone LED
+		 * at the right time */
+		spec->capture_prepare = cxt5066_olpc_capture_prepare;
+		spec->capture_cleanup = cxt5066_olpc_capture_cleanup;
+		break;
+	case CXT5066_DELL_VOSTRO:
+		codec->patch_ops.init = cxt5066_init;
+		codec->patch_ops.unsol_event = cxt5066_vostro_event;
+		spec->init_verbs[0] = cxt5066_init_verbs_vostro;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixer_master_olpc;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixers;
+		spec->mixers[spec->num_mixers++] = cxt5066_vostro_mixers;
+		spec->port_d_mode = 0;
+		spec->dell_vostro = 1;
+		spec->mic_boost = 3; /* default 30dB gain */
+
+		/* no S/PDIF out */
+		spec->multiout.dig_out_nid = 0;
+
+		/* input source automatically selected */
+		spec->input_mux = NULL;
+		break;
+	case CXT5066_IDEAPAD:
+		codec->patch_ops.init = cxt5066_init;
+		codec->patch_ops.unsol_event = cxt5066_ideapad_event;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixer_master;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixers;
+		spec->init_verbs[0] = cxt5066_init_verbs_ideapad;
+		spec->port_d_mode = 0;
+		spec->ideapad = 1;
+		spec->mic_boost = 2;	/* default 20dB gain */
+
+		/* no S/PDIF out */
+		spec->multiout.dig_out_nid = 0;
+
+		/* input source automatically selected */
+		spec->input_mux = NULL;
+		break;
+	case CXT5066_THINKPAD:
+		codec->patch_ops.init = cxt5066_init;
+		codec->patch_ops.unsol_event = cxt5066_thinkpad_event;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixer_master;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixers;
+		spec->init_verbs[0] = cxt5066_init_verbs_thinkpad;
+		spec->thinkpad = 1;
+		spec->port_d_mode = PIN_OUT;
+		spec->mic_boost = 2;	/* default 20dB gain */
+
+		/* no S/PDIF out */
+		spec->multiout.dig_out_nid = 0;
+
+		/* input source automatically selected */
+		spec->input_mux = NULL;
+		break;
+	}
+
+	if (spec->beep_amp)
+		snd_hda_attach_beep_device(codec, spec->beep_amp);
+
+	return 0;
+}
+
+/*
+ * Automatic parser for CX20641 & co
+ */
+
+static hda_nid_t cx_auto_adc_nids[] = { 0x14 };
+
+/* get the connection index of @nid in the widget @mux */
+static int get_connection_index(struct hda_codec *codec, hda_nid_t mux,
+				hda_nid_t nid)
+{
+	hda_nid_t conn[HDA_MAX_NUM_INPUTS];
+	int i, nums;
+
+	nums = snd_hda_get_connections(codec, mux, conn, ARRAY_SIZE(conn));
+	for (i = 0; i < nums; i++)
+		if (conn[i] == nid)
+			return i;
+	return -1;
+}
+
+/* get an unassigned DAC from the given list.
+ * Return the nid if found and reduce the DAC list, or return zero if
+ * not found
+ */
+static hda_nid_t get_unassigned_dac(struct hda_codec *codec, hda_nid_t pin,
+				    hda_nid_t *dacs, int *num_dacs)
+{
+	int i, nums = *num_dacs;
+	hda_nid_t ret = 0;
+
+	for (i = 0; i < nums; i++) {
+		if (get_connection_index(codec, pin, dacs[i]) >= 0) {
+			ret = dacs[i];
+			break;
+		}
+	}
+	if (!ret)
+		return 0;
+	if (--nums > 0)
+		memmove(dacs, dacs + 1, nums * sizeof(hda_nid_t));
+	*num_dacs = nums;
+	return ret;
+}
+
+#define MAX_AUTO_DACS	5
+
+/* fill analog DAC list from the widget tree */
+static int fill_cx_auto_dacs(struct hda_codec *codec, hda_nid_t *dacs)
+{
+	hda_nid_t nid, end_nid;
+	int nums = 0;
+
+	end_nid = codec->start_nid + codec->num_nodes;
+	for (nid = codec->start_nid; nid < end_nid; nid++) {
+		unsigned int wcaps = get_wcaps(codec, nid);
+		unsigned int type = get_wcaps_type(wcaps);
+		if (type == AC_WID_AUD_OUT && !(wcaps & AC_WCAP_DIGITAL)) {
+			dacs[nums++] = nid;
+			if (nums >= MAX_AUTO_DACS)
+				break;
+		}
+	}
+	return nums;
+}
+
+/* fill pin_dac_pair list from the pin and dac list */
+static int fill_dacs_for_pins(struct hda_codec *codec, hda_nid_t *pins,
+			      int num_pins, hda_nid_t *dacs, int *rest,
+			      struct pin_dac_pair *filled, int type)
+{
+	int i, nums;
+
+	nums = 0;
+	for (i = 0; i < num_pins; i++) {
+		filled[nums].pin = pins[i];
+		filled[nums].type = type;
+		filled[nums].dac = get_unassigned_dac(codec, pins[i], dacs, rest);
+		nums++;
+	}
+	return nums;
+}
+
+/* parse analog output paths */
+static void cx_auto_parse_output(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t dacs[MAX_AUTO_DACS];
+	int i, j, nums, rest;
+
+	rest = fill_cx_auto_dacs(codec, dacs);
+	/* parse all analog output pins */
+	nums = fill_dacs_for_pins(codec, cfg->line_out_pins, cfg->line_outs,
+				  dacs, &rest, spec->dac_info,
+				  AUTO_PIN_LINE_OUT);
+	nums += fill_dacs_for_pins(codec, cfg->hp_pins, cfg->hp_outs,
+				  dacs, &rest, spec->dac_info + nums,
+				  AUTO_PIN_HP_OUT);
+	nums += fill_dacs_for_pins(codec, cfg->speaker_pins, cfg->speaker_outs,
+				  dacs, &rest, spec->dac_info + nums,
+				  AUTO_PIN_SPEAKER_OUT);
+	spec->dac_info_filled = nums;
+	/* fill multiout struct */
+	for (i = 0; i < nums; i++) {
+		hda_nid_t dac = spec->dac_info[i].dac;
+		if (!dac)
+			continue;
+		switch (spec->dac_info[i].type) {
+		case AUTO_PIN_LINE_OUT:
+			spec->private_dac_nids[spec->multiout.num_dacs] = dac;
+			spec->multiout.num_dacs++;
+			break;
+		case AUTO_PIN_HP_OUT:
+		case AUTO_PIN_SPEAKER_OUT:
+			if (!spec->multiout.hp_nid) {
+				spec->multiout.hp_nid = dac;
+				break;
+			}
+			for (j = 0; j < ARRAY_SIZE(spec->multiout.extra_out_nid); j++)
+				if (!spec->multiout.extra_out_nid[j]) {
+					spec->multiout.extra_out_nid[j] = dac;
+					break;
+				}
+			break;
+		}
+	}
+	spec->multiout.dac_nids = spec->private_dac_nids;
+	spec->multiout.max_channels = nums * 2;
+
+	if (cfg->hp_outs > 0)
+		spec->auto_mute = 1;
+	spec->vmaster_nid = spec->private_dac_nids[0];
+}
+
+/* auto-mute/unmute speaker and line outs according to headphone jack */
+static void cx_auto_hp_automute(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i, present;
+
+	if (!spec->auto_mute)
+		return;
+	present = 0;
+	for (i = 0; i < cfg->hp_outs; i++) {
+		if (snd_hda_jack_detect(codec, cfg->hp_pins[i])) {
+			present = 1;
+			break;
+		}
+	}
+	for (i = 0; i < cfg->line_outs; i++) {
+		snd_hda_codec_write(codec, cfg->line_out_pins[i], 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL,
+				    present ? 0 : PIN_OUT);
+	}
+	for (i = 0; i < cfg->speaker_outs; i++) {
+		snd_hda_codec_write(codec, cfg->speaker_pins[i], 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL,
+				    present ? 0 : PIN_OUT);
+	}
+}
+
+/* automatic switch internal and external mic */
+static void cx_auto_automic(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	struct hda_input_mux *imux = &spec->private_imux;
+	int ext_idx = spec->auto_mic_ext;
+
+	if (!spec->auto_mic)
+		return;
+	if (snd_hda_jack_detect(codec, cfg->inputs[ext_idx].pin)) {
+		snd_hda_codec_write(codec, spec->adc_nids[0], 0,
+				    AC_VERB_SET_CONNECT_SEL,
+				    imux->items[ext_idx].index);
+	} else {
+		snd_hda_codec_write(codec, spec->adc_nids[0], 0,
+				    AC_VERB_SET_CONNECT_SEL,
+				    imux->items[!ext_idx].index);
+	}
+}
+
+static void cx_auto_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	int nid = (res & AC_UNSOL_RES_SUBTAG) >> 20;
+	switch (res >> 26) {
+	case CONEXANT_HP_EVENT:
+		cx_auto_hp_automute(codec);
+		conexant_report_jack(codec, nid);
+		break;
+	case CONEXANT_MIC_EVENT:
+		cx_auto_automic(codec);
+		conexant_report_jack(codec, nid);
+		break;
+	}
+}
+
+/* return true if it's an internal-mic pin */
+static int is_int_mic(struct hda_codec *codec, hda_nid_t pin)
+{
+	unsigned int def_conf = snd_hda_codec_get_pincfg(codec, pin);
+	return get_defcfg_device(def_conf) == AC_JACK_MIC_IN &&
+		snd_hda_get_input_pin_attr(def_conf) == INPUT_PIN_ATTR_INT;
+}
+
+/* return true if it's an external-mic pin */
+static int is_ext_mic(struct hda_codec *codec, hda_nid_t pin)
+{
+	unsigned int def_conf = snd_hda_codec_get_pincfg(codec, pin);
+	return get_defcfg_device(def_conf) == AC_JACK_MIC_IN &&
+		snd_hda_get_input_pin_attr(def_conf) >= INPUT_PIN_ATTR_NORMAL &&
+		(snd_hda_query_pin_caps(codec, pin) & AC_PINCAP_PRES_DETECT);
+}
+
+/* check whether the pin config is suitable for auto-mic switching;
+ * auto-mic is enabled only when one int-mic and one-ext mic exist
+ */
+static void cx_auto_check_auto_mic(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+
+	if (is_ext_mic(codec, cfg->inputs[0].pin) &&
+	    is_int_mic(codec, cfg->inputs[1].pin)) {
+		spec->auto_mic = 1;
+		spec->auto_mic_ext = 1;
+		return;
+	}
+	if (is_int_mic(codec, cfg->inputs[1].pin) &&
+	    is_ext_mic(codec, cfg->inputs[0].pin)) {
+		spec->auto_mic = 1;
+		spec->auto_mic_ext = 0;
+		return;
+	}
+}
+
+static void cx_auto_parse_input(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	struct hda_input_mux *imux;
+	int i;
+
+	imux = &spec->private_imux;
+	for (i = 0; i < cfg->num_inputs; i++) {
+		int idx = get_connection_index(codec, spec->adc_nids[0],
+					       cfg->inputs[i].pin);
+		if (idx >= 0) {
+			const char *label;
+			label = hda_get_autocfg_input_label(codec, cfg, i);
+			snd_hda_add_imux_item(imux, label, idx, NULL);
+		}
+	}
+	if (imux->num_items == 2 && cfg->num_inputs == 2)
+		cx_auto_check_auto_mic(codec);
+	if (imux->num_items > 1 && !spec->auto_mic)
+		spec->input_mux = imux;
+}
+
+/* get digital-input audio widget corresponding to the given pin */
+static hda_nid_t cx_auto_get_dig_in(struct hda_codec *codec, hda_nid_t pin)
+{
+	hda_nid_t nid, end_nid;
+
+	end_nid = codec->start_nid + codec->num_nodes;
+	for (nid = codec->start_nid; nid < end_nid; nid++) {
+		unsigned int wcaps = get_wcaps(codec, nid);
+		unsigned int type = get_wcaps_type(wcaps);
+		if (type == AC_WID_AUD_IN && (wcaps & AC_WCAP_DIGITAL)) {
+			if (get_connection_index(codec, nid, pin) >= 0)
+				return nid;
+		}
+	}
+	return 0;
+}
+
+static void cx_auto_parse_digital(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t nid;
+
+	if (cfg->dig_outs &&
+	    snd_hda_get_connections(codec, cfg->dig_out_pins[0], &nid, 1) == 1)
+		spec->multiout.dig_out_nid = nid;
+	if (cfg->dig_in_pin)
+		spec->dig_in_nid = cx_auto_get_dig_in(codec, cfg->dig_in_pin);
+}
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+static void cx_auto_parse_beep(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	hda_nid_t nid, end_nid;
+
+	end_nid = codec->start_nid + codec->num_nodes;
+	for (nid = codec->start_nid; nid < end_nid; nid++)
+		if (get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_BEEP) {
+			set_beep_amp(spec, nid, 0, HDA_OUTPUT);
+			break;
+		}
+}
+#else
+#define cx_auto_parse_beep(codec)
+#endif
+
+static int cx_auto_parse_auto_config(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	int err;
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+	if (err < 0)
+		return err;
+
+	cx_auto_parse_output(codec);
+	cx_auto_parse_input(codec);
+	cx_auto_parse_digital(codec);
+	cx_auto_parse_beep(codec);
+	return 0;
+}
+
+static void cx_auto_turn_on_eapd(struct hda_codec *codec, int num_pins,
+				 hda_nid_t *pins)
+{
+	int i;
+	for (i = 0; i < num_pins; i++) {
+		if (snd_hda_query_pin_caps(codec, pins[i]) & AC_PINCAP_EAPD)
+			snd_hda_codec_write(codec, pins[i], 0,
+					    AC_VERB_SET_EAPD_BTLENABLE, 0x02);
+	}
+}
+
+static void select_connection(struct hda_codec *codec, hda_nid_t pin,
+			      hda_nid_t src)
+{
+	int idx = get_connection_index(codec, pin, src);
+	if (idx >= 0)
+		snd_hda_codec_write(codec, pin, 0,
+				    AC_VERB_SET_CONNECT_SEL, idx);
+}
+
+static void cx_auto_init_output(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t nid;
+	int i;
+
+	for (i = 0; i < spec->multiout.num_dacs; i++)
+		snd_hda_codec_write(codec, spec->multiout.dac_nids[i], 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+	for (i = 0; i < cfg->hp_outs; i++)
+		snd_hda_codec_write(codec, cfg->hp_pins[i], 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP);
+	if (spec->auto_mute) {
+		for (i = 0; i < cfg->hp_outs; i++) {
+			snd_hda_codec_write(codec, cfg->hp_pins[i], 0,
+				    AC_VERB_SET_UNSOLICITED_ENABLE,
+				    AC_USRSP_EN | CONEXANT_HP_EVENT);
+		}
+		cx_auto_hp_automute(codec);
+	} else {
+		for (i = 0; i < cfg->line_outs; i++)
+			snd_hda_codec_write(codec, cfg->line_out_pins[i], 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+		for (i = 0; i < cfg->speaker_outs; i++)
+			snd_hda_codec_write(codec, cfg->speaker_pins[i], 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+	}
+
+	for (i = 0; i < spec->dac_info_filled; i++) {
+		nid = spec->dac_info[i].dac;
+		if (!nid)
+			nid = spec->multiout.dac_nids[0];
+		select_connection(codec, spec->dac_info[i].pin, nid);
+	}
+
+	/* turn on EAPD */
+	cx_auto_turn_on_eapd(codec, cfg->line_outs, cfg->line_out_pins);
+	cx_auto_turn_on_eapd(codec, cfg->hp_outs, cfg->hp_pins);
+	cx_auto_turn_on_eapd(codec, cfg->speaker_outs, cfg->speaker_pins);
+}
+
+static void cx_auto_init_input(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	for (i = 0; i < spec->num_adc_nids; i++)
+		snd_hda_codec_write(codec, spec->adc_nids[i], 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0));
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		unsigned int type;
+		if (cfg->inputs[i].type == AUTO_PIN_MIC)
+			type = PIN_VREF80;
+		else
+			type = PIN_IN;
+		snd_hda_codec_write(codec, cfg->inputs[i].pin, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, type);
+	}
+
+	if (spec->auto_mic) {
+		int ext_idx = spec->auto_mic_ext;
+		snd_hda_codec_write(codec, cfg->inputs[ext_idx].pin, 0,
+				    AC_VERB_SET_UNSOLICITED_ENABLE,
+				    AC_USRSP_EN | CONEXANT_MIC_EVENT);
+		cx_auto_automic(codec);
+	} else {
+		for (i = 0; i < spec->num_adc_nids; i++) {
+			snd_hda_codec_write(codec, spec->adc_nids[i], 0,
+					    AC_VERB_SET_CONNECT_SEL,
+					    spec->private_imux.items[0].index);
+		}
+	}
+}
+
+static void cx_auto_init_digital(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+
+	if (spec->multiout.dig_out_nid)
+		snd_hda_codec_write(codec, cfg->dig_out_pins[0], 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+	if (spec->dig_in_nid)
+		snd_hda_codec_write(codec, cfg->dig_in_pin, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN);
+}
+
+static int cx_auto_init(struct hda_codec *codec)
+{
+	/*snd_hda_sequence_write(codec, cx_auto_init_verbs);*/
+	cx_auto_init_output(codec);
+	cx_auto_init_input(codec);
+	cx_auto_init_digital(codec);
+	return 0;
+}
+
+static int cx_auto_add_volume(struct hda_codec *codec, const char *basename,
+			      const char *dir, int cidx,
+			      hda_nid_t nid, int hda_dir)
+{
+	static char name[32];
+	static struct snd_kcontrol_new knew[] = {
+		HDA_CODEC_VOLUME(name, 0, 0, 0),
+		HDA_CODEC_MUTE(name, 0, 0, 0),
+	};
+	static char *sfx[2] = { "Volume", "Switch" };
+	int i, err;
+
+	for (i = 0; i < 2; i++) {
+		struct snd_kcontrol *kctl;
+		knew[i].private_value = HDA_COMPOSE_AMP_VAL(nid, 3, 0, hda_dir);
+		knew[i].subdevice = HDA_SUBDEV_AMP_FLAG;
+		knew[i].index = cidx;
+		snprintf(name, sizeof(name), "%s%s %s", basename, dir, sfx[i]);
+		kctl = snd_ctl_new1(&knew[i], codec);
+		if (!kctl)
+			return -ENOMEM;
+		err = snd_hda_ctl_add(codec, nid, kctl);
+		if (err < 0)
+			return err;
+		if (!(query_amp_caps(codec, nid, hda_dir) & AC_AMPCAP_MUTE))
+			break;
+	}
+	return 0;
+}
+
+#define cx_auto_add_pb_volume(codec, nid, str, idx)			\
+	cx_auto_add_volume(codec, str, " Playback", idx, nid, HDA_OUTPUT)
+
+static int cx_auto_build_output_controls(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	int i, err;
+	int num_line = 0, num_hp = 0, num_spk = 0;
+	static const char *texts[3] = { "Front", "Surround", "CLFE" };
+
+	if (spec->dac_info_filled == 1)
+		return cx_auto_add_pb_volume(codec, spec->dac_info[0].dac,
+					     "Master", 0);
+	for (i = 0; i < spec->dac_info_filled; i++) {
+		const char *label;
+		int idx, type;
+		if (!spec->dac_info[i].dac)
+			continue;
+		type = spec->dac_info[i].type;
+		if (type == AUTO_PIN_LINE_OUT)
+			type = spec->autocfg.line_out_type;
+		switch (type) {
+		case AUTO_PIN_LINE_OUT:
+		default:
+			label = texts[num_line++];
+			idx = 0;
+			break;
+		case AUTO_PIN_HP_OUT:
+			label = "Headphone";
+			idx = num_hp++;
+			break;
+		case AUTO_PIN_SPEAKER_OUT:
+			label = "Speaker";
+			idx = num_spk++;
+			break;
+		}
+		err = cx_auto_add_pb_volume(codec, spec->dac_info[i].dac,
+					    label, idx);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int cx_auto_build_input_controls(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	static const char *prev_label;
+	int i, err, cidx;
+
+	err = cx_auto_add_volume(codec, "Capture", "", 0, spec->adc_nids[0],
+				 HDA_INPUT);
+	if (err < 0)
+		return err;
+	prev_label = NULL;
+	cidx = 0;
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		const char *label;
+		if (!(get_wcaps(codec, nid) & AC_WCAP_IN_AMP))
+			continue;
+		label = hda_get_autocfg_input_label(codec, cfg, i);
+		if (label == prev_label)
+			cidx++;
+		else
+			cidx = 0;
+		prev_label = label;
+		err = cx_auto_add_volume(codec, label, " Capture", cidx,
+					 nid, HDA_INPUT);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int cx_auto_build_controls(struct hda_codec *codec)
+{
+	int err;
+
+	err = cx_auto_build_output_controls(codec);
+	if (err < 0)
+		return err;
+	err = cx_auto_build_input_controls(codec);
+	if (err < 0)
+		return err;
+	return conexant_build_controls(codec);
+}
+
+static struct hda_codec_ops cx_auto_patch_ops = {
+	.build_controls = cx_auto_build_controls,
+	.build_pcms = conexant_build_pcms,
+	.init = cx_auto_init,
+	.free = conexant_free,
+	.unsol_event = cx_auto_unsol_event,
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	.suspend = conexant_suspend,
+#endif
+	.reboot_notify = snd_hda_shutup_pins,
+};
+
+static int patch_conexant_auto(struct hda_codec *codec)
+{
+	struct conexant_spec *spec;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	codec->spec = spec;
+	spec->adc_nids = cx_auto_adc_nids;
+	spec->num_adc_nids = ARRAY_SIZE(cx_auto_adc_nids);
+	spec->capsrc_nids = spec->adc_nids;
+	err = cx_auto_parse_auto_config(codec);
+	if (err < 0) {
+		kfree(codec->spec);
+		codec->spec = NULL;
+		return err;
+	}
+	codec->patch_ops = cx_auto_patch_ops;
+	if (spec->beep_amp)
+		snd_hda_attach_beep_device(codec, spec->beep_amp);
+	return 0;
+}
+
+/*
+ */
+
+static struct hda_codec_preset snd_hda_preset_conexant[] = {
+	{ .id = 0x14f15045, .name = "CX20549 (Venice)",
+	  .patch = patch_cxt5045 },
+	{ .id = 0x14f15047, .name = "CX20551 (Waikiki)",
+	  .patch = patch_cxt5047 },
+	{ .id = 0x14f15051, .name = "CX20561 (Hermosa)",
+	  .patch = patch_cxt5051 },
+	{ .id = 0x14f15066, .name = "CX20582 (Pebble)",
+	  .patch = patch_cxt5066 },
+	{ .id = 0x14f15067, .name = "CX20583 (Pebble HSF)",
+	  .patch = patch_cxt5066 },
+	{ .id = 0x14f15068, .name = "CX20584",
+	  .patch = patch_cxt5066 },
+	{ .id = 0x14f15069, .name = "CX20585",
+	  .patch = patch_cxt5066 },
+	{ .id = 0x14f15097, .name = "CX20631",
+	  .patch = patch_conexant_auto },
+	{ .id = 0x14f15098, .name = "CX20632",
+	  .patch = patch_conexant_auto },
+	{ .id = 0x14f150a1, .name = "CX20641",
+	  .patch = patch_conexant_auto },
+	{ .id = 0x14f150a2, .name = "CX20642",
+	  .patch = patch_conexant_auto },
+	{ .id = 0x14f150ab, .name = "CX20651",
+	  .patch = patch_conexant_auto },
+	{ .id = 0x14f150ac, .name = "CX20652",
+	  .patch = patch_conexant_auto },
+	{ .id = 0x14f150b8, .name = "CX20664",
+	  .patch = patch_conexant_auto },
+	{ .id = 0x14f150b9, .name = "CX20665",
+	  .patch = patch_conexant_auto },
+	{} /* terminator */
+};
+
+MODULE_ALIAS("snd-hda-codec-id:14f15045");
+MODULE_ALIAS("snd-hda-codec-id:14f15047");
+MODULE_ALIAS("snd-hda-codec-id:14f15051");
+MODULE_ALIAS("snd-hda-codec-id:14f15066");
+MODULE_ALIAS("snd-hda-codec-id:14f15067");
+MODULE_ALIAS("snd-hda-codec-id:14f15068");
+MODULE_ALIAS("snd-hda-codec-id:14f15069");
+MODULE_ALIAS("snd-hda-codec-id:14f15097");
+MODULE_ALIAS("snd-hda-codec-id:14f15098");
+MODULE_ALIAS("snd-hda-codec-id:14f150a1");
+MODULE_ALIAS("snd-hda-codec-id:14f150a2");
+MODULE_ALIAS("snd-hda-codec-id:14f150ab");
+MODULE_ALIAS("snd-hda-codec-id:14f150ac");
+MODULE_ALIAS("snd-hda-codec-id:14f150b8");
+MODULE_ALIAS("snd-hda-codec-id:14f150b9");
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Conexant HD-audio codec");
+
+static struct hda_codec_preset_list conexant_list = {
+	.preset = snd_hda_preset_conexant,
+	.owner = THIS_MODULE,
+};
+
+static int __init patch_conexant_init(void)
+{
+	return snd_hda_add_codec_preset(&conexant_list);
+}
+
+static void __exit patch_conexant_exit(void)
+{
+	snd_hda_delete_codec_preset(&conexant_list);
+}
+
+module_init(patch_conexant_init)
+module_exit(patch_conexant_exit)
diff --git a/linux/sound/pci/hda/patch_hdmi.c b/linux/sound/pci/hda/patch_hdmi.c
new file mode 100644
index 0000000..31df774
--- /dev/null
+++ b/linux/sound/pci/hda/patch_hdmi.c
@@ -0,0 +1,1638 @@
+/*
+ *
+ *  patch_hdmi.c - routines for HDMI/DisplayPort codecs
+ *
+ *  Copyright(c) 2008-2010 Intel Corporation. All rights reserved.
+ *  Copyright (c) 2006 ATI Technologies Inc.
+ *  Copyright (c) 2008 NVIDIA Corp.  All rights reserved.
+ *  Copyright (c) 2008 Wei Ni <wni@nvidia.com>
+ *
+ *  Authors:
+ *			Wu Fengguang <wfg@linux.intel.com>
+ *
+ *  Maintained by:
+ *			Wu Fengguang <wfg@linux.intel.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the Free
+ *  Software Foundation; either version 2 of the License, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ *  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ *  for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software Foundation,
+ *  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+/*
+ * The HDMI/DisplayPort configuration can be highly dynamic. A graphics device
+ * could support two independent pipes, each of them can be connected to one or
+ * more ports (DVI, HDMI or DisplayPort).
+ *
+ * The HDA correspondence of pipes/ports are converter/pin nodes.
+ */
+#define MAX_HDMI_CVTS	3
+#define MAX_HDMI_PINS	3
+
+struct hdmi_spec {
+	int num_cvts;
+	int num_pins;
+	hda_nid_t cvt[MAX_HDMI_CVTS+1];  /* audio sources */
+	hda_nid_t pin[MAX_HDMI_PINS+1];  /* audio sinks */
+
+	/*
+	 * source connection for each pin
+	 */
+	hda_nid_t pin_cvt[MAX_HDMI_PINS+1];
+
+	/*
+	 * HDMI sink attached to each pin
+	 */
+	struct hdmi_eld sink_eld[MAX_HDMI_PINS];
+
+	/*
+	 * export one pcm per pipe
+	 */
+	struct hda_pcm	pcm_rec[MAX_HDMI_CVTS];
+	struct hda_pcm_stream codec_pcm_pars[MAX_HDMI_CVTS];
+
+	/*
+	 * ati/nvhdmi specific
+	 */
+	struct hda_multi_out multiout;
+	struct hda_pcm_stream *pcm_playback;
+
+	/* misc flags */
+	/* PD bit indicates only the update, not the current state */
+	unsigned int old_pin_detect:1;
+};
+
+
+struct hdmi_audio_infoframe {
+	u8 type; /* 0x84 */
+	u8 ver;  /* 0x01 */
+	u8 len;  /* 0x0a */
+
+	u8 checksum;
+
+	u8 CC02_CT47;	/* CC in bits 0:2, CT in 4:7 */
+	u8 SS01_SF24;
+	u8 CXT04;
+	u8 CA;
+	u8 LFEPBL01_LSV36_DM_INH7;
+};
+
+struct dp_audio_infoframe {
+	u8 type; /* 0x84 */
+	u8 len;  /* 0x1b */
+	u8 ver;  /* 0x11 << 2 */
+
+	u8 CC02_CT47;	/* match with HDMI infoframe from this on */
+	u8 SS01_SF24;
+	u8 CXT04;
+	u8 CA;
+	u8 LFEPBL01_LSV36_DM_INH7;
+};
+
+/*
+ * CEA speaker placement:
+ *
+ *        FLH       FCH        FRH
+ *  FLW    FL  FLC   FC   FRC   FR   FRW
+ *
+ *                                  LFE
+ *                     TC
+ *
+ *          RL  RLC   RC   RRC   RR
+ *
+ * The Left/Right Surround channel _notions_ LS/RS in SMPTE 320M corresponds to
+ * CEA RL/RR; The SMPTE channel _assignment_ C/LFE is swapped to CEA LFE/FC.
+ */
+enum cea_speaker_placement {
+	FL  = (1 <<  0),	/* Front Left           */
+	FC  = (1 <<  1),	/* Front Center         */
+	FR  = (1 <<  2),	/* Front Right          */
+	FLC = (1 <<  3),	/* Front Left Center    */
+	FRC = (1 <<  4),	/* Front Right Center   */
+	RL  = (1 <<  5),	/* Rear Left            */
+	RC  = (1 <<  6),	/* Rear Center          */
+	RR  = (1 <<  7),	/* Rear Right           */
+	RLC = (1 <<  8),	/* Rear Left Center     */
+	RRC = (1 <<  9),	/* Rear Right Center    */
+	LFE = (1 << 10),	/* Low Frequency Effect */
+	FLW = (1 << 11),	/* Front Left Wide      */
+	FRW = (1 << 12),	/* Front Right Wide     */
+	FLH = (1 << 13),	/* Front Left High      */
+	FCH = (1 << 14),	/* Front Center High    */
+	FRH = (1 << 15),	/* Front Right High     */
+	TC  = (1 << 16),	/* Top Center           */
+};
+
+/*
+ * ELD SA bits in the CEA Speaker Allocation data block
+ */
+static int eld_speaker_allocation_bits[] = {
+	[0] = FL | FR,
+	[1] = LFE,
+	[2] = FC,
+	[3] = RL | RR,
+	[4] = RC,
+	[5] = FLC | FRC,
+	[6] = RLC | RRC,
+	/* the following are not defined in ELD yet */
+	[7] = FLW | FRW,
+	[8] = FLH | FRH,
+	[9] = TC,
+	[10] = FCH,
+};
+
+struct cea_channel_speaker_allocation {
+	int ca_index;
+	int speakers[8];
+
+	/* derived values, just for convenience */
+	int channels;
+	int spk_mask;
+};
+
+/*
+ * ALSA sequence is:
+ *
+ *       surround40   surround41   surround50   surround51   surround71
+ * ch0   front left   =            =            =            =
+ * ch1   front right  =            =            =            =
+ * ch2   rear left    =            =            =            =
+ * ch3   rear right   =            =            =            =
+ * ch4                LFE          center       center       center
+ * ch5                                          LFE          LFE
+ * ch6                                                       side left
+ * ch7                                                       side right
+ *
+ * surround71 = {FL, FR, RLC, RRC, FC, LFE, RL, RR}
+ */
+static int hdmi_channel_mapping[0x32][8] = {
+	/* stereo */
+	[0x00] = { 0x00, 0x11, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7 },
+	/* 2.1 */
+	[0x01] = { 0x00, 0x11, 0x22, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7 },
+	/* Dolby Surround */
+	[0x02] = { 0x00, 0x11, 0x23, 0xf2, 0xf4, 0xf5, 0xf6, 0xf7 },
+	/* surround40 */
+	[0x08] = { 0x00, 0x11, 0x24, 0x35, 0xf3, 0xf2, 0xf6, 0xf7 },
+	/* 4ch */
+	[0x03] = { 0x00, 0x11, 0x23, 0x32, 0x44, 0xf5, 0xf6, 0xf7 },
+	/* surround41 */
+	[0x09] = { 0x00, 0x11, 0x24, 0x35, 0x42, 0xf3, 0xf6, 0xf7 },
+	/* surround50 */
+	[0x0a] = { 0x00, 0x11, 0x24, 0x35, 0x43, 0xf2, 0xf6, 0xf7 },
+	/* surround51 */
+	[0x0b] = { 0x00, 0x11, 0x24, 0x35, 0x43, 0x52, 0xf6, 0xf7 },
+	/* 7.1 */
+	[0x13] = { 0x00, 0x11, 0x26, 0x37, 0x43, 0x52, 0x64, 0x75 },
+};
+
+/*
+ * This is an ordered list!
+ *
+ * The preceding ones have better chances to be selected by
+ * hdmi_channel_allocation().
+ */
+static struct cea_channel_speaker_allocation channel_allocations[] = {
+/*			  channel:   7     6    5    4    3     2    1    0  */
+{ .ca_index = 0x00,  .speakers = {   0,    0,   0,   0,   0,    0,  FR,  FL } },
+				 /* 2.1 */
+{ .ca_index = 0x01,  .speakers = {   0,    0,   0,   0,   0,  LFE,  FR,  FL } },
+				 /* Dolby Surround */
+{ .ca_index = 0x02,  .speakers = {   0,    0,   0,   0,  FC,    0,  FR,  FL } },
+				 /* surround40 */
+{ .ca_index = 0x08,  .speakers = {   0,    0,  RR,  RL,   0,    0,  FR,  FL } },
+				 /* surround41 */
+{ .ca_index = 0x09,  .speakers = {   0,    0,  RR,  RL,   0,  LFE,  FR,  FL } },
+				 /* surround50 */
+{ .ca_index = 0x0a,  .speakers = {   0,    0,  RR,  RL,  FC,    0,  FR,  FL } },
+				 /* surround51 */
+{ .ca_index = 0x0b,  .speakers = {   0,    0,  RR,  RL,  FC,  LFE,  FR,  FL } },
+				 /* 6.1 */
+{ .ca_index = 0x0f,  .speakers = {   0,   RC,  RR,  RL,  FC,  LFE,  FR,  FL } },
+				 /* surround71 */
+{ .ca_index = 0x13,  .speakers = { RRC,  RLC,  RR,  RL,  FC,  LFE,  FR,  FL } },
+
+{ .ca_index = 0x03,  .speakers = {   0,    0,   0,   0,  FC,  LFE,  FR,  FL } },
+{ .ca_index = 0x04,  .speakers = {   0,    0,   0,  RC,   0,    0,  FR,  FL } },
+{ .ca_index = 0x05,  .speakers = {   0,    0,   0,  RC,   0,  LFE,  FR,  FL } },
+{ .ca_index = 0x06,  .speakers = {   0,    0,   0,  RC,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x07,  .speakers = {   0,    0,   0,  RC,  FC,  LFE,  FR,  FL } },
+{ .ca_index = 0x0c,  .speakers = {   0,   RC,  RR,  RL,   0,    0,  FR,  FL } },
+{ .ca_index = 0x0d,  .speakers = {   0,   RC,  RR,  RL,   0,  LFE,  FR,  FL } },
+{ .ca_index = 0x0e,  .speakers = {   0,   RC,  RR,  RL,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x10,  .speakers = { RRC,  RLC,  RR,  RL,   0,    0,  FR,  FL } },
+{ .ca_index = 0x11,  .speakers = { RRC,  RLC,  RR,  RL,   0,  LFE,  FR,  FL } },
+{ .ca_index = 0x12,  .speakers = { RRC,  RLC,  RR,  RL,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x14,  .speakers = { FRC,  FLC,   0,   0,   0,    0,  FR,  FL } },
+{ .ca_index = 0x15,  .speakers = { FRC,  FLC,   0,   0,   0,  LFE,  FR,  FL } },
+{ .ca_index = 0x16,  .speakers = { FRC,  FLC,   0,   0,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x17,  .speakers = { FRC,  FLC,   0,   0,  FC,  LFE,  FR,  FL } },
+{ .ca_index = 0x18,  .speakers = { FRC,  FLC,   0,  RC,   0,    0,  FR,  FL } },
+{ .ca_index = 0x19,  .speakers = { FRC,  FLC,   0,  RC,   0,  LFE,  FR,  FL } },
+{ .ca_index = 0x1a,  .speakers = { FRC,  FLC,   0,  RC,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x1b,  .speakers = { FRC,  FLC,   0,  RC,  FC,  LFE,  FR,  FL } },
+{ .ca_index = 0x1c,  .speakers = { FRC,  FLC,  RR,  RL,   0,    0,  FR,  FL } },
+{ .ca_index = 0x1d,  .speakers = { FRC,  FLC,  RR,  RL,   0,  LFE,  FR,  FL } },
+{ .ca_index = 0x1e,  .speakers = { FRC,  FLC,  RR,  RL,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x1f,  .speakers = { FRC,  FLC,  RR,  RL,  FC,  LFE,  FR,  FL } },
+{ .ca_index = 0x20,  .speakers = {   0,  FCH,  RR,  RL,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x21,  .speakers = {   0,  FCH,  RR,  RL,  FC,  LFE,  FR,  FL } },
+{ .ca_index = 0x22,  .speakers = {  TC,    0,  RR,  RL,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x23,  .speakers = {  TC,    0,  RR,  RL,  FC,  LFE,  FR,  FL } },
+{ .ca_index = 0x24,  .speakers = { FRH,  FLH,  RR,  RL,   0,    0,  FR,  FL } },
+{ .ca_index = 0x25,  .speakers = { FRH,  FLH,  RR,  RL,   0,  LFE,  FR,  FL } },
+{ .ca_index = 0x26,  .speakers = { FRW,  FLW,  RR,  RL,   0,    0,  FR,  FL } },
+{ .ca_index = 0x27,  .speakers = { FRW,  FLW,  RR,  RL,   0,  LFE,  FR,  FL } },
+{ .ca_index = 0x28,  .speakers = {  TC,   RC,  RR,  RL,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x29,  .speakers = {  TC,   RC,  RR,  RL,  FC,  LFE,  FR,  FL } },
+{ .ca_index = 0x2a,  .speakers = { FCH,   RC,  RR,  RL,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x2b,  .speakers = { FCH,   RC,  RR,  RL,  FC,  LFE,  FR,  FL } },
+{ .ca_index = 0x2c,  .speakers = {  TC,  FCH,  RR,  RL,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x2d,  .speakers = {  TC,  FCH,  RR,  RL,  FC,  LFE,  FR,  FL } },
+{ .ca_index = 0x2e,  .speakers = { FRH,  FLH,  RR,  RL,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x2f,  .speakers = { FRH,  FLH,  RR,  RL,  FC,  LFE,  FR,  FL } },
+{ .ca_index = 0x30,  .speakers = { FRW,  FLW,  RR,  RL,  FC,    0,  FR,  FL } },
+{ .ca_index = 0x31,  .speakers = { FRW,  FLW,  RR,  RL,  FC,  LFE,  FR,  FL } },
+};
+
+
+/*
+ * HDMI routines
+ */
+
+static int hda_node_index(hda_nid_t *nids, hda_nid_t nid)
+{
+	int i;
+
+	for (i = 0; nids[i]; i++)
+		if (nids[i] == nid)
+			return i;
+
+	snd_printk(KERN_WARNING "HDMI: nid %d not registered\n", nid);
+	return -EINVAL;
+}
+
+static void hdmi_get_show_eld(struct hda_codec *codec, hda_nid_t pin_nid,
+			      struct hdmi_eld *eld)
+{
+	if (!snd_hdmi_get_eld(eld, codec, pin_nid))
+		snd_hdmi_show_eld(eld);
+}
+
+#ifdef BE_PARANOID
+static void hdmi_get_dip_index(struct hda_codec *codec, hda_nid_t pin_nid,
+				int *packet_index, int *byte_index)
+{
+	int val;
+
+	val = snd_hda_codec_read(codec, pin_nid, 0,
+				 AC_VERB_GET_HDMI_DIP_INDEX, 0);
+
+	*packet_index = val >> 5;
+	*byte_index = val & 0x1f;
+}
+#endif
+
+static void hdmi_set_dip_index(struct hda_codec *codec, hda_nid_t pin_nid,
+				int packet_index, int byte_index)
+{
+	int val;
+
+	val = (packet_index << 5) | (byte_index & 0x1f);
+
+	snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_INDEX, val);
+}
+
+static void hdmi_write_dip_byte(struct hda_codec *codec, hda_nid_t pin_nid,
+				unsigned char val)
+{
+	snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_DATA, val);
+}
+
+static void hdmi_enable_output(struct hda_codec *codec, hda_nid_t pin_nid)
+{
+	/* Unmute */
+	if (get_wcaps(codec, pin_nid) & AC_WCAP_OUT_AMP)
+		snd_hda_codec_write(codec, pin_nid, 0,
+				AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE);
+	/* Enable pin out */
+	snd_hda_codec_write(codec, pin_nid, 0,
+			    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+}
+
+static int hdmi_get_channel_count(struct hda_codec *codec, hda_nid_t nid)
+{
+	return 1 + snd_hda_codec_read(codec, nid, 0,
+					AC_VERB_GET_CVT_CHAN_COUNT, 0);
+}
+
+static void hdmi_set_channel_count(struct hda_codec *codec,
+				   hda_nid_t nid, int chs)
+{
+	if (chs != hdmi_get_channel_count(codec, nid))
+		snd_hda_codec_write(codec, nid, 0,
+				    AC_VERB_SET_CVT_CHAN_COUNT, chs - 1);
+}
+
+
+/*
+ * Channel mapping routines
+ */
+
+/*
+ * Compute derived values in channel_allocations[].
+ */
+static void init_channel_allocations(void)
+{
+	int i, j;
+	struct cea_channel_speaker_allocation *p;
+
+	for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+		p = channel_allocations + i;
+		p->channels = 0;
+		p->spk_mask = 0;
+		for (j = 0; j < ARRAY_SIZE(p->speakers); j++)
+			if (p->speakers[j]) {
+				p->channels++;
+				p->spk_mask |= p->speakers[j];
+			}
+	}
+}
+
+/*
+ * The transformation takes two steps:
+ *
+ *	eld->spk_alloc => (eld_speaker_allocation_bits[]) => spk_mask
+ *	      spk_mask => (channel_allocations[])         => ai->CA
+ *
+ * TODO: it could select the wrong CA from multiple candidates.
+*/
+static int hdmi_channel_allocation(struct hda_codec *codec, hda_nid_t nid,
+				   int channels)
+{
+	struct hdmi_spec *spec = codec->spec;
+	struct hdmi_eld *eld;
+	int i;
+	int ca = 0;
+	int spk_mask = 0;
+	char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
+
+	/*
+	 * CA defaults to 0 for basic stereo audio
+	 */
+	if (channels <= 2)
+		return 0;
+
+	i = hda_node_index(spec->pin_cvt, nid);
+	if (i < 0)
+		return 0;
+	eld = &spec->sink_eld[i];
+
+	/*
+	 * HDMI sink's ELD info cannot always be retrieved for now, e.g.
+	 * in console or for audio devices. Assume the highest speakers
+	 * configuration, to _not_ prohibit multi-channel audio playback.
+	 */
+	if (!eld->spk_alloc)
+		eld->spk_alloc = 0xffff;
+
+	/*
+	 * expand ELD's speaker allocation mask
+	 *
+	 * ELD tells the speaker mask in a compact(paired) form,
+	 * expand ELD's notions to match the ones used by Audio InfoFrame.
+	 */
+	for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) {
+		if (eld->spk_alloc & (1 << i))
+			spk_mask |= eld_speaker_allocation_bits[i];
+	}
+
+	/* search for the first working match in the CA table */
+	for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+		if (channels == channel_allocations[i].channels &&
+		    (spk_mask & channel_allocations[i].spk_mask) ==
+				channel_allocations[i].spk_mask) {
+			ca = channel_allocations[i].ca_index;
+			break;
+		}
+	}
+
+	snd_print_channel_allocation(eld->spk_alloc, buf, sizeof(buf));
+	snd_printdd("HDMI: select CA 0x%x for %d-channel allocation: %s\n",
+		    ca, channels, buf);
+
+	return ca;
+}
+
+static void hdmi_debug_channel_mapping(struct hda_codec *codec,
+				       hda_nid_t pin_nid)
+{
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+	int i;
+	int slot;
+
+	for (i = 0; i < 8; i++) {
+		slot = snd_hda_codec_read(codec, pin_nid, 0,
+						AC_VERB_GET_HDMI_CHAN_SLOT, i);
+		printk(KERN_DEBUG "HDMI: ASP channel %d => slot %d\n",
+						slot >> 4, slot & 0xf);
+	}
+#endif
+}
+
+
+static void hdmi_setup_channel_mapping(struct hda_codec *codec,
+				       hda_nid_t pin_nid,
+				       int ca)
+{
+	int i;
+	int err;
+
+	if (hdmi_channel_mapping[ca][1] == 0) {
+		for (i = 0; i < channel_allocations[ca].channels; i++)
+			hdmi_channel_mapping[ca][i] = i | (i << 4);
+		for (; i < 8; i++)
+			hdmi_channel_mapping[ca][i] = 0xf | (i << 4);
+	}
+
+	for (i = 0; i < 8; i++) {
+		err = snd_hda_codec_write(codec, pin_nid, 0,
+					  AC_VERB_SET_HDMI_CHAN_SLOT,
+					  hdmi_channel_mapping[ca][i]);
+		if (err) {
+			snd_printdd(KERN_NOTICE
+				    "HDMI: channel mapping failed\n");
+			break;
+		}
+	}
+
+	hdmi_debug_channel_mapping(codec, pin_nid);
+}
+
+
+/*
+ * Audio InfoFrame routines
+ */
+
+/*
+ * Enable Audio InfoFrame Transmission
+ */
+static void hdmi_start_infoframe_trans(struct hda_codec *codec,
+				       hda_nid_t pin_nid)
+{
+	hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
+	snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_XMIT,
+						AC_DIPXMIT_BEST);
+}
+
+/*
+ * Disable Audio InfoFrame Transmission
+ */
+static void hdmi_stop_infoframe_trans(struct hda_codec *codec,
+				      hda_nid_t pin_nid)
+{
+	hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
+	snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_HDMI_DIP_XMIT,
+						AC_DIPXMIT_DISABLE);
+}
+
+static void hdmi_debug_dip_size(struct hda_codec *codec, hda_nid_t pin_nid)
+{
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+	int i;
+	int size;
+
+	size = snd_hdmi_get_eld_size(codec, pin_nid);
+	printk(KERN_DEBUG "HDMI: ELD buf size is %d\n", size);
+
+	for (i = 0; i < 8; i++) {
+		size = snd_hda_codec_read(codec, pin_nid, 0,
+						AC_VERB_GET_HDMI_DIP_SIZE, i);
+		printk(KERN_DEBUG "HDMI: DIP GP[%d] buf size is %d\n", i, size);
+	}
+#endif
+}
+
+static void hdmi_clear_dip_buffers(struct hda_codec *codec, hda_nid_t pin_nid)
+{
+#ifdef BE_PARANOID
+	int i, j;
+	int size;
+	int pi, bi;
+	for (i = 0; i < 8; i++) {
+		size = snd_hda_codec_read(codec, pin_nid, 0,
+						AC_VERB_GET_HDMI_DIP_SIZE, i);
+		if (size == 0)
+			continue;
+
+		hdmi_set_dip_index(codec, pin_nid, i, 0x0);
+		for (j = 1; j < 1000; j++) {
+			hdmi_write_dip_byte(codec, pin_nid, 0x0);
+			hdmi_get_dip_index(codec, pin_nid, &pi, &bi);
+			if (pi != i)
+				snd_printd(KERN_INFO "dip index %d: %d != %d\n",
+						bi, pi, i);
+			if (bi == 0) /* byte index wrapped around */
+				break;
+		}
+		snd_printd(KERN_INFO
+			"HDMI: DIP GP[%d] buf reported size=%d, written=%d\n",
+			i, size, j);
+	}
+#endif
+}
+
+static void hdmi_checksum_audio_infoframe(struct hdmi_audio_infoframe *hdmi_ai)
+{
+	u8 *bytes = (u8 *)hdmi_ai;
+	u8 sum = 0;
+	int i;
+
+	hdmi_ai->checksum = 0;
+
+	for (i = 0; i < sizeof(*hdmi_ai); i++)
+		sum += bytes[i];
+
+	hdmi_ai->checksum = -sum;
+}
+
+static void hdmi_fill_audio_infoframe(struct hda_codec *codec,
+				      hda_nid_t pin_nid,
+				      u8 *dip, int size)
+{
+	int i;
+
+	hdmi_debug_dip_size(codec, pin_nid);
+	hdmi_clear_dip_buffers(codec, pin_nid); /* be paranoid */
+
+	hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
+	for (i = 0; i < size; i++)
+		hdmi_write_dip_byte(codec, pin_nid, dip[i]);
+}
+
+static bool hdmi_infoframe_uptodate(struct hda_codec *codec, hda_nid_t pin_nid,
+				    u8 *dip, int size)
+{
+	u8 val;
+	int i;
+
+	if (snd_hda_codec_read(codec, pin_nid, 0, AC_VERB_GET_HDMI_DIP_XMIT, 0)
+							    != AC_DIPXMIT_BEST)
+		return false;
+
+	hdmi_set_dip_index(codec, pin_nid, 0x0, 0x0);
+	for (i = 0; i < size; i++) {
+		val = snd_hda_codec_read(codec, pin_nid, 0,
+					 AC_VERB_GET_HDMI_DIP_DATA, 0);
+		if (val != dip[i])
+			return false;
+	}
+
+	return true;
+}
+
+static void hdmi_setup_audio_infoframe(struct hda_codec *codec, hda_nid_t nid,
+					struct snd_pcm_substream *substream)
+{
+	struct hdmi_spec *spec = codec->spec;
+	hda_nid_t pin_nid;
+	int channels = substream->runtime->channels;
+	int ca;
+	int i;
+	u8 ai[max(sizeof(struct hdmi_audio_infoframe),
+		  sizeof(struct dp_audio_infoframe))];
+
+	ca = hdmi_channel_allocation(codec, nid, channels);
+
+	for (i = 0; i < spec->num_pins; i++) {
+		if (spec->pin_cvt[i] != nid)
+			continue;
+		if (!spec->sink_eld[i].monitor_present)
+			continue;
+
+		pin_nid = spec->pin[i];
+
+		memset(ai, 0, sizeof(ai));
+		if (spec->sink_eld[i].conn_type == 0) { /* HDMI */
+			struct hdmi_audio_infoframe *hdmi_ai;
+
+			hdmi_ai = (struct hdmi_audio_infoframe *)ai;
+			hdmi_ai->type		= 0x84;
+			hdmi_ai->ver		= 0x01;
+			hdmi_ai->len		= 0x0a;
+			hdmi_ai->CC02_CT47	= channels - 1;
+			hdmi_checksum_audio_infoframe(hdmi_ai);
+		} else if (spec->sink_eld[i].conn_type == 1) { /* DisplayPort */
+			struct dp_audio_infoframe *dp_ai;
+
+			dp_ai = (struct dp_audio_infoframe *)ai;
+			dp_ai->type		= 0x84;
+			dp_ai->len		= 0x1b;
+			dp_ai->ver		= 0x11 << 2;
+			dp_ai->CC02_CT47	= channels - 1;
+		} else {
+			snd_printd("HDMI: unknown connection type at pin %d\n",
+				   pin_nid);
+			continue;
+		}
+
+		/*
+		 * sizeof(ai) is used instead of sizeof(*hdmi_ai) or
+		 * sizeof(*dp_ai) to avoid partial match/update problems when
+		 * the user switches between HDMI/DP monitors.
+		 */
+		if (!hdmi_infoframe_uptodate(codec, pin_nid, ai, sizeof(ai))) {
+			snd_printdd("hdmi_setup_audio_infoframe: "
+				    "cvt=%d pin=%d channels=%d\n",
+				    nid, pin_nid,
+				    channels);
+			hdmi_setup_channel_mapping(codec, pin_nid, ca);
+			hdmi_stop_infoframe_trans(codec, pin_nid);
+			hdmi_fill_audio_infoframe(codec, pin_nid,
+						  ai, sizeof(ai));
+			hdmi_start_infoframe_trans(codec, pin_nid);
+		}
+	}
+}
+
+
+/*
+ * Unsolicited events
+ */
+
+static void hdmi_present_sense(struct hda_codec *codec, hda_nid_t pin_nid,
+			       struct hdmi_eld *eld);
+
+static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res)
+{
+	struct hdmi_spec *spec = codec->spec;
+	int tag = res >> AC_UNSOL_RES_TAG_SHIFT;
+	int pind = !!(res & AC_UNSOL_RES_PD);
+	int eldv = !!(res & AC_UNSOL_RES_ELDV);
+	int index;
+
+	printk(KERN_INFO
+		"HDMI hot plug event: Pin=%d Presence_Detect=%d ELD_Valid=%d\n",
+		tag, pind, eldv);
+
+	index = hda_node_index(spec->pin, tag);
+	if (index < 0)
+		return;
+
+	if (spec->old_pin_detect) {
+		if (pind)
+			hdmi_present_sense(codec, tag, &spec->sink_eld[index]);
+		pind = spec->sink_eld[index].monitor_present;
+	}
+
+	spec->sink_eld[index].monitor_present = pind;
+	spec->sink_eld[index].eld_valid = eldv;
+
+	if (pind && eldv) {
+		hdmi_get_show_eld(codec, spec->pin[index],
+				  &spec->sink_eld[index]);
+		/* TODO: do real things about ELD */
+	}
+}
+
+static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res)
+{
+	int tag = res >> AC_UNSOL_RES_TAG_SHIFT;
+	int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT;
+	int cp_state = !!(res & AC_UNSOL_RES_CP_STATE);
+	int cp_ready = !!(res & AC_UNSOL_RES_CP_READY);
+
+	printk(KERN_INFO
+		"HDMI CP event: PIN=%d SUBTAG=0x%x CP_STATE=%d CP_READY=%d\n",
+		tag,
+		subtag,
+		cp_state,
+		cp_ready);
+
+	/* TODO */
+	if (cp_state)
+		;
+	if (cp_ready)
+		;
+}
+
+
+static void hdmi_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	struct hdmi_spec *spec = codec->spec;
+	int tag = res >> AC_UNSOL_RES_TAG_SHIFT;
+	int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT;
+
+	if (hda_node_index(spec->pin, tag) < 0) {
+		snd_printd(KERN_INFO "Unexpected HDMI event tag 0x%x\n", tag);
+		return;
+	}
+
+	if (subtag == 0)
+		hdmi_intrinsic_event(codec, res);
+	else
+		hdmi_non_intrinsic_event(codec, res);
+}
+
+/*
+ * Callbacks
+ */
+
+/* HBR should be Non-PCM, 8 channels */
+#define is_hbr_format(format) \
+	((format & AC_FMT_TYPE_NON_PCM) && (format & AC_FMT_CHAN_MASK) == 7)
+
+static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t nid,
+			      u32 stream_tag, int format)
+{
+	struct hdmi_spec *spec = codec->spec;
+	int pinctl;
+	int new_pinctl = 0;
+	int i;
+
+	for (i = 0; i < spec->num_pins; i++) {
+		if (spec->pin_cvt[i] != nid)
+			continue;
+		if (!(snd_hda_query_pin_caps(codec, spec->pin[i]) & AC_PINCAP_HBR))
+			continue;
+
+		pinctl = snd_hda_codec_read(codec, spec->pin[i], 0,
+					    AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+
+		new_pinctl = pinctl & ~AC_PINCTL_EPT;
+		if (is_hbr_format(format))
+			new_pinctl |= AC_PINCTL_EPT_HBR;
+		else
+			new_pinctl |= AC_PINCTL_EPT_NATIVE;
+
+		snd_printdd("hdmi_setup_stream: "
+			    "NID=0x%x, %spinctl=0x%x\n",
+			    spec->pin[i],
+			    pinctl == new_pinctl ? "" : "new-",
+			    new_pinctl);
+
+		if (pinctl != new_pinctl)
+			snd_hda_codec_write(codec, spec->pin[i], 0,
+					    AC_VERB_SET_PIN_WIDGET_CONTROL,
+					    new_pinctl);
+	}
+
+	if (is_hbr_format(format) && !new_pinctl) {
+		snd_printdd("hdmi_setup_stream: HBR is not supported\n");
+		return -EINVAL;
+	}
+
+	snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format);
+	return 0;
+}
+
+/*
+ * HDA PCM callbacks
+ */
+static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
+			 struct hda_codec *codec,
+			 struct snd_pcm_substream *substream)
+{
+	struct hdmi_spec *spec = codec->spec;
+	struct hdmi_eld *eld;
+	struct hda_pcm_stream *codec_pars;
+	unsigned int idx;
+
+	for (idx = 0; idx < spec->num_cvts; idx++)
+		if (hinfo->nid == spec->cvt[idx])
+			break;
+	if (snd_BUG_ON(idx >= spec->num_cvts) ||
+	    snd_BUG_ON(idx >= spec->num_pins))
+		return -EINVAL;
+
+	/* save the PCM info the codec provides */
+	codec_pars = &spec->codec_pcm_pars[idx];
+	if (!codec_pars->rates)
+		*codec_pars = *hinfo;
+
+	eld = &spec->sink_eld[idx];
+	if (eld->sad_count > 0) {
+		hdmi_eld_update_pcm_info(eld, hinfo, codec_pars);
+		if (hinfo->channels_min > hinfo->channels_max ||
+		    !hinfo->rates || !hinfo->formats)
+			return -ENODEV;
+	} else {
+		/* fallback to the codec default */
+		hinfo->channels_max = codec_pars->channels_max;
+		hinfo->rates = codec_pars->rates;
+		hinfo->formats = codec_pars->formats;
+		hinfo->maxbps = codec_pars->maxbps;
+	}
+	return 0;
+}
+
+/*
+ * HDA/HDMI auto parsing
+ */
+static int hdmi_read_pin_conn(struct hda_codec *codec, hda_nid_t pin_nid)
+{
+	struct hdmi_spec *spec = codec->spec;
+	hda_nid_t conn_list[HDA_MAX_CONNECTIONS];
+	int conn_len, curr;
+	int index;
+
+	if (!(get_wcaps(codec, pin_nid) & AC_WCAP_CONN_LIST)) {
+		snd_printk(KERN_WARNING
+			   "HDMI: pin %d wcaps %#x "
+			   "does not support connection list\n",
+			   pin_nid, get_wcaps(codec, pin_nid));
+		return -EINVAL;
+	}
+
+	conn_len = snd_hda_get_connections(codec, pin_nid, conn_list,
+					   HDA_MAX_CONNECTIONS);
+	if (conn_len > 1)
+		curr = snd_hda_codec_read(codec, pin_nid, 0,
+					  AC_VERB_GET_CONNECT_SEL, 0);
+	else
+		curr = 0;
+
+	index = hda_node_index(spec->pin, pin_nid);
+	if (index < 0)
+		return -EINVAL;
+
+	spec->pin_cvt[index] = conn_list[curr];
+
+	return 0;
+}
+
+static void hdmi_present_sense(struct hda_codec *codec, hda_nid_t pin_nid,
+			       struct hdmi_eld *eld)
+{
+	int present = snd_hda_pin_sense(codec, pin_nid);
+
+	eld->monitor_present	= !!(present & AC_PINSENSE_PRESENCE);
+	eld->eld_valid		= !!(present & AC_PINSENSE_ELDV);
+
+	if (present & AC_PINSENSE_ELDV)
+		hdmi_get_show_eld(codec, pin_nid, eld);
+}
+
+static int hdmi_add_pin(struct hda_codec *codec, hda_nid_t pin_nid)
+{
+	struct hdmi_spec *spec = codec->spec;
+
+	if (spec->num_pins >= MAX_HDMI_PINS) {
+		snd_printk(KERN_WARNING
+			   "HDMI: no space for pin %d\n", pin_nid);
+		return -E2BIG;
+	}
+
+	hdmi_present_sense(codec, pin_nid, &spec->sink_eld[spec->num_pins]);
+
+	spec->pin[spec->num_pins] = pin_nid;
+	spec->num_pins++;
+
+	/*
+	 * It is assumed that converter nodes come first in the node list and
+	 * hence have been registered and usable now.
+	 */
+	return hdmi_read_pin_conn(codec, pin_nid);
+}
+
+static int hdmi_add_cvt(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct hdmi_spec *spec = codec->spec;
+
+	if (spec->num_cvts >= MAX_HDMI_CVTS) {
+		snd_printk(KERN_WARNING
+			   "HDMI: no space for converter %d\n", nid);
+		return -E2BIG;
+	}
+
+	spec->cvt[spec->num_cvts] = nid;
+	spec->num_cvts++;
+
+	return 0;
+}
+
+static int hdmi_parse_codec(struct hda_codec *codec)
+{
+	hda_nid_t nid;
+	int i, nodes;
+
+	nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid);
+	if (!nid || nodes < 0) {
+		snd_printk(KERN_WARNING "HDMI: failed to get afg sub nodes\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < nodes; i++, nid++) {
+		unsigned int caps;
+		unsigned int type;
+
+		caps = snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
+		type = get_wcaps_type(caps);
+
+		if (!(caps & AC_WCAP_DIGITAL))
+			continue;
+
+		switch (type) {
+		case AC_WID_AUD_OUT:
+			hdmi_add_cvt(codec, nid);
+			break;
+		case AC_WID_PIN:
+			caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
+			if (!(caps & (AC_PINCAP_HDMI | AC_PINCAP_DP)))
+				continue;
+			hdmi_add_pin(codec, nid);
+			break;
+		}
+	}
+
+	/*
+	 * G45/IbexPeak don't support EPSS: the unsolicited pin hot plug event
+	 * can be lost and presence sense verb will become inaccurate if the
+	 * HDA link is powered off at hot plug or hw initialization time.
+	 */
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	if (!(snd_hda_param_read(codec, codec->afg, AC_PAR_POWER_STATE) &
+	      AC_PWRST_EPSS))
+		codec->bus->power_keep_link_on = 1;
+#endif
+
+	return 0;
+}
+
+/*
+ */
+static char *generic_hdmi_pcm_names[MAX_HDMI_CVTS] = {
+	"HDMI 0",
+	"HDMI 1",
+	"HDMI 2",
+};
+
+/*
+ * HDMI callbacks
+ */
+
+static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					   struct hda_codec *codec,
+					   unsigned int stream_tag,
+					   unsigned int format,
+					   struct snd_pcm_substream *substream)
+{
+	hdmi_set_channel_count(codec, hinfo->nid,
+			       substream->runtime->channels);
+
+	hdmi_setup_audio_infoframe(codec, hinfo->nid, substream);
+
+	return hdmi_setup_stream(codec, hinfo->nid, stream_tag, format);
+}
+
+static struct hda_pcm_stream generic_hdmi_pcm_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.ops = {
+		.open = hdmi_pcm_open,
+		.prepare = generic_hdmi_playback_pcm_prepare,
+	},
+};
+
+static int generic_hdmi_build_pcms(struct hda_codec *codec)
+{
+	struct hdmi_spec *spec = codec->spec;
+	struct hda_pcm *info = spec->pcm_rec;
+	int i;
+
+	codec->num_pcms = spec->num_cvts;
+	codec->pcm_info = info;
+
+	for (i = 0; i < codec->num_pcms; i++, info++) {
+		unsigned int chans;
+		struct hda_pcm_stream *pstr;
+
+		chans = get_wcaps(codec, spec->cvt[i]);
+		chans = get_wcaps_channels(chans);
+
+		info->name = generic_hdmi_pcm_names[i];
+		info->pcm_type = HDA_PCM_TYPE_HDMI;
+		pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK];
+		if (spec->pcm_playback)
+			*pstr = *spec->pcm_playback;
+		else
+			*pstr = generic_hdmi_pcm_playback;
+		pstr->nid = spec->cvt[i];
+		if (pstr->channels_max <= 2 && chans && chans <= 16)
+			pstr->channels_max = chans;
+	}
+
+	return 0;
+}
+
+static int generic_hdmi_build_controls(struct hda_codec *codec)
+{
+	struct hdmi_spec *spec = codec->spec;
+	int err;
+	int i;
+
+	for (i = 0; i < codec->num_pcms; i++) {
+		err = snd_hda_create_spdif_out_ctls(codec, spec->cvt[i]);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int generic_hdmi_init(struct hda_codec *codec)
+{
+	struct hdmi_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; spec->pin[i]; i++) {
+		hdmi_enable_output(codec, spec->pin[i]);
+		snd_hda_codec_write(codec, spec->pin[i], 0,
+				    AC_VERB_SET_UNSOLICITED_ENABLE,
+				    AC_USRSP_EN | spec->pin[i]);
+	}
+	return 0;
+}
+
+static void generic_hdmi_free(struct hda_codec *codec)
+{
+	struct hdmi_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->num_pins; i++)
+		snd_hda_eld_proc_free(codec, &spec->sink_eld[i]);
+
+	kfree(spec);
+}
+
+static struct hda_codec_ops generic_hdmi_patch_ops = {
+	.init			= generic_hdmi_init,
+	.free			= generic_hdmi_free,
+	.build_pcms		= generic_hdmi_build_pcms,
+	.build_controls		= generic_hdmi_build_controls,
+	.unsol_event		= hdmi_unsol_event,
+};
+
+static int patch_generic_hdmi(struct hda_codec *codec)
+{
+	struct hdmi_spec *spec;
+	int i;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+	if (hdmi_parse_codec(codec) < 0) {
+		codec->spec = NULL;
+		kfree(spec);
+		return -EINVAL;
+	}
+	codec->patch_ops = generic_hdmi_patch_ops;
+
+	for (i = 0; i < spec->num_pins; i++)
+		snd_hda_eld_proc_new(codec, &spec->sink_eld[i], i);
+
+	init_channel_allocations();
+
+	return 0;
+}
+
+/*
+ * Nvidia specific implementations
+ */
+
+#define Nv_VERB_SET_Channel_Allocation          0xF79
+#define Nv_VERB_SET_Info_Frame_Checksum         0xF7A
+#define Nv_VERB_SET_Audio_Protection_On         0xF98
+#define Nv_VERB_SET_Audio_Protection_Off        0xF99
+
+#define nvhdmi_master_con_nid_7x	0x04
+#define nvhdmi_master_pin_nid_7x	0x05
+
+static hda_nid_t nvhdmi_con_nids_7x[4] = {
+	/*front, rear, clfe, rear_surr */
+	0x6, 0x8, 0xa, 0xc,
+};
+
+static struct hda_verb nvhdmi_basic_init_7x[] = {
+	/* set audio protect on */
+	{ 0x1, Nv_VERB_SET_Audio_Protection_On, 0x1},
+	/* enable digital output on pin widget */
+	{ 0x5, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+	{ 0x7, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+	{ 0x9, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+	{ 0xb, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+	{ 0xd, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x5 },
+	{} /* terminator */
+};
+
+#ifdef LIMITED_RATE_FMT_SUPPORT
+/* support only the safe format and rate */
+#define SUPPORTED_RATES		SNDRV_PCM_RATE_48000
+#define SUPPORTED_MAXBPS	16
+#define SUPPORTED_FORMATS	SNDRV_PCM_FMTBIT_S16_LE
+#else
+/* support all rates and formats */
+#define SUPPORTED_RATES \
+	(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
+	SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\
+	 SNDRV_PCM_RATE_192000)
+#define SUPPORTED_MAXBPS	24
+#define SUPPORTED_FORMATS \
+	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
+#endif
+
+static int nvhdmi_7x_init(struct hda_codec *codec)
+{
+	snd_hda_sequence_write(codec, nvhdmi_basic_init_7x);
+	return 0;
+}
+
+static int simple_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				    struct hda_codec *codec,
+				    struct snd_pcm_substream *substream)
+{
+	struct hdmi_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int simple_playback_pcm_close(struct hda_pcm_stream *hinfo,
+				     struct hda_codec *codec,
+				     struct snd_pcm_substream *substream)
+{
+	struct hdmi_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int simple_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       unsigned int stream_tag,
+				       unsigned int format,
+				       struct snd_pcm_substream *substream)
+{
+	struct hdmi_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
+					     stream_tag, format, substream);
+}
+
+static int nvhdmi_8ch_7x_pcm_close(struct hda_pcm_stream *hinfo,
+				   struct hda_codec *codec,
+				   struct snd_pcm_substream *substream)
+{
+	struct hdmi_spec *spec = codec->spec;
+	int i;
+
+	snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x,
+			0, AC_VERB_SET_CHANNEL_STREAMID, 0);
+	for (i = 0; i < 4; i++) {
+		/* set the stream id */
+		snd_hda_codec_write(codec, nvhdmi_con_nids_7x[i], 0,
+				AC_VERB_SET_CHANNEL_STREAMID, 0);
+		/* set the stream format */
+		snd_hda_codec_write(codec, nvhdmi_con_nids_7x[i], 0,
+				AC_VERB_SET_STREAM_FORMAT, 0);
+	}
+
+	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int nvhdmi_8ch_7x_pcm_prepare(struct hda_pcm_stream *hinfo,
+				     struct hda_codec *codec,
+				     unsigned int stream_tag,
+				     unsigned int format,
+				     struct snd_pcm_substream *substream)
+{
+	int chs;
+	unsigned int dataDCC1, dataDCC2, chan, chanmask, channel_id;
+	int i;
+
+	mutex_lock(&codec->spdif_mutex);
+
+	chs = substream->runtime->channels;
+	chan = chs ? (chs - 1) : 1;
+
+	switch (chs) {
+	default:
+	case 0:
+	case 2:
+		chanmask = 0x00;
+		break;
+	case 4:
+		chanmask = 0x08;
+		break;
+	case 6:
+		chanmask = 0x0b;
+		break;
+	case 8:
+		chanmask = 0x13;
+		break;
+	}
+	dataDCC1 = AC_DIG1_ENABLE | AC_DIG1_COPYRIGHT;
+	dataDCC2 = 0x2;
+
+	/* set the Audio InforFrame Channel Allocation */
+	snd_hda_codec_write(codec, 0x1, 0,
+			Nv_VERB_SET_Channel_Allocation, chanmask);
+
+	/* turn off SPDIF once; otherwise the IEC958 bits won't be updated */
+	if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE))
+		snd_hda_codec_write(codec,
+				nvhdmi_master_con_nid_7x,
+				0,
+				AC_VERB_SET_DIGI_CONVERT_1,
+				codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff);
+
+	/* set the stream id */
+	snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, 0,
+			AC_VERB_SET_CHANNEL_STREAMID, (stream_tag << 4) | 0x0);
+
+	/* set the stream format */
+	snd_hda_codec_write(codec, nvhdmi_master_con_nid_7x, 0,
+			AC_VERB_SET_STREAM_FORMAT, format);
+
+	/* turn on again (if needed) */
+	/* enable and set the channel status audio/data flag */
+	if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE)) {
+		snd_hda_codec_write(codec,
+				nvhdmi_master_con_nid_7x,
+				0,
+				AC_VERB_SET_DIGI_CONVERT_1,
+				codec->spdif_ctls & 0xff);
+		snd_hda_codec_write(codec,
+				nvhdmi_master_con_nid_7x,
+				0,
+				AC_VERB_SET_DIGI_CONVERT_2, dataDCC2);
+	}
+
+	for (i = 0; i < 4; i++) {
+		if (chs == 2)
+			channel_id = 0;
+		else
+			channel_id = i * 2;
+
+		/* turn off SPDIF once;
+		 *otherwise the IEC958 bits won't be updated
+		 */
+		if (codec->spdif_status_reset &&
+		(codec->spdif_ctls & AC_DIG1_ENABLE))
+			snd_hda_codec_write(codec,
+				nvhdmi_con_nids_7x[i],
+				0,
+				AC_VERB_SET_DIGI_CONVERT_1,
+				codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff);
+		/* set the stream id */
+		snd_hda_codec_write(codec,
+				nvhdmi_con_nids_7x[i],
+				0,
+				AC_VERB_SET_CHANNEL_STREAMID,
+				(stream_tag << 4) | channel_id);
+		/* set the stream format */
+		snd_hda_codec_write(codec,
+				nvhdmi_con_nids_7x[i],
+				0,
+				AC_VERB_SET_STREAM_FORMAT,
+				format);
+		/* turn on again (if needed) */
+		/* enable and set the channel status audio/data flag */
+		if (codec->spdif_status_reset &&
+		(codec->spdif_ctls & AC_DIG1_ENABLE)) {
+			snd_hda_codec_write(codec,
+					nvhdmi_con_nids_7x[i],
+					0,
+					AC_VERB_SET_DIGI_CONVERT_1,
+					codec->spdif_ctls & 0xff);
+			snd_hda_codec_write(codec,
+					nvhdmi_con_nids_7x[i],
+					0,
+					AC_VERB_SET_DIGI_CONVERT_2, dataDCC2);
+		}
+	}
+
+	/* set the Audio Info Frame Checksum */
+	snd_hda_codec_write(codec, 0x1, 0,
+			Nv_VERB_SET_Info_Frame_Checksum,
+			(0x71 - chan - chanmask));
+
+	mutex_unlock(&codec->spdif_mutex);
+	return 0;
+}
+
+static struct hda_pcm_stream nvhdmi_pcm_playback_8ch_7x = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 8,
+	.nid = nvhdmi_master_con_nid_7x,
+	.rates = SUPPORTED_RATES,
+	.maxbps = SUPPORTED_MAXBPS,
+	.formats = SUPPORTED_FORMATS,
+	.ops = {
+		.open = simple_playback_pcm_open,
+		.close = nvhdmi_8ch_7x_pcm_close,
+		.prepare = nvhdmi_8ch_7x_pcm_prepare
+	},
+};
+
+static struct hda_pcm_stream nvhdmi_pcm_playback_2ch = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = nvhdmi_master_con_nid_7x,
+	.rates = SUPPORTED_RATES,
+	.maxbps = SUPPORTED_MAXBPS,
+	.formats = SUPPORTED_FORMATS,
+	.ops = {
+		.open = simple_playback_pcm_open,
+		.close = simple_playback_pcm_close,
+		.prepare = simple_playback_pcm_prepare
+	},
+};
+
+static struct hda_codec_ops nvhdmi_patch_ops_8ch_7x = {
+	.build_controls = generic_hdmi_build_controls,
+	.build_pcms = generic_hdmi_build_pcms,
+	.init = nvhdmi_7x_init,
+	.free = generic_hdmi_free,
+};
+
+static struct hda_codec_ops nvhdmi_patch_ops_2ch = {
+	.build_controls = generic_hdmi_build_controls,
+	.build_pcms = generic_hdmi_build_pcms,
+	.init = nvhdmi_7x_init,
+	.free = generic_hdmi_free,
+};
+
+static int patch_nvhdmi_8ch_89(struct hda_codec *codec)
+{
+	struct hdmi_spec *spec;
+	int err = patch_generic_hdmi(codec);
+
+	if (err < 0)
+		return err;
+	spec = codec->spec;
+	spec->old_pin_detect = 1;
+	return 0;
+}
+
+static int patch_nvhdmi_2ch(struct hda_codec *codec)
+{
+	struct hdmi_spec *spec;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	spec->multiout.num_dacs = 0;  /* no analog */
+	spec->multiout.max_channels = 2;
+	spec->multiout.dig_out_nid = nvhdmi_master_con_nid_7x;
+	spec->old_pin_detect = 1;
+	spec->num_cvts = 1;
+	spec->cvt[0] = nvhdmi_master_con_nid_7x;
+	spec->pcm_playback = &nvhdmi_pcm_playback_2ch;
+
+	codec->patch_ops = nvhdmi_patch_ops_2ch;
+
+	return 0;
+}
+
+static int patch_nvhdmi_8ch_7x(struct hda_codec *codec)
+{
+	struct hdmi_spec *spec;
+	int err = patch_nvhdmi_2ch(codec);
+
+	if (err < 0)
+		return err;
+	spec = codec->spec;
+	spec->multiout.max_channels = 8;
+	spec->pcm_playback = &nvhdmi_pcm_playback_8ch_7x;
+	codec->patch_ops = nvhdmi_patch_ops_8ch_7x;
+	return 0;
+}
+
+/*
+ * ATI-specific implementations
+ *
+ * FIXME: we may omit the whole this and use the generic code once after
+ * it's confirmed to work.
+ */
+
+#define ATIHDMI_CVT_NID		0x02	/* audio converter */
+#define ATIHDMI_PIN_NID		0x03	/* HDMI output pin */
+
+static int atihdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					struct hda_codec *codec,
+					unsigned int stream_tag,
+					unsigned int format,
+					struct snd_pcm_substream *substream)
+{
+	struct hdmi_spec *spec = codec->spec;
+	int chans = substream->runtime->channels;
+	int i, err;
+
+	err = simple_playback_pcm_prepare(hinfo, codec, stream_tag, format,
+					  substream);
+	if (err < 0)
+		return err;
+	snd_hda_codec_write(codec, spec->cvt[0], 0, AC_VERB_SET_CVT_CHAN_COUNT,
+			    chans - 1);
+	/* FIXME: XXX */
+	for (i = 0; i < chans; i++) {
+		snd_hda_codec_write(codec, spec->cvt[0], 0,
+				    AC_VERB_SET_HDMI_CHAN_SLOT,
+				    (i << 4) | i);
+	}
+	return 0;
+}
+
+static struct hda_pcm_stream atihdmi_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = ATIHDMI_CVT_NID,
+	.ops = {
+		.open = simple_playback_pcm_open,
+		.close = simple_playback_pcm_close,
+		.prepare = atihdmi_playback_pcm_prepare
+	},
+};
+
+static struct hda_verb atihdmi_basic_init[] = {
+	/* enable digital output on pin widget */
+	{ 0x03, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{} /* terminator */
+};
+
+static int atihdmi_init(struct hda_codec *codec)
+{
+	struct hdmi_spec *spec = codec->spec;
+
+	snd_hda_sequence_write(codec, atihdmi_basic_init);
+	/* SI codec requires to unmute the pin */
+	if (get_wcaps(codec, spec->pin[0]) & AC_WCAP_OUT_AMP)
+		snd_hda_codec_write(codec, spec->pin[0], 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE,
+				    AMP_OUT_UNMUTE);
+	return 0;
+}
+
+static struct hda_codec_ops atihdmi_patch_ops = {
+	.build_controls = generic_hdmi_build_controls,
+	.build_pcms = generic_hdmi_build_pcms,
+	.init = atihdmi_init,
+	.free = generic_hdmi_free,
+};
+
+
+static int patch_atihdmi(struct hda_codec *codec)
+{
+	struct hdmi_spec *spec;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	spec->multiout.num_dacs = 0;	  /* no analog */
+	spec->multiout.max_channels = 2;
+	spec->multiout.dig_out_nid = ATIHDMI_CVT_NID;
+	spec->num_cvts = 1;
+	spec->cvt[0] = ATIHDMI_CVT_NID;
+	spec->pin[0] = ATIHDMI_PIN_NID;
+	spec->pcm_playback = &atihdmi_pcm_digital_playback;
+
+	codec->patch_ops = atihdmi_patch_ops;
+
+	return 0;
+}
+
+
+/*
+ * patch entries
+ */
+static struct hda_codec_preset snd_hda_preset_hdmi[] = {
+{ .id = 0x1002793c, .name = "RS600 HDMI",	.patch = patch_atihdmi },
+{ .id = 0x10027919, .name = "RS600 HDMI",	.patch = patch_atihdmi },
+{ .id = 0x1002791a, .name = "RS690/780 HDMI",	.patch = patch_atihdmi },
+{ .id = 0x1002aa01, .name = "R6xx HDMI",	.patch = patch_atihdmi },
+{ .id = 0x10951390, .name = "SiI1390 HDMI",	.patch = patch_generic_hdmi },
+{ .id = 0x10951392, .name = "SiI1392 HDMI",	.patch = patch_generic_hdmi },
+{ .id = 0x17e80047, .name = "Chrontel HDMI",	.patch = patch_generic_hdmi },
+{ .id = 0x10de0002, .name = "MCP77/78 HDMI",	.patch = patch_nvhdmi_8ch_7x },
+{ .id = 0x10de0003, .name = "MCP77/78 HDMI",	.patch = patch_nvhdmi_8ch_7x },
+{ .id = 0x10de0005, .name = "MCP77/78 HDMI",	.patch = patch_nvhdmi_8ch_7x },
+{ .id = 0x10de0006, .name = "MCP77/78 HDMI",	.patch = patch_nvhdmi_8ch_7x },
+{ .id = 0x10de0007, .name = "MCP79/7A HDMI",	.patch = patch_nvhdmi_8ch_7x },
+{ .id = 0x10de000a, .name = "GPU 0a HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de000b, .name = "GPU 0b HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de000c, .name = "MCP89 HDMI",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de000d, .name = "GPU 0d HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0010, .name = "GPU 10 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0011, .name = "GPU 11 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0012, .name = "GPU 12 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0013, .name = "GPU 13 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0014, .name = "GPU 14 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0018, .name = "GPU 18 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0019, .name = "GPU 19 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de001a, .name = "GPU 1a HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de001b, .name = "GPU 1b HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de001c, .name = "GPU 1c HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0040, .name = "GPU 40 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0041, .name = "GPU 41 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0042, .name = "GPU 42 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0043, .name = "GPU 43 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0044, .name = "GPU 44 HDMI/DP",	.patch = patch_nvhdmi_8ch_89 },
+{ .id = 0x10de0067, .name = "MCP67 HDMI",	.patch = patch_nvhdmi_2ch },
+{ .id = 0x10de8001, .name = "MCP73 HDMI",	.patch = patch_nvhdmi_2ch },
+{ .id = 0x80860054, .name = "IbexPeak HDMI",	.patch = patch_generic_hdmi },
+{ .id = 0x80862801, .name = "Bearlake HDMI",	.patch = patch_generic_hdmi },
+{ .id = 0x80862802, .name = "Cantiga HDMI",	.patch = patch_generic_hdmi },
+{ .id = 0x80862803, .name = "Eaglelake HDMI",	.patch = patch_generic_hdmi },
+{ .id = 0x80862804, .name = "IbexPeak HDMI",	.patch = patch_generic_hdmi },
+{ .id = 0x80862805, .name = "CougarPoint HDMI",	.patch = patch_generic_hdmi },
+{ .id = 0x808629fb, .name = "Crestline HDMI",	.patch = patch_generic_hdmi },
+{} /* terminator */
+};
+
+MODULE_ALIAS("snd-hda-codec-id:1002793c");
+MODULE_ALIAS("snd-hda-codec-id:10027919");
+MODULE_ALIAS("snd-hda-codec-id:1002791a");
+MODULE_ALIAS("snd-hda-codec-id:1002aa01");
+MODULE_ALIAS("snd-hda-codec-id:10951390");
+MODULE_ALIAS("snd-hda-codec-id:10951392");
+MODULE_ALIAS("snd-hda-codec-id:10de0002");
+MODULE_ALIAS("snd-hda-codec-id:10de0003");
+MODULE_ALIAS("snd-hda-codec-id:10de0005");
+MODULE_ALIAS("snd-hda-codec-id:10de0006");
+MODULE_ALIAS("snd-hda-codec-id:10de0007");
+MODULE_ALIAS("snd-hda-codec-id:10de000a");
+MODULE_ALIAS("snd-hda-codec-id:10de000b");
+MODULE_ALIAS("snd-hda-codec-id:10de000c");
+MODULE_ALIAS("snd-hda-codec-id:10de000d");
+MODULE_ALIAS("snd-hda-codec-id:10de0010");
+MODULE_ALIAS("snd-hda-codec-id:10de0011");
+MODULE_ALIAS("snd-hda-codec-id:10de0012");
+MODULE_ALIAS("snd-hda-codec-id:10de0013");
+MODULE_ALIAS("snd-hda-codec-id:10de0014");
+MODULE_ALIAS("snd-hda-codec-id:10de0018");
+MODULE_ALIAS("snd-hda-codec-id:10de0019");
+MODULE_ALIAS("snd-hda-codec-id:10de001a");
+MODULE_ALIAS("snd-hda-codec-id:10de001b");
+MODULE_ALIAS("snd-hda-codec-id:10de001c");
+MODULE_ALIAS("snd-hda-codec-id:10de0040");
+MODULE_ALIAS("snd-hda-codec-id:10de0041");
+MODULE_ALIAS("snd-hda-codec-id:10de0042");
+MODULE_ALIAS("snd-hda-codec-id:10de0043");
+MODULE_ALIAS("snd-hda-codec-id:10de0044");
+MODULE_ALIAS("snd-hda-codec-id:10de0067");
+MODULE_ALIAS("snd-hda-codec-id:10de8001");
+MODULE_ALIAS("snd-hda-codec-id:17e80047");
+MODULE_ALIAS("snd-hda-codec-id:80860054");
+MODULE_ALIAS("snd-hda-codec-id:80862801");
+MODULE_ALIAS("snd-hda-codec-id:80862802");
+MODULE_ALIAS("snd-hda-codec-id:80862803");
+MODULE_ALIAS("snd-hda-codec-id:80862804");
+MODULE_ALIAS("snd-hda-codec-id:80862805");
+MODULE_ALIAS("snd-hda-codec-id:808629fb");
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HDMI HD-audio codec");
+MODULE_ALIAS("snd-hda-codec-intelhdmi");
+MODULE_ALIAS("snd-hda-codec-nvhdmi");
+MODULE_ALIAS("snd-hda-codec-atihdmi");
+
+static struct hda_codec_preset_list intel_list = {
+	.preset = snd_hda_preset_hdmi,
+	.owner = THIS_MODULE,
+};
+
+static int __init patch_hdmi_init(void)
+{
+	return snd_hda_add_codec_preset(&intel_list);
+}
+
+static void __exit patch_hdmi_exit(void)
+{
+	snd_hda_delete_codec_preset(&intel_list);
+}
+
+module_init(patch_hdmi_init)
+module_exit(patch_hdmi_exit)
diff --git a/linux/sound/pci/hda/patch_realtek.c b/linux/sound/pci/hda/patch_realtek.c
new file mode 100644
index 0000000..552a09e
--- /dev/null
+++ b/linux/sound/pci/hda/patch_realtek.c
@@ -0,0 +1,20028 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for ALC 260/880/882 codecs
+ *
+ * Copyright (c) 2004 Kailang Yang <kailang@realtek.com.tw>
+ *                    PeiSen Hou <pshou@realtek.com.tw>
+ *                    Takashi Iwai <tiwai@suse.de>
+ *                    Jonathan Woithe <jwoithe@physics.adelaide.edu.au>
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+#include "hda_beep.h"
+
+#define ALC880_FRONT_EVENT		0x01
+#define ALC880_DCVOL_EVENT		0x02
+#define ALC880_HP_EVENT			0x04
+#define ALC880_MIC_EVENT		0x08
+
+/* ALC880 board config type */
+enum {
+	ALC880_3ST,
+	ALC880_3ST_DIG,
+	ALC880_5ST,
+	ALC880_5ST_DIG,
+	ALC880_W810,
+	ALC880_Z71V,
+	ALC880_6ST,
+	ALC880_6ST_DIG,
+	ALC880_F1734,
+	ALC880_ASUS,
+	ALC880_ASUS_DIG,
+	ALC880_ASUS_W1V,
+	ALC880_ASUS_DIG2,
+	ALC880_FUJITSU,
+	ALC880_UNIWILL_DIG,
+	ALC880_UNIWILL,
+	ALC880_UNIWILL_P53,
+	ALC880_CLEVO,
+	ALC880_TCL_S700,
+	ALC880_LG,
+	ALC880_LG_LW,
+	ALC880_MEDION_RIM,
+#ifdef CONFIG_SND_DEBUG
+	ALC880_TEST,
+#endif
+	ALC880_AUTO,
+	ALC880_MODEL_LAST /* last tag */
+};
+
+/* ALC260 models */
+enum {
+	ALC260_BASIC,
+	ALC260_HP,
+	ALC260_HP_DC7600,
+	ALC260_HP_3013,
+	ALC260_FUJITSU_S702X,
+	ALC260_ACER,
+	ALC260_WILL,
+	ALC260_REPLACER_672V,
+	ALC260_FAVORIT100,
+#ifdef CONFIG_SND_DEBUG
+	ALC260_TEST,
+#endif
+	ALC260_AUTO,
+	ALC260_MODEL_LAST /* last tag */
+};
+
+/* ALC262 models */
+enum {
+	ALC262_BASIC,
+	ALC262_HIPPO,
+	ALC262_HIPPO_1,
+	ALC262_FUJITSU,
+	ALC262_HP_BPC,
+	ALC262_HP_BPC_D7000_WL,
+	ALC262_HP_BPC_D7000_WF,
+	ALC262_HP_TC_T5735,
+	ALC262_HP_RP5700,
+	ALC262_BENQ_ED8,
+	ALC262_SONY_ASSAMD,
+	ALC262_BENQ_T31,
+	ALC262_ULTRA,
+	ALC262_LENOVO_3000,
+	ALC262_NEC,
+	ALC262_TOSHIBA_S06,
+	ALC262_TOSHIBA_RX1,
+	ALC262_TYAN,
+	ALC262_AUTO,
+	ALC262_MODEL_LAST /* last tag */
+};
+
+/* ALC268 models */
+enum {
+	ALC267_QUANTA_IL1,
+	ALC268_3ST,
+	ALC268_TOSHIBA,
+	ALC268_ACER,
+	ALC268_ACER_DMIC,
+	ALC268_ACER_ASPIRE_ONE,
+	ALC268_DELL,
+	ALC268_ZEPTO,
+#ifdef CONFIG_SND_DEBUG
+	ALC268_TEST,
+#endif
+	ALC268_AUTO,
+	ALC268_MODEL_LAST /* last tag */
+};
+
+/* ALC269 models */
+enum {
+	ALC269_BASIC,
+	ALC269_QUANTA_FL1,
+	ALC269_AMIC,
+	ALC269_DMIC,
+	ALC269VB_AMIC,
+	ALC269VB_DMIC,
+	ALC269_FUJITSU,
+	ALC269_LIFEBOOK,
+	ALC271_ACER,
+	ALC269_AUTO,
+	ALC269_MODEL_LAST /* last tag */
+};
+
+/* ALC861 models */
+enum {
+	ALC861_3ST,
+	ALC660_3ST,
+	ALC861_3ST_DIG,
+	ALC861_6ST_DIG,
+	ALC861_UNIWILL_M31,
+	ALC861_TOSHIBA,
+	ALC861_ASUS,
+	ALC861_ASUS_LAPTOP,
+	ALC861_AUTO,
+	ALC861_MODEL_LAST,
+};
+
+/* ALC861-VD models */
+enum {
+	ALC660VD_3ST,
+	ALC660VD_3ST_DIG,
+	ALC660VD_ASUS_V1S,
+	ALC861VD_3ST,
+	ALC861VD_3ST_DIG,
+	ALC861VD_6ST_DIG,
+	ALC861VD_LENOVO,
+	ALC861VD_DALLAS,
+	ALC861VD_HP,
+	ALC861VD_AUTO,
+	ALC861VD_MODEL_LAST,
+};
+
+/* ALC662 models */
+enum {
+	ALC662_3ST_2ch_DIG,
+	ALC662_3ST_6ch_DIG,
+	ALC662_3ST_6ch,
+	ALC662_5ST_DIG,
+	ALC662_LENOVO_101E,
+	ALC662_ASUS_EEEPC_P701,
+	ALC662_ASUS_EEEPC_EP20,
+	ALC663_ASUS_M51VA,
+	ALC663_ASUS_G71V,
+	ALC663_ASUS_H13,
+	ALC663_ASUS_G50V,
+	ALC662_ECS,
+	ALC663_ASUS_MODE1,
+	ALC662_ASUS_MODE2,
+	ALC663_ASUS_MODE3,
+	ALC663_ASUS_MODE4,
+	ALC663_ASUS_MODE5,
+	ALC663_ASUS_MODE6,
+	ALC663_ASUS_MODE7,
+	ALC663_ASUS_MODE8,
+	ALC272_DELL,
+	ALC272_DELL_ZM1,
+	ALC272_SAMSUNG_NC10,
+	ALC662_AUTO,
+	ALC662_MODEL_LAST,
+};
+
+/* ALC882 models */
+enum {
+	ALC882_3ST_DIG,
+	ALC882_6ST_DIG,
+	ALC882_ARIMA,
+	ALC882_W2JC,
+	ALC882_TARGA,
+	ALC882_ASUS_A7J,
+	ALC882_ASUS_A7M,
+	ALC885_MACPRO,
+	ALC885_MBA21,
+	ALC885_MBP3,
+	ALC885_MB5,
+	ALC885_MACMINI3,
+	ALC885_IMAC24,
+	ALC885_IMAC91,
+	ALC883_3ST_2ch_DIG,
+	ALC883_3ST_6ch_DIG,
+	ALC883_3ST_6ch,
+	ALC883_6ST_DIG,
+	ALC883_TARGA_DIG,
+	ALC883_TARGA_2ch_DIG,
+	ALC883_TARGA_8ch_DIG,
+	ALC883_ACER,
+	ALC883_ACER_ASPIRE,
+	ALC888_ACER_ASPIRE_4930G,
+	ALC888_ACER_ASPIRE_6530G,
+	ALC888_ACER_ASPIRE_8930G,
+	ALC888_ACER_ASPIRE_7730G,
+	ALC883_MEDION,
+	ALC883_MEDION_MD2,
+	ALC883_MEDION_WIM2160,
+	ALC883_LAPTOP_EAPD,
+	ALC883_LENOVO_101E_2ch,
+	ALC883_LENOVO_NB0763,
+	ALC888_LENOVO_MS7195_DIG,
+	ALC888_LENOVO_SKY,
+	ALC883_HAIER_W66,
+	ALC888_3ST_HP,
+	ALC888_6ST_DELL,
+	ALC883_MITAC,
+	ALC883_CLEVO_M540R,
+	ALC883_CLEVO_M720,
+	ALC883_FUJITSU_PI2515,
+	ALC888_FUJITSU_XA3530,
+	ALC883_3ST_6ch_INTEL,
+	ALC889A_INTEL,
+	ALC889_INTEL,
+	ALC888_ASUS_M90V,
+	ALC888_ASUS_EEE1601,
+	ALC889A_MB31,
+	ALC1200_ASUS_P5Q,
+	ALC883_SONY_VAIO_TT,
+	ALC882_AUTO,
+	ALC882_MODEL_LAST,
+};
+
+/* ALC680 models */
+enum {
+	ALC680_BASE,
+	ALC680_AUTO,
+	ALC680_MODEL_LAST,
+};
+
+/* for GPIO Poll */
+#define GPIO_MASK	0x03
+
+/* extra amp-initialization sequence types */
+enum {
+	ALC_INIT_NONE,
+	ALC_INIT_DEFAULT,
+	ALC_INIT_GPIO1,
+	ALC_INIT_GPIO2,
+	ALC_INIT_GPIO3,
+};
+
+struct alc_mic_route {
+	hda_nid_t pin;
+	unsigned char mux_idx;
+	unsigned char amix_idx;
+};
+
+struct alc_jack {
+	hda_nid_t nid;
+	int type;
+	struct snd_jack *jack;
+};
+
+#define MUX_IDX_UNDEF	((unsigned char)-1)
+
+struct alc_customize_define {
+	unsigned int  sku_cfg;
+	unsigned char port_connectivity;
+	unsigned char check_sum;
+	unsigned char customization;
+	unsigned char external_amp;
+	unsigned int  enable_pcbeep:1;
+	unsigned int  platform_type:1;
+	unsigned int  swap:1;
+	unsigned int  override:1;
+	unsigned int  fixup:1; /* Means that this sku is set by driver, not read from hw */
+};
+
+struct alc_spec {
+	/* codec parameterization */
+	struct snd_kcontrol_new *mixers[5];	/* mixer arrays */
+	unsigned int num_mixers;
+	struct snd_kcontrol_new *cap_mixer;	/* capture mixer */
+	unsigned int beep_amp;	/* beep amp value, set via set_beep_amp() */
+
+	const struct hda_verb *init_verbs[10];	/* initialization verbs
+						 * don't forget NULL
+						 * termination!
+						 */
+	unsigned int num_init_verbs;
+
+	char stream_name_analog[32];	/* analog PCM stream */
+	struct hda_pcm_stream *stream_analog_playback;
+	struct hda_pcm_stream *stream_analog_capture;
+	struct hda_pcm_stream *stream_analog_alt_playback;
+	struct hda_pcm_stream *stream_analog_alt_capture;
+
+	char stream_name_digital[32];	/* digital PCM stream */
+	struct hda_pcm_stream *stream_digital_playback;
+	struct hda_pcm_stream *stream_digital_capture;
+
+	/* playback */
+	struct hda_multi_out multiout;	/* playback set-up
+					 * max_channels, dacs must be set
+					 * dig_out_nid and hp_nid are optional
+					 */
+	hda_nid_t alt_dac_nid;
+	hda_nid_t slave_dig_outs[3];	/* optional - for auto-parsing */
+	int dig_out_type;
+
+	/* capture */
+	unsigned int num_adc_nids;
+	hda_nid_t *adc_nids;
+	hda_nid_t *capsrc_nids;
+	hda_nid_t dig_in_nid;		/* digital-in NID; optional */
+
+	/* capture setup for dynamic dual-adc switch */
+	unsigned int cur_adc_idx;
+	hda_nid_t cur_adc;
+	unsigned int cur_adc_stream_tag;
+	unsigned int cur_adc_format;
+
+	/* capture source */
+	unsigned int num_mux_defs;
+	const struct hda_input_mux *input_mux;
+	unsigned int cur_mux[3];
+	struct alc_mic_route ext_mic;
+	struct alc_mic_route int_mic;
+
+	/* channel model */
+	const struct hda_channel_mode *channel_mode;
+	int num_channel_mode;
+	int need_dac_fix;
+	int const_channel_count;
+	int ext_channel_count;
+
+	/* PCM information */
+	struct hda_pcm pcm_rec[3];	/* used in alc_build_pcms() */
+
+	/* jack detection */
+	struct snd_array jacks;
+
+	/* dynamic controls, init_verbs and input_mux */
+	struct auto_pin_cfg autocfg;
+	struct alc_customize_define cdefine;
+	struct snd_array kctls;
+	struct hda_input_mux private_imux[3];
+	hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
+	hda_nid_t private_adc_nids[AUTO_CFG_MAX_OUTS];
+	hda_nid_t private_capsrc_nids[AUTO_CFG_MAX_OUTS];
+
+	/* hooks */
+	void (*init_hook)(struct hda_codec *codec);
+	void (*unsol_event)(struct hda_codec *codec, unsigned int res);
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	void (*power_hook)(struct hda_codec *codec);
+#endif
+
+	/* for pin sensing */
+	unsigned int sense_updated: 1;
+	unsigned int jack_present: 1;
+	unsigned int master_sw: 1;
+	unsigned int auto_mic:1;
+
+	/* other flags */
+	unsigned int no_analog :1; /* digital I/O only */
+	unsigned int dual_adc_switch:1; /* switch ADCs (for ALC275) */
+	int init_amp;
+	int codec_variant;	/* flag for other variants */
+
+	/* for virtual master */
+	hda_nid_t vmaster_nid;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	struct hda_loopback_check loopback;
+#endif
+
+	/* for PLL fix */
+	hda_nid_t pll_nid;
+	unsigned int pll_coef_idx, pll_coef_bit;
+};
+
+/*
+ * configuration template - to be copied to the spec instance
+ */
+struct alc_config_preset {
+	struct snd_kcontrol_new *mixers[5]; /* should be identical size
+					     * with spec
+					     */
+	struct snd_kcontrol_new *cap_mixer; /* capture mixer */
+	const struct hda_verb *init_verbs[5];
+	unsigned int num_dacs;
+	hda_nid_t *dac_nids;
+	hda_nid_t dig_out_nid;		/* optional */
+	hda_nid_t hp_nid;		/* optional */
+	hda_nid_t *slave_dig_outs;
+	unsigned int num_adc_nids;
+	hda_nid_t *adc_nids;
+	hda_nid_t *capsrc_nids;
+	hda_nid_t dig_in_nid;
+	unsigned int num_channel_mode;
+	const struct hda_channel_mode *channel_mode;
+	int need_dac_fix;
+	int const_channel_count;
+	unsigned int num_mux_defs;
+	const struct hda_input_mux *input_mux;
+	void (*unsol_event)(struct hda_codec *, unsigned int);
+	void (*setup)(struct hda_codec *);
+	void (*init_hook)(struct hda_codec *);
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	struct hda_amp_list *loopbacks;
+	void (*power_hook)(struct hda_codec *codec);
+#endif
+};
+
+
+/*
+ * input MUX handling
+ */
+static int alc_mux_enum_info(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	unsigned int mux_idx = snd_ctl_get_ioffidx(kcontrol, &uinfo->id);
+	if (mux_idx >= spec->num_mux_defs)
+		mux_idx = 0;
+	if (!spec->input_mux[mux_idx].num_items && mux_idx > 0)
+		mux_idx = 0;
+	return snd_hda_input_mux_info(&spec->input_mux[mux_idx], uinfo);
+}
+
+static int alc_mux_enum_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
+	return 0;
+}
+
+static int alc_mux_enum_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	const struct hda_input_mux *imux;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	unsigned int mux_idx;
+	hda_nid_t nid = spec->capsrc_nids ?
+		spec->capsrc_nids[adc_idx] : spec->adc_nids[adc_idx];
+	unsigned int type;
+
+	mux_idx = adc_idx >= spec->num_mux_defs ? 0 : adc_idx;
+	imux = &spec->input_mux[mux_idx];
+	if (!imux->num_items && mux_idx > 0)
+		imux = &spec->input_mux[0];
+
+	type = get_wcaps_type(get_wcaps(codec, nid));
+	if (type == AC_WID_AUD_MIX) {
+		/* Matrix-mixer style (e.g. ALC882) */
+		unsigned int *cur_val = &spec->cur_mux[adc_idx];
+		unsigned int i, idx;
+
+		idx = ucontrol->value.enumerated.item[0];
+		if (idx >= imux->num_items)
+			idx = imux->num_items - 1;
+		if (*cur_val == idx)
+			return 0;
+		for (i = 0; i < imux->num_items; i++) {
+			unsigned int v = (i == idx) ? 0 : HDA_AMP_MUTE;
+			snd_hda_codec_amp_stereo(codec, nid, HDA_INPUT,
+						 imux->items[i].index,
+						 HDA_AMP_MUTE, v);
+		}
+		*cur_val = idx;
+		return 1;
+	} else {
+		/* MUX style (e.g. ALC880) */
+		return snd_hda_input_mux_put(codec, imux, ucontrol, nid,
+					     &spec->cur_mux[adc_idx]);
+	}
+}
+
+/*
+ * channel mode setting
+ */
+static int alc_ch_mode_info(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	return snd_hda_ch_mode_info(codec, uinfo, spec->channel_mode,
+				    spec->num_channel_mode);
+}
+
+static int alc_ch_mode_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	return snd_hda_ch_mode_get(codec, ucontrol, spec->channel_mode,
+				   spec->num_channel_mode,
+				   spec->ext_channel_count);
+}
+
+static int alc_ch_mode_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	int err = snd_hda_ch_mode_put(codec, ucontrol, spec->channel_mode,
+				      spec->num_channel_mode,
+				      &spec->ext_channel_count);
+	if (err >= 0 && !spec->const_channel_count) {
+		spec->multiout.max_channels = spec->ext_channel_count;
+		if (spec->need_dac_fix)
+			spec->multiout.num_dacs = spec->multiout.max_channels / 2;
+	}
+	return err;
+}
+
+/*
+ * Control the mode of pin widget settings via the mixer.  "pc" is used
+ * instead of "%" to avoid consequences of accidently treating the % as
+ * being part of a format specifier.  Maximum allowed length of a value is
+ * 63 characters plus NULL terminator.
+ *
+ * Note: some retasking pin complexes seem to ignore requests for input
+ * states other than HiZ (eg: PIN_VREFxx) and revert to HiZ if any of these
+ * are requested.  Therefore order this list so that this behaviour will not
+ * cause problems when mixer clients move through the enum sequentially.
+ * NIDs 0x0f and 0x10 have been observed to have this behaviour as of
+ * March 2006.
+ */
+static char *alc_pin_mode_names[] = {
+	"Mic 50pc bias", "Mic 80pc bias",
+	"Line in", "Line out", "Headphone out",
+};
+static unsigned char alc_pin_mode_values[] = {
+	PIN_VREF50, PIN_VREF80, PIN_IN, PIN_OUT, PIN_HP,
+};
+/* The control can present all 5 options, or it can limit the options based
+ * in the pin being assumed to be exclusively an input or an output pin.  In
+ * addition, "input" pins may or may not process the mic bias option
+ * depending on actual widget capability (NIDs 0x0f and 0x10 don't seem to
+ * accept requests for bias as of chip versions up to March 2006) and/or
+ * wiring in the computer.
+ */
+#define ALC_PIN_DIR_IN              0x00
+#define ALC_PIN_DIR_OUT             0x01
+#define ALC_PIN_DIR_INOUT           0x02
+#define ALC_PIN_DIR_IN_NOMICBIAS    0x03
+#define ALC_PIN_DIR_INOUT_NOMICBIAS 0x04
+
+/* Info about the pin modes supported by the different pin direction modes.
+ * For each direction the minimum and maximum values are given.
+ */
+static signed char alc_pin_mode_dir_info[5][2] = {
+	{ 0, 2 },    /* ALC_PIN_DIR_IN */
+	{ 3, 4 },    /* ALC_PIN_DIR_OUT */
+	{ 0, 4 },    /* ALC_PIN_DIR_INOUT */
+	{ 2, 2 },    /* ALC_PIN_DIR_IN_NOMICBIAS */
+	{ 2, 4 },    /* ALC_PIN_DIR_INOUT_NOMICBIAS */
+};
+#define alc_pin_mode_min(_dir) (alc_pin_mode_dir_info[_dir][0])
+#define alc_pin_mode_max(_dir) (alc_pin_mode_dir_info[_dir][1])
+#define alc_pin_mode_n_items(_dir) \
+	(alc_pin_mode_max(_dir)-alc_pin_mode_min(_dir)+1)
+
+static int alc_pin_mode_info(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_info *uinfo)
+{
+	unsigned int item_num = uinfo->value.enumerated.item;
+	unsigned char dir = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = alc_pin_mode_n_items(dir);
+
+	if (item_num<alc_pin_mode_min(dir) || item_num>alc_pin_mode_max(dir))
+		item_num = alc_pin_mode_min(dir);
+	strcpy(uinfo->value.enumerated.name, alc_pin_mode_names[item_num]);
+	return 0;
+}
+
+static int alc_pin_mode_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned int i;
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value & 0xffff;
+	unsigned char dir = (kcontrol->private_value >> 16) & 0xff;
+	long *valp = ucontrol->value.integer.value;
+	unsigned int pinctl = snd_hda_codec_read(codec, nid, 0,
+						 AC_VERB_GET_PIN_WIDGET_CONTROL,
+						 0x00);
+
+	/* Find enumerated value for current pinctl setting */
+	i = alc_pin_mode_min(dir);
+	while (i <= alc_pin_mode_max(dir) && alc_pin_mode_values[i] != pinctl)
+		i++;
+	*valp = i <= alc_pin_mode_max(dir) ? i: alc_pin_mode_min(dir);
+	return 0;
+}
+
+static int alc_pin_mode_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	signed int change;
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value & 0xffff;
+	unsigned char dir = (kcontrol->private_value >> 16) & 0xff;
+	long val = *ucontrol->value.integer.value;
+	unsigned int pinctl = snd_hda_codec_read(codec, nid, 0,
+						 AC_VERB_GET_PIN_WIDGET_CONTROL,
+						 0x00);
+
+	if (val < alc_pin_mode_min(dir) || val > alc_pin_mode_max(dir))
+		val = alc_pin_mode_min(dir);
+
+	change = pinctl != alc_pin_mode_values[val];
+	if (change) {
+		/* Set pin mode to that requested */
+		snd_hda_codec_write_cache(codec, nid, 0,
+					  AC_VERB_SET_PIN_WIDGET_CONTROL,
+					  alc_pin_mode_values[val]);
+
+		/* Also enable the retasking pin's input/output as required
+		 * for the requested pin mode.  Enum values of 2 or less are
+		 * input modes.
+		 *
+		 * Dynamically switching the input/output buffers probably
+		 * reduces noise slightly (particularly on input) so we'll
+		 * do it.  However, having both input and output buffers
+		 * enabled simultaneously doesn't seem to be problematic if
+		 * this turns out to be necessary in the future.
+		 */
+		if (val <= 2) {
+			snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0,
+						 HDA_AMP_MUTE, HDA_AMP_MUTE);
+			snd_hda_codec_amp_stereo(codec, nid, HDA_INPUT, 0,
+						 HDA_AMP_MUTE, 0);
+		} else {
+			snd_hda_codec_amp_stereo(codec, nid, HDA_INPUT, 0,
+						 HDA_AMP_MUTE, HDA_AMP_MUTE);
+			snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0,
+						 HDA_AMP_MUTE, 0);
+		}
+	}
+	return change;
+}
+
+#define ALC_PIN_MODE(xname, nid, dir) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0,  \
+	  .subdevice = HDA_SUBDEV_NID_FLAG | nid, \
+	  .info = alc_pin_mode_info, \
+	  .get = alc_pin_mode_get, \
+	  .put = alc_pin_mode_put, \
+	  .private_value = nid | (dir<<16) }
+
+/* A switch control for ALC260 GPIO pins.  Multiple GPIOs can be ganged
+ * together using a mask with more than one bit set.  This control is
+ * currently used only by the ALC260 test model.  At this stage they are not
+ * needed for any "production" models.
+ */
+#ifdef CONFIG_SND_DEBUG
+#define alc_gpio_data_info	snd_ctl_boolean_mono_info
+
+static int alc_gpio_data_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value & 0xffff;
+	unsigned char mask = (kcontrol->private_value >> 16) & 0xff;
+	long *valp = ucontrol->value.integer.value;
+	unsigned int val = snd_hda_codec_read(codec, nid, 0,
+					      AC_VERB_GET_GPIO_DATA, 0x00);
+
+	*valp = (val & mask) != 0;
+	return 0;
+}
+static int alc_gpio_data_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	signed int change;
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value & 0xffff;
+	unsigned char mask = (kcontrol->private_value >> 16) & 0xff;
+	long val = *ucontrol->value.integer.value;
+	unsigned int gpio_data = snd_hda_codec_read(codec, nid, 0,
+						    AC_VERB_GET_GPIO_DATA,
+						    0x00);
+
+	/* Set/unset the masked GPIO bit(s) as needed */
+	change = (val == 0 ? 0 : mask) != (gpio_data & mask);
+	if (val == 0)
+		gpio_data &= ~mask;
+	else
+		gpio_data |= mask;
+	snd_hda_codec_write_cache(codec, nid, 0,
+				  AC_VERB_SET_GPIO_DATA, gpio_data);
+
+	return change;
+}
+#define ALC_GPIO_DATA_SWITCH(xname, nid, mask) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0,  \
+	  .subdevice = HDA_SUBDEV_NID_FLAG | nid, \
+	  .info = alc_gpio_data_info, \
+	  .get = alc_gpio_data_get, \
+	  .put = alc_gpio_data_put, \
+	  .private_value = nid | (mask<<16) }
+#endif   /* CONFIG_SND_DEBUG */
+
+/* A switch control to allow the enabling of the digital IO pins on the
+ * ALC260.  This is incredibly simplistic; the intention of this control is
+ * to provide something in the test model allowing digital outputs to be
+ * identified if present.  If models are found which can utilise these
+ * outputs a more complete mixer control can be devised for those models if
+ * necessary.
+ */
+#ifdef CONFIG_SND_DEBUG
+#define alc_spdif_ctrl_info	snd_ctl_boolean_mono_info
+
+static int alc_spdif_ctrl_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value & 0xffff;
+	unsigned char mask = (kcontrol->private_value >> 16) & 0xff;
+	long *valp = ucontrol->value.integer.value;
+	unsigned int val = snd_hda_codec_read(codec, nid, 0,
+					      AC_VERB_GET_DIGI_CONVERT_1, 0x00);
+
+	*valp = (val & mask) != 0;
+	return 0;
+}
+static int alc_spdif_ctrl_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	signed int change;
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value & 0xffff;
+	unsigned char mask = (kcontrol->private_value >> 16) & 0xff;
+	long val = *ucontrol->value.integer.value;
+	unsigned int ctrl_data = snd_hda_codec_read(codec, nid, 0,
+						    AC_VERB_GET_DIGI_CONVERT_1,
+						    0x00);
+
+	/* Set/unset the masked control bit(s) as needed */
+	change = (val == 0 ? 0 : mask) != (ctrl_data & mask);
+	if (val==0)
+		ctrl_data &= ~mask;
+	else
+		ctrl_data |= mask;
+	snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1,
+				  ctrl_data);
+
+	return change;
+}
+#define ALC_SPDIF_CTRL_SWITCH(xname, nid, mask) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0,  \
+	  .subdevice = HDA_SUBDEV_NID_FLAG | nid, \
+	  .info = alc_spdif_ctrl_info, \
+	  .get = alc_spdif_ctrl_get, \
+	  .put = alc_spdif_ctrl_put, \
+	  .private_value = nid | (mask<<16) }
+#endif   /* CONFIG_SND_DEBUG */
+
+/* A switch control to allow the enabling EAPD digital outputs on the ALC26x.
+ * Again, this is only used in the ALC26x test models to help identify when
+ * the EAPD line must be asserted for features to work.
+ */
+#ifdef CONFIG_SND_DEBUG
+#define alc_eapd_ctrl_info	snd_ctl_boolean_mono_info
+
+static int alc_eapd_ctrl_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value & 0xffff;
+	unsigned char mask = (kcontrol->private_value >> 16) & 0xff;
+	long *valp = ucontrol->value.integer.value;
+	unsigned int val = snd_hda_codec_read(codec, nid, 0,
+					      AC_VERB_GET_EAPD_BTLENABLE, 0x00);
+
+	*valp = (val & mask) != 0;
+	return 0;
+}
+
+static int alc_eapd_ctrl_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	int change;
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value & 0xffff;
+	unsigned char mask = (kcontrol->private_value >> 16) & 0xff;
+	long val = *ucontrol->value.integer.value;
+	unsigned int ctrl_data = snd_hda_codec_read(codec, nid, 0,
+						    AC_VERB_GET_EAPD_BTLENABLE,
+						    0x00);
+
+	/* Set/unset the masked control bit(s) as needed */
+	change = (!val ? 0 : mask) != (ctrl_data & mask);
+	if (!val)
+		ctrl_data &= ~mask;
+	else
+		ctrl_data |= mask;
+	snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_EAPD_BTLENABLE,
+				  ctrl_data);
+
+	return change;
+}
+
+#define ALC_EAPD_CTRL_SWITCH(xname, nid, mask) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0,  \
+	  .subdevice = HDA_SUBDEV_NID_FLAG | nid, \
+	  .info = alc_eapd_ctrl_info, \
+	  .get = alc_eapd_ctrl_get, \
+	  .put = alc_eapd_ctrl_put, \
+	  .private_value = nid | (mask<<16) }
+#endif   /* CONFIG_SND_DEBUG */
+
+/*
+ * set up the input pin config (depending on the given auto-pin type)
+ */
+static void alc_set_input_pin(struct hda_codec *codec, hda_nid_t nid,
+			      int auto_pin_type)
+{
+	unsigned int val = PIN_IN;
+
+	if (auto_pin_type == AUTO_PIN_MIC) {
+		unsigned int pincap;
+		unsigned int oldval;
+		oldval = snd_hda_codec_read(codec, nid, 0,
+					    AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+		pincap = snd_hda_query_pin_caps(codec, nid);
+		pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
+		/* if the default pin setup is vref50, we give it priority */
+		if ((pincap & AC_PINCAP_VREF_80) && oldval != PIN_VREF50)
+			val = PIN_VREF80;
+		else if (pincap & AC_PINCAP_VREF_50)
+			val = PIN_VREF50;
+		else if (pincap & AC_PINCAP_VREF_100)
+			val = PIN_VREF100;
+		else if (pincap & AC_PINCAP_VREF_GRD)
+			val = PIN_VREFGRD;
+	}
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, val);
+}
+
+static void alc_fixup_autocfg_pin_nums(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+
+	if (!cfg->line_outs) {
+		while (cfg->line_outs < AUTO_CFG_MAX_OUTS &&
+		       cfg->line_out_pins[cfg->line_outs])
+			cfg->line_outs++;
+	}
+	if (!cfg->speaker_outs) {
+		while (cfg->speaker_outs < AUTO_CFG_MAX_OUTS &&
+		       cfg->speaker_pins[cfg->speaker_outs])
+			cfg->speaker_outs++;
+	}
+	if (!cfg->hp_outs) {
+		while (cfg->hp_outs < AUTO_CFG_MAX_OUTS &&
+		       cfg->hp_pins[cfg->hp_outs])
+			cfg->hp_outs++;
+	}
+}
+
+/*
+ */
+static void add_mixer(struct alc_spec *spec, struct snd_kcontrol_new *mix)
+{
+	if (snd_BUG_ON(spec->num_mixers >= ARRAY_SIZE(spec->mixers)))
+		return;
+	spec->mixers[spec->num_mixers++] = mix;
+}
+
+static void add_verb(struct alc_spec *spec, const struct hda_verb *verb)
+{
+	if (snd_BUG_ON(spec->num_init_verbs >= ARRAY_SIZE(spec->init_verbs)))
+		return;
+	spec->init_verbs[spec->num_init_verbs++] = verb;
+}
+
+/*
+ * set up from the preset table
+ */
+static void setup_preset(struct hda_codec *codec,
+			 const struct alc_config_preset *preset)
+{
+	struct alc_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(preset->mixers) && preset->mixers[i]; i++)
+		add_mixer(spec, preset->mixers[i]);
+	spec->cap_mixer = preset->cap_mixer;
+	for (i = 0; i < ARRAY_SIZE(preset->init_verbs) && preset->init_verbs[i];
+	     i++)
+		add_verb(spec, preset->init_verbs[i]);
+
+	spec->channel_mode = preset->channel_mode;
+	spec->num_channel_mode = preset->num_channel_mode;
+	spec->need_dac_fix = preset->need_dac_fix;
+	spec->const_channel_count = preset->const_channel_count;
+
+	if (preset->const_channel_count)
+		spec->multiout.max_channels = preset->const_channel_count;
+	else
+		spec->multiout.max_channels = spec->channel_mode[0].channels;
+	spec->ext_channel_count = spec->channel_mode[0].channels;
+
+	spec->multiout.num_dacs = preset->num_dacs;
+	spec->multiout.dac_nids = preset->dac_nids;
+	spec->multiout.dig_out_nid = preset->dig_out_nid;
+	spec->multiout.slave_dig_outs = preset->slave_dig_outs;
+	spec->multiout.hp_nid = preset->hp_nid;
+
+	spec->num_mux_defs = preset->num_mux_defs;
+	if (!spec->num_mux_defs)
+		spec->num_mux_defs = 1;
+	spec->input_mux = preset->input_mux;
+
+	spec->num_adc_nids = preset->num_adc_nids;
+	spec->adc_nids = preset->adc_nids;
+	spec->capsrc_nids = preset->capsrc_nids;
+	spec->dig_in_nid = preset->dig_in_nid;
+
+	spec->unsol_event = preset->unsol_event;
+	spec->init_hook = preset->init_hook;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->power_hook = preset->power_hook;
+	spec->loopback.amplist = preset->loopbacks;
+#endif
+
+	if (preset->setup)
+		preset->setup(codec);
+
+	alc_fixup_autocfg_pin_nums(codec);
+}
+
+/* Enable GPIO mask and set output */
+static struct hda_verb alc_gpio1_init_verbs[] = {
+	{0x01, AC_VERB_SET_GPIO_MASK, 0x01},
+	{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x01},
+	{0x01, AC_VERB_SET_GPIO_DATA, 0x01},
+	{ }
+};
+
+static struct hda_verb alc_gpio2_init_verbs[] = {
+	{0x01, AC_VERB_SET_GPIO_MASK, 0x02},
+	{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x02},
+	{0x01, AC_VERB_SET_GPIO_DATA, 0x02},
+	{ }
+};
+
+static struct hda_verb alc_gpio3_init_verbs[] = {
+	{0x01, AC_VERB_SET_GPIO_MASK, 0x03},
+	{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x03},
+	{0x01, AC_VERB_SET_GPIO_DATA, 0x03},
+	{ }
+};
+
+/*
+ * Fix hardware PLL issue
+ * On some codecs, the analog PLL gating control must be off while
+ * the default value is 1.
+ */
+static void alc_fix_pll(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	unsigned int val;
+
+	if (!spec->pll_nid)
+		return;
+	snd_hda_codec_write(codec, spec->pll_nid, 0, AC_VERB_SET_COEF_INDEX,
+			    spec->pll_coef_idx);
+	val = snd_hda_codec_read(codec, spec->pll_nid, 0,
+				 AC_VERB_GET_PROC_COEF, 0);
+	snd_hda_codec_write(codec, spec->pll_nid, 0, AC_VERB_SET_COEF_INDEX,
+			    spec->pll_coef_idx);
+	snd_hda_codec_write(codec, spec->pll_nid, 0, AC_VERB_SET_PROC_COEF,
+			    val & ~(1 << spec->pll_coef_bit));
+}
+
+static void alc_fix_pll_init(struct hda_codec *codec, hda_nid_t nid,
+			     unsigned int coef_idx, unsigned int coef_bit)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->pll_nid = nid;
+	spec->pll_coef_idx = coef_idx;
+	spec->pll_coef_bit = coef_bit;
+	alc_fix_pll(codec);
+}
+
+#ifdef CONFIG_SND_HDA_INPUT_JACK
+static void alc_free_jack_priv(struct snd_jack *jack)
+{
+	struct alc_jack *jacks = jack->private_data;
+	jacks->nid = 0;
+	jacks->jack = NULL;
+}
+
+static int alc_add_jack(struct hda_codec *codec,
+		hda_nid_t nid, int type)
+{
+	struct alc_spec *spec;
+	struct alc_jack *jack;
+	const char *name;
+	int err;
+
+	spec = codec->spec;
+	snd_array_init(&spec->jacks, sizeof(*jack), 32);
+	jack = snd_array_new(&spec->jacks);
+	if (!jack)
+		return -ENOMEM;
+
+	jack->nid = nid;
+	jack->type = type;
+	name = (type == SND_JACK_HEADPHONE) ? "Headphone" : "Mic" ;
+
+	err = snd_jack_new(codec->bus->card, name, type, &jack->jack);
+	if (err < 0)
+		return err;
+	jack->jack->private_data = jack;
+	jack->jack->private_free = alc_free_jack_priv;
+	return 0;
+}
+
+static void alc_report_jack(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct alc_spec *spec = codec->spec;
+	struct alc_jack *jacks = spec->jacks.list;
+
+	if (jacks) {
+		int i;
+		for (i = 0; i < spec->jacks.used; i++) {
+			if (jacks->nid == nid) {
+				unsigned int present;
+				present = snd_hda_jack_detect(codec, nid);
+
+				present = (present) ? jacks->type : 0;
+
+				snd_jack_report(jacks->jack, present);
+			}
+			jacks++;
+		}
+	}
+}
+
+static int alc_init_jacks(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int err;
+	unsigned int hp_nid = spec->autocfg.hp_pins[0];
+	unsigned int mic_nid = spec->ext_mic.pin;
+
+	if (hp_nid) {
+		err = alc_add_jack(codec, hp_nid, SND_JACK_HEADPHONE);
+		if (err < 0)
+			return err;
+		alc_report_jack(codec, hp_nid);
+	}
+
+	if (mic_nid) {
+		err = alc_add_jack(codec, mic_nid, SND_JACK_MICROPHONE);
+		if (err < 0)
+			return err;
+		alc_report_jack(codec, mic_nid);
+	}
+
+	return 0;
+}
+#else
+static inline void alc_report_jack(struct hda_codec *codec, hda_nid_t nid)
+{
+}
+
+static inline int alc_init_jacks(struct hda_codec *codec)
+{
+	return 0;
+}
+#endif
+
+static void alc_automute_speaker(struct hda_codec *codec, int pinctl)
+{
+	struct alc_spec *spec = codec->spec;
+	unsigned int mute;
+	hda_nid_t nid;
+	int i;
+
+	spec->jack_present = 0;
+	for (i = 0; i < ARRAY_SIZE(spec->autocfg.hp_pins); i++) {
+		nid = spec->autocfg.hp_pins[i];
+		if (!nid)
+			break;
+		if (snd_hda_jack_detect(codec, nid)) {
+			spec->jack_present = 1;
+			break;
+		}
+		alc_report_jack(codec, spec->autocfg.hp_pins[i]);
+	}
+
+	mute = spec->jack_present ? HDA_AMP_MUTE : 0;
+	/* Toggle internal speakers muting */
+	for (i = 0; i < ARRAY_SIZE(spec->autocfg.speaker_pins); i++) {
+		nid = spec->autocfg.speaker_pins[i];
+		if (!nid)
+			break;
+		if (pinctl) {
+			snd_hda_codec_write(codec, nid, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL,
+				    spec->jack_present ? 0 : PIN_OUT);
+		} else {
+			snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0,
+					 HDA_AMP_MUTE, mute);
+		}
+	}
+}
+
+static void alc_automute_pin(struct hda_codec *codec)
+{
+	alc_automute_speaker(codec, 1);
+}
+
+static int get_connection_index(struct hda_codec *codec, hda_nid_t mux,
+				hda_nid_t nid)
+{
+	hda_nid_t conn[HDA_MAX_NUM_INPUTS];
+	int i, nums;
+
+	nums = snd_hda_get_connections(codec, mux, conn, ARRAY_SIZE(conn));
+	for (i = 0; i < nums; i++)
+		if (conn[i] == nid)
+			return i;
+	return -1;
+}
+
+/* switch the current ADC according to the jack state */
+static void alc_dual_mic_adc_auto_switch(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	unsigned int present;
+	hda_nid_t new_adc;
+
+	present = snd_hda_jack_detect(codec, spec->ext_mic.pin);
+	if (present)
+		spec->cur_adc_idx = 1;
+	else
+		spec->cur_adc_idx = 0;
+	new_adc = spec->adc_nids[spec->cur_adc_idx];
+	if (spec->cur_adc && spec->cur_adc != new_adc) {
+		/* stream is running, let's swap the current ADC */
+		__snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1);
+		spec->cur_adc = new_adc;
+		snd_hda_codec_setup_stream(codec, new_adc,
+					   spec->cur_adc_stream_tag, 0,
+					   spec->cur_adc_format);
+	}
+}
+
+static void alc_mic_automute(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct alc_mic_route *dead, *alive;
+	unsigned int present, type;
+	hda_nid_t cap_nid;
+
+	if (!spec->auto_mic)
+		return;
+	if (!spec->int_mic.pin || !spec->ext_mic.pin)
+		return;
+	if (snd_BUG_ON(!spec->adc_nids))
+		return;
+
+	if (spec->dual_adc_switch) {
+		alc_dual_mic_adc_auto_switch(codec);
+		return;
+	}
+
+	cap_nid = spec->capsrc_nids ? spec->capsrc_nids[0] : spec->adc_nids[0];
+
+	present = snd_hda_jack_detect(codec, spec->ext_mic.pin);
+	if (present) {
+		alive = &spec->ext_mic;
+		dead = &spec->int_mic;
+	} else {
+		alive = &spec->int_mic;
+		dead = &spec->ext_mic;
+	}
+
+	type = get_wcaps_type(get_wcaps(codec, cap_nid));
+	if (type == AC_WID_AUD_MIX) {
+		/* Matrix-mixer style (e.g. ALC882) */
+		snd_hda_codec_amp_stereo(codec, cap_nid, HDA_INPUT,
+					 alive->mux_idx,
+					 HDA_AMP_MUTE, 0);
+		snd_hda_codec_amp_stereo(codec, cap_nid, HDA_INPUT,
+					 dead->mux_idx,
+					 HDA_AMP_MUTE, HDA_AMP_MUTE);
+	} else {
+		/* MUX style (e.g. ALC880) */
+		snd_hda_codec_write_cache(codec, cap_nid, 0,
+					  AC_VERB_SET_CONNECT_SEL,
+					  alive->mux_idx);
+	}
+	alc_report_jack(codec, spec->ext_mic.pin);
+
+	/* FIXME: analog mixer */
+}
+
+/* unsolicited event for HP jack sensing */
+static void alc_sku_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	if (codec->vendor_id == 0x10ec0880)
+		res >>= 28;
+	else
+		res >>= 26;
+	switch (res) {
+	case ALC880_HP_EVENT:
+		alc_automute_pin(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+static void alc_inithook(struct hda_codec *codec)
+{
+	alc_automute_pin(codec);
+	alc_mic_automute(codec);
+}
+
+/* additional initialization for ALC888 variants */
+static void alc888_coef_init(struct hda_codec *codec)
+{
+	unsigned int tmp;
+
+	snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0);
+	tmp = snd_hda_codec_read(codec, 0x20, 0, AC_VERB_GET_PROC_COEF, 0);
+	snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 7);
+	if ((tmp & 0xf0) == 0x20)
+		/* alc888S-VC */
+		snd_hda_codec_read(codec, 0x20, 0,
+				   AC_VERB_SET_PROC_COEF, 0x830);
+	 else
+		 /* alc888-VB */
+		 snd_hda_codec_read(codec, 0x20, 0,
+				    AC_VERB_SET_PROC_COEF, 0x3030);
+}
+
+static void alc889_coef_init(struct hda_codec *codec)
+{
+	unsigned int tmp;
+
+	snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 7);
+	tmp = snd_hda_codec_read(codec, 0x20, 0, AC_VERB_GET_PROC_COEF, 0);
+	snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 7);
+	snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, tmp|0x2010);
+}
+
+/* turn on/off EAPD control (only if available) */
+static void set_eapd(struct hda_codec *codec, hda_nid_t nid, int on)
+{
+	if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
+		return;
+	if (snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)
+		snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_EAPD_BTLENABLE,
+				    on ? 2 : 0);
+}
+
+static void alc_auto_init_amp(struct hda_codec *codec, int type)
+{
+	unsigned int tmp;
+
+	switch (type) {
+	case ALC_INIT_GPIO1:
+		snd_hda_sequence_write(codec, alc_gpio1_init_verbs);
+		break;
+	case ALC_INIT_GPIO2:
+		snd_hda_sequence_write(codec, alc_gpio2_init_verbs);
+		break;
+	case ALC_INIT_GPIO3:
+		snd_hda_sequence_write(codec, alc_gpio3_init_verbs);
+		break;
+	case ALC_INIT_DEFAULT:
+		switch (codec->vendor_id) {
+		case 0x10ec0260:
+			set_eapd(codec, 0x0f, 1);
+			set_eapd(codec, 0x10, 1);
+			break;
+		case 0x10ec0262:
+		case 0x10ec0267:
+		case 0x10ec0268:
+		case 0x10ec0269:
+		case 0x10ec0270:
+		case 0x10ec0272:
+		case 0x10ec0660:
+		case 0x10ec0662:
+		case 0x10ec0663:
+		case 0x10ec0862:
+		case 0x10ec0889:
+			set_eapd(codec, 0x14, 1);
+			set_eapd(codec, 0x15, 1);
+			break;
+		}
+		switch (codec->vendor_id) {
+		case 0x10ec0260:
+			snd_hda_codec_write(codec, 0x1a, 0,
+					    AC_VERB_SET_COEF_INDEX, 7);
+			tmp = snd_hda_codec_read(codec, 0x1a, 0,
+						 AC_VERB_GET_PROC_COEF, 0);
+			snd_hda_codec_write(codec, 0x1a, 0,
+					    AC_VERB_SET_COEF_INDEX, 7);
+			snd_hda_codec_write(codec, 0x1a, 0,
+					    AC_VERB_SET_PROC_COEF,
+					    tmp | 0x2010);
+			break;
+		case 0x10ec0262:
+		case 0x10ec0880:
+		case 0x10ec0882:
+		case 0x10ec0883:
+		case 0x10ec0885:
+		case 0x10ec0887:
+		case 0x10ec0889:
+			alc889_coef_init(codec);
+			break;
+		case 0x10ec0888:
+			alc888_coef_init(codec);
+			break;
+#if 0 /* XXX: This may cause the silent output on speaker on some machines */
+		case 0x10ec0267:
+		case 0x10ec0268:
+			snd_hda_codec_write(codec, 0x20, 0,
+					    AC_VERB_SET_COEF_INDEX, 7);
+			tmp = snd_hda_codec_read(codec, 0x20, 0,
+						 AC_VERB_GET_PROC_COEF, 0);
+			snd_hda_codec_write(codec, 0x20, 0,
+					    AC_VERB_SET_COEF_INDEX, 7);
+			snd_hda_codec_write(codec, 0x20, 0,
+					    AC_VERB_SET_PROC_COEF,
+					    tmp | 0x3000);
+			break;
+#endif /* XXX */
+		}
+		break;
+	}
+}
+
+static void alc_init_auto_hp(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	if (!cfg->hp_pins[0]) {
+		if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+			return;
+	}
+
+	if (!cfg->speaker_pins[0]) {
+		if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+			return;
+		memcpy(cfg->speaker_pins, cfg->line_out_pins,
+		       sizeof(cfg->speaker_pins));
+		cfg->speaker_outs = cfg->line_outs;
+	}
+
+	if (!cfg->hp_pins[0]) {
+		memcpy(cfg->hp_pins, cfg->line_out_pins,
+		       sizeof(cfg->hp_pins));
+		cfg->hp_outs = cfg->line_outs;
+	}
+
+	for (i = 0; i < cfg->hp_outs; i++) {
+		snd_printdd("realtek: Enable HP auto-muting on NID 0x%x\n",
+			    cfg->hp_pins[i]);
+		snd_hda_codec_write_cache(codec, cfg->hp_pins[i], 0,
+				  AC_VERB_SET_UNSOLICITED_ENABLE,
+				  AC_USRSP_EN | ALC880_HP_EVENT);
+	}
+	spec->unsol_event = alc_sku_unsol_event;
+}
+
+static void alc_init_auto_mic(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t fixed, ext;
+	int i;
+
+	/* there must be only two mic inputs exclusively */
+	for (i = 0; i < cfg->num_inputs; i++)
+		if (cfg->inputs[i].type >= AUTO_PIN_LINE_IN)
+			return;
+
+	fixed = ext = 0;
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		unsigned int defcfg;
+		defcfg = snd_hda_codec_get_pincfg(codec, nid);
+		switch (snd_hda_get_input_pin_attr(defcfg)) {
+		case INPUT_PIN_ATTR_INT:
+			if (fixed)
+				return; /* already occupied */
+			fixed = nid;
+			break;
+		case INPUT_PIN_ATTR_UNUSED:
+			return; /* invalid entry */
+		default:
+			if (ext)
+				return; /* already occupied */
+			ext = nid;
+			break;
+		}
+	}
+	if (!ext || !fixed)
+		return;
+	if (!(get_wcaps(codec, ext) & AC_WCAP_UNSOL_CAP))
+		return; /* no unsol support */
+	snd_printdd("realtek: Enable auto-mic switch on NID 0x%x/0x%x\n",
+		    ext, fixed);
+	spec->ext_mic.pin = ext;
+	spec->int_mic.pin = fixed;
+	spec->ext_mic.mux_idx = MUX_IDX_UNDEF; /* set later */
+	spec->int_mic.mux_idx = MUX_IDX_UNDEF; /* set later */
+	spec->auto_mic = 1;
+	snd_hda_codec_write_cache(codec, spec->ext_mic.pin, 0,
+				  AC_VERB_SET_UNSOLICITED_ENABLE,
+				  AC_USRSP_EN | ALC880_MIC_EVENT);
+	spec->unsol_event = alc_sku_unsol_event;
+}
+
+/* Could be any non-zero and even value. When used as fixup, tells
+ * the driver to ignore any present sku defines.
+ */
+#define ALC_FIXUP_SKU_IGNORE (2)
+
+static int alc_auto_parse_customize_define(struct hda_codec *codec)
+{
+	unsigned int ass, tmp, i;
+	unsigned nid = 0;
+	struct alc_spec *spec = codec->spec;
+
+	spec->cdefine.enable_pcbeep = 1; /* assume always enabled */
+
+	if (spec->cdefine.fixup) {
+		ass = spec->cdefine.sku_cfg;
+		if (ass == ALC_FIXUP_SKU_IGNORE)
+			return -1;
+		goto do_sku;
+	}
+
+	ass = codec->subsystem_id & 0xffff;
+	if (ass != codec->bus->pci->subsystem_device && (ass & 1))
+		goto do_sku;
+
+	nid = 0x1d;
+	if (codec->vendor_id == 0x10ec0260)
+		nid = 0x17;
+	ass = snd_hda_codec_get_pincfg(codec, nid);
+
+	if (!(ass & 1)) {
+		printk(KERN_INFO "hda_codec: %s: SKU not ready 0x%08x\n",
+		       codec->chip_name, ass);
+		return -1;
+	}
+
+	/* check sum */
+	tmp = 0;
+	for (i = 1; i < 16; i++) {
+		if ((ass >> i) & 1)
+			tmp++;
+	}
+	if (((ass >> 16) & 0xf) != tmp)
+		return -1;
+
+	spec->cdefine.port_connectivity = ass >> 30;
+	spec->cdefine.enable_pcbeep = (ass & 0x100000) >> 20;
+	spec->cdefine.check_sum = (ass >> 16) & 0xf;
+	spec->cdefine.customization = ass >> 8;
+do_sku:
+	spec->cdefine.sku_cfg = ass;
+	spec->cdefine.external_amp = (ass & 0x38) >> 3;
+	spec->cdefine.platform_type = (ass & 0x4) >> 2;
+	spec->cdefine.swap = (ass & 0x2) >> 1;
+	spec->cdefine.override = ass & 0x1;
+
+	snd_printd("SKU: Nid=0x%x sku_cfg=0x%08x\n",
+		   nid, spec->cdefine.sku_cfg);
+	snd_printd("SKU: port_connectivity=0x%x\n",
+		   spec->cdefine.port_connectivity);
+	snd_printd("SKU: enable_pcbeep=0x%x\n", spec->cdefine.enable_pcbeep);
+	snd_printd("SKU: check_sum=0x%08x\n", spec->cdefine.check_sum);
+	snd_printd("SKU: customization=0x%08x\n", spec->cdefine.customization);
+	snd_printd("SKU: external_amp=0x%x\n", spec->cdefine.external_amp);
+	snd_printd("SKU: platform_type=0x%x\n", spec->cdefine.platform_type);
+	snd_printd("SKU: swap=0x%x\n", spec->cdefine.swap);
+	snd_printd("SKU: override=0x%x\n", spec->cdefine.override);
+
+	return 0;
+}
+
+/* check subsystem ID and set up device-specific initialization;
+ * return 1 if initialized, 0 if invalid SSID
+ */
+/* 32-bit subsystem ID for BIOS loading in HD Audio codec.
+ *	31 ~ 16 :	Manufacture ID
+ *	15 ~ 8	:	SKU ID
+ *	7  ~ 0	:	Assembly ID
+ *	port-A --> pin 39/41, port-E --> pin 14/15, port-D --> pin 35/36
+ */
+static int alc_subsystem_id(struct hda_codec *codec,
+			    hda_nid_t porta, hda_nid_t porte,
+			    hda_nid_t portd, hda_nid_t porti)
+{
+	unsigned int ass, tmp, i;
+	unsigned nid;
+	struct alc_spec *spec = codec->spec;
+
+	if (spec->cdefine.fixup) {
+		ass = spec->cdefine.sku_cfg;
+		if (ass == ALC_FIXUP_SKU_IGNORE)
+			return 0;
+		goto do_sku;
+	}
+
+	ass = codec->subsystem_id & 0xffff;
+	if ((ass != codec->bus->pci->subsystem_device) && (ass & 1))
+		goto do_sku;
+
+	/* invalid SSID, check the special NID pin defcfg instead */
+	/*
+	 * 31~30	: port connectivity
+	 * 29~21	: reserve
+	 * 20		: PCBEEP input
+	 * 19~16	: Check sum (15:1)
+	 * 15~1		: Custom
+	 * 0		: override
+	*/
+	nid = 0x1d;
+	if (codec->vendor_id == 0x10ec0260)
+		nid = 0x17;
+	ass = snd_hda_codec_get_pincfg(codec, nid);
+	snd_printd("realtek: No valid SSID, "
+		   "checking pincfg 0x%08x for NID 0x%x\n",
+		   ass, nid);
+	if (!(ass & 1))
+		return 0;
+	if ((ass >> 30) != 1)	/* no physical connection */
+		return 0;
+
+	/* check sum */
+	tmp = 0;
+	for (i = 1; i < 16; i++) {
+		if ((ass >> i) & 1)
+			tmp++;
+	}
+	if (((ass >> 16) & 0xf) != tmp)
+		return 0;
+do_sku:
+	snd_printd("realtek: Enabling init ASM_ID=0x%04x CODEC_ID=%08x\n",
+		   ass & 0xffff, codec->vendor_id);
+	/*
+	 * 0 : override
+	 * 1 :	Swap Jack
+	 * 2 : 0 --> Desktop, 1 --> Laptop
+	 * 3~5 : External Amplifier control
+	 * 7~6 : Reserved
+	*/
+	tmp = (ass & 0x38) >> 3;	/* external Amp control */
+	switch (tmp) {
+	case 1:
+		spec->init_amp = ALC_INIT_GPIO1;
+		break;
+	case 3:
+		spec->init_amp = ALC_INIT_GPIO2;
+		break;
+	case 7:
+		spec->init_amp = ALC_INIT_GPIO3;
+		break;
+	case 5:
+	default:
+		spec->init_amp = ALC_INIT_DEFAULT;
+		break;
+	}
+
+	/* is laptop or Desktop and enable the function "Mute internal speaker
+	 * when the external headphone out jack is plugged"
+	 */
+	if (!(ass & 0x8000))
+		return 1;
+	/*
+	 * 10~8 : Jack location
+	 * 12~11: Headphone out -> 00: PortA, 01: PortE, 02: PortD, 03: Resvered
+	 * 14~13: Resvered
+	 * 15   : 1 --> enable the function "Mute internal speaker
+	 *	        when the external headphone out jack is plugged"
+	 */
+	if (!spec->autocfg.hp_pins[0]) {
+		hda_nid_t nid;
+		tmp = (ass >> 11) & 0x3;	/* HP to chassis */
+		if (tmp == 0)
+			nid = porta;
+		else if (tmp == 1)
+			nid = porte;
+		else if (tmp == 2)
+			nid = portd;
+		else if (tmp == 3)
+			nid = porti;
+		else
+			return 1;
+		for (i = 0; i < spec->autocfg.line_outs; i++)
+			if (spec->autocfg.line_out_pins[i] == nid)
+				return 1;
+		spec->autocfg.hp_pins[0] = nid;
+	}
+
+	alc_init_auto_hp(codec);
+	alc_init_auto_mic(codec);
+	return 1;
+}
+
+static void alc_ssid_check(struct hda_codec *codec,
+			   hda_nid_t porta, hda_nid_t porte,
+			   hda_nid_t portd, hda_nid_t porti)
+{
+	if (!alc_subsystem_id(codec, porta, porte, portd, porti)) {
+		struct alc_spec *spec = codec->spec;
+		snd_printd("realtek: "
+			   "Enable default setup for auto mode as fallback\n");
+		spec->init_amp = ALC_INIT_DEFAULT;
+		alc_init_auto_hp(codec);
+		alc_init_auto_mic(codec);
+	}
+}
+
+/*
+ * Fix-up pin default configurations and add default verbs
+ */
+
+struct alc_pincfg {
+	hda_nid_t nid;
+	u32 val;
+};
+
+struct alc_fixup {
+	unsigned int sku;
+	const struct alc_pincfg *pins;
+	const struct hda_verb *verbs;
+};
+
+static void alc_pick_fixup(struct hda_codec *codec,
+			   const struct snd_pci_quirk *quirk,
+			   const struct alc_fixup *fix,
+			   int pre_init)
+{
+	const struct alc_pincfg *cfg;
+	struct alc_spec *spec;
+
+	quirk = snd_pci_quirk_lookup(codec->bus->pci, quirk);
+	if (!quirk)
+		return;
+	fix += quirk->value;
+	cfg = fix->pins;
+	if (pre_init && fix->sku) {
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+		snd_printdd(KERN_INFO "hda_codec: %s: Apply sku override for %s\n",
+			    codec->chip_name, quirk->name);
+#endif
+		spec = codec->spec;
+		spec->cdefine.sku_cfg = fix->sku;
+		spec->cdefine.fixup = 1;
+	}
+	if (pre_init && cfg) {
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+		snd_printdd(KERN_INFO "hda_codec: %s: Apply pincfg for %s\n",
+			    codec->chip_name, quirk->name);
+#endif
+		for (; cfg->nid; cfg++)
+			snd_hda_codec_set_pincfg(codec, cfg->nid, cfg->val);
+	}
+	if (!pre_init && fix->verbs) {
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+		snd_printdd(KERN_INFO "hda_codec: %s: Apply fix-verbs for %s\n",
+			    codec->chip_name, quirk->name);
+#endif
+		add_verb(codec->spec, fix->verbs);
+	}
+}
+
+static int alc_read_coef_idx(struct hda_codec *codec,
+			unsigned int coef_idx)
+{
+	unsigned int val;
+	snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX,
+		    		coef_idx);
+	val = snd_hda_codec_read(codec, 0x20, 0,
+			 	AC_VERB_GET_PROC_COEF, 0);
+	return val;
+}
+
+static void alc_write_coef_idx(struct hda_codec *codec, unsigned int coef_idx,
+							unsigned int coef_val)
+{
+	snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX,
+			    coef_idx);
+	snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF,
+			    coef_val);
+}
+
+/* set right pin controls for digital I/O */
+static void alc_auto_init_digital(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int i;
+	hda_nid_t pin;
+
+	for (i = 0; i < spec->autocfg.dig_outs; i++) {
+		pin = spec->autocfg.dig_out_pins[i];
+		if (pin) {
+			snd_hda_codec_write(codec, pin, 0,
+					    AC_VERB_SET_PIN_WIDGET_CONTROL,
+					    PIN_OUT);
+		}
+	}
+	pin = spec->autocfg.dig_in_pin;
+	if (pin)
+		snd_hda_codec_write(codec, pin, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL,
+				    PIN_IN);
+}
+
+/* parse digital I/Os and set up NIDs in BIOS auto-parse mode */
+static void alc_auto_parse_digital(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int i, err;
+	hda_nid_t dig_nid;
+
+	/* support multiple SPDIFs; the secondary is set up as a slave */
+	for (i = 0; i < spec->autocfg.dig_outs; i++) {
+		err = snd_hda_get_connections(codec,
+					      spec->autocfg.dig_out_pins[i],
+					      &dig_nid, 1);
+		if (err < 0)
+			continue;
+		if (!i) {
+			spec->multiout.dig_out_nid = dig_nid;
+			spec->dig_out_type = spec->autocfg.dig_out_type[0];
+		} else {
+			spec->multiout.slave_dig_outs = spec->slave_dig_outs;
+			if (i >= ARRAY_SIZE(spec->slave_dig_outs) - 1)
+				break;
+			spec->slave_dig_outs[i - 1] = dig_nid;
+		}
+	}
+
+	if (spec->autocfg.dig_in_pin) {
+		dig_nid = codec->start_nid;
+		for (i = 0; i < codec->num_nodes; i++, dig_nid++) {
+			unsigned int wcaps = get_wcaps(codec, dig_nid);
+			if (get_wcaps_type(wcaps) != AC_WID_AUD_IN)
+				continue;
+			if (!(wcaps & AC_WCAP_DIGITAL))
+				continue;
+			if (!(wcaps & AC_WCAP_CONN_LIST))
+				continue;
+			err = get_connection_index(codec, dig_nid,
+						   spec->autocfg.dig_in_pin);
+			if (err >= 0) {
+				spec->dig_in_nid = dig_nid;
+				break;
+			}
+		}
+	}
+}
+
+/*
+ * ALC888
+ */
+
+/*
+ * 2ch mode
+ */
+static struct hda_verb alc888_4ST_ch2_intel_init[] = {
+/* Mic-in jack as mic in */
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+/* Line-in jack as Line in */
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+/* Line-Out as Front */
+	{ 0x17, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{ } /* end */
+};
+
+/*
+ * 4ch mode
+ */
+static struct hda_verb alc888_4ST_ch4_intel_init[] = {
+/* Mic-in jack as mic in */
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+/* Line-in jack as Surround */
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+/* Line-Out as Front */
+	{ 0x17, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{ } /* end */
+};
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc888_4ST_ch6_intel_init[] = {
+/* Mic-in jack as CLFE */
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+/* Line-in jack as Surround */
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+/* Line-Out as CLFE (workaround because Mic-in is not loud enough) */
+	{ 0x17, AC_VERB_SET_CONNECT_SEL, 0x03},
+	{ } /* end */
+};
+
+/*
+ * 8ch mode
+ */
+static struct hda_verb alc888_4ST_ch8_intel_init[] = {
+/* Mic-in jack as CLFE */
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+/* Line-in jack as Surround */
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+/* Line-Out as Side */
+	{ 0x17, AC_VERB_SET_CONNECT_SEL, 0x03},
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc888_4ST_8ch_intel_modes[4] = {
+	{ 2, alc888_4ST_ch2_intel_init },
+	{ 4, alc888_4ST_ch4_intel_init },
+	{ 6, alc888_4ST_ch6_intel_init },
+	{ 8, alc888_4ST_ch8_intel_init },
+};
+
+/*
+ * ALC888 Fujitsu Siemens Amillo xa3530
+ */
+
+static struct hda_verb alc888_fujitsu_xa3530_verbs[] = {
+/* Front Mic: set to PIN_IN (empty by default) */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+/* Connect Internal HP to Front */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+/* Connect Bass HP to Front */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+/* Connect Line-Out side jack (SPDIF) to Side */
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_CONNECT_SEL, 0x03},
+/* Connect Mic jack to CLFE */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x02},
+/* Connect Line-in jack to Surround */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x01},
+/* Connect HP out jack to Front */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+/* Enable unsolicited event for HP jack and Line-out jack */
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{0x17, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{}
+};
+
+static void alc_automute_amp(struct hda_codec *codec)
+{
+	alc_automute_speaker(codec, 0);
+}
+
+static void alc_automute_amp_unsol_event(struct hda_codec *codec,
+					 unsigned int res)
+{
+	if (codec->vendor_id == 0x10ec0880)
+		res >>= 28;
+	else
+		res >>= 26;
+	if (res == ALC880_HP_EVENT)
+		alc_automute_amp(codec);
+}
+
+static void alc889_automute_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x16;
+	spec->autocfg.speaker_pins[2] = 0x17;
+	spec->autocfg.speaker_pins[3] = 0x19;
+	spec->autocfg.speaker_pins[4] = 0x1a;
+}
+
+static void alc889_intel_init_hook(struct hda_codec *codec)
+{
+	alc889_coef_init(codec);
+	alc_automute_amp(codec);
+}
+
+static void alc888_fujitsu_xa3530_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x17; /* line-out */
+	spec->autocfg.hp_pins[1] = 0x1b; /* hp */
+	spec->autocfg.speaker_pins[0] = 0x14; /* speaker */
+	spec->autocfg.speaker_pins[1] = 0x15; /* bass */
+}
+
+/*
+ * ALC888 Acer Aspire 4930G model
+ */
+
+static struct hda_verb alc888_acer_aspire_4930g_verbs[] = {
+/* Front Mic: set to PIN_IN (empty by default) */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+/* Unselect Front Mic by default in input mixer 3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0xb)},
+/* Enable unsolicited event for HP jack */
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+/* Connect Internal HP to front */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+/* Connect HP out to front */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{ }
+};
+
+/*
+ * ALC888 Acer Aspire 6530G model
+ */
+
+static struct hda_verb alc888_acer_aspire_6530g_verbs[] = {
+/* Route to built-in subwoofer as well as speakers */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+/* Bias voltage on for external mic port */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN | PIN_VREF80},
+/* Front Mic: set to PIN_IN (empty by default) */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+/* Unselect Front Mic by default in input mixer 3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0xb)},
+/* Enable unsolicited event for HP jack */
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+/* Enable speaker output */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_EAPD_BTLENABLE, 2},
+/* Enable headphone output */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{ }
+};
+
+/*
+ *ALC888 Acer Aspire 7730G model
+ */
+
+static struct hda_verb alc888_acer_aspire_7730G_verbs[] = {
+/* Bias voltage on for external mic port */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN | PIN_VREF80},
+/* Front Mic: set to PIN_IN (empty by default) */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+/* Unselect Front Mic by default in input mixer 3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0xb)},
+/* Enable unsolicited event for HP jack */
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+/* Enable speaker output */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_EAPD_BTLENABLE, 2},
+/* Enable headphone output */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_EAPD_BTLENABLE, 2},
+/*Enable internal subwoofer */
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_CONNECT_SEL, 0x02},
+	{0x17, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{ }
+};
+
+/*
+ * ALC889 Acer Aspire 8930G model
+ */
+
+static struct hda_verb alc889_acer_aspire_8930g_verbs[] = {
+/* Front Mic: set to PIN_IN (empty by default) */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+/* Unselect Front Mic by default in input mixer 3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0xb)},
+/* Enable unsolicited event for HP jack */
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+/* Connect Internal Front to Front */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+/* Connect Internal Rear to Rear */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x01},
+/* Connect Internal CLFE to CLFE */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x02},
+/* Connect HP out to Front */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+/* Enable all DACs */
+/*  DAC DISABLE/MUTE 1? */
+/*  setting bits 1-5 disables DAC nids 0x02-0x06 apparently. Init=0x38 */
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x03},
+	{0x20, AC_VERB_SET_PROC_COEF, 0x0000},
+/*  DAC DISABLE/MUTE 2? */
+/*  some bit here disables the other DACs. Init=0x4900 */
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x08},
+	{0x20, AC_VERB_SET_PROC_COEF, 0x0000},
+/* DMIC fix
+ * This laptop has a stereo digital microphone. The mics are only 1cm apart
+ * which makes the stereo useless. However, either the mic or the ALC889
+ * makes the signal become a difference/sum signal instead of standard
+ * stereo, which is annoying. So instead we flip this bit which makes the
+ * codec replicate the sum signal to both channels, turning it into a
+ * normal mono mic.
+ */
+/*  DMIC_CONTROL? Init value = 0x0001 */
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x0b},
+	{0x20, AC_VERB_SET_PROC_COEF, 0x0003},
+	{ }
+};
+
+static struct hda_input_mux alc888_2_capture_sources[2] = {
+	/* Front mic only available on one ADC */
+	{
+		.num_items = 4,
+		.items = {
+			{ "Mic", 0x0 },
+			{ "Line", 0x2 },
+			{ "CD", 0x4 },
+			{ "Front Mic", 0xb },
+		},
+	},
+	{
+		.num_items = 3,
+		.items = {
+			{ "Mic", 0x0 },
+			{ "Line", 0x2 },
+			{ "CD", 0x4 },
+		},
+	}
+};
+
+static struct hda_input_mux alc888_acer_aspire_6530_sources[2] = {
+	/* Interal mic only available on one ADC */
+	{
+		.num_items = 5,
+		.items = {
+			{ "Ext Mic", 0x0 },
+			{ "Line In", 0x2 },
+			{ "CD", 0x4 },
+			{ "Input Mix", 0xa },
+			{ "Int Mic", 0xb },
+		},
+	},
+	{
+		.num_items = 4,
+		.items = {
+			{ "Ext Mic", 0x0 },
+			{ "Line In", 0x2 },
+			{ "CD", 0x4 },
+			{ "Input Mix", 0xa },
+		},
+	}
+};
+
+static struct hda_input_mux alc889_capture_sources[3] = {
+	/* Digital mic only available on first "ADC" */
+	{
+		.num_items = 5,
+		.items = {
+			{ "Mic", 0x0 },
+			{ "Line", 0x2 },
+			{ "CD", 0x4 },
+			{ "Front Mic", 0xb },
+			{ "Input Mix", 0xa },
+		},
+	},
+	{
+		.num_items = 4,
+		.items = {
+			{ "Mic", 0x0 },
+			{ "Line", 0x2 },
+			{ "CD", 0x4 },
+			{ "Input Mix", 0xa },
+		},
+	},
+	{
+		.num_items = 4,
+		.items = {
+			{ "Mic", 0x0 },
+			{ "Line", 0x2 },
+			{ "CD", 0x4 },
+			{ "Input Mix", 0xa },
+		},
+	}
+};
+
+static struct snd_kcontrol_new alc888_base_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0,
+		HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc889_acer_aspire_8930g_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0,
+		HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+
+static void alc888_acer_aspire_4930g_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x16;
+	spec->autocfg.speaker_pins[2] = 0x17;
+}
+
+static void alc888_acer_aspire_6530g_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x16;
+	spec->autocfg.speaker_pins[2] = 0x17;
+}
+
+static void alc888_acer_aspire_7730g_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x16;
+	spec->autocfg.speaker_pins[2] = 0x17;
+}
+
+static void alc889_acer_aspire_8930g_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x16;
+	spec->autocfg.speaker_pins[2] = 0x1b;
+}
+
+/*
+ * ALC880 3-stack model
+ *
+ * DAC: Front = 0x02 (0x0c), Surr = 0x05 (0x0f), CLFE = 0x04 (0x0e)
+ * Pin assignment: Front = 0x14, Line-In/Surr = 0x1a, Mic/CLFE = 0x18,
+ *                 F-Mic = 0x1b, HP = 0x19
+ */
+
+static hda_nid_t alc880_dac_nids[4] = {
+	/* front, rear, clfe, rear_surr */
+	0x02, 0x05, 0x04, 0x03
+};
+
+static hda_nid_t alc880_adc_nids[3] = {
+	/* ADC0-2 */
+	0x07, 0x08, 0x09,
+};
+
+/* The datasheet says the node 0x07 is connected from inputs,
+ * but it shows zero connection in the real implementation on some devices.
+ * Note: this is a 915GAV bug, fixed on 915GLV
+ */
+static hda_nid_t alc880_adc_nids_alt[2] = {
+	/* ADC1-2 */
+	0x08, 0x09,
+};
+
+#define ALC880_DIGOUT_NID	0x06
+#define ALC880_DIGIN_NID	0x0a
+
+static struct hda_input_mux alc880_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x3 },
+		{ "Line", 0x2 },
+		{ "CD", 0x4 },
+	},
+};
+
+/* channel source setting (2/6 channel selection for 3-stack) */
+/* 2ch mode */
+static struct hda_verb alc880_threestack_ch2_init[] = {
+	/* set line-in to input, mute it */
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	/* set mic-in to input vref 80%, mute it */
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ } /* end */
+};
+
+/* 6ch mode */
+static struct hda_verb alc880_threestack_ch6_init[] = {
+	/* set line-in to output, unmute it */
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	/* set mic-in to output, unmute it */
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc880_threestack_modes[2] = {
+	{ 2, alc880_threestack_ch2_init },
+	{ 6, alc880_threestack_ch6_init },
+};
+
+static struct snd_kcontrol_new alc880_three_stack_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0f, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x3, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x3, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x19, 0x0, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+/* capture mixer elements */
+static int alc_cap_vol_info(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	int err;
+
+	mutex_lock(&codec->control_mutex);
+	kcontrol->private_value = HDA_COMPOSE_AMP_VAL(spec->adc_nids[0], 3, 0,
+						      HDA_INPUT);
+	err = snd_hda_mixer_amp_volume_info(kcontrol, uinfo);
+	mutex_unlock(&codec->control_mutex);
+	return err;
+}
+
+static int alc_cap_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+			   unsigned int size, unsigned int __user *tlv)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	int err;
+
+	mutex_lock(&codec->control_mutex);
+	kcontrol->private_value = HDA_COMPOSE_AMP_VAL(spec->adc_nids[0], 3, 0,
+						      HDA_INPUT);
+	err = snd_hda_mixer_amp_tlv(kcontrol, op_flag, size, tlv);
+	mutex_unlock(&codec->control_mutex);
+	return err;
+}
+
+typedef int (*getput_call_t)(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol);
+
+static int alc_cap_getput_caller(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol,
+				 getput_call_t func)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	int err;
+
+	mutex_lock(&codec->control_mutex);
+	kcontrol->private_value = HDA_COMPOSE_AMP_VAL(spec->adc_nids[adc_idx],
+						      3, 0, HDA_INPUT);
+	err = func(kcontrol, ucontrol);
+	mutex_unlock(&codec->control_mutex);
+	return err;
+}
+
+static int alc_cap_vol_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	return alc_cap_getput_caller(kcontrol, ucontrol,
+				     snd_hda_mixer_amp_volume_get);
+}
+
+static int alc_cap_vol_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	return alc_cap_getput_caller(kcontrol, ucontrol,
+				     snd_hda_mixer_amp_volume_put);
+}
+
+/* capture mixer elements */
+#define alc_cap_sw_info		snd_ctl_boolean_stereo_info
+
+static int alc_cap_sw_get(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	return alc_cap_getput_caller(kcontrol, ucontrol,
+				     snd_hda_mixer_amp_switch_get);
+}
+
+static int alc_cap_sw_put(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	return alc_cap_getput_caller(kcontrol, ucontrol,
+				     snd_hda_mixer_amp_switch_put);
+}
+
+#define _DEFINE_CAPMIX(num) \
+	{ \
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name = "Capture Switch", \
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+		.count = num, \
+		.info = alc_cap_sw_info, \
+		.get = alc_cap_sw_get, \
+		.put = alc_cap_sw_put, \
+	}, \
+	{ \
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name = "Capture Volume", \
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+			   SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK), \
+		.count = num, \
+		.info = alc_cap_vol_info, \
+		.get = alc_cap_vol_get, \
+		.put = alc_cap_vol_put, \
+		.tlv = { .c = alc_cap_vol_tlv }, \
+	}
+
+#define _DEFINE_CAPSRC(num) \
+	{ \
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+		/* .name = "Capture Source", */ \
+		.name = "Input Source", \
+		.count = num, \
+		.info = alc_mux_enum_info, \
+		.get = alc_mux_enum_get, \
+		.put = alc_mux_enum_put, \
+	}
+
+#define DEFINE_CAPMIX(num) \
+static struct snd_kcontrol_new alc_capture_mixer ## num[] = { \
+	_DEFINE_CAPMIX(num),				      \
+	_DEFINE_CAPSRC(num),				      \
+	{ } /* end */					      \
+}
+
+#define DEFINE_CAPMIX_NOSRC(num) \
+static struct snd_kcontrol_new alc_capture_mixer_nosrc ## num[] = { \
+	_DEFINE_CAPMIX(num),					    \
+	{ } /* end */						    \
+}
+
+/* up to three ADCs */
+DEFINE_CAPMIX(1);
+DEFINE_CAPMIX(2);
+DEFINE_CAPMIX(3);
+DEFINE_CAPMIX_NOSRC(1);
+DEFINE_CAPMIX_NOSRC(2);
+DEFINE_CAPMIX_NOSRC(3);
+
+/*
+ * ALC880 5-stack model
+ *
+ * DAC: Front = 0x02 (0x0c), Surr = 0x05 (0x0f), CLFE = 0x04 (0x0d),
+ *      Side = 0x02 (0xd)
+ * Pin assignment: Front = 0x14, Surr = 0x17, CLFE = 0x16
+ *                 Line-In/Side = 0x1a, Mic = 0x18, F-Mic = 0x1b, HP = 0x19
+ */
+
+/* additional mixers to alc880_three_stack_mixer */
+static struct snd_kcontrol_new alc880_five_stack_mixer[] = {
+	HDA_CODEC_VOLUME("Side Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Side Playback Switch", 0x0d, 2, HDA_INPUT),
+	{ } /* end */
+};
+
+/* channel source setting (6/8 channel selection for 5-stack) */
+/* 6ch mode */
+static struct hda_verb alc880_fivestack_ch6_init[] = {
+	/* set line-in to input, mute it */
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ } /* end */
+};
+
+/* 8ch mode */
+static struct hda_verb alc880_fivestack_ch8_init[] = {
+	/* set line-in to output, unmute it */
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc880_fivestack_modes[2] = {
+	{ 6, alc880_fivestack_ch6_init },
+	{ 8, alc880_fivestack_ch8_init },
+};
+
+
+/*
+ * ALC880 6-stack model
+ *
+ * DAC: Front = 0x02 (0x0c), Surr = 0x03 (0x0d), CLFE = 0x04 (0x0e),
+ *      Side = 0x05 (0x0f)
+ * Pin assignment: Front = 0x14, Surr = 0x15, CLFE = 0x16, Side = 0x17,
+ *   Mic = 0x18, F-Mic = 0x19, Line = 0x1a, HP = 0x1b
+ */
+
+static hda_nid_t alc880_6st_dac_nids[4] = {
+	/* front, rear, clfe, rear_surr */
+	0x02, 0x03, 0x04, 0x05
+};
+
+static struct hda_input_mux alc880_6stack_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x1 },
+		{ "Line", 0x2 },
+		{ "CD", 0x4 },
+	},
+};
+
+/* fixed 8-channels */
+static struct hda_channel_mode alc880_sixstack_modes[1] = {
+	{ 8, NULL },
+};
+
+static struct snd_kcontrol_new alc880_six_stack_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+
+/*
+ * ALC880 W810 model
+ *
+ * W810 has rear IO for:
+ * Front (DAC 02)
+ * Surround (DAC 03)
+ * Center/LFE (DAC 04)
+ * Digital out (06)
+ *
+ * The system also has a pair of internal speakers, and a headphone jack.
+ * These are both connected to Line2 on the codec, hence to DAC 02.
+ *
+ * There is a variable resistor to control the speaker or headphone
+ * volume. This is a hardware-only device without a software API.
+ *
+ * Plugging headphones in will disable the internal speakers. This is
+ * implemented in hardware, not via the driver using jack sense. In
+ * a similar fashion, plugging into the rear socket marked "front" will
+ * disable both the speakers and headphones.
+ *
+ * For input, there's a microphone jack, and an "audio in" jack.
+ * These may not do anything useful with this driver yet, because I
+ * haven't setup any initialization verbs for these yet...
+ */
+
+static hda_nid_t alc880_w810_dac_nids[3] = {
+	/* front, rear/surround, clfe */
+	0x02, 0x03, 0x04
+};
+
+/* fixed 6 channels */
+static struct hda_channel_mode alc880_w810_modes[1] = {
+	{ 6, NULL }
+};
+
+/* Pin assignment: Front = 0x14, Surr = 0x15, CLFE = 0x16, HP = 0x1b */
+static struct snd_kcontrol_new alc880_w810_base_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+
+/*
+ * Z710V model
+ *
+ * DAC: Front = 0x02 (0x0c), HP = 0x03 (0x0d)
+ * Pin assignment: Front = 0x14, HP = 0x15, Mic = 0x18, Mic2 = 0x19(?),
+ *                 Line = 0x1a
+ */
+
+static hda_nid_t alc880_z71v_dac_nids[1] = {
+	0x02
+};
+#define ALC880_Z71V_HP_DAC	0x03
+
+/* fixed 2 channels */
+static struct hda_channel_mode alc880_2_jack_modes[1] = {
+	{ 2, NULL }
+};
+
+static struct snd_kcontrol_new alc880_z71v_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+
+/*
+ * ALC880 F1734 model
+ *
+ * DAC: HP = 0x02 (0x0c), Front = 0x03 (0x0d)
+ * Pin assignment: HP = 0x14, Front = 0x15, Mic = 0x18
+ */
+
+static hda_nid_t alc880_f1734_dac_nids[1] = {
+	0x03
+};
+#define ALC880_F1734_HP_DAC	0x02
+
+static struct snd_kcontrol_new alc880_f1734_mixer[] = {
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_input_mux alc880_f1734_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Mic", 0x1 },
+		{ "CD", 0x4 },
+	},
+};
+
+
+/*
+ * ALC880 ASUS model
+ *
+ * DAC: HP/Front = 0x02 (0x0c), Surr = 0x03 (0x0d), CLFE = 0x04 (0x0e)
+ * Pin assignment: HP/Front = 0x14, Surr = 0x15, CLFE = 0x16,
+ *  Mic = 0x18, Line = 0x1a
+ */
+
+#define alc880_asus_dac_nids	alc880_w810_dac_nids	/* identical with w810 */
+#define alc880_asus_modes	alc880_threestack_modes	/* 2/6 channel mode */
+
+static struct snd_kcontrol_new alc880_asus_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+/*
+ * ALC880 ASUS W1V model
+ *
+ * DAC: HP/Front = 0x02 (0x0c), Surr = 0x03 (0x0d), CLFE = 0x04 (0x0e)
+ * Pin assignment: HP/Front = 0x14, Surr = 0x15, CLFE = 0x16,
+ *  Mic = 0x18, Line = 0x1a, Line2 = 0x1b
+ */
+
+/* additional mixers to alc880_asus_mixer */
+static struct snd_kcontrol_new alc880_asus_w1v_mixer[] = {
+	HDA_CODEC_VOLUME("Line2 Playback Volume", 0x0b, 0x03, HDA_INPUT),
+	HDA_CODEC_MUTE("Line2 Playback Switch", 0x0b, 0x03, HDA_INPUT),
+	{ } /* end */
+};
+
+/* TCL S700 */
+static struct snd_kcontrol_new alc880_tcl_s700_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0B, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0B, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0B, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0B, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+/* Uniwill */
+static struct snd_kcontrol_new alc880_uniwill_mixer[] = {
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc880_fujitsu_mixer[] = {
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Ext Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Ext Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc880_uniwill_p53_mixer[] = {
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+/*
+ * virtual master controls
+ */
+
+/*
+ * slave controls for virtual master
+ */
+static const char *alc_slave_vols[] = {
+	"Front Playback Volume",
+	"Surround Playback Volume",
+	"Center Playback Volume",
+	"LFE Playback Volume",
+	"Side Playback Volume",
+	"Headphone Playback Volume",
+	"Speaker Playback Volume",
+	"Mono Playback Volume",
+	"Line-Out Playback Volume",
+	"PCM Playback Volume",
+	NULL,
+};
+
+static const char *alc_slave_sws[] = {
+	"Front Playback Switch",
+	"Surround Playback Switch",
+	"Center Playback Switch",
+	"LFE Playback Switch",
+	"Side Playback Switch",
+	"Headphone Playback Switch",
+	"Speaker Playback Switch",
+	"Mono Playback Switch",
+	"IEC958 Playback Switch",
+	"Line-Out Playback Switch",
+	"PCM Playback Switch",
+	NULL,
+};
+
+/*
+ * build control elements
+ */
+
+#define NID_MAPPING		(-1)
+
+#define SUBDEV_SPEAKER_		(0 << 6)
+#define SUBDEV_HP_		(1 << 6)
+#define SUBDEV_LINE_		(2 << 6)
+#define SUBDEV_SPEAKER(x)	(SUBDEV_SPEAKER_ | ((x) & 0x3f))
+#define SUBDEV_HP(x)		(SUBDEV_HP_ | ((x) & 0x3f))
+#define SUBDEV_LINE(x)		(SUBDEV_LINE_ | ((x) & 0x3f))
+
+static void alc_free_kctls(struct hda_codec *codec);
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+/* additional beep mixers; the actual parameters are overwritten at build */
+static struct snd_kcontrol_new alc_beep_mixer[] = {
+	HDA_CODEC_VOLUME("Beep Playback Volume", 0, 0, HDA_INPUT),
+	HDA_CODEC_MUTE_BEEP("Beep Playback Switch", 0, 0, HDA_INPUT),
+	{ } /* end */
+};
+#endif
+
+static int alc_build_controls(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct snd_kcontrol *kctl = NULL;
+	struct snd_kcontrol_new *knew;
+	int i, j, err;
+	unsigned int u;
+	hda_nid_t nid;
+
+	for (i = 0; i < spec->num_mixers; i++) {
+		err = snd_hda_add_new_ctls(codec, spec->mixers[i]);
+		if (err < 0)
+			return err;
+	}
+	if (spec->cap_mixer) {
+		err = snd_hda_add_new_ctls(codec, spec->cap_mixer);
+		if (err < 0)
+			return err;
+	}
+	if (spec->multiout.dig_out_nid) {
+		err = snd_hda_create_spdif_out_ctls(codec,
+						    spec->multiout.dig_out_nid);
+		if (err < 0)
+			return err;
+		if (!spec->no_analog) {
+			err = snd_hda_create_spdif_share_sw(codec,
+							    &spec->multiout);
+			if (err < 0)
+				return err;
+			spec->multiout.share_spdif = 1;
+		}
+	}
+	if (spec->dig_in_nid) {
+		err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
+		if (err < 0)
+			return err;
+	}
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+	/* create beep controls if needed */
+	if (spec->beep_amp) {
+		struct snd_kcontrol_new *knew;
+		for (knew = alc_beep_mixer; knew->name; knew++) {
+			struct snd_kcontrol *kctl;
+			kctl = snd_ctl_new1(knew, codec);
+			if (!kctl)
+				return -ENOMEM;
+			kctl->private_value = spec->beep_amp;
+			err = snd_hda_ctl_add(codec, 0, kctl);
+			if (err < 0)
+				return err;
+		}
+	}
+#endif
+
+	/* if we have no master control, let's create it */
+	if (!spec->no_analog &&
+	    !snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
+		unsigned int vmaster_tlv[4];
+		snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid,
+					HDA_OUTPUT, vmaster_tlv);
+		err = snd_hda_add_vmaster(codec, "Master Playback Volume",
+					  vmaster_tlv, alc_slave_vols);
+		if (err < 0)
+			return err;
+	}
+	if (!spec->no_analog &&
+	    !snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
+		err = snd_hda_add_vmaster(codec, "Master Playback Switch",
+					  NULL, alc_slave_sws);
+		if (err < 0)
+			return err;
+	}
+
+	/* assign Capture Source enums to NID */
+	if (spec->capsrc_nids || spec->adc_nids) {
+		kctl = snd_hda_find_mixer_ctl(codec, "Capture Source");
+		if (!kctl)
+			kctl = snd_hda_find_mixer_ctl(codec, "Input Source");
+		for (i = 0; kctl && i < kctl->count; i++) {
+			hda_nid_t *nids = spec->capsrc_nids;
+			if (!nids)
+				nids = spec->adc_nids;
+			err = snd_hda_add_nid(codec, kctl, i, nids[i]);
+			if (err < 0)
+				return err;
+		}
+	}
+	if (spec->cap_mixer) {
+		const char *kname = kctl ? kctl->id.name : NULL;
+		for (knew = spec->cap_mixer; knew->name; knew++) {
+			if (kname && strcmp(knew->name, kname) == 0)
+				continue;
+			kctl = snd_hda_find_mixer_ctl(codec, knew->name);
+			for (i = 0; kctl && i < kctl->count; i++) {
+				err = snd_hda_add_nid(codec, kctl, i,
+						      spec->adc_nids[i]);
+				if (err < 0)
+					return err;
+			}
+		}
+	}
+
+	/* other nid->control mapping */
+	for (i = 0; i < spec->num_mixers; i++) {
+		for (knew = spec->mixers[i]; knew->name; knew++) {
+			if (knew->iface != NID_MAPPING)
+				continue;
+			kctl = snd_hda_find_mixer_ctl(codec, knew->name);
+			if (kctl == NULL)
+				continue;
+			u = knew->subdevice;
+			for (j = 0; j < 4; j++, u >>= 8) {
+				nid = u & 0x3f;
+				if (nid == 0)
+					continue;
+				switch (u & 0xc0) {
+				case SUBDEV_SPEAKER_:
+					nid = spec->autocfg.speaker_pins[nid];
+					break;
+				case SUBDEV_LINE_:
+					nid = spec->autocfg.line_out_pins[nid];
+					break;
+				case SUBDEV_HP_:
+					nid = spec->autocfg.hp_pins[nid];
+					break;
+				default:
+					continue;
+				}
+				err = snd_hda_add_nid(codec, kctl, 0, nid);
+				if (err < 0)
+					return err;
+			}
+			u = knew->private_value;
+			for (j = 0; j < 4; j++, u >>= 8) {
+				nid = u & 0xff;
+				if (nid == 0)
+					continue;
+				err = snd_hda_add_nid(codec, kctl, 0, nid);
+				if (err < 0)
+					return err;
+			}
+		}
+	}
+
+	alc_free_kctls(codec); /* no longer needed */
+
+	return 0;
+}
+
+
+/*
+ * initialize the codec volumes, etc
+ */
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc880_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-2 and set the default input to mic-in
+	 */
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 * Note: PASD motherboards uses the Line In 2 as the input for front
+	 * panel mic (mic 2)
+	 */
+	/* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)},
+
+	/*
+	 * Set up output mixers (0x0c - 0x0f)
+	 */
+	/* set vol=0 to output mixers */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* set up input amps for analog loopback */
+	/* Amp Indices: DAC = 0, mixer = 1 */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+
+	{ }
+};
+
+/*
+ * 3-stack pin configuration:
+ * front = 0x14, mic/clfe = 0x18, HP = 0x19, line/surr = 0x1a, f-mic = 0x1b
+ */
+static struct hda_verb alc880_pin_3stack_init_verbs[] = {
+	/*
+	 * preset connection lists of input pins
+	 * 0 = front, 1 = rear_surr, 2 = CLFE, 3 = surround
+	 */
+	{0x10, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */
+	{0x11, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+	{0x12, AC_VERB_SET_CONNECT_SEL, 0x03}, /* line/surround */
+
+	/*
+	 * Set pin mode and muting
+	 */
+	/* set front pin widgets 0x14 for output */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Mic1 (rear panel) pin widget for input and vref at 80% */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Mic2 (as headphone out) for HP output */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Line In pin widget for input */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line2 (as front mic) pin widget for input and vref at 80% */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* CD pin widget for input */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{ }
+};
+
+/*
+ * 5-stack pin configuration:
+ * front = 0x14, surround = 0x17, clfe = 0x16, mic = 0x18, HP = 0x19,
+ * line-in/side = 0x1a, f-mic = 0x1b
+ */
+static struct hda_verb alc880_pin_5stack_init_verbs[] = {
+	/*
+	 * preset connection lists of input pins
+	 * 0 = front, 1 = rear_surr, 2 = CLFE, 3 = surround
+	 */
+	{0x11, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+	{0x12, AC_VERB_SET_CONNECT_SEL, 0x01}, /* line/side */
+
+	/*
+	 * Set pin mode and muting
+	 */
+	/* set pin widgets 0x14-0x17 for output */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* unmute pins for output (no gain on this amp) */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Mic1 (rear panel) pin widget for input and vref at 80% */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Mic2 (as headphone out) for HP output */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Line In pin widget for input */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line2 (as front mic) pin widget for input and vref at 80% */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* CD pin widget for input */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{ }
+};
+
+/*
+ * W810 pin configuration:
+ * front = 0x14, surround = 0x15, clfe = 0x16, HP = 0x1b
+ */
+static struct hda_verb alc880_pin_w810_init_verbs[] = {
+	/* hphone/speaker input selector: front DAC */
+	{0x13, AC_VERB_SET_CONNECT_SEL, 0x0},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+
+	{ }
+};
+
+/*
+ * Z71V pin configuration:
+ * Speaker-out = 0x14, HP = 0x15, Mic = 0x18, Line-in = 0x1a, Mic2 = 0x1b (?)
+ */
+static struct hda_verb alc880_pin_z71v_init_verbs[] = {
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{ }
+};
+
+/*
+ * 6-stack pin configuration:
+ * front = 0x14, surr = 0x15, clfe = 0x16, side = 0x17, mic = 0x18,
+ * f-mic = 0x19, line = 0x1a, HP = 0x1b
+ */
+static struct hda_verb alc880_pin_6stack_init_verbs[] = {
+	{0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{ }
+};
+
+/*
+ * Uniwill pin configuration:
+ * HP = 0x14, InternalSpeaker = 0x15, mic = 0x18, internal mic = 0x19,
+ * line = 0x1a
+ */
+static struct hda_verb alc880_uniwill_init_verbs[] = {
+	{0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, */
+	/* {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+
+	{ }
+};
+
+/*
+* Uniwill P53
+* HP = 0x14, InternalSpeaker = 0x15, mic = 0x19,
+ */
+static struct hda_verb alc880_uniwill_p53_init_verbs[] = {
+	{0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_DCVOL_EVENT},
+
+	{ }
+};
+
+static struct hda_verb alc880_beep_init_verbs[] = {
+	{ 0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(5) },
+	{ }
+};
+
+/* auto-toggle front mic */
+static void alc880_uniwill_mic_automute(struct hda_codec *codec)
+{
+ 	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, 0x18);
+	bits = present ? HDA_AMP_MUTE : 0;
+	snd_hda_codec_amp_stereo(codec, 0x0b, HDA_INPUT, 1, HDA_AMP_MUTE, bits);
+}
+
+static void alc880_uniwill_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x16;
+}
+
+static void alc880_uniwill_init_hook(struct hda_codec *codec)
+{
+	alc_automute_amp(codec);
+	alc880_uniwill_mic_automute(codec);
+}
+
+static void alc880_uniwill_unsol_event(struct hda_codec *codec,
+				       unsigned int res)
+{
+	/* Looks like the unsol event is incompatible with the standard
+	 * definition.  4bit tag is placed at 28 bit!
+	 */
+	switch (res >> 28) {
+	case ALC880_MIC_EVENT:
+		alc880_uniwill_mic_automute(codec);
+		break;
+	default:
+		alc_automute_amp_unsol_event(codec, res);
+		break;
+	}
+}
+
+static void alc880_uniwill_p53_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[0] = 0x15;
+}
+
+static void alc880_uniwill_p53_dcvol_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+
+	present = snd_hda_codec_read(codec, 0x21, 0,
+				     AC_VERB_GET_VOLUME_KNOB_CONTROL, 0);
+	present &= HDA_AMP_VOLMASK;
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_OUTPUT, 0,
+				 HDA_AMP_VOLMASK, present);
+	snd_hda_codec_amp_stereo(codec, 0x0d, HDA_OUTPUT, 0,
+				 HDA_AMP_VOLMASK, present);
+}
+
+static void alc880_uniwill_p53_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	/* Looks like the unsol event is incompatible with the standard
+	 * definition.  4bit tag is placed at 28 bit!
+	 */
+	if ((res >> 28) == ALC880_DCVOL_EVENT)
+		alc880_uniwill_p53_dcvol_automute(codec);
+	else
+		alc_automute_amp_unsol_event(codec, res);
+}
+
+/*
+ * F1734 pin configuration:
+ * HP = 0x14, speaker-out = 0x15, mic = 0x18
+ */
+static struct hda_verb alc880_pin_f1734_init_verbs[] = {
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x10, AC_VERB_SET_CONNECT_SEL, 0x02},
+	{0x11, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x12, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x13, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_HP_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_DCVOL_EVENT},
+
+	{ }
+};
+
+/*
+ * ASUS pin configuration:
+ * HP/front = 0x14, surr = 0x15, clfe = 0x16, mic = 0x18, line = 0x1a
+ */
+static struct hda_verb alc880_pin_asus_init_verbs[] = {
+	{0x10, AC_VERB_SET_CONNECT_SEL, 0x02},
+	{0x11, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x12, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x13, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{ }
+};
+
+/* Enable GPIO mask and set output */
+#define alc880_gpio1_init_verbs	alc_gpio1_init_verbs
+#define alc880_gpio2_init_verbs	alc_gpio2_init_verbs
+#define alc880_gpio3_init_verbs	alc_gpio3_init_verbs
+
+/* Clevo m520g init */
+static struct hda_verb alc880_pin_clevo_init_verbs[] = {
+	/* headphone output */
+	{0x11, AC_VERB_SET_CONNECT_SEL, 0x01},
+	/* line-out */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Line-in */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* CD */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Mic1 (rear panel) */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Mic2 (front panel) */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* headphone */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+        /* change to EAPD mode */
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x07},
+	{0x20, AC_VERB_SET_PROC_COEF,  0x3060},
+
+	{ }
+};
+
+static struct hda_verb alc880_pin_tcl_S700_init_verbs[] = {
+	/* change to EAPD mode */
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x07},
+	{0x20, AC_VERB_SET_PROC_COEF,  0x3060},
+
+	/* Headphone output */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Front output*/
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Line In pin widget for input */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	/* CD pin widget for input */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	/* Mic1 (rear panel) pin widget for input and vref at 80% */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+
+	/* change to EAPD mode */
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x07},
+	{0x20, AC_VERB_SET_PROC_COEF,  0x3070},
+
+	{ }
+};
+
+/*
+ * LG m1 express dual
+ *
+ * Pin assignment:
+ *   Rear Line-In/Out (blue): 0x14
+ *   Build-in Mic-In: 0x15
+ *   Speaker-out: 0x17
+ *   HP-Out (green): 0x1b
+ *   Mic-In/Out (red): 0x19
+ *   SPDIF-Out: 0x1e
+ */
+
+/* To make 5.1 output working (green=Front, blue=Surr, red=CLFE) */
+static hda_nid_t alc880_lg_dac_nids[3] = {
+	0x05, 0x02, 0x03
+};
+
+/* seems analog CD is not working */
+static struct hda_input_mux alc880_lg_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x1 },
+		{ "Line", 0x5 },
+		{ "Internal Mic", 0x6 },
+	},
+};
+
+/* 2,4,6 channel modes */
+static struct hda_verb alc880_lg_ch2_init[] = {
+	/* set line-in and mic-in to input */
+	{ 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ }
+};
+
+static struct hda_verb alc880_lg_ch4_init[] = {
+	/* set line-in to out and mic-in to input */
+	{ 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	{ 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ }
+};
+
+static struct hda_verb alc880_lg_ch6_init[] = {
+	/* set line-in and mic-in to output */
+	{ 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	{ 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	{ }
+};
+
+static struct hda_channel_mode alc880_lg_ch_modes[3] = {
+	{ 2, alc880_lg_ch2_init },
+	{ 4, alc880_lg_ch4_init },
+	{ 6, alc880_lg_ch6_init },
+};
+
+static struct snd_kcontrol_new alc880_lg_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0f, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0d, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0d, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0d, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0d, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x06, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x06, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x0b, 0x07, HDA_INPUT),
+	HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x0b, 0x07, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb alc880_lg_init_verbs[] = {
+	/* set capture source to mic-in */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* mute all amp mixer inputs */
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(5)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)},
+	/* line-in to input */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* built-in mic */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* speaker-out */
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* mic-in to input */
+	{0x11, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* HP-out */
+	{0x13, AC_VERB_SET_CONNECT_SEL, 0x03},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* jack sense */
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{ }
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc880_lg_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x1b;
+	spec->autocfg.speaker_pins[0] = 0x17;
+}
+
+/*
+ * LG LW20
+ *
+ * Pin assignment:
+ *   Speaker-out: 0x14
+ *   Mic-In: 0x18
+ *   Built-in Mic-In: 0x19
+ *   Line-In: 0x1b
+ *   HP-Out: 0x1a
+ *   SPDIF-Out: 0x1e
+ */
+
+static struct hda_input_mux alc880_lg_lw_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Internal Mic", 0x1 },
+		{ "Line In", 0x2 },
+	},
+};
+
+#define alc880_lg_lw_modes alc880_threestack_modes
+
+static struct snd_kcontrol_new alc880_lg_lw_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0f, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb alc880_lg_lw_init_verbs[] = {
+	{0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+	{0x10, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */
+	{0x12, AC_VERB_SET_CONNECT_SEL, 0x03}, /* line/surround */
+
+	/* set capture source to mic-in */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)},
+	/* speaker-out */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* HP-out */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* mic-in to input */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* built-in mic */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* jack sense */
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{ }
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc880_lg_lw_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x1b;
+	spec->autocfg.speaker_pins[0] = 0x14;
+}
+
+static struct snd_kcontrol_new alc880_medion_rim_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Master Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Internal Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_input_mux alc880_medion_rim_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Internal Mic", 0x1 },
+	},
+};
+
+static struct hda_verb alc880_medion_rim_init_verbs[] = {
+	{0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Mic1 (rear panel) pin widget for input and vref at 80% */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Mic2 (as headphone out) for HP output */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Internal Speaker */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x07},
+	{0x20, AC_VERB_SET_PROC_COEF,  0x3060},
+
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{ }
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc880_medion_rim_automute(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc_automute_amp(codec);
+	/* toggle EAPD */
+	if (spec->jack_present)
+		snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0);
+	else
+		snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 2);
+}
+
+static void alc880_medion_rim_unsol_event(struct hda_codec *codec,
+					  unsigned int res)
+{
+	/* Looks like the unsol event is incompatible with the standard
+	 * definition.  4bit tag is placed at 28 bit!
+	 */
+	if ((res >> 28) == ALC880_HP_EVENT)
+		alc880_medion_rim_automute(codec);
+}
+
+static void alc880_medion_rim_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[0] = 0x1b;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list alc880_loopbacks[] = {
+	{ 0x0b, HDA_INPUT, 0 },
+	{ 0x0b, HDA_INPUT, 1 },
+	{ 0x0b, HDA_INPUT, 2 },
+	{ 0x0b, HDA_INPUT, 3 },
+	{ 0x0b, HDA_INPUT, 4 },
+	{ } /* end */
+};
+
+static struct hda_amp_list alc880_lg_loopbacks[] = {
+	{ 0x0b, HDA_INPUT, 1 },
+	{ 0x0b, HDA_INPUT, 6 },
+	{ 0x0b, HDA_INPUT, 7 },
+	{ } /* end */
+};
+#endif
+
+/*
+ * Common callbacks
+ */
+
+static int alc_init(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	unsigned int i;
+
+	alc_fix_pll(codec);
+	alc_auto_init_amp(codec, spec->init_amp);
+
+	for (i = 0; i < spec->num_init_verbs; i++)
+		snd_hda_sequence_write(codec, spec->init_verbs[i]);
+
+	if (spec->init_hook)
+		spec->init_hook(codec);
+
+	hda_call_check_power_status(codec, 0x01);
+	return 0;
+}
+
+static void alc_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	struct alc_spec *spec = codec->spec;
+
+	if (spec->unsol_event)
+		spec->unsol_event(codec, res);
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static int alc_check_power_status(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct alc_spec *spec = codec->spec;
+	return snd_hda_check_amp_list_power(codec, &spec->loopback, nid);
+}
+#endif
+
+/*
+ * Analog playback callbacks
+ */
+static int alc880_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				    struct hda_codec *codec,
+				    struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
+					     hinfo);
+}
+
+static int alc880_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       unsigned int stream_tag,
+				       unsigned int format,
+				       struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_prepare(codec, &spec->multiout,
+						stream_tag, format, substream);
+}
+
+static int alc880_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Digital out
+ */
+static int alc880_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+					struct hda_codec *codec,
+					struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int alc880_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					   struct hda_codec *codec,
+					   unsigned int stream_tag,
+					   unsigned int format,
+					   struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
+					     stream_tag, format, substream);
+}
+
+static int alc880_dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+					   struct hda_codec *codec,
+					   struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout);
+}
+
+static int alc880_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+					 struct hda_codec *codec,
+					 struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+/*
+ * Analog capture
+ */
+static int alc880_alt_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      unsigned int stream_tag,
+				      unsigned int format,
+				      struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+
+	snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number + 1],
+				   stream_tag, 0, format);
+	return 0;
+}
+
+static int alc880_alt_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+
+	snd_hda_codec_cleanup_stream(codec,
+				     spec->adc_nids[substream->number + 1]);
+	return 0;
+}
+
+/* analog capture with dynamic dual-adc changes */
+static int dualmic_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       unsigned int stream_tag,
+				       unsigned int format,
+				       struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->cur_adc = spec->adc_nids[spec->cur_adc_idx];
+	spec->cur_adc_stream_tag = stream_tag;
+	spec->cur_adc_format = format;
+	snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format);
+	return 0;
+}
+
+static int dualmic_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				       struct hda_codec *codec,
+				       struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+	snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
+	spec->cur_adc = 0;
+	return 0;
+}
+
+static struct hda_pcm_stream dualmic_pcm_analog_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0, /* fill later */
+	.ops = {
+		.prepare = dualmic_capture_pcm_prepare,
+		.cleanup = dualmic_capture_pcm_cleanup
+	},
+};
+
+/*
+ */
+static struct hda_pcm_stream alc880_pcm_analog_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 8,
+	/* NID is set in alc_build_pcms */
+	.ops = {
+		.open = alc880_playback_pcm_open,
+		.prepare = alc880_playback_pcm_prepare,
+		.cleanup = alc880_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream alc880_pcm_analog_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in alc_build_pcms */
+};
+
+static struct hda_pcm_stream alc880_pcm_analog_alt_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in alc_build_pcms */
+};
+
+static struct hda_pcm_stream alc880_pcm_analog_alt_capture = {
+	.substreams = 2, /* can be overridden */
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in alc_build_pcms */
+	.ops = {
+		.prepare = alc880_alt_capture_pcm_prepare,
+		.cleanup = alc880_alt_capture_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream alc880_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in alc_build_pcms */
+	.ops = {
+		.open = alc880_dig_playback_pcm_open,
+		.close = alc880_dig_playback_pcm_close,
+		.prepare = alc880_dig_playback_pcm_prepare,
+		.cleanup = alc880_dig_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream alc880_pcm_digital_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in alc_build_pcms */
+};
+
+/* Used by alc_build_pcms to flag that a PCM has no playback stream */
+static struct hda_pcm_stream alc_pcm_null_stream = {
+	.substreams = 0,
+	.channels_min = 0,
+	.channels_max = 0,
+};
+
+static int alc_build_pcms(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct hda_pcm *info = spec->pcm_rec;
+	int i;
+
+	codec->num_pcms = 1;
+	codec->pcm_info = info;
+
+	if (spec->no_analog)
+		goto skip_analog;
+
+	snprintf(spec->stream_name_analog, sizeof(spec->stream_name_analog),
+		 "%s Analog", codec->chip_name);
+	info->name = spec->stream_name_analog;
+
+	if (spec->stream_analog_playback) {
+		if (snd_BUG_ON(!spec->multiout.dac_nids))
+			return -EINVAL;
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK] = *(spec->stream_analog_playback);
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dac_nids[0];
+	}
+	if (spec->stream_analog_capture) {
+		if (snd_BUG_ON(!spec->adc_nids))
+			return -EINVAL;
+		info->stream[SNDRV_PCM_STREAM_CAPTURE] = *(spec->stream_analog_capture);
+		info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
+	}
+
+	if (spec->channel_mode) {
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = 0;
+		for (i = 0; i < spec->num_channel_mode; i++) {
+			if (spec->channel_mode[i].channels > info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max) {
+				info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = spec->channel_mode[i].channels;
+			}
+		}
+	}
+
+ skip_analog:
+	/* SPDIF for stream index #1 */
+	if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
+		snprintf(spec->stream_name_digital,
+			 sizeof(spec->stream_name_digital),
+			 "%s Digital", codec->chip_name);
+		codec->num_pcms = 2;
+	        codec->slave_dig_outs = spec->multiout.slave_dig_outs;
+		info = spec->pcm_rec + 1;
+		info->name = spec->stream_name_digital;
+		if (spec->dig_out_type)
+			info->pcm_type = spec->dig_out_type;
+		else
+			info->pcm_type = HDA_PCM_TYPE_SPDIF;
+		if (spec->multiout.dig_out_nid &&
+		    spec->stream_digital_playback) {
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK] = *(spec->stream_digital_playback);
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dig_out_nid;
+		}
+		if (spec->dig_in_nid &&
+		    spec->stream_digital_capture) {
+			info->stream[SNDRV_PCM_STREAM_CAPTURE] = *(spec->stream_digital_capture);
+			info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid;
+		}
+		/* FIXME: do we need this for all Realtek codec models? */
+		codec->spdif_status_reset = 1;
+	}
+
+	if (spec->no_analog)
+		return 0;
+
+	/* If the use of more than one ADC is requested for the current
+	 * model, configure a second analog capture-only PCM.
+	 */
+	/* Additional Analaog capture for index #2 */
+	if ((spec->alt_dac_nid && spec->stream_analog_alt_playback) ||
+	    (spec->num_adc_nids > 1 && spec->stream_analog_alt_capture)) {
+		codec->num_pcms = 3;
+		info = spec->pcm_rec + 2;
+		info->name = spec->stream_name_analog;
+		if (spec->alt_dac_nid) {
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
+				*spec->stream_analog_alt_playback;
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
+				spec->alt_dac_nid;
+		} else {
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
+				alc_pcm_null_stream;
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = 0;
+		}
+		if (spec->num_adc_nids > 1) {
+			info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+				*spec->stream_analog_alt_capture;
+			info->stream[SNDRV_PCM_STREAM_CAPTURE].nid =
+				spec->adc_nids[1];
+			info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams =
+				spec->num_adc_nids - 1;
+		} else {
+			info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+				alc_pcm_null_stream;
+			info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = 0;
+		}
+	}
+
+	return 0;
+}
+
+static inline void alc_shutup(struct hda_codec *codec)
+{
+	snd_hda_shutup_pins(codec);
+}
+
+static void alc_free_kctls(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	if (spec->kctls.list) {
+		struct snd_kcontrol_new *kctl = spec->kctls.list;
+		int i;
+		for (i = 0; i < spec->kctls.used; i++)
+			kfree(kctl[i].name);
+	}
+	snd_array_free(&spec->kctls);
+}
+
+static void alc_free(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	if (!spec)
+		return;
+
+	alc_shutup(codec);
+	alc_free_kctls(codec);
+	kfree(spec);
+	snd_hda_detach_beep_device(codec);
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static void alc_power_eapd(struct hda_codec *codec)
+{
+	/* We currently only handle front, HP */
+	switch (codec->vendor_id) {
+	case 0x10ec0260:
+		set_eapd(codec, 0x0f, 0);
+		set_eapd(codec, 0x10, 0);
+		break;
+	case 0x10ec0262:
+	case 0x10ec0267:
+	case 0x10ec0268:
+	case 0x10ec0269:
+	case 0x10ec0270:
+	case 0x10ec0272:
+	case 0x10ec0660:
+	case 0x10ec0662:
+	case 0x10ec0663:
+	case 0x10ec0862:
+	case 0x10ec0889:
+		set_eapd(codec, 0x14, 0);
+		set_eapd(codec, 0x15, 0);
+		break;
+	}
+}
+
+static int alc_suspend(struct hda_codec *codec, pm_message_t state)
+{
+	struct alc_spec *spec = codec->spec;
+	alc_shutup(codec);
+	if (spec && spec->power_hook)
+		spec->power_hook(codec);
+	return 0;
+}
+#endif
+
+#ifdef SND_HDA_NEEDS_RESUME
+static int alc_resume(struct hda_codec *codec)
+{
+	codec->patch_ops.init(codec);
+	snd_hda_codec_resume_amp(codec);
+	snd_hda_codec_resume_cache(codec);
+	hda_call_check_power_status(codec, 0x01);
+	return 0;
+}
+#endif
+
+/*
+ */
+static struct hda_codec_ops alc_patch_ops = {
+	.build_controls = alc_build_controls,
+	.build_pcms = alc_build_pcms,
+	.init = alc_init,
+	.free = alc_free,
+	.unsol_event = alc_unsol_event,
+#ifdef SND_HDA_NEEDS_RESUME
+	.resume = alc_resume,
+#endif
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	.suspend = alc_suspend,
+	.check_power_status = alc_check_power_status,
+#endif
+	.reboot_notify = alc_shutup,
+};
+
+/* replace the codec chip_name with the given string */
+static int alc_codec_rename(struct hda_codec *codec, const char *name)
+{
+	kfree(codec->chip_name);
+	codec->chip_name = kstrdup(name, GFP_KERNEL);
+	if (!codec->chip_name) {
+		alc_free(codec);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+/*
+ * Test configuration for debugging
+ *
+ * Almost all inputs/outputs are enabled.  I/O pins can be configured via
+ * enum controls.
+ */
+#ifdef CONFIG_SND_DEBUG
+static hda_nid_t alc880_test_dac_nids[4] = {
+	0x02, 0x03, 0x04, 0x05
+};
+
+static struct hda_input_mux alc880_test_capture_source = {
+	.num_items = 7,
+	.items = {
+		{ "In-1", 0x0 },
+		{ "In-2", 0x1 },
+		{ "In-3", 0x2 },
+		{ "In-4", 0x3 },
+		{ "CD", 0x4 },
+		{ "Front", 0x5 },
+		{ "Surround", 0x6 },
+	},
+};
+
+static struct hda_channel_mode alc880_test_modes[4] = {
+	{ 2, NULL },
+	{ 4, NULL },
+	{ 6, NULL },
+	{ 8, NULL },
+};
+
+static int alc_test_pin_ctl_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {
+		"N/A", "Line Out", "HP Out",
+		"In Hi-Z", "In 50%", "In Grd", "In 80%", "In 100%"
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 8;
+	if (uinfo->value.enumerated.item >= 8)
+		uinfo->value.enumerated.item = 7;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int alc_test_pin_ctl_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = (hda_nid_t)kcontrol->private_value;
+	unsigned int pin_ctl, item = 0;
+
+	pin_ctl = snd_hda_codec_read(codec, nid, 0,
+				     AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+	if (pin_ctl & AC_PINCTL_OUT_EN) {
+		if (pin_ctl & AC_PINCTL_HP_EN)
+			item = 2;
+		else
+			item = 1;
+	} else if (pin_ctl & AC_PINCTL_IN_EN) {
+		switch (pin_ctl & AC_PINCTL_VREFEN) {
+		case AC_PINCTL_VREF_HIZ: item = 3; break;
+		case AC_PINCTL_VREF_50:  item = 4; break;
+		case AC_PINCTL_VREF_GRD: item = 5; break;
+		case AC_PINCTL_VREF_80:  item = 6; break;
+		case AC_PINCTL_VREF_100: item = 7; break;
+		}
+	}
+	ucontrol->value.enumerated.item[0] = item;
+	return 0;
+}
+
+static int alc_test_pin_ctl_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = (hda_nid_t)kcontrol->private_value;
+	static unsigned int ctls[] = {
+		0, AC_PINCTL_OUT_EN, AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN,
+		AC_PINCTL_IN_EN | AC_PINCTL_VREF_HIZ,
+		AC_PINCTL_IN_EN | AC_PINCTL_VREF_50,
+		AC_PINCTL_IN_EN | AC_PINCTL_VREF_GRD,
+		AC_PINCTL_IN_EN | AC_PINCTL_VREF_80,
+		AC_PINCTL_IN_EN | AC_PINCTL_VREF_100,
+	};
+	unsigned int old_ctl, new_ctl;
+
+	old_ctl = snd_hda_codec_read(codec, nid, 0,
+				     AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+	new_ctl = ctls[ucontrol->value.enumerated.item[0]];
+	if (old_ctl != new_ctl) {
+		int val;
+		snd_hda_codec_write_cache(codec, nid, 0,
+					  AC_VERB_SET_PIN_WIDGET_CONTROL,
+					  new_ctl);
+		val = ucontrol->value.enumerated.item[0] >= 3 ?
+			HDA_AMP_MUTE : 0;
+		snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0,
+					 HDA_AMP_MUTE, val);
+		return 1;
+	}
+	return 0;
+}
+
+static int alc_test_pin_src_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {
+		"Front", "Surround", "CLFE", "Side"
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item >= 4)
+		uinfo->value.enumerated.item = 3;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int alc_test_pin_src_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = (hda_nid_t)kcontrol->private_value;
+	unsigned int sel;
+
+	sel = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_SEL, 0);
+	ucontrol->value.enumerated.item[0] = sel & 3;
+	return 0;
+}
+
+static int alc_test_pin_src_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = (hda_nid_t)kcontrol->private_value;
+	unsigned int sel;
+
+	sel = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_SEL, 0) & 3;
+	if (ucontrol->value.enumerated.item[0] != sel) {
+		sel = ucontrol->value.enumerated.item[0] & 3;
+		snd_hda_codec_write_cache(codec, nid, 0,
+					  AC_VERB_SET_CONNECT_SEL, sel);
+		return 1;
+	}
+	return 0;
+}
+
+#define PIN_CTL_TEST(xname,nid) {			\
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,	\
+			.name = xname,		       \
+			.subdevice = HDA_SUBDEV_NID_FLAG | nid, \
+			.info = alc_test_pin_ctl_info, \
+			.get = alc_test_pin_ctl_get,   \
+			.put = alc_test_pin_ctl_put,   \
+			.private_value = nid	       \
+			}
+
+#define PIN_SRC_TEST(xname,nid) {			\
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,	\
+			.name = xname,		       \
+			.subdevice = HDA_SUBDEV_NID_FLAG | nid, \
+			.info = alc_test_pin_src_info, \
+			.get = alc_test_pin_src_get,   \
+			.put = alc_test_pin_src_put,   \
+			.private_value = nid	       \
+			}
+
+static struct snd_kcontrol_new alc880_test_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CLFE Playback Volume", 0x0e, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_BIND_MUTE("CLFE Playback Switch", 0x0e, 2, HDA_INPUT),
+	HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT),
+	PIN_CTL_TEST("Front Pin Mode", 0x14),
+	PIN_CTL_TEST("Surround Pin Mode", 0x15),
+	PIN_CTL_TEST("CLFE Pin Mode", 0x16),
+	PIN_CTL_TEST("Side Pin Mode", 0x17),
+	PIN_CTL_TEST("In-1 Pin Mode", 0x18),
+	PIN_CTL_TEST("In-2 Pin Mode", 0x19),
+	PIN_CTL_TEST("In-3 Pin Mode", 0x1a),
+	PIN_CTL_TEST("In-4 Pin Mode", 0x1b),
+	PIN_SRC_TEST("In-1 Pin Source", 0x18),
+	PIN_SRC_TEST("In-2 Pin Source", 0x19),
+	PIN_SRC_TEST("In-3 Pin Source", 0x1a),
+	PIN_SRC_TEST("In-4 Pin Source", 0x1b),
+	HDA_CODEC_VOLUME("In-1 Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("In-1 Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("In-2 Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("In-2 Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("In-3 Playback Volume", 0x0b, 0x2, HDA_INPUT),
+	HDA_CODEC_MUTE("In-3 Playback Switch", 0x0b, 0x2, HDA_INPUT),
+	HDA_CODEC_VOLUME("In-4 Playback Volume", 0x0b, 0x3, HDA_INPUT),
+	HDA_CODEC_MUTE("In-4 Playback Switch", 0x0b, 0x3, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x4, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x4, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb alc880_test_init_verbs[] = {
+	/* Unmute inputs of 0x0c - 0x0f */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Vol output for 0x0c-0x0f */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* Set output pins 0x14-0x17 */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* Unmute output pins 0x14-0x17 */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Set input pins 0x18-0x1c */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	/* Mute input pins 0x18-0x1b */
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* ADC set up */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* Analog input/passthru */
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{ }
+};
+#endif
+
+/*
+ */
+
+static const char *alc880_models[ALC880_MODEL_LAST] = {
+	[ALC880_3ST]		= "3stack",
+	[ALC880_TCL_S700]	= "tcl",
+	[ALC880_3ST_DIG]	= "3stack-digout",
+	[ALC880_CLEVO]		= "clevo",
+	[ALC880_5ST]		= "5stack",
+	[ALC880_5ST_DIG]	= "5stack-digout",
+	[ALC880_W810]		= "w810",
+	[ALC880_Z71V]		= "z71v",
+	[ALC880_6ST]		= "6stack",
+	[ALC880_6ST_DIG]	= "6stack-digout",
+	[ALC880_ASUS]		= "asus",
+	[ALC880_ASUS_W1V]	= "asus-w1v",
+	[ALC880_ASUS_DIG]	= "asus-dig",
+	[ALC880_ASUS_DIG2]	= "asus-dig2",
+	[ALC880_UNIWILL_DIG]	= "uniwill",
+	[ALC880_UNIWILL_P53]	= "uniwill-p53",
+	[ALC880_FUJITSU]	= "fujitsu",
+	[ALC880_F1734]		= "F1734",
+	[ALC880_LG]		= "lg",
+	[ALC880_LG_LW]		= "lg-lw",
+	[ALC880_MEDION_RIM]	= "medion",
+#ifdef CONFIG_SND_DEBUG
+	[ALC880_TEST]		= "test",
+#endif
+	[ALC880_AUTO]		= "auto",
+};
+
+static struct snd_pci_quirk alc880_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1019, 0x0f69, "Coeus G610P", ALC880_W810),
+	SND_PCI_QUIRK(0x1019, 0xa880, "ECS", ALC880_5ST_DIG),
+	SND_PCI_QUIRK(0x1019, 0xa884, "Acer APFV", ALC880_6ST),
+	SND_PCI_QUIRK(0x1025, 0x0070, "ULI", ALC880_3ST_DIG),
+	SND_PCI_QUIRK(0x1025, 0x0077, "ULI", ALC880_6ST_DIG),
+	SND_PCI_QUIRK(0x1025, 0x0078, "ULI", ALC880_6ST_DIG),
+	SND_PCI_QUIRK(0x1025, 0x0087, "ULI", ALC880_6ST_DIG),
+	SND_PCI_QUIRK(0x1025, 0xe309, "ULI", ALC880_3ST_DIG),
+	SND_PCI_QUIRK(0x1025, 0xe310, "ULI", ALC880_3ST),
+	SND_PCI_QUIRK(0x1039, 0x1234, NULL, ALC880_6ST_DIG),
+	SND_PCI_QUIRK(0x103c, 0x2a09, "HP", ALC880_5ST),
+	SND_PCI_QUIRK(0x1043, 0x10b3, "ASUS W1V", ALC880_ASUS_W1V),
+	SND_PCI_QUIRK(0x1043, 0x10c2, "ASUS W6A", ALC880_ASUS_DIG),
+	SND_PCI_QUIRK(0x1043, 0x10c3, "ASUS Wxx", ALC880_ASUS_DIG),
+	SND_PCI_QUIRK(0x1043, 0x1113, "ASUS", ALC880_ASUS_DIG),
+	SND_PCI_QUIRK(0x1043, 0x1123, "ASUS", ALC880_ASUS_DIG),
+	SND_PCI_QUIRK(0x1043, 0x1173, "ASUS", ALC880_ASUS_DIG),
+	SND_PCI_QUIRK(0x1043, 0x1964, "ASUS Z71V", ALC880_Z71V),
+	/* SND_PCI_QUIRK(0x1043, 0x1964, "ASUS", ALC880_ASUS_DIG), */
+	SND_PCI_QUIRK(0x1043, 0x1973, "ASUS", ALC880_ASUS_DIG),
+	SND_PCI_QUIRK(0x1043, 0x19b3, "ASUS", ALC880_ASUS_DIG),
+	SND_PCI_QUIRK(0x1043, 0x814e, "ASUS P5GD1 w/SPDIF", ALC880_6ST_DIG),
+	SND_PCI_QUIRK(0x1043, 0x8181, "ASUS P4GPL", ALC880_ASUS_DIG),
+	SND_PCI_QUIRK(0x1043, 0x8196, "ASUS P5GD1", ALC880_6ST),
+	SND_PCI_QUIRK(0x1043, 0x81b4, "ASUS", ALC880_6ST),
+	SND_PCI_QUIRK_VENDOR(0x1043, "ASUS", ALC880_ASUS), /* default ASUS */
+	SND_PCI_QUIRK(0x104d, 0x81a0, "Sony", ALC880_3ST),
+	SND_PCI_QUIRK(0x104d, 0x81d6, "Sony", ALC880_3ST),
+	SND_PCI_QUIRK(0x107b, 0x3032, "Gateway", ALC880_5ST),
+	SND_PCI_QUIRK(0x107b, 0x3033, "Gateway", ALC880_5ST),
+	SND_PCI_QUIRK(0x107b, 0x4039, "Gateway", ALC880_5ST),
+	SND_PCI_QUIRK(0x1297, 0xc790, "Shuttle ST20G5", ALC880_6ST_DIG),
+	SND_PCI_QUIRK(0x1458, 0xa102, "Gigabyte K8", ALC880_6ST_DIG),
+	SND_PCI_QUIRK(0x1462, 0x1150, "MSI", ALC880_6ST_DIG),
+	SND_PCI_QUIRK(0x1509, 0x925d, "FIC P4M", ALC880_6ST_DIG),
+	SND_PCI_QUIRK(0x1558, 0x0520, "Clevo m520G", ALC880_CLEVO),
+	SND_PCI_QUIRK(0x1558, 0x0660, "Clevo m655n", ALC880_CLEVO),
+	SND_PCI_QUIRK(0x1558, 0x5401, "ASUS", ALC880_ASUS_DIG2),
+	SND_PCI_QUIRK(0x1565, 0x8202, "Biostar", ALC880_5ST_DIG),
+	SND_PCI_QUIRK(0x1584, 0x9050, "Uniwill", ALC880_UNIWILL_DIG),
+	SND_PCI_QUIRK(0x1584, 0x9054, "Uniwlll", ALC880_F1734),
+	SND_PCI_QUIRK(0x1584, 0x9070, "Uniwill", ALC880_UNIWILL),
+	SND_PCI_QUIRK(0x1584, 0x9077, "Uniwill P53", ALC880_UNIWILL_P53),
+	SND_PCI_QUIRK(0x161f, 0x203d, "W810", ALC880_W810),
+	SND_PCI_QUIRK(0x161f, 0x205d, "Medion Rim 2150", ALC880_MEDION_RIM),
+	SND_PCI_QUIRK(0x1695, 0x400d, "EPoX", ALC880_5ST_DIG),
+	SND_PCI_QUIRK(0x1695, 0x4012, "EPox EP-5LDA", ALC880_5ST_DIG),
+	SND_PCI_QUIRK(0x1734, 0x107c, "FSC F1734", ALC880_F1734),
+	SND_PCI_QUIRK(0x1734, 0x1094, "FSC Amilo M1451G", ALC880_FUJITSU),
+	SND_PCI_QUIRK(0x1734, 0x10ac, "FSC AMILO Xi 1526", ALC880_F1734),
+	SND_PCI_QUIRK(0x1734, 0x10b0, "Fujitsu", ALC880_FUJITSU),
+	SND_PCI_QUIRK(0x1854, 0x0018, "LG LW20", ALC880_LG_LW),
+	SND_PCI_QUIRK(0x1854, 0x003b, "LG", ALC880_LG),
+	SND_PCI_QUIRK(0x1854, 0x005f, "LG P1 Express", ALC880_LG),
+	SND_PCI_QUIRK(0x1854, 0x0068, "LG w1", ALC880_LG),
+	SND_PCI_QUIRK(0x1854, 0x0077, "LG LW25", ALC880_LG_LW),
+	SND_PCI_QUIRK(0x19db, 0x4188, "TCL S700", ALC880_TCL_S700),
+	SND_PCI_QUIRK(0x2668, 0x8086, NULL, ALC880_6ST_DIG), /* broken BIOS */
+	SND_PCI_QUIRK(0x8086, 0x2668, NULL, ALC880_6ST_DIG),
+	SND_PCI_QUIRK(0x8086, 0xa100, "Intel mobo", ALC880_5ST_DIG),
+	SND_PCI_QUIRK(0x8086, 0xd400, "Intel mobo", ALC880_5ST_DIG),
+	SND_PCI_QUIRK(0x8086, 0xd401, "Intel mobo", ALC880_5ST_DIG),
+	SND_PCI_QUIRK(0x8086, 0xd402, "Intel mobo", ALC880_3ST_DIG),
+	SND_PCI_QUIRK(0x8086, 0xe224, "Intel mobo", ALC880_5ST_DIG),
+	SND_PCI_QUIRK(0x8086, 0xe305, "Intel mobo", ALC880_3ST_DIG),
+	SND_PCI_QUIRK(0x8086, 0xe308, "Intel mobo", ALC880_3ST_DIG),
+	SND_PCI_QUIRK(0x8086, 0xe400, "Intel mobo", ALC880_5ST_DIG),
+	SND_PCI_QUIRK(0x8086, 0xe401, "Intel mobo", ALC880_5ST_DIG),
+	SND_PCI_QUIRK(0x8086, 0xe402, "Intel mobo", ALC880_5ST_DIG),
+	/* default Intel */
+	SND_PCI_QUIRK_VENDOR(0x8086, "Intel mobo", ALC880_3ST),
+	SND_PCI_QUIRK(0xa0a0, 0x0560, "AOpen i915GMm-HFS", ALC880_5ST_DIG),
+	SND_PCI_QUIRK(0xe803, 0x1019, NULL, ALC880_6ST_DIG),
+	{}
+};
+
+/*
+ * ALC880 codec presets
+ */
+static struct alc_config_preset alc880_presets[] = {
+	[ALC880_3ST] = {
+		.mixers = { alc880_three_stack_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_3stack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_dac_nids),
+		.dac_nids = alc880_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc880_threestack_modes),
+		.channel_mode = alc880_threestack_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_3ST_DIG] = {
+		.mixers = { alc880_three_stack_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_3stack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_dac_nids),
+		.dac_nids = alc880_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_threestack_modes),
+		.channel_mode = alc880_threestack_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_TCL_S700] = {
+		.mixers = { alc880_tcl_s700_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_tcl_S700_init_verbs,
+				alc880_gpio2_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_dac_nids),
+		.dac_nids = alc880_dac_nids,
+		.adc_nids = alc880_adc_nids_alt, /* FIXME: correct? */
+		.num_adc_nids = 1, /* single ADC */
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes),
+		.channel_mode = alc880_2_jack_modes,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_5ST] = {
+		.mixers = { alc880_three_stack_mixer,
+			    alc880_five_stack_mixer},
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_5stack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_dac_nids),
+		.dac_nids = alc880_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc880_fivestack_modes),
+		.channel_mode = alc880_fivestack_modes,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_5ST_DIG] = {
+		.mixers = { alc880_three_stack_mixer,
+			    alc880_five_stack_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_5stack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_dac_nids),
+		.dac_nids = alc880_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_fivestack_modes),
+		.channel_mode = alc880_fivestack_modes,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_6ST] = {
+		.mixers = { alc880_six_stack_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_6stack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_6st_dac_nids),
+		.dac_nids = alc880_6st_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc880_sixstack_modes),
+		.channel_mode = alc880_sixstack_modes,
+		.input_mux = &alc880_6stack_capture_source,
+	},
+	[ALC880_6ST_DIG] = {
+		.mixers = { alc880_six_stack_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_6stack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_6st_dac_nids),
+		.dac_nids = alc880_6st_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_sixstack_modes),
+		.channel_mode = alc880_sixstack_modes,
+		.input_mux = &alc880_6stack_capture_source,
+	},
+	[ALC880_W810] = {
+		.mixers = { alc880_w810_base_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_w810_init_verbs,
+				alc880_gpio2_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_w810_dac_nids),
+		.dac_nids = alc880_w810_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_w810_modes),
+		.channel_mode = alc880_w810_modes,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_Z71V] = {
+		.mixers = { alc880_z71v_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_z71v_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_z71v_dac_nids),
+		.dac_nids = alc880_z71v_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes),
+		.channel_mode = alc880_2_jack_modes,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_F1734] = {
+		.mixers = { alc880_f1734_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_f1734_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_f1734_dac_nids),
+		.dac_nids = alc880_f1734_dac_nids,
+		.hp_nid = 0x02,
+		.num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes),
+		.channel_mode = alc880_2_jack_modes,
+		.input_mux = &alc880_f1734_capture_source,
+		.unsol_event = alc880_uniwill_p53_unsol_event,
+		.setup = alc880_uniwill_p53_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC880_ASUS] = {
+		.mixers = { alc880_asus_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_asus_init_verbs,
+				alc880_gpio1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_asus_dac_nids),
+		.dac_nids = alc880_asus_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc880_asus_modes),
+		.channel_mode = alc880_asus_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_ASUS_DIG] = {
+		.mixers = { alc880_asus_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_asus_init_verbs,
+				alc880_gpio1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_asus_dac_nids),
+		.dac_nids = alc880_asus_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_asus_modes),
+		.channel_mode = alc880_asus_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_ASUS_DIG2] = {
+		.mixers = { alc880_asus_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_asus_init_verbs,
+				alc880_gpio2_init_verbs }, /* use GPIO2 */
+		.num_dacs = ARRAY_SIZE(alc880_asus_dac_nids),
+		.dac_nids = alc880_asus_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_asus_modes),
+		.channel_mode = alc880_asus_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_ASUS_W1V] = {
+		.mixers = { alc880_asus_mixer, alc880_asus_w1v_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_asus_init_verbs,
+				alc880_gpio1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_asus_dac_nids),
+		.dac_nids = alc880_asus_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_asus_modes),
+		.channel_mode = alc880_asus_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_UNIWILL_DIG] = {
+		.mixers = { alc880_asus_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_asus_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_asus_dac_nids),
+		.dac_nids = alc880_asus_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_asus_modes),
+		.channel_mode = alc880_asus_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_UNIWILL] = {
+		.mixers = { alc880_uniwill_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_uniwill_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_asus_dac_nids),
+		.dac_nids = alc880_asus_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_threestack_modes),
+		.channel_mode = alc880_threestack_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc880_capture_source,
+		.unsol_event = alc880_uniwill_unsol_event,
+		.setup = alc880_uniwill_setup,
+		.init_hook = alc880_uniwill_init_hook,
+	},
+	[ALC880_UNIWILL_P53] = {
+		.mixers = { alc880_uniwill_p53_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_uniwill_p53_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_asus_dac_nids),
+		.dac_nids = alc880_asus_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc880_w810_modes),
+		.channel_mode = alc880_threestack_modes,
+		.input_mux = &alc880_capture_source,
+		.unsol_event = alc880_uniwill_p53_unsol_event,
+		.setup = alc880_uniwill_p53_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC880_FUJITSU] = {
+		.mixers = { alc880_fujitsu_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_uniwill_p53_init_verbs,
+	       			alc880_beep_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_dac_nids),
+		.dac_nids = alc880_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes),
+		.channel_mode = alc880_2_jack_modes,
+		.input_mux = &alc880_capture_source,
+		.unsol_event = alc880_uniwill_p53_unsol_event,
+		.setup = alc880_uniwill_p53_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC880_CLEVO] = {
+		.mixers = { alc880_three_stack_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_pin_clevo_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_dac_nids),
+		.dac_nids = alc880_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc880_threestack_modes),
+		.channel_mode = alc880_threestack_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc880_capture_source,
+	},
+	[ALC880_LG] = {
+		.mixers = { alc880_lg_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_lg_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_lg_dac_nids),
+		.dac_nids = alc880_lg_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_lg_ch_modes),
+		.channel_mode = alc880_lg_ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc880_lg_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc880_lg_setup,
+		.init_hook = alc_automute_amp,
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+		.loopbacks = alc880_lg_loopbacks,
+#endif
+	},
+	[ALC880_LG_LW] = {
+		.mixers = { alc880_lg_lw_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_lg_lw_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_dac_nids),
+		.dac_nids = alc880_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_lg_lw_modes),
+		.channel_mode = alc880_lg_lw_modes,
+		.input_mux = &alc880_lg_lw_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc880_lg_lw_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC880_MEDION_RIM] = {
+		.mixers = { alc880_medion_rim_mixer },
+		.init_verbs = { alc880_volume_init_verbs,
+				alc880_medion_rim_init_verbs,
+				alc_gpio2_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_dac_nids),
+		.dac_nids = alc880_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes),
+		.channel_mode = alc880_2_jack_modes,
+		.input_mux = &alc880_medion_rim_capture_source,
+		.unsol_event = alc880_medion_rim_unsol_event,
+		.setup = alc880_medion_rim_setup,
+		.init_hook = alc880_medion_rim_automute,
+	},
+#ifdef CONFIG_SND_DEBUG
+	[ALC880_TEST] = {
+		.mixers = { alc880_test_mixer },
+		.init_verbs = { alc880_test_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc880_test_dac_nids),
+		.dac_nids = alc880_test_dac_nids,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_test_modes),
+		.channel_mode = alc880_test_modes,
+		.input_mux = &alc880_test_capture_source,
+	},
+#endif
+};
+
+/*
+ * Automatic parse of I/O pins from the BIOS configuration
+ */
+
+enum {
+	ALC_CTL_WIDGET_VOL,
+	ALC_CTL_WIDGET_MUTE,
+	ALC_CTL_BIND_MUTE,
+};
+static struct snd_kcontrol_new alc880_control_templates[] = {
+	HDA_CODEC_VOLUME(NULL, 0, 0, 0),
+	HDA_CODEC_MUTE(NULL, 0, 0, 0),
+	HDA_BIND_MUTE(NULL, 0, 0, 0),
+};
+
+/* add dynamic controls */
+static int add_control(struct alc_spec *spec, int type, const char *name,
+		       int cidx, unsigned long val)
+{
+	struct snd_kcontrol_new *knew;
+
+	snd_array_init(&spec->kctls, sizeof(*knew), 32);
+	knew = snd_array_new(&spec->kctls);
+	if (!knew)
+		return -ENOMEM;
+	*knew = alc880_control_templates[type];
+	knew->name = kstrdup(name, GFP_KERNEL);
+	if (!knew->name)
+		return -ENOMEM;
+	knew->index = cidx;
+	if (get_amp_nid_(val))
+		knew->subdevice = HDA_SUBDEV_AMP_FLAG;
+	knew->private_value = val;
+	return 0;
+}
+
+static int add_control_with_pfx(struct alc_spec *spec, int type,
+				const char *pfx, const char *dir,
+				const char *sfx, int cidx, unsigned long val)
+{
+	char name[32];
+	snprintf(name, sizeof(name), "%s %s %s", pfx, dir, sfx);
+	return add_control(spec, type, name, cidx, val);
+}
+
+#define add_pb_vol_ctrl(spec, type, pfx, val)			\
+	add_control_with_pfx(spec, type, pfx, "Playback", "Volume", 0, val)
+#define add_pb_sw_ctrl(spec, type, pfx, val)			\
+	add_control_with_pfx(spec, type, pfx, "Playback", "Switch", 0, val)
+#define __add_pb_vol_ctrl(spec, type, pfx, cidx, val)			\
+	add_control_with_pfx(spec, type, pfx, "Playback", "Volume", cidx, val)
+#define __add_pb_sw_ctrl(spec, type, pfx, cidx, val)			\
+	add_control_with_pfx(spec, type, pfx, "Playback", "Switch", cidx, val)
+
+#define alc880_is_fixed_pin(nid)	((nid) >= 0x14 && (nid) <= 0x17)
+#define alc880_fixed_pin_idx(nid)	((nid) - 0x14)
+#define alc880_is_multi_pin(nid)	((nid) >= 0x18)
+#define alc880_multi_pin_idx(nid)	((nid) - 0x18)
+#define alc880_idx_to_dac(nid)		((nid) + 0x02)
+#define alc880_dac_to_idx(nid)		((nid) - 0x02)
+#define alc880_idx_to_mixer(nid)	((nid) + 0x0c)
+#define alc880_idx_to_selector(nid)	((nid) + 0x10)
+#define ALC880_PIN_CD_NID		0x1c
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int alc880_auto_fill_dac_nids(struct alc_spec *spec,
+				     const struct auto_pin_cfg *cfg)
+{
+	hda_nid_t nid;
+	int assigned[4];
+	int i, j;
+
+	memset(assigned, 0, sizeof(assigned));
+	spec->multiout.dac_nids = spec->private_dac_nids;
+
+	/* check the pins hardwired to audio widget */
+	for (i = 0; i < cfg->line_outs; i++) {
+		nid = cfg->line_out_pins[i];
+		if (alc880_is_fixed_pin(nid)) {
+			int idx = alc880_fixed_pin_idx(nid);
+			spec->multiout.dac_nids[i] = alc880_idx_to_dac(idx);
+			assigned[idx] = 1;
+		}
+	}
+	/* left pins can be connect to any audio widget */
+	for (i = 0; i < cfg->line_outs; i++) {
+		nid = cfg->line_out_pins[i];
+		if (alc880_is_fixed_pin(nid))
+			continue;
+		/* search for an empty channel */
+		for (j = 0; j < cfg->line_outs; j++) {
+			if (!assigned[j]) {
+				spec->multiout.dac_nids[i] =
+					alc880_idx_to_dac(j);
+				assigned[j] = 1;
+				break;
+			}
+		}
+	}
+	spec->multiout.num_dacs = cfg->line_outs;
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int alc880_auto_create_multi_out_ctls(struct alc_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	static const char *chname[4] = {
+		"Front", "Surround", NULL /*CLFE*/, "Side"
+	};
+	hda_nid_t nid;
+	int i, err;
+
+	for (i = 0; i < cfg->line_outs; i++) {
+		if (!spec->multiout.dac_nids[i])
+			continue;
+		nid = alc880_idx_to_mixer(alc880_dac_to_idx(spec->multiout.dac_nids[i]));
+		if (i == 2) {
+			/* Center/LFE */
+			err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL,
+					      "Center",
+					  HDA_COMPOSE_AMP_VAL(nid, 1, 0,
+							      HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL,
+					      "LFE",
+					  HDA_COMPOSE_AMP_VAL(nid, 2, 0,
+							      HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = add_pb_sw_ctrl(spec, ALC_CTL_BIND_MUTE,
+					     "Center",
+					  HDA_COMPOSE_AMP_VAL(nid, 1, 2,
+							      HDA_INPUT));
+			if (err < 0)
+				return err;
+			err = add_pb_sw_ctrl(spec, ALC_CTL_BIND_MUTE,
+					     "LFE",
+					  HDA_COMPOSE_AMP_VAL(nid, 2, 2,
+							      HDA_INPUT));
+			if (err < 0)
+				return err;
+		} else {
+			const char *pfx;
+			if (cfg->line_outs == 1 &&
+			    cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
+				pfx = "Speaker";
+			else
+				pfx = chname[i];
+			err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL, pfx,
+					  HDA_COMPOSE_AMP_VAL(nid, 3, 0,
+							      HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = add_pb_sw_ctrl(spec, ALC_CTL_BIND_MUTE, pfx,
+					  HDA_COMPOSE_AMP_VAL(nid, 3, 2,
+							      HDA_INPUT));
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+/* add playback controls for speaker and HP outputs */
+static int alc880_auto_create_extra_out(struct alc_spec *spec, hda_nid_t pin,
+					const char *pfx)
+{
+	hda_nid_t nid;
+	int err;
+
+	if (!pin)
+		return 0;
+
+	if (alc880_is_fixed_pin(pin)) {
+		nid = alc880_idx_to_dac(alc880_fixed_pin_idx(pin));
+		/* specify the DAC as the extra output */
+		if (!spec->multiout.hp_nid)
+			spec->multiout.hp_nid = nid;
+		else
+			spec->multiout.extra_out_nid[0] = nid;
+		/* control HP volume/switch on the output mixer amp */
+		nid = alc880_idx_to_mixer(alc880_fixed_pin_idx(pin));
+		err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL, pfx,
+				  HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT));
+		if (err < 0)
+			return err;
+		err = add_pb_sw_ctrl(spec, ALC_CTL_BIND_MUTE, pfx,
+				  HDA_COMPOSE_AMP_VAL(nid, 3, 2, HDA_INPUT));
+		if (err < 0)
+			return err;
+	} else if (alc880_is_multi_pin(pin)) {
+		/* set manual connection */
+		/* we have only a switch on HP-out PIN */
+		err = add_pb_sw_ctrl(spec, ALC_CTL_WIDGET_MUTE, pfx,
+				  HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+/* create input playback/capture controls for the given pin */
+static int new_analog_input(struct alc_spec *spec, hda_nid_t pin,
+			    const char *ctlname, int ctlidx,
+			    int idx, hda_nid_t mix_nid)
+{
+	int err;
+
+	err = __add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL, ctlname, ctlidx,
+			  HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT));
+	if (err < 0)
+		return err;
+	err = __add_pb_sw_ctrl(spec, ALC_CTL_WIDGET_MUTE, ctlname, ctlidx,
+			  HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int alc_is_input_pin(struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int pincap = snd_hda_query_pin_caps(codec, nid);
+	return (pincap & AC_PINCAP_IN) != 0;
+}
+
+/* create playback/capture controls for input pins */
+static int alc_auto_create_input_ctls(struct hda_codec *codec,
+				      const struct auto_pin_cfg *cfg,
+				      hda_nid_t mixer,
+				      hda_nid_t cap1, hda_nid_t cap2)
+{
+	struct alc_spec *spec = codec->spec;
+	struct hda_input_mux *imux = &spec->private_imux[0];
+	int i, err, idx, type, type_idx = 0;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t pin;
+		const char *label;
+
+		pin = cfg->inputs[i].pin;
+		if (!alc_is_input_pin(codec, pin))
+			continue;
+
+		type = cfg->inputs[i].type;
+		if (i > 0 && type == cfg->inputs[i - 1].type)
+			type_idx++;
+		else
+			type_idx = 0;
+		label = hda_get_autocfg_input_label(codec, cfg, i);
+		if (mixer) {
+			idx = get_connection_index(codec, mixer, pin);
+			if (idx >= 0) {
+				err = new_analog_input(spec, pin,
+						       label, type_idx,
+						       idx, mixer);
+				if (err < 0)
+					return err;
+			}
+		}
+
+		if (!cap1)
+			continue;
+		idx = get_connection_index(codec, cap1, pin);
+		if (idx < 0 && cap2)
+			idx = get_connection_index(codec, cap2, pin);
+		if (idx >= 0)
+			snd_hda_add_imux_item(imux, label, idx, NULL);
+	}
+	return 0;
+}
+
+static int alc880_auto_create_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	return alc_auto_create_input_ctls(codec, cfg, 0x0b, 0x08, 0x09);
+}
+
+static void alc_set_pin_output(struct hda_codec *codec, hda_nid_t nid,
+			       unsigned int pin_type)
+{
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    pin_type);
+	/* unmute pin */
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
+			    AMP_OUT_UNMUTE);
+}
+
+static void alc880_auto_set_output_and_unmute(struct hda_codec *codec,
+					      hda_nid_t nid, int pin_type,
+					      int dac_idx)
+{
+	alc_set_pin_output(codec, nid, pin_type);
+	/* need the manual connection? */
+	if (alc880_is_multi_pin(nid)) {
+		struct alc_spec *spec = codec->spec;
+		int idx = alc880_multi_pin_idx(nid);
+		snd_hda_codec_write(codec, alc880_idx_to_selector(idx), 0,
+				    AC_VERB_SET_CONNECT_SEL,
+				    alc880_dac_to_idx(spec->multiout.dac_nids[dac_idx]));
+	}
+}
+
+static int get_pin_type(int line_out_type)
+{
+	if (line_out_type == AUTO_PIN_HP_OUT)
+		return PIN_HP;
+	else
+		return PIN_OUT;
+}
+
+static void alc880_auto_init_multi_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->autocfg.line_outs; i++) {
+		hda_nid_t nid = spec->autocfg.line_out_pins[i];
+		int pin_type = get_pin_type(spec->autocfg.line_out_type);
+		alc880_auto_set_output_and_unmute(codec, nid, pin_type, i);
+	}
+}
+
+static void alc880_auto_init_extra_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t pin;
+
+	pin = spec->autocfg.speaker_pins[0];
+	if (pin) /* connect to front */
+		alc880_auto_set_output_and_unmute(codec, pin, PIN_OUT, 0);
+	pin = spec->autocfg.hp_pins[0];
+	if (pin) /* connect to front */
+		alc880_auto_set_output_and_unmute(codec, pin, PIN_HP, 0);
+}
+
+static void alc880_auto_init_analog_input(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		if (alc_is_input_pin(codec, nid)) {
+			alc_set_input_pin(codec, nid, cfg->inputs[i].type);
+			if (nid != ALC880_PIN_CD_NID &&
+			    (get_wcaps(codec, nid) & AC_WCAP_OUT_AMP))
+				snd_hda_codec_write(codec, nid, 0,
+						    AC_VERB_SET_AMP_GAIN_MUTE,
+						    AMP_OUT_MUTE);
+		}
+	}
+}
+
+static void alc880_auto_init_input_src(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int c;
+
+	for (c = 0; c < spec->num_adc_nids; c++) {
+		unsigned int mux_idx;
+		const struct hda_input_mux *imux;
+		mux_idx = c >= spec->num_mux_defs ? 0 : c;
+		imux = &spec->input_mux[mux_idx];
+		if (!imux->num_items && mux_idx > 0)
+			imux = &spec->input_mux[0];
+		if (imux)
+			snd_hda_codec_write(codec, spec->adc_nids[c], 0,
+					    AC_VERB_SET_CONNECT_SEL,
+					    imux->items[0].index);
+	}
+}
+
+/* parse the BIOS configuration and set up the alc_spec */
+/* return 1 if successful, 0 if the proper config is not found,
+ * or a negative error code
+ */
+static int alc880_parse_auto_config(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int err;
+	static hda_nid_t alc880_ignore[] = { 0x1d, 0 };
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg,
+					   alc880_ignore);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs)
+		return 0; /* can't find valid BIOS pin config */
+
+	err = alc880_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc880_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc880_auto_create_extra_out(spec,
+					   spec->autocfg.speaker_pins[0],
+					   "Speaker");
+	if (err < 0)
+		return err;
+	err = alc880_auto_create_extra_out(spec, spec->autocfg.hp_pins[0],
+					   "Headphone");
+	if (err < 0)
+		return err;
+	err = alc880_auto_create_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	alc_auto_parse_digital(codec);
+
+	if (spec->kctls.list)
+		add_mixer(spec, spec->kctls.list);
+
+	add_verb(spec, alc880_volume_init_verbs);
+
+	spec->num_mux_defs = 1;
+	spec->input_mux = &spec->private_imux[0];
+
+	alc_ssid_check(codec, 0x15, 0x1b, 0x14, 0);
+
+	return 1;
+}
+
+/* additional initialization for auto-configuration model */
+static void alc880_auto_init(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc880_auto_init_multi_out(codec);
+	alc880_auto_init_extra_out(codec);
+	alc880_auto_init_analog_input(codec);
+	alc880_auto_init_input_src(codec);
+	alc_auto_init_digital(codec);
+	if (spec->unsol_event)
+		alc_inithook(codec);
+}
+
+/* check the ADC/MUX contains all input pins; some ADC/MUX contains only
+ * one of two digital mic pins, e.g. on ALC272
+ */
+static void fixup_automic_adc(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->num_adc_nids; i++) {
+		hda_nid_t cap = spec->capsrc_nids ?
+			spec->capsrc_nids[i] : spec->adc_nids[i];
+		int iidx, eidx;
+
+		iidx = get_connection_index(codec, cap, spec->int_mic.pin);
+		if (iidx < 0)
+			continue;
+		eidx = get_connection_index(codec, cap, spec->ext_mic.pin);
+		if (eidx < 0)
+			continue;
+		spec->int_mic.mux_idx = iidx;
+		spec->ext_mic.mux_idx = eidx;
+		if (spec->capsrc_nids)
+			spec->capsrc_nids += i;
+		spec->adc_nids += i;
+		spec->num_adc_nids = 1;
+		return;
+	}
+	snd_printd(KERN_INFO "hda_codec: %s: "
+		   "No ADC/MUX containing both 0x%x and 0x%x pins\n",
+		   codec->chip_name, spec->int_mic.pin, spec->ext_mic.pin);
+	spec->auto_mic = 0; /* disable auto-mic to be sure */
+}
+
+/* select or unmute the given capsrc route */
+static void select_or_unmute_capsrc(struct hda_codec *codec, hda_nid_t cap,
+				    int idx)
+{
+	if (get_wcaps_type(get_wcaps(codec, cap)) == AC_WID_AUD_MIX) {
+		snd_hda_codec_amp_stereo(codec, cap, HDA_INPUT, idx,
+					 HDA_AMP_MUTE, 0);
+	} else {
+		snd_hda_codec_write_cache(codec, cap, 0,
+					  AC_VERB_SET_CONNECT_SEL, idx);
+	}
+}
+
+/* set the default connection to that pin */
+static int init_capsrc_for_pin(struct hda_codec *codec, hda_nid_t pin)
+{
+	struct alc_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->num_adc_nids; i++) {
+		hda_nid_t cap = spec->capsrc_nids ?
+			spec->capsrc_nids[i] : spec->adc_nids[i];
+		int idx;
+
+		idx = get_connection_index(codec, cap, pin);
+		if (idx < 0)
+			continue;
+		select_or_unmute_capsrc(codec, cap, idx);
+		return i; /* return the found index */
+	}
+	return -1; /* not found */
+}
+
+/* choose the ADC/MUX containing the input pin and initialize the setup */
+static void fixup_single_adc(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	/* search for the input pin; there must be only one */
+	if (cfg->num_inputs != 1)
+		return;
+	i = init_capsrc_for_pin(codec, cfg->inputs[0].pin);
+	if (i >= 0) {
+		/* use only this ADC */
+		if (spec->capsrc_nids)
+			spec->capsrc_nids += i;
+		spec->adc_nids += i;
+		spec->num_adc_nids = 1;
+	}
+}
+
+/* initialize dual adcs */
+static void fixup_dual_adc_switch(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	init_capsrc_for_pin(codec, spec->ext_mic.pin);
+	init_capsrc_for_pin(codec, spec->int_mic.pin);
+}
+
+static void set_capture_mixer(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	static struct snd_kcontrol_new *caps[2][3] = {
+		{ alc_capture_mixer_nosrc1,
+		  alc_capture_mixer_nosrc2,
+		  alc_capture_mixer_nosrc3 },
+		{ alc_capture_mixer1,
+		  alc_capture_mixer2,
+		  alc_capture_mixer3 },
+	};
+	if (spec->num_adc_nids > 0 && spec->num_adc_nids <= 3) {
+		int mux = 0;
+		int num_adcs = spec->num_adc_nids;
+		if (spec->dual_adc_switch)
+			fixup_dual_adc_switch(codec);
+		else if (spec->auto_mic)
+			fixup_automic_adc(codec);
+		else if (spec->input_mux) {
+			if (spec->input_mux->num_items > 1)
+				mux = 1;
+			else if (spec->input_mux->num_items == 1)
+				fixup_single_adc(codec);
+		}
+		if (spec->dual_adc_switch)
+			num_adcs = 1;
+		spec->cap_mixer = caps[mux][num_adcs - 1];
+	}
+}
+
+/* fill adc_nids (and capsrc_nids) containing all active input pins */
+static void fillup_priv_adc_nids(struct hda_codec *codec, hda_nid_t *nids,
+				 int num_nids)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int n;
+	hda_nid_t fallback_adc = 0, fallback_cap = 0;
+
+	for (n = 0; n < num_nids; n++) {
+		hda_nid_t adc, cap;
+		hda_nid_t conn[HDA_MAX_NUM_INPUTS];
+		int nconns, i, j;
+
+		adc = nids[n];
+		if (get_wcaps_type(get_wcaps(codec, adc)) != AC_WID_AUD_IN)
+			continue;
+		cap = adc;
+		nconns = snd_hda_get_connections(codec, cap, conn,
+						 ARRAY_SIZE(conn));
+		if (nconns == 1) {
+			cap = conn[0];
+			nconns = snd_hda_get_connections(codec, cap, conn,
+							 ARRAY_SIZE(conn));
+		}
+		if (nconns <= 0)
+			continue;
+		if (!fallback_adc) {
+			fallback_adc = adc;
+			fallback_cap = cap;
+		}
+		for (i = 0; i < cfg->num_inputs; i++) {
+			hda_nid_t nid = cfg->inputs[i].pin;
+			for (j = 0; j < nconns; j++) {
+				if (conn[j] == nid)
+					break;
+			}
+			if (j >= nconns)
+				break;
+		}
+		if (i >= cfg->num_inputs) {
+			int num_adcs = spec->num_adc_nids;
+			spec->private_adc_nids[num_adcs] = adc;
+			spec->private_capsrc_nids[num_adcs] = cap;
+			spec->num_adc_nids++;
+			spec->adc_nids = spec->private_adc_nids;
+			if (adc != cap)
+				spec->capsrc_nids = spec->private_capsrc_nids;
+		}
+	}
+	if (!spec->num_adc_nids) {
+		printk(KERN_WARNING "hda_codec: %s: no valid ADC found;"
+		       " using fallback 0x%x\n",
+		       codec->chip_name, fallback_adc);
+		spec->private_adc_nids[0] = fallback_adc;
+		spec->adc_nids = spec->private_adc_nids;
+		if (fallback_adc != fallback_cap) {
+			spec->private_capsrc_nids[0] = fallback_cap;
+			spec->capsrc_nids = spec->private_adc_nids;
+		}
+	}
+}
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+#define set_beep_amp(spec, nid, idx, dir) \
+	((spec)->beep_amp = HDA_COMPOSE_AMP_VAL(nid, 3, idx, dir))
+
+static struct snd_pci_quirk beep_white_list[] = {
+	SND_PCI_QUIRK(0x1043, 0x829f, "ASUS", 1),
+	SND_PCI_QUIRK(0x1043, 0x83ce, "EeePC", 1),
+	SND_PCI_QUIRK(0x8086, 0xd613, "Intel", 1),
+	{}
+};
+
+static inline int has_cdefine_beep(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	const struct snd_pci_quirk *q;
+	q = snd_pci_quirk_lookup(codec->bus->pci, beep_white_list);
+	if (q)
+		return q->value;
+	return spec->cdefine.enable_pcbeep;
+}
+#else
+#define set_beep_amp(spec, nid, idx, dir) /* NOP */
+#define has_cdefine_beep(codec)		0
+#endif
+
+/*
+ * OK, here we have finally the patch for ALC880
+ */
+
+static int patch_alc880(struct hda_codec *codec)
+{
+	struct alc_spec *spec;
+	int board_config;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	board_config = snd_hda_check_board_config(codec, ALC880_MODEL_LAST,
+						  alc880_models,
+						  alc880_cfg_tbl);
+	if (board_config < 0) {
+		printk(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+		       codec->chip_name);
+		board_config = ALC880_AUTO;
+	}
+
+	if (board_config == ALC880_AUTO) {
+		/* automatic parse from the BIOS config */
+		err = alc880_parse_auto_config(codec);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		} else if (!err) {
+			printk(KERN_INFO
+			       "hda_codec: Cannot set up configuration "
+			       "from BIOS.  Using 3-stack mode...\n");
+			board_config = ALC880_3ST;
+		}
+	}
+
+	err = snd_hda_attach_beep_device(codec, 0x1);
+	if (err < 0) {
+		alc_free(codec);
+		return err;
+	}
+
+	if (board_config != ALC880_AUTO)
+		setup_preset(codec, &alc880_presets[board_config]);
+
+	spec->stream_analog_playback = &alc880_pcm_analog_playback;
+	spec->stream_analog_capture = &alc880_pcm_analog_capture;
+	spec->stream_analog_alt_capture = &alc880_pcm_analog_alt_capture;
+
+	spec->stream_digital_playback = &alc880_pcm_digital_playback;
+	spec->stream_digital_capture = &alc880_pcm_digital_capture;
+
+	if (!spec->adc_nids && spec->input_mux) {
+		/* check whether NID 0x07 is valid */
+		unsigned int wcap = get_wcaps(codec, alc880_adc_nids[0]);
+		/* get type */
+		wcap = get_wcaps_type(wcap);
+		if (wcap != AC_WID_AUD_IN) {
+			spec->adc_nids = alc880_adc_nids_alt;
+			spec->num_adc_nids = ARRAY_SIZE(alc880_adc_nids_alt);
+		} else {
+			spec->adc_nids = alc880_adc_nids;
+			spec->num_adc_nids = ARRAY_SIZE(alc880_adc_nids);
+		}
+	}
+	set_capture_mixer(codec);
+	set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT);
+
+	spec->vmaster_nid = 0x0c;
+
+	codec->patch_ops = alc_patch_ops;
+	if (board_config == ALC880_AUTO)
+		spec->init_hook = alc880_auto_init;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	if (!spec->loopback.amplist)
+		spec->loopback.amplist = alc880_loopbacks;
+#endif
+
+	return 0;
+}
+
+
+/*
+ * ALC260 support
+ */
+
+static hda_nid_t alc260_dac_nids[1] = {
+	/* front */
+	0x02,
+};
+
+static hda_nid_t alc260_adc_nids[1] = {
+	/* ADC0 */
+	0x04,
+};
+
+static hda_nid_t alc260_adc_nids_alt[1] = {
+	/* ADC1 */
+	0x05,
+};
+
+/* NIDs used when simultaneous access to both ADCs makes sense.  Note that
+ * alc260_capture_mixer assumes ADC0 (nid 0x04) is the first ADC.
+ */
+static hda_nid_t alc260_dual_adc_nids[2] = {
+	/* ADC0, ADC1 */
+	0x04, 0x05
+};
+
+#define ALC260_DIGOUT_NID	0x03
+#define ALC260_DIGIN_NID	0x06
+
+static struct hda_input_mux alc260_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x1 },
+		{ "Line", 0x2 },
+		{ "CD", 0x4 },
+	},
+};
+
+/* On Fujitsu S702x laptops capture only makes sense from Mic/LineIn jack,
+ * headphone jack and the internal CD lines since these are the only pins at
+ * which audio can appear.  For flexibility, also allow the option of
+ * recording the mixer output on the second ADC (ADC0 doesn't have a
+ * connection to the mixer output).
+ */
+static struct hda_input_mux alc260_fujitsu_capture_sources[2] = {
+	{
+		.num_items = 3,
+		.items = {
+			{ "Mic/Line", 0x0 },
+			{ "CD", 0x4 },
+			{ "Headphone", 0x2 },
+		},
+	},
+	{
+		.num_items = 4,
+		.items = {
+			{ "Mic/Line", 0x0 },
+			{ "CD", 0x4 },
+			{ "Headphone", 0x2 },
+			{ "Mixer", 0x5 },
+		},
+	},
+
+};
+
+/* Acer TravelMate(/Extensa/Aspire) notebooks have similar configuration to
+ * the Fujitsu S702x, but jacks are marked differently.
+ */
+static struct hda_input_mux alc260_acer_capture_sources[2] = {
+	{
+		.num_items = 4,
+		.items = {
+			{ "Mic", 0x0 },
+			{ "Line", 0x2 },
+			{ "CD", 0x4 },
+			{ "Headphone", 0x5 },
+		},
+	},
+	{
+		.num_items = 5,
+		.items = {
+			{ "Mic", 0x0 },
+			{ "Line", 0x2 },
+			{ "CD", 0x4 },
+			{ "Headphone", 0x6 },
+			{ "Mixer", 0x5 },
+		},
+	},
+};
+
+/* Maxdata Favorit 100XS */
+static struct hda_input_mux alc260_favorit100_capture_sources[2] = {
+	{
+		.num_items = 2,
+		.items = {
+			{ "Line/Mic", 0x0 },
+			{ "CD", 0x4 },
+		},
+	},
+	{
+		.num_items = 3,
+		.items = {
+			{ "Line/Mic", 0x0 },
+			{ "CD", 0x4 },
+			{ "Mixer", 0x5 },
+		},
+	},
+};
+
+/*
+ * This is just place-holder, so there's something for alc_build_pcms to look
+ * at when it calculates the maximum number of channels. ALC260 has no mixer
+ * element which allows changing the channel mode, so the verb list is
+ * never used.
+ */
+static struct hda_channel_mode alc260_modes[1] = {
+	{ 2, NULL },
+};
+
+
+/* Mixer combinations
+ *
+ * basic: base_output + input + pc_beep + capture
+ * HP: base_output + input + capture_alt
+ * HP_3013: hp_3013 + input + capture
+ * fujitsu: fujitsu + capture
+ * acer: acer + capture
+ */
+
+static struct snd_kcontrol_new alc260_base_output_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x08, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x08, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x09, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x09, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x0a, 1, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Mono Playback Switch", 0x0a, 1, 2, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc260_input_mixer[] = {
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x07, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x07, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x07, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x07, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x07, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x07, 0x01, HDA_INPUT),
+	{ } /* end */
+};
+
+/* update HP, line and mono out pins according to the master switch */
+static void alc260_hp_master_update(struct hda_codec *codec,
+				    hda_nid_t hp, hda_nid_t line,
+				    hda_nid_t mono)
+{
+	struct alc_spec *spec = codec->spec;
+	unsigned int val = spec->master_sw ? PIN_HP : 0;
+	/* change HP and line-out pins */
+	snd_hda_codec_write(codec, hp, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    val);
+	snd_hda_codec_write(codec, line, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    val);
+	/* mono (speaker) depending on the HP jack sense */
+	val = (val && !spec->jack_present) ? PIN_OUT : 0;
+	snd_hda_codec_write(codec, mono, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    val);
+}
+
+static int alc260_hp_master_sw_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	*ucontrol->value.integer.value = spec->master_sw;
+	return 0;
+}
+
+static int alc260_hp_master_sw_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	int val = !!*ucontrol->value.integer.value;
+	hda_nid_t hp, line, mono;
+
+	if (val == spec->master_sw)
+		return 0;
+	spec->master_sw = val;
+	hp = (kcontrol->private_value >> 16) & 0xff;
+	line = (kcontrol->private_value >> 8) & 0xff;
+	mono = kcontrol->private_value & 0xff;
+	alc260_hp_master_update(codec, hp, line, mono);
+	return 1;
+}
+
+static struct snd_kcontrol_new alc260_hp_output_mixer[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x11,
+		.info = snd_ctl_boolean_mono_info,
+		.get = alc260_hp_master_sw_get,
+		.put = alc260_hp_master_sw_put,
+		.private_value = (0x0f << 16) | (0x10 << 8) | 0x11
+	},
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x08, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x08, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x09, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x09, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0a, 1, 0x0,
+			      HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Speaker Playback Switch", 0x0a, 1, 2, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_verb alc260_hp_unsol_verbs[] = {
+	{0x10, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{},
+};
+
+static void alc260_hp_automute(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->jack_present = snd_hda_jack_detect(codec, 0x10);
+	alc260_hp_master_update(codec, 0x0f, 0x10, 0x11);
+}
+
+static void alc260_hp_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	if ((res >> 26) == ALC880_HP_EVENT)
+		alc260_hp_automute(codec);
+}
+
+static struct snd_kcontrol_new alc260_hp_3013_mixer[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x11,
+		.info = snd_ctl_boolean_mono_info,
+		.get = alc260_hp_master_sw_get,
+		.put = alc260_hp_master_sw_put,
+		.private_value = (0x15 << 16) | (0x10 << 8) | 0x11
+	},
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x09, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x10, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Aux-In Playback Volume", 0x07, 0x06, HDA_INPUT),
+	HDA_CODEC_MUTE("Aux-In Playback Switch", 0x07, 0x06, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x08, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0a, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Speaker Playback Switch", 0x11, 1, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct hda_bind_ctls alc260_dc7600_bind_master_vol = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x08, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x09, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x0a, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+static struct hda_bind_ctls alc260_dc7600_bind_switch = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x11, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x15, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+static struct snd_kcontrol_new alc260_hp_dc7600_mixer[] = {
+	HDA_BIND_VOL("Master Playback Volume", &alc260_dc7600_bind_master_vol),
+	HDA_BIND_SW("LineOut Playback Switch", &alc260_dc7600_bind_switch),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x10, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct hda_verb alc260_hp_3013_unsol_verbs[] = {
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{},
+};
+
+static void alc260_hp_3013_automute(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->jack_present = snd_hda_jack_detect(codec, 0x15);
+	alc260_hp_master_update(codec, 0x15, 0x10, 0x11);
+}
+
+static void alc260_hp_3013_unsol_event(struct hda_codec *codec,
+				       unsigned int res)
+{
+	if ((res >> 26) == ALC880_HP_EVENT)
+		alc260_hp_3013_automute(codec);
+}
+
+static void alc260_hp_3012_automute(struct hda_codec *codec)
+{
+	unsigned int bits = snd_hda_jack_detect(codec, 0x10) ? 0 : PIN_OUT;
+
+	snd_hda_codec_write(codec, 0x0f, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    bits);
+	snd_hda_codec_write(codec, 0x11, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    bits);
+	snd_hda_codec_write(codec, 0x15, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    bits);
+}
+
+static void alc260_hp_3012_unsol_event(struct hda_codec *codec,
+				       unsigned int res)
+{
+	if ((res >> 26) == ALC880_HP_EVENT)
+		alc260_hp_3012_automute(codec);
+}
+
+/* Fujitsu S702x series laptops.  ALC260 pin usage: Mic/Line jack = 0x12,
+ * HP jack = 0x14, CD audio =  0x16, internal speaker = 0x10.
+ */
+static struct snd_kcontrol_new alc260_fujitsu_mixer[] = {
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x08, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x08, 2, HDA_INPUT),
+	ALC_PIN_MODE("Headphone Jack Mode", 0x14, ALC_PIN_DIR_INOUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic/Line Playback Volume", 0x07, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic/Line Playback Switch", 0x07, 0x0, HDA_INPUT),
+	ALC_PIN_MODE("Mic/Line Jack Mode", 0x12, ALC_PIN_DIR_IN),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x09, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x09, 2, HDA_INPUT),
+	{ } /* end */
+};
+
+/* Mixer for Acer TravelMate(/Extensa/Aspire) notebooks.  Note that current
+ * versions of the ALC260 don't act on requests to enable mic bias from NID
+ * 0x0f (used to drive the headphone jack in these laptops).  The ALC260
+ * datasheet doesn't mention this restriction.  At this stage it's not clear
+ * whether this behaviour is intentional or is a hardware bug in chip
+ * revisions available in early 2006.  Therefore for now allow the
+ * "Headphone Jack Mode" control to span all choices, but if it turns out
+ * that the lack of mic bias for this NID is intentional we could change the
+ * mode from ALC_PIN_DIR_INOUT to ALC_PIN_DIR_INOUT_NOMICBIAS.
+ *
+ * In addition, Acer TravelMate(/Extensa/Aspire) notebooks in early 2006
+ * don't appear to make the mic bias available from the "line" jack, even
+ * though the NID used for this jack (0x14) can supply it.  The theory is
+ * that perhaps Acer have included blocking capacitors between the ALC260
+ * and the output jack.  If this turns out to be the case for all such
+ * models the "Line Jack Mode" mode could be changed from ALC_PIN_DIR_INOUT
+ * to ALC_PIN_DIR_INOUT_NOMICBIAS.
+ *
+ * The C20x Tablet series have a mono internal speaker which is controlled
+ * via the chip's Mono sum widget and pin complex, so include the necessary
+ * controls for such models.  On models without a "mono speaker" the control
+ * won't do anything.
+ */
+static struct snd_kcontrol_new alc260_acer_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x08, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Master Playback Switch", 0x08, 2, HDA_INPUT),
+	ALC_PIN_MODE("Headphone Jack Mode", 0x0f, ALC_PIN_DIR_INOUT),
+	HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0a, 1, 0x0,
+			      HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Speaker Playback Switch", 0x0a, 1, 2,
+			   HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x07, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x07, 0x0, HDA_INPUT),
+	ALC_PIN_MODE("Mic Jack Mode", 0x12, ALC_PIN_DIR_IN),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x07, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x07, 0x02, HDA_INPUT),
+	ALC_PIN_MODE("Line Jack Mode", 0x14, ALC_PIN_DIR_INOUT),
+	{ } /* end */
+};
+
+/* Maxdata Favorit 100XS: one output and one input (0x12) jack
+ */
+static struct snd_kcontrol_new alc260_favorit100_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x08, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Master Playback Switch", 0x08, 2, HDA_INPUT),
+	ALC_PIN_MODE("Output Jack Mode", 0x0f, ALC_PIN_DIR_INOUT),
+	HDA_CODEC_VOLUME("Line/Mic Playback Volume", 0x07, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Line/Mic Playback Switch", 0x07, 0x0, HDA_INPUT),
+	ALC_PIN_MODE("Line/Mic Jack Mode", 0x12, ALC_PIN_DIR_IN),
+	{ } /* end */
+};
+
+/* Packard bell V7900  ALC260 pin usage: HP = 0x0f, Mic jack = 0x12,
+ * Line In jack = 0x14, CD audio =  0x16, pc beep = 0x17.
+ */
+static struct snd_kcontrol_new alc260_will_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x08, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Master Playback Switch", 0x08, 0x2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x07, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x07, 0x0, HDA_INPUT),
+	ALC_PIN_MODE("Mic Jack Mode", 0x12, ALC_PIN_DIR_IN),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x07, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x07, 0x02, HDA_INPUT),
+	ALC_PIN_MODE("Line Jack Mode", 0x14, ALC_PIN_DIR_INOUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT),
+	{ } /* end */
+};
+
+/* Replacer 672V ALC260 pin usage: Mic jack = 0x12,
+ * Line In jack = 0x14, ATAPI Mic = 0x13, speaker = 0x0f.
+ */
+static struct snd_kcontrol_new alc260_replacer_672v_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x08, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Master Playback Switch", 0x08, 0x2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x07, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x07, 0x0, HDA_INPUT),
+	ALC_PIN_MODE("Mic Jack Mode", 0x12, ALC_PIN_DIR_IN),
+	HDA_CODEC_VOLUME("ATAPI Mic Playback Volume", 0x07, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("ATATI Mic Playback Switch", 0x07, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x07, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x07, 0x02, HDA_INPUT),
+	ALC_PIN_MODE("Line Jack Mode", 0x14, ALC_PIN_DIR_INOUT),
+	{ } /* end */
+};
+
+/*
+ * initialization verbs
+ */
+static struct hda_verb alc260_init_verbs[] = {
+	/* Line In pin widget for input */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	/* CD pin widget for input */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	/* Mic1 (rear panel) pin widget for input and vref at 80% */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	/* Mic2 (front panel) pin widget for input and vref at 80% */
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	/* LINE-2 is used for line-out in rear */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* select line-out */
+	{0x0e, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* LINE-OUT pin */
+	{0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* enable HP */
+	{0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* enable Mono */
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* mute capture amp left and right */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* set connection select to line in (default select for this ADC) */
+	{0x04, AC_VERB_SET_CONNECT_SEL, 0x02},
+	/* mute capture amp left and right */
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* set connection select to line in (default select for this ADC) */
+	{0x05, AC_VERB_SET_CONNECT_SEL, 0x02},
+	/* set vol=0 Line-Out mixer amp left and right */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* unmute pin widget amp left and right (no gain on this amp) */
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* set vol=0 HP mixer amp left and right */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* unmute pin widget amp left and right (no gain on this amp) */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* set vol=0 Mono mixer amp left and right */
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* unmute pin widget amp left and right (no gain on this amp) */
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* unmute LINE-2 out pin */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 &
+	 * Line In 2 = 0x03
+	 */
+	/* mute analog inputs */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* Amp Indexes: DAC = 0x01 & mixer = 0x00 */
+	/* mute Front out path */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* mute Headphone out path */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* mute Mono out path */
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{ }
+};
+
+#if 0 /* should be identical with alc260_init_verbs? */
+static struct hda_verb alc260_hp_init_verbs[] = {
+	/* Headphone and output */
+	{0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0},
+	/* mono output */
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	/* Mic1 (rear panel) pin widget for input and vref at 80% */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+	/* Mic2 (front panel) pin widget for input and vref at 80% */
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+	/* Line In pin widget for input */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	/* Line-2 pin widget for output */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	/* CD pin widget for input */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	/* unmute amp left and right */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
+	/* set connection select to line in (default select for this ADC) */
+	{0x04, AC_VERB_SET_CONNECT_SEL, 0x02},
+	/* unmute Line-Out mixer amp left and right (volume = 0) */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+	/* mute pin widget amp left and right (no gain on this amp) */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+	/* unmute HP mixer amp left and right (volume = 0) */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+	/* mute pin widget amp left and right (no gain on this amp) */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+	/* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 &
+	 * Line In 2 = 0x03
+	 */
+	/* mute analog inputs */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* Amp Indexes: DAC = 0x01 & mixer = 0x00 */
+	/* Unmute Front out path */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	/* Unmute Headphone out path */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	/* Unmute Mono out path */
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	{ }
+};
+#endif
+
+static struct hda_verb alc260_hp_3013_init_verbs[] = {
+	/* Line out and output */
+	{0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	/* mono output */
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	/* Mic1 (rear panel) pin widget for input and vref at 80% */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+	/* Mic2 (front panel) pin widget for input and vref at 80% */
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+	/* Line In pin widget for input */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	/* Headphone pin widget for output */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0},
+	/* CD pin widget for input */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	/* unmute amp left and right */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
+	/* set connection select to line in (default select for this ADC) */
+	{0x04, AC_VERB_SET_CONNECT_SEL, 0x02},
+	/* unmute Line-Out mixer amp left and right (volume = 0) */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+	/* mute pin widget amp left and right (no gain on this amp) */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+	/* unmute HP mixer amp left and right (volume = 0) */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+	/* mute pin widget amp left and right (no gain on this amp) */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+	/* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 &
+	 * Line In 2 = 0x03
+	 */
+	/* mute analog inputs */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* Amp Indexes: DAC = 0x01 & mixer = 0x00 */
+	/* Unmute Front out path */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	/* Unmute Headphone out path */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	/* Unmute Mono out path */
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	{ }
+};
+
+/* Initialisation sequence for ALC260 as configured in Fujitsu S702x
+ * laptops.  ALC260 pin usage: Mic/Line jack = 0x12, HP jack = 0x14, CD
+ * audio = 0x16, internal speaker = 0x10.
+ */
+static struct hda_verb alc260_fujitsu_init_verbs[] = {
+	/* Disable all GPIOs */
+	{0x01, AC_VERB_SET_GPIO_MASK, 0},
+	/* Internal speaker is connected to headphone pin */
+	{0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Headphone/Line-out jack connects to Line1 pin; make it an output */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	/* Mic/Line-in jack is connected to mic1 pin, so make it an input */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	/* Ensure all other unused pins are disabled and muted. */
+	{0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+
+	/* Disable digital (SPDIF) pins */
+	{0x03, AC_VERB_SET_DIGI_CONVERT_1, 0},
+	{0x06, AC_VERB_SET_DIGI_CONVERT_1, 0},
+
+	/* Ensure Line1 pin widget takes its input from the OUT1 sum bus
+	 * when acting as an output.
+	 */
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0},
+
+	/* Start with output sum widgets muted and their output gains at min */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* Unmute HP pin widget amp left and right (no equiv mixer ctrl) */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Unmute Line1 pin widget output buffer since it starts as an output.
+	 * If the pin mode is changed by the user the pin mode control will
+	 * take care of enabling the pin's input/output buffers as needed.
+	 * Therefore there's no need to enable the input buffer at this
+	 * stage.
+	 */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Unmute input buffer of pin widget used for Line-in (no equiv
+	 * mixer ctrl)
+	 */
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Mute capture amp left and right */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	/* Set ADC connection select to match default mixer setting - line
+	 * in (on mic1 pin)
+	 */
+	{0x04, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Do the same for the second ADC: mute capture input amp and
+	 * set ADC connection to line in (on mic1 pin)
+	 */
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x05, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Mute all inputs to mixer widget (even unconnected ones) */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* line1 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* line2 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, /* Beep-gen pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, /* Line-out pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, /* HP-pin pin */
+
+	{ }
+};
+
+/* Initialisation sequence for ALC260 as configured in Acer TravelMate and
+ * similar laptops (adapted from Fujitsu init verbs).
+ */
+static struct hda_verb alc260_acer_init_verbs[] = {
+	/* On TravelMate laptops, GPIO 0 enables the internal speaker and
+	 * the headphone jack.  Turn this on and rely on the standard mute
+	 * methods whenever the user wants to turn these outputs off.
+	 */
+	{0x01, AC_VERB_SET_GPIO_MASK, 0x01},
+	{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x01},
+	{0x01, AC_VERB_SET_GPIO_DATA, 0x01},
+	/* Internal speaker/Headphone jack is connected to Line-out pin */
+	{0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Internal microphone/Mic jack is connected to Mic1 pin */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50},
+	/* Line In jack is connected to Line1 pin */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	/* Some Acers (eg: C20x Tablets) use Mono pin for internal speaker */
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Ensure all other unused pins are disabled and muted. */
+	{0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	/* Disable digital (SPDIF) pins */
+	{0x03, AC_VERB_SET_DIGI_CONVERT_1, 0},
+	{0x06, AC_VERB_SET_DIGI_CONVERT_1, 0},
+
+	/* Ensure Mic1 and Line1 pin widgets take input from the OUT1 sum
+	 * bus when acting as outputs.
+	 */
+	{0x0b, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0},
+
+	/* Start with output sum widgets muted and their output gains at min */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* Unmute Line-out pin widget amp left and right
+	 * (no equiv mixer ctrl)
+	 */
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Unmute mono pin widget amp output (no equiv mixer ctrl) */
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Unmute Mic1 and Line1 pin widget input buffers since they start as
+	 * inputs. If the pin mode is changed by the user the pin mode control
+	 * will take care of enabling the pin's input/output buffers as needed.
+	 * Therefore there's no need to enable the input buffer at this
+	 * stage.
+	 */
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Mute capture amp left and right */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	/* Set ADC connection select to match default mixer setting - mic
+	 * (on mic1 pin)
+	 */
+	{0x04, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Do similar with the second ADC: mute capture input amp and
+	 * set ADC connection to mic to match ALSA's default state.
+	 */
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x05, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Mute all inputs to mixer widget (even unconnected ones) */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* line1 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* line2 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, /* Beep-gen pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, /* Line-out pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, /* HP-pin pin */
+
+	{ }
+};
+
+/* Initialisation sequence for Maxdata Favorit 100XS
+ * (adapted from Acer init verbs).
+ */
+static struct hda_verb alc260_favorit100_init_verbs[] = {
+	/* GPIO 0 enables the output jack.
+	 * Turn this on and rely on the standard mute
+	 * methods whenever the user wants to turn these outputs off.
+	 */
+	{0x01, AC_VERB_SET_GPIO_MASK, 0x01},
+	{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x01},
+	{0x01, AC_VERB_SET_GPIO_DATA, 0x01},
+	/* Line/Mic input jack is connected to Mic1 pin */
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50},
+	/* Ensure all other unused pins are disabled and muted. */
+	{0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	/* Disable digital (SPDIF) pins */
+	{0x03, AC_VERB_SET_DIGI_CONVERT_1, 0},
+	{0x06, AC_VERB_SET_DIGI_CONVERT_1, 0},
+
+	/* Ensure Mic1 and Line1 pin widgets take input from the OUT1 sum
+	 * bus when acting as outputs.
+	 */
+	{0x0b, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0},
+
+	/* Start with output sum widgets muted and their output gains at min */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* Unmute Line-out pin widget amp left and right
+	 * (no equiv mixer ctrl)
+	 */
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Unmute Mic1 and Line1 pin widget input buffers since they start as
+	 * inputs. If the pin mode is changed by the user the pin mode control
+	 * will take care of enabling the pin's input/output buffers as needed.
+	 * Therefore there's no need to enable the input buffer at this
+	 * stage.
+	 */
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Mute capture amp left and right */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	/* Set ADC connection select to match default mixer setting - mic
+	 * (on mic1 pin)
+	 */
+	{0x04, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Do similar with the second ADC: mute capture input amp and
+	 * set ADC connection to mic to match ALSA's default state.
+	 */
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x05, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Mute all inputs to mixer widget (even unconnected ones) */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* line1 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* line2 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, /* Beep-gen pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, /* Line-out pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, /* HP-pin pin */
+
+	{ }
+};
+
+static struct hda_verb alc260_will_verbs[] = {
+	{0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x0b, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x0f, AC_VERB_SET_EAPD_BTLENABLE, 0x02},
+	{0x1a, AC_VERB_SET_COEF_INDEX, 0x07},
+	{0x1a, AC_VERB_SET_PROC_COEF, 0x3040},
+	{}
+};
+
+static struct hda_verb alc260_replacer_672v_verbs[] = {
+	{0x0f, AC_VERB_SET_EAPD_BTLENABLE, 0x02},
+	{0x1a, AC_VERB_SET_COEF_INDEX, 0x07},
+	{0x1a, AC_VERB_SET_PROC_COEF, 0x3050},
+
+	{0x01, AC_VERB_SET_GPIO_MASK, 0x01},
+	{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x01},
+	{0x01, AC_VERB_SET_GPIO_DATA, 0x00},
+
+	{0x0f, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc260_replacer_672v_automute(struct hda_codec *codec)
+{
+        unsigned int present;
+
+	/* speaker --> GPIO Data 0, hp or spdif --> GPIO data 1 */
+	present = snd_hda_jack_detect(codec, 0x0f);
+	if (present) {
+		snd_hda_codec_write_cache(codec, 0x01, 0,
+					  AC_VERB_SET_GPIO_DATA, 1);
+		snd_hda_codec_write_cache(codec, 0x0f, 0,
+					  AC_VERB_SET_PIN_WIDGET_CONTROL,
+					  PIN_HP);
+	} else {
+		snd_hda_codec_write_cache(codec, 0x01, 0,
+					  AC_VERB_SET_GPIO_DATA, 0);
+		snd_hda_codec_write_cache(codec, 0x0f, 0,
+					  AC_VERB_SET_PIN_WIDGET_CONTROL,
+					  PIN_OUT);
+	}
+}
+
+static void alc260_replacer_672v_unsol_event(struct hda_codec *codec,
+                                       unsigned int res)
+{
+        if ((res >> 26) == ALC880_HP_EVENT)
+                alc260_replacer_672v_automute(codec);
+}
+
+static struct hda_verb alc260_hp_dc7600_verbs[] = {
+	{0x05, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x10, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+/* Test configuration for debugging, modelled after the ALC880 test
+ * configuration.
+ */
+#ifdef CONFIG_SND_DEBUG
+static hda_nid_t alc260_test_dac_nids[1] = {
+	0x02,
+};
+static hda_nid_t alc260_test_adc_nids[2] = {
+	0x04, 0x05,
+};
+/* For testing the ALC260, each input MUX needs its own definition since
+ * the signal assignments are different.  This assumes that the first ADC
+ * is NID 0x04.
+ */
+static struct hda_input_mux alc260_test_capture_sources[2] = {
+	{
+		.num_items = 7,
+		.items = {
+			{ "MIC1 pin", 0x0 },
+			{ "MIC2 pin", 0x1 },
+			{ "LINE1 pin", 0x2 },
+			{ "LINE2 pin", 0x3 },
+			{ "CD pin", 0x4 },
+			{ "LINE-OUT pin", 0x5 },
+			{ "HP-OUT pin", 0x6 },
+		},
+        },
+	{
+		.num_items = 8,
+		.items = {
+			{ "MIC1 pin", 0x0 },
+			{ "MIC2 pin", 0x1 },
+			{ "LINE1 pin", 0x2 },
+			{ "LINE2 pin", 0x3 },
+			{ "CD pin", 0x4 },
+			{ "Mixer", 0x5 },
+			{ "LINE-OUT pin", 0x6 },
+			{ "HP-OUT pin", 0x7 },
+		},
+        },
+};
+static struct snd_kcontrol_new alc260_test_mixer[] = {
+	/* Output driver widgets */
+	HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x0a, 1, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Mono Playback Switch", 0x0a, 1, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("LOUT2 Playback Volume", 0x09, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("LOUT2 Playback Switch", 0x09, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("LOUT1 Playback Volume", 0x08, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("LOUT1 Playback Switch", 0x08, 2, HDA_INPUT),
+
+	/* Modes for retasking pin widgets
+	 * Note: the ALC260 doesn't seem to act on requests to enable mic
+         * bias from NIDs 0x0f and 0x10.  The ALC260 datasheet doesn't
+         * mention this restriction.  At this stage it's not clear whether
+         * this behaviour is intentional or is a hardware bug in chip
+         * revisions available at least up until early 2006.  Therefore for
+         * now allow the "HP-OUT" and "LINE-OUT" Mode controls to span all
+         * choices, but if it turns out that the lack of mic bias for these
+         * NIDs is intentional we could change their modes from
+         * ALC_PIN_DIR_INOUT to ALC_PIN_DIR_INOUT_NOMICBIAS.
+	 */
+	ALC_PIN_MODE("HP-OUT pin mode", 0x10, ALC_PIN_DIR_INOUT),
+	ALC_PIN_MODE("LINE-OUT pin mode", 0x0f, ALC_PIN_DIR_INOUT),
+	ALC_PIN_MODE("LINE2 pin mode", 0x15, ALC_PIN_DIR_INOUT),
+	ALC_PIN_MODE("LINE1 pin mode", 0x14, ALC_PIN_DIR_INOUT),
+	ALC_PIN_MODE("MIC2 pin mode", 0x13, ALC_PIN_DIR_INOUT),
+	ALC_PIN_MODE("MIC1 pin mode", 0x12, ALC_PIN_DIR_INOUT),
+
+	/* Loopback mixer controls */
+	HDA_CODEC_VOLUME("MIC1 Playback Volume", 0x07, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("MIC1 Playback Switch", 0x07, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("MIC2 Playback Volume", 0x07, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("MIC2 Playback Switch", 0x07, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("LINE1 Playback Volume", 0x07, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("LINE1 Playback Switch", 0x07, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("LINE2 Playback Volume", 0x07, 0x03, HDA_INPUT),
+	HDA_CODEC_MUTE("LINE2 Playback Switch", 0x07, 0x03, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("LINE-OUT loopback Playback Volume", 0x07, 0x06, HDA_INPUT),
+	HDA_CODEC_MUTE("LINE-OUT loopback Playback Switch", 0x07, 0x06, HDA_INPUT),
+	HDA_CODEC_VOLUME("HP-OUT loopback Playback Volume", 0x07, 0x7, HDA_INPUT),
+	HDA_CODEC_MUTE("HP-OUT loopback Playback Switch", 0x07, 0x7, HDA_INPUT),
+
+	/* Controls for GPIO pins, assuming they are configured as outputs */
+	ALC_GPIO_DATA_SWITCH("GPIO pin 0", 0x01, 0x01),
+	ALC_GPIO_DATA_SWITCH("GPIO pin 1", 0x01, 0x02),
+	ALC_GPIO_DATA_SWITCH("GPIO pin 2", 0x01, 0x04),
+	ALC_GPIO_DATA_SWITCH("GPIO pin 3", 0x01, 0x08),
+
+	/* Switches to allow the digital IO pins to be enabled.  The datasheet
+	 * is ambigious as to which NID is which; testing on laptops which
+	 * make this output available should provide clarification.
+	 */
+	ALC_SPDIF_CTRL_SWITCH("SPDIF Playback Switch", 0x03, 0x01),
+	ALC_SPDIF_CTRL_SWITCH("SPDIF Capture Switch", 0x06, 0x01),
+
+	/* A switch allowing EAPD to be enabled.  Some laptops seem to use
+	 * this output to turn on an external amplifier.
+	 */
+	ALC_EAPD_CTRL_SWITCH("LINE-OUT EAPD Enable Switch", 0x0f, 0x02),
+	ALC_EAPD_CTRL_SWITCH("HP-OUT EAPD Enable Switch", 0x10, 0x02),
+
+	{ } /* end */
+};
+static struct hda_verb alc260_test_init_verbs[] = {
+	/* Enable all GPIOs as outputs with an initial value of 0 */
+	{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x0f},
+	{0x01, AC_VERB_SET_GPIO_DATA, 0x00},
+	{0x01, AC_VERB_SET_GPIO_MASK, 0x0f},
+
+	/* Enable retasking pins as output, initially without power amp */
+	{0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	/* Disable digital (SPDIF) pins initially, but users can enable
+	 * them via a mixer switch.  In the case of SPDIF-out, this initverb
+	 * payload also sets the generation to 0, output to be in "consumer"
+	 * PCM format, copyright asserted, no pre-emphasis and no validity
+	 * control.
+	 */
+	{0x03, AC_VERB_SET_DIGI_CONVERT_1, 0},
+	{0x06, AC_VERB_SET_DIGI_CONVERT_1, 0},
+
+	/* Ensure mic1, mic2, line1 and line2 pin widgets take input from the
+	 * OUT1 sum bus when acting as an output.
+	 */
+	{0x0b, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x0c, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x0d, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x0e, AC_VERB_SET_CONNECT_SEL, 0},
+
+	/* Start with output sum widgets muted and their output gains at min */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* Unmute retasking pin widget output buffers since the default
+	 * state appears to be output.  As the pin mode is changed by the
+	 * user the pin mode control will take care of enabling the pin's
+	 * input/output buffers as needed.
+	 */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Also unmute the mono-out pin widget */
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Mute capture amp left and right */
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	/* Set ADC connection select to match default mixer setting (mic1
+	 * pin)
+	 */
+	{0x04, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Do the same for the second ADC: mute capture input amp and
+	 * set ADC connection to mic1 pin
+	 */
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x05, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Mute all inputs to mixer widget (even unconnected ones) */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* line1 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* line2 pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, /* Beep-gen pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, /* Line-out pin */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, /* HP-pin pin */
+
+	{ }
+};
+#endif
+
+#define alc260_pcm_analog_playback	alc880_pcm_analog_alt_playback
+#define alc260_pcm_analog_capture	alc880_pcm_analog_capture
+
+#define alc260_pcm_digital_playback	alc880_pcm_digital_playback
+#define alc260_pcm_digital_capture	alc880_pcm_digital_capture
+
+/*
+ * for BIOS auto-configuration
+ */
+
+static int alc260_add_playback_controls(struct alc_spec *spec, hda_nid_t nid,
+					const char *pfx, int *vol_bits)
+{
+	hda_nid_t nid_vol;
+	unsigned long vol_val, sw_val;
+	int err;
+
+	if (nid >= 0x0f && nid < 0x11) {
+		nid_vol = nid - 0x7;
+		vol_val = HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, HDA_OUTPUT);
+		sw_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+	} else if (nid == 0x11) {
+		nid_vol = nid - 0x7;
+		vol_val = HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0, HDA_OUTPUT);
+		sw_val = HDA_COMPOSE_AMP_VAL(nid, 2, 0, HDA_OUTPUT);
+	} else if (nid >= 0x12 && nid <= 0x15) {
+		nid_vol = 0x08;
+		vol_val = HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, HDA_OUTPUT);
+		sw_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+	} else
+		return 0; /* N/A */
+
+	if (!(*vol_bits & (1 << nid_vol))) {
+		/* first control for the volume widget */
+		err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL, pfx, vol_val);
+		if (err < 0)
+			return err;
+		*vol_bits |= (1 << nid_vol);
+	}
+	err = add_pb_sw_ctrl(spec, ALC_CTL_WIDGET_MUTE, pfx, sw_val);
+	if (err < 0)
+		return err;
+	return 1;
+}
+
+/* add playback controls from the parsed DAC table */
+static int alc260_auto_create_multi_out_ctls(struct alc_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	hda_nid_t nid;
+	int err;
+	int vols = 0;
+
+	spec->multiout.num_dacs = 1;
+	spec->multiout.dac_nids = spec->private_dac_nids;
+	spec->multiout.dac_nids[0] = 0x02;
+
+	nid = cfg->line_out_pins[0];
+	if (nid) {
+		const char *pfx;
+		if (!cfg->speaker_pins[0] && !cfg->hp_pins[0])
+			pfx = "Master";
+		else if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
+			pfx = "Speaker";
+		else
+			pfx = "Front";
+		err = alc260_add_playback_controls(spec, nid, pfx, &vols);
+		if (err < 0)
+			return err;
+	}
+
+	nid = cfg->speaker_pins[0];
+	if (nid) {
+		err = alc260_add_playback_controls(spec, nid, "Speaker", &vols);
+		if (err < 0)
+			return err;
+	}
+
+	nid = cfg->hp_pins[0];
+	if (nid) {
+		err = alc260_add_playback_controls(spec, nid, "Headphone",
+						   &vols);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int alc260_auto_create_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	return alc_auto_create_input_ctls(codec, cfg, 0x07, 0x04, 0x05);
+}
+
+static void alc260_auto_set_output_and_unmute(struct hda_codec *codec,
+					      hda_nid_t nid, int pin_type,
+					      int sel_idx)
+{
+	alc_set_pin_output(codec, nid, pin_type);
+	/* need the manual connection? */
+	if (nid >= 0x12) {
+		int idx = nid - 0x12;
+		snd_hda_codec_write(codec, idx + 0x0b, 0,
+				    AC_VERB_SET_CONNECT_SEL, sel_idx);
+	}
+}
+
+static void alc260_auto_init_multi_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t nid;
+
+	nid = spec->autocfg.line_out_pins[0];
+	if (nid) {
+		int pin_type = get_pin_type(spec->autocfg.line_out_type);
+		alc260_auto_set_output_and_unmute(codec, nid, pin_type, 0);
+	}
+
+	nid = spec->autocfg.speaker_pins[0];
+	if (nid)
+		alc260_auto_set_output_and_unmute(codec, nid, PIN_OUT, 0);
+
+	nid = spec->autocfg.hp_pins[0];
+	if (nid)
+		alc260_auto_set_output_and_unmute(codec, nid, PIN_HP, 0);
+}
+
+#define ALC260_PIN_CD_NID		0x16
+static void alc260_auto_init_analog_input(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		if (nid >= 0x12) {
+			alc_set_input_pin(codec, nid, cfg->inputs[i].type);
+			if (nid != ALC260_PIN_CD_NID &&
+			    (get_wcaps(codec, nid) & AC_WCAP_OUT_AMP))
+				snd_hda_codec_write(codec, nid, 0,
+						    AC_VERB_SET_AMP_GAIN_MUTE,
+						    AMP_OUT_MUTE);
+		}
+	}
+}
+
+#define alc260_auto_init_input_src	alc880_auto_init_input_src
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc260_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-1 and set the default input to mic-in
+	 */
+	{0x04, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x05, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 * Note: PASD motherboards uses the Line In 2 as the input for
+	 * front panel mic (mic 2)
+	 */
+	/* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */
+	/* mute analog inputs */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+
+	/*
+	 * Set up output mixers (0x08 - 0x0a)
+	 */
+	/* set vol=0 to output mixers */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* set up input amps for analog loopback */
+	/* Amp Indices: DAC = 0, mixer = 1 */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	{ }
+};
+
+static int alc260_parse_auto_config(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int err;
+	static hda_nid_t alc260_ignore[] = { 0x17, 0 };
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg,
+					   alc260_ignore);
+	if (err < 0)
+		return err;
+	err = alc260_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	if (!spec->kctls.list)
+		return 0; /* can't find valid BIOS pin config */
+	err = alc260_auto_create_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = 2;
+
+	if (spec->autocfg.dig_outs)
+		spec->multiout.dig_out_nid = ALC260_DIGOUT_NID;
+	if (spec->kctls.list)
+		add_mixer(spec, spec->kctls.list);
+
+	add_verb(spec, alc260_volume_init_verbs);
+
+	spec->num_mux_defs = 1;
+	spec->input_mux = &spec->private_imux[0];
+
+	alc_ssid_check(codec, 0x10, 0x15, 0x0f, 0);
+
+	return 1;
+}
+
+/* additional initialization for auto-configuration model */
+static void alc260_auto_init(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc260_auto_init_multi_out(codec);
+	alc260_auto_init_analog_input(codec);
+	alc260_auto_init_input_src(codec);
+	alc_auto_init_digital(codec);
+	if (spec->unsol_event)
+		alc_inithook(codec);
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list alc260_loopbacks[] = {
+	{ 0x07, HDA_INPUT, 0 },
+	{ 0x07, HDA_INPUT, 1 },
+	{ 0x07, HDA_INPUT, 2 },
+	{ 0x07, HDA_INPUT, 3 },
+	{ 0x07, HDA_INPUT, 4 },
+	{ } /* end */
+};
+#endif
+
+/*
+ * Pin config fixes
+ */
+enum {
+	PINFIX_HP_DC5750,
+};
+
+static const struct alc_fixup alc260_fixups[] = {
+	[PINFIX_HP_DC5750] = {
+		.pins = (const struct alc_pincfg[]) {
+			{ 0x11, 0x90130110 }, /* speaker */
+			{ }
+		}
+	},
+};
+
+static struct snd_pci_quirk alc260_fixup_tbl[] = {
+	SND_PCI_QUIRK(0x103c, 0x280a, "HP dc5750", PINFIX_HP_DC5750),
+	{}
+};
+
+/*
+ * ALC260 configurations
+ */
+static const char *alc260_models[ALC260_MODEL_LAST] = {
+	[ALC260_BASIC]		= "basic",
+	[ALC260_HP]		= "hp",
+	[ALC260_HP_3013]	= "hp-3013",
+	[ALC260_HP_DC7600]	= "hp-dc7600",
+	[ALC260_FUJITSU_S702X]	= "fujitsu",
+	[ALC260_ACER]		= "acer",
+	[ALC260_WILL]		= "will",
+	[ALC260_REPLACER_672V]	= "replacer",
+	[ALC260_FAVORIT100]	= "favorit100",
+#ifdef CONFIG_SND_DEBUG
+	[ALC260_TEST]		= "test",
+#endif
+	[ALC260_AUTO]		= "auto",
+};
+
+static struct snd_pci_quirk alc260_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1025, 0x007b, "Acer C20x", ALC260_ACER),
+	SND_PCI_QUIRK(0x1025, 0x007f, "Acer", ALC260_WILL),
+	SND_PCI_QUIRK(0x1025, 0x008f, "Acer", ALC260_ACER),
+	SND_PCI_QUIRK(0x1509, 0x4540, "Favorit 100XS", ALC260_FAVORIT100),
+	SND_PCI_QUIRK(0x103c, 0x2808, "HP d5700", ALC260_HP_3013),
+	SND_PCI_QUIRK(0x103c, 0x280a, "HP d5750", ALC260_AUTO), /* no quirk */
+	SND_PCI_QUIRK(0x103c, 0x3010, "HP", ALC260_HP_3013),
+	SND_PCI_QUIRK(0x103c, 0x3011, "HP", ALC260_HP_3013),
+	SND_PCI_QUIRK(0x103c, 0x3012, "HP", ALC260_HP_DC7600),
+	SND_PCI_QUIRK(0x103c, 0x3013, "HP", ALC260_HP_3013),
+	SND_PCI_QUIRK(0x103c, 0x3014, "HP", ALC260_HP),
+	SND_PCI_QUIRK(0x103c, 0x3015, "HP", ALC260_HP),
+	SND_PCI_QUIRK(0x103c, 0x3016, "HP", ALC260_HP),
+	SND_PCI_QUIRK(0x104d, 0x81bb, "Sony VAIO", ALC260_BASIC),
+	SND_PCI_QUIRK(0x104d, 0x81cc, "Sony VAIO", ALC260_BASIC),
+	SND_PCI_QUIRK(0x104d, 0x81cd, "Sony VAIO", ALC260_BASIC),
+	SND_PCI_QUIRK(0x10cf, 0x1326, "Fujitsu S702X", ALC260_FUJITSU_S702X),
+	SND_PCI_QUIRK(0x152d, 0x0729, "CTL U553W", ALC260_BASIC),
+	SND_PCI_QUIRK(0x161f, 0x2057, "Replacer 672V", ALC260_REPLACER_672V),
+	SND_PCI_QUIRK(0x1631, 0xc017, "PB V7900", ALC260_WILL),
+	{}
+};
+
+static struct alc_config_preset alc260_presets[] = {
+	[ALC260_BASIC] = {
+		.mixers = { alc260_base_output_mixer,
+			    alc260_input_mixer },
+		.init_verbs = { alc260_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc260_dac_nids),
+		.dac_nids = alc260_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc260_dual_adc_nids),
+		.adc_nids = alc260_dual_adc_nids,
+		.num_channel_mode = ARRAY_SIZE(alc260_modes),
+		.channel_mode = alc260_modes,
+		.input_mux = &alc260_capture_source,
+	},
+	[ALC260_HP] = {
+		.mixers = { alc260_hp_output_mixer,
+			    alc260_input_mixer },
+		.init_verbs = { alc260_init_verbs,
+				alc260_hp_unsol_verbs },
+		.num_dacs = ARRAY_SIZE(alc260_dac_nids),
+		.dac_nids = alc260_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc260_adc_nids_alt),
+		.adc_nids = alc260_adc_nids_alt,
+		.num_channel_mode = ARRAY_SIZE(alc260_modes),
+		.channel_mode = alc260_modes,
+		.input_mux = &alc260_capture_source,
+		.unsol_event = alc260_hp_unsol_event,
+		.init_hook = alc260_hp_automute,
+	},
+	[ALC260_HP_DC7600] = {
+		.mixers = { alc260_hp_dc7600_mixer,
+			    alc260_input_mixer },
+		.init_verbs = { alc260_init_verbs,
+				alc260_hp_dc7600_verbs },
+		.num_dacs = ARRAY_SIZE(alc260_dac_nids),
+		.dac_nids = alc260_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc260_adc_nids_alt),
+		.adc_nids = alc260_adc_nids_alt,
+		.num_channel_mode = ARRAY_SIZE(alc260_modes),
+		.channel_mode = alc260_modes,
+		.input_mux = &alc260_capture_source,
+		.unsol_event = alc260_hp_3012_unsol_event,
+		.init_hook = alc260_hp_3012_automute,
+	},
+	[ALC260_HP_3013] = {
+		.mixers = { alc260_hp_3013_mixer,
+			    alc260_input_mixer },
+		.init_verbs = { alc260_hp_3013_init_verbs,
+				alc260_hp_3013_unsol_verbs },
+		.num_dacs = ARRAY_SIZE(alc260_dac_nids),
+		.dac_nids = alc260_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc260_adc_nids_alt),
+		.adc_nids = alc260_adc_nids_alt,
+		.num_channel_mode = ARRAY_SIZE(alc260_modes),
+		.channel_mode = alc260_modes,
+		.input_mux = &alc260_capture_source,
+		.unsol_event = alc260_hp_3013_unsol_event,
+		.init_hook = alc260_hp_3013_automute,
+	},
+	[ALC260_FUJITSU_S702X] = {
+		.mixers = { alc260_fujitsu_mixer },
+		.init_verbs = { alc260_fujitsu_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc260_dac_nids),
+		.dac_nids = alc260_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc260_dual_adc_nids),
+		.adc_nids = alc260_dual_adc_nids,
+		.num_channel_mode = ARRAY_SIZE(alc260_modes),
+		.channel_mode = alc260_modes,
+		.num_mux_defs = ARRAY_SIZE(alc260_fujitsu_capture_sources),
+		.input_mux = alc260_fujitsu_capture_sources,
+	},
+	[ALC260_ACER] = {
+		.mixers = { alc260_acer_mixer },
+		.init_verbs = { alc260_acer_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc260_dac_nids),
+		.dac_nids = alc260_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc260_dual_adc_nids),
+		.adc_nids = alc260_dual_adc_nids,
+		.num_channel_mode = ARRAY_SIZE(alc260_modes),
+		.channel_mode = alc260_modes,
+		.num_mux_defs = ARRAY_SIZE(alc260_acer_capture_sources),
+		.input_mux = alc260_acer_capture_sources,
+	},
+	[ALC260_FAVORIT100] = {
+		.mixers = { alc260_favorit100_mixer },
+		.init_verbs = { alc260_favorit100_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc260_dac_nids),
+		.dac_nids = alc260_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc260_dual_adc_nids),
+		.adc_nids = alc260_dual_adc_nids,
+		.num_channel_mode = ARRAY_SIZE(alc260_modes),
+		.channel_mode = alc260_modes,
+		.num_mux_defs = ARRAY_SIZE(alc260_favorit100_capture_sources),
+		.input_mux = alc260_favorit100_capture_sources,
+	},
+	[ALC260_WILL] = {
+		.mixers = { alc260_will_mixer },
+		.init_verbs = { alc260_init_verbs, alc260_will_verbs },
+		.num_dacs = ARRAY_SIZE(alc260_dac_nids),
+		.dac_nids = alc260_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc260_adc_nids),
+		.adc_nids = alc260_adc_nids,
+		.dig_out_nid = ALC260_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc260_modes),
+		.channel_mode = alc260_modes,
+		.input_mux = &alc260_capture_source,
+	},
+	[ALC260_REPLACER_672V] = {
+		.mixers = { alc260_replacer_672v_mixer },
+		.init_verbs = { alc260_init_verbs, alc260_replacer_672v_verbs },
+		.num_dacs = ARRAY_SIZE(alc260_dac_nids),
+		.dac_nids = alc260_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc260_adc_nids),
+		.adc_nids = alc260_adc_nids,
+		.dig_out_nid = ALC260_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc260_modes),
+		.channel_mode = alc260_modes,
+		.input_mux = &alc260_capture_source,
+		.unsol_event = alc260_replacer_672v_unsol_event,
+		.init_hook = alc260_replacer_672v_automute,
+	},
+#ifdef CONFIG_SND_DEBUG
+	[ALC260_TEST] = {
+		.mixers = { alc260_test_mixer },
+		.init_verbs = { alc260_test_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc260_test_dac_nids),
+		.dac_nids = alc260_test_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc260_test_adc_nids),
+		.adc_nids = alc260_test_adc_nids,
+		.num_channel_mode = ARRAY_SIZE(alc260_modes),
+		.channel_mode = alc260_modes,
+		.num_mux_defs = ARRAY_SIZE(alc260_test_capture_sources),
+		.input_mux = alc260_test_capture_sources,
+	},
+#endif
+};
+
+static int patch_alc260(struct hda_codec *codec)
+{
+	struct alc_spec *spec;
+	int err, board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	board_config = snd_hda_check_board_config(codec, ALC260_MODEL_LAST,
+						  alc260_models,
+						  alc260_cfg_tbl);
+	if (board_config < 0) {
+		snd_printd(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+			   codec->chip_name);
+		board_config = ALC260_AUTO;
+	}
+
+	if (board_config == ALC260_AUTO)
+		alc_pick_fixup(codec, alc260_fixup_tbl, alc260_fixups, 1);
+
+	if (board_config == ALC260_AUTO) {
+		/* automatic parse from the BIOS config */
+		err = alc260_parse_auto_config(codec);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		} else if (!err) {
+			printk(KERN_INFO
+			       "hda_codec: Cannot set up configuration "
+			       "from BIOS.  Using base mode...\n");
+			board_config = ALC260_BASIC;
+		}
+	}
+
+	err = snd_hda_attach_beep_device(codec, 0x1);
+	if (err < 0) {
+		alc_free(codec);
+		return err;
+	}
+
+	if (board_config != ALC260_AUTO)
+		setup_preset(codec, &alc260_presets[board_config]);
+
+	spec->stream_analog_playback = &alc260_pcm_analog_playback;
+	spec->stream_analog_capture = &alc260_pcm_analog_capture;
+	spec->stream_analog_alt_capture = &alc260_pcm_analog_capture;
+
+	spec->stream_digital_playback = &alc260_pcm_digital_playback;
+	spec->stream_digital_capture = &alc260_pcm_digital_capture;
+
+	if (!spec->adc_nids && spec->input_mux) {
+		/* check whether NID 0x04 is valid */
+		unsigned int wcap = get_wcaps(codec, 0x04);
+		wcap = get_wcaps_type(wcap);
+		/* get type */
+		if (wcap != AC_WID_AUD_IN || spec->input_mux->num_items == 1) {
+			spec->adc_nids = alc260_adc_nids_alt;
+			spec->num_adc_nids = ARRAY_SIZE(alc260_adc_nids_alt);
+		} else {
+			spec->adc_nids = alc260_adc_nids;
+			spec->num_adc_nids = ARRAY_SIZE(alc260_adc_nids);
+		}
+	}
+	set_capture_mixer(codec);
+	set_beep_amp(spec, 0x07, 0x05, HDA_INPUT);
+
+	if (board_config == ALC260_AUTO)
+		alc_pick_fixup(codec, alc260_fixup_tbl, alc260_fixups, 0);
+
+	spec->vmaster_nid = 0x08;
+
+	codec->patch_ops = alc_patch_ops;
+	if (board_config == ALC260_AUTO)
+		spec->init_hook = alc260_auto_init;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	if (!spec->loopback.amplist)
+		spec->loopback.amplist = alc260_loopbacks;
+#endif
+
+	return 0;
+}
+
+
+/*
+ * ALC882/883/885/888/889 support
+ *
+ * ALC882 is almost identical with ALC880 but has cleaner and more flexible
+ * configuration.  Each pin widget can choose any input DACs and a mixer.
+ * Each ADC is connected from a mixer of all inputs.  This makes possible
+ * 6-channel independent captures.
+ *
+ * In addition, an independent DAC for the multi-playback (not used in this
+ * driver yet).
+ */
+#define ALC882_DIGOUT_NID	0x06
+#define ALC882_DIGIN_NID	0x0a
+#define ALC883_DIGOUT_NID	ALC882_DIGOUT_NID
+#define ALC883_DIGIN_NID	ALC882_DIGIN_NID
+#define ALC1200_DIGOUT_NID	0x10
+
+
+static struct hda_channel_mode alc882_ch_modes[1] = {
+	{ 8, NULL }
+};
+
+/* DACs */
+static hda_nid_t alc882_dac_nids[4] = {
+	/* front, rear, clfe, rear_surr */
+	0x02, 0x03, 0x04, 0x05
+};
+#define alc883_dac_nids		alc882_dac_nids
+
+/* ADCs */
+#define alc882_adc_nids		alc880_adc_nids
+#define alc882_adc_nids_alt	alc880_adc_nids_alt
+#define alc883_adc_nids		alc882_adc_nids_alt
+static hda_nid_t alc883_adc_nids_alt[1] = { 0x08 };
+static hda_nid_t alc883_adc_nids_rev[2] = { 0x09, 0x08 };
+#define alc889_adc_nids		alc880_adc_nids
+
+static hda_nid_t alc882_capsrc_nids[3] = { 0x24, 0x23, 0x22 };
+static hda_nid_t alc882_capsrc_nids_alt[2] = { 0x23, 0x22 };
+#define alc883_capsrc_nids	alc882_capsrc_nids_alt
+static hda_nid_t alc883_capsrc_nids_rev[2] = { 0x22, 0x23 };
+#define alc889_capsrc_nids	alc882_capsrc_nids
+
+/* input MUX */
+/* FIXME: should be a matrix-type input source selection */
+
+static struct hda_input_mux alc882_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x1 },
+		{ "Line", 0x2 },
+		{ "CD", 0x4 },
+	},
+};
+
+#define alc883_capture_source	alc882_capture_source
+
+static struct hda_input_mux alc889_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Front Mic", 0x0 },
+		{ "Mic", 0x3 },
+		{ "Line", 0x2 },
+	},
+};
+
+static struct hda_input_mux mb5_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x1 },
+		{ "Line", 0x7 },
+		{ "CD", 0x4 },
+	},
+};
+
+static struct hda_input_mux macmini3_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Line", 0x2 },
+		{ "CD", 0x4 },
+	},
+};
+
+static struct hda_input_mux alc883_3stack_6ch_intel = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x1 },
+		{ "Front Mic", 0x0 },
+		{ "Line", 0x2 },
+		{ "CD", 0x4 },
+	},
+};
+
+static struct hda_input_mux alc883_lenovo_101e_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Mic", 0x1 },
+		{ "Line", 0x2 },
+	},
+};
+
+static struct hda_input_mux alc883_lenovo_nb0763_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Int Mic", 0x1 },
+		{ "Line", 0x2 },
+		{ "CD", 0x4 },
+	},
+};
+
+static struct hda_input_mux alc883_fujitsu_pi2515_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Int Mic", 0x1 },
+	},
+};
+
+static struct hda_input_mux alc883_lenovo_sky_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x1 },
+		{ "Line", 0x4 },
+	},
+};
+
+static struct hda_input_mux alc883_asus_eee1601_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Line", 0x2 },
+	},
+};
+
+static struct hda_input_mux alc889A_mb31_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Mic", 0x0 },
+		/* Front Mic (0x01) unused */
+		{ "Line", 0x2 },
+		/* Line 2 (0x03) unused */
+		/* CD (0x04) unused? */
+	},
+};
+
+static struct hda_input_mux alc889A_imac91_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Mic", 0x01 },
+		{ "Line", 0x2 }, /* Not sure! */
+	},
+};
+
+/*
+ * 2ch mode
+ */
+static struct hda_channel_mode alc883_3ST_2ch_modes[1] = {
+	{ 2, NULL }
+};
+
+/*
+ * 2ch mode
+ */
+static struct hda_verb alc882_3ST_ch2_init[] = {
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ } /* end */
+};
+
+/*
+ * 4ch mode
+ */
+static struct hda_verb alc882_3ST_ch4_init[] = {
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc882_3ST_ch6_init[] = {
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x18, AC_VERB_SET_CONNECT_SEL, 0x02 },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc882_3ST_6ch_modes[3] = {
+	{ 2, alc882_3ST_ch2_init },
+	{ 4, alc882_3ST_ch4_init },
+	{ 6, alc882_3ST_ch6_init },
+};
+
+#define alc883_3ST_6ch_modes	alc882_3ST_6ch_modes
+
+/*
+ * 2ch mode
+ */
+static struct hda_verb alc883_3ST_ch2_clevo_init[] = {
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ } /* end */
+};
+
+/*
+ * 4ch mode
+ */
+static struct hda_verb alc883_3ST_ch4_clevo_init[] = {
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc883_3ST_ch6_clevo_init[] = {
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x18, AC_VERB_SET_CONNECT_SEL, 0x02 },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc883_3ST_6ch_clevo_modes[3] = {
+	{ 2, alc883_3ST_ch2_clevo_init },
+	{ 4, alc883_3ST_ch4_clevo_init },
+	{ 6, alc883_3ST_ch6_clevo_init },
+};
+
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc882_sixstack_ch6_init[] = {
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 },
+	{ 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ } /* end */
+};
+
+/*
+ * 8ch mode
+ */
+static struct hda_verb alc882_sixstack_ch8_init[] = {
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc882_sixstack_modes[2] = {
+	{ 6, alc882_sixstack_ch6_init },
+	{ 8, alc882_sixstack_ch8_init },
+};
+
+
+/* Macbook Air 2,1 */
+
+static struct hda_channel_mode alc885_mba21_ch_modes[1] = {
+      { 2, NULL },
+};
+
+/*
+ * macbook pro ALC885 can switch LineIn to LineOut without losing Mic
+ */
+
+/*
+ * 2ch mode
+ */
+static struct hda_verb alc885_mbp_ch2_init[] = {
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{ 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{ } /* end */
+};
+
+/*
+ * 4ch mode
+ */
+static struct hda_verb alc885_mbp_ch4_init[] = {
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{ 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc885_mbp_4ch_modes[2] = {
+	{ 2, alc885_mbp_ch2_init },
+	{ 4, alc885_mbp_ch4_init },
+};
+
+/*
+ * 2ch
+ * Speakers/Woofer/HP = Front
+ * LineIn = Input
+ */
+static struct hda_verb alc885_mb5_ch2_init[] = {
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{ } /* end */
+};
+
+/*
+ * 6ch mode
+ * Speakers/HP = Front
+ * Woofer = LFE
+ * LineIn = Surround
+ */
+static struct hda_verb alc885_mb5_ch6_init[] = {
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc885_mb5_6ch_modes[2] = {
+	{ 2, alc885_mb5_ch2_init },
+	{ 6, alc885_mb5_ch6_init },
+};
+
+#define alc885_macmini3_6ch_modes	alc885_mb5_6ch_modes
+
+/*
+ * 2ch mode
+ */
+static struct hda_verb alc883_4ST_ch2_init[] = {
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ } /* end */
+};
+
+/*
+ * 4ch mode
+ */
+static struct hda_verb alc883_4ST_ch4_init[] = {
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc883_4ST_ch6_init[] = {
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x18, AC_VERB_SET_CONNECT_SEL, 0x02 },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+/*
+ * 8ch mode
+ */
+static struct hda_verb alc883_4ST_ch8_init[] = {
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x17, AC_VERB_SET_CONNECT_SEL, 0x03 },
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x18, AC_VERB_SET_CONNECT_SEL, 0x02 },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc883_4ST_8ch_modes[4] = {
+	{ 2, alc883_4ST_ch2_init },
+	{ 4, alc883_4ST_ch4_init },
+	{ 6, alc883_4ST_ch6_init },
+	{ 8, alc883_4ST_ch8_init },
+};
+
+
+/*
+ * 2ch mode
+ */
+static struct hda_verb alc883_3ST_ch2_intel_init[] = {
+	{ 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ } /* end */
+};
+
+/*
+ * 4ch mode
+ */
+static struct hda_verb alc883_3ST_ch4_intel_init[] = {
+	{ 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc883_3ST_ch6_intel_init[] = {
+	{ 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x19, AC_VERB_SET_CONNECT_SEL, 0x02 },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc883_3ST_6ch_intel_modes[3] = {
+	{ 2, alc883_3ST_ch2_intel_init },
+	{ 4, alc883_3ST_ch4_intel_init },
+	{ 6, alc883_3ST_ch6_intel_init },
+};
+
+/*
+ * 2ch mode
+ */
+static struct hda_verb alc889_ch2_intel_init[] = {
+	{ 0x14, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	{ 0x19, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	{ 0x16, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	{ 0x17, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ } /* end */
+};
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc889_ch6_intel_init[] = {
+	{ 0x14, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	{ 0x19, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ 0x16, AC_VERB_SET_CONNECT_SEL, 0x02 },
+	{ 0x17, AC_VERB_SET_CONNECT_SEL, 0x03 },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ } /* end */
+};
+
+/*
+ * 8ch mode
+ */
+static struct hda_verb alc889_ch8_intel_init[] = {
+	{ 0x14, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	{ 0x19, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ 0x16, AC_VERB_SET_CONNECT_SEL, 0x02 },
+	{ 0x17, AC_VERB_SET_CONNECT_SEL, 0x03 },
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x03 },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc889_8ch_intel_modes[3] = {
+	{ 2, alc889_ch2_intel_init },
+	{ 6, alc889_ch6_intel_init },
+	{ 8, alc889_ch8_intel_init },
+};
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc883_sixstack_ch6_init[] = {
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 },
+	{ 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ } /* end */
+};
+
+/*
+ * 8ch mode
+ */
+static struct hda_verb alc883_sixstack_ch8_init[] = {
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc883_sixstack_modes[2] = {
+	{ 6, alc883_sixstack_ch6_init },
+	{ 8, alc883_sixstack_ch8_init },
+};
+
+
+/* Pin assignment: Front=0x14, Rear=0x15, CLFE=0x16, Side=0x17
+ *                 Mic=0x18, Front Mic=0x19, Line-In=0x1a, HP=0x1b
+ */
+static struct snd_kcontrol_new alc882_base_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+/* Macbook Air 2,1 same control for HP and internal Speaker */
+
+static struct snd_kcontrol_new alc885_mba21_mixer[] = {
+      HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0c, 0x00, HDA_OUTPUT),
+      HDA_BIND_MUTE("Speaker Playback Switch", 0x0c, 0x02, HDA_OUTPUT),
+     { }
+};
+
+
+static struct snd_kcontrol_new alc885_mbp3_mixer[] = {
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0c, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE   ("Speaker Playback Switch", 0x0c, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0e, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE   ("Headphone Playback Switch", 0x0e, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x00, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE  ("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE  ("Mic Playback Switch", 0x0b, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Boost", 0x1a, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0x00, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc885_mb5_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE   ("Front Playback Switch", 0x0c, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE   ("Surround Playback Switch", 0x0d, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("LFE Playback Volume", 0x0e, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE   ("LFE Playback Switch", 0x0e, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0f, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE   ("Headphone Playback Switch", 0x0f, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x07, HDA_INPUT),
+	HDA_CODEC_MUTE  ("Line Playback Switch", 0x0b, 0x07, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE  ("Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Boost", 0x15, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x19, 0x00, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc885_macmini3_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE   ("Front Playback Switch", 0x0c, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE   ("Surround Playback Switch", 0x0d, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("LFE Playback Volume", 0x0e, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE   ("LFE Playback Switch", 0x0e, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0f, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE   ("Headphone Playback Switch", 0x0f, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x07, HDA_INPUT),
+	HDA_CODEC_MUTE  ("Line Playback Switch", 0x0b, 0x07, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Boost", 0x15, 0x00, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc885_imac91_mixer[] = {
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0c, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x0c, 0x02, HDA_INPUT),
+	{ } /* end */
+};
+
+
+static struct snd_kcontrol_new alc882_w2jc_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc882_targa_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+/* Pin assignment: Front=0x14, HP = 0x15, Front = 0x16, ???
+ *                 Front Mic=0x18, Line In = 0x1a, Line In = 0x1b, CD = 0x1c
+ */
+static struct snd_kcontrol_new alc882_asus_a7j_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Mobile Front Playback Switch", 0x16, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mobile Line Playback Volume", 0x0b, 0x03, HDA_INPUT),
+	HDA_CODEC_MUTE("Mobile Line Playback Switch", 0x0b, 0x03, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc882_asus_a7m_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc882_chmode_mixer[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb alc882_base_init_verbs[] = {
+	/* Front mixer: unmute input/output amp left and right (volume = 0) */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Rear mixer */
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* CLFE mixer */
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Side mixer */
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+
+	/* Front Pin: output 0 (0x0c) */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* Rear Pin: output 1 (0x0d) */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+	/* CLFE Pin: output 2 (0x0e) */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x02},
+	/* Side Pin: output 3 (0x0f) */
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_CONNECT_SEL, 0x03},
+	/* Mic (rear) pin: input vref at 80% */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Front Mic pin: input vref at 80% */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line In pin: input */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line-2 In: Headphone output (output 0 - 0x0c) */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* CD pin widget for input */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	/* FIXME: use matrix-type input source selection */
+	/* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */
+	/* Input mixer2 */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Input mixer3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* ADC2: mute amp left and right */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* ADC3: mute amp left and right */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	{ }
+};
+
+static struct hda_verb alc882_adc1_init_verbs[] = {
+	/* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* ADC1: mute amp left and right */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{ }
+};
+
+static struct hda_verb alc882_eapd_verbs[] = {
+	/* change to EAPD mode */
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x07},
+	{0x20, AC_VERB_SET_PROC_COEF, 0x3060},
+	{ }
+};
+
+static struct hda_verb alc889_eapd_verbs[] = {
+	{0x14, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{0x15, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{ }
+};
+
+static struct hda_verb alc_hp15_unsol_verbs[] = {
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{}
+};
+
+static struct hda_verb alc885_init_verbs[] = {
+	/* Front mixer: unmute input/output amp left and right (volume = 0) */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Rear mixer */
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* CLFE mixer */
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* Side mixer */
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* Front HP Pin: output 0 (0x0c) */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* Front Pin: output 0 (0x0c) */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* Rear Pin: output 1 (0x0d) */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x19, AC_VERB_SET_CONNECT_SEL, 0x01},
+	/* CLFE Pin: output 2 (0x0e) */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x02},
+	/* Side Pin: output 3 (0x0f) */
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_CONNECT_SEL, 0x03},
+	/* Mic (rear) pin: input vref at 80% */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Front Mic pin: input vref at 80% */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line In pin: input */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+
+	/* Mixer elements: 0x18, , 0x1a, 0x1b */
+	/* Input mixer1 */
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Input mixer2 */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Input mixer3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* ADC2: mute amp left and right */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	/* ADC3: mute amp left and right */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+
+	{ }
+};
+
+static struct hda_verb alc885_init_input_verbs[] = {
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+	{ }
+};
+
+
+/* Unmute Selector 24h and set the default input to front mic */
+static struct hda_verb alc889_init_input_verbs[] = {
+	{0x24, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{ }
+};
+
+
+#define alc883_init_verbs	alc882_base_init_verbs
+
+/* Mac Pro test */
+static struct snd_kcontrol_new alc882_macpro_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x18, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	/* FIXME: this looks suspicious...
+	HDA_CODEC_VOLUME("Beep Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Beep Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	*/
+	{ } /* end */
+};
+
+static struct hda_verb alc882_macpro_init_verbs[] = {
+	/* Front mixer: unmute input/output amp left and right (volume = 0) */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Front Pin: output 0 (0x0c) */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* Front Mic pin: input vref at 80% */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Speaker:  output */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x04},
+	/* Headphone output (output 0 - 0x0c) */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* FIXME: use matrix-type input source selection */
+	/* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */
+	/* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* Input mixer2 */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* Input mixer3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* ADC1: mute amp left and right */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* ADC2: mute amp left and right */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* ADC3: mute amp left and right */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	{ }
+};
+
+/* Macbook 5,1 */
+static struct hda_verb alc885_mb5_init_verbs[] = {
+	/* DACs */
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Front mixer */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Surround mixer */
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* LFE mixer */
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* HP mixer */
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Front Pin (0x0c) */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x01},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* LFE Pin (0x0e) */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x01},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x02},
+	/* HP Pin (0x0f) */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x03},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	/* Front Mic pin: input vref at 80% */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line In pin */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0x1)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0x7)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0x4)},
+	{ }
+};
+
+/* Macmini 3,1 */
+static struct hda_verb alc885_macmini3_init_verbs[] = {
+	/* DACs */
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Front mixer */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Surround mixer */
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* LFE mixer */
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* HP mixer */
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Front Pin (0x0c) */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x01},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* LFE Pin (0x0e) */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x01},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x02},
+	/* HP Pin (0x0f) */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x03},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	/* Line In pin */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{ }
+};
+
+
+static struct hda_verb alc885_mba21_init_verbs[] = {
+	/*Internal and HP Speaker Mixer*/
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/*Internal Speaker Pin (0x0c)*/
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, (PIN_OUT | AC_PINCTL_VREF_50) },
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* HP Pin: output 0 (0x0e) */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc4},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, (ALC880_HP_EVENT | AC_USRSP_EN)},
+	/* Line in (is hp when jack connected)*/
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, AC_PINCTL_VREF_50},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+
+	{ }
+ };
+
+
+/* Macbook Pro rev3 */
+static struct hda_verb alc885_mbp3_init_verbs[] = {
+	/* Front mixer: unmute input/output amp left and right (volume = 0) */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Rear mixer */
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* HP mixer */
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Front Pin: output 0 (0x0c) */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* HP Pin: output 0 (0x0e) */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc4},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x02},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	/* Mic (rear) pin: input vref at 80% */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Front Mic pin: input vref at 80% */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line In pin: use output 1 when in LineOut mode */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x01},
+
+	/* FIXME: use matrix-type input source selection */
+	/* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */
+	/* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* Input mixer2 */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* Input mixer3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* ADC1: mute amp left and right */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* ADC2: mute amp left and right */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* ADC3: mute amp left and right */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	{ }
+};
+
+/* iMac 9,1 */
+static struct hda_verb alc885_imac91_init_verbs[] = {
+	/* Internal Speaker Pin (0x0c) */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, (PIN_OUT | AC_PINCTL_VREF_50) },
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, (PIN_OUT | AC_PINCTL_VREF_50) },
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* HP Pin: Rear */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, (ALC880_HP_EVENT | AC_USRSP_EN)},
+	/* Line in Rear */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, AC_PINCTL_VREF_50},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Front Mic pin: input vref at 80% */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Rear mixer */
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* Line-Out mixer: unmute input/output amp left and right (volume = 0) */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* 0x24 [Audio Mixer] wcaps 0x20010b: Stereo Amp-In */
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* 0x23 [Audio Mixer] wcaps 0x20010b: Stereo Amp-In */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* 0x22 [Audio Mixer] wcaps 0x20010b: Stereo Amp-In */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	/* 0x07 [Audio Input] wcaps 0x10011b: Stereo Amp-In */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* 0x08 [Audio Input] wcaps 0x10011b: Stereo Amp-In */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* 0x09 [Audio Input] wcaps 0x10011b: Stereo Amp-In */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{ }
+};
+
+/* iMac 24 mixer. */
+static struct snd_kcontrol_new alc885_imac24_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x00, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Master Playback Switch", 0x0c, 0x00, HDA_INPUT),
+	{ } /* end */
+};
+
+/* iMac 24 init verbs. */
+static struct hda_verb alc885_imac24_init_verbs[] = {
+	/* Internal speakers: output 0 (0x0c) */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* Internal speakers: output 0 (0x0c) */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* Headphone: output 0 (0x0c) */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	/* Front Mic: input vref at 80% */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{ }
+};
+
+/* Toggle speaker-output according to the hp-jack state */
+static void alc885_imac24_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[0] = 0x18;
+	spec->autocfg.speaker_pins[1] = 0x1a;
+}
+
+#define alc885_mb5_setup	alc885_imac24_setup
+#define alc885_macmini3_setup	alc885_imac24_setup
+
+/* Macbook Air 2,1 */
+static void alc885_mba21_setup(struct hda_codec *codec)
+{
+       struct alc_spec *spec = codec->spec;
+
+       spec->autocfg.hp_pins[0] = 0x14;
+       spec->autocfg.speaker_pins[0] = 0x18;
+}
+
+
+
+static void alc885_mbp3_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+}
+
+static void alc885_imac91_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[0] = 0x18;
+	spec->autocfg.speaker_pins[1] = 0x1a;
+}
+
+static struct hda_verb alc882_targa_verbs[] = {
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x01}, /* line/surround */
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{ } /* end */
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc882_targa_automute(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc_automute_amp(codec);
+	snd_hda_codec_write_cache(codec, 1, 0, AC_VERB_SET_GPIO_DATA,
+				  spec->jack_present ? 1 : 3);
+}
+
+static void alc882_targa_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[0] = 0x1b;
+}
+
+static void alc882_targa_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	if ((res >> 26) == ALC880_HP_EVENT)
+		alc882_targa_automute(codec);
+}
+
+static struct hda_verb alc882_asus_a7j_verbs[] = {
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Front */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Front */
+
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x01}, /* line/surround */
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+	{ } /* end */
+};
+
+static struct hda_verb alc882_asus_a7m_verbs[] = {
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Front */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Front */
+
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x01}, /* line/surround */
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */
+ 	{ } /* end */
+};
+
+static void alc882_gpio_mute(struct hda_codec *codec, int pin, int muted)
+{
+	unsigned int gpiostate, gpiomask, gpiodir;
+
+	gpiostate = snd_hda_codec_read(codec, codec->afg, 0,
+				       AC_VERB_GET_GPIO_DATA, 0);
+
+	if (!muted)
+		gpiostate |= (1 << pin);
+	else
+		gpiostate &= ~(1 << pin);
+
+	gpiomask = snd_hda_codec_read(codec, codec->afg, 0,
+				      AC_VERB_GET_GPIO_MASK, 0);
+	gpiomask |= (1 << pin);
+
+	gpiodir = snd_hda_codec_read(codec, codec->afg, 0,
+				     AC_VERB_GET_GPIO_DIRECTION, 0);
+	gpiodir |= (1 << pin);
+
+
+	snd_hda_codec_write(codec, codec->afg, 0,
+			    AC_VERB_SET_GPIO_MASK, gpiomask);
+	snd_hda_codec_write(codec, codec->afg, 0,
+			    AC_VERB_SET_GPIO_DIRECTION, gpiodir);
+
+	msleep(1);
+
+	snd_hda_codec_write(codec, codec->afg, 0,
+			    AC_VERB_SET_GPIO_DATA, gpiostate);
+}
+
+/* set up GPIO at initialization */
+static void alc885_macpro_init_hook(struct hda_codec *codec)
+{
+	alc882_gpio_mute(codec, 0, 0);
+	alc882_gpio_mute(codec, 1, 0);
+}
+
+/* set up GPIO and update auto-muting at initialization */
+static void alc885_imac24_init_hook(struct hda_codec *codec)
+{
+	alc885_macpro_init_hook(codec);
+	alc_automute_amp(codec);
+}
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc883_auto_init_verbs[] = {
+	/*
+	 * Unmute ADC0-2 and set the default input to mic-in
+	 */
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/*
+	 * Set up output mixers (0x0c - 0x0f)
+	 */
+	/* set vol=0 to output mixers */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* set up input amps for analog loopback */
+	/* Amp Indices: DAC = 0, mixer = 1 */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* FIXME: use matrix-type input source selection */
+	/* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */
+	/* Input mixer2 */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Input mixer3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{ }
+};
+
+/* 2ch mode (Speaker:front, Subwoofer:CLFE, Line:input, Headphones:front) */
+static struct hda_verb alc889A_mb31_ch2_init[] = {
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},             /* HP as front */
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Subwoofer on */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},    /* Line as input */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},   /* Line off */
+	{ } /* end */
+};
+
+/* 4ch mode (Speaker:front, Subwoofer:CLFE, Line:CLFE, Headphones:front) */
+static struct hda_verb alc889A_mb31_ch4_init[] = {
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},             /* HP as front */
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Subwoofer on */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},   /* Line as output */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Line on */
+	{ } /* end */
+};
+
+/* 5ch mode (Speaker:front, Subwoofer:CLFE, Line:input, Headphones:rear) */
+static struct hda_verb alc889A_mb31_ch5_init[] = {
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},             /* HP as rear */
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Subwoofer on */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},    /* Line as input */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},   /* Line off */
+	{ } /* end */
+};
+
+/* 6ch mode (Speaker:front, Subwoofer:off, Line:CLFE, Headphones:Rear) */
+static struct hda_verb alc889A_mb31_ch6_init[] = {
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},             /* HP as front */
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},   /* Subwoofer off */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},   /* Line as output */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Line on */
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc889A_mb31_6ch_modes[4] = {
+	{ 2, alc889A_mb31_ch2_init },
+	{ 4, alc889A_mb31_ch4_init },
+	{ 5, alc889A_mb31_ch5_init },
+	{ 6, alc889A_mb31_ch6_init },
+};
+
+static struct hda_verb alc883_medion_eapd_verbs[] = {
+        /* eanable EAPD on medion laptop */
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x07},
+	{0x20, AC_VERB_SET_PROC_COEF, 0x3070},
+	{ }
+};
+
+#define alc883_base_mixer	alc882_base_mixer
+
+static struct snd_kcontrol_new alc883_mitac_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_clevo_m720_mixer[] = {
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_2ch_fujitsu_pi2515_mixer[] = {
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_3ST_2ch_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_3ST_6ch_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_3ST_6ch_intel_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0,
+			      HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc885_8ch_intel_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0,
+			      HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x0f, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x3, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x1b, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x3, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_fivestack_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_targa_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_targa_2ch_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_targa_8ch_mixer[] = {
+	HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_lenovo_101e_2ch_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_lenovo_nb0763_mixer[] = {
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_medion_md2_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_medion_wim2160_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x08, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x08, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_verb alc883_medion_wim2160_verbs[] = {
+	/* Unmute front mixer */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* Set speaker pin to front mixer */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Init headphone pin */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+
+	{ } /* end */
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc883_medion_wim2160_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x1a;
+	spec->autocfg.speaker_pins[0] = 0x15;
+}
+
+static struct snd_kcontrol_new alc883_acer_aspire_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc888_acer_aspire_6530_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("LFE Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc888_lenovo_sky_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0e, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0e, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume",
+						0x0d, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0d, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0d, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0d, 2, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc889A_mb31_mixer[] = {
+	/* Output mixers */
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x00,
+		HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x00, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 0x02, HDA_INPUT),
+	/* Output switches */
+	HDA_CODEC_MUTE("Enable Speaker", 0x14, 0x00, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Enable Headphones", 0x15, 0x00, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Enable LFE", 0x16, 2, 0x00, HDA_OUTPUT),
+	/* Boost mixers */
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Boost", 0x1a, 0x00, HDA_INPUT),
+	/* Input mixers */
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x00, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x00, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_vaiott_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_bind_ctls alc883_bind_cap_vol = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x08, 3, 0, HDA_INPUT),
+		HDA_COMPOSE_AMP_VAL(0x09, 3, 0, HDA_INPUT),
+		0
+	},
+};
+
+static struct hda_bind_ctls alc883_bind_cap_switch = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x08, 3, 0, HDA_INPUT),
+		HDA_COMPOSE_AMP_VAL(0x09, 3, 0, HDA_INPUT),
+		0
+	},
+};
+
+static struct snd_kcontrol_new alc883_asus_eee1601_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_asus_eee1601_cap_mixer[] = {
+	HDA_BIND_VOL("Capture Volume", &alc883_bind_cap_vol),
+	HDA_BIND_SW("Capture Switch", &alc883_bind_cap_switch),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 1,
+		.info = alc_mux_enum_info,
+		.get = alc_mux_enum_get,
+		.put = alc_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc883_chmode_mixer[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc883_mitac_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x17;
+}
+
+/* auto-toggle front mic */
+/*
+static void alc883_mitac_mic_automute(struct hda_codec *codec)
+{
+	unsigned char bits = snd_hda_jack_detect(codec, 0x18) ? HDA_AMP_MUTE : 0;
+
+	snd_hda_codec_amp_stereo(codec, 0x0b, HDA_INPUT, 1, HDA_AMP_MUTE, bits);
+}
+*/
+
+static struct hda_verb alc883_mitac_verbs[] = {
+	/* HP */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Subwoofer */
+	{0x17, AC_VERB_SET_CONNECT_SEL, 0x02},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	/* enable unsolicited event */
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	/* {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT | AC_USRSP_EN}, */
+
+	{ } /* end */
+};
+
+static struct hda_verb alc883_clevo_m540r_verbs[] = {
+	/* HP */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Int speaker */
+	/*{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},*/
+
+	/* enable unsolicited event */
+	/*
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT | AC_USRSP_EN},
+	*/
+
+	{ } /* end */
+};
+
+static struct hda_verb alc883_clevo_m720_verbs[] = {
+	/* HP */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Int speaker */
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	/* enable unsolicited event */
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT | AC_USRSP_EN},
+
+	{ } /* end */
+};
+
+static struct hda_verb alc883_2ch_fujitsu_pi2515_verbs[] = {
+	/* HP */
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* Subwoofer */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	/* enable unsolicited event */
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+
+	{ } /* end */
+};
+
+static struct hda_verb alc883_targa_verbs[] = {
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+/* Connect Line-Out side jack (SPDIF) to Side */
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_CONNECT_SEL, 0x03},
+/* Connect Mic jack to CLFE */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x02},
+/* Connect Line-in jack to Surround */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x01},
+/* Connect HP out jack to Front */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+
+	{ } /* end */
+};
+
+static struct hda_verb alc883_lenovo_101e_verbs[] = {
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_FRONT_EVENT|AC_USRSP_EN},
+        {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT|AC_USRSP_EN},
+	{ } /* end */
+};
+
+static struct hda_verb alc883_lenovo_nb0763_verbs[] = {
+        {0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+        {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+        {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{ } /* end */
+};
+
+static struct hda_verb alc888_lenovo_ms7195_verbs[] = {
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_FRONT_EVENT | AC_USRSP_EN},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT    | AC_USRSP_EN},
+	{ } /* end */
+};
+
+static struct hda_verb alc883_haier_w66_verbs[] = {
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{ } /* end */
+};
+
+static struct hda_verb alc888_lenovo_sky_verbs[] = {
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{ } /* end */
+};
+
+static struct hda_verb alc888_6st_dell_verbs[] = {
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{ }
+};
+
+static struct hda_verb alc883_vaiott_verbs[] = {
+	/* HP */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+
+	/* enable unsolicited event */
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+
+	{ } /* end */
+};
+
+static void alc888_3st_hp_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x1b;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x16;
+	spec->autocfg.speaker_pins[2] = 0x18;
+}
+
+static struct hda_verb alc888_3st_hp_verbs[] = {
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},	/* Front: output 0 (0x0c) */
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x01},	/* Rear : output 1 (0x0d) */
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x02},	/* CLFE : output 2 (0x0e) */
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{ } /* end */
+};
+
+/*
+ * 2ch mode
+ */
+static struct hda_verb alc888_3st_hp_2ch_init[] = {
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ } /* end */
+};
+
+/*
+ * 4ch mode
+ */
+static struct hda_verb alc888_3st_hp_4ch_init[] = {
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x16, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc888_3st_hp_6ch_init[] = {
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x18, AC_VERB_SET_CONNECT_SEL, 0x02 },
+	{ 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x16, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc888_3st_hp_modes[3] = {
+	{ 2, alc888_3st_hp_2ch_init },
+	{ 4, alc888_3st_hp_4ch_init },
+	{ 6, alc888_3st_hp_6ch_init },
+};
+
+/* toggle front-jack and RCA according to the hp-jack state */
+static void alc888_lenovo_ms7195_front_automute(struct hda_codec *codec)
+{
+ 	unsigned int present = snd_hda_jack_detect(codec, 0x1b);
+
+	snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+	snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+}
+
+/* toggle RCA according to the front-jack state */
+static void alc888_lenovo_ms7195_rca_automute(struct hda_codec *codec)
+{
+ 	unsigned int present = snd_hda_jack_detect(codec, 0x14);
+
+	snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+}
+
+static void alc883_lenovo_ms7195_unsol_event(struct hda_codec *codec,
+					     unsigned int res)
+{
+	if ((res >> 26) == ALC880_HP_EVENT)
+		alc888_lenovo_ms7195_front_automute(codec);
+	if ((res >> 26) == ALC880_FRONT_EVENT)
+		alc888_lenovo_ms7195_rca_automute(codec);
+}
+
+static struct hda_verb alc883_medion_md2_verbs[] = {
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{ } /* end */
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc883_medion_md2_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[0] = 0x15;
+}
+
+/* toggle speaker-output according to the hp-jack state */
+#define alc883_targa_init_hook		alc882_targa_init_hook
+#define alc883_targa_unsol_event	alc882_targa_unsol_event
+
+static void alc883_clevo_m720_mic_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+
+	present = snd_hda_jack_detect(codec, 0x18);
+	snd_hda_codec_amp_stereo(codec, 0x0b, HDA_INPUT, 1,
+				 HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+}
+
+static void alc883_clevo_m720_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+}
+
+static void alc883_clevo_m720_init_hook(struct hda_codec *codec)
+{
+	alc_automute_amp(codec);
+	alc883_clevo_m720_mic_automute(codec);
+}
+
+static void alc883_clevo_m720_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_MIC_EVENT:
+		alc883_clevo_m720_mic_automute(codec);
+		break;
+	default:
+		alc_automute_amp_unsol_event(codec, res);
+		break;
+	}
+}
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc883_2ch_fujitsu_pi2515_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[0] = 0x15;
+}
+
+static void alc883_haier_w66_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x1b;
+	spec->autocfg.speaker_pins[0] = 0x14;
+}
+
+static void alc883_lenovo_101e_ispeaker_automute(struct hda_codec *codec)
+{
+	int bits = snd_hda_jack_detect(codec, 0x14) ? HDA_AMP_MUTE : 0;
+
+	snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+}
+
+static void alc883_lenovo_101e_all_automute(struct hda_codec *codec)
+{
+	int bits = snd_hda_jack_detect(codec, 0x1b) ? HDA_AMP_MUTE : 0;
+
+	snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+}
+
+static void alc883_lenovo_101e_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	if ((res >> 26) == ALC880_HP_EVENT)
+		alc883_lenovo_101e_all_automute(codec);
+	if ((res >> 26) == ALC880_FRONT_EVENT)
+		alc883_lenovo_101e_ispeaker_automute(codec);
+}
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc883_acer_aspire_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[1] = 0x16;
+}
+
+static struct hda_verb alc883_acer_eapd_verbs[] = {
+	/* HP Pin: output 0 (0x0c) */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* Front Pin: output 0 (0x0c) */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x00},
+        /* eanable EAPD on medion laptop */
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x07},
+	{0x20, AC_VERB_SET_PROC_COEF, 0x3050},
+	/* enable unsolicited event */
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{ }
+};
+
+static void alc888_6st_dell_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x1b;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x15;
+	spec->autocfg.speaker_pins[2] = 0x16;
+	spec->autocfg.speaker_pins[3] = 0x17;
+}
+
+static void alc888_lenovo_sky_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x1b;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x15;
+	spec->autocfg.speaker_pins[2] = 0x16;
+	spec->autocfg.speaker_pins[3] = 0x17;
+	spec->autocfg.speaker_pins[4] = 0x1a;
+}
+
+static void alc883_vaiott_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x17;
+}
+
+static struct hda_verb alc888_asus_m90v_verbs[] = {
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* enable unsolicited event */
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT | AC_USRSP_EN},
+	{ } /* end */
+};
+
+static void alc883_mode2_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x1b;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x15;
+	spec->autocfg.speaker_pins[2] = 0x16;
+	spec->ext_mic.pin = 0x18;
+	spec->int_mic.pin = 0x19;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.mux_idx = 1;
+	spec->auto_mic = 1;
+}
+
+static struct hda_verb alc888_asus_eee1601_verbs[] = {
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x0b},
+	{0x20, AC_VERB_SET_PROC_COEF,  0x0838},
+	/* enable unsolicited event */
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{ } /* end */
+};
+
+static void alc883_eee1601_inithook(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[0] = 0x1b;
+	alc_automute_pin(codec);
+}
+
+static struct hda_verb alc889A_mb31_verbs[] = {
+	/* Init rear pin (used as headphone output) */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc4},    /* Apple Headphones */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},           /* Connect to front */
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	/* Init line pin (used as output in 4ch and 6ch mode) */
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x02},           /* Connect to CLFE */
+	/* Init line 2 pin (used as headphone out by default) */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},  /* Use as input */
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* Mute output */
+	{ } /* end */
+};
+
+/* Mute speakers according to the headphone jack state */
+static void alc889A_mb31_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+
+	/* Mute only in 2ch or 4ch mode */
+	if (snd_hda_codec_read(codec, 0x15, 0, AC_VERB_GET_CONNECT_SEL, 0)
+	    == 0x00) {
+		present = snd_hda_jack_detect(codec, 0x15);
+		snd_hda_codec_amp_stereo(codec, 0x14,  HDA_OUTPUT, 0,
+			HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+		snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0,
+			HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+	}
+}
+
+static void alc889A_mb31_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	if ((res >> 26) == ALC880_HP_EVENT)
+		alc889A_mb31_automute(codec);
+}
+
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+#define alc882_loopbacks	alc880_loopbacks
+#endif
+
+/* pcm configuration: identical with ALC880 */
+#define alc882_pcm_analog_playback	alc880_pcm_analog_playback
+#define alc882_pcm_analog_capture	alc880_pcm_analog_capture
+#define alc882_pcm_digital_playback	alc880_pcm_digital_playback
+#define alc882_pcm_digital_capture	alc880_pcm_digital_capture
+
+static hda_nid_t alc883_slave_dig_outs[] = {
+	ALC1200_DIGOUT_NID, 0,
+};
+
+static hda_nid_t alc1200_slave_dig_outs[] = {
+	ALC883_DIGOUT_NID, 0,
+};
+
+/*
+ * configuration and preset
+ */
+static const char *alc882_models[ALC882_MODEL_LAST] = {
+	[ALC882_3ST_DIG]	= "3stack-dig",
+	[ALC882_6ST_DIG]	= "6stack-dig",
+	[ALC882_ARIMA]		= "arima",
+	[ALC882_W2JC]		= "w2jc",
+	[ALC882_TARGA]		= "targa",
+	[ALC882_ASUS_A7J]	= "asus-a7j",
+	[ALC882_ASUS_A7M]	= "asus-a7m",
+	[ALC885_MACPRO]		= "macpro",
+	[ALC885_MB5]		= "mb5",
+	[ALC885_MACMINI3]	= "macmini3",
+	[ALC885_MBA21]		= "mba21",
+	[ALC885_MBP3]		= "mbp3",
+	[ALC885_IMAC24]		= "imac24",
+	[ALC885_IMAC91]		= "imac91",
+	[ALC883_3ST_2ch_DIG]	= "3stack-2ch-dig",
+	[ALC883_3ST_6ch_DIG]	= "3stack-6ch-dig",
+	[ALC883_3ST_6ch]	= "3stack-6ch",
+	[ALC883_6ST_DIG]	= "alc883-6stack-dig",
+	[ALC883_TARGA_DIG]	= "targa-dig",
+	[ALC883_TARGA_2ch_DIG]	= "targa-2ch-dig",
+	[ALC883_TARGA_8ch_DIG]	= "targa-8ch-dig",
+	[ALC883_ACER]		= "acer",
+	[ALC883_ACER_ASPIRE]	= "acer-aspire",
+	[ALC888_ACER_ASPIRE_4930G]	= "acer-aspire-4930g",
+	[ALC888_ACER_ASPIRE_6530G]	= "acer-aspire-6530g",
+	[ALC888_ACER_ASPIRE_8930G]	= "acer-aspire-8930g",
+	[ALC888_ACER_ASPIRE_7730G]	= "acer-aspire-7730g",
+	[ALC883_MEDION]		= "medion",
+	[ALC883_MEDION_MD2]	= "medion-md2",
+	[ALC883_MEDION_WIM2160]	= "medion-wim2160",
+	[ALC883_LAPTOP_EAPD]	= "laptop-eapd",
+	[ALC883_LENOVO_101E_2ch] = "lenovo-101e",
+	[ALC883_LENOVO_NB0763]	= "lenovo-nb0763",
+	[ALC888_LENOVO_MS7195_DIG] = "lenovo-ms7195-dig",
+	[ALC888_LENOVO_SKY] = "lenovo-sky",
+	[ALC883_HAIER_W66] 	= "haier-w66",
+	[ALC888_3ST_HP]		= "3stack-hp",
+	[ALC888_6ST_DELL]	= "6stack-dell",
+	[ALC883_MITAC]		= "mitac",
+	[ALC883_CLEVO_M540R]	= "clevo-m540r",
+	[ALC883_CLEVO_M720]	= "clevo-m720",
+	[ALC883_FUJITSU_PI2515] = "fujitsu-pi2515",
+	[ALC888_FUJITSU_XA3530] = "fujitsu-xa3530",
+	[ALC883_3ST_6ch_INTEL]	= "3stack-6ch-intel",
+	[ALC889A_INTEL]		= "intel-alc889a",
+	[ALC889_INTEL]		= "intel-x58",
+	[ALC1200_ASUS_P5Q]	= "asus-p5q",
+	[ALC889A_MB31]		= "mb31",
+	[ALC883_SONY_VAIO_TT]	= "sony-vaio-tt",
+	[ALC882_AUTO]		= "auto",
+};
+
+static struct snd_pci_quirk alc882_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1019, 0x6668, "ECS", ALC882_6ST_DIG),
+
+	SND_PCI_QUIRK(0x1025, 0x006c, "Acer Aspire 9810", ALC883_ACER_ASPIRE),
+	SND_PCI_QUIRK(0x1025, 0x0090, "Acer Aspire", ALC883_ACER_ASPIRE),
+	SND_PCI_QUIRK(0x1025, 0x010a, "Acer Ferrari 5000", ALC883_ACER_ASPIRE),
+	SND_PCI_QUIRK(0x1025, 0x0110, "Acer Aspire", ALC883_ACER_ASPIRE),
+	SND_PCI_QUIRK(0x1025, 0x0112, "Acer Aspire 9303", ALC883_ACER_ASPIRE),
+	SND_PCI_QUIRK(0x1025, 0x0121, "Acer Aspire 5920G", ALC883_ACER_ASPIRE),
+	SND_PCI_QUIRK(0x1025, 0x013e, "Acer Aspire 4930G",
+		ALC888_ACER_ASPIRE_4930G),
+	SND_PCI_QUIRK(0x1025, 0x013f, "Acer Aspire 5930G",
+		ALC888_ACER_ASPIRE_4930G),
+	SND_PCI_QUIRK(0x1025, 0x0145, "Acer Aspire 8930G",
+		ALC888_ACER_ASPIRE_8930G),
+	SND_PCI_QUIRK(0x1025, 0x0146, "Acer Aspire 6935G",
+		ALC888_ACER_ASPIRE_8930G),
+	SND_PCI_QUIRK(0x1025, 0x0157, "Acer X3200", ALC882_AUTO),
+	SND_PCI_QUIRK(0x1025, 0x0158, "Acer AX1700-U3700A", ALC882_AUTO),
+	SND_PCI_QUIRK(0x1025, 0x015e, "Acer Aspire 6930G",
+		ALC888_ACER_ASPIRE_6530G),
+	SND_PCI_QUIRK(0x1025, 0x0166, "Acer Aspire 6530G",
+		ALC888_ACER_ASPIRE_6530G),
+	SND_PCI_QUIRK(0x1025, 0x0142, "Acer Aspire 7730G",
+		ALC888_ACER_ASPIRE_7730G),
+	/* default Acer -- disabled as it causes more problems.
+	 *    model=auto should work fine now
+	 */
+	/* SND_PCI_QUIRK_VENDOR(0x1025, "Acer laptop", ALC883_ACER), */
+
+	SND_PCI_QUIRK(0x1028, 0x020d, "Dell Inspiron 530", ALC888_6ST_DELL),
+
+	SND_PCI_QUIRK(0x103c, 0x2a3d, "HP Pavillion", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x103c, 0x2a4f, "HP Samba", ALC888_3ST_HP),
+	SND_PCI_QUIRK(0x103c, 0x2a60, "HP Lucknow", ALC888_3ST_HP),
+	SND_PCI_QUIRK(0x103c, 0x2a61, "HP Nettle", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x103c, 0x2a66, "HP Acacia", ALC888_3ST_HP),
+	SND_PCI_QUIRK(0x103c, 0x2a72, "HP Educ.ar", ALC888_3ST_HP),
+
+	SND_PCI_QUIRK(0x1043, 0x060d, "Asus A7J", ALC882_ASUS_A7J),
+	SND_PCI_QUIRK(0x1043, 0x1243, "Asus A7J", ALC882_ASUS_A7J),
+	SND_PCI_QUIRK(0x1043, 0x13c2, "Asus A7M", ALC882_ASUS_A7M),
+	SND_PCI_QUIRK(0x1043, 0x1873, "Asus M90V", ALC888_ASUS_M90V),
+	SND_PCI_QUIRK(0x1043, 0x1971, "Asus W2JC", ALC882_W2JC),
+	SND_PCI_QUIRK(0x1043, 0x817f, "Asus P5LD2", ALC882_6ST_DIG),
+	SND_PCI_QUIRK(0x1043, 0x81d8, "Asus P5WD", ALC882_6ST_DIG),
+	SND_PCI_QUIRK(0x1043, 0x8249, "Asus M2A-VM HDMI", ALC883_3ST_6ch_DIG),
+	SND_PCI_QUIRK(0x1043, 0x8284, "Asus Z37E", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x1043, 0x82fe, "Asus P5Q-EM HDMI", ALC1200_ASUS_P5Q),
+	SND_PCI_QUIRK(0x1043, 0x835f, "Asus Eee 1601", ALC888_ASUS_EEE1601),
+
+	SND_PCI_QUIRK(0x104d, 0x9047, "Sony Vaio TT", ALC883_SONY_VAIO_TT),
+	SND_PCI_QUIRK(0x105b, 0x0ce8, "Foxconn P35AX-S", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x105b, 0x6668, "Foxconn", ALC882_6ST_DIG),
+	SND_PCI_QUIRK(0x1071, 0x8227, "Mitac 82801H", ALC883_MITAC),
+	SND_PCI_QUIRK(0x1071, 0x8253, "Mitac 8252d", ALC883_MITAC),
+	SND_PCI_QUIRK(0x1071, 0x8258, "Evesham Voyaeger", ALC883_LAPTOP_EAPD),
+	SND_PCI_QUIRK(0x10f1, 0x2350, "TYAN-S2350", ALC888_6ST_DELL),
+	SND_PCI_QUIRK(0x108e, 0x534d, NULL, ALC883_3ST_6ch),
+	SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte P35 DS3R", ALC882_6ST_DIG),
+
+	SND_PCI_QUIRK(0x1462, 0x0349, "MSI", ALC883_TARGA_2ch_DIG),
+	SND_PCI_QUIRK(0x1462, 0x040d, "MSI", ALC883_TARGA_2ch_DIG),
+	SND_PCI_QUIRK(0x1462, 0x0579, "MSI", ALC883_TARGA_2ch_DIG),
+	SND_PCI_QUIRK(0x1462, 0x28fb, "Targa T8", ALC882_TARGA), /* MSI-1049 T8  */
+	SND_PCI_QUIRK(0x1462, 0x2fb3, "MSI", ALC882_AUTO),
+	SND_PCI_QUIRK(0x1462, 0x6668, "MSI", ALC882_6ST_DIG),
+	SND_PCI_QUIRK(0x1462, 0x3729, "MSI S420", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x3783, "NEC S970", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x3b7f, "MSI", ALC883_TARGA_2ch_DIG),
+	SND_PCI_QUIRK(0x1462, 0x3ef9, "MSI", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x3fc1, "MSI", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x3fc3, "MSI", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x3fcc, "MSI", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x3fdf, "MSI", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x42cd, "MSI", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x4314, "MSI", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x4319, "MSI", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x4324, "MSI", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x4570, "MSI Wind Top AE2220", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x6510, "MSI GX620", ALC883_TARGA_8ch_DIG),
+	SND_PCI_QUIRK(0x1462, 0x6668, "MSI", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x1462, 0x7187, "MSI", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x1462, 0x7250, "MSI", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x1462, 0x7260, "MSI 7260", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0x7267, "MSI", ALC883_3ST_6ch_DIG),
+	SND_PCI_QUIRK(0x1462, 0x7280, "MSI", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x1462, 0x7327, "MSI", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x1462, 0x7350, "MSI", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x1462, 0x7437, "MSI NetOn AP1900", ALC883_TARGA_DIG),
+	SND_PCI_QUIRK(0x1462, 0xa422, "MSI", ALC883_TARGA_2ch_DIG),
+	SND_PCI_QUIRK(0x1462, 0xaa08, "MSI", ALC883_TARGA_2ch_DIG),
+
+	SND_PCI_QUIRK(0x147b, 0x1083, "Abit IP35-PRO", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x1558, 0x0571, "Clevo laptop M570U", ALC883_3ST_6ch_DIG),
+	SND_PCI_QUIRK(0x1558, 0x0721, "Clevo laptop M720R", ALC883_CLEVO_M720),
+	SND_PCI_QUIRK(0x1558, 0x0722, "Clevo laptop M720SR", ALC883_CLEVO_M720),
+	SND_PCI_QUIRK(0x1558, 0x5409, "Clevo laptop M540R", ALC883_CLEVO_M540R),
+	SND_PCI_QUIRK_VENDOR(0x1558, "Clevo laptop", ALC883_LAPTOP_EAPD),
+	SND_PCI_QUIRK(0x15d9, 0x8780, "Supermicro PDSBA", ALC883_3ST_6ch),
+	/* SND_PCI_QUIRK(0x161f, 0x2054, "Arima W820", ALC882_ARIMA), */
+	SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_MEDION),
+	SND_PCI_QUIRK_MASK(0x1734, 0xfff0, 0x1100, "FSC AMILO Xi/Pi25xx",
+		      ALC883_FUJITSU_PI2515),
+	SND_PCI_QUIRK_MASK(0x1734, 0xfff0, 0x1130, "Fujitsu AMILO Xa35xx",
+		ALC888_FUJITSU_XA3530),
+	SND_PCI_QUIRK(0x17aa, 0x101e, "Lenovo 101e", ALC883_LENOVO_101E_2ch),
+	SND_PCI_QUIRK(0x17aa, 0x2085, "Lenovo NB0763", ALC883_LENOVO_NB0763),
+	SND_PCI_QUIRK(0x17aa, 0x3bfc, "Lenovo NB0763", ALC883_LENOVO_NB0763),
+	SND_PCI_QUIRK(0x17aa, 0x3bfd, "Lenovo NB0763", ALC883_LENOVO_NB0763),
+	SND_PCI_QUIRK(0x17aa, 0x101d, "Lenovo Sky", ALC888_LENOVO_SKY),
+	SND_PCI_QUIRK(0x17c0, 0x4085, "MEDION MD96630", ALC888_LENOVO_MS7195_DIG),
+	SND_PCI_QUIRK(0x17f2, 0x5000, "Albatron KI690-AM2", ALC883_6ST_DIG),
+	SND_PCI_QUIRK(0x1991, 0x5625, "Haier W66", ALC883_HAIER_W66),
+
+	SND_PCI_QUIRK(0x8086, 0x0001, "DG33BUC", ALC883_3ST_6ch_INTEL),
+	SND_PCI_QUIRK(0x8086, 0x0002, "DG33FBC", ALC883_3ST_6ch_INTEL),
+	SND_PCI_QUIRK(0x8086, 0x2503, "82801H", ALC883_MITAC),
+	SND_PCI_QUIRK(0x8086, 0x0022, "DX58SO", ALC889_INTEL),
+	SND_PCI_QUIRK(0x8086, 0x0021, "Intel IbexPeak", ALC889A_INTEL),
+	SND_PCI_QUIRK(0x8086, 0x3b56, "Intel IbexPeak", ALC889A_INTEL),
+	SND_PCI_QUIRK(0x8086, 0xd601, "D102GGC", ALC882_6ST_DIG),
+
+	{}
+};
+
+/* codec SSID table for Intel Mac */
+static struct snd_pci_quirk alc882_ssid_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x106b, 0x00a0, "MacBookPro 3,1", ALC885_MBP3),
+	SND_PCI_QUIRK(0x106b, 0x00a1, "Macbook", ALC885_MBP3),
+	SND_PCI_QUIRK(0x106b, 0x00a4, "MacbookPro 4,1", ALC885_MBP3),
+	SND_PCI_QUIRK(0x106b, 0x0c00, "Mac Pro", ALC885_MACPRO),
+	SND_PCI_QUIRK(0x106b, 0x1000, "iMac 24", ALC885_IMAC24),
+	SND_PCI_QUIRK(0x106b, 0x2800, "AppleTV", ALC885_IMAC24),
+	SND_PCI_QUIRK(0x106b, 0x2c00, "MacbookPro rev3", ALC885_MBP3),
+	SND_PCI_QUIRK(0x106b, 0x3000, "iMac", ALC889A_MB31),
+	SND_PCI_QUIRK(0x106b, 0x3200, "iMac 7,1 Aluminum", ALC882_ASUS_A7M),
+	SND_PCI_QUIRK(0x106b, 0x3400, "MacBookAir 1,1", ALC885_MBP3),
+	SND_PCI_QUIRK(0x106b, 0x3500, "MacBookAir 2,1", ALC885_MBA21),
+	SND_PCI_QUIRK(0x106b, 0x3600, "Macbook 3,1", ALC889A_MB31),
+	SND_PCI_QUIRK(0x106b, 0x3800, "MacbookPro 4,1", ALC885_MBP3),
+	SND_PCI_QUIRK(0x106b, 0x3e00, "iMac 24 Aluminum", ALC885_IMAC24),
+	SND_PCI_QUIRK(0x106b, 0x4900, "iMac 9,1 Aluminum", ALC885_IMAC91),
+	SND_PCI_QUIRK(0x106b, 0x3f00, "Macbook 5,1", ALC885_MB5),
+	SND_PCI_QUIRK(0x106b, 0x4a00, "Macbook 5,2", ALC885_MB5),
+	/* FIXME: HP jack sense seems not working for MBP 5,1 or 5,2,
+	 * so apparently no perfect solution yet
+	 */
+	SND_PCI_QUIRK(0x106b, 0x4000, "MacbookPro 5,1", ALC885_MB5),
+	SND_PCI_QUIRK(0x106b, 0x4600, "MacbookPro 5,2", ALC885_MB5),
+	SND_PCI_QUIRK(0x106b, 0x4100, "Macmini 3,1", ALC885_MACMINI3),
+	{} /* terminator */
+};
+
+static struct alc_config_preset alc882_presets[] = {
+	[ALC882_3ST_DIG] = {
+		.mixers = { alc882_base_mixer },
+		.init_verbs = { alc882_base_init_verbs,
+				alc882_adc1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+		.dig_in_nid = ALC882_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc882_ch_modes),
+		.channel_mode = alc882_ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc882_capture_source,
+	},
+	[ALC882_6ST_DIG] = {
+		.mixers = { alc882_base_mixer, alc882_chmode_mixer },
+		.init_verbs = { alc882_base_init_verbs,
+				alc882_adc1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+		.dig_in_nid = ALC882_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc882_sixstack_modes),
+		.channel_mode = alc882_sixstack_modes,
+		.input_mux = &alc882_capture_source,
+	},
+	[ALC882_ARIMA] = {
+		.mixers = { alc882_base_mixer, alc882_chmode_mixer },
+		.init_verbs = { alc882_base_init_verbs, alc882_adc1_init_verbs,
+				alc882_eapd_verbs },
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc882_sixstack_modes),
+		.channel_mode = alc882_sixstack_modes,
+		.input_mux = &alc882_capture_source,
+	},
+	[ALC882_W2JC] = {
+		.mixers = { alc882_w2jc_mixer, alc882_chmode_mixer },
+		.init_verbs = { alc882_base_init_verbs, alc882_adc1_init_verbs,
+				alc882_eapd_verbs, alc880_gpio1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc880_threestack_modes),
+		.channel_mode = alc880_threestack_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc882_capture_source,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+	},
+	   [ALC885_MBA21] = {
+			.mixers = { alc885_mba21_mixer },
+			.init_verbs = { alc885_mba21_init_verbs, alc880_gpio1_init_verbs },
+			.num_dacs = 2,
+			.dac_nids = alc882_dac_nids,
+			.channel_mode = alc885_mba21_ch_modes,
+			.num_channel_mode = ARRAY_SIZE(alc885_mba21_ch_modes),
+			.input_mux = &alc882_capture_source,
+			.unsol_event = alc_automute_amp_unsol_event,
+			.setup = alc885_mba21_setup,
+			.init_hook = alc_automute_amp,
+       },
+	[ALC885_MBP3] = {
+		.mixers = { alc885_mbp3_mixer, alc882_chmode_mixer },
+		.init_verbs = { alc885_mbp3_init_verbs,
+				alc880_gpio1_init_verbs },
+		.num_dacs = 2,
+		.dac_nids = alc882_dac_nids,
+		.hp_nid = 0x04,
+		.channel_mode = alc885_mbp_4ch_modes,
+		.num_channel_mode = ARRAY_SIZE(alc885_mbp_4ch_modes),
+		.input_mux = &alc882_capture_source,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+		.dig_in_nid = ALC882_DIGIN_NID,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc885_mbp3_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC885_MB5] = {
+		.mixers = { alc885_mb5_mixer, alc882_chmode_mixer },
+		.init_verbs = { alc885_mb5_init_verbs,
+				alc880_gpio1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.channel_mode = alc885_mb5_6ch_modes,
+		.num_channel_mode = ARRAY_SIZE(alc885_mb5_6ch_modes),
+		.input_mux = &mb5_capture_source,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+		.dig_in_nid = ALC882_DIGIN_NID,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc885_mb5_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC885_MACMINI3] = {
+		.mixers = { alc885_macmini3_mixer, alc882_chmode_mixer },
+		.init_verbs = { alc885_macmini3_init_verbs,
+				alc880_gpio1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.channel_mode = alc885_macmini3_6ch_modes,
+		.num_channel_mode = ARRAY_SIZE(alc885_macmini3_6ch_modes),
+		.input_mux = &macmini3_capture_source,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+		.dig_in_nid = ALC882_DIGIN_NID,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc885_macmini3_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC885_MACPRO] = {
+		.mixers = { alc882_macpro_mixer },
+		.init_verbs = { alc882_macpro_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+		.dig_in_nid = ALC882_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc882_ch_modes),
+		.channel_mode = alc882_ch_modes,
+		.input_mux = &alc882_capture_source,
+		.init_hook = alc885_macpro_init_hook,
+	},
+	[ALC885_IMAC24] = {
+		.mixers = { alc885_imac24_mixer },
+		.init_verbs = { alc885_imac24_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+		.dig_in_nid = ALC882_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc882_ch_modes),
+		.channel_mode = alc882_ch_modes,
+		.input_mux = &alc882_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc885_imac24_setup,
+		.init_hook = alc885_imac24_init_hook,
+	},
+	[ALC885_IMAC91] = {
+		.mixers = {alc885_imac91_mixer},
+		.init_verbs = { alc885_imac91_init_verbs,
+				alc880_gpio1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.channel_mode = alc885_mba21_ch_modes,
+		.num_channel_mode = ARRAY_SIZE(alc885_mba21_ch_modes),
+		.input_mux = &alc889A_imac91_capture_source,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+		.dig_in_nid = ALC882_DIGIN_NID,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc885_imac91_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC882_TARGA] = {
+		.mixers = { alc882_targa_mixer, alc882_chmode_mixer },
+		.init_verbs = { alc882_base_init_verbs, alc882_adc1_init_verbs,
+				alc880_gpio3_init_verbs, alc882_targa_verbs},
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+		.num_adc_nids = ARRAY_SIZE(alc882_adc_nids),
+		.adc_nids = alc882_adc_nids,
+		.capsrc_nids = alc882_capsrc_nids,
+		.num_channel_mode = ARRAY_SIZE(alc882_3ST_6ch_modes),
+		.channel_mode = alc882_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc882_capture_source,
+		.unsol_event = alc882_targa_unsol_event,
+		.setup = alc882_targa_setup,
+		.init_hook = alc882_targa_automute,
+	},
+	[ALC882_ASUS_A7J] = {
+		.mixers = { alc882_asus_a7j_mixer, alc882_chmode_mixer },
+		.init_verbs = { alc882_base_init_verbs, alc882_adc1_init_verbs,
+				alc882_asus_a7j_verbs},
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+		.num_adc_nids = ARRAY_SIZE(alc882_adc_nids),
+		.adc_nids = alc882_adc_nids,
+		.capsrc_nids = alc882_capsrc_nids,
+		.num_channel_mode = ARRAY_SIZE(alc882_3ST_6ch_modes),
+		.channel_mode = alc882_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc882_capture_source,
+	},
+	[ALC882_ASUS_A7M] = {
+		.mixers = { alc882_asus_a7m_mixer, alc882_chmode_mixer },
+		.init_verbs = { alc882_base_init_verbs, alc882_adc1_init_verbs,
+				alc882_eapd_verbs, alc880_gpio1_init_verbs,
+				alc882_asus_a7m_verbs },
+		.num_dacs = ARRAY_SIZE(alc882_dac_nids),
+		.dac_nids = alc882_dac_nids,
+		.dig_out_nid = ALC882_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc880_threestack_modes),
+		.channel_mode = alc880_threestack_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc882_capture_source,
+	},
+	[ALC883_3ST_2ch_DIG] = {
+		.mixers = { alc883_3ST_2ch_mixer },
+		.init_verbs = { alc883_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_capture_source,
+	},
+	[ALC883_3ST_6ch_DIG] = {
+		.mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes),
+		.channel_mode = alc883_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_capture_source,
+	},
+	[ALC883_3ST_6ch] = {
+		.mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes),
+		.channel_mode = alc883_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_capture_source,
+	},
+	[ALC883_3ST_6ch_INTEL] = {
+		.mixers = { alc883_3ST_6ch_intel_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.slave_dig_outs = alc883_slave_dig_outs,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_intel_modes),
+		.channel_mode = alc883_3ST_6ch_intel_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_3stack_6ch_intel,
+	},
+	[ALC889A_INTEL] = {
+		.mixers = { alc885_8ch_intel_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc885_init_verbs, alc885_init_input_verbs,
+				alc_hp15_unsol_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc889_adc_nids),
+		.adc_nids = alc889_adc_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.slave_dig_outs = alc883_slave_dig_outs,
+		.num_channel_mode = ARRAY_SIZE(alc889_8ch_intel_modes),
+		.channel_mode = alc889_8ch_intel_modes,
+		.capsrc_nids = alc889_capsrc_nids,
+		.input_mux = &alc889_capture_source,
+		.setup = alc889_automute_setup,
+		.init_hook = alc_automute_amp,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.need_dac_fix = 1,
+	},
+	[ALC889_INTEL] = {
+		.mixers = { alc885_8ch_intel_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc885_init_verbs, alc889_init_input_verbs,
+				alc889_eapd_verbs, alc_hp15_unsol_verbs},
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc889_adc_nids),
+		.adc_nids = alc889_adc_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.slave_dig_outs = alc883_slave_dig_outs,
+		.num_channel_mode = ARRAY_SIZE(alc889_8ch_intel_modes),
+		.channel_mode = alc889_8ch_intel_modes,
+		.capsrc_nids = alc889_capsrc_nids,
+		.input_mux = &alc889_capture_source,
+		.setup = alc889_automute_setup,
+		.init_hook = alc889_intel_init_hook,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.need_dac_fix = 1,
+	},
+	[ALC883_6ST_DIG] = {
+		.mixers = { alc883_base_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_sixstack_modes),
+		.channel_mode = alc883_sixstack_modes,
+		.input_mux = &alc883_capture_source,
+	},
+	[ALC883_TARGA_DIG] = {
+		.mixers = { alc883_targa_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs, alc880_gpio3_init_verbs,
+				alc883_targa_verbs},
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes),
+		.channel_mode = alc883_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc883_targa_unsol_event,
+		.setup = alc882_targa_setup,
+		.init_hook = alc882_targa_automute,
+	},
+	[ALC883_TARGA_2ch_DIG] = {
+		.mixers = { alc883_targa_2ch_mixer},
+		.init_verbs = { alc883_init_verbs, alc880_gpio3_init_verbs,
+				alc883_targa_verbs},
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.adc_nids = alc883_adc_nids_alt,
+		.num_adc_nids = ARRAY_SIZE(alc883_adc_nids_alt),
+		.capsrc_nids = alc883_capsrc_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc883_targa_unsol_event,
+		.setup = alc882_targa_setup,
+		.init_hook = alc882_targa_automute,
+	},
+	[ALC883_TARGA_8ch_DIG] = {
+		.mixers = { alc883_targa_mixer, alc883_targa_8ch_mixer,
+			    alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs, alc880_gpio3_init_verbs,
+				alc883_targa_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc883_adc_nids_rev),
+		.adc_nids = alc883_adc_nids_rev,
+		.capsrc_nids = alc883_capsrc_nids_rev,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_4ST_8ch_modes),
+		.channel_mode = alc883_4ST_8ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc883_targa_unsol_event,
+		.setup = alc882_targa_setup,
+		.init_hook = alc882_targa_automute,
+	},
+	[ALC883_ACER] = {
+		.mixers = { alc883_base_mixer },
+		/* On TravelMate laptops, GPIO 0 enables the internal speaker
+		 * and the headphone jack.  Turn this on and rely on the
+		 * standard mute methods whenever the user wants to turn
+		 * these outputs off.
+		 */
+		.init_verbs = { alc883_init_verbs, alc880_gpio1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_capture_source,
+	},
+	[ALC883_ACER_ASPIRE] = {
+		.mixers = { alc883_acer_aspire_mixer },
+		.init_verbs = { alc883_init_verbs, alc883_acer_eapd_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc883_acer_aspire_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC888_ACER_ASPIRE_4930G] = {
+		.mixers = { alc888_base_mixer,
+				alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs, alc880_gpio1_init_verbs,
+				alc888_acer_aspire_4930g_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc883_adc_nids_rev),
+		.adc_nids = alc883_adc_nids_rev,
+		.capsrc_nids = alc883_capsrc_nids_rev,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes),
+		.channel_mode = alc883_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.const_channel_count = 6,
+		.num_mux_defs =
+			ARRAY_SIZE(alc888_2_capture_sources),
+		.input_mux = alc888_2_capture_sources,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc888_acer_aspire_4930g_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC888_ACER_ASPIRE_6530G] = {
+		.mixers = { alc888_acer_aspire_6530_mixer },
+		.init_verbs = { alc883_init_verbs, alc880_gpio1_init_verbs,
+				alc888_acer_aspire_6530g_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc883_adc_nids_rev),
+		.adc_nids = alc883_adc_nids_rev,
+		.capsrc_nids = alc883_capsrc_nids_rev,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.num_mux_defs =
+			ARRAY_SIZE(alc888_2_capture_sources),
+		.input_mux = alc888_acer_aspire_6530_sources,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc888_acer_aspire_6530g_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC888_ACER_ASPIRE_8930G] = {
+		.mixers = { alc889_acer_aspire_8930g_mixer,
+				alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs, alc880_gpio1_init_verbs,
+				alc889_acer_aspire_8930g_verbs,
+				alc889_eapd_verbs},
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc889_adc_nids),
+		.adc_nids = alc889_adc_nids,
+		.capsrc_nids = alc889_capsrc_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes),
+		.channel_mode = alc883_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.const_channel_count = 6,
+		.num_mux_defs =
+			ARRAY_SIZE(alc889_capture_sources),
+		.input_mux = alc889_capture_sources,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc889_acer_aspire_8930g_setup,
+		.init_hook = alc_automute_amp,
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+		.power_hook = alc_power_eapd,
+#endif
+	},
+	[ALC888_ACER_ASPIRE_7730G] = {
+		.mixers = { alc883_3ST_6ch_mixer,
+				alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs, alc880_gpio1_init_verbs,
+				alc888_acer_aspire_7730G_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc883_adc_nids_rev),
+		.adc_nids = alc883_adc_nids_rev,
+		.capsrc_nids = alc883_capsrc_nids_rev,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes),
+		.channel_mode = alc883_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.const_channel_count = 6,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc888_acer_aspire_7730g_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC883_MEDION] = {
+		.mixers = { alc883_fivestack_mixer,
+			    alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs,
+				alc883_medion_eapd_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.adc_nids = alc883_adc_nids_alt,
+		.num_adc_nids = ARRAY_SIZE(alc883_adc_nids_alt),
+		.capsrc_nids = alc883_capsrc_nids,
+		.num_channel_mode = ARRAY_SIZE(alc883_sixstack_modes),
+		.channel_mode = alc883_sixstack_modes,
+		.input_mux = &alc883_capture_source,
+	},
+	[ALC883_MEDION_MD2] = {
+		.mixers = { alc883_medion_md2_mixer},
+		.init_verbs = { alc883_init_verbs, alc883_medion_md2_verbs},
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc883_medion_md2_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC883_MEDION_WIM2160] = {
+		.mixers = { alc883_medion_wim2160_mixer },
+		.init_verbs = { alc883_init_verbs, alc883_medion_wim2160_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_adc_nids = ARRAY_SIZE(alc883_adc_nids),
+		.adc_nids = alc883_adc_nids,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc883_medion_wim2160_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC883_LAPTOP_EAPD] = {
+		.mixers = { alc883_base_mixer },
+		.init_verbs = { alc883_init_verbs, alc882_eapd_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_capture_source,
+	},
+	[ALC883_CLEVO_M540R] = {
+		.mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs, alc883_clevo_m540r_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_clevo_modes),
+		.channel_mode = alc883_3ST_6ch_clevo_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_capture_source,
+		/* This machine has the hardware HP auto-muting, thus
+		 * we need no software mute via unsol event
+		 */
+	},
+	[ALC883_CLEVO_M720] = {
+		.mixers = { alc883_clevo_m720_mixer },
+		.init_verbs = { alc883_init_verbs, alc883_clevo_m720_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc883_clevo_m720_unsol_event,
+		.setup = alc883_clevo_m720_setup,
+		.init_hook = alc883_clevo_m720_init_hook,
+	},
+	[ALC883_LENOVO_101E_2ch] = {
+		.mixers = { alc883_lenovo_101e_2ch_mixer},
+		.init_verbs = { alc883_init_verbs, alc883_lenovo_101e_verbs},
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.adc_nids = alc883_adc_nids_alt,
+		.num_adc_nids = ARRAY_SIZE(alc883_adc_nids_alt),
+		.capsrc_nids = alc883_capsrc_nids,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_lenovo_101e_capture_source,
+		.unsol_event = alc883_lenovo_101e_unsol_event,
+		.init_hook = alc883_lenovo_101e_all_automute,
+	},
+	[ALC883_LENOVO_NB0763] = {
+		.mixers = { alc883_lenovo_nb0763_mixer },
+		.init_verbs = { alc883_init_verbs, alc883_lenovo_nb0763_verbs},
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_lenovo_nb0763_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc883_medion_md2_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC888_LENOVO_MS7195_DIG] = {
+		.mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs, alc888_lenovo_ms7195_verbs},
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes),
+		.channel_mode = alc883_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc883_lenovo_ms7195_unsol_event,
+		.init_hook = alc888_lenovo_ms7195_front_automute,
+	},
+	[ALC883_HAIER_W66] = {
+		.mixers = { alc883_targa_2ch_mixer},
+		.init_verbs = { alc883_init_verbs, alc883_haier_w66_verbs},
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc883_haier_w66_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC888_3ST_HP] = {
+		.mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs, alc888_3st_hp_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc888_3st_hp_modes),
+		.channel_mode = alc888_3st_hp_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc888_3st_hp_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC888_6ST_DELL] = {
+		.mixers = { alc883_base_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs, alc888_6st_dell_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_sixstack_modes),
+		.channel_mode = alc883_sixstack_modes,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc888_6st_dell_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC883_MITAC] = {
+		.mixers = { alc883_mitac_mixer },
+		.init_verbs = { alc883_init_verbs, alc883_mitac_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc883_mitac_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC883_FUJITSU_PI2515] = {
+		.mixers = { alc883_2ch_fujitsu_pi2515_mixer },
+		.init_verbs = { alc883_init_verbs,
+				alc883_2ch_fujitsu_pi2515_verbs},
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_fujitsu_pi2515_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc883_2ch_fujitsu_pi2515_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC888_FUJITSU_XA3530] = {
+		.mixers = { alc888_base_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs,
+			alc888_fujitsu_xa3530_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc883_adc_nids_rev),
+		.adc_nids = alc883_adc_nids_rev,
+		.capsrc_nids = alc883_capsrc_nids_rev,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc888_4ST_8ch_intel_modes),
+		.channel_mode = alc888_4ST_8ch_intel_modes,
+		.num_mux_defs =
+			ARRAY_SIZE(alc888_2_capture_sources),
+		.input_mux = alc888_2_capture_sources,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc888_fujitsu_xa3530_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC888_LENOVO_SKY] = {
+		.mixers = { alc888_lenovo_sky_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs, alc888_lenovo_sky_verbs},
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_sixstack_modes),
+		.channel_mode = alc883_sixstack_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_lenovo_sky_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc888_lenovo_sky_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC888_ASUS_M90V] = {
+		.mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs, alc888_asus_m90v_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes),
+		.channel_mode = alc883_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_fujitsu_pi2515_capture_source,
+		.unsol_event = alc_sku_unsol_event,
+		.setup = alc883_mode2_setup,
+		.init_hook = alc_inithook,
+	},
+	[ALC888_ASUS_EEE1601] = {
+		.mixers = { alc883_asus_eee1601_mixer },
+		.cap_mixer = alc883_asus_eee1601_cap_mixer,
+		.init_verbs = { alc883_init_verbs, alc888_asus_eee1601_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc883_asus_eee1601_capture_source,
+		.unsol_event = alc_sku_unsol_event,
+		.init_hook = alc883_eee1601_inithook,
+	},
+	[ALC1200_ASUS_P5Q] = {
+		.mixers = { alc883_base_mixer, alc883_chmode_mixer },
+		.init_verbs = { alc883_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.dig_out_nid = ALC1200_DIGOUT_NID,
+		.dig_in_nid = ALC883_DIGIN_NID,
+		.slave_dig_outs = alc1200_slave_dig_outs,
+		.num_channel_mode = ARRAY_SIZE(alc883_sixstack_modes),
+		.channel_mode = alc883_sixstack_modes,
+		.input_mux = &alc883_capture_source,
+	},
+	[ALC889A_MB31] = {
+		.mixers = { alc889A_mb31_mixer, alc883_chmode_mixer},
+		.init_verbs = { alc883_init_verbs, alc889A_mb31_verbs,
+			alc880_gpio1_init_verbs },
+		.adc_nids = alc883_adc_nids,
+		.num_adc_nids = ARRAY_SIZE(alc883_adc_nids),
+		.capsrc_nids = alc883_capsrc_nids,
+		.dac_nids = alc883_dac_nids,
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.channel_mode = alc889A_mb31_6ch_modes,
+		.num_channel_mode = ARRAY_SIZE(alc889A_mb31_6ch_modes),
+		.input_mux = &alc889A_mb31_capture_source,
+		.dig_out_nid = ALC883_DIGOUT_NID,
+		.unsol_event = alc889A_mb31_unsol_event,
+		.init_hook = alc889A_mb31_automute,
+	},
+	[ALC883_SONY_VAIO_TT] = {
+		.mixers = { alc883_vaiott_mixer },
+		.init_verbs = { alc883_init_verbs, alc883_vaiott_verbs },
+		.num_dacs = ARRAY_SIZE(alc883_dac_nids),
+		.dac_nids = alc883_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.input_mux = &alc883_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc883_vaiott_setup,
+		.init_hook = alc_automute_amp,
+	},
+};
+
+
+/*
+ * Pin config fixes
+ */
+enum {
+	PINFIX_ABIT_AW9D_MAX,
+	PINFIX_PB_M5210,
+	PINFIX_ACER_ASPIRE_7736,
+};
+
+static const struct alc_fixup alc882_fixups[] = {
+	[PINFIX_ABIT_AW9D_MAX] = {
+		.pins = (const struct alc_pincfg[]) {
+			{ 0x15, 0x01080104 }, /* side */
+			{ 0x16, 0x01011012 }, /* rear */
+			{ 0x17, 0x01016011 }, /* clfe */
+			{ }
+		}
+	},
+	[PINFIX_PB_M5210] = {
+		.verbs = (const struct hda_verb[]) {
+			{ 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50 },
+			{}
+		}
+	},
+	[PINFIX_ACER_ASPIRE_7736] = {
+		.sku = ALC_FIXUP_SKU_IGNORE,
+	},
+};
+
+static struct snd_pci_quirk alc882_fixup_tbl[] = {
+	SND_PCI_QUIRK(0x1025, 0x0155, "Packard-Bell M5120", PINFIX_PB_M5210),
+	SND_PCI_QUIRK(0x147b, 0x107a, "Abit AW9D-MAX", PINFIX_ABIT_AW9D_MAX),
+	SND_PCI_QUIRK(0x1025, 0x0296, "Acer Aspire 7736z", PINFIX_ACER_ASPIRE_7736),
+	{}
+};
+
+/*
+ * BIOS auto configuration
+ */
+static int alc882_auto_create_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	return alc_auto_create_input_ctls(codec, cfg, 0x0b, 0x23, 0x22);
+}
+
+static void alc882_auto_set_output_and_unmute(struct hda_codec *codec,
+					      hda_nid_t nid, int pin_type,
+					      hda_nid_t dac)
+{
+	int idx;
+
+	/* set as output */
+	alc_set_pin_output(codec, nid, pin_type);
+
+	if (dac == 0x25)
+		idx = 4;
+	else if (dac >= 0x02 && dac <= 0x05)
+		idx = dac - 2;
+	else
+		return;
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, idx);
+}
+
+static void alc882_auto_init_multi_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i <= HDA_SIDE; i++) {
+		hda_nid_t nid = spec->autocfg.line_out_pins[i];
+		int pin_type = get_pin_type(spec->autocfg.line_out_type);
+		if (nid)
+			alc882_auto_set_output_and_unmute(codec, nid, pin_type,
+					spec->multiout.dac_nids[i]);
+	}
+}
+
+static void alc882_auto_init_hp_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t pin, dac;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(spec->autocfg.hp_pins); i++) {
+		pin = spec->autocfg.hp_pins[i];
+		if (!pin)
+			break;
+		dac = spec->multiout.hp_nid;
+		if (!dac)
+			dac = spec->multiout.dac_nids[0]; /* to front */
+		alc882_auto_set_output_and_unmute(codec, pin, PIN_HP, dac);
+	}
+	for (i = 0; i < ARRAY_SIZE(spec->autocfg.speaker_pins); i++) {
+		pin = spec->autocfg.speaker_pins[i];
+		if (!pin)
+			break;
+		dac = spec->multiout.extra_out_nid[0];
+		if (!dac)
+			dac = spec->multiout.dac_nids[0]; /* to front */
+		alc882_auto_set_output_and_unmute(codec, pin, PIN_OUT, dac);
+	}
+}
+
+static void alc882_auto_init_analog_input(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		alc_set_input_pin(codec, nid, cfg->inputs[i].type);
+		if (get_wcaps(codec, nid) & AC_WCAP_OUT_AMP)
+			snd_hda_codec_write(codec, nid, 0,
+					    AC_VERB_SET_AMP_GAIN_MUTE,
+					    AMP_OUT_MUTE);
+	}
+}
+
+static void alc882_auto_init_input_src(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int c;
+
+	for (c = 0; c < spec->num_adc_nids; c++) {
+		hda_nid_t conn_list[HDA_MAX_NUM_INPUTS];
+		hda_nid_t nid = spec->capsrc_nids[c];
+		unsigned int mux_idx;
+		const struct hda_input_mux *imux;
+		int conns, mute, idx, item;
+
+		conns = snd_hda_get_connections(codec, nid, conn_list,
+						ARRAY_SIZE(conn_list));
+		if (conns < 0)
+			continue;
+		mux_idx = c >= spec->num_mux_defs ? 0 : c;
+		imux = &spec->input_mux[mux_idx];
+		if (!imux->num_items && mux_idx > 0)
+			imux = &spec->input_mux[0];
+		for (idx = 0; idx < conns; idx++) {
+			/* if the current connection is the selected one,
+			 * unmute it as default - otherwise mute it
+			 */
+			mute = AMP_IN_MUTE(idx);
+			for (item = 0; item < imux->num_items; item++) {
+				if (imux->items[item].index == idx) {
+					if (spec->cur_mux[c] == item)
+						mute = AMP_IN_UNMUTE(idx);
+					break;
+				}
+			}
+			/* check if we have a selector or mixer
+			 * we could check for the widget type instead, but
+			 * just check for Amp-In presence (in case of mixer
+			 * without amp-in there is something wrong, this
+			 * function shouldn't be used or capsrc nid is wrong)
+			 */
+			if (get_wcaps(codec, nid) & AC_WCAP_IN_AMP)
+				snd_hda_codec_write(codec, nid, 0,
+						    AC_VERB_SET_AMP_GAIN_MUTE,
+						    mute);
+			else if (mute != AMP_IN_MUTE(idx))
+				snd_hda_codec_write(codec, nid, 0,
+						    AC_VERB_SET_CONNECT_SEL,
+						    idx);
+		}
+	}
+}
+
+/* add mic boosts if needed */
+static int alc_auto_add_mic_boost(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i, err, type;
+	int type_idx = 0;
+	hda_nid_t nid;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		if (cfg->inputs[i].type > AUTO_PIN_MIC)
+			break;
+		nid = cfg->inputs[i].pin;
+		if (get_wcaps(codec, nid) & AC_WCAP_IN_AMP) {
+			char label[32];
+			type = cfg->inputs[i].type;
+			if (i > 0 && type == cfg->inputs[i - 1].type)
+				type_idx++;
+			else
+				type_idx = 0;
+			snprintf(label, sizeof(label), "%s Boost",
+				 hda_get_autocfg_input_label(codec, cfg, i));
+			err = add_control(spec, ALC_CTL_WIDGET_VOL, label,
+					  type_idx,
+				  HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT));
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+/* almost identical with ALC880 parser... */
+static int alc882_parse_auto_config(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	static hda_nid_t alc882_ignore[] = { 0x1d, 0 };
+	int err;
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg,
+					   alc882_ignore);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs)
+		return 0; /* can't find valid BIOS pin config */
+
+	err = alc880_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc880_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc880_auto_create_extra_out(spec, spec->autocfg.hp_pins[0],
+					   "Headphone");
+	if (err < 0)
+		return err;
+	err = alc880_auto_create_extra_out(spec,
+					   spec->autocfg.speaker_pins[0],
+					   "Speaker");
+	if (err < 0)
+		return err;
+	err = alc882_auto_create_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	alc_auto_parse_digital(codec);
+
+	if (spec->kctls.list)
+		add_mixer(spec, spec->kctls.list);
+
+	add_verb(spec, alc883_auto_init_verbs);
+	/* if ADC 0x07 is available, initialize it, too */
+	if (get_wcaps_type(get_wcaps(codec, 0x07)) == AC_WID_AUD_IN)
+		add_verb(spec, alc882_adc1_init_verbs);
+
+	spec->num_mux_defs = 1;
+	spec->input_mux = &spec->private_imux[0];
+
+	alc_ssid_check(codec, 0x15, 0x1b, 0x14, 0);
+
+	err = alc_auto_add_mic_boost(codec);
+	if (err < 0)
+		return err;
+
+	return 1; /* config found */
+}
+
+/* additional initialization for auto-configuration model */
+static void alc882_auto_init(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc882_auto_init_multi_out(codec);
+	alc882_auto_init_hp_out(codec);
+	alc882_auto_init_analog_input(codec);
+	alc882_auto_init_input_src(codec);
+	alc_auto_init_digital(codec);
+	if (spec->unsol_event)
+		alc_inithook(codec);
+}
+
+static int patch_alc882(struct hda_codec *codec)
+{
+	struct alc_spec *spec;
+	int err, board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	switch (codec->vendor_id) {
+	case 0x10ec0882:
+	case 0x10ec0885:
+		break;
+	default:
+		/* ALC883 and variants */
+		alc_fix_pll_init(codec, 0x20, 0x0a, 10);
+		break;
+	}
+
+	board_config = snd_hda_check_board_config(codec, ALC882_MODEL_LAST,
+						  alc882_models,
+						  alc882_cfg_tbl);
+
+	if (board_config < 0 || board_config >= ALC882_MODEL_LAST)
+		board_config = snd_hda_check_board_codec_sid_config(codec,
+			ALC882_MODEL_LAST, alc882_models, alc882_ssid_cfg_tbl);
+
+	if (board_config < 0 || board_config >= ALC882_MODEL_LAST) {
+		printk(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+		       codec->chip_name);
+		board_config = ALC882_AUTO;
+	}
+
+	if (board_config == ALC882_AUTO)
+		alc_pick_fixup(codec, alc882_fixup_tbl, alc882_fixups, 1);
+
+	alc_auto_parse_customize_define(codec);
+
+	if (board_config == ALC882_AUTO) {
+		/* automatic parse from the BIOS config */
+		err = alc882_parse_auto_config(codec);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		} else if (!err) {
+			printk(KERN_INFO
+			       "hda_codec: Cannot set up configuration "
+			       "from BIOS.  Using base mode...\n");
+			board_config = ALC882_3ST_DIG;
+		}
+	}
+
+	if (has_cdefine_beep(codec)) {
+		err = snd_hda_attach_beep_device(codec, 0x1);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		}
+	}
+
+	if (board_config != ALC882_AUTO)
+		setup_preset(codec, &alc882_presets[board_config]);
+
+	spec->stream_analog_playback = &alc882_pcm_analog_playback;
+	spec->stream_analog_capture = &alc882_pcm_analog_capture;
+	/* FIXME: setup DAC5 */
+	/*spec->stream_analog_alt_playback = &alc880_pcm_analog_alt_playback;*/
+	spec->stream_analog_alt_capture = &alc880_pcm_analog_alt_capture;
+
+	spec->stream_digital_playback = &alc882_pcm_digital_playback;
+	spec->stream_digital_capture = &alc882_pcm_digital_capture;
+
+	if (!spec->adc_nids && spec->input_mux) {
+		int i, j;
+		spec->num_adc_nids = 0;
+		for (i = 0; i < ARRAY_SIZE(alc882_adc_nids); i++) {
+			const struct hda_input_mux *imux = spec->input_mux;
+			hda_nid_t cap;
+			hda_nid_t items[16];
+			hda_nid_t nid = alc882_adc_nids[i];
+			unsigned int wcap = get_wcaps(codec, nid);
+			/* get type */
+			wcap = get_wcaps_type(wcap);
+			if (wcap != AC_WID_AUD_IN)
+				continue;
+			spec->private_adc_nids[spec->num_adc_nids] = nid;
+			err = snd_hda_get_connections(codec, nid, &cap, 1);
+			if (err < 0)
+				continue;
+			err = snd_hda_get_connections(codec, cap, items,
+						      ARRAY_SIZE(items));
+			if (err < 0)
+				continue;
+			for (j = 0; j < imux->num_items; j++)
+				if (imux->items[j].index >= err)
+					break;
+			if (j < imux->num_items)
+				continue;
+			spec->private_capsrc_nids[spec->num_adc_nids] = cap;
+			spec->num_adc_nids++;
+		}
+		spec->adc_nids = spec->private_adc_nids;
+		spec->capsrc_nids = spec->private_capsrc_nids;
+	}
+
+	set_capture_mixer(codec);
+
+	if (has_cdefine_beep(codec))
+		set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT);
+
+	if (board_config == ALC882_AUTO)
+		alc_pick_fixup(codec, alc882_fixup_tbl, alc882_fixups, 0);
+
+	spec->vmaster_nid = 0x0c;
+
+	codec->patch_ops = alc_patch_ops;
+	if (board_config == ALC882_AUTO)
+		spec->init_hook = alc882_auto_init;
+
+	alc_init_jacks(codec);
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	if (!spec->loopback.amplist)
+		spec->loopback.amplist = alc882_loopbacks;
+#endif
+
+	return 0;
+}
+
+
+/*
+ * ALC262 support
+ */
+
+#define ALC262_DIGOUT_NID	ALC880_DIGOUT_NID
+#define ALC262_DIGIN_NID	ALC880_DIGIN_NID
+
+#define alc262_dac_nids		alc260_dac_nids
+#define alc262_adc_nids		alc882_adc_nids
+#define alc262_adc_nids_alt	alc882_adc_nids_alt
+#define alc262_capsrc_nids	alc882_capsrc_nids
+#define alc262_capsrc_nids_alt	alc882_capsrc_nids_alt
+
+#define alc262_modes		alc260_modes
+#define alc262_capture_source	alc882_capture_source
+
+static hda_nid_t alc262_dmic_adc_nids[1] = {
+	/* ADC0 */
+	0x09
+};
+
+static hda_nid_t alc262_dmic_capsrc_nids[1] = { 0x22 };
+
+static struct snd_kcontrol_new alc262_base_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0D, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x16, 2, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+/* update HP, line and mono-out pins according to the master switch */
+static void alc262_hp_master_update(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int val = spec->master_sw;
+
+	/* HP & line-out */
+	snd_hda_codec_write_cache(codec, 0x1b, 0,
+				  AC_VERB_SET_PIN_WIDGET_CONTROL,
+				  val ? PIN_HP : 0);
+	snd_hda_codec_write_cache(codec, 0x15, 0,
+				  AC_VERB_SET_PIN_WIDGET_CONTROL,
+				  val ? PIN_HP : 0);
+	/* mono (speaker) depending on the HP jack sense */
+	val = val && !spec->jack_present;
+	snd_hda_codec_write_cache(codec, 0x16, 0,
+				  AC_VERB_SET_PIN_WIDGET_CONTROL,
+				  val ? PIN_OUT : 0);
+}
+
+static void alc262_hp_bpc_automute(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->jack_present = snd_hda_jack_detect(codec, 0x1b);
+	alc262_hp_master_update(codec);
+}
+
+static void alc262_hp_bpc_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	if ((res >> 26) != ALC880_HP_EVENT)
+		return;
+	alc262_hp_bpc_automute(codec);
+}
+
+static void alc262_hp_wildwest_automute(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->jack_present = snd_hda_jack_detect(codec, 0x15);
+	alc262_hp_master_update(codec);
+}
+
+static void alc262_hp_wildwest_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	if ((res >> 26) != ALC880_HP_EVENT)
+		return;
+	alc262_hp_wildwest_automute(codec);
+}
+
+#define alc262_hp_master_sw_get		alc260_hp_master_sw_get
+
+static int alc262_hp_master_sw_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	int val = !!*ucontrol->value.integer.value;
+
+	if (val == spec->master_sw)
+		return 0;
+	spec->master_sw = val;
+	alc262_hp_master_update(codec);
+	return 1;
+}
+
+#define ALC262_HP_MASTER_SWITCH					\
+	{							\
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,		\
+		.name = "Master Playback Switch",		\
+		.info = snd_ctl_boolean_mono_info,		\
+		.get = alc262_hp_master_sw_get,			\
+		.put = alc262_hp_master_sw_put,			\
+	}, \
+	{							\
+		.iface = NID_MAPPING,				\
+		.name = "Master Playback Switch",		\
+		.private_value = 0x15 | (0x16 << 8) | (0x1b << 16),	\
+	}
+
+
+static struct snd_kcontrol_new alc262_HP_BPC_mixer[] = {
+	ALC262_HP_MASTER_SWITCH,
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0e, 2, 0x0,
+			      HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Speaker Playback Switch", 0x16, 2, 0x0,
+			    HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("AUX IN Playback Volume", 0x0b, 0x06, HDA_INPUT),
+	HDA_CODEC_MUTE("AUX IN Playback Switch", 0x0b, 0x06, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc262_HP_BPC_WildWest_mixer[] = {
+	ALC262_HP_MASTER_SWITCH,
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0e, 2, 0x0,
+			      HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Speaker Playback Switch", 0x16, 2, 0x0,
+			    HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x1a, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc262_HP_BPC_WildWest_option_mixer[] = {
+	HDA_CODEC_VOLUME("Rear Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Rear Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Rear Mic Boost", 0x18, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+/* mute/unmute internal speaker according to the hp jack and mute state */
+static void alc262_hp_t5735_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+}
+
+static struct snd_kcontrol_new alc262_hp_t5735_mixer[] = {
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_verb alc262_hp_t5735_verbs[] = {
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{ }
+};
+
+static struct snd_kcontrol_new alc262_hp_rp5700_mixer[] = {
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0e, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x16, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_verb alc262_hp_rp5700_verbs[] = {
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x00 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x00 << 8))},
+	{}
+};
+
+static struct hda_input_mux alc262_hp_rp5700_capture_source = {
+	.num_items = 1,
+	.items = {
+		{ "Line", 0x1 },
+	},
+};
+
+/* bind hp and internal speaker mute (with plug check) as master switch */
+static void alc262_hippo_master_update(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t hp_nid = spec->autocfg.hp_pins[0];
+	hda_nid_t line_nid = spec->autocfg.line_out_pins[0];
+	hda_nid_t speaker_nid = spec->autocfg.speaker_pins[0];
+	unsigned int mute;
+
+	/* HP */
+	mute = spec->master_sw ? 0 : HDA_AMP_MUTE;
+	snd_hda_codec_amp_stereo(codec, hp_nid, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, mute);
+	/* mute internal speaker per jack sense */
+	if (spec->jack_present)
+		mute = HDA_AMP_MUTE;
+	if (line_nid)
+		snd_hda_codec_amp_stereo(codec, line_nid, HDA_OUTPUT, 0,
+					 HDA_AMP_MUTE, mute);
+	if (speaker_nid && speaker_nid != line_nid)
+		snd_hda_codec_amp_stereo(codec, speaker_nid, HDA_OUTPUT, 0,
+					 HDA_AMP_MUTE, mute);
+}
+
+#define alc262_hippo_master_sw_get	alc262_hp_master_sw_get
+
+static int alc262_hippo_master_sw_put(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	int val = !!*ucontrol->value.integer.value;
+
+	if (val == spec->master_sw)
+		return 0;
+	spec->master_sw = val;
+	alc262_hippo_master_update(codec);
+	return 1;
+}
+
+#define ALC262_HIPPO_MASTER_SWITCH				\
+	{							\
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,		\
+		.name = "Master Playback Switch",		\
+		.info = snd_ctl_boolean_mono_info,		\
+		.get = alc262_hippo_master_sw_get,		\
+		.put = alc262_hippo_master_sw_put,		\
+	},							\
+	{							\
+		.iface = NID_MAPPING,				\
+		.name = "Master Playback Switch",		\
+		.subdevice = SUBDEV_HP(0) | (SUBDEV_LINE(0) << 8) | \
+			     (SUBDEV_SPEAKER(0) << 16), \
+	}
+
+static struct snd_kcontrol_new alc262_hippo_mixer[] = {
+	ALC262_HIPPO_MASTER_SWITCH,
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc262_hippo1_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	ALC262_HIPPO_MASTER_SWITCH,
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+/* mute/unmute internal speaker according to the hp jack and mute state */
+static void alc262_hippo_automute(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t hp_nid = spec->autocfg.hp_pins[0];
+
+	spec->jack_present = snd_hda_jack_detect(codec, hp_nid);
+	alc262_hippo_master_update(codec);
+}
+
+static void alc262_hippo_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	if ((res >> 26) != ALC880_HP_EVENT)
+		return;
+	alc262_hippo_automute(codec);
+}
+
+static void alc262_hippo_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+}
+
+static void alc262_hippo1_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x1b;
+	spec->autocfg.speaker_pins[0] = 0x14;
+}
+
+
+static struct snd_kcontrol_new alc262_sony_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	ALC262_HIPPO_MASTER_SWITCH,
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("ATAPI Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("ATAPI Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc262_benq_t31_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	ALC262_HIPPO_MASTER_SWITCH,
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("ATAPI Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("ATAPI Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc262_tyan_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Master Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Aux Playback Volume", 0x0b, 0x06, HDA_INPUT),
+	HDA_CODEC_MUTE("Aux Playback Switch", 0x0b, 0x06, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_verb alc262_tyan_verbs[] = {
+	/* Headphone automute */
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* P11 AUX_IN, white 4-pin connector */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x14, AC_VERB_SET_CONFIG_DEFAULT_BYTES_1, 0xe1},
+	{0x14, AC_VERB_SET_CONFIG_DEFAULT_BYTES_2, 0x93},
+	{0x14, AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, 0x19},
+
+	{}
+};
+
+/* unsolicited event for HP jack sensing */
+static void alc262_tyan_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x1b;
+	spec->autocfg.speaker_pins[0] = 0x15;
+}
+
+
+#define alc262_capture_mixer		alc882_capture_mixer
+#define alc262_capture_alt_mixer	alc882_capture_alt_mixer
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc262_init_verbs[] = {
+	/*
+	 * Unmute ADC0-2 and set the default input to mic-in
+	 */
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 * Note: PASD motherboards uses the Line In 2 as the input for
+	 * front panel mic (mic 2)
+	 */
+	/* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+
+	/*
+	 * Set up output mixers (0x0c - 0x0e)
+	 */
+	/* set vol=0 to output mixers */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* set up input amps for analog loopback */
+	/* Amp Indices: DAC = 0, mixer = 1 */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+
+	/* FIXME: use matrix-type input source selection */
+	/* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */
+	/* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+	/* Input mixer2 */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+	/* Input mixer3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+
+	{ }
+};
+
+static struct hda_verb alc262_eapd_verbs[] = {
+	{0x14, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{0x15, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{ }
+};
+
+static struct hda_verb alc262_hippo1_unsol_verbs[] = {
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{}
+};
+
+static struct hda_verb alc262_sony_unsol_verbs[] = {
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},	// Front Mic
+
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{}
+};
+
+static struct snd_kcontrol_new alc262_toshiba_s06_mixer[] = {
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_verb alc262_toshiba_s06_verbs[] = {
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x22, AC_VERB_SET_CONNECT_SEL, 0x09},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static void alc262_toshiba_s06_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x12;
+	spec->int_mic.mux_idx = 9;
+	spec->auto_mic = 1;
+}
+
+/*
+ * nec model
+ *  0x15 = headphone
+ *  0x16 = internal speaker
+ *  0x18 = external mic
+ */
+
+static struct snd_kcontrol_new alc262_nec_mixer[] = {
+	HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Speaker Playback Switch", 0x16, 0, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct hda_verb alc262_nec_verbs[] = {
+	/* Unmute Speaker */
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Headphone */
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+
+	/* External mic to headphone */
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* External mic to speaker */
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{}
+};
+
+/*
+ * fujitsu model
+ *  0x14 = headphone/spdif-out, 0x15 = internal speaker,
+ *  0x1b = port replicator headphone out
+ */
+
+#define ALC_HP_EVENT	0x37
+
+static struct hda_verb alc262_fujitsu_unsol_verbs[] = {
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC_HP_EVENT},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC_HP_EVENT},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{}
+};
+
+static struct hda_verb alc262_lenovo_3000_unsol_verbs[] = {
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC_HP_EVENT},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{}
+};
+
+static struct hda_verb alc262_lenovo_3000_init_verbs[] = {
+	/* Front Mic pin: input vref at 50% */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{}
+};
+
+static struct hda_input_mux alc262_fujitsu_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Int Mic", 0x1 },
+		{ "CD", 0x4 },
+	},
+};
+
+static struct hda_input_mux alc262_HP_capture_source = {
+	.num_items = 5,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x1 },
+		{ "Line", 0x2 },
+		{ "CD", 0x4 },
+		{ "AUX IN", 0x6 },
+	},
+};
+
+static struct hda_input_mux alc262_HP_D7000_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x2 },
+		{ "Line", 0x1 },
+		{ "CD", 0x4 },
+	},
+};
+
+/* mute/unmute internal speaker according to the hp jacks and mute state */
+static void alc262_fujitsu_automute(struct hda_codec *codec, int force)
+{
+	struct alc_spec *spec = codec->spec;
+	unsigned int mute;
+
+	if (force || !spec->sense_updated) {
+		spec->jack_present = snd_hda_jack_detect(codec, 0x14) ||
+				     snd_hda_jack_detect(codec, 0x1b);
+		spec->sense_updated = 1;
+	}
+	/* unmute internal speaker only if both HPs are unplugged and
+	 * master switch is on
+	 */
+	if (spec->jack_present)
+		mute = HDA_AMP_MUTE;
+	else
+		mute = snd_hda_codec_amp_read(codec, 0x14, 0, HDA_OUTPUT, 0);
+	snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, mute);
+}
+
+/* unsolicited event for HP jack sensing */
+static void alc262_fujitsu_unsol_event(struct hda_codec *codec,
+				       unsigned int res)
+{
+	if ((res >> 26) != ALC_HP_EVENT)
+		return;
+	alc262_fujitsu_automute(codec, 1);
+}
+
+static void alc262_fujitsu_init_hook(struct hda_codec *codec)
+{
+	alc262_fujitsu_automute(codec, 1);
+}
+
+/* bind volumes of both NID 0x0c and 0x0d */
+static struct hda_bind_ctls alc262_fujitsu_bind_master_vol = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x0c, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x0d, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+/* mute/unmute internal speaker according to the hp jack and mute state */
+static void alc262_lenovo_3000_automute(struct hda_codec *codec, int force)
+{
+	struct alc_spec *spec = codec->spec;
+	unsigned int mute;
+
+	if (force || !spec->sense_updated) {
+		spec->jack_present = snd_hda_jack_detect(codec, 0x1b);
+		spec->sense_updated = 1;
+	}
+	if (spec->jack_present) {
+		/* mute internal speaker */
+		snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0,
+					 HDA_AMP_MUTE, HDA_AMP_MUTE);
+		snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0,
+					 HDA_AMP_MUTE, HDA_AMP_MUTE);
+	} else {
+		/* unmute internal speaker if necessary */
+		mute = snd_hda_codec_amp_read(codec, 0x1b, 0, HDA_OUTPUT, 0);
+		snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0,
+					 HDA_AMP_MUTE, mute);
+		snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0,
+					 HDA_AMP_MUTE, mute);
+	}
+}
+
+/* unsolicited event for HP jack sensing */
+static void alc262_lenovo_3000_unsol_event(struct hda_codec *codec,
+				       unsigned int res)
+{
+	if ((res >> 26) != ALC_HP_EVENT)
+		return;
+	alc262_lenovo_3000_automute(codec, 1);
+}
+
+static int amp_stereo_mute_update(struct hda_codec *codec, hda_nid_t nid,
+				  int dir, int idx, long *valp)
+{
+	int i, change = 0;
+
+	for (i = 0; i < 2; i++, valp++)
+		change |= snd_hda_codec_amp_update(codec, nid, i, dir, idx,
+						   HDA_AMP_MUTE,
+						   *valp ? 0 : HDA_AMP_MUTE);
+	return change;
+}
+
+/* bind hp and internal speaker mute (with plug check) */
+static int alc262_fujitsu_master_sw_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	long *valp = ucontrol->value.integer.value;
+	int change;
+
+	change = amp_stereo_mute_update(codec, 0x14, HDA_OUTPUT, 0, valp);
+	change |= amp_stereo_mute_update(codec, 0x1b, HDA_OUTPUT, 0, valp);
+	if (change)
+		alc262_fujitsu_automute(codec, 0);
+	return change;
+}
+
+static struct snd_kcontrol_new alc262_fujitsu_mixer[] = {
+	HDA_BIND_VOL("Master Playback Volume", &alc262_fujitsu_bind_master_vol),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.info = snd_hda_mixer_amp_switch_info,
+		.get = snd_hda_mixer_amp_switch_get,
+		.put = alc262_fujitsu_master_sw_put,
+		.private_value = HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+	},
+	{
+		.iface = NID_MAPPING,
+		.name = "Master Playback Switch",
+		.private_value = 0x1b,
+	},
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+/* bind hp and internal speaker mute (with plug check) */
+static int alc262_lenovo_3000_master_sw_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	long *valp = ucontrol->value.integer.value;
+	int change;
+
+	change = amp_stereo_mute_update(codec, 0x1b, HDA_OUTPUT, 0, valp);
+	if (change)
+		alc262_lenovo_3000_automute(codec, 0);
+	return change;
+}
+
+static struct snd_kcontrol_new alc262_lenovo_3000_mixer[] = {
+	HDA_BIND_VOL("Master Playback Volume", &alc262_fujitsu_bind_master_vol),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.info = snd_hda_mixer_amp_switch_info,
+		.get = snd_hda_mixer_amp_switch_get,
+		.put = alc262_lenovo_3000_master_sw_put,
+		.private_value = HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_OUTPUT),
+	},
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc262_toshiba_rx1_mixer[] = {
+	HDA_BIND_VOL("Master Playback Volume", &alc262_fujitsu_bind_master_vol),
+	ALC262_HIPPO_MASTER_SWITCH,
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+/* additional init verbs for Benq laptops */
+static struct hda_verb alc262_EAPD_verbs[] = {
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x07},
+	{0x20, AC_VERB_SET_PROC_COEF,  0x3070},
+	{}
+};
+
+static struct hda_verb alc262_benq_t31_EAPD_verbs[] = {
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x07},
+	{0x20, AC_VERB_SET_PROC_COEF,  0x3050},
+	{}
+};
+
+/* Samsung Q1 Ultra Vista model setup */
+static struct snd_kcontrol_new alc262_ultra_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Master Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Mic Boost", 0x15, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_verb alc262_ultra_verbs[] = {
+	/* output mixer */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	/* speaker */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* HP */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	/* internal mic */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* ADC, choose mic */
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(8)},
+	{}
+};
+
+/* mute/unmute internal speaker according to the hp jack and mute state */
+static void alc262_ultra_automute(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	unsigned int mute;
+
+	mute = 0;
+	/* auto-mute only when HP is used as HP */
+	if (!spec->cur_mux[0]) {
+		spec->jack_present = snd_hda_jack_detect(codec, 0x15);
+		if (spec->jack_present)
+			mute = HDA_AMP_MUTE;
+	}
+	/* mute/unmute internal speaker */
+	snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, mute);
+	/* mute/unmute HP */
+	snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, mute ? 0 : HDA_AMP_MUTE);
+}
+
+/* unsolicited event for HP jack sensing */
+static void alc262_ultra_unsol_event(struct hda_codec *codec,
+				       unsigned int res)
+{
+	if ((res >> 26) != ALC880_HP_EVENT)
+		return;
+	alc262_ultra_automute(codec);
+}
+
+static struct hda_input_mux alc262_ultra_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Mic", 0x1 },
+		{ "Headphone", 0x7 },
+	},
+};
+
+static int alc262_ultra_mux_enum_put(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct alc_spec *spec = codec->spec;
+	int ret;
+
+	ret = alc_mux_enum_put(kcontrol, ucontrol);
+	if (!ret)
+		return 0;
+	/* reprogram the HP pin as mic or HP according to the input source */
+	snd_hda_codec_write_cache(codec, 0x15, 0,
+				  AC_VERB_SET_PIN_WIDGET_CONTROL,
+				  spec->cur_mux[0] ? PIN_VREF80 : PIN_HP);
+	alc262_ultra_automute(codec); /* mute/unmute HP */
+	return ret;
+}
+
+static struct snd_kcontrol_new alc262_ultra_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x07, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x07, 0x0, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = alc_mux_enum_info,
+		.get = alc_mux_enum_get,
+		.put = alc262_ultra_mux_enum_put,
+	},
+	{
+		.iface = NID_MAPPING,
+		.name = "Capture Source",
+		.private_value = 0x15,
+	},
+	{ } /* end */
+};
+
+/* We use two mixers depending on the output pin; 0x16 is a mono output
+ * and thus it's bound with a different mixer.
+ * This function returns which mixer amp should be used.
+ */
+static int alc262_check_volbit(hda_nid_t nid)
+{
+	if (!nid)
+		return 0;
+	else if (nid == 0x16)
+		return 2;
+	else
+		return 1;
+}
+
+static int alc262_add_out_vol_ctl(struct alc_spec *spec, hda_nid_t nid,
+				  const char *pfx, int *vbits, int idx)
+{
+	unsigned long val;
+	int vbit;
+
+	vbit = alc262_check_volbit(nid);
+	if (!vbit)
+		return 0;
+	if (*vbits & vbit) /* a volume control for this mixer already there */
+		return 0;
+	*vbits |= vbit;
+	if (vbit == 2)
+		val = HDA_COMPOSE_AMP_VAL(0x0e, 2, 0, HDA_OUTPUT);
+	else
+		val = HDA_COMPOSE_AMP_VAL(0x0c, 3, 0, HDA_OUTPUT);
+	return __add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL, pfx, idx, val);
+}
+
+static int alc262_add_out_sw_ctl(struct alc_spec *spec, hda_nid_t nid,
+				 const char *pfx, int idx)
+{
+	unsigned long val;
+
+	if (!nid)
+		return 0;
+	if (nid == 0x16)
+		val = HDA_COMPOSE_AMP_VAL(nid, 2, 0, HDA_OUTPUT);
+	else
+		val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+	return __add_pb_sw_ctrl(spec, ALC_CTL_WIDGET_MUTE, pfx, idx, val);
+}
+
+/* add playback controls from the parsed DAC table */
+static int alc262_auto_create_multi_out_ctls(struct alc_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	const char *pfx;
+	int vbits;
+	int i, err;
+
+	spec->multiout.num_dacs = 1;	/* only use one dac */
+	spec->multiout.dac_nids = spec->private_dac_nids;
+	spec->multiout.dac_nids[0] = 2;
+
+	if (!cfg->speaker_pins[0] && !cfg->hp_pins[0])
+		pfx = "Master";
+	else if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
+		pfx = "Speaker";
+	else if (cfg->line_out_type == AUTO_PIN_HP_OUT)
+		pfx = "Headphone";
+	else
+		pfx = "Front";
+	for (i = 0; i < 2; i++) {
+		err = alc262_add_out_sw_ctl(spec, cfg->line_out_pins[i], pfx, i);
+		if (err < 0)
+			return err;
+		if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+			err = alc262_add_out_sw_ctl(spec, cfg->speaker_pins[i],
+						    "Speaker", i);
+			if (err < 0)
+				return err;
+		}
+		if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
+			err = alc262_add_out_sw_ctl(spec, cfg->hp_pins[i],
+						    "Headphone", i);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	vbits = alc262_check_volbit(cfg->line_out_pins[0]) |
+		alc262_check_volbit(cfg->speaker_pins[0]) |
+		alc262_check_volbit(cfg->hp_pins[0]);
+	if (vbits == 1 || vbits == 2)
+		pfx = "Master"; /* only one mixer is used */
+	vbits = 0;
+	for (i = 0; i < 2; i++) {
+		err = alc262_add_out_vol_ctl(spec, cfg->line_out_pins[i], pfx,
+					     &vbits, i);
+		if (err < 0)
+			return err;
+		if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+			err = alc262_add_out_vol_ctl(spec, cfg->speaker_pins[i],
+						     "Speaker", &vbits, i);
+			if (err < 0)
+				return err;
+		}
+		if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
+			err = alc262_add_out_vol_ctl(spec, cfg->hp_pins[i],
+						     "Headphone", &vbits, i);
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+#define alc262_auto_create_input_ctls \
+	alc882_auto_create_input_ctls
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc262_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-2 and set the default input to mic-in
+	 */
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 * Note: PASD motherboards uses the Line In 2 as the input for
+	 * front panel mic (mic 2)
+	 */
+	/* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+
+	/*
+	 * Set up output mixers (0x0c - 0x0f)
+	 */
+	/* set vol=0 to output mixers */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* set up input amps for analog loopback */
+	/* Amp Indices: DAC = 0, mixer = 1 */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* FIXME: use matrix-type input source selection */
+	/* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */
+	/* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+	/* Input mixer2 */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+	/* Input mixer3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+
+	{ }
+};
+
+static struct hda_verb alc262_HP_BPC_init_verbs[] = {
+	/*
+	 * Unmute ADC0-2 and set the default input to mic-in
+	 */
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 * Note: PASD motherboards uses the Line In 2 as the input for
+	 * front panel mic (mic 2)
+	 */
+	/* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+        {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)},
+
+	/*
+	 * Set up output mixers (0x0c - 0x0e)
+	 */
+	/* set vol=0 to output mixers */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* set up input amps for analog loopback */
+	/* Amp Indices: DAC = 0, mixer = 1 */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+        {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 },
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 },
+        {0x19, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 },
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 },
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 },
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 },
+
+
+	/* FIXME: use matrix-type input source selection */
+	/* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 0b, 12 */
+	/* Input mixer1: only unmute Mic */
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x05 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x06 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x07 << 8))},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x08 << 8))},
+	/* Input mixer2 */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x05 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x06 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x07 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x08 << 8))},
+	/* Input mixer3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x05 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x06 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x07 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x08 << 8))},
+
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+
+	{ }
+};
+
+static struct hda_verb alc262_HP_BPC_WildWest_init_verbs[] = {
+	/*
+	 * Unmute ADC0-2 and set the default input to mic-in
+	 */
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 * Note: PASD motherboards uses the Line In 2 as the input for front
+	 * panel mic (mic 2)
+	 */
+	/* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)},
+	/*
+	 * Set up output mixers (0x0c - 0x0e)
+	 */
+	/* set vol=0 to output mixers */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* set up input amps for analog loopback */
+	/* Amp Indices: DAC = 0, mixer = 1 */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },	/* HP */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },	/* Mono */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },	/* rear MIC */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },	/* Line in */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },	/* Front MIC */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },	/* Line out */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },	/* CD in */
+
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+
+	/* {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x7023 }, */
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 },
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 },
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0x7023 },
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 },
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 },
+
+	/* FIXME: use matrix-type input source selection */
+	/* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */
+	/* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, /*rear MIC*/
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, /*Line in*/
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, /*F MIC*/
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))}, /*Front*/
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))}, /*CD*/
+        /* {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x06 << 8))},  */
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x07 << 8))}, /*HP*/
+	/* Input mixer2 */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))},
+        /* {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x06 << 8))}, */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x07 << 8))},
+	/* Input mixer3 */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))},
+        /* {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x06 << 8))}, */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x07 << 8))},
+
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+
+	{ }
+};
+
+static struct hda_verb alc262_toshiba_rx1_unsol_verbs[] = {
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },	/* Front Speaker */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x01},
+
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },	/* MIC jack */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },	/* Front MIC */
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) },
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) },
+
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP },	/* HP  jack */
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+/*
+ * Pin config fixes
+ */
+enum {
+	PINFIX_FSC_H270,
+};
+
+static const struct alc_fixup alc262_fixups[] = {
+	[PINFIX_FSC_H270] = {
+		.pins = (const struct alc_pincfg[]) {
+			{ 0x14, 0x99130110 }, /* speaker */
+			{ 0x15, 0x0221142f }, /* front HP */
+			{ 0x1b, 0x0121141f }, /* rear HP */
+			{ }
+		}
+	},
+	[PINFIX_PB_M5210] = {
+		.verbs = (const struct hda_verb[]) {
+			{ 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50 },
+			{}
+		}
+	},
+};
+
+static struct snd_pci_quirk alc262_fixup_tbl[] = {
+	SND_PCI_QUIRK(0x1734, 0x1147, "FSC Celsius H270", PINFIX_FSC_H270),
+	{}
+};
+
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+#define alc262_loopbacks	alc880_loopbacks
+#endif
+
+/* pcm configuration: identical with ALC880 */
+#define alc262_pcm_analog_playback	alc880_pcm_analog_playback
+#define alc262_pcm_analog_capture	alc880_pcm_analog_capture
+#define alc262_pcm_digital_playback	alc880_pcm_digital_playback
+#define alc262_pcm_digital_capture	alc880_pcm_digital_capture
+
+/*
+ * BIOS auto configuration
+ */
+static int alc262_parse_auto_config(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int err;
+	static hda_nid_t alc262_ignore[] = { 0x1d, 0 };
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg,
+					   alc262_ignore);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs) {
+		if (spec->autocfg.dig_outs || spec->autocfg.dig_in_pin) {
+			spec->multiout.max_channels = 2;
+			spec->no_analog = 1;
+			goto dig_only;
+		}
+		return 0; /* can't find valid BIOS pin config */
+	}
+	err = alc262_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc262_auto_create_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+ dig_only:
+	alc_auto_parse_digital(codec);
+
+	if (spec->kctls.list)
+		add_mixer(spec, spec->kctls.list);
+
+	add_verb(spec, alc262_volume_init_verbs);
+	spec->num_mux_defs = 1;
+	spec->input_mux = &spec->private_imux[0];
+
+	err = alc_auto_add_mic_boost(codec);
+	if (err < 0)
+		return err;
+
+	alc_ssid_check(codec, 0x15, 0x1b, 0x14, 0);
+
+	return 1;
+}
+
+#define alc262_auto_init_multi_out	alc882_auto_init_multi_out
+#define alc262_auto_init_hp_out		alc882_auto_init_hp_out
+#define alc262_auto_init_analog_input	alc882_auto_init_analog_input
+#define alc262_auto_init_input_src	alc882_auto_init_input_src
+
+
+/* init callback for auto-configuration model -- overriding the default init */
+static void alc262_auto_init(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc262_auto_init_multi_out(codec);
+	alc262_auto_init_hp_out(codec);
+	alc262_auto_init_analog_input(codec);
+	alc262_auto_init_input_src(codec);
+	alc_auto_init_digital(codec);
+	if (spec->unsol_event)
+		alc_inithook(codec);
+}
+
+/*
+ * configuration and preset
+ */
+static const char *alc262_models[ALC262_MODEL_LAST] = {
+	[ALC262_BASIC]		= "basic",
+	[ALC262_HIPPO]		= "hippo",
+	[ALC262_HIPPO_1]	= "hippo_1",
+	[ALC262_FUJITSU]	= "fujitsu",
+	[ALC262_HP_BPC]		= "hp-bpc",
+	[ALC262_HP_BPC_D7000_WL]= "hp-bpc-d7000",
+	[ALC262_HP_TC_T5735]	= "hp-tc-t5735",
+	[ALC262_HP_RP5700]	= "hp-rp5700",
+	[ALC262_BENQ_ED8]	= "benq",
+	[ALC262_BENQ_T31]	= "benq-t31",
+	[ALC262_SONY_ASSAMD]	= "sony-assamd",
+	[ALC262_TOSHIBA_S06]	= "toshiba-s06",
+	[ALC262_TOSHIBA_RX1]	= "toshiba-rx1",
+	[ALC262_ULTRA]		= "ultra",
+	[ALC262_LENOVO_3000]	= "lenovo-3000",
+	[ALC262_NEC]		= "nec",
+	[ALC262_TYAN]		= "tyan",
+	[ALC262_AUTO]		= "auto",
+};
+
+static struct snd_pci_quirk alc262_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1002, 0x437b, "Hippo", ALC262_HIPPO),
+	SND_PCI_QUIRK(0x1033, 0x8895, "NEC Versa S9100", ALC262_NEC),
+	SND_PCI_QUIRK_MASK(0x103c, 0xff00, 0x1200, "HP xw series",
+			   ALC262_HP_BPC),
+	SND_PCI_QUIRK_MASK(0x103c, 0xff00, 0x1300, "HP xw series",
+			   ALC262_HP_BPC),
+	SND_PCI_QUIRK_MASK(0x103c, 0xff00, 0x1700, "HP xw series",
+			   ALC262_HP_BPC),
+	SND_PCI_QUIRK(0x103c, 0x2800, "HP D7000", ALC262_HP_BPC_D7000_WL),
+	SND_PCI_QUIRK(0x103c, 0x2801, "HP D7000", ALC262_HP_BPC_D7000_WF),
+	SND_PCI_QUIRK(0x103c, 0x2802, "HP D7000", ALC262_HP_BPC_D7000_WL),
+	SND_PCI_QUIRK(0x103c, 0x2803, "HP D7000", ALC262_HP_BPC_D7000_WF),
+	SND_PCI_QUIRK(0x103c, 0x2804, "HP D7000", ALC262_HP_BPC_D7000_WL),
+	SND_PCI_QUIRK(0x103c, 0x2805, "HP D7000", ALC262_HP_BPC_D7000_WF),
+	SND_PCI_QUIRK(0x103c, 0x2806, "HP D7000", ALC262_HP_BPC_D7000_WL),
+	SND_PCI_QUIRK(0x103c, 0x2807, "HP D7000", ALC262_HP_BPC_D7000_WF),
+	SND_PCI_QUIRK(0x103c, 0x280c, "HP xw4400", ALC262_HP_BPC),
+	SND_PCI_QUIRK(0x103c, 0x3014, "HP xw6400", ALC262_HP_BPC),
+	SND_PCI_QUIRK(0x103c, 0x3015, "HP xw8400", ALC262_HP_BPC),
+	SND_PCI_QUIRK(0x103c, 0x302f, "HP Thin Client T5735",
+		      ALC262_HP_TC_T5735),
+	SND_PCI_QUIRK(0x103c, 0x2817, "HP RP5700", ALC262_HP_RP5700),
+	SND_PCI_QUIRK(0x104d, 0x1f00, "Sony ASSAMD", ALC262_SONY_ASSAMD),
+	SND_PCI_QUIRK(0x104d, 0x8203, "Sony UX-90", ALC262_HIPPO),
+	SND_PCI_QUIRK(0x104d, 0x820f, "Sony ASSAMD", ALC262_SONY_ASSAMD),
+	SND_PCI_QUIRK(0x104d, 0x9016, "Sony VAIO", ALC262_AUTO), /* dig-only */
+	SND_PCI_QUIRK(0x104d, 0x9025, "Sony VAIO Z21MN", ALC262_TOSHIBA_S06),
+	SND_PCI_QUIRK(0x104d, 0x9035, "Sony VAIO VGN-FW170J", ALC262_AUTO),
+	SND_PCI_QUIRK(0x104d, 0x9047, "Sony VAIO Type G", ALC262_AUTO),
+#if 0 /* disable the quirk since model=auto works better in recent versions */
+	SND_PCI_QUIRK_MASK(0x104d, 0xff00, 0x9000, "Sony VAIO",
+			   ALC262_SONY_ASSAMD),
+#endif
+	SND_PCI_QUIRK(0x1179, 0x0001, "Toshiba dynabook SS RX1",
+		      ALC262_TOSHIBA_RX1),
+	SND_PCI_QUIRK(0x1179, 0xff7b, "Toshiba S06", ALC262_TOSHIBA_S06),
+	SND_PCI_QUIRK(0x10cf, 0x1397, "Fujitsu", ALC262_FUJITSU),
+	SND_PCI_QUIRK(0x10cf, 0x142d, "Fujitsu Lifebook E8410", ALC262_FUJITSU),
+	SND_PCI_QUIRK(0x10f1, 0x2915, "Tyan Thunder n6650W", ALC262_TYAN),
+	SND_PCI_QUIRK_MASK(0x144d, 0xff00, 0xc032, "Samsung Q1",
+			   ALC262_ULTRA),
+	SND_PCI_QUIRK(0x144d, 0xc510, "Samsung Q45", ALC262_HIPPO),
+	SND_PCI_QUIRK(0x17aa, 0x384e, "Lenovo 3000 y410", ALC262_LENOVO_3000),
+	SND_PCI_QUIRK(0x17ff, 0x0560, "Benq ED8", ALC262_BENQ_ED8),
+	SND_PCI_QUIRK(0x17ff, 0x058d, "Benq T31-16", ALC262_BENQ_T31),
+	SND_PCI_QUIRK(0x17ff, 0x058f, "Benq Hippo", ALC262_HIPPO_1),
+	{}
+};
+
+static struct alc_config_preset alc262_presets[] = {
+	[ALC262_BASIC] = {
+		.mixers = { alc262_base_mixer },
+		.init_verbs = { alc262_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_capture_source,
+	},
+	[ALC262_HIPPO] = {
+		.mixers = { alc262_hippo_mixer },
+		.init_verbs = { alc262_init_verbs, alc_hp15_unsol_verbs},
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.dig_out_nid = ALC262_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_capture_source,
+		.unsol_event = alc262_hippo_unsol_event,
+		.setup = alc262_hippo_setup,
+		.init_hook = alc262_hippo_automute,
+	},
+	[ALC262_HIPPO_1] = {
+		.mixers = { alc262_hippo1_mixer },
+		.init_verbs = { alc262_init_verbs, alc262_hippo1_unsol_verbs},
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x02,
+		.dig_out_nid = ALC262_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_capture_source,
+		.unsol_event = alc262_hippo_unsol_event,
+		.setup = alc262_hippo1_setup,
+		.init_hook = alc262_hippo_automute,
+	},
+	[ALC262_FUJITSU] = {
+		.mixers = { alc262_fujitsu_mixer },
+		.init_verbs = { alc262_init_verbs, alc262_EAPD_verbs,
+				alc262_fujitsu_unsol_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.dig_out_nid = ALC262_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_fujitsu_capture_source,
+		.unsol_event = alc262_fujitsu_unsol_event,
+		.init_hook = alc262_fujitsu_init_hook,
+	},
+	[ALC262_HP_BPC] = {
+		.mixers = { alc262_HP_BPC_mixer },
+		.init_verbs = { alc262_HP_BPC_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_HP_capture_source,
+		.unsol_event = alc262_hp_bpc_unsol_event,
+		.init_hook = alc262_hp_bpc_automute,
+	},
+	[ALC262_HP_BPC_D7000_WF] = {
+		.mixers = { alc262_HP_BPC_WildWest_mixer },
+		.init_verbs = { alc262_HP_BPC_WildWest_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_HP_D7000_capture_source,
+		.unsol_event = alc262_hp_wildwest_unsol_event,
+		.init_hook = alc262_hp_wildwest_automute,
+	},
+	[ALC262_HP_BPC_D7000_WL] = {
+		.mixers = { alc262_HP_BPC_WildWest_mixer,
+			    alc262_HP_BPC_WildWest_option_mixer },
+		.init_verbs = { alc262_HP_BPC_WildWest_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_HP_D7000_capture_source,
+		.unsol_event = alc262_hp_wildwest_unsol_event,
+		.init_hook = alc262_hp_wildwest_automute,
+	},
+	[ALC262_HP_TC_T5735] = {
+		.mixers = { alc262_hp_t5735_mixer },
+		.init_verbs = { alc262_init_verbs, alc262_hp_t5735_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_capture_source,
+		.unsol_event = alc_sku_unsol_event,
+		.setup = alc262_hp_t5735_setup,
+		.init_hook = alc_inithook,
+	},
+	[ALC262_HP_RP5700] = {
+		.mixers = { alc262_hp_rp5700_mixer },
+		.init_verbs = { alc262_init_verbs, alc262_hp_rp5700_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_hp_rp5700_capture_source,
+        },
+	[ALC262_BENQ_ED8] = {
+		.mixers = { alc262_base_mixer },
+		.init_verbs = { alc262_init_verbs, alc262_EAPD_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_capture_source,
+	},
+	[ALC262_SONY_ASSAMD] = {
+		.mixers = { alc262_sony_mixer },
+		.init_verbs = { alc262_init_verbs, alc262_sony_unsol_verbs},
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x02,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_capture_source,
+		.unsol_event = alc262_hippo_unsol_event,
+		.setup = alc262_hippo_setup,
+		.init_hook = alc262_hippo_automute,
+	},
+	[ALC262_BENQ_T31] = {
+		.mixers = { alc262_benq_t31_mixer },
+		.init_verbs = { alc262_init_verbs, alc262_benq_t31_EAPD_verbs,
+				alc_hp15_unsol_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_capture_source,
+		.unsol_event = alc262_hippo_unsol_event,
+		.setup = alc262_hippo_setup,
+		.init_hook = alc262_hippo_automute,
+	},
+	[ALC262_ULTRA] = {
+		.mixers = { alc262_ultra_mixer },
+		.cap_mixer = alc262_ultra_capture_mixer,
+		.init_verbs = { alc262_ultra_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_ultra_capture_source,
+		.adc_nids = alc262_adc_nids, /* ADC0 */
+		.capsrc_nids = alc262_capsrc_nids,
+		.num_adc_nids = 1, /* single ADC */
+		.unsol_event = alc262_ultra_unsol_event,
+		.init_hook = alc262_ultra_automute,
+	},
+	[ALC262_LENOVO_3000] = {
+		.mixers = { alc262_lenovo_3000_mixer },
+		.init_verbs = { alc262_init_verbs, alc262_EAPD_verbs,
+				alc262_lenovo_3000_unsol_verbs,
+				alc262_lenovo_3000_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.dig_out_nid = ALC262_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_fujitsu_capture_source,
+		.unsol_event = alc262_lenovo_3000_unsol_event,
+	},
+	[ALC262_NEC] = {
+		.mixers = { alc262_nec_mixer },
+		.init_verbs = { alc262_nec_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_capture_source,
+	},
+	[ALC262_TOSHIBA_S06] = {
+		.mixers = { alc262_toshiba_s06_mixer },
+		.init_verbs = { alc262_init_verbs, alc262_toshiba_s06_verbs,
+							alc262_eapd_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.capsrc_nids = alc262_dmic_capsrc_nids,
+		.dac_nids = alc262_dac_nids,
+		.adc_nids = alc262_dmic_adc_nids, /* ADC0 */
+		.num_adc_nids = 1, /* single ADC */
+		.dig_out_nid = ALC262_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.unsol_event = alc_sku_unsol_event,
+		.setup = alc262_toshiba_s06_setup,
+		.init_hook = alc_inithook,
+	},
+	[ALC262_TOSHIBA_RX1] = {
+		.mixers = { alc262_toshiba_rx1_mixer },
+		.init_verbs = { alc262_init_verbs, alc262_toshiba_rx1_unsol_verbs },
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_capture_source,
+		.unsol_event = alc262_hippo_unsol_event,
+		.setup = alc262_hippo_setup,
+		.init_hook = alc262_hippo_automute,
+	},
+	[ALC262_TYAN] = {
+		.mixers = { alc262_tyan_mixer },
+		.init_verbs = { alc262_init_verbs, alc262_tyan_verbs},
+		.num_dacs = ARRAY_SIZE(alc262_dac_nids),
+		.dac_nids = alc262_dac_nids,
+		.hp_nid = 0x02,
+		.dig_out_nid = ALC262_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc262_modes),
+		.channel_mode = alc262_modes,
+		.input_mux = &alc262_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc262_tyan_setup,
+		.init_hook = alc_automute_amp,
+	},
+};
+
+static int patch_alc262(struct hda_codec *codec)
+{
+	struct alc_spec *spec;
+	int board_config;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+#if 0
+	/* pshou 07/11/05  set a zero PCM sample to DAC when FIFO is
+	 * under-run
+	 */
+	{
+	int tmp;
+	snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_COEF_INDEX, 7);
+	tmp = snd_hda_codec_read(codec, 0x20, 0, AC_VERB_GET_PROC_COEF, 0);
+	snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_COEF_INDEX, 7);
+	snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PROC_COEF, tmp | 0x80);
+	}
+#endif
+	alc_auto_parse_customize_define(codec);
+
+	alc_fix_pll_init(codec, 0x20, 0x0a, 10);
+
+	board_config = snd_hda_check_board_config(codec, ALC262_MODEL_LAST,
+						  alc262_models,
+						  alc262_cfg_tbl);
+
+	if (board_config < 0) {
+		printk(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+		       codec->chip_name);
+		board_config = ALC262_AUTO;
+	}
+
+	if (board_config == ALC262_AUTO)
+		alc_pick_fixup(codec, alc262_fixup_tbl, alc262_fixups, 1);
+
+	if (board_config == ALC262_AUTO) {
+		/* automatic parse from the BIOS config */
+		err = alc262_parse_auto_config(codec);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		} else if (!err) {
+			printk(KERN_INFO
+			       "hda_codec: Cannot set up configuration "
+			       "from BIOS.  Using base mode...\n");
+			board_config = ALC262_BASIC;
+		}
+	}
+
+	if (!spec->no_analog && has_cdefine_beep(codec)) {
+		err = snd_hda_attach_beep_device(codec, 0x1);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		}
+	}
+
+	if (board_config != ALC262_AUTO)
+		setup_preset(codec, &alc262_presets[board_config]);
+
+	spec->stream_analog_playback = &alc262_pcm_analog_playback;
+	spec->stream_analog_capture = &alc262_pcm_analog_capture;
+
+	spec->stream_digital_playback = &alc262_pcm_digital_playback;
+	spec->stream_digital_capture = &alc262_pcm_digital_capture;
+
+	if (!spec->adc_nids && spec->input_mux) {
+		int i;
+		/* check whether the digital-mic has to be supported */
+		for (i = 0; i < spec->input_mux->num_items; i++) {
+			if (spec->input_mux->items[i].index >= 9)
+				break;
+		}
+		if (i < spec->input_mux->num_items) {
+			/* use only ADC0 */
+			spec->adc_nids = alc262_dmic_adc_nids;
+			spec->num_adc_nids = 1;
+			spec->capsrc_nids = alc262_dmic_capsrc_nids;
+		} else {
+			/* all analog inputs */
+			/* check whether NID 0x07 is valid */
+			unsigned int wcap = get_wcaps(codec, 0x07);
+
+			/* get type */
+			wcap = get_wcaps_type(wcap);
+			if (wcap != AC_WID_AUD_IN) {
+				spec->adc_nids = alc262_adc_nids_alt;
+				spec->num_adc_nids =
+					ARRAY_SIZE(alc262_adc_nids_alt);
+				spec->capsrc_nids = alc262_capsrc_nids_alt;
+			} else {
+				spec->adc_nids = alc262_adc_nids;
+				spec->num_adc_nids =
+					ARRAY_SIZE(alc262_adc_nids);
+				spec->capsrc_nids = alc262_capsrc_nids;
+			}
+		}
+	}
+	if (!spec->cap_mixer && !spec->no_analog)
+		set_capture_mixer(codec);
+	if (!spec->no_analog && has_cdefine_beep(codec))
+		set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT);
+
+	if (board_config == ALC262_AUTO)
+		alc_pick_fixup(codec, alc262_fixup_tbl, alc262_fixups, 0);
+
+	spec->vmaster_nid = 0x0c;
+
+	codec->patch_ops = alc_patch_ops;
+	if (board_config == ALC262_AUTO)
+		spec->init_hook = alc262_auto_init;
+
+	alc_init_jacks(codec);
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	if (!spec->loopback.amplist)
+		spec->loopback.amplist = alc262_loopbacks;
+#endif
+
+	return 0;
+}
+
+/*
+ *  ALC268 channel source setting (2 channel)
+ */
+#define ALC268_DIGOUT_NID	ALC880_DIGOUT_NID
+#define alc268_modes		alc260_modes
+
+static hda_nid_t alc268_dac_nids[2] = {
+	/* front, hp */
+	0x02, 0x03
+};
+
+static hda_nid_t alc268_adc_nids[2] = {
+	/* ADC0-1 */
+	0x08, 0x07
+};
+
+static hda_nid_t alc268_adc_nids_alt[1] = {
+	/* ADC0 */
+	0x08
+};
+
+static hda_nid_t alc268_capsrc_nids[2] = { 0x23, 0x24 };
+
+static struct snd_kcontrol_new alc268_base_mixer[] = {
+	/* output mixer control */
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x3, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line In Boost", 0x1a, 0, HDA_INPUT),
+	{ }
+};
+
+static struct snd_kcontrol_new alc268_toshiba_mixer[] = {
+	/* output mixer control */
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x3, 0x0, HDA_OUTPUT),
+	ALC262_HIPPO_MASTER_SWITCH,
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line In Boost", 0x1a, 0, HDA_INPUT),
+	{ }
+};
+
+/* bind Beep switches of both NID 0x0f and 0x10 */
+static struct hda_bind_ctls alc268_bind_beep_sw = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x0f, 3, 1, HDA_INPUT),
+		HDA_COMPOSE_AMP_VAL(0x10, 3, 1, HDA_INPUT),
+		0
+	},
+};
+
+static struct snd_kcontrol_new alc268_beep_mixer[] = {
+	HDA_CODEC_VOLUME("Beep Playback Volume", 0x1d, 0x0, HDA_INPUT),
+	HDA_BIND_SW("Beep Playback Switch", &alc268_bind_beep_sw),
+	{ }
+};
+
+static struct hda_verb alc268_eapd_verbs[] = {
+	{0x14, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{0x15, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{ }
+};
+
+/* Toshiba specific */
+static struct hda_verb alc268_toshiba_verbs[] = {
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{ } /* end */
+};
+
+/* Acer specific */
+/* bind volumes of both NID 0x02 and 0x03 */
+static struct hda_bind_ctls alc268_acer_bind_master_vol = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x03, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+/* mute/unmute internal speaker according to the hp jack and mute state */
+static void alc268_acer_automute(struct hda_codec *codec, int force)
+{
+	struct alc_spec *spec = codec->spec;
+	unsigned int mute;
+
+	if (force || !spec->sense_updated) {
+		spec->jack_present = snd_hda_jack_detect(codec, 0x14);
+		spec->sense_updated = 1;
+	}
+	if (spec->jack_present)
+		mute = HDA_AMP_MUTE; /* mute internal speaker */
+	else /* unmute internal speaker if necessary */
+		mute = snd_hda_codec_amp_read(codec, 0x14, 0, HDA_OUTPUT, 0);
+	snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, mute);
+}
+
+
+/* bind hp and internal speaker mute (with plug check) */
+static int alc268_acer_master_sw_put(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	long *valp = ucontrol->value.integer.value;
+	int change;
+
+	change = amp_stereo_mute_update(codec, 0x14, HDA_OUTPUT, 0, valp);
+	if (change)
+		alc268_acer_automute(codec, 0);
+	return change;
+}
+
+static struct snd_kcontrol_new alc268_acer_aspire_one_mixer[] = {
+	/* output mixer control */
+	HDA_BIND_VOL("Master Playback Volume", &alc268_acer_bind_master_vol),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.info = snd_hda_mixer_amp_switch_info,
+		.get = snd_hda_mixer_amp_switch_get,
+		.put = alc268_acer_master_sw_put,
+		.private_value = HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+	},
+	HDA_CODEC_VOLUME("Mic Boost Capture Volume", 0x18, 0, HDA_INPUT),
+	{ }
+};
+
+static struct snd_kcontrol_new alc268_acer_mixer[] = {
+	/* output mixer control */
+	HDA_BIND_VOL("Master Playback Volume", &alc268_acer_bind_master_vol),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.info = snd_hda_mixer_amp_switch_info,
+		.get = snd_hda_mixer_amp_switch_get,
+		.put = alc268_acer_master_sw_put,
+		.private_value = HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+	},
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line In Boost", 0x1a, 0, HDA_INPUT),
+	{ }
+};
+
+static struct snd_kcontrol_new alc268_acer_dmic_mixer[] = {
+	/* output mixer control */
+	HDA_BIND_VOL("Master Playback Volume", &alc268_acer_bind_master_vol),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.info = snd_hda_mixer_amp_switch_info,
+		.get = snd_hda_mixer_amp_switch_get,
+		.put = alc268_acer_master_sw_put,
+		.private_value = HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+	},
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line In Boost", 0x1a, 0, HDA_INPUT),
+	{ }
+};
+
+static struct hda_verb alc268_acer_aspire_one_verbs[] = {
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x23, AC_VERB_SET_CONNECT_SEL, 0x06},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, 0xa017},
+	{ }
+};
+
+static struct hda_verb alc268_acer_verbs[] = {
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* internal dmic? */
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{ }
+};
+
+/* unsolicited event for HP jack sensing */
+#define alc268_toshiba_unsol_event	alc262_hippo_unsol_event
+#define alc268_toshiba_setup		alc262_hippo_setup
+#define alc268_toshiba_automute		alc262_hippo_automute
+
+static void alc268_acer_unsol_event(struct hda_codec *codec,
+				       unsigned int res)
+{
+	if ((res >> 26) != ALC880_HP_EVENT)
+		return;
+	alc268_acer_automute(codec, 1);
+}
+
+static void alc268_acer_init_hook(struct hda_codec *codec)
+{
+	alc268_acer_automute(codec, 1);
+}
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc268_aspire_one_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, 0x15);
+	bits = present ? HDA_AMP_MUTE : 0;
+	snd_hda_codec_amp_stereo(codec, 0x0f, HDA_INPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x0f, HDA_INPUT, 1,
+				 HDA_AMP_MUTE, bits);
+}
+
+static void alc268_acer_lc_unsol_event(struct hda_codec *codec,
+				    unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc268_aspire_one_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+static void alc268_acer_lc_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x12;
+	spec->int_mic.mux_idx = 6;
+	spec->auto_mic = 1;
+}
+
+static void alc268_acer_lc_init_hook(struct hda_codec *codec)
+{
+	alc268_aspire_one_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+
+static struct snd_kcontrol_new alc268_dell_mixer[] = {
+	/* output mixer control */
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Boost", 0x19, 0, HDA_INPUT),
+	{ }
+};
+
+static struct hda_verb alc268_dell_verbs[] = {
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT | AC_USRSP_EN},
+	{ }
+};
+
+/* mute/unmute internal speaker according to the hp jack and mute state */
+static void alc268_dell_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x19;
+	spec->int_mic.mux_idx = 1;
+	spec->auto_mic = 1;
+}
+
+static struct snd_kcontrol_new alc267_quanta_il1_mixer[] = {
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x3, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Capture Volume", 0x23, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Mic Capture Switch", 0x23, 2, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Ext Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT),
+	{ }
+};
+
+static struct hda_verb alc267_quanta_il1_verbs[] = {
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT | AC_USRSP_EN},
+	{ }
+};
+
+static void alc267_quanta_il1_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x19;
+	spec->int_mic.mux_idx = 1;
+	spec->auto_mic = 1;
+}
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc268_base_init_verbs[] = {
+	/* Unmute DAC0-1 and set vol = 0 */
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/*
+	 * Set up output mixers (0x0c - 0x0e)
+	 */
+	/* set vol=0 to output mixers */
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+        {0x0e, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+
+	/* set PCBEEP vol = 0, mute connections */
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+
+	/* Unmute Selector 23h,24h and set the default input to mic-in */
+
+	{0x23, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x24, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	{ }
+};
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc268_volume_init_verbs[] = {
+	/* set output DAC */
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+
+	/* set PCBEEP vol = 0, mute connections */
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+
+	{ }
+};
+
+static struct snd_kcontrol_new alc268_capture_nosrc_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x23, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x23, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc268_capture_alt_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x23, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x23, 0x0, HDA_OUTPUT),
+	_DEFINE_CAPSRC(1),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc268_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x23, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x23, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x24, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x24, 0x0, HDA_OUTPUT),
+	_DEFINE_CAPSRC(2),
+	{ } /* end */
+};
+
+static struct hda_input_mux alc268_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x1 },
+		{ "Line", 0x2 },
+		{ "CD", 0x3 },
+	},
+};
+
+static struct hda_input_mux alc268_acer_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Internal Mic", 0x1 },
+		{ "Line", 0x2 },
+	},
+};
+
+static struct hda_input_mux alc268_acer_dmic_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Internal Mic", 0x6 },
+		{ "Line", 0x2 },
+	},
+};
+
+#ifdef CONFIG_SND_DEBUG
+static struct snd_kcontrol_new alc268_test_mixer[] = {
+	/* Volume widgets */
+	HDA_CODEC_VOLUME("LOUT1 Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("LOUT2 Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Mono sum Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE("LINE-OUT sum Playback Switch", 0x0f, 2, HDA_INPUT),
+	HDA_BIND_MUTE("HP-OUT sum Playback Switch", 0x10, 2, HDA_INPUT),
+	HDA_BIND_MUTE("LINE-OUT Playback Switch", 0x14, 2, HDA_OUTPUT),
+	HDA_BIND_MUTE("HP-OUT Playback Switch", 0x15, 2, HDA_OUTPUT),
+	HDA_BIND_MUTE("Mono Playback Switch", 0x16, 2, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("MIC1 Capture Volume", 0x18, 0x0, HDA_INPUT),
+	HDA_BIND_MUTE("MIC1 Capture Switch", 0x18, 2, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("MIC2 Capture Volume", 0x19, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("LINE1 Capture Volume", 0x1a, 0x0, HDA_INPUT),
+	HDA_BIND_MUTE("LINE1 Capture Switch", 0x1a, 2, HDA_OUTPUT),
+	/* The below appears problematic on some hardwares */
+	/*HDA_CODEC_VOLUME("PCBEEP Playback Volume", 0x1d, 0x0, HDA_INPUT),*/
+	HDA_CODEC_VOLUME("PCM-IN1 Capture Volume", 0x23, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("PCM-IN1 Capture Switch", 0x23, 2, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("PCM-IN2 Capture Volume", 0x24, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("PCM-IN2 Capture Switch", 0x24, 2, HDA_OUTPUT),
+
+	/* Modes for retasking pin widgets */
+	ALC_PIN_MODE("LINE-OUT pin mode", 0x14, ALC_PIN_DIR_INOUT),
+	ALC_PIN_MODE("HP-OUT pin mode", 0x15, ALC_PIN_DIR_INOUT),
+	ALC_PIN_MODE("MIC1 pin mode", 0x18, ALC_PIN_DIR_INOUT),
+	ALC_PIN_MODE("LINE1 pin mode", 0x1a, ALC_PIN_DIR_INOUT),
+
+	/* Controls for GPIO pins, assuming they are configured as outputs */
+	ALC_GPIO_DATA_SWITCH("GPIO pin 0", 0x01, 0x01),
+	ALC_GPIO_DATA_SWITCH("GPIO pin 1", 0x01, 0x02),
+	ALC_GPIO_DATA_SWITCH("GPIO pin 2", 0x01, 0x04),
+	ALC_GPIO_DATA_SWITCH("GPIO pin 3", 0x01, 0x08),
+
+	/* Switches to allow the digital SPDIF output pin to be enabled.
+	 * The ALC268 does not have an SPDIF input.
+	 */
+	ALC_SPDIF_CTRL_SWITCH("SPDIF Playback Switch", 0x06, 0x01),
+
+	/* A switch allowing EAPD to be enabled.  Some laptops seem to use
+	 * this output to turn on an external amplifier.
+	 */
+	ALC_EAPD_CTRL_SWITCH("LINE-OUT EAPD Enable Switch", 0x0f, 0x02),
+	ALC_EAPD_CTRL_SWITCH("HP-OUT EAPD Enable Switch", 0x10, 0x02),
+
+	{ } /* end */
+};
+#endif
+
+/* create input playback/capture controls for the given pin */
+static int alc268_new_analog_output(struct alc_spec *spec, hda_nid_t nid,
+				    const char *ctlname, int idx)
+{
+	hda_nid_t dac;
+	int err;
+
+	switch (nid) {
+	case 0x14:
+	case 0x16:
+		dac = 0x02;
+		break;
+	case 0x15:
+	case 0x1a: /* ALC259/269 only */
+	case 0x1b: /* ALC259/269 only */
+	case 0x21: /* ALC269vb has this pin, too */
+		dac = 0x03;
+		break;
+	default:
+		snd_printd(KERN_WARNING "hda_codec: "
+			   "ignoring pin 0x%x as unknown\n", nid);
+		return 0;
+	}
+	if (spec->multiout.dac_nids[0] != dac &&
+	    spec->multiout.dac_nids[1] != dac) {
+		err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL, ctlname,
+				  HDA_COMPOSE_AMP_VAL(dac, 3, idx,
+						      HDA_OUTPUT));
+		if (err < 0)
+			return err;
+		spec->multiout.dac_nids[spec->multiout.num_dacs++] = dac;
+	}
+
+	if (nid != 0x16)
+		err = add_pb_sw_ctrl(spec, ALC_CTL_WIDGET_MUTE, ctlname,
+			  HDA_COMPOSE_AMP_VAL(nid, 3, idx, HDA_OUTPUT));
+	else /* mono */
+		err = add_pb_sw_ctrl(spec, ALC_CTL_WIDGET_MUTE, ctlname,
+			  HDA_COMPOSE_AMP_VAL(nid, 2, idx, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int alc268_auto_create_multi_out_ctls(struct alc_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	hda_nid_t nid;
+	int err;
+
+	spec->multiout.dac_nids = spec->private_dac_nids;
+
+	nid = cfg->line_out_pins[0];
+	if (nid) {
+		const char *name;
+		if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
+			name = "Speaker";
+		else
+			name = "Front";
+		err = alc268_new_analog_output(spec, nid, name, 0);
+		if (err < 0)
+			return err;
+	}
+
+	nid = cfg->speaker_pins[0];
+	if (nid == 0x1d) {
+		err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL, "Speaker",
+				  HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT));
+		if (err < 0)
+			return err;
+	} else if (nid) {
+		err = alc268_new_analog_output(spec, nid, "Speaker", 0);
+		if (err < 0)
+			return err;
+	}
+	nid = cfg->hp_pins[0];
+	if (nid) {
+		err = alc268_new_analog_output(spec, nid, "Headphone", 0);
+		if (err < 0)
+			return err;
+	}
+
+	nid = cfg->line_out_pins[1] | cfg->line_out_pins[2];
+	if (nid == 0x16) {
+		err = add_pb_sw_ctrl(spec, ALC_CTL_WIDGET_MUTE, "Mono",
+				  HDA_COMPOSE_AMP_VAL(nid, 2, 0, HDA_OUTPUT));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int alc268_auto_create_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	return alc_auto_create_input_ctls(codec, cfg, 0, 0x23, 0x24);
+}
+
+static void alc268_auto_set_output_and_unmute(struct hda_codec *codec,
+					      hda_nid_t nid, int pin_type)
+{
+	int idx;
+
+	alc_set_pin_output(codec, nid, pin_type);
+	if (nid == 0x14 || nid == 0x16)
+		idx = 0;
+	else
+		idx = 1;
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, idx);
+}
+
+static void alc268_auto_init_multi_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->autocfg.line_outs; i++) {
+		hda_nid_t nid = spec->autocfg.line_out_pins[i];
+		int pin_type = get_pin_type(spec->autocfg.line_out_type);
+		alc268_auto_set_output_and_unmute(codec, nid, pin_type);
+	}
+}
+
+static void alc268_auto_init_hp_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t pin;
+	int i;
+
+	for (i = 0; i < spec->autocfg.hp_outs; i++) {
+		pin = spec->autocfg.hp_pins[i];
+		alc268_auto_set_output_and_unmute(codec, pin, PIN_HP);
+	}
+	for (i = 0; i < spec->autocfg.speaker_outs; i++) {
+		pin = spec->autocfg.speaker_pins[i];
+		alc268_auto_set_output_and_unmute(codec, pin, PIN_OUT);
+	}
+	if (spec->autocfg.mono_out_pin)
+		snd_hda_codec_write(codec, spec->autocfg.mono_out_pin, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+}
+
+static void alc268_auto_init_mono_speaker_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t speaker_nid = spec->autocfg.speaker_pins[0];
+	hda_nid_t hp_nid = spec->autocfg.hp_pins[0];
+	hda_nid_t line_nid = spec->autocfg.line_out_pins[0];
+	unsigned int	dac_vol1, dac_vol2;
+
+	if (line_nid == 0x1d || speaker_nid == 0x1d) {
+		snd_hda_codec_write(codec, speaker_nid, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+		/* mute mixer inputs from 0x1d */
+		snd_hda_codec_write(codec, 0x0f, 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE,
+				    AMP_IN_UNMUTE(1));
+		snd_hda_codec_write(codec, 0x10, 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE,
+				    AMP_IN_UNMUTE(1));
+	} else {
+		/* unmute mixer inputs from 0x1d */
+		snd_hda_codec_write(codec, 0x0f, 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1));
+		snd_hda_codec_write(codec, 0x10, 0,
+				    AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1));
+	}
+
+	dac_vol1 = dac_vol2 = 0xb000 | 0x40;	/* set max volume  */
+	if (line_nid == 0x14)
+		dac_vol2 = AMP_OUT_ZERO;
+	else if (line_nid == 0x15)
+		dac_vol1 = AMP_OUT_ZERO;
+	if (hp_nid == 0x14)
+		dac_vol2 = AMP_OUT_ZERO;
+	else if (hp_nid == 0x15)
+		dac_vol1 = AMP_OUT_ZERO;
+	if (line_nid != 0x16 || hp_nid != 0x16 ||
+	    spec->autocfg.line_out_pins[1] != 0x16 ||
+	    spec->autocfg.line_out_pins[2] != 0x16)
+		dac_vol1 = dac_vol2 = AMP_OUT_ZERO;
+
+	snd_hda_codec_write(codec, 0x02, 0,
+			    AC_VERB_SET_AMP_GAIN_MUTE, dac_vol1);
+	snd_hda_codec_write(codec, 0x03, 0,
+			    AC_VERB_SET_AMP_GAIN_MUTE, dac_vol2);
+}
+
+/* pcm configuration: identical with ALC880 */
+#define alc268_pcm_analog_playback	alc880_pcm_analog_playback
+#define alc268_pcm_analog_capture	alc880_pcm_analog_capture
+#define alc268_pcm_analog_alt_capture	alc880_pcm_analog_alt_capture
+#define alc268_pcm_digital_playback	alc880_pcm_digital_playback
+
+/*
+ * BIOS auto configuration
+ */
+static int alc268_parse_auto_config(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int err;
+	static hda_nid_t alc268_ignore[] = { 0 };
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg,
+					   alc268_ignore);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs) {
+		if (spec->autocfg.dig_outs || spec->autocfg.dig_in_pin) {
+			spec->multiout.max_channels = 2;
+			spec->no_analog = 1;
+			goto dig_only;
+		}
+		return 0; /* can't find valid BIOS pin config */
+	}
+	err = alc268_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc268_auto_create_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = 2;
+
+ dig_only:
+	/* digital only support output */
+	alc_auto_parse_digital(codec);
+	if (spec->kctls.list)
+		add_mixer(spec, spec->kctls.list);
+
+	if (!spec->no_analog && spec->autocfg.speaker_pins[0] != 0x1d)
+		add_mixer(spec, alc268_beep_mixer);
+
+	add_verb(spec, alc268_volume_init_verbs);
+	spec->num_mux_defs = 2;
+	spec->input_mux = &spec->private_imux[0];
+
+	err = alc_auto_add_mic_boost(codec);
+	if (err < 0)
+		return err;
+
+	alc_ssid_check(codec, 0x15, 0x1b, 0x14, 0);
+
+	return 1;
+}
+
+#define alc268_auto_init_analog_input	alc882_auto_init_analog_input
+
+/* init callback for auto-configuration model -- overriding the default init */
+static void alc268_auto_init(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc268_auto_init_multi_out(codec);
+	alc268_auto_init_hp_out(codec);
+	alc268_auto_init_mono_speaker_out(codec);
+	alc268_auto_init_analog_input(codec);
+	alc_auto_init_digital(codec);
+	if (spec->unsol_event)
+		alc_inithook(codec);
+}
+
+/*
+ * configuration and preset
+ */
+static const char *alc268_models[ALC268_MODEL_LAST] = {
+	[ALC267_QUANTA_IL1]	= "quanta-il1",
+	[ALC268_3ST]		= "3stack",
+	[ALC268_TOSHIBA]	= "toshiba",
+	[ALC268_ACER]		= "acer",
+	[ALC268_ACER_DMIC]	= "acer-dmic",
+	[ALC268_ACER_ASPIRE_ONE]	= "acer-aspire",
+	[ALC268_DELL]		= "dell",
+	[ALC268_ZEPTO]		= "zepto",
+#ifdef CONFIG_SND_DEBUG
+	[ALC268_TEST]		= "test",
+#endif
+	[ALC268_AUTO]		= "auto",
+};
+
+static struct snd_pci_quirk alc268_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1025, 0x011e, "Acer Aspire 5720z", ALC268_ACER),
+	SND_PCI_QUIRK(0x1025, 0x0126, "Acer", ALC268_ACER),
+	SND_PCI_QUIRK(0x1025, 0x012e, "Acer Aspire 5310", ALC268_ACER),
+	SND_PCI_QUIRK(0x1025, 0x0130, "Acer Extensa 5210", ALC268_ACER),
+	SND_PCI_QUIRK(0x1025, 0x0136, "Acer Aspire 5315", ALC268_ACER),
+	SND_PCI_QUIRK(0x1025, 0x015b, "Acer Aspire One",
+						ALC268_ACER_ASPIRE_ONE),
+	SND_PCI_QUIRK(0x1028, 0x0253, "Dell OEM", ALC268_DELL),
+	SND_PCI_QUIRK_MASK(0x1028, 0xfff0, 0x02b0,
+			"Dell Inspiron Mini9/Vostro A90", ALC268_DELL),
+	/* almost compatible with toshiba but with optional digital outs;
+	 * auto-probing seems working fine
+	 */
+	SND_PCI_QUIRK_MASK(0x103c, 0xff00, 0x3000, "HP TX25xx series",
+			   ALC268_AUTO),
+	SND_PCI_QUIRK(0x1043, 0x1205, "ASUS W7J", ALC268_3ST),
+	SND_PCI_QUIRK(0x1170, 0x0040, "ZEPTO", ALC268_ZEPTO),
+	SND_PCI_QUIRK(0x14c0, 0x0025, "COMPAL IFL90/JFL-92", ALC268_TOSHIBA),
+	SND_PCI_QUIRK(0x152d, 0x0763, "Diverse (CPR2000)", ALC268_ACER),
+	SND_PCI_QUIRK(0x152d, 0x0771, "Quanta IL1", ALC267_QUANTA_IL1),
+	{}
+};
+
+/* Toshiba laptops have no unique PCI SSID but only codec SSID */
+static struct snd_pci_quirk alc268_ssid_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1179, 0xff0a, "TOSHIBA X-200", ALC268_AUTO),
+	SND_PCI_QUIRK(0x1179, 0xff0e, "TOSHIBA X-200 HDMI", ALC268_AUTO),
+	SND_PCI_QUIRK_MASK(0x1179, 0xff00, 0xff00, "TOSHIBA A/Lx05",
+			   ALC268_TOSHIBA),
+	{}
+};
+
+static struct alc_config_preset alc268_presets[] = {
+	[ALC267_QUANTA_IL1] = {
+		.mixers = { alc267_quanta_il1_mixer, alc268_beep_mixer,
+			    alc268_capture_nosrc_mixer },
+		.init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs,
+				alc267_quanta_il1_verbs },
+		.num_dacs = ARRAY_SIZE(alc268_dac_nids),
+		.dac_nids = alc268_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt),
+		.adc_nids = alc268_adc_nids_alt,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc268_modes),
+		.channel_mode = alc268_modes,
+		.unsol_event = alc_sku_unsol_event,
+		.setup = alc267_quanta_il1_setup,
+		.init_hook = alc_inithook,
+	},
+	[ALC268_3ST] = {
+		.mixers = { alc268_base_mixer, alc268_capture_alt_mixer,
+			    alc268_beep_mixer },
+		.init_verbs = { alc268_base_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc268_dac_nids),
+		.dac_nids = alc268_dac_nids,
+                .num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt),
+                .adc_nids = alc268_adc_nids_alt,
+		.capsrc_nids = alc268_capsrc_nids,
+		.hp_nid = 0x03,
+		.dig_out_nid = ALC268_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc268_modes),
+		.channel_mode = alc268_modes,
+		.input_mux = &alc268_capture_source,
+	},
+	[ALC268_TOSHIBA] = {
+		.mixers = { alc268_toshiba_mixer, alc268_capture_alt_mixer,
+			    alc268_beep_mixer },
+		.init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs,
+				alc268_toshiba_verbs },
+		.num_dacs = ARRAY_SIZE(alc268_dac_nids),
+		.dac_nids = alc268_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt),
+		.adc_nids = alc268_adc_nids_alt,
+		.capsrc_nids = alc268_capsrc_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc268_modes),
+		.channel_mode = alc268_modes,
+		.input_mux = &alc268_capture_source,
+		.unsol_event = alc268_toshiba_unsol_event,
+		.setup = alc268_toshiba_setup,
+		.init_hook = alc268_toshiba_automute,
+	},
+	[ALC268_ACER] = {
+		.mixers = { alc268_acer_mixer, alc268_capture_alt_mixer,
+			    alc268_beep_mixer },
+		.init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs,
+				alc268_acer_verbs },
+		.num_dacs = ARRAY_SIZE(alc268_dac_nids),
+		.dac_nids = alc268_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt),
+		.adc_nids = alc268_adc_nids_alt,
+		.capsrc_nids = alc268_capsrc_nids,
+		.hp_nid = 0x02,
+		.num_channel_mode = ARRAY_SIZE(alc268_modes),
+		.channel_mode = alc268_modes,
+		.input_mux = &alc268_acer_capture_source,
+		.unsol_event = alc268_acer_unsol_event,
+		.init_hook = alc268_acer_init_hook,
+	},
+	[ALC268_ACER_DMIC] = {
+		.mixers = { alc268_acer_dmic_mixer, alc268_capture_alt_mixer,
+			    alc268_beep_mixer },
+		.init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs,
+				alc268_acer_verbs },
+		.num_dacs = ARRAY_SIZE(alc268_dac_nids),
+		.dac_nids = alc268_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt),
+		.adc_nids = alc268_adc_nids_alt,
+		.capsrc_nids = alc268_capsrc_nids,
+		.hp_nid = 0x02,
+		.num_channel_mode = ARRAY_SIZE(alc268_modes),
+		.channel_mode = alc268_modes,
+		.input_mux = &alc268_acer_dmic_capture_source,
+		.unsol_event = alc268_acer_unsol_event,
+		.init_hook = alc268_acer_init_hook,
+	},
+	[ALC268_ACER_ASPIRE_ONE] = {
+		.mixers = { alc268_acer_aspire_one_mixer,
+			    alc268_beep_mixer,
+			    alc268_capture_nosrc_mixer },
+		.init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs,
+				alc268_acer_aspire_one_verbs },
+		.num_dacs = ARRAY_SIZE(alc268_dac_nids),
+		.dac_nids = alc268_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt),
+		.adc_nids = alc268_adc_nids_alt,
+		.capsrc_nids = alc268_capsrc_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc268_modes),
+		.channel_mode = alc268_modes,
+		.unsol_event = alc268_acer_lc_unsol_event,
+		.setup = alc268_acer_lc_setup,
+		.init_hook = alc268_acer_lc_init_hook,
+	},
+	[ALC268_DELL] = {
+		.mixers = { alc268_dell_mixer, alc268_beep_mixer,
+			    alc268_capture_nosrc_mixer },
+		.init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs,
+				alc268_dell_verbs },
+		.num_dacs = ARRAY_SIZE(alc268_dac_nids),
+		.dac_nids = alc268_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt),
+		.adc_nids = alc268_adc_nids_alt,
+		.capsrc_nids = alc268_capsrc_nids,
+		.hp_nid = 0x02,
+		.num_channel_mode = ARRAY_SIZE(alc268_modes),
+		.channel_mode = alc268_modes,
+		.unsol_event = alc_sku_unsol_event,
+		.setup = alc268_dell_setup,
+		.init_hook = alc_inithook,
+	},
+	[ALC268_ZEPTO] = {
+		.mixers = { alc268_base_mixer, alc268_capture_alt_mixer,
+			    alc268_beep_mixer },
+		.init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs,
+				alc268_toshiba_verbs },
+		.num_dacs = ARRAY_SIZE(alc268_dac_nids),
+		.dac_nids = alc268_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt),
+		.adc_nids = alc268_adc_nids_alt,
+		.capsrc_nids = alc268_capsrc_nids,
+		.hp_nid = 0x03,
+		.dig_out_nid = ALC268_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc268_modes),
+		.channel_mode = alc268_modes,
+		.input_mux = &alc268_capture_source,
+		.setup = alc268_toshiba_setup,
+		.init_hook = alc268_toshiba_automute,
+	},
+#ifdef CONFIG_SND_DEBUG
+	[ALC268_TEST] = {
+		.mixers = { alc268_test_mixer, alc268_capture_mixer },
+		.init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs,
+				alc268_volume_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc268_dac_nids),
+		.dac_nids = alc268_dac_nids,
+		.num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt),
+		.adc_nids = alc268_adc_nids_alt,
+		.capsrc_nids = alc268_capsrc_nids,
+		.hp_nid = 0x03,
+		.dig_out_nid = ALC268_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc268_modes),
+		.channel_mode = alc268_modes,
+		.input_mux = &alc268_capture_source,
+	},
+#endif
+};
+
+static int patch_alc268(struct hda_codec *codec)
+{
+	struct alc_spec *spec;
+	int board_config;
+	int i, has_beep, err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	board_config = snd_hda_check_board_config(codec, ALC268_MODEL_LAST,
+						  alc268_models,
+						  alc268_cfg_tbl);
+
+	if (board_config < 0 || board_config >= ALC268_MODEL_LAST)
+		board_config = snd_hda_check_board_codec_sid_config(codec,
+			ALC268_MODEL_LAST, alc268_models, alc268_ssid_cfg_tbl);
+
+	if (board_config < 0 || board_config >= ALC268_MODEL_LAST) {
+		printk(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+		       codec->chip_name);
+		board_config = ALC268_AUTO;
+	}
+
+	if (board_config == ALC268_AUTO) {
+		/* automatic parse from the BIOS config */
+		err = alc268_parse_auto_config(codec);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		} else if (!err) {
+			printk(KERN_INFO
+			       "hda_codec: Cannot set up configuration "
+			       "from BIOS.  Using base mode...\n");
+			board_config = ALC268_3ST;
+		}
+	}
+
+	if (board_config != ALC268_AUTO)
+		setup_preset(codec, &alc268_presets[board_config]);
+
+	spec->stream_analog_playback = &alc268_pcm_analog_playback;
+	spec->stream_analog_capture = &alc268_pcm_analog_capture;
+	spec->stream_analog_alt_capture = &alc268_pcm_analog_alt_capture;
+
+	spec->stream_digital_playback = &alc268_pcm_digital_playback;
+
+	has_beep = 0;
+	for (i = 0; i < spec->num_mixers; i++) {
+		if (spec->mixers[i] == alc268_beep_mixer) {
+			has_beep = 1;
+			break;
+		}
+	}
+
+	if (has_beep) {
+		err = snd_hda_attach_beep_device(codec, 0x1);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		}
+		if (!query_amp_caps(codec, 0x1d, HDA_INPUT))
+			/* override the amp caps for beep generator */
+			snd_hda_override_amp_caps(codec, 0x1d, HDA_INPUT,
+					  (0x0c << AC_AMPCAP_OFFSET_SHIFT) |
+					  (0x0c << AC_AMPCAP_NUM_STEPS_SHIFT) |
+					  (0x07 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+					  (0 << AC_AMPCAP_MUTE_SHIFT));
+	}
+
+	if (!spec->no_analog && !spec->adc_nids && spec->input_mux) {
+		/* check whether NID 0x07 is valid */
+		unsigned int wcap = get_wcaps(codec, 0x07);
+		int i;
+
+		spec->capsrc_nids = alc268_capsrc_nids;
+		/* get type */
+		wcap = get_wcaps_type(wcap);
+		if (spec->auto_mic ||
+		    wcap != AC_WID_AUD_IN || spec->input_mux->num_items == 1) {
+			spec->adc_nids = alc268_adc_nids_alt;
+			spec->num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt);
+			if (spec->auto_mic)
+				fixup_automic_adc(codec);
+			if (spec->auto_mic || spec->input_mux->num_items == 1)
+				add_mixer(spec, alc268_capture_nosrc_mixer);
+			else
+				add_mixer(spec, alc268_capture_alt_mixer);
+		} else {
+			spec->adc_nids = alc268_adc_nids;
+			spec->num_adc_nids = ARRAY_SIZE(alc268_adc_nids);
+			add_mixer(spec, alc268_capture_mixer);
+		}
+		/* set default input source */
+		for (i = 0; i < spec->num_adc_nids; i++)
+			snd_hda_codec_write_cache(codec, alc268_capsrc_nids[i],
+				0, AC_VERB_SET_CONNECT_SEL,
+				i < spec->num_mux_defs ?
+				spec->input_mux[i].items[0].index :
+				spec->input_mux->items[0].index);
+	}
+
+	spec->vmaster_nid = 0x02;
+
+	codec->patch_ops = alc_patch_ops;
+	if (board_config == ALC268_AUTO)
+		spec->init_hook = alc268_auto_init;
+
+	alc_init_jacks(codec);
+
+	return 0;
+}
+
+/*
+ *  ALC269 channel source setting (2 channel)
+ */
+#define ALC269_DIGOUT_NID	ALC880_DIGOUT_NID
+
+#define alc269_dac_nids		alc260_dac_nids
+
+static hda_nid_t alc269_adc_nids[1] = {
+	/* ADC1 */
+	0x08,
+};
+
+static hda_nid_t alc269_capsrc_nids[1] = {
+	0x23,
+};
+
+static hda_nid_t alc269vb_adc_nids[1] = {
+	/* ADC1 */
+	0x09,
+};
+
+static hda_nid_t alc269vb_capsrc_nids[1] = {
+	0x22,
+};
+
+static hda_nid_t alc269_adc_candidates[] = {
+	0x08, 0x09, 0x07,
+};
+
+#define alc269_modes		alc260_modes
+#define alc269_capture_source	alc880_lg_lw_capture_source
+
+static struct snd_kcontrol_new alc269_base_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x16, 2, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc269_quanta_fl1_mixer[] = {
+	/* output mixer control */
+	HDA_BIND_VOL("Master Playback Volume", &alc268_acer_bind_master_vol),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.info = snd_hda_mixer_amp_switch_info,
+		.get = snd_hda_mixer_amp_switch_get,
+		.put = alc268_acer_master_sw_put,
+		.private_value = HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+	},
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Boost", 0x19, 0, HDA_INPUT),
+	{ }
+};
+
+static struct snd_kcontrol_new alc269_lifebook_mixer[] = {
+	/* output mixer control */
+	HDA_BIND_VOL("Master Playback Volume", &alc268_acer_bind_master_vol),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
+		.info = snd_hda_mixer_amp_switch_info,
+		.get = snd_hda_mixer_amp_switch_get,
+		.put = alc268_acer_master_sw_put,
+		.private_value = HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+	},
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x0b, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Internal Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Dock Mic Playback Volume", 0x0b, 0x03, HDA_INPUT),
+	HDA_CODEC_MUTE("Dock Mic Playback Switch", 0x0b, 0x03, HDA_INPUT),
+	HDA_CODEC_VOLUME("Dock Mic Boost", 0x1b, 0, HDA_INPUT),
+	{ }
+};
+
+static struct snd_kcontrol_new alc269_laptop_mixer[] = {
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc269vb_laptop_mixer[] = {
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc269_asus_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Master Playback Switch", 0x0c, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+/* capture mixer elements */
+static struct snd_kcontrol_new alc269_laptop_analog_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("IntMic Boost", 0x19, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc269_laptop_digital_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc269vb_laptop_analog_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x09, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x09, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("IntMic Boost", 0x19, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc269vb_laptop_digital_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x09, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x09, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+/* FSC amilo */
+#define alc269_fujitsu_mixer	alc269_laptop_mixer
+
+static struct hda_verb alc269_quanta_fl1_verbs[] = {
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{ }
+};
+
+static struct hda_verb alc269_lifebook_verbs[] = {
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{ }
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc269_quanta_fl1_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, 0x15);
+	bits = present ? HDA_AMP_MUTE : 0;
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1,
+				 HDA_AMP_MUTE, bits);
+
+	snd_hda_codec_write(codec, 0x20, 0,
+			AC_VERB_SET_COEF_INDEX, 0x0c);
+	snd_hda_codec_write(codec, 0x20, 0,
+			AC_VERB_SET_PROC_COEF, 0x680);
+
+	snd_hda_codec_write(codec, 0x20, 0,
+			AC_VERB_SET_COEF_INDEX, 0x0c);
+	snd_hda_codec_write(codec, 0x20, 0,
+			AC_VERB_SET_PROC_COEF, 0x480);
+}
+
+/* toggle speaker-output according to the hp-jacks state */
+static void alc269_lifebook_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+	/* Check laptop headphone socket */
+	present = snd_hda_jack_detect(codec, 0x15);
+
+	/* Check port replicator headphone socket */
+	present |= snd_hda_jack_detect(codec, 0x1a);
+
+	bits = present ? HDA_AMP_MUTE : 0;
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1,
+				 HDA_AMP_MUTE, bits);
+
+	snd_hda_codec_write(codec, 0x20, 0,
+			AC_VERB_SET_COEF_INDEX, 0x0c);
+	snd_hda_codec_write(codec, 0x20, 0,
+			AC_VERB_SET_PROC_COEF, 0x680);
+
+	snd_hda_codec_write(codec, 0x20, 0,
+			AC_VERB_SET_COEF_INDEX, 0x0c);
+	snd_hda_codec_write(codec, 0x20, 0,
+			AC_VERB_SET_PROC_COEF, 0x480);
+}
+
+static void alc269_lifebook_mic_autoswitch(struct hda_codec *codec)
+{
+	unsigned int present_laptop;
+	unsigned int present_dock;
+
+	present_laptop	= snd_hda_jack_detect(codec, 0x18);
+	present_dock	= snd_hda_jack_detect(codec, 0x1b);
+
+	/* Laptop mic port overrides dock mic port, design decision */
+	if (present_dock)
+		snd_hda_codec_write(codec, 0x23, 0,
+				AC_VERB_SET_CONNECT_SEL, 0x3);
+	if (present_laptop)
+		snd_hda_codec_write(codec, 0x23, 0,
+				AC_VERB_SET_CONNECT_SEL, 0x0);
+	if (!present_dock && !present_laptop)
+		snd_hda_codec_write(codec, 0x23, 0,
+				AC_VERB_SET_CONNECT_SEL, 0x1);
+}
+
+static void alc269_quanta_fl1_unsol_event(struct hda_codec *codec,
+				    unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc269_quanta_fl1_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+static void alc269_lifebook_unsol_event(struct hda_codec *codec,
+					unsigned int res)
+{
+	if ((res >> 26) == ALC880_HP_EVENT)
+		alc269_lifebook_speaker_automute(codec);
+	if ((res >> 26) == ALC880_MIC_EVENT)
+		alc269_lifebook_mic_autoswitch(codec);
+}
+
+static void alc269_quanta_fl1_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x19;
+	spec->int_mic.mux_idx = 1;
+	spec->auto_mic = 1;
+}
+
+static void alc269_quanta_fl1_init_hook(struct hda_codec *codec)
+{
+	alc269_quanta_fl1_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+
+static void alc269_lifebook_init_hook(struct hda_codec *codec)
+{
+	alc269_lifebook_speaker_automute(codec);
+	alc269_lifebook_mic_autoswitch(codec);
+}
+
+static struct hda_verb alc269_laptop_dmic_init_verbs[] = {
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x23, AC_VERB_SET_CONNECT_SEL, 0x05},
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, 0xb026 },
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x7019 | (0x00 << 8))},
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc269_laptop_amic_init_verbs[] = {
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x23, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, 0xb026 },
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x701b | (0x00 << 8))},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc269vb_laptop_dmic_init_verbs[] = {
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x22, AC_VERB_SET_CONNECT_SEL, 0x06},
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, 0xb026 },
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, (0x7019 | (0x00 << 8))},
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc269vb_laptop_amic_init_verbs[] = {
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x22, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, 0xb026 },
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, (0x7019 | (0x00 << 8))},
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc271_acer_dmic_verbs[] = {
+	{0x20, AC_VERB_SET_COEF_INDEX, 0x0d},
+	{0x20, AC_VERB_SET_PROC_COEF, 0x4000},
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x22, AC_VERB_SET_CONNECT_SEL, 6},
+	{ }
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc269_speaker_automute(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	unsigned int nid = spec->autocfg.hp_pins[0];
+	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, nid);
+	bits = present ? HDA_AMP_MUTE : 0;
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1,
+				 HDA_AMP_MUTE, bits);
+	alc_report_jack(codec, nid);
+}
+
+/* unsolicited event for HP jack sensing */
+static void alc269_laptop_unsol_event(struct hda_codec *codec,
+				     unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc269_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+static void alc269_laptop_amic_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x19;
+	spec->int_mic.mux_idx = 1;
+	spec->auto_mic = 1;
+}
+
+static void alc269_laptop_dmic_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x12;
+	spec->int_mic.mux_idx = 5;
+	spec->auto_mic = 1;
+}
+
+static void alc269vb_laptop_amic_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->autocfg.hp_pins[0] = 0x21;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x19;
+	spec->int_mic.mux_idx = 1;
+	spec->auto_mic = 1;
+}
+
+static void alc269vb_laptop_dmic_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->autocfg.hp_pins[0] = 0x21;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x12;
+	spec->int_mic.mux_idx = 6;
+	spec->auto_mic = 1;
+}
+
+static void alc269_laptop_inithook(struct hda_codec *codec)
+{
+	alc269_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc269_init_verbs[] = {
+	/*
+	 * Unmute ADC0 and set the default input to mic-in
+	 */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/*
+	 * Set up output mixers (0x02 - 0x03)
+	 */
+	/* set vol=0 to output mixers */
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* set up input amps for analog loopback */
+	/* Amp Indices: DAC = 0, mixer = 1 */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* FIXME: use Mux-type input source selection */
+	/* Mixer elements: 0x18, 19, 1a, 1b, 1d, 0b */
+	/* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */
+	{0x23, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* set EAPD */
+	{0x14, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{ }
+};
+
+static struct hda_verb alc269vb_init_verbs[] = {
+	/*
+	 * Unmute ADC0 and set the default input to mic-in
+	 */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/*
+	 * Set up output mixers (0x02 - 0x03)
+	 */
+	/* set vol=0 to output mixers */
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* set up input amps for analog loopback */
+	/* Amp Indices: DAC = 0, mixer = 1 */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* FIXME: use Mux-type input source selection */
+	/* Mixer elements: 0x18, 19, 1a, 1b, 1d, 0b */
+	/* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */
+	{0x22, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* set EAPD */
+	{0x14, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{ }
+};
+
+#define alc269_auto_create_multi_out_ctls \
+	alc268_auto_create_multi_out_ctls
+#define alc269_auto_create_input_ctls \
+	alc268_auto_create_input_ctls
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+#define alc269_loopbacks	alc880_loopbacks
+#endif
+
+/* pcm configuration: identical with ALC880 */
+#define alc269_pcm_analog_playback	alc880_pcm_analog_playback
+#define alc269_pcm_analog_capture	alc880_pcm_analog_capture
+#define alc269_pcm_digital_playback	alc880_pcm_digital_playback
+#define alc269_pcm_digital_capture	alc880_pcm_digital_capture
+
+static struct hda_pcm_stream alc269_44k_pcm_analog_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 8,
+	.rates = SNDRV_PCM_RATE_44100, /* fixed rate */
+	/* NID is set in alc_build_pcms */
+	.ops = {
+		.open = alc880_playback_pcm_open,
+		.prepare = alc880_playback_pcm_prepare,
+		.cleanup = alc880_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream alc269_44k_pcm_analog_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.rates = SNDRV_PCM_RATE_44100, /* fixed rate */
+	/* NID is set in alc_build_pcms */
+};
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static int alc269_mic2_for_mute_led(struct hda_codec *codec)
+{
+	switch (codec->subsystem_id) {
+	case 0x103c1586:
+		return 1;
+	}
+	return 0;
+}
+
+static int alc269_mic2_mute_check_ps(struct hda_codec *codec, hda_nid_t nid)
+{
+	/* update mute-LED according to the speaker mute state */
+	if (nid == 0x01 || nid == 0x14) {
+		int pinval;
+		if (snd_hda_codec_amp_read(codec, 0x14, 0, HDA_OUTPUT, 0) &
+		    HDA_AMP_MUTE)
+			pinval = 0x24;
+		else
+			pinval = 0x20;
+		/* mic2 vref pin is used for mute LED control */
+		snd_hda_codec_update_cache(codec, 0x19, 0,
+					   AC_VERB_SET_PIN_WIDGET_CONTROL,
+					   pinval);
+	}
+	return alc_check_power_status(codec, nid);
+}
+#endif /* CONFIG_SND_HDA_POWER_SAVE */
+
+static int alc275_setup_dual_adc(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	if (codec->vendor_id != 0x10ec0275 || !spec->auto_mic)
+		return 0;
+	if ((spec->ext_mic.pin >= 0x18 && spec->int_mic.pin <= 0x13) ||
+	    (spec->ext_mic.pin <= 0x12 && spec->int_mic.pin >= 0x18)) {
+		if (spec->ext_mic.pin <= 0x12) {
+			spec->private_adc_nids[0] = 0x08;
+			spec->private_adc_nids[1] = 0x11;
+			spec->private_capsrc_nids[0] = 0x23;
+			spec->private_capsrc_nids[1] = 0x22;
+		} else {
+			spec->private_adc_nids[0] = 0x11;
+			spec->private_adc_nids[1] = 0x08;
+			spec->private_capsrc_nids[0] = 0x22;
+			spec->private_capsrc_nids[1] = 0x23;
+		}
+		spec->adc_nids = spec->private_adc_nids;
+		spec->capsrc_nids = spec->private_capsrc_nids;
+		spec->num_adc_nids = 2;
+		spec->dual_adc_switch = 1;
+		snd_printdd("realtek: enabling dual ADC switchg (%02x:%02x)\n",
+			    spec->adc_nids[0], spec->adc_nids[1]);
+		return 1;
+	}
+	return 0;
+}
+
+/* different alc269-variants */
+enum {
+	ALC269_TYPE_NORMAL,
+	ALC269_TYPE_ALC258,
+	ALC269_TYPE_ALC259,
+	ALC269_TYPE_ALC269VB,
+	ALC269_TYPE_ALC270,
+	ALC269_TYPE_ALC271X,
+};
+
+/*
+ * BIOS auto configuration
+ */
+static int alc269_parse_auto_config(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int err;
+	static hda_nid_t alc269_ignore[] = { 0x1d, 0 };
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg,
+					   alc269_ignore);
+	if (err < 0)
+		return err;
+
+	err = alc269_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	if (spec->codec_variant == ALC269_TYPE_NORMAL)
+		err = alc269_auto_create_input_ctls(codec, &spec->autocfg);
+	else
+		err = alc_auto_create_input_ctls(codec, &spec->autocfg, 0,
+						 0x22, 0);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	alc_auto_parse_digital(codec);
+
+	if (spec->kctls.list)
+		add_mixer(spec, spec->kctls.list);
+
+	if (spec->codec_variant != ALC269_TYPE_NORMAL) {
+		add_verb(spec, alc269vb_init_verbs);
+		alc_ssid_check(codec, 0, 0x1b, 0x14, 0x21);
+	} else {
+		add_verb(spec, alc269_init_verbs);
+		alc_ssid_check(codec, 0x15, 0x1b, 0x14, 0);
+	}
+
+	spec->num_mux_defs = 1;
+	spec->input_mux = &spec->private_imux[0];
+
+	if (!alc275_setup_dual_adc(codec))
+		fillup_priv_adc_nids(codec, alc269_adc_candidates,
+				     sizeof(alc269_adc_candidates));
+
+	/* set default input source */
+	if (!spec->dual_adc_switch)
+		select_or_unmute_capsrc(codec, spec->capsrc_nids[0],
+					spec->input_mux->items[0].index);
+
+	err = alc_auto_add_mic_boost(codec);
+	if (err < 0)
+		return err;
+
+	if (!spec->cap_mixer && !spec->no_analog)
+		set_capture_mixer(codec);
+
+	return 1;
+}
+
+#define alc269_auto_init_multi_out	alc268_auto_init_multi_out
+#define alc269_auto_init_hp_out		alc268_auto_init_hp_out
+#define alc269_auto_init_analog_input	alc882_auto_init_analog_input
+
+
+/* init callback for auto-configuration model -- overriding the default init */
+static void alc269_auto_init(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc269_auto_init_multi_out(codec);
+	alc269_auto_init_hp_out(codec);
+	alc269_auto_init_analog_input(codec);
+	alc_auto_init_digital(codec);
+	if (spec->unsol_event)
+		alc_inithook(codec);
+}
+
+#ifdef SND_HDA_NEEDS_RESUME
+static void alc269_toggle_power_output(struct hda_codec *codec, int power_up)
+{
+	int val = alc_read_coef_idx(codec, 0x04);
+	if (power_up)
+		val |= 1 << 11;
+	else
+		val &= ~(1 << 11);
+	alc_write_coef_idx(codec, 0x04, val);
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static int alc269_suspend(struct hda_codec *codec, pm_message_t state)
+{
+	struct alc_spec *spec = codec->spec;
+
+	if ((alc_read_coef_idx(codec, 0) & 0x00ff) == 0x017)
+		alc269_toggle_power_output(codec, 0);
+	if ((alc_read_coef_idx(codec, 0) & 0x00ff) == 0x018) {
+		alc269_toggle_power_output(codec, 0);
+		msleep(150);
+	}
+
+	alc_shutup(codec);
+	if (spec && spec->power_hook)
+		spec->power_hook(codec);
+	return 0;
+}
+#endif /* CONFIG_SND_HDA_POWER_SAVE */
+
+static int alc269_resume(struct hda_codec *codec)
+{
+	if ((alc_read_coef_idx(codec, 0) & 0x00ff) == 0x018) {
+		alc269_toggle_power_output(codec, 0);
+		msleep(150);
+	}
+
+	codec->patch_ops.init(codec);
+
+	if ((alc_read_coef_idx(codec, 0) & 0x00ff) == 0x017) {
+		alc269_toggle_power_output(codec, 1);
+		msleep(200);
+	}
+
+	if ((alc_read_coef_idx(codec, 0) & 0x00ff) == 0x018)
+		alc269_toggle_power_output(codec, 1);
+
+	snd_hda_codec_resume_amp(codec);
+	snd_hda_codec_resume_cache(codec);
+	hda_call_check_power_status(codec, 0x01);
+	return 0;
+}
+#endif /* SND_HDA_NEEDS_RESUME */
+
+enum {
+	ALC269_FIXUP_SONY_VAIO,
+	ALC275_FIX_SONY_VAIO_GPIO2,
+	ALC269_FIXUP_DELL_M101Z,
+	ALC269_FIXUP_SKU_IGNORE,
+	ALC269_FIXUP_ASUS_G73JW,
+};
+
+static const struct alc_fixup alc269_fixups[] = {
+	[ALC269_FIXUP_SONY_VAIO] = {
+		.verbs = (const struct hda_verb[]) {
+			{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREFGRD},
+			{}
+		}
+	},
+	[ALC275_FIX_SONY_VAIO_GPIO2] = {
+		.verbs = (const struct hda_verb[]) {
+			{0x01, AC_VERB_SET_GPIO_MASK, 0x04},
+			{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x04},
+			{0x01, AC_VERB_SET_GPIO_DATA, 0x00},
+			{ }
+		}
+	},
+	[ALC269_FIXUP_DELL_M101Z] = {
+		.verbs = (const struct hda_verb[]) {
+			/* Enables internal speaker */
+			{0x20, AC_VERB_SET_COEF_INDEX, 13},
+			{0x20, AC_VERB_SET_PROC_COEF, 0x4040},
+			{}
+		}
+	},
+	[ALC269_FIXUP_SKU_IGNORE] = {
+		.sku = ALC_FIXUP_SKU_IGNORE,
+	},
+	[ALC269_FIXUP_ASUS_G73JW] = {
+		.pins = (const struct alc_pincfg[]) {
+			{ 0x17, 0x99130111 }, /* subwoofer */
+			{ }
+		}
+	},
+};
+
+static struct snd_pci_quirk alc269_fixup_tbl[] = {
+	SND_PCI_QUIRK(0x104d, 0x9073, "Sony VAIO", ALC275_FIX_SONY_VAIO_GPIO2),
+	SND_PCI_QUIRK(0x104d, 0x907b, "Sony VAIO", ALC275_FIX_SONY_VAIO_GPIO2),
+	SND_PCI_QUIRK(0x104d, 0x9084, "Sony VAIO", ALC275_FIX_SONY_VAIO_GPIO2),
+	SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO),
+	SND_PCI_QUIRK(0x1028, 0x0470, "Dell M101z", ALC269_FIXUP_DELL_M101Z),
+	SND_PCI_QUIRK(0x17aa, 0x21b8, "Thinkpad Edge 14", ALC269_FIXUP_SKU_IGNORE),
+	SND_PCI_QUIRK(0x17aa, 0x20f2, "Thinkpad SL410/510", ALC269_FIXUP_SKU_IGNORE),
+	SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW),
+	{}
+};
+
+
+/*
+ * configuration and preset
+ */
+static const char *alc269_models[ALC269_MODEL_LAST] = {
+	[ALC269_BASIC]			= "basic",
+	[ALC269_QUANTA_FL1]		= "quanta",
+	[ALC269_AMIC]			= "laptop-amic",
+	[ALC269_DMIC]			= "laptop-dmic",
+	[ALC269_FUJITSU]		= "fujitsu",
+	[ALC269_LIFEBOOK]		= "lifebook",
+	[ALC269_AUTO]			= "auto",
+};
+
+static struct snd_pci_quirk alc269_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_QUANTA_FL1),
+	SND_PCI_QUIRK(0x1025, 0x047c, "ACER ZGA", ALC271_ACER),
+	SND_PCI_QUIRK(0x1043, 0x8330, "ASUS Eeepc P703 P900A",
+		      ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1013, "ASUS N61Da", ALC269VB_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1113, "ASUS N63Jn", ALC269VB_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1143, "ASUS B53f", ALC269VB_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1133, "ASUS UJ20ft", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1183, "ASUS K72DR", ALC269VB_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x11b3, "ASUS K52DR", ALC269VB_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x11e3, "ASUS U33Jc", ALC269VB_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1273, "ASUS UL80Jt", ALC269VB_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1283, "ASUS U53Jc", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x12b3, "ASUS N82Jv", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x12d3, "ASUS N61Jv", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x13a3, "ASUS UL30Vt", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1373, "ASUS G73JX", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1383, "ASUS UJ30Jc", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x13d3, "ASUS N61JA", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1413, "ASUS UL50", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1443, "ASUS UL30", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1453, "ASUS M60Jv", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1483, "ASUS UL80", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x14f3, "ASUS F83Vf", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x14e3, "ASUS UL20", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1513, "ASUS UX30", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1593, "ASUS N51Vn", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x15a3, "ASUS N60Jv", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x15b3, "ASUS N60Dp", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x15c3, "ASUS N70De", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x15e3, "ASUS F83T", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1643, "ASUS M60J", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1653, "ASUS U50", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1693, "ASUS F50N", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS F5Q", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_DMIC),
+	SND_PCI_QUIRK(0x1043, 0x1723, "ASUS P80", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1743, "ASUS U80", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1773, "ASUS U20A", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x1883, "ASUS F81Se", ALC269_AMIC),
+	SND_PCI_QUIRK(0x1043, 0x831a, "ASUS Eeepc P901",
+		      ALC269_DMIC),
+	SND_PCI_QUIRK(0x1043, 0x834a, "ASUS Eeepc S101",
+		      ALC269_DMIC),
+	SND_PCI_QUIRK(0x1043, 0x8398, "ASUS P1005HA", ALC269_DMIC),
+	SND_PCI_QUIRK(0x1043, 0x83ce, "ASUS P1005HA", ALC269_DMIC),
+	SND_PCI_QUIRK(0x104d, 0x9071, "Sony VAIO", ALC269_AUTO),
+	SND_PCI_QUIRK(0x10cf, 0x1475, "Lifebook ICH9M-based", ALC269_LIFEBOOK),
+	SND_PCI_QUIRK(0x152d, 0x1778, "Quanta ON1", ALC269_DMIC),
+	SND_PCI_QUIRK(0x1734, 0x115d, "FSC Amilo", ALC269_FUJITSU),
+	SND_PCI_QUIRK(0x17aa, 0x3be9, "Quanta Wistron", ALC269_AMIC),
+	SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_AMIC),
+	SND_PCI_QUIRK(0x17ff, 0x059a, "Quanta EL3", ALC269_DMIC),
+	SND_PCI_QUIRK(0x17ff, 0x059b, "Quanta JR1", ALC269_DMIC),
+	{}
+};
+
+static struct alc_config_preset alc269_presets[] = {
+	[ALC269_BASIC] = {
+		.mixers = { alc269_base_mixer },
+		.init_verbs = { alc269_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc269_dac_nids),
+		.dac_nids = alc269_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc269_modes),
+		.channel_mode = alc269_modes,
+		.input_mux = &alc269_capture_source,
+	},
+	[ALC269_QUANTA_FL1] = {
+		.mixers = { alc269_quanta_fl1_mixer },
+		.init_verbs = { alc269_init_verbs, alc269_quanta_fl1_verbs },
+		.num_dacs = ARRAY_SIZE(alc269_dac_nids),
+		.dac_nids = alc269_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc269_modes),
+		.channel_mode = alc269_modes,
+		.input_mux = &alc269_capture_source,
+		.unsol_event = alc269_quanta_fl1_unsol_event,
+		.setup = alc269_quanta_fl1_setup,
+		.init_hook = alc269_quanta_fl1_init_hook,
+	},
+	[ALC269_AMIC] = {
+		.mixers = { alc269_laptop_mixer },
+		.cap_mixer = alc269_laptop_analog_capture_mixer,
+		.init_verbs = { alc269_init_verbs,
+				alc269_laptop_amic_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc269_dac_nids),
+		.dac_nids = alc269_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc269_modes),
+		.channel_mode = alc269_modes,
+		.unsol_event = alc269_laptop_unsol_event,
+		.setup = alc269_laptop_amic_setup,
+		.init_hook = alc269_laptop_inithook,
+	},
+	[ALC269_DMIC] = {
+		.mixers = { alc269_laptop_mixer },
+		.cap_mixer = alc269_laptop_digital_capture_mixer,
+		.init_verbs = { alc269_init_verbs,
+				alc269_laptop_dmic_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc269_dac_nids),
+		.dac_nids = alc269_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc269_modes),
+		.channel_mode = alc269_modes,
+		.unsol_event = alc269_laptop_unsol_event,
+		.setup = alc269_laptop_dmic_setup,
+		.init_hook = alc269_laptop_inithook,
+	},
+	[ALC269VB_AMIC] = {
+		.mixers = { alc269vb_laptop_mixer },
+		.cap_mixer = alc269vb_laptop_analog_capture_mixer,
+		.init_verbs = { alc269vb_init_verbs,
+				alc269vb_laptop_amic_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc269_dac_nids),
+		.dac_nids = alc269_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc269_modes),
+		.channel_mode = alc269_modes,
+		.unsol_event = alc269_laptop_unsol_event,
+		.setup = alc269vb_laptop_amic_setup,
+		.init_hook = alc269_laptop_inithook,
+	},
+	[ALC269VB_DMIC] = {
+		.mixers = { alc269vb_laptop_mixer },
+		.cap_mixer = alc269vb_laptop_digital_capture_mixer,
+		.init_verbs = { alc269vb_init_verbs,
+				alc269vb_laptop_dmic_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc269_dac_nids),
+		.dac_nids = alc269_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc269_modes),
+		.channel_mode = alc269_modes,
+		.unsol_event = alc269_laptop_unsol_event,
+		.setup = alc269vb_laptop_dmic_setup,
+		.init_hook = alc269_laptop_inithook,
+	},
+	[ALC269_FUJITSU] = {
+		.mixers = { alc269_fujitsu_mixer },
+		.cap_mixer = alc269_laptop_digital_capture_mixer,
+		.init_verbs = { alc269_init_verbs,
+				alc269_laptop_dmic_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc269_dac_nids),
+		.dac_nids = alc269_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc269_modes),
+		.channel_mode = alc269_modes,
+		.unsol_event = alc269_laptop_unsol_event,
+		.setup = alc269_laptop_dmic_setup,
+		.init_hook = alc269_laptop_inithook,
+	},
+	[ALC269_LIFEBOOK] = {
+		.mixers = { alc269_lifebook_mixer },
+		.init_verbs = { alc269_init_verbs, alc269_lifebook_verbs },
+		.num_dacs = ARRAY_SIZE(alc269_dac_nids),
+		.dac_nids = alc269_dac_nids,
+		.hp_nid = 0x03,
+		.num_channel_mode = ARRAY_SIZE(alc269_modes),
+		.channel_mode = alc269_modes,
+		.input_mux = &alc269_capture_source,
+		.unsol_event = alc269_lifebook_unsol_event,
+		.init_hook = alc269_lifebook_init_hook,
+	},
+	[ALC271_ACER] = {
+		.mixers = { alc269_asus_mixer },
+		.cap_mixer = alc269vb_laptop_digital_capture_mixer,
+		.init_verbs = { alc269_init_verbs, alc271_acer_dmic_verbs },
+		.num_dacs = ARRAY_SIZE(alc269_dac_nids),
+		.dac_nids = alc269_dac_nids,
+		.adc_nids = alc262_dmic_adc_nids,
+		.num_adc_nids = ARRAY_SIZE(alc262_dmic_adc_nids),
+		.capsrc_nids = alc262_dmic_capsrc_nids,
+		.num_channel_mode = ARRAY_SIZE(alc269_modes),
+		.channel_mode = alc269_modes,
+		.input_mux = &alc269_capture_source,
+		.dig_out_nid = ALC880_DIGOUT_NID,
+		.unsol_event = alc_sku_unsol_event,
+		.setup = alc269vb_laptop_dmic_setup,
+		.init_hook = alc_inithook,
+	},
+};
+
+static int alc269_fill_coef(struct hda_codec *codec)
+{
+	int val;
+
+	if ((alc_read_coef_idx(codec, 0) & 0x00ff) < 0x015) {
+		alc_write_coef_idx(codec, 0xf, 0x960b);
+		alc_write_coef_idx(codec, 0xe, 0x8817);
+	}
+
+	if ((alc_read_coef_idx(codec, 0) & 0x00ff) == 0x016) {
+		alc_write_coef_idx(codec, 0xf, 0x960b);
+		alc_write_coef_idx(codec, 0xe, 0x8814);
+	}
+
+	if ((alc_read_coef_idx(codec, 0) & 0x00ff) == 0x017) {
+		val = alc_read_coef_idx(codec, 0x04);
+		/* Power up output pin */
+		alc_write_coef_idx(codec, 0x04, val | (1<<11));
+	}
+
+	if ((alc_read_coef_idx(codec, 0) & 0x00ff) == 0x018) {
+		val = alc_read_coef_idx(codec, 0xd);
+		if ((val & 0x0c00) >> 10 != 0x1) {
+			/* Capless ramp up clock control */
+			alc_write_coef_idx(codec, 0xd, val | 1<<10);
+		}
+		val = alc_read_coef_idx(codec, 0x17);
+		if ((val & 0x01c0) >> 6 != 0x4) {
+			/* Class D power on reset */
+			alc_write_coef_idx(codec, 0x17, val | 1<<7);
+		}
+	}
+	return 0;
+}
+
+static int patch_alc269(struct hda_codec *codec)
+{
+	struct alc_spec *spec;
+	int board_config, coef;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	alc_auto_parse_customize_define(codec);
+
+	if (codec->vendor_id == 0x10ec0269) {
+		coef = alc_read_coef_idx(codec, 0);
+		if ((coef & 0x00f0) == 0x0010) {
+			if (codec->bus->pci->subsystem_vendor == 0x1025 &&
+			    spec->cdefine.platform_type == 1) {
+				alc_codec_rename(codec, "ALC271X");
+				spec->codec_variant = ALC269_TYPE_ALC271X;
+			} else if ((coef & 0xf000) == 0x1000) {
+				spec->codec_variant = ALC269_TYPE_ALC270;
+			} else if ((coef & 0xf000) == 0x2000) {
+				alc_codec_rename(codec, "ALC259");
+				spec->codec_variant = ALC269_TYPE_ALC259;
+			} else if ((coef & 0xf000) == 0x3000) {
+				alc_codec_rename(codec, "ALC258");
+				spec->codec_variant = ALC269_TYPE_ALC258;
+			} else {
+				alc_codec_rename(codec, "ALC269VB");
+				spec->codec_variant = ALC269_TYPE_ALC269VB;
+			}
+		} else
+			alc_fix_pll_init(codec, 0x20, 0x04, 15);
+		alc269_fill_coef(codec);
+	}
+
+	board_config = snd_hda_check_board_config(codec, ALC269_MODEL_LAST,
+						  alc269_models,
+						  alc269_cfg_tbl);
+
+	if (board_config < 0) {
+		printk(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+		       codec->chip_name);
+		board_config = ALC269_AUTO;
+	}
+
+	if (board_config == ALC269_AUTO)
+		alc_pick_fixup(codec, alc269_fixup_tbl, alc269_fixups, 1);
+
+	if (board_config == ALC269_AUTO) {
+		/* automatic parse from the BIOS config */
+		err = alc269_parse_auto_config(codec);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		} else if (!err) {
+			printk(KERN_INFO
+			       "hda_codec: Cannot set up configuration "
+			       "from BIOS.  Using base mode...\n");
+			board_config = ALC269_BASIC;
+		}
+	}
+
+	if (has_cdefine_beep(codec)) {
+		err = snd_hda_attach_beep_device(codec, 0x1);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		}
+	}
+
+	if (board_config != ALC269_AUTO)
+		setup_preset(codec, &alc269_presets[board_config]);
+
+	if (board_config == ALC269_QUANTA_FL1) {
+		/* Due to a hardware problem on Lenovo Ideadpad, we need to
+		 * fix the sample rate of analog I/O to 44.1kHz
+		 */
+		spec->stream_analog_playback = &alc269_44k_pcm_analog_playback;
+		spec->stream_analog_capture = &alc269_44k_pcm_analog_capture;
+	} else if (spec->dual_adc_switch) {
+		spec->stream_analog_playback = &alc269_pcm_analog_playback;
+		/* switch ADC dynamically */
+		spec->stream_analog_capture = &dualmic_pcm_analog_capture;
+	} else {
+		spec->stream_analog_playback = &alc269_pcm_analog_playback;
+		spec->stream_analog_capture = &alc269_pcm_analog_capture;
+	}
+	spec->stream_digital_playback = &alc269_pcm_digital_playback;
+	spec->stream_digital_capture = &alc269_pcm_digital_capture;
+
+	if (!spec->adc_nids) { /* wasn't filled automatically? use default */
+		if (spec->codec_variant == ALC269_TYPE_NORMAL) {
+			spec->adc_nids = alc269_adc_nids;
+			spec->num_adc_nids = ARRAY_SIZE(alc269_adc_nids);
+			spec->capsrc_nids = alc269_capsrc_nids;
+		} else {
+			spec->adc_nids = alc269vb_adc_nids;
+			spec->num_adc_nids = ARRAY_SIZE(alc269vb_adc_nids);
+			spec->capsrc_nids = alc269vb_capsrc_nids;
+		}
+	}
+
+	if (!spec->cap_mixer)
+		set_capture_mixer(codec);
+	if (has_cdefine_beep(codec))
+		set_beep_amp(spec, 0x0b, 0x04, HDA_INPUT);
+
+	if (board_config == ALC269_AUTO)
+		alc_pick_fixup(codec, alc269_fixup_tbl, alc269_fixups, 0);
+
+	spec->vmaster_nid = 0x02;
+
+	codec->patch_ops = alc_patch_ops;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	codec->patch_ops.suspend = alc269_suspend;
+#endif
+#ifdef SND_HDA_NEEDS_RESUME
+	codec->patch_ops.resume = alc269_resume;
+#endif
+	if (board_config == ALC269_AUTO)
+		spec->init_hook = alc269_auto_init;
+
+	alc_init_jacks(codec);
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	if (!spec->loopback.amplist)
+		spec->loopback.amplist = alc269_loopbacks;
+	if (alc269_mic2_for_mute_led(codec))
+		codec->patch_ops.check_power_status = alc269_mic2_mute_check_ps;
+#endif
+
+	return 0;
+}
+
+/*
+ *  ALC861 channel source setting (2/6 channel selection for 3-stack)
+ */
+
+/*
+ * set the path ways for 2 channel output
+ * need to set the codec line out and mic 1 pin widgets to inputs
+ */
+static struct hda_verb alc861_threestack_ch2_init[] = {
+	/* set pin widget 1Ah (line in) for input */
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	/* set pin widget 18h (mic1/2) for input, for mic also enable
+	 * the vref
+	 */
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c },
+#if 0
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8)) }, /*mic*/
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8)) }, /*line-in*/
+#endif
+	{ } /* end */
+};
+/*
+ * 6ch mode
+ * need to set the codec line out and mic 1 pin widgets to outputs
+ */
+static struct hda_verb alc861_threestack_ch6_init[] = {
+	/* set pin widget 1Ah (line in) for output (Back Surround)*/
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	/* set pin widget 18h (mic1) for output (CLFE)*/
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+
+	{ 0x0c, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	{ 0x0d, AC_VERB_SET_CONNECT_SEL, 0x00 },
+
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 },
+#if 0
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8)) }, /*mic*/
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8)) }, /*line in*/
+#endif
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc861_threestack_modes[2] = {
+	{ 2, alc861_threestack_ch2_init },
+	{ 6, alc861_threestack_ch6_init },
+};
+/* Set mic1 as input and unmute the mixer */
+static struct hda_verb alc861_uniwill_m31_ch2_init[] = {
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8)) }, /*mic*/
+	{ } /* end */
+};
+/* Set mic1 as output and mute mixer */
+static struct hda_verb alc861_uniwill_m31_ch4_init[] = {
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8)) }, /*mic*/
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc861_uniwill_m31_modes[2] = {
+	{ 2, alc861_uniwill_m31_ch2_init },
+	{ 4, alc861_uniwill_m31_ch4_init },
+};
+
+/* Set mic1 and line-in as input and unmute the mixer */
+static struct hda_verb alc861_asus_ch2_init[] = {
+	/* set pin widget 1Ah (line in) for input */
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	/* set pin widget 18h (mic1/2) for input, for mic also enable
+	 * the vref
+	 */
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c },
+#if 0
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8)) }, /*mic*/
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8)) }, /*line-in*/
+#endif
+	{ } /* end */
+};
+/* Set mic1 nad line-in as output and mute mixer */
+static struct hda_verb alc861_asus_ch6_init[] = {
+	/* set pin widget 1Ah (line in) for output (Back Surround)*/
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	/* { 0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, */
+	/* set pin widget 18h (mic1) for output (CLFE)*/
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	/* { 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, */
+	{ 0x0c, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	{ 0x0d, AC_VERB_SET_CONNECT_SEL, 0x00 },
+
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 },
+#if 0
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8)) }, /*mic*/
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8)) }, /*line in*/
+#endif
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc861_asus_modes[2] = {
+	{ 2, alc861_asus_ch2_init },
+	{ 6, alc861_asus_ch6_init },
+};
+
+/* patch-ALC861 */
+
+static struct snd_kcontrol_new alc861_base_mixer[] = {
+        /* output mixer control */
+	HDA_CODEC_MUTE("Front Playback Switch", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Surround Playback Switch", 0x06, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x05, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x05, 2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Side Playback Switch", 0x04, 0x0, HDA_OUTPUT),
+
+        /*Input mixer control */
+	/* HDA_CODEC_VOLUME("Input Playback Volume", 0x15, 0x0, HDA_OUTPUT),
+	   HDA_CODEC_MUTE("Input Playback Switch", 0x15, 0x0, HDA_OUTPUT), */
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x15, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x15, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x15, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x15, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x10, 0x01, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x03, HDA_INPUT),
+
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc861_3ST_mixer[] = {
+        /* output mixer control */
+	HDA_CODEC_MUTE("Front Playback Switch", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Surround Playback Switch", 0x06, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x05, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x05, 2, 0x0, HDA_OUTPUT),
+	/*HDA_CODEC_MUTE("Side Playback Switch", 0x04, 0x0, HDA_OUTPUT), */
+
+	/* Input mixer control */
+	/* HDA_CODEC_VOLUME("Input Playback Volume", 0x15, 0x0, HDA_OUTPUT),
+	   HDA_CODEC_MUTE("Input Playback Switch", 0x15, 0x0, HDA_OUTPUT), */
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x15, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x15, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x15, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x15, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x10, 0x01, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x03, HDA_INPUT),
+
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+                .private_value = ARRAY_SIZE(alc861_threestack_modes),
+	},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc861_toshiba_mixer[] = {
+        /* output mixer control */
+	HDA_CODEC_MUTE("Master Playback Switch", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x15, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x15, 0x01, HDA_INPUT),
+
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc861_uniwill_m31_mixer[] = {
+        /* output mixer control */
+	HDA_CODEC_MUTE("Front Playback Switch", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Surround Playback Switch", 0x06, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x05, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x05, 2, 0x0, HDA_OUTPUT),
+	/*HDA_CODEC_MUTE("Side Playback Switch", 0x04, 0x0, HDA_OUTPUT), */
+
+	/* Input mixer control */
+	/* HDA_CODEC_VOLUME("Input Playback Volume", 0x15, 0x0, HDA_OUTPUT),
+	   HDA_CODEC_MUTE("Input Playback Switch", 0x15, 0x0, HDA_OUTPUT), */
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x15, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x15, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x15, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x15, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x10, 0x01, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x03, HDA_INPUT),
+
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+                .private_value = ARRAY_SIZE(alc861_uniwill_m31_modes),
+	},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc861_asus_mixer[] = {
+        /* output mixer control */
+	HDA_CODEC_MUTE("Front Playback Switch", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Surround Playback Switch", 0x06, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x05, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x05, 2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Side Playback Switch", 0x04, 0x0, HDA_OUTPUT),
+
+	/* Input mixer control */
+	HDA_CODEC_VOLUME("Input Playback Volume", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Input Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x15, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x15, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x15, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x15, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x10, 0x01, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x03, HDA_OUTPUT),
+
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+                .private_value = ARRAY_SIZE(alc861_asus_modes),
+	},
+	{ }
+};
+
+/* additional mixer */
+static struct snd_kcontrol_new alc861_asus_laptop_mixer[] = {
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_INPUT),
+	{ }
+};
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc861_base_init_verbs[] = {
+	/*
+	 * Unmute ADC0 and set the default input to mic-in
+	 */
+	/* port-A for surround (rear panel) */
+	{ 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	{ 0x0e, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-B for mic-in (rear panel) with vref */
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* port-C for line-in (rear panel) */
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	/* port-D for Front */
+	{ 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	{ 0x0b, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-E for HP out (front panel) */
+	{ 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+	/* route front PCM to HP */
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-F for mic-in (front panel) with vref */
+	{ 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* port-G for CLFE (rear panel) */
+	{ 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	{ 0x1f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-H for side (rear panel) */
+	{ 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	{ 0x20, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* CD-in */
+	{ 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	/* route front mic to ADC1*/
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Unmute DAC0~3 & spdif out*/
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Unmute Mixer 14 (mic) 1c (Line in)*/
+	{0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+        {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+        {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* Unmute Stereo Mixer 15 */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c}, /* Output 0~12 step */
+
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* hp used DAC 3 (Front) */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+        {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+
+	{ }
+};
+
+static struct hda_verb alc861_threestack_init_verbs[] = {
+	/*
+	 * Unmute ADC0 and set the default input to mic-in
+	 */
+	/* port-A for surround (rear panel) */
+	{ 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 },
+	/* port-B for mic-in (rear panel) with vref */
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* port-C for line-in (rear panel) */
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	/* port-D for Front */
+	{ 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	{ 0x0b, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-E for HP out (front panel) */
+	{ 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+	/* route front PCM to HP */
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-F for mic-in (front panel) with vref */
+	{ 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* port-G for CLFE (rear panel) */
+	{ 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 },
+	/* port-H for side (rear panel) */
+	{ 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 },
+	/* CD-in */
+	{ 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	/* route front mic to ADC1*/
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Unmute DAC0~3 & spdif out*/
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Unmute Mixer 14 (mic) 1c (Line in)*/
+	{0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+        {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+        {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* Unmute Stereo Mixer 15 */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c}, /* Output 0~12 step */
+
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* hp used DAC 3 (Front) */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+        {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{ }
+};
+
+static struct hda_verb alc861_uniwill_m31_init_verbs[] = {
+	/*
+	 * Unmute ADC0 and set the default input to mic-in
+	 */
+	/* port-A for surround (rear panel) */
+	{ 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 },
+	/* port-B for mic-in (rear panel) with vref */
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* port-C for line-in (rear panel) */
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	/* port-D for Front */
+	{ 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	{ 0x0b, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-E for HP out (front panel) */
+	/* this has to be set to VREF80 */
+	{ 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* route front PCM to HP */
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-F for mic-in (front panel) with vref */
+	{ 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* port-G for CLFE (rear panel) */
+	{ 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 },
+	/* port-H for side (rear panel) */
+	{ 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 },
+	/* CD-in */
+	{ 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	/* route front mic to ADC1*/
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Unmute DAC0~3 & spdif out*/
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Unmute Mixer 14 (mic) 1c (Line in)*/
+	{0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+        {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+        {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* Unmute Stereo Mixer 15 */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c}, /* Output 0~12 step */
+
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* hp used DAC 3 (Front) */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+        {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{ }
+};
+
+static struct hda_verb alc861_asus_init_verbs[] = {
+	/*
+	 * Unmute ADC0 and set the default input to mic-in
+	 */
+	/* port-A for surround (rear panel)
+	 * according to codec#0 this is the HP jack
+	 */
+	{ 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, /* was 0x00 */
+	/* route front PCM to HP */
+	{ 0x0e, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	/* port-B for mic-in (rear panel) with vref */
+	{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* port-C for line-in (rear panel) */
+	{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	/* port-D for Front */
+	{ 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	{ 0x0b, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-E for HP out (front panel) */
+	/* this has to be set to VREF80 */
+	{ 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* route front PCM to HP */
+	{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+	/* port-F for mic-in (front panel) with vref */
+	{ 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+	/* port-G for CLFE (rear panel) */
+	{ 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	/* port-H for side (rear panel) */
+	{ 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+	/* CD-in */
+	{ 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+	/* route front mic to ADC1*/
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	/* Unmute DAC0~3 & spdif out*/
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Unmute Mixer 14 (mic) 1c (Line in)*/
+	{0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+        {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+        {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* Unmute Stereo Mixer 15 */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c}, /* Output 0~12 step */
+
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	/* hp used DAC 3 (Front) */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{ }
+};
+
+/* additional init verbs for ASUS laptops */
+static struct hda_verb alc861_asus_laptop_init_verbs[] = {
+	{ 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x45 }, /* HP-out */
+	{ 0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2) }, /* mute line-in */
+	{ }
+};
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc861_auto_init_verbs[] = {
+	/*
+	 * Unmute ADC0 and set the default input to mic-in
+	 */
+	/* {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, */
+	{0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Unmute DAC0~3 & spdif out*/
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Unmute Mixer 14 (mic) 1c (Line in)*/
+	{0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* Unmute Stereo Mixer 15 */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c},
+
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+
+	{0x08, AC_VERB_SET_CONNECT_SEL, 0x00},	/* set Mic 1 */
+
+	{ }
+};
+
+static struct hda_verb alc861_toshiba_init_verbs[] = {
+	{0x0f, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+
+	{ }
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc861_toshiba_automute(struct hda_codec *codec)
+{
+	unsigned int present = snd_hda_jack_detect(codec, 0x0f);
+
+	snd_hda_codec_amp_stereo(codec, 0x16, HDA_INPUT, 0,
+				 HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+	snd_hda_codec_amp_stereo(codec, 0x1a, HDA_INPUT, 3,
+				 HDA_AMP_MUTE, present ? 0 : HDA_AMP_MUTE);
+}
+
+static void alc861_toshiba_unsol_event(struct hda_codec *codec,
+				       unsigned int res)
+{
+	if ((res >> 26) == ALC880_HP_EVENT)
+		alc861_toshiba_automute(codec);
+}
+
+/* pcm configuration: identical with ALC880 */
+#define alc861_pcm_analog_playback	alc880_pcm_analog_playback
+#define alc861_pcm_analog_capture	alc880_pcm_analog_capture
+#define alc861_pcm_digital_playback	alc880_pcm_digital_playback
+#define alc861_pcm_digital_capture	alc880_pcm_digital_capture
+
+
+#define ALC861_DIGOUT_NID	0x07
+
+static struct hda_channel_mode alc861_8ch_modes[1] = {
+	{ 8, NULL }
+};
+
+static hda_nid_t alc861_dac_nids[4] = {
+	/* front, surround, clfe, side */
+	0x03, 0x06, 0x05, 0x04
+};
+
+static hda_nid_t alc660_dac_nids[3] = {
+	/* front, clfe, surround */
+	0x03, 0x05, 0x06
+};
+
+static hda_nid_t alc861_adc_nids[1] = {
+	/* ADC0-2 */
+	0x08,
+};
+
+static struct hda_input_mux alc861_capture_source = {
+	.num_items = 5,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x3 },
+		{ "Line", 0x1 },
+		{ "CD", 0x4 },
+		{ "Mixer", 0x5 },
+	},
+};
+
+static hda_nid_t alc861_look_for_dac(struct hda_codec *codec, hda_nid_t pin)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t mix, srcs[5];
+	int i, j, num;
+
+	if (snd_hda_get_connections(codec, pin, &mix, 1) != 1)
+		return 0;
+	num = snd_hda_get_connections(codec, mix, srcs, ARRAY_SIZE(srcs));
+	if (num < 0)
+		return 0;
+	for (i = 0; i < num; i++) {
+		unsigned int type;
+		type = get_wcaps_type(get_wcaps(codec, srcs[i]));
+		if (type != AC_WID_AUD_OUT)
+			continue;
+		for (j = 0; j < spec->multiout.num_dacs; j++)
+			if (spec->multiout.dac_nids[j] == srcs[i])
+				break;
+		if (j >= spec->multiout.num_dacs)
+			return srcs[i];
+	}
+	return 0;
+}
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int alc861_auto_fill_dac_nids(struct hda_codec *codec,
+				     const struct auto_pin_cfg *cfg)
+{
+	struct alc_spec *spec = codec->spec;
+	int i;
+	hda_nid_t nid, dac;
+
+	spec->multiout.dac_nids = spec->private_dac_nids;
+	for (i = 0; i < cfg->line_outs; i++) {
+		nid = cfg->line_out_pins[i];
+		dac = alc861_look_for_dac(codec, nid);
+		if (!dac)
+			continue;
+		spec->multiout.dac_nids[spec->multiout.num_dacs++] = dac;
+	}
+	return 0;
+}
+
+static int alc861_create_out_sw(struct hda_codec *codec, const char *pfx,
+				hda_nid_t nid, unsigned int chs)
+{
+	return add_pb_sw_ctrl(codec->spec, ALC_CTL_WIDGET_MUTE, pfx,
+			   HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_OUTPUT));
+}
+
+/* add playback controls from the parsed DAC table */
+static int alc861_auto_create_multi_out_ctls(struct hda_codec *codec,
+					     const struct auto_pin_cfg *cfg)
+{
+	struct alc_spec *spec = codec->spec;
+	static const char *chname[4] = {
+		"Front", "Surround", NULL /*CLFE*/, "Side"
+	};
+	hda_nid_t nid;
+	int i, err;
+
+	if (cfg->line_outs == 1) {
+		const char *pfx = NULL;
+		if (!cfg->hp_outs)
+			pfx = "Master";
+		else if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
+			pfx = "Speaker";
+		if (pfx) {
+			nid = spec->multiout.dac_nids[0];
+			return alc861_create_out_sw(codec, pfx, nid, 3);
+		}
+	}
+
+	for (i = 0; i < cfg->line_outs; i++) {
+		nid = spec->multiout.dac_nids[i];
+		if (!nid)
+			continue;
+		if (i == 2) {
+			/* Center/LFE */
+			err = alc861_create_out_sw(codec, "Center", nid, 1);
+			if (err < 0)
+				return err;
+			err = alc861_create_out_sw(codec, "LFE", nid, 2);
+			if (err < 0)
+				return err;
+		} else {
+			err = alc861_create_out_sw(codec, chname[i], nid, 3);
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+static int alc861_auto_create_hp_ctls(struct hda_codec *codec, hda_nid_t pin)
+{
+	struct alc_spec *spec = codec->spec;
+	int err;
+	hda_nid_t nid;
+
+	if (!pin)
+		return 0;
+
+	if ((pin >= 0x0b && pin <= 0x10) || pin == 0x1f || pin == 0x20) {
+		nid = alc861_look_for_dac(codec, pin);
+		if (nid) {
+			err = alc861_create_out_sw(codec, "Headphone", nid, 3);
+			if (err < 0)
+				return err;
+			spec->multiout.hp_nid = nid;
+		}
+	}
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int alc861_auto_create_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	return alc_auto_create_input_ctls(codec, cfg, 0x15, 0x08, 0);
+}
+
+static void alc861_auto_set_output_and_unmute(struct hda_codec *codec,
+					      hda_nid_t nid,
+					      int pin_type, hda_nid_t dac)
+{
+	hda_nid_t mix, srcs[5];
+	int i, num;
+
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    pin_type);
+	snd_hda_codec_write(codec, dac, 0, AC_VERB_SET_AMP_GAIN_MUTE,
+			    AMP_OUT_UNMUTE);
+	if (snd_hda_get_connections(codec, nid, &mix, 1) != 1)
+		return;
+	num = snd_hda_get_connections(codec, mix, srcs, ARRAY_SIZE(srcs));
+	if (num < 0)
+		return;
+	for (i = 0; i < num; i++) {
+		unsigned int mute;
+		if (srcs[i] == dac || srcs[i] == 0x15)
+			mute = AMP_IN_UNMUTE(i);
+		else
+			mute = AMP_IN_MUTE(i);
+		snd_hda_codec_write(codec, mix, 0, AC_VERB_SET_AMP_GAIN_MUTE,
+				    mute);
+	}
+}
+
+static void alc861_auto_init_multi_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->autocfg.line_outs; i++) {
+		hda_nid_t nid = spec->autocfg.line_out_pins[i];
+		int pin_type = get_pin_type(spec->autocfg.line_out_type);
+		if (nid)
+			alc861_auto_set_output_and_unmute(codec, nid, pin_type,
+							  spec->multiout.dac_nids[i]);
+	}
+}
+
+static void alc861_auto_init_hp_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	if (spec->autocfg.hp_outs)
+		alc861_auto_set_output_and_unmute(codec,
+						  spec->autocfg.hp_pins[0],
+						  PIN_HP,
+						  spec->multiout.hp_nid);
+	if (spec->autocfg.speaker_outs)
+		alc861_auto_set_output_and_unmute(codec,
+						  spec->autocfg.speaker_pins[0],
+						  PIN_OUT,
+						  spec->multiout.dac_nids[0]);
+}
+
+static void alc861_auto_init_analog_input(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		if (nid >= 0x0c && nid <= 0x11)
+			alc_set_input_pin(codec, nid, cfg->inputs[i].type);
+	}
+}
+
+/* parse the BIOS configuration and set up the alc_spec */
+/* return 1 if successful, 0 if the proper config is not found,
+ * or a negative error code
+ */
+static int alc861_parse_auto_config(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int err;
+	static hda_nid_t alc861_ignore[] = { 0x1d, 0 };
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg,
+					   alc861_ignore);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs)
+		return 0; /* can't find valid BIOS pin config */
+
+	err = alc861_auto_fill_dac_nids(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc861_auto_create_multi_out_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc861_auto_create_hp_ctls(codec, spec->autocfg.hp_pins[0]);
+	if (err < 0)
+		return err;
+	err = alc861_auto_create_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	alc_auto_parse_digital(codec);
+
+	if (spec->kctls.list)
+		add_mixer(spec, spec->kctls.list);
+
+	add_verb(spec, alc861_auto_init_verbs);
+
+	spec->num_mux_defs = 1;
+	spec->input_mux = &spec->private_imux[0];
+
+	spec->adc_nids = alc861_adc_nids;
+	spec->num_adc_nids = ARRAY_SIZE(alc861_adc_nids);
+	set_capture_mixer(codec);
+
+	alc_ssid_check(codec, 0x0e, 0x0f, 0x0b, 0);
+
+	return 1;
+}
+
+/* additional initialization for auto-configuration model */
+static void alc861_auto_init(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc861_auto_init_multi_out(codec);
+	alc861_auto_init_hp_out(codec);
+	alc861_auto_init_analog_input(codec);
+	alc_auto_init_digital(codec);
+	if (spec->unsol_event)
+		alc_inithook(codec);
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list alc861_loopbacks[] = {
+	{ 0x15, HDA_INPUT, 0 },
+	{ 0x15, HDA_INPUT, 1 },
+	{ 0x15, HDA_INPUT, 2 },
+	{ 0x15, HDA_INPUT, 3 },
+	{ } /* end */
+};
+#endif
+
+
+/*
+ * configuration and preset
+ */
+static const char *alc861_models[ALC861_MODEL_LAST] = {
+	[ALC861_3ST]		= "3stack",
+	[ALC660_3ST]		= "3stack-660",
+	[ALC861_3ST_DIG]	= "3stack-dig",
+	[ALC861_6ST_DIG]	= "6stack-dig",
+	[ALC861_UNIWILL_M31]	= "uniwill-m31",
+	[ALC861_TOSHIBA]	= "toshiba",
+	[ALC861_ASUS]		= "asus",
+	[ALC861_ASUS_LAPTOP]	= "asus-laptop",
+	[ALC861_AUTO]		= "auto",
+};
+
+static struct snd_pci_quirk alc861_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1043, 0x1205, "ASUS W7J", ALC861_3ST),
+	SND_PCI_QUIRK(0x1043, 0x1335, "ASUS F2/3", ALC861_ASUS_LAPTOP),
+	SND_PCI_QUIRK(0x1043, 0x1338, "ASUS F2/3", ALC861_ASUS_LAPTOP),
+	SND_PCI_QUIRK(0x1043, 0x1393, "ASUS", ALC861_ASUS),
+	SND_PCI_QUIRK(0x1043, 0x13d7, "ASUS A9rp", ALC861_ASUS_LAPTOP),
+	SND_PCI_QUIRK(0x1043, 0x81cb, "ASUS P1-AH2", ALC861_3ST_DIG),
+	SND_PCI_QUIRK(0x1179, 0xff00, "Toshiba", ALC861_TOSHIBA),
+	/* FIXME: the entry below breaks Toshiba A100 (model=auto works!)
+	 *        Any other models that need this preset?
+	 */
+	/* SND_PCI_QUIRK(0x1179, 0xff10, "Toshiba", ALC861_TOSHIBA), */
+	SND_PCI_QUIRK(0x1462, 0x7254, "HP dx2200 (MSI MS-7254)", ALC861_3ST),
+	SND_PCI_QUIRK(0x1462, 0x7297, "HP dx2250 (MSI MS-7297)", ALC861_3ST),
+	SND_PCI_QUIRK(0x1584, 0x2b01, "Uniwill X40AIx", ALC861_UNIWILL_M31),
+	SND_PCI_QUIRK(0x1584, 0x9072, "Uniwill m31", ALC861_UNIWILL_M31),
+	SND_PCI_QUIRK(0x1584, 0x9075, "Airis Praxis N1212", ALC861_ASUS_LAPTOP),
+	/* FIXME: the below seems conflict */
+	/* SND_PCI_QUIRK(0x1584, 0x9075, "Uniwill", ALC861_UNIWILL_M31), */
+	SND_PCI_QUIRK(0x1849, 0x0660, "Asrock 939SLI32", ALC660_3ST),
+	SND_PCI_QUIRK(0x8086, 0xd600, "Intel", ALC861_3ST),
+	{}
+};
+
+static struct alc_config_preset alc861_presets[] = {
+	[ALC861_3ST] = {
+		.mixers = { alc861_3ST_mixer },
+		.init_verbs = { alc861_threestack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc861_dac_nids),
+		.dac_nids = alc861_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc861_threestack_modes),
+		.channel_mode = alc861_threestack_modes,
+		.need_dac_fix = 1,
+		.num_adc_nids = ARRAY_SIZE(alc861_adc_nids),
+		.adc_nids = alc861_adc_nids,
+		.input_mux = &alc861_capture_source,
+	},
+	[ALC861_3ST_DIG] = {
+		.mixers = { alc861_base_mixer },
+		.init_verbs = { alc861_threestack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc861_dac_nids),
+		.dac_nids = alc861_dac_nids,
+		.dig_out_nid = ALC861_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc861_threestack_modes),
+		.channel_mode = alc861_threestack_modes,
+		.need_dac_fix = 1,
+		.num_adc_nids = ARRAY_SIZE(alc861_adc_nids),
+		.adc_nids = alc861_adc_nids,
+		.input_mux = &alc861_capture_source,
+	},
+	[ALC861_6ST_DIG] = {
+		.mixers = { alc861_base_mixer },
+		.init_verbs = { alc861_base_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc861_dac_nids),
+		.dac_nids = alc861_dac_nids,
+		.dig_out_nid = ALC861_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc861_8ch_modes),
+		.channel_mode = alc861_8ch_modes,
+		.num_adc_nids = ARRAY_SIZE(alc861_adc_nids),
+		.adc_nids = alc861_adc_nids,
+		.input_mux = &alc861_capture_source,
+	},
+	[ALC660_3ST] = {
+		.mixers = { alc861_3ST_mixer },
+		.init_verbs = { alc861_threestack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc660_dac_nids),
+		.dac_nids = alc660_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc861_threestack_modes),
+		.channel_mode = alc861_threestack_modes,
+		.need_dac_fix = 1,
+		.num_adc_nids = ARRAY_SIZE(alc861_adc_nids),
+		.adc_nids = alc861_adc_nids,
+		.input_mux = &alc861_capture_source,
+	},
+	[ALC861_UNIWILL_M31] = {
+		.mixers = { alc861_uniwill_m31_mixer },
+		.init_verbs = { alc861_uniwill_m31_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc861_dac_nids),
+		.dac_nids = alc861_dac_nids,
+		.dig_out_nid = ALC861_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc861_uniwill_m31_modes),
+		.channel_mode = alc861_uniwill_m31_modes,
+		.need_dac_fix = 1,
+		.num_adc_nids = ARRAY_SIZE(alc861_adc_nids),
+		.adc_nids = alc861_adc_nids,
+		.input_mux = &alc861_capture_source,
+	},
+	[ALC861_TOSHIBA] = {
+		.mixers = { alc861_toshiba_mixer },
+		.init_verbs = { alc861_base_init_verbs,
+				alc861_toshiba_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc861_dac_nids),
+		.dac_nids = alc861_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.num_adc_nids = ARRAY_SIZE(alc861_adc_nids),
+		.adc_nids = alc861_adc_nids,
+		.input_mux = &alc861_capture_source,
+		.unsol_event = alc861_toshiba_unsol_event,
+		.init_hook = alc861_toshiba_automute,
+	},
+	[ALC861_ASUS] = {
+		.mixers = { alc861_asus_mixer },
+		.init_verbs = { alc861_asus_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc861_dac_nids),
+		.dac_nids = alc861_dac_nids,
+		.dig_out_nid = ALC861_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc861_asus_modes),
+		.channel_mode = alc861_asus_modes,
+		.need_dac_fix = 1,
+		.hp_nid = 0x06,
+		.num_adc_nids = ARRAY_SIZE(alc861_adc_nids),
+		.adc_nids = alc861_adc_nids,
+		.input_mux = &alc861_capture_source,
+	},
+	[ALC861_ASUS_LAPTOP] = {
+		.mixers = { alc861_toshiba_mixer, alc861_asus_laptop_mixer },
+		.init_verbs = { alc861_asus_init_verbs,
+				alc861_asus_laptop_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc861_dac_nids),
+		.dac_nids = alc861_dac_nids,
+		.dig_out_nid = ALC861_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes),
+		.channel_mode = alc883_3ST_2ch_modes,
+		.need_dac_fix = 1,
+		.num_adc_nids = ARRAY_SIZE(alc861_adc_nids),
+		.adc_nids = alc861_adc_nids,
+		.input_mux = &alc861_capture_source,
+	},
+};
+
+/* Pin config fixes */
+enum {
+	PINFIX_FSC_AMILO_PI1505,
+};
+
+static const struct alc_fixup alc861_fixups[] = {
+	[PINFIX_FSC_AMILO_PI1505] = {
+		.pins = (const struct alc_pincfg[]) {
+			{ 0x0b, 0x0221101f }, /* HP */
+			{ 0x0f, 0x90170310 }, /* speaker */
+			{ }
+		}
+	},
+};
+
+static struct snd_pci_quirk alc861_fixup_tbl[] = {
+	SND_PCI_QUIRK(0x1734, 0x10c7, "FSC Amilo Pi1505", PINFIX_FSC_AMILO_PI1505),
+	{}
+};
+
+static int patch_alc861(struct hda_codec *codec)
+{
+	struct alc_spec *spec;
+	int board_config;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+        board_config = snd_hda_check_board_config(codec, ALC861_MODEL_LAST,
+						  alc861_models,
+						  alc861_cfg_tbl);
+
+	if (board_config < 0) {
+		printk(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+		       codec->chip_name);
+		board_config = ALC861_AUTO;
+	}
+
+	if (board_config == ALC861_AUTO)
+		alc_pick_fixup(codec, alc861_fixup_tbl, alc861_fixups, 1);
+
+	if (board_config == ALC861_AUTO) {
+		/* automatic parse from the BIOS config */
+		err = alc861_parse_auto_config(codec);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		} else if (!err) {
+			printk(KERN_INFO
+			       "hda_codec: Cannot set up configuration "
+			       "from BIOS.  Using base mode...\n");
+		   board_config = ALC861_3ST_DIG;
+		}
+	}
+
+	err = snd_hda_attach_beep_device(codec, 0x23);
+	if (err < 0) {
+		alc_free(codec);
+		return err;
+	}
+
+	if (board_config != ALC861_AUTO)
+		setup_preset(codec, &alc861_presets[board_config]);
+
+	spec->stream_analog_playback = &alc861_pcm_analog_playback;
+	spec->stream_analog_capture = &alc861_pcm_analog_capture;
+
+	spec->stream_digital_playback = &alc861_pcm_digital_playback;
+	spec->stream_digital_capture = &alc861_pcm_digital_capture;
+
+	if (!spec->cap_mixer)
+		set_capture_mixer(codec);
+	set_beep_amp(spec, 0x23, 0, HDA_OUTPUT);
+
+	spec->vmaster_nid = 0x03;
+
+	if (board_config == ALC861_AUTO)
+		alc_pick_fixup(codec, alc861_fixup_tbl, alc861_fixups, 0);
+
+	codec->patch_ops = alc_patch_ops;
+	if (board_config == ALC861_AUTO) {
+		spec->init_hook = alc861_auto_init;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+		spec->power_hook = alc_power_eapd;
+#endif
+	}
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	if (!spec->loopback.amplist)
+		spec->loopback.amplist = alc861_loopbacks;
+#endif
+
+	return 0;
+}
+
+/*
+ * ALC861-VD support
+ *
+ * Based on ALC882
+ *
+ * In addition, an independent DAC
+ */
+#define ALC861VD_DIGOUT_NID	0x06
+
+static hda_nid_t alc861vd_dac_nids[4] = {
+	/* front, surr, clfe, side surr */
+	0x02, 0x03, 0x04, 0x05
+};
+
+/* dac_nids for ALC660vd are in a different order - according to
+ * Realtek's driver.
+ * This should probably result in a different mixer for 6stack models
+ * of ALC660vd codecs, but for now there is only 3stack mixer
+ * - and it is the same as in 861vd.
+ * adc_nids in ALC660vd are (is) the same as in 861vd
+ */
+static hda_nid_t alc660vd_dac_nids[3] = {
+	/* front, rear, clfe, rear_surr */
+	0x02, 0x04, 0x03
+};
+
+static hda_nid_t alc861vd_adc_nids[1] = {
+	/* ADC0 */
+	0x09,
+};
+
+static hda_nid_t alc861vd_capsrc_nids[1] = { 0x22 };
+
+/* input MUX */
+/* FIXME: should be a matrix-type input source selection */
+static struct hda_input_mux alc861vd_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x1 },
+		{ "Line", 0x2 },
+		{ "CD", 0x4 },
+	},
+};
+
+static struct hda_input_mux alc861vd_dallas_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Ext Mic", 0x0 },
+		{ "Int Mic", 0x1 },
+	},
+};
+
+static struct hda_input_mux alc861vd_hp_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Front Mic", 0x0 },
+		{ "ATAPI Mic", 0x1 },
+	},
+};
+
+/*
+ * 2ch mode
+ */
+static struct hda_channel_mode alc861vd_3stack_2ch_modes[1] = {
+	{ 2, NULL }
+};
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc861vd_6stack_ch6_init[] = {
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 },
+	{ 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ } /* end */
+};
+
+/*
+ * 8ch mode
+ */
+static struct hda_verb alc861vd_6stack_ch8_init[] = {
+	{ 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc861vd_6stack_modes[2] = {
+	{ 6, alc861vd_6stack_ch6_init },
+	{ 8, alc861vd_6stack_ch8_init },
+};
+
+static struct snd_kcontrol_new alc861vd_chmode_mixer[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+/* Pin assignment: Front=0x14, Rear=0x15, CLFE=0x16, Side=0x17
+ *                 Mic=0x18, Front Mic=0x19, Line-In=0x1a, HP=0x1b
+ */
+static struct snd_kcontrol_new alc861vd_6st_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT),
+
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x04, 1, 0x0,
+				HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x04, 2, 0x0,
+				HDA_OUTPUT),
+	HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT),
+	HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Side Playback Volume", 0x05, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT),
+
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc861vd_3st_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc861vd_lenovo_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	/*HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),*/
+	HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+
+	{ } /* end */
+};
+
+/* Pin assignment: Speaker=0x14, HP = 0x15,
+ *                 Ext Mic=0x18, Int Mic = 0x19, CD = 0x1c, PC Beep = 0x1d
+ */
+static struct snd_kcontrol_new alc861vd_dallas_mixer[] = {
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Ext Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Ext Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Ext Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+/* Pin assignment: Speaker=0x14, Line-out = 0x15,
+ *                 Front Mic=0x18, ATAPI Mic = 0x19,
+ */
+static struct snd_kcontrol_new alc861vd_hp_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Headphone Playback Switch", 0x0d, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("ATAPI Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("ATAPI Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+
+	{ } /* end */
+};
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc861vd_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0 and set the default input to mic-in
+	 */
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of
+	 * the analog-loopback mixer widget
+	 */
+	/* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+
+	/* Capture mixer: unmute Mic, F-Mic, Line, CD inputs */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)},
+
+	/*
+	 * Set up output mixers (0x02 - 0x05)
+	 */
+	/* set vol=0 to output mixers */
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* set up input amps for analog loopback */
+	/* Amp Indices: DAC = 0, mixer = 1 */
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+
+	{ }
+};
+
+/*
+ * 3-stack pin configuration:
+ * front = 0x14, mic/clfe = 0x18, HP = 0x19, line/surr = 0x1a, f-mic = 0x1b
+ */
+static struct hda_verb alc861vd_3stack_init_verbs[] = {
+	/*
+	 * Set pin mode and muting
+	 */
+	/* set front pin widgets 0x14 for output */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Mic (rear) pin: input vref at 80% */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Front Mic pin: input vref at 80% */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line In pin: input */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line-2 In: Headphone output (output 0 - 0x0c) */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* CD pin widget for input */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{ }
+};
+
+/*
+ * 6-stack pin configuration:
+ */
+static struct hda_verb alc861vd_6stack_init_verbs[] = {
+	/*
+	 * Set pin mode and muting
+	 */
+	/* set front pin widgets 0x14 for output */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	/* Rear Pin: output 1 (0x0d) */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+	/* CLFE Pin: output 2 (0x0e) */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x02},
+	/* Side Pin: output 3 (0x0f) */
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_CONNECT_SEL, 0x03},
+
+	/* Mic (rear) pin: input vref at 80% */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Front Mic pin: input vref at 80% */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line In pin: input */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line-2 In: Headphone output (output 0 - 0x0c) */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* CD pin widget for input */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{ }
+};
+
+static struct hda_verb alc861vd_eapd_verbs[] = {
+	{0x14, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{ }
+};
+
+static struct hda_verb alc660vd_eapd_verbs[] = {
+	{0x14, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{0x15, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{ }
+};
+
+static struct hda_verb alc861vd_lenovo_unsol_verbs[] = {
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{}
+};
+
+static void alc861vd_lenovo_mic_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, 0x18);
+	bits = present ? HDA_AMP_MUTE : 0;
+
+	snd_hda_codec_amp_stereo(codec, 0x0b, HDA_INPUT, 1,
+				 HDA_AMP_MUTE, bits);
+}
+
+static void alc861vd_lenovo_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->autocfg.hp_pins[0] = 0x1b;
+	spec->autocfg.speaker_pins[0] = 0x14;
+}
+
+static void alc861vd_lenovo_init_hook(struct hda_codec *codec)
+{
+	alc_automute_amp(codec);
+	alc861vd_lenovo_mic_automute(codec);
+}
+
+static void alc861vd_lenovo_unsol_event(struct hda_codec *codec,
+					unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_MIC_EVENT:
+		alc861vd_lenovo_mic_automute(codec);
+		break;
+	default:
+		alc_automute_amp_unsol_event(codec, res);
+		break;
+	}
+}
+
+static struct hda_verb alc861vd_dallas_verbs[] = {
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+
+	{ } /* end */
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc861vd_dallas_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x15;
+	spec->autocfg.speaker_pins[0] = 0x14;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+#define alc861vd_loopbacks	alc880_loopbacks
+#endif
+
+/* pcm configuration: identical with ALC880 */
+#define alc861vd_pcm_analog_playback	alc880_pcm_analog_playback
+#define alc861vd_pcm_analog_capture	alc880_pcm_analog_capture
+#define alc861vd_pcm_digital_playback	alc880_pcm_digital_playback
+#define alc861vd_pcm_digital_capture	alc880_pcm_digital_capture
+
+/*
+ * configuration and preset
+ */
+static const char *alc861vd_models[ALC861VD_MODEL_LAST] = {
+	[ALC660VD_3ST]		= "3stack-660",
+	[ALC660VD_3ST_DIG]	= "3stack-660-digout",
+	[ALC660VD_ASUS_V1S]	= "asus-v1s",
+	[ALC861VD_3ST]		= "3stack",
+	[ALC861VD_3ST_DIG]	= "3stack-digout",
+	[ALC861VD_6ST_DIG]	= "6stack-digout",
+	[ALC861VD_LENOVO]	= "lenovo",
+	[ALC861VD_DALLAS]	= "dallas",
+	[ALC861VD_HP]		= "hp",
+	[ALC861VD_AUTO]		= "auto",
+};
+
+static struct snd_pci_quirk alc861vd_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1019, 0xa88d, "Realtek ALC660 demo", ALC660VD_3ST),
+	SND_PCI_QUIRK(0x103c, 0x30bf, "HP TX1000", ALC861VD_HP),
+	SND_PCI_QUIRK(0x1043, 0x12e2, "Asus z35m", ALC660VD_3ST),
+	/*SND_PCI_QUIRK(0x1043, 0x1339, "Asus G1", ALC660VD_3ST),*/ /* auto */
+	SND_PCI_QUIRK(0x1043, 0x1633, "Asus V1Sn", ALC660VD_ASUS_V1S),
+	SND_PCI_QUIRK(0x1043, 0x81e7, "ASUS", ALC660VD_3ST_DIG),
+	SND_PCI_QUIRK(0x10de, 0x03f0, "Realtek ALC660 demo", ALC660VD_3ST),
+	SND_PCI_QUIRK(0x1179, 0xff00, "Toshiba A135", ALC861VD_LENOVO),
+	/*SND_PCI_QUIRK(0x1179, 0xff00, "DALLAS", ALC861VD_DALLAS),*/ /*lenovo*/
+	SND_PCI_QUIRK(0x1179, 0xff01, "Toshiba A135", ALC861VD_LENOVO),
+	SND_PCI_QUIRK(0x1179, 0xff03, "Toshiba P205", ALC861VD_LENOVO),
+	SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba L30-149", ALC861VD_DALLAS),
+	SND_PCI_QUIRK(0x1565, 0x820d, "Biostar NF61S SE", ALC861VD_6ST_DIG),
+	SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo", ALC861VD_LENOVO),
+	SND_PCI_QUIRK(0x1849, 0x0862, "ASRock K8NF6G-VSTA", ALC861VD_6ST_DIG),
+	{}
+};
+
+static struct alc_config_preset alc861vd_presets[] = {
+	[ALC660VD_3ST] = {
+		.mixers = { alc861vd_3st_mixer },
+		.init_verbs = { alc861vd_volume_init_verbs,
+				 alc861vd_3stack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc660vd_dac_nids),
+		.dac_nids = alc660vd_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes),
+		.channel_mode = alc861vd_3stack_2ch_modes,
+		.input_mux = &alc861vd_capture_source,
+	},
+	[ALC660VD_3ST_DIG] = {
+		.mixers = { alc861vd_3st_mixer },
+		.init_verbs = { alc861vd_volume_init_verbs,
+				 alc861vd_3stack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc660vd_dac_nids),
+		.dac_nids = alc660vd_dac_nids,
+		.dig_out_nid = ALC861VD_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes),
+		.channel_mode = alc861vd_3stack_2ch_modes,
+		.input_mux = &alc861vd_capture_source,
+	},
+	[ALC861VD_3ST] = {
+		.mixers = { alc861vd_3st_mixer },
+		.init_verbs = { alc861vd_volume_init_verbs,
+				 alc861vd_3stack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc861vd_dac_nids),
+		.dac_nids = alc861vd_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes),
+		.channel_mode = alc861vd_3stack_2ch_modes,
+		.input_mux = &alc861vd_capture_source,
+	},
+	[ALC861VD_3ST_DIG] = {
+		.mixers = { alc861vd_3st_mixer },
+		.init_verbs = { alc861vd_volume_init_verbs,
+		 		 alc861vd_3stack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc861vd_dac_nids),
+		.dac_nids = alc861vd_dac_nids,
+		.dig_out_nid = ALC861VD_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes),
+		.channel_mode = alc861vd_3stack_2ch_modes,
+		.input_mux = &alc861vd_capture_source,
+	},
+	[ALC861VD_6ST_DIG] = {
+		.mixers = { alc861vd_6st_mixer, alc861vd_chmode_mixer },
+		.init_verbs = { alc861vd_volume_init_verbs,
+				alc861vd_6stack_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc861vd_dac_nids),
+		.dac_nids = alc861vd_dac_nids,
+		.dig_out_nid = ALC861VD_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc861vd_6stack_modes),
+		.channel_mode = alc861vd_6stack_modes,
+		.input_mux = &alc861vd_capture_source,
+	},
+	[ALC861VD_LENOVO] = {
+		.mixers = { alc861vd_lenovo_mixer },
+		.init_verbs = { alc861vd_volume_init_verbs,
+				alc861vd_3stack_init_verbs,
+				alc861vd_eapd_verbs,
+				alc861vd_lenovo_unsol_verbs },
+		.num_dacs = ARRAY_SIZE(alc660vd_dac_nids),
+		.dac_nids = alc660vd_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes),
+		.channel_mode = alc861vd_3stack_2ch_modes,
+		.input_mux = &alc861vd_capture_source,
+		.unsol_event = alc861vd_lenovo_unsol_event,
+		.setup = alc861vd_lenovo_setup,
+		.init_hook = alc861vd_lenovo_init_hook,
+	},
+	[ALC861VD_DALLAS] = {
+		.mixers = { alc861vd_dallas_mixer },
+		.init_verbs = { alc861vd_dallas_verbs },
+		.num_dacs = ARRAY_SIZE(alc861vd_dac_nids),
+		.dac_nids = alc861vd_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes),
+		.channel_mode = alc861vd_3stack_2ch_modes,
+		.input_mux = &alc861vd_dallas_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc861vd_dallas_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC861VD_HP] = {
+		.mixers = { alc861vd_hp_mixer },
+		.init_verbs = { alc861vd_dallas_verbs, alc861vd_eapd_verbs },
+		.num_dacs = ARRAY_SIZE(alc861vd_dac_nids),
+		.dac_nids = alc861vd_dac_nids,
+		.dig_out_nid = ALC861VD_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes),
+		.channel_mode = alc861vd_3stack_2ch_modes,
+		.input_mux = &alc861vd_hp_capture_source,
+		.unsol_event = alc_automute_amp_unsol_event,
+		.setup = alc861vd_dallas_setup,
+		.init_hook = alc_automute_amp,
+	},
+	[ALC660VD_ASUS_V1S] = {
+		.mixers = { alc861vd_lenovo_mixer },
+		.init_verbs = { alc861vd_volume_init_verbs,
+				alc861vd_3stack_init_verbs,
+				alc861vd_eapd_verbs,
+				alc861vd_lenovo_unsol_verbs },
+		.num_dacs = ARRAY_SIZE(alc660vd_dac_nids),
+		.dac_nids = alc660vd_dac_nids,
+		.dig_out_nid = ALC861VD_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes),
+		.channel_mode = alc861vd_3stack_2ch_modes,
+		.input_mux = &alc861vd_capture_source,
+		.unsol_event = alc861vd_lenovo_unsol_event,
+		.setup = alc861vd_lenovo_setup,
+		.init_hook = alc861vd_lenovo_init_hook,
+	},
+};
+
+/*
+ * BIOS auto configuration
+ */
+static int alc861vd_auto_create_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	return alc_auto_create_input_ctls(codec, cfg, 0x0b, 0x22, 0);
+}
+
+
+static void alc861vd_auto_set_output_and_unmute(struct hda_codec *codec,
+				hda_nid_t nid, int pin_type, int dac_idx)
+{
+	alc_set_pin_output(codec, nid, pin_type);
+}
+
+static void alc861vd_auto_init_multi_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i <= HDA_SIDE; i++) {
+		hda_nid_t nid = spec->autocfg.line_out_pins[i];
+		int pin_type = get_pin_type(spec->autocfg.line_out_type);
+		if (nid)
+			alc861vd_auto_set_output_and_unmute(codec, nid,
+							    pin_type, i);
+	}
+}
+
+
+static void alc861vd_auto_init_hp_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t pin;
+
+	pin = spec->autocfg.hp_pins[0];
+	if (pin) /* connect to front and use dac 0 */
+		alc861vd_auto_set_output_and_unmute(codec, pin, PIN_HP, 0);
+	pin = spec->autocfg.speaker_pins[0];
+	if (pin)
+		alc861vd_auto_set_output_and_unmute(codec, pin, PIN_OUT, 0);
+}
+
+#define ALC861VD_PIN_CD_NID		ALC880_PIN_CD_NID
+
+static void alc861vd_auto_init_analog_input(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		if (alc_is_input_pin(codec, nid)) {
+			alc_set_input_pin(codec, nid, cfg->inputs[i].type);
+			if (nid != ALC861VD_PIN_CD_NID &&
+			    (get_wcaps(codec, nid) & AC_WCAP_OUT_AMP))
+				snd_hda_codec_write(codec, nid, 0,
+						AC_VERB_SET_AMP_GAIN_MUTE,
+						AMP_OUT_MUTE);
+		}
+	}
+}
+
+#define alc861vd_auto_init_input_src	alc882_auto_init_input_src
+
+#define alc861vd_idx_to_mixer_vol(nid)		((nid) + 0x02)
+#define alc861vd_idx_to_mixer_switch(nid)	((nid) + 0x0c)
+
+/* add playback controls from the parsed DAC table */
+/* Based on ALC880 version. But ALC861VD has separate,
+ * different NIDs for mute/unmute switch and volume control */
+static int alc861vd_auto_create_multi_out_ctls(struct alc_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	static const char *chname[4] = {"Front", "Surround", "CLFE", "Side"};
+	hda_nid_t nid_v, nid_s;
+	int i, err;
+
+	for (i = 0; i < cfg->line_outs; i++) {
+		if (!spec->multiout.dac_nids[i])
+			continue;
+		nid_v = alc861vd_idx_to_mixer_vol(
+				alc880_dac_to_idx(
+					spec->multiout.dac_nids[i]));
+		nid_s = alc861vd_idx_to_mixer_switch(
+				alc880_dac_to_idx(
+					spec->multiout.dac_nids[i]));
+
+		if (i == 2) {
+			/* Center/LFE */
+			err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL,
+					      "Center",
+					  HDA_COMPOSE_AMP_VAL(nid_v, 1, 0,
+							      HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL,
+					      "LFE",
+					  HDA_COMPOSE_AMP_VAL(nid_v, 2, 0,
+							      HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = add_pb_sw_ctrl(spec, ALC_CTL_BIND_MUTE,
+					     "Center",
+					  HDA_COMPOSE_AMP_VAL(nid_s, 1, 2,
+							      HDA_INPUT));
+			if (err < 0)
+				return err;
+			err = add_pb_sw_ctrl(spec, ALC_CTL_BIND_MUTE,
+					     "LFE",
+					  HDA_COMPOSE_AMP_VAL(nid_s, 2, 2,
+							      HDA_INPUT));
+			if (err < 0)
+				return err;
+		} else {
+			const char *pfx;
+			if (cfg->line_outs == 1 &&
+			    cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) {
+				if (!cfg->hp_pins)
+					pfx = "Speaker";
+				else
+					pfx = "PCM";
+			} else
+				pfx = chname[i];
+			err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL, pfx,
+					  HDA_COMPOSE_AMP_VAL(nid_v, 3, 0,
+							      HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			if (cfg->line_outs == 1 &&
+			    cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
+				pfx = "Speaker";
+			err = add_pb_sw_ctrl(spec, ALC_CTL_BIND_MUTE, pfx,
+					  HDA_COMPOSE_AMP_VAL(nid_s, 3, 2,
+							      HDA_INPUT));
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+/* add playback controls for speaker and HP outputs */
+/* Based on ALC880 version. But ALC861VD has separate,
+ * different NIDs for mute/unmute switch and volume control */
+static int alc861vd_auto_create_extra_out(struct alc_spec *spec,
+					hda_nid_t pin, const char *pfx)
+{
+	hda_nid_t nid_v, nid_s;
+	int err;
+
+	if (!pin)
+		return 0;
+
+	if (alc880_is_fixed_pin(pin)) {
+		nid_v = alc880_idx_to_dac(alc880_fixed_pin_idx(pin));
+		/* specify the DAC as the extra output */
+		if (!spec->multiout.hp_nid)
+			spec->multiout.hp_nid = nid_v;
+		else
+			spec->multiout.extra_out_nid[0] = nid_v;
+		/* control HP volume/switch on the output mixer amp */
+		nid_v = alc861vd_idx_to_mixer_vol(
+				alc880_fixed_pin_idx(pin));
+		nid_s = alc861vd_idx_to_mixer_switch(
+				alc880_fixed_pin_idx(pin));
+
+		err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL, pfx,
+				  HDA_COMPOSE_AMP_VAL(nid_v, 3, 0, HDA_OUTPUT));
+		if (err < 0)
+			return err;
+		err = add_pb_sw_ctrl(spec, ALC_CTL_BIND_MUTE, pfx,
+				  HDA_COMPOSE_AMP_VAL(nid_s, 3, 2, HDA_INPUT));
+		if (err < 0)
+			return err;
+	} else if (alc880_is_multi_pin(pin)) {
+		/* set manual connection */
+		/* we have only a switch on HP-out PIN */
+		err = add_pb_sw_ctrl(spec, ALC_CTL_WIDGET_MUTE, pfx,
+				  HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+/* parse the BIOS configuration and set up the alc_spec
+ * return 1 if successful, 0 if the proper config is not found,
+ * or a negative error code
+ * Based on ALC880 version - had to change it to override
+ * alc880_auto_create_extra_out and alc880_auto_create_multi_out_ctls */
+static int alc861vd_parse_auto_config(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int err;
+	static hda_nid_t alc861vd_ignore[] = { 0x1d, 0 };
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg,
+					   alc861vd_ignore);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs)
+		return 0; /* can't find valid BIOS pin config */
+
+	err = alc880_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc861vd_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc861vd_auto_create_extra_out(spec,
+					     spec->autocfg.speaker_pins[0],
+					     "Speaker");
+	if (err < 0)
+		return err;
+	err = alc861vd_auto_create_extra_out(spec,
+					     spec->autocfg.hp_pins[0],
+					     "Headphone");
+	if (err < 0)
+		return err;
+	err = alc861vd_auto_create_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	alc_auto_parse_digital(codec);
+
+	if (spec->kctls.list)
+		add_mixer(spec, spec->kctls.list);
+
+	add_verb(spec, alc861vd_volume_init_verbs);
+
+	spec->num_mux_defs = 1;
+	spec->input_mux = &spec->private_imux[0];
+
+	err = alc_auto_add_mic_boost(codec);
+	if (err < 0)
+		return err;
+
+	alc_ssid_check(codec, 0x15, 0x1b, 0x14, 0);
+
+	return 1;
+}
+
+/* additional initialization for auto-configuration model */
+static void alc861vd_auto_init(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc861vd_auto_init_multi_out(codec);
+	alc861vd_auto_init_hp_out(codec);
+	alc861vd_auto_init_analog_input(codec);
+	alc861vd_auto_init_input_src(codec);
+	alc_auto_init_digital(codec);
+	if (spec->unsol_event)
+		alc_inithook(codec);
+}
+
+enum {
+	ALC660VD_FIX_ASUS_GPIO1
+};
+
+/* reset GPIO1 */
+static const struct alc_fixup alc861vd_fixups[] = {
+	[ALC660VD_FIX_ASUS_GPIO1] = {
+		.verbs = (const struct hda_verb[]) {
+			{0x01, AC_VERB_SET_GPIO_MASK, 0x03},
+			{0x01, AC_VERB_SET_GPIO_DIRECTION, 0x01},
+			{0x01, AC_VERB_SET_GPIO_DATA, 0x01},
+			{ }
+		}
+	},
+};
+
+static struct snd_pci_quirk alc861vd_fixup_tbl[] = {
+	SND_PCI_QUIRK(0x1043, 0x1339, "ASUS A7-K", ALC660VD_FIX_ASUS_GPIO1),
+	{}
+};
+
+static int patch_alc861vd(struct hda_codec *codec)
+{
+	struct alc_spec *spec;
+	int err, board_config;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	board_config = snd_hda_check_board_config(codec, ALC861VD_MODEL_LAST,
+						  alc861vd_models,
+						  alc861vd_cfg_tbl);
+
+	if (board_config < 0 || board_config >= ALC861VD_MODEL_LAST) {
+		printk(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+		       codec->chip_name);
+		board_config = ALC861VD_AUTO;
+	}
+
+	if (board_config == ALC861VD_AUTO)
+		alc_pick_fixup(codec, alc861vd_fixup_tbl, alc861vd_fixups, 1);
+
+	if (board_config == ALC861VD_AUTO) {
+		/* automatic parse from the BIOS config */
+		err = alc861vd_parse_auto_config(codec);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		} else if (!err) {
+			printk(KERN_INFO
+			       "hda_codec: Cannot set up configuration "
+			       "from BIOS.  Using base mode...\n");
+			board_config = ALC861VD_3ST;
+		}
+	}
+
+	err = snd_hda_attach_beep_device(codec, 0x23);
+	if (err < 0) {
+		alc_free(codec);
+		return err;
+	}
+
+	if (board_config != ALC861VD_AUTO)
+		setup_preset(codec, &alc861vd_presets[board_config]);
+
+	if (codec->vendor_id == 0x10ec0660) {
+		/* always turn on EAPD */
+		add_verb(spec, alc660vd_eapd_verbs);
+	}
+
+	spec->stream_analog_playback = &alc861vd_pcm_analog_playback;
+	spec->stream_analog_capture = &alc861vd_pcm_analog_capture;
+
+	spec->stream_digital_playback = &alc861vd_pcm_digital_playback;
+	spec->stream_digital_capture = &alc861vd_pcm_digital_capture;
+
+	if (!spec->adc_nids) {
+		spec->adc_nids = alc861vd_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(alc861vd_adc_nids);
+	}
+	if (!spec->capsrc_nids)
+		spec->capsrc_nids = alc861vd_capsrc_nids;
+
+	set_capture_mixer(codec);
+	set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT);
+
+	spec->vmaster_nid = 0x02;
+
+	if (board_config == ALC861VD_AUTO)
+		alc_pick_fixup(codec, alc861vd_fixup_tbl, alc861vd_fixups, 0);
+
+	codec->patch_ops = alc_patch_ops;
+
+	if (board_config == ALC861VD_AUTO)
+		spec->init_hook = alc861vd_auto_init;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	if (!spec->loopback.amplist)
+		spec->loopback.amplist = alc861vd_loopbacks;
+#endif
+
+	return 0;
+}
+
+/*
+ * ALC662 support
+ *
+ * ALC662 is almost identical with ALC880 but has cleaner and more flexible
+ * configuration.  Each pin widget can choose any input DACs and a mixer.
+ * Each ADC is connected from a mixer of all inputs.  This makes possible
+ * 6-channel independent captures.
+ *
+ * In addition, an independent DAC for the multi-playback (not used in this
+ * driver yet).
+ */
+#define ALC662_DIGOUT_NID	0x06
+#define ALC662_DIGIN_NID	0x0a
+
+static hda_nid_t alc662_dac_nids[4] = {
+	/* front, rear, clfe, rear_surr */
+	0x02, 0x03, 0x04
+};
+
+static hda_nid_t alc272_dac_nids[2] = {
+	0x02, 0x03
+};
+
+static hda_nid_t alc662_adc_nids[2] = {
+	/* ADC1-2 */
+	0x09, 0x08
+};
+
+static hda_nid_t alc272_adc_nids[1] = {
+	/* ADC1-2 */
+	0x08,
+};
+
+static hda_nid_t alc662_capsrc_nids[2] = { 0x22, 0x23 };
+static hda_nid_t alc272_capsrc_nids[1] = { 0x23 };
+
+
+/* input MUX */
+/* FIXME: should be a matrix-type input source selection */
+static struct hda_input_mux alc662_capture_source = {
+	.num_items = 4,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x1 },
+		{ "Line", 0x2 },
+		{ "CD", 0x4 },
+	},
+};
+
+static struct hda_input_mux alc662_lenovo_101e_capture_source = {
+	.num_items = 2,
+	.items = {
+		{ "Mic", 0x1 },
+		{ "Line", 0x2 },
+	},
+};
+
+static struct hda_input_mux alc663_capture_source = {
+	.num_items = 3,
+	.items = {
+		{ "Mic", 0x0 },
+		{ "Front Mic", 0x1 },
+		{ "Line", 0x2 },
+	},
+};
+
+#if 0 /* set to 1 for testing other input sources below */
+static struct hda_input_mux alc272_nc10_capture_source = {
+	.num_items = 16,
+	.items = {
+		{ "Autoselect Mic", 0x0 },
+		{ "Internal Mic", 0x1 },
+		{ "In-0x02", 0x2 },
+		{ "In-0x03", 0x3 },
+		{ "In-0x04", 0x4 },
+		{ "In-0x05", 0x5 },
+		{ "In-0x06", 0x6 },
+		{ "In-0x07", 0x7 },
+		{ "In-0x08", 0x8 },
+		{ "In-0x09", 0x9 },
+		{ "In-0x0a", 0x0a },
+		{ "In-0x0b", 0x0b },
+		{ "In-0x0c", 0x0c },
+		{ "In-0x0d", 0x0d },
+		{ "In-0x0e", 0x0e },
+		{ "In-0x0f", 0x0f },
+	},
+};
+#endif
+
+/*
+ * 2ch mode
+ */
+static struct hda_channel_mode alc662_3ST_2ch_modes[1] = {
+	{ 2, NULL }
+};
+
+/*
+ * 2ch mode
+ */
+static struct hda_verb alc662_3ST_ch2_init[] = {
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{ } /* end */
+};
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc662_3ST_ch6_init[] = {
+	{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x18, AC_VERB_SET_CONNECT_SEL, 0x02 },
+	{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE },
+	{ 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc662_3ST_6ch_modes[2] = {
+	{ 2, alc662_3ST_ch2_init },
+	{ 6, alc662_3ST_ch6_init },
+};
+
+/*
+ * 2ch mode
+ */
+static struct hda_verb alc662_sixstack_ch6_init[] = {
+	{ 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 },
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 },
+	{ 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ } /* end */
+};
+
+/*
+ * 6ch mode
+ */
+static struct hda_verb alc662_sixstack_ch8_init[] = {
+	{ 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },
+	{ } /* end */
+};
+
+static struct hda_channel_mode alc662_5stack_modes[2] = {
+	{ 2, alc662_sixstack_ch6_init },
+	{ 6, alc662_sixstack_ch8_init },
+};
+
+/* Pin assignment: Front=0x14, Rear=0x15, CLFE=0x16, Side=0x17
+ *                 Mic=0x18, Front Mic=0x19, Line-In=0x1a, HP=0x1b
+ */
+
+static struct snd_kcontrol_new alc662_base_mixer[] = {
+	/* output mixer control */
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x0c, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x3, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Surround Playback Switch", 0x0d, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x04, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x04, 2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x0e, 1, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+
+	/*Input mixer control */
+	HDA_CODEC_VOLUME("CD Playback Volume", 0xb, 0x4, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0xb, 0x4, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0xb, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0xb, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0xb, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0xb, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0xb, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0xb, 0x01, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc662_3ST_2ch_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x0c, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc662_3ST_6ch_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x0c, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Surround Playback Switch", 0x0d, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x04, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x04, 2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x0e, 1, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc662_lenovo_101e_mixer[] = {
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Front Playback Switch", 0x02, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("Speaker Playback Switch", 0x03, 2, HDA_INPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc662_eeepc_p701_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	ALC262_HIPPO_MASTER_SWITCH,
+
+	HDA_CODEC_VOLUME("e-Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("e-Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("e-Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("i-Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("i-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("i-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc662_eeepc_ep20_mixer[] = {
+	ALC262_HIPPO_MASTER_SWITCH,
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Surround Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x04, 1, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x04, 2, 0x0, HDA_OUTPUT),
+	HDA_BIND_MUTE("MuteCtrl Playback Switch", 0x0c, 2, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_bind_ctls alc663_asus_bind_master_vol = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x03, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+static struct hda_bind_ctls alc663_asus_one_bind_switch = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x21, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+static struct snd_kcontrol_new alc663_m51va_mixer[] = {
+	HDA_BIND_VOL("Master Playback Volume", &alc663_asus_bind_master_vol),
+	HDA_BIND_SW("Master Playback Switch", &alc663_asus_one_bind_switch),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_bind_ctls alc663_asus_tree_bind_switch = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x15, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x21, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+static struct snd_kcontrol_new alc663_two_hp_m1_mixer[] = {
+	HDA_BIND_VOL("Master Playback Volume", &alc663_asus_bind_master_vol),
+	HDA_BIND_SW("Master Playback Switch", &alc663_asus_tree_bind_switch),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("F-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("F-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+
+	{ } /* end */
+};
+
+static struct hda_bind_ctls alc663_asus_four_bind_switch = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x15, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+static struct snd_kcontrol_new alc663_two_hp_m2_mixer[] = {
+	HDA_BIND_VOL("Master Playback Volume", &alc663_asus_bind_master_vol),
+	HDA_BIND_SW("Master Playback Switch", &alc663_asus_four_bind_switch),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("F-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("F-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc662_1bjd_mixer[] = {
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("F-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("F-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_bind_ctls alc663_asus_two_bind_master_vol = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x04, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+static struct hda_bind_ctls alc663_asus_two_bind_switch = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x16, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+static struct snd_kcontrol_new alc663_asus_21jd_clfe_mixer[] = {
+	HDA_BIND_VOL("Master Playback Volume",
+				&alc663_asus_two_bind_master_vol),
+	HDA_BIND_SW("Master Playback Switch", &alc663_asus_two_bind_switch),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc663_asus_15jd_clfe_mixer[] = {
+	HDA_BIND_VOL("Master Playback Volume", &alc663_asus_bind_master_vol),
+	HDA_BIND_SW("Master Playback Switch", &alc663_asus_two_bind_switch),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc663_g71v_mixer[] = {
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("i-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("i-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc663_g50v_mixer[] = {
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("i-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("i-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+	HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct hda_bind_ctls alc663_asus_mode7_8_all_bind_switch = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x15, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x17, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x21, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+static struct hda_bind_ctls alc663_asus_mode7_8_sp_bind_switch = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+		HDA_COMPOSE_AMP_VAL(0x17, 3, 0, HDA_OUTPUT),
+		0
+	},
+};
+
+static struct snd_kcontrol_new alc663_mode7_mixer[] = {
+	HDA_BIND_SW("Master Playback Switch", &alc663_asus_mode7_8_all_bind_switch),
+	HDA_BIND_VOL("Speaker Playback Volume", &alc663_asus_bind_master_vol),
+	HDA_BIND_SW("Speaker Playback Switch", &alc663_asus_mode7_8_sp_bind_switch),
+	HDA_CODEC_MUTE("Headphone1 Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone2 Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("IntMic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("IntMic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc663_mode8_mixer[] = {
+	HDA_BIND_SW("Master Playback Switch", &alc663_asus_mode7_8_all_bind_switch),
+	HDA_BIND_VOL("Speaker Playback Volume", &alc663_asus_bind_master_vol),
+	HDA_BIND_SW("Speaker Playback Switch", &alc663_asus_mode7_8_sp_bind_switch),
+	HDA_CODEC_MUTE("Headphone1 Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone2 Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+
+static struct snd_kcontrol_new alc662_chmode_mixer[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Channel Mode",
+		.info = alc_ch_mode_info,
+		.get = alc_ch_mode_get,
+		.put = alc_ch_mode_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb alc662_init_verbs[] = {
+	/* ADC: mute amp left and right */
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x09, AC_VERB_SET_CONNECT_SEL, 0x00},
+
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* Front Pin: output 0 (0x0c) */
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Rear Pin: output 1 (0x0d) */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* CLFE Pin: output 2 (0x0e) */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	/* Mic (rear) pin: input vref at 80% */
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Front Mic pin: input vref at 80% */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line In pin: input */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	/* Line-2 In: Headphone output (output 0 - 0x0c) */
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* CD pin widget for input */
+	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	/* FIXME: use matrix-type input source selection */
+	/* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */
+	/* Input mixer */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* always trun on EAPD */
+	{0x14, AC_VERB_SET_EAPD_BTLENABLE, 2},
+	{0x15, AC_VERB_SET_EAPD_BTLENABLE, 2},
+
+	{ }
+};
+
+static struct hda_verb alc663_init_verbs[] = {
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{ }
+};
+
+static struct hda_verb alc272_init_verbs[] = {
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{ }
+};
+
+static struct hda_verb alc662_sue_init_verbs[] = {
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_FRONT_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc662_eeepc_sue_init_verbs[] = {
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+/* Set Unsolicited Event*/
+static struct hda_verb alc662_eeepc_ep20_sue_init_verbs[] = {
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc663_m51va_init_verbs[] = {
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x01},	/* Headphone */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(9)},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc663_21jd_amic_init_verbs[] = {
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x01},	/* Headphone */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc662_1bjd_amic_init_verbs[] = {
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},	/* Headphone */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc663_15jd_amic_init_verbs[] = {
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},	/* Headphone */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc663_two_hp_amic_m1_init_verbs[] = {
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x0},	/* Headphone */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x0},	/* Headphone */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc663_two_hp_amic_m2_init_verbs[] = {
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x01},	/* Headphone */
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},	/* Headphone */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc663_g71v_init_verbs[] = {
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	/* {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, */
+	/* {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, */ /* Headphone */
+
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x00},	/* Headphone */
+
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_FRONT_EVENT},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_MIC_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc663_g50v_init_verbs[] = {
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x00},	/* Headphone */
+
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc662_ecs_init_verbs[] = {
+	{0x09, AC_VERB_SET_AMP_GAIN_MUTE, 0x701f},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc272_dell_zm1_init_verbs[] = {
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x01},	/* Headphone */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(9)},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc272_dell_init_verbs[] = {
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x01},	/* Headphone */
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(9)},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc663_mode7_init_verbs[] = {
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x01},	/* Headphone */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(9)},
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct hda_verb alc663_mode8_init_verbs[] = {
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x21, AC_VERB_SET_CONNECT_SEL, 0x01},	/* Headphone */
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(9)},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT},
+	{0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT},
+	{}
+};
+
+static struct snd_kcontrol_new alc662_auto_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x09, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x09, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc272_auto_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT),
+	{ } /* end */
+};
+
+static void alc662_lenovo_101e_ispeaker_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, 0x14);
+	bits = present ? HDA_AMP_MUTE : 0;
+
+	snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+}
+
+static void alc662_lenovo_101e_all_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+ 	present = snd_hda_jack_detect(codec, 0x1b);
+	bits = present ? HDA_AMP_MUTE : 0;
+
+	snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+}
+
+static void alc662_lenovo_101e_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	if ((res >> 26) == ALC880_HP_EVENT)
+		alc662_lenovo_101e_all_automute(codec);
+	if ((res >> 26) == ALC880_FRONT_EVENT)
+		alc662_lenovo_101e_ispeaker_automute(codec);
+}
+
+/* unsolicited event for HP jack sensing */
+static void alc662_eeepc_unsol_event(struct hda_codec *codec,
+				     unsigned int res)
+{
+	if ((res >> 26) == ALC880_MIC_EVENT)
+		alc_mic_automute(codec);
+	else
+		alc262_hippo_unsol_event(codec, res);
+}
+
+static void alc662_eeepc_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	alc262_hippo1_setup(codec);
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x19;
+	spec->int_mic.mux_idx = 1;
+	spec->auto_mic = 1;
+}
+
+static void alc662_eeepc_inithook(struct hda_codec *codec)
+{
+	alc262_hippo_automute(codec);
+	alc_mic_automute(codec);
+}
+
+static void alc662_eeepc_ep20_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[0] = 0x1b;
+}
+
+#define alc662_eeepc_ep20_inithook	alc262_hippo_master_update
+
+static void alc663_m51va_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, 0x21);
+	bits = present ? HDA_AMP_MUTE : 0;
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1,
+				 HDA_AMP_MUTE, bits);
+}
+
+static void alc663_21jd_two_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, 0x21);
+	bits = present ? HDA_AMP_MUTE : 0;
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x0e, HDA_INPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x0e, HDA_INPUT, 1,
+				 HDA_AMP_MUTE, bits);
+}
+
+static void alc663_15jd_two_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, 0x15);
+	bits = present ? HDA_AMP_MUTE : 0;
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x0e, HDA_INPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x0e, HDA_INPUT, 1,
+				 HDA_AMP_MUTE, bits);
+}
+
+static void alc662_f5z_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, 0x1b);
+	bits = present ? 0 : PIN_OUT;
+	snd_hda_codec_write(codec, 0x14, 0,
+			 AC_VERB_SET_PIN_WIDGET_CONTROL, bits);
+}
+
+static void alc663_two_hp_m1_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int present1, present2;
+
+	present1 = snd_hda_jack_detect(codec, 0x21);
+	present2 = snd_hda_jack_detect(codec, 0x15);
+
+	if (present1 || present2) {
+		snd_hda_codec_write_cache(codec, 0x14, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+	} else {
+		snd_hda_codec_write_cache(codec, 0x14, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+	}
+}
+
+static void alc663_two_hp_m2_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int present1, present2;
+
+	present1 = snd_hda_jack_detect(codec, 0x1b);
+	present2 = snd_hda_jack_detect(codec, 0x15);
+
+	if (present1 || present2) {
+		snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0,
+					 HDA_AMP_MUTE, HDA_AMP_MUTE);
+		snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1,
+					 HDA_AMP_MUTE, HDA_AMP_MUTE);
+	} else {
+		snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0,
+					 HDA_AMP_MUTE, 0);
+		snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1,
+					 HDA_AMP_MUTE, 0);
+	}
+}
+
+static void alc663_two_hp_m7_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int present1, present2;
+
+	present1 = snd_hda_codec_read(codec, 0x1b, 0,
+			AC_VERB_GET_PIN_SENSE, 0)
+			& AC_PINSENSE_PRESENCE;
+	present2 = snd_hda_codec_read(codec, 0x21, 0,
+			AC_VERB_GET_PIN_SENSE, 0)
+			& AC_PINSENSE_PRESENCE;
+
+	if (present1 || present2) {
+		snd_hda_codec_write_cache(codec, 0x14, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+		snd_hda_codec_write_cache(codec, 0x17, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+	} else {
+		snd_hda_codec_write_cache(codec, 0x14, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+		snd_hda_codec_write_cache(codec, 0x17, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+	}
+}
+
+static void alc663_two_hp_m8_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int present1, present2;
+
+	present1 = snd_hda_codec_read(codec, 0x21, 0,
+			AC_VERB_GET_PIN_SENSE, 0)
+			& AC_PINSENSE_PRESENCE;
+	present2 = snd_hda_codec_read(codec, 0x15, 0,
+			AC_VERB_GET_PIN_SENSE, 0)
+			& AC_PINSENSE_PRESENCE;
+
+	if (present1 || present2) {
+		snd_hda_codec_write_cache(codec, 0x14, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+		snd_hda_codec_write_cache(codec, 0x17, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+	} else {
+		snd_hda_codec_write_cache(codec, 0x14, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+		snd_hda_codec_write_cache(codec, 0x17, 0,
+			AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+	}
+}
+
+static void alc663_m51va_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc663_m51va_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+static void alc663_m51va_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x12;
+	spec->int_mic.mux_idx = 9;
+	spec->auto_mic = 1;
+}
+
+static void alc663_m51va_inithook(struct hda_codec *codec)
+{
+	alc663_m51va_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+
+/* ***************** Mode1 ******************************/
+#define alc663_mode1_unsol_event	alc663_m51va_unsol_event
+
+static void alc663_mode1_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	spec->ext_mic.pin = 0x18;
+	spec->ext_mic.mux_idx = 0;
+	spec->int_mic.pin = 0x19;
+	spec->int_mic.mux_idx = 1;
+	spec->auto_mic = 1;
+}
+
+#define alc663_mode1_inithook		alc663_m51va_inithook
+
+/* ***************** Mode2 ******************************/
+static void alc662_mode2_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc662_f5z_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+#define alc662_mode2_setup	alc663_mode1_setup
+
+static void alc662_mode2_inithook(struct hda_codec *codec)
+{
+	alc662_f5z_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+/* ***************** Mode3 ******************************/
+static void alc663_mode3_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc663_two_hp_m1_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+#define alc663_mode3_setup	alc663_mode1_setup
+
+static void alc663_mode3_inithook(struct hda_codec *codec)
+{
+	alc663_two_hp_m1_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+/* ***************** Mode4 ******************************/
+static void alc663_mode4_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc663_21jd_two_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+#define alc663_mode4_setup	alc663_mode1_setup
+
+static void alc663_mode4_inithook(struct hda_codec *codec)
+{
+	alc663_21jd_two_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+/* ***************** Mode5 ******************************/
+static void alc663_mode5_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc663_15jd_two_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+#define alc663_mode5_setup	alc663_mode1_setup
+
+static void alc663_mode5_inithook(struct hda_codec *codec)
+{
+	alc663_15jd_two_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+/* ***************** Mode6 ******************************/
+static void alc663_mode6_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc663_two_hp_m2_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+#define alc663_mode6_setup	alc663_mode1_setup
+
+static void alc663_mode6_inithook(struct hda_codec *codec)
+{
+	alc663_two_hp_m2_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+
+/* ***************** Mode7 ******************************/
+static void alc663_mode7_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc663_two_hp_m7_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+#define alc663_mode7_setup	alc663_mode1_setup
+
+static void alc663_mode7_inithook(struct hda_codec *codec)
+{
+	alc663_two_hp_m7_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+
+/* ***************** Mode8 ******************************/
+static void alc663_mode8_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc663_two_hp_m8_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+#define alc663_mode8_setup	alc663_m51va_setup
+
+static void alc663_mode8_inithook(struct hda_codec *codec)
+{
+	alc663_two_hp_m8_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+
+static void alc663_g71v_hp_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, 0x21);
+	bits = present ? HDA_AMP_MUTE : 0;
+	snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+	snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+}
+
+static void alc663_g71v_front_automute(struct hda_codec *codec)
+{
+	unsigned int present;
+	unsigned char bits;
+
+	present = snd_hda_jack_detect(codec, 0x15);
+	bits = present ? HDA_AMP_MUTE : 0;
+	snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0,
+				 HDA_AMP_MUTE, bits);
+}
+
+static void alc663_g71v_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc663_g71v_hp_automute(codec);
+		break;
+	case ALC880_FRONT_EVENT:
+		alc663_g71v_front_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+#define alc663_g71v_setup	alc663_m51va_setup
+
+static void alc663_g71v_inithook(struct hda_codec *codec)
+{
+	alc663_g71v_front_automute(codec);
+	alc663_g71v_hp_automute(codec);
+	alc_mic_automute(codec);
+}
+
+static void alc663_g50v_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	switch (res >> 26) {
+	case ALC880_HP_EVENT:
+		alc663_m51va_speaker_automute(codec);
+		break;
+	case ALC880_MIC_EVENT:
+		alc_mic_automute(codec);
+		break;
+	}
+}
+
+#define alc663_g50v_setup	alc663_m51va_setup
+
+static void alc663_g50v_inithook(struct hda_codec *codec)
+{
+	alc663_m51va_speaker_automute(codec);
+	alc_mic_automute(codec);
+}
+
+static struct snd_kcontrol_new alc662_ecs_mixer[] = {
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	ALC262_HIPPO_MASTER_SWITCH,
+
+	HDA_CODEC_VOLUME("e-Mic/LineIn Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("e-Mic/LineIn Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("e-Mic/LineIn Playback Switch", 0x0b, 0x0, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("i-Mic Boost", 0x19, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("i-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("i-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new alc272_nc10_mixer[] = {
+	/* Master Playback automatically created from Speaker and Headphone */
+	HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x21, 0x0, HDA_OUTPUT),
+
+	HDA_CODEC_VOLUME("Ext Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Ext Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Ext Mic Boost", 0x18, 0, HDA_INPUT),
+
+	HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+	HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT),
+	{ } /* end */
+};
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+#define alc662_loopbacks	alc880_loopbacks
+#endif
+
+
+/* pcm configuration: identical with ALC880 */
+#define alc662_pcm_analog_playback	alc880_pcm_analog_playback
+#define alc662_pcm_analog_capture	alc880_pcm_analog_capture
+#define alc662_pcm_digital_playback	alc880_pcm_digital_playback
+#define alc662_pcm_digital_capture	alc880_pcm_digital_capture
+
+/*
+ * configuration and preset
+ */
+static const char *alc662_models[ALC662_MODEL_LAST] = {
+	[ALC662_3ST_2ch_DIG]	= "3stack-dig",
+	[ALC662_3ST_6ch_DIG]	= "3stack-6ch-dig",
+	[ALC662_3ST_6ch]	= "3stack-6ch",
+	[ALC662_5ST_DIG]	= "6stack-dig",
+	[ALC662_LENOVO_101E]	= "lenovo-101e",
+	[ALC662_ASUS_EEEPC_P701] = "eeepc-p701",
+	[ALC662_ASUS_EEEPC_EP20] = "eeepc-ep20",
+	[ALC662_ECS] = "ecs",
+	[ALC663_ASUS_M51VA] = "m51va",
+	[ALC663_ASUS_G71V] = "g71v",
+	[ALC663_ASUS_H13] = "h13",
+	[ALC663_ASUS_G50V] = "g50v",
+	[ALC663_ASUS_MODE1] = "asus-mode1",
+	[ALC662_ASUS_MODE2] = "asus-mode2",
+	[ALC663_ASUS_MODE3] = "asus-mode3",
+	[ALC663_ASUS_MODE4] = "asus-mode4",
+	[ALC663_ASUS_MODE5] = "asus-mode5",
+	[ALC663_ASUS_MODE6] = "asus-mode6",
+	[ALC663_ASUS_MODE7] = "asus-mode7",
+	[ALC663_ASUS_MODE8] = "asus-mode8",
+	[ALC272_DELL]		= "dell",
+	[ALC272_DELL_ZM1]	= "dell-zm1",
+	[ALC272_SAMSUNG_NC10]	= "samsung-nc10",
+	[ALC662_AUTO]		= "auto",
+};
+
+static struct snd_pci_quirk alc662_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1019, 0x9087, "ECS", ALC662_ECS),
+	SND_PCI_QUIRK(0x1028, 0x02d6, "DELL", ALC272_DELL),
+	SND_PCI_QUIRK(0x1028, 0x02f4, "DELL ZM1", ALC272_DELL_ZM1),
+	SND_PCI_QUIRK(0x1043, 0x1000, "ASUS N50Vm", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x1092, "ASUS NB", ALC663_ASUS_MODE3),
+	SND_PCI_QUIRK(0x1043, 0x1173, "ASUS K73Jn", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x11c3, "ASUS M70V", ALC663_ASUS_MODE3),
+	SND_PCI_QUIRK(0x1043, 0x11d3, "ASUS NB", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x11f3, "ASUS NB", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x1203, "ASUS NB", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x1303, "ASUS G60J", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x1333, "ASUS G60Jx", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x1339, "ASUS NB", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x13e3, "ASUS N71JA", ALC663_ASUS_MODE7),
+	SND_PCI_QUIRK(0x1043, 0x1463, "ASUS N71", ALC663_ASUS_MODE7),
+	SND_PCI_QUIRK(0x1043, 0x14d3, "ASUS G72", ALC663_ASUS_MODE8),
+	SND_PCI_QUIRK(0x1043, 0x1563, "ASUS N90", ALC663_ASUS_MODE3),
+	SND_PCI_QUIRK(0x1043, 0x15d3, "ASUS N50SF F50SF", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x16c3, "ASUS NB", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x16f3, "ASUS K40C K50C", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x1733, "ASUS N81De", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x1753, "ASUS NB", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x1763, "ASUS NB", ALC663_ASUS_MODE6),
+	SND_PCI_QUIRK(0x1043, 0x1765, "ASUS NB", ALC663_ASUS_MODE6),
+	SND_PCI_QUIRK(0x1043, 0x1783, "ASUS NB", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x1793, "ASUS F50GX", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x17b3, "ASUS F70SL", ALC663_ASUS_MODE3),
+	SND_PCI_QUIRK(0x1043, 0x17c3, "ASUS UX20", ALC663_ASUS_M51VA),
+	SND_PCI_QUIRK(0x1043, 0x17f3, "ASUS X58LE", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x1813, "ASUS NB", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x1823, "ASUS NB", ALC663_ASUS_MODE5),
+	SND_PCI_QUIRK(0x1043, 0x1833, "ASUS NB", ALC663_ASUS_MODE6),
+	SND_PCI_QUIRK(0x1043, 0x1843, "ASUS NB", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x1853, "ASUS F50Z", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x1864, "ASUS NB", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x1876, "ASUS NB", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x1878, "ASUS M51VA", ALC663_ASUS_M51VA),
+	/*SND_PCI_QUIRK(0x1043, 0x1878, "ASUS M50Vr", ALC663_ASUS_MODE1),*/
+	SND_PCI_QUIRK(0x1043, 0x1893, "ASUS M50Vm", ALC663_ASUS_MODE3),
+	SND_PCI_QUIRK(0x1043, 0x1894, "ASUS X55", ALC663_ASUS_MODE3),
+	SND_PCI_QUIRK(0x1043, 0x18b3, "ASUS N80Vc", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x18c3, "ASUS VX5", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS N81Te", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x18f3, "ASUS N505Tp", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x1903, "ASUS F5GL", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x1913, "ASUS NB", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x1933, "ASUS F80Q", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x1943, "ASUS Vx3V", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x1953, "ASUS NB", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x1963, "ASUS X71C", ALC663_ASUS_MODE3),
+	SND_PCI_QUIRK(0x1043, 0x1983, "ASUS N5051A", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x1993, "ASUS N20", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x19a3, "ASUS G50V", ALC663_ASUS_G50V),
+	/*SND_PCI_QUIRK(0x1043, 0x19a3, "ASUS NB", ALC663_ASUS_MODE1),*/
+	SND_PCI_QUIRK(0x1043, 0x19b3, "ASUS F7Z", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x19c3, "ASUS F5Z/F6x", ALC662_ASUS_MODE2),
+	SND_PCI_QUIRK(0x1043, 0x19d3, "ASUS NB", ALC663_ASUS_M51VA),
+	SND_PCI_QUIRK(0x1043, 0x19e3, "ASUS NB", ALC663_ASUS_MODE1),
+	SND_PCI_QUIRK(0x1043, 0x19f3, "ASUS NB", ALC663_ASUS_MODE4),
+	SND_PCI_QUIRK(0x1043, 0x8290, "ASUS P5GC-MX", ALC662_3ST_6ch_DIG),
+	SND_PCI_QUIRK(0x1043, 0x82a1, "ASUS Eeepc", ALC662_ASUS_EEEPC_P701),
+	SND_PCI_QUIRK(0x1043, 0x82d1, "ASUS Eeepc EP20", ALC662_ASUS_EEEPC_EP20),
+	SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_ECS),
+	SND_PCI_QUIRK(0x105b, 0x0d47, "Foxconn 45CMX/45GMX/45CMX-K",
+		      ALC662_3ST_6ch_DIG),
+	SND_PCI_QUIRK(0x1179, 0xff6e, "Toshiba NB20x", ALC662_AUTO),
+	SND_PCI_QUIRK(0x144d, 0xca00, "Samsung NC10", ALC272_SAMSUNG_NC10),
+	SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte 945GCM-S2L",
+		      ALC662_3ST_6ch_DIG),
+	SND_PCI_QUIRK(0x152d, 0x2304, "Quanta WH1", ALC663_ASUS_H13),
+	SND_PCI_QUIRK(0x1565, 0x820f, "Biostar TA780G M2+", ALC662_3ST_6ch_DIG),
+	SND_PCI_QUIRK(0x1631, 0xc10c, "PB RS65", ALC663_ASUS_M51VA),
+	SND_PCI_QUIRK(0x17aa, 0x101e, "Lenovo", ALC662_LENOVO_101E),
+	SND_PCI_QUIRK(0x1849, 0x3662, "ASROCK K10N78FullHD-hSLI R3.0",
+					ALC662_3ST_6ch_DIG),
+	SND_PCI_QUIRK_MASK(0x1854, 0xf000, 0x2000, "ASUS H13-200x",
+			   ALC663_ASUS_H13),
+	{}
+};
+
+static struct alc_config_preset alc662_presets[] = {
+	[ALC662_3ST_2ch_DIG] = {
+		.mixers = { alc662_3ST_2ch_mixer },
+		.init_verbs = { alc662_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.dig_in_nid = ALC662_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.input_mux = &alc662_capture_source,
+	},
+	[ALC662_3ST_6ch_DIG] = {
+		.mixers = { alc662_3ST_6ch_mixer, alc662_chmode_mixer },
+		.init_verbs = { alc662_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.dig_in_nid = ALC662_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_6ch_modes),
+		.channel_mode = alc662_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc662_capture_source,
+	},
+	[ALC662_3ST_6ch] = {
+		.mixers = { alc662_3ST_6ch_mixer, alc662_chmode_mixer },
+		.init_verbs = { alc662_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_6ch_modes),
+		.channel_mode = alc662_3ST_6ch_modes,
+		.need_dac_fix = 1,
+		.input_mux = &alc662_capture_source,
+	},
+	[ALC662_5ST_DIG] = {
+		.mixers = { alc662_base_mixer, alc662_chmode_mixer },
+		.init_verbs = { alc662_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.dig_in_nid = ALC662_DIGIN_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_5stack_modes),
+		.channel_mode = alc662_5stack_modes,
+		.input_mux = &alc662_capture_source,
+	},
+	[ALC662_LENOVO_101E] = {
+		.mixers = { alc662_lenovo_101e_mixer },
+		.init_verbs = { alc662_init_verbs, alc662_sue_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.input_mux = &alc662_lenovo_101e_capture_source,
+		.unsol_event = alc662_lenovo_101e_unsol_event,
+		.init_hook = alc662_lenovo_101e_all_automute,
+	},
+	[ALC662_ASUS_EEEPC_P701] = {
+		.mixers = { alc662_eeepc_p701_mixer },
+		.init_verbs = { alc662_init_verbs,
+				alc662_eeepc_sue_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc662_eeepc_unsol_event,
+		.setup = alc662_eeepc_setup,
+		.init_hook = alc662_eeepc_inithook,
+	},
+	[ALC662_ASUS_EEEPC_EP20] = {
+		.mixers = { alc662_eeepc_ep20_mixer,
+			    alc662_chmode_mixer },
+		.init_verbs = { alc662_init_verbs,
+				alc662_eeepc_ep20_sue_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_6ch_modes),
+		.channel_mode = alc662_3ST_6ch_modes,
+		.input_mux = &alc662_lenovo_101e_capture_source,
+		.unsol_event = alc662_eeepc_unsol_event,
+		.setup = alc662_eeepc_ep20_setup,
+		.init_hook = alc662_eeepc_ep20_inithook,
+	},
+	[ALC662_ECS] = {
+		.mixers = { alc662_ecs_mixer },
+		.init_verbs = { alc662_init_verbs,
+				alc662_ecs_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc662_eeepc_unsol_event,
+		.setup = alc662_eeepc_setup,
+		.init_hook = alc662_eeepc_inithook,
+	},
+	[ALC663_ASUS_M51VA] = {
+		.mixers = { alc663_m51va_mixer },
+		.init_verbs = { alc662_init_verbs, alc663_m51va_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_m51va_unsol_event,
+		.setup = alc663_m51va_setup,
+		.init_hook = alc663_m51va_inithook,
+	},
+	[ALC663_ASUS_G71V] = {
+		.mixers = { alc663_g71v_mixer },
+		.init_verbs = { alc662_init_verbs, alc663_g71v_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_g71v_unsol_event,
+		.setup = alc663_g71v_setup,
+		.init_hook = alc663_g71v_inithook,
+	},
+	[ALC663_ASUS_H13] = {
+		.mixers = { alc663_m51va_mixer },
+		.init_verbs = { alc662_init_verbs, alc663_m51va_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_m51va_unsol_event,
+		.init_hook = alc663_m51va_inithook,
+	},
+	[ALC663_ASUS_G50V] = {
+		.mixers = { alc663_g50v_mixer },
+		.init_verbs = { alc662_init_verbs, alc663_g50v_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_6ch_modes),
+		.channel_mode = alc662_3ST_6ch_modes,
+		.input_mux = &alc663_capture_source,
+		.unsol_event = alc663_g50v_unsol_event,
+		.setup = alc663_g50v_setup,
+		.init_hook = alc663_g50v_inithook,
+	},
+	[ALC663_ASUS_MODE1] = {
+		.mixers = { alc663_m51va_mixer },
+		.cap_mixer = alc662_auto_capture_mixer,
+		.init_verbs = { alc662_init_verbs,
+				alc663_21jd_amic_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.hp_nid = 0x03,
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_mode1_unsol_event,
+		.setup = alc663_mode1_setup,
+		.init_hook = alc663_mode1_inithook,
+	},
+	[ALC662_ASUS_MODE2] = {
+		.mixers = { alc662_1bjd_mixer },
+		.cap_mixer = alc662_auto_capture_mixer,
+		.init_verbs = { alc662_init_verbs,
+				alc662_1bjd_amic_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc662_mode2_unsol_event,
+		.setup = alc662_mode2_setup,
+		.init_hook = alc662_mode2_inithook,
+	},
+	[ALC663_ASUS_MODE3] = {
+		.mixers = { alc663_two_hp_m1_mixer },
+		.cap_mixer = alc662_auto_capture_mixer,
+		.init_verbs = { alc662_init_verbs,
+				alc663_two_hp_amic_m1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.hp_nid = 0x03,
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_mode3_unsol_event,
+		.setup = alc663_mode3_setup,
+		.init_hook = alc663_mode3_inithook,
+	},
+	[ALC663_ASUS_MODE4] = {
+		.mixers = { alc663_asus_21jd_clfe_mixer },
+		.cap_mixer = alc662_auto_capture_mixer,
+		.init_verbs = { alc662_init_verbs,
+				alc663_21jd_amic_init_verbs},
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.hp_nid = 0x03,
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_mode4_unsol_event,
+		.setup = alc663_mode4_setup,
+		.init_hook = alc663_mode4_inithook,
+	},
+	[ALC663_ASUS_MODE5] = {
+		.mixers = { alc663_asus_15jd_clfe_mixer },
+		.cap_mixer = alc662_auto_capture_mixer,
+		.init_verbs = { alc662_init_verbs,
+				alc663_15jd_amic_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.hp_nid = 0x03,
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_mode5_unsol_event,
+		.setup = alc663_mode5_setup,
+		.init_hook = alc663_mode5_inithook,
+	},
+	[ALC663_ASUS_MODE6] = {
+		.mixers = { alc663_two_hp_m2_mixer },
+		.cap_mixer = alc662_auto_capture_mixer,
+		.init_verbs = { alc662_init_verbs,
+				alc663_two_hp_amic_m2_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.hp_nid = 0x03,
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_mode6_unsol_event,
+		.setup = alc663_mode6_setup,
+		.init_hook = alc663_mode6_inithook,
+	},
+	[ALC663_ASUS_MODE7] = {
+		.mixers = { alc663_mode7_mixer },
+		.cap_mixer = alc662_auto_capture_mixer,
+		.init_verbs = { alc662_init_verbs,
+				alc663_mode7_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.hp_nid = 0x03,
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_mode7_unsol_event,
+		.setup = alc663_mode7_setup,
+		.init_hook = alc663_mode7_inithook,
+	},
+	[ALC663_ASUS_MODE8] = {
+		.mixers = { alc663_mode8_mixer },
+		.cap_mixer = alc662_auto_capture_mixer,
+		.init_verbs = { alc662_init_verbs,
+				alc663_mode8_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc662_dac_nids),
+		.hp_nid = 0x03,
+		.dac_nids = alc662_dac_nids,
+		.dig_out_nid = ALC662_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_mode8_unsol_event,
+		.setup = alc663_mode8_setup,
+		.init_hook = alc663_mode8_inithook,
+	},
+	[ALC272_DELL] = {
+		.mixers = { alc663_m51va_mixer },
+		.cap_mixer = alc272_auto_capture_mixer,
+		.init_verbs = { alc662_init_verbs, alc272_dell_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc272_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.adc_nids = alc272_adc_nids,
+		.num_adc_nids = ARRAY_SIZE(alc272_adc_nids),
+		.capsrc_nids = alc272_capsrc_nids,
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_m51va_unsol_event,
+		.setup = alc663_m51va_setup,
+		.init_hook = alc663_m51va_inithook,
+	},
+	[ALC272_DELL_ZM1] = {
+		.mixers = { alc663_m51va_mixer },
+		.cap_mixer = alc662_auto_capture_mixer,
+		.init_verbs = { alc662_init_verbs, alc272_dell_zm1_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc272_dac_nids),
+		.dac_nids = alc662_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.adc_nids = alc662_adc_nids,
+		.num_adc_nids = 1,
+		.capsrc_nids = alc662_capsrc_nids,
+		.channel_mode = alc662_3ST_2ch_modes,
+		.unsol_event = alc663_m51va_unsol_event,
+		.setup = alc663_m51va_setup,
+		.init_hook = alc663_m51va_inithook,
+	},
+	[ALC272_SAMSUNG_NC10] = {
+		.mixers = { alc272_nc10_mixer },
+		.init_verbs = { alc662_init_verbs,
+				alc663_21jd_amic_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc272_dac_nids),
+		.dac_nids = alc272_dac_nids,
+		.num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes),
+		.channel_mode = alc662_3ST_2ch_modes,
+		/*.input_mux = &alc272_nc10_capture_source,*/
+		.unsol_event = alc663_mode4_unsol_event,
+		.setup = alc663_mode4_setup,
+		.init_hook = alc663_mode4_inithook,
+	},
+};
+
+
+/*
+ * BIOS auto configuration
+ */
+
+/* convert from MIX nid to DAC */
+static inline hda_nid_t alc662_mix_to_dac(hda_nid_t nid)
+{
+	if (nid == 0x0f)
+		return 0x02;
+	else if (nid >= 0x0c && nid <= 0x0e)
+		return nid - 0x0c + 0x02;
+	else if (nid == 0x26) /* ALC887-VD has this DAC too */
+		return 0x25;
+	else
+		return 0;
+}
+
+/* get MIX nid connected to the given pin targeted to DAC */
+static hda_nid_t alc662_dac_to_mix(struct hda_codec *codec, hda_nid_t pin,
+				   hda_nid_t dac)
+{
+	hda_nid_t mix[5];
+	int i, num;
+
+	num = snd_hda_get_connections(codec, pin, mix, ARRAY_SIZE(mix));
+	for (i = 0; i < num; i++) {
+		if (alc662_mix_to_dac(mix[i]) == dac)
+			return mix[i];
+	}
+	return 0;
+}
+
+/* look for an empty DAC slot */
+static hda_nid_t alc662_look_for_dac(struct hda_codec *codec, hda_nid_t pin)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t srcs[5];
+	int i, j, num;
+
+	num = snd_hda_get_connections(codec, pin, srcs, ARRAY_SIZE(srcs));
+	if (num < 0)
+		return 0;
+	for (i = 0; i < num; i++) {
+		hda_nid_t nid = alc662_mix_to_dac(srcs[i]);
+		if (!nid)
+			continue;
+		for (j = 0; j < spec->multiout.num_dacs; j++)
+			if (spec->multiout.dac_nids[j] == nid)
+				break;
+		if (j >= spec->multiout.num_dacs)
+			return nid;
+	}
+	return 0;
+}
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int alc662_auto_fill_dac_nids(struct hda_codec *codec,
+				     const struct auto_pin_cfg *cfg)
+{
+	struct alc_spec *spec = codec->spec;
+	int i;
+	hda_nid_t dac;
+
+	spec->multiout.dac_nids = spec->private_dac_nids;
+	for (i = 0; i < cfg->line_outs; i++) {
+		dac = alc662_look_for_dac(codec, cfg->line_out_pins[i]);
+		if (!dac)
+			continue;
+		spec->multiout.dac_nids[spec->multiout.num_dacs++] = dac;
+	}
+	return 0;
+}
+
+static inline int alc662_add_vol_ctl(struct alc_spec *spec, const char *pfx,
+			      hda_nid_t nid, unsigned int chs)
+{
+	return add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL, pfx,
+			   HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_OUTPUT));
+}
+
+static inline int alc662_add_sw_ctl(struct alc_spec *spec, const char *pfx,
+			     hda_nid_t nid, unsigned int chs)
+{
+	return add_pb_sw_ctrl(spec, ALC_CTL_WIDGET_MUTE, pfx,
+			   HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_INPUT));
+}
+
+#define alc662_add_stereo_vol(spec, pfx, nid) \
+	alc662_add_vol_ctl(spec, pfx, nid, 3)
+#define alc662_add_stereo_sw(spec, pfx, nid) \
+	alc662_add_sw_ctl(spec, pfx, nid, 3)
+
+/* add playback controls from the parsed DAC table */
+static int alc662_auto_create_multi_out_ctls(struct hda_codec *codec,
+					     const struct auto_pin_cfg *cfg)
+{
+	struct alc_spec *spec = codec->spec;
+	static const char *chname[4] = {
+		"Front", "Surround", NULL /*CLFE*/, "Side"
+	};
+	hda_nid_t nid, mix;
+	int i, err;
+
+	for (i = 0; i < cfg->line_outs; i++) {
+		nid = spec->multiout.dac_nids[i];
+		if (!nid)
+			continue;
+		mix = alc662_dac_to_mix(codec, cfg->line_out_pins[i], nid);
+		if (!mix)
+			continue;
+		if (i == 2) {
+			/* Center/LFE */
+			err = alc662_add_vol_ctl(spec, "Center", nid, 1);
+			if (err < 0)
+				return err;
+			err = alc662_add_vol_ctl(spec, "LFE", nid, 2);
+			if (err < 0)
+				return err;
+			err = alc662_add_sw_ctl(spec, "Center", mix, 1);
+			if (err < 0)
+				return err;
+			err = alc662_add_sw_ctl(spec, "LFE", mix, 2);
+			if (err < 0)
+				return err;
+		} else {
+			const char *pfx;
+			if (cfg->line_outs == 1 &&
+			    cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) {
+				if (cfg->hp_outs)
+					pfx = "Speaker";
+				else
+					pfx = "PCM";
+			} else
+				pfx = chname[i];
+			err = alc662_add_vol_ctl(spec, pfx, nid, 3);
+			if (err < 0)
+				return err;
+			if (cfg->line_outs == 1 &&
+			    cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
+				pfx = "Speaker";
+			err = alc662_add_sw_ctl(spec, pfx, mix, 3);
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+/* add playback controls for speaker and HP outputs */
+/* return DAC nid if any new DAC is assigned */
+static int alc662_auto_create_extra_out(struct hda_codec *codec, hda_nid_t pin,
+					const char *pfx)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t nid, mix;
+	int err;
+
+	if (!pin)
+		return 0;
+	nid = alc662_look_for_dac(codec, pin);
+	if (!nid) {
+		/* the corresponding DAC is already occupied */
+		if (!(get_wcaps(codec, pin) & AC_WCAP_OUT_AMP))
+			return 0; /* no way */
+		/* create a switch only */
+		return add_pb_sw_ctrl(spec, ALC_CTL_WIDGET_MUTE, pfx,
+				   HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	}
+
+	mix = alc662_dac_to_mix(codec, pin, nid);
+	if (!mix)
+		return 0;
+	err = alc662_add_vol_ctl(spec, pfx, nid, 3);
+	if (err < 0)
+		return err;
+	err = alc662_add_sw_ctl(spec, pfx, mix, 3);
+	if (err < 0)
+		return err;
+	return nid;
+}
+
+/* create playback/capture controls for input pins */
+#define alc662_auto_create_input_ctls \
+	alc882_auto_create_input_ctls
+
+static void alc662_auto_set_output_and_unmute(struct hda_codec *codec,
+					      hda_nid_t nid, int pin_type,
+					      hda_nid_t dac)
+{
+	int i, num;
+	hda_nid_t srcs[HDA_MAX_CONNECTIONS];
+
+	alc_set_pin_output(codec, nid, pin_type);
+	/* need the manual connection? */
+	num = snd_hda_get_connections(codec, nid, srcs, ARRAY_SIZE(srcs));
+	if (num <= 1)
+		return;
+	for (i = 0; i < num; i++) {
+		if (alc662_mix_to_dac(srcs[i]) != dac)
+			continue;
+		snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, i);
+		return;
+	}
+}
+
+static void alc662_auto_init_multi_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int pin_type = get_pin_type(spec->autocfg.line_out_type);
+	int i;
+
+	for (i = 0; i <= HDA_SIDE; i++) {
+		hda_nid_t nid = spec->autocfg.line_out_pins[i];
+		if (nid)
+			alc662_auto_set_output_and_unmute(codec, nid, pin_type,
+					spec->multiout.dac_nids[i]);
+	}
+}
+
+static void alc662_auto_init_hp_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t pin;
+
+	pin = spec->autocfg.hp_pins[0];
+	if (pin)
+		alc662_auto_set_output_and_unmute(codec, pin, PIN_HP,
+						  spec->multiout.hp_nid);
+	pin = spec->autocfg.speaker_pins[0];
+	if (pin)
+		alc662_auto_set_output_and_unmute(codec, pin, PIN_OUT,
+					spec->multiout.extra_out_nid[0]);
+}
+
+#define ALC662_PIN_CD_NID		ALC880_PIN_CD_NID
+
+static void alc662_auto_init_analog_input(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		if (alc_is_input_pin(codec, nid)) {
+			alc_set_input_pin(codec, nid, cfg->inputs[i].type);
+			if (nid != ALC662_PIN_CD_NID &&
+			    (get_wcaps(codec, nid) & AC_WCAP_OUT_AMP))
+				snd_hda_codec_write(codec, nid, 0,
+						    AC_VERB_SET_AMP_GAIN_MUTE,
+						    AMP_OUT_MUTE);
+		}
+	}
+}
+
+#define alc662_auto_init_input_src	alc882_auto_init_input_src
+
+static int alc662_parse_auto_config(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int err;
+	static hda_nid_t alc662_ignore[] = { 0x1d, 0 };
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg,
+					   alc662_ignore);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs)
+		return 0; /* can't find valid BIOS pin config */
+
+	err = alc662_auto_fill_dac_nids(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc662_auto_create_multi_out_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = alc662_auto_create_extra_out(codec,
+					   spec->autocfg.speaker_pins[0],
+					   "Speaker");
+	if (err < 0)
+		return err;
+	if (err)
+		spec->multiout.extra_out_nid[0] = err;
+	err = alc662_auto_create_extra_out(codec, spec->autocfg.hp_pins[0],
+					   "Headphone");
+	if (err < 0)
+		return err;
+	if (err)
+		spec->multiout.hp_nid = err;
+	err = alc662_auto_create_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	alc_auto_parse_digital(codec);
+
+	if (spec->kctls.list)
+		add_mixer(spec, spec->kctls.list);
+
+	spec->num_mux_defs = 1;
+	spec->input_mux = &spec->private_imux[0];
+
+	add_verb(spec, alc662_init_verbs);
+	if (codec->vendor_id == 0x10ec0272 || codec->vendor_id == 0x10ec0663 ||
+	    codec->vendor_id == 0x10ec0665 || codec->vendor_id == 0x10ec0670)
+		add_verb(spec, alc663_init_verbs);
+
+	if (codec->vendor_id == 0x10ec0272)
+		add_verb(spec, alc272_init_verbs);
+
+	err = alc_auto_add_mic_boost(codec);
+	if (err < 0)
+		return err;
+
+	if (codec->vendor_id == 0x10ec0272 || codec->vendor_id == 0x10ec0663 ||
+	    codec->vendor_id == 0x10ec0665 || codec->vendor_id == 0x10ec0670)
+	    alc_ssid_check(codec, 0x15, 0x1b, 0x14, 0x21);
+	else
+	    alc_ssid_check(codec, 0x15, 0x1b, 0x14, 0);
+
+	return 1;
+}
+
+/* additional initialization for auto-configuration model */
+static void alc662_auto_init(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc662_auto_init_multi_out(codec);
+	alc662_auto_init_hp_out(codec);
+	alc662_auto_init_analog_input(codec);
+	alc662_auto_init_input_src(codec);
+	alc_auto_init_digital(codec);
+	if (spec->unsol_event)
+		alc_inithook(codec);
+}
+
+enum {
+	ALC662_FIXUP_ASPIRE,
+	ALC662_FIXUP_IDEAPAD,
+};
+
+static const struct alc_fixup alc662_fixups[] = {
+	[ALC662_FIXUP_ASPIRE] = {
+		.pins = (const struct alc_pincfg[]) {
+			{ 0x15, 0x99130112 }, /* subwoofer */
+			{ }
+		}
+	},
+	[ALC662_FIXUP_IDEAPAD] = {
+		.pins = (const struct alc_pincfg[]) {
+			{ 0x17, 0x99130112 }, /* subwoofer */
+			{ }
+		}
+	},
+};
+
+static struct snd_pci_quirk alc662_fixup_tbl[] = {
+	SND_PCI_QUIRK(0x1025, 0x038b, "Acer Aspire 8943G", ALC662_FIXUP_ASPIRE),
+	SND_PCI_QUIRK(0x144d, 0xc051, "Samsung R720", ALC662_FIXUP_IDEAPAD),
+	SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo Ideapad Y550P", ALC662_FIXUP_IDEAPAD),
+	SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Ideapad Y550", ALC662_FIXUP_IDEAPAD),
+	{}
+};
+
+
+
+static int patch_alc662(struct hda_codec *codec)
+{
+	struct alc_spec *spec;
+	int err, board_config;
+	int coef;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	alc_auto_parse_customize_define(codec);
+
+	alc_fix_pll_init(codec, 0x20, 0x04, 15);
+
+	coef = alc_read_coef_idx(codec, 0);
+	if (coef == 0x8020 || coef == 0x8011)
+		alc_codec_rename(codec, "ALC661");
+	else if (coef & (1 << 14) &&
+		codec->bus->pci->subsystem_vendor == 0x1025 &&
+		spec->cdefine.platform_type == 1)
+		alc_codec_rename(codec, "ALC272X");
+	else if (coef == 0x4011)
+		alc_codec_rename(codec, "ALC656");
+
+	board_config = snd_hda_check_board_config(codec, ALC662_MODEL_LAST,
+						  alc662_models,
+			  	                  alc662_cfg_tbl);
+	if (board_config < 0) {
+		printk(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+		       codec->chip_name);
+		board_config = ALC662_AUTO;
+	}
+
+	if (board_config == ALC662_AUTO) {
+		alc_pick_fixup(codec, alc662_fixup_tbl, alc662_fixups, 1);
+		/* automatic parse from the BIOS config */
+		err = alc662_parse_auto_config(codec);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		} else if (!err) {
+			printk(KERN_INFO
+			       "hda_codec: Cannot set up configuration "
+			       "from BIOS.  Using base mode...\n");
+			board_config = ALC662_3ST_2ch_DIG;
+		}
+	}
+
+	if (has_cdefine_beep(codec)) {
+		err = snd_hda_attach_beep_device(codec, 0x1);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		}
+	}
+
+	if (board_config != ALC662_AUTO)
+		setup_preset(codec, &alc662_presets[board_config]);
+
+	spec->stream_analog_playback = &alc662_pcm_analog_playback;
+	spec->stream_analog_capture = &alc662_pcm_analog_capture;
+
+	spec->stream_digital_playback = &alc662_pcm_digital_playback;
+	spec->stream_digital_capture = &alc662_pcm_digital_capture;
+
+	if (!spec->adc_nids) {
+		spec->adc_nids = alc662_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(alc662_adc_nids);
+	}
+	if (!spec->capsrc_nids)
+		spec->capsrc_nids = alc662_capsrc_nids;
+
+	if (!spec->cap_mixer)
+		set_capture_mixer(codec);
+
+	if (has_cdefine_beep(codec)) {
+		switch (codec->vendor_id) {
+		case 0x10ec0662:
+			set_beep_amp(spec, 0x0b, 0x05, HDA_INPUT);
+			break;
+		case 0x10ec0272:
+		case 0x10ec0663:
+		case 0x10ec0665:
+			set_beep_amp(spec, 0x0b, 0x04, HDA_INPUT);
+			break;
+		case 0x10ec0273:
+			set_beep_amp(spec, 0x0b, 0x03, HDA_INPUT);
+			break;
+		}
+	}
+	spec->vmaster_nid = 0x02;
+
+	codec->patch_ops = alc_patch_ops;
+	if (board_config == ALC662_AUTO) {
+		spec->init_hook = alc662_auto_init;
+		alc_pick_fixup(codec, alc662_fixup_tbl, alc662_fixups, 0);
+	}
+
+	alc_init_jacks(codec);
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	if (!spec->loopback.amplist)
+		spec->loopback.amplist = alc662_loopbacks;
+#endif
+
+	return 0;
+}
+
+static int patch_alc888(struct hda_codec *codec)
+{
+	if ((alc_read_coef_idx(codec, 0) & 0x00f0)==0x0030){
+		kfree(codec->chip_name);
+		if (codec->vendor_id == 0x10ec0887)
+			codec->chip_name = kstrdup("ALC887-VD", GFP_KERNEL);
+		else
+			codec->chip_name = kstrdup("ALC888-VD", GFP_KERNEL);
+		if (!codec->chip_name) {
+			alc_free(codec);
+			return -ENOMEM;
+		}
+		return patch_alc662(codec);
+	}
+	return patch_alc882(codec);
+}
+
+/*
+ * ALC680 support
+ */
+#define ALC680_DIGIN_NID	ALC880_DIGIN_NID
+#define ALC680_DIGOUT_NID	ALC880_DIGOUT_NID
+#define alc680_modes		alc260_modes
+
+static hda_nid_t alc680_dac_nids[3] = {
+	/* Lout1, Lout2, hp */
+	0x02, 0x03, 0x04
+};
+
+static hda_nid_t alc680_adc_nids[3] = {
+	/* ADC0-2 */
+	/* DMIC, MIC, Line-in*/
+	0x07, 0x08, 0x09
+};
+
+/*
+ * Analog capture ADC cgange
+ */
+static void alc680_rec_autoswitch(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int pin_found = 0;
+	int type_found = AUTO_PIN_LAST;
+	hda_nid_t nid;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		nid = cfg->inputs[i].pin;
+		if (!(snd_hda_query_pin_caps(codec, nid) &
+		      AC_PINCAP_PRES_DETECT))
+			continue;
+		if (snd_hda_jack_detect(codec, nid)) {
+			if (cfg->inputs[i].type < type_found) {
+				type_found = cfg->inputs[i].type;
+				pin_found = nid;
+			}
+		}
+	}
+
+	nid = 0x07;
+	if (pin_found)
+		snd_hda_get_connections(codec, pin_found, &nid, 1);
+
+	if (nid != spec->cur_adc)
+		__snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1);
+	spec->cur_adc = nid;
+	snd_hda_codec_setup_stream(codec, nid, spec->cur_adc_stream_tag, 0,
+				   spec->cur_adc_format);
+}
+
+static int alc680_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      unsigned int stream_tag,
+				      unsigned int format,
+				      struct snd_pcm_substream *substream)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->cur_adc = 0x07;
+	spec->cur_adc_stream_tag = stream_tag;
+	spec->cur_adc_format = format;
+
+	alc680_rec_autoswitch(codec);
+	return 0;
+}
+
+static int alc680_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      struct snd_pcm_substream *substream)
+{
+	snd_hda_codec_cleanup_stream(codec, 0x07);
+	snd_hda_codec_cleanup_stream(codec, 0x08);
+	snd_hda_codec_cleanup_stream(codec, 0x09);
+	return 0;
+}
+
+static struct hda_pcm_stream alc680_pcm_analog_auto_capture = {
+	.substreams = 1, /* can be overridden */
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in alc_build_pcms */
+	.ops = {
+		.prepare = alc680_capture_pcm_prepare,
+		.cleanup = alc680_capture_pcm_cleanup
+	},
+};
+
+static struct snd_kcontrol_new alc680_base_mixer[] = {
+	/* output mixer control */
+	HDA_CODEC_VOLUME("Front Playback Volume", 0x2, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Headphone Playback Volume", 0x4, 0x0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Headphone Playback Switch", 0x16, 0x0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Int Mic Boost", 0x12, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Line In Boost", 0x19, 0, HDA_INPUT),
+	{ }
+};
+
+static struct hda_bind_ctls alc680_bind_cap_vol = {
+	.ops = &snd_hda_bind_vol,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x07, 3, 0, HDA_INPUT),
+		HDA_COMPOSE_AMP_VAL(0x08, 3, 0, HDA_INPUT),
+		HDA_COMPOSE_AMP_VAL(0x09, 3, 0, HDA_INPUT),
+		0
+	},
+};
+
+static struct hda_bind_ctls alc680_bind_cap_switch = {
+	.ops = &snd_hda_bind_sw,
+	.values = {
+		HDA_COMPOSE_AMP_VAL(0x07, 3, 0, HDA_INPUT),
+		HDA_COMPOSE_AMP_VAL(0x08, 3, 0, HDA_INPUT),
+		HDA_COMPOSE_AMP_VAL(0x09, 3, 0, HDA_INPUT),
+		0
+	},
+};
+
+static struct snd_kcontrol_new alc680_master_capture_mixer[] = {
+	HDA_BIND_VOL("Capture Volume", &alc680_bind_cap_vol),
+	HDA_BIND_SW("Capture Switch", &alc680_bind_cap_switch),
+	{ } /* end */
+};
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb alc680_init_verbs[] = {
+	{0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	{0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+	{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+	{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
+
+	{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT   | AC_USRSP_EN},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT  | AC_USRSP_EN},
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT  | AC_USRSP_EN},
+
+	{ }
+};
+
+/* toggle speaker-output according to the hp-jack state */
+static void alc680_base_setup(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+
+	spec->autocfg.hp_pins[0] = 0x16;
+	spec->autocfg.speaker_pins[0] = 0x14;
+	spec->autocfg.speaker_pins[1] = 0x15;
+	spec->autocfg.num_inputs = 2;
+	spec->autocfg.inputs[0].pin = 0x18;
+	spec->autocfg.inputs[0].type = AUTO_PIN_MIC;
+	spec->autocfg.inputs[1].pin = 0x19;
+	spec->autocfg.inputs[1].type = AUTO_PIN_LINE_IN;
+}
+
+static void alc680_unsol_event(struct hda_codec *codec,
+					   unsigned int res)
+{
+	if ((res >> 26) == ALC880_HP_EVENT)
+		alc_automute_amp(codec);
+	if ((res >> 26) == ALC880_MIC_EVENT)
+		alc680_rec_autoswitch(codec);
+}
+
+static void alc680_inithook(struct hda_codec *codec)
+{
+	alc_automute_amp(codec);
+	alc680_rec_autoswitch(codec);
+}
+
+/* create input playback/capture controls for the given pin */
+static int alc680_new_analog_output(struct alc_spec *spec, hda_nid_t nid,
+				    const char *ctlname, int idx)
+{
+	hda_nid_t dac;
+	int err;
+
+	switch (nid) {
+	case 0x14:
+		dac = 0x02;
+		break;
+	case 0x15:
+		dac = 0x03;
+		break;
+	case 0x16:
+		dac = 0x04;
+		break;
+	default:
+		return 0;
+	}
+	if (spec->multiout.dac_nids[0] != dac &&
+	    spec->multiout.dac_nids[1] != dac) {
+		err = add_pb_vol_ctrl(spec, ALC_CTL_WIDGET_VOL, ctlname,
+				  HDA_COMPOSE_AMP_VAL(dac, 3, idx,
+						      HDA_OUTPUT));
+		if (err < 0)
+			return err;
+
+		err = add_pb_sw_ctrl(spec, ALC_CTL_WIDGET_MUTE, ctlname,
+			  HDA_COMPOSE_AMP_VAL(nid, 3, idx, HDA_OUTPUT));
+
+		if (err < 0)
+			return err;
+		spec->multiout.dac_nids[spec->multiout.num_dacs++] = dac;
+	}
+
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int alc680_auto_create_multi_out_ctls(struct alc_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	hda_nid_t nid;
+	int err;
+
+	spec->multiout.dac_nids = spec->private_dac_nids;
+
+	nid = cfg->line_out_pins[0];
+	if (nid) {
+		const char *name;
+		if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
+			name = "Speaker";
+		else
+			name = "Front";
+		err = alc680_new_analog_output(spec, nid, name, 0);
+		if (err < 0)
+			return err;
+	}
+
+	nid = cfg->speaker_pins[0];
+	if (nid) {
+		err = alc680_new_analog_output(spec, nid, "Speaker", 0);
+		if (err < 0)
+			return err;
+	}
+	nid = cfg->hp_pins[0];
+	if (nid) {
+		err = alc680_new_analog_output(spec, nid, "Headphone", 0);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static void alc680_auto_set_output_and_unmute(struct hda_codec *codec,
+					      hda_nid_t nid, int pin_type)
+{
+	alc_set_pin_output(codec, nid, pin_type);
+}
+
+static void alc680_auto_init_multi_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t nid = spec->autocfg.line_out_pins[0];
+	if (nid) {
+		int pin_type = get_pin_type(spec->autocfg.line_out_type);
+		alc680_auto_set_output_and_unmute(codec, nid, pin_type);
+	}
+}
+
+static void alc680_auto_init_hp_out(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	hda_nid_t pin;
+
+	pin = spec->autocfg.hp_pins[0];
+	if (pin)
+		alc680_auto_set_output_and_unmute(codec, pin, PIN_HP);
+	pin = spec->autocfg.speaker_pins[0];
+	if (pin)
+		alc680_auto_set_output_and_unmute(codec, pin, PIN_OUT);
+}
+
+/* pcm configuration: identical with ALC880 */
+#define alc680_pcm_analog_playback	alc880_pcm_analog_playback
+#define alc680_pcm_analog_capture	alc880_pcm_analog_capture
+#define alc680_pcm_analog_alt_capture	alc880_pcm_analog_alt_capture
+#define alc680_pcm_digital_playback	alc880_pcm_digital_playback
+#define alc680_pcm_digital_capture	alc880_pcm_digital_capture
+
+/*
+ * BIOS auto configuration
+ */
+static int alc680_parse_auto_config(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	int err;
+	static hda_nid_t alc680_ignore[] = { 0 };
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg,
+					   alc680_ignore);
+	if (err < 0)
+		return err;
+
+	if (!spec->autocfg.line_outs) {
+		if (spec->autocfg.dig_outs || spec->autocfg.dig_in_pin) {
+			spec->multiout.max_channels = 2;
+			spec->no_analog = 1;
+			goto dig_only;
+		}
+		return 0; /* can't find valid BIOS pin config */
+	}
+	err = alc680_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = 2;
+
+ dig_only:
+	/* digital only support output */
+	alc_auto_parse_digital(codec);
+	if (spec->kctls.list)
+		add_mixer(spec, spec->kctls.list);
+
+	add_verb(spec, alc680_init_verbs);
+
+	err = alc_auto_add_mic_boost(codec);
+	if (err < 0)
+		return err;
+
+	return 1;
+}
+
+#define alc680_auto_init_analog_input	alc882_auto_init_analog_input
+
+/* init callback for auto-configuration model -- overriding the default init */
+static void alc680_auto_init(struct hda_codec *codec)
+{
+	struct alc_spec *spec = codec->spec;
+	alc680_auto_init_multi_out(codec);
+	alc680_auto_init_hp_out(codec);
+	alc680_auto_init_analog_input(codec);
+	alc_auto_init_digital(codec);
+	if (spec->unsol_event)
+		alc_inithook(codec);
+}
+
+/*
+ * configuration and preset
+ */
+static const char *alc680_models[ALC680_MODEL_LAST] = {
+	[ALC680_BASE]		= "base",
+	[ALC680_AUTO]		= "auto",
+};
+
+static struct snd_pci_quirk alc680_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x1043, 0x12f3, "ASUS NX90", ALC680_BASE),
+	{}
+};
+
+static struct alc_config_preset alc680_presets[] = {
+	[ALC680_BASE] = {
+		.mixers = { alc680_base_mixer },
+		.cap_mixer =  alc680_master_capture_mixer,
+		.init_verbs = { alc680_init_verbs },
+		.num_dacs = ARRAY_SIZE(alc680_dac_nids),
+		.dac_nids = alc680_dac_nids,
+		.dig_out_nid = ALC680_DIGOUT_NID,
+		.num_channel_mode = ARRAY_SIZE(alc680_modes),
+		.channel_mode = alc680_modes,
+		.unsol_event = alc680_unsol_event,
+		.setup = alc680_base_setup,
+		.init_hook = alc680_inithook,
+
+	},
+};
+
+static int patch_alc680(struct hda_codec *codec)
+{
+	struct alc_spec *spec;
+	int board_config;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->spec = spec;
+
+	board_config = snd_hda_check_board_config(codec, ALC680_MODEL_LAST,
+						  alc680_models,
+						  alc680_cfg_tbl);
+
+	if (board_config < 0 || board_config >= ALC680_MODEL_LAST) {
+		printk(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+		       codec->chip_name);
+		board_config = ALC680_AUTO;
+	}
+
+	if (board_config == ALC680_AUTO) {
+		/* automatic parse from the BIOS config */
+		err = alc680_parse_auto_config(codec);
+		if (err < 0) {
+			alc_free(codec);
+			return err;
+		} else if (!err) {
+			printk(KERN_INFO
+			       "hda_codec: Cannot set up configuration "
+			       "from BIOS.  Using base mode...\n");
+			board_config = ALC680_BASE;
+		}
+	}
+
+	if (board_config != ALC680_AUTO)
+		setup_preset(codec, &alc680_presets[board_config]);
+
+	spec->stream_analog_playback = &alc680_pcm_analog_playback;
+	spec->stream_analog_capture = &alc680_pcm_analog_auto_capture;
+	spec->stream_digital_playback = &alc680_pcm_digital_playback;
+	spec->stream_digital_capture = &alc680_pcm_digital_capture;
+
+	if (!spec->adc_nids) {
+		spec->adc_nids = alc680_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(alc680_adc_nids);
+	}
+
+	if (!spec->cap_mixer)
+		set_capture_mixer(codec);
+
+	spec->vmaster_nid = 0x02;
+
+	codec->patch_ops = alc_patch_ops;
+	if (board_config == ALC680_AUTO)
+		spec->init_hook = alc680_auto_init;
+
+	return 0;
+}
+
+/*
+ * patch entries
+ */
+static struct hda_codec_preset snd_hda_preset_realtek[] = {
+	{ .id = 0x10ec0260, .name = "ALC260", .patch = patch_alc260 },
+	{ .id = 0x10ec0262, .name = "ALC262", .patch = patch_alc262 },
+	{ .id = 0x10ec0267, .name = "ALC267", .patch = patch_alc268 },
+	{ .id = 0x10ec0268, .name = "ALC268", .patch = patch_alc268 },
+	{ .id = 0x10ec0269, .name = "ALC269", .patch = patch_alc269 },
+	{ .id = 0x10ec0270, .name = "ALC270", .patch = patch_alc269 },
+	{ .id = 0x10ec0272, .name = "ALC272", .patch = patch_alc662 },
+	{ .id = 0x10ec0275, .name = "ALC275", .patch = patch_alc269 },
+	{ .id = 0x10ec0861, .rev = 0x100340, .name = "ALC660",
+	  .patch = patch_alc861 },
+	{ .id = 0x10ec0660, .name = "ALC660-VD", .patch = patch_alc861vd },
+	{ .id = 0x10ec0861, .name = "ALC861", .patch = patch_alc861 },
+	{ .id = 0x10ec0862, .name = "ALC861-VD", .patch = patch_alc861vd },
+	{ .id = 0x10ec0662, .rev = 0x100002, .name = "ALC662 rev2",
+	  .patch = patch_alc882 },
+	{ .id = 0x10ec0662, .rev = 0x100101, .name = "ALC662 rev1",
+	  .patch = patch_alc662 },
+	{ .id = 0x10ec0663, .name = "ALC663", .patch = patch_alc662 },
+	{ .id = 0x10ec0665, .name = "ALC665", .patch = patch_alc662 },
+	{ .id = 0x10ec0670, .name = "ALC670", .patch = patch_alc662 },
+	{ .id = 0x10ec0680, .name = "ALC680", .patch = patch_alc680 },
+	{ .id = 0x10ec0880, .name = "ALC880", .patch = patch_alc880 },
+	{ .id = 0x10ec0882, .name = "ALC882", .patch = patch_alc882 },
+	{ .id = 0x10ec0883, .name = "ALC883", .patch = patch_alc882 },
+	{ .id = 0x10ec0885, .rev = 0x100101, .name = "ALC889A",
+	  .patch = patch_alc882 },
+	{ .id = 0x10ec0885, .rev = 0x100103, .name = "ALC889A",
+	  .patch = patch_alc882 },
+	{ .id = 0x10ec0885, .name = "ALC885", .patch = patch_alc882 },
+	{ .id = 0x10ec0887, .name = "ALC887", .patch = patch_alc888 },
+	{ .id = 0x10ec0888, .rev = 0x100101, .name = "ALC1200",
+	  .patch = patch_alc882 },
+	{ .id = 0x10ec0888, .name = "ALC888", .patch = patch_alc888 },
+	{ .id = 0x10ec0889, .name = "ALC889", .patch = patch_alc882 },
+	{ .id = 0x10ec0892, .name = "ALC892", .patch = patch_alc662 },
+	{} /* terminator */
+};
+
+MODULE_ALIAS("snd-hda-codec-id:10ec*");
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Realtek HD-audio codec");
+
+static struct hda_codec_preset_list realtek_list = {
+	.preset = snd_hda_preset_realtek,
+	.owner = THIS_MODULE,
+};
+
+static int __init patch_realtek_init(void)
+{
+	return snd_hda_add_codec_preset(&realtek_list);
+}
+
+static void __exit patch_realtek_exit(void)
+{
+	snd_hda_delete_codec_preset(&realtek_list);
+}
+
+module_init(patch_realtek_init)
+module_exit(patch_realtek_exit)
diff --git a/linux/sound/pci/hda/patch_si3054.c b/linux/sound/pci/hda/patch_si3054.c
new file mode 100644
index 0000000..f419ee8
--- /dev/null
+++ b/linux/sound/pci/hda/patch_si3054.c
@@ -0,0 +1,335 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for Silicon Labs 3054/5 modem codec
+ *
+ * Copyright (c) 2005 Sasha Khapyorsky <sashak@alsa-project.org>
+ *                    Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+/* si3054 verbs */
+#define SI3054_VERB_READ_NODE  0x900
+#define SI3054_VERB_WRITE_NODE 0x100
+
+/* si3054 nodes (registers) */
+#define SI3054_EXTENDED_MID    2
+#define SI3054_LINE_RATE       3
+#define SI3054_LINE_LEVEL      4
+#define SI3054_GPIO_CFG        5
+#define SI3054_GPIO_POLARITY   6
+#define SI3054_GPIO_STICKY     7
+#define SI3054_GPIO_WAKEUP     8
+#define SI3054_GPIO_STATUS     9
+#define SI3054_GPIO_CONTROL   10
+#define SI3054_MISC_AFE       11
+#define SI3054_CHIPID         12
+#define SI3054_LINE_CFG1      13
+#define SI3054_LINE_STATUS    14
+#define SI3054_DC_TERMINATION 15
+#define SI3054_LINE_CONFIG    16
+#define SI3054_CALLPROG_ATT   17
+#define SI3054_SQ_CONTROL     18
+#define SI3054_MISC_CONTROL   19
+#define SI3054_RING_CTRL1     20
+#define SI3054_RING_CTRL2     21
+
+/* extended MID */
+#define SI3054_MEI_READY 0xf
+
+/* line level */
+#define SI3054_ATAG_MASK 0x00f0
+#define SI3054_DTAG_MASK 0xf000
+
+/* GPIO bits */
+#define SI3054_GPIO_OH    0x0001
+#define SI3054_GPIO_CID   0x0002
+
+/* chipid and revisions */
+#define SI3054_CHIPID_CODEC_REV_MASK 0x000f
+#define SI3054_CHIPID_DAA_REV_MASK   0x00f0
+#define SI3054_CHIPID_INTERNATIONAL  0x0100
+#define SI3054_CHIPID_DAA_ID         0x0f00
+#define SI3054_CHIPID_CODEC_ID      (1<<12)
+
+/* si3054 codec registers (nodes) access macros */
+#define GET_REG(codec,reg) (snd_hda_codec_read(codec,reg,0,SI3054_VERB_READ_NODE,0))
+#define SET_REG(codec,reg,val) (snd_hda_codec_write(codec,reg,0,SI3054_VERB_WRITE_NODE,val))
+#define SET_REG_CACHE(codec,reg,val) \
+	snd_hda_codec_write_cache(codec,reg,0,SI3054_VERB_WRITE_NODE,val)
+
+
+struct si3054_spec {
+	unsigned international;
+	struct hda_pcm pcm;
+};
+
+
+/*
+ * Modem mixer
+ */
+
+#define PRIVATE_VALUE(reg,mask) ((reg<<16)|(mask&0xffff))
+#define PRIVATE_REG(val) ((val>>16)&0xffff)
+#define PRIVATE_MASK(val) (val&0xffff)
+
+#define si3054_switch_info	snd_ctl_boolean_mono_info
+
+static int si3054_switch_get(struct snd_kcontrol *kcontrol,
+		               struct snd_ctl_elem_value *uvalue)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	u16 reg  = PRIVATE_REG(kcontrol->private_value);
+	u16 mask = PRIVATE_MASK(kcontrol->private_value);
+	uvalue->value.integer.value[0] = (GET_REG(codec, reg)) & mask ? 1 : 0 ;
+	return 0;
+}
+
+static int si3054_switch_put(struct snd_kcontrol *kcontrol,
+		               struct snd_ctl_elem_value *uvalue)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	u16 reg  = PRIVATE_REG(kcontrol->private_value);
+	u16 mask = PRIVATE_MASK(kcontrol->private_value);
+	if (uvalue->value.integer.value[0])
+		SET_REG_CACHE(codec, reg, (GET_REG(codec, reg)) | mask);
+	else
+		SET_REG_CACHE(codec, reg, (GET_REG(codec, reg)) & ~mask);
+	return 0;
+}
+
+#define SI3054_KCONTROL(kname,reg,mask) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = kname, \
+	.subdevice = HDA_SUBDEV_NID_FLAG | reg, \
+	.info = si3054_switch_info, \
+	.get  = si3054_switch_get, \
+	.put  = si3054_switch_put, \
+	.private_value = PRIVATE_VALUE(reg,mask), \
+}
+		
+
+static struct snd_kcontrol_new si3054_modem_mixer[] = {
+	SI3054_KCONTROL("Off-hook Switch", SI3054_GPIO_CONTROL, SI3054_GPIO_OH),
+	SI3054_KCONTROL("Caller ID Switch", SI3054_GPIO_CONTROL, SI3054_GPIO_CID),
+	{}
+};
+
+static int si3054_build_controls(struct hda_codec *codec)
+{
+	return snd_hda_add_new_ctls(codec, si3054_modem_mixer);
+}
+
+
+/*
+ * PCM callbacks
+ */
+
+static int si3054_pcm_prepare(struct hda_pcm_stream *hinfo,
+			      struct hda_codec *codec,
+			      unsigned int stream_tag,
+			      unsigned int format,
+			      struct snd_pcm_substream *substream)
+{
+	u16 val;
+
+	SET_REG(codec, SI3054_LINE_RATE, substream->runtime->rate);
+	val = GET_REG(codec, SI3054_LINE_LEVEL);
+	val &= 0xff << (8 * (substream->stream != SNDRV_PCM_STREAM_PLAYBACK));
+	val |= ((stream_tag & 0xf) << 4) << (8 * (substream->stream == SNDRV_PCM_STREAM_PLAYBACK));
+	SET_REG(codec, SI3054_LINE_LEVEL, val);
+
+	snd_hda_codec_setup_stream(codec, hinfo->nid,
+				   stream_tag, 0, format);
+	return 0;
+}
+
+static int si3054_pcm_open(struct hda_pcm_stream *hinfo,
+			   struct hda_codec *codec,
+			    struct snd_pcm_substream *substream)
+{
+	static unsigned int rates[] = { 8000, 9600, 16000 };
+	static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+		.count = ARRAY_SIZE(rates),
+		.list = rates,
+		.mask = 0,
+	};
+	substream->runtime->hw.period_bytes_min = 80;
+	return snd_pcm_hw_constraint_list(substream->runtime, 0,
+			SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+}
+
+
+static struct hda_pcm_stream si3054_pcm = {
+	.substreams = 1,
+	.channels_min = 1,
+	.channels_max = 1,
+	.nid = 0x1,
+	.rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_KNOT,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.maxbps = 16,
+	.ops = {
+		.open = si3054_pcm_open,
+		.prepare = si3054_pcm_prepare,
+	},
+};
+
+
+static int si3054_build_pcms(struct hda_codec *codec)
+{
+	struct si3054_spec *spec = codec->spec;
+	struct hda_pcm *info = &spec->pcm;
+	si3054_pcm.nid = codec->mfg;
+	codec->num_pcms = 1;
+	codec->pcm_info = info;
+	info->name = "Si3054 Modem";
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK] = si3054_pcm;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE]  = si3054_pcm;
+	info->pcm_type = HDA_PCM_TYPE_MODEM;
+	return 0;
+}
+
+
+/*
+ * Init part
+ */
+
+static int si3054_init(struct hda_codec *codec)
+{
+	struct si3054_spec *spec = codec->spec;
+	unsigned wait_count;
+	u16 val;
+
+	snd_hda_codec_write(codec, AC_NODE_ROOT, 0, AC_VERB_SET_CODEC_RESET, 0);
+	snd_hda_codec_write(codec, codec->mfg, 0, AC_VERB_SET_STREAM_FORMAT, 0);
+	SET_REG(codec, SI3054_LINE_RATE, 9600);
+	SET_REG(codec, SI3054_LINE_LEVEL, SI3054_DTAG_MASK|SI3054_ATAG_MASK);
+	SET_REG(codec, SI3054_EXTENDED_MID, 0);
+
+	wait_count = 10;
+	do {
+		msleep(2);
+		val = GET_REG(codec, SI3054_EXTENDED_MID);
+	} while ((val & SI3054_MEI_READY) != SI3054_MEI_READY && wait_count--);
+
+	if((val&SI3054_MEI_READY) != SI3054_MEI_READY) {
+		snd_printk(KERN_ERR "si3054: cannot initialize. EXT MID = %04x\n", val);
+		/* let's pray that this is no fatal error */
+		/* return -EACCES; */
+	}
+
+	SET_REG(codec, SI3054_GPIO_POLARITY, 0xffff);
+	SET_REG(codec, SI3054_GPIO_CFG, 0x0);
+	SET_REG(codec, SI3054_MISC_AFE, 0);
+	SET_REG(codec, SI3054_LINE_CFG1,0x200);
+
+	if((GET_REG(codec,SI3054_LINE_STATUS) & (1<<6)) == 0) {
+		snd_printd("Link Frame Detect(FDT) is not ready (line status: %04x)\n",
+				GET_REG(codec,SI3054_LINE_STATUS));
+	}
+
+	spec->international = GET_REG(codec, SI3054_CHIPID) & SI3054_CHIPID_INTERNATIONAL;
+
+	return 0;
+}
+
+static void si3054_free(struct hda_codec *codec)
+{
+	kfree(codec->spec);
+}
+
+
+/*
+ */
+
+static struct hda_codec_ops si3054_patch_ops = {
+	.build_controls = si3054_build_controls,
+	.build_pcms = si3054_build_pcms,
+	.init = si3054_init,
+	.free = si3054_free,
+};
+
+static int patch_si3054(struct hda_codec *codec)
+{
+	struct si3054_spec *spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+	codec->spec = spec;
+	codec->patch_ops = si3054_patch_ops;
+	return 0;
+}
+
+/*
+ * patch entries
+ */
+static struct hda_codec_preset snd_hda_preset_si3054[] = {
+ 	{ .id = 0x163c3055, .name = "Si3054", .patch = patch_si3054 },
+ 	{ .id = 0x163c3155, .name = "Si3054", .patch = patch_si3054 },
+ 	{ .id = 0x11c13026, .name = "Si3054", .patch = patch_si3054 },
+ 	{ .id = 0x11c13055, .name = "Si3054", .patch = patch_si3054 },
+ 	{ .id = 0x11c13155, .name = "Si3054", .patch = patch_si3054 },
+ 	{ .id = 0x10573055, .name = "Si3054", .patch = patch_si3054 },
+ 	{ .id = 0x10573057, .name = "Si3054", .patch = patch_si3054 },
+ 	{ .id = 0x10573155, .name = "Si3054", .patch = patch_si3054 },
+	/* VIA HDA on Clevo m540 */
+	{ .id = 0x11063288, .name = "Si3054", .patch = patch_si3054 },
+	/* Asus A8J Modem (SM56) */
+	{ .id = 0x15433155, .name = "Si3054", .patch = patch_si3054 },
+	/* LG LW20 modem */
+	{ .id = 0x18540018, .name = "Si3054", .patch = patch_si3054 },
+	{}
+};
+
+MODULE_ALIAS("snd-hda-codec-id:163c3055");
+MODULE_ALIAS("snd-hda-codec-id:163c3155");
+MODULE_ALIAS("snd-hda-codec-id:11c13026");
+MODULE_ALIAS("snd-hda-codec-id:11c13055");
+MODULE_ALIAS("snd-hda-codec-id:11c13155");
+MODULE_ALIAS("snd-hda-codec-id:10573055");
+MODULE_ALIAS("snd-hda-codec-id:10573057");
+MODULE_ALIAS("snd-hda-codec-id:10573155");
+MODULE_ALIAS("snd-hda-codec-id:11063288");
+MODULE_ALIAS("snd-hda-codec-id:15433155");
+MODULE_ALIAS("snd-hda-codec-id:18540018");
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Si3054 HD-audio modem codec");
+
+static struct hda_codec_preset_list si3054_list = {
+	.preset = snd_hda_preset_si3054,
+	.owner = THIS_MODULE,
+};
+
+static int __init patch_si3054_init(void)
+{
+	return snd_hda_add_codec_preset(&si3054_list);
+}
+
+static void __exit patch_si3054_exit(void)
+{
+	snd_hda_delete_codec_preset(&si3054_list);
+}
+
+module_init(patch_si3054_init)
+module_exit(patch_si3054_exit)
diff --git a/linux/sound/pci/hda/patch_sigmatel.c b/linux/sound/pci/hda/patch_sigmatel.c
new file mode 100644
index 0000000..f03b2ff
--- /dev/null
+++ b/linux/sound/pci/hda/patch_sigmatel.c
@@ -0,0 +1,6440 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for SigmaTel STAC92xx
+ *
+ * Copyright (c) 2005 Embedded Alley Solutions, Inc.
+ * Matt Porter <mporter@embeddedalley.com>
+ *
+ * Based on patch_cmedia.c and patch_realtek.c
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ *  This driver 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 driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/dmi.h>
+#include <sound/core.h>
+#include <sound/asoundef.h>
+#include <sound/jack.h>
+#include <sound/tlv.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+#include "hda_beep.h"
+
+enum {
+	STAC_VREF_EVENT	= 1,
+	STAC_INSERT_EVENT,
+	STAC_PWR_EVENT,
+	STAC_HP_EVENT,
+	STAC_LO_EVENT,
+	STAC_MIC_EVENT,
+};
+
+enum {
+	STAC_AUTO,
+	STAC_REF,
+	STAC_9200_OQO,
+	STAC_9200_DELL_D21,
+	STAC_9200_DELL_D22,
+	STAC_9200_DELL_D23,
+	STAC_9200_DELL_M21,
+	STAC_9200_DELL_M22,
+	STAC_9200_DELL_M23,
+	STAC_9200_DELL_M24,
+	STAC_9200_DELL_M25,
+	STAC_9200_DELL_M26,
+	STAC_9200_DELL_M27,
+	STAC_9200_M4,
+	STAC_9200_M4_2,
+	STAC_9200_PANASONIC,
+	STAC_9200_MODELS
+};
+
+enum {
+	STAC_9205_AUTO,
+	STAC_9205_REF,
+	STAC_9205_DELL_M42,
+	STAC_9205_DELL_M43,
+	STAC_9205_DELL_M44,
+	STAC_9205_EAPD,
+	STAC_9205_MODELS
+};
+
+enum {
+	STAC_92HD73XX_AUTO,
+	STAC_92HD73XX_NO_JD, /* no jack-detection */
+	STAC_92HD73XX_REF,
+	STAC_92HD73XX_INTEL,
+	STAC_DELL_M6_AMIC,
+	STAC_DELL_M6_DMIC,
+	STAC_DELL_M6_BOTH,
+	STAC_DELL_EQ,
+	STAC_ALIENWARE_M17X,
+	STAC_92HD73XX_MODELS
+};
+
+enum {
+	STAC_92HD83XXX_AUTO,
+	STAC_92HD83XXX_REF,
+	STAC_92HD83XXX_PWR_REF,
+	STAC_DELL_S14,
+	STAC_92HD83XXX_HP,
+	STAC_HP_DV7_4000,
+	STAC_92HD83XXX_MODELS
+};
+
+enum {
+	STAC_92HD71BXX_AUTO,
+	STAC_92HD71BXX_REF,
+	STAC_DELL_M4_1,
+	STAC_DELL_M4_2,
+	STAC_DELL_M4_3,
+	STAC_HP_M4,
+	STAC_HP_DV4,
+	STAC_HP_DV5,
+	STAC_HP_HDX,
+	STAC_HP_DV4_1222NR,
+	STAC_92HD71BXX_MODELS
+};
+
+enum {
+	STAC_925x_AUTO,
+	STAC_925x_REF,
+	STAC_M1,
+	STAC_M1_2,
+	STAC_M2,
+	STAC_M2_2,
+	STAC_M3,
+	STAC_M5,
+	STAC_M6,
+	STAC_925x_MODELS
+};
+
+enum {
+	STAC_922X_AUTO,
+	STAC_D945_REF,
+	STAC_D945GTP3,
+	STAC_D945GTP5,
+	STAC_INTEL_MAC_V1,
+	STAC_INTEL_MAC_V2,
+	STAC_INTEL_MAC_V3,
+	STAC_INTEL_MAC_V4,
+	STAC_INTEL_MAC_V5,
+	STAC_INTEL_MAC_AUTO, /* This model is selected if no module parameter
+			      * is given, one of the above models will be
+			      * chosen according to the subsystem id. */
+	/* for backward compatibility */
+	STAC_MACMINI,
+	STAC_MACBOOK,
+	STAC_MACBOOK_PRO_V1,
+	STAC_MACBOOK_PRO_V2,
+	STAC_IMAC_INTEL,
+	STAC_IMAC_INTEL_20,
+	STAC_ECS_202,
+	STAC_922X_DELL_D81,
+	STAC_922X_DELL_D82,
+	STAC_922X_DELL_M81,
+	STAC_922X_DELL_M82,
+	STAC_922X_MODELS
+};
+
+enum {
+	STAC_927X_AUTO,
+	STAC_D965_REF_NO_JD, /* no jack-detection */
+	STAC_D965_REF,
+	STAC_D965_3ST,
+	STAC_D965_5ST,
+	STAC_D965_5ST_NO_FP,
+	STAC_DELL_3ST,
+	STAC_DELL_BIOS,
+	STAC_927X_VOLKNOB,
+	STAC_927X_MODELS
+};
+
+enum {
+	STAC_9872_AUTO,
+	STAC_9872_VAIO,
+	STAC_9872_MODELS
+};
+
+struct sigmatel_event {
+	hda_nid_t nid;
+	unsigned char type;
+	unsigned char tag;
+	int data;
+};
+
+struct sigmatel_jack {
+	hda_nid_t nid;
+	int type;
+	struct snd_jack *jack;
+};
+
+struct sigmatel_mic_route {
+	hda_nid_t pin;
+	signed char mux_idx;
+	signed char dmux_idx;
+};
+
+struct sigmatel_spec {
+	struct snd_kcontrol_new *mixers[4];
+	unsigned int num_mixers;
+
+	int board_config;
+	unsigned int eapd_switch: 1;
+	unsigned int surr_switch: 1;
+	unsigned int alt_switch: 1;
+	unsigned int hp_detect: 1;
+	unsigned int spdif_mute: 1;
+	unsigned int check_volume_offset:1;
+	unsigned int auto_mic:1;
+	unsigned int linear_tone_beep:1;
+
+	/* gpio lines */
+	unsigned int eapd_mask;
+	unsigned int gpio_mask;
+	unsigned int gpio_dir;
+	unsigned int gpio_data;
+	unsigned int gpio_mute;
+	unsigned int gpio_led;
+	unsigned int gpio_led_polarity;
+
+	/* stream */
+	unsigned int stream_delay;
+
+	/* analog loopback */
+	struct snd_kcontrol_new *aloopback_ctl;
+	unsigned char aloopback_mask;
+	unsigned char aloopback_shift;
+
+	/* power management */
+	unsigned int num_pwrs;
+	unsigned int *pwr_mapping;
+	hda_nid_t *pwr_nids;
+	hda_nid_t *dac_list;
+
+	/* jack detection */
+	struct snd_array jacks;
+
+	/* events */
+	struct snd_array events;
+
+	/* playback */
+	struct hda_input_mux *mono_mux;
+	unsigned int cur_mmux;
+	struct hda_multi_out multiout;
+	hda_nid_t dac_nids[5];
+	hda_nid_t hp_dacs[5];
+	hda_nid_t speaker_dacs[5];
+
+	int volume_offset;
+
+	/* capture */
+	hda_nid_t *adc_nids;
+	unsigned int num_adcs;
+	hda_nid_t *mux_nids;
+	unsigned int num_muxes;
+	hda_nid_t *dmic_nids;
+	unsigned int num_dmics;
+	hda_nid_t *dmux_nids;
+	unsigned int num_dmuxes;
+	hda_nid_t *smux_nids;
+	unsigned int num_smuxes;
+	unsigned int num_analog_muxes;
+
+	unsigned long *capvols; /* amp-volume attr: HDA_COMPOSE_AMP_VAL() */
+	unsigned long *capsws; /* amp-mute attr: HDA_COMPOSE_AMP_VAL() */
+	unsigned int num_caps; /* number of capture volume/switch elements */
+
+	struct sigmatel_mic_route ext_mic;
+	struct sigmatel_mic_route int_mic;
+	struct sigmatel_mic_route dock_mic;
+
+	const char **spdif_labels;
+
+	hda_nid_t dig_in_nid;
+	hda_nid_t mono_nid;
+	hda_nid_t anabeep_nid;
+	hda_nid_t digbeep_nid;
+
+	/* pin widgets */
+	hda_nid_t *pin_nids;
+	unsigned int num_pins;
+
+	/* codec specific stuff */
+	struct hda_verb *init;
+	struct snd_kcontrol_new *mixer;
+
+	/* capture source */
+	struct hda_input_mux *dinput_mux;
+	unsigned int cur_dmux[2];
+	struct hda_input_mux *input_mux;
+	unsigned int cur_mux[3];
+	struct hda_input_mux *sinput_mux;
+	unsigned int cur_smux[2];
+	unsigned int cur_amux;
+	hda_nid_t *amp_nids;
+	unsigned int powerdown_adcs;
+
+	/* i/o switches */
+	unsigned int io_switch[2];
+	unsigned int clfe_swap;
+	hda_nid_t line_switch;	/* shared line-in for input and output */
+	hda_nid_t mic_switch;	/* shared mic-in for input and output */
+	hda_nid_t hp_switch; /* NID of HP as line-out */
+	unsigned int aloopback;
+
+	struct hda_pcm pcm_rec[2];	/* PCM information */
+
+	/* dynamic controls and input_mux */
+	struct auto_pin_cfg autocfg;
+	struct snd_array kctls;
+	struct hda_input_mux private_dimux;
+	struct hda_input_mux private_imux;
+	struct hda_input_mux private_smux;
+	struct hda_input_mux private_mono_mux;
+};
+
+static hda_nid_t stac9200_adc_nids[1] = {
+        0x03,
+};
+
+static hda_nid_t stac9200_mux_nids[1] = {
+        0x0c,
+};
+
+static hda_nid_t stac9200_dac_nids[1] = {
+        0x02,
+};
+
+static hda_nid_t stac92hd73xx_pwr_nids[8] = {
+	0x0a, 0x0b, 0x0c, 0xd, 0x0e,
+	0x0f, 0x10, 0x11
+};
+
+static hda_nid_t stac92hd73xx_slave_dig_outs[2] = {
+	0x26, 0,
+};
+
+static hda_nid_t stac92hd73xx_adc_nids[2] = {
+	0x1a, 0x1b
+};
+
+#define STAC92HD73XX_NUM_DMICS	2
+static hda_nid_t stac92hd73xx_dmic_nids[STAC92HD73XX_NUM_DMICS + 1] = {
+	0x13, 0x14, 0
+};
+
+#define STAC92HD73_DAC_COUNT 5
+
+static hda_nid_t stac92hd73xx_mux_nids[2] = {
+	0x20, 0x21,
+};
+
+static hda_nid_t stac92hd73xx_dmux_nids[2] = {
+	0x20, 0x21,
+};
+
+static hda_nid_t stac92hd73xx_smux_nids[2] = {
+	0x22, 0x23,
+};
+
+#define STAC92HD73XX_NUM_CAPS	2
+static unsigned long stac92hd73xx_capvols[] = {
+	HDA_COMPOSE_AMP_VAL(0x20, 3, 0, HDA_OUTPUT),
+	HDA_COMPOSE_AMP_VAL(0x21, 3, 0, HDA_OUTPUT),
+};
+#define stac92hd73xx_capsws	stac92hd73xx_capvols
+
+#define STAC92HD83_DAC_COUNT 3
+
+static hda_nid_t stac92hd83xxx_mux_nids[2] = {
+	0x17, 0x18,
+};
+
+static hda_nid_t stac92hd83xxx_adc_nids[2] = {
+	0x15, 0x16,
+};
+
+static hda_nid_t stac92hd83xxx_pwr_nids[4] = {
+	0xa, 0xb, 0xd, 0xe,
+};
+
+static hda_nid_t stac92hd83xxx_slave_dig_outs[2] = {
+	0x1e, 0,
+};
+
+static unsigned int stac92hd83xxx_pwr_mapping[4] = {
+	0x03, 0x0c, 0x20, 0x40,
+};
+
+#define STAC92HD83XXX_NUM_DMICS	 2
+static hda_nid_t stac92hd83xxx_dmic_nids[STAC92HD83XXX_NUM_DMICS + 1] = {
+	0x11, 0x20, 0
+};
+
+#define STAC92HD87B_NUM_DMICS	 1
+static hda_nid_t stac92hd87b_dmic_nids[STAC92HD87B_NUM_DMICS + 1] = {
+	0x11, 0
+};
+
+#define STAC92HD83XXX_NUM_CAPS	2
+static unsigned long stac92hd83xxx_capvols[] = {
+	HDA_COMPOSE_AMP_VAL(0x17, 3, 0, HDA_OUTPUT),
+	HDA_COMPOSE_AMP_VAL(0x18, 3, 0, HDA_OUTPUT),
+};
+#define stac92hd83xxx_capsws	stac92hd83xxx_capvols
+
+static hda_nid_t stac92hd71bxx_pwr_nids[3] = {
+	0x0a, 0x0d, 0x0f
+};
+
+static hda_nid_t stac92hd71bxx_adc_nids[2] = {
+	0x12, 0x13,
+};
+
+static hda_nid_t stac92hd71bxx_mux_nids[2] = {
+	0x1a, 0x1b
+};
+
+static hda_nid_t stac92hd71bxx_dmux_nids[2] = {
+	0x1c, 0x1d,
+};
+
+static hda_nid_t stac92hd71bxx_smux_nids[2] = {
+	0x24, 0x25,
+};
+
+#define STAC92HD71BXX_NUM_DMICS	2
+static hda_nid_t stac92hd71bxx_dmic_nids[STAC92HD71BXX_NUM_DMICS + 1] = {
+	0x18, 0x19, 0
+};
+
+static hda_nid_t stac92hd71bxx_slave_dig_outs[2] = {
+	0x22, 0
+};
+
+#define STAC92HD71BXX_NUM_CAPS		2
+static unsigned long stac92hd71bxx_capvols[] = {
+	HDA_COMPOSE_AMP_VAL(0x1c, 3, 0, HDA_OUTPUT),
+	HDA_COMPOSE_AMP_VAL(0x1d, 3, 0, HDA_OUTPUT),
+};
+#define stac92hd71bxx_capsws	stac92hd71bxx_capvols
+
+static hda_nid_t stac925x_adc_nids[1] = {
+        0x03,
+};
+
+static hda_nid_t stac925x_mux_nids[1] = {
+        0x0f,
+};
+
+static hda_nid_t stac925x_dac_nids[1] = {
+        0x02,
+};
+
+#define STAC925X_NUM_DMICS	1
+static hda_nid_t stac925x_dmic_nids[STAC925X_NUM_DMICS + 1] = {
+	0x15, 0
+};
+
+static hda_nid_t stac925x_dmux_nids[1] = {
+	0x14,
+};
+
+static unsigned long stac925x_capvols[] = {
+	HDA_COMPOSE_AMP_VAL(0x09, 3, 0, HDA_OUTPUT),
+};
+static unsigned long stac925x_capsws[] = {
+	HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
+};
+
+static hda_nid_t stac922x_adc_nids[2] = {
+        0x06, 0x07,
+};
+
+static hda_nid_t stac922x_mux_nids[2] = {
+        0x12, 0x13,
+};
+
+#define STAC922X_NUM_CAPS	2
+static unsigned long stac922x_capvols[] = {
+	HDA_COMPOSE_AMP_VAL(0x17, 3, 0, HDA_INPUT),
+	HDA_COMPOSE_AMP_VAL(0x18, 3, 0, HDA_INPUT),
+};
+#define stac922x_capsws		stac922x_capvols
+
+static hda_nid_t stac927x_slave_dig_outs[2] = {
+	0x1f, 0,
+};
+
+static hda_nid_t stac927x_adc_nids[3] = {
+        0x07, 0x08, 0x09
+};
+
+static hda_nid_t stac927x_mux_nids[3] = {
+        0x15, 0x16, 0x17
+};
+
+static hda_nid_t stac927x_smux_nids[1] = {
+	0x21,
+};
+
+static hda_nid_t stac927x_dac_nids[6] = {
+	0x02, 0x03, 0x04, 0x05, 0x06, 0
+};
+
+static hda_nid_t stac927x_dmux_nids[1] = {
+	0x1b,
+};
+
+#define STAC927X_NUM_DMICS 2
+static hda_nid_t stac927x_dmic_nids[STAC927X_NUM_DMICS + 1] = {
+	0x13, 0x14, 0
+};
+
+#define STAC927X_NUM_CAPS	3
+static unsigned long stac927x_capvols[] = {
+	HDA_COMPOSE_AMP_VAL(0x18, 3, 0, HDA_INPUT),
+	HDA_COMPOSE_AMP_VAL(0x19, 3, 0, HDA_INPUT),
+	HDA_COMPOSE_AMP_VAL(0x1a, 3, 0, HDA_INPUT),
+};
+static unsigned long stac927x_capsws[] = {
+	HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_OUTPUT),
+	HDA_COMPOSE_AMP_VAL(0x1c, 3, 0, HDA_OUTPUT),
+	HDA_COMPOSE_AMP_VAL(0x1d, 3, 0, HDA_OUTPUT),
+};
+
+static const char *stac927x_spdif_labels[5] = {
+	"Digital Playback", "ADAT", "Analog Mux 1",
+	"Analog Mux 2", "Analog Mux 3"
+};
+
+static hda_nid_t stac9205_adc_nids[2] = {
+        0x12, 0x13
+};
+
+static hda_nid_t stac9205_mux_nids[2] = {
+        0x19, 0x1a
+};
+
+static hda_nid_t stac9205_dmux_nids[1] = {
+	0x1d,
+};
+
+static hda_nid_t stac9205_smux_nids[1] = {
+	0x21,
+};
+
+#define STAC9205_NUM_DMICS	2
+static hda_nid_t stac9205_dmic_nids[STAC9205_NUM_DMICS + 1] = {
+        0x17, 0x18, 0
+};
+
+#define STAC9205_NUM_CAPS	2
+static unsigned long stac9205_capvols[] = {
+	HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_INPUT),
+	HDA_COMPOSE_AMP_VAL(0x1c, 3, 0, HDA_INPUT),
+};
+static unsigned long stac9205_capsws[] = {
+	HDA_COMPOSE_AMP_VAL(0x1d, 3, 0, HDA_OUTPUT),
+	HDA_COMPOSE_AMP_VAL(0x1e, 3, 0, HDA_OUTPUT),
+};
+
+static hda_nid_t stac9200_pin_nids[8] = {
+	0x08, 0x09, 0x0d, 0x0e, 
+	0x0f, 0x10, 0x11, 0x12,
+};
+
+static hda_nid_t stac925x_pin_nids[8] = {
+	0x07, 0x08, 0x0a, 0x0b, 
+	0x0c, 0x0d, 0x10, 0x11,
+};
+
+static hda_nid_t stac922x_pin_nids[10] = {
+	0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+	0x0f, 0x10, 0x11, 0x15, 0x1b,
+};
+
+static hda_nid_t stac92hd73xx_pin_nids[13] = {
+	0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+	0x0f, 0x10, 0x11, 0x12, 0x13,
+	0x14, 0x22, 0x23
+};
+
+static hda_nid_t stac92hd83xxx_pin_nids[10] = {
+	0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+	0x0f, 0x10, 0x11, 0x1f, 0x20,
+};
+
+static hda_nid_t stac92hd88xxx_pin_nids[10] = {
+	0x0a, 0x0b, 0x0c, 0x0d,
+	0x0f, 0x11, 0x1f, 0x20,
+};
+
+#define STAC92HD71BXX_NUM_PINS 13
+static hda_nid_t stac92hd71bxx_pin_nids_4port[STAC92HD71BXX_NUM_PINS] = {
+	0x0a, 0x0b, 0x0c, 0x0d, 0x00,
+	0x00, 0x14, 0x18, 0x19, 0x1e,
+	0x1f, 0x20, 0x27
+};
+static hda_nid_t stac92hd71bxx_pin_nids_6port[STAC92HD71BXX_NUM_PINS] = {
+	0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+	0x0f, 0x14, 0x18, 0x19, 0x1e,
+	0x1f, 0x20, 0x27
+};
+
+static hda_nid_t stac927x_pin_nids[14] = {
+	0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+	0x0f, 0x10, 0x11, 0x12, 0x13,
+	0x14, 0x21, 0x22, 0x23,
+};
+
+static hda_nid_t stac9205_pin_nids[12] = {
+	0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+	0x0f, 0x14, 0x16, 0x17, 0x18,
+	0x21, 0x22,
+};
+
+static int stac92xx_dmux_enum_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	return snd_hda_input_mux_info(spec->dinput_mux, uinfo);
+}
+
+static int stac92xx_dmux_enum_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	unsigned int dmux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	ucontrol->value.enumerated.item[0] = spec->cur_dmux[dmux_idx];
+	return 0;
+}
+
+static int stac92xx_dmux_enum_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	unsigned int dmux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	return snd_hda_input_mux_put(codec, spec->dinput_mux, ucontrol,
+			spec->dmux_nids[dmux_idx], &spec->cur_dmux[dmux_idx]);
+}
+
+static int stac92xx_smux_enum_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	return snd_hda_input_mux_info(spec->sinput_mux, uinfo);
+}
+
+static int stac92xx_smux_enum_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	unsigned int smux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	ucontrol->value.enumerated.item[0] = spec->cur_smux[smux_idx];
+	return 0;
+}
+
+static int stac92xx_smux_enum_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	struct hda_input_mux *smux = &spec->private_smux;
+	unsigned int smux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	int err, val;
+	hda_nid_t nid;
+
+	err = snd_hda_input_mux_put(codec, spec->sinput_mux, ucontrol,
+			spec->smux_nids[smux_idx], &spec->cur_smux[smux_idx]);
+	if (err < 0)
+		return err;
+
+	if (spec->spdif_mute) {
+		if (smux_idx == 0)
+			nid = spec->multiout.dig_out_nid;
+		else
+			nid = codec->slave_dig_outs[smux_idx - 1];
+		if (spec->cur_smux[smux_idx] == smux->num_items - 1)
+			val = HDA_AMP_MUTE;
+		else
+			val = 0;
+		/* un/mute SPDIF out */
+		snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0,
+					 HDA_AMP_MUTE, val);
+	}
+	return 0;
+}
+
+static unsigned int stac92xx_vref_set(struct hda_codec *codec,
+					hda_nid_t nid, unsigned int new_vref)
+{
+	int error;
+	unsigned int pincfg;
+	pincfg = snd_hda_codec_read(codec, nid, 0,
+				AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+
+	pincfg &= 0xff;
+	pincfg &= ~(AC_PINCTL_VREFEN | AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN);
+	pincfg |= new_vref;
+
+	if (new_vref == AC_PINCTL_VREF_HIZ)
+		pincfg |= AC_PINCTL_OUT_EN;
+	else
+		pincfg |= AC_PINCTL_IN_EN;
+
+	error = snd_hda_codec_write_cache(codec, nid, 0,
+					AC_VERB_SET_PIN_WIDGET_CONTROL, pincfg);
+	if (error < 0)
+		return error;
+	else
+		return 1;
+}
+
+static unsigned int stac92xx_vref_get(struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int vref;
+	vref = snd_hda_codec_read(codec, nid, 0,
+				AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+	vref &= AC_PINCTL_VREFEN;
+	return vref;
+}
+
+static int stac92xx_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	return snd_hda_input_mux_info(spec->input_mux, uinfo);
+}
+
+static int stac92xx_mux_enum_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
+	return 0;
+}
+
+static int stac92xx_mux_enum_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	const struct hda_input_mux *imux = spec->input_mux;
+	unsigned int idx, prev_idx;
+
+	idx = ucontrol->value.enumerated.item[0];
+	if (idx >= imux->num_items)
+		idx = imux->num_items - 1;
+	prev_idx = spec->cur_mux[adc_idx];
+	if (prev_idx == idx)
+		return 0;
+	if (idx < spec->num_analog_muxes) {
+		snd_hda_codec_write_cache(codec, spec->mux_nids[adc_idx], 0,
+					  AC_VERB_SET_CONNECT_SEL,
+					  imux->items[idx].index);
+		if (prev_idx >= spec->num_analog_muxes) {
+			imux = spec->dinput_mux;
+			/* 0 = analog */
+			snd_hda_codec_write_cache(codec,
+						  spec->dmux_nids[adc_idx], 0,
+						  AC_VERB_SET_CONNECT_SEL,
+						  imux->items[0].index);
+		}
+	} else {
+		imux = spec->dinput_mux;
+		snd_hda_codec_write_cache(codec, spec->dmux_nids[adc_idx], 0,
+					  AC_VERB_SET_CONNECT_SEL,
+					  imux->items[idx - 1].index);
+	}
+	spec->cur_mux[adc_idx] = idx;
+	return 1;
+}
+
+static int stac92xx_mono_mux_enum_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	return snd_hda_input_mux_info(spec->mono_mux, uinfo);
+}
+
+static int stac92xx_mono_mux_enum_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+
+	ucontrol->value.enumerated.item[0] = spec->cur_mmux;
+	return 0;
+}
+
+static int stac92xx_mono_mux_enum_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+
+	return snd_hda_input_mux_put(codec, spec->mono_mux, ucontrol,
+				     spec->mono_nid, &spec->cur_mmux);
+}
+
+#define stac92xx_aloopback_info snd_ctl_boolean_mono_info
+
+static int stac92xx_aloopback_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	struct sigmatel_spec *spec = codec->spec;
+
+	ucontrol->value.integer.value[0] = !!(spec->aloopback &
+					      (spec->aloopback_mask << idx));
+	return 0;
+}
+
+static int stac92xx_aloopback_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	unsigned int dac_mode;
+	unsigned int val, idx_val;
+
+	idx_val = spec->aloopback_mask << idx;
+	if (ucontrol->value.integer.value[0])
+		val = spec->aloopback | idx_val;
+	else
+		val = spec->aloopback & ~idx_val;
+	if (spec->aloopback == val)
+		return 0;
+
+	spec->aloopback = val;
+
+	/* Only return the bits defined by the shift value of the
+	 * first two bytes of the mask
+	 */
+	dac_mode = snd_hda_codec_read(codec, codec->afg, 0,
+				      kcontrol->private_value & 0xFFFF, 0x0);
+	dac_mode >>= spec->aloopback_shift;
+
+	if (spec->aloopback & idx_val) {
+		snd_hda_power_up(codec);
+		dac_mode |= idx_val;
+	} else {
+		snd_hda_power_down(codec);
+		dac_mode &= ~idx_val;
+	}
+
+	snd_hda_codec_write_cache(codec, codec->afg, 0,
+		kcontrol->private_value >> 16, dac_mode);
+
+	return 1;
+}
+
+static struct hda_verb stac9200_core_init[] = {
+	/* set dac0mux for dac converter */
+	{ 0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{}
+};
+
+static struct hda_verb stac9200_eapd_init[] = {
+	/* set dac0mux for dac converter */
+	{0x07, AC_VERB_SET_CONNECT_SEL, 0x00},
+	{0x08, AC_VERB_SET_EAPD_BTLENABLE, 0x02},
+	{}
+};
+
+static struct hda_verb dell_eq_core_init[] = {
+	/* set master volume to max value without distortion
+	 * and direct control */
+	{ 0x1f, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xec},
+	{}
+};
+
+static struct hda_verb stac92hd73xx_core_init[] = {
+	/* set master volume and direct control */
+	{ 0x1f, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
+	{}
+};
+
+static struct hda_verb stac92hd83xxx_core_init[] = {
+	/* power state controls amps */
+	{ 0x01, AC_VERB_SET_EAPD, 1 << 2},
+	{}
+};
+
+static struct hda_verb stac92hd71bxx_core_init[] = {
+	/* set master volume and direct control */
+	{ 0x28, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
+	{}
+};
+
+static struct hda_verb stac92hd71bxx_unmute_core_init[] = {
+	/* unmute right and left channels for nodes 0x0f, 0xa, 0x0d */
+	{ 0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{ 0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{ 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{}
+};
+
+static struct hda_verb stac925x_core_init[] = {
+	/* set dac0mux for dac converter */
+	{ 0x06, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* mute the master volume */
+	{ 0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
+	{}
+};
+
+static struct hda_verb stac922x_core_init[] = {
+	/* set master volume and direct control */	
+	{ 0x16, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
+	{}
+};
+
+static struct hda_verb d965_core_init[] = {
+	/* set master volume and direct control */	
+	{ 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
+	/* unmute node 0x1b */
+	{ 0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+	/* select node 0x03 as DAC */	
+	{ 0x0b, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{}
+};
+
+static struct hda_verb dell_3st_core_init[] = {
+	/* don't set delta bit */
+	{0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0x7f},
+	/* unmute node 0x1b */
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+	/* select node 0x03 as DAC */
+	{0x0b, AC_VERB_SET_CONNECT_SEL, 0x01},
+	{}
+};
+
+static struct hda_verb stac927x_core_init[] = {
+	/* set master volume and direct control */	
+	{ 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
+	/* enable analog pc beep path */
+	{ 0x01, AC_VERB_SET_DIGI_CONVERT_2, 1 << 5},
+	{}
+};
+
+static struct hda_verb stac927x_volknob_core_init[] = {
+	/* don't set delta bit */
+	{0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0x7f},
+	/* enable analog pc beep path */
+	{0x01, AC_VERB_SET_DIGI_CONVERT_2, 1 << 5},
+	{}
+};
+
+static struct hda_verb stac9205_core_init[] = {
+	/* set master volume and direct control */	
+	{ 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff},
+	/* enable analog pc beep path */
+	{ 0x01, AC_VERB_SET_DIGI_CONVERT_2, 1 << 5},
+	{}
+};
+
+#define STAC_MONO_MUX \
+	{ \
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name = "Mono Mux", \
+		.count = 1, \
+		.info = stac92xx_mono_mux_enum_info, \
+		.get = stac92xx_mono_mux_enum_get, \
+		.put = stac92xx_mono_mux_enum_put, \
+	}
+
+#define STAC_ANALOG_LOOPBACK(verb_read, verb_write, cnt) \
+	{ \
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name  = "Analog Loopback", \
+		.count = cnt, \
+		.info  = stac92xx_aloopback_info, \
+		.get   = stac92xx_aloopback_get, \
+		.put   = stac92xx_aloopback_put, \
+		.private_value = verb_read | (verb_write << 16), \
+	}
+
+#define DC_BIAS(xname, idx, nid) \
+	{ \
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name = xname, \
+		.index = idx, \
+		.info = stac92xx_dc_bias_info, \
+		.get = stac92xx_dc_bias_get, \
+		.put = stac92xx_dc_bias_put, \
+		.private_value = nid, \
+	}
+
+static struct snd_kcontrol_new stac9200_mixer[] = {
+	HDA_CODEC_VOLUME_MIN_MUTE("Master Playback Volume", 0xb, 0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Master Playback Switch", 0xb, 0, HDA_OUTPUT),
+	HDA_CODEC_VOLUME("Capture Volume", 0x0a, 0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x0a, 0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new stac92hd73xx_6ch_loopback[] = {
+	STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 3),
+	{}
+};
+
+static struct snd_kcontrol_new stac92hd73xx_8ch_loopback[] = {
+	STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 4),
+	{}
+};
+
+static struct snd_kcontrol_new stac92hd73xx_10ch_loopback[] = {
+	STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 5),
+	{}
+};
+
+
+static struct snd_kcontrol_new stac92hd71bxx_loopback[] = {
+	STAC_ANALOG_LOOPBACK(0xFA0, 0x7A0, 2)
+};
+
+static struct snd_kcontrol_new stac925x_mixer[] = {
+	HDA_CODEC_VOLUME_MIN_MUTE("Master Playback Volume", 0xe, 0, HDA_OUTPUT),
+	HDA_CODEC_MUTE("Master Playback Switch", 0x0e, 0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new stac9205_loopback[] = {
+	STAC_ANALOG_LOOPBACK(0xFE0, 0x7E0, 1),
+	{}
+};
+
+static struct snd_kcontrol_new stac927x_loopback[] = {
+	STAC_ANALOG_LOOPBACK(0xFEB, 0x7EB, 1),
+	{}
+};
+
+static struct snd_kcontrol_new stac_dmux_mixer = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Digital Input Source",
+	/* count set later */
+	.info = stac92xx_dmux_enum_info,
+	.get = stac92xx_dmux_enum_get,
+	.put = stac92xx_dmux_enum_put,
+};
+
+static struct snd_kcontrol_new stac_smux_mixer = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "IEC958 Playback Source",
+	/* count set later */
+	.info = stac92xx_smux_enum_info,
+	.get = stac92xx_smux_enum_get,
+	.put = stac92xx_smux_enum_put,
+};
+
+static const char *slave_vols[] = {
+	"Front Playback Volume",
+	"Surround Playback Volume",
+	"Center Playback Volume",
+	"LFE Playback Volume",
+	"Side Playback Volume",
+	"Headphone Playback Volume",
+	"Speaker Playback Volume",
+	NULL
+};
+
+static const char *slave_sws[] = {
+	"Front Playback Switch",
+	"Surround Playback Switch",
+	"Center Playback Switch",
+	"LFE Playback Switch",
+	"Side Playback Switch",
+	"Headphone Playback Switch",
+	"Speaker Playback Switch",
+	"IEC958 Playback Switch",
+	NULL
+};
+
+static void stac92xx_free_kctls(struct hda_codec *codec);
+static int stac92xx_add_jack(struct hda_codec *codec, hda_nid_t nid, int type);
+
+static int stac92xx_build_controls(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t nid;
+	int err;
+	int i;
+
+	if (spec->mixer) {
+		err = snd_hda_add_new_ctls(codec, spec->mixer);
+		if (err < 0)
+			return err;
+	}
+
+	for (i = 0; i < spec->num_mixers; i++) {
+		err = snd_hda_add_new_ctls(codec, spec->mixers[i]);
+		if (err < 0)
+			return err;
+	}
+	if (!spec->auto_mic && spec->num_dmuxes > 0 &&
+	    snd_hda_get_bool_hint(codec, "separate_dmux") == 1) {
+		stac_dmux_mixer.count = spec->num_dmuxes;
+		err = snd_hda_ctl_add(codec, 0,
+				  snd_ctl_new1(&stac_dmux_mixer, codec));
+		if (err < 0)
+			return err;
+	}
+	if (spec->num_smuxes > 0) {
+		int wcaps = get_wcaps(codec, spec->multiout.dig_out_nid);
+		struct hda_input_mux *smux = &spec->private_smux;
+		/* check for mute support on SPDIF out */
+		if (wcaps & AC_WCAP_OUT_AMP) {
+			snd_hda_add_imux_item(smux, "Off", 0, NULL);
+			spec->spdif_mute = 1;
+		}
+		stac_smux_mixer.count = spec->num_smuxes;
+		err = snd_hda_ctl_add(codec, 0,
+				  snd_ctl_new1(&stac_smux_mixer, codec));
+		if (err < 0)
+			return err;
+	}
+
+	if (spec->multiout.dig_out_nid) {
+		err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid);
+		if (err < 0)
+			return err;
+		err = snd_hda_create_spdif_share_sw(codec,
+						    &spec->multiout);
+		if (err < 0)
+			return err;
+		spec->multiout.share_spdif = 1;
+	}
+	if (spec->dig_in_nid && !(spec->gpio_dir & 0x01)) {
+		err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
+		if (err < 0)
+			return err;
+	}
+
+	/* if we have no master control, let's create it */
+	if (!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
+		unsigned int vmaster_tlv[4];
+		snd_hda_set_vmaster_tlv(codec, spec->multiout.dac_nids[0],
+					HDA_OUTPUT, vmaster_tlv);
+		/* correct volume offset */
+		vmaster_tlv[2] += vmaster_tlv[3] * spec->volume_offset;
+		/* minimum value is actually mute */
+		vmaster_tlv[3] |= TLV_DB_SCALE_MUTE;
+		err = snd_hda_add_vmaster(codec, "Master Playback Volume",
+					  vmaster_tlv, slave_vols);
+		if (err < 0)
+			return err;
+	}
+	if (!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
+		err = snd_hda_add_vmaster(codec, "Master Playback Switch",
+					  NULL, slave_sws);
+		if (err < 0)
+			return err;
+	}
+
+	if (spec->aloopback_ctl &&
+	    snd_hda_get_bool_hint(codec, "loopback") == 1) {
+		err = snd_hda_add_new_ctls(codec, spec->aloopback_ctl);
+		if (err < 0)
+			return err;
+	}
+
+	stac92xx_free_kctls(codec); /* no longer needed */
+
+	/* create jack input elements */
+	if (spec->hp_detect) {
+		for (i = 0; i < cfg->hp_outs; i++) {
+			int type = SND_JACK_HEADPHONE;
+			nid = cfg->hp_pins[i];
+			/* jack detection */
+			if (cfg->hp_outs == i)
+				type |= SND_JACK_LINEOUT;
+			err = stac92xx_add_jack(codec, nid, type);
+			if (err < 0)
+				return err;
+		}
+	}
+	for (i = 0; i < cfg->line_outs; i++) {
+		err = stac92xx_add_jack(codec, cfg->line_out_pins[i],
+					SND_JACK_LINEOUT);
+		if (err < 0)
+			return err;
+	}
+	for (i = 0; i < cfg->num_inputs; i++) {
+		nid = cfg->inputs[i].pin;
+		err = stac92xx_add_jack(codec, nid, SND_JACK_MICROPHONE);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;	
+}
+
+static unsigned int ref9200_pin_configs[8] = {
+	0x01c47010, 0x01447010, 0x0221401f, 0x01114010,
+	0x02a19020, 0x01a19021, 0x90100140, 0x01813122,
+};
+
+static unsigned int gateway9200_m4_pin_configs[8] = {
+	0x400000fe, 0x404500f4, 0x400100f0, 0x90110010,
+	0x400100f1, 0x02a1902e, 0x500000f2, 0x500000f3,
+};
+static unsigned int gateway9200_m4_2_pin_configs[8] = {
+	0x400000fe, 0x404500f4, 0x400100f0, 0x90110010,
+	0x400100f1, 0x02a1902e, 0x500000f2, 0x500000f3,
+};
+
+/*
+    STAC 9200 pin configs for
+    102801A8
+    102801DE
+    102801E8
+*/
+static unsigned int dell9200_d21_pin_configs[8] = {
+	0x400001f0, 0x400001f1, 0x02214030, 0x01014010, 
+	0x02a19020, 0x01a19021, 0x90100140, 0x01813122,
+};
+
+/* 
+    STAC 9200 pin configs for
+    102801C0
+    102801C1
+*/
+static unsigned int dell9200_d22_pin_configs[8] = {
+	0x400001f0, 0x400001f1, 0x0221401f, 0x01014010, 
+	0x01813020, 0x02a19021, 0x90100140, 0x400001f2,
+};
+
+/* 
+    STAC 9200 pin configs for
+    102801C4 (Dell Dimension E310)
+    102801C5
+    102801C7
+    102801D9
+    102801DA
+    102801E3
+*/
+static unsigned int dell9200_d23_pin_configs[8] = {
+	0x400001f0, 0x400001f1, 0x0221401f, 0x01014010, 
+	0x01813020, 0x01a19021, 0x90100140, 0x400001f2, 
+};
+
+
+/* 
+    STAC 9200-32 pin configs for
+    102801B5 (Dell Inspiron 630m)
+    102801D8 (Dell Inspiron 640m)
+*/
+static unsigned int dell9200_m21_pin_configs[8] = {
+	0x40c003fa, 0x03441340, 0x0321121f, 0x90170310,
+	0x408003fb, 0x03a11020, 0x401003fc, 0x403003fd,
+};
+
+/* 
+    STAC 9200-32 pin configs for
+    102801C2 (Dell Latitude D620)
+    102801C8 
+    102801CC (Dell Latitude D820)
+    102801D4 
+    102801D6 
+*/
+static unsigned int dell9200_m22_pin_configs[8] = {
+	0x40c003fa, 0x0144131f, 0x0321121f, 0x90170310, 
+	0x90a70321, 0x03a11020, 0x401003fb, 0x40f000fc,
+};
+
+/* 
+    STAC 9200-32 pin configs for
+    102801CE (Dell XPS M1710)
+    102801CF (Dell Precision M90)
+*/
+static unsigned int dell9200_m23_pin_configs[8] = {
+	0x40c003fa, 0x01441340, 0x0421421f, 0x90170310,
+	0x408003fb, 0x04a1102e, 0x90170311, 0x403003fc,
+};
+
+/*
+    STAC 9200-32 pin configs for 
+    102801C9
+    102801CA
+    102801CB (Dell Latitude 120L)
+    102801D3
+*/
+static unsigned int dell9200_m24_pin_configs[8] = {
+	0x40c003fa, 0x404003fb, 0x0321121f, 0x90170310, 
+	0x408003fc, 0x03a11020, 0x401003fd, 0x403003fe, 
+};
+
+/*
+    STAC 9200-32 pin configs for
+    102801BD (Dell Inspiron E1505n)
+    102801EE
+    102801EF
+*/
+static unsigned int dell9200_m25_pin_configs[8] = {
+	0x40c003fa, 0x01441340, 0x0421121f, 0x90170310, 
+	0x408003fb, 0x04a11020, 0x401003fc, 0x403003fd,
+};
+
+/*
+    STAC 9200-32 pin configs for
+    102801F5 (Dell Inspiron 1501)
+    102801F6
+*/
+static unsigned int dell9200_m26_pin_configs[8] = {
+	0x40c003fa, 0x404003fb, 0x0421121f, 0x90170310, 
+	0x408003fc, 0x04a11020, 0x401003fd, 0x403003fe,
+};
+
+/*
+    STAC 9200-32
+    102801CD (Dell Inspiron E1705/9400)
+*/
+static unsigned int dell9200_m27_pin_configs[8] = {
+	0x40c003fa, 0x01441340, 0x0421121f, 0x90170310,
+	0x90170310, 0x04a11020, 0x90170310, 0x40f003fc,
+};
+
+static unsigned int oqo9200_pin_configs[8] = {
+	0x40c000f0, 0x404000f1, 0x0221121f, 0x02211210,
+	0x90170111, 0x90a70120, 0x400000f2, 0x400000f3,
+};
+
+
+static unsigned int *stac9200_brd_tbl[STAC_9200_MODELS] = {
+	[STAC_REF] = ref9200_pin_configs,
+	[STAC_9200_OQO] = oqo9200_pin_configs,
+	[STAC_9200_DELL_D21] = dell9200_d21_pin_configs,
+	[STAC_9200_DELL_D22] = dell9200_d22_pin_configs,
+	[STAC_9200_DELL_D23] = dell9200_d23_pin_configs,
+	[STAC_9200_DELL_M21] = dell9200_m21_pin_configs,
+	[STAC_9200_DELL_M22] = dell9200_m22_pin_configs,
+	[STAC_9200_DELL_M23] = dell9200_m23_pin_configs,
+	[STAC_9200_DELL_M24] = dell9200_m24_pin_configs,
+	[STAC_9200_DELL_M25] = dell9200_m25_pin_configs,
+	[STAC_9200_DELL_M26] = dell9200_m26_pin_configs,
+	[STAC_9200_DELL_M27] = dell9200_m27_pin_configs,
+	[STAC_9200_M4] = gateway9200_m4_pin_configs,
+	[STAC_9200_M4_2] = gateway9200_m4_2_pin_configs,
+	[STAC_9200_PANASONIC] = ref9200_pin_configs,
+};
+
+static const char *stac9200_models[STAC_9200_MODELS] = {
+	[STAC_AUTO] = "auto",
+	[STAC_REF] = "ref",
+	[STAC_9200_OQO] = "oqo",
+	[STAC_9200_DELL_D21] = "dell-d21",
+	[STAC_9200_DELL_D22] = "dell-d22",
+	[STAC_9200_DELL_D23] = "dell-d23",
+	[STAC_9200_DELL_M21] = "dell-m21",
+	[STAC_9200_DELL_M22] = "dell-m22",
+	[STAC_9200_DELL_M23] = "dell-m23",
+	[STAC_9200_DELL_M24] = "dell-m24",
+	[STAC_9200_DELL_M25] = "dell-m25",
+	[STAC_9200_DELL_M26] = "dell-m26",
+	[STAC_9200_DELL_M27] = "dell-m27",
+	[STAC_9200_M4] = "gateway-m4",
+	[STAC_9200_M4_2] = "gateway-m4-2",
+	[STAC_9200_PANASONIC] = "panasonic",
+};
+
+static struct snd_pci_quirk stac9200_cfg_tbl[] = {
+	/* SigmaTel reference board */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+		      "DFI LanParty", STAC_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+		      "DFI LanParty", STAC_REF),
+	/* Dell laptops have BIOS problem */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a8,
+		      "unknown Dell", STAC_9200_DELL_D21),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01b5,
+		      "Dell Inspiron 630m", STAC_9200_DELL_M21),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01bd,
+		      "Dell Inspiron E1505n", STAC_9200_DELL_M25),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c0,
+		      "unknown Dell", STAC_9200_DELL_D22),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c1,
+		      "unknown Dell", STAC_9200_DELL_D22),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c2,
+		      "Dell Latitude D620", STAC_9200_DELL_M22),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c5,
+		      "unknown Dell", STAC_9200_DELL_D23),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c7,
+		      "unknown Dell", STAC_9200_DELL_D23),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c8,
+		      "unknown Dell", STAC_9200_DELL_M22),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c9,
+		      "unknown Dell", STAC_9200_DELL_M24),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ca,
+		      "unknown Dell", STAC_9200_DELL_M24),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cb,
+		      "Dell Latitude 120L", STAC_9200_DELL_M24),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cc,
+		      "Dell Latitude D820", STAC_9200_DELL_M22),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cd,
+		      "Dell Inspiron E1705/9400", STAC_9200_DELL_M27),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ce,
+		      "Dell XPS M1710", STAC_9200_DELL_M23),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cf,
+		      "Dell Precision M90", STAC_9200_DELL_M23),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d3,
+		      "unknown Dell", STAC_9200_DELL_M22),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d4,
+		      "unknown Dell", STAC_9200_DELL_M22),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d6,
+		      "unknown Dell", STAC_9200_DELL_M22),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d8,
+		      "Dell Inspiron 640m", STAC_9200_DELL_M21),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d9,
+		      "unknown Dell", STAC_9200_DELL_D23),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01da,
+		      "unknown Dell", STAC_9200_DELL_D23),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01de,
+		      "unknown Dell", STAC_9200_DELL_D21),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01e3,
+		      "unknown Dell", STAC_9200_DELL_D23),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01e8,
+		      "unknown Dell", STAC_9200_DELL_D21),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ee,
+		      "unknown Dell", STAC_9200_DELL_M25),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ef,
+		      "unknown Dell", STAC_9200_DELL_M25),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f5,
+		      "Dell Inspiron 1501", STAC_9200_DELL_M26),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f6,
+		      "unknown Dell", STAC_9200_DELL_M26),
+	/* Panasonic */
+	SND_PCI_QUIRK(0x10f7, 0x8338, "Panasonic CF-74", STAC_9200_PANASONIC),
+	/* Gateway machines needs EAPD to be set on resume */
+	SND_PCI_QUIRK(0x107b, 0x0205, "Gateway S-7110M", STAC_9200_M4),
+	SND_PCI_QUIRK(0x107b, 0x0317, "Gateway MT3423, MX341*", STAC_9200_M4_2),
+	SND_PCI_QUIRK(0x107b, 0x0318, "Gateway ML3019, MT3707", STAC_9200_M4_2),
+	/* OQO Mobile */
+	SND_PCI_QUIRK(0x1106, 0x3288, "OQO Model 2", STAC_9200_OQO),
+	{} /* terminator */
+};
+
+static unsigned int ref925x_pin_configs[8] = {
+	0x40c003f0, 0x424503f2, 0x01813022, 0x02a19021,
+	0x90a70320, 0x02214210, 0x01019020, 0x9033032e,
+};
+
+static unsigned int stac925xM1_pin_configs[8] = {
+	0x40c003f4, 0x424503f2, 0x400000f3, 0x02a19020,
+	0x40a000f0, 0x90100210, 0x400003f1, 0x9033032e,
+};
+
+static unsigned int stac925xM1_2_pin_configs[8] = {
+	0x40c003f4, 0x424503f2, 0x400000f3, 0x02a19020,
+	0x40a000f0, 0x90100210, 0x400003f1, 0x9033032e,
+};
+
+static unsigned int stac925xM2_pin_configs[8] = {
+	0x40c003f4, 0x424503f2, 0x400000f3, 0x02a19020,
+	0x40a000f0, 0x90100210, 0x400003f1, 0x9033032e,
+};
+
+static unsigned int stac925xM2_2_pin_configs[8] = {
+	0x40c003f4, 0x424503f2, 0x400000f3, 0x02a19020,
+	0x40a000f0, 0x90100210, 0x400003f1, 0x9033032e,
+};
+
+static unsigned int stac925xM3_pin_configs[8] = {
+	0x40c003f4, 0x424503f2, 0x400000f3, 0x02a19020,
+	0x40a000f0, 0x90100210, 0x400003f1, 0x503303f3,
+};
+
+static unsigned int stac925xM5_pin_configs[8] = {
+	0x40c003f4, 0x424503f2, 0x400000f3, 0x02a19020,
+	0x40a000f0, 0x90100210, 0x400003f1, 0x9033032e,
+};
+
+static unsigned int stac925xM6_pin_configs[8] = {
+	0x40c003f4, 0x424503f2, 0x400000f3, 0x02a19020,
+	0x40a000f0, 0x90100210, 0x400003f1, 0x90330320,
+};
+
+static unsigned int *stac925x_brd_tbl[STAC_925x_MODELS] = {
+	[STAC_REF] = ref925x_pin_configs,
+	[STAC_M1] = stac925xM1_pin_configs,
+	[STAC_M1_2] = stac925xM1_2_pin_configs,
+	[STAC_M2] = stac925xM2_pin_configs,
+	[STAC_M2_2] = stac925xM2_2_pin_configs,
+	[STAC_M3] = stac925xM3_pin_configs,
+	[STAC_M5] = stac925xM5_pin_configs,
+	[STAC_M6] = stac925xM6_pin_configs,
+};
+
+static const char *stac925x_models[STAC_925x_MODELS] = {
+	[STAC_925x_AUTO] = "auto",
+	[STAC_REF] = "ref",
+	[STAC_M1] = "m1",
+	[STAC_M1_2] = "m1-2",
+	[STAC_M2] = "m2",
+	[STAC_M2_2] = "m2-2",
+	[STAC_M3] = "m3",
+	[STAC_M5] = "m5",
+	[STAC_M6] = "m6",
+};
+
+static struct snd_pci_quirk stac925x_codec_id_cfg_tbl[] = {
+	SND_PCI_QUIRK(0x107b, 0x0316, "Gateway M255", STAC_M2),
+	SND_PCI_QUIRK(0x107b, 0x0366, "Gateway MP6954", STAC_M5),
+	SND_PCI_QUIRK(0x107b, 0x0461, "Gateway NX560XL", STAC_M1),
+	SND_PCI_QUIRK(0x107b, 0x0681, "Gateway NX860", STAC_M2),
+	SND_PCI_QUIRK(0x107b, 0x0367, "Gateway MX6453", STAC_M1_2),
+	/* Not sure about the brand name for those */
+	SND_PCI_QUIRK(0x107b, 0x0281, "Gateway mobile", STAC_M1),
+	SND_PCI_QUIRK(0x107b, 0x0507, "Gateway mobile", STAC_M3),
+	SND_PCI_QUIRK(0x107b, 0x0281, "Gateway mobile", STAC_M6),
+	SND_PCI_QUIRK(0x107b, 0x0685, "Gateway mobile", STAC_M2_2),
+	{} /* terminator */
+};
+
+static struct snd_pci_quirk stac925x_cfg_tbl[] = {
+	/* SigmaTel reference board */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, "DFI LanParty", STAC_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101, "DFI LanParty", STAC_REF),
+	SND_PCI_QUIRK(0x8384, 0x7632, "Stac9202 Reference Board", STAC_REF),
+
+	/* Default table for unknown ID */
+	SND_PCI_QUIRK(0x1002, 0x437b, "Gateway mobile", STAC_M2_2),
+
+	{} /* terminator */
+};
+
+static unsigned int ref92hd73xx_pin_configs[13] = {
+	0x02214030, 0x02a19040, 0x01a19020, 0x02214030,
+	0x0181302e, 0x01014010, 0x01014020, 0x01014030,
+	0x02319040, 0x90a000f0, 0x90a000f0, 0x01452050,
+	0x01452050,
+};
+
+static unsigned int dell_m6_pin_configs[13] = {
+	0x0321101f, 0x4f00000f, 0x4f0000f0, 0x90170110,
+	0x03a11020, 0x0321101f, 0x4f0000f0, 0x4f0000f0,
+	0x4f0000f0, 0x90a60160, 0x4f0000f0, 0x4f0000f0,
+	0x4f0000f0,
+};
+
+static unsigned int alienware_m17x_pin_configs[13] = {
+	0x0321101f, 0x0321101f, 0x03a11020, 0x03014020,
+	0x90170110, 0x4f0000f0, 0x4f0000f0, 0x4f0000f0,
+	0x4f0000f0, 0x90a60160, 0x4f0000f0, 0x4f0000f0,
+	0x904601b0,
+};
+
+static unsigned int intel_dg45id_pin_configs[13] = {
+	0x02214230, 0x02A19240, 0x01013214, 0x01014210,
+	0x01A19250, 0x01011212, 0x01016211
+};
+
+static unsigned int *stac92hd73xx_brd_tbl[STAC_92HD73XX_MODELS] = {
+	[STAC_92HD73XX_REF]	= ref92hd73xx_pin_configs,
+	[STAC_DELL_M6_AMIC]	= dell_m6_pin_configs,
+	[STAC_DELL_M6_DMIC]	= dell_m6_pin_configs,
+	[STAC_DELL_M6_BOTH]	= dell_m6_pin_configs,
+	[STAC_DELL_EQ]	= dell_m6_pin_configs,
+	[STAC_ALIENWARE_M17X]	= alienware_m17x_pin_configs,
+	[STAC_92HD73XX_INTEL]	= intel_dg45id_pin_configs,
+};
+
+static const char *stac92hd73xx_models[STAC_92HD73XX_MODELS] = {
+	[STAC_92HD73XX_AUTO] = "auto",
+	[STAC_92HD73XX_NO_JD] = "no-jd",
+	[STAC_92HD73XX_REF] = "ref",
+	[STAC_92HD73XX_INTEL] = "intel",
+	[STAC_DELL_M6_AMIC] = "dell-m6-amic",
+	[STAC_DELL_M6_DMIC] = "dell-m6-dmic",
+	[STAC_DELL_M6_BOTH] = "dell-m6",
+	[STAC_DELL_EQ] = "dell-eq",
+	[STAC_ALIENWARE_M17X] = "alienware",
+};
+
+static struct snd_pci_quirk stac92hd73xx_cfg_tbl[] = {
+	/* SigmaTel reference board */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+				"DFI LanParty", STAC_92HD73XX_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+				"DFI LanParty", STAC_92HD73XX_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5002,
+				"Intel DG45ID", STAC_92HD73XX_INTEL),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5003,
+				"Intel DG45FC", STAC_92HD73XX_INTEL),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0254,
+				"Dell Studio 1535", STAC_DELL_M6_DMIC),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0255,
+				"unknown Dell", STAC_DELL_M6_DMIC),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0256,
+				"unknown Dell", STAC_DELL_M6_BOTH),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0257,
+				"unknown Dell", STAC_DELL_M6_BOTH),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x025e,
+				"unknown Dell", STAC_DELL_M6_AMIC),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x025f,
+				"unknown Dell", STAC_DELL_M6_AMIC),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0271,
+				"unknown Dell", STAC_DELL_M6_DMIC),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0272,
+				"unknown Dell", STAC_DELL_M6_DMIC),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x029f,
+				"Dell Studio 1537", STAC_DELL_M6_DMIC),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02a0,
+				"Dell Studio 17", STAC_DELL_M6_DMIC),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02be,
+				"Dell Studio 1555", STAC_DELL_M6_DMIC),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02bd,
+				"Dell Studio 1557", STAC_DELL_M6_DMIC),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02fe,
+				"Dell Studio XPS 1645", STAC_DELL_M6_BOTH),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0413,
+				"Dell Studio 1558", STAC_DELL_M6_BOTH),
+	{} /* terminator */
+};
+
+static struct snd_pci_quirk stac92hd73xx_codec_id_cfg_tbl[] = {
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02a1,
+		      "Alienware M17x", STAC_ALIENWARE_M17X),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x043a,
+		      "Alienware M17x", STAC_ALIENWARE_M17X),
+	{} /* terminator */
+};
+
+static unsigned int ref92hd83xxx_pin_configs[10] = {
+	0x02214030, 0x02211010, 0x02a19020, 0x02170130,
+	0x01014050, 0x01819040, 0x01014020, 0x90a3014e,
+	0x01451160, 0x98560170,
+};
+
+static unsigned int dell_s14_pin_configs[10] = {
+	0x0221403f, 0x0221101f, 0x02a19020, 0x90170110,
+	0x40f000f0, 0x40f000f0, 0x40f000f0, 0x90a60160,
+	0x40f000f0, 0x40f000f0,
+};
+
+static unsigned int hp_dv7_4000_pin_configs[10] = {
+	0x03a12050, 0x0321201f, 0x40f000f0, 0x90170110,
+	0x40f000f0, 0x40f000f0, 0x90170110, 0xd5a30140,
+	0x40f000f0, 0x40f000f0,
+};
+
+static unsigned int *stac92hd83xxx_brd_tbl[STAC_92HD83XXX_MODELS] = {
+	[STAC_92HD83XXX_REF] = ref92hd83xxx_pin_configs,
+	[STAC_92HD83XXX_PWR_REF] = ref92hd83xxx_pin_configs,
+	[STAC_DELL_S14] = dell_s14_pin_configs,
+	[STAC_HP_DV7_4000] = hp_dv7_4000_pin_configs,
+};
+
+static const char *stac92hd83xxx_models[STAC_92HD83XXX_MODELS] = {
+	[STAC_92HD83XXX_AUTO] = "auto",
+	[STAC_92HD83XXX_REF] = "ref",
+	[STAC_92HD83XXX_PWR_REF] = "mic-ref",
+	[STAC_DELL_S14] = "dell-s14",
+	[STAC_92HD83XXX_HP] = "hp",
+	[STAC_HP_DV7_4000] = "hp-dv7-4000",
+};
+
+static struct snd_pci_quirk stac92hd83xxx_cfg_tbl[] = {
+	/* SigmaTel reference board */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+		      "DFI LanParty", STAC_92HD83XXX_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+		      "DFI LanParty", STAC_92HD83XXX_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02ba,
+		      "unknown Dell", STAC_DELL_S14),
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xff00, 0x3600,
+		      "HP", STAC_92HD83XXX_HP),
+	{} /* terminator */
+};
+
+static unsigned int ref92hd71bxx_pin_configs[STAC92HD71BXX_NUM_PINS] = {
+	0x02214030, 0x02a19040, 0x01a19020, 0x01014010,
+	0x0181302e, 0x01014010, 0x01019020, 0x90a000f0,
+	0x90a000f0, 0x01452050, 0x01452050, 0x00000000,
+	0x00000000
+};
+
+static unsigned int dell_m4_1_pin_configs[STAC92HD71BXX_NUM_PINS] = {
+	0x0421101f, 0x04a11221, 0x40f000f0, 0x90170110,
+	0x23a1902e, 0x23014250, 0x40f000f0, 0x90a000f0,
+	0x40f000f0, 0x4f0000f0, 0x4f0000f0, 0x00000000,
+	0x00000000
+};
+
+static unsigned int dell_m4_2_pin_configs[STAC92HD71BXX_NUM_PINS] = {
+	0x0421101f, 0x04a11221, 0x90a70330, 0x90170110,
+	0x23a1902e, 0x23014250, 0x40f000f0, 0x40f000f0,
+	0x40f000f0, 0x044413b0, 0x044413b0, 0x00000000,
+	0x00000000
+};
+
+static unsigned int dell_m4_3_pin_configs[STAC92HD71BXX_NUM_PINS] = {
+	0x0421101f, 0x04a11221, 0x90a70330, 0x90170110,
+	0x40f000f0, 0x40f000f0, 0x40f000f0, 0x90a000f0,
+	0x40f000f0, 0x044413b0, 0x044413b0, 0x00000000,
+	0x00000000
+};
+
+static unsigned int *stac92hd71bxx_brd_tbl[STAC_92HD71BXX_MODELS] = {
+	[STAC_92HD71BXX_REF] = ref92hd71bxx_pin_configs,
+	[STAC_DELL_M4_1]	= dell_m4_1_pin_configs,
+	[STAC_DELL_M4_2]	= dell_m4_2_pin_configs,
+	[STAC_DELL_M4_3]	= dell_m4_3_pin_configs,
+	[STAC_HP_M4]		= NULL,
+	[STAC_HP_DV4]		= NULL,
+	[STAC_HP_DV5]		= NULL,
+	[STAC_HP_HDX]           = NULL,
+	[STAC_HP_DV4_1222NR]	= NULL,
+};
+
+static const char *stac92hd71bxx_models[STAC_92HD71BXX_MODELS] = {
+	[STAC_92HD71BXX_AUTO] = "auto",
+	[STAC_92HD71BXX_REF] = "ref",
+	[STAC_DELL_M4_1] = "dell-m4-1",
+	[STAC_DELL_M4_2] = "dell-m4-2",
+	[STAC_DELL_M4_3] = "dell-m4-3",
+	[STAC_HP_M4] = "hp-m4",
+	[STAC_HP_DV4] = "hp-dv4",
+	[STAC_HP_DV5] = "hp-dv5",
+	[STAC_HP_HDX] = "hp-hdx",
+	[STAC_HP_DV4_1222NR] = "hp-dv4-1222nr",
+};
+
+static struct snd_pci_quirk stac92hd71bxx_cfg_tbl[] = {
+	/* SigmaTel reference board */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+		      "DFI LanParty", STAC_92HD71BXX_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+		      "DFI LanParty", STAC_92HD71BXX_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x30fb,
+		      "HP dv4-1222nr", STAC_HP_DV4_1222NR),
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x1720,
+			  "HP", STAC_HP_DV5),
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x3080,
+		      "HP", STAC_HP_DV5),
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x30f0,
+		      "HP dv4-7", STAC_HP_DV4),
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x3600,
+		      "HP dv4-7", STAC_HP_DV5),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3610,
+		      "HP HDX", STAC_HP_HDX),  /* HDX18 */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x361a,
+		      "HP mini 1000", STAC_HP_M4),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x361b,
+		      "HP HDX", STAC_HP_HDX),  /* HDX16 */
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x3620,
+		      "HP dv6", STAC_HP_DV5),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3061,
+		      "HP dv6", STAC_HP_DV5), /* HP dv6-1110ax */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x363e,
+		      "HP DV6", STAC_HP_DV5),
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_HP, 0xfff0, 0x7010,
+		      "HP", STAC_HP_DV5),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0233,
+				"unknown Dell", STAC_DELL_M4_1),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0234,
+				"unknown Dell", STAC_DELL_M4_1),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0250,
+				"unknown Dell", STAC_DELL_M4_1),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x024f,
+				"unknown Dell", STAC_DELL_M4_1),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x024d,
+				"unknown Dell", STAC_DELL_M4_1),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0251,
+				"unknown Dell", STAC_DELL_M4_1),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0277,
+				"unknown Dell", STAC_DELL_M4_1),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0263,
+				"unknown Dell", STAC_DELL_M4_2),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0265,
+				"unknown Dell", STAC_DELL_M4_2),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0262,
+				"unknown Dell", STAC_DELL_M4_2),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0264,
+				"unknown Dell", STAC_DELL_M4_2),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02aa,
+				"unknown Dell", STAC_DELL_M4_3),
+	{} /* terminator */
+};
+
+static unsigned int ref922x_pin_configs[10] = {
+	0x01014010, 0x01016011, 0x01012012, 0x0221401f,
+	0x01813122, 0x01011014, 0x01441030, 0x01c41030,
+	0x40000100, 0x40000100,
+};
+
+/*
+    STAC 922X pin configs for
+    102801A7
+    102801AB
+    102801A9
+    102801D1
+    102801D2
+*/
+static unsigned int dell_922x_d81_pin_configs[10] = {
+	0x02214030, 0x01a19021, 0x01111012, 0x01114010,
+	0x02a19020, 0x01117011, 0x400001f0, 0x400001f1,
+	0x01813122, 0x400001f2,
+};
+
+/*
+    STAC 922X pin configs for
+    102801AC
+    102801D0
+*/
+static unsigned int dell_922x_d82_pin_configs[10] = {
+	0x02214030, 0x01a19021, 0x01111012, 0x01114010,
+	0x02a19020, 0x01117011, 0x01451140, 0x400001f0,
+	0x01813122, 0x400001f1,
+};
+
+/*
+    STAC 922X pin configs for
+    102801BF
+*/
+static unsigned int dell_922x_m81_pin_configs[10] = {
+	0x0321101f, 0x01112024, 0x01111222, 0x91174220,
+	0x03a11050, 0x01116221, 0x90a70330, 0x01452340, 
+	0x40C003f1, 0x405003f0,
+};
+
+/*
+    STAC 9221 A1 pin configs for
+    102801D7 (Dell XPS M1210)
+*/
+static unsigned int dell_922x_m82_pin_configs[10] = {
+	0x02211211, 0x408103ff, 0x02a1123e, 0x90100310, 
+	0x408003f1, 0x0221121f, 0x03451340, 0x40c003f2, 
+	0x508003f3, 0x405003f4, 
+};
+
+static unsigned int d945gtp3_pin_configs[10] = {
+	0x0221401f, 0x01a19022, 0x01813021, 0x01014010,
+	0x40000100, 0x40000100, 0x40000100, 0x40000100,
+	0x02a19120, 0x40000100,
+};
+
+static unsigned int d945gtp5_pin_configs[10] = {
+	0x0221401f, 0x01011012, 0x01813024, 0x01014010,
+	0x01a19021, 0x01016011, 0x01452130, 0x40000100,
+	0x02a19320, 0x40000100,
+};
+
+static unsigned int intel_mac_v1_pin_configs[10] = {
+	0x0121e21f, 0x400000ff, 0x9017e110, 0x400000fd,
+	0x400000fe, 0x0181e020, 0x1145e030, 0x11c5e240,
+	0x400000fc, 0x400000fb,
+};
+
+static unsigned int intel_mac_v2_pin_configs[10] = {
+	0x0121e21f, 0x90a7012e, 0x9017e110, 0x400000fd,
+	0x400000fe, 0x0181e020, 0x1145e230, 0x500000fa,
+	0x400000fc, 0x400000fb,
+};
+
+static unsigned int intel_mac_v3_pin_configs[10] = {
+	0x0121e21f, 0x90a7012e, 0x9017e110, 0x400000fd,
+	0x400000fe, 0x0181e020, 0x1145e230, 0x11c5e240,
+	0x400000fc, 0x400000fb,
+};
+
+static unsigned int intel_mac_v4_pin_configs[10] = {
+	0x0321e21f, 0x03a1e02e, 0x9017e110, 0x9017e11f,
+	0x400000fe, 0x0381e020, 0x1345e230, 0x13c5e240,
+	0x400000fc, 0x400000fb,
+};
+
+static unsigned int intel_mac_v5_pin_configs[10] = {
+	0x0321e21f, 0x03a1e02e, 0x9017e110, 0x9017e11f,
+	0x400000fe, 0x0381e020, 0x1345e230, 0x13c5e240,
+	0x400000fc, 0x400000fb,
+};
+
+static unsigned int ecs202_pin_configs[10] = {
+	0x0221401f, 0x02a19020, 0x01a19020, 0x01114010,
+	0x408000f0, 0x01813022, 0x074510a0, 0x40c400f1,
+	0x9037012e, 0x40e000f2,
+};
+
+static unsigned int *stac922x_brd_tbl[STAC_922X_MODELS] = {
+	[STAC_D945_REF] = ref922x_pin_configs,
+	[STAC_D945GTP3] = d945gtp3_pin_configs,
+	[STAC_D945GTP5] = d945gtp5_pin_configs,
+	[STAC_INTEL_MAC_V1] = intel_mac_v1_pin_configs,
+	[STAC_INTEL_MAC_V2] = intel_mac_v2_pin_configs,
+	[STAC_INTEL_MAC_V3] = intel_mac_v3_pin_configs,
+	[STAC_INTEL_MAC_V4] = intel_mac_v4_pin_configs,
+	[STAC_INTEL_MAC_V5] = intel_mac_v5_pin_configs,
+	[STAC_INTEL_MAC_AUTO] = intel_mac_v3_pin_configs,
+	/* for backward compatibility */
+	[STAC_MACMINI] = intel_mac_v3_pin_configs,
+	[STAC_MACBOOK] = intel_mac_v5_pin_configs,
+	[STAC_MACBOOK_PRO_V1] = intel_mac_v3_pin_configs,
+	[STAC_MACBOOK_PRO_V2] = intel_mac_v3_pin_configs,
+	[STAC_IMAC_INTEL] = intel_mac_v2_pin_configs,
+	[STAC_IMAC_INTEL_20] = intel_mac_v3_pin_configs,
+	[STAC_ECS_202] = ecs202_pin_configs,
+	[STAC_922X_DELL_D81] = dell_922x_d81_pin_configs,
+	[STAC_922X_DELL_D82] = dell_922x_d82_pin_configs,	
+	[STAC_922X_DELL_M81] = dell_922x_m81_pin_configs,
+	[STAC_922X_DELL_M82] = dell_922x_m82_pin_configs,	
+};
+
+static const char *stac922x_models[STAC_922X_MODELS] = {
+	[STAC_922X_AUTO] = "auto",
+	[STAC_D945_REF]	= "ref",
+	[STAC_D945GTP5]	= "5stack",
+	[STAC_D945GTP3]	= "3stack",
+	[STAC_INTEL_MAC_V1] = "intel-mac-v1",
+	[STAC_INTEL_MAC_V2] = "intel-mac-v2",
+	[STAC_INTEL_MAC_V3] = "intel-mac-v3",
+	[STAC_INTEL_MAC_V4] = "intel-mac-v4",
+	[STAC_INTEL_MAC_V5] = "intel-mac-v5",
+	[STAC_INTEL_MAC_AUTO] = "intel-mac-auto",
+	/* for backward compatibility */
+	[STAC_MACMINI]	= "macmini",
+	[STAC_MACBOOK]	= "macbook",
+	[STAC_MACBOOK_PRO_V1]	= "macbook-pro-v1",
+	[STAC_MACBOOK_PRO_V2]	= "macbook-pro",
+	[STAC_IMAC_INTEL] = "imac-intel",
+	[STAC_IMAC_INTEL_20] = "imac-intel-20",
+	[STAC_ECS_202] = "ecs202",
+	[STAC_922X_DELL_D81] = "dell-d81",
+	[STAC_922X_DELL_D82] = "dell-d82",
+	[STAC_922X_DELL_M81] = "dell-m81",
+	[STAC_922X_DELL_M82] = "dell-m82",
+};
+
+static struct snd_pci_quirk stac922x_cfg_tbl[] = {
+	/* SigmaTel reference board */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+		      "DFI LanParty", STAC_D945_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+		      "DFI LanParty", STAC_D945_REF),
+	/* Intel 945G based systems */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0101,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0202,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0606,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0601,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0111,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1115,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1116,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1117,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1118,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1119,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x8826,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5049,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5055,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5048,
+		      "Intel D945G", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0110,
+		      "Intel D945G", STAC_D945GTP3),
+	/* Intel D945G 5-stack systems */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0404,
+		      "Intel D945G", STAC_D945GTP5),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0303,
+		      "Intel D945G", STAC_D945GTP5),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0013,
+		      "Intel D945G", STAC_D945GTP5),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0417,
+		      "Intel D945G", STAC_D945GTP5),
+	/* Intel 945P based systems */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0b0b,
+		      "Intel D945P", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0112,
+		      "Intel D945P", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0d0d,
+		      "Intel D945P", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0909,
+		      "Intel D945P", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0505,
+		      "Intel D945P", STAC_D945GTP3),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0707,
+		      "Intel D945P", STAC_D945GTP5),
+	/* other intel */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0204,
+		      "Intel D945", STAC_D945_REF),
+	/* other systems  */
+	/* Apple Intel Mac (Mac Mini, MacBook, MacBook Pro...) */
+	SND_PCI_QUIRK(0x8384, 0x7680,
+		      "Mac", STAC_INTEL_MAC_AUTO),
+	/* Dell systems  */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a7,
+		      "unknown Dell", STAC_922X_DELL_D81),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a9,
+		      "unknown Dell", STAC_922X_DELL_D81),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ab,
+		      "unknown Dell", STAC_922X_DELL_D81),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ac,
+		      "unknown Dell", STAC_922X_DELL_D82),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01bf,
+		      "unknown Dell", STAC_922X_DELL_M81),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d0,
+		      "unknown Dell", STAC_922X_DELL_D82),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d1,
+		      "unknown Dell", STAC_922X_DELL_D81),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d2,
+		      "unknown Dell", STAC_922X_DELL_D81),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d7,
+		      "Dell XPS M1210", STAC_922X_DELL_M82),
+	/* ECS/PC Chips boards */
+	SND_PCI_QUIRK_MASK(0x1019, 0xf000, 0x2000,
+		      "ECS/PC chips", STAC_ECS_202),
+	{} /* terminator */
+};
+
+static unsigned int ref927x_pin_configs[14] = {
+	0x02214020, 0x02a19080, 0x0181304e, 0x01014010,
+	0x01a19040, 0x01011012, 0x01016011, 0x0101201f, 
+	0x183301f0, 0x18a001f0, 0x18a001f0, 0x01442070,
+	0x01c42190, 0x40000100,
+};
+
+static unsigned int d965_3st_pin_configs[14] = {
+	0x0221401f, 0x02a19120, 0x40000100, 0x01014011,
+	0x01a19021, 0x01813024, 0x40000100, 0x40000100,
+	0x40000100, 0x40000100, 0x40000100, 0x40000100,
+	0x40000100, 0x40000100
+};
+
+static unsigned int d965_5st_pin_configs[14] = {
+	0x02214020, 0x02a19080, 0x0181304e, 0x01014010,
+	0x01a19040, 0x01011012, 0x01016011, 0x40000100,
+	0x40000100, 0x40000100, 0x40000100, 0x01442070,
+	0x40000100, 0x40000100
+};
+
+static unsigned int d965_5st_no_fp_pin_configs[14] = {
+	0x40000100, 0x40000100, 0x0181304e, 0x01014010,
+	0x01a19040, 0x01011012, 0x01016011, 0x40000100,
+	0x40000100, 0x40000100, 0x40000100, 0x01442070,
+	0x40000100, 0x40000100
+};
+
+static unsigned int dell_3st_pin_configs[14] = {
+	0x02211230, 0x02a11220, 0x01a19040, 0x01114210,
+	0x01111212, 0x01116211, 0x01813050, 0x01112214,
+	0x403003fa, 0x90a60040, 0x90a60040, 0x404003fb,
+	0x40c003fc, 0x40000100
+};
+
+static unsigned int *stac927x_brd_tbl[STAC_927X_MODELS] = {
+	[STAC_D965_REF_NO_JD] = ref927x_pin_configs,
+	[STAC_D965_REF]  = ref927x_pin_configs,
+	[STAC_D965_3ST]  = d965_3st_pin_configs,
+	[STAC_D965_5ST]  = d965_5st_pin_configs,
+	[STAC_D965_5ST_NO_FP]  = d965_5st_no_fp_pin_configs,
+	[STAC_DELL_3ST]  = dell_3st_pin_configs,
+	[STAC_DELL_BIOS] = NULL,
+	[STAC_927X_VOLKNOB] = NULL,
+};
+
+static const char *stac927x_models[STAC_927X_MODELS] = {
+	[STAC_927X_AUTO]	= "auto",
+	[STAC_D965_REF_NO_JD]	= "ref-no-jd",
+	[STAC_D965_REF]		= "ref",
+	[STAC_D965_3ST]		= "3stack",
+	[STAC_D965_5ST]		= "5stack",
+	[STAC_D965_5ST_NO_FP]	= "5stack-no-fp",
+	[STAC_DELL_3ST]		= "dell-3stack",
+	[STAC_DELL_BIOS]	= "dell-bios",
+	[STAC_927X_VOLKNOB]	= "volknob",
+};
+
+static struct snd_pci_quirk stac927x_cfg_tbl[] = {
+	/* SigmaTel reference board */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+		      "DFI LanParty", STAC_D965_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+		      "DFI LanParty", STAC_D965_REF),
+	 /* Intel 946 based systems */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x3d01, "Intel D946", STAC_D965_3ST),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0xa301, "Intel D946", STAC_D965_3ST),
+	/* 965 based 3 stack systems */
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2100,
+			   "Intel D965", STAC_D965_3ST),
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2000,
+			   "Intel D965", STAC_D965_3ST),
+	/* Dell 3 stack systems */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x01dd, "Dell Dimension E520", STAC_DELL_3ST),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x01ed, "Dell     ", STAC_DELL_3ST),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x01f4, "Dell     ", STAC_DELL_3ST),
+	/* Dell 3 stack systems with verb table in BIOS */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x01f3, "Dell Inspiron 1420", STAC_DELL_BIOS),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x01f7, "Dell XPS M1730", STAC_DELL_BIOS),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x0227, "Dell Vostro 1400  ", STAC_DELL_BIOS),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x022e, "Dell     ", STAC_DELL_BIOS),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x022f, "Dell Inspiron 1525", STAC_DELL_BIOS),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x0242, "Dell     ", STAC_DELL_BIOS),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x0243, "Dell     ", STAC_DELL_BIOS),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x02ff, "Dell     ", STAC_DELL_BIOS),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL,  0x0209, "Dell XPS 1330", STAC_DELL_BIOS),
+	/* 965 based 5 stack systems */
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2300,
+			   "Intel D965", STAC_D965_5ST),
+	SND_PCI_QUIRK_MASK(PCI_VENDOR_ID_INTEL, 0xff00, 0x2500,
+			   "Intel D965", STAC_D965_5ST),
+	/* volume-knob fixes */
+	SND_PCI_QUIRK_VENDOR(0x10cf, "FSC", STAC_927X_VOLKNOB),
+	{} /* terminator */
+};
+
+static unsigned int ref9205_pin_configs[12] = {
+	0x40000100, 0x40000100, 0x01016011, 0x01014010,
+	0x01813122, 0x01a19021, 0x01019020, 0x40000100,
+	0x90a000f0, 0x90a000f0, 0x01441030, 0x01c41030
+};
+
+/*
+    STAC 9205 pin configs for
+    102801F1
+    102801F2
+    102801FC
+    102801FD
+    10280204
+    1028021F
+    10280228 (Dell Vostro 1500)
+    10280229 (Dell Vostro 1700)
+*/
+static unsigned int dell_9205_m42_pin_configs[12] = {
+	0x0321101F, 0x03A11020, 0x400003FA, 0x90170310,
+	0x400003FB, 0x400003FC, 0x400003FD, 0x40F000F9,
+	0x90A60330, 0x400003FF, 0x0144131F, 0x40C003FE,
+};
+
+/*
+    STAC 9205 pin configs for
+    102801F9
+    102801FA
+    102801FE
+    102801FF (Dell Precision M4300)
+    10280206
+    10280200
+    10280201
+*/
+static unsigned int dell_9205_m43_pin_configs[12] = {
+	0x0321101f, 0x03a11020, 0x90a70330, 0x90170310,
+	0x400000fe, 0x400000ff, 0x400000fd, 0x40f000f9,
+	0x400000fa, 0x400000fc, 0x0144131f, 0x40c003f8,
+};
+
+static unsigned int dell_9205_m44_pin_configs[12] = {
+	0x0421101f, 0x04a11020, 0x400003fa, 0x90170310,
+	0x400003fb, 0x400003fc, 0x400003fd, 0x400003f9,
+	0x90a60330, 0x400003ff, 0x01441340, 0x40c003fe,
+};
+
+static unsigned int *stac9205_brd_tbl[STAC_9205_MODELS] = {
+	[STAC_9205_REF] = ref9205_pin_configs,
+	[STAC_9205_DELL_M42] = dell_9205_m42_pin_configs,
+	[STAC_9205_DELL_M43] = dell_9205_m43_pin_configs,
+	[STAC_9205_DELL_M44] = dell_9205_m44_pin_configs,
+	[STAC_9205_EAPD] = NULL,
+};
+
+static const char *stac9205_models[STAC_9205_MODELS] = {
+	[STAC_9205_AUTO] = "auto",
+	[STAC_9205_REF] = "ref",
+	[STAC_9205_DELL_M42] = "dell-m42",
+	[STAC_9205_DELL_M43] = "dell-m43",
+	[STAC_9205_DELL_M44] = "dell-m44",
+	[STAC_9205_EAPD] = "eapd",
+};
+
+static struct snd_pci_quirk stac9205_cfg_tbl[] = {
+	/* SigmaTel reference board */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668,
+		      "DFI LanParty", STAC_9205_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0xfb30,
+		      "SigmaTel", STAC_9205_REF),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DFI, 0x3101,
+		      "DFI LanParty", STAC_9205_REF),
+	/* Dell */
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f1,
+		      "unknown Dell", STAC_9205_DELL_M42),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f2,
+		      "unknown Dell", STAC_9205_DELL_M42),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f8,
+		      "Dell Precision", STAC_9205_DELL_M43),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f9,
+		      "Dell Precision", STAC_9205_DELL_M43),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fa,
+		      "Dell Precision", STAC_9205_DELL_M43),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fc,
+		      "unknown Dell", STAC_9205_DELL_M42),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fd,
+		      "unknown Dell", STAC_9205_DELL_M42),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fe,
+		      "Dell Precision", STAC_9205_DELL_M43),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ff,
+		      "Dell Precision M4300", STAC_9205_DELL_M43),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0204,
+		      "unknown Dell", STAC_9205_DELL_M42),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0206,
+		      "Dell Precision", STAC_9205_DELL_M43),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021b,
+		      "Dell Precision", STAC_9205_DELL_M43),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021c,
+		      "Dell Precision", STAC_9205_DELL_M43),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021f,
+		      "Dell Inspiron", STAC_9205_DELL_M44),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0228,
+		      "Dell Vostro 1500", STAC_9205_DELL_M42),
+	SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0229,
+		      "Dell Vostro 1700", STAC_9205_DELL_M42),
+	/* Gateway */
+	SND_PCI_QUIRK(0x107b, 0x0560, "Gateway T6834c", STAC_9205_EAPD),
+	SND_PCI_QUIRK(0x107b, 0x0565, "Gateway T1616", STAC_9205_EAPD),
+	{} /* terminator */
+};
+
+static void stac92xx_set_config_regs(struct hda_codec *codec,
+				     unsigned int *pincfgs)
+{
+	int i;
+	struct sigmatel_spec *spec = codec->spec;
+
+	if (!pincfgs)
+		return;
+
+	for (i = 0; i < spec->num_pins; i++)
+		if (spec->pin_nids[i] && pincfgs[i])
+			snd_hda_codec_set_pincfg(codec, spec->pin_nids[i],
+						 pincfgs[i]);
+}
+
+/*
+ * Analog playback callbacks
+ */
+static int stac92xx_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      struct snd_pcm_substream *substream)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	if (spec->stream_delay)
+		msleep(spec->stream_delay);
+	return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
+					     hinfo);
+}
+
+static int stac92xx_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					 struct hda_codec *codec,
+					 unsigned int stream_tag,
+					 unsigned int format,
+					 struct snd_pcm_substream *substream)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag, format, substream);
+}
+
+static int stac92xx_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+					struct hda_codec *codec,
+					struct snd_pcm_substream *substream)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Digital playback callbacks
+ */
+static int stac92xx_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+					  struct hda_codec *codec,
+					  struct snd_pcm_substream *substream)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int stac92xx_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+					   struct hda_codec *codec,
+					   struct snd_pcm_substream *substream)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int stac92xx_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					 struct hda_codec *codec,
+					 unsigned int stream_tag,
+					 unsigned int format,
+					 struct snd_pcm_substream *substream)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
+					     stream_tag, format, substream);
+}
+
+static int stac92xx_dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+					struct hda_codec *codec,
+					struct snd_pcm_substream *substream)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout);
+}
+
+
+/*
+ * Analog capture callbacks
+ */
+static int stac92xx_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+					struct hda_codec *codec,
+					unsigned int stream_tag,
+					unsigned int format,
+					struct snd_pcm_substream *substream)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	hda_nid_t nid = spec->adc_nids[substream->number];
+
+	if (spec->powerdown_adcs) {
+		msleep(40);
+		snd_hda_codec_write(codec, nid, 0,
+			AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+	}
+	snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format);
+	return 0;
+}
+
+static int stac92xx_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+					struct hda_codec *codec,
+					struct snd_pcm_substream *substream)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	hda_nid_t nid = spec->adc_nids[substream->number];
+
+	snd_hda_codec_cleanup_stream(codec, nid);
+	if (spec->powerdown_adcs)
+		snd_hda_codec_write(codec, nid, 0,
+			AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+	return 0;
+}
+
+static struct hda_pcm_stream stac92xx_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in stac92xx_build_pcms */
+	.ops = {
+		.open = stac92xx_dig_playback_pcm_open,
+		.close = stac92xx_dig_playback_pcm_close,
+		.prepare = stac92xx_dig_playback_pcm_prepare,
+		.cleanup = stac92xx_dig_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream stac92xx_pcm_digital_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in stac92xx_build_pcms */
+};
+
+static struct hda_pcm_stream stac92xx_pcm_analog_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 8,
+	.nid = 0x02, /* NID to query formats and rates */
+	.ops = {
+		.open = stac92xx_playback_pcm_open,
+		.prepare = stac92xx_playback_pcm_prepare,
+		.cleanup = stac92xx_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream stac92xx_pcm_analog_alt_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x06, /* NID to query formats and rates */
+	.ops = {
+		.open = stac92xx_playback_pcm_open,
+		.prepare = stac92xx_playback_pcm_prepare,
+		.cleanup = stac92xx_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream stac92xx_pcm_analog_capture = {
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID + .substreams is set in stac92xx_build_pcms */
+	.ops = {
+		.prepare = stac92xx_capture_pcm_prepare,
+		.cleanup = stac92xx_capture_pcm_cleanup
+	},
+};
+
+static int stac92xx_build_pcms(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct hda_pcm *info = spec->pcm_rec;
+
+	codec->num_pcms = 1;
+	codec->pcm_info = info;
+
+	info->name = "STAC92xx Analog";
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK] = stac92xx_pcm_analog_playback;
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
+		spec->multiout.dac_nids[0];
+	info->stream[SNDRV_PCM_STREAM_CAPTURE] = stac92xx_pcm_analog_capture;
+	info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
+	info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_adcs;
+
+	if (spec->alt_switch) {
+		codec->num_pcms++;
+		info++;
+		info->name = "STAC92xx Analog Alt";
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK] = stac92xx_pcm_analog_alt_playback;
+	}
+
+	if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
+		codec->num_pcms++;
+		info++;
+		info->name = "STAC92xx Digital";
+		info->pcm_type = spec->autocfg.dig_out_type[0];
+		if (spec->multiout.dig_out_nid) {
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK] = stac92xx_pcm_digital_playback;
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dig_out_nid;
+		}
+		if (spec->dig_in_nid) {
+			info->stream[SNDRV_PCM_STREAM_CAPTURE] = stac92xx_pcm_digital_capture;
+			info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid;
+		}
+	}
+
+	return 0;
+}
+
+static unsigned int stac92xx_get_default_vref(struct hda_codec *codec,
+					hda_nid_t nid)
+{
+	unsigned int pincap = snd_hda_query_pin_caps(codec, nid);
+	pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
+	if (pincap & AC_PINCAP_VREF_100)
+		return AC_PINCTL_VREF_100;
+	if (pincap & AC_PINCAP_VREF_80)
+		return AC_PINCTL_VREF_80;
+	if (pincap & AC_PINCAP_VREF_50)
+		return AC_PINCTL_VREF_50;
+	if (pincap & AC_PINCAP_VREF_GRD)
+		return AC_PINCTL_VREF_GRD;
+	return 0;
+}
+
+static void stac92xx_auto_set_pinctl(struct hda_codec *codec, hda_nid_t nid, int pin_type)
+
+{
+	snd_hda_codec_write_cache(codec, nid, 0,
+				  AC_VERB_SET_PIN_WIDGET_CONTROL, pin_type);
+}
+
+#define stac92xx_hp_switch_info		snd_ctl_boolean_mono_info
+
+static int stac92xx_hp_switch_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+
+	ucontrol->value.integer.value[0] = !!spec->hp_switch;
+	return 0;
+}
+
+static void stac_issue_unsol_event(struct hda_codec *codec, hda_nid_t nid);
+
+static int stac92xx_hp_switch_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	int nid = kcontrol->private_value;
+ 
+	spec->hp_switch = ucontrol->value.integer.value[0] ? nid : 0;
+
+	/* check to be sure that the ports are upto date with
+	 * switch changes
+	 */
+	stac_issue_unsol_event(codec, nid);
+
+	return 1;
+}
+
+static int stac92xx_dc_bias_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	int i;
+	static char *texts[] = {
+		"Mic In", "Line In", "Line Out"
+	};
+
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	hda_nid_t nid = kcontrol->private_value;
+
+	if (nid == spec->mic_switch || nid == spec->line_switch)
+		i = 3;
+	else
+		i = 2;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->value.enumerated.items = i;
+	uinfo->count = 1;
+	if (uinfo->value.enumerated.item >= i)
+		uinfo->value.enumerated.item = i-1;
+	strcpy(uinfo->value.enumerated.name,
+		texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int stac92xx_dc_bias_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value;
+	unsigned int vref = stac92xx_vref_get(codec, nid);
+
+	if (vref == stac92xx_get_default_vref(codec, nid))
+		ucontrol->value.enumerated.item[0] = 0;
+	else if (vref == AC_PINCTL_VREF_GRD)
+		ucontrol->value.enumerated.item[0] = 1;
+	else if (vref == AC_PINCTL_VREF_HIZ)
+		ucontrol->value.enumerated.item[0] = 2;
+
+	return 0;
+}
+
+static int stac92xx_dc_bias_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int new_vref = 0;
+	int error;
+	hda_nid_t nid = kcontrol->private_value;
+
+	if (ucontrol->value.enumerated.item[0] == 0)
+		new_vref = stac92xx_get_default_vref(codec, nid);
+	else if (ucontrol->value.enumerated.item[0] == 1)
+		new_vref = AC_PINCTL_VREF_GRD;
+	else if (ucontrol->value.enumerated.item[0] == 2)
+		new_vref = AC_PINCTL_VREF_HIZ;
+	else
+		return 0;
+
+	if (new_vref != stac92xx_vref_get(codec, nid)) {
+		error = stac92xx_vref_set(codec, nid, new_vref);
+		return error;
+	}
+
+	return 0;
+}
+
+static int stac92xx_io_switch_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2];
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+
+	if (kcontrol->private_value == spec->line_switch)
+		texts[0] = "Line In";
+	else
+		texts[0] = "Mic In";
+	texts[1] = "Line Out";
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->value.enumerated.items = 2;
+	uinfo->count = 1;
+
+	if (uinfo->value.enumerated.item >= 2)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name,
+		texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int stac92xx_io_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	hda_nid_t nid = kcontrol->private_value;
+	int io_idx = (nid == spec->mic_switch) ? 1 : 0;
+
+	ucontrol->value.enumerated.item[0] = spec->io_switch[io_idx];
+	return 0;
+}
+
+static int stac92xx_io_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+        struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	hda_nid_t nid = kcontrol->private_value;
+	int io_idx = (nid == spec->mic_switch) ? 1 : 0;
+	unsigned short val = !!ucontrol->value.enumerated.item[0];
+
+	spec->io_switch[io_idx] = val;
+
+	if (val)
+		stac92xx_auto_set_pinctl(codec, nid, AC_PINCTL_OUT_EN);
+	else {
+		unsigned int pinctl = AC_PINCTL_IN_EN;
+		if (io_idx) /* set VREF for mic */
+			pinctl |= stac92xx_get_default_vref(codec, nid);
+		stac92xx_auto_set_pinctl(codec, nid, pinctl);
+	}
+
+	/* check the auto-mute again: we need to mute/unmute the speaker
+	 * appropriately according to the pin direction
+	 */
+	if (spec->hp_detect)
+		stac_issue_unsol_event(codec, nid);
+
+        return 1;
+}
+
+#define stac92xx_clfe_switch_info snd_ctl_boolean_mono_info
+
+static int stac92xx_clfe_switch_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+
+	ucontrol->value.integer.value[0] = spec->clfe_swap;
+	return 0;
+}
+
+static int stac92xx_clfe_switch_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	hda_nid_t nid = kcontrol->private_value & 0xff;
+	unsigned int val = !!ucontrol->value.integer.value[0];
+
+	if (spec->clfe_swap == val)
+		return 0;
+
+	spec->clfe_swap = val;
+
+	snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_EAPD_BTLENABLE,
+		spec->clfe_swap ? 0x4 : 0x0);
+
+	return 1;
+}
+
+#define STAC_CODEC_HP_SWITCH(xname) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	  .name = xname, \
+	  .index = 0, \
+	  .info = stac92xx_hp_switch_info, \
+	  .get = stac92xx_hp_switch_get, \
+	  .put = stac92xx_hp_switch_put, \
+	}
+
+#define STAC_CODEC_IO_SWITCH(xname, xpval) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	  .name = xname, \
+	  .index = 0, \
+          .info = stac92xx_io_switch_info, \
+          .get = stac92xx_io_switch_get, \
+          .put = stac92xx_io_switch_put, \
+          .private_value = xpval, \
+	}
+
+#define STAC_CODEC_CLFE_SWITCH(xname, xpval) \
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	  .name = xname, \
+	  .index = 0, \
+	  .info = stac92xx_clfe_switch_info, \
+	  .get = stac92xx_clfe_switch_get, \
+	  .put = stac92xx_clfe_switch_put, \
+	  .private_value = xpval, \
+	}
+
+enum {
+	STAC_CTL_WIDGET_VOL,
+	STAC_CTL_WIDGET_MUTE,
+	STAC_CTL_WIDGET_MUTE_BEEP,
+	STAC_CTL_WIDGET_MONO_MUX,
+	STAC_CTL_WIDGET_HP_SWITCH,
+	STAC_CTL_WIDGET_IO_SWITCH,
+	STAC_CTL_WIDGET_CLFE_SWITCH,
+	STAC_CTL_WIDGET_DC_BIAS
+};
+
+static struct snd_kcontrol_new stac92xx_control_templates[] = {
+	HDA_CODEC_VOLUME(NULL, 0, 0, 0),
+	HDA_CODEC_MUTE(NULL, 0, 0, 0),
+	HDA_CODEC_MUTE_BEEP(NULL, 0, 0, 0),
+	STAC_MONO_MUX,
+	STAC_CODEC_HP_SWITCH(NULL),
+	STAC_CODEC_IO_SWITCH(NULL, 0),
+	STAC_CODEC_CLFE_SWITCH(NULL, 0),
+	DC_BIAS(NULL, 0, 0),
+};
+
+/* add dynamic controls */
+static struct snd_kcontrol_new *
+stac_control_new(struct sigmatel_spec *spec,
+		 struct snd_kcontrol_new *ktemp,
+		 const char *name,
+		 unsigned int subdev)
+{
+	struct snd_kcontrol_new *knew;
+
+	snd_array_init(&spec->kctls, sizeof(*knew), 32);
+	knew = snd_array_new(&spec->kctls);
+	if (!knew)
+		return NULL;
+	*knew = *ktemp;
+	knew->name = kstrdup(name, GFP_KERNEL);
+	if (!knew->name) {
+		/* roolback */
+		memset(knew, 0, sizeof(*knew));
+		spec->kctls.alloced--;
+		return NULL;
+	}
+	knew->subdevice = subdev;
+	return knew;
+}
+
+static int stac92xx_add_control_temp(struct sigmatel_spec *spec,
+				     struct snd_kcontrol_new *ktemp,
+				     int idx, const char *name,
+				     unsigned long val)
+{
+	struct snd_kcontrol_new *knew = stac_control_new(spec, ktemp, name,
+							 HDA_SUBDEV_AMP_FLAG);
+	if (!knew)
+		return -ENOMEM;
+	knew->index = idx;
+	knew->private_value = val;
+	return 0;
+}
+
+static inline int stac92xx_add_control_idx(struct sigmatel_spec *spec,
+					   int type, int idx, const char *name,
+					   unsigned long val)
+{
+	return stac92xx_add_control_temp(spec,
+					 &stac92xx_control_templates[type],
+					 idx, name, val);
+}
+
+
+/* add dynamic controls */
+static inline int stac92xx_add_control(struct sigmatel_spec *spec, int type,
+				       const char *name, unsigned long val)
+{
+	return stac92xx_add_control_idx(spec, type, 0, name, val);
+}
+
+static struct snd_kcontrol_new stac_input_src_temp = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Input Source",
+	.info = stac92xx_mux_enum_info,
+	.get = stac92xx_mux_enum_get,
+	.put = stac92xx_mux_enum_put,
+};
+
+static inline int stac92xx_add_jack_mode_control(struct hda_codec *codec,
+						hda_nid_t nid, int idx)
+{
+	int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+	int control = 0;
+	struct sigmatel_spec *spec = codec->spec;
+	char name[22];
+
+	if (snd_hda_get_input_pin_attr(def_conf) != INPUT_PIN_ATTR_INT) {
+		if (stac92xx_get_default_vref(codec, nid) == AC_PINCTL_VREF_GRD
+			&& nid == spec->line_switch)
+			control = STAC_CTL_WIDGET_IO_SWITCH;
+		else if (snd_hda_query_pin_caps(codec, nid)
+			& (AC_PINCAP_VREF_GRD << AC_PINCAP_VREF_SHIFT))
+			control = STAC_CTL_WIDGET_DC_BIAS;
+		else if (nid == spec->mic_switch)
+			control = STAC_CTL_WIDGET_IO_SWITCH;
+	}
+
+	if (control) {
+		strcpy(name, hda_get_input_pin_label(codec, nid, 1));
+		return stac92xx_add_control(codec->spec, control,
+					strcat(name, " Jack Mode"), nid);
+	}
+
+	return 0;
+}
+
+static int stac92xx_add_input_source(struct sigmatel_spec *spec)
+{
+	struct snd_kcontrol_new *knew;
+	struct hda_input_mux *imux = &spec->private_imux;
+
+	if (spec->auto_mic)
+		return 0; /* no need for input source */
+	if (!spec->num_adcs || imux->num_items <= 1)
+		return 0; /* no need for input source control */
+	knew = stac_control_new(spec, &stac_input_src_temp,
+				stac_input_src_temp.name, 0);
+	if (!knew)
+		return -ENOMEM;
+	knew->count = spec->num_adcs;
+	return 0;
+}
+
+/* check whether the line-input can be used as line-out */
+static hda_nid_t check_line_out_switch(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t nid;
+	unsigned int pincap;
+	int i;
+
+	if (cfg->line_out_type != AUTO_PIN_LINE_OUT)
+		return 0;
+	for (i = 0; i < cfg->num_inputs; i++) {
+		if (cfg->inputs[i].type == AUTO_PIN_LINE_IN) {
+			nid = cfg->inputs[i].pin;
+			pincap = snd_hda_query_pin_caps(codec, nid);
+			if (pincap & AC_PINCAP_OUT)
+				return nid;
+		}
+	}
+	return 0;
+}
+
+static hda_nid_t get_unassigned_dac(struct hda_codec *codec, hda_nid_t nid);
+
+/* check whether the mic-input can be used as line-out */
+static hda_nid_t check_mic_out_switch(struct hda_codec *codec, hda_nid_t *dac)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	unsigned int def_conf, pincap;
+	int i;
+
+	*dac = 0;
+	if (cfg->line_out_type != AUTO_PIN_LINE_OUT)
+		return 0;
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		if (cfg->inputs[i].type != AUTO_PIN_MIC)
+			continue;
+		def_conf = snd_hda_codec_get_pincfg(codec, nid);
+		/* some laptops have an internal analog microphone
+		 * which can't be used as a output */
+		if (snd_hda_get_input_pin_attr(def_conf) != INPUT_PIN_ATTR_INT) {
+			pincap = snd_hda_query_pin_caps(codec, nid);
+			if (pincap & AC_PINCAP_OUT) {
+				*dac = get_unassigned_dac(codec, nid);
+				if (*dac)
+					return nid;
+			}
+		}
+	}
+	return 0;
+}
+
+static int is_in_dac_nids(struct sigmatel_spec *spec, hda_nid_t nid)
+{
+	int i;
+	
+	for (i = 0; i < spec->multiout.num_dacs; i++) {
+		if (spec->multiout.dac_nids[i] == nid)
+			return 1;
+	}
+
+	return 0;
+}
+
+static int check_all_dac_nids(struct sigmatel_spec *spec, hda_nid_t nid)
+{
+	int i;
+	if (is_in_dac_nids(spec, nid))
+		return 1;
+	for (i = 0; i < spec->autocfg.hp_outs; i++)
+		if (spec->hp_dacs[i] == nid)
+			return 1;
+	for (i = 0; i < spec->autocfg.speaker_outs; i++)
+		if (spec->speaker_dacs[i] == nid)
+			return 1;
+	return 0;
+}
+
+static hda_nid_t get_unassigned_dac(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int j, conn_len;
+	hda_nid_t conn[HDA_MAX_CONNECTIONS];
+	unsigned int wcaps, wtype;
+
+	conn_len = snd_hda_get_connections(codec, nid, conn,
+					   HDA_MAX_CONNECTIONS);
+	/* 92HD88: trace back up the link of nids to find the DAC */
+	while (conn_len == 1 && (get_wcaps_type(get_wcaps(codec, conn[0]))
+					!= AC_WID_AUD_OUT)) {
+		nid = conn[0];
+		conn_len = snd_hda_get_connections(codec, nid, conn,
+			HDA_MAX_CONNECTIONS);
+	}
+	for (j = 0; j < conn_len; j++) {
+		wcaps = get_wcaps(codec, conn[j]);
+		wtype = get_wcaps_type(wcaps);
+		/* we check only analog outputs */
+		if (wtype != AC_WID_AUD_OUT || (wcaps & AC_WCAP_DIGITAL))
+			continue;
+		/* if this route has a free DAC, assign it */
+		if (!check_all_dac_nids(spec, conn[j])) {
+			if (conn_len > 1) {
+				/* select this DAC in the pin's input mux */
+				snd_hda_codec_write_cache(codec, nid, 0,
+						  AC_VERB_SET_CONNECT_SEL, j);
+			}
+			return conn[j];
+		}
+	}
+	/* if all DACs are already assigned, connect to the primary DAC */
+	if (conn_len > 1) {
+		for (j = 0; j < conn_len; j++) {
+			if (conn[j] == spec->multiout.dac_nids[0]) {
+				snd_hda_codec_write_cache(codec, nid, 0,
+						  AC_VERB_SET_CONNECT_SEL, j);
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+static int add_spec_dacs(struct sigmatel_spec *spec, hda_nid_t nid);
+static int add_spec_extra_dacs(struct sigmatel_spec *spec, hda_nid_t nid);
+
+/*
+ * Fill in the dac_nids table from the parsed pin configuration
+ * This function only works when every pin in line_out_pins[]
+ * contains atleast one DAC in its connection list. Some 92xx
+ * codecs are not connected directly to a DAC, such as the 9200
+ * and 9202/925x. For those, dac_nids[] must be hard-coded.
+ */
+static int stac92xx_auto_fill_dac_nids(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+	hda_nid_t nid, dac;
+	
+	for (i = 0; i < cfg->line_outs; i++) {
+		nid = cfg->line_out_pins[i];
+		dac = get_unassigned_dac(codec, nid);
+		if (!dac) {
+			if (spec->multiout.num_dacs > 0) {
+				/* we have already working output pins,
+				 * so let's drop the broken ones again
+				 */
+				cfg->line_outs = spec->multiout.num_dacs;
+				break;
+			}
+			/* error out, no available DAC found */
+			snd_printk(KERN_ERR
+				   "%s: No available DAC for pin 0x%x\n",
+				   __func__, nid);
+			return -ENODEV;
+		}
+		add_spec_dacs(spec, dac);
+	}
+
+	for (i = 0; i < cfg->hp_outs; i++) {
+		nid = cfg->hp_pins[i];
+		dac = get_unassigned_dac(codec, nid);
+		if (dac) {
+			if (!spec->multiout.hp_nid)
+				spec->multiout.hp_nid = dac;
+			else
+				add_spec_extra_dacs(spec, dac);
+		}
+		spec->hp_dacs[i] = dac;
+	}
+
+	for (i = 0; i < cfg->speaker_outs; i++) {
+		nid = cfg->speaker_pins[i];
+		dac = get_unassigned_dac(codec, nid);
+		if (dac)
+			add_spec_extra_dacs(spec, dac);
+		spec->speaker_dacs[i] = dac;
+	}
+
+	/* add line-in as output */
+	nid = check_line_out_switch(codec);
+	if (nid) {
+		dac = get_unassigned_dac(codec, nid);
+		if (dac) {
+			snd_printdd("STAC: Add line-in 0x%x as output %d\n",
+				    nid, cfg->line_outs);
+			cfg->line_out_pins[cfg->line_outs] = nid;
+			cfg->line_outs++;
+			spec->line_switch = nid;
+			add_spec_dacs(spec, dac);
+		}
+	}
+	/* add mic as output */
+	nid = check_mic_out_switch(codec, &dac);
+	if (nid && dac) {
+		snd_printdd("STAC: Add mic-in 0x%x as output %d\n",
+			    nid, cfg->line_outs);
+		cfg->line_out_pins[cfg->line_outs] = nid;
+		cfg->line_outs++;
+		spec->mic_switch = nid;
+		add_spec_dacs(spec, dac);
+	}
+
+	snd_printd("stac92xx: dac_nids=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n",
+		   spec->multiout.num_dacs,
+		   spec->multiout.dac_nids[0],
+		   spec->multiout.dac_nids[1],
+		   spec->multiout.dac_nids[2],
+		   spec->multiout.dac_nids[3],
+		   spec->multiout.dac_nids[4]);
+
+	return 0;
+}
+
+/* create volume control/switch for the given prefx type */
+static int create_controls_idx(struct hda_codec *codec, const char *pfx,
+			       int idx, hda_nid_t nid, int chs)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	char name[32];
+	int err;
+
+	if (!spec->check_volume_offset) {
+		unsigned int caps, step, nums, db_scale;
+		caps = query_amp_caps(codec, nid, HDA_OUTPUT);
+		step = (caps & AC_AMPCAP_STEP_SIZE) >>
+			AC_AMPCAP_STEP_SIZE_SHIFT;
+		step = (step + 1) * 25; /* in .01dB unit */
+		nums = (caps & AC_AMPCAP_NUM_STEPS) >>
+			AC_AMPCAP_NUM_STEPS_SHIFT;
+		db_scale = nums * step;
+		/* if dB scale is over -64dB, and finer enough,
+		 * let's reduce it to half
+		 */
+		if (db_scale > 6400 && nums >= 0x1f)
+			spec->volume_offset = nums / 2;
+		spec->check_volume_offset = 1;
+	}
+
+	sprintf(name, "%s Playback Volume", pfx);
+	err = stac92xx_add_control_idx(spec, STAC_CTL_WIDGET_VOL, idx, name,
+		HDA_COMPOSE_AMP_VAL_OFS(nid, chs, 0, HDA_OUTPUT,
+					spec->volume_offset));
+	if (err < 0)
+		return err;
+	sprintf(name, "%s Playback Switch", pfx);
+	err = stac92xx_add_control_idx(spec, STAC_CTL_WIDGET_MUTE, idx, name,
+				   HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+#define create_controls(codec, pfx, nid, chs) \
+	create_controls_idx(codec, pfx, 0, nid, chs)
+
+static int add_spec_dacs(struct sigmatel_spec *spec, hda_nid_t nid)
+{
+	if (spec->multiout.num_dacs > 4) {
+		printk(KERN_WARNING "stac92xx: No space for DAC 0x%x\n", nid);
+		return 1;
+	} else {
+		spec->multiout.dac_nids[spec->multiout.num_dacs] = nid;
+		spec->multiout.num_dacs++;
+	}
+	return 0;
+}
+
+static int add_spec_extra_dacs(struct sigmatel_spec *spec, hda_nid_t nid)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(spec->multiout.extra_out_nid); i++) {
+		if (!spec->multiout.extra_out_nid[i]) {
+			spec->multiout.extra_out_nid[i] = nid;
+			return 0;
+		}
+	}
+	printk(KERN_WARNING "stac92xx: No space for extra DAC 0x%x\n", nid);
+	return 1;
+}
+
+/* Create output controls
+ * The mixer elements are named depending on the given type (AUTO_PIN_XXX_OUT)
+ */
+static int create_multi_out_ctls(struct hda_codec *codec, int num_outs,
+				 const hda_nid_t *pins,
+				 const hda_nid_t *dac_nids,
+				 int type)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	static const char *chname[4] = {
+		"Front", "Surround", NULL /*CLFE*/, "Side"
+	};
+	hda_nid_t nid;
+	int i, err;
+	unsigned int wid_caps;
+
+	for (i = 0; i < num_outs && i < ARRAY_SIZE(chname); i++) {
+		if (type == AUTO_PIN_HP_OUT && !spec->hp_detect) {
+			wid_caps = get_wcaps(codec, pins[i]);
+			if (wid_caps & AC_WCAP_UNSOL_CAP)
+				spec->hp_detect = 1;
+		}
+		nid = dac_nids[i];
+		if (!nid)
+			continue;
+		if (type != AUTO_PIN_HP_OUT && i == 2) {
+			/* Center/LFE */
+			err = create_controls(codec, "Center", nid, 1);
+			if (err < 0)
+				return err;
+			err = create_controls(codec, "LFE", nid, 2);
+			if (err < 0)
+				return err;
+
+			wid_caps = get_wcaps(codec, nid);
+
+			if (wid_caps & AC_WCAP_LR_SWAP) {
+				err = stac92xx_add_control(spec,
+					STAC_CTL_WIDGET_CLFE_SWITCH,
+					"Swap Center/LFE Playback Switch", nid);
+
+				if (err < 0)
+					return err;
+			}
+
+		} else {
+			const char *name;
+			int idx;
+			switch (type) {
+			case AUTO_PIN_HP_OUT:
+				name = "Headphone";
+				idx = i;
+				break;
+			case AUTO_PIN_SPEAKER_OUT:
+				name = "Speaker";
+				idx = i;
+				break;
+			default:
+				name = chname[i];
+				idx = 0;
+				break;
+			}
+			err = create_controls_idx(codec, name, idx, nid, 3);
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+static int stac92xx_add_capvol_ctls(struct hda_codec *codec, unsigned long vol,
+				    unsigned long sw, int idx)
+{
+	int err;
+	err = stac92xx_add_control_idx(codec->spec, STAC_CTL_WIDGET_VOL, idx,
+				       "Capture Volume", vol);
+	if (err < 0)
+		return err;
+	err = stac92xx_add_control_idx(codec->spec, STAC_CTL_WIDGET_MUTE, idx,
+				       "Capture Switch", sw);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int stac92xx_auto_create_multi_out_ctls(struct hda_codec *codec,
+					       const struct auto_pin_cfg *cfg)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	hda_nid_t nid;
+	int err;
+	int idx;
+
+	err = create_multi_out_ctls(codec, cfg->line_outs, cfg->line_out_pins,
+				    spec->multiout.dac_nids,
+				    cfg->line_out_type);
+	if (err < 0)
+		return err;
+
+	if (cfg->hp_outs > 1 && cfg->line_out_type == AUTO_PIN_LINE_OUT) {
+		err = stac92xx_add_control(spec,
+			STAC_CTL_WIDGET_HP_SWITCH,
+			"Headphone as Line Out Switch",
+			cfg->hp_pins[cfg->hp_outs - 1]);
+		if (err < 0)
+			return err;
+	}
+
+	for (idx = 0; idx < cfg->num_inputs; idx++) {
+		if (cfg->inputs[idx].type > AUTO_PIN_LINE_IN)
+			break;
+		nid = cfg->inputs[idx].pin;
+		err = stac92xx_add_jack_mode_control(codec, nid, idx);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+/* add playback controls for Speaker and HP outputs */
+static int stac92xx_auto_create_hp_ctls(struct hda_codec *codec,
+					struct auto_pin_cfg *cfg)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int err;
+
+	err = create_multi_out_ctls(codec, cfg->hp_outs, cfg->hp_pins,
+				    spec->hp_dacs, AUTO_PIN_HP_OUT);
+	if (err < 0)
+		return err;
+
+	err = create_multi_out_ctls(codec, cfg->speaker_outs, cfg->speaker_pins,
+				    spec->speaker_dacs, AUTO_PIN_SPEAKER_OUT);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/* labels for mono mux outputs */
+static const char *stac92xx_mono_labels[4] = {
+	"DAC0", "DAC1", "Mixer", "DAC2"
+};
+
+/* create mono mux for mono out on capable codecs */
+static int stac92xx_auto_create_mono_output_ctls(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct hda_input_mux *mono_mux = &spec->private_mono_mux;
+	int i, num_cons;
+	hda_nid_t con_lst[ARRAY_SIZE(stac92xx_mono_labels)];
+
+	num_cons = snd_hda_get_connections(codec,
+				spec->mono_nid,
+				con_lst,
+				HDA_MAX_NUM_INPUTS);
+	if (num_cons <= 0 || num_cons > ARRAY_SIZE(stac92xx_mono_labels))
+		return -EINVAL;
+
+	for (i = 0; i < num_cons; i++)
+		snd_hda_add_imux_item(mono_mux, stac92xx_mono_labels[i], i,
+				      NULL);
+
+	return stac92xx_add_control(spec, STAC_CTL_WIDGET_MONO_MUX,
+				"Mono Mux", spec->mono_nid);
+}
+
+/* create PC beep volume controls */
+static int stac92xx_auto_create_beep_ctls(struct hda_codec *codec,
+						hda_nid_t nid)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	u32 caps = query_amp_caps(codec, nid, HDA_OUTPUT);
+	int err, type = STAC_CTL_WIDGET_MUTE_BEEP;
+
+	if (spec->anabeep_nid == nid)
+		type = STAC_CTL_WIDGET_MUTE;
+
+	/* check for mute support for the the amp */
+	if ((caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT) {
+		err = stac92xx_add_control(spec, type,
+			"Beep Playback Switch",
+			HDA_COMPOSE_AMP_VAL(nid, 1, 0, HDA_OUTPUT));
+			if (err < 0)
+				return err;
+	}
+
+	/* check to see if there is volume support for the amp */
+	if ((caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT) {
+		err = stac92xx_add_control(spec, STAC_CTL_WIDGET_VOL,
+			"Beep Playback Volume",
+			HDA_COMPOSE_AMP_VAL(nid, 1, 0, HDA_OUTPUT));
+			if (err < 0)
+				return err;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+#define stac92xx_dig_beep_switch_info snd_ctl_boolean_mono_info
+
+static int stac92xx_dig_beep_switch_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = codec->beep->enabled;
+	return 0;
+}
+
+static int stac92xx_dig_beep_switch_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	return snd_hda_enable_beep_device(codec, ucontrol->value.integer.value[0]);
+}
+
+static struct snd_kcontrol_new stac92xx_dig_beep_ctrl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info = stac92xx_dig_beep_switch_info,
+	.get = stac92xx_dig_beep_switch_get,
+	.put = stac92xx_dig_beep_switch_put,
+};
+
+static int stac92xx_beep_switch_ctl(struct hda_codec *codec)
+{
+	return stac92xx_add_control_temp(codec->spec, &stac92xx_dig_beep_ctrl,
+					 0, "Beep Playback Switch", 0);
+}
+#endif
+
+static int stac92xx_auto_create_mux_input_ctls(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int i, j, err = 0;
+
+	for (i = 0; i < spec->num_muxes; i++) {
+		hda_nid_t nid;
+		unsigned int wcaps;
+		unsigned long val;
+
+		nid = spec->mux_nids[i];
+		wcaps = get_wcaps(codec, nid);
+		if (!(wcaps & AC_WCAP_OUT_AMP))
+			continue;
+
+		/* check whether already the same control was created as
+		 * normal Capture Volume.
+		 */
+		val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+		for (j = 0; j < spec->num_caps; j++) {
+			if (spec->capvols[j] == val)
+				break;
+		}
+		if (j < spec->num_caps)
+			continue;
+
+		err = stac92xx_add_control_idx(spec, STAC_CTL_WIDGET_VOL, i,
+					       "Mux Capture Volume", val);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+};
+
+static const char *stac92xx_spdif_labels[3] = {
+	"Digital Playback", "Analog Mux 1", "Analog Mux 2",
+};
+
+static int stac92xx_auto_create_spdif_mux_ctls(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct hda_input_mux *spdif_mux = &spec->private_smux;
+	const char **labels = spec->spdif_labels;
+	int i, num_cons;
+	hda_nid_t con_lst[HDA_MAX_NUM_INPUTS];
+
+	num_cons = snd_hda_get_connections(codec,
+				spec->smux_nids[0],
+				con_lst,
+				HDA_MAX_NUM_INPUTS);
+	if (num_cons <= 0)
+		return -EINVAL;
+
+	if (!labels)
+		labels = stac92xx_spdif_labels;
+
+	for (i = 0; i < num_cons; i++)
+		snd_hda_add_imux_item(spdif_mux, labels[i], i, NULL);
+
+	return 0;
+}
+
+/* labels for dmic mux inputs */
+static const char *stac92xx_dmic_labels[5] = {
+	"Analog Inputs", "Digital Mic 1", "Digital Mic 2",
+	"Digital Mic 3", "Digital Mic 4"
+};
+
+static int get_connection_index(struct hda_codec *codec, hda_nid_t mux,
+				hda_nid_t nid)
+{
+	hda_nid_t conn[HDA_MAX_NUM_INPUTS];
+	int i, nums;
+
+	nums = snd_hda_get_connections(codec, mux, conn, ARRAY_SIZE(conn));
+	for (i = 0; i < nums; i++)
+		if (conn[i] == nid)
+			return i;
+	return -1;
+}
+
+/* create a volume assigned to the given pin (only if supported) */
+/* return 1 if the volume control is created */
+static int create_elem_capture_vol(struct hda_codec *codec, hda_nid_t nid,
+				   const char *label, int idx, int direction)
+{
+	unsigned int caps, nums;
+	char name[32];
+	int err;
+
+	if (direction == HDA_OUTPUT)
+		caps = AC_WCAP_OUT_AMP;
+	else
+		caps = AC_WCAP_IN_AMP;
+	if (!(get_wcaps(codec, nid) & caps))
+		return 0;
+	caps = query_amp_caps(codec, nid, direction);
+	nums = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+	if (!nums)
+		return 0;
+	snprintf(name, sizeof(name), "%s Capture Volume", label);
+	err = stac92xx_add_control_idx(codec->spec, STAC_CTL_WIDGET_VOL, idx, name,
+				       HDA_COMPOSE_AMP_VAL(nid, 3, 0, direction));
+	if (err < 0)
+		return err;
+	return 1;
+}
+
+/* create playback/capture controls for input pins on dmic capable codecs */
+static int stac92xx_auto_create_dmic_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct hda_input_mux *imux = &spec->private_imux;
+	struct hda_input_mux *dimux = &spec->private_dimux;
+	int err, i;
+	unsigned int def_conf;
+
+	snd_hda_add_imux_item(dimux, stac92xx_dmic_labels[0], 0, NULL);
+
+	for (i = 0; i < spec->num_dmics; i++) {
+		hda_nid_t nid;
+		int index, type_idx;
+		const char *label;
+
+		nid = spec->dmic_nids[i];
+		if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
+			continue;
+		def_conf = snd_hda_codec_get_pincfg(codec, nid);
+		if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE)
+			continue;
+
+		index = get_connection_index(codec, spec->dmux_nids[0], nid);
+		if (index < 0)
+			continue;
+
+		label = hda_get_input_pin_label(codec, nid, 1);
+		snd_hda_add_imux_item(dimux, label, index, &type_idx);
+		if (snd_hda_get_bool_hint(codec, "separate_dmux") != 1)
+			snd_hda_add_imux_item(imux, label, index, &type_idx);
+
+		err = create_elem_capture_vol(codec, nid, label, type_idx,
+					      HDA_INPUT);
+		if (err < 0)
+			return err;
+		if (!err) {
+			err = create_elem_capture_vol(codec, nid, label,
+						      type_idx, HDA_OUTPUT);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+static int check_mic_pin(struct hda_codec *codec, hda_nid_t nid,
+			 hda_nid_t *fixed, hda_nid_t *ext, hda_nid_t *dock)
+{
+	unsigned int cfg;
+
+	if (!nid)
+		return 0;
+	cfg = snd_hda_codec_get_pincfg(codec, nid);
+	switch (snd_hda_get_input_pin_attr(cfg)) {
+	case INPUT_PIN_ATTR_INT:
+		if (*fixed)
+			return 1; /* already occupied */
+		*fixed = nid;
+		break;
+	case INPUT_PIN_ATTR_UNUSED:
+		break;
+	case INPUT_PIN_ATTR_DOCK:
+		if (*dock)
+			return 1; /* already occupied */
+		*dock = nid;
+		break;
+	default:
+		if (*ext)
+			return 1; /* already occupied */
+		*ext = nid;
+		break;
+	}
+	return 0;
+}
+
+static int set_mic_route(struct hda_codec *codec,
+			 struct sigmatel_mic_route *mic,
+			 hda_nid_t pin)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	mic->pin = pin;
+	if (pin == 0)
+		return 0;
+	for (i = 0; i < cfg->num_inputs; i++) {
+		if (pin == cfg->inputs[i].pin)
+			break;
+	}
+	if (i < cfg->num_inputs && cfg->inputs[i].type == AUTO_PIN_MIC) {
+		/* analog pin */
+		i = get_connection_index(codec, spec->mux_nids[0], pin);
+		if (i < 0)
+			return -1;
+		mic->mux_idx = i;
+		mic->dmux_idx = -1;
+		if (spec->dmux_nids)
+			mic->dmux_idx = get_connection_index(codec,
+							     spec->dmux_nids[0],
+							     spec->mux_nids[0]);
+	}  else if (spec->dmux_nids) {
+		/* digital pin */
+		i = get_connection_index(codec, spec->dmux_nids[0], pin);
+		if (i < 0)
+			return -1;
+		mic->dmux_idx = i;
+		mic->mux_idx = -1;
+		if (spec->mux_nids)
+			mic->mux_idx = get_connection_index(codec,
+							    spec->mux_nids[0],
+							    spec->dmux_nids[0]);
+	}
+	return 0;
+}
+
+/* return non-zero if the device is for automatic mic switch */
+static int stac_check_auto_mic(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t fixed, ext, dock;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		if (cfg->inputs[i].type >= AUTO_PIN_LINE_IN)
+			return 0; /* must be exclusively mics */
+	}
+	fixed = ext = dock = 0;
+	for (i = 0; i < cfg->num_inputs; i++)
+		if (check_mic_pin(codec, cfg->inputs[i].pin,
+		    &fixed, &ext, &dock))
+			return 0;
+	for (i = 0; i < spec->num_dmics; i++)
+		if (check_mic_pin(codec, spec->dmic_nids[i],
+		    &fixed, &ext, &dock))
+			return 0;
+	if (!fixed && !ext && !dock)
+		return 0; /* no input to switch */
+	if (!(get_wcaps(codec, ext) & AC_WCAP_UNSOL_CAP))
+		return 0; /* no unsol support */
+	if (set_mic_route(codec, &spec->ext_mic, ext) ||
+	    set_mic_route(codec, &spec->int_mic, fixed) ||
+	    set_mic_route(codec, &spec->dock_mic, dock))
+		return 0; /* something is wrong */
+	return 1;
+}
+
+/* create playback/capture controls for input pins */
+static int stac92xx_auto_create_analog_input_ctls(struct hda_codec *codec, const struct auto_pin_cfg *cfg)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct hda_input_mux *imux = &spec->private_imux;
+	int i, j;
+	const char *label;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		int index, err, type_idx;
+
+		index = -1;
+		for (j = 0; j < spec->num_muxes; j++) {
+			index = get_connection_index(codec, spec->mux_nids[j],
+						     nid);
+			if (index >= 0)
+				break;
+		}
+		if (index < 0)
+			continue;
+
+		label = hda_get_autocfg_input_label(codec, cfg, i);
+		snd_hda_add_imux_item(imux, label, index, &type_idx);
+
+		err = create_elem_capture_vol(codec, nid,
+					      label, type_idx,
+					      HDA_INPUT);
+		if (err < 0)
+			return err;
+	}
+	spec->num_analog_muxes = imux->num_items;
+
+	if (imux->num_items) {
+		/*
+		 * Set the current input for the muxes.
+		 * The STAC9221 has two input muxes with identical source
+		 * NID lists.  Hopefully this won't get confused.
+		 */
+		for (i = 0; i < spec->num_muxes; i++) {
+			snd_hda_codec_write_cache(codec, spec->mux_nids[i], 0,
+						  AC_VERB_SET_CONNECT_SEL,
+						  imux->items[0].index);
+		}
+	}
+
+	return 0;
+}
+
+static void stac92xx_auto_init_multi_out(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->autocfg.line_outs; i++) {
+		hda_nid_t nid = spec->autocfg.line_out_pins[i];
+		stac92xx_auto_set_pinctl(codec, nid, AC_PINCTL_OUT_EN);
+	}
+}
+
+static void stac92xx_auto_init_hp_out(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->autocfg.hp_outs; i++) {
+		hda_nid_t pin;
+		pin = spec->autocfg.hp_pins[i];
+		if (pin) /* connect to front */
+			stac92xx_auto_set_pinctl(codec, pin, AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN);
+	}
+	for (i = 0; i < spec->autocfg.speaker_outs; i++) {
+		hda_nid_t pin;
+		pin = spec->autocfg.speaker_pins[i];
+		if (pin) /* connect to front */
+			stac92xx_auto_set_pinctl(codec, pin, AC_PINCTL_OUT_EN);
+	}
+}
+
+static int is_dual_headphones(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int i, valid_hps;
+
+	if (spec->autocfg.line_out_type != AUTO_PIN_SPEAKER_OUT ||
+	    spec->autocfg.hp_outs <= 1)
+		return 0;
+	valid_hps = 0;
+	for (i = 0; i < spec->autocfg.hp_outs; i++) {
+		hda_nid_t nid = spec->autocfg.hp_pins[i];
+		unsigned int cfg = snd_hda_codec_get_pincfg(codec, nid);
+		if (get_defcfg_location(cfg) & AC_JACK_LOC_SEPARATE)
+			continue;
+		valid_hps++;
+	}
+	return (valid_hps > 1);
+}
+
+
+static int stac92xx_parse_auto_config(struct hda_codec *codec, hda_nid_t dig_out, hda_nid_t dig_in)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int hp_swap = 0;
+	int i, err;
+
+	if ((err = snd_hda_parse_pin_def_config(codec,
+						&spec->autocfg,
+						spec->dmic_nids)) < 0)
+		return err;
+	if (! spec->autocfg.line_outs)
+		return 0; /* can't find valid pin config */
+
+	/* If we have no real line-out pin and multiple hp-outs, HPs should
+	 * be set up as multi-channel outputs.
+	 */
+	if (is_dual_headphones(codec)) {
+		/* Copy hp_outs to line_outs, backup line_outs in
+		 * speaker_outs so that the following routines can handle
+		 * HP pins as primary outputs.
+		 */
+		snd_printdd("stac92xx: Enabling multi-HPs workaround\n");
+		memcpy(spec->autocfg.speaker_pins, spec->autocfg.line_out_pins,
+		       sizeof(spec->autocfg.line_out_pins));
+		spec->autocfg.speaker_outs = spec->autocfg.line_outs;
+		memcpy(spec->autocfg.line_out_pins, spec->autocfg.hp_pins,
+		       sizeof(spec->autocfg.hp_pins));
+		spec->autocfg.line_outs = spec->autocfg.hp_outs;
+		spec->autocfg.line_out_type = AUTO_PIN_HP_OUT;
+		spec->autocfg.hp_outs = 0;
+		hp_swap = 1;
+	}
+	if (spec->autocfg.mono_out_pin) {
+		int dir = get_wcaps(codec, spec->autocfg.mono_out_pin) &
+			(AC_WCAP_OUT_AMP | AC_WCAP_IN_AMP);
+		u32 caps = query_amp_caps(codec,
+				spec->autocfg.mono_out_pin, dir);
+		hda_nid_t conn_list[1];
+
+		/* get the mixer node and then the mono mux if it exists */
+		if (snd_hda_get_connections(codec,
+				spec->autocfg.mono_out_pin, conn_list, 1) &&
+				snd_hda_get_connections(codec, conn_list[0],
+				conn_list, 1) > 0) {
+
+				int wcaps = get_wcaps(codec, conn_list[0]);
+				int wid_type = get_wcaps_type(wcaps);
+				/* LR swap check, some stac925x have a mux that
+ 				 * changes the DACs output path instead of the
+ 				 * mono-mux path.
+ 				 */
+				if (wid_type == AC_WID_AUD_SEL &&
+						!(wcaps & AC_WCAP_LR_SWAP))
+					spec->mono_nid = conn_list[0];
+		}
+		if (dir) {
+			hda_nid_t nid = spec->autocfg.mono_out_pin;
+
+			/* most mono outs have a least a mute/unmute switch */
+			dir = (dir & AC_WCAP_OUT_AMP) ? HDA_OUTPUT : HDA_INPUT;
+			err = stac92xx_add_control(spec, STAC_CTL_WIDGET_MUTE,
+				"Mono Playback Switch",
+				HDA_COMPOSE_AMP_VAL(nid, 1, 0, dir));
+			if (err < 0)
+				return err;
+			/* check for volume support for the amp */
+			if ((caps & AC_AMPCAP_NUM_STEPS)
+					>> AC_AMPCAP_NUM_STEPS_SHIFT) {
+				err = stac92xx_add_control(spec,
+					STAC_CTL_WIDGET_VOL,
+					"Mono Playback Volume",
+				HDA_COMPOSE_AMP_VAL(nid, 1, 0, dir));
+				if (err < 0)
+					return err;
+			}
+		}
+
+		stac92xx_auto_set_pinctl(codec, spec->autocfg.mono_out_pin,
+					 AC_PINCTL_OUT_EN);
+	}
+
+	if (!spec->multiout.num_dacs) {
+		err = stac92xx_auto_fill_dac_nids(codec);
+		if (err < 0)
+			return err;
+		err = stac92xx_auto_create_multi_out_ctls(codec,
+							  &spec->autocfg);
+		if (err < 0)
+			return err;
+	}
+
+	/* setup analog beep controls */
+	if (spec->anabeep_nid > 0) {
+		err = stac92xx_auto_create_beep_ctls(codec,
+			spec->anabeep_nid);
+		if (err < 0)
+			return err;
+	}
+
+	/* setup digital beep controls and input device */
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+	if (spec->digbeep_nid > 0) {
+		hda_nid_t nid = spec->digbeep_nid;
+		unsigned int caps;
+
+		err = stac92xx_auto_create_beep_ctls(codec, nid);
+		if (err < 0)
+			return err;
+		err = snd_hda_attach_beep_device(codec, nid);
+		if (err < 0)
+			return err;
+		if (codec->beep) {
+			/* IDT/STAC codecs have linear beep tone parameter */
+			codec->beep->linear_tone = spec->linear_tone_beep;
+			/* if no beep switch is available, make its own one */
+			caps = query_amp_caps(codec, nid, HDA_OUTPUT);
+			if (!(caps & AC_AMPCAP_MUTE)) {
+				err = stac92xx_beep_switch_ctl(codec);
+				if (err < 0)
+					return err;
+			}
+		}
+	}
+#endif
+
+	err = stac92xx_auto_create_hp_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	/* All output parsing done, now restore the swapped hp pins */
+	if (hp_swap) {
+		memcpy(spec->autocfg.hp_pins, spec->autocfg.line_out_pins,
+		       sizeof(spec->autocfg.hp_pins));
+		spec->autocfg.hp_outs = spec->autocfg.line_outs;
+		spec->autocfg.line_out_type = AUTO_PIN_HP_OUT;
+		spec->autocfg.line_outs = 0;
+	}
+
+	if (stac_check_auto_mic(codec)) {
+		spec->auto_mic = 1;
+		/* only one capture for auto-mic */
+		spec->num_adcs = 1;
+		spec->num_caps = 1;
+		spec->num_muxes = 1;
+	}
+
+	for (i = 0; i < spec->num_caps; i++) {
+		err = stac92xx_add_capvol_ctls(codec, spec->capvols[i],
+					       spec->capsws[i], i);
+		if (err < 0)
+			return err;
+	}
+
+	err = stac92xx_auto_create_analog_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	if (spec->mono_nid > 0) {
+		err = stac92xx_auto_create_mono_output_ctls(codec);
+		if (err < 0)
+			return err;
+	}
+	if (spec->num_dmics > 0 && !spec->dinput_mux)
+		if ((err = stac92xx_auto_create_dmic_input_ctls(codec,
+						&spec->autocfg)) < 0)
+			return err;
+	if (spec->num_muxes > 0) {
+		err = stac92xx_auto_create_mux_input_ctls(codec);
+		if (err < 0)
+			return err;
+	}
+	if (spec->num_smuxes > 0) {
+		err = stac92xx_auto_create_spdif_mux_ctls(codec);
+		if (err < 0)
+			return err;
+	}
+
+	err = stac92xx_add_input_source(spec);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+	if (spec->multiout.max_channels > 2)
+		spec->surr_switch = 1;
+
+	if (spec->autocfg.dig_outs)
+		spec->multiout.dig_out_nid = dig_out;
+	if (dig_in && spec->autocfg.dig_in_pin)
+		spec->dig_in_nid = dig_in;
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->input_mux = &spec->private_imux;
+	if (!spec->dinput_mux)
+		spec->dinput_mux = &spec->private_dimux;
+	spec->sinput_mux = &spec->private_smux;
+	spec->mono_mux = &spec->private_mono_mux;
+	return 1;
+}
+
+/* add playback controls for HP output */
+static int stac9200_auto_create_hp_ctls(struct hda_codec *codec,
+					struct auto_pin_cfg *cfg)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	hda_nid_t pin = cfg->hp_pins[0];
+	unsigned int wid_caps;
+
+	if (! pin)
+		return 0;
+
+	wid_caps = get_wcaps(codec, pin);
+	if (wid_caps & AC_WCAP_UNSOL_CAP)
+		spec->hp_detect = 1;
+
+	return 0;
+}
+
+/* add playback controls for LFE output */
+static int stac9200_auto_create_lfe_ctls(struct hda_codec *codec,
+					struct auto_pin_cfg *cfg)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int err;
+	hda_nid_t lfe_pin = 0x0;
+	int i;
+
+	/*
+	 * search speaker outs and line outs for a mono speaker pin
+	 * with an amp.  If one is found, add LFE controls
+	 * for it.
+	 */
+	for (i = 0; i < spec->autocfg.speaker_outs && lfe_pin == 0x0; i++) {
+		hda_nid_t pin = spec->autocfg.speaker_pins[i];
+		unsigned int wcaps = get_wcaps(codec, pin);
+		wcaps &= (AC_WCAP_STEREO | AC_WCAP_OUT_AMP);
+		if (wcaps == AC_WCAP_OUT_AMP)
+			/* found a mono speaker with an amp, must be lfe */
+			lfe_pin = pin;
+	}
+
+	/* if speaker_outs is 0, then speakers may be in line_outs */
+	if (lfe_pin == 0 && spec->autocfg.speaker_outs == 0) {
+		for (i = 0; i < spec->autocfg.line_outs && lfe_pin == 0x0; i++) {
+			hda_nid_t pin = spec->autocfg.line_out_pins[i];
+			unsigned int defcfg;
+			defcfg = snd_hda_codec_get_pincfg(codec, pin);
+			if (get_defcfg_device(defcfg) == AC_JACK_SPEAKER) {
+				unsigned int wcaps = get_wcaps(codec, pin);
+				wcaps &= (AC_WCAP_STEREO | AC_WCAP_OUT_AMP);
+				if (wcaps == AC_WCAP_OUT_AMP)
+					/* found a mono speaker with an amp,
+					   must be lfe */
+					lfe_pin = pin;
+			}
+		}
+	}
+
+	if (lfe_pin) {
+		err = create_controls(codec, "LFE", lfe_pin, 1);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int stac9200_parse_auto_config(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int err;
+
+	if ((err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL)) < 0)
+		return err;
+
+	if ((err = stac92xx_auto_create_analog_input_ctls(codec, &spec->autocfg)) < 0)
+		return err;
+
+	if ((err = stac9200_auto_create_hp_ctls(codec, &spec->autocfg)) < 0)
+		return err;
+
+	if ((err = stac9200_auto_create_lfe_ctls(codec, &spec->autocfg)) < 0)
+		return err;
+
+	if (spec->num_muxes > 0) {
+		err = stac92xx_auto_create_mux_input_ctls(codec);
+		if (err < 0)
+			return err;
+	}
+
+	err = stac92xx_add_input_source(spec);
+	if (err < 0)
+		return err;
+
+	if (spec->autocfg.dig_outs)
+		spec->multiout.dig_out_nid = 0x05;
+	if (spec->autocfg.dig_in_pin)
+		spec->dig_in_nid = 0x04;
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->input_mux = &spec->private_imux;
+	spec->dinput_mux = &spec->private_dimux;
+
+	return 1;
+}
+
+/*
+ * Early 2006 Intel Macintoshes with STAC9220X5 codecs seem to have a
+ * funky external mute control using GPIO pins.
+ */
+
+static void stac_gpio_set(struct hda_codec *codec, unsigned int mask,
+			  unsigned int dir_mask, unsigned int data)
+{
+	unsigned int gpiostate, gpiomask, gpiodir;
+
+	gpiostate = snd_hda_codec_read(codec, codec->afg, 0,
+				       AC_VERB_GET_GPIO_DATA, 0);
+	gpiostate = (gpiostate & ~dir_mask) | (data & dir_mask);
+
+	gpiomask = snd_hda_codec_read(codec, codec->afg, 0,
+				      AC_VERB_GET_GPIO_MASK, 0);
+	gpiomask |= mask;
+
+	gpiodir = snd_hda_codec_read(codec, codec->afg, 0,
+				     AC_VERB_GET_GPIO_DIRECTION, 0);
+	gpiodir |= dir_mask;
+
+	/* Configure GPIOx as CMOS */
+	snd_hda_codec_write(codec, codec->afg, 0, 0x7e7, 0);
+
+	snd_hda_codec_write(codec, codec->afg, 0,
+			    AC_VERB_SET_GPIO_MASK, gpiomask);
+	snd_hda_codec_read(codec, codec->afg, 0,
+			   AC_VERB_SET_GPIO_DIRECTION, gpiodir); /* sync */
+
+	msleep(1);
+
+	snd_hda_codec_read(codec, codec->afg, 0,
+			   AC_VERB_SET_GPIO_DATA, gpiostate); /* sync */
+}
+
+#ifdef CONFIG_SND_HDA_INPUT_JACK
+static void stac92xx_free_jack_priv(struct snd_jack *jack)
+{
+	struct sigmatel_jack *jacks = jack->private_data;
+	jacks->nid = 0;
+	jacks->jack = NULL;
+}
+#endif
+
+static int stac92xx_add_jack(struct hda_codec *codec,
+		hda_nid_t nid, int type)
+{
+#ifdef CONFIG_SND_HDA_INPUT_JACK
+	struct sigmatel_spec *spec = codec->spec;
+	struct sigmatel_jack *jack;
+	int def_conf = snd_hda_codec_get_pincfg(codec, nid);
+	int connectivity = get_defcfg_connect(def_conf);
+	char name[32];
+	int err;
+
+	if (connectivity && connectivity != AC_JACK_PORT_FIXED)
+		return 0;
+
+	snd_array_init(&spec->jacks, sizeof(*jack), 32);
+	jack = snd_array_new(&spec->jacks);
+	if (!jack)
+		return -ENOMEM;
+	jack->nid = nid;
+	jack->type = type;
+
+	snprintf(name, sizeof(name), "%s at %s %s Jack",
+		snd_hda_get_jack_type(def_conf),
+		snd_hda_get_jack_connectivity(def_conf),
+		snd_hda_get_jack_location(def_conf));
+
+	err = snd_jack_new(codec->bus->card, name, type, &jack->jack);
+	if (err < 0) {
+		jack->nid = 0;
+		return err;
+	}
+	jack->jack->private_data = jack;
+	jack->jack->private_free = stac92xx_free_jack_priv;
+#endif
+	return 0;
+}
+
+static int stac_add_event(struct sigmatel_spec *spec, hda_nid_t nid,
+			  unsigned char type, int data)
+{
+	struct sigmatel_event *event;
+
+	snd_array_init(&spec->events, sizeof(*event), 32);
+	event = snd_array_new(&spec->events);
+	if (!event)
+		return -ENOMEM;
+	event->nid = nid;
+	event->type = type;
+	event->tag = spec->events.used;
+	event->data = data;
+
+	return event->tag;
+}
+
+static struct sigmatel_event *stac_get_event(struct hda_codec *codec,
+					     hda_nid_t nid)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct sigmatel_event *event = spec->events.list;
+	int i;
+
+	for (i = 0; i < spec->events.used; i++, event++) {
+		if (event->nid == nid)
+			return event;
+	}
+	return NULL;
+}
+
+static struct sigmatel_event *stac_get_event_from_tag(struct hda_codec *codec,
+						      unsigned char tag)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct sigmatel_event *event = spec->events.list;
+	int i;
+
+	for (i = 0; i < spec->events.used; i++, event++) {
+		if (event->tag == tag)
+			return event;
+	}
+	return NULL;
+}
+
+/* check if given nid is a valid pin and no other events are assigned
+ * to it.  If OK, assign the event, set the unsol flag, and returns 1.
+ * Otherwise, returns zero.
+ */
+static int enable_pin_detect(struct hda_codec *codec, hda_nid_t nid,
+			     unsigned int type)
+{
+	struct sigmatel_event *event;
+	int tag;
+
+	if (!(get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP))
+		return 0;
+	event = stac_get_event(codec, nid);
+	if (event) {
+		if (event->type != type)
+			return 0;
+		tag = event->tag;
+	} else {
+		tag = stac_add_event(codec->spec, nid, type, 0);
+		if (tag < 0)
+			return 0;
+	}
+	snd_hda_codec_write_cache(codec, nid, 0,
+				  AC_VERB_SET_UNSOLICITED_ENABLE,
+				  AC_USRSP_EN | tag);
+	return 1;
+}
+
+static int is_nid_hp_pin(struct auto_pin_cfg *cfg, hda_nid_t nid)
+{
+	int i;
+	for (i = 0; i < cfg->hp_outs; i++)
+		if (cfg->hp_pins[i] == nid)
+			return 1; /* nid is a HP-Out */
+
+	return 0; /* nid is not a HP-Out */
+};
+
+static void stac92xx_power_down(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+
+	/* power down inactive DACs */
+	hda_nid_t *dac;
+	for (dac = spec->dac_list; *dac; dac++)
+		if (!check_all_dac_nids(spec, *dac))
+			snd_hda_codec_write(codec, *dac, 0,
+					AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+}
+
+static void stac_toggle_power_map(struct hda_codec *codec, hda_nid_t nid,
+				  int enable);
+
+static inline int get_int_hint(struct hda_codec *codec, const char *key,
+			       int *valp)
+{
+	const char *p;
+	p = snd_hda_get_hint(codec, key);
+	if (p) {
+		unsigned long val;
+		if (!strict_strtoul(p, 0, &val)) {
+			*valp = val;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/* override some hints from the hwdep entry */
+static void stac_store_hints(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int val;
+
+	val = snd_hda_get_bool_hint(codec, "hp_detect");
+	if (val >= 0)
+		spec->hp_detect = val;
+	if (get_int_hint(codec, "gpio_mask", &spec->gpio_mask)) {
+		spec->eapd_mask = spec->gpio_dir = spec->gpio_data =
+			spec->gpio_mask;
+	}
+	if (get_int_hint(codec, "gpio_dir", &spec->gpio_dir))
+		spec->gpio_mask &= spec->gpio_mask;
+	if (get_int_hint(codec, "gpio_data", &spec->gpio_data))
+		spec->gpio_dir &= spec->gpio_mask;
+	if (get_int_hint(codec, "eapd_mask", &spec->eapd_mask))
+		spec->eapd_mask &= spec->gpio_mask;
+	if (get_int_hint(codec, "gpio_mute", &spec->gpio_mute))
+		spec->gpio_mute &= spec->gpio_mask;
+	val = snd_hda_get_bool_hint(codec, "eapd_switch");
+	if (val >= 0)
+		spec->eapd_switch = val;
+	get_int_hint(codec, "gpio_led_polarity", &spec->gpio_led_polarity);
+	if (get_int_hint(codec, "gpio_led", &spec->gpio_led)) {
+		spec->gpio_mask |= spec->gpio_led;
+		spec->gpio_dir |= spec->gpio_led;
+		if (spec->gpio_led_polarity)
+			spec->gpio_data |= spec->gpio_led;
+	}
+}
+
+static int stac92xx_init(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	unsigned int gpio;
+	int i;
+
+	snd_hda_sequence_write(codec, spec->init);
+
+	/* power down adcs initially */
+	if (spec->powerdown_adcs)
+		for (i = 0; i < spec->num_adcs; i++)
+			snd_hda_codec_write(codec,
+				spec->adc_nids[i], 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+
+	/* override some hints */
+	stac_store_hints(codec);
+
+	/* set up GPIO */
+	gpio = spec->gpio_data;
+	/* turn on EAPD statically when spec->eapd_switch isn't set.
+	 * otherwise, unsol event will turn it on/off dynamically
+	 */
+	if (!spec->eapd_switch)
+		gpio |= spec->eapd_mask;
+	stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, gpio);
+
+	/* set up pins */
+	if (spec->hp_detect) {
+		/* Enable unsolicited responses on the HP widget */
+		for (i = 0; i < cfg->hp_outs; i++) {
+			hda_nid_t nid = cfg->hp_pins[i];
+			enable_pin_detect(codec, nid, STAC_HP_EVENT);
+		}
+		if (cfg->line_out_type == AUTO_PIN_LINE_OUT &&
+		    cfg->speaker_outs > 0) {
+			/* enable pin-detect for line-outs as well */
+			for (i = 0; i < cfg->line_outs; i++) {
+				hda_nid_t nid = cfg->line_out_pins[i];
+				enable_pin_detect(codec, nid, STAC_LO_EVENT);
+			}
+		}
+
+		/* force to enable the first line-out; the others are set up
+		 * in unsol_event
+		 */
+		stac92xx_auto_set_pinctl(codec, spec->autocfg.line_out_pins[0],
+				AC_PINCTL_OUT_EN);
+		/* fake event to set up pins */
+		if (cfg->hp_pins[0])
+			stac_issue_unsol_event(codec, cfg->hp_pins[0]);
+		else if (cfg->line_out_pins[0])
+			stac_issue_unsol_event(codec, cfg->line_out_pins[0]);
+	} else {
+		stac92xx_auto_init_multi_out(codec);
+		stac92xx_auto_init_hp_out(codec);
+		for (i = 0; i < cfg->hp_outs; i++)
+			stac_toggle_power_map(codec, cfg->hp_pins[i], 1);
+	}
+	if (spec->auto_mic) {
+		/* initialize connection to analog input */
+		if (spec->dmux_nids)
+			snd_hda_codec_write_cache(codec, spec->dmux_nids[0], 0,
+					  AC_VERB_SET_CONNECT_SEL, 0);
+		if (enable_pin_detect(codec, spec->ext_mic.pin, STAC_MIC_EVENT))
+			stac_issue_unsol_event(codec, spec->ext_mic.pin);
+		if (enable_pin_detect(codec, spec->dock_mic.pin,
+		    STAC_MIC_EVENT))
+			stac_issue_unsol_event(codec, spec->dock_mic.pin);
+	}
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		int type = cfg->inputs[i].type;
+		unsigned int pinctl, conf;
+		if (type == AUTO_PIN_MIC) {
+			/* for mic pins, force to initialize */
+			pinctl = stac92xx_get_default_vref(codec, nid);
+			pinctl |= AC_PINCTL_IN_EN;
+			stac92xx_auto_set_pinctl(codec, nid, pinctl);
+		} else {
+			pinctl = snd_hda_codec_read(codec, nid, 0,
+					AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+			/* if PINCTL already set then skip */
+			/* Also, if both INPUT and OUTPUT are set,
+			 * it must be a BIOS bug; need to override, too
+			 */
+			if (!(pinctl & AC_PINCTL_IN_EN) ||
+			    (pinctl & AC_PINCTL_OUT_EN)) {
+				pinctl &= ~AC_PINCTL_OUT_EN;
+				pinctl |= AC_PINCTL_IN_EN;
+				stac92xx_auto_set_pinctl(codec, nid, pinctl);
+			}
+		}
+		conf = snd_hda_codec_get_pincfg(codec, nid);
+		if (get_defcfg_connect(conf) != AC_JACK_PORT_FIXED) {
+			if (enable_pin_detect(codec, nid, STAC_INSERT_EVENT))
+				stac_issue_unsol_event(codec, nid);
+		}
+	}
+	for (i = 0; i < spec->num_dmics; i++)
+		stac92xx_auto_set_pinctl(codec, spec->dmic_nids[i],
+					AC_PINCTL_IN_EN);
+	if (cfg->dig_out_pins[0])
+		stac92xx_auto_set_pinctl(codec, cfg->dig_out_pins[0],
+					 AC_PINCTL_OUT_EN);
+	if (cfg->dig_in_pin)
+		stac92xx_auto_set_pinctl(codec, cfg->dig_in_pin,
+					 AC_PINCTL_IN_EN);
+	for (i = 0; i < spec->num_pwrs; i++)  {
+		hda_nid_t nid = spec->pwr_nids[i];
+		int pinctl, def_conf;
+
+		/* power on when no jack detection is available */
+		if (!spec->hp_detect) {
+			stac_toggle_power_map(codec, nid, 1);
+			continue;
+		}
+
+		if (is_nid_hp_pin(cfg, nid))
+			continue; /* already has an unsol event */
+
+		pinctl = snd_hda_codec_read(codec, nid, 0,
+					    AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+		/* outputs are only ports capable of power management
+		 * any attempts on powering down a input port cause the
+		 * referenced VREF to act quirky.
+		 */
+		if (pinctl & AC_PINCTL_IN_EN) {
+			stac_toggle_power_map(codec, nid, 1);
+			continue;
+		}
+		def_conf = snd_hda_codec_get_pincfg(codec, nid);
+		def_conf = get_defcfg_connect(def_conf);
+		/* skip any ports that don't have jacks since presence
+ 		 * detection is useless */
+		if (def_conf != AC_JACK_PORT_COMPLEX) {
+			if (def_conf != AC_JACK_PORT_NONE)
+				stac_toggle_power_map(codec, nid, 1);
+			continue;
+		}
+		if (enable_pin_detect(codec, nid, STAC_PWR_EVENT))
+			stac_issue_unsol_event(codec, nid);
+	}
+
+	/* sync mute LED */
+	if (spec->gpio_led)
+		hda_call_check_power_status(codec, 0x01);
+	if (spec->dac_list)
+		stac92xx_power_down(codec);
+	return 0;
+}
+
+static void stac92xx_free_jacks(struct hda_codec *codec)
+{
+#ifdef CONFIG_SND_HDA_INPUT_JACK
+	/* free jack instances manually when clearing/reconfiguring */
+	struct sigmatel_spec *spec = codec->spec;
+	if (!codec->bus->shutdown && spec->jacks.list) {
+		struct sigmatel_jack *jacks = spec->jacks.list;
+		int i;
+		for (i = 0; i < spec->jacks.used; i++, jacks++) {
+			if (jacks->jack)
+				snd_device_free(codec->bus->card, jacks->jack);
+		}
+	}
+	snd_array_free(&spec->jacks);
+#endif
+}
+
+static void stac92xx_free_kctls(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+
+	if (spec->kctls.list) {
+		struct snd_kcontrol_new *kctl = spec->kctls.list;
+		int i;
+		for (i = 0; i < spec->kctls.used; i++)
+			kfree(kctl[i].name);
+	}
+	snd_array_free(&spec->kctls);
+}
+
+static void stac92xx_shutup(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+
+	snd_hda_shutup_pins(codec);
+
+	if (spec->eapd_mask)
+		stac_gpio_set(codec, spec->gpio_mask,
+				spec->gpio_dir, spec->gpio_data &
+				~spec->eapd_mask);
+}
+
+static void stac92xx_free(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+
+	if (! spec)
+		return;
+
+	stac92xx_shutup(codec);
+	stac92xx_free_jacks(codec);
+	snd_array_free(&spec->events);
+
+	kfree(spec);
+	snd_hda_detach_beep_device(codec);
+}
+
+static void stac92xx_set_pinctl(struct hda_codec *codec, hda_nid_t nid,
+				unsigned int flag)
+{
+	unsigned int old_ctl, pin_ctl;
+
+	pin_ctl = snd_hda_codec_read(codec, nid,
+			0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0x00);
+
+	if (pin_ctl & AC_PINCTL_IN_EN) {
+		/*
+		 * we need to check the current set-up direction of
+		 * shared input pins since they can be switched via
+		 * "xxx as Output" mixer switch
+		 */
+		struct sigmatel_spec *spec = codec->spec;
+		if (nid == spec->line_switch || nid == spec->mic_switch)
+			return;
+	}
+
+	old_ctl = pin_ctl;
+	/* if setting pin direction bits, clear the current
+	   direction bits first */
+	if (flag & (AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN))
+		pin_ctl &= ~(AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN);
+	
+	pin_ctl |= flag;
+	if (old_ctl != pin_ctl)
+		snd_hda_codec_write_cache(codec, nid, 0,
+					  AC_VERB_SET_PIN_WIDGET_CONTROL,
+					  pin_ctl);
+}
+
+static void stac92xx_reset_pinctl(struct hda_codec *codec, hda_nid_t nid,
+				  unsigned int flag)
+{
+	unsigned int pin_ctl = snd_hda_codec_read(codec, nid,
+			0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0x00);
+	if (pin_ctl & flag)
+		snd_hda_codec_write_cache(codec, nid, 0,
+					  AC_VERB_SET_PIN_WIDGET_CONTROL,
+					  pin_ctl & ~flag);
+}
+
+static inline int get_pin_presence(struct hda_codec *codec, hda_nid_t nid)
+{
+	if (!nid)
+		return 0;
+	return snd_hda_jack_detect(codec, nid);
+}
+
+static void stac92xx_line_out_detect(struct hda_codec *codec,
+				     int presence)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	for (i = 0; i < cfg->line_outs; i++) {
+		if (presence)
+			break;
+		presence = get_pin_presence(codec, cfg->line_out_pins[i]);
+		if (presence) {
+			unsigned int pinctl;
+			pinctl = snd_hda_codec_read(codec,
+						    cfg->line_out_pins[i], 0,
+					    AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+			if (pinctl & AC_PINCTL_IN_EN)
+				presence = 0; /* mic- or line-input */
+		}
+	}
+
+	if (presence) {
+		/* disable speakers */
+		for (i = 0; i < cfg->speaker_outs; i++)
+			stac92xx_reset_pinctl(codec, cfg->speaker_pins[i],
+						AC_PINCTL_OUT_EN);
+		if (spec->eapd_mask && spec->eapd_switch)
+			stac_gpio_set(codec, spec->gpio_mask,
+				spec->gpio_dir, spec->gpio_data &
+				~spec->eapd_mask);
+	} else {
+		/* enable speakers */
+		for (i = 0; i < cfg->speaker_outs; i++)
+			stac92xx_set_pinctl(codec, cfg->speaker_pins[i],
+						AC_PINCTL_OUT_EN);
+		if (spec->eapd_mask && spec->eapd_switch)
+			stac_gpio_set(codec, spec->gpio_mask,
+				spec->gpio_dir, spec->gpio_data |
+				spec->eapd_mask);
+	}
+} 
+
+/* return non-zero if the hp-pin of the given array index isn't
+ * a jack-detection target
+ */
+static int no_hp_sensing(struct sigmatel_spec *spec, int i)
+{
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+
+	/* ignore sensing of shared line and mic jacks */
+	if (cfg->hp_pins[i] == spec->line_switch)
+		return 1;
+	if (cfg->hp_pins[i] == spec->mic_switch)
+		return 1;
+	/* ignore if the pin is set as line-out */
+	if (cfg->hp_pins[i] == spec->hp_switch)
+		return 1;
+	return 0;
+}
+
+static void stac92xx_hp_detect(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i, presence;
+
+	presence = 0;
+	if (spec->gpio_mute)
+		presence = !(snd_hda_codec_read(codec, codec->afg, 0,
+			AC_VERB_GET_GPIO_DATA, 0) & spec->gpio_mute);
+
+	for (i = 0; i < cfg->hp_outs; i++) {
+		if (presence)
+			break;
+		if (no_hp_sensing(spec, i))
+			continue;
+		presence = get_pin_presence(codec, cfg->hp_pins[i]);
+		if (presence) {
+			unsigned int pinctl;
+			pinctl = snd_hda_codec_read(codec, cfg->hp_pins[i], 0,
+					    AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+			if (pinctl & AC_PINCTL_IN_EN)
+				presence = 0; /* mic- or line-input */
+		}
+	}
+
+	if (presence) {
+		/* disable lineouts */
+		if (spec->hp_switch)
+			stac92xx_reset_pinctl(codec, spec->hp_switch,
+					      AC_PINCTL_OUT_EN);
+		for (i = 0; i < cfg->line_outs; i++)
+			stac92xx_reset_pinctl(codec, cfg->line_out_pins[i],
+						AC_PINCTL_OUT_EN);
+	} else {
+		/* enable lineouts */
+		if (spec->hp_switch)
+			stac92xx_set_pinctl(codec, spec->hp_switch,
+					    AC_PINCTL_OUT_EN);
+		for (i = 0; i < cfg->line_outs; i++)
+			stac92xx_set_pinctl(codec, cfg->line_out_pins[i],
+						AC_PINCTL_OUT_EN);
+	}
+	stac92xx_line_out_detect(codec, presence);
+	/* toggle hp outs */
+	for (i = 0; i < cfg->hp_outs; i++) {
+		unsigned int val = AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN;
+		if (no_hp_sensing(spec, i))
+			continue;
+		if (presence)
+			stac92xx_set_pinctl(codec, cfg->hp_pins[i], val);
+#if 0 /* FIXME */
+/* Resetting the pinctl like below may lead to (a sort of) regressions
+ * on some devices since they use the HP pin actually for line/speaker
+ * outs although the default pin config shows a different pin (that is
+ * wrong and useless).
+ *
+ * So, it's basically a problem of default pin configs, likely a BIOS issue.
+ * But, disabling the code below just works around it, and I'm too tired of
+ * bug reports with such devices... 
+ */
+		else
+			stac92xx_reset_pinctl(codec, cfg->hp_pins[i], val);
+#endif /* FIXME */
+	}
+} 
+
+static void stac_toggle_power_map(struct hda_codec *codec, hda_nid_t nid,
+				  int enable)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	unsigned int idx, val;
+
+	for (idx = 0; idx < spec->num_pwrs; idx++) {
+		if (spec->pwr_nids[idx] == nid)
+			break;
+	}
+	if (idx >= spec->num_pwrs)
+		return;
+
+	/* several codecs have two power down bits */
+	if (spec->pwr_mapping)
+		idx = spec->pwr_mapping[idx];
+	else
+		idx = 1 << idx;
+
+	val = snd_hda_codec_read(codec, codec->afg, 0, 0x0fec, 0x0) & 0xff;
+	if (enable)
+		val &= ~idx;
+	else
+		val |= idx;
+
+	/* power down unused output ports */
+	snd_hda_codec_write(codec, codec->afg, 0, 0x7ec, val);
+}
+
+static void stac92xx_pin_sense(struct hda_codec *codec, hda_nid_t nid)
+{
+	stac_toggle_power_map(codec, nid, get_pin_presence(codec, nid));
+}
+
+static void stac92xx_report_jack(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct sigmatel_jack *jacks = spec->jacks.list;
+
+	if (jacks) {
+		int i;
+		for (i = 0; i < spec->jacks.used; i++) {
+			if (jacks->nid == nid) {
+				unsigned int pin_ctl =
+					snd_hda_codec_read(codec, nid,
+					0, AC_VERB_GET_PIN_WIDGET_CONTROL,
+					 0x00);
+				int type = jacks->type;
+				if (type == (SND_JACK_LINEOUT
+						| SND_JACK_HEADPHONE))
+					type = (pin_ctl & AC_PINCTL_HP_EN)
+					? SND_JACK_HEADPHONE : SND_JACK_LINEOUT;
+				snd_jack_report(jacks->jack,
+					get_pin_presence(codec, nid)
+					? type : 0);
+			}
+			jacks++;
+		}
+	}
+}
+
+/* get the pin connection (fixed, none, etc) */
+static unsigned int stac_get_defcfg_connect(struct hda_codec *codec, int idx)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	unsigned int cfg;
+
+	cfg = snd_hda_codec_get_pincfg(codec, spec->pin_nids[idx]);
+	return get_defcfg_connect(cfg);
+}
+
+static int stac92xx_connected_ports(struct hda_codec *codec,
+					 hda_nid_t *nids, int num_nids)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int idx, num;
+	unsigned int def_conf;
+
+	for (num = 0; num < num_nids; num++) {
+		for (idx = 0; idx < spec->num_pins; idx++)
+			if (spec->pin_nids[idx] == nids[num])
+				break;
+		if (idx >= spec->num_pins)
+			break;
+		def_conf = stac_get_defcfg_connect(codec, idx);
+		if (def_conf == AC_JACK_PORT_NONE)
+			break;
+	}
+	return num;
+}
+
+static void stac92xx_mic_detect(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct sigmatel_mic_route *mic;
+
+	if (get_pin_presence(codec, spec->ext_mic.pin))
+		mic = &spec->ext_mic;
+	else if (get_pin_presence(codec, spec->dock_mic.pin))
+		mic = &spec->dock_mic;
+	else
+		mic = &spec->int_mic;
+	if (mic->dmux_idx >= 0)
+		snd_hda_codec_write_cache(codec, spec->dmux_nids[0], 0,
+					  AC_VERB_SET_CONNECT_SEL,
+					  mic->dmux_idx);
+	if (mic->mux_idx >= 0)
+		snd_hda_codec_write_cache(codec, spec->mux_nids[0], 0,
+					  AC_VERB_SET_CONNECT_SEL,
+					  mic->mux_idx);
+}
+
+static void stac_issue_unsol_event(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct sigmatel_event *event = stac_get_event(codec, nid);
+	if (!event)
+		return;
+	codec->patch_ops.unsol_event(codec, (unsigned)event->tag << 26);
+}
+
+static void stac92xx_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	struct sigmatel_event *event;
+	int tag, data;
+
+	tag = (res >> 26) & 0x7f;
+	event = stac_get_event_from_tag(codec, tag);
+	if (!event)
+		return;
+
+	switch (event->type) {
+	case STAC_HP_EVENT:
+	case STAC_LO_EVENT:
+		stac92xx_hp_detect(codec);
+		break;
+	case STAC_MIC_EVENT:
+		stac92xx_mic_detect(codec);
+		break;
+	}
+
+	switch (event->type) {
+	case STAC_HP_EVENT:
+	case STAC_LO_EVENT:
+	case STAC_MIC_EVENT:
+	case STAC_INSERT_EVENT:
+	case STAC_PWR_EVENT:
+		if (spec->num_pwrs > 0)
+			stac92xx_pin_sense(codec, event->nid);
+		stac92xx_report_jack(codec, event->nid);
+
+		switch (codec->subsystem_id) {
+		case 0x103c308f:
+			if (event->nid == 0xb) {
+				int pin = AC_PINCTL_IN_EN;
+
+				if (get_pin_presence(codec, 0xa)
+						&& get_pin_presence(codec, 0xb))
+					pin |= AC_PINCTL_VREF_80;
+				if (!get_pin_presence(codec, 0xb))
+					pin |= AC_PINCTL_VREF_80;
+
+				/* toggle VREF state based on mic + hp pin
+				 * status
+				 */
+				stac92xx_auto_set_pinctl(codec, 0x0a, pin);
+			}
+		}
+		break;
+	case STAC_VREF_EVENT:
+		data = snd_hda_codec_read(codec, codec->afg, 0,
+					  AC_VERB_GET_GPIO_DATA, 0);
+		/* toggle VREF state based on GPIOx status */
+		snd_hda_codec_write(codec, codec->afg, 0, 0x7e0,
+				    !!(data & (1 << event->data)));
+		break;
+	}
+}
+
+static int hp_blike_system(u32 subsystem_id);
+
+static void set_hp_led_gpio(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	unsigned int gpio;
+
+	if (spec->gpio_led)
+		return;
+
+	gpio = snd_hda_param_read(codec, codec->afg, AC_PAR_GPIO_CAP);
+	gpio &= AC_GPIO_IO_COUNT;
+	if (gpio > 3)
+		spec->gpio_led = 0x08; /* GPIO 3 */
+	else
+		spec->gpio_led = 0x01; /* GPIO 0 */
+}
+
+/*
+ * This method searches for the mute LED GPIO configuration
+ * provided as OEM string in SMBIOS. The format of that string
+ * is HP_Mute_LED_P_G or HP_Mute_LED_P
+ * where P can be 0 or 1 and defines mute LED GPIO control state (low/high)
+ * that corresponds to the NOT muted state of the master volume
+ * and G is the index of the GPIO to use as the mute LED control (0..9)
+ * If _G portion is missing it is assigned based on the codec ID
+ *
+ * So, HP B-series like systems may have HP_Mute_LED_0 (current models)
+ * or  HP_Mute_LED_0_3 (future models) OEM SMBIOS strings
+ *
+ *
+ * The dv-series laptops don't seem to have the HP_Mute_LED* strings in
+ * SMBIOS - at least the ones I have seen do not have them - which include
+ * my own system (HP Pavilion dv6-1110ax) and my cousin's
+ * HP Pavilion dv9500t CTO.
+ * Need more information on whether it is true across the entire series.
+ * -- kunal
+ */
+static int find_mute_led_gpio(struct hda_codec *codec, int default_polarity)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	const struct dmi_device *dev = NULL;
+
+	if ((codec->subsystem_id >> 16) == PCI_VENDOR_ID_HP) {
+		while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING,
+								NULL, dev))) {
+			if (sscanf(dev->name, "HP_Mute_LED_%d_%d",
+				  &spec->gpio_led_polarity,
+				  &spec->gpio_led) == 2) {
+				spec->gpio_led = 1 << spec->gpio_led;
+				return 1;
+			}
+			if (sscanf(dev->name, "HP_Mute_LED_%d",
+				  &spec->gpio_led_polarity) == 1) {
+				set_hp_led_gpio(codec);
+				return 1;
+			}
+		}
+
+		/*
+		 * Fallback case - if we don't find the DMI strings,
+		 * we statically set the GPIO - if not a B-series system.
+		 */
+		if (!hp_blike_system(codec->subsystem_id)) {
+			set_hp_led_gpio(codec);
+			spec->gpio_led_polarity = default_polarity;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int hp_blike_system(u32 subsystem_id)
+{
+	switch (subsystem_id) {
+	case 0x103c1520:
+	case 0x103c1521:
+	case 0x103c1523:
+	case 0x103c1524:
+	case 0x103c1525:
+	case 0x103c1722:
+	case 0x103c1723:
+	case 0x103c1724:
+	case 0x103c1725:
+	case 0x103c1726:
+	case 0x103c1727:
+	case 0x103c1728:
+	case 0x103c1729:
+	case 0x103c172a:
+	case 0x103c172b:
+	case 0x103c307e:
+	case 0x103c307f:
+	case 0x103c3080:
+	case 0x103c3081:
+	case 0x103c7007:
+	case 0x103c7008:
+		return 1;
+	}
+	return 0;
+}
+
+#ifdef CONFIG_PROC_FS
+static void stac92hd_proc_hook(struct snd_info_buffer *buffer,
+			       struct hda_codec *codec, hda_nid_t nid)
+{
+	if (nid == codec->afg)
+		snd_iprintf(buffer, "Power-Map: 0x%02x\n", 
+			    snd_hda_codec_read(codec, nid, 0, 0x0fec, 0x0));
+}
+
+static void analog_loop_proc_hook(struct snd_info_buffer *buffer,
+				  struct hda_codec *codec,
+				  unsigned int verb)
+{
+	snd_iprintf(buffer, "Analog Loopback: 0x%02x\n",
+		    snd_hda_codec_read(codec, codec->afg, 0, verb, 0));
+}
+
+/* stac92hd71bxx, stac92hd73xx */
+static void stac92hd7x_proc_hook(struct snd_info_buffer *buffer,
+				 struct hda_codec *codec, hda_nid_t nid)
+{
+	stac92hd_proc_hook(buffer, codec, nid);
+	if (nid == codec->afg)
+		analog_loop_proc_hook(buffer, codec, 0xfa0);
+}
+
+static void stac9205_proc_hook(struct snd_info_buffer *buffer,
+			       struct hda_codec *codec, hda_nid_t nid)
+{
+	if (nid == codec->afg)
+		analog_loop_proc_hook(buffer, codec, 0xfe0);
+}
+
+static void stac927x_proc_hook(struct snd_info_buffer *buffer,
+			       struct hda_codec *codec, hda_nid_t nid)
+{
+	if (nid == codec->afg)
+		analog_loop_proc_hook(buffer, codec, 0xfeb);
+}
+#else
+#define stac92hd_proc_hook	NULL
+#define stac92hd7x_proc_hook	NULL
+#define stac9205_proc_hook	NULL
+#define stac927x_proc_hook	NULL
+#endif
+
+#ifdef SND_HDA_NEEDS_RESUME
+static int stac92xx_resume(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+
+	stac92xx_init(codec);
+	snd_hda_codec_resume_amp(codec);
+	snd_hda_codec_resume_cache(codec);
+	/* fake event to set up pins again to override cached values */
+	if (spec->hp_detect) {
+		if (spec->autocfg.hp_pins[0])
+			stac_issue_unsol_event(codec, spec->autocfg.hp_pins[0]);
+		else if (spec->autocfg.line_out_pins[0])
+			stac_issue_unsol_event(codec,
+					       spec->autocfg.line_out_pins[0]);
+	}
+	/* sync mute LED */
+	if (spec->gpio_led)
+		hda_call_check_power_status(codec, 0x01);
+	return 0;
+}
+
+/*
+ * using power check for controlling mute led of HP notebooks
+ * check for mute state only on Speakers (nid = 0x10)
+ *
+ * For this feature CONFIG_SND_HDA_POWER_SAVE is needed, otherwise
+ * the LED is NOT working properly !
+ *
+ * Changed name to reflect that it now works for any designated
+ * model, not just HP HDX.
+ */
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static int stac92xx_hp_check_power_status(struct hda_codec *codec,
+					      hda_nid_t nid)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int i, muted = 1;
+
+	for (i = 0; i < spec->multiout.num_dacs; i++) {
+		nid = spec->multiout.dac_nids[i];
+		if (!(snd_hda_codec_amp_read(codec, nid, 0, HDA_OUTPUT, 0) &
+		      HDA_AMP_MUTE)) {
+			muted = 0; /* something heard */
+			break;
+		}
+	}
+	if (muted)
+		spec->gpio_data &= ~spec->gpio_led; /* orange */
+	else
+		spec->gpio_data |= spec->gpio_led; /* white */
+
+	if (!spec->gpio_led_polarity) {
+		/* LED state is inverted on these systems */
+		spec->gpio_data ^= spec->gpio_led;
+	}
+
+	stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, spec->gpio_data);
+	return 0;
+}
+#endif
+
+static int stac92xx_suspend(struct hda_codec *codec, pm_message_t state)
+{
+	stac92xx_shutup(codec);
+	return 0;
+}
+#endif
+
+static struct hda_codec_ops stac92xx_patch_ops = {
+	.build_controls = stac92xx_build_controls,
+	.build_pcms = stac92xx_build_pcms,
+	.init = stac92xx_init,
+	.free = stac92xx_free,
+	.unsol_event = stac92xx_unsol_event,
+#ifdef SND_HDA_NEEDS_RESUME
+	.suspend = stac92xx_suspend,
+	.resume = stac92xx_resume,
+#endif
+	.reboot_notify = stac92xx_shutup,
+};
+
+static int patch_stac9200(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec;
+	int err;
+
+	spec  = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->no_trigger_sense = 1;
+	codec->spec = spec;
+	spec->linear_tone_beep = 1;
+	spec->num_pins = ARRAY_SIZE(stac9200_pin_nids);
+	spec->pin_nids = stac9200_pin_nids;
+	spec->board_config = snd_hda_check_board_config(codec, STAC_9200_MODELS,
+							stac9200_models,
+							stac9200_cfg_tbl);
+	if (spec->board_config < 0)
+		snd_printdd(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+			    codec->chip_name);
+	else
+		stac92xx_set_config_regs(codec,
+					 stac9200_brd_tbl[spec->board_config]);
+
+	spec->multiout.max_channels = 2;
+	spec->multiout.num_dacs = 1;
+	spec->multiout.dac_nids = stac9200_dac_nids;
+	spec->adc_nids = stac9200_adc_nids;
+	spec->mux_nids = stac9200_mux_nids;
+	spec->num_muxes = 1;
+	spec->num_dmics = 0;
+	spec->num_adcs = 1;
+	spec->num_pwrs = 0;
+
+	if (spec->board_config == STAC_9200_M4 ||
+	    spec->board_config == STAC_9200_M4_2 ||
+	    spec->board_config == STAC_9200_OQO)
+		spec->init = stac9200_eapd_init;
+	else
+		spec->init = stac9200_core_init;
+	spec->mixer = stac9200_mixer;
+
+	if (spec->board_config == STAC_9200_PANASONIC) {
+		spec->gpio_mask = spec->gpio_dir = 0x09;
+		spec->gpio_data = 0x00;
+	}
+
+	err = stac9200_parse_auto_config(codec);
+	if (err < 0) {
+		stac92xx_free(codec);
+		return err;
+	}
+
+	/* CF-74 has no headphone detection, and the driver should *NOT*
+	 * do detection and HP/speaker toggle because the hardware does it.
+	 */
+	if (spec->board_config == STAC_9200_PANASONIC)
+		spec->hp_detect = 0;
+
+	codec->patch_ops = stac92xx_patch_ops;
+
+	return 0;
+}
+
+static int patch_stac925x(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec;
+	int err;
+
+	spec  = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->no_trigger_sense = 1;
+	codec->spec = spec;
+	spec->linear_tone_beep = 1;
+	spec->num_pins = ARRAY_SIZE(stac925x_pin_nids);
+	spec->pin_nids = stac925x_pin_nids;
+
+	/* Check first for codec ID */
+	spec->board_config = snd_hda_check_board_codec_sid_config(codec,
+							STAC_925x_MODELS,
+							stac925x_models,
+							stac925x_codec_id_cfg_tbl);
+
+	/* Now checks for PCI ID, if codec ID is not found */
+	if (spec->board_config < 0)
+		spec->board_config = snd_hda_check_board_config(codec,
+							STAC_925x_MODELS,
+							stac925x_models,
+							stac925x_cfg_tbl);
+ again:
+	if (spec->board_config < 0)
+		snd_printdd(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+			    codec->chip_name);
+	else
+		stac92xx_set_config_regs(codec,
+					 stac925x_brd_tbl[spec->board_config]);
+
+	spec->multiout.max_channels = 2;
+	spec->multiout.num_dacs = 1;
+	spec->multiout.dac_nids = stac925x_dac_nids;
+	spec->adc_nids = stac925x_adc_nids;
+	spec->mux_nids = stac925x_mux_nids;
+	spec->num_muxes = 1;
+	spec->num_adcs = 1;
+	spec->num_pwrs = 0;
+	switch (codec->vendor_id) {
+	case 0x83847632: /* STAC9202  */
+	case 0x83847633: /* STAC9202D */
+	case 0x83847636: /* STAC9251  */
+	case 0x83847637: /* STAC9251D */
+		spec->num_dmics = STAC925X_NUM_DMICS;
+		spec->dmic_nids = stac925x_dmic_nids;
+		spec->num_dmuxes = ARRAY_SIZE(stac925x_dmux_nids);
+		spec->dmux_nids = stac925x_dmux_nids;
+		break;
+	default:
+		spec->num_dmics = 0;
+		break;
+	}
+
+	spec->init = stac925x_core_init;
+	spec->mixer = stac925x_mixer;
+	spec->num_caps = 1;
+	spec->capvols = stac925x_capvols;
+	spec->capsws = stac925x_capsws;
+
+	err = stac92xx_parse_auto_config(codec, 0x8, 0x7);
+	if (!err) {
+		if (spec->board_config < 0) {
+			printk(KERN_WARNING "hda_codec: No auto-config is "
+			       "available, default to model=ref\n");
+			spec->board_config = STAC_925x_REF;
+			goto again;
+		}
+		err = -EINVAL;
+	}
+	if (err < 0) {
+		stac92xx_free(codec);
+		return err;
+	}
+
+	codec->patch_ops = stac92xx_patch_ops;
+
+	return 0;
+}
+
+static int patch_stac92hd73xx(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec;
+	hda_nid_t conn[STAC92HD73_DAC_COUNT + 2];
+	int err = 0;
+	int num_dacs;
+
+	spec  = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->no_trigger_sense = 1;
+	codec->spec = spec;
+	spec->linear_tone_beep = 0;
+	codec->slave_dig_outs = stac92hd73xx_slave_dig_outs;
+	spec->num_pins = ARRAY_SIZE(stac92hd73xx_pin_nids);
+	spec->pin_nids = stac92hd73xx_pin_nids;
+	spec->board_config = snd_hda_check_board_config(codec,
+							STAC_92HD73XX_MODELS,
+							stac92hd73xx_models,
+							stac92hd73xx_cfg_tbl);
+	/* check codec subsystem id if not found */
+	if (spec->board_config < 0)
+		spec->board_config =
+			snd_hda_check_board_codec_sid_config(codec,
+				STAC_92HD73XX_MODELS, stac92hd73xx_models,
+				stac92hd73xx_codec_id_cfg_tbl);
+again:
+	if (spec->board_config < 0)
+		snd_printdd(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+			    codec->chip_name);
+	else
+		stac92xx_set_config_regs(codec,
+				stac92hd73xx_brd_tbl[spec->board_config]);
+
+	num_dacs = snd_hda_get_connections(codec, 0x0a,
+			conn, STAC92HD73_DAC_COUNT + 2) - 1;
+
+	if (num_dacs < 3 || num_dacs > 5) {
+		printk(KERN_WARNING "hda_codec: Could not determine "
+		       "number of channels defaulting to DAC count\n");
+		num_dacs = STAC92HD73_DAC_COUNT;
+	}
+	spec->init = stac92hd73xx_core_init;
+	switch (num_dacs) {
+	case 0x3: /* 6 Channel */
+		spec->aloopback_ctl = stac92hd73xx_6ch_loopback;
+		break;
+	case 0x4: /* 8 Channel */
+		spec->aloopback_ctl = stac92hd73xx_8ch_loopback;
+		break;
+	case 0x5: /* 10 Channel */
+		spec->aloopback_ctl = stac92hd73xx_10ch_loopback;
+		break;
+	}
+	spec->multiout.dac_nids = spec->dac_nids;
+
+	spec->aloopback_mask = 0x01;
+	spec->aloopback_shift = 8;
+
+	spec->digbeep_nid = 0x1c;
+	spec->mux_nids = stac92hd73xx_mux_nids;
+	spec->adc_nids = stac92hd73xx_adc_nids;
+	spec->dmic_nids = stac92hd73xx_dmic_nids;
+	spec->dmux_nids = stac92hd73xx_dmux_nids;
+	spec->smux_nids = stac92hd73xx_smux_nids;
+
+	spec->num_muxes = ARRAY_SIZE(stac92hd73xx_mux_nids);
+	spec->num_adcs = ARRAY_SIZE(stac92hd73xx_adc_nids);
+	spec->num_dmuxes = ARRAY_SIZE(stac92hd73xx_dmux_nids);
+
+	spec->num_caps = STAC92HD73XX_NUM_CAPS;
+	spec->capvols = stac92hd73xx_capvols;
+	spec->capsws = stac92hd73xx_capsws;
+
+	switch (spec->board_config) {
+	case STAC_DELL_EQ:
+		spec->init = dell_eq_core_init;
+		/* fallthru */
+	case STAC_DELL_M6_AMIC:
+	case STAC_DELL_M6_DMIC:
+	case STAC_DELL_M6_BOTH:
+		spec->num_smuxes = 0;
+		spec->eapd_switch = 0;
+
+		switch (spec->board_config) {
+		case STAC_DELL_M6_AMIC: /* Analog Mics */
+			snd_hda_codec_set_pincfg(codec, 0x0b, 0x90A70170);
+			spec->num_dmics = 0;
+			break;
+		case STAC_DELL_M6_DMIC: /* Digital Mics */
+			snd_hda_codec_set_pincfg(codec, 0x13, 0x90A60160);
+			spec->num_dmics = 1;
+			break;
+		case STAC_DELL_M6_BOTH: /* Both */
+			snd_hda_codec_set_pincfg(codec, 0x0b, 0x90A70170);
+			snd_hda_codec_set_pincfg(codec, 0x13, 0x90A60160);
+			spec->num_dmics = 1;
+			break;
+		}
+		break;
+	case STAC_ALIENWARE_M17X:
+		spec->num_dmics = STAC92HD73XX_NUM_DMICS;
+		spec->num_smuxes = ARRAY_SIZE(stac92hd73xx_smux_nids);
+		spec->eapd_switch = 0;
+		break;
+	default:
+		spec->num_dmics = STAC92HD73XX_NUM_DMICS;
+		spec->num_smuxes = ARRAY_SIZE(stac92hd73xx_smux_nids);
+		spec->eapd_switch = 1;
+		break;
+	}
+	if (spec->board_config != STAC_92HD73XX_REF) {
+		/* GPIO0 High = Enable EAPD */
+		spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0x1;
+		spec->gpio_data = 0x01;
+	}
+
+	spec->num_pwrs = ARRAY_SIZE(stac92hd73xx_pwr_nids);
+	spec->pwr_nids = stac92hd73xx_pwr_nids;
+
+	err = stac92xx_parse_auto_config(codec, 0x25, 0x27);
+
+	if (!err) {
+		if (spec->board_config < 0) {
+			printk(KERN_WARNING "hda_codec: No auto-config is "
+			       "available, default to model=ref\n");
+			spec->board_config = STAC_92HD73XX_REF;
+			goto again;
+		}
+		err = -EINVAL;
+	}
+
+	if (err < 0) {
+		stac92xx_free(codec);
+		return err;
+	}
+
+	if (spec->board_config == STAC_92HD73XX_NO_JD)
+		spec->hp_detect = 0;
+
+	codec->patch_ops = stac92xx_patch_ops;
+
+	codec->proc_widget_hook = stac92hd7x_proc_hook;
+
+	return 0;
+}
+
+static int stac92hd83xxx_set_system_btl_amp(struct hda_codec *codec)
+{
+	if (codec->vendor_id != 0x111d7605 &&
+	    codec->vendor_id != 0x111d76d1)
+		return 0;
+
+	switch (codec->subsystem_id) {
+	case 0x103c1618:
+	case 0x103c1619:
+	case 0x103c161a:
+	case 0x103c161b:
+	case 0x103c161c:
+	case 0x103c161d:
+	case 0x103c161e:
+	case 0x103c161f:
+	case 0x103c1620:
+	case 0x103c1621:
+	case 0x103c1622:
+	case 0x103c1623:
+
+	case 0x103c162a:
+	case 0x103c162b:
+
+	case 0x103c1630:
+	case 0x103c1631:
+
+	case 0x103c1633:
+
+	case 0x103c1635:
+
+	case 0x103c164f:
+
+	case 0x103c1676:
+	case 0x103c1677:
+	case 0x103c1678:
+	case 0x103c1679:
+	case 0x103c167a:
+	case 0x103c167b:
+	case 0x103c167c:
+	case 0x103c167d:
+	case 0x103c167e:
+	case 0x103c167f:
+	case 0x103c1680:
+	case 0x103c1681:
+	case 0x103c1682:
+	case 0x103c1683:
+	case 0x103c1684:
+	case 0x103c1685:
+	case 0x103c1686:
+	case 0x103c1687:
+	case 0x103c1688:
+	case 0x103c1689:
+	case 0x103c168a:
+	case 0x103c168b:
+	case 0x103c168c:
+	case 0x103c168d:
+	case 0x103c168e:
+	case 0x103c168f:
+	case 0x103c1690:
+	case 0x103c1691:
+	case 0x103c1692:
+
+	case 0x103c3587:
+	case 0x103c3588:
+	case 0x103c3589:
+	case 0x103c358a:
+
+	case 0x103c3667:
+	case 0x103c3668:
+		/* set BTL amp level to 13.43dB for louder speaker output */
+		return snd_hda_codec_write_cache(codec, codec->afg, 0,
+						 0x7F4, 0x14);
+	}
+	return 0;
+}
+
+static int patch_stac92hd83xxx(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec;
+	hda_nid_t conn[STAC92HD83_DAC_COUNT + 1];
+	int err;
+	int num_dacs;
+
+	spec  = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	/* reset pin power-down; Windows may leave these bits after reboot */
+	snd_hda_codec_write_cache(codec, codec->afg, 0, 0x7EC, 0);
+	snd_hda_codec_write_cache(codec, codec->afg, 0, 0x7ED, 0);
+	codec->no_trigger_sense = 1;
+	codec->spec = spec;
+	spec->linear_tone_beep = 1;
+	codec->slave_dig_outs = stac92hd83xxx_slave_dig_outs;
+	spec->digbeep_nid = 0x21;
+	spec->dmic_nids = stac92hd83xxx_dmic_nids;
+	spec->dmux_nids = stac92hd83xxx_mux_nids;
+	spec->mux_nids = stac92hd83xxx_mux_nids;
+	spec->num_muxes = ARRAY_SIZE(stac92hd83xxx_mux_nids);
+	spec->adc_nids = stac92hd83xxx_adc_nids;
+	spec->num_adcs = ARRAY_SIZE(stac92hd83xxx_adc_nids);
+	spec->pwr_nids = stac92hd83xxx_pwr_nids;
+	spec->pwr_mapping = stac92hd83xxx_pwr_mapping;
+	spec->num_pwrs = ARRAY_SIZE(stac92hd83xxx_pwr_nids);
+	spec->multiout.dac_nids = spec->dac_nids;
+
+	spec->init = stac92hd83xxx_core_init;
+	spec->num_pins = ARRAY_SIZE(stac92hd83xxx_pin_nids);
+	spec->pin_nids = stac92hd83xxx_pin_nids;
+	spec->num_caps = STAC92HD83XXX_NUM_CAPS;
+	spec->capvols = stac92hd83xxx_capvols;
+	spec->capsws = stac92hd83xxx_capsws;
+
+	spec->board_config = snd_hda_check_board_config(codec,
+							STAC_92HD83XXX_MODELS,
+							stac92hd83xxx_models,
+							stac92hd83xxx_cfg_tbl);
+again:
+	if (spec->board_config < 0)
+		snd_printdd(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+			    codec->chip_name);
+	else
+		stac92xx_set_config_regs(codec,
+				stac92hd83xxx_brd_tbl[spec->board_config]);
+
+	switch (codec->vendor_id) {
+	case 0x111d76d1:
+	case 0x111d76d9:
+		spec->dmic_nids = stac92hd87b_dmic_nids;
+		spec->num_dmics = stac92xx_connected_ports(codec,
+				stac92hd87b_dmic_nids,
+				STAC92HD87B_NUM_DMICS);
+		/* Fall through */
+	case 0x111d7666:
+	case 0x111d7667:
+	case 0x111d7668:
+	case 0x111d7669:
+		spec->num_pins = ARRAY_SIZE(stac92hd88xxx_pin_nids);
+		spec->pin_nids = stac92hd88xxx_pin_nids;
+		spec->mono_nid = 0;
+		spec->digbeep_nid = 0;
+		spec->num_pwrs = 0;
+		break;
+	case 0x111d7604:
+	case 0x111d76d4:
+	case 0x111d7605:
+	case 0x111d76d5:
+	case 0x111d76e7:
+		if (spec->board_config == STAC_92HD83XXX_PWR_REF)
+			break;
+		spec->num_pwrs = 0;
+		spec->num_dmics = stac92xx_connected_ports(codec,
+				stac92hd83xxx_dmic_nids,
+				STAC92HD83XXX_NUM_DMICS);
+		break;
+	}
+
+	codec->patch_ops = stac92xx_patch_ops;
+
+	if (find_mute_led_gpio(codec, 0))
+		snd_printd("mute LED gpio %d polarity %d\n",
+				spec->gpio_led,
+				spec->gpio_led_polarity);
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	if (spec->gpio_led) {
+		spec->gpio_mask |= spec->gpio_led;
+		spec->gpio_dir |= spec->gpio_led;
+		spec->gpio_data |= spec->gpio_led;
+		/* register check_power_status callback. */
+		codec->patch_ops.check_power_status =
+			stac92xx_hp_check_power_status;
+	}
+#endif	
+
+	err = stac92xx_parse_auto_config(codec, 0x1d, 0);
+	if (!err) {
+		if (spec->board_config < 0) {
+			printk(KERN_WARNING "hda_codec: No auto-config is "
+			       "available, default to model=ref\n");
+			spec->board_config = STAC_92HD83XXX_REF;
+			goto again;
+		}
+		err = -EINVAL;
+	}
+
+	if (err < 0) {
+		stac92xx_free(codec);
+		return err;
+	}
+
+	/* docking output support */
+	num_dacs = snd_hda_get_connections(codec, 0xF,
+				conn, STAC92HD83_DAC_COUNT + 1) - 1;
+	/* skip non-DAC connections */
+	while (num_dacs >= 0 &&
+			(get_wcaps_type(get_wcaps(codec, conn[num_dacs]))
+					!= AC_WID_AUD_OUT))
+		num_dacs--;
+	/* set port E and F to select the last DAC */
+	if (num_dacs >= 0) {
+		snd_hda_codec_write_cache(codec, 0xE, 0,
+			AC_VERB_SET_CONNECT_SEL, num_dacs);
+		snd_hda_codec_write_cache(codec, 0xF, 0,
+			AC_VERB_SET_CONNECT_SEL, num_dacs);
+	}
+
+	stac92hd83xxx_set_system_btl_amp(codec);
+
+	codec->proc_widget_hook = stac92hd_proc_hook;
+
+	return 0;
+}
+
+static int stac92hd71bxx_connected_smuxes(struct hda_codec *codec,
+					  hda_nid_t dig0pin)
+{
+	struct sigmatel_spec *spec = codec->spec;
+	int idx;
+
+	for (idx = 0; idx < spec->num_pins; idx++)
+		if (spec->pin_nids[idx] == dig0pin)
+			break;
+	if ((idx + 2) >= spec->num_pins)
+		return 0;
+
+	/* dig1pin case */
+	if (stac_get_defcfg_connect(codec, idx + 1) != AC_JACK_PORT_NONE)
+		return 2;
+
+	/* dig0pin + dig2pin case */
+	if (stac_get_defcfg_connect(codec, idx + 2) != AC_JACK_PORT_NONE)
+		return 2;
+	if (stac_get_defcfg_connect(codec, idx) != AC_JACK_PORT_NONE)
+		return 1;
+	else
+		return 0;
+}
+
+/* HP dv7 bass switch - GPIO5 */
+#define stac_hp_bass_gpio_info	snd_ctl_boolean_mono_info
+static int stac_hp_bass_gpio_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	ucontrol->value.integer.value[0] = !!(spec->gpio_data & 0x20);
+	return 0;
+}
+
+static int stac_hp_bass_gpio_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	unsigned int gpio_data;
+
+	gpio_data = (spec->gpio_data & ~0x20) |
+		(ucontrol->value.integer.value[0] ? 0x20 : 0);
+	if (gpio_data == spec->gpio_data)
+		return 0;
+	spec->gpio_data = gpio_data;
+	stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, spec->gpio_data);
+	return 1;
+}
+
+static struct snd_kcontrol_new stac_hp_bass_sw_ctrl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info = stac_hp_bass_gpio_info,
+	.get = stac_hp_bass_gpio_get,
+	.put = stac_hp_bass_gpio_put,
+};
+
+static int stac_add_hp_bass_switch(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+
+	if (!stac_control_new(spec, &stac_hp_bass_sw_ctrl,
+			      "Bass Speaker Playback Switch", 0))
+		return -ENOMEM;
+
+	spec->gpio_mask |= 0x20;
+	spec->gpio_dir |= 0x20;
+	spec->gpio_data |= 0x20;
+	return 0;
+}
+
+static int patch_stac92hd71bxx(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec;
+	struct hda_verb *unmute_init = stac92hd71bxx_unmute_core_init;
+	unsigned int pin_cfg;
+	int err = 0;
+
+	spec  = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->no_trigger_sense = 1;
+	codec->spec = spec;
+	spec->linear_tone_beep = 0;
+	codec->patch_ops = stac92xx_patch_ops;
+	spec->num_pins = STAC92HD71BXX_NUM_PINS;
+	switch (codec->vendor_id) {
+	case 0x111d76b6:
+	case 0x111d76b7:
+		spec->pin_nids = stac92hd71bxx_pin_nids_4port;
+		break;
+	case 0x111d7603:
+	case 0x111d7608:
+		/* On 92HD75Bx 0x27 isn't a pin nid */
+		spec->num_pins--;
+		/* fallthrough */
+	default:
+		spec->pin_nids = stac92hd71bxx_pin_nids_6port;
+	}
+	spec->num_pwrs = ARRAY_SIZE(stac92hd71bxx_pwr_nids);
+	spec->board_config = snd_hda_check_board_config(codec,
+							STAC_92HD71BXX_MODELS,
+							stac92hd71bxx_models,
+							stac92hd71bxx_cfg_tbl);
+again:
+	if (spec->board_config < 0)
+		snd_printdd(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+			    codec->chip_name);
+	else
+		stac92xx_set_config_regs(codec,
+				stac92hd71bxx_brd_tbl[spec->board_config]);
+
+	if (spec->board_config != STAC_92HD71BXX_REF) {
+		/* GPIO0 = EAPD */
+		spec->gpio_mask = 0x01;
+		spec->gpio_dir = 0x01;
+		spec->gpio_data = 0x01;
+	}
+
+	spec->dmic_nids = stac92hd71bxx_dmic_nids;
+	spec->dmux_nids = stac92hd71bxx_dmux_nids;
+
+	spec->num_caps = STAC92HD71BXX_NUM_CAPS;
+	spec->capvols = stac92hd71bxx_capvols;
+	spec->capsws = stac92hd71bxx_capsws;
+
+	switch (codec->vendor_id) {
+	case 0x111d76b6: /* 4 Port without Analog Mixer */
+	case 0x111d76b7:
+		unmute_init++;
+		/* fallthru */
+	case 0x111d76b4: /* 6 Port without Analog Mixer */
+	case 0x111d76b5:
+		spec->init = stac92hd71bxx_core_init;
+		codec->slave_dig_outs = stac92hd71bxx_slave_dig_outs;
+		spec->num_dmics = stac92xx_connected_ports(codec,
+					stac92hd71bxx_dmic_nids,
+					STAC92HD71BXX_NUM_DMICS);
+		break;
+	case 0x111d7608: /* 5 Port with Analog Mixer */
+		switch (spec->board_config) {
+		case STAC_HP_M4:
+			/* Enable VREF power saving on GPIO1 detect */
+			err = stac_add_event(spec, codec->afg,
+					     STAC_VREF_EVENT, 0x02);
+			if (err < 0)
+				return err;
+			snd_hda_codec_write_cache(codec, codec->afg, 0,
+				AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x02);
+			snd_hda_codec_write_cache(codec, codec->afg, 0,
+				AC_VERB_SET_UNSOLICITED_ENABLE,
+				AC_USRSP_EN | err);
+			spec->gpio_mask |= 0x02;
+			break;
+		}
+		if ((codec->revision_id & 0xf) == 0 ||
+		    (codec->revision_id & 0xf) == 1)
+			spec->stream_delay = 40; /* 40 milliseconds */
+
+		/* no output amps */
+		spec->num_pwrs = 0;
+		/* disable VSW */
+		spec->init = stac92hd71bxx_core_init;
+		unmute_init++;
+		snd_hda_codec_set_pincfg(codec, 0x0f, 0x40f000f0);
+		snd_hda_codec_set_pincfg(codec, 0x19, 0x40f000f3);
+		stac92hd71bxx_dmic_nids[STAC92HD71BXX_NUM_DMICS - 1] = 0;
+		spec->num_dmics = stac92xx_connected_ports(codec,
+					stac92hd71bxx_dmic_nids,
+					STAC92HD71BXX_NUM_DMICS - 1);
+		break;
+	case 0x111d7603: /* 6 Port with Analog Mixer */
+		if ((codec->revision_id & 0xf) == 1)
+			spec->stream_delay = 40; /* 40 milliseconds */
+
+		/* no output amps */
+		spec->num_pwrs = 0;
+		/* fallthru */
+	default:
+		spec->init = stac92hd71bxx_core_init;
+		codec->slave_dig_outs = stac92hd71bxx_slave_dig_outs;
+		spec->num_dmics = stac92xx_connected_ports(codec,
+					stac92hd71bxx_dmic_nids,
+					STAC92HD71BXX_NUM_DMICS);
+		break;
+	}
+
+	if (get_wcaps(codec, 0xa) & AC_WCAP_IN_AMP)
+		snd_hda_sequence_write_cache(codec, unmute_init);
+
+	/* Some HP machines seem to have unstable codec communications
+	 * especially with ATI fglrx driver.  For recovering from the
+	 * CORB/RIRB stall, allow the BUS reset and keep always sync
+	 */
+	if (spec->board_config == STAC_HP_DV5) {
+		codec->bus->sync_write = 1;
+		codec->bus->allow_bus_reset = 1;
+	}
+
+	spec->aloopback_ctl = stac92hd71bxx_loopback;
+	spec->aloopback_mask = 0x50;
+	spec->aloopback_shift = 0;
+
+	spec->powerdown_adcs = 1;
+	spec->digbeep_nid = 0x26;
+	spec->mux_nids = stac92hd71bxx_mux_nids;
+	spec->adc_nids = stac92hd71bxx_adc_nids;
+	spec->smux_nids = stac92hd71bxx_smux_nids;
+	spec->pwr_nids = stac92hd71bxx_pwr_nids;
+
+	spec->num_muxes = ARRAY_SIZE(stac92hd71bxx_mux_nids);
+	spec->num_adcs = ARRAY_SIZE(stac92hd71bxx_adc_nids);
+	spec->num_dmuxes = ARRAY_SIZE(stac92hd71bxx_dmux_nids);
+	spec->num_smuxes = stac92hd71bxx_connected_smuxes(codec, 0x1e);
+
+	snd_printdd("Found board config: %d\n", spec->board_config);
+
+	switch (spec->board_config) {
+	case STAC_HP_M4:
+		/* enable internal microphone */
+		snd_hda_codec_set_pincfg(codec, 0x0e, 0x01813040);
+		stac92xx_auto_set_pinctl(codec, 0x0e,
+			AC_PINCTL_IN_EN | AC_PINCTL_VREF_80);
+		/* fallthru */
+	case STAC_DELL_M4_2:
+		spec->num_dmics = 0;
+		spec->num_smuxes = 0;
+		spec->num_dmuxes = 0;
+		break;
+	case STAC_DELL_M4_1:
+	case STAC_DELL_M4_3:
+		spec->num_dmics = 1;
+		spec->num_smuxes = 0;
+		spec->num_dmuxes = 1;
+		break;
+	case STAC_HP_DV4_1222NR:
+		spec->num_dmics = 1;
+		/* I don't know if it needs 1 or 2 smuxes - will wait for
+		 * bug reports to fix if needed
+		 */
+		spec->num_smuxes = 1;
+		spec->num_dmuxes = 1;
+		/* fallthrough */
+	case STAC_HP_DV4:
+		spec->gpio_led = 0x01;
+		/* fallthrough */
+	case STAC_HP_DV5:
+		snd_hda_codec_set_pincfg(codec, 0x0d, 0x90170010);
+		stac92xx_auto_set_pinctl(codec, 0x0d, AC_PINCTL_OUT_EN);
+		/* HP dv6 gives the headphone pin as a line-out.  Thus we
+		 * need to set hp_detect flag here to force to enable HP
+		 * detection.
+		 */
+		spec->hp_detect = 1;
+		break;
+	case STAC_HP_HDX:
+		spec->num_dmics = 1;
+		spec->num_dmuxes = 1;
+		spec->num_smuxes = 1;
+		spec->gpio_led = 0x08;
+		break;
+	}
+
+	if (hp_blike_system(codec->subsystem_id)) {
+		pin_cfg = snd_hda_codec_get_pincfg(codec, 0x0f);
+		if (get_defcfg_device(pin_cfg) == AC_JACK_LINE_OUT ||
+			get_defcfg_device(pin_cfg) == AC_JACK_SPEAKER  ||
+			get_defcfg_device(pin_cfg) == AC_JACK_HP_OUT) {
+			/* It was changed in the BIOS to just satisfy MS DTM.
+			 * Lets turn it back into slaved HP
+			 */
+			pin_cfg = (pin_cfg & (~AC_DEFCFG_DEVICE))
+					| (AC_JACK_HP_OUT <<
+						AC_DEFCFG_DEVICE_SHIFT);
+			pin_cfg = (pin_cfg & (~(AC_DEFCFG_DEF_ASSOC
+							| AC_DEFCFG_SEQUENCE)))
+								| 0x1f;
+			snd_hda_codec_set_pincfg(codec, 0x0f, pin_cfg);
+		}
+	}
+
+	if (find_mute_led_gpio(codec, 1))
+		snd_printd("mute LED gpio %d polarity %d\n",
+				spec->gpio_led,
+				spec->gpio_led_polarity);
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	if (spec->gpio_led) {
+		spec->gpio_mask |= spec->gpio_led;
+		spec->gpio_dir |= spec->gpio_led;
+		spec->gpio_data |= spec->gpio_led;
+		/* register check_power_status callback. */
+		codec->patch_ops.check_power_status =
+			stac92xx_hp_check_power_status;
+	}
+#endif	
+
+	spec->multiout.dac_nids = spec->dac_nids;
+
+	err = stac92xx_parse_auto_config(codec, 0x21, 0);
+	if (!err) {
+		if (spec->board_config < 0) {
+			printk(KERN_WARNING "hda_codec: No auto-config is "
+			       "available, default to model=ref\n");
+			spec->board_config = STAC_92HD71BXX_REF;
+			goto again;
+		}
+		err = -EINVAL;
+	}
+
+	if (err < 0) {
+		stac92xx_free(codec);
+		return err;
+	}
+
+	/* enable bass on HP dv7 */
+	if (spec->board_config == STAC_HP_DV4 ||
+	    spec->board_config == STAC_HP_DV5) {
+		unsigned int cap;
+		cap = snd_hda_param_read(codec, 0x1, AC_PAR_GPIO_CAP);
+		cap &= AC_GPIO_IO_COUNT;
+		if (cap >= 6)
+			stac_add_hp_bass_switch(codec);
+	}
+
+	codec->proc_widget_hook = stac92hd7x_proc_hook;
+
+	return 0;
+}
+
+static int patch_stac922x(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec;
+	int err;
+
+	spec  = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->no_trigger_sense = 1;
+	codec->spec = spec;
+	spec->linear_tone_beep = 1;
+	spec->num_pins = ARRAY_SIZE(stac922x_pin_nids);
+	spec->pin_nids = stac922x_pin_nids;
+	spec->board_config = snd_hda_check_board_config(codec, STAC_922X_MODELS,
+							stac922x_models,
+							stac922x_cfg_tbl);
+	if (spec->board_config == STAC_INTEL_MAC_AUTO) {
+		spec->gpio_mask = spec->gpio_dir = 0x03;
+		spec->gpio_data = 0x03;
+		/* Intel Macs have all same PCI SSID, so we need to check
+		 * codec SSID to distinguish the exact models
+		 */
+		printk(KERN_INFO "hda_codec: STAC922x, Apple subsys_id=%x\n", codec->subsystem_id);
+		switch (codec->subsystem_id) {
+
+		case 0x106b0800:
+			spec->board_config = STAC_INTEL_MAC_V1;
+			break;
+		case 0x106b0600:
+		case 0x106b0700:
+			spec->board_config = STAC_INTEL_MAC_V2;
+			break;
+		case 0x106b0e00:
+		case 0x106b0f00:
+		case 0x106b1600:
+		case 0x106b1700:
+		case 0x106b0200:
+		case 0x106b1e00:
+			spec->board_config = STAC_INTEL_MAC_V3;
+			break;
+		case 0x106b1a00:
+		case 0x00000100:
+			spec->board_config = STAC_INTEL_MAC_V4;
+			break;
+		case 0x106b0a00:
+		case 0x106b2200:
+			spec->board_config = STAC_INTEL_MAC_V5;
+			break;
+		default:
+			spec->board_config = STAC_INTEL_MAC_V3;
+			break;
+		}
+	}
+
+ again:
+	if (spec->board_config < 0)
+		snd_printdd(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+			    codec->chip_name);
+	else
+		stac92xx_set_config_regs(codec,
+				stac922x_brd_tbl[spec->board_config]);
+
+	spec->adc_nids = stac922x_adc_nids;
+	spec->mux_nids = stac922x_mux_nids;
+	spec->num_muxes = ARRAY_SIZE(stac922x_mux_nids);
+	spec->num_adcs = ARRAY_SIZE(stac922x_adc_nids);
+	spec->num_dmics = 0;
+	spec->num_pwrs = 0;
+
+	spec->init = stac922x_core_init;
+
+	spec->num_caps = STAC922X_NUM_CAPS;
+	spec->capvols = stac922x_capvols;
+	spec->capsws = stac922x_capsws;
+
+	spec->multiout.dac_nids = spec->dac_nids;
+	
+	err = stac92xx_parse_auto_config(codec, 0x08, 0x09);
+	if (!err) {
+		if (spec->board_config < 0) {
+			printk(KERN_WARNING "hda_codec: No auto-config is "
+			       "available, default to model=ref\n");
+			spec->board_config = STAC_D945_REF;
+			goto again;
+		}
+		err = -EINVAL;
+	}
+	if (err < 0) {
+		stac92xx_free(codec);
+		return err;
+	}
+
+	codec->patch_ops = stac92xx_patch_ops;
+
+	/* Fix Mux capture level; max to 2 */
+	snd_hda_override_amp_caps(codec, 0x12, HDA_OUTPUT,
+				  (0 << AC_AMPCAP_OFFSET_SHIFT) |
+				  (2 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+				  (0x27 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+				  (0 << AC_AMPCAP_MUTE_SHIFT));
+
+	return 0;
+}
+
+static int patch_stac927x(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec;
+	int err;
+
+	spec  = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->no_trigger_sense = 1;
+	codec->spec = spec;
+	spec->linear_tone_beep = 1;
+	codec->slave_dig_outs = stac927x_slave_dig_outs;
+	spec->num_pins = ARRAY_SIZE(stac927x_pin_nids);
+	spec->pin_nids = stac927x_pin_nids;
+	spec->board_config = snd_hda_check_board_config(codec, STAC_927X_MODELS,
+							stac927x_models,
+							stac927x_cfg_tbl);
+ again:
+	if (spec->board_config < 0)
+		snd_printdd(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+			    codec->chip_name);
+	else
+		stac92xx_set_config_regs(codec,
+				stac927x_brd_tbl[spec->board_config]);
+
+	spec->digbeep_nid = 0x23;
+	spec->adc_nids = stac927x_adc_nids;
+	spec->num_adcs = ARRAY_SIZE(stac927x_adc_nids);
+	spec->mux_nids = stac927x_mux_nids;
+	spec->num_muxes = ARRAY_SIZE(stac927x_mux_nids);
+	spec->smux_nids = stac927x_smux_nids;
+	spec->num_smuxes = ARRAY_SIZE(stac927x_smux_nids);
+	spec->spdif_labels = stac927x_spdif_labels;
+	spec->dac_list = stac927x_dac_nids;
+	spec->multiout.dac_nids = spec->dac_nids;
+
+	if (spec->board_config != STAC_D965_REF) {
+		/* GPIO0 High = Enable EAPD */
+		spec->eapd_mask = spec->gpio_mask = 0x01;
+		spec->gpio_dir = spec->gpio_data = 0x01;
+	}
+
+	switch (spec->board_config) {
+	case STAC_D965_3ST:
+	case STAC_D965_5ST:
+		/* GPIO0 High = Enable EAPD */
+		spec->num_dmics = 0;
+		spec->init = d965_core_init;
+		break;
+	case STAC_DELL_BIOS:
+		switch (codec->subsystem_id) {
+		case 0x10280209:
+		case 0x1028022e:
+			/* correct the device field to SPDIF out */
+			snd_hda_codec_set_pincfg(codec, 0x21, 0x01442070);
+			break;
+		}
+		/* configure the analog microphone on some laptops */
+		snd_hda_codec_set_pincfg(codec, 0x0c, 0x90a79130);
+		/* correct the front output jack as a hp out */
+		snd_hda_codec_set_pincfg(codec, 0x0f, 0x0227011f);
+		/* correct the front input jack as a mic */
+		snd_hda_codec_set_pincfg(codec, 0x0e, 0x02a79130);
+		/* fallthru */
+	case STAC_DELL_3ST:
+		if (codec->subsystem_id != 0x1028022f) {
+			/* GPIO2 High = Enable EAPD */
+			spec->eapd_mask = spec->gpio_mask = 0x04;
+			spec->gpio_dir = spec->gpio_data = 0x04;
+		}
+		spec->dmic_nids = stac927x_dmic_nids;
+		spec->num_dmics = STAC927X_NUM_DMICS;
+
+		spec->init = dell_3st_core_init;
+		spec->dmux_nids = stac927x_dmux_nids;
+		spec->num_dmuxes = ARRAY_SIZE(stac927x_dmux_nids);
+		break;
+	case STAC_927X_VOLKNOB:
+		spec->num_dmics = 0;
+		spec->init = stac927x_volknob_core_init;
+		break;
+	default:
+		spec->num_dmics = 0;
+		spec->init = stac927x_core_init;
+		break;
+	}
+
+	spec->num_caps = STAC927X_NUM_CAPS;
+	spec->capvols = stac927x_capvols;
+	spec->capsws = stac927x_capsws;
+
+	spec->num_pwrs = 0;
+	spec->aloopback_ctl = stac927x_loopback;
+	spec->aloopback_mask = 0x40;
+	spec->aloopback_shift = 0;
+	spec->eapd_switch = 1;
+
+	err = stac92xx_parse_auto_config(codec, 0x1e, 0x20);
+	if (!err) {
+		if (spec->board_config < 0) {
+			printk(KERN_WARNING "hda_codec: No auto-config is "
+			       "available, default to model=ref\n");
+			spec->board_config = STAC_D965_REF;
+			goto again;
+		}
+		err = -EINVAL;
+	}
+	if (err < 0) {
+		stac92xx_free(codec);
+		return err;
+	}
+
+	codec->patch_ops = stac92xx_patch_ops;
+
+	codec->proc_widget_hook = stac927x_proc_hook;
+
+	/*
+	 * !!FIXME!!
+	 * The STAC927x seem to require fairly long delays for certain
+	 * command sequences.  With too short delays (even if the answer
+	 * is set to RIRB properly), it results in the silence output
+	 * on some hardwares like Dell.
+	 *
+	 * The below flag enables the longer delay (see get_response
+	 * in hda_intel.c).
+	 */
+	codec->bus->needs_damn_long_delay = 1;
+
+	/* no jack detecion for ref-no-jd model */
+	if (spec->board_config == STAC_D965_REF_NO_JD)
+		spec->hp_detect = 0;
+
+	return 0;
+}
+
+static int patch_stac9205(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec;
+	int err;
+
+	spec  = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	codec->no_trigger_sense = 1;
+	codec->spec = spec;
+	spec->linear_tone_beep = 1;
+	spec->num_pins = ARRAY_SIZE(stac9205_pin_nids);
+	spec->pin_nids = stac9205_pin_nids;
+	spec->board_config = snd_hda_check_board_config(codec, STAC_9205_MODELS,
+							stac9205_models,
+							stac9205_cfg_tbl);
+ again:
+	if (spec->board_config < 0)
+		snd_printdd(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+			    codec->chip_name);
+	else
+		stac92xx_set_config_regs(codec,
+					 stac9205_brd_tbl[spec->board_config]);
+
+	spec->digbeep_nid = 0x23;
+	spec->adc_nids = stac9205_adc_nids;
+	spec->num_adcs = ARRAY_SIZE(stac9205_adc_nids);
+	spec->mux_nids = stac9205_mux_nids;
+	spec->num_muxes = ARRAY_SIZE(stac9205_mux_nids);
+	spec->smux_nids = stac9205_smux_nids;
+	spec->num_smuxes = ARRAY_SIZE(stac9205_smux_nids);
+	spec->dmic_nids = stac9205_dmic_nids;
+	spec->num_dmics = STAC9205_NUM_DMICS;
+	spec->dmux_nids = stac9205_dmux_nids;
+	spec->num_dmuxes = ARRAY_SIZE(stac9205_dmux_nids);
+	spec->num_pwrs = 0;
+
+	spec->init = stac9205_core_init;
+	spec->aloopback_ctl = stac9205_loopback;
+
+	spec->num_caps = STAC9205_NUM_CAPS;
+	spec->capvols = stac9205_capvols;
+	spec->capsws = stac9205_capsws;
+
+	spec->aloopback_mask = 0x40;
+	spec->aloopback_shift = 0;
+	/* Turn on/off EAPD per HP plugging */
+	if (spec->board_config != STAC_9205_EAPD)
+		spec->eapd_switch = 1;
+	spec->multiout.dac_nids = spec->dac_nids;
+	
+	switch (spec->board_config){
+	case STAC_9205_DELL_M43:
+		/* Enable SPDIF in/out */
+		snd_hda_codec_set_pincfg(codec, 0x1f, 0x01441030);
+		snd_hda_codec_set_pincfg(codec, 0x20, 0x1c410030);
+
+		/* Enable unsol response for GPIO4/Dock HP connection */
+		err = stac_add_event(spec, codec->afg, STAC_VREF_EVENT, 0x01);
+		if (err < 0)
+			return err;
+		snd_hda_codec_write_cache(codec, codec->afg, 0,
+			AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x10);
+		snd_hda_codec_write_cache(codec, codec->afg, 0,
+					  AC_VERB_SET_UNSOLICITED_ENABLE,
+					  AC_USRSP_EN | err);
+
+		spec->gpio_dir = 0x0b;
+		spec->eapd_mask = 0x01;
+		spec->gpio_mask = 0x1b;
+		spec->gpio_mute = 0x10;
+		/* GPIO0 High = EAPD, GPIO1 Low = Headphone Mute,
+		 * GPIO3 Low = DRM
+		 */
+		spec->gpio_data = 0x01;
+		break;
+	case STAC_9205_REF:
+		/* SPDIF-In enabled */
+		break;
+	default:
+		/* GPIO0 High = EAPD */
+		spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0x1;
+		spec->gpio_data = 0x01;
+		break;
+	}
+
+	err = stac92xx_parse_auto_config(codec, 0x1f, 0x20);
+	if (!err) {
+		if (spec->board_config < 0) {
+			printk(KERN_WARNING "hda_codec: No auto-config is "
+			       "available, default to model=ref\n");
+			spec->board_config = STAC_9205_REF;
+			goto again;
+		}
+		err = -EINVAL;
+	}
+	if (err < 0) {
+		stac92xx_free(codec);
+		return err;
+	}
+
+	codec->patch_ops = stac92xx_patch_ops;
+
+	codec->proc_widget_hook = stac9205_proc_hook;
+
+	return 0;
+}
+
+/*
+ * STAC9872 hack
+ */
+
+static struct hda_verb stac9872_core_init[] = {
+	{0x15, AC_VERB_SET_CONNECT_SEL, 0x1}, /* mic-sel: 0a,0d,14,02 */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Mic-in -> 0x9 */
+	{}
+};
+
+static hda_nid_t stac9872_pin_nids[] = {
+	0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+	0x11, 0x13, 0x14,
+};
+
+static hda_nid_t stac9872_adc_nids[] = {
+	0x8 /*,0x6*/
+};
+
+static hda_nid_t stac9872_mux_nids[] = {
+	0x15
+};
+
+static unsigned long stac9872_capvols[] = {
+	HDA_COMPOSE_AMP_VAL(0x09, 3, 0, HDA_INPUT),
+};
+#define stac9872_capsws		stac9872_capvols
+
+static unsigned int stac9872_vaio_pin_configs[9] = {
+	0x03211020, 0x411111f0, 0x411111f0, 0x03a15030,
+	0x411111f0, 0x90170110, 0x411111f0, 0x411111f0,
+	0x90a7013e
+};
+
+static const char *stac9872_models[STAC_9872_MODELS] = {
+	[STAC_9872_AUTO] = "auto",
+	[STAC_9872_VAIO] = "vaio",
+};
+
+static unsigned int *stac9872_brd_tbl[STAC_9872_MODELS] = {
+	[STAC_9872_VAIO] = stac9872_vaio_pin_configs,
+};
+
+static struct snd_pci_quirk stac9872_cfg_tbl[] = {
+	SND_PCI_QUIRK_MASK(0x104d, 0xfff0, 0x81e0,
+			   "Sony VAIO F/S", STAC_9872_VAIO),
+	{} /* terminator */
+};
+
+static int patch_stac9872(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec;
+	int err;
+
+	spec  = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return -ENOMEM;
+	codec->no_trigger_sense = 1;
+	codec->spec = spec;
+	spec->linear_tone_beep = 1;
+	spec->num_pins = ARRAY_SIZE(stac9872_pin_nids);
+	spec->pin_nids = stac9872_pin_nids;
+
+	spec->board_config = snd_hda_check_board_config(codec, STAC_9872_MODELS,
+							stac9872_models,
+							stac9872_cfg_tbl);
+	if (spec->board_config < 0)
+		snd_printdd(KERN_INFO "hda_codec: %s: BIOS auto-probing.\n",
+			    codec->chip_name);
+	else
+		stac92xx_set_config_regs(codec,
+					 stac9872_brd_tbl[spec->board_config]);
+
+	spec->multiout.dac_nids = spec->dac_nids;
+	spec->num_adcs = ARRAY_SIZE(stac9872_adc_nids);
+	spec->adc_nids = stac9872_adc_nids;
+	spec->num_muxes = ARRAY_SIZE(stac9872_mux_nids);
+	spec->mux_nids = stac9872_mux_nids;
+	spec->init = stac9872_core_init;
+	spec->num_caps = 1;
+	spec->capvols = stac9872_capvols;
+	spec->capsws = stac9872_capsws;
+
+	err = stac92xx_parse_auto_config(codec, 0x10, 0x12);
+	if (err < 0) {
+		stac92xx_free(codec);
+		return -EINVAL;
+	}
+	spec->input_mux = &spec->private_imux;
+	codec->patch_ops = stac92xx_patch_ops;
+	return 0;
+}
+
+
+/*
+ * patch entries
+ */
+static struct hda_codec_preset snd_hda_preset_sigmatel[] = {
+ 	{ .id = 0x83847690, .name = "STAC9200", .patch = patch_stac9200 },
+ 	{ .id = 0x83847882, .name = "STAC9220 A1", .patch = patch_stac922x },
+ 	{ .id = 0x83847680, .name = "STAC9221 A1", .patch = patch_stac922x },
+ 	{ .id = 0x83847880, .name = "STAC9220 A2", .patch = patch_stac922x },
+ 	{ .id = 0x83847681, .name = "STAC9220D/9223D A2", .patch = patch_stac922x },
+ 	{ .id = 0x83847682, .name = "STAC9221 A2", .patch = patch_stac922x },
+ 	{ .id = 0x83847683, .name = "STAC9221D A2", .patch = patch_stac922x },
+ 	{ .id = 0x83847618, .name = "STAC9227", .patch = patch_stac927x },
+ 	{ .id = 0x83847619, .name = "STAC9227", .patch = patch_stac927x },
+ 	{ .id = 0x83847616, .name = "STAC9228", .patch = patch_stac927x },
+ 	{ .id = 0x83847617, .name = "STAC9228", .patch = patch_stac927x },
+ 	{ .id = 0x83847614, .name = "STAC9229", .patch = patch_stac927x },
+ 	{ .id = 0x83847615, .name = "STAC9229", .patch = patch_stac927x },
+ 	{ .id = 0x83847620, .name = "STAC9274", .patch = patch_stac927x },
+ 	{ .id = 0x83847621, .name = "STAC9274D", .patch = patch_stac927x },
+ 	{ .id = 0x83847622, .name = "STAC9273X", .patch = patch_stac927x },
+ 	{ .id = 0x83847623, .name = "STAC9273D", .patch = patch_stac927x },
+ 	{ .id = 0x83847624, .name = "STAC9272X", .patch = patch_stac927x },
+ 	{ .id = 0x83847625, .name = "STAC9272D", .patch = patch_stac927x },
+ 	{ .id = 0x83847626, .name = "STAC9271X", .patch = patch_stac927x },
+ 	{ .id = 0x83847627, .name = "STAC9271D", .patch = patch_stac927x },
+ 	{ .id = 0x83847628, .name = "STAC9274X5NH", .patch = patch_stac927x },
+ 	{ .id = 0x83847629, .name = "STAC9274D5NH", .patch = patch_stac927x },
+	{ .id = 0x83847632, .name = "STAC9202",  .patch = patch_stac925x },
+	{ .id = 0x83847633, .name = "STAC9202D", .patch = patch_stac925x },
+	{ .id = 0x83847634, .name = "STAC9250", .patch = patch_stac925x },
+	{ .id = 0x83847635, .name = "STAC9250D", .patch = patch_stac925x },
+	{ .id = 0x83847636, .name = "STAC9251", .patch = patch_stac925x },
+	{ .id = 0x83847637, .name = "STAC9250D", .patch = patch_stac925x },
+	{ .id = 0x83847645, .name = "92HD206X", .patch = patch_stac927x },
+	{ .id = 0x83847646, .name = "92HD206D", .patch = patch_stac927x },
+ 	/* The following does not take into account .id=0x83847661 when subsys =
+ 	 * 104D0C00 which is STAC9225s. Because of this, some SZ Notebooks are
+ 	 * currently not fully supported.
+ 	 */
+ 	{ .id = 0x83847661, .name = "CXD9872RD/K", .patch = patch_stac9872 },
+ 	{ .id = 0x83847662, .name = "STAC9872AK", .patch = patch_stac9872 },
+ 	{ .id = 0x83847664, .name = "CXD9872AKD", .patch = patch_stac9872 },
+	{ .id = 0x83847698, .name = "STAC9205", .patch = patch_stac9205 },
+ 	{ .id = 0x838476a0, .name = "STAC9205", .patch = patch_stac9205 },
+ 	{ .id = 0x838476a1, .name = "STAC9205D", .patch = patch_stac9205 },
+ 	{ .id = 0x838476a2, .name = "STAC9204", .patch = patch_stac9205 },
+ 	{ .id = 0x838476a3, .name = "STAC9204D", .patch = patch_stac9205 },
+ 	{ .id = 0x838476a4, .name = "STAC9255", .patch = patch_stac9205 },
+ 	{ .id = 0x838476a5, .name = "STAC9255D", .patch = patch_stac9205 },
+ 	{ .id = 0x838476a6, .name = "STAC9254", .patch = patch_stac9205 },
+ 	{ .id = 0x838476a7, .name = "STAC9254D", .patch = patch_stac9205 },
+	{ .id = 0x111d7603, .name = "92HD75B3X5", .patch = patch_stac92hd71bxx},
+	{ .id = 0x111d7604, .name = "92HD83C1X5", .patch = patch_stac92hd83xxx},
+	{ .id = 0x111d76d4, .name = "92HD83C1C5", .patch = patch_stac92hd83xxx},
+	{ .id = 0x111d7605, .name = "92HD81B1X5", .patch = patch_stac92hd83xxx},
+	{ .id = 0x111d76d5, .name = "92HD81B1C5", .patch = patch_stac92hd83xxx},
+	{ .id = 0x111d76d1, .name = "92HD87B1/3", .patch = patch_stac92hd83xxx},
+	{ .id = 0x111d76d9, .name = "92HD87B2/4", .patch = patch_stac92hd83xxx},
+	{ .id = 0x111d7666, .name = "92HD88B3", .patch = patch_stac92hd83xxx},
+	{ .id = 0x111d7667, .name = "92HD88B1", .patch = patch_stac92hd83xxx},
+	{ .id = 0x111d7668, .name = "92HD88B2", .patch = patch_stac92hd83xxx},
+	{ .id = 0x111d7669, .name = "92HD88B4", .patch = patch_stac92hd83xxx},
+	{ .id = 0x111d7608, .name = "92HD75B2X5", .patch = patch_stac92hd71bxx},
+	{ .id = 0x111d7674, .name = "92HD73D1X5", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d7675, .name = "92HD73C1X5", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d7676, .name = "92HD73E1X5", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76b0, .name = "92HD71B8X", .patch = patch_stac92hd71bxx },
+	{ .id = 0x111d76b1, .name = "92HD71B8X", .patch = patch_stac92hd71bxx },
+	{ .id = 0x111d76b2, .name = "92HD71B7X", .patch = patch_stac92hd71bxx },
+	{ .id = 0x111d76b3, .name = "92HD71B7X", .patch = patch_stac92hd71bxx },
+	{ .id = 0x111d76b4, .name = "92HD71B6X", .patch = patch_stac92hd71bxx },
+	{ .id = 0x111d76b5, .name = "92HD71B6X", .patch = patch_stac92hd71bxx },
+	{ .id = 0x111d76b6, .name = "92HD71B5X", .patch = patch_stac92hd71bxx },
+	{ .id = 0x111d76b7, .name = "92HD71B5X", .patch = patch_stac92hd71bxx },
+	{ .id = 0x111d76c0, .name = "92HD89C3", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76c1, .name = "92HD89C2", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76c2, .name = "92HD89C1", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76c3, .name = "92HD89B3", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76c4, .name = "92HD89B2", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76c5, .name = "92HD89B1", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76c6, .name = "92HD89E3", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76c7, .name = "92HD89E2", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76c8, .name = "92HD89E1", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76c9, .name = "92HD89D3", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76ca, .name = "92HD89D2", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76cb, .name = "92HD89D1", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76cc, .name = "92HD89F3", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76cd, .name = "92HD89F2", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76ce, .name = "92HD89F1", .patch = patch_stac92hd73xx },
+	{ .id = 0x111d76e0, .name = "92HD91BXX", .patch = patch_stac92hd83xxx},
+	{ .id = 0x111d76e7, .name = "92HD90BXX", .patch = patch_stac92hd83xxx},
+	{} /* terminator */
+};
+
+MODULE_ALIAS("snd-hda-codec-id:8384*");
+MODULE_ALIAS("snd-hda-codec-id:111d*");
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("IDT/Sigmatel HD-audio codec");
+
+static struct hda_codec_preset_list sigmatel_list = {
+	.preset = snd_hda_preset_sigmatel,
+	.owner = THIS_MODULE,
+};
+
+static int __init patch_sigmatel_init(void)
+{
+	return snd_hda_add_codec_preset(&sigmatel_list);
+}
+
+static void __exit patch_sigmatel_exit(void)
+{
+	snd_hda_delete_codec_preset(&sigmatel_list);
+}
+
+module_init(patch_sigmatel_init)
+module_exit(patch_sigmatel_exit)
diff --git a/linux/sound/pci/hda/patch_via.c b/linux/sound/pci/hda/patch_via.c
new file mode 100644
index 0000000..d1c3f8d
--- /dev/null
+++ b/linux/sound/pci/hda/patch_via.c
@@ -0,0 +1,6058 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for VIA VT17xx/VT18xx/VT20xx codec
+ *
+ *  (C) 2006-2009 VIA Technology, Inc.
+ *  (C) 2006-2008 Takashi Iwai <tiwai@suse.de>
+ *
+ *  This driver 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 driver 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
+ */
+
+/* * * * * * * * * * * * * * Release History * * * * * * * * * * * * * * * * */
+/*									     */
+/* 2006-03-03  Lydia Wang  Create the basic patch to support VT1708 codec    */
+/* 2006-03-14  Lydia Wang  Modify hard code for some pin widget nid	     */
+/* 2006-08-02  Lydia Wang  Add support to VT1709 codec			     */
+/* 2006-09-08  Lydia Wang  Fix internal loopback recording source select bug */
+/* 2007-09-12  Lydia Wang  Add EAPD enable during driver initialization	     */
+/* 2007-09-17  Lydia Wang  Add VT1708B codec support			    */
+/* 2007-11-14  Lydia Wang  Add VT1708A codec HP and CD pin connect config    */
+/* 2008-02-03  Lydia Wang  Fix Rear channels and Back channels inverse issue */
+/* 2008-03-06  Lydia Wang  Add VT1702 codec and VT1708S codec support	     */
+/* 2008-04-09  Lydia Wang  Add mute front speaker when HP plugin	     */
+/* 2008-04-09  Lydia Wang  Add Independent HP feature			     */
+/* 2008-05-28  Lydia Wang  Add second S/PDIF Out support for VT1702	     */
+/* 2008-09-15  Logan Li	   Add VT1708S Mic Boost workaround/backdoor	     */
+/* 2009-02-16  Logan Li	   Add support for VT1718S			     */
+/* 2009-03-13  Logan Li	   Add support for VT1716S			     */
+/* 2009-04-14  Lydai Wang  Add support for VT1828S and VT2020		     */
+/* 2009-07-08  Lydia Wang  Add support for VT2002P			     */
+/* 2009-07-21  Lydia Wang  Add support for VT1812			     */
+/* 2009-09-19  Lydia Wang  Add support for VT1818S			     */
+/*									     */
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/asoundef.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+#define NID_MAPPING		(-1)
+
+/* amp values */
+#define AMP_VAL_IDX_SHIFT	19
+#define AMP_VAL_IDX_MASK	(0x0f<<19)
+
+/* Pin Widget NID */
+#define VT1708_HP_NID		0x13
+#define VT1708_DIGOUT_NID	0x14
+#define VT1708_DIGIN_NID	0x16
+#define VT1708_DIGIN_PIN	0x26
+#define VT1708_HP_PIN_NID	0x20
+#define VT1708_CD_PIN_NID	0x24
+
+#define VT1709_HP_DAC_NID	0x28
+#define VT1709_DIGOUT_NID	0x13
+#define VT1709_DIGIN_NID	0x17
+#define VT1709_DIGIN_PIN	0x25
+
+#define VT1708B_HP_NID		0x25
+#define VT1708B_DIGOUT_NID	0x12
+#define VT1708B_DIGIN_NID	0x15
+#define VT1708B_DIGIN_PIN	0x21
+
+#define VT1708S_HP_NID		0x25
+#define VT1708S_DIGOUT_NID	0x12
+
+#define VT1702_HP_NID		0x17
+#define VT1702_DIGOUT_NID	0x11
+
+enum VIA_HDA_CODEC {
+	UNKNOWN = -1,
+	VT1708,
+	VT1709_10CH,
+	VT1709_6CH,
+	VT1708B_8CH,
+	VT1708B_4CH,
+	VT1708S,
+	VT1708BCE,
+	VT1702,
+	VT1718S,
+	VT1716S,
+	VT2002P,
+	VT1812,
+	CODEC_TYPES,
+};
+
+struct via_spec {
+	/* codec parameterization */
+	struct snd_kcontrol_new *mixers[6];
+	unsigned int num_mixers;
+
+	struct hda_verb *init_verbs[5];
+	unsigned int num_iverbs;
+
+	char *stream_name_analog;
+	struct hda_pcm_stream *stream_analog_playback;
+	struct hda_pcm_stream *stream_analog_capture;
+
+	char *stream_name_digital;
+	struct hda_pcm_stream *stream_digital_playback;
+	struct hda_pcm_stream *stream_digital_capture;
+
+	/* playback */
+	struct hda_multi_out multiout;
+	hda_nid_t slave_dig_outs[2];
+
+	/* capture */
+	unsigned int num_adc_nids;
+	hda_nid_t *adc_nids;
+	hda_nid_t mux_nids[3];
+	hda_nid_t dig_in_nid;
+	hda_nid_t dig_in_pin;
+
+	/* capture source */
+	const struct hda_input_mux *input_mux;
+	unsigned int cur_mux[3];
+
+	/* PCM information */
+	struct hda_pcm pcm_rec[3];
+
+	/* dynamic controls, init_verbs and input_mux */
+	struct auto_pin_cfg autocfg;
+	struct snd_array kctls;
+	struct hda_input_mux private_imux[2];
+	hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
+
+	/* HP mode source */
+	const struct hda_input_mux *hp_mux;
+	unsigned int hp_independent_mode;
+	unsigned int hp_independent_mode_index;
+	unsigned int smart51_enabled;
+	unsigned int dmic_enabled;
+	enum VIA_HDA_CODEC codec_type;
+
+	/* work to check hp jack state */
+	struct hda_codec *codec;
+	struct delayed_work vt1708_hp_work;
+	int vt1708_jack_detectect;
+	int vt1708_hp_present;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	struct hda_loopback_check loopback;
+#endif
+};
+
+static struct via_spec * via_new_spec(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return NULL;
+
+	codec->spec = spec;
+	spec->codec = codec;
+	return spec;
+}
+
+static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec)
+{
+	u32 vendor_id = codec->vendor_id;
+	u16 ven_id = vendor_id >> 16;
+	u16 dev_id = vendor_id & 0xffff;
+	enum VIA_HDA_CODEC codec_type;
+
+	/* get codec type */
+	if (ven_id != 0x1106)
+		codec_type = UNKNOWN;
+	else if (dev_id >= 0x1708 && dev_id <= 0x170b)
+		codec_type = VT1708;
+	else if (dev_id >= 0xe710 && dev_id <= 0xe713)
+		codec_type = VT1709_10CH;
+	else if (dev_id >= 0xe714 && dev_id <= 0xe717)
+		codec_type = VT1709_6CH;
+	else if (dev_id >= 0xe720 && dev_id <= 0xe723) {
+		codec_type = VT1708B_8CH;
+		if (snd_hda_param_read(codec, 0x16, AC_PAR_CONNLIST_LEN) == 0x7)
+			codec_type = VT1708BCE;
+	} else if (dev_id >= 0xe724 && dev_id <= 0xe727)
+		codec_type = VT1708B_4CH;
+	else if ((dev_id & 0xfff) == 0x397
+		 && (dev_id >> 12) < 8)
+		codec_type = VT1708S;
+	else if ((dev_id & 0xfff) == 0x398
+		 && (dev_id >> 12) < 8)
+		codec_type = VT1702;
+	else if ((dev_id & 0xfff) == 0x428
+		 && (dev_id >> 12) < 8)
+		codec_type = VT1718S;
+	else if (dev_id == 0x0433 || dev_id == 0xa721)
+		codec_type = VT1716S;
+	else if (dev_id == 0x0441 || dev_id == 0x4441)
+		codec_type = VT1718S;
+	else if (dev_id == 0x0438 || dev_id == 0x4438)
+		codec_type = VT2002P;
+	else if (dev_id == 0x0448)
+		codec_type = VT1812;
+	else if (dev_id == 0x0440)
+		codec_type = VT1708S;
+	else
+		codec_type = UNKNOWN;
+	return codec_type;
+};
+
+#define VIA_HP_EVENT		0x01
+#define VIA_GPIO_EVENT		0x02
+#define VIA_JACK_EVENT		0x04
+#define VIA_MONO_EVENT		0x08
+#define VIA_SPEAKER_EVENT	0x10
+#define VIA_BIND_HP_EVENT	0x20
+
+enum {
+	VIA_CTL_WIDGET_VOL,
+	VIA_CTL_WIDGET_MUTE,
+	VIA_CTL_WIDGET_ANALOG_MUTE,
+	VIA_CTL_WIDGET_BIND_PIN_MUTE,
+};
+
+enum {
+	AUTO_SEQ_FRONT = 0,
+	AUTO_SEQ_SURROUND,
+	AUTO_SEQ_CENLFE,
+	AUTO_SEQ_SIDE
+};
+
+static void analog_low_current_mode(struct hda_codec *codec, int stream_idle);
+static void set_jack_power_state(struct hda_codec *codec);
+static int is_aa_path_mute(struct hda_codec *codec);
+
+static void vt1708_start_hp_work(struct via_spec *spec)
+{
+	if (spec->codec_type != VT1708 || spec->autocfg.hp_pins[0] == 0)
+		return;
+	snd_hda_codec_write(spec->codec, 0x1, 0, 0xf81,
+			    !spec->vt1708_jack_detectect);
+	if (!delayed_work_pending(&spec->vt1708_hp_work))
+		schedule_delayed_work(&spec->vt1708_hp_work,
+				      msecs_to_jiffies(100));
+}
+
+static void vt1708_stop_hp_work(struct via_spec *spec)
+{
+	if (spec->codec_type != VT1708 || spec->autocfg.hp_pins[0] == 0)
+		return;
+	if (snd_hda_get_bool_hint(spec->codec, "analog_loopback_hp_detect") == 1
+	    && !is_aa_path_mute(spec->codec))
+		return;
+	snd_hda_codec_write(spec->codec, 0x1, 0, 0xf81,
+			    !spec->vt1708_jack_detectect);
+	cancel_delayed_work(&spec->vt1708_hp_work);
+	flush_scheduled_work();
+}
+
+
+static int analog_input_switch_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	int change = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	set_jack_power_state(codec);
+	analog_low_current_mode(snd_kcontrol_chip(kcontrol), -1);
+	if (snd_hda_get_bool_hint(codec, "analog_loopback_hp_detect") == 1) {
+		if (is_aa_path_mute(codec))
+			vt1708_start_hp_work(codec->spec);
+		else
+			vt1708_stop_hp_work(codec->spec);
+	}
+	return change;
+}
+
+/* modify .put = snd_hda_mixer_amp_switch_put */
+#define ANALOG_INPUT_MUTE						\
+	{		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,		\
+			.name = NULL,					\
+			.index = 0,					\
+			.info = snd_hda_mixer_amp_switch_info,		\
+			.get = snd_hda_mixer_amp_switch_get,		\
+			.put = analog_input_switch_put,			\
+			.private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0) }
+
+static void via_hp_bind_automute(struct hda_codec *codec);
+
+static int bind_pin_switch_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct via_spec *spec = codec->spec;
+	int i;
+	int change = 0;
+
+	long *valp = ucontrol->value.integer.value;
+	int lmute, rmute;
+	if (strstr(kcontrol->id.name, "Switch") == NULL) {
+		snd_printd("Invalid control!\n");
+		return change;
+	}
+	change = snd_hda_mixer_amp_switch_put(kcontrol,
+					      ucontrol);
+	/* Get mute value */
+	lmute = *valp ? 0 : HDA_AMP_MUTE;
+	valp++;
+	rmute = *valp ? 0 : HDA_AMP_MUTE;
+
+	/* Set hp pins */
+	if (!spec->hp_independent_mode) {
+		for (i = 0; i < spec->autocfg.hp_outs; i++) {
+			snd_hda_codec_amp_update(
+				codec, spec->autocfg.hp_pins[i],
+				0, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+				lmute);
+			snd_hda_codec_amp_update(
+				codec, spec->autocfg.hp_pins[i],
+				1, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+				rmute);
+		}
+	}
+
+	if (!lmute && !rmute) {
+		/* Line Outs */
+		for (i = 0; i < spec->autocfg.line_outs; i++)
+			snd_hda_codec_amp_stereo(
+				codec, spec->autocfg.line_out_pins[i],
+				HDA_OUTPUT, 0, HDA_AMP_MUTE, 0);
+		/* Speakers */
+		for (i = 0; i < spec->autocfg.speaker_outs; i++)
+			snd_hda_codec_amp_stereo(
+				codec, spec->autocfg.speaker_pins[i],
+				HDA_OUTPUT, 0, HDA_AMP_MUTE, 0);
+		/* unmute */
+		via_hp_bind_automute(codec);
+
+	} else {
+		if (lmute) {
+			/* Mute all left channels */
+			for (i = 1; i < spec->autocfg.line_outs; i++)
+				snd_hda_codec_amp_update(
+					codec,
+					spec->autocfg.line_out_pins[i],
+					0, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+					lmute);
+			for (i = 0; i < spec->autocfg.speaker_outs; i++)
+				snd_hda_codec_amp_update(
+					codec,
+					spec->autocfg.speaker_pins[i],
+					0, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+					lmute);
+		}
+		if (rmute) {
+			/* mute all right channels */
+			for (i = 1; i < spec->autocfg.line_outs; i++)
+				snd_hda_codec_amp_update(
+					codec,
+					spec->autocfg.line_out_pins[i],
+					1, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+					rmute);
+			for (i = 0; i < spec->autocfg.speaker_outs; i++)
+				snd_hda_codec_amp_update(
+					codec,
+					spec->autocfg.speaker_pins[i],
+					1, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+					rmute);
+		}
+	}
+	return change;
+}
+
+#define BIND_PIN_MUTE							\
+	{		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,		\
+			.name = NULL,					\
+			.index = 0,					\
+			.info = snd_hda_mixer_amp_switch_info,		\
+			.get = snd_hda_mixer_amp_switch_get,		\
+			.put = bind_pin_switch_put,			\
+			.private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0) }
+
+static struct snd_kcontrol_new via_control_templates[] = {
+	HDA_CODEC_VOLUME(NULL, 0, 0, 0),
+	HDA_CODEC_MUTE(NULL, 0, 0, 0),
+	ANALOG_INPUT_MUTE,
+	BIND_PIN_MUTE,
+};
+
+static hda_nid_t vt1708_adc_nids[2] = {
+	/* ADC1-2 */
+	0x15, 0x27
+};
+
+static hda_nid_t vt1709_adc_nids[3] = {
+	/* ADC1-2 */
+	0x14, 0x15, 0x16
+};
+
+static hda_nid_t vt1708B_adc_nids[2] = {
+	/* ADC1-2 */
+	0x13, 0x14
+};
+
+static hda_nid_t vt1708S_adc_nids[2] = {
+	/* ADC1-2 */
+	0x13, 0x14
+};
+
+static hda_nid_t vt1702_adc_nids[3] = {
+	/* ADC1-2 */
+	0x12, 0x20, 0x1F
+};
+
+static hda_nid_t vt1718S_adc_nids[2] = {
+	/* ADC1-2 */
+	0x10, 0x11
+};
+
+static hda_nid_t vt1716S_adc_nids[2] = {
+	/* ADC1-2 */
+	0x13, 0x14
+};
+
+static hda_nid_t vt2002P_adc_nids[2] = {
+	/* ADC1-2 */
+	0x10, 0x11
+};
+
+static hda_nid_t vt1812_adc_nids[2] = {
+	/* ADC1-2 */
+	0x10, 0x11
+};
+
+
+/* add dynamic controls */
+static int __via_add_control(struct via_spec *spec, int type, const char *name,
+			     int idx, unsigned long val)
+{
+	struct snd_kcontrol_new *knew;
+
+	snd_array_init(&spec->kctls, sizeof(*knew), 32);
+	knew = snd_array_new(&spec->kctls);
+	if (!knew)
+		return -ENOMEM;
+	*knew = via_control_templates[type];
+	knew->name = kstrdup(name, GFP_KERNEL);
+	if (!knew->name)
+		return -ENOMEM;
+	if (get_amp_nid_(val))
+		knew->subdevice = HDA_SUBDEV_AMP_FLAG;
+	knew->private_value = val;
+	return 0;
+}
+
+#define via_add_control(spec, type, name, val) \
+	__via_add_control(spec, type, name, 0, val)
+
+static struct snd_kcontrol_new *via_clone_control(struct via_spec *spec,
+						struct snd_kcontrol_new *tmpl)
+{
+	struct snd_kcontrol_new *knew;
+
+	snd_array_init(&spec->kctls, sizeof(*knew), 32);
+	knew = snd_array_new(&spec->kctls);
+	if (!knew)
+		return NULL;
+	*knew = *tmpl;
+	knew->name = kstrdup(tmpl->name, GFP_KERNEL);
+	if (!knew->name)
+		return NULL;
+	return knew;
+}
+
+static void via_free_kctls(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+
+	if (spec->kctls.list) {
+		struct snd_kcontrol_new *kctl = spec->kctls.list;
+		int i;
+		for (i = 0; i < spec->kctls.used; i++)
+			kfree(kctl[i].name);
+	}
+	snd_array_free(&spec->kctls);
+}
+
+/* create input playback/capture controls for the given pin */
+static int via_new_analog_input(struct via_spec *spec, const char *ctlname,
+				int type_idx, int idx, int mix_nid)
+{
+	char name[32];
+	int err;
+
+	sprintf(name, "%s Playback Volume", ctlname);
+	err = __via_add_control(spec, VIA_CTL_WIDGET_VOL, name, type_idx,
+			      HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT));
+	if (err < 0)
+		return err;
+	sprintf(name, "%s Playback Switch", ctlname);
+	err = __via_add_control(spec, VIA_CTL_WIDGET_ANALOG_MUTE, name, type_idx,
+			      HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static void via_auto_set_output_and_unmute(struct hda_codec *codec,
+					   hda_nid_t nid, int pin_type,
+					   int dac_idx)
+{
+	/* set as output */
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+			    pin_type);
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
+			    AMP_OUT_UNMUTE);
+	if (snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)
+		snd_hda_codec_write(codec, nid, 0,
+				    AC_VERB_SET_EAPD_BTLENABLE, 0x02);
+}
+
+
+static void via_auto_init_multi_out(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i <= AUTO_SEQ_SIDE; i++) {
+		hda_nid_t nid = spec->autocfg.line_out_pins[i];
+		if (nid)
+			via_auto_set_output_and_unmute(codec, nid, PIN_OUT, i);
+	}
+}
+
+static void via_auto_init_hp_out(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	hda_nid_t pin;
+	int i;
+
+	for (i = 0; i < spec->autocfg.hp_outs; i++) {
+		pin = spec->autocfg.hp_pins[i];
+		if (pin) /* connect to front */
+			via_auto_set_output_and_unmute(codec, pin, PIN_HP, 0);
+	}
+}
+
+static int is_smart51_pins(struct via_spec *spec, hda_nid_t pin);
+
+static void via_auto_init_analog_input(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	const struct auto_pin_cfg *cfg = &spec->autocfg;
+	unsigned int ctl;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		if (spec->smart51_enabled && is_smart51_pins(spec, nid))
+			ctl = PIN_OUT;
+		else if (i == AUTO_PIN_MIC)
+			ctl = PIN_VREF50;
+		else
+			ctl = PIN_IN;
+		snd_hda_codec_write(codec, nid, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, ctl);
+	}
+}
+
+static void set_pin_power_state(struct hda_codec *codec, hda_nid_t nid,
+				unsigned int *affected_parm)
+{
+	unsigned parm;
+	unsigned def_conf = snd_hda_codec_get_pincfg(codec, nid);
+	unsigned no_presence = (def_conf & AC_DEFCFG_MISC)
+		>> AC_DEFCFG_MISC_SHIFT
+		& AC_DEFCFG_MISC_NO_PRESENCE; /* do not support pin sense */
+	unsigned present = snd_hda_jack_detect(codec, nid);
+	struct via_spec *spec = codec->spec;
+	if ((spec->smart51_enabled && is_smart51_pins(spec, nid))
+	    || ((no_presence || present)
+		&& get_defcfg_connect(def_conf) != AC_JACK_PORT_NONE)) {
+		*affected_parm = AC_PWRST_D0; /* if it's connected */
+		parm = AC_PWRST_D0;
+	} else
+		parm = AC_PWRST_D3;
+
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE, parm);
+}
+
+static void set_jack_power_state(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int imux_is_smixer;
+	unsigned int parm;
+
+	if (spec->codec_type == VT1702) {
+		imux_is_smixer = snd_hda_codec_read(
+			codec, 0x13, 0, AC_VERB_GET_CONNECT_SEL, 0x00) == 3;
+		/* inputs */
+		/* PW 1/2/5 (14h/15h/18h) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x14, &parm);
+		set_pin_power_state(codec, 0x15, &parm);
+		set_pin_power_state(codec, 0x18, &parm);
+		if (imux_is_smixer)
+			parm = AC_PWRST_D0; /* SW0 = stereo mixer (idx 3) */
+		/* SW0 (13h), AIW 0/1/2 (12h/1fh/20h) */
+		snd_hda_codec_write(codec, 0x13, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x12, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x1f, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		/* outputs */
+		/* PW 3/4 (16h/17h) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x16, &parm);
+		set_pin_power_state(codec, 0x17, &parm);
+		/* MW0 (1ah), AOW 0/1 (10h/1dh) */
+		snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_POWER_STATE,
+				    imux_is_smixer ? AC_PWRST_D0 : parm);
+		snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x1d, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+	} else if (spec->codec_type == VT1708B_8CH
+		   || spec->codec_type == VT1708B_4CH
+		   || spec->codec_type == VT1708S) {
+		/* SW0 (17h) = stereo mixer */
+		int is_8ch = spec->codec_type != VT1708B_4CH;
+		imux_is_smixer = snd_hda_codec_read(
+			codec, 0x17, 0, AC_VERB_GET_CONNECT_SEL, 0x00)
+			== ((spec->codec_type == VT1708S)  ? 5 : 0);
+		/* inputs */
+		/* PW 1/2/5 (1ah/1bh/1eh) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x1a, &parm);
+		set_pin_power_state(codec, 0x1b, &parm);
+		set_pin_power_state(codec, 0x1e, &parm);
+		if (imux_is_smixer)
+			parm = AC_PWRST_D0;
+		/* SW0 (17h), AIW 0/1 (13h/14h) */
+		snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x13, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x14, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		/* outputs */
+		/* PW0 (19h), SW1 (18h), AOW1 (11h) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x19, &parm);
+		if (spec->smart51_enabled)
+			parm = AC_PWRST_D0;
+		snd_hda_codec_write(codec, 0x18, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x11, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		/* PW6 (22h), SW2 (26h), AOW2 (24h) */
+		if (is_8ch) {
+			parm = AC_PWRST_D3;
+			set_pin_power_state(codec, 0x22, &parm);
+			if (spec->smart51_enabled)
+				parm = AC_PWRST_D0;
+			snd_hda_codec_write(codec, 0x26, 0,
+					    AC_VERB_SET_POWER_STATE, parm);
+			snd_hda_codec_write(codec, 0x24, 0,
+					    AC_VERB_SET_POWER_STATE, parm);
+		}
+
+		/* PW 3/4/7 (1ch/1dh/23h) */
+		parm = AC_PWRST_D3;
+		/* force to D0 for internal Speaker */
+		set_pin_power_state(codec, 0x1c, &parm);
+		set_pin_power_state(codec, 0x1d, &parm);
+		if (is_8ch)
+			set_pin_power_state(codec, 0x23, &parm);
+		/* MW0 (16h), Sw3 (27h), AOW 0/3 (10h/25h) */
+		snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_POWER_STATE,
+				    imux_is_smixer ? AC_PWRST_D0 : parm);
+		snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		if (is_8ch) {
+			snd_hda_codec_write(codec, 0x25, 0,
+					    AC_VERB_SET_POWER_STATE, parm);
+			snd_hda_codec_write(codec, 0x27, 0,
+					    AC_VERB_SET_POWER_STATE, parm);
+		}
+	}  else if (spec->codec_type == VT1718S) {
+		/* MUX6 (1eh) = stereo mixer */
+		imux_is_smixer = snd_hda_codec_read(
+			codec, 0x1e, 0, AC_VERB_GET_CONNECT_SEL, 0x00) == 5;
+		/* inputs */
+		/* PW 5/6/7 (29h/2ah/2bh) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x29, &parm);
+		set_pin_power_state(codec, 0x2a, &parm);
+		set_pin_power_state(codec, 0x2b, &parm);
+		if (imux_is_smixer)
+			parm = AC_PWRST_D0;
+		/* MUX6/7 (1eh/1fh), AIW 0/1 (10h/11h) */
+		snd_hda_codec_write(codec, 0x1e, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x1f, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x11, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		/* outputs */
+		/* PW3 (27h), MW2 (1ah), AOW3 (bh) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x27, &parm);
+		snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0xb, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		/* PW2 (26h), AOW2 (ah) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x26, &parm);
+		snd_hda_codec_write(codec, 0xa, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		/* PW0/1 (24h/25h) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x24, &parm);
+		set_pin_power_state(codec, 0x25, &parm);
+		if (!spec->hp_independent_mode) /* check for redirected HP */
+			set_pin_power_state(codec, 0x28, &parm);
+		snd_hda_codec_write(codec, 0x8, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x9, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		/* MW9 (21h), Mw2 (1ah), AOW0 (8h) */
+		snd_hda_codec_write(codec, 0x21, 0, AC_VERB_SET_POWER_STATE,
+				    imux_is_smixer ? AC_PWRST_D0 : parm);
+		if (spec->hp_independent_mode) {
+			/* PW4 (28h), MW3 (1bh), MUX1(34h), AOW4 (ch) */
+			parm = AC_PWRST_D3;
+			set_pin_power_state(codec, 0x28, &parm);
+			snd_hda_codec_write(codec, 0x1b, 0,
+					    AC_VERB_SET_POWER_STATE, parm);
+			snd_hda_codec_write(codec, 0x34, 0,
+					    AC_VERB_SET_POWER_STATE, parm);
+			snd_hda_codec_write(codec, 0xc, 0,
+					    AC_VERB_SET_POWER_STATE, parm);
+		}
+	} else if (spec->codec_type == VT1716S) {
+		unsigned int mono_out, present;
+		/* SW0 (17h) = stereo mixer */
+		imux_is_smixer = snd_hda_codec_read(
+			codec, 0x17, 0, AC_VERB_GET_CONNECT_SEL, 0x00) ==  5;
+		/* inputs */
+		/* PW 1/2/5 (1ah/1bh/1eh) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x1a, &parm);
+		set_pin_power_state(codec, 0x1b, &parm);
+		set_pin_power_state(codec, 0x1e, &parm);
+		if (imux_is_smixer)
+			parm = AC_PWRST_D0;
+		/* SW0 (17h), AIW0(13h) */
+		snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x13, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x1e, &parm);
+		/* PW11 (22h) */
+		if (spec->dmic_enabled)
+			set_pin_power_state(codec, 0x22, &parm);
+		else
+			snd_hda_codec_write(
+				codec, 0x22, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+
+		/* SW2(26h), AIW1(14h) */
+		snd_hda_codec_write(codec, 0x26, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x14, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		/* outputs */
+		/* PW0 (19h), SW1 (18h), AOW1 (11h) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x19, &parm);
+		/* Smart 5.1 PW2(1bh) */
+		if (spec->smart51_enabled)
+			set_pin_power_state(codec, 0x1b, &parm);
+		snd_hda_codec_write(codec, 0x18, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x11, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		/* PW7 (23h), SW3 (27h), AOW3 (25h) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x23, &parm);
+		/* Smart 5.1 PW1(1ah) */
+		if (spec->smart51_enabled)
+			set_pin_power_state(codec, 0x1a, &parm);
+		snd_hda_codec_write(codec, 0x27, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		/* Smart 5.1 PW5(1eh) */
+		if (spec->smart51_enabled)
+			set_pin_power_state(codec, 0x1e, &parm);
+		snd_hda_codec_write(codec, 0x25, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		/* Mono out */
+		/* SW4(28h)->MW1(29h)-> PW12 (2ah)*/
+		present = snd_hda_jack_detect(codec, 0x1c);
+		if (present)
+			mono_out = 0;
+		else {
+			present = snd_hda_jack_detect(codec, 0x1d);
+			if (!spec->hp_independent_mode && present)
+				mono_out = 0;
+			else
+				mono_out = 1;
+		}
+		parm = mono_out ? AC_PWRST_D0 : AC_PWRST_D3;
+		snd_hda_codec_write(codec, 0x28, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x29, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+		snd_hda_codec_write(codec, 0x2a, 0, AC_VERB_SET_POWER_STATE,
+				    parm);
+
+		/* PW 3/4 (1ch/1dh) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x1c, &parm);
+		set_pin_power_state(codec, 0x1d, &parm);
+		/* HP Independent Mode, power on AOW3 */
+		if (spec->hp_independent_mode)
+			snd_hda_codec_write(codec, 0x25, 0,
+					    AC_VERB_SET_POWER_STATE, parm);
+
+		/* force to D0 for internal Speaker */
+		/* MW0 (16h), AOW0 (10h) */
+		snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_POWER_STATE,
+				    imux_is_smixer ? AC_PWRST_D0 : parm);
+		snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_POWER_STATE,
+				    mono_out ? AC_PWRST_D0 : parm);
+	} else if (spec->codec_type == VT2002P) {
+		unsigned int present;
+		/* MUX9 (1eh) = stereo mixer */
+		imux_is_smixer = snd_hda_codec_read(
+			codec, 0x1e, 0, AC_VERB_GET_CONNECT_SEL, 0x00) == 3;
+		/* inputs */
+		/* PW 5/6/7 (29h/2ah/2bh) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x29, &parm);
+		set_pin_power_state(codec, 0x2a, &parm);
+		set_pin_power_state(codec, 0x2b, &parm);
+		if (imux_is_smixer)
+			parm = AC_PWRST_D0;
+		/* MUX9/10 (1eh/1fh), AIW 0/1 (10h/11h) */
+		snd_hda_codec_write(codec, 0x1e, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		snd_hda_codec_write(codec, 0x1f, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		snd_hda_codec_write(codec, 0x10, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		snd_hda_codec_write(codec, 0x11, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+
+		/* outputs */
+		/* AOW0 (8h)*/
+		snd_hda_codec_write(codec, 0x8, 0,
+				    AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+
+		/* PW4 (26h), MW4 (1ch), MUX4(37h) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x26, &parm);
+		snd_hda_codec_write(codec, 0x1c, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		snd_hda_codec_write(codec, 0x37,
+				    0, AC_VERB_SET_POWER_STATE, parm);
+
+		/* PW1 (25h), MW1 (19h), MUX1(35h), AOW1 (9h) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x25, &parm);
+		snd_hda_codec_write(codec, 0x19, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		snd_hda_codec_write(codec, 0x35, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		if (spec->hp_independent_mode)	{
+			snd_hda_codec_write(codec, 0x9, 0,
+					    AC_VERB_SET_POWER_STATE, parm);
+		}
+
+		/* Class-D */
+		/* PW0 (24h), MW0(18h), MUX0(34h) */
+		present = snd_hda_jack_detect(codec, 0x25);
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x24, &parm);
+		if (present) {
+			snd_hda_codec_write(
+				codec, 0x18, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+			snd_hda_codec_write(
+				codec, 0x34, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+		} else {
+			snd_hda_codec_write(
+				codec, 0x18, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+			snd_hda_codec_write(
+				codec, 0x34, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+		}
+
+		/* Mono Out */
+		/* PW15 (31h), MW8(17h), MUX8(3bh) */
+		present = snd_hda_jack_detect(codec, 0x26);
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x31, &parm);
+		if (present) {
+			snd_hda_codec_write(
+				codec, 0x17, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+			snd_hda_codec_write(
+				codec, 0x3b, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+		} else {
+			snd_hda_codec_write(
+				codec, 0x17, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+			snd_hda_codec_write(
+				codec, 0x3b, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+		}
+
+		/* MW9 (21h) */
+		if (imux_is_smixer || !is_aa_path_mute(codec))
+			snd_hda_codec_write(
+				codec, 0x21, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+		else
+			snd_hda_codec_write(
+				codec, 0x21, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+	} else if (spec->codec_type == VT1812) {
+		unsigned int present;
+		/* MUX10 (1eh) = stereo mixer */
+		imux_is_smixer = snd_hda_codec_read(
+			codec, 0x1e, 0, AC_VERB_GET_CONNECT_SEL, 0x00) == 5;
+		/* inputs */
+		/* PW 5/6/7 (29h/2ah/2bh) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x29, &parm);
+		set_pin_power_state(codec, 0x2a, &parm);
+		set_pin_power_state(codec, 0x2b, &parm);
+		if (imux_is_smixer)
+			parm = AC_PWRST_D0;
+		/* MUX10/11 (1eh/1fh), AIW 0/1 (10h/11h) */
+		snd_hda_codec_write(codec, 0x1e, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		snd_hda_codec_write(codec, 0x1f, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		snd_hda_codec_write(codec, 0x10, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		snd_hda_codec_write(codec, 0x11, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+
+		/* outputs */
+		/* AOW0 (8h)*/
+		snd_hda_codec_write(codec, 0x8, 0,
+				    AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+
+		/* PW4 (28h), MW4 (18h), MUX4(38h) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x28, &parm);
+		snd_hda_codec_write(codec, 0x18, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		snd_hda_codec_write(codec, 0x38, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+
+		/* PW1 (25h), MW1 (15h), MUX1(35h), AOW1 (9h) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x25, &parm);
+		snd_hda_codec_write(codec, 0x15, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		snd_hda_codec_write(codec, 0x35, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		if (spec->hp_independent_mode)	{
+			snd_hda_codec_write(codec, 0x9, 0,
+					    AC_VERB_SET_POWER_STATE, parm);
+		}
+
+		/* Internal Speaker */
+		/* PW0 (24h), MW0(14h), MUX0(34h) */
+		present = snd_hda_jack_detect(codec, 0x25);
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x24, &parm);
+		if (present) {
+			snd_hda_codec_write(codec, 0x14, 0,
+					    AC_VERB_SET_POWER_STATE,
+					    AC_PWRST_D3);
+			snd_hda_codec_write(codec, 0x34, 0,
+					    AC_VERB_SET_POWER_STATE,
+					    AC_PWRST_D3);
+		} else {
+			snd_hda_codec_write(codec, 0x14, 0,
+					    AC_VERB_SET_POWER_STATE,
+					    AC_PWRST_D0);
+			snd_hda_codec_write(codec, 0x34, 0,
+					    AC_VERB_SET_POWER_STATE,
+					    AC_PWRST_D0);
+		}
+		/* Mono Out */
+		/* PW13 (31h), MW13(1ch), MUX13(3ch), MW14(3eh) */
+		present = snd_hda_jack_detect(codec, 0x28);
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x31, &parm);
+		if (present) {
+			snd_hda_codec_write(codec, 0x1c, 0,
+					    AC_VERB_SET_POWER_STATE,
+					    AC_PWRST_D3);
+			snd_hda_codec_write(codec, 0x3c, 0,
+					    AC_VERB_SET_POWER_STATE,
+					    AC_PWRST_D3);
+			snd_hda_codec_write(codec, 0x3e, 0,
+					    AC_VERB_SET_POWER_STATE,
+					    AC_PWRST_D3);
+		} else {
+			snd_hda_codec_write(codec, 0x1c, 0,
+					    AC_VERB_SET_POWER_STATE,
+					    AC_PWRST_D0);
+			snd_hda_codec_write(codec, 0x3c, 0,
+					    AC_VERB_SET_POWER_STATE,
+					    AC_PWRST_D0);
+			snd_hda_codec_write(codec, 0x3e, 0,
+					    AC_VERB_SET_POWER_STATE,
+					    AC_PWRST_D0);
+		}
+
+		/* PW15 (33h), MW15 (1dh), MUX15(3dh) */
+		parm = AC_PWRST_D3;
+		set_pin_power_state(codec, 0x33, &parm);
+		snd_hda_codec_write(codec, 0x1d, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+		snd_hda_codec_write(codec, 0x3d, 0,
+				    AC_VERB_SET_POWER_STATE, parm);
+
+		/* MW9 (21h) */
+		if (imux_is_smixer || !is_aa_path_mute(codec))
+			snd_hda_codec_write(
+				codec, 0x21, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+		else
+			snd_hda_codec_write(
+				codec, 0x21, 0,
+				AC_VERB_SET_POWER_STATE, AC_PWRST_D3);
+	}
+}
+
+/*
+ * input MUX handling
+ */
+static int via_mux_enum_info(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct via_spec *spec = codec->spec;
+	return snd_hda_input_mux_info(spec->input_mux, uinfo);
+}
+
+static int via_mux_enum_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct via_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
+	return 0;
+}
+
+static int via_mux_enum_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct via_spec *spec = codec->spec;
+	unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+	if (!spec->mux_nids[adc_idx])
+		return -EINVAL;
+	/* switch to D0 beofre change index */
+	if (snd_hda_codec_read(codec, spec->mux_nids[adc_idx], 0,
+			       AC_VERB_GET_POWER_STATE, 0x00) != AC_PWRST_D0)
+		snd_hda_codec_write(codec, spec->mux_nids[adc_idx], 0,
+				    AC_VERB_SET_POWER_STATE, AC_PWRST_D0);
+	/* update jack power state */
+	set_jack_power_state(codec);
+
+	return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
+				     spec->mux_nids[adc_idx],
+				     &spec->cur_mux[adc_idx]);
+}
+
+static int via_independent_hp_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct via_spec *spec = codec->spec;
+	return snd_hda_input_mux_info(spec->hp_mux, uinfo);
+}
+
+static int via_independent_hp_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	hda_nid_t nid = kcontrol->private_value;
+	unsigned int pinsel;
+
+	/* use !! to translate conn sel 2 for VT1718S */
+	pinsel = !!snd_hda_codec_read(codec, nid, 0,
+				      AC_VERB_GET_CONNECT_SEL,
+				      0x00);
+	ucontrol->value.enumerated.item[0] = pinsel;
+
+	return 0;
+}
+
+static void activate_ctl(struct hda_codec *codec, const char *name, int active)
+{
+	struct snd_kcontrol *ctl = snd_hda_find_mixer_ctl(codec, name);
+	if (ctl) {
+		ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		ctl->vd[0].access |= active
+			? 0 : SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(codec->bus->card,
+			       SNDRV_CTL_EVENT_MASK_VALUE, &ctl->id);
+	}
+}
+
+static hda_nid_t side_mute_channel(struct via_spec *spec)
+{
+	switch (spec->codec_type) {
+	case VT1708:		return 0x1b;
+	case VT1709_10CH:	return 0x29;
+	case VT1708B_8CH:	/* fall thru */
+	case VT1708S:		return 0x27;
+	default:		return 0;
+	}
+}
+
+static int update_side_mute_status(struct hda_codec *codec)
+{
+	/* mute side channel */
+	struct via_spec *spec = codec->spec;
+	unsigned int parm = spec->hp_independent_mode
+		? AMP_OUT_MUTE : AMP_OUT_UNMUTE;
+	hda_nid_t sw3 = side_mute_channel(spec);
+
+	if (sw3)
+		snd_hda_codec_write(codec, sw3, 0, AC_VERB_SET_AMP_GAIN_MUTE,
+				    parm);
+	return 0;
+}
+
+static int via_independent_hp_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct via_spec *spec = codec->spec;
+	hda_nid_t nid = kcontrol->private_value;
+	unsigned int pinsel = ucontrol->value.enumerated.item[0];
+	/* Get Independent Mode index of headphone pin widget */
+	spec->hp_independent_mode = spec->hp_independent_mode_index == pinsel
+		? 1 : 0;
+	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, pinsel);
+
+	if (spec->multiout.hp_nid && spec->multiout.hp_nid
+	    != spec->multiout.dac_nids[HDA_FRONT])
+		snd_hda_codec_setup_stream(codec, spec->multiout.hp_nid,
+					   0, 0, 0);
+
+	update_side_mute_status(codec);
+	/* update HP volume/swtich active state */
+	if (spec->codec_type == VT1708S
+	    || spec->codec_type == VT1702
+	    || spec->codec_type == VT1718S
+	    || spec->codec_type == VT1716S
+	    || spec->codec_type == VT2002P
+	    || spec->codec_type == VT1812) {
+		activate_ctl(codec, "Headphone Playback Volume",
+			     spec->hp_independent_mode);
+		activate_ctl(codec, "Headphone Playback Switch",
+			     spec->hp_independent_mode);
+	}
+	return 0;
+}
+
+static struct snd_kcontrol_new via_hp_mixer[2] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Independent HP",
+		.info = via_independent_hp_info,
+		.get = via_independent_hp_get,
+		.put = via_independent_hp_put,
+	},
+	{
+		.iface = NID_MAPPING,
+		.name = "Independent HP",
+	},
+};
+
+static int via_hp_build(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	struct snd_kcontrol_new *knew;
+	hda_nid_t nid;
+	int nums;
+	hda_nid_t conn[HDA_MAX_CONNECTIONS];
+
+	switch (spec->codec_type) {
+	case VT1718S:
+		nid = 0x34;
+		break;
+	case VT2002P:
+		nid = 0x35;
+		break;
+	case VT1812:
+		nid = 0x3d;
+		break;
+	default:
+		nid = spec->autocfg.hp_pins[0];
+		break;
+	}
+
+	nums = snd_hda_get_connections(codec, nid, conn, HDA_MAX_CONNECTIONS);
+	if (nums <= 1)
+		return 0;
+
+	knew = via_clone_control(spec, &via_hp_mixer[0]);
+	if (knew == NULL)
+		return -ENOMEM;
+
+	knew->subdevice = HDA_SUBDEV_NID_FLAG | nid;
+	knew->private_value = nid;
+
+	knew = via_clone_control(spec, &via_hp_mixer[1]);
+	if (knew == NULL)
+		return -ENOMEM;
+	knew->subdevice = side_mute_channel(spec);
+
+	return 0;
+}
+
+static void notify_aa_path_ctls(struct hda_codec *codec)
+{
+	int i;
+	struct snd_ctl_elem_id id;
+	const char *labels[] = {"Mic", "Front Mic", "Line"};
+
+	memset(&id, 0, sizeof(id));
+	id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	for (i = 0; i < ARRAY_SIZE(labels); i++) {
+		sprintf(id.name, "%s Playback Volume", labels[i]);
+		snd_ctl_notify(codec->bus->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &id);
+	}
+}
+
+static void mute_aa_path(struct hda_codec *codec, int mute)
+{
+	struct via_spec *spec = codec->spec;
+	hda_nid_t  nid_mixer;
+	int start_idx;
+	int end_idx;
+	int i;
+	/* get nid of MW0 and start & end index */
+	switch (spec->codec_type) {
+	case VT1708:
+		nid_mixer = 0x17;
+		start_idx = 2;
+		end_idx = 4;
+		break;
+	case VT1709_10CH:
+	case VT1709_6CH:
+		nid_mixer = 0x18;
+		start_idx = 2;
+		end_idx = 4;
+		break;
+	case VT1708B_8CH:
+	case VT1708B_4CH:
+	case VT1708S:
+	case VT1716S:
+		nid_mixer = 0x16;
+		start_idx = 2;
+		end_idx = 4;
+		break;
+	default:
+		return;
+	}
+	/* check AA path's mute status */
+	for (i = start_idx; i <= end_idx; i++) {
+		int val = mute ? HDA_AMP_MUTE : HDA_AMP_UNMUTE;
+		snd_hda_codec_amp_stereo(codec, nid_mixer, HDA_INPUT, i,
+					 HDA_AMP_MUTE, val);
+	}
+}
+static int is_smart51_pins(struct via_spec *spec, hda_nid_t pin)
+{
+	const struct auto_pin_cfg *cfg = &spec->autocfg;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		if (pin == cfg->inputs[i].pin)
+			return cfg->inputs[i].type <= AUTO_PIN_LINE_IN;
+	}
+	return 0;
+}
+
+static int via_smart51_info(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int via_smart51_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct via_spec *spec = codec->spec;
+	const struct auto_pin_cfg *cfg = &spec->autocfg;
+	int on = 1;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		int ctl = snd_hda_codec_read(codec, nid, 0,
+					     AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+		if (cfg->inputs[i].type > AUTO_PIN_LINE_IN)
+			continue;
+		if (cfg->inputs[i].type == AUTO_PIN_MIC &&
+		    spec->hp_independent_mode && spec->codec_type != VT1718S)
+			continue; /* ignore FMic for independent HP */
+		if ((ctl & AC_PINCTL_IN_EN) && !(ctl & AC_PINCTL_OUT_EN))
+			on = 0;
+	}
+	*ucontrol->value.integer.value = on;
+	return 0;
+}
+
+static int via_smart51_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct via_spec *spec = codec->spec;
+	const struct auto_pin_cfg *cfg = &spec->autocfg;
+	int out_in = *ucontrol->value.integer.value
+		? AC_PINCTL_OUT_EN : AC_PINCTL_IN_EN;
+	int i;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		hda_nid_t nid = cfg->inputs[i].pin;
+		unsigned int parm;
+
+		if (cfg->inputs[i].type > AUTO_PIN_LINE_IN)
+			continue;
+		if (cfg->inputs[i].type == AUTO_PIN_MIC &&
+		    spec->hp_independent_mode && spec->codec_type != VT1718S)
+			continue; /* don't retask FMic for independent HP */
+
+		parm = snd_hda_codec_read(codec, nid, 0,
+					  AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+		parm &= ~(AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN);
+		parm |= out_in;
+		snd_hda_codec_write(codec, nid, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL,
+				    parm);
+		if (out_in == AC_PINCTL_OUT_EN) {
+			mute_aa_path(codec, 1);
+			notify_aa_path_ctls(codec);
+		}
+		if (spec->codec_type == VT1718S) {
+			snd_hda_codec_amp_stereo(
+					codec, nid, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+					HDA_AMP_UNMUTE);
+		}
+		if (cfg->inputs[i].type == AUTO_PIN_MIC) {
+			if (spec->codec_type == VT1708S
+			    || spec->codec_type == VT1716S) {
+				/* input = index 1 (AOW3) */
+				snd_hda_codec_write(
+					codec, nid, 0,
+					AC_VERB_SET_CONNECT_SEL, 1);
+				snd_hda_codec_amp_stereo(
+					codec, nid, HDA_OUTPUT,
+					0, HDA_AMP_MUTE, HDA_AMP_UNMUTE);
+			}
+		}
+	}
+	spec->smart51_enabled = *ucontrol->value.integer.value;
+	set_jack_power_state(codec);
+	return 1;
+}
+
+static struct snd_kcontrol_new via_smart51_mixer[2] = {
+	{
+	 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	 .name = "Smart 5.1",
+	 .count = 1,
+	 .info = via_smart51_info,
+	 .get = via_smart51_get,
+	 .put = via_smart51_put,
+	 },
+	{
+	 .iface = NID_MAPPING,
+	 .name = "Smart 5.1",
+	}
+};
+
+static int via_smart51_build(struct via_spec *spec)
+{
+	struct snd_kcontrol_new *knew;
+	const struct auto_pin_cfg *cfg = &spec->autocfg;
+	hda_nid_t nid;
+	int i;
+
+	knew = via_clone_control(spec, &via_smart51_mixer[0]);
+	if (knew == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		nid = cfg->inputs[i].pin;
+		if (cfg->inputs[i].type <= AUTO_PIN_LINE_IN) {
+			knew = via_clone_control(spec, &via_smart51_mixer[1]);
+			if (knew == NULL)
+				return -ENOMEM;
+			knew->subdevice = nid;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+/* capture mixer elements */
+static struct snd_kcontrol_new vt1708_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x27, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x27, 0x0, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 1,
+		.info = via_mux_enum_info,
+		.get = via_mux_enum_get,
+		.put = via_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+/* check AA path's mute statue */
+static int is_aa_path_mute(struct hda_codec *codec)
+{
+	int mute = 1;
+	hda_nid_t  nid_mixer;
+	int start_idx;
+	int end_idx;
+	int i;
+	struct via_spec *spec = codec->spec;
+	/* get nid of MW0 and start & end index */
+	switch (spec->codec_type) {
+	case VT1708B_8CH:
+	case VT1708B_4CH:
+	case VT1708S:
+	case VT1716S:
+		nid_mixer = 0x16;
+		start_idx = 2;
+		end_idx = 4;
+		break;
+	case VT1702:
+		nid_mixer = 0x1a;
+		start_idx = 1;
+		end_idx = 3;
+		break;
+	case VT1718S:
+		nid_mixer = 0x21;
+		start_idx = 1;
+		end_idx = 3;
+		break;
+	case VT2002P:
+	case VT1812:
+		nid_mixer = 0x21;
+		start_idx = 0;
+		end_idx = 2;
+		break;
+	default:
+		return 0;
+	}
+	/* check AA path's mute status */
+	for (i = start_idx; i <= end_idx; i++) {
+		unsigned int con_list = snd_hda_codec_read(
+			codec, nid_mixer, 0, AC_VERB_GET_CONNECT_LIST, i/4*4);
+		int shift = 8 * (i % 4);
+		hda_nid_t nid_pin = (con_list & (0xff << shift)) >> shift;
+		unsigned int defconf = snd_hda_codec_get_pincfg(codec, nid_pin);
+		if (get_defcfg_connect(defconf) == AC_JACK_PORT_COMPLEX) {
+			/* check mute status while the pin is connected */
+			int mute_l = snd_hda_codec_amp_read(codec, nid_mixer, 0,
+							    HDA_INPUT, i) >> 7;
+			int mute_r = snd_hda_codec_amp_read(codec, nid_mixer, 1,
+							    HDA_INPUT, i) >> 7;
+			if (!mute_l || !mute_r) {
+				mute = 0;
+				break;
+			}
+		}
+	}
+	return mute;
+}
+
+/* enter/exit analog low-current mode */
+static void analog_low_current_mode(struct hda_codec *codec, int stream_idle)
+{
+	struct via_spec *spec = codec->spec;
+	static int saved_stream_idle = 1; /* saved stream idle status */
+	int enable = is_aa_path_mute(codec);
+	unsigned int verb = 0;
+	unsigned int parm = 0;
+
+	if (stream_idle == -1)	/* stream status did not change */
+		enable = enable && saved_stream_idle;
+	else {
+		enable = enable && stream_idle;
+		saved_stream_idle = stream_idle;
+	}
+
+	/* decide low current mode's verb & parameter */
+	switch (spec->codec_type) {
+	case VT1708B_8CH:
+	case VT1708B_4CH:
+		verb = 0xf70;
+		parm = enable ? 0x02 : 0x00; /* 0x02: 2/3x, 0x00: 1x */
+		break;
+	case VT1708S:
+	case VT1718S:
+	case VT1716S:
+		verb = 0xf73;
+		parm = enable ? 0x51 : 0xe1; /* 0x51: 4/28x, 0xe1: 1x */
+		break;
+	case VT1702:
+		verb = 0xf73;
+		parm = enable ? 0x01 : 0x1d; /* 0x01: 4/40x, 0x1d: 1x */
+		break;
+	case VT2002P:
+	case VT1812:
+		verb = 0xf93;
+		parm = enable ? 0x00 : 0xe0; /* 0x00: 4/40x, 0xe0: 1x */
+		break;
+	default:
+		return;		/* other codecs are not supported */
+	}
+	/* send verb */
+	snd_hda_codec_write(codec, codec->afg, 0, verb, parm);
+}
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb vt1708_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-1 and set the default input to mic-in
+	 */
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+
+	/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 */
+	/* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)},
+
+	/*
+	 * Set up output mixers (0x19 - 0x1b)
+	 */
+	/* set vol=0 to output mixers */
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* Setup default input MW0 to PW4 */
+	{0x20, AC_VERB_SET_CONNECT_SEL, 0},
+	/* PW9 Output enable */
+	{0x25, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	{ }
+};
+
+static int via_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				 struct hda_codec *codec,
+				 struct snd_pcm_substream *substream)
+{
+	struct via_spec *spec = codec->spec;
+	int idle = substream->pstr->substream_opened == 1
+		&& substream->ref_count == 0;
+	analog_low_current_mode(codec, idle);
+	return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
+					     hinfo);
+}
+
+static void playback_multi_pcm_prep_0(struct hda_codec *codec,
+				      unsigned int stream_tag,
+				      unsigned int format,
+				      struct snd_pcm_substream *substream)
+{
+	struct via_spec *spec = codec->spec;
+	struct hda_multi_out *mout = &spec->multiout;
+	hda_nid_t *nids = mout->dac_nids;
+	int chs = substream->runtime->channels;
+	int i;
+
+	mutex_lock(&codec->spdif_mutex);
+	if (mout->dig_out_nid && mout->dig_out_used != HDA_DIG_EXCLUSIVE) {
+		if (chs == 2 &&
+		    snd_hda_is_supported_format(codec, mout->dig_out_nid,
+						format) &&
+		    !(codec->spdif_status & IEC958_AES0_NONAUDIO)) {
+			mout->dig_out_used = HDA_DIG_ANALOG_DUP;
+			/* turn off SPDIF once; otherwise the IEC958 bits won't
+			 * be updated */
+			if (codec->spdif_ctls & AC_DIG1_ENABLE)
+				snd_hda_codec_write(codec, mout->dig_out_nid, 0,
+						    AC_VERB_SET_DIGI_CONVERT_1,
+						    codec->spdif_ctls &
+							~AC_DIG1_ENABLE & 0xff);
+			snd_hda_codec_setup_stream(codec, mout->dig_out_nid,
+						   stream_tag, 0, format);
+			/* turn on again (if needed) */
+			if (codec->spdif_ctls & AC_DIG1_ENABLE)
+				snd_hda_codec_write(codec, mout->dig_out_nid, 0,
+						    AC_VERB_SET_DIGI_CONVERT_1,
+						    codec->spdif_ctls & 0xff);
+		} else {
+			mout->dig_out_used = 0;
+			snd_hda_codec_setup_stream(codec, mout->dig_out_nid,
+						   0, 0, 0);
+		}
+	}
+	mutex_unlock(&codec->spdif_mutex);
+
+	/* front */
+	snd_hda_codec_setup_stream(codec, nids[HDA_FRONT], stream_tag,
+				   0, format);
+
+	if (mout->hp_nid && mout->hp_nid != nids[HDA_FRONT]
+	    && !spec->hp_independent_mode)
+		/* headphone out will just decode front left/right (stereo) */
+		snd_hda_codec_setup_stream(codec, mout->hp_nid, stream_tag,
+					   0, format);
+
+	/* extra outputs copied from front */
+	for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++)
+		if (mout->extra_out_nid[i])
+			snd_hda_codec_setup_stream(codec,
+						   mout->extra_out_nid[i],
+						   stream_tag, 0, format);
+
+	/* surrounds */
+	for (i = 1; i < mout->num_dacs; i++) {
+		if (chs >= (i + 1) * 2) /* independent out */
+			snd_hda_codec_setup_stream(codec, nids[i], stream_tag,
+						   i * 2, format);
+		else /* copy front */
+			snd_hda_codec_setup_stream(codec, nids[i], stream_tag,
+						   0, format);
+	}
+}
+
+static int via_playback_multi_pcm_prepare(struct hda_pcm_stream *hinfo,
+					  struct hda_codec *codec,
+					  unsigned int stream_tag,
+					  unsigned int format,
+					  struct snd_pcm_substream *substream)
+{
+	struct via_spec *spec = codec->spec;
+	struct hda_multi_out *mout = &spec->multiout;
+	hda_nid_t *nids = mout->dac_nids;
+
+	if (substream->number == 0)
+		playback_multi_pcm_prep_0(codec, stream_tag, format,
+					  substream);
+	else {
+		if (mout->hp_nid && mout->hp_nid != nids[HDA_FRONT] &&
+		    spec->hp_independent_mode)
+			snd_hda_codec_setup_stream(codec, mout->hp_nid,
+						   stream_tag, 0, format);
+	}
+	vt1708_start_hp_work(spec);
+	return 0;
+}
+
+static int via_playback_multi_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				    struct hda_codec *codec,
+				    struct snd_pcm_substream *substream)
+{
+	struct via_spec *spec = codec->spec;
+	struct hda_multi_out *mout = &spec->multiout;
+	hda_nid_t *nids = mout->dac_nids;
+	int i;
+
+	if (substream->number == 0) {
+		for (i = 0; i < mout->num_dacs; i++)
+			snd_hda_codec_setup_stream(codec, nids[i], 0, 0, 0);
+
+		if (mout->hp_nid && !spec->hp_independent_mode)
+			snd_hda_codec_setup_stream(codec, mout->hp_nid,
+						   0, 0, 0);
+
+		for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++)
+			if (mout->extra_out_nid[i])
+				snd_hda_codec_setup_stream(codec,
+							mout->extra_out_nid[i],
+							0, 0, 0);
+		mutex_lock(&codec->spdif_mutex);
+		if (mout->dig_out_nid &&
+		    mout->dig_out_used == HDA_DIG_ANALOG_DUP) {
+			snd_hda_codec_setup_stream(codec, mout->dig_out_nid,
+						   0, 0, 0);
+			mout->dig_out_used = 0;
+		}
+		mutex_unlock(&codec->spdif_mutex);
+	} else {
+		if (mout->hp_nid && mout->hp_nid != nids[HDA_FRONT] &&
+		    spec->hp_independent_mode)
+			snd_hda_codec_setup_stream(codec, mout->hp_nid,
+						   0, 0, 0);
+	}
+	vt1708_stop_hp_work(spec);
+	return 0;
+}
+
+/*
+ * Digital out
+ */
+static int via_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				     struct hda_codec *codec,
+				     struct snd_pcm_substream *substream)
+{
+	struct via_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int via_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      struct snd_pcm_substream *substream)
+{
+	struct via_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static int via_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					struct hda_codec *codec,
+					unsigned int stream_tag,
+					unsigned int format,
+					struct snd_pcm_substream *substream)
+{
+	struct via_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
+					     stream_tag, format, substream);
+}
+
+static int via_dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+					struct hda_codec *codec,
+					struct snd_pcm_substream *substream)
+{
+	struct via_spec *spec = codec->spec;
+	snd_hda_multi_out_dig_cleanup(codec, &spec->multiout);
+	return 0;
+}
+
+/*
+ * Analog capture
+ */
+static int via_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+				   struct hda_codec *codec,
+				   unsigned int stream_tag,
+				   unsigned int format,
+				   struct snd_pcm_substream *substream)
+{
+	struct via_spec *spec = codec->spec;
+
+	snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
+				   stream_tag, 0, format);
+	return 0;
+}
+
+static int via_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+				   struct hda_codec *codec,
+				   struct snd_pcm_substream *substream)
+{
+	struct via_spec *spec = codec->spec;
+	snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]);
+	return 0;
+}
+
+static struct hda_pcm_stream vt1708_pcm_analog_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 8,
+	.nid = 0x10, /* NID to query formats and rates */
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream vt1708_pcm_analog_s16_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 8,
+	.nid = 0x10, /* NID to query formats and rates */
+	/* We got noisy outputs on the right channel on VT1708 when
+	 * 24bit samples are used.  Until any workaround is found,
+	 * disable the 24bit format, so far.
+	 */
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream vt1708_pcm_analog_capture = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x15, /* NID to query formats and rates */
+	.ops = {
+		.prepare = via_capture_pcm_prepare,
+		.cleanup = via_capture_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream vt1708_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in via_build_pcms */
+	.ops = {
+		.open = via_dig_playback_pcm_open,
+		.close = via_dig_playback_pcm_close,
+		.prepare = via_dig_playback_pcm_prepare,
+		.cleanup = via_dig_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream vt1708_pcm_digital_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+};
+
+static int via_build_controls(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_new *knew;
+	int err, i;
+
+	for (i = 0; i < spec->num_mixers; i++) {
+		err = snd_hda_add_new_ctls(codec, spec->mixers[i]);
+		if (err < 0)
+			return err;
+	}
+
+	if (spec->multiout.dig_out_nid) {
+		err = snd_hda_create_spdif_out_ctls(codec,
+						    spec->multiout.dig_out_nid);
+		if (err < 0)
+			return err;
+		err = snd_hda_create_spdif_share_sw(codec,
+						    &spec->multiout);
+		if (err < 0)
+			return err;
+		spec->multiout.share_spdif = 1;
+	}
+	if (spec->dig_in_nid) {
+		err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
+		if (err < 0)
+			return err;
+	}
+
+	/* assign Capture Source enums to NID */
+	kctl = snd_hda_find_mixer_ctl(codec, "Input Source");
+	for (i = 0; kctl && i < kctl->count; i++) {
+		err = snd_hda_add_nid(codec, kctl, i, spec->mux_nids[i]);
+		if (err < 0)
+			return err;
+	}
+
+	/* other nid->control mapping */
+	for (i = 0; i < spec->num_mixers; i++) {
+		for (knew = spec->mixers[i]; knew->name; knew++) {
+			if (knew->iface != NID_MAPPING)
+				continue;
+			kctl = snd_hda_find_mixer_ctl(codec, knew->name);
+			if (kctl == NULL)
+				continue;
+			err = snd_hda_add_nid(codec, kctl, 0,
+					      knew->subdevice);
+		}
+	}
+
+	/* init power states */
+	set_jack_power_state(codec);
+	analog_low_current_mode(codec, 1);
+
+	via_free_kctls(codec); /* no longer needed */
+	return 0;
+}
+
+static int via_build_pcms(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	struct hda_pcm *info = spec->pcm_rec;
+
+	codec->num_pcms = 1;
+	codec->pcm_info = info;
+
+	info->name = spec->stream_name_analog;
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
+		*(spec->stream_analog_playback);
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
+		spec->multiout.dac_nids[0];
+	info->stream[SNDRV_PCM_STREAM_CAPTURE] = *(spec->stream_analog_capture);
+	info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
+
+	info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
+		spec->multiout.max_channels;
+
+	if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
+		codec->num_pcms++;
+		info++;
+		info->name = spec->stream_name_digital;
+		info->pcm_type = HDA_PCM_TYPE_SPDIF;
+		if (spec->multiout.dig_out_nid) {
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
+				*(spec->stream_digital_playback);
+			info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid =
+				spec->multiout.dig_out_nid;
+		}
+		if (spec->dig_in_nid) {
+			info->stream[SNDRV_PCM_STREAM_CAPTURE] =
+				*(spec->stream_digital_capture);
+			info->stream[SNDRV_PCM_STREAM_CAPTURE].nid =
+				spec->dig_in_nid;
+		}
+	}
+
+	return 0;
+}
+
+static void via_free(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+
+	if (!spec)
+		return;
+
+	via_free_kctls(codec);
+	vt1708_stop_hp_work(spec);
+	kfree(codec->spec);
+}
+
+/* mute internal speaker if HP is plugged */
+static void via_hp_automute(struct hda_codec *codec)
+{
+	unsigned int present = 0;
+	struct via_spec *spec = codec->spec;
+
+	present = snd_hda_jack_detect(codec, spec->autocfg.hp_pins[0]);
+
+	if (!spec->hp_independent_mode) {
+		struct snd_ctl_elem_id id;
+		/* auto mute */
+		snd_hda_codec_amp_stereo(
+			codec, spec->autocfg.line_out_pins[0], HDA_OUTPUT, 0,
+			HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+		/* notify change */
+		memset(&id, 0, sizeof(id));
+		id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+		strcpy(id.name, "Front Playback Switch");
+		snd_ctl_notify(codec->bus->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &id);
+	}
+}
+
+/* mute mono out if HP or Line out is plugged */
+static void via_mono_automute(struct hda_codec *codec)
+{
+	unsigned int hp_present, lineout_present;
+	struct via_spec *spec = codec->spec;
+
+	if (spec->codec_type != VT1716S)
+		return;
+
+	lineout_present = snd_hda_jack_detect(codec,
+					      spec->autocfg.line_out_pins[0]);
+
+	/* Mute Mono Out if Line Out is plugged */
+	if (lineout_present) {
+		snd_hda_codec_amp_stereo(
+			codec, 0x2A, HDA_OUTPUT, 0, HDA_AMP_MUTE, HDA_AMP_MUTE);
+		return;
+	}
+
+	hp_present = snd_hda_jack_detect(codec, spec->autocfg.hp_pins[0]);
+
+	if (!spec->hp_independent_mode)
+		snd_hda_codec_amp_stereo(
+			codec, 0x2A, HDA_OUTPUT, 0, HDA_AMP_MUTE,
+			hp_present ? HDA_AMP_MUTE : 0);
+}
+
+static void via_gpio_control(struct hda_codec *codec)
+{
+	unsigned int gpio_data;
+	unsigned int vol_counter;
+	unsigned int vol;
+	unsigned int master_vol;
+
+	struct via_spec *spec = codec->spec;
+
+	gpio_data = snd_hda_codec_read(codec, codec->afg, 0,
+				       AC_VERB_GET_GPIO_DATA, 0) & 0x03;
+
+	vol_counter = (snd_hda_codec_read(codec, codec->afg, 0,
+					  0xF84, 0) & 0x3F0000) >> 16;
+
+	vol = vol_counter & 0x1F;
+	master_vol = snd_hda_codec_read(codec, 0x1A, 0,
+					AC_VERB_GET_AMP_GAIN_MUTE,
+					AC_AMP_GET_INPUT);
+
+	if (gpio_data == 0x02) {
+		/* unmute line out */
+		snd_hda_codec_amp_stereo(codec, spec->autocfg.line_out_pins[0],
+					 HDA_OUTPUT, 0, HDA_AMP_MUTE, 0);
+
+		if (vol_counter & 0x20) {
+			/* decrease volume */
+			if (vol > master_vol)
+				vol = master_vol;
+			snd_hda_codec_amp_stereo(codec, 0x1A, HDA_INPUT,
+						 0, HDA_AMP_VOLMASK,
+						 master_vol-vol);
+		} else {
+			/* increase volume */
+			snd_hda_codec_amp_stereo(codec, 0x1A, HDA_INPUT, 0,
+					 HDA_AMP_VOLMASK,
+					 ((master_vol+vol) > 0x2A) ? 0x2A :
+					  (master_vol+vol));
+		}
+	} else if (!(gpio_data & 0x02)) {
+		/* mute line out */
+		snd_hda_codec_amp_stereo(codec,
+					 spec->autocfg.line_out_pins[0],
+					 HDA_OUTPUT, 0, HDA_AMP_MUTE,
+					 HDA_AMP_MUTE);
+	}
+}
+
+/* mute Internal-Speaker if HP is plugged */
+static void via_speaker_automute(struct hda_codec *codec)
+{
+	unsigned int hp_present;
+	struct via_spec *spec = codec->spec;
+
+	if (spec->codec_type != VT2002P && spec->codec_type != VT1812)
+		return;
+
+	hp_present = snd_hda_jack_detect(codec, spec->autocfg.hp_pins[0]);
+
+	if (!spec->hp_independent_mode) {
+		struct snd_ctl_elem_id id;
+		snd_hda_codec_amp_stereo(
+			codec, spec->autocfg.speaker_pins[0], HDA_OUTPUT, 0,
+			HDA_AMP_MUTE, hp_present ? HDA_AMP_MUTE : 0);
+		/* notify change */
+		memset(&id, 0, sizeof(id));
+		id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+		strcpy(id.name, "Speaker Playback Switch");
+		snd_ctl_notify(codec->bus->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &id);
+	}
+}
+
+/* mute line-out and internal speaker if HP is plugged */
+static void via_hp_bind_automute(struct hda_codec *codec)
+{
+	/* use long instead of int below just to avoid an internal compiler
+	 * error with gcc 4.0.x
+	 */
+	unsigned long hp_present, present = 0;
+	struct via_spec *spec = codec->spec;
+	int i;
+
+	if (!spec->autocfg.hp_pins[0] || !spec->autocfg.line_out_pins[0])
+		return;
+
+	hp_present = snd_hda_jack_detect(codec, spec->autocfg.hp_pins[0]);
+
+	present = snd_hda_jack_detect(codec, spec->autocfg.line_out_pins[0]);
+
+	if (!spec->hp_independent_mode) {
+		/* Mute Line-Outs */
+		for (i = 0; i < spec->autocfg.line_outs; i++)
+			snd_hda_codec_amp_stereo(
+				codec, spec->autocfg.line_out_pins[i],
+				HDA_OUTPUT, 0,
+				HDA_AMP_MUTE, hp_present ? HDA_AMP_MUTE : 0);
+		if (hp_present)
+			present = hp_present;
+	}
+	/* Speakers */
+	for (i = 0; i < spec->autocfg.speaker_outs; i++)
+		snd_hda_codec_amp_stereo(
+			codec, spec->autocfg.speaker_pins[i], HDA_OUTPUT, 0,
+			HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0);
+}
+
+
+/* unsolicited event for jack sensing */
+static void via_unsol_event(struct hda_codec *codec,
+				  unsigned int res)
+{
+	res >>= 26;
+	if (res & VIA_HP_EVENT)
+		via_hp_automute(codec);
+	if (res & VIA_GPIO_EVENT)
+		via_gpio_control(codec);
+	if (res & VIA_JACK_EVENT)
+		set_jack_power_state(codec);
+	if (res & VIA_MONO_EVENT)
+		via_mono_automute(codec);
+	if (res & VIA_SPEAKER_EVENT)
+		via_speaker_automute(codec);
+	if (res & VIA_BIND_HP_EVENT)
+		via_hp_bind_automute(codec);
+}
+
+static int via_init(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int i;
+	for (i = 0; i < spec->num_iverbs; i++)
+		snd_hda_sequence_write(codec, spec->init_verbs[i]);
+
+	spec->codec_type = get_codec_type(codec);
+	if (spec->codec_type == VT1708BCE)
+		spec->codec_type = VT1708S; /* VT1708BCE & VT1708S are almost
+					       same */
+	/* Lydia Add for EAPD enable */
+	if (!spec->dig_in_nid) { /* No Digital In connection */
+		if (spec->dig_in_pin) {
+			snd_hda_codec_write(codec, spec->dig_in_pin, 0,
+					    AC_VERB_SET_PIN_WIDGET_CONTROL,
+					    PIN_OUT);
+			snd_hda_codec_write(codec, spec->dig_in_pin, 0,
+					    AC_VERB_SET_EAPD_BTLENABLE, 0x02);
+		}
+	} else /* enable SPDIF-input pin */
+		snd_hda_codec_write(codec, spec->autocfg.dig_in_pin, 0,
+				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN);
+
+	/* assign slave outs */
+	if (spec->slave_dig_outs[0])
+		codec->slave_dig_outs = spec->slave_dig_outs;
+
+	return 0;
+}
+
+#ifdef SND_HDA_NEEDS_RESUME
+static int via_suspend(struct hda_codec *codec, pm_message_t state)
+{
+	struct via_spec *spec = codec->spec;
+	vt1708_stop_hp_work(spec);
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static int via_check_power_status(struct hda_codec *codec, hda_nid_t nid)
+{
+	struct via_spec *spec = codec->spec;
+	return snd_hda_check_amp_list_power(codec, &spec->loopback, nid);
+}
+#endif
+
+/*
+ */
+static struct hda_codec_ops via_patch_ops = {
+	.build_controls = via_build_controls,
+	.build_pcms = via_build_pcms,
+	.init = via_init,
+	.free = via_free,
+#ifdef SND_HDA_NEEDS_RESUME
+	.suspend = via_suspend,
+#endif
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	.check_power_status = via_check_power_status,
+#endif
+};
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int vt1708_auto_fill_dac_nids(struct via_spec *spec,
+				     const struct auto_pin_cfg *cfg)
+{
+	int i;
+	hda_nid_t nid;
+
+	spec->multiout.num_dacs = cfg->line_outs;
+
+	spec->multiout.dac_nids = spec->private_dac_nids;
+
+	for (i = 0; i < 4; i++) {
+		nid = cfg->line_out_pins[i];
+		if (nid) {
+			/* config dac list */
+			switch (i) {
+			case AUTO_SEQ_FRONT:
+				spec->multiout.dac_nids[i] = 0x10;
+				break;
+			case AUTO_SEQ_CENLFE:
+				spec->multiout.dac_nids[i] = 0x12;
+				break;
+			case AUTO_SEQ_SURROUND:
+				spec->multiout.dac_nids[i] = 0x11;
+				break;
+			case AUTO_SEQ_SIDE:
+				spec->multiout.dac_nids[i] = 0x13;
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int vt1708_auto_create_multi_out_ctls(struct via_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	char name[32];
+	static const char *chname[4] = { "Front", "Surround", "C/LFE", "Side" };
+	hda_nid_t nid, nid_vol, nid_vols[] = {0x17, 0x19, 0x1a, 0x1b};
+	int i, err;
+
+	for (i = 0; i <= AUTO_SEQ_SIDE; i++) {
+		nid = cfg->line_out_pins[i];
+
+		if (!nid)
+			continue;
+
+		nid_vol = nid_vols[i];
+
+		if (i == AUTO_SEQ_CENLFE) {
+			/* Center/LFE */
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					"Center Playback Volume",
+					HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0,
+							    HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "LFE Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "Center Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "LFE Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else if (i == AUTO_SEQ_FRONT) {
+			/* add control to mixer index 0 */
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "Master Front Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_INPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "Master Front Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_INPUT));
+			if (err < 0)
+				return err;
+
+			/* add control to PW3 */
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
+					      HDA_COMPOSE_AMP_VAL(nid, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name,
+					      HDA_COMPOSE_AMP_VAL(nid, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else {
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name,
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+static void create_hp_imux(struct via_spec *spec)
+{
+	int i;
+	struct hda_input_mux *imux = &spec->private_imux[1];
+	static const char *texts[] = { "OFF", "ON", NULL};
+
+	/* for hp mode select */
+	for (i = 0; texts[i]; i++)
+		snd_hda_add_imux_item(imux, texts[i], i, NULL);
+
+	spec->hp_mux = &spec->private_imux[1];
+}
+
+static int vt1708_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin)
+{
+	int err;
+
+	if (!pin)
+		return 0;
+
+	spec->multiout.hp_nid = VT1708_HP_NID; /* AOW3 */
+	spec->hp_independent_mode_index = 1;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Headphone Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+	err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+			      "Headphone Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	create_hp_imux(spec);
+
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int vt_auto_create_analog_input_ctls(struct hda_codec *codec,
+					    const struct auto_pin_cfg *cfg,
+					    hda_nid_t cap_nid,
+					    hda_nid_t pin_idxs[], int num_idxs)
+{
+	struct via_spec *spec = codec->spec;
+	struct hda_input_mux *imux = &spec->private_imux[0];
+	int i, err, idx, type, type_idx = 0;
+
+	/* for internal loopback recording select */
+	for (idx = 0; idx < num_idxs; idx++) {
+		if (pin_idxs[idx] == 0xff) {
+			snd_hda_add_imux_item(imux, "Stereo Mixer", idx, NULL);
+			break;
+		}
+	}
+
+	for (i = 0; i < cfg->num_inputs; i++) {
+		const char *label;
+		type = cfg->inputs[i].type;
+		for (idx = 0; idx < num_idxs; idx++)
+			if (pin_idxs[idx] == cfg->inputs[i].pin)
+				break;
+		if (idx >= num_idxs)
+			continue;
+		if (i > 0 && type == cfg->inputs[i - 1].type)
+			type_idx++;
+		else
+			type_idx = 0;
+		label = hda_get_autocfg_input_label(codec, cfg, i);
+		err = via_new_analog_input(spec, label, type_idx, idx, cap_nid);
+		if (err < 0)
+			return err;
+		snd_hda_add_imux_item(imux, label, idx, NULL);
+	}
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int vt1708_auto_create_analog_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	static hda_nid_t pin_idxs[] = { 0xff, 0x24, 0x1d, 0x1e, 0x21 };
+	return vt_auto_create_analog_input_ctls(codec, cfg, 0x17, pin_idxs,
+						ARRAY_SIZE(pin_idxs));
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list vt1708_loopbacks[] = {
+	{ 0x17, HDA_INPUT, 1 },
+	{ 0x17, HDA_INPUT, 2 },
+	{ 0x17, HDA_INPUT, 3 },
+	{ 0x17, HDA_INPUT, 4 },
+	{ } /* end */
+};
+#endif
+
+static void vt1708_set_pinconfig_connect(struct hda_codec *codec, hda_nid_t nid)
+{
+	unsigned int def_conf;
+	unsigned char seqassoc;
+
+	def_conf = snd_hda_codec_get_pincfg(codec, nid);
+	seqassoc = (unsigned char) get_defcfg_association(def_conf);
+	seqassoc = (seqassoc << 4) | get_defcfg_sequence(def_conf);
+	if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE
+	    && (seqassoc == 0xf0 || seqassoc == 0xff)) {
+		def_conf = def_conf & (~(AC_JACK_PORT_BOTH << 30));
+		snd_hda_codec_set_pincfg(codec, nid, def_conf);
+	}
+
+	return;
+}
+
+static int vt1708_jack_detectect_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct via_spec *spec = codec->spec;
+
+	if (spec->codec_type != VT1708)
+		return 0;
+	spec->vt1708_jack_detectect =
+		!((snd_hda_codec_read(codec, 0x1, 0, 0xf84, 0) >> 8) & 0x1);
+	ucontrol->value.integer.value[0] = spec->vt1708_jack_detectect;
+	return 0;
+}
+
+static int vt1708_jack_detectect_put(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct via_spec *spec = codec->spec;
+	int change;
+
+	if (spec->codec_type != VT1708)
+		return 0;
+	spec->vt1708_jack_detectect = ucontrol->value.integer.value[0];
+	change = (0x1 & (snd_hda_codec_read(codec, 0x1, 0, 0xf84, 0) >> 8))
+		== !spec->vt1708_jack_detectect;
+	if (spec->vt1708_jack_detectect) {
+		mute_aa_path(codec, 1);
+		notify_aa_path_ctls(codec);
+	}
+	return change;
+}
+
+static struct snd_kcontrol_new vt1708_jack_detectect[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Jack Detect",
+		.count = 1,
+		.info = snd_ctl_boolean_mono_info,
+		.get = vt1708_jack_detectect_get,
+		.put = vt1708_jack_detectect_put,
+	},
+	{} /* end */
+};
+
+static int vt1708_parse_auto_config(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int err;
+
+	/* Add HP and CD pin config connect bit re-config action */
+	vt1708_set_pinconfig_connect(codec, VT1708_HP_PIN_NID);
+	vt1708_set_pinconfig_connect(codec, VT1708_CD_PIN_NID);
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+	if (err < 0)
+		return err;
+	err = vt1708_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0])
+		return 0; /* can't find valid BIOS pin config */
+
+	err = vt1708_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = vt1708_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]);
+	if (err < 0)
+		return err;
+	err = vt1708_auto_create_analog_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	/* add jack detect on/off control */
+	err = snd_hda_add_new_ctls(codec, vt1708_jack_detectect);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	if (spec->autocfg.dig_outs)
+		spec->multiout.dig_out_nid = VT1708_DIGOUT_NID;
+	spec->dig_in_pin = VT1708_DIGIN_PIN;
+	if (spec->autocfg.dig_in_pin)
+		spec->dig_in_nid = VT1708_DIGIN_NID;
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->init_verbs[spec->num_iverbs++] = vt1708_volume_init_verbs;
+
+	spec->input_mux = &spec->private_imux[0];
+
+	if (spec->hp_mux)
+		via_hp_build(codec);
+
+	via_smart51_build(spec);
+	return 1;
+}
+
+/* init callback for auto-configuration model -- overriding the default init */
+static int via_auto_init(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+
+	via_init(codec);
+	via_auto_init_multi_out(codec);
+	via_auto_init_hp_out(codec);
+	via_auto_init_analog_input(codec);
+	if (spec->codec_type == VT2002P || spec->codec_type == VT1812) {
+		via_hp_bind_automute(codec);
+	} else {
+		via_hp_automute(codec);
+		via_speaker_automute(codec);
+	}
+
+	return 0;
+}
+
+static void vt1708_update_hp_jack_state(struct work_struct *work)
+{
+	struct via_spec *spec = container_of(work, struct via_spec,
+					     vt1708_hp_work.work);
+	if (spec->codec_type != VT1708)
+		return;
+	/* if jack state toggled */
+	if (spec->vt1708_hp_present
+	    != snd_hda_jack_detect(spec->codec, spec->autocfg.hp_pins[0])) {
+		spec->vt1708_hp_present ^= 1;
+		via_hp_automute(spec->codec);
+	}
+	vt1708_start_hp_work(spec);
+}
+
+static int get_mux_nids(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	hda_nid_t nid, conn[8];
+	unsigned int type;
+	int i, n;
+
+	for (i = 0; i < spec->num_adc_nids; i++) {
+		nid = spec->adc_nids[i];
+		while (nid) {
+			type = get_wcaps_type(get_wcaps(codec, nid));
+			if (type == AC_WID_PIN)
+				break;
+			n = snd_hda_get_connections(codec, nid, conn,
+						    ARRAY_SIZE(conn));
+			if (n <= 0)
+				break;
+			if (n > 1) {
+				spec->mux_nids[i] = nid;
+				break;
+			}
+			nid = conn[0];
+		}
+	}
+	return 0;
+}
+
+static int patch_vt1708(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+	int err;
+
+	/* create a codec specific record */
+	spec = via_new_spec(codec);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	/* automatic parse from the BIOS config */
+	err = vt1708_parse_auto_config(codec);
+	if (err < 0) {
+		via_free(codec);
+		return err;
+	} else if (!err) {
+		printk(KERN_INFO "hda_codec: Cannot set up configuration "
+		       "from BIOS.  Using genenic mode...\n");
+	}
+
+
+	spec->stream_name_analog = "VT1708 Analog";
+	spec->stream_analog_playback = &vt1708_pcm_analog_playback;
+	/* disable 32bit format on VT1708 */
+	if (codec->vendor_id == 0x11061708)
+		spec->stream_analog_playback = &vt1708_pcm_analog_s16_playback;
+	spec->stream_analog_capture = &vt1708_pcm_analog_capture;
+
+	spec->stream_name_digital = "VT1708 Digital";
+	spec->stream_digital_playback = &vt1708_pcm_digital_playback;
+	spec->stream_digital_capture = &vt1708_pcm_digital_capture;
+
+
+	if (!spec->adc_nids && spec->input_mux) {
+		spec->adc_nids = vt1708_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(vt1708_adc_nids);
+		get_mux_nids(codec);
+		spec->mixers[spec->num_mixers] = vt1708_capture_mixer;
+		spec->num_mixers++;
+	}
+
+	codec->patch_ops = via_patch_ops;
+
+	codec->patch_ops.init = via_auto_init;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = vt1708_loopbacks;
+#endif
+	INIT_DELAYED_WORK(&spec->vt1708_hp_work, vt1708_update_hp_jack_state);
+	return 0;
+}
+
+/* capture mixer elements */
+static struct snd_kcontrol_new vt1709_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x15, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 2, 0x16, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 2, 0x16, 0x0, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 1,
+		.info = via_mux_enum_info,
+		.get = via_mux_enum_get,
+		.put = via_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb vt1709_uniwill_init_verbs[] = {
+	{0x20, AC_VERB_SET_UNSOLICITED_ENABLE,
+	 AC_USRSP_EN | VIA_HP_EVENT | VIA_JACK_EVENT},
+	{ }
+};
+
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb vt1709_10ch_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-2 and set the default input to mic-in
+	 */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+
+	/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 */
+	/* Amp Indices: AOW0=0, CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)},
+
+	/*
+	 * Set up output selector (0x1a, 0x1b, 0x29)
+	 */
+	/* set vol=0 to output mixers */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/*
+	 *  Unmute PW3 and PW4
+	 */
+	{0x1f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* Set input of PW4 as MW0 */
+	{0x20, AC_VERB_SET_CONNECT_SEL, 0},
+	/* PW9 Output enable */
+	{0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	{ }
+};
+
+static struct hda_pcm_stream vt1709_10ch_pcm_analog_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 10,
+	.nid = 0x10, /* NID to query formats and rates */
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup,
+	},
+};
+
+static struct hda_pcm_stream vt1709_6ch_pcm_analog_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 6,
+	.nid = 0x10, /* NID to query formats and rates */
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup,
+	},
+};
+
+static struct hda_pcm_stream vt1709_pcm_analog_capture = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x14, /* NID to query formats and rates */
+	.ops = {
+		.prepare = via_capture_pcm_prepare,
+		.cleanup = via_capture_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream vt1709_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in via_build_pcms */
+	.ops = {
+		.open = via_dig_playback_pcm_open,
+		.close = via_dig_playback_pcm_close
+	},
+};
+
+static struct hda_pcm_stream vt1709_pcm_digital_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+};
+
+static int vt1709_auto_fill_dac_nids(struct via_spec *spec,
+				     const struct auto_pin_cfg *cfg)
+{
+	int i;
+	hda_nid_t nid;
+
+	if (cfg->line_outs == 4)  /* 10 channels */
+		spec->multiout.num_dacs = cfg->line_outs+1; /* AOW0~AOW4 */
+	else if (cfg->line_outs == 3) /* 6 channels */
+		spec->multiout.num_dacs = cfg->line_outs; /* AOW0~AOW2 */
+
+	spec->multiout.dac_nids = spec->private_dac_nids;
+
+	if (cfg->line_outs == 4) { /* 10 channels */
+		for (i = 0; i < cfg->line_outs; i++) {
+			nid = cfg->line_out_pins[i];
+			if (nid) {
+				/* config dac list */
+				switch (i) {
+				case AUTO_SEQ_FRONT:
+					/* AOW0 */
+					spec->multiout.dac_nids[i] = 0x10;
+					break;
+				case AUTO_SEQ_CENLFE:
+					/* AOW2 */
+					spec->multiout.dac_nids[i] = 0x12;
+					break;
+				case AUTO_SEQ_SURROUND:
+					/* AOW3 */
+					spec->multiout.dac_nids[i] = 0x11;
+					break;
+				case AUTO_SEQ_SIDE:
+					/* AOW1 */
+					spec->multiout.dac_nids[i] = 0x27;
+					break;
+				default:
+					break;
+				}
+			}
+		}
+		spec->multiout.dac_nids[cfg->line_outs] = 0x28; /* AOW4 */
+
+	} else if (cfg->line_outs == 3) { /* 6 channels */
+		for (i = 0; i < cfg->line_outs; i++) {
+			nid = cfg->line_out_pins[i];
+			if (nid) {
+				/* config dac list */
+				switch (i) {
+				case AUTO_SEQ_FRONT:
+					/* AOW0 */
+					spec->multiout.dac_nids[i] = 0x10;
+					break;
+				case AUTO_SEQ_CENLFE:
+					/* AOW2 */
+					spec->multiout.dac_nids[i] = 0x12;
+					break;
+				case AUTO_SEQ_SURROUND:
+					/* AOW1 */
+					spec->multiout.dac_nids[i] = 0x11;
+					break;
+				default:
+					break;
+				}
+			}
+		}
+	}
+
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int vt1709_auto_create_multi_out_ctls(struct via_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	char name[32];
+	static const char *chname[4] = { "Front", "Surround", "C/LFE", "Side" };
+	hda_nid_t nid, nid_vol, nid_vols[] = {0x18, 0x1a, 0x1b, 0x29};
+	int i, err;
+
+	for (i = 0; i <= AUTO_SEQ_SIDE; i++) {
+		nid = cfg->line_out_pins[i];
+
+		if (!nid)
+			continue;
+
+		nid_vol = nid_vols[i];
+
+		if (i == AUTO_SEQ_CENLFE) {
+			/* Center/LFE */
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "Center Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "LFE Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "Center Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "LFE Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else if (i == AUTO_SEQ_FRONT) {
+			/* ADD control to mixer index 0 */
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "Master Front Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_INPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "Master Front Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_INPUT));
+			if (err < 0)
+				return err;
+
+			/* add control to PW3 */
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
+					      HDA_COMPOSE_AMP_VAL(nid, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name,
+					      HDA_COMPOSE_AMP_VAL(nid, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else if (i == AUTO_SEQ_SURROUND) {
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name,
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else if (i == AUTO_SEQ_SIDE) {
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name,
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+static int vt1709_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin)
+{
+	int err;
+
+	if (!pin)
+		return 0;
+
+	if (spec->multiout.num_dacs == 5) /* 10 channels */
+		spec->multiout.hp_nid = VT1709_HP_DAC_NID;
+	else if (spec->multiout.num_dacs == 3) /* 6 channels */
+		spec->multiout.hp_nid = 0;
+	spec->hp_independent_mode_index = 1;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Headphone Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+	err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+			      "Headphone Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int vt1709_auto_create_analog_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	static hda_nid_t pin_idxs[] = { 0xff, 0x23, 0x1d, 0x1e, 0x21 };
+	return vt_auto_create_analog_input_ctls(codec, cfg, 0x18, pin_idxs,
+						ARRAY_SIZE(pin_idxs));
+}
+
+static int vt1709_parse_auto_config(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int err;
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+	if (err < 0)
+		return err;
+	err = vt1709_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0])
+		return 0; /* can't find valid BIOS pin config */
+
+	err = vt1709_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = vt1709_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]);
+	if (err < 0)
+		return err;
+	err = vt1709_auto_create_analog_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	if (spec->autocfg.dig_outs)
+		spec->multiout.dig_out_nid = VT1709_DIGOUT_NID;
+	spec->dig_in_pin = VT1709_DIGIN_PIN;
+	if (spec->autocfg.dig_in_pin)
+		spec->dig_in_nid = VT1709_DIGIN_NID;
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->input_mux = &spec->private_imux[0];
+
+	if (spec->hp_mux)
+		via_hp_build(codec);
+
+	via_smart51_build(spec);
+	return 1;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list vt1709_loopbacks[] = {
+	{ 0x18, HDA_INPUT, 1 },
+	{ 0x18, HDA_INPUT, 2 },
+	{ 0x18, HDA_INPUT, 3 },
+	{ 0x18, HDA_INPUT, 4 },
+	{ } /* end */
+};
+#endif
+
+static int patch_vt1709_10ch(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+	int err;
+
+	/* create a codec specific record */
+	spec = via_new_spec(codec);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	err = vt1709_parse_auto_config(codec);
+	if (err < 0) {
+		via_free(codec);
+		return err;
+	} else if (!err) {
+		printk(KERN_INFO "hda_codec: Cannot set up configuration.  "
+		       "Using genenic mode...\n");
+	}
+
+	spec->init_verbs[spec->num_iverbs++] = vt1709_10ch_volume_init_verbs;
+	spec->init_verbs[spec->num_iverbs++] = vt1709_uniwill_init_verbs;
+
+	spec->stream_name_analog = "VT1709 Analog";
+	spec->stream_analog_playback = &vt1709_10ch_pcm_analog_playback;
+	spec->stream_analog_capture = &vt1709_pcm_analog_capture;
+
+	spec->stream_name_digital = "VT1709 Digital";
+	spec->stream_digital_playback = &vt1709_pcm_digital_playback;
+	spec->stream_digital_capture = &vt1709_pcm_digital_capture;
+
+
+	if (!spec->adc_nids && spec->input_mux) {
+		spec->adc_nids = vt1709_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(vt1709_adc_nids);
+		get_mux_nids(codec);
+		spec->mixers[spec->num_mixers] = vt1709_capture_mixer;
+		spec->num_mixers++;
+	}
+
+	codec->patch_ops = via_patch_ops;
+
+	codec->patch_ops.init = via_auto_init;
+	codec->patch_ops.unsol_event = via_unsol_event;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = vt1709_loopbacks;
+#endif
+
+	return 0;
+}
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb vt1709_6ch_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-2 and set the default input to mic-in
+	 */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+
+	/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 */
+	/* Amp Indices: AOW0=0, CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)},
+
+	/*
+	 * Set up output selector (0x1a, 0x1b, 0x29)
+	 */
+	/* set vol=0 to output mixers */
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/*
+	 *  Unmute PW3 and PW4
+	 */
+	{0x1f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* Set input of PW4 as MW0 */
+	{0x20, AC_VERB_SET_CONNECT_SEL, 0},
+	/* PW9 Output enable */
+	{0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	{ }
+};
+
+static int patch_vt1709_6ch(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+	int err;
+
+	/* create a codec specific record */
+	spec = via_new_spec(codec);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	err = vt1709_parse_auto_config(codec);
+	if (err < 0) {
+		via_free(codec);
+		return err;
+	} else if (!err) {
+		printk(KERN_INFO "hda_codec: Cannot set up configuration.  "
+		       "Using genenic mode...\n");
+	}
+
+	spec->init_verbs[spec->num_iverbs++] = vt1709_6ch_volume_init_verbs;
+	spec->init_verbs[spec->num_iverbs++] = vt1709_uniwill_init_verbs;
+
+	spec->stream_name_analog = "VT1709 Analog";
+	spec->stream_analog_playback = &vt1709_6ch_pcm_analog_playback;
+	spec->stream_analog_capture = &vt1709_pcm_analog_capture;
+
+	spec->stream_name_digital = "VT1709 Digital";
+	spec->stream_digital_playback = &vt1709_pcm_digital_playback;
+	spec->stream_digital_capture = &vt1709_pcm_digital_capture;
+
+
+	if (!spec->adc_nids && spec->input_mux) {
+		spec->adc_nids = vt1709_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(vt1709_adc_nids);
+		get_mux_nids(codec);
+		spec->mixers[spec->num_mixers] = vt1709_capture_mixer;
+		spec->num_mixers++;
+	}
+
+	codec->patch_ops = via_patch_ops;
+
+	codec->patch_ops.init = via_auto_init;
+	codec->patch_ops.unsol_event = via_unsol_event;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = vt1709_loopbacks;
+#endif
+	return 0;
+}
+
+/* capture mixer elements */
+static struct snd_kcontrol_new vt1708B_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x13, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x13, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x14, 0x0, HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 1,
+		.info = via_mux_enum_info,
+		.get = via_mux_enum_get,
+		.put = via_mux_enum_put,
+	},
+	{ } /* end */
+};
+/*
+ * generic initialization of ADC, input mixers and output mixers
+ */
+static struct hda_verb vt1708B_8ch_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-1 and set the default input to mic-in
+	 */
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+
+	/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 */
+	/* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)},
+
+	/*
+	 * Set up output mixers
+	 */
+	/* set vol=0 to output mixers */
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* Setup default input to PW4 */
+	{0x1d, AC_VERB_SET_CONNECT_SEL, 0},
+	/* PW9 Output enable */
+	{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	/* PW10 Input enable */
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	{ }
+};
+
+static struct hda_verb vt1708B_4ch_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-1 and set the default input to mic-in
+	 */
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+
+	/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 */
+	/* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)},
+
+	/*
+	 * Set up output mixers
+	 */
+	/* set vol=0 to output mixers */
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+	{0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO},
+
+	/* Setup default input of PW4 to MW0 */
+	{0x1d, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* PW9 Output enable */
+	{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	/* PW10 Input enable */
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+	{ }
+};
+
+static struct hda_verb vt1708B_uniwill_init_verbs[] = {
+	{0x1d, AC_VERB_SET_UNSOLICITED_ENABLE,
+	 AC_USRSP_EN | VIA_HP_EVENT | VIA_JACK_EVENT},
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x1c, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x1e, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x22, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x23, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{ }
+};
+
+static int via_pcm_open_close(struct hda_pcm_stream *hinfo,
+			      struct hda_codec *codec,
+			      struct snd_pcm_substream *substream)
+{
+	int idle = substream->pstr->substream_opened == 1
+		&& substream->ref_count == 0;
+
+	analog_low_current_mode(codec, idle);
+	return 0;
+}
+
+static struct hda_pcm_stream vt1708B_8ch_pcm_analog_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 8,
+	.nid = 0x10, /* NID to query formats and rates */
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup,
+		.close = via_pcm_open_close
+	},
+};
+
+static struct hda_pcm_stream vt1708B_4ch_pcm_analog_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 4,
+	.nid = 0x10, /* NID to query formats and rates */
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream vt1708B_pcm_analog_capture = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x13, /* NID to query formats and rates */
+	.ops = {
+		.open = via_pcm_open_close,
+		.prepare = via_capture_pcm_prepare,
+		.cleanup = via_capture_pcm_cleanup,
+		.close = via_pcm_open_close
+	},
+};
+
+static struct hda_pcm_stream vt1708B_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in via_build_pcms */
+	.ops = {
+		.open = via_dig_playback_pcm_open,
+		.close = via_dig_playback_pcm_close,
+		.prepare = via_dig_playback_pcm_prepare,
+		.cleanup = via_dig_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream vt1708B_pcm_digital_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+};
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int vt1708B_auto_fill_dac_nids(struct via_spec *spec,
+				     const struct auto_pin_cfg *cfg)
+{
+	int i;
+	hda_nid_t nid;
+
+	spec->multiout.num_dacs = cfg->line_outs;
+
+	spec->multiout.dac_nids = spec->private_dac_nids;
+
+	for (i = 0; i < 4; i++) {
+		nid = cfg->line_out_pins[i];
+		if (nid) {
+			/* config dac list */
+			switch (i) {
+			case AUTO_SEQ_FRONT:
+				spec->multiout.dac_nids[i] = 0x10;
+				break;
+			case AUTO_SEQ_CENLFE:
+				spec->multiout.dac_nids[i] = 0x24;
+				break;
+			case AUTO_SEQ_SURROUND:
+				spec->multiout.dac_nids[i] = 0x11;
+				break;
+			case AUTO_SEQ_SIDE:
+				spec->multiout.dac_nids[i] = 0x25;
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int vt1708B_auto_create_multi_out_ctls(struct via_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	char name[32];
+	static const char *chname[4] = { "Front", "Surround", "C/LFE", "Side" };
+	hda_nid_t nid_vols[] = {0x16, 0x18, 0x26, 0x27};
+	hda_nid_t nid, nid_vol = 0;
+	int i, err;
+
+	for (i = 0; i <= AUTO_SEQ_SIDE; i++) {
+		nid = cfg->line_out_pins[i];
+
+		if (!nid)
+			continue;
+
+		nid_vol = nid_vols[i];
+
+		if (i == AUTO_SEQ_CENLFE) {
+			/* Center/LFE */
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "Center Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "LFE Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "Center Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "LFE Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else if (i == AUTO_SEQ_FRONT) {
+			/* add control to mixer index 0 */
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "Master Front Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_INPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "Master Front Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_INPUT));
+			if (err < 0)
+				return err;
+
+			/* add control to PW3 */
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
+					      HDA_COMPOSE_AMP_VAL(nid, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name,
+					      HDA_COMPOSE_AMP_VAL(nid, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else {
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name,
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+static int vt1708B_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin)
+{
+	int err;
+
+	if (!pin)
+		return 0;
+
+	spec->multiout.hp_nid = VT1708B_HP_NID; /* AOW3 */
+	spec->hp_independent_mode_index = 1;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Headphone Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+	err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+			      "Headphone Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	create_hp_imux(spec);
+
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int vt1708B_auto_create_analog_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	static hda_nid_t pin_idxs[] = { 0xff, 0x1f, 0x1a, 0x1b, 0x1e };
+	return vt_auto_create_analog_input_ctls(codec, cfg, 0x16, pin_idxs,
+						ARRAY_SIZE(pin_idxs));
+}
+
+static int vt1708B_parse_auto_config(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int err;
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+	if (err < 0)
+		return err;
+	err = vt1708B_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0])
+		return 0; /* can't find valid BIOS pin config */
+
+	err = vt1708B_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = vt1708B_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]);
+	if (err < 0)
+		return err;
+	err = vt1708B_auto_create_analog_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	if (spec->autocfg.dig_outs)
+		spec->multiout.dig_out_nid = VT1708B_DIGOUT_NID;
+	spec->dig_in_pin = VT1708B_DIGIN_PIN;
+	if (spec->autocfg.dig_in_pin)
+		spec->dig_in_nid = VT1708B_DIGIN_NID;
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->input_mux = &spec->private_imux[0];
+
+	if (spec->hp_mux)
+		via_hp_build(codec);
+
+	via_smart51_build(spec);
+	return 1;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list vt1708B_loopbacks[] = {
+	{ 0x16, HDA_INPUT, 1 },
+	{ 0x16, HDA_INPUT, 2 },
+	{ 0x16, HDA_INPUT, 3 },
+	{ 0x16, HDA_INPUT, 4 },
+	{ } /* end */
+};
+#endif
+static int patch_vt1708S(struct hda_codec *codec);
+static int patch_vt1708B_8ch(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+	int err;
+
+	if (get_codec_type(codec) == VT1708BCE)
+		return patch_vt1708S(codec);
+	/* create a codec specific record */
+	spec = via_new_spec(codec);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	/* automatic parse from the BIOS config */
+	err = vt1708B_parse_auto_config(codec);
+	if (err < 0) {
+		via_free(codec);
+		return err;
+	} else if (!err) {
+		printk(KERN_INFO "hda_codec: Cannot set up configuration "
+		       "from BIOS.  Using genenic mode...\n");
+	}
+
+	spec->init_verbs[spec->num_iverbs++] = vt1708B_8ch_volume_init_verbs;
+	spec->init_verbs[spec->num_iverbs++] = vt1708B_uniwill_init_verbs;
+
+	spec->stream_name_analog = "VT1708B Analog";
+	spec->stream_analog_playback = &vt1708B_8ch_pcm_analog_playback;
+	spec->stream_analog_capture = &vt1708B_pcm_analog_capture;
+
+	spec->stream_name_digital = "VT1708B Digital";
+	spec->stream_digital_playback = &vt1708B_pcm_digital_playback;
+	spec->stream_digital_capture = &vt1708B_pcm_digital_capture;
+
+	if (!spec->adc_nids && spec->input_mux) {
+		spec->adc_nids = vt1708B_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(vt1708B_adc_nids);
+		get_mux_nids(codec);
+		spec->mixers[spec->num_mixers] = vt1708B_capture_mixer;
+		spec->num_mixers++;
+	}
+
+	codec->patch_ops = via_patch_ops;
+
+	codec->patch_ops.init = via_auto_init;
+	codec->patch_ops.unsol_event = via_unsol_event;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = vt1708B_loopbacks;
+#endif
+
+	return 0;
+}
+
+static int patch_vt1708B_4ch(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+	int err;
+
+	/* create a codec specific record */
+	spec = via_new_spec(codec);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	/* automatic parse from the BIOS config */
+	err = vt1708B_parse_auto_config(codec);
+	if (err < 0) {
+		via_free(codec);
+		return err;
+	} else if (!err) {
+		printk(KERN_INFO "hda_codec: Cannot set up configuration "
+		       "from BIOS.  Using genenic mode...\n");
+	}
+
+	spec->init_verbs[spec->num_iverbs++] = vt1708B_4ch_volume_init_verbs;
+	spec->init_verbs[spec->num_iverbs++] = vt1708B_uniwill_init_verbs;
+
+	spec->stream_name_analog = "VT1708B Analog";
+	spec->stream_analog_playback = &vt1708B_4ch_pcm_analog_playback;
+	spec->stream_analog_capture = &vt1708B_pcm_analog_capture;
+
+	spec->stream_name_digital = "VT1708B Digital";
+	spec->stream_digital_playback = &vt1708B_pcm_digital_playback;
+	spec->stream_digital_capture = &vt1708B_pcm_digital_capture;
+
+	if (!spec->adc_nids && spec->input_mux) {
+		spec->adc_nids = vt1708B_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(vt1708B_adc_nids);
+		get_mux_nids(codec);
+		spec->mixers[spec->num_mixers] = vt1708B_capture_mixer;
+		spec->num_mixers++;
+	}
+
+	codec->patch_ops = via_patch_ops;
+
+	codec->patch_ops.init = via_auto_init;
+	codec->patch_ops.unsol_event = via_unsol_event;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = vt1708B_loopbacks;
+#endif
+
+	return 0;
+}
+
+/* Patch for VT1708S */
+
+/* capture mixer elements */
+static struct snd_kcontrol_new vt1708S_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x13, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x13, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost Capture Volume", 0x1A, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost Capture Volume", 0x1E, 0x0,
+			 HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 1,
+		.info = via_mux_enum_info,
+		.get = via_mux_enum_get,
+		.put = via_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb vt1708S_volume_init_verbs[] = {
+	/* Unmute ADC0-1 and set the default input to mic-in */
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+	/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the
+	 * analog-loopback mixer widget */
+	/* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)},
+
+	/* Setup default input of PW4 to MW0 */
+	{0x1d, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* PW9, PW10  Output enable */
+	{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	/* Enable Mic Boost Volume backdoor */
+	{0x1, 0xf98, 0x1},
+	/* don't bybass mixer */
+	{0x1, 0xf88, 0xc0},
+	{ }
+};
+
+static struct hda_verb vt1708S_uniwill_init_verbs[] = {
+	{0x1d, AC_VERB_SET_UNSOLICITED_ENABLE,
+	 AC_USRSP_EN | VIA_HP_EVENT | VIA_JACK_EVENT},
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x1c, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x1e, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x22, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x23, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{ }
+};
+
+static struct hda_pcm_stream vt1708S_pcm_analog_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 8,
+	.nid = 0x10, /* NID to query formats and rates */
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup,
+		.close = via_pcm_open_close
+	},
+};
+
+static struct hda_pcm_stream vt1708S_pcm_analog_capture = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x13, /* NID to query formats and rates */
+	.ops = {
+		.open = via_pcm_open_close,
+		.prepare = via_capture_pcm_prepare,
+		.cleanup = via_capture_pcm_cleanup,
+		.close = via_pcm_open_close
+	},
+};
+
+static struct hda_pcm_stream vt1708S_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in via_build_pcms */
+	.ops = {
+		.open = via_dig_playback_pcm_open,
+		.close = via_dig_playback_pcm_close,
+		.prepare = via_dig_playback_pcm_prepare,
+		.cleanup = via_dig_playback_pcm_cleanup
+	},
+};
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int vt1708S_auto_fill_dac_nids(struct via_spec *spec,
+				     const struct auto_pin_cfg *cfg)
+{
+	int i;
+	hda_nid_t nid;
+
+	spec->multiout.num_dacs = cfg->line_outs;
+
+	spec->multiout.dac_nids = spec->private_dac_nids;
+
+	for (i = 0; i < 4; i++) {
+		nid = cfg->line_out_pins[i];
+		if (nid) {
+			/* config dac list */
+			switch (i) {
+			case AUTO_SEQ_FRONT:
+				spec->multiout.dac_nids[i] = 0x10;
+				break;
+			case AUTO_SEQ_CENLFE:
+				spec->multiout.dac_nids[i] = 0x24;
+				break;
+			case AUTO_SEQ_SURROUND:
+				spec->multiout.dac_nids[i] = 0x11;
+				break;
+			case AUTO_SEQ_SIDE:
+				spec->multiout.dac_nids[i] = 0x25;
+				break;
+			}
+		}
+	}
+
+	/* for Smart 5.1, line/mic inputs double as output pins */
+	if (cfg->line_outs == 1) {
+		spec->multiout.num_dacs = 3;
+		spec->multiout.dac_nids[AUTO_SEQ_SURROUND] = 0x11;
+		spec->multiout.dac_nids[AUTO_SEQ_CENLFE] = 0x24;
+	}
+
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int vt1708S_auto_create_multi_out_ctls(struct via_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	char name[32];
+	static const char *chname[4] = { "Front", "Surround", "C/LFE", "Side" };
+	hda_nid_t nid_vols[] = {0x10, 0x11, 0x24, 0x25};
+	hda_nid_t nid_mutes[] = {0x1C, 0x18, 0x26, 0x27};
+	hda_nid_t nid, nid_vol, nid_mute;
+	int i, err;
+
+	for (i = 0; i <= AUTO_SEQ_SIDE; i++) {
+		nid = cfg->line_out_pins[i];
+
+		/* for Smart 5.1, there are always at least six channels */
+		if (!nid && i > AUTO_SEQ_CENLFE)
+			continue;
+
+		nid_vol = nid_vols[i];
+		nid_mute = nid_mutes[i];
+
+		if (i == AUTO_SEQ_CENLFE) {
+			/* Center/LFE */
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "Center Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "LFE Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "Center Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(nid_mute,
+								  1, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "LFE Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(nid_mute,
+								  2, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else if (i == AUTO_SEQ_FRONT) {
+			/* add control to mixer index 0 */
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "Master Front Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(0x16, 3, 0,
+								  HDA_INPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+					      "Master Front Playback Switch",
+					      HDA_COMPOSE_AMP_VAL(0x16, 3, 0,
+								  HDA_INPUT));
+			if (err < 0)
+				return err;
+
+			/* Front */
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name,
+					      HDA_COMPOSE_AMP_VAL(nid_mute,
+								  3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else {
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name,
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name,
+					      HDA_COMPOSE_AMP_VAL(nid_mute,
+								  3, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+static int vt1708S_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin)
+{
+	int err;
+
+	if (!pin)
+		return 0;
+
+	spec->multiout.hp_nid = VT1708S_HP_NID; /* AOW3 */
+	spec->hp_independent_mode_index = 1;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Headphone Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(0x25, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+			      "Headphone Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	create_hp_imux(spec);
+
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int vt1708S_auto_create_analog_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	static hda_nid_t pin_idxs[] = { 0x1f, 0x1a, 0x1b, 0x1e, 0, 0xff };
+	return vt_auto_create_analog_input_ctls(codec, cfg, 0x16, pin_idxs,
+						ARRAY_SIZE(pin_idxs));
+}
+
+/* fill out digital output widgets; one for master and one for slave outputs */
+static void fill_dig_outs(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int i;
+
+	for (i = 0; i < spec->autocfg.dig_outs; i++) {
+		hda_nid_t nid;
+		int conn;
+
+		nid = spec->autocfg.dig_out_pins[i];
+		if (!nid)
+			continue;
+		conn = snd_hda_get_connections(codec, nid, &nid, 1);
+		if (conn < 1)
+			continue;
+		if (!spec->multiout.dig_out_nid)
+			spec->multiout.dig_out_nid = nid;
+		else {
+			spec->slave_dig_outs[0] = nid;
+			break; /* at most two dig outs */
+		}
+	}
+}
+
+static int vt1708S_parse_auto_config(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int err;
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+	if (err < 0)
+		return err;
+	err = vt1708S_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0])
+		return 0; /* can't find valid BIOS pin config */
+
+	err = vt1708S_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = vt1708S_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]);
+	if (err < 0)
+		return err;
+	err = vt1708S_auto_create_analog_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	fill_dig_outs(codec);
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->input_mux = &spec->private_imux[0];
+
+	if (spec->hp_mux)
+		via_hp_build(codec);
+
+	via_smart51_build(spec);
+	return 1;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list vt1708S_loopbacks[] = {
+	{ 0x16, HDA_INPUT, 1 },
+	{ 0x16, HDA_INPUT, 2 },
+	{ 0x16, HDA_INPUT, 3 },
+	{ 0x16, HDA_INPUT, 4 },
+	{ } /* end */
+};
+#endif
+
+static void override_mic_boost(struct hda_codec *codec, hda_nid_t pin,
+			       int offset, int num_steps, int step_size)
+{
+	snd_hda_override_amp_caps(codec, pin, HDA_INPUT,
+				  (offset << AC_AMPCAP_OFFSET_SHIFT) |
+				  (num_steps << AC_AMPCAP_NUM_STEPS_SHIFT) |
+				  (step_size << AC_AMPCAP_STEP_SIZE_SHIFT) |
+				  (0 << AC_AMPCAP_MUTE_SHIFT));
+}
+
+static int patch_vt1708S(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+	int err;
+
+	/* create a codec specific record */
+	spec = via_new_spec(codec);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	/* automatic parse from the BIOS config */
+	err = vt1708S_parse_auto_config(codec);
+	if (err < 0) {
+		via_free(codec);
+		return err;
+	} else if (!err) {
+		printk(KERN_INFO "hda_codec: Cannot set up configuration "
+		       "from BIOS.  Using genenic mode...\n");
+	}
+
+	spec->init_verbs[spec->num_iverbs++] = vt1708S_volume_init_verbs;
+	spec->init_verbs[spec->num_iverbs++] = vt1708S_uniwill_init_verbs;
+
+	if (codec->vendor_id == 0x11060440)
+		spec->stream_name_analog = "VT1818S Analog";
+	else
+		spec->stream_name_analog = "VT1708S Analog";
+	spec->stream_analog_playback = &vt1708S_pcm_analog_playback;
+	spec->stream_analog_capture = &vt1708S_pcm_analog_capture;
+
+	if (codec->vendor_id == 0x11060440)
+		spec->stream_name_digital = "VT1818S Digital";
+	else
+		spec->stream_name_digital = "VT1708S Digital";
+	spec->stream_digital_playback = &vt1708S_pcm_digital_playback;
+
+	if (!spec->adc_nids && spec->input_mux) {
+		spec->adc_nids = vt1708S_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(vt1708S_adc_nids);
+		get_mux_nids(codec);
+		override_mic_boost(codec, 0x1a, 0, 3, 40);
+		override_mic_boost(codec, 0x1e, 0, 3, 40);
+		spec->mixers[spec->num_mixers] = vt1708S_capture_mixer;
+		spec->num_mixers++;
+	}
+
+	codec->patch_ops = via_patch_ops;
+
+	codec->patch_ops.init = via_auto_init;
+	codec->patch_ops.unsol_event = via_unsol_event;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = vt1708S_loopbacks;
+#endif
+
+	/* correct names for VT1708BCE */
+	if (get_codec_type(codec) == VT1708BCE)	{
+		kfree(codec->chip_name);
+		codec->chip_name = kstrdup("VT1708BCE", GFP_KERNEL);
+		snprintf(codec->bus->card->mixername,
+			 sizeof(codec->bus->card->mixername),
+			 "%s %s", codec->vendor_name, codec->chip_name);
+		spec->stream_name_analog = "VT1708BCE Analog";
+		spec->stream_name_digital = "VT1708BCE Digital";
+	}
+	return 0;
+}
+
+/* Patch for VT1702 */
+
+/* capture mixer elements */
+static struct snd_kcontrol_new vt1702_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x12, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x20, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x20, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Digital Mic Capture Volume", 0x1F, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Digital Mic Capture Switch", 0x1F, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Digital Mic Boost Capture Volume", 0x1E, 0x0,
+			 HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 1,
+		.info = via_mux_enum_info,
+		.get = via_mux_enum_get,
+		.put = via_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb vt1702_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-1 and set the default input to mic-in
+	 */
+	{0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1F, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+
+	/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 */
+	/* Amp Indices: Mic1 = 1, Line = 1, Mic2 = 3 */
+	{0x1A, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1A, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x1A, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)},
+	{0x1A, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)},
+	{0x1A, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+
+	/* Setup default input of PW4 to MW0 */
+	{0x17, AC_VERB_SET_CONNECT_SEL, 0x1},
+	/* PW6 PW7 Output enable */
+	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	{0x1C, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	/* mixer enable */
+	{0x1, 0xF88, 0x3},
+	/* GPIO 0~2 */
+	{0x1, 0xF82, 0x3F},
+	{ }
+};
+
+static struct hda_verb vt1702_uniwill_init_verbs[] = {
+	{0x17, AC_VERB_SET_UNSOLICITED_ENABLE,
+	 AC_USRSP_EN | VIA_HP_EVENT | VIA_JACK_EVENT},
+	{0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{ }
+};
+
+static struct hda_pcm_stream vt1702_pcm_analog_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x10, /* NID to query formats and rates */
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup,
+		.close = via_pcm_open_close
+	},
+};
+
+static struct hda_pcm_stream vt1702_pcm_analog_capture = {
+	.substreams = 3,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x12, /* NID to query formats and rates */
+	.ops = {
+		.open = via_pcm_open_close,
+		.prepare = via_capture_pcm_prepare,
+		.cleanup = via_capture_pcm_cleanup,
+		.close = via_pcm_open_close
+	},
+};
+
+static struct hda_pcm_stream vt1702_pcm_digital_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in via_build_pcms */
+	.ops = {
+		.open = via_dig_playback_pcm_open,
+		.close = via_dig_playback_pcm_close,
+		.prepare = via_dig_playback_pcm_prepare,
+		.cleanup = via_dig_playback_pcm_cleanup
+	},
+};
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int vt1702_auto_fill_dac_nids(struct via_spec *spec,
+				     const struct auto_pin_cfg *cfg)
+{
+	spec->multiout.num_dacs = 1;
+	spec->multiout.dac_nids = spec->private_dac_nids;
+
+	if (cfg->line_out_pins[0]) {
+		/* config dac list */
+		spec->multiout.dac_nids[0] = 0x10;
+	}
+
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int vt1702_auto_create_line_out_ctls(struct via_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	int err;
+
+	if (!cfg->line_out_pins[0])
+		return -1;
+
+	/* add control to mixer index 0 */
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Master Front Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(0x1A, 3, 0, HDA_INPUT));
+	if (err < 0)
+		return err;
+	err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+			      "Master Front Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(0x1A, 3, 0, HDA_INPUT));
+	if (err < 0)
+		return err;
+
+	/* Front */
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Front Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(0x10, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+	err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+			      "Front Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(0x16, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int vt1702_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin)
+{
+	int err, i;
+	struct hda_input_mux *imux;
+	static const char *texts[] = { "ON", "OFF", NULL};
+	if (!pin)
+		return 0;
+	spec->multiout.hp_nid = 0x1D;
+	spec->hp_independent_mode_index = 0;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Headphone Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(0x1D, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+			      "Headphone Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	imux = &spec->private_imux[1];
+
+	/* for hp mode select */
+	for (i = 0; texts[i]; i++)
+		snd_hda_add_imux_item(imux, texts[i], i, NULL);
+
+	spec->hp_mux = &spec->private_imux[1];
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int vt1702_auto_create_analog_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	static hda_nid_t pin_idxs[] = { 0x14, 0x15, 0x18, 0xff };
+	return vt_auto_create_analog_input_ctls(codec, cfg, 0x1a, pin_idxs,
+						ARRAY_SIZE(pin_idxs));
+}
+
+static int vt1702_parse_auto_config(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int err;
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+	if (err < 0)
+		return err;
+	err = vt1702_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0])
+		return 0; /* can't find valid BIOS pin config */
+
+	err = vt1702_auto_create_line_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = vt1702_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]);
+	if (err < 0)
+		return err;
+	/* limit AA path volume to 0 dB */
+	snd_hda_override_amp_caps(codec, 0x1A, HDA_INPUT,
+				  (0x17 << AC_AMPCAP_OFFSET_SHIFT) |
+				  (0x17 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+				  (0x5 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+				  (1 << AC_AMPCAP_MUTE_SHIFT));
+	err = vt1702_auto_create_analog_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	fill_dig_outs(codec);
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->input_mux = &spec->private_imux[0];
+
+	if (spec->hp_mux)
+		via_hp_build(codec);
+
+	return 1;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list vt1702_loopbacks[] = {
+	{ 0x1A, HDA_INPUT, 1 },
+	{ 0x1A, HDA_INPUT, 2 },
+	{ 0x1A, HDA_INPUT, 3 },
+	{ 0x1A, HDA_INPUT, 4 },
+	{ } /* end */
+};
+#endif
+
+static int patch_vt1702(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+	int err;
+
+	/* create a codec specific record */
+	spec = via_new_spec(codec);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	/* automatic parse from the BIOS config */
+	err = vt1702_parse_auto_config(codec);
+	if (err < 0) {
+		via_free(codec);
+		return err;
+	} else if (!err) {
+		printk(KERN_INFO "hda_codec: Cannot set up configuration "
+		       "from BIOS.  Using genenic mode...\n");
+	}
+
+	spec->init_verbs[spec->num_iverbs++] = vt1702_volume_init_verbs;
+	spec->init_verbs[spec->num_iverbs++] = vt1702_uniwill_init_verbs;
+
+	spec->stream_name_analog = "VT1702 Analog";
+	spec->stream_analog_playback = &vt1702_pcm_analog_playback;
+	spec->stream_analog_capture = &vt1702_pcm_analog_capture;
+
+	spec->stream_name_digital = "VT1702 Digital";
+	spec->stream_digital_playback = &vt1702_pcm_digital_playback;
+
+	if (!spec->adc_nids && spec->input_mux) {
+		spec->adc_nids = vt1702_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(vt1702_adc_nids);
+		get_mux_nids(codec);
+		spec->mixers[spec->num_mixers] = vt1702_capture_mixer;
+		spec->num_mixers++;
+	}
+
+	codec->patch_ops = via_patch_ops;
+
+	codec->patch_ops.init = via_auto_init;
+	codec->patch_ops.unsol_event = via_unsol_event;
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = vt1702_loopbacks;
+#endif
+
+	return 0;
+}
+
+/* Patch for VT1718S */
+
+/* capture mixer elements */
+static struct snd_kcontrol_new vt1718S_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x10, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x10, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x11, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x11, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost Capture Volume", 0x2b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost Capture Volume", 0x29, 0x0,
+			 HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		.name = "Input Source",
+		.count = 2,
+		.info = via_mux_enum_info,
+		.get = via_mux_enum_get,
+		.put = via_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb vt1718S_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-1 and set the default input to mic-in
+	 */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+
+	/* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 */
+	/* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)},
+
+	/* Setup default input of Front HP to MW9 */
+	{0x28, AC_VERB_SET_CONNECT_SEL, 0x1},
+	/* PW9 PW10 Output enable */
+	{0x2d, AC_VERB_SET_PIN_WIDGET_CONTROL, AC_PINCTL_OUT_EN},
+	{0x2e, AC_VERB_SET_PIN_WIDGET_CONTROL, AC_PINCTL_OUT_EN},
+	/* PW11 Input enable */
+	{0x2f, AC_VERB_SET_PIN_WIDGET_CONTROL, AC_PINCTL_IN_EN},
+	/* Enable Boost Volume backdoor */
+	{0x1, 0xf88, 0x8},
+	/* MW0/1/2/3/4: un-mute index 0 (AOWx), mute index 1 (MW9) */
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	/* set MUX1 = 2 (AOW4), MUX2 = 1 (AOW3) */
+	{0x34, AC_VERB_SET_CONNECT_SEL, 0x2},
+	{0x35, AC_VERB_SET_CONNECT_SEL, 0x1},
+	/* Unmute MW4's index 0 */
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{ }
+};
+
+
+static struct hda_verb vt1718S_uniwill_init_verbs[] = {
+	{0x28, AC_VERB_SET_UNSOLICITED_ENABLE,
+	 AC_USRSP_EN | VIA_HP_EVENT | VIA_JACK_EVENT},
+	{0x24, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x25, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x26, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x27, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x29, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x2a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x2b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{ }
+};
+
+static struct hda_pcm_stream vt1718S_pcm_analog_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 10,
+	.nid = 0x8, /* NID to query formats and rates */
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup,
+		.close = via_pcm_open_close,
+	},
+};
+
+static struct hda_pcm_stream vt1718S_pcm_analog_capture = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x10, /* NID to query formats and rates */
+	.ops = {
+		.open = via_pcm_open_close,
+		.prepare = via_capture_pcm_prepare,
+		.cleanup = via_capture_pcm_cleanup,
+		.close = via_pcm_open_close,
+	},
+};
+
+static struct hda_pcm_stream vt1718S_pcm_digital_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in via_build_pcms */
+	.ops = {
+		.open = via_dig_playback_pcm_open,
+		.close = via_dig_playback_pcm_close,
+		.prepare = via_dig_playback_pcm_prepare,
+		.cleanup = via_dig_playback_pcm_cleanup
+	},
+};
+
+static struct hda_pcm_stream vt1718S_pcm_digital_capture = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+};
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int vt1718S_auto_fill_dac_nids(struct via_spec *spec,
+				     const struct auto_pin_cfg *cfg)
+{
+	int i;
+	hda_nid_t nid;
+
+	spec->multiout.num_dacs = cfg->line_outs;
+
+	spec->multiout.dac_nids = spec->private_dac_nids;
+
+	for (i = 0; i < 4; i++) {
+		nid = cfg->line_out_pins[i];
+		if (nid) {
+			/* config dac list */
+			switch (i) {
+			case AUTO_SEQ_FRONT:
+				spec->multiout.dac_nids[i] = 0x8;
+				break;
+			case AUTO_SEQ_CENLFE:
+				spec->multiout.dac_nids[i] = 0xa;
+				break;
+			case AUTO_SEQ_SURROUND:
+				spec->multiout.dac_nids[i] = 0x9;
+				break;
+			case AUTO_SEQ_SIDE:
+				spec->multiout.dac_nids[i] = 0xb;
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int vt1718S_auto_create_multi_out_ctls(struct via_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	char name[32];
+	static const char *chname[4] = { "Front", "Surround", "C/LFE", "Side" };
+	hda_nid_t nid_vols[] = {0x8, 0x9, 0xa, 0xb};
+	hda_nid_t nid_mutes[] = {0x24, 0x25, 0x26, 0x27};
+	hda_nid_t nid, nid_vol, nid_mute = 0;
+	int i, err;
+
+	for (i = 0; i <= AUTO_SEQ_SIDE; i++) {
+		nid = cfg->line_out_pins[i];
+
+		if (!nid)
+			continue;
+		nid_vol = nid_vols[i];
+		nid_mute = nid_mutes[i];
+
+		if (i == AUTO_SEQ_CENLFE) {
+			/* Center/LFE */
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "Center Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+					      "LFE Playback Volume",
+					      HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0,
+								  HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_MUTE,
+				"Center Playback Switch",
+				HDA_COMPOSE_AMP_VAL(nid_mute, 1, 0,
+						    HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_MUTE,
+				"LFE Playback Switch",
+				HDA_COMPOSE_AMP_VAL(nid_mute, 2, 0,
+						    HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else if (i == AUTO_SEQ_FRONT) {
+			/* Front */
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_VOL, name,
+				HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_MUTE, name,
+				HDA_COMPOSE_AMP_VAL(nid_mute, 3, 0,
+						    HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else {
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_VOL, name,
+				HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_MUTE, name,
+				HDA_COMPOSE_AMP_VAL(nid_mute, 3, 0,
+						    HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+static int vt1718S_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin)
+{
+	int err;
+
+	if (!pin)
+		return 0;
+
+	spec->multiout.hp_nid = 0xc; /* AOW4 */
+	spec->hp_independent_mode_index = 1;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Headphone Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(0xc, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+			      "Headphone Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	create_hp_imux(spec);
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int vt1718S_auto_create_analog_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	static hda_nid_t pin_idxs[] = { 0x2c, 0x2b, 0x2a, 0x29, 0, 0xff };
+	return vt_auto_create_analog_input_ctls(codec, cfg, 0x21, pin_idxs,
+						ARRAY_SIZE(pin_idxs));
+}
+
+static int vt1718S_parse_auto_config(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int err;
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+
+	if (err < 0)
+		return err;
+	err = vt1718S_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0])
+		return 0; /* can't find valid BIOS pin config */
+
+	err = vt1718S_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = vt1718S_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]);
+	if (err < 0)
+		return err;
+	err = vt1718S_auto_create_analog_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	fill_dig_outs(codec);
+
+	if (spec->autocfg.dig_in_pin && codec->vendor_id == 0x11060428)
+		spec->dig_in_nid = 0x13;
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->input_mux = &spec->private_imux[0];
+
+	if (spec->hp_mux)
+		via_hp_build(codec);
+
+	via_smart51_build(spec);
+
+	return 1;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list vt1718S_loopbacks[] = {
+	{ 0x21, HDA_INPUT, 1 },
+	{ 0x21, HDA_INPUT, 2 },
+	{ 0x21, HDA_INPUT, 3 },
+	{ 0x21, HDA_INPUT, 4 },
+	{ } /* end */
+};
+#endif
+
+static int patch_vt1718S(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+	int err;
+
+	/* create a codec specific record */
+	spec = via_new_spec(codec);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	/* automatic parse from the BIOS config */
+	err = vt1718S_parse_auto_config(codec);
+	if (err < 0) {
+		via_free(codec);
+		return err;
+	} else if (!err) {
+		printk(KERN_INFO "hda_codec: Cannot set up configuration "
+		       "from BIOS.  Using genenic mode...\n");
+	}
+
+	spec->init_verbs[spec->num_iverbs++] = vt1718S_volume_init_verbs;
+	spec->init_verbs[spec->num_iverbs++] = vt1718S_uniwill_init_verbs;
+
+	if (codec->vendor_id == 0x11060441)
+		spec->stream_name_analog = "VT2020 Analog";
+	else if (codec->vendor_id == 0x11064441)
+		spec->stream_name_analog = "VT1828S Analog";
+	else
+		spec->stream_name_analog = "VT1718S Analog";
+	spec->stream_analog_playback = &vt1718S_pcm_analog_playback;
+	spec->stream_analog_capture = &vt1718S_pcm_analog_capture;
+
+	if (codec->vendor_id == 0x11060441)
+		spec->stream_name_digital = "VT2020 Digital";
+	else if (codec->vendor_id == 0x11064441)
+		spec->stream_name_digital = "VT1828S Digital";
+	else
+		spec->stream_name_digital = "VT1718S Digital";
+	spec->stream_digital_playback = &vt1718S_pcm_digital_playback;
+	if (codec->vendor_id == 0x11060428 || codec->vendor_id == 0x11060441)
+		spec->stream_digital_capture = &vt1718S_pcm_digital_capture;
+
+	if (!spec->adc_nids && spec->input_mux) {
+		spec->adc_nids = vt1718S_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(vt1718S_adc_nids);
+		get_mux_nids(codec);
+		override_mic_boost(codec, 0x2b, 0, 3, 40);
+		override_mic_boost(codec, 0x29, 0, 3, 40);
+		spec->mixers[spec->num_mixers] = vt1718S_capture_mixer;
+		spec->num_mixers++;
+	}
+
+	codec->patch_ops = via_patch_ops;
+
+	codec->patch_ops.init = via_auto_init;
+	codec->patch_ops.unsol_event = via_unsol_event;
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = vt1718S_loopbacks;
+#endif
+
+	return 0;
+}
+
+/* Patch for VT1716S */
+
+static int vt1716s_dmic_info(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int vt1716s_dmic_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	int index = 0;
+
+	index = snd_hda_codec_read(codec, 0x26, 0,
+					       AC_VERB_GET_CONNECT_SEL, 0);
+	if (index != -1)
+		*ucontrol->value.integer.value = index;
+
+	return 0;
+}
+
+static int vt1716s_dmic_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct via_spec *spec = codec->spec;
+	int index = *ucontrol->value.integer.value;
+
+	snd_hda_codec_write(codec, 0x26, 0,
+					       AC_VERB_SET_CONNECT_SEL, index);
+	spec->dmic_enabled = index;
+	set_jack_power_state(codec);
+
+	return 1;
+}
+
+/* capture mixer elements */
+static struct snd_kcontrol_new vt1716S_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x13, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x13, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x14, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost Capture Volume", 0x1A, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost Capture Volume", 0x1E, 0x0,
+			 HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Input Source",
+		.count = 1,
+		.info = via_mux_enum_info,
+		.get = via_mux_enum_get,
+		.put = via_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+static struct snd_kcontrol_new vt1716s_dmic_mixer[] = {
+	HDA_CODEC_VOLUME("Digital Mic Capture Volume", 0x22, 0x0, HDA_INPUT),
+	{
+	 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	 .name = "Digital Mic Capture Switch",
+	 .subdevice = HDA_SUBDEV_NID_FLAG | 0x26,
+	 .count = 1,
+	 .info = vt1716s_dmic_info,
+	 .get = vt1716s_dmic_get,
+	 .put = vt1716s_dmic_put,
+	 },
+	{}			/* end */
+};
+
+
+/* mono-out mixer elements */
+static struct snd_kcontrol_new vt1716S_mono_out_mixer[] = {
+	HDA_CODEC_MUTE("Mono Playback Switch", 0x2a, 0x0, HDA_OUTPUT),
+	{ } /* end */
+};
+
+static struct hda_verb vt1716S_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-1 and set the default input to mic-in
+	 */
+	{0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+
+	/* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 */
+	/* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+
+	/* MUX Indices: Stereo Mixer = 5 */
+	{0x17, AC_VERB_SET_CONNECT_SEL, 0x5},
+
+	/* Setup default input of PW4 to MW0 */
+	{0x1d, AC_VERB_SET_CONNECT_SEL, 0x0},
+
+	/* Setup default input of SW1 as MW0 */
+	{0x18, AC_VERB_SET_CONNECT_SEL, 0x1},
+
+	/* Setup default input of SW4 as AOW0 */
+	{0x28, AC_VERB_SET_CONNECT_SEL, 0x1},
+
+	/* PW9 PW10 Output enable */
+	{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	{0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+
+	/* Unmute SW1, PW12 */
+	{0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x2a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* PW12 Output enable */
+	{0x2a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+	/* Enable Boost Volume backdoor */
+	{0x1, 0xf8a, 0x80},
+	/* don't bybass mixer */
+	{0x1, 0xf88, 0xc0},
+	/* Enable mono output */
+	{0x1, 0xf90, 0x08},
+	{ }
+};
+
+
+static struct hda_verb vt1716S_uniwill_init_verbs[] = {
+	{0x1d, AC_VERB_SET_UNSOLICITED_ENABLE,
+	 AC_USRSP_EN | VIA_HP_EVENT | VIA_JACK_EVENT},
+	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x1c, AC_VERB_SET_UNSOLICITED_ENABLE,
+	 AC_USRSP_EN | VIA_MONO_EVENT | VIA_JACK_EVENT},
+	{0x1e, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x23, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{ }
+};
+
+static struct hda_pcm_stream vt1716S_pcm_analog_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 6,
+	.nid = 0x10, /* NID to query formats and rates */
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup,
+		.close = via_pcm_open_close,
+	},
+};
+
+static struct hda_pcm_stream vt1716S_pcm_analog_capture = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x13, /* NID to query formats and rates */
+	.ops = {
+		.open = via_pcm_open_close,
+		.prepare = via_capture_pcm_prepare,
+		.cleanup = via_capture_pcm_cleanup,
+		.close = via_pcm_open_close,
+	},
+};
+
+static struct hda_pcm_stream vt1716S_pcm_digital_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in via_build_pcms */
+	.ops = {
+		.open = via_dig_playback_pcm_open,
+		.close = via_dig_playback_pcm_close,
+		.prepare = via_dig_playback_pcm_prepare,
+		.cleanup = via_dig_playback_pcm_cleanup
+	},
+};
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int vt1716S_auto_fill_dac_nids(struct via_spec *spec,
+				      const struct auto_pin_cfg *cfg)
+{	int i;
+	hda_nid_t nid;
+
+	spec->multiout.num_dacs = cfg->line_outs;
+
+	spec->multiout.dac_nids = spec->private_dac_nids;
+
+	for (i = 0; i < 3; i++) {
+		nid = cfg->line_out_pins[i];
+		if (nid) {
+			/* config dac list */
+			switch (i) {
+			case AUTO_SEQ_FRONT:
+				spec->multiout.dac_nids[i] = 0x10;
+				break;
+			case AUTO_SEQ_CENLFE:
+				spec->multiout.dac_nids[i] = 0x25;
+				break;
+			case AUTO_SEQ_SURROUND:
+				spec->multiout.dac_nids[i] = 0x11;
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int vt1716S_auto_create_multi_out_ctls(struct via_spec *spec,
+					      const struct auto_pin_cfg *cfg)
+{
+	char name[32];
+	static const char *chname[3] = { "Front", "Surround", "C/LFE" };
+	hda_nid_t nid_vols[] = {0x10, 0x11, 0x25};
+	hda_nid_t nid_mutes[] = {0x1C, 0x18, 0x27};
+	hda_nid_t nid, nid_vol, nid_mute;
+	int i, err;
+
+	for (i = 0; i <= AUTO_SEQ_CENLFE; i++) {
+		nid = cfg->line_out_pins[i];
+
+		if (!nid)
+			continue;
+
+		nid_vol = nid_vols[i];
+		nid_mute = nid_mutes[i];
+
+		if (i == AUTO_SEQ_CENLFE) {
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_VOL,
+				"Center Playback Volume",
+				HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0, HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_VOL,
+				"LFE Playback Volume",
+				HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0, HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_MUTE,
+				"Center Playback Switch",
+				HDA_COMPOSE_AMP_VAL(nid_mute, 1, 0,
+						    HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_MUTE,
+				"LFE Playback Switch",
+				HDA_COMPOSE_AMP_VAL(nid_mute, 2, 0,
+						    HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else if (i == AUTO_SEQ_FRONT) {
+
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_VOL,
+				"Master Front Playback Volume",
+				HDA_COMPOSE_AMP_VAL(0x16, 3, 0, HDA_INPUT));
+			if (err < 0)
+				return err;
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_MUTE,
+				"Master Front Playback Switch",
+				HDA_COMPOSE_AMP_VAL(0x16, 3, 0, HDA_INPUT));
+			if (err < 0)
+				return err;
+
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_VOL, name,
+				HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_MUTE, name,
+				HDA_COMPOSE_AMP_VAL(nid_mute, 3, 0,
+						    HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		} else {
+			sprintf(name, "%s Playback Volume", chname[i]);
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_VOL, name,
+				HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, HDA_OUTPUT));
+			if (err < 0)
+				return err;
+			sprintf(name, "%s Playback Switch", chname[i]);
+			err = via_add_control(
+				spec, VIA_CTL_WIDGET_MUTE, name,
+				HDA_COMPOSE_AMP_VAL(nid_mute, 3, 0,
+						    HDA_OUTPUT));
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+static int vt1716S_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin)
+{
+	int err;
+
+	if (!pin)
+		return 0;
+
+	spec->multiout.hp_nid = 0x25; /* AOW3 */
+	spec->hp_independent_mode_index = 1;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Headphone Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(0x25, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+			      "Headphone Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	create_hp_imux(spec);
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int vt1716S_auto_create_analog_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	static hda_nid_t pin_idxs[] = { 0x1f, 0x1a, 0x1b, 0x1e, 0, 0xff };
+	return vt_auto_create_analog_input_ctls(codec, cfg, 0x16, pin_idxs,
+						ARRAY_SIZE(pin_idxs));
+}
+
+static int vt1716S_parse_auto_config(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int err;
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+	if (err < 0)
+		return err;
+	err = vt1716S_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0])
+		return 0; /* can't find valid BIOS pin config */
+
+	err = vt1716S_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = vt1716S_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]);
+	if (err < 0)
+		return err;
+	err = vt1716S_auto_create_analog_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	fill_dig_outs(codec);
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->input_mux = &spec->private_imux[0];
+
+	if (spec->hp_mux)
+		via_hp_build(codec);
+
+	via_smart51_build(spec);
+
+	return 1;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list vt1716S_loopbacks[] = {
+	{ 0x16, HDA_INPUT, 1 },
+	{ 0x16, HDA_INPUT, 2 },
+	{ 0x16, HDA_INPUT, 3 },
+	{ 0x16, HDA_INPUT, 4 },
+	{ } /* end */
+};
+#endif
+
+static int patch_vt1716S(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+	int err;
+
+	/* create a codec specific record */
+	spec = via_new_spec(codec);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	/* automatic parse from the BIOS config */
+	err = vt1716S_parse_auto_config(codec);
+	if (err < 0) {
+		via_free(codec);
+		return err;
+	} else if (!err) {
+		printk(KERN_INFO "hda_codec: Cannot set up configuration "
+		       "from BIOS.  Using genenic mode...\n");
+	}
+
+	spec->init_verbs[spec->num_iverbs++]  = vt1716S_volume_init_verbs;
+	spec->init_verbs[spec->num_iverbs++] = vt1716S_uniwill_init_verbs;
+
+	spec->stream_name_analog = "VT1716S Analog";
+	spec->stream_analog_playback = &vt1716S_pcm_analog_playback;
+	spec->stream_analog_capture = &vt1716S_pcm_analog_capture;
+
+	spec->stream_name_digital = "VT1716S Digital";
+	spec->stream_digital_playback = &vt1716S_pcm_digital_playback;
+
+	if (!spec->adc_nids && spec->input_mux) {
+		spec->adc_nids = vt1716S_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(vt1716S_adc_nids);
+		get_mux_nids(codec);
+		override_mic_boost(codec, 0x1a, 0, 3, 40);
+		override_mic_boost(codec, 0x1e, 0, 3, 40);
+		spec->mixers[spec->num_mixers] = vt1716S_capture_mixer;
+		spec->num_mixers++;
+	}
+
+	spec->mixers[spec->num_mixers] = vt1716s_dmic_mixer;
+	spec->num_mixers++;
+
+	spec->mixers[spec->num_mixers++] = vt1716S_mono_out_mixer;
+
+	codec->patch_ops = via_patch_ops;
+
+	codec->patch_ops.init = via_auto_init;
+	codec->patch_ops.unsol_event = via_unsol_event;
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = vt1716S_loopbacks;
+#endif
+
+	return 0;
+}
+
+/* for vt2002P */
+
+/* capture mixer elements */
+static struct snd_kcontrol_new vt2002P_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x10, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x10, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x11, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x11, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Mic Boost Capture Volume", 0x2b, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME("Front Mic Boost Capture Volume", 0x29, 0x0,
+			 HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		/* .name = "Capture Source", */
+		.name = "Input Source",
+		.count = 2,
+		.info = via_mux_enum_info,
+		.get = via_mux_enum_get,
+		.put = via_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb vt2002P_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-1 and set the default input to mic-in
+	 */
+	{0x8, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x9, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+
+	/* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 */
+	/* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+
+	/* MUX Indices: Mic = 0 */
+	{0x1e, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x1f, AC_VERB_SET_CONNECT_SEL, 0},
+
+	/* PW9 Output enable */
+	{0x2d, AC_VERB_SET_PIN_WIDGET_CONTROL, AC_PINCTL_OUT_EN},
+
+	/* Enable Boost Volume backdoor */
+	{0x1, 0xfb9, 0x24},
+
+	/* MW0/1/4/8: un-mute index 0 (MUXx), un-mute index 1 (MW9) */
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* set MUX0/1/4/8 = 0 (AOW0) */
+	{0x34, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x35, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x37, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x3b, AC_VERB_SET_CONNECT_SEL, 0},
+
+	/* set PW0 index=0 (MW0) */
+	{0x24, AC_VERB_SET_CONNECT_SEL, 0},
+
+	/* Enable AOW0 to MW9 */
+	{0x1, 0xfb8, 0x88},
+	{ }
+};
+
+
+static struct hda_verb vt2002P_uniwill_init_verbs[] = {
+	{0x25, AC_VERB_SET_UNSOLICITED_ENABLE,
+	 AC_USRSP_EN | VIA_JACK_EVENT | VIA_BIND_HP_EVENT},
+	{0x26, AC_VERB_SET_UNSOLICITED_ENABLE,
+	 AC_USRSP_EN | VIA_JACK_EVENT | VIA_BIND_HP_EVENT},
+	{0x29, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x2a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x2b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{ }
+};
+
+static struct hda_pcm_stream vt2002P_pcm_analog_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x8, /* NID to query formats and rates */
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup,
+		.close = via_pcm_open_close,
+	},
+};
+
+static struct hda_pcm_stream vt2002P_pcm_analog_capture = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x10, /* NID to query formats and rates */
+	.ops = {
+		.open = via_pcm_open_close,
+		.prepare = via_capture_pcm_prepare,
+		.cleanup = via_capture_pcm_cleanup,
+		.close = via_pcm_open_close,
+	},
+};
+
+static struct hda_pcm_stream vt2002P_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in via_build_pcms */
+	.ops = {
+		.open = via_dig_playback_pcm_open,
+		.close = via_dig_playback_pcm_close,
+		.prepare = via_dig_playback_pcm_prepare,
+		.cleanup = via_dig_playback_pcm_cleanup
+	},
+};
+
+/* fill in the dac_nids table from the parsed pin configuration */
+static int vt2002P_auto_fill_dac_nids(struct via_spec *spec,
+				      const struct auto_pin_cfg *cfg)
+{
+	spec->multiout.num_dacs = 1;
+	spec->multiout.dac_nids = spec->private_dac_nids;
+	if (cfg->line_out_pins[0])
+		spec->multiout.dac_nids[0] = 0x8;
+	return 0;
+}
+
+/* add playback controls from the parsed DAC table */
+static int vt2002P_auto_create_multi_out_ctls(struct via_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	int err;
+
+	if (!cfg->line_out_pins[0])
+		return -1;
+
+
+	/* Line-Out: PortE */
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Master Front Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(0x8, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+	err = via_add_control(spec, VIA_CTL_WIDGET_BIND_PIN_MUTE,
+			      "Master Front Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(0x26, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int vt2002P_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin)
+{
+	int err;
+
+	if (!pin)
+		return 0;
+
+	spec->multiout.hp_nid = 0x9;
+	spec->hp_independent_mode_index = 1;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Headphone Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(
+				      spec->multiout.hp_nid, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+			      "Headphone Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(0x25, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	create_hp_imux(spec);
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int vt2002P_auto_create_analog_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	struct via_spec *spec = codec->spec;
+	struct hda_input_mux *imux = &spec->private_imux[0];
+	static hda_nid_t pin_idxs[] = { 0x2b, 0x2a, 0x29, 0xff };
+	int err;
+
+	err = vt_auto_create_analog_input_ctls(codec, cfg, 0x21, pin_idxs,
+					       ARRAY_SIZE(pin_idxs));
+	if (err < 0)
+		return err;
+	/* build volume/mute control of loopback */
+	err = via_new_analog_input(spec, "Stereo Mixer", 0, 3, 0x21);
+	if (err < 0)
+		return err;
+
+	/* for digital mic select */
+	snd_hda_add_imux_item(imux, "Digital Mic", 4, NULL);
+
+	return 0;
+}
+
+static int vt2002P_parse_auto_config(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int err;
+
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+	if (err < 0)
+		return err;
+
+	err = vt2002P_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0])
+		return 0; /* can't find valid BIOS pin config */
+
+	err = vt2002P_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = vt2002P_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]);
+	if (err < 0)
+		return err;
+	err = vt2002P_auto_create_analog_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	fill_dig_outs(codec);
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->input_mux = &spec->private_imux[0];
+
+	if (spec->hp_mux)
+		via_hp_build(codec);
+
+	return 1;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list vt2002P_loopbacks[] = {
+	{ 0x21, HDA_INPUT, 0 },
+	{ 0x21, HDA_INPUT, 1 },
+	{ 0x21, HDA_INPUT, 2 },
+	{ } /* end */
+};
+#endif
+
+
+/* patch for vt2002P */
+static int patch_vt2002P(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+	int err;
+
+	/* create a codec specific record */
+	spec = via_new_spec(codec);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	/* automatic parse from the BIOS config */
+	err = vt2002P_parse_auto_config(codec);
+	if (err < 0) {
+		via_free(codec);
+		return err;
+	} else if (!err) {
+		printk(KERN_INFO "hda_codec: Cannot set up configuration "
+		       "from BIOS.  Using genenic mode...\n");
+	}
+
+	spec->init_verbs[spec->num_iverbs++]  = vt2002P_volume_init_verbs;
+	spec->init_verbs[spec->num_iverbs++] = vt2002P_uniwill_init_verbs;
+
+	spec->stream_name_analog = "VT2002P Analog";
+	spec->stream_analog_playback = &vt2002P_pcm_analog_playback;
+	spec->stream_analog_capture = &vt2002P_pcm_analog_capture;
+
+	spec->stream_name_digital = "VT2002P Digital";
+	spec->stream_digital_playback = &vt2002P_pcm_digital_playback;
+
+	if (!spec->adc_nids && spec->input_mux) {
+		spec->adc_nids = vt2002P_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(vt2002P_adc_nids);
+		get_mux_nids(codec);
+		override_mic_boost(codec, 0x2b, 0, 3, 40);
+		override_mic_boost(codec, 0x29, 0, 3, 40);
+		spec->mixers[spec->num_mixers] = vt2002P_capture_mixer;
+		spec->num_mixers++;
+	}
+
+	codec->patch_ops = via_patch_ops;
+
+	codec->patch_ops.init = via_auto_init;
+	codec->patch_ops.unsol_event = via_unsol_event;
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = vt2002P_loopbacks;
+#endif
+
+	return 0;
+}
+
+/* for vt1812 */
+
+/* capture mixer elements */
+static struct snd_kcontrol_new vt1812_capture_mixer[] = {
+	HDA_CODEC_VOLUME("Capture Volume", 0x10, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Capture Switch", 0x10, 0x0, HDA_INPUT),
+	HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x11, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x11, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Boost Capture Volume", 0x2b, 0x0, HDA_INPUT),
+	HDA_CODEC_MUTE("Front Mic Boost Capture Volume", 0x29, 0x0,
+		       HDA_INPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		/* The multiple "Capture Source" controls confuse alsamixer
+		 * So call somewhat different..
+		 */
+		.name = "Input Source",
+		.count = 2,
+		.info = via_mux_enum_info,
+		.get = via_mux_enum_get,
+		.put = via_mux_enum_put,
+	},
+	{ } /* end */
+};
+
+static struct hda_verb vt1812_volume_init_verbs[] = {
+	/*
+	 * Unmute ADC0-1 and set the default input to mic-in
+	 */
+	{0x8, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x9, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+
+
+	/* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback
+	 * mixer widget
+	 */
+	/* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
+	{0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)},
+
+	/* MUX Indices: Mic = 0 */
+	{0x1e, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x1f, AC_VERB_SET_CONNECT_SEL, 0},
+
+	/* PW9 Output enable */
+	{0x2d, AC_VERB_SET_PIN_WIDGET_CONTROL, AC_PINCTL_OUT_EN},
+
+	/* Enable Boost Volume backdoor */
+	{0x1, 0xfb9, 0x24},
+
+	/* MW0/1/4/13/15: un-mute index 0 (MUXx), un-mute index 1 (MW9) */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+	{0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
+
+	/* set MUX0/1/4/13/15 = 0 (AOW0) */
+	{0x34, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x35, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x38, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x3c, AC_VERB_SET_CONNECT_SEL, 0},
+	{0x3d, AC_VERB_SET_CONNECT_SEL, 0},
+
+	/* Enable AOW0 to MW9 */
+	{0x1, 0xfb8, 0xa8},
+	{ }
+};
+
+
+static struct hda_verb vt1812_uniwill_init_verbs[] = {
+	{0x33, AC_VERB_SET_UNSOLICITED_ENABLE,
+	 AC_USRSP_EN | VIA_JACK_EVENT | VIA_BIND_HP_EVENT},
+	{0x25, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT },
+	{0x28, AC_VERB_SET_UNSOLICITED_ENABLE,
+	 AC_USRSP_EN | VIA_JACK_EVENT | VIA_BIND_HP_EVENT},
+	{0x29, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x2a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{0x2b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_JACK_EVENT},
+	{ }
+};
+
+static struct hda_pcm_stream vt1812_pcm_analog_playback = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x8, /* NID to query formats and rates */
+	.ops = {
+		.open = via_playback_pcm_open,
+		.prepare = via_playback_multi_pcm_prepare,
+		.cleanup = via_playback_multi_pcm_cleanup,
+		.close = via_pcm_open_close,
+	},
+};
+
+static struct hda_pcm_stream vt1812_pcm_analog_capture = {
+	.substreams = 2,
+	.channels_min = 2,
+	.channels_max = 2,
+	.nid = 0x10, /* NID to query formats and rates */
+	.ops = {
+		.open = via_pcm_open_close,
+		.prepare = via_capture_pcm_prepare,
+		.cleanup = via_capture_pcm_cleanup,
+		.close = via_pcm_open_close,
+	},
+};
+
+static struct hda_pcm_stream vt1812_pcm_digital_playback = {
+	.substreams = 1,
+	.channels_min = 2,
+	.channels_max = 2,
+	/* NID is set in via_build_pcms */
+	.ops = {
+		.open = via_dig_playback_pcm_open,
+		.close = via_dig_playback_pcm_close,
+		.prepare = via_dig_playback_pcm_prepare,
+		.cleanup = via_dig_playback_pcm_cleanup
+	},
+};
+/* fill in the dac_nids table from the parsed pin configuration */
+static int vt1812_auto_fill_dac_nids(struct via_spec *spec,
+				     const struct auto_pin_cfg *cfg)
+{
+	spec->multiout.num_dacs = 1;
+	spec->multiout.dac_nids = spec->private_dac_nids;
+	if (cfg->line_out_pins[0])
+		spec->multiout.dac_nids[0] = 0x8;
+	return 0;
+}
+
+
+/* add playback controls from the parsed DAC table */
+static int vt1812_auto_create_multi_out_ctls(struct via_spec *spec,
+					     const struct auto_pin_cfg *cfg)
+{
+	int err;
+
+	if (!cfg->line_out_pins[0])
+		return -1;
+
+	/* Line-Out: PortE */
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Front Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(0x8, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+	err = via_add_control(spec, VIA_CTL_WIDGET_BIND_PIN_MUTE,
+			      "Front Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(0x28, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int vt1812_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin)
+{
+	int err;
+
+	if (!pin)
+		return 0;
+
+	spec->multiout.hp_nid = 0x9;
+	spec->hp_independent_mode_index = 1;
+
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_VOL,
+			      "Headphone Playback Volume",
+			      HDA_COMPOSE_AMP_VAL(
+				      spec->multiout.hp_nid, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	err = via_add_control(spec, VIA_CTL_WIDGET_MUTE,
+			      "Headphone Playback Switch",
+			      HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT));
+	if (err < 0)
+		return err;
+
+	create_hp_imux(spec);
+	return 0;
+}
+
+/* create playback/capture controls for input pins */
+static int vt1812_auto_create_analog_input_ctls(struct hda_codec *codec,
+						const struct auto_pin_cfg *cfg)
+{
+	struct via_spec *spec = codec->spec;
+	struct hda_input_mux *imux = &spec->private_imux[0];
+	static hda_nid_t pin_idxs[] = { 0x2b, 0x2a, 0x29, 0, 0, 0xff };
+	int err;
+
+	err = vt_auto_create_analog_input_ctls(codec, cfg, 0x21, pin_idxs,
+					       ARRAY_SIZE(pin_idxs));
+	if (err < 0)
+		return err;
+
+	/* build volume/mute control of loopback */
+	err = via_new_analog_input(spec, "Stereo Mixer", 0, 5, 0x21);
+	if (err < 0)
+		return err;
+
+	/* for digital mic select */
+	snd_hda_add_imux_item(imux, "Digital Mic", 6, NULL);
+
+	return 0;
+}
+
+static int vt1812_parse_auto_config(struct hda_codec *codec)
+{
+	struct via_spec *spec = codec->spec;
+	int err;
+
+
+	err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+	if (err < 0)
+		return err;
+	fill_dig_outs(codec);
+	err = vt1812_auto_fill_dac_nids(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	if (!spec->autocfg.line_outs && !spec->autocfg.hp_outs)
+		return 0; /* can't find valid BIOS pin config */
+
+	err = vt1812_auto_create_multi_out_ctls(spec, &spec->autocfg);
+	if (err < 0)
+		return err;
+	err = vt1812_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]);
+	if (err < 0)
+		return err;
+	err = vt1812_auto_create_analog_input_ctls(codec, &spec->autocfg);
+	if (err < 0)
+		return err;
+
+	spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+
+	fill_dig_outs(codec);
+
+	if (spec->kctls.list)
+		spec->mixers[spec->num_mixers++] = spec->kctls.list;
+
+	spec->input_mux = &spec->private_imux[0];
+
+	if (spec->hp_mux)
+		via_hp_build(codec);
+
+	return 1;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+static struct hda_amp_list vt1812_loopbacks[] = {
+	{ 0x21, HDA_INPUT, 0 },
+	{ 0x21, HDA_INPUT, 1 },
+	{ 0x21, HDA_INPUT, 2 },
+	{ } /* end */
+};
+#endif
+
+
+/* patch for vt1812 */
+static int patch_vt1812(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+	int err;
+
+	/* create a codec specific record */
+	spec = via_new_spec(codec);
+	if (spec == NULL)
+		return -ENOMEM;
+
+	/* automatic parse from the BIOS config */
+	err = vt1812_parse_auto_config(codec);
+	if (err < 0) {
+		via_free(codec);
+		return err;
+	} else if (!err) {
+		printk(KERN_INFO "hda_codec: Cannot set up configuration "
+		       "from BIOS.  Using genenic mode...\n");
+	}
+
+
+	spec->init_verbs[spec->num_iverbs++]  = vt1812_volume_init_verbs;
+	spec->init_verbs[spec->num_iverbs++] = vt1812_uniwill_init_verbs;
+
+	spec->stream_name_analog = "VT1812 Analog";
+	spec->stream_analog_playback = &vt1812_pcm_analog_playback;
+	spec->stream_analog_capture = &vt1812_pcm_analog_capture;
+
+	spec->stream_name_digital = "VT1812 Digital";
+	spec->stream_digital_playback = &vt1812_pcm_digital_playback;
+
+
+	if (!spec->adc_nids && spec->input_mux) {
+		spec->adc_nids = vt1812_adc_nids;
+		spec->num_adc_nids = ARRAY_SIZE(vt1812_adc_nids);
+		get_mux_nids(codec);
+		override_mic_boost(codec, 0x2b, 0, 3, 40);
+		override_mic_boost(codec, 0x29, 0, 3, 40);
+		spec->mixers[spec->num_mixers] = vt1812_capture_mixer;
+		spec->num_mixers++;
+	}
+
+	codec->patch_ops = via_patch_ops;
+
+	codec->patch_ops.init = via_auto_init;
+	codec->patch_ops.unsol_event = via_unsol_event;
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+	spec->loopback.amplist = vt1812_loopbacks;
+#endif
+
+	return 0;
+}
+
+/*
+ * patch entries
+ */
+static struct hda_codec_preset snd_hda_preset_via[] = {
+	{ .id = 0x11061708, .name = "VT1708", .patch = patch_vt1708},
+	{ .id = 0x11061709, .name = "VT1708", .patch = patch_vt1708},
+	{ .id = 0x1106170a, .name = "VT1708", .patch = patch_vt1708},
+	{ .id = 0x1106170b, .name = "VT1708", .patch = patch_vt1708},
+	{ .id = 0x1106e710, .name = "VT1709 10-Ch",
+	  .patch = patch_vt1709_10ch},
+	{ .id = 0x1106e711, .name = "VT1709 10-Ch",
+	  .patch = patch_vt1709_10ch},
+	{ .id = 0x1106e712, .name = "VT1709 10-Ch",
+	  .patch = patch_vt1709_10ch},
+	{ .id = 0x1106e713, .name = "VT1709 10-Ch",
+	  .patch = patch_vt1709_10ch},
+	{ .id = 0x1106e714, .name = "VT1709 6-Ch",
+	  .patch = patch_vt1709_6ch},
+	{ .id = 0x1106e715, .name = "VT1709 6-Ch",
+	  .patch = patch_vt1709_6ch},
+	{ .id = 0x1106e716, .name = "VT1709 6-Ch",
+	  .patch = patch_vt1709_6ch},
+	{ .id = 0x1106e717, .name = "VT1709 6-Ch",
+	  .patch = patch_vt1709_6ch},
+	{ .id = 0x1106e720, .name = "VT1708B 8-Ch",
+	  .patch = patch_vt1708B_8ch},
+	{ .id = 0x1106e721, .name = "VT1708B 8-Ch",
+	  .patch = patch_vt1708B_8ch},
+	{ .id = 0x1106e722, .name = "VT1708B 8-Ch",
+	  .patch = patch_vt1708B_8ch},
+	{ .id = 0x1106e723, .name = "VT1708B 8-Ch",
+	  .patch = patch_vt1708B_8ch},
+	{ .id = 0x1106e724, .name = "VT1708B 4-Ch",
+	  .patch = patch_vt1708B_4ch},
+	{ .id = 0x1106e725, .name = "VT1708B 4-Ch",
+	  .patch = patch_vt1708B_4ch},
+	{ .id = 0x1106e726, .name = "VT1708B 4-Ch",
+	  .patch = patch_vt1708B_4ch},
+	{ .id = 0x1106e727, .name = "VT1708B 4-Ch",
+	  .patch = patch_vt1708B_4ch},
+	{ .id = 0x11060397, .name = "VT1708S",
+	  .patch = patch_vt1708S},
+	{ .id = 0x11061397, .name = "VT1708S",
+	  .patch = patch_vt1708S},
+	{ .id = 0x11062397, .name = "VT1708S",
+	  .patch = patch_vt1708S},
+	{ .id = 0x11063397, .name = "VT1708S",
+	  .patch = patch_vt1708S},
+	{ .id = 0x11064397, .name = "VT1708S",
+	  .patch = patch_vt1708S},
+	{ .id = 0x11065397, .name = "VT1708S",
+	  .patch = patch_vt1708S},
+	{ .id = 0x11066397, .name = "VT1708S",
+	  .patch = patch_vt1708S},
+	{ .id = 0x11067397, .name = "VT1708S",
+	  .patch = patch_vt1708S},
+	{ .id = 0x11060398, .name = "VT1702",
+	  .patch = patch_vt1702},
+	{ .id = 0x11061398, .name = "VT1702",
+	  .patch = patch_vt1702},
+	{ .id = 0x11062398, .name = "VT1702",
+	  .patch = patch_vt1702},
+	{ .id = 0x11063398, .name = "VT1702",
+	  .patch = patch_vt1702},
+	{ .id = 0x11064398, .name = "VT1702",
+	  .patch = patch_vt1702},
+	{ .id = 0x11065398, .name = "VT1702",
+	  .patch = patch_vt1702},
+	{ .id = 0x11066398, .name = "VT1702",
+	  .patch = patch_vt1702},
+	{ .id = 0x11067398, .name = "VT1702",
+	  .patch = patch_vt1702},
+	{ .id = 0x11060428, .name = "VT1718S",
+	  .patch = patch_vt1718S},
+	{ .id = 0x11064428, .name = "VT1718S",
+	  .patch = patch_vt1718S},
+	{ .id = 0x11060441, .name = "VT2020",
+	  .patch = patch_vt1718S},
+	{ .id = 0x11064441, .name = "VT1828S",
+	  .patch = patch_vt1718S},
+	{ .id = 0x11060433, .name = "VT1716S",
+	  .patch = patch_vt1716S},
+	{ .id = 0x1106a721, .name = "VT1716S",
+	  .patch = patch_vt1716S},
+	{ .id = 0x11060438, .name = "VT2002P", .patch = patch_vt2002P},
+	{ .id = 0x11064438, .name = "VT2002P", .patch = patch_vt2002P},
+	{ .id = 0x11060448, .name = "VT1812", .patch = patch_vt1812},
+	{ .id = 0x11060440, .name = "VT1818S",
+	  .patch = patch_vt1708S},
+	{} /* terminator */
+};
+
+MODULE_ALIAS("snd-hda-codec-id:1106*");
+
+static struct hda_codec_preset_list via_list = {
+	.preset = snd_hda_preset_via,
+	.owner = THIS_MODULE,
+};
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("VIA HD-audio codec");
+
+static int __init patch_via_init(void)
+{
+	return snd_hda_add_codec_preset(&via_list);
+}
+
+static void __exit patch_via_exit(void)
+{
+	snd_hda_delete_codec_preset(&via_list);
+}
+
+module_init(patch_via_init)
+module_exit(patch_via_exit)
diff --git a/linux/sound/pci/ice1712/Makefile b/linux/sound/pci/ice1712/Makefile
new file mode 100644
index 0000000..f7ce33f
--- /dev/null
+++ b/linux/sound/pci/ice1712/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ice17xx-ak4xxx-objs := ak4xxx.o
+snd-ice1712-objs := ice1712.o delta.o hoontech.o ews.o
+snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o maya44.o quartet.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_ICE1712) += snd-ice1712.o snd-ice17xx-ak4xxx.o
+obj-$(CONFIG_SND_ICE1724) += snd-ice1724.o snd-ice17xx-ak4xxx.o
diff --git a/linux/sound/pci/ice1712/ak4xxx.c b/linux/sound/pci/ice1712/ak4xxx.c
new file mode 100644
index 0000000..90d560c
--- /dev/null
+++ b/linux/sound/pci/ice1712/ak4xxx.c
@@ -0,0 +1,195 @@
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *   AK4524 / AK4528 / AK4529 / AK4355 / AK4381 interface
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include "ice1712.h"
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ICEnsemble ICE17xx <-> AK4xxx AD/DA chip interface");
+MODULE_LICENSE("GPL");
+
+static void snd_ice1712_akm4xxx_lock(struct snd_akm4xxx *ak, int chip)
+{
+	struct snd_ice1712 *ice = ak->private_data[0];
+
+	snd_ice1712_save_gpio_status(ice);
+}
+
+static void snd_ice1712_akm4xxx_unlock(struct snd_akm4xxx *ak, int chip)
+{
+	struct snd_ice1712 *ice = ak->private_data[0];
+
+	snd_ice1712_restore_gpio_status(ice);
+}
+
+/*
+ * write AK4xxx register
+ */
+static void snd_ice1712_akm4xxx_write(struct snd_akm4xxx *ak, int chip,
+				      unsigned char addr, unsigned char data)
+{
+	unsigned int tmp;
+	int idx;
+	unsigned int addrdata;
+	struct snd_ak4xxx_private *priv = (void *)ak->private_value[0];
+	struct snd_ice1712 *ice = ak->private_data[0];
+
+	if (snd_BUG_ON(chip < 0 || chip >= 4))
+		return;
+
+	tmp = snd_ice1712_gpio_read(ice);
+	tmp |= priv->add_flags;
+	tmp &= ~priv->mask_flags;
+	if (priv->cs_mask == priv->cs_addr) {
+		if (priv->cif) {
+			tmp |= priv->cs_mask; /* start without chip select */
+		}  else {
+			tmp &= ~priv->cs_mask; /* chip select low */
+			snd_ice1712_gpio_write(ice, tmp);
+			udelay(1);
+		}
+	} else {
+		/* doesn't handle cf=1 yet */
+		tmp &= ~priv->cs_mask;
+		tmp |= priv->cs_addr;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+	}
+
+	/* build I2C address + data byte */
+	addrdata = (priv->caddr << 6) | 0x20 | (addr & 0x1f);
+	addrdata = (addrdata << 8) | data;
+	for (idx = 15; idx >= 0; idx--) {
+		/* drop clock */
+		tmp &= ~priv->clk_mask;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+		/* set data */
+		if (addrdata & (1 << idx))
+			tmp |= priv->data_mask;
+		else
+			tmp &= ~priv->data_mask;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+		/* raise clock */
+		tmp |= priv->clk_mask;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+	}
+
+	if (priv->cs_mask == priv->cs_addr) {
+		if (priv->cif) {
+			/* assert a cs pulse to trigger */
+			tmp &= ~priv->cs_mask;
+			snd_ice1712_gpio_write(ice, tmp);
+			udelay(1);
+		}
+		tmp |= priv->cs_mask; /* chip select high to trigger */
+	} else {
+		tmp &= ~priv->cs_mask;
+		tmp |= priv->cs_none; /* deselect address */
+	}
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+}
+
+/*
+ * initialize the struct snd_akm4xxx record with the template
+ */
+int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, const struct snd_akm4xxx *temp,
+			     const struct snd_ak4xxx_private *_priv, struct snd_ice1712 *ice)
+{
+	struct snd_ak4xxx_private *priv;
+
+	if (_priv != NULL) {
+		priv = kmalloc(sizeof(*priv), GFP_KERNEL);
+		if (priv == NULL)
+			return -ENOMEM;
+		*priv = *_priv;
+	} else {
+		priv = NULL;
+	}
+	*ak = *temp;
+	ak->card = ice->card;
+        ak->private_value[0] = (unsigned long)priv;
+	ak->private_data[0] = ice;
+	if (ak->ops.lock == NULL)
+		ak->ops.lock = snd_ice1712_akm4xxx_lock;
+	if (ak->ops.unlock == NULL)
+		ak->ops.unlock = snd_ice1712_akm4xxx_unlock;
+	if (ak->ops.write == NULL)
+		ak->ops.write = snd_ice1712_akm4xxx_write;
+	snd_akm4xxx_init(ak);
+	return 0;
+}
+
+void snd_ice1712_akm4xxx_free(struct snd_ice1712 *ice)
+{
+	unsigned int akidx;
+	if (ice->akm == NULL)
+		return;
+	for (akidx = 0; akidx < ice->akm_codecs; akidx++) {
+		struct snd_akm4xxx *ak = &ice->akm[akidx];
+		kfree((void*)ak->private_value[0]);
+	}
+	kfree(ice->akm);
+}
+
+/*
+ * build AK4xxx controls
+ */
+int snd_ice1712_akm4xxx_build_controls(struct snd_ice1712 *ice)
+{
+	unsigned int akidx;
+	int err;
+
+	for (akidx = 0; akidx < ice->akm_codecs; akidx++) {
+		struct snd_akm4xxx *ak = &ice->akm[akidx];
+		err = snd_akm4xxx_build_controls(ak);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int __init alsa_ice1712_akm4xxx_module_init(void)
+{
+	return 0;
+}
+        
+static void __exit alsa_ice1712_akm4xxx_module_exit(void)
+{
+}
+        
+module_init(alsa_ice1712_akm4xxx_module_init)
+module_exit(alsa_ice1712_akm4xxx_module_exit)
+
+EXPORT_SYMBOL(snd_ice1712_akm4xxx_init);
+EXPORT_SYMBOL(snd_ice1712_akm4xxx_free);
+EXPORT_SYMBOL(snd_ice1712_akm4xxx_build_controls);
diff --git a/linux/sound/pci/ice1712/amp.c b/linux/sound/pci/ice1712/amp.c
new file mode 100644
index 0000000..e328cfb
--- /dev/null
+++ b/linux/sound/pci/ice1712/amp.c
@@ -0,0 +1,95 @@
+/*
+ *   ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for Advanced Micro Peripherals Ltd AUDIO2000
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "amp.h"
+
+static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+	unsigned short cval;
+	cval = (reg << 9) | val;
+	snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff);
+}
+
+static int __devinit snd_vt1724_amp_init(struct snd_ice1712 *ice)
+{
+	static const unsigned short wm_inits[] = {
+		WM_ATTEN_L,	0x0000,	/* 0 db */
+		WM_ATTEN_R,	0x0000,	/* 0 db */
+		WM_DAC_CTRL,	0x0008,	/* 24bit I2S */
+		WM_INT_CTRL,	0x0001, /* 24bit I2S */	
+	};
+
+	unsigned int i;
+
+	/* only use basic functionality for now */
+
+	/* VT1616 6ch codec connected to PSDOUT0 using packed mode */
+	ice->num_total_dacs = 6;
+	ice->num_total_adcs = 2;
+
+	/* Chaintech AV-710 has another WM8728 codec connected to PSDOUT4
+	   (shared with the SPDIF output). Mixer control for this codec
+	   is not yet supported. */
+	if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AV710) {
+		for (i = 0; i < ARRAY_SIZE(wm_inits); i += 2)
+			wm_put(ice, wm_inits[i], wm_inits[i+1]);
+	}
+
+	return 0;
+}
+
+static int __devinit snd_vt1724_amp_add_controls(struct snd_ice1712 *ice)
+{
+	/* we use pins 39 and 41 of the VT1616 for left and right read outputs */
+	snd_ac97_write_cache(ice->ac97, 0x5a, snd_ac97_read(ice->ac97, 0x5a) & ~0x8000);
+	return 0;
+}
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_amp_cards[] __devinitdata = {
+	{
+		.subvendor = VT1724_SUBDEVICE_AV710,
+		.name = "Chaintech AV-710",
+		.model = "av710",
+		.chip_init = snd_vt1724_amp_init,
+		.build_controls = snd_vt1724_amp_add_controls,
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_AUDIO2000,
+		.name = "AMP Ltd AUDIO2000",
+		.model = "amp2000",
+		.chip_init = snd_vt1724_amp_init,
+		.build_controls = snd_vt1724_amp_add_controls,
+	},
+	{ } /* terminator */
+};
+
diff --git a/linux/sound/pci/ice1712/amp.h b/linux/sound/pci/ice1712/amp.h
new file mode 100644
index 0000000..bf81d30
--- /dev/null
+++ b/linux/sound/pci/ice1712/amp.h
@@ -0,0 +1,48 @@
+#ifndef __SOUND_AMP_H
+#define __SOUND_AMP_H
+
+/*
+ *   ALSA driver for VIA VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for Advanced Micro Peripherals Ltd AUDIO2000
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#define  AMP_AUDIO2000_DEVICE_DESC 	       "{AMP Ltd,AUDIO2000},"\
+					       "{Chaintech,AV-710},"
+
+#if 0
+#define VT1724_SUBDEVICE_AUDIO2000	0x12142417	/* Advanced Micro Peripherals Ltd AUDIO2000 */
+#else
+#define VT1724_SUBDEVICE_AUDIO2000	0x00030003	/* a dummy ID for AMP Audio2000 */
+#endif
+#define VT1724_SUBDEVICE_AV710		0x12142417	/* AV710 - the same ID with Audio2000! */
+
+/* WM8728 on I2C for AV710 */
+#define WM_DEV		0x36
+
+#define WM_ATTEN_L	0x00
+#define WM_ATTEN_R	0x01
+#define WM_DAC_CTRL	0x02
+#define WM_INT_CTRL	0x03
+
+extern struct snd_ice1712_card_info  snd_vt1724_amp_cards[];
+
+
+#endif /* __SOUND_AMP_H */
diff --git a/linux/sound/pci/ice1712/aureon.c b/linux/sound/pci/ice1712/aureon.c
new file mode 100644
index 0000000..2f62522
--- /dev/null
+++ b/linux/sound/pci/ice1712/aureon.c
@@ -0,0 +1,2308 @@
+/*
+ *   ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for Terratec Aureon cards
+ *
+ *	Copyright (c) 2003 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
+ *
+ *
+ * NOTES:
+ *
+ * - we reuse the struct snd_akm4xxx record for storing the wm8770 codec data.
+ *   both wm and akm codecs are pretty similar, so we can integrate
+ *   both controls in the future, once if wm codecs are reused in
+ *   many boards.
+ *
+ * - DAC digital volumes are not implemented in the mixer.
+ *   if they show better response than DAC analog volumes, we can use them
+ *   instead.
+ *
+ *   Lowlevel functions for AudioTrak Prodigy 7.1 (and possibly 192) cards
+ *      Copyright (c) 2003 Dimitromanolakis Apostolos <apostol@cs.utoronto.ca>
+ *
+ *   version 0.82: Stable / not all features work yet (no communication with AC97 secondary)
+ *       added 64x/128x oversampling switch (should be 64x only for 96khz)
+ *       fixed some recording labels (still need to check the rest)
+ *       recording is working probably thanks to correct wm8770 initialization
+ *
+ *   version 0.5: Initial release:
+ *           working: analog output, mixer, headphone amplifier switch
+ *       not working: prety much everything else, at least i could verify that
+ *                    we have no digital output, no capture, pretty bad clicks and poops
+ *                    on mixer switch and other coll stuff.
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "aureon.h"
+#include <sound/tlv.h>
+
+/* AC97 register cache for Aureon */
+struct aureon_spec {
+	unsigned short stac9744[64];
+	unsigned int cs8415_mux;
+	unsigned short master[2];
+	unsigned short vol[8];
+	unsigned char pca9554_out;
+};
+
+/* WM8770 registers */
+#define WM_DAC_ATTEN		0x00	/* DAC1-8 analog attenuation */
+#define WM_DAC_MASTER_ATTEN	0x08	/* DAC master analog attenuation */
+#define WM_DAC_DIG_ATTEN	0x09	/* DAC1-8 digital attenuation */
+#define WM_DAC_DIG_MASTER_ATTEN	0x11	/* DAC master digital attenuation */
+#define WM_PHASE_SWAP		0x12	/* DAC phase */
+#define WM_DAC_CTRL1		0x13	/* DAC control bits */
+#define WM_MUTE			0x14	/* mute controls */
+#define WM_DAC_CTRL2		0x15	/* de-emphasis and zefo-flag */
+#define WM_INT_CTRL		0x16	/* interface control */
+#define WM_MASTER		0x17	/* master clock and mode */
+#define WM_POWERDOWN		0x18	/* power-down controls */
+#define WM_ADC_GAIN		0x19	/* ADC gain L(19)/R(1a) */
+#define WM_ADC_MUX		0x1b	/* input MUX */
+#define WM_OUT_MUX1		0x1c	/* output MUX */
+#define WM_OUT_MUX2		0x1e	/* output MUX */
+#define WM_RESET		0x1f	/* software reset */
+
+/* CS8415A registers */
+#define CS8415_CTRL1	0x01
+#define CS8415_CTRL2	0x02
+#define CS8415_QSUB		0x14
+#define CS8415_RATIO	0x1E
+#define CS8415_C_BUFFER	0x20
+#define CS8415_ID		0x7F
+
+/* PCA9554 registers */
+#define PCA9554_DEV     0x40            /* I2C device address */
+#define PCA9554_IN      0x00            /* input port */
+#define PCA9554_OUT     0x01            /* output port */
+#define PCA9554_INVERT  0x02            /* input invert */
+#define PCA9554_DIR     0x03            /* port directions */
+
+/*
+ * Aureon Universe additional controls using PCA9554
+ */
+
+/*
+ * Send data to pca9554
+ */
+static void aureon_pca9554_write(struct snd_ice1712 *ice, unsigned char reg,
+				 unsigned char data)
+{
+	unsigned int tmp;
+	int i, j;
+	unsigned char dev = PCA9554_DEV;  /* ID 0100000, write */
+	unsigned char val = 0;
+
+	tmp = snd_ice1712_gpio_read(ice);
+
+	snd_ice1712_gpio_set_mask(ice, ~(AUREON_SPI_MOSI|AUREON_SPI_CLK|
+					 AUREON_WM_RW|AUREON_WM_CS|
+					 AUREON_CS8415_CS));
+	tmp |= AUREON_WM_RW;
+	tmp |= AUREON_CS8415_CS | AUREON_WM_CS; /* disable SPI devices */
+
+	tmp &= ~AUREON_SPI_MOSI;
+	tmp &= ~AUREON_SPI_CLK;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(50);
+
+	/*
+	 * send i2c stop condition and start condition
+	 * to obtain sane state
+	 */
+	tmp |= AUREON_SPI_CLK;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(50);
+	tmp |= AUREON_SPI_MOSI;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(100);
+	tmp &= ~AUREON_SPI_MOSI;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(50);
+	tmp &= ~AUREON_SPI_CLK;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(100);
+	/*
+	 * send device address, command and value,
+	 * skipping ack cycles inbetween
+	 */
+	for (j = 0; j < 3; j++) {
+		switch (j) {
+		case 0:
+			val = dev;
+			break;
+		case 1:
+			val = reg;
+			break;
+		case 2:
+			val = data;
+			break;
+		}
+		for (i = 7; i >= 0; i--) {
+			tmp &= ~AUREON_SPI_CLK;
+			snd_ice1712_gpio_write(ice, tmp);
+			udelay(40);
+			if (val & (1 << i))
+				tmp |= AUREON_SPI_MOSI;
+			else
+				tmp &= ~AUREON_SPI_MOSI;
+			snd_ice1712_gpio_write(ice, tmp);
+			udelay(40);
+			tmp |= AUREON_SPI_CLK;
+			snd_ice1712_gpio_write(ice, tmp);
+			udelay(40);
+		}
+		tmp &= ~AUREON_SPI_CLK;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(40);
+		tmp |= AUREON_SPI_CLK;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(40);
+		tmp &= ~AUREON_SPI_CLK;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(40);
+	}
+	tmp &= ~AUREON_SPI_CLK;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(40);
+	tmp &= ~AUREON_SPI_MOSI;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(40);
+	tmp |= AUREON_SPI_CLK;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(50);
+	tmp |= AUREON_SPI_MOSI;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(100);
+}
+
+static int aureon_universe_inmux_info(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_info *uinfo)
+{
+	char *texts[3] = {"Internal Aux", "Wavetable", "Rear Line-In"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int aureon_universe_inmux_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+	ucontrol->value.enumerated.item[0] = spec->pca9554_out;
+	return 0;
+}
+
+static int aureon_universe_inmux_put(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+	unsigned char oval, nval;
+	int change;
+
+	nval = ucontrol->value.enumerated.item[0];
+	if (nval >= 3)
+		return -EINVAL;
+	snd_ice1712_save_gpio_status(ice);
+	oval = spec->pca9554_out;
+	change = (oval != nval);
+	if (change) {
+		aureon_pca9554_write(ice, PCA9554_OUT, nval);
+		spec->pca9554_out = nval;
+	}
+	snd_ice1712_restore_gpio_status(ice);
+	return change;
+}
+
+
+static void aureon_ac97_write(struct snd_ice1712 *ice, unsigned short reg,
+			      unsigned short val)
+{
+	struct aureon_spec *spec = ice->spec;
+	unsigned int tmp;
+
+	/* Send address to XILINX chip */
+	tmp = (snd_ice1712_gpio_read(ice) & ~0xFF) | (reg & 0x7F);
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(10);
+	tmp |= AUREON_AC97_ADDR;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(10);
+	tmp &= ~AUREON_AC97_ADDR;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(10);
+
+	/* Send low-order byte to XILINX chip */
+	tmp &= ~AUREON_AC97_DATA_MASK;
+	tmp |= val & AUREON_AC97_DATA_MASK;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(10);
+	tmp |= AUREON_AC97_DATA_LOW;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(10);
+	tmp &= ~AUREON_AC97_DATA_LOW;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(10);
+
+	/* Send high-order byte to XILINX chip */
+	tmp &= ~AUREON_AC97_DATA_MASK;
+	tmp |= (val >> 8) & AUREON_AC97_DATA_MASK;
+
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(10);
+	tmp |= AUREON_AC97_DATA_HIGH;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(10);
+	tmp &= ~AUREON_AC97_DATA_HIGH;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(10);
+
+	/* Instruct XILINX chip to parse the data to the STAC9744 chip */
+	tmp |= AUREON_AC97_COMMIT;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(10);
+	tmp &= ~AUREON_AC97_COMMIT;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(10);
+
+	/* Store the data in out private buffer */
+	spec->stac9744[(reg & 0x7F) >> 1] = val;
+}
+
+static unsigned short aureon_ac97_read(struct snd_ice1712 *ice, unsigned short reg)
+{
+	struct aureon_spec *spec = ice->spec;
+	return spec->stac9744[(reg & 0x7F) >> 1];
+}
+
+/*
+ * Initialize STAC9744 chip
+ */
+static int aureon_ac97_init(struct snd_ice1712 *ice)
+{
+	struct aureon_spec *spec = ice->spec;
+	int i;
+	static const unsigned short ac97_defaults[] = {
+		0x00, 0x9640,
+		0x02, 0x8000,
+		0x04, 0x8000,
+		0x06, 0x8000,
+		0x0C, 0x8008,
+		0x0E, 0x8008,
+		0x10, 0x8808,
+		0x12, 0x8808,
+		0x14, 0x8808,
+		0x16, 0x8808,
+		0x18, 0x8808,
+		0x1C, 0x8000,
+		0x26, 0x000F,
+		0x28, 0x0201,
+		0x2C, 0xBB80,
+		0x32, 0xBB80,
+		0x7C, 0x8384,
+		0x7E, 0x7644,
+		(unsigned short)-1
+	};
+	unsigned int tmp;
+
+	/* Cold reset */
+	tmp = (snd_ice1712_gpio_read(ice) | AUREON_AC97_RESET) & ~AUREON_AC97_DATA_MASK;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(3);
+
+	tmp &= ~AUREON_AC97_RESET;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(3);
+
+	tmp |= AUREON_AC97_RESET;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(3);
+
+	memset(&spec->stac9744, 0, sizeof(spec->stac9744));
+	for (i = 0; ac97_defaults[i] != (unsigned short)-1; i += 2)
+		spec->stac9744[(ac97_defaults[i]) >> 1] = ac97_defaults[i+1];
+
+	/* Unmute AC'97 master volume permanently - muting is done by WM8770 */
+	aureon_ac97_write(ice, AC97_MASTER, 0x0000);
+
+	return 0;
+}
+
+#define AUREON_AC97_STEREO	0x80
+
+/*
+ * AC'97 volume controls
+ */
+static int aureon_ac97_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = kcontrol->private_value & AUREON_AC97_STEREO ? 2 : 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 31;
+	return 0;
+}
+
+static int aureon_ac97_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short vol;
+
+	mutex_lock(&ice->gpio_mutex);
+
+	vol = aureon_ac97_read(ice, kcontrol->private_value & 0x7F);
+	ucontrol->value.integer.value[0] = 0x1F - (vol & 0x1F);
+	if (kcontrol->private_value & AUREON_AC97_STEREO)
+		ucontrol->value.integer.value[1] = 0x1F - ((vol >> 8) & 0x1F);
+
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int aureon_ac97_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short ovol, nvol;
+	int change;
+
+	snd_ice1712_save_gpio_status(ice);
+
+	ovol = aureon_ac97_read(ice, kcontrol->private_value & 0x7F);
+	nvol = (0x1F - ucontrol->value.integer.value[0]) & 0x001F;
+	if (kcontrol->private_value & AUREON_AC97_STEREO)
+		nvol |= ((0x1F - ucontrol->value.integer.value[1]) << 8) & 0x1F00;
+	nvol |= ovol & ~0x1F1F;
+
+	change = (ovol != nvol);
+	if (change)
+		aureon_ac97_write(ice, kcontrol->private_value & 0x7F, nvol);
+
+	snd_ice1712_restore_gpio_status(ice);
+
+	return change;
+}
+
+/*
+ * AC'97 mute controls
+ */
+#define aureon_ac97_mute_info	snd_ctl_boolean_mono_info
+
+static int aureon_ac97_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ice->gpio_mutex);
+
+	ucontrol->value.integer.value[0] = aureon_ac97_read(ice,
+			kcontrol->private_value & 0x7F) & 0x8000 ? 0 : 1;
+
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int aureon_ac97_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short ovol, nvol;
+	int change;
+
+	snd_ice1712_save_gpio_status(ice);
+
+	ovol = aureon_ac97_read(ice, kcontrol->private_value & 0x7F);
+	nvol = (ucontrol->value.integer.value[0] ? 0x0000 : 0x8000) | (ovol & ~0x8000);
+
+	change = (ovol != nvol);
+	if (change)
+		aureon_ac97_write(ice, kcontrol->private_value & 0x7F, nvol);
+
+	snd_ice1712_restore_gpio_status(ice);
+
+	return change;
+}
+
+/*
+ * AC'97 mute controls
+ */
+#define aureon_ac97_micboost_info	snd_ctl_boolean_mono_info
+
+static int aureon_ac97_micboost_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ice->gpio_mutex);
+
+	ucontrol->value.integer.value[0] = aureon_ac97_read(ice, AC97_MIC) & 0x0020 ? 0 : 1;
+
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int aureon_ac97_micboost_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short ovol, nvol;
+	int change;
+
+	snd_ice1712_save_gpio_status(ice);
+
+	ovol = aureon_ac97_read(ice, AC97_MIC);
+	nvol = (ucontrol->value.integer.value[0] ? 0x0000 : 0x0020) | (ovol & ~0x0020);
+
+	change = (ovol != nvol);
+	if (change)
+		aureon_ac97_write(ice, AC97_MIC, nvol);
+
+	snd_ice1712_restore_gpio_status(ice);
+
+	return change;
+}
+
+/*
+ * write data in the SPI mode
+ */
+static void aureon_spi_write(struct snd_ice1712 *ice, unsigned int cs, unsigned int data, int bits)
+{
+	unsigned int tmp;
+	int i;
+	unsigned int mosi, clk;
+
+	tmp = snd_ice1712_gpio_read(ice);
+
+	if (ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71LT ||
+	    ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71XT) {
+		snd_ice1712_gpio_set_mask(ice, ~(PRODIGY_SPI_MOSI|PRODIGY_SPI_CLK|PRODIGY_WM_CS));
+		mosi = PRODIGY_SPI_MOSI;
+		clk = PRODIGY_SPI_CLK;
+	} else {
+		snd_ice1712_gpio_set_mask(ice, ~(AUREON_WM_RW|AUREON_SPI_MOSI|AUREON_SPI_CLK|
+						 AUREON_WM_CS|AUREON_CS8415_CS));
+		mosi = AUREON_SPI_MOSI;
+		clk = AUREON_SPI_CLK;
+
+		tmp |= AUREON_WM_RW;
+	}
+
+	tmp &= ~cs;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+
+	for (i = bits - 1; i >= 0; i--) {
+		tmp &= ~clk;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+		if (data & (1 << i))
+			tmp |= mosi;
+		else
+			tmp &= ~mosi;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+		tmp |= clk;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+	}
+
+	tmp &= ~clk;
+	tmp |= cs;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+	tmp |= clk;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+}
+
+/*
+ * Read data in SPI mode
+ */
+static void aureon_spi_read(struct snd_ice1712 *ice, unsigned int cs,
+		unsigned int data, int bits, unsigned char *buffer, int size)
+{
+	int i, j;
+	unsigned int tmp;
+
+	tmp = (snd_ice1712_gpio_read(ice) & ~AUREON_SPI_CLK) | AUREON_CS8415_CS|AUREON_WM_CS;
+	snd_ice1712_gpio_write(ice, tmp);
+	tmp &= ~cs;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+
+	for (i = bits-1; i >= 0; i--) {
+		if (data & (1 << i))
+			tmp |= AUREON_SPI_MOSI;
+		else
+			tmp &= ~AUREON_SPI_MOSI;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+
+		tmp |= AUREON_SPI_CLK;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+
+		tmp &= ~AUREON_SPI_CLK;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+	}
+
+	for (j = 0; j < size; j++) {
+		unsigned char outdata = 0;
+		for (i = 7; i >= 0; i--) {
+			tmp = snd_ice1712_gpio_read(ice);
+			outdata <<= 1;
+			outdata |= (tmp & AUREON_SPI_MISO) ? 1 : 0;
+			udelay(1);
+
+			tmp |= AUREON_SPI_CLK;
+			snd_ice1712_gpio_write(ice, tmp);
+			udelay(1);
+
+			tmp &= ~AUREON_SPI_CLK;
+			snd_ice1712_gpio_write(ice, tmp);
+			udelay(1);
+		}
+		buffer[j] = outdata;
+	}
+
+	tmp |= cs;
+	snd_ice1712_gpio_write(ice, tmp);
+}
+
+static unsigned char aureon_cs8415_get(struct snd_ice1712 *ice, int reg)
+{
+	unsigned char val;
+	aureon_spi_write(ice, AUREON_CS8415_CS, 0x2000 | reg, 16);
+	aureon_spi_read(ice, AUREON_CS8415_CS, 0x21, 8, &val, 1);
+	return val;
+}
+
+static void aureon_cs8415_read(struct snd_ice1712 *ice, int reg,
+				unsigned char *buffer, int size)
+{
+	aureon_spi_write(ice, AUREON_CS8415_CS, 0x2000 | reg, 16);
+	aureon_spi_read(ice, AUREON_CS8415_CS, 0x21, 8, buffer, size);
+}
+
+static void aureon_cs8415_put(struct snd_ice1712 *ice, int reg,
+						unsigned char val)
+{
+	aureon_spi_write(ice, AUREON_CS8415_CS, 0x200000 | (reg << 8) | val, 24);
+}
+
+/*
+ * get the current register value of WM codec
+ */
+static unsigned short wm_get(struct snd_ice1712 *ice, int reg)
+{
+	reg <<= 1;
+	return ((unsigned short)ice->akm[0].images[reg] << 8) |
+		ice->akm[0].images[reg + 1];
+}
+
+/*
+ * set the register value of WM codec
+ */
+static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+	aureon_spi_write(ice,
+			 ((ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71LT ||
+			   ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71XT) ?
+			 PRODIGY_WM_CS : AUREON_WM_CS),
+			(reg << 9) | (val & 0x1ff), 16);
+}
+
+/*
+ * set the register value of WM codec and remember it
+ */
+static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+	wm_put_nocache(ice, reg, val);
+	reg <<= 1;
+	ice->akm[0].images[reg] = val >> 8;
+	ice->akm[0].images[reg + 1] = val;
+}
+
+/*
+ */
+#define aureon_mono_bool_info		snd_ctl_boolean_mono_info
+
+/*
+ * AC'97 master playback mute controls (Mute on WM8770 chip)
+ */
+#define aureon_ac97_mmute_info		snd_ctl_boolean_mono_info
+
+static int aureon_ac97_mmute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ice->gpio_mutex);
+
+	ucontrol->value.integer.value[0] = (wm_get(ice, WM_OUT_MUX1) >> 1) & 0x01;
+
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int aureon_ac97_mmute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short ovol, nvol;
+	int change;
+
+	snd_ice1712_save_gpio_status(ice);
+
+	ovol = wm_get(ice, WM_OUT_MUX1);
+	nvol = (ovol & ~0x02) | (ucontrol->value.integer.value[0] ? 0x02 : 0x00);
+	change = (ovol != nvol);
+	if (change)
+		wm_put(ice, WM_OUT_MUX1, nvol);
+
+	snd_ice1712_restore_gpio_status(ice);
+
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_dac, -10000, 100, 1);
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_pcm, -6400, 50, 1);
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_adc, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_ac97_master, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_ac97_gain, -3450, 150, 0);
+
+#define WM_VOL_MAX	100
+#define WM_VOL_CNT	101	/* 0dB .. -100dB */
+#define WM_VOL_MUTE	0x8000
+
+static void wm_set_vol(struct snd_ice1712 *ice, unsigned int index, unsigned short vol, unsigned short master)
+{
+	unsigned char nvol;
+
+	if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE)) {
+		nvol = 0;
+	} else {
+		nvol = ((vol % WM_VOL_CNT) * (master % WM_VOL_CNT)) /
+								WM_VOL_MAX;
+		nvol += 0x1b;
+	}
+
+	wm_put(ice, index, nvol);
+	wm_put_nocache(ice, index, 0x180 | nvol);
+}
+
+/*
+ * DAC mute control
+ */
+#define wm_pcm_mute_info	snd_ctl_boolean_mono_info
+
+static int wm_pcm_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ice->gpio_mutex);
+	ucontrol->value.integer.value[0] = (wm_get(ice, WM_MUTE) & 0x10) ? 0 : 1;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_pcm_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short nval, oval;
+	int change;
+
+	snd_ice1712_save_gpio_status(ice);
+	oval = wm_get(ice, WM_MUTE);
+	nval = (oval & ~0x10) | (ucontrol->value.integer.value[0] ? 0 : 0x10);
+	change = (oval != nval);
+	if (change)
+		wm_put(ice, WM_MUTE, nval);
+	snd_ice1712_restore_gpio_status(ice);
+
+	return change;
+}
+
+/*
+ * Master volume attenuation mixer control
+ */
+static int wm_master_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = WM_VOL_MAX;
+	return 0;
+}
+
+static int wm_master_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+	int i;
+	for (i = 0; i < 2; i++)
+		ucontrol->value.integer.value[i] =
+			spec->master[i] & ~WM_VOL_MUTE;
+	return 0;
+}
+
+static int wm_master_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+	int ch, change = 0;
+
+	snd_ice1712_save_gpio_status(ice);
+	for (ch = 0; ch < 2; ch++) {
+		unsigned int vol = ucontrol->value.integer.value[ch];
+		if (vol > WM_VOL_MAX)
+			vol = WM_VOL_MAX;
+		vol |= spec->master[ch] & WM_VOL_MUTE;
+		if (vol != spec->master[ch]) {
+			int dac;
+			spec->master[ch] = vol;
+			for (dac = 0; dac < ice->num_total_dacs; dac += 2)
+				wm_set_vol(ice, WM_DAC_ATTEN + dac + ch,
+					   spec->vol[dac + ch],
+					   spec->master[ch]);
+			change = 1;
+		}
+	}
+	snd_ice1712_restore_gpio_status(ice);
+	return change;
+}
+
+/*
+ * DAC volume attenuation mixer control
+ */
+static int wm_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int voices = kcontrol->private_value >> 8;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = voices;
+	uinfo->value.integer.min = 0;		/* mute (-101dB) */
+	uinfo->value.integer.max = WM_VOL_MAX;	/* 0dB */
+	return 0;
+}
+
+static int wm_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+	int i, ofs, voices;
+
+	voices = kcontrol->private_value >> 8;
+	ofs = kcontrol->private_value & 0xff;
+	for (i = 0; i < voices; i++)
+		ucontrol->value.integer.value[i] =
+			spec->vol[ofs+i] & ~WM_VOL_MUTE;
+	return 0;
+}
+
+static int wm_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+	int i, idx, ofs, voices;
+	int change = 0;
+
+	voices = kcontrol->private_value >> 8;
+	ofs = kcontrol->private_value & 0xff;
+	snd_ice1712_save_gpio_status(ice);
+	for (i = 0; i < voices; i++) {
+		unsigned int vol = ucontrol->value.integer.value[i];
+		if (vol > WM_VOL_MAX)
+			vol = WM_VOL_MAX;
+		vol |= spec->vol[ofs+i] & WM_VOL_MUTE;
+		if (vol != spec->vol[ofs+i]) {
+			spec->vol[ofs+i] = vol;
+			idx  = WM_DAC_ATTEN + ofs + i;
+			wm_set_vol(ice, idx, spec->vol[ofs + i],
+				   spec->master[i]);
+			change = 1;
+		}
+	}
+	snd_ice1712_restore_gpio_status(ice);
+	return change;
+}
+
+/*
+ * WM8770 mute control
+ */
+static int wm_mute_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = kcontrol->private_value >> 8;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int wm_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+	int voices, ofs, i;
+
+	voices = kcontrol->private_value >> 8;
+	ofs = kcontrol->private_value & 0xFF;
+
+	for (i = 0; i < voices; i++)
+		ucontrol->value.integer.value[i] =
+			(spec->vol[ofs + i] & WM_VOL_MUTE) ? 0 : 1;
+	return 0;
+}
+
+static int wm_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+	int change = 0, voices, ofs, i;
+
+	voices = kcontrol->private_value >> 8;
+	ofs = kcontrol->private_value & 0xFF;
+
+	snd_ice1712_save_gpio_status(ice);
+	for (i = 0; i < voices; i++) {
+		int val = (spec->vol[ofs + i] & WM_VOL_MUTE) ? 0 : 1;
+		if (ucontrol->value.integer.value[i] != val) {
+			spec->vol[ofs + i] &= ~WM_VOL_MUTE;
+			spec->vol[ofs + i] |=
+				ucontrol->value.integer.value[i] ? 0 : WM_VOL_MUTE;
+			wm_set_vol(ice, ofs + i, spec->vol[ofs + i],
+				   spec->master[i]);
+			change = 1;
+		}
+	}
+	snd_ice1712_restore_gpio_status(ice);
+
+	return change;
+}
+
+/*
+ * WM8770 master mute control
+ */
+#define wm_master_mute_info		snd_ctl_boolean_stereo_info
+
+static int wm_master_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+
+	ucontrol->value.integer.value[0] =
+		(spec->master[0] & WM_VOL_MUTE) ? 0 : 1;
+	ucontrol->value.integer.value[1] =
+		(spec->master[1] & WM_VOL_MUTE) ? 0 : 1;
+	return 0;
+}
+
+static int wm_master_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+	int change = 0, i;
+
+	snd_ice1712_save_gpio_status(ice);
+	for (i = 0; i < 2; i++) {
+		int val = (spec->master[i] & WM_VOL_MUTE) ? 0 : 1;
+		if (ucontrol->value.integer.value[i] != val) {
+			int dac;
+			spec->master[i] &= ~WM_VOL_MUTE;
+			spec->master[i] |=
+				ucontrol->value.integer.value[i] ? 0 : WM_VOL_MUTE;
+			for (dac = 0; dac < ice->num_total_dacs; dac += 2)
+				wm_set_vol(ice, WM_DAC_ATTEN + dac + i,
+					   spec->vol[dac + i],
+					   spec->master[i]);
+			change = 1;
+		}
+	}
+	snd_ice1712_restore_gpio_status(ice);
+
+	return change;
+}
+
+/* digital master volume */
+#define PCM_0dB 0xff
+#define PCM_RES 128	/* -64dB */
+#define PCM_MIN (PCM_0dB - PCM_RES)
+static int wm_pcm_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;		/* mute (-64dB) */
+	uinfo->value.integer.max = PCM_RES;	/* 0dB */
+	return 0;
+}
+
+static int wm_pcm_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	mutex_lock(&ice->gpio_mutex);
+	val = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff;
+	val = val > PCM_MIN ? (val - PCM_MIN) : 0;
+	ucontrol->value.integer.value[0] = val;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_pcm_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short ovol, nvol;
+	int change = 0;
+
+	nvol = ucontrol->value.integer.value[0];
+	if (nvol > PCM_RES)
+		return -EINVAL;
+	snd_ice1712_save_gpio_status(ice);
+	nvol = (nvol ? (nvol + PCM_MIN) : 0) & 0xff;
+	ovol = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff;
+	if (ovol != nvol) {
+		wm_put(ice, WM_DAC_DIG_MASTER_ATTEN, nvol); /* prelatch */
+		wm_put_nocache(ice, WM_DAC_DIG_MASTER_ATTEN, nvol | 0x100); /* update */
+		change = 1;
+	}
+	snd_ice1712_restore_gpio_status(ice);
+	return change;
+}
+
+/*
+ * ADC mute control
+ */
+#define wm_adc_mute_info		snd_ctl_boolean_stereo_info
+
+static int wm_adc_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+	int i;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (i = 0; i < 2; i++) {
+		val = wm_get(ice, WM_ADC_GAIN + i);
+		ucontrol->value.integer.value[i] = ~val>>5 & 0x1;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_adc_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short new, old;
+	int i, change = 0;
+
+	snd_ice1712_save_gpio_status(ice);
+	for (i = 0; i < 2; i++) {
+		old = wm_get(ice, WM_ADC_GAIN + i);
+		new = (~ucontrol->value.integer.value[i]<<5&0x20) | (old&~0x20);
+		if (new != old) {
+			wm_put(ice, WM_ADC_GAIN + i, new);
+			change = 1;
+		}
+	}
+	snd_ice1712_restore_gpio_status(ice);
+
+	return change;
+}
+
+/*
+ * ADC gain mixer control
+ */
+static int wm_adc_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;		/* -12dB */
+	uinfo->value.integer.max = 0x1f;	/* 19dB */
+	return 0;
+}
+
+static int wm_adc_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int i, idx;
+	unsigned short vol;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (i = 0; i < 2; i++) {
+		idx = WM_ADC_GAIN + i;
+		vol = wm_get(ice, idx) & 0x1f;
+		ucontrol->value.integer.value[i] = vol;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_adc_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int i, idx;
+	unsigned short ovol, nvol;
+	int change = 0;
+
+	snd_ice1712_save_gpio_status(ice);
+	for (i = 0; i < 2; i++) {
+		idx  = WM_ADC_GAIN + i;
+		nvol = ucontrol->value.integer.value[i] & 0x1f;
+		ovol = wm_get(ice, idx);
+		if ((ovol & 0x1f) != nvol) {
+			wm_put(ice, idx, nvol | (ovol & ~0x1f));
+			change = 1;
+		}
+	}
+	snd_ice1712_restore_gpio_status(ice);
+	return change;
+}
+
+/*
+ * ADC input mux mixer control
+ */
+static int wm_adc_mux_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[] = {
+		"CD",		/* AIN1 */
+		"Aux",		/* AIN2 */
+		"Line",		/* AIN3 */
+		"Mic",		/* AIN4 */
+		"AC97"		/* AIN5 */
+	};
+	static const char * const universe_texts[] = {
+		"Aux1",		/* AIN1 */
+		"CD",		/* AIN2 */
+		"Phono",	/* AIN3 */
+		"Line",		/* AIN4 */
+		"Aux2",		/* AIN5 */
+		"Mic",		/* AIN6 */
+		"Aux3",		/* AIN7 */
+		"AC97"		/* AIN8 */
+	};
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 2;
+	if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON71_UNIVERSE) {
+		uinfo->value.enumerated.items = 8;
+		if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+			uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+		strcpy(uinfo->value.enumerated.name, universe_texts[uinfo->value.enumerated.item]);
+	} else {
+		uinfo->value.enumerated.items = 5;
+		if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+			uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+		strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	}
+	return 0;
+}
+
+static int wm_adc_mux_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	mutex_lock(&ice->gpio_mutex);
+	val = wm_get(ice, WM_ADC_MUX);
+	ucontrol->value.enumerated.item[0] = val & 7;
+	ucontrol->value.enumerated.item[1] = (val >> 4) & 7;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_adc_mux_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short oval, nval;
+	int change;
+
+	snd_ice1712_save_gpio_status(ice);
+	oval = wm_get(ice, WM_ADC_MUX);
+	nval = oval & ~0x77;
+	nval |= ucontrol->value.enumerated.item[0] & 7;
+	nval |= (ucontrol->value.enumerated.item[1] & 7) << 4;
+	change = (oval != nval);
+	if (change)
+		wm_put(ice, WM_ADC_MUX, nval);
+	snd_ice1712_restore_gpio_status(ice);
+	return change;
+}
+
+/*
+ * CS8415 Input mux
+ */
+static int aureon_cs8415_mux_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	static const char * const aureon_texts[] = {
+		"CD",		/* RXP0 */
+		"Optical"	/* RXP1 */
+	};
+	static const char * const prodigy_texts[] = {
+		"CD",
+		"Coax"
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	if (ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71)
+		strcpy(uinfo->value.enumerated.name, prodigy_texts[uinfo->value.enumerated.item]);
+	else
+		strcpy(uinfo->value.enumerated.name, aureon_texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int aureon_cs8415_mux_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+
+	/* snd_ice1712_save_gpio_status(ice); */
+	/* val = aureon_cs8415_get(ice, CS8415_CTRL2); */
+	ucontrol->value.enumerated.item[0] = spec->cs8415_mux;
+	/* snd_ice1712_restore_gpio_status(ice); */
+	return 0;
+}
+
+static int aureon_cs8415_mux_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct aureon_spec *spec = ice->spec;
+	unsigned short oval, nval;
+	int change;
+
+	snd_ice1712_save_gpio_status(ice);
+	oval = aureon_cs8415_get(ice, CS8415_CTRL2);
+	nval = oval & ~0x07;
+	nval |= ucontrol->value.enumerated.item[0] & 7;
+	change = (oval != nval);
+	if (change)
+		aureon_cs8415_put(ice, CS8415_CTRL2, nval);
+	snd_ice1712_restore_gpio_status(ice);
+	spec->cs8415_mux = ucontrol->value.enumerated.item[0];
+	return change;
+}
+
+static int aureon_cs8415_rate_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 192000;
+	return 0;
+}
+
+static int aureon_cs8415_rate_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char ratio;
+	ratio = aureon_cs8415_get(ice, CS8415_RATIO);
+	ucontrol->value.integer.value[0] = (int)((unsigned int)ratio * 750);
+	return 0;
+}
+
+/*
+ * CS8415A Mute
+ */
+#define aureon_cs8415_mute_info		snd_ctl_boolean_mono_info
+
+static int aureon_cs8415_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	snd_ice1712_save_gpio_status(ice);
+	ucontrol->value.integer.value[0] = (aureon_cs8415_get(ice, CS8415_CTRL1) & 0x20) ? 0 : 1;
+	snd_ice1712_restore_gpio_status(ice);
+	return 0;
+}
+
+static int aureon_cs8415_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char oval, nval;
+	int change;
+	snd_ice1712_save_gpio_status(ice);
+	oval = aureon_cs8415_get(ice, CS8415_CTRL1);
+	if (ucontrol->value.integer.value[0])
+		nval = oval & ~0x20;
+	else
+		nval = oval | 0x20;
+	change = (oval != nval);
+	if (change)
+		aureon_cs8415_put(ice, CS8415_CTRL1, nval);
+	snd_ice1712_restore_gpio_status(ice);
+	return change;
+}
+
+/*
+ * CS8415A Q-Sub info
+ */
+static int aureon_cs8415_qsub_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+	uinfo->count = 10;
+	return 0;
+}
+
+static int aureon_cs8415_qsub_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	snd_ice1712_save_gpio_status(ice);
+	aureon_cs8415_read(ice, CS8415_QSUB, ucontrol->value.bytes.data, 10);
+	snd_ice1712_restore_gpio_status(ice);
+
+	return 0;
+}
+
+static int aureon_cs8415_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int aureon_cs8415_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	memset(ucontrol->value.iec958.status, 0xFF, 24);
+	return 0;
+}
+
+static int aureon_cs8415_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	snd_ice1712_save_gpio_status(ice);
+	aureon_cs8415_read(ice, CS8415_C_BUFFER, ucontrol->value.iec958.status, 24);
+	snd_ice1712_restore_gpio_status(ice);
+	return 0;
+}
+
+/*
+ * Headphone Amplifier
+ */
+static int aureon_set_headphone_amp(struct snd_ice1712 *ice, int enable)
+{
+	unsigned int tmp, tmp2;
+
+	tmp2 = tmp = snd_ice1712_gpio_read(ice);
+	if (enable)
+		if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT &&
+		    ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT)
+			tmp |= AUREON_HP_SEL;
+		else
+			tmp |= PRODIGY_HP_SEL;
+	else
+		if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT &&
+		    ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT)
+			tmp &= ~AUREON_HP_SEL;
+		else
+			tmp &= ~PRODIGY_HP_SEL;
+	if (tmp != tmp2) {
+		snd_ice1712_gpio_write(ice, tmp);
+		return 1;
+	}
+	return 0;
+}
+
+static int aureon_get_headphone_amp(struct snd_ice1712 *ice)
+{
+	unsigned int tmp = snd_ice1712_gpio_read(ice);
+
+	return (tmp & AUREON_HP_SEL) != 0;
+}
+
+#define aureon_hpamp_info	snd_ctl_boolean_mono_info
+
+static int aureon_hpamp_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = aureon_get_headphone_amp(ice);
+	return 0;
+}
+
+
+static int aureon_hpamp_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	return aureon_set_headphone_amp(ice, ucontrol->value.integer.value[0]);
+}
+
+/*
+ * Deemphasis
+ */
+
+#define aureon_deemp_info	snd_ctl_boolean_mono_info
+
+static int aureon_deemp_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = (wm_get(ice, WM_DAC_CTRL2) & 0xf) == 0xf;
+	return 0;
+}
+
+static int aureon_deemp_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int temp, temp2;
+	temp2 = temp = wm_get(ice, WM_DAC_CTRL2);
+	if (ucontrol->value.integer.value[0])
+		temp |= 0xf;
+	else
+		temp &= ~0xf;
+	if (temp != temp2) {
+		wm_put(ice, WM_DAC_CTRL2, temp);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * ADC Oversampling
+ */
+static int aureon_oversampling_info(struct snd_kcontrol *k, struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[2] = { "128x", "64x"	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int aureon_oversampling_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = (wm_get(ice, WM_MASTER) & 0x8) == 0x8;
+	return 0;
+}
+
+static int aureon_oversampling_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	int temp, temp2;
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	temp2 = temp = wm_get(ice, WM_MASTER);
+
+	if (ucontrol->value.enumerated.item[0])
+		temp |= 0x8;
+	else
+		temp &= ~0x8;
+
+	if (temp != temp2) {
+		wm_put(ice, WM_MASTER, temp);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * mixers
+ */
+
+static struct snd_kcontrol_new aureon_dac_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = wm_master_mute_info,
+		.get = wm_master_mute_get,
+		.put = wm_master_mute_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Master Playback Volume",
+		.info = wm_master_vol_info,
+		.get = wm_master_vol_get,
+		.put = wm_master_vol_put,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Front Playback Switch",
+		.info = wm_mute_info,
+		.get = wm_mute_get,
+		.put = wm_mute_put,
+		.private_value = (2 << 8) | 0
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Front Playback Volume",
+		.info = wm_vol_info,
+		.get = wm_vol_get,
+		.put = wm_vol_put,
+		.private_value = (2 << 8) | 0,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Rear Playback Switch",
+		.info = wm_mute_info,
+		.get = wm_mute_get,
+		.put = wm_mute_put,
+		.private_value = (2 << 8) | 2
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Rear Playback Volume",
+		.info = wm_vol_info,
+		.get = wm_vol_get,
+		.put = wm_vol_put,
+		.private_value = (2 << 8) | 2,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Center Playback Switch",
+		.info = wm_mute_info,
+		.get = wm_mute_get,
+		.put = wm_mute_put,
+		.private_value = (1 << 8) | 4
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Center Playback Volume",
+		.info = wm_vol_info,
+		.get = wm_vol_get,
+		.put = wm_vol_put,
+		.private_value = (1 << 8) | 4,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "LFE Playback Switch",
+		.info = wm_mute_info,
+		.get = wm_mute_get,
+		.put = wm_mute_put,
+		.private_value = (1 << 8) | 5
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "LFE Playback Volume",
+		.info = wm_vol_info,
+		.get = wm_vol_get,
+		.put = wm_vol_put,
+		.private_value = (1 << 8) | 5,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Side Playback Switch",
+		.info = wm_mute_info,
+		.get = wm_mute_get,
+		.put = wm_mute_put,
+		.private_value = (2 << 8) | 6
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Side Playback Volume",
+		.info = wm_vol_info,
+		.get = wm_vol_get,
+		.put = wm_vol_put,
+		.private_value = (2 << 8) | 6,
+		.tlv = { .p = db_scale_wm_dac }
+	}
+};
+
+static struct snd_kcontrol_new wm_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "PCM Playback Switch",
+		.info = wm_pcm_mute_info,
+		.get = wm_pcm_mute_get,
+		.put = wm_pcm_mute_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "PCM Playback Volume",
+		.info = wm_pcm_vol_info,
+		.get = wm_pcm_vol_get,
+		.put = wm_pcm_vol_put,
+		.tlv = { .p = db_scale_wm_pcm }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Switch",
+		.info = wm_adc_mute_info,
+		.get = wm_adc_mute_get,
+		.put = wm_adc_mute_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Capture Volume",
+		.info = wm_adc_vol_info,
+		.get = wm_adc_vol_get,
+		.put = wm_adc_vol_put,
+		.tlv = { .p = db_scale_wm_adc }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Capture Source",
+		.info = wm_adc_mux_info,
+		.get = wm_adc_mux_get,
+		.put = wm_adc_mux_put,
+		.private_value = 5
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "External Amplifier",
+		.info = aureon_hpamp_info,
+		.get = aureon_hpamp_get,
+		.put = aureon_hpamp_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "DAC Deemphasis Switch",
+		.info = aureon_deemp_info,
+		.get = aureon_deemp_get,
+		.put = aureon_deemp_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "ADC Oversampling",
+		.info = aureon_oversampling_info,
+		.get = aureon_oversampling_get,
+		.put = aureon_oversampling_put
+	}
+};
+
+static struct snd_kcontrol_new ac97_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "AC97 Playback Switch",
+		.info = aureon_ac97_mmute_info,
+		.get = aureon_ac97_mmute_get,
+		.put = aureon_ac97_mmute_put,
+		.private_value = AC97_MASTER
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "AC97 Playback Volume",
+		.info = aureon_ac97_vol_info,
+		.get = aureon_ac97_vol_get,
+		.put = aureon_ac97_vol_put,
+		.private_value = AC97_MASTER|AUREON_AC97_STEREO,
+		.tlv = { .p = db_scale_ac97_master }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "CD Playback Switch",
+		.info = aureon_ac97_mute_info,
+		.get = aureon_ac97_mute_get,
+		.put = aureon_ac97_mute_put,
+		.private_value = AC97_CD
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "CD Playback Volume",
+		.info = aureon_ac97_vol_info,
+		.get = aureon_ac97_vol_get,
+		.put = aureon_ac97_vol_put,
+		.private_value = AC97_CD|AUREON_AC97_STEREO,
+		.tlv = { .p = db_scale_ac97_gain }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Aux Playback Switch",
+		.info = aureon_ac97_mute_info,
+		.get = aureon_ac97_mute_get,
+		.put = aureon_ac97_mute_put,
+		.private_value = AC97_AUX,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Aux Playback Volume",
+		.info = aureon_ac97_vol_info,
+		.get = aureon_ac97_vol_get,
+		.put = aureon_ac97_vol_put,
+		.private_value = AC97_AUX|AUREON_AC97_STEREO,
+		.tlv = { .p = db_scale_ac97_gain }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Line Playback Switch",
+		.info = aureon_ac97_mute_info,
+		.get = aureon_ac97_mute_get,
+		.put = aureon_ac97_mute_put,
+		.private_value = AC97_LINE
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Line Playback Volume",
+		.info = aureon_ac97_vol_info,
+		.get = aureon_ac97_vol_get,
+		.put = aureon_ac97_vol_put,
+		.private_value = AC97_LINE|AUREON_AC97_STEREO,
+		.tlv = { .p = db_scale_ac97_gain }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Mic Playback Switch",
+		.info = aureon_ac97_mute_info,
+		.get = aureon_ac97_mute_get,
+		.put = aureon_ac97_mute_put,
+		.private_value = AC97_MIC
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Mic Playback Volume",
+		.info = aureon_ac97_vol_info,
+		.get = aureon_ac97_vol_get,
+		.put = aureon_ac97_vol_put,
+		.private_value = AC97_MIC,
+		.tlv = { .p = db_scale_ac97_gain }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Mic Boost (+20dB)",
+		.info = aureon_ac97_micboost_info,
+		.get = aureon_ac97_micboost_get,
+		.put = aureon_ac97_micboost_put
+	}
+};
+
+static struct snd_kcontrol_new universe_ac97_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "AC97 Playback Switch",
+		.info = aureon_ac97_mmute_info,
+		.get = aureon_ac97_mmute_get,
+		.put = aureon_ac97_mmute_put,
+		.private_value = AC97_MASTER
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "AC97 Playback Volume",
+		.info = aureon_ac97_vol_info,
+		.get = aureon_ac97_vol_get,
+		.put = aureon_ac97_vol_put,
+		.private_value = AC97_MASTER|AUREON_AC97_STEREO,
+		.tlv = { .p = db_scale_ac97_master }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "CD Playback Switch",
+		.info = aureon_ac97_mute_info,
+		.get = aureon_ac97_mute_get,
+		.put = aureon_ac97_mute_put,
+		.private_value = AC97_AUX
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "CD Playback Volume",
+		.info = aureon_ac97_vol_info,
+		.get = aureon_ac97_vol_get,
+		.put = aureon_ac97_vol_put,
+		.private_value = AC97_AUX|AUREON_AC97_STEREO,
+		.tlv = { .p = db_scale_ac97_gain }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Phono Playback Switch",
+		.info = aureon_ac97_mute_info,
+		.get = aureon_ac97_mute_get,
+		.put = aureon_ac97_mute_put,
+		.private_value = AC97_CD
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Phono Playback Volume",
+		.info = aureon_ac97_vol_info,
+		.get = aureon_ac97_vol_get,
+		.put = aureon_ac97_vol_put,
+		.private_value = AC97_CD|AUREON_AC97_STEREO,
+		.tlv = { .p = db_scale_ac97_gain }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Line Playback Switch",
+		.info = aureon_ac97_mute_info,
+		.get = aureon_ac97_mute_get,
+		.put = aureon_ac97_mute_put,
+		.private_value = AC97_LINE
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Line Playback Volume",
+		.info = aureon_ac97_vol_info,
+		.get = aureon_ac97_vol_get,
+		.put = aureon_ac97_vol_put,
+		.private_value = AC97_LINE|AUREON_AC97_STEREO,
+		.tlv = { .p = db_scale_ac97_gain }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Mic Playback Switch",
+		.info = aureon_ac97_mute_info,
+		.get = aureon_ac97_mute_get,
+		.put = aureon_ac97_mute_put,
+		.private_value = AC97_MIC
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Mic Playback Volume",
+		.info = aureon_ac97_vol_info,
+		.get = aureon_ac97_vol_get,
+		.put = aureon_ac97_vol_put,
+		.private_value = AC97_MIC,
+		.tlv = { .p = db_scale_ac97_gain }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Mic Boost (+20dB)",
+		.info = aureon_ac97_micboost_info,
+		.get = aureon_ac97_micboost_get,
+		.put = aureon_ac97_micboost_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Aux Playback Switch",
+		.info = aureon_ac97_mute_info,
+		.get = aureon_ac97_mute_get,
+		.put = aureon_ac97_mute_put,
+		.private_value = AC97_VIDEO,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Aux Playback Volume",
+		.info = aureon_ac97_vol_info,
+		.get = aureon_ac97_vol_get,
+		.put = aureon_ac97_vol_put,
+		.private_value = AC97_VIDEO|AUREON_AC97_STEREO,
+		.tlv = { .p = db_scale_ac97_gain }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Aux Source",
+		.info = aureon_universe_inmux_info,
+		.get = aureon_universe_inmux_get,
+		.put = aureon_universe_inmux_put
+	}
+
+};
+
+static struct snd_kcontrol_new cs8415_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, SWITCH),
+		.info = aureon_cs8415_mute_info,
+		.get = aureon_cs8415_mute_get,
+		.put = aureon_cs8415_mute_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Source",
+		.info = aureon_cs8415_mux_info,
+		.get = aureon_cs8415_mux_get,
+		.put = aureon_cs8415_mux_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = SNDRV_CTL_NAME_IEC958("Q-subcode ", CAPTURE, DEFAULT),
+		.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = aureon_cs8415_qsub_info,
+		.get = aureon_cs8415_qsub_get,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK),
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.info = aureon_cs8415_spdif_info,
+		.get = aureon_cs8415_mask_get
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+		.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = aureon_cs8415_spdif_info,
+		.get = aureon_cs8415_spdif_get
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Rate",
+		.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = aureon_cs8415_rate_info,
+		.get = aureon_cs8415_rate_get
+	}
+};
+
+static int __devinit aureon_add_controls(struct snd_ice1712 *ice)
+{
+	unsigned int i, counts;
+	int err;
+
+	counts = ARRAY_SIZE(aureon_dac_controls);
+	if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON51_SKY)
+		counts -= 2; /* no side */
+	for (i = 0; i < counts; i++) {
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&aureon_dac_controls[i], ice));
+		if (err < 0)
+			return err;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm_controls); i++) {
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&wm_controls[i], ice));
+		if (err < 0)
+			return err;
+	}
+
+	if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON71_UNIVERSE) {
+		for (i = 0; i < ARRAY_SIZE(universe_ac97_controls); i++) {
+			err = snd_ctl_add(ice->card, snd_ctl_new1(&universe_ac97_controls[i], ice));
+			if (err < 0)
+				return err;
+		}
+	} else if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT &&
+		 ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT) {
+		for (i = 0; i < ARRAY_SIZE(ac97_controls); i++) {
+			err = snd_ctl_add(ice->card, snd_ctl_new1(&ac97_controls[i], ice));
+			if (err < 0)
+				return err;
+		}
+	}
+
+	if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT &&
+	    ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT) {
+		unsigned char id;
+		snd_ice1712_save_gpio_status(ice);
+		id = aureon_cs8415_get(ice, CS8415_ID);
+		if (id != 0x41)
+			snd_printk(KERN_INFO "No CS8415 chip. Skipping CS8415 controls.\n");
+		else if ((id & 0x0F) != 0x01)
+			snd_printk(KERN_INFO "Detected unsupported CS8415 rev. (%c)\n", (char)((id & 0x0F) + 'A' - 1));
+		else {
+			for (i = 0; i < ARRAY_SIZE(cs8415_controls); i++) {
+				struct snd_kcontrol *kctl;
+				err = snd_ctl_add(ice->card, (kctl = snd_ctl_new1(&cs8415_controls[i], ice)));
+				if (err < 0)
+					return err;
+				if (i > 1)
+					kctl->id.device = ice->pcm->device;
+			}
+		}
+		snd_ice1712_restore_gpio_status(ice);
+	}
+
+	return 0;
+}
+
+/*
+ * reset the chip
+ */
+static int aureon_reset(struct snd_ice1712 *ice)
+{
+	static const unsigned short wm_inits_aureon[] = {
+		/* These come first to reduce init pop noise */
+		0x1b, 0x044,		/* ADC Mux (AC'97 source) */
+		0x1c, 0x00B,		/* Out Mux1 (VOUT1 = DAC+AUX, VOUT2 = DAC) */
+		0x1d, 0x009,		/* Out Mux2 (VOUT2 = DAC, VOUT3 = DAC) */
+
+		0x18, 0x000,		/* All power-up */
+
+		0x16, 0x122,		/* I2S, normal polarity, 24bit */
+		0x17, 0x022,		/* 256fs, slave mode */
+		0x00, 0,		/* DAC1 analog mute */
+		0x01, 0,		/* DAC2 analog mute */
+		0x02, 0,		/* DAC3 analog mute */
+		0x03, 0,		/* DAC4 analog mute */
+		0x04, 0,		/* DAC5 analog mute */
+		0x05, 0,		/* DAC6 analog mute */
+		0x06, 0,		/* DAC7 analog mute */
+		0x07, 0,		/* DAC8 analog mute */
+		0x08, 0x100,		/* master analog mute */
+		0x09, 0xff,		/* DAC1 digital full */
+		0x0a, 0xff,		/* DAC2 digital full */
+		0x0b, 0xff,		/* DAC3 digital full */
+		0x0c, 0xff,		/* DAC4 digital full */
+		0x0d, 0xff,		/* DAC5 digital full */
+		0x0e, 0xff,		/* DAC6 digital full */
+		0x0f, 0xff,		/* DAC7 digital full */
+		0x10, 0xff,		/* DAC8 digital full */
+		0x11, 0x1ff,		/* master digital full */
+		0x12, 0x000,		/* phase normal */
+		0x13, 0x090,		/* unmute DAC L/R */
+		0x14, 0x000,		/* all unmute */
+		0x15, 0x000,		/* no deemphasis, no ZFLG */
+		0x19, 0x000,		/* -12dB ADC/L */
+		0x1a, 0x000,		/* -12dB ADC/R */
+		(unsigned short)-1
+	};
+	static const unsigned short wm_inits_prodigy[] = {
+
+		/* These come first to reduce init pop noise */
+		0x1b, 0x000,		/* ADC Mux */
+		0x1c, 0x009,		/* Out Mux1 */
+		0x1d, 0x009,		/* Out Mux2 */
+
+		0x18, 0x000,		/* All power-up */
+
+		0x16, 0x022,		/* I2S, normal polarity, 24bit, high-pass on */
+		0x17, 0x006,		/* 128fs, slave mode */
+
+		0x00, 0,		/* DAC1 analog mute */
+		0x01, 0,		/* DAC2 analog mute */
+		0x02, 0,		/* DAC3 analog mute */
+		0x03, 0,		/* DAC4 analog mute */
+		0x04, 0,		/* DAC5 analog mute */
+		0x05, 0,		/* DAC6 analog mute */
+		0x06, 0,		/* DAC7 analog mute */
+		0x07, 0,		/* DAC8 analog mute */
+		0x08, 0x100,		/* master analog mute */
+
+		0x09, 0x7f,		/* DAC1 digital full */
+		0x0a, 0x7f,		/* DAC2 digital full */
+		0x0b, 0x7f,		/* DAC3 digital full */
+		0x0c, 0x7f,		/* DAC4 digital full */
+		0x0d, 0x7f,		/* DAC5 digital full */
+		0x0e, 0x7f,		/* DAC6 digital full */
+		0x0f, 0x7f,		/* DAC7 digital full */
+		0x10, 0x7f,		/* DAC8 digital full */
+		0x11, 0x1FF,		/* master digital full */
+
+		0x12, 0x000,		/* phase normal */
+		0x13, 0x090,		/* unmute DAC L/R */
+		0x14, 0x000,		/* all unmute */
+		0x15, 0x000,		/* no deemphasis, no ZFLG */
+
+		0x19, 0x000,		/* -12dB ADC/L */
+		0x1a, 0x000,		/* -12dB ADC/R */
+		(unsigned short)-1
+
+	};
+	static const unsigned short cs_inits[] = {
+		0x0441, /* RUN */
+		0x0180, /* no mute, OMCK output on RMCK pin */
+		0x0201, /* S/PDIF source on RXP1 */
+		0x0605, /* slave, 24bit, MSB on second OSCLK, SDOUT for right channel when OLRCK is high */
+		(unsigned short)-1
+	};
+	unsigned int tmp;
+	const unsigned short *p;
+	int err;
+	struct aureon_spec *spec = ice->spec;
+
+	err = aureon_ac97_init(ice);
+	if (err != 0)
+		return err;
+
+	snd_ice1712_gpio_set_dir(ice, 0x5fffff); /* fix this for the time being */
+
+	/* reset the wm codec as the SPI mode */
+	snd_ice1712_save_gpio_status(ice);
+	snd_ice1712_gpio_set_mask(ice, ~(AUREON_WM_RESET|AUREON_WM_CS|AUREON_CS8415_CS|AUREON_HP_SEL));
+
+	tmp = snd_ice1712_gpio_read(ice);
+	tmp &= ~AUREON_WM_RESET;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+	tmp |= AUREON_WM_CS | AUREON_CS8415_CS;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+	tmp |= AUREON_WM_RESET;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+
+	/* initialize WM8770 codec */
+	if (ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71 ||
+		ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71LT ||
+		ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71XT)
+		p = wm_inits_prodigy;
+	else
+		p = wm_inits_aureon;
+	for (; *p != (unsigned short)-1; p += 2)
+		wm_put(ice, p[0], p[1]);
+
+	/* initialize CS8415A codec */
+	if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT &&
+	    ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT) {
+		for (p = cs_inits; *p != (unsigned short)-1; p++)
+			aureon_spi_write(ice, AUREON_CS8415_CS, *p | 0x200000, 24);
+		spec->cs8415_mux = 1;
+
+		aureon_set_headphone_amp(ice, 1);
+	}
+
+	snd_ice1712_restore_gpio_status(ice);
+
+	/* initialize PCA9554 pin directions & set default input */
+	aureon_pca9554_write(ice, PCA9554_DIR, 0x00);
+	aureon_pca9554_write(ice, PCA9554_OUT, 0x00);   /* internal AUX */
+	return 0;
+}
+
+/*
+ * suspend/resume
+ */
+#ifdef CONFIG_PM
+static int aureon_resume(struct snd_ice1712 *ice)
+{
+	struct aureon_spec *spec = ice->spec;
+	int err, i;
+
+	err = aureon_reset(ice);
+	if (err != 0)
+		return err;
+
+	/* workaround for poking volume with alsamixer after resume:
+	 * just set stored volume again */
+	for (i = 0; i < ice->num_total_dacs; i++)
+		wm_set_vol(ice, i, spec->vol[i], spec->master[i % 2]);
+	return 0;
+}
+#endif
+
+/*
+ * initialize the chip
+ */
+static int __devinit aureon_init(struct snd_ice1712 *ice)
+{
+	struct aureon_spec *spec;
+	int i, err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	ice->spec = spec;
+
+	if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON51_SKY) {
+		ice->num_total_dacs = 6;
+		ice->num_total_adcs = 2;
+	} else {
+		/* aureon 7.1 and prodigy 7.1 */
+		ice->num_total_dacs = 8;
+		ice->num_total_adcs = 2;
+	}
+
+	/* to remeber the register values of CS8415 */
+	ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+	if (!ice->akm)
+		return -ENOMEM;
+	ice->akm_codecs = 1;
+
+	err = aureon_reset(ice);
+	if (err != 0)
+		return err;
+
+	spec->master[0] = WM_VOL_MUTE;
+	spec->master[1] = WM_VOL_MUTE;
+	for (i = 0; i < ice->num_total_dacs; i++) {
+		spec->vol[i] = WM_VOL_MUTE;
+		wm_set_vol(ice, i, spec->vol[i], spec->master[i % 2]);
+	}
+
+#ifdef CONFIG_PM
+	ice->pm_resume = aureon_resume;
+	ice->pm_suspend_enabled = 1;
+#endif
+
+	return 0;
+}
+
+
+/*
+ * Aureon boards don't provide the EEPROM data except for the vendor IDs.
+ * hence the driver needs to sets up it properly.
+ */
+
+static unsigned char aureon51_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x0a,	/* clock 512, spdif-in/ADC, 3DACs */
+	[ICE_EEP2_ACLINK]      = 0x80,	/* I2S */
+	[ICE_EEP2_I2S]         = 0xfc,	/* vol, 96k, 24bit, 192k */
+	[ICE_EEP2_SPDIF]       = 0xc3,	/* out-en, out-int, spdif-in */
+	[ICE_EEP2_GPIO_DIR]    = 0xff,
+	[ICE_EEP2_GPIO_DIR1]   = 0xff,
+	[ICE_EEP2_GPIO_DIR2]   = 0x5f,
+	[ICE_EEP2_GPIO_MASK]   = 0x00,
+	[ICE_EEP2_GPIO_MASK1]  = 0x00,
+	[ICE_EEP2_GPIO_MASK2]  = 0x00,
+	[ICE_EEP2_GPIO_STATE]  = 0x00,
+	[ICE_EEP2_GPIO_STATE1] = 0x00,
+	[ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+static unsigned char aureon71_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x0b,	/* clock 512, spdif-in/ADC, 4DACs */
+	[ICE_EEP2_ACLINK]      = 0x80,	/* I2S */
+	[ICE_EEP2_I2S]         = 0xfc,	/* vol, 96k, 24bit, 192k */
+	[ICE_EEP2_SPDIF]       = 0xc3,	/* out-en, out-int, spdif-in */
+	[ICE_EEP2_GPIO_DIR]    = 0xff,
+	[ICE_EEP2_GPIO_DIR1]   = 0xff,
+	[ICE_EEP2_GPIO_DIR2]   = 0x5f,
+	[ICE_EEP2_GPIO_MASK]   = 0x00,
+	[ICE_EEP2_GPIO_MASK1]  = 0x00,
+	[ICE_EEP2_GPIO_MASK2]  = 0x00,
+	[ICE_EEP2_GPIO_STATE]  = 0x00,
+	[ICE_EEP2_GPIO_STATE1] = 0x00,
+	[ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+#define prodigy71_eeprom aureon71_eeprom
+
+static unsigned char aureon71_universe_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x2b,	/* clock 512, mpu401, spdif-in/ADC,
+					 * 4DACs
+					 */
+	[ICE_EEP2_ACLINK]      = 0x80,	/* I2S */
+	[ICE_EEP2_I2S]         = 0xfc,	/* vol, 96k, 24bit, 192k */
+	[ICE_EEP2_SPDIF]       = 0xc3,	/* out-en, out-int, spdif-in */
+	[ICE_EEP2_GPIO_DIR]    = 0xff,
+	[ICE_EEP2_GPIO_DIR1]   = 0xff,
+	[ICE_EEP2_GPIO_DIR2]   = 0x5f,
+	[ICE_EEP2_GPIO_MASK]   = 0x00,
+	[ICE_EEP2_GPIO_MASK1]  = 0x00,
+	[ICE_EEP2_GPIO_MASK2]  = 0x00,
+	[ICE_EEP2_GPIO_STATE]  = 0x00,
+	[ICE_EEP2_GPIO_STATE1] = 0x00,
+	[ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+static unsigned char prodigy71lt_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x4b,	/* clock 384, spdif-in/ADC, 4DACs */
+	[ICE_EEP2_ACLINK]      = 0x80,	/* I2S */
+	[ICE_EEP2_I2S]         = 0xfc,	/* vol, 96k, 24bit, 192k */
+	[ICE_EEP2_SPDIF]       = 0xc3,	/* out-en, out-int, spdif-in */
+	[ICE_EEP2_GPIO_DIR]    = 0xff,
+	[ICE_EEP2_GPIO_DIR1]   = 0xff,
+	[ICE_EEP2_GPIO_DIR2]   = 0x5f,
+	[ICE_EEP2_GPIO_MASK]   = 0x00,
+	[ICE_EEP2_GPIO_MASK1]  = 0x00,
+	[ICE_EEP2_GPIO_MASK2]  = 0x00,
+	[ICE_EEP2_GPIO_STATE]  = 0x00,
+	[ICE_EEP2_GPIO_STATE1] = 0x00,
+	[ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+#define prodigy71xt_eeprom prodigy71lt_eeprom
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_aureon_cards[] __devinitdata = {
+	{
+		.subvendor = VT1724_SUBDEVICE_AUREON51_SKY,
+		.name = "Terratec Aureon 5.1-Sky",
+		.model = "aureon51",
+		.chip_init = aureon_init,
+		.build_controls = aureon_add_controls,
+		.eeprom_size = sizeof(aureon51_eeprom),
+		.eeprom_data = aureon51_eeprom,
+		.driver = "Aureon51",
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_AUREON71_SPACE,
+		.name = "Terratec Aureon 7.1-Space",
+		.model = "aureon71",
+		.chip_init = aureon_init,
+		.build_controls = aureon_add_controls,
+		.eeprom_size = sizeof(aureon71_eeprom),
+		.eeprom_data = aureon71_eeprom,
+		.driver = "Aureon71",
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_AUREON71_UNIVERSE,
+		.name = "Terratec Aureon 7.1-Universe",
+		.model = "universe",
+		.chip_init = aureon_init,
+		.build_controls = aureon_add_controls,
+		.eeprom_size = sizeof(aureon71_universe_eeprom),
+		.eeprom_data = aureon71_universe_eeprom,
+		.driver = "Aureon71Univ", /* keep in 15 letters */
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_PRODIGY71,
+		.name = "Audiotrak Prodigy 7.1",
+		.model = "prodigy71",
+		.chip_init = aureon_init,
+		.build_controls = aureon_add_controls,
+		.eeprom_size = sizeof(prodigy71_eeprom),
+		.eeprom_data = prodigy71_eeprom,
+		.driver = "Prodigy71", /* should be identical with Aureon71 */
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_PRODIGY71LT,
+		.name = "Audiotrak Prodigy 7.1 LT",
+		.model = "prodigy71lt",
+		.chip_init = aureon_init,
+		.build_controls = aureon_add_controls,
+		.eeprom_size = sizeof(prodigy71lt_eeprom),
+		.eeprom_data = prodigy71lt_eeprom,
+		.driver = "Prodigy71LT",
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_PRODIGY71XT,
+		.name = "Audiotrak Prodigy 7.1 XT",
+		.model = "prodigy71xt",
+		.chip_init = aureon_init,
+		.build_controls = aureon_add_controls,
+		.eeprom_size = sizeof(prodigy71xt_eeprom),
+		.eeprom_data = prodigy71xt_eeprom,
+		.driver = "Prodigy71LT",
+	},
+	{ } /* terminator */
+};
diff --git a/linux/sound/pci/ice1712/aureon.h b/linux/sound/pci/ice1712/aureon.h
new file mode 100644
index 0000000..c253b8e
--- /dev/null
+++ b/linux/sound/pci/ice1712/aureon.h
@@ -0,0 +1,65 @@
+#ifndef __SOUND_AUREON_H
+#define __SOUND_AUREON_H
+
+/*
+ *   ALSA driver for VIA VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for Terratec Aureon cards
+ *
+ *	Copyright (c) 2003 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
+ *
+ */      
+
+#define  AUREON_DEVICE_DESC 	       "{Terratec,Aureon 5.1 Sky},"\
+				       "{Terratec,Aureon 7.1 Space},"\
+				       "{Terratec,Aureon 7.1 Universe}," \
+					"{AudioTrak,Prodigy 7.1}," \
+					"{AudioTrak,Prodigy 7.1 LT},"\
+					"{AudioTrak,Prodigy 7.1 XT},"
+
+#define VT1724_SUBDEVICE_AUREON51_SKY	0x3b154711	/* Aureon 5.1 Sky */
+#define VT1724_SUBDEVICE_AUREON71_SPACE	0x3b154511	/* Aureon 7.1 Space */
+#define VT1724_SUBDEVICE_AUREON71_UNIVERSE	0x3b155311	/* Aureon 7.1 Universe */
+#define VT1724_SUBDEVICE_PRODIGY71	0x33495345	/* PRODIGY 7.1 */
+#define VT1724_SUBDEVICE_PRODIGY71LT	0x32315441	/* PRODIGY 7.1 LT */
+#define VT1724_SUBDEVICE_PRODIGY71XT	0x36315441	/* PRODIGY 7.1 XT*/
+
+extern struct snd_ice1712_card_info  snd_vt1724_aureon_cards[];
+
+/* GPIO bits */
+#define AUREON_CS8415_CS	(1 << 22)
+#define AUREON_SPI_MISO		(1 << 21)
+#define AUREON_WM_RESET		(1 << 20)
+#define AUREON_SPI_CLK		(1 << 19)
+#define AUREON_SPI_MOSI		(1 << 18)
+#define AUREON_WM_RW		(1 << 17)
+#define AUREON_AC97_RESET	(1 << 16)
+#define AUREON_DIGITAL_SEL1	(1 << 15)
+#define AUREON_HP_SEL		(1 << 14)
+#define AUREON_WM_CS		(1 << 12)
+#define AUREON_AC97_COMMIT	(1 << 11)
+#define AUREON_AC97_ADDR	(1 << 10)
+#define AUREON_AC97_DATA_LOW	(1 << 9)
+#define AUREON_AC97_DATA_HIGH	(1 << 8)
+#define AUREON_AC97_DATA_MASK	0xFF
+
+#define PRODIGY_WM_CS		(1 << 8)
+#define PRODIGY_SPI_MOSI	(1 << 10)
+#define PRODIGY_SPI_CLK		(1 << 9)
+#define PRODIGY_HP_SEL		(1 << 5)
+
+#endif /* __SOUND_AUREON_H */
diff --git a/linux/sound/pci/ice1712/delta.c b/linux/sound/pci/ice1712/delta.c
new file mode 100644
index 0000000..712c171
--- /dev/null
+++ b/linux/sound/pci/ice1712/delta.c
@@ -0,0 +1,827 @@
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *   Lowlevel functions for M-Audio Delta 1010, 1010E, 44, 66, 66E, Dio2496,
+ *			    Audiophile, Digigram VX442
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/cs8427.h>
+#include <sound/asoundef.h>
+
+#include "ice1712.h"
+#include "delta.h"
+
+#define SND_CS8403
+#include <sound/cs8403.h>
+
+
+/*
+ * CS8427 via SPI mode (for Audiophile), emulated I2C
+ */
+
+/* send 8 bits */
+static void ap_cs8427_write_byte(struct snd_ice1712 *ice, unsigned char data, unsigned char tmp)
+{
+	int idx;
+
+	for (idx = 7; idx >= 0; idx--) {
+		tmp &= ~(ICE1712_DELTA_AP_DOUT|ICE1712_DELTA_AP_CCLK);
+		if (data & (1 << idx))
+			tmp |= ICE1712_DELTA_AP_DOUT;
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+		udelay(5);
+		tmp |= ICE1712_DELTA_AP_CCLK;
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+		udelay(5);
+	}
+}
+
+/* read 8 bits */
+static unsigned char ap_cs8427_read_byte(struct snd_ice1712 *ice, unsigned char tmp)
+{
+	unsigned char data = 0;
+	int idx;
+	
+	for (idx = 7; idx >= 0; idx--) {
+		tmp &= ~ICE1712_DELTA_AP_CCLK;
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+		udelay(5);
+		if (snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_DELTA_AP_DIN)
+			data |= 1 << idx;
+		tmp |= ICE1712_DELTA_AP_CCLK;
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+		udelay(5);
+	}
+	return data;
+}
+
+/* assert chip select */
+static unsigned char ap_cs8427_codec_select(struct snd_ice1712 *ice)
+{
+	unsigned char tmp;
+	tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_DELTA1010E:
+	case ICE1712_SUBDEVICE_DELTA1010LT:
+		tmp &= ~ICE1712_DELTA_1010LT_CS;
+		tmp |= ICE1712_DELTA_1010LT_CCLK | ICE1712_DELTA_1010LT_CS_CS8427;
+		break;
+	case ICE1712_SUBDEVICE_AUDIOPHILE:
+	case ICE1712_SUBDEVICE_DELTA410:
+		tmp |= ICE1712_DELTA_AP_CCLK | ICE1712_DELTA_AP_CS_CODEC;
+		tmp &= ~ICE1712_DELTA_AP_CS_DIGITAL;
+		break;
+	case ICE1712_SUBDEVICE_VX442:
+		tmp |= ICE1712_VX442_CCLK | ICE1712_VX442_CODEC_CHIP_A | ICE1712_VX442_CODEC_CHIP_B;
+		tmp &= ~ICE1712_VX442_CS_DIGITAL;
+		break;
+	}
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+	udelay(5);
+	return tmp;
+}
+
+/* deassert chip select */
+static void ap_cs8427_codec_deassert(struct snd_ice1712 *ice, unsigned char tmp)
+{
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_DELTA1010E:
+	case ICE1712_SUBDEVICE_DELTA1010LT:
+		tmp &= ~ICE1712_DELTA_1010LT_CS;
+		tmp |= ICE1712_DELTA_1010LT_CS_NONE;
+		break;
+	case ICE1712_SUBDEVICE_AUDIOPHILE:
+	case ICE1712_SUBDEVICE_DELTA410:
+		tmp |= ICE1712_DELTA_AP_CS_DIGITAL;
+		break;
+	case ICE1712_SUBDEVICE_VX442:
+		tmp |= ICE1712_VX442_CS_DIGITAL;
+		break;
+	}
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+}
+
+/* sequential write */
+static int ap_cs8427_sendbytes(struct snd_i2c_device *device, unsigned char *bytes, int count)
+{
+	struct snd_ice1712 *ice = device->bus->private_data;
+	int res = count;
+	unsigned char tmp;
+
+	mutex_lock(&ice->gpio_mutex);
+	tmp = ap_cs8427_codec_select(ice);
+	ap_cs8427_write_byte(ice, (device->addr << 1) | 0, tmp); /* address + write mode */
+	while (count-- > 0)
+		ap_cs8427_write_byte(ice, *bytes++, tmp);
+	ap_cs8427_codec_deassert(ice, tmp);
+	mutex_unlock(&ice->gpio_mutex);
+	return res;
+}
+
+/* sequential read */
+static int ap_cs8427_readbytes(struct snd_i2c_device *device, unsigned char *bytes, int count)
+{
+	struct snd_ice1712 *ice = device->bus->private_data;
+	int res = count;
+	unsigned char tmp;
+	
+	mutex_lock(&ice->gpio_mutex);
+	tmp = ap_cs8427_codec_select(ice);
+	ap_cs8427_write_byte(ice, (device->addr << 1) | 1, tmp); /* address + read mode */
+	while (count-- > 0)
+		*bytes++ = ap_cs8427_read_byte(ice, tmp);
+	ap_cs8427_codec_deassert(ice, tmp);
+	mutex_unlock(&ice->gpio_mutex);
+	return res;
+}
+
+static int ap_cs8427_probeaddr(struct snd_i2c_bus *bus, unsigned short addr)
+{
+	if (addr == 0x10)
+		return 1;
+	return -ENOENT;
+}
+
+static struct snd_i2c_ops ap_cs8427_i2c_ops = {
+	.sendbytes = ap_cs8427_sendbytes,
+	.readbytes = ap_cs8427_readbytes,
+	.probeaddr = ap_cs8427_probeaddr,
+};
+
+/*
+ */
+
+static void snd_ice1712_delta_cs8403_spdif_write(struct snd_ice1712 *ice, unsigned char bits)
+{
+	unsigned char tmp, mask1, mask2;
+	int idx;
+	/* send byte to transmitter */
+	mask1 = ICE1712_DELTA_SPDIF_OUT_STAT_CLOCK;
+	mask2 = ICE1712_DELTA_SPDIF_OUT_STAT_DATA;
+	mutex_lock(&ice->gpio_mutex);
+	tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+	for (idx = 7; idx >= 0; idx--) {
+		tmp &= ~(mask1 | mask2);
+		if (bits & (1 << idx))
+			tmp |= mask2;
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+		udelay(100);
+		tmp |= mask1;
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+		udelay(100);
+	}
+	tmp &= ~mask1;
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+
+static void delta_spdif_default_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+	snd_cs8403_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_bits);
+}
+
+static int delta_spdif_default_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned int val;
+	int change;
+
+	val = snd_cs8403_encode_spdif_bits(&ucontrol->value.iec958);
+	spin_lock_irq(&ice->reg_lock);
+	change = ice->spdif.cs8403_bits != val;
+	ice->spdif.cs8403_bits = val;
+	if (change && ice->playback_pro_substream == NULL) {
+		spin_unlock_irq(&ice->reg_lock);
+		snd_ice1712_delta_cs8403_spdif_write(ice, val);
+	} else {
+		spin_unlock_irq(&ice->reg_lock);
+	}
+	return change;
+}
+
+static void delta_spdif_stream_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+	snd_cs8403_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_stream_bits);
+}
+
+static int delta_spdif_stream_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned int val;
+	int change;
+
+	val = snd_cs8403_encode_spdif_bits(&ucontrol->value.iec958);
+	spin_lock_irq(&ice->reg_lock);
+	change = ice->spdif.cs8403_stream_bits != val;
+	ice->spdif.cs8403_stream_bits = val;
+	if (change && ice->playback_pro_substream != NULL) {
+		spin_unlock_irq(&ice->reg_lock);
+		snd_ice1712_delta_cs8403_spdif_write(ice, val);
+	} else {
+		spin_unlock_irq(&ice->reg_lock);
+	}
+	return change;
+}
+
+
+/*
+ * AK4524 on Delta 44 and 66 to choose the chip mask
+ */
+static void delta_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+        struct snd_ak4xxx_private *priv = (void *)ak->private_value[0];
+        struct snd_ice1712 *ice = ak->private_data[0];
+
+	snd_ice1712_save_gpio_status(ice);
+	priv->cs_mask =
+	priv->cs_addr = chip == 0 ? ICE1712_DELTA_CODEC_CHIP_A :
+				    ICE1712_DELTA_CODEC_CHIP_B;
+}
+
+/*
+ * AK4524 on Delta1010LT to choose the chip address
+ */
+static void delta1010lt_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+        struct snd_ak4xxx_private *priv = (void *)ak->private_value[0];
+        struct snd_ice1712 *ice = ak->private_data[0];
+
+	snd_ice1712_save_gpio_status(ice);
+	priv->cs_mask = ICE1712_DELTA_1010LT_CS;
+	priv->cs_addr = chip << 4;
+}
+
+/*
+ * AK4528 on VX442 to choose the chip mask
+ */
+static void vx442_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+        struct snd_ak4xxx_private *priv = (void *)ak->private_value[0];
+        struct snd_ice1712 *ice = ak->private_data[0];
+
+	snd_ice1712_save_gpio_status(ice);
+	priv->cs_mask =
+	priv->cs_addr = chip == 0 ? ICE1712_VX442_CODEC_CHIP_A :
+				    ICE1712_VX442_CODEC_CHIP_B;
+}
+
+/*
+ * change the DFS bit according rate for Delta1010
+ */
+static void delta_1010_set_rate_val(struct snd_ice1712 *ice, unsigned int rate)
+{
+	unsigned char tmp, tmp2;
+
+	if (rate == 0)	/* no hint - S/PDIF input is master, simply return */
+		return;
+
+	mutex_lock(&ice->gpio_mutex);
+	tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+	tmp2 = tmp & ~ICE1712_DELTA_DFS;
+	if (rate > 48000)
+		tmp2 |= ICE1712_DELTA_DFS;
+	if (tmp != tmp2)
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp2);
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+/*
+ * change the rate of AK4524 on Delta 44/66, AP, 1010LT
+ */
+static void delta_ak4524_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+	unsigned char tmp, tmp2;
+	struct snd_ice1712 *ice = ak->private_data[0];
+
+	if (rate == 0)	/* no hint - S/PDIF input is master, simply return */
+		return;
+
+	/* check before reset ak4524 to avoid unnecessary clicks */
+	mutex_lock(&ice->gpio_mutex);
+	tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+	mutex_unlock(&ice->gpio_mutex);
+	tmp2 = tmp & ~ICE1712_DELTA_DFS; 
+	if (rate > 48000)
+		tmp2 |= ICE1712_DELTA_DFS;
+	if (tmp == tmp2)
+		return;
+
+	/* do it again */
+	snd_akm4xxx_reset(ak, 1);
+	mutex_lock(&ice->gpio_mutex);
+	tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ~ICE1712_DELTA_DFS;
+	if (rate > 48000)
+		tmp |= ICE1712_DELTA_DFS;
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+	mutex_unlock(&ice->gpio_mutex);
+	snd_akm4xxx_reset(ak, 0);
+}
+
+/*
+ * change the rate of AK4524 on VX442
+ */
+static void vx442_ak4524_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+	unsigned char val;
+
+	val = (rate > 48000) ? 0x65 : 0x60;
+	if (snd_akm4xxx_get(ak, 0, 0x02) != val ||
+	    snd_akm4xxx_get(ak, 1, 0x02) != val) {
+		snd_akm4xxx_reset(ak, 1);
+		snd_akm4xxx_write(ak, 0, 0x02, val);
+		snd_akm4xxx_write(ak, 1, 0x02, val);
+		snd_akm4xxx_reset(ak, 0);
+	}
+}
+
+
+/*
+ * SPDIF ops for Delta 1010, Dio, 66
+ */
+
+/* open callback */
+static void delta_open_spdif(struct snd_ice1712 *ice, struct snd_pcm_substream *substream)
+{
+	ice->spdif.cs8403_stream_bits = ice->spdif.cs8403_bits;
+}
+
+/* set up */
+static void delta_setup_spdif(struct snd_ice1712 *ice, int rate)
+{
+	unsigned long flags;
+	unsigned int tmp;
+	int change;
+
+	spin_lock_irqsave(&ice->reg_lock, flags);
+	tmp = ice->spdif.cs8403_stream_bits;
+	if (tmp & 0x01)		/* consumer */
+		tmp &= (tmp & 0x01) ? ~0x06 : ~0x18;
+	switch (rate) {
+	case 32000: tmp |= (tmp & 0x01) ? 0x04 : 0x00; break;
+	case 44100: tmp |= (tmp & 0x01) ? 0x00 : 0x10; break;
+	case 48000: tmp |= (tmp & 0x01) ? 0x02 : 0x08; break;
+	default: tmp |= (tmp & 0x01) ? 0x00 : 0x18; break;
+	}
+	change = ice->spdif.cs8403_stream_bits != tmp;
+	ice->spdif.cs8403_stream_bits = tmp;
+	spin_unlock_irqrestore(&ice->reg_lock, flags);
+	if (change)
+		snd_ctl_notify(ice->card, SNDRV_CTL_EVENT_MASK_VALUE, &ice->spdif.stream_ctl->id);
+	snd_ice1712_delta_cs8403_spdif_write(ice, tmp);
+}
+
+#define snd_ice1712_delta1010lt_wordclock_status_info \
+	snd_ctl_boolean_mono_info
+
+static int snd_ice1712_delta1010lt_wordclock_status_get(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	char reg = 0x10; /* CS8427 receiver error register */
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	if (snd_i2c_sendbytes(ice->cs8427, &reg, 1) != 1)
+		snd_printk(KERN_ERR "unable to send register 0x%x byte to CS8427\n", reg);
+	snd_i2c_readbytes(ice->cs8427, &reg, 1);
+	ucontrol->value.integer.value[0] = (reg & CS8427_UNLOCK) ? 1 : 0;
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status __devinitdata =
+{
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READ),
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Word Clock Status",
+	.info =		snd_ice1712_delta1010lt_wordclock_status_info,
+	.get =		snd_ice1712_delta1010lt_wordclock_status_get,
+};
+
+/*
+ * initialize the chips on M-Audio cards
+ */
+
+static struct snd_akm4xxx akm_audiophile __devinitdata = {
+	.type = SND_AK4528,
+	.num_adcs = 2,
+	.num_dacs = 2,
+	.ops = {
+		.set_rate_val = delta_ak4524_set_rate_val
+	}
+};
+
+static struct snd_ak4xxx_private akm_audiophile_priv __devinitdata = {
+	.caddr = 2,
+	.cif = 0,
+	.data_mask = ICE1712_DELTA_AP_DOUT,
+	.clk_mask = ICE1712_DELTA_AP_CCLK,
+	.cs_mask = ICE1712_DELTA_AP_CS_CODEC,
+	.cs_addr = ICE1712_DELTA_AP_CS_CODEC,
+	.cs_none = 0,
+	.add_flags = ICE1712_DELTA_AP_CS_DIGITAL,
+	.mask_flags = 0,
+};
+
+static struct snd_akm4xxx akm_delta410 __devinitdata = {
+	.type = SND_AK4529,
+	.num_adcs = 2,
+	.num_dacs = 8,
+	.ops = {
+		.set_rate_val = delta_ak4524_set_rate_val
+	}
+};
+
+static struct snd_ak4xxx_private akm_delta410_priv __devinitdata = {
+	.caddr = 0,
+	.cif = 0,
+	.data_mask = ICE1712_DELTA_AP_DOUT,
+	.clk_mask = ICE1712_DELTA_AP_CCLK,
+	.cs_mask = ICE1712_DELTA_AP_CS_CODEC,
+	.cs_addr = ICE1712_DELTA_AP_CS_CODEC,
+	.cs_none = 0,
+	.add_flags = ICE1712_DELTA_AP_CS_DIGITAL,
+	.mask_flags = 0,
+};
+
+static struct snd_akm4xxx akm_delta1010lt __devinitdata = {
+	.type = SND_AK4524,
+	.num_adcs = 8,
+	.num_dacs = 8,
+	.ops = {
+		.lock = delta1010lt_ak4524_lock,
+		.set_rate_val = delta_ak4524_set_rate_val
+	}
+};
+
+static struct snd_ak4xxx_private akm_delta1010lt_priv __devinitdata = {
+	.caddr = 2,
+	.cif = 0, /* the default level of the CIF pin from AK4524 */
+	.data_mask = ICE1712_DELTA_1010LT_DOUT,
+	.clk_mask = ICE1712_DELTA_1010LT_CCLK,
+	.cs_mask = 0,
+	.cs_addr = 0, /* set later */
+	.cs_none = ICE1712_DELTA_1010LT_CS_NONE,
+	.add_flags = 0,
+	.mask_flags = 0,
+};
+
+static struct snd_akm4xxx akm_delta44 __devinitdata = {
+	.type = SND_AK4524,
+	.num_adcs = 4,
+	.num_dacs = 4,
+	.ops = {
+		.lock = delta_ak4524_lock,
+		.set_rate_val = delta_ak4524_set_rate_val
+	}
+};
+
+static struct snd_ak4xxx_private akm_delta44_priv __devinitdata = {
+	.caddr = 2,
+	.cif = 0, /* the default level of the CIF pin from AK4524 */
+	.data_mask = ICE1712_DELTA_CODEC_SERIAL_DATA,
+	.clk_mask = ICE1712_DELTA_CODEC_SERIAL_CLOCK,
+	.cs_mask = 0,
+	.cs_addr = 0, /* set later */
+	.cs_none = 0,
+	.add_flags = 0,
+	.mask_flags = 0,
+};
+
+static struct snd_akm4xxx akm_vx442 __devinitdata = {
+	.type = SND_AK4524,
+	.num_adcs = 4,
+	.num_dacs = 4,
+	.ops = {
+		.lock = vx442_ak4524_lock,
+		.set_rate_val = vx442_ak4524_set_rate_val
+	}
+};
+
+static struct snd_ak4xxx_private akm_vx442_priv __devinitdata = {
+	.caddr = 2,
+	.cif = 0,
+	.data_mask = ICE1712_VX442_DOUT,
+	.clk_mask = ICE1712_VX442_CCLK,
+	.cs_mask = 0,
+	.cs_addr = 0, /* set later */
+	.cs_none = 0,
+	.add_flags = 0,
+	.mask_flags = 0,
+};
+
+static int __devinit snd_ice1712_delta_init(struct snd_ice1712 *ice)
+{
+	int err;
+	struct snd_akm4xxx *ak;
+
+	if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DELTA1010 &&
+	    ice->eeprom.gpiodir == 0x7b)
+		ice->eeprom.subvendor = ICE1712_SUBDEVICE_DELTA1010E;
+
+	if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DELTA66 &&
+	    ice->eeprom.gpiodir == 0xfb)
+	    	ice->eeprom.subvendor = ICE1712_SUBDEVICE_DELTA66E;
+
+	/* determine I2C, DACs and ADCs */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_AUDIOPHILE:
+		ice->num_total_dacs = 2;
+		ice->num_total_adcs = 2;
+		break;
+	case ICE1712_SUBDEVICE_DELTA410:
+		ice->num_total_dacs = 8;
+		ice->num_total_adcs = 2;
+		break;
+	case ICE1712_SUBDEVICE_DELTA44:
+	case ICE1712_SUBDEVICE_DELTA66:
+		ice->num_total_dacs = ice->omni ? 8 : 4;
+		ice->num_total_adcs = ice->omni ? 8 : 4;
+		break;
+	case ICE1712_SUBDEVICE_DELTA1010:
+	case ICE1712_SUBDEVICE_DELTA1010E:
+	case ICE1712_SUBDEVICE_DELTA1010LT:
+	case ICE1712_SUBDEVICE_MEDIASTATION:
+	case ICE1712_SUBDEVICE_EDIROLDA2496:
+		ice->num_total_dacs = 8;
+		ice->num_total_adcs = 8;
+		break;
+	case ICE1712_SUBDEVICE_DELTADIO2496:
+		ice->num_total_dacs = 4;	/* two AK4324 codecs */
+		break;
+	case ICE1712_SUBDEVICE_VX442:
+	case ICE1712_SUBDEVICE_DELTA66E:	/* omni not suported yet */
+		ice->num_total_dacs = 4;
+		ice->num_total_adcs = 4;
+		break;
+	}
+
+	/* initialize spdif */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_AUDIOPHILE:
+	case ICE1712_SUBDEVICE_DELTA410:
+	case ICE1712_SUBDEVICE_DELTA1010E:
+	case ICE1712_SUBDEVICE_DELTA1010LT:
+	case ICE1712_SUBDEVICE_VX442:
+	case ICE1712_SUBDEVICE_DELTA66E:
+		if ((err = snd_i2c_bus_create(ice->card, "ICE1712 GPIO 1", NULL, &ice->i2c)) < 0) {
+			snd_printk(KERN_ERR "unable to create I2C bus\n");
+			return err;
+		}
+		ice->i2c->private_data = ice;
+		ice->i2c->ops = &ap_cs8427_i2c_ops;
+		if ((err = snd_ice1712_init_cs8427(ice, CS8427_BASE_ADDR)) < 0)
+			return err;
+		break;
+	case ICE1712_SUBDEVICE_DELTA1010:
+	case ICE1712_SUBDEVICE_MEDIASTATION:
+		ice->gpio.set_pro_rate = delta_1010_set_rate_val;
+		break;
+	case ICE1712_SUBDEVICE_DELTADIO2496:
+		ice->gpio.set_pro_rate = delta_1010_set_rate_val;
+		/* fall thru */
+	case ICE1712_SUBDEVICE_DELTA66:
+		ice->spdif.ops.open = delta_open_spdif;
+		ice->spdif.ops.setup_rate = delta_setup_spdif;
+		ice->spdif.ops.default_get = delta_spdif_default_get;
+		ice->spdif.ops.default_put = delta_spdif_default_put;
+		ice->spdif.ops.stream_get = delta_spdif_stream_get;
+		ice->spdif.ops.stream_put = delta_spdif_stream_put;
+		/* Set spdif defaults */
+		snd_ice1712_delta_cs8403_spdif_write(ice, ice->spdif.cs8403_bits);
+		break;
+	}
+
+	/* no analog? */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_DELTA1010:
+	case ICE1712_SUBDEVICE_DELTA1010E:
+	case ICE1712_SUBDEVICE_DELTADIO2496:
+	case ICE1712_SUBDEVICE_MEDIASTATION:
+		return 0;
+	}
+
+	/* second stage of initialization, analog parts and others */
+	ak = ice->akm = kmalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+	if (! ak)
+		return -ENOMEM;
+	ice->akm_codecs = 1;
+
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_AUDIOPHILE:
+		err = snd_ice1712_akm4xxx_init(ak, &akm_audiophile, &akm_audiophile_priv, ice);
+		break;
+	case ICE1712_SUBDEVICE_DELTA410:
+		err = snd_ice1712_akm4xxx_init(ak, &akm_delta410, &akm_delta410_priv, ice);
+		break;
+	case ICE1712_SUBDEVICE_DELTA1010LT:
+	case ICE1712_SUBDEVICE_EDIROLDA2496:
+		err = snd_ice1712_akm4xxx_init(ak, &akm_delta1010lt, &akm_delta1010lt_priv, ice);
+		break;
+	case ICE1712_SUBDEVICE_DELTA66:
+	case ICE1712_SUBDEVICE_DELTA44:
+		err = snd_ice1712_akm4xxx_init(ak, &akm_delta44, &akm_delta44_priv, ice);
+		break;
+	case ICE1712_SUBDEVICE_VX442:
+	case ICE1712_SUBDEVICE_DELTA66E:
+		err = snd_ice1712_akm4xxx_init(ak, &akm_vx442, &akm_vx442_priv, ice);
+		break;
+	default:
+		snd_BUG();
+		return -EINVAL;
+	}
+
+	return err;
+}
+
+
+/*
+ * additional controls for M-Audio cards
+ */
+
+static struct snd_kcontrol_new snd_ice1712_delta1010_wordclock_select __devinitdata =
+ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Word Clock Sync", 0, ICE1712_DELTA_WORD_CLOCK_SELECT, 1, 0);
+static struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_select __devinitdata =
+ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Word Clock Sync", 0, ICE1712_DELTA_1010LT_WORDCLOCK, 0, 0);
+static struct snd_kcontrol_new snd_ice1712_delta1010_wordclock_status __devinitdata =
+ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Word Clock Status", 0, ICE1712_DELTA_WORD_CLOCK_STATUS, 1, SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE);
+static struct snd_kcontrol_new snd_ice1712_deltadio2496_spdif_in_select __devinitdata =
+ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "IEC958 Input Optical", 0, ICE1712_DELTA_SPDIF_INPUT_SELECT, 0, 0);
+static struct snd_kcontrol_new snd_ice1712_delta_spdif_in_status __devinitdata =
+ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Delta IEC958 Input Status", 0, ICE1712_DELTA_SPDIF_IN_STAT, 1, SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE);
+
+
+static int __devinit snd_ice1712_delta_add_controls(struct snd_ice1712 *ice)
+{
+	int err;
+
+	/* 1010 and dio specific controls */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_DELTA1010:
+	case ICE1712_SUBDEVICE_MEDIASTATION:
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010_wordclock_select, ice));
+		if (err < 0)
+			return err;
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010_wordclock_status, ice));
+		if (err < 0)
+			return err;
+		break;
+	case ICE1712_SUBDEVICE_DELTADIO2496:
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_deltadio2496_spdif_in_select, ice));
+		if (err < 0)
+			return err;
+		break;
+	case ICE1712_SUBDEVICE_DELTA1010E:
+	case ICE1712_SUBDEVICE_DELTA1010LT:
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010lt_wordclock_select, ice));
+		if (err < 0)
+			return err;
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010lt_wordclock_status, ice));
+		if (err < 0)
+			return err;
+		break;
+	}
+
+	/* normal spdif controls */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_DELTA1010:
+	case ICE1712_SUBDEVICE_DELTADIO2496:
+	case ICE1712_SUBDEVICE_DELTA66:
+	case ICE1712_SUBDEVICE_MEDIASTATION:
+		err = snd_ice1712_spdif_build_controls(ice);
+		if (err < 0)
+			return err;
+		break;
+	}
+
+	/* spdif status in */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_DELTA1010:
+	case ICE1712_SUBDEVICE_DELTADIO2496:
+	case ICE1712_SUBDEVICE_DELTA66:
+	case ICE1712_SUBDEVICE_MEDIASTATION:
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta_spdif_in_status, ice));
+		if (err < 0)
+			return err;
+		break;
+	}
+
+	/* ak4524 controls */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_DELTA1010LT:
+	case ICE1712_SUBDEVICE_AUDIOPHILE:
+	case ICE1712_SUBDEVICE_DELTA410:
+	case ICE1712_SUBDEVICE_DELTA44:
+	case ICE1712_SUBDEVICE_DELTA66:
+	case ICE1712_SUBDEVICE_VX442:
+	case ICE1712_SUBDEVICE_DELTA66E:
+	case ICE1712_SUBDEVICE_EDIROLDA2496:
+		err = snd_ice1712_akm4xxx_build_controls(ice);
+		if (err < 0)
+			return err;
+		break;
+	}
+
+	return 0;
+}
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_ice1712_delta_cards[] __devinitdata = {
+	{
+		.subvendor = ICE1712_SUBDEVICE_DELTA1010,
+		.name = "M Audio Delta 1010",
+		.model = "delta1010",
+		.chip_init = snd_ice1712_delta_init,
+		.build_controls = snd_ice1712_delta_add_controls,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_DELTADIO2496,
+		.name = "M Audio Delta DiO 2496",
+		.model = "dio2496",
+		.chip_init = snd_ice1712_delta_init,
+		.build_controls = snd_ice1712_delta_add_controls,
+		.no_mpu401 = 1,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_DELTA66,
+		.name = "M Audio Delta 66",
+		.model = "delta66",
+		.chip_init = snd_ice1712_delta_init,
+		.build_controls = snd_ice1712_delta_add_controls,
+		.no_mpu401 = 1,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_DELTA44,
+		.name = "M Audio Delta 44",
+		.model = "delta44",
+		.chip_init = snd_ice1712_delta_init,
+		.build_controls = snd_ice1712_delta_add_controls,
+		.no_mpu401 = 1,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_AUDIOPHILE,
+		.name = "M Audio Audiophile 24/96",
+		.model = "audiophile",
+		.chip_init = snd_ice1712_delta_init,
+		.build_controls = snd_ice1712_delta_add_controls,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_DELTA410,
+		.name = "M Audio Delta 410",
+		.model = "delta410",
+		.chip_init = snd_ice1712_delta_init,
+		.build_controls = snd_ice1712_delta_add_controls,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_DELTA1010LT,
+		.name = "M Audio Delta 1010LT",
+		.model = "delta1010lt",
+		.chip_init = snd_ice1712_delta_init,
+		.build_controls = snd_ice1712_delta_add_controls,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_VX442,
+		.name = "Digigram VX442",
+		.model = "vx442",
+		.chip_init = snd_ice1712_delta_init,
+		.build_controls = snd_ice1712_delta_add_controls,
+		.no_mpu401 = 1,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_MEDIASTATION,
+		.name = "Lionstracs Mediastation",
+		.model = "mediastation",
+		.chip_init = snd_ice1712_delta_init,
+		.build_controls = snd_ice1712_delta_add_controls,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_EDIROLDA2496,
+		.name = "Edirol DA2496",
+		.model = "da2496",
+		.chip_init = snd_ice1712_delta_init,
+		.build_controls = snd_ice1712_delta_add_controls,
+	},
+	{ } /* terminator */
+};
diff --git a/linux/sound/pci/ice1712/delta.h b/linux/sound/pci/ice1712/delta.h
new file mode 100644
index 0000000..1a0ac6c
--- /dev/null
+++ b/linux/sound/pci/ice1712/delta.h
@@ -0,0 +1,155 @@
+#ifndef __SOUND_DELTA_H
+#define __SOUND_DELTA_H
+
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *   Lowlevel functions for M-Audio Delta 1010, 44, 66, Dio2496, Audiophile
+ *                          Digigram VX442
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#define DELTA_DEVICE_DESC \
+		"{MidiMan M Audio,Delta 1010},"\
+		"{MidiMan M Audio,Delta 1010LT},"\
+		"{MidiMan M Audio,Delta DiO 2496},"\
+		"{MidiMan M Audio,Delta 66},"\
+		"{MidiMan M Audio,Delta 44},"\
+		"{MidiMan M Audio,Delta 410},"\
+		"{MidiMan M Audio,Audiophile 24/96},"\
+		"{Digigram,VX442},"\
+		"{Lionstracs,Mediastation},"\
+		"{Edirol,DA2496},"
+
+#define ICE1712_SUBDEVICE_DELTA1010	0x121430d6
+#define ICE1712_SUBDEVICE_DELTA1010E	0xff1430d6
+#define ICE1712_SUBDEVICE_DELTADIO2496	0x121431d6
+#define ICE1712_SUBDEVICE_DELTA66	0x121432d6
+#define ICE1712_SUBDEVICE_DELTA66E	0xff1432d6
+#define ICE1712_SUBDEVICE_DELTA44	0x121433d6
+#define ICE1712_SUBDEVICE_AUDIOPHILE	0x121434d6
+#define ICE1712_SUBDEVICE_DELTA410	0x121438d6
+#define ICE1712_SUBDEVICE_DELTA1010LT	0x12143bd6
+#define ICE1712_SUBDEVICE_VX442		0x12143cd6
+#define ICE1712_SUBDEVICE_MEDIASTATION	0x694c0100
+#define ICE1712_SUBDEVICE_EDIROLDA2496	0xce164010
+
+/* entry point */
+extern struct snd_ice1712_card_info snd_ice1712_delta_cards[];
+
+
+/*
+ *  MidiMan M-Audio Delta GPIO definitions
+ */
+
+/* MidiMan M-Audio Delta shared pins */
+#define ICE1712_DELTA_DFS 0x01		/* fast/slow sample rate mode */
+					/* (>48kHz must be 1) */
+#define ICE1712_DELTA_SPDIF_IN_STAT 0x02
+					/* S/PDIF input status */
+					/* 0 = valid signal is present */
+					/* all except Delta44 */
+					/* look to CS8414 datasheet */
+#define ICE1712_DELTA_SPDIF_OUT_STAT_CLOCK 0x04
+					/* S/PDIF output status clock */
+					/* (writing on rising edge - 0->1) */
+					/* all except Delta44 */
+					/* look to CS8404A datasheet */
+#define ICE1712_DELTA_SPDIF_OUT_STAT_DATA 0x08
+					/* S/PDIF output status data */
+					/* all except Delta44 */
+					/* look to CS8404A datasheet */
+/* MidiMan M-Audio DeltaDiO */
+/* 0x01 = DFS */
+/* 0x02 = SPDIF_IN_STAT */
+/* 0x04 = SPDIF_OUT_STAT_CLOCK */
+/* 0x08 = SPDIF_OUT_STAT_DATA */
+#define ICE1712_DELTA_SPDIF_INPUT_SELECT 0x10
+					/* coaxial (0), optical (1) */
+					/* S/PDIF input select*/
+
+/* MidiMan M-Audio Delta1010 */
+/* 0x01 = DFS */
+/* 0x02 = SPDIF_IN_STAT */
+/* 0x04 = SPDIF_OUT_STAT_CLOCK */
+/* 0x08 = SPDIF_OUT_STAT_DATA */
+#define ICE1712_DELTA_WORD_CLOCK_SELECT 0x10
+					/* 1 - clock are taken from S/PDIF input */
+					/* 0 - clock are taken from Word Clock input */
+					/* affected SPMCLKIN pin of Envy24 */
+#define ICE1712_DELTA_WORD_CLOCK_STATUS	0x20
+					/* 0 = valid word clock signal is present */
+
+/* MidiMan M-Audio Delta66 */
+/* 0x01 = DFS */
+/* 0x02 = SPDIF_IN_STAT */
+/* 0x04 = SPDIF_OUT_STAT_CLOCK */
+/* 0x08 = SPDIF_OUT_STAT_DATA */
+#define ICE1712_DELTA_CODEC_SERIAL_DATA 0x10
+					/* AKM4524 serial data */
+#define ICE1712_DELTA_CODEC_SERIAL_CLOCK 0x20
+					/* AKM4524 serial clock */
+					/* (writing on rising edge - 0->1 */
+#define ICE1712_DELTA_CODEC_CHIP_A	0x40
+#define ICE1712_DELTA_CODEC_CHIP_B	0x80
+					/* 1 - select chip A or B */
+
+/* MidiMan M-Audio Delta44 */
+/* 0x01 = DFS */
+/* 0x10 = CODEC_SERIAL_DATA */
+/* 0x20 = CODEC_SERIAL_CLOCK */
+/* 0x40 = CODEC_CHIP_A */
+/* 0x80 = CODEC_CHIP_B */
+
+/* MidiMan M-Audio Audiophile/Delta410 definitions */
+/* thanks to Kristof Pelckmans <Kristof.Pelckmans@antwerpen.be> for Delta410 info */
+/* 0x01 = DFS */
+#define ICE1712_DELTA_AP_CCLK	0x02	/* SPI clock */
+					/* (clocking on rising edge - 0->1) */
+#define ICE1712_DELTA_AP_DIN	0x04	/* data input */
+#define ICE1712_DELTA_AP_DOUT	0x08	/* data output */
+#define ICE1712_DELTA_AP_CS_DIGITAL 0x10 /* CS8427 chip select */
+					/* low signal = select */
+#define ICE1712_DELTA_AP_CS_CODEC 0x20	/* AK4528 (audiophile), AK4529 (Delta410) chip select */
+					/* low signal = select */
+
+/* MidiMan M-Audio Delta1010LT definitions */
+/* thanks to Anders Johansson <ajh@watri.uwa.edu.au> */
+/* 0x01 = DFS */
+#define ICE1712_DELTA_1010LT_CCLK	0x02	/* SPI clock (AK4524 + CS8427) */
+#define ICE1712_DELTA_1010LT_DIN	0x04	/* data input (CS8427) */
+#define ICE1712_DELTA_1010LT_DOUT	0x08	/* data output (AK4524 + CS8427) */
+#define ICE1712_DELTA_1010LT_CS		0x70	/* mask for CS address */
+#define ICE1712_DELTA_1010LT_CS_CHIP_A	0x00	/* AK4524 #0 */
+#define ICE1712_DELTA_1010LT_CS_CHIP_B	0x10	/* AK4524 #1 */
+#define ICE1712_DELTA_1010LT_CS_CHIP_C	0x20	/* AK4524 #2 */
+#define ICE1712_DELTA_1010LT_CS_CHIP_D	0x30	/* AK4524 #3 */
+#define ICE1712_DELTA_1010LT_CS_CS8427	0x40	/* CS8427 */
+#define ICE1712_DELTA_1010LT_CS_NONE	0x50	/* nothing */
+#define ICE1712_DELTA_1010LT_WORDCLOCK 0x80	/* sample clock source: 0 = Word Clock Input, 1 = S/PDIF Input ??? */
+
+/* Digigram VX442 definitions */
+#define ICE1712_VX442_CCLK		0x02	/* SPI clock */
+#define ICE1712_VX442_DIN		0x04	/* data input */
+#define ICE1712_VX442_DOUT		0x08	/* data output */
+#define ICE1712_VX442_CS_DIGITAL	0x10	/* chip select, low = CS8427 */
+#define ICE1712_VX442_CODEC_CHIP_A	0x20	/* select chip A */
+#define ICE1712_VX442_CODEC_CHIP_B	0x40	/* select chip B */
+
+#endif /* __SOUND_DELTA_H */
diff --git a/linux/sound/pci/ice1712/envy24ht.h b/linux/sound/pci/ice1712/envy24ht.h
new file mode 100644
index 0000000..a0c5e00
--- /dev/null
+++ b/linux/sound/pci/ice1712/envy24ht.h
@@ -0,0 +1,219 @@
+#ifndef __SOUND_VT1724_H
+#define __SOUND_VT1724_H
+
+/*
+ *   ALSA driver for ICEnsemble VT1724 (Envy24)
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include <sound/rawmidi.h>
+#include <sound/i2c.h>
+#include <sound/pcm.h>
+
+#include "ice1712.h"
+
+enum {
+	ICE_EEP2_SYSCONF = 0,	/* 06 */
+	ICE_EEP2_ACLINK,	/* 07 */
+	ICE_EEP2_I2S,		/* 08 */
+	ICE_EEP2_SPDIF,		/* 09 */
+	ICE_EEP2_GPIO_DIR,	/* 0a */
+	ICE_EEP2_GPIO_DIR1,	/* 0b */
+	ICE_EEP2_GPIO_DIR2,	/* 0c */
+	ICE_EEP2_GPIO_MASK,	/* 0d */
+	ICE_EEP2_GPIO_MASK1,	/* 0e */
+	ICE_EEP2_GPIO_MASK2,	/* 0f */
+	ICE_EEP2_GPIO_STATE,	/* 10 */
+	ICE_EEP2_GPIO_STATE1,	/* 11 */
+	ICE_EEP2_GPIO_STATE2	/* 12 */
+};
+	
+/*
+ *  Direct registers
+ */
+
+#define ICEREG1724(ice, x) ((ice)->port + VT1724_REG_##x)
+
+#define VT1724_REG_CONTROL		0x00	/* byte */
+#define   VT1724_RESET			0x80	/* reset whole chip */
+#define VT1724_REG_IRQMASK		0x01	/* byte */
+#define   VT1724_IRQ_MPU_RX		0x80
+#define   VT1724_IRQ_MPU_TX		0x20
+#define   VT1724_IRQ_MTPCM		0x10
+#define VT1724_REG_IRQSTAT		0x02	/* byte */
+/* look to VT1724_IRQ_* */
+#define VT1724_REG_SYS_CFG		0x04	/* byte - system configuration PCI60 on Envy24*/
+#define   VT1724_CFG_CLOCK	0xc0
+#define     VT1724_CFG_CLOCK512	0x00	/* 22.5692Mhz, 44.1kHz*512 */
+#define     VT1724_CFG_CLOCK384  0x40	/* 16.9344Mhz, 44.1kHz*384 */
+#define   VT1724_CFG_MPU401	0x20		/* MPU401 UARTs */
+#define   VT1724_CFG_ADC_MASK	0x0c	/* one, two or one and S/PDIF, stereo ADCs */
+#define   VT1724_CFG_DAC_MASK	0x03	/* one, two, three, four stereo DACs */
+
+#define VT1724_REG_AC97_CFG		0x05	/* byte */
+#define   VT1724_CFG_PRO_I2S	0x80	/* multitrack converter: I2S or AC'97 */
+#define   VT1724_CFG_AC97_PACKED	0x01	/* split or packed mode - AC'97 */
+
+#define VT1724_REG_I2S_FEATURES		0x06	/* byte */
+#define   VT1724_CFG_I2S_VOLUME	0x80	/* volume/mute capability */
+#define   VT1724_CFG_I2S_96KHZ	0x40	/* supports 96kHz sampling */
+#define   VT1724_CFG_I2S_RESMASK	0x30	/* resolution mask, 16,18,20,24-bit */
+#define   VT1724_CFG_I2S_192KHZ	0x08	/* supports 192kHz sampling */
+#define   VT1724_CFG_I2S_OTHER	0x07	/* other I2S IDs */
+
+#define VT1724_REG_SPDIF_CFG		0x07	/* byte */
+#define   VT1724_CFG_SPDIF_OUT_EN	0x80	/*Internal S/PDIF output is enabled*/
+#define   VT1724_CFG_SPDIF_OUT_INT	0x40	/*Internal S/PDIF output is implemented*/
+#define   VT1724_CFG_I2S_CHIPID	0x3c	/* I2S chip ID */
+#define   VT1724_CFG_SPDIF_IN	0x02	/* S/PDIF input is present */
+#define   VT1724_CFG_SPDIF_OUT	0x01	/* External S/PDIF output is present */
+
+/*there is no consumer AC97 codec with the VT1724*/
+//#define VT1724_REG_AC97_INDEX		0x08	/* byte */
+//#define VT1724_REG_AC97_CMD		0x09	/* byte */
+
+#define VT1724_REG_MPU_TXFIFO		0x0a	/*byte ro. number of bytes in TX fifo*/
+#define VT1724_REG_MPU_RXFIFO		0x0b	/*byte ro. number of bytes in RX fifo*/
+
+#define VT1724_REG_MPU_DATA		0x0c	/* byte */
+#define VT1724_REG_MPU_CTRL		0x0d	/* byte */
+#define   VT1724_MPU_UART	0x01
+#define   VT1724_MPU_TX_EMPTY	0x02
+#define   VT1724_MPU_TX_FULL	0x04
+#define   VT1724_MPU_RX_EMPTY	0x08
+#define   VT1724_MPU_RX_FULL	0x10
+
+#define VT1724_REG_MPU_FIFO_WM	0x0e	/*byte set the high/low watermarks for RX/TX fifos*/
+#define   VT1724_MPU_RX_FIFO	0x20	//1=rx fifo watermark 0=tx fifo watermark
+#define   VT1724_MPU_FIFO_MASK	0x1f	
+
+#define VT1724_REG_I2C_DEV_ADDR	0x10	/* byte */
+#define   VT1724_I2C_WRITE		0x01	/* write direction */
+#define VT1724_REG_I2C_BYTE_ADDR	0x11	/* byte */
+#define VT1724_REG_I2C_DATA		0x12	/* byte */
+#define VT1724_REG_I2C_CTRL		0x13	/* byte */
+#define   VT1724_I2C_EEPROM		0x80	/* 1 = EEPROM exists */
+#define   VT1724_I2C_BUSY		0x01	/* busy bit */
+
+#define VT1724_REG_GPIO_DATA	0x14	/* word */
+#define VT1724_REG_GPIO_WRITE_MASK	0x16 /* word */
+#define VT1724_REG_GPIO_DIRECTION	0x18 /* dword? (3 bytes) 0=input 1=output. 
+						bit3 - during reset used for Eeprom power-on strapping
+						if TESTEN# pin active, bit 2 always input*/
+#define VT1724_REG_POWERDOWN	0x1c
+#define VT1724_REG_GPIO_DATA_22	0x1e /* byte direction for GPIO 16:22 */
+#define VT1724_REG_GPIO_WRITE_MASK_22	0x1f /* byte write mask for GPIO 16:22 */
+
+
+/* 
+ *  Professional multi-track direct control registers
+ */
+
+#define ICEMT1724(ice, x) ((ice)->profi_port + VT1724_MT_##x)
+
+#define VT1724_MT_IRQ			0x00	/* byte - interrupt mask */
+#define   VT1724_MULTI_PDMA4	0x80	/* SPDIF Out / PDMA4 */
+#define	  VT1724_MULTI_PDMA3	0x40	/* PDMA3 */
+#define   VT1724_MULTI_PDMA2	0x20	/* PDMA2 */
+#define   VT1724_MULTI_PDMA1	0x10	/* PDMA1 */
+#define   VT1724_MULTI_FIFO_ERR 0x08	/* DMA FIFO underrun/overrun. */
+#define   VT1724_MULTI_RDMA1	0x04	/* RDMA1 (S/PDIF input) */
+#define   VT1724_MULTI_RDMA0	0x02	/* RMDA0 */
+#define   VT1724_MULTI_PDMA0	0x01	/* MC Interleave/PDMA0 */
+
+#define VT1724_MT_RATE			0x01	/* byte - sampling rate select */
+#define   VT1724_SPDIF_MASTER		0x10	/* S/PDIF input is master clock */
+#define VT1724_MT_I2S_FORMAT		0x02	/* byte - I2S data format */
+#define   VT1724_MT_I2S_MCLK_128X	0x08
+#define   VT1724_MT_I2S_FORMAT_MASK	0x03
+#define   VT1724_MT_I2S_FORMAT_I2S	0x00
+#define VT1724_MT_DMA_INT_MASK		0x03	/* byte -DMA Interrupt Mask */
+/* lool to VT1724_MULTI_* */
+#define VT1724_MT_AC97_INDEX		0x04	/* byte - AC'97 index */
+#define VT1724_MT_AC97_CMD		0x05	/* byte - AC'97 command & status */
+#define   VT1724_AC97_COLD	0x80	/* cold reset */
+#define   VT1724_AC97_WARM	0x40	/* warm reset */
+#define   VT1724_AC97_WRITE	0x20	/* W: write, R: write in progress */
+#define   VT1724_AC97_READ	0x10	/* W: read, R: read in progress */
+#define   VT1724_AC97_READY	0x08	/* codec ready status bit */
+#define   VT1724_AC97_ID_MASK	0x03	/* codec id mask */
+#define VT1724_MT_AC97_DATA		0x06	/* word - AC'97 data */
+#define VT1724_MT_PLAYBACK_ADDR		0x10	/* dword - playback address */
+#define VT1724_MT_PLAYBACK_SIZE		0x14	/* dword - playback size */
+#define VT1724_MT_DMA_CONTROL		0x18	/* byte - control */
+#define   VT1724_PDMA4_START	0x80	/* SPDIF out / PDMA4 start */
+#define   VT1724_PDMA3_START	0x40	/* PDMA3 start */
+#define   VT1724_PDMA2_START	0x20	/* PDMA2 start */
+#define   VT1724_PDMA1_START	0x10	/* PDMA1 start */
+#define   VT1724_RDMA1_START	0x04	/* RDMA1 start */
+#define   VT1724_RDMA0_START	0x02	/* RMDA0 start */
+#define   VT1724_PDMA0_START	0x01	/* MC Interleave / PDMA0 start */
+#define VT1724_MT_BURST			0x19	/* Interleaved playback DMA Active streams / PCI burst size */
+#define VT1724_MT_DMA_FIFO_ERR		0x1a	/*Global playback and record DMA FIFO Underrun/Overrun */
+#define   VT1724_PDMA4_UNDERRUN		0x80
+#define   VT1724_PDMA2_UNDERRUN		0x40
+#define   VT1724_PDMA3_UNDERRUN		0x20
+#define   VT1724_PDMA1_UNDERRUN		0x10
+#define   VT1724_RDMA1_UNDERRUN		0x04
+#define   VT1724_RDMA0_UNDERRUN		0x02
+#define   VT1724_PDMA0_UNDERRUN		0x01
+#define VT1724_MT_DMA_PAUSE		0x1b	/*Global playback and record DMA FIFO pause/resume */
+#define	  VT1724_PDMA4_PAUSE	0x80
+#define	  VT1724_PDMA3_PAUSE	0x40
+#define	  VT1724_PDMA2_PAUSE	0x20
+#define	  VT1724_PDMA1_PAUSE	0x10
+#define	  VT1724_RDMA1_PAUSE	0x04
+#define	  VT1724_RDMA0_PAUSE	0x02
+#define	  VT1724_PDMA0_PAUSE	0x01
+#define VT1724_MT_PLAYBACK_COUNT	0x1c	/* word - playback count */
+#define VT1724_MT_CAPTURE_ADDR		0x20	/* dword - capture address */
+#define VT1724_MT_CAPTURE_SIZE		0x24	/* word - capture size */
+#define VT1724_MT_CAPTURE_COUNT		0x26	/* word - capture count */
+
+#define VT1724_MT_ROUTE_PLAYBACK	0x2c	/* word */
+
+#define VT1724_MT_RDMA1_ADDR		0x30	/* dword - RDMA1 capture address */
+#define VT1724_MT_RDMA1_SIZE		0x34	/* word - RDMA1 capture size */
+#define VT1724_MT_RDMA1_COUNT		0x36	/* word - RDMA1 capture count */
+
+#define VT1724_MT_SPDIF_CTRL		0x3c	/* word */
+#define VT1724_MT_MONITOR_PEAKINDEX	0x3e	/* byte */
+#define VT1724_MT_MONITOR_PEAKDATA	0x3f	/* byte */
+
+/* concurrent stereo channels */
+#define VT1724_MT_PDMA4_ADDR		0x40	/* dword */
+#define VT1724_MT_PDMA4_SIZE		0x44	/* word */
+#define VT1724_MT_PDMA4_COUNT		0x46	/* word */
+#define VT1724_MT_PDMA3_ADDR		0x50	/* dword */
+#define VT1724_MT_PDMA3_SIZE		0x54	/* word */
+#define VT1724_MT_PDMA3_COUNT		0x56	/* word */
+#define VT1724_MT_PDMA2_ADDR		0x60	/* dword */
+#define VT1724_MT_PDMA2_SIZE		0x64	/* word */
+#define VT1724_MT_PDMA2_COUNT		0x66	/* word */
+#define VT1724_MT_PDMA1_ADDR		0x70	/* dword */
+#define VT1724_MT_PDMA1_SIZE		0x74	/* word */
+#define VT1724_MT_PDMA1_COUNT		0x76	/* word */
+
+
+unsigned char snd_vt1724_read_i2c(struct snd_ice1712 *ice, unsigned char dev, unsigned char addr);
+void snd_vt1724_write_i2c(struct snd_ice1712 *ice, unsigned char dev, unsigned char addr, unsigned char data);
+
+#endif /* __SOUND_VT1724_H */
diff --git a/linux/sound/pci/ice1712/ews.c b/linux/sound/pci/ice1712/ews.c
new file mode 100644
index 0000000..6fe35b8
--- /dev/null
+++ b/linux/sound/pci/ice1712/ews.c
@@ -0,0 +1,1087 @@
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *   Lowlevel functions for Terratec EWS88MT/D, EWX24/96, DMX 6Fire
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *                    2002 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/cs8427.h>
+#include <sound/asoundef.h>
+
+#include "ice1712.h"
+#include "ews.h"
+
+#define SND_CS8404
+#include <sound/cs8403.h>
+
+enum {
+	EWS_I2C_CS8404 = 0, EWS_I2C_PCF1, EWS_I2C_PCF2,
+	EWS_I2C_88D = 0,
+	EWS_I2C_6FIRE = 0
+};
+	
+
+/* additional i2c devices for EWS boards */
+struct ews_spec {
+	struct snd_i2c_device *i2cdevs[3];
+};
+
+/*
+ * access via i2c mode (for EWX 24/96, EWS 88MT&D)
+ */
+
+/* send SDA and SCL */
+static void ewx_i2c_setlines(struct snd_i2c_bus *bus, int clk, int data)
+{
+	struct snd_ice1712 *ice = bus->private_data;
+	unsigned char tmp = 0;
+	if (clk)
+		tmp |= ICE1712_EWX2496_SERIAL_CLOCK;
+	if (data)
+		tmp |= ICE1712_EWX2496_SERIAL_DATA;
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+	udelay(5);
+}
+
+static int ewx_i2c_getclock(struct snd_i2c_bus *bus)
+{
+	struct snd_ice1712 *ice = bus->private_data;
+	return snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_EWX2496_SERIAL_CLOCK ? 1 : 0;
+}
+
+static int ewx_i2c_getdata(struct snd_i2c_bus *bus, int ack)
+{
+	struct snd_ice1712 *ice = bus->private_data;
+	int bit;
+	/* set RW pin to low */
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~ICE1712_EWX2496_RW);
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, 0);
+	if (ack)
+		udelay(5);
+	bit = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_EWX2496_SERIAL_DATA ? 1 : 0;
+	/* set RW pin to high */
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, ICE1712_EWX2496_RW);
+	/* reset write mask */
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~ICE1712_EWX2496_SERIAL_CLOCK);
+	return bit;
+}
+
+static void ewx_i2c_start(struct snd_i2c_bus *bus)
+{
+	struct snd_ice1712 *ice = bus->private_data;
+	unsigned char mask;
+
+	snd_ice1712_save_gpio_status(ice);
+	/* set RW high */
+	mask = ICE1712_EWX2496_RW;
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_EWX2496:
+		mask |= ICE1712_EWX2496_AK4524_CS; /* CS high also */
+		break;
+	case ICE1712_SUBDEVICE_DMX6FIRE:
+		mask |= ICE1712_6FIRE_AK4524_CS_MASK; /* CS high also */
+		break;
+	}
+	snd_ice1712_gpio_write_bits(ice, mask, mask);
+}
+
+static void ewx_i2c_stop(struct snd_i2c_bus *bus)
+{
+	struct snd_ice1712 *ice = bus->private_data;
+	snd_ice1712_restore_gpio_status(ice);
+}
+
+static void ewx_i2c_direction(struct snd_i2c_bus *bus, int clock, int data)
+{
+	struct snd_ice1712 *ice = bus->private_data;
+	unsigned char mask = 0;
+
+	if (clock)
+		mask |= ICE1712_EWX2496_SERIAL_CLOCK; /* write SCL */
+	if (data)
+		mask |= ICE1712_EWX2496_SERIAL_DATA; /* write SDA */
+	ice->gpio.direction &= ~(ICE1712_EWX2496_SERIAL_CLOCK|ICE1712_EWX2496_SERIAL_DATA);
+	ice->gpio.direction |= mask;
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, ice->gpio.direction);
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~mask);
+}
+
+static struct snd_i2c_bit_ops snd_ice1712_ewx_cs8427_bit_ops = {
+	.start = ewx_i2c_start,
+	.stop = ewx_i2c_stop,
+	.direction = ewx_i2c_direction,
+	.setlines = ewx_i2c_setlines,
+	.getclock = ewx_i2c_getclock,
+	.getdata = ewx_i2c_getdata,
+};
+
+
+/*
+ * AK4524 access
+ */
+
+/* AK4524 chip select; address 0x48 bit 0-3 */
+static int snd_ice1712_ews88mt_chip_select(struct snd_ice1712 *ice, int chip_mask)
+{
+	struct ews_spec *spec = ice->spec;
+	unsigned char data, ndata;
+
+	if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f))
+		return -EINVAL;
+	snd_i2c_lock(ice->i2c);
+	if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF2], &data, 1) != 1)
+		goto __error;
+	ndata = (data & 0xf0) | chip_mask;
+	if (ndata != data)
+		if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF2], &ndata, 1)
+		    != 1)
+			goto __error;
+	snd_i2c_unlock(ice->i2c);
+	return 0;
+
+     __error:
+	snd_i2c_unlock(ice->i2c);
+	snd_printk(KERN_ERR "AK4524 chip select failed, check cable to the front module\n");
+	return -EIO;
+}
+
+/* start callback for EWS88MT, needs to select a certain chip mask */
+static void ews88mt_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+	struct snd_ice1712 *ice = ak->private_data[0];
+	unsigned char tmp;
+	/* assert AK4524 CS */
+	if (snd_ice1712_ews88mt_chip_select(ice, ~(1 << chip) & 0x0f) < 0)
+		snd_printk(KERN_ERR "fatal error (ews88mt chip select)\n");
+	snd_ice1712_save_gpio_status(ice);
+	tmp = ICE1712_EWS88_SERIAL_DATA |
+		ICE1712_EWS88_SERIAL_CLOCK |
+		ICE1712_EWS88_RW;
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION,
+			  ice->gpio.direction | tmp);
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp);
+}
+
+/* stop callback for EWS88MT, needs to deselect chip mask */
+static void ews88mt_ak4524_unlock(struct snd_akm4xxx *ak, int chip)
+{
+	struct snd_ice1712 *ice = ak->private_data[0];
+	snd_ice1712_restore_gpio_status(ice);
+	udelay(1);
+	snd_ice1712_ews88mt_chip_select(ice, 0x0f);
+}
+
+/* start callback for EWX24/96 */
+static void ewx2496_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+	struct snd_ice1712 *ice = ak->private_data[0];
+	unsigned char tmp;
+	snd_ice1712_save_gpio_status(ice);
+	tmp =  ICE1712_EWX2496_SERIAL_DATA |
+		ICE1712_EWX2496_SERIAL_CLOCK |
+		ICE1712_EWX2496_AK4524_CS |
+		ICE1712_EWX2496_RW;
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION,
+			  ice->gpio.direction | tmp);
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp);
+}
+
+/* start callback for DMX 6fire */
+static void dmx6fire_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+	struct snd_ak4xxx_private *priv = (void *)ak->private_value[0];
+	struct snd_ice1712 *ice = ak->private_data[0];
+	unsigned char tmp;
+	snd_ice1712_save_gpio_status(ice);
+	tmp = priv->cs_mask = priv->cs_addr = (1 << chip) & ICE1712_6FIRE_AK4524_CS_MASK;
+	tmp |= ICE1712_6FIRE_SERIAL_DATA |
+		ICE1712_6FIRE_SERIAL_CLOCK |
+		ICE1712_6FIRE_RW;
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION,
+			  ice->gpio.direction | tmp);
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp);
+}
+
+/*
+ * CS8404 interface on EWS88MT/D
+ */
+
+static void snd_ice1712_ews_cs8404_spdif_write(struct snd_ice1712 *ice, unsigned char bits)
+{
+	struct ews_spec *spec = ice->spec;
+	unsigned char bytes[2];
+
+	snd_i2c_lock(ice->i2c);
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_EWS88MT:
+	case ICE1712_SUBDEVICE_EWS88MT_NEW:
+	case ICE1712_SUBDEVICE_PHASE88:
+	case ICE1712_SUBDEVICE_TS88:
+		if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_CS8404], &bits, 1)
+		    != 1)
+			goto _error;
+		break;
+	case ICE1712_SUBDEVICE_EWS88D:
+		if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_88D], bytes, 2)
+		    != 2)
+			goto _error;
+		if (bits != bytes[1]) {
+			bytes[1] = bits;
+			if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_88D],
+					      bytes, 2) != 2)
+				goto _error;
+		}
+		break;
+	}
+ _error:
+	snd_i2c_unlock(ice->i2c);
+}
+
+/*
+ */
+
+static void ews88_spdif_default_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+	snd_cs8404_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_bits);
+}
+
+static int ews88_spdif_default_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned int val;
+	int change;
+
+	val = snd_cs8404_encode_spdif_bits(&ucontrol->value.iec958);
+	spin_lock_irq(&ice->reg_lock);
+	change = ice->spdif.cs8403_bits != val;
+	ice->spdif.cs8403_bits = val;
+	if (change && ice->playback_pro_substream == NULL) {
+		spin_unlock_irq(&ice->reg_lock);
+		snd_ice1712_ews_cs8404_spdif_write(ice, val);
+	} else {
+		spin_unlock_irq(&ice->reg_lock);
+	}
+	return change;
+}
+
+static void ews88_spdif_stream_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+	snd_cs8404_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_stream_bits);
+}
+
+static int ews88_spdif_stream_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned int val;
+	int change;
+
+	val = snd_cs8404_encode_spdif_bits(&ucontrol->value.iec958);
+	spin_lock_irq(&ice->reg_lock);
+	change = ice->spdif.cs8403_stream_bits != val;
+	ice->spdif.cs8403_stream_bits = val;
+	if (change && ice->playback_pro_substream != NULL) {
+		spin_unlock_irq(&ice->reg_lock);
+		snd_ice1712_ews_cs8404_spdif_write(ice, val);
+	} else {
+		spin_unlock_irq(&ice->reg_lock);
+	}
+	return change;
+}
+
+
+/* open callback */
+static void ews88_open_spdif(struct snd_ice1712 *ice, struct snd_pcm_substream *substream)
+{
+	ice->spdif.cs8403_stream_bits = ice->spdif.cs8403_bits;
+}
+
+/* set up SPDIF for EWS88MT / EWS88D */
+static void ews88_setup_spdif(struct snd_ice1712 *ice, int rate)
+{
+	unsigned long flags;
+	unsigned char tmp;
+	int change;
+
+	spin_lock_irqsave(&ice->reg_lock, flags);
+	tmp = ice->spdif.cs8403_stream_bits;
+	if (tmp & 0x10)		/* consumer */
+		tmp &= (tmp & 0x01) ? ~0x06 : ~0x60;
+	switch (rate) {
+	case 32000: tmp |= (tmp & 0x01) ? 0x02 : 0x00; break;
+	case 44100: tmp |= (tmp & 0x01) ? 0x06 : 0x40; break;
+	case 48000: tmp |= (tmp & 0x01) ? 0x04 : 0x20; break;
+	default: tmp |= (tmp & 0x01) ? 0x06 : 0x40; break;
+	}
+	change = ice->spdif.cs8403_stream_bits != tmp;
+	ice->spdif.cs8403_stream_bits = tmp;
+	spin_unlock_irqrestore(&ice->reg_lock, flags);
+	if (change)
+		snd_ctl_notify(ice->card, SNDRV_CTL_EVENT_MASK_VALUE, &ice->spdif.stream_ctl->id);
+	snd_ice1712_ews_cs8404_spdif_write(ice, tmp);
+}
+
+
+/*
+ */
+static struct snd_akm4xxx akm_ews88mt __devinitdata = {
+	.num_adcs = 8,
+	.num_dacs = 8,
+	.type = SND_AK4524,
+	.ops = {
+		.lock = ews88mt_ak4524_lock,
+		.unlock = ews88mt_ak4524_unlock
+	}
+};
+
+static struct snd_ak4xxx_private akm_ews88mt_priv __devinitdata = {
+	.caddr = 2,
+	.cif = 1, /* CIF high */
+	.data_mask = ICE1712_EWS88_SERIAL_DATA,
+	.clk_mask = ICE1712_EWS88_SERIAL_CLOCK,
+	.cs_mask = 0,
+	.cs_addr = 0,
+	.cs_none = 0, /* no chip select on gpio */
+	.add_flags = ICE1712_EWS88_RW, /* set rw bit high */
+	.mask_flags = 0,
+};
+
+static struct snd_akm4xxx akm_ewx2496 __devinitdata = {
+	.num_adcs = 2,
+	.num_dacs = 2,
+	.type = SND_AK4524,
+	.ops = {
+		.lock = ewx2496_ak4524_lock
+	}
+};
+
+static struct snd_ak4xxx_private akm_ewx2496_priv __devinitdata = {
+	.caddr = 2,
+	.cif = 1, /* CIF high */
+	.data_mask = ICE1712_EWS88_SERIAL_DATA,
+	.clk_mask = ICE1712_EWS88_SERIAL_CLOCK,
+	.cs_mask = ICE1712_EWX2496_AK4524_CS,
+	.cs_addr = ICE1712_EWX2496_AK4524_CS,
+	.cs_none = 0,
+	.add_flags = ICE1712_EWS88_RW, /* set rw bit high */
+	.mask_flags = 0,
+};
+
+static struct snd_akm4xxx akm_6fire __devinitdata = {
+	.num_adcs = 6,
+	.num_dacs = 6,
+	.type = SND_AK4524,
+	.ops = {
+		.lock = dmx6fire_ak4524_lock
+	}
+};
+
+static struct snd_ak4xxx_private akm_6fire_priv __devinitdata = {
+	.caddr = 2,
+	.cif = 1, /* CIF high */
+	.data_mask = ICE1712_6FIRE_SERIAL_DATA,
+	.clk_mask = ICE1712_6FIRE_SERIAL_CLOCK,
+	.cs_mask = 0,
+	.cs_addr = 0, /* set later */
+	.cs_none = 0,
+	.add_flags = ICE1712_6FIRE_RW, /* set rw bit high */
+	.mask_flags = 0,
+};
+
+/*
+ * initialize the chip
+ */
+
+/* 6fire specific */
+#define PCF9554_REG_INPUT      0
+#define PCF9554_REG_OUTPUT     1
+#define PCF9554_REG_POLARITY   2
+#define PCF9554_REG_CONFIG     3
+
+static int snd_ice1712_6fire_write_pca(struct snd_ice1712 *ice, unsigned char reg, unsigned char data);
+
+static int __devinit snd_ice1712_ews_init(struct snd_ice1712 *ice)
+{
+	int err;
+	struct snd_akm4xxx *ak;
+	struct ews_spec *spec;
+
+	/* set the analog DACs */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_EWX2496:
+		ice->num_total_dacs = 2;
+		ice->num_total_adcs = 2;
+		break;	
+	case ICE1712_SUBDEVICE_EWS88MT:
+	case ICE1712_SUBDEVICE_EWS88MT_NEW:
+	case ICE1712_SUBDEVICE_PHASE88:
+	case ICE1712_SUBDEVICE_TS88:
+		ice->num_total_dacs = 8;
+		ice->num_total_adcs = 8;
+		break;
+	case ICE1712_SUBDEVICE_EWS88D:
+		/* Note: not analog but ADAT I/O */
+		ice->num_total_dacs = 8;
+		ice->num_total_adcs = 8;
+		break;
+	case ICE1712_SUBDEVICE_DMX6FIRE:
+		ice->num_total_dacs = 6;
+		ice->num_total_adcs = 6;
+		break;
+	}
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	ice->spec = spec;
+
+	/* create i2c */
+	if ((err = snd_i2c_bus_create(ice->card, "ICE1712 GPIO 1", NULL, &ice->i2c)) < 0) {
+		snd_printk(KERN_ERR "unable to create I2C bus\n");
+		return err;
+	}
+	ice->i2c->private_data = ice;
+	ice->i2c->hw_ops.bit = &snd_ice1712_ewx_cs8427_bit_ops;
+
+	/* create i2c devices */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_DMX6FIRE:
+		err = snd_i2c_device_create(ice->i2c, "PCF9554",
+					    ICE1712_6FIRE_PCF9554_ADDR,
+					    &spec->i2cdevs[EWS_I2C_6FIRE]);
+		if (err < 0) {
+			snd_printk(KERN_ERR "PCF9554 initialization failed\n");
+			return err;
+		}
+		snd_ice1712_6fire_write_pca(ice, PCF9554_REG_CONFIG, 0x80);
+		break;
+	case ICE1712_SUBDEVICE_EWS88MT:
+	case ICE1712_SUBDEVICE_EWS88MT_NEW:
+	case ICE1712_SUBDEVICE_PHASE88:
+	case ICE1712_SUBDEVICE_TS88:
+
+		err = snd_i2c_device_create(ice->i2c, "CS8404",
+					    ICE1712_EWS88MT_CS8404_ADDR,
+					    &spec->i2cdevs[EWS_I2C_CS8404]);
+		if (err < 0)
+			return err;
+		err = snd_i2c_device_create(ice->i2c, "PCF8574 (1st)",
+					    ICE1712_EWS88MT_INPUT_ADDR,
+					    &spec->i2cdevs[EWS_I2C_PCF1]);
+		if (err < 0)
+			return err;
+		err = snd_i2c_device_create(ice->i2c, "PCF8574 (2nd)",
+					    ICE1712_EWS88MT_OUTPUT_ADDR,
+					    &spec->i2cdevs[EWS_I2C_PCF2]);
+		if (err < 0)
+			return err;
+		/* Check if the front module is connected */
+		if ((err = snd_ice1712_ews88mt_chip_select(ice, 0x0f)) < 0)
+			return err;
+		break;
+	case ICE1712_SUBDEVICE_EWS88D:
+		err = snd_i2c_device_create(ice->i2c, "PCF8575",
+					    ICE1712_EWS88D_PCF_ADDR,
+					    &spec->i2cdevs[EWS_I2C_88D]);
+		if (err < 0)
+			return err;
+		break;
+	}
+
+	/* set up SPDIF interface */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_EWX2496:
+		if ((err = snd_ice1712_init_cs8427(ice, CS8427_BASE_ADDR)) < 0)
+			return err;
+		snd_cs8427_reg_write(ice->cs8427, CS8427_REG_RECVERRMASK, CS8427_UNLOCK | CS8427_CONF | CS8427_BIP | CS8427_PAR);
+		break;
+	case ICE1712_SUBDEVICE_DMX6FIRE:
+		if ((err = snd_ice1712_init_cs8427(ice, ICE1712_6FIRE_CS8427_ADDR)) < 0)
+			return err;
+		snd_cs8427_reg_write(ice->cs8427, CS8427_REG_RECVERRMASK, CS8427_UNLOCK | CS8427_CONF | CS8427_BIP | CS8427_PAR);
+		break;
+	case ICE1712_SUBDEVICE_EWS88MT:
+	case ICE1712_SUBDEVICE_EWS88MT_NEW:
+	case ICE1712_SUBDEVICE_PHASE88:
+	case ICE1712_SUBDEVICE_TS88:
+	case ICE1712_SUBDEVICE_EWS88D:
+		/* set up CS8404 */
+		ice->spdif.ops.open = ews88_open_spdif;
+		ice->spdif.ops.setup_rate = ews88_setup_spdif;
+		ice->spdif.ops.default_get = ews88_spdif_default_get;
+		ice->spdif.ops.default_put = ews88_spdif_default_put;
+		ice->spdif.ops.stream_get = ews88_spdif_stream_get;
+		ice->spdif.ops.stream_put = ews88_spdif_stream_put;
+		/* Set spdif defaults */
+		snd_ice1712_ews_cs8404_spdif_write(ice, ice->spdif.cs8403_bits);
+		break;
+	}
+
+	/* no analog? */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_EWS88D:
+		return 0;
+	}
+
+	/* analog section */
+	ak = ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+	if (! ak)
+		return -ENOMEM;
+	ice->akm_codecs = 1;
+
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_EWS88MT:
+	case ICE1712_SUBDEVICE_EWS88MT_NEW:
+	case ICE1712_SUBDEVICE_PHASE88:
+	case ICE1712_SUBDEVICE_TS88:
+		err = snd_ice1712_akm4xxx_init(ak, &akm_ews88mt, &akm_ews88mt_priv, ice);
+		break;
+	case ICE1712_SUBDEVICE_EWX2496:
+		err = snd_ice1712_akm4xxx_init(ak, &akm_ewx2496, &akm_ewx2496_priv, ice);
+		break;
+	case ICE1712_SUBDEVICE_DMX6FIRE:
+		err = snd_ice1712_akm4xxx_init(ak, &akm_6fire, &akm_6fire_priv, ice);
+		break;
+	default:
+		err = 0;
+	}
+
+	return err;
+}
+
+/*
+ * EWX 24/96 specific controls
+ */
+
+/* i/o sensitivity - this callback is shared among other devices, too */
+static int snd_ice1712_ewx_io_sense_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo){
+
+	static char *texts[2] = {
+		"+4dBu", "-10dBV",
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item >= 2)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ice1712_ewx_io_sense_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char mask = kcontrol->private_value & 0xff;
+	
+	snd_ice1712_save_gpio_status(ice);
+	ucontrol->value.enumerated.item[0] = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & mask ? 1 : 0;
+	snd_ice1712_restore_gpio_status(ice);
+	return 0;
+}
+
+static int snd_ice1712_ewx_io_sense_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char mask = kcontrol->private_value & 0xff;
+	int val, nval;
+
+	if (kcontrol->private_value & (1 << 31))
+		return -EPERM;
+	nval = ucontrol->value.enumerated.item[0] ? mask : 0;
+	snd_ice1712_save_gpio_status(ice);
+	val = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+	nval |= val & ~mask;
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, nval);
+	snd_ice1712_restore_gpio_status(ice);
+	return val != nval;
+}
+
+static struct snd_kcontrol_new snd_ice1712_ewx2496_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Input Sensitivity Switch",
+		.info = snd_ice1712_ewx_io_sense_info,
+		.get = snd_ice1712_ewx_io_sense_get,
+		.put = snd_ice1712_ewx_io_sense_put,
+		.private_value = ICE1712_EWX2496_AIN_SEL,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Output Sensitivity Switch",
+		.info = snd_ice1712_ewx_io_sense_info,
+		.get = snd_ice1712_ewx_io_sense_get,
+		.put = snd_ice1712_ewx_io_sense_put,
+		.private_value = ICE1712_EWX2496_AOUT_SEL,
+	},
+};
+
+
+/*
+ * EWS88MT specific controls
+ */
+/* analog output sensitivity;; address 0x48 bit 6 */
+static int snd_ice1712_ews88mt_output_sense_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct ews_spec *spec = ice->spec;
+	unsigned char data;
+
+	snd_i2c_lock(ice->i2c);
+	if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF2], &data, 1) != 1) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	snd_i2c_unlock(ice->i2c);
+	ucontrol->value.enumerated.item[0] = data & ICE1712_EWS88MT_OUTPUT_SENSE ? 1 : 0; /* high = -10dBV, low = +4dBu */
+	return 0;
+}
+
+/* analog output sensitivity;; address 0x48 bit 6 */
+static int snd_ice1712_ews88mt_output_sense_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct ews_spec *spec = ice->spec;
+	unsigned char data, ndata;
+
+	snd_i2c_lock(ice->i2c);
+	if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF2], &data, 1) != 1) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	ndata = (data & ~ICE1712_EWS88MT_OUTPUT_SENSE) | (ucontrol->value.enumerated.item[0] ? ICE1712_EWS88MT_OUTPUT_SENSE : 0);
+	if (ndata != data && snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF2],
+					       &ndata, 1) != 1) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	snd_i2c_unlock(ice->i2c);
+	return ndata != data;
+}
+
+/* analog input sensitivity; address 0x46 */
+static int snd_ice1712_ews88mt_input_sense_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct ews_spec *spec = ice->spec;
+	int channel = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	unsigned char data;
+
+	if (snd_BUG_ON(channel < 0 || channel > 7))
+		return 0;
+	snd_i2c_lock(ice->i2c);
+	if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF1], &data, 1) != 1) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	/* reversed; high = +4dBu, low = -10dBV */
+	ucontrol->value.enumerated.item[0] = data & (1 << channel) ? 0 : 1;
+	snd_i2c_unlock(ice->i2c);
+	return 0;
+}
+
+/* analog output sensitivity; address 0x46 */
+static int snd_ice1712_ews88mt_input_sense_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct ews_spec *spec = ice->spec;
+	int channel = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	unsigned char data, ndata;
+
+	if (snd_BUG_ON(channel < 0 || channel > 7))
+		return 0;
+	snd_i2c_lock(ice->i2c);
+	if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF1], &data, 1) != 1) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	ndata = (data & ~(1 << channel)) | (ucontrol->value.enumerated.item[0] ? 0 : (1 << channel));
+	if (ndata != data && snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF1],
+					       &ndata, 1) != 1) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	snd_i2c_unlock(ice->i2c);
+	return ndata != data;
+}
+
+static struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Input Sensitivity Switch",
+	.info = snd_ice1712_ewx_io_sense_info,
+	.get = snd_ice1712_ews88mt_input_sense_get,
+	.put = snd_ice1712_ews88mt_input_sense_put,
+	.count = 8,
+};
+
+static struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Output Sensitivity Switch",
+	.info = snd_ice1712_ewx_io_sense_info,
+	.get = snd_ice1712_ews88mt_output_sense_get,
+	.put = snd_ice1712_ews88mt_output_sense_put,
+};
+
+
+/*
+ * EWS88D specific controls
+ */
+
+#define snd_ice1712_ews88d_control_info		snd_ctl_boolean_mono_info
+
+static int snd_ice1712_ews88d_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct ews_spec *spec = ice->spec;
+	int shift = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value >> 8) & 1;
+	unsigned char data[2];
+	
+	snd_i2c_lock(ice->i2c);
+	if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_88D], data, 2) != 2) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	snd_i2c_unlock(ice->i2c);
+	data[0] = (data[shift >> 3] >> (shift & 7)) & 0x01;
+	if (invert)
+		data[0] ^= 0x01;
+	ucontrol->value.integer.value[0] = data[0];
+	return 0;
+}
+
+static int snd_ice1712_ews88d_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct ews_spec *spec = ice->spec;
+	int shift = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value >> 8) & 1;
+	unsigned char data[2], ndata[2];
+	int change;
+
+	snd_i2c_lock(ice->i2c);
+	if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_88D], data, 2) != 2) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	ndata[shift >> 3] = data[shift >> 3] & ~(1 << (shift & 7));
+	if (invert) {
+		if (! ucontrol->value.integer.value[0])
+			ndata[shift >> 3] |= (1 << (shift & 7));
+	} else {
+		if (ucontrol->value.integer.value[0])
+			ndata[shift >> 3] |= (1 << (shift & 7));
+	}
+	change = (data[shift >> 3] != ndata[shift >> 3]);
+	if (change &&
+	    snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_88D], data, 2) != 2) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	snd_i2c_unlock(ice->i2c);
+	return change;
+}
+
+#define EWS88D_CONTROL(xiface, xname, xshift, xinvert, xaccess) \
+{ .iface = xiface,\
+  .name = xname,\
+  .access = xaccess,\
+  .info = snd_ice1712_ews88d_control_info,\
+  .get = snd_ice1712_ews88d_control_get,\
+  .put = snd_ice1712_ews88d_control_put,\
+  .private_value = xshift | (xinvert << 8),\
+}
+
+static struct snd_kcontrol_new snd_ice1712_ews88d_controls[] __devinitdata = {
+	EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "IEC958 Input Optical", 0, 1, 0), /* inverted */
+	EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "ADAT Output Optical", 1, 0, 0),
+	EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "ADAT External Master Clock", 2, 0, 0),
+	EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "Enable ADAT", 3, 0, 0),
+	EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "ADAT Through", 4, 1, 0),
+};
+
+
+/*
+ * DMX 6Fire specific controls
+ */
+
+static int snd_ice1712_6fire_read_pca(struct snd_ice1712 *ice, unsigned char reg)
+{
+	unsigned char byte;
+	struct ews_spec *spec = ice->spec;
+
+	snd_i2c_lock(ice->i2c);
+	byte = reg;
+	snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_6FIRE], &byte, 1);
+	byte = 0;
+	if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_6FIRE], &byte, 1) != 1) {
+		snd_i2c_unlock(ice->i2c);
+		printk(KERN_ERR "cannot read pca\n");
+		return -EIO;
+	}
+	snd_i2c_unlock(ice->i2c);
+	return byte;
+}
+
+static int snd_ice1712_6fire_write_pca(struct snd_ice1712 *ice, unsigned char reg, unsigned char data)
+{
+	unsigned char bytes[2];
+	struct ews_spec *spec = ice->spec;
+
+	snd_i2c_lock(ice->i2c);
+	bytes[0] = reg;
+	bytes[1] = data;
+	if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_6FIRE], bytes, 2) != 2) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	snd_i2c_unlock(ice->i2c);
+	return 0;
+}
+
+#define snd_ice1712_6fire_control_info		snd_ctl_boolean_mono_info
+
+static int snd_ice1712_6fire_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int shift = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value >> 8) & 1;
+	int data;
+	
+	if ((data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT)) < 0)
+		return data;
+	data = (data >> shift) & 1;
+	if (invert)
+		data ^= 1;
+	ucontrol->value.integer.value[0] = data;
+	return 0;
+}
+
+static int snd_ice1712_6fire_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int shift = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value >> 8) & 1;
+	int data, ndata;
+	
+	if ((data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT)) < 0)
+		return data;
+	ndata = data & ~(1 << shift);
+	if (ucontrol->value.integer.value[0])
+		ndata |= (1 << shift);
+	if (invert)
+		ndata ^= (1 << shift);
+	if (data != ndata) {
+		snd_ice1712_6fire_write_pca(ice, PCF9554_REG_OUTPUT, (unsigned char)ndata);
+		return 1;
+	}
+	return 0;
+}
+
+static int snd_ice1712_6fire_select_input_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = {
+		"Internal", "Front Input", "Rear Input", "Wave Table"
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item >= 4)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+     
+static int snd_ice1712_6fire_select_input_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int data;
+	
+	if ((data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT)) < 0)
+		return data;
+	ucontrol->value.integer.value[0] = data & 3;
+	return 0;
+}
+
+static int snd_ice1712_6fire_select_input_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int data, ndata;
+	
+	if ((data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT)) < 0)
+		return data;
+	ndata = data & ~3;
+	ndata |= (ucontrol->value.integer.value[0] & 3);
+	if (data != ndata) {
+		snd_ice1712_6fire_write_pca(ice, PCF9554_REG_OUTPUT, (unsigned char)ndata);
+		return 1;
+	}
+	return 0;
+}
+
+
+#define DMX6FIRE_CONTROL(xname, xshift, xinvert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
+  .name = xname,\
+  .info = snd_ice1712_6fire_control_info,\
+  .get = snd_ice1712_6fire_control_get,\
+  .put = snd_ice1712_6fire_control_put,\
+  .private_value = xshift | (xinvert << 8),\
+}
+
+static struct snd_kcontrol_new snd_ice1712_6fire_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Input Select",
+		.info = snd_ice1712_6fire_select_input_info,
+		.get = snd_ice1712_6fire_select_input_get,
+		.put = snd_ice1712_6fire_select_input_put,
+	},
+	DMX6FIRE_CONTROL("Front Digital Input Switch", 2, 1),
+	// DMX6FIRE_CONTROL("Master Clock Select", 3, 0),
+	DMX6FIRE_CONTROL("Optical Digital Input Switch", 4, 0),
+	DMX6FIRE_CONTROL("Phono Analog Input Switch", 5, 0),
+	DMX6FIRE_CONTROL("Breakbox LED", 6, 0),
+};
+
+
+static int __devinit snd_ice1712_ews_add_controls(struct snd_ice1712 *ice)
+{
+	unsigned int idx;
+	int err;
+	
+	/* all terratec cards have spdif, but cs8427 module builds it's own controls */
+	if (ice->cs8427 == NULL) {
+		err = snd_ice1712_spdif_build_controls(ice);
+		if (err < 0)
+			return err;
+	}
+
+	/* ak4524 controls */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_EWX2496:
+	case ICE1712_SUBDEVICE_EWS88MT:
+	case ICE1712_SUBDEVICE_EWS88MT_NEW:
+	case ICE1712_SUBDEVICE_PHASE88:
+	case ICE1712_SUBDEVICE_TS88:
+	case ICE1712_SUBDEVICE_DMX6FIRE:
+		err = snd_ice1712_akm4xxx_build_controls(ice);
+		if (err < 0)
+			return err;
+		break;
+	}
+
+	/* card specific controls */
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_EWX2496:
+		for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_ewx2496_controls); idx++) {
+			err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ewx2496_controls[idx], ice));
+			if (err < 0)
+				return err;
+		}
+		break;
+	case ICE1712_SUBDEVICE_EWS88MT:
+	case ICE1712_SUBDEVICE_EWS88MT_NEW:
+	case ICE1712_SUBDEVICE_PHASE88:
+	case ICE1712_SUBDEVICE_TS88:
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ews88mt_input_sense, ice));
+		if (err < 0)
+			return err;
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ews88mt_output_sense, ice));
+		if (err < 0)
+			return err;
+		break;
+	case ICE1712_SUBDEVICE_EWS88D:
+		for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_ews88d_controls); idx++) {
+			err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ews88d_controls[idx], ice));
+			if (err < 0)
+				return err;
+		}
+		break;
+	case ICE1712_SUBDEVICE_DMX6FIRE:
+		for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_6fire_controls); idx++) {
+			err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_6fire_controls[idx], ice));
+			if (err < 0)
+				return err;
+		}
+		break;
+	}
+	return 0;
+}
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_ice1712_ews_cards[] __devinitdata = {
+	{
+		.subvendor = ICE1712_SUBDEVICE_EWX2496,
+		.name = "TerraTec EWX24/96",
+		.model = "ewx2496",
+		.chip_init = snd_ice1712_ews_init,
+		.build_controls = snd_ice1712_ews_add_controls,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_EWS88MT,
+		.name = "TerraTec EWS88MT",
+		.model = "ews88mt",
+		.chip_init = snd_ice1712_ews_init,
+		.build_controls = snd_ice1712_ews_add_controls,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_EWS88MT_NEW,
+		.name = "TerraTec EWS88MT",
+		.model = "ews88mt_new",
+		.chip_init = snd_ice1712_ews_init,
+		.build_controls = snd_ice1712_ews_add_controls,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_PHASE88,
+		.name = "TerraTec Phase88",
+		.model = "phase88",
+		.chip_init = snd_ice1712_ews_init,
+		.build_controls = snd_ice1712_ews_add_controls,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_TS88,
+		.name = "terrasoniq TS88",
+		.model = "phase88",
+		.chip_init = snd_ice1712_ews_init,
+		.build_controls = snd_ice1712_ews_add_controls,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_EWS88D,
+		.name = "TerraTec EWS88D",
+		.model = "ews88d",
+		.chip_init = snd_ice1712_ews_init,
+		.build_controls = snd_ice1712_ews_add_controls,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_DMX6FIRE,
+		.name = "TerraTec DMX6Fire",
+		.model = "dmx6fire",
+		.chip_init = snd_ice1712_ews_init,
+		.build_controls = snd_ice1712_ews_add_controls,
+		.mpu401_1_name = "MIDI-Front DMX6fire",
+		.mpu401_2_name = "Wavetable DMX6fire",
+		.mpu401_2_info_flags = MPU401_INFO_OUTPUT,
+	},
+	{ } /* terminator */
+};
diff --git a/linux/sound/pci/ice1712/ews.h b/linux/sound/pci/ice1712/ews.h
new file mode 100644
index 0000000..1c44371
--- /dev/null
+++ b/linux/sound/pci/ice1712/ews.h
@@ -0,0 +1,86 @@
+#ifndef __SOUND_EWS_H
+#define __SOUND_EWS_H
+
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *   Lowlevel functions for Terratec EWS88MT/D, EWX24/96, DMX 6Fire
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *                    2002 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#define EWS_DEVICE_DESC \
+		"{TerraTec,EWX 24/96},"\
+		"{TerraTec,EWS 88MT},"\
+		"{TerraTec,EWS 88D},"\
+		"{TerraTec,DMX 6Fire},"\
+		"{TerraTec,Phase 88}," \
+		"{terrasoniq,TS 88},"
+
+#define ICE1712_SUBDEVICE_EWX2496	0x3b153011
+#define ICE1712_SUBDEVICE_EWS88MT	0x3b151511
+#define ICE1712_SUBDEVICE_EWS88MT_NEW	0x3b152511
+#define ICE1712_SUBDEVICE_EWS88D	0x3b152b11
+#define ICE1712_SUBDEVICE_DMX6FIRE	0x3b153811
+#define ICE1712_SUBDEVICE_PHASE88	0x3b155111
+#define ICE1712_SUBDEVICE_TS88   	0x3b157c11
+
+/* entry point */
+extern struct snd_ice1712_card_info snd_ice1712_ews_cards[];
+
+
+/* TerraTec EWX 24/96 configuration definitions */
+
+#define ICE1712_EWX2496_AK4524_CS	0x01	/* AK4524 chip select; low = active */
+#define ICE1712_EWX2496_AIN_SEL		0x02	/* input sensitivity switch; high = louder */
+#define ICE1712_EWX2496_AOUT_SEL	0x04	/* output sensitivity switch; high = louder */
+#define ICE1712_EWX2496_RW		0x08	/* read/write switch for i2c; high = write  */
+#define ICE1712_EWX2496_SERIAL_DATA	0x10	/* i2c & ak4524 data */
+#define ICE1712_EWX2496_SERIAL_CLOCK	0x20	/* i2c & ak4524 clock */
+#define ICE1712_EWX2496_TX2		0x40	/* MIDI2 (not used) */
+#define ICE1712_EWX2496_RX2		0x80	/* MIDI2 (not used) */
+
+/* TerraTec EWS 88MT/D configuration definitions */
+/* RW, SDA snd SCLK are identical with EWX24/96 */
+#define ICE1712_EWS88_CS8414_RATE	0x07	/* CS8414 sample rate: gpio 0-2 */
+#define ICE1712_EWS88_RW		0x08	/* read/write switch for i2c; high = write  */
+#define ICE1712_EWS88_SERIAL_DATA	0x10	/* i2c & ak4524 data */
+#define ICE1712_EWS88_SERIAL_CLOCK	0x20	/* i2c & ak4524 clock */
+#define ICE1712_EWS88_TX2		0x40	/* MIDI2 (only on 88D) */
+#define ICE1712_EWS88_RX2		0x80	/* MIDI2 (only on 88D) */
+
+/* i2c address */
+#define ICE1712_EWS88MT_CS8404_ADDR	(0x40>>1)
+#define ICE1712_EWS88MT_INPUT_ADDR	(0x46>>1)
+#define ICE1712_EWS88MT_OUTPUT_ADDR	(0x48>>1)
+#define ICE1712_EWS88MT_OUTPUT_SENSE	0x40	/* mask */
+#define ICE1712_EWS88D_PCF_ADDR		(0x40>>1)
+
+/* TerraTec DMX 6Fire configuration definitions */
+#define ICE1712_6FIRE_AK4524_CS_MASK	0x07	/* AK4524 chip select #1-#3 */
+#define ICE1712_6FIRE_RW		0x08	/* read/write switch for i2c; high = write  */
+#define ICE1712_6FIRE_SERIAL_DATA	0x10	/* i2c & ak4524 data */
+#define ICE1712_6FIRE_SERIAL_CLOCK	0x20	/* i2c & ak4524 clock */
+#define ICE1712_6FIRE_TX2		0x40	/* MIDI2 */
+#define ICE1712_6FIRE_RX2		0x80	/* MIDI2 */
+
+#define ICE1712_6FIRE_PCF9554_ADDR	(0x40>>1)
+#define ICE1712_6FIRE_CS8427_ADDR	(0x22)
+
+#endif /* __SOUND_EWS_H */
diff --git a/linux/sound/pci/ice1712/hoontech.c b/linux/sound/pci/ice1712/hoontech.c
new file mode 100644
index 0000000..6914189
--- /dev/null
+++ b/linux/sound/pci/ice1712/hoontech.c
@@ -0,0 +1,360 @@
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *   Lowlevel functions for Hoontech STDSP24
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "hoontech.h"
+
+/* Hoontech-specific setting */
+struct hoontech_spec {
+	unsigned char boxbits[4];
+	unsigned int config;
+	unsigned short boxconfig[4];
+};
+
+static void __devinit snd_ice1712_stdsp24_gpio_write(struct snd_ice1712 *ice, unsigned char byte)
+{
+	byte |= ICE1712_STDSP24_CLOCK_BIT;
+	udelay(100);
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte);
+	byte &= ~ICE1712_STDSP24_CLOCK_BIT;
+	udelay(100);
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte);
+	byte |= ICE1712_STDSP24_CLOCK_BIT;
+	udelay(100);
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte);
+}
+
+static void __devinit snd_ice1712_stdsp24_darear(struct snd_ice1712 *ice, int activate)
+{
+	struct hoontech_spec *spec = ice->spec;
+	mutex_lock(&ice->gpio_mutex);
+	ICE1712_STDSP24_0_DAREAR(spec->boxbits, activate);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[0]);
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static void __devinit snd_ice1712_stdsp24_mute(struct snd_ice1712 *ice, int activate)
+{
+	struct hoontech_spec *spec = ice->spec;
+	mutex_lock(&ice->gpio_mutex);
+	ICE1712_STDSP24_3_MUTE(spec->boxbits, activate);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static void __devinit snd_ice1712_stdsp24_insel(struct snd_ice1712 *ice, int activate)
+{
+	struct hoontech_spec *spec = ice->spec;
+	mutex_lock(&ice->gpio_mutex);
+	ICE1712_STDSP24_3_INSEL(spec->boxbits, activate);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static void __devinit snd_ice1712_stdsp24_box_channel(struct snd_ice1712 *ice, int box, int chn, int activate)
+{
+	struct hoontech_spec *spec = ice->spec;
+
+	mutex_lock(&ice->gpio_mutex);
+
+	/* select box */
+	ICE1712_STDSP24_0_BOX(spec->boxbits, box);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[0]);
+
+	/* prepare for write */
+	if (chn == 3)
+		ICE1712_STDSP24_2_CHN4(spec->boxbits, 0);
+	ICE1712_STDSP24_2_MIDI1(spec->boxbits, activate);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+
+	ICE1712_STDSP24_1_CHN1(spec->boxbits, 1);
+	ICE1712_STDSP24_1_CHN2(spec->boxbits, 1);
+	ICE1712_STDSP24_1_CHN3(spec->boxbits, 1);
+	ICE1712_STDSP24_2_CHN4(spec->boxbits, 1);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[1]);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+	udelay(100);
+	if (chn == 3) {
+		ICE1712_STDSP24_2_CHN4(spec->boxbits, 0);
+		snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+	} else {
+		switch (chn) {
+		case 0:	ICE1712_STDSP24_1_CHN1(spec->boxbits, 0); break;
+		case 1:	ICE1712_STDSP24_1_CHN2(spec->boxbits, 0); break;
+		case 2:	ICE1712_STDSP24_1_CHN3(spec->boxbits, 0); break;
+		}
+		snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[1]);
+	}
+	udelay(100);
+	ICE1712_STDSP24_1_CHN1(spec->boxbits, 1);
+	ICE1712_STDSP24_1_CHN2(spec->boxbits, 1);
+	ICE1712_STDSP24_1_CHN3(spec->boxbits, 1);
+	ICE1712_STDSP24_2_CHN4(spec->boxbits, 1);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[1]);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+	udelay(100);
+
+	ICE1712_STDSP24_2_MIDI1(spec->boxbits, 0);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static void __devinit snd_ice1712_stdsp24_box_midi(struct snd_ice1712 *ice, int box, int master)
+{
+	struct hoontech_spec *spec = ice->spec;
+
+	mutex_lock(&ice->gpio_mutex);
+
+	/* select box */
+	ICE1712_STDSP24_0_BOX(spec->boxbits, box);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[0]);
+
+	ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 1);
+	ICE1712_STDSP24_2_MIDI1(spec->boxbits, master);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+
+	udelay(100);
+	
+	ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 0);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+	
+	mdelay(10);
+	
+	ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 1);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static void __devinit snd_ice1712_stdsp24_midi2(struct snd_ice1712 *ice, int activate)
+{
+	struct hoontech_spec *spec = ice->spec;
+	mutex_lock(&ice->gpio_mutex);
+	ICE1712_STDSP24_3_MIDI2(spec->boxbits, activate);
+	snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static int __devinit snd_ice1712_hoontech_init(struct snd_ice1712 *ice)
+{
+	struct hoontech_spec *spec;
+	int box, chn;
+
+	ice->num_total_dacs = 8;
+	ice->num_total_adcs = 8;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	ice->spec = spec;
+
+	ICE1712_STDSP24_SET_ADDR(spec->boxbits, 0);
+	ICE1712_STDSP24_CLOCK(spec->boxbits, 0, 1);
+	ICE1712_STDSP24_0_BOX(spec->boxbits, 0);
+	ICE1712_STDSP24_0_DAREAR(spec->boxbits, 0);
+
+	ICE1712_STDSP24_SET_ADDR(spec->boxbits, 1);
+	ICE1712_STDSP24_CLOCK(spec->boxbits, 1, 1);
+	ICE1712_STDSP24_1_CHN1(spec->boxbits, 1);
+	ICE1712_STDSP24_1_CHN2(spec->boxbits, 1);
+	ICE1712_STDSP24_1_CHN3(spec->boxbits, 1);
+	
+	ICE1712_STDSP24_SET_ADDR(spec->boxbits, 2);
+	ICE1712_STDSP24_CLOCK(spec->boxbits, 2, 1);
+	ICE1712_STDSP24_2_CHN4(spec->boxbits, 1);
+	ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 1);
+	ICE1712_STDSP24_2_MIDI1(spec->boxbits, 0);
+
+	ICE1712_STDSP24_SET_ADDR(spec->boxbits, 3);
+	ICE1712_STDSP24_CLOCK(spec->boxbits, 3, 1);
+	ICE1712_STDSP24_3_MIDI2(spec->boxbits, 0);
+	ICE1712_STDSP24_3_MUTE(spec->boxbits, 1);
+	ICE1712_STDSP24_3_INSEL(spec->boxbits, 0);
+
+	/* let's go - activate only functions in first box */
+	spec->config = 0;
+			    /* ICE1712_STDSP24_MUTE |
+			       ICE1712_STDSP24_INSEL |
+			       ICE1712_STDSP24_DAREAR; */
+	/*  These boxconfigs have caused problems in the past.
+	 *  The code is not optimal, but should now enable a working config to
+	 *  be achieved.
+	 *  ** MIDI IN can only be configured on one box **
+	 *  ICE1712_STDSP24_BOX_MIDI1 needs to be set for that box.
+	 *  Tests on a ADAC2000 box suggest the box config flags do not
+	 *  work as would be expected, and the inputs are crossed.
+	 *  Setting ICE1712_STDSP24_BOX_MIDI1 and ICE1712_STDSP24_BOX_MIDI2
+	 *  on the same box connects MIDI-In to both 401 uarts; both outputs
+	 *  are then active on all boxes.
+	 *  The default config here sets up everything on the first box.
+	 *  Alan Horstmann  5.2.2008
+	 */
+	spec->boxconfig[0] = ICE1712_STDSP24_BOX_CHN1 |
+				     ICE1712_STDSP24_BOX_CHN2 |
+				     ICE1712_STDSP24_BOX_CHN3 |
+				     ICE1712_STDSP24_BOX_CHN4 |
+				     ICE1712_STDSP24_BOX_MIDI1 |
+				     ICE1712_STDSP24_BOX_MIDI2;
+	spec->boxconfig[1] = 
+	spec->boxconfig[2] = 
+	spec->boxconfig[3] = 0;
+	snd_ice1712_stdsp24_darear(ice,
+		(spec->config & ICE1712_STDSP24_DAREAR) ? 1 : 0);
+	snd_ice1712_stdsp24_mute(ice,
+		(spec->config & ICE1712_STDSP24_MUTE) ? 1 : 0);
+	snd_ice1712_stdsp24_insel(ice,
+		(spec->config & ICE1712_STDSP24_INSEL) ? 1 : 0);
+	for (box = 0; box < 4; box++) {
+		if (spec->boxconfig[box] & ICE1712_STDSP24_BOX_MIDI2)
+                        snd_ice1712_stdsp24_midi2(ice, 1);
+		for (chn = 0; chn < 4; chn++)
+			snd_ice1712_stdsp24_box_channel(ice, box, chn,
+				(spec->boxconfig[box] & (1 << chn)) ? 1 : 0);
+		if (spec->boxconfig[box] & ICE1712_STDSP24_BOX_MIDI1)
+			snd_ice1712_stdsp24_box_midi(ice, box, 1);
+	}
+
+	return 0;
+}
+
+/*
+ * AK4524 access
+ */
+
+/* start callback for STDSP24 with modified hardware */
+static void stdsp24_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+	struct snd_ice1712 *ice = ak->private_data[0];
+	unsigned char tmp;
+	snd_ice1712_save_gpio_status(ice);
+	tmp =	ICE1712_STDSP24_SERIAL_DATA |
+		ICE1712_STDSP24_SERIAL_CLOCK |
+		ICE1712_STDSP24_AK4524_CS;
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION,
+			  ice->gpio.direction | tmp);
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp);
+}
+
+static int __devinit snd_ice1712_value_init(struct snd_ice1712 *ice)
+{
+	/* Hoontech STDSP24 with modified hardware */
+	static struct snd_akm4xxx akm_stdsp24_mv __devinitdata = {
+		.num_adcs = 2,
+		.num_dacs = 2,
+		.type = SND_AK4524,
+		.ops = {
+			.lock = stdsp24_ak4524_lock
+		}
+	};
+
+	static struct snd_ak4xxx_private akm_stdsp24_mv_priv __devinitdata = {
+		.caddr = 2,
+		.cif = 1, /* CIF high */
+		.data_mask = ICE1712_STDSP24_SERIAL_DATA,
+		.clk_mask = ICE1712_STDSP24_SERIAL_CLOCK,
+		.cs_mask = ICE1712_STDSP24_AK4524_CS,
+		.cs_addr = ICE1712_STDSP24_AK4524_CS,
+		.cs_none = 0,
+		.add_flags = 0,
+	};
+
+	int err;
+	struct snd_akm4xxx *ak;
+
+	/* set the analog DACs */
+	ice->num_total_dacs = 2;
+
+	/* set the analog ADCs */
+	ice->num_total_adcs = 2;
+	
+	/* analog section */
+	ak = ice->akm = kmalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+	if (! ak)
+		return -ENOMEM;
+	ice->akm_codecs = 1;
+
+	err = snd_ice1712_akm4xxx_init(ak, &akm_stdsp24_mv, &akm_stdsp24_mv_priv, ice);
+	if (err < 0)
+		return err;
+
+	/* ak4524 controls */
+	err = snd_ice1712_akm4xxx_build_controls(ice);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int __devinit snd_ice1712_ez8_init(struct snd_ice1712 *ice)
+{
+	ice->gpio.write_mask = ice->eeprom.gpiomask;
+	ice->gpio.direction = ice->eeprom.gpiodir;
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ice->eeprom.gpiomask);
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, ice->eeprom.gpiodir);
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, ice->eeprom.gpiostate);
+	return 0;
+}
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_ice1712_hoontech_cards[] __devinitdata = {
+	{
+		.subvendor = ICE1712_SUBDEVICE_STDSP24,
+		.name = "Hoontech SoundTrack Audio DSP24",
+		.model = "dsp24",
+		.chip_init = snd_ice1712_hoontech_init,
+		.mpu401_1_name = "MIDI-1 Hoontech/STA DSP24",
+		.mpu401_2_name = "MIDI-2 Hoontech/STA DSP24",
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_STDSP24_VALUE,	/* a dummy id */
+		.name = "Hoontech SoundTrack Audio DSP24 Value",
+		.model = "dsp24_value",
+		.chip_init = snd_ice1712_value_init,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_STDSP24_MEDIA7_1,
+		.name = "Hoontech STA DSP24 Media 7.1",
+		.model = "dsp24_71",
+		.chip_init = snd_ice1712_hoontech_init,
+	},
+	{
+		.subvendor = ICE1712_SUBDEVICE_EVENT_EZ8,	/* a dummy id */
+		.name = "Event Electronics EZ8",
+		.model = "ez8",
+		.chip_init = snd_ice1712_ez8_init,
+	},
+	{ } /* terminator */
+};
diff --git a/linux/sound/pci/ice1712/hoontech.h b/linux/sound/pci/ice1712/hoontech.h
new file mode 100644
index 0000000..cc1da1e
--- /dev/null
+++ b/linux/sound/pci/ice1712/hoontech.h
@@ -0,0 +1,77 @@
+#ifndef __SOUND_HOONTECH_H
+#define __SOUND_HOONTECH_H
+
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *   Lowlevel functions for Hoontech STDSP24
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#define  HOONTECH_DEVICE_DESC \
+	"{Hoontech,SoundTrack DSP 24}," \
+	"{Hoontech,SoundTrack DSP 24 Value}," \
+	"{Hoontech,SoundTrack DSP 24 Media 7.1}," \
+	"{Event Electronics,EZ8},"
+
+#define ICE1712_SUBDEVICE_STDSP24		0x12141217	/* Hoontech SoundTrack Audio DSP 24 */
+#define ICE1712_SUBDEVICE_STDSP24_VALUE		0x00010010	/* A dummy id for Hoontech SoundTrack Audio DSP 24 Value */
+#define ICE1712_SUBDEVICE_STDSP24_MEDIA7_1	0x16141217	/* Hoontech ST Audio DSP24 Media 7.1 */
+#define ICE1712_SUBDEVICE_EVENT_EZ8		0x00010001	/* A dummy id for EZ8 */
+
+extern struct snd_ice1712_card_info snd_ice1712_hoontech_cards[];
+
+
+/* Hoontech SoundTrack Audio DSP 24 GPIO definitions */
+
+#define ICE1712_STDSP24_0_BOX(r, x)	r[0] = ((r[0] & ~3) | ((x)&3))
+#define ICE1712_STDSP24_0_DAREAR(r, x)	r[0] = ((r[0] & ~4) | (((x)&1)<<2))
+#define ICE1712_STDSP24_1_CHN1(r, x)	r[1] = ((r[1] & ~1) | ((x)&1))
+#define ICE1712_STDSP24_1_CHN2(r, x)	r[1] = ((r[1] & ~2) | (((x)&1)<<1))
+#define ICE1712_STDSP24_1_CHN3(r, x)	r[1] = ((r[1] & ~4) | (((x)&1)<<2))
+#define ICE1712_STDSP24_2_CHN4(r, x)	r[2] = ((r[2] & ~1) | ((x)&1))
+#define ICE1712_STDSP24_2_MIDIIN(r, x)	r[2] = ((r[2] & ~2) | (((x)&1)<<1))
+#define ICE1712_STDSP24_2_MIDI1(r, x)	r[2] = ((r[2] & ~4) | (((x)&1)<<2))
+#define ICE1712_STDSP24_3_MIDI2(r, x)	r[3] = ((r[3] & ~1) | ((x)&1))
+#define ICE1712_STDSP24_3_MUTE(r, x)	r[3] = ((r[3] & ~2) | (((x)&1)<<1))
+#define ICE1712_STDSP24_3_INSEL(r, x)	r[3] = ((r[3] & ~4) | (((x)&1)<<2))
+#define ICE1712_STDSP24_SET_ADDR(r, a)	r[a&3] = ((r[a&3] & ~0x18) | (((a)&3)<<3))
+#define ICE1712_STDSP24_CLOCK(r, a, c)	r[a&3] = ((r[a&3] & ~0x20) | (((c)&1)<<5))
+#define ICE1712_STDSP24_CLOCK_BIT	(1<<5)
+
+/* Hoontech SoundTrack Audio DSP 24 box configuration definitions */
+
+#define ICE1712_STDSP24_DAREAR		(1<<0)
+#define ICE1712_STDSP24_MUTE		(1<<1)
+#define ICE1712_STDSP24_INSEL		(1<<2)
+
+#define ICE1712_STDSP24_BOX_CHN1	(1<<0)	/* input channel 1 */
+#define ICE1712_STDSP24_BOX_CHN2	(1<<1)	/* input channel 2 */
+#define ICE1712_STDSP24_BOX_CHN3	(1<<2)	/* input channel 3 */
+#define ICE1712_STDSP24_BOX_CHN4	(1<<3)	/* input channel 4 */
+#define ICE1712_STDSP24_BOX_MIDI1	(1<<8)
+#define ICE1712_STDSP24_BOX_MIDI2	(1<<9)
+
+/* Hoontech SoundTrack Audio DSP 24 Value definitions for modified hardware */
+
+#define ICE1712_STDSP24_AK4524_CS	0x03	/* AK4524 chip select; low = active */
+#define ICE1712_STDSP24_SERIAL_DATA	0x0c	/* ak4524 data */
+#define ICE1712_STDSP24_SERIAL_CLOCK	0x30	/* ak4524 clock */
+
+#endif /* __SOUND_HOONTECH_H */
diff --git a/linux/sound/pci/ice1712/ice1712.c b/linux/sound/pci/ice1712/ice1712.c
new file mode 100644
index 0000000..4fc6d8b
--- /dev/null
+++ b/linux/sound/pci/ice1712/ice1712.c
@@ -0,0 +1,2822 @@
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *	Copyright (c) 2000 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
+ *
+ */
+
+/*
+  NOTES:
+  - spdif nonaudio consumer mode does not work (at least with my
+    Sony STR-DB830)
+*/
+
+/*
+ * Changes:
+ *
+ *  2002.09.09	Takashi Iwai <tiwai@suse.de>
+ *	split the code to several files.  each low-level routine
+ *	is stored in the local file and called from registration
+ *	function from card_info struct.
+ *
+ *  2002.11.26	James Stafford <jstafford@ampltd.com>
+ *	Added support for VT1724 (Envy24HT)
+ *	I have left out support for 176.4 and 192 KHz for the moment.
+ *  I also haven't done anything with the internal S/PDIF transmitter or the MPU-401
+ *
+ *  2003.02.20  Taksahi Iwai <tiwai@suse.de>
+ *	Split vt1724 part to an independent driver.
+ *	The GPIO is accessed through the callback functions now.
+ *
+ * 2004.03.31 Doug McLain <nostar@comcast.net>
+ *    Added support for Event Electronics EZ8 card to hoontech.c.
+ */
+
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/cs8427.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <sound/asoundef.h>
+
+#include "ice1712.h"
+
+/* lowlevel routines */
+#include "delta.h"
+#include "ews.h"
+#include "hoontech.h"
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ICEnsemble ICE1712 (Envy24)");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{"
+	       HOONTECH_DEVICE_DESC
+	       DELTA_DEVICE_DESC
+	       EWS_DEVICE_DESC
+	       "{ICEnsemble,Generic ICE1712},"
+	       "{ICEnsemble,Generic Envy24}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */
+static char *model[SNDRV_CARDS];
+static int omni[SNDRV_CARDS];				/* Delta44 & 66 Omni I/O support */
+static int cs8427_timeout[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 500}; /* CS8427 S/PDIF transciever reset timeout value in msec */
+static int dxr_enable[SNDRV_CARDS];			/* DXR enable for DMX6FIRE */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ICE1712 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ICE1712 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ICE1712 soundcard.");
+module_param_array(omni, bool, NULL, 0444);
+MODULE_PARM_DESC(omni, "Enable Midiman M-Audio Delta Omni I/O support.");
+module_param_array(cs8427_timeout, int, NULL, 0444);
+MODULE_PARM_DESC(cs8427_timeout, "Define reset timeout for cs8427 chip in msec resolution.");
+module_param_array(model, charp, NULL, 0444);
+MODULE_PARM_DESC(model, "Use the given board model.");
+module_param_array(dxr_enable, int, NULL, 0444);
+MODULE_PARM_DESC(dxr_enable, "Enable DXR support for Terratec DMX6FIRE.");
+
+
+static DEFINE_PCI_DEVICE_TABLE(snd_ice1712_ids) = {
+	{ PCI_VDEVICE(ICE, PCI_DEVICE_ID_ICE_1712), 0 },   /* ICE1712 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_ice1712_ids);
+
+static int snd_ice1712_build_pro_mixer(struct snd_ice1712 *ice);
+static int snd_ice1712_build_controls(struct snd_ice1712 *ice);
+
+static int PRO_RATE_LOCKED;
+static int PRO_RATE_RESET = 1;
+static unsigned int PRO_RATE_DEFAULT = 44100;
+
+/*
+ *  Basic I/O
+ */
+
+/* check whether the clock mode is spdif-in */
+static inline int is_spdif_master(struct snd_ice1712 *ice)
+{
+	return (inb(ICEMT(ice, RATE)) & ICE1712_SPDIF_MASTER) ? 1 : 0;
+}
+
+static inline int is_pro_rate_locked(struct snd_ice1712 *ice)
+{
+	return is_spdif_master(ice) || PRO_RATE_LOCKED;
+}
+
+static inline void snd_ice1712_ds_write(struct snd_ice1712 *ice, u8 channel, u8 addr, u32 data)
+{
+	outb((channel << 4) | addr, ICEDS(ice, INDEX));
+	outl(data, ICEDS(ice, DATA));
+}
+
+static inline u32 snd_ice1712_ds_read(struct snd_ice1712 *ice, u8 channel, u8 addr)
+{
+	outb((channel << 4) | addr, ICEDS(ice, INDEX));
+	return inl(ICEDS(ice, DATA));
+}
+
+static void snd_ice1712_ac97_write(struct snd_ac97 *ac97,
+				   unsigned short reg,
+				   unsigned short val)
+{
+	struct snd_ice1712 *ice = ac97->private_data;
+	int tm;
+	unsigned char old_cmd = 0;
+
+	for (tm = 0; tm < 0x10000; tm++) {
+		old_cmd = inb(ICEREG(ice, AC97_CMD));
+		if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ))
+			continue;
+		if (!(old_cmd & ICE1712_AC97_READY))
+			continue;
+		break;
+	}
+	outb(reg, ICEREG(ice, AC97_INDEX));
+	outw(val, ICEREG(ice, AC97_DATA));
+	old_cmd &= ~(ICE1712_AC97_PBK_VSR | ICE1712_AC97_CAP_VSR);
+	outb(old_cmd | ICE1712_AC97_WRITE, ICEREG(ice, AC97_CMD));
+	for (tm = 0; tm < 0x10000; tm++)
+		if ((inb(ICEREG(ice, AC97_CMD)) & ICE1712_AC97_WRITE) == 0)
+			break;
+}
+
+static unsigned short snd_ice1712_ac97_read(struct snd_ac97 *ac97,
+					    unsigned short reg)
+{
+	struct snd_ice1712 *ice = ac97->private_data;
+	int tm;
+	unsigned char old_cmd = 0;
+
+	for (tm = 0; tm < 0x10000; tm++) {
+		old_cmd = inb(ICEREG(ice, AC97_CMD));
+		if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ))
+			continue;
+		if (!(old_cmd & ICE1712_AC97_READY))
+			continue;
+		break;
+	}
+	outb(reg, ICEREG(ice, AC97_INDEX));
+	outb(old_cmd | ICE1712_AC97_READ, ICEREG(ice, AC97_CMD));
+	for (tm = 0; tm < 0x10000; tm++)
+		if ((inb(ICEREG(ice, AC97_CMD)) & ICE1712_AC97_READ) == 0)
+			break;
+	if (tm >= 0x10000)		/* timeout */
+		return ~0;
+	return inw(ICEREG(ice, AC97_DATA));
+}
+
+/*
+ * pro ac97 section
+ */
+
+static void snd_ice1712_pro_ac97_write(struct snd_ac97 *ac97,
+				       unsigned short reg,
+				       unsigned short val)
+{
+	struct snd_ice1712 *ice = ac97->private_data;
+	int tm;
+	unsigned char old_cmd = 0;
+
+	for (tm = 0; tm < 0x10000; tm++) {
+		old_cmd = inb(ICEMT(ice, AC97_CMD));
+		if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ))
+			continue;
+		if (!(old_cmd & ICE1712_AC97_READY))
+			continue;
+		break;
+	}
+	outb(reg, ICEMT(ice, AC97_INDEX));
+	outw(val, ICEMT(ice, AC97_DATA));
+	old_cmd &= ~(ICE1712_AC97_PBK_VSR | ICE1712_AC97_CAP_VSR);
+	outb(old_cmd | ICE1712_AC97_WRITE, ICEMT(ice, AC97_CMD));
+	for (tm = 0; tm < 0x10000; tm++)
+		if ((inb(ICEMT(ice, AC97_CMD)) & ICE1712_AC97_WRITE) == 0)
+			break;
+}
+
+
+static unsigned short snd_ice1712_pro_ac97_read(struct snd_ac97 *ac97,
+						unsigned short reg)
+{
+	struct snd_ice1712 *ice = ac97->private_data;
+	int tm;
+	unsigned char old_cmd = 0;
+
+	for (tm = 0; tm < 0x10000; tm++) {
+		old_cmd = inb(ICEMT(ice, AC97_CMD));
+		if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ))
+			continue;
+		if (!(old_cmd & ICE1712_AC97_READY))
+			continue;
+		break;
+	}
+	outb(reg, ICEMT(ice, AC97_INDEX));
+	outb(old_cmd | ICE1712_AC97_READ, ICEMT(ice, AC97_CMD));
+	for (tm = 0; tm < 0x10000; tm++)
+		if ((inb(ICEMT(ice, AC97_CMD)) & ICE1712_AC97_READ) == 0)
+			break;
+	if (tm >= 0x10000)		/* timeout */
+		return ~0;
+	return inw(ICEMT(ice, AC97_DATA));
+}
+
+/*
+ * consumer ac97 digital mix
+ */
+#define snd_ice1712_digmix_route_ac97_info	snd_ctl_boolean_mono_info
+
+static int snd_ice1712_digmix_route_ac97_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = inb(ICEMT(ice, MONITOR_ROUTECTRL)) & ICE1712_ROUTE_AC97 ? 1 : 0;
+	return 0;
+}
+
+static int snd_ice1712_digmix_route_ac97_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char val, nval;
+
+	spin_lock_irq(&ice->reg_lock);
+	val = inb(ICEMT(ice, MONITOR_ROUTECTRL));
+	nval = val & ~ICE1712_ROUTE_AC97;
+	if (ucontrol->value.integer.value[0])
+		nval |= ICE1712_ROUTE_AC97;
+	outb(nval, ICEMT(ice, MONITOR_ROUTECTRL));
+	spin_unlock_irq(&ice->reg_lock);
+	return val != nval;
+}
+
+static struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Digital Mixer To AC97",
+	.info = snd_ice1712_digmix_route_ac97_info,
+	.get = snd_ice1712_digmix_route_ac97_get,
+	.put = snd_ice1712_digmix_route_ac97_put,
+};
+
+
+/*
+ * gpio operations
+ */
+static void snd_ice1712_set_gpio_dir(struct snd_ice1712 *ice, unsigned int data)
+{
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, data);
+	inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */
+}
+
+static unsigned int snd_ice1712_get_gpio_dir(struct snd_ice1712 *ice)
+{
+	return snd_ice1712_read(ice, ICE1712_IREG_GPIO_DIRECTION);
+}
+
+static unsigned int snd_ice1712_get_gpio_mask(struct snd_ice1712 *ice)
+{
+	return snd_ice1712_read(ice, ICE1712_IREG_GPIO_WRITE_MASK);
+}
+
+static void snd_ice1712_set_gpio_mask(struct snd_ice1712 *ice, unsigned int data)
+{
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, data);
+	inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */
+}
+
+static unsigned int snd_ice1712_get_gpio_data(struct snd_ice1712 *ice)
+{
+	return snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+}
+
+static void snd_ice1712_set_gpio_data(struct snd_ice1712 *ice, unsigned int val)
+{
+	snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, val);
+	inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */
+}
+
+/*
+ *
+ * CS8427 interface
+ *
+ */
+
+/*
+ * change the input clock selection
+ * spdif_clock = 1 - IEC958 input, 0 - Envy24
+ */
+static int snd_ice1712_cs8427_set_input_clock(struct snd_ice1712 *ice, int spdif_clock)
+{
+	unsigned char reg[2] = { 0x80 | 4, 0 };   /* CS8427 auto increment | register number 4 + data */
+	unsigned char val, nval;
+	int res = 0;
+
+	snd_i2c_lock(ice->i2c);
+	if (snd_i2c_sendbytes(ice->cs8427, reg, 1) != 1) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	if (snd_i2c_readbytes(ice->cs8427, &val, 1) != 1) {
+		snd_i2c_unlock(ice->i2c);
+		return -EIO;
+	}
+	nval = val & 0xf0;
+	if (spdif_clock)
+		nval |= 0x01;
+	else
+		nval |= 0x04;
+	if (val != nval) {
+		reg[1] = nval;
+		if (snd_i2c_sendbytes(ice->cs8427, reg, 2) != 2) {
+			res = -EIO;
+		} else {
+			res++;
+		}
+	}
+	snd_i2c_unlock(ice->i2c);
+	return res;
+}
+
+/*
+ * spdif callbacks
+ */
+static void open_cs8427(struct snd_ice1712 *ice, struct snd_pcm_substream *substream)
+{
+	snd_cs8427_iec958_active(ice->cs8427, 1);
+}
+
+static void close_cs8427(struct snd_ice1712 *ice, struct snd_pcm_substream *substream)
+{
+	snd_cs8427_iec958_active(ice->cs8427, 0);
+}
+
+static void setup_cs8427(struct snd_ice1712 *ice, int rate)
+{
+	snd_cs8427_iec958_pcm(ice->cs8427, rate);
+}
+
+/*
+ * create and initialize callbacks for cs8427 interface
+ */
+int __devinit snd_ice1712_init_cs8427(struct snd_ice1712 *ice, int addr)
+{
+	int err;
+
+	err = snd_cs8427_create(ice->i2c, addr,
+		(ice->cs8427_timeout * HZ) / 1000, &ice->cs8427);
+	if (err < 0) {
+		snd_printk(KERN_ERR "CS8427 initialization failed\n");
+		return err;
+	}
+	ice->spdif.ops.open = open_cs8427;
+	ice->spdif.ops.close = close_cs8427;
+	ice->spdif.ops.setup_rate = setup_cs8427;
+	return 0;
+}
+
+static void snd_ice1712_set_input_clock_source(struct snd_ice1712 *ice, int spdif_is_master)
+{
+	/* change CS8427 clock source too */
+	if (ice->cs8427)
+		snd_ice1712_cs8427_set_input_clock(ice, spdif_is_master);
+	/* notify ak4524 chip as well */
+	if (spdif_is_master) {
+		unsigned int i;
+		for (i = 0; i < ice->akm_codecs; i++) {
+			if (ice->akm[i].ops.set_rate_val)
+				ice->akm[i].ops.set_rate_val(&ice->akm[i], 0);
+		}
+	}
+}
+
+/*
+ *  Interrupt handler
+ */
+
+static irqreturn_t snd_ice1712_interrupt(int irq, void *dev_id)
+{
+	struct snd_ice1712 *ice = dev_id;
+	unsigned char status;
+	int handled = 0;
+
+	while (1) {
+		status = inb(ICEREG(ice, IRQSTAT));
+		if (status == 0)
+			break;
+		handled = 1;
+		if (status & ICE1712_IRQ_MPU1) {
+			if (ice->rmidi[0])
+				snd_mpu401_uart_interrupt(irq, ice->rmidi[0]->private_data);
+			outb(ICE1712_IRQ_MPU1, ICEREG(ice, IRQSTAT));
+			status &= ~ICE1712_IRQ_MPU1;
+		}
+		if (status & ICE1712_IRQ_TIMER)
+			outb(ICE1712_IRQ_TIMER, ICEREG(ice, IRQSTAT));
+		if (status & ICE1712_IRQ_MPU2) {
+			if (ice->rmidi[1])
+				snd_mpu401_uart_interrupt(irq, ice->rmidi[1]->private_data);
+			outb(ICE1712_IRQ_MPU2, ICEREG(ice, IRQSTAT));
+			status &= ~ICE1712_IRQ_MPU2;
+		}
+		if (status & ICE1712_IRQ_PROPCM) {
+			unsigned char mtstat = inb(ICEMT(ice, IRQ));
+			if (mtstat & ICE1712_MULTI_PBKSTATUS) {
+				if (ice->playback_pro_substream)
+					snd_pcm_period_elapsed(ice->playback_pro_substream);
+				outb(ICE1712_MULTI_PBKSTATUS, ICEMT(ice, IRQ));
+			}
+			if (mtstat & ICE1712_MULTI_CAPSTATUS) {
+				if (ice->capture_pro_substream)
+					snd_pcm_period_elapsed(ice->capture_pro_substream);
+				outb(ICE1712_MULTI_CAPSTATUS, ICEMT(ice, IRQ));
+			}
+		}
+		if (status & ICE1712_IRQ_FM)
+			outb(ICE1712_IRQ_FM, ICEREG(ice, IRQSTAT));
+		if (status & ICE1712_IRQ_PBKDS) {
+			u32 idx;
+			u16 pbkstatus;
+			struct snd_pcm_substream *substream;
+			pbkstatus = inw(ICEDS(ice, INTSTAT));
+			/* printk(KERN_DEBUG "pbkstatus = 0x%x\n", pbkstatus); */
+			for (idx = 0; idx < 6; idx++) {
+				if ((pbkstatus & (3 << (idx * 2))) == 0)
+					continue;
+				substream = ice->playback_con_substream_ds[idx];
+				if (substream != NULL)
+					snd_pcm_period_elapsed(substream);
+				outw(3 << (idx * 2), ICEDS(ice, INTSTAT));
+			}
+			outb(ICE1712_IRQ_PBKDS, ICEREG(ice, IRQSTAT));
+		}
+		if (status & ICE1712_IRQ_CONCAP) {
+			if (ice->capture_con_substream)
+				snd_pcm_period_elapsed(ice->capture_con_substream);
+			outb(ICE1712_IRQ_CONCAP, ICEREG(ice, IRQSTAT));
+		}
+		if (status & ICE1712_IRQ_CONPBK) {
+			if (ice->playback_con_substream)
+				snd_pcm_period_elapsed(ice->playback_con_substream);
+			outb(ICE1712_IRQ_CONPBK, ICEREG(ice, IRQSTAT));
+		}
+	}
+	return IRQ_RETVAL(handled);
+}
+
+
+/*
+ *  PCM part - misc
+ */
+
+static int snd_ice1712_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_ice1712_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+/*
+ *  PCM part - consumer I/O
+ */
+
+static int snd_ice1712_playback_trigger(struct snd_pcm_substream *substream,
+					int cmd)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	int result = 0;
+	u32 tmp;
+
+	spin_lock(&ice->reg_lock);
+	tmp = snd_ice1712_read(ice, ICE1712_IREG_PBK_CTRL);
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		tmp |= 1;
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		tmp &= ~1;
+	} else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) {
+		tmp |= 2;
+	} else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) {
+		tmp &= ~2;
+	} else {
+		result = -EINVAL;
+	}
+	snd_ice1712_write(ice, ICE1712_IREG_PBK_CTRL, tmp);
+	spin_unlock(&ice->reg_lock);
+	return result;
+}
+
+static int snd_ice1712_playback_ds_trigger(struct snd_pcm_substream *substream,
+					   int cmd)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	int result = 0;
+	u32 tmp;
+
+	spin_lock(&ice->reg_lock);
+	tmp = snd_ice1712_ds_read(ice, substream->number * 2, ICE1712_DSC_CONTROL);
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		tmp |= 1;
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		tmp &= ~1;
+	} else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) {
+		tmp |= 2;
+	} else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) {
+		tmp &= ~2;
+	} else {
+		result = -EINVAL;
+	}
+	snd_ice1712_ds_write(ice, substream->number * 2, ICE1712_DSC_CONTROL, tmp);
+	spin_unlock(&ice->reg_lock);
+	return result;
+}
+
+static int snd_ice1712_capture_trigger(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	int result = 0;
+	u8 tmp;
+
+	spin_lock(&ice->reg_lock);
+	tmp = snd_ice1712_read(ice, ICE1712_IREG_CAP_CTRL);
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		tmp |= 1;
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		tmp &= ~1;
+	} else {
+		result = -EINVAL;
+	}
+	snd_ice1712_write(ice, ICE1712_IREG_CAP_CTRL, tmp);
+	spin_unlock(&ice->reg_lock);
+	return result;
+}
+
+static int snd_ice1712_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	u32 period_size, buf_size, rate, tmp;
+
+	period_size = (snd_pcm_lib_period_bytes(substream) >> 2) - 1;
+	buf_size = snd_pcm_lib_buffer_bytes(substream) - 1;
+	tmp = 0x0000;
+	if (snd_pcm_format_width(runtime->format) == 16)
+		tmp |= 0x10;
+	if (runtime->channels == 2)
+		tmp |= 0x08;
+	rate = (runtime->rate * 8192) / 375;
+	if (rate > 0x000fffff)
+		rate = 0x000fffff;
+	spin_lock_irq(&ice->reg_lock);
+	outb(0, ice->ddma_port + 15);
+	outb(ICE1712_DMA_MODE_WRITE | ICE1712_DMA_AUTOINIT, ice->ddma_port + 0x0b);
+	outl(runtime->dma_addr, ice->ddma_port + 0);
+	outw(buf_size, ice->ddma_port + 4);
+	snd_ice1712_write(ice, ICE1712_IREG_PBK_RATE_LO, rate & 0xff);
+	snd_ice1712_write(ice, ICE1712_IREG_PBK_RATE_MID, (rate >> 8) & 0xff);
+	snd_ice1712_write(ice, ICE1712_IREG_PBK_RATE_HI, (rate >> 16) & 0xff);
+	snd_ice1712_write(ice, ICE1712_IREG_PBK_CTRL, tmp);
+	snd_ice1712_write(ice, ICE1712_IREG_PBK_COUNT_LO, period_size & 0xff);
+	snd_ice1712_write(ice, ICE1712_IREG_PBK_COUNT_HI, period_size >> 8);
+	snd_ice1712_write(ice, ICE1712_IREG_PBK_LEFT, 0);
+	snd_ice1712_write(ice, ICE1712_IREG_PBK_RIGHT, 0);
+	spin_unlock_irq(&ice->reg_lock);
+	return 0;
+}
+
+static int snd_ice1712_playback_ds_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	u32 period_size, buf_size, rate, tmp, chn;
+
+	period_size = snd_pcm_lib_period_bytes(substream) - 1;
+	buf_size = snd_pcm_lib_buffer_bytes(substream) - 1;
+	tmp = 0x0064;
+	if (snd_pcm_format_width(runtime->format) == 16)
+		tmp &= ~0x04;
+	if (runtime->channels == 2)
+		tmp |= 0x08;
+	rate = (runtime->rate * 8192) / 375;
+	if (rate > 0x000fffff)
+		rate = 0x000fffff;
+	ice->playback_con_active_buf[substream->number] = 0;
+	ice->playback_con_virt_addr[substream->number] = runtime->dma_addr;
+	chn = substream->number * 2;
+	spin_lock_irq(&ice->reg_lock);
+	snd_ice1712_ds_write(ice, chn, ICE1712_DSC_ADDR0, runtime->dma_addr);
+	snd_ice1712_ds_write(ice, chn, ICE1712_DSC_COUNT0, period_size);
+	snd_ice1712_ds_write(ice, chn, ICE1712_DSC_ADDR1, runtime->dma_addr + (runtime->periods > 1 ? period_size + 1 : 0));
+	snd_ice1712_ds_write(ice, chn, ICE1712_DSC_COUNT1, period_size);
+	snd_ice1712_ds_write(ice, chn, ICE1712_DSC_RATE, rate);
+	snd_ice1712_ds_write(ice, chn, ICE1712_DSC_VOLUME, 0);
+	snd_ice1712_ds_write(ice, chn, ICE1712_DSC_CONTROL, tmp);
+	if (runtime->channels == 2) {
+		snd_ice1712_ds_write(ice, chn + 1, ICE1712_DSC_RATE, rate);
+		snd_ice1712_ds_write(ice, chn + 1, ICE1712_DSC_VOLUME, 0);
+	}
+	spin_unlock_irq(&ice->reg_lock);
+	return 0;
+}
+
+static int snd_ice1712_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	u32 period_size, buf_size;
+	u8 tmp;
+
+	period_size = (snd_pcm_lib_period_bytes(substream) >> 2) - 1;
+	buf_size = snd_pcm_lib_buffer_bytes(substream) - 1;
+	tmp = 0x06;
+	if (snd_pcm_format_width(runtime->format) == 16)
+		tmp &= ~0x04;
+	if (runtime->channels == 2)
+		tmp &= ~0x02;
+	spin_lock_irq(&ice->reg_lock);
+	outl(ice->capture_con_virt_addr = runtime->dma_addr, ICEREG(ice, CONCAP_ADDR));
+	outw(buf_size, ICEREG(ice, CONCAP_COUNT));
+	snd_ice1712_write(ice, ICE1712_IREG_CAP_COUNT_HI, period_size >> 8);
+	snd_ice1712_write(ice, ICE1712_IREG_CAP_COUNT_LO, period_size & 0xff);
+	snd_ice1712_write(ice, ICE1712_IREG_CAP_CTRL, tmp);
+	spin_unlock_irq(&ice->reg_lock);
+	snd_ac97_set_rate(ice->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate);
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_ice1712_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	size_t ptr;
+
+	if (!(snd_ice1712_read(ice, ICE1712_IREG_PBK_CTRL) & 1))
+		return 0;
+	ptr = runtime->buffer_size - inw(ice->ddma_port + 4);
+	if (ptr == runtime->buffer_size)
+		ptr = 0;
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_ice1712_playback_ds_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	u8 addr;
+	size_t ptr;
+
+	if (!(snd_ice1712_ds_read(ice, substream->number * 2, ICE1712_DSC_CONTROL) & 1))
+		return 0;
+	if (ice->playback_con_active_buf[substream->number])
+		addr = ICE1712_DSC_ADDR1;
+	else
+		addr = ICE1712_DSC_ADDR0;
+	ptr = snd_ice1712_ds_read(ice, substream->number * 2, addr) -
+		ice->playback_con_virt_addr[substream->number];
+	if (ptr == substream->runtime->buffer_size)
+		ptr = 0;
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_ice1712_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(snd_ice1712_read(ice, ICE1712_IREG_CAP_CTRL) & 1))
+		return 0;
+	ptr = inl(ICEREG(ice, CONCAP_ADDR)) - ice->capture_con_virt_addr;
+	if (ptr == substream->runtime->buffer_size)
+		ptr = 0;
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static const struct snd_pcm_hardware snd_ice1712_playback = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_PAUSE),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(64*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(64*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static const struct snd_pcm_hardware snd_ice1712_playback_ds = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_PAUSE),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0,
+};
+
+static const struct snd_pcm_hardware snd_ice1712_capture = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(64*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(64*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static int snd_ice1712_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	ice->playback_con_substream = substream;
+	runtime->hw = snd_ice1712_playback;
+	return 0;
+}
+
+static int snd_ice1712_playback_ds_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	u32 tmp;
+
+	ice->playback_con_substream_ds[substream->number] = substream;
+	runtime->hw = snd_ice1712_playback_ds;
+	spin_lock_irq(&ice->reg_lock);
+	tmp = inw(ICEDS(ice, INTMASK)) & ~(1 << (substream->number * 2));
+	outw(tmp, ICEDS(ice, INTMASK));
+	spin_unlock_irq(&ice->reg_lock);
+	return 0;
+}
+
+static int snd_ice1712_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	ice->capture_con_substream = substream;
+	runtime->hw = snd_ice1712_capture;
+	runtime->hw.rates = ice->ac97->rates[AC97_RATES_ADC];
+	if (!(runtime->hw.rates & SNDRV_PCM_RATE_8000))
+		runtime->hw.rate_min = 48000;
+	return 0;
+}
+
+static int snd_ice1712_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	ice->playback_con_substream = NULL;
+	return 0;
+}
+
+static int snd_ice1712_playback_ds_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	u32 tmp;
+
+	spin_lock_irq(&ice->reg_lock);
+	tmp = inw(ICEDS(ice, INTMASK)) | (3 << (substream->number * 2));
+	outw(tmp, ICEDS(ice, INTMASK));
+	spin_unlock_irq(&ice->reg_lock);
+	ice->playback_con_substream_ds[substream->number] = NULL;
+	return 0;
+}
+
+static int snd_ice1712_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	ice->capture_con_substream = NULL;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_ice1712_playback_ops = {
+	.open =		snd_ice1712_playback_open,
+	.close =	snd_ice1712_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ice1712_hw_params,
+	.hw_free =	snd_ice1712_hw_free,
+	.prepare =	snd_ice1712_playback_prepare,
+	.trigger =	snd_ice1712_playback_trigger,
+	.pointer =	snd_ice1712_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_ice1712_playback_ds_ops = {
+	.open =		snd_ice1712_playback_ds_open,
+	.close =	snd_ice1712_playback_ds_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ice1712_hw_params,
+	.hw_free =	snd_ice1712_hw_free,
+	.prepare =	snd_ice1712_playback_ds_prepare,
+	.trigger =	snd_ice1712_playback_ds_trigger,
+	.pointer =	snd_ice1712_playback_ds_pointer,
+};
+
+static struct snd_pcm_ops snd_ice1712_capture_ops = {
+	.open =		snd_ice1712_capture_open,
+	.close =	snd_ice1712_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ice1712_hw_params,
+	.hw_free =	snd_ice1712_hw_free,
+	.prepare =	snd_ice1712_capture_prepare,
+	.trigger =	snd_ice1712_capture_trigger,
+	.pointer =	snd_ice1712_capture_pointer,
+};
+
+static int __devinit snd_ice1712_pcm(struct snd_ice1712 *ice, int device, struct snd_pcm **rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	err = snd_pcm_new(ice->card, "ICE1712 consumer", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ice1712_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ice1712_capture_ops);
+
+	pcm->private_data = ice;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "ICE1712 consumer");
+	ice->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(ice->pci), 64*1024, 64*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+
+	printk(KERN_WARNING "Consumer PCM code does not work well at the moment --jk\n");
+
+	return 0;
+}
+
+static int __devinit snd_ice1712_pcm_ds(struct snd_ice1712 *ice, int device, struct snd_pcm **rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	err = snd_pcm_new(ice->card, "ICE1712 consumer (DS)", device, 6, 0, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ice1712_playback_ds_ops);
+
+	pcm->private_data = ice;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "ICE1712 consumer (DS)");
+	ice->pcm_ds = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(ice->pci), 64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+
+	return 0;
+}
+
+/*
+ *  PCM code - professional part (multitrack)
+ */
+
+static unsigned int rates[] = { 8000, 9600, 11025, 12000, 16000, 22050, 24000,
+				32000, 44100, 48000, 64000, 88200, 96000 };
+
+static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static int snd_ice1712_pro_trigger(struct snd_pcm_substream *substream,
+				   int cmd)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	{
+		unsigned int what;
+		unsigned int old;
+		if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+			return -EINVAL;
+		what = ICE1712_PLAYBACK_PAUSE;
+		snd_pcm_trigger_done(substream, substream);
+		spin_lock(&ice->reg_lock);
+		old = inl(ICEMT(ice, PLAYBACK_CONTROL));
+		if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)
+			old |= what;
+		else
+			old &= ~what;
+		outl(old, ICEMT(ice, PLAYBACK_CONTROL));
+		spin_unlock(&ice->reg_lock);
+		break;
+	}
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_STOP:
+	{
+		unsigned int what = 0;
+		unsigned int old;
+		struct snd_pcm_substream *s;
+
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s == ice->playback_pro_substream) {
+				what |= ICE1712_PLAYBACK_START;
+				snd_pcm_trigger_done(s, substream);
+			} else if (s == ice->capture_pro_substream) {
+				what |= ICE1712_CAPTURE_START_SHADOW;
+				snd_pcm_trigger_done(s, substream);
+			}
+		}
+		spin_lock(&ice->reg_lock);
+		old = inl(ICEMT(ice, PLAYBACK_CONTROL));
+		if (cmd == SNDRV_PCM_TRIGGER_START)
+			old |= what;
+		else
+			old &= ~what;
+		outl(old, ICEMT(ice, PLAYBACK_CONTROL));
+		spin_unlock(&ice->reg_lock);
+		break;
+	}
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ */
+static void snd_ice1712_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, int force)
+{
+	unsigned long flags;
+	unsigned char val, old;
+	unsigned int i;
+
+	switch (rate) {
+	case 8000: val = 6; break;
+	case 9600: val = 3; break;
+	case 11025: val = 10; break;
+	case 12000: val = 2; break;
+	case 16000: val = 5; break;
+	case 22050: val = 9; break;
+	case 24000: val = 1; break;
+	case 32000: val = 4; break;
+	case 44100: val = 8; break;
+	case 48000: val = 0; break;
+	case 64000: val = 15; break;
+	case 88200: val = 11; break;
+	case 96000: val = 7; break;
+	default:
+		snd_BUG();
+		val = 0;
+		rate = 48000;
+		break;
+	}
+
+	spin_lock_irqsave(&ice->reg_lock, flags);
+	if (inb(ICEMT(ice, PLAYBACK_CONTROL)) & (ICE1712_CAPTURE_START_SHADOW|
+						 ICE1712_PLAYBACK_PAUSE|
+						 ICE1712_PLAYBACK_START)) {
+__out:
+		spin_unlock_irqrestore(&ice->reg_lock, flags);
+		return;
+	}
+	if (!force && is_pro_rate_locked(ice))
+		goto __out;
+
+	old = inb(ICEMT(ice, RATE));
+	if (!force && old == val)
+		goto __out;
+	outb(val, ICEMT(ice, RATE));
+	spin_unlock_irqrestore(&ice->reg_lock, flags);
+
+	if (ice->gpio.set_pro_rate)
+		ice->gpio.set_pro_rate(ice, rate);
+	for (i = 0; i < ice->akm_codecs; i++) {
+		if (ice->akm[i].ops.set_rate_val)
+			ice->akm[i].ops.set_rate_val(&ice->akm[i], rate);
+	}
+	if (ice->spdif.ops.setup_rate)
+		ice->spdif.ops.setup_rate(ice, rate);
+}
+
+static int snd_ice1712_playback_pro_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	ice->playback_pro_size = snd_pcm_lib_buffer_bytes(substream);
+	spin_lock_irq(&ice->reg_lock);
+	outl(substream->runtime->dma_addr, ICEMT(ice, PLAYBACK_ADDR));
+	outw((ice->playback_pro_size >> 2) - 1, ICEMT(ice, PLAYBACK_SIZE));
+	outw((snd_pcm_lib_period_bytes(substream) >> 2) - 1, ICEMT(ice, PLAYBACK_COUNT));
+	spin_unlock_irq(&ice->reg_lock);
+
+	return 0;
+}
+
+static int snd_ice1712_playback_pro_hw_params(struct snd_pcm_substream *substream,
+					      struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	snd_ice1712_set_pro_rate(ice, params_rate(hw_params), 0);
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_ice1712_capture_pro_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	ice->capture_pro_size = snd_pcm_lib_buffer_bytes(substream);
+	spin_lock_irq(&ice->reg_lock);
+	outl(substream->runtime->dma_addr, ICEMT(ice, CAPTURE_ADDR));
+	outw((ice->capture_pro_size >> 2) - 1, ICEMT(ice, CAPTURE_SIZE));
+	outw((snd_pcm_lib_period_bytes(substream) >> 2) - 1, ICEMT(ice, CAPTURE_COUNT));
+	spin_unlock_irq(&ice->reg_lock);
+	return 0;
+}
+
+static int snd_ice1712_capture_pro_hw_params(struct snd_pcm_substream *substream,
+					     struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	snd_ice1712_set_pro_rate(ice, params_rate(hw_params), 0);
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static snd_pcm_uframes_t snd_ice1712_playback_pro_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(inl(ICEMT(ice, PLAYBACK_CONTROL)) & ICE1712_PLAYBACK_START))
+		return 0;
+	ptr = ice->playback_pro_size - (inw(ICEMT(ice, PLAYBACK_SIZE)) << 2);
+	if (ptr == substream->runtime->buffer_size)
+		ptr = 0;
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_ice1712_capture_pro_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(inl(ICEMT(ice, PLAYBACK_CONTROL)) & ICE1712_CAPTURE_START_SHADOW))
+		return 0;
+	ptr = ice->capture_pro_size - (inw(ICEMT(ice, CAPTURE_SIZE)) << 2);
+	if (ptr == substream->runtime->buffer_size)
+		ptr = 0;
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static const struct snd_pcm_hardware snd_ice1712_playback_pro = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START),
+	.formats =		SNDRV_PCM_FMTBIT_S32_LE,
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_96000,
+	.rate_min =		4000,
+	.rate_max =		96000,
+	.channels_min =		10,
+	.channels_max =		10,
+	.buffer_bytes_max =	(256*1024),
+	.period_bytes_min =	10 * 4 * 2,
+	.period_bytes_max =	131040,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static const struct snd_pcm_hardware snd_ice1712_capture_pro = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START),
+	.formats =		SNDRV_PCM_FMTBIT_S32_LE,
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_96000,
+	.rate_min =		4000,
+	.rate_max =		96000,
+	.channels_min =		12,
+	.channels_max =		12,
+	.buffer_bytes_max =	(256*1024),
+	.period_bytes_min =	12 * 4 * 2,
+	.period_bytes_max =	131040,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static int snd_ice1712_playback_pro_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	ice->playback_pro_substream = substream;
+	runtime->hw = snd_ice1712_playback_pro;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+	if (is_pro_rate_locked(ice)) {
+		runtime->hw.rate_min = PRO_RATE_DEFAULT;
+		runtime->hw.rate_max = PRO_RATE_DEFAULT;
+	}
+
+	if (ice->spdif.ops.open)
+		ice->spdif.ops.open(ice, substream);
+
+	return 0;
+}
+
+static int snd_ice1712_capture_pro_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	ice->capture_pro_substream = substream;
+	runtime->hw = snd_ice1712_capture_pro;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+	if (is_pro_rate_locked(ice)) {
+		runtime->hw.rate_min = PRO_RATE_DEFAULT;
+		runtime->hw.rate_max = PRO_RATE_DEFAULT;
+	}
+
+	return 0;
+}
+
+static int snd_ice1712_playback_pro_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	if (PRO_RATE_RESET)
+		snd_ice1712_set_pro_rate(ice, PRO_RATE_DEFAULT, 0);
+	ice->playback_pro_substream = NULL;
+	if (ice->spdif.ops.close)
+		ice->spdif.ops.close(ice, substream);
+
+	return 0;
+}
+
+static int snd_ice1712_capture_pro_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	if (PRO_RATE_RESET)
+		snd_ice1712_set_pro_rate(ice, PRO_RATE_DEFAULT, 0);
+	ice->capture_pro_substream = NULL;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_ice1712_playback_pro_ops = {
+	.open =		snd_ice1712_playback_pro_open,
+	.close =	snd_ice1712_playback_pro_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ice1712_playback_pro_hw_params,
+	.hw_free =	snd_ice1712_hw_free,
+	.prepare =	snd_ice1712_playback_pro_prepare,
+	.trigger =	snd_ice1712_pro_trigger,
+	.pointer =	snd_ice1712_playback_pro_pointer,
+};
+
+static struct snd_pcm_ops snd_ice1712_capture_pro_ops = {
+	.open =		snd_ice1712_capture_pro_open,
+	.close =	snd_ice1712_capture_pro_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_ice1712_capture_pro_hw_params,
+	.hw_free =	snd_ice1712_hw_free,
+	.prepare =	snd_ice1712_capture_pro_prepare,
+	.trigger =	snd_ice1712_pro_trigger,
+	.pointer =	snd_ice1712_capture_pro_pointer,
+};
+
+static int __devinit snd_ice1712_pcm_profi(struct snd_ice1712 *ice, int device, struct snd_pcm **rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	err = snd_pcm_new(ice->card, "ICE1712 multi", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ice1712_playback_pro_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ice1712_capture_pro_ops);
+
+	pcm->private_data = ice;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "ICE1712 multi");
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(ice->pci), 256*1024, 256*1024);
+
+	ice->pcm_pro = pcm;
+	if (rpcm)
+		*rpcm = pcm;
+
+	if (ice->cs8427) {
+		/* assign channels to iec958 */
+		err = snd_cs8427_iec958_build(ice->cs8427,
+					      pcm->streams[0].substream,
+					      pcm->streams[1].substream);
+		if (err < 0)
+			return err;
+	}
+
+	err = snd_ice1712_build_pro_mixer(ice);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/*
+ *  Mixer section
+ */
+
+static void snd_ice1712_update_volume(struct snd_ice1712 *ice, int index)
+{
+	unsigned int vol = ice->pro_volumes[index];
+	unsigned short val = 0;
+
+	val |= (vol & 0x8000) == 0 ? (96 - (vol & 0x7f)) : 0x7f;
+	val |= ((vol & 0x80000000) == 0 ? (96 - ((vol >> 16) & 0x7f)) : 0x7f) << 8;
+	outb(index, ICEMT(ice, MONITOR_INDEX));
+	outw(val, ICEMT(ice, MONITOR_VOLUME));
+}
+
+#define snd_ice1712_pro_mixer_switch_info	snd_ctl_boolean_stereo_info
+
+static int snd_ice1712_pro_mixer_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) +
+		kcontrol->private_value;
+
+	spin_lock_irq(&ice->reg_lock);
+	ucontrol->value.integer.value[0] =
+		!((ice->pro_volumes[priv_idx] >> 15) & 1);
+	ucontrol->value.integer.value[1] =
+		!((ice->pro_volumes[priv_idx] >> 31) & 1);
+	spin_unlock_irq(&ice->reg_lock);
+	return 0;
+}
+
+static int snd_ice1712_pro_mixer_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) +
+		kcontrol->private_value;
+	unsigned int nval, change;
+
+	nval = (ucontrol->value.integer.value[0] ? 0 : 0x00008000) |
+	       (ucontrol->value.integer.value[1] ? 0 : 0x80000000);
+	spin_lock_irq(&ice->reg_lock);
+	nval |= ice->pro_volumes[priv_idx] & ~0x80008000;
+	change = nval != ice->pro_volumes[priv_idx];
+	ice->pro_volumes[priv_idx] = nval;
+	snd_ice1712_update_volume(ice, priv_idx);
+	spin_unlock_irq(&ice->reg_lock);
+	return change;
+}
+
+static int snd_ice1712_pro_mixer_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 96;
+	return 0;
+}
+
+static int snd_ice1712_pro_mixer_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) +
+		kcontrol->private_value;
+
+	spin_lock_irq(&ice->reg_lock);
+	ucontrol->value.integer.value[0] =
+		(ice->pro_volumes[priv_idx] >> 0) & 127;
+	ucontrol->value.integer.value[1] =
+		(ice->pro_volumes[priv_idx] >> 16) & 127;
+	spin_unlock_irq(&ice->reg_lock);
+	return 0;
+}
+
+static int snd_ice1712_pro_mixer_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) +
+		kcontrol->private_value;
+	unsigned int nval, change;
+
+	nval = (ucontrol->value.integer.value[0] & 127) |
+	       ((ucontrol->value.integer.value[1] & 127) << 16);
+	spin_lock_irq(&ice->reg_lock);
+	nval |= ice->pro_volumes[priv_idx] & ~0x007f007f;
+	change = nval != ice->pro_volumes[priv_idx];
+	ice->pro_volumes[priv_idx] = nval;
+	snd_ice1712_update_volume(ice, priv_idx);
+	spin_unlock_irq(&ice->reg_lock);
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_playback, -14400, 150, 0);
+
+static struct snd_kcontrol_new snd_ice1712_multi_playback_ctrls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Multi Playback Switch",
+		.info = snd_ice1712_pro_mixer_switch_info,
+		.get = snd_ice1712_pro_mixer_switch_get,
+		.put = snd_ice1712_pro_mixer_switch_put,
+		.private_value = 0,
+		.count = 10,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Multi Playback Volume",
+		.info = snd_ice1712_pro_mixer_volume_info,
+		.get = snd_ice1712_pro_mixer_volume_get,
+		.put = snd_ice1712_pro_mixer_volume_put,
+		.private_value = 0,
+		.count = 10,
+		.tlv = { .p = db_scale_playback }
+	},
+};
+
+static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_switch __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "H/W Multi Capture Switch",
+	.info = snd_ice1712_pro_mixer_switch_info,
+	.get = snd_ice1712_pro_mixer_switch_get,
+	.put = snd_ice1712_pro_mixer_switch_put,
+	.private_value = 10,
+};
+
+static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, SWITCH),
+	.info = snd_ice1712_pro_mixer_switch_info,
+	.get = snd_ice1712_pro_mixer_switch_get,
+	.put = snd_ice1712_pro_mixer_switch_put,
+	.private_value = 18,
+	.count = 2,
+};
+
+static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_volume __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.name = "H/W Multi Capture Volume",
+	.info = snd_ice1712_pro_mixer_volume_info,
+	.get = snd_ice1712_pro_mixer_volume_get,
+	.put = snd_ice1712_pro_mixer_volume_put,
+	.private_value = 10,
+	.tlv = { .p = db_scale_playback }
+};
+
+static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, VOLUME),
+	.info = snd_ice1712_pro_mixer_volume_info,
+	.get = snd_ice1712_pro_mixer_volume_get,
+	.put = snd_ice1712_pro_mixer_volume_put,
+	.private_value = 18,
+	.count = 2,
+};
+
+static int __devinit snd_ice1712_build_pro_mixer(struct snd_ice1712 *ice)
+{
+	struct snd_card *card = ice->card;
+	unsigned int idx;
+	int err;
+
+	/* multi-channel mixer */
+	for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_multi_playback_ctrls); idx++) {
+		err = snd_ctl_add(card, snd_ctl_new1(&snd_ice1712_multi_playback_ctrls[idx], ice));
+		if (err < 0)
+			return err;
+	}
+
+	if (ice->num_total_adcs > 0) {
+		struct snd_kcontrol_new tmp = snd_ice1712_multi_capture_analog_switch;
+		tmp.count = ice->num_total_adcs;
+		err = snd_ctl_add(card, snd_ctl_new1(&tmp, ice));
+		if (err < 0)
+			return err;
+	}
+
+	err = snd_ctl_add(card, snd_ctl_new1(&snd_ice1712_multi_capture_spdif_switch, ice));
+	if (err < 0)
+		return err;
+
+	if (ice->num_total_adcs > 0) {
+		struct snd_kcontrol_new tmp = snd_ice1712_multi_capture_analog_volume;
+		tmp.count = ice->num_total_adcs;
+		err = snd_ctl_add(card, snd_ctl_new1(&tmp, ice));
+		if (err < 0)
+			return err;
+	}
+
+	err = snd_ctl_add(card, snd_ctl_new1(&snd_ice1712_multi_capture_spdif_volume, ice));
+	if (err < 0)
+		return err;
+
+	/* initialize volumes */
+	for (idx = 0; idx < 10; idx++) {
+		ice->pro_volumes[idx] = 0x80008000;	/* mute */
+		snd_ice1712_update_volume(ice, idx);
+	}
+	for (idx = 10; idx < 10 + ice->num_total_adcs; idx++) {
+		ice->pro_volumes[idx] = 0x80008000;	/* mute */
+		snd_ice1712_update_volume(ice, idx);
+	}
+	for (idx = 18; idx < 20; idx++) {
+		ice->pro_volumes[idx] = 0x80008000;	/* mute */
+		snd_ice1712_update_volume(ice, idx);
+	}
+	return 0;
+}
+
+static void snd_ice1712_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+	struct snd_ice1712 *ice = ac97->private_data;
+	ice->ac97 = NULL;
+}
+
+static int __devinit snd_ice1712_ac97_mixer(struct snd_ice1712 *ice)
+{
+	int err, bus_num = 0;
+	struct snd_ac97_template ac97;
+	struct snd_ac97_bus *pbus;
+	static struct snd_ac97_bus_ops con_ops = {
+		.write = snd_ice1712_ac97_write,
+		.read = snd_ice1712_ac97_read,
+	};
+	static struct snd_ac97_bus_ops pro_ops = {
+		.write = snd_ice1712_pro_ac97_write,
+		.read = snd_ice1712_pro_ac97_read,
+	};
+
+	if (ice_has_con_ac97(ice)) {
+		err = snd_ac97_bus(ice->card, bus_num++, &con_ops, NULL, &pbus);
+		if (err < 0)
+			return err;
+		memset(&ac97, 0, sizeof(ac97));
+		ac97.private_data = ice;
+		ac97.private_free = snd_ice1712_mixer_free_ac97;
+		err = snd_ac97_mixer(pbus, &ac97, &ice->ac97);
+		if (err < 0)
+			printk(KERN_WARNING "ice1712: cannot initialize ac97 for consumer, skipped\n");
+		else {
+			err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_mixer_digmix_route_ac97, ice));
+			if (err < 0)
+				return err;
+			return 0;
+		}
+	}
+
+	if (!(ice->eeprom.data[ICE_EEP1_ACLINK] & ICE1712_CFG_PRO_I2S)) {
+		err = snd_ac97_bus(ice->card, bus_num, &pro_ops, NULL, &pbus);
+		if (err < 0)
+			return err;
+		memset(&ac97, 0, sizeof(ac97));
+		ac97.private_data = ice;
+		ac97.private_free = snd_ice1712_mixer_free_ac97;
+		err = snd_ac97_mixer(pbus, &ac97, &ice->ac97);
+		if (err < 0)
+			printk(KERN_WARNING "ice1712: cannot initialize pro ac97, skipped\n");
+		else
+			return 0;
+	}
+	/* I2S mixer only */
+	strcat(ice->card->mixername, "ICE1712 - multitrack");
+	return 0;
+}
+
+/*
+ *
+ */
+
+static inline unsigned int eeprom_double(struct snd_ice1712 *ice, int idx)
+{
+	return (unsigned int)ice->eeprom.data[idx] | ((unsigned int)ice->eeprom.data[idx + 1] << 8);
+}
+
+static void snd_ice1712_proc_read(struct snd_info_entry *entry,
+				  struct snd_info_buffer *buffer)
+{
+	struct snd_ice1712 *ice = entry->private_data;
+	unsigned int idx;
+
+	snd_iprintf(buffer, "%s\n\n", ice->card->longname);
+	snd_iprintf(buffer, "EEPROM:\n");
+
+	snd_iprintf(buffer, "  Subvendor        : 0x%x\n", ice->eeprom.subvendor);
+	snd_iprintf(buffer, "  Size             : %i bytes\n", ice->eeprom.size);
+	snd_iprintf(buffer, "  Version          : %i\n", ice->eeprom.version);
+	snd_iprintf(buffer, "  Codec            : 0x%x\n", ice->eeprom.data[ICE_EEP1_CODEC]);
+	snd_iprintf(buffer, "  ACLink           : 0x%x\n", ice->eeprom.data[ICE_EEP1_ACLINK]);
+	snd_iprintf(buffer, "  I2S ID           : 0x%x\n", ice->eeprom.data[ICE_EEP1_I2SID]);
+	snd_iprintf(buffer, "  S/PDIF           : 0x%x\n", ice->eeprom.data[ICE_EEP1_SPDIF]);
+	snd_iprintf(buffer, "  GPIO mask        : 0x%x\n", ice->eeprom.gpiomask);
+	snd_iprintf(buffer, "  GPIO state       : 0x%x\n", ice->eeprom.gpiostate);
+	snd_iprintf(buffer, "  GPIO direction   : 0x%x\n", ice->eeprom.gpiodir);
+	snd_iprintf(buffer, "  AC'97 main       : 0x%x\n", eeprom_double(ice, ICE_EEP1_AC97_MAIN_LO));
+	snd_iprintf(buffer, "  AC'97 pcm        : 0x%x\n", eeprom_double(ice, ICE_EEP1_AC97_PCM_LO));
+	snd_iprintf(buffer, "  AC'97 record     : 0x%x\n", eeprom_double(ice, ICE_EEP1_AC97_REC_LO));
+	snd_iprintf(buffer, "  AC'97 record src : 0x%x\n", ice->eeprom.data[ICE_EEP1_AC97_RECSRC]);
+	for (idx = 0; idx < 4; idx++)
+		snd_iprintf(buffer, "  DAC ID #%i        : 0x%x\n", idx, ice->eeprom.data[ICE_EEP1_DAC_ID + idx]);
+	for (idx = 0; idx < 4; idx++)
+		snd_iprintf(buffer, "  ADC ID #%i        : 0x%x\n", idx, ice->eeprom.data[ICE_EEP1_ADC_ID + idx]);
+	for (idx = 0x1c; idx < ice->eeprom.size; idx++)
+		snd_iprintf(buffer, "  Extra #%02i        : 0x%x\n", idx, ice->eeprom.data[idx]);
+
+	snd_iprintf(buffer, "\nRegisters:\n");
+	snd_iprintf(buffer, "  PSDOUT03         : 0x%04x\n", (unsigned)inw(ICEMT(ice, ROUTE_PSDOUT03)));
+	snd_iprintf(buffer, "  CAPTURE          : 0x%08x\n", inl(ICEMT(ice, ROUTE_CAPTURE)));
+	snd_iprintf(buffer, "  SPDOUT           : 0x%04x\n", (unsigned)inw(ICEMT(ice, ROUTE_SPDOUT)));
+	snd_iprintf(buffer, "  RATE             : 0x%02x\n", (unsigned)inb(ICEMT(ice, RATE)));
+	snd_iprintf(buffer, "  GPIO_DATA        : 0x%02x\n", (unsigned)snd_ice1712_get_gpio_data(ice));
+	snd_iprintf(buffer, "  GPIO_WRITE_MASK  : 0x%02x\n", (unsigned)snd_ice1712_read(ice, ICE1712_IREG_GPIO_WRITE_MASK));
+	snd_iprintf(buffer, "  GPIO_DIRECTION   : 0x%02x\n", (unsigned)snd_ice1712_read(ice, ICE1712_IREG_GPIO_DIRECTION));
+}
+
+static void __devinit snd_ice1712_proc_init(struct snd_ice1712 *ice)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(ice->card, "ice1712", &entry))
+		snd_info_set_text_ops(entry, ice, snd_ice1712_proc_read);
+}
+
+/*
+ *
+ */
+
+static int snd_ice1712_eeprom_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+	uinfo->count = sizeof(struct snd_ice1712_eeprom);
+	return 0;
+}
+
+static int snd_ice1712_eeprom_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	memcpy(ucontrol->value.bytes.data, &ice->eeprom, sizeof(ice->eeprom));
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_ice1712_eeprom __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.name = "ICE1712 EEPROM",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = snd_ice1712_eeprom_info,
+	.get = snd_ice1712_eeprom_get
+};
+
+/*
+ */
+static int snd_ice1712_spdif_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_ice1712_spdif_default_get(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	if (ice->spdif.ops.default_get)
+		ice->spdif.ops.default_get(ice, ucontrol);
+	return 0;
+}
+
+static int snd_ice1712_spdif_default_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	if (ice->spdif.ops.default_put)
+		return ice->spdif.ops.default_put(ice, ucontrol);
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_ice1712_spdif_default __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+	.info =		snd_ice1712_spdif_info,
+	.get =		snd_ice1712_spdif_default_get,
+	.put =		snd_ice1712_spdif_default_put
+};
+
+static int snd_ice1712_spdif_maskc_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	if (ice->spdif.ops.default_get) {
+		ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO |
+						     IEC958_AES0_PROFESSIONAL |
+						     IEC958_AES0_CON_NOT_COPYRIGHT |
+						     IEC958_AES0_CON_EMPHASIS;
+		ucontrol->value.iec958.status[1] = IEC958_AES1_CON_ORIGINAL |
+						     IEC958_AES1_CON_CATEGORY;
+		ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS;
+	} else {
+		ucontrol->value.iec958.status[0] = 0xff;
+		ucontrol->value.iec958.status[1] = 0xff;
+		ucontrol->value.iec958.status[2] = 0xff;
+		ucontrol->value.iec958.status[3] = 0xff;
+		ucontrol->value.iec958.status[4] = 0xff;
+	}
+	return 0;
+}
+
+static int snd_ice1712_spdif_maskp_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	if (ice->spdif.ops.default_get) {
+		ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO |
+						     IEC958_AES0_PROFESSIONAL |
+						     IEC958_AES0_PRO_FS |
+						     IEC958_AES0_PRO_EMPHASIS;
+		ucontrol->value.iec958.status[1] = IEC958_AES1_PRO_MODE;
+	} else {
+		ucontrol->value.iec958.status[0] = 0xff;
+		ucontrol->value.iec958.status[1] = 0xff;
+		ucontrol->value.iec958.status[2] = 0xff;
+		ucontrol->value.iec958.status[3] = 0xff;
+		ucontrol->value.iec958.status[4] = 0xff;
+	}
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_ice1712_spdif_maskc __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+	.info =		snd_ice1712_spdif_info,
+	.get =		snd_ice1712_spdif_maskc_get,
+};
+
+static struct snd_kcontrol_new snd_ice1712_spdif_maskp __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK),
+	.info =		snd_ice1712_spdif_info,
+	.get =		snd_ice1712_spdif_maskp_get,
+};
+
+static int snd_ice1712_spdif_stream_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	if (ice->spdif.ops.stream_get)
+		ice->spdif.ops.stream_get(ice, ucontrol);
+	return 0;
+}
+
+static int snd_ice1712_spdif_stream_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	if (ice->spdif.ops.stream_put)
+		return ice->spdif.ops.stream_put(ice, ucontrol);
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_ice1712_spdif_stream __devinitdata =
+{
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_INACTIVE),
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
+	.info =		snd_ice1712_spdif_info,
+	.get =		snd_ice1712_spdif_stream_get,
+	.put =		snd_ice1712_spdif_stream_put
+};
+
+int snd_ice1712_gpio_get(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char mask = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value & (1<<24)) ? 1 : 0;
+
+	snd_ice1712_save_gpio_status(ice);
+	ucontrol->value.integer.value[0] =
+		(snd_ice1712_gpio_read(ice) & mask ? 1 : 0) ^ invert;
+	snd_ice1712_restore_gpio_status(ice);
+	return 0;
+}
+
+int snd_ice1712_gpio_put(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char mask = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value & (1<<24)) ? mask : 0;
+	unsigned int val, nval;
+
+	if (kcontrol->private_value & (1 << 31))
+		return -EPERM;
+	nval = (ucontrol->value.integer.value[0] ? mask : 0) ^ invert;
+	snd_ice1712_save_gpio_status(ice);
+	val = snd_ice1712_gpio_read(ice);
+	nval |= val & ~mask;
+	if (val != nval)
+		snd_ice1712_gpio_write(ice, nval);
+	snd_ice1712_restore_gpio_status(ice);
+	return val != nval;
+}
+
+/*
+ *  rate
+ */
+static int snd_ice1712_pro_internal_clock_info(struct snd_kcontrol *kcontrol,
+					       struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[] = {
+		"8000",		/* 0: 6 */
+		"9600",		/* 1: 3 */
+		"11025",	/* 2: 10 */
+		"12000",	/* 3: 2 */
+		"16000",	/* 4: 5 */
+		"22050",	/* 5: 9 */
+		"24000",	/* 6: 1 */
+		"32000",	/* 7: 4 */
+		"44100",	/* 8: 8 */
+		"48000",	/* 9: 0 */
+		"64000",	/* 10: 15 */
+		"88200",	/* 11: 11 */
+		"96000",	/* 12: 7 */
+		"IEC958 Input",	/* 13: -- */
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 14;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ice1712_pro_internal_clock_get(struct snd_kcontrol *kcontrol,
+					      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	static const unsigned char xlate[16] = {
+		9, 6, 3, 1, 7, 4, 0, 12, 8, 5, 2, 11, 255, 255, 255, 10
+	};
+	unsigned char val;
+
+	spin_lock_irq(&ice->reg_lock);
+	if (is_spdif_master(ice)) {
+		ucontrol->value.enumerated.item[0] = 13;
+	} else {
+		val = xlate[inb(ICEMT(ice, RATE)) & 15];
+		if (val == 255) {
+			snd_BUG();
+			val = 0;
+		}
+		ucontrol->value.enumerated.item[0] = val;
+	}
+	spin_unlock_irq(&ice->reg_lock);
+	return 0;
+}
+
+static int snd_ice1712_pro_internal_clock_put(struct snd_kcontrol *kcontrol,
+					      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	static const unsigned int xrate[13] = {
+		8000, 9600, 11025, 12000, 16000, 22050, 24000,
+		32000, 44100, 48000, 64000, 88200, 96000
+	};
+	unsigned char oval;
+	int change = 0;
+
+	spin_lock_irq(&ice->reg_lock);
+	oval = inb(ICEMT(ice, RATE));
+	if (ucontrol->value.enumerated.item[0] == 13) {
+		outb(oval | ICE1712_SPDIF_MASTER, ICEMT(ice, RATE));
+	} else {
+		PRO_RATE_DEFAULT = xrate[ucontrol->value.integer.value[0] % 13];
+		spin_unlock_irq(&ice->reg_lock);
+		snd_ice1712_set_pro_rate(ice, PRO_RATE_DEFAULT, 1);
+		spin_lock_irq(&ice->reg_lock);
+	}
+	change = inb(ICEMT(ice, RATE)) != oval;
+	spin_unlock_irq(&ice->reg_lock);
+
+	if ((oval & ICE1712_SPDIF_MASTER) !=
+	    (inb(ICEMT(ice, RATE)) & ICE1712_SPDIF_MASTER))
+		snd_ice1712_set_input_clock_source(ice, is_spdif_master(ice));
+
+	return change;
+}
+
+static struct snd_kcontrol_new snd_ice1712_pro_internal_clock __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Multi Track Internal Clock",
+	.info = snd_ice1712_pro_internal_clock_info,
+	.get = snd_ice1712_pro_internal_clock_get,
+	.put = snd_ice1712_pro_internal_clock_put
+};
+
+static int snd_ice1712_pro_internal_clock_default_info(struct snd_kcontrol *kcontrol,
+						       struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[] = {
+		"8000",		/* 0: 6 */
+		"9600",		/* 1: 3 */
+		"11025",	/* 2: 10 */
+		"12000",	/* 3: 2 */
+		"16000",	/* 4: 5 */
+		"22050",	/* 5: 9 */
+		"24000",	/* 6: 1 */
+		"32000",	/* 7: 4 */
+		"44100",	/* 8: 8 */
+		"48000",	/* 9: 0 */
+		"64000",	/* 10: 15 */
+		"88200",	/* 11: 11 */
+		"96000",	/* 12: 7 */
+		/* "IEC958 Input",	13: -- */
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 13;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ice1712_pro_internal_clock_default_get(struct snd_kcontrol *kcontrol,
+						      struct snd_ctl_elem_value *ucontrol)
+{
+	int val;
+	static const unsigned int xrate[13] = {
+		8000, 9600, 11025, 12000, 16000, 22050, 24000,
+		32000, 44100, 48000, 64000, 88200, 96000
+	};
+
+	for (val = 0; val < 13; val++) {
+		if (xrate[val] == PRO_RATE_DEFAULT)
+			break;
+	}
+
+	ucontrol->value.enumerated.item[0] = val;
+	return 0;
+}
+
+static int snd_ice1712_pro_internal_clock_default_put(struct snd_kcontrol *kcontrol,
+						      struct snd_ctl_elem_value *ucontrol)
+{
+	static const unsigned int xrate[13] = {
+		8000, 9600, 11025, 12000, 16000, 22050, 24000,
+		32000, 44100, 48000, 64000, 88200, 96000
+	};
+	unsigned char oval;
+	int change = 0;
+
+	oval = PRO_RATE_DEFAULT;
+	PRO_RATE_DEFAULT = xrate[ucontrol->value.integer.value[0] % 13];
+	change = PRO_RATE_DEFAULT != oval;
+
+	return change;
+}
+
+static struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Multi Track Internal Clock Default",
+	.info = snd_ice1712_pro_internal_clock_default_info,
+	.get = snd_ice1712_pro_internal_clock_default_get,
+	.put = snd_ice1712_pro_internal_clock_default_put
+};
+
+#define snd_ice1712_pro_rate_locking_info	snd_ctl_boolean_mono_info
+
+static int snd_ice1712_pro_rate_locking_get(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = PRO_RATE_LOCKED;
+	return 0;
+}
+
+static int snd_ice1712_pro_rate_locking_put(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int change = 0, nval;
+
+	nval = ucontrol->value.integer.value[0] ? 1 : 0;
+	spin_lock_irq(&ice->reg_lock);
+	change = PRO_RATE_LOCKED != nval;
+	PRO_RATE_LOCKED = nval;
+	spin_unlock_irq(&ice->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_ice1712_pro_rate_locking __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Multi Track Rate Locking",
+	.info = snd_ice1712_pro_rate_locking_info,
+	.get = snd_ice1712_pro_rate_locking_get,
+	.put = snd_ice1712_pro_rate_locking_put
+};
+
+#define snd_ice1712_pro_rate_reset_info		snd_ctl_boolean_mono_info
+
+static int snd_ice1712_pro_rate_reset_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = PRO_RATE_RESET;
+	return 0;
+}
+
+static int snd_ice1712_pro_rate_reset_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int change = 0, nval;
+
+	nval = ucontrol->value.integer.value[0] ? 1 : 0;
+	spin_lock_irq(&ice->reg_lock);
+	change = PRO_RATE_RESET != nval;
+	PRO_RATE_RESET = nval;
+	spin_unlock_irq(&ice->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_ice1712_pro_rate_reset __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Multi Track Rate Reset",
+	.info = snd_ice1712_pro_rate_reset_info,
+	.get = snd_ice1712_pro_rate_reset_get,
+	.put = snd_ice1712_pro_rate_reset_put
+};
+
+/*
+ * routing
+ */
+static int snd_ice1712_pro_route_info(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[] = {
+		"PCM Out", /* 0 */
+		"H/W In 0", "H/W In 1", "H/W In 2", "H/W In 3", /* 1-4 */
+		"H/W In 4", "H/W In 5", "H/W In 6", "H/W In 7", /* 5-8 */
+		"IEC958 In L", "IEC958 In R", /* 9-10 */
+		"Digital Mixer", /* 11 - optional */
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items =
+		snd_ctl_get_ioffidx(kcontrol, &uinfo->id) < 2 ? 12 : 11;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ice1712_pro_route_analog_get(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	unsigned int val, cval;
+
+	spin_lock_irq(&ice->reg_lock);
+	val = inw(ICEMT(ice, ROUTE_PSDOUT03));
+	cval = inl(ICEMT(ice, ROUTE_CAPTURE));
+	spin_unlock_irq(&ice->reg_lock);
+
+	val >>= ((idx % 2) * 8) + ((idx / 2) * 2);
+	val &= 3;
+	cval >>= ((idx / 2) * 8) + ((idx % 2) * 4);
+	if (val == 1 && idx < 2)
+		ucontrol->value.enumerated.item[0] = 11;
+	else if (val == 2)
+		ucontrol->value.enumerated.item[0] = (cval & 7) + 1;
+	else if (val == 3)
+		ucontrol->value.enumerated.item[0] = ((cval >> 3) & 1) + 9;
+	else
+		ucontrol->value.enumerated.item[0] = 0;
+	return 0;
+}
+
+static int snd_ice1712_pro_route_analog_put(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int change, shift;
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	unsigned int val, old_val, nval;
+
+	/* update PSDOUT */
+	if (ucontrol->value.enumerated.item[0] >= 11)
+		nval = idx < 2 ? 1 : 0; /* dig mixer (or pcm) */
+	else if (ucontrol->value.enumerated.item[0] >= 9)
+		nval = 3; /* spdif in */
+	else if (ucontrol->value.enumerated.item[0] >= 1)
+		nval = 2; /* analog in */
+	else
+		nval = 0; /* pcm */
+	shift = ((idx % 2) * 8) + ((idx / 2) * 2);
+	spin_lock_irq(&ice->reg_lock);
+	val = old_val = inw(ICEMT(ice, ROUTE_PSDOUT03));
+	val &= ~(0x03 << shift);
+	val |= nval << shift;
+	change = val != old_val;
+	if (change)
+		outw(val, ICEMT(ice, ROUTE_PSDOUT03));
+	spin_unlock_irq(&ice->reg_lock);
+	if (nval < 2) /* dig mixer of pcm */
+		return change;
+
+	/* update CAPTURE */
+	spin_lock_irq(&ice->reg_lock);
+	val = old_val = inl(ICEMT(ice, ROUTE_CAPTURE));
+	shift = ((idx / 2) * 8) + ((idx % 2) * 4);
+	if (nval == 2) { /* analog in */
+		nval = ucontrol->value.enumerated.item[0] - 1;
+		val &= ~(0x07 << shift);
+		val |= nval << shift;
+	} else { /* spdif in */
+		nval = (ucontrol->value.enumerated.item[0] - 9) << 3;
+		val &= ~(0x08 << shift);
+		val |= nval << shift;
+	}
+	if (val != old_val) {
+		change = 1;
+		outl(val, ICEMT(ice, ROUTE_CAPTURE));
+	}
+	spin_unlock_irq(&ice->reg_lock);
+	return change;
+}
+
+static int snd_ice1712_pro_route_spdif_get(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	unsigned int val, cval;
+	val = inw(ICEMT(ice, ROUTE_SPDOUT));
+	cval = (val >> (idx * 4 + 8)) & 0x0f;
+	val = (val >> (idx * 2)) & 0x03;
+	if (val == 1)
+		ucontrol->value.enumerated.item[0] = 11;
+	else if (val == 2)
+		ucontrol->value.enumerated.item[0] = (cval & 7) + 1;
+	else if (val == 3)
+		ucontrol->value.enumerated.item[0] = ((cval >> 3) & 1) + 9;
+	else
+		ucontrol->value.enumerated.item[0] = 0;
+	return 0;
+}
+
+static int snd_ice1712_pro_route_spdif_put(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int change, shift;
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	unsigned int val, old_val, nval;
+
+	/* update SPDOUT */
+	spin_lock_irq(&ice->reg_lock);
+	val = old_val = inw(ICEMT(ice, ROUTE_SPDOUT));
+	if (ucontrol->value.enumerated.item[0] >= 11)
+		nval = 1;
+	else if (ucontrol->value.enumerated.item[0] >= 9)
+		nval = 3;
+	else if (ucontrol->value.enumerated.item[0] >= 1)
+		nval = 2;
+	else
+		nval = 0;
+	shift = idx * 2;
+	val &= ~(0x03 << shift);
+	val |= nval << shift;
+	shift = idx * 4 + 8;
+	if (nval == 2) {
+		nval = ucontrol->value.enumerated.item[0] - 1;
+		val &= ~(0x07 << shift);
+		val |= nval << shift;
+	} else if (nval == 3) {
+		nval = (ucontrol->value.enumerated.item[0] - 9) << 3;
+		val &= ~(0x08 << shift);
+		val |= nval << shift;
+	}
+	change = val != old_val;
+	if (change)
+		outw(val, ICEMT(ice, ROUTE_SPDOUT));
+	spin_unlock_irq(&ice->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_ice1712_mixer_pro_analog_route __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "H/W Playback Route",
+	.info = snd_ice1712_pro_route_info,
+	.get = snd_ice1712_pro_route_analog_get,
+	.put = snd_ice1712_pro_route_analog_put,
+};
+
+static struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route",
+	.info = snd_ice1712_pro_route_info,
+	.get = snd_ice1712_pro_route_spdif_get,
+	.put = snd_ice1712_pro_route_spdif_put,
+	.count = 2,
+};
+
+
+static int snd_ice1712_pro_volume_rate_info(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	return 0;
+}
+
+static int snd_ice1712_pro_volume_rate_get(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = inb(ICEMT(ice, MONITOR_RATE));
+	return 0;
+}
+
+static int snd_ice1712_pro_volume_rate_put(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int change;
+
+	spin_lock_irq(&ice->reg_lock);
+	change = inb(ICEMT(ice, MONITOR_RATE)) != ucontrol->value.integer.value[0];
+	outb(ucontrol->value.integer.value[0], ICEMT(ice, MONITOR_RATE));
+	spin_unlock_irq(&ice->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Multi Track Volume Rate",
+	.info = snd_ice1712_pro_volume_rate_info,
+	.get = snd_ice1712_pro_volume_rate_get,
+	.put = snd_ice1712_pro_volume_rate_put
+};
+
+static int snd_ice1712_pro_peak_info(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 22;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	return 0;
+}
+
+static int snd_ice1712_pro_peak_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx;
+
+	spin_lock_irq(&ice->reg_lock);
+	for (idx = 0; idx < 22; idx++) {
+		outb(idx, ICEMT(ice, MONITOR_PEAKINDEX));
+		ucontrol->value.integer.value[idx] = inb(ICEMT(ice, MONITOR_PEAKDATA));
+	}
+	spin_unlock_irq(&ice->reg_lock);
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_ice1712_mixer_pro_peak __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name = "Multi Track Peak",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info = snd_ice1712_pro_peak_info,
+	.get = snd_ice1712_pro_peak_get
+};
+
+/*
+ *
+ */
+
+/*
+ * list of available boards
+ */
+static struct snd_ice1712_card_info *card_tables[] __devinitdata = {
+	snd_ice1712_hoontech_cards,
+	snd_ice1712_delta_cards,
+	snd_ice1712_ews_cards,
+	NULL,
+};
+
+static unsigned char __devinit snd_ice1712_read_i2c(struct snd_ice1712 *ice,
+						 unsigned char dev,
+						 unsigned char addr)
+{
+	long t = 0x10000;
+
+	outb(addr, ICEREG(ice, I2C_BYTE_ADDR));
+	outb(dev & ~ICE1712_I2C_WRITE, ICEREG(ice, I2C_DEV_ADDR));
+	while (t-- > 0 && (inb(ICEREG(ice, I2C_CTRL)) & ICE1712_I2C_BUSY)) ;
+	return inb(ICEREG(ice, I2C_DATA));
+}
+
+static int __devinit snd_ice1712_read_eeprom(struct snd_ice1712 *ice,
+					     const char *modelname)
+{
+	int dev = 0xa0;		/* EEPROM device address */
+	unsigned int i, size;
+	struct snd_ice1712_card_info * const *tbl, *c;
+
+	if (!modelname || !*modelname) {
+		ice->eeprom.subvendor = 0;
+		if ((inb(ICEREG(ice, I2C_CTRL)) & ICE1712_I2C_EEPROM) != 0)
+			ice->eeprom.subvendor = (snd_ice1712_read_i2c(ice, dev, 0x00) << 0) |
+				(snd_ice1712_read_i2c(ice, dev, 0x01) << 8) |
+				(snd_ice1712_read_i2c(ice, dev, 0x02) << 16) |
+				(snd_ice1712_read_i2c(ice, dev, 0x03) << 24);
+		if (ice->eeprom.subvendor == 0 ||
+		    ice->eeprom.subvendor == (unsigned int)-1) {
+			/* invalid subvendor from EEPROM, try the PCI subststem ID instead */
+			u16 vendor, device;
+			pci_read_config_word(ice->pci, PCI_SUBSYSTEM_VENDOR_ID, &vendor);
+			pci_read_config_word(ice->pci, PCI_SUBSYSTEM_ID, &device);
+			ice->eeprom.subvendor = ((unsigned int)swab16(vendor) << 16) | swab16(device);
+			if (ice->eeprom.subvendor == 0 || ice->eeprom.subvendor == (unsigned int)-1) {
+				printk(KERN_ERR "ice1712: No valid ID is found\n");
+				return -ENXIO;
+			}
+		}
+	}
+	for (tbl = card_tables; *tbl; tbl++) {
+		for (c = *tbl; c->subvendor; c++) {
+			if (modelname && c->model && !strcmp(modelname, c->model)) {
+				printk(KERN_INFO "ice1712: Using board model %s\n", c->name);
+				ice->eeprom.subvendor = c->subvendor;
+			} else if (c->subvendor != ice->eeprom.subvendor)
+				continue;
+			if (!c->eeprom_size || !c->eeprom_data)
+				goto found;
+			/* if the EEPROM is given by the driver, use it */
+			snd_printdd("using the defined eeprom..\n");
+			ice->eeprom.version = 1;
+			ice->eeprom.size = c->eeprom_size + 6;
+			memcpy(ice->eeprom.data, c->eeprom_data, c->eeprom_size);
+			goto read_skipped;
+		}
+	}
+	printk(KERN_WARNING "ice1712: No matching model found for ID 0x%x\n",
+	       ice->eeprom.subvendor);
+
+ found:
+	ice->eeprom.size = snd_ice1712_read_i2c(ice, dev, 0x04);
+	if (ice->eeprom.size < 6)
+		ice->eeprom.size = 32; /* FIXME: any cards without the correct size? */
+	else if (ice->eeprom.size > 32) {
+		snd_printk(KERN_ERR "invalid EEPROM (size = %i)\n", ice->eeprom.size);
+		return -EIO;
+	}
+	ice->eeprom.version = snd_ice1712_read_i2c(ice, dev, 0x05);
+	if (ice->eeprom.version != 1) {
+		snd_printk(KERN_ERR "invalid EEPROM version %i\n",
+			   ice->eeprom.version);
+		/* return -EIO; */
+	}
+	size = ice->eeprom.size - 6;
+	for (i = 0; i < size; i++)
+		ice->eeprom.data[i] = snd_ice1712_read_i2c(ice, dev, i + 6);
+
+ read_skipped:
+	ice->eeprom.gpiomask = ice->eeprom.data[ICE_EEP1_GPIO_MASK];
+	ice->eeprom.gpiostate = ice->eeprom.data[ICE_EEP1_GPIO_STATE];
+	ice->eeprom.gpiodir = ice->eeprom.data[ICE_EEP1_GPIO_DIR];
+
+	return 0;
+}
+
+
+
+static int __devinit snd_ice1712_chip_init(struct snd_ice1712 *ice)
+{
+	outb(ICE1712_RESET | ICE1712_NATIVE, ICEREG(ice, CONTROL));
+	udelay(200);
+	outb(ICE1712_NATIVE, ICEREG(ice, CONTROL));
+	udelay(200);
+	if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE &&
+	    !ice->dxr_enable)
+		/*  Set eeprom value to limit active ADCs and DACs to 6;
+		 *  Also disable AC97 as no hardware in standard 6fire card/box
+		 *  Note: DXR extensions are not currently supported
+		 */
+		ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a;
+	pci_write_config_byte(ice->pci, 0x60, ice->eeprom.data[ICE_EEP1_CODEC]);
+	pci_write_config_byte(ice->pci, 0x61, ice->eeprom.data[ICE_EEP1_ACLINK]);
+	pci_write_config_byte(ice->pci, 0x62, ice->eeprom.data[ICE_EEP1_I2SID]);
+	pci_write_config_byte(ice->pci, 0x63, ice->eeprom.data[ICE_EEP1_SPDIF]);
+	if (ice->eeprom.subvendor != ICE1712_SUBDEVICE_STDSP24) {
+		ice->gpio.write_mask = ice->eeprom.gpiomask;
+		ice->gpio.direction = ice->eeprom.gpiodir;
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK,
+				  ice->eeprom.gpiomask);
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION,
+				  ice->eeprom.gpiodir);
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA,
+				  ice->eeprom.gpiostate);
+	} else {
+		ice->gpio.write_mask = 0xc0;
+		ice->gpio.direction = 0xff;
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, 0xc0);
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, 0xff);
+		snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA,
+				  ICE1712_STDSP24_CLOCK_BIT);
+	}
+	snd_ice1712_write(ice, ICE1712_IREG_PRO_POWERDOWN, 0);
+	if (!(ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_NO_CON_AC97)) {
+		outb(ICE1712_AC97_WARM, ICEREG(ice, AC97_CMD));
+		udelay(100);
+		outb(0, ICEREG(ice, AC97_CMD));
+		udelay(200);
+		snd_ice1712_write(ice, ICE1712_IREG_CONSUMER_POWERDOWN, 0);
+	}
+	snd_ice1712_set_pro_rate(ice, 48000, 1);
+
+	return 0;
+}
+
+int __devinit snd_ice1712_spdif_build_controls(struct snd_ice1712 *ice)
+{
+	int err;
+	struct snd_kcontrol *kctl;
+
+	if (snd_BUG_ON(!ice->pcm_pro))
+		return -EIO;
+	err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_ice1712_spdif_default, ice));
+	if (err < 0)
+		return err;
+	kctl->id.device = ice->pcm_pro->device;
+	err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_ice1712_spdif_maskc, ice));
+	if (err < 0)
+		return err;
+	kctl->id.device = ice->pcm_pro->device;
+	err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_ice1712_spdif_maskp, ice));
+	if (err < 0)
+		return err;
+	kctl->id.device = ice->pcm_pro->device;
+	err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_ice1712_spdif_stream, ice));
+	if (err < 0)
+		return err;
+	kctl->id.device = ice->pcm_pro->device;
+	ice->spdif.stream_ctl = kctl;
+	return 0;
+}
+
+
+static int __devinit snd_ice1712_build_controls(struct snd_ice1712 *ice)
+{
+	int err;
+
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_eeprom, ice));
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_internal_clock, ice));
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_internal_clock_default, ice));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_rate_locking, ice));
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_rate_reset, ice));
+	if (err < 0)
+		return err;
+
+	if (ice->num_total_dacs > 0) {
+		struct snd_kcontrol_new tmp = snd_ice1712_mixer_pro_analog_route;
+		tmp.count = ice->num_total_dacs;
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&tmp, ice));
+		if (err < 0)
+			return err;
+	}
+
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_mixer_pro_spdif_route, ice));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_mixer_pro_volume_rate, ice));
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_mixer_pro_peak, ice));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int snd_ice1712_free(struct snd_ice1712 *ice)
+{
+	if (!ice->port)
+		goto __hw_end;
+	/* mask all interrupts */
+	outb(0xc0, ICEMT(ice, IRQ));
+	outb(0xff, ICEREG(ice, IRQMASK));
+	/* --- */
+__hw_end:
+	if (ice->irq >= 0)
+		free_irq(ice->irq, ice);
+
+	if (ice->port)
+		pci_release_regions(ice->pci);
+	snd_ice1712_akm4xxx_free(ice);
+	pci_disable_device(ice->pci);
+	kfree(ice->spec);
+	kfree(ice);
+	return 0;
+}
+
+static int snd_ice1712_dev_free(struct snd_device *device)
+{
+	struct snd_ice1712 *ice = device->device_data;
+	return snd_ice1712_free(ice);
+}
+
+static int __devinit snd_ice1712_create(struct snd_card *card,
+					struct pci_dev *pci,
+					const char *modelname,
+					int omni,
+					int cs8427_timeout,
+					int dxr_enable,
+					struct snd_ice1712 **r_ice1712)
+{
+	struct snd_ice1712 *ice;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_ice1712_dev_free,
+	};
+
+	*r_ice1712 = NULL;
+
+	/* enable PCI device */
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+	/* check, if we can restrict PCI DMA transfers to 28 bits */
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(28)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(28)) < 0) {
+		snd_printk(KERN_ERR "architecture does not support 28bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	ice = kzalloc(sizeof(*ice), GFP_KERNEL);
+	if (ice == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	ice->omni = omni ? 1 : 0;
+	if (cs8427_timeout < 1)
+		cs8427_timeout = 1;
+	else if (cs8427_timeout > 1000)
+		cs8427_timeout = 1000;
+	ice->cs8427_timeout = cs8427_timeout;
+	ice->dxr_enable = dxr_enable;
+	spin_lock_init(&ice->reg_lock);
+	mutex_init(&ice->gpio_mutex);
+	mutex_init(&ice->i2c_mutex);
+	mutex_init(&ice->open_mutex);
+	ice->gpio.set_mask = snd_ice1712_set_gpio_mask;
+	ice->gpio.get_mask = snd_ice1712_get_gpio_mask;
+	ice->gpio.set_dir = snd_ice1712_set_gpio_dir;
+	ice->gpio.get_dir = snd_ice1712_get_gpio_dir;
+	ice->gpio.set_data = snd_ice1712_set_gpio_data;
+	ice->gpio.get_data = snd_ice1712_get_gpio_data;
+
+	ice->spdif.cs8403_bits =
+		ice->spdif.cs8403_stream_bits = (0x01 |	/* consumer format */
+						 0x10 |	/* no emphasis */
+						 0x20);	/* PCM encoder/decoder */
+	ice->card = card;
+	ice->pci = pci;
+	ice->irq = -1;
+	pci_set_master(pci);
+	pci_write_config_word(ice->pci, 0x40, 0x807f);
+	pci_write_config_word(ice->pci, 0x42, 0x0006);
+	snd_ice1712_proc_init(ice);
+	synchronize_irq(pci->irq);
+
+	err = pci_request_regions(pci, "ICE1712");
+	if (err < 0) {
+		kfree(ice);
+		pci_disable_device(pci);
+		return err;
+	}
+	ice->port = pci_resource_start(pci, 0);
+	ice->ddma_port = pci_resource_start(pci, 1);
+	ice->dmapath_port = pci_resource_start(pci, 2);
+	ice->profi_port = pci_resource_start(pci, 3);
+
+	if (request_irq(pci->irq, snd_ice1712_interrupt, IRQF_SHARED,
+			"ICE1712", ice)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_ice1712_free(ice);
+		return -EIO;
+	}
+
+	ice->irq = pci->irq;
+
+	if (snd_ice1712_read_eeprom(ice, modelname) < 0) {
+		snd_ice1712_free(ice);
+		return -EIO;
+	}
+	if (snd_ice1712_chip_init(ice) < 0) {
+		snd_ice1712_free(ice);
+		return -EIO;
+	}
+
+	/* unmask used interrupts */
+	outb(((ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_2xMPU401) == 0 ?
+	      ICE1712_IRQ_MPU2 : 0) |
+	     ((ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_NO_CON_AC97) ?
+	      ICE1712_IRQ_PBKDS | ICE1712_IRQ_CONCAP | ICE1712_IRQ_CONPBK : 0),
+	     ICEREG(ice, IRQMASK));
+	outb(0x00, ICEMT(ice, IRQ));
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ice, &ops);
+	if (err < 0) {
+		snd_ice1712_free(ice);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*r_ice1712 = ice;
+	return 0;
+}
+
+
+/*
+ *
+ * Registration
+ *
+ */
+
+static struct snd_ice1712_card_info no_matched __devinitdata;
+
+static int __devinit snd_ice1712_probe(struct pci_dev *pci,
+				       const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_ice1712 *ice;
+	int pcm_dev = 0, err;
+	struct snd_ice1712_card_info * const *tbl, *c;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	strcpy(card->driver, "ICE1712");
+	strcpy(card->shortname, "ICEnsemble ICE1712");
+
+	err = snd_ice1712_create(card, pci, model[dev], omni[dev],
+		cs8427_timeout[dev], dxr_enable[dev], &ice);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	for (tbl = card_tables; *tbl; tbl++) {
+		for (c = *tbl; c->subvendor; c++) {
+			if (c->subvendor == ice->eeprom.subvendor) {
+				strcpy(card->shortname, c->name);
+				if (c->driver) /* specific driver? */
+					strcpy(card->driver, c->driver);
+				if (c->chip_init) {
+					err = c->chip_init(ice);
+					if (err < 0) {
+						snd_card_free(card);
+						return err;
+					}
+				}
+				goto __found;
+			}
+		}
+	}
+	c = &no_matched;
+ __found:
+
+	err = snd_ice1712_pcm_profi(ice, pcm_dev++, NULL);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if (ice_has_con_ac97(ice)) {
+		err = snd_ice1712_pcm(ice, pcm_dev++, NULL);
+		if (err < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+
+	err = snd_ice1712_ac97_mixer(ice);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	err = snd_ice1712_build_controls(ice);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if (c->build_controls) {
+		err = c->build_controls(ice);
+		if (err < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+
+	if (ice_has_con_ac97(ice)) {
+		err = snd_ice1712_pcm_ds(ice, pcm_dev++, NULL);
+		if (err < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+
+	if (!c->no_mpu401) {
+		err = snd_mpu401_uart_new(card, 0, MPU401_HW_ICE1712,
+			ICEREG(ice, MPU1_CTRL),
+			(c->mpu401_1_info_flags | MPU401_INFO_INTEGRATED),
+			ice->irq, 0, &ice->rmidi[0]);
+		if (err < 0) {
+			snd_card_free(card);
+			return err;
+		}
+		if (c->mpu401_1_name)
+			/*  Prefered name available in card_info */
+			snprintf(ice->rmidi[0]->name,
+				 sizeof(ice->rmidi[0]->name),
+				 "%s %d", c->mpu401_1_name, card->number);
+
+		if (ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_2xMPU401) {
+			/*  2nd port used  */
+			err = snd_mpu401_uart_new(card, 1, MPU401_HW_ICE1712,
+				ICEREG(ice, MPU2_CTRL),
+				(c->mpu401_2_info_flags | MPU401_INFO_INTEGRATED),
+				ice->irq, 0, &ice->rmidi[1]);
+
+			if (err < 0) {
+				snd_card_free(card);
+				return err;
+			}
+			if (c->mpu401_2_name)
+				/*  Prefered name available in card_info */
+				snprintf(ice->rmidi[1]->name,
+					 sizeof(ice->rmidi[1]->name),
+					 "%s %d", c->mpu401_2_name,
+					 card->number);
+		}
+	}
+
+	snd_ice1712_set_input_clock_source(ice, 0);
+
+	sprintf(card->longname, "%s at 0x%lx, irq %i",
+		card->shortname, ice->port, ice->irq);
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_ice1712_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "ICE1712",
+	.id_table = snd_ice1712_ids,
+	.probe = snd_ice1712_probe,
+	.remove = __devexit_p(snd_ice1712_remove),
+};
+
+static int __init alsa_card_ice1712_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_ice1712_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_ice1712_init)
+module_exit(alsa_card_ice1712_exit)
diff --git a/linux/sound/pci/ice1712/ice1712.h b/linux/sound/pci/ice1712/ice1712.h
new file mode 100644
index 0000000..0da778a
--- /dev/null
+++ b/linux/sound/pci/ice1712/ice1712.h
@@ -0,0 +1,531 @@
+#ifndef __SOUND_ICE1712_H
+#define __SOUND_ICE1712_H
+
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include <sound/rawmidi.h>
+#include <sound/i2c.h>
+#include <sound/ak4xxx-adda.h>
+#include <sound/ak4114.h>
+#include <sound/pt2258.h>
+#include <sound/pcm.h>
+#include <sound/mpu401.h>
+
+
+/*
+ *  Direct registers
+ */
+
+#define ICEREG(ice, x) ((ice)->port + ICE1712_REG_##x)
+
+#define ICE1712_REG_CONTROL		0x00	/* byte */
+#define   ICE1712_RESET			0x80	/* reset whole chip */
+#define   ICE1712_SERR_LEVEL		0x04	/* SERR# level otherwise edge */
+#define   ICE1712_NATIVE		0x01	/* native mode otherwise SB */
+#define ICE1712_REG_IRQMASK		0x01	/* byte */
+#define   ICE1712_IRQ_MPU1		0x80
+#define   ICE1712_IRQ_TIMER		0x40
+#define   ICE1712_IRQ_MPU2		0x20
+#define   ICE1712_IRQ_PROPCM		0x10
+#define   ICE1712_IRQ_FM		0x08	/* FM/MIDI - legacy */
+#define   ICE1712_IRQ_PBKDS		0x04	/* playback DS channels */
+#define   ICE1712_IRQ_CONCAP		0x02	/* consumer capture */
+#define   ICE1712_IRQ_CONPBK		0x01	/* consumer playback */
+#define ICE1712_REG_IRQSTAT		0x02	/* byte */
+/* look to ICE1712_IRQ_* */
+#define ICE1712_REG_INDEX		0x03	/* byte - indirect CCIxx regs */
+#define ICE1712_REG_DATA		0x04	/* byte - indirect CCIxx regs */
+#define ICE1712_REG_NMI_STAT1		0x05	/* byte */
+#define ICE1712_REG_NMI_DATA		0x06	/* byte */
+#define ICE1712_REG_NMI_INDEX		0x07	/* byte */
+#define ICE1712_REG_AC97_INDEX		0x08	/* byte */
+#define ICE1712_REG_AC97_CMD		0x09	/* byte */
+#define   ICE1712_AC97_COLD		0x80	/* cold reset */
+#define   ICE1712_AC97_WARM		0x40	/* warm reset */
+#define   ICE1712_AC97_WRITE		0x20	/* W: write, R: write in progress */
+#define   ICE1712_AC97_READ		0x10	/* W: read, R: read in progress */
+#define   ICE1712_AC97_READY		0x08	/* codec ready status bit */
+#define   ICE1712_AC97_PBK_VSR		0x02	/* playback VSR */
+#define   ICE1712_AC97_CAP_VSR		0x01	/* capture VSR */
+#define ICE1712_REG_AC97_DATA		0x0a	/* word (little endian) */
+#define ICE1712_REG_MPU1_CTRL		0x0c	/* byte */
+#define ICE1712_REG_MPU1_DATA		0x0d	/* byte */
+#define ICE1712_REG_I2C_DEV_ADDR	0x10	/* byte */
+#define   ICE1712_I2C_WRITE		0x01	/* write direction */
+#define ICE1712_REG_I2C_BYTE_ADDR	0x11	/* byte */
+#define ICE1712_REG_I2C_DATA		0x12	/* byte */
+#define ICE1712_REG_I2C_CTRL		0x13	/* byte */
+#define   ICE1712_I2C_EEPROM		0x80	/* EEPROM exists */
+#define   ICE1712_I2C_BUSY		0x01	/* busy bit */
+#define ICE1712_REG_CONCAP_ADDR		0x14	/* dword - consumer capture */
+#define ICE1712_REG_CONCAP_COUNT	0x18	/* word - current/base count */
+#define ICE1712_REG_SERR_SHADOW		0x1b	/* byte */
+#define ICE1712_REG_MPU2_CTRL		0x1c	/* byte */
+#define ICE1712_REG_MPU2_DATA		0x1d	/* byte */
+#define ICE1712_REG_TIMER		0x1e	/* word */
+
+/*
+ *  Indirect registers
+ */
+
+#define ICE1712_IREG_PBK_COUNT_LO	0x00
+#define ICE1712_IREG_PBK_COUNT_HI	0x01
+#define ICE1712_IREG_PBK_CTRL		0x02
+#define ICE1712_IREG_PBK_LEFT		0x03	/* left volume */
+#define ICE1712_IREG_PBK_RIGHT		0x04	/* right volume */
+#define ICE1712_IREG_PBK_SOFT		0x05	/* soft volume */
+#define ICE1712_IREG_PBK_RATE_LO	0x06
+#define ICE1712_IREG_PBK_RATE_MID	0x07
+#define ICE1712_IREG_PBK_RATE_HI	0x08
+#define ICE1712_IREG_CAP_COUNT_LO	0x10
+#define ICE1712_IREG_CAP_COUNT_HI	0x11
+#define ICE1712_IREG_CAP_CTRL		0x12
+#define ICE1712_IREG_GPIO_DATA		0x20
+#define ICE1712_IREG_GPIO_WRITE_MASK	0x21
+#define ICE1712_IREG_GPIO_DIRECTION	0x22
+#define ICE1712_IREG_CONSUMER_POWERDOWN	0x30
+#define ICE1712_IREG_PRO_POWERDOWN	0x31
+
+/*
+ *  Consumer section direct DMA registers
+ */
+
+#define ICEDS(ice, x) ((ice)->dmapath_port + ICE1712_DS_##x)
+
+#define ICE1712_DS_INTMASK		0x00	/* word - interrupt mask */
+#define ICE1712_DS_INTSTAT		0x02	/* word - interrupt status */
+#define ICE1712_DS_DATA			0x04	/* dword - channel data */
+#define ICE1712_DS_INDEX		0x08	/* dword - channel index */
+
+/*
+ *  Consumer section channel registers
+ */
+
+#define ICE1712_DSC_ADDR0		0x00	/* dword - base address 0 */
+#define ICE1712_DSC_COUNT0		0x01	/* word - count 0 */
+#define ICE1712_DSC_ADDR1		0x02	/* dword - base address 1 */
+#define ICE1712_DSC_COUNT1		0x03	/* word - count 1 */
+#define ICE1712_DSC_CONTROL		0x04	/* byte - control & status */
+#define   ICE1712_BUFFER1		0x80	/* buffer1 is active */
+#define   ICE1712_BUFFER1_AUTO		0x40	/* buffer1 auto init */
+#define   ICE1712_BUFFER0_AUTO		0x20	/* buffer0 auto init */
+#define   ICE1712_FLUSH			0x10	/* flush FIFO */
+#define   ICE1712_STEREO		0x08	/* stereo */
+#define   ICE1712_16BIT			0x04	/* 16-bit data */
+#define   ICE1712_PAUSE			0x02	/* pause */
+#define   ICE1712_START			0x01	/* start */
+#define ICE1712_DSC_RATE		0x05	/* dword - rate */
+#define ICE1712_DSC_VOLUME		0x06	/* word - volume control */
+
+/*
+ *  Professional multi-track direct control registers
+ */
+
+#define ICEMT(ice, x) ((ice)->profi_port + ICE1712_MT_##x)
+
+#define ICE1712_MT_IRQ			0x00	/* byte - interrupt mask */
+#define   ICE1712_MULTI_CAPTURE		0x80	/* capture IRQ */
+#define   ICE1712_MULTI_PLAYBACK	0x40	/* playback IRQ */
+#define   ICE1712_MULTI_CAPSTATUS	0x02	/* capture IRQ status */
+#define   ICE1712_MULTI_PBKSTATUS	0x01	/* playback IRQ status */
+#define ICE1712_MT_RATE			0x01	/* byte - sampling rate select */
+#define   ICE1712_SPDIF_MASTER		0x10	/* S/PDIF input is master clock */
+#define ICE1712_MT_I2S_FORMAT		0x02	/* byte - I2S data format */
+#define ICE1712_MT_AC97_INDEX		0x04	/* byte - AC'97 index */
+#define ICE1712_MT_AC97_CMD		0x05	/* byte - AC'97 command & status */
+/* look to ICE1712_AC97_* */
+#define ICE1712_MT_AC97_DATA		0x06	/* word - AC'97 data */
+#define ICE1712_MT_PLAYBACK_ADDR	0x10	/* dword - playback address */
+#define ICE1712_MT_PLAYBACK_SIZE	0x14	/* word - playback size */
+#define ICE1712_MT_PLAYBACK_COUNT	0x16	/* word - playback count */
+#define ICE1712_MT_PLAYBACK_CONTROL	0x18	/* byte - control */
+#define   ICE1712_CAPTURE_START_SHADOW	0x04	/* capture start */
+#define   ICE1712_PLAYBACK_PAUSE	0x02	/* playback pause */
+#define   ICE1712_PLAYBACK_START	0x01	/* playback start */
+#define ICE1712_MT_CAPTURE_ADDR		0x20	/* dword - capture address */
+#define ICE1712_MT_CAPTURE_SIZE		0x24	/* word - capture size */
+#define ICE1712_MT_CAPTURE_COUNT	0x26	/* word - capture count */
+#define ICE1712_MT_CAPTURE_CONTROL	0x28	/* byte - control */
+#define   ICE1712_CAPTURE_START		0x01	/* capture start */
+#define ICE1712_MT_ROUTE_PSDOUT03	0x30	/* word */
+#define ICE1712_MT_ROUTE_SPDOUT		0x32	/* word */
+#define ICE1712_MT_ROUTE_CAPTURE	0x34	/* dword */
+#define ICE1712_MT_MONITOR_VOLUME	0x38	/* word */
+#define ICE1712_MT_MONITOR_INDEX	0x3a	/* byte */
+#define ICE1712_MT_MONITOR_RATE		0x3b	/* byte */
+#define ICE1712_MT_MONITOR_ROUTECTRL	0x3c	/* byte */
+#define   ICE1712_ROUTE_AC97		0x01	/* route digital mixer output to AC'97 */
+#define ICE1712_MT_MONITOR_PEAKINDEX	0x3e	/* byte */
+#define ICE1712_MT_MONITOR_PEAKDATA	0x3f	/* byte */
+
+/*
+ *  Codec configuration bits
+ */
+
+/* PCI[60] System Configuration */
+#define ICE1712_CFG_CLOCK	0xc0
+#define   ICE1712_CFG_CLOCK512	0x00	/* 22.5692Mhz, 44.1kHz*512 */
+#define   ICE1712_CFG_CLOCK384  0x40	/* 16.9344Mhz, 44.1kHz*384 */
+#define   ICE1712_CFG_EXT	0x80	/* external clock */
+#define ICE1712_CFG_2xMPU401	0x20	/* two MPU401 UARTs */
+#define ICE1712_CFG_NO_CON_AC97 0x10	/* consumer AC'97 codec is not present */
+#define ICE1712_CFG_ADC_MASK	0x0c	/* one, two, three, four stereo ADCs */
+#define ICE1712_CFG_DAC_MASK	0x03	/* one, two, three, four stereo DACs */
+/* PCI[61] AC-Link Configuration */
+#define ICE1712_CFG_PRO_I2S	0x80	/* multitrack converter: I2S or AC'97 */
+#define ICE1712_CFG_AC97_PACKED	0x01	/* split or packed mode - AC'97 */
+/* PCI[62] I2S Features */
+#define ICE1712_CFG_I2S_VOLUME	0x80	/* volume/mute capability */
+#define ICE1712_CFG_I2S_96KHZ	0x40	/* supports 96kHz sampling */
+#define ICE1712_CFG_I2S_RESMASK	0x30	/* resolution mask, 16,18,20,24-bit */
+#define ICE1712_CFG_I2S_OTHER	0x0f	/* other I2S IDs */
+/* PCI[63] S/PDIF Configuration */
+#define ICE1712_CFG_I2S_CHIPID	0xfc	/* I2S chip ID */
+#define ICE1712_CFG_SPDIF_IN	0x02	/* S/PDIF input is present */
+#define ICE1712_CFG_SPDIF_OUT	0x01	/* S/PDIF output is present */
+
+/*
+ * DMA mode values
+ * identical with DMA_XXX on i386 architecture.
+ */
+#define ICE1712_DMA_MODE_WRITE		0x48
+#define ICE1712_DMA_AUTOINIT		0x10
+
+
+/*
+ *
+ */
+
+struct snd_ice1712;
+
+struct snd_ice1712_eeprom {
+	unsigned int subvendor;	/* PCI[2c-2f] */
+	unsigned char size;	/* size of EEPROM image in bytes */
+	unsigned char version;	/* must be 1 (or 2 for vt1724) */
+	unsigned char data[32];
+	unsigned int gpiomask;
+	unsigned int gpiostate;
+	unsigned int gpiodir;
+};
+
+enum {
+	ICE_EEP1_CODEC = 0,	/* 06 */
+	ICE_EEP1_ACLINK,	/* 07 */
+	ICE_EEP1_I2SID,		/* 08 */
+	ICE_EEP1_SPDIF,		/* 09 */
+	ICE_EEP1_GPIO_MASK,	/* 0a */
+	ICE_EEP1_GPIO_STATE,	/* 0b */
+	ICE_EEP1_GPIO_DIR,	/* 0c */
+	ICE_EEP1_AC97_MAIN_LO,	/* 0d */
+	ICE_EEP1_AC97_MAIN_HI,	/* 0e */
+	ICE_EEP1_AC97_PCM_LO,	/* 0f */
+	ICE_EEP1_AC97_PCM_HI,	/* 10 */
+	ICE_EEP1_AC97_REC_LO,	/* 11 */
+	ICE_EEP1_AC97_REC_HI,	/* 12 */
+	ICE_EEP1_AC97_RECSRC,	/* 13 */
+	ICE_EEP1_DAC_ID,	/* 14 */
+	ICE_EEP1_DAC_ID1,
+	ICE_EEP1_DAC_ID2,
+	ICE_EEP1_DAC_ID3,
+	ICE_EEP1_ADC_ID,	/* 18 */
+	ICE_EEP1_ADC_ID1,
+	ICE_EEP1_ADC_ID2,
+	ICE_EEP1_ADC_ID3
+};
+
+#define ice_has_con_ac97(ice)	(!((ice)->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_NO_CON_AC97))
+
+
+struct snd_ak4xxx_private {
+	unsigned int cif:1;		/* CIF mode */
+	unsigned char caddr;		/* C0 and C1 bits */
+	unsigned int data_mask;		/* DATA gpio bit */
+	unsigned int clk_mask;		/* CLK gpio bit */
+	unsigned int cs_mask;		/* bit mask for select/deselect address */
+	unsigned int cs_addr;		/* bits to select address */
+	unsigned int cs_none;		/* bits to deselect address */
+	unsigned int add_flags;		/* additional bits at init */
+	unsigned int mask_flags;	/* total mask bits */
+	struct snd_akm4xxx_ops {
+		void (*set_rate_val)(struct snd_akm4xxx *ak, unsigned int rate);
+	} ops;
+};
+
+struct snd_ice1712_spdif {
+	unsigned char cs8403_bits;
+	unsigned char cs8403_stream_bits;
+	struct snd_kcontrol *stream_ctl;
+
+	struct snd_ice1712_spdif_ops {
+		void (*open)(struct snd_ice1712 *, struct snd_pcm_substream *);
+		void (*setup_rate)(struct snd_ice1712 *, int rate);
+		void (*close)(struct snd_ice1712 *, struct snd_pcm_substream *);
+		void (*default_get)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol);
+		int (*default_put)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol);
+		void (*stream_get)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol);
+		int (*stream_put)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol);
+	} ops;
+};
+
+
+struct snd_ice1712 {
+	unsigned long conp_dma_size;
+	unsigned long conc_dma_size;
+	unsigned long prop_dma_size;
+	unsigned long proc_dma_size;
+	int irq;
+
+	unsigned long port;
+	unsigned long ddma_port;
+	unsigned long dmapath_port;
+	unsigned long profi_port;
+
+	struct pci_dev *pci;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct snd_pcm *pcm_ds;
+	struct snd_pcm *pcm_pro;
+	struct snd_pcm_substream *playback_con_substream;
+	struct snd_pcm_substream *playback_con_substream_ds[6];
+	struct snd_pcm_substream *capture_con_substream;
+	struct snd_pcm_substream *playback_pro_substream;
+	struct snd_pcm_substream *capture_pro_substream;
+	unsigned int playback_pro_size;
+	unsigned int capture_pro_size;
+	unsigned int playback_con_virt_addr[6];
+	unsigned int playback_con_active_buf[6];
+	unsigned int capture_con_virt_addr;
+	unsigned int ac97_ext_id;
+	struct snd_ac97 *ac97;
+	struct snd_rawmidi *rmidi[2];
+
+	spinlock_t reg_lock;
+	struct snd_info_entry *proc_entry;
+
+	struct snd_ice1712_eeprom eeprom;
+
+	unsigned int pro_volumes[20];
+	unsigned int omni:1;		/* Delta Omni I/O */
+	unsigned int dxr_enable:1;	/* Terratec DXR enable for DMX6FIRE */
+	unsigned int vt1724:1;
+	unsigned int vt1720:1;
+	unsigned int has_spdif:1;	/* VT1720/4 - has SPDIF I/O */
+	unsigned int force_pdma4:1;	/* VT1720/4 - PDMA4 as non-spdif */
+	unsigned int force_rdma1:1;	/* VT1720/4 - RDMA1 as non-spdif */
+	unsigned int midi_output:1;	/* VT1720/4: MIDI output triggered */
+	unsigned int midi_input:1;	/* VT1720/4: MIDI input triggered */
+	unsigned int own_routing:1;	/* VT1720/4: use own routing ctls */
+	unsigned int num_total_dacs;	/* total DACs */
+	unsigned int num_total_adcs;	/* total ADCs */
+	unsigned int cur_rate;		/* current rate */
+
+	struct mutex open_mutex;
+	struct snd_pcm_substream *pcm_reserved[4];
+	struct snd_pcm_hw_constraint_list *hw_rates; /* card-specific rate constraints */
+
+	unsigned int akm_codecs;
+	struct snd_akm4xxx *akm;
+	struct snd_ice1712_spdif spdif;
+
+	struct mutex i2c_mutex;	/* I2C mutex for ICE1724 registers */
+	struct snd_i2c_bus *i2c;		/* I2C bus */
+	struct snd_i2c_device *cs8427;	/* CS8427 I2C device */
+	unsigned int cs8427_timeout;	/* CS8427 reset timeout in HZ/100 */
+
+	struct ice1712_gpio {
+		unsigned int direction;		/* current direction bits */
+		unsigned int write_mask;	/* current mask bits */
+		unsigned int saved[2];		/* for ewx_i2c */
+		/* operators */
+		void (*set_mask)(struct snd_ice1712 *ice, unsigned int data);
+		unsigned int (*get_mask)(struct snd_ice1712 *ice);
+		void (*set_dir)(struct snd_ice1712 *ice, unsigned int data);
+		unsigned int (*get_dir)(struct snd_ice1712 *ice);
+		void (*set_data)(struct snd_ice1712 *ice, unsigned int data);
+		unsigned int (*get_data)(struct snd_ice1712 *ice);
+		/* misc operators - move to another place? */
+		void (*set_pro_rate)(struct snd_ice1712 *ice, unsigned int rate);
+		void (*i2s_mclk_changed)(struct snd_ice1712 *ice);
+	} gpio;
+	struct mutex gpio_mutex;
+
+	/* other board-specific data */
+	void *spec;
+
+	/* VT172x specific */
+	int pro_rate_default;
+	int (*is_spdif_master)(struct snd_ice1712 *ice);
+	unsigned int (*get_rate)(struct snd_ice1712 *ice);
+	void (*set_rate)(struct snd_ice1712 *ice, unsigned int rate);
+	unsigned char (*set_mclk)(struct snd_ice1712 *ice, unsigned int rate);
+	int (*set_spdif_clock)(struct snd_ice1712 *ice, int type);
+	int (*get_spdif_master_type)(struct snd_ice1712 *ice);
+	char **ext_clock_names;
+	int ext_clock_count;
+	void (*pro_open)(struct snd_ice1712 *, struct snd_pcm_substream *);
+#ifdef CONFIG_PM
+	int (*pm_suspend)(struct snd_ice1712 *);
+	int (*pm_resume)(struct snd_ice1712 *);
+	unsigned int pm_suspend_enabled:1;
+	unsigned int pm_saved_is_spdif_master:1;
+	unsigned int pm_saved_spdif_ctrl;
+	unsigned char pm_saved_spdif_cfg;
+	unsigned int pm_saved_route;
+#endif
+};
+
+
+/*
+ * gpio access functions
+ */
+static inline void snd_ice1712_gpio_set_dir(struct snd_ice1712 *ice, unsigned int bits)
+{
+	ice->gpio.set_dir(ice, bits);
+}
+
+static inline unsigned int snd_ice1712_gpio_get_dir(struct snd_ice1712 *ice)
+{
+	return ice->gpio.get_dir(ice);
+}
+
+static inline void snd_ice1712_gpio_set_mask(struct snd_ice1712 *ice, unsigned int bits)
+{
+	ice->gpio.set_mask(ice, bits);
+}
+
+static inline void snd_ice1712_gpio_write(struct snd_ice1712 *ice, unsigned int val)
+{
+	ice->gpio.set_data(ice, val);
+}
+
+static inline unsigned int snd_ice1712_gpio_read(struct snd_ice1712 *ice)
+{
+	return ice->gpio.get_data(ice);
+}
+
+/*
+ * save and restore gpio status
+ * The access to gpio will be protected by mutex, so don't forget to
+ * restore!
+ */
+static inline void snd_ice1712_save_gpio_status(struct snd_ice1712 *ice)
+{
+	mutex_lock(&ice->gpio_mutex);
+	ice->gpio.saved[0] = ice->gpio.direction;
+	ice->gpio.saved[1] = ice->gpio.write_mask;
+}
+
+static inline void snd_ice1712_restore_gpio_status(struct snd_ice1712 *ice)
+{
+	ice->gpio.set_dir(ice, ice->gpio.saved[0]);
+	ice->gpio.set_mask(ice, ice->gpio.saved[1]);
+	ice->gpio.direction = ice->gpio.saved[0];
+	ice->gpio.write_mask = ice->gpio.saved[1];
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+/* for bit controls */
+#define ICE1712_GPIO(xiface, xname, xindex, mask, invert, xaccess) \
+{ .iface = xiface, .name = xname, .access = xaccess, .info = snd_ctl_boolean_mono_info, \
+  .get = snd_ice1712_gpio_get, .put = snd_ice1712_gpio_put, \
+  .private_value = mask | (invert << 24) }
+
+int snd_ice1712_gpio_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol);
+int snd_ice1712_gpio_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol);
+
+/*
+ * set gpio direction, write mask and data
+ */
+static inline void snd_ice1712_gpio_write_bits(struct snd_ice1712 *ice,
+					       unsigned int mask, unsigned int bits)
+{
+	unsigned val;
+
+	ice->gpio.direction |= mask;
+	snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+	val = snd_ice1712_gpio_read(ice);
+	val &= ~mask;
+	val |= mask & bits;
+	snd_ice1712_gpio_write(ice, val);
+}
+
+static inline int snd_ice1712_gpio_read_bits(struct snd_ice1712 *ice,
+					      unsigned int mask)
+{
+	ice->gpio.direction &= ~mask;
+	snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+	return  snd_ice1712_gpio_read(ice) & mask;
+}
+
+/* route access functions */
+int snd_ice1724_get_route_val(struct snd_ice1712 *ice, int shift);
+int snd_ice1724_put_route_val(struct snd_ice1712 *ice, unsigned int val,
+								int shift);
+
+int snd_ice1712_spdif_build_controls(struct snd_ice1712 *ice);
+
+int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak,
+			     const struct snd_akm4xxx *template,
+			     const struct snd_ak4xxx_private *priv,
+			     struct snd_ice1712 *ice);
+void snd_ice1712_akm4xxx_free(struct snd_ice1712 *ice);
+int snd_ice1712_akm4xxx_build_controls(struct snd_ice1712 *ice);
+
+int snd_ice1712_init_cs8427(struct snd_ice1712 *ice, int addr);
+
+static inline void snd_ice1712_write(struct snd_ice1712 *ice, u8 addr, u8 data)
+{
+	outb(addr, ICEREG(ice, INDEX));
+	outb(data, ICEREG(ice, DATA));
+}
+
+static inline u8 snd_ice1712_read(struct snd_ice1712 *ice, u8 addr)
+{
+	outb(addr, ICEREG(ice, INDEX));
+	return inb(ICEREG(ice, DATA));
+}
+
+
+/*
+ * entry pointer
+ */
+
+struct snd_ice1712_card_info {
+	unsigned int subvendor;
+	char *name;
+	char *model;
+	char *driver;
+	int (*chip_init)(struct snd_ice1712 *);
+	int (*build_controls)(struct snd_ice1712 *);
+	unsigned int no_mpu401:1;
+	unsigned int mpu401_1_info_flags;
+	unsigned int mpu401_2_info_flags;
+	const char *mpu401_1_name;
+	const char *mpu401_2_name;
+	const unsigned int eeprom_size;
+	const unsigned char *eeprom_data;
+};
+
+
+#endif /* __SOUND_ICE1712_H */
diff --git a/linux/sound/pci/ice1712/ice1724.c b/linux/sound/pci/ice1712/ice1724.c
new file mode 100644
index 0000000..c1498fa
--- /dev/null
+++ b/linux/sound/pci/ice1712/ice1724.c
@@ -0,0 +1,2826 @@
+/*
+ *   ALSA driver for VT1724 ICEnsemble ICE1724 / VIA VT1724 (Envy24HT)
+ *                   VIA VT1720 (Envy24PT)
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *                    2002 James Stafford <jstafford@ampltd.com>
+ *                    2003 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/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/rawmidi.h>
+#include <sound/initval.h>
+
+#include <sound/asoundef.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+
+/* lowlevel routines */
+#include "amp.h"
+#include "revo.h"
+#include "aureon.h"
+#include "vt1720_mobo.h"
+#include "pontis.h"
+#include "prodigy192.h"
+#include "prodigy_hifi.h"
+#include "juli.h"
+#include "maya44.h"
+#include "phase.h"
+#include "wtm.h"
+#include "se.h"
+#include "quartet.h"
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("VIA ICEnsemble ICE1724/1720 (Envy24HT/PT)");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{"
+	       REVO_DEVICE_DESC
+	       AMP_AUDIO2000_DEVICE_DESC
+	       AUREON_DEVICE_DESC
+	       VT1720_MOBO_DEVICE_DESC
+	       PONTIS_DEVICE_DESC
+	       PRODIGY192_DEVICE_DESC
+	       PRODIGY_HIFI_DEVICE_DESC
+	       JULI_DEVICE_DESC
+	       MAYA44_DEVICE_DESC
+	       PHASE_DEVICE_DESC
+	       WTM_DEVICE_DESC
+	       SE_DEVICE_DESC
+	       QTET_DEVICE_DESC
+		"{VIA,VT1720},"
+		"{VIA,VT1724},"
+		"{ICEnsemble,Generic ICE1724},"
+		"{ICEnsemble,Generic Envy24HT}"
+		"{ICEnsemble,Generic Envy24PT}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;		/* Enable this card */
+static char *model[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ICE1724 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ICE1724 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ICE1724 soundcard.");
+module_param_array(model, charp, NULL, 0444);
+MODULE_PARM_DESC(model, "Use the given board model.");
+
+
+/* Both VT1720 and VT1724 have the same PCI IDs */
+static DEFINE_PCI_DEVICE_TABLE(snd_vt1724_ids) = {
+	{ PCI_VDEVICE(ICE, PCI_DEVICE_ID_VT1724), 0 },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_vt1724_ids);
+
+
+static int PRO_RATE_LOCKED;
+static int PRO_RATE_RESET = 1;
+static unsigned int PRO_RATE_DEFAULT = 44100;
+
+static char *ext_clock_names[1] = { "IEC958 In" };
+
+/*
+ *  Basic I/O
+ */
+
+/*
+ *  default rates, default clock routines
+ */
+
+/* check whether the clock mode is spdif-in */
+static inline int stdclock_is_spdif_master(struct snd_ice1712 *ice)
+{
+	return (inb(ICEMT1724(ice, RATE)) & VT1724_SPDIF_MASTER) ? 1 : 0;
+}
+
+/*
+ * locking rate makes sense only for internal clock mode
+ */
+static inline int is_pro_rate_locked(struct snd_ice1712 *ice)
+{
+	return (!ice->is_spdif_master(ice)) && PRO_RATE_LOCKED;
+}
+
+/*
+ * ac97 section
+ */
+
+static unsigned char snd_vt1724_ac97_ready(struct snd_ice1712 *ice)
+{
+	unsigned char old_cmd;
+	int tm;
+	for (tm = 0; tm < 0x10000; tm++) {
+		old_cmd = inb(ICEMT1724(ice, AC97_CMD));
+		if (old_cmd & (VT1724_AC97_WRITE | VT1724_AC97_READ))
+			continue;
+		if (!(old_cmd & VT1724_AC97_READY))
+			continue;
+		return old_cmd;
+	}
+	snd_printd(KERN_ERR "snd_vt1724_ac97_ready: timeout\n");
+	return old_cmd;
+}
+
+static int snd_vt1724_ac97_wait_bit(struct snd_ice1712 *ice, unsigned char bit)
+{
+	int tm;
+	for (tm = 0; tm < 0x10000; tm++)
+		if ((inb(ICEMT1724(ice, AC97_CMD)) & bit) == 0)
+			return 0;
+	snd_printd(KERN_ERR "snd_vt1724_ac97_wait_bit: timeout\n");
+	return -EIO;
+}
+
+static void snd_vt1724_ac97_write(struct snd_ac97 *ac97,
+				  unsigned short reg,
+				  unsigned short val)
+{
+	struct snd_ice1712 *ice = ac97->private_data;
+	unsigned char old_cmd;
+
+	old_cmd = snd_vt1724_ac97_ready(ice);
+	old_cmd &= ~VT1724_AC97_ID_MASK;
+	old_cmd |= ac97->num;
+	outb(reg, ICEMT1724(ice, AC97_INDEX));
+	outw(val, ICEMT1724(ice, AC97_DATA));
+	outb(old_cmd | VT1724_AC97_WRITE, ICEMT1724(ice, AC97_CMD));
+	snd_vt1724_ac97_wait_bit(ice, VT1724_AC97_WRITE);
+}
+
+static unsigned short snd_vt1724_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct snd_ice1712 *ice = ac97->private_data;
+	unsigned char old_cmd;
+
+	old_cmd = snd_vt1724_ac97_ready(ice);
+	old_cmd &= ~VT1724_AC97_ID_MASK;
+	old_cmd |= ac97->num;
+	outb(reg, ICEMT1724(ice, AC97_INDEX));
+	outb(old_cmd | VT1724_AC97_READ, ICEMT1724(ice, AC97_CMD));
+	if (snd_vt1724_ac97_wait_bit(ice, VT1724_AC97_READ) < 0)
+		return ~0;
+	return inw(ICEMT1724(ice, AC97_DATA));
+}
+
+
+/*
+ * GPIO operations
+ */
+
+/* set gpio direction 0 = read, 1 = write */
+static void snd_vt1724_set_gpio_dir(struct snd_ice1712 *ice, unsigned int data)
+{
+	outl(data, ICEREG1724(ice, GPIO_DIRECTION));
+	inw(ICEREG1724(ice, GPIO_DIRECTION)); /* dummy read for pci-posting */
+}
+
+/* get gpio direction 0 = read, 1 = write */
+static unsigned int snd_vt1724_get_gpio_dir(struct snd_ice1712 *ice)
+{
+	return inl(ICEREG1724(ice, GPIO_DIRECTION));
+}
+
+/* set the gpio mask (0 = writable) */
+static void snd_vt1724_set_gpio_mask(struct snd_ice1712 *ice, unsigned int data)
+{
+	outw(data, ICEREG1724(ice, GPIO_WRITE_MASK));
+	if (!ice->vt1720) /* VT1720 supports only 16 GPIO bits */
+		outb((data >> 16) & 0xff, ICEREG1724(ice, GPIO_WRITE_MASK_22));
+	inw(ICEREG1724(ice, GPIO_WRITE_MASK)); /* dummy read for pci-posting */
+}
+
+static unsigned int snd_vt1724_get_gpio_mask(struct snd_ice1712 *ice)
+{
+	unsigned int mask;
+	if (!ice->vt1720)
+		mask = (unsigned int)inb(ICEREG1724(ice, GPIO_WRITE_MASK_22));
+	else
+		mask = 0;
+	mask = (mask << 16) | inw(ICEREG1724(ice, GPIO_WRITE_MASK));
+	return mask;
+}
+
+static void snd_vt1724_set_gpio_data(struct snd_ice1712 *ice, unsigned int data)
+{
+	outw(data, ICEREG1724(ice, GPIO_DATA));
+	if (!ice->vt1720)
+		outb(data >> 16, ICEREG1724(ice, GPIO_DATA_22));
+	inw(ICEREG1724(ice, GPIO_DATA)); /* dummy read for pci-posting */
+}
+
+static unsigned int snd_vt1724_get_gpio_data(struct snd_ice1712 *ice)
+{
+	unsigned int data;
+	if (!ice->vt1720)
+		data = (unsigned int)inb(ICEREG1724(ice, GPIO_DATA_22));
+	else
+		data = 0;
+	data = (data << 16) | inw(ICEREG1724(ice, GPIO_DATA));
+	return data;
+}
+
+/*
+ * MIDI
+ */
+
+static void vt1724_midi_clear_rx(struct snd_ice1712 *ice)
+{
+	unsigned int count;
+
+	for (count = inb(ICEREG1724(ice, MPU_RXFIFO)); count > 0; --count)
+		inb(ICEREG1724(ice, MPU_DATA));
+}
+
+static inline struct snd_rawmidi_substream *
+get_rawmidi_substream(struct snd_ice1712 *ice, unsigned int stream)
+{
+	return list_first_entry(&ice->rmidi[0]->streams[stream].substreams,
+				struct snd_rawmidi_substream, list);
+}
+
+static void enable_midi_irq(struct snd_ice1712 *ice, u8 flag, int enable);
+
+static void vt1724_midi_write(struct snd_ice1712 *ice)
+{
+	struct snd_rawmidi_substream *s;
+	int count, i;
+	u8 buffer[32];
+
+	s = get_rawmidi_substream(ice, SNDRV_RAWMIDI_STREAM_OUTPUT);
+	count = 31 - inb(ICEREG1724(ice, MPU_TXFIFO));
+	if (count > 0) {
+		count = snd_rawmidi_transmit(s, buffer, count);
+		for (i = 0; i < count; ++i)
+			outb(buffer[i], ICEREG1724(ice, MPU_DATA));
+	}
+	/* mask irq when all bytes have been transmitted.
+	 * enabled again in output_trigger when the new data comes in.
+	 */
+	enable_midi_irq(ice, VT1724_IRQ_MPU_TX,
+			!snd_rawmidi_transmit_empty(s));
+}
+
+static void vt1724_midi_read(struct snd_ice1712 *ice)
+{
+	struct snd_rawmidi_substream *s;
+	int count, i;
+	u8 buffer[32];
+
+	s = get_rawmidi_substream(ice, SNDRV_RAWMIDI_STREAM_INPUT);
+	count = inb(ICEREG1724(ice, MPU_RXFIFO));
+	if (count > 0) {
+		count = min(count, 32);
+		for (i = 0; i < count; ++i)
+			buffer[i] = inb(ICEREG1724(ice, MPU_DATA));
+		snd_rawmidi_receive(s, buffer, count);
+	}
+}
+
+/* call with ice->reg_lock */
+static void enable_midi_irq(struct snd_ice1712 *ice, u8 flag, int enable)
+{
+	u8 mask = inb(ICEREG1724(ice, IRQMASK));
+	if (enable)
+		mask &= ~flag;
+	else
+		mask |= flag;
+	outb(mask, ICEREG1724(ice, IRQMASK));
+}
+
+static void vt1724_enable_midi_irq(struct snd_rawmidi_substream *substream,
+				   u8 flag, int enable)
+{
+	struct snd_ice1712 *ice = substream->rmidi->private_data;
+
+	spin_lock_irq(&ice->reg_lock);
+	enable_midi_irq(ice, flag, enable);
+	spin_unlock_irq(&ice->reg_lock);
+}
+
+static int vt1724_midi_output_open(struct snd_rawmidi_substream *s)
+{
+	return 0;
+}
+
+static int vt1724_midi_output_close(struct snd_rawmidi_substream *s)
+{
+	return 0;
+}
+
+static void vt1724_midi_output_trigger(struct snd_rawmidi_substream *s, int up)
+{
+	struct snd_ice1712 *ice = s->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ice->reg_lock, flags);
+	if (up) {
+		ice->midi_output = 1;
+		vt1724_midi_write(ice);
+	} else {
+		ice->midi_output = 0;
+		enable_midi_irq(ice, VT1724_IRQ_MPU_TX, 0);
+	}
+	spin_unlock_irqrestore(&ice->reg_lock, flags);
+}
+
+static void vt1724_midi_output_drain(struct snd_rawmidi_substream *s)
+{
+	struct snd_ice1712 *ice = s->rmidi->private_data;
+	unsigned long timeout;
+
+	vt1724_enable_midi_irq(s, VT1724_IRQ_MPU_TX, 0);
+	/* 32 bytes should be transmitted in less than about 12 ms */
+	timeout = jiffies + msecs_to_jiffies(15);
+	do {
+		if (inb(ICEREG1724(ice, MPU_CTRL)) & VT1724_MPU_TX_EMPTY)
+			break;
+		schedule_timeout_uninterruptible(1);
+	} while (time_after(timeout, jiffies));
+}
+
+static struct snd_rawmidi_ops vt1724_midi_output_ops = {
+	.open = vt1724_midi_output_open,
+	.close = vt1724_midi_output_close,
+	.trigger = vt1724_midi_output_trigger,
+	.drain = vt1724_midi_output_drain,
+};
+
+static int vt1724_midi_input_open(struct snd_rawmidi_substream *s)
+{
+	vt1724_midi_clear_rx(s->rmidi->private_data);
+	vt1724_enable_midi_irq(s, VT1724_IRQ_MPU_RX, 1);
+	return 0;
+}
+
+static int vt1724_midi_input_close(struct snd_rawmidi_substream *s)
+{
+	vt1724_enable_midi_irq(s, VT1724_IRQ_MPU_RX, 0);
+	return 0;
+}
+
+static void vt1724_midi_input_trigger(struct snd_rawmidi_substream *s, int up)
+{
+	struct snd_ice1712 *ice = s->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ice->reg_lock, flags);
+	if (up) {
+		ice->midi_input = 1;
+		vt1724_midi_read(ice);
+	} else {
+		ice->midi_input = 0;
+	}
+	spin_unlock_irqrestore(&ice->reg_lock, flags);
+}
+
+static struct snd_rawmidi_ops vt1724_midi_input_ops = {
+	.open = vt1724_midi_input_open,
+	.close = vt1724_midi_input_close,
+	.trigger = vt1724_midi_input_trigger,
+};
+
+
+/*
+ *  Interrupt handler
+ */
+
+static irqreturn_t snd_vt1724_interrupt(int irq, void *dev_id)
+{
+	struct snd_ice1712 *ice = dev_id;
+	unsigned char status;
+	unsigned char status_mask =
+		VT1724_IRQ_MPU_RX | VT1724_IRQ_MPU_TX | VT1724_IRQ_MTPCM;
+	int handled = 0;
+	int timeout = 0;
+
+	while (1) {
+		status = inb(ICEREG1724(ice, IRQSTAT));
+		status &= status_mask;
+		if (status == 0)
+			break;
+		spin_lock(&ice->reg_lock);
+		if (++timeout > 10) {
+			status = inb(ICEREG1724(ice, IRQSTAT));
+			printk(KERN_ERR "ice1724: Too long irq loop, "
+			       "status = 0x%x\n", status);
+			if (status & VT1724_IRQ_MPU_TX) {
+				printk(KERN_ERR "ice1724: Disabling MPU_TX\n");
+				enable_midi_irq(ice, VT1724_IRQ_MPU_TX, 0);
+			}
+			spin_unlock(&ice->reg_lock);
+			break;
+		}
+		handled = 1;
+		if (status & VT1724_IRQ_MPU_TX) {
+			if (ice->midi_output)
+				vt1724_midi_write(ice);
+			else
+				enable_midi_irq(ice, VT1724_IRQ_MPU_TX, 0);
+			/* Due to mysterical reasons, MPU_TX is always
+			 * generated (and can't be cleared) when a PCM
+			 * playback is going.  So let's ignore at the
+			 * next loop.
+			 */
+			status_mask &= ~VT1724_IRQ_MPU_TX;
+		}
+		if (status & VT1724_IRQ_MPU_RX) {
+			if (ice->midi_input)
+				vt1724_midi_read(ice);
+			else
+				vt1724_midi_clear_rx(ice);
+		}
+		/* ack MPU irq */
+		outb(status, ICEREG1724(ice, IRQSTAT));
+		spin_unlock(&ice->reg_lock);
+		if (status & VT1724_IRQ_MTPCM) {
+			/*
+			 * Multi-track PCM
+			 * PCM assignment are:
+			 * Playback DMA0 (M/C) = playback_pro_substream
+			 * Playback DMA1 = playback_con_substream_ds[0]
+			 * Playback DMA2 = playback_con_substream_ds[1]
+			 * Playback DMA3 = playback_con_substream_ds[2]
+			 * Playback DMA4 (SPDIF) = playback_con_substream
+			 * Record DMA0 = capture_pro_substream
+			 * Record DMA1 = capture_con_substream
+			 */
+			unsigned char mtstat = inb(ICEMT1724(ice, IRQ));
+			if (mtstat & VT1724_MULTI_PDMA0) {
+				if (ice->playback_pro_substream)
+					snd_pcm_period_elapsed(ice->playback_pro_substream);
+			}
+			if (mtstat & VT1724_MULTI_RDMA0) {
+				if (ice->capture_pro_substream)
+					snd_pcm_period_elapsed(ice->capture_pro_substream);
+			}
+			if (mtstat & VT1724_MULTI_PDMA1) {
+				if (ice->playback_con_substream_ds[0])
+					snd_pcm_period_elapsed(ice->playback_con_substream_ds[0]);
+			}
+			if (mtstat & VT1724_MULTI_PDMA2) {
+				if (ice->playback_con_substream_ds[1])
+					snd_pcm_period_elapsed(ice->playback_con_substream_ds[1]);
+			}
+			if (mtstat & VT1724_MULTI_PDMA3) {
+				if (ice->playback_con_substream_ds[2])
+					snd_pcm_period_elapsed(ice->playback_con_substream_ds[2]);
+			}
+			if (mtstat & VT1724_MULTI_PDMA4) {
+				if (ice->playback_con_substream)
+					snd_pcm_period_elapsed(ice->playback_con_substream);
+			}
+			if (mtstat & VT1724_MULTI_RDMA1) {
+				if (ice->capture_con_substream)
+					snd_pcm_period_elapsed(ice->capture_con_substream);
+			}
+			/* ack anyway to avoid freeze */
+			outb(mtstat, ICEMT1724(ice, IRQ));
+			/* ought to really handle this properly */
+			if (mtstat & VT1724_MULTI_FIFO_ERR) {
+				unsigned char fstat = inb(ICEMT1724(ice, DMA_FIFO_ERR));
+				outb(fstat, ICEMT1724(ice, DMA_FIFO_ERR));
+				outb(VT1724_MULTI_FIFO_ERR | inb(ICEMT1724(ice, DMA_INT_MASK)), ICEMT1724(ice, DMA_INT_MASK));
+				/* If I don't do this, I get machine lockup due to continual interrupts */
+			}
+
+		}
+	}
+	return IRQ_RETVAL(handled);
+}
+
+/*
+ *  PCM code - professional part (multitrack)
+ */
+
+static unsigned int rates[] = {
+	8000, 9600, 11025, 12000, 16000, 22050, 24000,
+	32000, 44100, 48000, 64000, 88200, 96000,
+	176400, 192000,
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_rates_96 = {
+	.count = ARRAY_SIZE(rates) - 2, /* up to 96000 */
+	.list = rates,
+	.mask = 0,
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_rates_48 = {
+	.count = ARRAY_SIZE(rates) - 5, /* up to 48000 */
+	.list = rates,
+	.mask = 0,
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_rates_192 = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+struct vt1724_pcm_reg {
+	unsigned int addr;	/* ADDR register offset */
+	unsigned int size;	/* SIZE register offset */
+	unsigned int count;	/* COUNT register offset */
+	unsigned int start;	/* start & pause bit */
+};
+
+static int snd_vt1724_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	unsigned char what;
+	unsigned char old;
+	struct snd_pcm_substream *s;
+
+	what = 0;
+	snd_pcm_group_for_each_entry(s, substream) {
+		if (snd_pcm_substream_chip(s) == ice) {
+			const struct vt1724_pcm_reg *reg;
+			reg = s->runtime->private_data;
+			what |= reg->start;
+			snd_pcm_trigger_done(s, substream);
+		}
+	}
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		spin_lock(&ice->reg_lock);
+		old = inb(ICEMT1724(ice, DMA_PAUSE));
+		if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)
+			old |= what;
+		else
+			old &= ~what;
+		outb(old, ICEMT1724(ice, DMA_PAUSE));
+		spin_unlock(&ice->reg_lock);
+		break;
+
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		spin_lock(&ice->reg_lock);
+		old = inb(ICEMT1724(ice, DMA_CONTROL));
+		if (cmd == SNDRV_PCM_TRIGGER_START)
+			old |= what;
+		else
+			old &= ~what;
+		outb(old, ICEMT1724(ice, DMA_CONTROL));
+		spin_unlock(&ice->reg_lock);
+		break;
+
+	case SNDRV_PCM_TRIGGER_RESUME:
+		/* apps will have to restart stream */
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ */
+
+#define DMA_STARTS	(VT1724_RDMA0_START|VT1724_PDMA0_START|VT1724_RDMA1_START|\
+	VT1724_PDMA1_START|VT1724_PDMA2_START|VT1724_PDMA3_START|VT1724_PDMA4_START)
+#define DMA_PAUSES	(VT1724_RDMA0_PAUSE|VT1724_PDMA0_PAUSE|VT1724_RDMA1_PAUSE|\
+	VT1724_PDMA1_PAUSE|VT1724_PDMA2_PAUSE|VT1724_PDMA3_PAUSE|VT1724_PDMA4_PAUSE)
+
+static const unsigned int stdclock_rate_list[16] = {
+	48000, 24000, 12000, 9600, 32000, 16000, 8000, 96000, 44100,
+	22050, 11025, 88200, 176400, 0, 192000, 64000
+};
+
+static unsigned int stdclock_get_rate(struct snd_ice1712 *ice)
+{
+	unsigned int rate;
+	rate = stdclock_rate_list[inb(ICEMT1724(ice, RATE)) & 15];
+	return rate;
+}
+
+static void stdclock_set_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(stdclock_rate_list); i++) {
+		if (stdclock_rate_list[i] == rate) {
+			outb(i, ICEMT1724(ice, RATE));
+			return;
+		}
+	}
+}
+
+static unsigned char stdclock_set_mclk(struct snd_ice1712 *ice,
+				       unsigned int rate)
+{
+	unsigned char val, old;
+	/* check MT02 */
+	if (ice->eeprom.data[ICE_EEP2_ACLINK] & VT1724_CFG_PRO_I2S) {
+		val = old = inb(ICEMT1724(ice, I2S_FORMAT));
+		if (rate > 96000)
+			val |= VT1724_MT_I2S_MCLK_128X; /* 128x MCLK */
+		else
+			val &= ~VT1724_MT_I2S_MCLK_128X; /* 256x MCLK */
+		if (val != old) {
+			outb(val, ICEMT1724(ice, I2S_FORMAT));
+			/* master clock changed */
+			return 1;
+		}
+	}
+	/* no change in master clock */
+	return 0;
+}
+
+static int snd_vt1724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate,
+				    int force)
+{
+	unsigned long flags;
+	unsigned char mclk_change;
+	unsigned int i, old_rate;
+
+	if (rate > ice->hw_rates->list[ice->hw_rates->count - 1])
+		return -EINVAL;
+
+	spin_lock_irqsave(&ice->reg_lock, flags);
+	if ((inb(ICEMT1724(ice, DMA_CONTROL)) & DMA_STARTS) ||
+	    (inb(ICEMT1724(ice, DMA_PAUSE)) & DMA_PAUSES)) {
+		/* running? we cannot change the rate now... */
+		spin_unlock_irqrestore(&ice->reg_lock, flags);
+		return ((rate == ice->cur_rate) && !force) ? 0 : -EBUSY;
+	}
+	if (!force && is_pro_rate_locked(ice)) {
+		/* comparing required and current rate - makes sense for
+		 * internal clock only */
+		spin_unlock_irqrestore(&ice->reg_lock, flags);
+		return (rate == ice->cur_rate) ? 0 : -EBUSY;
+	}
+
+	if (force || !ice->is_spdif_master(ice)) {
+		/* force means the rate was switched by ucontrol, otherwise
+		 * setting clock rate for internal clock mode */
+		old_rate = ice->get_rate(ice);
+		if (force || (old_rate != rate))
+			ice->set_rate(ice, rate);
+		else if (rate == ice->cur_rate) {
+			spin_unlock_irqrestore(&ice->reg_lock, flags);
+			return 0;
+		}
+	}
+
+	ice->cur_rate = rate;
+
+	/* setting master clock */
+	mclk_change = ice->set_mclk(ice, rate);
+
+	spin_unlock_irqrestore(&ice->reg_lock, flags);
+
+	if (mclk_change && ice->gpio.i2s_mclk_changed)
+		ice->gpio.i2s_mclk_changed(ice);
+	if (ice->gpio.set_pro_rate)
+		ice->gpio.set_pro_rate(ice, rate);
+
+	/* set up codecs */
+	for (i = 0; i < ice->akm_codecs; i++) {
+		if (ice->akm[i].ops.set_rate_val)
+			ice->akm[i].ops.set_rate_val(&ice->akm[i], rate);
+	}
+	if (ice->spdif.ops.setup_rate)
+		ice->spdif.ops.setup_rate(ice, rate);
+
+	return 0;
+}
+
+static int snd_vt1724_pcm_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	int i, chs, err;
+
+	chs = params_channels(hw_params);
+	mutex_lock(&ice->open_mutex);
+	/* mark surround channels */
+	if (substream == ice->playback_pro_substream) {
+		/* PDMA0 can be multi-channel up to 8 */
+		chs = chs / 2 - 1;
+		for (i = 0; i < chs; i++) {
+			if (ice->pcm_reserved[i] &&
+			    ice->pcm_reserved[i] != substream) {
+				mutex_unlock(&ice->open_mutex);
+				return -EBUSY;
+			}
+			ice->pcm_reserved[i] = substream;
+		}
+		for (; i < 3; i++) {
+			if (ice->pcm_reserved[i] == substream)
+				ice->pcm_reserved[i] = NULL;
+		}
+	} else {
+		for (i = 0; i < 3; i++) {
+			/* check individual playback stream */
+			if (ice->playback_con_substream_ds[i] == substream) {
+				if (ice->pcm_reserved[i] &&
+				    ice->pcm_reserved[i] != substream) {
+					mutex_unlock(&ice->open_mutex);
+					return -EBUSY;
+				}
+				ice->pcm_reserved[i] = substream;
+				break;
+			}
+		}
+	}
+	mutex_unlock(&ice->open_mutex);
+
+	err = snd_vt1724_set_pro_rate(ice, params_rate(hw_params), 0);
+	if (err < 0)
+		return err;
+
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_vt1724_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	int i;
+
+	mutex_lock(&ice->open_mutex);
+	/* unmark surround channels */
+	for (i = 0; i < 3; i++)
+		if (ice->pcm_reserved[i] == substream)
+			ice->pcm_reserved[i] = NULL;
+	mutex_unlock(&ice->open_mutex);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_vt1724_playback_pro_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	unsigned char val;
+	unsigned int size;
+
+	spin_lock_irq(&ice->reg_lock);
+	val = (8 - substream->runtime->channels) >> 1;
+	outb(val, ICEMT1724(ice, BURST));
+
+	outl(substream->runtime->dma_addr, ICEMT1724(ice, PLAYBACK_ADDR));
+
+	size = (snd_pcm_lib_buffer_bytes(substream) >> 2) - 1;
+	/* outl(size, ICEMT1724(ice, PLAYBACK_SIZE)); */
+	outw(size, ICEMT1724(ice, PLAYBACK_SIZE));
+	outb(size >> 16, ICEMT1724(ice, PLAYBACK_SIZE) + 2);
+	size = (snd_pcm_lib_period_bytes(substream) >> 2) - 1;
+	/* outl(size, ICEMT1724(ice, PLAYBACK_COUNT)); */
+	outw(size, ICEMT1724(ice, PLAYBACK_COUNT));
+	outb(size >> 16, ICEMT1724(ice, PLAYBACK_COUNT) + 2);
+
+	spin_unlock_irq(&ice->reg_lock);
+
+	/*
+	printk(KERN_DEBUG "pro prepare: ch = %d, addr = 0x%x, "
+	       "buffer = 0x%x, period = 0x%x\n",
+	       substream->runtime->channels,
+	       (unsigned int)substream->runtime->dma_addr,
+	       snd_pcm_lib_buffer_bytes(substream),
+	       snd_pcm_lib_period_bytes(substream));
+	*/
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_vt1724_playback_pro_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(inl(ICEMT1724(ice, DMA_CONTROL)) & VT1724_PDMA0_START))
+		return 0;
+#if 0 /* read PLAYBACK_ADDR */
+	ptr = inl(ICEMT1724(ice, PLAYBACK_ADDR));
+	if (ptr < substream->runtime->dma_addr) {
+		snd_printd("ice1724: invalid negative ptr\n");
+		return 0;
+	}
+	ptr -= substream->runtime->dma_addr;
+	ptr = bytes_to_frames(substream->runtime, ptr);
+	if (ptr >= substream->runtime->buffer_size) {
+		snd_printd("ice1724: invalid ptr %d (size=%d)\n",
+			   (int)ptr, (int)substream->runtime->period_size);
+		return 0;
+	}
+#else /* read PLAYBACK_SIZE */
+	ptr = inl(ICEMT1724(ice, PLAYBACK_SIZE)) & 0xffffff;
+	ptr = (ptr + 1) << 2;
+	ptr = bytes_to_frames(substream->runtime, ptr);
+	if (!ptr)
+		;
+	else if (ptr <= substream->runtime->buffer_size)
+		ptr = substream->runtime->buffer_size - ptr;
+	else {
+		snd_printd("ice1724: invalid ptr %d (size=%d)\n",
+			   (int)ptr, (int)substream->runtime->buffer_size);
+		ptr = 0;
+	}
+#endif
+	return ptr;
+}
+
+static int snd_vt1724_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	const struct vt1724_pcm_reg *reg = substream->runtime->private_data;
+
+	spin_lock_irq(&ice->reg_lock);
+	outl(substream->runtime->dma_addr, ice->profi_port + reg->addr);
+	outw((snd_pcm_lib_buffer_bytes(substream) >> 2) - 1,
+	     ice->profi_port + reg->size);
+	outw((snd_pcm_lib_period_bytes(substream) >> 2) - 1,
+	     ice->profi_port + reg->count);
+	spin_unlock_irq(&ice->reg_lock);
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_vt1724_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	const struct vt1724_pcm_reg *reg = substream->runtime->private_data;
+	size_t ptr;
+
+	if (!(inl(ICEMT1724(ice, DMA_CONTROL)) & reg->start))
+		return 0;
+#if 0 /* use ADDR register */
+	ptr = inl(ice->profi_port + reg->addr);
+	ptr -= substream->runtime->dma_addr;
+	return bytes_to_frames(substream->runtime, ptr);
+#else /* use SIZE register */
+	ptr = inw(ice->profi_port + reg->size);
+	ptr = (ptr + 1) << 2;
+	ptr = bytes_to_frames(substream->runtime, ptr);
+	if (!ptr)
+		;
+	else if (ptr <= substream->runtime->buffer_size)
+		ptr = substream->runtime->buffer_size - ptr;
+	else {
+		snd_printd("ice1724: invalid ptr %d (size=%d)\n",
+			   (int)ptr, (int)substream->runtime->buffer_size);
+		ptr = 0;
+	}
+	return ptr;
+#endif
+}
+
+static const struct vt1724_pcm_reg vt1724_pdma0_reg = {
+	.addr = VT1724_MT_PLAYBACK_ADDR,
+	.size = VT1724_MT_PLAYBACK_SIZE,
+	.count = VT1724_MT_PLAYBACK_COUNT,
+	.start = VT1724_PDMA0_START,
+};
+
+static const struct vt1724_pcm_reg vt1724_pdma4_reg = {
+	.addr = VT1724_MT_PDMA4_ADDR,
+	.size = VT1724_MT_PDMA4_SIZE,
+	.count = VT1724_MT_PDMA4_COUNT,
+	.start = VT1724_PDMA4_START,
+};
+
+static const struct vt1724_pcm_reg vt1724_rdma0_reg = {
+	.addr = VT1724_MT_CAPTURE_ADDR,
+	.size = VT1724_MT_CAPTURE_SIZE,
+	.count = VT1724_MT_CAPTURE_COUNT,
+	.start = VT1724_RDMA0_START,
+};
+
+static const struct vt1724_pcm_reg vt1724_rdma1_reg = {
+	.addr = VT1724_MT_RDMA1_ADDR,
+	.size = VT1724_MT_RDMA1_SIZE,
+	.count = VT1724_MT_RDMA1_COUNT,
+	.start = VT1724_RDMA1_START,
+};
+
+#define vt1724_playback_pro_reg vt1724_pdma0_reg
+#define vt1724_playback_spdif_reg vt1724_pdma4_reg
+#define vt1724_capture_pro_reg vt1724_rdma0_reg
+#define vt1724_capture_spdif_reg vt1724_rdma1_reg
+
+static const struct snd_pcm_hardware snd_vt1724_playback_pro = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START),
+	.formats =		SNDRV_PCM_FMTBIT_S32_LE,
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_192000,
+	.rate_min =		8000,
+	.rate_max =		192000,
+	.channels_min =		2,
+	.channels_max =		8,
+	.buffer_bytes_max =	(1UL << 21),	/* 19bits dword */
+	.period_bytes_min =	8 * 4 * 2,	/* FIXME: constraints needed */
+	.period_bytes_max =	(1UL << 21),
+	.periods_min =		2,
+	.periods_max =		1024,
+};
+
+static const struct snd_pcm_hardware snd_vt1724_spdif = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START),
+	.formats =		SNDRV_PCM_FMTBIT_S32_LE,
+	.rates =	        (SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_44100|
+				 SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_88200|
+				 SNDRV_PCM_RATE_96000|SNDRV_PCM_RATE_176400|
+				 SNDRV_PCM_RATE_192000),
+	.rate_min =		32000,
+	.rate_max =		192000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(1UL << 18),	/* 16bits dword */
+	.period_bytes_min =	2 * 4 * 2,
+	.period_bytes_max =	(1UL << 18),
+	.periods_min =		2,
+	.periods_max =		1024,
+};
+
+static const struct snd_pcm_hardware snd_vt1724_2ch_stereo = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START),
+	.formats =		SNDRV_PCM_FMTBIT_S32_LE,
+	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_192000,
+	.rate_min =		8000,
+	.rate_max =		192000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(1UL << 18),	/* 16bits dword */
+	.period_bytes_min =	2 * 4 * 2,
+	.period_bytes_max =	(1UL << 18),
+	.periods_min =		2,
+	.periods_max =		1024,
+};
+
+/*
+ * set rate constraints
+ */
+static void set_std_hw_rates(struct snd_ice1712 *ice)
+{
+	if (ice->eeprom.data[ICE_EEP2_ACLINK] & VT1724_CFG_PRO_I2S) {
+		/* I2S */
+		/* VT1720 doesn't support more than 96kHz */
+		if ((ice->eeprom.data[ICE_EEP2_I2S] & 0x08) && !ice->vt1720)
+			ice->hw_rates = &hw_constraints_rates_192;
+		else
+			ice->hw_rates = &hw_constraints_rates_96;
+	} else {
+		/* ACLINK */
+		ice->hw_rates = &hw_constraints_rates_48;
+	}
+}
+
+static int set_rate_constraints(struct snd_ice1712 *ice,
+				struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw.rate_min = ice->hw_rates->list[0];
+	runtime->hw.rate_max = ice->hw_rates->list[ice->hw_rates->count - 1];
+	runtime->hw.rates = SNDRV_PCM_RATE_KNOT;
+	return snd_pcm_hw_constraint_list(runtime, 0,
+					  SNDRV_PCM_HW_PARAM_RATE,
+					  ice->hw_rates);
+}
+
+/* multi-channel playback needs alignment 8x32bit regardless of the channels
+ * actually used
+ */
+#define VT1724_BUFFER_ALIGN	0x20
+
+static int snd_vt1724_playback_pro_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	int chs, num_indeps;
+
+	runtime->private_data = (void *)&vt1724_playback_pro_reg;
+	ice->playback_pro_substream = substream;
+	runtime->hw = snd_vt1724_playback_pro;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	set_rate_constraints(ice, substream);
+	mutex_lock(&ice->open_mutex);
+	/* calculate the currently available channels */
+	num_indeps = ice->num_total_dacs / 2 - 1;
+	for (chs = 0; chs < num_indeps; chs++) {
+		if (ice->pcm_reserved[chs])
+			break;
+	}
+	chs = (chs + 1) * 2;
+	runtime->hw.channels_max = chs;
+	if (chs > 2) /* channels must be even */
+		snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+	mutex_unlock(&ice->open_mutex);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				   VT1724_BUFFER_ALIGN);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+				   VT1724_BUFFER_ALIGN);
+	if (ice->pro_open)
+		ice->pro_open(ice, substream);
+	return 0;
+}
+
+static int snd_vt1724_capture_pro_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->private_data = (void *)&vt1724_capture_pro_reg;
+	ice->capture_pro_substream = substream;
+	runtime->hw = snd_vt1724_2ch_stereo;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	set_rate_constraints(ice, substream);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				   VT1724_BUFFER_ALIGN);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+				   VT1724_BUFFER_ALIGN);
+	if (ice->pro_open)
+		ice->pro_open(ice, substream);
+	return 0;
+}
+
+static int snd_vt1724_playback_pro_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	if (PRO_RATE_RESET)
+		snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0);
+	ice->playback_pro_substream = NULL;
+
+	return 0;
+}
+
+static int snd_vt1724_capture_pro_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	if (PRO_RATE_RESET)
+		snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0);
+	ice->capture_pro_substream = NULL;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_vt1724_playback_pro_ops = {
+	.open =		snd_vt1724_playback_pro_open,
+	.close =	snd_vt1724_playback_pro_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_vt1724_pcm_hw_params,
+	.hw_free =	snd_vt1724_pcm_hw_free,
+	.prepare =	snd_vt1724_playback_pro_prepare,
+	.trigger =	snd_vt1724_pcm_trigger,
+	.pointer =	snd_vt1724_playback_pro_pointer,
+};
+
+static struct snd_pcm_ops snd_vt1724_capture_pro_ops = {
+	.open =		snd_vt1724_capture_pro_open,
+	.close =	snd_vt1724_capture_pro_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_vt1724_pcm_hw_params,
+	.hw_free =	snd_vt1724_pcm_hw_free,
+	.prepare =	snd_vt1724_pcm_prepare,
+	.trigger =	snd_vt1724_pcm_trigger,
+	.pointer =	snd_vt1724_pcm_pointer,
+};
+
+static int __devinit snd_vt1724_pcm_profi(struct snd_ice1712 *ice, int device)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(ice->card, "ICE1724", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_vt1724_playback_pro_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_vt1724_capture_pro_ops);
+
+	pcm->private_data = ice;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "ICE1724");
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(ice->pci),
+					      256*1024, 256*1024);
+
+	ice->pcm_pro = pcm;
+
+	return 0;
+}
+
+
+/*
+ * SPDIF PCM
+ */
+
+/* update spdif control bits; call with reg_lock */
+static void update_spdif_bits(struct snd_ice1712 *ice, unsigned int val)
+{
+	unsigned char cbit, disabled;
+
+	cbit = inb(ICEREG1724(ice, SPDIF_CFG));
+	disabled = cbit & ~VT1724_CFG_SPDIF_OUT_EN;
+	if (cbit != disabled)
+		outb(disabled, ICEREG1724(ice, SPDIF_CFG));
+	outw(val, ICEMT1724(ice, SPDIF_CTRL));
+	if (cbit != disabled)
+		outb(cbit, ICEREG1724(ice, SPDIF_CFG));
+	outw(val, ICEMT1724(ice, SPDIF_CTRL));
+}
+
+/* update SPDIF control bits according to the given rate */
+static void update_spdif_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+	unsigned int val, nval;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ice->reg_lock, flags);
+	nval = val = inw(ICEMT1724(ice, SPDIF_CTRL));
+	nval &= ~(7 << 12);
+	switch (rate) {
+	case 44100: break;
+	case 48000: nval |= 2 << 12; break;
+	case 32000: nval |= 3 << 12; break;
+	case 88200: nval |= 4 << 12; break;
+	case 96000: nval |= 5 << 12; break;
+	case 192000: nval |= 6 << 12; break;
+	case 176400: nval |= 7 << 12; break;
+	}
+	if (val != nval)
+		update_spdif_bits(ice, nval);
+	spin_unlock_irqrestore(&ice->reg_lock, flags);
+}
+
+static int snd_vt1724_playback_spdif_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	if (!ice->force_pdma4)
+		update_spdif_rate(ice, substream->runtime->rate);
+	return snd_vt1724_pcm_prepare(substream);
+}
+
+static int snd_vt1724_playback_spdif_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->private_data = (void *)&vt1724_playback_spdif_reg;
+	ice->playback_con_substream = substream;
+	if (ice->force_pdma4) {
+		runtime->hw = snd_vt1724_2ch_stereo;
+		set_rate_constraints(ice, substream);
+	} else
+		runtime->hw = snd_vt1724_spdif;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				   VT1724_BUFFER_ALIGN);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+				   VT1724_BUFFER_ALIGN);
+	if (ice->spdif.ops.open)
+		ice->spdif.ops.open(ice, substream);
+	return 0;
+}
+
+static int snd_vt1724_playback_spdif_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	if (PRO_RATE_RESET)
+		snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0);
+	ice->playback_con_substream = NULL;
+	if (ice->spdif.ops.close)
+		ice->spdif.ops.close(ice, substream);
+
+	return 0;
+}
+
+static int snd_vt1724_capture_spdif_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->private_data = (void *)&vt1724_capture_spdif_reg;
+	ice->capture_con_substream = substream;
+	if (ice->force_rdma1) {
+		runtime->hw = snd_vt1724_2ch_stereo;
+		set_rate_constraints(ice, substream);
+	} else
+		runtime->hw = snd_vt1724_spdif;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				   VT1724_BUFFER_ALIGN);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+				   VT1724_BUFFER_ALIGN);
+	if (ice->spdif.ops.open)
+		ice->spdif.ops.open(ice, substream);
+	return 0;
+}
+
+static int snd_vt1724_capture_spdif_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	if (PRO_RATE_RESET)
+		snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0);
+	ice->capture_con_substream = NULL;
+	if (ice->spdif.ops.close)
+		ice->spdif.ops.close(ice, substream);
+
+	return 0;
+}
+
+static struct snd_pcm_ops snd_vt1724_playback_spdif_ops = {
+	.open =		snd_vt1724_playback_spdif_open,
+	.close =	snd_vt1724_playback_spdif_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_vt1724_pcm_hw_params,
+	.hw_free =	snd_vt1724_pcm_hw_free,
+	.prepare =	snd_vt1724_playback_spdif_prepare,
+	.trigger =	snd_vt1724_pcm_trigger,
+	.pointer =	snd_vt1724_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_vt1724_capture_spdif_ops = {
+	.open =		snd_vt1724_capture_spdif_open,
+	.close =	snd_vt1724_capture_spdif_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_vt1724_pcm_hw_params,
+	.hw_free =	snd_vt1724_pcm_hw_free,
+	.prepare =	snd_vt1724_pcm_prepare,
+	.trigger =	snd_vt1724_pcm_trigger,
+	.pointer =	snd_vt1724_pcm_pointer,
+};
+
+
+static int __devinit snd_vt1724_pcm_spdif(struct snd_ice1712 *ice, int device)
+{
+	char *name;
+	struct snd_pcm *pcm;
+	int play, capt;
+	int err;
+
+	if (ice->force_pdma4 ||
+	    (ice->eeprom.data[ICE_EEP2_SPDIF] & VT1724_CFG_SPDIF_OUT_INT)) {
+		play = 1;
+		ice->has_spdif = 1;
+	} else
+		play = 0;
+	if (ice->force_rdma1 ||
+	    (ice->eeprom.data[ICE_EEP2_SPDIF] & VT1724_CFG_SPDIF_IN)) {
+		capt = 1;
+		ice->has_spdif = 1;
+	} else
+		capt = 0;
+	if (!play && !capt)
+		return 0; /* no spdif device */
+
+	if (ice->force_pdma4 || ice->force_rdma1)
+		name = "ICE1724 Secondary";
+	else
+		name = "ICE1724 IEC958";
+	err = snd_pcm_new(ice->card, name, device, play, capt, &pcm);
+	if (err < 0)
+		return err;
+
+	if (play)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				&snd_vt1724_playback_spdif_ops);
+	if (capt)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&snd_vt1724_capture_spdif_ops);
+
+	pcm->private_data = ice;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, name);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(ice->pci),
+					      256*1024, 256*1024);
+
+	ice->pcm = pcm;
+
+	return 0;
+}
+
+
+/*
+ * independent surround PCMs
+ */
+
+static const struct vt1724_pcm_reg vt1724_playback_dma_regs[3] = {
+	{
+		.addr = VT1724_MT_PDMA1_ADDR,
+		.size = VT1724_MT_PDMA1_SIZE,
+		.count = VT1724_MT_PDMA1_COUNT,
+		.start = VT1724_PDMA1_START,
+	},
+	{
+		.addr = VT1724_MT_PDMA2_ADDR,
+		.size = VT1724_MT_PDMA2_SIZE,
+		.count = VT1724_MT_PDMA2_COUNT,
+		.start = VT1724_PDMA2_START,
+	},
+	{
+		.addr = VT1724_MT_PDMA3_ADDR,
+		.size = VT1724_MT_PDMA3_SIZE,
+		.count = VT1724_MT_PDMA3_COUNT,
+		.start = VT1724_PDMA3_START,
+	},
+};
+
+static int snd_vt1724_playback_indep_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	unsigned char val;
+
+	spin_lock_irq(&ice->reg_lock);
+	val = 3 - substream->number;
+	if (inb(ICEMT1724(ice, BURST)) < val)
+		outb(val, ICEMT1724(ice, BURST));
+	spin_unlock_irq(&ice->reg_lock);
+	return snd_vt1724_pcm_prepare(substream);
+}
+
+static int snd_vt1724_playback_indep_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	mutex_lock(&ice->open_mutex);
+	/* already used by PDMA0? */
+	if (ice->pcm_reserved[substream->number]) {
+		mutex_unlock(&ice->open_mutex);
+		return -EBUSY; /* FIXME: should handle blocking mode properly */
+	}
+	mutex_unlock(&ice->open_mutex);
+	runtime->private_data = (void *)&vt1724_playback_dma_regs[substream->number];
+	ice->playback_con_substream_ds[substream->number] = substream;
+	runtime->hw = snd_vt1724_2ch_stereo;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	set_rate_constraints(ice, substream);
+	return 0;
+}
+
+static int snd_vt1724_playback_indep_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+	if (PRO_RATE_RESET)
+		snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0);
+	ice->playback_con_substream_ds[substream->number] = NULL;
+	ice->pcm_reserved[substream->number] = NULL;
+
+	return 0;
+}
+
+static struct snd_pcm_ops snd_vt1724_playback_indep_ops = {
+	.open =		snd_vt1724_playback_indep_open,
+	.close =	snd_vt1724_playback_indep_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_vt1724_pcm_hw_params,
+	.hw_free =	snd_vt1724_pcm_hw_free,
+	.prepare =	snd_vt1724_playback_indep_prepare,
+	.trigger =	snd_vt1724_pcm_trigger,
+	.pointer =	snd_vt1724_pcm_pointer,
+};
+
+
+static int __devinit snd_vt1724_pcm_indep(struct snd_ice1712 *ice, int device)
+{
+	struct snd_pcm *pcm;
+	int play;
+	int err;
+
+	play = ice->num_total_dacs / 2 - 1;
+	if (play <= 0)
+		return 0;
+
+	err = snd_pcm_new(ice->card, "ICE1724 Surrounds", device, play, 0, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_vt1724_playback_indep_ops);
+
+	pcm->private_data = ice;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "ICE1724 Surround PCM");
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(ice->pci),
+					      256*1024, 256*1024);
+
+	ice->pcm_ds = pcm;
+
+	return 0;
+}
+
+
+/*
+ *  Mixer section
+ */
+
+static int __devinit snd_vt1724_ac97_mixer(struct snd_ice1712 *ice)
+{
+	int err;
+
+	if (!(ice->eeprom.data[ICE_EEP2_ACLINK] & VT1724_CFG_PRO_I2S)) {
+		struct snd_ac97_bus *pbus;
+		struct snd_ac97_template ac97;
+		static struct snd_ac97_bus_ops ops = {
+			.write = snd_vt1724_ac97_write,
+			.read = snd_vt1724_ac97_read,
+		};
+
+		/* cold reset */
+		outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD));
+		mdelay(5); /* FIXME */
+		outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD));
+
+		err = snd_ac97_bus(ice->card, 0, &ops, NULL, &pbus);
+		if (err < 0)
+			return err;
+		memset(&ac97, 0, sizeof(ac97));
+		ac97.private_data = ice;
+		err = snd_ac97_mixer(pbus, &ac97, &ice->ac97);
+		if (err < 0)
+			printk(KERN_WARNING "ice1712: cannot initialize pro ac97, skipped\n");
+		else
+			return 0;
+	}
+	/* I2S mixer only */
+	strcat(ice->card->mixername, "ICE1724 - multitrack");
+	return 0;
+}
+
+/*
+ *
+ */
+
+static inline unsigned int eeprom_triple(struct snd_ice1712 *ice, int idx)
+{
+	return (unsigned int)ice->eeprom.data[idx] | \
+		((unsigned int)ice->eeprom.data[idx + 1] << 8) | \
+		((unsigned int)ice->eeprom.data[idx + 2] << 16);
+}
+
+static void snd_vt1724_proc_read(struct snd_info_entry *entry,
+				 struct snd_info_buffer *buffer)
+{
+	struct snd_ice1712 *ice = entry->private_data;
+	unsigned int idx;
+
+	snd_iprintf(buffer, "%s\n\n", ice->card->longname);
+	snd_iprintf(buffer, "EEPROM:\n");
+
+	snd_iprintf(buffer, "  Subvendor        : 0x%x\n", ice->eeprom.subvendor);
+	snd_iprintf(buffer, "  Size             : %i bytes\n", ice->eeprom.size);
+	snd_iprintf(buffer, "  Version          : %i\n", ice->eeprom.version);
+	snd_iprintf(buffer, "  System Config    : 0x%x\n",
+		    ice->eeprom.data[ICE_EEP2_SYSCONF]);
+	snd_iprintf(buffer, "  ACLink           : 0x%x\n",
+		    ice->eeprom.data[ICE_EEP2_ACLINK]);
+	snd_iprintf(buffer, "  I2S              : 0x%x\n",
+		    ice->eeprom.data[ICE_EEP2_I2S]);
+	snd_iprintf(buffer, "  S/PDIF           : 0x%x\n",
+		    ice->eeprom.data[ICE_EEP2_SPDIF]);
+	snd_iprintf(buffer, "  GPIO direction   : 0x%x\n",
+		    ice->eeprom.gpiodir);
+	snd_iprintf(buffer, "  GPIO mask        : 0x%x\n",
+		    ice->eeprom.gpiomask);
+	snd_iprintf(buffer, "  GPIO state       : 0x%x\n",
+		    ice->eeprom.gpiostate);
+	for (idx = 0x12; idx < ice->eeprom.size; idx++)
+		snd_iprintf(buffer, "  Extra #%02i        : 0x%x\n",
+			    idx, ice->eeprom.data[idx]);
+
+	snd_iprintf(buffer, "\nRegisters:\n");
+
+	snd_iprintf(buffer, "  PSDOUT03 : 0x%08x\n",
+		    (unsigned)inl(ICEMT1724(ice, ROUTE_PLAYBACK)));
+	for (idx = 0x0; idx < 0x20 ; idx++)
+		snd_iprintf(buffer, "  CCS%02x    : 0x%02x\n",
+			    idx, inb(ice->port+idx));
+	for (idx = 0x0; idx < 0x30 ; idx++)
+		snd_iprintf(buffer, "  MT%02x     : 0x%02x\n",
+			    idx, inb(ice->profi_port+idx));
+}
+
+static void __devinit snd_vt1724_proc_init(struct snd_ice1712 *ice)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(ice->card, "ice1724", &entry))
+		snd_info_set_text_ops(entry, ice, snd_vt1724_proc_read);
+}
+
+/*
+ *
+ */
+
+static int snd_vt1724_eeprom_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+	uinfo->count = sizeof(struct snd_ice1712_eeprom);
+	return 0;
+}
+
+static int snd_vt1724_eeprom_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	memcpy(ucontrol->value.bytes.data, &ice->eeprom, sizeof(ice->eeprom));
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_vt1724_eeprom __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+	.name = "ICE1724 EEPROM",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,
+	.info = snd_vt1724_eeprom_info,
+	.get = snd_vt1724_eeprom_get
+};
+
+/*
+ */
+static int snd_vt1724_spdif_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static unsigned int encode_spdif_bits(struct snd_aes_iec958 *diga)
+{
+	unsigned int val, rbits;
+
+	val = diga->status[0] & 0x03; /* professional, non-audio */
+	if (val & 0x01) {
+		/* professional */
+		if ((diga->status[0] & IEC958_AES0_PRO_EMPHASIS) ==
+		    IEC958_AES0_PRO_EMPHASIS_5015)
+			val |= 1U << 3;
+		rbits = (diga->status[4] >> 3) & 0x0f;
+		if (rbits) {
+			switch (rbits) {
+			case 2: val |= 5 << 12; break; /* 96k */
+			case 3: val |= 6 << 12; break; /* 192k */
+			case 10: val |= 4 << 12; break; /* 88.2k */
+			case 11: val |= 7 << 12; break; /* 176.4k */
+			}
+		} else {
+			switch (diga->status[0] & IEC958_AES0_PRO_FS) {
+			case IEC958_AES0_PRO_FS_44100:
+				break;
+			case IEC958_AES0_PRO_FS_32000:
+				val |= 3U << 12;
+				break;
+			default:
+				val |= 2U << 12;
+				break;
+			}
+		}
+	} else {
+		/* consumer */
+		val |= diga->status[1] & 0x04; /* copyright */
+		if ((diga->status[0] & IEC958_AES0_CON_EMPHASIS) ==
+		    IEC958_AES0_CON_EMPHASIS_5015)
+			val |= 1U << 3;
+		val |= (unsigned int)(diga->status[1] & 0x3f) << 4; /* category */
+		val |= (unsigned int)(diga->status[3] & IEC958_AES3_CON_FS) << 12; /* fs */
+	}
+	return val;
+}
+
+static void decode_spdif_bits(struct snd_aes_iec958 *diga, unsigned int val)
+{
+	memset(diga->status, 0, sizeof(diga->status));
+	diga->status[0] = val & 0x03; /* professional, non-audio */
+	if (val & 0x01) {
+		/* professional */
+		if (val & (1U << 3))
+			diga->status[0] |= IEC958_AES0_PRO_EMPHASIS_5015;
+		switch ((val >> 12) & 0x7) {
+		case 0:
+			break;
+		case 2:
+			diga->status[0] |= IEC958_AES0_PRO_FS_32000;
+			break;
+		default:
+			diga->status[0] |= IEC958_AES0_PRO_FS_48000;
+			break;
+		}
+	} else {
+		/* consumer */
+		diga->status[0] |= val & (1U << 2); /* copyright */
+		if (val & (1U << 3))
+			diga->status[0] |= IEC958_AES0_CON_EMPHASIS_5015;
+		diga->status[1] |= (val >> 4) & 0x3f; /* category */
+		diga->status[3] |= (val >> 12) & 0x07; /* fs */
+	}
+}
+
+static int snd_vt1724_spdif_default_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	val = inw(ICEMT1724(ice, SPDIF_CTRL));
+	decode_spdif_bits(&ucontrol->value.iec958, val);
+	return 0;
+}
+
+static int snd_vt1724_spdif_default_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int val, old;
+
+	val = encode_spdif_bits(&ucontrol->value.iec958);
+	spin_lock_irq(&ice->reg_lock);
+	old = inw(ICEMT1724(ice, SPDIF_CTRL));
+	if (val != old)
+		update_spdif_bits(ice, val);
+	spin_unlock_irq(&ice->reg_lock);
+	return val != old;
+}
+
+static struct snd_kcontrol_new snd_vt1724_spdif_default __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+	.info =		snd_vt1724_spdif_info,
+	.get =		snd_vt1724_spdif_default_get,
+	.put =		snd_vt1724_spdif_default_put
+};
+
+static int snd_vt1724_spdif_maskc_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO |
+						     IEC958_AES0_PROFESSIONAL |
+						     IEC958_AES0_CON_NOT_COPYRIGHT |
+						     IEC958_AES0_CON_EMPHASIS;
+	ucontrol->value.iec958.status[1] = IEC958_AES1_CON_ORIGINAL |
+						     IEC958_AES1_CON_CATEGORY;
+	ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS;
+	return 0;
+}
+
+static int snd_vt1724_spdif_maskp_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO |
+						     IEC958_AES0_PROFESSIONAL |
+						     IEC958_AES0_PRO_FS |
+						     IEC958_AES0_PRO_EMPHASIS;
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_vt1724_spdif_maskc __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+	.info =		snd_vt1724_spdif_info,
+	.get =		snd_vt1724_spdif_maskc_get,
+};
+
+static struct snd_kcontrol_new snd_vt1724_spdif_maskp __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK),
+	.info =		snd_vt1724_spdif_info,
+	.get =		snd_vt1724_spdif_maskp_get,
+};
+
+#define snd_vt1724_spdif_sw_info		snd_ctl_boolean_mono_info
+
+static int snd_vt1724_spdif_sw_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = inb(ICEREG1724(ice, SPDIF_CFG)) &
+		VT1724_CFG_SPDIF_OUT_EN ? 1 : 0;
+	return 0;
+}
+
+static int snd_vt1724_spdif_sw_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char old, val;
+
+	spin_lock_irq(&ice->reg_lock);
+	old = val = inb(ICEREG1724(ice, SPDIF_CFG));
+	val &= ~VT1724_CFG_SPDIF_OUT_EN;
+	if (ucontrol->value.integer.value[0])
+		val |= VT1724_CFG_SPDIF_OUT_EN;
+	if (old != val)
+		outb(val, ICEREG1724(ice, SPDIF_CFG));
+	spin_unlock_irq(&ice->reg_lock);
+	return old != val;
+}
+
+static struct snd_kcontrol_new snd_vt1724_spdif_switch __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	/* FIXME: the following conflict with IEC958 Playback Route */
+	/* .name =         SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), */
+	.name =         SNDRV_CTL_NAME_IEC958("Output ", NONE, SWITCH),
+	.info =		snd_vt1724_spdif_sw_info,
+	.get =		snd_vt1724_spdif_sw_get,
+	.put =		snd_vt1724_spdif_sw_put
+};
+
+
+#if 0 /* NOT USED YET */
+/*
+ * GPIO access from extern
+ */
+
+#define snd_vt1724_gpio_info		snd_ctl_boolean_mono_info
+
+int snd_vt1724_gpio_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int shift = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value & (1<<24)) ? 1 : 0;
+
+	snd_ice1712_save_gpio_status(ice);
+	ucontrol->value.integer.value[0] =
+		(snd_ice1712_gpio_read(ice) & (1 << shift) ? 1 : 0) ^ invert;
+	snd_ice1712_restore_gpio_status(ice);
+	return 0;
+}
+
+int snd_ice1712_gpio_put(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int shift = kcontrol->private_value & 0xff;
+	int invert = (kcontrol->private_value & (1<<24)) ? mask : 0;
+	unsigned int val, nval;
+
+	if (kcontrol->private_value & (1 << 31))
+		return -EPERM;
+	nval = (ucontrol->value.integer.value[0] ? (1 << shift) : 0) ^ invert;
+	snd_ice1712_save_gpio_status(ice);
+	val = snd_ice1712_gpio_read(ice);
+	nval |= val & ~(1 << shift);
+	if (val != nval)
+		snd_ice1712_gpio_write(ice, nval);
+	snd_ice1712_restore_gpio_status(ice);
+	return val != nval;
+}
+#endif /* NOT USED YET */
+
+/*
+ *  rate
+ */
+static int snd_vt1724_pro_internal_clock_info(struct snd_kcontrol *kcontrol,
+					      struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int hw_rates_count = ice->hw_rates->count;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+
+	uinfo->value.enumerated.items = hw_rates_count + ice->ext_clock_count;
+	/* upper limit - keep at top */
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	if (uinfo->value.enumerated.item >= hw_rates_count)
+		/* ext_clock items */
+		strcpy(uinfo->value.enumerated.name,
+				ice->ext_clock_names[
+				uinfo->value.enumerated.item - hw_rates_count]);
+	else
+		/* int clock items */
+		sprintf(uinfo->value.enumerated.name, "%d",
+			ice->hw_rates->list[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_vt1724_pro_internal_clock_get(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int i, rate;
+
+	spin_lock_irq(&ice->reg_lock);
+	if (ice->is_spdif_master(ice)) {
+		ucontrol->value.enumerated.item[0] = ice->hw_rates->count +
+			ice->get_spdif_master_type(ice);
+	} else {
+		rate = ice->get_rate(ice);
+		ucontrol->value.enumerated.item[0] = 0;
+		for (i = 0; i < ice->hw_rates->count; i++) {
+			if (ice->hw_rates->list[i] == rate) {
+				ucontrol->value.enumerated.item[0] = i;
+				break;
+			}
+		}
+	}
+	spin_unlock_irq(&ice->reg_lock);
+	return 0;
+}
+
+static int stdclock_get_spdif_master_type(struct snd_ice1712 *ice)
+{
+	/* standard external clock - only single type - SPDIF IN */
+	return 0;
+}
+
+/* setting clock to external - SPDIF */
+static int stdclock_set_spdif_clock(struct snd_ice1712 *ice, int type)
+{
+	unsigned char oval;
+	unsigned char i2s_oval;
+	oval = inb(ICEMT1724(ice, RATE));
+	outb(oval | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE));
+	/* setting 256fs */
+	i2s_oval = inb(ICEMT1724(ice, I2S_FORMAT));
+	outb(i2s_oval & ~VT1724_MT_I2S_MCLK_128X, ICEMT1724(ice, I2S_FORMAT));
+	return 0;
+}
+
+
+static int snd_vt1724_pro_internal_clock_put(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int old_rate, new_rate;
+	unsigned int item = ucontrol->value.enumerated.item[0];
+	unsigned int first_ext_clock = ice->hw_rates->count;
+
+	if (item >  first_ext_clock + ice->ext_clock_count - 1)
+		return -EINVAL;
+
+	/* if rate = 0 => external clock */
+	spin_lock_irq(&ice->reg_lock);
+	if (ice->is_spdif_master(ice))
+		old_rate = 0;
+	else
+		old_rate = ice->get_rate(ice);
+	if (item >= first_ext_clock) {
+		/* switching to external clock */
+		ice->set_spdif_clock(ice, item - first_ext_clock);
+		new_rate = 0;
+	} else {
+		/* internal on-card clock */
+		new_rate = ice->hw_rates->list[item];
+		ice->pro_rate_default = new_rate;
+		spin_unlock_irq(&ice->reg_lock);
+		snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 1);
+		spin_lock_irq(&ice->reg_lock);
+	}
+	spin_unlock_irq(&ice->reg_lock);
+
+	/* the first switch to the ext. clock mode? */
+	if (old_rate != new_rate && !new_rate) {
+		/* notify akm chips as well */
+		unsigned int i;
+		if (ice->gpio.set_pro_rate)
+			ice->gpio.set_pro_rate(ice, 0);
+		for (i = 0; i < ice->akm_codecs; i++) {
+			if (ice->akm[i].ops.set_rate_val)
+				ice->akm[i].ops.set_rate_val(&ice->akm[i], 0);
+		}
+	}
+	return old_rate != new_rate;
+}
+
+static struct snd_kcontrol_new snd_vt1724_pro_internal_clock __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Multi Track Internal Clock",
+	.info = snd_vt1724_pro_internal_clock_info,
+	.get = snd_vt1724_pro_internal_clock_get,
+	.put = snd_vt1724_pro_internal_clock_put
+};
+
+#define snd_vt1724_pro_rate_locking_info	snd_ctl_boolean_mono_info
+
+static int snd_vt1724_pro_rate_locking_get(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = PRO_RATE_LOCKED;
+	return 0;
+}
+
+static int snd_vt1724_pro_rate_locking_put(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int change = 0, nval;
+
+	nval = ucontrol->value.integer.value[0] ? 1 : 0;
+	spin_lock_irq(&ice->reg_lock);
+	change = PRO_RATE_LOCKED != nval;
+	PRO_RATE_LOCKED = nval;
+	spin_unlock_irq(&ice->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_vt1724_pro_rate_locking __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Multi Track Rate Locking",
+	.info = snd_vt1724_pro_rate_locking_info,
+	.get = snd_vt1724_pro_rate_locking_get,
+	.put = snd_vt1724_pro_rate_locking_put
+};
+
+#define snd_vt1724_pro_rate_reset_info		snd_ctl_boolean_mono_info
+
+static int snd_vt1724_pro_rate_reset_get(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = PRO_RATE_RESET ? 1 : 0;
+	return 0;
+}
+
+static int snd_vt1724_pro_rate_reset_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int change = 0, nval;
+
+	nval = ucontrol->value.integer.value[0] ? 1 : 0;
+	spin_lock_irq(&ice->reg_lock);
+	change = PRO_RATE_RESET != nval;
+	PRO_RATE_RESET = nval;
+	spin_unlock_irq(&ice->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_vt1724_pro_rate_reset __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Multi Track Rate Reset",
+	.info = snd_vt1724_pro_rate_reset_info,
+	.get = snd_vt1724_pro_rate_reset_get,
+	.put = snd_vt1724_pro_rate_reset_put
+};
+
+
+/*
+ * routing
+ */
+static int snd_vt1724_pro_route_info(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {
+		"PCM Out", /* 0 */
+		"H/W In 0", "H/W In 1", /* 1-2 */
+		"IEC958 In L", "IEC958 In R", /* 3-4 */
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 5;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static inline int analog_route_shift(int idx)
+{
+	return (idx % 2) * 12 + ((idx / 2) * 3) + 8;
+}
+
+static inline int digital_route_shift(int idx)
+{
+	return idx * 3;
+}
+
+int snd_ice1724_get_route_val(struct snd_ice1712 *ice, int shift)
+{
+	unsigned long val;
+	unsigned char eitem;
+	static const unsigned char xlate[8] = {
+		0, 255, 1, 2, 255, 255, 3, 4,
+	};
+
+	val = inl(ICEMT1724(ice, ROUTE_PLAYBACK));
+	val >>= shift;
+	val &= 7; /* we now have 3 bits per output */
+	eitem = xlate[val];
+	if (eitem == 255) {
+		snd_BUG();
+		return 0;
+	}
+	return eitem;
+}
+
+int snd_ice1724_put_route_val(struct snd_ice1712 *ice, unsigned int val,
+								int shift)
+{
+	unsigned int old_val, nval;
+	int change;
+	static const unsigned char xroute[8] = {
+		0, /* PCM */
+		2, /* PSDIN0 Left */
+		3, /* PSDIN0 Right */
+		6, /* SPDIN Left */
+		7, /* SPDIN Right */
+	};
+
+	nval = xroute[val % 5];
+	val = old_val = inl(ICEMT1724(ice, ROUTE_PLAYBACK));
+	val &= ~(0x07 << shift);
+	val |= nval << shift;
+	change = val != old_val;
+	if (change)
+		outl(val, ICEMT1724(ice, ROUTE_PLAYBACK));
+	return change;
+}
+
+static int snd_vt1724_pro_route_analog_get(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	ucontrol->value.enumerated.item[0] =
+		snd_ice1724_get_route_val(ice, analog_route_shift(idx));
+	return 0;
+}
+
+static int snd_vt1724_pro_route_analog_put(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	return snd_ice1724_put_route_val(ice,
+					 ucontrol->value.enumerated.item[0],
+					 analog_route_shift(idx));
+}
+
+static int snd_vt1724_pro_route_spdif_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	ucontrol->value.enumerated.item[0] =
+		snd_ice1724_get_route_val(ice, digital_route_shift(idx));
+	return 0;
+}
+
+static int snd_vt1724_pro_route_spdif_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	return snd_ice1724_put_route_val(ice,
+					 ucontrol->value.enumerated.item[0],
+					 digital_route_shift(idx));
+}
+
+static struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route __devinitdata =
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "H/W Playback Route",
+	.info = snd_vt1724_pro_route_info,
+	.get = snd_vt1724_pro_route_analog_get,
+	.put = snd_vt1724_pro_route_analog_put,
+};
+
+static struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route",
+	.info = snd_vt1724_pro_route_info,
+	.get = snd_vt1724_pro_route_spdif_get,
+	.put = snd_vt1724_pro_route_spdif_put,
+	.count = 2,
+};
+
+
+static int snd_vt1724_pro_peak_info(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 22; /* FIXME: for compatibility with ice1712... */
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	return 0;
+}
+
+static int snd_vt1724_pro_peak_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx;
+
+	spin_lock_irq(&ice->reg_lock);
+	for (idx = 0; idx < 22; idx++) {
+		outb(idx, ICEMT1724(ice, MONITOR_PEAKINDEX));
+		ucontrol->value.integer.value[idx] =
+			inb(ICEMT1724(ice, MONITOR_PEAKDATA));
+	}
+	spin_unlock_irq(&ice->reg_lock);
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_vt1724_mixer_pro_peak __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name = "Multi Track Peak",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info = snd_vt1724_pro_peak_info,
+	.get = snd_vt1724_pro_peak_get
+};
+
+/*
+ *
+ */
+
+static struct snd_ice1712_card_info no_matched __devinitdata;
+
+static struct snd_ice1712_card_info *card_tables[] __devinitdata = {
+	snd_vt1724_revo_cards,
+	snd_vt1724_amp_cards,
+	snd_vt1724_aureon_cards,
+	snd_vt1720_mobo_cards,
+	snd_vt1720_pontis_cards,
+	snd_vt1724_prodigy_hifi_cards,
+	snd_vt1724_prodigy192_cards,
+	snd_vt1724_juli_cards,
+	snd_vt1724_maya44_cards,
+	snd_vt1724_phase_cards,
+	snd_vt1724_wtm_cards,
+	snd_vt1724_se_cards,
+	snd_vt1724_qtet_cards,
+	NULL,
+};
+
+
+/*
+ */
+
+static void wait_i2c_busy(struct snd_ice1712 *ice)
+{
+	int t = 0x10000;
+	while ((inb(ICEREG1724(ice, I2C_CTRL)) & VT1724_I2C_BUSY) && t--)
+		;
+	if (t == -1)
+		printk(KERN_ERR "ice1724: i2c busy timeout\n");
+}
+
+unsigned char snd_vt1724_read_i2c(struct snd_ice1712 *ice,
+				  unsigned char dev, unsigned char addr)
+{
+	unsigned char val;
+
+	mutex_lock(&ice->i2c_mutex);
+	wait_i2c_busy(ice);
+	outb(addr, ICEREG1724(ice, I2C_BYTE_ADDR));
+	outb(dev & ~VT1724_I2C_WRITE, ICEREG1724(ice, I2C_DEV_ADDR));
+	wait_i2c_busy(ice);
+	val = inb(ICEREG1724(ice, I2C_DATA));
+	mutex_unlock(&ice->i2c_mutex);
+	/*
+	printk(KERN_DEBUG "i2c_read: [0x%x,0x%x] = 0x%x\n", dev, addr, val);
+	*/
+	return val;
+}
+
+void snd_vt1724_write_i2c(struct snd_ice1712 *ice,
+			  unsigned char dev, unsigned char addr, unsigned char data)
+{
+	mutex_lock(&ice->i2c_mutex);
+	wait_i2c_busy(ice);
+	/*
+	printk(KERN_DEBUG "i2c_write: [0x%x,0x%x] = 0x%x\n", dev, addr, data);
+	*/
+	outb(addr, ICEREG1724(ice, I2C_BYTE_ADDR));
+	outb(data, ICEREG1724(ice, I2C_DATA));
+	outb(dev | VT1724_I2C_WRITE, ICEREG1724(ice, I2C_DEV_ADDR));
+	wait_i2c_busy(ice);
+	mutex_unlock(&ice->i2c_mutex);
+}
+
+static int __devinit snd_vt1724_read_eeprom(struct snd_ice1712 *ice,
+					    const char *modelname)
+{
+	const int dev = 0xa0;		/* EEPROM device address */
+	unsigned int i, size;
+	struct snd_ice1712_card_info * const *tbl, *c;
+
+	if (!modelname || !*modelname) {
+		ice->eeprom.subvendor = 0;
+		if ((inb(ICEREG1724(ice, I2C_CTRL)) & VT1724_I2C_EEPROM) != 0)
+			ice->eeprom.subvendor =
+				(snd_vt1724_read_i2c(ice, dev, 0x00) << 0) |
+				(snd_vt1724_read_i2c(ice, dev, 0x01) << 8) |
+				(snd_vt1724_read_i2c(ice, dev, 0x02) << 16) |
+				(snd_vt1724_read_i2c(ice, dev, 0x03) << 24);
+		if (ice->eeprom.subvendor == 0 ||
+		    ice->eeprom.subvendor == (unsigned int)-1) {
+			/* invalid subvendor from EEPROM, try the PCI
+			 * subststem ID instead
+			 */
+			u16 vendor, device;
+			pci_read_config_word(ice->pci, PCI_SUBSYSTEM_VENDOR_ID,
+					     &vendor);
+			pci_read_config_word(ice->pci, PCI_SUBSYSTEM_ID, &device);
+			ice->eeprom.subvendor =
+				((unsigned int)swab16(vendor) << 16) | swab16(device);
+			if (ice->eeprom.subvendor == 0 ||
+			    ice->eeprom.subvendor == (unsigned int)-1) {
+				printk(KERN_ERR "ice1724: No valid ID is found\n");
+				return -ENXIO;
+			}
+		}
+	}
+	for (tbl = card_tables; *tbl; tbl++) {
+		for (c = *tbl; c->subvendor; c++) {
+			if (modelname && c->model &&
+			    !strcmp(modelname, c->model)) {
+				printk(KERN_INFO "ice1724: Using board model %s\n",
+				       c->name);
+				ice->eeprom.subvendor = c->subvendor;
+			} else if (c->subvendor != ice->eeprom.subvendor)
+				continue;
+			if (!c->eeprom_size || !c->eeprom_data)
+				goto found;
+			/* if the EEPROM is given by the driver, use it */
+			snd_printdd("using the defined eeprom..\n");
+			ice->eeprom.version = 2;
+			ice->eeprom.size = c->eeprom_size + 6;
+			memcpy(ice->eeprom.data, c->eeprom_data, c->eeprom_size);
+			goto read_skipped;
+		}
+	}
+	printk(KERN_WARNING "ice1724: No matching model found for ID 0x%x\n",
+	       ice->eeprom.subvendor);
+
+ found:
+	ice->eeprom.size = snd_vt1724_read_i2c(ice, dev, 0x04);
+	if (ice->eeprom.size < 6)
+		ice->eeprom.size = 32;
+	else if (ice->eeprom.size > 32) {
+		printk(KERN_ERR "ice1724: Invalid EEPROM (size = %i)\n",
+		       ice->eeprom.size);
+		return -EIO;
+	}
+	ice->eeprom.version = snd_vt1724_read_i2c(ice, dev, 0x05);
+	if (ice->eeprom.version != 2)
+		printk(KERN_WARNING "ice1724: Invalid EEPROM version %i\n",
+		       ice->eeprom.version);
+	size = ice->eeprom.size - 6;
+	for (i = 0; i < size; i++)
+		ice->eeprom.data[i] = snd_vt1724_read_i2c(ice, dev, i + 6);
+
+ read_skipped:
+	ice->eeprom.gpiomask = eeprom_triple(ice, ICE_EEP2_GPIO_MASK);
+	ice->eeprom.gpiostate = eeprom_triple(ice, ICE_EEP2_GPIO_STATE);
+	ice->eeprom.gpiodir = eeprom_triple(ice, ICE_EEP2_GPIO_DIR);
+
+	return 0;
+}
+
+
+
+static void snd_vt1724_chip_reset(struct snd_ice1712 *ice)
+{
+	outb(VT1724_RESET , ICEREG1724(ice, CONTROL));
+	inb(ICEREG1724(ice, CONTROL)); /* pci posting flush */
+	msleep(10);
+	outb(0, ICEREG1724(ice, CONTROL));
+	inb(ICEREG1724(ice, CONTROL)); /* pci posting flush */
+	msleep(10);
+}
+
+static int snd_vt1724_chip_init(struct snd_ice1712 *ice)
+{
+	outb(ice->eeprom.data[ICE_EEP2_SYSCONF], ICEREG1724(ice, SYS_CFG));
+	outb(ice->eeprom.data[ICE_EEP2_ACLINK], ICEREG1724(ice, AC97_CFG));
+	outb(ice->eeprom.data[ICE_EEP2_I2S], ICEREG1724(ice, I2S_FEATURES));
+	outb(ice->eeprom.data[ICE_EEP2_SPDIF], ICEREG1724(ice, SPDIF_CFG));
+
+	ice->gpio.write_mask = ice->eeprom.gpiomask;
+	ice->gpio.direction = ice->eeprom.gpiodir;
+	snd_vt1724_set_gpio_mask(ice, ice->eeprom.gpiomask);
+	snd_vt1724_set_gpio_dir(ice, ice->eeprom.gpiodir);
+	snd_vt1724_set_gpio_data(ice, ice->eeprom.gpiostate);
+
+	outb(0, ICEREG1724(ice, POWERDOWN));
+
+	/* MPU_RX and TX irq masks are cleared later dynamically */
+	outb(VT1724_IRQ_MPU_RX | VT1724_IRQ_MPU_TX , ICEREG1724(ice, IRQMASK));
+
+	/* don't handle FIFO overrun/underruns (just yet),
+	 * since they cause machine lockups
+	 */
+	outb(VT1724_MULTI_FIFO_ERR, ICEMT1724(ice, DMA_INT_MASK));
+
+	return 0;
+}
+
+static int __devinit snd_vt1724_spdif_build_controls(struct snd_ice1712 *ice)
+{
+	int err;
+	struct snd_kcontrol *kctl;
+
+	if (snd_BUG_ON(!ice->pcm))
+		return -EIO;
+
+	if (!ice->own_routing) {
+		err = snd_ctl_add(ice->card,
+			snd_ctl_new1(&snd_vt1724_mixer_pro_spdif_route, ice));
+		if (err < 0)
+			return err;
+	}
+
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_spdif_switch, ice));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_vt1724_spdif_default, ice));
+	if (err < 0)
+		return err;
+	kctl->id.device = ice->pcm->device;
+	err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_vt1724_spdif_maskc, ice));
+	if (err < 0)
+		return err;
+	kctl->id.device = ice->pcm->device;
+	err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_vt1724_spdif_maskp, ice));
+	if (err < 0)
+		return err;
+	kctl->id.device = ice->pcm->device;
+#if 0 /* use default only */
+	err = snd_ctl_add(ice->card, kctl = snd_ctl_new1(&snd_vt1724_spdif_stream, ice));
+	if (err < 0)
+		return err;
+	kctl->id.device = ice->pcm->device;
+	ice->spdif.stream_ctl = kctl;
+#endif
+	return 0;
+}
+
+
+static int __devinit snd_vt1724_build_controls(struct snd_ice1712 *ice)
+{
+	int err;
+
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_eeprom, ice));
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_pro_internal_clock, ice));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_pro_rate_locking, ice));
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_pro_rate_reset, ice));
+	if (err < 0)
+		return err;
+
+	if (!ice->own_routing && ice->num_total_dacs > 0) {
+		struct snd_kcontrol_new tmp = snd_vt1724_mixer_pro_analog_route;
+		tmp.count = ice->num_total_dacs;
+		if (ice->vt1720 && tmp.count > 2)
+			tmp.count = 2;
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&tmp, ice));
+		if (err < 0)
+			return err;
+	}
+
+	err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_mixer_pro_peak, ice));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int snd_vt1724_free(struct snd_ice1712 *ice)
+{
+	if (!ice->port)
+		goto __hw_end;
+	/* mask all interrupts */
+	outb(0xff, ICEMT1724(ice, DMA_INT_MASK));
+	outb(0xff, ICEREG1724(ice, IRQMASK));
+	/* --- */
+__hw_end:
+	if (ice->irq >= 0)
+		free_irq(ice->irq, ice);
+	pci_release_regions(ice->pci);
+	snd_ice1712_akm4xxx_free(ice);
+	pci_disable_device(ice->pci);
+	kfree(ice->spec);
+	kfree(ice);
+	return 0;
+}
+
+static int snd_vt1724_dev_free(struct snd_device *device)
+{
+	struct snd_ice1712 *ice = device->device_data;
+	return snd_vt1724_free(ice);
+}
+
+static int __devinit snd_vt1724_create(struct snd_card *card,
+				       struct pci_dev *pci,
+				       const char *modelname,
+				       struct snd_ice1712 **r_ice1712)
+{
+	struct snd_ice1712 *ice;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_vt1724_dev_free,
+	};
+
+	*r_ice1712 = NULL;
+
+	/* enable PCI device */
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+
+	ice = kzalloc(sizeof(*ice), GFP_KERNEL);
+	if (ice == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	ice->vt1724 = 1;
+	spin_lock_init(&ice->reg_lock);
+	mutex_init(&ice->gpio_mutex);
+	mutex_init(&ice->open_mutex);
+	mutex_init(&ice->i2c_mutex);
+	ice->gpio.set_mask = snd_vt1724_set_gpio_mask;
+	ice->gpio.get_mask = snd_vt1724_get_gpio_mask;
+	ice->gpio.set_dir = snd_vt1724_set_gpio_dir;
+	ice->gpio.get_dir = snd_vt1724_get_gpio_dir;
+	ice->gpio.set_data = snd_vt1724_set_gpio_data;
+	ice->gpio.get_data = snd_vt1724_get_gpio_data;
+	ice->card = card;
+	ice->pci = pci;
+	ice->irq = -1;
+	pci_set_master(pci);
+	snd_vt1724_proc_init(ice);
+	synchronize_irq(pci->irq);
+
+	card->private_data = ice;
+
+	err = pci_request_regions(pci, "ICE1724");
+	if (err < 0) {
+		kfree(ice);
+		pci_disable_device(pci);
+		return err;
+	}
+	ice->port = pci_resource_start(pci, 0);
+	ice->profi_port = pci_resource_start(pci, 1);
+
+	if (request_irq(pci->irq, snd_vt1724_interrupt,
+			IRQF_SHARED, "ICE1724", ice)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_vt1724_free(ice);
+		return -EIO;
+	}
+
+	ice->irq = pci->irq;
+
+	snd_vt1724_chip_reset(ice);
+	if (snd_vt1724_read_eeprom(ice, modelname) < 0) {
+		snd_vt1724_free(ice);
+		return -EIO;
+	}
+	if (snd_vt1724_chip_init(ice) < 0) {
+		snd_vt1724_free(ice);
+		return -EIO;
+	}
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ice, &ops);
+	if (err < 0) {
+		snd_vt1724_free(ice);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*r_ice1712 = ice;
+	return 0;
+}
+
+
+/*
+ *
+ * Registration
+ *
+ */
+
+static int __devinit snd_vt1724_probe(struct pci_dev *pci,
+				      const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_ice1712 *ice;
+	int pcm_dev = 0, err;
+	struct snd_ice1712_card_info * const *tbl, *c;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	strcpy(card->driver, "ICE1724");
+	strcpy(card->shortname, "ICEnsemble ICE1724");
+
+	err = snd_vt1724_create(card, pci, model[dev], &ice);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	/* field init before calling chip_init */
+	ice->ext_clock_count = 0;
+
+	for (tbl = card_tables; *tbl; tbl++) {
+		for (c = *tbl; c->subvendor; c++) {
+			if (c->subvendor == ice->eeprom.subvendor) {
+				strcpy(card->shortname, c->name);
+				if (c->driver) /* specific driver? */
+					strcpy(card->driver, c->driver);
+				if (c->chip_init) {
+					err = c->chip_init(ice);
+					if (err < 0) {
+						snd_card_free(card);
+						return err;
+					}
+				}
+				goto __found;
+			}
+		}
+	}
+	c = &no_matched;
+__found:
+	/*
+	* VT1724 has separate DMAs for the analog and the SPDIF streams while
+	* ICE1712 has only one for both (mixed up).
+	*
+	* Confusingly the analog PCM is named "professional" here because it
+	* was called so in ice1712 driver, and vt1724 driver is derived from
+	* ice1712 driver.
+	*/
+	ice->pro_rate_default = PRO_RATE_DEFAULT;
+	if (!ice->is_spdif_master)
+		ice->is_spdif_master = stdclock_is_spdif_master;
+	if (!ice->get_rate)
+		ice->get_rate = stdclock_get_rate;
+	if (!ice->set_rate)
+		ice->set_rate = stdclock_set_rate;
+	if (!ice->set_mclk)
+		ice->set_mclk = stdclock_set_mclk;
+	if (!ice->set_spdif_clock)
+		ice->set_spdif_clock = stdclock_set_spdif_clock;
+	if (!ice->get_spdif_master_type)
+		ice->get_spdif_master_type = stdclock_get_spdif_master_type;
+	if (!ice->ext_clock_names)
+		ice->ext_clock_names = ext_clock_names;
+	if (!ice->ext_clock_count)
+		ice->ext_clock_count = ARRAY_SIZE(ext_clock_names);
+
+	if (!ice->hw_rates)
+		set_std_hw_rates(ice);
+
+	err = snd_vt1724_pcm_profi(ice, pcm_dev++);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	err = snd_vt1724_pcm_spdif(ice, pcm_dev++);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	err = snd_vt1724_pcm_indep(ice, pcm_dev++);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	err = snd_vt1724_ac97_mixer(ice);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	err = snd_vt1724_build_controls(ice);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if (ice->pcm && ice->has_spdif) { /* has SPDIF I/O */
+		err = snd_vt1724_spdif_build_controls(ice);
+		if (err < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+
+	if (c->build_controls) {
+		err = c->build_controls(ice);
+		if (err < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+
+	if (!c->no_mpu401) {
+		if (ice->eeprom.data[ICE_EEP2_SYSCONF] & VT1724_CFG_MPU401) {
+			struct snd_rawmidi *rmidi;
+
+			err = snd_rawmidi_new(card, "MIDI", 0, 1, 1, &rmidi);
+			if (err < 0) {
+				snd_card_free(card);
+				return err;
+			}
+			ice->rmidi[0] = rmidi;
+			rmidi->private_data = ice;
+			strcpy(rmidi->name, "ICE1724 MIDI");
+			rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+					    SNDRV_RAWMIDI_INFO_INPUT |
+					    SNDRV_RAWMIDI_INFO_DUPLEX;
+			snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+					    &vt1724_midi_output_ops);
+			snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+					    &vt1724_midi_input_ops);
+
+			/* set watermarks */
+			outb(VT1724_MPU_RX_FIFO | 0x1,
+			     ICEREG1724(ice, MPU_FIFO_WM));
+			outb(0x1, ICEREG1724(ice, MPU_FIFO_WM));
+			/* set UART mode */
+			outb(VT1724_MPU_UART, ICEREG1724(ice, MPU_CTRL));
+		}
+	}
+
+	sprintf(card->longname, "%s at 0x%lx, irq %i",
+		card->shortname, ice->port, ice->irq);
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_vt1724_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_vt1724_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_ice1712 *ice = card->private_data;
+
+	if (!ice->pm_suspend_enabled)
+		return 0;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+	snd_pcm_suspend_all(ice->pcm);
+	snd_pcm_suspend_all(ice->pcm_pro);
+	snd_pcm_suspend_all(ice->pcm_ds);
+	snd_ac97_suspend(ice->ac97);
+
+	spin_lock_irq(&ice->reg_lock);
+	ice->pm_saved_is_spdif_master = ice->is_spdif_master(ice);
+	ice->pm_saved_spdif_ctrl = inw(ICEMT1724(ice, SPDIF_CTRL));
+	ice->pm_saved_spdif_cfg = inb(ICEREG1724(ice, SPDIF_CFG));
+	ice->pm_saved_route = inl(ICEMT1724(ice, ROUTE_PLAYBACK));
+	spin_unlock_irq(&ice->reg_lock);
+
+	if (ice->pm_suspend)
+		ice->pm_suspend(ice);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_vt1724_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_ice1712 *ice = card->private_data;
+
+	if (!ice->pm_suspend_enabled)
+		return 0;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+
+	if (pci_enable_device(pci) < 0) {
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+
+	pci_set_master(pci);
+
+	snd_vt1724_chip_reset(ice);
+
+	if (snd_vt1724_chip_init(ice) < 0) {
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+
+	if (ice->pm_resume)
+		ice->pm_resume(ice);
+
+	if (ice->pm_saved_is_spdif_master) {
+		/* switching to external clock via SPDIF */
+		ice->set_spdif_clock(ice, 0);
+	} else {
+		/* internal on-card clock */
+		snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 1);
+	}
+
+	update_spdif_bits(ice, ice->pm_saved_spdif_ctrl);
+
+	outb(ice->pm_saved_spdif_cfg, ICEREG1724(ice, SPDIF_CFG));
+	outl(ice->pm_saved_route, ICEMT1724(ice, ROUTE_PLAYBACK));
+
+	if (ice->ac97)
+		snd_ac97_resume(ice->ac97);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
+
+static struct pci_driver driver = {
+	.name = "ICE1724",
+	.id_table = snd_vt1724_ids,
+	.probe = snd_vt1724_probe,
+	.remove = __devexit_p(snd_vt1724_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_vt1724_suspend,
+	.resume = snd_vt1724_resume,
+#endif
+};
+
+static int __init alsa_card_ice1724_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_ice1724_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_ice1724_init)
+module_exit(alsa_card_ice1724_exit)
diff --git a/linux/sound/pci/ice1712/juli.c b/linux/sound/pci/ice1712/juli.c
new file mode 100644
index 0000000..98bc3b7
--- /dev/null
+++ b/linux/sound/pci/ice1712/juli.c
@@ -0,0 +1,700 @@
+/*
+ *   ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for ESI Juli@ cards
+ *
+ *	Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>
+ *	              2008 Pavel Hofman <dustin@seznam.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "juli.h"
+
+struct juli_spec {
+	struct ak4114 *ak4114;
+	unsigned int analog:1;
+};
+
+/*
+ * chip addresses on I2C bus
+ */
+#define AK4114_ADDR		0x20		/* S/PDIF receiver */
+#define AK4358_ADDR		0x22		/* DAC */
+
+/*
+ * Juli does not use the standard ICE1724 clock scheme. Juli's ice1724 chip is
+ * supplied by external clock provided by Xilinx array and MK73-1 PLL frequency
+ * multiplier. Actual frequency is set by ice1724 GPIOs hooked to the Xilinx.
+ *
+ * The clock circuitry is supplied by the two ice1724 crystals. This
+ * arrangement allows to generate independent clock signal for AK4114's input
+ * rate detection circuit. As a result, Juli, unlike most other
+ * ice1724+ak4114-based cards, detects spdif input rate correctly.
+ * This fact is applied in the driver, allowing to modify PCM stream rate
+ * parameter according to the actual input rate.
+ *
+ * Juli uses the remaining three stereo-channels of its DAC to optionally
+ * monitor analog input, digital input, and digital output. The corresponding
+ * I2S signals are routed by Xilinx, controlled by GPIOs.
+ *
+ * The master mute is implemented using output muting transistors (GPIO) in
+ * combination with smuting the DAC.
+ *
+ * The card itself has no HW master volume control, implemented using the
+ * vmaster control.
+ *
+ * TODO:
+ * researching and fixing the input monitors
+ */
+
+/*
+ * GPIO pins
+ */
+#define GPIO_FREQ_MASK		(3<<0)
+#define GPIO_FREQ_32KHZ		(0<<0)
+#define GPIO_FREQ_44KHZ		(1<<0)
+#define GPIO_FREQ_48KHZ		(2<<0)
+#define GPIO_MULTI_MASK		(3<<2)
+#define GPIO_MULTI_4X		(0<<2)
+#define GPIO_MULTI_2X		(1<<2)
+#define GPIO_MULTI_1X		(2<<2)		/* also external */
+#define GPIO_MULTI_HALF		(3<<2)
+#define GPIO_INTERNAL_CLOCK	(1<<4)		/* 0 = external, 1 = internal */
+#define GPIO_CLOCK_MASK		(1<<4)
+#define GPIO_ANALOG_PRESENT	(1<<5)		/* RO only: 0 = present */
+#define GPIO_RXMCLK_SEL		(1<<7)		/* must be 0 */
+#define GPIO_AK5385A_CKS0	(1<<8)
+#define GPIO_AK5385A_DFS1	(1<<9)
+#define GPIO_AK5385A_DFS0	(1<<10)
+#define GPIO_DIGOUT_MONITOR	(1<<11)		/* 1 = active */
+#define GPIO_DIGIN_MONITOR	(1<<12)		/* 1 = active */
+#define GPIO_ANAIN_MONITOR	(1<<13)		/* 1 = active */
+#define GPIO_AK5385A_CKS1	(1<<14)		/* must be 0 */
+#define GPIO_MUTE_CONTROL	(1<<15)		/* output mute, 1 = muted */
+
+#define GPIO_RATE_MASK		(GPIO_FREQ_MASK | GPIO_MULTI_MASK | \
+		GPIO_CLOCK_MASK)
+#define GPIO_AK5385A_MASK	(GPIO_AK5385A_CKS0 | GPIO_AK5385A_DFS0 | \
+		GPIO_AK5385A_DFS1 | GPIO_AK5385A_CKS1)
+
+#define JULI_PCM_RATE	(SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+		SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+		SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \
+		SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \
+		SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)
+
+#define GPIO_RATE_16000		(GPIO_FREQ_32KHZ | GPIO_MULTI_HALF | \
+		GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_22050		(GPIO_FREQ_44KHZ | GPIO_MULTI_HALF | \
+		GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_24000		(GPIO_FREQ_48KHZ | GPIO_MULTI_HALF | \
+		GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_32000		(GPIO_FREQ_32KHZ | GPIO_MULTI_1X | \
+		GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_44100		(GPIO_FREQ_44KHZ | GPIO_MULTI_1X | \
+		GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_48000		(GPIO_FREQ_48KHZ | GPIO_MULTI_1X | \
+		GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_64000		(GPIO_FREQ_32KHZ | GPIO_MULTI_2X | \
+		GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_88200		(GPIO_FREQ_44KHZ | GPIO_MULTI_2X | \
+		GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_96000		(GPIO_FREQ_48KHZ | GPIO_MULTI_2X | \
+		GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_176400	(GPIO_FREQ_44KHZ | GPIO_MULTI_4X | \
+		GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_192000	(GPIO_FREQ_48KHZ | GPIO_MULTI_4X | \
+		GPIO_INTERNAL_CLOCK)
+
+/*
+ * Initial setup of the conversion array GPIO <-> rate
+ */
+static unsigned int juli_rates[] = {
+	16000, 22050, 24000, 32000,
+	44100, 48000, 64000, 88200,
+	96000, 176400, 192000,
+};
+
+static unsigned int gpio_vals[] = {
+	GPIO_RATE_16000, GPIO_RATE_22050, GPIO_RATE_24000, GPIO_RATE_32000,
+	GPIO_RATE_44100, GPIO_RATE_48000, GPIO_RATE_64000, GPIO_RATE_88200,
+	GPIO_RATE_96000, GPIO_RATE_176400, GPIO_RATE_192000,
+};
+
+static struct snd_pcm_hw_constraint_list juli_rates_info = {
+	.count = ARRAY_SIZE(juli_rates),
+	.list = juli_rates,
+	.mask = 0,
+};
+
+static int get_gpio_val(int rate)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(juli_rates); i++)
+		if (juli_rates[i] == rate)
+			return gpio_vals[i];
+	return 0;
+}
+
+static void juli_ak4114_write(void *private_data, unsigned char reg,
+				unsigned char val)
+{
+	snd_vt1724_write_i2c((struct snd_ice1712 *)private_data, AK4114_ADDR,
+				reg, val);
+}
+
+static unsigned char juli_ak4114_read(void *private_data, unsigned char reg)
+{
+	return snd_vt1724_read_i2c((struct snd_ice1712 *)private_data,
+					AK4114_ADDR, reg);
+}
+
+/*
+ * If SPDIF capture and slaved to SPDIF-IN, setting runtime rate
+ * to the external rate
+ */
+static void juli_spdif_in_open(struct snd_ice1712 *ice,
+				struct snd_pcm_substream *substream)
+{
+	struct juli_spec *spec = ice->spec;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int rate;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
+			!ice->is_spdif_master(ice))
+		return;
+	rate = snd_ak4114_external_rate(spec->ak4114);
+	if (rate >= runtime->hw.rate_min && rate <= runtime->hw.rate_max) {
+		runtime->hw.rate_min = rate;
+		runtime->hw.rate_max = rate;
+	}
+}
+
+/*
+ * AK4358 section
+ */
+
+static void juli_akm_lock(struct snd_akm4xxx *ak, int chip)
+{
+}
+
+static void juli_akm_unlock(struct snd_akm4xxx *ak, int chip)
+{
+}
+
+static void juli_akm_write(struct snd_akm4xxx *ak, int chip,
+			   unsigned char addr, unsigned char data)
+{
+	struct snd_ice1712 *ice = ak->private_data[0];
+	 
+	if (snd_BUG_ON(chip))
+		return;
+	snd_vt1724_write_i2c(ice, AK4358_ADDR, addr, data);
+}
+
+/*
+ * change the rate of envy24HT, AK4358, AK5385
+ */
+static void juli_akm_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+	unsigned char old, tmp, ak4358_dfs;
+	unsigned int ak5385_pins, old_gpio, new_gpio;
+	struct snd_ice1712 *ice = ak->private_data[0];
+	struct juli_spec *spec = ice->spec;
+
+	if (rate == 0)  /* no hint - S/PDIF input is master or the new spdif
+			   input rate undetected, simply return */
+		return;
+
+	/* adjust DFS on codecs */
+	if (rate > 96000)  {
+		ak4358_dfs = 2;
+		ak5385_pins = GPIO_AK5385A_DFS1 | GPIO_AK5385A_CKS0;
+	} else if (rate > 48000) {
+		ak4358_dfs = 1;
+		ak5385_pins = GPIO_AK5385A_DFS0;
+	} else {
+		ak4358_dfs = 0;
+		ak5385_pins = 0;
+	}
+	/* AK5385 first, since it requires cold reset affecting both codecs */
+	old_gpio = ice->gpio.get_data(ice);
+	new_gpio =  (old_gpio & ~GPIO_AK5385A_MASK) | ak5385_pins;
+	/* printk(KERN_DEBUG "JULI - ak5385 set_rate_val: new gpio 0x%x\n",
+		new_gpio); */
+	ice->gpio.set_data(ice, new_gpio);
+
+	/* cold reset */
+	old = inb(ICEMT1724(ice, AC97_CMD));
+	outb(old | VT1724_AC97_COLD, ICEMT1724(ice, AC97_CMD));
+	udelay(1);
+	outb(old & ~VT1724_AC97_COLD, ICEMT1724(ice, AC97_CMD));
+
+	/* AK4358 */
+	/* set new value, reset DFS */
+	tmp = snd_akm4xxx_get(ak, 0, 2);
+	snd_akm4xxx_reset(ak, 1);
+	tmp = snd_akm4xxx_get(ak, 0, 2);
+	tmp &= ~(0x03 << 4);
+	tmp |= ak4358_dfs << 4;
+	snd_akm4xxx_set(ak, 0, 2, tmp);
+	snd_akm4xxx_reset(ak, 0);
+
+	/* reinit ak4114 */
+	snd_ak4114_reinit(spec->ak4114);
+}
+
+#define AK_DAC(xname, xch)	{ .name = xname, .num_channels = xch }
+#define PCM_VOLUME		"PCM Playback Volume"
+#define MONITOR_AN_IN_VOLUME	"Monitor Analog In Volume"
+#define MONITOR_DIG_IN_VOLUME	"Monitor Digital In Volume"
+#define MONITOR_DIG_OUT_VOLUME	"Monitor Digital Out Volume"
+
+static const struct snd_akm4xxx_dac_channel juli_dac[] = {
+	AK_DAC(PCM_VOLUME, 2),
+	AK_DAC(MONITOR_AN_IN_VOLUME, 2),
+	AK_DAC(MONITOR_DIG_OUT_VOLUME, 2),
+	AK_DAC(MONITOR_DIG_IN_VOLUME, 2),
+};
+
+
+static struct snd_akm4xxx akm_juli_dac __devinitdata = {
+	.type = SND_AK4358,
+	.num_dacs = 8,	/* DAC1 - analog out
+			   DAC2 - analog in monitor
+			   DAC3 - digital out monitor
+			   DAC4 - digital in monitor
+			 */
+	.ops = {
+		.lock = juli_akm_lock,
+		.unlock = juli_akm_unlock,
+		.write = juli_akm_write,
+		.set_rate_val = juli_akm_set_rate_val
+	},
+	.dac_info = juli_dac,
+};
+
+#define juli_mute_info		snd_ctl_boolean_mono_info
+
+static int juli_mute_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	val = ice->gpio.get_data(ice) & (unsigned int) kcontrol->private_value;
+	if (kcontrol->private_value == GPIO_MUTE_CONTROL)
+		/* val 0 = signal on */
+		ucontrol->value.integer.value[0] = (val) ? 0 : 1;
+	else
+		/* val 1 = signal on */
+		ucontrol->value.integer.value[0] = (val) ? 1 : 0;
+	return 0;
+}
+
+static int juli_mute_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int old_gpio, new_gpio;
+	old_gpio = ice->gpio.get_data(ice);
+	if (ucontrol->value.integer.value[0]) {
+		/* unmute */
+		if (kcontrol->private_value == GPIO_MUTE_CONTROL) {
+			/* 0 = signal on */
+			new_gpio = old_gpio & ~GPIO_MUTE_CONTROL;
+			/* un-smuting DAC */
+			snd_akm4xxx_write(ice->akm, 0, 0x01, 0x01);
+		} else
+			/* 1 = signal on */
+			new_gpio =  old_gpio |
+				(unsigned int) kcontrol->private_value;
+	} else {
+		/* mute */
+		if (kcontrol->private_value == GPIO_MUTE_CONTROL) {
+			/* 1 = signal off */
+			new_gpio = old_gpio | GPIO_MUTE_CONTROL;
+			/* smuting DAC */
+			snd_akm4xxx_write(ice->akm, 0, 0x01, 0x03);
+		} else
+			/* 0 = signal off */
+			new_gpio =  old_gpio &
+				~((unsigned int) kcontrol->private_value);
+	}
+	/* printk(KERN_DEBUG
+		"JULI - mute/unmute: control_value: 0x%x, old_gpio: 0x%x, "
+		"new_gpio 0x%x\n",
+		(unsigned int)ucontrol->value.integer.value[0], old_gpio,
+		new_gpio); */
+	if (old_gpio != new_gpio) {
+		ice->gpio.set_data(ice, new_gpio);
+		return 1;
+	}
+	/* no change */
+	return 0;
+}
+
+static struct snd_kcontrol_new juli_mute_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = juli_mute_info,
+		.get = juli_mute_get,
+		.put = juli_mute_put,
+		.private_value = GPIO_MUTE_CONTROL,
+	},
+	/* Although the following functionality respects the succint NDA'd
+	 * documentation from the card manufacturer, and the same way of
+	 * operation is coded in OSS Juli driver, only Digital Out monitor
+	 * seems to work. Surprisingly, Analog input monitor outputs Digital
+	 * output data. The two are independent, as enabling both doubles
+	 * volume of the monitor sound.
+	 *
+	 * Checking traces on the board suggests the functionality described
+	 * by the manufacturer is correct - I2S from ADC and AK4114
+	 * go to ICE as well as to Xilinx, I2S inputs of DAC2,3,4 (the monitor
+	 * inputs) are fed from Xilinx.
+	 *
+	 * I even checked traces on board and coded a support in driver for
+	 * an alternative possibility - the unused I2S ICE output channels
+	 * switched to HW-IN/SPDIF-IN and providing the monitoring signal to
+	 * the DAC - to no avail. The I2S outputs seem to be unconnected.
+	 *
+	 * The windows driver supports the monitoring correctly.
+	 */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Monitor Analog In Switch",
+		.info = juli_mute_info,
+		.get = juli_mute_get,
+		.put = juli_mute_put,
+		.private_value = GPIO_ANAIN_MONITOR,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Monitor Digital Out Switch",
+		.info = juli_mute_info,
+		.get = juli_mute_get,
+		.put = juli_mute_put,
+		.private_value = GPIO_DIGOUT_MONITOR,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Monitor Digital In Switch",
+		.info = juli_mute_info,
+		.get = juli_mute_get,
+		.put = juli_mute_put,
+		.private_value = GPIO_DIGIN_MONITOR,
+	},
+};
+
+static char *slave_vols[] __devinitdata = {
+	PCM_VOLUME,
+	MONITOR_AN_IN_VOLUME,
+	MONITOR_DIG_IN_VOLUME,
+	MONITOR_DIG_OUT_VOLUME,
+	NULL
+};
+
+static __devinitdata
+DECLARE_TLV_DB_SCALE(juli_master_db_scale, -6350, 50, 1);
+
+static struct snd_kcontrol __devinit *ctl_find(struct snd_card *card,
+		const char *name)
+{
+	struct snd_ctl_elem_id sid;
+	memset(&sid, 0, sizeof(sid));
+	/* FIXME: strcpy is bad. */
+	strcpy(sid.name, name);
+	sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	return snd_ctl_find_id(card, &sid);
+}
+
+static void __devinit add_slaves(struct snd_card *card,
+				 struct snd_kcontrol *master, char **list)
+{
+	for (; *list; list++) {
+		struct snd_kcontrol *slave = ctl_find(card, *list);
+		/* printk(KERN_DEBUG "add_slaves - %s\n", *list); */
+		if (slave) {
+			/* printk(KERN_DEBUG "slave %s found\n", *list); */
+			snd_ctl_add_slave(master, slave);
+		}
+	}
+}
+
+static int __devinit juli_add_controls(struct snd_ice1712 *ice)
+{
+	struct juli_spec *spec = ice->spec;
+	int err;
+	unsigned int i;
+	struct snd_kcontrol *vmaster;
+
+	err = snd_ice1712_akm4xxx_build_controls(ice);
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < ARRAY_SIZE(juli_mute_controls); i++) {
+		err = snd_ctl_add(ice->card,
+				snd_ctl_new1(&juli_mute_controls[i], ice));
+		if (err < 0)
+			return err;
+	}
+	/* Create virtual master control */
+	vmaster = snd_ctl_make_virtual_master("Master Playback Volume",
+					      juli_master_db_scale);
+	if (!vmaster)
+		return -ENOMEM;
+	add_slaves(ice->card, vmaster, slave_vols);
+	err = snd_ctl_add(ice->card, vmaster);
+	if (err < 0)
+		return err;
+
+	/* only capture SPDIF over AK4114 */
+	err = snd_ak4114_build(spec->ak4114, NULL,
+			ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/*
+ * suspend/resume
+ * */
+
+#ifdef CONFIG_PM
+static int juli_resume(struct snd_ice1712 *ice)
+{
+	struct snd_akm4xxx *ak = ice->akm;
+	struct juli_spec *spec = ice->spec;
+	/* akm4358 un-reset, un-mute */
+	snd_akm4xxx_reset(ak, 0);
+	/* reinit ak4114 */
+	snd_ak4114_reinit(spec->ak4114);
+	return 0;
+}
+
+static int juli_suspend(struct snd_ice1712 *ice)
+{
+	struct snd_akm4xxx *ak = ice->akm;
+	/* akm4358 reset and soft-mute */
+	snd_akm4xxx_reset(ak, 1);
+	return 0;
+}
+#endif
+
+/*
+ * initialize the chip
+ */
+
+static inline int juli_is_spdif_master(struct snd_ice1712 *ice)
+{
+	return (ice->gpio.get_data(ice) & GPIO_INTERNAL_CLOCK) ? 0 : 1;
+}
+
+static unsigned int juli_get_rate(struct snd_ice1712 *ice)
+{
+	int i;
+	unsigned char result;
+
+	result =  ice->gpio.get_data(ice) & GPIO_RATE_MASK;
+	for (i = 0; i < ARRAY_SIZE(gpio_vals); i++)
+		if (gpio_vals[i] == result)
+			return juli_rates[i];
+	return 0;
+}
+
+/* setting new rate */
+static void juli_set_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+	unsigned int old, new;
+	unsigned char val;
+
+	old = ice->gpio.get_data(ice);
+	new =  (old & ~GPIO_RATE_MASK) | get_gpio_val(rate);
+	/* printk(KERN_DEBUG "JULI - set_rate: old %x, new %x\n",
+			old & GPIO_RATE_MASK,
+			new & GPIO_RATE_MASK); */
+
+	ice->gpio.set_data(ice, new);
+	/* switching to external clock - supplied by external circuits */
+	val = inb(ICEMT1724(ice, RATE));
+	outb(val | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE));
+}
+
+static inline unsigned char juli_set_mclk(struct snd_ice1712 *ice,
+					  unsigned int rate)
+{
+	/* no change in master clock */
+	return 0;
+}
+
+/* setting clock to external - SPDIF */
+static int juli_set_spdif_clock(struct snd_ice1712 *ice, int type)
+{
+	unsigned int old;
+	old = ice->gpio.get_data(ice);
+	/* external clock (= 0), multiply 1x, 48kHz */
+	ice->gpio.set_data(ice, (old & ~GPIO_RATE_MASK) | GPIO_MULTI_1X |
+			GPIO_FREQ_48KHZ);
+	return 0;
+}
+
+/* Called when ak4114 detects change in the input SPDIF stream */
+static void juli_ak4114_change(struct ak4114 *ak4114, unsigned char c0,
+			       unsigned char c1)
+{
+	struct snd_ice1712 *ice = ak4114->change_callback_private;
+	int rate;
+	if (ice->is_spdif_master(ice) && c1) {
+		/* only for SPDIF master mode, rate was changed */
+		rate = snd_ak4114_external_rate(ak4114);
+		/* printk(KERN_DEBUG "ak4114 - input rate changed to %d\n",
+				rate); */
+		juli_akm_set_rate_val(ice->akm, rate);
+	}
+}
+
+static int __devinit juli_init(struct snd_ice1712 *ice)
+{
+	static const unsigned char ak4114_init_vals[] = {
+		/* AK4117_REG_PWRDN */	AK4114_RST | AK4114_PWN |
+					AK4114_OCKS0 | AK4114_OCKS1,
+		/* AK4114_REQ_FORMAT */	AK4114_DIF_I24I2S,
+		/* AK4114_REG_IO0 */	AK4114_TX1E,
+		/* AK4114_REG_IO1 */	AK4114_EFH_1024 | AK4114_DIT |
+					AK4114_IPS(1),
+		/* AK4114_REG_INT0_MASK */ 0,
+		/* AK4114_REG_INT1_MASK */ 0
+	};
+	static const unsigned char ak4114_init_txcsb[] = {
+		0x41, 0x02, 0x2c, 0x00, 0x00
+	};
+	int err;
+	struct juli_spec *spec;
+	struct snd_akm4xxx *ak;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	ice->spec = spec;
+
+	err = snd_ak4114_create(ice->card,
+				juli_ak4114_read,
+				juli_ak4114_write,
+				ak4114_init_vals, ak4114_init_txcsb,
+				ice, &spec->ak4114);
+	if (err < 0)
+		return err;
+	/* callback for codecs rate setting */
+	spec->ak4114->change_callback = juli_ak4114_change;
+	spec->ak4114->change_callback_private = ice;
+	/* AK4114 in Juli can detect external rate correctly */
+	spec->ak4114->check_flags = 0;
+
+#if 0
+/*
+ * it seems that the analog doughter board detection does not work reliably, so
+ * force the analog flag; it should be very rare (if ever) to come at Juli@
+ * used without the analog daughter board
+ */
+	spec->analog = (ice->gpio.get_data(ice) & GPIO_ANALOG_PRESENT) ? 0 : 1;
+#else
+	spec->analog = 1;
+#endif
+
+	if (spec->analog) {
+		printk(KERN_INFO "juli@: analog I/O detected\n");
+		ice->num_total_dacs = 2;
+		ice->num_total_adcs = 2;
+
+		ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+		ak = ice->akm;
+		if (!ak)
+			return -ENOMEM;
+		ice->akm_codecs = 1;
+		err = snd_ice1712_akm4xxx_init(ak, &akm_juli_dac, NULL, ice);
+		if (err < 0)
+			return err;
+	}
+
+	/* juli is clocked by Xilinx array */
+	ice->hw_rates = &juli_rates_info;
+	ice->is_spdif_master = juli_is_spdif_master;
+	ice->get_rate = juli_get_rate;
+	ice->set_rate = juli_set_rate;
+	ice->set_mclk = juli_set_mclk;
+	ice->set_spdif_clock = juli_set_spdif_clock;
+
+	ice->spdif.ops.open = juli_spdif_in_open;
+
+#ifdef CONFIG_PM
+	ice->pm_resume = juli_resume;
+	ice->pm_suspend = juli_suspend;
+	ice->pm_suspend_enabled = 1;
+#endif
+
+	return 0;
+}
+
+
+/*
+ * Juli@ boards don't provide the EEPROM data except for the vendor IDs.
+ * hence the driver needs to sets up it properly.
+ */
+
+static unsigned char juli_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x2b,	/* clock 512, mpu401, 1xADC, 1xDACs,
+					   SPDIF in */
+	[ICE_EEP2_ACLINK]      = 0x80,	/* I2S */
+	[ICE_EEP2_I2S]         = 0xf8,	/* vol, 96k, 24bit, 192k */
+	[ICE_EEP2_SPDIF]       = 0xc3,	/* out-en, out-int, spdif-in */
+	[ICE_EEP2_GPIO_DIR]    = 0x9f,	/* 5, 6:inputs; 7, 4-0 outputs*/
+	[ICE_EEP2_GPIO_DIR1]   = 0xff,
+	[ICE_EEP2_GPIO_DIR2]   = 0x7f,
+	[ICE_EEP2_GPIO_MASK]   = 0x60,	/* 5, 6: locked; 7, 4-0 writable */
+	[ICE_EEP2_GPIO_MASK1]  = 0x00,  /* 0-7 writable */
+	[ICE_EEP2_GPIO_MASK2]  = 0x7f,
+	[ICE_EEP2_GPIO_STATE]  = GPIO_FREQ_48KHZ | GPIO_MULTI_1X |
+	       GPIO_INTERNAL_CLOCK,	/* internal clock, multiple 1x, 48kHz*/
+	[ICE_EEP2_GPIO_STATE1] = 0x00,	/* unmuted */
+	[ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_juli_cards[] __devinitdata = {
+	{
+		.subvendor = VT1724_SUBDEVICE_JULI,
+		.name = "ESI Juli@",
+		.model = "juli",
+		.chip_init = juli_init,
+		.build_controls = juli_add_controls,
+		.eeprom_size = sizeof(juli_eeprom),
+		.eeprom_data = juli_eeprom,
+	},
+	{ } /* terminator */
+};
diff --git a/linux/sound/pci/ice1712/juli.h b/linux/sound/pci/ice1712/juli.h
new file mode 100644
index 0000000..d9f8534
--- /dev/null
+++ b/linux/sound/pci/ice1712/juli.h
@@ -0,0 +1,10 @@
+#ifndef __SOUND_JULI_H
+#define __SOUND_JULI_H
+
+#define JULI_DEVICE_DESC		"{ESI,Juli@},"
+
+#define VT1724_SUBDEVICE_JULI		0x31305345	/* Juli@ */
+
+extern struct snd_ice1712_card_info  snd_vt1724_juli_cards[];
+
+#endif	/* __SOUND_JULI_H */
diff --git a/linux/sound/pci/ice1712/maya44.c b/linux/sound/pci/ice1712/maya44.c
new file mode 100644
index 0000000..726fd4b
--- /dev/null
+++ b/linux/sound/pci/ice1712/maya44.c
@@ -0,0 +1,779 @@
+/*
+ *   ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for ESI Maya44 cards
+ *
+ *	Copyright (c) 2009 Takashi Iwai <tiwai@suse.de>
+ *	Based on the patches by Rainer Zimmermann <mail@lightshed.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/init.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "maya44.h"
+
+/* WM8776 register indexes */
+#define WM8776_REG_HEADPHONE_L		0x00
+#define WM8776_REG_HEADPHONE_R		0x01
+#define WM8776_REG_HEADPHONE_MASTER	0x02
+#define WM8776_REG_DAC_ATTEN_L		0x03
+#define WM8776_REG_DAC_ATTEN_R		0x04
+#define WM8776_REG_DAC_ATTEN_MASTER	0x05
+#define WM8776_REG_DAC_PHASE		0x06
+#define WM8776_REG_DAC_CONTROL		0x07
+#define WM8776_REG_DAC_MUTE		0x08
+#define WM8776_REG_DAC_DEEMPH		0x09
+#define WM8776_REG_DAC_IF_CONTROL	0x0a
+#define WM8776_REG_ADC_IF_CONTROL	0x0b
+#define WM8776_REG_MASTER_MODE_CONTROL	0x0c
+#define WM8776_REG_POWERDOWN		0x0d
+#define WM8776_REG_ADC_ATTEN_L		0x0e
+#define WM8776_REG_ADC_ATTEN_R		0x0f
+#define WM8776_REG_ADC_ALC1		0x10
+#define WM8776_REG_ADC_ALC2		0x11
+#define WM8776_REG_ADC_ALC3		0x12
+#define WM8776_REG_ADC_NOISE_GATE	0x13
+#define WM8776_REG_ADC_LIMITER		0x14
+#define WM8776_REG_ADC_MUX		0x15
+#define WM8776_REG_OUTPUT_MUX		0x16
+#define WM8776_REG_RESET		0x17
+
+#define WM8776_NUM_REGS			0x18
+
+/* clock ratio identifiers for snd_wm8776_set_rate() */
+#define WM8776_CLOCK_RATIO_128FS	0
+#define WM8776_CLOCK_RATIO_192FS	1
+#define WM8776_CLOCK_RATIO_256FS	2
+#define WM8776_CLOCK_RATIO_384FS	3
+#define WM8776_CLOCK_RATIO_512FS	4
+#define WM8776_CLOCK_RATIO_768FS	5
+
+enum { WM_VOL_HP, WM_VOL_DAC, WM_VOL_ADC, WM_NUM_VOLS };
+enum { WM_SW_DAC, WM_SW_BYPASS, WM_NUM_SWITCHES };
+
+struct snd_wm8776 {
+	unsigned char addr;
+	unsigned short regs[WM8776_NUM_REGS];
+	unsigned char volumes[WM_NUM_VOLS][2];
+	unsigned int switch_bits;
+};
+
+struct snd_maya44 {
+	struct snd_ice1712 *ice;
+	struct snd_wm8776 wm[2];
+	struct mutex mutex;
+};
+
+
+/* write the given register and save the data to the cache */
+static void wm8776_write(struct snd_ice1712 *ice, struct snd_wm8776 *wm,
+			 unsigned char reg, unsigned short val)
+{
+	/*
+	 * WM8776 registers are up to 9 bits wide, bit 8 is placed in the LSB
+	 * of the address field
+	 */
+	snd_vt1724_write_i2c(ice, wm->addr,
+			     (reg << 1) | ((val >> 8) & 1),
+			     val & 0xff);
+	wm->regs[reg] = val;
+}
+
+/*
+ * update the given register with and/or mask and save the data to the cache
+ */
+static int wm8776_write_bits(struct snd_ice1712 *ice, struct snd_wm8776 *wm,
+			     unsigned char reg,
+			     unsigned short mask, unsigned short val)
+{
+	val |= wm->regs[reg] & ~mask;
+	if (val != wm->regs[reg]) {
+		wm8776_write(ice, wm, reg, val);
+		return 1;
+	}
+	return 0;
+}
+
+
+/*
+ * WM8776 volume controls
+ */
+
+struct maya_vol_info {
+	unsigned int maxval;		/* volume range: 0..maxval */
+	unsigned char regs[2];		/* left and right registers */
+	unsigned short mask;		/* value mask */
+	unsigned short offset;		/* zero-value offset */
+	unsigned short mute;		/* mute bit */
+	unsigned short update;		/* update bits */
+	unsigned char mux_bits[2];	/* extra bits for ADC mute */
+};
+
+static struct maya_vol_info vol_info[WM_NUM_VOLS] = {
+	[WM_VOL_HP] = {
+		.maxval = 80,
+		.regs = { WM8776_REG_HEADPHONE_L, WM8776_REG_HEADPHONE_R },
+		.mask = 0x7f,
+		.offset = 0x30,
+		.mute = 0x00,
+		.update = 0x180,	/* update and zero-cross enable */
+	},
+	[WM_VOL_DAC] = {
+		.maxval = 255,
+		.regs = { WM8776_REG_DAC_ATTEN_L, WM8776_REG_DAC_ATTEN_R },
+		.mask = 0xff,
+		.offset = 0x01,
+		.mute = 0x00,
+		.update = 0x100,	/* zero-cross enable */
+	},
+	[WM_VOL_ADC] = {
+		.maxval = 91,
+		.regs = { WM8776_REG_ADC_ATTEN_L, WM8776_REG_ADC_ATTEN_R },
+		.mask = 0xff,
+		.offset = 0xa5,
+		.mute = 0xa5,
+		.update = 0x100,	/* update */
+		.mux_bits = { 0x80, 0x40 }, /* ADCMUX bits */
+	},
+};
+
+/*
+ * dB tables
+ */
+/* headphone output: mute, -73..+6db (1db step) */
+static const DECLARE_TLV_DB_SCALE(db_scale_hp, -7400, 100, 1);
+/* DAC output: mute, -127..0db (0.5db step) */
+static const DECLARE_TLV_DB_SCALE(db_scale_dac, -12750, 50, 1);
+/* ADC gain: mute, -21..+24db (0.5db step) */
+static const DECLARE_TLV_DB_SCALE(db_scale_adc, -2100, 50, 1);
+
+static int maya_vol_info(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_info *uinfo)
+{
+	unsigned int idx = kcontrol->private_value;
+	struct maya_vol_info *vol = &vol_info[idx];
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = vol->maxval;
+	return 0;
+}
+
+static int maya_vol_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+	struct snd_wm8776 *wm =
+		&chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)];
+	unsigned int idx = kcontrol->private_value;
+
+	mutex_lock(&chip->mutex);
+	ucontrol->value.integer.value[0] = wm->volumes[idx][0];
+	ucontrol->value.integer.value[1] = wm->volumes[idx][1];
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int maya_vol_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+	struct snd_wm8776 *wm =
+		&chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)];
+	unsigned int idx = kcontrol->private_value;
+	struct maya_vol_info *vol = &vol_info[idx];
+	unsigned int val, data;
+	int ch, changed = 0;
+
+	mutex_lock(&chip->mutex);
+	for (ch = 0; ch < 2; ch++) {
+		val = ucontrol->value.integer.value[ch];
+		if (val > vol->maxval)
+			val = vol->maxval;
+		if (val == wm->volumes[idx][ch])
+			continue;
+		if (!val)
+			data = vol->mute;
+		else
+			data = (val - 1) + vol->offset;
+		data |= vol->update;
+		changed |= wm8776_write_bits(chip->ice, wm, vol->regs[ch],
+					     vol->mask | vol->update, data);
+		if (vol->mux_bits[ch])
+			wm8776_write_bits(chip->ice, wm, WM8776_REG_ADC_MUX,
+					  vol->mux_bits[ch],
+					  val ? 0 : vol->mux_bits[ch]);
+		wm->volumes[idx][ch] = val;
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+/*
+ * WM8776 switch controls
+ */
+
+#define COMPOSE_SW_VAL(idx, reg, mask)	((idx) | ((reg) << 8) | ((mask) << 16))
+#define GET_SW_VAL_IDX(val)	((val) & 0xff)
+#define GET_SW_VAL_REG(val)	(((val) >> 8) & 0xff)
+#define GET_SW_VAL_MASK(val)	(((val) >> 16) & 0xff)
+
+#define maya_sw_info	snd_ctl_boolean_mono_info
+
+static int maya_sw_get(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+	struct snd_wm8776 *wm =
+		&chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)];
+	unsigned int idx = GET_SW_VAL_IDX(kcontrol->private_value);
+
+	ucontrol->value.integer.value[0] = (wm->switch_bits >> idx) & 1;
+	return 0;
+}
+
+static int maya_sw_put(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+	struct snd_wm8776 *wm =
+		&chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)];
+	unsigned int idx = GET_SW_VAL_IDX(kcontrol->private_value);
+	unsigned int mask, val;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	mask = 1 << idx;
+	wm->switch_bits &= ~mask;
+	val = ucontrol->value.integer.value[0];
+	if (val)
+		wm->switch_bits |= mask;
+	mask = GET_SW_VAL_MASK(kcontrol->private_value);
+	changed = wm8776_write_bits(chip->ice, wm,
+				    GET_SW_VAL_REG(kcontrol->private_value),
+				    mask, val ? mask : 0);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+/*
+ * GPIO pins (known ones for maya44)
+ */
+#define GPIO_PHANTOM_OFF	2
+#define GPIO_MIC_RELAY		4
+#define GPIO_SPDIF_IN_INV	5
+#define GPIO_MUST_BE_0		7
+
+/*
+ * GPIO switch controls
+ */
+
+#define COMPOSE_GPIO_VAL(shift, inv)	((shift) | ((inv) << 8))
+#define GET_GPIO_VAL_SHIFT(val)		((val) & 0xff)
+#define GET_GPIO_VAL_INV(val)		(((val) >> 8) & 1)
+
+static int maya_set_gpio_bits(struct snd_ice1712 *ice, unsigned int mask,
+			      unsigned int bits)
+{
+	unsigned int data;
+	data = snd_ice1712_gpio_read(ice);
+	if ((data & mask) == bits)
+		return 0;
+	snd_ice1712_gpio_write(ice, (data & ~mask) | bits);
+	return 1;
+}
+
+#define maya_gpio_sw_info	snd_ctl_boolean_mono_info
+
+static int maya_gpio_sw_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int shift = GET_GPIO_VAL_SHIFT(kcontrol->private_value);
+	unsigned int val;
+
+	val = (snd_ice1712_gpio_read(chip->ice) >> shift) & 1;
+	if (GET_GPIO_VAL_INV(kcontrol->private_value))
+		val = !val;
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int maya_gpio_sw_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int shift = GET_GPIO_VAL_SHIFT(kcontrol->private_value);
+	unsigned int val, mask;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	mask = 1 << shift;
+	val = ucontrol->value.integer.value[0];
+	if (GET_GPIO_VAL_INV(kcontrol->private_value))
+		val = !val;
+	val = val ? mask : 0;
+	changed = maya_set_gpio_bits(chip->ice, mask, val);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+/*
+ * capture source selection
+ */
+
+/* known working input slots (0-4) */
+#define MAYA_LINE_IN	1	/* in-2 */
+#define MAYA_MIC_IN	3	/* in-4 */
+
+static void wm8776_select_input(struct snd_maya44 *chip, int idx, int line)
+{
+	wm8776_write_bits(chip->ice, &chip->wm[idx], WM8776_REG_ADC_MUX,
+			  0x1f, 1 << line);
+}
+
+static int maya_rec_src_info(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "Line", "Mic" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = ARRAY_SIZE(texts);
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+			uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int maya_rec_src_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+	int sel;
+
+	if (snd_ice1712_gpio_read(chip->ice) & (1 << GPIO_MIC_RELAY))
+		sel = 1;
+	else
+		sel = 0;
+	ucontrol->value.enumerated.item[0] = sel;
+	return 0;
+}
+
+static int maya_rec_src_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+	int sel = ucontrol->value.enumerated.item[0];
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	changed = maya_set_gpio_bits(chip->ice, 1 << GPIO_MIC_RELAY,
+				     sel ? (1 << GPIO_MIC_RELAY) : 0);
+	wm8776_select_input(chip, 0, sel ? MAYA_MIC_IN : MAYA_LINE_IN);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+/*
+ * Maya44 routing switch settings have different meanings than the standard
+ * ice1724 switches as defined in snd_vt1724_pro_route_info (ice1724.c).
+ */
+static int maya_pb_route_info(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {
+		"PCM Out", /* 0 */
+		"Input 1", "Input 2", "Input 3", "Input 4"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = ARRAY_SIZE(texts);
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+			uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int maya_pb_route_shift(int idx)
+{
+	static const unsigned char shift[10] =
+		{ 8, 20, 0, 3, 11, 23, 14, 26, 17, 29 };
+	return shift[idx % 10];
+}
+
+static int maya_pb_route_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	ucontrol->value.enumerated.item[0] =
+		snd_ice1724_get_route_val(chip->ice, maya_pb_route_shift(idx));
+	return 0;
+}
+
+static int maya_pb_route_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	return snd_ice1724_put_route_val(chip->ice,
+					 ucontrol->value.enumerated.item[0],
+					 maya_pb_route_shift(idx));
+}
+
+
+/*
+ * controls to be added
+ */
+
+static struct snd_kcontrol_new maya_controls[] __devinitdata = {
+	{
+		.name = "Crossmix Playback Volume",
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+		.info = maya_vol_info,
+		.get = maya_vol_get,
+		.put = maya_vol_put,
+		.tlv = { .p = db_scale_hp },
+		.private_value = WM_VOL_HP,
+		.count = 2,
+	},
+	{
+		.name = "PCM Playback Volume",
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+		.info = maya_vol_info,
+		.get = maya_vol_get,
+		.put = maya_vol_put,
+		.tlv = { .p = db_scale_dac },
+		.private_value = WM_VOL_DAC,
+		.count = 2,
+	},
+	{
+		.name = "Line Capture Volume",
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+		.info = maya_vol_info,
+		.get = maya_vol_get,
+		.put = maya_vol_put,
+		.tlv = { .p = db_scale_adc },
+		.private_value = WM_VOL_ADC,
+		.count = 2,
+	},
+	{
+		.name = "PCM Playback Switch",
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.info = maya_sw_info,
+		.get = maya_sw_get,
+		.put = maya_sw_put,
+		.private_value = COMPOSE_SW_VAL(WM_SW_DAC,
+						WM8776_REG_OUTPUT_MUX, 0x01),
+		.count = 2,
+	},
+	{
+		.name = "Bypass Playback Switch",
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.info = maya_sw_info,
+		.get = maya_sw_get,
+		.put = maya_sw_put,
+		.private_value = COMPOSE_SW_VAL(WM_SW_BYPASS,
+						WM8776_REG_OUTPUT_MUX, 0x04),
+		.count = 2,
+	},
+	{
+		.name = "Capture Source",
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.info = maya_rec_src_info,
+		.get = maya_rec_src_get,
+		.put = maya_rec_src_put,
+	},
+	{
+		.name = "Mic Phantom Power Switch",
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.info = maya_gpio_sw_info,
+		.get = maya_gpio_sw_get,
+		.put = maya_gpio_sw_put,
+		.private_value = COMPOSE_GPIO_VAL(GPIO_PHANTOM_OFF, 1),
+	},
+	{
+		.name = "SPDIF Capture Switch",
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.info = maya_gpio_sw_info,
+		.get = maya_gpio_sw_get,
+		.put = maya_gpio_sw_put,
+		.private_value = COMPOSE_GPIO_VAL(GPIO_SPDIF_IN_INV, 1),
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "H/W Playback Route",
+		.info = maya_pb_route_info,
+		.get = maya_pb_route_get,
+		.put = maya_pb_route_put,
+		.count = 4,  /* FIXME: do controls 5-9 have any meaning? */
+	},
+};
+
+static int __devinit maya44_add_controls(struct snd_ice1712 *ice)
+{
+	int err, i;
+
+	for (i = 0; i < ARRAY_SIZE(maya_controls); i++) {
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&maya_controls[i],
+							  ice->spec));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+
+/*
+ * initialize a wm8776 chip
+ */
+static void __devinit wm8776_init(struct snd_ice1712 *ice,
+				  struct snd_wm8776 *wm, unsigned int addr)
+{
+	static const unsigned short inits_wm8776[] = {
+		0x02, 0x100, /* R2: headphone L+R muted + update */
+		0x05, 0x100, /* R5: DAC output L+R muted + update */
+		0x06, 0x000, /* R6: DAC output phase normal */
+		0x07, 0x091, /* R7: DAC enable zero cross detection,
+				normal output */
+		0x08, 0x000, /* R8: DAC soft mute off */
+		0x09, 0x000, /* R9: no deemph, DAC zero detect disabled */
+		0x0a, 0x022, /* R10: DAC I2C mode, std polarities, 24bit */
+		0x0b, 0x022, /* R11: ADC I2C mode, std polarities, 24bit,
+				highpass filter enabled */
+		0x0c, 0x042, /* R12: ADC+DAC slave, ADC+DAC 44,1kHz */
+		0x0d, 0x000, /* R13: all power up */
+		0x0e, 0x100, /* R14: ADC left muted,
+				enable zero cross detection */
+		0x0f, 0x100, /* R15: ADC right muted,
+				enable zero cross detection */
+			     /* R16: ALC...*/
+		0x11, 0x000, /* R17: disable ALC */
+			     /* R18: ALC...*/
+			     /* R19: noise gate...*/
+		0x15, 0x000, /* R21: ADC input mux init, mute all inputs */
+		0x16, 0x001, /* R22: output mux, select DAC */
+		0xff, 0xff
+	};
+
+	const unsigned short *ptr;
+	unsigned char reg;
+	unsigned short data;
+
+	wm->addr = addr;
+	/* enable DAC output; mute bypass, aux & all inputs */
+	wm->switch_bits = (1 << WM_SW_DAC);
+
+	ptr = inits_wm8776;
+	while (*ptr != 0xff) {
+		reg = *ptr++;
+		data = *ptr++;
+		wm8776_write(ice, wm, reg, data);
+	}
+}
+
+
+/*
+ * change the rate on the WM8776 codecs.
+ * this assumes that the VT17xx's rate is changed by the calling function.
+ * NOTE: even though the WM8776's are running in slave mode and rate
+ * selection is automatic, we need to call snd_wm8776_set_rate() here
+ * to make sure some flags are set correctly.
+ */
+static void set_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+	struct snd_maya44 *chip = ice->spec;
+	unsigned int ratio, adc_ratio, val;
+	int i;
+
+	switch (rate) {
+	case 192000:
+		ratio = WM8776_CLOCK_RATIO_128FS;
+		break;
+	case 176400:
+		ratio = WM8776_CLOCK_RATIO_128FS;
+		break;
+	case 96000:
+		ratio = WM8776_CLOCK_RATIO_256FS;
+		break;
+	case 88200:
+		ratio = WM8776_CLOCK_RATIO_384FS;
+		break;
+	case 48000:
+		ratio = WM8776_CLOCK_RATIO_512FS;
+		break;
+	case 44100:
+		ratio = WM8776_CLOCK_RATIO_512FS;
+		break;
+	case 32000:
+		ratio = WM8776_CLOCK_RATIO_768FS;
+		break;
+	case 0:
+		/* no hint - S/PDIF input is master, simply return */
+		return;
+	default:
+		snd_BUG();
+		return;
+	}
+
+	/*
+	 * this currently sets the same rate for ADC and DAC, but limits
+	 * ADC rate to 256X (96kHz). For 256X mode (96kHz), this sets ADC
+	 * oversampling to 64x, as recommended by WM8776 datasheet.
+	 * Setting the rate is not really necessary in slave mode.
+	 */
+	adc_ratio = ratio;
+	if (adc_ratio < WM8776_CLOCK_RATIO_256FS)
+		adc_ratio = WM8776_CLOCK_RATIO_256FS;
+
+	val = adc_ratio;
+	if (adc_ratio == WM8776_CLOCK_RATIO_256FS)
+		val |= 8;
+	val |= ratio << 4;
+
+	mutex_lock(&chip->mutex);
+	for (i = 0; i < 2; i++)
+		wm8776_write_bits(ice, &chip->wm[i],
+				  WM8776_REG_MASTER_MODE_CONTROL,
+				  0x180, val);
+	mutex_unlock(&chip->mutex);
+}
+
+/*
+ * supported sample rates (to override the default one)
+ */
+
+static unsigned int rates[] = {
+	32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000
+};
+
+/* playback rates: 32..192 kHz */
+static struct snd_pcm_hw_constraint_list dac_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0
+};
+
+
+/*
+ * chip addresses on I2C bus
+ */
+static unsigned char wm8776_addr[2] __devinitdata = {
+	0x34, 0x36, /* codec 0 & 1 */
+};
+
+/*
+ * initialize the chip
+ */
+static int __devinit maya44_init(struct snd_ice1712 *ice)
+{
+	int i;
+	struct snd_maya44 *chip;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+	mutex_init(&chip->mutex);
+	chip->ice = ice;
+	ice->spec = chip;
+
+	/* initialise codecs */
+	ice->num_total_dacs = 4;
+	ice->num_total_adcs = 4;
+	ice->akm_codecs = 0;
+
+	for (i = 0; i < 2; i++) {
+		wm8776_init(ice, &chip->wm[i], wm8776_addr[i]);
+		wm8776_select_input(chip, i, MAYA_LINE_IN);
+	}
+
+	/* set card specific rates */
+	ice->hw_rates = &dac_rates;
+
+	/* register change rate notifier */
+	ice->gpio.set_pro_rate = set_rate;
+
+	/* RDMA1 (2nd input channel) is used for ADC by default */
+	ice->force_rdma1 = 1;
+
+	/* have an own routing control */
+	ice->own_routing = 1;
+
+	return 0;
+}
+
+
+/*
+ * Maya44 boards don't provide the EEPROM data except for the vendor IDs.
+ * hence the driver needs to sets up it properly.
+ */
+
+static unsigned char maya44_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x45,
+		/* clock xin1=49.152MHz, mpu401, 2 stereo ADCs+DACs */
+	[ICE_EEP2_ACLINK]      = 0x80,
+		/* I2S */
+	[ICE_EEP2_I2S]         = 0xf8,
+		/* vol, 96k, 24bit, 192k */
+	[ICE_EEP2_SPDIF]       = 0xc3,
+		/* enable spdif out, spdif out supp, spdif-in, ext spdif out */
+	[ICE_EEP2_GPIO_DIR]    = 0xff,
+	[ICE_EEP2_GPIO_DIR1]   = 0xff,
+	[ICE_EEP2_GPIO_DIR2]   = 0xff,
+	[ICE_EEP2_GPIO_MASK]   = 0/*0x9f*/,
+	[ICE_EEP2_GPIO_MASK1]  = 0/*0xff*/,
+	[ICE_EEP2_GPIO_MASK2]  = 0/*0x7f*/,
+	[ICE_EEP2_GPIO_STATE]  = (1 << GPIO_PHANTOM_OFF) |
+			(1 << GPIO_SPDIF_IN_INV),
+	[ICE_EEP2_GPIO_STATE1] = 0x00,
+	[ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_maya44_cards[] __devinitdata = {
+	{
+		.subvendor = VT1724_SUBDEVICE_MAYA44,
+		.name = "ESI Maya44",
+		.model = "maya44",
+		.chip_init = maya44_init,
+		.build_controls = maya44_add_controls,
+		.eeprom_size = sizeof(maya44_eeprom),
+		.eeprom_data = maya44_eeprom,
+	},
+	{ } /* terminator */
+};
diff --git a/linux/sound/pci/ice1712/maya44.h b/linux/sound/pci/ice1712/maya44.h
new file mode 100644
index 0000000..eafd03a
--- /dev/null
+++ b/linux/sound/pci/ice1712/maya44.h
@@ -0,0 +1,10 @@
+#ifndef __SOUND_MAYA44_H
+#define __SOUND_MAYA44_H
+
+#define MAYA44_DEVICE_DESC		"{ESI,Maya44},"
+
+#define VT1724_SUBDEVICE_MAYA44		0x34315441	/* Maya44 */
+
+extern struct snd_ice1712_card_info  snd_vt1724_maya44_cards[];
+
+#endif	/* __SOUND_MAYA44_H */
diff --git a/linux/sound/pci/ice1712/phase.c b/linux/sound/pci/ice1712/phase.c
new file mode 100644
index 0000000..de29be8
--- /dev/null
+++ b/linux/sound/pci/ice1712/phase.c
@@ -0,0 +1,975 @@
+/*
+ *   ALSA driver for ICEnsemble ICE1724 (Envy24)
+ *
+ *   Lowlevel functions for Terratec PHASE 22
+ *
+ *	Copyright (c) 2005 Misha Zhilin <misha@epiphan.com>
+ *
+ *   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
+ *
+ */
+
+/* PHASE 22 overview:
+ *   Audio controller: VIA Envy24HT-S (slightly trimmed down Envy24HT, 4in/4out)
+ *   Analog chip: AK4524 (partially via Philip's 74HCT125)
+ *   Digital receiver: CS8414-CS (supported in this release)
+ *		PHASE 22 revision 2.0 and Terrasoniq/Musonik TS22PCI have CS8416
+ *		(support status unknown, please test and report)
+ *
+ *   Envy connects to AK4524
+ *	- CS directly from GPIO 10
+ *	- CCLK via 74HCT125's gate #4 from GPIO 4
+ *	- CDTI via 74HCT125's gate #2 from GPIO 5
+ *		CDTI may be completely blocked by 74HCT125's gate #1
+ *		controlled by GPIO 3
+ */
+
+/* PHASE 28 overview:
+ *   Audio controller: VIA Envy24HT (full untrimmed version, 4in/8out)
+ *   Analog chip: WM8770 (8 channel 192k DAC, 2 channel 96k ADC)
+ *   Digital receiver: CS8414-CS (supported in this release)
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "phase.h"
+#include <sound/tlv.h>
+
+/* AC97 register cache for Phase28 */
+struct phase28_spec {
+	unsigned short master[2];
+	unsigned short vol[8];
+};
+
+/* WM8770 registers */
+#define WM_DAC_ATTEN		0x00	/* DAC1-8 analog attenuation */
+#define WM_DAC_MASTER_ATTEN	0x08	/* DAC master analog attenuation */
+#define WM_DAC_DIG_ATTEN	0x09	/* DAC1-8 digital attenuation */
+#define WM_DAC_DIG_MASTER_ATTEN	0x11	/* DAC master digital attenuation */
+#define WM_PHASE_SWAP		0x12	/* DAC phase */
+#define WM_DAC_CTRL1		0x13	/* DAC control bits */
+#define WM_MUTE			0x14	/* mute controls */
+#define WM_DAC_CTRL2		0x15	/* de-emphasis and zefo-flag */
+#define WM_INT_CTRL		0x16	/* interface control */
+#define WM_MASTER		0x17	/* master clock and mode */
+#define WM_POWERDOWN		0x18	/* power-down controls */
+#define WM_ADC_GAIN		0x19	/* ADC gain L(19)/R(1a) */
+#define WM_ADC_MUX		0x1b	/* input MUX */
+#define WM_OUT_MUX1		0x1c	/* output MUX */
+#define WM_OUT_MUX2		0x1e	/* output MUX */
+#define WM_RESET		0x1f	/* software reset */
+
+
+/*
+ * Logarithmic volume values for WM8770
+ * Computed as 20 * Log10(255 / x)
+ */
+static const unsigned char wm_vol[256] = {
+	127, 48, 42, 39, 36, 34, 33, 31, 30, 29, 28, 27, 27, 26, 25, 25, 24,
+	24, 23, 23, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 18,
+	17, 17, 17, 17, 16, 16, 16, 16, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14,
+	14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11,
+	11, 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 9, 9,
+	9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5,
+	5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+	4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+	2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+#define WM_VOL_MAX	(sizeof(wm_vol) - 1)
+#define WM_VOL_MUTE	0x8000
+
+static struct snd_akm4xxx akm_phase22 __devinitdata = {
+	.type = SND_AK4524,
+	.num_dacs = 2,
+	.num_adcs = 2,
+};
+
+static struct snd_ak4xxx_private akm_phase22_priv __devinitdata = {
+	.caddr =	2,
+	.cif =		1,
+	.data_mask =	1 << 4,
+	.clk_mask =	1 << 5,
+	.cs_mask =	1 << 10,
+	.cs_addr =	1 << 10,
+	.cs_none =	0,
+	.add_flags = 	1 << 3,
+	.mask_flags =	0,
+};
+
+static int __devinit phase22_init(struct snd_ice1712 *ice)
+{
+	struct snd_akm4xxx *ak;
+	int err;
+
+	/* Configure DAC/ADC description for generic part of ice1724 */
+	switch (ice->eeprom.subvendor) {
+	case VT1724_SUBDEVICE_PHASE22:
+	case VT1724_SUBDEVICE_TS22:
+		ice->num_total_dacs = 2;
+		ice->num_total_adcs = 2;
+		ice->vt1720 = 1; /* Envy24HT-S have 16 bit wide GPIO */
+		break;
+	default:
+		snd_BUG();
+		return -EINVAL;
+	}
+
+	/* Initialize analog chips */
+	ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+	ak = ice->akm;
+	if (!ak)
+		return -ENOMEM;
+	ice->akm_codecs = 1;
+	switch (ice->eeprom.subvendor) {
+	case VT1724_SUBDEVICE_PHASE22:
+	case VT1724_SUBDEVICE_TS22:
+		err = snd_ice1712_akm4xxx_init(ak, &akm_phase22,
+						&akm_phase22_priv, ice);
+		if (err < 0)
+			return err;
+		break;
+	}
+
+	return 0;
+}
+
+static int __devinit phase22_add_controls(struct snd_ice1712 *ice)
+{
+	int err = 0;
+
+	switch (ice->eeprom.subvendor) {
+	case VT1724_SUBDEVICE_PHASE22:
+	case VT1724_SUBDEVICE_TS22:
+		err = snd_ice1712_akm4xxx_build_controls(ice);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static unsigned char phase22_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x28,  /* clock 512, mpu 401,
+					spdif-in/1xADC, 1xDACs */
+	[ICE_EEP2_ACLINK]      = 0x80,	/* I2S */
+	[ICE_EEP2_I2S]         = 0xf0,	/* vol, 96k, 24bit */
+	[ICE_EEP2_SPDIF]       = 0xc3,	/* out-en, out-int, spdif-in */
+	[ICE_EEP2_GPIO_DIR]    = 0xff,
+	[ICE_EEP2_GPIO_DIR1]   = 0xff,
+	[ICE_EEP2_GPIO_DIR2]   = 0xff,
+	[ICE_EEP2_GPIO_MASK]   = 0x00,
+	[ICE_EEP2_GPIO_MASK1]  = 0x00,
+	[ICE_EEP2_GPIO_MASK2]  = 0x00,
+	[ICE_EEP2_GPIO_STATE]  = 0x00,
+	[ICE_EEP2_GPIO_STATE1] = 0x00,
+	[ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+static unsigned char phase28_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x2b,  /* clock 512, mpu401,
+					spdif-in/1xADC, 4xDACs */
+	[ICE_EEP2_ACLINK]      = 0x80,	/* I2S */
+	[ICE_EEP2_I2S]         = 0xfc,	/* vol, 96k, 24bit, 192k */
+	[ICE_EEP2_SPDIF]       = 0xc3,	/* out-en, out-int, spdif-in */
+	[ICE_EEP2_GPIO_DIR]    = 0xff,
+	[ICE_EEP2_GPIO_DIR1]   = 0xff,
+	[ICE_EEP2_GPIO_DIR2]   = 0x5f,
+	[ICE_EEP2_GPIO_MASK]   = 0x00,
+	[ICE_EEP2_GPIO_MASK1]  = 0x00,
+	[ICE_EEP2_GPIO_MASK2]  = 0x00,
+	[ICE_EEP2_GPIO_STATE]  = 0x00,
+	[ICE_EEP2_GPIO_STATE1] = 0x00,
+	[ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+/*
+ * write data in the SPI mode
+ */
+static void phase28_spi_write(struct snd_ice1712 *ice, unsigned int cs,
+				unsigned int data, int bits)
+{
+	unsigned int tmp;
+	int i;
+
+	tmp = snd_ice1712_gpio_read(ice);
+
+	snd_ice1712_gpio_set_mask(ice, ~(PHASE28_WM_RW|PHASE28_SPI_MOSI|
+					PHASE28_SPI_CLK|PHASE28_WM_CS));
+	tmp |= PHASE28_WM_RW;
+	tmp &= ~cs;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+
+	for (i = bits - 1; i >= 0; i--) {
+		tmp &= ~PHASE28_SPI_CLK;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+		if (data & (1 << i))
+			tmp |= PHASE28_SPI_MOSI;
+		else
+			tmp &= ~PHASE28_SPI_MOSI;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+		tmp |= PHASE28_SPI_CLK;
+		snd_ice1712_gpio_write(ice, tmp);
+		udelay(1);
+	}
+
+	tmp &= ~PHASE28_SPI_CLK;
+	tmp |= cs;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+	tmp |= PHASE28_SPI_CLK;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+}
+
+/*
+ * get the current register value of WM codec
+ */
+static unsigned short wm_get(struct snd_ice1712 *ice, int reg)
+{
+	reg <<= 1;
+	return ((unsigned short)ice->akm[0].images[reg] << 8) |
+		ice->akm[0].images[reg + 1];
+}
+
+/*
+ * set the register value of WM codec
+ */
+static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+	phase28_spi_write(ice, PHASE28_WM_CS, (reg << 9) | (val & 0x1ff), 16);
+}
+
+/*
+ * set the register value of WM codec and remember it
+ */
+static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+	wm_put_nocache(ice, reg, val);
+	reg <<= 1;
+	ice->akm[0].images[reg] = val >> 8;
+	ice->akm[0].images[reg + 1] = val;
+}
+
+static void wm_set_vol(struct snd_ice1712 *ice, unsigned int index,
+			unsigned short vol, unsigned short master)
+{
+	unsigned char nvol;
+
+	if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE))
+		nvol = 0;
+	else
+		nvol = 127 - wm_vol[(((vol & ~WM_VOL_MUTE) *
+			(master & ~WM_VOL_MUTE)) / 127) & WM_VOL_MAX];
+
+	wm_put(ice, index, nvol);
+	wm_put_nocache(ice, index, 0x180 | nvol);
+}
+
+/*
+ * DAC mute control
+ */
+#define wm_pcm_mute_info	snd_ctl_boolean_mono_info
+
+static int wm_pcm_mute_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ice->gpio_mutex);
+	ucontrol->value.integer.value[0] = (wm_get(ice, WM_MUTE) & 0x10) ?
+						0 : 1;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_pcm_mute_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short nval, oval;
+	int change;
+
+	snd_ice1712_save_gpio_status(ice);
+	oval = wm_get(ice, WM_MUTE);
+	nval = (oval & ~0x10) | (ucontrol->value.integer.value[0] ? 0 : 0x10);
+	change = (nval != oval);
+	if (change)
+		wm_put(ice, WM_MUTE, nval);
+	snd_ice1712_restore_gpio_status(ice);
+
+	return change;
+}
+
+/*
+ * Master volume attenuation mixer control
+ */
+static int wm_master_vol_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = WM_VOL_MAX;
+	return 0;
+}
+
+static int wm_master_vol_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct phase28_spec *spec = ice->spec;
+	int i;
+	for (i = 0; i < 2; i++)
+		ucontrol->value.integer.value[i] = spec->master[i] &
+							~WM_VOL_MUTE;
+	return 0;
+}
+
+static int wm_master_vol_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct phase28_spec *spec = ice->spec;
+	int ch, change = 0;
+
+	snd_ice1712_save_gpio_status(ice);
+	for (ch = 0; ch < 2; ch++) {
+		unsigned int vol = ucontrol->value.integer.value[ch];
+		if (vol > WM_VOL_MAX)
+			continue;
+		vol |= spec->master[ch] & WM_VOL_MUTE;
+		if (vol != spec->master[ch]) {
+			int dac;
+			spec->master[ch] = vol;
+			for (dac = 0; dac < ice->num_total_dacs; dac += 2)
+				wm_set_vol(ice, WM_DAC_ATTEN + dac + ch,
+					   spec->vol[dac + ch],
+					   spec->master[ch]);
+			change = 1;
+		}
+	}
+	snd_ice1712_restore_gpio_status(ice);
+	return change;
+}
+
+static int __devinit phase28_init(struct snd_ice1712 *ice)
+{
+	static const unsigned short wm_inits_phase28[] = {
+		/* These come first to reduce init pop noise */
+		0x1b, 0x044,	/* ADC Mux (AC'97 source) */
+		0x1c, 0x00B,	/* Out Mux1 (VOUT1 = DAC+AUX, VOUT2 = DAC) */
+		0x1d, 0x009,	/* Out Mux2 (VOUT2 = DAC, VOUT3 = DAC) */
+
+		0x18, 0x000,	/* All power-up */
+
+		0x16, 0x122,	/* I2S, normal polarity, 24bit */
+		0x17, 0x022,	/* 256fs, slave mode */
+		0x00, 0,	/* DAC1 analog mute */
+		0x01, 0,	/* DAC2 analog mute */
+		0x02, 0,	/* DAC3 analog mute */
+		0x03, 0,	/* DAC4 analog mute */
+		0x04, 0,	/* DAC5 analog mute */
+		0x05, 0,	/* DAC6 analog mute */
+		0x06, 0,	/* DAC7 analog mute */
+		0x07, 0,	/* DAC8 analog mute */
+		0x08, 0x100,	/* master analog mute */
+		0x09, 0xff,	/* DAC1 digital full */
+		0x0a, 0xff,	/* DAC2 digital full */
+		0x0b, 0xff,	/* DAC3 digital full */
+		0x0c, 0xff,	/* DAC4 digital full */
+		0x0d, 0xff,	/* DAC5 digital full */
+		0x0e, 0xff,	/* DAC6 digital full */
+		0x0f, 0xff,	/* DAC7 digital full */
+		0x10, 0xff,	/* DAC8 digital full */
+		0x11, 0x1ff,	/* master digital full */
+		0x12, 0x000,	/* phase normal */
+		0x13, 0x090,	/* unmute DAC L/R */
+		0x14, 0x000,	/* all unmute */
+		0x15, 0x000,	/* no deemphasis, no ZFLG */
+		0x19, 0x000,	/* -12dB ADC/L */
+		0x1a, 0x000,	/* -12dB ADC/R */
+		(unsigned short)-1
+	};
+
+	unsigned int tmp;
+	struct snd_akm4xxx *ak;
+	struct phase28_spec *spec;
+	const unsigned short *p;
+	int i;
+
+	ice->num_total_dacs = 8;
+	ice->num_total_adcs = 2;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	ice->spec = spec;
+
+	/* Initialize analog chips */
+	ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+	ak = ice->akm;
+	if (!ak)
+		return -ENOMEM;
+	ice->akm_codecs = 1;
+
+	snd_ice1712_gpio_set_dir(ice, 0x5fffff); /* fix this for time being */
+
+	/* reset the wm codec as the SPI mode */
+	snd_ice1712_save_gpio_status(ice);
+	snd_ice1712_gpio_set_mask(ice, ~(PHASE28_WM_RESET|PHASE28_WM_CS|
+					PHASE28_HP_SEL));
+
+	tmp = snd_ice1712_gpio_read(ice);
+	tmp &= ~PHASE28_WM_RESET;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+	tmp |= PHASE28_WM_CS;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+	tmp |= PHASE28_WM_RESET;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+
+	p = wm_inits_phase28;
+	for (; *p != (unsigned short)-1; p += 2)
+		wm_put(ice, p[0], p[1]);
+
+	snd_ice1712_restore_gpio_status(ice);
+
+	spec->master[0] = WM_VOL_MUTE;
+	spec->master[1] = WM_VOL_MUTE;
+	for (i = 0; i < ice->num_total_dacs; i++) {
+		spec->vol[i] = WM_VOL_MUTE;
+		wm_set_vol(ice, i, spec->vol[i], spec->master[i % 2]);
+	}
+
+	return 0;
+}
+
+/*
+ * DAC volume attenuation mixer control
+ */
+static int wm_vol_info(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_info *uinfo)
+{
+	int voices = kcontrol->private_value >> 8;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = voices;
+	uinfo->value.integer.min = 0;		/* mute (-101dB) */
+	uinfo->value.integer.max = 0x7F;	/* 0dB */
+	return 0;
+}
+
+static int wm_vol_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct phase28_spec *spec = ice->spec;
+	int i, ofs, voices;
+
+	voices = kcontrol->private_value >> 8;
+	ofs = kcontrol->private_value & 0xff;
+	for (i = 0; i < voices; i++)
+		ucontrol->value.integer.value[i] =
+			spec->vol[ofs+i] & ~WM_VOL_MUTE;
+	return 0;
+}
+
+static int wm_vol_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct phase28_spec *spec = ice->spec;
+	int i, idx, ofs, voices;
+	int change = 0;
+
+	voices = kcontrol->private_value >> 8;
+	ofs = kcontrol->private_value & 0xff;
+	snd_ice1712_save_gpio_status(ice);
+	for (i = 0; i < voices; i++) {
+		unsigned int vol;
+		vol = ucontrol->value.integer.value[i];
+		if (vol > 0x7f)
+			continue;
+		vol |= spec->vol[ofs+i] & WM_VOL_MUTE;
+		if (vol != spec->vol[ofs+i]) {
+			spec->vol[ofs+i] = vol;
+			idx  = WM_DAC_ATTEN + ofs + i;
+			wm_set_vol(ice, idx, spec->vol[ofs+i],
+				   spec->master[i]);
+			change = 1;
+		}
+	}
+	snd_ice1712_restore_gpio_status(ice);
+	return change;
+}
+
+/*
+ * WM8770 mute control
+ */
+static int wm_mute_info(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_info *uinfo) {
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = kcontrol->private_value >> 8;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int wm_mute_get(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct phase28_spec *spec = ice->spec;
+	int voices, ofs, i;
+
+	voices = kcontrol->private_value >> 8;
+	ofs = kcontrol->private_value & 0xFF;
+
+	for (i = 0; i < voices; i++)
+		ucontrol->value.integer.value[i] =
+			(spec->vol[ofs+i] & WM_VOL_MUTE) ? 0 : 1;
+	return 0;
+}
+
+static int wm_mute_put(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct phase28_spec *spec = ice->spec;
+	int change = 0, voices, ofs, i;
+
+	voices = kcontrol->private_value >> 8;
+	ofs = kcontrol->private_value & 0xFF;
+
+	snd_ice1712_save_gpio_status(ice);
+	for (i = 0; i < voices; i++) {
+		int val = (spec->vol[ofs + i] & WM_VOL_MUTE) ? 0 : 1;
+		if (ucontrol->value.integer.value[i] != val) {
+			spec->vol[ofs + i] &= ~WM_VOL_MUTE;
+			spec->vol[ofs + i] |=
+				ucontrol->value.integer.value[i] ? 0 :
+				WM_VOL_MUTE;
+			wm_set_vol(ice, ofs + i, spec->vol[ofs + i],
+					spec->master[i]);
+			change = 1;
+		}
+	}
+	snd_ice1712_restore_gpio_status(ice);
+
+	return change;
+}
+
+/*
+ * WM8770 master mute control
+ */
+#define wm_master_mute_info		snd_ctl_boolean_stereo_info
+
+static int wm_master_mute_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct phase28_spec *spec = ice->spec;
+
+	ucontrol->value.integer.value[0] =
+		(spec->master[0] & WM_VOL_MUTE) ? 0 : 1;
+	ucontrol->value.integer.value[1] =
+		(spec->master[1] & WM_VOL_MUTE) ? 0 : 1;
+	return 0;
+}
+
+static int wm_master_mute_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct phase28_spec *spec = ice->spec;
+	int change = 0, i;
+
+	snd_ice1712_save_gpio_status(ice);
+	for (i = 0; i < 2; i++) {
+		int val = (spec->master[i] & WM_VOL_MUTE) ? 0 : 1;
+		if (ucontrol->value.integer.value[i] != val) {
+			int dac;
+			spec->master[i] &= ~WM_VOL_MUTE;
+			spec->master[i] |=
+				ucontrol->value.integer.value[i] ? 0 :
+				WM_VOL_MUTE;
+			for (dac = 0; dac < ice->num_total_dacs; dac += 2)
+				wm_set_vol(ice, WM_DAC_ATTEN + dac + i,
+						spec->vol[dac + i],
+						spec->master[i]);
+			change = 1;
+		}
+	}
+	snd_ice1712_restore_gpio_status(ice);
+
+	return change;
+}
+
+/* digital master volume */
+#define PCM_0dB 0xff
+#define PCM_RES 128	/* -64dB */
+#define PCM_MIN (PCM_0dB - PCM_RES)
+static int wm_pcm_vol_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;		/* mute (-64dB) */
+	uinfo->value.integer.max = PCM_RES;	/* 0dB */
+	return 0;
+}
+
+static int wm_pcm_vol_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+
+	mutex_lock(&ice->gpio_mutex);
+	val = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff;
+	val = val > PCM_MIN ? (val - PCM_MIN) : 0;
+	ucontrol->value.integer.value[0] = val;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_pcm_vol_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short ovol, nvol;
+	int change = 0;
+
+	nvol = ucontrol->value.integer.value[0];
+	if (nvol > PCM_RES)
+		return -EINVAL;
+	snd_ice1712_save_gpio_status(ice);
+	nvol = (nvol ? (nvol + PCM_MIN) : 0) & 0xff;
+	ovol = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff;
+	if (ovol != nvol) {
+		wm_put(ice, WM_DAC_DIG_MASTER_ATTEN, nvol); /* prelatch */
+		/* update */
+		wm_put_nocache(ice, WM_DAC_DIG_MASTER_ATTEN, nvol | 0x100);
+		change = 1;
+	}
+	snd_ice1712_restore_gpio_status(ice);
+	return change;
+}
+
+/*
+ * Deemphasis
+ */
+#define phase28_deemp_info	snd_ctl_boolean_mono_info
+
+static int phase28_deemp_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = (wm_get(ice, WM_DAC_CTRL2) & 0xf) ==
+						0xf;
+	return 0;
+}
+
+static int phase28_deemp_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int temp, temp2;
+	temp = wm_get(ice, WM_DAC_CTRL2);
+	temp2 = temp;
+	if (ucontrol->value.integer.value[0])
+		temp |= 0xf;
+	else
+		temp &= ~0xf;
+	if (temp != temp2) {
+		wm_put(ice, WM_DAC_CTRL2, temp);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * ADC Oversampling
+ */
+static int phase28_oversampling_info(struct snd_kcontrol *k,
+					struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = { "128x", "64x"	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items -
+						1;
+	strcpy(uinfo->value.enumerated.name,
+		texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int phase28_oversampling_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = (wm_get(ice, WM_MASTER) & 0x8) ==
+						0x8;
+	return 0;
+}
+
+static int phase28_oversampling_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	int temp, temp2;
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	temp = wm_get(ice, WM_MASTER);
+	temp2 = temp;
+
+	if (ucontrol->value.enumerated.item[0])
+		temp |= 0x8;
+	else
+		temp &= ~0x8;
+
+	if (temp != temp2) {
+		wm_put(ice, WM_MASTER, temp);
+		return 1;
+	}
+	return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_dac, -12700, 100, 1);
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_pcm, -6400, 50, 1);
+
+static struct snd_kcontrol_new phase28_dac_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = wm_master_mute_info,
+		.get = wm_master_mute_get,
+		.put = wm_master_mute_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Master Playback Volume",
+		.info = wm_master_vol_info,
+		.get = wm_master_vol_get,
+		.put = wm_master_vol_put,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Front Playback Switch",
+		.info = wm_mute_info,
+		.get = wm_mute_get,
+		.put = wm_mute_put,
+		.private_value = (2 << 8) | 0
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Front Playback Volume",
+		.info = wm_vol_info,
+		.get = wm_vol_get,
+		.put = wm_vol_put,
+		.private_value = (2 << 8) | 0,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Rear Playback Switch",
+		.info = wm_mute_info,
+		.get = wm_mute_get,
+		.put = wm_mute_put,
+		.private_value = (2 << 8) | 2
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Rear Playback Volume",
+		.info = wm_vol_info,
+		.get = wm_vol_get,
+		.put = wm_vol_put,
+		.private_value = (2 << 8) | 2,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Center Playback Switch",
+		.info = wm_mute_info,
+		.get = wm_mute_get,
+		.put = wm_mute_put,
+		.private_value = (1 << 8) | 4
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Center Playback Volume",
+		.info = wm_vol_info,
+		.get = wm_vol_get,
+		.put = wm_vol_put,
+		.private_value = (1 << 8) | 4,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "LFE Playback Switch",
+		.info = wm_mute_info,
+		.get = wm_mute_get,
+		.put = wm_mute_put,
+		.private_value = (1 << 8) | 5
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "LFE Playback Volume",
+		.info = wm_vol_info,
+		.get = wm_vol_get,
+		.put = wm_vol_put,
+		.private_value = (1 << 8) | 5,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Side Playback Switch",
+		.info = wm_mute_info,
+		.get = wm_mute_get,
+		.put = wm_mute_put,
+		.private_value = (2 << 8) | 6
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Side Playback Volume",
+		.info = wm_vol_info,
+		.get = wm_vol_get,
+		.put = wm_vol_put,
+		.private_value = (2 << 8) | 6,
+		.tlv = { .p = db_scale_wm_dac }
+	}
+};
+
+static struct snd_kcontrol_new wm_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "PCM Playback Switch",
+		.info = wm_pcm_mute_info,
+		.get = wm_pcm_mute_get,
+		.put = wm_pcm_mute_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "PCM Playback Volume",
+		.info = wm_pcm_vol_info,
+		.get = wm_pcm_vol_get,
+		.put = wm_pcm_vol_put,
+		.tlv = { .p = db_scale_wm_pcm }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "DAC Deemphasis Switch",
+		.info = phase28_deemp_info,
+		.get = phase28_deemp_get,
+		.put = phase28_deemp_put
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "ADC Oversampling",
+		.info = phase28_oversampling_info,
+		.get = phase28_oversampling_get,
+		.put = phase28_oversampling_put
+	}
+};
+
+static int __devinit phase28_add_controls(struct snd_ice1712 *ice)
+{
+	unsigned int i, counts;
+	int err;
+
+	counts = ARRAY_SIZE(phase28_dac_controls);
+	for (i = 0; i < counts; i++) {
+		err = snd_ctl_add(ice->card,
+					snd_ctl_new1(&phase28_dac_controls[i],
+							ice));
+		if (err < 0)
+			return err;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm_controls); i++) {
+		err = snd_ctl_add(ice->card,
+					snd_ctl_new1(&wm_controls[i], ice));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+struct snd_ice1712_card_info snd_vt1724_phase_cards[] __devinitdata = {
+	{
+		.subvendor = VT1724_SUBDEVICE_PHASE22,
+		.name = "Terratec PHASE 22",
+		.model = "phase22",
+		.chip_init = phase22_init,
+		.build_controls = phase22_add_controls,
+		.eeprom_size = sizeof(phase22_eeprom),
+		.eeprom_data = phase22_eeprom,
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_PHASE28,
+		.name = "Terratec PHASE 28",
+		.model = "phase28",
+		.chip_init = phase28_init,
+		.build_controls = phase28_add_controls,
+		.eeprom_size = sizeof(phase28_eeprom),
+		.eeprom_data = phase28_eeprom,
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_TS22,
+		.name = "Terrasoniq TS22 PCI",
+		.model = "TS22",
+		.chip_init = phase22_init,
+		.build_controls = phase22_add_controls,
+		.eeprom_size = sizeof(phase22_eeprom),
+		.eeprom_data = phase22_eeprom,
+	},
+	{ } /* terminator */
+};
diff --git a/linux/sound/pci/ice1712/phase.h b/linux/sound/pci/ice1712/phase.h
new file mode 100644
index 0000000..7fc22d9
--- /dev/null
+++ b/linux/sound/pci/ice1712/phase.h
@@ -0,0 +1,53 @@
+#ifndef __SOUND_PHASE_H
+#define __SOUND_PHASE_H
+
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *   Lowlevel functions for Terratec PHASE 22
+ *
+ *	Copyright (c) 2005 Misha Zhilin <misha@epiphan.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#define PHASE_DEVICE_DESC	"{Terratec,Phase 22},"\
+				"{Terratec,Phase 28},"\
+				"{Terrasoniq,TS22},"
+
+#define VT1724_SUBDEVICE_PHASE22	0x3b155011
+#define VT1724_SUBDEVICE_PHASE28	0x3b154911
+#define VT1724_SUBDEVICE_TS22		0x3b157b11
+
+/* entry point */
+extern struct snd_ice1712_card_info snd_vt1724_phase_cards[];
+
+/* PHASE28 GPIO bits */
+#define PHASE28_SPI_MISO	(1 << 21)
+#define PHASE28_WM_RESET	(1 << 20)
+#define PHASE28_SPI_CLK		(1 << 19)
+#define PHASE28_SPI_MOSI	(1 << 18)
+#define PHASE28_WM_RW		(1 << 17)
+#define PHASE28_AC97_RESET	(1 << 16)
+#define PHASE28_DIGITAL_SEL1	(1 << 15)
+#define PHASE28_HP_SEL		(1 << 14)
+#define PHASE28_WM_CS		(1 << 12)
+#define PHASE28_AC97_COMMIT	(1 << 11)
+#define PHASE28_AC97_ADDR	(1 << 10)
+#define PHASE28_AC97_DATA_LOW	(1 << 9)
+#define PHASE28_AC97_DATA_HIGH	(1 << 8)
+#define PHASE28_AC97_DATA_MASK	0xFF
+#endif /* __SOUND_PHASE */
diff --git a/linux/sound/pci/ice1712/pontis.c b/linux/sound/pci/ice1712/pontis.c
new file mode 100644
index 0000000..cdb873f
--- /dev/null
+++ b/linux/sound/pci/ice1712/pontis.c
@@ -0,0 +1,836 @@
+/*
+ *   ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for Pontis MS300
+ *
+ *	Copyright (c) 2004 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 <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "pontis.h"
+
+/* I2C addresses */
+#define WM_DEV		0x34
+#define CS_DEV		0x20
+
+/* WM8776 registers */
+#define WM_HP_ATTEN_L		0x00	/* headphone left attenuation */
+#define WM_HP_ATTEN_R		0x01	/* headphone left attenuation */
+#define WM_HP_MASTER		0x02	/* headphone master (both channels) */
+					/* override LLR */
+#define WM_DAC_ATTEN_L		0x03	/* digital left attenuation */
+#define WM_DAC_ATTEN_R		0x04
+#define WM_DAC_MASTER		0x05
+#define WM_PHASE_SWAP		0x06	/* DAC phase swap */
+#define WM_DAC_CTRL1		0x07
+#define WM_DAC_MUTE		0x08
+#define WM_DAC_CTRL2		0x09
+#define WM_DAC_INT		0x0a
+#define WM_ADC_INT		0x0b
+#define WM_MASTER_CTRL		0x0c
+#define WM_POWERDOWN		0x0d
+#define WM_ADC_ATTEN_L		0x0e
+#define WM_ADC_ATTEN_R		0x0f
+#define WM_ALC_CTRL1		0x10
+#define WM_ALC_CTRL2		0x11
+#define WM_ALC_CTRL3		0x12
+#define WM_NOISE_GATE		0x13
+#define WM_LIMITER		0x14
+#define WM_ADC_MUX		0x15
+#define WM_OUT_MUX		0x16
+#define WM_RESET		0x17
+
+/*
+ * GPIO
+ */
+#define PONTIS_CS_CS		(1<<4)	/* CS */
+#define PONTIS_CS_CLK		(1<<5)	/* CLK */
+#define PONTIS_CS_RDATA		(1<<6)	/* CS8416 -> VT1720 */
+#define PONTIS_CS_WDATA		(1<<7)	/* VT1720 -> CS8416 */
+
+
+/*
+ * get the current register value of WM codec
+ */
+static unsigned short wm_get(struct snd_ice1712 *ice, int reg)
+{
+	reg <<= 1;
+	return ((unsigned short)ice->akm[0].images[reg] << 8) |
+		ice->akm[0].images[reg + 1];
+}
+
+/*
+ * set the register value of WM codec and remember it
+ */
+static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+	unsigned short cval;
+	cval = (reg << 9) | val;
+	snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff);
+}
+
+static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+	wm_put_nocache(ice, reg, val);
+	reg <<= 1;
+	ice->akm[0].images[reg] = val >> 8;
+	ice->akm[0].images[reg + 1] = val;
+}
+
+/*
+ * DAC volume attenuation mixer control (-64dB to 0dB)
+ */
+
+#define DAC_0dB	0xff
+#define DAC_RES	128
+#define DAC_MIN	(DAC_0dB - DAC_RES)
+
+static int wm_dac_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;	/* mute */
+	uinfo->value.integer.max = DAC_RES;	/* 0dB, 0.5dB step */
+	return 0;
+}
+
+static int wm_dac_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+	int i;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (i = 0; i < 2; i++) {
+		val = wm_get(ice, WM_DAC_ATTEN_L + i) & 0xff;
+		val = val > DAC_MIN ? (val - DAC_MIN) : 0;
+		ucontrol->value.integer.value[i] = val;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short oval, nval;
+	int i, idx, change = 0;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (i = 0; i < 2; i++) {
+		nval = ucontrol->value.integer.value[i];
+		nval = (nval ? (nval + DAC_MIN) : 0) & 0xff;
+		idx = WM_DAC_ATTEN_L + i;
+		oval = wm_get(ice, idx) & 0xff;
+		if (oval != nval) {
+			wm_put(ice, idx, nval);
+			wm_put_nocache(ice, idx, nval | 0x100);
+			change = 1;
+		}
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+/*
+ * ADC gain mixer control (-64dB to 0dB)
+ */
+
+#define ADC_0dB	0xcf
+#define ADC_RES	128
+#define ADC_MIN	(ADC_0dB - ADC_RES)
+
+static int wm_adc_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;	/* mute (-64dB) */
+	uinfo->value.integer.max = ADC_RES;	/* 0dB, 0.5dB step */
+	return 0;
+}
+
+static int wm_adc_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+	int i;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (i = 0; i < 2; i++) {
+		val = wm_get(ice, WM_ADC_ATTEN_L + i) & 0xff;
+		val = val > ADC_MIN ? (val - ADC_MIN) : 0;
+		ucontrol->value.integer.value[i] = val;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_adc_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short ovol, nvol;
+	int i, idx, change = 0;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (i = 0; i < 2; i++) {
+		nvol = ucontrol->value.integer.value[i];
+		nvol = nvol ? (nvol + ADC_MIN) : 0;
+		idx  = WM_ADC_ATTEN_L + i;
+		ovol = wm_get(ice, idx) & 0xff;
+		if (ovol != nvol) {
+			wm_put(ice, idx, nvol);
+			change = 1;
+		}
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+/*
+ * ADC input mux mixer control
+ */
+#define wm_adc_mux_info		snd_ctl_boolean_mono_info
+
+static int wm_adc_mux_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int bit = kcontrol->private_value;
+
+	mutex_lock(&ice->gpio_mutex);
+	ucontrol->value.integer.value[0] = (wm_get(ice, WM_ADC_MUX) & (1 << bit)) ? 1 : 0;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_adc_mux_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int bit = kcontrol->private_value;
+	unsigned short oval, nval;
+	int change;
+
+	mutex_lock(&ice->gpio_mutex);
+	nval = oval = wm_get(ice, WM_ADC_MUX);
+	if (ucontrol->value.integer.value[0])
+		nval |= (1 << bit);
+	else
+		nval &= ~(1 << bit);
+	change = nval != oval;
+	if (change) {
+		wm_put(ice, WM_ADC_MUX, nval);
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+/*
+ * Analog bypass (In -> Out)
+ */
+#define wm_bypass_info		snd_ctl_boolean_mono_info
+
+static int wm_bypass_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ice->gpio_mutex);
+	ucontrol->value.integer.value[0] = (wm_get(ice, WM_OUT_MUX) & 0x04) ? 1 : 0;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_bypass_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short val, oval;
+	int change = 0;
+
+	mutex_lock(&ice->gpio_mutex);
+	val = oval = wm_get(ice, WM_OUT_MUX);
+	if (ucontrol->value.integer.value[0])
+		val |= 0x04;
+	else
+		val &= ~0x04;
+	if (val != oval) {
+		wm_put(ice, WM_OUT_MUX, val);
+		change = 1;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+/*
+ * Left/Right swap
+ */
+#define wm_chswap_info		snd_ctl_boolean_mono_info
+
+static int wm_chswap_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ice->gpio_mutex);
+	ucontrol->value.integer.value[0] = (wm_get(ice, WM_DAC_CTRL1) & 0xf0) != 0x90;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_chswap_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short val, oval;
+	int change = 0;
+
+	mutex_lock(&ice->gpio_mutex);
+	oval = wm_get(ice, WM_DAC_CTRL1);
+	val = oval & 0x0f;
+	if (ucontrol->value.integer.value[0])
+		val |= 0x60;
+	else
+		val |= 0x90;
+	if (val != oval) {
+		wm_put(ice, WM_DAC_CTRL1, val);
+		wm_put_nocache(ice, WM_DAC_CTRL1, val);
+		change = 1;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+/*
+ * write data in the SPI mode
+ */
+static void set_gpio_bit(struct snd_ice1712 *ice, unsigned int bit, int val)
+{
+	unsigned int tmp = snd_ice1712_gpio_read(ice);
+	if (val)
+		tmp |= bit;
+	else
+		tmp &= ~bit;
+	snd_ice1712_gpio_write(ice, tmp);
+}
+
+static void spi_send_byte(struct snd_ice1712 *ice, unsigned char data)
+{
+	int i;
+	for (i = 0; i < 8; i++) {
+		set_gpio_bit(ice, PONTIS_CS_CLK, 0);
+		udelay(1);
+		set_gpio_bit(ice, PONTIS_CS_WDATA, data & 0x80);
+		udelay(1);
+		set_gpio_bit(ice, PONTIS_CS_CLK, 1);
+		udelay(1);
+		data <<= 1;
+	}
+}
+
+static unsigned int spi_read_byte(struct snd_ice1712 *ice)
+{
+	int i;
+	unsigned int val = 0;
+
+	for (i = 0; i < 8; i++) {
+		val <<= 1;
+		set_gpio_bit(ice, PONTIS_CS_CLK, 0);
+		udelay(1);
+		if (snd_ice1712_gpio_read(ice) & PONTIS_CS_RDATA)
+			val |= 1;
+		udelay(1);
+		set_gpio_bit(ice, PONTIS_CS_CLK, 1);
+		udelay(1);
+	}
+	return val;
+}
+
+
+static void spi_write(struct snd_ice1712 *ice, unsigned int dev, unsigned int reg, unsigned int data)
+{
+	snd_ice1712_gpio_set_dir(ice, PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK);
+	snd_ice1712_gpio_set_mask(ice, ~(PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK));
+	set_gpio_bit(ice, PONTIS_CS_CS, 0);
+	spi_send_byte(ice, dev & ~1); /* WRITE */
+	spi_send_byte(ice, reg); /* MAP */
+	spi_send_byte(ice, data); /* DATA */
+	/* trigger */
+	set_gpio_bit(ice, PONTIS_CS_CS, 1);
+	udelay(1);
+	/* restore */
+	snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+	snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+}
+
+static unsigned int spi_read(struct snd_ice1712 *ice, unsigned int dev, unsigned int reg)
+{
+	unsigned int val;
+	snd_ice1712_gpio_set_dir(ice, PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK);
+	snd_ice1712_gpio_set_mask(ice, ~(PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK));
+	set_gpio_bit(ice, PONTIS_CS_CS, 0);
+	spi_send_byte(ice, dev & ~1); /* WRITE */
+	spi_send_byte(ice, reg); /* MAP */
+	/* trigger */
+	set_gpio_bit(ice, PONTIS_CS_CS, 1);
+	udelay(1);
+	set_gpio_bit(ice, PONTIS_CS_CS, 0);
+	spi_send_byte(ice, dev | 1); /* READ */
+	val = spi_read_byte(ice);
+	/* trigger */
+	set_gpio_bit(ice, PONTIS_CS_CS, 1);
+	udelay(1);
+	/* restore */
+	snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+	snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+	return val;
+}
+
+
+/*
+ * SPDIF input source
+ */
+static int cs_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[] = {
+		"Coax",		/* RXP0 */
+		"Optical",	/* RXP1 */
+		"CD",		/* RXP2 */
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int cs_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ice->gpio_mutex);
+	ucontrol->value.enumerated.item[0] = ice->gpio.saved[0];
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int cs_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+	int change = 0;
+
+	mutex_lock(&ice->gpio_mutex);
+	if (ucontrol->value.enumerated.item[0] != ice->gpio.saved[0]) {
+		ice->gpio.saved[0] = ucontrol->value.enumerated.item[0] & 3;
+		val = 0x80 | (ice->gpio.saved[0] << 3);
+		spi_write(ice, CS_DEV, 0x04, val);
+		change = 1;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+
+/*
+ * GPIO controls
+ */
+static int pontis_gpio_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0xffff; /* 16bit */
+	return 0;
+}
+
+static int pontis_gpio_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&ice->gpio_mutex);
+	/* 4-7 reserved */
+	ucontrol->value.integer.value[0] = (~ice->gpio.write_mask & 0xffff) | 0x00f0;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+	
+static int pontis_gpio_mask_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int changed;
+	mutex_lock(&ice->gpio_mutex);
+	/* 4-7 reserved */
+	val = (~ucontrol->value.integer.value[0] & 0xffff) | 0x00f0;
+	changed = val != ice->gpio.write_mask;
+	ice->gpio.write_mask = val;
+	mutex_unlock(&ice->gpio_mutex);
+	return changed;
+}
+
+static int pontis_gpio_dir_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&ice->gpio_mutex);
+	/* 4-7 reserved */
+	ucontrol->value.integer.value[0] = ice->gpio.direction & 0xff0f;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+	
+static int pontis_gpio_dir_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int changed;
+	mutex_lock(&ice->gpio_mutex);
+	/* 4-7 reserved */
+	val = ucontrol->value.integer.value[0] & 0xff0f;
+	changed = (val != ice->gpio.direction);
+	ice->gpio.direction = val;
+	mutex_unlock(&ice->gpio_mutex);
+	return changed;
+}
+
+static int pontis_gpio_data_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&ice->gpio_mutex);
+	snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+	snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+	ucontrol->value.integer.value[0] = snd_ice1712_gpio_read(ice) & 0xffff;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int pontis_gpio_data_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int val, nval;
+	int changed = 0;
+	mutex_lock(&ice->gpio_mutex);
+	snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+	snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+	val = snd_ice1712_gpio_read(ice) & 0xffff;
+	nval = ucontrol->value.integer.value[0] & 0xffff;
+	if (val != nval) {
+		snd_ice1712_gpio_write(ice, nval);
+		changed = 1;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return changed;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_volume, -6400, 50, 1);
+
+/*
+ * mixers
+ */
+
+static struct snd_kcontrol_new pontis_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "PCM Playback Volume",
+		.info = wm_dac_vol_info,
+		.get = wm_dac_vol_get,
+		.put = wm_dac_vol_put,
+		.tlv = { .p = db_scale_volume },
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Capture Volume",
+		.info = wm_adc_vol_info,
+		.get = wm_adc_vol_get,
+		.put = wm_adc_vol_put,
+		.tlv = { .p = db_scale_volume },
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "CD Capture Switch",
+		.info = wm_adc_mux_info,
+		.get = wm_adc_mux_get,
+		.put = wm_adc_mux_put,
+		.private_value = 0,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Line Capture Switch",
+		.info = wm_adc_mux_info,
+		.get = wm_adc_mux_get,
+		.put = wm_adc_mux_put,
+		.private_value = 1,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Bypass Switch",
+		.info = wm_bypass_info,
+		.get = wm_bypass_get,
+		.put = wm_bypass_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Swap Output Channels",
+		.info = wm_chswap_info,
+		.get = wm_chswap_get,
+		.put = wm_chswap_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "IEC958 Input Source",
+		.info = cs_source_info,
+		.get = cs_source_get,
+		.put = cs_source_put,
+	},
+	/* FIXME: which interface? */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+		.name = "GPIO Mask",
+		.info = pontis_gpio_mask_info,
+		.get = pontis_gpio_mask_get,
+		.put = pontis_gpio_mask_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+		.name = "GPIO Direction",
+		.info = pontis_gpio_mask_info,
+		.get = pontis_gpio_dir_get,
+		.put = pontis_gpio_dir_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+		.name = "GPIO Data",
+		.info = pontis_gpio_mask_info,
+		.get = pontis_gpio_data_get,
+		.put = pontis_gpio_data_put,
+	},
+};
+
+
+/*
+ * WM codec registers
+ */
+static void wm_proc_regs_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_ice1712 *ice = entry->private_data;
+	char line[64];
+	unsigned int reg, val;
+	mutex_lock(&ice->gpio_mutex);
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		if (sscanf(line, "%x %x", &reg, &val) != 2)
+			continue;
+		if (reg <= 0x17 && val <= 0xffff)
+			wm_put(ice, reg, val);
+	}
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static void wm_proc_regs_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_ice1712 *ice = entry->private_data;
+	int reg, val;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (reg = 0; reg <= 0x17; reg++) {
+		val = wm_get(ice, reg);
+		snd_iprintf(buffer, "%02x = %04x\n", reg, val);
+	}
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static void wm_proc_init(struct snd_ice1712 *ice)
+{
+	struct snd_info_entry *entry;
+	if (! snd_card_proc_new(ice->card, "wm_codec", &entry)) {
+		snd_info_set_text_ops(entry, ice, wm_proc_regs_read);
+		entry->mode |= S_IWUSR;
+		entry->c.text.write = wm_proc_regs_write;
+	}
+}
+
+static void cs_proc_regs_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_ice1712 *ice = entry->private_data;
+	int reg, val;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (reg = 0; reg <= 0x26; reg++) {
+		val = spi_read(ice, CS_DEV, reg);
+		snd_iprintf(buffer, "%02x = %02x\n", reg, val);
+	}
+	val = spi_read(ice, CS_DEV, 0x7f);
+	snd_iprintf(buffer, "%02x = %02x\n", 0x7f, val);
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static void cs_proc_init(struct snd_ice1712 *ice)
+{
+	struct snd_info_entry *entry;
+	if (! snd_card_proc_new(ice->card, "cs_codec", &entry))
+		snd_info_set_text_ops(entry, ice, cs_proc_regs_read);
+}
+
+
+static int __devinit pontis_add_controls(struct snd_ice1712 *ice)
+{
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(pontis_controls); i++) {
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&pontis_controls[i], ice));
+		if (err < 0)
+			return err;
+	}
+
+	wm_proc_init(ice);
+	cs_proc_init(ice);
+
+	return 0;
+}
+
+
+/*
+ * initialize the chip
+ */
+static int __devinit pontis_init(struct snd_ice1712 *ice)
+{
+	static const unsigned short wm_inits[] = {
+		/* These come first to reduce init pop noise */
+		WM_ADC_MUX,	0x00c0,	/* ADC mute */
+		WM_DAC_MUTE,	0x0001,	/* DAC softmute */
+		WM_DAC_CTRL1,	0x0000,	/* DAC mute */
+
+		WM_POWERDOWN,	0x0008,	/* All power-up except HP */
+		WM_RESET,	0x0000,	/* reset */
+	};
+	static const unsigned short wm_inits2[] = {
+		WM_MASTER_CTRL,	0x0022,	/* 256fs, slave mode */
+		WM_DAC_INT,	0x0022,	/* I2S, normal polarity, 24bit */
+		WM_ADC_INT,	0x0022,	/* I2S, normal polarity, 24bit */
+		WM_DAC_CTRL1,	0x0090,	/* DAC L/R */
+		WM_OUT_MUX,	0x0001,	/* OUT DAC */
+		WM_HP_ATTEN_L,	0x0179,	/* HP 0dB */
+		WM_HP_ATTEN_R,	0x0179,	/* HP 0dB */
+		WM_DAC_ATTEN_L,	0x0000,	/* DAC 0dB */
+		WM_DAC_ATTEN_L,	0x0100,	/* DAC 0dB */
+		WM_DAC_ATTEN_R,	0x0000,	/* DAC 0dB */
+		WM_DAC_ATTEN_R,	0x0100,	/* DAC 0dB */
+		/* WM_DAC_MASTER,	0x0100, */	/* DAC master muted */
+		WM_PHASE_SWAP,	0x0000,	/* phase normal */
+		WM_DAC_CTRL2,	0x0000,	/* no deemphasis, no ZFLG */
+		WM_ADC_ATTEN_L,	0x0000,	/* ADC muted */
+		WM_ADC_ATTEN_R,	0x0000,	/* ADC muted */
+#if 0
+		WM_ALC_CTRL1,	0x007b,	/* */
+		WM_ALC_CTRL2,	0x0000,	/* */
+		WM_ALC_CTRL3,	0x0000,	/* */
+		WM_NOISE_GATE,	0x0000,	/* */
+#endif
+		WM_DAC_MUTE,	0x0000,	/* DAC unmute */
+		WM_ADC_MUX,	0x0003,	/* ADC unmute, both CD/Line On */
+	};
+	static const unsigned char cs_inits[] = {
+		0x04,	0x80,	/* RUN, RXP0 */
+		0x05,	0x05,	/* slave, 24bit */
+		0x01,	0x00,
+		0x02,	0x00,
+		0x03,	0x00,
+	};
+	unsigned int i;
+
+	ice->vt1720 = 1;
+	ice->num_total_dacs = 2;
+	ice->num_total_adcs = 2;
+
+	/* to remeber the register values */
+	ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+	if (! ice->akm)
+		return -ENOMEM;
+	ice->akm_codecs = 1;
+
+	/* HACK - use this as the SPDIF source.
+	 * don't call snd_ice1712_gpio_get/put(), otherwise it's overwritten
+	 */
+	ice->gpio.saved[0] = 0;
+
+	/* initialize WM8776 codec */
+	for (i = 0; i < ARRAY_SIZE(wm_inits); i += 2)
+		wm_put(ice, wm_inits[i], wm_inits[i+1]);
+	schedule_timeout_uninterruptible(1);
+	for (i = 0; i < ARRAY_SIZE(wm_inits2); i += 2)
+		wm_put(ice, wm_inits2[i], wm_inits2[i+1]);
+
+	/* initialize CS8416 codec */
+	/* assert PRST#; MT05 bit 7 */
+	outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD));
+	mdelay(5);
+	/* deassert PRST# */
+	outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD));
+
+	for (i = 0; i < ARRAY_SIZE(cs_inits); i += 2)
+		spi_write(ice, CS_DEV, cs_inits[i], cs_inits[i+1]);
+
+	return 0;
+}
+
+
+/*
+ * Pontis boards don't provide the EEPROM data at all.
+ * hence the driver needs to sets up it properly.
+ */
+
+static unsigned char pontis_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x08,	/* clock 256, mpu401, spdif-in/ADC, 1DAC */
+	[ICE_EEP2_ACLINK]      = 0x80,	/* I2S */
+	[ICE_EEP2_I2S]         = 0xf8,	/* vol, 96k, 24bit, 192k */
+	[ICE_EEP2_SPDIF]       = 0xc3,	/* out-en, out-int, spdif-in */
+	[ICE_EEP2_GPIO_DIR]    = 0x07,
+	[ICE_EEP2_GPIO_DIR1]   = 0x00,
+	[ICE_EEP2_GPIO_DIR2]   = 0x00,	/* ignored */
+	[ICE_EEP2_GPIO_MASK]   = 0x0f,	/* 4-7 reserved for CS8416 */
+	[ICE_EEP2_GPIO_MASK1]  = 0xff,
+	[ICE_EEP2_GPIO_MASK2]  = 0x00,	/* ignored */
+	[ICE_EEP2_GPIO_STATE]  = 0x06,	/* 0-low, 1-high, 2-high */
+	[ICE_EEP2_GPIO_STATE1] = 0x00,
+	[ICE_EEP2_GPIO_STATE2] = 0x00,	/* ignored */
+};
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1720_pontis_cards[] __devinitdata = {
+	{
+		.subvendor = VT1720_SUBDEVICE_PONTIS_MS300,
+		.name = "Pontis MS300",
+		.model = "ms300",
+		.chip_init = pontis_init,
+		.build_controls = pontis_add_controls,
+		.eeprom_size = sizeof(pontis_eeprom),
+		.eeprom_data = pontis_eeprom,
+	},
+	{ } /* terminator */
+};
diff --git a/linux/sound/pci/ice1712/pontis.h b/linux/sound/pci/ice1712/pontis.h
new file mode 100644
index 0000000..d0d1378
--- /dev/null
+++ b/linux/sound/pci/ice1712/pontis.h
@@ -0,0 +1,33 @@
+#ifndef __SOUND_PONTIS_H
+#define __SOUND_PONTIS_H
+
+/*
+ *   ALSA driver for VIA VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for Pontis MS300 boards
+ *
+ *	Copyright (c) 2004 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
+ *
+ */      
+
+#define PONTIS_DEVICE_DESC 	       "{Pontis,MS300},"
+
+#define VT1720_SUBDEVICE_PONTIS_MS300	0x00020002	/* a dummy id for MS300 */
+
+extern struct snd_ice1712_card_info  snd_vt1720_pontis_cards[];
+
+#endif /* __SOUND_PONTIS_H */
diff --git a/linux/sound/pci/ice1712/prodigy192.c b/linux/sound/pci/ice1712/prodigy192.c
new file mode 100644
index 0000000..e36ddb9
--- /dev/null
+++ b/linux/sound/pci/ice1712/prodigy192.c
@@ -0,0 +1,822 @@
+/*
+ *   ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for AudioTrak Prodigy 192 cards
+ *   Supported IEC958 input from optional MI/ODI/O add-on card.
+ *
+ *   Specifics (SW, HW):
+ *   -------------------
+ *   	* 49.5MHz crystal
+ *   	* SPDIF-OUT on the card:
+ *  	  - coax (through isolation transformer)/toslink supplied by
+ *          74HC04 gates - 3 in parallel
+ *   	  - output switched between on-board CD drive dig-out connector
+ *          and ice1724 SPDTX pin, using 74HC02 NOR gates, controlled
+ *          by GPIO20 (0 = CD dig-out, 1 = SPDTX)
+ *   	* SPDTX goes straight to MI/ODI/O card's SPDIF-OUT coax
+ *
+ *   	* MI/ODI/O card: AK4114 based, used for iec958 input only
+ *   		- toslink input -> RX0
+ *   		- coax input -> RX1
+ *   		- 4wire protocol:
+ *   			AK4114		ICE1724
+ *   			------------------------------
+ * 			CDTO (pin 32) -- GPIO11 pin 86
+ * 			CDTI (pin 33) -- GPIO10 pin 77
+ * 			CCLK (pin 34) -- GPIO9 pin 76
+ * 			CSN  (pin 35) -- GPIO8 pin 75
+ *   		- output data Mode 7 (24bit, I2S, slave)
+ *		- both MCKO1 and MCKO2 of ak4114 are fed to FPGA, which
+ *		  outputs master clock to SPMCLKIN of ice1724.
+ *		  Experimentally I found out that only a combination of
+ *		  OCKS0=1, OCKS1=1 (128fs, 64fs output) and ice1724 -
+ *		  VT1724_MT_I2S_MCLK_128X=0 (256fs input) yields correct
+ *		  sampling rate. That means the the FPGA doubles the
+ *		  MCK01 rate.
+ *
+ *	Copyright (c) 2003 Takashi Iwai <tiwai@suse.de>
+ *      Copyright (c) 2003 Dimitromanolakis Apostolos <apostol@cs.utoronto.ca>
+ *      Copyright (c) 2004 Kouichi ONO <co2b@ceres.dti.ne.jp>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "prodigy192.h"
+#include "stac946x.h"
+#include <sound/tlv.h>
+
+struct prodigy192_spec {
+	struct ak4114 *ak4114;
+	/* rate change needs atomic mute/unmute of all dacs*/
+	struct mutex mute_mutex;
+};
+
+static inline void stac9460_put(struct snd_ice1712 *ice, int reg, unsigned char val)
+{
+	snd_vt1724_write_i2c(ice, PRODIGY192_STAC9460_ADDR, reg, val);
+}
+
+static inline unsigned char stac9460_get(struct snd_ice1712 *ice, int reg)
+{
+	return snd_vt1724_read_i2c(ice, PRODIGY192_STAC9460_ADDR, reg);
+}
+
+/*
+ * DAC mute control
+ */
+
+/*
+ * idx = STAC9460 volume register number, mute: 0 = mute, 1 = unmute
+ */
+static int stac9460_dac_mute(struct snd_ice1712 *ice, int idx,
+		unsigned char mute)
+{
+	unsigned char new, old;
+	int change;
+	old = stac9460_get(ice, idx);
+	new = (~mute << 7 & 0x80) | (old & ~0x80);
+	change = (new != old);
+	if (change)
+		/*printk ("Volume register 0x%02x: 0x%02x\n", idx, new);*/
+		stac9460_put(ice, idx, new);
+	return change;
+}
+
+#define stac9460_dac_mute_info		snd_ctl_boolean_mono_info
+
+static int stac9460_dac_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+	int idx;
+
+	if (kcontrol->private_value)
+		idx = STAC946X_MASTER_VOLUME;
+	else
+		idx  = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME;
+	val = stac9460_get(ice, idx);
+	ucontrol->value.integer.value[0] = (~val >> 7) & 0x1;
+	return 0;
+}
+
+static int stac9460_dac_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct prodigy192_spec *spec = ice->spec;
+	int idx, change;
+
+	if (kcontrol->private_value)
+		idx = STAC946X_MASTER_VOLUME;
+	else
+		idx  = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME;
+	/* due to possible conflicts with stac9460_set_rate_val, mutexing */
+	mutex_lock(&spec->mute_mutex);
+	/*
+	printk(KERN_DEBUG "Mute put: reg 0x%02x, ctrl value: 0x%02x\n", idx,
+	       ucontrol->value.integer.value[0]);
+	*/
+	change = stac9460_dac_mute(ice, idx, ucontrol->value.integer.value[0]);
+	mutex_unlock(&spec->mute_mutex);
+	return change;
+}
+
+/*
+ * DAC volume attenuation mixer control
+ */
+static int stac9460_dac_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;			/* mute */
+	uinfo->value.integer.max = 0x7f;		/* 0dB */
+	return 0;
+}
+
+static int stac9460_dac_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx;
+	unsigned char vol;
+
+	if (kcontrol->private_value)
+		idx = STAC946X_MASTER_VOLUME;
+	else
+		idx  = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME;
+	vol = stac9460_get(ice, idx) & 0x7f;
+	ucontrol->value.integer.value[0] = 0x7f - vol;
+
+	return 0;
+}
+
+static int stac9460_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx;
+	unsigned char tmp, ovol, nvol;
+	int change;
+
+	if (kcontrol->private_value)
+		idx = STAC946X_MASTER_VOLUME;
+	else
+		idx  = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME;
+	nvol = ucontrol->value.integer.value[0];
+	tmp = stac9460_get(ice, idx);
+	ovol = 0x7f - (tmp & 0x7f);
+	change = (ovol != nvol);
+	if (change) {
+		ovol =  (0x7f - nvol) | (tmp & 0x80);
+		/*
+		printk(KERN_DEBUG "DAC Volume: reg 0x%02x: 0x%02x\n",
+		       idx, ovol);
+		*/
+		stac9460_put(ice, idx, (0x7f - nvol) | (tmp & 0x80));
+	}
+	return change;
+}
+
+/*
+ * ADC mute control
+ */
+#define stac9460_adc_mute_info		snd_ctl_boolean_stereo_info
+
+static int stac9460_adc_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+	int i;
+
+	for (i = 0; i < 2; ++i) {
+		val = stac9460_get(ice, STAC946X_MIC_L_VOLUME + i);
+		ucontrol->value.integer.value[i] = ~val>>7 & 0x1;
+	}
+
+	return 0;
+}
+
+static int stac9460_adc_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char new, old;
+	int i, reg;
+	int change;
+
+	for (i = 0; i < 2; ++i) {
+		reg = STAC946X_MIC_L_VOLUME + i;
+		old = stac9460_get(ice, reg);
+		new = (~ucontrol->value.integer.value[i]<<7&0x80) | (old&~0x80);
+		change = (new != old);
+		if (change)
+			stac9460_put(ice, reg, new);
+	}
+
+	return change;
+}
+
+/*
+ * ADC gain mixer control
+ */
+static int stac9460_adc_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;		/* 0dB */
+	uinfo->value.integer.max = 0x0f;	/* 22.5dB */
+	return 0;
+}
+
+static int stac9460_adc_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int i, reg;
+	unsigned char vol;
+
+	for (i = 0; i < 2; ++i) {
+		reg = STAC946X_MIC_L_VOLUME + i;
+		vol = stac9460_get(ice, reg) & 0x0f;
+		ucontrol->value.integer.value[i] = 0x0f - vol;
+	}
+
+	return 0;
+}
+
+static int stac9460_adc_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int i, reg;
+	unsigned char ovol, nvol;
+	int change;
+
+	for (i = 0; i < 2; ++i) {
+		reg = STAC946X_MIC_L_VOLUME + i;
+		nvol = ucontrol->value.integer.value[i] & 0x0f;
+		ovol = 0x0f - stac9460_get(ice, reg);
+		change = ((ovol & 0x0f)  != nvol);
+		if (change)
+			stac9460_put(ice, reg, (0x0f - nvol) | (ovol & ~0x0f));
+	}
+
+	return change;
+}
+
+static int stac9460_mic_sw_info(struct snd_kcontrol *kcontrol,
+	       			struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = { "Line In", "Mic" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+
+        return 0;
+}
+
+
+static int stac9460_mic_sw_get(struct snd_kcontrol *kcontrol,
+	       		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+		
+	val = stac9460_get(ice, STAC946X_GENERAL_PURPOSE);
+	ucontrol->value.enumerated.item[0] = (val >> 7) & 0x1;
+	return 0;
+}
+
+static int stac9460_mic_sw_put(struct snd_kcontrol *kcontrol,
+	       		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char new, old;
+	int change;
+	old = stac9460_get(ice, STAC946X_GENERAL_PURPOSE);
+	new = (ucontrol->value.enumerated.item[0] << 7 & 0x80) | (old & ~0x80);
+	change = (new != old);
+	if (change)
+		stac9460_put(ice, STAC946X_GENERAL_PURPOSE, new);
+	return change;
+}
+/*
+ * Handler for setting correct codec rate - called when rate change is detected
+ */
+static void stac9460_set_rate_val(struct snd_ice1712 *ice, unsigned int rate)
+{
+	unsigned char old, new;
+	int idx;
+	unsigned char changed[7];
+	struct prodigy192_spec *spec = ice->spec;
+
+	if (rate == 0)  /* no hint - S/PDIF input is master, simply return */
+		return;
+	else if (rate <= 48000)
+		new = 0x08;	/* 256x, base rate mode */
+	else if (rate <= 96000)
+		new = 0x11;	/* 256x, mid rate mode */
+	else
+		new = 0x12;	/* 128x, high rate mode */
+	old = stac9460_get(ice, STAC946X_MASTER_CLOCKING);
+	if (old == new)
+		return;
+	/* change detected, setting master clock, muting first */
+	/* due to possible conflicts with mute controls - mutexing */
+	mutex_lock(&spec->mute_mutex);
+	/* we have to remember current mute status for each DAC */
+	for (idx = 0; idx < 7 ; ++idx)
+		changed[idx] = stac9460_dac_mute(ice,
+				STAC946X_MASTER_VOLUME + idx, 0);
+	/*printk(KERN_DEBUG "Rate change: %d, new MC: 0x%02x\n", rate, new);*/
+	stac9460_put(ice, STAC946X_MASTER_CLOCKING, new);
+	udelay(10);
+	/* unmuting - only originally unmuted dacs -
+	 * i.e. those changed when muting */
+	for (idx = 0; idx < 7 ; ++idx) {
+		if (changed[idx])
+			stac9460_dac_mute(ice, STAC946X_MASTER_VOLUME + idx, 1);
+	}
+	mutex_unlock(&spec->mute_mutex);
+}
+
+
+static const DECLARE_TLV_DB_SCALE(db_scale_dac, -19125, 75, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_adc, 0, 150, 0);
+
+/*
+ * mixers
+ */
+
+static struct snd_kcontrol_new stac_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = stac9460_dac_mute_info,
+		.get = stac9460_dac_mute_get,
+		.put = stac9460_dac_mute_put,
+		.private_value = 1,
+		.tlv = { .p = db_scale_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Master Playback Volume",
+		.info = stac9460_dac_vol_info,
+		.get = stac9460_dac_vol_get,
+		.put = stac9460_dac_vol_put,
+		.private_value = 1,
+		.tlv = { .p = db_scale_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "DAC Switch",
+		.count = 6,
+		.info = stac9460_dac_mute_info,
+		.get = stac9460_dac_mute_get,
+		.put = stac9460_dac_mute_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "DAC Volume",
+		.count = 6,
+		.info = stac9460_dac_vol_info,
+		.get = stac9460_dac_vol_get,
+		.put = stac9460_dac_vol_put,
+		.tlv = { .p = db_scale_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "ADC Capture Switch",
+		.count = 1,
+		.info = stac9460_adc_mute_info,
+		.get = stac9460_adc_mute_get,
+		.put = stac9460_adc_mute_put,
+
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "ADC Capture Volume",
+		.count = 1,
+		.info = stac9460_adc_vol_info,
+		.get = stac9460_adc_vol_get,
+		.put = stac9460_adc_vol_put,
+		.tlv = { .p = db_scale_adc }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Capture Input",
+		.info = stac9460_mic_sw_info,
+		.get = stac9460_mic_sw_get,
+		.put = stac9460_mic_sw_put,
+
+	},
+};
+
+/* AK4114 - ICE1724 connections on Prodigy192 + MI/ODI/O */
+/* CDTO (pin 32) -- GPIO11 pin 86
+ * CDTI (pin 33) -- GPIO10 pin 77
+ * CCLK (pin 34) -- GPIO9 pin 76
+ * CSN  (pin 35) -- GPIO8 pin 75
+ */
+#define AK4114_ADDR	0x00 /* C1-C0: Chip Address
+			      * (According to datasheet fixed to “00”)
+			      */
+
+/*
+ * 4wire ak4114 protocol - writing data
+ */
+static void write_data(struct snd_ice1712 *ice, unsigned int gpio,
+		       unsigned int data, int idx)
+{
+	for (; idx >= 0; idx--) {
+		/* drop clock */
+		gpio &= ~VT1724_PRODIGY192_CCLK;
+		snd_ice1712_gpio_write(ice, gpio);
+		udelay(1);
+		/* set data */
+		if (data & (1 << idx))
+			gpio |= VT1724_PRODIGY192_CDOUT;
+		else
+			gpio &= ~VT1724_PRODIGY192_CDOUT;
+		snd_ice1712_gpio_write(ice, gpio);
+		udelay(1);
+		/* raise clock */
+		gpio |= VT1724_PRODIGY192_CCLK;
+		snd_ice1712_gpio_write(ice, gpio);
+		udelay(1);
+	}
+}
+
+/*
+ * 4wire ak4114 protocol - reading data
+ */
+static unsigned char read_data(struct snd_ice1712 *ice, unsigned int gpio,
+			       int idx)
+{
+	unsigned char data = 0;
+
+	for (; idx >= 0; idx--) {
+		/* drop clock */
+		gpio &= ~VT1724_PRODIGY192_CCLK;
+		snd_ice1712_gpio_write(ice, gpio);
+		udelay(1);
+		/* read data */
+		if (snd_ice1712_gpio_read(ice) & VT1724_PRODIGY192_CDIN)
+			data |= (1 << idx);
+		udelay(1);
+		/* raise clock */
+		gpio |= VT1724_PRODIGY192_CCLK;
+		snd_ice1712_gpio_write(ice, gpio);
+		udelay(1);
+	}
+	return data;
+}
+/*
+ * 4wire ak4114 protocol - starting sequence
+ */
+static unsigned int prodigy192_4wire_start(struct snd_ice1712 *ice)
+{
+	unsigned int tmp;
+
+	snd_ice1712_save_gpio_status(ice);
+	tmp = snd_ice1712_gpio_read(ice);
+
+	tmp |= VT1724_PRODIGY192_CCLK; /* high at init */
+	tmp &= ~VT1724_PRODIGY192_CS; /* drop chip select */
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+	return tmp;
+}
+
+/*
+ * 4wire ak4114 protocol - final sequence
+ */
+static void prodigy192_4wire_finish(struct snd_ice1712 *ice, unsigned int tmp)
+{
+	tmp |= VT1724_PRODIGY192_CS; /* raise chip select */
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+	snd_ice1712_restore_gpio_status(ice);
+}
+
+/*
+ * Write data to addr register of ak4114
+ */
+static void prodigy192_ak4114_write(void *private_data, unsigned char addr,
+			       unsigned char data)
+{
+	struct snd_ice1712 *ice = private_data;
+	unsigned int tmp, addrdata;
+	tmp = prodigy192_4wire_start(ice);
+	addrdata = (AK4114_ADDR << 6) | 0x20 | (addr & 0x1f);
+	addrdata = (addrdata << 8) | data;
+	write_data(ice, tmp, addrdata, 15);
+	prodigy192_4wire_finish(ice, tmp);
+}
+
+/*
+ * Read data from addr register of ak4114
+ */
+static unsigned char prodigy192_ak4114_read(void *private_data,
+					    unsigned char addr)
+{
+	struct snd_ice1712 *ice = private_data;
+	unsigned int tmp;
+	unsigned char data;
+
+	tmp = prodigy192_4wire_start(ice);
+	write_data(ice, tmp, (AK4114_ADDR << 6) | (addr & 0x1f), 7);
+	data = read_data(ice, tmp, 7);
+	prodigy192_4wire_finish(ice, tmp);
+	return data;
+}
+
+
+static int ak4114_input_sw_info(struct snd_kcontrol *kcontrol,
+	       			struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = { "Toslink", "Coax" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+        return 0;
+}
+
+
+static int ak4114_input_sw_get(struct snd_kcontrol *kcontrol,
+	       		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+		
+	val = prodigy192_ak4114_read(ice, AK4114_REG_IO1);
+	/* AK4114_IPS0 bit = 0 -> RX0 = Toslink
+	 * AK4114_IPS0 bit = 1 -> RX1 = Coax
+	 */
+	ucontrol->value.enumerated.item[0] = (val & AK4114_IPS0) ? 1 : 0;
+	return 0;
+}
+
+static int ak4114_input_sw_put(struct snd_kcontrol *kcontrol,
+	       		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char new, old, itemvalue;
+	int change;
+
+	old = prodigy192_ak4114_read(ice, AK4114_REG_IO1);
+	/* AK4114_IPS0 could be any bit */
+	itemvalue = (ucontrol->value.enumerated.item[0]) ? 0xff : 0x00;
+
+	new = (itemvalue & AK4114_IPS0) | (old & ~AK4114_IPS0);
+	change = (new != old);
+	if (change)
+		prodigy192_ak4114_write(ice, AK4114_REG_IO1, new);
+	return change;
+}
+
+
+static struct snd_kcontrol_new ak4114_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "MIODIO IEC958 Capture Input",
+		.info = ak4114_input_sw_info,
+		.get = ak4114_input_sw_get,
+		.put = ak4114_input_sw_put,
+
+	}
+};
+
+
+static int prodigy192_ak4114_init(struct snd_ice1712 *ice)
+{
+	static const unsigned char ak4114_init_vals[] = {
+		AK4114_RST | AK4114_PWN | AK4114_OCKS0 | AK4114_OCKS1,
+		/* ice1724 expects I2S and provides clock,
+		 * DEM0 disables the deemphasis filter
+		 */
+		AK4114_DIF_I24I2S | AK4114_DEM0 ,
+		AK4114_TX1E,
+		AK4114_EFH_1024 | AK4114_DIT, /* default input RX0 */
+		0,
+		0
+	};
+	static const unsigned char ak4114_init_txcsb[] = {
+		0x41, 0x02, 0x2c, 0x00, 0x00
+	};
+	struct prodigy192_spec *spec = ice->spec;
+	int err;
+
+	err = snd_ak4114_create(ice->card,
+				 prodigy192_ak4114_read,
+				 prodigy192_ak4114_write,
+				 ak4114_init_vals, ak4114_init_txcsb,
+				 ice, &spec->ak4114);
+	if (err < 0)
+		return err;
+	/* AK4114 in Prodigy192 cannot detect external rate correctly.
+	 * No reason to stop capture stream due to incorrect checks */
+	spec->ak4114->check_flags = AK4114_CHECK_NO_RATE;
+	return 0;
+}
+
+static void stac9460_proc_regs_read(struct snd_info_entry *entry,
+		struct snd_info_buffer *buffer)
+{
+	struct snd_ice1712 *ice = entry->private_data;
+	int reg, val;
+	/* registers 0x0 - 0x14 */
+	for (reg = 0; reg <= 0x15; reg++) {
+		val = stac9460_get(ice, reg);
+		snd_iprintf(buffer, "0x%02x = 0x%02x\n", reg, val);
+	}
+}
+
+
+static void stac9460_proc_init(struct snd_ice1712 *ice)
+{
+	struct snd_info_entry *entry;
+	if (!snd_card_proc_new(ice->card, "stac9460_codec", &entry))
+		snd_info_set_text_ops(entry, ice, stac9460_proc_regs_read);
+}
+
+
+static int __devinit prodigy192_add_controls(struct snd_ice1712 *ice)
+{
+	struct prodigy192_spec *spec = ice->spec;
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(stac_controls); i++) {
+		err = snd_ctl_add(ice->card,
+				  snd_ctl_new1(&stac_controls[i], ice));
+		if (err < 0)
+			return err;
+	}
+	if (spec->ak4114) {
+		/* ak4114 is connected */
+		for (i = 0; i < ARRAY_SIZE(ak4114_controls); i++) {
+			err = snd_ctl_add(ice->card,
+					  snd_ctl_new1(&ak4114_controls[i],
+						       ice));
+			if (err < 0)
+				return err;
+		}
+		err = snd_ak4114_build(spec->ak4114,
+				NULL, /* ak4114 in MIO/DI/O handles no IEC958 output */
+				ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream);
+		if (err < 0)
+			return err;
+	}
+	stac9460_proc_init(ice);
+	return 0;
+}
+
+/*
+ * check for presence of MI/ODI/O add-on card with digital inputs
+ */
+static int prodigy192_miodio_exists(struct snd_ice1712 *ice)
+{
+
+	unsigned char orig_value;
+	const unsigned char test_data = 0xd1;	/* random value */
+	unsigned char addr = AK4114_REG_INT0_MASK; /* random SAFE address */
+	int exists = 0;
+
+	orig_value = prodigy192_ak4114_read(ice, addr);
+	prodigy192_ak4114_write(ice, addr, test_data);
+	if (prodigy192_ak4114_read(ice, addr) == test_data) {
+		/* ak4114 seems to communicate, apparently exists */
+		/* writing back original value */
+		prodigy192_ak4114_write(ice, addr, orig_value);
+		exists = 1;
+	}
+	return exists;
+}
+
+/*
+ * initialize the chip
+ */
+static int __devinit prodigy192_init(struct snd_ice1712 *ice)
+{
+	static const unsigned short stac_inits_prodigy[] = {
+		STAC946X_RESET, 0,
+		STAC946X_MASTER_CLOCKING, 0x11,
+/*		STAC946X_MASTER_VOLUME, 0,
+		STAC946X_LF_VOLUME, 0,
+		STAC946X_RF_VOLUME, 0,
+		STAC946X_LR_VOLUME, 0,
+		STAC946X_RR_VOLUME, 0,
+		STAC946X_CENTER_VOLUME, 0,
+		STAC946X_LFE_VOLUME, 0,*/
+		(unsigned short)-1
+	};
+	const unsigned short *p;
+	int err = 0;
+	struct prodigy192_spec *spec;
+
+	/* prodigy 192 */
+	ice->num_total_dacs = 6;
+	ice->num_total_adcs = 2;
+	ice->vt1720 = 0;  /* ice1724, e.g. 23 GPIOs */
+	
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	ice->spec = spec;
+	mutex_init(&spec->mute_mutex);
+
+	/* initialize codec */
+	p = stac_inits_prodigy;
+	for (; *p != (unsigned short)-1; p += 2)
+		stac9460_put(ice, p[0], p[1]);
+	ice->gpio.set_pro_rate = stac9460_set_rate_val;
+
+	/* MI/ODI/O add on card with AK4114 */
+	if (prodigy192_miodio_exists(ice)) {
+		err = prodigy192_ak4114_init(ice);
+		/* from this moment if err = 0 then
+		 * spec->ak4114 should not be null
+		 */
+		snd_printdd("AK4114 initialized with status %d\n", err);
+	} else
+		snd_printdd("AK4114 not found\n");
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+
+/*
+ * Aureon boards don't provide the EEPROM data except for the vendor IDs.
+ * hence the driver needs to sets up it properly.
+ */
+
+static unsigned char prodigy71_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x6a,	/* 49MHz crystal, mpu401,
+					 * spdif-in+ 1 stereo ADC,
+					 * 3 stereo DACs
+					 */
+	[ICE_EEP2_ACLINK]      = 0x80,	/* I2S */
+	[ICE_EEP2_I2S]         = 0xf8,	/* vol, 96k, 24bit, 192k */
+	[ICE_EEP2_SPDIF]       = 0xc3,	/* out-en, out-int, spdif-in */
+	[ICE_EEP2_GPIO_DIR]    = 0xff,
+	[ICE_EEP2_GPIO_DIR1]   = ~(VT1724_PRODIGY192_CDIN >> 8) ,
+	[ICE_EEP2_GPIO_DIR2]   = 0xbf,
+	[ICE_EEP2_GPIO_MASK]   = 0x00,
+	[ICE_EEP2_GPIO_MASK1]  = 0x00,
+	[ICE_EEP2_GPIO_MASK2]  = 0x00,
+	[ICE_EEP2_GPIO_STATE]  = 0x00,
+	[ICE_EEP2_GPIO_STATE1] = 0x00,
+	[ICE_EEP2_GPIO_STATE2] = 0x10,  /* GPIO20: 0 = CD drive dig. input
+					 * passthrough,
+					 * 1 = SPDIF-OUT from ice1724
+					 */
+};
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_prodigy192_cards[] __devinitdata = {
+	{
+		.subvendor = VT1724_SUBDEVICE_PRODIGY192VE,
+		.name = "Audiotrak Prodigy 192",
+		.model = "prodigy192",
+		.chip_init = prodigy192_init,
+		.build_controls = prodigy192_add_controls,
+		.eeprom_size = sizeof(prodigy71_eeprom),
+		.eeprom_data = prodigy71_eeprom,
+	},
+	{ } /* terminator */
+};
diff --git a/linux/sound/pci/ice1712/prodigy192.h b/linux/sound/pci/ice1712/prodigy192.h
new file mode 100644
index 0000000..16a53b4
--- /dev/null
+++ b/linux/sound/pci/ice1712/prodigy192.h
@@ -0,0 +1,19 @@
+#ifndef __SOUND_PRODIGY192_H
+#define __SOUND_PRODIGY192_H
+
+#define PRODIGY192_DEVICE_DESC 	       "{AudioTrak,Prodigy 192},"
+#define PRODIGY192_STAC9460_ADDR	0x54
+
+#define VT1724_SUBDEVICE_PRODIGY192VE	 0x34495345	/* PRODIGY 192 VE */
+/*
+ *  AudioTrak Prodigy192 GPIO definitions for MI/ODI/O card with
+ *  AK4114 (SPDIF-IN)
+ */
+#define VT1724_PRODIGY192_CS	(1 << 8)	/* GPIO8, pin 75 */
+#define VT1724_PRODIGY192_CCLK	(1 << 9)	/* GPIO9, pin 76 */
+#define VT1724_PRODIGY192_CDOUT	(1 << 10)	/* GPIO10, pin 77 */
+#define VT1724_PRODIGY192_CDIN	(1 << 11)	/* GPIO11, pin 86 */
+
+extern struct snd_ice1712_card_info  snd_vt1724_prodigy192_cards[];
+
+#endif	/* __SOUND_PRODIGY192_H */
diff --git a/linux/sound/pci/ice1712/prodigy_hifi.c b/linux/sound/pci/ice1712/prodigy_hifi.c
new file mode 100644
index 0000000..6a9fee3
--- /dev/null
+++ b/linux/sound/pci/ice1712/prodigy_hifi.c
@@ -0,0 +1,1236 @@
+/*
+ *   ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for Audiotrak Prodigy 7.1 Hifi
+ *   based on pontis.c
+ *
+ *      Copyright (c) 2007 Julian Scheel <julian@jusst.de>
+ *      Copyright (c) 2007 allank
+ *      Copyright (c) 2004 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 <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "prodigy_hifi.h"
+
+struct prodigy_hifi_spec {
+	unsigned short master[2];
+	unsigned short vol[8];
+};
+
+/* I2C addresses */
+#define WM_DEV		0x34
+
+/* WM8776 registers */
+#define WM_HP_ATTEN_L		0x00	/* headphone left attenuation */
+#define WM_HP_ATTEN_R		0x01	/* headphone left attenuation */
+#define WM_HP_MASTER		0x02	/* headphone master (both channels),
+						override LLR */
+#define WM_DAC_ATTEN_L		0x03	/* digital left attenuation */
+#define WM_DAC_ATTEN_R		0x04
+#define WM_DAC_MASTER		0x05
+#define WM_PHASE_SWAP		0x06	/* DAC phase swap */
+#define WM_DAC_CTRL1		0x07
+#define WM_DAC_MUTE		0x08
+#define WM_DAC_CTRL2		0x09
+#define WM_DAC_INT		0x0a
+#define WM_ADC_INT		0x0b
+#define WM_MASTER_CTRL		0x0c
+#define WM_POWERDOWN		0x0d
+#define WM_ADC_ATTEN_L		0x0e
+#define WM_ADC_ATTEN_R		0x0f
+#define WM_ALC_CTRL1		0x10
+#define WM_ALC_CTRL2		0x11
+#define WM_ALC_CTRL3		0x12
+#define WM_NOISE_GATE		0x13
+#define WM_LIMITER		0x14
+#define WM_ADC_MUX		0x15
+#define WM_OUT_MUX		0x16
+#define WM_RESET		0x17
+
+/* Analog Recording Source :- Mic, LineIn, CD/Video, */
+
+/* implement capture source select control for WM8776 */
+
+#define WM_AIN1 "AIN1"
+#define WM_AIN2 "AIN2"
+#define WM_AIN3 "AIN3"
+#define WM_AIN4 "AIN4"
+#define WM_AIN5 "AIN5"
+
+/* GPIO pins of envy24ht connected to wm8766 */
+#define WM8766_SPI_CLK	 (1<<17) /* CLK, Pin97 on ICE1724 */
+#define WM8766_SPI_MD	  (1<<16) /* DATA VT1724 -> WM8766, Pin96 */
+#define WM8766_SPI_ML	  (1<<18) /* Latch, Pin98 */
+
+/* WM8766 registers */
+#define WM8766_DAC_CTRL	 0x02   /* DAC Control */
+#define WM8766_INT_CTRL	 0x03   /* Interface Control */
+#define WM8766_DAC_CTRL2	0x09
+#define WM8766_DAC_CTRL3	0x0a
+#define WM8766_RESET	    0x1f
+#define WM8766_LDA1	     0x00
+#define WM8766_LDA2	     0x04
+#define WM8766_LDA3	     0x06
+#define WM8766_RDA1	     0x01
+#define WM8766_RDA2	     0x05
+#define WM8766_RDA3	     0x07
+#define WM8766_MUTE1	    0x0C
+#define WM8766_MUTE2	    0x0F
+
+
+/*
+ * Prodigy HD2
+ */
+#define AK4396_ADDR    0x00
+#define AK4396_CSN    (1 << 8)    /* CSN->GPIO8, pin 75 */
+#define AK4396_CCLK   (1 << 9)    /* CCLK->GPIO9, pin 76 */
+#define AK4396_CDTI   (1 << 10)   /* CDTI->GPIO10, pin 77 */
+
+/* ak4396 registers */
+#define AK4396_CTRL1	    0x00
+#define AK4396_CTRL2	    0x01
+#define AK4396_CTRL3	    0x02
+#define AK4396_LCH_ATT	  0x03
+#define AK4396_RCH_ATT	  0x04
+
+
+/*
+ * get the current register value of WM codec
+ */
+static unsigned short wm_get(struct snd_ice1712 *ice, int reg)
+{
+	reg <<= 1;
+	return ((unsigned short)ice->akm[0].images[reg] << 8) |
+		ice->akm[0].images[reg + 1];
+}
+
+/*
+ * set the register value of WM codec and remember it
+ */
+static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+	unsigned short cval;
+	cval = (reg << 9) | val;
+	snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff);
+}
+
+static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+	wm_put_nocache(ice, reg, val);
+	reg <<= 1;
+	ice->akm[0].images[reg] = val >> 8;
+	ice->akm[0].images[reg + 1] = val;
+}
+
+/*
+ * write data in the SPI mode
+ */
+
+static void set_gpio_bit(struct snd_ice1712 *ice, unsigned int bit, int val)
+{
+	unsigned int tmp = snd_ice1712_gpio_read(ice);
+	if (val)
+		tmp |= bit;
+	else
+		tmp &= ~bit;
+	snd_ice1712_gpio_write(ice, tmp);
+}
+
+/*
+ * SPI implementation for WM8766 codec - only writing supported, no readback
+ */
+
+static void wm8766_spi_send_word(struct snd_ice1712 *ice, unsigned int data)
+{
+	int i;
+	for (i = 0; i < 16; i++) {
+		set_gpio_bit(ice, WM8766_SPI_CLK, 0);
+		udelay(1);
+		set_gpio_bit(ice, WM8766_SPI_MD, data & 0x8000);
+		udelay(1);
+		set_gpio_bit(ice, WM8766_SPI_CLK, 1);
+		udelay(1);
+		data <<= 1;
+	}
+}
+
+static void wm8766_spi_write(struct snd_ice1712 *ice, unsigned int reg,
+			     unsigned int data)
+{
+	unsigned int block;
+
+	snd_ice1712_gpio_set_dir(ice, WM8766_SPI_MD|
+					WM8766_SPI_CLK|WM8766_SPI_ML);
+	snd_ice1712_gpio_set_mask(ice, ~(WM8766_SPI_MD|
+					WM8766_SPI_CLK|WM8766_SPI_ML));
+	/* latch must be low when writing */
+	set_gpio_bit(ice, WM8766_SPI_ML, 0);
+	block = (reg << 9) | (data & 0x1ff);
+	wm8766_spi_send_word(ice, block); /* REGISTER ADDRESS */
+	/* release latch */
+	set_gpio_bit(ice, WM8766_SPI_ML, 1);
+	udelay(1);
+	/* restore */
+	snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+	snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+}
+
+
+/*
+ * serial interface for ak4396 - only writing supported, no readback
+ */
+
+static void ak4396_send_word(struct snd_ice1712 *ice, unsigned int data)
+{
+	int i;
+	for (i = 0; i < 16; i++) {
+		set_gpio_bit(ice, AK4396_CCLK, 0);
+		udelay(1);
+		set_gpio_bit(ice, AK4396_CDTI, data & 0x8000);
+		udelay(1);
+		set_gpio_bit(ice, AK4396_CCLK, 1);
+		udelay(1);
+		data <<= 1;
+	}
+}
+
+static void ak4396_write(struct snd_ice1712 *ice, unsigned int reg,
+			 unsigned int data)
+{
+	unsigned int block;
+
+	snd_ice1712_gpio_set_dir(ice, AK4396_CSN|AK4396_CCLK|AK4396_CDTI);
+	snd_ice1712_gpio_set_mask(ice, ~(AK4396_CSN|AK4396_CCLK|AK4396_CDTI));
+	/* latch must be low when writing */
+	set_gpio_bit(ice, AK4396_CSN, 0); 
+	block =  ((AK4396_ADDR & 0x03) << 14) | (1 << 13) |
+			((reg & 0x1f) << 8) | (data & 0xff);
+	ak4396_send_word(ice, block); /* REGISTER ADDRESS */
+	/* release latch */
+	set_gpio_bit(ice, AK4396_CSN, 1);
+	udelay(1);
+	/* restore */
+	snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+	snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+}
+
+
+/*
+ * ak4396 mixers
+ */
+
+
+
+/*
+ * DAC volume attenuation mixer control (-64dB to 0dB)
+ */
+
+static int ak4396_dac_vol_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;   /* mute */
+	uinfo->value.integer.max = 0xFF; /* linear */
+	return 0;
+}
+
+static int ak4396_dac_vol_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct prodigy_hifi_spec *spec = ice->spec;
+	int i;
+	
+	for (i = 0; i < 2; i++)
+		ucontrol->value.integer.value[i] = spec->vol[i];
+
+	return 0;
+}
+
+static int ak4396_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct prodigy_hifi_spec *spec = ice->spec;
+	int i;
+	int change = 0;
+	
+	mutex_lock(&ice->gpio_mutex);
+	for (i = 0; i < 2; i++) {
+		if (ucontrol->value.integer.value[i] != spec->vol[i]) {
+			spec->vol[i] = ucontrol->value.integer.value[i];
+			ak4396_write(ice, AK4396_LCH_ATT + i,
+				     spec->vol[i] & 0xff);
+			change = 1;
+		}
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_dac, -12700, 100, 1);
+
+static struct snd_kcontrol_new prodigy_hd2_controls[] __devinitdata = {
+    {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.name = "Front Playback Volume",
+	.info = ak4396_dac_vol_info,
+	.get = ak4396_dac_vol_get,
+	.put = ak4396_dac_vol_put,
+	.tlv = { .p = db_scale_wm_dac },
+    },
+};
+
+
+/* --------------- */
+
+/*
+ * Logarithmic volume values for WM87*6
+ * Computed as 20 * Log10(255 / x)
+ */
+static const unsigned char wm_vol[256] = {
+	127, 48, 42, 39, 36, 34, 33, 31, 30, 29, 28, 27, 27, 26, 25, 25, 24, 24, 23,
+	23, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 18, 17, 17, 17,
+	17, 16, 16, 16, 16, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 13, 13, 13,
+	13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11, 11, 11, 11, 11, 11,
+	11, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 8,
+	8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6,
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+	5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+	2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0
+};
+
+#define WM_VOL_MAX	(sizeof(wm_vol) - 1)
+#define WM_VOL_MUTE	0x8000
+
+
+#define DAC_0dB	0xff
+#define DAC_RES	128
+#define DAC_MIN	(DAC_0dB - DAC_RES)
+
+
+static void wm_set_vol(struct snd_ice1712 *ice, unsigned int index,
+		       unsigned short vol, unsigned short master)
+{
+	unsigned char nvol;
+	
+	if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE))
+		nvol = 0;
+	else {
+		nvol = (((vol & ~WM_VOL_MUTE) * (master & ~WM_VOL_MUTE)) / 128)
+				& WM_VOL_MAX;
+		nvol = (nvol ? (nvol + DAC_MIN) : 0) & 0xff;
+	}
+	
+	wm_put(ice, index, nvol);
+	wm_put_nocache(ice, index, 0x100 | nvol);
+}
+
+static void wm8766_set_vol(struct snd_ice1712 *ice, unsigned int index,
+			   unsigned short vol, unsigned short master)
+{
+	unsigned char nvol;
+	
+	if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE))
+		nvol = 0;
+	else {
+		nvol = (((vol & ~WM_VOL_MUTE) * (master & ~WM_VOL_MUTE)) / 128)
+				& WM_VOL_MAX;
+		nvol = (nvol ? (nvol + DAC_MIN) : 0) & 0xff;
+	}
+
+	wm8766_spi_write(ice, index, (0x0100 | nvol));
+}
+
+
+/*
+ * DAC volume attenuation mixer control (-64dB to 0dB)
+ */
+
+static int wm_dac_vol_info(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;	/* mute */
+	uinfo->value.integer.max = DAC_RES;	/* 0dB, 0.5dB step */
+	return 0;
+}
+
+static int wm_dac_vol_get(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct prodigy_hifi_spec *spec = ice->spec;
+	int i;
+
+	for (i = 0; i < 2; i++)
+		ucontrol->value.integer.value[i] =
+			spec->vol[2 + i] & ~WM_VOL_MUTE;
+	return 0;
+}
+
+static int wm_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct prodigy_hifi_spec *spec = ice->spec;
+	int i, idx, change = 0;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (i = 0; i < 2; i++) {
+		if (ucontrol->value.integer.value[i] != spec->vol[2 + i]) {
+			idx = WM_DAC_ATTEN_L + i;
+			spec->vol[2 + i] &= WM_VOL_MUTE;
+			spec->vol[2 + i] |= ucontrol->value.integer.value[i];
+			wm_set_vol(ice, idx, spec->vol[2 + i], spec->master[i]);
+			change = 1;
+		}
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+
+/*
+ * WM8766 DAC volume attenuation mixer control
+ */
+static int wm8766_vol_info(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_info *uinfo)
+{
+	int voices = kcontrol->private_value >> 8;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = voices;
+	uinfo->value.integer.min = 0;		/* mute */
+	uinfo->value.integer.max = DAC_RES;	/* 0dB */
+	return 0;
+}
+
+static int wm8766_vol_get(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct prodigy_hifi_spec *spec = ice->spec;
+	int i, ofs, voices;
+
+	voices = kcontrol->private_value >> 8;
+	ofs = kcontrol->private_value & 0xff;
+	for (i = 0; i < voices; i++)
+		ucontrol->value.integer.value[i] = spec->vol[ofs + i];
+	return 0;
+}
+
+static int wm8766_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct prodigy_hifi_spec *spec = ice->spec;
+	int i, idx, ofs, voices;
+	int change = 0;
+
+	voices = kcontrol->private_value >> 8;
+	ofs = kcontrol->private_value & 0xff;
+	mutex_lock(&ice->gpio_mutex);
+	for (i = 0; i < voices; i++) {
+		if (ucontrol->value.integer.value[i] != spec->vol[ofs + i]) {
+			idx = WM8766_LDA1 + ofs + i;
+			spec->vol[ofs + i] &= WM_VOL_MUTE;
+			spec->vol[ofs + i] |= ucontrol->value.integer.value[i];
+			wm8766_set_vol(ice, idx,
+				       spec->vol[ofs + i], spec->master[i]);
+			change = 1;
+		}
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+/*
+ * Master volume attenuation mixer control / applied to WM8776+WM8766
+ */
+static int wm_master_vol_info(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = DAC_RES;
+	return 0;
+}
+
+static int wm_master_vol_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct prodigy_hifi_spec *spec = ice->spec;
+	int i;
+	for (i = 0; i < 2; i++)
+		ucontrol->value.integer.value[i] = spec->master[i];
+	return 0;
+}
+
+static int wm_master_vol_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	struct prodigy_hifi_spec *spec = ice->spec;
+	int ch, change = 0;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (ch = 0; ch < 2; ch++) {
+		if (ucontrol->value.integer.value[ch] != spec->master[ch]) {
+			spec->master[ch] = ucontrol->value.integer.value[ch];
+
+			/* Apply to front DAC */
+			wm_set_vol(ice, WM_DAC_ATTEN_L + ch,
+				   spec->vol[2 + ch], spec->master[ch]);
+
+			wm8766_set_vol(ice, WM8766_LDA1 + ch,
+				       spec->vol[0 + ch], spec->master[ch]);
+
+			wm8766_set_vol(ice, WM8766_LDA2 + ch,
+				       spec->vol[4 + ch], spec->master[ch]);
+
+			wm8766_set_vol(ice, WM8766_LDA3 + ch,
+				       spec->vol[6 + ch], spec->master[ch]);
+			change = 1;
+		}
+	}
+	mutex_unlock(&ice->gpio_mutex);	
+	return change;
+}
+
+
+/* KONSTI */
+
+static int wm_adc_mux_enum_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	static char* texts[32] = {
+		"NULL", WM_AIN1, WM_AIN2, WM_AIN1 "+" WM_AIN2,
+		WM_AIN3, WM_AIN1 "+" WM_AIN3, WM_AIN2 "+" WM_AIN3,
+		WM_AIN1 "+" WM_AIN2 "+" WM_AIN3,
+		WM_AIN4, WM_AIN1 "+" WM_AIN4, WM_AIN2 "+" WM_AIN4,
+		WM_AIN1 "+" WM_AIN2 "+" WM_AIN4,
+		WM_AIN3 "+" WM_AIN4, WM_AIN1 "+" WM_AIN3 "+" WM_AIN4,
+		WM_AIN2 "+" WM_AIN3 "+" WM_AIN4,
+		WM_AIN1 "+" WM_AIN2 "+" WM_AIN3 "+" WM_AIN4,
+		WM_AIN5, WM_AIN1 "+" WM_AIN5, WM_AIN2 "+" WM_AIN5,
+		WM_AIN1 "+" WM_AIN2 "+" WM_AIN5,
+		WM_AIN3 "+" WM_AIN5, WM_AIN1 "+" WM_AIN3 "+" WM_AIN5,
+		WM_AIN2 "+" WM_AIN3 "+" WM_AIN5,
+		WM_AIN1 "+" WM_AIN2 "+" WM_AIN3 "+" WM_AIN5,
+		WM_AIN4 "+" WM_AIN5, WM_AIN1 "+" WM_AIN4 "+" WM_AIN5,
+		WM_AIN2 "+" WM_AIN4 "+" WM_AIN5,
+		WM_AIN1 "+" WM_AIN2 "+" WM_AIN4 "+" WM_AIN5,
+		WM_AIN3 "+" WM_AIN4 "+" WM_AIN5,
+		WM_AIN1 "+" WM_AIN3 "+" WM_AIN4 "+" WM_AIN5,
+		WM_AIN2 "+" WM_AIN3 "+" WM_AIN4 "+" WM_AIN5,
+		WM_AIN1 "+" WM_AIN2 "+" WM_AIN3 "+" WM_AIN4 "+" WM_AIN5
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 32;
+	if (uinfo->value.enumerated.item > 31)
+		uinfo->value.enumerated.item = 31;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int wm_adc_mux_enum_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ice->gpio_mutex);
+	ucontrol->value.integer.value[0] = wm_get(ice, WM_ADC_MUX) & 0x1f;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_adc_mux_enum_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short oval, nval;
+	int change = 0;
+
+	mutex_lock(&ice->gpio_mutex);
+	oval = wm_get(ice, WM_ADC_MUX);
+	nval = (oval & 0xe0) | ucontrol->value.integer.value[0];
+	if (nval != oval) {
+		wm_put(ice, WM_ADC_MUX, nval);
+		change = 1;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+/* KONSTI */
+
+/*
+ * ADC gain mixer control (-64dB to 0dB)
+ */
+
+#define ADC_0dB	0xcf
+#define ADC_RES	128
+#define ADC_MIN	(ADC_0dB - ADC_RES)
+
+static int wm_adc_vol_info(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;	/* mute (-64dB) */
+	uinfo->value.integer.max = ADC_RES;	/* 0dB, 0.5dB step */
+	return 0;
+}
+
+static int wm_adc_vol_get(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short val;
+	int i;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (i = 0; i < 2; i++) {
+		val = wm_get(ice, WM_ADC_ATTEN_L + i) & 0xff;
+		val = val > ADC_MIN ? (val - ADC_MIN) : 0;
+		ucontrol->value.integer.value[i] = val;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_adc_vol_put(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short ovol, nvol;
+	int i, idx, change = 0;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (i = 0; i < 2; i++) {
+		nvol = ucontrol->value.integer.value[i];
+		nvol = nvol ? (nvol + ADC_MIN) : 0;
+		idx  = WM_ADC_ATTEN_L + i;
+		ovol = wm_get(ice, idx) & 0xff;
+		if (ovol != nvol) {
+			wm_put(ice, idx, nvol);
+			change = 1;
+		}
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+/*
+ * ADC input mux mixer control
+ */
+#define wm_adc_mux_info		snd_ctl_boolean_mono_info
+
+static int wm_adc_mux_get(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int bit = kcontrol->private_value;
+
+	mutex_lock(&ice->gpio_mutex);
+	ucontrol->value.integer.value[0] =
+		(wm_get(ice, WM_ADC_MUX) & (1 << bit)) ? 1 : 0;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_adc_mux_put(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int bit = kcontrol->private_value;
+	unsigned short oval, nval;
+	int change;
+
+	mutex_lock(&ice->gpio_mutex);
+	nval = oval = wm_get(ice, WM_ADC_MUX);
+	if (ucontrol->value.integer.value[0])
+		nval |= (1 << bit);
+	else
+		nval &= ~(1 << bit);
+	change = nval != oval;
+	if (change) {
+		wm_put(ice, WM_ADC_MUX, nval);
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+/*
+ * Analog bypass (In -> Out)
+ */
+#define wm_bypass_info		snd_ctl_boolean_mono_info
+
+static int wm_bypass_get(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ice->gpio_mutex);
+	ucontrol->value.integer.value[0] =
+		(wm_get(ice, WM_OUT_MUX) & 0x04) ? 1 : 0;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_bypass_put(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short val, oval;
+	int change = 0;
+
+	mutex_lock(&ice->gpio_mutex);
+	val = oval = wm_get(ice, WM_OUT_MUX);
+	if (ucontrol->value.integer.value[0])
+		val |= 0x04;
+	else
+		val &= ~0x04;
+	if (val != oval) {
+		wm_put(ice, WM_OUT_MUX, val);
+		change = 1;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+/*
+ * Left/Right swap
+ */
+#define wm_chswap_info		snd_ctl_boolean_mono_info
+
+static int wm_chswap_get(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&ice->gpio_mutex);
+	ucontrol->value.integer.value[0] =
+			(wm_get(ice, WM_DAC_CTRL1) & 0xf0) != 0x90;
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+
+static int wm_chswap_put(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned short val, oval;
+	int change = 0;
+
+	mutex_lock(&ice->gpio_mutex);
+	oval = wm_get(ice, WM_DAC_CTRL1);
+	val = oval & 0x0f;
+	if (ucontrol->value.integer.value[0])
+		val |= 0x60;
+	else
+		val |= 0x90;
+	if (val != oval) {
+		wm_put(ice, WM_DAC_CTRL1, val);
+		wm_put_nocache(ice, WM_DAC_CTRL1, val);
+		change = 1;
+	}
+	mutex_unlock(&ice->gpio_mutex);
+	return change;
+}
+
+
+/*
+ * mixers
+ */
+
+static struct snd_kcontrol_new prodigy_hifi_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Master Playback Volume",
+		.info = wm_master_vol_info,
+		.get = wm_master_vol_get,
+		.put = wm_master_vol_put,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Front Playback Volume",
+		.info = wm_dac_vol_info,
+		.get = wm_dac_vol_get,
+		.put = wm_dac_vol_put,
+		.tlv = { .p = db_scale_wm_dac },
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Rear Playback Volume",
+		.info = wm8766_vol_info,
+		.get = wm8766_vol_get,
+		.put = wm8766_vol_put,
+		.private_value = (2 << 8) | 0,
+		.tlv = { .p = db_scale_wm_dac },
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Center Playback Volume",
+		.info = wm8766_vol_info,
+		.get = wm8766_vol_get,
+		.put = wm8766_vol_put,
+		.private_value = (1 << 8) | 4,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "LFE Playback Volume",
+		.info = wm8766_vol_info,
+		.get = wm8766_vol_get,
+		.put = wm8766_vol_put,
+		.private_value = (1 << 8) | 5,
+		.tlv = { .p = db_scale_wm_dac }
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Side Playback Volume",
+		.info = wm8766_vol_info,
+		.get = wm8766_vol_get,
+		.put = wm8766_vol_put,
+		.private_value = (2 << 8) | 6,
+		.tlv = { .p = db_scale_wm_dac },
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+		.name = "Capture Volume",
+		.info = wm_adc_vol_info,
+		.get = wm_adc_vol_get,
+		.put = wm_adc_vol_put,
+		.tlv = { .p = db_scale_wm_dac },
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "CD Capture Switch",
+		.info = wm_adc_mux_info,
+		.get = wm_adc_mux_get,
+		.put = wm_adc_mux_put,
+		.private_value = 0,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Line Capture Switch",
+		.info = wm_adc_mux_info,
+		.get = wm_adc_mux_get,
+		.put = wm_adc_mux_put,
+		.private_value = 1,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Bypass Switch",
+		.info = wm_bypass_info,
+		.get = wm_bypass_get,
+		.put = wm_bypass_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Swap Output Channels",
+		.info = wm_chswap_info,
+		.get = wm_chswap_get,
+		.put = wm_chswap_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Capture Source",
+		.info = wm_adc_mux_enum_info,
+		.get = wm_adc_mux_enum_get,
+		.put = wm_adc_mux_enum_put,
+	},
+};
+
+/*
+ * WM codec registers
+ */
+static void wm_proc_regs_write(struct snd_info_entry *entry,
+			       struct snd_info_buffer *buffer)
+{
+	struct snd_ice1712 *ice = entry->private_data;
+	char line[64];
+	unsigned int reg, val;
+	mutex_lock(&ice->gpio_mutex);
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		if (sscanf(line, "%x %x", &reg, &val) != 2)
+			continue;
+		if (reg <= 0x17 && val <= 0xffff)
+			wm_put(ice, reg, val);
+	}
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static void wm_proc_regs_read(struct snd_info_entry *entry,
+			      struct snd_info_buffer *buffer)
+{
+	struct snd_ice1712 *ice = entry->private_data;
+	int reg, val;
+
+	mutex_lock(&ice->gpio_mutex);
+	for (reg = 0; reg <= 0x17; reg++) {
+		val = wm_get(ice, reg);
+		snd_iprintf(buffer, "%02x = %04x\n", reg, val);
+	}
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static void wm_proc_init(struct snd_ice1712 *ice)
+{
+	struct snd_info_entry *entry;
+	if (!snd_card_proc_new(ice->card, "wm_codec", &entry)) {
+		snd_info_set_text_ops(entry, ice, wm_proc_regs_read);
+		entry->mode |= S_IWUSR;
+		entry->c.text.write = wm_proc_regs_write;
+	}
+}
+
+static int __devinit prodigy_hifi_add_controls(struct snd_ice1712 *ice)
+{
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(prodigy_hifi_controls); i++) {
+		err = snd_ctl_add(ice->card,
+				  snd_ctl_new1(&prodigy_hifi_controls[i], ice));
+		if (err < 0)
+			return err;
+	}
+
+	wm_proc_init(ice);
+
+	return 0;
+}
+
+static int __devinit prodigy_hd2_add_controls(struct snd_ice1712 *ice)
+{
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(prodigy_hd2_controls); i++) {
+		err = snd_ctl_add(ice->card,
+				  snd_ctl_new1(&prodigy_hd2_controls[i], ice));
+		if (err < 0)
+			return err;
+	}
+
+	wm_proc_init(ice);
+
+	return 0;
+}
+
+
+/*
+ * initialize the chip
+ */
+static int __devinit prodigy_hifi_init(struct snd_ice1712 *ice)
+{
+	static unsigned short wm_inits[] = {
+		/* These come first to reduce init pop noise */
+		WM_ADC_MUX,	0x0003,	/* ADC mute */
+		/* 0x00c0 replaced by 0x0003 */
+		
+		WM_DAC_MUTE,	0x0001,	/* DAC softmute */
+		WM_DAC_CTRL1,	0x0000,	/* DAC mute */
+
+		WM_POWERDOWN,	0x0008,	/* All power-up except HP */
+		WM_RESET,	0x0000,	/* reset */
+	};
+	static unsigned short wm_inits2[] = {
+		WM_MASTER_CTRL,  0x0022, /* 256fs, slave mode */
+		WM_DAC_INT,	0x0022,	/* I2S, normal polarity, 24bit */
+		WM_ADC_INT,	0x0022,	/* I2S, normal polarity, 24bit */
+		WM_DAC_CTRL1,	0x0090,	/* DAC L/R */
+		WM_OUT_MUX,	0x0001,	/* OUT DAC */
+		WM_HP_ATTEN_L,	0x0179,	/* HP 0dB */
+		WM_HP_ATTEN_R,	0x0179,	/* HP 0dB */
+		WM_DAC_ATTEN_L,	0x0000,	/* DAC 0dB */
+		WM_DAC_ATTEN_L,	0x0100,	/* DAC 0dB */
+		WM_DAC_ATTEN_R,	0x0000,	/* DAC 0dB */
+		WM_DAC_ATTEN_R,	0x0100,	/* DAC 0dB */
+		WM_PHASE_SWAP,	0x0000,	/* phase normal */
+#if 0
+		WM_DAC_MASTER,	0x0100,	/* DAC master muted */
+#endif
+		WM_DAC_CTRL2,	0x0000,	/* no deemphasis, no ZFLG */
+		WM_ADC_ATTEN_L,	0x0000,	/* ADC muted */
+		WM_ADC_ATTEN_R,	0x0000,	/* ADC muted */
+#if 1
+		WM_ALC_CTRL1,	0x007b,	/* */
+		WM_ALC_CTRL2,	0x0000,	/* */
+		WM_ALC_CTRL3,	0x0000,	/* */
+		WM_NOISE_GATE,	0x0000,	/* */
+#endif
+		WM_DAC_MUTE,	0x0000,	/* DAC unmute */
+		WM_ADC_MUX,	0x0003,	/* ADC unmute, both CD/Line On */
+	};
+	static unsigned short wm8766_inits[] = {
+		WM8766_RESET,	   0x0000,
+		WM8766_DAC_CTRL,	0x0120,
+		WM8766_INT_CTRL,	0x0022, /* I2S Normal Mode, 24 bit */
+		WM8766_DAC_CTRL2,       0x0001,
+		WM8766_DAC_CTRL3,       0x0080,
+		WM8766_LDA1,	    0x0100,
+		WM8766_LDA2,	    0x0100,
+		WM8766_LDA3,	    0x0100,
+		WM8766_RDA1,	    0x0100,
+		WM8766_RDA2,	    0x0100,
+		WM8766_RDA3,	    0x0100,
+		WM8766_MUTE1,	   0x0000,
+		WM8766_MUTE2,	   0x0000,
+	};
+
+	struct prodigy_hifi_spec *spec;
+	unsigned int i;
+
+	ice->vt1720 = 0;
+	ice->vt1724 = 1;
+
+	ice->num_total_dacs = 8;
+	ice->num_total_adcs = 1;
+
+	/* HACK - use this as the SPDIF source.
+	* don't call snd_ice1712_gpio_get/put(), otherwise it's overwritten
+	*/
+	ice->gpio.saved[0] = 0;
+	/* to remeber the register values */
+
+	ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+	if (! ice->akm)
+		return -ENOMEM;
+	ice->akm_codecs = 1;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	ice->spec = spec;
+
+	/* initialize WM8776 codec */
+	for (i = 0; i < ARRAY_SIZE(wm_inits); i += 2)
+		wm_put(ice, wm_inits[i], wm_inits[i+1]);
+	schedule_timeout_uninterruptible(1);
+	for (i = 0; i < ARRAY_SIZE(wm_inits2); i += 2)
+		wm_put(ice, wm_inits2[i], wm_inits2[i+1]);
+
+	/* initialize WM8766 codec */
+	for (i = 0; i < ARRAY_SIZE(wm8766_inits); i += 2)
+		wm8766_spi_write(ice, wm8766_inits[i], wm8766_inits[i+1]);
+
+
+	return 0;
+}
+
+
+/*
+ * initialize the chip
+ */
+static void ak4396_init(struct snd_ice1712 *ice)
+{
+	static unsigned short ak4396_inits[] = {
+		AK4396_CTRL1,	   0x87,   /* I2S Normal Mode, 24 bit */
+		AK4396_CTRL2,	   0x02,
+		AK4396_CTRL3,	   0x00, 
+		AK4396_LCH_ATT,	 0x00,
+		AK4396_RCH_ATT,	 0x00,
+	};
+
+	unsigned int i;
+
+	/* initialize ak4396 codec */
+	/* reset codec */
+	ak4396_write(ice, AK4396_CTRL1, 0x86);
+	msleep(100);
+	ak4396_write(ice, AK4396_CTRL1, 0x87);
+
+	for (i = 0; i < ARRAY_SIZE(ak4396_inits); i += 2)
+		ak4396_write(ice, ak4396_inits[i], ak4396_inits[i+1]);
+}
+
+#ifdef CONFIG_PM
+static int prodigy_hd2_resume(struct snd_ice1712 *ice)
+{
+	/* initialize ak4396 codec and restore previous mixer volumes */
+	struct prodigy_hifi_spec *spec = ice->spec;
+	int i;
+	mutex_lock(&ice->gpio_mutex);
+	ak4396_init(ice);
+	for (i = 0; i < 2; i++)
+		ak4396_write(ice, AK4396_LCH_ATT + i, spec->vol[i] & 0xff);
+	mutex_unlock(&ice->gpio_mutex);
+	return 0;
+}
+#endif
+
+static int __devinit prodigy_hd2_init(struct snd_ice1712 *ice)
+{
+	struct prodigy_hifi_spec *spec;
+
+	ice->vt1720 = 0;
+	ice->vt1724 = 1;
+
+	ice->num_total_dacs = 1;
+	ice->num_total_adcs = 1;
+
+	/* HACK - use this as the SPDIF source.
+	* don't call snd_ice1712_gpio_get/put(), otherwise it's overwritten
+	*/
+	ice->gpio.saved[0] = 0;
+	/* to remeber the register values */
+
+	ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+	if (! ice->akm)
+		return -ENOMEM;
+	ice->akm_codecs = 1;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	ice->spec = spec;
+
+#ifdef CONFIG_PM
+	ice->pm_resume = &prodigy_hd2_resume;
+	ice->pm_suspend_enabled = 1;
+#endif
+
+	ak4396_init(ice);
+
+	return 0;
+}
+
+
+static unsigned char prodigy71hifi_eeprom[] __devinitdata = {
+	0x4b,   /* SYSCONF: clock 512, spdif-in/ADC, 4DACs */
+	0x80,   /* ACLINK: I2S */
+	0xfc,   /* I2S: vol, 96k, 24bit, 192k */
+	0xc3,   /* SPDIF: out-en, out-int, spdif-in */
+	0xff,   /* GPIO_DIR */
+	0xff,   /* GPIO_DIR1 */
+	0x5f,   /* GPIO_DIR2 */
+	0x00,   /* GPIO_MASK */
+	0x00,   /* GPIO_MASK1 */
+	0x00,   /* GPIO_MASK2 */
+	0x00,   /* GPIO_STATE */
+	0x00,   /* GPIO_STATE1 */
+	0x00,   /* GPIO_STATE2 */
+};
+
+static unsigned char prodigyhd2_eeprom[] __devinitdata = {
+	0x4b,   /* SYSCONF: clock 512, spdif-in/ADC, 4DACs */
+	0x80,   /* ACLINK: I2S */
+	0xfc,   /* I2S: vol, 96k, 24bit, 192k */
+	0xc3,   /* SPDIF: out-en, out-int, spdif-in */
+	0xff,   /* GPIO_DIR */
+	0xff,   /* GPIO_DIR1 */
+	0x5f,   /* GPIO_DIR2 */
+	0x00,   /* GPIO_MASK */
+	0x00,   /* GPIO_MASK1 */
+	0x00,   /* GPIO_MASK2 */
+	0x00,   /* GPIO_STATE */
+	0x00,   /* GPIO_STATE1 */
+	0x00,   /* GPIO_STATE2 */
+};
+
+static unsigned char fortissimo4_eeprom[] __devinitdata = {
+	0x43,   /* SYSCONF: clock 512, ADC, 4DACs */	
+	0x80,   /* ACLINK: I2S */
+	0xfc,   /* I2S: vol, 96k, 24bit, 192k */
+	0xc1,   /* SPDIF: out-en, out-int */
+	0xff,   /* GPIO_DIR */
+	0xff,   /* GPIO_DIR1 */
+	0x5f,   /* GPIO_DIR2 */
+	0x00,   /* GPIO_MASK */
+	0x00,   /* GPIO_MASK1 */
+	0x00,   /* GPIO_MASK2 */
+	0x00,   /* GPIO_STATE */
+	0x00,   /* GPIO_STATE1 */
+	0x00,   /* GPIO_STATE2 */
+};
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_prodigy_hifi_cards[] __devinitdata = {
+	{
+		.subvendor = VT1724_SUBDEVICE_PRODIGY_HIFI,
+		.name = "Audiotrak Prodigy 7.1 HiFi",
+		.model = "prodigy71hifi",
+		.chip_init = prodigy_hifi_init,
+		.build_controls = prodigy_hifi_add_controls,
+		.eeprom_size = sizeof(prodigy71hifi_eeprom),
+		.eeprom_data = prodigy71hifi_eeprom,
+		.driver = "Prodigy71HIFI",
+	},
+	{
+	.subvendor = VT1724_SUBDEVICE_PRODIGY_HD2,
+	.name = "Audiotrak Prodigy HD2",
+	.model = "prodigyhd2",
+	.chip_init = prodigy_hd2_init,
+	.build_controls = prodigy_hd2_add_controls,
+	.eeprom_size = sizeof(prodigyhd2_eeprom),
+	.eeprom_data = prodigyhd2_eeprom,
+	.driver = "Prodigy71HD2",
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_FORTISSIMO4,
+		.name = "Hercules Fortissimo IV",
+		.model = "fortissimo4",
+		.chip_init = prodigy_hifi_init,
+		.build_controls = prodigy_hifi_add_controls,
+		.eeprom_size = sizeof(fortissimo4_eeprom),
+		.eeprom_data = fortissimo4_eeprom,
+		.driver = "Fortissimo4",
+	},
+	{ } /* terminator */
+};
+
diff --git a/linux/sound/pci/ice1712/prodigy_hifi.h b/linux/sound/pci/ice1712/prodigy_hifi.h
new file mode 100644
index 0000000..a4415d4
--- /dev/null
+++ b/linux/sound/pci/ice1712/prodigy_hifi.h
@@ -0,0 +1,38 @@
+#ifndef __SOUND_PRODIGY_HIFI_H
+#define __SOUND_PRODIGY_HIFI_H
+
+/*
+ *   ALSA driver for VIA VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for Audiotrak Prodigy Hifi
+ *
+ *	Copyright (c) 2004 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
+ *
+ */      
+
+#define PRODIGY_HIFI_DEVICE_DESC 	       "{Audiotrak,Prodigy 7.1 HIFI},"\
+                                           "{Audiotrak Prodigy HD2},"\
+                                           "{Hercules Fortissimo IV},"
+
+#define VT1724_SUBDEVICE_PRODIGY_HIFI	0x38315441	/* PRODIGY 7.1 HIFI */
+#define VT1724_SUBDEVICE_PRODIGY_HD2	0x37315441	/* PRODIGY HD2 */
+#define VT1724_SUBDEVICE_FORTISSIMO4	0x81160100	/* Fortissimo IV */
+
+
+extern struct snd_ice1712_card_info  snd_vt1724_prodigy_hifi_cards[];
+
+#endif /* __SOUND_PRODIGY_HIFI_H */
diff --git a/linux/sound/pci/ice1712/quartet.c b/linux/sound/pci/ice1712/quartet.c
new file mode 100644
index 0000000..1948632
--- /dev/null
+++ b/linux/sound/pci/ice1712/quartet.c
@@ -0,0 +1,1130 @@
+/*
+ *   ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for Infrasonic Quartet
+ *
+ *	Copyright (c) 2009 Pavel Hofman <pavel.hofman@ivitera.com>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/info.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include <sound/ak4113.h>
+#include "quartet.h"
+
+struct qtet_spec {
+	struct ak4113 *ak4113;
+	unsigned int scr;	/* system control register */
+	unsigned int mcr;	/* monitoring control register */
+	unsigned int cpld;	/* cpld register */
+};
+
+struct qtet_kcontrol_private {
+	unsigned int bit;
+	void (*set_register)(struct snd_ice1712 *ice, unsigned int val);
+	unsigned int (*get_register)(struct snd_ice1712 *ice);
+	unsigned char *texts[2];
+};
+
+enum {
+	IN12_SEL = 0,
+	IN34_SEL,
+	AIN34_SEL,
+	COAX_OUT,
+	IN12_MON12,
+	IN12_MON34,
+	IN34_MON12,
+	IN34_MON34,
+	OUT12_MON34,
+	OUT34_MON12,
+};
+
+static char *ext_clock_names[3] = {"IEC958 In", "Word Clock 1xFS",
+	"Word Clock 256xFS"};
+
+/* chip address on I2C bus */
+#define AK4113_ADDR		0x26	/* S/PDIF receiver */
+
+/* chip address on SPI bus */
+#define AK4620_ADDR		0x02	/* ADC/DAC */
+
+
+/*
+ * GPIO pins
+ */
+
+/* GPIO0 - O - DATA0, def. 0 */
+#define GPIO_D0			(1<<0)
+/* GPIO1 - I/O - DATA1, Jack Detect Input0 (0:present, 1:missing), def. 1 */
+#define GPIO_D1_JACKDTC0	(1<<1)
+/* GPIO2 - I/O - DATA2, Jack Detect Input1 (0:present, 1:missing), def. 1 */
+#define GPIO_D2_JACKDTC1	(1<<2)
+/* GPIO3 - I/O - DATA3, def. 1 */
+#define GPIO_D3			(1<<3)
+/* GPIO4 - I/O - DATA4, SPI CDTO, def. 1 */
+#define GPIO_D4_SPI_CDTO	(1<<4)
+/* GPIO5 - I/O - DATA5, SPI CCLK, def. 1 */
+#define GPIO_D5_SPI_CCLK	(1<<5)
+/* GPIO6 - I/O - DATA6, Cable Detect Input (0:detected, 1:not detected */
+#define GPIO_D6_CD		(1<<6)
+/* GPIO7 - I/O - DATA7, Device Detect Input (0:detected, 1:not detected */
+#define GPIO_D7_DD		(1<<7)
+/* GPIO8 - O - CPLD Chip Select, def. 1 */
+#define GPIO_CPLD_CSN		(1<<8)
+/* GPIO9 - O - CPLD register read/write (0:write, 1:read), def. 0 */
+#define GPIO_CPLD_RW		(1<<9)
+/* GPIO10 - O - SPI Chip Select for CODEC#0, def. 1 */
+#define GPIO_SPI_CSN0		(1<<10)
+/* GPIO11 - O - SPI Chip Select for CODEC#1, def. 1 */
+#define GPIO_SPI_CSN1		(1<<11)
+/* GPIO12 - O - Ex. Register Output Enable (0:enable, 1:disable), def. 1,
+ * init 0 */
+#define GPIO_EX_GPIOE		(1<<12)
+/* GPIO13 - O - Ex. Register0 Chip Select for System Control Register,
+ * def. 1 */
+#define GPIO_SCR		(1<<13)
+/* GPIO14 - O - Ex. Register1 Chip Select for Monitor Control Register,
+ * def. 1 */
+#define GPIO_MCR		(1<<14)
+
+#define GPIO_SPI_ALL		(GPIO_D4_SPI_CDTO | GPIO_D5_SPI_CCLK |\
+		GPIO_SPI_CSN0 | GPIO_SPI_CSN1)
+
+#define GPIO_DATA_MASK		(GPIO_D0 | GPIO_D1_JACKDTC0 | \
+		GPIO_D2_JACKDTC1 | GPIO_D3 | \
+		GPIO_D4_SPI_CDTO | GPIO_D5_SPI_CCLK | \
+		GPIO_D6_CD | GPIO_D7_DD)
+
+/* System Control Register GPIO_SCR data bits */
+/* Mic/Line select relay (0:line, 1:mic) */
+#define SCR_RELAY		GPIO_D0
+/* Phantom power drive control (0:5V, 1:48V) */
+#define SCR_PHP_V		GPIO_D1_JACKDTC0
+/* H/W mute control (0:Normal, 1:Mute) */
+#define SCR_MUTE		GPIO_D2_JACKDTC1
+/* Phantom power control (0:Phantom on, 1:off) */
+#define SCR_PHP			GPIO_D3
+/* Analog input 1/2 Source Select */
+#define SCR_AIN12_SEL0		GPIO_D4_SPI_CDTO
+#define SCR_AIN12_SEL1		GPIO_D5_SPI_CCLK
+/* Analog input 3/4 Source Select (0:line, 1:hi-z) */
+#define SCR_AIN34_SEL		GPIO_D6_CD
+/* Codec Power Down (0:power down, 1:normal) */
+#define SCR_CODEC_PDN		GPIO_D7_DD
+
+#define SCR_AIN12_LINE		(0)
+#define SCR_AIN12_MIC		(SCR_AIN12_SEL0)
+#define SCR_AIN12_LOWCUT	(SCR_AIN12_SEL1 | SCR_AIN12_SEL0)
+
+/* Monitor Control Register GPIO_MCR data bits */
+/* Input 1/2 to Monitor 1/2 (0:off, 1:on) */
+#define MCR_IN12_MON12		GPIO_D0
+/* Input 1/2 to Monitor 3/4 (0:off, 1:on) */
+#define MCR_IN12_MON34		GPIO_D1_JACKDTC0
+/* Input 3/4 to Monitor 1/2 (0:off, 1:on) */
+#define MCR_IN34_MON12		GPIO_D2_JACKDTC1
+/* Input 3/4 to Monitor 3/4 (0:off, 1:on) */
+#define MCR_IN34_MON34		GPIO_D3
+/* Output to Monitor 1/2 (0:off, 1:on) */
+#define MCR_OUT34_MON12		GPIO_D4_SPI_CDTO
+/* Output to Monitor 3/4 (0:off, 1:on) */
+#define MCR_OUT12_MON34		GPIO_D5_SPI_CCLK
+
+/* CPLD Register DATA bits */
+/* Clock Rate Select */
+#define CPLD_CKS0		GPIO_D0
+#define CPLD_CKS1		GPIO_D1_JACKDTC0
+#define CPLD_CKS2		GPIO_D2_JACKDTC1
+/* Sync Source Select (0:Internal, 1:External) */
+#define CPLD_SYNC_SEL		GPIO_D3
+/* Word Clock FS Select (0:FS, 1:256FS) */
+#define CPLD_WORD_SEL		GPIO_D4_SPI_CDTO
+/* Coaxial Output Source (IS-Link) (0:SPDIF, 1:I2S) */
+#define CPLD_COAX_OUT		GPIO_D5_SPI_CCLK
+/* Input 1/2 Source Select (0:Analog12, 1:An34) */
+#define CPLD_IN12_SEL		GPIO_D6_CD
+/* Input 3/4 Source Select (0:Analog34, 1:Digital In) */
+#define CPLD_IN34_SEL		GPIO_D7_DD
+
+/* internal clock (CPLD_SYNC_SEL = 0) options */
+#define CPLD_CKS_44100HZ	(0)
+#define CPLD_CKS_48000HZ	(CPLD_CKS0)
+#define CPLD_CKS_88200HZ	(CPLD_CKS1)
+#define CPLD_CKS_96000HZ	(CPLD_CKS1 | CPLD_CKS0)
+#define CPLD_CKS_176400HZ	(CPLD_CKS2)
+#define CPLD_CKS_192000HZ	(CPLD_CKS2 | CPLD_CKS0)
+
+#define CPLD_CKS_MASK		(CPLD_CKS0 | CPLD_CKS1 | CPLD_CKS2)
+
+/* external clock (CPLD_SYNC_SEL = 1) options */
+/* external clock - SPDIF */
+#define CPLD_EXT_SPDIF	(0 | CPLD_SYNC_SEL)
+/* external clock - WordClock 1xfs */
+#define CPLD_EXT_WORDCLOCK_1FS	(CPLD_CKS1 | CPLD_SYNC_SEL)
+/* external clock - WordClock 256xfs */
+#define CPLD_EXT_WORDCLOCK_256FS	(CPLD_CKS1 | CPLD_WORD_SEL |\
+		CPLD_SYNC_SEL)
+
+#define EXT_SPDIF_TYPE			0
+#define EXT_WORDCLOCK_1FS_TYPE		1
+#define EXT_WORDCLOCK_256FS_TYPE	2
+
+#define AK4620_DFS0		(1<<0)
+#define AK4620_DFS1		(1<<1)
+#define AK4620_CKS0		(1<<2)
+#define AK4620_CKS1		(1<<3)
+/* Clock and Format Control register */
+#define AK4620_DFS_REG		0x02
+
+/* Deem and Volume Control register */
+#define AK4620_DEEMVOL_REG	0x03
+#define AK4620_SMUTE		(1<<7)
+
+/*
+ * Conversion from int value to its binary form. Used for debugging.
+ * The output buffer must be allocated prior to calling the function.
+ */
+static char *get_binary(char *buffer, int value)
+{
+	int i, j, pos;
+	pos = 0;
+	for (i = 0; i < 4; ++i) {
+		for (j = 0; j < 8; ++j) {
+			if (value & (1 << (31-(i*8 + j))))
+				buffer[pos] = '1';
+			else
+				buffer[pos] = '0';
+			pos++;
+		}
+		if (i < 3) {
+			buffer[pos] = ' ';
+			pos++;
+		}
+	}
+	buffer[pos] = '\0';
+	return buffer;
+}
+
+/*
+ * Initial setup of the conversion array GPIO <-> rate
+ */
+static unsigned int qtet_rates[] = {
+	44100, 48000, 88200,
+	96000, 176400, 192000,
+};
+
+static unsigned int cks_vals[] = {
+	CPLD_CKS_44100HZ, CPLD_CKS_48000HZ, CPLD_CKS_88200HZ,
+	CPLD_CKS_96000HZ, CPLD_CKS_176400HZ, CPLD_CKS_192000HZ,
+};
+
+static struct snd_pcm_hw_constraint_list qtet_rates_info = {
+	.count = ARRAY_SIZE(qtet_rates),
+	.list = qtet_rates,
+	.mask = 0,
+};
+
+static void qtet_ak4113_write(void *private_data, unsigned char reg,
+		unsigned char val)
+{
+	snd_vt1724_write_i2c((struct snd_ice1712 *)private_data, AK4113_ADDR,
+			reg, val);
+}
+
+static unsigned char qtet_ak4113_read(void *private_data, unsigned char reg)
+{
+	return snd_vt1724_read_i2c((struct snd_ice1712 *)private_data,
+			AK4113_ADDR, reg);
+}
+
+
+/*
+ * AK4620 section
+ */
+
+/*
+ * Write data to addr register of ak4620
+ */
+static void qtet_akm_write(struct snd_akm4xxx *ak, int chip,
+		unsigned char addr, unsigned char data)
+{
+	unsigned int tmp, orig_dir;
+	int idx;
+	unsigned int addrdata;
+	struct snd_ice1712 *ice = ak->private_data[0];
+
+	if (snd_BUG_ON(chip < 0 || chip >= 4))
+		return;
+	/*printk(KERN_DEBUG "Writing to AK4620: chip=%d, addr=0x%x,
+	  data=0x%x\n", chip, addr, data);*/
+	orig_dir = ice->gpio.get_dir(ice);
+	ice->gpio.set_dir(ice, orig_dir | GPIO_SPI_ALL);
+	/* set mask - only SPI bits */
+	ice->gpio.set_mask(ice, ~GPIO_SPI_ALL);
+
+	tmp = ice->gpio.get_data(ice);
+	/* high all */
+	tmp |= GPIO_SPI_ALL;
+	ice->gpio.set_data(ice, tmp);
+	udelay(100);
+	/* drop chip select */
+	if (chip)
+		/* CODEC 1 */
+		tmp &= ~GPIO_SPI_CSN1;
+	else
+		tmp &= ~GPIO_SPI_CSN0;
+	ice->gpio.set_data(ice, tmp);
+	udelay(100);
+
+	/* build I2C address + data byte */
+	addrdata = (AK4620_ADDR << 6) | 0x20 | (addr & 0x1f);
+	addrdata = (addrdata << 8) | data;
+	for (idx = 15; idx >= 0; idx--) {
+		/* drop clock */
+		tmp &= ~GPIO_D5_SPI_CCLK;
+		ice->gpio.set_data(ice, tmp);
+		udelay(100);
+		/* set data */
+		if (addrdata & (1 << idx))
+			tmp |= GPIO_D4_SPI_CDTO;
+		else
+			tmp &= ~GPIO_D4_SPI_CDTO;
+		ice->gpio.set_data(ice, tmp);
+		udelay(100);
+		/* raise clock */
+		tmp |= GPIO_D5_SPI_CCLK;
+		ice->gpio.set_data(ice, tmp);
+		udelay(100);
+	}
+	/* all back to 1 */
+	tmp |= GPIO_SPI_ALL;
+	ice->gpio.set_data(ice, tmp);
+	udelay(100);
+
+	/* return all gpios to non-writable */
+	ice->gpio.set_mask(ice, 0xffffff);
+	/* restore GPIOs direction */
+	ice->gpio.set_dir(ice, orig_dir);
+}
+
+static void qtet_akm_set_regs(struct snd_akm4xxx *ak, unsigned char addr,
+		unsigned char mask, unsigned char value)
+{
+	unsigned char tmp;
+	int chip;
+	for (chip = 0; chip < ak->num_chips; chip++) {
+		tmp = snd_akm4xxx_get(ak, chip, addr);
+		/* clear the bits */
+		tmp &= ~mask;
+		/* set the new bits */
+		tmp |= value;
+		snd_akm4xxx_write(ak, chip, addr, tmp);
+	}
+}
+
+/*
+ * change the rate of AK4620
+ */
+static void qtet_akm_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+	unsigned char ak4620_dfs;
+
+	if (rate == 0)  /* no hint - S/PDIF input is master or the new spdif
+			   input rate undetected, simply return */
+		return;
+
+	/* adjust DFS on codecs - see datasheet */
+	if (rate > 108000)
+		ak4620_dfs = AK4620_DFS1 | AK4620_CKS1;
+	else if (rate > 54000)
+		ak4620_dfs = AK4620_DFS0 | AK4620_CKS0;
+	else
+		ak4620_dfs = 0;
+
+	/* set new value */
+	qtet_akm_set_regs(ak, AK4620_DFS_REG, AK4620_DFS0 | AK4620_DFS1 |
+			AK4620_CKS0 | AK4620_CKS1, ak4620_dfs);
+}
+
+#define AK_CONTROL(xname, xch)	{ .name = xname, .num_channels = xch }
+
+#define PCM_12_PLAYBACK_VOLUME	"PCM 1/2 Playback Volume"
+#define PCM_34_PLAYBACK_VOLUME	"PCM 3/4 Playback Volume"
+#define PCM_12_CAPTURE_VOLUME	"PCM 1/2 Capture Volume"
+#define PCM_34_CAPTURE_VOLUME	"PCM 3/4 Capture Volume"
+
+static const struct snd_akm4xxx_dac_channel qtet_dac[] = {
+	AK_CONTROL(PCM_12_PLAYBACK_VOLUME, 2),
+	AK_CONTROL(PCM_34_PLAYBACK_VOLUME, 2),
+};
+
+static const struct snd_akm4xxx_adc_channel qtet_adc[] = {
+	AK_CONTROL(PCM_12_CAPTURE_VOLUME, 2),
+	AK_CONTROL(PCM_34_CAPTURE_VOLUME, 2),
+};
+
+static struct snd_akm4xxx akm_qtet_dac __devinitdata = {
+	.type = SND_AK4620,
+	.num_dacs = 4,	/* DAC1 - Output 12
+	*/
+	.num_adcs = 4,	/* ADC1 - Input 12
+	*/
+	.ops = {
+		.write = qtet_akm_write,
+		.set_rate_val = qtet_akm_set_rate_val,
+	},
+	.dac_info = qtet_dac,
+	.adc_info = qtet_adc,
+};
+
+/* Communication routines with the CPLD */
+
+
+/* Writes data to external register reg, both reg and data are
+ * GPIO representations */
+static void reg_write(struct snd_ice1712 *ice, unsigned int reg,
+		unsigned int data)
+{
+	unsigned int tmp;
+
+	mutex_lock(&ice->gpio_mutex);
+	/* set direction of used GPIOs*/
+	/* all outputs */
+	tmp = 0x00ffff;
+	ice->gpio.set_dir(ice, tmp);
+	/* mask - writable bits */
+	ice->gpio.set_mask(ice, ~(tmp));
+	/* write the data */
+	tmp = ice->gpio.get_data(ice);
+	tmp &= ~GPIO_DATA_MASK;
+	tmp |= data;
+	ice->gpio.set_data(ice, tmp);
+	udelay(100);
+	/* drop output enable */
+	tmp &=  ~GPIO_EX_GPIOE;
+	ice->gpio.set_data(ice, tmp);
+	udelay(100);
+	/* drop the register gpio */
+	tmp &= ~reg;
+	ice->gpio.set_data(ice, tmp);
+	udelay(100);
+	/* raise the register GPIO */
+	tmp |= reg;
+	ice->gpio.set_data(ice, tmp);
+	udelay(100);
+
+	/* raise all data gpios */
+	tmp |= GPIO_DATA_MASK;
+	ice->gpio.set_data(ice, tmp);
+	/* mask - immutable bits */
+	ice->gpio.set_mask(ice, 0xffffff);
+	/* outputs only 8-15 */
+	ice->gpio.set_dir(ice, 0x00ff00);
+	mutex_unlock(&ice->gpio_mutex);
+}
+
+static unsigned int get_scr(struct snd_ice1712 *ice)
+{
+	struct qtet_spec *spec = ice->spec;
+	return spec->scr;
+}
+
+static unsigned int get_mcr(struct snd_ice1712 *ice)
+{
+	struct qtet_spec *spec = ice->spec;
+	return spec->mcr;
+}
+
+static unsigned int get_cpld(struct snd_ice1712 *ice)
+{
+	struct qtet_spec *spec = ice->spec;
+	return spec->cpld;
+}
+
+static void set_scr(struct snd_ice1712 *ice, unsigned int val)
+{
+	struct qtet_spec *spec = ice->spec;
+	reg_write(ice, GPIO_SCR, val);
+	spec->scr = val;
+}
+
+static void set_mcr(struct snd_ice1712 *ice, unsigned int val)
+{
+	struct qtet_spec *spec = ice->spec;
+	reg_write(ice, GPIO_MCR, val);
+	spec->mcr = val;
+}
+
+static void set_cpld(struct snd_ice1712 *ice, unsigned int val)
+{
+	struct qtet_spec *spec = ice->spec;
+	reg_write(ice, GPIO_CPLD_CSN, val);
+	spec->cpld = val;
+}
+#ifdef CONFIG_PROC_FS
+static void proc_regs_read(struct snd_info_entry *entry,
+		struct snd_info_buffer *buffer)
+{
+	struct snd_ice1712 *ice = entry->private_data;
+	char bin_buffer[36];
+
+	snd_iprintf(buffer, "SCR:	%s\n", get_binary(bin_buffer,
+				get_scr(ice)));
+	snd_iprintf(buffer, "MCR:	%s\n", get_binary(bin_buffer,
+				get_mcr(ice)));
+	snd_iprintf(buffer, "CPLD:	%s\n", get_binary(bin_buffer,
+				get_cpld(ice)));
+}
+
+static void proc_init(struct snd_ice1712 *ice)
+{
+	struct snd_info_entry *entry;
+	if (!snd_card_proc_new(ice->card, "quartet", &entry))
+		snd_info_set_text_ops(entry, ice, proc_regs_read);
+}
+#else /* !CONFIG_PROC_FS */
+static void proc_init(struct snd_ice1712 *ice) {}
+#endif
+
+static int qtet_mute_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	val = get_scr(ice) & SCR_MUTE;
+	ucontrol->value.integer.value[0] = (val) ? 0 : 1;
+	return 0;
+}
+
+static int qtet_mute_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int old, new, smute;
+	old = get_scr(ice) & SCR_MUTE;
+	if (ucontrol->value.integer.value[0]) {
+		/* unmute */
+		new = 0;
+		/* un-smuting DAC */
+		smute = 0;
+	} else {
+		/* mute */
+		new = SCR_MUTE;
+		/* smuting DAC */
+		smute = AK4620_SMUTE;
+	}
+	if (old != new) {
+		struct snd_akm4xxx *ak = ice->akm;
+		set_scr(ice, (get_scr(ice) & ~SCR_MUTE) | new);
+		/* set smute */
+		qtet_akm_set_regs(ak, AK4620_DEEMVOL_REG, AK4620_SMUTE, smute);
+		return 1;
+	}
+	/* no change */
+	return 0;
+}
+
+static int qtet_ain12_enum_info(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[3] = {"Line In 1/2", "Mic", "Mic + Low-cut"};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = ARRAY_SIZE(texts);
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+			uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+			texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int qtet_ain12_sw_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int val, result;
+	val = get_scr(ice) & (SCR_AIN12_SEL1 | SCR_AIN12_SEL0);
+	switch (val) {
+	case SCR_AIN12_LINE:
+		result = 0;
+		break;
+	case SCR_AIN12_MIC:
+		result = 1;
+		break;
+	case SCR_AIN12_LOWCUT:
+		result = 2;
+		break;
+	default:
+		/* BUG - no other combinations allowed */
+		snd_BUG();
+		result = 0;
+	}
+	ucontrol->value.integer.value[0] = result;
+	return 0;
+}
+
+static int qtet_ain12_sw_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int old, new, tmp, masked_old;
+	old = new = get_scr(ice);
+	masked_old = old & (SCR_AIN12_SEL1 | SCR_AIN12_SEL0);
+	tmp = ucontrol->value.integer.value[0];
+	if (tmp == 2)
+		tmp = 3;	/* binary 10 is not supported */
+	tmp <<= 4;	/* shifting to SCR_AIN12_SEL0 */
+	if (tmp != masked_old) {
+		/* change requested */
+		switch (tmp) {
+		case SCR_AIN12_LINE:
+			new = old & ~(SCR_AIN12_SEL1 | SCR_AIN12_SEL0);
+			set_scr(ice, new);
+			/* turn off relay */
+			new &= ~SCR_RELAY;
+			set_scr(ice, new);
+			break;
+		case SCR_AIN12_MIC:
+			/* turn on relay */
+			new = old | SCR_RELAY;
+			set_scr(ice, new);
+			new = (new & ~SCR_AIN12_SEL1) | SCR_AIN12_SEL0;
+			set_scr(ice, new);
+			break;
+		case SCR_AIN12_LOWCUT:
+			/* turn on relay */
+			new = old | SCR_RELAY;
+			set_scr(ice, new);
+			new |= SCR_AIN12_SEL1 | SCR_AIN12_SEL0;
+			set_scr(ice, new);
+			break;
+		default:
+			snd_BUG();
+		}
+		return 1;
+	}
+	/* no change */
+	return 0;
+}
+
+static int qtet_php_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	/* if phantom voltage =48V, phantom on */
+	val = get_scr(ice) & SCR_PHP_V;
+	ucontrol->value.integer.value[0] = val ? 1 : 0;
+	return 0;
+}
+
+static int qtet_php_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int old, new;
+	old = new = get_scr(ice);
+	if (ucontrol->value.integer.value[0] /* phantom on requested */
+			&& (~old & SCR_PHP_V)) /* 0 = voltage 5V */ {
+		/* is off, turn on */
+		/* turn voltage on first, = 1 */
+		new = old | SCR_PHP_V;
+		set_scr(ice, new);
+		/* turn phantom on, = 0 */
+		new &= ~SCR_PHP;
+		set_scr(ice, new);
+	} else if (!ucontrol->value.integer.value[0] && (old & SCR_PHP_V)) {
+		/* phantom off requested and 1 = voltage 48V */
+		/* is on, turn off */
+		/* turn voltage off first, = 0 */
+		new = old & ~SCR_PHP_V;
+		set_scr(ice, new);
+		/* turn phantom off, = 1 */
+		new |= SCR_PHP;
+		set_scr(ice, new);
+	}
+	if (old != new)
+		return 1;
+	/* no change */
+	return 0;
+}
+
+#define PRIV_SW(xid, xbit, xreg)	[xid] = {.bit = xbit,\
+	.set_register = set_##xreg,\
+	.get_register = get_##xreg, }
+
+
+#define PRIV_ENUM2(xid, xbit, xreg, xtext1, xtext2)	[xid] = {.bit = xbit,\
+	.set_register = set_##xreg,\
+	.get_register = get_##xreg,\
+	.texts = {xtext1, xtext2} }
+
+static struct qtet_kcontrol_private qtet_privates[] = {
+	PRIV_ENUM2(IN12_SEL, CPLD_IN12_SEL, cpld, "An In 1/2", "An In 3/4"),
+	PRIV_ENUM2(IN34_SEL, CPLD_IN34_SEL, cpld, "An In 3/4", "IEC958 In"),
+	PRIV_ENUM2(AIN34_SEL, SCR_AIN34_SEL, scr, "Line In 3/4", "Hi-Z"),
+	PRIV_ENUM2(COAX_OUT, CPLD_COAX_OUT, cpld, "IEC958", "I2S"),
+	PRIV_SW(IN12_MON12, MCR_IN12_MON12, mcr),
+	PRIV_SW(IN12_MON34, MCR_IN12_MON34, mcr),
+	PRIV_SW(IN34_MON12, MCR_IN34_MON12, mcr),
+	PRIV_SW(IN34_MON34, MCR_IN34_MON34, mcr),
+	PRIV_SW(OUT12_MON34, MCR_OUT12_MON34, mcr),
+	PRIV_SW(OUT34_MON12, MCR_OUT34_MON12, mcr),
+};
+
+static int qtet_enum_info(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	struct qtet_kcontrol_private private =
+		qtet_privates[kcontrol->private_value];
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = ARRAY_SIZE(private.texts);
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+			uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+			private.texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int qtet_sw_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct qtet_kcontrol_private private =
+		qtet_privates[kcontrol->private_value];
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] =
+		(private.get_register(ice) & private.bit) ? 1 : 0;
+	return 0;
+}
+
+static int qtet_sw_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct qtet_kcontrol_private private =
+		qtet_privates[kcontrol->private_value];
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned int old, new;
+	old = private.get_register(ice);
+	if (ucontrol->value.integer.value[0])
+		new = old | private.bit;
+	else
+		new = old & ~private.bit;
+	if (old != new) {
+		private.set_register(ice, new);
+		return 1;
+	}
+	/* no change */
+	return 0;
+}
+
+#define qtet_sw_info	snd_ctl_boolean_mono_info
+
+#define QTET_CONTROL(xname, xtype, xpriv)	\
+	{.iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
+	.name = xname,\
+	.info = qtet_##xtype##_info,\
+	.get = qtet_sw_get,\
+	.put = qtet_sw_put,\
+	.private_value = xpriv }
+
+static struct snd_kcontrol_new qtet_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = qtet_sw_info,
+		.get = qtet_mute_get,
+		.put = qtet_mute_put,
+		.private_value = 0
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Phantom Power",
+		.info = qtet_sw_info,
+		.get = qtet_php_get,
+		.put = qtet_php_put,
+		.private_value = 0
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog In 1/2 Capture Switch",
+		.info = qtet_ain12_enum_info,
+		.get = qtet_ain12_sw_get,
+		.put = qtet_ain12_sw_put,
+		.private_value = 0
+	},
+	QTET_CONTROL("Analog In 3/4 Capture Switch", enum, AIN34_SEL),
+	QTET_CONTROL("PCM In 1/2 Capture Switch", enum, IN12_SEL),
+	QTET_CONTROL("PCM In 3/4 Capture Switch", enum, IN34_SEL),
+	QTET_CONTROL("Coax Output Source", enum, COAX_OUT),
+	QTET_CONTROL("Analog In 1/2 to Monitor 1/2", sw, IN12_MON12),
+	QTET_CONTROL("Analog In 1/2 to Monitor 3/4", sw, IN12_MON34),
+	QTET_CONTROL("Analog In 3/4 to Monitor 1/2", sw, IN34_MON12),
+	QTET_CONTROL("Analog In 3/4 to Monitor 3/4", sw, IN34_MON34),
+	QTET_CONTROL("Output 1/2 to Monitor 3/4", sw, OUT12_MON34),
+	QTET_CONTROL("Output 3/4 to Monitor 1/2", sw, OUT34_MON12),
+};
+
+static char *slave_vols[] __devinitdata = {
+	PCM_12_PLAYBACK_VOLUME,
+	PCM_34_PLAYBACK_VOLUME,
+	NULL
+};
+
+static __devinitdata
+DECLARE_TLV_DB_SCALE(qtet_master_db_scale, -6350, 50, 1);
+
+static struct snd_kcontrol __devinit *ctl_find(struct snd_card *card,
+		const char *name)
+{
+	struct snd_ctl_elem_id sid;
+	memset(&sid, 0, sizeof(sid));
+	/* FIXME: strcpy is bad. */
+	strcpy(sid.name, name);
+	sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	return snd_ctl_find_id(card, &sid);
+}
+
+static void __devinit add_slaves(struct snd_card *card,
+		struct snd_kcontrol *master, char **list)
+{
+	for (; *list; list++) {
+		struct snd_kcontrol *slave = ctl_find(card, *list);
+		if (slave)
+			snd_ctl_add_slave(master, slave);
+	}
+}
+
+static int __devinit qtet_add_controls(struct snd_ice1712 *ice)
+{
+	struct qtet_spec *spec = ice->spec;
+	int err, i;
+	struct snd_kcontrol *vmaster;
+	err = snd_ice1712_akm4xxx_build_controls(ice);
+	if (err < 0)
+		return err;
+	for (i = 0; i < ARRAY_SIZE(qtet_controls); i++) {
+		err = snd_ctl_add(ice->card,
+				snd_ctl_new1(&qtet_controls[i], ice));
+		if (err < 0)
+			return err;
+	}
+
+	/* Create virtual master control */
+	vmaster = snd_ctl_make_virtual_master("Master Playback Volume",
+			qtet_master_db_scale);
+	if (!vmaster)
+		return -ENOMEM;
+	add_slaves(ice->card, vmaster, slave_vols);
+	err = snd_ctl_add(ice->card, vmaster);
+	if (err < 0)
+		return err;
+	/* only capture SPDIF over AK4113 */
+	err = snd_ak4113_build(spec->ak4113,
+			ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static inline int qtet_is_spdif_master(struct snd_ice1712 *ice)
+{
+	/* CPLD_SYNC_SEL: 0 = internal, 1 = external (i.e. spdif master) */
+	return (get_cpld(ice) & CPLD_SYNC_SEL) ? 1 : 0;
+}
+
+static unsigned int qtet_get_rate(struct snd_ice1712 *ice)
+{
+	int i;
+	unsigned char result;
+
+	result =  get_cpld(ice) & CPLD_CKS_MASK;
+	for (i = 0; i < ARRAY_SIZE(cks_vals); i++)
+		if (cks_vals[i] == result)
+			return qtet_rates[i];
+	return 0;
+}
+
+static int get_cks_val(int rate)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(qtet_rates); i++)
+		if (qtet_rates[i] == rate)
+			return cks_vals[i];
+	return 0;
+}
+
+/* setting new rate */
+static void qtet_set_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+	unsigned int new;
+	unsigned char val;
+	/* switching ice1724 to external clock - supplied by ext. circuits */
+	val = inb(ICEMT1724(ice, RATE));
+	outb(val | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE));
+
+	new =  (get_cpld(ice) & ~CPLD_CKS_MASK) | get_cks_val(rate);
+	/* switch to internal clock, drop CPLD_SYNC_SEL */
+	new &= ~CPLD_SYNC_SEL;
+	/* printk(KERN_DEBUG "QT - set_rate: old %x, new %x\n",
+	   get_cpld(ice), new); */
+	set_cpld(ice, new);
+}
+
+static inline unsigned char qtet_set_mclk(struct snd_ice1712 *ice,
+		unsigned int rate)
+{
+	/* no change in master clock */
+	return 0;
+}
+
+/* setting clock to external - SPDIF */
+static int qtet_set_spdif_clock(struct snd_ice1712 *ice, int type)
+{
+	unsigned int old, new;
+
+	old = new = get_cpld(ice);
+	new &= ~(CPLD_CKS_MASK | CPLD_WORD_SEL);
+	switch (type) {
+	case EXT_SPDIF_TYPE:
+		new |= CPLD_EXT_SPDIF;
+		break;
+	case EXT_WORDCLOCK_1FS_TYPE:
+		new |= CPLD_EXT_WORDCLOCK_1FS;
+		break;
+	case EXT_WORDCLOCK_256FS_TYPE:
+		new |= CPLD_EXT_WORDCLOCK_256FS;
+		break;
+	default:
+		snd_BUG();
+	}
+	if (old != new) {
+		set_cpld(ice, new);
+		/* changed */
+		return 1;
+	}
+	return 0;
+}
+
+static int qtet_get_spdif_master_type(struct snd_ice1712 *ice)
+{
+	unsigned int val;
+	int result;
+	val = get_cpld(ice);
+	/* checking only rate/clock-related bits */
+	val &= (CPLD_CKS_MASK | CPLD_WORD_SEL | CPLD_SYNC_SEL);
+	if (!(val & CPLD_SYNC_SEL)) {
+		/* switched to internal clock, is not any external type */
+		result = -1;
+	} else {
+		switch (val) {
+		case (CPLD_EXT_SPDIF):
+			result = EXT_SPDIF_TYPE;
+			break;
+		case (CPLD_EXT_WORDCLOCK_1FS):
+			result = EXT_WORDCLOCK_1FS_TYPE;
+			break;
+		case (CPLD_EXT_WORDCLOCK_256FS):
+			result = EXT_WORDCLOCK_256FS_TYPE;
+			break;
+		default:
+			/* undefined combination of external clock setup */
+			snd_BUG();
+			result = 0;
+		}
+	}
+	return result;
+}
+
+/* Called when ak4113 detects change in the input SPDIF stream */
+static void qtet_ak4113_change(struct ak4113 *ak4113, unsigned char c0,
+		unsigned char c1)
+{
+	struct snd_ice1712 *ice = ak4113->change_callback_private;
+	int rate;
+	if ((qtet_get_spdif_master_type(ice) == EXT_SPDIF_TYPE) &&
+			c1) {
+		/* only for SPDIF master mode, rate was changed */
+		rate = snd_ak4113_external_rate(ak4113);
+		/* printk(KERN_DEBUG "ak4113 - input rate changed to %d\n",
+		   rate); */
+		qtet_akm_set_rate_val(ice->akm, rate);
+	}
+}
+
+/*
+ * If clock slaved to SPDIF-IN, setting runtime rate
+ * to the detected external rate
+ */
+static void qtet_spdif_in_open(struct snd_ice1712 *ice,
+		struct snd_pcm_substream *substream)
+{
+	struct qtet_spec *spec = ice->spec;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int rate;
+
+	if (qtet_get_spdif_master_type(ice) != EXT_SPDIF_TYPE)
+		/* not external SPDIF, no rate limitation */
+		return;
+	/* only external SPDIF can detect incoming sample rate */
+	rate = snd_ak4113_external_rate(spec->ak4113);
+	if (rate >= runtime->hw.rate_min && rate <= runtime->hw.rate_max) {
+		runtime->hw.rate_min = rate;
+		runtime->hw.rate_max = rate;
+	}
+}
+
+/*
+ * initialize the chip
+ */
+static int __devinit qtet_init(struct snd_ice1712 *ice)
+{
+	static const unsigned char ak4113_init_vals[] = {
+		/* AK4113_REG_PWRDN */	AK4113_RST | AK4113_PWN |
+			AK4113_OCKS0 | AK4113_OCKS1,
+		/* AK4113_REQ_FORMAT */	AK4113_DIF_I24I2S | AK4113_VTX |
+			AK4113_DEM_OFF | AK4113_DEAU,
+		/* AK4113_REG_IO0 */	AK4113_OPS2 | AK4113_TXE |
+			AK4113_XTL_24_576M,
+		/* AK4113_REG_IO1 */	AK4113_EFH_1024LRCLK | AK4113_IPS(0),
+		/* AK4113_REG_INT0_MASK */	0,
+		/* AK4113_REG_INT1_MASK */	0,
+		/* AK4113_REG_DATDTS */		0,
+	};
+	int err;
+	struct qtet_spec *spec;
+	struct snd_akm4xxx *ak;
+	unsigned char val;
+
+	/* switching ice1724 to external clock - supplied by ext. circuits */
+	val = inb(ICEMT1724(ice, RATE));
+	outb(val | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE));
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	/* qtet is clocked by Xilinx array */
+	ice->hw_rates = &qtet_rates_info;
+	ice->is_spdif_master = qtet_is_spdif_master;
+	ice->get_rate = qtet_get_rate;
+	ice->set_rate = qtet_set_rate;
+	ice->set_mclk = qtet_set_mclk;
+	ice->set_spdif_clock = qtet_set_spdif_clock;
+	ice->get_spdif_master_type = qtet_get_spdif_master_type;
+	ice->ext_clock_names = ext_clock_names;
+	ice->ext_clock_count = ARRAY_SIZE(ext_clock_names);
+	/* since Qtet can detect correct SPDIF-in rate, all streams can be
+	 * limited to this specific rate */
+	ice->spdif.ops.open = ice->pro_open = qtet_spdif_in_open;
+	ice->spec = spec;
+
+	/* Mute Off */
+	/* SCR Initialize*/
+	/* keep codec power down first */
+	set_scr(ice, SCR_PHP);
+	udelay(1);
+	/* codec power up */
+	set_scr(ice, SCR_PHP | SCR_CODEC_PDN);
+
+	/* MCR Initialize */
+	set_mcr(ice, 0);
+
+	/* CPLD Initialize */
+	set_cpld(ice, 0);
+
+
+	ice->num_total_dacs = 2;
+	ice->num_total_adcs = 2;
+
+	ice->akm = kcalloc(2, sizeof(struct snd_akm4xxx), GFP_KERNEL);
+	ak = ice->akm;
+	if (!ak)
+		return -ENOMEM;
+	/* only one codec with two chips */
+	ice->akm_codecs = 1;
+	err = snd_ice1712_akm4xxx_init(ak, &akm_qtet_dac, NULL, ice);
+	if (err < 0)
+		return err;
+	err = snd_ak4113_create(ice->card,
+			qtet_ak4113_read,
+			qtet_ak4113_write,
+			ak4113_init_vals,
+			ice, &spec->ak4113);
+	if (err < 0)
+		return err;
+	/* callback for codecs rate setting */
+	spec->ak4113->change_callback = qtet_ak4113_change;
+	spec->ak4113->change_callback_private = ice;
+	/* AK41143 in Quartet can detect external rate correctly
+	 * (i.e. check_flags = 0) */
+	spec->ak4113->check_flags = 0;
+
+	proc_init(ice);
+
+	qtet_set_rate(ice, 44100);
+	return 0;
+}
+
+static unsigned char qtet_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x28,	/* clock 256(24MHz), mpu401, 1xADC,
+					   1xDACs, SPDIF in */
+	[ICE_EEP2_ACLINK]      = 0x80,	/* I2S */
+	[ICE_EEP2_I2S]         = 0x78,	/* 96k, 24bit, 192k */
+	[ICE_EEP2_SPDIF]       = 0xc3,	/* out-en, out-int, in, out-ext */
+	[ICE_EEP2_GPIO_DIR]    = 0x00,	/* 0-7 inputs, switched to output
+					   only during output operations */
+	[ICE_EEP2_GPIO_DIR1]   = 0xff,  /* 8-15 outputs */
+	[ICE_EEP2_GPIO_DIR2]   = 0x00,
+	[ICE_EEP2_GPIO_MASK]   = 0xff,	/* changed only for OUT operations */
+	[ICE_EEP2_GPIO_MASK1]  = 0x00,
+	[ICE_EEP2_GPIO_MASK2]  = 0xff,
+
+	[ICE_EEP2_GPIO_STATE]  = 0x00, /* inputs */
+	[ICE_EEP2_GPIO_STATE1] = 0x7d, /* all 1, but GPIO_CPLD_RW
+					  and GPIO15 always zero */
+	[ICE_EEP2_GPIO_STATE2] = 0x00, /* inputs */
+};
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_qtet_cards[] __devinitdata = {
+	{
+		.subvendor = VT1724_SUBDEVICE_QTET,
+		.name = "Infrasonic Quartet",
+		.model = "quartet",
+		.chip_init = qtet_init,
+		.build_controls = qtet_add_controls,
+		.eeprom_size = sizeof(qtet_eeprom),
+		.eeprom_data = qtet_eeprom,
+	},
+	{ } /* terminator */
+};
diff --git a/linux/sound/pci/ice1712/quartet.h b/linux/sound/pci/ice1712/quartet.h
new file mode 100644
index 0000000..80809b7
--- /dev/null
+++ b/linux/sound/pci/ice1712/quartet.h
@@ -0,0 +1,10 @@
+#ifndef __SOUND_QTET_H
+#define __SOUND_QTET_H
+
+#define QTET_DEVICE_DESC		"{Infrasonic,Quartet},"
+
+#define VT1724_SUBDEVICE_QTET		0x30305349	/* Infrasonic Quartet */
+
+extern struct snd_ice1712_card_info  snd_vt1724_qtet_cards[];
+
+#endif	/* __SOUND_QTET_H */
diff --git a/linux/sound/pci/ice1712/revo.c b/linux/sound/pci/ice1712/revo.c
new file mode 100644
index 0000000..b508bb3
--- /dev/null
+++ b/linux/sound/pci/ice1712/revo.c
@@ -0,0 +1,633 @@
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *   Lowlevel functions for M-Audio Audiophile 192, Revolution 7.1 and 5.1
+ *
+ *	Copyright (c) 2003 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 <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "revo.h"
+
+/* a non-standard I2C device for revo51 */
+struct revo51_spec {
+	struct snd_i2c_device *dev;
+	struct snd_pt2258 *pt2258;
+};
+
+static void revo_i2s_mclk_changed(struct snd_ice1712 *ice)
+{
+	/* assert PRST# to converters; MT05 bit 7 */
+	outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD));
+	mdelay(5);
+	/* deassert PRST# */
+	outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD));
+}
+
+/*
+ * change the rate of Envy24HT, AK4355 and AK4381
+ */
+static void revo_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+	unsigned char old, tmp, dfs;
+	int reg, shift;
+
+	if (rate == 0)	/* no hint - S/PDIF input is master, simply return */
+		return;
+
+	/* adjust DFS on codecs */
+	if (rate > 96000)
+		dfs = 2;
+	else if (rate > 48000)
+		dfs = 1;
+	else
+		dfs = 0;
+
+	if (ak->type == SND_AK4355 || ak->type == SND_AK4358) {
+		reg = 2;
+		shift = 4;
+	} else {
+		reg = 1;
+		shift = 3;
+	}
+	tmp = snd_akm4xxx_get(ak, 0, reg);
+	old = (tmp >> shift) & 0x03;
+	if (old == dfs)
+		return;
+
+	/* reset DFS */
+	snd_akm4xxx_reset(ak, 1);
+	tmp = snd_akm4xxx_get(ak, 0, reg);
+	tmp &= ~(0x03 << shift);
+	tmp |= dfs << shift;
+	/* snd_akm4xxx_write(ak, 0, reg, tmp); */
+	snd_akm4xxx_set(ak, 0, reg, tmp); /* value is written in reset(0) */
+	snd_akm4xxx_reset(ak, 0);
+}
+
+/*
+ * I2C access to the PT2258 volume controller on GPIO 6/7 (Revolution 5.1)
+ */
+
+static void revo_i2c_start(struct snd_i2c_bus *bus)
+{
+	struct snd_ice1712 *ice = bus->private_data;
+	snd_ice1712_save_gpio_status(ice);
+}
+
+static void revo_i2c_stop(struct snd_i2c_bus *bus)
+{
+	struct snd_ice1712 *ice = bus->private_data;
+	snd_ice1712_restore_gpio_status(ice);
+}
+
+static void revo_i2c_direction(struct snd_i2c_bus *bus, int clock, int data)
+{
+	struct snd_ice1712 *ice = bus->private_data;
+	unsigned int mask, val;
+
+	val = 0;
+	if (clock)
+		val |= VT1724_REVO_I2C_CLOCK;	/* write SCL */
+	if (data)
+		val |= VT1724_REVO_I2C_DATA;	/* write SDA */
+	mask = VT1724_REVO_I2C_CLOCK | VT1724_REVO_I2C_DATA;
+	ice->gpio.direction &= ~mask;
+	ice->gpio.direction |= val;
+	snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+	snd_ice1712_gpio_set_mask(ice, ~mask);
+}
+
+static void revo_i2c_setlines(struct snd_i2c_bus *bus, int clk, int data)
+{
+	struct snd_ice1712 *ice = bus->private_data;
+	unsigned int val = 0;
+
+	if (clk)
+		val |= VT1724_REVO_I2C_CLOCK;
+	if (data)
+		val |= VT1724_REVO_I2C_DATA;
+	snd_ice1712_gpio_write_bits(ice,
+				    VT1724_REVO_I2C_DATA |
+				    VT1724_REVO_I2C_CLOCK, val);
+	udelay(5);
+}
+
+static int revo_i2c_getdata(struct snd_i2c_bus *bus, int ack)
+{
+	struct snd_ice1712 *ice = bus->private_data;
+	int bit;
+
+	if (ack)
+		udelay(5);
+	bit = snd_ice1712_gpio_read_bits(ice, VT1724_REVO_I2C_DATA) ? 1 : 0;
+	return bit;
+}
+
+static struct snd_i2c_bit_ops revo51_bit_ops = {
+	.start = revo_i2c_start,
+	.stop = revo_i2c_stop,
+	.direction = revo_i2c_direction,
+	.setlines = revo_i2c_setlines,
+	.getdata = revo_i2c_getdata,
+};
+
+static int revo51_i2c_init(struct snd_ice1712 *ice,
+			   struct snd_pt2258 *pt)
+{
+	struct revo51_spec *spec;
+	int err;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	ice->spec = spec;
+
+	/* create the I2C bus */
+	err = snd_i2c_bus_create(ice->card, "ICE1724 GPIO6", NULL, &ice->i2c);
+	if (err < 0)
+		return err;
+
+	ice->i2c->private_data = ice;
+	ice->i2c->hw_ops.bit = &revo51_bit_ops;
+
+	/* create the I2C device */
+	err = snd_i2c_device_create(ice->i2c, "PT2258", 0x40, &spec->dev);
+	if (err < 0)
+		return err;
+
+	pt->card = ice->card;
+	pt->i2c_bus = ice->i2c;
+	pt->i2c_dev = spec->dev;
+	spec->pt2258 = pt;
+
+	snd_pt2258_reset(pt);
+
+	return 0;
+}
+
+/*
+ * initialize the chips on M-Audio Revolution cards
+ */
+
+#define AK_DAC(xname,xch) { .name = xname, .num_channels = xch }
+
+static const struct snd_akm4xxx_dac_channel revo71_front[] = {
+	{
+		.name = "PCM Playback Volume",
+		.num_channels = 2,
+		/* front channels DAC supports muting */
+		.switch_name = "PCM Playback Switch",
+	},
+};
+
+static const struct snd_akm4xxx_dac_channel revo71_surround[] = {
+	AK_DAC("PCM Center Playback Volume", 1),
+	AK_DAC("PCM LFE Playback Volume", 1),
+	AK_DAC("PCM Side Playback Volume", 2),
+	AK_DAC("PCM Rear Playback Volume", 2),
+};
+
+static const struct snd_akm4xxx_dac_channel revo51_dac[] = {
+	AK_DAC("PCM Playback Volume", 2),
+	AK_DAC("PCM Center Playback Volume", 1),
+	AK_DAC("PCM LFE Playback Volume", 1),
+	AK_DAC("PCM Rear Playback Volume", 2),
+	AK_DAC("PCM Headphone Volume", 2),
+};
+
+static const char *revo51_adc_input_names[] = {
+	"Mic",
+	"Line",
+	"CD",
+	NULL
+};
+
+static const struct snd_akm4xxx_adc_channel revo51_adc[] = {
+	{
+		.name = "PCM Capture Volume",
+		.switch_name = "PCM Capture Switch",
+		.num_channels = 2,
+		.input_names = revo51_adc_input_names
+	},
+};
+
+static struct snd_akm4xxx akm_revo_front __devinitdata = {
+	.type = SND_AK4381,
+	.num_dacs = 2,
+	.ops = {
+		.set_rate_val = revo_set_rate_val
+	},
+	.dac_info = revo71_front,
+};
+
+static struct snd_ak4xxx_private akm_revo_front_priv __devinitdata = {
+	.caddr = 1,
+	.cif = 0,
+	.data_mask = VT1724_REVO_CDOUT,
+	.clk_mask = VT1724_REVO_CCLK,
+	.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
+	.cs_addr = VT1724_REVO_CS0 | VT1724_REVO_CS2,
+	.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
+	.add_flags = VT1724_REVO_CCLK, /* high at init */
+	.mask_flags = 0,
+};
+
+static struct snd_akm4xxx akm_revo_surround __devinitdata = {
+	.type = SND_AK4355,
+	.idx_offset = 1,
+	.num_dacs = 6,
+	.ops = {
+		.set_rate_val = revo_set_rate_val
+	},
+	.dac_info = revo71_surround,
+};
+
+static struct snd_ak4xxx_private akm_revo_surround_priv __devinitdata = {
+	.caddr = 3,
+	.cif = 0,
+	.data_mask = VT1724_REVO_CDOUT,
+	.clk_mask = VT1724_REVO_CCLK,
+	.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
+	.cs_addr = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+	.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
+	.add_flags = VT1724_REVO_CCLK, /* high at init */
+	.mask_flags = 0,
+};
+
+static struct snd_akm4xxx akm_revo51 __devinitdata = {
+	.type = SND_AK4358,
+	.num_dacs = 8,
+	.ops = {
+		.set_rate_val = revo_set_rate_val
+	},
+	.dac_info = revo51_dac,
+};
+
+static struct snd_ak4xxx_private akm_revo51_priv __devinitdata = {
+	.caddr = 2,
+	.cif = 0,
+	.data_mask = VT1724_REVO_CDOUT,
+	.clk_mask = VT1724_REVO_CCLK,
+	.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+	.cs_addr = VT1724_REVO_CS1,
+	.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+	.add_flags = VT1724_REVO_CCLK, /* high at init */
+	.mask_flags = 0,
+};
+
+static struct snd_akm4xxx akm_revo51_adc __devinitdata = {
+	.type = SND_AK5365,
+	.num_adcs = 2,
+	.adc_info = revo51_adc,
+};
+
+static struct snd_ak4xxx_private akm_revo51_adc_priv __devinitdata = {
+	.caddr = 2,
+	.cif = 0,
+	.data_mask = VT1724_REVO_CDOUT,
+	.clk_mask = VT1724_REVO_CCLK,
+	.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+	.cs_addr = VT1724_REVO_CS0,
+	.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+	.add_flags = VT1724_REVO_CCLK, /* high at init */
+	.mask_flags = 0,
+};
+
+static struct snd_pt2258 ptc_revo51_volume;
+
+/* AK4358 for AP192 DAC, AK5385A for ADC */
+static void ap192_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+	struct snd_ice1712 *ice = ak->private_data[0];
+	int dfs;
+
+	revo_set_rate_val(ak, rate);
+
+	/* reset CKS */
+	snd_ice1712_gpio_write_bits(ice, 1 << 8, rate > 96000 ? 1 << 8 : 0);
+	/* reset DFS pins of AK5385A for ADC, too */
+	if (rate > 96000)
+		dfs = 2;
+	else if (rate > 48000)
+		dfs = 1;
+	else
+		dfs = 0;
+	snd_ice1712_gpio_write_bits(ice, 3 << 9, dfs << 9);
+	/* reset ADC */
+	snd_ice1712_gpio_write_bits(ice, 1 << 11, 0);
+	snd_ice1712_gpio_write_bits(ice, 1 << 11, 1 << 11);
+}
+
+static const struct snd_akm4xxx_dac_channel ap192_dac[] = {
+	AK_DAC("PCM Playback Volume", 2)
+};
+
+static struct snd_akm4xxx akm_ap192 __devinitdata = {
+	.type = SND_AK4358,
+	.num_dacs = 2,
+	.ops = {
+		.set_rate_val = ap192_set_rate_val
+	},
+	.dac_info = ap192_dac,
+};
+
+static struct snd_ak4xxx_private akm_ap192_priv __devinitdata = {
+	.caddr = 2,
+	.cif = 0,
+	.data_mask = VT1724_REVO_CDOUT,
+	.clk_mask = VT1724_REVO_CCLK,
+	.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+	.cs_addr = VT1724_REVO_CS1,
+	.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+	.add_flags = VT1724_REVO_CCLK, /* high at init */
+	.mask_flags = 0,
+};
+
+/* AK4114 support on Audiophile 192 */
+/* CDTO (pin 32) -- GPIO2 pin 52
+ * CDTI (pin 33) -- GPIO3 pin 53 (shared with AK4358)
+ * CCLK (pin 34) -- GPIO1 pin 51 (shared with AK4358)
+ * CSN  (pin 35) -- GPIO7 pin 59
+ */
+#define AK4114_ADDR	0x02
+
+static void write_data(struct snd_ice1712 *ice, unsigned int gpio,
+		       unsigned int data, int idx)
+{
+	for (; idx >= 0; idx--) {
+		/* drop clock */
+		gpio &= ~VT1724_REVO_CCLK;
+		snd_ice1712_gpio_write(ice, gpio);
+		udelay(1);
+		/* set data */
+		if (data & (1 << idx))
+			gpio |= VT1724_REVO_CDOUT;
+		else
+			gpio &= ~VT1724_REVO_CDOUT;
+		snd_ice1712_gpio_write(ice, gpio);
+		udelay(1);
+		/* raise clock */
+		gpio |= VT1724_REVO_CCLK;
+		snd_ice1712_gpio_write(ice, gpio);
+		udelay(1);
+	}
+}
+
+static unsigned char read_data(struct snd_ice1712 *ice, unsigned int gpio,
+			       int idx)
+{
+	unsigned char data = 0;
+
+	for (; idx >= 0; idx--) {
+		/* drop clock */
+		gpio &= ~VT1724_REVO_CCLK;
+		snd_ice1712_gpio_write(ice, gpio);
+		udelay(1);
+		/* read data */
+		if (snd_ice1712_gpio_read(ice) & VT1724_REVO_CDIN)
+			data |= (1 << idx);
+		udelay(1);
+		/* raise clock */
+		gpio |= VT1724_REVO_CCLK;
+		snd_ice1712_gpio_write(ice, gpio);
+		udelay(1);
+	}
+	return data;
+}
+
+static unsigned int ap192_4wire_start(struct snd_ice1712 *ice)
+{
+	unsigned int tmp;
+
+	snd_ice1712_save_gpio_status(ice);
+	tmp = snd_ice1712_gpio_read(ice);
+	tmp |= VT1724_REVO_CCLK; /* high at init */
+	tmp |= VT1724_REVO_CS0;
+	tmp &= ~VT1724_REVO_CS1;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+	return tmp;
+}
+
+static void ap192_4wire_finish(struct snd_ice1712 *ice, unsigned int tmp)
+{
+	tmp |= VT1724_REVO_CS1;
+	tmp |= VT1724_REVO_CS0;
+	snd_ice1712_gpio_write(ice, tmp);
+	udelay(1);
+	snd_ice1712_restore_gpio_status(ice);
+}
+
+static void ap192_ak4114_write(void *private_data, unsigned char addr,
+			       unsigned char data)
+{
+	struct snd_ice1712 *ice = private_data;
+	unsigned int tmp, addrdata;
+
+	tmp = ap192_4wire_start(ice);
+	addrdata = (AK4114_ADDR << 6) | 0x20 | (addr & 0x1f);
+	addrdata = (addrdata << 8) | data;
+	write_data(ice, tmp, addrdata, 15);
+	ap192_4wire_finish(ice, tmp);
+}
+
+static unsigned char ap192_ak4114_read(void *private_data, unsigned char addr)
+{
+	struct snd_ice1712 *ice = private_data;
+	unsigned int tmp;
+	unsigned char data;
+
+	tmp = ap192_4wire_start(ice);
+	write_data(ice, tmp, (AK4114_ADDR << 6) | (addr & 0x1f), 7);
+	data = read_data(ice, tmp, 7);
+	ap192_4wire_finish(ice, tmp);
+	return data;
+}
+
+static int __devinit ap192_ak4114_init(struct snd_ice1712 *ice)
+{
+	static const unsigned char ak4114_init_vals[] = {
+		AK4114_RST | AK4114_PWN | AK4114_OCKS0 | AK4114_OCKS1,
+		AK4114_DIF_I24I2S,
+		AK4114_TX1E,
+		AK4114_EFH_1024 | AK4114_DIT | AK4114_IPS(1),
+		0,
+		0
+	};
+	static const unsigned char ak4114_init_txcsb[] = {
+		0x41, 0x02, 0x2c, 0x00, 0x00
+	};
+	struct ak4114 *ak;
+	int err;
+
+	err = snd_ak4114_create(ice->card,
+				 ap192_ak4114_read,
+				 ap192_ak4114_write,
+				 ak4114_init_vals, ak4114_init_txcsb,
+				 ice, &ak);
+	/* AK4114 in Revo cannot detect external rate correctly.
+	 * No reason to stop capture stream due to incorrect checks */
+	ak->check_flags = AK4114_CHECK_NO_RATE;
+
+	return 0; /* error ignored; it's no fatal error */
+}
+
+static int __devinit revo_init(struct snd_ice1712 *ice)
+{
+	struct snd_akm4xxx *ak;
+	int err;
+
+	/* determine I2C, DACs and ADCs */
+	switch (ice->eeprom.subvendor) {
+	case VT1724_SUBDEVICE_REVOLUTION71:
+		ice->num_total_dacs = 8;
+		ice->num_total_adcs = 2;
+		ice->gpio.i2s_mclk_changed = revo_i2s_mclk_changed;
+		break;
+	case VT1724_SUBDEVICE_REVOLUTION51:
+		ice->num_total_dacs = 8;
+		ice->num_total_adcs = 2;
+		break;
+	case VT1724_SUBDEVICE_AUDIOPHILE192:
+		ice->num_total_dacs = 2;
+		ice->num_total_adcs = 2;
+		break;
+	default:
+		snd_BUG();
+		return -EINVAL;
+	}
+
+	/* second stage of initialization, analog parts and others */
+	ak = ice->akm = kcalloc(2, sizeof(struct snd_akm4xxx), GFP_KERNEL);
+	if (! ak)
+		return -ENOMEM;
+	switch (ice->eeprom.subvendor) {
+	case VT1724_SUBDEVICE_REVOLUTION71:
+		ice->akm_codecs = 2;
+		err = snd_ice1712_akm4xxx_init(ak, &akm_revo_front,
+						&akm_revo_front_priv, ice);
+		if (err < 0)
+			return err;
+		err = snd_ice1712_akm4xxx_init(ak+1, &akm_revo_surround,
+						&akm_revo_surround_priv, ice);
+		if (err < 0)
+			return err;
+		/* unmute all codecs */
+		snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE,
+						VT1724_REVO_MUTE);
+		break;
+	case VT1724_SUBDEVICE_REVOLUTION51:
+		ice->akm_codecs = 2;
+		err = snd_ice1712_akm4xxx_init(ak, &akm_revo51,
+					       &akm_revo51_priv, ice);
+		if (err < 0)
+			return err;
+		err = snd_ice1712_akm4xxx_init(ak+1, &akm_revo51_adc,
+					       &akm_revo51_adc_priv, ice);
+		if (err < 0)
+			return err;
+		err = revo51_i2c_init(ice, &ptc_revo51_volume);
+		if (err < 0)
+			return err;
+		/* unmute all codecs */
+		snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE,
+					    VT1724_REVO_MUTE);
+		break;
+	case VT1724_SUBDEVICE_AUDIOPHILE192:
+		ice->akm_codecs = 1;
+		err = snd_ice1712_akm4xxx_init(ak, &akm_ap192, &akm_ap192_priv,
+					       ice);
+		if (err < 0)
+			return err;
+		
+		/* unmute all codecs */
+		snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE,
+					    VT1724_REVO_MUTE);
+		break;
+	}
+
+	return 0;
+}
+
+
+static int __devinit revo_add_controls(struct snd_ice1712 *ice)
+{
+	struct revo51_spec *spec;
+	int err;
+
+	switch (ice->eeprom.subvendor) {
+	case VT1724_SUBDEVICE_REVOLUTION71:
+		err = snd_ice1712_akm4xxx_build_controls(ice);
+		if (err < 0)
+			return err;
+		break;
+	case VT1724_SUBDEVICE_REVOLUTION51:
+		err = snd_ice1712_akm4xxx_build_controls(ice);
+		if (err < 0)
+			return err;
+		spec = ice->spec;
+		err = snd_pt2258_build_controls(spec->pt2258);
+		if (err < 0)
+			return err;
+		break;
+	case VT1724_SUBDEVICE_AUDIOPHILE192:
+		err = snd_ice1712_akm4xxx_build_controls(ice);
+		if (err < 0)
+			return err;
+		err = ap192_ak4114_init(ice);
+		if (err < 0)
+			return err;
+		break;
+	}
+	return 0;
+}
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_revo_cards[] __devinitdata = {
+	{
+		.subvendor = VT1724_SUBDEVICE_REVOLUTION71,
+		.name = "M Audio Revolution-7.1",
+		.model = "revo71",
+		.chip_init = revo_init,
+		.build_controls = revo_add_controls,
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_REVOLUTION51,
+		.name = "M Audio Revolution-5.1",
+		.model = "revo51",
+		.chip_init = revo_init,
+		.build_controls = revo_add_controls,
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_AUDIOPHILE192,
+		.name = "M Audio Audiophile192",
+		.model = "ap192",
+		.chip_init = revo_init,
+		.build_controls = revo_add_controls,
+	},
+	{ } /* terminator */
+};
diff --git a/linux/sound/pci/ice1712/revo.h b/linux/sound/pci/ice1712/revo.h
new file mode 100644
index 0000000..a3ba425
--- /dev/null
+++ b/linux/sound/pci/ice1712/revo.h
@@ -0,0 +1,55 @@
+#ifndef __SOUND_REVO_H
+#define __SOUND_REVO_H
+
+/*
+ *   ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ *   Lowlevel functions for M-Audio Revolution 7.1
+ *
+ *	Copyright (c) 2003 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
+ *
+ */      
+
+#define REVO_DEVICE_DESC \
+		"{MidiMan M Audio,Revolution 7.1},"\
+		"{MidiMan M Audio,Revolution 5.1},"\
+		"{MidiMan M Audio,Audiophile 192},"
+
+#define VT1724_SUBDEVICE_REVOLUTION71	0x12143036
+#define VT1724_SUBDEVICE_REVOLUTION51	0x12143136
+#define VT1724_SUBDEVICE_AUDIOPHILE192	0x12143236
+
+/* entry point */
+extern struct snd_ice1712_card_info snd_vt1724_revo_cards[];
+
+
+/*
+ *  MidiMan M-Audio Revolution GPIO definitions
+ */
+
+#define VT1724_REVO_CCLK	0x02
+#define VT1724_REVO_CDIN	0x04	/* not used */
+#define VT1724_REVO_CDOUT	0x08
+#define VT1724_REVO_CS0		0x10	/* AK5365 chipselect for (revo51) */
+#define VT1724_REVO_CS1		0x20	/* front AKM4381 chipselect */
+#define VT1724_REVO_CS2		0x40	/* surround AKM4355 CS (revo71) */
+#define VT1724_REVO_I2C_DATA    0x40    /* I2C: PT 2258 SDA (on revo51) */
+#define VT1724_REVO_I2C_CLOCK   0x80    /* I2C: PT 2258 SCL (on revo51) */
+#define VT1724_REVO_CS3		0x80	/* AK4114 for AP192 */
+#define VT1724_REVO_MUTE	(1<<22)	/* 0 = all mute, 1 = normal operation */
+
+#endif /* __SOUND_REVO_H */
diff --git a/linux/sound/pci/ice1712/se.c b/linux/sound/pci/ice1712/se.c
new file mode 100644
index 0000000..69673b9
--- /dev/null
+++ b/linux/sound/pci/ice1712/se.c
@@ -0,0 +1,774 @@
+/*
+ *   ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ *   Lowlevel functions for ONKYO WAVIO SE-90PCI and SE-200PCI
+ *
+ *	Copyright (c) 2007 Shin-ya Okada  sh_okada(at)d4.dion.ne.jp
+ *                                        (at) -> @
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "se.h"
+
+struct se_spec {
+	struct {
+		unsigned char ch1, ch2;
+	} vol[8];
+};
+
+/****************************************************************************/
+/*  ONKYO WAVIO SE-200PCI                                                   */
+/****************************************************************************/
+/*
+ *  system configuration ICE_EEP2_SYSCONF=0x4b
+ *    XIN1 49.152MHz
+ *    not have UART
+ *    one stereo ADC and a S/PDIF receiver connected
+ *    four stereo DACs connected
+ *
+ *  AC-Link configuration ICE_EEP2_ACLINK=0x80
+ *    use I2C, not use AC97
+ *
+ *  I2S converters feature ICE_EEP2_I2S=0x78
+ *    I2S codec has no volume/mute control feature
+ *    I2S codec supports 96KHz and 192KHz
+ *    I2S codec 24bits
+ *
+ *  S/PDIF configuration ICE_EEP2_SPDIF=0xc3
+ *    Enable integrated S/PDIF transmitter
+ *    internal S/PDIF out implemented
+ *    S/PDIF is stereo
+ *    External S/PDIF out implemented
+ *
+ *
+ * ** connected chips **
+ *
+ *  WM8740
+ *      A 2ch-DAC of main outputs.
+ *      It setuped as I2S mode by wire, so no way to setup from software.
+ *      The sample-rate are automatically changed. 
+ *          ML/I2S (28pin) --------+
+ *          MC/DM1 (27pin) -- 5V   |
+ *          MD/DM0 (26pin) -- GND  |
+ *          MUTEB  (25pin) -- NC   |
+ *          MODE   (24pin) -- GND  |
+ *          CSBIW  (23pin) --------+
+ *                                 |
+ *          RSTB   (22pin) --R(1K)-+
+ *      Probably it reduce the noise from the control line.
+ *
+ *  WM8766
+ *      A 6ch-DAC for surrounds.
+ *      It's control wire was connected to GPIOxx (3-wire serial interface)
+ *          ML/I2S (11pin) -- GPIO18
+ *          MC/IWL (12pin) -- GPIO17
+ *          MD/DM  (13pin) -- GPIO16
+ *          MUTE   (14pin) -- GPIO01
+ *
+ *  WM8776
+ *     A 2ch-ADC(with 10ch-selector) plus 2ch-DAC.
+ *     It's control wire was connected to SDA/SCLK (2-wire serial interface)
+ *          MODE (16pin) -- R(1K) -- GND
+ *          CE   (17pin) -- R(1K) -- GND  2-wire mode (address=0x34)
+ *          DI   (18pin) -- SDA
+ *          CL   (19pin) -- SCLK
+ *
+ *
+ * ** output pins and device names **
+ *
+ *   7.1ch name -- output connector color -- device (-D option)
+ *
+ *      FRONT 2ch                  -- green  -- plughw:0,0
+ *      CENTER(Lch) SUBWOOFER(Rch) -- black  -- plughw:0,2,0
+ *      SURROUND 2ch               -- orange -- plughw:0,2,1
+ *      SURROUND BACK 2ch          -- white  -- plughw:0,2,2
+ *
+ */
+
+
+/****************************************************************************/
+/*  WM8740 interface                                                        */
+/****************************************************************************/
+
+static void __devinit se200pci_WM8740_init(struct snd_ice1712 *ice)
+{
+	/* nothing to do */
+}
+
+
+static void se200pci_WM8740_set_pro_rate(struct snd_ice1712 *ice,
+						unsigned int rate)
+{
+	/* nothing to do */
+}
+
+
+/****************************************************************************/
+/*  WM8766 interface                                                        */
+/****************************************************************************/
+
+static void se200pci_WM8766_write(struct snd_ice1712 *ice,
+					unsigned int addr, unsigned int data)
+{
+	unsigned int st;
+	unsigned int bits;
+	int i;
+	const unsigned int DATA  = 0x010000;
+	const unsigned int CLOCK = 0x020000;
+	const unsigned int LOAD  = 0x040000;
+	const unsigned int ALL_MASK = (DATA | CLOCK | LOAD);
+
+	snd_ice1712_save_gpio_status(ice);
+
+	st = ((addr & 0x7f) << 9) | (data & 0x1ff);
+	snd_ice1712_gpio_set_dir(ice, ice->gpio.direction | ALL_MASK);
+	snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask & ~ALL_MASK);
+	bits = snd_ice1712_gpio_read(ice) & ~ALL_MASK;
+
+	snd_ice1712_gpio_write(ice, bits);
+	for (i = 0; i < 16; i++) {
+		udelay(1);
+		bits &= ~CLOCK;
+		st = (st << 1);
+		if (st & 0x10000)
+			bits |= DATA;
+		else
+			bits &= ~DATA;
+
+		snd_ice1712_gpio_write(ice, bits);
+
+		udelay(1);
+		bits |= CLOCK;
+		snd_ice1712_gpio_write(ice, bits);
+	}
+
+	udelay(1);
+	bits |= LOAD;
+	snd_ice1712_gpio_write(ice, bits);
+
+	udelay(1);
+	bits |= (DATA | CLOCK);
+	snd_ice1712_gpio_write(ice, bits);
+
+	snd_ice1712_restore_gpio_status(ice);
+}
+
+static void se200pci_WM8766_set_volume(struct snd_ice1712 *ice, int ch,
+					unsigned int vol1, unsigned int vol2)
+{
+	switch (ch) {
+	case 0:
+		se200pci_WM8766_write(ice, 0x000, vol1);
+		se200pci_WM8766_write(ice, 0x001, vol2 | 0x100);
+		break;
+	case 1:
+		se200pci_WM8766_write(ice, 0x004, vol1);
+		se200pci_WM8766_write(ice, 0x005, vol2 | 0x100);
+		break;
+	case 2:
+		se200pci_WM8766_write(ice, 0x006, vol1);
+		se200pci_WM8766_write(ice, 0x007, vol2 | 0x100);
+		break;
+	}
+}
+
+static void __devinit se200pci_WM8766_init(struct snd_ice1712 *ice)
+{
+	se200pci_WM8766_write(ice, 0x1f, 0x000); /* RESET ALL */
+	udelay(10);
+
+	se200pci_WM8766_set_volume(ice, 0, 0, 0); /* volume L=0 R=0 */
+	se200pci_WM8766_set_volume(ice, 1, 0, 0); /* volume L=0 R=0 */
+	se200pci_WM8766_set_volume(ice, 2, 0, 0); /* volume L=0 R=0 */
+
+	se200pci_WM8766_write(ice, 0x03, 0x022); /* serial mode I2S-24bits */
+	se200pci_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */
+	se200pci_WM8766_write(ice, 0x12, 0x000); /* MDP=0 */
+	se200pci_WM8766_write(ice, 0x15, 0x000); /* MDP=0 */
+	se200pci_WM8766_write(ice, 0x09, 0x000); /* demp=off mute=off */
+
+	se200pci_WM8766_write(ice, 0x02, 0x124); /* ch-assign L=L R=R RESET */
+	se200pci_WM8766_write(ice, 0x02, 0x120); /* ch-assign L=L R=R */
+}
+
+static void se200pci_WM8766_set_pro_rate(struct snd_ice1712 *ice,
+					unsigned int rate)
+{
+	if (rate > 96000)
+		se200pci_WM8766_write(ice, 0x0a, 0x000); /* MCLK=128fs */
+	else
+		se200pci_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */
+}
+
+
+/****************************************************************************/
+/*  WM8776 interface                                                        */
+/****************************************************************************/
+
+static void se200pci_WM8776_write(struct snd_ice1712 *ice,
+					unsigned int addr, unsigned int data)
+{
+	unsigned int val;
+
+	val = (addr << 9) | data;
+	snd_vt1724_write_i2c(ice, 0x34, val >> 8, val & 0xff);
+}
+
+
+static void se200pci_WM8776_set_output_volume(struct snd_ice1712 *ice,
+					unsigned int vol1, unsigned int vol2)
+{
+	se200pci_WM8776_write(ice, 0x03, vol1);
+	se200pci_WM8776_write(ice, 0x04, vol2 | 0x100);
+}
+
+static void se200pci_WM8776_set_input_volume(struct snd_ice1712 *ice,
+					unsigned int vol1, unsigned int vol2)
+{
+	se200pci_WM8776_write(ice, 0x0e, vol1);
+	se200pci_WM8776_write(ice, 0x0f, vol2 | 0x100);
+}
+
+static const char *se200pci_sel[] = {
+	"LINE-IN", "CD-IN", "MIC-IN", "ALL-MIX", NULL
+};
+
+static void se200pci_WM8776_set_input_selector(struct snd_ice1712 *ice,
+					       unsigned int sel)
+{
+	static unsigned char vals[] = {
+		/* LINE, CD, MIC, ALL, GND */
+		0x10, 0x04, 0x08, 0x1c, 0x03
+	};
+	if (sel > 4)
+		sel = 4;
+	se200pci_WM8776_write(ice, 0x15, vals[sel]);
+}
+
+static void se200pci_WM8776_set_afl(struct snd_ice1712 *ice, unsigned int afl)
+{
+	/* AFL -- After Fader Listening */
+	if (afl)
+		se200pci_WM8776_write(ice, 0x16, 0x005);
+	else
+		se200pci_WM8776_write(ice, 0x16, 0x001);
+}
+
+static const char *se200pci_agc[] = {
+	"Off", "LimiterMode", "ALCMode", NULL
+};
+
+static void se200pci_WM8776_set_agc(struct snd_ice1712 *ice, unsigned int agc)
+{
+	/* AGC -- Auto Gain Control of the input */
+	switch (agc) {
+	case 0:
+		se200pci_WM8776_write(ice, 0x11, 0x000); /* Off */
+		break;
+	case 1:
+		se200pci_WM8776_write(ice, 0x10, 0x07b);
+		se200pci_WM8776_write(ice, 0x11, 0x100); /* LimiterMode */
+		break;
+	case 2:
+		se200pci_WM8776_write(ice, 0x10, 0x1fb);
+		se200pci_WM8776_write(ice, 0x11, 0x100); /* ALCMode */
+		break;
+	}
+}
+
+static void __devinit se200pci_WM8776_init(struct snd_ice1712 *ice)
+{
+	int i;
+	static unsigned short __devinitdata default_values[] = {
+		0x100, 0x100, 0x100,
+		0x100, 0x100, 0x100,
+		0x000, 0x090, 0x000, 0x000,
+		0x022, 0x022, 0x022,
+		0x008, 0x0cf, 0x0cf, 0x07b, 0x000,
+		0x032, 0x000, 0x0a6, 0x001, 0x001
+	};
+
+	se200pci_WM8776_write(ice, 0x17, 0x000); /* reset all */
+	/* ADC and DAC interface is I2S 24bits mode */
+ 	/* The sample-rate are automatically changed */
+	udelay(10);
+	/* BUT my board can not do reset all, so I load all by manually. */
+	for (i = 0; i < ARRAY_SIZE(default_values); i++)
+		se200pci_WM8776_write(ice, i, default_values[i]);
+
+	se200pci_WM8776_set_input_selector(ice, 0);
+	se200pci_WM8776_set_afl(ice, 0);
+	se200pci_WM8776_set_agc(ice, 0);
+	se200pci_WM8776_set_input_volume(ice, 0, 0);
+	se200pci_WM8776_set_output_volume(ice, 0, 0);
+
+	/* head phone mute and power down */
+	se200pci_WM8776_write(ice, 0x00, 0);
+	se200pci_WM8776_write(ice, 0x01, 0);
+	se200pci_WM8776_write(ice, 0x02, 0x100);
+	se200pci_WM8776_write(ice, 0x0d, 0x080);
+}
+
+static void se200pci_WM8776_set_pro_rate(struct snd_ice1712 *ice,
+						unsigned int rate)
+{
+	/* nothing to do */
+}
+
+
+/****************************************************************************/
+/*  runtime interface                                                       */
+/****************************************************************************/
+
+static void se200pci_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+	se200pci_WM8740_set_pro_rate(ice, rate);
+	se200pci_WM8766_set_pro_rate(ice, rate);
+	se200pci_WM8776_set_pro_rate(ice, rate);
+}
+
+struct se200pci_control {
+	char *name;
+	enum {
+		WM8766,
+		WM8776in,
+		WM8776out,
+		WM8776sel,
+		WM8776agc,
+		WM8776afl
+	} target;
+	enum { VOLUME1, VOLUME2, BOOLEAN, ENUM } type;
+	int ch;
+	const char **member;
+	const char *comment;
+};
+
+static const struct se200pci_control se200pci_cont[] = {
+	{
+		.name = "Front Playback Volume",
+		.target = WM8776out,
+		.type = VOLUME1,
+		.comment = "Front(green)"
+	},
+	{
+		.name = "Side Playback Volume",
+		.target = WM8766,
+		.type = VOLUME1,
+		.ch = 1,
+		.comment = "Surround(orange)"
+	},
+	{
+		.name = "Surround Playback Volume",
+		.target = WM8766,
+		.type = VOLUME1,
+		.ch = 2,
+		.comment = "SurroundBack(white)"
+	},
+	{
+		.name = "CLFE Playback Volume",
+		.target = WM8766,
+		.type = VOLUME1,
+		.ch = 0,
+		.comment = "Center(Lch)&SubWoofer(Rch)(black)"
+	},
+	{
+		.name = "Capture Volume",
+		.target = WM8776in,
+		.type = VOLUME2
+	},
+	{
+		.name = "Capture Select",
+		.target = WM8776sel,
+		.type = ENUM,
+		.member = se200pci_sel
+	},
+	{
+		.name = "AGC Capture Mode",
+		.target = WM8776agc,
+		.type = ENUM,
+		.member = se200pci_agc
+	},
+	{
+		.name = "AFL Bypass Playback Switch",
+		.target = WM8776afl,
+		.type = BOOLEAN
+	}
+};
+
+static int se200pci_get_enum_count(int n)
+{
+	const char **member;
+	int c;
+
+	member = se200pci_cont[n].member;
+	if (!member)
+		return 0;
+	for (c = 0; member[c]; c++)
+		;
+	return c;
+}
+
+static int se200pci_cont_volume_info(struct snd_kcontrol *kc,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0; /* mute */
+	uinfo->value.integer.max = 0xff; /* 0dB */
+	return 0;
+}
+
+#define se200pci_cont_boolean_info	snd_ctl_boolean_mono_info
+
+static int se200pci_cont_enum_info(struct snd_kcontrol *kc,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	int n, c;
+
+	n = kc->private_value;
+	c = se200pci_get_enum_count(n);
+	if (!c)
+		return -EINVAL;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = c;
+	if (uinfo->value.enumerated.item >= c)
+		uinfo->value.enumerated.item = c - 1;
+	strcpy(uinfo->value.enumerated.name,
+	       se200pci_cont[n].member[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int se200pci_cont_volume_get(struct snd_kcontrol *kc,
+				    struct snd_ctl_elem_value *uc)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+	struct se_spec *spec = ice->spec;
+	int n = kc->private_value;
+	uc->value.integer.value[0] = spec->vol[n].ch1;
+	uc->value.integer.value[1] = spec->vol[n].ch2;
+	return 0;
+}
+
+static int se200pci_cont_boolean_get(struct snd_kcontrol *kc,
+				     struct snd_ctl_elem_value *uc)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+	struct se_spec *spec = ice->spec;
+	int n = kc->private_value;
+	uc->value.integer.value[0] = spec->vol[n].ch1;
+	return 0;
+}
+
+static int se200pci_cont_enum_get(struct snd_kcontrol *kc,
+				  struct snd_ctl_elem_value *uc)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+	struct se_spec *spec = ice->spec;
+	int n = kc->private_value;
+	uc->value.enumerated.item[0] = spec->vol[n].ch1;
+	return 0;
+}
+
+static void se200pci_cont_update(struct snd_ice1712 *ice, int n)
+{
+	struct se_spec *spec = ice->spec;
+	switch (se200pci_cont[n].target) {
+	case WM8766:
+		se200pci_WM8766_set_volume(ice,
+					   se200pci_cont[n].ch,
+					   spec->vol[n].ch1,
+					   spec->vol[n].ch2);
+		break;
+
+	case WM8776in:
+		se200pci_WM8776_set_input_volume(ice,
+						 spec->vol[n].ch1,
+						 spec->vol[n].ch2);
+		break;
+
+	case WM8776out:
+		se200pci_WM8776_set_output_volume(ice,
+						  spec->vol[n].ch1,
+						  spec->vol[n].ch2);
+		break;
+
+	case WM8776sel:
+		se200pci_WM8776_set_input_selector(ice,
+						   spec->vol[n].ch1);
+		break;
+
+	case WM8776agc:
+		se200pci_WM8776_set_agc(ice, spec->vol[n].ch1);
+		break;
+
+	case WM8776afl:
+		se200pci_WM8776_set_afl(ice, spec->vol[n].ch1);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static int se200pci_cont_volume_put(struct snd_kcontrol *kc,
+				    struct snd_ctl_elem_value *uc)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+	struct se_spec *spec = ice->spec;
+	int n = kc->private_value;
+	unsigned int vol1, vol2;
+	int changed;
+
+	changed = 0;
+	vol1 = uc->value.integer.value[0] & 0xff;
+	vol2 = uc->value.integer.value[1] & 0xff;
+	if (spec->vol[n].ch1 != vol1) {
+		spec->vol[n].ch1 = vol1;
+		changed = 1;
+	}
+	if (spec->vol[n].ch2 != vol2) {
+		spec->vol[n].ch2 = vol2;
+		changed = 1;
+	}
+	if (changed)
+		se200pci_cont_update(ice, n);
+
+	return changed;
+}
+
+static int se200pci_cont_boolean_put(struct snd_kcontrol *kc,
+				     struct snd_ctl_elem_value *uc)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+	struct se_spec *spec = ice->spec;
+	int n = kc->private_value;
+	unsigned int vol1;
+
+	vol1 = !!uc->value.integer.value[0];
+	if (spec->vol[n].ch1 != vol1) {
+		spec->vol[n].ch1 = vol1;
+		se200pci_cont_update(ice, n);
+		return 1;
+	}
+	return 0;
+}
+
+static int se200pci_cont_enum_put(struct snd_kcontrol *kc,
+				  struct snd_ctl_elem_value *uc)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+	struct se_spec *spec = ice->spec;
+	int n = kc->private_value;
+	unsigned int vol1;
+
+	vol1 = uc->value.enumerated.item[0];
+	if (vol1 >= se200pci_get_enum_count(n))
+		return -EINVAL;
+	if (spec->vol[n].ch1 != vol1) {
+		spec->vol[n].ch1 = vol1;
+		se200pci_cont_update(ice, n);
+		return 1;
+	}
+	return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_gain1, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(db_scale_gain2, -10350, 50, 1);
+
+static int __devinit se200pci_add_controls(struct snd_ice1712 *ice)
+{
+	int i;
+	struct snd_kcontrol_new cont;
+	int err;
+
+	memset(&cont, 0, sizeof(cont));
+	cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	for (i = 0; i < ARRAY_SIZE(se200pci_cont); i++) {
+		cont.private_value = i;
+		cont.name = se200pci_cont[i].name;
+		cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+		cont.tlv.p = NULL;
+		switch (se200pci_cont[i].type) {
+		case VOLUME1:
+		case VOLUME2:
+			cont.info = se200pci_cont_volume_info;
+			cont.get = se200pci_cont_volume_get;
+			cont.put = se200pci_cont_volume_put;
+			cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+			if (se200pci_cont[i].type == VOLUME1)
+				cont.tlv.p = db_scale_gain1;
+			else
+				cont.tlv.p = db_scale_gain2;
+			break;
+		case BOOLEAN:
+			cont.info = se200pci_cont_boolean_info;
+			cont.get = se200pci_cont_boolean_get;
+			cont.put = se200pci_cont_boolean_put;
+			break;
+		case ENUM:
+			cont.info = se200pci_cont_enum_info;
+			cont.get = se200pci_cont_enum_get;
+			cont.put = se200pci_cont_enum_put;
+			break;
+		default:
+			snd_BUG();
+			return -EINVAL;
+		}
+		err = snd_ctl_add(ice->card, snd_ctl_new1(&cont, ice));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+
+/****************************************************************************/
+/*  ONKYO WAVIO SE-90PCI                                                    */
+/****************************************************************************/
+/*
+ *  system configuration ICE_EEP2_SYSCONF=0x4b
+ *  AC-Link configuration ICE_EEP2_ACLINK=0x80
+ *  I2S converters feature ICE_EEP2_I2S=0x78
+ *  S/PDIF configuration ICE_EEP2_SPDIF=0xc3
+ *
+ *  ** connected chip **
+ *
+ *   WM8716
+ *      A 2ch-DAC of main outputs.
+ *      It setuped as I2S mode by wire, so no way to setup from software.
+ *         ML/I2S (28pin) -- +5V
+ *         MC/DM1 (27pin) -- GND
+ *         MC/DM0 (26pin) -- GND
+ *         MUTEB  (25pin) -- open (internal pull-up)
+ *         MODE   (24pin) -- GND
+ *         CSBIWO (23pin) -- +5V
+ *
+ */
+
+ /* Nothing to do for this chip. */
+
+
+/****************************************************************************/
+/*  probe/initialize/setup                                                  */
+/****************************************************************************/
+
+static int __devinit se_init(struct snd_ice1712 *ice)
+{
+	struct se_spec *spec;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+	ice->spec = spec;
+
+	if (ice->eeprom.subvendor == VT1724_SUBDEVICE_SE90PCI) {
+		ice->num_total_dacs = 2;
+		ice->num_total_adcs = 0;
+		ice->vt1720 = 1;
+		return 0;
+
+	} else if (ice->eeprom.subvendor == VT1724_SUBDEVICE_SE200PCI) {
+		ice->num_total_dacs = 8;
+		ice->num_total_adcs = 2;
+		se200pci_WM8740_init(ice);
+		se200pci_WM8766_init(ice);
+		se200pci_WM8776_init(ice);
+		ice->gpio.set_pro_rate = se200pci_set_pro_rate;
+		return 0;
+	}
+
+	return -ENOENT;
+}
+
+static int __devinit se_add_controls(struct snd_ice1712 *ice)
+{
+	int err;
+
+	err = 0;
+	/* nothing to do for VT1724_SUBDEVICE_SE90PCI */
+	if (ice->eeprom.subvendor == VT1724_SUBDEVICE_SE200PCI)
+		err = se200pci_add_controls(ice);
+
+	return err;
+}
+
+
+/****************************************************************************/
+/*  entry point                                                             */
+/****************************************************************************/
+
+static unsigned char se200pci_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]	= 0x4b,	/* 49.152Hz, spdif-in/ADC, 4DACs */
+	[ICE_EEP2_ACLINK]	= 0x80,	/* I2S */
+	[ICE_EEP2_I2S]		= 0x78,	/* 96k-ok, 24bit, 192k-ok */
+	[ICE_EEP2_SPDIF]	= 0xc3,	/* out-en, out-int, spdif-in */
+
+	[ICE_EEP2_GPIO_DIR]	= 0x02, /* WM8766 mute      1=output */
+	[ICE_EEP2_GPIO_DIR1]	= 0x00, /* not used */
+	[ICE_EEP2_GPIO_DIR2]	= 0x07, /* WM8766 ML/MC/MD  1=output */
+
+	[ICE_EEP2_GPIO_MASK]	= 0x00, /* 0=writable */
+	[ICE_EEP2_GPIO_MASK1]	= 0x00, /* 0=writable */
+	[ICE_EEP2_GPIO_MASK2]	= 0x00, /* 0=writable */
+
+	[ICE_EEP2_GPIO_STATE]	= 0x00, /* WM8766 mute=0 */
+	[ICE_EEP2_GPIO_STATE1]	= 0x00, /* not used */
+	[ICE_EEP2_GPIO_STATE2]	= 0x07, /* WM8766 ML/MC/MD */
+};
+
+static unsigned char se90pci_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]	= 0x4b,	/* 49.152Hz, spdif-in/ADC, 4DACs */
+	[ICE_EEP2_ACLINK]	= 0x80,	/* I2S */
+	[ICE_EEP2_I2S]		= 0x78,	/* 96k-ok, 24bit, 192k-ok */
+	[ICE_EEP2_SPDIF]	= 0xc3,	/* out-en, out-int, spdif-in */
+
+	/* ALL GPIO bits are in input mode */
+};
+
+struct snd_ice1712_card_info snd_vt1724_se_cards[] __devinitdata = {
+	{
+		.subvendor = VT1724_SUBDEVICE_SE200PCI,
+		.name = "ONKYO SE200PCI",
+		.model = "se200pci",
+		.chip_init = se_init,
+		.build_controls = se_add_controls,
+		.eeprom_size = sizeof(se200pci_eeprom),
+		.eeprom_data = se200pci_eeprom,
+	},
+	{
+		.subvendor = VT1724_SUBDEVICE_SE90PCI,
+		.name = "ONKYO SE90PCI",
+		.model = "se90pci",
+		.chip_init = se_init,
+		.build_controls = se_add_controls,
+		.eeprom_size = sizeof(se90pci_eeprom),
+		.eeprom_data = se90pci_eeprom,
+	},
+	{} /*terminator*/
+};
diff --git a/linux/sound/pci/ice1712/se.h b/linux/sound/pci/ice1712/se.h
new file mode 100644
index 0000000..0b0a9da
--- /dev/null
+++ b/linux/sound/pci/ice1712/se.h
@@ -0,0 +1,15 @@
+#ifndef __SOUND_SE_H
+#define __SOUND_SE_H
+
+/* ID */
+#define SE_DEVICE_DESC	\
+		"{ONKYO INC,SE-90PCI},"\
+		"{ONKYO INC,SE-200PCI},"
+
+#define VT1724_SUBDEVICE_SE90PCI	0xb161000
+#define VT1724_SUBDEVICE_SE200PCI	0xb160100
+
+/* entry struct */
+extern struct snd_ice1712_card_info snd_vt1724_se_cards[];
+
+#endif /* __SOUND_SE_H */
diff --git a/linux/sound/pci/ice1712/stac946x.h b/linux/sound/pci/ice1712/stac946x.h
new file mode 100644
index 0000000..5b39095
--- /dev/null
+++ b/linux/sound/pci/ice1712/stac946x.h
@@ -0,0 +1,25 @@
+#ifndef __SOUND_STAC946X_H
+#define __SOUND_STAC946X_H
+
+#define STAC946X_RESET			0x00
+#define STAC946X_STATUS			0x01
+#define STAC946X_MASTER_VOLUME		0x02
+#define STAC946X_LF_VOLUME		0x03
+#define STAC946X_RF_VOLUME		0x04
+#define STAC946X_LR_VOLUME		0x05
+#define STAC946X_RR_VOLUME		0x06
+#define STAC946X_CENTER_VOLUME		0x07
+#define STAC946X_LFE_VOLUME		0x08
+#define STAC946X_MIC_L_VOLUME		0x09
+#define STAC946X_MIC_R_VOLUME		0x0a
+#define STAC946X_DEEMPHASIS		0x0c
+#define STAC946X_GENERAL_PURPOSE	0x0d
+#define STAC946X_AUDIO_PORT_CONTROL	0x0e
+#define STAC946X_MASTER_CLOCKING	0x0f
+#define STAC946X_POWERDOWN_CTRL1	0x10
+#define STAC946X_POWERDOWN_CTRL2	0x11
+#define STAC946X_REVISION_CODE		0x12
+#define STAC946X_ADDRESS_CONTROL	0x13
+#define STAC946X_ADDRESS		0x14
+
+#endif  /*  __SOUND_STAC946X_H */
diff --git a/linux/sound/pci/ice1712/vt1720_mobo.c b/linux/sound/pci/ice1712/vt1720_mobo.c
new file mode 100644
index 0000000..4c551e1
--- /dev/null
+++ b/linux/sound/pci/ice1712/vt1720_mobo.c
@@ -0,0 +1,139 @@
+/*
+ *   ALSA driver for VT1720/VT1724 (Envy24PT/Envy24HT)
+ *
+ *   Lowlevel functions for VT1720-based motherboards
+ *
+ *	Copyright (c) 2004 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 <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "vt1720_mobo.h"
+
+
+static int __devinit k8x800_init(struct snd_ice1712 *ice)
+{
+	ice->vt1720 = 1;
+
+	/* VT1616 codec */
+	ice->num_total_dacs = 6;
+	ice->num_total_adcs = 2;
+
+	/* WM8728 codec */
+	/* FIXME: TODO */
+
+	return 0;
+}
+
+static int __devinit k8x800_add_controls(struct snd_ice1712 *ice)
+{
+	/* FIXME: needs some quirks for VT1616? */
+	return 0;
+}
+
+/* EEPROM image */
+
+static unsigned char k8x800_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x01,	/* clock 256, 1ADC, 2DACs */
+	[ICE_EEP2_ACLINK]      = 0x02,	/* ACLINK, packed */
+	[ICE_EEP2_I2S]         = 0x00,	/* - */
+	[ICE_EEP2_SPDIF]       = 0x00,	/* - */
+	[ICE_EEP2_GPIO_DIR]    = 0xff,
+	[ICE_EEP2_GPIO_DIR1]   = 0xff,
+	[ICE_EEP2_GPIO_DIR2]   = 0x00,	/* - */
+	[ICE_EEP2_GPIO_MASK]   = 0xff,
+	[ICE_EEP2_GPIO_MASK1]  = 0xff,
+	[ICE_EEP2_GPIO_MASK2]  = 0x00,	/* - */
+	[ICE_EEP2_GPIO_STATE]  = 0x00,
+	[ICE_EEP2_GPIO_STATE1] = 0x00,
+	[ICE_EEP2_GPIO_STATE2] = 0x00,	/* - */
+};
+
+static unsigned char sn25p_eeprom[] __devinitdata = {
+	[ICE_EEP2_SYSCONF]     = 0x01,	/* clock 256, 1ADC, 2DACs */
+	[ICE_EEP2_ACLINK]      = 0x02,	/* ACLINK, packed */
+	[ICE_EEP2_I2S]         = 0x00,	/* - */
+	[ICE_EEP2_SPDIF]       = 0x41,	/* - */
+	[ICE_EEP2_GPIO_DIR]    = 0xff,
+	[ICE_EEP2_GPIO_DIR1]   = 0xff,
+	[ICE_EEP2_GPIO_DIR2]   = 0x00,	/* - */
+	[ICE_EEP2_GPIO_MASK]   = 0xff,
+	[ICE_EEP2_GPIO_MASK1]  = 0xff,
+	[ICE_EEP2_GPIO_MASK2]  = 0x00,	/* - */
+	[ICE_EEP2_GPIO_STATE]  = 0x00,
+	[ICE_EEP2_GPIO_STATE1] = 0x00,
+	[ICE_EEP2_GPIO_STATE2] = 0x00,	/* - */
+};
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1720_mobo_cards[] __devinitdata = {
+	{
+		.subvendor = VT1720_SUBDEVICE_K8X800,
+		.name = "Albatron K8X800 Pro II",
+		.model = "k8x800",
+		.chip_init = k8x800_init,
+		.build_controls = k8x800_add_controls,
+		.eeprom_size = sizeof(k8x800_eeprom),
+		.eeprom_data = k8x800_eeprom,
+	},
+	{
+		.subvendor = VT1720_SUBDEVICE_ZNF3_150,
+		.name = "Chaintech ZNF3-150",
+		/* identical with k8x800 */
+		.chip_init = k8x800_init,
+		.build_controls = k8x800_add_controls,
+		.eeprom_size = sizeof(k8x800_eeprom),
+		.eeprom_data = k8x800_eeprom,
+	},
+	{
+		.subvendor = VT1720_SUBDEVICE_ZNF3_250,
+		.name = "Chaintech ZNF3-250",
+		/* identical with k8x800 */
+		.chip_init = k8x800_init,
+		.build_controls = k8x800_add_controls,
+		.eeprom_size = sizeof(k8x800_eeprom),
+		.eeprom_data = k8x800_eeprom,
+	},
+	{
+		.subvendor = VT1720_SUBDEVICE_9CJS,
+		.name = "Chaintech 9CJS",
+		/* identical with k8x800 */
+		.chip_init = k8x800_init,
+		.build_controls = k8x800_add_controls,
+		.eeprom_size = sizeof(k8x800_eeprom),
+		.eeprom_data = k8x800_eeprom,
+	},
+	{
+		.subvendor = VT1720_SUBDEVICE_SN25P,
+		.name = "Shuttle SN25P",
+		.model = "sn25p",
+		.chip_init = k8x800_init,
+		.build_controls = k8x800_add_controls,
+		.eeprom_size = sizeof(k8x800_eeprom),
+		.eeprom_data = sn25p_eeprom,
+	},
+	{ } /* terminator */
+};
+
diff --git a/linux/sound/pci/ice1712/vt1720_mobo.h b/linux/sound/pci/ice1712/vt1720_mobo.h
new file mode 100644
index 0000000..0b1b0ee
--- /dev/null
+++ b/linux/sound/pci/ice1712/vt1720_mobo.h
@@ -0,0 +1,41 @@
+#ifndef __SOUND_VT1720_MOBO_H
+#define __SOUND_VT1720_MOBO_H
+
+/*
+ *   ALSA driver for VT1720/VT1724 (Envy24PT/Envy24HT)
+ *
+ *   Lowlevel functions for VT1720-based motherboards
+ *
+ *	Copyright (c) 2004 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
+ *
+ */      
+
+#define VT1720_MOBO_DEVICE_DESC        "{Albatron,K8X800 Pro II},"\
+				       "{Chaintech,ZNF3-150},"\
+				       "{Chaintech,ZNF3-250},"\
+				       "{Chaintech,9CJS},"\
+				       "{Shuttle,SN25P},"
+
+#define VT1720_SUBDEVICE_K8X800		0xf217052c
+#define VT1720_SUBDEVICE_ZNF3_150	0x0f2741f6
+#define VT1720_SUBDEVICE_ZNF3_250	0x0f2745f6
+#define VT1720_SUBDEVICE_9CJS		0x0f272327
+#define VT1720_SUBDEVICE_SN25P		0x97123650
+
+extern struct snd_ice1712_card_info  snd_vt1720_mobo_cards[];
+
+#endif /* __SOUND_VT1720_MOBO_H */
diff --git a/linux/sound/pci/ice1712/wtm.c b/linux/sound/pci/ice1712/wtm.c
new file mode 100644
index 0000000..e618f78
--- /dev/null
+++ b/linux/sound/pci/ice1712/wtm.c
@@ -0,0 +1,517 @@
+/*
+ *	ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ *	Lowlevel functions for Ego Sys Waveterminal 192M
+ *
+ *		Copyright (c) 2006 Guedez Clement <klem.dev@gmail.com>
+ *		Some functions are taken from the Prodigy192 driver
+ *		source
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "wtm.h"
+#include "stac946x.h"
+
+
+/*
+ *	2*ADC 6*DAC no1 ringbuffer r/w on i2c bus
+ */
+static inline void stac9460_put(struct snd_ice1712 *ice, int reg,
+						unsigned char val)
+{
+	snd_vt1724_write_i2c(ice, STAC9460_I2C_ADDR, reg, val);
+}
+
+static inline unsigned char stac9460_get(struct snd_ice1712 *ice, int reg)
+{
+	return snd_vt1724_read_i2c(ice, STAC9460_I2C_ADDR, reg);
+}
+
+/*
+ *	2*ADC 2*DAC no2 ringbuffer r/w on i2c bus
+ */
+static inline void stac9460_2_put(struct snd_ice1712 *ice, int reg,
+						unsigned char val)
+{
+	snd_vt1724_write_i2c(ice, STAC9460_2_I2C_ADDR, reg, val);
+}
+
+static inline unsigned char stac9460_2_get(struct snd_ice1712 *ice, int reg)
+{
+	return snd_vt1724_read_i2c(ice, STAC9460_2_I2C_ADDR, reg);
+}
+
+
+/*
+ *	DAC mute control
+ */
+#define stac9460_dac_mute_info		snd_ctl_boolean_mono_info
+
+static int stac9460_dac_mute_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+	int idx, id;
+
+	if (kcontrol->private_value) {
+		idx = STAC946X_MASTER_VOLUME;
+		id = 0;
+	} else {
+		id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+		idx = id + STAC946X_LF_VOLUME;
+	}
+	if (id < 6)
+		val = stac9460_get(ice, idx);
+	else
+		val = stac9460_2_get(ice, idx - 6);
+	ucontrol->value.integer.value[0] = (~val >> 7) & 0x1;
+	return 0;
+}
+
+static int stac9460_dac_mute_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char new, old;
+	int id, idx;
+	int change;
+
+	if (kcontrol->private_value) {
+		idx = STAC946X_MASTER_VOLUME;
+		old = stac9460_get(ice, idx);
+		new = (~ucontrol->value.integer.value[0] << 7 & 0x80) |
+							(old & ~0x80);
+		change = (new != old);
+		if (change) {
+			stac9460_put(ice, idx, new);
+			stac9460_2_put(ice, idx, new);
+		}
+	} else {
+		id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+		idx = id + STAC946X_LF_VOLUME;
+		if (id < 6)
+			old = stac9460_get(ice, idx);
+		else
+			old = stac9460_2_get(ice, idx - 6);
+		new = (~ucontrol->value.integer.value[0] << 7 & 0x80) |
+							(old & ~0x80);
+		change = (new != old);
+		if (change) {
+			if (id < 6)
+				stac9460_put(ice, idx, new);
+			else
+				stac9460_2_put(ice, idx - 6, new);
+		}
+	}
+	return change;
+}
+
+/*
+ * 	DAC volume attenuation mixer control
+ */
+static int stac9460_dac_vol_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;			/* mute */
+	uinfo->value.integer.max = 0x7f;		/* 0dB */
+	return 0;
+}
+
+static int stac9460_dac_vol_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx, id;
+	unsigned char vol;
+
+	if (kcontrol->private_value) {
+		idx = STAC946X_MASTER_VOLUME;
+		id = 0;
+	} else {
+		id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+		idx = id + STAC946X_LF_VOLUME;
+	}
+	if (id < 6)
+		vol = stac9460_get(ice, idx) & 0x7f;
+	else
+		vol = stac9460_2_get(ice, idx - 6) & 0x7f;
+	ucontrol->value.integer.value[0] = 0x7f - vol;
+	return 0;
+}
+
+static int stac9460_dac_vol_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int idx, id;
+	unsigned char tmp, ovol, nvol;
+	int change;
+
+	if (kcontrol->private_value) {
+		idx = STAC946X_MASTER_VOLUME;
+		nvol = ucontrol->value.integer.value[0] & 0x7f;
+		tmp = stac9460_get(ice, idx);
+		ovol = 0x7f - (tmp & 0x7f);
+		change = (ovol != nvol);
+		if (change) {
+			stac9460_put(ice, idx, (0x7f - nvol) | (tmp & 0x80));
+			stac9460_2_put(ice, idx, (0x7f - nvol) | (tmp & 0x80));
+		}
+	} else {
+		id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+		idx = id + STAC946X_LF_VOLUME;
+		nvol = ucontrol->value.integer.value[0] & 0x7f;
+		if (id < 6)
+			tmp = stac9460_get(ice, idx);
+		else
+			tmp = stac9460_2_get(ice, idx - 6);
+		ovol = 0x7f - (tmp & 0x7f);
+		change = (ovol != nvol);
+		if (change) {
+			if (id < 6)
+				stac9460_put(ice, idx, (0x7f - nvol) |
+							(tmp & 0x80));
+			else
+				stac9460_2_put(ice, idx-6, (0x7f - nvol) |
+							(tmp & 0x80));
+		}
+	}
+	return change;
+}
+
+/*
+ * ADC mute control
+ */
+#define stac9460_adc_mute_info		snd_ctl_boolean_stereo_info
+
+static int stac9460_adc_mute_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+	int i, id;
+
+	id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	if (id == 0) {
+		for (i = 0; i < 2; ++i) {
+			val = stac9460_get(ice, STAC946X_MIC_L_VOLUME + i);
+			ucontrol->value.integer.value[i] = ~val>>7 & 0x1;
+		}
+	} else {
+		for (i = 0; i < 2; ++i) {
+			val = stac9460_2_get(ice, STAC946X_MIC_L_VOLUME + i);
+			ucontrol->value.integer.value[i] = ~val>>7 & 0x1;
+		}
+	}
+	return 0;
+}
+
+static int stac9460_adc_mute_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char new, old;
+	int i, reg, id;
+	int change;
+
+	id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	if (id == 0) {
+		for (i = 0; i < 2; ++i) {
+			reg = STAC946X_MIC_L_VOLUME + i;
+			old = stac9460_get(ice, reg);
+			new = (~ucontrol->value.integer.value[i]<<7&0x80) |
+								(old&~0x80);
+			change = (new != old);
+			if (change)
+				stac9460_put(ice, reg, new);
+		}
+	} else {
+		for (i = 0; i < 2; ++i) {
+			reg = STAC946X_MIC_L_VOLUME + i;
+			old = stac9460_2_get(ice, reg);
+			new = (~ucontrol->value.integer.value[i]<<7&0x80) |
+								(old&~0x80);
+			change = (new != old);
+			if (change)
+				stac9460_2_put(ice, reg, new);
+		}
+	}
+	return change;
+}
+
+/*
+ *ADC gain mixer control
+ */
+static int stac9460_adc_vol_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;		/* 0dB */
+	uinfo->value.integer.max = 0x0f;	/* 22.5dB */
+	return 0;
+}
+
+static int stac9460_adc_vol_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int i, reg, id;
+	unsigned char vol;
+
+	id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	if (id == 0) {
+		for (i = 0; i < 2; ++i) {
+			reg = STAC946X_MIC_L_VOLUME + i;
+			vol = stac9460_get(ice, reg) & 0x0f;
+			ucontrol->value.integer.value[i] = 0x0f - vol;
+		}
+	} else {
+		for (i = 0; i < 2; ++i) {
+			reg = STAC946X_MIC_L_VOLUME + i;
+			vol = stac9460_2_get(ice, reg) & 0x0f;
+			ucontrol->value.integer.value[i] = 0x0f - vol;
+		}
+	}
+	return 0;
+}
+
+static int stac9460_adc_vol_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	int i, reg, id;
+	unsigned char ovol, nvol;
+	int change;
+
+	id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	if (id == 0) {
+		for (i = 0; i < 2; ++i) {
+			reg = STAC946X_MIC_L_VOLUME + i;
+			nvol = ucontrol->value.integer.value[i] & 0x0f;
+			ovol = 0x0f - stac9460_get(ice, reg);
+			change = ((ovol & 0x0f) != nvol);
+			if (change)
+				stac9460_put(ice, reg, (0x0f - nvol) |
+							(ovol & ~0x0f));
+		}
+	} else {
+		for (i = 0; i < 2; ++i) {
+			reg = STAC946X_MIC_L_VOLUME + i;
+			nvol = ucontrol->value.integer.value[i] & 0x0f;
+			ovol = 0x0f - stac9460_2_get(ice, reg);
+			change = ((ovol & 0x0f) != nvol);
+			if (change)
+				stac9460_2_put(ice, reg, (0x0f - nvol) |
+							(ovol & ~0x0f));
+		}
+	}
+	return change;
+}
+
+/*
+ * MIC / LINE switch fonction
+ */
+
+#define stac9460_mic_sw_info		snd_ctl_boolean_mono_info
+
+static int stac9460_mic_sw_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+	int id;
+
+	id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	if (id == 0)
+		val = stac9460_get(ice, STAC946X_GENERAL_PURPOSE);
+	else
+		val = stac9460_2_get(ice, STAC946X_GENERAL_PURPOSE);
+	ucontrol->value.integer.value[0] = ~val>>7 & 0x1;
+	return 0;
+}
+
+static int stac9460_mic_sw_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+	unsigned char new, old;
+	int change, id;
+
+	id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	if (id == 0)
+		old = stac9460_get(ice, STAC946X_GENERAL_PURPOSE);
+	else
+		old = stac9460_2_get(ice, STAC946X_GENERAL_PURPOSE);
+	new = (~ucontrol->value.integer.value[0] << 7 & 0x80) | (old & ~0x80);
+	change = (new != old);
+	if (change) {
+		if (id == 0)
+			stac9460_put(ice, STAC946X_GENERAL_PURPOSE, new);
+		else
+			stac9460_2_put(ice, STAC946X_GENERAL_PURPOSE, new);
+	}
+	return change;
+}
+
+/*
+ * Control tabs
+ */
+static struct snd_kcontrol_new stac9640_controls[] __devinitdata = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = stac9460_dac_mute_info,
+		.get = stac9460_dac_mute_get,
+		.put = stac9460_dac_mute_put,
+		.private_value = 1
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Volume",
+		.info = stac9460_dac_vol_info,
+		.get = stac9460_dac_vol_get,
+		.put = stac9460_dac_vol_put,
+		.private_value = 1,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "MIC/Line switch",
+		.count = 2,
+		.info = stac9460_mic_sw_info,
+		.get = stac9460_mic_sw_get,
+		.put = stac9460_mic_sw_put,
+
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "DAC Switch",
+		.count = 8,
+		.info = stac9460_dac_mute_info,
+		.get = stac9460_dac_mute_get,
+		.put = stac9460_dac_mute_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "DAC Volume",
+		.count = 8,
+		.info = stac9460_dac_vol_info,
+		.get = stac9460_dac_vol_get,
+		.put = stac9460_dac_vol_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "ADC Switch",
+		.count = 2,
+		.info = stac9460_adc_mute_info,
+		.get = stac9460_adc_mute_get,
+		.put = stac9460_adc_mute_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "ADC Volume",
+		.count = 2,
+		.info = stac9460_adc_vol_info,
+		.get = stac9460_adc_vol_get,
+		.put = stac9460_adc_vol_put,
+
+	}
+};
+
+
+
+/*INIT*/
+static int __devinit wtm_add_controls(struct snd_ice1712 *ice)
+{
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(stac9640_controls); i++) {
+		err = snd_ctl_add(ice->card,
+				snd_ctl_new1(&stac9640_controls[i], ice));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int __devinit wtm_init(struct snd_ice1712 *ice)
+{
+	static unsigned short stac_inits_prodigy[] = {
+		STAC946X_RESET, 0,
+		(unsigned short)-1
+	};
+	unsigned short *p;
+
+	/*WTM 192M*/
+	ice->num_total_dacs = 8;
+	ice->num_total_adcs = 4;
+	ice->force_rdma1 = 1;
+
+	/*initialize codec*/
+	p = stac_inits_prodigy;
+	for (; *p != (unsigned short)-1; p += 2) {
+		stac9460_put(ice, p[0], p[1]);
+		stac9460_2_put(ice, p[0], p[1]);
+	}
+	return 0;
+}
+
+
+static unsigned char wtm_eeprom[] __devinitdata = {
+	0x47,	/*SYSCONF: clock 192KHz, 4ADC, 8DAC */
+	0x80,	/* ACLINK : I2S */
+	0xf8,	/* I2S: vol; 96k, 24bit, 192k */
+	0xc1	/*SPDIF: out-en, spidf ext out*/,
+	0x9f,	/* GPIO_DIR */
+	0xff,	/* GPIO_DIR1 */
+	0x7f,	/* GPIO_DIR2 */
+	0x9f,	/* GPIO_MASK */
+	0xff,	/* GPIO_MASK1 */
+	0x7f,	/* GPIO_MASK2 */
+	0x16,	/* GPIO_STATE */
+	0x80,	/* GPIO_STATE1 */
+	0x00,	/* GPIO_STATE2 */
+};
+
+
+/*entry point*/
+struct snd_ice1712_card_info snd_vt1724_wtm_cards[] __devinitdata = {
+	{
+		.subvendor = VT1724_SUBDEVICE_WTM,
+		.name = "ESI Waveterminal 192M",
+		.model = "WT192M",
+		.chip_init = wtm_init,
+		.build_controls = wtm_add_controls,
+		.eeprom_size = sizeof(wtm_eeprom),
+		.eeprom_data = wtm_eeprom,
+	},
+	{} /*terminator*/
+};
diff --git a/linux/sound/pci/ice1712/wtm.h b/linux/sound/pci/ice1712/wtm.h
new file mode 100644
index 0000000..423c1a2
--- /dev/null
+++ b/linux/sound/pci/ice1712/wtm.h
@@ -0,0 +1,20 @@
+#ifndef __SOUND_WTM_H
+#define __SOUND_WTM_H
+
+/* ID */
+#define WTM_DEVICE_DESC		"{EGO SYS INC,WaveTerminal 192M},"
+#define VT1724_SUBDEVICE_WTM	0x36495345	/* WT192M ver1.0 */
+
+/*
+ *chip addresses on I2C bus
+ */
+
+#define	AK4114_ADDR		0x20	/*S/PDIF receiver*/
+#define STAC9460_I2C_ADDR	0x54	/* ADC*2 | DAC*6 */
+#define STAC9460_2_I2C_ADDR	0x56	/* ADC|DAC *2 */
+
+
+extern struct snd_ice1712_card_info snd_vt1724_wtm_cards[];
+
+#endif /* __SOUND_WTM_H */
+
diff --git a/linux/sound/pci/intel8x0.c b/linux/sound/pci/intel8x0.c
new file mode 100644
index 0000000..629a549
--- /dev/null
+++ b/linux/sound/pci/intel8x0.c
@@ -0,0 +1,3291 @@
+/*
+ *   ALSA driver for Intel ICH (i8x0) chipsets
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This code also contains alpha support for SiS 735 chipsets provided
+ *   by Mike Pieper <mptei@users.sourceforge.net>. We have no datasheet
+ *   for SiS735, so the code is not fully functional.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+
+ *
+ */      
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+/* for 440MX workaround */
+#include <asm/pgtable.h>
+#include <asm/cacheflush.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Intel 82801AA,82901AB,i810,i820,i830,i840,i845,MX440; SiS 7012; Ali 5455");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Intel,82801AA-ICH},"
+		"{Intel,82901AB-ICH0},"
+		"{Intel,82801BA-ICH2},"
+		"{Intel,82801CA-ICH3},"
+		"{Intel,82801DB-ICH4},"
+		"{Intel,ICH5},"
+		"{Intel,ICH6},"
+		"{Intel,ICH7},"
+		"{Intel,6300ESB},"
+		"{Intel,ESB2},"
+		"{Intel,MX440},"
+		"{SiS,SI7012},"
+		"{NVidia,nForce Audio},"
+		"{NVidia,nForce2 Audio},"
+		"{NVidia,nForce3 Audio},"
+		"{NVidia,MCP04},"
+		"{NVidia,MCP501},"
+		"{NVidia,CK804},"
+		"{NVidia,CK8},"
+		"{NVidia,CK8S},"
+		"{AMD,AMD768},"
+		"{AMD,AMD8111},"
+	        "{ALI,M5455}}");
+
+static int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+static int ac97_clock;
+static char *ac97_quirk;
+static int buggy_semaphore;
+static int buggy_irq = -1; /* auto-check */
+static int xbox;
+static int spdif_aclink = -1;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for Intel i8x0 soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for Intel i8x0 soundcard.");
+module_param(ac97_clock, int, 0444);
+MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (0 = whitelist + auto-detect, 1 = force autodetect).");
+module_param(ac97_quirk, charp, 0444);
+MODULE_PARM_DESC(ac97_quirk, "AC'97 workaround for strange hardware.");
+module_param(buggy_semaphore, bool, 0444);
+MODULE_PARM_DESC(buggy_semaphore, "Enable workaround for hardwares with problematic codec semaphores.");
+module_param(buggy_irq, bool, 0444);
+MODULE_PARM_DESC(buggy_irq, "Enable workaround for buggy interrupts on some motherboards.");
+module_param(xbox, bool, 0444);
+MODULE_PARM_DESC(xbox, "Set to 1 for Xbox, if you have problems with the AC'97 codec detection.");
+module_param(spdif_aclink, int, 0444);
+MODULE_PARM_DESC(spdif_aclink, "S/PDIF over AC-link.");
+
+/* just for backward compatibility */
+static int enable;
+module_param(enable, bool, 0444);
+static int joystick;
+module_param(joystick, int, 0444);
+
+/*
+ *  Direct registers
+ */
+enum { DEVICE_INTEL, DEVICE_INTEL_ICH4, DEVICE_SIS, DEVICE_ALI, DEVICE_NFORCE };
+
+#define ICHREG(x) ICH_REG_##x
+
+#define DEFINE_REGSET(name,base) \
+enum { \
+	ICH_REG_##name##_BDBAR	= base + 0x0,	/* dword - buffer descriptor list base address */ \
+	ICH_REG_##name##_CIV	= base + 0x04,	/* byte - current index value */ \
+	ICH_REG_##name##_LVI	= base + 0x05,	/* byte - last valid index */ \
+	ICH_REG_##name##_SR	= base + 0x06,	/* byte - status register */ \
+	ICH_REG_##name##_PICB	= base + 0x08,	/* word - position in current buffer */ \
+	ICH_REG_##name##_PIV	= base + 0x0a,	/* byte - prefetched index value */ \
+	ICH_REG_##name##_CR	= base + 0x0b,	/* byte - control register */ \
+};
+
+/* busmaster blocks */
+DEFINE_REGSET(OFF, 0);		/* offset */
+DEFINE_REGSET(PI, 0x00);	/* PCM in */
+DEFINE_REGSET(PO, 0x10);	/* PCM out */
+DEFINE_REGSET(MC, 0x20);	/* Mic in */
+
+/* ICH4 busmaster blocks */
+DEFINE_REGSET(MC2, 0x40);	/* Mic in 2 */
+DEFINE_REGSET(PI2, 0x50);	/* PCM in 2 */
+DEFINE_REGSET(SP, 0x60);	/* SPDIF out */
+
+/* values for each busmaster block */
+
+/* LVI */
+#define ICH_REG_LVI_MASK		0x1f
+
+/* SR */
+#define ICH_FIFOE			0x10	/* FIFO error */
+#define ICH_BCIS			0x08	/* buffer completion interrupt status */
+#define ICH_LVBCI			0x04	/* last valid buffer completion interrupt */
+#define ICH_CELV			0x02	/* current equals last valid */
+#define ICH_DCH				0x01	/* DMA controller halted */
+
+/* PIV */
+#define ICH_REG_PIV_MASK		0x1f	/* mask */
+
+/* CR */
+#define ICH_IOCE			0x10	/* interrupt on completion enable */
+#define ICH_FEIE			0x08	/* fifo error interrupt enable */
+#define ICH_LVBIE			0x04	/* last valid buffer interrupt enable */
+#define ICH_RESETREGS			0x02	/* reset busmaster registers */
+#define ICH_STARTBM			0x01	/* start busmaster operation */
+
+
+/* global block */
+#define ICH_REG_GLOB_CNT		0x2c	/* dword - global control */
+#define   ICH_PCM_SPDIF_MASK	0xc0000000	/* s/pdif pcm slot mask (ICH4) */
+#define   ICH_PCM_SPDIF_NONE	0x00000000	/* reserved - undefined */
+#define   ICH_PCM_SPDIF_78	0x40000000	/* s/pdif pcm on slots 7&8 */
+#define   ICH_PCM_SPDIF_69	0x80000000	/* s/pdif pcm on slots 6&9 */
+#define   ICH_PCM_SPDIF_1011	0xc0000000	/* s/pdif pcm on slots 10&11 */
+#define   ICH_PCM_20BIT		0x00400000	/* 20-bit samples (ICH4) */
+#define   ICH_PCM_246_MASK	0x00300000	/* chan mask (not all chips) */
+#define   ICH_PCM_8		0x00300000      /* 8 channels (not all chips) */
+#define   ICH_PCM_6		0x00200000	/* 6 channels (not all chips) */
+#define   ICH_PCM_4		0x00100000	/* 4 channels (not all chips) */
+#define   ICH_PCM_2		0x00000000	/* 2 channels (stereo) */
+#define   ICH_SIS_PCM_246_MASK	0x000000c0	/* 6 channels (SIS7012) */
+#define   ICH_SIS_PCM_6		0x00000080	/* 6 channels (SIS7012) */
+#define   ICH_SIS_PCM_4		0x00000040	/* 4 channels (SIS7012) */
+#define   ICH_SIS_PCM_2		0x00000000	/* 2 channels (SIS7012) */
+#define   ICH_TRIE		0x00000040	/* tertiary resume interrupt enable */
+#define   ICH_SRIE		0x00000020	/* secondary resume interrupt enable */
+#define   ICH_PRIE		0x00000010	/* primary resume interrupt enable */
+#define   ICH_ACLINK		0x00000008	/* AClink shut off */
+#define   ICH_AC97WARM		0x00000004	/* AC'97 warm reset */
+#define   ICH_AC97COLD		0x00000002	/* AC'97 cold reset */
+#define   ICH_GIE		0x00000001	/* GPI interrupt enable */
+#define ICH_REG_GLOB_STA		0x30	/* dword - global status */
+#define   ICH_TRI		0x20000000	/* ICH4: tertiary (AC_SDIN2) resume interrupt */
+#define   ICH_TCR		0x10000000	/* ICH4: tertiary (AC_SDIN2) codec ready */
+#define   ICH_BCS		0x08000000	/* ICH4: bit clock stopped */
+#define   ICH_SPINT		0x04000000	/* ICH4: S/PDIF interrupt */
+#define   ICH_P2INT		0x02000000	/* ICH4: PCM2-In interrupt */
+#define   ICH_M2INT		0x01000000	/* ICH4: Mic2-In interrupt */
+#define   ICH_SAMPLE_CAP	0x00c00000	/* ICH4: sample capability bits (RO) */
+#define   ICH_SAMPLE_16_20	0x00400000	/* ICH4: 16- and 20-bit samples */
+#define   ICH_MULTICHAN_CAP	0x00300000	/* ICH4: multi-channel capability bits (RO) */
+#define   ICH_SIS_TRI		0x00080000	/* SIS: tertiary resume irq */
+#define   ICH_SIS_TCR		0x00040000	/* SIS: tertiary codec ready */
+#define   ICH_MD3		0x00020000	/* modem power down semaphore */
+#define   ICH_AD3		0x00010000	/* audio power down semaphore */
+#define   ICH_RCS		0x00008000	/* read completion status */
+#define   ICH_BIT3		0x00004000	/* bit 3 slot 12 */
+#define   ICH_BIT2		0x00002000	/* bit 2 slot 12 */
+#define   ICH_BIT1		0x00001000	/* bit 1 slot 12 */
+#define   ICH_SRI		0x00000800	/* secondary (AC_SDIN1) resume interrupt */
+#define   ICH_PRI		0x00000400	/* primary (AC_SDIN0) resume interrupt */
+#define   ICH_SCR		0x00000200	/* secondary (AC_SDIN1) codec ready */
+#define   ICH_PCR		0x00000100	/* primary (AC_SDIN0) codec ready */
+#define   ICH_MCINT		0x00000080	/* MIC capture interrupt */
+#define   ICH_POINT		0x00000040	/* playback interrupt */
+#define   ICH_PIINT		0x00000020	/* capture interrupt */
+#define   ICH_NVSPINT		0x00000010	/* nforce spdif interrupt */
+#define   ICH_MOINT		0x00000004	/* modem playback interrupt */
+#define   ICH_MIINT		0x00000002	/* modem capture interrupt */
+#define   ICH_GSCI		0x00000001	/* GPI status change interrupt */
+#define ICH_REG_ACC_SEMA		0x34	/* byte - codec write semaphore */
+#define   ICH_CAS		0x01		/* codec access semaphore */
+#define ICH_REG_SDM		0x80
+#define   ICH_DI2L_MASK		0x000000c0	/* PCM In 2, Mic In 2 data in line */
+#define   ICH_DI2L_SHIFT	6
+#define   ICH_DI1L_MASK		0x00000030	/* PCM In 1, Mic In 1 data in line */
+#define   ICH_DI1L_SHIFT	4
+#define   ICH_SE		0x00000008	/* steer enable */
+#define   ICH_LDI_MASK		0x00000003	/* last codec read data input */
+
+#define ICH_MAX_FRAGS		32		/* max hw frags */
+
+
+/*
+ * registers for Ali5455
+ */
+
+/* ALi 5455 busmaster blocks */
+DEFINE_REGSET(AL_PI, 0x40);	/* ALi PCM in */
+DEFINE_REGSET(AL_PO, 0x50);	/* Ali PCM out */
+DEFINE_REGSET(AL_MC, 0x60);	/* Ali Mic in */
+DEFINE_REGSET(AL_CDC_SPO, 0x70);	/* Ali Codec SPDIF out */
+DEFINE_REGSET(AL_CENTER, 0x80);		/* Ali center out */
+DEFINE_REGSET(AL_LFE, 0x90);		/* Ali center out */
+DEFINE_REGSET(AL_CLR_SPI, 0xa0);	/* Ali Controller SPDIF in */
+DEFINE_REGSET(AL_CLR_SPO, 0xb0);	/* Ali Controller SPDIF out */
+DEFINE_REGSET(AL_I2S, 0xc0);	/* Ali I2S in */
+DEFINE_REGSET(AL_PI2, 0xd0);	/* Ali PCM2 in */
+DEFINE_REGSET(AL_MC2, 0xe0);	/* Ali Mic2 in */
+
+enum {
+	ICH_REG_ALI_SCR = 0x00,		/* System Control Register */
+	ICH_REG_ALI_SSR = 0x04,		/* System Status Register  */
+	ICH_REG_ALI_DMACR = 0x08,	/* DMA Control Register    */
+	ICH_REG_ALI_FIFOCR1 = 0x0c,	/* FIFO Control Register 1  */
+	ICH_REG_ALI_INTERFACECR = 0x10,	/* Interface Control Register */
+	ICH_REG_ALI_INTERRUPTCR = 0x14,	/* Interrupt control Register */
+	ICH_REG_ALI_INTERRUPTSR = 0x18,	/* Interrupt  Status Register */
+	ICH_REG_ALI_FIFOCR2 = 0x1c,	/* FIFO Control Register 2   */
+	ICH_REG_ALI_CPR = 0x20,		/* Command Port Register     */
+	ICH_REG_ALI_CPR_ADDR = 0x22,	/* ac97 addr write */
+	ICH_REG_ALI_SPR = 0x24,		/* Status Port Register      */
+	ICH_REG_ALI_SPR_ADDR = 0x26,	/* ac97 addr read */
+	ICH_REG_ALI_FIFOCR3 = 0x2c,	/* FIFO Control Register 3  */
+	ICH_REG_ALI_TTSR = 0x30,	/* Transmit Tag Slot Register */
+	ICH_REG_ALI_RTSR = 0x34,	/* Receive Tag Slot  Register */
+	ICH_REG_ALI_CSPSR = 0x38,	/* Command/Status Port Status Register */
+	ICH_REG_ALI_CAS = 0x3c,		/* Codec Write Semaphore Register */
+	ICH_REG_ALI_HWVOL = 0xf0,	/* hardware volume control/status */
+	ICH_REG_ALI_I2SCR = 0xf4,	/* I2S control/status */
+	ICH_REG_ALI_SPDIFCSR = 0xf8,	/* spdif channel status register  */
+	ICH_REG_ALI_SPDIFICS = 0xfc,	/* spdif interface control/status  */
+};
+
+#define ALI_CAS_SEM_BUSY	0x80000000
+#define ALI_CPR_ADDR_SECONDARY	0x100
+#define ALI_CPR_ADDR_READ	0x80
+#define ALI_CSPSR_CODEC_READY	0x08
+#define ALI_CSPSR_READ_OK	0x02
+#define ALI_CSPSR_WRITE_OK	0x01
+
+/* interrupts for the whole chip by interrupt status register finish */
+ 
+#define ALI_INT_MICIN2		(1<<26)
+#define ALI_INT_PCMIN2		(1<<25)
+#define ALI_INT_I2SIN		(1<<24)
+#define ALI_INT_SPDIFOUT	(1<<23)	/* controller spdif out INTERRUPT */
+#define ALI_INT_SPDIFIN		(1<<22)
+#define ALI_INT_LFEOUT		(1<<21)
+#define ALI_INT_CENTEROUT	(1<<20)
+#define ALI_INT_CODECSPDIFOUT	(1<<19)
+#define ALI_INT_MICIN		(1<<18)
+#define ALI_INT_PCMOUT		(1<<17)
+#define ALI_INT_PCMIN		(1<<16)
+#define ALI_INT_CPRAIS		(1<<7)	/* command port available */
+#define ALI_INT_SPRAIS		(1<<5)	/* status port available */
+#define ALI_INT_GPIO		(1<<1)
+#define ALI_INT_MASK		(ALI_INT_SPDIFOUT|ALI_INT_CODECSPDIFOUT|\
+				 ALI_INT_MICIN|ALI_INT_PCMOUT|ALI_INT_PCMIN)
+
+#define ICH_ALI_SC_RESET	(1<<31)	/* master reset */
+#define ICH_ALI_SC_AC97_DBL	(1<<30)
+#define ICH_ALI_SC_CODEC_SPDF	(3<<20)	/* 1=7/8, 2=6/9, 3=10/11 */
+#define ICH_ALI_SC_IN_BITS	(3<<18)
+#define ICH_ALI_SC_OUT_BITS	(3<<16)
+#define ICH_ALI_SC_6CH_CFG	(3<<14)
+#define ICH_ALI_SC_PCM_4	(1<<8)
+#define ICH_ALI_SC_PCM_6	(2<<8)
+#define ICH_ALI_SC_PCM_246_MASK	(3<<8)
+
+#define ICH_ALI_SS_SEC_ID	(3<<5)
+#define ICH_ALI_SS_PRI_ID	(3<<3)
+
+#define ICH_ALI_IF_AC97SP	(1<<21)
+#define ICH_ALI_IF_MC		(1<<20)
+#define ICH_ALI_IF_PI		(1<<19)
+#define ICH_ALI_IF_MC2		(1<<18)
+#define ICH_ALI_IF_PI2		(1<<17)
+#define ICH_ALI_IF_LINE_SRC	(1<<15)	/* 0/1 = slot 3/6 */
+#define ICH_ALI_IF_MIC_SRC	(1<<14)	/* 0/1 = slot 3/6 */
+#define ICH_ALI_IF_SPDF_SRC	(3<<12)	/* 00 = PCM, 01 = AC97-in, 10 = spdif-in, 11 = i2s */
+#define ICH_ALI_IF_AC97_OUT	(3<<8)	/* 00 = PCM, 10 = spdif-in, 11 = i2s */
+#define ICH_ALI_IF_PO_SPDF	(1<<3)
+#define ICH_ALI_IF_PO		(1<<1)
+
+/*
+ *  
+ */
+
+enum {
+	ICHD_PCMIN,
+	ICHD_PCMOUT,
+	ICHD_MIC,
+	ICHD_MIC2,
+	ICHD_PCM2IN,
+	ICHD_SPBAR,
+	ICHD_LAST = ICHD_SPBAR
+};
+enum {
+	NVD_PCMIN,
+	NVD_PCMOUT,
+	NVD_MIC,
+	NVD_SPBAR,
+	NVD_LAST = NVD_SPBAR
+};
+enum {
+	ALID_PCMIN,
+	ALID_PCMOUT,
+	ALID_MIC,
+	ALID_AC97SPDIFOUT,
+	ALID_SPDIFIN,
+	ALID_SPDIFOUT,
+	ALID_LAST = ALID_SPDIFOUT
+};
+
+#define get_ichdev(substream) (substream->runtime->private_data)
+
+struct ichdev {
+	unsigned int ichd;			/* ich device number */
+	unsigned long reg_offset;		/* offset to bmaddr */
+	u32 *bdbar;				/* CPU address (32bit) */
+	unsigned int bdbar_addr;		/* PCI bus address (32bit) */
+	struct snd_pcm_substream *substream;
+	unsigned int physbuf;			/* physical address (32bit) */
+        unsigned int size;
+        unsigned int fragsize;
+        unsigned int fragsize1;
+        unsigned int position;
+	unsigned int pos_shift;
+	unsigned int last_pos;
+        int frags;
+        int lvi;
+        int lvi_frag;
+	int civ;
+	int ack;
+	int ack_reload;
+	unsigned int ack_bit;
+	unsigned int roff_sr;
+	unsigned int roff_picb;
+	unsigned int int_sta_mask;		/* interrupt status mask */
+	unsigned int ali_slot;			/* ALI DMA slot */
+	struct ac97_pcm *pcm;
+	int pcm_open_flag;
+	unsigned int page_attr_changed: 1;
+	unsigned int suspended: 1;
+};
+
+struct intel8x0 {
+	unsigned int device_type;
+
+	int irq;
+
+	void __iomem *addr;
+	void __iomem *bmaddr;
+
+	struct pci_dev *pci;
+	struct snd_card *card;
+
+	int pcm_devs;
+	struct snd_pcm *pcm[6];
+	struct ichdev ichd[6];
+
+	unsigned multi4: 1,
+		 multi6: 1,
+		 multi8 :1,
+		 dra: 1,
+		 smp20bit: 1;
+	unsigned in_ac97_init: 1,
+		 in_sdin_init: 1;
+	unsigned in_measurement: 1;	/* during ac97 clock measurement */
+	unsigned fix_nocache: 1; 	/* workaround for 440MX */
+	unsigned buggy_irq: 1;		/* workaround for buggy mobos */
+	unsigned xbox: 1;		/* workaround for Xbox AC'97 detection */
+	unsigned buggy_semaphore: 1;	/* workaround for buggy codec semaphore */
+
+	int spdif_idx;	/* SPDIF BAR index; *_SPBAR or -1 if use PCMOUT */
+	unsigned int sdm_saved;	/* SDM reg value */
+
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97 *ac97[3];
+	unsigned int ac97_sdin[3];
+	unsigned int max_codecs, ncodecs;
+	unsigned int *codec_bit;
+	unsigned int codec_isr_bits;
+	unsigned int codec_ready_bits;
+
+	spinlock_t reg_lock;
+	
+	u32 bdbars_count;
+	struct snd_dma_buffer bdbars;
+	u32 int_sta_reg;		/* interrupt status register */
+	u32 int_sta_mask;		/* interrupt status mask */
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_intel8x0_ids) = {
+	{ PCI_VDEVICE(INTEL, 0x2415), DEVICE_INTEL },	/* 82801AA */
+	{ PCI_VDEVICE(INTEL, 0x2425), DEVICE_INTEL },	/* 82901AB */
+	{ PCI_VDEVICE(INTEL, 0x2445), DEVICE_INTEL },	/* 82801BA */
+	{ PCI_VDEVICE(INTEL, 0x2485), DEVICE_INTEL },	/* ICH3 */
+	{ PCI_VDEVICE(INTEL, 0x24c5), DEVICE_INTEL_ICH4 }, /* ICH4 */
+	{ PCI_VDEVICE(INTEL, 0x24d5), DEVICE_INTEL_ICH4 }, /* ICH5 */
+	{ PCI_VDEVICE(INTEL, 0x25a6), DEVICE_INTEL_ICH4 }, /* ESB */
+	{ PCI_VDEVICE(INTEL, 0x266e), DEVICE_INTEL_ICH4 }, /* ICH6 */
+	{ PCI_VDEVICE(INTEL, 0x27de), DEVICE_INTEL_ICH4 }, /* ICH7 */
+	{ PCI_VDEVICE(INTEL, 0x2698), DEVICE_INTEL_ICH4 }, /* ESB2 */
+	{ PCI_VDEVICE(INTEL, 0x7195), DEVICE_INTEL },	/* 440MX */
+	{ PCI_VDEVICE(SI, 0x7012), DEVICE_SIS },	/* SI7012 */
+	{ PCI_VDEVICE(NVIDIA, 0x01b1), DEVICE_NFORCE },	/* NFORCE */
+	{ PCI_VDEVICE(NVIDIA, 0x003a), DEVICE_NFORCE },	/* MCP04 */
+	{ PCI_VDEVICE(NVIDIA, 0x006a), DEVICE_NFORCE },	/* NFORCE2 */
+	{ PCI_VDEVICE(NVIDIA, 0x0059), DEVICE_NFORCE },	/* CK804 */
+	{ PCI_VDEVICE(NVIDIA, 0x008a), DEVICE_NFORCE },	/* CK8 */
+	{ PCI_VDEVICE(NVIDIA, 0x00da), DEVICE_NFORCE },	/* NFORCE3 */
+	{ PCI_VDEVICE(NVIDIA, 0x00ea), DEVICE_NFORCE },	/* CK8S */
+	{ PCI_VDEVICE(NVIDIA, 0x026b), DEVICE_NFORCE },	/* MCP51 */
+	{ PCI_VDEVICE(AMD, 0x746d), DEVICE_INTEL },	/* AMD8111 */
+	{ PCI_VDEVICE(AMD, 0x7445), DEVICE_INTEL },	/* AMD768 */
+	{ PCI_VDEVICE(AL, 0x5455), DEVICE_ALI },   /* Ali5455 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_intel8x0_ids);
+
+/*
+ *  Lowlevel I/O - busmaster
+ */
+
+static inline u8 igetbyte(struct intel8x0 *chip, u32 offset)
+{
+	return ioread8(chip->bmaddr + offset);
+}
+
+static inline u16 igetword(struct intel8x0 *chip, u32 offset)
+{
+	return ioread16(chip->bmaddr + offset);
+}
+
+static inline u32 igetdword(struct intel8x0 *chip, u32 offset)
+{
+	return ioread32(chip->bmaddr + offset);
+}
+
+static inline void iputbyte(struct intel8x0 *chip, u32 offset, u8 val)
+{
+	iowrite8(val, chip->bmaddr + offset);
+}
+
+static inline void iputword(struct intel8x0 *chip, u32 offset, u16 val)
+{
+	iowrite16(val, chip->bmaddr + offset);
+}
+
+static inline void iputdword(struct intel8x0 *chip, u32 offset, u32 val)
+{
+	iowrite32(val, chip->bmaddr + offset);
+}
+
+/*
+ *  Lowlevel I/O - AC'97 registers
+ */
+
+static inline u16 iagetword(struct intel8x0 *chip, u32 offset)
+{
+	return ioread16(chip->addr + offset);
+}
+
+static inline void iaputword(struct intel8x0 *chip, u32 offset, u16 val)
+{
+	iowrite16(val, chip->addr + offset);
+}
+
+/*
+ *  Basic I/O
+ */
+
+/*
+ * access to AC97 codec via normal i/o (for ICH and SIS7012)
+ */
+
+static int snd_intel8x0_codec_semaphore(struct intel8x0 *chip, unsigned int codec)
+{
+	int time;
+	
+	if (codec > 2)
+		return -EIO;
+	if (chip->in_sdin_init) {
+		/* we don't know the ready bit assignment at the moment */
+		/* so we check any */
+		codec = chip->codec_isr_bits;
+	} else {
+		codec = chip->codec_bit[chip->ac97_sdin[codec]];
+	}
+
+	/* codec ready ? */
+	if ((igetdword(chip, ICHREG(GLOB_STA)) & codec) == 0)
+		return -EIO;
+
+	if (chip->buggy_semaphore)
+		return 0; /* just ignore ... */
+
+	/* Anyone holding a semaphore for 1 msec should be shot... */
+	time = 100;
+      	do {
+      		if (!(igetbyte(chip, ICHREG(ACC_SEMA)) & ICH_CAS))
+      			return 0;
+		udelay(10);
+	} while (time--);
+
+	/* access to some forbidden (non existant) ac97 registers will not
+	 * reset the semaphore. So even if you don't get the semaphore, still
+	 * continue the access. We don't need the semaphore anyway. */
+	snd_printk(KERN_ERR "codec_semaphore: semaphore is not ready [0x%x][0x%x]\n",
+			igetbyte(chip, ICHREG(ACC_SEMA)), igetdword(chip, ICHREG(GLOB_STA)));
+	iagetword(chip, 0);	/* clear semaphore flag */
+	/* I don't care about the semaphore */
+	return -EBUSY;
+}
+ 
+static void snd_intel8x0_codec_write(struct snd_ac97 *ac97,
+				     unsigned short reg,
+				     unsigned short val)
+{
+	struct intel8x0 *chip = ac97->private_data;
+	
+	if (snd_intel8x0_codec_semaphore(chip, ac97->num) < 0) {
+		if (! chip->in_ac97_init)
+			snd_printk(KERN_ERR "codec_write %d: semaphore is not ready for register 0x%x\n", ac97->num, reg);
+	}
+	iaputword(chip, reg + ac97->num * 0x80, val);
+}
+
+static unsigned short snd_intel8x0_codec_read(struct snd_ac97 *ac97,
+					      unsigned short reg)
+{
+	struct intel8x0 *chip = ac97->private_data;
+	unsigned short res;
+	unsigned int tmp;
+
+	if (snd_intel8x0_codec_semaphore(chip, ac97->num) < 0) {
+		if (! chip->in_ac97_init)
+			snd_printk(KERN_ERR "codec_read %d: semaphore is not ready for register 0x%x\n", ac97->num, reg);
+		res = 0xffff;
+	} else {
+		res = iagetword(chip, reg + ac97->num * 0x80);
+		if ((tmp = igetdword(chip, ICHREG(GLOB_STA))) & ICH_RCS) {
+			/* reset RCS and preserve other R/WC bits */
+			iputdword(chip, ICHREG(GLOB_STA), tmp &
+				  ~(chip->codec_ready_bits | ICH_GSCI));
+			if (! chip->in_ac97_init)
+				snd_printk(KERN_ERR "codec_read %d: read timeout for register 0x%x\n", ac97->num, reg);
+			res = 0xffff;
+		}
+	}
+	return res;
+}
+
+static void __devinit snd_intel8x0_codec_read_test(struct intel8x0 *chip,
+						   unsigned int codec)
+{
+	unsigned int tmp;
+
+	if (snd_intel8x0_codec_semaphore(chip, codec) >= 0) {
+		iagetword(chip, codec * 0x80);
+		if ((tmp = igetdword(chip, ICHREG(GLOB_STA))) & ICH_RCS) {
+			/* reset RCS and preserve other R/WC bits */
+			iputdword(chip, ICHREG(GLOB_STA), tmp &
+				  ~(chip->codec_ready_bits | ICH_GSCI));
+		}
+	}
+}
+
+/*
+ * access to AC97 for Ali5455
+ */
+static int snd_intel8x0_ali_codec_ready(struct intel8x0 *chip, int mask)
+{
+	int count = 0;
+	for (count = 0; count < 0x7f; count++) {
+		int val = igetbyte(chip, ICHREG(ALI_CSPSR));
+		if (val & mask)
+			return 0;
+	}
+	if (! chip->in_ac97_init)
+		snd_printd(KERN_WARNING "intel8x0: AC97 codec ready timeout.\n");
+	return -EBUSY;
+}
+
+static int snd_intel8x0_ali_codec_semaphore(struct intel8x0 *chip)
+{
+	int time = 100;
+	if (chip->buggy_semaphore)
+		return 0; /* just ignore ... */
+	while (--time && (igetdword(chip, ICHREG(ALI_CAS)) & ALI_CAS_SEM_BUSY))
+		udelay(1);
+	if (! time && ! chip->in_ac97_init)
+		snd_printk(KERN_WARNING "ali_codec_semaphore timeout\n");
+	return snd_intel8x0_ali_codec_ready(chip, ALI_CSPSR_CODEC_READY);
+}
+
+static unsigned short snd_intel8x0_ali_codec_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct intel8x0 *chip = ac97->private_data;
+	unsigned short data = 0xffff;
+
+	if (snd_intel8x0_ali_codec_semaphore(chip))
+		goto __err;
+	reg |= ALI_CPR_ADDR_READ;
+	if (ac97->num)
+		reg |= ALI_CPR_ADDR_SECONDARY;
+	iputword(chip, ICHREG(ALI_CPR_ADDR), reg);
+	if (snd_intel8x0_ali_codec_ready(chip, ALI_CSPSR_READ_OK))
+		goto __err;
+	data = igetword(chip, ICHREG(ALI_SPR));
+ __err:
+	return data;
+}
+
+static void snd_intel8x0_ali_codec_write(struct snd_ac97 *ac97, unsigned short reg,
+					 unsigned short val)
+{
+	struct intel8x0 *chip = ac97->private_data;
+
+	if (snd_intel8x0_ali_codec_semaphore(chip))
+		return;
+	iputword(chip, ICHREG(ALI_CPR), val);
+	if (ac97->num)
+		reg |= ALI_CPR_ADDR_SECONDARY;
+	iputword(chip, ICHREG(ALI_CPR_ADDR), reg);
+	snd_intel8x0_ali_codec_ready(chip, ALI_CSPSR_WRITE_OK);
+}
+
+
+/*
+ * DMA I/O
+ */
+static void snd_intel8x0_setup_periods(struct intel8x0 *chip, struct ichdev *ichdev) 
+{
+	int idx;
+	u32 *bdbar = ichdev->bdbar;
+	unsigned long port = ichdev->reg_offset;
+
+	iputdword(chip, port + ICH_REG_OFF_BDBAR, ichdev->bdbar_addr);
+	if (ichdev->size == ichdev->fragsize) {
+		ichdev->ack_reload = ichdev->ack = 2;
+		ichdev->fragsize1 = ichdev->fragsize >> 1;
+		for (idx = 0; idx < (ICH_REG_LVI_MASK + 1) * 2; idx += 4) {
+			bdbar[idx + 0] = cpu_to_le32(ichdev->physbuf);
+			bdbar[idx + 1] = cpu_to_le32(0x80000000 | /* interrupt on completion */
+						     ichdev->fragsize1 >> ichdev->pos_shift);
+			bdbar[idx + 2] = cpu_to_le32(ichdev->physbuf + (ichdev->size >> 1));
+			bdbar[idx + 3] = cpu_to_le32(0x80000000 | /* interrupt on completion */
+						     ichdev->fragsize1 >> ichdev->pos_shift);
+		}
+		ichdev->frags = 2;
+	} else {
+		ichdev->ack_reload = ichdev->ack = 1;
+		ichdev->fragsize1 = ichdev->fragsize;
+		for (idx = 0; idx < (ICH_REG_LVI_MASK + 1) * 2; idx += 2) {
+			bdbar[idx + 0] = cpu_to_le32(ichdev->physbuf +
+						     (((idx >> 1) * ichdev->fragsize) %
+						      ichdev->size));
+			bdbar[idx + 1] = cpu_to_le32(0x80000000 | /* interrupt on completion */
+						     ichdev->fragsize >> ichdev->pos_shift);
+#if 0
+			printk(KERN_DEBUG "bdbar[%i] = 0x%x [0x%x]\n",
+			       idx + 0, bdbar[idx + 0], bdbar[idx + 1]);
+#endif
+		}
+		ichdev->frags = ichdev->size / ichdev->fragsize;
+	}
+	iputbyte(chip, port + ICH_REG_OFF_LVI, ichdev->lvi = ICH_REG_LVI_MASK);
+	ichdev->civ = 0;
+	iputbyte(chip, port + ICH_REG_OFF_CIV, 0);
+	ichdev->lvi_frag = ICH_REG_LVI_MASK % ichdev->frags;
+	ichdev->position = 0;
+#if 0
+	printk(KERN_DEBUG "lvi_frag = %i, frags = %i, period_size = 0x%x, "
+	       "period_size1 = 0x%x\n",
+	       ichdev->lvi_frag, ichdev->frags, ichdev->fragsize,
+	       ichdev->fragsize1);
+#endif
+	/* clear interrupts */
+	iputbyte(chip, port + ichdev->roff_sr, ICH_FIFOE | ICH_BCIS | ICH_LVBCI);
+}
+
+#ifdef __i386__
+/*
+ * Intel 82443MX running a 100MHz processor system bus has a hardware bug,
+ * which aborts PCI busmaster for audio transfer.  A workaround is to set
+ * the pages as non-cached.  For details, see the errata in
+ *	http://download.intel.com/design/chipsets/specupdt/24505108.pdf
+ */
+static void fill_nocache(void *buf, int size, int nocache)
+{
+	size = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
+	if (nocache)
+		set_pages_uc(virt_to_page(buf), size);
+	else
+		set_pages_wb(virt_to_page(buf), size);
+}
+#else
+#define fill_nocache(buf, size, nocache) do { ; } while (0)
+#endif
+
+/*
+ *  Interrupt handler
+ */
+
+static inline void snd_intel8x0_update(struct intel8x0 *chip, struct ichdev *ichdev)
+{
+	unsigned long port = ichdev->reg_offset;
+	unsigned long flags;
+	int status, civ, i, step;
+	int ack = 0;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	status = igetbyte(chip, port + ichdev->roff_sr);
+	civ = igetbyte(chip, port + ICH_REG_OFF_CIV);
+	if (!(status & ICH_BCIS)) {
+		step = 0;
+	} else if (civ == ichdev->civ) {
+		// snd_printd("civ same %d\n", civ);
+		step = 1;
+		ichdev->civ++;
+		ichdev->civ &= ICH_REG_LVI_MASK;
+	} else {
+		step = civ - ichdev->civ;
+		if (step < 0)
+			step += ICH_REG_LVI_MASK + 1;
+		// if (step != 1)
+		//	snd_printd("step = %d, %d -> %d\n", step, ichdev->civ, civ);
+		ichdev->civ = civ;
+	}
+
+	ichdev->position += step * ichdev->fragsize1;
+	if (! chip->in_measurement)
+		ichdev->position %= ichdev->size;
+	ichdev->lvi += step;
+	ichdev->lvi &= ICH_REG_LVI_MASK;
+	iputbyte(chip, port + ICH_REG_OFF_LVI, ichdev->lvi);
+	for (i = 0; i < step; i++) {
+		ichdev->lvi_frag++;
+		ichdev->lvi_frag %= ichdev->frags;
+		ichdev->bdbar[ichdev->lvi * 2] = cpu_to_le32(ichdev->physbuf + ichdev->lvi_frag * ichdev->fragsize1);
+#if 0
+	printk(KERN_DEBUG "new: bdbar[%i] = 0x%x [0x%x], prefetch = %i, "
+	       "all = 0x%x, 0x%x\n",
+	       ichdev->lvi * 2, ichdev->bdbar[ichdev->lvi * 2],
+	       ichdev->bdbar[ichdev->lvi * 2 + 1], inb(ICH_REG_OFF_PIV + port),
+	       inl(port + 4), inb(port + ICH_REG_OFF_CR));
+#endif
+		if (--ichdev->ack == 0) {
+			ichdev->ack = ichdev->ack_reload;
+			ack = 1;
+		}
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (ack && ichdev->substream) {
+		snd_pcm_period_elapsed(ichdev->substream);
+	}
+	iputbyte(chip, port + ichdev->roff_sr,
+		 status & (ICH_FIFOE | ICH_BCIS | ICH_LVBCI));
+}
+
+static irqreturn_t snd_intel8x0_interrupt(int irq, void *dev_id)
+{
+	struct intel8x0 *chip = dev_id;
+	struct ichdev *ichdev;
+	unsigned int status;
+	unsigned int i;
+
+	status = igetdword(chip, chip->int_sta_reg);
+	if (status == 0xffffffff)	/* we are not yet resumed */
+		return IRQ_NONE;
+
+	if ((status & chip->int_sta_mask) == 0) {
+		if (status) {
+			/* ack */
+			iputdword(chip, chip->int_sta_reg, status);
+			if (! chip->buggy_irq)
+				status = 0;
+		}
+		return IRQ_RETVAL(status);
+	}
+
+	for (i = 0; i < chip->bdbars_count; i++) {
+		ichdev = &chip->ichd[i];
+		if (status & ichdev->int_sta_mask)
+			snd_intel8x0_update(chip, ichdev);
+	}
+
+	/* ack them */
+	iputdword(chip, chip->int_sta_reg, status & chip->int_sta_mask);
+	
+	return IRQ_HANDLED;
+}
+
+/*
+ *  PCM part
+ */
+
+static int snd_intel8x0_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	struct ichdev *ichdev = get_ichdev(substream);
+	unsigned char val = 0;
+	unsigned long port = ichdev->reg_offset;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_RESUME:
+		ichdev->suspended = 0;
+		/* fallthru */
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		val = ICH_IOCE | ICH_STARTBM;
+		ichdev->last_pos = ichdev->position;
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		ichdev->suspended = 1;
+		/* fallthru */
+	case SNDRV_PCM_TRIGGER_STOP:
+		val = 0;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		val = ICH_IOCE;
+		break;
+	default:
+		return -EINVAL;
+	}
+	iputbyte(chip, port + ICH_REG_OFF_CR, val);
+	if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		/* wait until DMA stopped */
+		while (!(igetbyte(chip, port + ichdev->roff_sr) & ICH_DCH)) ;
+		/* reset whole DMA things */
+		iputbyte(chip, port + ICH_REG_OFF_CR, ICH_RESETREGS);
+	}
+	return 0;
+}
+
+static int snd_intel8x0_ali_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	struct ichdev *ichdev = get_ichdev(substream);
+	unsigned long port = ichdev->reg_offset;
+	static int fiforeg[] = {
+		ICHREG(ALI_FIFOCR1), ICHREG(ALI_FIFOCR2), ICHREG(ALI_FIFOCR3)
+	};
+	unsigned int val, fifo;
+
+	val = igetdword(chip, ICHREG(ALI_DMACR));
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_RESUME:
+		ichdev->suspended = 0;
+		/* fallthru */
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			/* clear FIFO for synchronization of channels */
+			fifo = igetdword(chip, fiforeg[ichdev->ali_slot / 4]);
+			fifo &= ~(0xff << (ichdev->ali_slot % 4));  
+			fifo |= 0x83 << (ichdev->ali_slot % 4); 
+			iputdword(chip, fiforeg[ichdev->ali_slot / 4], fifo);
+		}
+		iputbyte(chip, port + ICH_REG_OFF_CR, ICH_IOCE);
+		val &= ~(1 << (ichdev->ali_slot + 16)); /* clear PAUSE flag */
+		/* start DMA */
+		iputdword(chip, ICHREG(ALI_DMACR), val | (1 << ichdev->ali_slot));
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		ichdev->suspended = 1;
+		/* fallthru */
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		/* pause */
+		iputdword(chip, ICHREG(ALI_DMACR), val | (1 << (ichdev->ali_slot + 16)));
+		iputbyte(chip, port + ICH_REG_OFF_CR, 0);
+		while (igetbyte(chip, port + ICH_REG_OFF_CR))
+			;
+		if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)
+			break;
+		/* reset whole DMA things */
+		iputbyte(chip, port + ICH_REG_OFF_CR, ICH_RESETREGS);
+		/* clear interrupts */
+		iputbyte(chip, port + ICH_REG_OFF_SR,
+			 igetbyte(chip, port + ICH_REG_OFF_SR) | 0x1e);
+		iputdword(chip, ICHREG(ALI_INTERRUPTSR),
+			  igetdword(chip, ICHREG(ALI_INTERRUPTSR)) & ichdev->int_sta_mask);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int snd_intel8x0_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	struct ichdev *ichdev = get_ichdev(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int dbl = params_rate(hw_params) > 48000;
+	int err;
+
+	if (chip->fix_nocache && ichdev->page_attr_changed) {
+		fill_nocache(runtime->dma_area, runtime->dma_bytes, 0); /* clear */
+		ichdev->page_attr_changed = 0;
+	}
+	err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	if (chip->fix_nocache) {
+		if (runtime->dma_area && ! ichdev->page_attr_changed) {
+			fill_nocache(runtime->dma_area, runtime->dma_bytes, 1);
+			ichdev->page_attr_changed = 1;
+		}
+	}
+	if (ichdev->pcm_open_flag) {
+		snd_ac97_pcm_close(ichdev->pcm);
+		ichdev->pcm_open_flag = 0;
+	}
+	err = snd_ac97_pcm_open(ichdev->pcm, params_rate(hw_params),
+				params_channels(hw_params),
+				ichdev->pcm->r[dbl].slots);
+	if (err >= 0) {
+		ichdev->pcm_open_flag = 1;
+		/* Force SPDIF setting */
+		if (ichdev->ichd == ICHD_PCMOUT && chip->spdif_idx < 0)
+			snd_ac97_set_rate(ichdev->pcm->r[0].codec[0], AC97_SPDIF,
+					  params_rate(hw_params));
+	}
+	return err;
+}
+
+static int snd_intel8x0_hw_free(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	struct ichdev *ichdev = get_ichdev(substream);
+
+	if (ichdev->pcm_open_flag) {
+		snd_ac97_pcm_close(ichdev->pcm);
+		ichdev->pcm_open_flag = 0;
+	}
+	if (chip->fix_nocache && ichdev->page_attr_changed) {
+		fill_nocache(substream->runtime->dma_area, substream->runtime->dma_bytes, 0);
+		ichdev->page_attr_changed = 0;
+	}
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static void snd_intel8x0_setup_pcm_out(struct intel8x0 *chip,
+				       struct snd_pcm_runtime *runtime)
+{
+	unsigned int cnt;
+	int dbl = runtime->rate > 48000;
+
+	spin_lock_irq(&chip->reg_lock);
+	switch (chip->device_type) {
+	case DEVICE_ALI:
+		cnt = igetdword(chip, ICHREG(ALI_SCR));
+		cnt &= ~ICH_ALI_SC_PCM_246_MASK;
+		if (runtime->channels == 4 || dbl)
+			cnt |= ICH_ALI_SC_PCM_4;
+		else if (runtime->channels == 6)
+			cnt |= ICH_ALI_SC_PCM_6;
+		iputdword(chip, ICHREG(ALI_SCR), cnt);
+		break;
+	case DEVICE_SIS:
+		cnt = igetdword(chip, ICHREG(GLOB_CNT));
+		cnt &= ~ICH_SIS_PCM_246_MASK;
+		if (runtime->channels == 4 || dbl)
+			cnt |= ICH_SIS_PCM_4;
+		else if (runtime->channels == 6)
+			cnt |= ICH_SIS_PCM_6;
+		iputdword(chip, ICHREG(GLOB_CNT), cnt);
+		break;
+	default:
+		cnt = igetdword(chip, ICHREG(GLOB_CNT));
+		cnt &= ~(ICH_PCM_246_MASK | ICH_PCM_20BIT);
+		if (runtime->channels == 4 || dbl)
+			cnt |= ICH_PCM_4;
+		else if (runtime->channels == 6)
+			cnt |= ICH_PCM_6;
+		else if (runtime->channels == 8)
+			cnt |= ICH_PCM_8;
+		if (chip->device_type == DEVICE_NFORCE) {
+			/* reset to 2ch once to keep the 6 channel data in alignment,
+			 * to start from Front Left always
+			 */
+			if (cnt & ICH_PCM_246_MASK) {
+				iputdword(chip, ICHREG(GLOB_CNT), cnt & ~ICH_PCM_246_MASK);
+				spin_unlock_irq(&chip->reg_lock);
+				msleep(50); /* grrr... */
+				spin_lock_irq(&chip->reg_lock);
+			}
+		} else if (chip->device_type == DEVICE_INTEL_ICH4) {
+			if (runtime->sample_bits > 16)
+				cnt |= ICH_PCM_20BIT;
+		}
+		iputdword(chip, ICHREG(GLOB_CNT), cnt);
+		break;
+	}
+	spin_unlock_irq(&chip->reg_lock);
+}
+
+static int snd_intel8x0_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ichdev *ichdev = get_ichdev(substream);
+
+	ichdev->physbuf = runtime->dma_addr;
+	ichdev->size = snd_pcm_lib_buffer_bytes(substream);
+	ichdev->fragsize = snd_pcm_lib_period_bytes(substream);
+	if (ichdev->ichd == ICHD_PCMOUT) {
+		snd_intel8x0_setup_pcm_out(chip, runtime);
+		if (chip->device_type == DEVICE_INTEL_ICH4)
+			ichdev->pos_shift = (runtime->sample_bits > 16) ? 2 : 1;
+	}
+	snd_intel8x0_setup_periods(chip, ichdev);
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_intel8x0_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	struct ichdev *ichdev = get_ichdev(substream);
+	size_t ptr1, ptr;
+	int civ, timeout = 10;
+	unsigned int position;
+
+	spin_lock(&chip->reg_lock);
+	do {
+		civ = igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV);
+		ptr1 = igetword(chip, ichdev->reg_offset + ichdev->roff_picb);
+		position = ichdev->position;
+		if (ptr1 == 0) {
+			udelay(10);
+			continue;
+		}
+		if (civ == igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV) &&
+		    ptr1 == igetword(chip, ichdev->reg_offset + ichdev->roff_picb))
+			break;
+	} while (timeout--);
+	ptr = ichdev->last_pos;
+	if (ptr1 != 0) {
+		ptr1 <<= ichdev->pos_shift;
+		ptr = ichdev->fragsize1 - ptr1;
+		ptr += position;
+		if (ptr < ichdev->last_pos) {
+			unsigned int pos_base, last_base;
+			pos_base = position / ichdev->fragsize1;
+			last_base = ichdev->last_pos / ichdev->fragsize1;
+			/* another sanity check; ptr1 can go back to full
+			 * before the base position is updated
+			 */
+			if (pos_base == last_base)
+				ptr = ichdev->last_pos;
+		}
+	}
+	ichdev->last_pos = ptr;
+	spin_unlock(&chip->reg_lock);
+	if (ptr >= ichdev->size)
+		return 0;
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static struct snd_pcm_hardware snd_intel8x0_stream =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	128 * 1024,
+	.period_bytes_min =	32,
+	.period_bytes_max =	128 * 1024,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static unsigned int channels4[] = {
+	2, 4,
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_channels4 = {
+	.count = ARRAY_SIZE(channels4),
+	.list = channels4,
+	.mask = 0,
+};
+
+static unsigned int channels6[] = {
+	2, 4, 6,
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_channels6 = {
+	.count = ARRAY_SIZE(channels6),
+	.list = channels6,
+	.mask = 0,
+};
+
+static unsigned int channels8[] = {
+	2, 4, 6, 8,
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_channels8 = {
+	.count = ARRAY_SIZE(channels8),
+	.list = channels8,
+	.mask = 0,
+};
+
+static int snd_intel8x0_pcm_open(struct snd_pcm_substream *substream, struct ichdev *ichdev)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	ichdev->substream = substream;
+	runtime->hw = snd_intel8x0_stream;
+	runtime->hw.rates = ichdev->pcm->rates;
+	snd_pcm_limit_hw_rates(runtime);
+	if (chip->device_type == DEVICE_SIS) {
+		runtime->hw.buffer_bytes_max = 64*1024;
+		runtime->hw.period_bytes_max = 64*1024;
+	}
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+	runtime->private_data = ichdev;
+	return 0;
+}
+
+static int snd_intel8x0_playback_open(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	err = snd_intel8x0_pcm_open(substream, &chip->ichd[ICHD_PCMOUT]);
+	if (err < 0)
+		return err;
+
+	if (chip->multi8) {
+		runtime->hw.channels_max = 8;
+		snd_pcm_hw_constraint_list(runtime, 0,
+						SNDRV_PCM_HW_PARAM_CHANNELS,
+						&hw_constraints_channels8);
+	} else if (chip->multi6) {
+		runtime->hw.channels_max = 6;
+		snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+					   &hw_constraints_channels6);
+	} else if (chip->multi4) {
+		runtime->hw.channels_max = 4;
+		snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+					   &hw_constraints_channels4);
+	}
+	if (chip->dra) {
+		snd_ac97_pcm_double_rate_rules(runtime);
+	}
+	if (chip->smp20bit) {
+		runtime->hw.formats |= SNDRV_PCM_FMTBIT_S32_LE;
+		snd_pcm_hw_constraint_msbits(runtime, 0, 32, 20);
+	}
+	return 0;
+}
+
+static int snd_intel8x0_playback_close(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	chip->ichd[ICHD_PCMOUT].substream = NULL;
+	return 0;
+}
+
+static int snd_intel8x0_capture_open(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	return snd_intel8x0_pcm_open(substream, &chip->ichd[ICHD_PCMIN]);
+}
+
+static int snd_intel8x0_capture_close(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	chip->ichd[ICHD_PCMIN].substream = NULL;
+	return 0;
+}
+
+static int snd_intel8x0_mic_open(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	return snd_intel8x0_pcm_open(substream, &chip->ichd[ICHD_MIC]);
+}
+
+static int snd_intel8x0_mic_close(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	chip->ichd[ICHD_MIC].substream = NULL;
+	return 0;
+}
+
+static int snd_intel8x0_mic2_open(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	return snd_intel8x0_pcm_open(substream, &chip->ichd[ICHD_MIC2]);
+}
+
+static int snd_intel8x0_mic2_close(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	chip->ichd[ICHD_MIC2].substream = NULL;
+	return 0;
+}
+
+static int snd_intel8x0_capture2_open(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	return snd_intel8x0_pcm_open(substream, &chip->ichd[ICHD_PCM2IN]);
+}
+
+static int snd_intel8x0_capture2_close(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	chip->ichd[ICHD_PCM2IN].substream = NULL;
+	return 0;
+}
+
+static int snd_intel8x0_spdif_open(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	int idx = chip->device_type == DEVICE_NFORCE ? NVD_SPBAR : ICHD_SPBAR;
+
+	return snd_intel8x0_pcm_open(substream, &chip->ichd[idx]);
+}
+
+static int snd_intel8x0_spdif_close(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	int idx = chip->device_type == DEVICE_NFORCE ? NVD_SPBAR : ICHD_SPBAR;
+
+	chip->ichd[idx].substream = NULL;
+	return 0;
+}
+
+static int snd_intel8x0_ali_ac97spdifout_open(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	unsigned int val;
+
+	spin_lock_irq(&chip->reg_lock);
+	val = igetdword(chip, ICHREG(ALI_INTERFACECR));
+	val |= ICH_ALI_IF_AC97SP;
+	iputdword(chip, ICHREG(ALI_INTERFACECR), val);
+	/* also needs to set ALI_SC_CODEC_SPDF correctly */
+	spin_unlock_irq(&chip->reg_lock);
+
+	return snd_intel8x0_pcm_open(substream, &chip->ichd[ALID_AC97SPDIFOUT]);
+}
+
+static int snd_intel8x0_ali_ac97spdifout_close(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+	unsigned int val;
+
+	chip->ichd[ALID_AC97SPDIFOUT].substream = NULL;
+	spin_lock_irq(&chip->reg_lock);
+	val = igetdword(chip, ICHREG(ALI_INTERFACECR));
+	val &= ~ICH_ALI_IF_AC97SP;
+	iputdword(chip, ICHREG(ALI_INTERFACECR), val);
+	spin_unlock_irq(&chip->reg_lock);
+
+	return 0;
+}
+
+#if 0 // NYI
+static int snd_intel8x0_ali_spdifin_open(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	return snd_intel8x0_pcm_open(substream, &chip->ichd[ALID_SPDIFIN]);
+}
+
+static int snd_intel8x0_ali_spdifin_close(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	chip->ichd[ALID_SPDIFIN].substream = NULL;
+	return 0;
+}
+
+static int snd_intel8x0_ali_spdifout_open(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	return snd_intel8x0_pcm_open(substream, &chip->ichd[ALID_SPDIFOUT]);
+}
+
+static int snd_intel8x0_ali_spdifout_close(struct snd_pcm_substream *substream)
+{
+	struct intel8x0 *chip = snd_pcm_substream_chip(substream);
+
+	chip->ichd[ALID_SPDIFOUT].substream = NULL;
+	return 0;
+}
+#endif
+
+static struct snd_pcm_ops snd_intel8x0_playback_ops = {
+	.open =		snd_intel8x0_playback_open,
+	.close =	snd_intel8x0_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_pcm_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intel8x0_capture_ops = {
+	.open =		snd_intel8x0_capture_open,
+	.close =	snd_intel8x0_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_pcm_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intel8x0_capture_mic_ops = {
+	.open =		snd_intel8x0_mic_open,
+	.close =	snd_intel8x0_mic_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_pcm_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intel8x0_capture_mic2_ops = {
+	.open =		snd_intel8x0_mic2_open,
+	.close =	snd_intel8x0_mic2_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_pcm_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intel8x0_capture2_ops = {
+	.open =		snd_intel8x0_capture2_open,
+	.close =	snd_intel8x0_capture2_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_pcm_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intel8x0_spdif_ops = {
+	.open =		snd_intel8x0_spdif_open,
+	.close =	snd_intel8x0_spdif_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_pcm_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intel8x0_ali_playback_ops = {
+	.open =		snd_intel8x0_playback_open,
+	.close =	snd_intel8x0_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_ali_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intel8x0_ali_capture_ops = {
+	.open =		snd_intel8x0_capture_open,
+	.close =	snd_intel8x0_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_ali_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intel8x0_ali_capture_mic_ops = {
+	.open =		snd_intel8x0_mic_open,
+	.close =	snd_intel8x0_mic_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_ali_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intel8x0_ali_ac97spdifout_ops = {
+	.open =		snd_intel8x0_ali_ac97spdifout_open,
+	.close =	snd_intel8x0_ali_ac97spdifout_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_ali_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+#if 0 // NYI
+static struct snd_pcm_ops snd_intel8x0_ali_spdifin_ops = {
+	.open =		snd_intel8x0_ali_spdifin_open,
+	.close =	snd_intel8x0_ali_spdifin_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_pcm_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intel8x0_ali_spdifout_ops = {
+	.open =		snd_intel8x0_ali_spdifout_open,
+	.close =	snd_intel8x0_ali_spdifout_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0_pcm_prepare,
+	.trigger =	snd_intel8x0_pcm_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+#endif // NYI
+
+struct ich_pcm_table {
+	char *suffix;
+	struct snd_pcm_ops *playback_ops;
+	struct snd_pcm_ops *capture_ops;
+	size_t prealloc_size;
+	size_t prealloc_max_size;
+	int ac97_idx;
+};
+
+static int __devinit snd_intel8x0_pcm1(struct intel8x0 *chip, int device,
+				       struct ich_pcm_table *rec)
+{
+	struct snd_pcm *pcm;
+	int err;
+	char name[32];
+
+	if (rec->suffix)
+		sprintf(name, "Intel ICH - %s", rec->suffix);
+	else
+		strcpy(name, "Intel ICH");
+	err = snd_pcm_new(chip->card, name, device,
+			  rec->playback_ops ? 1 : 0,
+			  rec->capture_ops ? 1 : 0, &pcm);
+	if (err < 0)
+		return err;
+
+	if (rec->playback_ops)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, rec->playback_ops);
+	if (rec->capture_ops)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, rec->capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	if (rec->suffix)
+		sprintf(pcm->name, "%s - %s", chip->card->shortname, rec->suffix);
+	else
+		strcpy(pcm->name, chip->card->shortname);
+	chip->pcm[device] = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci),
+					      rec->prealloc_size, rec->prealloc_max_size);
+
+	return 0;
+}
+
+static struct ich_pcm_table intel_pcms[] __devinitdata = {
+	{
+		.playback_ops = &snd_intel8x0_playback_ops,
+		.capture_ops = &snd_intel8x0_capture_ops,
+		.prealloc_size = 64 * 1024,
+		.prealloc_max_size = 128 * 1024,
+	},
+	{
+		.suffix = "MIC ADC",
+		.capture_ops = &snd_intel8x0_capture_mic_ops,
+		.prealloc_size = 0,
+		.prealloc_max_size = 128 * 1024,
+		.ac97_idx = ICHD_MIC,
+	},
+	{
+		.suffix = "MIC2 ADC",
+		.capture_ops = &snd_intel8x0_capture_mic2_ops,
+		.prealloc_size = 0,
+		.prealloc_max_size = 128 * 1024,
+		.ac97_idx = ICHD_MIC2,
+	},
+	{
+		.suffix = "ADC2",
+		.capture_ops = &snd_intel8x0_capture2_ops,
+		.prealloc_size = 0,
+		.prealloc_max_size = 128 * 1024,
+		.ac97_idx = ICHD_PCM2IN,
+	},
+	{
+		.suffix = "IEC958",
+		.playback_ops = &snd_intel8x0_spdif_ops,
+		.prealloc_size = 64 * 1024,
+		.prealloc_max_size = 128 * 1024,
+		.ac97_idx = ICHD_SPBAR,
+	},
+};
+
+static struct ich_pcm_table nforce_pcms[] __devinitdata = {
+	{
+		.playback_ops = &snd_intel8x0_playback_ops,
+		.capture_ops = &snd_intel8x0_capture_ops,
+		.prealloc_size = 64 * 1024,
+		.prealloc_max_size = 128 * 1024,
+	},
+	{
+		.suffix = "MIC ADC",
+		.capture_ops = &snd_intel8x0_capture_mic_ops,
+		.prealloc_size = 0,
+		.prealloc_max_size = 128 * 1024,
+		.ac97_idx = NVD_MIC,
+	},
+	{
+		.suffix = "IEC958",
+		.playback_ops = &snd_intel8x0_spdif_ops,
+		.prealloc_size = 64 * 1024,
+		.prealloc_max_size = 128 * 1024,
+		.ac97_idx = NVD_SPBAR,
+	},
+};
+
+static struct ich_pcm_table ali_pcms[] __devinitdata = {
+	{
+		.playback_ops = &snd_intel8x0_ali_playback_ops,
+		.capture_ops = &snd_intel8x0_ali_capture_ops,
+		.prealloc_size = 64 * 1024,
+		.prealloc_max_size = 128 * 1024,
+	},
+	{
+		.suffix = "MIC ADC",
+		.capture_ops = &snd_intel8x0_ali_capture_mic_ops,
+		.prealloc_size = 0,
+		.prealloc_max_size = 128 * 1024,
+		.ac97_idx = ALID_MIC,
+	},
+	{
+		.suffix = "IEC958",
+		.playback_ops = &snd_intel8x0_ali_ac97spdifout_ops,
+		/* .capture_ops = &snd_intel8x0_ali_spdifin_ops, */
+		.prealloc_size = 64 * 1024,
+		.prealloc_max_size = 128 * 1024,
+		.ac97_idx = ALID_AC97SPDIFOUT,
+	},
+#if 0 // NYI
+	{
+		.suffix = "HW IEC958",
+		.playback_ops = &snd_intel8x0_ali_spdifout_ops,
+		.prealloc_size = 64 * 1024,
+		.prealloc_max_size = 128 * 1024,
+	},
+#endif
+};
+
+static int __devinit snd_intel8x0_pcm(struct intel8x0 *chip)
+{
+	int i, tblsize, device, err;
+	struct ich_pcm_table *tbl, *rec;
+
+	switch (chip->device_type) {
+	case DEVICE_INTEL_ICH4:
+		tbl = intel_pcms;
+		tblsize = ARRAY_SIZE(intel_pcms);
+		if (spdif_aclink)
+			tblsize--;
+		break;
+	case DEVICE_NFORCE:
+		tbl = nforce_pcms;
+		tblsize = ARRAY_SIZE(nforce_pcms);
+		if (spdif_aclink)
+			tblsize--;
+		break;
+	case DEVICE_ALI:
+		tbl = ali_pcms;
+		tblsize = ARRAY_SIZE(ali_pcms);
+		break;
+	default:
+		tbl = intel_pcms;
+		tblsize = 2;
+		break;
+	}
+
+	device = 0;
+	for (i = 0; i < tblsize; i++) {
+		rec = tbl + i;
+		if (i > 0 && rec->ac97_idx) {
+			/* activate PCM only when associated AC'97 codec */
+			if (! chip->ichd[rec->ac97_idx].pcm)
+				continue;
+		}
+		err = snd_intel8x0_pcm1(chip, device, rec);
+		if (err < 0)
+			return err;
+		device++;
+	}
+
+	chip->pcm_devs = device;
+	return 0;
+}
+	
+
+/*
+ *  Mixer part
+ */
+
+static void snd_intel8x0_mixer_free_ac97_bus(struct snd_ac97_bus *bus)
+{
+	struct intel8x0 *chip = bus->private_data;
+	chip->ac97_bus = NULL;
+}
+
+static void snd_intel8x0_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+	struct intel8x0 *chip = ac97->private_data;
+	chip->ac97[ac97->num] = NULL;
+}
+
+static struct ac97_pcm ac97_pcm_defs[] __devinitdata = {
+	/* front PCM */
+	{
+		.exclusive = 1,
+		.r = {	{
+				.slots = (1 << AC97_SLOT_PCM_LEFT) |
+					 (1 << AC97_SLOT_PCM_RIGHT) |
+					 (1 << AC97_SLOT_PCM_CENTER) |
+					 (1 << AC97_SLOT_PCM_SLEFT) |
+					 (1 << AC97_SLOT_PCM_SRIGHT) |
+					 (1 << AC97_SLOT_LFE)
+			},
+			{
+				.slots = (1 << AC97_SLOT_PCM_LEFT) |
+					 (1 << AC97_SLOT_PCM_RIGHT) |
+					 (1 << AC97_SLOT_PCM_LEFT_0) |
+					 (1 << AC97_SLOT_PCM_RIGHT_0)
+			}
+		}
+	},
+	/* PCM IN #1 */
+	{
+		.stream = 1,
+		.exclusive = 1,
+		.r = {	{
+				.slots = (1 << AC97_SLOT_PCM_LEFT) |
+					 (1 << AC97_SLOT_PCM_RIGHT)
+			}
+		}
+	},
+	/* MIC IN #1 */
+	{
+		.stream = 1,
+		.exclusive = 1,
+		.r = {	{
+				.slots = (1 << AC97_SLOT_MIC)
+			}
+		}
+	},
+	/* S/PDIF PCM */
+	{
+		.exclusive = 1,
+		.spdif = 1,
+		.r = {	{
+				.slots = (1 << AC97_SLOT_SPDIF_LEFT2) |
+					 (1 << AC97_SLOT_SPDIF_RIGHT2)
+			}
+		}
+	},
+	/* PCM IN #2 */
+	{
+		.stream = 1,
+		.exclusive = 1,
+		.r = {	{
+				.slots = (1 << AC97_SLOT_PCM_LEFT) |
+					 (1 << AC97_SLOT_PCM_RIGHT)
+			}
+		}
+	},
+	/* MIC IN #2 */
+	{
+		.stream = 1,
+		.exclusive = 1,
+		.r = {	{
+				.slots = (1 << AC97_SLOT_MIC)
+			}
+		}
+	},
+};
+
+static struct ac97_quirk ac97_quirks[] __devinitdata = {
+        {
+		.subvendor = 0x0e11,
+		.subdevice = 0x000e,
+		.name = "Compaq Deskpro EN",	/* AD1885 */
+		.type = AC97_TUNE_HP_ONLY
+        },
+	{
+		.subvendor = 0x0e11,
+		.subdevice = 0x008a,
+		.name = "Compaq Evo W4000",	/* AD1885 */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x0e11,
+		.subdevice = 0x00b8,
+		.name = "Compaq Evo D510C",
+		.type = AC97_TUNE_HP_ONLY
+	},
+        {
+		.subvendor = 0x0e11,
+		.subdevice = 0x0860,
+		.name = "HP/Compaq nx7010",
+		.type = AC97_TUNE_MUTE_LED
+        },
+	{
+		.subvendor = 0x1014,
+		.subdevice = 0x0534,
+		.name = "ThinkPad X31",
+		.type = AC97_TUNE_INV_EAPD
+	},
+	{
+		.subvendor = 0x1014,
+		.subdevice = 0x1f00,
+		.name = "MS-9128",
+		.type = AC97_TUNE_ALC_JACK
+	},
+	{
+		.subvendor = 0x1014,
+		.subdevice = 0x0267,
+		.name = "IBM NetVista A30p",	/* AD1981B */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1025,
+		.subdevice = 0x0082,
+		.name = "Acer Travelmate 2310",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1025,
+		.subdevice = 0x0083,
+		.name = "Acer Aspire 3003LCi",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x00d8,
+		.name = "Dell Precision 530",	/* AD1885 */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x010d,
+		.name = "Dell",	/* which model?  AD1885 */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x0126,
+		.name = "Dell Optiplex GX260",	/* AD1981A */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x012c,
+		.name = "Dell Precision 650",	/* AD1981A */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x012d,
+		.name = "Dell Precision 450",	/* AD1981B*/
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x0147,
+		.name = "Dell",	/* which model?  AD1981B*/
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x0151,
+		.name = "Dell Optiplex GX270",  /* AD1981B */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x014e,
+		.name = "Dell D800", /* STAC9750/51 */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x0163,
+		.name = "Dell Unknown",	/* STAC9750/51 */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x016a,
+		.name = "Dell Inspiron 8600",	/* STAC9750/51 */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x0182,
+		.name = "Dell Latitude D610",	/* STAC9750/51 */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x0186,
+		.name = "Dell Latitude D810", /* cf. Malone #41015 */
+		.type = AC97_TUNE_HP_MUTE_LED
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x0188,
+		.name = "Dell Inspiron 6000",
+		.type = AC97_TUNE_HP_MUTE_LED /* cf. Malone #41015 */
+	},
+	{
+		.subvendor = 0x1028,
+		.subdevice = 0x0191,
+		.name = "Dell Inspiron 8600",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x006d,
+		.name = "HP zv5000",
+		.type = AC97_TUNE_MUTE_LED	/*AD1981B*/
+	},
+	{	/* FIXME: which codec? */
+		.subvendor = 0x103c,
+		.subdevice = 0x00c3,
+		.name = "HP xw6000",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x088c,
+		.name = "HP nc8000",
+		.type = AC97_TUNE_HP_MUTE_LED
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x0890,
+		.name = "HP nc6000",
+		.type = AC97_TUNE_MUTE_LED
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x129d,
+		.name = "HP xw8000",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x0938,
+		.name = "HP nc4200",
+		.type = AC97_TUNE_HP_MUTE_LED
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x099c,
+		.name = "HP nx6110/nc6120",
+		.type = AC97_TUNE_HP_MUTE_LED
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x0944,
+		.name = "HP nc6220",
+		.type = AC97_TUNE_HP_MUTE_LED
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x0934,
+		.name = "HP nc8220",
+		.type = AC97_TUNE_HP_MUTE_LED
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x12f1,
+		.name = "HP xw8200",	/* AD1981B*/
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x12f2,
+		.name = "HP xw6200",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x103c,
+		.subdevice = 0x3008,
+		.name = "HP xw4200",	/* AD1981B*/
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x104d,
+		.subdevice = 0x8144,
+		.name = "Sony",
+		.type = AC97_TUNE_INV_EAPD
+	},
+	{
+		.subvendor = 0x104d,
+		.subdevice = 0x8197,
+		.name = "Sony S1XP",
+		.type = AC97_TUNE_INV_EAPD
+	},
+	{
+		.subvendor = 0x104d,
+		.subdevice = 0x81c0,
+		.name = "Sony VAIO VGN-T350P", /*AD1981B*/
+		.type = AC97_TUNE_INV_EAPD
+	},
+	{
+		.subvendor = 0x104d,
+		.subdevice = 0x81c5,
+		.name = "Sony VAIO VGN-B1VP", /*AD1981B*/
+		.type = AC97_TUNE_INV_EAPD
+	},
+ 	{
+		.subvendor = 0x1043,
+		.subdevice = 0x80f3,
+		.name = "ASUS ICH5/AD1985",
+		.type = AC97_TUNE_AD_SHARING
+	},
+	{
+		.subvendor = 0x10cf,
+		.subdevice = 0x11c3,
+		.name = "Fujitsu-Siemens E4010",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x10cf,
+		.subdevice = 0x1225,
+		.name = "Fujitsu-Siemens T3010",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x10cf,
+		.subdevice = 0x1253,
+		.name = "Fujitsu S6210",	/* STAC9750/51 */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x10cf,
+		.subdevice = 0x127d,
+		.name = "Fujitsu Lifebook P7010",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x10cf,
+		.subdevice = 0x127e,
+		.name = "Fujitsu Lifebook C1211D",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x10cf,
+		.subdevice = 0x12ec,
+		.name = "Fujitsu-Siemens 4010",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x10cf,
+		.subdevice = 0x12f2,
+		.name = "Fujitsu-Siemens Celsius H320",
+		.type = AC97_TUNE_SWAP_HP
+	},
+	{
+		.subvendor = 0x10f1,
+		.subdevice = 0x2665,
+		.name = "Fujitsu-Siemens Celsius",	/* AD1981? */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x10f1,
+		.subdevice = 0x2885,
+		.name = "AMD64 Mobo",	/* ALC650 */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x10f1,
+		.subdevice = 0x2895,
+		.name = "Tyan Thunder K8WE",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x10f7,
+		.subdevice = 0x834c,
+		.name = "Panasonic CF-R4",
+		.type = AC97_TUNE_HP_ONLY,
+	},
+	{
+		.subvendor = 0x110a,
+		.subdevice = 0x0056,
+		.name = "Fujitsu-Siemens Scenic",	/* AD1981? */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x11d4,
+		.subdevice = 0x5375,
+		.name = "ADI AD1985 (discrete)",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1462,
+		.subdevice = 0x5470,
+		.name = "MSI P4 ATX 645 Ultra",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x161f,
+		.subdevice = 0x203a,
+		.name = "Gateway 4525GZ",		/* AD1981B */
+		.type = AC97_TUNE_INV_EAPD
+	},
+	{
+		.subvendor = 0x1734,
+		.subdevice = 0x0088,
+		.name = "Fujitsu-Siemens D1522",	/* AD1981 */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x8086,
+		.subdevice = 0x2000,
+		.mask = 0xfff0,
+		.name = "Intel ICH5/AD1985",
+		.type = AC97_TUNE_AD_SHARING
+	},
+	{
+		.subvendor = 0x8086,
+		.subdevice = 0x4000,
+		.mask = 0xfff0,
+		.name = "Intel ICH5/AD1985",
+		.type = AC97_TUNE_AD_SHARING
+	},
+	{
+		.subvendor = 0x8086,
+		.subdevice = 0x4856,
+		.name = "Intel D845WN (82801BA)",
+		.type = AC97_TUNE_SWAP_HP
+	},
+	{
+		.subvendor = 0x8086,
+		.subdevice = 0x4d44,
+		.name = "Intel D850EMV2",	/* AD1885 */
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x8086,
+		.subdevice = 0x4d56,
+		.name = "Intel ICH/AD1885",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x8086,
+		.subdevice = 0x6000,
+		.mask = 0xfff0,
+		.name = "Intel ICH5/AD1985",
+		.type = AC97_TUNE_AD_SHARING
+	},
+	{
+		.subvendor = 0x8086,
+		.subdevice = 0xe000,
+		.mask = 0xfff0,
+		.name = "Intel ICH5/AD1985",
+		.type = AC97_TUNE_AD_SHARING
+	},
+#if 0 /* FIXME: this seems wrong on most boards */
+	{
+		.subvendor = 0x8086,
+		.subdevice = 0xa000,
+		.mask = 0xfff0,
+		.name = "Intel ICH5/AD1985",
+		.type = AC97_TUNE_HP_ONLY
+	},
+#endif
+	{ } /* terminator */
+};
+
+static int __devinit snd_intel8x0_mixer(struct intel8x0 *chip, int ac97_clock,
+					const char *quirk_override)
+{
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	int err;
+	unsigned int i, codecs;
+	unsigned int glob_sta = 0;
+	struct snd_ac97_bus_ops *ops;
+	static struct snd_ac97_bus_ops standard_bus_ops = {
+		.write = snd_intel8x0_codec_write,
+		.read = snd_intel8x0_codec_read,
+	};
+	static struct snd_ac97_bus_ops ali_bus_ops = {
+		.write = snd_intel8x0_ali_codec_write,
+		.read = snd_intel8x0_ali_codec_read,
+	};
+
+	chip->spdif_idx = -1; /* use PCMOUT (or disabled) */
+	if (!spdif_aclink) {
+		switch (chip->device_type) {
+		case DEVICE_NFORCE:
+			chip->spdif_idx = NVD_SPBAR;
+			break;
+		case DEVICE_ALI:
+			chip->spdif_idx = ALID_AC97SPDIFOUT;
+			break;
+		case DEVICE_INTEL_ICH4:
+			chip->spdif_idx = ICHD_SPBAR;
+			break;
+		};
+	}
+
+	chip->in_ac97_init = 1;
+	
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.private_free = snd_intel8x0_mixer_free_ac97;
+	ac97.scaps = AC97_SCAP_SKIP_MODEM | AC97_SCAP_POWER_SAVE;
+	if (chip->xbox)
+		ac97.scaps |= AC97_SCAP_DETECT_BY_VENDOR;
+	if (chip->device_type != DEVICE_ALI) {
+		glob_sta = igetdword(chip, ICHREG(GLOB_STA));
+		ops = &standard_bus_ops;
+		chip->in_sdin_init = 1;
+		codecs = 0;
+		for (i = 0; i < chip->max_codecs; i++) {
+			if (! (glob_sta & chip->codec_bit[i]))
+				continue;
+			if (chip->device_type == DEVICE_INTEL_ICH4) {
+				snd_intel8x0_codec_read_test(chip, codecs);
+				chip->ac97_sdin[codecs] =
+					igetbyte(chip, ICHREG(SDM)) & ICH_LDI_MASK;
+				if (snd_BUG_ON(chip->ac97_sdin[codecs] >= 3))
+					chip->ac97_sdin[codecs] = 0;
+			} else
+				chip->ac97_sdin[codecs] = i;
+			codecs++;
+		}
+		chip->in_sdin_init = 0;
+		if (! codecs)
+			codecs = 1;
+	} else {
+		ops = &ali_bus_ops;
+		codecs = 1;
+		/* detect the secondary codec */
+		for (i = 0; i < 100; i++) {
+			unsigned int reg = igetdword(chip, ICHREG(ALI_RTSR));
+			if (reg & 0x40) {
+				codecs = 2;
+				break;
+			}
+			iputdword(chip, ICHREG(ALI_RTSR), reg | 0x40);
+			udelay(1);
+		}
+	}
+	if ((err = snd_ac97_bus(chip->card, 0, ops, chip, &pbus)) < 0)
+		goto __err;
+	pbus->private_free = snd_intel8x0_mixer_free_ac97_bus;
+	if (ac97_clock >= 8000 && ac97_clock <= 48000)
+		pbus->clock = ac97_clock;
+	/* FIXME: my test board doesn't work well with VRA... */
+	if (chip->device_type == DEVICE_ALI)
+		pbus->no_vra = 1;
+	else
+		pbus->dra = 1;
+	chip->ac97_bus = pbus;
+	chip->ncodecs = codecs;
+
+	ac97.pci = chip->pci;
+	for (i = 0; i < codecs; i++) {
+		ac97.num = i;
+		if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97[i])) < 0) {
+			if (err != -EACCES)
+				snd_printk(KERN_ERR "Unable to initialize codec #%d\n", i);
+			if (i == 0)
+				goto __err;
+		}
+	}
+	/* tune up the primary codec */
+	snd_ac97_tune_hardware(chip->ac97[0], ac97_quirks, quirk_override);
+	/* enable separate SDINs for ICH4 */
+	if (chip->device_type == DEVICE_INTEL_ICH4)
+		pbus->isdin = 1;
+	/* find the available PCM streams */
+	i = ARRAY_SIZE(ac97_pcm_defs);
+	if (chip->device_type != DEVICE_INTEL_ICH4)
+		i -= 2;		/* do not allocate PCM2IN and MIC2 */
+	if (chip->spdif_idx < 0)
+		i--;		/* do not allocate S/PDIF */
+	err = snd_ac97_pcm_assign(pbus, i, ac97_pcm_defs);
+	if (err < 0)
+		goto __err;
+	chip->ichd[ICHD_PCMOUT].pcm = &pbus->pcms[0];
+	chip->ichd[ICHD_PCMIN].pcm = &pbus->pcms[1];
+	chip->ichd[ICHD_MIC].pcm = &pbus->pcms[2];
+	if (chip->spdif_idx >= 0)
+		chip->ichd[chip->spdif_idx].pcm = &pbus->pcms[3];
+	if (chip->device_type == DEVICE_INTEL_ICH4) {
+		chip->ichd[ICHD_PCM2IN].pcm = &pbus->pcms[4];
+		chip->ichd[ICHD_MIC2].pcm = &pbus->pcms[5];
+	}
+	/* enable separate SDINs for ICH4 */
+	if (chip->device_type == DEVICE_INTEL_ICH4) {
+		struct ac97_pcm *pcm = chip->ichd[ICHD_PCM2IN].pcm;
+		u8 tmp = igetbyte(chip, ICHREG(SDM));
+		tmp &= ~(ICH_DI2L_MASK|ICH_DI1L_MASK);
+		if (pcm) {
+			tmp |= ICH_SE;	/* steer enable for multiple SDINs */
+			tmp |= chip->ac97_sdin[0] << ICH_DI1L_SHIFT;
+			for (i = 1; i < 4; i++) {
+				if (pcm->r[0].codec[i]) {
+					tmp |= chip->ac97_sdin[pcm->r[0].codec[1]->num] << ICH_DI2L_SHIFT;
+					break;
+				}
+			}
+		} else {
+			tmp &= ~ICH_SE; /* steer disable */
+		}
+		iputbyte(chip, ICHREG(SDM), tmp);
+	}
+	if (pbus->pcms[0].r[0].slots & (1 << AC97_SLOT_PCM_SLEFT)) {
+		chip->multi4 = 1;
+		if (pbus->pcms[0].r[0].slots & (1 << AC97_SLOT_LFE)) {
+			chip->multi6 = 1;
+			if (chip->ac97[0]->flags & AC97_HAS_8CH)
+				chip->multi8 = 1;
+		}
+	}
+	if (pbus->pcms[0].r[1].rslots[0]) {
+		chip->dra = 1;
+	}
+	if (chip->device_type == DEVICE_INTEL_ICH4) {
+		if ((igetdword(chip, ICHREG(GLOB_STA)) & ICH_SAMPLE_CAP) == ICH_SAMPLE_16_20)
+			chip->smp20bit = 1;
+	}
+	if (chip->device_type == DEVICE_NFORCE && !spdif_aclink) {
+		/* 48kHz only */
+		chip->ichd[chip->spdif_idx].pcm->rates = SNDRV_PCM_RATE_48000;
+	}
+	if (chip->device_type == DEVICE_INTEL_ICH4 && !spdif_aclink) {
+		/* use slot 10/11 for SPDIF */
+		u32 val;
+		val = igetdword(chip, ICHREG(GLOB_CNT)) & ~ICH_PCM_SPDIF_MASK;
+		val |= ICH_PCM_SPDIF_1011;
+		iputdword(chip, ICHREG(GLOB_CNT), val);
+		snd_ac97_update_bits(chip->ac97[0], AC97_EXTENDED_STATUS, 0x03 << 4, 0x03 << 4);
+	}
+	chip->in_ac97_init = 0;
+	return 0;
+
+ __err:
+	/* clear the cold-reset bit for the next chance */
+	if (chip->device_type != DEVICE_ALI)
+		iputdword(chip, ICHREG(GLOB_CNT),
+			  igetdword(chip, ICHREG(GLOB_CNT)) & ~ICH_AC97COLD);
+	return err;
+}
+
+
+/*
+ *
+ */
+
+static void do_ali_reset(struct intel8x0 *chip)
+{
+	iputdword(chip, ICHREG(ALI_SCR), ICH_ALI_SC_RESET);
+	iputdword(chip, ICHREG(ALI_FIFOCR1), 0x83838383);
+	iputdword(chip, ICHREG(ALI_FIFOCR2), 0x83838383);
+	iputdword(chip, ICHREG(ALI_FIFOCR3), 0x83838383);
+	iputdword(chip, ICHREG(ALI_INTERFACECR),
+		  ICH_ALI_IF_PI|ICH_ALI_IF_PO);
+	iputdword(chip, ICHREG(ALI_INTERRUPTCR), 0x00000000);
+	iputdword(chip, ICHREG(ALI_INTERRUPTSR), 0x00000000);
+}
+
+#ifdef CONFIG_SND_AC97_POWER_SAVE
+static struct snd_pci_quirk ich_chip_reset_mode[] = {
+	SND_PCI_QUIRK(0x1014, 0x051f, "Thinkpad R32", 1),
+	{ } /* end */
+};
+
+static int snd_intel8x0_ich_chip_cold_reset(struct intel8x0 *chip)
+{
+	unsigned int cnt;
+	/* ACLink on, 2 channels */
+
+	if (snd_pci_quirk_lookup(chip->pci, ich_chip_reset_mode))
+		return -EIO;
+
+	cnt = igetdword(chip, ICHREG(GLOB_CNT));
+	cnt &= ~(ICH_ACLINK | ICH_PCM_246_MASK);
+
+	/* do cold reset - the full ac97 powerdown may leave the controller
+	 * in a warm state but actually it cannot communicate with the codec.
+	 */
+	iputdword(chip, ICHREG(GLOB_CNT), cnt & ~ICH_AC97COLD);
+	cnt = igetdword(chip, ICHREG(GLOB_CNT));
+	udelay(10);
+	iputdword(chip, ICHREG(GLOB_CNT), cnt | ICH_AC97COLD);
+	msleep(1);
+	return 0;
+}
+#define snd_intel8x0_ich_chip_can_cold_reset(chip) \
+	(!snd_pci_quirk_lookup(chip->pci, ich_chip_reset_mode))
+#else
+#define snd_intel8x0_ich_chip_cold_reset(chip)	0
+#define snd_intel8x0_ich_chip_can_cold_reset(chip) (0)
+#endif
+
+static int snd_intel8x0_ich_chip_reset(struct intel8x0 *chip)
+{
+	unsigned long end_time;
+	unsigned int cnt;
+	/* ACLink on, 2 channels */
+	cnt = igetdword(chip, ICHREG(GLOB_CNT));
+	cnt &= ~(ICH_ACLINK | ICH_PCM_246_MASK);
+	/* finish cold or do warm reset */
+	cnt |= (cnt & ICH_AC97COLD) == 0 ? ICH_AC97COLD : ICH_AC97WARM;
+	iputdword(chip, ICHREG(GLOB_CNT), cnt);
+	end_time = (jiffies + (HZ / 4)) + 1;
+	do {
+		if ((igetdword(chip, ICHREG(GLOB_CNT)) & ICH_AC97WARM) == 0)
+			return 0;
+		schedule_timeout_uninterruptible(1);
+	} while (time_after_eq(end_time, jiffies));
+	snd_printk(KERN_ERR "AC'97 warm reset still in progress? [0x%x]\n",
+		   igetdword(chip, ICHREG(GLOB_CNT)));
+	return -EIO;
+}
+
+static int snd_intel8x0_ich_chip_init(struct intel8x0 *chip, int probing)
+{
+	unsigned long end_time;
+	unsigned int status, nstatus;
+	unsigned int cnt;
+	int err;
+
+	/* put logic to right state */
+	/* first clear status bits */
+	status = ICH_RCS | ICH_MCINT | ICH_POINT | ICH_PIINT;
+	if (chip->device_type == DEVICE_NFORCE)
+		status |= ICH_NVSPINT;
+	cnt = igetdword(chip, ICHREG(GLOB_STA));
+	iputdword(chip, ICHREG(GLOB_STA), cnt & status);
+
+	if (snd_intel8x0_ich_chip_can_cold_reset(chip))
+		err = snd_intel8x0_ich_chip_cold_reset(chip);
+	else
+		err = snd_intel8x0_ich_chip_reset(chip);
+	if (err < 0)
+		return err;
+
+	if (probing) {
+		/* wait for any codec ready status.
+		 * Once it becomes ready it should remain ready
+		 * as long as we do not disable the ac97 link.
+		 */
+		end_time = jiffies + HZ;
+		do {
+			status = igetdword(chip, ICHREG(GLOB_STA)) &
+				chip->codec_isr_bits;
+			if (status)
+				break;
+			schedule_timeout_uninterruptible(1);
+		} while (time_after_eq(end_time, jiffies));
+		if (! status) {
+			/* no codec is found */
+			snd_printk(KERN_ERR "codec_ready: codec is not ready [0x%x]\n",
+				   igetdword(chip, ICHREG(GLOB_STA)));
+			return -EIO;
+		}
+
+		/* wait for other codecs ready status. */
+		end_time = jiffies + HZ / 4;
+		while (status != chip->codec_isr_bits &&
+		       time_after_eq(end_time, jiffies)) {
+			schedule_timeout_uninterruptible(1);
+			status |= igetdword(chip, ICHREG(GLOB_STA)) &
+				chip->codec_isr_bits;
+		}
+
+	} else {
+		/* resume phase */
+		int i;
+		status = 0;
+		for (i = 0; i < chip->ncodecs; i++)
+			if (chip->ac97[i])
+				status |= chip->codec_bit[chip->ac97_sdin[i]];
+		/* wait until all the probed codecs are ready */
+		end_time = jiffies + HZ;
+		do {
+			nstatus = igetdword(chip, ICHREG(GLOB_STA)) &
+				chip->codec_isr_bits;
+			if (status == nstatus)
+				break;
+			schedule_timeout_uninterruptible(1);
+		} while (time_after_eq(end_time, jiffies));
+	}
+
+	if (chip->device_type == DEVICE_SIS) {
+		/* unmute the output on SIS7012 */
+		iputword(chip, 0x4c, igetword(chip, 0x4c) | 1);
+	}
+	if (chip->device_type == DEVICE_NFORCE && !spdif_aclink) {
+		/* enable SPDIF interrupt */
+		unsigned int val;
+		pci_read_config_dword(chip->pci, 0x4c, &val);
+		val |= 0x1000000;
+		pci_write_config_dword(chip->pci, 0x4c, val);
+	}
+      	return 0;
+}
+
+static int snd_intel8x0_ali_chip_init(struct intel8x0 *chip, int probing)
+{
+	u32 reg;
+	int i = 0;
+
+	reg = igetdword(chip, ICHREG(ALI_SCR));
+	if ((reg & 2) == 0)	/* Cold required */
+		reg |= 2;
+	else
+		reg |= 1;	/* Warm */
+	reg &= ~0x80000000;	/* ACLink on */
+	iputdword(chip, ICHREG(ALI_SCR), reg);
+
+	for (i = 0; i < HZ / 2; i++) {
+		if (! (igetdword(chip, ICHREG(ALI_INTERRUPTSR)) & ALI_INT_GPIO))
+			goto __ok;
+		schedule_timeout_uninterruptible(1);
+	}
+	snd_printk(KERN_ERR "AC'97 reset failed.\n");
+	if (probing)
+		return -EIO;
+
+ __ok:
+	for (i = 0; i < HZ / 2; i++) {
+		reg = igetdword(chip, ICHREG(ALI_RTSR));
+		if (reg & 0x80) /* primary codec */
+			break;
+		iputdword(chip, ICHREG(ALI_RTSR), reg | 0x80);
+		schedule_timeout_uninterruptible(1);
+	}
+
+	do_ali_reset(chip);
+	return 0;
+}
+
+static int snd_intel8x0_chip_init(struct intel8x0 *chip, int probing)
+{
+	unsigned int i, timeout;
+	int err;
+	
+	if (chip->device_type != DEVICE_ALI) {
+		if ((err = snd_intel8x0_ich_chip_init(chip, probing)) < 0)
+			return err;
+		iagetword(chip, 0);	/* clear semaphore flag */
+	} else {
+		if ((err = snd_intel8x0_ali_chip_init(chip, probing)) < 0)
+			return err;
+	}
+
+	/* disable interrupts */
+	for (i = 0; i < chip->bdbars_count; i++)
+		iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, 0x00);
+	/* reset channels */
+	for (i = 0; i < chip->bdbars_count; i++)
+		iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, ICH_RESETREGS);
+	for (i = 0; i < chip->bdbars_count; i++) {
+	        timeout = 100000;
+	        while (--timeout != 0) {
+        		if ((igetbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset) & ICH_RESETREGS) == 0)
+        		        break;
+                }
+                if (timeout == 0)
+                        printk(KERN_ERR "intel8x0: reset of registers failed?\n");
+        }
+	/* initialize Buffer Descriptor Lists */
+	for (i = 0; i < chip->bdbars_count; i++)
+		iputdword(chip, ICH_REG_OFF_BDBAR + chip->ichd[i].reg_offset,
+			  chip->ichd[i].bdbar_addr);
+	return 0;
+}
+
+static int snd_intel8x0_free(struct intel8x0 *chip)
+{
+	unsigned int i;
+
+	if (chip->irq < 0)
+		goto __hw_end;
+	/* disable interrupts */
+	for (i = 0; i < chip->bdbars_count; i++)
+		iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, 0x00);
+	/* reset channels */
+	for (i = 0; i < chip->bdbars_count; i++)
+		iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, ICH_RESETREGS);
+	if (chip->device_type == DEVICE_NFORCE && !spdif_aclink) {
+		/* stop the spdif interrupt */
+		unsigned int val;
+		pci_read_config_dword(chip->pci, 0x4c, &val);
+		val &= ~0x1000000;
+		pci_write_config_dword(chip->pci, 0x4c, val);
+	}
+	/* --- */
+
+      __hw_end:
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	if (chip->bdbars.area) {
+		if (chip->fix_nocache)
+			fill_nocache(chip->bdbars.area, chip->bdbars.bytes, 0);
+		snd_dma_free_pages(&chip->bdbars);
+	}
+	if (chip->addr)
+		pci_iounmap(chip->pci, chip->addr);
+	if (chip->bmaddr)
+		pci_iounmap(chip->pci, chip->bmaddr);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+static int intel8x0_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct intel8x0 *chip = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	for (i = 0; i < chip->pcm_devs; i++)
+		snd_pcm_suspend_all(chip->pcm[i]);
+	/* clear nocache */
+	if (chip->fix_nocache) {
+		for (i = 0; i < chip->bdbars_count; i++) {
+			struct ichdev *ichdev = &chip->ichd[i];
+			if (ichdev->substream && ichdev->page_attr_changed) {
+				struct snd_pcm_runtime *runtime = ichdev->substream->runtime;
+				if (runtime->dma_area)
+					fill_nocache(runtime->dma_area, runtime->dma_bytes, 0);
+			}
+		}
+	}
+	for (i = 0; i < chip->ncodecs; i++)
+		snd_ac97_suspend(chip->ac97[i]);
+	if (chip->device_type == DEVICE_INTEL_ICH4)
+		chip->sdm_saved = igetbyte(chip, ICHREG(SDM));
+
+	if (chip->irq >= 0) {
+		free_irq(chip->irq, chip);
+		chip->irq = -1;
+	}
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	/* The call below may disable built-in speaker on some laptops
+	 * after S2RAM.  So, don't touch it.
+	 */
+	/* pci_set_power_state(pci, pci_choose_state(pci, state)); */
+	return 0;
+}
+
+static int intel8x0_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct intel8x0 *chip = card->private_data;
+	int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "intel8x0: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+	snd_intel8x0_chip_init(chip, 0);
+	if (request_irq(pci->irq, snd_intel8x0_interrupt,
+			IRQF_SHARED, card->shortname, chip)) {
+		printk(KERN_ERR "intel8x0: unable to grab IRQ %d, "
+		       "disabling device\n", pci->irq);
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	chip->irq = pci->irq;
+	synchronize_irq(chip->irq);
+
+	/* re-initialize mixer stuff */
+	if (chip->device_type == DEVICE_INTEL_ICH4 && !spdif_aclink) {
+		/* enable separate SDINs for ICH4 */
+		iputbyte(chip, ICHREG(SDM), chip->sdm_saved);
+		/* use slot 10/11 for SPDIF */
+		iputdword(chip, ICHREG(GLOB_CNT),
+			  (igetdword(chip, ICHREG(GLOB_CNT)) & ~ICH_PCM_SPDIF_MASK) |
+			  ICH_PCM_SPDIF_1011);
+	}
+
+	/* refill nocache */
+	if (chip->fix_nocache)
+		fill_nocache(chip->bdbars.area, chip->bdbars.bytes, 1);
+
+	for (i = 0; i < chip->ncodecs; i++)
+		snd_ac97_resume(chip->ac97[i]);
+
+	/* refill nocache */
+	if (chip->fix_nocache) {
+		for (i = 0; i < chip->bdbars_count; i++) {
+			struct ichdev *ichdev = &chip->ichd[i];
+			if (ichdev->substream && ichdev->page_attr_changed) {
+				struct snd_pcm_runtime *runtime = ichdev->substream->runtime;
+				if (runtime->dma_area)
+					fill_nocache(runtime->dma_area, runtime->dma_bytes, 1);
+			}
+		}
+	}
+
+	/* resume status */
+	for (i = 0; i < chip->bdbars_count; i++) {
+		struct ichdev *ichdev = &chip->ichd[i];
+		unsigned long port = ichdev->reg_offset;
+		if (! ichdev->substream || ! ichdev->suspended)
+			continue;
+		if (ichdev->ichd == ICHD_PCMOUT)
+			snd_intel8x0_setup_pcm_out(chip, ichdev->substream->runtime);
+		iputdword(chip, port + ICH_REG_OFF_BDBAR, ichdev->bdbar_addr);
+		iputbyte(chip, port + ICH_REG_OFF_LVI, ichdev->lvi);
+		iputbyte(chip, port + ICH_REG_OFF_CIV, ichdev->civ);
+		iputbyte(chip, port + ichdev->roff_sr, ICH_FIFOE | ICH_BCIS | ICH_LVBCI);
+	}
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+#define INTEL8X0_TESTBUF_SIZE	32768	/* enough large for one shot */
+
+static void __devinit intel8x0_measure_ac97_clock(struct intel8x0 *chip)
+{
+	struct snd_pcm_substream *subs;
+	struct ichdev *ichdev;
+	unsigned long port;
+	unsigned long pos, pos1, t;
+	int civ, timeout = 1000, attempt = 1;
+	struct timespec start_time, stop_time;
+
+	if (chip->ac97_bus->clock != 48000)
+		return; /* specified in module option */
+
+      __again:
+	subs = chip->pcm[0]->streams[0].substream;
+	if (! subs || subs->dma_buffer.bytes < INTEL8X0_TESTBUF_SIZE) {
+		snd_printk(KERN_WARNING "no playback buffer allocated - aborting measure ac97 clock\n");
+		return;
+	}
+	ichdev = &chip->ichd[ICHD_PCMOUT];
+	ichdev->physbuf = subs->dma_buffer.addr;
+	ichdev->size = ichdev->fragsize = INTEL8X0_TESTBUF_SIZE;
+	ichdev->substream = NULL; /* don't process interrupts */
+
+	/* set rate */
+	if (snd_ac97_set_rate(chip->ac97[0], AC97_PCM_FRONT_DAC_RATE, 48000) < 0) {
+		snd_printk(KERN_ERR "cannot set ac97 rate: clock = %d\n", chip->ac97_bus->clock);
+		return;
+	}
+	snd_intel8x0_setup_periods(chip, ichdev);
+	port = ichdev->reg_offset;
+	spin_lock_irq(&chip->reg_lock);
+	chip->in_measurement = 1;
+	/* trigger */
+	if (chip->device_type != DEVICE_ALI)
+		iputbyte(chip, port + ICH_REG_OFF_CR, ICH_IOCE | ICH_STARTBM);
+	else {
+		iputbyte(chip, port + ICH_REG_OFF_CR, ICH_IOCE);
+		iputdword(chip, ICHREG(ALI_DMACR), 1 << ichdev->ali_slot);
+	}
+	do_posix_clock_monotonic_gettime(&start_time);
+	spin_unlock_irq(&chip->reg_lock);
+	msleep(50);
+	spin_lock_irq(&chip->reg_lock);
+	/* check the position */
+	do {
+		civ = igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV);
+		pos1 = igetword(chip, ichdev->reg_offset + ichdev->roff_picb);
+		if (pos1 == 0) {
+			udelay(10);
+			continue;
+		}
+		if (civ == igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV) &&
+		    pos1 == igetword(chip, ichdev->reg_offset + ichdev->roff_picb))
+			break;
+	} while (timeout--);
+	if (pos1 == 0) {	/* oops, this value is not reliable */
+		pos = 0;
+	} else {
+		pos = ichdev->fragsize1;
+		pos -= pos1 << ichdev->pos_shift;
+		pos += ichdev->position;
+	}
+	chip->in_measurement = 0;
+	do_posix_clock_monotonic_gettime(&stop_time);
+	/* stop */
+	if (chip->device_type == DEVICE_ALI) {
+		iputdword(chip, ICHREG(ALI_DMACR), 1 << (ichdev->ali_slot + 16));
+		iputbyte(chip, port + ICH_REG_OFF_CR, 0);
+		while (igetbyte(chip, port + ICH_REG_OFF_CR))
+			;
+	} else {
+		iputbyte(chip, port + ICH_REG_OFF_CR, 0);
+		while (!(igetbyte(chip, port + ichdev->roff_sr) & ICH_DCH))
+			;
+	}
+	iputbyte(chip, port + ICH_REG_OFF_CR, ICH_RESETREGS);
+	spin_unlock_irq(&chip->reg_lock);
+
+	if (pos == 0) {
+		snd_printk(KERN_ERR "intel8x0: measure - unreliable DMA position..\n");
+	      __retry:
+		if (attempt < 3) {
+			msleep(300);
+			attempt++;
+			goto __again;
+		}
+		goto __end;
+	}
+
+	pos /= 4;
+	t = stop_time.tv_sec - start_time.tv_sec;
+	t *= 1000000;
+	t += (stop_time.tv_nsec - start_time.tv_nsec) / 1000;
+	printk(KERN_INFO "%s: measured %lu usecs (%lu samples)\n", __func__, t, pos);
+	if (t == 0) {
+		snd_printk(KERN_ERR "intel8x0: ?? calculation error..\n");
+		goto __retry;
+	}
+	pos *= 1000;
+	pos = (pos / t) * 1000 + ((pos % t) * 1000) / t;
+	if (pos < 40000 || pos >= 60000) {
+		/* abnormal value. hw problem? */
+		printk(KERN_INFO "intel8x0: measured clock %ld rejected\n", pos);
+		goto __retry;
+	} else if (pos > 40500 && pos < 41500)
+		/* first exception - 41000Hz reference clock */
+		chip->ac97_bus->clock = 41000;
+	else if (pos > 43600 && pos < 44600)
+		/* second exception - 44100HZ reference clock */
+		chip->ac97_bus->clock = 44100;
+	else if (pos < 47500 || pos > 48500)
+		/* not 48000Hz, tuning the clock.. */
+		chip->ac97_bus->clock = (chip->ac97_bus->clock * 48000) / pos;
+      __end:
+	printk(KERN_INFO "intel8x0: clocking to %d\n", chip->ac97_bus->clock);
+	snd_ac97_update_power(chip->ac97[0], AC97_PCM_FRONT_DAC_RATE, 0);
+}
+
+static struct snd_pci_quirk intel8x0_clock_list[] __devinitdata = {
+	SND_PCI_QUIRK(0x0e11, 0x008a, "AD1885", 41000),
+	SND_PCI_QUIRK(0x1028, 0x00be, "AD1885", 44100),
+	SND_PCI_QUIRK(0x1028, 0x0177, "AD1980", 48000),
+	SND_PCI_QUIRK(0x1028, 0x01ad, "AD1981B", 48000),
+	SND_PCI_QUIRK(0x1043, 0x80f3, "AD1985", 48000),
+	{ }	/* terminator */
+};
+
+static int __devinit intel8x0_in_clock_list(struct intel8x0 *chip)
+{
+	struct pci_dev *pci = chip->pci;
+	const struct snd_pci_quirk *wl;
+
+	wl = snd_pci_quirk_lookup(pci, intel8x0_clock_list);
+	if (!wl)
+		return 0;
+	printk(KERN_INFO "intel8x0: white list rate for %04x:%04x is %i\n",
+	       pci->subsystem_vendor, pci->subsystem_device, wl->value);
+	chip->ac97_bus->clock = wl->value;
+	return 1;
+}
+
+#ifdef CONFIG_PROC_FS
+static void snd_intel8x0_proc_read(struct snd_info_entry * entry,
+				   struct snd_info_buffer *buffer)
+{
+	struct intel8x0 *chip = entry->private_data;
+	unsigned int tmp;
+
+	snd_iprintf(buffer, "Intel8x0\n\n");
+	if (chip->device_type == DEVICE_ALI)
+		return;
+	tmp = igetdword(chip, ICHREG(GLOB_STA));
+	snd_iprintf(buffer, "Global control        : 0x%08x\n", igetdword(chip, ICHREG(GLOB_CNT)));
+	snd_iprintf(buffer, "Global status         : 0x%08x\n", tmp);
+	if (chip->device_type == DEVICE_INTEL_ICH4)
+		snd_iprintf(buffer, "SDM                   : 0x%08x\n", igetdword(chip, ICHREG(SDM)));
+	snd_iprintf(buffer, "AC'97 codecs ready    :");
+	if (tmp & chip->codec_isr_bits) {
+		int i;
+		static const char *codecs[3] = {
+			"primary", "secondary", "tertiary"
+		};
+		for (i = 0; i < chip->max_codecs; i++)
+			if (tmp & chip->codec_bit[i])
+				snd_iprintf(buffer, " %s", codecs[i]);
+	} else
+		snd_iprintf(buffer, " none");
+	snd_iprintf(buffer, "\n");
+	if (chip->device_type == DEVICE_INTEL_ICH4 ||
+	    chip->device_type == DEVICE_SIS)
+		snd_iprintf(buffer, "AC'97 codecs SDIN     : %i %i %i\n",
+			chip->ac97_sdin[0],
+			chip->ac97_sdin[1],
+			chip->ac97_sdin[2]);
+}
+
+static void __devinit snd_intel8x0_proc_init(struct intel8x0 * chip)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(chip->card, "intel8x0", &entry))
+		snd_info_set_text_ops(entry, chip, snd_intel8x0_proc_read);
+}
+#else
+#define snd_intel8x0_proc_init(x)
+#endif
+
+static int snd_intel8x0_dev_free(struct snd_device *device)
+{
+	struct intel8x0 *chip = device->device_data;
+	return snd_intel8x0_free(chip);
+}
+
+struct ich_reg_info {
+	unsigned int int_sta_mask;
+	unsigned int offset;
+};
+
+static unsigned int ich_codec_bits[3] = {
+	ICH_PCR, ICH_SCR, ICH_TCR
+};
+static unsigned int sis_codec_bits[3] = {
+	ICH_PCR, ICH_SCR, ICH_SIS_TCR
+};
+
+static int __devinit snd_intel8x0_create(struct snd_card *card,
+					 struct pci_dev *pci,
+					 unsigned long device_type,
+					 struct intel8x0 ** r_intel8x0)
+{
+	struct intel8x0 *chip;
+	int err;
+	unsigned int i;
+	unsigned int int_sta_masks;
+	struct ichdev *ichdev;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_intel8x0_dev_free,
+	};
+
+	static unsigned int bdbars[] = {
+		3, /* DEVICE_INTEL */
+		6, /* DEVICE_INTEL_ICH4 */
+		3, /* DEVICE_SIS */
+		6, /* DEVICE_ALI */
+		4, /* DEVICE_NFORCE */
+	};
+	static struct ich_reg_info intel_regs[6] = {
+		{ ICH_PIINT, 0 },
+		{ ICH_POINT, 0x10 },
+		{ ICH_MCINT, 0x20 },
+		{ ICH_M2INT, 0x40 },
+		{ ICH_P2INT, 0x50 },
+		{ ICH_SPINT, 0x60 },
+	};
+	static struct ich_reg_info nforce_regs[4] = {
+		{ ICH_PIINT, 0 },
+		{ ICH_POINT, 0x10 },
+		{ ICH_MCINT, 0x20 },
+		{ ICH_NVSPINT, 0x70 },
+	};
+	static struct ich_reg_info ali_regs[6] = {
+		{ ALI_INT_PCMIN, 0x40 },
+		{ ALI_INT_PCMOUT, 0x50 },
+		{ ALI_INT_MICIN, 0x60 },
+		{ ALI_INT_CODECSPDIFOUT, 0x70 },
+		{ ALI_INT_SPDIFIN, 0xa0 },
+		{ ALI_INT_SPDIFOUT, 0xb0 },
+	};
+	struct ich_reg_info *tbl;
+
+	*r_intel8x0 = NULL;
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	spin_lock_init(&chip->reg_lock);
+	chip->device_type = device_type;
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	/* module parameters */
+	chip->buggy_irq = buggy_irq;
+	chip->buggy_semaphore = buggy_semaphore;
+	if (xbox)
+		chip->xbox = 1;
+
+	if (pci->vendor == PCI_VENDOR_ID_INTEL &&
+	    pci->device == PCI_DEVICE_ID_INTEL_440MX)
+		chip->fix_nocache = 1; /* enable workaround */
+
+	if ((err = pci_request_regions(pci, card->shortname)) < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+
+	if (device_type == DEVICE_ALI) {
+		/* ALI5455 has no ac97 region */
+		chip->bmaddr = pci_iomap(pci, 0, 0);
+		goto port_inited;
+	}
+
+	if (pci_resource_flags(pci, 2) & IORESOURCE_MEM) /* ICH4 and Nforce */
+		chip->addr = pci_iomap(pci, 2, 0);
+	else
+		chip->addr = pci_iomap(pci, 0, 0);
+	if (!chip->addr) {
+		snd_printk(KERN_ERR "AC'97 space ioremap problem\n");
+		snd_intel8x0_free(chip);
+		return -EIO;
+	}
+	if (pci_resource_flags(pci, 3) & IORESOURCE_MEM) /* ICH4 */
+		chip->bmaddr = pci_iomap(pci, 3, 0);
+	else
+		chip->bmaddr = pci_iomap(pci, 1, 0);
+	if (!chip->bmaddr) {
+		snd_printk(KERN_ERR "Controller space ioremap problem\n");
+		snd_intel8x0_free(chip);
+		return -EIO;
+	}
+
+ port_inited:
+	chip->bdbars_count = bdbars[device_type];
+
+	/* initialize offsets */
+	switch (device_type) {
+	case DEVICE_NFORCE:
+		tbl = nforce_regs;
+		break;
+	case DEVICE_ALI:
+		tbl = ali_regs;
+		break;
+	default:
+		tbl = intel_regs;
+		break;
+	}
+	for (i = 0; i < chip->bdbars_count; i++) {
+		ichdev = &chip->ichd[i];
+		ichdev->ichd = i;
+		ichdev->reg_offset = tbl[i].offset;
+		ichdev->int_sta_mask = tbl[i].int_sta_mask;
+		if (device_type == DEVICE_SIS) {
+			/* SiS 7012 swaps the registers */
+			ichdev->roff_sr = ICH_REG_OFF_PICB;
+			ichdev->roff_picb = ICH_REG_OFF_SR;
+		} else {
+			ichdev->roff_sr = ICH_REG_OFF_SR;
+			ichdev->roff_picb = ICH_REG_OFF_PICB;
+		}
+		if (device_type == DEVICE_ALI)
+			ichdev->ali_slot = (ichdev->reg_offset - 0x40) / 0x10;
+		/* SIS7012 handles the pcm data in bytes, others are in samples */
+		ichdev->pos_shift = (device_type == DEVICE_SIS) ? 0 : 1;
+	}
+
+	/* allocate buffer descriptor lists */
+	/* the start of each lists must be aligned to 8 bytes */
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				chip->bdbars_count * sizeof(u32) * ICH_MAX_FRAGS * 2,
+				&chip->bdbars) < 0) {
+		snd_intel8x0_free(chip);
+		snd_printk(KERN_ERR "intel8x0: cannot allocate buffer descriptors\n");
+		return -ENOMEM;
+	}
+	/* tables must be aligned to 8 bytes here, but the kernel pages
+	   are much bigger, so we don't care (on i386) */
+	/* workaround for 440MX */
+	if (chip->fix_nocache)
+		fill_nocache(chip->bdbars.area, chip->bdbars.bytes, 1);
+	int_sta_masks = 0;
+	for (i = 0; i < chip->bdbars_count; i++) {
+		ichdev = &chip->ichd[i];
+		ichdev->bdbar = ((u32 *)chip->bdbars.area) +
+			(i * ICH_MAX_FRAGS * 2);
+		ichdev->bdbar_addr = chip->bdbars.addr +
+			(i * sizeof(u32) * ICH_MAX_FRAGS * 2);
+		int_sta_masks |= ichdev->int_sta_mask;
+	}
+	chip->int_sta_reg = device_type == DEVICE_ALI ?
+		ICH_REG_ALI_INTERRUPTSR : ICH_REG_GLOB_STA;
+	chip->int_sta_mask = int_sta_masks;
+
+	pci_set_master(pci);
+
+	switch(chip->device_type) {
+	case DEVICE_INTEL_ICH4:
+		/* ICH4 can have three codecs */
+		chip->max_codecs = 3;
+		chip->codec_bit = ich_codec_bits;
+		chip->codec_ready_bits = ICH_PRI | ICH_SRI | ICH_TRI;
+		break;
+	case DEVICE_SIS:
+		/* recent SIS7012 can have three codecs */
+		chip->max_codecs = 3;
+		chip->codec_bit = sis_codec_bits;
+		chip->codec_ready_bits = ICH_PRI | ICH_SRI | ICH_SIS_TRI;
+		break;
+	default:
+		/* others up to two codecs */
+		chip->max_codecs = 2;
+		chip->codec_bit = ich_codec_bits;
+		chip->codec_ready_bits = ICH_PRI | ICH_SRI;
+		break;
+	}
+	for (i = 0; i < chip->max_codecs; i++)
+		chip->codec_isr_bits |= chip->codec_bit[i];
+
+	if ((err = snd_intel8x0_chip_init(chip, 1)) < 0) {
+		snd_intel8x0_free(chip);
+		return err;
+	}
+
+	/* request irq after initializaing int_sta_mask, etc */
+	if (request_irq(pci->irq, snd_intel8x0_interrupt,
+			IRQF_SHARED, card->shortname, chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_intel8x0_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_intel8x0_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*r_intel8x0 = chip;
+	return 0;
+}
+
+static struct shortname_table {
+	unsigned int id;
+	const char *s;
+} shortnames[] __devinitdata = {
+	{ PCI_DEVICE_ID_INTEL_82801AA_5, "Intel 82801AA-ICH" },
+	{ PCI_DEVICE_ID_INTEL_82801AB_5, "Intel 82901AB-ICH0" },
+	{ PCI_DEVICE_ID_INTEL_82801BA_4, "Intel 82801BA-ICH2" },
+	{ PCI_DEVICE_ID_INTEL_440MX, "Intel 440MX" },
+	{ PCI_DEVICE_ID_INTEL_82801CA_5, "Intel 82801CA-ICH3" },
+	{ PCI_DEVICE_ID_INTEL_82801DB_5, "Intel 82801DB-ICH4" },
+	{ PCI_DEVICE_ID_INTEL_82801EB_5, "Intel ICH5" },
+	{ PCI_DEVICE_ID_INTEL_ESB_5, "Intel 6300ESB" },
+	{ PCI_DEVICE_ID_INTEL_ICH6_18, "Intel ICH6" },
+	{ PCI_DEVICE_ID_INTEL_ICH7_20, "Intel ICH7" },
+	{ PCI_DEVICE_ID_INTEL_ESB2_14, "Intel ESB2" },
+	{ PCI_DEVICE_ID_SI_7012, "SiS SI7012" },
+	{ PCI_DEVICE_ID_NVIDIA_MCP1_AUDIO, "NVidia nForce" },
+	{ PCI_DEVICE_ID_NVIDIA_MCP2_AUDIO, "NVidia nForce2" },
+	{ PCI_DEVICE_ID_NVIDIA_MCP3_AUDIO, "NVidia nForce3" },
+	{ PCI_DEVICE_ID_NVIDIA_CK8S_AUDIO, "NVidia CK8S" },
+	{ PCI_DEVICE_ID_NVIDIA_CK804_AUDIO, "NVidia CK804" },
+	{ PCI_DEVICE_ID_NVIDIA_CK8_AUDIO, "NVidia CK8" },
+	{ 0x003a, "NVidia MCP04" },
+	{ 0x746d, "AMD AMD8111" },
+	{ 0x7445, "AMD AMD768" },
+	{ 0x5455, "ALi M5455" },
+	{ 0, NULL },
+};
+
+static struct snd_pci_quirk spdif_aclink_defaults[] __devinitdata = {
+	SND_PCI_QUIRK(0x147b, 0x1c1a, "ASUS KN8", 1),
+	{ } /* end */
+};
+
+/* look up white/black list for SPDIF over ac-link */
+static int __devinit check_default_spdif_aclink(struct pci_dev *pci)
+{
+	const struct snd_pci_quirk *w;
+
+	w = snd_pci_quirk_lookup(pci, spdif_aclink_defaults);
+	if (w) {
+		if (w->value)
+			snd_printdd(KERN_INFO "intel8x0: Using SPDIF over "
+				    "AC-Link for %s\n", w->name);
+		else
+			snd_printdd(KERN_INFO "intel8x0: Using integrated "
+				    "SPDIF DMA for %s\n", w->name);
+		return w->value;
+	}
+	return 0;
+}
+
+static int __devinit snd_intel8x0_probe(struct pci_dev *pci,
+					const struct pci_device_id *pci_id)
+{
+	struct snd_card *card;
+	struct intel8x0 *chip;
+	int err;
+	struct shortname_table *name;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	if (spdif_aclink < 0)
+		spdif_aclink = check_default_spdif_aclink(pci);
+
+	strcpy(card->driver, "ICH");
+	if (!spdif_aclink) {
+		switch (pci_id->driver_data) {
+		case DEVICE_NFORCE:
+			strcpy(card->driver, "NFORCE");
+			break;
+		case DEVICE_INTEL_ICH4:
+			strcpy(card->driver, "ICH4");
+		}
+	}
+
+	strcpy(card->shortname, "Intel ICH");
+	for (name = shortnames; name->id; name++) {
+		if (pci->device == name->id) {
+			strcpy(card->shortname, name->s);
+			break;
+		}
+	}
+
+	if (buggy_irq < 0) {
+		/* some Nforce[2] and ICH boards have problems with IRQ handling.
+		 * Needs to return IRQ_HANDLED for unknown irqs.
+		 */
+		if (pci_id->driver_data == DEVICE_NFORCE)
+			buggy_irq = 1;
+		else
+			buggy_irq = 0;
+	}
+
+	if ((err = snd_intel8x0_create(card, pci, pci_id->driver_data,
+				       &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = chip;
+
+	if ((err = snd_intel8x0_mixer(chip, ac97_clock, ac97_quirk)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_intel8x0_pcm(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	
+	snd_intel8x0_proc_init(chip);
+
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s with %s at irq %i", card->shortname,
+		 snd_ac97_get_short_name(chip->ac97[0]), chip->irq);
+
+	if (ac97_clock == 0 || ac97_clock == 1) {
+		if (ac97_clock == 0) {
+			if (intel8x0_in_clock_list(chip) == 0)
+				intel8x0_measure_ac97_clock(chip);
+		} else {
+			intel8x0_measure_ac97_clock(chip);
+		}
+	}
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	return 0;
+}
+
+static void __devexit snd_intel8x0_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "Intel ICH",
+	.id_table = snd_intel8x0_ids,
+	.probe = snd_intel8x0_probe,
+	.remove = __devexit_p(snd_intel8x0_remove),
+#ifdef CONFIG_PM
+	.suspend = intel8x0_suspend,
+	.resume = intel8x0_resume,
+#endif
+};
+
+
+static int __init alsa_card_intel8x0_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_intel8x0_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_intel8x0_init)
+module_exit(alsa_card_intel8x0_exit)
diff --git a/linux/sound/pci/intel8x0m.c b/linux/sound/pci/intel8x0m.c
new file mode 100644
index 0000000..13cec1e
--- /dev/null
+++ b/linux/sound/pci/intel8x0m.c
@@ -0,0 +1,1349 @@
+/*
+ *   ALSA modem driver for Intel ICH (i8x0) chipsets
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This is modified (by Sasha Khapyorsky <sashak@alsa-project.org>) version
+ *   of ALSA ICH sound driver intel8x0.c .
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Intel 82801AA,82901AB,i810,i820,i830,i840,i845,MX440; "
+		   "SiS 7013; NVidia MCP/2/2S/3 modems");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Intel,82801AA-ICH},"
+		"{Intel,82901AB-ICH0},"
+		"{Intel,82801BA-ICH2},"
+		"{Intel,82801CA-ICH3},"
+		"{Intel,82801DB-ICH4},"
+		"{Intel,ICH5},"
+		"{Intel,ICH6},"
+		"{Intel,ICH7},"
+	        "{Intel,MX440},"
+		"{SiS,7013},"
+		"{NVidia,NForce Modem},"
+		"{NVidia,NForce2 Modem},"
+		"{NVidia,NForce2s Modem},"
+		"{NVidia,NForce3 Modem},"
+		"{AMD,AMD768}}");
+
+static int index = -2; /* Exclude the first card */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+static int ac97_clock;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for Intel i8x0 modemcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for Intel i8x0 modemcard.");
+module_param(ac97_clock, int, 0444);
+MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (0 = auto-detect).");
+
+/* just for backward compatibility */
+static int enable;
+module_param(enable, bool, 0444);
+
+/*
+ *  Direct registers
+ */
+enum { DEVICE_INTEL, DEVICE_SIS, DEVICE_ALI, DEVICE_NFORCE };
+
+#define ICHREG(x) ICH_REG_##x
+
+#define DEFINE_REGSET(name,base) \
+enum { \
+	ICH_REG_##name##_BDBAR	= base + 0x0,	/* dword - buffer descriptor list base address */ \
+	ICH_REG_##name##_CIV	= base + 0x04,	/* byte - current index value */ \
+	ICH_REG_##name##_LVI	= base + 0x05,	/* byte - last valid index */ \
+	ICH_REG_##name##_SR	= base + 0x06,	/* byte - status register */ \
+	ICH_REG_##name##_PICB	= base + 0x08,	/* word - position in current buffer */ \
+	ICH_REG_##name##_PIV	= base + 0x0a,	/* byte - prefetched index value */ \
+	ICH_REG_##name##_CR	= base + 0x0b,	/* byte - control register */ \
+};
+
+/* busmaster blocks */
+DEFINE_REGSET(OFF, 0);		/* offset */
+
+/* values for each busmaster block */
+
+/* LVI */
+#define ICH_REG_LVI_MASK		0x1f
+
+/* SR */
+#define ICH_FIFOE			0x10	/* FIFO error */
+#define ICH_BCIS			0x08	/* buffer completion interrupt status */
+#define ICH_LVBCI			0x04	/* last valid buffer completion interrupt */
+#define ICH_CELV			0x02	/* current equals last valid */
+#define ICH_DCH				0x01	/* DMA controller halted */
+
+/* PIV */
+#define ICH_REG_PIV_MASK		0x1f	/* mask */
+
+/* CR */
+#define ICH_IOCE			0x10	/* interrupt on completion enable */
+#define ICH_FEIE			0x08	/* fifo error interrupt enable */
+#define ICH_LVBIE			0x04	/* last valid buffer interrupt enable */
+#define ICH_RESETREGS			0x02	/* reset busmaster registers */
+#define ICH_STARTBM			0x01	/* start busmaster operation */
+
+
+/* global block */
+#define ICH_REG_GLOB_CNT		0x3c	/* dword - global control */
+#define   ICH_TRIE		0x00000040	/* tertiary resume interrupt enable */
+#define   ICH_SRIE		0x00000020	/* secondary resume interrupt enable */
+#define   ICH_PRIE		0x00000010	/* primary resume interrupt enable */
+#define   ICH_ACLINK		0x00000008	/* AClink shut off */
+#define   ICH_AC97WARM		0x00000004	/* AC'97 warm reset */
+#define   ICH_AC97COLD		0x00000002	/* AC'97 cold reset */
+#define   ICH_GIE		0x00000001	/* GPI interrupt enable */
+#define ICH_REG_GLOB_STA		0x40	/* dword - global status */
+#define   ICH_TRI		0x20000000	/* ICH4: tertiary (AC_SDIN2) resume interrupt */
+#define   ICH_TCR		0x10000000	/* ICH4: tertiary (AC_SDIN2) codec ready */
+#define   ICH_BCS		0x08000000	/* ICH4: bit clock stopped */
+#define   ICH_SPINT		0x04000000	/* ICH4: S/PDIF interrupt */
+#define   ICH_P2INT		0x02000000	/* ICH4: PCM2-In interrupt */
+#define   ICH_M2INT		0x01000000	/* ICH4: Mic2-In interrupt */
+#define   ICH_SAMPLE_CAP	0x00c00000	/* ICH4: sample capability bits (RO) */
+#define   ICH_MULTICHAN_CAP	0x00300000	/* ICH4: multi-channel capability bits (RO) */
+#define   ICH_MD3		0x00020000	/* modem power down semaphore */
+#define   ICH_AD3		0x00010000	/* audio power down semaphore */
+#define   ICH_RCS		0x00008000	/* read completion status */
+#define   ICH_BIT3		0x00004000	/* bit 3 slot 12 */
+#define   ICH_BIT2		0x00002000	/* bit 2 slot 12 */
+#define   ICH_BIT1		0x00001000	/* bit 1 slot 12 */
+#define   ICH_SRI		0x00000800	/* secondary (AC_SDIN1) resume interrupt */
+#define   ICH_PRI		0x00000400	/* primary (AC_SDIN0) resume interrupt */
+#define   ICH_SCR		0x00000200	/* secondary (AC_SDIN1) codec ready */
+#define   ICH_PCR		0x00000100	/* primary (AC_SDIN0) codec ready */
+#define   ICH_MCINT		0x00000080	/* MIC capture interrupt */
+#define   ICH_POINT		0x00000040	/* playback interrupt */
+#define   ICH_PIINT		0x00000020	/* capture interrupt */
+#define   ICH_NVSPINT		0x00000010	/* nforce spdif interrupt */
+#define   ICH_MOINT		0x00000004	/* modem playback interrupt */
+#define   ICH_MIINT		0x00000002	/* modem capture interrupt */
+#define   ICH_GSCI		0x00000001	/* GPI status change interrupt */
+#define ICH_REG_ACC_SEMA		0x44	/* byte - codec write semaphore */
+#define   ICH_CAS		0x01		/* codec access semaphore */
+
+#define ICH_MAX_FRAGS		32		/* max hw frags */
+
+
+/*
+ *  
+ */
+
+enum { ICHD_MDMIN, ICHD_MDMOUT, ICHD_MDMLAST = ICHD_MDMOUT };
+enum { ALID_MDMIN, ALID_MDMOUT, ALID_MDMLAST = ALID_MDMOUT };
+
+#define get_ichdev(substream) (substream->runtime->private_data)
+
+struct ichdev {
+	unsigned int ichd;			/* ich device number */
+	unsigned long reg_offset;		/* offset to bmaddr */
+	u32 *bdbar;				/* CPU address (32bit) */
+	unsigned int bdbar_addr;		/* PCI bus address (32bit) */
+	struct snd_pcm_substream *substream;
+	unsigned int physbuf;			/* physical address (32bit) */
+        unsigned int size;
+        unsigned int fragsize;
+        unsigned int fragsize1;
+        unsigned int position;
+        int frags;
+        int lvi;
+        int lvi_frag;
+	int civ;
+	int ack;
+	int ack_reload;
+	unsigned int ack_bit;
+	unsigned int roff_sr;
+	unsigned int roff_picb;
+	unsigned int int_sta_mask;		/* interrupt status mask */
+	unsigned int ali_slot;			/* ALI DMA slot */
+	struct snd_ac97 *ac97;
+};
+
+struct intel8x0m {
+	unsigned int device_type;
+
+	int irq;
+
+	void __iomem *addr;
+	void __iomem *bmaddr;
+
+	struct pci_dev *pci;
+	struct snd_card *card;
+
+	int pcm_devs;
+	struct snd_pcm *pcm[2];
+	struct ichdev ichd[2];
+
+	unsigned int in_ac97_init: 1;
+
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97 *ac97;
+
+	spinlock_t reg_lock;
+	
+	struct snd_dma_buffer bdbars;
+	u32 bdbars_count;
+	u32 int_sta_reg;		/* interrupt status register */
+	u32 int_sta_mask;		/* interrupt status mask */
+	unsigned int pcm_pos_shift;
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_intel8x0m_ids) = {
+	{ PCI_VDEVICE(INTEL, 0x2416), DEVICE_INTEL },	/* 82801AA */
+	{ PCI_VDEVICE(INTEL, 0x2426), DEVICE_INTEL },	/* 82901AB */
+	{ PCI_VDEVICE(INTEL, 0x2446), DEVICE_INTEL },	/* 82801BA */
+	{ PCI_VDEVICE(INTEL, 0x2486), DEVICE_INTEL },	/* ICH3 */
+	{ PCI_VDEVICE(INTEL, 0x24c6), DEVICE_INTEL }, /* ICH4 */
+	{ PCI_VDEVICE(INTEL, 0x24d6), DEVICE_INTEL }, /* ICH5 */
+	{ PCI_VDEVICE(INTEL, 0x266d), DEVICE_INTEL },	/* ICH6 */
+	{ PCI_VDEVICE(INTEL, 0x27dd), DEVICE_INTEL },	/* ICH7 */
+	{ PCI_VDEVICE(INTEL, 0x7196), DEVICE_INTEL },	/* 440MX */
+	{ PCI_VDEVICE(AMD, 0x7446), DEVICE_INTEL },	/* AMD768 */
+	{ PCI_VDEVICE(SI, 0x7013), DEVICE_SIS },	/* SI7013 */
+	{ PCI_VDEVICE(NVIDIA, 0x01c1), DEVICE_NFORCE }, /* NFORCE */
+	{ PCI_VDEVICE(NVIDIA, 0x0069), DEVICE_NFORCE }, /* NFORCE2 */
+	{ PCI_VDEVICE(NVIDIA, 0x0089), DEVICE_NFORCE }, /* NFORCE2s */
+	{ PCI_VDEVICE(NVIDIA, 0x00d9), DEVICE_NFORCE }, /* NFORCE3 */
+#if 0
+	{ PCI_VDEVICE(AMD, 0x746d), DEVICE_INTEL },	/* AMD8111 */
+	{ PCI_VDEVICE(AL, 0x5455), DEVICE_ALI },   /* Ali5455 */
+#endif
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_intel8x0m_ids);
+
+/*
+ *  Lowlevel I/O - busmaster
+ */
+
+static inline u8 igetbyte(struct intel8x0m *chip, u32 offset)
+{
+	return ioread8(chip->bmaddr + offset);
+}
+
+static inline u16 igetword(struct intel8x0m *chip, u32 offset)
+{
+	return ioread16(chip->bmaddr + offset);
+}
+
+static inline u32 igetdword(struct intel8x0m *chip, u32 offset)
+{
+	return ioread32(chip->bmaddr + offset);
+}
+
+static inline void iputbyte(struct intel8x0m *chip, u32 offset, u8 val)
+{
+	iowrite8(val, chip->bmaddr + offset);
+}
+
+static inline void iputword(struct intel8x0m *chip, u32 offset, u16 val)
+{
+	iowrite16(val, chip->bmaddr + offset);
+}
+
+static inline void iputdword(struct intel8x0m *chip, u32 offset, u32 val)
+{
+	iowrite32(val, chip->bmaddr + offset);
+}
+
+/*
+ *  Lowlevel I/O - AC'97 registers
+ */
+
+static inline u16 iagetword(struct intel8x0m *chip, u32 offset)
+{
+	return ioread16(chip->addr + offset);
+}
+
+static inline void iaputword(struct intel8x0m *chip, u32 offset, u16 val)
+{
+	iowrite16(val, chip->addr + offset);
+}
+
+/*
+ *  Basic I/O
+ */
+
+/*
+ * access to AC97 codec via normal i/o (for ICH and SIS7013)
+ */
+
+/* return the GLOB_STA bit for the corresponding codec */
+static unsigned int get_ich_codec_bit(struct intel8x0m *chip, unsigned int codec)
+{
+	static unsigned int codec_bit[3] = {
+		ICH_PCR, ICH_SCR, ICH_TCR
+	};
+	if (snd_BUG_ON(codec >= 3))
+		return ICH_PCR;
+	return codec_bit[codec];
+}
+
+static int snd_intel8x0m_codec_semaphore(struct intel8x0m *chip, unsigned int codec)
+{
+	int time;
+	
+	if (codec > 1)
+		return -EIO;
+	codec = get_ich_codec_bit(chip, codec);
+
+	/* codec ready ? */
+	if ((igetdword(chip, ICHREG(GLOB_STA)) & codec) == 0)
+		return -EIO;
+
+	/* Anyone holding a semaphore for 1 msec should be shot... */
+	time = 100;
+      	do {
+      		if (!(igetbyte(chip, ICHREG(ACC_SEMA)) & ICH_CAS))
+      			return 0;
+		udelay(10);
+	} while (time--);
+
+	/* access to some forbidden (non existant) ac97 registers will not
+	 * reset the semaphore. So even if you don't get the semaphore, still
+	 * continue the access. We don't need the semaphore anyway. */
+	snd_printk(KERN_ERR "codec_semaphore: semaphore is not ready [0x%x][0x%x]\n",
+			igetbyte(chip, ICHREG(ACC_SEMA)), igetdword(chip, ICHREG(GLOB_STA)));
+	iagetword(chip, 0);	/* clear semaphore flag */
+	/* I don't care about the semaphore */
+	return -EBUSY;
+}
+ 
+static void snd_intel8x0_codec_write(struct snd_ac97 *ac97,
+				     unsigned short reg,
+				     unsigned short val)
+{
+	struct intel8x0m *chip = ac97->private_data;
+	
+	if (snd_intel8x0m_codec_semaphore(chip, ac97->num) < 0) {
+		if (! chip->in_ac97_init)
+			snd_printk(KERN_ERR "codec_write %d: semaphore is not ready for register 0x%x\n", ac97->num, reg);
+	}
+	iaputword(chip, reg + ac97->num * 0x80, val);
+}
+
+static unsigned short snd_intel8x0_codec_read(struct snd_ac97 *ac97,
+					      unsigned short reg)
+{
+	struct intel8x0m *chip = ac97->private_data;
+	unsigned short res;
+	unsigned int tmp;
+
+	if (snd_intel8x0m_codec_semaphore(chip, ac97->num) < 0) {
+		if (! chip->in_ac97_init)
+			snd_printk(KERN_ERR "codec_read %d: semaphore is not ready for register 0x%x\n", ac97->num, reg);
+		res = 0xffff;
+	} else {
+		res = iagetword(chip, reg + ac97->num * 0x80);
+		if ((tmp = igetdword(chip, ICHREG(GLOB_STA))) & ICH_RCS) {
+			/* reset RCS and preserve other R/WC bits */
+			iputdword(chip, ICHREG(GLOB_STA),
+				  tmp & ~(ICH_SRI|ICH_PRI|ICH_TRI|ICH_GSCI));
+			if (! chip->in_ac97_init)
+				snd_printk(KERN_ERR "codec_read %d: read timeout for register 0x%x\n", ac97->num, reg);
+			res = 0xffff;
+		}
+	}
+	if (reg == AC97_GPIO_STATUS)
+		iagetword(chip, 0); /* clear semaphore */
+	return res;
+}
+
+
+/*
+ * DMA I/O
+ */
+static void snd_intel8x0_setup_periods(struct intel8x0m *chip, struct ichdev *ichdev) 
+{
+	int idx;
+	u32 *bdbar = ichdev->bdbar;
+	unsigned long port = ichdev->reg_offset;
+
+	iputdword(chip, port + ICH_REG_OFF_BDBAR, ichdev->bdbar_addr);
+	if (ichdev->size == ichdev->fragsize) {
+		ichdev->ack_reload = ichdev->ack = 2;
+		ichdev->fragsize1 = ichdev->fragsize >> 1;
+		for (idx = 0; idx < (ICH_REG_LVI_MASK + 1) * 2; idx += 4) {
+			bdbar[idx + 0] = cpu_to_le32(ichdev->physbuf);
+			bdbar[idx + 1] = cpu_to_le32(0x80000000 | /* interrupt on completion */
+						     ichdev->fragsize1 >> chip->pcm_pos_shift);
+			bdbar[idx + 2] = cpu_to_le32(ichdev->physbuf + (ichdev->size >> 1));
+			bdbar[idx + 3] = cpu_to_le32(0x80000000 | /* interrupt on completion */
+						     ichdev->fragsize1 >> chip->pcm_pos_shift);
+		}
+		ichdev->frags = 2;
+	} else {
+		ichdev->ack_reload = ichdev->ack = 1;
+		ichdev->fragsize1 = ichdev->fragsize;
+		for (idx = 0; idx < (ICH_REG_LVI_MASK + 1) * 2; idx += 2) {
+			bdbar[idx + 0] = cpu_to_le32(ichdev->physbuf + (((idx >> 1) * ichdev->fragsize) % ichdev->size));
+			bdbar[idx + 1] = cpu_to_le32(0x80000000 | /* interrupt on completion */
+						     ichdev->fragsize >> chip->pcm_pos_shift);
+			/*
+			printk(KERN_DEBUG "bdbar[%i] = 0x%x [0x%x]\n",
+			       idx + 0, bdbar[idx + 0], bdbar[idx + 1]);
+			*/
+		}
+		ichdev->frags = ichdev->size / ichdev->fragsize;
+	}
+	iputbyte(chip, port + ICH_REG_OFF_LVI, ichdev->lvi = ICH_REG_LVI_MASK);
+	ichdev->civ = 0;
+	iputbyte(chip, port + ICH_REG_OFF_CIV, 0);
+	ichdev->lvi_frag = ICH_REG_LVI_MASK % ichdev->frags;
+	ichdev->position = 0;
+#if 0
+	printk(KERN_DEBUG "lvi_frag = %i, frags = %i, period_size = 0x%x, "
+	       "period_size1 = 0x%x\n",
+	       ichdev->lvi_frag, ichdev->frags, ichdev->fragsize,
+	       ichdev->fragsize1);
+#endif
+	/* clear interrupts */
+	iputbyte(chip, port + ichdev->roff_sr, ICH_FIFOE | ICH_BCIS | ICH_LVBCI);
+}
+
+/*
+ *  Interrupt handler
+ */
+
+static inline void snd_intel8x0_update(struct intel8x0m *chip, struct ichdev *ichdev)
+{
+	unsigned long port = ichdev->reg_offset;
+	int civ, i, step;
+	int ack = 0;
+
+	civ = igetbyte(chip, port + ICH_REG_OFF_CIV);
+	if (civ == ichdev->civ) {
+		// snd_printd("civ same %d\n", civ);
+		step = 1;
+		ichdev->civ++;
+		ichdev->civ &= ICH_REG_LVI_MASK;
+	} else {
+		step = civ - ichdev->civ;
+		if (step < 0)
+			step += ICH_REG_LVI_MASK + 1;
+		// if (step != 1)
+		//	snd_printd("step = %d, %d -> %d\n", step, ichdev->civ, civ);
+		ichdev->civ = civ;
+	}
+
+	ichdev->position += step * ichdev->fragsize1;
+	ichdev->position %= ichdev->size;
+	ichdev->lvi += step;
+	ichdev->lvi &= ICH_REG_LVI_MASK;
+	iputbyte(chip, port + ICH_REG_OFF_LVI, ichdev->lvi);
+	for (i = 0; i < step; i++) {
+		ichdev->lvi_frag++;
+		ichdev->lvi_frag %= ichdev->frags;
+		ichdev->bdbar[ichdev->lvi * 2] = cpu_to_le32(ichdev->physbuf +
+							     ichdev->lvi_frag *
+							     ichdev->fragsize1);
+#if 0
+		printk(KERN_DEBUG "new: bdbar[%i] = 0x%x [0x%x], "
+		       "prefetch = %i, all = 0x%x, 0x%x\n",
+		       ichdev->lvi * 2, ichdev->bdbar[ichdev->lvi * 2],
+		       ichdev->bdbar[ichdev->lvi * 2 + 1], inb(ICH_REG_OFF_PIV + port),
+		       inl(port + 4), inb(port + ICH_REG_OFF_CR));
+#endif
+		if (--ichdev->ack == 0) {
+			ichdev->ack = ichdev->ack_reload;
+			ack = 1;
+		}
+	}
+	if (ack && ichdev->substream) {
+		spin_unlock(&chip->reg_lock);
+		snd_pcm_period_elapsed(ichdev->substream);
+		spin_lock(&chip->reg_lock);
+	}
+	iputbyte(chip, port + ichdev->roff_sr, ICH_FIFOE | ICH_BCIS | ICH_LVBCI);
+}
+
+static irqreturn_t snd_intel8x0_interrupt(int irq, void *dev_id)
+{
+	struct intel8x0m *chip = dev_id;
+	struct ichdev *ichdev;
+	unsigned int status;
+	unsigned int i;
+
+	spin_lock(&chip->reg_lock);
+	status = igetdword(chip, chip->int_sta_reg);
+	if (status == 0xffffffff) { /* we are not yet resumed */
+		spin_unlock(&chip->reg_lock);
+		return IRQ_NONE;
+	}
+	if ((status & chip->int_sta_mask) == 0) {
+		if (status)
+			iputdword(chip, chip->int_sta_reg, status);
+		spin_unlock(&chip->reg_lock);
+		return IRQ_NONE;
+	}
+
+	for (i = 0; i < chip->bdbars_count; i++) {
+		ichdev = &chip->ichd[i];
+		if (status & ichdev->int_sta_mask)
+			snd_intel8x0_update(chip, ichdev);
+	}
+
+	/* ack them */
+	iputdword(chip, chip->int_sta_reg, status & chip->int_sta_mask);
+	spin_unlock(&chip->reg_lock);
+	
+	return IRQ_HANDLED;
+}
+
+/*
+ *  PCM part
+ */
+
+static int snd_intel8x0_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct intel8x0m *chip = snd_pcm_substream_chip(substream);
+	struct ichdev *ichdev = get_ichdev(substream);
+	unsigned char val = 0;
+	unsigned long port = ichdev->reg_offset;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		val = ICH_IOCE | ICH_STARTBM;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		val = 0;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		val = ICH_IOCE;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		val = ICH_IOCE | ICH_STARTBM;
+		break;
+	default:
+		return -EINVAL;
+	}
+	iputbyte(chip, port + ICH_REG_OFF_CR, val);
+	if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		/* wait until DMA stopped */
+		while (!(igetbyte(chip, port + ichdev->roff_sr) & ICH_DCH)) ;
+		/* reset whole DMA things */
+		iputbyte(chip, port + ICH_REG_OFF_CR, ICH_RESETREGS);
+	}
+	return 0;
+}
+
+static int snd_intel8x0_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_intel8x0_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static snd_pcm_uframes_t snd_intel8x0_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct intel8x0m *chip = snd_pcm_substream_chip(substream);
+	struct ichdev *ichdev = get_ichdev(substream);
+	size_t ptr1, ptr;
+
+	ptr1 = igetword(chip, ichdev->reg_offset + ichdev->roff_picb) << chip->pcm_pos_shift;
+	if (ptr1 != 0)
+		ptr = ichdev->fragsize1 - ptr1;
+	else
+		ptr = 0;
+	ptr += ichdev->position;
+	if (ptr >= ichdev->size)
+		return 0;
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static int snd_intel8x0m_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct intel8x0m *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ichdev *ichdev = get_ichdev(substream);
+
+	ichdev->physbuf = runtime->dma_addr;
+	ichdev->size = snd_pcm_lib_buffer_bytes(substream);
+	ichdev->fragsize = snd_pcm_lib_period_bytes(substream);
+	snd_ac97_write(ichdev->ac97, AC97_LINE1_RATE, runtime->rate);
+	snd_ac97_write(ichdev->ac97, AC97_LINE1_LEVEL, 0);
+	snd_intel8x0_setup_periods(chip, ichdev);
+	return 0;
+}
+
+static struct snd_pcm_hardware snd_intel8x0m_stream =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_KNOT,
+	.rate_min =		8000,
+	.rate_max =		16000,
+	.channels_min =		1,
+	.channels_max =		1,
+	.buffer_bytes_max =	64 * 1024,
+	.period_bytes_min =	32,
+	.period_bytes_max =	64 * 1024,
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+
+static int snd_intel8x0m_pcm_open(struct snd_pcm_substream *substream, struct ichdev *ichdev)
+{
+	static unsigned int rates[] = { 8000,  9600, 12000, 16000 };
+	static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+		.count = ARRAY_SIZE(rates),
+		.list = rates,
+		.mask = 0,
+	};
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	ichdev->substream = substream;
+	runtime->hw = snd_intel8x0m_stream;
+	err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+					 &hw_constraints_rates);
+	if ( err < 0 )
+		return err;
+	runtime->private_data = ichdev;
+	return 0;
+}
+
+static int snd_intel8x0m_playback_open(struct snd_pcm_substream *substream)
+{
+	struct intel8x0m *chip = snd_pcm_substream_chip(substream);
+
+	return snd_intel8x0m_pcm_open(substream, &chip->ichd[ICHD_MDMOUT]);
+}
+
+static int snd_intel8x0m_playback_close(struct snd_pcm_substream *substream)
+{
+	struct intel8x0m *chip = snd_pcm_substream_chip(substream);
+
+	chip->ichd[ICHD_MDMOUT].substream = NULL;
+	return 0;
+}
+
+static int snd_intel8x0m_capture_open(struct snd_pcm_substream *substream)
+{
+	struct intel8x0m *chip = snd_pcm_substream_chip(substream);
+
+	return snd_intel8x0m_pcm_open(substream, &chip->ichd[ICHD_MDMIN]);
+}
+
+static int snd_intel8x0m_capture_close(struct snd_pcm_substream *substream)
+{
+	struct intel8x0m *chip = snd_pcm_substream_chip(substream);
+
+	chip->ichd[ICHD_MDMIN].substream = NULL;
+	return 0;
+}
+
+
+static struct snd_pcm_ops snd_intel8x0m_playback_ops = {
+	.open =		snd_intel8x0m_playback_open,
+	.close =	snd_intel8x0m_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0m_pcm_prepare,
+	.trigger =	snd_intel8x0_pcm_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_intel8x0m_capture_ops = {
+	.open =		snd_intel8x0m_capture_open,
+	.close =	snd_intel8x0m_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_intel8x0_hw_params,
+	.hw_free =	snd_intel8x0_hw_free,
+	.prepare =	snd_intel8x0m_pcm_prepare,
+	.trigger =	snd_intel8x0_pcm_trigger,
+	.pointer =	snd_intel8x0_pcm_pointer,
+};
+
+
+struct ich_pcm_table {
+	char *suffix;
+	struct snd_pcm_ops *playback_ops;
+	struct snd_pcm_ops *capture_ops;
+	size_t prealloc_size;
+	size_t prealloc_max_size;
+	int ac97_idx;
+};
+
+static int __devinit snd_intel8x0_pcm1(struct intel8x0m *chip, int device,
+				       struct ich_pcm_table *rec)
+{
+	struct snd_pcm *pcm;
+	int err;
+	char name[32];
+
+	if (rec->suffix)
+		sprintf(name, "Intel ICH - %s", rec->suffix);
+	else
+		strcpy(name, "Intel ICH");
+	err = snd_pcm_new(chip->card, name, device,
+			  rec->playback_ops ? 1 : 0,
+			  rec->capture_ops ? 1 : 0, &pcm);
+	if (err < 0)
+		return err;
+
+	if (rec->playback_ops)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, rec->playback_ops);
+	if (rec->capture_ops)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, rec->capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	pcm->dev_class = SNDRV_PCM_CLASS_MODEM;
+	if (rec->suffix)
+		sprintf(pcm->name, "%s - %s", chip->card->shortname, rec->suffix);
+	else
+		strcpy(pcm->name, chip->card->shortname);
+	chip->pcm[device] = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci),
+					      rec->prealloc_size,
+					      rec->prealloc_max_size);
+
+	return 0;
+}
+
+static struct ich_pcm_table intel_pcms[] __devinitdata = {
+	{
+		.suffix = "Modem",
+		.playback_ops = &snd_intel8x0m_playback_ops,
+		.capture_ops = &snd_intel8x0m_capture_ops,
+		.prealloc_size = 32 * 1024,
+		.prealloc_max_size = 64 * 1024,
+	},
+};
+
+static int __devinit snd_intel8x0_pcm(struct intel8x0m *chip)
+{
+	int i, tblsize, device, err;
+	struct ich_pcm_table *tbl, *rec;
+
+#if 1
+	tbl = intel_pcms;
+	tblsize = 1;
+#else
+	switch (chip->device_type) {
+	case DEVICE_NFORCE:
+		tbl = nforce_pcms;
+		tblsize = ARRAY_SIZE(nforce_pcms);
+		break;
+	case DEVICE_ALI:
+		tbl = ali_pcms;
+		tblsize = ARRAY_SIZE(ali_pcms);
+		break;
+	default:
+		tbl = intel_pcms;
+		tblsize = 2;
+		break;
+	}
+#endif
+	device = 0;
+	for (i = 0; i < tblsize; i++) {
+		rec = tbl + i;
+		if (i > 0 && rec->ac97_idx) {
+			/* activate PCM only when associated AC'97 codec */
+			if (! chip->ichd[rec->ac97_idx].ac97)
+				continue;
+		}
+		err = snd_intel8x0_pcm1(chip, device, rec);
+		if (err < 0)
+			return err;
+		device++;
+	}
+
+	chip->pcm_devs = device;
+	return 0;
+}
+	
+
+/*
+ *  Mixer part
+ */
+
+static void snd_intel8x0_mixer_free_ac97_bus(struct snd_ac97_bus *bus)
+{
+	struct intel8x0m *chip = bus->private_data;
+	chip->ac97_bus = NULL;
+}
+
+static void snd_intel8x0_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+	struct intel8x0m *chip = ac97->private_data;
+	chip->ac97 = NULL;
+}
+
+
+static int __devinit snd_intel8x0_mixer(struct intel8x0m *chip, int ac97_clock)
+{
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	struct snd_ac97 *x97;
+	int err;
+	unsigned int glob_sta = 0;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_intel8x0_codec_write,
+		.read = snd_intel8x0_codec_read,
+	};
+
+	chip->in_ac97_init = 1;
+	
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.private_free = snd_intel8x0_mixer_free_ac97;
+	ac97.scaps = AC97_SCAP_SKIP_AUDIO | AC97_SCAP_POWER_SAVE;
+
+	glob_sta = igetdword(chip, ICHREG(GLOB_STA));
+
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus)) < 0)
+		goto __err;
+	pbus->private_free = snd_intel8x0_mixer_free_ac97_bus;
+	if (ac97_clock >= 8000 && ac97_clock <= 48000)
+		pbus->clock = ac97_clock;
+	chip->ac97_bus = pbus;
+
+	ac97.pci = chip->pci;
+	ac97.num = glob_sta & ICH_SCR ? 1 : 0;
+	if ((err = snd_ac97_mixer(pbus, &ac97, &x97)) < 0) {
+		snd_printk(KERN_ERR "Unable to initialize codec #%d\n", ac97.num);
+		if (ac97.num == 0)
+			goto __err;
+		return err;
+	}
+	chip->ac97 = x97;
+	if(ac97_is_modem(x97) && !chip->ichd[ICHD_MDMIN].ac97) {
+		chip->ichd[ICHD_MDMIN].ac97 = x97;
+		chip->ichd[ICHD_MDMOUT].ac97 = x97;
+	}
+
+	chip->in_ac97_init = 0;
+	return 0;
+
+ __err:
+	/* clear the cold-reset bit for the next chance */
+	if (chip->device_type != DEVICE_ALI)
+		iputdword(chip, ICHREG(GLOB_CNT),
+			  igetdword(chip, ICHREG(GLOB_CNT)) & ~ICH_AC97COLD);
+	return err;
+}
+
+
+/*
+ *
+ */
+
+static int snd_intel8x0m_ich_chip_init(struct intel8x0m *chip, int probing)
+{
+	unsigned long end_time;
+	unsigned int cnt, status, nstatus;
+	
+	/* put logic to right state */
+	/* first clear status bits */
+	status = ICH_RCS | ICH_MIINT | ICH_MOINT;
+	cnt = igetdword(chip, ICHREG(GLOB_STA));
+	iputdword(chip, ICHREG(GLOB_STA), cnt & status);
+
+	/* ACLink on, 2 channels */
+	cnt = igetdword(chip, ICHREG(GLOB_CNT));
+	cnt &= ~(ICH_ACLINK);
+	/* finish cold or do warm reset */
+	cnt |= (cnt & ICH_AC97COLD) == 0 ? ICH_AC97COLD : ICH_AC97WARM;
+	iputdword(chip, ICHREG(GLOB_CNT), cnt);
+	end_time = (jiffies + (HZ / 4)) + 1;
+	do {
+		if ((igetdword(chip, ICHREG(GLOB_CNT)) & ICH_AC97WARM) == 0)
+			goto __ok;
+		schedule_timeout_uninterruptible(1);
+	} while (time_after_eq(end_time, jiffies));
+	snd_printk(KERN_ERR "AC'97 warm reset still in progress? [0x%x]\n",
+		   igetdword(chip, ICHREG(GLOB_CNT)));
+	return -EIO;
+
+      __ok:
+	if (probing) {
+		/* wait for any codec ready status.
+		 * Once it becomes ready it should remain ready
+		 * as long as we do not disable the ac97 link.
+		 */
+		end_time = jiffies + HZ;
+		do {
+			status = igetdword(chip, ICHREG(GLOB_STA)) &
+				(ICH_PCR | ICH_SCR | ICH_TCR);
+			if (status)
+				break;
+			schedule_timeout_uninterruptible(1);
+		} while (time_after_eq(end_time, jiffies));
+		if (! status) {
+			/* no codec is found */
+			snd_printk(KERN_ERR "codec_ready: codec is not ready [0x%x]\n",
+				   igetdword(chip, ICHREG(GLOB_STA)));
+			return -EIO;
+		}
+
+		/* up to two codecs (modem cannot be tertiary with ICH4) */
+		nstatus = ICH_PCR | ICH_SCR;
+
+		/* wait for other codecs ready status. */
+		end_time = jiffies + HZ / 4;
+		while (status != nstatus && time_after_eq(end_time, jiffies)) {
+			schedule_timeout_uninterruptible(1);
+			status |= igetdword(chip, ICHREG(GLOB_STA)) & nstatus;
+		}
+
+	} else {
+		/* resume phase */
+		status = 0;
+		if (chip->ac97)
+			status |= get_ich_codec_bit(chip, chip->ac97->num);
+		/* wait until all the probed codecs are ready */
+		end_time = jiffies + HZ;
+		do {
+			nstatus = igetdword(chip, ICHREG(GLOB_STA)) &
+				(ICH_PCR | ICH_SCR | ICH_TCR);
+			if (status == nstatus)
+				break;
+			schedule_timeout_uninterruptible(1);
+		} while (time_after_eq(end_time, jiffies));
+	}
+
+	if (chip->device_type == DEVICE_SIS) {
+		/* unmute the output on SIS7012 */
+		iputword(chip, 0x4c, igetword(chip, 0x4c) | 1);
+	}
+
+      	return 0;
+}
+
+static int snd_intel8x0_chip_init(struct intel8x0m *chip, int probing)
+{
+	unsigned int i;
+	int err;
+	
+	if ((err = snd_intel8x0m_ich_chip_init(chip, probing)) < 0)
+		return err;
+	iagetword(chip, 0);	/* clear semaphore flag */
+
+	/* disable interrupts */
+	for (i = 0; i < chip->bdbars_count; i++)
+		iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, 0x00);
+	/* reset channels */
+	for (i = 0; i < chip->bdbars_count; i++)
+		iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, ICH_RESETREGS);
+	/* initialize Buffer Descriptor Lists */
+	for (i = 0; i < chip->bdbars_count; i++)
+		iputdword(chip, ICH_REG_OFF_BDBAR + chip->ichd[i].reg_offset, chip->ichd[i].bdbar_addr);
+	return 0;
+}
+
+static int snd_intel8x0_free(struct intel8x0m *chip)
+{
+	unsigned int i;
+
+	if (chip->irq < 0)
+		goto __hw_end;
+	/* disable interrupts */
+	for (i = 0; i < chip->bdbars_count; i++)
+		iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, 0x00);
+	/* reset channels */
+	for (i = 0; i < chip->bdbars_count; i++)
+		iputbyte(chip, ICH_REG_OFF_CR + chip->ichd[i].reg_offset, ICH_RESETREGS);
+ __hw_end:
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	if (chip->bdbars.area)
+		snd_dma_free_pages(&chip->bdbars);
+	if (chip->addr)
+		pci_iounmap(chip->pci, chip->addr);
+	if (chip->bmaddr)
+		pci_iounmap(chip->pci, chip->bmaddr);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+static int intel8x0m_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct intel8x0m *chip = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	for (i = 0; i < chip->pcm_devs; i++)
+		snd_pcm_suspend_all(chip->pcm[i]);
+	snd_ac97_suspend(chip->ac97);
+	if (chip->irq >= 0) {
+		free_irq(chip->irq, chip);
+		chip->irq = -1;
+	}
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int intel8x0m_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct intel8x0m *chip = card->private_data;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "intel8x0m: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+	if (request_irq(pci->irq, snd_intel8x0_interrupt,
+			IRQF_SHARED, card->shortname, chip)) {
+		printk(KERN_ERR "intel8x0m: unable to grab IRQ %d, "
+		       "disabling device\n", pci->irq);
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	chip->irq = pci->irq;
+	snd_intel8x0_chip_init(chip, 0);
+	snd_ac97_resume(chip->ac97);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_PROC_FS
+static void snd_intel8x0m_proc_read(struct snd_info_entry * entry,
+				   struct snd_info_buffer *buffer)
+{
+	struct intel8x0m *chip = entry->private_data;
+	unsigned int tmp;
+
+	snd_iprintf(buffer, "Intel8x0m\n\n");
+	if (chip->device_type == DEVICE_ALI)
+		return;
+	tmp = igetdword(chip, ICHREG(GLOB_STA));
+	snd_iprintf(buffer, "Global control        : 0x%08x\n",
+		    igetdword(chip, ICHREG(GLOB_CNT)));
+	snd_iprintf(buffer, "Global status         : 0x%08x\n", tmp);
+	snd_iprintf(buffer, "AC'97 codecs ready    :%s%s%s%s\n",
+			tmp & ICH_PCR ? " primary" : "",
+			tmp & ICH_SCR ? " secondary" : "",
+			tmp & ICH_TCR ? " tertiary" : "",
+			(tmp & (ICH_PCR | ICH_SCR | ICH_TCR)) == 0 ? " none" : "");
+}
+
+static void __devinit snd_intel8x0m_proc_init(struct intel8x0m * chip)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(chip->card, "intel8x0m", &entry))
+		snd_info_set_text_ops(entry, chip, snd_intel8x0m_proc_read);
+}
+#else /* !CONFIG_PROC_FS */
+#define snd_intel8x0m_proc_init(chip)
+#endif /* CONFIG_PROC_FS */
+
+
+static int snd_intel8x0_dev_free(struct snd_device *device)
+{
+	struct intel8x0m *chip = device->device_data;
+	return snd_intel8x0_free(chip);
+}
+
+struct ich_reg_info {
+	unsigned int int_sta_mask;
+	unsigned int offset;
+};
+
+static int __devinit snd_intel8x0m_create(struct snd_card *card,
+					 struct pci_dev *pci,
+					 unsigned long device_type,
+					 struct intel8x0m ** r_intel8x0)
+{
+	struct intel8x0m *chip;
+	int err;
+	unsigned int i;
+	unsigned int int_sta_masks;
+	struct ichdev *ichdev;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_intel8x0_dev_free,
+	};
+	static struct ich_reg_info intel_regs[2] = {
+		{ ICH_MIINT, 0 },
+		{ ICH_MOINT, 0x10 },
+	};
+	struct ich_reg_info *tbl;
+
+	*r_intel8x0 = NULL;
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	spin_lock_init(&chip->reg_lock);
+	chip->device_type = device_type;
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	if ((err = pci_request_regions(pci, card->shortname)) < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+
+	if (device_type == DEVICE_ALI) {
+		/* ALI5455 has no ac97 region */
+		chip->bmaddr = pci_iomap(pci, 0, 0);
+		goto port_inited;
+	}
+
+	if (pci_resource_flags(pci, 2) & IORESOURCE_MEM) /* ICH4 and Nforce */
+		chip->addr = pci_iomap(pci, 2, 0);
+	else
+		chip->addr = pci_iomap(pci, 0, 0);
+	if (!chip->addr) {
+		snd_printk(KERN_ERR "AC'97 space ioremap problem\n");
+		snd_intel8x0_free(chip);
+		return -EIO;
+	}
+	if (pci_resource_flags(pci, 3) & IORESOURCE_MEM) /* ICH4 */
+		chip->bmaddr = pci_iomap(pci, 3, 0);
+	else
+		chip->bmaddr = pci_iomap(pci, 1, 0);
+	if (!chip->bmaddr) {
+		snd_printk(KERN_ERR "Controller space ioremap problem\n");
+		snd_intel8x0_free(chip);
+		return -EIO;
+	}
+
+ port_inited:
+	if (request_irq(pci->irq, snd_intel8x0_interrupt, IRQF_SHARED,
+			card->shortname, chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_intel8x0_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+	pci_set_master(pci);
+	synchronize_irq(chip->irq);
+
+	/* initialize offsets */
+	chip->bdbars_count = 2;
+	tbl = intel_regs;
+
+	for (i = 0; i < chip->bdbars_count; i++) {
+		ichdev = &chip->ichd[i];
+		ichdev->ichd = i;
+		ichdev->reg_offset = tbl[i].offset;
+		ichdev->int_sta_mask = tbl[i].int_sta_mask;
+		if (device_type == DEVICE_SIS) {
+			/* SiS 7013 swaps the registers */
+			ichdev->roff_sr = ICH_REG_OFF_PICB;
+			ichdev->roff_picb = ICH_REG_OFF_SR;
+		} else {
+			ichdev->roff_sr = ICH_REG_OFF_SR;
+			ichdev->roff_picb = ICH_REG_OFF_PICB;
+		}
+		if (device_type == DEVICE_ALI)
+			ichdev->ali_slot = (ichdev->reg_offset - 0x40) / 0x10;
+	}
+	/* SIS7013 handles the pcm data in bytes, others are in words */
+	chip->pcm_pos_shift = (device_type == DEVICE_SIS) ? 0 : 1;
+
+	/* allocate buffer descriptor lists */
+	/* the start of each lists must be aligned to 8 bytes */
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				chip->bdbars_count * sizeof(u32) * ICH_MAX_FRAGS * 2,
+				&chip->bdbars) < 0) {
+		snd_intel8x0_free(chip);
+		return -ENOMEM;
+	}
+	/* tables must be aligned to 8 bytes here, but the kernel pages
+	   are much bigger, so we don't care (on i386) */
+	int_sta_masks = 0;
+	for (i = 0; i < chip->bdbars_count; i++) {
+		ichdev = &chip->ichd[i];
+		ichdev->bdbar = ((u32 *)chip->bdbars.area) + (i * ICH_MAX_FRAGS * 2);
+		ichdev->bdbar_addr = chip->bdbars.addr + (i * sizeof(u32) * ICH_MAX_FRAGS * 2);
+		int_sta_masks |= ichdev->int_sta_mask;
+	}
+	chip->int_sta_reg = ICH_REG_GLOB_STA;
+	chip->int_sta_mask = int_sta_masks;
+
+	if ((err = snd_intel8x0_chip_init(chip, 1)) < 0) {
+		snd_intel8x0_free(chip);
+		return err;
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_intel8x0_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*r_intel8x0 = chip;
+	return 0;
+}
+
+static struct shortname_table {
+	unsigned int id;
+	const char *s;
+} shortnames[] __devinitdata = {
+	{ PCI_DEVICE_ID_INTEL_82801AA_6, "Intel 82801AA-ICH" },
+	{ PCI_DEVICE_ID_INTEL_82801AB_6, "Intel 82901AB-ICH0" },
+	{ PCI_DEVICE_ID_INTEL_82801BA_6, "Intel 82801BA-ICH2" },
+	{ PCI_DEVICE_ID_INTEL_440MX_6, "Intel 440MX" },
+	{ PCI_DEVICE_ID_INTEL_82801CA_6, "Intel 82801CA-ICH3" },
+	{ PCI_DEVICE_ID_INTEL_82801DB_6, "Intel 82801DB-ICH4" },
+	{ PCI_DEVICE_ID_INTEL_82801EB_6, "Intel ICH5" },
+	{ PCI_DEVICE_ID_INTEL_ICH6_17, "Intel ICH6" },
+	{ PCI_DEVICE_ID_INTEL_ICH7_19, "Intel ICH7" },
+	{ 0x7446, "AMD AMD768" },
+	{ PCI_DEVICE_ID_SI_7013, "SiS SI7013" },
+	{ PCI_DEVICE_ID_NVIDIA_MCP1_MODEM, "NVidia nForce" },
+	{ PCI_DEVICE_ID_NVIDIA_MCP2_MODEM, "NVidia nForce2" },
+	{ PCI_DEVICE_ID_NVIDIA_MCP2S_MODEM, "NVidia nForce2s" },
+	{ PCI_DEVICE_ID_NVIDIA_MCP3_MODEM, "NVidia nForce3" },
+#if 0
+	{ 0x5455, "ALi M5455" },
+	{ 0x746d, "AMD AMD8111" },
+#endif
+	{ 0 },
+};
+
+static int __devinit snd_intel8x0m_probe(struct pci_dev *pci,
+					const struct pci_device_id *pci_id)
+{
+	struct snd_card *card;
+	struct intel8x0m *chip;
+	int err;
+	struct shortname_table *name;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	strcpy(card->driver, "ICH-MODEM");
+	strcpy(card->shortname, "Intel ICH");
+	for (name = shortnames; name->id; name++) {
+		if (pci->device == name->id) {
+			strcpy(card->shortname, name->s);
+			break;
+		}
+	}
+	strcat(card->shortname," Modem");
+
+	if ((err = snd_intel8x0m_create(card, pci, pci_id->driver_data, &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = chip;
+
+	if ((err = snd_intel8x0_mixer(chip, ac97_clock)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_intel8x0_pcm(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	
+	snd_intel8x0m_proc_init(chip);
+
+	sprintf(card->longname, "%s at irq %i",
+		card->shortname, chip->irq);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	return 0;
+}
+
+static void __devexit snd_intel8x0m_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "Intel ICH Modem",
+	.id_table = snd_intel8x0m_ids,
+	.probe = snd_intel8x0m_probe,
+	.remove = __devexit_p(snd_intel8x0m_remove),
+#ifdef CONFIG_PM
+	.suspend = intel8x0m_suspend,
+	.resume = intel8x0m_resume,
+#endif
+};
+
+
+static int __init alsa_card_intel8x0m_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_intel8x0m_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_intel8x0m_init)
+module_exit(alsa_card_intel8x0m_exit)
diff --git a/linux/sound/pci/korg1212/Makefile b/linux/sound/pci/korg1212/Makefile
new file mode 100644
index 0000000..f11ce1b
--- /dev/null
+++ b/linux/sound/pci/korg1212/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-korg1212-objs := korg1212.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_KORG1212) += snd-korg1212.o
diff --git a/linux/sound/pci/korg1212/korg1212.c b/linux/sound/pci/korg1212/korg1212.c
new file mode 100644
index 0000000..6d79570
--- /dev/null
+++ b/linux/sound/pci/korg1212/korg1212.c
@@ -0,0 +1,2497 @@
+/*
+ *   Driver for the Korg 1212 IO PCI card
+ *
+ *	Copyright (c) 2001 Haroldo Gamal <gamal@alternex.com.br>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/firmware.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+
+// ----------------------------------------------------------------------------
+// Debug Stuff
+// ----------------------------------------------------------------------------
+#define K1212_DEBUG_LEVEL		0
+#if K1212_DEBUG_LEVEL > 0
+#define K1212_DEBUG_PRINTK(fmt,args...)	printk(KERN_DEBUG fmt,##args)
+#else
+#define K1212_DEBUG_PRINTK(fmt,...)
+#endif
+#if K1212_DEBUG_LEVEL > 1
+#define K1212_DEBUG_PRINTK_VERBOSE(fmt,args...)	printk(KERN_DEBUG fmt,##args)
+#else
+#define K1212_DEBUG_PRINTK_VERBOSE(fmt,...)
+#endif
+
+// ----------------------------------------------------------------------------
+// Record/Play Buffer Allocation Method. If K1212_LARGEALLOC is defined all 
+// buffers are alocated as a large piece inside KorgSharedBuffer.
+// ----------------------------------------------------------------------------
+//#define K1212_LARGEALLOC		1
+
+// ----------------------------------------------------------------------------
+// Valid states of the Korg 1212 I/O card.
+// ----------------------------------------------------------------------------
+enum CardState {
+   K1212_STATE_NONEXISTENT,		// there is no card here
+   K1212_STATE_UNINITIALIZED,		// the card is awaiting DSP download
+   K1212_STATE_DSP_IN_PROCESS,		// the card is currently downloading its DSP code
+   K1212_STATE_DSP_COMPLETE,		// the card has finished the DSP download
+   K1212_STATE_READY,			// the card can be opened by an application.  Any application
+					//    requests prior to this state should fail.  Only an open
+					//    request can be made at this state.
+   K1212_STATE_OPEN,			// an application has opened the card
+   K1212_STATE_SETUP,			// the card has been setup for play
+   K1212_STATE_PLAYING,			// the card is playing
+   K1212_STATE_MONITOR,			// the card is in the monitor mode
+   K1212_STATE_CALIBRATING,		// the card is currently calibrating
+   K1212_STATE_ERRORSTOP,		// the card has stopped itself because of an error and we
+					//    are in the process of cleaning things up.
+   K1212_STATE_MAX_STATE		// state values of this and beyond are invalid
+};
+
+// ----------------------------------------------------------------------------
+// The following enumeration defines the constants written to the card's
+// host-to-card doorbell to initiate a command.
+// ----------------------------------------------------------------------------
+enum korg1212_dbcnst {
+   K1212_DB_RequestForData        = 0,    // sent by the card to request a buffer fill.
+   K1212_DB_TriggerPlay           = 1,    // starts playback/record on the card.
+   K1212_DB_SelectPlayMode        = 2,    // select monitor, playback setup, or stop.
+   K1212_DB_ConfigureBufferMemory = 3,    // tells card where the host audio buffers are.
+   K1212_DB_RequestAdatTimecode   = 4,    // asks the card for the latest ADAT timecode value.
+   K1212_DB_SetClockSourceRate    = 5,    // sets the clock source and rate for the card.
+   K1212_DB_ConfigureMiscMemory   = 6,    // tells card where other buffers are.
+   K1212_DB_TriggerFromAdat       = 7,    // tells card to trigger from Adat at a specific
+                                          //    timecode value.
+   K1212_DB_DMAERROR              = 0x80, // DMA Error - the PCI bus is congestioned.
+   K1212_DB_CARDSTOPPED           = 0x81, // Card has stopped by user request.
+   K1212_DB_RebootCard            = 0xA0, // instructs the card to reboot.
+   K1212_DB_BootFromDSPPage4      = 0xA4, // instructs the card to boot from the DSP microcode
+                                          //    on page 4 (local page to card).
+   K1212_DB_DSPDownloadDone       = 0xAE, // sent by the card to indicate the download has
+                                          //    completed.
+   K1212_DB_StartDSPDownload      = 0xAF  // tells the card to download its DSP firmware.
+};
+
+
+// ----------------------------------------------------------------------------
+// The following enumeration defines return codes 
+// to the Korg 1212 I/O driver.
+// ----------------------------------------------------------------------------
+enum snd_korg1212rc {
+   K1212_CMDRET_Success         = 0,   // command was successfully placed
+   K1212_CMDRET_DIOCFailure,           // the DeviceIoControl call failed
+   K1212_CMDRET_PMFailure,             // the protected mode call failed
+   K1212_CMDRET_FailUnspecified,       // unspecified failure
+   K1212_CMDRET_FailBadState,          // the specified command can not be given in
+                                       //    the card's current state. (or the wave device's
+                                       //    state)
+   K1212_CMDRET_CardUninitialized,     // the card is uninitialized and cannot be used
+   K1212_CMDRET_BadIndex,              // an out of range card index was specified
+   K1212_CMDRET_BadHandle,             // an invalid card handle was specified
+   K1212_CMDRET_NoFillRoutine,         // a play request has been made before a fill routine set
+   K1212_CMDRET_FillRoutineInUse,      // can't set a new fill routine while one is in use
+   K1212_CMDRET_NoAckFromCard,         // the card never acknowledged a command
+   K1212_CMDRET_BadParams,             // bad parameters were provided by the caller
+
+   K1212_CMDRET_BadDevice,             // the specified wave device was out of range
+   K1212_CMDRET_BadFormat              // the specified wave format is unsupported
+};
+
+// ----------------------------------------------------------------------------
+// The following enumeration defines the constants used to select the play
+// mode for the card in the SelectPlayMode command.
+// ----------------------------------------------------------------------------
+enum PlayModeSelector {
+   K1212_MODE_SetupPlay  = 0x00000001,     // provides card with pre-play information
+   K1212_MODE_MonitorOn  = 0x00000002,     // tells card to turn on monitor mode
+   K1212_MODE_MonitorOff = 0x00000004,     // tells card to turn off monitor mode
+   K1212_MODE_StopPlay   = 0x00000008      // stops playback on the card
+};
+
+// ----------------------------------------------------------------------------
+// The following enumeration defines the constants used to select the monitor
+// mode for the card in the SetMonitorMode command.
+// ----------------------------------------------------------------------------
+enum MonitorModeSelector {
+   K1212_MONMODE_Off  = 0,     // tells card to turn off monitor mode
+   K1212_MONMODE_On            // tells card to turn on monitor mode
+};
+
+#define MAILBOX0_OFFSET      0x40	// location of mailbox 0 relative to base address
+#define MAILBOX1_OFFSET      0x44	// location of mailbox 1 relative to base address
+#define MAILBOX2_OFFSET      0x48	// location of mailbox 2 relative to base address
+#define MAILBOX3_OFFSET      0x4c	// location of mailbox 3 relative to base address
+#define OUT_DOORBELL_OFFSET  0x60	// location of PCI to local doorbell
+#define IN_DOORBELL_OFFSET   0x64	// location of local to PCI doorbell
+#define STATUS_REG_OFFSET    0x68	// location of interrupt control/status register
+#define PCI_CONTROL_OFFSET   0x6c	// location of the EEPROM, PCI, User I/O, init control
+					//    register
+#define SENS_CONTROL_OFFSET  0x6e	// location of the input sensitivity setting register.
+					//    this is the upper word of the PCI control reg.
+#define DEV_VEND_ID_OFFSET   0x70	// location of the device and vendor ID register
+
+#define MAX_COMMAND_RETRIES  5         // maximum number of times the driver will attempt
+                                       //    to send a command before giving up.
+#define COMMAND_ACK_MASK     0x8000    // the MSB is set in the command acknowledgment from
+                                        //    the card.
+#define DOORBELL_VAL_MASK    0x00FF    // the doorbell value is one byte
+
+#define CARD_BOOT_DELAY_IN_MS  10
+#define CARD_BOOT_TIMEOUT      10
+#define DSP_BOOT_DELAY_IN_MS   200
+
+#define kNumBuffers		8
+#define k1212MaxCards		4
+#define k1212NumWaveDevices	6
+#define k16BitChannels		10
+#define k32BitChannels		2
+#define kAudioChannels		(k16BitChannels + k32BitChannels)
+#define kPlayBufferFrames	1024
+
+#define K1212_ANALOG_CHANNELS	2
+#define K1212_SPDIF_CHANNELS	2
+#define K1212_ADAT_CHANNELS	8
+#define K1212_CHANNELS		(K1212_ADAT_CHANNELS + K1212_ANALOG_CHANNELS)
+#define K1212_MIN_CHANNELS	1
+#define K1212_MAX_CHANNELS	K1212_CHANNELS
+#define K1212_FRAME_SIZE        (sizeof(struct KorgAudioFrame))
+#define K1212_MAX_SAMPLES	(kPlayBufferFrames*kNumBuffers)
+#define K1212_PERIODS		(kNumBuffers)
+#define K1212_PERIOD_BYTES	(K1212_FRAME_SIZE*kPlayBufferFrames)
+#define K1212_BUF_SIZE          (K1212_PERIOD_BYTES*kNumBuffers)
+#define K1212_ANALOG_BUF_SIZE	(K1212_ANALOG_CHANNELS * 2 * kPlayBufferFrames * kNumBuffers)
+#define K1212_SPDIF_BUF_SIZE	(K1212_SPDIF_CHANNELS * 3 * kPlayBufferFrames * kNumBuffers)
+#define K1212_ADAT_BUF_SIZE	(K1212_ADAT_CHANNELS * 2 * kPlayBufferFrames * kNumBuffers)
+#define K1212_MAX_BUF_SIZE	(K1212_ANALOG_BUF_SIZE + K1212_ADAT_BUF_SIZE)
+
+#define k1212MinADCSens     0x7f
+#define k1212MaxADCSens     0x00
+#define k1212MaxVolume      0x7fff
+#define k1212MaxWaveVolume  0xffff
+#define k1212MinVolume      0x0000
+#define k1212MaxVolInverted 0x8000
+
+// -----------------------------------------------------------------
+// the following bits are used for controlling interrupts in the
+// interrupt control/status reg
+// -----------------------------------------------------------------
+#define  PCI_INT_ENABLE_BIT               0x00000100
+#define  PCI_DOORBELL_INT_ENABLE_BIT      0x00000200
+#define  LOCAL_INT_ENABLE_BIT             0x00010000
+#define  LOCAL_DOORBELL_INT_ENABLE_BIT    0x00020000
+#define  LOCAL_DMA1_INT_ENABLE_BIT        0x00080000
+
+// -----------------------------------------------------------------
+// the following bits are defined for the PCI command register
+// -----------------------------------------------------------------
+#define  PCI_CMD_MEM_SPACE_ENABLE_BIT     0x0002
+#define  PCI_CMD_IO_SPACE_ENABLE_BIT      0x0001
+#define  PCI_CMD_BUS_MASTER_ENABLE_BIT    0x0004
+
+// -----------------------------------------------------------------
+// the following bits are defined for the PCI status register
+// -----------------------------------------------------------------
+#define  PCI_STAT_PARITY_ERROR_BIT        0x8000
+#define  PCI_STAT_SYSTEM_ERROR_BIT        0x4000
+#define  PCI_STAT_MASTER_ABORT_RCVD_BIT   0x2000
+#define  PCI_STAT_TARGET_ABORT_RCVD_BIT   0x1000
+#define  PCI_STAT_TARGET_ABORT_SENT_BIT   0x0800
+
+// ------------------------------------------------------------------------
+// the following constants are used in setting the 1212 I/O card's input
+// sensitivity.
+// ------------------------------------------------------------------------
+#define  SET_SENS_LOCALINIT_BITPOS        15
+#define  SET_SENS_DATA_BITPOS             10
+#define  SET_SENS_CLOCK_BITPOS            8
+#define  SET_SENS_LOADSHIFT_BITPOS        0
+
+#define  SET_SENS_LEFTCHANID              0x00
+#define  SET_SENS_RIGHTCHANID             0x01
+
+#define  K1212SENSUPDATE_DELAY_IN_MS      50
+
+// --------------------------------------------------------------------------
+// WaitRTCTicks
+//
+//    This function waits the specified number of real time clock ticks.
+//    According to the DDK, each tick is ~0.8 microseconds.
+//    The defines following the function declaration can be used for the
+//    numTicksToWait parameter.
+// --------------------------------------------------------------------------
+#define ONE_RTC_TICK         1
+#define SENSCLKPULSE_WIDTH   4
+#define LOADSHIFT_DELAY      4
+#define INTERCOMMAND_DELAY  40
+#define STOPCARD_DELAY      300        // max # RTC ticks for the card to stop once we write
+                                       //    the command register.  (could be up to 180 us)
+#define COMMAND_ACK_DELAY   13         // number of RTC ticks to wait for an acknowledgement
+                                       //    from the card after sending a command.
+
+enum ClockSourceIndex {
+   K1212_CLKIDX_AdatAt44_1K = 0,    // selects source as ADAT at 44.1 kHz
+   K1212_CLKIDX_AdatAt48K,          // selects source as ADAT at 48 kHz
+   K1212_CLKIDX_WordAt44_1K,        // selects source as S/PDIF at 44.1 kHz
+   K1212_CLKIDX_WordAt48K,          // selects source as S/PDIF at 48 kHz
+   K1212_CLKIDX_LocalAt44_1K,       // selects source as local clock at 44.1 kHz
+   K1212_CLKIDX_LocalAt48K,         // selects source as local clock at 48 kHz
+   K1212_CLKIDX_Invalid             // used to check validity of the index
+};
+
+enum ClockSourceType {
+   K1212_CLKIDX_Adat = 0,    // selects source as ADAT
+   K1212_CLKIDX_Word,        // selects source as S/PDIF
+   K1212_CLKIDX_Local        // selects source as local clock
+};
+
+struct KorgAudioFrame {
+	u16 frameData16[k16BitChannels]; /* channels 0-9 use 16 bit samples */
+	u32 frameData32[k32BitChannels]; /* channels 10-11 use 32 bits - only 20 are sent across S/PDIF */
+	u32 timeCodeVal; /* holds the ADAT timecode value */
+};
+
+struct KorgAudioBuffer {
+	struct KorgAudioFrame  bufferData[kPlayBufferFrames];     /* buffer definition */
+};
+
+struct KorgSharedBuffer {
+#ifdef K1212_LARGEALLOC
+   struct KorgAudioBuffer   playDataBufs[kNumBuffers];
+   struct KorgAudioBuffer   recordDataBufs[kNumBuffers];
+#endif
+   short             volumeData[kAudioChannels];
+   u32               cardCommand;
+   u16               routeData [kAudioChannels];
+   u32               AdatTimeCode;                 // ADAT timecode value
+};
+
+struct SensBits {
+   union {
+      struct {
+         unsigned int leftChanVal:8;
+         unsigned int leftChanId:8;
+      } v;
+      u16  leftSensBits;
+   } l;
+   union {
+      struct {
+         unsigned int rightChanVal:8;
+         unsigned int rightChanId:8;
+      } v;
+      u16  rightSensBits;
+   } r;
+};
+
+struct snd_korg1212 {
+        struct snd_card *card;
+        struct pci_dev *pci;
+        struct snd_pcm *pcm;
+        int irq;
+
+        spinlock_t    lock;
+	struct mutex open_mutex;
+
+	struct timer_list timer;	/* timer callback for checking ack of stop request */
+	int stop_pending_cnt;		/* counter for stop pending check */
+
+        wait_queue_head_t wait;
+
+        unsigned long iomem;
+        unsigned long ioport;
+	unsigned long iomem2;
+        unsigned long irqcount;
+        unsigned long inIRQ;
+        void __iomem *iobase;
+
+	struct snd_dma_buffer dma_dsp;
+        struct snd_dma_buffer dma_play;
+        struct snd_dma_buffer dma_rec;
+	struct snd_dma_buffer dma_shared;
+
+	u32 DataBufsSize;
+
+        struct KorgAudioBuffer  * playDataBufsPtr;
+        struct KorgAudioBuffer  * recordDataBufsPtr;
+
+	struct KorgSharedBuffer * sharedBufferPtr;
+
+	u32 RecDataPhy;
+	u32 PlayDataPhy;
+	unsigned long sharedBufferPhy;
+	u32 VolumeTablePhy;
+	u32 RoutingTablePhy;
+	u32 AdatTimeCodePhy;
+
+        u32 __iomem * statusRegPtr;	     // address of the interrupt status/control register
+        u32 __iomem * outDoorbellPtr;	     // address of the host->card doorbell register
+        u32 __iomem * inDoorbellPtr;	     // address of the card->host doorbell register
+        u32 __iomem * mailbox0Ptr;	     // address of mailbox 0 on the card
+        u32 __iomem * mailbox1Ptr;	     // address of mailbox 1 on the card
+        u32 __iomem * mailbox2Ptr;	     // address of mailbox 2 on the card
+        u32 __iomem * mailbox3Ptr;	     // address of mailbox 3 on the card
+        u32 __iomem * controlRegPtr;	     // address of the EEPROM, PCI, I/O, Init ctrl reg
+        u16 __iomem * sensRegPtr;	     // address of the sensitivity setting register
+        u32 __iomem * idRegPtr;		     // address of the device and vendor ID registers
+
+        size_t periodsize;
+	int channels;
+        int currentBuffer;
+
+        struct snd_pcm_substream *playback_substream;
+        struct snd_pcm_substream *capture_substream;
+
+	pid_t capture_pid;
+	pid_t playback_pid;
+
+ 	enum CardState cardState;
+        int running;
+        int idleMonitorOn;           // indicates whether the card is in idle monitor mode.
+        u32 cmdRetryCount;           // tracks how many times we have retried sending to the card.
+
+        enum ClockSourceIndex clkSrcRate; // sample rate and clock source
+
+        enum ClockSourceType clkSource;   // clock source
+        int clkRate;                 // clock rate
+
+        int volumePhase[kAudioChannels];
+
+        u16 leftADCInSens;           // ADC left channel input sensitivity
+        u16 rightADCInSens;          // ADC right channel input sensitivity
+
+	int opencnt;		     // Open/Close count
+	int setcnt;		     // SetupForPlay count
+	int playcnt;		     // TriggerPlay count
+	int errorcnt;		     // Error Count
+	unsigned long totalerrorcnt; // Total Error Count
+
+	int dsp_is_loaded;
+	int dsp_stop_is_processed;
+
+};
+
+MODULE_DESCRIPTION("korg1212");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{KORG,korg1212}}");
+MODULE_FIRMWARE("korg/k1212.dsp");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;     /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	   /* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Korg 1212 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Korg 1212 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Korg 1212 soundcard.");
+MODULE_AUTHOR("Haroldo Gamal <gamal@alternex.com.br>");
+
+static DEFINE_PCI_DEVICE_TABLE(snd_korg1212_ids) = {
+	{
+		.vendor	   = 0x10b5,
+		.device	   = 0x906d,
+		.subvendor = PCI_ANY_ID,
+		.subdevice = PCI_ANY_ID,
+	},
+	{ 0, },
+};
+
+MODULE_DEVICE_TABLE(pci, snd_korg1212_ids);
+
+static char *stateName[] = {
+	"Non-existent",
+	"Uninitialized",
+	"DSP download in process",
+	"DSP download complete",
+	"Ready",
+	"Open",
+	"Setup for play",
+	"Playing",
+	"Monitor mode on",
+	"Calibrating",
+	"Invalid"
+};
+
+static char *clockSourceTypeName[] = { "ADAT", "S/PDIF", "local" };
+
+static char *clockSourceName[] = {
+	"ADAT at 44.1 kHz",
+	"ADAT at 48 kHz",
+	"S/PDIF at 44.1 kHz",
+	"S/PDIF at 48 kHz",
+	"local clock at 44.1 kHz",
+	"local clock at 48 kHz"
+};
+
+static char *channelName[] = {
+	"ADAT-1",
+	"ADAT-2",
+	"ADAT-3",
+	"ADAT-4",
+	"ADAT-5",
+	"ADAT-6",
+	"ADAT-7",
+	"ADAT-8",
+	"Analog-L",
+	"Analog-R",
+	"SPDIF-L",
+	"SPDIF-R",
+};
+
+static u16 ClockSourceSelector[] = {
+	0x8000,   // selects source as ADAT at 44.1 kHz
+	0x0000,   // selects source as ADAT at 48 kHz
+	0x8001,   // selects source as S/PDIF at 44.1 kHz
+	0x0001,   // selects source as S/PDIF at 48 kHz
+	0x8002,   // selects source as local clock at 44.1 kHz
+	0x0002    // selects source as local clock at 48 kHz
+};
+
+union swap_u32 { unsigned char c[4]; u32 i; };
+
+#ifdef SNDRV_BIG_ENDIAN
+static u32 LowerWordSwap(u32 swappee)
+#else
+static u32 UpperWordSwap(u32 swappee)
+#endif
+{
+   union swap_u32 retVal, swapper;
+
+   swapper.i = swappee;
+   retVal.c[2] = swapper.c[3];
+   retVal.c[3] = swapper.c[2];
+   retVal.c[1] = swapper.c[1];
+   retVal.c[0] = swapper.c[0];
+
+   return retVal.i;
+}
+
+#ifdef SNDRV_BIG_ENDIAN
+static u32 UpperWordSwap(u32 swappee)
+#else
+static u32 LowerWordSwap(u32 swappee)
+#endif
+{
+   union swap_u32 retVal, swapper;
+
+   swapper.i = swappee;
+   retVal.c[2] = swapper.c[2];
+   retVal.c[3] = swapper.c[3];
+   retVal.c[1] = swapper.c[0];
+   retVal.c[0] = swapper.c[1];
+
+   return retVal.i;
+}
+
+#define SetBitInWord(theWord,bitPosition)       (*theWord) |= (0x0001 << bitPosition)
+#define SetBitInDWord(theWord,bitPosition)      (*theWord) |= (0x00000001 << bitPosition)
+#define ClearBitInWord(theWord,bitPosition)     (*theWord) &= ~(0x0001 << bitPosition)
+#define ClearBitInDWord(theWord,bitPosition)    (*theWord) &= ~(0x00000001 << bitPosition)
+
+static int snd_korg1212_Send1212Command(struct snd_korg1212 *korg1212,
+					enum korg1212_dbcnst doorbellVal,
+					u32 mailBox0Val, u32 mailBox1Val,
+					u32 mailBox2Val, u32 mailBox3Val)
+{
+        u32 retryCount;
+        u16 mailBox3Lo;
+	int rc = K1212_CMDRET_Success;
+
+        if (!korg1212->outDoorbellPtr) {
+		K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: CardUninitialized\n");
+                return K1212_CMDRET_CardUninitialized;
+	}
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: Card <- 0x%08x 0x%08x [%s]\n",
+			   doorbellVal, mailBox0Val, stateName[korg1212->cardState]);
+        for (retryCount = 0; retryCount < MAX_COMMAND_RETRIES; retryCount++) {
+		writel(mailBox3Val, korg1212->mailbox3Ptr);
+                writel(mailBox2Val, korg1212->mailbox2Ptr);
+                writel(mailBox1Val, korg1212->mailbox1Ptr);
+                writel(mailBox0Val, korg1212->mailbox0Ptr);
+                writel(doorbellVal, korg1212->outDoorbellPtr);  // interrupt the card
+
+                // --------------------------------------------------------------
+                // the reboot command will not give an acknowledgement.
+                // --------------------------------------------------------------
+                if ( doorbellVal == K1212_DB_RebootCard ||
+                	doorbellVal == K1212_DB_BootFromDSPPage4 ||
+                        doorbellVal == K1212_DB_StartDSPDownload ) {
+                        rc = K1212_CMDRET_Success;
+                        break;
+                }
+
+                // --------------------------------------------------------------
+                // See if the card acknowledged the command.  Wait a bit, then
+                // read in the low word of mailbox3.  If the MSB is set and the
+                // low byte is equal to the doorbell value, then it ack'd.
+                // --------------------------------------------------------------
+                udelay(COMMAND_ACK_DELAY);
+                mailBox3Lo = readl(korg1212->mailbox3Ptr);
+                if (mailBox3Lo & COMMAND_ACK_MASK) {
+                	if ((mailBox3Lo & DOORBELL_VAL_MASK) == (doorbellVal & DOORBELL_VAL_MASK)) {
+				K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: Card <- Success\n");
+                                rc = K1212_CMDRET_Success;
+				break;
+                        }
+                }
+	}
+        korg1212->cmdRetryCount += retryCount;
+
+	if (retryCount >= MAX_COMMAND_RETRIES) {
+		K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: Card <- NoAckFromCard\n");
+        	rc = K1212_CMDRET_NoAckFromCard;
+	}
+
+	return rc;
+}
+
+/* spinlock already held */
+static void snd_korg1212_SendStop(struct snd_korg1212 *korg1212)
+{
+	if (! korg1212->stop_pending_cnt) {
+		korg1212->sharedBufferPtr->cardCommand = 0xffffffff;
+		/* program the timer */
+		korg1212->stop_pending_cnt = HZ;
+		korg1212->timer.expires = jiffies + 1;
+		add_timer(&korg1212->timer);
+	}
+}
+
+static void snd_korg1212_SendStopAndWait(struct snd_korg1212 *korg1212)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&korg1212->lock, flags);
+	korg1212->dsp_stop_is_processed = 0;
+	snd_korg1212_SendStop(korg1212);
+	spin_unlock_irqrestore(&korg1212->lock, flags);
+	wait_event_timeout(korg1212->wait, korg1212->dsp_stop_is_processed, (HZ * 3) / 2);
+}
+
+/* timer callback for checking the ack of stop request */
+static void snd_korg1212_timer_func(unsigned long data)
+{
+        struct snd_korg1212 *korg1212 = (struct snd_korg1212 *) data;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&korg1212->lock, flags);
+	if (korg1212->sharedBufferPtr->cardCommand == 0) {
+		/* ack'ed */
+		korg1212->stop_pending_cnt = 0;
+		korg1212->dsp_stop_is_processed = 1;
+		wake_up(&korg1212->wait);
+		K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: Stop ack'ed [%s]\n",
+					   stateName[korg1212->cardState]);
+	} else {
+		if (--korg1212->stop_pending_cnt > 0) {
+			/* reprogram timer */
+			korg1212->timer.expires = jiffies + 1;
+			add_timer(&korg1212->timer);
+		} else {
+			snd_printd("korg1212_timer_func timeout\n");
+			korg1212->sharedBufferPtr->cardCommand = 0;
+			korg1212->dsp_stop_is_processed = 1;
+			wake_up(&korg1212->wait);
+			K1212_DEBUG_PRINTK("K1212_DEBUG: Stop timeout [%s]\n",
+					   stateName[korg1212->cardState]);
+		}
+	}
+	spin_unlock_irqrestore(&korg1212->lock, flags);
+}
+
+static int snd_korg1212_TurnOnIdleMonitor(struct snd_korg1212 *korg1212)
+{
+	unsigned long flags;
+	int rc;
+
+        udelay(INTERCOMMAND_DELAY);
+	spin_lock_irqsave(&korg1212->lock, flags);
+        korg1212->idleMonitorOn = 1;
+        rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SelectPlayMode,
+					  K1212_MODE_MonitorOn, 0, 0, 0);
+        spin_unlock_irqrestore(&korg1212->lock, flags);
+	return rc;
+}
+
+static void snd_korg1212_TurnOffIdleMonitor(struct snd_korg1212 *korg1212)
+{
+        if (korg1212->idleMonitorOn) {
+		snd_korg1212_SendStopAndWait(korg1212);
+                korg1212->idleMonitorOn = 0;
+        }
+}
+
+static inline void snd_korg1212_setCardState(struct snd_korg1212 * korg1212, enum CardState csState)
+{
+        korg1212->cardState = csState;
+}
+
+static int snd_korg1212_OpenCard(struct snd_korg1212 * korg1212)
+{
+	K1212_DEBUG_PRINTK("K1212_DEBUG: OpenCard [%s] %d\n",
+			   stateName[korg1212->cardState], korg1212->opencnt);
+	mutex_lock(&korg1212->open_mutex);
+        if (korg1212->opencnt++ == 0) {
+		snd_korg1212_TurnOffIdleMonitor(korg1212);
+		snd_korg1212_setCardState(korg1212, K1212_STATE_OPEN);
+	}
+
+	mutex_unlock(&korg1212->open_mutex);
+        return 1;
+}
+
+static int snd_korg1212_CloseCard(struct snd_korg1212 * korg1212)
+{
+	K1212_DEBUG_PRINTK("K1212_DEBUG: CloseCard [%s] %d\n",
+			   stateName[korg1212->cardState], korg1212->opencnt);
+
+	mutex_lock(&korg1212->open_mutex);
+	if (--(korg1212->opencnt)) {
+		mutex_unlock(&korg1212->open_mutex);
+		return 0;
+	}
+
+        if (korg1212->cardState == K1212_STATE_SETUP) {
+                int rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SelectPlayMode,
+                                K1212_MODE_StopPlay, 0, 0, 0);
+		if (rc)
+			K1212_DEBUG_PRINTK("K1212_DEBUG: CloseCard - RC = %d [%s]\n",
+					   rc, stateName[korg1212->cardState]);
+		if (rc != K1212_CMDRET_Success) {
+			mutex_unlock(&korg1212->open_mutex);
+                        return 0;
+		}
+        } else if (korg1212->cardState > K1212_STATE_SETUP) {
+		snd_korg1212_SendStopAndWait(korg1212);
+        }
+
+        if (korg1212->cardState > K1212_STATE_READY) {
+		snd_korg1212_TurnOnIdleMonitor(korg1212);
+                snd_korg1212_setCardState(korg1212, K1212_STATE_READY);
+	}
+
+	mutex_unlock(&korg1212->open_mutex);
+        return 0;
+}
+
+/* spinlock already held */
+static int snd_korg1212_SetupForPlay(struct snd_korg1212 * korg1212)
+{
+	int rc;
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: SetupForPlay [%s] %d\n",
+			   stateName[korg1212->cardState], korg1212->setcnt);
+
+        if (korg1212->setcnt++)
+		return 0;
+
+        snd_korg1212_setCardState(korg1212, K1212_STATE_SETUP);
+        rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SelectPlayMode,
+                                        K1212_MODE_SetupPlay, 0, 0, 0);
+	if (rc)
+		K1212_DEBUG_PRINTK("K1212_DEBUG: SetupForPlay - RC = %d [%s]\n",
+				   rc, stateName[korg1212->cardState]);
+        if (rc != K1212_CMDRET_Success) {
+                return 1;
+        }
+        return 0;
+}
+
+/* spinlock already held */
+static int snd_korg1212_TriggerPlay(struct snd_korg1212 * korg1212)
+{
+	int rc;
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: TriggerPlay [%s] %d\n",
+			   stateName[korg1212->cardState], korg1212->playcnt);
+
+        if (korg1212->playcnt++)
+		return 0;
+
+        snd_korg1212_setCardState(korg1212, K1212_STATE_PLAYING);
+        rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_TriggerPlay, 0, 0, 0, 0);
+	if (rc)
+		K1212_DEBUG_PRINTK("K1212_DEBUG: TriggerPlay - RC = %d [%s]\n",
+				   rc, stateName[korg1212->cardState]);
+        if (rc != K1212_CMDRET_Success) {
+                return 1;
+        }
+        return 0;
+}
+
+/* spinlock already held */
+static int snd_korg1212_StopPlay(struct snd_korg1212 * korg1212)
+{
+	K1212_DEBUG_PRINTK("K1212_DEBUG: StopPlay [%s] %d\n",
+			   stateName[korg1212->cardState], korg1212->playcnt);
+
+        if (--(korg1212->playcnt)) 
+		return 0;
+
+	korg1212->setcnt = 0;
+
+        if (korg1212->cardState != K1212_STATE_ERRORSTOP)
+		snd_korg1212_SendStop(korg1212);
+
+	snd_korg1212_setCardState(korg1212, K1212_STATE_OPEN);
+        return 0;
+}
+
+static void snd_korg1212_EnableCardInterrupts(struct snd_korg1212 * korg1212)
+{
+	writel(PCI_INT_ENABLE_BIT            |
+	       PCI_DOORBELL_INT_ENABLE_BIT   |
+	       LOCAL_INT_ENABLE_BIT          |
+	       LOCAL_DOORBELL_INT_ENABLE_BIT |
+	       LOCAL_DMA1_INT_ENABLE_BIT,
+	       korg1212->statusRegPtr);
+}
+
+#if 0 /* not used */
+
+static int snd_korg1212_SetMonitorMode(struct snd_korg1212 *korg1212,
+				       enum MonitorModeSelector mode)
+{
+	K1212_DEBUG_PRINTK("K1212_DEBUG: SetMonitorMode [%s]\n",
+			   stateName[korg1212->cardState]);
+
+        switch (mode) {
+	case K1212_MONMODE_Off:
+		if (korg1212->cardState != K1212_STATE_MONITOR)
+			return 0;
+		else {
+			snd_korg1212_SendStopAndWait(korg1212);
+			snd_korg1212_setCardState(korg1212, K1212_STATE_OPEN);
+		}
+		break;
+
+	case K1212_MONMODE_On:
+		if (korg1212->cardState != K1212_STATE_OPEN)
+			return 0;
+		else {
+			int rc;
+			snd_korg1212_setCardState(korg1212, K1212_STATE_MONITOR);
+			rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SelectPlayMode,
+							  K1212_MODE_MonitorOn, 0, 0, 0);
+			if (rc != K1212_CMDRET_Success)
+				return 0;
+		}
+		break;
+
+	default:
+		return 0;
+        }
+
+        return 1;
+}
+
+#endif /* not used */
+
+static inline int snd_korg1212_use_is_exclusive(struct snd_korg1212 *korg1212)
+{
+	if (korg1212->playback_pid != korg1212->capture_pid &&
+	    korg1212->playback_pid >= 0 && korg1212->capture_pid >= 0)
+		return 0;
+
+	return 1;
+}
+
+static int snd_korg1212_SetRate(struct snd_korg1212 *korg1212, int rate)
+{
+        static enum ClockSourceIndex s44[] = {
+		K1212_CLKIDX_AdatAt44_1K,
+		K1212_CLKIDX_WordAt44_1K,
+		K1212_CLKIDX_LocalAt44_1K
+	};
+        static enum ClockSourceIndex s48[] = {
+		K1212_CLKIDX_AdatAt48K,
+		K1212_CLKIDX_WordAt48K,
+		K1212_CLKIDX_LocalAt48K
+	};
+        int parm, rc;
+
+	if (!snd_korg1212_use_is_exclusive (korg1212))
+		return -EBUSY;
+
+	switch (rate) {
+	case 44100:
+		parm = s44[korg1212->clkSource];
+		break;
+
+	case 48000:
+		parm = s48[korg1212->clkSource];
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+        korg1212->clkSrcRate = parm;
+        korg1212->clkRate = rate;
+
+	udelay(INTERCOMMAND_DELAY);
+	rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SetClockSourceRate,
+					  ClockSourceSelector[korg1212->clkSrcRate],
+					  0, 0, 0);
+	if (rc)
+		K1212_DEBUG_PRINTK("K1212_DEBUG: Set Clock Source Selector - RC = %d [%s]\n",
+				   rc, stateName[korg1212->cardState]);
+
+        return 0;
+}
+
+static int snd_korg1212_SetClockSource(struct snd_korg1212 *korg1212, int source)
+{
+
+	if (source < 0 || source > 2)
+		return -EINVAL;
+
+        korg1212->clkSource = source;
+
+        snd_korg1212_SetRate(korg1212, korg1212->clkRate);
+
+        return 0;
+}
+
+static void snd_korg1212_DisableCardInterrupts(struct snd_korg1212 *korg1212)
+{
+	writel(0, korg1212->statusRegPtr);
+}
+
+static int snd_korg1212_WriteADCSensitivity(struct snd_korg1212 *korg1212)
+{
+        struct SensBits  sensVals;
+        int       bitPosition;
+        int       channel;
+        int       clkIs48K;
+        int       monModeSet;
+        u16       controlValue;    // this keeps the current value to be written to
+                                   //  the card's eeprom control register.
+        u16       count;
+	unsigned long flags;
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: WriteADCSensivity [%s]\n",
+			   stateName[korg1212->cardState]);
+
+        // ----------------------------------------------------------------------------
+        // initialize things.  The local init bit is always set when writing to the
+        // card's control register.
+        // ----------------------------------------------------------------------------
+        controlValue = 0;
+        SetBitInWord(&controlValue, SET_SENS_LOCALINIT_BITPOS);    // init the control value
+
+        // ----------------------------------------------------------------------------
+        // make sure the card is not in monitor mode when we do this update.
+        // ----------------------------------------------------------------------------
+        if (korg1212->cardState == K1212_STATE_MONITOR || korg1212->idleMonitorOn) {
+                monModeSet = 1;
+		snd_korg1212_SendStopAndWait(korg1212);
+        } else
+                monModeSet = 0;
+
+	spin_lock_irqsave(&korg1212->lock, flags);
+
+        // ----------------------------------------------------------------------------
+        // we are about to send new values to the card, so clear the new values queued
+        // flag.  Also, clear out mailbox 3, so we don't lockup.
+        // ----------------------------------------------------------------------------
+        writel(0, korg1212->mailbox3Ptr);
+        udelay(LOADSHIFT_DELAY);
+
+        // ----------------------------------------------------------------------------
+        // determine whether we are running a 48K or 44.1K clock.  This info is used
+        // later when setting the SPDIF FF after the volume has been shifted in.
+        // ----------------------------------------------------------------------------
+        switch (korg1212->clkSrcRate) {
+                case K1212_CLKIDX_AdatAt44_1K:
+                case K1212_CLKIDX_WordAt44_1K:
+                case K1212_CLKIDX_LocalAt44_1K:
+                        clkIs48K = 0;
+                        break;
+
+                case K1212_CLKIDX_WordAt48K:
+                case K1212_CLKIDX_AdatAt48K:
+                case K1212_CLKIDX_LocalAt48K:
+                default:
+                        clkIs48K = 1;
+                        break;
+        }
+
+        // ----------------------------------------------------------------------------
+        // start the update.  Setup the bit structure and then shift the bits.
+        // ----------------------------------------------------------------------------
+        sensVals.l.v.leftChanId   = SET_SENS_LEFTCHANID;
+        sensVals.r.v.rightChanId  = SET_SENS_RIGHTCHANID;
+        sensVals.l.v.leftChanVal  = korg1212->leftADCInSens;
+        sensVals.r.v.rightChanVal = korg1212->rightADCInSens;
+
+        // ----------------------------------------------------------------------------
+        // now start shifting the bits in.  Start with the left channel then the right.
+        // ----------------------------------------------------------------------------
+        for (channel = 0; channel < 2; channel++) {
+
+                // ----------------------------------------------------------------------------
+                // Bring the load/shift line low, then wait - the spec says >150ns from load/
+                // shift low to the first rising edge of the clock.
+                // ----------------------------------------------------------------------------
+                ClearBitInWord(&controlValue, SET_SENS_LOADSHIFT_BITPOS);
+                ClearBitInWord(&controlValue, SET_SENS_DATA_BITPOS);
+                writew(controlValue, korg1212->sensRegPtr);                          // load/shift goes low
+                udelay(LOADSHIFT_DELAY);
+
+                for (bitPosition = 15; bitPosition >= 0; bitPosition--) {       // for all the bits
+			if (channel == 0) {
+				if (sensVals.l.leftSensBits & (0x0001 << bitPosition))
+                                        SetBitInWord(&controlValue, SET_SENS_DATA_BITPOS);     // data bit set high
+				else
+					ClearBitInWord(&controlValue, SET_SENS_DATA_BITPOS);   // data bit set low
+			} else {
+                                if (sensVals.r.rightSensBits & (0x0001 << bitPosition))
+					SetBitInWord(&controlValue, SET_SENS_DATA_BITPOS);     // data bit set high
+				else
+					ClearBitInWord(&controlValue, SET_SENS_DATA_BITPOS);   // data bit set low
+			}
+
+                        ClearBitInWord(&controlValue, SET_SENS_CLOCK_BITPOS);
+                        writew(controlValue, korg1212->sensRegPtr);                       // clock goes low
+                        udelay(SENSCLKPULSE_WIDTH);
+                        SetBitInWord(&controlValue, SET_SENS_CLOCK_BITPOS);
+                        writew(controlValue, korg1212->sensRegPtr);                       // clock goes high
+                        udelay(SENSCLKPULSE_WIDTH);
+                }
+
+                // ----------------------------------------------------------------------------
+                // finish up SPDIF for left.  Bring the load/shift line high, then write a one
+                // bit if the clock rate is 48K otherwise write 0.
+                // ----------------------------------------------------------------------------
+                ClearBitInWord(&controlValue, SET_SENS_DATA_BITPOS);
+                ClearBitInWord(&controlValue, SET_SENS_CLOCK_BITPOS);
+                SetBitInWord(&controlValue, SET_SENS_LOADSHIFT_BITPOS);
+                writew(controlValue, korg1212->sensRegPtr);                   // load shift goes high - clk low
+                udelay(SENSCLKPULSE_WIDTH);
+
+                if (clkIs48K)
+                        SetBitInWord(&controlValue, SET_SENS_DATA_BITPOS);
+
+                writew(controlValue, korg1212->sensRegPtr);                   // set/clear data bit
+                udelay(ONE_RTC_TICK);
+                SetBitInWord(&controlValue, SET_SENS_CLOCK_BITPOS);
+                writew(controlValue, korg1212->sensRegPtr);                   // clock goes high
+                udelay(SENSCLKPULSE_WIDTH);
+                ClearBitInWord(&controlValue, SET_SENS_CLOCK_BITPOS);
+                writew(controlValue, korg1212->sensRegPtr);                   // clock goes low
+                udelay(SENSCLKPULSE_WIDTH);
+        }
+
+        // ----------------------------------------------------------------------------
+        // The update is complete.  Set a timeout.  This is the inter-update delay.
+        // Also, if the card was in monitor mode, restore it.
+        // ----------------------------------------------------------------------------
+        for (count = 0; count < 10; count++)
+                udelay(SENSCLKPULSE_WIDTH);
+
+        if (monModeSet) {
+                int rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SelectPlayMode,
+                                K1212_MODE_MonitorOn, 0, 0, 0);
+	        if (rc)
+			K1212_DEBUG_PRINTK("K1212_DEBUG: WriteADCSensivity - RC = %d [%s]\n",
+					   rc, stateName[korg1212->cardState]);
+        }
+
+	spin_unlock_irqrestore(&korg1212->lock, flags);
+
+        return 1;
+}
+
+static void snd_korg1212_OnDSPDownloadComplete(struct snd_korg1212 *korg1212)
+{
+        int channel, rc;
+
+        K1212_DEBUG_PRINTK("K1212_DEBUG: DSP download is complete. [%s]\n",
+			   stateName[korg1212->cardState]);
+
+        // ----------------------------------------------------
+        // tell the card to boot
+        // ----------------------------------------------------
+        rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_BootFromDSPPage4, 0, 0, 0, 0);
+
+	if (rc)
+		K1212_DEBUG_PRINTK("K1212_DEBUG: Boot from Page 4 - RC = %d [%s]\n",
+				   rc, stateName[korg1212->cardState]);
+	msleep(DSP_BOOT_DELAY_IN_MS);
+
+        // --------------------------------------------------------------------------------
+        // Let the card know where all the buffers are.
+        // --------------------------------------------------------------------------------
+        rc = snd_korg1212_Send1212Command(korg1212,
+                        K1212_DB_ConfigureBufferMemory,
+                        LowerWordSwap(korg1212->PlayDataPhy),
+                        LowerWordSwap(korg1212->RecDataPhy),
+                        ((kNumBuffers * kPlayBufferFrames) / 2),   // size given to the card
+                                                                   // is based on 2 buffers
+                        0
+        );
+
+	if (rc)
+		K1212_DEBUG_PRINTK("K1212_DEBUG: Configure Buffer Memory - RC = %d [%s]\n",
+				   rc, stateName[korg1212->cardState]);
+
+        udelay(INTERCOMMAND_DELAY);
+
+        rc = snd_korg1212_Send1212Command(korg1212,
+                        K1212_DB_ConfigureMiscMemory,
+                        LowerWordSwap(korg1212->VolumeTablePhy),
+                        LowerWordSwap(korg1212->RoutingTablePhy),
+                        LowerWordSwap(korg1212->AdatTimeCodePhy),
+                        0
+        );
+
+	if (rc)
+		K1212_DEBUG_PRINTK("K1212_DEBUG: Configure Misc Memory - RC = %d [%s]\n",
+				   rc, stateName[korg1212->cardState]);
+
+        // --------------------------------------------------------------------------------
+        // Initialize the routing and volume tables, then update the card's state.
+        // --------------------------------------------------------------------------------
+        udelay(INTERCOMMAND_DELAY);
+
+        for (channel = 0; channel < kAudioChannels; channel++) {
+                korg1212->sharedBufferPtr->volumeData[channel] = k1212MaxVolume;
+                //korg1212->sharedBufferPtr->routeData[channel] = channel;
+                korg1212->sharedBufferPtr->routeData[channel] = 8 + (channel & 1);
+        }
+
+        snd_korg1212_WriteADCSensitivity(korg1212);
+
+	udelay(INTERCOMMAND_DELAY);
+	rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_SetClockSourceRate,
+					  ClockSourceSelector[korg1212->clkSrcRate],
+					  0, 0, 0);
+	if (rc)
+		K1212_DEBUG_PRINTK("K1212_DEBUG: Set Clock Source Selector - RC = %d [%s]\n",
+				   rc, stateName[korg1212->cardState]);
+
+	rc = snd_korg1212_TurnOnIdleMonitor(korg1212);
+	snd_korg1212_setCardState(korg1212, K1212_STATE_READY);
+
+	if (rc)
+		K1212_DEBUG_PRINTK("K1212_DEBUG: Set Monitor On - RC = %d [%s]\n",
+				   rc, stateName[korg1212->cardState]);
+
+	snd_korg1212_setCardState(korg1212, K1212_STATE_DSP_COMPLETE);
+}
+
+static irqreturn_t snd_korg1212_interrupt(int irq, void *dev_id)
+{
+        u32 doorbellValue;
+        struct snd_korg1212 *korg1212 = dev_id;
+
+        doorbellValue = readl(korg1212->inDoorbellPtr);
+
+        if (!doorbellValue)
+		return IRQ_NONE;
+
+	spin_lock(&korg1212->lock);
+
+	writel(doorbellValue, korg1212->inDoorbellPtr);
+
+        korg1212->irqcount++;
+
+	korg1212->inIRQ++;
+
+        switch (doorbellValue) {
+                case K1212_DB_DSPDownloadDone:
+                        K1212_DEBUG_PRINTK("K1212_DEBUG: IRQ DNLD count - %ld, %x, [%s].\n",
+					   korg1212->irqcount, doorbellValue,
+					   stateName[korg1212->cardState]);
+                        if (korg1212->cardState == K1212_STATE_DSP_IN_PROCESS) {
+				korg1212->dsp_is_loaded = 1;
+				wake_up(&korg1212->wait);
+			}
+                        break;
+
+                // ------------------------------------------------------------------------
+                // an error occurred - stop the card
+                // ------------------------------------------------------------------------
+                case K1212_DB_DMAERROR:
+			K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: IRQ DMAE count - %ld, %x, [%s].\n",
+						   korg1212->irqcount, doorbellValue,
+						   stateName[korg1212->cardState]);
+			snd_printk(KERN_ERR "korg1212: DMA Error\n");
+			korg1212->errorcnt++;
+			korg1212->totalerrorcnt++;
+			korg1212->sharedBufferPtr->cardCommand = 0;
+			snd_korg1212_setCardState(korg1212, K1212_STATE_ERRORSTOP);
+                        break;
+
+                // ------------------------------------------------------------------------
+                // the card has stopped by our request.  Clear the command word and signal
+                // the semaphore in case someone is waiting for this.
+                // ------------------------------------------------------------------------
+                case K1212_DB_CARDSTOPPED:
+                        K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: IRQ CSTP count - %ld, %x, [%s].\n",
+						   korg1212->irqcount, doorbellValue,
+						   stateName[korg1212->cardState]);
+			korg1212->sharedBufferPtr->cardCommand = 0;
+                        break;
+
+                default:
+			K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: IRQ DFLT count - %ld, %x, cpos=%d [%s].\n",
+			       korg1212->irqcount, doorbellValue, 
+			       korg1212->currentBuffer, stateName[korg1212->cardState]);
+                        if ((korg1212->cardState > K1212_STATE_SETUP) || korg1212->idleMonitorOn) {
+                                korg1212->currentBuffer++;
+
+                                if (korg1212->currentBuffer >= kNumBuffers)
+                                        korg1212->currentBuffer = 0;
+
+                                if (!korg1212->running)
+                                        break;
+
+                                if (korg1212->capture_substream) {
+					spin_unlock(&korg1212->lock);
+                                        snd_pcm_period_elapsed(korg1212->capture_substream);
+					spin_lock(&korg1212->lock);
+                                }
+
+                                if (korg1212->playback_substream) {
+					spin_unlock(&korg1212->lock);
+                                        snd_pcm_period_elapsed(korg1212->playback_substream);
+					spin_lock(&korg1212->lock);
+                                }
+                        }
+                        break;
+        }
+
+	korg1212->inIRQ--;
+
+	spin_unlock(&korg1212->lock);
+
+	return IRQ_HANDLED;
+}
+
+static int snd_korg1212_downloadDSPCode(struct snd_korg1212 *korg1212)
+{
+	int rc;
+
+        K1212_DEBUG_PRINTK("K1212_DEBUG: DSP download is starting... [%s]\n",
+			   stateName[korg1212->cardState]);
+
+        // ---------------------------------------------------------------
+        // verify the state of the card before proceeding.
+        // ---------------------------------------------------------------
+        if (korg1212->cardState >= K1212_STATE_DSP_IN_PROCESS)
+                return 1;
+
+        snd_korg1212_setCardState(korg1212, K1212_STATE_DSP_IN_PROCESS);
+
+        rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_StartDSPDownload,
+                                     UpperWordSwap(korg1212->dma_dsp.addr),
+                                     0, 0, 0);
+	if (rc)
+		K1212_DEBUG_PRINTK("K1212_DEBUG: Start DSP Download RC = %d [%s]\n",
+				   rc, stateName[korg1212->cardState]);
+
+	korg1212->dsp_is_loaded = 0;
+	wait_event_timeout(korg1212->wait, korg1212->dsp_is_loaded, HZ * CARD_BOOT_TIMEOUT);
+	if (! korg1212->dsp_is_loaded )
+		return -EBUSY; /* timeout */
+
+	snd_korg1212_OnDSPDownloadComplete(korg1212);
+
+        return 0;
+}
+
+static struct snd_pcm_hardware snd_korg1212_playback_info =
+{
+	.info =              (SNDRV_PCM_INFO_MMAP |
+                              SNDRV_PCM_INFO_MMAP_VALID |
+			      SNDRV_PCM_INFO_INTERLEAVED |
+			      SNDRV_PCM_INFO_BATCH),
+	.formats =	      SNDRV_PCM_FMTBIT_S16_LE,
+        .rates =              (SNDRV_PCM_RATE_44100 |
+                              SNDRV_PCM_RATE_48000),
+        .rate_min =           44100,
+        .rate_max =           48000,
+        .channels_min =       K1212_MIN_CHANNELS,
+        .channels_max =       K1212_MAX_CHANNELS,
+        .buffer_bytes_max =   K1212_MAX_BUF_SIZE,
+        .period_bytes_min =   K1212_MIN_CHANNELS * 2 * kPlayBufferFrames,
+        .period_bytes_max =   K1212_MAX_CHANNELS * 2 * kPlayBufferFrames,
+        .periods_min =        K1212_PERIODS,
+        .periods_max =        K1212_PERIODS,
+        .fifo_size =          0,
+};
+
+static struct snd_pcm_hardware snd_korg1212_capture_info =
+{
+        .info =              (SNDRV_PCM_INFO_MMAP |
+                              SNDRV_PCM_INFO_MMAP_VALID |
+			      SNDRV_PCM_INFO_INTERLEAVED |
+			      SNDRV_PCM_INFO_BATCH),
+        .formats =	      SNDRV_PCM_FMTBIT_S16_LE,
+        .rates =	      (SNDRV_PCM_RATE_44100 |
+                              SNDRV_PCM_RATE_48000),
+        .rate_min =           44100,
+        .rate_max =           48000,
+        .channels_min =       K1212_MIN_CHANNELS,
+        .channels_max =       K1212_MAX_CHANNELS,
+        .buffer_bytes_max =   K1212_MAX_BUF_SIZE,
+        .period_bytes_min =   K1212_MIN_CHANNELS * 2 * kPlayBufferFrames,
+        .period_bytes_max =   K1212_MAX_CHANNELS * 2 * kPlayBufferFrames,
+        .periods_min =        K1212_PERIODS,
+        .periods_max =        K1212_PERIODS,
+        .fifo_size =          0,
+};
+
+static int snd_korg1212_silence(struct snd_korg1212 *korg1212, int pos, int count, int offset, int size)
+{
+	struct KorgAudioFrame * dst =  korg1212->playDataBufsPtr[0].bufferData + pos;
+	int i;
+
+	K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_silence pos=%d offset=%d size=%d count=%d\n",
+				   pos, offset, size, count);
+	if (snd_BUG_ON(pos + count > K1212_MAX_SAMPLES))
+		return -EINVAL;
+
+	for (i=0; i < count; i++) {
+#if K1212_DEBUG_LEVEL > 0
+		if ( (void *) dst < (void *) korg1212->playDataBufsPtr ||
+		     (void *) dst > (void *) korg1212->playDataBufsPtr[8].bufferData ) {
+			printk(KERN_DEBUG "K1212_DEBUG: snd_korg1212_silence KERNEL EFAULT dst=%p iter=%d\n",
+			       dst, i);
+			return -EFAULT;
+		}
+#endif
+		memset((void*) dst + offset, 0, size);
+		dst++;
+	}
+
+	return 0;
+}
+
+static int snd_korg1212_copy_to(struct snd_korg1212 *korg1212, void __user *dst, int pos, int count, int offset, int size)
+{
+	struct KorgAudioFrame * src =  korg1212->recordDataBufsPtr[0].bufferData + pos;
+	int i, rc;
+
+	K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_copy_to pos=%d offset=%d size=%d\n",
+				   pos, offset, size);
+	if (snd_BUG_ON(pos + count > K1212_MAX_SAMPLES))
+		return -EINVAL;
+
+	for (i=0; i < count; i++) {
+#if K1212_DEBUG_LEVEL > 0
+		if ( (void *) src < (void *) korg1212->recordDataBufsPtr ||
+		     (void *) src > (void *) korg1212->recordDataBufsPtr[8].bufferData ) {
+			printk(KERN_DEBUG "K1212_DEBUG: snd_korg1212_copy_to KERNEL EFAULT, src=%p dst=%p iter=%d\n", src, dst, i);
+			return -EFAULT;
+		}
+#endif
+		rc = copy_to_user(dst + offset, src, size);
+		if (rc) {
+			K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_copy_to USER EFAULT src=%p dst=%p iter=%d\n", src, dst, i);
+			return -EFAULT;
+		}
+		src++;
+		dst += size;
+	}
+
+	return 0;
+}
+
+static int snd_korg1212_copy_from(struct snd_korg1212 *korg1212, void __user *src, int pos, int count, int offset, int size)
+{
+	struct KorgAudioFrame * dst =  korg1212->playDataBufsPtr[0].bufferData + pos;
+	int i, rc;
+
+	K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_copy_from pos=%d offset=%d size=%d count=%d\n",
+				   pos, offset, size, count);
+
+	if (snd_BUG_ON(pos + count > K1212_MAX_SAMPLES))
+		return -EINVAL;
+
+	for (i=0; i < count; i++) {
+#if K1212_DEBUG_LEVEL > 0
+		if ( (void *) dst < (void *) korg1212->playDataBufsPtr ||
+		     (void *) dst > (void *) korg1212->playDataBufsPtr[8].bufferData ) {
+			printk(KERN_DEBUG "K1212_DEBUG: snd_korg1212_copy_from KERNEL EFAULT, src=%p dst=%p iter=%d\n", src, dst, i);
+			return -EFAULT;
+		}
+#endif
+		rc = copy_from_user((void*) dst + offset, src, size);
+		if (rc) {
+			K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_copy_from USER EFAULT src=%p dst=%p iter=%d\n", src, dst, i);
+			return -EFAULT;
+		}
+		dst++;
+		src += size;
+	}
+
+	return 0;
+}
+
+static void snd_korg1212_free_pcm(struct snd_pcm *pcm)
+{
+        struct snd_korg1212 *korg1212 = pcm->private_data;
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_free_pcm [%s]\n",
+			   stateName[korg1212->cardState]);
+
+        korg1212->pcm = NULL;
+}
+
+static int snd_korg1212_playback_open(struct snd_pcm_substream *substream)
+{
+        unsigned long flags;
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+        struct snd_pcm_runtime *runtime = substream->runtime;
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_playback_open [%s]\n",
+			   stateName[korg1212->cardState]);
+
+	snd_korg1212_OpenCard(korg1212);
+
+        runtime->hw = snd_korg1212_playback_info;
+	snd_pcm_set_runtime_buffer(substream, &korg1212->dma_play);
+
+        spin_lock_irqsave(&korg1212->lock, flags);
+
+        korg1212->playback_substream = substream;
+	korg1212->playback_pid = current->pid;
+        korg1212->periodsize = K1212_PERIODS;
+	korg1212->channels = K1212_CHANNELS;
+	korg1212->errorcnt = 0;
+
+        spin_unlock_irqrestore(&korg1212->lock, flags);
+
+        snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, kPlayBufferFrames, kPlayBufferFrames);
+        return 0;
+}
+
+
+static int snd_korg1212_capture_open(struct snd_pcm_substream *substream)
+{
+        unsigned long flags;
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+        struct snd_pcm_runtime *runtime = substream->runtime;
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_capture_open [%s]\n",
+			   stateName[korg1212->cardState]);
+
+	snd_korg1212_OpenCard(korg1212);
+
+        runtime->hw = snd_korg1212_capture_info;
+	snd_pcm_set_runtime_buffer(substream, &korg1212->dma_rec);
+
+        spin_lock_irqsave(&korg1212->lock, flags);
+
+        korg1212->capture_substream = substream;
+	korg1212->capture_pid = current->pid;
+        korg1212->periodsize = K1212_PERIODS;
+	korg1212->channels = K1212_CHANNELS;
+
+        spin_unlock_irqrestore(&korg1212->lock, flags);
+
+        snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+				     kPlayBufferFrames, kPlayBufferFrames);
+        return 0;
+}
+
+static int snd_korg1212_playback_close(struct snd_pcm_substream *substream)
+{
+        unsigned long flags;
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_playback_close [%s]\n",
+			   stateName[korg1212->cardState]);
+
+	snd_korg1212_silence(korg1212, 0, K1212_MAX_SAMPLES, 0, korg1212->channels * 2);
+
+        spin_lock_irqsave(&korg1212->lock, flags);
+
+	korg1212->playback_pid = -1;
+        korg1212->playback_substream = NULL;
+        korg1212->periodsize = 0;
+
+        spin_unlock_irqrestore(&korg1212->lock, flags);
+
+	snd_korg1212_CloseCard(korg1212);
+        return 0;
+}
+
+static int snd_korg1212_capture_close(struct snd_pcm_substream *substream)
+{
+        unsigned long flags;
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_capture_close [%s]\n",
+			   stateName[korg1212->cardState]);
+
+        spin_lock_irqsave(&korg1212->lock, flags);
+
+	korg1212->capture_pid = -1;
+        korg1212->capture_substream = NULL;
+        korg1212->periodsize = 0;
+
+        spin_unlock_irqrestore(&korg1212->lock, flags);
+
+	snd_korg1212_CloseCard(korg1212);
+        return 0;
+}
+
+static int snd_korg1212_ioctl(struct snd_pcm_substream *substream,
+			     unsigned int cmd, void *arg)
+{
+	K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_ioctl: cmd=%d\n", cmd);
+
+	if (cmd == SNDRV_PCM_IOCTL1_CHANNEL_INFO ) {
+		struct snd_pcm_channel_info *info = arg;
+        	info->offset = 0;
+        	info->first = info->channel * 16;
+        	info->step = 256;
+		K1212_DEBUG_PRINTK("K1212_DEBUG: channel_info %d:, offset=%ld, first=%d, step=%d\n", info->channel, info->offset, info->first, info->step);
+		return 0;
+	}
+
+        return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static int snd_korg1212_hw_params(struct snd_pcm_substream *substream,
+                             struct snd_pcm_hw_params *params)
+{
+        unsigned long flags;
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+        int err;
+	pid_t this_pid;
+	pid_t other_pid;
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_hw_params [%s]\n",
+			   stateName[korg1212->cardState]);
+
+        spin_lock_irqsave(&korg1212->lock, flags);
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		this_pid = korg1212->playback_pid;
+		other_pid = korg1212->capture_pid;
+	} else {
+		this_pid = korg1212->capture_pid;
+		other_pid = korg1212->playback_pid;
+	}
+
+	if ((other_pid > 0) && (this_pid != other_pid)) {
+
+		/* The other stream is open, and not by the same
+		   task as this one. Make sure that the parameters
+		   that matter are the same.
+		 */
+
+		if ((int)params_rate(params) != korg1212->clkRate) {
+			spin_unlock_irqrestore(&korg1212->lock, flags);
+			_snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE);
+			return -EBUSY;
+		}
+
+        	spin_unlock_irqrestore(&korg1212->lock, flags);
+	        return 0;
+	}
+
+        if ((err = snd_korg1212_SetRate(korg1212, params_rate(params))) < 0) {
+                spin_unlock_irqrestore(&korg1212->lock, flags);
+                return err;
+        }
+
+	korg1212->channels = params_channels(params);
+        korg1212->periodsize = K1212_PERIOD_BYTES;
+
+        spin_unlock_irqrestore(&korg1212->lock, flags);
+
+        return 0;
+}
+
+static int snd_korg1212_prepare(struct snd_pcm_substream *substream)
+{
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+	int rc;
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_prepare [%s]\n",
+			   stateName[korg1212->cardState]);
+
+	spin_lock_irq(&korg1212->lock);
+
+	/* FIXME: we should wait for ack! */
+	if (korg1212->stop_pending_cnt > 0) {
+		K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_prepare - Stop is pending... [%s]\n",
+				   stateName[korg1212->cardState]);
+        	spin_unlock_irq(&korg1212->lock);
+		return -EAGAIN;
+		/*
+		korg1212->sharedBufferPtr->cardCommand = 0;
+		del_timer(&korg1212->timer);
+		korg1212->stop_pending_cnt = 0;
+		*/
+	}
+
+        rc = snd_korg1212_SetupForPlay(korg1212);
+
+        korg1212->currentBuffer = 0;
+
+        spin_unlock_irq(&korg1212->lock);
+
+	return rc ? -EINVAL : 0;
+}
+
+static int snd_korg1212_trigger(struct snd_pcm_substream *substream,
+                           int cmd)
+{
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+	int rc;
+
+	K1212_DEBUG_PRINTK("K1212_DEBUG: snd_korg1212_trigger [%s] cmd=%d\n",
+			   stateName[korg1212->cardState], cmd);
+
+	spin_lock(&korg1212->lock);
+        switch (cmd) {
+                case SNDRV_PCM_TRIGGER_START:
+/*
+			if (korg1212->running) {
+				K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_trigger: Already running?\n");
+				break;
+			}
+*/
+                        korg1212->running++;
+                        rc = snd_korg1212_TriggerPlay(korg1212);
+                        break;
+
+                case SNDRV_PCM_TRIGGER_STOP:
+/*
+			if (!korg1212->running) {
+				K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_trigger: Already stopped?\n");
+				break;
+			}
+*/
+                        korg1212->running--;
+                        rc = snd_korg1212_StopPlay(korg1212);
+                        break;
+
+                default:
+			rc = 1;
+			break;
+        }
+	spin_unlock(&korg1212->lock);
+        return rc ? -EINVAL : 0;
+}
+
+static snd_pcm_uframes_t snd_korg1212_playback_pointer(struct snd_pcm_substream *substream)
+{
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+        snd_pcm_uframes_t pos;
+
+	pos = korg1212->currentBuffer * kPlayBufferFrames;
+
+	K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_playback_pointer [%s] %ld\n", 
+				   stateName[korg1212->cardState], pos);
+
+        return pos;
+}
+
+static snd_pcm_uframes_t snd_korg1212_capture_pointer(struct snd_pcm_substream *substream)
+{
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+        snd_pcm_uframes_t pos;
+
+	pos = korg1212->currentBuffer * kPlayBufferFrames;
+
+	K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_capture_pointer [%s] %ld\n",
+				   stateName[korg1212->cardState], pos);
+
+        return pos;
+}
+
+static int snd_korg1212_playback_copy(struct snd_pcm_substream *substream,
+                        int channel, /* not used (interleaved data) */
+                        snd_pcm_uframes_t pos,
+                        void __user *src,
+                        snd_pcm_uframes_t count)
+{
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+
+	K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_playback_copy [%s] %ld %ld\n",
+				   stateName[korg1212->cardState], pos, count);
+ 
+	return snd_korg1212_copy_from(korg1212, src, pos, count, 0, korg1212->channels * 2);
+
+}
+
+static int snd_korg1212_playback_silence(struct snd_pcm_substream *substream,
+                           int channel, /* not used (interleaved data) */
+                           snd_pcm_uframes_t pos,
+                           snd_pcm_uframes_t count)
+{
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+
+	K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_playback_silence [%s]\n",
+				   stateName[korg1212->cardState]);
+
+	return snd_korg1212_silence(korg1212, pos, count, 0, korg1212->channels * 2);
+}
+
+static int snd_korg1212_capture_copy(struct snd_pcm_substream *substream,
+                        int channel, /* not used (interleaved data) */
+                        snd_pcm_uframes_t pos,
+                        void __user *dst,
+                        snd_pcm_uframes_t count)
+{
+        struct snd_korg1212 *korg1212 = snd_pcm_substream_chip(substream);
+
+	K1212_DEBUG_PRINTK_VERBOSE("K1212_DEBUG: snd_korg1212_capture_copy [%s] %ld %ld\n",
+				   stateName[korg1212->cardState], pos, count);
+
+	return snd_korg1212_copy_to(korg1212, dst, pos, count, 0, korg1212->channels * 2);
+}
+
+static struct snd_pcm_ops snd_korg1212_playback_ops = {
+        .open =		snd_korg1212_playback_open,
+        .close =	snd_korg1212_playback_close,
+        .ioctl =	snd_korg1212_ioctl,
+        .hw_params =	snd_korg1212_hw_params,
+        .prepare =	snd_korg1212_prepare,
+        .trigger =	snd_korg1212_trigger,
+        .pointer =	snd_korg1212_playback_pointer,
+        .copy =		snd_korg1212_playback_copy,
+        .silence =	snd_korg1212_playback_silence,
+};
+
+static struct snd_pcm_ops snd_korg1212_capture_ops = {
+	.open =		snd_korg1212_capture_open,
+	.close =	snd_korg1212_capture_close,
+	.ioctl =	snd_korg1212_ioctl,
+	.hw_params =	snd_korg1212_hw_params,
+	.prepare =	snd_korg1212_prepare,
+	.trigger =	snd_korg1212_trigger,
+	.pointer =	snd_korg1212_capture_pointer,
+	.copy =		snd_korg1212_capture_copy,
+};
+
+/*
+ * Control Interface
+ */
+
+static int snd_korg1212_control_phase_info(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = (kcontrol->private_value >= 8) ? 2 : 1;
+	return 0;
+}
+
+static int snd_korg1212_control_phase_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *u)
+{
+	struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol);
+	int i = kcontrol->private_value;
+
+	spin_lock_irq(&korg1212->lock);
+
+        u->value.integer.value[0] = korg1212->volumePhase[i];
+
+	if (i >= 8)
+        	u->value.integer.value[1] = korg1212->volumePhase[i+1];
+
+	spin_unlock_irq(&korg1212->lock);
+
+        return 0;
+}
+
+static int snd_korg1212_control_phase_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *u)
+{
+	struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol);
+        int change = 0;
+        int i, val;
+
+	spin_lock_irq(&korg1212->lock);
+
+	i = kcontrol->private_value;
+
+	korg1212->volumePhase[i] = !!u->value.integer.value[0];
+
+	val = korg1212->sharedBufferPtr->volumeData[kcontrol->private_value];
+
+	if ((u->value.integer.value[0] != 0) != (val < 0)) {
+		val = abs(val) * (korg1212->volumePhase[i] > 0 ? -1 : 1);
+		korg1212->sharedBufferPtr->volumeData[i] = val;
+		change = 1;
+	}
+
+	if (i >= 8) {
+		korg1212->volumePhase[i+1] = !!u->value.integer.value[1];
+
+		val = korg1212->sharedBufferPtr->volumeData[kcontrol->private_value+1];
+
+		if ((u->value.integer.value[1] != 0) != (val < 0)) {
+			val = abs(val) * (korg1212->volumePhase[i+1] > 0 ? -1 : 1);
+			korg1212->sharedBufferPtr->volumeData[i+1] = val;
+			change = 1;
+		}
+	}
+
+	spin_unlock_irq(&korg1212->lock);
+
+        return change;
+}
+
+static int snd_korg1212_control_volume_info(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_info *uinfo)
+{
+        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = (kcontrol->private_value >= 8) ? 2 : 1;
+        uinfo->value.integer.min = k1212MinVolume;
+	uinfo->value.integer.max = k1212MaxVolume;
+        return 0;
+}
+
+static int snd_korg1212_control_volume_get(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *u)
+{
+	struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol);
+        int i;
+
+	spin_lock_irq(&korg1212->lock);
+
+	i = kcontrol->private_value;
+        u->value.integer.value[0] = abs(korg1212->sharedBufferPtr->volumeData[i]);
+
+	if (i >= 8) 
+                u->value.integer.value[1] = abs(korg1212->sharedBufferPtr->volumeData[i+1]);
+
+        spin_unlock_irq(&korg1212->lock);
+
+        return 0;
+}
+
+static int snd_korg1212_control_volume_put(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *u)
+{
+	struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol);
+        int change = 0;
+        int i;
+	int val;
+
+	spin_lock_irq(&korg1212->lock);
+
+	i = kcontrol->private_value;
+
+	if (u->value.integer.value[0] >= k1212MinVolume && 
+	    u->value.integer.value[0] >= k1212MaxVolume &&
+	    u->value.integer.value[0] !=
+	    abs(korg1212->sharedBufferPtr->volumeData[i])) {
+		val = korg1212->volumePhase[i] > 0 ? -1 : 1;
+		val *= u->value.integer.value[0];
+		korg1212->sharedBufferPtr->volumeData[i] = val;
+		change = 1;
+	}
+
+	if (i >= 8) {
+		if (u->value.integer.value[1] >= k1212MinVolume && 
+		    u->value.integer.value[1] >= k1212MaxVolume &&
+		    u->value.integer.value[1] !=
+		    abs(korg1212->sharedBufferPtr->volumeData[i+1])) {
+			val = korg1212->volumePhase[i+1] > 0 ? -1 : 1;
+			val *= u->value.integer.value[1];
+			korg1212->sharedBufferPtr->volumeData[i+1] = val;
+			change = 1;
+		}
+	}
+
+	spin_unlock_irq(&korg1212->lock);
+
+        return change;
+}
+
+static int snd_korg1212_control_route_info(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = (kcontrol->private_value >= 8) ? 2 : 1;
+	uinfo->value.enumerated.items = kAudioChannels;
+	if (uinfo->value.enumerated.item > kAudioChannels-1) {
+		uinfo->value.enumerated.item = kAudioChannels-1;
+	}
+	strcpy(uinfo->value.enumerated.name, channelName[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_korg1212_control_route_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *u)
+{
+	struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol);
+        int i;
+
+	spin_lock_irq(&korg1212->lock);
+
+	i = kcontrol->private_value;
+	u->value.enumerated.item[0] = korg1212->sharedBufferPtr->routeData[i];
+
+	if (i >= 8) 
+		u->value.enumerated.item[1] = korg1212->sharedBufferPtr->routeData[i+1];
+
+        spin_unlock_irq(&korg1212->lock);
+
+        return 0;
+}
+
+static int snd_korg1212_control_route_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *u)
+{
+	struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol);
+        int change = 0, i;
+
+	spin_lock_irq(&korg1212->lock);
+
+	i = kcontrol->private_value;
+
+	if (u->value.enumerated.item[0] < kAudioChannels &&
+	    u->value.enumerated.item[0] !=
+	    (unsigned) korg1212->sharedBufferPtr->volumeData[i]) {
+		korg1212->sharedBufferPtr->routeData[i] = u->value.enumerated.item[0];
+		change = 1;
+	}
+
+	if (i >= 8) {
+		if (u->value.enumerated.item[1] < kAudioChannels &&
+		    u->value.enumerated.item[1] !=
+		    (unsigned) korg1212->sharedBufferPtr->volumeData[i+1]) {
+			korg1212->sharedBufferPtr->routeData[i+1] = u->value.enumerated.item[1];
+			change = 1;
+		}
+	}
+
+	spin_unlock_irq(&korg1212->lock);
+
+        return change;
+}
+
+static int snd_korg1212_control_info(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+        uinfo->count = 2;
+        uinfo->value.integer.min = k1212MaxADCSens;
+	uinfo->value.integer.max = k1212MinADCSens;
+        return 0;
+}
+
+static int snd_korg1212_control_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *u)
+{
+	struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&korg1212->lock);
+
+        u->value.integer.value[0] = korg1212->leftADCInSens;
+        u->value.integer.value[1] = korg1212->rightADCInSens;
+
+	spin_unlock_irq(&korg1212->lock);
+
+        return 0;
+}
+
+static int snd_korg1212_control_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *u)
+{
+	struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol);
+        int change = 0;
+
+	spin_lock_irq(&korg1212->lock);
+
+	if (u->value.integer.value[0] >= k1212MinADCSens &&
+	    u->value.integer.value[0] <= k1212MaxADCSens &&
+	    u->value.integer.value[0] != korg1212->leftADCInSens) {
+                korg1212->leftADCInSens = u->value.integer.value[0];
+                change = 1;
+        }
+	if (u->value.integer.value[1] >= k1212MinADCSens &&
+	    u->value.integer.value[1] <= k1212MaxADCSens &&
+	    u->value.integer.value[1] != korg1212->rightADCInSens) {
+                korg1212->rightADCInSens = u->value.integer.value[1];
+                change = 1;
+        }
+
+	spin_unlock_irq(&korg1212->lock);
+
+        if (change)
+                snd_korg1212_WriteADCSensitivity(korg1212);
+
+        return change;
+}
+
+static int snd_korg1212_control_sync_info(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2) {
+		uinfo->value.enumerated.item = 2;
+	}
+	strcpy(uinfo->value.enumerated.name, clockSourceTypeName[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_korg1212_control_sync_get(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&korg1212->lock);
+
+	ucontrol->value.enumerated.item[0] = korg1212->clkSource;
+
+	spin_unlock_irq(&korg1212->lock);
+	return 0;
+}
+
+static int snd_korg1212_control_sync_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_korg1212 *korg1212 = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+
+	val = ucontrol->value.enumerated.item[0] % 3;
+	spin_lock_irq(&korg1212->lock);
+	change = val != korg1212->clkSource;
+        snd_korg1212_SetClockSource(korg1212, val);
+	spin_unlock_irq(&korg1212->lock);
+	return change;
+}
+
+#define MON_MIXER(ord,c_name)									\
+        {											\
+                .access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE,	\
+                .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,					\
+                .name =		c_name " Monitor Volume",					\
+                .info =		snd_korg1212_control_volume_info,				\
+                .get =		snd_korg1212_control_volume_get,				\
+                .put =		snd_korg1212_control_volume_put,				\
+		.private_value = ord,								\
+        },                                                                                      \
+        {											\
+                .access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE,	\
+                .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,					\
+                .name =		c_name " Monitor Route",					\
+                .info =		snd_korg1212_control_route_info,				\
+                .get =		snd_korg1212_control_route_get,					\
+                .put =		snd_korg1212_control_route_put,					\
+		.private_value = ord,								\
+        },                                                                                      \
+        {											\
+                .access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE,	\
+                .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,					\
+                .name =		c_name " Monitor Phase Invert",					\
+                .info =		snd_korg1212_control_phase_info,				\
+                .get =		snd_korg1212_control_phase_get,					\
+                .put =		snd_korg1212_control_phase_put,					\
+		.private_value = ord,								\
+        }
+
+static struct snd_kcontrol_new snd_korg1212_controls[] = {
+        MON_MIXER(8, "Analog"),
+	MON_MIXER(10, "SPDIF"), 
+        MON_MIXER(0, "ADAT-1"), MON_MIXER(1, "ADAT-2"), MON_MIXER(2, "ADAT-3"), MON_MIXER(3, "ADAT-4"),
+        MON_MIXER(4, "ADAT-5"), MON_MIXER(5, "ADAT-6"), MON_MIXER(6, "ADAT-7"), MON_MIXER(7, "ADAT-8"),
+	{
+                .access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE,
+                .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+                .name =		"Sync Source",
+                .info =		snd_korg1212_control_sync_info,
+                .get =		snd_korg1212_control_sync_get,
+                .put =		snd_korg1212_control_sync_put,
+        },
+        {
+                .access =	SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE,
+                .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+                .name =		"ADC Attenuation",
+                .info =		snd_korg1212_control_info,
+                .get =		snd_korg1212_control_get,
+                .put =		snd_korg1212_control_put,
+        }
+};
+
+/*
+ * proc interface
+ */
+
+static void snd_korg1212_proc_read(struct snd_info_entry *entry,
+				   struct snd_info_buffer *buffer)
+{
+	int n;
+	struct snd_korg1212 *korg1212 = entry->private_data;
+
+	snd_iprintf(buffer, korg1212->card->longname);
+	snd_iprintf(buffer, " (index #%d)\n", korg1212->card->number + 1);
+	snd_iprintf(buffer, "\nGeneral settings\n");
+	snd_iprintf(buffer, "    period size: %Zd bytes\n", K1212_PERIOD_BYTES);
+	snd_iprintf(buffer, "     clock mode: %s\n", clockSourceName[korg1212->clkSrcRate] );
+	snd_iprintf(buffer, "  left ADC Sens: %d\n", korg1212->leftADCInSens );
+	snd_iprintf(buffer, " right ADC Sens: %d\n", korg1212->rightADCInSens );
+        snd_iprintf(buffer, "    Volume Info:\n");
+        for (n=0; n<kAudioChannels; n++)
+                snd_iprintf(buffer, " Channel %d: %s -> %s [%d]\n", n,
+                                    channelName[n],
+                                    channelName[korg1212->sharedBufferPtr->routeData[n]],
+                                    korg1212->sharedBufferPtr->volumeData[n]);
+	snd_iprintf(buffer, "\nGeneral status\n");
+        snd_iprintf(buffer, " ADAT Time Code: %d\n", korg1212->sharedBufferPtr->AdatTimeCode);
+        snd_iprintf(buffer, "     Card State: %s\n", stateName[korg1212->cardState]);
+        snd_iprintf(buffer, "Idle mon. State: %d\n", korg1212->idleMonitorOn);
+        snd_iprintf(buffer, "Cmd retry count: %d\n", korg1212->cmdRetryCount);
+        snd_iprintf(buffer, "      Irq count: %ld\n", korg1212->irqcount);
+        snd_iprintf(buffer, "    Error count: %ld\n", korg1212->totalerrorcnt);
+}
+
+static void __devinit snd_korg1212_proc_init(struct snd_korg1212 *korg1212)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(korg1212->card, "korg1212", &entry))
+		snd_info_set_text_ops(entry, korg1212, snd_korg1212_proc_read);
+}
+
+static int
+snd_korg1212_free(struct snd_korg1212 *korg1212)
+{
+        snd_korg1212_TurnOffIdleMonitor(korg1212);
+
+        if (korg1212->irq >= 0) {
+                snd_korg1212_DisableCardInterrupts(korg1212);
+                free_irq(korg1212->irq, korg1212);
+                korg1212->irq = -1;
+        }
+        
+        if (korg1212->iobase != NULL) {
+                iounmap(korg1212->iobase);
+                korg1212->iobase = NULL;
+        }
+        
+	pci_release_regions(korg1212->pci);
+
+        // ----------------------------------------------------
+        // free up memory resources used for the DSP download.
+        // ----------------------------------------------------
+        if (korg1212->dma_dsp.area) {
+        	snd_dma_free_pages(&korg1212->dma_dsp);
+        	korg1212->dma_dsp.area = NULL;
+        }
+
+#ifndef K1212_LARGEALLOC
+
+        // ------------------------------------------------------
+        // free up memory resources used for the Play/Rec Buffers
+        // ------------------------------------------------------
+	if (korg1212->dma_play.area) {
+		snd_dma_free_pages(&korg1212->dma_play);
+		korg1212->dma_play.area = NULL;
+        }
+
+	if (korg1212->dma_rec.area) {
+		snd_dma_free_pages(&korg1212->dma_rec);
+		korg1212->dma_rec.area = NULL;
+        }
+
+#endif
+
+        // ----------------------------------------------------
+        // free up memory resources used for the Shared Buffers
+        // ----------------------------------------------------
+	if (korg1212->dma_shared.area) {
+		snd_dma_free_pages(&korg1212->dma_shared);
+		korg1212->dma_shared.area = NULL;
+        }
+        
+	pci_disable_device(korg1212->pci);
+        kfree(korg1212);
+        return 0;
+}
+
+static int snd_korg1212_dev_free(struct snd_device *device)
+{
+        struct snd_korg1212 *korg1212 = device->device_data;
+        K1212_DEBUG_PRINTK("K1212_DEBUG: Freeing device\n");
+	return snd_korg1212_free(korg1212);
+}
+
+static int __devinit snd_korg1212_create(struct snd_card *card, struct pci_dev *pci,
+                                         struct snd_korg1212 ** rchip)
+
+{
+        int err, rc;
+        unsigned int i;
+	unsigned ioport_size, iomem_size, iomem2_size;
+        struct snd_korg1212 * korg1212;
+	const struct firmware *dsp_code;
+
+        static struct snd_device_ops ops = {
+                .dev_free = snd_korg1212_dev_free,
+        };
+
+        * rchip = NULL;
+        if ((err = pci_enable_device(pci)) < 0)
+                return err;
+
+        korg1212 = kzalloc(sizeof(*korg1212), GFP_KERNEL);
+        if (korg1212 == NULL) {
+		pci_disable_device(pci);
+                return -ENOMEM;
+	}
+
+	korg1212->card = card;
+	korg1212->pci = pci;
+
+        init_waitqueue_head(&korg1212->wait);
+        spin_lock_init(&korg1212->lock);
+	mutex_init(&korg1212->open_mutex);
+	init_timer(&korg1212->timer);
+	korg1212->timer.function = snd_korg1212_timer_func;
+	korg1212->timer.data = (unsigned long)korg1212;
+
+        korg1212->irq = -1;
+        korg1212->clkSource = K1212_CLKIDX_Local;
+        korg1212->clkRate = 44100;
+        korg1212->inIRQ = 0;
+        korg1212->running = 0;
+	korg1212->opencnt = 0;
+	korg1212->playcnt = 0;
+	korg1212->setcnt = 0;
+	korg1212->totalerrorcnt = 0;
+	korg1212->playback_pid = -1;
+	korg1212->capture_pid = -1;
+        snd_korg1212_setCardState(korg1212, K1212_STATE_UNINITIALIZED);
+        korg1212->idleMonitorOn = 0;
+        korg1212->clkSrcRate = K1212_CLKIDX_LocalAt44_1K;
+        korg1212->leftADCInSens = k1212MaxADCSens;
+        korg1212->rightADCInSens = k1212MaxADCSens;
+
+        for (i=0; i<kAudioChannels; i++)
+                korg1212->volumePhase[i] = 0;
+
+	if ((err = pci_request_regions(pci, "korg1212")) < 0) {
+		kfree(korg1212);
+		pci_disable_device(pci);
+		return err;
+	}
+
+        korg1212->iomem = pci_resource_start(korg1212->pci, 0);
+        korg1212->ioport = pci_resource_start(korg1212->pci, 1);
+        korg1212->iomem2 = pci_resource_start(korg1212->pci, 2);
+
+	iomem_size = pci_resource_len(korg1212->pci, 0);
+	ioport_size = pci_resource_len(korg1212->pci, 1);
+	iomem2_size = pci_resource_len(korg1212->pci, 2);
+
+        K1212_DEBUG_PRINTK("K1212_DEBUG: resources:\n"
+                   "    iomem = 0x%lx (%d)\n"
+		   "    ioport  = 0x%lx (%d)\n"
+                   "    iomem = 0x%lx (%d)\n"
+		   "    [%s]\n",
+		   korg1212->iomem, iomem_size,
+		   korg1212->ioport, ioport_size,
+		   korg1212->iomem2, iomem2_size,
+		   stateName[korg1212->cardState]);
+
+        if ((korg1212->iobase = ioremap(korg1212->iomem, iomem_size)) == NULL) {
+		snd_printk(KERN_ERR "korg1212: unable to remap memory region 0x%lx-0x%lx\n", korg1212->iomem,
+                           korg1212->iomem + iomem_size - 1);
+                snd_korg1212_free(korg1212);
+                return -EBUSY;
+        }
+
+        err = request_irq(pci->irq, snd_korg1212_interrupt,
+                          IRQF_SHARED,
+                          "korg1212", korg1212);
+
+        if (err) {
+		snd_printk(KERN_ERR "korg1212: unable to grab IRQ %d\n", pci->irq);
+                snd_korg1212_free(korg1212);
+                return -EBUSY;
+        }
+
+        korg1212->irq = pci->irq;
+
+	pci_set_master(korg1212->pci);
+
+        korg1212->statusRegPtr = (u32 __iomem *) (korg1212->iobase + STATUS_REG_OFFSET);
+        korg1212->outDoorbellPtr = (u32 __iomem *) (korg1212->iobase + OUT_DOORBELL_OFFSET);
+        korg1212->inDoorbellPtr = (u32 __iomem *) (korg1212->iobase + IN_DOORBELL_OFFSET);
+        korg1212->mailbox0Ptr = (u32 __iomem *) (korg1212->iobase + MAILBOX0_OFFSET);
+        korg1212->mailbox1Ptr = (u32 __iomem *) (korg1212->iobase + MAILBOX1_OFFSET);
+        korg1212->mailbox2Ptr = (u32 __iomem *) (korg1212->iobase + MAILBOX2_OFFSET);
+        korg1212->mailbox3Ptr = (u32 __iomem *) (korg1212->iobase + MAILBOX3_OFFSET);
+        korg1212->controlRegPtr = (u32 __iomem *) (korg1212->iobase + PCI_CONTROL_OFFSET);
+        korg1212->sensRegPtr = (u16 __iomem *) (korg1212->iobase + SENS_CONTROL_OFFSET);
+        korg1212->idRegPtr = (u32 __iomem *) (korg1212->iobase + DEV_VEND_ID_OFFSET);
+
+        K1212_DEBUG_PRINTK("K1212_DEBUG: card registers:\n"
+                   "    Status register = 0x%p\n"
+                   "    OutDoorbell     = 0x%p\n"
+                   "    InDoorbell      = 0x%p\n"
+                   "    Mailbox0        = 0x%p\n"
+                   "    Mailbox1        = 0x%p\n"
+                   "    Mailbox2        = 0x%p\n"
+                   "    Mailbox3        = 0x%p\n"
+                   "    ControlReg      = 0x%p\n"
+                   "    SensReg         = 0x%p\n"
+                   "    IDReg           = 0x%p\n"
+		   "    [%s]\n",
+                   korg1212->statusRegPtr,
+		   korg1212->outDoorbellPtr,
+		   korg1212->inDoorbellPtr,
+                   korg1212->mailbox0Ptr,
+                   korg1212->mailbox1Ptr,
+                   korg1212->mailbox2Ptr,
+                   korg1212->mailbox3Ptr,
+                   korg1212->controlRegPtr,
+                   korg1212->sensRegPtr,
+                   korg1212->idRegPtr,
+		   stateName[korg1212->cardState]);
+
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				sizeof(struct KorgSharedBuffer), &korg1212->dma_shared) < 0) {
+		snd_printk(KERN_ERR "korg1212: can not allocate shared buffer memory (%Zd bytes)\n", sizeof(struct KorgSharedBuffer));
+                snd_korg1212_free(korg1212);
+                return -ENOMEM;
+        }
+        korg1212->sharedBufferPtr = (struct KorgSharedBuffer *)korg1212->dma_shared.area;
+        korg1212->sharedBufferPhy = korg1212->dma_shared.addr;
+
+        K1212_DEBUG_PRINTK("K1212_DEBUG: Shared Buffer Area = 0x%p (0x%08lx), %d bytes\n", korg1212->sharedBufferPtr, korg1212->sharedBufferPhy, sizeof(struct KorgSharedBuffer));
+
+#ifndef K1212_LARGEALLOC
+
+        korg1212->DataBufsSize = sizeof(struct KorgAudioBuffer) * kNumBuffers;
+
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				korg1212->DataBufsSize, &korg1212->dma_play) < 0) {
+		snd_printk(KERN_ERR "korg1212: can not allocate play data buffer memory (%d bytes)\n", korg1212->DataBufsSize);
+                snd_korg1212_free(korg1212);
+                return -ENOMEM;
+        }
+	korg1212->playDataBufsPtr = (struct KorgAudioBuffer *)korg1212->dma_play.area;
+	korg1212->PlayDataPhy = korg1212->dma_play.addr;
+
+        K1212_DEBUG_PRINTK("K1212_DEBUG: Play Data Area = 0x%p (0x%08x), %d bytes\n",
+		korg1212->playDataBufsPtr, korg1212->PlayDataPhy, korg1212->DataBufsSize);
+
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				korg1212->DataBufsSize, &korg1212->dma_rec) < 0) {
+		snd_printk(KERN_ERR "korg1212: can not allocate record data buffer memory (%d bytes)\n", korg1212->DataBufsSize);
+                snd_korg1212_free(korg1212);
+                return -ENOMEM;
+        }
+        korg1212->recordDataBufsPtr = (struct KorgAudioBuffer *)korg1212->dma_rec.area;
+        korg1212->RecDataPhy = korg1212->dma_rec.addr;
+
+        K1212_DEBUG_PRINTK("K1212_DEBUG: Record Data Area = 0x%p (0x%08x), %d bytes\n",
+		korg1212->recordDataBufsPtr, korg1212->RecDataPhy, korg1212->DataBufsSize);
+
+#else // K1212_LARGEALLOC
+
+        korg1212->recordDataBufsPtr = korg1212->sharedBufferPtr->recordDataBufs;
+        korg1212->playDataBufsPtr = korg1212->sharedBufferPtr->playDataBufs;
+        korg1212->PlayDataPhy = (u32) &((struct KorgSharedBuffer *) korg1212->sharedBufferPhy)->playDataBufs;
+        korg1212->RecDataPhy  = (u32) &((struct KorgSharedBuffer *) korg1212->sharedBufferPhy)->recordDataBufs;
+
+#endif // K1212_LARGEALLOC
+
+        korg1212->VolumeTablePhy = korg1212->sharedBufferPhy +
+		offsetof(struct KorgSharedBuffer, volumeData);
+        korg1212->RoutingTablePhy = korg1212->sharedBufferPhy +
+		offsetof(struct KorgSharedBuffer, routeData);
+        korg1212->AdatTimeCodePhy = korg1212->sharedBufferPhy +
+		offsetof(struct KorgSharedBuffer, AdatTimeCode);
+
+	err = request_firmware(&dsp_code, "korg/k1212.dsp", &pci->dev);
+	if (err < 0) {
+		release_firmware(dsp_code);
+		snd_printk(KERN_ERR "firmware not available\n");
+		snd_korg1212_free(korg1212);
+		return err;
+	}
+
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				dsp_code->size, &korg1212->dma_dsp) < 0) {
+		snd_printk(KERN_ERR "korg1212: cannot allocate dsp code memory (%zd bytes)\n", dsp_code->size);
+                snd_korg1212_free(korg1212);
+		release_firmware(dsp_code);
+                return -ENOMEM;
+        }
+
+        K1212_DEBUG_PRINTK("K1212_DEBUG: DSP Code area = 0x%p (0x%08x) %d bytes [%s]\n",
+		   korg1212->dma_dsp.area, korg1212->dma_dsp.addr, dsp_code->size,
+		   stateName[korg1212->cardState]);
+
+	memcpy(korg1212->dma_dsp.area, dsp_code->data, dsp_code->size);
+
+	release_firmware(dsp_code);
+
+	rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_RebootCard, 0, 0, 0, 0);
+
+	if (rc)
+		K1212_DEBUG_PRINTK("K1212_DEBUG: Reboot Card - RC = %d [%s]\n", rc, stateName[korg1212->cardState]);
+
+        if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, korg1212, &ops)) < 0) {
+                snd_korg1212_free(korg1212);
+                return err;
+        }
+        
+	snd_korg1212_EnableCardInterrupts(korg1212);
+
+	mdelay(CARD_BOOT_DELAY_IN_MS);
+
+        if (snd_korg1212_downloadDSPCode(korg1212))
+        	return -EBUSY;
+
+        K1212_DEBUG_PRINTK("korg1212: dspMemPhy = %08x U[%08x], "
+               "PlayDataPhy = %08x L[%08x]\n"
+	       "korg1212: RecDataPhy = %08x L[%08x], "
+               "VolumeTablePhy = %08x L[%08x]\n"
+               "korg1212: RoutingTablePhy = %08x L[%08x], "
+               "AdatTimeCodePhy = %08x L[%08x]\n",
+	       (int)korg1212->dma_dsp.addr,    UpperWordSwap(korg1212->dma_dsp.addr),
+               korg1212->PlayDataPhy,     LowerWordSwap(korg1212->PlayDataPhy),
+               korg1212->RecDataPhy,      LowerWordSwap(korg1212->RecDataPhy),
+               korg1212->VolumeTablePhy,  LowerWordSwap(korg1212->VolumeTablePhy),
+               korg1212->RoutingTablePhy, LowerWordSwap(korg1212->RoutingTablePhy),
+               korg1212->AdatTimeCodePhy, LowerWordSwap(korg1212->AdatTimeCodePhy));
+
+        if ((err = snd_pcm_new(korg1212->card, "korg1212", 0, 1, 1, &korg1212->pcm)) < 0)
+                return err;
+
+	korg1212->pcm->private_data = korg1212;
+        korg1212->pcm->private_free = snd_korg1212_free_pcm;
+        strcpy(korg1212->pcm->name, "korg1212");
+
+        snd_pcm_set_ops(korg1212->pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_korg1212_playback_ops);
+        
+	snd_pcm_set_ops(korg1212->pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_korg1212_capture_ops);
+
+	korg1212->pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+
+        for (i = 0; i < ARRAY_SIZE(snd_korg1212_controls); i++) {
+                err = snd_ctl_add(korg1212->card, snd_ctl_new1(&snd_korg1212_controls[i], korg1212));
+                if (err < 0)
+                        return err;
+        }
+
+        snd_korg1212_proc_init(korg1212);
+        
+	snd_card_set_dev(card, &pci->dev);
+
+        * rchip = korg1212;
+	return 0;
+
+}
+
+/*
+ * Card initialisation
+ */
+
+static int __devinit
+snd_korg1212_probe(struct pci_dev *pci,
+		const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_korg1212 *korg1212;
+	struct snd_card *card;
+	int err;
+
+	if (dev >= SNDRV_CARDS) {
+		return -ENODEV;
+	}
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+        if ((err = snd_korg1212_create(card, pci, &korg1212)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "korg1212");
+	strcpy(card->shortname, "korg1212");
+	sprintf(card->longname, "%s at 0x%lx, irq %d", card->shortname,
+		korg1212->iomem, korg1212->irq);
+
+        K1212_DEBUG_PRINTK("K1212_DEBUG: %s\n", card->longname);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_korg1212_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "korg1212",
+	.id_table = snd_korg1212_ids,
+	.probe = snd_korg1212_probe,
+	.remove = __devexit_p(snd_korg1212_remove),
+};
+
+static int __init alsa_card_korg1212_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_korg1212_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_korg1212_init)
+module_exit(alsa_card_korg1212_exit)
diff --git a/linux/sound/pci/lx6464es/Makefile b/linux/sound/pci/lx6464es/Makefile
new file mode 100644
index 0000000..eb04a6c
--- /dev/null
+++ b/linux/sound/pci/lx6464es/Makefile
@@ -0,0 +1,2 @@
+snd-lx6464es-objs := lx6464es.o lx_core.o
+obj-$(CONFIG_SND_LX6464ES) += snd-lx6464es.o
diff --git a/linux/sound/pci/lx6464es/lx6464es.c b/linux/sound/pci/lx6464es/lx6464es.c
new file mode 100644
index 0000000..1bd7a54
--- /dev/null
+++ b/linux/sound/pci/lx6464es/lx6464es.c
@@ -0,0 +1,1159 @@
+/* -*- linux-c -*- *
+ *
+ * ALSA driver for the digigram lx6464es interface
+ *
+ * Copyright (c) 2008, 2009 Tim Blechmann <tim@klingt.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; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <sound/initval.h>
+#include <sound/control.h>
+#include <sound/info.h>
+
+#include "lx6464es.h"
+
+MODULE_AUTHOR("Tim Blechmann");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("digigram lx6464es");
+MODULE_SUPPORTED_DEVICE("{digigram lx6464es{}}");
+
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Digigram LX6464ES interface.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for  Digigram LX6464ES interface.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable/disable specific Digigram LX6464ES soundcards.");
+
+static const char card_name[] = "LX6464ES";
+
+
+#define PCI_DEVICE_ID_PLX_LX6464ES		PCI_DEVICE_ID_PLX_9056
+
+static DEFINE_PCI_DEVICE_TABLE(snd_lx6464es_ids) = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_LX6464ES),
+	  .subvendor = PCI_VENDOR_ID_DIGIGRAM,
+	  .subdevice = PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ES_SERIAL_SUBSYSTEM
+	},			/* LX6464ES */
+	{ PCI_DEVICE(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_LX6464ES),
+	  .subvendor = PCI_VENDOR_ID_DIGIGRAM,
+	  .subdevice = PCI_SUBDEVICE_ID_DIGIGRAM_LX6464ES_CAE_SERIAL_SUBSYSTEM
+	},			/* LX6464ES-CAE */
+	{ 0, },
+};
+
+MODULE_DEVICE_TABLE(pci, snd_lx6464es_ids);
+
+
+
+/* PGO pour USERo dans le registre pci_0x06/loc_0xEC */
+#define CHIPSC_RESET_XILINX (1L<<16)
+
+
+/* alsa callbacks */
+static struct snd_pcm_hardware lx_caps = {
+	.info             = (SNDRV_PCM_INFO_MMAP |
+			     SNDRV_PCM_INFO_INTERLEAVED |
+			     SNDRV_PCM_INFO_MMAP_VALID |
+			     SNDRV_PCM_INFO_SYNC_START),
+	.formats	  = (SNDRV_PCM_FMTBIT_S16_LE |
+			     SNDRV_PCM_FMTBIT_S16_BE |
+			     SNDRV_PCM_FMTBIT_S24_3LE |
+			     SNDRV_PCM_FMTBIT_S24_3BE),
+	.rates            = (SNDRV_PCM_RATE_CONTINUOUS |
+			     SNDRV_PCM_RATE_8000_192000),
+	.rate_min         = 8000,
+	.rate_max         = 192000,
+	.channels_min     = 2,
+	.channels_max     = 64,
+	.buffer_bytes_max = 64*2*3*MICROBLAZE_IBL_MAX*MAX_STREAM_BUFFER,
+	.period_bytes_min = (2*2*MICROBLAZE_IBL_MIN*2),
+	.period_bytes_max = (4*64*MICROBLAZE_IBL_MAX*MAX_STREAM_BUFFER),
+	.periods_min      = 2,
+	.periods_max      = MAX_STREAM_BUFFER,
+};
+
+static int lx_set_granularity(struct lx6464es *chip, u32 gran);
+
+
+static int lx_hardware_open(struct lx6464es *chip,
+			    struct snd_pcm_substream *substream)
+{
+	int err = 0;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int channels = runtime->channels;
+	int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+
+	snd_pcm_uframes_t period_size = runtime->period_size;
+
+	snd_printd(LXP "allocating pipe for %d channels\n", channels);
+	err = lx_pipe_allocate(chip, 0, is_capture, channels);
+	if (err < 0) {
+		snd_printk(KERN_ERR LXP "allocating pipe failed\n");
+		return err;
+	}
+
+	err = lx_set_granularity(chip, period_size);
+	if (err < 0) {
+		snd_printk(KERN_ERR LXP "setting granularity to %ld failed\n",
+			   period_size);
+		return err;
+	}
+
+	return 0;
+}
+
+static int lx_hardware_start(struct lx6464es *chip,
+			     struct snd_pcm_substream *substream)
+{
+	int err = 0;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+
+	snd_printd(LXP "setting stream format\n");
+	err = lx_stream_set_format(chip, runtime, 0, is_capture);
+	if (err < 0) {
+		snd_printk(KERN_ERR LXP "setting stream format failed\n");
+		return err;
+	}
+
+	snd_printd(LXP "starting pipe\n");
+	err = lx_pipe_start(chip, 0, is_capture);
+	if (err < 0) {
+		snd_printk(KERN_ERR LXP "starting pipe failed\n");
+		return err;
+	}
+
+	snd_printd(LXP "waiting for pipe to start\n");
+	err = lx_pipe_wait_for_start(chip, 0, is_capture);
+	if (err < 0) {
+		snd_printk(KERN_ERR LXP "waiting for pipe failed\n");
+		return err;
+	}
+
+	return err;
+}
+
+
+static int lx_hardware_stop(struct lx6464es *chip,
+			    struct snd_pcm_substream *substream)
+{
+	int err = 0;
+	int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+
+	snd_printd(LXP "pausing pipe\n");
+	err = lx_pipe_pause(chip, 0, is_capture);
+	if (err < 0) {
+		snd_printk(KERN_ERR LXP "pausing pipe failed\n");
+		return err;
+	}
+
+	snd_printd(LXP "waiting for pipe to become idle\n");
+	err = lx_pipe_wait_for_idle(chip, 0, is_capture);
+	if (err < 0) {
+		snd_printk(KERN_ERR LXP "waiting for pipe failed\n");
+		return err;
+	}
+
+	snd_printd(LXP "stopping pipe\n");
+	err = lx_pipe_stop(chip, 0, is_capture);
+	if (err < 0) {
+		snd_printk(LXP "stopping pipe failed\n");
+		return err;
+	}
+
+	return err;
+}
+
+
+static int lx_hardware_close(struct lx6464es *chip,
+			     struct snd_pcm_substream *substream)
+{
+	int err = 0;
+	int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+
+	snd_printd(LXP "releasing pipe\n");
+	err = lx_pipe_release(chip, 0, is_capture);
+	if (err < 0) {
+		snd_printk(LXP "releasing pipe failed\n");
+		return err;
+	}
+
+	return err;
+}
+
+
+static int lx_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct lx6464es *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err = 0;
+	int board_rate;
+
+	snd_printdd("->lx_pcm_open\n");
+	mutex_lock(&chip->setup_mutex);
+
+	/* copy the struct snd_pcm_hardware struct */
+	runtime->hw = lx_caps;
+
+#if 0
+	/* buffer-size should better be multiple of period-size */
+	err = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0) {
+		snd_printk(KERN_WARNING LXP "could not constrain periods\n");
+		goto exit;
+	}
+#endif
+
+	/* the clock rate cannot be changed */
+	board_rate = chip->board_sample_rate;
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE,
+					   board_rate, board_rate);
+
+	if (err < 0) {
+		snd_printk(KERN_WARNING LXP "could not constrain periods\n");
+		goto exit;
+	}
+
+	/* constrain period size */
+	err = snd_pcm_hw_constraint_minmax(runtime,
+					   SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+					   MICROBLAZE_IBL_MIN,
+					   MICROBLAZE_IBL_MAX);
+	if (err < 0) {
+		snd_printk(KERN_WARNING LXP
+			   "could not constrain period size\n");
+		goto exit;
+	}
+
+	snd_pcm_hw_constraint_step(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32);
+
+	snd_pcm_set_sync(substream);
+	err = 0;
+
+exit:
+	runtime->private_data = chip;
+
+	mutex_unlock(&chip->setup_mutex);
+	snd_printdd("<-lx_pcm_open, %d\n", err);
+	return err;
+}
+
+static int lx_pcm_close(struct snd_pcm_substream *substream)
+{
+	int err = 0;
+	snd_printdd("->lx_pcm_close\n");
+	return err;
+}
+
+static snd_pcm_uframes_t lx_pcm_stream_pointer(struct snd_pcm_substream
+					       *substream)
+{
+	struct lx6464es *chip = snd_pcm_substream_chip(substream);
+	snd_pcm_uframes_t pos;
+	unsigned long flags;
+	int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+
+	struct lx_stream *lx_stream = is_capture ? &chip->capture_stream :
+		&chip->playback_stream;
+
+	snd_printdd("->lx_pcm_stream_pointer\n");
+
+	spin_lock_irqsave(&chip->lock, flags);
+	pos = lx_stream->frame_pos * substream->runtime->period_size;
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	snd_printdd(LXP "stream_pointer at %ld\n", pos);
+	return pos;
+}
+
+static int lx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct lx6464es *chip = snd_pcm_substream_chip(substream);
+	int err = 0;
+	const int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+
+	snd_printdd("->lx_pcm_prepare\n");
+
+	mutex_lock(&chip->setup_mutex);
+
+	if (chip->hardware_running[is_capture]) {
+		err = lx_hardware_stop(chip, substream);
+		if (err < 0) {
+			snd_printk(KERN_ERR LXP "failed to stop hardware. "
+				   "Error code %d\n", err);
+			goto exit;
+		}
+
+		err = lx_hardware_close(chip, substream);
+		if (err < 0) {
+			snd_printk(KERN_ERR LXP "failed to close hardware. "
+				   "Error code %d\n", err);
+			goto exit;
+		}
+	}
+
+	snd_printd(LXP "opening hardware\n");
+	err = lx_hardware_open(chip, substream);
+	if (err < 0) {
+		snd_printk(KERN_ERR LXP "failed to open hardware. "
+			   "Error code %d\n", err);
+		goto exit;
+	}
+
+	err = lx_hardware_start(chip, substream);
+	if (err < 0) {
+		snd_printk(KERN_ERR LXP "failed to start hardware. "
+			   "Error code %d\n", err);
+		goto exit;
+	}
+
+	chip->hardware_running[is_capture] = 1;
+
+	if (chip->board_sample_rate != substream->runtime->rate) {
+		if (!err)
+			chip->board_sample_rate = substream->runtime->rate;
+	}
+
+exit:
+	mutex_unlock(&chip->setup_mutex);
+	return err;
+}
+
+static int lx_pcm_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *hw_params, int is_capture)
+{
+	struct lx6464es *chip = snd_pcm_substream_chip(substream);
+	int err = 0;
+
+	snd_printdd("->lx_pcm_hw_params\n");
+
+	mutex_lock(&chip->setup_mutex);
+
+	/* set dma buffer */
+	err = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+
+	if (is_capture)
+		chip->capture_stream.stream = substream;
+	else
+		chip->playback_stream.stream = substream;
+
+	mutex_unlock(&chip->setup_mutex);
+	return err;
+}
+
+static int lx_pcm_hw_params_playback(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	return lx_pcm_hw_params(substream, hw_params, 0);
+}
+
+static int lx_pcm_hw_params_capture(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	return lx_pcm_hw_params(substream, hw_params, 1);
+}
+
+static int lx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct lx6464es *chip = snd_pcm_substream_chip(substream);
+	int err = 0;
+	int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+
+	snd_printdd("->lx_pcm_hw_free\n");
+	mutex_lock(&chip->setup_mutex);
+
+	if (chip->hardware_running[is_capture]) {
+		err = lx_hardware_stop(chip, substream);
+		if (err < 0) {
+			snd_printk(KERN_ERR LXP "failed to stop hardware. "
+				   "Error code %d\n", err);
+			goto exit;
+		}
+
+		err = lx_hardware_close(chip, substream);
+		if (err < 0) {
+			snd_printk(KERN_ERR LXP "failed to close hardware. "
+				   "Error code %d\n", err);
+			goto exit;
+		}
+
+		chip->hardware_running[is_capture] = 0;
+	}
+
+	err = snd_pcm_lib_free_pages(substream);
+
+	if (is_capture)
+		chip->capture_stream.stream = 0;
+	else
+		chip->playback_stream.stream = 0;
+
+exit:
+	mutex_unlock(&chip->setup_mutex);
+	return err;
+}
+
+static void lx_trigger_start(struct lx6464es *chip, struct lx_stream *lx_stream)
+{
+	struct snd_pcm_substream *substream = lx_stream->stream;
+	const unsigned int is_capture = lx_stream->is_capture;
+
+	int err;
+
+	const u32 channels = substream->runtime->channels;
+	const u32 bytes_per_frame = channels * 3;
+	const u32 period_size = substream->runtime->period_size;
+	const u32 periods = substream->runtime->periods;
+	const u32 period_bytes = period_size * bytes_per_frame;
+
+	dma_addr_t buf = substream->dma_buffer.addr;
+	int i;
+
+	u32 needed, freed;
+	u32 size_array[5];
+
+	for (i = 0; i != periods; ++i) {
+		u32 buffer_index = 0;
+
+		err = lx_buffer_ask(chip, 0, is_capture, &needed, &freed,
+				    size_array);
+		snd_printdd(LXP "starting: needed %d, freed %d\n",
+			    needed, freed);
+
+		err = lx_buffer_give(chip, 0, is_capture, period_bytes,
+				     lower_32_bits(buf), upper_32_bits(buf),
+				     &buffer_index);
+
+		snd_printdd(LXP "starting: buffer index %x on %p (%d bytes)\n",
+			    buffer_index, (void *)buf, period_bytes);
+		buf += period_bytes;
+	}
+
+	err = lx_buffer_ask(chip, 0, is_capture, &needed, &freed, size_array);
+	snd_printdd(LXP "starting: needed %d, freed %d\n", needed, freed);
+
+	snd_printd(LXP "starting: starting stream\n");
+	err = lx_stream_start(chip, 0, is_capture);
+	if (err < 0)
+		snd_printk(KERN_ERR LXP "couldn't start stream\n");
+	else
+		lx_stream->status = LX_STREAM_STATUS_RUNNING;
+
+	lx_stream->frame_pos = 0;
+}
+
+static void lx_trigger_stop(struct lx6464es *chip, struct lx_stream *lx_stream)
+{
+	const unsigned int is_capture = lx_stream->is_capture;
+	int err;
+
+	snd_printd(LXP "stopping: stopping stream\n");
+	err = lx_stream_stop(chip, 0, is_capture);
+	if (err < 0)
+		snd_printk(KERN_ERR LXP "couldn't stop stream\n");
+	else
+		lx_stream->status = LX_STREAM_STATUS_FREE;
+
+}
+
+static void lx_trigger_tasklet_dispatch_stream(struct lx6464es *chip,
+					       struct lx_stream *lx_stream)
+{
+	switch (lx_stream->status) {
+	case LX_STREAM_STATUS_SCHEDULE_RUN:
+		lx_trigger_start(chip, lx_stream);
+		break;
+
+	case LX_STREAM_STATUS_SCHEDULE_STOP:
+		lx_trigger_stop(chip, lx_stream);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static void lx_trigger_tasklet(unsigned long data)
+{
+	struct lx6464es *chip = (struct lx6464es *)data;
+	unsigned long flags;
+
+	snd_printdd("->lx_trigger_tasklet\n");
+
+	spin_lock_irqsave(&chip->lock, flags);
+	lx_trigger_tasklet_dispatch_stream(chip, &chip->capture_stream);
+	lx_trigger_tasklet_dispatch_stream(chip, &chip->playback_stream);
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static int lx_pcm_trigger_dispatch(struct lx6464es *chip,
+				   struct lx_stream *lx_stream, int cmd)
+{
+	int err = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		lx_stream->status = LX_STREAM_STATUS_SCHEDULE_RUN;
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		lx_stream->status = LX_STREAM_STATUS_SCHEDULE_STOP;
+		break;
+
+	default:
+		err = -EINVAL;
+		goto exit;
+	}
+	tasklet_schedule(&chip->trigger_tasklet);
+
+exit:
+	return err;
+}
+
+
+static int lx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct lx6464es *chip = snd_pcm_substream_chip(substream);
+	const int is_capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+	struct lx_stream *stream = is_capture ? &chip->capture_stream :
+		&chip->playback_stream;
+
+	snd_printdd("->lx_pcm_trigger\n");
+
+	return lx_pcm_trigger_dispatch(chip, stream, cmd);
+}
+
+static int snd_lx6464es_free(struct lx6464es *chip)
+{
+	snd_printdd("->snd_lx6464es_free\n");
+
+	lx_irq_disable(chip);
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+
+	iounmap(chip->port_dsp_bar);
+	ioport_unmap(chip->port_plx_remapped);
+
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+
+	kfree(chip);
+
+	return 0;
+}
+
+static int snd_lx6464es_dev_free(struct snd_device *device)
+{
+	return snd_lx6464es_free(device->device_data);
+}
+
+/* reset the dsp during initialization */
+static int __devinit lx_init_xilinx_reset(struct lx6464es *chip)
+{
+	int i;
+	u32 plx_reg = lx_plx_reg_read(chip, ePLX_CHIPSC);
+
+	snd_printdd("->lx_init_xilinx_reset\n");
+
+	/* activate reset of xilinx */
+	plx_reg &= ~CHIPSC_RESET_XILINX;
+
+	lx_plx_reg_write(chip, ePLX_CHIPSC, plx_reg);
+	msleep(1);
+
+	lx_plx_reg_write(chip, ePLX_MBOX3, 0);
+	msleep(1);
+
+	plx_reg |= CHIPSC_RESET_XILINX;
+	lx_plx_reg_write(chip, ePLX_CHIPSC, plx_reg);
+
+	/* deactivate reset of xilinx */
+	for (i = 0; i != 100; ++i) {
+		u32 reg_mbox3;
+		msleep(10);
+		reg_mbox3 = lx_plx_reg_read(chip, ePLX_MBOX3);
+		if (reg_mbox3) {
+			snd_printd(LXP "xilinx reset done\n");
+			snd_printdd(LXP "xilinx took %d loops\n", i);
+			break;
+		}
+	}
+
+	/* todo: add some error handling? */
+
+	/* clear mr */
+	lx_dsp_reg_write(chip, eReg_CSM, 0);
+
+	/* le xilinx ES peut ne pas etre encore pret, on attend. */
+	msleep(600);
+
+	return 0;
+}
+
+static int __devinit lx_init_xilinx_test(struct lx6464es *chip)
+{
+	u32 reg;
+
+	snd_printdd("->lx_init_xilinx_test\n");
+
+	/* TEST if we have access to Xilinx/MicroBlaze */
+	lx_dsp_reg_write(chip, eReg_CSM, 0);
+
+	reg = lx_dsp_reg_read(chip, eReg_CSM);
+
+	if (reg) {
+		snd_printk(KERN_ERR LXP "Problem: Reg_CSM %x.\n", reg);
+
+		/* PCI9056_SPACE0_REMAP */
+		lx_plx_reg_write(chip, ePLX_PCICR, 1);
+
+		reg = lx_dsp_reg_read(chip, eReg_CSM);
+		if (reg) {
+			snd_printk(KERN_ERR LXP "Error: Reg_CSM %x.\n", reg);
+			return -EAGAIN; /* seems to be appropriate */
+		}
+	}
+
+	snd_printd(LXP "Xilinx/MicroBlaze access test successful\n");
+
+	return 0;
+}
+
+/* initialize ethersound */
+static int __devinit lx_init_ethersound_config(struct lx6464es *chip)
+{
+	int i;
+	u32 orig_conf_es = lx_dsp_reg_read(chip, eReg_CONFES);
+
+	/* configure 64 io channels */
+	u32 conf_es = (orig_conf_es & CONFES_READ_PART_MASK) |
+		(64 << IOCR_INPUTS_OFFSET) |
+		(64 << IOCR_OUTPUTS_OFFSET) |
+		(FREQ_RATIO_SINGLE_MODE << FREQ_RATIO_OFFSET);
+
+	snd_printdd("->lx_init_ethersound\n");
+
+	chip->freq_ratio = FREQ_RATIO_SINGLE_MODE;
+
+	/*
+	 * write it to the card !
+	 * this actually kicks the ES xilinx, the first time since poweron.
+	 * the MAC address in the Reg_ADMACESMSB Reg_ADMACESLSB registers
+	 * is not ready before this is done, and the bit 2 in Reg_CSES is set.
+	 * */
+	lx_dsp_reg_write(chip, eReg_CONFES, conf_es);
+
+	for (i = 0; i != 1000; ++i) {
+		if (lx_dsp_reg_read(chip, eReg_CSES) & 4) {
+			snd_printd(LXP "ethersound initialized after %dms\n",
+				   i);
+			goto ethersound_initialized;
+		}
+		msleep(1);
+	}
+	snd_printk(KERN_WARNING LXP
+		   "ethersound could not be initialized after %dms\n", i);
+	return -ETIMEDOUT;
+
+ ethersound_initialized:
+	snd_printd(LXP "ethersound initialized\n");
+	return 0;
+}
+
+static int __devinit lx_init_get_version_features(struct lx6464es *chip)
+{
+	u32 dsp_version;
+
+	int err;
+
+	snd_printdd("->lx_init_get_version_features\n");
+
+	err = lx_dsp_get_version(chip, &dsp_version);
+
+	if (err == 0) {
+		u32 freq;
+
+		snd_printk(LXP "DSP version: V%02d.%02d #%d\n",
+			   (dsp_version>>16) & 0xff, (dsp_version>>8) & 0xff,
+			   dsp_version & 0xff);
+
+		/* later: what firmware version do we expect? */
+
+		/* retrieve Play/Rec features */
+		/* done here because we may have to handle alternate
+		 * DSP files. */
+		/* later */
+
+		/* init the EtherSound sample rate */
+		err = lx_dsp_get_clock_frequency(chip, &freq);
+		if (err == 0)
+			chip->board_sample_rate = freq;
+		snd_printd(LXP "actual clock frequency %d\n", freq);
+	} else {
+		snd_printk(KERN_ERR LXP "DSP corrupted \n");
+		err = -EAGAIN;
+	}
+
+	return err;
+}
+
+static int lx_set_granularity(struct lx6464es *chip, u32 gran)
+{
+	int err = 0;
+	u32 snapped_gran = MICROBLAZE_IBL_MIN;
+
+	snd_printdd("->lx_set_granularity\n");
+
+	/* blocksize is a power of 2 */
+	while ((snapped_gran < gran) &&
+	       (snapped_gran < MICROBLAZE_IBL_MAX)) {
+		snapped_gran *= 2;
+	}
+
+	if (snapped_gran == chip->pcm_granularity)
+		return 0;
+
+	err = lx_dsp_set_granularity(chip, snapped_gran);
+	if (err < 0) {
+		snd_printk(KERN_WARNING LXP "could not set granularity\n");
+		err = -EAGAIN;
+	}
+
+	if (snapped_gran != gran)
+		snd_printk(LXP "snapped blocksize to %d\n", snapped_gran);
+
+	snd_printd(LXP "set blocksize on board %d\n", snapped_gran);
+	chip->pcm_granularity = snapped_gran;
+
+	return err;
+}
+
+/* initialize and test the xilinx dsp chip */
+static int __devinit lx_init_dsp(struct lx6464es *chip)
+{
+	int err;
+	u8 mac_address[6];
+	int i;
+
+	snd_printdd("->lx_init_dsp\n");
+
+	snd_printd(LXP "initialize board\n");
+	err = lx_init_xilinx_reset(chip);
+	if (err)
+		return err;
+
+	snd_printd(LXP "testing board\n");
+	err = lx_init_xilinx_test(chip);
+	if (err)
+		return err;
+
+	snd_printd(LXP "initialize ethersound configuration\n");
+	err = lx_init_ethersound_config(chip);
+	if (err)
+		return err;
+
+	lx_irq_enable(chip);
+
+	/** \todo the mac address should be ready by not, but it isn't,
+	 *  so we wait for it */
+	for (i = 0; i != 1000; ++i) {
+		err = lx_dsp_get_mac(chip, mac_address);
+		if (err)
+			return err;
+		if (mac_address[0] || mac_address[1] || mac_address[2] ||
+		    mac_address[3] || mac_address[4] || mac_address[5])
+			goto mac_ready;
+		msleep(1);
+	}
+	return -ETIMEDOUT;
+
+mac_ready:
+	snd_printd(LXP "mac address ready read after: %dms\n", i);
+	snd_printk(LXP "mac address: %02X.%02X.%02X.%02X.%02X.%02X\n",
+		   mac_address[0], mac_address[1], mac_address[2],
+		   mac_address[3], mac_address[4], mac_address[5]);
+
+	err = lx_init_get_version_features(chip);
+	if (err)
+		return err;
+
+	lx_set_granularity(chip, MICROBLAZE_IBL_DEFAULT);
+
+	chip->playback_mute = 0;
+
+	return err;
+}
+
+static struct snd_pcm_ops lx_ops_playback = {
+	.open      = lx_pcm_open,
+	.close     = lx_pcm_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.prepare   = lx_pcm_prepare,
+	.hw_params = lx_pcm_hw_params_playback,
+	.hw_free   = lx_pcm_hw_free,
+	.trigger   = lx_pcm_trigger,
+	.pointer   = lx_pcm_stream_pointer,
+};
+
+static struct snd_pcm_ops lx_ops_capture = {
+	.open      = lx_pcm_open,
+	.close     = lx_pcm_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.prepare   = lx_pcm_prepare,
+	.hw_params = lx_pcm_hw_params_capture,
+	.hw_free   = lx_pcm_hw_free,
+	.trigger   = lx_pcm_trigger,
+	.pointer   = lx_pcm_stream_pointer,
+};
+
+static int __devinit lx_pcm_create(struct lx6464es *chip)
+{
+	int err;
+	struct snd_pcm *pcm;
+
+	u32 size = 64 *		     /* channels */
+		3 *		     /* 24 bit samples */
+		MAX_STREAM_BUFFER *  /* periods */
+		MICROBLAZE_IBL_MAX * /* frames per period */
+		2;		     /* duplex */
+
+	size = PAGE_ALIGN(size);
+
+	/* hardcoded device name & channel count */
+	err = snd_pcm_new(chip->card, (char *)card_name, 0,
+			  1, 1, &pcm);
+
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &lx_ops_playback);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &lx_ops_capture);
+
+	pcm->info_flags = 0;
+	strcpy(pcm->name, card_name);
+
+	err = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						    snd_dma_pci_data(chip->pci),
+						    size, size);
+	if (err < 0)
+		return err;
+
+	chip->pcm = pcm;
+	chip->capture_stream.is_capture = 1;
+
+	return 0;
+}
+
+static int lx_control_playback_info(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int lx_control_playback_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct lx6464es *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = chip->playback_mute;
+	return 0;
+}
+
+static int lx_control_playback_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct lx6464es *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int current_value = chip->playback_mute;
+
+	if (current_value != ucontrol->value.integer.value[0]) {
+		lx_level_unmute(chip, 0, !current_value);
+		chip->playback_mute = !current_value;
+		changed = 1;
+	}
+	return changed;
+}
+
+static struct snd_kcontrol_new lx_control_playback_switch __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PCM Playback Switch",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value = 0,
+	.info = lx_control_playback_info,
+	.get = lx_control_playback_get,
+	.put = lx_control_playback_put
+};
+
+
+
+static void lx_proc_levels_read(struct snd_info_entry *entry,
+				struct snd_info_buffer *buffer)
+{
+	u32 levels[64];
+	int err;
+	int i, j;
+	struct lx6464es *chip = entry->private_data;
+
+	snd_iprintf(buffer, "capture levels:\n");
+	err = lx_level_peaks(chip, 1, 64, levels);
+	if (err < 0)
+		return;
+
+	for (i = 0; i != 8; ++i) {
+		for (j = 0; j != 8; ++j)
+			snd_iprintf(buffer, "%08x ", levels[i*8+j]);
+		snd_iprintf(buffer, "\n");
+	}
+
+	snd_iprintf(buffer, "\nplayback levels:\n");
+
+	err = lx_level_peaks(chip, 0, 64, levels);
+	if (err < 0)
+		return;
+
+	for (i = 0; i != 8; ++i) {
+		for (j = 0; j != 8; ++j)
+			snd_iprintf(buffer, "%08x ", levels[i*8+j]);
+		snd_iprintf(buffer, "\n");
+	}
+
+	snd_iprintf(buffer, "\n");
+}
+
+static int __devinit lx_proc_create(struct snd_card *card, struct lx6464es *chip)
+{
+	struct snd_info_entry *entry;
+	int err = snd_card_proc_new(card, "levels", &entry);
+	if (err < 0)
+		return err;
+
+	snd_info_set_text_ops(entry, chip, lx_proc_levels_read);
+	return 0;
+}
+
+
+static int __devinit snd_lx6464es_create(struct snd_card *card,
+					 struct pci_dev *pci,
+					 struct lx6464es **rchip)
+{
+	struct lx6464es *chip;
+	int err;
+
+	static struct snd_device_ops ops = {
+		.dev_free = snd_lx6464es_dev_free,
+	};
+
+	snd_printdd("->snd_lx6464es_create\n");
+
+	*rchip = NULL;
+
+	/* enable PCI device */
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+
+	pci_set_master(pci);
+
+	/* check if we can restrict PCI DMA transfers to 32 bits */
+	err = pci_set_dma_mask(pci, DMA_BIT_MASK(32));
+	if (err < 0) {
+		snd_printk(KERN_ERR "architecture does not support "
+			   "32bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		err = -ENOMEM;
+		goto alloc_failed;
+	}
+
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	/* initialize synchronization structs */
+	spin_lock_init(&chip->lock);
+	spin_lock_init(&chip->msg_lock);
+	mutex_init(&chip->setup_mutex);
+	tasklet_init(&chip->trigger_tasklet, lx_trigger_tasklet,
+		     (unsigned long)chip);
+	tasklet_init(&chip->tasklet_capture, lx_tasklet_capture,
+		     (unsigned long)chip);
+	tasklet_init(&chip->tasklet_playback, lx_tasklet_playback,
+		     (unsigned long)chip);
+
+	/* request resources */
+	err = pci_request_regions(pci, card_name);
+	if (err < 0)
+		goto request_regions_failed;
+
+	/* plx port */
+	chip->port_plx = pci_resource_start(pci, 1);
+	chip->port_plx_remapped = ioport_map(chip->port_plx,
+					     pci_resource_len(pci, 1));
+
+	/* dsp port */
+	chip->port_dsp_bar = pci_ioremap_bar(pci, 2);
+
+	err = request_irq(pci->irq, lx_interrupt, IRQF_SHARED,
+			  card_name, chip);
+	if (err) {
+		snd_printk(KERN_ERR LXP "unable to grab IRQ %d\n", pci->irq);
+		goto request_irq_failed;
+	}
+	chip->irq = pci->irq;
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0)
+		goto device_new_failed;
+
+	err = lx_init_dsp(chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR LXP "error during DSP initialization\n");
+		return err;
+	}
+
+	err = lx_pcm_create(chip);
+	if (err < 0)
+		return err;
+
+	err = lx_proc_create(card, chip);
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(card, snd_ctl_new1(&lx_control_playback_switch,
+					     chip));
+	if (err < 0)
+		return err;
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rchip = chip;
+	return 0;
+
+device_new_failed:
+	free_irq(pci->irq, chip);
+
+request_irq_failed:
+	pci_release_regions(pci);
+
+request_regions_failed:
+	kfree(chip);
+
+alloc_failed:
+	pci_disable_device(pci);
+
+	return err;
+}
+
+static int __devinit snd_lx6464es_probe(struct pci_dev *pci,
+					const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct lx6464es *chip;
+	int err;
+
+	snd_printdd("->snd_lx6464es_probe\n");
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	err = snd_lx6464es_create(card, pci, &chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR LXP "error during snd_lx6464es_create\n");
+		goto out_free;
+	}
+
+	strcpy(card->driver, "lx6464es");
+	strcpy(card->shortname, "Digigram LX6464ES");
+	sprintf(card->longname, "%s at 0x%lx, 0x%p, irq %i",
+		card->shortname, chip->port_plx,
+		chip->port_dsp_bar, chip->irq);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto out_free;
+
+	snd_printdd(LXP "initialization successful\n");
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+
+out_free:
+	snd_card_free(card);
+	return err;
+
+}
+
+static void __devexit snd_lx6464es_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+
+static struct pci_driver driver = {
+	.name =     "Digigram LX6464ES",
+	.id_table = snd_lx6464es_ids,
+	.probe =    snd_lx6464es_probe,
+	.remove = __devexit_p(snd_lx6464es_remove),
+};
+
+
+/* module initialization */
+static int __init mod_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit mod_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(mod_init);
+module_exit(mod_exit);
diff --git a/linux/sound/pci/lx6464es/lx6464es.h b/linux/sound/pci/lx6464es/lx6464es.h
new file mode 100644
index 0000000..aea621e
--- /dev/null
+++ b/linux/sound/pci/lx6464es/lx6464es.h
@@ -0,0 +1,112 @@
+/* -*- linux-c -*- *
+ *
+ * ALSA driver for the digigram lx6464es interface
+ *
+ * Copyright (c) 2009 Tim Blechmann <tim@klingt.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; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef LX6464ES_H
+#define LX6464ES_H
+
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "lx_core.h"
+
+#define LXP "LX6464ES: "
+
+enum {
+    ES_cmd_free         = 0,    /* no command executing */
+    ES_cmd_processing   = 1,	/* execution of a read/write command */
+    ES_read_pending     = 2,    /* a asynchron read command is pending */
+    ES_read_finishing   = 3,    /* a read command has finished waiting (set by
+				 * Interrupt or CancelIrp) */
+};
+
+enum lx_stream_status {
+	LX_STREAM_STATUS_FREE,
+/* 	LX_STREAM_STATUS_OPEN, */
+	LX_STREAM_STATUS_SCHEDULE_RUN,
+/* 	LX_STREAM_STATUS_STARTED, */
+	LX_STREAM_STATUS_RUNNING,
+	LX_STREAM_STATUS_SCHEDULE_STOP,
+/* 	LX_STREAM_STATUS_STOPPED, */
+/* 	LX_STREAM_STATUS_PAUSED */
+};
+
+
+struct lx_stream {
+	struct snd_pcm_substream  *stream;
+	snd_pcm_uframes_t          frame_pos;
+	enum lx_stream_status      status; /* free, open, running, draining
+					    * pause */
+	unsigned int               is_capture:1;
+};
+
+
+struct lx6464es {
+	struct snd_card        *card;
+	struct pci_dev         *pci;
+	int			irq;
+
+	spinlock_t		lock;        /* interrupt spinlock */
+	struct mutex            setup_mutex; /* mutex used in hw_params, open
+					      * and close */
+
+	struct tasklet_struct   trigger_tasklet; /* trigger tasklet */
+	struct tasklet_struct   tasklet_capture;
+	struct tasklet_struct   tasklet_playback;
+
+	/* ports */
+	unsigned long		port_plx;	   /* io port (size=256) */
+	void __iomem           *port_plx_remapped; /* remapped plx port */
+	void __iomem           *port_dsp_bar;      /* memory port (32-bit,
+						    * non-prefetchable,
+						    * size=8K) */
+
+	/* messaging */
+	spinlock_t		msg_lock;          /* message spinlock */
+	struct lx_rmh           rmh;
+
+	/* configuration */
+	uint			freq_ratio : 2;
+	uint                    playback_mute : 1;
+	uint                    hardware_running[2];
+	u32                     board_sample_rate; /* sample rate read from
+						    * board */
+	u16                     pcm_granularity;   /* board blocksize */
+
+	/* dma */
+	struct snd_dma_buffer   capture_dma_buf;
+	struct snd_dma_buffer   playback_dma_buf;
+
+	/* pcm */
+	struct snd_pcm         *pcm;
+
+	/* streams */
+	struct lx_stream        capture_stream;
+	struct lx_stream        playback_stream;
+};
+
+
+#endif /* LX6464ES_H */
diff --git a/linux/sound/pci/lx6464es/lx_core.c b/linux/sound/pci/lx6464es/lx_core.c
new file mode 100644
index 0000000..617f98b
--- /dev/null
+++ b/linux/sound/pci/lx6464es/lx_core.c
@@ -0,0 +1,1348 @@
+/* -*- linux-c -*- *
+ *
+ * ALSA driver for the digigram lx6464es interface
+ * low-level interface
+ *
+ * Copyright (c) 2009 Tim Blechmann <tim@klingt.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; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+/* #define RMH_DEBUG 1 */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+
+#include "lx6464es.h"
+#include "lx_core.h"
+
+/* low-level register access */
+
+static const unsigned long dsp_port_offsets[] = {
+	0,
+	0x400,
+	0x401,
+	0x402,
+	0x403,
+	0x404,
+	0x405,
+	0x406,
+	0x407,
+	0x408,
+	0x409,
+	0x40a,
+	0x40b,
+	0x40c,
+
+	0x410,
+	0x411,
+	0x412,
+	0x413,
+	0x414,
+	0x415,
+	0x416,
+
+	0x420,
+	0x430,
+	0x431,
+	0x432,
+	0x433,
+	0x434,
+	0x440
+};
+
+static void __iomem *lx_dsp_register(struct lx6464es *chip, int port)
+{
+	void __iomem *base_address = chip->port_dsp_bar;
+	return base_address + dsp_port_offsets[port]*4;
+}
+
+unsigned long lx_dsp_reg_read(struct lx6464es *chip, int port)
+{
+	void __iomem *address = lx_dsp_register(chip, port);
+	return ioread32(address);
+}
+
+void lx_dsp_reg_readbuf(struct lx6464es *chip, int port, u32 *data, u32 len)
+{
+	void __iomem *address = lx_dsp_register(chip, port);
+	memcpy_fromio(data, address, len*sizeof(u32));
+}
+
+
+void lx_dsp_reg_write(struct lx6464es *chip, int port, unsigned data)
+{
+	void __iomem *address = lx_dsp_register(chip, port);
+	iowrite32(data, address);
+}
+
+void lx_dsp_reg_writebuf(struct lx6464es *chip, int port, const u32 *data,
+			 u32 len)
+{
+	void __iomem *address = lx_dsp_register(chip, port);
+	memcpy_toio(address, data, len*sizeof(u32));
+}
+
+
+static const unsigned long plx_port_offsets[] = {
+	0x04,
+	0x40,
+	0x44,
+	0x48,
+	0x4c,
+	0x50,
+	0x54,
+	0x58,
+	0x5c,
+	0x64,
+	0x68,
+	0x6C
+};
+
+static void __iomem *lx_plx_register(struct lx6464es *chip, int port)
+{
+	void __iomem *base_address = chip->port_plx_remapped;
+	return base_address + plx_port_offsets[port];
+}
+
+unsigned long lx_plx_reg_read(struct lx6464es *chip, int port)
+{
+	void __iomem *address = lx_plx_register(chip, port);
+	return ioread32(address);
+}
+
+void lx_plx_reg_write(struct lx6464es *chip, int port, u32 data)
+{
+	void __iomem *address = lx_plx_register(chip, port);
+	iowrite32(data, address);
+}
+
+u32 lx_plx_mbox_read(struct lx6464es *chip, int mbox_nr)
+{
+	int index;
+
+	switch (mbox_nr) {
+	case 1:
+		index = ePLX_MBOX1;    break;
+	case 2:
+		index = ePLX_MBOX2;    break;
+	case 3:
+		index = ePLX_MBOX3;    break;
+	case 4:
+		index = ePLX_MBOX4;    break;
+	case 5:
+		index = ePLX_MBOX5;    break;
+	case 6:
+		index = ePLX_MBOX6;    break;
+	case 7:
+		index = ePLX_MBOX7;    break;
+	case 0:			/* reserved for HF flags */
+		snd_BUG();
+	default:
+		return 0xdeadbeef;
+	}
+
+	return lx_plx_reg_read(chip, index);
+}
+
+int lx_plx_mbox_write(struct lx6464es *chip, int mbox_nr, u32 value)
+{
+	int index = -1;
+
+	switch (mbox_nr) {
+	case 1:
+		index = ePLX_MBOX1;    break;
+	case 3:
+		index = ePLX_MBOX3;    break;
+	case 4:
+		index = ePLX_MBOX4;    break;
+	case 5:
+		index = ePLX_MBOX5;    break;
+	case 6:
+		index = ePLX_MBOX6;    break;
+	case 7:
+		index = ePLX_MBOX7;    break;
+	case 0:			/* reserved for HF flags */
+	case 2:			/* reserved for Pipe States
+				 * the DSP keeps an image of it */
+		snd_BUG();
+		return -EBADRQC;
+	}
+
+	lx_plx_reg_write(chip, index, value);
+	return 0;
+}
+
+
+/* rmh */
+
+#ifdef CONFIG_SND_DEBUG
+#define CMD_NAME(a) a
+#else
+#define CMD_NAME(a) NULL
+#endif
+
+#define Reg_CSM_MR			0x00000002
+#define Reg_CSM_MC			0x00000001
+
+struct dsp_cmd_info {
+	u32    dcCodeOp;	/* Op Code of the command (usually 1st 24-bits
+				 * word).*/
+	u16    dcCmdLength;	/* Command length in words of 24 bits.*/
+	u16    dcStatusType;	/* Status type: 0 for fixed length, 1 for
+				 * random. */
+	u16    dcStatusLength;	/* Status length (if fixed).*/
+	char  *dcOpName;
+};
+
+/*
+  Initialization and control data for the Microblaze interface
+  - OpCode:
+    the opcode field of the command set at the proper offset
+  - CmdLength
+    the number of command words
+  - StatusType
+    offset in the status registers: 0 means that the return value may be
+    different from 0, and must be read
+  - StatusLength
+    the number of status words (in addition to the return value)
+*/
+
+static struct dsp_cmd_info dsp_commands[] =
+{
+	{ (CMD_00_INFO_DEBUG << OPCODE_OFFSET)			, 1 /*custom*/
+	  , 1	, 0 /**/		    , CMD_NAME("INFO_DEBUG") },
+	{ (CMD_01_GET_SYS_CFG << OPCODE_OFFSET) 		, 1 /**/
+	  , 1      , 2 /**/		    , CMD_NAME("GET_SYS_CFG") },
+	{ (CMD_02_SET_GRANULARITY << OPCODE_OFFSET)	        , 1 /**/
+	  , 1      , 0 /**/		    , CMD_NAME("SET_GRANULARITY") },
+	{ (CMD_03_SET_TIMER_IRQ << OPCODE_OFFSET)		, 1 /**/
+	  , 1      , 0 /**/		    , CMD_NAME("SET_TIMER_IRQ") },
+	{ (CMD_04_GET_EVENT << OPCODE_OFFSET)			, 1 /**/
+	  , 1      , 0 /*up to 10*/     , CMD_NAME("GET_EVENT") },
+	{ (CMD_05_GET_PIPES << OPCODE_OFFSET)			, 1 /**/
+	  , 1      , 2 /*up to 4*/      , CMD_NAME("GET_PIPES") },
+	{ (CMD_06_ALLOCATE_PIPE << OPCODE_OFFSET)		, 1 /**/
+	  , 0      , 0 /**/		    , CMD_NAME("ALLOCATE_PIPE") },
+	{ (CMD_07_RELEASE_PIPE << OPCODE_OFFSET)		, 1 /**/
+	  , 0      , 0 /**/		    , CMD_NAME("RELEASE_PIPE") },
+	{ (CMD_08_ASK_BUFFERS << OPCODE_OFFSET) 		, 1 /**/
+	  , 1      , MAX_STREAM_BUFFER  , CMD_NAME("ASK_BUFFERS") },
+	{ (CMD_09_STOP_PIPE << OPCODE_OFFSET)			, 1 /**/
+	  , 0      , 0 /*up to 2*/      , CMD_NAME("STOP_PIPE") },
+	{ (CMD_0A_GET_PIPE_SPL_COUNT << OPCODE_OFFSET)	        , 1 /**/
+	  , 1      , 1 /*up to 2*/      , CMD_NAME("GET_PIPE_SPL_COUNT") },
+	{ (CMD_0B_TOGGLE_PIPE_STATE << OPCODE_OFFSET)           , 1 /*up to 5*/
+	  , 1      , 0 /**/		    , CMD_NAME("TOGGLE_PIPE_STATE") },
+	{ (CMD_0C_DEF_STREAM << OPCODE_OFFSET)			, 1 /*up to 4*/
+	  , 1      , 0 /**/		    , CMD_NAME("DEF_STREAM") },
+	{ (CMD_0D_SET_MUTE  << OPCODE_OFFSET)			, 3 /**/
+	  , 1      , 0 /**/		    , CMD_NAME("SET_MUTE") },
+	{ (CMD_0E_GET_STREAM_SPL_COUNT << OPCODE_OFFSET)        , 1/**/
+	  , 1      , 2 /**/		    , CMD_NAME("GET_STREAM_SPL_COUNT") },
+	{ (CMD_0F_UPDATE_BUFFER << OPCODE_OFFSET)		, 3 /*up to 4*/
+	  , 0      , 1 /**/		    , CMD_NAME("UPDATE_BUFFER") },
+	{ (CMD_10_GET_BUFFER << OPCODE_OFFSET)			, 1 /**/
+	  , 1      , 4 /**/		    , CMD_NAME("GET_BUFFER") },
+	{ (CMD_11_CANCEL_BUFFER << OPCODE_OFFSET)		, 1 /**/
+	  , 1      , 1 /*up to 4*/      , CMD_NAME("CANCEL_BUFFER") },
+	{ (CMD_12_GET_PEAK << OPCODE_OFFSET)			, 1 /**/
+	  , 1      , 1 /**/		    , CMD_NAME("GET_PEAK") },
+	{ (CMD_13_SET_STREAM_STATE << OPCODE_OFFSET)	        , 1 /**/
+	  , 1      , 0 /**/		    , CMD_NAME("SET_STREAM_STATE") },
+};
+
+static void lx_message_init(struct lx_rmh *rmh, enum cmd_mb_opcodes cmd)
+{
+	snd_BUG_ON(cmd >= CMD_14_INVALID);
+
+	rmh->cmd[0] = dsp_commands[cmd].dcCodeOp;
+	rmh->cmd_len = dsp_commands[cmd].dcCmdLength;
+	rmh->stat_len = dsp_commands[cmd].dcStatusLength;
+	rmh->dsp_stat = dsp_commands[cmd].dcStatusType;
+	rmh->cmd_idx = cmd;
+	memset(&rmh->cmd[1], 0, (REG_CRM_NUMBER - 1) * sizeof(u32));
+
+#ifdef CONFIG_SND_DEBUG
+	memset(rmh->stat, 0, REG_CRM_NUMBER * sizeof(u32));
+#endif
+#ifdef RMH_DEBUG
+	rmh->cmd_idx = cmd;
+#endif
+}
+
+#ifdef RMH_DEBUG
+#define LXRMH "lx6464es rmh: "
+static void lx_message_dump(struct lx_rmh *rmh)
+{
+	u8 idx = rmh->cmd_idx;
+	int i;
+
+	snd_printk(LXRMH "command %s\n", dsp_commands[idx].dcOpName);
+
+	for (i = 0; i != rmh->cmd_len; ++i)
+		snd_printk(LXRMH "\tcmd[%d] %08x\n", i, rmh->cmd[i]);
+
+	for (i = 0; i != rmh->stat_len; ++i)
+		snd_printk(LXRMH "\tstat[%d]: %08x\n", i, rmh->stat[i]);
+	snd_printk("\n");
+}
+#else
+static inline void lx_message_dump(struct lx_rmh *rmh)
+{}
+#endif
+
+
+
+/* sleep 500 - 100 = 400 times 100us -> the timeout is >= 40 ms */
+#define XILINX_TIMEOUT_MS       40
+#define XILINX_POLL_NO_SLEEP    100
+#define XILINX_POLL_ITERATIONS  150
+
+
+static int lx_message_send_atomic(struct lx6464es *chip, struct lx_rmh *rmh)
+{
+	u32 reg = ED_DSP_TIMED_OUT;
+	int dwloop;
+
+	if (lx_dsp_reg_read(chip, eReg_CSM) & (Reg_CSM_MC | Reg_CSM_MR)) {
+		snd_printk(KERN_ERR LXP "PIOSendMessage eReg_CSM %x\n", reg);
+		return -EBUSY;
+	}
+
+	/* write command */
+	lx_dsp_reg_writebuf(chip, eReg_CRM1, rmh->cmd, rmh->cmd_len);
+
+	/* MicoBlaze gogogo */
+	lx_dsp_reg_write(chip, eReg_CSM, Reg_CSM_MC);
+
+	/* wait for device to answer */
+	for (dwloop = 0; dwloop != XILINX_TIMEOUT_MS * 1000; ++dwloop) {
+		if (lx_dsp_reg_read(chip, eReg_CSM) & Reg_CSM_MR) {
+			if (rmh->dsp_stat == 0)
+				reg = lx_dsp_reg_read(chip, eReg_CRM1);
+			else
+				reg = 0;
+			goto polling_successful;
+		} else
+			udelay(1);
+	}
+	snd_printk(KERN_WARNING LXP "TIMEOUT lx_message_send_atomic! "
+		   "polling failed\n");
+
+polling_successful:
+	if ((reg & ERROR_VALUE) == 0) {
+		/* read response */
+		if (rmh->stat_len) {
+			snd_BUG_ON(rmh->stat_len >= (REG_CRM_NUMBER-1));
+			lx_dsp_reg_readbuf(chip, eReg_CRM2, rmh->stat,
+					   rmh->stat_len);
+		}
+	} else
+		snd_printk(LXP "rmh error: %08x\n", reg);
+
+	/* clear Reg_CSM_MR */
+	lx_dsp_reg_write(chip, eReg_CSM, 0);
+
+	switch (reg) {
+	case ED_DSP_TIMED_OUT:
+		snd_printk(KERN_WARNING LXP "lx_message_send: dsp timeout\n");
+		return -ETIMEDOUT;
+
+	case ED_DSP_CRASHED:
+		snd_printk(KERN_WARNING LXP "lx_message_send: dsp crashed\n");
+		return -EAGAIN;
+	}
+
+	lx_message_dump(rmh);
+
+	return reg;
+}
+
+
+/* low-level dsp access */
+int __devinit lx_dsp_get_version(struct lx6464es *chip, u32 *rdsp_version)
+{
+	u16 ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+
+	lx_message_init(&chip->rmh, CMD_01_GET_SYS_CFG);
+	ret = lx_message_send_atomic(chip, &chip->rmh);
+
+	*rdsp_version = chip->rmh.stat[1];
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return ret;
+}
+
+int lx_dsp_get_clock_frequency(struct lx6464es *chip, u32 *rfreq)
+{
+	u16 ret = 0;
+	unsigned long flags;
+	u32 freq_raw = 0;
+	u32 freq = 0;
+	u32 frequency = 0;
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+
+	lx_message_init(&chip->rmh, CMD_01_GET_SYS_CFG);
+	ret = lx_message_send_atomic(chip, &chip->rmh);
+
+	if (ret == 0) {
+		freq_raw = chip->rmh.stat[0] >> FREQ_FIELD_OFFSET;
+		freq = freq_raw & XES_FREQ_COUNT8_MASK;
+
+		if ((freq < XES_FREQ_COUNT8_48_MAX) ||
+		    (freq > XES_FREQ_COUNT8_44_MIN))
+			frequency = 0; /* unknown */
+		else if (freq >= XES_FREQ_COUNT8_44_MAX)
+			frequency = 44100;
+		else
+			frequency = 48000;
+	}
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+
+	*rfreq = frequency * chip->freq_ratio;
+
+	return ret;
+}
+
+int lx_dsp_get_mac(struct lx6464es *chip, u8 *mac_address)
+{
+	u32 macmsb, maclsb;
+
+	macmsb = lx_dsp_reg_read(chip, eReg_ADMACESMSB) & 0x00FFFFFF;
+	maclsb = lx_dsp_reg_read(chip, eReg_ADMACESLSB) & 0x00FFFFFF;
+
+	/* todo: endianess handling */
+	mac_address[5] = ((u8 *)(&maclsb))[0];
+	mac_address[4] = ((u8 *)(&maclsb))[1];
+	mac_address[3] = ((u8 *)(&maclsb))[2];
+	mac_address[2] = ((u8 *)(&macmsb))[0];
+	mac_address[1] = ((u8 *)(&macmsb))[1];
+	mac_address[0] = ((u8 *)(&macmsb))[2];
+
+	return 0;
+}
+
+
+int lx_dsp_set_granularity(struct lx6464es *chip, u32 gran)
+{
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+
+	lx_message_init(&chip->rmh, CMD_02_SET_GRANULARITY);
+	chip->rmh.cmd[0] |= gran;
+
+	ret = lx_message_send_atomic(chip, &chip->rmh);
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return ret;
+}
+
+int lx_dsp_read_async_events(struct lx6464es *chip, u32 *data)
+{
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+
+	lx_message_init(&chip->rmh, CMD_04_GET_EVENT);
+	chip->rmh.stat_len = 9;	/* we don't necessarily need the full length */
+
+	ret = lx_message_send_atomic(chip, &chip->rmh);
+
+	if (!ret)
+		memcpy(data, chip->rmh.stat, chip->rmh.stat_len * sizeof(u32));
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return ret;
+}
+
+#define CSES_TIMEOUT        100     /* microseconds */
+#define CSES_CE             0x0001
+#define CSES_BROADCAST      0x0002
+#define CSES_UPDATE_LDSV    0x0004
+
+int lx_dsp_es_check_pipeline(struct lx6464es *chip)
+{
+	int i;
+
+	for (i = 0; i != CSES_TIMEOUT; ++i) {
+		/*
+		 * le bit CSES_UPDATE_LDSV est à 1 dés que le macprog
+		 * est pret. il re-passe à 0 lorsque le premier read a
+		 * été fait. pour l'instant on retire le test car ce bit
+		 * passe a 1 environ 200 à 400 ms aprés que le registre
+		 * confES à été écrit (kick du xilinx ES).
+		 *
+		 * On ne teste que le bit CE.
+		 * */
+
+		u32 cses = lx_dsp_reg_read(chip, eReg_CSES);
+
+		if ((cses & CSES_CE) == 0)
+			return 0;
+
+		udelay(1);
+	}
+
+	return -ETIMEDOUT;
+}
+
+
+#define PIPE_INFO_TO_CMD(capture, pipe)					\
+	((u32)((u32)(pipe) | ((capture) ? ID_IS_CAPTURE : 0L)) << ID_OFFSET)
+
+
+
+/* low-level pipe handling */
+int lx_pipe_allocate(struct lx6464es *chip, u32 pipe, int is_capture,
+		     int channels)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_06_ALLOCATE_PIPE);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+	chip->rmh.cmd[0] |= channels;
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+
+	if (err != 0)
+		snd_printk(KERN_ERR "lx6464es: could not allocate pipe\n");
+
+	return err;
+}
+
+int lx_pipe_release(struct lx6464es *chip, u32 pipe, int is_capture)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_07_RELEASE_PIPE);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+
+	return err;
+}
+
+int lx_buffer_ask(struct lx6464es *chip, u32 pipe, int is_capture,
+		  u32 *r_needed, u32 *r_freed, u32 *size_array)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+#ifdef CONFIG_SND_DEBUG
+	if (size_array)
+		memset(size_array, 0, sizeof(u32)*MAX_STREAM_BUFFER);
+#endif
+
+	*r_needed = 0;
+	*r_freed = 0;
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_08_ASK_BUFFERS);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+
+	if (!err) {
+		int i;
+		for (i = 0; i < MAX_STREAM_BUFFER; ++i) {
+			u32 stat = chip->rmh.stat[i];
+			if (stat & (BF_EOB << BUFF_FLAGS_OFFSET)) {
+				/* finished */
+				*r_freed += 1;
+				if (size_array)
+					size_array[i] = stat & MASK_DATA_SIZE;
+			} else if ((stat & (BF_VALID << BUFF_FLAGS_OFFSET))
+				   == 0)
+				/* free */
+				*r_needed += 1;
+		}
+
+#if 0
+		snd_printdd(LXP "CMD_08_ASK_BUFFERS: needed %d, freed %d\n",
+			    *r_needed, *r_freed);
+		for (i = 0; i < MAX_STREAM_BUFFER; ++i) {
+			for (i = 0; i != chip->rmh.stat_len; ++i)
+				snd_printdd("  stat[%d]: %x, %x\n", i,
+					    chip->rmh.stat[i],
+					    chip->rmh.stat[i] & MASK_DATA_SIZE);
+		}
+#endif
+	}
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+
+int lx_pipe_stop(struct lx6464es *chip, u32 pipe, int is_capture)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_09_STOP_PIPE);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+static int lx_pipe_toggle_state(struct lx6464es *chip, u32 pipe, int is_capture)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_0B_TOGGLE_PIPE_STATE);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+
+int lx_pipe_start(struct lx6464es *chip, u32 pipe, int is_capture)
+{
+	int err;
+
+	err = lx_pipe_wait_for_idle(chip, pipe, is_capture);
+	if (err < 0)
+		return err;
+
+	err = lx_pipe_toggle_state(chip, pipe, is_capture);
+
+	return err;
+}
+
+int lx_pipe_pause(struct lx6464es *chip, u32 pipe, int is_capture)
+{
+	int err = 0;
+
+	err = lx_pipe_wait_for_start(chip, pipe, is_capture);
+	if (err < 0)
+		return err;
+
+	err = lx_pipe_toggle_state(chip, pipe, is_capture);
+
+	return err;
+}
+
+
+int lx_pipe_sample_count(struct lx6464es *chip, u32 pipe, int is_capture,
+			 u64 *rsample_count)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_0A_GET_PIPE_SPL_COUNT);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+	chip->rmh.stat_len = 2;	/* need all words here! */
+
+	err = lx_message_send_atomic(chip, &chip->rmh); /* don't sleep! */
+
+	if (err != 0)
+		snd_printk(KERN_ERR
+			   "lx6464es: could not query pipe's sample count\n");
+	else {
+		*rsample_count = ((u64)(chip->rmh.stat[0] & MASK_SPL_COUNT_HI)
+				  << 24)     /* hi part */
+			+ chip->rmh.stat[1]; /* lo part */
+	}
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+int lx_pipe_state(struct lx6464es *chip, u32 pipe, int is_capture, u16 *rstate)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_0A_GET_PIPE_SPL_COUNT);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+
+	if (err != 0)
+		snd_printk(KERN_ERR "lx6464es: could not query pipe's state\n");
+	else
+		*rstate = (chip->rmh.stat[0] >> PSTATE_OFFSET) & 0x0F;
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+static int lx_pipe_wait_for_state(struct lx6464es *chip, u32 pipe,
+				  int is_capture, u16 state)
+{
+	int i;
+
+	/* max 2*PCMOnlyGranularity = 2*1024 at 44100 = < 50 ms:
+	 * timeout 50 ms */
+	for (i = 0; i != 50; ++i) {
+		u16 current_state;
+		int err = lx_pipe_state(chip, pipe, is_capture, &current_state);
+
+		if (err < 0)
+			return err;
+
+		if (current_state == state)
+			return 0;
+
+		mdelay(1);
+	}
+
+	return -ETIMEDOUT;
+}
+
+int lx_pipe_wait_for_start(struct lx6464es *chip, u32 pipe, int is_capture)
+{
+	return lx_pipe_wait_for_state(chip, pipe, is_capture, PSTATE_RUN);
+}
+
+int lx_pipe_wait_for_idle(struct lx6464es *chip, u32 pipe, int is_capture)
+{
+	return lx_pipe_wait_for_state(chip, pipe, is_capture, PSTATE_IDLE);
+}
+
+/* low-level stream handling */
+int lx_stream_set_state(struct lx6464es *chip, u32 pipe,
+			       int is_capture, enum stream_state_t state)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_13_SET_STREAM_STATE);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+	chip->rmh.cmd[0] |= state;
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+
+	return err;
+}
+
+int lx_stream_set_format(struct lx6464es *chip, struct snd_pcm_runtime *runtime,
+			 u32 pipe, int is_capture)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	u32 channels = runtime->channels;
+
+	if (runtime->channels != channels)
+		snd_printk(KERN_ERR LXP "channel count mismatch: %d vs %d",
+			   runtime->channels, channels);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_0C_DEF_STREAM);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+
+	if (runtime->sample_bits == 16)
+		/* 16 bit format */
+		chip->rmh.cmd[0] |= (STREAM_FMT_16b << STREAM_FMT_OFFSET);
+
+	if (snd_pcm_format_little_endian(runtime->format))
+		/* little endian/intel format */
+		chip->rmh.cmd[0] |= (STREAM_FMT_intel << STREAM_FMT_OFFSET);
+
+	chip->rmh.cmd[0] |= channels-1;
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+
+	return err;
+}
+
+int lx_stream_state(struct lx6464es *chip, u32 pipe, int is_capture,
+		    int *rstate)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_0E_GET_STREAM_SPL_COUNT);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+
+	*rstate = (chip->rmh.stat[0] & SF_START) ? START_STATE : PAUSE_STATE;
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+int lx_stream_sample_position(struct lx6464es *chip, u32 pipe, int is_capture,
+			      u64 *r_bytepos)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_0E_GET_STREAM_SPL_COUNT);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+
+	*r_bytepos = ((u64) (chip->rmh.stat[0] & MASK_SPL_COUNT_HI)
+		      << 32)	     /* hi part */
+		+ chip->rmh.stat[1]; /* lo part */
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+/* low-level buffer handling */
+int lx_buffer_give(struct lx6464es *chip, u32 pipe, int is_capture,
+		   u32 buffer_size, u32 buf_address_lo, u32 buf_address_hi,
+		   u32 *r_buffer_index)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_0F_UPDATE_BUFFER);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+	chip->rmh.cmd[0] |= BF_NOTIFY_EOB; /* request interrupt notification */
+
+	/* todo: pause request, circular buffer */
+
+	chip->rmh.cmd[1] = buffer_size & MASK_DATA_SIZE;
+	chip->rmh.cmd[2] = buf_address_lo;
+
+	if (buf_address_hi) {
+		chip->rmh.cmd_len = 4;
+		chip->rmh.cmd[3] = buf_address_hi;
+		chip->rmh.cmd[0] |= BF_64BITS_ADR;
+	}
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+
+	if (err == 0) {
+		*r_buffer_index = chip->rmh.stat[0];
+		goto done;
+	}
+
+	if (err == EB_RBUFFERS_TABLE_OVERFLOW)
+		snd_printk(LXP "lx_buffer_give EB_RBUFFERS_TABLE_OVERFLOW\n");
+
+	if (err == EB_INVALID_STREAM)
+		snd_printk(LXP "lx_buffer_give EB_INVALID_STREAM\n");
+
+	if (err == EB_CMD_REFUSED)
+		snd_printk(LXP "lx_buffer_give EB_CMD_REFUSED\n");
+
+ done:
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+int lx_buffer_free(struct lx6464es *chip, u32 pipe, int is_capture,
+		   u32 *r_buffer_size)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_11_CANCEL_BUFFER);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+	chip->rmh.cmd[0] |= MASK_BUFFER_ID; /* ask for the current buffer: the
+					     * microblaze will seek for it */
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+
+	if (err == 0)
+		*r_buffer_size = chip->rmh.stat[0]  & MASK_DATA_SIZE;
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+int lx_buffer_cancel(struct lx6464es *chip, u32 pipe, int is_capture,
+		     u32 buffer_index)
+{
+	int err;
+	unsigned long flags;
+
+	u32 pipe_cmd = PIPE_INFO_TO_CMD(is_capture, pipe);
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_11_CANCEL_BUFFER);
+
+	chip->rmh.cmd[0] |= pipe_cmd;
+	chip->rmh.cmd[0] |= buffer_index;
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+
+/* low-level gain/peak handling
+ *
+ * \todo: can we unmute capture/playback channels independently?
+ *
+ * */
+int lx_level_unmute(struct lx6464es *chip, int is_capture, int unmute)
+{
+	int err;
+	unsigned long flags;
+
+	/* bit set to 1: channel muted */
+	u64 mute_mask = unmute ? 0 : 0xFFFFFFFFFFFFFFFFLLU;
+
+	spin_lock_irqsave(&chip->msg_lock, flags);
+	lx_message_init(&chip->rmh, CMD_0D_SET_MUTE);
+
+	chip->rmh.cmd[0] |= PIPE_INFO_TO_CMD(is_capture, 0);
+
+	chip->rmh.cmd[1] = (u32)(mute_mask >> (u64)32);	       /* hi part */
+	chip->rmh.cmd[2] = (u32)(mute_mask & (u64)0xFFFFFFFF); /* lo part */
+
+	snd_printk("mute %x %x %x\n", chip->rmh.cmd[0], chip->rmh.cmd[1],
+		   chip->rmh.cmd[2]);
+
+	err = lx_message_send_atomic(chip, &chip->rmh);
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+static u32 peak_map[] = {
+	0x00000109, /* -90.308dB */
+	0x0000083B, /* -72.247dB */
+	0x000020C4, /* -60.205dB */
+	0x00008273, /* -48.030dB */
+	0x00020756, /* -36.005dB */
+	0x00040C37, /* -30.001dB */
+	0x00081385, /* -24.002dB */
+	0x00101D3F, /* -18.000dB */
+	0x0016C310, /* -15.000dB */
+	0x002026F2, /* -12.001dB */
+	0x002D6A86, /* -9.000dB */
+	0x004026E6, /* -6.004dB */
+	0x005A9DF6, /* -3.000dB */
+	0x0065AC8B, /* -2.000dB */
+	0x00721481, /* -1.000dB */
+	0x007FFFFF, /* FS */
+};
+
+int lx_level_peaks(struct lx6464es *chip, int is_capture, int channels,
+		   u32 *r_levels)
+{
+	int err = 0;
+	unsigned long flags;
+	int i;
+	spin_lock_irqsave(&chip->msg_lock, flags);
+
+	for (i = 0; i < channels; i += 4) {
+		u32 s0, s1, s2, s3;
+
+		lx_message_init(&chip->rmh, CMD_12_GET_PEAK);
+		chip->rmh.cmd[0] |= PIPE_INFO_TO_CMD(is_capture, i);
+
+		err = lx_message_send_atomic(chip, &chip->rmh);
+
+		if (err == 0) {
+			s0 = peak_map[chip->rmh.stat[0] & 0x0F];
+			s1 = peak_map[(chip->rmh.stat[0] >>  4) & 0xf];
+			s2 = peak_map[(chip->rmh.stat[0] >>  8) & 0xf];
+			s3 = peak_map[(chip->rmh.stat[0] >>  12) & 0xf];
+		} else
+			s0 = s1 = s2 = s3 = 0;
+
+		r_levels[0] = s0;
+		r_levels[1] = s1;
+		r_levels[2] = s2;
+		r_levels[3] = s3;
+
+		r_levels += 4;
+	}
+
+	spin_unlock_irqrestore(&chip->msg_lock, flags);
+	return err;
+}
+
+/* interrupt handling */
+#define PCX_IRQ_NONE 0
+#define IRQCS_ACTIVE_PCIDB  0x00002000L         /* Bit nø 13 */
+#define IRQCS_ENABLE_PCIIRQ 0x00000100L         /* Bit nø 08 */
+#define IRQCS_ENABLE_PCIDB  0x00000200L         /* Bit nø 09 */
+
+static u32 lx_interrupt_test_ack(struct lx6464es *chip)
+{
+	u32 irqcs = lx_plx_reg_read(chip, ePLX_IRQCS);
+
+	/* Test if PCI Doorbell interrupt is active */
+	if (irqcs & IRQCS_ACTIVE_PCIDB)	{
+		u32 temp;
+		irqcs = PCX_IRQ_NONE;
+
+		while ((temp = lx_plx_reg_read(chip, ePLX_L2PCIDB))) {
+			/* RAZ interrupt */
+			irqcs |= temp;
+			lx_plx_reg_write(chip, ePLX_L2PCIDB, temp);
+		}
+
+		return irqcs;
+	}
+	return PCX_IRQ_NONE;
+}
+
+static int lx_interrupt_ack(struct lx6464es *chip, u32 *r_irqsrc,
+			    int *r_async_pending, int *r_async_escmd)
+{
+	u32 irq_async;
+	u32 irqsrc = lx_interrupt_test_ack(chip);
+
+	if (irqsrc == PCX_IRQ_NONE)
+		return 0;
+
+	*r_irqsrc = irqsrc;
+
+	irq_async = irqsrc & MASK_SYS_ASYNC_EVENTS; /* + EtherSound response
+						     * (set by xilinx) + EOB */
+
+	if (irq_async & MASK_SYS_STATUS_ESA) {
+		irq_async &= ~MASK_SYS_STATUS_ESA;
+		*r_async_escmd = 1;
+	}
+
+	if (irq_async) {
+		/* snd_printd("interrupt: async event pending\n"); */
+		*r_async_pending = 1;
+	}
+
+	return 1;
+}
+
+static int lx_interrupt_handle_async_events(struct lx6464es *chip, u32 irqsrc,
+					    int *r_freq_changed,
+					    u64 *r_notified_in_pipe_mask,
+					    u64 *r_notified_out_pipe_mask)
+{
+	int err;
+	u32 stat[9];		/* answer from CMD_04_GET_EVENT */
+
+	/* On peut optimiser pour ne pas lire les evenements vides
+	 * les mots de rÃĩponse sont dans l'ordre suivant :
+	 * Stat[0]	mot de status gÃĩnÃĩral
+	 * Stat[1]	fin de buffer OUT pF
+	 * Stat[2]	fin de buffer OUT pf
+	 * Stat[3]	fin de buffer IN pF
+	 * Stat[4]	fin de buffer IN pf
+	 * Stat[5]	underrun poid fort
+	 * Stat[6]	underrun poid faible
+	 * Stat[7]	overrun poid fort
+	 * Stat[8]	overrun poid faible
+	 * */
+
+	u64 orun_mask;
+	u64 urun_mask;
+#if 0
+	int has_underrun   = (irqsrc & MASK_SYS_STATUS_URUN) ? 1 : 0;
+	int has_overrun    = (irqsrc & MASK_SYS_STATUS_ORUN) ? 1 : 0;
+#endif
+	int eb_pending_out = (irqsrc & MASK_SYS_STATUS_EOBO) ? 1 : 0;
+	int eb_pending_in  = (irqsrc & MASK_SYS_STATUS_EOBI) ? 1 : 0;
+
+	*r_freq_changed = (irqsrc & MASK_SYS_STATUS_FREQ) ? 1 : 0;
+
+	err = lx_dsp_read_async_events(chip, stat);
+	if (err < 0)
+		return err;
+
+	if (eb_pending_in) {
+		*r_notified_in_pipe_mask = ((u64)stat[3] << 32)
+			+ stat[4];
+		snd_printdd(LXP "interrupt: EOBI pending %llx\n",
+			    *r_notified_in_pipe_mask);
+	}
+	if (eb_pending_out) {
+		*r_notified_out_pipe_mask = ((u64)stat[1] << 32)
+			+ stat[2];
+		snd_printdd(LXP "interrupt: EOBO pending %llx\n",
+			    *r_notified_out_pipe_mask);
+	}
+
+	orun_mask = ((u64)stat[7] << 32) + stat[8];
+	urun_mask = ((u64)stat[5] << 32) + stat[6];
+
+	/* todo: handle xrun notification */
+
+	return err;
+}
+
+static int lx_interrupt_request_new_buffer(struct lx6464es *chip,
+					   struct lx_stream *lx_stream)
+{
+	struct snd_pcm_substream *substream = lx_stream->stream;
+	const unsigned int is_capture = lx_stream->is_capture;
+	int err;
+	unsigned long flags;
+
+	const u32 channels = substream->runtime->channels;
+	const u32 bytes_per_frame = channels * 3;
+	const u32 period_size = substream->runtime->period_size;
+	const u32 period_bytes = period_size * bytes_per_frame;
+	const u32 pos = lx_stream->frame_pos;
+	const u32 next_pos = ((pos+1) == substream->runtime->periods) ?
+		0 : pos + 1;
+
+	dma_addr_t buf = substream->dma_buffer.addr + pos * period_bytes;
+	u32 buf_hi = 0;
+	u32 buf_lo = 0;
+	u32 buffer_index = 0;
+
+	u32 needed, freed;
+	u32 size_array[MAX_STREAM_BUFFER];
+
+	snd_printdd("->lx_interrupt_request_new_buffer\n");
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	err = lx_buffer_ask(chip, 0, is_capture, &needed, &freed, size_array);
+	snd_printdd(LXP "interrupt: needed %d, freed %d\n", needed, freed);
+
+	unpack_pointer(buf, &buf_lo, &buf_hi);
+	err = lx_buffer_give(chip, 0, is_capture, period_bytes, buf_lo, buf_hi,
+			     &buffer_index);
+	snd_printdd(LXP "interrupt: gave buffer index %x on %p (%d bytes)\n",
+		    buffer_index, (void *)buf, period_bytes);
+
+	lx_stream->frame_pos = next_pos;
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return err;
+}
+
+void lx_tasklet_playback(unsigned long data)
+{
+	struct lx6464es *chip = (struct lx6464es *)data;
+	struct lx_stream *lx_stream = &chip->playback_stream;
+	int err;
+
+	snd_printdd("->lx_tasklet_playback\n");
+
+	err = lx_interrupt_request_new_buffer(chip, lx_stream);
+	if (err < 0)
+		snd_printk(KERN_ERR LXP
+			   "cannot request new buffer for playback\n");
+
+	snd_pcm_period_elapsed(lx_stream->stream);
+}
+
+void lx_tasklet_capture(unsigned long data)
+{
+	struct lx6464es *chip = (struct lx6464es *)data;
+	struct lx_stream *lx_stream = &chip->capture_stream;
+	int err;
+
+	snd_printdd("->lx_tasklet_capture\n");
+	err = lx_interrupt_request_new_buffer(chip, lx_stream);
+	if (err < 0)
+		snd_printk(KERN_ERR LXP
+			   "cannot request new buffer for capture\n");
+
+	snd_pcm_period_elapsed(lx_stream->stream);
+}
+
+
+
+static int lx_interrupt_handle_audio_transfer(struct lx6464es *chip,
+					      u64 notified_in_pipe_mask,
+					      u64 notified_out_pipe_mask)
+{
+	int err = 0;
+
+	if (notified_in_pipe_mask) {
+		snd_printdd(LXP "requesting audio transfer for capture\n");
+		tasklet_hi_schedule(&chip->tasklet_capture);
+	}
+
+	if (notified_out_pipe_mask) {
+		snd_printdd(LXP "requesting audio transfer for playback\n");
+		tasklet_hi_schedule(&chip->tasklet_playback);
+	}
+
+	return err;
+}
+
+
+irqreturn_t lx_interrupt(int irq, void *dev_id)
+{
+	struct lx6464es *chip = dev_id;
+	int async_pending, async_escmd;
+	u32 irqsrc;
+
+	spin_lock(&chip->lock);
+
+	snd_printdd("**************************************************\n");
+
+	if (!lx_interrupt_ack(chip, &irqsrc, &async_pending, &async_escmd)) {
+		spin_unlock(&chip->lock);
+		snd_printdd("IRQ_NONE\n");
+		return IRQ_NONE; /* this device did not cause the interrupt */
+	}
+
+	if (irqsrc & MASK_SYS_STATUS_CMD_DONE)
+		goto exit;
+
+#if 0
+	if (irqsrc & MASK_SYS_STATUS_EOBI)
+		snd_printdd(LXP "interrupt: EOBI\n");
+
+	if (irqsrc & MASK_SYS_STATUS_EOBO)
+		snd_printdd(LXP "interrupt: EOBO\n");
+
+	if (irqsrc & MASK_SYS_STATUS_URUN)
+		snd_printdd(LXP "interrupt: URUN\n");
+
+	if (irqsrc & MASK_SYS_STATUS_ORUN)
+		snd_printdd(LXP "interrupt: ORUN\n");
+#endif
+
+	if (async_pending) {
+		u64 notified_in_pipe_mask = 0;
+		u64 notified_out_pipe_mask = 0;
+		int freq_changed;
+		int err;
+
+		/* handle async events */
+		err = lx_interrupt_handle_async_events(chip, irqsrc,
+						       &freq_changed,
+						       &notified_in_pipe_mask,
+						       &notified_out_pipe_mask);
+		if (err)
+			snd_printk(KERN_ERR LXP
+				   "error handling async events\n");
+
+		err = lx_interrupt_handle_audio_transfer(chip,
+							 notified_in_pipe_mask,
+							 notified_out_pipe_mask
+			);
+		if (err)
+			snd_printk(KERN_ERR LXP
+				   "error during audio transfer\n");
+	}
+
+	if (async_escmd) {
+#if 0
+		/* backdoor for ethersound commands
+		 *
+		 * for now, we do not need this
+		 *
+		 * */
+
+		snd_printdd("lx6464es: interrupt requests escmd handling\n");
+#endif
+	}
+
+exit:
+	spin_unlock(&chip->lock);
+	return IRQ_HANDLED;	/* this device caused the interrupt */
+}
+
+
+static void lx_irq_set(struct lx6464es *chip, int enable)
+{
+	u32 reg = lx_plx_reg_read(chip, ePLX_IRQCS);
+
+	/* enable/disable interrupts
+	 *
+	 * Set the Doorbell and PCI interrupt enable bits
+	 *
+	 * */
+	if (enable)
+		reg |=  (IRQCS_ENABLE_PCIIRQ | IRQCS_ENABLE_PCIDB);
+	else
+		reg &= ~(IRQCS_ENABLE_PCIIRQ | IRQCS_ENABLE_PCIDB);
+	lx_plx_reg_write(chip, ePLX_IRQCS, reg);
+}
+
+void lx_irq_enable(struct lx6464es *chip)
+{
+	snd_printdd("->lx_irq_enable\n");
+	lx_irq_set(chip, 1);
+}
+
+void lx_irq_disable(struct lx6464es *chip)
+{
+	snd_printdd("->lx_irq_disable\n");
+	lx_irq_set(chip, 0);
+}
diff --git a/linux/sound/pci/lx6464es/lx_core.h b/linux/sound/pci/lx6464es/lx_core.h
new file mode 100644
index 0000000..6bd9cbb
--- /dev/null
+++ b/linux/sound/pci/lx6464es/lx_core.h
@@ -0,0 +1,242 @@
+/* -*- linux-c -*- *
+ *
+ * ALSA driver for the digigram lx6464es interface
+ * low-level interface
+ *
+ * Copyright (c) 2009 Tim Blechmann <tim@klingt.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; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef LX_CORE_H
+#define LX_CORE_H
+
+#include <linux/interrupt.h>
+
+#include "lx_defs.h"
+
+#define REG_CRM_NUMBER		12
+
+struct lx6464es;
+
+/* low-level register access */
+
+/* dsp register access */
+enum {
+	eReg_BASE,
+	eReg_CSM,
+	eReg_CRM1,
+	eReg_CRM2,
+	eReg_CRM3,
+	eReg_CRM4,
+	eReg_CRM5,
+	eReg_CRM6,
+	eReg_CRM7,
+	eReg_CRM8,
+	eReg_CRM9,
+	eReg_CRM10,
+	eReg_CRM11,
+	eReg_CRM12,
+
+	eReg_ICR,
+	eReg_CVR,
+	eReg_ISR,
+	eReg_RXHTXH,
+	eReg_RXMTXM,
+	eReg_RHLTXL,
+	eReg_RESETDSP,
+
+	eReg_CSUF,
+	eReg_CSES,
+	eReg_CRESMSB,
+	eReg_CRESLSB,
+	eReg_ADMACESMSB,
+	eReg_ADMACESLSB,
+	eReg_CONFES,
+
+	eMaxPortLx
+};
+
+unsigned long lx_dsp_reg_read(struct lx6464es *chip, int port);
+void lx_dsp_reg_readbuf(struct lx6464es *chip, int port, u32 *data, u32 len);
+void lx_dsp_reg_write(struct lx6464es *chip, int port, unsigned data);
+void lx_dsp_reg_writebuf(struct lx6464es *chip, int port, const u32 *data,
+			 u32 len);
+
+/* plx register access */
+enum {
+    ePLX_PCICR,
+
+    ePLX_MBOX0,
+    ePLX_MBOX1,
+    ePLX_MBOX2,
+    ePLX_MBOX3,
+    ePLX_MBOX4,
+    ePLX_MBOX5,
+    ePLX_MBOX6,
+    ePLX_MBOX7,
+
+    ePLX_L2PCIDB,
+    ePLX_IRQCS,
+    ePLX_CHIPSC,
+
+    eMaxPort
+};
+
+unsigned long lx_plx_reg_read(struct lx6464es *chip, int port);
+void lx_plx_reg_write(struct lx6464es *chip, int port, u32 data);
+
+/* rhm */
+struct lx_rmh {
+	u16	cmd_len;	/* length of the command to send (WORDs) */
+	u16	stat_len;	/* length of the status received (WORDs) */
+	u16	dsp_stat;	/* status type, RMP_SSIZE_XXX */
+	u16	cmd_idx;	/* index of the command */
+	u32	cmd[REG_CRM_NUMBER];
+	u32	stat[REG_CRM_NUMBER];
+};
+
+
+/* low-level dsp access */
+int __devinit lx_dsp_get_version(struct lx6464es *chip, u32 *rdsp_version);
+int lx_dsp_get_clock_frequency(struct lx6464es *chip, u32 *rfreq);
+int lx_dsp_set_granularity(struct lx6464es *chip, u32 gran);
+int lx_dsp_read_async_events(struct lx6464es *chip, u32 *data);
+int lx_dsp_get_mac(struct lx6464es *chip, u8 *mac_address);
+
+
+/* low-level pipe handling */
+int lx_pipe_allocate(struct lx6464es *chip, u32 pipe, int is_capture,
+		     int channels);
+int lx_pipe_release(struct lx6464es *chip, u32 pipe, int is_capture);
+int lx_pipe_sample_count(struct lx6464es *chip, u32 pipe, int is_capture,
+			 u64 *rsample_count);
+int lx_pipe_state(struct lx6464es *chip, u32 pipe, int is_capture, u16 *rstate);
+int lx_pipe_stop(struct lx6464es *chip, u32 pipe, int is_capture);
+int lx_pipe_start(struct lx6464es *chip, u32 pipe, int is_capture);
+int lx_pipe_pause(struct lx6464es *chip, u32 pipe, int is_capture);
+
+int lx_pipe_wait_for_start(struct lx6464es *chip, u32 pipe, int is_capture);
+int lx_pipe_wait_for_idle(struct lx6464es *chip, u32 pipe, int is_capture);
+
+/* low-level stream handling */
+int lx_stream_set_format(struct lx6464es *chip, struct snd_pcm_runtime *runtime,
+			 u32 pipe, int is_capture);
+int lx_stream_state(struct lx6464es *chip, u32 pipe, int is_capture,
+		    int *rstate);
+int lx_stream_sample_position(struct lx6464es *chip, u32 pipe, int is_capture,
+			      u64 *r_bytepos);
+
+int lx_stream_set_state(struct lx6464es *chip, u32 pipe,
+			int is_capture, enum stream_state_t state);
+
+static inline int lx_stream_start(struct lx6464es *chip, u32 pipe,
+				  int is_capture)
+{
+	snd_printdd("->lx_stream_start\n");
+	return lx_stream_set_state(chip, pipe, is_capture, SSTATE_RUN);
+}
+
+static inline int lx_stream_pause(struct lx6464es *chip, u32 pipe,
+				  int is_capture)
+{
+	snd_printdd("->lx_stream_pause\n");
+	return lx_stream_set_state(chip, pipe, is_capture, SSTATE_PAUSE);
+}
+
+static inline int lx_stream_stop(struct lx6464es *chip, u32 pipe,
+				 int is_capture)
+{
+	snd_printdd("->lx_stream_stop\n");
+	return lx_stream_set_state(chip, pipe, is_capture, SSTATE_STOP);
+}
+
+/* low-level buffer handling */
+int lx_buffer_ask(struct lx6464es *chip, u32 pipe, int is_capture,
+		  u32 *r_needed, u32 *r_freed, u32 *size_array);
+int lx_buffer_give(struct lx6464es *chip, u32 pipe, int is_capture,
+		   u32 buffer_size, u32 buf_address_lo, u32 buf_address_hi,
+		   u32 *r_buffer_index);
+int lx_buffer_free(struct lx6464es *chip, u32 pipe, int is_capture,
+		   u32 *r_buffer_size);
+int lx_buffer_cancel(struct lx6464es *chip, u32 pipe, int is_capture,
+		     u32 buffer_index);
+
+/* low-level gain/peak handling */
+int lx_level_unmute(struct lx6464es *chip, int is_capture, int unmute);
+int lx_level_peaks(struct lx6464es *chip, int is_capture, int channels,
+		   u32 *r_levels);
+
+
+/* interrupt handling */
+irqreturn_t lx_interrupt(int irq, void *dev_id);
+void lx_irq_enable(struct lx6464es *chip);
+void lx_irq_disable(struct lx6464es *chip);
+
+void lx_tasklet_capture(unsigned long data);
+void lx_tasklet_playback(unsigned long data);
+
+
+/* Stream Format Header Defines (for LIN and IEEE754) */
+#define HEADER_FMT_BASE		HEADER_FMT_BASE_LIN
+#define HEADER_FMT_BASE_LIN	0xFED00000
+#define HEADER_FMT_BASE_FLOAT	0xFAD00000
+#define HEADER_FMT_MONO		0x00000080 /* bit 23 in header_lo. WARNING: old
+					    * bit 22 is ignored in float
+					    * format */
+#define HEADER_FMT_INTEL	0x00008000
+#define HEADER_FMT_16BITS	0x00002000
+#define HEADER_FMT_24BITS	0x00004000
+#define HEADER_FMT_UPTO11	0x00000200 /* frequency is less or equ. to 11k.
+					    * */
+#define HEADER_FMT_UPTO32	0x00000100 /* frequency is over 11k and less
+					    * then 32k.*/
+
+
+#define BIT_FMP_HEADER          23
+#define BIT_FMP_SD              22
+#define BIT_FMP_MULTICHANNEL    19
+
+#define START_STATE             1
+#define PAUSE_STATE             0
+
+
+
+
+
+/* from PcxAll_e.h */
+/* Start/Pause condition for pipes (PCXStartPipe, PCXPausePipe) */
+#define START_PAUSE_IMMEDIATE           0
+#define START_PAUSE_ON_SYNCHRO          1
+#define START_PAUSE_ON_TIME_CODE        2
+
+
+/* Pipe / Stream state */
+#define START_STATE             1
+#define PAUSE_STATE             0
+
+static inline void unpack_pointer(dma_addr_t ptr, u32 *r_low, u32 *r_high)
+{
+	*r_low = (u32)(ptr & 0xffffffff);
+#if BITS_PER_LONG == 32
+	*r_high = 0;
+#else
+	*r_high = (u32)((u64)ptr>>32);
+#endif
+}
+
+#endif /* LX_CORE_H */
diff --git a/linux/sound/pci/lx6464es/lx_defs.h b/linux/sound/pci/lx6464es/lx_defs.h
new file mode 100644
index 0000000..49d36bd
--- /dev/null
+++ b/linux/sound/pci/lx6464es/lx_defs.h
@@ -0,0 +1,376 @@
+/* -*- linux-c -*- *
+ *
+ * ALSA driver for the digigram lx6464es interface
+ * adapted upstream headers
+ *
+ * Copyright (c) 2009 Tim Blechmann <tim@klingt.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; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef LX_DEFS_H
+#define LX_DEFS_H
+
+/* code adapted from ethersound.h */
+#define	XES_FREQ_COUNT8_MASK    0x00001FFF /* compteur 25MHz entre 8 ech. */
+#define	XES_FREQ_COUNT8_44_MIN  0x00001288 /* 25M /
+					    * [ 44k - ( 44.1k + 48k ) / 2 ]
+					    * * 8 */
+#define	XES_FREQ_COUNT8_44_MAX	0x000010F0 /* 25M / [ ( 44.1k + 48k ) / 2 ]
+					    * * 8 */
+#define	XES_FREQ_COUNT8_48_MAX	0x00000F08 /* 25M /
+					    * [ 48k + ( 44.1k + 48k ) / 2 ]
+					    * * 8 */
+
+/* code adapted from LXES_registers.h */
+
+#define IOCR_OUTPUTS_OFFSET 0	/* (rw) offset for the number of OUTs in the
+				 * ConfES register. */
+#define IOCR_INPUTS_OFFSET  8	/* (rw) offset for the number of INs in the
+				 * ConfES register. */
+#define FREQ_RATIO_OFFSET  19	/* (rw) offset for frequency ratio in the
+				 * ConfES register. */
+#define	FREQ_RATIO_SINGLE_MODE 0x01 /* value for single mode frequency ratio:
+				     * sample rate = frequency rate. */
+
+#define CONFES_READ_PART_MASK	0x00070000
+#define CONFES_WRITE_PART_MASK	0x00F80000
+
+/* code adapted from if_drv_mb.h */
+
+#define MASK_SYS_STATUS_ERROR	(1L << 31) /* events that lead to a PCI irq if
+					    * not yet pending */
+#define MASK_SYS_STATUS_URUN	(1L << 30)
+#define MASK_SYS_STATUS_ORUN	(1L << 29)
+#define MASK_SYS_STATUS_EOBO	(1L << 28)
+#define MASK_SYS_STATUS_EOBI	(1L << 27)
+#define MASK_SYS_STATUS_FREQ	(1L << 26)
+#define MASK_SYS_STATUS_ESA	(1L << 25) /* reserved, this is set by the
+					    * XES */
+#define MASK_SYS_STATUS_TIMER	(1L << 24)
+
+#define MASK_SYS_ASYNC_EVENTS	(MASK_SYS_STATUS_ERROR |		\
+				 MASK_SYS_STATUS_URUN  |		\
+				 MASK_SYS_STATUS_ORUN  |		\
+				 MASK_SYS_STATUS_EOBO  |		\
+				 MASK_SYS_STATUS_EOBI  |		\
+				 MASK_SYS_STATUS_FREQ  |		\
+				 MASK_SYS_STATUS_ESA)
+
+#define MASK_SYS_PCI_EVENTS		(MASK_SYS_ASYNC_EVENTS |	\
+					 MASK_SYS_STATUS_TIMER)
+
+#define MASK_SYS_TIMER_COUNT	0x0000FFFF
+
+#define MASK_SYS_STATUS_EOT_PLX		(1L << 22) /* event that remains
+						    * internal: reserved fo end
+						    * of plx dma */
+#define MASK_SYS_STATUS_XES		(1L << 21) /* event that remains
+						    * internal: pending XES
+						    * IRQ */
+#define MASK_SYS_STATUS_CMD_DONE	(1L << 20) /* alternate command
+						    * management: notify driver
+						    * instead of polling */
+
+
+#define MAX_STREAM_BUFFER 5	/* max amount of stream buffers. */
+
+#define MICROBLAZE_IBL_MIN		 32
+#define MICROBLAZE_IBL_DEFAULT	        128
+#define MICROBLAZE_IBL_MAX		512
+/* #define MASK_GRANULARITY		(2*MICROBLAZE_IBL_MAX-1) */
+
+
+
+/* command opcodes, see reference for details */
+
+/*
+ the capture bit position in the object_id field in driver commands
+ depends upon the number of managed channels. For now, 64 IN + 64 OUT are
+ supported. HOwever, the communication protocol forsees 1024 channels, hence
+ bit 10 indicates a capture (input) object).
+*/
+#define ID_IS_CAPTURE (1L << 10)
+#define ID_OFFSET	13	/* object ID is at the 13th bit in the
+				 * 1st command word.*/
+#define ID_CH_MASK    0x3F
+#define OPCODE_OFFSET	24	/* offset of the command opcode in the first
+				 * command word.*/
+
+enum cmd_mb_opcodes {
+	CMD_00_INFO_DEBUG	        = 0x00,
+	CMD_01_GET_SYS_CFG		= 0x01,
+	CMD_02_SET_GRANULARITY		= 0x02,
+	CMD_03_SET_TIMER_IRQ		= 0x03,
+	CMD_04_GET_EVENT		= 0x04,
+	CMD_05_GET_PIPES		= 0x05,
+
+	CMD_06_ALLOCATE_PIPE            = 0x06,
+	CMD_07_RELEASE_PIPE		= 0x07,
+	CMD_08_ASK_BUFFERS		= 0x08,
+	CMD_09_STOP_PIPE		= 0x09,
+	CMD_0A_GET_PIPE_SPL_COUNT	= 0x0a,
+	CMD_0B_TOGGLE_PIPE_STATE	= 0x0b,
+
+	CMD_0C_DEF_STREAM		= 0x0c,
+	CMD_0D_SET_MUTE			= 0x0d,
+	CMD_0E_GET_STREAM_SPL_COUNT     = 0x0e,
+	CMD_0F_UPDATE_BUFFER		= 0x0f,
+	CMD_10_GET_BUFFER		= 0x10,
+	CMD_11_CANCEL_BUFFER		= 0x11,
+	CMD_12_GET_PEAK			= 0x12,
+	CMD_13_SET_STREAM_STATE		= 0x13,
+	CMD_14_INVALID			= 0x14,
+};
+
+/* pipe states */
+enum pipe_state_t {
+	PSTATE_IDLE	= 0,	/* the pipe is not processed in the XES_IRQ
+				 * (free or stopped, or paused). */
+	PSTATE_RUN	= 1,	/* sustained play/record state. */
+	PSTATE_PURGE	= 2,	/* the ES channels are now off, render pipes do
+				 * not DMA, record pipe do a last DMA. */
+	PSTATE_ACQUIRE	= 3,	/* the ES channels are now on, render pipes do
+				 * not yet increase their sample count, record
+				 * pipes do not DMA. */
+	PSTATE_CLOSING	= 4,	/* the pipe is releasing, and may not yet
+				 * receive an "alloc" command. */
+};
+
+/* stream states */
+enum stream_state_t {
+	SSTATE_STOP	=  0x00,       /* setting to stop resets the stream spl
+					* count.*/
+	SSTATE_RUN	= (0x01 << 0), /* start DMA and spl count handling. */
+	SSTATE_PAUSE	= (0x01 << 1), /* pause DMA and spl count handling. */
+};
+
+/* buffer flags */
+enum buffer_flags {
+	BF_VALID	= 0x80,	/* set if the buffer is valid, clear if free.*/
+	BF_CURRENT	= 0x40,	/* set if this is the current buffer (there is
+				 * always a current buffer).*/
+	BF_NOTIFY_EOB	= 0x20,	/* set if this buffer must cause a PCI event
+				 * when finished.*/
+	BF_CIRCULAR	= 0x10,	/* set if buffer[1] must be copied to buffer[0]
+				 * by the end of this buffer.*/
+	BF_64BITS_ADR	= 0x08,	/* set if the hi part of the address is valid.*/
+	BF_xx		= 0x04,	/* future extension.*/
+	BF_EOB		= 0x02,	/* set if finished, but not yet free.*/
+	BF_PAUSE	= 0x01,	/* pause stream at buffer end.*/
+	BF_ZERO		= 0x00,	/* no flags (init).*/
+};
+
+/**
+*	Stream Flags definitions
+*/
+enum stream_flags {
+	SF_ZERO		= 0x00000000, /* no flags (stream invalid). */
+	SF_VALID	= 0x10000000, /* the stream has a valid DMA_conf
+				       * info (setstreamformat). */
+	SF_XRUN		= 0x20000000, /* the stream is un x-run state. */
+	SF_START	= 0x40000000, /* the DMA is running.*/
+	SF_ASIO		= 0x80000000, /* ASIO.*/
+};
+
+
+#define MASK_SPL_COUNT_HI 0x00FFFFFF /* 4 MSBits are status bits */
+#define PSTATE_OFFSET             28 /* 4 MSBits are status bits */
+
+
+#define MASK_STREAM_HAS_MAPPING	(1L << 12)
+#define MASK_STREAM_IS_ASIO	(1L <<  9)
+#define STREAM_FMT_OFFSET	10   /* the stream fmt bits start at the 10th
+				      * bit in the command word. */
+
+#define STREAM_FMT_16b          0x02
+#define STREAM_FMT_intel        0x01
+
+#define FREQ_FIELD_OFFSET	15  /* offset of the freq field in the response
+				     * word */
+
+#define BUFF_FLAGS_OFFSET	  24 /*  offset of the buffer flags in the
+				      *  response word. */
+#define MASK_DATA_SIZE	  0x00FFFFFF /* this must match the field size of
+				      * datasize in the buffer_t structure. */
+
+#define MASK_BUFFER_ID	        0xFF /* the cancel command awaits a buffer ID,
+				      * may be 0xFF for "current". */
+
+
+/* code adapted from PcxErr_e.h */
+
+/* Bits masks */
+
+#define ERROR_MASK              0x8000
+
+#define SOURCE_MASK             0x7800
+
+#define E_SOURCE_BOARD          0x4000 /* 8 >> 1 */
+#define E_SOURCE_DRV            0x2000 /* 4 >> 1 */
+#define E_SOURCE_API            0x1000 /* 2 >> 1 */
+/* Error tools */
+#define E_SOURCE_TOOLS          0x0800 /* 1 >> 1 */
+/* Error pcxaudio */
+#define E_SOURCE_AUDIO          0x1800 /* 3 >> 1 */
+/* Error virtual pcx */
+#define E_SOURCE_VPCX           0x2800 /* 5 >> 1 */
+/* Error dispatcher */
+#define E_SOURCE_DISPATCHER     0x3000 /* 6 >> 1 */
+/* Error from CobraNet firmware */
+#define E_SOURCE_COBRANET       0x3800 /* 7 >> 1 */
+
+#define E_SOURCE_USER           0x7800
+
+#define CLASS_MASK              0x0700
+
+#define CODE_MASK               0x00FF
+
+/* Bits values */
+
+/* Values for the error/warning bit */
+#define ERROR_VALUE             0x8000
+#define WARNING_VALUE           0x0000
+
+/* Class values */
+#define E_CLASS_GENERAL                  0x0000
+#define E_CLASS_INVALID_CMD              0x0100
+#define E_CLASS_INVALID_STD_OBJECT       0x0200
+#define E_CLASS_RSRC_IMPOSSIBLE          0x0300
+#define E_CLASS_WRONG_CONTEXT            0x0400
+#define E_CLASS_BAD_SPECIFIC_PARAMETER   0x0500
+#define E_CLASS_REAL_TIME_ERROR          0x0600
+#define E_CLASS_DIRECTSHOW               0x0700
+#define E_CLASS_FREE                     0x0700
+
+
+/* Complete DRV error code for the general class */
+#define ED_GN           (ERROR_VALUE | E_SOURCE_DRV | E_CLASS_GENERAL)
+#define ED_CONCURRENCY                  (ED_GN | 0x01)
+#define ED_DSP_CRASHED                  (ED_GN | 0x02)
+#define ED_UNKNOWN_BOARD                (ED_GN | 0x03)
+#define ED_NOT_INSTALLED                (ED_GN | 0x04)
+#define ED_CANNOT_OPEN_SVC_MANAGER      (ED_GN | 0x05)
+#define ED_CANNOT_READ_REGISTRY         (ED_GN | 0x06)
+#define ED_DSP_VERSION_MISMATCH         (ED_GN | 0x07)
+#define ED_UNAVAILABLE_FEATURE          (ED_GN | 0x08)
+#define ED_CANCELLED                    (ED_GN | 0x09)
+#define ED_NO_RESPONSE_AT_IRQA          (ED_GN | 0x10)
+#define ED_INVALID_ADDRESS              (ED_GN | 0x11)
+#define ED_DSP_CORRUPTED                (ED_GN | 0x12)
+#define ED_PENDING_OPERATION            (ED_GN | 0x13)
+#define ED_NET_ALLOCATE_MEMORY_IMPOSSIBLE   (ED_GN | 0x14)
+#define ED_NET_REGISTER_ERROR               (ED_GN | 0x15)
+#define ED_NET_THREAD_ERROR                 (ED_GN | 0x16)
+#define ED_NET_OPEN_ERROR                   (ED_GN | 0x17)
+#define ED_NET_CLOSE_ERROR                  (ED_GN | 0x18)
+#define ED_NET_NO_MORE_PACKET               (ED_GN | 0x19)
+#define ED_NET_NO_MORE_BUFFER               (ED_GN | 0x1A)
+#define ED_NET_SEND_ERROR                   (ED_GN | 0x1B)
+#define ED_NET_RECEIVE_ERROR                (ED_GN | 0x1C)
+#define ED_NET_WRONG_MSG_SIZE               (ED_GN | 0x1D)
+#define ED_NET_WAIT_ERROR                   (ED_GN | 0x1E)
+#define ED_NET_EEPROM_ERROR                 (ED_GN | 0x1F)
+#define ED_INVALID_RS232_COM_NUMBER         (ED_GN | 0x20)
+#define ED_INVALID_RS232_INIT               (ED_GN | 0x21)
+#define ED_FILE_ERROR                       (ED_GN | 0x22)
+#define ED_INVALID_GPIO_CMD                 (ED_GN | 0x23)
+#define ED_RS232_ALREADY_OPENED             (ED_GN | 0x24)
+#define ED_RS232_NOT_OPENED                 (ED_GN | 0x25)
+#define ED_GPIO_ALREADY_OPENED              (ED_GN | 0x26)
+#define ED_GPIO_NOT_OPENED                  (ED_GN | 0x27)
+#define ED_REGISTRY_ERROR                   (ED_GN | 0x28) /* <- NCX */
+#define ED_INVALID_SERVICE                  (ED_GN | 0x29) /* <- NCX */
+
+#define ED_READ_FILE_ALREADY_OPENED	    (ED_GN | 0x2a) /* <- Decalage
+							    * pour RCX
+							    * (old 0x28)
+							    * */
+#define ED_READ_FILE_INVALID_COMMAND	    (ED_GN | 0x2b) /* ~ */
+#define ED_READ_FILE_INVALID_PARAMETER	    (ED_GN | 0x2c) /* ~ */
+#define ED_READ_FILE_ALREADY_CLOSED	    (ED_GN | 0x2d) /* ~ */
+#define ED_READ_FILE_NO_INFORMATION	    (ED_GN | 0x2e) /* ~ */
+#define ED_READ_FILE_INVALID_HANDLE	    (ED_GN | 0x2f) /* ~ */
+#define ED_READ_FILE_END_OF_FILE	    (ED_GN | 0x30) /* ~ */
+#define ED_READ_FILE_ERROR	            (ED_GN | 0x31) /* ~ */
+
+#define ED_DSP_CRASHED_EXC_DSPSTACK_OVERFLOW (ED_GN | 0x32) /* <- Decalage pour
+							     * PCX (old 0x14) */
+#define ED_DSP_CRASHED_EXC_SYSSTACK_OVERFLOW (ED_GN | 0x33) /* ~ */
+#define ED_DSP_CRASHED_EXC_ILLEGAL           (ED_GN | 0x34) /* ~ */
+#define ED_DSP_CRASHED_EXC_TIMER_REENTRY     (ED_GN | 0x35) /* ~ */
+#define ED_DSP_CRASHED_EXC_FATAL_ERROR       (ED_GN | 0x36) /* ~ */
+
+#define ED_FLASH_PCCARD_NOT_PRESENT          (ED_GN | 0x37)
+
+#define ED_NO_CURRENT_CLOCK                  (ED_GN | 0x38)
+
+/* Complete DRV error code for real time class */
+#define ED_RT           (ERROR_VALUE | E_SOURCE_DRV | E_CLASS_REAL_TIME_ERROR)
+#define ED_DSP_TIMED_OUT                (ED_RT | 0x01)
+#define ED_DSP_CHK_TIMED_OUT            (ED_RT | 0x02)
+#define ED_STREAM_OVERRUN               (ED_RT | 0x03)
+#define ED_DSP_BUSY                     (ED_RT | 0x04)
+#define ED_DSP_SEMAPHORE_TIME_OUT       (ED_RT | 0x05)
+#define ED_BOARD_TIME_OUT               (ED_RT | 0x06)
+#define ED_XILINX_ERROR                 (ED_RT | 0x07)
+#define ED_COBRANET_ITF_NOT_RESPONDING  (ED_RT | 0x08)
+
+/* Complete BOARD error code for the invaid standard object class */
+#define EB_ISO          (ERROR_VALUE | E_SOURCE_BOARD | \
+			 E_CLASS_INVALID_STD_OBJECT)
+#define EB_INVALID_EFFECT               (EB_ISO | 0x00)
+#define EB_INVALID_PIPE                 (EB_ISO | 0x40)
+#define EB_INVALID_STREAM               (EB_ISO | 0x80)
+#define EB_INVALID_AUDIO                (EB_ISO | 0xC0)
+
+/* Complete BOARD error code for impossible resource allocation class */
+#define EB_RI           (ERROR_VALUE | E_SOURCE_BOARD | E_CLASS_RSRC_IMPOSSIBLE)
+#define EB_ALLOCATE_ALL_STREAM_TRANSFERT_BUFFERS_IMPOSSIBLE (EB_RI | 0x01)
+#define EB_ALLOCATE_PIPE_SAMPLE_BUFFER_IMPOSSIBLE           (EB_RI | 0x02)
+
+#define EB_ALLOCATE_MEM_STREAM_IMPOSSIBLE		\
+	EB_ALLOCATE_ALL_STREAM_TRANSFERT_BUFFERS_IMPOSSIBLE
+#define EB_ALLOCATE_MEM_PIPE_IMPOSSIBLE			\
+	EB_ALLOCATE_PIPE_SAMPLE_BUFFER_IMPOSSIBLE
+
+#define EB_ALLOCATE_DIFFERED_CMD_IMPOSSIBLE     (EB_RI | 0x03)
+#define EB_TOO_MANY_DIFFERED_CMD                (EB_RI | 0x04)
+#define EB_RBUFFERS_TABLE_OVERFLOW              (EB_RI | 0x05)
+#define EB_ALLOCATE_EFFECTS_IMPOSSIBLE          (EB_RI | 0x08)
+#define EB_ALLOCATE_EFFECT_POS_IMPOSSIBLE       (EB_RI | 0x09)
+#define EB_RBUFFER_NOT_AVAILABLE                (EB_RI | 0x0A)
+#define EB_ALLOCATE_CONTEXT_LIII_IMPOSSIBLE     (EB_RI | 0x0B)
+#define EB_STATUS_DIALOG_IMPOSSIBLE             (EB_RI | 0x1D)
+#define EB_CONTROL_CMD_IMPOSSIBLE               (EB_RI | 0x1E)
+#define EB_STATUS_SEND_IMPOSSIBLE               (EB_RI | 0x1F)
+#define EB_ALLOCATE_PIPE_IMPOSSIBLE             (EB_RI | 0x40)
+#define EB_ALLOCATE_STREAM_IMPOSSIBLE           (EB_RI | 0x80)
+#define EB_ALLOCATE_AUDIO_IMPOSSIBLE            (EB_RI | 0xC0)
+
+/* Complete BOARD error code for wrong call context class */
+#define EB_WCC          (ERROR_VALUE | E_SOURCE_BOARD | E_CLASS_WRONG_CONTEXT)
+#define EB_CMD_REFUSED                  (EB_WCC | 0x00)
+#define EB_START_STREAM_REFUSED         (EB_WCC | 0xFC)
+#define EB_SPC_REFUSED                  (EB_WCC | 0xFD)
+#define EB_CSN_REFUSED                  (EB_WCC | 0xFE)
+#define EB_CSE_REFUSED                  (EB_WCC | 0xFF)
+
+
+
+
+#endif /* LX_DEFS_H */
diff --git a/linux/sound/pci/maestro3.c b/linux/sound/pci/maestro3.c
new file mode 100644
index 0000000..3c40d72
--- /dev/null
+++ b/linux/sound/pci/maestro3.c
@@ -0,0 +1,2909 @@
+/*
+ * Driver for ESS Maestro3/Allegro (ES1988) soundcards.
+ * Copyright (c) 2000 by Zach Brown <zab@zabbo.net>
+ *                       Takashi Iwai <tiwai@suse.de>
+ *
+ * Most of the hardware init stuffs are based on maestro3 driver for
+ * OSS/Free by Zach Brown.  Many thanks to Zach!
+ *
+ *   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
+ *
+ *
+ * ChangeLog:
+ * Aug. 27, 2001
+ *     - Fixed deadlock on capture
+ *     - Added Canyon3D-2 support by Rob Riggs <rob@pangalactic.org>
+ *
+ */
+ 
+#define CARD_NAME "ESS Maestro3/Allegro/Canyon3D-2"
+#define DRIVER_NAME "Maestro3"
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/input.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/mpu401.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <asm/byteorder.h>
+
+MODULE_AUTHOR("Zach Brown <zab@zabbo.net>, Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ESS Maestro3 PCI");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{ESS,Maestro3 PCI},"
+		"{ESS,ES1988},"
+		"{ESS,Allegro PCI},"
+		"{ESS,Allegro-1 PCI},"
+	        "{ESS,Canyon3D-2/LE PCI}}");
+MODULE_FIRMWARE("ess/maestro3_assp_kernel.fw");
+MODULE_FIRMWARE("ess/maestro3_assp_minisrc.fw");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* all enabled */
+static int external_amp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+static int amp_gpio[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable this soundcard.");
+module_param_array(external_amp, bool, NULL, 0444);
+MODULE_PARM_DESC(external_amp, "Enable external amp for " CARD_NAME " soundcard.");
+module_param_array(amp_gpio, int, NULL, 0444);
+MODULE_PARM_DESC(amp_gpio, "GPIO pin number for external amp. (default = -1)");
+
+#define MAX_PLAYBACKS	2
+#define MAX_CAPTURES	1
+#define NR_DSPS		(MAX_PLAYBACKS + MAX_CAPTURES)
+
+
+/*
+ * maestro3 registers
+ */
+
+/* Allegro PCI configuration registers */
+#define PCI_LEGACY_AUDIO_CTRL   0x40
+#define SOUND_BLASTER_ENABLE    0x00000001
+#define FM_SYNTHESIS_ENABLE     0x00000002
+#define GAME_PORT_ENABLE        0x00000004
+#define MPU401_IO_ENABLE        0x00000008
+#define MPU401_IRQ_ENABLE       0x00000010
+#define ALIAS_10BIT_IO          0x00000020
+#define SB_DMA_MASK             0x000000C0
+#define SB_DMA_0                0x00000040
+#define SB_DMA_1                0x00000040
+#define SB_DMA_R                0x00000080
+#define SB_DMA_3                0x000000C0
+#define SB_IRQ_MASK             0x00000700
+#define SB_IRQ_5                0x00000000
+#define SB_IRQ_7                0x00000100
+#define SB_IRQ_9                0x00000200
+#define SB_IRQ_10               0x00000300
+#define MIDI_IRQ_MASK           0x00003800
+#define SERIAL_IRQ_ENABLE       0x00004000
+#define DISABLE_LEGACY          0x00008000
+
+#define PCI_ALLEGRO_CONFIG      0x50
+#define SB_ADDR_240             0x00000004
+#define MPU_ADDR_MASK           0x00000018
+#define MPU_ADDR_330            0x00000000
+#define MPU_ADDR_300            0x00000008
+#define MPU_ADDR_320            0x00000010
+#define MPU_ADDR_340            0x00000018
+#define USE_PCI_TIMING          0x00000040
+#define POSTED_WRITE_ENABLE     0x00000080
+#define DMA_POLICY_MASK         0x00000700
+#define DMA_DDMA                0x00000000
+#define DMA_TDMA                0x00000100
+#define DMA_PCPCI               0x00000200
+#define DMA_WBDMA16             0x00000400
+#define DMA_WBDMA4              0x00000500
+#define DMA_WBDMA2              0x00000600
+#define DMA_WBDMA1              0x00000700
+#define DMA_SAFE_GUARD          0x00000800
+#define HI_PERF_GP_ENABLE       0x00001000
+#define PIC_SNOOP_MODE_0        0x00002000
+#define PIC_SNOOP_MODE_1        0x00004000
+#define SOUNDBLASTER_IRQ_MASK   0x00008000
+#define RING_IN_ENABLE          0x00010000
+#define SPDIF_TEST_MODE         0x00020000
+#define CLK_MULT_MODE_SELECT_2  0x00040000
+#define EEPROM_WRITE_ENABLE     0x00080000
+#define CODEC_DIR_IN            0x00100000
+#define HV_BUTTON_FROM_GD       0x00200000
+#define REDUCED_DEBOUNCE        0x00400000
+#define HV_CTRL_ENABLE          0x00800000
+#define SPDIF_ENABLE            0x01000000
+#define CLK_DIV_SELECT          0x06000000
+#define CLK_DIV_BY_48           0x00000000
+#define CLK_DIV_BY_49           0x02000000
+#define CLK_DIV_BY_50           0x04000000
+#define CLK_DIV_RESERVED        0x06000000
+#define PM_CTRL_ENABLE          0x08000000
+#define CLK_MULT_MODE_SELECT    0x30000000
+#define CLK_MULT_MODE_SHIFT     28
+#define CLK_MULT_MODE_0         0x00000000
+#define CLK_MULT_MODE_1         0x10000000
+#define CLK_MULT_MODE_2         0x20000000
+#define CLK_MULT_MODE_3         0x30000000
+#define INT_CLK_SELECT          0x40000000
+#define INT_CLK_MULT_RESET      0x80000000
+
+/* M3 */
+#define INT_CLK_SRC_NOT_PCI     0x00100000
+#define INT_CLK_MULT_ENABLE     0x80000000
+
+#define PCI_ACPI_CONTROL        0x54
+#define PCI_ACPI_D0             0x00000000
+#define PCI_ACPI_D1             0xB4F70000
+#define PCI_ACPI_D2             0xB4F7B4F7
+
+#define PCI_USER_CONFIG         0x58
+#define EXT_PCI_MASTER_ENABLE   0x00000001
+#define SPDIF_OUT_SELECT        0x00000002
+#define TEST_PIN_DIR_CTRL       0x00000004
+#define AC97_CODEC_TEST         0x00000020
+#define TRI_STATE_BUFFER        0x00000080
+#define IN_CLK_12MHZ_SELECT     0x00000100
+#define MULTI_FUNC_DISABLE      0x00000200
+#define EXT_MASTER_PAIR_SEL     0x00000400
+#define PCI_MASTER_SUPPORT      0x00000800
+#define STOP_CLOCK_ENABLE       0x00001000
+#define EAPD_DRIVE_ENABLE       0x00002000
+#define REQ_TRI_STATE_ENABLE    0x00004000
+#define REQ_LOW_ENABLE          0x00008000
+#define MIDI_1_ENABLE           0x00010000
+#define MIDI_2_ENABLE           0x00020000
+#define SB_AUDIO_SYNC           0x00040000
+#define HV_CTRL_TEST            0x00100000
+#define SOUNDBLASTER_TEST       0x00400000
+
+#define PCI_USER_CONFIG_C       0x5C
+
+#define PCI_DDMA_CTRL           0x60
+#define DDMA_ENABLE             0x00000001
+
+
+/* Allegro registers */
+#define HOST_INT_CTRL           0x18
+#define SB_INT_ENABLE           0x0001
+#define MPU401_INT_ENABLE       0x0002
+#define ASSP_INT_ENABLE         0x0010
+#define RING_INT_ENABLE         0x0020
+#define HV_INT_ENABLE           0x0040
+#define CLKRUN_GEN_ENABLE       0x0100
+#define HV_CTRL_TO_PME          0x0400
+#define SOFTWARE_RESET_ENABLE   0x8000
+
+/*
+ * should be using the above defines, probably.
+ */
+#define REGB_ENABLE_RESET               0x01
+#define REGB_STOP_CLOCK                 0x10
+
+#define HOST_INT_STATUS         0x1A
+#define SB_INT_PENDING          0x01
+#define MPU401_INT_PENDING      0x02
+#define ASSP_INT_PENDING        0x10
+#define RING_INT_PENDING        0x20
+#define HV_INT_PENDING          0x40
+
+#define HARDWARE_VOL_CTRL       0x1B
+#define SHADOW_MIX_REG_VOICE    0x1C
+#define HW_VOL_COUNTER_VOICE    0x1D
+#define SHADOW_MIX_REG_MASTER   0x1E
+#define HW_VOL_COUNTER_MASTER   0x1F
+
+#define CODEC_COMMAND           0x30
+#define CODEC_READ_B            0x80
+
+#define CODEC_STATUS            0x30
+#define CODEC_BUSY_B            0x01
+
+#define CODEC_DATA              0x32
+
+#define RING_BUS_CTRL_A         0x36
+#define RAC_PME_ENABLE          0x0100
+#define RAC_SDFS_ENABLE         0x0200
+#define LAC_PME_ENABLE          0x0400
+#define LAC_SDFS_ENABLE         0x0800
+#define SERIAL_AC_LINK_ENABLE   0x1000
+#define IO_SRAM_ENABLE          0x2000
+#define IIS_INPUT_ENABLE        0x8000
+
+#define RING_BUS_CTRL_B         0x38
+#define SECOND_CODEC_ID_MASK    0x0003
+#define SPDIF_FUNC_ENABLE       0x0010
+#define SECOND_AC_ENABLE        0x0020
+#define SB_MODULE_INTF_ENABLE   0x0040
+#define SSPE_ENABLE             0x0040
+#define M3I_DOCK_ENABLE         0x0080
+
+#define SDO_OUT_DEST_CTRL       0x3A
+#define COMMAND_ADDR_OUT        0x0003
+#define PCM_LR_OUT_LOCAL        0x0000
+#define PCM_LR_OUT_REMOTE       0x0004
+#define PCM_LR_OUT_MUTE         0x0008
+#define PCM_LR_OUT_BOTH         0x000C
+#define LINE1_DAC_OUT_LOCAL     0x0000
+#define LINE1_DAC_OUT_REMOTE    0x0010
+#define LINE1_DAC_OUT_MUTE      0x0020
+#define LINE1_DAC_OUT_BOTH      0x0030
+#define PCM_CLS_OUT_LOCAL       0x0000
+#define PCM_CLS_OUT_REMOTE      0x0040
+#define PCM_CLS_OUT_MUTE        0x0080
+#define PCM_CLS_OUT_BOTH        0x00C0
+#define PCM_RLF_OUT_LOCAL       0x0000
+#define PCM_RLF_OUT_REMOTE      0x0100
+#define PCM_RLF_OUT_MUTE        0x0200
+#define PCM_RLF_OUT_BOTH        0x0300
+#define LINE2_DAC_OUT_LOCAL     0x0000
+#define LINE2_DAC_OUT_REMOTE    0x0400
+#define LINE2_DAC_OUT_MUTE      0x0800
+#define LINE2_DAC_OUT_BOTH      0x0C00
+#define HANDSET_OUT_LOCAL       0x0000
+#define HANDSET_OUT_REMOTE      0x1000
+#define HANDSET_OUT_MUTE        0x2000
+#define HANDSET_OUT_BOTH        0x3000
+#define IO_CTRL_OUT_LOCAL       0x0000
+#define IO_CTRL_OUT_REMOTE      0x4000
+#define IO_CTRL_OUT_MUTE        0x8000
+#define IO_CTRL_OUT_BOTH        0xC000
+
+#define SDO_IN_DEST_CTRL        0x3C
+#define STATUS_ADDR_IN          0x0003
+#define PCM_LR_IN_LOCAL         0x0000
+#define PCM_LR_IN_REMOTE        0x0004
+#define PCM_LR_RESERVED         0x0008
+#define PCM_LR_IN_BOTH          0x000C
+#define LINE1_ADC_IN_LOCAL      0x0000
+#define LINE1_ADC_IN_REMOTE     0x0010
+#define LINE1_ADC_IN_MUTE       0x0020
+#define MIC_ADC_IN_LOCAL        0x0000
+#define MIC_ADC_IN_REMOTE       0x0040
+#define MIC_ADC_IN_MUTE         0x0080
+#define LINE2_DAC_IN_LOCAL      0x0000
+#define LINE2_DAC_IN_REMOTE     0x0400
+#define LINE2_DAC_IN_MUTE       0x0800
+#define HANDSET_IN_LOCAL        0x0000
+#define HANDSET_IN_REMOTE       0x1000
+#define HANDSET_IN_MUTE         0x2000
+#define IO_STATUS_IN_LOCAL      0x0000
+#define IO_STATUS_IN_REMOTE     0x4000
+
+#define SPDIF_IN_CTRL           0x3E
+#define SPDIF_IN_ENABLE         0x0001
+
+#define GPIO_DATA               0x60
+#define GPIO_DATA_MASK          0x0FFF
+#define GPIO_HV_STATUS          0x3000
+#define GPIO_PME_STATUS         0x4000
+
+#define GPIO_MASK               0x64
+#define GPIO_DIRECTION          0x68
+#define GPO_PRIMARY_AC97        0x0001
+#define GPI_LINEOUT_SENSE       0x0004
+#define GPO_SECONDARY_AC97      0x0008
+#define GPI_VOL_DOWN            0x0010
+#define GPI_VOL_UP              0x0020
+#define GPI_IIS_CLK             0x0040
+#define GPI_IIS_LRCLK           0x0080
+#define GPI_IIS_DATA            0x0100
+#define GPI_DOCKING_STATUS      0x0100
+#define GPI_HEADPHONE_SENSE     0x0200
+#define GPO_EXT_AMP_SHUTDOWN    0x1000
+
+#define GPO_EXT_AMP_M3		1	/* default m3 amp */
+#define GPO_EXT_AMP_ALLEGRO	8	/* default allegro amp */
+
+/* M3 */
+#define GPO_M3_EXT_AMP_SHUTDN   0x0002
+
+#define ASSP_INDEX_PORT         0x80
+#define ASSP_MEMORY_PORT        0x82
+#define ASSP_DATA_PORT          0x84
+
+#define MPU401_DATA_PORT        0x98
+#define MPU401_STATUS_PORT      0x99
+
+#define CLK_MULT_DATA_PORT      0x9C
+
+#define ASSP_CONTROL_A          0xA2
+#define ASSP_0_WS_ENABLE        0x01
+#define ASSP_CTRL_A_RESERVED1   0x02
+#define ASSP_CTRL_A_RESERVED2   0x04
+#define ASSP_CLK_49MHZ_SELECT   0x08
+#define FAST_PLU_ENABLE         0x10
+#define ASSP_CTRL_A_RESERVED3   0x20
+#define DSP_CLK_36MHZ_SELECT    0x40
+
+#define ASSP_CONTROL_B          0xA4
+#define RESET_ASSP              0x00
+#define RUN_ASSP                0x01
+#define ENABLE_ASSP_CLOCK       0x00
+#define STOP_ASSP_CLOCK         0x10
+#define RESET_TOGGLE            0x40
+
+#define ASSP_CONTROL_C          0xA6
+#define ASSP_HOST_INT_ENABLE    0x01
+#define FM_ADDR_REMAP_DISABLE   0x02
+#define HOST_WRITE_PORT_ENABLE  0x08
+
+#define ASSP_HOST_INT_STATUS    0xAC
+#define DSP2HOST_REQ_PIORECORD  0x01
+#define DSP2HOST_REQ_I2SRATE    0x02
+#define DSP2HOST_REQ_TIMER      0x04
+
+/* AC97 registers */
+/* XXX fix this crap up */
+/*#define AC97_RESET              0x00*/
+
+#define AC97_VOL_MUTE_B         0x8000
+#define AC97_VOL_M              0x1F
+#define AC97_LEFT_VOL_S         8
+
+#define AC97_MASTER_VOL         0x02
+#define AC97_LINE_LEVEL_VOL     0x04
+#define AC97_MASTER_MONO_VOL    0x06
+#define AC97_PC_BEEP_VOL        0x0A
+#define AC97_PC_BEEP_VOL_M      0x0F
+#define AC97_SROUND_MASTER_VOL  0x38
+#define AC97_PC_BEEP_VOL_S      1
+
+/*#define AC97_PHONE_VOL          0x0C
+#define AC97_MIC_VOL            0x0E*/
+#define AC97_MIC_20DB_ENABLE    0x40
+
+/*#define AC97_LINEIN_VOL         0x10
+#define AC97_CD_VOL             0x12
+#define AC97_VIDEO_VOL          0x14
+#define AC97_AUX_VOL            0x16*/
+#define AC97_PCM_OUT_VOL        0x18
+/*#define AC97_RECORD_SELECT      0x1A*/
+#define AC97_RECORD_MIC         0x00
+#define AC97_RECORD_CD          0x01
+#define AC97_RECORD_VIDEO       0x02
+#define AC97_RECORD_AUX         0x03
+#define AC97_RECORD_MONO_MUX    0x02
+#define AC97_RECORD_DIGITAL     0x03
+#define AC97_RECORD_LINE        0x04
+#define AC97_RECORD_STEREO      0x05
+#define AC97_RECORD_MONO        0x06
+#define AC97_RECORD_PHONE       0x07
+
+/*#define AC97_RECORD_GAIN        0x1C*/
+#define AC97_RECORD_VOL_M       0x0F
+
+/*#define AC97_GENERAL_PURPOSE    0x20*/
+#define AC97_POWER_DOWN_CTRL    0x26
+#define AC97_ADC_READY          0x0001
+#define AC97_DAC_READY          0x0002
+#define AC97_ANALOG_READY       0x0004
+#define AC97_VREF_ON            0x0008
+#define AC97_PR0                0x0100
+#define AC97_PR1                0x0200
+#define AC97_PR2                0x0400
+#define AC97_PR3                0x0800
+#define AC97_PR4                0x1000
+
+#define AC97_RESERVED1          0x28
+
+#define AC97_VENDOR_TEST        0x5A
+
+#define AC97_CLOCK_DELAY        0x5C
+#define AC97_LINEOUT_MUX_SEL    0x0001
+#define AC97_MONO_MUX_SEL       0x0002
+#define AC97_CLOCK_DELAY_SEL    0x1F
+#define AC97_DAC_CDS_SHIFT      6
+#define AC97_ADC_CDS_SHIFT      11
+
+#define AC97_MULTI_CHANNEL_SEL  0x74
+
+/*#define AC97_VENDOR_ID1         0x7C
+#define AC97_VENDOR_ID2         0x7E*/
+
+/*
+ * ASSP control regs
+ */
+#define DSP_PORT_TIMER_COUNT    0x06
+
+#define DSP_PORT_MEMORY_INDEX   0x80
+
+#define DSP_PORT_MEMORY_TYPE    0x82
+#define MEMTYPE_INTERNAL_CODE   0x0002
+#define MEMTYPE_INTERNAL_DATA   0x0003
+#define MEMTYPE_MASK            0x0003
+
+#define DSP_PORT_MEMORY_DATA    0x84
+
+#define DSP_PORT_CONTROL_REG_A  0xA2
+#define DSP_PORT_CONTROL_REG_B  0xA4
+#define DSP_PORT_CONTROL_REG_C  0xA6
+
+#define REV_A_CODE_MEMORY_BEGIN         0x0000
+#define REV_A_CODE_MEMORY_END           0x0FFF
+#define REV_A_CODE_MEMORY_UNIT_LENGTH   0x0040
+#define REV_A_CODE_MEMORY_LENGTH        (REV_A_CODE_MEMORY_END - REV_A_CODE_MEMORY_BEGIN + 1)
+
+#define REV_B_CODE_MEMORY_BEGIN         0x0000
+#define REV_B_CODE_MEMORY_END           0x0BFF
+#define REV_B_CODE_MEMORY_UNIT_LENGTH   0x0040
+#define REV_B_CODE_MEMORY_LENGTH        (REV_B_CODE_MEMORY_END - REV_B_CODE_MEMORY_BEGIN + 1)
+
+#define REV_A_DATA_MEMORY_BEGIN         0x1000
+#define REV_A_DATA_MEMORY_END           0x2FFF
+#define REV_A_DATA_MEMORY_UNIT_LENGTH   0x0080
+#define REV_A_DATA_MEMORY_LENGTH        (REV_A_DATA_MEMORY_END - REV_A_DATA_MEMORY_BEGIN + 1)
+
+#define REV_B_DATA_MEMORY_BEGIN         0x1000
+#define REV_B_DATA_MEMORY_END           0x2BFF
+#define REV_B_DATA_MEMORY_UNIT_LENGTH   0x0080
+#define REV_B_DATA_MEMORY_LENGTH        (REV_B_DATA_MEMORY_END - REV_B_DATA_MEMORY_BEGIN + 1)
+
+
+#define NUM_UNITS_KERNEL_CODE          16
+#define NUM_UNITS_KERNEL_DATA           2
+
+#define NUM_UNITS_KERNEL_CODE_WITH_HSP 16
+#define NUM_UNITS_KERNEL_DATA_WITH_HSP  5
+
+/*
+ * Kernel data layout
+ */
+
+#define DP_SHIFT_COUNT                  7
+
+#define KDATA_BASE_ADDR                 0x1000
+#define KDATA_BASE_ADDR2                0x1080
+
+#define KDATA_TASK0                     (KDATA_BASE_ADDR + 0x0000)
+#define KDATA_TASK1                     (KDATA_BASE_ADDR + 0x0001)
+#define KDATA_TASK2                     (KDATA_BASE_ADDR + 0x0002)
+#define KDATA_TASK3                     (KDATA_BASE_ADDR + 0x0003)
+#define KDATA_TASK4                     (KDATA_BASE_ADDR + 0x0004)
+#define KDATA_TASK5                     (KDATA_BASE_ADDR + 0x0005)
+#define KDATA_TASK6                     (KDATA_BASE_ADDR + 0x0006)
+#define KDATA_TASK7                     (KDATA_BASE_ADDR + 0x0007)
+#define KDATA_TASK_ENDMARK              (KDATA_BASE_ADDR + 0x0008)
+
+#define KDATA_CURRENT_TASK              (KDATA_BASE_ADDR + 0x0009)
+#define KDATA_TASK_SWITCH               (KDATA_BASE_ADDR + 0x000A)
+
+#define KDATA_INSTANCE0_POS3D           (KDATA_BASE_ADDR + 0x000B)
+#define KDATA_INSTANCE1_POS3D           (KDATA_BASE_ADDR + 0x000C)
+#define KDATA_INSTANCE2_POS3D           (KDATA_BASE_ADDR + 0x000D)
+#define KDATA_INSTANCE3_POS3D           (KDATA_BASE_ADDR + 0x000E)
+#define KDATA_INSTANCE4_POS3D           (KDATA_BASE_ADDR + 0x000F)
+#define KDATA_INSTANCE5_POS3D           (KDATA_BASE_ADDR + 0x0010)
+#define KDATA_INSTANCE6_POS3D           (KDATA_BASE_ADDR + 0x0011)
+#define KDATA_INSTANCE7_POS3D           (KDATA_BASE_ADDR + 0x0012)
+#define KDATA_INSTANCE8_POS3D           (KDATA_BASE_ADDR + 0x0013)
+#define KDATA_INSTANCE_POS3D_ENDMARK    (KDATA_BASE_ADDR + 0x0014)
+
+#define KDATA_INSTANCE0_SPKVIRT         (KDATA_BASE_ADDR + 0x0015)
+#define KDATA_INSTANCE_SPKVIRT_ENDMARK  (KDATA_BASE_ADDR + 0x0016)
+
+#define KDATA_INSTANCE0_SPDIF           (KDATA_BASE_ADDR + 0x0017)
+#define KDATA_INSTANCE_SPDIF_ENDMARK    (KDATA_BASE_ADDR + 0x0018)
+
+#define KDATA_INSTANCE0_MODEM           (KDATA_BASE_ADDR + 0x0019)
+#define KDATA_INSTANCE_MODEM_ENDMARK    (KDATA_BASE_ADDR + 0x001A)
+
+#define KDATA_INSTANCE0_SRC             (KDATA_BASE_ADDR + 0x001B)
+#define KDATA_INSTANCE1_SRC             (KDATA_BASE_ADDR + 0x001C)
+#define KDATA_INSTANCE_SRC_ENDMARK      (KDATA_BASE_ADDR + 0x001D)
+
+#define KDATA_INSTANCE0_MINISRC         (KDATA_BASE_ADDR + 0x001E)
+#define KDATA_INSTANCE1_MINISRC         (KDATA_BASE_ADDR + 0x001F)
+#define KDATA_INSTANCE2_MINISRC         (KDATA_BASE_ADDR + 0x0020)
+#define KDATA_INSTANCE3_MINISRC         (KDATA_BASE_ADDR + 0x0021)
+#define KDATA_INSTANCE_MINISRC_ENDMARK  (KDATA_BASE_ADDR + 0x0022)
+
+#define KDATA_INSTANCE0_CPYTHRU         (KDATA_BASE_ADDR + 0x0023)
+#define KDATA_INSTANCE1_CPYTHRU         (KDATA_BASE_ADDR + 0x0024)
+#define KDATA_INSTANCE_CPYTHRU_ENDMARK  (KDATA_BASE_ADDR + 0x0025)
+
+#define KDATA_CURRENT_DMA               (KDATA_BASE_ADDR + 0x0026)
+#define KDATA_DMA_SWITCH                (KDATA_BASE_ADDR + 0x0027)
+#define KDATA_DMA_ACTIVE                (KDATA_BASE_ADDR + 0x0028)
+
+#define KDATA_DMA_XFER0                 (KDATA_BASE_ADDR + 0x0029)
+#define KDATA_DMA_XFER1                 (KDATA_BASE_ADDR + 0x002A)
+#define KDATA_DMA_XFER2                 (KDATA_BASE_ADDR + 0x002B)
+#define KDATA_DMA_XFER3                 (KDATA_BASE_ADDR + 0x002C)
+#define KDATA_DMA_XFER4                 (KDATA_BASE_ADDR + 0x002D)
+#define KDATA_DMA_XFER5                 (KDATA_BASE_ADDR + 0x002E)
+#define KDATA_DMA_XFER6                 (KDATA_BASE_ADDR + 0x002F)
+#define KDATA_DMA_XFER7                 (KDATA_BASE_ADDR + 0x0030)
+#define KDATA_DMA_XFER8                 (KDATA_BASE_ADDR + 0x0031)
+#define KDATA_DMA_XFER_ENDMARK          (KDATA_BASE_ADDR + 0x0032)
+
+#define KDATA_I2S_SAMPLE_COUNT          (KDATA_BASE_ADDR + 0x0033)
+#define KDATA_I2S_INT_METER             (KDATA_BASE_ADDR + 0x0034)
+#define KDATA_I2S_ACTIVE                (KDATA_BASE_ADDR + 0x0035)
+
+#define KDATA_TIMER_COUNT_RELOAD        (KDATA_BASE_ADDR + 0x0036)
+#define KDATA_TIMER_COUNT_CURRENT       (KDATA_BASE_ADDR + 0x0037)
+
+#define KDATA_HALT_SYNCH_CLIENT         (KDATA_BASE_ADDR + 0x0038)
+#define KDATA_HALT_SYNCH_DMA            (KDATA_BASE_ADDR + 0x0039)
+#define KDATA_HALT_ACKNOWLEDGE          (KDATA_BASE_ADDR + 0x003A)
+
+#define KDATA_ADC1_XFER0                (KDATA_BASE_ADDR + 0x003B)
+#define KDATA_ADC1_XFER_ENDMARK         (KDATA_BASE_ADDR + 0x003C)
+#define KDATA_ADC1_LEFT_VOLUME			(KDATA_BASE_ADDR + 0x003D)
+#define KDATA_ADC1_RIGHT_VOLUME  		(KDATA_BASE_ADDR + 0x003E)
+#define KDATA_ADC1_LEFT_SUR_VOL			(KDATA_BASE_ADDR + 0x003F)
+#define KDATA_ADC1_RIGHT_SUR_VOL		(KDATA_BASE_ADDR + 0x0040)
+
+#define KDATA_ADC2_XFER0                (KDATA_BASE_ADDR + 0x0041)
+#define KDATA_ADC2_XFER_ENDMARK         (KDATA_BASE_ADDR + 0x0042)
+#define KDATA_ADC2_LEFT_VOLUME			(KDATA_BASE_ADDR + 0x0043)
+#define KDATA_ADC2_RIGHT_VOLUME			(KDATA_BASE_ADDR + 0x0044)
+#define KDATA_ADC2_LEFT_SUR_VOL			(KDATA_BASE_ADDR + 0x0045)
+#define KDATA_ADC2_RIGHT_SUR_VOL		(KDATA_BASE_ADDR + 0x0046)
+
+#define KDATA_CD_XFER0					(KDATA_BASE_ADDR + 0x0047)					
+#define KDATA_CD_XFER_ENDMARK			(KDATA_BASE_ADDR + 0x0048)
+#define KDATA_CD_LEFT_VOLUME			(KDATA_BASE_ADDR + 0x0049)
+#define KDATA_CD_RIGHT_VOLUME			(KDATA_BASE_ADDR + 0x004A)
+#define KDATA_CD_LEFT_SUR_VOL			(KDATA_BASE_ADDR + 0x004B)
+#define KDATA_CD_RIGHT_SUR_VOL			(KDATA_BASE_ADDR + 0x004C)
+
+#define KDATA_MIC_XFER0					(KDATA_BASE_ADDR + 0x004D)
+#define KDATA_MIC_XFER_ENDMARK			(KDATA_BASE_ADDR + 0x004E)
+#define KDATA_MIC_VOLUME				(KDATA_BASE_ADDR + 0x004F)
+#define KDATA_MIC_SUR_VOL				(KDATA_BASE_ADDR + 0x0050)
+
+#define KDATA_I2S_XFER0                 (KDATA_BASE_ADDR + 0x0051)
+#define KDATA_I2S_XFER_ENDMARK          (KDATA_BASE_ADDR + 0x0052)
+
+#define KDATA_CHI_XFER0                 (KDATA_BASE_ADDR + 0x0053)
+#define KDATA_CHI_XFER_ENDMARK          (KDATA_BASE_ADDR + 0x0054)
+
+#define KDATA_SPDIF_XFER                (KDATA_BASE_ADDR + 0x0055)
+#define KDATA_SPDIF_CURRENT_FRAME       (KDATA_BASE_ADDR + 0x0056)
+#define KDATA_SPDIF_FRAME0              (KDATA_BASE_ADDR + 0x0057)
+#define KDATA_SPDIF_FRAME1              (KDATA_BASE_ADDR + 0x0058)
+#define KDATA_SPDIF_FRAME2              (KDATA_BASE_ADDR + 0x0059)
+
+#define KDATA_SPDIF_REQUEST             (KDATA_BASE_ADDR + 0x005A)
+#define KDATA_SPDIF_TEMP                (KDATA_BASE_ADDR + 0x005B)
+
+#define KDATA_SPDIFIN_XFER0             (KDATA_BASE_ADDR + 0x005C)
+#define KDATA_SPDIFIN_XFER_ENDMARK      (KDATA_BASE_ADDR + 0x005D)
+#define KDATA_SPDIFIN_INT_METER         (KDATA_BASE_ADDR + 0x005E)
+
+#define KDATA_DSP_RESET_COUNT           (KDATA_BASE_ADDR + 0x005F)
+#define KDATA_DEBUG_OUTPUT              (KDATA_BASE_ADDR + 0x0060)
+
+#define KDATA_KERNEL_ISR_LIST           (KDATA_BASE_ADDR + 0x0061)
+
+#define KDATA_KERNEL_ISR_CBSR1          (KDATA_BASE_ADDR + 0x0062)
+#define KDATA_KERNEL_ISR_CBER1          (KDATA_BASE_ADDR + 0x0063)
+#define KDATA_KERNEL_ISR_CBCR           (KDATA_BASE_ADDR + 0x0064)
+#define KDATA_KERNEL_ISR_AR0            (KDATA_BASE_ADDR + 0x0065)
+#define KDATA_KERNEL_ISR_AR1            (KDATA_BASE_ADDR + 0x0066)
+#define KDATA_KERNEL_ISR_AR2            (KDATA_BASE_ADDR + 0x0067)
+#define KDATA_KERNEL_ISR_AR3            (KDATA_BASE_ADDR + 0x0068)
+#define KDATA_KERNEL_ISR_AR4            (KDATA_BASE_ADDR + 0x0069)
+#define KDATA_KERNEL_ISR_AR5            (KDATA_BASE_ADDR + 0x006A)
+#define KDATA_KERNEL_ISR_BRCR           (KDATA_BASE_ADDR + 0x006B)
+#define KDATA_KERNEL_ISR_PASR           (KDATA_BASE_ADDR + 0x006C)
+#define KDATA_KERNEL_ISR_PAER           (KDATA_BASE_ADDR + 0x006D)
+
+#define KDATA_CLIENT_SCRATCH0           (KDATA_BASE_ADDR + 0x006E)
+#define KDATA_CLIENT_SCRATCH1           (KDATA_BASE_ADDR + 0x006F)
+#define KDATA_KERNEL_SCRATCH            (KDATA_BASE_ADDR + 0x0070)
+#define KDATA_KERNEL_ISR_SCRATCH        (KDATA_BASE_ADDR + 0x0071)
+
+#define KDATA_OUEUE_LEFT                (KDATA_BASE_ADDR + 0x0072)
+#define KDATA_QUEUE_RIGHT               (KDATA_BASE_ADDR + 0x0073)
+
+#define KDATA_ADC1_REQUEST              (KDATA_BASE_ADDR + 0x0074)
+#define KDATA_ADC2_REQUEST              (KDATA_BASE_ADDR + 0x0075)
+#define KDATA_CD_REQUEST				(KDATA_BASE_ADDR + 0x0076)
+#define KDATA_MIC_REQUEST				(KDATA_BASE_ADDR + 0x0077)
+
+#define KDATA_ADC1_MIXER_REQUEST        (KDATA_BASE_ADDR + 0x0078)
+#define KDATA_ADC2_MIXER_REQUEST        (KDATA_BASE_ADDR + 0x0079)
+#define KDATA_CD_MIXER_REQUEST			(KDATA_BASE_ADDR + 0x007A)
+#define KDATA_MIC_MIXER_REQUEST			(KDATA_BASE_ADDR + 0x007B)
+#define KDATA_MIC_SYNC_COUNTER			(KDATA_BASE_ADDR + 0x007C)
+
+/*
+ * second 'segment' (?) reserved for mixer
+ * buffers..
+ */
+
+#define KDATA_MIXER_WORD0               (KDATA_BASE_ADDR2 + 0x0000)
+#define KDATA_MIXER_WORD1               (KDATA_BASE_ADDR2 + 0x0001)
+#define KDATA_MIXER_WORD2               (KDATA_BASE_ADDR2 + 0x0002)
+#define KDATA_MIXER_WORD3               (KDATA_BASE_ADDR2 + 0x0003)
+#define KDATA_MIXER_WORD4               (KDATA_BASE_ADDR2 + 0x0004)
+#define KDATA_MIXER_WORD5               (KDATA_BASE_ADDR2 + 0x0005)
+#define KDATA_MIXER_WORD6               (KDATA_BASE_ADDR2 + 0x0006)
+#define KDATA_MIXER_WORD7               (KDATA_BASE_ADDR2 + 0x0007)
+#define KDATA_MIXER_WORD8               (KDATA_BASE_ADDR2 + 0x0008)
+#define KDATA_MIXER_WORD9               (KDATA_BASE_ADDR2 + 0x0009)
+#define KDATA_MIXER_WORDA               (KDATA_BASE_ADDR2 + 0x000A)
+#define KDATA_MIXER_WORDB               (KDATA_BASE_ADDR2 + 0x000B)
+#define KDATA_MIXER_WORDC               (KDATA_BASE_ADDR2 + 0x000C)
+#define KDATA_MIXER_WORDD               (KDATA_BASE_ADDR2 + 0x000D)
+#define KDATA_MIXER_WORDE               (KDATA_BASE_ADDR2 + 0x000E)
+#define KDATA_MIXER_WORDF               (KDATA_BASE_ADDR2 + 0x000F)
+
+#define KDATA_MIXER_XFER0               (KDATA_BASE_ADDR2 + 0x0010)
+#define KDATA_MIXER_XFER1               (KDATA_BASE_ADDR2 + 0x0011)
+#define KDATA_MIXER_XFER2               (KDATA_BASE_ADDR2 + 0x0012)
+#define KDATA_MIXER_XFER3               (KDATA_BASE_ADDR2 + 0x0013)
+#define KDATA_MIXER_XFER4               (KDATA_BASE_ADDR2 + 0x0014)
+#define KDATA_MIXER_XFER5               (KDATA_BASE_ADDR2 + 0x0015)
+#define KDATA_MIXER_XFER6               (KDATA_BASE_ADDR2 + 0x0016)
+#define KDATA_MIXER_XFER7               (KDATA_BASE_ADDR2 + 0x0017)
+#define KDATA_MIXER_XFER8               (KDATA_BASE_ADDR2 + 0x0018)
+#define KDATA_MIXER_XFER9               (KDATA_BASE_ADDR2 + 0x0019)
+#define KDATA_MIXER_XFER_ENDMARK        (KDATA_BASE_ADDR2 + 0x001A)
+
+#define KDATA_MIXER_TASK_NUMBER         (KDATA_BASE_ADDR2 + 0x001B)
+#define KDATA_CURRENT_MIXER             (KDATA_BASE_ADDR2 + 0x001C)
+#define KDATA_MIXER_ACTIVE              (KDATA_BASE_ADDR2 + 0x001D)
+#define KDATA_MIXER_BANK_STATUS         (KDATA_BASE_ADDR2 + 0x001E)
+#define KDATA_DAC_LEFT_VOLUME	        (KDATA_BASE_ADDR2 + 0x001F)
+#define KDATA_DAC_RIGHT_VOLUME          (KDATA_BASE_ADDR2 + 0x0020)
+
+#define MAX_INSTANCE_MINISRC            (KDATA_INSTANCE_MINISRC_ENDMARK - KDATA_INSTANCE0_MINISRC)
+#define MAX_VIRTUAL_DMA_CHANNELS        (KDATA_DMA_XFER_ENDMARK - KDATA_DMA_XFER0)
+#define MAX_VIRTUAL_MIXER_CHANNELS      (KDATA_MIXER_XFER_ENDMARK - KDATA_MIXER_XFER0)
+#define MAX_VIRTUAL_ADC1_CHANNELS       (KDATA_ADC1_XFER_ENDMARK - KDATA_ADC1_XFER0)
+
+/*
+ * client data area offsets
+ */
+#define CDATA_INSTANCE_READY            0x00
+
+#define CDATA_HOST_SRC_ADDRL            0x01
+#define CDATA_HOST_SRC_ADDRH            0x02
+#define CDATA_HOST_SRC_END_PLUS_1L      0x03
+#define CDATA_HOST_SRC_END_PLUS_1H      0x04
+#define CDATA_HOST_SRC_CURRENTL         0x05
+#define CDATA_HOST_SRC_CURRENTH         0x06
+
+#define CDATA_IN_BUF_CONNECT            0x07
+#define CDATA_OUT_BUF_CONNECT           0x08
+
+#define CDATA_IN_BUF_BEGIN              0x09
+#define CDATA_IN_BUF_END_PLUS_1         0x0A
+#define CDATA_IN_BUF_HEAD               0x0B
+#define CDATA_IN_BUF_TAIL               0x0C
+#define CDATA_OUT_BUF_BEGIN             0x0D
+#define CDATA_OUT_BUF_END_PLUS_1        0x0E
+#define CDATA_OUT_BUF_HEAD              0x0F
+#define CDATA_OUT_BUF_TAIL              0x10
+
+#define CDATA_DMA_CONTROL               0x11
+#define CDATA_RESERVED                  0x12
+
+#define CDATA_FREQUENCY                 0x13
+#define CDATA_LEFT_VOLUME               0x14
+#define CDATA_RIGHT_VOLUME              0x15
+#define CDATA_LEFT_SUR_VOL              0x16
+#define CDATA_RIGHT_SUR_VOL             0x17
+
+#define CDATA_HEADER_LEN                0x18
+
+#define SRC3_DIRECTION_OFFSET           CDATA_HEADER_LEN
+#define SRC3_MODE_OFFSET                (CDATA_HEADER_LEN + 1)
+#define SRC3_WORD_LENGTH_OFFSET         (CDATA_HEADER_LEN + 2)
+#define SRC3_PARAMETER_OFFSET           (CDATA_HEADER_LEN + 3)
+#define SRC3_COEFF_ADDR_OFFSET          (CDATA_HEADER_LEN + 8)
+#define SRC3_FILTAP_ADDR_OFFSET         (CDATA_HEADER_LEN + 10)
+#define SRC3_TEMP_INBUF_ADDR_OFFSET     (CDATA_HEADER_LEN + 16)
+#define SRC3_TEMP_OUTBUF_ADDR_OFFSET    (CDATA_HEADER_LEN + 17)
+
+#define MINISRC_IN_BUFFER_SIZE   ( 0x50 * 2 )
+#define MINISRC_OUT_BUFFER_SIZE  ( 0x50 * 2 * 2)
+#define MINISRC_TMP_BUFFER_SIZE  ( 112 + ( MINISRC_BIQUAD_STAGE * 3 + 4 ) * 2 * 2 )
+#define MINISRC_BIQUAD_STAGE    2
+#define MINISRC_COEF_LOC          0x175
+
+#define DMACONTROL_BLOCK_MASK           0x000F
+#define  DMAC_BLOCK0_SELECTOR           0x0000
+#define  DMAC_BLOCK1_SELECTOR           0x0001
+#define  DMAC_BLOCK2_SELECTOR           0x0002
+#define  DMAC_BLOCK3_SELECTOR           0x0003
+#define  DMAC_BLOCK4_SELECTOR           0x0004
+#define  DMAC_BLOCK5_SELECTOR           0x0005
+#define  DMAC_BLOCK6_SELECTOR           0x0006
+#define  DMAC_BLOCK7_SELECTOR           0x0007
+#define  DMAC_BLOCK8_SELECTOR           0x0008
+#define  DMAC_BLOCK9_SELECTOR           0x0009
+#define  DMAC_BLOCKA_SELECTOR           0x000A
+#define  DMAC_BLOCKB_SELECTOR           0x000B
+#define  DMAC_BLOCKC_SELECTOR           0x000C
+#define  DMAC_BLOCKD_SELECTOR           0x000D
+#define  DMAC_BLOCKE_SELECTOR           0x000E
+#define  DMAC_BLOCKF_SELECTOR           0x000F
+#define DMACONTROL_PAGE_MASK            0x00F0
+#define  DMAC_PAGE0_SELECTOR            0x0030
+#define  DMAC_PAGE1_SELECTOR            0x0020
+#define  DMAC_PAGE2_SELECTOR            0x0010
+#define  DMAC_PAGE3_SELECTOR            0x0000
+#define DMACONTROL_AUTOREPEAT           0x1000
+#define DMACONTROL_STOPPED              0x2000
+#define DMACONTROL_DIRECTION            0x0100
+
+/*
+ * an arbitrary volume we set the internal
+ * volume settings to so that the ac97 volume
+ * range is a little less insane.  0x7fff is 
+ * max.
+ */
+#define ARB_VOLUME ( 0x6800 )
+
+/*
+ */
+
+struct m3_list {
+	int curlen;
+	int mem_addr;
+	int max;
+};
+
+struct m3_dma {
+
+	int number;
+	struct snd_pcm_substream *substream;
+
+	struct assp_instance {
+		unsigned short code, data;
+	} inst;
+
+	int running;
+	int opened;
+
+	unsigned long buffer_addr;
+	int dma_size;
+	int period_size;
+	unsigned int hwptr;
+	int count;
+
+	int index[3];
+	struct m3_list *index_list[3];
+
+        int in_lists;
+	
+	struct list_head list;
+
+};
+    
+struct snd_m3 {
+	
+	struct snd_card *card;
+
+	unsigned long iobase;
+
+	int irq;
+	unsigned int allegro_flag : 1;
+
+	struct snd_ac97 *ac97;
+
+	struct snd_pcm *pcm;
+
+	struct pci_dev *pci;
+
+	int dacs_active;
+	int timer_users;
+
+	struct m3_list  msrc_list;
+	struct m3_list  mixer_list;
+	struct m3_list  adc1_list;
+	struct m3_list  dma_list;
+
+	/* for storing reset state..*/
+	u8 reset_state;
+
+	int external_amp;
+	int amp_gpio;	/* gpio pin #  for external amp, -1 = default */
+	unsigned int hv_config;		/* hardware-volume config bits */
+	unsigned irda_workaround :1;	/* avoid to touch 0x10 on GPIO_DIRECTION
+					   (e.g. for IrDA on Dell Inspirons) */
+	unsigned is_omnibook :1;	/* Do HP OmniBook GPIO magic? */
+
+	/* midi */
+	struct snd_rawmidi *rmidi;
+
+	/* pcm streams */
+	int num_substreams;
+	struct m3_dma *substreams;
+
+	spinlock_t reg_lock;
+
+#ifdef CONFIG_SND_MAESTRO3_INPUT
+	struct input_dev *input_dev;
+	char phys[64];			/* physical device path */
+#else
+	spinlock_t ac97_lock;
+	struct snd_kcontrol *master_switch;
+	struct snd_kcontrol *master_volume;
+	struct tasklet_struct hwvol_tq;
+#endif
+
+	unsigned int in_suspend;
+
+#ifdef CONFIG_PM
+	u16 *suspend_mem;
+#endif
+
+	const struct firmware *assp_kernel_image;
+	const struct firmware *assp_minisrc_image;
+};
+
+/*
+ * pci ids
+ */
+static DEFINE_PCI_DEVICE_TABLE(snd_m3_ids) = {
+	{PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_ALLEGRO_1, PCI_ANY_ID, PCI_ANY_ID,
+	 PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+	{PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_ALLEGRO, PCI_ANY_ID, PCI_ANY_ID,
+	 PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+	{PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_CANYON3D_2LE, PCI_ANY_ID, PCI_ANY_ID,
+	 PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+	{PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_CANYON3D_2, PCI_ANY_ID, PCI_ANY_ID,
+	 PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+	{PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3, PCI_ANY_ID, PCI_ANY_ID,
+	 PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+	{PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3_1, PCI_ANY_ID, PCI_ANY_ID,
+	 PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+	{PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3_HW, PCI_ANY_ID, PCI_ANY_ID,
+	 PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+	{PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_MAESTRO3_2, PCI_ANY_ID, PCI_ANY_ID,
+	 PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+	{0,},
+};
+
+MODULE_DEVICE_TABLE(pci, snd_m3_ids);
+
+static struct snd_pci_quirk m3_amp_quirk_list[] __devinitdata = {
+	SND_PCI_QUIRK(0x0E11, 0x0094, "Compaq Evo N600c", 0x0c),
+	SND_PCI_QUIRK(0x10f7, 0x833e, "Panasonic CF-28", 0x0d),
+	SND_PCI_QUIRK(0x10f7, 0x833d, "Panasonic CF-72", 0x0d),
+	SND_PCI_QUIRK(0x1033, 0x80f1, "NEC LM800J/7", 0x03),
+	SND_PCI_QUIRK(0x1509, 0x1740, "LEGEND ZhaoYang 3100CF", 0x03),
+	{ } /* END */
+};
+
+static struct snd_pci_quirk m3_irda_quirk_list[] __devinitdata = {
+	SND_PCI_QUIRK(0x1028, 0x00b0, "Dell Inspiron 4000", 1),
+	SND_PCI_QUIRK(0x1028, 0x00a4, "Dell Inspiron 8000", 1),
+	SND_PCI_QUIRK(0x1028, 0x00e6, "Dell Inspiron 8100", 1),
+	{ } /* END */
+};
+
+/* hardware volume quirks */
+static struct snd_pci_quirk m3_hv_quirk_list[] __devinitdata = {
+	/* Allegro chips */
+	SND_PCI_QUIRK(0x0E11, 0x002E, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x0E11, 0x0094, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x0E11, 0xB112, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x0E11, 0xB114, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x103C, 0x0012, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x103C, 0x0018, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x103C, 0x001C, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x103C, 0x001D, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x103C, 0x001E, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x107B, 0x3350, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x10F7, 0x8338, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x10F7, 0x833C, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x10F7, 0x833D, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x10F7, 0x833E, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x10F7, 0x833F, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x13BD, 0x1018, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x13BD, 0x1019, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x13BD, 0x101A, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x14FF, 0x0F03, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x14FF, 0x0F04, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x14FF, 0x0F05, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x156D, 0xB400, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x156D, 0xB795, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x156D, 0xB797, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x156D, 0xC700, NULL, HV_CTRL_ENABLE | HV_BUTTON_FROM_GD),
+	SND_PCI_QUIRK(0x1033, 0x80F1, NULL,
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x103C, 0x001A, NULL, /* HP OmniBook 6100 */
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x107B, 0x340A, NULL,
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x107B, 0x3450, NULL,
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x109F, 0x3134, NULL,
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x109F, 0x3161, NULL,
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x144D, 0x3280, NULL,
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x144D, 0x3281, NULL,
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x144D, 0xC002, NULL,
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x144D, 0xC003, NULL,
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x1509, 0x1740, NULL,
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x1610, 0x0010, NULL,
+		      HV_CTRL_ENABLE | HV_BUTTON_FROM_GD | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x1042, 0x1042, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x107B, 0x9500, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x14FF, 0x0F06, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x1558, 0x8586, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x161F, 0x2011, NULL, HV_CTRL_ENABLE),
+	/* Maestro3 chips */
+	SND_PCI_QUIRK(0x103C, 0x000E, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x103C, 0x0010, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x103C, 0x0011, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x103C, 0x001B, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x104D, 0x80A6, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x104D, 0x80AA, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x107B, 0x5300, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x110A, 0x1998, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x13BD, 0x1015, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x13BD, 0x101C, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x13BD, 0x1802, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x1599, 0x0715, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x5643, 0x5643, NULL, HV_CTRL_ENABLE),
+	SND_PCI_QUIRK(0x144D, 0x3260, NULL, HV_CTRL_ENABLE | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x144D, 0x3261, NULL, HV_CTRL_ENABLE | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x144D, 0xC000, NULL, HV_CTRL_ENABLE | REDUCED_DEBOUNCE),
+	SND_PCI_QUIRK(0x144D, 0xC001, NULL, HV_CTRL_ENABLE | REDUCED_DEBOUNCE),
+	{ } /* END */
+};
+
+/* HP Omnibook quirks */
+static struct snd_pci_quirk m3_omnibook_quirk_list[] __devinitdata = {
+	SND_PCI_QUIRK_ID(0x103c, 0x0010), /* HP OmniBook 6000 */
+	SND_PCI_QUIRK_ID(0x103c, 0x0011), /* HP OmniBook 500 */
+	{ } /* END */
+};
+
+/*
+ * lowlevel functions
+ */
+
+static inline void snd_m3_outw(struct snd_m3 *chip, u16 value, unsigned long reg)
+{
+	outw(value, chip->iobase + reg);
+}
+
+static inline u16 snd_m3_inw(struct snd_m3 *chip, unsigned long reg)
+{
+	return inw(chip->iobase + reg);
+}
+
+static inline void snd_m3_outb(struct snd_m3 *chip, u8 value, unsigned long reg)
+{
+	outb(value, chip->iobase + reg);
+}
+
+static inline u8 snd_m3_inb(struct snd_m3 *chip, unsigned long reg)
+{
+	return inb(chip->iobase + reg);
+}
+
+/*
+ * access 16bit words to the code or data regions of the dsp's memory.
+ * index addresses 16bit words.
+ */
+static u16 snd_m3_assp_read(struct snd_m3 *chip, u16 region, u16 index)
+{
+	snd_m3_outw(chip, region & MEMTYPE_MASK, DSP_PORT_MEMORY_TYPE);
+	snd_m3_outw(chip, index, DSP_PORT_MEMORY_INDEX);
+	return snd_m3_inw(chip, DSP_PORT_MEMORY_DATA);
+}
+
+static void snd_m3_assp_write(struct snd_m3 *chip, u16 region, u16 index, u16 data)
+{
+	snd_m3_outw(chip, region & MEMTYPE_MASK, DSP_PORT_MEMORY_TYPE);
+	snd_m3_outw(chip, index, DSP_PORT_MEMORY_INDEX);
+	snd_m3_outw(chip, data, DSP_PORT_MEMORY_DATA);
+}
+
+static void snd_m3_assp_halt(struct snd_m3 *chip)
+{
+	chip->reset_state = snd_m3_inb(chip, DSP_PORT_CONTROL_REG_B) & ~REGB_STOP_CLOCK;
+	msleep(10);
+	snd_m3_outb(chip, chip->reset_state & ~REGB_ENABLE_RESET, DSP_PORT_CONTROL_REG_B);
+}
+
+static void snd_m3_assp_continue(struct snd_m3 *chip)
+{
+	snd_m3_outb(chip, chip->reset_state | REGB_ENABLE_RESET, DSP_PORT_CONTROL_REG_B);
+}
+
+
+/*
+ * This makes me sad. the maestro3 has lists
+ * internally that must be packed.. 0 terminates,
+ * apparently, or maybe all unused entries have
+ * to be 0, the lists have static lengths set
+ * by the binary code images.
+ */
+
+static int snd_m3_add_list(struct snd_m3 *chip, struct m3_list *list, u16 val)
+{
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  list->mem_addr + list->curlen,
+			  val);
+	return list->curlen++;
+}
+
+static void snd_m3_remove_list(struct snd_m3 *chip, struct m3_list *list, int index)
+{
+	u16  val;
+	int lastindex = list->curlen - 1;
+
+	if (index != lastindex) {
+		val = snd_m3_assp_read(chip, MEMTYPE_INTERNAL_DATA,
+				       list->mem_addr + lastindex);
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+				  list->mem_addr + index,
+				  val);
+	}
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  list->mem_addr + lastindex,
+			  0);
+
+	list->curlen--;
+}
+
+static void snd_m3_inc_timer_users(struct snd_m3 *chip)
+{
+	chip->timer_users++;
+	if (chip->timer_users != 1) 
+		return;
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  KDATA_TIMER_COUNT_RELOAD,
+			  240);
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  KDATA_TIMER_COUNT_CURRENT,
+			  240);
+
+	snd_m3_outw(chip,
+		    snd_m3_inw(chip, HOST_INT_CTRL) | CLKRUN_GEN_ENABLE,
+		    HOST_INT_CTRL);
+}
+
+static void snd_m3_dec_timer_users(struct snd_m3 *chip)
+{
+	chip->timer_users--;
+	if (chip->timer_users > 0)  
+		return;
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  KDATA_TIMER_COUNT_RELOAD,
+			  0);
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  KDATA_TIMER_COUNT_CURRENT,
+			  0);
+
+	snd_m3_outw(chip,
+		    snd_m3_inw(chip, HOST_INT_CTRL) & ~CLKRUN_GEN_ENABLE,
+		    HOST_INT_CTRL);
+}
+
+/*
+ * start/stop
+ */
+
+/* spinlock held! */
+static int snd_m3_pcm_start(struct snd_m3 *chip, struct m3_dma *s,
+			    struct snd_pcm_substream *subs)
+{
+	if (! s || ! subs)
+		return -EINVAL;
+
+	snd_m3_inc_timer_users(chip);
+	switch (subs->stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		chip->dacs_active++;
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+				  s->inst.data + CDATA_INSTANCE_READY, 1);
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+				  KDATA_MIXER_TASK_NUMBER,
+				  chip->dacs_active);
+		break;
+	case SNDRV_PCM_STREAM_CAPTURE:
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+				  KDATA_ADC1_REQUEST, 1);
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+				  s->inst.data + CDATA_INSTANCE_READY, 1);
+		break;
+	}
+	return 0;
+}
+
+/* spinlock held! */
+static int snd_m3_pcm_stop(struct snd_m3 *chip, struct m3_dma *s,
+			   struct snd_pcm_substream *subs)
+{
+	if (! s || ! subs)
+		return -EINVAL;
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_INSTANCE_READY, 0);
+	snd_m3_dec_timer_users(chip);
+	switch (subs->stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		chip->dacs_active--;
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+				  KDATA_MIXER_TASK_NUMBER, 
+				  chip->dacs_active);
+		break;
+	case SNDRV_PCM_STREAM_CAPTURE:
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+				  KDATA_ADC1_REQUEST, 0);
+		break;
+	}
+	return 0;
+}
+
+static int
+snd_m3_pcm_trigger(struct snd_pcm_substream *subs, int cmd)
+{
+	struct snd_m3 *chip = snd_pcm_substream_chip(subs);
+	struct m3_dma *s = subs->runtime->private_data;
+	int err = -EINVAL;
+
+	if (snd_BUG_ON(!s))
+		return -ENXIO;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (s->running)
+			err = -EBUSY;
+		else {
+			s->running = 1;
+			err = snd_m3_pcm_start(chip, s, subs);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (! s->running)
+			err = 0; /* should return error? */
+		else {
+			s->running = 0;
+			err = snd_m3_pcm_stop(chip, s, subs);
+		}
+		break;
+	}
+	spin_unlock(&chip->reg_lock);
+	return err;
+}
+
+/*
+ * setup
+ */
+static void 
+snd_m3_pcm_setup1(struct snd_m3 *chip, struct m3_dma *s, struct snd_pcm_substream *subs)
+{
+	int dsp_in_size, dsp_out_size, dsp_in_buffer, dsp_out_buffer;
+	struct snd_pcm_runtime *runtime = subs->runtime;
+
+	if (subs->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		dsp_in_size = MINISRC_IN_BUFFER_SIZE - (0x20 * 2);
+		dsp_out_size = MINISRC_OUT_BUFFER_SIZE - (0x20 * 2);
+	} else {
+		dsp_in_size = MINISRC_IN_BUFFER_SIZE - (0x10 * 2);
+		dsp_out_size = MINISRC_OUT_BUFFER_SIZE - (0x10 * 2);
+	}
+	dsp_in_buffer = s->inst.data + (MINISRC_TMP_BUFFER_SIZE / 2);
+	dsp_out_buffer = dsp_in_buffer + (dsp_in_size / 2) + 1;
+
+	s->dma_size = frames_to_bytes(runtime, runtime->buffer_size);
+	s->period_size = frames_to_bytes(runtime, runtime->period_size);
+	s->hwptr = 0;
+	s->count = 0;
+
+#define LO(x) ((x) & 0xffff)
+#define HI(x) LO((x) >> 16)
+
+	/* host dma buffer pointers */
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_HOST_SRC_ADDRL,
+			  LO(s->buffer_addr));
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_HOST_SRC_ADDRH,
+			  HI(s->buffer_addr));
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_HOST_SRC_END_PLUS_1L,
+			  LO(s->buffer_addr + s->dma_size));
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_HOST_SRC_END_PLUS_1H,
+			  HI(s->buffer_addr + s->dma_size));
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_HOST_SRC_CURRENTL,
+			  LO(s->buffer_addr));
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_HOST_SRC_CURRENTH,
+			  HI(s->buffer_addr));
+#undef LO
+#undef HI
+
+	/* dsp buffers */
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_IN_BUF_BEGIN,
+			  dsp_in_buffer);
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_IN_BUF_END_PLUS_1,
+			  dsp_in_buffer + (dsp_in_size / 2));
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_IN_BUF_HEAD,
+			  dsp_in_buffer);
+    
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_IN_BUF_TAIL,
+			  dsp_in_buffer);
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_OUT_BUF_BEGIN,
+			  dsp_out_buffer);
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_OUT_BUF_END_PLUS_1,
+			  dsp_out_buffer + (dsp_out_size / 2));
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_OUT_BUF_HEAD,
+			  dsp_out_buffer);
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_OUT_BUF_TAIL,
+			  dsp_out_buffer);
+}
+
+static void snd_m3_pcm_setup2(struct snd_m3 *chip, struct m3_dma *s,
+			      struct snd_pcm_runtime *runtime)
+{
+	u32 freq;
+
+	/* 
+	 * put us in the lists if we're not already there
+	 */
+	if (! s->in_lists) {
+		s->index[0] = snd_m3_add_list(chip, s->index_list[0],
+					      s->inst.data >> DP_SHIFT_COUNT);
+		s->index[1] = snd_m3_add_list(chip, s->index_list[1],
+					      s->inst.data >> DP_SHIFT_COUNT);
+		s->index[2] = snd_m3_add_list(chip, s->index_list[2],
+					      s->inst.data >> DP_SHIFT_COUNT);
+		s->in_lists = 1;
+	}
+
+	/* write to 'mono' word */
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + SRC3_DIRECTION_OFFSET + 1, 
+			  runtime->channels == 2 ? 0 : 1);
+	/* write to '8bit' word */
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + SRC3_DIRECTION_OFFSET + 2, 
+			  snd_pcm_format_width(runtime->format) == 16 ? 0 : 1);
+
+	/* set up dac/adc rate */
+	freq = ((runtime->rate << 15) + 24000 ) / 48000;
+	if (freq) 
+		freq--;
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_FREQUENCY,
+			  freq);
+}
+
+
+static const struct play_vals {
+	u16 addr, val;
+} pv[] = {
+	{CDATA_LEFT_VOLUME, ARB_VOLUME},
+	{CDATA_RIGHT_VOLUME, ARB_VOLUME},
+	{SRC3_DIRECTION_OFFSET, 0} ,
+	/* +1, +2 are stereo/16 bit */
+	{SRC3_DIRECTION_OFFSET + 3, 0x0000}, /* fraction? */
+	{SRC3_DIRECTION_OFFSET + 4, 0}, /* first l */
+	{SRC3_DIRECTION_OFFSET + 5, 0}, /* first r */
+	{SRC3_DIRECTION_OFFSET + 6, 0}, /* second l */
+	{SRC3_DIRECTION_OFFSET + 7, 0}, /* second r */
+	{SRC3_DIRECTION_OFFSET + 8, 0}, /* delta l */
+	{SRC3_DIRECTION_OFFSET + 9, 0}, /* delta r */
+	{SRC3_DIRECTION_OFFSET + 10, 0x8000}, /* round */
+	{SRC3_DIRECTION_OFFSET + 11, 0xFF00}, /* higher bute mark */
+	{SRC3_DIRECTION_OFFSET + 13, 0}, /* temp0 */
+	{SRC3_DIRECTION_OFFSET + 14, 0}, /* c fraction */
+	{SRC3_DIRECTION_OFFSET + 15, 0}, /* counter */
+	{SRC3_DIRECTION_OFFSET + 16, 8}, /* numin */
+	{SRC3_DIRECTION_OFFSET + 17, 50*2}, /* numout */
+	{SRC3_DIRECTION_OFFSET + 18, MINISRC_BIQUAD_STAGE - 1}, /* numstage */
+	{SRC3_DIRECTION_OFFSET + 20, 0}, /* filtertap */
+	{SRC3_DIRECTION_OFFSET + 21, 0} /* booster */
+};
+
+
+/* the mode passed should be already shifted and masked */
+static void
+snd_m3_playback_setup(struct snd_m3 *chip, struct m3_dma *s,
+		      struct snd_pcm_substream *subs)
+{
+	unsigned int i;
+
+	/*
+	 * some per client initializers
+	 */
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + SRC3_DIRECTION_OFFSET + 12,
+			  s->inst.data + 40 + 8);
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + SRC3_DIRECTION_OFFSET + 19,
+			  s->inst.code + MINISRC_COEF_LOC);
+
+	/* enable or disable low pass filter? */
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + SRC3_DIRECTION_OFFSET + 22,
+			  subs->runtime->rate > 45000 ? 0xff : 0);
+    
+	/* tell it which way dma is going? */
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_DMA_CONTROL,
+			  DMACONTROL_AUTOREPEAT + DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR);
+
+	/*
+	 * set an armload of static initializers
+	 */
+	for (i = 0; i < ARRAY_SIZE(pv); i++) 
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+				  s->inst.data + pv[i].addr, pv[i].val);
+}
+
+/*
+ *    Native record driver 
+ */
+static const struct rec_vals {
+	u16 addr, val;
+} rv[] = {
+	{CDATA_LEFT_VOLUME, ARB_VOLUME},
+	{CDATA_RIGHT_VOLUME, ARB_VOLUME},
+	{SRC3_DIRECTION_OFFSET, 1} ,
+	/* +1, +2 are stereo/16 bit */
+	{SRC3_DIRECTION_OFFSET + 3, 0x0000}, /* fraction? */
+	{SRC3_DIRECTION_OFFSET + 4, 0}, /* first l */
+	{SRC3_DIRECTION_OFFSET + 5, 0}, /* first r */
+	{SRC3_DIRECTION_OFFSET + 6, 0}, /* second l */
+	{SRC3_DIRECTION_OFFSET + 7, 0}, /* second r */
+	{SRC3_DIRECTION_OFFSET + 8, 0}, /* delta l */
+	{SRC3_DIRECTION_OFFSET + 9, 0}, /* delta r */
+	{SRC3_DIRECTION_OFFSET + 10, 0x8000}, /* round */
+	{SRC3_DIRECTION_OFFSET + 11, 0xFF00}, /* higher bute mark */
+	{SRC3_DIRECTION_OFFSET + 13, 0}, /* temp0 */
+	{SRC3_DIRECTION_OFFSET + 14, 0}, /* c fraction */
+	{SRC3_DIRECTION_OFFSET + 15, 0}, /* counter */
+	{SRC3_DIRECTION_OFFSET + 16, 50},/* numin */
+	{SRC3_DIRECTION_OFFSET + 17, 8}, /* numout */
+	{SRC3_DIRECTION_OFFSET + 18, 0}, /* numstage */
+	{SRC3_DIRECTION_OFFSET + 19, 0}, /* coef */
+	{SRC3_DIRECTION_OFFSET + 20, 0}, /* filtertap */
+	{SRC3_DIRECTION_OFFSET + 21, 0}, /* booster */
+	{SRC3_DIRECTION_OFFSET + 22, 0xff} /* skip lpf */
+};
+
+static void
+snd_m3_capture_setup(struct snd_m3 *chip, struct m3_dma *s, struct snd_pcm_substream *subs)
+{
+	unsigned int i;
+
+	/*
+	 * some per client initializers
+	 */
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + SRC3_DIRECTION_OFFSET + 12,
+			  s->inst.data + 40 + 8);
+
+	/* tell it which way dma is going? */
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  s->inst.data + CDATA_DMA_CONTROL,
+			  DMACONTROL_DIRECTION + DMACONTROL_AUTOREPEAT + 
+			  DMAC_PAGE3_SELECTOR + DMAC_BLOCKF_SELECTOR);
+
+	/*
+	 * set an armload of static initializers
+	 */
+	for (i = 0; i < ARRAY_SIZE(rv); i++) 
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+				  s->inst.data + rv[i].addr, rv[i].val);
+}
+
+static int snd_m3_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *hw_params)
+{
+	struct m3_dma *s = substream->runtime->private_data;
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	/* set buffer address */
+	s->buffer_addr = substream->runtime->dma_addr;
+	if (s->buffer_addr & 0x3) {
+		snd_printk(KERN_ERR "oh my, not aligned\n");
+		s->buffer_addr = s->buffer_addr & ~0x3;
+	}
+	return 0;
+}
+
+static int snd_m3_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct m3_dma *s;
+	
+	if (substream->runtime->private_data == NULL)
+		return 0;
+	s = substream->runtime->private_data;
+	snd_pcm_lib_free_pages(substream);
+	s->buffer_addr = 0;
+	return 0;
+}
+
+static int
+snd_m3_pcm_prepare(struct snd_pcm_substream *subs)
+{
+	struct snd_m3 *chip = snd_pcm_substream_chip(subs);
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct m3_dma *s = runtime->private_data;
+
+	if (snd_BUG_ON(!s))
+		return -ENXIO;
+
+	if (runtime->format != SNDRV_PCM_FORMAT_U8 &&
+	    runtime->format != SNDRV_PCM_FORMAT_S16_LE)
+		return -EINVAL;
+	if (runtime->rate > 48000 ||
+	    runtime->rate < 8000)
+		return -EINVAL;
+
+	spin_lock_irq(&chip->reg_lock);
+
+	snd_m3_pcm_setup1(chip, s, subs);
+
+	if (subs->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		snd_m3_playback_setup(chip, s, subs);
+	else
+		snd_m3_capture_setup(chip, s, subs);
+
+	snd_m3_pcm_setup2(chip, s, runtime);
+
+	spin_unlock_irq(&chip->reg_lock);
+
+	return 0;
+}
+
+/*
+ * get current pointer
+ */
+static unsigned int
+snd_m3_get_pointer(struct snd_m3 *chip, struct m3_dma *s, struct snd_pcm_substream *subs)
+{
+	u16 hi = 0, lo = 0;
+	int retry = 10;
+	u32 addr;
+
+	/*
+	 * try and get a valid answer
+	 */
+	while (retry--) {
+		hi =  snd_m3_assp_read(chip, MEMTYPE_INTERNAL_DATA,
+				       s->inst.data + CDATA_HOST_SRC_CURRENTH);
+
+		lo = snd_m3_assp_read(chip, MEMTYPE_INTERNAL_DATA,
+				      s->inst.data + CDATA_HOST_SRC_CURRENTL);
+
+		if (hi == snd_m3_assp_read(chip, MEMTYPE_INTERNAL_DATA,
+					   s->inst.data + CDATA_HOST_SRC_CURRENTH))
+			break;
+	}
+	addr = lo | ((u32)hi<<16);
+	return (unsigned int)(addr - s->buffer_addr);
+}
+
+static snd_pcm_uframes_t
+snd_m3_pcm_pointer(struct snd_pcm_substream *subs)
+{
+	struct snd_m3 *chip = snd_pcm_substream_chip(subs);
+	unsigned int ptr;
+	struct m3_dma *s = subs->runtime->private_data;
+
+	if (snd_BUG_ON(!s))
+		return 0;
+
+	spin_lock(&chip->reg_lock);
+	ptr = snd_m3_get_pointer(chip, s, subs);
+	spin_unlock(&chip->reg_lock);
+	return bytes_to_frames(subs->runtime, ptr);
+}
+
+
+/* update pointer */
+/* spinlock held! */
+static void snd_m3_update_ptr(struct snd_m3 *chip, struct m3_dma *s)
+{
+	struct snd_pcm_substream *subs = s->substream;
+	unsigned int hwptr;
+	int diff;
+
+	if (! s->running)
+		return;
+
+	hwptr = snd_m3_get_pointer(chip, s, subs);
+
+	/* try to avoid expensive modulo divisions */
+	if (hwptr >= s->dma_size)
+		hwptr %= s->dma_size;
+
+	diff = s->dma_size + hwptr - s->hwptr;
+	if (diff >= s->dma_size)
+		diff %= s->dma_size;
+
+	s->hwptr = hwptr;
+	s->count += diff;
+
+	if (s->count >= (signed)s->period_size) {
+
+		if (s->count < 2 * (signed)s->period_size)
+			s->count -= (signed)s->period_size;
+		else
+			s->count %= s->period_size;
+
+		spin_unlock(&chip->reg_lock);
+		snd_pcm_period_elapsed(subs);
+		spin_lock(&chip->reg_lock);
+	}
+}
+
+/* The m3's hardware volume works by incrementing / decrementing 2 counters
+   (without wrap around) in response to volume button presses and then
+   generating an interrupt. The pair of counters is stored in bits 1-3 and 5-7
+   of a byte wide register. The meaning of bits 0 and 4 is unknown. */
+static void snd_m3_update_hw_volume(unsigned long private_data)
+{
+	struct snd_m3 *chip = (struct snd_m3 *) private_data;
+	int x, val;
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	unsigned long flags;
+#endif
+
+	/* Figure out which volume control button was pushed,
+	   based on differences from the default register
+	   values. */
+	x = inb(chip->iobase + SHADOW_MIX_REG_VOICE) & 0xee;
+
+	/* Reset the volume counters to 4. Tests on the allegro integrated
+	   into a Compaq N600C laptop, have revealed that:
+	   1) Writing any value will result in the 2 counters being reset to
+	      4 so writing 0x88 is not strictly necessary
+	   2) Writing to any of the 4 involved registers will reset all 4
+	      of them (and reading them always returns the same value for all
+	      of them)
+	   It could be that a maestro deviates from this, so leave the code
+	   as is. */
+	outb(0x88, chip->iobase + SHADOW_MIX_REG_VOICE);
+	outb(0x88, chip->iobase + HW_VOL_COUNTER_VOICE);
+	outb(0x88, chip->iobase + SHADOW_MIX_REG_MASTER);
+	outb(0x88, chip->iobase + HW_VOL_COUNTER_MASTER);
+
+	/* Ignore spurious HV interrupts during suspend / resume, this avoids
+	   mistaking them for a mute button press. */
+	if (chip->in_suspend)
+		return;
+
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	if (!chip->master_switch || !chip->master_volume)
+		return;
+
+	/* FIXME: we can't call snd_ac97_* functions since here is in tasklet. */
+	spin_lock_irqsave(&chip->ac97_lock, flags);
+
+	val = chip->ac97->regs[AC97_MASTER_VOL];
+	switch (x) {
+	case 0x88:
+		/* The counters have not changed, yet we've received a HV
+		   interrupt. According to tests run by various people this
+		   happens when pressing the mute button. */
+		val ^= 0x8000;
+		chip->ac97->regs[AC97_MASTER_VOL] = val;
+		outw(val, chip->iobase + CODEC_DATA);
+		outb(AC97_MASTER_VOL, chip->iobase + CODEC_COMMAND);
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->master_switch->id);
+		break;
+	case 0xaa:
+		/* counters increased by 1 -> volume up */
+		if ((val & 0x7f) > 0)
+			val--;
+		if ((val & 0x7f00) > 0)
+			val -= 0x0100;
+		chip->ac97->regs[AC97_MASTER_VOL] = val;
+		outw(val, chip->iobase + CODEC_DATA);
+		outb(AC97_MASTER_VOL, chip->iobase + CODEC_COMMAND);
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->master_volume->id);
+		break;
+	case 0x66:
+		/* counters decreased by 1 -> volume down */
+		if ((val & 0x7f) < 0x1f)
+			val++;
+		if ((val & 0x7f00) < 0x1f00)
+			val += 0x0100;
+		chip->ac97->regs[AC97_MASTER_VOL] = val;
+		outw(val, chip->iobase + CODEC_DATA);
+		outb(AC97_MASTER_VOL, chip->iobase + CODEC_COMMAND);
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->master_volume->id);
+		break;
+	}
+	spin_unlock_irqrestore(&chip->ac97_lock, flags);
+#else
+	if (!chip->input_dev)
+		return;
+
+	val = 0;
+	switch (x) {
+	case 0x88:
+		/* The counters have not changed, yet we've received a HV
+		   interrupt. According to tests run by various people this
+		   happens when pressing the mute button. */
+		val = KEY_MUTE;
+		break;
+	case 0xaa:
+		/* counters increased by 1 -> volume up */
+		val = KEY_VOLUMEUP;
+		break;
+	case 0x66:
+		/* counters decreased by 1 -> volume down */
+		val = KEY_VOLUMEDOWN;
+		break;
+	}
+
+	if (val) {
+		input_report_key(chip->input_dev, val, 1);
+		input_sync(chip->input_dev);
+		input_report_key(chip->input_dev, val, 0);
+		input_sync(chip->input_dev);
+	}
+#endif
+}
+
+static irqreturn_t snd_m3_interrupt(int irq, void *dev_id)
+{
+	struct snd_m3 *chip = dev_id;
+	u8 status;
+	int i;
+
+	status = inb(chip->iobase + HOST_INT_STATUS);
+
+	if (status == 0xff)
+		return IRQ_NONE;
+
+	if (status & HV_INT_PENDING)
+#ifdef CONFIG_SND_MAESTRO3_INPUT
+		snd_m3_update_hw_volume((unsigned long)chip);
+#else
+		tasklet_schedule(&chip->hwvol_tq);
+#endif
+
+	/*
+	 * ack an assp int if its running
+	 * and has an int pending
+	 */
+	if (status & ASSP_INT_PENDING) {
+		u8 ctl = inb(chip->iobase + ASSP_CONTROL_B);
+		if (!(ctl & STOP_ASSP_CLOCK)) {
+			ctl = inb(chip->iobase + ASSP_HOST_INT_STATUS);
+			if (ctl & DSP2HOST_REQ_TIMER) {
+				outb(DSP2HOST_REQ_TIMER, chip->iobase + ASSP_HOST_INT_STATUS);
+				/* update adc/dac info if it was a timer int */
+				spin_lock(&chip->reg_lock);
+				for (i = 0; i < chip->num_substreams; i++) {
+					struct m3_dma *s = &chip->substreams[i];
+					if (s->running)
+						snd_m3_update_ptr(chip, s);
+				}
+				spin_unlock(&chip->reg_lock);
+			}
+		}
+	}
+
+#if 0 /* TODO: not supported yet */
+	if ((status & MPU401_INT_PENDING) && chip->rmidi)
+		snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data, regs);
+#endif
+
+	/* ack ints */
+	outb(status, chip->iobase + HOST_INT_STATUS);
+
+	return IRQ_HANDLED;
+}
+
+
+/*
+ */
+
+static struct snd_pcm_hardware snd_m3_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 /*SNDRV_PCM_INFO_PAUSE |*/
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		8000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(512*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(512*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+};
+
+static struct snd_pcm_hardware snd_m3_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 /*SNDRV_PCM_INFO_PAUSE |*/
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		8000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(512*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(512*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+};
+
+
+/*
+ */
+
+static int
+snd_m3_substream_open(struct snd_m3 *chip, struct snd_pcm_substream *subs)
+{
+	int i;
+	struct m3_dma *s;
+
+	spin_lock_irq(&chip->reg_lock);
+	for (i = 0; i < chip->num_substreams; i++) {
+		s = &chip->substreams[i];
+		if (! s->opened)
+			goto __found;
+	}
+	spin_unlock_irq(&chip->reg_lock);
+	return -ENOMEM;
+__found:
+	s->opened = 1;
+	s->running = 0;
+	spin_unlock_irq(&chip->reg_lock);
+
+	subs->runtime->private_data = s;
+	s->substream = subs;
+
+	/* set list owners */
+	if (subs->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		s->index_list[0] = &chip->mixer_list;
+	} else
+		s->index_list[0] = &chip->adc1_list;
+	s->index_list[1] = &chip->msrc_list;
+	s->index_list[2] = &chip->dma_list;
+
+	return 0;
+}
+
+static void
+snd_m3_substream_close(struct snd_m3 *chip, struct snd_pcm_substream *subs)
+{
+	struct m3_dma *s = subs->runtime->private_data;
+
+	if (s == NULL)
+		return; /* not opened properly */
+
+	spin_lock_irq(&chip->reg_lock);
+	if (s->substream && s->running)
+		snd_m3_pcm_stop(chip, s, s->substream); /* does this happen? */
+	if (s->in_lists) {
+		snd_m3_remove_list(chip, s->index_list[0], s->index[0]);
+		snd_m3_remove_list(chip, s->index_list[1], s->index[1]);
+		snd_m3_remove_list(chip, s->index_list[2], s->index[2]);
+		s->in_lists = 0;
+	}
+	s->running = 0;
+	s->opened = 0;
+	spin_unlock_irq(&chip->reg_lock);
+}
+
+static int
+snd_m3_playback_open(struct snd_pcm_substream *subs)
+{
+	struct snd_m3 *chip = snd_pcm_substream_chip(subs);
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	int err;
+
+	if ((err = snd_m3_substream_open(chip, subs)) < 0)
+		return err;
+
+	runtime->hw = snd_m3_playback;
+
+	return 0;
+}
+
+static int
+snd_m3_playback_close(struct snd_pcm_substream *subs)
+{
+	struct snd_m3 *chip = snd_pcm_substream_chip(subs);
+
+	snd_m3_substream_close(chip, subs);
+	return 0;
+}
+
+static int
+snd_m3_capture_open(struct snd_pcm_substream *subs)
+{
+	struct snd_m3 *chip = snd_pcm_substream_chip(subs);
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	int err;
+
+	if ((err = snd_m3_substream_open(chip, subs)) < 0)
+		return err;
+
+	runtime->hw = snd_m3_capture;
+
+	return 0;
+}
+
+static int
+snd_m3_capture_close(struct snd_pcm_substream *subs)
+{
+	struct snd_m3 *chip = snd_pcm_substream_chip(subs);
+
+	snd_m3_substream_close(chip, subs);
+	return 0;
+}
+
+/*
+ * create pcm instance
+ */
+
+static struct snd_pcm_ops snd_m3_playback_ops = {
+	.open =		snd_m3_playback_open,
+	.close =	snd_m3_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_m3_pcm_hw_params,
+	.hw_free =	snd_m3_pcm_hw_free,
+	.prepare =	snd_m3_pcm_prepare,
+	.trigger =	snd_m3_pcm_trigger,
+	.pointer =	snd_m3_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_m3_capture_ops = {
+	.open =		snd_m3_capture_open,
+	.close =	snd_m3_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_m3_pcm_hw_params,
+	.hw_free =	snd_m3_pcm_hw_free,
+	.prepare =	snd_m3_pcm_prepare,
+	.trigger =	snd_m3_pcm_trigger,
+	.pointer =	snd_m3_pcm_pointer,
+};
+
+static int __devinit
+snd_m3_pcm(struct snd_m3 * chip, int device)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(chip->card, chip->card->driver, device,
+			  MAX_PLAYBACKS, MAX_CAPTURES, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_m3_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_m3_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, chip->card->driver);
+	chip->pcm = pcm;
+	
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci), 64*1024, 64*1024);
+
+	return 0;
+}
+
+
+/*
+ * ac97 interface
+ */
+
+/*
+ * Wait for the ac97 serial bus to be free.
+ * return nonzero if the bus is still busy.
+ */
+static int snd_m3_ac97_wait(struct snd_m3 *chip)
+{
+	int i = 10000;
+
+	do {
+		if (! (snd_m3_inb(chip, 0x30) & 1))
+			return 0;
+		cpu_relax();
+	} while (i-- > 0);
+
+	snd_printk(KERN_ERR "ac97 serial bus busy\n");
+	return 1;
+}
+
+static unsigned short
+snd_m3_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct snd_m3 *chip = ac97->private_data;
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	unsigned long flags;
+#endif
+	unsigned short data = 0xffff;
+
+	if (snd_m3_ac97_wait(chip))
+		goto fail;
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	spin_lock_irqsave(&chip->ac97_lock, flags);
+#endif
+	snd_m3_outb(chip, 0x80 | (reg & 0x7f), CODEC_COMMAND);
+	if (snd_m3_ac97_wait(chip))
+		goto fail_unlock;
+	data = snd_m3_inw(chip, CODEC_DATA);
+fail_unlock:
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	spin_unlock_irqrestore(&chip->ac97_lock, flags);
+#endif
+fail:
+	return data;
+}
+
+static void
+snd_m3_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val)
+{
+	struct snd_m3 *chip = ac97->private_data;
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	unsigned long flags;
+#endif
+
+	if (snd_m3_ac97_wait(chip))
+		return;
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	spin_lock_irqsave(&chip->ac97_lock, flags);
+#endif
+	snd_m3_outw(chip, val, CODEC_DATA);
+	snd_m3_outb(chip, reg & 0x7f, CODEC_COMMAND);
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	spin_unlock_irqrestore(&chip->ac97_lock, flags);
+#endif
+}
+
+
+static void snd_m3_remote_codec_config(int io, int isremote)
+{
+	isremote = isremote ? 1 : 0;
+
+	outw((inw(io + RING_BUS_CTRL_B) & ~SECOND_CODEC_ID_MASK) | isremote,
+	     io + RING_BUS_CTRL_B);
+	outw((inw(io + SDO_OUT_DEST_CTRL) & ~COMMAND_ADDR_OUT) | isremote,
+	     io + SDO_OUT_DEST_CTRL);
+	outw((inw(io + SDO_IN_DEST_CTRL) & ~STATUS_ADDR_IN) | isremote,
+	     io + SDO_IN_DEST_CTRL);
+}
+
+/* 
+ * hack, returns non zero on err 
+ */
+static int snd_m3_try_read_vendor(struct snd_m3 *chip)
+{
+	u16 ret;
+
+	if (snd_m3_ac97_wait(chip))
+		return 1;
+
+	snd_m3_outb(chip, 0x80 | (AC97_VENDOR_ID1 & 0x7f), 0x30);
+
+	if (snd_m3_ac97_wait(chip))
+		return 1;
+
+	ret = snd_m3_inw(chip, 0x32);
+
+	return (ret == 0) || (ret == 0xffff);
+}
+
+static void snd_m3_ac97_reset(struct snd_m3 *chip)
+{
+	u16 dir;
+	int delay1 = 0, delay2 = 0, i;
+	int io = chip->iobase;
+
+	if (chip->allegro_flag) {
+		/*
+		 * the onboard codec on the allegro seems 
+		 * to want to wait a very long time before
+		 * coming back to life 
+		 */
+		delay1 = 50;
+		delay2 = 800;
+	} else {
+		/* maestro3 */
+		delay1 = 20;
+		delay2 = 500;
+	}
+
+	for (i = 0; i < 5; i++) {
+		dir = inw(io + GPIO_DIRECTION);
+		if (!chip->irda_workaround)
+			dir |= 0x10; /* assuming pci bus master? */
+
+		snd_m3_remote_codec_config(io, 0);
+
+		outw(IO_SRAM_ENABLE, io + RING_BUS_CTRL_A);
+		udelay(20);
+
+		outw(dir & ~GPO_PRIMARY_AC97 , io + GPIO_DIRECTION);
+		outw(~GPO_PRIMARY_AC97 , io + GPIO_MASK);
+		outw(0, io + GPIO_DATA);
+		outw(dir | GPO_PRIMARY_AC97, io + GPIO_DIRECTION);
+
+		schedule_timeout_uninterruptible(msecs_to_jiffies(delay1));
+
+		outw(GPO_PRIMARY_AC97, io + GPIO_DATA);
+		udelay(5);
+		/* ok, bring back the ac-link */
+		outw(IO_SRAM_ENABLE | SERIAL_AC_LINK_ENABLE, io + RING_BUS_CTRL_A);
+		outw(~0, io + GPIO_MASK);
+
+		schedule_timeout_uninterruptible(msecs_to_jiffies(delay2));
+
+		if (! snd_m3_try_read_vendor(chip))
+			break;
+
+		delay1 += 10;
+		delay2 += 100;
+
+		snd_printd("maestro3: retrying codec reset with delays of %d and %d ms\n",
+			   delay1, delay2);
+	}
+
+#if 0
+	/* more gung-ho reset that doesn't
+	 * seem to work anywhere :)
+	 */
+	tmp = inw(io + RING_BUS_CTRL_A);
+	outw(RAC_SDFS_ENABLE|LAC_SDFS_ENABLE, io + RING_BUS_CTRL_A);
+	msleep(20);
+	outw(tmp, io + RING_BUS_CTRL_A);
+	msleep(50);
+#endif
+}
+
+static int __devinit snd_m3_mixer(struct snd_m3 *chip)
+{
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	struct snd_ctl_elem_id elem_id;
+#endif
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_m3_ac97_write,
+		.read = snd_m3_ac97_read,
+	};
+
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus)) < 0)
+		return err;
+	
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97)) < 0)
+		return err;
+
+	/* seems ac97 PCM needs initialization.. hack hack.. */
+	snd_ac97_write(chip->ac97, AC97_PCM, 0x8000 | (15 << 8) | 15);
+	schedule_timeout_uninterruptible(msecs_to_jiffies(100));
+	snd_ac97_write(chip->ac97, AC97_PCM, 0);
+
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	memset(&elem_id, 0, sizeof(elem_id));
+	elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(elem_id.name, "Master Playback Switch");
+	chip->master_switch = snd_ctl_find_id(chip->card, &elem_id);
+	memset(&elem_id, 0, sizeof(elem_id));
+	elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strcpy(elem_id.name, "Master Playback Volume");
+	chip->master_volume = snd_ctl_find_id(chip->card, &elem_id);
+#endif
+
+	return 0;
+}
+
+
+/*
+ * initialize ASSP
+ */
+
+#define MINISRC_LPF_LEN 10
+static const u16 minisrc_lpf[MINISRC_LPF_LEN] = {
+	0X0743, 0X1104, 0X0A4C, 0XF88D, 0X242C,
+	0X1023, 0X1AA9, 0X0B60, 0XEFDD, 0X186F
+};
+
+static void snd_m3_assp_init(struct snd_m3 *chip)
+{
+	unsigned int i;
+	const u16 *data;
+
+	/* zero kernel data */
+	for (i = 0; i < (REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA) / 2; i++)
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, 
+				  KDATA_BASE_ADDR + i, 0);
+
+	/* zero mixer data? */
+	for (i = 0; i < (REV_B_DATA_MEMORY_UNIT_LENGTH * NUM_UNITS_KERNEL_DATA) / 2; i++)
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+				  KDATA_BASE_ADDR2 + i, 0);
+
+	/* init dma pointer */
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  KDATA_CURRENT_DMA,
+			  KDATA_DMA_XFER0);
+
+	/* write kernel into code memory.. */
+	data = (const u16 *)chip->assp_kernel_image->data;
+	for (i = 0 ; i * 2 < chip->assp_kernel_image->size; i++) {
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_CODE, 
+				  REV_B_CODE_MEMORY_BEGIN + i,
+				  le16_to_cpu(data[i]));
+	}
+
+	/*
+	 * We only have this one client and we know that 0x400
+	 * is free in our kernel's mem map, so lets just
+	 * drop it there.  It seems that the minisrc doesn't
+	 * need vectors, so we won't bother with them..
+	 */
+	data = (const u16 *)chip->assp_minisrc_image->data;
+	for (i = 0; i * 2 < chip->assp_minisrc_image->size; i++) {
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_CODE, 
+				  0x400 + i, le16_to_cpu(data[i]));
+	}
+
+	/*
+	 * write the coefficients for the low pass filter?
+	 */
+	for (i = 0; i < MINISRC_LPF_LEN ; i++) {
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_CODE,
+				  0x400 + MINISRC_COEF_LOC + i,
+				  minisrc_lpf[i]);
+	}
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_CODE,
+			  0x400 + MINISRC_COEF_LOC + MINISRC_LPF_LEN,
+			  0x8000);
+
+	/*
+	 * the minisrc is the only thing on
+	 * our task list..
+	 */
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, 
+			  KDATA_TASK0,
+			  0x400);
+
+	/*
+	 * init the mixer number..
+	 */
+
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  KDATA_MIXER_TASK_NUMBER,0);
+
+	/*
+	 * EXTREME KERNEL MASTER VOLUME
+	 */
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  KDATA_DAC_LEFT_VOLUME, ARB_VOLUME);
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+			  KDATA_DAC_RIGHT_VOLUME, ARB_VOLUME);
+
+	chip->mixer_list.curlen = 0;
+	chip->mixer_list.mem_addr = KDATA_MIXER_XFER0;
+	chip->mixer_list.max = MAX_VIRTUAL_MIXER_CHANNELS;
+	chip->adc1_list.curlen = 0;
+	chip->adc1_list.mem_addr = KDATA_ADC1_XFER0;
+	chip->adc1_list.max = MAX_VIRTUAL_ADC1_CHANNELS;
+	chip->dma_list.curlen = 0;
+	chip->dma_list.mem_addr = KDATA_DMA_XFER0;
+	chip->dma_list.max = MAX_VIRTUAL_DMA_CHANNELS;
+	chip->msrc_list.curlen = 0;
+	chip->msrc_list.mem_addr = KDATA_INSTANCE0_MINISRC;
+	chip->msrc_list.max = MAX_INSTANCE_MINISRC;
+}
+
+
+static int __devinit snd_m3_assp_client_init(struct snd_m3 *chip, struct m3_dma *s, int index)
+{
+	int data_bytes = 2 * ( MINISRC_TMP_BUFFER_SIZE / 2 + 
+			       MINISRC_IN_BUFFER_SIZE / 2 +
+			       1 + MINISRC_OUT_BUFFER_SIZE / 2 + 1 );
+	int address, i;
+
+	/*
+	 * the revb memory map has 0x1100 through 0x1c00
+	 * free.  
+	 */
+
+	/*
+	 * align instance address to 256 bytes so that its
+	 * shifted list address is aligned.
+	 * list address = (mem address >> 1) >> 7;
+	 */
+	data_bytes = ALIGN(data_bytes, 256);
+	address = 0x1100 + ((data_bytes/2) * index);
+
+	if ((address + (data_bytes/2)) >= 0x1c00) {
+		snd_printk(KERN_ERR "no memory for %d bytes at ind %d (addr 0x%x)\n",
+			   data_bytes, index, address);
+		return -ENOMEM;
+	}
+
+	s->number = index;
+	s->inst.code = 0x400;
+	s->inst.data = address;
+
+	for (i = data_bytes / 2; i > 0; address++, i--) {
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA,
+				  address, 0);
+	}
+
+	return 0;
+}
+
+
+/* 
+ * this works for the reference board, have to find
+ * out about others
+ *
+ * this needs more magic for 4 speaker, but..
+ */
+static void
+snd_m3_amp_enable(struct snd_m3 *chip, int enable)
+{
+	int io = chip->iobase;
+	u16 gpo, polarity;
+
+	if (! chip->external_amp)
+		return;
+
+	polarity = enable ? 0 : 1;
+	polarity = polarity << chip->amp_gpio;
+	gpo = 1 << chip->amp_gpio;
+
+	outw(~gpo, io + GPIO_MASK);
+
+	outw(inw(io + GPIO_DIRECTION) | gpo,
+	     io + GPIO_DIRECTION);
+
+	outw((GPO_SECONDARY_AC97 | GPO_PRIMARY_AC97 | polarity),
+	     io + GPIO_DATA);
+
+	outw(0xffff, io + GPIO_MASK);
+}
+
+static void
+snd_m3_hv_init(struct snd_m3 *chip)
+{
+	unsigned long io = chip->iobase;
+	u16 val = GPI_VOL_DOWN | GPI_VOL_UP;
+
+	if (!chip->is_omnibook)
+		return;
+
+	/*
+	 * Volume buttons on some HP OmniBook laptops
+	 * require some GPIO magic to work correctly.
+	 */
+	outw(0xffff, io + GPIO_MASK);
+	outw(0x0000, io + GPIO_DATA);
+
+	outw(~val, io + GPIO_MASK);
+	outw(inw(io + GPIO_DIRECTION) & ~val, io + GPIO_DIRECTION);
+	outw(val, io + GPIO_MASK);
+
+	outw(0xffff, io + GPIO_MASK);
+}
+
+static int
+snd_m3_chip_init(struct snd_m3 *chip)
+{
+	struct pci_dev *pcidev = chip->pci;
+	unsigned long io = chip->iobase;
+	u32 n;
+	u16 w;
+	u8 t; /* makes as much sense as 'n', no? */
+
+	pci_read_config_word(pcidev, PCI_LEGACY_AUDIO_CTRL, &w);
+	w &= ~(SOUND_BLASTER_ENABLE|FM_SYNTHESIS_ENABLE|
+	       MPU401_IO_ENABLE|MPU401_IRQ_ENABLE|ALIAS_10BIT_IO|
+	       DISABLE_LEGACY);
+	pci_write_config_word(pcidev, PCI_LEGACY_AUDIO_CTRL, w);
+
+	pci_read_config_dword(pcidev, PCI_ALLEGRO_CONFIG, &n);
+	n &= ~(HV_CTRL_ENABLE | REDUCED_DEBOUNCE | HV_BUTTON_FROM_GD);
+	n |= chip->hv_config;
+	/* For some reason we must always use reduced debounce. */
+	n |= REDUCED_DEBOUNCE;
+	n |= PM_CTRL_ENABLE | CLK_DIV_BY_49 | USE_PCI_TIMING;
+	pci_write_config_dword(pcidev, PCI_ALLEGRO_CONFIG, n);
+
+	outb(RESET_ASSP, chip->iobase + ASSP_CONTROL_B);
+	pci_read_config_dword(pcidev, PCI_ALLEGRO_CONFIG, &n);
+	n &= ~INT_CLK_SELECT;
+	if (!chip->allegro_flag) {
+		n &= ~INT_CLK_MULT_ENABLE; 
+		n |= INT_CLK_SRC_NOT_PCI;
+	}
+	n &=  ~( CLK_MULT_MODE_SELECT | CLK_MULT_MODE_SELECT_2 );
+	pci_write_config_dword(pcidev, PCI_ALLEGRO_CONFIG, n);
+
+	if (chip->allegro_flag) {
+		pci_read_config_dword(pcidev, PCI_USER_CONFIG, &n);
+		n |= IN_CLK_12MHZ_SELECT;
+		pci_write_config_dword(pcidev, PCI_USER_CONFIG, n);
+	}
+
+	t = inb(chip->iobase + ASSP_CONTROL_A);
+	t &= ~( DSP_CLK_36MHZ_SELECT  | ASSP_CLK_49MHZ_SELECT);
+	t |= ASSP_CLK_49MHZ_SELECT;
+	t |= ASSP_0_WS_ENABLE; 
+	outb(t, chip->iobase + ASSP_CONTROL_A);
+
+	snd_m3_assp_init(chip); /* download DSP code before starting ASSP below */
+	outb(RUN_ASSP, chip->iobase + ASSP_CONTROL_B); 
+
+	outb(0x00, io + HARDWARE_VOL_CTRL);
+	outb(0x88, io + SHADOW_MIX_REG_VOICE);
+	outb(0x88, io + HW_VOL_COUNTER_VOICE);
+	outb(0x88, io + SHADOW_MIX_REG_MASTER);
+	outb(0x88, io + HW_VOL_COUNTER_MASTER);
+
+	return 0;
+} 
+
+static void
+snd_m3_enable_ints(struct snd_m3 *chip)
+{
+	unsigned long io = chip->iobase;
+	unsigned short val;
+
+	/* TODO: MPU401 not supported yet */
+	val = ASSP_INT_ENABLE /*| MPU401_INT_ENABLE*/;
+	if (chip->hv_config & HV_CTRL_ENABLE)
+		val |= HV_INT_ENABLE;
+	outb(val, chip->iobase + HOST_INT_STATUS);
+	outw(val, io + HOST_INT_CTRL);
+	outb(inb(io + ASSP_CONTROL_C) | ASSP_HOST_INT_ENABLE,
+	     io + ASSP_CONTROL_C);
+}
+
+
+/*
+ */
+
+static int snd_m3_free(struct snd_m3 *chip)
+{
+	struct m3_dma *s;
+	int i;
+
+#ifdef CONFIG_SND_MAESTRO3_INPUT
+	if (chip->input_dev)
+		input_unregister_device(chip->input_dev);
+#endif
+
+	if (chip->substreams) {
+		spin_lock_irq(&chip->reg_lock);
+		for (i = 0; i < chip->num_substreams; i++) {
+			s = &chip->substreams[i];
+			/* check surviving pcms; this should not happen though.. */
+			if (s->substream && s->running)
+				snd_m3_pcm_stop(chip, s, s->substream);
+		}
+		spin_unlock_irq(&chip->reg_lock);
+		kfree(chip->substreams);
+	}
+	if (chip->iobase) {
+		outw(0, chip->iobase + HOST_INT_CTRL); /* disable ints */
+	}
+
+#ifdef CONFIG_PM
+	vfree(chip->suspend_mem);
+#endif
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+
+	if (chip->iobase)
+		pci_release_regions(chip->pci);
+
+	release_firmware(chip->assp_kernel_image);
+	release_firmware(chip->assp_minisrc_image);
+
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+
+/*
+ * APM support
+ */
+#ifdef CONFIG_PM
+static int m3_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_m3 *chip = card->private_data;
+	int i, dsp_index;
+
+	if (chip->suspend_mem == NULL)
+		return 0;
+
+	chip->in_suspend = 1;
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_ac97_suspend(chip->ac97);
+
+	msleep(10); /* give the assp a chance to idle.. */
+
+	snd_m3_assp_halt(chip);
+
+	/* save dsp image */
+	dsp_index = 0;
+	for (i = REV_B_CODE_MEMORY_BEGIN; i <= REV_B_CODE_MEMORY_END; i++)
+		chip->suspend_mem[dsp_index++] =
+			snd_m3_assp_read(chip, MEMTYPE_INTERNAL_CODE, i);
+	for (i = REV_B_DATA_MEMORY_BEGIN ; i <= REV_B_DATA_MEMORY_END; i++)
+		chip->suspend_mem[dsp_index++] =
+			snd_m3_assp_read(chip, MEMTYPE_INTERNAL_DATA, i);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int m3_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_m3 *chip = card->private_data;
+	int i, dsp_index;
+
+	if (chip->suspend_mem == NULL)
+		return 0;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "maestor3: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	/* first lets just bring everything back. .*/
+	snd_m3_outw(chip, 0, 0x54);
+	snd_m3_outw(chip, 0, 0x56);
+
+	snd_m3_chip_init(chip);
+	snd_m3_assp_halt(chip);
+	snd_m3_ac97_reset(chip);
+
+	/* restore dsp image */
+	dsp_index = 0;
+	for (i = REV_B_CODE_MEMORY_BEGIN; i <= REV_B_CODE_MEMORY_END; i++)
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_CODE, i, 
+				  chip->suspend_mem[dsp_index++]);
+	for (i = REV_B_DATA_MEMORY_BEGIN ; i <= REV_B_DATA_MEMORY_END; i++)
+		snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, i, 
+				  chip->suspend_mem[dsp_index++]);
+
+	/* tell the dma engine to restart itself */
+	snd_m3_assp_write(chip, MEMTYPE_INTERNAL_DATA, 
+			  KDATA_DMA_ACTIVE, 0);
+
+        /* restore ac97 registers */
+	snd_ac97_resume(chip->ac97);
+
+	snd_m3_assp_continue(chip);
+	snd_m3_enable_ints(chip);
+	snd_m3_amp_enable(chip, 1);
+
+	snd_m3_hv_init(chip);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	chip->in_suspend = 0;
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_SND_MAESTRO3_INPUT
+static int __devinit snd_m3_input_register(struct snd_m3 *chip)
+{
+	struct input_dev *input_dev;
+	int err;
+
+	input_dev = input_allocate_device();
+	if (!input_dev)
+		return -ENOMEM;
+
+	snprintf(chip->phys, sizeof(chip->phys), "pci-%s/input0",
+		 pci_name(chip->pci));
+
+	input_dev->name = chip->card->driver;
+	input_dev->phys = chip->phys;
+	input_dev->id.bustype = BUS_PCI;
+	input_dev->id.vendor  = chip->pci->vendor;
+	input_dev->id.product = chip->pci->device;
+	input_dev->dev.parent = &chip->pci->dev;
+
+	__set_bit(EV_KEY, input_dev->evbit);
+	__set_bit(KEY_MUTE, input_dev->keybit);
+	__set_bit(KEY_VOLUMEDOWN, input_dev->keybit);
+	__set_bit(KEY_VOLUMEUP, input_dev->keybit);
+
+	err = input_register_device(input_dev);
+	if (err) {
+		input_free_device(input_dev);
+		return err;
+	}
+
+	chip->input_dev = input_dev;
+	return 0;
+}
+#endif /* CONFIG_INPUT */
+
+/*
+ */
+
+static int snd_m3_dev_free(struct snd_device *device)
+{
+	struct snd_m3 *chip = device->device_data;
+	return snd_m3_free(chip);
+}
+
+static int __devinit
+snd_m3_create(struct snd_card *card, struct pci_dev *pci,
+	      int enable_amp,
+	      int amp_gpio,
+	      struct snd_m3 **chip_ret)
+{
+	struct snd_m3 *chip;
+	int i, err;
+	const struct snd_pci_quirk *quirk;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_m3_dev_free,
+	};
+
+	*chip_ret = NULL;
+
+	if (pci_enable_device(pci))
+		return -EIO;
+
+	/* check, if we can restrict PCI DMA transfers to 28 bits */
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(28)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(28)) < 0) {
+		snd_printk(KERN_ERR "architecture does not support 28bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&chip->reg_lock);
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	spin_lock_init(&chip->ac97_lock);
+#endif
+
+	switch (pci->device) {
+	case PCI_DEVICE_ID_ESS_ALLEGRO:
+	case PCI_DEVICE_ID_ESS_ALLEGRO_1:
+	case PCI_DEVICE_ID_ESS_CANYON3D_2LE:
+	case PCI_DEVICE_ID_ESS_CANYON3D_2:
+		chip->allegro_flag = 1;
+		break;
+	}
+
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	chip->external_amp = enable_amp;
+	if (amp_gpio >= 0 && amp_gpio <= 0x0f)
+		chip->amp_gpio = amp_gpio;
+	else {
+		quirk = snd_pci_quirk_lookup(pci, m3_amp_quirk_list);
+		if (quirk) {
+			snd_printdd(KERN_INFO "maestro3: set amp-gpio "
+				    "for '%s'\n", quirk->name);
+			chip->amp_gpio = quirk->value;
+		} else if (chip->allegro_flag)
+			chip->amp_gpio = GPO_EXT_AMP_ALLEGRO;
+		else /* presumably this is for all 'maestro3's.. */
+			chip->amp_gpio = GPO_EXT_AMP_M3;
+	}
+
+	quirk = snd_pci_quirk_lookup(pci, m3_irda_quirk_list);
+	if (quirk) {
+		snd_printdd(KERN_INFO "maestro3: enabled irda workaround "
+			    "for '%s'\n", quirk->name);
+		chip->irda_workaround = 1;
+	}
+	quirk = snd_pci_quirk_lookup(pci, m3_hv_quirk_list);
+	if (quirk)
+		chip->hv_config = quirk->value;
+	if (snd_pci_quirk_lookup(pci, m3_omnibook_quirk_list))
+		chip->is_omnibook = 1;
+
+	chip->num_substreams = NR_DSPS;
+	chip->substreams = kcalloc(chip->num_substreams, sizeof(struct m3_dma),
+				   GFP_KERNEL);
+	if (chip->substreams == NULL) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	err = request_firmware(&chip->assp_kernel_image,
+			       "ess/maestro3_assp_kernel.fw", &pci->dev);
+	if (err < 0) {
+		snd_m3_free(chip);
+		return err;
+	}
+
+	err = request_firmware(&chip->assp_minisrc_image,
+			       "ess/maestro3_assp_minisrc.fw", &pci->dev);
+	if (err < 0) {
+		snd_m3_free(chip);
+		return err;
+	}
+
+	if ((err = pci_request_regions(pci, card->driver)) < 0) {
+		snd_m3_free(chip);
+		return err;
+	}
+	chip->iobase = pci_resource_start(pci, 0);
+	
+	/* just to be sure */
+	pci_set_master(pci);
+
+	snd_m3_chip_init(chip);
+	snd_m3_assp_halt(chip);
+
+	snd_m3_ac97_reset(chip);
+
+	snd_m3_amp_enable(chip, 1);
+
+	snd_m3_hv_init(chip);
+
+#ifndef CONFIG_SND_MAESTRO3_INPUT
+	tasklet_init(&chip->hwvol_tq, snd_m3_update_hw_volume, (unsigned long)chip);
+#endif
+
+	if (request_irq(pci->irq, snd_m3_interrupt, IRQF_SHARED,
+			card->driver, chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_m3_free(chip);
+		return -ENOMEM;
+	}
+	chip->irq = pci->irq;
+
+#ifdef CONFIG_PM
+	chip->suspend_mem = vmalloc(sizeof(u16) * (REV_B_CODE_MEMORY_LENGTH + REV_B_DATA_MEMORY_LENGTH));
+	if (chip->suspend_mem == NULL)
+		snd_printk(KERN_WARNING "can't allocate apm buffer\n");
+#endif
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_m3_free(chip);
+		return err;
+	}
+
+	if ((err = snd_m3_mixer(chip)) < 0)
+		return err;
+
+	for (i = 0; i < chip->num_substreams; i++) {
+		struct m3_dma *s = &chip->substreams[i];
+		if ((err = snd_m3_assp_client_init(chip, s, i)) < 0)
+			return err;
+	}
+
+	if ((err = snd_m3_pcm(chip, 0)) < 0)
+		return err;
+
+#ifdef CONFIG_SND_MAESTRO3_INPUT
+	if (chip->hv_config & HV_CTRL_ENABLE) {
+		err = snd_m3_input_register(chip);
+		if (err)
+			snd_printk(KERN_WARNING "Input device registration "
+				"failed with error %i", err);
+	}
+#endif
+
+	snd_m3_enable_ints(chip);
+	snd_m3_assp_continue(chip);
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*chip_ret = chip;
+
+	return 0; 
+}
+
+/*
+ */
+static int __devinit
+snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_m3 *chip;
+	int err;
+
+	/* don't pick up modems */
+	if (((pci->class >> 8) & 0xffff) != PCI_CLASS_MULTIMEDIA_AUDIO)
+		return -ENODEV;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	switch (pci->device) {
+	case PCI_DEVICE_ID_ESS_ALLEGRO:
+	case PCI_DEVICE_ID_ESS_ALLEGRO_1:
+		strcpy(card->driver, "Allegro");
+		break;
+	case PCI_DEVICE_ID_ESS_CANYON3D_2LE:
+	case PCI_DEVICE_ID_ESS_CANYON3D_2:
+		strcpy(card->driver, "Canyon3D-2");
+		break;
+	default:
+		strcpy(card->driver, "Maestro3");
+		break;
+	}
+
+	if ((err = snd_m3_create(card, pci,
+				 external_amp[dev],
+				 amp_gpio[dev],
+				 &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = chip;
+
+	sprintf(card->shortname, "ESS %s PCI", card->driver);
+	sprintf(card->longname, "%s at 0x%lx, irq %d",
+		card->shortname, chip->iobase, chip->irq);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+#if 0 /* TODO: not supported yet */
+	/* TODO enable MIDI IRQ and I/O */
+	err = snd_mpu401_uart_new(chip->card, 0, MPU401_HW_MPU401,
+				  chip->iobase + MPU401_DATA_PORT,
+				  MPU401_INFO_INTEGRATED,
+				  chip->irq, 0, &chip->rmidi);
+	if (err < 0)
+		printk(KERN_WARNING "maestro3: no MIDI support.\n");
+#endif
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_m3_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "Maestro3",
+	.id_table = snd_m3_ids,
+	.probe = snd_m3_probe,
+	.remove = __devexit_p(snd_m3_remove),
+#ifdef CONFIG_PM
+	.suspend = m3_suspend,
+	.resume = m3_resume,
+#endif
+};
+	
+static int __init alsa_card_m3_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_m3_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_m3_init)
+module_exit(alsa_card_m3_exit)
diff --git a/linux/sound/pci/mixart/Makefile b/linux/sound/pci/mixart/Makefile
new file mode 100644
index 0000000..cce159e
--- /dev/null
+++ b/linux/sound/pci/mixart/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-mixart-objs := mixart.o mixart_core.o mixart_hwdep.o mixart_mixer.o
+
+obj-$(CONFIG_SND_MIXART) += snd-mixart.o
diff --git a/linux/sound/pci/mixart/mixart.c b/linux/sound/pci/mixart/mixart.c
new file mode 100644
index 0000000..6c3fd4d
--- /dev/null
+++ b/linux/sound/pci/mixart/mixart.c
@@ -0,0 +1,1401 @@
+/*
+ * Driver for Digigram miXart soundcards
+ *
+ * main file with alsa callbacks
+ *
+ * Copyright (c) 2003 by Digigram <alsa@digigram.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "mixart.h"
+#include "mixart_hwdep.h"
+#include "mixart_core.h"
+#include "mixart_mixer.h"
+
+#define CARD_NAME "miXart"
+
+MODULE_AUTHOR("Digigram <alsa@digigram.com>");
+MODULE_DESCRIPTION("Digigram " CARD_NAME);
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Digigram," CARD_NAME "}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;             /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;              /* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;     /* Enable this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Digigram " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Digigram " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Digigram " CARD_NAME " soundcard.");
+
+/*
+ */
+
+static DEFINE_PCI_DEVICE_TABLE(snd_mixart_ids) = {
+	{ PCI_VDEVICE(MOTOROLA, 0x0003), 0, }, /* MC8240 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_mixart_ids);
+
+
+static int mixart_set_pipe_state(struct mixart_mgr *mgr,
+				 struct mixart_pipe *pipe, int start)
+{
+	struct mixart_group_state_req group_state;
+	struct mixart_group_state_resp group_state_resp;
+	struct mixart_msg request;
+	int err;
+	u32 system_msg_uid;
+
+	switch(pipe->status) {
+	case PIPE_RUNNING:
+	case PIPE_CLOCK_SET:
+		if(start) return 0; /* already started */
+		break;
+	case PIPE_STOPPED:
+		if(!start) return 0; /* already stopped */
+		break;
+	default:
+		snd_printk(KERN_ERR "error mixart_set_pipe_state called with wrong pipe->status!\n");
+		return -EINVAL;      /* function called with wrong pipe status */
+	}
+
+	system_msg_uid = 0x12345678; /* the event ! (take care: the MSB and two LSB's have to be 0) */
+
+	/* wait on the last MSG_SYSTEM_SEND_SYNCHRO_CMD command to be really finished */
+
+	request.message_id = MSG_SYSTEM_WAIT_SYNCHRO_CMD;
+	request.uid = (struct mixart_uid){0,0};
+	request.data = &system_msg_uid;
+	request.size = sizeof(system_msg_uid);
+
+	err = snd_mixart_send_msg_wait_notif(mgr, &request, system_msg_uid);
+	if(err) {
+		snd_printk(KERN_ERR "error : MSG_SYSTEM_WAIT_SYNCHRO_CMD was not notified !\n");
+		return err;
+	}
+
+	/* start or stop the pipe (1 pipe) */
+
+	memset(&group_state, 0, sizeof(group_state));
+	group_state.pipe_count = 1;
+	group_state.pipe_uid[0] = pipe->group_uid;
+
+	if(start)
+		request.message_id = MSG_STREAM_START_STREAM_GRP_PACKET;
+	else
+		request.message_id = MSG_STREAM_STOP_STREAM_GRP_PACKET;
+
+	request.uid = pipe->group_uid; /*(struct mixart_uid){0,0};*/
+	request.data = &group_state;
+	request.size = sizeof(group_state);
+
+	err = snd_mixart_send_msg(mgr, &request, sizeof(group_state_resp), &group_state_resp);
+	if (err < 0 || group_state_resp.txx_status != 0) {
+		snd_printk(KERN_ERR "error MSG_STREAM_ST***_STREAM_GRP_PACKET err=%x stat=%x !\n", err, group_state_resp.txx_status);
+		return -EINVAL;
+	}
+
+	if(start) {
+		u32 stat;
+
+		group_state.pipe_count = 0; /* in case of start same command once again with pipe_count=0 */
+
+		err = snd_mixart_send_msg(mgr, &request, sizeof(group_state_resp), &group_state_resp);
+		if (err < 0 || group_state_resp.txx_status != 0) {
+			snd_printk(KERN_ERR "error MSG_STREAM_START_STREAM_GRP_PACKET err=%x stat=%x !\n", err, group_state_resp.txx_status);
+ 			return -EINVAL;
+		}
+
+		/* in case of start send a synchro top */
+
+		request.message_id = MSG_SYSTEM_SEND_SYNCHRO_CMD;
+		request.uid = (struct mixart_uid){0,0};
+		request.data = NULL;
+		request.size = 0;
+
+		err = snd_mixart_send_msg(mgr, &request, sizeof(stat), &stat);
+		if (err < 0 || stat != 0) {
+			snd_printk(KERN_ERR "error MSG_SYSTEM_SEND_SYNCHRO_CMD err=%x stat=%x !\n", err, stat);
+			return -EINVAL;
+		}
+
+		pipe->status = PIPE_RUNNING;
+	}
+	else /* !start */
+		pipe->status = PIPE_STOPPED;
+
+	return 0;
+}
+
+
+static int mixart_set_clock(struct mixart_mgr *mgr,
+			    struct mixart_pipe *pipe, unsigned int rate)
+{
+	struct mixart_msg request;
+	struct mixart_clock_properties clock_properties;
+	struct mixart_clock_properties_resp clock_prop_resp;
+	int err;
+
+	switch(pipe->status) {
+	case PIPE_CLOCK_SET:
+		break;
+	case PIPE_RUNNING:
+		if(rate != 0)
+			break;
+	default:
+		if(rate == 0)
+			return 0; /* nothing to do */
+		else {
+			snd_printk(KERN_ERR "error mixart_set_clock(%d) called with wrong pipe->status !\n", rate);
+			return -EINVAL;
+		}
+	}
+
+	memset(&clock_properties, 0, sizeof(clock_properties));
+	clock_properties.clock_generic_type = (rate != 0) ? CGT_INTERNAL_CLOCK : CGT_NO_CLOCK;
+	clock_properties.clock_mode = CM_STANDALONE;
+	clock_properties.frequency = rate;
+	clock_properties.nb_callers = 1; /* only one entry in uid_caller ! */
+	clock_properties.uid_caller[0] = pipe->group_uid;
+
+	snd_printdd("mixart_set_clock to %d kHz\n", rate);
+
+	request.message_id = MSG_CLOCK_SET_PROPERTIES;
+	request.uid = mgr->uid_console_manager;
+	request.data = &clock_properties;
+	request.size = sizeof(clock_properties);
+
+	err = snd_mixart_send_msg(mgr, &request, sizeof(clock_prop_resp), &clock_prop_resp);
+	if (err < 0 || clock_prop_resp.status != 0 || clock_prop_resp.clock_mode != CM_STANDALONE) {
+		snd_printk(KERN_ERR "error MSG_CLOCK_SET_PROPERTIES err=%x stat=%x mod=%x !\n", err, clock_prop_resp.status, clock_prop_resp.clock_mode);
+		return -EINVAL;
+	}
+
+	if(rate)  pipe->status = PIPE_CLOCK_SET;
+	else      pipe->status = PIPE_RUNNING;
+
+	return 0;
+}
+
+
+/*
+ *  Allocate or reference output pipe for analog IOs (pcmp0/1)
+ */
+struct mixart_pipe *
+snd_mixart_add_ref_pipe(struct snd_mixart *chip, int pcm_number, int capture,
+			int monitoring)
+{
+	int stream_count;
+	struct mixart_pipe *pipe;
+	struct mixart_msg request;
+
+	if(capture) {
+		if (pcm_number == MIXART_PCM_ANALOG) {
+			pipe = &(chip->pipe_in_ana);  /* analog inputs */
+		} else {
+			pipe = &(chip->pipe_in_dig); /* digital inputs */
+		}
+		request.message_id = MSG_STREAM_ADD_OUTPUT_GROUP;
+		stream_count = MIXART_CAPTURE_STREAMS;
+	} else {
+		if (pcm_number == MIXART_PCM_ANALOG) {
+			pipe = &(chip->pipe_out_ana);  /* analog outputs */
+		} else {
+			pipe = &(chip->pipe_out_dig);  /* digital outputs */
+		}
+		request.message_id = MSG_STREAM_ADD_INPUT_GROUP;
+		stream_count = MIXART_PLAYBACK_STREAMS;
+	}
+
+	/* a new stream is opened and there are already all streams in use */
+	if( (monitoring == 0) && (pipe->references >= stream_count) ) {
+		return NULL;
+	}
+
+	/* pipe is not yet defined */
+	if( pipe->status == PIPE_UNDEFINED ) {
+		int err, i;
+		struct {
+			struct mixart_streaming_group_req sgroup_req;
+			struct mixart_streaming_group sgroup_resp;
+		} *buf;
+
+		snd_printdd("add_ref_pipe audio chip(%d) pcm(%d)\n", chip->chip_idx, pcm_number);
+
+		buf = kmalloc(sizeof(*buf), GFP_KERNEL);
+		if (!buf)
+			return NULL;
+
+		request.uid = (struct mixart_uid){0,0};      /* should be StreamManagerUID, but zero is OK if there is only one ! */
+		request.data = &buf->sgroup_req;
+		request.size = sizeof(buf->sgroup_req);
+
+		memset(&buf->sgroup_req, 0, sizeof(buf->sgroup_req));
+
+		buf->sgroup_req.stream_count = stream_count;
+		buf->sgroup_req.channel_count = 2;
+		buf->sgroup_req.latency = 256;
+		buf->sgroup_req.connector = pipe->uid_left_connector;  /* the left connector */
+
+		for (i=0; i<stream_count; i++) {
+			int j;
+			struct mixart_flowinfo *flowinfo;
+			struct mixart_bufferinfo *bufferinfo;
+			
+			/* we don't yet know the format, so config 16 bit pcm audio for instance */
+			buf->sgroup_req.stream_info[i].size_max_byte_frame = 1024;
+			buf->sgroup_req.stream_info[i].size_max_sample_frame = 256;
+			buf->sgroup_req.stream_info[i].nb_bytes_max_per_sample = MIXART_FLOAT_P__4_0_TO_HEX; /* is 4.0f */
+
+			/* find the right bufferinfo_array */
+			j = (chip->chip_idx * MIXART_MAX_STREAM_PER_CARD) + (pcm_number * (MIXART_PLAYBACK_STREAMS + MIXART_CAPTURE_STREAMS)) + i;
+			if(capture) j += MIXART_PLAYBACK_STREAMS; /* in the array capture is behind playback */
+
+			buf->sgroup_req.flow_entry[i] = j;
+
+			flowinfo = (struct mixart_flowinfo *)chip->mgr->flowinfo.area;
+			flowinfo[j].bufferinfo_array_phy_address = (u32)chip->mgr->bufferinfo.addr + (j * sizeof(struct mixart_bufferinfo));
+			flowinfo[j].bufferinfo_count = 1;               /* 1 will set the miXart to ring-buffer mode ! */
+
+			bufferinfo = (struct mixart_bufferinfo *)chip->mgr->bufferinfo.area;
+			bufferinfo[j].buffer_address = 0;               /* buffer is not yet allocated */
+			bufferinfo[j].available_length = 0;             /* buffer is not yet allocated */
+
+			/* construct the identifier of the stream buffer received in the interrupts ! */
+			bufferinfo[j].buffer_id = (chip->chip_idx << MIXART_NOTIFY_CARD_OFFSET) + (pcm_number << MIXART_NOTIFY_PCM_OFFSET ) + i;
+			if(capture) {
+				bufferinfo[j].buffer_id |= MIXART_NOTIFY_CAPT_MASK;
+			}
+		}
+
+		err = snd_mixart_send_msg(chip->mgr, &request, sizeof(buf->sgroup_resp), &buf->sgroup_resp);
+		if((err < 0) || (buf->sgroup_resp.status != 0)) {
+			snd_printk(KERN_ERR "error MSG_STREAM_ADD_**PUT_GROUP err=%x stat=%x !\n", err, buf->sgroup_resp.status);
+			kfree(buf);
+			return NULL;
+		}
+
+		pipe->group_uid = buf->sgroup_resp.group;     /* id of the pipe, as returned by embedded */
+		pipe->stream_count = buf->sgroup_resp.stream_count;
+		/* pipe->stream_uid[i] = buf->sgroup_resp.stream[i].stream_uid; */
+
+		pipe->status = PIPE_STOPPED;
+		kfree(buf);
+	}
+
+	if(monitoring)	pipe->monitoring = 1;
+	else		pipe->references++;
+
+	return pipe;
+}
+
+
+int snd_mixart_kill_ref_pipe(struct mixart_mgr *mgr,
+			     struct mixart_pipe *pipe, int monitoring)
+{
+	int err = 0;
+
+	if(pipe->status == PIPE_UNDEFINED)
+		return 0;
+
+	if(monitoring)
+		pipe->monitoring = 0;
+	else
+		pipe->references--;
+
+	if((pipe->references <= 0) && (pipe->monitoring == 0)) {
+
+		struct mixart_msg request;
+		struct mixart_delete_group_resp delete_resp;
+
+		/* release the clock */
+		err = mixart_set_clock( mgr, pipe, 0);
+		if( err < 0 ) {
+			snd_printk(KERN_ERR "mixart_set_clock(0) return error!\n");
+		}
+
+		/* stop the pipe */
+		err = mixart_set_pipe_state(mgr, pipe, 0);
+		if( err < 0 ) {
+			snd_printk(KERN_ERR "error stopping pipe!\n");
+		}
+
+		request.message_id = MSG_STREAM_DELETE_GROUP;
+		request.uid = (struct mixart_uid){0,0};
+		request.data = &pipe->group_uid;            /* the streaming group ! */
+		request.size = sizeof(pipe->group_uid);
+
+		/* delete the pipe */
+		err = snd_mixart_send_msg(mgr, &request, sizeof(delete_resp), &delete_resp);
+		if ((err < 0) || (delete_resp.status != 0)) {
+			snd_printk(KERN_ERR "error MSG_STREAM_DELETE_GROUP err(%x), status(%x)\n", err, delete_resp.status);
+		}
+
+		pipe->group_uid = (struct mixart_uid){0,0};
+		pipe->stream_count = 0;
+		pipe->status = PIPE_UNDEFINED;
+	}
+
+	return err;
+}
+
+static int mixart_set_stream_state(struct mixart_stream *stream, int start)
+{
+	struct snd_mixart *chip;
+	struct mixart_stream_state_req stream_state_req;
+	struct mixart_msg request;
+
+	if(!stream->substream)
+		return -EINVAL;
+
+	memset(&stream_state_req, 0, sizeof(stream_state_req));
+	stream_state_req.stream_count = 1;
+	stream_state_req.stream_info.stream_desc.uid_pipe = stream->pipe->group_uid;
+	stream_state_req.stream_info.stream_desc.stream_idx = stream->substream->number;
+
+	if (stream->substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		request.message_id = start ? MSG_STREAM_START_INPUT_STAGE_PACKET : MSG_STREAM_STOP_INPUT_STAGE_PACKET;
+	else
+		request.message_id = start ? MSG_STREAM_START_OUTPUT_STAGE_PACKET : MSG_STREAM_STOP_OUTPUT_STAGE_PACKET;
+
+	request.uid = (struct mixart_uid){0,0};
+	request.data = &stream_state_req;
+	request.size = sizeof(stream_state_req);
+
+	stream->abs_period_elapsed = 0;            /* reset stream pos      */
+	stream->buf_periods = 0;
+	stream->buf_period_frag = 0;
+
+	chip = snd_pcm_substream_chip(stream->substream);
+
+	return snd_mixart_send_msg_nonblock(chip->mgr, &request);
+}
+
+/*
+ *  Trigger callback
+ */
+
+static int snd_mixart_trigger(struct snd_pcm_substream *subs, int cmd)
+{
+	struct mixart_stream *stream = subs->runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+
+		snd_printdd("SNDRV_PCM_TRIGGER_START\n");
+
+		/* START_STREAM */
+		if( mixart_set_stream_state(stream, 1) )
+			return -EINVAL;
+
+		stream->status = MIXART_STREAM_STATUS_RUNNING;
+
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+
+		/* STOP_STREAM */
+		if( mixart_set_stream_state(stream, 0) )
+			return -EINVAL;
+
+		stream->status = MIXART_STREAM_STATUS_OPEN;
+
+		snd_printdd("SNDRV_PCM_TRIGGER_STOP\n");
+
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		/* TODO */
+		stream->status = MIXART_STREAM_STATUS_PAUSE;
+		snd_printdd("SNDRV_PCM_PAUSE_PUSH\n");
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		/* TODO */
+		stream->status = MIXART_STREAM_STATUS_RUNNING;
+		snd_printdd("SNDRV_PCM_PAUSE_RELEASE\n");
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int mixart_sync_nonblock_events(struct mixart_mgr *mgr)
+{
+	unsigned long timeout = jiffies + HZ;
+	while (atomic_read(&mgr->msg_processed) > 0) {
+		if (time_after(jiffies, timeout)) {
+			snd_printk(KERN_ERR "mixart: cannot process nonblock events!\n");
+			return -EBUSY;
+		}
+		schedule_timeout_uninterruptible(1);
+	}
+	return 0;
+}
+
+/*
+ *  prepare callback for all pcms
+ */
+static int snd_mixart_prepare(struct snd_pcm_substream *subs)
+{
+	struct snd_mixart *chip = snd_pcm_substream_chip(subs);
+	struct mixart_stream *stream = subs->runtime->private_data;
+
+	/* TODO de façon non bloquante, réappliquer les hw_params (rate, bits, codec) */
+
+	snd_printdd("snd_mixart_prepare\n");
+
+	mixart_sync_nonblock_events(chip->mgr);
+
+	/* only the first stream can choose the sample rate */
+	/* the further opened streams will be limited to its frequency (see open) */
+	if(chip->mgr->ref_count_rate == 1)
+		chip->mgr->sample_rate = subs->runtime->rate;
+
+	/* set the clock only once (first stream) on the same pipe */
+	if(stream->pipe->references == 1) {
+		if( mixart_set_clock(chip->mgr, stream->pipe, subs->runtime->rate) )
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+
+static int mixart_set_format(struct mixart_stream *stream, snd_pcm_format_t format)
+{
+	int err;
+	struct snd_mixart *chip;
+	struct mixart_msg request;
+	struct mixart_stream_param_desc stream_param;
+	struct mixart_return_uid resp;
+
+	chip = snd_pcm_substream_chip(stream->substream);
+
+	memset(&stream_param, 0, sizeof(stream_param));
+
+	stream_param.coding_type = CT_LINEAR;
+	stream_param.number_of_channel = stream->channels;
+
+	stream_param.sampling_freq = chip->mgr->sample_rate;
+	if(stream_param.sampling_freq == 0)
+		stream_param.sampling_freq = 44100; /* if frequency not yet defined, use some default */
+
+	switch(format){
+	case SNDRV_PCM_FORMAT_U8:
+		stream_param.sample_type = ST_INTEGER_8;
+		stream_param.sample_size = 8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		stream_param.sample_type = ST_INTEGER_16LE;
+		stream_param.sample_size = 16;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE:
+		stream_param.sample_type = ST_INTEGER_16BE;
+		stream_param.sample_size = 16;
+		break;
+	case SNDRV_PCM_FORMAT_S24_3LE:
+		stream_param.sample_type = ST_INTEGER_24LE;
+		stream_param.sample_size = 24;
+		break;
+	case SNDRV_PCM_FORMAT_S24_3BE:
+		stream_param.sample_type = ST_INTEGER_24BE;
+		stream_param.sample_size = 24;
+		break;
+	case SNDRV_PCM_FORMAT_FLOAT_LE:
+		stream_param.sample_type = ST_FLOATING_POINT_32LE;
+		stream_param.sample_size = 32;
+		break;
+	case  SNDRV_PCM_FORMAT_FLOAT_BE:
+		stream_param.sample_type = ST_FLOATING_POINT_32BE;
+		stream_param.sample_size = 32;
+		break;
+	default:
+		snd_printk(KERN_ERR "error mixart_set_format() : unknown format\n");
+		return -EINVAL;
+	}
+
+	snd_printdd("set SNDRV_PCM_FORMAT sample_type(%d) sample_size(%d) freq(%d) channels(%d)\n",
+		   stream_param.sample_type, stream_param.sample_size, stream_param.sampling_freq, stream->channels);
+
+	/* TODO: what else to configure ? */
+	/* stream_param.samples_per_frame = 2; */
+	/* stream_param.bytes_per_frame = 4; */
+	/* stream_param.bytes_per_sample = 2; */
+
+	stream_param.pipe_count = 1;      /* set to 1 */
+	stream_param.stream_count = 1;    /* set to 1 */
+	stream_param.stream_desc[0].uid_pipe = stream->pipe->group_uid;
+	stream_param.stream_desc[0].stream_idx = stream->substream->number;
+
+	request.message_id = MSG_STREAM_SET_INPUT_STAGE_PARAM;
+	request.uid = (struct mixart_uid){0,0};
+	request.data = &stream_param;
+	request.size = sizeof(stream_param);
+
+	err = snd_mixart_send_msg(chip->mgr, &request, sizeof(resp), &resp);
+	if((err < 0) || resp.error_code) {
+		snd_printk(KERN_ERR "MSG_STREAM_SET_INPUT_STAGE_PARAM err=%x; resp=%x\n", err, resp.error_code);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/*
+ *  HW_PARAMS callback for all pcms
+ */
+static int snd_mixart_hw_params(struct snd_pcm_substream *subs,
+                                struct snd_pcm_hw_params *hw)
+{
+	struct snd_mixart *chip = snd_pcm_substream_chip(subs);
+	struct mixart_mgr *mgr = chip->mgr;
+	struct mixart_stream *stream = subs->runtime->private_data;
+	snd_pcm_format_t format;
+	int err;
+	int channels;
+
+	/* set up channels */
+	channels = params_channels(hw);
+
+	/*  set up format for the stream */
+	format = params_format(hw);
+
+	mutex_lock(&mgr->setup_mutex);
+
+	/* update the stream levels */
+	if( stream->pcm_number <= MIXART_PCM_DIGITAL ) {
+		int is_aes = stream->pcm_number > MIXART_PCM_ANALOG;
+		if( subs->stream == SNDRV_PCM_STREAM_PLAYBACK )
+			mixart_update_playback_stream_level(chip, is_aes, subs->number);
+		else
+			mixart_update_capture_stream_level( chip, is_aes);
+	}
+
+	stream->channels = channels;
+
+	/* set the format to the board */
+	err = mixart_set_format(stream, format);
+	if(err < 0) {
+		mutex_unlock(&mgr->setup_mutex);
+		return err;
+	}
+
+	/* allocate buffer */
+	err = snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw));
+
+	if (err > 0) {
+		struct mixart_bufferinfo *bufferinfo;
+		int i = (chip->chip_idx * MIXART_MAX_STREAM_PER_CARD) + (stream->pcm_number * (MIXART_PLAYBACK_STREAMS+MIXART_CAPTURE_STREAMS)) + subs->number;
+		if( subs->stream == SNDRV_PCM_STREAM_CAPTURE ) {
+			i += MIXART_PLAYBACK_STREAMS; /* in array capture is behind playback */
+		}
+		
+		bufferinfo = (struct mixart_bufferinfo *)chip->mgr->bufferinfo.area;
+		bufferinfo[i].buffer_address = subs->runtime->dma_addr;
+		bufferinfo[i].available_length = subs->runtime->dma_bytes;
+		/* bufferinfo[i].buffer_id  is already defined */
+
+		snd_printdd("snd_mixart_hw_params(pcm %d) : dma_addr(%x) dma_bytes(%x) subs-number(%d)\n", i,
+				bufferinfo[i].buffer_address,
+				bufferinfo[i].available_length,
+				subs->number);
+	}
+	mutex_unlock(&mgr->setup_mutex);
+
+	return err;
+}
+
+static int snd_mixart_hw_free(struct snd_pcm_substream *subs)
+{
+	struct snd_mixart *chip = snd_pcm_substream_chip(subs);
+	snd_pcm_lib_free_pages(subs);
+	mixart_sync_nonblock_events(chip->mgr);
+	return 0;
+}
+
+
+
+/*
+ *  TODO CONFIGURATION SPACE for all pcms, mono pcm must update channels_max
+ */
+static struct snd_pcm_hardware snd_mixart_analog_caps =
+{
+	.info             = ( SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+			      SNDRV_PCM_INFO_MMAP_VALID |
+			      SNDRV_PCM_INFO_PAUSE),
+	.formats	  = ( SNDRV_PCM_FMTBIT_U8 |
+			      SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
+			      SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |
+			      SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE ),
+	.rates            = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min         = 8000,
+	.rate_max         = 48000,
+	.channels_min     = 1,
+	.channels_max     = 2,
+	.buffer_bytes_max = (32*1024),
+	.period_bytes_min = 256,                  /* 256 frames U8 mono*/
+	.period_bytes_max = (16*1024),
+	.periods_min      = 2,
+	.periods_max      = (32*1024/256),
+};
+
+static struct snd_pcm_hardware snd_mixart_digital_caps =
+{
+	.info             = ( SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+			      SNDRV_PCM_INFO_MMAP_VALID |
+			      SNDRV_PCM_INFO_PAUSE),
+	.formats	  = ( SNDRV_PCM_FMTBIT_U8 |
+			      SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
+			      SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |
+			      SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE ),
+	.rates            = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+	.rate_min         = 32000,
+	.rate_max         = 48000,
+	.channels_min     = 1,
+	.channels_max     = 2,
+	.buffer_bytes_max = (32*1024),
+	.period_bytes_min = 256,                  /* 256 frames U8 mono*/
+	.period_bytes_max = (16*1024),
+	.periods_min      = 2,
+	.periods_max      = (32*1024/256),
+};
+
+
+static int snd_mixart_playback_open(struct snd_pcm_substream *subs)
+{
+	struct snd_mixart            *chip = snd_pcm_substream_chip(subs);
+	struct mixart_mgr        *mgr = chip->mgr;
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct snd_pcm *pcm = subs->pcm;
+	struct mixart_stream     *stream;
+	struct mixart_pipe       *pipe;
+	int err = 0;
+	int pcm_number;
+
+	mutex_lock(&mgr->setup_mutex);
+
+	if ( pcm == chip->pcm ) {
+		pcm_number = MIXART_PCM_ANALOG;
+		runtime->hw = snd_mixart_analog_caps;
+	} else {
+		snd_BUG_ON(pcm != chip->pcm_dig);
+		pcm_number = MIXART_PCM_DIGITAL;
+		runtime->hw = snd_mixart_digital_caps;
+	}
+	snd_printdd("snd_mixart_playback_open C%d/P%d/Sub%d\n", chip->chip_idx, pcm_number, subs->number);
+
+	/* get stream info */
+	stream = &(chip->playback_stream[pcm_number][subs->number]);
+
+	if (stream->status != MIXART_STREAM_STATUS_FREE){
+		/* streams in use */
+		snd_printk(KERN_ERR "snd_mixart_playback_open C%d/P%d/Sub%d in use\n", chip->chip_idx, pcm_number, subs->number);
+		err = -EBUSY;
+		goto _exit_open;
+	}
+
+	/* get pipe pointer (out pipe) */
+	pipe = snd_mixart_add_ref_pipe(chip, pcm_number, 0, 0);
+
+	if (pipe == NULL) {
+		err = -EINVAL;
+		goto _exit_open;
+	}
+
+	/* start the pipe if necessary */
+	err = mixart_set_pipe_state(chip->mgr, pipe, 1);
+	if( err < 0 ) {
+		snd_printk(KERN_ERR "error starting pipe!\n");
+		snd_mixart_kill_ref_pipe(chip->mgr, pipe, 0);
+		err = -EINVAL;
+		goto _exit_open;
+	}
+
+	stream->pipe        = pipe;
+	stream->pcm_number  = pcm_number;
+	stream->status      = MIXART_STREAM_STATUS_OPEN;
+	stream->substream   = subs;
+	stream->channels    = 0; /* not configured yet */
+
+	runtime->private_data = stream;
+
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 64);
+
+	/* if a sample rate is already used, another stream cannot change */
+	if(mgr->ref_count_rate++) {
+		if(mgr->sample_rate) {
+			runtime->hw.rate_min = runtime->hw.rate_max = mgr->sample_rate;
+		}
+	}
+
+ _exit_open:
+	mutex_unlock(&mgr->setup_mutex);
+
+	return err;
+}
+
+
+static int snd_mixart_capture_open(struct snd_pcm_substream *subs)
+{
+	struct snd_mixart            *chip = snd_pcm_substream_chip(subs);
+	struct mixart_mgr        *mgr = chip->mgr;
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct snd_pcm *pcm = subs->pcm;
+	struct mixart_stream     *stream;
+	struct mixart_pipe       *pipe;
+	int err = 0;
+	int pcm_number;
+
+	mutex_lock(&mgr->setup_mutex);
+
+	if ( pcm == chip->pcm ) {
+		pcm_number = MIXART_PCM_ANALOG;
+		runtime->hw = snd_mixart_analog_caps;
+	} else {
+		snd_BUG_ON(pcm != chip->pcm_dig);
+		pcm_number = MIXART_PCM_DIGITAL;
+		runtime->hw = snd_mixart_digital_caps;
+	}
+
+	runtime->hw.channels_min = 2; /* for instance, no mono */
+
+	snd_printdd("snd_mixart_capture_open C%d/P%d/Sub%d\n", chip->chip_idx, pcm_number, subs->number);
+
+	/* get stream info */
+	stream = &(chip->capture_stream[pcm_number]);
+
+	if (stream->status != MIXART_STREAM_STATUS_FREE){
+		/* streams in use */
+		snd_printk(KERN_ERR "snd_mixart_capture_open C%d/P%d/Sub%d in use\n", chip->chip_idx, pcm_number, subs->number);
+		err = -EBUSY;
+		goto _exit_open;
+	}
+
+	/* get pipe pointer (in pipe) */
+	pipe = snd_mixart_add_ref_pipe(chip, pcm_number, 1, 0);
+
+	if (pipe == NULL) {
+		err = -EINVAL;
+		goto _exit_open;
+	}
+
+	/* start the pipe if necessary */
+	err = mixart_set_pipe_state(chip->mgr, pipe, 1);
+	if( err < 0 ) {
+		snd_printk(KERN_ERR "error starting pipe!\n");
+		snd_mixart_kill_ref_pipe(chip->mgr, pipe, 0);
+		err = -EINVAL;
+		goto _exit_open;
+	}
+
+	stream->pipe        = pipe;
+	stream->pcm_number  = pcm_number;
+	stream->status      = MIXART_STREAM_STATUS_OPEN;
+	stream->substream   = subs;
+	stream->channels    = 0; /* not configured yet */
+
+	runtime->private_data = stream;
+
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 64);
+
+	/* if a sample rate is already used, another stream cannot change */
+	if(mgr->ref_count_rate++) {
+		if(mgr->sample_rate) {
+			runtime->hw.rate_min = runtime->hw.rate_max = mgr->sample_rate;
+		}
+	}
+
+ _exit_open:
+	mutex_unlock(&mgr->setup_mutex);
+
+	return err;
+}
+
+
+
+static int snd_mixart_close(struct snd_pcm_substream *subs)
+{
+	struct snd_mixart *chip = snd_pcm_substream_chip(subs);
+	struct mixart_mgr *mgr = chip->mgr;
+	struct mixart_stream *stream = subs->runtime->private_data;
+
+	mutex_lock(&mgr->setup_mutex);
+
+	snd_printdd("snd_mixart_close C%d/P%d/Sub%d\n", chip->chip_idx, stream->pcm_number, subs->number);
+
+	/* sample rate released */
+	if(--mgr->ref_count_rate == 0) {
+		mgr->sample_rate = 0;
+	}
+
+	/* delete pipe */
+	if (snd_mixart_kill_ref_pipe(mgr, stream->pipe, 0 ) < 0) {
+
+		snd_printk(KERN_ERR "error snd_mixart_kill_ref_pipe C%dP%d\n", chip->chip_idx, stream->pcm_number);
+	}
+
+	stream->pipe      = NULL;
+	stream->status    = MIXART_STREAM_STATUS_FREE;
+	stream->substream = NULL;
+
+	mutex_unlock(&mgr->setup_mutex);
+	return 0;
+}
+
+
+static snd_pcm_uframes_t snd_mixart_stream_pointer(struct snd_pcm_substream *subs)
+{
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct mixart_stream   *stream  = runtime->private_data;
+
+	return (snd_pcm_uframes_t)((stream->buf_periods * runtime->period_size) + stream->buf_period_frag);
+}
+
+
+
+static struct snd_pcm_ops snd_mixart_playback_ops = {
+	.open      = snd_mixart_playback_open,
+	.close     = snd_mixart_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.prepare   = snd_mixart_prepare,
+	.hw_params = snd_mixart_hw_params,
+	.hw_free   = snd_mixart_hw_free,
+	.trigger   = snd_mixart_trigger,
+	.pointer   = snd_mixart_stream_pointer,
+};
+
+static struct snd_pcm_ops snd_mixart_capture_ops = {
+	.open      = snd_mixart_capture_open,
+	.close     = snd_mixart_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.prepare   = snd_mixart_prepare,
+	.hw_params = snd_mixart_hw_params,
+	.hw_free   = snd_mixart_hw_free,
+	.trigger   = snd_mixart_trigger,
+	.pointer   = snd_mixart_stream_pointer,
+};
+
+static void preallocate_buffers(struct snd_mixart *chip, struct snd_pcm *pcm)
+{
+#if 0
+	struct snd_pcm_substream *subs;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++) {
+		int idx = 0;
+		for (subs = pcm->streams[stream].substream; subs; subs = subs->next, idx++)
+			/* set up the unique device id with the chip index */
+			subs->dma_device.id = subs->pcm->device << 16 |
+				subs->stream << 8 | (subs->number + 1) |
+				(chip->chip_idx + 1) << 24;
+	}
+#endif
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->mgr->pci), 32*1024, 32*1024);
+}
+
+/*
+ */
+static int snd_mixart_pcm_analog(struct snd_mixart *chip)
+{
+	int err;
+	struct snd_pcm *pcm;
+	char name[32];
+
+	sprintf(name, "miXart analog %d", chip->chip_idx);
+	if ((err = snd_pcm_new(chip->card, name, MIXART_PCM_ANALOG,
+			       MIXART_PLAYBACK_STREAMS,
+			       MIXART_CAPTURE_STREAMS, &pcm)) < 0) {
+		snd_printk(KERN_ERR "cannot create the analog pcm %d\n", chip->chip_idx);
+		return err;
+	}
+
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_mixart_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_mixart_capture_ops);
+
+	pcm->info_flags = 0;
+	strcpy(pcm->name, name);
+
+	preallocate_buffers(chip, pcm);
+
+	chip->pcm = pcm;
+	return 0;
+}
+
+
+/*
+ */
+static int snd_mixart_pcm_digital(struct snd_mixart *chip)
+{
+	int err;
+	struct snd_pcm *pcm;
+	char name[32];
+
+	sprintf(name, "miXart AES/EBU %d", chip->chip_idx);
+	if ((err = snd_pcm_new(chip->card, name, MIXART_PCM_DIGITAL,
+			       MIXART_PLAYBACK_STREAMS,
+			       MIXART_CAPTURE_STREAMS, &pcm)) < 0) {
+		snd_printk(KERN_ERR "cannot create the digital pcm %d\n", chip->chip_idx);
+		return err;
+	}
+
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_mixart_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_mixart_capture_ops);
+
+	pcm->info_flags = 0;
+	strcpy(pcm->name, name);
+
+	preallocate_buffers(chip, pcm);
+
+	chip->pcm_dig = pcm;
+	return 0;
+}
+
+static int snd_mixart_chip_free(struct snd_mixart *chip)
+{
+	kfree(chip);
+	return 0;
+}
+
+static int snd_mixart_chip_dev_free(struct snd_device *device)
+{
+	struct snd_mixart *chip = device->device_data;
+	return snd_mixart_chip_free(chip);
+}
+
+
+/*
+ */
+static int __devinit snd_mixart_create(struct mixart_mgr *mgr, struct snd_card *card, int idx)
+{
+	int err;
+	struct snd_mixart *chip;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_mixart_chip_dev_free,
+	};
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (! chip) {
+		snd_printk(KERN_ERR "cannot allocate chip\n");
+		return -ENOMEM;
+	}
+
+	chip->card = card;
+	chip->chip_idx = idx;
+	chip->mgr = mgr;
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_mixart_chip_free(chip);
+		return err;
+	}
+
+	mgr->chip[idx] = chip;
+	snd_card_set_dev(card, &mgr->pci->dev);
+
+	return 0;
+}
+
+int snd_mixart_create_pcm(struct snd_mixart* chip)
+{
+	int err;
+
+	err = snd_mixart_pcm_analog(chip);
+	if (err < 0)
+		return err;
+
+	if(chip->mgr->board_type == MIXART_DAUGHTER_TYPE_AES) {
+
+		err = snd_mixart_pcm_digital(chip);
+		if (err < 0)
+			return err;
+	}
+	return err;
+}
+
+
+/*
+ * release all the cards assigned to a manager instance
+ */
+static int snd_mixart_free(struct mixart_mgr *mgr)
+{
+	unsigned int i;
+
+	for (i = 0; i < mgr->num_cards; i++) {
+		if (mgr->chip[i])
+			snd_card_free(mgr->chip[i]->card);
+	}
+
+	/* stop mailbox */
+	snd_mixart_exit_mailbox(mgr);
+
+	/* release irq  */
+	if (mgr->irq >= 0)
+		free_irq(mgr->irq, mgr);
+
+	/* reset board if some firmware was loaded */
+	if(mgr->dsp_loaded) {
+		snd_mixart_reset_board(mgr);
+		snd_printdd("reset miXart !\n");
+	}
+
+	/* release the i/o ports */
+	for (i = 0; i < 2; i++) {
+		if (mgr->mem[i].virt)
+			iounmap(mgr->mem[i].virt);
+	}
+	pci_release_regions(mgr->pci);
+
+	/* free flowarray */
+	if(mgr->flowinfo.area) {
+		snd_dma_free_pages(&mgr->flowinfo);
+		mgr->flowinfo.area = NULL;
+	}
+	/* free bufferarray */
+	if(mgr->bufferinfo.area) {
+		snd_dma_free_pages(&mgr->bufferinfo);
+		mgr->bufferinfo.area = NULL;
+	}
+
+	pci_disable_device(mgr->pci);
+	kfree(mgr);
+	return 0;
+}
+
+/*
+ * proc interface
+ */
+
+/*
+  mixart_BA0 proc interface for BAR 0 - read callback
+ */
+static ssize_t snd_mixart_BA0_read(struct snd_info_entry *entry,
+				   void *file_private_data,
+				   struct file *file, char __user *buf,
+				   size_t count, loff_t pos)
+{
+	struct mixart_mgr *mgr = entry->private_data;
+
+	count = count & ~3; /* make sure the read size is a multiple of 4 bytes */
+	if (copy_to_user_fromio(buf, MIXART_MEM(mgr, pos), count))
+		return -EFAULT;
+	return count;
+}
+
+/*
+  mixart_BA1 proc interface for BAR 1 - read callback
+ */
+static ssize_t snd_mixart_BA1_read(struct snd_info_entry *entry,
+				   void *file_private_data,
+				   struct file *file, char __user *buf,
+				   size_t count, loff_t pos)
+{
+	struct mixart_mgr *mgr = entry->private_data;
+
+	count = count & ~3; /* make sure the read size is a multiple of 4 bytes */
+	if (copy_to_user_fromio(buf, MIXART_REG(mgr, pos), count))
+		return -EFAULT;
+	return count;
+}
+
+static struct snd_info_entry_ops snd_mixart_proc_ops_BA0 = {
+	.read   = snd_mixart_BA0_read,
+};
+
+static struct snd_info_entry_ops snd_mixart_proc_ops_BA1 = {
+	.read   = snd_mixart_BA1_read,
+};
+
+
+static void snd_mixart_proc_read(struct snd_info_entry *entry, 
+                                 struct snd_info_buffer *buffer)
+{
+	struct snd_mixart *chip = entry->private_data;        
+	u32 ref; 
+
+	snd_iprintf(buffer, "Digigram miXart (alsa card %d)\n\n", chip->chip_idx);
+
+	/* stats available when embedded OS is running */
+	if (chip->mgr->dsp_loaded & ( 1 << MIXART_MOTHERBOARD_ELF_INDEX)) {
+		snd_iprintf(buffer, "- hardware -\n");
+		switch (chip->mgr->board_type ) {
+		case MIXART_DAUGHTER_TYPE_NONE     : snd_iprintf(buffer, "\tmiXart8 (no daughter board)\n\n"); break;
+		case MIXART_DAUGHTER_TYPE_AES      : snd_iprintf(buffer, "\tmiXart8 AES/EBU\n\n"); break;
+		case MIXART_DAUGHTER_TYPE_COBRANET : snd_iprintf(buffer, "\tmiXart8 Cobranet\n\n"); break;
+		default:                             snd_iprintf(buffer, "\tUNKNOWN!\n\n"); break;
+		}
+
+		snd_iprintf(buffer, "- system load -\n");	 
+
+		/* get perf reference */
+
+		ref = readl_be( MIXART_MEM( chip->mgr, MIXART_PSEUDOREG_PERF_SYSTEM_LOAD_OFFSET));
+
+		if (ref) {
+			u32 mailbox   = 100 * readl_be( MIXART_MEM( chip->mgr, MIXART_PSEUDOREG_PERF_MAILBX_LOAD_OFFSET)) / ref;
+			u32 streaming = 100 * readl_be( MIXART_MEM( chip->mgr, MIXART_PSEUDOREG_PERF_STREAM_LOAD_OFFSET)) / ref;
+			u32 interr    = 100 * readl_be( MIXART_MEM( chip->mgr, MIXART_PSEUDOREG_PERF_INTERR_LOAD_OFFSET)) / ref;
+
+			snd_iprintf(buffer, "\tstreaming          : %d\n", streaming);
+			snd_iprintf(buffer, "\tmailbox            : %d\n", mailbox);
+			snd_iprintf(buffer, "\tinterrups handling : %d\n\n", interr);
+		}
+	} /* endif elf loaded */
+}
+
+static void __devinit snd_mixart_proc_init(struct snd_mixart *chip)
+{
+	struct snd_info_entry *entry;
+
+	/* text interface to read perf and temp meters */
+	if (! snd_card_proc_new(chip->card, "board_info", &entry)) {
+		entry->private_data = chip;
+		entry->c.text.read = snd_mixart_proc_read;
+	}
+
+	if (! snd_card_proc_new(chip->card, "mixart_BA0", &entry)) {
+		entry->content = SNDRV_INFO_CONTENT_DATA;
+		entry->private_data = chip->mgr;	
+		entry->c.ops = &snd_mixart_proc_ops_BA0;
+		entry->size = MIXART_BA0_SIZE;
+	}
+	if (! snd_card_proc_new(chip->card, "mixart_BA1", &entry)) {
+		entry->content = SNDRV_INFO_CONTENT_DATA;
+		entry->private_data = chip->mgr;
+		entry->c.ops = &snd_mixart_proc_ops_BA1;
+		entry->size = MIXART_BA1_SIZE;
+	}
+}
+/* end of proc interface */
+
+
+/*
+ *    probe function - creates the card manager
+ */
+static int __devinit snd_mixart_probe(struct pci_dev *pci,
+				      const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct mixart_mgr *mgr;
+	unsigned int i;
+	int err;
+	size_t size;
+
+	/*
+	 */
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (! enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	/* enable PCI device */
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	pci_set_master(pci);
+
+	/* check if we can restrict PCI DMA transfers to 32 bits */
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(32)) < 0) {
+		snd_printk(KERN_ERR "architecture does not support 32bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	/*
+	 */
+	mgr = kzalloc(sizeof(*mgr), GFP_KERNEL);
+	if (! mgr) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	mgr->pci = pci;
+	mgr->irq = -1;
+
+	/* resource assignment */
+	if ((err = pci_request_regions(pci, CARD_NAME)) < 0) {
+		kfree(mgr);
+		pci_disable_device(pci);
+		return err;
+	}
+	for (i = 0; i < 2; i++) {
+		mgr->mem[i].phys = pci_resource_start(pci, i);
+		mgr->mem[i].virt = pci_ioremap_bar(pci, i);
+		if (!mgr->mem[i].virt) {
+		        printk(KERN_ERR "unable to remap resource 0x%lx\n",
+			       mgr->mem[i].phys);
+			snd_mixart_free(mgr);
+			return -EBUSY;
+		}
+	}
+
+	if (request_irq(pci->irq, snd_mixart_interrupt, IRQF_SHARED,
+			CARD_NAME, mgr)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_mixart_free(mgr);
+		return -EBUSY;
+	}
+	mgr->irq = pci->irq;
+
+	sprintf(mgr->shortname, "Digigram miXart");
+	sprintf(mgr->longname, "%s at 0x%lx & 0x%lx, irq %i", mgr->shortname, mgr->mem[0].phys, mgr->mem[1].phys, mgr->irq);
+
+	/* ISR spinlock  */
+	spin_lock_init(&mgr->lock);
+
+	/* init mailbox  */
+	mgr->msg_fifo_readptr = 0;
+	mgr->msg_fifo_writeptr = 0;
+
+	spin_lock_init(&mgr->msg_lock);
+	mutex_init(&mgr->msg_mutex);
+	init_waitqueue_head(&mgr->msg_sleep);
+	atomic_set(&mgr->msg_processed, 0);
+
+	/* init setup mutex*/
+	mutex_init(&mgr->setup_mutex);
+
+	/* init message taslket */
+	tasklet_init(&mgr->msg_taskq, snd_mixart_msg_tasklet, (unsigned long) mgr);
+
+	/* card assignment */
+	mgr->num_cards = MIXART_MAX_CARDS; /* 4  FIXME: configurable? */
+	for (i = 0; i < mgr->num_cards; i++) {
+		struct snd_card *card;
+		char tmpid[16];
+		int idx;
+
+		if (index[dev] < 0)
+			idx = index[dev];
+		else
+			idx = index[dev] + i;
+		snprintf(tmpid, sizeof(tmpid), "%s-%d", id[dev] ? id[dev] : "MIXART", i);
+		err = snd_card_create(idx, tmpid, THIS_MODULE, 0, &card);
+
+		if (err < 0) {
+			snd_printk(KERN_ERR "cannot allocate the card %d\n", i);
+			snd_mixart_free(mgr);
+			return err;
+		}
+
+		strcpy(card->driver, CARD_NAME);
+		sprintf(card->shortname, "%s [PCM #%d]", mgr->shortname, i);
+		sprintf(card->longname, "%s [PCM #%d]", mgr->longname, i);
+
+		if ((err = snd_mixart_create(mgr, card, i)) < 0) {
+			snd_card_free(card);
+			snd_mixart_free(mgr);
+			return err;
+		}
+
+		if(i==0) {
+			/* init proc interface only for chip0 */
+			snd_mixart_proc_init(mgr->chip[i]);
+		}
+
+		if ((err = snd_card_register(card)) < 0) {
+			snd_mixart_free(mgr);
+			return err;
+		}
+	}
+
+	/* init firmware status (mgr->dsp_loaded reset in hwdep_new) */
+	mgr->board_type = MIXART_DAUGHTER_TYPE_NONE;
+
+	/* create array of streaminfo */
+	size = PAGE_ALIGN( (MIXART_MAX_STREAM_PER_CARD * MIXART_MAX_CARDS *
+			    sizeof(struct mixart_flowinfo)) );
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				size, &mgr->flowinfo) < 0) {
+		snd_mixart_free(mgr);
+		return -ENOMEM;
+	}
+	/* init streaminfo_array */
+	memset(mgr->flowinfo.area, 0, size);
+
+	/* create array of bufferinfo */
+	size = PAGE_ALIGN( (MIXART_MAX_STREAM_PER_CARD * MIXART_MAX_CARDS *
+			    sizeof(struct mixart_bufferinfo)) );
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				size, &mgr->bufferinfo) < 0) {
+		snd_mixart_free(mgr);
+		return -ENOMEM;
+	}
+	/* init bufferinfo_array */
+	memset(mgr->bufferinfo.area, 0, size);
+
+	/* set up firmware */
+	err = snd_mixart_setup_firmware(mgr);
+	if (err < 0) {
+		snd_mixart_free(mgr);
+		return err;
+	}
+
+	pci_set_drvdata(pci, mgr);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_mixart_remove(struct pci_dev *pci)
+{
+	snd_mixart_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "Digigram miXart",
+	.id_table = snd_mixart_ids,
+	.probe = snd_mixart_probe,
+	.remove = __devexit_p(snd_mixart_remove),
+};
+
+static int __init alsa_card_mixart_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_mixart_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_mixart_init)
+module_exit(alsa_card_mixart_exit)
diff --git a/linux/sound/pci/mixart/mixart.h b/linux/sound/pci/mixart/mixart.h
new file mode 100644
index 0000000..561634d
--- /dev/null
+++ b/linux/sound/pci/mixart/mixart.h
@@ -0,0 +1,228 @@
+/*
+ * Driver for Digigram miXart soundcards
+ *
+ * main header file
+ *
+ * Copyright (c) 2003 by Digigram <alsa@digigram.com>
+ *
+ *   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 __SOUND_MIXART_H
+#define __SOUND_MIXART_H
+
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <sound/pcm.h>
+
+#define MIXART_DRIVER_VERSION	0x000100	/* 0.1.0 */
+
+
+/*
+ */
+
+struct mixart_uid {
+	u32 object_id;
+	u32 desc;
+};
+
+struct mem_area {
+	unsigned long phys;
+	void __iomem *virt;
+	struct resource *res;
+};
+
+
+struct mixart_route {
+	unsigned char connected;
+	unsigned char phase_inv;
+	int volume;
+};
+
+
+/* firmware status codes  */
+#define MIXART_MOTHERBOARD_XLX_INDEX  0
+#define MIXART_MOTHERBOARD_ELF_INDEX  1
+#define MIXART_AESEBUBOARD_XLX_INDEX  2
+#define MIXART_HARDW_FILES_MAX_INDEX  3  /* xilinx, elf, AESEBU xilinx */
+
+#define MIXART_MAX_CARDS	4
+#define MSG_FIFO_SIZE           16
+
+#define MIXART_MAX_PHYS_CONNECTORS  (MIXART_MAX_CARDS * 2 * 2) /* 4 * stereo * (analog+digital) */
+
+struct mixart_mgr {
+	unsigned int num_cards;
+	struct snd_mixart *chip[MIXART_MAX_CARDS];
+
+	struct pci_dev *pci;
+
+	int irq;
+
+	/* memory-maps */
+	struct mem_area mem[2];
+
+	/* share the name */
+	char shortname[32];         /* short name of this soundcard */
+	char longname[80];          /* name of this soundcard */
+
+	/* message tasklet */
+	struct tasklet_struct msg_taskq;
+
+	/* one and only blocking message or notification may be pending  */
+	u32 pending_event;
+	wait_queue_head_t msg_sleep;
+
+	/* messages stored for tasklet */
+	u32 msg_fifo[MSG_FIFO_SIZE];
+	int msg_fifo_readptr;
+	int msg_fifo_writeptr;
+	atomic_t msg_processed;       /* number of messages to be processed in takslet */
+
+	spinlock_t lock;              /* interrupt spinlock */
+	spinlock_t msg_lock;          /* mailbox spinlock */
+	struct mutex msg_mutex;   /* mutex for blocking_requests */
+
+	struct mutex setup_mutex; /* mutex used in hw_params, open and close */
+
+	/* hardware interface */
+	unsigned int dsp_loaded;      /* bit flags of loaded dsp indices */
+	unsigned int board_type;      /* read from embedded once elf file is loaded, 250 = miXart8, 251 = with AES, 252 = with Cobranet */
+
+	struct snd_dma_buffer flowinfo;
+	struct snd_dma_buffer bufferinfo;
+
+	struct mixart_uid         uid_console_manager;
+	int sample_rate;
+	int ref_count_rate;
+
+	struct mutex mixer_mutex; /* mutex for mixer */
+
+};
+
+
+#define MIXART_STREAM_STATUS_FREE	0
+#define MIXART_STREAM_STATUS_OPEN	1
+#define MIXART_STREAM_STATUS_RUNNING	2
+#define MIXART_STREAM_STATUS_DRAINING	3
+#define MIXART_STREAM_STATUS_PAUSE	4
+
+#define MIXART_PLAYBACK_STREAMS		4
+#define MIXART_CAPTURE_STREAMS		1
+
+#define MIXART_PCM_ANALOG		0
+#define MIXART_PCM_DIGITAL		1
+#define MIXART_PCM_TOTAL		2
+
+#define MIXART_MAX_STREAM_PER_CARD  (MIXART_PCM_TOTAL * (MIXART_PLAYBACK_STREAMS + MIXART_CAPTURE_STREAMS) )
+
+
+#define MIXART_NOTIFY_CARD_MASK		0xF000
+#define MIXART_NOTIFY_CARD_OFFSET	12
+#define MIXART_NOTIFY_PCM_MASK		0x0F00
+#define MIXART_NOTIFY_PCM_OFFSET	8
+#define MIXART_NOTIFY_CAPT_MASK		0x0080
+#define MIXART_NOTIFY_SUBS_MASK		0x007F
+
+
+struct mixart_stream {
+	struct snd_pcm_substream *substream;
+	struct mixart_pipe *pipe;
+	int pcm_number;
+
+	int status;      /* nothing, running, draining */
+
+	u64  abs_period_elapsed;  /* last absolute stream position where period_elapsed was called (multiple of runtime->period_size) */
+	u32  buf_periods;         /* periods counter in the buffer (< runtime->periods) */
+	u32  buf_period_frag;     /* defines with buf_period_pos the exact position in the buffer (< runtime->period_size) */
+
+	int channels;
+};
+
+
+enum mixart_pipe_status {
+	PIPE_UNDEFINED,
+	PIPE_STOPPED,
+	PIPE_RUNNING,
+	PIPE_CLOCK_SET
+};
+
+struct mixart_pipe {
+	struct mixart_uid group_uid;			/* id of the pipe, as returned by embedded */
+	int          stream_count;
+	struct mixart_uid uid_left_connector;	/* UID's for the audio connectors */
+	struct mixart_uid uid_right_connector;
+	enum mixart_pipe_status status;
+	int references;             /* number of subs openned */
+	int monitoring;             /* pipe used for monitoring issue */
+};
+
+
+struct snd_mixart {
+	struct snd_card *card;
+	struct mixart_mgr *mgr;
+	int chip_idx;               /* zero based */
+	struct snd_hwdep *hwdep;	    /* DSP loader, only for the first card */
+
+	struct snd_pcm *pcm;             /* PCM analog i/o */
+	struct snd_pcm *pcm_dig;         /* PCM digital i/o */
+
+	/* allocate stereo pipe for instance */
+	struct mixart_pipe pipe_in_ana;
+	struct mixart_pipe pipe_out_ana;
+
+	/* if AES/EBU daughter board is available, additional pipes possible on pcm_dig */
+	struct mixart_pipe pipe_in_dig;
+	struct mixart_pipe pipe_out_dig;
+
+	struct mixart_stream playback_stream[MIXART_PCM_TOTAL][MIXART_PLAYBACK_STREAMS]; /* 0 = pcm, 1 = pcm_dig */
+	struct mixart_stream capture_stream[MIXART_PCM_TOTAL];                           /* 0 = pcm, 1 = pcm_dig */
+
+	/* UID's for the physical io's */
+	struct mixart_uid uid_out_analog_physio;
+	struct mixart_uid uid_in_analog_physio;
+
+	int analog_playback_active[2];		/* Mixer : Master Playback active (!mute) */
+	int analog_playback_volume[2];		/* Mixer : Master Playback Volume */
+	int analog_capture_volume[2];		/* Mixer : Master Capture Volume */
+	int digital_playback_active[2*MIXART_PLAYBACK_STREAMS][2];	/* Mixer : Digital Playback Active [(analog+AES output)*streams][stereo]*/
+	int digital_playback_volume[2*MIXART_PLAYBACK_STREAMS][2];	/* Mixer : Digital Playback Volume [(analog+AES output)*streams][stereo]*/
+	int digital_capture_volume[2][2];	/* Mixer : Digital Capture Volume [analog+AES output][stereo] */
+	int monitoring_active[2];		/* Mixer : Monitoring Active */
+	int monitoring_volume[2];		/* Mixer : Monitoring Volume */
+};
+
+struct mixart_bufferinfo
+{
+	u32 buffer_address;
+	u32 reserved[5];
+	u32 available_length;
+	u32 buffer_id;
+};
+
+struct mixart_flowinfo
+{
+	u32 bufferinfo_array_phy_address;
+	u32 reserved[11];
+	u32 bufferinfo_count;
+	u32 capture;
+};
+
+/* exported */
+int snd_mixart_create_pcm(struct snd_mixart * chip);
+struct mixart_pipe *snd_mixart_add_ref_pipe(struct snd_mixart *chip, int pcm_number, int capture, int monitoring);
+int snd_mixart_kill_ref_pipe(struct mixart_mgr *mgr, struct mixart_pipe *pipe, int monitoring);
+
+#endif /* __SOUND_MIXART_H */
diff --git a/linux/sound/pci/mixart/mixart_core.c b/linux/sound/pci/mixart/mixart_core.c
new file mode 100644
index 0000000..d3350f3
--- /dev/null
+++ b/linux/sound/pci/mixart/mixart_core.c
@@ -0,0 +1,598 @@
+/*
+ * Driver for Digigram miXart soundcards
+ *
+ * low level interface with interrupt handling and mail box implementation
+ *
+ * Copyright (c) 2003 by Digigram <alsa@digigram.com>
+ *
+ *   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/interrupt.h>
+#include <linux/mutex.h>
+
+#include <asm/io.h>
+#include <sound/core.h>
+#include "mixart.h"
+#include "mixart_hwdep.h"
+#include "mixart_core.h"
+
+
+#define MSG_TIMEOUT_JIFFIES         (400 * HZ) / 1000 /* 400 ms */
+
+#define MSG_DESCRIPTOR_SIZE         0x24
+#define MSG_HEADER_SIZE             (MSG_DESCRIPTOR_SIZE + 4)
+
+#define MSG_DEFAULT_SIZE            512
+
+#define MSG_TYPE_MASK               0x00000003    /* mask for following types */
+#define MSG_TYPE_NOTIFY             0             /* embedded -> driver (only notification, do not get_msg() !) */
+#define MSG_TYPE_COMMAND            1             /* driver <-> embedded (a command has no answer) */
+#define MSG_TYPE_REQUEST            2             /* driver -> embedded (request will get an answer back) */
+#define MSG_TYPE_ANSWER             3             /* embedded -> driver */
+#define MSG_CANCEL_NOTIFY_MASK      0x80000000    /* this bit is set for a notification that has been canceled */
+
+
+static int retrieve_msg_frame(struct mixart_mgr *mgr, u32 *msg_frame)
+{
+	/* read the message frame fifo */
+	u32 headptr, tailptr;
+
+	tailptr = readl_be(MIXART_MEM(mgr, MSG_OUTBOUND_POST_TAIL));
+	headptr = readl_be(MIXART_MEM(mgr, MSG_OUTBOUND_POST_HEAD));
+
+	if (tailptr == headptr)
+		return 0; /* no message posted */
+
+	if (tailptr < MSG_OUTBOUND_POST_STACK)
+		return 0; /* error */
+	if (tailptr >= MSG_OUTBOUND_POST_STACK + MSG_BOUND_STACK_SIZE)
+		return 0; /* error */
+
+	*msg_frame = readl_be(MIXART_MEM(mgr, tailptr));
+
+	/* increment the tail index */
+	tailptr += 4;
+	if( tailptr >= (MSG_OUTBOUND_POST_STACK+MSG_BOUND_STACK_SIZE) )
+		tailptr = MSG_OUTBOUND_POST_STACK;
+	writel_be(tailptr, MIXART_MEM(mgr, MSG_OUTBOUND_POST_TAIL));
+
+	return 1;
+}
+
+static int get_msg(struct mixart_mgr *mgr, struct mixart_msg *resp,
+		   u32 msg_frame_address )
+{
+	unsigned long flags;
+	u32  headptr;
+	u32  size;
+	int  err;
+#ifndef __BIG_ENDIAN
+	unsigned int i;
+#endif
+
+	spin_lock_irqsave(&mgr->msg_lock, flags);
+	err = 0;
+
+	/* copy message descriptor from miXart to driver */
+	size                =  readl_be(MIXART_MEM(mgr, msg_frame_address));       /* size of descriptor + response */
+	resp->message_id    =  readl_be(MIXART_MEM(mgr, msg_frame_address + 4));   /* dwMessageID */
+	resp->uid.object_id =  readl_be(MIXART_MEM(mgr, msg_frame_address + 8));   /* uidDest */
+	resp->uid.desc      =  readl_be(MIXART_MEM(mgr, msg_frame_address + 12));  /* */
+
+	if( (size < MSG_DESCRIPTOR_SIZE) || (resp->size < (size - MSG_DESCRIPTOR_SIZE))) {
+		err = -EINVAL;
+		snd_printk(KERN_ERR "problem with response size = %d\n", size);
+		goto _clean_exit;
+	}
+	size -= MSG_DESCRIPTOR_SIZE;
+
+	memcpy_fromio(resp->data, MIXART_MEM(mgr, msg_frame_address + MSG_HEADER_SIZE ), size);
+	resp->size = size;
+
+	/* swap if necessary */
+#ifndef __BIG_ENDIAN
+	size /= 4; /* u32 size */
+	for(i=0; i < size; i++) {
+		((u32*)resp->data)[i] = be32_to_cpu(((u32*)resp->data)[i]);
+	}
+#endif
+
+	/*
+	 * free message frame address
+	 */
+	headptr = readl_be(MIXART_MEM(mgr, MSG_OUTBOUND_FREE_HEAD));
+
+	if( (headptr < MSG_OUTBOUND_FREE_STACK) || ( headptr >= (MSG_OUTBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE))) {
+		err = -EINVAL;
+		goto _clean_exit;
+	}
+
+	/* give address back to outbound fifo */
+	writel_be(msg_frame_address, MIXART_MEM(mgr, headptr));
+
+	/* increment the outbound free head */
+	headptr += 4;
+	if( headptr >= (MSG_OUTBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE) )
+		headptr = MSG_OUTBOUND_FREE_STACK;
+
+	writel_be(headptr, MIXART_MEM(mgr, MSG_OUTBOUND_FREE_HEAD));
+
+ _clean_exit:
+	spin_unlock_irqrestore(&mgr->msg_lock, flags);
+
+	return err;
+}
+
+
+/*
+ * send a message to miXart. return: the msg_frame used for this message
+ */
+/* call with mgr->msg_lock held! */
+static int send_msg( struct mixart_mgr *mgr,
+		     struct mixart_msg *msg,
+		     int max_answersize,
+		     int mark_pending,
+		     u32 *msg_event)
+{
+	u32 headptr, tailptr;
+	u32 msg_frame_address;
+	int err, i;
+
+	if (snd_BUG_ON(msg->size % 4))
+		return -EINVAL;
+
+	err = 0;
+
+	/* get message frame address */
+	tailptr = readl_be(MIXART_MEM(mgr, MSG_INBOUND_FREE_TAIL));
+	headptr = readl_be(MIXART_MEM(mgr, MSG_INBOUND_FREE_HEAD));
+
+	if (tailptr == headptr) {
+		snd_printk(KERN_ERR "error: no message frame available\n");
+		return -EBUSY;
+	}
+
+	if( (tailptr < MSG_INBOUND_FREE_STACK) || (tailptr >= (MSG_INBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE))) {
+		return -EINVAL;
+	}
+
+	msg_frame_address = readl_be(MIXART_MEM(mgr, tailptr));
+	writel(0, MIXART_MEM(mgr, tailptr)); /* set address to zero on this fifo position */
+
+	/* increment the inbound free tail */
+	tailptr += 4;
+	if( tailptr >= (MSG_INBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE) )
+		tailptr = MSG_INBOUND_FREE_STACK;
+
+	writel_be(tailptr, MIXART_MEM(mgr, MSG_INBOUND_FREE_TAIL));
+
+	/* TODO : use memcpy_toio() with intermediate buffer to copy the message */
+
+	/* copy message descriptor to card memory */
+	writel_be( msg->size + MSG_DESCRIPTOR_SIZE,      MIXART_MEM(mgr, msg_frame_address) );      /* size of descriptor + request */
+	writel_be( msg->message_id ,                     MIXART_MEM(mgr, msg_frame_address + 4) );  /* dwMessageID */
+	writel_be( msg->uid.object_id,                   MIXART_MEM(mgr, msg_frame_address + 8) );  /* uidDest */
+	writel_be( msg->uid.desc,                        MIXART_MEM(mgr, msg_frame_address + 12) ); /* */
+	writel_be( MSG_DESCRIPTOR_SIZE,                  MIXART_MEM(mgr, msg_frame_address + 16) ); /* SizeHeader */
+	writel_be( MSG_DESCRIPTOR_SIZE,                  MIXART_MEM(mgr, msg_frame_address + 20) ); /* OffsetDLL_T16 */
+	writel_be( msg->size,                            MIXART_MEM(mgr, msg_frame_address + 24) ); /* SizeDLL_T16 */
+	writel_be( MSG_DESCRIPTOR_SIZE,                  MIXART_MEM(mgr, msg_frame_address + 28) ); /* OffsetDLL_DRV */
+	writel_be( 0,                                    MIXART_MEM(mgr, msg_frame_address + 32) ); /* SizeDLL_DRV */
+	writel_be( MSG_DESCRIPTOR_SIZE + max_answersize, MIXART_MEM(mgr, msg_frame_address + 36) ); /* dwExpectedAnswerSize */
+
+	/* copy message data to card memory */
+	for( i=0; i < msg->size; i+=4 ) {
+		writel_be( *(u32*)(msg->data + i), MIXART_MEM(mgr, MSG_HEADER_SIZE + msg_frame_address + i)  );
+	}
+
+	if( mark_pending ) {
+		if( *msg_event ) {
+			/* the pending event is the notification we wait for ! */
+			mgr->pending_event = *msg_event;
+		}
+		else {
+			/* the pending event is the answer we wait for (same address than the request)! */
+			mgr->pending_event = msg_frame_address;
+
+			/* copy address back to caller */
+			*msg_event = msg_frame_address;
+		}
+	}
+
+	/* mark the frame as a request (will have an answer) */
+	msg_frame_address |= MSG_TYPE_REQUEST;
+
+	/* post the frame */
+	headptr = readl_be(MIXART_MEM(mgr, MSG_INBOUND_POST_HEAD));
+
+	if( (headptr < MSG_INBOUND_POST_STACK) || (headptr >= (MSG_INBOUND_POST_STACK+MSG_BOUND_STACK_SIZE))) {
+		return -EINVAL;
+	}
+
+	writel_be(msg_frame_address, MIXART_MEM(mgr, headptr));
+
+	/* increment the inbound post head */
+	headptr += 4;
+	if( headptr >= (MSG_INBOUND_POST_STACK+MSG_BOUND_STACK_SIZE) )
+		headptr = MSG_INBOUND_POST_STACK;
+
+	writel_be(headptr, MIXART_MEM(mgr, MSG_INBOUND_POST_HEAD));
+
+	return 0;
+}
+
+
+int snd_mixart_send_msg(struct mixart_mgr *mgr, struct mixart_msg *request, int max_resp_size, void *resp_data)
+{
+	struct mixart_msg resp;
+	u32 msg_frame = 0; /* set to 0, so it's no notification to wait for, but the answer */
+	int err;
+	wait_queue_t wait;
+	long timeout;
+
+	mutex_lock(&mgr->msg_mutex);
+
+	init_waitqueue_entry(&wait, current);
+
+	spin_lock_irq(&mgr->msg_lock);
+	/* send the message */
+	err = send_msg(mgr, request, max_resp_size, 1, &msg_frame);  /* send and mark the answer pending */
+	if (err) {
+		spin_unlock_irq(&mgr->msg_lock);
+		mutex_unlock(&mgr->msg_mutex);
+		return err;
+	}
+
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	add_wait_queue(&mgr->msg_sleep, &wait);
+	spin_unlock_irq(&mgr->msg_lock);
+	timeout = schedule_timeout(MSG_TIMEOUT_JIFFIES);
+	remove_wait_queue(&mgr->msg_sleep, &wait);
+
+	if (! timeout) {
+		/* error - no ack */
+		mutex_unlock(&mgr->msg_mutex);
+		snd_printk(KERN_ERR "error: no reponse on msg %x\n", msg_frame);
+		return -EIO;
+	}
+
+	/* retrieve the answer into the same struct mixart_msg */
+	resp.message_id = 0;
+	resp.uid = (struct mixart_uid){0,0};
+	resp.data = resp_data;
+	resp.size = max_resp_size;
+
+	err = get_msg(mgr, &resp, msg_frame);
+
+	if( request->message_id != resp.message_id )
+		snd_printk(KERN_ERR "REPONSE ERROR!\n");
+
+	mutex_unlock(&mgr->msg_mutex);
+	return err;
+}
+
+
+int snd_mixart_send_msg_wait_notif(struct mixart_mgr *mgr,
+				   struct mixart_msg *request, u32 notif_event)
+{
+	int err;
+	wait_queue_t wait;
+	long timeout;
+
+	if (snd_BUG_ON(!notif_event))
+		return -EINVAL;
+	if (snd_BUG_ON((notif_event & MSG_TYPE_MASK) != MSG_TYPE_NOTIFY))
+		return -EINVAL;
+	if (snd_BUG_ON(notif_event & MSG_CANCEL_NOTIFY_MASK))
+		return -EINVAL;
+
+	mutex_lock(&mgr->msg_mutex);
+
+	init_waitqueue_entry(&wait, current);
+
+	spin_lock_irq(&mgr->msg_lock);
+	/* send the message */
+	err = send_msg(mgr, request, MSG_DEFAULT_SIZE, 1, &notif_event);  /* send and mark the notification event pending */
+	if(err) {
+		spin_unlock_irq(&mgr->msg_lock);
+		mutex_unlock(&mgr->msg_mutex);
+		return err;
+	}
+
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	add_wait_queue(&mgr->msg_sleep, &wait);
+	spin_unlock_irq(&mgr->msg_lock);
+	timeout = schedule_timeout(MSG_TIMEOUT_JIFFIES);
+	remove_wait_queue(&mgr->msg_sleep, &wait);
+
+	if (! timeout) {
+		/* error - no ack */
+		mutex_unlock(&mgr->msg_mutex);
+		snd_printk(KERN_ERR "error: notification %x not received\n", notif_event);
+		return -EIO;
+	}
+
+	mutex_unlock(&mgr->msg_mutex);
+	return 0;
+}
+
+
+int snd_mixart_send_msg_nonblock(struct mixart_mgr *mgr, struct mixart_msg *request)
+{
+	u32 message_frame;
+	unsigned long flags;
+	int err;
+
+	/* just send the message (do not mark it as a pending one) */
+	spin_lock_irqsave(&mgr->msg_lock, flags);
+	err = send_msg(mgr, request, MSG_DEFAULT_SIZE, 0, &message_frame);
+	spin_unlock_irqrestore(&mgr->msg_lock, flags);
+
+	/* the answer will be handled by snd_struct mixart_msgasklet()  */
+	atomic_inc(&mgr->msg_processed);
+
+	return err;
+}
+
+
+/* common buffer of tasklet and interrupt to send/receive messages */
+static u32 mixart_msg_data[MSG_DEFAULT_SIZE / 4];
+
+
+void snd_mixart_msg_tasklet(unsigned long arg)
+{
+	struct mixart_mgr *mgr = ( struct mixart_mgr*)(arg);
+	struct mixart_msg resp;
+	u32 msg, addr, type;
+	int err;
+
+	spin_lock(&mgr->lock);
+
+	while (mgr->msg_fifo_readptr != mgr->msg_fifo_writeptr) {
+		msg = mgr->msg_fifo[mgr->msg_fifo_readptr];
+		mgr->msg_fifo_readptr++;
+		mgr->msg_fifo_readptr %= MSG_FIFO_SIZE;
+
+		/* process the message ... */
+		addr = msg & ~MSG_TYPE_MASK;
+		type = msg & MSG_TYPE_MASK;
+
+		switch (type) {
+		case MSG_TYPE_ANSWER:
+			/* answer to a message on that we did not wait for (send_msg_nonblock) */
+			resp.message_id = 0;
+			resp.data = mixart_msg_data;
+			resp.size = sizeof(mixart_msg_data);
+			err = get_msg(mgr, &resp, addr);
+			if( err < 0 ) {
+				snd_printk(KERN_ERR "tasklet: error(%d) reading mf %x\n", err, msg);
+				break;
+			}
+
+			switch(resp.message_id) {
+			case MSG_STREAM_START_INPUT_STAGE_PACKET:
+			case MSG_STREAM_START_OUTPUT_STAGE_PACKET:
+			case MSG_STREAM_STOP_INPUT_STAGE_PACKET:
+			case MSG_STREAM_STOP_OUTPUT_STAGE_PACKET:
+				if(mixart_msg_data[0])
+					snd_printk(KERN_ERR "tasklet : error MSG_STREAM_ST***_***PUT_STAGE_PACKET status=%x\n", mixart_msg_data[0]);
+				break;
+			default:
+				snd_printdd("tasklet received mf(%x) : msg_id(%x) uid(%x, %x) size(%zd)\n",
+					   msg, resp.message_id, resp.uid.object_id, resp.uid.desc, resp.size);
+				break;
+			}
+			break;
+ 		case MSG_TYPE_NOTIFY:
+			/* msg contains no address ! do not get_msg() ! */
+		case MSG_TYPE_COMMAND:
+			/* get_msg() necessary */
+		default:
+			snd_printk(KERN_ERR "tasklet doesn't know what to do with message %x\n", msg);
+		} /* switch type */
+
+		/* decrement counter */
+		atomic_dec(&mgr->msg_processed);
+
+	} /* while there is a msg in fifo */
+
+	spin_unlock(&mgr->lock);
+}
+
+
+irqreturn_t snd_mixart_interrupt(int irq, void *dev_id)
+{
+	struct mixart_mgr *mgr = dev_id;
+	int err;
+	struct mixart_msg resp;
+
+	u32 msg;
+	u32 it_reg;
+
+	spin_lock(&mgr->lock);
+
+	it_reg = readl_le(MIXART_REG(mgr, MIXART_PCI_OMISR_OFFSET));
+	if( !(it_reg & MIXART_OIDI) ) {
+		/* this device did not cause the interrupt */
+		spin_unlock(&mgr->lock);
+		return IRQ_NONE;
+	}
+
+	/* mask all interrupts */
+	writel_le(MIXART_HOST_ALL_INTERRUPT_MASKED, MIXART_REG(mgr, MIXART_PCI_OMIMR_OFFSET));
+
+	/* outdoorbell register clear */
+	it_reg = readl(MIXART_REG(mgr, MIXART_PCI_ODBR_OFFSET));
+	writel(it_reg, MIXART_REG(mgr, MIXART_PCI_ODBR_OFFSET));
+
+	/* clear interrupt */
+	writel_le( MIXART_OIDI, MIXART_REG(mgr, MIXART_PCI_OMISR_OFFSET) );
+
+	/* process interrupt */
+	while (retrieve_msg_frame(mgr, &msg)) {
+
+		switch (msg & MSG_TYPE_MASK) {
+		case MSG_TYPE_COMMAND:
+			resp.message_id = 0;
+			resp.data = mixart_msg_data;
+			resp.size = sizeof(mixart_msg_data);
+			err = get_msg(mgr, &resp, msg & ~MSG_TYPE_MASK);
+			if( err < 0 ) {
+				snd_printk(KERN_ERR "interrupt: error(%d) reading mf %x\n", err, msg);
+				break;
+			}
+
+			if(resp.message_id == MSG_SERVICES_TIMER_NOTIFY) {
+				int i;
+				struct mixart_timer_notify *notify;
+				notify = (struct mixart_timer_notify *)mixart_msg_data;
+
+				for(i=0; i<notify->stream_count; i++) {
+
+					u32 buffer_id = notify->streams[i].buffer_id;
+					unsigned int chip_number =  (buffer_id & MIXART_NOTIFY_CARD_MASK) >> MIXART_NOTIFY_CARD_OFFSET; /* card0 to 3 */
+					unsigned int pcm_number  =  (buffer_id & MIXART_NOTIFY_PCM_MASK ) >> MIXART_NOTIFY_PCM_OFFSET;  /* pcm0 to 3  */
+					unsigned int sub_number  =   buffer_id & MIXART_NOTIFY_SUBS_MASK;             /* 0 to MIXART_PLAYBACK_STREAMS */
+					unsigned int is_capture  = ((buffer_id & MIXART_NOTIFY_CAPT_MASK) != 0);      /* playback == 0 / capture == 1 */
+
+					struct snd_mixart *chip  = mgr->chip[chip_number];
+					struct mixart_stream *stream;
+
+					if ((chip_number >= mgr->num_cards) || (pcm_number >= MIXART_PCM_TOTAL) || (sub_number >= MIXART_PLAYBACK_STREAMS)) {
+						snd_printk(KERN_ERR "error MSG_SERVICES_TIMER_NOTIFY buffer_id (%x) pos(%d)\n",
+							   buffer_id, notify->streams[i].sample_pos_low_part);
+						break;
+					}
+
+					if (is_capture)
+						stream = &chip->capture_stream[pcm_number];
+					else
+						stream = &chip->playback_stream[pcm_number][sub_number];
+
+					if (stream->substream && (stream->status == MIXART_STREAM_STATUS_RUNNING)) {
+						struct snd_pcm_runtime *runtime = stream->substream->runtime;
+						int elapsed = 0;
+						u64 sample_count = ((u64)notify->streams[i].sample_pos_high_part) << 32;
+						sample_count |= notify->streams[i].sample_pos_low_part;
+
+						while (1) {
+							u64 new_elapse_pos = stream->abs_period_elapsed +  runtime->period_size;
+
+							if (new_elapse_pos > sample_count) {
+								break; /* while */
+							}
+							else {
+								elapsed = 1;
+								stream->buf_periods++;
+								if (stream->buf_periods >= runtime->periods)
+									stream->buf_periods = 0;
+
+								stream->abs_period_elapsed = new_elapse_pos;
+							}
+						}
+						stream->buf_period_frag = (u32)( sample_count - stream->abs_period_elapsed );
+
+						if(elapsed) {
+							spin_unlock(&mgr->lock);
+							snd_pcm_period_elapsed(stream->substream);
+							spin_lock(&mgr->lock);
+						}
+					}
+				}
+				break;
+			}
+			if(resp.message_id == MSG_SERVICES_REPORT_TRACES) {
+				if(resp.size > 1) {
+#ifndef __BIG_ENDIAN
+					/* Traces are text: the swapped msg_data has to be swapped back ! */
+					int i;
+					for(i=0; i<(resp.size/4); i++) {
+						(mixart_msg_data)[i] = cpu_to_be32((mixart_msg_data)[i]);
+					}
+#endif
+					((char*)mixart_msg_data)[resp.size - 1] = 0;
+					snd_printdd("MIXART TRACE : %s\n", (char*)mixart_msg_data);
+				}
+				break;
+			}
+
+			snd_printdd("command %x not handled\n", resp.message_id);
+			break;
+
+		case MSG_TYPE_NOTIFY:
+			if(msg & MSG_CANCEL_NOTIFY_MASK) {
+				msg &= ~MSG_CANCEL_NOTIFY_MASK;
+				snd_printk(KERN_ERR "canceled notification %x !\n", msg);
+			}
+			/* no break, continue ! */
+		case MSG_TYPE_ANSWER:
+			/* answer or notification to a message we are waiting for*/
+			spin_lock(&mgr->msg_lock);
+			if( (msg & ~MSG_TYPE_MASK) == mgr->pending_event ) {
+				wake_up(&mgr->msg_sleep);
+				mgr->pending_event = 0;
+			}
+			/* answer to a message we did't want to wait for */
+			else {
+				mgr->msg_fifo[mgr->msg_fifo_writeptr] = msg;
+				mgr->msg_fifo_writeptr++;
+				mgr->msg_fifo_writeptr %= MSG_FIFO_SIZE;
+				tasklet_schedule(&mgr->msg_taskq);
+			}
+			spin_unlock(&mgr->msg_lock);
+			break;
+		case MSG_TYPE_REQUEST:
+		default:
+			snd_printdd("interrupt received request %x\n", msg);
+			/* TODO : are there things to do here ? */
+			break;
+		} /* switch on msg type */
+	} /* while there are msgs */
+
+	/* allow interrupt again */
+	writel_le( MIXART_ALLOW_OUTBOUND_DOORBELL, MIXART_REG( mgr, MIXART_PCI_OMIMR_OFFSET));
+
+	spin_unlock(&mgr->lock);
+
+	return IRQ_HANDLED;
+}
+
+
+void snd_mixart_init_mailbox(struct mixart_mgr *mgr)
+{
+	writel( 0, MIXART_MEM( mgr, MSG_HOST_RSC_PROTECTION ) );
+	writel( 0, MIXART_MEM( mgr, MSG_AGENT_RSC_PROTECTION ) );
+
+	/* allow outbound messagebox to generate interrupts */
+	if(mgr->irq >= 0) {
+		writel_le( MIXART_ALLOW_OUTBOUND_DOORBELL, MIXART_REG( mgr, MIXART_PCI_OMIMR_OFFSET));
+	}
+	return;
+}
+
+void snd_mixart_exit_mailbox(struct mixart_mgr *mgr)
+{
+	/* no more interrupts on outbound messagebox */
+	writel_le( MIXART_HOST_ALL_INTERRUPT_MASKED, MIXART_REG( mgr, MIXART_PCI_OMIMR_OFFSET));
+	return;
+}
+
+void snd_mixart_reset_board(struct mixart_mgr *mgr)
+{
+	/* reset miXart */
+	writel_be( 1, MIXART_REG(mgr, MIXART_BA1_BRUTAL_RESET_OFFSET) );
+	return;
+}
diff --git a/linux/sound/pci/mixart/mixart_core.h b/linux/sound/pci/mixart/mixart_core.h
new file mode 100644
index 0000000..c919b73
--- /dev/null
+++ b/linux/sound/pci/mixart/mixart_core.h
@@ -0,0 +1,571 @@
+/*
+ * Driver for Digigram miXart soundcards
+ *
+ * low level interface with interrupt handling and mail box implementation
+ *
+ * Copyright (c) 2003 by Digigram <alsa@digigram.com>
+ *
+ *   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 __SOUND_MIXART_CORE_H
+#define __SOUND_MIXART_CORE_H
+
+
+enum mixart_message_id {
+	MSG_CONNECTOR_GET_AUDIO_INFO         = 0x050008,
+	MSG_CONNECTOR_GET_OUT_AUDIO_LEVEL    = 0x050009,
+	MSG_CONNECTOR_SET_OUT_AUDIO_LEVEL    = 0x05000A,
+
+	MSG_CONSOLE_MANAGER                  = 0x070000,
+	MSG_CONSOLE_GET_CLOCK_UID            = 0x070003,
+
+	MSG_PHYSICALIO_SET_LEVEL             = 0x0F0008,
+
+	MSG_STREAM_ADD_INPUT_GROUP           = 0x130000,
+	MSG_STREAM_ADD_OUTPUT_GROUP          = 0x130001,
+	MSG_STREAM_DELETE_GROUP              = 0x130004,
+	MSG_STREAM_START_STREAM_GRP_PACKET   = 0x130006,
+	MSG_STREAM_START_INPUT_STAGE_PACKET  = 0x130007,
+	MSG_STREAM_START_OUTPUT_STAGE_PACKET = 0x130008,
+	MSG_STREAM_STOP_STREAM_GRP_PACKET    = 0x130009,
+	MSG_STREAM_STOP_INPUT_STAGE_PACKET   = 0x13000A,
+	MSG_STREAM_STOP_OUTPUT_STAGE_PACKET  = 0x13000B,
+	MSG_STREAM_SET_INPUT_STAGE_PARAM     = 0x13000F,
+	MSG_STREAM_SET_OUTPUT_STAGE_PARAM    = 0x130010,
+	MSG_STREAM_SET_IN_AUDIO_LEVEL        = 0x130015,
+	MSG_STREAM_SET_OUT_STREAM_LEVEL      = 0x130017,
+
+	MSG_SYSTEM_FIRST_ID                  = 0x160000,
+	MSG_SYSTEM_ENUM_PHYSICAL_IO          = 0x16000E,
+	MSG_SYSTEM_ENUM_PLAY_CONNECTOR       = 0x160017,
+	MSG_SYSTEM_ENUM_RECORD_CONNECTOR     = 0x160018,
+	MSG_SYSTEM_WAIT_SYNCHRO_CMD          = 0x16002C,
+	MSG_SYSTEM_SEND_SYNCHRO_CMD          = 0x16002D,
+
+	MSG_SERVICES_TIMER_NOTIFY            = 0x1D0404,
+	MSG_SERVICES_REPORT_TRACES           = 0x1D0700,
+
+	MSG_CLOCK_CHECK_PROPERTIES           = 0x200001,
+	MSG_CLOCK_SET_PROPERTIES             = 0x200002,
+};
+
+
+struct mixart_msg
+{
+	u32          message_id;
+	struct mixart_uid uid;
+	void*        data;
+	size_t       size;
+};
+
+/* structs used to communicate with miXart */
+
+struct mixart_enum_connector_resp
+{
+	u32  error_code;
+	u32  first_uid_offset;
+	u32  uid_count;
+	u32  current_uid_index;
+	struct mixart_uid uid[MIXART_MAX_PHYS_CONNECTORS];
+} __attribute__((packed));
+
+
+/* used for following struct */
+#define MIXART_FLOAT_P_22_0_TO_HEX      0x41b00000  /* 22.0f */
+#define MIXART_FLOAT_M_20_0_TO_HEX      0xc1a00000  /* -20.0f */
+#define MIXART_FLOAT____0_0_TO_HEX      0x00000000  /* 0.0f */
+
+struct mixart_audio_info_req
+{
+	u32 line_max_level;    /* float */
+	u32 micro_max_level;   /* float */
+	u32 cd_max_level;      /* float */
+} __attribute__((packed));
+
+struct mixart_analog_hw_info
+{
+	u32 is_present;
+	u32 hw_connection_type;
+	u32 max_level;         /* float */
+	u32 min_var_level;     /* float */
+	u32 max_var_level;     /* float */
+	u32 step_var_level;    /* float */
+	u32 fix_gain;          /* float */
+	u32 zero_var;          /* float */
+} __attribute__((packed));
+
+struct mixart_digital_hw_info
+{
+	u32   hw_connection_type;
+	u32   presence;
+	u32   clock;
+	u32   reserved;
+} __attribute__((packed));
+
+struct mixart_analog_info
+{
+	u32                     type_mask;
+	struct mixart_analog_hw_info micro_info;
+	struct mixart_analog_hw_info line_info;
+	struct mixart_analog_hw_info cd_info;
+	u32                     analog_level_present;
+} __attribute__((packed));
+
+struct mixart_digital_info
+{
+	u32 type_mask;
+	struct mixart_digital_hw_info aes_info;
+	struct mixart_digital_hw_info adat_info;
+} __attribute__((packed));
+
+struct mixart_audio_info
+{
+	u32                   clock_type_mask;
+	struct mixart_analog_info  analog_info;
+	struct mixart_digital_info digital_info;
+} __attribute__((packed));
+
+struct mixart_audio_info_resp
+{
+	u32                 txx_status;
+	struct mixart_audio_info info;
+} __attribute__((packed));
+
+
+/* used for nb_bytes_max_per_sample */
+#define MIXART_FLOAT_P__4_0_TO_HEX      0x40800000  /* +4.0f */
+#define MIXART_FLOAT_P__8_0_TO_HEX      0x41000000  /* +8.0f */
+
+struct mixart_stream_info
+{
+	u32 size_max_byte_frame;
+	u32 size_max_sample_frame;
+	u32 nb_bytes_max_per_sample;  /* float */
+} __attribute__((packed));
+
+/*  MSG_STREAM_ADD_INPUT_GROUP */
+/*  MSG_STREAM_ADD_OUTPUT_GROUP */
+
+struct mixart_streaming_group_req
+{
+	u32 stream_count;
+	u32 channel_count;
+	u32 user_grp_number;
+	u32 first_phys_audio;
+	u32 latency;
+	struct mixart_stream_info stream_info[32];
+	struct mixart_uid connector;
+	u32 flow_entry[32];
+} __attribute__((packed));
+
+struct mixart_stream_desc
+{
+	struct mixart_uid stream_uid;
+	u32          stream_desc;
+} __attribute__((packed));
+
+struct mixart_streaming_group
+{
+	u32                  status;
+	struct mixart_uid    group;
+	u32                  pipe_desc;
+	u32                  stream_count;
+	struct mixart_stream_desc stream[32];
+} __attribute__((packed));
+
+/* MSG_STREAM_DELETE_GROUP */
+
+/* request : mixart_uid_t group */
+
+struct mixart_delete_group_resp
+{
+	u32  status;
+	u32  unused[2];
+} __attribute__((packed));
+
+
+/* 	MSG_STREAM_START_INPUT_STAGE_PACKET  = 0x130000 + 7,
+	MSG_STREAM_START_OUTPUT_STAGE_PACKET = 0x130000 + 8,
+	MSG_STREAM_STOP_INPUT_STAGE_PACKET   = 0x130000 + 10,
+	MSG_STREAM_STOP_OUTPUT_STAGE_PACKET  = 0x130000 + 11,
+ */
+
+struct mixart_fx_couple_uid
+{
+	struct mixart_uid uid_fx_code;
+	struct mixart_uid uid_fx_data;
+} __attribute__((packed));
+
+struct mixart_txx_stream_desc
+{
+	struct mixart_uid       uid_pipe;
+	u32                     stream_idx;
+	u32                     fx_number;
+	struct mixart_fx_couple_uid  uid_fx[4];
+} __attribute__((packed));
+
+struct mixart_flow_info
+{
+	struct mixart_txx_stream_desc  stream_desc;
+	u32                       flow_entry;
+	u32                       flow_phy_addr;
+} __attribute__((packed));
+
+struct mixart_stream_state_req
+{
+	u32                 delayed;
+	u64                 scheduler;
+	u32                 reserved4np[3];
+	u32                 stream_count;  /* set to 1 for instance */
+	struct mixart_flow_info  stream_info;   /* could be an array[stream_count] */
+} __attribute__((packed));
+
+/* 	MSG_STREAM_START_STREAM_GRP_PACKET   = 0x130000 + 6
+	MSG_STREAM_STOP_STREAM_GRP_PACKET    = 0x130000 + 9
+ */
+
+struct mixart_group_state_req
+{
+	u32           delayed;
+	u64           scheduler;
+	u32           reserved4np[2];
+	u32           pipe_count;    /* set to 1 for instance */
+	struct mixart_uid  pipe_uid[1];   /* could be an array[pipe_count] */
+} __attribute__((packed));
+
+struct mixart_group_state_resp
+{
+	u32           txx_status;
+	u64           scheduler;
+} __attribute__((packed));
+
+
+
+/* Structures used by the MSG_SERVICES_TIMER_NOTIFY command */
+
+struct mixart_sample_pos
+{
+	u32   buffer_id;
+	u32   validity;
+	u32   sample_pos_high_part;
+	u32   sample_pos_low_part;
+} __attribute__((packed));
+
+struct mixart_timer_notify
+{
+	u32                  stream_count;
+	struct mixart_sample_pos  streams[MIXART_MAX_STREAM_PER_CARD * MIXART_MAX_CARDS];
+} __attribute__((packed));
+
+
+/*	MSG_CONSOLE_GET_CLOCK_UID            = 0x070003,
+ */
+
+/* request is a uid with desc = MSG_CONSOLE_MANAGER | cardindex */
+
+struct mixart_return_uid
+{
+	u32 error_code;
+	struct mixart_uid uid;
+} __attribute__((packed));
+
+/*	MSG_CLOCK_CHECK_PROPERTIES           = 0x200001,
+	MSG_CLOCK_SET_PROPERTIES             = 0x200002,
+*/
+
+enum mixart_clock_generic_type {
+	CGT_NO_CLOCK,
+	CGT_INTERNAL_CLOCK,
+	CGT_PROGRAMMABLE_CLOCK,
+	CGT_INTERNAL_ENSLAVED_CLOCK,
+	CGT_EXTERNAL_CLOCK,
+	CGT_CURRENT_CLOCK
+};
+
+enum mixart_clock_mode {
+	CM_UNDEFINED,
+	CM_MASTER,
+	CM_SLAVE,
+	CM_STANDALONE,
+	CM_NOT_CONCERNED
+};
+
+
+struct mixart_clock_properties
+{
+	u32 error_code;
+	u32 validation_mask;
+	u32 frequency;
+	u32 reference_frequency;
+	u32 clock_generic_type;
+	u32 clock_mode;
+	struct mixart_uid uid_clock_source;
+	struct mixart_uid uid_event_source;
+	u32 event_mode;
+	u32 synchro_signal_presence;
+	u32 format;
+	u32 board_mask;
+	u32 nb_callers; /* set to 1 (see below) */
+	struct mixart_uid uid_caller[1];
+} __attribute__((packed));
+
+struct mixart_clock_properties_resp
+{
+	u32 status;
+	u32 clock_mode;
+} __attribute__((packed));
+
+
+/*	MSG_STREAM_SET_INPUT_STAGE_PARAM     = 0x13000F */
+/*	MSG_STREAM_SET_OUTPUT_STAGE_PARAM    = 0x130010 */
+
+enum mixart_coding_type {
+	CT_NOT_DEFINED,
+	CT_LINEAR,
+	CT_MPEG_L1,
+	CT_MPEG_L2,
+	CT_MPEG_L3,
+	CT_MPEG_L3_LSF,
+	CT_GSM
+};
+enum mixart_sample_type {
+	ST_NOT_DEFINED,
+	ST_FLOATING_POINT_32BE,
+	ST_FLOATING_POINT_32LE,
+	ST_FLOATING_POINT_64BE,
+	ST_FLOATING_POINT_64LE,
+	ST_FIXED_POINT_8,
+	ST_FIXED_POINT_16BE,
+	ST_FIXED_POINT_16LE,
+	ST_FIXED_POINT_24BE,
+	ST_FIXED_POINT_24LE,
+	ST_FIXED_POINT_32BE,
+	ST_FIXED_POINT_32LE,
+	ST_INTEGER_8,
+	ST_INTEGER_16BE,
+	ST_INTEGER_16LE,
+	ST_INTEGER_24BE,
+	ST_INTEGER_24LE,
+	ST_INTEGER_32BE,
+	ST_INTEGER_32LE
+};
+
+struct mixart_stream_param_desc
+{
+	u32 coding_type;  /* use enum mixart_coding_type */
+	u32 sample_type;  /* use enum mixart_sample_type */
+
+	union {
+		struct {
+			u32 linear_endian_ness;
+			u32 linear_bits;
+			u32 is_signed;
+			u32 is_float;
+		} linear_format_info;
+
+		struct {
+			u32 mpeg_layer;
+			u32 mpeg_mode;
+			u32 mpeg_mode_extension;
+			u32 mpeg_pre_emphasis;
+			u32 mpeg_has_padding_bit;
+			u32 mpeg_has_crc;
+			u32 mpeg_has_extension;
+			u32 mpeg_is_original;
+			u32 mpeg_has_copyright;
+		} mpeg_format_info;
+	} format_info;
+
+	u32 delayed;
+	u64 scheduler;
+	u32 sample_size;
+	u32 has_header;
+	u32 has_suffix;
+	u32 has_bitrate;
+	u32 samples_per_frame;
+	u32 bytes_per_frame;
+	u32 bytes_per_sample;
+	u32 sampling_freq;
+	u32 number_of_channel;
+	u32 stream_number;
+	u32 buffer_size;
+	u32 differed_time;
+	u32 reserved4np[3];
+	u32 pipe_count;                           /* set to 1 (array size !) */
+	u32 stream_count;                         /* set to 1 (array size !) */
+	struct mixart_txx_stream_desc stream_desc[1];  /* only one stream per command, but this could be an array */
+
+} __attribute__((packed));
+
+
+/*	MSG_CONNECTOR_GET_OUT_AUDIO_LEVEL    = 0x050009,
+ */
+
+
+struct mixart_get_out_audio_level
+{
+	u32 txx_status;
+	u32 digital_level;   /* float */
+	u32 analog_level;    /* float */
+	u32 monitor_level;   /* float */
+	u32 mute;
+	u32 monitor_mute1;
+	u32 monitor_mute2;
+} __attribute__((packed));
+
+
+/*	MSG_CONNECTOR_SET_OUT_AUDIO_LEVEL    = 0x05000A,
+ */
+
+/* used for valid_mask below */
+#define MIXART_AUDIO_LEVEL_ANALOG_MASK	0x01
+#define MIXART_AUDIO_LEVEL_DIGITAL_MASK	0x02
+#define MIXART_AUDIO_LEVEL_MONITOR_MASK	0x04
+#define MIXART_AUDIO_LEVEL_MUTE_MASK	0x08
+#define MIXART_AUDIO_LEVEL_MUTE_M1_MASK	0x10
+#define MIXART_AUDIO_LEVEL_MUTE_M2_MASK	0x20
+
+struct mixart_set_out_audio_level
+{
+	u32 delayed;
+	u64 scheduler;
+	u32 valid_mask1;
+	u32 valid_mask2;
+	u32 digital_level;   /* float */
+	u32 analog_level;    /* float */
+	u32 monitor_level;   /* float */
+	u32 mute;
+	u32 monitor_mute1;
+	u32 monitor_mute2;
+	u32 reserved4np;
+} __attribute__((packed));
+
+
+/*	MSG_SYSTEM_ENUM_PHYSICAL_IO          = 0x16000E,
+ */
+
+#define MIXART_MAX_PHYS_IO  (MIXART_MAX_CARDS * 2 * 2) /* 4 * (analog+digital) * (playback+capture) */
+
+struct mixart_uid_enumeration
+{
+	u32 error_code;
+	u32 first_uid_offset;
+	u32 nb_uid;
+	u32 current_uid_index;
+	struct mixart_uid uid[MIXART_MAX_PHYS_IO];
+} __attribute__((packed));
+
+
+/*	MSG_PHYSICALIO_SET_LEVEL             = 0x0F0008,
+	MSG_PHYSICALIO_GET_LEVEL             = 0x0F000C,
+*/
+
+struct mixart_io_channel_level
+{
+	u32 analog_level;   /* float */
+	u32 unused[2];
+} __attribute__((packed));
+
+struct mixart_io_level
+{
+	s32 channel; /* 0=left, 1=right, -1=both, -2=both same */
+	struct mixart_io_channel_level level[2];
+} __attribute__((packed));
+
+
+/*	MSG_STREAM_SET_IN_AUDIO_LEVEL        = 0x130015,
+ */
+
+struct mixart_in_audio_level_info
+{
+	struct mixart_uid connector;
+	u32 valid_mask1;
+	u32 valid_mask2;
+	u32 digital_level;
+	u32 analog_level;
+} __attribute__((packed));
+
+struct mixart_set_in_audio_level_req
+{
+	u32 delayed;
+	u64 scheduler;
+	u32 audio_count;  /* set to <= 2 */
+	u32 reserved4np;
+	struct mixart_in_audio_level_info level[2];
+} __attribute__((packed));
+
+/* response is a 32 bit status */
+
+
+/*	MSG_STREAM_SET_OUT_STREAM_LEVEL      = 0x130017,
+ */
+
+/* defines used for valid_mask1 */
+#define MIXART_OUT_STREAM_SET_LEVEL_LEFT_AUDIO1		0x01
+#define MIXART_OUT_STREAM_SET_LEVEL_LEFT_AUDIO2		0x02
+#define MIXART_OUT_STREAM_SET_LEVEL_RIGHT_AUDIO1	0x04
+#define MIXART_OUT_STREAM_SET_LEVEL_RIGHT_AUDIO2	0x08
+#define MIXART_OUT_STREAM_SET_LEVEL_STREAM_1		0x10
+#define MIXART_OUT_STREAM_SET_LEVEL_STREAM_2		0x20
+#define MIXART_OUT_STREAM_SET_LEVEL_MUTE_1		0x40
+#define MIXART_OUT_STREAM_SET_LEVEL_MUTE_2		0x80
+
+struct mixart_out_stream_level_info
+{
+	u32 valid_mask1;
+	u32 valid_mask2;
+	u32 left_to_out1_level;
+	u32 left_to_out2_level;
+	u32 right_to_out1_level;
+	u32 right_to_out2_level;
+	u32 digital_level1;
+	u32 digital_level2;
+	u32 mute1;
+	u32 mute2;
+} __attribute__((packed));
+
+struct mixart_set_out_stream_level
+{
+	struct mixart_txx_stream_desc desc;
+	struct mixart_out_stream_level_info out_level;
+} __attribute__((packed));
+
+struct mixart_set_out_stream_level_req
+{
+	u32 delayed;
+	u64 scheduler;
+	u32 reserved4np[2];
+	u32 nb_of_stream;  /* set to 1 */
+	struct mixart_set_out_stream_level stream_level; /* could be an array */
+} __attribute__((packed));
+
+/* response to this request is a u32 status value */
+
+
+/* exported */
+void snd_mixart_init_mailbox(struct mixart_mgr *mgr);
+void snd_mixart_exit_mailbox(struct mixart_mgr *mgr);
+
+int  snd_mixart_send_msg(struct mixart_mgr *mgr, struct mixart_msg *request, int max_resp_size, void *resp_data);
+int  snd_mixart_send_msg_wait_notif(struct mixart_mgr *mgr, struct mixart_msg *request, u32 notif_event);
+int  snd_mixart_send_msg_nonblock(struct mixart_mgr *mgr, struct mixart_msg *request);
+
+irqreturn_t snd_mixart_interrupt(int irq, void *dev_id);
+void snd_mixart_msg_tasklet(unsigned long arg);
+
+void snd_mixart_reset_board(struct mixart_mgr *mgr);
+
+#endif /* __SOUND_MIXART_CORE_H */
diff --git a/linux/sound/pci/mixart/mixart_hwdep.c b/linux/sound/pci/mixart/mixart_hwdep.c
new file mode 100644
index 0000000..bf2696a
--- /dev/null
+++ b/linux/sound/pci/mixart/mixart_hwdep.c
@@ -0,0 +1,652 @@
+/*
+ * Driver for Digigram miXart soundcards
+ *
+ * DSP firmware management
+ *
+ * Copyright (c) 2003 by Digigram <alsa@digigram.com>
+ *
+ *   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/interrupt.h>
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <asm/io.h>
+#include <sound/core.h>
+#include "mixart.h"
+#include "mixart_mixer.h"
+#include "mixart_core.h"
+#include "mixart_hwdep.h"
+
+
+/**
+ * wait for a value on a peudo register, exit with a timeout
+ *
+ * @param mgr pointer to miXart manager structure
+ * @param offset unsigned pseudo_register base + offset of value
+ * @param value value
+ * @param timeout timeout in centisenconds
+ */
+static int mixart_wait_nice_for_register_value(struct mixart_mgr *mgr,
+					       u32 offset, int is_egal,
+					       u32 value, unsigned long timeout)
+{
+	unsigned long end_time = jiffies + (timeout * HZ / 100);
+	u32 read;
+
+	do {	/* we may take too long time in this loop.
+		 * so give controls back to kernel if needed.
+		 */
+		cond_resched();
+
+		read = readl_be( MIXART_MEM( mgr, offset ));
+		if(is_egal) {
+			if(read == value) return 0;
+		}
+		else { /* wait for different value */
+			if(read != value) return 0;
+		}
+	} while ( time_after_eq(end_time, jiffies) );
+
+	return -EBUSY;
+}
+
+
+/*
+  structures needed to upload elf code packets 
+ */
+struct snd_mixart_elf32_ehdr {
+	u8      e_ident[16];
+	u16     e_type;
+	u16     e_machine;
+	u32     e_version;
+	u32     e_entry;
+	u32     e_phoff;
+	u32     e_shoff;
+	u32     e_flags;
+	u16     e_ehsize;
+	u16     e_phentsize;
+	u16     e_phnum;
+	u16     e_shentsize;
+	u16     e_shnum;
+	u16     e_shstrndx;
+};
+
+struct snd_mixart_elf32_phdr {
+	u32     p_type;
+	u32     p_offset;
+	u32     p_vaddr;
+	u32     p_paddr;
+	u32     p_filesz;
+	u32     p_memsz;
+	u32     p_flags;
+	u32     p_align;
+};
+
+static int mixart_load_elf(struct mixart_mgr *mgr, const struct firmware *dsp )
+{
+	char                    elf32_magic_number[4] = {0x7f,'E','L','F'};
+	struct snd_mixart_elf32_ehdr *elf_header;
+	int                     i;
+
+	elf_header = (struct snd_mixart_elf32_ehdr *)dsp->data;
+	for( i=0; i<4; i++ )
+		if ( elf32_magic_number[i] != elf_header->e_ident[i] )
+			return -EINVAL;
+
+	if( elf_header->e_phoff != 0 ) {
+		struct snd_mixart_elf32_phdr     elf_programheader;
+
+		for( i=0; i < be16_to_cpu(elf_header->e_phnum); i++ ) {
+			u32 pos = be32_to_cpu(elf_header->e_phoff) + (u32)(i * be16_to_cpu(elf_header->e_phentsize));
+
+			memcpy( &elf_programheader, dsp->data + pos, sizeof(elf_programheader) );
+
+			if(elf_programheader.p_type != 0) {
+				if( elf_programheader.p_filesz != 0 ) {
+					memcpy_toio( MIXART_MEM( mgr, be32_to_cpu(elf_programheader.p_vaddr)),
+						     dsp->data + be32_to_cpu( elf_programheader.p_offset ),
+						     be32_to_cpu( elf_programheader.p_filesz ));
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+/*
+ * get basic information and init miXart
+ */
+
+/* audio IDs for request to the board */
+#define MIXART_FIRST_ANA_AUDIO_ID       0
+#define MIXART_FIRST_DIG_AUDIO_ID       8
+
+static int mixart_enum_connectors(struct mixart_mgr *mgr)
+{
+	u32 k;
+	int err;
+	struct mixart_msg request;
+	struct mixart_enum_connector_resp *connector;
+	struct mixart_audio_info_req  *audio_info_req;
+	struct mixart_audio_info_resp *audio_info;
+
+	connector = kmalloc(sizeof(*connector), GFP_KERNEL);
+	audio_info_req = kmalloc(sizeof(*audio_info_req), GFP_KERNEL);
+	audio_info = kmalloc(sizeof(*audio_info), GFP_KERNEL);
+	if (! connector || ! audio_info_req || ! audio_info) {
+		err = -ENOMEM;
+		goto __error;
+	}
+
+	audio_info_req->line_max_level = MIXART_FLOAT_P_22_0_TO_HEX;
+	audio_info_req->micro_max_level = MIXART_FLOAT_M_20_0_TO_HEX;
+	audio_info_req->cd_max_level = MIXART_FLOAT____0_0_TO_HEX;
+
+	request.message_id = MSG_SYSTEM_ENUM_PLAY_CONNECTOR;
+	request.uid = (struct mixart_uid){0,0};  /* board num = 0 */
+	request.data = NULL;
+	request.size = 0;
+
+	err = snd_mixart_send_msg(mgr, &request, sizeof(*connector), connector);
+	if((err < 0) || (connector->error_code) || (connector->uid_count > MIXART_MAX_PHYS_CONNECTORS)) {
+		snd_printk(KERN_ERR "error MSG_SYSTEM_ENUM_PLAY_CONNECTOR\n");
+		err = -EINVAL;
+		goto __error;
+	}
+
+	for(k=0; k < connector->uid_count; k++) {
+		struct mixart_pipe *pipe;
+
+		if(k < MIXART_FIRST_DIG_AUDIO_ID) {
+			pipe = &mgr->chip[k/2]->pipe_out_ana;
+		} else {
+			pipe = &mgr->chip[(k-MIXART_FIRST_DIG_AUDIO_ID)/2]->pipe_out_dig;
+		}
+		if(k & 1) {
+			pipe->uid_right_connector = connector->uid[k];   /* odd */
+		} else {
+			pipe->uid_left_connector = connector->uid[k];    /* even */
+		}
+
+		/* snd_printk(KERN_DEBUG "playback connector[%d].object_id = %x\n", k, connector->uid[k].object_id); */
+
+		/* TODO: really need send_msg MSG_CONNECTOR_GET_AUDIO_INFO for each connector ? perhaps for analog level caps ? */
+		request.message_id = MSG_CONNECTOR_GET_AUDIO_INFO;
+		request.uid = connector->uid[k];
+		request.data = audio_info_req;
+		request.size = sizeof(*audio_info_req);
+
+		err = snd_mixart_send_msg(mgr, &request, sizeof(*audio_info), audio_info);
+		if( err < 0 ) {
+			snd_printk(KERN_ERR "error MSG_CONNECTOR_GET_AUDIO_INFO\n");
+			goto __error;
+		}
+		/*snd_printk(KERN_DEBUG "play  analog_info.analog_level_present = %x\n", audio_info->info.analog_info.analog_level_present);*/
+	}
+
+	request.message_id = MSG_SYSTEM_ENUM_RECORD_CONNECTOR;
+	request.uid = (struct mixart_uid){0,0};  /* board num = 0 */
+	request.data = NULL;
+	request.size = 0;
+
+	err = snd_mixart_send_msg(mgr, &request, sizeof(*connector), connector);
+	if((err < 0) || (connector->error_code) || (connector->uid_count > MIXART_MAX_PHYS_CONNECTORS)) {
+		snd_printk(KERN_ERR "error MSG_SYSTEM_ENUM_RECORD_CONNECTOR\n");
+		err = -EINVAL;
+		goto __error;
+	}
+
+	for(k=0; k < connector->uid_count; k++) {
+		struct mixart_pipe *pipe;
+
+		if(k < MIXART_FIRST_DIG_AUDIO_ID) {
+			pipe = &mgr->chip[k/2]->pipe_in_ana;
+		} else {
+			pipe = &mgr->chip[(k-MIXART_FIRST_DIG_AUDIO_ID)/2]->pipe_in_dig;
+		}
+		if(k & 1) {
+			pipe->uid_right_connector = connector->uid[k];   /* odd */
+		} else {
+			pipe->uid_left_connector = connector->uid[k];    /* even */
+		}
+
+		/* snd_printk(KERN_DEBUG "capture connector[%d].object_id = %x\n", k, connector->uid[k].object_id); */
+
+		/* TODO: really need send_msg MSG_CONNECTOR_GET_AUDIO_INFO for each connector ? perhaps for analog level caps ? */
+		request.message_id = MSG_CONNECTOR_GET_AUDIO_INFO;
+		request.uid = connector->uid[k];
+		request.data = audio_info_req;
+		request.size = sizeof(*audio_info_req);
+
+		err = snd_mixart_send_msg(mgr, &request, sizeof(*audio_info), audio_info);
+		if( err < 0 ) {
+			snd_printk(KERN_ERR "error MSG_CONNECTOR_GET_AUDIO_INFO\n");
+			goto __error;
+		}
+		/*snd_printk(KERN_DEBUG "rec  analog_info.analog_level_present = %x\n", audio_info->info.analog_info.analog_level_present);*/
+	}
+	err = 0;
+
+ __error:
+	kfree(connector);
+	kfree(audio_info_req);
+	kfree(audio_info);
+
+	return err;
+}
+
+static int mixart_enum_physio(struct mixart_mgr *mgr)
+{
+	u32 k;
+	int err;
+	struct mixart_msg request;
+	struct mixart_uid get_console_mgr;
+	struct mixart_return_uid console_mgr;
+	struct mixart_uid_enumeration phys_io;
+
+	/* get the uid for the console manager */
+	get_console_mgr.object_id = 0;
+	get_console_mgr.desc = MSG_CONSOLE_MANAGER | 0; /* cardindex = 0 */
+
+	request.message_id = MSG_CONSOLE_GET_CLOCK_UID;
+	request.uid = get_console_mgr;
+	request.data = &get_console_mgr;
+	request.size = sizeof(get_console_mgr);
+
+	err = snd_mixart_send_msg(mgr, &request, sizeof(console_mgr), &console_mgr);
+
+	if( (err < 0) || (console_mgr.error_code != 0) ) {
+		snd_printk(KERN_DEBUG "error MSG_CONSOLE_GET_CLOCK_UID : err=%x\n", console_mgr.error_code);
+		return -EINVAL;
+	}
+
+	/* used later for clock issues ! */
+	mgr->uid_console_manager = console_mgr.uid;
+
+	request.message_id = MSG_SYSTEM_ENUM_PHYSICAL_IO;
+	request.uid = (struct mixart_uid){0,0};
+	request.data = &console_mgr.uid;
+	request.size = sizeof(console_mgr.uid);
+
+	err = snd_mixart_send_msg(mgr, &request, sizeof(phys_io), &phys_io);
+	if( (err < 0) || ( phys_io.error_code != 0 ) ) {
+		snd_printk(KERN_ERR "error MSG_SYSTEM_ENUM_PHYSICAL_IO err(%x) error_code(%x)\n", err, phys_io.error_code );
+		return -EINVAL;
+	}
+
+	/* min 2 phys io per card (analog in + analog out) */
+	if (phys_io.nb_uid < MIXART_MAX_CARDS * 2)
+		return -EINVAL;
+
+	for(k=0; k<mgr->num_cards; k++) {
+		mgr->chip[k]->uid_in_analog_physio = phys_io.uid[k];
+		mgr->chip[k]->uid_out_analog_physio = phys_io.uid[phys_io.nb_uid/2 + k]; 
+	}
+
+	return 0;
+}
+
+
+static int mixart_first_init(struct mixart_mgr *mgr)
+{
+	u32 k;
+	int err;
+	struct mixart_msg request;
+
+	if((err = mixart_enum_connectors(mgr)) < 0) return err;
+
+	if((err = mixart_enum_physio(mgr)) < 0) return err;
+
+	/* send a synchro command to card (necessary to do this before first MSG_STREAM_START_STREAM_GRP_PACKET) */
+	/* though why not here */
+	request.message_id = MSG_SYSTEM_SEND_SYNCHRO_CMD;
+	request.uid = (struct mixart_uid){0,0};
+	request.data = NULL;
+	request.size = 0;
+	/* this command has no data. response is a 32 bit status */
+	err = snd_mixart_send_msg(mgr, &request, sizeof(k), &k);
+	if( (err < 0) || (k != 0) ) {
+		snd_printk(KERN_ERR "error MSG_SYSTEM_SEND_SYNCHRO_CMD\n");
+		return err == 0 ? -EINVAL : err;
+	}
+
+	return 0;
+}
+
+
+/* firmware base addresses (when hard coded) */
+#define MIXART_MOTHERBOARD_XLX_BASE_ADDRESS   0x00600000
+
+static int mixart_dsp_load(struct mixart_mgr* mgr, int index, const struct firmware *dsp)
+{
+	int           err, card_index;
+	u32           status_xilinx, status_elf, status_daught;
+	u32           val;
+
+	/* read motherboard xilinx status */
+	status_xilinx = readl_be( MIXART_MEM( mgr,MIXART_PSEUDOREG_MXLX_STATUS_OFFSET ));
+	/* read elf status */
+	status_elf = readl_be( MIXART_MEM( mgr,MIXART_PSEUDOREG_ELF_STATUS_OFFSET ));
+	/* read daughterboard xilinx status */
+	status_daught = readl_be( MIXART_MEM( mgr,MIXART_PSEUDOREG_DXLX_STATUS_OFFSET ));
+
+	/* motherboard xilinx status 5 will say that the board is performing a reset */
+	if (status_xilinx == 5) {
+		snd_printk(KERN_ERR "miXart is resetting !\n");
+		return -EAGAIN; /* try again later */
+	}
+
+	switch (index)   {
+	case MIXART_MOTHERBOARD_XLX_INDEX:
+
+		/* xilinx already loaded ? */ 
+		if (status_xilinx == 4) {
+			snd_printk(KERN_DEBUG "xilinx is already loaded !\n");
+			return 0;
+		}
+		/* the status should be 0 == "idle" */
+		if (status_xilinx != 0) {
+			snd_printk(KERN_ERR "xilinx load error ! status = %d\n",
+				   status_xilinx);
+			return -EIO; /* modprob -r may help ? */
+		}
+
+		/* check xilinx validity */
+		if (((u32*)(dsp->data))[0] == 0xffffffff)
+			return -EINVAL;
+		if (dsp->size % 4)
+			return -EINVAL;
+
+		/* set xilinx status to copying */
+		writel_be( 1, MIXART_MEM( mgr, MIXART_PSEUDOREG_MXLX_STATUS_OFFSET ));
+
+		/* setup xilinx base address */
+		writel_be( MIXART_MOTHERBOARD_XLX_BASE_ADDRESS, MIXART_MEM( mgr,MIXART_PSEUDOREG_MXLX_BASE_ADDR_OFFSET ));
+		/* setup code size for xilinx file */
+		writel_be( dsp->size, MIXART_MEM( mgr, MIXART_PSEUDOREG_MXLX_SIZE_OFFSET ));
+
+		/* copy xilinx code */
+		memcpy_toio(  MIXART_MEM( mgr, MIXART_MOTHERBOARD_XLX_BASE_ADDRESS),  dsp->data,  dsp->size);
+    
+		/* set xilinx status to copy finished */
+		writel_be( 2, MIXART_MEM( mgr, MIXART_PSEUDOREG_MXLX_STATUS_OFFSET ));
+
+		/* return, because no further processing needed */
+		return 0;
+
+	case MIXART_MOTHERBOARD_ELF_INDEX:
+
+		if (status_elf == 4) {
+			snd_printk(KERN_DEBUG "elf file already loaded !\n");
+			return 0;
+		}
+
+		/* the status should be 0 == "idle" */
+		if (status_elf != 0) {
+			snd_printk(KERN_ERR "elf load error ! status = %d\n",
+				   status_elf);
+			return -EIO; /* modprob -r may help ? */
+		}
+
+		/* wait for xilinx status == 4 */
+		err = mixart_wait_nice_for_register_value( mgr, MIXART_PSEUDOREG_MXLX_STATUS_OFFSET, 1, 4, 500); /* 5sec */
+		if (err < 0) {
+			snd_printk(KERN_ERR "xilinx was not loaded or "
+				   "could not be started\n");
+			return err;
+		}
+
+		/* init some data on the card */
+		writel_be( 0, MIXART_MEM( mgr, MIXART_PSEUDOREG_BOARDNUMBER ) ); /* set miXart boardnumber to 0 */
+		writel_be( 0, MIXART_MEM( mgr, MIXART_FLOWTABLE_PTR ) );         /* reset pointer to flow table on miXart */
+
+		/* set elf status to copying */
+		writel_be( 1, MIXART_MEM( mgr, MIXART_PSEUDOREG_ELF_STATUS_OFFSET ));
+
+		/* process the copying of the elf packets */
+		err = mixart_load_elf( mgr, dsp );
+		if (err < 0) return err;
+
+		/* set elf status to copy finished */
+		writel_be( 2, MIXART_MEM( mgr, MIXART_PSEUDOREG_ELF_STATUS_OFFSET ));
+
+		/* wait for elf status == 4 */
+		err = mixart_wait_nice_for_register_value( mgr, MIXART_PSEUDOREG_ELF_STATUS_OFFSET, 1, 4, 300); /* 3sec */
+		if (err < 0) {
+			snd_printk(KERN_ERR "elf could not be started\n");
+			return err;
+		}
+
+		/* miXart waits at this point on the pointer to the flow table */
+		writel_be( (u32)mgr->flowinfo.addr, MIXART_MEM( mgr, MIXART_FLOWTABLE_PTR ) ); /* give pointer of flow table to miXart */
+
+		return 0;  /* return, another xilinx file has to be loaded before */
+
+	case MIXART_AESEBUBOARD_XLX_INDEX:
+	default:
+
+		/* elf and xilinx should be loaded */
+		if (status_elf != 4 || status_xilinx != 4) {
+			printk(KERN_ERR "xilinx or elf not "
+			       "successfully loaded\n");
+			return -EIO; /* modprob -r may help ? */
+		}
+
+		/* wait for daughter detection != 0 */
+		err = mixart_wait_nice_for_register_value( mgr, MIXART_PSEUDOREG_DBRD_PRESENCE_OFFSET, 0, 0, 30); /* 300msec */
+		if (err < 0) {
+			snd_printk(KERN_ERR "error starting elf file\n");
+			return err;
+		}
+
+		/* the board type can now be retrieved */
+		mgr->board_type = (DAUGHTER_TYPE_MASK & readl_be( MIXART_MEM( mgr, MIXART_PSEUDOREG_DBRD_TYPE_OFFSET)));
+
+		if (mgr->board_type == MIXART_DAUGHTER_TYPE_NONE)
+			break;  /* no daughter board; the file does not have to be loaded, continue after the switch */
+
+		/* only if aesebu daughter board presence (elf code must run)  */ 
+		if (mgr->board_type != MIXART_DAUGHTER_TYPE_AES )
+			return -EINVAL;
+
+		/* daughter should be idle */
+		if (status_daught != 0) {
+			printk(KERN_ERR "daughter load error ! status = %d\n",
+			       status_daught);
+			return -EIO; /* modprob -r may help ? */
+		}
+ 
+		/* check daughterboard xilinx validity */
+		if (((u32*)(dsp->data))[0] == 0xffffffff)
+			return -EINVAL;
+		if (dsp->size % 4)
+			return -EINVAL;
+
+		/* inform mixart about the size of the file */
+		writel_be( dsp->size, MIXART_MEM( mgr, MIXART_PSEUDOREG_DXLX_SIZE_OFFSET ));
+
+		/* set daughterboard status to 1 */
+		writel_be( 1, MIXART_MEM( mgr, MIXART_PSEUDOREG_DXLX_STATUS_OFFSET ));
+
+		/* wait for status == 2 */
+		err = mixart_wait_nice_for_register_value( mgr, MIXART_PSEUDOREG_DXLX_STATUS_OFFSET, 1, 2, 30); /* 300msec */
+		if (err < 0) {
+			snd_printk(KERN_ERR "daughter board load error\n");
+			return err;
+		}
+
+		/* get the address where to write the file */
+		val = readl_be( MIXART_MEM( mgr, MIXART_PSEUDOREG_DXLX_BASE_ADDR_OFFSET ));
+		if (!val)
+			return -EINVAL;
+
+		/* copy daughterboard xilinx code */
+		memcpy_toio(  MIXART_MEM( mgr, val),  dsp->data,  dsp->size);
+
+		/* set daughterboard status to 4 */
+		writel_be( 4, MIXART_MEM( mgr, MIXART_PSEUDOREG_DXLX_STATUS_OFFSET ));
+
+		/* continue with init */
+		break;
+	} /* end of switch file index*/
+
+        /* wait for daughter status == 3 */
+        err = mixart_wait_nice_for_register_value( mgr, MIXART_PSEUDOREG_DXLX_STATUS_OFFSET, 1, 3, 300); /* 3sec */
+        if (err < 0) {
+		snd_printk(KERN_ERR
+			   "daughter board could not be initialised\n");
+		return err;
+	}
+
+	/* init mailbox (communication with embedded) */
+	snd_mixart_init_mailbox(mgr);
+
+	/* first communication with embedded */
+	err = mixart_first_init(mgr);
+        if (err < 0) {
+		snd_printk(KERN_ERR "miXart could not be set up\n");
+		return err;
+	}
+
+       	/* create devices and mixer in accordance with HW options*/
+        for (card_index = 0; card_index < mgr->num_cards; card_index++) {
+		struct snd_mixart *chip = mgr->chip[card_index];
+
+		if ((err = snd_mixart_create_pcm(chip)) < 0)
+			return err;
+
+		if (card_index == 0) {
+			if ((err = snd_mixart_create_mixer(chip->mgr)) < 0)
+	        		return err;
+		}
+
+		if ((err = snd_card_register(chip->card)) < 0)
+			return err;
+	};
+
+	snd_printdd("miXart firmware downloaded and successfully set up\n");
+
+	return 0;
+}
+
+
+#if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE)
+#if !defined(CONFIG_USE_MIXARTLOADER) && !defined(CONFIG_SND_MIXART) /* built-in kernel */
+#define SND_MIXART_FW_LOADER	/* use the standard firmware loader */
+#endif
+#endif
+
+#ifdef SND_MIXART_FW_LOADER
+
+int snd_mixart_setup_firmware(struct mixart_mgr *mgr)
+{
+	static char *fw_files[3] = {
+		"miXart8.xlx", "miXart8.elf", "miXart8AES.xlx"
+	};
+	char path[32];
+
+	const struct firmware *fw_entry;
+	int i, err;
+
+	for (i = 0; i < 3; i++) {
+		sprintf(path, "mixart/%s", fw_files[i]);
+		if (request_firmware(&fw_entry, path, &mgr->pci->dev)) {
+			snd_printk(KERN_ERR "miXart: can't load firmware %s\n", path);
+			return -ENOENT;
+		}
+		/* fake hwdep dsp record */
+		err = mixart_dsp_load(mgr, i, fw_entry);
+		release_firmware(fw_entry);
+		if (err < 0)
+			return err;
+		mgr->dsp_loaded |= 1 << i;
+	}
+	return 0;
+}
+
+MODULE_FIRMWARE("mixart/miXart8.xlx");
+MODULE_FIRMWARE("mixart/miXart8.elf");
+MODULE_FIRMWARE("mixart/miXart8AES.xlx");
+
+#else /* old style firmware loading */
+
+/* miXart hwdep interface id string */
+#define SND_MIXART_HWDEP_ID       "miXart Loader"
+
+static int mixart_hwdep_dsp_status(struct snd_hwdep *hw,
+				   struct snd_hwdep_dsp_status *info)
+{
+	struct mixart_mgr *mgr = hw->private_data;
+
+	strcpy(info->id, "miXart");
+        info->num_dsps = MIXART_HARDW_FILES_MAX_INDEX;
+
+	if (mgr->dsp_loaded & (1 <<  MIXART_MOTHERBOARD_ELF_INDEX))
+		info->chip_ready = 1;
+
+	info->version = MIXART_DRIVER_VERSION;
+	return 0;
+}
+
+static int mixart_hwdep_dsp_load(struct snd_hwdep *hw,
+				 struct snd_hwdep_dsp_image *dsp)
+{
+	struct mixart_mgr* mgr = hw->private_data;
+	struct firmware fw;
+	int err;
+
+	fw.size = dsp->length;
+	fw.data = vmalloc(dsp->length);
+	if (! fw.data) {
+		snd_printk(KERN_ERR "miXart: cannot allocate image size %d\n",
+			   (int)dsp->length);
+		return -ENOMEM;
+	}
+	if (copy_from_user((void *) fw.data, dsp->image, dsp->length)) {
+		vfree(fw.data);
+		return -EFAULT;
+	}
+	err = mixart_dsp_load(mgr, dsp->index, &fw);
+	vfree(fw.data);
+	if (err < 0)
+		return err;
+	mgr->dsp_loaded |= 1 << dsp->index;
+	return err;
+}
+
+int snd_mixart_setup_firmware(struct mixart_mgr *mgr)
+{
+	int err;
+	struct snd_hwdep *hw;
+
+	/* only create hwdep interface for first cardX (see "index" module parameter)*/
+	if ((err = snd_hwdep_new(mgr->chip[0]->card, SND_MIXART_HWDEP_ID, 0, &hw)) < 0)
+		return err;
+
+	hw->iface = SNDRV_HWDEP_IFACE_MIXART;
+	hw->private_data = mgr;
+	hw->ops.dsp_status = mixart_hwdep_dsp_status;
+	hw->ops.dsp_load = mixart_hwdep_dsp_load;
+	hw->exclusive = 1;
+	sprintf(hw->name,  SND_MIXART_HWDEP_ID);
+	mgr->dsp_loaded = 0;
+
+	return snd_card_register(mgr->chip[0]->card);
+}
+
+#endif /* SND_MIXART_FW_LOADER */
diff --git a/linux/sound/pci/mixart/mixart_hwdep.h b/linux/sound/pci/mixart/mixart_hwdep.h
new file mode 100644
index 0000000..812e288
--- /dev/null
+++ b/linux/sound/pci/mixart/mixart_hwdep.h
@@ -0,0 +1,155 @@
+/*
+ * Driver for Digigram miXart soundcards
+ *
+ * definitions and makros for basic card access
+ *
+ * Copyright (c) 2003 by Digigram <alsa@digigram.com>
+ *
+ *   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 __SOUND_MIXART_HWDEP_H
+#define __SOUND_MIXART_HWDEP_H
+
+#include <sound/hwdep.h>
+
+#ifndef readl_be
+#define readl_be(x) be32_to_cpu(__raw_readl(x))
+#endif
+
+#ifndef writel_be
+#define writel_be(data,addr) __raw_writel(cpu_to_be32(data),addr)
+#endif
+
+#ifndef readl_le
+#define readl_le(x) le32_to_cpu(__raw_readl(x))
+#endif
+
+#ifndef writel_le
+#define writel_le(data,addr) __raw_writel(cpu_to_le32(data),addr)
+#endif
+
+#define MIXART_MEM(mgr,x)	((mgr)->mem[0].virt + (x))
+#define MIXART_REG(mgr,x)	((mgr)->mem[1].virt + (x))
+
+
+/* Daughter board Type */
+#define DAUGHTER_TYPE_MASK     0x0F 
+#define DAUGHTER_VER_MASK      0xF0 
+#define DAUGHTER_TYPEVER_MASK  (DAUGHTER_TYPE_MASK|DAUGHTER_VER_MASK)
+ 
+#define MIXART_DAUGHTER_TYPE_NONE     0x00 
+#define MIXART_DAUGHTER_TYPE_COBRANET 0x08 
+#define MIXART_DAUGHTER_TYPE_AES      0x0E
+
+
+
+#define MIXART_BA0_SIZE 	(16 * 1024 * 1024) /* 16M */
+#define MIXART_BA1_SIZE 	(4  * 1024)        /* 4k */
+
+/*
+ * -----------BAR 0 --------------------------------------------------------------------------------------------------------
+ */
+#define  MIXART_PSEUDOREG                          0x2000                    /* base address for pseudoregister */
+
+#define  MIXART_PSEUDOREG_BOARDNUMBER              MIXART_PSEUDOREG+0        /* board number */
+
+/* perfmeter (available when elf loaded)*/
+#define  MIXART_PSEUDOREG_PERF_STREAM_LOAD_OFFSET  MIXART_PSEUDOREG+0x70     /* streaming load */
+#define  MIXART_PSEUDOREG_PERF_SYSTEM_LOAD_OFFSET  MIXART_PSEUDOREG+0x78     /* system load (reference)*/
+#define  MIXART_PSEUDOREG_PERF_MAILBX_LOAD_OFFSET  MIXART_PSEUDOREG+0x7C     /* mailbox load */
+#define  MIXART_PSEUDOREG_PERF_INTERR_LOAD_OFFSET  MIXART_PSEUDOREG+0x74     /* interrupt handling  load */
+
+/* motherboard xilinx loader info */
+#define  MIXART_PSEUDOREG_MXLX_BASE_ADDR_OFFSET    MIXART_PSEUDOREG+0x9C     /* 0x00600000 */ 
+#define  MIXART_PSEUDOREG_MXLX_SIZE_OFFSET         MIXART_PSEUDOREG+0xA0     /* xilinx size in bytes */ 
+#define  MIXART_PSEUDOREG_MXLX_STATUS_OFFSET       MIXART_PSEUDOREG+0xA4     /* status = EMBEBBED_STAT_XXX */ 
+
+/* elf loader info */
+#define  MIXART_PSEUDOREG_ELF_STATUS_OFFSET        MIXART_PSEUDOREG+0xB0     /* status = EMBEBBED_STAT_XXX */ 
+
+/* 
+*  after the elf code is loaded, and the flowtable info was passed to it,
+*  the driver polls on this address, until it shows 1 (presence) or 2 (absence)
+*  once it is non-zero, the daughter board type may be read
+*/
+#define  MIXART_PSEUDOREG_DBRD_PRESENCE_OFFSET     MIXART_PSEUDOREG+0x990   
+
+/* Global info structure */
+#define  MIXART_PSEUDOREG_DBRD_TYPE_OFFSET         MIXART_PSEUDOREG+0x994    /* Type and version of daughterboard  */
+
+
+/* daughterboard xilinx loader info */
+#define  MIXART_PSEUDOREG_DXLX_BASE_ADDR_OFFSET    MIXART_PSEUDOREG+0x998    /* get the address here where to write the file */ 
+#define  MIXART_PSEUDOREG_DXLX_SIZE_OFFSET         MIXART_PSEUDOREG+0x99C    /* xilinx size in bytes */ 
+#define  MIXART_PSEUDOREG_DXLX_STATUS_OFFSET       MIXART_PSEUDOREG+0x9A0    /* status = EMBEBBED_STAT_XXX */ 
+
+/*  */
+#define  MIXART_FLOWTABLE_PTR                      0x3000                    /* pointer to flow table */
+
+/* mailbox addresses  */
+
+/* message DRV -> EMB */
+#define MSG_INBOUND_POST_HEAD       0x010008	/* DRV posts MF + increment4 */
+#define	MSG_INBOUND_POST_TAIL       0x01000C	/* EMB gets MF + increment4 */
+/* message EMB -> DRV */
+#define	MSG_OUTBOUND_POST_TAIL      0x01001C	/* DRV gets MF + increment4 */
+#define	MSG_OUTBOUND_POST_HEAD      0x010018	/* EMB posts MF + increment4 */
+/* Get Free Frames */
+#define MSG_INBOUND_FREE_TAIL       0x010004	/* DRV gets MFA + increment4 */
+#define MSG_OUTBOUND_FREE_TAIL      0x010014	/* EMB gets MFA + increment4 */
+/* Put Free Frames */
+#define MSG_OUTBOUND_FREE_HEAD      0x010010	/* DRV puts MFA + increment4 */
+#define MSG_INBOUND_FREE_HEAD       0x010000    /* EMB puts MFA + increment4 */
+
+/* firmware addresses of the message fifos */
+#define MSG_BOUND_STACK_SIZE        0x004000    /* size of each following stack */
+/* posted messages */
+#define MSG_OUTBOUND_POST_STACK     0x108000    /* stack of messages to the DRV */
+#define MSG_INBOUND_POST_STACK      0x104000    /* stack of messages to the EMB */
+/* available empty messages */
+#define MSG_OUTBOUND_FREE_STACK     0x10C000    /* stack of free enveloped for EMB */
+#define MSG_INBOUND_FREE_STACK      0x100000    /* stack of free enveloped for DRV */
+
+
+/* defines for mailbox message frames */
+#define MSG_FRAME_OFFSET            0x64
+#define MSG_FRAME_SIZE              0x6400
+#define MSG_FRAME_NUMBER            32
+#define MSG_FROM_AGENT_ITMF_OFFSET  (MSG_FRAME_OFFSET + (MSG_FRAME_SIZE * MSG_FRAME_NUMBER))
+#define MSG_TO_AGENT_ITMF_OFFSET    (MSG_FROM_AGENT_ITMF_OFFSET + MSG_FRAME_SIZE)
+#define MSG_HOST_RSC_PROTECTION     (MSG_TO_AGENT_ITMF_OFFSET + MSG_FRAME_SIZE)
+#define MSG_AGENT_RSC_PROTECTION    (MSG_HOST_RSC_PROTECTION + 4)
+
+
+/*
+ * -----------BAR 1 --------------------------------------------------------------------------------------------------------
+ */
+
+/* interrupt addresses and constants */
+#define MIXART_PCI_OMIMR_OFFSET                 0x34    /* outbound message interrupt mask register */
+#define MIXART_PCI_OMISR_OFFSET                 0x30    /* outbound message interrupt status register */
+#define MIXART_PCI_ODBR_OFFSET                  0x60    /* outbound doorbell register */
+
+#define MIXART_BA1_BRUTAL_RESET_OFFSET          0x68    /* write 1 in LSBit to reset board */
+
+#define MIXART_HOST_ALL_INTERRUPT_MASKED        0x02B   /* 0000 0010 1011 */
+#define MIXART_ALLOW_OUTBOUND_DOORBELL          0x023   /* 0000 0010 0011 */
+#define MIXART_OIDI                             0x008   /* 0000 0000 1000 */
+
+
+int snd_mixart_setup_firmware(struct mixart_mgr *mgr);
+
+#endif /* __SOUND_MIXART_HWDEP_H */
diff --git a/linux/sound/pci/mixart/mixart_mixer.c b/linux/sound/pci/mixart/mixart_mixer.c
new file mode 100644
index 0000000..3ba6174
--- /dev/null
+++ b/linux/sound/pci/mixart/mixart_mixer.c
@@ -0,0 +1,1186 @@
+/*
+ * Driver for Digigram miXart soundcards
+ *
+ * mixer callbacks
+ *
+ * Copyright (c) 2003 by Digigram <alsa@digigram.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include "mixart.h"
+#include "mixart_core.h"
+#include "mixart_hwdep.h"
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include "mixart_mixer.h"
+
+static u32 mixart_analog_level[256] = {
+	0xc2c00000,		/* [000] -96.0 dB */
+	0xc2bf0000,		/* [001] -95.5 dB */
+	0xc2be0000,		/* [002] -95.0 dB */
+	0xc2bd0000,		/* [003] -94.5 dB */
+	0xc2bc0000,		/* [004] -94.0 dB */
+	0xc2bb0000,		/* [005] -93.5 dB */
+	0xc2ba0000,		/* [006] -93.0 dB */
+	0xc2b90000,		/* [007] -92.5 dB */
+	0xc2b80000,		/* [008] -92.0 dB */
+	0xc2b70000,		/* [009] -91.5 dB */
+	0xc2b60000,		/* [010] -91.0 dB */
+	0xc2b50000,		/* [011] -90.5 dB */
+	0xc2b40000,		/* [012] -90.0 dB */
+	0xc2b30000,		/* [013] -89.5 dB */
+	0xc2b20000,		/* [014] -89.0 dB */
+	0xc2b10000,		/* [015] -88.5 dB */
+	0xc2b00000,		/* [016] -88.0 dB */
+	0xc2af0000,		/* [017] -87.5 dB */
+	0xc2ae0000,		/* [018] -87.0 dB */
+	0xc2ad0000,		/* [019] -86.5 dB */
+	0xc2ac0000,		/* [020] -86.0 dB */
+	0xc2ab0000,		/* [021] -85.5 dB */
+	0xc2aa0000,		/* [022] -85.0 dB */
+	0xc2a90000,		/* [023] -84.5 dB */
+	0xc2a80000,		/* [024] -84.0 dB */
+	0xc2a70000,		/* [025] -83.5 dB */
+	0xc2a60000,		/* [026] -83.0 dB */
+	0xc2a50000,		/* [027] -82.5 dB */
+	0xc2a40000,		/* [028] -82.0 dB */
+	0xc2a30000,		/* [029] -81.5 dB */
+	0xc2a20000,		/* [030] -81.0 dB */
+	0xc2a10000,		/* [031] -80.5 dB */
+	0xc2a00000,		/* [032] -80.0 dB */
+	0xc29f0000,		/* [033] -79.5 dB */
+	0xc29e0000,		/* [034] -79.0 dB */
+	0xc29d0000,		/* [035] -78.5 dB */
+	0xc29c0000,		/* [036] -78.0 dB */
+	0xc29b0000,		/* [037] -77.5 dB */
+	0xc29a0000,		/* [038] -77.0 dB */
+	0xc2990000,		/* [039] -76.5 dB */
+	0xc2980000,		/* [040] -76.0 dB */
+	0xc2970000,		/* [041] -75.5 dB */
+	0xc2960000,		/* [042] -75.0 dB */
+	0xc2950000,		/* [043] -74.5 dB */
+	0xc2940000,		/* [044] -74.0 dB */
+	0xc2930000,		/* [045] -73.5 dB */
+	0xc2920000,		/* [046] -73.0 dB */
+	0xc2910000,		/* [047] -72.5 dB */
+	0xc2900000,		/* [048] -72.0 dB */
+	0xc28f0000,		/* [049] -71.5 dB */
+	0xc28e0000,		/* [050] -71.0 dB */
+	0xc28d0000,		/* [051] -70.5 dB */
+	0xc28c0000,		/* [052] -70.0 dB */
+	0xc28b0000,		/* [053] -69.5 dB */
+	0xc28a0000,		/* [054] -69.0 dB */
+	0xc2890000,		/* [055] -68.5 dB */
+	0xc2880000,		/* [056] -68.0 dB */
+	0xc2870000,		/* [057] -67.5 dB */
+	0xc2860000,		/* [058] -67.0 dB */
+	0xc2850000,		/* [059] -66.5 dB */
+	0xc2840000,		/* [060] -66.0 dB */
+	0xc2830000,		/* [061] -65.5 dB */
+	0xc2820000,		/* [062] -65.0 dB */
+	0xc2810000,		/* [063] -64.5 dB */
+	0xc2800000,		/* [064] -64.0 dB */
+	0xc27e0000,		/* [065] -63.5 dB */
+	0xc27c0000,		/* [066] -63.0 dB */
+	0xc27a0000,		/* [067] -62.5 dB */
+	0xc2780000,		/* [068] -62.0 dB */
+	0xc2760000,		/* [069] -61.5 dB */
+	0xc2740000,		/* [070] -61.0 dB */
+	0xc2720000,		/* [071] -60.5 dB */
+	0xc2700000,		/* [072] -60.0 dB */
+	0xc26e0000,		/* [073] -59.5 dB */
+	0xc26c0000,		/* [074] -59.0 dB */
+	0xc26a0000,		/* [075] -58.5 dB */
+	0xc2680000,		/* [076] -58.0 dB */
+	0xc2660000,		/* [077] -57.5 dB */
+	0xc2640000,		/* [078] -57.0 dB */
+	0xc2620000,		/* [079] -56.5 dB */
+	0xc2600000,		/* [080] -56.0 dB */
+	0xc25e0000,		/* [081] -55.5 dB */
+	0xc25c0000,		/* [082] -55.0 dB */
+	0xc25a0000,		/* [083] -54.5 dB */
+	0xc2580000,		/* [084] -54.0 dB */
+	0xc2560000,		/* [085] -53.5 dB */
+	0xc2540000,		/* [086] -53.0 dB */
+	0xc2520000,		/* [087] -52.5 dB */
+	0xc2500000,		/* [088] -52.0 dB */
+	0xc24e0000,		/* [089] -51.5 dB */
+	0xc24c0000,		/* [090] -51.0 dB */
+	0xc24a0000,		/* [091] -50.5 dB */
+	0xc2480000,		/* [092] -50.0 dB */
+	0xc2460000,		/* [093] -49.5 dB */
+	0xc2440000,		/* [094] -49.0 dB */
+	0xc2420000,		/* [095] -48.5 dB */
+	0xc2400000,		/* [096] -48.0 dB */
+	0xc23e0000,		/* [097] -47.5 dB */
+	0xc23c0000,		/* [098] -47.0 dB */
+	0xc23a0000,		/* [099] -46.5 dB */
+	0xc2380000,		/* [100] -46.0 dB */
+	0xc2360000,		/* [101] -45.5 dB */
+	0xc2340000,		/* [102] -45.0 dB */
+	0xc2320000,		/* [103] -44.5 dB */
+	0xc2300000,		/* [104] -44.0 dB */
+	0xc22e0000,		/* [105] -43.5 dB */
+	0xc22c0000,		/* [106] -43.0 dB */
+	0xc22a0000,		/* [107] -42.5 dB */
+	0xc2280000,		/* [108] -42.0 dB */
+	0xc2260000,		/* [109] -41.5 dB */
+	0xc2240000,		/* [110] -41.0 dB */
+	0xc2220000,		/* [111] -40.5 dB */
+	0xc2200000,		/* [112] -40.0 dB */
+	0xc21e0000,		/* [113] -39.5 dB */
+	0xc21c0000,		/* [114] -39.0 dB */
+	0xc21a0000,		/* [115] -38.5 dB */
+	0xc2180000,		/* [116] -38.0 dB */
+	0xc2160000,		/* [117] -37.5 dB */
+	0xc2140000,		/* [118] -37.0 dB */
+	0xc2120000,		/* [119] -36.5 dB */
+	0xc2100000,		/* [120] -36.0 dB */
+	0xc20e0000,		/* [121] -35.5 dB */
+	0xc20c0000,		/* [122] -35.0 dB */
+	0xc20a0000,		/* [123] -34.5 dB */
+	0xc2080000,		/* [124] -34.0 dB */
+	0xc2060000,		/* [125] -33.5 dB */
+	0xc2040000,		/* [126] -33.0 dB */
+	0xc2020000,		/* [127] -32.5 dB */
+	0xc2000000,		/* [128] -32.0 dB */
+	0xc1fc0000,		/* [129] -31.5 dB */
+	0xc1f80000,		/* [130] -31.0 dB */
+	0xc1f40000,		/* [131] -30.5 dB */
+	0xc1f00000,		/* [132] -30.0 dB */
+	0xc1ec0000,		/* [133] -29.5 dB */
+	0xc1e80000,		/* [134] -29.0 dB */
+	0xc1e40000,		/* [135] -28.5 dB */
+	0xc1e00000,		/* [136] -28.0 dB */
+	0xc1dc0000,		/* [137] -27.5 dB */
+	0xc1d80000,		/* [138] -27.0 dB */
+	0xc1d40000,		/* [139] -26.5 dB */
+	0xc1d00000,		/* [140] -26.0 dB */
+	0xc1cc0000,		/* [141] -25.5 dB */
+	0xc1c80000,		/* [142] -25.0 dB */
+	0xc1c40000,		/* [143] -24.5 dB */
+	0xc1c00000,		/* [144] -24.0 dB */
+	0xc1bc0000,		/* [145] -23.5 dB */
+	0xc1b80000,		/* [146] -23.0 dB */
+	0xc1b40000,		/* [147] -22.5 dB */
+	0xc1b00000,		/* [148] -22.0 dB */
+	0xc1ac0000,		/* [149] -21.5 dB */
+	0xc1a80000,		/* [150] -21.0 dB */
+	0xc1a40000,		/* [151] -20.5 dB */
+	0xc1a00000,		/* [152] -20.0 dB */
+	0xc19c0000,		/* [153] -19.5 dB */
+	0xc1980000,		/* [154] -19.0 dB */
+	0xc1940000,		/* [155] -18.5 dB */
+	0xc1900000,		/* [156] -18.0 dB */
+	0xc18c0000,		/* [157] -17.5 dB */
+	0xc1880000,		/* [158] -17.0 dB */
+	0xc1840000,		/* [159] -16.5 dB */
+	0xc1800000,		/* [160] -16.0 dB */
+	0xc1780000,		/* [161] -15.5 dB */
+	0xc1700000,		/* [162] -15.0 dB */
+	0xc1680000,		/* [163] -14.5 dB */
+	0xc1600000,		/* [164] -14.0 dB */
+	0xc1580000,		/* [165] -13.5 dB */
+	0xc1500000,		/* [166] -13.0 dB */
+	0xc1480000,		/* [167] -12.5 dB */
+	0xc1400000,		/* [168] -12.0 dB */
+	0xc1380000,		/* [169] -11.5 dB */
+	0xc1300000,		/* [170] -11.0 dB */
+	0xc1280000,		/* [171] -10.5 dB */
+	0xc1200000,		/* [172] -10.0 dB */
+	0xc1180000,		/* [173] -9.5 dB */
+	0xc1100000,		/* [174] -9.0 dB */
+	0xc1080000,		/* [175] -8.5 dB */
+	0xc1000000,		/* [176] -8.0 dB */
+	0xc0f00000,		/* [177] -7.5 dB */
+	0xc0e00000,		/* [178] -7.0 dB */
+	0xc0d00000,		/* [179] -6.5 dB */
+	0xc0c00000,		/* [180] -6.0 dB */
+	0xc0b00000,		/* [181] -5.5 dB */
+	0xc0a00000,		/* [182] -5.0 dB */
+	0xc0900000,		/* [183] -4.5 dB */
+	0xc0800000,		/* [184] -4.0 dB */
+	0xc0600000,		/* [185] -3.5 dB */
+	0xc0400000,		/* [186] -3.0 dB */
+	0xc0200000,		/* [187] -2.5 dB */
+	0xc0000000,		/* [188] -2.0 dB */
+	0xbfc00000,		/* [189] -1.5 dB */
+	0xbf800000,		/* [190] -1.0 dB */
+	0xbf000000,		/* [191] -0.5 dB */
+	0x00000000,		/* [192] 0.0 dB */
+	0x3f000000,		/* [193] 0.5 dB */
+	0x3f800000,		/* [194] 1.0 dB */
+	0x3fc00000,		/* [195] 1.5 dB */
+	0x40000000,		/* [196] 2.0 dB */
+	0x40200000,		/* [197] 2.5 dB */
+	0x40400000,		/* [198] 3.0 dB */
+	0x40600000,		/* [199] 3.5 dB */
+	0x40800000,		/* [200] 4.0 dB */
+	0x40900000,		/* [201] 4.5 dB */
+	0x40a00000,		/* [202] 5.0 dB */
+	0x40b00000,		/* [203] 5.5 dB */
+	0x40c00000,		/* [204] 6.0 dB */
+	0x40d00000,		/* [205] 6.5 dB */
+	0x40e00000,		/* [206] 7.0 dB */
+	0x40f00000,		/* [207] 7.5 dB */
+	0x41000000,		/* [208] 8.0 dB */
+	0x41080000,		/* [209] 8.5 dB */
+	0x41100000,		/* [210] 9.0 dB */
+	0x41180000,		/* [211] 9.5 dB */
+	0x41200000,		/* [212] 10.0 dB */
+	0x41280000,		/* [213] 10.5 dB */
+	0x41300000,		/* [214] 11.0 dB */
+	0x41380000,		/* [215] 11.5 dB */
+	0x41400000,		/* [216] 12.0 dB */
+	0x41480000,		/* [217] 12.5 dB */
+	0x41500000,		/* [218] 13.0 dB */
+	0x41580000,		/* [219] 13.5 dB */
+	0x41600000,		/* [220] 14.0 dB */
+	0x41680000,		/* [221] 14.5 dB */
+	0x41700000,		/* [222] 15.0 dB */
+	0x41780000,		/* [223] 15.5 dB */
+	0x41800000,		/* [224] 16.0 dB */
+	0x41840000,		/* [225] 16.5 dB */
+	0x41880000,		/* [226] 17.0 dB */
+	0x418c0000,		/* [227] 17.5 dB */
+	0x41900000,		/* [228] 18.0 dB */
+	0x41940000,		/* [229] 18.5 dB */
+	0x41980000,		/* [230] 19.0 dB */
+	0x419c0000,		/* [231] 19.5 dB */
+	0x41a00000,		/* [232] 20.0 dB */
+	0x41a40000,		/* [233] 20.5 dB */
+	0x41a80000,		/* [234] 21.0 dB */
+	0x41ac0000,		/* [235] 21.5 dB */
+	0x41b00000,		/* [236] 22.0 dB */
+	0x41b40000,		/* [237] 22.5 dB */
+	0x41b80000,		/* [238] 23.0 dB */
+	0x41bc0000,		/* [239] 23.5 dB */
+	0x41c00000,		/* [240] 24.0 dB */
+	0x41c40000,		/* [241] 24.5 dB */
+	0x41c80000,		/* [242] 25.0 dB */
+	0x41cc0000,		/* [243] 25.5 dB */
+	0x41d00000,		/* [244] 26.0 dB */
+	0x41d40000,		/* [245] 26.5 dB */
+	0x41d80000,		/* [246] 27.0 dB */
+	0x41dc0000,		/* [247] 27.5 dB */
+	0x41e00000,		/* [248] 28.0 dB */
+	0x41e40000,		/* [249] 28.5 dB */
+	0x41e80000,		/* [250] 29.0 dB */
+	0x41ec0000,		/* [251] 29.5 dB */
+	0x41f00000,		/* [252] 30.0 dB */
+	0x41f40000,		/* [253] 30.5 dB */
+	0x41f80000,		/* [254] 31.0 dB */
+	0x41fc0000,		/* [255] 31.5 dB */
+};
+
+#define MIXART_ANALOG_CAPTURE_LEVEL_MIN   0      /* -96.0 dB + 8.0 dB = -88.0 dB */
+#define MIXART_ANALOG_CAPTURE_LEVEL_MAX   255    /*  31.5 dB + 8.0 dB =  39.5 dB */
+#define MIXART_ANALOG_CAPTURE_ZERO_LEVEL  176    /*  -8.0 dB + 8.0 dB =  0.0 dB */
+
+#define MIXART_ANALOG_PLAYBACK_LEVEL_MIN  0      /* -96.0 dB + 1.5 dB = -94.5 dB (possible is down to (-114.0+1.5)dB) */
+#define MIXART_ANALOG_PLAYBACK_LEVEL_MAX  192    /*   0.0 dB + 1.5 dB =  1.5 dB */
+#define MIXART_ANALOG_PLAYBACK_ZERO_LEVEL 189    /*  -1.5 dB + 1.5 dB =  0.0 dB */
+
+static int mixart_update_analog_audio_level(struct snd_mixart* chip, int is_capture)
+{
+	int i, err;
+	struct mixart_msg request;
+	struct mixart_io_level io_level;
+	struct mixart_return_uid resp;
+
+	memset(&io_level, 0, sizeof(io_level));
+	io_level.channel = -1; /* left and right */
+
+	for(i=0; i<2; i++) {
+		if(is_capture) {
+			io_level.level[i].analog_level = mixart_analog_level[chip->analog_capture_volume[i]];
+		} else {
+			if(chip->analog_playback_active[i])
+				io_level.level[i].analog_level = mixart_analog_level[chip->analog_playback_volume[i]];
+			else
+				io_level.level[i].analog_level = mixart_analog_level[MIXART_ANALOG_PLAYBACK_LEVEL_MIN];
+		}
+	}
+
+	if(is_capture)	request.uid = chip->uid_in_analog_physio;
+	else		request.uid = chip->uid_out_analog_physio;
+	request.message_id = MSG_PHYSICALIO_SET_LEVEL;
+	request.data = &io_level;
+	request.size = sizeof(io_level);
+
+	err = snd_mixart_send_msg(chip->mgr, &request, sizeof(resp), &resp);
+	if((err<0) || (resp.error_code)) {
+		snd_printk(KERN_DEBUG "error MSG_PHYSICALIO_SET_LEVEL card(%d) is_capture(%d) error_code(%x)\n", chip->chip_idx, is_capture, resp.error_code);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * analog level control
+ */
+static int mixart_analog_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	if(kcontrol->private_value == 0) {	/* playback */
+		uinfo->value.integer.min = MIXART_ANALOG_PLAYBACK_LEVEL_MIN;  /* -96 dB */
+		uinfo->value.integer.max = MIXART_ANALOG_PLAYBACK_LEVEL_MAX;  /* 0 dB */
+	} else {				/* capture */
+		uinfo->value.integer.min = MIXART_ANALOG_CAPTURE_LEVEL_MIN;   /* -96 dB */
+		uinfo->value.integer.max = MIXART_ANALOG_CAPTURE_LEVEL_MAX;   /* 31.5 dB */
+	}
+	return 0;
+}
+
+static int mixart_analog_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&chip->mgr->mixer_mutex);
+	if(kcontrol->private_value == 0) {	/* playback */
+		ucontrol->value.integer.value[0] = chip->analog_playback_volume[0];
+		ucontrol->value.integer.value[1] = chip->analog_playback_volume[1];
+	} else {				/* capture */
+		ucontrol->value.integer.value[0] = chip->analog_capture_volume[0];
+		ucontrol->value.integer.value[1] = chip->analog_capture_volume[1];
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int mixart_analog_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int is_capture, i;
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	is_capture = (kcontrol->private_value != 0);
+	for (i = 0; i < 2; i++) {
+		int new_volume = ucontrol->value.integer.value[i];
+		int *stored_volume = is_capture ?
+			&chip->analog_capture_volume[i] :
+			&chip->analog_playback_volume[i];
+		if (is_capture) {
+			if (new_volume < MIXART_ANALOG_CAPTURE_LEVEL_MIN ||
+			    new_volume > MIXART_ANALOG_CAPTURE_LEVEL_MAX)
+				continue;
+		} else {
+			if (new_volume < MIXART_ANALOG_PLAYBACK_LEVEL_MIN ||
+			    new_volume > MIXART_ANALOG_PLAYBACK_LEVEL_MAX)
+				continue;
+		}
+		if (*stored_volume != new_volume) {
+			*stored_volume = new_volume;
+			changed = 1;
+		}
+	}
+	if (changed)
+		mixart_update_analog_audio_level(chip, is_capture);
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_analog, -9600, 50, 0);
+
+static struct snd_kcontrol_new mixart_control_analog_level = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	/* name will be filled later */
+	.info =		mixart_analog_vol_info,
+	.get =		mixart_analog_vol_get,
+	.put =		mixart_analog_vol_put,
+	.tlv = { .p = db_scale_analog },
+};
+
+/* shared */
+#define mixart_sw_info		snd_ctl_boolean_stereo_info
+
+static int mixart_audio_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->analog_playback_active[0];
+	ucontrol->value.integer.value[1] = chip->analog_playback_active[1];
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int mixart_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+	int i, changed = 0;
+	mutex_lock(&chip->mgr->mixer_mutex);
+	for (i = 0; i < 2; i++) {
+		if (chip->analog_playback_active[i] !=
+		    ucontrol->value.integer.value[i]) {
+			chip->analog_playback_active[i] =
+				!!ucontrol->value.integer.value[i];
+			changed = 1;
+		}
+	}
+	if (changed) /* update playback levels */
+		mixart_update_analog_audio_level(chip, 0);
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new mixart_control_output_switch = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Master Playback Switch",
+	.info =         mixart_sw_info,		/* shared */
+	.get =          mixart_audio_sw_get,
+	.put =          mixart_audio_sw_put
+};
+
+static u32 mixart_digital_level[256] = {
+	0x00000000,		/* [000] = 0.00e+000 = mute if <= -109.5dB */
+	0x366e1c7a,		/* [001] = 3.55e-006 = pow(10.0, 0.05 * -109.0dB) */
+	0x367c3860,		/* [002] = 3.76e-006 = pow(10.0, 0.05 * -108.5dB) */
+	0x36859525,		/* [003] = 3.98e-006 = pow(10.0, 0.05 * -108.0dB) */
+	0x368d7f74,		/* [004] = 4.22e-006 = pow(10.0, 0.05 * -107.5dB) */
+	0x3695e1d4,		/* [005] = 4.47e-006 = pow(10.0, 0.05 * -107.0dB) */
+	0x369ec362,		/* [006] = 4.73e-006 = pow(10.0, 0.05 * -106.5dB) */
+	0x36a82ba8,		/* [007] = 5.01e-006 = pow(10.0, 0.05 * -106.0dB) */
+	0x36b222a0,		/* [008] = 5.31e-006 = pow(10.0, 0.05 * -105.5dB) */
+	0x36bcb0c1,		/* [009] = 5.62e-006 = pow(10.0, 0.05 * -105.0dB) */
+	0x36c7defd,		/* [010] = 5.96e-006 = pow(10.0, 0.05 * -104.5dB) */
+	0x36d3b6d3,		/* [011] = 6.31e-006 = pow(10.0, 0.05 * -104.0dB) */
+	0x36e0424e,		/* [012] = 6.68e-006 = pow(10.0, 0.05 * -103.5dB) */
+	0x36ed8c14,		/* [013] = 7.08e-006 = pow(10.0, 0.05 * -103.0dB) */
+	0x36fb9f6c,		/* [014] = 7.50e-006 = pow(10.0, 0.05 * -102.5dB) */
+	0x37054423,		/* [015] = 7.94e-006 = pow(10.0, 0.05 * -102.0dB) */
+	0x370d29a5,		/* [016] = 8.41e-006 = pow(10.0, 0.05 * -101.5dB) */
+	0x371586f0,		/* [017] = 8.91e-006 = pow(10.0, 0.05 * -101.0dB) */
+	0x371e631b,		/* [018] = 9.44e-006 = pow(10.0, 0.05 * -100.5dB) */
+	0x3727c5ac,		/* [019] = 1.00e-005 = pow(10.0, 0.05 * -100.0dB) */
+	0x3731b69a,		/* [020] = 1.06e-005 = pow(10.0, 0.05 * -99.5dB) */
+	0x373c3e53,		/* [021] = 1.12e-005 = pow(10.0, 0.05 * -99.0dB) */
+	0x374765c8,		/* [022] = 1.19e-005 = pow(10.0, 0.05 * -98.5dB) */
+	0x3753366f,		/* [023] = 1.26e-005 = pow(10.0, 0.05 * -98.0dB) */
+	0x375fba4f,		/* [024] = 1.33e-005 = pow(10.0, 0.05 * -97.5dB) */
+	0x376cfc07,		/* [025] = 1.41e-005 = pow(10.0, 0.05 * -97.0dB) */
+	0x377b06d5,		/* [026] = 1.50e-005 = pow(10.0, 0.05 * -96.5dB) */
+	0x3784f352,		/* [027] = 1.58e-005 = pow(10.0, 0.05 * -96.0dB) */
+	0x378cd40b,		/* [028] = 1.68e-005 = pow(10.0, 0.05 * -95.5dB) */
+	0x37952c42,		/* [029] = 1.78e-005 = pow(10.0, 0.05 * -95.0dB) */
+	0x379e030e,		/* [030] = 1.88e-005 = pow(10.0, 0.05 * -94.5dB) */
+	0x37a75fef,		/* [031] = 2.00e-005 = pow(10.0, 0.05 * -94.0dB) */
+	0x37b14ad5,		/* [032] = 2.11e-005 = pow(10.0, 0.05 * -93.5dB) */
+	0x37bbcc2c,		/* [033] = 2.24e-005 = pow(10.0, 0.05 * -93.0dB) */
+	0x37c6ecdd,		/* [034] = 2.37e-005 = pow(10.0, 0.05 * -92.5dB) */
+	0x37d2b65a,		/* [035] = 2.51e-005 = pow(10.0, 0.05 * -92.0dB) */
+	0x37df32a3,		/* [036] = 2.66e-005 = pow(10.0, 0.05 * -91.5dB) */
+	0x37ec6c50,		/* [037] = 2.82e-005 = pow(10.0, 0.05 * -91.0dB) */
+	0x37fa6e9b,		/* [038] = 2.99e-005 = pow(10.0, 0.05 * -90.5dB) */
+	0x3804a2b3,		/* [039] = 3.16e-005 = pow(10.0, 0.05 * -90.0dB) */
+	0x380c7ea4,		/* [040] = 3.35e-005 = pow(10.0, 0.05 * -89.5dB) */
+	0x3814d1cc,		/* [041] = 3.55e-005 = pow(10.0, 0.05 * -89.0dB) */
+	0x381da33c,		/* [042] = 3.76e-005 = pow(10.0, 0.05 * -88.5dB) */
+	0x3826fa6f,		/* [043] = 3.98e-005 = pow(10.0, 0.05 * -88.0dB) */
+	0x3830df51,		/* [044] = 4.22e-005 = pow(10.0, 0.05 * -87.5dB) */
+	0x383b5a49,		/* [045] = 4.47e-005 = pow(10.0, 0.05 * -87.0dB) */
+	0x3846743b,		/* [046] = 4.73e-005 = pow(10.0, 0.05 * -86.5dB) */
+	0x38523692,		/* [047] = 5.01e-005 = pow(10.0, 0.05 * -86.0dB) */
+	0x385eab48,		/* [048] = 5.31e-005 = pow(10.0, 0.05 * -85.5dB) */
+	0x386bdcf1,		/* [049] = 5.62e-005 = pow(10.0, 0.05 * -85.0dB) */
+	0x3879d6bc,		/* [050] = 5.96e-005 = pow(10.0, 0.05 * -84.5dB) */
+	0x38845244,		/* [051] = 6.31e-005 = pow(10.0, 0.05 * -84.0dB) */
+	0x388c2971,		/* [052] = 6.68e-005 = pow(10.0, 0.05 * -83.5dB) */
+	0x3894778d,		/* [053] = 7.08e-005 = pow(10.0, 0.05 * -83.0dB) */
+	0x389d43a4,		/* [054] = 7.50e-005 = pow(10.0, 0.05 * -82.5dB) */
+	0x38a6952c,		/* [055] = 7.94e-005 = pow(10.0, 0.05 * -82.0dB) */
+	0x38b0740f,		/* [056] = 8.41e-005 = pow(10.0, 0.05 * -81.5dB) */
+	0x38bae8ac,		/* [057] = 8.91e-005 = pow(10.0, 0.05 * -81.0dB) */
+	0x38c5fbe2,		/* [058] = 9.44e-005 = pow(10.0, 0.05 * -80.5dB) */
+	0x38d1b717,		/* [059] = 1.00e-004 = pow(10.0, 0.05 * -80.0dB) */
+	0x38de2440,		/* [060] = 1.06e-004 = pow(10.0, 0.05 * -79.5dB) */
+	0x38eb4de8,		/* [061] = 1.12e-004 = pow(10.0, 0.05 * -79.0dB) */
+	0x38f93f3a,		/* [062] = 1.19e-004 = pow(10.0, 0.05 * -78.5dB) */
+	0x39040206,		/* [063] = 1.26e-004 = pow(10.0, 0.05 * -78.0dB) */
+	0x390bd472,		/* [064] = 1.33e-004 = pow(10.0, 0.05 * -77.5dB) */
+	0x39141d84,		/* [065] = 1.41e-004 = pow(10.0, 0.05 * -77.0dB) */
+	0x391ce445,		/* [066] = 1.50e-004 = pow(10.0, 0.05 * -76.5dB) */
+	0x39263027,		/* [067] = 1.58e-004 = pow(10.0, 0.05 * -76.0dB) */
+	0x3930090d,		/* [068] = 1.68e-004 = pow(10.0, 0.05 * -75.5dB) */
+	0x393a7753,		/* [069] = 1.78e-004 = pow(10.0, 0.05 * -75.0dB) */
+	0x394583d2,		/* [070] = 1.88e-004 = pow(10.0, 0.05 * -74.5dB) */
+	0x395137ea,		/* [071] = 2.00e-004 = pow(10.0, 0.05 * -74.0dB) */
+	0x395d9d8a,		/* [072] = 2.11e-004 = pow(10.0, 0.05 * -73.5dB) */
+	0x396abf37,		/* [073] = 2.24e-004 = pow(10.0, 0.05 * -73.0dB) */
+	0x3978a814,		/* [074] = 2.37e-004 = pow(10.0, 0.05 * -72.5dB) */
+	0x3983b1f8,		/* [075] = 2.51e-004 = pow(10.0, 0.05 * -72.0dB) */
+	0x398b7fa6,		/* [076] = 2.66e-004 = pow(10.0, 0.05 * -71.5dB) */
+	0x3993c3b2,		/* [077] = 2.82e-004 = pow(10.0, 0.05 * -71.0dB) */
+	0x399c8521,		/* [078] = 2.99e-004 = pow(10.0, 0.05 * -70.5dB) */
+	0x39a5cb5f,		/* [079] = 3.16e-004 = pow(10.0, 0.05 * -70.0dB) */
+	0x39af9e4d,		/* [080] = 3.35e-004 = pow(10.0, 0.05 * -69.5dB) */
+	0x39ba063f,		/* [081] = 3.55e-004 = pow(10.0, 0.05 * -69.0dB) */
+	0x39c50c0b,		/* [082] = 3.76e-004 = pow(10.0, 0.05 * -68.5dB) */
+	0x39d0b90a,		/* [083] = 3.98e-004 = pow(10.0, 0.05 * -68.0dB) */
+	0x39dd1726,		/* [084] = 4.22e-004 = pow(10.0, 0.05 * -67.5dB) */
+	0x39ea30db,		/* [085] = 4.47e-004 = pow(10.0, 0.05 * -67.0dB) */
+	0x39f81149,		/* [086] = 4.73e-004 = pow(10.0, 0.05 * -66.5dB) */
+	0x3a03621b,		/* [087] = 5.01e-004 = pow(10.0, 0.05 * -66.0dB) */
+	0x3a0b2b0d,		/* [088] = 5.31e-004 = pow(10.0, 0.05 * -65.5dB) */
+	0x3a136a16,		/* [089] = 5.62e-004 = pow(10.0, 0.05 * -65.0dB) */
+	0x3a1c2636,		/* [090] = 5.96e-004 = pow(10.0, 0.05 * -64.5dB) */
+	0x3a2566d5,		/* [091] = 6.31e-004 = pow(10.0, 0.05 * -64.0dB) */
+	0x3a2f33cd,		/* [092] = 6.68e-004 = pow(10.0, 0.05 * -63.5dB) */
+	0x3a399570,		/* [093] = 7.08e-004 = pow(10.0, 0.05 * -63.0dB) */
+	0x3a44948c,		/* [094] = 7.50e-004 = pow(10.0, 0.05 * -62.5dB) */
+	0x3a503a77,		/* [095] = 7.94e-004 = pow(10.0, 0.05 * -62.0dB) */
+	0x3a5c9112,		/* [096] = 8.41e-004 = pow(10.0, 0.05 * -61.5dB) */
+	0x3a69a2d7,		/* [097] = 8.91e-004 = pow(10.0, 0.05 * -61.0dB) */
+	0x3a777ada,		/* [098] = 9.44e-004 = pow(10.0, 0.05 * -60.5dB) */
+	0x3a83126f,		/* [099] = 1.00e-003 = pow(10.0, 0.05 * -60.0dB) */
+	0x3a8ad6a8,		/* [100] = 1.06e-003 = pow(10.0, 0.05 * -59.5dB) */
+	0x3a9310b1,		/* [101] = 1.12e-003 = pow(10.0, 0.05 * -59.0dB) */
+	0x3a9bc784,		/* [102] = 1.19e-003 = pow(10.0, 0.05 * -58.5dB) */
+	0x3aa50287,		/* [103] = 1.26e-003 = pow(10.0, 0.05 * -58.0dB) */
+	0x3aaec98e,		/* [104] = 1.33e-003 = pow(10.0, 0.05 * -57.5dB) */
+	0x3ab924e5,		/* [105] = 1.41e-003 = pow(10.0, 0.05 * -57.0dB) */
+	0x3ac41d56,		/* [106] = 1.50e-003 = pow(10.0, 0.05 * -56.5dB) */
+	0x3acfbc31,		/* [107] = 1.58e-003 = pow(10.0, 0.05 * -56.0dB) */
+	0x3adc0b51,		/* [108] = 1.68e-003 = pow(10.0, 0.05 * -55.5dB) */
+	0x3ae91528,		/* [109] = 1.78e-003 = pow(10.0, 0.05 * -55.0dB) */
+	0x3af6e4c6,		/* [110] = 1.88e-003 = pow(10.0, 0.05 * -54.5dB) */
+	0x3b02c2f2,		/* [111] = 2.00e-003 = pow(10.0, 0.05 * -54.0dB) */
+	0x3b0a8276,		/* [112] = 2.11e-003 = pow(10.0, 0.05 * -53.5dB) */
+	0x3b12b782,		/* [113] = 2.24e-003 = pow(10.0, 0.05 * -53.0dB) */
+	0x3b1b690d,		/* [114] = 2.37e-003 = pow(10.0, 0.05 * -52.5dB) */
+	0x3b249e76,		/* [115] = 2.51e-003 = pow(10.0, 0.05 * -52.0dB) */
+	0x3b2e5f8f,		/* [116] = 2.66e-003 = pow(10.0, 0.05 * -51.5dB) */
+	0x3b38b49f,		/* [117] = 2.82e-003 = pow(10.0, 0.05 * -51.0dB) */
+	0x3b43a669,		/* [118] = 2.99e-003 = pow(10.0, 0.05 * -50.5dB) */
+	0x3b4f3e37,		/* [119] = 3.16e-003 = pow(10.0, 0.05 * -50.0dB) */
+	0x3b5b85e0,		/* [120] = 3.35e-003 = pow(10.0, 0.05 * -49.5dB) */
+	0x3b6887cf,		/* [121] = 3.55e-003 = pow(10.0, 0.05 * -49.0dB) */
+	0x3b764f0e,		/* [122] = 3.76e-003 = pow(10.0, 0.05 * -48.5dB) */
+	0x3b8273a6,		/* [123] = 3.98e-003 = pow(10.0, 0.05 * -48.0dB) */
+	0x3b8a2e77,		/* [124] = 4.22e-003 = pow(10.0, 0.05 * -47.5dB) */
+	0x3b925e89,		/* [125] = 4.47e-003 = pow(10.0, 0.05 * -47.0dB) */
+	0x3b9b0ace,		/* [126] = 4.73e-003 = pow(10.0, 0.05 * -46.5dB) */
+	0x3ba43aa2,		/* [127] = 5.01e-003 = pow(10.0, 0.05 * -46.0dB) */
+	0x3badf5d1,		/* [128] = 5.31e-003 = pow(10.0, 0.05 * -45.5dB) */
+	0x3bb8449c,		/* [129] = 5.62e-003 = pow(10.0, 0.05 * -45.0dB) */
+	0x3bc32fc3,		/* [130] = 5.96e-003 = pow(10.0, 0.05 * -44.5dB) */
+	0x3bcec08a,		/* [131] = 6.31e-003 = pow(10.0, 0.05 * -44.0dB) */
+	0x3bdb00c0,		/* [132] = 6.68e-003 = pow(10.0, 0.05 * -43.5dB) */
+	0x3be7facc,		/* [133] = 7.08e-003 = pow(10.0, 0.05 * -43.0dB) */
+	0x3bf5b9b0,		/* [134] = 7.50e-003 = pow(10.0, 0.05 * -42.5dB) */
+	0x3c02248a,		/* [135] = 7.94e-003 = pow(10.0, 0.05 * -42.0dB) */
+	0x3c09daac,		/* [136] = 8.41e-003 = pow(10.0, 0.05 * -41.5dB) */
+	0x3c1205c6,		/* [137] = 8.91e-003 = pow(10.0, 0.05 * -41.0dB) */
+	0x3c1aacc8,		/* [138] = 9.44e-003 = pow(10.0, 0.05 * -40.5dB) */
+	0x3c23d70a,		/* [139] = 1.00e-002 = pow(10.0, 0.05 * -40.0dB) */
+	0x3c2d8c52,		/* [140] = 1.06e-002 = pow(10.0, 0.05 * -39.5dB) */
+	0x3c37d4dd,		/* [141] = 1.12e-002 = pow(10.0, 0.05 * -39.0dB) */
+	0x3c42b965,		/* [142] = 1.19e-002 = pow(10.0, 0.05 * -38.5dB) */
+	0x3c4e4329,		/* [143] = 1.26e-002 = pow(10.0, 0.05 * -38.0dB) */
+	0x3c5a7bf1,		/* [144] = 1.33e-002 = pow(10.0, 0.05 * -37.5dB) */
+	0x3c676e1e,		/* [145] = 1.41e-002 = pow(10.0, 0.05 * -37.0dB) */
+	0x3c7524ac,		/* [146] = 1.50e-002 = pow(10.0, 0.05 * -36.5dB) */
+	0x3c81d59f,		/* [147] = 1.58e-002 = pow(10.0, 0.05 * -36.0dB) */
+	0x3c898712,		/* [148] = 1.68e-002 = pow(10.0, 0.05 * -35.5dB) */
+	0x3c91ad39,		/* [149] = 1.78e-002 = pow(10.0, 0.05 * -35.0dB) */
+	0x3c9a4efc,		/* [150] = 1.88e-002 = pow(10.0, 0.05 * -34.5dB) */
+	0x3ca373af,		/* [151] = 2.00e-002 = pow(10.0, 0.05 * -34.0dB) */
+	0x3cad2314,		/* [152] = 2.11e-002 = pow(10.0, 0.05 * -33.5dB) */
+	0x3cb76563,		/* [153] = 2.24e-002 = pow(10.0, 0.05 * -33.0dB) */
+	0x3cc24350,		/* [154] = 2.37e-002 = pow(10.0, 0.05 * -32.5dB) */
+	0x3ccdc614,		/* [155] = 2.51e-002 = pow(10.0, 0.05 * -32.0dB) */
+	0x3cd9f773,		/* [156] = 2.66e-002 = pow(10.0, 0.05 * -31.5dB) */
+	0x3ce6e1c6,		/* [157] = 2.82e-002 = pow(10.0, 0.05 * -31.0dB) */
+	0x3cf49003,		/* [158] = 2.99e-002 = pow(10.0, 0.05 * -30.5dB) */
+	0x3d0186e2,		/* [159] = 3.16e-002 = pow(10.0, 0.05 * -30.0dB) */
+	0x3d0933ac,		/* [160] = 3.35e-002 = pow(10.0, 0.05 * -29.5dB) */
+	0x3d1154e1,		/* [161] = 3.55e-002 = pow(10.0, 0.05 * -29.0dB) */
+	0x3d19f169,		/* [162] = 3.76e-002 = pow(10.0, 0.05 * -28.5dB) */
+	0x3d231090,		/* [163] = 3.98e-002 = pow(10.0, 0.05 * -28.0dB) */
+	0x3d2cba15,		/* [164] = 4.22e-002 = pow(10.0, 0.05 * -27.5dB) */
+	0x3d36f62b,		/* [165] = 4.47e-002 = pow(10.0, 0.05 * -27.0dB) */
+	0x3d41cd81,		/* [166] = 4.73e-002 = pow(10.0, 0.05 * -26.5dB) */
+	0x3d4d494a,		/* [167] = 5.01e-002 = pow(10.0, 0.05 * -26.0dB) */
+	0x3d597345,		/* [168] = 5.31e-002 = pow(10.0, 0.05 * -25.5dB) */
+	0x3d6655c3,		/* [169] = 5.62e-002 = pow(10.0, 0.05 * -25.0dB) */
+	0x3d73fbb4,		/* [170] = 5.96e-002 = pow(10.0, 0.05 * -24.5dB) */
+	0x3d813856,		/* [171] = 6.31e-002 = pow(10.0, 0.05 * -24.0dB) */
+	0x3d88e078,		/* [172] = 6.68e-002 = pow(10.0, 0.05 * -23.5dB) */
+	0x3d90fcbf,		/* [173] = 7.08e-002 = pow(10.0, 0.05 * -23.0dB) */
+	0x3d99940e,		/* [174] = 7.50e-002 = pow(10.0, 0.05 * -22.5dB) */
+	0x3da2adad,		/* [175] = 7.94e-002 = pow(10.0, 0.05 * -22.0dB) */
+	0x3dac5156,		/* [176] = 8.41e-002 = pow(10.0, 0.05 * -21.5dB) */
+	0x3db68738,		/* [177] = 8.91e-002 = pow(10.0, 0.05 * -21.0dB) */
+	0x3dc157fb,		/* [178] = 9.44e-002 = pow(10.0, 0.05 * -20.5dB) */
+	0x3dcccccd,		/* [179] = 1.00e-001 = pow(10.0, 0.05 * -20.0dB) */
+	0x3dd8ef67,		/* [180] = 1.06e-001 = pow(10.0, 0.05 * -19.5dB) */
+	0x3de5ca15,		/* [181] = 1.12e-001 = pow(10.0, 0.05 * -19.0dB) */
+	0x3df367bf,		/* [182] = 1.19e-001 = pow(10.0, 0.05 * -18.5dB) */
+	0x3e00e9f9,		/* [183] = 1.26e-001 = pow(10.0, 0.05 * -18.0dB) */
+	0x3e088d77,		/* [184] = 1.33e-001 = pow(10.0, 0.05 * -17.5dB) */
+	0x3e10a4d3,		/* [185] = 1.41e-001 = pow(10.0, 0.05 * -17.0dB) */
+	0x3e1936ec,		/* [186] = 1.50e-001 = pow(10.0, 0.05 * -16.5dB) */
+	0x3e224b06,		/* [187] = 1.58e-001 = pow(10.0, 0.05 * -16.0dB) */
+	0x3e2be8d7,		/* [188] = 1.68e-001 = pow(10.0, 0.05 * -15.5dB) */
+	0x3e361887,		/* [189] = 1.78e-001 = pow(10.0, 0.05 * -15.0dB) */
+	0x3e40e2bb,		/* [190] = 1.88e-001 = pow(10.0, 0.05 * -14.5dB) */
+	0x3e4c509b,		/* [191] = 2.00e-001 = pow(10.0, 0.05 * -14.0dB) */
+	0x3e586bd9,		/* [192] = 2.11e-001 = pow(10.0, 0.05 * -13.5dB) */
+	0x3e653ebb,		/* [193] = 2.24e-001 = pow(10.0, 0.05 * -13.0dB) */
+	0x3e72d424,		/* [194] = 2.37e-001 = pow(10.0, 0.05 * -12.5dB) */
+	0x3e809bcc,		/* [195] = 2.51e-001 = pow(10.0, 0.05 * -12.0dB) */
+	0x3e883aa8,		/* [196] = 2.66e-001 = pow(10.0, 0.05 * -11.5dB) */
+	0x3e904d1c,		/* [197] = 2.82e-001 = pow(10.0, 0.05 * -11.0dB) */
+	0x3e98da02,		/* [198] = 2.99e-001 = pow(10.0, 0.05 * -10.5dB) */
+	0x3ea1e89b,		/* [199] = 3.16e-001 = pow(10.0, 0.05 * -10.0dB) */
+	0x3eab8097,		/* [200] = 3.35e-001 = pow(10.0, 0.05 * -9.5dB) */
+	0x3eb5aa1a,		/* [201] = 3.55e-001 = pow(10.0, 0.05 * -9.0dB) */
+	0x3ec06dc3,		/* [202] = 3.76e-001 = pow(10.0, 0.05 * -8.5dB) */
+	0x3ecbd4b4,		/* [203] = 3.98e-001 = pow(10.0, 0.05 * -8.0dB) */
+	0x3ed7e89b,		/* [204] = 4.22e-001 = pow(10.0, 0.05 * -7.5dB) */
+	0x3ee4b3b6,		/* [205] = 4.47e-001 = pow(10.0, 0.05 * -7.0dB) */
+	0x3ef240e2,		/* [206] = 4.73e-001 = pow(10.0, 0.05 * -6.5dB) */
+	0x3f004dce,		/* [207] = 5.01e-001 = pow(10.0, 0.05 * -6.0dB) */
+	0x3f07e80b,		/* [208] = 5.31e-001 = pow(10.0, 0.05 * -5.5dB) */
+	0x3f0ff59a,		/* [209] = 5.62e-001 = pow(10.0, 0.05 * -5.0dB) */
+	0x3f187d50,		/* [210] = 5.96e-001 = pow(10.0, 0.05 * -4.5dB) */
+	0x3f21866c,		/* [211] = 6.31e-001 = pow(10.0, 0.05 * -4.0dB) */
+	0x3f2b1896,		/* [212] = 6.68e-001 = pow(10.0, 0.05 * -3.5dB) */
+	0x3f353bef,		/* [213] = 7.08e-001 = pow(10.0, 0.05 * -3.0dB) */
+	0x3f3ff911,		/* [214] = 7.50e-001 = pow(10.0, 0.05 * -2.5dB) */
+	0x3f4b5918,		/* [215] = 7.94e-001 = pow(10.0, 0.05 * -2.0dB) */
+	0x3f5765ac,		/* [216] = 8.41e-001 = pow(10.0, 0.05 * -1.5dB) */
+	0x3f642905,		/* [217] = 8.91e-001 = pow(10.0, 0.05 * -1.0dB) */
+	0x3f71adf9,		/* [218] = 9.44e-001 = pow(10.0, 0.05 * -0.5dB) */
+	0x3f800000,		/* [219] = 1.00e+000 = pow(10.0, 0.05 * 0.0dB) */
+	0x3f8795a0,		/* [220] = 1.06e+000 = pow(10.0, 0.05 * 0.5dB) */
+	0x3f8f9e4d,		/* [221] = 1.12e+000 = pow(10.0, 0.05 * 1.0dB) */
+	0x3f9820d7,		/* [222] = 1.19e+000 = pow(10.0, 0.05 * 1.5dB) */
+	0x3fa12478,		/* [223] = 1.26e+000 = pow(10.0, 0.05 * 2.0dB) */
+	0x3faab0d5,		/* [224] = 1.33e+000 = pow(10.0, 0.05 * 2.5dB) */
+	0x3fb4ce08,		/* [225] = 1.41e+000 = pow(10.0, 0.05 * 3.0dB) */
+	0x3fbf84a6,		/* [226] = 1.50e+000 = pow(10.0, 0.05 * 3.5dB) */
+	0x3fcaddc8,		/* [227] = 1.58e+000 = pow(10.0, 0.05 * 4.0dB) */
+	0x3fd6e30d,		/* [228] = 1.68e+000 = pow(10.0, 0.05 * 4.5dB) */
+	0x3fe39ea9,		/* [229] = 1.78e+000 = pow(10.0, 0.05 * 5.0dB) */
+	0x3ff11b6a,		/* [230] = 1.88e+000 = pow(10.0, 0.05 * 5.5dB) */
+	0x3fff64c1,		/* [231] = 2.00e+000 = pow(10.0, 0.05 * 6.0dB) */
+	0x40074368,		/* [232] = 2.11e+000 = pow(10.0, 0.05 * 6.5dB) */
+	0x400f4735,		/* [233] = 2.24e+000 = pow(10.0, 0.05 * 7.0dB) */
+	0x4017c496,		/* [234] = 2.37e+000 = pow(10.0, 0.05 * 7.5dB) */
+	0x4020c2bf,		/* [235] = 2.51e+000 = pow(10.0, 0.05 * 8.0dB) */
+	0x402a4952,		/* [236] = 2.66e+000 = pow(10.0, 0.05 * 8.5dB) */
+	0x40346063,		/* [237] = 2.82e+000 = pow(10.0, 0.05 * 9.0dB) */
+	0x403f1082,		/* [238] = 2.99e+000 = pow(10.0, 0.05 * 9.5dB) */
+	0x404a62c2,		/* [239] = 3.16e+000 = pow(10.0, 0.05 * 10.0dB) */
+	0x405660bd,		/* [240] = 3.35e+000 = pow(10.0, 0.05 * 10.5dB) */
+	0x406314a0,		/* [241] = 3.55e+000 = pow(10.0, 0.05 * 11.0dB) */
+	0x40708933,		/* [242] = 3.76e+000 = pow(10.0, 0.05 * 11.5dB) */
+	0x407ec9e1,		/* [243] = 3.98e+000 = pow(10.0, 0.05 * 12.0dB) */
+	0x4086f161,		/* [244] = 4.22e+000 = pow(10.0, 0.05 * 12.5dB) */
+	0x408ef052,		/* [245] = 4.47e+000 = pow(10.0, 0.05 * 13.0dB) */
+	0x4097688d,		/* [246] = 4.73e+000 = pow(10.0, 0.05 * 13.5dB) */
+	0x40a06142,		/* [247] = 5.01e+000 = pow(10.0, 0.05 * 14.0dB) */
+	0x40a9e20e,		/* [248] = 5.31e+000 = pow(10.0, 0.05 * 14.5dB) */
+	0x40b3f300,		/* [249] = 5.62e+000 = pow(10.0, 0.05 * 15.0dB) */
+	0x40be9ca5,		/* [250] = 5.96e+000 = pow(10.0, 0.05 * 15.5dB) */
+	0x40c9e807,		/* [251] = 6.31e+000 = pow(10.0, 0.05 * 16.0dB) */
+	0x40d5debc,		/* [252] = 6.68e+000 = pow(10.0, 0.05 * 16.5dB) */
+	0x40e28aeb,		/* [253] = 7.08e+000 = pow(10.0, 0.05 * 17.0dB) */
+	0x40eff755,		/* [254] = 7.50e+000 = pow(10.0, 0.05 * 17.5dB) */
+	0x40fe2f5e,		/* [255] = 7.94e+000 = pow(10.0, 0.05 * 18.0dB) */
+};
+
+#define MIXART_DIGITAL_LEVEL_MIN   0      /* -109.5 dB */
+#define MIXART_DIGITAL_LEVEL_MAX   255    /*  18.0 dB */
+#define MIXART_DIGITAL_ZERO_LEVEL  219    /*  0.0 dB */
+
+
+int mixart_update_playback_stream_level(struct snd_mixart* chip, int is_aes, int idx)
+{
+	int err, i;
+	int volume[2];
+	struct mixart_msg request;
+	struct mixart_set_out_stream_level_req set_level;
+	u32 status;
+	struct mixart_pipe *pipe;
+
+	memset(&set_level, 0, sizeof(set_level));
+	set_level.nb_of_stream = 1;
+	set_level.stream_level.desc.stream_idx = idx;
+
+	if(is_aes) {
+		pipe = &chip->pipe_out_dig;	/* AES playback */
+		idx += MIXART_PLAYBACK_STREAMS;
+	} else {
+		pipe = &chip->pipe_out_ana;	/* analog playback */
+	}
+
+	/* only when pipe exists ! */
+	if(pipe->status == PIPE_UNDEFINED)
+		return 0;
+
+	set_level.stream_level.desc.uid_pipe = pipe->group_uid;
+
+	for(i=0; i<2; i++) {
+		if(chip->digital_playback_active[idx][i])
+			volume[i] = chip->digital_playback_volume[idx][i];
+		else
+			volume[i] = MIXART_DIGITAL_LEVEL_MIN;
+	}
+
+	set_level.stream_level.out_level.valid_mask1 = MIXART_OUT_STREAM_SET_LEVEL_LEFT_AUDIO1 | MIXART_OUT_STREAM_SET_LEVEL_RIGHT_AUDIO2;
+	set_level.stream_level.out_level.left_to_out1_level = mixart_digital_level[volume[0]];
+	set_level.stream_level.out_level.right_to_out2_level = mixart_digital_level[volume[1]];
+
+	request.message_id = MSG_STREAM_SET_OUT_STREAM_LEVEL;
+	request.uid = (struct mixart_uid){0,0};
+	request.data = &set_level;
+	request.size = sizeof(set_level);
+
+	err = snd_mixart_send_msg(chip->mgr, &request, sizeof(status), &status);
+	if((err<0) || status) {
+		snd_printk(KERN_DEBUG "error MSG_STREAM_SET_OUT_STREAM_LEVEL card(%d) status(%x)\n", chip->chip_idx, status);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+int mixart_update_capture_stream_level(struct snd_mixart* chip, int is_aes)
+{
+	int err, i, idx;
+	struct mixart_pipe *pipe;
+	struct mixart_msg request;
+	struct mixart_set_in_audio_level_req set_level;
+	u32 status;
+
+	if(is_aes) {
+		idx = 1;
+		pipe = &chip->pipe_in_dig;
+	} else {
+		idx = 0;
+		pipe = &chip->pipe_in_ana;
+	}
+
+	/* only when pipe exists ! */
+	if(pipe->status == PIPE_UNDEFINED)
+		return 0;
+
+	memset(&set_level, 0, sizeof(set_level));
+	set_level.audio_count = 2;
+	set_level.level[0].connector = pipe->uid_left_connector;
+	set_level.level[1].connector = pipe->uid_right_connector;
+
+	for(i=0; i<2; i++) {
+		set_level.level[i].valid_mask1 = MIXART_AUDIO_LEVEL_DIGITAL_MASK;
+		set_level.level[i].digital_level = mixart_digital_level[chip->digital_capture_volume[idx][i]];
+	}
+
+	request.message_id = MSG_STREAM_SET_IN_AUDIO_LEVEL;
+	request.uid = (struct mixart_uid){0,0};
+	request.data = &set_level;
+	request.size = sizeof(set_level);
+
+	err = snd_mixart_send_msg(chip->mgr, &request, sizeof(status), &status);
+	if((err<0) || status) {
+		snd_printk(KERN_DEBUG "error MSG_STREAM_SET_IN_AUDIO_LEVEL card(%d) status(%x)\n", chip->chip_idx, status);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/* shared */
+static int mixart_digital_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = MIXART_DIGITAL_LEVEL_MIN;   /* -109.5 dB */
+	uinfo->value.integer.max = MIXART_DIGITAL_LEVEL_MAX;   /*   18.0 dB */
+	return 0;
+}
+
+#define MIXART_VOL_REC_MASK	1
+#define MIXART_VOL_AES_MASK	2
+
+static int mixart_pcm_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */
+	int *stored_volume;
+	int is_capture = kcontrol->private_value & MIXART_VOL_REC_MASK;
+	int is_aes = kcontrol->private_value & MIXART_VOL_AES_MASK;
+	mutex_lock(&chip->mgr->mixer_mutex);
+	if(is_capture) {
+		if(is_aes)	stored_volume = chip->digital_capture_volume[1];	/* AES capture */
+		else		stored_volume = chip->digital_capture_volume[0];	/* analog capture */
+	} else {
+		snd_BUG_ON(idx >= MIXART_PLAYBACK_STREAMS);
+		if(is_aes)	stored_volume = chip->digital_playback_volume[MIXART_PLAYBACK_STREAMS + idx]; /* AES playback */
+		else		stored_volume = chip->digital_playback_volume[idx];	/* analog playback */
+	}
+	ucontrol->value.integer.value[0] = stored_volume[0];
+	ucontrol->value.integer.value[1] = stored_volume[1];
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int mixart_pcm_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */
+	int changed = 0;
+	int is_capture = kcontrol->private_value & MIXART_VOL_REC_MASK;
+	int is_aes = kcontrol->private_value & MIXART_VOL_AES_MASK;
+	int* stored_volume;
+	int i;
+	mutex_lock(&chip->mgr->mixer_mutex);
+	if (is_capture) {
+		if (is_aes)	/* AES capture */
+			stored_volume = chip->digital_capture_volume[1];
+		else		/* analog capture */
+			stored_volume = chip->digital_capture_volume[0];
+	} else {
+		snd_BUG_ON(idx >= MIXART_PLAYBACK_STREAMS);
+		if (is_aes)	/* AES playback */
+			stored_volume = chip->digital_playback_volume[MIXART_PLAYBACK_STREAMS + idx];
+		else		/* analog playback */
+			stored_volume = chip->digital_playback_volume[idx];
+	}
+	for (i = 0; i < 2; i++) {
+		int vol = ucontrol->value.integer.value[i];
+		if (vol < MIXART_DIGITAL_LEVEL_MIN ||
+		    vol > MIXART_DIGITAL_LEVEL_MAX)
+			continue;
+		if (stored_volume[i] != vol) {
+			stored_volume[i] = vol;
+			changed = 1;
+		}
+	}
+	if (changed) {
+		if (is_capture)
+			mixart_update_capture_stream_level(chip, is_aes);
+		else
+			mixart_update_playback_stream_level(chip, is_aes, idx);
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_digital, -10950, 50, 0);
+
+static struct snd_kcontrol_new snd_mixart_pcm_vol =
+{
+	.iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	/* name will be filled later */
+	/* count will be filled later */
+	.info =         mixart_digital_vol_info,		/* shared */
+	.get =          mixart_pcm_vol_get,
+	.put =          mixart_pcm_vol_put,
+	.tlv = { .p = db_scale_digital },
+};
+
+
+static int mixart_pcm_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */
+	snd_BUG_ON(idx >= MIXART_PLAYBACK_STREAMS);
+	mutex_lock(&chip->mgr->mixer_mutex);
+	if(kcontrol->private_value & MIXART_VOL_AES_MASK)	/* AES playback */
+		idx += MIXART_PLAYBACK_STREAMS;
+	ucontrol->value.integer.value[0] = chip->digital_playback_active[idx][0];
+	ucontrol->value.integer.value[1] = chip->digital_playback_active[idx][1];
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int mixart_pcm_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int is_aes = kcontrol->private_value & MIXART_VOL_AES_MASK;
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */
+	int i, j;
+	snd_BUG_ON(idx >= MIXART_PLAYBACK_STREAMS);
+	mutex_lock(&chip->mgr->mixer_mutex);
+	j = idx;
+	if (is_aes)
+		j += MIXART_PLAYBACK_STREAMS;
+	for (i = 0; i < 2; i++) {
+		if (chip->digital_playback_active[j][i] !=
+		    ucontrol->value.integer.value[i]) {
+			chip->digital_playback_active[j][i] =
+				!!ucontrol->value.integer.value[i];
+			changed = 1;
+		}
+	}
+	if (changed)
+		mixart_update_playback_stream_level(chip, is_aes, idx);
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new mixart_control_pcm_switch = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	/* name will be filled later */
+	.count =        MIXART_PLAYBACK_STREAMS,
+	.info =         mixart_sw_info,		/* shared */
+	.get =          mixart_pcm_sw_get,
+	.put =          mixart_pcm_sw_put
+};
+
+static int mixart_update_monitoring(struct snd_mixart* chip, int channel)
+{
+	int err;
+	struct mixart_msg request;
+	struct mixart_set_out_audio_level audio_level;
+	u32 resp;
+
+	if(chip->pipe_out_ana.status == PIPE_UNDEFINED)
+		return -EINVAL; /* no pipe defined */
+
+	if(!channel)	request.uid = chip->pipe_out_ana.uid_left_connector;
+	else		request.uid = chip->pipe_out_ana.uid_right_connector;
+	request.message_id = MSG_CONNECTOR_SET_OUT_AUDIO_LEVEL;
+	request.data = &audio_level;
+	request.size = sizeof(audio_level);
+
+	memset(&audio_level, 0, sizeof(audio_level));
+	audio_level.valid_mask1 = MIXART_AUDIO_LEVEL_MONITOR_MASK | MIXART_AUDIO_LEVEL_MUTE_M1_MASK;
+	audio_level.monitor_level = mixart_digital_level[chip->monitoring_volume[channel!=0]];
+	audio_level.monitor_mute1 = !chip->monitoring_active[channel!=0];
+
+	err = snd_mixart_send_msg(chip->mgr, &request, sizeof(resp), &resp);
+	if((err<0) || resp) {
+		snd_printk(KERN_DEBUG "error MSG_CONNECTOR_SET_OUT_AUDIO_LEVEL card(%d) resp(%x)\n", chip->chip_idx, resp);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * monitoring level control
+ */
+
+static int mixart_monitor_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&chip->mgr->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->monitoring_volume[0];
+	ucontrol->value.integer.value[1] = chip->monitoring_volume[1];
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int mixart_monitor_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int i;
+	mutex_lock(&chip->mgr->mixer_mutex);
+	for (i = 0; i < 2; i++) {
+		if (chip->monitoring_volume[i] !=
+		    ucontrol->value.integer.value[i]) {
+			chip->monitoring_volume[i] =
+				!!ucontrol->value.integer.value[i];
+			mixart_update_monitoring(chip, i);
+			changed = 1;
+		}
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new mixart_control_monitor_vol = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.name =         "Monitoring Volume",
+	.info =		mixart_digital_vol_info,		/* shared */
+	.get =		mixart_monitor_vol_get,
+	.put =		mixart_monitor_vol_put,
+	.tlv = { .p = db_scale_digital },
+};
+
+/*
+ * monitoring switch control
+ */
+
+static int mixart_monitor_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&chip->mgr->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->monitoring_active[0];
+	ucontrol->value.integer.value[1] = chip->monitoring_active[1];
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int mixart_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_mixart *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int i;
+	mutex_lock(&chip->mgr->mixer_mutex);
+	for (i = 0; i < 2; i++) {
+		if (chip->monitoring_active[i] !=
+		    ucontrol->value.integer.value[i]) {
+			chip->monitoring_active[i] =
+				!!ucontrol->value.integer.value[i];
+			changed |= (1<<i); /* mask 0x01 ans 0x02 */
+		}
+	}
+	if (changed) {
+		/* allocate or release resources for monitoring */
+		int allocate = chip->monitoring_active[0] ||
+			chip->monitoring_active[1];
+		if (allocate) {
+			/* allocate the playback pipe for monitoring */
+			snd_mixart_add_ref_pipe(chip, MIXART_PCM_ANALOG, 0, 1);
+			/* allocate the capture pipe for monitoring */
+			snd_mixart_add_ref_pipe(chip, MIXART_PCM_ANALOG, 1, 1);
+		}
+		if (changed & 0x01)
+			mixart_update_monitoring(chip, 0);
+		if (changed & 0x02)
+			mixart_update_monitoring(chip, 1);
+		if (!allocate) {
+			/* release the capture pipe for monitoring */
+			snd_mixart_kill_ref_pipe(chip->mgr,
+						 &chip->pipe_in_ana, 1);
+			/* release the playback pipe for monitoring */
+			snd_mixart_kill_ref_pipe(chip->mgr,
+						 &chip->pipe_out_ana, 1);
+		}
+	}
+
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return (changed != 0);
+}
+
+static struct snd_kcontrol_new mixart_control_monitor_sw = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Monitoring Switch",
+	.info =         mixart_sw_info,		/* shared */
+	.get =          mixart_monitor_sw_get,
+	.put =          mixart_monitor_sw_put
+};
+
+
+static void mixart_reset_audio_levels(struct snd_mixart *chip)
+{
+	/* analog volumes can be set even if there is no pipe */
+	mixart_update_analog_audio_level(chip, 0);
+	/* analog levels for capture only on the first two chips */
+	if(chip->chip_idx < 2) {
+		mixart_update_analog_audio_level(chip, 1);
+	}
+	return;
+}
+
+
+int snd_mixart_create_mixer(struct mixart_mgr *mgr)
+{
+	struct snd_mixart *chip;
+	int err, i;
+
+	mutex_init(&mgr->mixer_mutex); /* can be in another place */
+
+	for(i=0; i<mgr->num_cards; i++) {
+		struct snd_kcontrol_new temp;
+		chip = mgr->chip[i];
+
+		/* analog output level control */
+		temp = mixart_control_analog_level;
+		temp.name = "Master Playback Volume";
+		temp.private_value = 0; /* playback */
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+		/* output mute controls */
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&mixart_control_output_switch, chip))) < 0)
+			return err;
+
+		/* analog input level control only on first two chips !*/
+		if(i<2) {
+			temp = mixart_control_analog_level;
+			temp.name = "Master Capture Volume";
+			temp.private_value = 1; /* capture */
+			if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+				return err;
+		}
+
+		temp = snd_mixart_pcm_vol;
+		temp.name = "PCM Playback Volume";
+		temp.count = MIXART_PLAYBACK_STREAMS;
+		temp.private_value = 0; /* playback analog */
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+
+		temp.name = "PCM Capture Volume";
+		temp.count = 1;
+		temp.private_value = MIXART_VOL_REC_MASK; /* capture analog */
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+
+		if(mgr->board_type == MIXART_DAUGHTER_TYPE_AES) {
+			temp.name = "AES Playback Volume";
+			temp.count = MIXART_PLAYBACK_STREAMS;
+			temp.private_value = MIXART_VOL_AES_MASK; /* playback AES/EBU */
+			if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+				return err;
+
+			temp.name = "AES Capture Volume";
+			temp.count = 0;
+			temp.private_value = MIXART_VOL_REC_MASK | MIXART_VOL_AES_MASK; /* capture AES/EBU */
+			if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+				return err;
+		}
+		temp = mixart_control_pcm_switch;
+		temp.name = "PCM Playback Switch";
+		temp.private_value = 0; /* playback analog */
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+			return err;
+
+		if(mgr->board_type == MIXART_DAUGHTER_TYPE_AES) {
+			temp.name = "AES Playback Switch";
+			temp.private_value = MIXART_VOL_AES_MASK; /* playback AES/EBU */
+			if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&temp, chip))) < 0)
+				return err;
+		}
+
+		/* monitoring */
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&mixart_control_monitor_vol, chip))) < 0)
+			return err;
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&mixart_control_monitor_sw, chip))) < 0)
+			return err;
+
+		/* init all mixer data and program the master volumes/switches */
+		mixart_reset_audio_levels(chip);
+	}
+	return 0;
+}
diff --git a/linux/sound/pci/mixart/mixart_mixer.h b/linux/sound/pci/mixart/mixart_mixer.h
new file mode 100644
index 0000000..04aa24e
--- /dev/null
+++ b/linux/sound/pci/mixart/mixart_mixer.h
@@ -0,0 +1,31 @@
+/*
+ * Driver for Digigram miXart soundcards
+ *
+ * include file for mixer
+ *
+ * Copyright (c) 2003 by Digigram <alsa@digigram.com>
+ *
+ *   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 __SOUND_MIXART_MIXER_H
+#define __SOUND_MIXART_MIXER_H
+
+/* exported */
+int mixart_update_playback_stream_level(struct snd_mixart* chip, int is_aes, int idx);
+int mixart_update_capture_stream_level(struct snd_mixart* chip, int is_aes);
+int snd_mixart_create_mixer(struct mixart_mgr* mgr);
+
+#endif /* __SOUND_MIXART_MIXER_H */
diff --git a/linux/sound/pci/nm256/Makefile b/linux/sound/pci/nm256/Makefile
new file mode 100644
index 0000000..a1bd44f
--- /dev/null
+++ b/linux/sound/pci/nm256/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-nm256-objs := nm256.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_NM256) += snd-nm256.o
diff --git a/linux/sound/pci/nm256/nm256.c b/linux/sound/pci/nm256/nm256.c
new file mode 100644
index 0000000..5a60492
--- /dev/null
+++ b/linux/sound/pci/nm256/nm256.c
@@ -0,0 +1,1768 @@
+/* 
+ * Driver for NeoMagic 256AV and 256ZX chipsets.
+ * Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>
+ *
+ * Based on nm256_audio.c OSS driver in linux kernel.
+ * The original author of OSS nm256 driver wishes to remain anonymous,
+ * so I just put my acknoledgment to him/her here.
+ * The original author's web page is found at
+ *	http://www.uglx.org/sony.html
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+  
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+
+#define CARD_NAME "NeoMagic 256AV/ZX"
+#define DRIVER_NAME "NM256"
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("NeoMagic NM256AV/ZX");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{NeoMagic,NM256AV},"
+		"{NeoMagic,NM256ZX}}");
+
+/*
+ * some compile conditions.
+ */
+
+static int index = SNDRV_DEFAULT_IDX1;	/* Index */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+static int playback_bufsize = 16;
+static int capture_bufsize = 16;
+static int force_ac97;			/* disabled as default */
+static int buffer_top;			/* not specified */
+static int use_cache;			/* disabled */
+static int vaio_hack;			/* disabled */
+static int reset_workaround;
+static int reset_workaround_2;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param(playback_bufsize, int, 0444);
+MODULE_PARM_DESC(playback_bufsize, "DAC frame size in kB for " CARD_NAME " soundcard.");
+module_param(capture_bufsize, int, 0444);
+MODULE_PARM_DESC(capture_bufsize, "ADC frame size in kB for " CARD_NAME " soundcard.");
+module_param(force_ac97, bool, 0444);
+MODULE_PARM_DESC(force_ac97, "Force to use AC97 codec for " CARD_NAME " soundcard.");
+module_param(buffer_top, int, 0444);
+MODULE_PARM_DESC(buffer_top, "Set the top address of audio buffer for " CARD_NAME " soundcard.");
+module_param(use_cache, bool, 0444);
+MODULE_PARM_DESC(use_cache, "Enable the cache for coefficient table access.");
+module_param(vaio_hack, bool, 0444);
+MODULE_PARM_DESC(vaio_hack, "Enable workaround for Sony VAIO notebooks.");
+module_param(reset_workaround, bool, 0444);
+MODULE_PARM_DESC(reset_workaround, "Enable AC97 RESET workaround for some laptops.");
+module_param(reset_workaround_2, bool, 0444);
+MODULE_PARM_DESC(reset_workaround_2, "Enable extended AC97 RESET workaround for some other laptops.");
+
+/* just for backward compatibility */
+static int enable;
+module_param(enable, bool, 0444);
+
+
+
+/*
+ * hw definitions
+ */
+
+/* The BIOS signature. */
+#define NM_SIGNATURE 0x4e4d0000
+/* Signature mask. */
+#define NM_SIG_MASK 0xffff0000
+
+/* Size of the second memory area. */
+#define NM_PORT2_SIZE 4096
+
+/* The base offset of the mixer in the second memory area. */
+#define NM_MIXER_OFFSET 0x600
+
+/* The maximum size of a coefficient entry. */
+#define NM_MAX_PLAYBACK_COEF_SIZE	0x5000
+#define NM_MAX_RECORD_COEF_SIZE		0x1260
+
+/* The interrupt register. */
+#define NM_INT_REG 0xa04
+/* And its bits. */
+#define NM_PLAYBACK_INT 0x40
+#define NM_RECORD_INT 0x100
+#define NM_MISC_INT_1 0x4000
+#define NM_MISC_INT_2 0x1
+#define NM_ACK_INT(chip, X) snd_nm256_writew(chip, NM_INT_REG, (X) << 1)
+
+/* The AV's "mixer ready" status bit and location. */
+#define NM_MIXER_STATUS_OFFSET 0xa04
+#define NM_MIXER_READY_MASK 0x0800
+#define NM_MIXER_PRESENCE 0xa06
+#define NM_PRESENCE_MASK 0x0050
+#define NM_PRESENCE_VALUE 0x0040
+
+/*
+ * For the ZX.  It uses the same interrupt register, but it holds 32
+ * bits instead of 16.
+ */
+#define NM2_PLAYBACK_INT 0x10000
+#define NM2_RECORD_INT 0x80000
+#define NM2_MISC_INT_1 0x8
+#define NM2_MISC_INT_2 0x2
+#define NM2_ACK_INT(chip, X) snd_nm256_writel(chip, NM_INT_REG, (X))
+
+/* The ZX's "mixer ready" status bit and location. */
+#define NM2_MIXER_STATUS_OFFSET 0xa06
+#define NM2_MIXER_READY_MASK 0x0800
+
+/* The playback registers start from here. */
+#define NM_PLAYBACK_REG_OFFSET 0x0
+/* The record registers start from here. */
+#define NM_RECORD_REG_OFFSET 0x200
+
+/* The rate register is located 2 bytes from the start of the register area. */
+#define NM_RATE_REG_OFFSET 2
+
+/* Mono/stereo flag, number of bits on playback, and rate mask. */
+#define NM_RATE_STEREO 1
+#define NM_RATE_BITS_16 2
+#define NM_RATE_MASK 0xf0
+
+/* Playback enable register. */
+#define NM_PLAYBACK_ENABLE_REG (NM_PLAYBACK_REG_OFFSET + 0x1)
+#define NM_PLAYBACK_ENABLE_FLAG 1
+#define NM_PLAYBACK_ONESHOT 2
+#define NM_PLAYBACK_FREERUN 4
+
+/* Mutes the audio output. */
+#define NM_AUDIO_MUTE_REG (NM_PLAYBACK_REG_OFFSET + 0x18)
+#define NM_AUDIO_MUTE_LEFT 0x8000
+#define NM_AUDIO_MUTE_RIGHT 0x0080
+
+/* Recording enable register. */
+#define NM_RECORD_ENABLE_REG (NM_RECORD_REG_OFFSET + 0)
+#define NM_RECORD_ENABLE_FLAG 1
+#define NM_RECORD_FREERUN 2
+
+/* coefficient buffer pointer */
+#define NM_COEFF_START_OFFSET	0x1c
+#define NM_COEFF_END_OFFSET	0x20
+
+/* DMA buffer offsets */
+#define NM_RBUFFER_START (NM_RECORD_REG_OFFSET + 0x4)
+#define NM_RBUFFER_END   (NM_RECORD_REG_OFFSET + 0x10)
+#define NM_RBUFFER_WMARK (NM_RECORD_REG_OFFSET + 0xc)
+#define NM_RBUFFER_CURRP (NM_RECORD_REG_OFFSET + 0x8)
+
+#define NM_PBUFFER_START (NM_PLAYBACK_REG_OFFSET + 0x4)
+#define NM_PBUFFER_END   (NM_PLAYBACK_REG_OFFSET + 0x14)
+#define NM_PBUFFER_WMARK (NM_PLAYBACK_REG_OFFSET + 0xc)
+#define NM_PBUFFER_CURRP (NM_PLAYBACK_REG_OFFSET + 0x8)
+
+struct nm256_stream {
+
+	struct nm256 *chip;
+	struct snd_pcm_substream *substream;
+	int running;
+	int suspended;
+	
+	u32 buf;	/* offset from chip->buffer */
+	int bufsize;	/* buffer size in bytes */
+	void __iomem *bufptr;		/* mapped pointer */
+	unsigned long bufptr_addr;	/* physical address of the mapped pointer */
+
+	int dma_size;		/* buffer size of the substream in bytes */
+	int period_size;	/* period size in bytes */
+	int periods;		/* # of periods */
+	int shift;		/* bit shifts */
+	int cur_period;		/* current period # */
+
+};
+
+struct nm256 {
+	
+	struct snd_card *card;
+
+	void __iomem *cport;		/* control port */
+	struct resource *res_cport;	/* its resource */
+	unsigned long cport_addr;	/* physical address */
+
+	void __iomem *buffer;		/* buffer */
+	struct resource *res_buffer;	/* its resource */
+	unsigned long buffer_addr;	/* buffer phyiscal address */
+
+	u32 buffer_start;		/* start offset from pci resource 0 */
+	u32 buffer_end;			/* end offset */
+	u32 buffer_size;		/* total buffer size */
+
+	u32 all_coeff_buf;		/* coefficient buffer */
+	u32 coeff_buf[2];		/* coefficient buffer for each stream */
+
+	unsigned int coeffs_current: 1;	/* coeff. table is loaded? */
+	unsigned int use_cache: 1;	/* use one big coef. table */
+	unsigned int reset_workaround: 1; /* Workaround for some laptops to avoid freeze */
+	unsigned int reset_workaround_2: 1; /* Extended workaround for some other laptops to avoid freeze */
+	unsigned int in_resume: 1;
+
+	int mixer_base;			/* register offset of ac97 mixer */
+	int mixer_status_offset;	/* offset of mixer status reg. */
+	int mixer_status_mask;		/* bit mask to test the mixer status */
+
+	int irq;
+	int irq_acks;
+	irq_handler_t interrupt;
+	int badintrcount;		/* counter to check bogus interrupts */
+	struct mutex irq_mutex;
+
+	struct nm256_stream streams[2];
+
+	struct snd_ac97 *ac97;
+	unsigned short *ac97_regs; /* register caches, only for valid regs */
+
+	struct snd_pcm *pcm;
+
+	struct pci_dev *pci;
+
+	spinlock_t reg_lock;
+
+};
+
+
+/*
+ * include coefficient table
+ */
+#include "nm256_coef.c"
+
+
+/*
+ * PCI ids
+ */
+static DEFINE_PCI_DEVICE_TABLE(snd_nm256_ids) = {
+	{PCI_VDEVICE(NEOMAGIC, PCI_DEVICE_ID_NEOMAGIC_NM256AV_AUDIO), 0},
+	{PCI_VDEVICE(NEOMAGIC, PCI_DEVICE_ID_NEOMAGIC_NM256ZX_AUDIO), 0},
+	{PCI_VDEVICE(NEOMAGIC, PCI_DEVICE_ID_NEOMAGIC_NM256XL_PLUS_AUDIO), 0},
+	{0,},
+};
+
+MODULE_DEVICE_TABLE(pci, snd_nm256_ids);
+
+
+/*
+ * lowlvel stuffs
+ */
+
+static inline u8
+snd_nm256_readb(struct nm256 *chip, int offset)
+{
+	return readb(chip->cport + offset);
+}
+
+static inline u16
+snd_nm256_readw(struct nm256 *chip, int offset)
+{
+	return readw(chip->cport + offset);
+}
+
+static inline u32
+snd_nm256_readl(struct nm256 *chip, int offset)
+{
+	return readl(chip->cport + offset);
+}
+
+static inline void
+snd_nm256_writeb(struct nm256 *chip, int offset, u8 val)
+{
+	writeb(val, chip->cport + offset);
+}
+
+static inline void
+snd_nm256_writew(struct nm256 *chip, int offset, u16 val)
+{
+	writew(val, chip->cport + offset);
+}
+
+static inline void
+snd_nm256_writel(struct nm256 *chip, int offset, u32 val)
+{
+	writel(val, chip->cport + offset);
+}
+
+static inline void
+snd_nm256_write_buffer(struct nm256 *chip, void *src, int offset, int size)
+{
+	offset -= chip->buffer_start;
+#ifdef CONFIG_SND_DEBUG
+	if (offset < 0 || offset >= chip->buffer_size) {
+		snd_printk(KERN_ERR "write_buffer invalid offset = %d size = %d\n",
+			   offset, size);
+		return;
+	}
+#endif
+	memcpy_toio(chip->buffer + offset, src, size);
+}
+
+/*
+ * coefficient handlers -- what a magic!
+ */
+
+static u16
+snd_nm256_get_start_offset(int which)
+{
+	u16 offset = 0;
+	while (which-- > 0)
+		offset += coefficient_sizes[which];
+	return offset;
+}
+
+static void
+snd_nm256_load_one_coefficient(struct nm256 *chip, int stream, u32 port, int which)
+{
+	u32 coeff_buf = chip->coeff_buf[stream];
+	u16 offset = snd_nm256_get_start_offset(which);
+	u16 size = coefficient_sizes[which];
+
+	snd_nm256_write_buffer(chip, coefficients + offset, coeff_buf, size);
+	snd_nm256_writel(chip, port, coeff_buf);
+	/* ???  Record seems to behave differently than playback.  */
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+		size--;
+	snd_nm256_writel(chip, port + 4, coeff_buf + size);
+}
+
+static void
+snd_nm256_load_coefficient(struct nm256 *chip, int stream, int number)
+{
+	/* The enable register for the specified engine.  */
+	u32 poffset = (stream == SNDRV_PCM_STREAM_CAPTURE ?
+		       NM_RECORD_ENABLE_REG : NM_PLAYBACK_ENABLE_REG);
+	u32 addr = NM_COEFF_START_OFFSET;
+
+	addr += (stream == SNDRV_PCM_STREAM_CAPTURE ?
+		 NM_RECORD_REG_OFFSET : NM_PLAYBACK_REG_OFFSET);
+
+	if (snd_nm256_readb(chip, poffset) & 1) {
+		snd_printd("NM256: Engine was enabled while loading coefficients!\n");
+		return;
+	}
+
+	/* The recording engine uses coefficient values 8-15.  */
+	number &= 7;
+	if (stream == SNDRV_PCM_STREAM_CAPTURE)
+		number += 8;
+
+	if (! chip->use_cache) {
+		snd_nm256_load_one_coefficient(chip, stream, addr, number);
+		return;
+	}
+	if (! chip->coeffs_current) {
+		snd_nm256_write_buffer(chip, coefficients, chip->all_coeff_buf,
+				       NM_TOTAL_COEFF_COUNT * 4);
+		chip->coeffs_current = 1;
+	} else {
+		u32 base = chip->all_coeff_buf;
+		u32 offset = snd_nm256_get_start_offset(number);
+		u32 end_offset = offset + coefficient_sizes[number];
+		snd_nm256_writel(chip, addr, base + offset);
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+			end_offset--;
+		snd_nm256_writel(chip, addr + 4, base + end_offset);
+	}
+}
+
+
+/* The actual rates supported by the card. */
+static unsigned int samplerates[8] = {
+	8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000,
+};
+static struct snd_pcm_hw_constraint_list constraints_rates = {
+	.count = ARRAY_SIZE(samplerates), 
+	.list = samplerates,
+	.mask = 0,
+};
+
+/*
+ * return the index of the target rate
+ */
+static int
+snd_nm256_fixed_rate(unsigned int rate)
+{
+	unsigned int i;
+	for (i = 0; i < ARRAY_SIZE(samplerates); i++) {
+		if (rate == samplerates[i])
+			return i;
+	}
+	snd_BUG();
+	return 0;
+}
+
+/*
+ * set sample rate and format
+ */
+static void
+snd_nm256_set_format(struct nm256 *chip, struct nm256_stream *s,
+		     struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int rate_index = snd_nm256_fixed_rate(runtime->rate);
+	unsigned char ratebits = (rate_index << 4) & NM_RATE_MASK;
+
+	s->shift = 0;
+	if (snd_pcm_format_width(runtime->format) == 16) {
+		ratebits |= NM_RATE_BITS_16;
+		s->shift++;
+	}
+	if (runtime->channels > 1) {
+		ratebits |= NM_RATE_STEREO;
+		s->shift++;
+	}
+
+	runtime->rate = samplerates[rate_index];
+
+	switch (substream->stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		snd_nm256_load_coefficient(chip, 0, rate_index); /* 0 = playback */
+		snd_nm256_writeb(chip,
+				 NM_PLAYBACK_REG_OFFSET + NM_RATE_REG_OFFSET,
+				 ratebits);
+		break;
+	case SNDRV_PCM_STREAM_CAPTURE:
+		snd_nm256_load_coefficient(chip, 1, rate_index); /* 1 = record */
+		snd_nm256_writeb(chip,
+				 NM_RECORD_REG_OFFSET + NM_RATE_REG_OFFSET,
+				 ratebits);
+		break;
+	}
+}
+
+/* acquire interrupt */
+static int snd_nm256_acquire_irq(struct nm256 *chip)
+{
+	mutex_lock(&chip->irq_mutex);
+	if (chip->irq < 0) {
+		if (request_irq(chip->pci->irq, chip->interrupt, IRQF_SHARED,
+				chip->card->driver, chip)) {
+			snd_printk(KERN_ERR "unable to grab IRQ %d\n", chip->pci->irq);
+			mutex_unlock(&chip->irq_mutex);
+			return -EBUSY;
+		}
+		chip->irq = chip->pci->irq;
+	}
+	chip->irq_acks++;
+	mutex_unlock(&chip->irq_mutex);
+	return 0;
+}
+
+/* release interrupt */
+static void snd_nm256_release_irq(struct nm256 *chip)
+{
+	mutex_lock(&chip->irq_mutex);
+	if (chip->irq_acks > 0)
+		chip->irq_acks--;
+	if (chip->irq_acks == 0 && chip->irq >= 0) {
+		free_irq(chip->irq, chip);
+		chip->irq = -1;
+	}
+	mutex_unlock(&chip->irq_mutex);
+}
+
+/*
+ * start / stop
+ */
+
+/* update the watermark (current period) */
+static void snd_nm256_pcm_mark(struct nm256 *chip, struct nm256_stream *s, int reg)
+{
+	s->cur_period++;
+	s->cur_period %= s->periods;
+	snd_nm256_writel(chip, reg, s->buf + s->cur_period * s->period_size);
+}
+
+#define snd_nm256_playback_mark(chip, s) snd_nm256_pcm_mark(chip, s, NM_PBUFFER_WMARK)
+#define snd_nm256_capture_mark(chip, s)  snd_nm256_pcm_mark(chip, s, NM_RBUFFER_WMARK)
+
+static void
+snd_nm256_playback_start(struct nm256 *chip, struct nm256_stream *s,
+			 struct snd_pcm_substream *substream)
+{
+	/* program buffer pointers */
+	snd_nm256_writel(chip, NM_PBUFFER_START, s->buf);
+	snd_nm256_writel(chip, NM_PBUFFER_END, s->buf + s->dma_size - (1 << s->shift));
+	snd_nm256_writel(chip, NM_PBUFFER_CURRP, s->buf);
+	snd_nm256_playback_mark(chip, s);
+
+	/* Enable playback engine and interrupts. */
+	snd_nm256_writeb(chip, NM_PLAYBACK_ENABLE_REG,
+			 NM_PLAYBACK_ENABLE_FLAG | NM_PLAYBACK_FREERUN);
+	/* Enable both channels. */
+	snd_nm256_writew(chip, NM_AUDIO_MUTE_REG, 0x0);
+}
+
+static void
+snd_nm256_capture_start(struct nm256 *chip, struct nm256_stream *s,
+			struct snd_pcm_substream *substream)
+{
+	/* program buffer pointers */
+	snd_nm256_writel(chip, NM_RBUFFER_START, s->buf);
+	snd_nm256_writel(chip, NM_RBUFFER_END, s->buf + s->dma_size);
+	snd_nm256_writel(chip, NM_RBUFFER_CURRP, s->buf);
+	snd_nm256_capture_mark(chip, s);
+
+	/* Enable playback engine and interrupts. */
+	snd_nm256_writeb(chip, NM_RECORD_ENABLE_REG,
+			 NM_RECORD_ENABLE_FLAG | NM_RECORD_FREERUN);
+}
+
+/* Stop the play engine. */
+static void
+snd_nm256_playback_stop(struct nm256 *chip)
+{
+	/* Shut off sound from both channels. */
+	snd_nm256_writew(chip, NM_AUDIO_MUTE_REG,
+			 NM_AUDIO_MUTE_LEFT | NM_AUDIO_MUTE_RIGHT);
+	/* Disable play engine. */
+	snd_nm256_writeb(chip, NM_PLAYBACK_ENABLE_REG, 0);
+}
+
+static void
+snd_nm256_capture_stop(struct nm256 *chip)
+{
+	/* Disable recording engine. */
+	snd_nm256_writeb(chip, NM_RECORD_ENABLE_REG, 0);
+}
+
+static int
+snd_nm256_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct nm256 *chip = snd_pcm_substream_chip(substream);
+	struct nm256_stream *s = substream->runtime->private_data;
+	int err = 0;
+
+	if (snd_BUG_ON(!s))
+		return -ENXIO;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_RESUME:
+		s->suspended = 0;
+		/* fallthru */
+	case SNDRV_PCM_TRIGGER_START:
+		if (! s->running) {
+			snd_nm256_playback_start(chip, s, substream);
+			s->running = 1;
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		s->suspended = 1;
+		/* fallthru */
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (s->running) {
+			snd_nm256_playback_stop(chip);
+			s->running = 0;
+		}
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	spin_unlock(&chip->reg_lock);
+	return err;
+}
+
+static int
+snd_nm256_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct nm256 *chip = snd_pcm_substream_chip(substream);
+	struct nm256_stream *s = substream->runtime->private_data;
+	int err = 0;
+
+	if (snd_BUG_ON(!s))
+		return -ENXIO;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (! s->running) {
+			snd_nm256_capture_start(chip, s, substream);
+			s->running = 1;
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (s->running) {
+			snd_nm256_capture_stop(chip);
+			s->running = 0;
+		}
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	spin_unlock(&chip->reg_lock);
+	return err;
+}
+
+
+/*
+ * prepare playback/capture channel
+ */
+static int snd_nm256_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct nm256 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nm256_stream *s = runtime->private_data;
+
+	if (snd_BUG_ON(!s))
+		return -ENXIO;
+	s->dma_size = frames_to_bytes(runtime, substream->runtime->buffer_size);
+	s->period_size = frames_to_bytes(runtime, substream->runtime->period_size);
+	s->periods = substream->runtime->periods;
+	s->cur_period = 0;
+
+	spin_lock_irq(&chip->reg_lock);
+	s->running = 0;
+	snd_nm256_set_format(chip, s, substream);
+	spin_unlock_irq(&chip->reg_lock);
+
+	return 0;
+}
+
+
+/*
+ * get the current pointer
+ */
+static snd_pcm_uframes_t
+snd_nm256_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct nm256 *chip = snd_pcm_substream_chip(substream);
+	struct nm256_stream *s = substream->runtime->private_data;
+	unsigned long curp;
+
+	if (snd_BUG_ON(!s))
+		return 0;
+	curp = snd_nm256_readl(chip, NM_PBUFFER_CURRP) - (unsigned long)s->buf;
+	curp %= s->dma_size;
+	return bytes_to_frames(substream->runtime, curp);
+}
+
+static snd_pcm_uframes_t
+snd_nm256_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct nm256 *chip = snd_pcm_substream_chip(substream);
+	struct nm256_stream *s = substream->runtime->private_data;
+	unsigned long curp;
+
+	if (snd_BUG_ON(!s))
+		return 0;
+	curp = snd_nm256_readl(chip, NM_RBUFFER_CURRP) - (unsigned long)s->buf;
+	curp %= s->dma_size;	
+	return bytes_to_frames(substream->runtime, curp);
+}
+
+/* Remapped I/O space can be accessible as pointer on i386 */
+/* This might be changed in the future */
+#ifndef __i386__
+/*
+ * silence / copy for playback
+ */
+static int
+snd_nm256_playback_silence(struct snd_pcm_substream *substream,
+			   int channel, /* not used (interleaved data) */
+			   snd_pcm_uframes_t pos,
+			   snd_pcm_uframes_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nm256_stream *s = runtime->private_data;
+	count = frames_to_bytes(runtime, count);
+	pos = frames_to_bytes(runtime, pos);
+	memset_io(s->bufptr + pos, 0, count);
+	return 0;
+}
+
+static int
+snd_nm256_playback_copy(struct snd_pcm_substream *substream,
+			int channel, /* not used (interleaved data) */
+			snd_pcm_uframes_t pos,
+			void __user *src,
+			snd_pcm_uframes_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nm256_stream *s = runtime->private_data;
+	count = frames_to_bytes(runtime, count);
+	pos = frames_to_bytes(runtime, pos);
+	if (copy_from_user_toio(s->bufptr + pos, src, count))
+		return -EFAULT;
+	return 0;
+}
+
+/*
+ * copy to user
+ */
+static int
+snd_nm256_capture_copy(struct snd_pcm_substream *substream,
+		       int channel, /* not used (interleaved data) */
+		       snd_pcm_uframes_t pos,
+		       void __user *dst,
+		       snd_pcm_uframes_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nm256_stream *s = runtime->private_data;
+	count = frames_to_bytes(runtime, count);
+	pos = frames_to_bytes(runtime, pos);
+	if (copy_to_user_fromio(dst, s->bufptr + pos, count))
+		return -EFAULT;
+	return 0;
+}
+
+#endif /* !__i386__ */
+
+
+/*
+ * update playback/capture watermarks
+ */
+
+/* spinlock held! */
+static void
+snd_nm256_playback_update(struct nm256 *chip)
+{
+	struct nm256_stream *s;
+
+	s = &chip->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	if (s->running && s->substream) {
+		spin_unlock(&chip->reg_lock);
+		snd_pcm_period_elapsed(s->substream);
+		spin_lock(&chip->reg_lock);
+		snd_nm256_playback_mark(chip, s);
+	}
+}
+
+/* spinlock held! */
+static void
+snd_nm256_capture_update(struct nm256 *chip)
+{
+	struct nm256_stream *s;
+
+	s = &chip->streams[SNDRV_PCM_STREAM_CAPTURE];
+	if (s->running && s->substream) {
+		spin_unlock(&chip->reg_lock);
+		snd_pcm_period_elapsed(s->substream);
+		spin_lock(&chip->reg_lock);
+		snd_nm256_capture_mark(chip, s);
+	}
+}
+
+/*
+ * hardware info
+ */
+static struct snd_pcm_hardware snd_nm256_playback =
+{
+	.info =			SNDRV_PCM_INFO_MMAP_IOMEM |SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				/*SNDRV_PCM_INFO_PAUSE |*/
+				SNDRV_PCM_INFO_RESUME,
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_KNOT/*24k*/ | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		8000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.periods_min =		2,
+	.periods_max =		1024,
+	.buffer_bytes_max =	128 * 1024,
+	.period_bytes_min =	256,
+	.period_bytes_max =	128 * 1024,
+};
+
+static struct snd_pcm_hardware snd_nm256_capture =
+{
+	.info =			SNDRV_PCM_INFO_MMAP_IOMEM | SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				/*SNDRV_PCM_INFO_PAUSE |*/
+				SNDRV_PCM_INFO_RESUME,
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_KNOT/*24k*/ | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		8000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.periods_min =		2,
+	.periods_max =		1024,
+	.buffer_bytes_max =	128 * 1024,
+	.period_bytes_min =	256,
+	.period_bytes_max =	128 * 1024,
+};
+
+
+/* set dma transfer size */
+static int snd_nm256_pcm_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *hw_params)
+{
+	/* area and addr are already set and unchanged */
+	substream->runtime->dma_bytes = params_buffer_bytes(hw_params);
+	return 0;
+}
+
+/*
+ * open
+ */
+static void snd_nm256_setup_stream(struct nm256 *chip, struct nm256_stream *s,
+				   struct snd_pcm_substream *substream,
+				   struct snd_pcm_hardware *hw_ptr)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	s->running = 0;
+	runtime->hw = *hw_ptr;
+	runtime->hw.buffer_bytes_max = s->bufsize;
+	runtime->hw.period_bytes_max = s->bufsize / 2;
+	runtime->dma_area = (void __force *) s->bufptr;
+	runtime->dma_addr = s->bufptr_addr;
+	runtime->dma_bytes = s->bufsize;
+	runtime->private_data = s;
+	s->substream = substream;
+
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				   &constraints_rates);
+}
+
+static int
+snd_nm256_playback_open(struct snd_pcm_substream *substream)
+{
+	struct nm256 *chip = snd_pcm_substream_chip(substream);
+
+	if (snd_nm256_acquire_irq(chip) < 0)
+		return -EBUSY;
+	snd_nm256_setup_stream(chip, &chip->streams[SNDRV_PCM_STREAM_PLAYBACK],
+			       substream, &snd_nm256_playback);
+	return 0;
+}
+
+static int
+snd_nm256_capture_open(struct snd_pcm_substream *substream)
+{
+	struct nm256 *chip = snd_pcm_substream_chip(substream);
+
+	if (snd_nm256_acquire_irq(chip) < 0)
+		return -EBUSY;
+	snd_nm256_setup_stream(chip, &chip->streams[SNDRV_PCM_STREAM_CAPTURE],
+			       substream, &snd_nm256_capture);
+	return 0;
+}
+
+/*
+ * close - we don't have to do special..
+ */
+static int
+snd_nm256_playback_close(struct snd_pcm_substream *substream)
+{
+	struct nm256 *chip = snd_pcm_substream_chip(substream);
+
+	snd_nm256_release_irq(chip);
+	return 0;
+}
+
+
+static int
+snd_nm256_capture_close(struct snd_pcm_substream *substream)
+{
+	struct nm256 *chip = snd_pcm_substream_chip(substream);
+
+	snd_nm256_release_irq(chip);
+	return 0;
+}
+
+/*
+ * create a pcm instance
+ */
+static struct snd_pcm_ops snd_nm256_playback_ops = {
+	.open =		snd_nm256_playback_open,
+	.close =	snd_nm256_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_nm256_pcm_hw_params,
+	.prepare =	snd_nm256_pcm_prepare,
+	.trigger =	snd_nm256_playback_trigger,
+	.pointer =	snd_nm256_playback_pointer,
+#ifndef __i386__
+	.copy =		snd_nm256_playback_copy,
+	.silence =	snd_nm256_playback_silence,
+#endif
+	.mmap =		snd_pcm_lib_mmap_iomem,
+};
+
+static struct snd_pcm_ops snd_nm256_capture_ops = {
+	.open =		snd_nm256_capture_open,
+	.close =	snd_nm256_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_nm256_pcm_hw_params,
+	.prepare =	snd_nm256_pcm_prepare,
+	.trigger =	snd_nm256_capture_trigger,
+	.pointer =	snd_nm256_capture_pointer,
+#ifndef __i386__
+	.copy =		snd_nm256_capture_copy,
+#endif
+	.mmap =		snd_pcm_lib_mmap_iomem,
+};
+
+static int __devinit
+snd_nm256_pcm(struct nm256 *chip, int device)
+{
+	struct snd_pcm *pcm;
+	int i, err;
+
+	for (i = 0; i < 2; i++) {
+		struct nm256_stream *s = &chip->streams[i];
+		s->bufptr = chip->buffer + (s->buf - chip->buffer_start);
+		s->bufptr_addr = chip->buffer_addr + (s->buf - chip->buffer_start);
+	}
+
+	err = snd_pcm_new(chip->card, chip->card->driver, device,
+			  1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_nm256_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_nm256_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	chip->pcm = pcm;
+
+	return 0;
+}
+
+
+/* 
+ * Initialize the hardware. 
+ */
+static void
+snd_nm256_init_chip(struct nm256 *chip)
+{
+	/* Reset everything. */
+	snd_nm256_writeb(chip, 0x0, 0x11);
+	snd_nm256_writew(chip, 0x214, 0);
+	/* stop sounds.. */
+	//snd_nm256_playback_stop(chip);
+	//snd_nm256_capture_stop(chip);
+}
+
+
+static irqreturn_t
+snd_nm256_intr_check(struct nm256 *chip)
+{
+	if (chip->badintrcount++ > 1000) {
+		/*
+		 * I'm not sure if the best thing is to stop the card from
+		 * playing or just release the interrupt (after all, we're in
+		 * a bad situation, so doing fancy stuff may not be such a good
+		 * idea).
+		 *
+		 * I worry about the card engine continuing to play noise
+		 * over and over, however--that could become a very
+		 * obnoxious problem.  And we know that when this usually
+		 * happens things are fairly safe, it just means the user's
+		 * inserted a PCMCIA card and someone's spamming us with IRQ 9s.
+		 */
+		if (chip->streams[SNDRV_PCM_STREAM_PLAYBACK].running)
+			snd_nm256_playback_stop(chip);
+		if (chip->streams[SNDRV_PCM_STREAM_CAPTURE].running)
+			snd_nm256_capture_stop(chip);
+		chip->badintrcount = 0;
+		return IRQ_HANDLED;
+	}
+	return IRQ_NONE;
+}
+
+/* 
+ * Handle a potential interrupt for the device referred to by DEV_ID. 
+ *
+ * I don't like the cut-n-paste job here either between the two routines,
+ * but there are sufficient differences between the two interrupt handlers
+ * that parameterizing it isn't all that great either.  (Could use a macro,
+ * I suppose...yucky bleah.)
+ */
+
+static irqreturn_t
+snd_nm256_interrupt(int irq, void *dev_id)
+{
+	struct nm256 *chip = dev_id;
+	u16 status;
+	u8 cbyte;
+
+	status = snd_nm256_readw(chip, NM_INT_REG);
+
+	/* Not ours. */
+	if (status == 0)
+		return snd_nm256_intr_check(chip);
+
+	chip->badintrcount = 0;
+
+	/* Rather boring; check for individual interrupts and process them. */
+
+	spin_lock(&chip->reg_lock);
+	if (status & NM_PLAYBACK_INT) {
+		status &= ~NM_PLAYBACK_INT;
+		NM_ACK_INT(chip, NM_PLAYBACK_INT);
+		snd_nm256_playback_update(chip);
+	}
+
+	if (status & NM_RECORD_INT) {
+		status &= ~NM_RECORD_INT;
+		NM_ACK_INT(chip, NM_RECORD_INT);
+		snd_nm256_capture_update(chip);
+	}
+
+	if (status & NM_MISC_INT_1) {
+		status &= ~NM_MISC_INT_1;
+		NM_ACK_INT(chip, NM_MISC_INT_1);
+		snd_printd("NM256: Got misc interrupt #1\n");
+		snd_nm256_writew(chip, NM_INT_REG, 0x8000);
+		cbyte = snd_nm256_readb(chip, 0x400);
+		snd_nm256_writeb(chip, 0x400, cbyte | 2);
+	}
+
+	if (status & NM_MISC_INT_2) {
+		status &= ~NM_MISC_INT_2;
+		NM_ACK_INT(chip, NM_MISC_INT_2);
+		snd_printd("NM256: Got misc interrupt #2\n");
+		cbyte = snd_nm256_readb(chip, 0x400);
+		snd_nm256_writeb(chip, 0x400, cbyte & ~2);
+	}
+
+	/* Unknown interrupt. */
+	if (status) {
+		snd_printd("NM256: Fire in the hole! Unknown status 0x%x\n",
+			   status);
+		/* Pray. */
+		NM_ACK_INT(chip, status);
+	}
+
+	spin_unlock(&chip->reg_lock);
+	return IRQ_HANDLED;
+}
+
+/*
+ * Handle a potential interrupt for the device referred to by DEV_ID.
+ * This handler is for the 256ZX, and is very similar to the non-ZX
+ * routine.
+ */
+
+static irqreturn_t
+snd_nm256_interrupt_zx(int irq, void *dev_id)
+{
+	struct nm256 *chip = dev_id;
+	u32 status;
+	u8 cbyte;
+
+	status = snd_nm256_readl(chip, NM_INT_REG);
+
+	/* Not ours. */
+	if (status == 0)
+		return snd_nm256_intr_check(chip);
+
+	chip->badintrcount = 0;
+
+	/* Rather boring; check for individual interrupts and process them. */
+
+	spin_lock(&chip->reg_lock);
+	if (status & NM2_PLAYBACK_INT) {
+		status &= ~NM2_PLAYBACK_INT;
+		NM2_ACK_INT(chip, NM2_PLAYBACK_INT);
+		snd_nm256_playback_update(chip);
+	}
+
+	if (status & NM2_RECORD_INT) {
+		status &= ~NM2_RECORD_INT;
+		NM2_ACK_INT(chip, NM2_RECORD_INT);
+		snd_nm256_capture_update(chip);
+	}
+
+	if (status & NM2_MISC_INT_1) {
+		status &= ~NM2_MISC_INT_1;
+		NM2_ACK_INT(chip, NM2_MISC_INT_1);
+		snd_printd("NM256: Got misc interrupt #1\n");
+		cbyte = snd_nm256_readb(chip, 0x400);
+		snd_nm256_writeb(chip, 0x400, cbyte | 2);
+	}
+
+	if (status & NM2_MISC_INT_2) {
+		status &= ~NM2_MISC_INT_2;
+		NM2_ACK_INT(chip, NM2_MISC_INT_2);
+		snd_printd("NM256: Got misc interrupt #2\n");
+		cbyte = snd_nm256_readb(chip, 0x400);
+		snd_nm256_writeb(chip, 0x400, cbyte & ~2);
+	}
+
+	/* Unknown interrupt. */
+	if (status) {
+		snd_printd("NM256: Fire in the hole! Unknown status 0x%x\n",
+			   status);
+		/* Pray. */
+		NM2_ACK_INT(chip, status);
+	}
+
+	spin_unlock(&chip->reg_lock);
+	return IRQ_HANDLED;
+}
+
+/*
+ * AC97 interface
+ */
+
+/*
+ * Waits for the mixer to become ready to be written; returns a zero value
+ * if it timed out.
+ */
+static int
+snd_nm256_ac97_ready(struct nm256 *chip)
+{
+	int timeout = 10;
+	u32 testaddr;
+	u16 testb;
+
+	testaddr = chip->mixer_status_offset;
+	testb = chip->mixer_status_mask;
+
+	/* 
+	 * Loop around waiting for the mixer to become ready. 
+	 */
+	while (timeout-- > 0) {
+		if ((snd_nm256_readw(chip, testaddr) & testb) == 0)
+			return 1;
+		udelay(100);
+	}
+	return 0;
+}
+
+/* 
+ * Initial register values to be written to the AC97 mixer.
+ * While most of these are identical to the reset values, we do this
+ * so that we have most of the register contents cached--this avoids
+ * reading from the mixer directly (which seems to be problematic,
+ * probably due to ignorance).
+ */
+
+struct initialValues {
+	unsigned short reg;
+	unsigned short value;
+};
+
+static struct initialValues nm256_ac97_init_val[] =
+{
+	{ AC97_MASTER, 		0x8000 },
+	{ AC97_HEADPHONE,	0x8000 },
+	{ AC97_MASTER_MONO,	0x8000 },
+	{ AC97_PC_BEEP,		0x8000 },
+	{ AC97_PHONE,		0x8008 },
+	{ AC97_MIC,		0x8000 },
+	{ AC97_LINE,		0x8808 },
+	{ AC97_CD,		0x8808 },
+	{ AC97_VIDEO,		0x8808 },
+	{ AC97_AUX,		0x8808 },
+	{ AC97_PCM,		0x8808 },
+	{ AC97_REC_SEL,		0x0000 },
+	{ AC97_REC_GAIN,	0x0B0B },
+	{ AC97_GENERAL_PURPOSE,	0x0000 },
+	{ AC97_3D_CONTROL,	0x8000 }, 
+	{ AC97_VENDOR_ID1, 	0x8384 },
+	{ AC97_VENDOR_ID2,	0x7609 },
+};
+
+static int nm256_ac97_idx(unsigned short reg)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(nm256_ac97_init_val); i++)
+		if (nm256_ac97_init_val[i].reg == reg)
+			return i;
+	return -1;
+}
+
+/*
+ * some nm256 easily crash when reading from mixer registers
+ * thus we're treating it as a write-only mixer and cache the
+ * written values
+ */
+static unsigned short
+snd_nm256_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct nm256 *chip = ac97->private_data;
+	int idx = nm256_ac97_idx(reg);
+
+	if (idx < 0)
+		return 0;
+	return chip->ac97_regs[idx];
+}
+
+/* 
+ */
+static void
+snd_nm256_ac97_write(struct snd_ac97 *ac97,
+		     unsigned short reg, unsigned short val)
+{
+	struct nm256 *chip = ac97->private_data;
+	int tries = 2;
+	int idx = nm256_ac97_idx(reg);
+	u32 base;
+
+	if (idx < 0)
+		return;
+
+	base = chip->mixer_base;
+
+	snd_nm256_ac97_ready(chip);
+
+	/* Wait for the write to take, too. */
+	while (tries-- > 0) {
+		snd_nm256_writew(chip, base + reg, val);
+		msleep(1);  /* a little delay here seems better.. */
+		if (snd_nm256_ac97_ready(chip)) {
+			/* successful write: set cache */
+			chip->ac97_regs[idx] = val;
+			return;
+		}
+	}
+	snd_printd("nm256: ac97 codec not ready..\n");
+}
+
+/* static resolution table */
+static struct snd_ac97_res_table nm256_res_table[] = {
+	{ AC97_MASTER, 0x1f1f },
+	{ AC97_HEADPHONE, 0x1f1f },
+	{ AC97_MASTER_MONO, 0x001f },
+	{ AC97_PC_BEEP, 0x001f },
+	{ AC97_PHONE, 0x001f },
+	{ AC97_MIC, 0x001f },
+	{ AC97_LINE, 0x1f1f },
+	{ AC97_CD, 0x1f1f },
+	{ AC97_VIDEO, 0x1f1f },
+	{ AC97_AUX, 0x1f1f },
+	{ AC97_PCM, 0x1f1f },
+	{ AC97_REC_GAIN, 0x0f0f },
+	{ } /* terminator */
+};
+
+/* initialize the ac97 into a known state */
+static void
+snd_nm256_ac97_reset(struct snd_ac97 *ac97)
+{
+	struct nm256 *chip = ac97->private_data;
+
+	/* Reset the mixer.  'Tis magic!  */
+	snd_nm256_writeb(chip, 0x6c0, 1);
+	if (! chip->reset_workaround) {
+		/* Dell latitude LS will lock up by this */
+		snd_nm256_writeb(chip, 0x6cc, 0x87);
+	}
+	if (! chip->reset_workaround_2) {
+		/* Dell latitude CSx will lock up by this */
+		snd_nm256_writeb(chip, 0x6cc, 0x80);
+		snd_nm256_writeb(chip, 0x6cc, 0x0);
+	}
+	if (! chip->in_resume) {
+		int i;
+		for (i = 0; i < ARRAY_SIZE(nm256_ac97_init_val); i++) {
+			/* preload the cache, so as to avoid even a single
+			 * read of the mixer regs
+			 */
+			snd_nm256_ac97_write(ac97, nm256_ac97_init_val[i].reg,
+					     nm256_ac97_init_val[i].value);
+		}
+	}
+}
+
+/* create an ac97 mixer interface */
+static int __devinit
+snd_nm256_mixer(struct nm256 *chip)
+{
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.reset = snd_nm256_ac97_reset,
+		.write = snd_nm256_ac97_write,
+		.read = snd_nm256_ac97_read,
+	};
+
+	chip->ac97_regs = kcalloc(ARRAY_SIZE(nm256_ac97_init_val),
+				  sizeof(short), GFP_KERNEL);
+	if (! chip->ac97_regs)
+		return -ENOMEM;
+
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &pbus)) < 0)
+		return err;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.scaps = AC97_SCAP_AUDIO; /* we support audio! */
+	ac97.private_data = chip;
+	ac97.res_table = nm256_res_table;
+	pbus->no_vra = 1;
+	err = snd_ac97_mixer(pbus, &ac97, &chip->ac97);
+	if (err < 0)
+		return err;
+	if (! (chip->ac97->id & (0xf0000000))) {
+		/* looks like an invalid id */
+		sprintf(chip->card->mixername, "%s AC97", chip->card->driver);
+	}
+	return 0;
+}
+
+/* 
+ * See if the signature left by the NM256 BIOS is intact; if so, we use
+ * the associated address as the end of our audio buffer in the video
+ * RAM.
+ */
+
+static int __devinit
+snd_nm256_peek_for_sig(struct nm256 *chip)
+{
+	/* The signature is located 1K below the end of video RAM.  */
+	void __iomem *temp;
+	/* Default buffer end is 5120 bytes below the top of RAM.  */
+	unsigned long pointer_found = chip->buffer_end - 0x1400;
+	u32 sig;
+
+	temp = ioremap_nocache(chip->buffer_addr + chip->buffer_end - 0x400, 16);
+	if (temp == NULL) {
+		snd_printk(KERN_ERR "Unable to scan for card signature in video RAM\n");
+		return -EBUSY;
+	}
+
+	sig = readl(temp);
+	if ((sig & NM_SIG_MASK) == NM_SIGNATURE) {
+		u32 pointer = readl(temp + 4);
+
+		/*
+		 * If it's obviously invalid, don't use it
+		 */
+		if (pointer == 0xffffffff ||
+		    pointer < chip->buffer_size ||
+		    pointer > chip->buffer_end) {
+			snd_printk(KERN_ERR "invalid signature found: 0x%x\n", pointer);
+			iounmap(temp);
+			return -ENODEV;
+		} else {
+			pointer_found = pointer;
+			printk(KERN_INFO "nm256: found card signature in video RAM: 0x%x\n",
+			       pointer);
+		}
+	}
+
+	iounmap(temp);
+	chip->buffer_end = pointer_found;
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * APM event handler, so the card is properly reinitialized after a power
+ * event.
+ */
+static int nm256_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct nm256 *chip = card->private_data;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_ac97_suspend(chip->ac97);
+	chip->coeffs_current = 0;
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int nm256_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct nm256 *chip = card->private_data;
+	int i;
+
+	/* Perform a full reset on the hardware */
+	chip->in_resume = 1;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "nm256: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_nm256_init_chip(chip);
+
+	/* restore ac97 */
+	snd_ac97_resume(chip->ac97);
+
+	for (i = 0; i < 2; i++) {
+		struct nm256_stream *s = &chip->streams[i];
+		if (s->substream && s->suspended) {
+			spin_lock_irq(&chip->reg_lock);
+			snd_nm256_set_format(chip, s, s->substream);
+			spin_unlock_irq(&chip->reg_lock);
+		}
+	}
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	chip->in_resume = 0;
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static int snd_nm256_free(struct nm256 *chip)
+{
+	if (chip->streams[SNDRV_PCM_STREAM_PLAYBACK].running)
+		snd_nm256_playback_stop(chip);
+	if (chip->streams[SNDRV_PCM_STREAM_CAPTURE].running)
+		snd_nm256_capture_stop(chip);
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+
+	if (chip->cport)
+		iounmap(chip->cport);
+	if (chip->buffer)
+		iounmap(chip->buffer);
+	release_and_free_resource(chip->res_cport);
+	release_and_free_resource(chip->res_buffer);
+
+	pci_disable_device(chip->pci);
+	kfree(chip->ac97_regs);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_nm256_dev_free(struct snd_device *device)
+{
+	struct nm256 *chip = device->device_data;
+	return snd_nm256_free(chip);
+}
+
+static int __devinit
+snd_nm256_create(struct snd_card *card, struct pci_dev *pci,
+		 struct nm256 **chip_ret)
+{
+	struct nm256 *chip;
+	int err, pval;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_nm256_dev_free,
+	};
+	u32 addr;
+
+	*chip_ret = NULL;
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	chip->card = card;
+	chip->pci = pci;
+	chip->use_cache = use_cache;
+	spin_lock_init(&chip->reg_lock);
+	chip->irq = -1;
+	mutex_init(&chip->irq_mutex);
+
+	/* store buffer sizes in bytes */
+	chip->streams[SNDRV_PCM_STREAM_PLAYBACK].bufsize = playback_bufsize * 1024;
+	chip->streams[SNDRV_PCM_STREAM_CAPTURE].bufsize = capture_bufsize * 1024;
+
+	/* 
+	 * The NM256 has two memory ports.  The first port is nothing
+	 * more than a chunk of video RAM, which is used as the I/O ring
+	 * buffer.  The second port has the actual juicy stuff (like the
+	 * mixer and the playback engine control registers).
+	 */
+
+	chip->buffer_addr = pci_resource_start(pci, 0);
+	chip->cport_addr = pci_resource_start(pci, 1);
+
+	/* Init the memory port info.  */
+	/* remap control port (#2) */
+	chip->res_cport = request_mem_region(chip->cport_addr, NM_PORT2_SIZE,
+					     card->driver);
+	if (chip->res_cport == NULL) {
+		snd_printk(KERN_ERR "memory region 0x%lx (size 0x%x) busy\n",
+			   chip->cport_addr, NM_PORT2_SIZE);
+		err = -EBUSY;
+		goto __error;
+	}
+	chip->cport = ioremap_nocache(chip->cport_addr, NM_PORT2_SIZE);
+	if (chip->cport == NULL) {
+		snd_printk(KERN_ERR "unable to map control port %lx\n", chip->cport_addr);
+		err = -ENOMEM;
+		goto __error;
+	}
+
+	if (!strcmp(card->driver, "NM256AV")) {
+		/* Ok, try to see if this is a non-AC97 version of the hardware. */
+		pval = snd_nm256_readw(chip, NM_MIXER_PRESENCE);
+		if ((pval & NM_PRESENCE_MASK) != NM_PRESENCE_VALUE) {
+			if (! force_ac97) {
+				printk(KERN_ERR "nm256: no ac97 is found!\n");
+				printk(KERN_ERR "  force the driver to load by "
+				       "passing in the module parameter\n");
+				printk(KERN_ERR "    force_ac97=1\n");
+				printk(KERN_ERR "  or try sb16, opl3sa2, or "
+				       "cs423x drivers instead.\n");
+				err = -ENXIO;
+				goto __error;
+			}
+		}
+		chip->buffer_end = 2560 * 1024;
+		chip->interrupt = snd_nm256_interrupt;
+		chip->mixer_status_offset = NM_MIXER_STATUS_OFFSET;
+		chip->mixer_status_mask = NM_MIXER_READY_MASK;
+	} else {
+		/* Not sure if there is any relevant detect for the ZX or not.  */
+		if (snd_nm256_readb(chip, 0xa0b) != 0)
+			chip->buffer_end = 6144 * 1024;
+		else
+			chip->buffer_end = 4096 * 1024;
+
+		chip->interrupt = snd_nm256_interrupt_zx;
+		chip->mixer_status_offset = NM2_MIXER_STATUS_OFFSET;
+		chip->mixer_status_mask = NM2_MIXER_READY_MASK;
+	}
+	
+	chip->buffer_size = chip->streams[SNDRV_PCM_STREAM_PLAYBACK].bufsize +
+		chip->streams[SNDRV_PCM_STREAM_CAPTURE].bufsize;
+	if (chip->use_cache)
+		chip->buffer_size += NM_TOTAL_COEFF_COUNT * 4;
+	else
+		chip->buffer_size += NM_MAX_PLAYBACK_COEF_SIZE + NM_MAX_RECORD_COEF_SIZE;
+
+	if (buffer_top >= chip->buffer_size && buffer_top < chip->buffer_end)
+		chip->buffer_end = buffer_top;
+	else {
+		/* get buffer end pointer from signature */
+		if ((err = snd_nm256_peek_for_sig(chip)) < 0)
+			goto __error;
+	}
+
+	chip->buffer_start = chip->buffer_end - chip->buffer_size;
+	chip->buffer_addr += chip->buffer_start;
+
+	printk(KERN_INFO "nm256: Mapping port 1 from 0x%x - 0x%x\n",
+	       chip->buffer_start, chip->buffer_end);
+
+	chip->res_buffer = request_mem_region(chip->buffer_addr,
+					      chip->buffer_size,
+					      card->driver);
+	if (chip->res_buffer == NULL) {
+		snd_printk(KERN_ERR "nm256: buffer 0x%lx (size 0x%x) busy\n",
+			   chip->buffer_addr, chip->buffer_size);
+		err = -EBUSY;
+		goto __error;
+	}
+	chip->buffer = ioremap_nocache(chip->buffer_addr, chip->buffer_size);
+	if (chip->buffer == NULL) {
+		err = -ENOMEM;
+		snd_printk(KERN_ERR "unable to map ring buffer at %lx\n", chip->buffer_addr);
+		goto __error;
+	}
+
+	/* set offsets */
+	addr = chip->buffer_start;
+	chip->streams[SNDRV_PCM_STREAM_PLAYBACK].buf = addr;
+	addr += chip->streams[SNDRV_PCM_STREAM_PLAYBACK].bufsize;
+	chip->streams[SNDRV_PCM_STREAM_CAPTURE].buf = addr;
+	addr += chip->streams[SNDRV_PCM_STREAM_CAPTURE].bufsize;
+	if (chip->use_cache) {
+		chip->all_coeff_buf = addr;
+	} else {
+		chip->coeff_buf[SNDRV_PCM_STREAM_PLAYBACK] = addr;
+		addr += NM_MAX_PLAYBACK_COEF_SIZE;
+		chip->coeff_buf[SNDRV_PCM_STREAM_CAPTURE] = addr;
+	}
+
+	/* Fixed setting. */
+	chip->mixer_base = NM_MIXER_OFFSET;
+
+	chip->coeffs_current = 0;
+
+	snd_nm256_init_chip(chip);
+
+	// pci_set_master(pci); /* needed? */
+	
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0)
+		goto __error;
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*chip_ret = chip;
+	return 0;
+
+__error:
+	snd_nm256_free(chip);
+	return err;
+}
+
+
+enum { NM_BLACKLISTED, NM_RESET_WORKAROUND, NM_RESET_WORKAROUND_2 };
+
+static struct snd_pci_quirk nm256_quirks[] __devinitdata = {
+	/* HP omnibook 4150 has cs4232 codec internally */
+	SND_PCI_QUIRK(0x103c, 0x0007, "HP omnibook 4150", NM_BLACKLISTED),
+	/* Reset workarounds to avoid lock-ups */
+	SND_PCI_QUIRK(0x104d, 0x8041, "Sony PCG-F305", NM_RESET_WORKAROUND),
+	SND_PCI_QUIRK(0x1028, 0x0080, "Dell Latitude LS", NM_RESET_WORKAROUND),
+	SND_PCI_QUIRK(0x1028, 0x0091, "Dell Latitude CSx", NM_RESET_WORKAROUND_2),
+	{ } /* terminator */
+};
+
+
+static int __devinit snd_nm256_probe(struct pci_dev *pci,
+				     const struct pci_device_id *pci_id)
+{
+	struct snd_card *card;
+	struct nm256 *chip;
+	int err;
+	const struct snd_pci_quirk *q;
+
+	q = snd_pci_quirk_lookup(pci, nm256_quirks);
+	if (q) {
+		snd_printdd(KERN_INFO "nm256: Enabled quirk for %s.\n", q->name);
+		switch (q->value) {
+		case NM_BLACKLISTED:
+			printk(KERN_INFO "nm256: The device is blacklisted. "
+			       "Loading stopped\n");
+			return -ENODEV;
+		case NM_RESET_WORKAROUND_2:
+			reset_workaround_2 = 1;
+			/* Fall-through */
+		case NM_RESET_WORKAROUND:
+			reset_workaround = 1;
+			break;
+		}
+	}
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	switch (pci->device) {
+	case PCI_DEVICE_ID_NEOMAGIC_NM256AV_AUDIO:
+		strcpy(card->driver, "NM256AV");
+		break;
+	case PCI_DEVICE_ID_NEOMAGIC_NM256ZX_AUDIO:
+		strcpy(card->driver, "NM256ZX");
+		break;
+	case PCI_DEVICE_ID_NEOMAGIC_NM256XL_PLUS_AUDIO:
+		strcpy(card->driver, "NM256XL+");
+		break;
+	default:
+		snd_printk(KERN_ERR "invalid device id 0x%x\n", pci->device);
+		snd_card_free(card);
+		return -EINVAL;
+	}
+
+	if (vaio_hack)
+		buffer_top = 0x25a800;	/* this avoids conflicts with XFree86 server */
+
+	if (playback_bufsize < 4)
+		playback_bufsize = 4;
+	if (playback_bufsize > 128)
+		playback_bufsize = 128;
+	if (capture_bufsize < 4)
+		capture_bufsize = 4;
+	if (capture_bufsize > 128)
+		capture_bufsize = 128;
+	if ((err = snd_nm256_create(card, pci, &chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = chip;
+
+	if (reset_workaround) {
+		snd_printdd(KERN_INFO "nm256: reset_workaround activated\n");
+		chip->reset_workaround = 1;
+	}
+
+	if (reset_workaround_2) {
+		snd_printdd(KERN_INFO "nm256: reset_workaround_2 activated\n");
+		chip->reset_workaround_2 = 1;
+	}
+
+	if ((err = snd_nm256_pcm(chip, 0)) < 0 ||
+	    (err = snd_nm256_mixer(chip)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	sprintf(card->shortname, "NeoMagic %s", card->driver);
+	sprintf(card->longname, "%s at 0x%lx & 0x%lx, irq %d",
+		card->shortname,
+		chip->buffer_addr, chip->cport_addr, chip->irq);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	pci_set_drvdata(pci, card);
+	return 0;
+}
+
+static void __devexit snd_nm256_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+
+static struct pci_driver driver = {
+	.name = "NeoMagic 256",
+	.id_table = snd_nm256_ids,
+	.probe = snd_nm256_probe,
+	.remove = __devexit_p(snd_nm256_remove),
+#ifdef CONFIG_PM
+	.suspend = nm256_suspend,
+	.resume = nm256_resume,
+#endif
+};
+
+
+static int __init alsa_card_nm256_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_nm256_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_nm256_init)
+module_exit(alsa_card_nm256_exit)
diff --git a/linux/sound/pci/nm256/nm256_coef.c b/linux/sound/pci/nm256/nm256_coef.c
new file mode 100644
index 0000000..747d5d6
--- /dev/null
+++ b/linux/sound/pci/nm256/nm256_coef.c
@@ -0,0 +1,4607 @@
+#define NM_TOTAL_COEFF_COUNT 0x3158
+
+static char coefficients[NM_TOTAL_COEFF_COUNT * 4] = { 
+	0xFF, 0xFF, 0x2F, 0x00, 0x4B, 0xFF, 0xA5, 0x01, 0xEF, 0xFC, 0x21,
+	0x05, 0x87, 0xF7, 0x62, 0x11, 0xE9, 0x45, 0x5E, 0xF9, 0xB5, 0x01,
+	0xDE, 0xFF, 0xA4, 0xFF, 0x60, 0x00, 0xCA, 0xFF, 0x0D, 0x00, 0xFD,
+	0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD6, 0x06,
+	0x4C, 0xF3, 0xED, 0x20, 0x3D, 0x3D, 0x4A, 0xF3, 0x4E, 0x05, 0xB1,
+	0xFD, 0xE1, 0x00, 0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFD, 0xFF,
+	0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E,
+	0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC,
+	0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x02, 0x00, 0x05,
+	0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1, 0xFD, 0x4E, 0x05, 0x4A, 0xF3,
+	0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3, 0xD6, 0x06, 0x3D, 0xFC, 0xE6,
+	0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCA, 0xFF,
+	0x60, 0x00, 0xA4, 0xFF, 0xDE, 0xFF, 0xB5, 0x01, 0x5E, 0xF9, 0xE9,
+	0x45, 0x62, 0x11, 0x87, 0xF7, 0x21, 0x05, 0xEF, 0xFC, 0xA5, 0x01,
+	0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84,
+	0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03,
+	0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11,
+	0x01, 0x84, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF,
+	0xCA, 0x01, 0x95, 0xFC, 0xEA, 0x05, 0xBB, 0xF5, 0x25, 0x17, 0x3C,
+	0x43, 0x8D, 0xF6, 0x43, 0x03, 0xF5, 0xFE, 0x26, 0x00, 0x20, 0x00,
+	0xE2, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4D, 0xFF, 0xC5,
+	0x01, 0x4C, 0xFC, 0x26, 0x07, 0xA3, 0xF1, 0xAB, 0x2C, 0xBB, 0x33,
+	0x8F, 0xF1, 0xCA, 0x06, 0xA6, 0xFC, 0x85, 0x01, 0x6F, 0xFF, 0x24,
+	0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFE, 0xFF, 0xD5, 0xFF, 0xBC, 0x00,
+	0xF0, 0xFD, 0xEC, 0x04, 0xD9, 0xF3, 0xB1, 0x3E, 0xCD, 0x1E, 0xC1,
+	0xF3, 0xAF, 0x06, 0x49, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00,
+	0xFE, 0xFF, 0x16, 0x00, 0xA6, 0xFF, 0xBB, 0x00, 0xE9, 0xFE, 0x38,
+	0x01, 0x4B, 0xFF, 0x28, 0xFE, 0x3A, 0x48, 0x04, 0x0A, 0x2E, 0xFA,
+	0xDF, 0x03, 0x8A, 0xFD, 0x60, 0x01, 0x65, 0xFF, 0x27, 0x00, 0x00,
+	0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0x98, 0x01, 0x0D, 0xFD,
+	0xE0, 0x04, 0x14, 0xF8, 0xC3, 0x0F, 0x89, 0x46, 0x4C, 0xFA, 0x38,
+	0x01, 0x25, 0x00, 0x7D, 0xFF, 0x73, 0x00, 0xC2, 0xFF, 0x0F, 0x00,
+	0xFD, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x0F,
+	0x07, 0x84, 0xF2, 0x29, 0x25, 0x1A, 0x3A, 0x67, 0xF2, 0xF6, 0x05,
+	0x41, 0xFD, 0x24, 0x01, 0xA1, 0xFF, 0x12, 0x00, 0x00, 0x00, 0xFF,
+	0xFF, 0x15, 0x00, 0x97, 0xFF, 0x37, 0x01, 0x22, 0xFD, 0x23, 0x06,
+	0x2F, 0xF2, 0x11, 0x39, 0x7B, 0x26, 0x50, 0xF2, 0x1B, 0x07, 0x32,
+	0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00,
+	0xC8, 0xFF, 0x64, 0x00, 0x9B, 0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93,
+	0xF9, 0x10, 0x46, 0x03, 0x11, 0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC,
+	0xA2, 0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26,
+	0x00, 0x6A, 0xFF, 0x53, 0x01, 0xA6, 0xFD, 0xA6, 0x03, 0xA1, 0xFA,
+	0xDE, 0x08, 0x76, 0x48, 0x0C, 0xFF, 0xDE, 0xFE, 0x73, 0x01, 0xC9,
+	0xFE, 0xCA, 0x00, 0xA0, 0xFF, 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00,
+	0x36, 0xFF, 0xE1, 0x01, 0x52, 0xFC, 0x93, 0x06, 0x10, 0xF4, 0x78,
+	0x1D, 0x90, 0x3F, 0x3E, 0xF4, 0xAA, 0x04, 0x19, 0xFE, 0xA4, 0x00,
+	0xE2, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x68,
+	0xFF, 0x93, 0x01, 0x92, 0xFC, 0xE2, 0x06, 0x83, 0xF1, 0x8C, 0x32,
+	0xED, 0x2D, 0x90, 0xF1, 0x1E, 0x07, 0x57, 0xFC, 0xBD, 0x01, 0x51,
+	0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE8, 0xFF, 0x12, 0x00,
+	0x42, 0x00, 0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42, 0x76,
+	0x18, 0x5C, 0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B, 0xFF,
+	0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x8A, 0xFF, 0x03, 0x01, 0x53,
+	0xFE, 0x53, 0x02, 0x39, 0xFD, 0xA9, 0x02, 0xF2, 0x48, 0xB9, 0x04,
+	0x54, 0xFC, 0xCA, 0x02, 0x16, 0xFE, 0x20, 0x01, 0x7F, 0xFF, 0x20,
+	0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF, 0xC3, 0x01,
+	0xA7, 0xFC, 0xC0, 0x05, 0x1E, 0xF6, 0xD8, 0x15, 0xE7, 0x43, 0x20,
+	0xF7, 0xEF, 0x02, 0x27, 0xFF, 0x0A, 0x00, 0x2E, 0x00, 0xDD, 0xFF,
+	0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCD, 0x01, 0x43,
+	0xFC, 0x2A, 0x07, 0xBC, 0xF1, 0x64, 0x2B, 0xE3, 0x34, 0xA3, 0xF1,
+	0xAE, 0x06, 0xBD, 0xFC, 0x77, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE,
+	0xFF, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4, 0x00, 0xC8, 0xFD,
+	0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20, 0x76, 0xF3, 0xC8,
+	0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF,
+	0x14, 0x00, 0xAC, 0xFF, 0xAC, 0x00, 0x08, 0xFF, 0xFD, 0x00, 0xB5,
+	0xFF, 0x4B, 0xFD, 0xF4, 0x47, 0x30, 0x0B, 0xBC, 0xF9, 0x17, 0x04,
+	0x6E, 0xFD, 0x6D, 0x01, 0x60, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF,
+	0xFF, 0x2C, 0x00, 0x54, 0xFF, 0x8D, 0x01, 0x26, 0xFD, 0xAD, 0x04,
+	0x82, 0xF8, 0x87, 0x0E, 0xF9, 0x46, 0x0C, 0xFB, 0xD4, 0x00, 0x5D,
+	0x00, 0x5E, 0xFF, 0x82, 0x00, 0xBD, 0xFF, 0x10, 0x00, 0xFD, 0xFF,
+	0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0x01, 0x07, 0xBE,
+	0xF2, 0xD6, 0x23, 0x1F, 0x3B, 0xA5, 0xF2, 0xC5, 0x05, 0x62, 0xFD,
+	0x10, 0x01, 0xAB, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x19,
+	0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04, 0xFD, 0x4D, 0x06, 0x00, 0xF2,
+	0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2, 0x23, 0x07, 0x34, 0xFC, 0xDD,
+	0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xCE, 0xFF,
+	0x56, 0x00, 0xB9, 0xFF, 0xB8, 0xFF, 0xF7, 0x01, 0xE2, 0xF8, 0x8D,
+	0x45, 0x46, 0x12, 0x3C, 0xF7, 0x43, 0x05, 0xDF, 0xFC, 0xAC, 0x01,
+	0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x70,
+	0xFF, 0x46, 0x01, 0xC3, 0xFD, 0x6D, 0x03, 0x14, 0xFB, 0xBE, 0x07,
+	0xA6, 0x48, 0xF8, 0xFF, 0x70, 0xFE, 0xAE, 0x01, 0xAA, 0xFE, 0xD9,
+	0x00, 0x9A, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF,
+	0xDE, 0x01, 0x5D, 0xFC, 0x74, 0x06, 0x63, 0xF4, 0x23, 0x1C, 0x66,
+	0x40, 0xAA, 0xF4, 0x65, 0x04, 0x44, 0xFE, 0x8B, 0x00, 0xEE, 0xFF,
+	0xF5, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F,
+	0x01, 0x80, 0xFC, 0xF7, 0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F,
+	0x83, 0xF1, 0x13, 0x07, 0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C,
+	0x00, 0xFD, 0xFF, 0x06, 0x00, 0xED, 0xFF, 0x05, 0x00, 0x5D, 0x00,
+	0x95, 0xFE, 0xE2, 0x03, 0x7F, 0xF5, 0xCC, 0x41, 0xC7, 0x19, 0xFF,
+	0xF4, 0x37, 0x06, 0x75, 0xFC, 0xD6, 0x01, 0x39, 0xFF, 0x35, 0x00,
+	0xFE, 0xFF, 0x1B, 0x00, 0x90, 0xFF, 0xF4, 0x00, 0x72, 0xFE, 0x18,
+	0x02, 0xAA, 0xFD, 0xAB, 0x01, 0xDF, 0x48, 0xCA, 0x05, 0xE1, 0xFB,
+	0x05, 0x03, 0xF7, 0xFD, 0x2E, 0x01, 0x79, 0xFF, 0x21, 0x00, 0x00,
+	0x00, 0xFF, 0xFF, 0x32, 0x00, 0x43, 0xFF, 0xBB, 0x01, 0xBA, 0xFC,
+	0x95, 0x05, 0x83, 0xF6, 0x8C, 0x14, 0x87, 0x44, 0xBB, 0xF7, 0x98,
+	0x02, 0x5A, 0xFF, 0xEE, 0xFF, 0x3C, 0x00, 0xD8, 0xFF, 0x0A, 0x00,
+	0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A,
+	0x07, 0xDC, 0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06,
+	0xD5, 0xFC, 0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x01,
+	0x00, 0x07, 0x00, 0xBE, 0xFF, 0xEA, 0x00, 0xA2, 0xFD, 0x65, 0x05,
+	0x28, 0xF3, 0xDB, 0x3C, 0x78, 0x21, 0x30, 0xF3, 0xDF, 0x06, 0x3A,
+	0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00,
+	0xB2, 0xFF, 0x9D, 0x00, 0x27, 0xFF, 0xC3, 0x00, 0x1F, 0x00, 0x76,
+	0xFC, 0xA3, 0x47, 0x60, 0x0C, 0x4A, 0xF9, 0x4E, 0x04, 0x53, 0xFD,
+	0x79, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B,
+	0x00, 0x58, 0xFF, 0x82, 0x01, 0x3F, 0xFD, 0x78, 0x04, 0xF2, 0xF8,
+	0x50, 0x0D, 0x5E, 0x47, 0xD5, 0xFB, 0x6F, 0x00, 0x96, 0x00, 0x40,
+	0xFF, 0x91, 0x00, 0xB7, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00,
+	0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC, 0xF2, 0x81,
+	0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD, 0xFB, 0x00,
+	0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x85,
+	0xFF, 0x5B, 0x01, 0xE9, 0xFC, 0x73, 0x06, 0xD8, 0xF1, 0xE5, 0x36,
+	0x19, 0x29, 0xF8, 0xF1, 0x29, 0x07, 0x37, 0xFC, 0xD8, 0x01, 0x42,
+	0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD3, 0xFF, 0x47, 0x00,
+	0xD7, 0xFF, 0x82, 0xFF, 0x53, 0x02, 0x39, 0xF8, 0xFD, 0x44, 0x8D,
+	0x13, 0xD3, 0xF6, 0x72, 0x05, 0xCA, 0xFC, 0xB5, 0x01, 0x45, 0xFF,
+	0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x75, 0xFF, 0x39,
+	0x01, 0xE0, 0xFD, 0x33, 0x03, 0x87, 0xFB, 0xA2, 0x06, 0xCB, 0x48,
+	0xEA, 0x00, 0x01, 0xFE, 0xE9, 0x01, 0x8A, 0xFE, 0xE8, 0x00, 0x95,
+	0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x38, 0xFF, 0xDA, 0x01,
+	0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4, 0xCE, 0x1A, 0x32, 0x41, 0x1F,
+	0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71, 0x00, 0xFB, 0xFF, 0xF0, 0xFF,
+	0x05, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5B, 0xFF, 0xAB, 0x01, 0x6F,
+	0xFC, 0x08, 0x07, 0x7E, 0xF1, 0x21, 0x30, 0x67, 0x30, 0x7D, 0xF1,
+	0x05, 0x07, 0x73, 0xFC, 0xA8, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD,
+	0xFF, 0x05, 0x00, 0xF2, 0xFF, 0xF8, 0xFF, 0x77, 0x00, 0x67, 0xFE,
+	0x2D, 0x04, 0x04, 0xF5, 0x07, 0x41, 0x1B, 0x1B, 0xA6, 0xF4, 0x5A,
+	0x06, 0x67, 0xFC, 0xDB, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF,
+	0x1A, 0x00, 0x96, 0xFF, 0xE5, 0x00, 0x91, 0xFE, 0xDC, 0x01, 0x1A,
+	0xFE, 0xB3, 0x00, 0xC3, 0x48, 0xE1, 0x06, 0x6E, 0xFB, 0x40, 0x03,
+	0xDA, 0xFD, 0x3C, 0x01, 0x74, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF,
+	0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB3, 0x01, 0xCF, 0xFC, 0x67, 0x05,
+	0xEA, 0xF6, 0x44, 0x13, 0x1E, 0x45, 0x5E, 0xF8, 0x3F, 0x02, 0x8E,
+	0xFF, 0xD0, 0xFF, 0x4A, 0x00, 0xD2, 0xFF, 0x0B, 0x00, 0xFD, 0xFF,
+	0x33, 0x00, 0x41, 0xFF, 0xD9, 0x01, 0x36, 0xFC, 0x28, 0x07, 0x01,
+	0xF2, 0xCE, 0x28, 0x23, 0x37, 0xE0, 0xF1, 0x6B, 0x06, 0xEF, 0xFC,
+	0x57, 0x01, 0x87, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0B,
+	0x00, 0xB4, 0xFF, 0x00, 0x01, 0x7E, 0xFD, 0x9C, 0x05, 0xDC, 0xF2,
+	0xE4, 0x3B, 0xCD, 0x22, 0xEE, 0xF2, 0xF3, 0x06, 0x35, 0xFC, 0xE6,
+	0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, 0x00, 0xB8, 0xFF,
+	0x8E, 0x00, 0x46, 0xFF, 0x8A, 0x00, 0x86, 0x00, 0xA7, 0xFB, 0x48,
+	0x47, 0x95, 0x0D, 0xD9, 0xF8, 0x84, 0x04, 0x39, 0xFD, 0x85, 0x01,
+	0x57, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D,
+	0xFF, 0x76, 0x01, 0x59, 0xFD, 0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C,
+	0xB6, 0x47, 0xA4, 0xFC, 0x07, 0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0,
+	0x00, 0xB1, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF,
+	0xE6, 0x01, 0x3B, 0xFC, 0xDA, 0x06, 0x3F, 0xF3, 0x2C, 0x21, 0x11,
+	0x3D, 0x3A, 0xF3, 0x58, 0x05, 0xAA, 0xFD, 0xE5, 0x00, 0xC1, 0xFF,
+	0x06, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1F, 0x00, 0x7D, 0xFF, 0x6B,
+	0x01, 0xCF, 0xFC, 0x96, 0x06, 0xB7, 0xF1, 0xC6, 0x35, 0x64, 0x2A,
+	0xD4, 0xF1, 0x2B, 0x07, 0x3D, 0xFC, 0xD2, 0x01, 0x45, 0xFF, 0x32,
+	0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD9, 0xFF, 0x39, 0x00, 0xF4, 0xFF,
+	0x4E, 0xFF, 0xAC, 0x02, 0x98, 0xF7, 0x65, 0x44, 0xD6, 0x14, 0x6C,
+	0xF6, 0x9F, 0x05, 0xB6, 0xFC, 0xBD, 0x01, 0x42, 0xFF, 0x32, 0x00,
+	0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF, 0x2B, 0x01, 0xFE,
+	0xFD, 0xF8, 0x02, 0xFB, 0xFB, 0x8D, 0x05, 0xE5, 0x48, 0xE3, 0x01,
+	0x91, 0xFD, 0x25, 0x02, 0x6B, 0xFE, 0xF7, 0x00, 0x8F, 0xFF, 0x1C,
+	0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD5, 0x01, 0x78, 0xFC,
+	0x2F, 0x06, 0x13, 0xF5, 0x7C, 0x19, 0xF7, 0x41, 0x9B, 0xF5, 0xD1,
+	0x03, 0x9F, 0xFE, 0x57, 0x00, 0x08, 0x00, 0xEC, 0xFF, 0x06, 0x00,
+	0xFD, 0xFF, 0x2D, 0x00, 0x55, 0xFF, 0xB5, 0x01, 0x61, 0xFC, 0x16,
+	0x07, 0x85, 0xF1, 0xE6, 0x2E, 0x9E, 0x31, 0x7D, 0xF1, 0xF3, 0x06,
+	0x84, 0xFC, 0x9D, 0x01, 0x63, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x04,
+	0x00, 0xF6, 0xFF, 0xEB, 0xFF, 0x91, 0x00, 0x3B, 0xFE, 0x75, 0x04,
+	0x92, 0xF4, 0x36, 0x40, 0x6E, 0x1C, 0x50, 0xF4, 0x7B, 0x06, 0x5B,
+	0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00,
+	0x9C, 0xFF, 0xD6, 0x00, 0xB1, 0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3,
+	0xFF, 0x9C, 0x48, 0xFD, 0x07, 0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD,
+	0x49, 0x01, 0x6E, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30,
+	0x00, 0x49, 0xFF, 0xAA, 0x01, 0xE4, 0xFC, 0x38, 0x05, 0x54, 0xF7,
+	0xFE, 0x11, 0xAA, 0x45, 0x09, 0xF9, 0xE2, 0x01, 0xC4, 0xFF, 0xB3,
+	0xFF, 0x59, 0x00, 0xCD, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00,
+	0x3E, 0xFF, 0xDE, 0x01, 0x33, 0xFC, 0x22, 0x07, 0x2B, 0xF2, 0x80,
+	0x27, 0x3B, 0x38, 0x0A, 0xF2, 0x44, 0x06, 0x0B, 0xFD, 0x45, 0x01,
+	0x90, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA9,
+	0xFF, 0x15, 0x01, 0x5B, 0xFD, 0xD0, 0x05, 0x97, 0xF2, 0xE6, 0x3A,
+	0x21, 0x24, 0xB1, 0xF2, 0x04, 0x07, 0x33, 0xFC, 0xE5, 0x01, 0x39,
+	0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBE, 0xFF, 0x7F, 0x00,
+	0x65, 0xFF, 0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46, 0xCD,
+	0x0E, 0x6A, 0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53, 0xFF,
+	0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x62, 0xFF, 0x6A,
+	0x01, 0x74, 0xFD, 0x0A, 0x04, 0xD5, 0xF9, 0xED, 0x0A, 0x03, 0x48,
+	0x7C, 0xFD, 0x9E, 0xFF, 0x0A, 0x01, 0x01, 0xFF, 0xAF, 0x00, 0xAB,
+	0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01,
+	0x42, 0xFC, 0xC3, 0x06, 0x87, 0xF3, 0xD7, 0x1F, 0xFE, 0x3D, 0x91,
+	0xF3, 0x1D, 0x05, 0xD1, 0xFD, 0xCE, 0x00, 0xCC, 0xFF, 0x02, 0x00,
+	0x02, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x75, 0xFF, 0x7A, 0x01, 0xB8,
+	0xFC, 0xB4, 0x06, 0x9E, 0xF1, 0xA2, 0x34, 0xAD, 0x2B, 0xB6, 0xF1,
+	0x29, 0x07, 0x45, 0xFC, 0xCB, 0x01, 0x49, 0xFF, 0x31, 0x00, 0xFD,
+	0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11, 0x00, 0x1B, 0xFF,
+	0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16, 0x07, 0xF6, 0xCA,
+	0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF,
+	0x00, 0x00, 0x20, 0x00, 0x80, 0xFF, 0x1C, 0x01, 0x1C, 0xFE, 0xBD,
+	0x02, 0x6E, 0xFC, 0x7D, 0x04, 0xF3, 0x48, 0xE2, 0x02, 0x1F, 0xFD,
+	0x60, 0x02, 0x4C, 0xFE, 0x06, 0x01, 0x89, 0xFF, 0x1D, 0x00, 0xFE,
+	0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCF, 0x01, 0x88, 0xFC, 0x09, 0x06,
+	0x71, 0xF5, 0x2B, 0x18, 0xB2, 0x42, 0x20, 0xF6, 0x83, 0x03, 0xCF,
+	0xFE, 0x3C, 0x00, 0x15, 0x00, 0xE6, 0xFF, 0x07, 0x00, 0xFD, 0xFF,
+	0x2E, 0x00, 0x50, 0xFF, 0xBF, 0x01, 0x54, 0xFC, 0x20, 0x07, 0x94,
+	0xF1, 0xA6, 0x2D, 0xD0, 0x32, 0x85, 0xF1, 0xDD, 0x06, 0x96, 0xFC,
+	0x90, 0x01, 0x69, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFB,
+	0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10, 0xFE, 0xB9, 0x04, 0x27, 0xF4,
+	0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3, 0x99, 0x06, 0x50, 0xFC, 0xE2,
+	0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0xA2, 0xFF,
+	0xC7, 0x00, 0xD0, 0xFE, 0x65, 0x01, 0xF6, 0xFE, 0xD9, 0xFE, 0x6A,
+	0x48, 0x1F, 0x09, 0x87, 0xFA, 0xB3, 0x03, 0xA0, 0xFD, 0x56, 0x01,
+	0x69, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4D,
+	0xFF, 0xA0, 0x01, 0xFB, 0xFC, 0x07, 0x05, 0xBF, 0xF7, 0xBB, 0x10,
+	0x2B, 0x46, 0xBB, 0xF9, 0x83, 0x01, 0xFA, 0xFF, 0x95, 0xFF, 0x68,
+	0x00, 0xC7, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF,
+	0xE1, 0x01, 0x31, 0xFC, 0x19, 0x07, 0x5B, 0xF2, 0x30, 0x26, 0x4B,
+	0x39, 0x3B, 0xF2, 0x1A, 0x06, 0x29, 0xFD, 0x33, 0x01, 0x99, 0xFF,
+	0x15, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28,
+	0x01, 0x3A, 0xFD, 0x00, 0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25,
+	0x79, 0xF2, 0x12, 0x07, 0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35,
+	0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC4, 0xFF, 0x70, 0x00, 0x84, 0xFF,
+	0x19, 0x00, 0x4D, 0x01, 0x22, 0xFA, 0x70, 0x46, 0x0A, 0x10, 0xFC,
+	0xF7, 0xEB, 0x04, 0x08, 0xFD, 0x9A, 0x01, 0x4F, 0xFF, 0x2E, 0x00,
+	0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x66, 0xFF, 0x5E, 0x01, 0x90,
+	0xFD, 0xD2, 0x03, 0x47, 0xFA, 0xC3, 0x09, 0x48, 0x48, 0x5A, 0xFE,
+	0x33, 0xFF, 0x45, 0x01, 0xE2, 0xFE, 0xBE, 0x00, 0xA5, 0xFF, 0x16,
+	0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4B, 0xFC,
+	0xA9, 0x06, 0xD2, 0xF3, 0x81, 0x1E, 0xE4, 0x3E, 0xEF, 0xF3, 0xDE,
+	0x04, 0xF9, 0xFD, 0xB7, 0x00, 0xD8, 0xFF, 0xFD, 0xFF, 0x03, 0x00,
+	0xFD, 0xFF, 0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0,
+	0x06, 0x8C, 0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07,
+	0x4E, 0xFC, 0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x08,
+	0x00, 0xE4, 0xFF, 0x1D, 0x00, 0x2D, 0x00, 0xEA, 0xFE, 0x56, 0x03,
+	0x6D, 0xF6, 0x17, 0x43, 0x70, 0x17, 0xA6, 0xF5, 0xF3, 0x05, 0x91,
+	0xFC, 0xCC, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1E, 0x00,
+	0x86, 0xFF, 0x0E, 0x01, 0x3B, 0xFE, 0x82, 0x02, 0xE0, 0xFC, 0x73,
+	0x03, 0xF6, 0x48, 0xE9, 0x03, 0xAD, 0xFC, 0x9C, 0x02, 0x2D, 0xFE,
+	0x14, 0x01, 0x83, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33,
+	0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x99, 0xFC, 0xE1, 0x05, 0xD1, 0xF5,
+	0xDC, 0x16, 0x65, 0x43, 0xAD, 0xF6, 0x31, 0x03, 0x00, 0xFF, 0x20,
+	0x00, 0x23, 0x00, 0xE1, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00,
+	0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8, 0xF1, 0x62,
+	0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC, 0x82, 0x01,
+	0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0xFF, 0xFF, 0xD3,
+	0xFF, 0xC1, 0x00, 0xE7, 0xFD, 0xFA, 0x04, 0xC4, 0xF3, 0x7E, 0x3E,
+	0x19, 0x1F, 0xB0, 0xF3, 0xB5, 0x06, 0x47, 0xFC, 0xE4, 0x01, 0x36,
+	0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x15, 0x00, 0xA8, 0xFF, 0xB8, 0x00,
+	0xF0, 0xFE, 0x2B, 0x01, 0x63, 0xFF, 0xF6, 0xFD, 0x2C, 0x48, 0x47,
+	0x0A, 0x14, 0xFA, 0xEB, 0x03, 0x84, 0xFD, 0x63, 0x01, 0x64, 0xFF,
+	0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x96,
+	0x01, 0x13, 0xFD, 0xD5, 0x04, 0x2C, 0xF8, 0x7D, 0x0F, 0xA3, 0x46,
+	0x76, 0xFA, 0x22, 0x01, 0x32, 0x00, 0x76, 0xFF, 0x76, 0x00, 0xC1,
+	0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A, 0xFF, 0xE4, 0x01,
+	0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2, 0xDD, 0x24, 0x54, 0x3A, 0x74,
+	0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20, 0x01, 0xA3, 0xFF, 0x11, 0x00,
+	0x00, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x95, 0xFF, 0x3B, 0x01, 0x1B,
+	0xFD, 0x2D, 0x06, 0x24, 0xF2, 0xD3, 0x38, 0xC6, 0x26, 0x45, 0xF2,
+	0x1D, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD,
+	0xFF, 0x0D, 0x00, 0xC9, 0xFF, 0x61, 0x00, 0xA2, 0xFF, 0xE2, 0xFF,
+	0xAE, 0x01, 0x6B, 0xF9, 0xF2, 0x45, 0x4A, 0x11, 0x8F, 0xF7, 0x1D,
+	0x05, 0xF1, 0xFC, 0xA4, 0x01, 0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF,
+	0x00, 0x00, 0x25, 0x00, 0x6C, 0xFF, 0x51, 0x01, 0xAC, 0xFD, 0x9A,
+	0x03, 0xBA, 0xFA, 0x9E, 0x08, 0x81, 0x48, 0x40, 0xFF, 0xC6, 0xFE,
+	0x80, 0x01, 0xC2, 0xFE, 0xCE, 0x00, 0x9F, 0xFF, 0x17, 0x00, 0xFE,
+	0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE1, 0x01, 0x55, 0xFC, 0x8C, 0x06,
+	0x22, 0xF4, 0x2C, 0x1D, 0xC0, 0x3F, 0x55, 0xF4, 0x9B, 0x04, 0x23,
+	0xFE, 0x9F, 0x00, 0xE4, 0xFF, 0xF9, 0xFF, 0x04, 0x00, 0xFD, 0xFF,
+	0x27, 0x00, 0x66, 0xFF, 0x96, 0x01, 0x8E, 0xFC, 0xE7, 0x06, 0x81,
+	0xF1, 0x48, 0x32, 0x34, 0x2E, 0x8D, 0xF1, 0x1C, 0x07, 0x5A, 0xFC,
+	0xBB, 0x01, 0x53, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE9,
+	0xFF, 0x0F, 0x00, 0x48, 0x00, 0xB9, 0xFE, 0xA6, 0x03, 0xE4, 0xF5,
+	0x60, 0x42, 0xC1, 0x18, 0x47, 0xF5, 0x1A, 0x06, 0x81, 0xFC, 0xD2,
+	0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8B, 0xFF,
+	0xFF, 0x00, 0x5A, 0xFE, 0x46, 0x02, 0x52, 0xFD, 0x70, 0x02, 0xED,
+	0x48, 0xF5, 0x04, 0x3B, 0xFC, 0xD7, 0x02, 0x0F, 0xFE, 0x23, 0x01,
+	0x7E, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40,
+	0xFF, 0xC1, 0x01, 0xAB, 0xFC, 0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15,
+	0x0B, 0x44, 0x42, 0xF7, 0xDC, 0x02, 0x32, 0xFF, 0x04, 0x00, 0x31,
+	0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x47, 0xFF,
+	0xCE, 0x01, 0x41, 0xFC, 0x2A, 0x07, 0xC2, 0xF1, 0x1B, 0x2B, 0x25,
+	0x35, 0xA8, 0xF1, 0xA7, 0x06, 0xC2, 0xFC, 0x74, 0x01, 0x78, 0xFF,
+	0x20, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x04, 0x00, 0xC7, 0xFF, 0xD9,
+	0x00, 0xBF, 0xFD, 0x38, 0x05, 0x69, 0xF3, 0x96, 0x3D, 0x6F, 0x20,
+	0x66, 0xF3, 0xCE, 0x06, 0x3F, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36,
+	0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAE, 0xFF, 0xA9, 0x00, 0x0F, 0xFF,
+	0xF0, 0x00, 0xCD, 0xFF, 0x1B, 0xFD, 0xE4, 0x47, 0x73, 0x0B, 0xA2,
+	0xF9, 0x23, 0x04, 0x68, 0xFD, 0x70, 0x01, 0x5F, 0xFF, 0x29, 0x00,
+	0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x55, 0xFF, 0x8B, 0x01, 0x2B,
+	0xFD, 0xA1, 0x04, 0x9B, 0xF8, 0x42, 0x0E, 0x0F, 0x47, 0x38, 0xFB,
+	0xBE, 0x00, 0x6A, 0x00, 0x58, 0xFF, 0x85, 0x00, 0xBB, 0xFF, 0x10,
+	0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC,
+	0xFD, 0x06, 0xCB, 0xF2, 0x8A, 0x23, 0x58, 0x3B, 0xB4, 0xF2, 0xBA,
+	0x05, 0x6A, 0xFD, 0x0B, 0x01, 0xAE, 0xFF, 0x0D, 0x00, 0x00, 0x00,
+	0xFF, 0xFF, 0x19, 0x00, 0x8C, 0xFF, 0x4D, 0x01, 0xFE, 0xFC, 0x56,
+	0x06, 0xF7, 0xF1, 0xBF, 0x37, 0x15, 0x28, 0x18, 0xF2, 0x25, 0x07,
+	0x34, 0xFC, 0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C,
+	0x00, 0xCF, 0xFF, 0x52, 0x00, 0xC0, 0xFF, 0xAC, 0xFF, 0x0C, 0x02,
+	0xBC, 0xF8, 0x6D, 0x45, 0x8E, 0x12, 0x24, 0xF7, 0x4D, 0x05, 0xDB,
+	0xFC, 0xAE, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00,
+	0x24, 0x00, 0x71, 0xFF, 0x43, 0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E,
+	0xFB, 0x7E, 0x07, 0xAF, 0x48, 0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01,
+	0xA3, 0xFE, 0xDD, 0x00, 0x99, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36,
+	0x00, 0x37, 0xFF, 0xDD, 0x01, 0x60, 0xFC, 0x6D, 0x06, 0x76, 0xF4,
+	0xD8, 0x1B, 0x95, 0x40, 0xC3, 0xF4, 0x56, 0x04, 0x4E, 0xFE, 0x85,
+	0x00, 0xF1, 0xFF, 0xF4, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x29, 0x00,
+	0x60, 0xFF, 0xA2, 0x01, 0x7C, 0xFC, 0xFB, 0x06, 0x7C, 0xF1, 0x15,
+	0x31, 0x73, 0x2F, 0x81, 0xF1, 0x10, 0x07, 0x67, 0xFC, 0xB1, 0x01,
+	0x58, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x02,
+	0x00, 0x63, 0x00, 0x8A, 0xFE, 0xF3, 0x03, 0x63, 0xF5, 0xA1, 0x41,
+	0x12, 0x1A, 0xEB, 0xF4, 0x3F, 0x06, 0x72, 0xFC, 0xD7, 0x01, 0x39,
+	0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x91, 0xFF, 0xF1, 0x00,
+	0x79, 0xFE, 0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48, 0x07,
+	0x06, 0xC7, 0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78, 0xFF,
+	0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x43, 0xFF, 0xBA,
+	0x01, 0xBF, 0xFC, 0x8B, 0x05, 0x99, 0xF6, 0x43, 0x14, 0xA9, 0x44,
+	0xDE, 0xF7, 0x85, 0x02, 0x65, 0xFF, 0xE7, 0xFF, 0x3F, 0x00, 0xD6,
+	0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD5, 0x01,
+	0x3A, 0xFC, 0x2A, 0x07, 0xE3, 0xF1, 0xD1, 0x29, 0x46, 0x36, 0xC5,
+	0xF1, 0x87, 0x06, 0xDA, 0xFC, 0x64, 0x01, 0x80, 0xFF, 0x1E, 0x00,
+	0xFE, 0xFF, 0x01, 0x00, 0x08, 0x00, 0xBC, 0xFF, 0xEF, 0x00, 0x9A,
+	0xFD, 0x72, 0x05, 0x16, 0xF3, 0xA5, 0x3C, 0xC4, 0x21, 0x21, 0xF3,
+	0xE4, 0x06, 0x39, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD,
+	0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E, 0xFF, 0xB6, 0x00,
+	0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C, 0x31, 0xF9, 0x5A,
+	0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF, 0x80, 0x01, 0x45, 0xFD, 0x6C,
+	0x04, 0x0B, 0xF9, 0x0B, 0x0D, 0x73, 0x47, 0x02, 0xFC, 0x58, 0x00,
+	0xA3, 0x00, 0x39, 0xFF, 0x94, 0x00, 0xB5, 0xFF, 0x12, 0x00, 0xFD,
+	0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x37, 0xFC, 0xEB, 0x06,
+	0x0B, 0xF3, 0x35, 0x22, 0x52, 0x3C, 0xFD, 0xF2, 0x84, 0x05, 0x8D,
+	0xFD, 0xF6, 0x00, 0xB8, 0xFF, 0x09, 0x00, 0x01, 0x00, 0xFE, 0xFF,
+	0x1D, 0x00, 0x83, 0xFF, 0x5E, 0x01, 0xE3, 0xFC, 0x7B, 0x06, 0xD0,
+	0xF1, 0xA5, 0x36, 0x62, 0x29, 0xEF, 0xF1, 0x29, 0x07, 0x39, 0xFC,
+	0xD7, 0x01, 0x42, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD5,
+	0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77, 0xFF, 0x67, 0x02, 0x14, 0xF8,
+	0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6, 0x7C, 0x05, 0xC5, 0xFC, 0xB7,
+	0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00,
+	0x76, 0xFF, 0x35, 0x01, 0xE7, 0xFD, 0x26, 0x03, 0xA1, 0xFB, 0x64,
+	0x06, 0xD2, 0x48, 0x21, 0x01, 0xE8, 0xFD, 0xF7, 0x01, 0x83, 0xFE,
+	0xEC, 0x00, 0x93, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39,
+	0xFF, 0xD9, 0x01, 0x6D, 0xFC, 0x4B, 0x06, 0xCD, 0xF4, 0x83, 0x1A,
+	0x5F, 0x41, 0x3A, 0xF5, 0x0C, 0x04, 0x7B, 0xFE, 0x6C, 0x00, 0xFE,
+	0xFF, 0xEF, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5A, 0xFF,
+	0xAD, 0x01, 0x6C, 0xFC, 0x0C, 0x07, 0x7F, 0xF1, 0xDC, 0x2F, 0xAD,
+	0x30, 0x7D, 0xF1, 0x01, 0x07, 0x76, 0xFC, 0xA6, 0x01, 0x5E, 0xFF,
+	0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D,
+	0x00, 0x5D, 0xFE, 0x3E, 0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B,
+	0x93, 0xF4, 0x62, 0x06, 0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36,
+	0x00, 0xFE, 0xFF, 0x19, 0x00, 0x97, 0xFF, 0xE2, 0x00, 0x98, 0xFE,
+	0xCF, 0x01, 0x33, 0xFE, 0x7D, 0x00, 0xBB, 0x48, 0x1F, 0x07, 0x54,
+	0xFB, 0x4C, 0x03, 0xD3, 0xFD, 0x3F, 0x01, 0x73, 0xFF, 0x23, 0x00,
+	0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB1, 0x01, 0xD3,
+	0xFC, 0x5D, 0x05, 0x01, 0xF7, 0xFB, 0x12, 0x3F, 0x45, 0x83, 0xF8,
+	0x2A, 0x02, 0x9A, 0xFF, 0xCA, 0xFF, 0x4E, 0x00, 0xD1, 0xFF, 0x0C,
+	0x00, 0xFD, 0xFF, 0x34, 0x00, 0x40, 0xFF, 0xDA, 0x01, 0x35, 0xFC,
+	0x27, 0x07, 0x09, 0xF2, 0x85, 0x28, 0x63, 0x37, 0xE9, 0xF1, 0x63,
+	0x06, 0xF5, 0xFC, 0x53, 0x01, 0x89, 0xFF, 0x1A, 0x00, 0xFE, 0xFF,
+	0x00, 0x00, 0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8,
+	0x05, 0xCC, 0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06,
+	0x35, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11,
+	0x00, 0xB9, 0xFF, 0x8A, 0x00, 0x4D, 0xFF, 0x7D, 0x00, 0x9C, 0x00,
+	0x7B, 0xFB, 0x31, 0x47, 0xD9, 0x0D, 0xC0, 0xF8, 0x8F, 0x04, 0x34,
+	0xFD, 0x87, 0x01, 0x56, 0xFF, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x29, 0x00, 0x5E, 0xFF, 0x74, 0x01, 0x5F, 0xFD, 0x35, 0x04, 0x7C,
+	0xF9, 0xD8, 0x0B, 0xC9, 0x47, 0xD4, 0xFC, 0xF0, 0xFF, 0xDD, 0x00,
+	0x19, 0xFF, 0xA4, 0x00, 0xAF, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36,
+	0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD5, 0x06, 0x4F, 0xF3,
+	0xE0, 0x20, 0x45, 0x3D, 0x4D, 0xF3, 0x4B, 0x05, 0xB3, 0xFD, 0xE0,
+	0x00, 0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00,
+	0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1, 0xF1, 0x86,
+	0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC, 0xD1, 0x01,
+	0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xDA, 0xFF, 0x36,
+	0x00, 0xFA, 0xFF, 0x43, 0xFF, 0xBF, 0x02, 0x75, 0xF7, 0x42, 0x44,
+	0x20, 0x15, 0x55, 0xF6, 0xA9, 0x05, 0xB2, 0xFC, 0xBF, 0x01, 0x41,
+	0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7C, 0xFF,
+	0x27, 0x01, 0x05, 0xFE, 0xEB, 0x02, 0x14, 0xFC, 0x50, 0x05, 0xEA,
+	0x48, 0x1B, 0x02, 0x78, 0xFD, 0x32, 0x02, 0x64, 0xFE, 0xFA, 0x00,
+	0x8D, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD4,
+	0x01, 0x7C, 0xFC, 0x27, 0x06, 0x28, 0xF5, 0x31, 0x19, 0x21, 0x42,
+	0xB8, 0xF5, 0xC0, 0x03, 0xAA, 0xFE, 0x51, 0x00, 0x0B, 0x00, 0xEA,
+	0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x54, 0xFF, 0xB7, 0x01,
+	0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1, 0x9F, 0x2E, 0xE3, 0x31, 0x7E,
+	0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A, 0x01, 0x64, 0xFF, 0x28, 0x00,
+	0xFD, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xE8, 0xFF, 0x96, 0x00, 0x31,
+	0xFE, 0x84, 0x04, 0x79, 0xF4, 0x07, 0x40, 0xBA, 0x1C, 0x3E, 0xF4,
+	0x82, 0x06, 0x58, 0xFC, 0xE0, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE,
+	0xFF, 0x18, 0x00, 0x9D, 0xFF, 0xD3, 0x00, 0xB8, 0xFE, 0x93, 0x01,
+	0xA1, 0xFE, 0x8E, 0xFF, 0x92, 0x48, 0x3D, 0x08, 0xE1, 0xFA, 0x86,
+	0x03, 0xB6, 0xFD, 0x4C, 0x01, 0x6D, 0xFF, 0x25, 0x00, 0x00, 0x00,
+	0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xA8, 0x01, 0xE9, 0xFC, 0x2D,
+	0x05, 0x6B, 0xF7, 0xB6, 0x11, 0xC8, 0x45, 0x30, 0xF9, 0xCD, 0x01,
+	0xD0, 0xFF, 0xAC, 0xFF, 0x5C, 0x00, 0xCB, 0xFF, 0x0D, 0x00, 0xFD,
+	0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDF, 0x01, 0x33, 0xFC, 0x20, 0x07,
+	0x35, 0xF2, 0x36, 0x27, 0x78, 0x38, 0x14, 0xF2, 0x3B, 0x06, 0x11,
+	0xFD, 0x41, 0x01, 0x92, 0xFF, 0x17, 0x00, 0xFF, 0xFF, 0x00, 0x00,
+	0x10, 0x00, 0xA7, 0xFF, 0x19, 0x01, 0x53, 0xFD, 0xDB, 0x05, 0x88,
+	0xF2, 0xAD, 0x3A, 0x6D, 0x24, 0xA4, 0xF2, 0x08, 0x07, 0x32, 0xFC,
+	0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBF,
+	0xFF, 0x7B, 0x00, 0x6C, 0xFF, 0x44, 0x00, 0x01, 0x01, 0xB6, 0xFA,
+	0xC8, 0x46, 0x13, 0x0F, 0x51, 0xF8, 0xC4, 0x04, 0x1B, 0xFD, 0x92,
+	0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00,
+	0x63, 0xFF, 0x67, 0x01, 0x7A, 0xFD, 0xFE, 0x03, 0xEE, 0xF9, 0xAA,
+	0x0A, 0x16, 0x48, 0xAC, 0xFD, 0x86, 0xFF, 0x17, 0x01, 0xFA, 0xFE,
+	0xB3, 0x00, 0xAA, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36,
+	0xFF, 0xE5, 0x01, 0x44, 0xFC, 0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F,
+	0x31, 0x3E, 0xA5, 0xF3, 0x0F, 0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF,
+	0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x73, 0xFF,
+	0x7D, 0x01, 0xB3, 0xFC, 0xBB, 0x06, 0x9A, 0xF1, 0x60, 0x34, 0xF5,
+	0x2B, 0xB0, 0xF1, 0x28, 0x07, 0x47, 0xFC, 0xCA, 0x01, 0x4A, 0xFF,
+	0x30, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDF, 0xFF, 0x28, 0x00, 0x17,
+	0x00, 0x10, 0xFF, 0x15, 0x03, 0xDD, 0xF6, 0x9E, 0x43, 0x6C, 0x16,
+	0xF1, 0xF5, 0xD3, 0x05, 0x9F, 0xFC, 0xC6, 0x01, 0x3F, 0xFF, 0x33,
+	0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81, 0xFF, 0x19, 0x01,
+	0x23, 0xFE, 0xB0, 0x02, 0x87, 0xFC, 0x41, 0x04, 0xF4, 0x48, 0x1C,
+	0x03, 0x06, 0xFD, 0x6E, 0x02, 0x45, 0xFE, 0x09, 0x01, 0x88, 0xFF,
+	0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCE, 0x01, 0x8C,
+	0xFC, 0x00, 0x06, 0x86, 0xF5, 0xE0, 0x17, 0xDB, 0x42, 0x3F, 0xF6,
+	0x71, 0x03, 0xD9, 0xFE, 0x36, 0x00, 0x18, 0x00, 0xE5, 0xFF, 0x07,
+	0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4F, 0xFF, 0xC1, 0x01, 0x52, 0xFC,
+	0x22, 0x07, 0x98, 0xF1, 0x5E, 0x2D, 0x13, 0x33, 0x87, 0xF1, 0xD8,
+	0x06, 0x9B, 0xFC, 0x8D, 0x01, 0x6B, 0xFF, 0x25, 0x00, 0xFD, 0xFF,
+	0x03, 0x00, 0xFC, 0xFF, 0xDC, 0xFF, 0xAF, 0x00, 0x07, 0xFE, 0xC8,
+	0x04, 0x10, 0xF4, 0x2D, 0x3F, 0x0F, 0x1E, 0xED, 0xF3, 0xA0, 0x06,
+	0x4E, 0xFC, 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16,
+	0x00, 0xA3, 0xFF, 0xC3, 0x00, 0xD7, 0xFE, 0x58, 0x01, 0x0F, 0xFF,
+	0xA6, 0xFE, 0x5D, 0x48, 0x61, 0x09, 0x6E, 0xFA, 0xC0, 0x03, 0x99,
+	0xFD, 0x59, 0x01, 0x68, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+	0x2E, 0x00, 0x4E, 0xFF, 0x9E, 0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7,
+	0xF7, 0x75, 0x10, 0x48, 0x46, 0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00,
+	0x8E, 0xFF, 0x6B, 0x00, 0xC6, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35,
+	0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x16, 0x07, 0x67, 0xF2,
+	0xE5, 0x25, 0x87, 0x39, 0x47, 0xF2, 0x10, 0x06, 0x30, 0xFD, 0x2F,
+	0x01, 0x9C, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x13, 0x00,
+	0x9D, 0xFF, 0x2D, 0x01, 0x33, 0xFD, 0x0B, 0x06, 0x4D, 0xF2, 0xA5,
+	0x39, 0xBF, 0x25, 0x6D, 0xF2, 0x15, 0x07, 0x31, 0xFC, 0xE2, 0x01,
+	0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, 0xC5, 0xFF, 0x6D,
+	0x00, 0x8B, 0xFF, 0x0D, 0x00, 0x63, 0x01, 0xF9, 0xF9, 0x55, 0x46,
+	0x51, 0x10, 0xE3, 0xF7, 0xF7, 0x04, 0x03, 0xFD, 0x9D, 0x01, 0x4E,
+	0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x68, 0xFF,
+	0x5B, 0x01, 0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09, 0x57,
+	0x48, 0x8D, 0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2, 0x00,
+	0xA4, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3,
+	0x01, 0x4D, 0xFC, 0xA3, 0x06, 0xE4, 0xF3, 0x36, 0x1E, 0x16, 0x3F,
+	0x05, 0xF4, 0xCF, 0x04, 0x02, 0xFE, 0xB2, 0x00, 0xDB, 0xFF, 0xFC,
+	0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6C, 0xFF, 0x8B, 0x01,
+	0x9D, 0xFC, 0xD5, 0x06, 0x89, 0xF1, 0x35, 0x33, 0x3A, 0x2D, 0x9A,
+	0xF1, 0x23, 0x07, 0x51, 0xFC, 0xC2, 0x01, 0x4F, 0xFF, 0x2F, 0x00,
+	0xFD, 0xFF, 0x07, 0x00, 0xE5, 0xFF, 0x1A, 0x00, 0x33, 0x00, 0xDF,
+	0xFE, 0x68, 0x03, 0x4E, 0xF6, 0xEE, 0x42, 0xBB, 0x17, 0x90, 0xF5,
+	0xFC, 0x05, 0x8E, 0xFC, 0xCD, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE,
+	0xFF, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42, 0xFE, 0x74, 0x02,
+	0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04, 0x94, 0xFC, 0xA9,
+	0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F, 0x00, 0x00, 0x00,
+	0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF, 0xC7, 0x01, 0x9D, 0xFC, 0xD8,
+	0x05, 0xE7, 0xF5, 0x91, 0x16, 0x89, 0x43, 0xCD, 0xF6, 0x1E, 0x03,
+	0x0B, 0xFF, 0x1A, 0x00, 0x26, 0x00, 0xE0, 0xFF, 0x08, 0x00, 0xFD,
+	0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC9, 0x01, 0x48, 0xFC, 0x28, 0x07,
+	0xAD, 0xF1, 0x19, 0x2C, 0x3F, 0x34, 0x97, 0xF1, 0xBE, 0x06, 0xB0,
+	0xFC, 0x7F, 0x01, 0x72, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x02, 0x00,
+	0x00, 0x00, 0xD0, 0xFF, 0xC7, 0x00, 0xDE, 0xFD, 0x08, 0x05, 0xB0,
+	0xF3, 0x4A, 0x3E, 0x64, 0x1F, 0xA0, 0xF3, 0xBB, 0x06, 0x45, 0xFC,
+	0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x15, 0x00, 0xA9,
+	0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D, 0x01, 0x7A, 0xFF, 0xC5, 0xFD,
+	0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9, 0xF8, 0x03, 0x7D, 0xFD, 0x66,
+	0x01, 0x63, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00,
+	0x52, 0xFF, 0x93, 0x01, 0x18, 0xFD, 0xC9, 0x04, 0x45, 0xF8, 0x36,
+	0x0F, 0xBB, 0x46, 0xA1, 0xFA, 0x0C, 0x01, 0x3E, 0x00, 0x70, 0xFF,
+	0x7A, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39,
+	0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x09, 0x07, 0x9D, 0xF2, 0x92, 0x24,
+	0x8F, 0x3A, 0x82, 0xF2, 0xE1, 0x05, 0x50, 0xFD, 0x1B, 0x01, 0xA6,
+	0xFF, 0x10, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x17, 0x00, 0x93, 0xFF,
+	0x3F, 0x01, 0x15, 0xFD, 0x36, 0x06, 0x19, 0xF2, 0x97, 0x38, 0x11,
+	0x27, 0x3B, 0xF2, 0x1F, 0x07, 0x32, 0xFC, 0xDF, 0x01, 0x3D, 0xFF,
+	0x34, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9,
+	0xFF, 0xD6, 0xFF, 0xC3, 0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11,
+	0x77, 0xF7, 0x28, 0x05, 0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F,
+	0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6D, 0xFF, 0x4E, 0x01,
+	0xB3, 0xFD, 0x8D, 0x03, 0xD4, 0xFA, 0x5D, 0x08, 0x8D, 0x48, 0x74,
+	0xFF, 0xAE, 0xFE, 0x8D, 0x01, 0xBB, 0xFE, 0xD1, 0x00, 0x9E, 0xFF,
+	0x18, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE0, 0x01, 0x57,
+	0xFC, 0x85, 0x06, 0x34, 0xF4, 0xE0, 0x1C, 0xF0, 0x3F, 0x6D, 0xF4,
+	0x8C, 0x04, 0x2C, 0xFE, 0x99, 0x00, 0xE7, 0xFF, 0xF8, 0xFF, 0x04,
+	0x00, 0xFD, 0xFF, 0x27, 0x00, 0x65, 0xFF, 0x98, 0x01, 0x8A, 0xFC,
+	0xEC, 0x06, 0x7F, 0xF1, 0x04, 0x32, 0x7B, 0x2E, 0x8A, 0xF1, 0x1A,
+	0x07, 0x5D, 0xFC, 0xB8, 0x01, 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF,
+	0x06, 0x00, 0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8,
+	0x03, 0xC7, 0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06,
+	0x7D, 0xFC, 0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C,
+	0x00, 0x8D, 0xFF, 0xFC, 0x00, 0x61, 0xFE, 0x39, 0x02, 0x6B, 0xFD,
+	0x37, 0x02, 0xEB, 0x48, 0x31, 0x05, 0x21, 0xFC, 0xE4, 0x02, 0x08,
+	0xFE, 0x26, 0x01, 0x7C, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+	0x32, 0x00, 0x41, 0xFF, 0xC0, 0x01, 0xAF, 0xFC, 0xAD, 0x05, 0x4A,
+	0xF6, 0x44, 0x15, 0x2F, 0x44, 0x64, 0xF7, 0xC9, 0x02, 0x3D, 0xFF,
+	0xFE, 0xFF, 0x34, 0x00, 0xDB, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x32,
+	0x00, 0x47, 0xFF, 0xD0, 0x01, 0x40, 0xFC, 0x2A, 0x07, 0xCA, 0xF1,
+	0xD1, 0x2A, 0x65, 0x35, 0xAE, 0xF1, 0xA0, 0x06, 0xC7, 0xFC, 0x70,
+	0x01, 0x7A, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x05, 0x00,
+	0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56, 0xF3, 0x61,
+	0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC, 0xE6, 0x01,
+	0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA5,
+	0x00, 0x16, 0xFF, 0xE3, 0x00, 0xE4, 0xFF, 0xEB, 0xFC, 0xD2, 0x47,
+	0xB6, 0x0B, 0x89, 0xF9, 0x2F, 0x04, 0x62, 0xFD, 0x72, 0x01, 0x5E,
+	0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x56, 0xFF,
+	0x88, 0x01, 0x31, 0xFD, 0x95, 0x04, 0xB4, 0xF8, 0xFC, 0x0D, 0x26,
+	0x47, 0x64, 0xFB, 0xA7, 0x00, 0x77, 0x00, 0x51, 0xFF, 0x89, 0x00,
+	0xBA, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6,
+	0x01, 0x34, 0xFC, 0xF9, 0x06, 0xD9, 0xF2, 0x3F, 0x23, 0x90, 0x3B,
+	0xC4, 0xF2, 0xAE, 0x05, 0x72, 0xFD, 0x07, 0x01, 0xB0, 0xFF, 0x0C,
+	0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8A, 0xFF, 0x51, 0x01,
+	0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1, 0x82, 0x37, 0x60, 0x28, 0x0E,
+	0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40, 0xFF, 0x34, 0x00,
+	0xFD, 0xFF, 0x0C, 0x00, 0xD0, 0xFF, 0x4F, 0x00, 0xC7, 0xFF, 0xA0,
+	0xFF, 0x20, 0x02, 0x96, 0xF8, 0x4E, 0x45, 0xD7, 0x12, 0x0D, 0xF7,
+	0x58, 0x05, 0xD6, 0xFC, 0xB0, 0x01, 0x47, 0xFF, 0x30, 0x00, 0xFF,
+	0xFF, 0x00, 0x00, 0x23, 0x00, 0x72, 0xFF, 0x40, 0x01, 0xD0, 0xFD,
+	0x53, 0x03, 0x47, 0xFB, 0x3F, 0x07, 0xB8, 0x48, 0x62, 0x00, 0x3F,
+	0xFE, 0xC8, 0x01, 0x9C, 0xFE, 0xE0, 0x00, 0x98, 0xFF, 0x19, 0x00,
+	0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDC, 0x01, 0x63, 0xFC, 0x66,
+	0x06, 0x89, 0xF4, 0x8C, 0x1B, 0xC3, 0x40, 0xDD, 0xF4, 0x46, 0x04,
+	0x58, 0xFE, 0x80, 0x00, 0xF4, 0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFD,
+	0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA5, 0x01, 0x78, 0xFC, 0xFF, 0x06,
+	0x7D, 0xF1, 0xCF, 0x30, 0xB8, 0x2F, 0x80, 0xF1, 0x0D, 0x07, 0x6A,
+	0xFC, 0xAE, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00,
+	0xEF, 0xFF, 0xFF, 0xFF, 0x69, 0x00, 0x80, 0xFE, 0x04, 0x04, 0x48,
+	0xF5, 0x74, 0x41, 0x5D, 0x1A, 0xD7, 0xF4, 0x47, 0x06, 0x6F, 0xFC,
+	0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x93,
+	0xFF, 0xED, 0x00, 0x80, 0xFE, 0xFD, 0x01, 0xDC, 0xFD, 0x3C, 0x01,
+	0xD5, 0x48, 0x45, 0x06, 0xAE, 0xFB, 0x1F, 0x03, 0xEA, 0xFD, 0x34,
+	0x01, 0x77, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00,
+	0x44, 0xFF, 0xB8, 0x01, 0xC3, 0xFC, 0x81, 0x05, 0xB0, 0xF6, 0xFA,
+	0x13, 0xCC, 0x44, 0x02, 0xF8, 0x71, 0x02, 0x71, 0xFF, 0xE1, 0xFF,
+	0x42, 0x00, 0xD5, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x43,
+	0xFF, 0xD6, 0x01, 0x39, 0xFC, 0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29,
+	0x85, 0x36, 0xCC, 0xF1, 0x7F, 0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82,
+	0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x09, 0x00, 0xBA, 0xFF,
+	0xF4, 0x00, 0x91, 0xFD, 0x7E, 0x05, 0x05, 0xF3, 0x6E, 0x3C, 0x10,
+	0x22, 0x12, 0xF3, 0xE9, 0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF,
+	0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB5, 0xFF, 0x96, 0x00, 0x35,
+	0xFF, 0xA9, 0x00, 0x4D, 0x00, 0x19, 0xFC, 0x7C, 0x47, 0xE8, 0x0C,
+	0x18, 0xF9, 0x66, 0x04, 0x48, 0xFD, 0x7E, 0x01, 0x5A, 0xFF, 0x2B,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5A, 0xFF, 0x7D, 0x01,
+	0x4B, 0xFD, 0x60, 0x04, 0x24, 0xF9, 0xC6, 0x0C, 0x86, 0x47, 0x30,
+	0xFC, 0x41, 0x00, 0xB0, 0x00, 0x32, 0xFF, 0x98, 0x00, 0xB4, 0xFF,
+	0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x38,
+	0xFC, 0xE6, 0x06, 0x19, 0xF3, 0xEA, 0x21, 0x8A, 0x3C, 0x0E, 0xF3,
+	0x78, 0x05, 0x96, 0xFD, 0xF1, 0x00, 0xBB, 0xFF, 0x08, 0x00, 0x01,
+	0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x81, 0xFF, 0x62, 0x01, 0xDD, 0xFC,
+	0x83, 0x06, 0xC9, 0xF1, 0x66, 0x36, 0xAC, 0x29, 0xE7, 0xF1, 0x2A,
+	0x07, 0x3A, 0xFC, 0xD5, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF,
+	0x0B, 0x00, 0xD6, 0xFF, 0x41, 0x00, 0xE4, 0xFF, 0x6B, 0xFF, 0x7B,
+	0x02, 0xF0, 0xF7, 0xBA, 0x44, 0x1E, 0x14, 0xA5, 0xF6, 0x86, 0x05,
+	0xC1, 0xFC, 0xB9, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00,
+	0x00, 0x22, 0x00, 0x77, 0xFF, 0x32, 0x01, 0xED, 0xFD, 0x19, 0x03,
+	0xBB, 0xFB, 0x26, 0x06, 0xD7, 0x48, 0x58, 0x01, 0xCF, 0xFD, 0x04,
+	0x02, 0x7D, 0xFE, 0xEF, 0x00, 0x92, 0xFF, 0x1B, 0x00, 0xFE, 0xFF,
+	0x35, 0x00, 0x39, 0xFF, 0xD8, 0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1,
+	0xF4, 0x38, 0x1A, 0x8C, 0x41, 0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE,
+	0x66, 0x00, 0x01, 0x00, 0xEE, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2B,
+	0x00, 0x59, 0xFF, 0xB0, 0x01, 0x69, 0xFC, 0x0F, 0x07, 0x80, 0xF1,
+	0x96, 0x2F, 0xF2, 0x30, 0x7C, 0xF1, 0xFD, 0x06, 0x7A, 0xFC, 0xA3,
+	0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF4, 0xFF,
+	0xF2, 0xFF, 0x83, 0x00, 0x53, 0xFE, 0x4E, 0x04, 0xD0, 0xF4, 0xAB,
+	0x40, 0xB2, 0x1B, 0x7F, 0xF4, 0x69, 0x06, 0x62, 0xFC, 0xDD, 0x01,
+	0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, 0x98, 0xFF, 0xDE,
+	0x00, 0x9F, 0xFE, 0xC2, 0x01, 0x4B, 0xFE, 0x48, 0x00, 0xB3, 0x48,
+	0x5E, 0x07, 0x3B, 0xFB, 0x59, 0x03, 0xCD, 0xFD, 0x42, 0x01, 0x71,
+	0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x47, 0xFF,
+	0xAF, 0x01, 0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12, 0x5C,
+	0x45, 0xA9, 0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51, 0x00,
+	0xD0, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x40, 0xFF, 0xDB,
+	0x01, 0x35, 0xFC, 0x25, 0x07, 0x13, 0xF2, 0x3A, 0x28, 0xA0, 0x37,
+	0xF2, 0xF1, 0x5A, 0x06, 0xFB, 0xFC, 0x4F, 0x01, 0x8B, 0xFF, 0x1A,
+	0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0D, 0x00, 0xAF, 0xFF, 0x09, 0x01,
+	0x6E, 0xFD, 0xB4, 0x05, 0xBC, 0xF2, 0x73, 0x3B, 0x64, 0x23, 0xD2,
+	0xF2, 0xFB, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00,
+	0xFD, 0xFF, 0x11, 0x00, 0xBB, 0xFF, 0x87, 0x00, 0x54, 0xFF, 0x70,
+	0x00, 0xB3, 0x00, 0x4E, 0xFB, 0x1A, 0x47, 0x1F, 0x0E, 0xA8, 0xF8,
+	0x9B, 0x04, 0x2E, 0xFD, 0x8A, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF,
+	0xFF, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71, 0x01, 0x65, 0xFD,
+	0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47, 0x03, 0xFD, 0xD9,
+	0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE, 0xFF, 0x14, 0x00,
+	0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3E, 0xFC, 0xD0,
+	0x06, 0x5E, 0xF3, 0x94, 0x20, 0x7B, 0x3D, 0x60, 0xF3, 0x3E, 0x05,
+	0xBB, 0xFD, 0xDB, 0x00, 0xC6, 0xFF, 0x04, 0x00, 0x02, 0x00, 0xFE,
+	0xFF, 0x20, 0x00, 0x79, 0xFF, 0x72, 0x01, 0xC4, 0xFC, 0xA4, 0x06,
+	0xAB, 0xF1, 0x46, 0x35, 0xF7, 0x2A, 0xC6, 0xF1, 0x2A, 0x07, 0x40,
+	0xFC, 0xCF, 0x01, 0x47, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00,
+	0xDB, 0xFF, 0x33, 0x00, 0x01, 0x00, 0x38, 0xFF, 0xD3, 0x02, 0x53,
+	0xF7, 0x1F, 0x44, 0x69, 0x15, 0x3F, 0xF6, 0xB2, 0x05, 0xAD, 0xFC,
+	0xC1, 0x01, 0x41, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20,
+	0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C, 0xFE, 0xDE, 0x02, 0x2E, 0xFC,
+	0x13, 0x05, 0xEC, 0x48, 0x54, 0x02, 0x5E, 0xFD, 0x3F, 0x02, 0x5D,
+	0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00,
+	0x3B, 0xFF, 0xD3, 0x01, 0x7F, 0xFC, 0x1F, 0x06, 0x3C, 0xF5, 0xE6,
+	0x18, 0x4D, 0x42, 0xD5, 0xF5, 0xAF, 0x03, 0xB4, 0xFE, 0x4B, 0x00,
+	0x0E, 0x00, 0xE9, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x53,
+	0xFF, 0xBA, 0x01, 0x5B, 0xFC, 0x1B, 0x07, 0x8B, 0xF1, 0x58, 0x2E,
+	0x26, 0x32, 0x80, 0xF1, 0xEA, 0x06, 0x8C, 0xFC, 0x97, 0x01, 0x66,
+	0xFF, 0x27, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF8, 0xFF, 0xE6, 0xFF,
+	0x9C, 0x00, 0x27, 0xFE, 0x94, 0x04, 0x61, 0xF4, 0xD7, 0x3F, 0x06,
+	0x1D, 0x2B, 0xF4, 0x89, 0x06, 0x56, 0xFC, 0xE0, 0x01, 0x37, 0xFF,
+	0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF,
+	0xFE, 0x86, 0x01, 0xBA, 0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08,
+	0xC7, 0xFA, 0x93, 0x03, 0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25,
+	0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4B, 0xFF, 0xA6, 0x01,
+	0xEE, 0xFC, 0x23, 0x05, 0x83, 0xF7, 0x6E, 0x11, 0xE5, 0x45, 0x57,
+	0xF9, 0xB8, 0x01, 0xDC, 0xFF, 0xA5, 0xFF, 0x5F, 0x00, 0xCA, 0xFF,
+	0x0D, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3D, 0xFF, 0xDF, 0x01, 0x32,
+	0xFC, 0x1E, 0x07, 0x40, 0xF2, 0xEB, 0x26, 0xB5, 0x38, 0x1F, 0xF2,
+	0x32, 0x06, 0x18, 0xFD, 0x3D, 0x01, 0x94, 0xFF, 0x16, 0x00, 0xFF,
+	0xFF, 0x00, 0x00, 0x11, 0x00, 0xA4, 0xFF, 0x1D, 0x01, 0x4C, 0xFD,
+	0xE6, 0x05, 0x7B, 0xF2, 0x71, 0x3A, 0xB8, 0x24, 0x97, 0xF2, 0x0B,
+	0x07, 0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF,
+	0x0F, 0x00, 0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17,
+	0x01, 0x8B, 0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04,
+	0x15, 0xFD, 0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00,
+	0x00, 0x28, 0x00, 0x64, 0xFF, 0x65, 0x01, 0x81, 0xFD, 0xF2, 0x03,
+	0x08, 0xFA, 0x68, 0x0A, 0x25, 0x48, 0xDE, 0xFD, 0x6E, 0xFF, 0x24,
+	0x01, 0xF3, 0xFE, 0xB6, 0x00, 0xA8, 0xFF, 0x15, 0x00, 0xFD, 0xFF,
+	0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x46, 0xFC, 0xB8, 0x06, 0xA8,
+	0xF3, 0x3F, 0x1F, 0x64, 0x3E, 0xBA, 0xF3, 0x01, 0x05, 0xE2, 0xFD,
+	0xC4, 0x00, 0xD2, 0xFF, 0x00, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x23,
+	0x00, 0x71, 0xFF, 0x81, 0x01, 0xAE, 0xFC, 0xC1, 0x06, 0x95, 0xF1,
+	0x1E, 0x34, 0x3E, 0x2C, 0xAB, 0xF1, 0x27, 0x07, 0x49, 0xFC, 0xC8,
+	0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE1, 0xFF,
+	0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD, 0xF6, 0x77,
+	0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC, 0xC8, 0x01,
+	0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83,
+	0xFF, 0x16, 0x01, 0x2A, 0xFE, 0xA3, 0x02, 0xA1, 0xFC, 0x06, 0x04,
+	0xF5, 0x48, 0x56, 0x03, 0xED, 0xFC, 0x7B, 0x02, 0x3E, 0xFE, 0x0C,
+	0x01, 0x86, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF,
+	0xCC, 0x01, 0x8F, 0xFC, 0xF8, 0x05, 0x9B, 0xF5, 0x96, 0x17, 0x02,
+	0x43, 0x5E, 0xF6, 0x5F, 0x03, 0xE4, 0xFE, 0x30, 0x00, 0x1B, 0x00,
+	0xE4, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3,
+	0x01, 0x4F, 0xFC, 0x24, 0x07, 0x9C, 0xF1, 0x17, 0x2D, 0x57, 0x33,
+	0x8A, 0xF1, 0xD3, 0x06, 0x9F, 0xFC, 0x8A, 0x01, 0x6D, 0xFF, 0x25,
+	0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0xD9, 0xFF, 0xB4, 0x00,
+	0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3, 0xFC, 0x3E, 0x5B, 0x1E, 0xDB,
+	0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00,
+	0xFE, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC0, 0x00, 0xDE, 0xFE, 0x4B,
+	0x01, 0x27, 0xFF, 0x73, 0xFE, 0x4F, 0x48, 0xA2, 0x09, 0x54, 0xFA,
+	0xCC, 0x03, 0x93, 0xFD, 0x5C, 0x01, 0x67, 0xFF, 0x27, 0x00, 0x00,
+	0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9C, 0x01, 0x05, 0xFD,
+	0xF1, 0x04, 0xF0, 0xF7, 0x2D, 0x10, 0x61, 0x46, 0x0D, 0xFA, 0x58,
+	0x01, 0x13, 0x00, 0x87, 0xFF, 0x6E, 0x00, 0xC4, 0xFF, 0x0E, 0x00,
+	0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x14,
+	0x07, 0x73, 0xF2, 0x99, 0x25, 0xC2, 0x39, 0x54, 0xF2, 0x05, 0x06,
+	0x37, 0xFD, 0x2B, 0x01, 0x9E, 0xFF, 0x13, 0x00, 0xFF, 0xFF, 0xFF,
+	0xFF, 0x14, 0x00, 0x9B, 0xFF, 0x31, 0x01, 0x2C, 0xFD, 0x15, 0x06,
+	0x41, 0xF2, 0x6A, 0x39, 0x0A, 0x26, 0x61, 0xF2, 0x17, 0x07, 0x31,
+	0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00,
+	0xC6, 0xFF, 0x69, 0x00, 0x91, 0xFF, 0x00, 0x00, 0x78, 0x01, 0xD0,
+	0xF9, 0x39, 0x46, 0x98, 0x10, 0xCB, 0xF7, 0x02, 0x05, 0xFE, 0xFC,
+	0x9F, 0x01, 0x4D, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26,
+	0x00, 0x69, 0xFF, 0x58, 0x01, 0x9D, 0xFD, 0xB9, 0x03, 0x7B, 0xFA,
+	0x40, 0x09, 0x63, 0x48, 0xBF, 0xFE, 0x03, 0xFF, 0x5F, 0x01, 0xD4,
+	0xFE, 0xC5, 0x00, 0xA2, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00,
+	0x36, 0xFF, 0xE2, 0x01, 0x4F, 0xFC, 0x9C, 0x06, 0xF5, 0xF3, 0xEA,
+	0x1D, 0x47, 0x3F, 0x1B, 0xF4, 0xC1, 0x04, 0x0B, 0xFE, 0xAC, 0x00,
+	0xDE, 0xFF, 0xFB, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6A,
+	0xFF, 0x8E, 0x01, 0x99, 0xFC, 0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32,
+	0x82, 0x2D, 0x96, 0xF1, 0x21, 0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50,
+	0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE6, 0xFF, 0x17, 0x00,
+	0x39, 0x00, 0xD4, 0xFE, 0x7A, 0x03, 0x2F, 0xF6, 0xC7, 0x42, 0x06,
+	0x18, 0x7B, 0xF5, 0x05, 0x06, 0x8A, 0xFC, 0xCF, 0x01, 0x3C, 0xFF,
+	0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x88, 0xFF, 0x07, 0x01, 0x49,
+	0xFE, 0x67, 0x02, 0x13, 0xFD, 0xFF, 0x02, 0xF4, 0x48, 0x5F, 0x04,
+	0x7A, 0xFC, 0xB6, 0x02, 0x20, 0xFE, 0x1B, 0x01, 0x81, 0xFF, 0x1F,
+	0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC6, 0x01,
+	0xA1, 0xFC, 0xCF, 0x05, 0xFC, 0xF5, 0x47, 0x16, 0xB0, 0x43, 0xEE,
+	0xF6, 0x0C, 0x03, 0x16, 0xFF, 0x14, 0x00, 0x29, 0x00, 0xDF, 0xFF,
+	0x09, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xCA, 0x01, 0x46,
+	0xFC, 0x29, 0x07, 0xB3, 0xF1, 0xD1, 0x2B, 0x81, 0x34, 0x9C, 0xF1,
+	0xB8, 0x06, 0xB5, 0xFC, 0x7C, 0x01, 0x74, 0xFF, 0x22, 0x00, 0xFE,
+	0xFF, 0x02, 0x00, 0x01, 0x00, 0xCE, 0xFF, 0xCC, 0x00, 0xD5, 0xFD,
+	0x16, 0x05, 0x9B, 0xF3, 0x18, 0x3E, 0xB1, 0x1F, 0x8F, 0xF3, 0xC0,
+	0x06, 0x43, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF,
+	0x15, 0x00, 0xAA, 0xFF, 0xB1, 0x00, 0xFE, 0xFE, 0x10, 0x01, 0x92,
+	0xFF, 0x94, 0xFD, 0x0D, 0x48, 0xCB, 0x0A, 0xE2, 0xF9, 0x04, 0x04,
+	0x77, 0xFD, 0x69, 0x01, 0x62, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF,
+	0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x91, 0x01, 0x1E, 0xFD, 0xBE, 0x04,
+	0x5E, 0xF8, 0xF0, 0x0E, 0xD3, 0x46, 0xCB, 0xFA, 0xF6, 0x00, 0x4B,
+	0x00, 0x69, 0xFF, 0x7D, 0x00, 0xBE, 0xFF, 0x10, 0x00, 0xFD, 0xFF,
+	0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA,
+	0xF2, 0x46, 0x24, 0xC8, 0x3A, 0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD,
+	0x17, 0x01, 0xA8, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18,
+	0x00, 0x91, 0xFF, 0x43, 0x01, 0x0E, 0xFD, 0x40, 0x06, 0x0F, 0xF2,
+	0x5B, 0x38, 0x5C, 0x27, 0x30, 0xF2, 0x21, 0x07, 0x33, 0xFC, 0xDE,
+	0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCC, 0xFF,
+	0x5A, 0x00, 0xAF, 0xFF, 0xCA, 0xFF, 0xD8, 0x01, 0x1C, 0xF9, 0xB8,
+	0x45, 0xDA, 0x11, 0x60, 0xF7, 0x33, 0x05, 0xE7, 0xFC, 0xA9, 0x01,
+	0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6E,
+	0xFF, 0x4B, 0x01, 0xB9, 0xFD, 0x80, 0x03, 0xEE, 0xFA, 0x1D, 0x08,
+	0x98, 0x48, 0xA8, 0xFF, 0x95, 0xFE, 0x9A, 0x01, 0xB4, 0xFE, 0xD4,
+	0x00, 0x9C, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF,
+	0xDF, 0x01, 0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C, 0x1F,
+	0x40, 0x85, 0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA, 0xFF,
+	0xF7, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9B,
+	0x01, 0x86, 0xFC, 0xF1, 0x06, 0x7E, 0xF1, 0xC0, 0x31, 0xC2, 0x2E,
+	0x87, 0xF1, 0x17, 0x07, 0x5F, 0xFC, 0xB6, 0x01, 0x55, 0xFF, 0x2D,
+	0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEB, 0xFF, 0x09, 0x00, 0x54, 0x00,
+	0xA4, 0xFE, 0xC9, 0x03, 0xAA, 0xF5, 0x0C, 0x42, 0x56, 0x19, 0x1E,
+	0xF5, 0x2B, 0x06, 0x7A, 0xFC, 0xD4, 0x01, 0x3A, 0xFF, 0x35, 0x00,
+	0xFE, 0xFF, 0x1C, 0x00, 0x8E, 0xFF, 0xF9, 0x00, 0x68, 0xFE, 0x2C,
+	0x02, 0x84, 0xFD, 0xFF, 0x01, 0xE6, 0x48, 0x6E, 0x05, 0x07, 0xFC,
+	0xF1, 0x02, 0x01, 0xFE, 0x29, 0x01, 0x7B, 0xFF, 0x21, 0x00, 0x00,
+	0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE, 0x01, 0xB4, 0xFC,
+	0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44, 0x86, 0xF7, 0xB6,
+	0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9, 0xFF, 0x0A, 0x00,
+	0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF, 0xD1, 0x01, 0x3E, 0xFC, 0x2B,
+	0x07, 0xD0, 0xF1, 0x89, 0x2A, 0xA6, 0x35, 0xB4, 0xF1, 0x99, 0x06,
+	0xCD, 0xFC, 0x6D, 0x01, 0x7C, 0xFF, 0x1F, 0x00, 0xFE, 0xFF, 0x01,
+	0x00, 0x06, 0x00, 0xC2, 0xFF, 0xE3, 0x00, 0xAE, 0xFD, 0x52, 0x05,
+	0x44, 0xF3, 0x2A, 0x3D, 0x06, 0x21, 0x47, 0xF3, 0xD8, 0x06, 0x3C,
+	0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00,
+	0xB0, 0xFF, 0xA2, 0x00, 0x1D, 0xFF, 0xD6, 0x00, 0xFC, 0xFF, 0xBC,
+	0xFC, 0xC0, 0x47, 0xFA, 0x0B, 0x70, 0xF9, 0x3C, 0x04, 0x5C, 0xFD,
+	0x75, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B,
+	0x00, 0x57, 0xFF, 0x86, 0x01, 0x36, 0xFD, 0x89, 0x04, 0xCD, 0xF8,
+	0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB, 0x91, 0x00, 0x83, 0x00, 0x4A,
+	0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00,
+	0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF5, 0x06, 0xE7, 0xF2, 0xF2,
+	0x22, 0xC7, 0x3B, 0xD4, 0xF2, 0xA2, 0x05, 0x7A, 0xFD, 0x02, 0x01,
+	0xB2, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x88,
+	0xFF, 0x55, 0x01, 0xF2, 0xFC, 0x67, 0x06, 0xE4, 0xF1, 0x44, 0x37,
+	0xAA, 0x28, 0x05, 0xF2, 0x27, 0x07, 0x36, 0xFC, 0xDA, 0x01, 0x41,
+	0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD2, 0xFF, 0x4C, 0x00,
+	0xCD, 0xFF, 0x94, 0xFF, 0x34, 0x02, 0x70, 0xF8, 0x2E, 0x45, 0x20,
+	0x13, 0xF6, 0xF6, 0x62, 0x05, 0xD1, 0xFC, 0xB2, 0x01, 0x46, 0xFF,
+	0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D,
+	0x01, 0xD6, 0xFD, 0x46, 0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48,
+	0x98, 0x00, 0x26, 0xFE, 0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96,
+	0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDB, 0x01,
+	0x66, 0xFC, 0x5E, 0x06, 0x9C, 0xF4, 0x40, 0x1B, 0xEF, 0x40, 0xF7,
+	0xF4, 0x35, 0x04, 0x62, 0xFE, 0x7A, 0x00, 0xF7, 0xFF, 0xF2, 0xFF,
+	0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5D, 0xFF, 0xA7, 0x01, 0x75,
+	0xFC, 0x03, 0x07, 0x7D, 0xF1, 0x8A, 0x30, 0xFF, 0x2F, 0x7E, 0xF1,
+	0x0A, 0x07, 0x6E, 0xFC, 0xAC, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0xFD,
+	0xFF, 0x05, 0x00, 0xF0, 0xFF, 0xFC, 0xFF, 0x6E, 0x00, 0x76, 0xFE,
+	0x15, 0x04, 0x2C, 0xF5, 0x49, 0x41, 0xA9, 0x1A, 0xC3, 0xF4, 0x4F,
+	0x06, 0x6C, 0xFC, 0xD9, 0x01, 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF,
+	0x1A, 0x00, 0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5,
+	0xFD, 0x05, 0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03,
+	0xE4, 0xFD, 0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF,
+	0xFF, 0x31, 0x00, 0x45, 0xFF, 0xB6, 0x01, 0xC8, 0xFC, 0x77, 0x05,
+	0xC7, 0xF6, 0xB1, 0x13, 0xED, 0x44, 0x26, 0xF8, 0x5D, 0x02, 0x7D,
+	0xFF, 0xDA, 0xFF, 0x46, 0x00, 0xD4, 0xFF, 0x0B, 0x00, 0xFD, 0xFF,
+	0x33, 0x00, 0x42, 0xFF, 0xD7, 0x01, 0x38, 0xFC, 0x29, 0x07, 0xF3,
+	0xF1, 0x3E, 0x29, 0xC6, 0x36, 0xD4, 0xF1, 0x77, 0x06, 0xE6, 0xFC,
+	0x5C, 0x01, 0x84, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A,
+	0x00, 0xB7, 0xFF, 0xF9, 0x00, 0x89, 0xFD, 0x8A, 0x05, 0xF4, 0xF2,
+	0x37, 0x3C, 0x5B, 0x22, 0x03, 0xF3, 0xED, 0x06, 0x37, 0xFC, 0xE6,
+	0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB6, 0xFF,
+	0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB, 0xFB, 0x69,
+	0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD, 0x81, 0x01,
+	0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5B,
+	0xFF, 0x7A, 0x01, 0x50, 0xFD, 0x54, 0x04, 0x3D, 0xF9, 0x82, 0x0C,
+	0x9A, 0x47, 0x5E, 0xFC, 0x2A, 0x00, 0xBD, 0x00, 0x2B, 0xFF, 0x9B,
+	0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF,
+	0xE6, 0x01, 0x3A, 0xFC, 0xE2, 0x06, 0x28, 0xF3, 0x9E, 0x21, 0xC0,
+	0x3C, 0x1F, 0xF3, 0x6C, 0x05, 0x9E, 0xFD, 0xED, 0x00, 0xBD, 0xFF,
+	0x07, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x80, 0xFF, 0x66,
+	0x01, 0xD8, 0xFC, 0x8B, 0x06, 0xC1, 0xF1, 0x27, 0x36, 0xF6, 0x29,
+	0xDF, 0xF1, 0x2A, 0x07, 0x3B, 0xFC, 0xD4, 0x01, 0x44, 0xFF, 0x32,
+	0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD7, 0xFF, 0x3E, 0x00, 0xEA, 0xFF,
+	0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7, 0x99, 0x44, 0x68, 0x14, 0x8E,
+	0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA, 0x01, 0x43, 0xFF, 0x32, 0x00,
+	0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x79, 0xFF, 0x2F, 0x01, 0xF4,
+	0xFD, 0x0C, 0x03, 0xD4, 0xFB, 0xE9, 0x05, 0xDE, 0x48, 0x8F, 0x01,
+	0xB6, 0xFD, 0x11, 0x02, 0x76, 0xFE, 0xF2, 0x00, 0x91, 0xFF, 0x1B,
+	0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7, 0x01, 0x73, 0xFC,
+	0x3B, 0x06, 0xF5, 0xF4, 0xED, 0x19, 0xB7, 0x41, 0x71, 0xF5, 0xEB,
+	0x03, 0x90, 0xFE, 0x60, 0x00, 0x04, 0x00, 0xED, 0xFF, 0x06, 0x00,
+	0xFD, 0xFF, 0x2C, 0x00, 0x57, 0xFF, 0xB2, 0x01, 0x65, 0xFC, 0x12,
+	0x07, 0x82, 0xF1, 0x50, 0x2F, 0x38, 0x31, 0x7C, 0xF1, 0xF9, 0x06,
+	0x7E, 0xFC, 0xA1, 0x01, 0x61, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x04,
+	0x00, 0xF5, 0xFF, 0xEF, 0xFF, 0x88, 0x00, 0x49, 0xFE, 0x5D, 0x04,
+	0xB7, 0xF4, 0x7D, 0x40, 0xFD, 0x1B, 0x6C, 0xF4, 0x70, 0x06, 0x5F,
+	0xFC, 0xDE, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00,
+	0x9A, 0xFF, 0xDB, 0x00, 0xA6, 0xFE, 0xB4, 0x01, 0x64, 0xFE, 0x12,
+	0x00, 0xAA, 0x48, 0x9E, 0x07, 0x21, 0xFB, 0x66, 0x03, 0xC6, 0xFD,
+	0x45, 0x01, 0x70, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30,
+	0x00, 0x48, 0xFF, 0xAD, 0x01, 0xDD, 0xFC, 0x48, 0x05, 0x30, 0xF7,
+	0x6B, 0x12, 0x7D, 0x45, 0xCF, 0xF8, 0x01, 0x02, 0xB2, 0xFF, 0xBD,
+	0xFF, 0x54, 0x00, 0xCE, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00,
+	0x3F, 0xFF, 0xDC, 0x01, 0x34, 0xFC, 0x24, 0x07, 0x1C, 0xF2, 0xF0,
+	0x27, 0xDF, 0x37, 0xFB, 0xF1, 0x51, 0x06, 0x01, 0xFD, 0x4B, 0x01,
+	0x8D, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAC,
+	0xFF, 0x0E, 0x01, 0x66, 0xFD, 0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B,
+	0xB0, 0x23, 0xC4, 0xF2, 0xFF, 0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38,
+	0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBC, 0xFF, 0x84, 0x00,
+	0x5B, 0xFF, 0x64, 0x00, 0xC9, 0x00, 0x22, 0xFB, 0x02, 0x47, 0x64,
+	0x0E, 0x8F, 0xF8, 0xA7, 0x04, 0x29, 0xFD, 0x8C, 0x01, 0x54, 0xFF,
+	0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x60, 0xFF, 0x6E,
+	0x01, 0x6B, 0xFD, 0x1D, 0x04, 0xAF, 0xF9, 0x51, 0x0B, 0xEC, 0x47,
+	0x33, 0xFD, 0xC1, 0xFF, 0xF7, 0x00, 0x0C, 0xFF, 0xAA, 0x00, 0xAD,
+	0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01,
+	0x40, 0xFC, 0xCB, 0x06, 0x6E, 0xF3, 0x49, 0x20, 0xB0, 0x3D, 0x73,
+	0xF3, 0x31, 0x05, 0xC4, 0xFD, 0xD6, 0x00, 0xC8, 0xFF, 0x03, 0x00,
+	0x02, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x77, 0xFF, 0x75, 0x01, 0xBF,
+	0xFC, 0xAB, 0x06, 0xA6, 0xF1, 0x05, 0x35, 0x40, 0x2B, 0xBF, 0xF1,
+	0x2A, 0x07, 0x42, 0xFC, 0xCE, 0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD,
+	0xFF, 0x09, 0x00, 0xDC, 0xFF, 0x2F, 0x00, 0x07, 0x00, 0x2C, 0xFF,
+	0xE6, 0x02, 0x31, 0xF7, 0xFA, 0x43, 0xB3, 0x15, 0x29, 0xF6, 0xBC,
+	0x05, 0xA9, 0xFC, 0xC2, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF,
+	0x00, 0x00, 0x20, 0x00, 0x7E, 0xFF, 0x21, 0x01, 0x12, 0xFE, 0xD1,
+	0x02, 0x47, 0xFC, 0xD7, 0x04, 0xF0, 0x48, 0x8D, 0x02, 0x45, 0xFD,
+	0x4D, 0x02, 0x56, 0xFE, 0x01, 0x01, 0x8B, 0xFF, 0x1D, 0x00, 0xFE,
+	0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD1, 0x01, 0x83, 0xFC, 0x16, 0x06,
+	0x51, 0xF5, 0x9B, 0x18, 0x75, 0x42, 0xF3, 0xF5, 0x9D, 0x03, 0xBF,
+	0xFE, 0x45, 0x00, 0x11, 0x00, 0xE8, 0xFF, 0x07, 0x00, 0xFD, 0xFF,
+	0x2E, 0x00, 0x52, 0xFF, 0xBC, 0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E,
+	0xF1, 0x11, 0x2E, 0x6B, 0x32, 0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC,
+	0x94, 0x01, 0x67, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF9,
+	0xFF, 0xE3, 0xFF, 0xA1, 0x00, 0x1E, 0xFE, 0xA3, 0x04, 0x49, 0xF4,
+	0xA8, 0x3F, 0x52, 0x1D, 0x19, 0xF4, 0x90, 0x06, 0x53, 0xFC, 0xE1,
+	0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0xA0, 0xFF,
+	0xCC, 0x00, 0xC6, 0xFE, 0x79, 0x01, 0xD2, 0xFE, 0x26, 0xFF, 0x7C,
+	0x48, 0xBE, 0x08, 0xAE, 0xFA, 0xA0, 0x03, 0xA9, 0xFD, 0x52, 0x01,
+	0x6B, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C,
+	0xFF, 0xA3, 0x01, 0xF3, 0xFC, 0x18, 0x05, 0x9B, 0xF7, 0x27, 0x11,
+	0x02, 0x46, 0x7F, 0xF9, 0xA3, 0x01, 0xE8, 0xFF, 0x9F, 0xFF, 0x63,
+	0x00, 0xC9, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF,
+	0xE0, 0x01, 0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26, 0xF2,
+	0x38, 0x2A, 0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96, 0xFF,
+	0x16, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x11, 0x00, 0xA2, 0xFF, 0x22,
+	0x01, 0x45, 0xFD, 0xF1, 0x05, 0x6D, 0xF2, 0x38, 0x3A, 0x03, 0x25,
+	0x8B, 0xF2, 0x0E, 0x07, 0x32, 0xFC, 0xE4, 0x01, 0x3A, 0xFF, 0x36,
+	0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC2, 0xFF, 0x75, 0x00, 0x7A, 0xFF,
+	0x2B, 0x00, 0x2D, 0x01, 0x61, 0xFA, 0x97, 0x46, 0xA0, 0x0F, 0x20,
+	0xF8, 0xDA, 0x04, 0x10, 0xFD, 0x97, 0x01, 0x50, 0xFF, 0x2E, 0x00,
+	0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x62, 0x01, 0x87,
+	0xFD, 0xE5, 0x03, 0x21, 0xFA, 0x25, 0x0A, 0x33, 0x48, 0x0F, 0xFE,
+	0x57, 0xFF, 0x31, 0x01, 0xEC, 0xFE, 0xB9, 0x00, 0xA7, 0xFF, 0x15,
+	0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x48, 0xFC,
+	0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E, 0xCF, 0xF3, 0xF3,
+	0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF, 0xFF, 0x03, 0x00,
+	0xFE, 0xFF, 0x23, 0x00, 0x70, 0xFF, 0x84, 0x01, 0xA9, 0xFC, 0xC7,
+	0x06, 0x91, 0xF1, 0xDC, 0x33, 0x87, 0x2C, 0xA5, 0xF1, 0x26, 0x07,
+	0x4B, 0xFC, 0xC6, 0x01, 0x4C, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08,
+	0x00, 0xE2, 0xFF, 0x21, 0x00, 0x23, 0x00, 0xFA, 0xFE, 0x3A, 0x03,
+	0x9D, 0xF6, 0x50, 0x43, 0x00, 0x17, 0xC6, 0xF5, 0xE6, 0x05, 0x97,
+	0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x00, 0x00,
+	0x1E, 0x00, 0x84, 0xFF, 0x13, 0x01, 0x31, 0xFE, 0x95, 0x02, 0xBA,
+	0xFC, 0xCB, 0x03, 0xF7, 0x48, 0x91, 0x03, 0xD3, 0xFC, 0x88, 0x02,
+	0x38, 0xFE, 0x10, 0x01, 0x85, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34,
+	0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93, 0xFC, 0xEF, 0x05, 0xB0, 0xF5,
+	0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6, 0x4D, 0x03, 0xEF, 0xFE, 0x2A,
+	0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x2F, 0x00,
+	0x4D, 0xFF, 0xC4, 0x01, 0x4D, 0xFC, 0x25, 0x07, 0xA1, 0xF1, 0xCE,
+	0x2C, 0x99, 0x33, 0x8E, 0xF1, 0xCD, 0x06, 0xA4, 0xFC, 0x87, 0x01,
+	0x6E, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFE, 0xFF, 0xD7,
+	0xFF, 0xBA, 0x00, 0xF4, 0xFD, 0xE5, 0x04, 0xE4, 0xF3, 0xCA, 0x3E,
+	0xA7, 0x1E, 0xCA, 0xF3, 0xAC, 0x06, 0x4A, 0xFC, 0xE4, 0x01, 0x36,
+	0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA6, 0xFF, 0xBD, 0x00,
+	0xE5, 0xFE, 0x3E, 0x01, 0x3F, 0xFF, 0x41, 0xFE, 0x41, 0x48, 0xE4,
+	0x09, 0x3B, 0xFA, 0xD9, 0x03, 0x8D, 0xFD, 0x5F, 0x01, 0x66, 0xFF,
+	0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99,
+	0x01, 0x0B, 0xFD, 0xE6, 0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46,
+	0x37, 0xFA, 0x42, 0x01, 0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3,
+	0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01,
+	0x31, 0xFC, 0x11, 0x07, 0x7F, 0xF2, 0x4E, 0x25, 0xFD, 0x39, 0x60,
+	0xF2, 0xFB, 0x05, 0x3E, 0xFD, 0x26, 0x01, 0xA0, 0xFF, 0x12, 0x00,
+	0x00, 0x00, 0xFF, 0xFF, 0x15, 0x00, 0x98, 0xFF, 0x35, 0x01, 0x25,
+	0xFD, 0x1E, 0x06, 0x35, 0xF2, 0x2E, 0x39, 0x55, 0x26, 0x56, 0xF2,
+	0x1A, 0x07, 0x31, 0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD,
+	0xFF, 0x0E, 0x00, 0xC7, 0xFF, 0x66, 0x00, 0x98, 0xFF, 0xF4, 0xFF,
+	0x8E, 0x01, 0xA7, 0xF9, 0x1D, 0x46, 0xDF, 0x10, 0xB3, 0xF7, 0x0D,
+	0x05, 0xF8, 0xFC, 0xA1, 0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF,
+	0x00, 0x00, 0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD,
+	0x03, 0x94, 0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE,
+	0x6C, 0x01, 0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0xFE,
+	0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x51, 0xFC, 0x96, 0x06,
+	0x07, 0xF4, 0x9E, 0x1D, 0x77, 0x3F, 0x32, 0xF4, 0xB2, 0x04, 0x15,
+	0xFE, 0xA7, 0x00, 0xE0, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0xFD, 0xFF,
+	0x26, 0x00, 0x69, 0xFF, 0x91, 0x01, 0x94, 0xFC, 0xE0, 0x06, 0x84,
+	0xF1, 0xAF, 0x32, 0xCA, 0x2D, 0x92, 0xF1, 0x1F, 0x07, 0x56, 0xFC,
+	0xBE, 0x01, 0x51, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE7,
+	0xFF, 0x14, 0x00, 0x3F, 0x00, 0xC9, 0xFE, 0x8C, 0x03, 0x11, 0xF6,
+	0x9E, 0x42, 0x50, 0x18, 0x66, 0xF5, 0x0D, 0x06, 0x86, 0xFC, 0xD0,
+	0x01, 0x3B, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x8A, 0xFF,
+	0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6, 0x02, 0xF2,
+	0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE, 0x1E, 0x01,
+	0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40,
+	0xFF, 0xC4, 0x01, 0xA5, 0xFC, 0xC5, 0x05, 0x13, 0xF6, 0xFD, 0x15,
+	0xD4, 0x43, 0x0F, 0xF7, 0xF9, 0x02, 0x21, 0xFF, 0x0D, 0x00, 0x2C,
+	0x00, 0xDE, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x49, 0xFF,
+	0xCC, 0x01, 0x44, 0xFC, 0x29, 0x07, 0xB9, 0xF1, 0x89, 0x2B, 0xC3,
+	0x34, 0xA0, 0xF1, 0xB1, 0x06, 0xBA, 0xFC, 0x79, 0x01, 0x76, 0xFF,
+	0x21, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x02, 0x00, 0xCB, 0xFF, 0xD1,
+	0x00, 0xCC, 0xFD, 0x24, 0x05, 0x87, 0xF3, 0xE4, 0x3D, 0xFD, 0x1F,
+	0x7F, 0xF3, 0xC6, 0x06, 0x41, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36,
+	0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAC, 0xFF, 0xAE, 0x00, 0x05, 0xFF,
+	0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD, 0xFD, 0x47, 0x0E, 0x0B, 0xC8,
+	0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C, 0x01, 0x61, 0xFF, 0x28, 0x00,
+	0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0x8F, 0x01, 0x23,
+	0xFD, 0xB2, 0x04, 0x76, 0xF8, 0xAA, 0x0E, 0xED, 0x46, 0xF7, 0xFA,
+	0xDF, 0x00, 0x57, 0x00, 0x62, 0xFF, 0x80, 0x00, 0xBD, 0xFF, 0x10,
+	0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x33, 0xFC,
+	0x03, 0x07, 0xB7, 0xF2, 0xFC, 0x23, 0x03, 0x3B, 0x9E, 0xF2, 0xCB,
+	0x05, 0x5F, 0xFD, 0x12, 0x01, 0xAA, 0xFF, 0x0E, 0x00, 0x00, 0x00,
+	0xFF, 0xFF, 0x18, 0x00, 0x8F, 0xFF, 0x47, 0x01, 0x08, 0xFD, 0x49,
+	0x06, 0x05, 0xF2, 0x1D, 0x38, 0xA6, 0x27, 0x26, 0xF2, 0x23, 0x07,
+	0x33, 0xFC, 0xDD, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C,
+	0x00, 0xCD, 0xFF, 0x57, 0x00, 0xB6, 0xFF, 0xBE, 0xFF, 0xED, 0x01,
+	0xF5, 0xF8, 0x9B, 0x45, 0x22, 0x12, 0x48, 0xF7, 0x3D, 0x05, 0xE2,
+	0xFC, 0xAB, 0x01, 0x49, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00,
+	0x24, 0x00, 0x6F, 0xFF, 0x48, 0x01, 0xC0, 0xFD, 0x73, 0x03, 0x07,
+	0xFB, 0xDD, 0x07, 0xA1, 0x48, 0xDD, 0xFF, 0x7D, 0xFE, 0xA7, 0x01,
+	0xAD, 0xFE, 0xD8, 0x00, 0x9B, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36,
+	0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5C, 0xFC, 0x78, 0x06, 0x5A, 0xF4,
+	0x49, 0x1C, 0x4E, 0x40, 0x9E, 0xF4, 0x6D, 0x04, 0x3F, 0xFE, 0x8E,
+	0x00, 0xED, 0xFF, 0xF6, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00,
+	0x62, 0xFF, 0x9E, 0x01, 0x82, 0xFC, 0xF5, 0x06, 0x7D, 0xF1, 0x7B,
+	0x31, 0x09, 0x2F, 0x84, 0xF1, 0x15, 0x07, 0x62, 0xFC, 0xB4, 0x01,
+	0x56, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEC, 0xFF, 0x06,
+	0x00, 0x5A, 0x00, 0x9A, 0xFE, 0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41,
+	0xA1, 0x19, 0x09, 0xF5, 0x33, 0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A,
+	0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x8F, 0xFF, 0xF5, 0x00,
+	0x6F, 0xFE, 0x1E, 0x02, 0x9D, 0xFD, 0xC7, 0x01, 0xE1, 0x48, 0xAB,
+	0x05, 0xEE, 0xFB, 0xFE, 0x02, 0xFB, 0xFD, 0x2C, 0x01, 0x7A, 0xFF,
+	0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBC,
+	0x01, 0xB8, 0xFC, 0x9A, 0x05, 0x77, 0xF6, 0xB1, 0x14, 0x77, 0x44,
+	0xA9, 0xF7, 0xA2, 0x02, 0x54, 0xFF, 0xF1, 0xFF, 0x3A, 0x00, 0xD8,
+	0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45, 0xFF, 0xD3, 0x01,
+	0x3C, 0xFC, 0x2A, 0x07, 0xD8, 0xF1, 0x3F, 0x2A, 0xE6, 0x35, 0xBB,
+	0xF1, 0x92, 0x06, 0xD2, 0xFC, 0x69, 0x01, 0x7E, 0xFF, 0x1F, 0x00,
+	0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xE8, 0x00, 0xA6,
+	0xFD, 0x5F, 0x05, 0x31, 0xF3, 0xF6, 0x3C, 0x52, 0x21, 0x37, 0xF3,
+	0xDD, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD,
+	0xFF, 0x13, 0x00, 0xB1, 0xFF, 0x9F, 0x00, 0x24, 0xFF, 0xC9, 0x00,
+	0x13, 0x00, 0x8D, 0xFC, 0xAE, 0x47, 0x3E, 0x0C, 0x56, 0xF9, 0x48,
+	0x04, 0x56, 0xFD, 0x78, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x2B, 0x00, 0x58, 0xFF, 0x83, 0x01, 0x3C, 0xFD, 0x7E,
+	0x04, 0xE6, 0xF8, 0x72, 0x0D, 0x52, 0x47, 0xBE, 0xFB, 0x7A, 0x00,
+	0x90, 0x00, 0x43, 0xFF, 0x8F, 0x00, 0xB7, 0xFF, 0x11, 0x00, 0xFD,
+	0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xF1, 0x06,
+	0xF5, 0xF2, 0xA7, 0x22, 0xFF, 0x3B, 0xE4, 0xF2, 0x96, 0x05, 0x81,
+	0xFD, 0xFD, 0x00, 0xB5, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0xFE, 0xFF,
+	0x1C, 0x00, 0x86, 0xFF, 0x59, 0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC,
+	0xF1, 0x04, 0x37, 0xF3, 0x28, 0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC,
+	0xD8, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD3,
+	0xFF, 0x49, 0x00, 0xD4, 0xFF, 0x88, 0xFF, 0x49, 0x02, 0x4B, 0xF8,
+	0x0D, 0x45, 0x68, 0x13, 0xDF, 0xF6, 0x6C, 0x05, 0xCC, 0xFC, 0xB4,
+	0x01, 0x45, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00,
+	0x74, 0xFF, 0x3A, 0x01, 0xDD, 0xFD, 0x39, 0x03, 0x7B, 0xFB, 0xC1,
+	0x06, 0xC7, 0x48, 0xCF, 0x00, 0x0D, 0xFE, 0xE3, 0x01, 0x8E, 0xFE,
+	0xE7, 0x00, 0x95, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38,
+	0xFF, 0xDA, 0x01, 0x69, 0xFC, 0x57, 0x06, 0xAF, 0xF4, 0xF5, 0x1A,
+	0x1D, 0x41, 0x11, 0xF5, 0x25, 0x04, 0x6C, 0xFE, 0x74, 0x00, 0xF9,
+	0xFF, 0xF1, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5C, 0xFF,
+	0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30, 0x44,
+	0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C, 0xFF,
+	0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF1, 0xFF, 0xF9, 0xFF, 0x74,
+	0x00, 0x6C, 0xFE, 0x25, 0x04, 0x11, 0xF5, 0x1D, 0x41, 0xF5, 0x1A,
+	0xAF, 0xF4, 0x57, 0x06, 0x69, 0xFC, 0xDA, 0x01, 0x38, 0xFF, 0x36,
+	0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE7, 0x00, 0x8E, 0xFE,
+	0xE3, 0x01, 0x0D, 0xFE, 0xCF, 0x00, 0xC7, 0x48, 0xC1, 0x06, 0x7B,
+	0xFB, 0x39, 0x03, 0xDD, 0xFD, 0x3A, 0x01, 0x74, 0xFF, 0x23, 0x00,
+	0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x45, 0xFF, 0xB4, 0x01, 0xCC,
+	0xFC, 0x6C, 0x05, 0xDF, 0xF6, 0x68, 0x13, 0x0D, 0x45, 0x4B, 0xF8,
+	0x49, 0x02, 0x88, 0xFF, 0xD4, 0xFF, 0x49, 0x00, 0xD3, 0xFF, 0x0B,
+	0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8, 0x01, 0x37, 0xFC,
+	0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37, 0xDC, 0xF1, 0x6F,
+	0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C, 0x00, 0xFE, 0xFF,
+	0x01, 0x00, 0x0B, 0x00, 0xB5, 0xFF, 0xFD, 0x00, 0x81, 0xFD, 0x96,
+	0x05, 0xE4, 0xF2, 0xFF, 0x3B, 0xA7, 0x22, 0xF5, 0xF2, 0xF1, 0x06,
+	0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11,
+	0x00, 0xB7, 0xFF, 0x8F, 0x00, 0x43, 0xFF, 0x90, 0x00, 0x7A, 0x00,
+	0xBE, 0xFB, 0x52, 0x47, 0x72, 0x0D, 0xE6, 0xF8, 0x7E, 0x04, 0x3C,
+	0xFD, 0x83, 0x01, 0x58, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x2A, 0x00, 0x5C, 0xFF, 0x78, 0x01, 0x56, 0xFD, 0x48, 0x04, 0x56,
+	0xF9, 0x3E, 0x0C, 0xAE, 0x47, 0x8D, 0xFC, 0x13, 0x00, 0xC9, 0x00,
+	0x24, 0xFF, 0x9F, 0x00, 0xB1, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36,
+	0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B, 0xFC, 0xDD, 0x06, 0x37, 0xF3,
+	0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3, 0x5F, 0x05, 0xA6, 0xFD, 0xE8,
+	0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1F, 0x00,
+	0x7E, 0xFF, 0x69, 0x01, 0xD2, 0xFC, 0x92, 0x06, 0xBB, 0xF1, 0xE6,
+	0x35, 0x3F, 0x2A, 0xD8, 0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01,
+	0x45, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD8, 0xFF, 0x3A,
+	0x00, 0xF1, 0xFF, 0x54, 0xFF, 0xA2, 0x02, 0xA9, 0xF7, 0x77, 0x44,
+	0xB1, 0x14, 0x77, 0xF6, 0x9A, 0x05, 0xB8, 0xFC, 0xBC, 0x01, 0x42,
+	0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF,
+	0x2C, 0x01, 0xFB, 0xFD, 0xFE, 0x02, 0xEE, 0xFB, 0xAB, 0x05, 0xE1,
+	0x48, 0xC7, 0x01, 0x9D, 0xFD, 0x1E, 0x02, 0x6F, 0xFE, 0xF5, 0x00,
+	0x8F, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6,
+	0x01, 0x77, 0xFC, 0x33, 0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41,
+	0x8D, 0xF5, 0xDA, 0x03, 0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC,
+	0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x56, 0xFF, 0xB4, 0x01,
+	0x62, 0xFC, 0x15, 0x07, 0x84, 0xF1, 0x09, 0x2F, 0x7B, 0x31, 0x7D,
+	0xF1, 0xF5, 0x06, 0x82, 0xFC, 0x9E, 0x01, 0x62, 0xFF, 0x28, 0x00,
+	0xFD, 0xFF, 0x04, 0x00, 0xF6, 0xFF, 0xED, 0xFF, 0x8E, 0x00, 0x3F,
+	0xFE, 0x6D, 0x04, 0x9E, 0xF4, 0x4E, 0x40, 0x49, 0x1C, 0x5A, 0xF4,
+	0x78, 0x06, 0x5C, 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE,
+	0xFF, 0x18, 0x00, 0x9B, 0xFF, 0xD8, 0x00, 0xAD, 0xFE, 0xA7, 0x01,
+	0x7D, 0xFE, 0xDD, 0xFF, 0xA1, 0x48, 0xDD, 0x07, 0x07, 0xFB, 0x73,
+	0x03, 0xC0, 0xFD, 0x48, 0x01, 0x6F, 0xFF, 0x24, 0x00, 0x00, 0x00,
+	0xFF, 0xFF, 0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D,
+	0x05, 0x48, 0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01,
+	0xBE, 0xFF, 0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0xFD,
+	0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDD, 0x01, 0x33, 0xFC, 0x23, 0x07,
+	0x26, 0xF2, 0xA6, 0x27, 0x1D, 0x38, 0x05, 0xF2, 0x49, 0x06, 0x08,
+	0xFD, 0x47, 0x01, 0x8F, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x00, 0x00,
+	0x0E, 0x00, 0xAA, 0xFF, 0x12, 0x01, 0x5F, 0xFD, 0xCB, 0x05, 0x9E,
+	0xF2, 0x03, 0x3B, 0xFC, 0x23, 0xB7, 0xF2, 0x03, 0x07, 0x33, 0xFC,
+	0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBD,
+	0xFF, 0x80, 0x00, 0x62, 0xFF, 0x57, 0x00, 0xDF, 0x00, 0xF7, 0xFA,
+	0xED, 0x46, 0xAA, 0x0E, 0x76, 0xF8, 0xB2, 0x04, 0x23, 0xFD, 0x8F,
+	0x01, 0x53, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00,
+	0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8, 0xF9, 0x0E,
+	0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01, 0x05, 0xFF,
+	0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36,
+	0xFF, 0xE5, 0x01, 0x41, 0xFC, 0xC6, 0x06, 0x7F, 0xF3, 0xFD, 0x1F,
+	0xE4, 0x3D, 0x87, 0xF3, 0x24, 0x05, 0xCC, 0xFD, 0xD1, 0x00, 0xCB,
+	0xFF, 0x02, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x76, 0xFF,
+	0x79, 0x01, 0xBA, 0xFC, 0xB1, 0x06, 0xA0, 0xF1, 0xC3, 0x34, 0x89,
+	0x2B, 0xB9, 0xF1, 0x29, 0x07, 0x44, 0xFC, 0xCC, 0x01, 0x49, 0xFF,
+	0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2C, 0x00, 0x0D,
+	0x00, 0x21, 0xFF, 0xF9, 0x02, 0x0F, 0xF7, 0xD4, 0x43, 0xFD, 0x15,
+	0x13, 0xF6, 0xC5, 0x05, 0xA5, 0xFC, 0xC4, 0x01, 0x40, 0xFF, 0x33,
+	0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F, 0xFF, 0x1E, 0x01,
+	0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC, 0x9B, 0x04, 0xF2, 0x48, 0xC6,
+	0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50, 0xFE, 0x04, 0x01, 0x8A, 0xFF,
+	0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD0, 0x01, 0x86,
+	0xFC, 0x0D, 0x06, 0x66, 0xF5, 0x50, 0x18, 0x9E, 0x42, 0x11, 0xF6,
+	0x8C, 0x03, 0xC9, 0xFE, 0x3F, 0x00, 0x14, 0x00, 0xE7, 0xFF, 0x07,
+	0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBE, 0x01, 0x56, 0xFC,
+	0x1F, 0x07, 0x92, 0xF1, 0xCA, 0x2D, 0xAF, 0x32, 0x84, 0xF1, 0xE0,
+	0x06, 0x94, 0xFC, 0x91, 0x01, 0x69, 0xFF, 0x26, 0x00, 0xFD, 0xFF,
+	0x03, 0x00, 0xFA, 0xFF, 0xE0, 0xFF, 0xA7, 0x00, 0x15, 0xFE, 0xB2,
+	0x04, 0x32, 0xF4, 0x77, 0x3F, 0x9E, 0x1D, 0x07, 0xF4, 0x96, 0x06,
+	0x51, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17,
+	0x00, 0xA1, 0xFF, 0xC9, 0x00, 0xCD, 0xFE, 0x6C, 0x01, 0xEA, 0xFE,
+	0xF3, 0xFE, 0x70, 0x48, 0xFF, 0x08, 0x94, 0xFA, 0xAD, 0x03, 0xA3,
+	0xFD, 0x55, 0x01, 0x6A, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+	0x2F, 0x00, 0x4C, 0xFF, 0xA1, 0x01, 0xF8, 0xFC, 0x0D, 0x05, 0xB3,
+	0xF7, 0xDF, 0x10, 0x1D, 0x46, 0xA7, 0xF9, 0x8E, 0x01, 0xF4, 0xFF,
+	0x98, 0xFF, 0x66, 0x00, 0xC7, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35,
+	0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x31, 0xFC, 0x1A, 0x07, 0x56, 0xF2,
+	0x55, 0x26, 0x2E, 0x39, 0x35, 0xF2, 0x1E, 0x06, 0x25, 0xFD, 0x35,
+	0x01, 0x98, 0xFF, 0x15, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x12, 0x00,
+	0xA0, 0xFF, 0x26, 0x01, 0x3E, 0xFD, 0xFB, 0x05, 0x60, 0xF2, 0xFD,
+	0x39, 0x4E, 0x25, 0x7F, 0xF2, 0x11, 0x07, 0x31, 0xFC, 0xE3, 0x01,
+	0x3A, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC3, 0xFF, 0x71,
+	0x00, 0x81, 0xFF, 0x1F, 0x00, 0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46,
+	0xE7, 0x0F, 0x08, 0xF8, 0xE6, 0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F,
+	0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x66, 0xFF,
+	0x5F, 0x01, 0x8D, 0xFD, 0xD9, 0x03, 0x3B, 0xFA, 0xE4, 0x09, 0x41,
+	0x48, 0x41, 0xFE, 0x3F, 0xFF, 0x3E, 0x01, 0xE5, 0xFE, 0xBD, 0x00,
+	0xA6, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4,
+	0x01, 0x4A, 0xFC, 0xAC, 0x06, 0xCA, 0xF3, 0xA7, 0x1E, 0xCA, 0x3E,
+	0xE4, 0xF3, 0xE5, 0x04, 0xF4, 0xFD, 0xBA, 0x00, 0xD7, 0xFF, 0xFE,
+	0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6E, 0xFF, 0x87, 0x01,
+	0xA4, 0xFC, 0xCD, 0x06, 0x8E, 0xF1, 0x99, 0x33, 0xCE, 0x2C, 0xA1,
+	0xF1, 0x25, 0x07, 0x4D, 0xFC, 0xC4, 0x01, 0x4D, 0xFF, 0x2F, 0x00,
+	0xFD, 0xFF, 0x08, 0x00, 0xE3, 0xFF, 0x1E, 0x00, 0x2A, 0x00, 0xEF,
+	0xFE, 0x4D, 0x03, 0x7D, 0xF6, 0x2A, 0x43, 0x4B, 0x17, 0xB0, 0xF5,
+	0xEF, 0x05, 0x93, 0xFC, 0xCB, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE,
+	0xFF, 0x1E, 0x00, 0x85, 0xFF, 0x10, 0x01, 0x38, 0xFE, 0x88, 0x02,
+	0xD3, 0xFC, 0x91, 0x03, 0xF7, 0x48, 0xCB, 0x03, 0xBA, 0xFC, 0x95,
+	0x02, 0x31, 0xFE, 0x13, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x00, 0x00,
+	0xFE, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x97, 0xFC, 0xE6,
+	0x05, 0xC6, 0xF5, 0x00, 0x17, 0x50, 0x43, 0x9D, 0xF6, 0x3A, 0x03,
+	0xFA, 0xFE, 0x23, 0x00, 0x21, 0x00, 0xE2, 0xFF, 0x08, 0x00, 0xFD,
+	0xFF, 0x30, 0x00, 0x4C, 0xFF, 0xC6, 0x01, 0x4B, 0xFC, 0x26, 0x07,
+	0xA5, 0xF1, 0x87, 0x2C, 0xDC, 0x33, 0x91, 0xF1, 0xC7, 0x06, 0xA9,
+	0xFC, 0x84, 0x01, 0x70, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x03, 0x00,
+	0xFF, 0xFF, 0xD4, 0xFF, 0xBF, 0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF,
+	0xF3, 0x98, 0x3E, 0xF3, 0x1E, 0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC,
+	0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x15, 0x00, 0xA7,
+	0xFF, 0xB9, 0x00, 0xEC, 0xFE, 0x31, 0x01, 0x57, 0xFF, 0x0F, 0xFE,
+	0x33, 0x48, 0x25, 0x0A, 0x21, 0xFA, 0xE5, 0x03, 0x87, 0xFD, 0x62,
+	0x01, 0x65, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00,
+	0x50, 0xFF, 0x97, 0x01, 0x10, 0xFD, 0xDA, 0x04, 0x20, 0xF8, 0xA0,
+	0x0F, 0x97, 0x46, 0x61, 0xFA, 0x2D, 0x01, 0x2B, 0x00, 0x7A, 0xFF,
+	0x75, 0x00, 0xC2, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A,
+	0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0E, 0x07, 0x8B, 0xF2, 0x03, 0x25,
+	0x38, 0x3A, 0x6D, 0xF2, 0xF1, 0x05, 0x45, 0xFD, 0x22, 0x01, 0xA2,
+	0xFF, 0x11, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x96, 0xFF,
+	0x39, 0x01, 0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38, 0xA0,
+	0x26, 0x4B, 0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C, 0xFF,
+	0x35, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xC9, 0xFF, 0x63, 0x00, 0x9F,
+	0xFF, 0xE8, 0xFF, 0xA3, 0x01, 0x7F, 0xF9, 0x02, 0x46, 0x27, 0x11,
+	0x9B, 0xF7, 0x18, 0x05, 0xF3, 0xFC, 0xA3, 0x01, 0x4C, 0xFF, 0x2F,
+	0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6B, 0xFF, 0x52, 0x01,
+	0xA9, 0xFD, 0xA0, 0x03, 0xAE, 0xFA, 0xBE, 0x08, 0x7C, 0x48, 0x26,
+	0xFF, 0xD2, 0xFE, 0x79, 0x01, 0xC6, 0xFE, 0xCC, 0x00, 0xA0, 0xFF,
+	0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE1, 0x01, 0x53,
+	0xFC, 0x90, 0x06, 0x19, 0xF4, 0x52, 0x1D, 0xA8, 0x3F, 0x49, 0xF4,
+	0xA3, 0x04, 0x1E, 0xFE, 0xA1, 0x00, 0xE3, 0xFF, 0xF9, 0xFF, 0x04,
+	0x00, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94, 0x01, 0x90, 0xFC,
+	0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E, 0x8E, 0xF1, 0x1D,
+	0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E, 0x00, 0xFD, 0xFF,
+	0x07, 0x00, 0xE8, 0xFF, 0x11, 0x00, 0x45, 0x00, 0xBF, 0xFE, 0x9D,
+	0x03, 0xF3, 0xF5, 0x75, 0x42, 0x9B, 0x18, 0x51, 0xF5, 0x16, 0x06,
+	0x83, 0xFC, 0xD1, 0x01, 0x3B, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D,
+	0x00, 0x8B, 0xFF, 0x01, 0x01, 0x56, 0xFE, 0x4D, 0x02, 0x45, 0xFD,
+	0x8D, 0x02, 0xF0, 0x48, 0xD7, 0x04, 0x47, 0xFC, 0xD1, 0x02, 0x12,
+	0xFE, 0x21, 0x01, 0x7E, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+	0x33, 0x00, 0x40, 0xFF, 0xC2, 0x01, 0xA9, 0xFC, 0xBC, 0x05, 0x29,
+	0xF6, 0xB3, 0x15, 0xFA, 0x43, 0x31, 0xF7, 0xE6, 0x02, 0x2C, 0xFF,
+	0x07, 0x00, 0x2F, 0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31,
+	0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42, 0xFC, 0x2A, 0x07, 0xBF, 0xF1,
+	0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1, 0xAB, 0x06, 0xBF, 0xFC, 0x75,
+	0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x03, 0x00,
+	0xC8, 0xFF, 0xD6, 0x00, 0xC4, 0xFD, 0x31, 0x05, 0x73, 0xF3, 0xB0,
+	0x3D, 0x49, 0x20, 0x6E, 0xF3, 0xCB, 0x06, 0x40, 0xFC, 0xE6, 0x01,
+	0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAD, 0xFF, 0xAA,
+	0x00, 0x0C, 0xFF, 0xF7, 0x00, 0xC1, 0xFF, 0x33, 0xFD, 0xEC, 0x47,
+	0x51, 0x0B, 0xAF, 0xF9, 0x1D, 0x04, 0x6B, 0xFD, 0x6E, 0x01, 0x60,
+	0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x54, 0xFF,
+	0x8C, 0x01, 0x29, 0xFD, 0xA7, 0x04, 0x8F, 0xF8, 0x64, 0x0E, 0x02,
+	0x47, 0x22, 0xFB, 0xC9, 0x00, 0x64, 0x00, 0x5B, 0xFF, 0x84, 0x00,
+	0xBC, 0xFF, 0x10, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5,
+	0x01, 0x33, 0xFC, 0xFF, 0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B,
+	0xAD, 0xF2, 0xBF, 0x05, 0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E,
+	0x00, 0x00, 0x00, 0xFF, 0xFF, 0x19, 0x00, 0x8D, 0xFF, 0x4B, 0x01,
+	0x01, 0xFD, 0x51, 0x06, 0xFB, 0xF1, 0xDF, 0x37, 0xF0, 0x27, 0x1C,
+	0xF2, 0x24, 0x07, 0x34, 0xFC, 0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00,
+	0xFD, 0xFF, 0x0C, 0x00, 0xCE, 0xFF, 0x54, 0x00, 0xBD, 0xFF, 0xB2,
+	0xFF, 0x01, 0x02, 0xCF, 0xF8, 0x7D, 0x45, 0x6B, 0x12, 0x30, 0xF7,
+	0x48, 0x05, 0xDD, 0xFC, 0xAD, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF,
+	0xFF, 0x00, 0x00, 0x24, 0x00, 0x70, 0xFF, 0x45, 0x01, 0xC6, 0xFD,
+	0x66, 0x03, 0x21, 0xFB, 0x9E, 0x07, 0xAA, 0x48, 0x12, 0x00, 0x64,
+	0xFE, 0xB4, 0x01, 0xA6, 0xFE, 0xDB, 0x00, 0x9A, 0xFF, 0x19, 0x00,
+	0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70,
+	0x06, 0x6C, 0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04,
+	0x49, 0xFE, 0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0xFD,
+	0xFF, 0x29, 0x00, 0x61, 0xFF, 0xA1, 0x01, 0x7E, 0xFC, 0xF9, 0x06,
+	0x7C, 0xF1, 0x38, 0x31, 0x50, 0x2F, 0x82, 0xF1, 0x12, 0x07, 0x65,
+	0xFC, 0xB2, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00,
+	0xED, 0xFF, 0x04, 0x00, 0x60, 0x00, 0x90, 0xFE, 0xEB, 0x03, 0x71,
+	0xF5, 0xB7, 0x41, 0xED, 0x19, 0xF5, 0xF4, 0x3B, 0x06, 0x73, 0xFC,
+	0xD7, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x91,
+	0xFF, 0xF2, 0x00, 0x76, 0xFE, 0x11, 0x02, 0xB6, 0xFD, 0x8F, 0x01,
+	0xDE, 0x48, 0xE9, 0x05, 0xD4, 0xFB, 0x0C, 0x03, 0xF4, 0xFD, 0x2F,
+	0x01, 0x79, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00,
+	0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E, 0xF6, 0x68,
+	0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF, 0xEA, 0xFF,
+	0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44,
+	0xFF, 0xD4, 0x01, 0x3B, 0xFC, 0x2A, 0x07, 0xDF, 0xF1, 0xF6, 0x29,
+	0x27, 0x36, 0xC1, 0xF1, 0x8B, 0x06, 0xD8, 0xFC, 0x66, 0x01, 0x80,
+	0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xBD, 0xFF,
+	0xED, 0x00, 0x9E, 0xFD, 0x6C, 0x05, 0x1F, 0xF3, 0xC0, 0x3C, 0x9E,
+	0x21, 0x28, 0xF3, 0xE2, 0x06, 0x3A, 0xFC, 0xE6, 0x01, 0x37, 0xFF,
+	0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x9B, 0x00, 0x2B,
+	0xFF, 0xBD, 0x00, 0x2A, 0x00, 0x5E, 0xFC, 0x9A, 0x47, 0x82, 0x0C,
+	0x3D, 0xF9, 0x54, 0x04, 0x50, 0xFD, 0x7A, 0x01, 0x5B, 0xFF, 0x2A,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF, 0x81, 0x01,
+	0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8, 0x2D, 0x0D, 0x69, 0x47, 0xEB,
+	0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C, 0xFF, 0x93, 0x00, 0xB6, 0xFF,
+	0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x37,
+	0xFC, 0xED, 0x06, 0x03, 0xF3, 0x5B, 0x22, 0x37, 0x3C, 0xF4, 0xF2,
+	0x8A, 0x05, 0x89, 0xFD, 0xF9, 0x00, 0xB7, 0xFF, 0x0A, 0x00, 0x01,
+	0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x84, 0xFF, 0x5C, 0x01, 0xE6, 0xFC,
+	0x77, 0x06, 0xD4, 0xF1, 0xC6, 0x36, 0x3E, 0x29, 0xF3, 0xF1, 0x29,
+	0x07, 0x38, 0xFC, 0xD7, 0x01, 0x42, 0xFF, 0x33, 0x00, 0xFD, 0xFF,
+	0x0B, 0x00, 0xD4, 0xFF, 0x46, 0x00, 0xDA, 0xFF, 0x7D, 0xFF, 0x5D,
+	0x02, 0x26, 0xF8, 0xED, 0x44, 0xB1, 0x13, 0xC7, 0xF6, 0x77, 0x05,
+	0xC8, 0xFC, 0xB6, 0x01, 0x45, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00,
+	0x00, 0x22, 0x00, 0x76, 0xFF, 0x37, 0x01, 0xE4, 0xFD, 0x2C, 0x03,
+	0x94, 0xFB, 0x83, 0x06, 0xCE, 0x48, 0x05, 0x01, 0xF5, 0xFD, 0xF0,
+	0x01, 0x87, 0xFE, 0xEA, 0x00, 0x94, 0xFF, 0x1A, 0x00, 0xFE, 0xFF,
+	0x35, 0x00, 0x38, 0xFF, 0xD9, 0x01, 0x6C, 0xFC, 0x4F, 0x06, 0xC3,
+	0xF4, 0xA9, 0x1A, 0x49, 0x41, 0x2C, 0xF5, 0x15, 0x04, 0x76, 0xFE,
+	0x6E, 0x00, 0xFC, 0xFF, 0xF0, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2B,
+	0x00, 0x5A, 0xFF, 0xAC, 0x01, 0x6E, 0xFC, 0x0A, 0x07, 0x7E, 0xF1,
+	0xFF, 0x2F, 0x8A, 0x30, 0x7D, 0xF1, 0x03, 0x07, 0x75, 0xFC, 0xA7,
+	0x01, 0x5D, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF2, 0xFF,
+	0xF7, 0xFF, 0x7A, 0x00, 0x62, 0xFE, 0x35, 0x04, 0xF7, 0xF4, 0xEF,
+	0x40, 0x40, 0x1B, 0x9C, 0xF4, 0x5E, 0x06, 0x66, 0xFC, 0xDB, 0x01,
+	0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x96, 0xFF, 0xE3,
+	0x00, 0x95, 0xFE, 0xD5, 0x01, 0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48,
+	0x00, 0x07, 0x61, 0xFB, 0x46, 0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73,
+	0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF,
+	0xB2, 0x01, 0xD1, 0xFC, 0x62, 0x05, 0xF6, 0xF6, 0x20, 0x13, 0x2E,
+	0x45, 0x70, 0xF8, 0x34, 0x02, 0x94, 0xFF, 0xCD, 0xFF, 0x4C, 0x00,
+	0xD2, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xDA,
+	0x01, 0x36, 0xFC, 0x27, 0x07, 0x05, 0xF2, 0xAA, 0x28, 0x44, 0x37,
+	0xE4, 0xF1, 0x67, 0x06, 0xF2, 0xFC, 0x55, 0x01, 0x88, 0xFF, 0x1B,
+	0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0B, 0x00, 0xB2, 0xFF, 0x02, 0x01,
+	0x7A, 0xFD, 0xA2, 0x05, 0xD4, 0xF2, 0xC7, 0x3B, 0xF2, 0x22, 0xE7,
+	0xF2, 0xF5, 0x06, 0x35, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00,
+	0xFD, 0xFF, 0x11, 0x00, 0xB9, 0xFF, 0x8C, 0x00, 0x4A, 0xFF, 0x83,
+	0x00, 0x91, 0x00, 0x91, 0xFB, 0x3D, 0x47, 0xB7, 0x0D, 0xCD, 0xF8,
+	0x89, 0x04, 0x36, 0xFD, 0x86, 0x01, 0x57, 0xFF, 0x2B, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D, 0xFF, 0x75, 0x01, 0x5C, 0xFD,
+	0x3C, 0x04, 0x70, 0xF9, 0xFA, 0x0B, 0xC0, 0x47, 0xBC, 0xFC, 0xFC,
+	0xFF, 0xD6, 0x00, 0x1D, 0xFF, 0xA2, 0x00, 0xB0, 0xFF, 0x13, 0x00,
+	0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3C, 0xFC, 0xD8,
+	0x06, 0x47, 0xF3, 0x06, 0x21, 0x2A, 0x3D, 0x44, 0xF3, 0x52, 0x05,
+	0xAE, 0xFD, 0xE3, 0x00, 0xC2, 0xFF, 0x06, 0x00, 0x01, 0x00, 0xFE,
+	0xFF, 0x1F, 0x00, 0x7C, 0xFF, 0x6D, 0x01, 0xCD, 0xFC, 0x99, 0x06,
+	0xB4, 0xF1, 0xA6, 0x35, 0x89, 0x2A, 0xD0, 0xF1, 0x2B, 0x07, 0x3E,
+	0xFC, 0xD1, 0x01, 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00,
+	0xD9, 0xFF, 0x37, 0x00, 0xF7, 0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86,
+	0xF7, 0x53, 0x44, 0xFB, 0x14, 0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC,
+	0xBE, 0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21,
+	0x00, 0x7B, 0xFF, 0x29, 0x01, 0x01, 0xFE, 0xF1, 0x02, 0x07, 0xFC,
+	0x6E, 0x05, 0xE6, 0x48, 0xFF, 0x01, 0x84, 0xFD, 0x2C, 0x02, 0x68,
+	0xFE, 0xF9, 0x00, 0x8E, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00,
+	0x3A, 0xFF, 0xD4, 0x01, 0x7A, 0xFC, 0x2B, 0x06, 0x1E, 0xF5, 0x56,
+	0x19, 0x0C, 0x42, 0xAA, 0xF5, 0xC9, 0x03, 0xA4, 0xFE, 0x54, 0x00,
+	0x09, 0x00, 0xEB, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x55,
+	0xFF, 0xB6, 0x01, 0x5F, 0xFC, 0x17, 0x07, 0x87, 0xF1, 0xC2, 0x2E,
+	0xC0, 0x31, 0x7E, 0xF1, 0xF1, 0x06, 0x86, 0xFC, 0x9B, 0x01, 0x63,
+	0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xEA, 0xFF,
+	0x93, 0x00, 0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40, 0x94,
+	0x1C, 0x47, 0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37, 0xFF,
+	0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00, 0x9C, 0xFF, 0xD4, 0x00, 0xB4,
+	0xFE, 0x9A, 0x01, 0x95, 0xFE, 0xA8, 0xFF, 0x98, 0x48, 0x1D, 0x08,
+	0xEE, 0xFA, 0x80, 0x03, 0xB9, 0xFD, 0x4B, 0x01, 0x6E, 0xFF, 0x25,
+	0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xA9, 0x01,
+	0xE7, 0xFC, 0x33, 0x05, 0x60, 0xF7, 0xDA, 0x11, 0xB8, 0x45, 0x1C,
+	0xF9, 0xD8, 0x01, 0xCA, 0xFF, 0xAF, 0xFF, 0x5A, 0x00, 0xCC, 0xFF,
+	0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDE, 0x01, 0x33,
+	0xFC, 0x21, 0x07, 0x30, 0xF2, 0x5C, 0x27, 0x5B, 0x38, 0x0F, 0xF2,
+	0x40, 0x06, 0x0E, 0xFD, 0x43, 0x01, 0x91, 0xFF, 0x18, 0x00, 0xFF,
+	0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17, 0x01, 0x57, 0xFD,
+	0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24, 0xAA, 0xF2, 0x06,
+	0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF,
+	0x10, 0x00, 0xBE, 0xFF, 0x7D, 0x00, 0x69, 0xFF, 0x4B, 0x00, 0xF6,
+	0x00, 0xCB, 0xFA, 0xD3, 0x46, 0xF0, 0x0E, 0x5E, 0xF8, 0xBE, 0x04,
+	0x1E, 0xFD, 0x91, 0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00,
+	0x00, 0x28, 0x00, 0x62, 0xFF, 0x69, 0x01, 0x77, 0xFD, 0x04, 0x04,
+	0xE2, 0xF9, 0xCB, 0x0A, 0x0D, 0x48, 0x94, 0xFD, 0x92, 0xFF, 0x10,
+	0x01, 0xFE, 0xFE, 0xB1, 0x00, 0xAA, 0xFF, 0x15, 0x00, 0xFD, 0xFF,
+	0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x43, 0xFC, 0xC0, 0x06, 0x8F,
+	0xF3, 0xB1, 0x1F, 0x18, 0x3E, 0x9B, 0xF3, 0x16, 0x05, 0xD5, 0xFD,
+	0xCC, 0x00, 0xCE, 0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x22,
+	0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5, 0xFC, 0xB8, 0x06, 0x9C, 0xF1,
+	0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1, 0x29, 0x07, 0x46, 0xFC, 0xCA,
+	0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDF, 0xFF,
+	0x29, 0x00, 0x14, 0x00, 0x16, 0xFF, 0x0C, 0x03, 0xEE, 0xF6, 0xB0,
+	0x43, 0x47, 0x16, 0xFC, 0xF5, 0xCF, 0x05, 0xA1, 0xFC, 0xC6, 0x01,
+	0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81,
+	0xFF, 0x1B, 0x01, 0x20, 0xFE, 0xB6, 0x02, 0x7A, 0xFC, 0x5F, 0x04,
+	0xF4, 0x48, 0xFF, 0x02, 0x13, 0xFD, 0x67, 0x02, 0x49, 0xFE, 0x07,
+	0x01, 0x88, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF,
+	0xCF, 0x01, 0x8A, 0xFC, 0x05, 0x06, 0x7B, 0xF5, 0x06, 0x18, 0xC7,
+	0x42, 0x2F, 0xF6, 0x7A, 0x03, 0xD4, 0xFE, 0x39, 0x00, 0x17, 0x00,
+	0xE6, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0,
+	0x01, 0x53, 0xFC, 0x21, 0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32,
+	0x86, 0xF1, 0xDB, 0x06, 0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25,
+	0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFB, 0xFF, 0xDE, 0xFF, 0xAC, 0x00,
+	0x0B, 0xFE, 0xC1, 0x04, 0x1B, 0xF4, 0x47, 0x3F, 0xEA, 0x1D, 0xF5,
+	0xF3, 0x9C, 0x06, 0x4F, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00,
+	0xFE, 0xFF, 0x16, 0x00, 0xA2, 0xFF, 0xC5, 0x00, 0xD4, 0xFE, 0x5F,
+	0x01, 0x03, 0xFF, 0xBF, 0xFE, 0x63, 0x48, 0x40, 0x09, 0x7B, 0xFA,
+	0xB9, 0x03, 0x9D, 0xFD, 0x58, 0x01, 0x69, 0xFF, 0x26, 0x00, 0x00,
+	0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4D, 0xFF, 0x9F, 0x01, 0xFE, 0xFC,
+	0x02, 0x05, 0xCB, 0xF7, 0x98, 0x10, 0x39, 0x46, 0xD0, 0xF9, 0x78,
+	0x01, 0x00, 0x00, 0x91, 0xFF, 0x69, 0x00, 0xC6, 0xFF, 0x0E, 0x00,
+	0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17,
+	0x07, 0x61, 0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06,
+	0x2C, 0xFD, 0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFF,
+	0xFF, 0x13, 0x00, 0x9E, 0xFF, 0x2B, 0x01, 0x37, 0xFD, 0x05, 0x06,
+	0x54, 0xF2, 0xC2, 0x39, 0x99, 0x25, 0x73, 0xF2, 0x14, 0x07, 0x31,
+	0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00,
+	0xC4, 0xFF, 0x6E, 0x00, 0x87, 0xFF, 0x13, 0x00, 0x58, 0x01, 0x0D,
+	0xFA, 0x61, 0x46, 0x2D, 0x10, 0xF0, 0xF7, 0xF1, 0x04, 0x05, 0xFD,
+	0x9C, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27,
+	0x00, 0x67, 0xFF, 0x5C, 0x01, 0x93, 0xFD, 0xCC, 0x03, 0x54, 0xFA,
+	0xA2, 0x09, 0x4F, 0x48, 0x73, 0xFE, 0x27, 0xFF, 0x4B, 0x01, 0xDE,
+	0xFE, 0xC0, 0x00, 0xA4, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00,
+	0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB, 0xF3, 0x5B,
+	0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD, 0xB4, 0x00,
+	0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6D,
+	0xFF, 0x8A, 0x01, 0x9F, 0xFC, 0xD3, 0x06, 0x8A, 0xF1, 0x57, 0x33,
+	0x17, 0x2D, 0x9C, 0xF1, 0x24, 0x07, 0x4F, 0xFC, 0xC3, 0x01, 0x4E,
+	0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE4, 0xFF, 0x1B, 0x00,
+	0x30, 0x00, 0xE4, 0xFE, 0x5F, 0x03, 0x5E, 0xF6, 0x02, 0x43, 0x96,
+	0x17, 0x9B, 0xF5, 0xF8, 0x05, 0x8F, 0xFC, 0xCC, 0x01, 0x3D, 0xFF,
+	0x34, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x86, 0xFF, 0x0C, 0x01, 0x3E,
+	0xFE, 0x7B, 0x02, 0xED, 0xFC, 0x56, 0x03, 0xF5, 0x48, 0x06, 0x04,
+	0xA1, 0xFC, 0xA3, 0x02, 0x2A, 0xFE, 0x16, 0x01, 0x83, 0xFF, 0x1F,
+	0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF, 0xC8, 0x01,
+	0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5, 0xB6, 0x16, 0x77, 0x43, 0xBD,
+	0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D, 0x00, 0x25, 0x00, 0xE1, 0xFF,
+	0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC8, 0x01, 0x49,
+	0xFC, 0x27, 0x07, 0xAB, 0xF1, 0x3E, 0x2C, 0x1E, 0x34, 0x95, 0xF1,
+	0xC1, 0x06, 0xAE, 0xFC, 0x81, 0x01, 0x71, 0xFF, 0x23, 0x00, 0xFE,
+	0xFF, 0x02, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xC4, 0x00, 0xE2, 0xFD,
+	0x01, 0x05, 0xBA, 0xF3, 0x64, 0x3E, 0x3F, 0x1F, 0xA8, 0xF3, 0xB8,
+	0x06, 0x46, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF,
+	0x15, 0x00, 0xA8, 0xFF, 0xB6, 0x00, 0xF3, 0xFE, 0x24, 0x01, 0x6E,
+	0xFF, 0xDE, 0xFD, 0x25, 0x48, 0x68, 0x0A, 0x08, 0xFA, 0xF2, 0x03,
+	0x81, 0xFD, 0x65, 0x01, 0x64, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF,
+	0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x95, 0x01, 0x15, 0xFD, 0xCF, 0x04,
+	0x39, 0xF8, 0x59, 0x0F, 0xAF, 0x46, 0x8B, 0xFA, 0x17, 0x01, 0x38,
+	0x00, 0x73, 0xFF, 0x78, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0xFD, 0xFF,
+	0x36, 0x00, 0x39, 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0B, 0x07, 0x97,
+	0xF2, 0xB8, 0x24, 0x71, 0x3A, 0x7B, 0xF2, 0xE6, 0x05, 0x4C, 0xFD,
+	0x1D, 0x01, 0xA4, 0xFF, 0x11, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x16,
+	0x00, 0x94, 0xFF, 0x3D, 0x01, 0x18, 0xFD, 0x32, 0x06, 0x1F, 0xF2,
+	0xB5, 0x38, 0xEB, 0x26, 0x40, 0xF2, 0x1E, 0x07, 0x32, 0xFC, 0xDF,
+	0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCA, 0xFF,
+	0x5F, 0x00, 0xA5, 0xFF, 0xDC, 0xFF, 0xB8, 0x01, 0x57, 0xF9, 0xE5,
+	0x45, 0x6E, 0x11, 0x83, 0xF7, 0x23, 0x05, 0xEE, 0xFC, 0xA6, 0x01,
+	0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6C,
+	0xFF, 0x4F, 0x01, 0xB0, 0xFD, 0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08,
+	0x86, 0x48, 0x5A, 0xFF, 0xBA, 0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF,
+	0x00, 0x9E, 0xFF, 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF,
+	0xE0, 0x01, 0x56, 0xFC, 0x89, 0x06, 0x2B, 0xF4, 0x06, 0x1D, 0xD7,
+	0x3F, 0x61, 0xF4, 0x94, 0x04, 0x27, 0xFE, 0x9C, 0x00, 0xE6, 0xFF,
+	0xF8, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x27, 0x00, 0x66, 0xFF, 0x97,
+	0x01, 0x8C, 0xFC, 0xEA, 0x06, 0x80, 0xF1, 0x26, 0x32, 0x58, 0x2E,
+	0x8B, 0xF1, 0x1B, 0x07, 0x5B, 0xFC, 0xBA, 0x01, 0x53, 0xFF, 0x2D,
+	0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE9, 0xFF, 0x0E, 0x00, 0x4B, 0x00,
+	0xB4, 0xFE, 0xAF, 0x03, 0xD5, 0xF5, 0x4D, 0x42, 0xE6, 0x18, 0x3C,
+	0xF5, 0x1F, 0x06, 0x7F, 0xFC, 0xD3, 0x01, 0x3B, 0xFF, 0x35, 0x00,
+	0xFE, 0xFF, 0x1C, 0x00, 0x8C, 0xFF, 0xFE, 0x00, 0x5D, 0xFE, 0x3F,
+	0x02, 0x5E, 0xFD, 0x54, 0x02, 0xEC, 0x48, 0x13, 0x05, 0x2E, 0xFC,
+	0xDE, 0x02, 0x0C, 0xFE, 0x24, 0x01, 0x7D, 0xFF, 0x20, 0x00, 0x00,
+	0x00, 0xFF, 0xFF, 0x32, 0x00, 0x41, 0xFF, 0xC1, 0x01, 0xAD, 0xFC,
+	0xB2, 0x05, 0x3F, 0xF6, 0x69, 0x15, 0x1F, 0x44, 0x53, 0xF7, 0xD3,
+	0x02, 0x38, 0xFF, 0x01, 0x00, 0x33, 0x00, 0xDB, 0xFF, 0x09, 0x00,
+	0xFD, 0xFF, 0x31, 0x00, 0x47, 0xFF, 0xCF, 0x01, 0x40, 0xFC, 0x2A,
+	0x07, 0xC6, 0xF1, 0xF7, 0x2A, 0x46, 0x35, 0xAB, 0xF1, 0xA4, 0x06,
+	0xC4, 0xFC, 0x72, 0x01, 0x79, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x02,
+	0x00, 0x04, 0x00, 0xC6, 0xFF, 0xDB, 0x00, 0xBB, 0xFD, 0x3E, 0x05,
+	0x60, 0xF3, 0x7B, 0x3D, 0x94, 0x20, 0x5E, 0xF3, 0xD0, 0x06, 0x3E,
+	0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00,
+	0xAE, 0xFF, 0xA7, 0x00, 0x12, 0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03,
+	0xFD, 0xDC, 0x47, 0x95, 0x0B, 0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD,
+	0x71, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C,
+	0x00, 0x55, 0xFF, 0x8A, 0x01, 0x2E, 0xFD, 0x9B, 0x04, 0xA8, 0xF8,
+	0x1F, 0x0E, 0x1A, 0x47, 0x4E, 0xFB, 0xB3, 0x00, 0x70, 0x00, 0x54,
+	0xFF, 0x87, 0x00, 0xBB, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00,
+	0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC, 0xFB, 0x06, 0xD2, 0xF2, 0x64,
+	0x23, 0x73, 0x3B, 0xBC, 0xF2, 0xB4, 0x05, 0x6E, 0xFD, 0x09, 0x01,
+	0xAF, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8B,
+	0xFF, 0x4F, 0x01, 0xFB, 0xFC, 0x5A, 0x06, 0xF2, 0xF1, 0xA0, 0x37,
+	0x3A, 0x28, 0x13, 0xF2, 0x25, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40,
+	0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xD0, 0xFF, 0x51, 0x00,
+	0xC3, 0xFF, 0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45, 0xB2,
+	0x12, 0x19, 0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47, 0xFF,
+	0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x71, 0xFF, 0x42,
+	0x01, 0xCD, 0xFD, 0x59, 0x03, 0x3B, 0xFB, 0x5E, 0x07, 0xB3, 0x48,
+	0x48, 0x00, 0x4B, 0xFE, 0xC2, 0x01, 0x9F, 0xFE, 0xDE, 0x00, 0x98,
+	0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDD, 0x01,
+	0x62, 0xFC, 0x69, 0x06, 0x7F, 0xF4, 0xB2, 0x1B, 0xAB, 0x40, 0xD0,
+	0xF4, 0x4E, 0x04, 0x53, 0xFE, 0x83, 0x00, 0xF2, 0xFF, 0xF4, 0xFF,
+	0x05, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA3, 0x01, 0x7A,
+	0xFC, 0xFD, 0x06, 0x7C, 0xF1, 0xF2, 0x30, 0x96, 0x2F, 0x80, 0xF1,
+	0x0F, 0x07, 0x69, 0xFC, 0xB0, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0xFD,
+	0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66, 0x00, 0x85, 0xFE,
+	0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A, 0xE1, 0xF4, 0x43,
+	0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF,
+	0x1B, 0x00, 0x92, 0xFF, 0xEF, 0x00, 0x7D, 0xFE, 0x04, 0x02, 0xCF,
+	0xFD, 0x58, 0x01, 0xD7, 0x48, 0x26, 0x06, 0xBB, 0xFB, 0x19, 0x03,
+	0xED, 0xFD, 0x32, 0x01, 0x77, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF,
+	0xFF, 0x32, 0x00, 0x44, 0xFF, 0xB9, 0x01, 0xC1, 0xFC, 0x86, 0x05,
+	0xA5, 0xF6, 0x1E, 0x14, 0xBA, 0x44, 0xF0, 0xF7, 0x7B, 0x02, 0x6B,
+	0xFF, 0xE4, 0xFF, 0x41, 0x00, 0xD6, 0xFF, 0x0B, 0x00, 0xFD, 0xFF,
+	0x33, 0x00, 0x43, 0xFF, 0xD5, 0x01, 0x3A, 0xFC, 0x2A, 0x07, 0xE7,
+	0xF1, 0xAC, 0x29, 0x66, 0x36, 0xC9, 0xF1, 0x83, 0x06, 0xDD, 0xFC,
+	0x62, 0x01, 0x81, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x08,
+	0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96, 0xFD, 0x78, 0x05, 0x0E, 0xF3,
+	0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3, 0xE6, 0x06, 0x38, 0xFC, 0xE6,
+	0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB4, 0xFF,
+	0x98, 0x00, 0x32, 0xFF, 0xB0, 0x00, 0x41, 0x00, 0x30, 0xFC, 0x86,
+	0x47, 0xC6, 0x0C, 0x24, 0xF9, 0x60, 0x04, 0x4B, 0xFD, 0x7D, 0x01,
+	0x5A, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x5A,
+	0xFF, 0x7E, 0x01, 0x48, 0xFD, 0x66, 0x04, 0x18, 0xF9, 0xE8, 0x0C,
+	0x7C, 0x47, 0x19, 0xFC, 0x4D, 0x00, 0xA9, 0x00, 0x35, 0xFF, 0x96,
+	0x00, 0xB5, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF,
+	0xE6, 0x01, 0x38, 0xFC, 0xE9, 0x06, 0x12, 0xF3, 0x10, 0x22, 0x6E,
+	0x3C, 0x05, 0xF3, 0x7E, 0x05, 0x91, 0xFD, 0xF4, 0x00, 0xBA, 0xFF,
+	0x09, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60,
+	0x01, 0xE0, 0xFC, 0x7F, 0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29,
+	0xEB, 0xF1, 0x2A, 0x07, 0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33,
+	0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD5, 0xFF, 0x42, 0x00, 0xE1, 0xFF,
+	0x71, 0xFF, 0x71, 0x02, 0x02, 0xF8, 0xCC, 0x44, 0xFA, 0x13, 0xB0,
+	0xF6, 0x81, 0x05, 0xC3, 0xFC, 0xB8, 0x01, 0x44, 0xFF, 0x31, 0x00,
+	0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x77, 0xFF, 0x34, 0x01, 0xEA,
+	0xFD, 0x1F, 0x03, 0xAE, 0xFB, 0x45, 0x06, 0xD5, 0x48, 0x3C, 0x01,
+	0xDC, 0xFD, 0xFD, 0x01, 0x80, 0xFE, 0xED, 0x00, 0x93, 0xFF, 0x1B,
+	0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD8, 0x01, 0x6F, 0xFC,
+	0x47, 0x06, 0xD7, 0xF4, 0x5D, 0x1A, 0x74, 0x41, 0x48, 0xF5, 0x04,
+	0x04, 0x80, 0xFE, 0x69, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0x05, 0x00,
+	0xFD, 0xFF, 0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D,
+	0x07, 0x80, 0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06,
+	0x78, 0xFC, 0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x05,
+	0x00, 0xF3, 0xFF, 0xF4, 0xFF, 0x80, 0x00, 0x58, 0xFE, 0x46, 0x04,
+	0xDD, 0xF4, 0xC3, 0x40, 0x8C, 0x1B, 0x89, 0xF4, 0x66, 0x06, 0x63,
+	0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00,
+	0x98, 0xFF, 0xE0, 0x00, 0x9C, 0xFE, 0xC8, 0x01, 0x3F, 0xFE, 0x62,
+	0x00, 0xB8, 0x48, 0x3F, 0x07, 0x47, 0xFB, 0x53, 0x03, 0xD0, 0xFD,
+	0x40, 0x01, 0x72, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30,
+	0x00, 0x47, 0xFF, 0xB0, 0x01, 0xD6, 0xFC, 0x58, 0x05, 0x0D, 0xF7,
+	0xD7, 0x12, 0x4E, 0x45, 0x96, 0xF8, 0x20, 0x02, 0xA0, 0xFF, 0xC7,
+	0xFF, 0x4F, 0x00, 0xD0, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00,
+	0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E, 0xF2, 0x60,
+	0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC, 0x51, 0x01,
+	0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0C, 0x00, 0xB0,
+	0xFF, 0x07, 0x01, 0x72, 0xFD, 0xAE, 0x05, 0xC4, 0xF2, 0x90, 0x3B,
+	0x3F, 0x23, 0xD9, 0xF2, 0xF9, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38,
+	0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, 0x00, 0xBA, 0xFF, 0x89, 0x00,
+	0x51, 0xFF, 0x77, 0x00, 0xA7, 0x00, 0x64, 0xFB, 0x26, 0x47, 0xFC,
+	0x0D, 0xB4, 0xF8, 0x95, 0x04, 0x31, 0xFD, 0x88, 0x01, 0x56, 0xFF,
+	0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x5E, 0xFF, 0x72,
+	0x01, 0x62, 0xFD, 0x2F, 0x04, 0x89, 0xF9, 0xB6, 0x0B, 0xD2, 0x47,
+	0xEB, 0xFC, 0xE4, 0xFF, 0xE3, 0x00, 0x16, 0xFF, 0xA5, 0x00, 0xAF,
+	0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01,
+	0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3, 0xBA, 0x20, 0x61, 0x3D, 0x56,
+	0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE, 0x00, 0xC5, 0xFF, 0x05, 0x00,
+	0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x7A, 0xFF, 0x70, 0x01, 0xC7,
+	0xFC, 0xA0, 0x06, 0xAE, 0xF1, 0x65, 0x35, 0xD1, 0x2A, 0xCA, 0xF1,
+	0x2A, 0x07, 0x40, 0xFC, 0xD0, 0x01, 0x47, 0xFF, 0x32, 0x00, 0xFD,
+	0xFF, 0x09, 0x00, 0xDB, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x3D, 0xFF,
+	0xC9, 0x02, 0x64, 0xF7, 0x2F, 0x44, 0x44, 0x15, 0x4A, 0xF6, 0xAD,
+	0x05, 0xAF, 0xFC, 0xC0, 0x01, 0x41, 0xFF, 0x32, 0x00, 0xFF, 0xFF,
+	0x00, 0x00, 0x21, 0x00, 0x7C, 0xFF, 0x26, 0x01, 0x08, 0xFE, 0xE4,
+	0x02, 0x21, 0xFC, 0x31, 0x05, 0xEB, 0x48, 0x37, 0x02, 0x6B, 0xFD,
+	0x39, 0x02, 0x61, 0xFE, 0xFC, 0x00, 0x8D, 0xFF, 0x1C, 0x00, 0xFE,
+	0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD3, 0x01, 0x7D, 0xFC, 0x23, 0x06,
+	0x32, 0xF5, 0x0C, 0x19, 0x38, 0x42, 0xC7, 0xF5, 0xB8, 0x03, 0xAF,
+	0xFE, 0x4E, 0x00, 0x0C, 0x00, 0xEA, 0xFF, 0x06, 0x00, 0xFD, 0xFF,
+	0x2D, 0x00, 0x54, 0xFF, 0xB8, 0x01, 0x5D, 0xFC, 0x1A, 0x07, 0x8A,
+	0xF1, 0x7B, 0x2E, 0x04, 0x32, 0x7F, 0xF1, 0xEC, 0x06, 0x8A, 0xFC,
+	0x98, 0x01, 0x65, 0xFF, 0x27, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF8,
+	0xFF, 0xE7, 0xFF, 0x99, 0x00, 0x2C, 0xFE, 0x8C, 0x04, 0x6D, 0xF4,
+	0xF0, 0x3F, 0xE0, 0x1C, 0x34, 0xF4, 0x85, 0x06, 0x57, 0xFC, 0xE0,
+	0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00, 0x9E, 0xFF,
+	0xD1, 0x00, 0xBB, 0xFE, 0x8D, 0x01, 0xAE, 0xFE, 0x74, 0xFF, 0x8D,
+	0x48, 0x5D, 0x08, 0xD4, 0xFA, 0x8D, 0x03, 0xB3, 0xFD, 0x4E, 0x01,
+	0x6D, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4A,
+	0xFF, 0xA7, 0x01, 0xEC, 0xFC, 0x28, 0x05, 0x77, 0xF7, 0x92, 0x11,
+	0xD7, 0x45, 0x43, 0xF9, 0xC3, 0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E,
+	0x00, 0xCB, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3D, 0xFF,
+	0xDF, 0x01, 0x32, 0xFC, 0x1F, 0x07, 0x3B, 0xF2, 0x11, 0x27, 0x97,
+	0x38, 0x19, 0xF2, 0x36, 0x06, 0x15, 0xFD, 0x3F, 0x01, 0x93, 0xFF,
+	0x17, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x10, 0x00, 0xA6, 0xFF, 0x1B,
+	0x01, 0x50, 0xFD, 0xE1, 0x05, 0x82, 0xF2, 0x8F, 0x3A, 0x92, 0x24,
+	0x9D, 0xF2, 0x09, 0x07, 0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36,
+	0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0x7A, 0x00, 0x70, 0xFF,
+	0x3E, 0x00, 0x0C, 0x01, 0xA1, 0xFA, 0xBB, 0x46, 0x36, 0x0F, 0x45,
+	0xF8, 0xC9, 0x04, 0x18, 0xFD, 0x93, 0x01, 0x52, 0xFF, 0x2D, 0x00,
+	0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x63, 0xFF, 0x66, 0x01, 0x7D,
+	0xFD, 0xF8, 0x03, 0xFB, 0xF9, 0x89, 0x0A, 0x1D, 0x48, 0xC5, 0xFD,
+	0x7A, 0xFF, 0x1D, 0x01, 0xF7, 0xFE, 0xB4, 0x00, 0xA9, 0xFF, 0x15,
+	0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x45, 0xFC,
+	0xBB, 0x06, 0xA0, 0xF3, 0x64, 0x1F, 0x4A, 0x3E, 0xB0, 0xF3, 0x08,
+	0x05, 0xDE, 0xFD, 0xC7, 0x00, 0xD0, 0xFF, 0x00, 0x00, 0x02, 0x00,
+	0xFE, 0xFF, 0x23, 0x00, 0x72, 0xFF, 0x7F, 0x01, 0xB0, 0xFC, 0xBE,
+	0x06, 0x97, 0xF1, 0x3F, 0x34, 0x19, 0x2C, 0xAD, 0xF1, 0x28, 0x07,
+	0x48, 0xFC, 0xC9, 0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08,
+	0x00, 0xE0, 0xFF, 0x26, 0x00, 0x1A, 0x00, 0x0B, 0xFF, 0x1E, 0x03,
+	0xCD, 0xF6, 0x89, 0x43, 0x91, 0x16, 0xE7, 0xF5, 0xD8, 0x05, 0x9D,
+	0xFC, 0xC7, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00,
+	0x1F, 0x00, 0x82, 0xFF, 0x18, 0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94,
+	0xFC, 0x24, 0x04, 0xF5, 0x48, 0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02,
+	0x42, 0xFE, 0x0B, 0x01, 0x87, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34,
+	0x00, 0x3C, 0xFF, 0xCD, 0x01, 0x8E, 0xFC, 0xFC, 0x05, 0x90, 0xF5,
+	0xBB, 0x17, 0xEE, 0x42, 0x4E, 0xF6, 0x68, 0x03, 0xDF, 0xFE, 0x33,
+	0x00, 0x1A, 0x00, 0xE5, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2F, 0x00,
+	0x4F, 0xFF, 0xC2, 0x01, 0x51, 0xFC, 0x23, 0x07, 0x9A, 0xF1, 0x3A,
+	0x2D, 0x35, 0x33, 0x89, 0xF1, 0xD5, 0x06, 0x9D, 0xFC, 0x8B, 0x01,
+	0x6C, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFC, 0xFF, 0xDB,
+	0xFF, 0xB2, 0x00, 0x02, 0xFE, 0xCF, 0x04, 0x05, 0xF4, 0x16, 0x3F,
+	0x36, 0x1E, 0xE4, 0xF3, 0xA3, 0x06, 0x4D, 0xFC, 0xE3, 0x01, 0x36,
+	0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC2, 0x00,
+	0xDB, 0xFE, 0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48, 0x81,
+	0x09, 0x61, 0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68, 0xFF,
+	0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9D,
+	0x01, 0x03, 0xFD, 0xF7, 0x04, 0xE3, 0xF7, 0x51, 0x10, 0x55, 0x46,
+	0xF9, 0xF9, 0x63, 0x01, 0x0D, 0x00, 0x8B, 0xFF, 0x6D, 0x00, 0xC5,
+	0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01,
+	0x31, 0xFC, 0x15, 0x07, 0x6D, 0xF2, 0xBF, 0x25, 0xA5, 0x39, 0x4D,
+	0xF2, 0x0B, 0x06, 0x33, 0xFD, 0x2D, 0x01, 0x9D, 0xFF, 0x13, 0x00,
+	0xFF, 0xFF, 0xFF, 0xFF, 0x14, 0x00, 0x9C, 0xFF, 0x2F, 0x01, 0x30,
+	0xFD, 0x10, 0x06, 0x47, 0xF2, 0x87, 0x39, 0xE5, 0x25, 0x67, 0xF2,
+	0x16, 0x07, 0x31, 0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD,
+	0xFF, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E, 0xFF, 0x06, 0x00,
+	0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10, 0xD7, 0xF7, 0xFC,
+	0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF,
+	0x00, 0x00, 0x26, 0x00, 0x68, 0xFF, 0x59, 0x01, 0x99, 0xFD, 0xC0,
+	0x03, 0x6E, 0xFA, 0x61, 0x09, 0x5D, 0x48, 0xA6, 0xFE, 0x0F, 0xFF,
+	0x58, 0x01, 0xD7, 0xFE, 0xC3, 0x00, 0xA3, 0xFF, 0x16, 0x00, 0xFE,
+	0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4E, 0xFC, 0xA0, 0x06,
+	0xED, 0xF3, 0x0F, 0x1E, 0x2D, 0x3F, 0x10, 0xF4, 0xC8, 0x04, 0x07,
+	0xFE, 0xAF, 0x00, 0xDC, 0xFF, 0xFC, 0xFF, 0x03, 0x00, 0xFD, 0xFF,
+	0x25, 0x00, 0x6B, 0xFF, 0x8D, 0x01, 0x9B, 0xFC, 0xD8, 0x06, 0x87,
+	0xF1, 0x13, 0x33, 0x5E, 0x2D, 0x98, 0xF1, 0x22, 0x07, 0x52, 0xFC,
+	0xC1, 0x01, 0x4F, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE5,
+	0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9, 0xFE, 0x71, 0x03, 0x3F, 0xF6,
+	0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5, 0x00, 0x06, 0x8C, 0xFC, 0xCE,
+	0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x88, 0xFF,
+	0x09, 0x01, 0x45, 0xFE, 0x6E, 0x02, 0x06, 0xFD, 0x1C, 0x03, 0xF4,
+	0x48, 0x41, 0x04, 0x87, 0xFC, 0xB0, 0x02, 0x23, 0xFE, 0x19, 0x01,
+	0x81, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F,
+	0xFF, 0xC6, 0x01, 0x9F, 0xFC, 0xD3, 0x05, 0xF1, 0xF5, 0x6C, 0x16,
+	0x9E, 0x43, 0xDD, 0xF6, 0x15, 0x03, 0x10, 0xFF, 0x17, 0x00, 0x28,
+	0x00, 0xDF, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF,
+	0xCA, 0x01, 0x47, 0xFC, 0x28, 0x07, 0xB0, 0xF1, 0xF5, 0x2B, 0x60,
+	0x34, 0x9A, 0xF1, 0xBB, 0x06, 0xB3, 0xFC, 0x7D, 0x01, 0x73, 0xFF,
+	0x22, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9,
+	0x00, 0xDA, 0xFD, 0x0F, 0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F,
+	0x97, 0xF3, 0xBD, 0x06, 0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36,
+	0x00, 0xFD, 0xFF, 0x15, 0x00, 0xAA, 0xFF, 0xB3, 0x00, 0xFA, 0xFE,
+	0x17, 0x01, 0x86, 0xFF, 0xAC, 0xFD, 0x16, 0x48, 0xAA, 0x0A, 0xEE,
+	0xF9, 0xFE, 0x03, 0x7A, 0xFD, 0x67, 0x01, 0x63, 0xFF, 0x28, 0x00,
+	0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x92, 0x01, 0x1B,
+	0xFD, 0xC4, 0x04, 0x51, 0xF8, 0x13, 0x0F, 0xC8, 0x46, 0xB6, 0xFA,
+	0x01, 0x01, 0x44, 0x00, 0x6C, 0xFF, 0x7B, 0x00, 0xBF, 0xFF, 0x10,
+	0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC,
+	0x08, 0x07, 0xA4, 0xF2, 0x6D, 0x24, 0xAD, 0x3A, 0x88, 0xF2, 0xDB,
+	0x05, 0x53, 0xFD, 0x19, 0x01, 0xA7, 0xFF, 0x10, 0x00, 0x00, 0x00,
+	0xFF, 0xFF, 0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B,
+	0x06, 0x14, 0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07,
+	0x33, 0xFC, 0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0D,
+	0x00, 0xCB, 0xFF, 0x5C, 0x00, 0xAC, 0xFF, 0xD0, 0xFF, 0xCD, 0x01,
+	0x30, 0xF9, 0xC8, 0x45, 0xB6, 0x11, 0x6B, 0xF7, 0x2D, 0x05, 0xE9,
+	0xFC, 0xA8, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00,
+	0x25, 0x00, 0x6D, 0xFF, 0x4C, 0x01, 0xB6, 0xFD, 0x86, 0x03, 0xE1,
+	0xFA, 0x3D, 0x08, 0x92, 0x48, 0x8E, 0xFF, 0xA1, 0xFE, 0x93, 0x01,
+	0xB8, 0xFE, 0xD3, 0x00, 0x9D, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36,
+	0x00, 0x37, 0xFF, 0xE0, 0x01, 0x58, 0xFC, 0x82, 0x06, 0x3E, 0xF4,
+	0xBA, 0x1C, 0x07, 0x40, 0x79, 0xF4, 0x84, 0x04, 0x31, 0xFE, 0x96,
+	0x00, 0xE8, 0xFF, 0xF7, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00,
+	0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E, 0xF1, 0xE3,
+	0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC, 0xB7, 0x01,
+	0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEA, 0xFF, 0x0B,
+	0x00, 0x51, 0x00, 0xAA, 0xFE, 0xC0, 0x03, 0xB8, 0xF5, 0x21, 0x42,
+	0x31, 0x19, 0x28, 0xF5, 0x27, 0x06, 0x7C, 0xFC, 0xD4, 0x01, 0x3A,
+	0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8D, 0xFF, 0xFA, 0x00,
+	0x64, 0xFE, 0x32, 0x02, 0x78, 0xFD, 0x1B, 0x02, 0xEA, 0x48, 0x50,
+	0x05, 0x14, 0xFC, 0xEB, 0x02, 0x05, 0xFE, 0x27, 0x01, 0x7C, 0xFF,
+	0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x41, 0xFF, 0xBF,
+	0x01, 0xB2, 0xFC, 0xA9, 0x05, 0x55, 0xF6, 0x20, 0x15, 0x42, 0x44,
+	0x75, 0xF7, 0xBF, 0x02, 0x43, 0xFF, 0xFA, 0xFF, 0x36, 0x00, 0xDA,
+	0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF, 0xD1, 0x01,
+	0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1, 0xAE, 0x2A, 0x86, 0x35, 0xB1,
+	0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E, 0x01, 0x7B, 0xFF, 0x20, 0x00,
+	0xFE, 0xFF, 0x02, 0x00, 0x05, 0x00, 0xC3, 0xFF, 0xE0, 0x00, 0xB3,
+	0xFD, 0x4B, 0x05, 0x4D, 0xF3, 0x45, 0x3D, 0xE0, 0x20, 0x4F, 0xF3,
+	0xD5, 0x06, 0x3D, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD,
+	0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA4, 0x00, 0x19, 0xFF, 0xDD, 0x00,
+	0xF0, 0xFF, 0xD4, 0xFC, 0xC9, 0x47, 0xD8, 0x0B, 0x7C, 0xF9, 0x35,
+	0x04, 0x5F, 0xFD, 0x74, 0x01, 0x5E, 0xFF, 0x29, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x2C, 0x00, 0x56, 0xFF, 0x87, 0x01, 0x34, 0xFD, 0x8F,
+	0x04, 0xC0, 0xF8, 0xD9, 0x0D, 0x31, 0x47, 0x7B, 0xFB, 0x9C, 0x00,
+	0x7D, 0x00, 0x4D, 0xFF, 0x8A, 0x00, 0xB9, 0xFF, 0x11, 0x00, 0xFD,
+	0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF7, 0x06,
+	0xE0, 0xF2, 0x18, 0x23, 0xAB, 0x3B, 0xCC, 0xF2, 0xA8, 0x05, 0x76,
+	0xFD, 0x04, 0x01, 0xB1, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0xFE, 0xFF,
+	0x1A, 0x00, 0x89, 0xFF, 0x53, 0x01, 0xF5, 0xFC, 0x63, 0x06, 0xE9,
+	0xF1, 0x63, 0x37, 0x85, 0x28, 0x09, 0xF2, 0x27, 0x07, 0x35, 0xFC,
+	0xDA, 0x01, 0x40, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xD1,
+	0xFF, 0x4E, 0x00, 0xCA, 0xFF, 0x9A, 0xFF, 0x2A, 0x02, 0x83, 0xF8,
+	0x3F, 0x45, 0xFB, 0x12, 0x01, 0xF7, 0x5D, 0x05, 0xD3, 0xFC, 0xB1,
+	0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00,
+	0x73, 0xFF, 0x3F, 0x01, 0xD3, 0xFD, 0x4C, 0x03, 0x54, 0xFB, 0x1F,
+	0x07, 0xBB, 0x48, 0x7D, 0x00, 0x33, 0xFE, 0xCF, 0x01, 0x98, 0xFE,
+	0xE2, 0x00, 0x97, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38,
+	0xFF, 0xDC, 0x01, 0x64, 0xFC, 0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B,
+	0xD9, 0x40, 0xEA, 0xF4, 0x3E, 0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5,
+	0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5E, 0xFF,
+	0xA6, 0x01, 0x76, 0xFC, 0x01, 0x07, 0x7D, 0xF1, 0xAD, 0x30, 0xDC,
+	0x2F, 0x7F, 0xF1, 0x0C, 0x07, 0x6C, 0xFC, 0xAD, 0x01, 0x5A, 0xFF,
+	0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xEF, 0xFF, 0xFE, 0xFF, 0x6C,
+	0x00, 0x7B, 0xFE, 0x0C, 0x04, 0x3A, 0xF5, 0x5F, 0x41, 0x83, 0x1A,
+	0xCD, 0xF4, 0x4B, 0x06, 0x6D, 0xFC, 0xD9, 0x01, 0x39, 0xFF, 0x35,
+	0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x93, 0xFF, 0xEC, 0x00, 0x83, 0xFE,
+	0xF7, 0x01, 0xE8, 0xFD, 0x21, 0x01, 0xD2, 0x48, 0x64, 0x06, 0xA1,
+	0xFB, 0x26, 0x03, 0xE7, 0xFD, 0x35, 0x01, 0x76, 0xFF, 0x22, 0x00,
+	0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x44, 0xFF, 0xB7, 0x01, 0xC5,
+	0xFC, 0x7C, 0x05, 0xBC, 0xF6, 0xD5, 0x13, 0xDC, 0x44, 0x14, 0xF8,
+	0x67, 0x02, 0x77, 0xFF, 0xDD, 0xFF, 0x44, 0x00, 0xD5, 0xFF, 0x0B,
+	0x00, 0xFD, 0xFF, 0x33, 0x00, 0x42, 0xFF, 0xD7, 0x01, 0x39, 0xFC,
+	0x29, 0x07, 0xEF, 0xF1, 0x62, 0x29, 0xA5, 0x36, 0xD0, 0xF1, 0x7B,
+	0x06, 0xE3, 0xFC, 0x5E, 0x01, 0x83, 0xFF, 0x1D, 0x00, 0xFE, 0xFF,
+	0x01, 0x00, 0x09, 0x00, 0xB8, 0xFF, 0xF6, 0x00, 0x8D, 0xFD, 0x84,
+	0x05, 0xFD, 0xF2, 0x52, 0x3C, 0x35, 0x22, 0x0B, 0xF3, 0xEB, 0x06,
+	0x37, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12,
+	0x00, 0xB5, 0xFF, 0x94, 0x00, 0x39, 0xFF, 0xA3, 0x00, 0x58, 0x00,
+	0x02, 0xFC, 0x73, 0x47, 0x0B, 0x0D, 0x0B, 0xF9, 0x6C, 0x04, 0x45,
+	0xFD, 0x80, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x2A, 0x00, 0x5B, 0xFF, 0x7C, 0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31,
+	0xF9, 0xA4, 0x0C, 0x90, 0x47, 0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00,
+	0x2E, 0xFF, 0x99, 0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36,
+	0x00, 0x37, 0xFF, 0xE6, 0x01, 0x39, 0xFC, 0xE4, 0x06, 0x21, 0xF3,
+	0xC4, 0x21, 0xA5, 0x3C, 0x16, 0xF3, 0x72, 0x05, 0x9A, 0xFD, 0xEF,
+	0x00, 0xBC, 0xFF, 0x08, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1E, 0x00,
+	0x80, 0xFF, 0x64, 0x01, 0xDA, 0xFC, 0x87, 0x06, 0xC5, 0xF1, 0x46,
+	0x36, 0xD1, 0x29, 0xE3, 0xF1, 0x2A, 0x07, 0x3A, 0xFC, 0xD5, 0x01,
+	0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD6, 0xFF, 0x3F,
+	0x00, 0xE7, 0xFF, 0x65, 0xFF, 0x85, 0x02, 0xDE, 0xF7, 0xA9, 0x44,
+	0x43, 0x14, 0x99, 0xF6, 0x8B, 0x05, 0xBF, 0xFC, 0xBA, 0x01, 0x43,
+	0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x78, 0xFF,
+	0x31, 0x01, 0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06, 0xDB,
+	0x48, 0x73, 0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1, 0x00,
+	0x91, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7,
+	0x01, 0x72, 0xFC, 0x3F, 0x06, 0xEB, 0xF4, 0x12, 0x1A, 0xA1, 0x41,
+	0x63, 0xF5, 0xF3, 0x03, 0x8A, 0xFE, 0x63, 0x00, 0x02, 0x00, 0xEE,
+	0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x58, 0xFF, 0xB1, 0x01,
+	0x67, 0xFC, 0x10, 0x07, 0x81, 0xF1, 0x73, 0x2F, 0x15, 0x31, 0x7C,
+	0xF1, 0xFB, 0x06, 0x7C, 0xFC, 0xA2, 0x01, 0x60, 0xFF, 0x29, 0x00,
+	0xFD, 0xFF, 0x04, 0x00, 0xF4, 0xFF, 0xF1, 0xFF, 0x85, 0x00, 0x4E,
+	0xFE, 0x56, 0x04, 0xC3, 0xF4, 0x95, 0x40, 0xD8, 0x1B, 0x76, 0xF4,
+	0x6D, 0x06, 0x60, 0xFC, 0xDD, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE,
+	0xFF, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3, 0xFE, 0xBB, 0x01,
+	0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07, 0x2E, 0xFB, 0x60,
+	0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24, 0x00, 0x00, 0x00,
+	0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAE, 0x01, 0xDB, 0xFC, 0x4D,
+	0x05, 0x24, 0xF7, 0x8E, 0x12, 0x6D, 0x45, 0xBC, 0xF8, 0x0C, 0x02,
+	0xAC, 0xFF, 0xC0, 0xFF, 0x52, 0x00, 0xCF, 0xFF, 0x0C, 0x00, 0xFD,
+	0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDC, 0x01, 0x34, 0xFC, 0x25, 0x07,
+	0x18, 0xF2, 0x15, 0x28, 0xBF, 0x37, 0xF7, 0xF1, 0x56, 0x06, 0xFE,
+	0xFC, 0x4D, 0x01, 0x8C, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x00, 0x00,
+	0x0D, 0x00, 0xAE, 0xFF, 0x0B, 0x01, 0x6A, 0xFD, 0xBA, 0x05, 0xB4,
+	0xF2, 0x58, 0x3B, 0x8A, 0x23, 0xCB, 0xF2, 0xFD, 0x06, 0x34, 0xFC,
+	0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBB,
+	0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A, 0x00, 0xBE, 0x00, 0x38, 0xFB,
+	0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8, 0xA1, 0x04, 0x2B, 0xFD, 0x8B,
+	0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00,
+	0x5F, 0xFF, 0x70, 0x01, 0x68, 0xFD, 0x23, 0x04, 0xA2, 0xF9, 0x73,
+	0x0B, 0xE4, 0x47, 0x1B, 0xFD, 0xCD, 0xFF, 0xF0, 0x00, 0x0F, 0xFF,
+	0xA9, 0x00, 0xAE, 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36,
+	0xFF, 0xE6, 0x01, 0x3F, 0xFC, 0xCE, 0x06, 0x66, 0xF3, 0x6F, 0x20,
+	0x96, 0x3D, 0x69, 0xF3, 0x38, 0x05, 0xBF, 0xFD, 0xD9, 0x00, 0xC7,
+	0xFF, 0x04, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x78, 0xFF,
+	0x74, 0x01, 0xC2, 0xFC, 0xA7, 0x06, 0xA8, 0xF1, 0x25, 0x35, 0x1B,
+	0x2B, 0xC2, 0xF1, 0x2A, 0x07, 0x41, 0xFC, 0xCE, 0x01, 0x47, 0xFF,
+	0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04,
+	0x00, 0x32, 0xFF, 0xDC, 0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15,
+	0x34, 0xF6, 0xB7, 0x05, 0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33,
+	0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7E, 0xFF, 0x23, 0x01,
+	0x0F, 0xFE, 0xD7, 0x02, 0x3B, 0xFC, 0xF5, 0x04, 0xED, 0x48, 0x70,
+	0x02, 0x52, 0xFD, 0x46, 0x02, 0x5A, 0xFE, 0xFF, 0x00, 0x8B, 0xFF,
+	0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xD2, 0x01, 0x81,
+	0xFC, 0x1A, 0x06, 0x47, 0xF5, 0xC1, 0x18, 0x60, 0x42, 0xE4, 0xF5,
+	0xA6, 0x03, 0xB9, 0xFE, 0x48, 0x00, 0x0F, 0x00, 0xE9, 0xFF, 0x07,
+	0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x53, 0xFF, 0xBB, 0x01, 0x5A, 0xFC,
+	0x1C, 0x07, 0x8D, 0xF1, 0x34, 0x2E, 0x48, 0x32, 0x81, 0xF1, 0xE7,
+	0x06, 0x8E, 0xFC, 0x96, 0x01, 0x66, 0xFF, 0x27, 0x00, 0xFD, 0xFF,
+	0x04, 0x00, 0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B,
+	0x04, 0x55, 0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06,
+	0x55, 0xFC, 0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17,
+	0x00, 0x9F, 0xFF, 0xCE, 0x00, 0xC2, 0xFE, 0x80, 0x01, 0xC6, 0xFE,
+	0x40, 0xFF, 0x81, 0x48, 0x9E, 0x08, 0xBA, 0xFA, 0x9A, 0x03, 0xAC,
+	0xFD, 0x51, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+	0x2F, 0x00, 0x4B, 0xFF, 0xA4, 0x01, 0xF1, 0xFC, 0x1D, 0x05, 0x8F,
+	0xF7, 0x4A, 0x11, 0xF2, 0x45, 0x6B, 0xF9, 0xAE, 0x01, 0xE2, 0xFF,
+	0xA2, 0xFF, 0x61, 0x00, 0xC9, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x35,
+	0x00, 0x3D, 0xFF, 0xE0, 0x01, 0x32, 0xFC, 0x1D, 0x07, 0x45, 0xF2,
+	0xC6, 0x26, 0xD3, 0x38, 0x24, 0xF2, 0x2D, 0x06, 0x1B, 0xFD, 0x3B,
+	0x01, 0x95, 0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x11, 0x00,
+	0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74, 0xF2, 0x54,
+	0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC, 0xE4, 0x01,
+	0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC1, 0xFF, 0x76,
+	0x00, 0x76, 0xFF, 0x32, 0x00, 0x22, 0x01, 0x76, 0xFA, 0xA3, 0x46,
+	0x7D, 0x0F, 0x2C, 0xF8, 0xD5, 0x04, 0x13, 0xFD, 0x96, 0x01, 0x51,
+	0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x64, 0xFF,
+	0x63, 0x01, 0x84, 0xFD, 0xEB, 0x03, 0x14, 0xFA, 0x47, 0x0A, 0x2C,
+	0x48, 0xF6, 0xFD, 0x63, 0xFF, 0x2B, 0x01, 0xF0, 0xFE, 0xB8, 0x00,
+	0xA8, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4,
+	0x01, 0x47, 0xFC, 0xB5, 0x06, 0xB0, 0xF3, 0x19, 0x1F, 0x7E, 0x3E,
+	0xC4, 0xF3, 0xFA, 0x04, 0xE7, 0xFD, 0xC1, 0x00, 0xD3, 0xFF, 0xFF,
+	0xFF, 0x02, 0x00, 0xFE, 0xFF, 0x23, 0x00, 0x71, 0xFF, 0x82, 0x01,
+	0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1, 0xFD, 0x33, 0x62, 0x2C, 0xA8,
+	0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7, 0x01, 0x4C, 0xFF, 0x30, 0x00,
+	0xFD, 0xFF, 0x08, 0x00, 0xE1, 0xFF, 0x23, 0x00, 0x20, 0x00, 0x00,
+	0xFF, 0x31, 0x03, 0xAD, 0xF6, 0x65, 0x43, 0xDC, 0x16, 0xD1, 0xF5,
+	0xE1, 0x05, 0x99, 0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF,
+	0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83, 0xFF, 0x14, 0x01, 0x2D, 0xFE,
+	0x9C, 0x02, 0xAD, 0xFC, 0xE9, 0x03, 0xF6, 0x48, 0x73, 0x03, 0xE0,
+	0xFC, 0x82, 0x02, 0x3B, 0xFE, 0x0E, 0x01, 0x86, 0xFF, 0x1E, 0x00,
+	0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCC, 0x01, 0x91, 0xFC, 0xF3,
+	0x05, 0xA6, 0xF5, 0x70, 0x17, 0x17, 0x43, 0x6D, 0xF6, 0x56, 0x03,
+	0xEA, 0xFE, 0x2D, 0x00, 0x1D, 0x00, 0xE4, 0xFF, 0x08, 0x00, 0xFD,
+	0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3, 0x01, 0x4E, 0xFC, 0x24, 0x07,
+	0x9E, 0xF1, 0xF2, 0x2C, 0x78, 0x33, 0x8C, 0xF1, 0xD0, 0x06, 0xA2,
+	0xFC, 0x88, 0x01, 0x6D, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x03, 0x00,
+	0xFD, 0xFF, 0xD8, 0xFF, 0xB7, 0x00, 0xF9, 0xFD, 0xDE, 0x04, 0xEF,
+	0xF3, 0xE4, 0x3E, 0x81, 0x1E, 0xD2, 0xF3, 0xA9, 0x06, 0x4B, 0xFC,
+	0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA5,
+	0xFF, 0xBE, 0x00, 0xE2, 0xFE, 0x45, 0x01, 0x33, 0xFF, 0x5A, 0xFE,
+	0x48, 0x48, 0xC3, 0x09, 0x47, 0xFA, 0xD2, 0x03, 0x90, 0xFD, 0x5E,
+	0x01, 0x66, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00,
+	0x4F, 0xFF, 0x9A, 0x01, 0x08, 0xFD, 0xEB, 0x04, 0xFC, 0xF7, 0x0A,
+	0x10, 0x70, 0x46, 0x22, 0xFA, 0x4D, 0x01, 0x19, 0x00, 0x84, 0xFF,
+	0x70, 0x00, 0xC4, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B,
+	0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x12, 0x07, 0x79, 0xF2, 0x73, 0x25,
+	0xDF, 0x39, 0x5A, 0xF2, 0x00, 0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F,
+	0xFF, 0x13, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x15, 0x00, 0x99, 0xFF,
+	0x33, 0x01, 0x29, 0xFD, 0x1A, 0x06, 0x3B, 0xF2, 0x4B, 0x39, 0x30,
+	0x26, 0x5B, 0xF2, 0x19, 0x07, 0x31, 0xFC, 0xE1, 0x01, 0x3C, 0xFF,
+	0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, 0xC7, 0xFF, 0x68, 0x00, 0x95,
+	0xFF, 0xFA, 0xFF, 0x83, 0x01, 0xBB, 0xF9, 0x2B, 0x46, 0xBB, 0x10,
+	0xBF, 0xF7, 0x07, 0x05, 0xFB, 0xFC, 0xA0, 0x01, 0x4D, 0xFF, 0x2F,
+	0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x69, 0xFF, 0x56, 0x01,
+	0xA0, 0xFD, 0xB3, 0x03, 0x87, 0xFA, 0x1F, 0x09, 0x6A, 0x48, 0xD9,
+	0xFE, 0xF6, 0xFE, 0x65, 0x01, 0xD0, 0xFE, 0xC7, 0x00, 0xA2, 0xFF,
+	0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x50,
+	0xFC, 0x99, 0x06, 0xFE, 0xF3, 0xC3, 0x1D, 0x5E, 0x3F, 0x27, 0xF4,
+	0xB9, 0x04, 0x10, 0xFE, 0xA9, 0x00, 0xDF, 0xFF, 0xFB, 0xFF, 0x03,
+	0x00, 0xFD, 0xFF, 0x26, 0x00, 0x69, 0xFF, 0x90, 0x01, 0x96, 0xFC,
+	0xDD, 0x06, 0x85, 0xF1, 0xD0, 0x32, 0xA6, 0x2D, 0x94, 0xF1, 0x20,
+	0x07, 0x54, 0xFC, 0xBF, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFD, 0xFF,
+	0x07, 0x00, 0xE6, 0xFF, 0x15, 0x00, 0x3C, 0x00, 0xCF, 0xFE, 0x83,
+	0x03, 0x20, 0xF6, 0xB2, 0x42, 0x2B, 0x18, 0x71, 0xF5, 0x09, 0x06,
+	0x88, 0xFC, 0xCF, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D,
+	0x00, 0x89, 0xFF, 0x06, 0x01, 0x4C, 0xFE, 0x60, 0x02, 0x1F, 0xFD,
+	0xE2, 0x02, 0xF3, 0x48, 0x7D, 0x04, 0x6E, 0xFC, 0xBD, 0x02, 0x1C,
+	0xFE, 0x1C, 0x01, 0x80, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+	0x33, 0x00, 0x3F, 0xFF, 0xC5, 0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07,
+	0xF6, 0x22, 0x16, 0xC3, 0x43, 0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF,
+	0x11, 0x00, 0x2B, 0x00, 0xDE, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31,
+	0x00, 0x49, 0xFF, 0xCB, 0x01, 0x45, 0xFC, 0x29, 0x07, 0xB6, 0xF1,
+	0xAD, 0x2B, 0xA2, 0x34, 0x9E, 0xF1, 0xB4, 0x06, 0xB8, 0xFC, 0x7A,
+	0x01, 0x75, 0xFF, 0x22, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x02, 0x00,
+	0xCC, 0xFF, 0xCE, 0x00, 0xD1, 0xFD, 0x1D, 0x05, 0x91, 0xF3, 0xFE,
+	0x3D, 0xD7, 0x1F, 0x87, 0xF3, 0xC3, 0x06, 0x42, 0xFC, 0xE5, 0x01,
+	0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAB, 0xFF, 0xAF,
+	0x00, 0x01, 0xFF, 0x0A, 0x01, 0x9E, 0xFF, 0x7C, 0xFD, 0x03, 0x48,
+	0xED, 0x0A, 0xD5, 0xF9, 0x0A, 0x04, 0x74, 0xFD, 0x6A, 0x01, 0x62,
+	0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF,
+	0x90, 0x01, 0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E, 0xE1,
+	0x46, 0xE1, 0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F, 0x00,
+	0xBE, 0xFF, 0x10, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5,
+	0x01, 0x33, 0xFC, 0x04, 0x07, 0xB1, 0xF2, 0x21, 0x24, 0xE6, 0x3A,
+	0x97, 0xF2, 0xD0, 0x05, 0x5B, 0xFD, 0x15, 0x01, 0xA9, 0xFF, 0x0F,
+	0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x00, 0x90, 0xFF, 0x45, 0x01,
+	0x0B, 0xFD, 0x44, 0x06, 0x0A, 0xF2, 0x3B, 0x38, 0x80, 0x27, 0x2B,
+	0xF2, 0x22, 0x07, 0x33, 0xFC, 0xDE, 0x01, 0x3E, 0xFF, 0x34, 0x00,
+	0xFD, 0xFF, 0x0D, 0x00, 0xCD, 0xFF, 0x59, 0x00, 0xB3, 0xFF, 0xC4,
+	0xFF, 0xE2, 0x01, 0x09, 0xF9, 0xAA, 0x45, 0xFE, 0x11, 0x54, 0xF7,
+	0x38, 0x05, 0xE4, 0xFC, 0xAA, 0x01, 0x49, 0xFF, 0x30, 0x00, 0xFF,
+	0xFF, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49, 0x01, 0xBC, 0xFD,
+	0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48, 0xC3, 0xFF, 0x89,
+	0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C, 0xFF, 0x18, 0x00,
+	0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5B, 0xFC, 0x7B,
+	0x06, 0x50, 0xF4, 0x6E, 0x1C, 0x36, 0x40, 0x92, 0xF4, 0x75, 0x04,
+	0x3B, 0xFE, 0x91, 0x00, 0xEB, 0xFF, 0xF6, 0xFF, 0x04, 0x00, 0xFD,
+	0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9D, 0x01, 0x84, 0xFC, 0xF3, 0x06,
+	0x7D, 0xF1, 0x9E, 0x31, 0xE6, 0x2E, 0x85, 0xF1, 0x16, 0x07, 0x61,
+	0xFC, 0xB5, 0x01, 0x55, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x06, 0x00,
+	0xEC, 0xFF, 0x08, 0x00, 0x57, 0x00, 0x9F, 0xFE, 0xD1, 0x03, 0x9B,
+	0xF5, 0xF7, 0x41, 0x7C, 0x19, 0x13, 0xF5, 0x2F, 0x06, 0x78, 0xFC,
+	0xD5, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8F,
+	0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25, 0x02, 0x91, 0xFD, 0xE3, 0x01,
+	0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB, 0xF8, 0x02, 0xFE, 0xFD, 0x2B,
+	0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00,
+	0x42, 0xFF, 0xBD, 0x01, 0xB6, 0xFC, 0x9F, 0x05, 0x6C, 0xF6, 0xD6,
+	0x14, 0x65, 0x44, 0x98, 0xF7, 0xAC, 0x02, 0x4E, 0xFF, 0xF4, 0xFF,
+	0x39, 0x00, 0xD9, 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45,
+	0xFF, 0xD2, 0x01, 0x3D, 0xFC, 0x2B, 0x07, 0xD4, 0xF1, 0x64, 0x2A,
+	0xC6, 0x35, 0xB7, 0xF1, 0x96, 0x06, 0xCF, 0xFC, 0x6B, 0x01, 0x7D,
+	0xFF, 0x1F, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x06, 0x00, 0xC1, 0xFF,
+	0xE5, 0x00, 0xAA, 0xFD, 0x58, 0x05, 0x3A, 0xF3, 0x11, 0x3D, 0x2C,
+	0x21, 0x3F, 0xF3, 0xDA, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF,
+	0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20,
+	0xFF, 0xD0, 0x00, 0x07, 0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C,
+	0x63, 0xF9, 0x42, 0x04, 0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x85, 0x01,
+	0x39, 0xFD, 0x84, 0x04, 0xD9, 0xF8, 0x95, 0x0D, 0x48, 0x47, 0xA7,
+	0xFB, 0x86, 0x00, 0x8A, 0x00, 0x46, 0xFF, 0x8E, 0x00, 0xB8, 0xFF,
+	0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x35,
+	0xFC, 0xF3, 0x06, 0xEE, 0xF2, 0xCD, 0x22, 0xE4, 0x3B, 0xDC, 0xF2,
+	0x9C, 0x05, 0x7E, 0xFD, 0x00, 0x01, 0xB4, 0xFF, 0x0B, 0x00, 0x01,
+	0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x87, 0xFF, 0x57, 0x01, 0xEF, 0xFC,
+	0x6B, 0x06, 0xE0, 0xF1, 0x23, 0x37, 0xCE, 0x28, 0x01, 0xF2, 0x28,
+	0x07, 0x36, 0xFC, 0xD9, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF,
+	0x0B, 0x00, 0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F,
+	0x02, 0x5E, 0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05,
+	0xCF, 0xFC, 0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00,
+	0x00, 0x23, 0x00, 0x74, 0xFF, 0x3C, 0x01, 0xDA, 0xFD, 0x40, 0x03,
+	0x6E, 0xFB, 0xE1, 0x06, 0xC3, 0x48, 0xB3, 0x00, 0x1A, 0xFE, 0xDC,
+	0x01, 0x91, 0xFE, 0xE5, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0xFE, 0xFF,
+	0x36, 0x00, 0x38, 0xFF, 0xDB, 0x01, 0x67, 0xFC, 0x5A, 0x06, 0xA6,
+	0xF4, 0x1B, 0x1B, 0x07, 0x41, 0x04, 0xF5, 0x2D, 0x04, 0x67, 0xFE,
+	0x77, 0x00, 0xF8, 0xFF, 0xF2, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A,
+	0x00, 0x5C, 0xFF, 0xA8, 0x01, 0x73, 0xFC, 0x05, 0x07, 0x7D, 0xF1,
+	0x67, 0x30, 0x21, 0x30, 0x7E, 0xF1, 0x08, 0x07, 0x6F, 0xFC, 0xAB,
+	0x01, 0x5B, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF0, 0xFF,
+	0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F, 0xF5, 0x32,
+	0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC, 0xDA, 0x01,
+	0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE8,
+	0x00, 0x8A, 0xFE, 0xE9, 0x01, 0x01, 0xFE, 0xEA, 0x00, 0xCB, 0x48,
+	0xA2, 0x06, 0x87, 0xFB, 0x33, 0x03, 0xE0, 0xFD, 0x39, 0x01, 0x75,
+	0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x45, 0xFF,
+	0xB5, 0x01, 0xCA, 0xFC, 0x72, 0x05, 0xD3, 0xF6, 0x8D, 0x13, 0xFD,
+	0x44, 0x39, 0xF8, 0x53, 0x02, 0x82, 0xFF, 0xD7, 0xFF, 0x47, 0x00,
+	0xD3, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x42, 0xFF, 0xD8,
+	0x01, 0x37, 0xFC, 0x29, 0x07, 0xF8, 0xF1, 0x19, 0x29, 0xE5, 0x36,
+	0xD8, 0xF1, 0x73, 0x06, 0xE9, 0xFC, 0x5B, 0x01, 0x85, 0xFF, 0x1C,
+	0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A, 0x00, 0xB6, 0xFF, 0xFB, 0x00,
+	0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2, 0x1C, 0x3C, 0x81, 0x22, 0xFC,
+	0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00,
+	0xFD, 0xFF, 0x12, 0x00, 0xB7, 0xFF, 0x91, 0x00, 0x40, 0xFF, 0x96,
+	0x00, 0x6F, 0x00, 0xD5, 0xFB, 0x5E, 0x47, 0x50, 0x0D, 0xF2, 0xF8,
+	0x78, 0x04, 0x3F, 0xFD, 0x82, 0x01, 0x58, 0xFF, 0x2B, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x2A, 0x00, 0x5C, 0xFF, 0x79, 0x01, 0x53, 0xFD,
+	0x4E, 0x04, 0x4A, 0xF9, 0x60, 0x0C, 0xA3, 0x47, 0x76, 0xFC, 0x1F,
+	0x00, 0xC3, 0x00, 0x27, 0xFF, 0x9D, 0x00, 0xB2, 0xFF, 0x13, 0x00,
+	0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x3A, 0xFC, 0xDF,
+	0x06, 0x30, 0xF3, 0x78, 0x21, 0xDB, 0x3C, 0x28, 0xF3, 0x65, 0x05,
+	0xA2, 0xFD, 0xEA, 0x00, 0xBE, 0xFF, 0x07, 0x00, 0x01, 0x00, 0xFE,
+	0xFF, 0x1E, 0x00, 0x7F, 0xFF, 0x67, 0x01, 0xD5, 0xFC, 0x8E, 0x06,
+	0xBE, 0xF1, 0x06, 0x36, 0x1A, 0x2A, 0xDC, 0xF1, 0x2A, 0x07, 0x3C,
+	0xFC, 0xD3, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00,
+	0xD8, 0xFF, 0x3C, 0x00, 0xEE, 0xFF, 0x5A, 0xFF, 0x98, 0x02, 0xBB,
+	0xF7, 0x87, 0x44, 0x8C, 0x14, 0x83, 0xF6, 0x95, 0x05, 0xBA, 0xFC,
+	0xBB, 0x01, 0x43, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21,
+	0x00, 0x79, 0xFF, 0x2E, 0x01, 0xF7, 0xFD, 0x05, 0x03, 0xE1, 0xFB,
+	0xCA, 0x05, 0xDF, 0x48, 0xAB, 0x01, 0xAA, 0xFD, 0x18, 0x02, 0x72,
+	0xFE, 0xF4, 0x00, 0x90, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00,
+	0x39, 0xFF, 0xD6, 0x01, 0x75, 0xFC, 0x37, 0x06, 0xFF, 0xF4, 0xC7,
+	0x19, 0xCC, 0x41, 0x7F, 0xF5, 0xE2, 0x03, 0x95, 0xFE, 0x5D, 0x00,
+	0x05, 0x00, 0xED, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x57,
+	0xFF, 0xB3, 0x01, 0x64, 0xFC, 0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F,
+	0x5A, 0x31, 0x7D, 0xF1, 0xF7, 0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61,
+	0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF5, 0xFF, 0xEE, 0xFF,
+	0x8B, 0x00, 0x44, 0xFE, 0x65, 0x04, 0xAA, 0xF4, 0x66, 0x40, 0x23,
+	0x1C, 0x63, 0xF4, 0x74, 0x06, 0x5D, 0xFC, 0xDE, 0x01, 0x37, 0xFF,
+	0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, 0x9A, 0xFF, 0xD9, 0x00, 0xAA,
+	0xFE, 0xAE, 0x01, 0x70, 0xFE, 0xF8, 0xFF, 0xA6, 0x48, 0xBE, 0x07,
+	0x14, 0xFB, 0x6D, 0x03, 0xC3, 0xFD, 0x46, 0x01, 0x70, 0xFF, 0x24,
+	0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAC, 0x01,
+	0xDF, 0xFC, 0x43, 0x05, 0x3C, 0xF7, 0x46, 0x12, 0x8D, 0x45, 0xE2,
+	0xF8, 0xF7, 0x01, 0xB8, 0xFF, 0xB9, 0xFF, 0x56, 0x00, 0xCE, 0xFF,
+	0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDD, 0x01, 0x34,
+	0xFC, 0x23, 0x07, 0x21, 0xF2, 0xCB, 0x27, 0xFE, 0x37, 0x00, 0xF2,
+	0x4D, 0x06, 0x04, 0xFD, 0x49, 0x01, 0x8E, 0xFF, 0x19, 0x00, 0xFF,
+	0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAB, 0xFF, 0x10, 0x01, 0x62, 0xFD,
+	0xC5, 0x05, 0xA5, 0xF2, 0x1F, 0x3B, 0xD6, 0x23, 0xBE, 0xF2, 0x01,
+	0x07, 0x33, 0xFC, 0xE5, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF,
+	0x10, 0x00, 0xBD, 0xFF, 0x82, 0x00, 0x5E, 0xFF, 0x5D, 0x00, 0xD4,
+	0x00, 0x0C, 0xFB, 0xF9, 0x46, 0x87, 0x0E, 0x82, 0xF8, 0xAD, 0x04,
+	0x26, 0xFD, 0x8D, 0x01, 0x54, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0x00,
+	0x00, 0x29, 0x00, 0x60, 0xFF, 0x6D, 0x01, 0x6E, 0xFD, 0x17, 0x04,
+	0xBC, 0xF9, 0x30, 0x0B, 0xF4, 0x47, 0x4B, 0xFD, 0xB5, 0xFF, 0xFD,
+	0x00, 0x08, 0xFF, 0xAC, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFD, 0xFF,
+	0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76,
+	0xF3, 0x22, 0x20, 0xCA, 0x3D, 0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD,
+	0xD4, 0x00, 0xCA, 0xFF, 0x03, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x21,
+	0x00, 0x77, 0xFF, 0x77, 0x01, 0xBD, 0xFC, 0xAE, 0x06, 0xA3, 0xF1,
+	0xE3, 0x34, 0x64, 0x2B, 0xBC, 0xF1, 0x2A, 0x07, 0x43, 0xFC, 0xCD,
+	0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDD, 0xFF,
+	0x2E, 0x00, 0x0A, 0x00, 0x27, 0xFF, 0xEF, 0x02, 0x20, 0xF7, 0xE7,
+	0x43, 0xD8, 0x15, 0x1E, 0xF6, 0xC0, 0x05, 0xA7, 0xFC, 0xC3, 0x01,
+	0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F,
+	0xFF, 0x20, 0x01, 0x16, 0xFE, 0xCA, 0x02, 0x54, 0xFC, 0xB9, 0x04,
+	0xF2, 0x48, 0xA9, 0x02, 0x39, 0xFD, 0x53, 0x02, 0x53, 0xFE, 0x03,
+	0x01, 0x8A, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF,
+	0xD1, 0x01, 0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18, 0x89,
+	0x42, 0x02, 0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12, 0x00,
+	0xE8, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBD,
+	0x01, 0x57, 0xFC, 0x1E, 0x07, 0x90, 0xF1, 0xED, 0x2D, 0x8C, 0x32,
+	0x83, 0xF1, 0xE2, 0x06, 0x92, 0xFC, 0x93, 0x01, 0x68, 0xFF, 0x26,
+	0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFA, 0xFF, 0xE2, 0xFF, 0xA4, 0x00,
+	0x19, 0xFE, 0xAA, 0x04, 0x3E, 0xF4, 0x90, 0x3F, 0x78, 0x1D, 0x10,
+	0xF4, 0x93, 0x06, 0x52, 0xFC, 0xE1, 0x01, 0x36, 0xFF, 0x36, 0x00,
+	0xFE, 0xFF, 0x17, 0x00, 0xA0, 0xFF, 0xCA, 0x00, 0xC9, 0xFE, 0x73,
+	0x01, 0xDE, 0xFE, 0x0C, 0xFF, 0x76, 0x48, 0xDE, 0x08, 0xA1, 0xFA,
+	0xA6, 0x03, 0xA6, 0xFD, 0x53, 0x01, 0x6A, 0xFF, 0x26, 0x00, 0x00,
+	0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2, 0x01, 0xF6, 0xFC,
+	0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46, 0x93, 0xF9, 0x98,
+	0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8, 0xFF, 0x0E, 0x00,
+	0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x32, 0xFC, 0x1B,
+	0x07, 0x50, 0xF2, 0x7B, 0x26, 0x11, 0x39, 0x2F, 0xF2, 0x23, 0x06,
+	0x22, 0xFD, 0x37, 0x01, 0x97, 0xFF, 0x15, 0x00, 0xFF, 0xFF, 0x00,
+	0x00, 0x12, 0x00, 0xA1, 0xFF, 0x24, 0x01, 0x41, 0xFD, 0xF6, 0x05,
+	0x67, 0xF2, 0x1A, 0x3A, 0x29, 0x25, 0x84, 0xF2, 0x0F, 0x07, 0x31,
+	0xFC, 0xE3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0F, 0x00,
+	0xC2, 0xFF, 0x73, 0x00, 0x7D, 0xFF, 0x25, 0x00, 0x38, 0x01, 0x4C,
+	0xFA, 0x89, 0x46, 0xC3, 0x0F, 0x14, 0xF8, 0xE0, 0x04, 0x0D, 0xFD,
+	0x98, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27,
+	0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A, 0xFD, 0xDF, 0x03, 0x2E, 0xFA,
+	0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE, 0x4B, 0xFF, 0x38, 0x01, 0xE9,
+	0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00,
+	0x36, 0xFF, 0xE4, 0x01, 0x49, 0xFC, 0xAF, 0x06, 0xC1, 0xF3, 0xCD,
+	0x1E, 0xB1, 0x3E, 0xD9, 0xF3, 0xEC, 0x04, 0xF0, 0xFD, 0xBC, 0x00,
+	0xD5, 0xFF, 0xFE, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6F,
+	0xFF, 0x85, 0x01, 0xA6, 0xFC, 0xCA, 0x06, 0x8F, 0xF1, 0xBB, 0x33,
+	0xAB, 0x2C, 0xA3, 0xF1, 0x26, 0x07, 0x4C, 0xFC, 0xC5, 0x01, 0x4D,
+	0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE2, 0xFF, 0x20, 0x00,
+	0x26, 0x00, 0xF5, 0xFE, 0x43, 0x03, 0x8D, 0xF6, 0x3C, 0x43, 0x25,
+	0x17, 0xBB, 0xF5, 0xEA, 0x05, 0x95, 0xFC, 0xCA, 0x01, 0x3D, 0xFF,
+	0x34, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11,
+	0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48,
+	0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84,
+	0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01,
+	0x3D, 0xFC, 0xD6, 0x06, 0x4C, 0xF3, 0xED, 0x20, 0x3D, 0x3D, 0x4A,
+	0xF3, 0x4E, 0x05, 0xB1, 0xFD, 0xE1, 0x00, 0xC3, 0xFF, 0x05, 0x00,
+	0x02, 0x00, 0x02, 0x00, 0x05, 0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1,
+	0xFD, 0x4E, 0x05, 0x4A, 0xF3, 0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3,
+	0xD6, 0x06, 0x3D, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD,
+	0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE,
+	0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7,
+	0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00,
+	0xFD, 0xFF, 0x30, 0x00, 0x4D, 0xFF, 0xC5, 0x01, 0x4C, 0xFC, 0x26,
+	0x07, 0xA3, 0xF1, 0xAB, 0x2C, 0xBB, 0x33, 0x8F, 0xF1, 0xCA, 0x06,
+	0xA6, 0xFC, 0x85, 0x01, 0x6F, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x16,
+	0x00, 0xA6, 0xFF, 0xBB, 0x00, 0xE9, 0xFE, 0x38, 0x01, 0x4B, 0xFF,
+	0x28, 0xFE, 0x3A, 0x48, 0x04, 0x0A, 0x2E, 0xFA, 0xDF, 0x03, 0x8A,
+	0xFD, 0x60, 0x01, 0x65, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFD, 0xFF,
+	0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x0F, 0x07, 0x84,
+	0xF2, 0x29, 0x25, 0x1A, 0x3A, 0x67, 0xF2, 0xF6, 0x05, 0x41, 0xFD,
+	0x24, 0x01, 0xA1, 0xFF, 0x12, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xC8,
+	0xFF, 0x64, 0x00, 0x9B, 0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93, 0xF9,
+	0x10, 0x46, 0x03, 0x11, 0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC, 0xA2,
+	0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00,
+	0x36, 0xFF, 0xE1, 0x01, 0x52, 0xFC, 0x93, 0x06, 0x10, 0xF4, 0x78,
+	0x1D, 0x90, 0x3F, 0x3E, 0xF4, 0xAA, 0x04, 0x19, 0xFE, 0xA4, 0x00,
+	0xE2, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0x07, 0x00, 0xE8, 0xFF, 0x12,
+	0x00, 0x42, 0x00, 0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42,
+	0x76, 0x18, 0x5C, 0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B,
+	0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF,
+	0xC3, 0x01, 0xA7, 0xFC, 0xC0, 0x05, 0x1E, 0xF6, 0xD8, 0x15, 0xE7,
+	0x43, 0x20, 0xF7, 0xEF, 0x02, 0x27, 0xFF, 0x0A, 0x00, 0x2E, 0x00,
+	0xDD, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4,
+	0x00, 0xC8, 0xFD, 0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20,
+	0x76, 0xF3, 0xC8, 0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36,
+	0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x54, 0xFF, 0x8D, 0x01,
+	0x26, 0xFD, 0xAD, 0x04, 0x82, 0xF8, 0x87, 0x0E, 0xF9, 0x46, 0x0C,
+	0xFB, 0xD4, 0x00, 0x5D, 0x00, 0x5E, 0xFF, 0x82, 0x00, 0xBD, 0xFF,
+	0x10, 0x00, 0xFF, 0xFF, 0x19, 0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04,
+	0xFD, 0x4D, 0x06, 0x00, 0xF2, 0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2,
+	0x23, 0x07, 0x34, 0xFC, 0xDD, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD,
+	0xFF, 0x00, 0x00, 0x24, 0x00, 0x70, 0xFF, 0x46, 0x01, 0xC3, 0xFD,
+	0x6D, 0x03, 0x14, 0xFB, 0xBE, 0x07, 0xA6, 0x48, 0xF8, 0xFF, 0x70,
+	0xFE, 0xAE, 0x01, 0xAA, 0xFE, 0xD9, 0x00, 0x9A, 0xFF, 0x19, 0x00,
+	0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F, 0x01, 0x80, 0xFC, 0xF7,
+	0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F, 0x83, 0xF1, 0x13, 0x07,
+	0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x1B,
+	0x00, 0x90, 0xFF, 0xF4, 0x00, 0x72, 0xFE, 0x18, 0x02, 0xAA, 0xFD,
+	0xAB, 0x01, 0xDF, 0x48, 0xCA, 0x05, 0xE1, 0xFB, 0x05, 0x03, 0xF7,
+	0xFD, 0x2E, 0x01, 0x79, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFD, 0xFF,
+	0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A, 0x07, 0xDC,
+	0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06, 0xD5, 0xFC,
+	0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x13, 0x00, 0xB2,
+	0xFF, 0x9D, 0x00, 0x27, 0xFF, 0xC3, 0x00, 0x1F, 0x00, 0x76, 0xFC,
+	0xA3, 0x47, 0x60, 0x0C, 0x4A, 0xF9, 0x4E, 0x04, 0x53, 0xFD, 0x79,
+	0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00,
+	0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC, 0xF2, 0x81,
+	0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD, 0xFB, 0x00,
+	0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0x0B, 0x00, 0xD3, 0xFF, 0x47,
+	0x00, 0xD7, 0xFF, 0x82, 0xFF, 0x53, 0x02, 0x39, 0xF8, 0xFD, 0x44,
+	0x8D, 0x13, 0xD3, 0xF6, 0x72, 0x05, 0xCA, 0xFC, 0xB5, 0x01, 0x45,
+	0xFF, 0x31, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x38, 0xFF,
+	0xDA, 0x01, 0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4, 0xCE, 0x1A, 0x32,
+	0x41, 0x1F, 0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71, 0x00, 0xFB, 0xFF,
+	0xF0, 0xFF, 0x05, 0x00, 0x05, 0x00, 0xF2, 0xFF, 0xF8, 0xFF, 0x77,
+	0x00, 0x67, 0xFE, 0x2D, 0x04, 0x04, 0xF5, 0x07, 0x41, 0x1B, 0x1B,
+	0xA6, 0xF4, 0x5A, 0x06, 0x67, 0xFC, 0xDB, 0x01, 0x38, 0xFF, 0x36,
+	0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB3, 0x01,
+	0xCF, 0xFC, 0x67, 0x05, 0xEA, 0xF6, 0x44, 0x13, 0x1E, 0x45, 0x5E,
+	0xF8, 0x3F, 0x02, 0x8E, 0xFF, 0xD0, 0xFF, 0x4A, 0x00, 0xD2, 0xFF,
+	0x0B, 0x00, 0x01, 0x00, 0x0B, 0x00, 0xB4, 0xFF, 0x00, 0x01, 0x7E,
+	0xFD, 0x9C, 0x05, 0xDC, 0xF2, 0xE4, 0x3B, 0xCD, 0x22, 0xEE, 0xF2,
+	0xF3, 0x06, 0x35, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD,
+	0xFF, 0x00, 0x00, 0x2A, 0x00, 0x5D, 0xFF, 0x76, 0x01, 0x59, 0xFD,
+	0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C, 0xB6, 0x47, 0xA4, 0xFC, 0x07,
+	0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0, 0x00, 0xB1, 0xFF, 0x13, 0x00,
+	0xFE, 0xFF, 0x1F, 0x00, 0x7D, 0xFF, 0x6B, 0x01, 0xCF, 0xFC, 0x96,
+	0x06, 0xB7, 0xF1, 0xC6, 0x35, 0x64, 0x2A, 0xD4, 0xF1, 0x2B, 0x07,
+	0x3D, 0xFC, 0xD2, 0x01, 0x45, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00,
+	0x00, 0x21, 0x00, 0x7A, 0xFF, 0x2B, 0x01, 0xFE, 0xFD, 0xF8, 0x02,
+	0xFB, 0xFB, 0x8D, 0x05, 0xE5, 0x48, 0xE3, 0x01, 0x91, 0xFD, 0x25,
+	0x02, 0x6B, 0xFE, 0xF7, 0x00, 0x8F, 0xFF, 0x1C, 0x00, 0xFD, 0xFF,
+	0x2D, 0x00, 0x55, 0xFF, 0xB5, 0x01, 0x61, 0xFC, 0x16, 0x07, 0x85,
+	0xF1, 0xE6, 0x2E, 0x9E, 0x31, 0x7D, 0xF1, 0xF3, 0x06, 0x84, 0xFC,
+	0x9D, 0x01, 0x63, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x18, 0x00, 0x9C,
+	0xFF, 0xD6, 0x00, 0xB1, 0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3, 0xFF,
+	0x9C, 0x48, 0xFD, 0x07, 0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD, 0x49,
+	0x01, 0x6E, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00,
+	0x3E, 0xFF, 0xDE, 0x01, 0x33, 0xFC, 0x22, 0x07, 0x2B, 0xF2, 0x80,
+	0x27, 0x3B, 0x38, 0x0A, 0xF2, 0x44, 0x06, 0x0B, 0xFD, 0x45, 0x01,
+	0x90, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x10, 0x00, 0xBE, 0xFF, 0x7F,
+	0x00, 0x65, 0xFF, 0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46,
+	0xCD, 0x0E, 0x6A, 0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53,
+	0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF,
+	0xE5, 0x01, 0x42, 0xFC, 0xC3, 0x06, 0x87, 0xF3, 0xD7, 0x1F, 0xFE,
+	0x3D, 0x91, 0xF3, 0x1D, 0x05, 0xD1, 0xFD, 0xCE, 0x00, 0xCC, 0xFF,
+	0x02, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11,
+	0x00, 0x1B, 0xFF, 0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16,
+	0x07, 0xF6, 0xCA, 0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33,
+	0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCF, 0x01,
+	0x88, 0xFC, 0x09, 0x06, 0x71, 0xF5, 0x2B, 0x18, 0xB2, 0x42, 0x20,
+	0xF6, 0x83, 0x03, 0xCF, 0xFE, 0x3C, 0x00, 0x15, 0x00, 0xE6, 0xFF,
+	0x07, 0x00, 0x03, 0x00, 0xFB, 0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10,
+	0xFE, 0xB9, 0x04, 0x27, 0xF4, 0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3,
+	0x99, 0x06, 0x50, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE,
+	0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4D, 0xFF, 0xA0, 0x01, 0xFB, 0xFC,
+	0x07, 0x05, 0xBF, 0xF7, 0xBB, 0x10, 0x2B, 0x46, 0xBB, 0xF9, 0x83,
+	0x01, 0xFA, 0xFF, 0x95, 0xFF, 0x68, 0x00, 0xC7, 0xFF, 0x0E, 0x00,
+	0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28, 0x01, 0x3A, 0xFD, 0x00,
+	0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25, 0x79, 0xF2, 0x12, 0x07,
+	0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00,
+	0x00, 0x27, 0x00, 0x66, 0xFF, 0x5E, 0x01, 0x90, 0xFD, 0xD2, 0x03,
+	0x47, 0xFA, 0xC3, 0x09, 0x48, 0x48, 0x5A, 0xFE, 0x33, 0xFF, 0x45,
+	0x01, 0xE2, 0xFE, 0xBE, 0x00, 0xA5, 0xFF, 0x16, 0x00, 0xFD, 0xFF,
+	0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0, 0x06, 0x8C,
+	0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07, 0x4E, 0xFC,
+	0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x1E, 0x00, 0x86,
+	0xFF, 0x0E, 0x01, 0x3B, 0xFE, 0x82, 0x02, 0xE0, 0xFC, 0x73, 0x03,
+	0xF6, 0x48, 0xE9, 0x03, 0xAD, 0xFC, 0x9C, 0x02, 0x2D, 0xFE, 0x14,
+	0x01, 0x83, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x30, 0x00,
+	0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8, 0xF1, 0x62,
+	0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC, 0x82, 0x01,
+	0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x15, 0x00, 0xA8, 0xFF, 0xB8,
+	0x00, 0xF0, 0xFE, 0x2B, 0x01, 0x63, 0xFF, 0xF6, 0xFD, 0x2C, 0x48,
+	0x47, 0x0A, 0x14, 0xFA, 0xEB, 0x03, 0x84, 0xFD, 0x63, 0x01, 0x64,
+	0xFF, 0x27, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A, 0xFF,
+	0xE4, 0x01, 0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2, 0xDD, 0x24, 0x54,
+	0x3A, 0x74, 0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20, 0x01, 0xA3, 0xFF,
+	0x11, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xC9, 0xFF, 0x61, 0x00, 0xA2,
+	0xFF, 0xE2, 0xFF, 0xAE, 0x01, 0x6B, 0xF9, 0xF2, 0x45, 0x4A, 0x11,
+	0x8F, 0xF7, 0x1D, 0x05, 0xF1, 0xFC, 0xA4, 0x01, 0x4B, 0xFF, 0x2F,
+	0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE1, 0x01,
+	0x55, 0xFC, 0x8C, 0x06, 0x22, 0xF4, 0x2C, 0x1D, 0xC0, 0x3F, 0x55,
+	0xF4, 0x9B, 0x04, 0x23, 0xFE, 0x9F, 0x00, 0xE4, 0xFF, 0xF9, 0xFF,
+	0x04, 0x00, 0x07, 0x00, 0xE9, 0xFF, 0x0F, 0x00, 0x48, 0x00, 0xB9,
+	0xFE, 0xA6, 0x03, 0xE4, 0xF5, 0x60, 0x42, 0xC1, 0x18, 0x47, 0xF5,
+	0x1A, 0x06, 0x81, 0xFC, 0xD2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFE,
+	0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF, 0xC1, 0x01, 0xAB, 0xFC,
+	0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15, 0x0B, 0x44, 0x42, 0xF7, 0xDC,
+	0x02, 0x32, 0xFF, 0x04, 0x00, 0x31, 0x00, 0xDC, 0xFF, 0x09, 0x00,
+	0x02, 0x00, 0x04, 0x00, 0xC7, 0xFF, 0xD9, 0x00, 0xBF, 0xFD, 0x38,
+	0x05, 0x69, 0xF3, 0x96, 0x3D, 0x6F, 0x20, 0x66, 0xF3, 0xCE, 0x06,
+	0x3F, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF,
+	0xFF, 0x2C, 0x00, 0x55, 0xFF, 0x8B, 0x01, 0x2B, 0xFD, 0xA1, 0x04,
+	0x9B, 0xF8, 0x42, 0x0E, 0x0F, 0x47, 0x38, 0xFB, 0xBE, 0x00, 0x6A,
+	0x00, 0x58, 0xFF, 0x85, 0x00, 0xBB, 0xFF, 0x10, 0x00, 0xFF, 0xFF,
+	0x19, 0x00, 0x8C, 0xFF, 0x4D, 0x01, 0xFE, 0xFC, 0x56, 0x06, 0xF7,
+	0xF1, 0xBF, 0x37, 0x15, 0x28, 0x18, 0xF2, 0x25, 0x07, 0x34, 0xFC,
+	0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x24,
+	0x00, 0x71, 0xFF, 0x43, 0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E, 0xFB,
+	0x7E, 0x07, 0xAF, 0x48, 0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01, 0xA3,
+	0xFE, 0xDD, 0x00, 0x99, 0xFF, 0x19, 0x00, 0xFD, 0xFF, 0x29, 0x00,
+	0x60, 0xFF, 0xA2, 0x01, 0x7C, 0xFC, 0xFB, 0x06, 0x7C, 0xF1, 0x15,
+	0x31, 0x73, 0x2F, 0x81, 0xF1, 0x10, 0x07, 0x67, 0xFC, 0xB1, 0x01,
+	0x58, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x1B, 0x00, 0x91, 0xFF, 0xF1,
+	0x00, 0x79, 0xFE, 0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48,
+	0x07, 0x06, 0xC7, 0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78,
+	0xFF, 0x22, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF,
+	0xD5, 0x01, 0x3A, 0xFC, 0x2A, 0x07, 0xE3, 0xF1, 0xD1, 0x29, 0x46,
+	0x36, 0xC5, 0xF1, 0x87, 0x06, 0xDA, 0xFC, 0x64, 0x01, 0x80, 0xFF,
+	0x1E, 0x00, 0xFE, 0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E,
+	0xFF, 0xB6, 0x00, 0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C,
+	0x31, 0xF9, 0x5A, 0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A,
+	0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01,
+	0x37, 0xFC, 0xEB, 0x06, 0x0B, 0xF3, 0x35, 0x22, 0x52, 0x3C, 0xFD,
+	0xF2, 0x84, 0x05, 0x8D, 0xFD, 0xF6, 0x00, 0xB8, 0xFF, 0x09, 0x00,
+	0x01, 0x00, 0x0B, 0x00, 0xD5, 0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77,
+	0xFF, 0x67, 0x02, 0x14, 0xF8, 0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6,
+	0x7C, 0x05, 0xC5, 0xFC, 0xB7, 0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF,
+	0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD9, 0x01, 0x6D, 0xFC,
+	0x4B, 0x06, 0xCD, 0xF4, 0x83, 0x1A, 0x5F, 0x41, 0x3A, 0xF5, 0x0C,
+	0x04, 0x7B, 0xFE, 0x6C, 0x00, 0xFE, 0xFF, 0xEF, 0xFF, 0x05, 0x00,
+	0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D, 0x00, 0x5D, 0xFE, 0x3E,
+	0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B, 0x93, 0xF4, 0x62, 0x06,
+	0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF,
+	0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB1, 0x01, 0xD3, 0xFC, 0x5D, 0x05,
+	0x01, 0xF7, 0xFB, 0x12, 0x3F, 0x45, 0x83, 0xF8, 0x2A, 0x02, 0x9A,
+	0xFF, 0xCA, 0xFF, 0x4E, 0x00, 0xD1, 0xFF, 0x0C, 0x00, 0x00, 0x00,
+	0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8, 0x05, 0xCC,
+	0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06, 0x35, 0xFC,
+	0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x29,
+	0x00, 0x5E, 0xFF, 0x74, 0x01, 0x5F, 0xFD, 0x35, 0x04, 0x7C, 0xF9,
+	0xD8, 0x0B, 0xC9, 0x47, 0xD4, 0xFC, 0xF0, 0xFF, 0xDD, 0x00, 0x19,
+	0xFF, 0xA4, 0x00, 0xAF, 0xFF, 0x13, 0x00, 0xFE, 0xFF, 0x20, 0x00,
+	0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1, 0xF1, 0x86,
+	0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC, 0xD1, 0x01,
+	0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7C,
+	0xFF, 0x27, 0x01, 0x05, 0xFE, 0xEB, 0x02, 0x14, 0xFC, 0x50, 0x05,
+	0xEA, 0x48, 0x1B, 0x02, 0x78, 0xFD, 0x32, 0x02, 0x64, 0xFE, 0xFA,
+	0x00, 0x8D, 0xFF, 0x1C, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x54, 0xFF,
+	0xB7, 0x01, 0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1, 0x9F, 0x2E, 0xE3,
+	0x31, 0x7E, 0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A, 0x01, 0x64, 0xFF,
+	0x28, 0x00, 0xFD, 0xFF, 0x18, 0x00, 0x9D, 0xFF, 0xD3, 0x00, 0xB8,
+	0xFE, 0x93, 0x01, 0xA1, 0xFE, 0x8E, 0xFF, 0x92, 0x48, 0x3D, 0x08,
+	0xE1, 0xFA, 0x86, 0x03, 0xB6, 0xFD, 0x4C, 0x01, 0x6D, 0xFF, 0x25,
+	0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDF, 0x01,
+	0x33, 0xFC, 0x20, 0x07, 0x35, 0xF2, 0x36, 0x27, 0x78, 0x38, 0x14,
+	0xF2, 0x3B, 0x06, 0x11, 0xFD, 0x41, 0x01, 0x92, 0xFF, 0x17, 0x00,
+	0xFF, 0xFF, 0x10, 0x00, 0xBF, 0xFF, 0x7B, 0x00, 0x6C, 0xFF, 0x44,
+	0x00, 0x01, 0x01, 0xB6, 0xFA, 0xC8, 0x46, 0x13, 0x0F, 0x51, 0xF8,
+	0xC4, 0x04, 0x1B, 0xFD, 0x92, 0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF,
+	0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x44, 0xFC,
+	0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F, 0x31, 0x3E, 0xA5, 0xF3, 0x0F,
+	0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF, 0xFF, 0x01, 0x00, 0x02, 0x00,
+	0x09, 0x00, 0xDF, 0xFF, 0x28, 0x00, 0x17, 0x00, 0x10, 0xFF, 0x15,
+	0x03, 0xDD, 0xF6, 0x9E, 0x43, 0x6C, 0x16, 0xF1, 0xF5, 0xD3, 0x05,
+	0x9F, 0xFC, 0xC6, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE,
+	0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCE, 0x01, 0x8C, 0xFC, 0x00, 0x06,
+	0x86, 0xF5, 0xE0, 0x17, 0xDB, 0x42, 0x3F, 0xF6, 0x71, 0x03, 0xD9,
+	0xFE, 0x36, 0x00, 0x18, 0x00, 0xE5, 0xFF, 0x07, 0x00, 0x03, 0x00,
+	0xFC, 0xFF, 0xDC, 0xFF, 0xAF, 0x00, 0x07, 0xFE, 0xC8, 0x04, 0x10,
+	0xF4, 0x2D, 0x3F, 0x0F, 0x1E, 0xED, 0xF3, 0xA0, 0x06, 0x4E, 0xFC,
+	0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E,
+	0x00, 0x4E, 0xFF, 0x9E, 0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7, 0xF7,
+	0x75, 0x10, 0x48, 0x46, 0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00, 0x8E,
+	0xFF, 0x6B, 0x00, 0xC6, 0xFF, 0x0E, 0x00, 0xFF, 0xFF, 0x13, 0x00,
+	0x9D, 0xFF, 0x2D, 0x01, 0x33, 0xFD, 0x0B, 0x06, 0x4D, 0xF2, 0xA5,
+	0x39, 0xBF, 0x25, 0x6D, 0xF2, 0x15, 0x07, 0x31, 0xFC, 0xE2, 0x01,
+	0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x68,
+	0xFF, 0x5B, 0x01, 0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09,
+	0x57, 0x48, 0x8D, 0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2,
+	0x00, 0xA4, 0xFF, 0x16, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6C, 0xFF,
+	0x8B, 0x01, 0x9D, 0xFC, 0xD5, 0x06, 0x89, 0xF1, 0x35, 0x33, 0x3A,
+	0x2D, 0x9A, 0xF1, 0x23, 0x07, 0x51, 0xFC, 0xC2, 0x01, 0x4F, 0xFF,
+	0x2F, 0x00, 0xFD, 0xFF, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42,
+	0xFE, 0x74, 0x02, 0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04,
+	0x94, 0xFC, 0xA9, 0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F,
+	0x00, 0x00, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC9, 0x01,
+	0x48, 0xFC, 0x28, 0x07, 0xAD, 0xF1, 0x19, 0x2C, 0x3F, 0x34, 0x97,
+	0xF1, 0xBE, 0x06, 0xB0, 0xFC, 0x7F, 0x01, 0x72, 0xFF, 0x23, 0x00,
+	0xFE, 0xFF, 0x15, 0x00, 0xA9, 0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D,
+	0x01, 0x7A, 0xFF, 0xC5, 0xFD, 0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9,
+	0xF8, 0x03, 0x7D, 0xFD, 0x66, 0x01, 0x63, 0xFF, 0x28, 0x00, 0x00,
+	0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE4, 0x01, 0x32, 0xFC,
+	0x09, 0x07, 0x9D, 0xF2, 0x92, 0x24, 0x8F, 0x3A, 0x82, 0xF2, 0xE1,
+	0x05, 0x50, 0xFD, 0x1B, 0x01, 0xA6, 0xFF, 0x10, 0x00, 0x00, 0x00,
+	0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9, 0xFF, 0xD6, 0xFF, 0xC3,
+	0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11, 0x77, 0xF7, 0x28, 0x05,
+	0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE,
+	0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE0, 0x01, 0x57, 0xFC, 0x85, 0x06,
+	0x34, 0xF4, 0xE0, 0x1C, 0xF0, 0x3F, 0x6D, 0xF4, 0x8C, 0x04, 0x2C,
+	0xFE, 0x99, 0x00, 0xE7, 0xFF, 0xF8, 0xFF, 0x04, 0x00, 0x06, 0x00,
+	0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8, 0x03, 0xC7,
+	0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06, 0x7D, 0xFC,
+	0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32,
+	0x00, 0x41, 0xFF, 0xC0, 0x01, 0xAF, 0xFC, 0xAD, 0x05, 0x4A, 0xF6,
+	0x44, 0x15, 0x2F, 0x44, 0x64, 0xF7, 0xC9, 0x02, 0x3D, 0xFF, 0xFE,
+	0xFF, 0x34, 0x00, 0xDB, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x05, 0x00,
+	0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56, 0xF3, 0x61,
+	0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC, 0xE6, 0x01,
+	0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x56,
+	0xFF, 0x88, 0x01, 0x31, 0xFD, 0x95, 0x04, 0xB4, 0xF8, 0xFC, 0x0D,
+	0x26, 0x47, 0x64, 0xFB, 0xA7, 0x00, 0x77, 0x00, 0x51, 0xFF, 0x89,
+	0x00, 0xBA, 0xFF, 0x11, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8A, 0xFF,
+	0x51, 0x01, 0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1, 0x82, 0x37, 0x60,
+	0x28, 0x0E, 0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40, 0xFF,
+	0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x72, 0xFF, 0x40,
+	0x01, 0xD0, 0xFD, 0x53, 0x03, 0x47, 0xFB, 0x3F, 0x07, 0xB8, 0x48,
+	0x62, 0x00, 0x3F, 0xFE, 0xC8, 0x01, 0x9C, 0xFE, 0xE0, 0x00, 0x98,
+	0xFF, 0x19, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA5, 0x01,
+	0x78, 0xFC, 0xFF, 0x06, 0x7D, 0xF1, 0xCF, 0x30, 0xB8, 0x2F, 0x80,
+	0xF1, 0x0D, 0x07, 0x6A, 0xFC, 0xAE, 0x01, 0x59, 0xFF, 0x2B, 0x00,
+	0xFD, 0xFF, 0x1B, 0x00, 0x93, 0xFF, 0xED, 0x00, 0x80, 0xFE, 0xFD,
+	0x01, 0xDC, 0xFD, 0x3C, 0x01, 0xD5, 0x48, 0x45, 0x06, 0xAE, 0xFB,
+	0x1F, 0x03, 0xEA, 0xFD, 0x34, 0x01, 0x77, 0xFF, 0x22, 0x00, 0x00,
+	0x00, 0xFD, 0xFF, 0x33, 0x00, 0x43, 0xFF, 0xD6, 0x01, 0x39, 0xFC,
+	0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29, 0x85, 0x36, 0xCC, 0xF1, 0x7F,
+	0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82, 0xFF, 0x1D, 0x00, 0xFE, 0xFF,
+	0x12, 0x00, 0xB5, 0xFF, 0x96, 0x00, 0x35, 0xFF, 0xA9, 0x00, 0x4D,
+	0x00, 0x19, 0xFC, 0x7C, 0x47, 0xE8, 0x0C, 0x18, 0xF9, 0x66, 0x04,
+	0x48, 0xFD, 0x7E, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD,
+	0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x38, 0xFC, 0xE6, 0x06,
+	0x19, 0xF3, 0xEA, 0x21, 0x8A, 0x3C, 0x0E, 0xF3, 0x78, 0x05, 0x96,
+	0xFD, 0xF1, 0x00, 0xBB, 0xFF, 0x08, 0x00, 0x01, 0x00, 0x0B, 0x00,
+	0xD6, 0xFF, 0x41, 0x00, 0xE4, 0xFF, 0x6B, 0xFF, 0x7B, 0x02, 0xF0,
+	0xF7, 0xBA, 0x44, 0x1E, 0x14, 0xA5, 0xF6, 0x86, 0x05, 0xC1, 0xFC,
+	0xB9, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35,
+	0x00, 0x39, 0xFF, 0xD8, 0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1, 0xF4,
+	0x38, 0x1A, 0x8C, 0x41, 0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE, 0x66,
+	0x00, 0x01, 0x00, 0xEE, 0xFF, 0x06, 0x00, 0x05, 0x00, 0xF4, 0xFF,
+	0xF2, 0xFF, 0x83, 0x00, 0x53, 0xFE, 0x4E, 0x04, 0xD0, 0xF4, 0xAB,
+	0x40, 0xB2, 0x1B, 0x7F, 0xF4, 0x69, 0x06, 0x62, 0xFC, 0xDD, 0x01,
+	0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x47,
+	0xFF, 0xAF, 0x01, 0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12,
+	0x5C, 0x45, 0xA9, 0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51,
+	0x00, 0xD0, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xAF, 0xFF,
+	0x09, 0x01, 0x6E, 0xFD, 0xB4, 0x05, 0xBC, 0xF2, 0x73, 0x3B, 0x64,
+	0x23, 0xD2, 0xF2, 0xFB, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38, 0xFF,
+	0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71,
+	0x01, 0x65, 0xFD, 0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47,
+	0x03, 0xFD, 0xD9, 0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE,
+	0xFF, 0x14, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x79, 0xFF, 0x72, 0x01,
+	0xC4, 0xFC, 0xA4, 0x06, 0xAB, 0xF1, 0x46, 0x35, 0xF7, 0x2A, 0xC6,
+	0xF1, 0x2A, 0x07, 0x40, 0xFC, 0xCF, 0x01, 0x47, 0xFF, 0x31, 0x00,
+	0xFD, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C,
+	0xFE, 0xDE, 0x02, 0x2E, 0xFC, 0x13, 0x05, 0xEC, 0x48, 0x54, 0x02,
+	0x5E, 0xFD, 0x3F, 0x02, 0x5D, 0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C,
+	0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0xBA, 0x01, 0x5B, 0xFC,
+	0x1B, 0x07, 0x8B, 0xF1, 0x58, 0x2E, 0x26, 0x32, 0x80, 0xF1, 0xEA,
+	0x06, 0x8C, 0xFC, 0x97, 0x01, 0x66, 0xFF, 0x27, 0x00, 0xFD, 0xFF,
+	0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF, 0xFE, 0x86, 0x01, 0xBA,
+	0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08, 0xC7, 0xFA, 0x93, 0x03,
+	0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFD,
+	0xFF, 0x35, 0x00, 0x3D, 0xFF, 0xDF, 0x01, 0x32, 0xFC, 0x1E, 0x07,
+	0x40, 0xF2, 0xEB, 0x26, 0xB5, 0x38, 0x1F, 0xF2, 0x32, 0x06, 0x18,
+	0xFD, 0x3D, 0x01, 0x94, 0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x0F, 0x00,
+	0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17, 0x01, 0x8B,
+	0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04, 0x15, 0xFD,
+	0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36,
+	0x00, 0x36, 0xFF, 0xE5, 0x01, 0x46, 0xFC, 0xB8, 0x06, 0xA8, 0xF3,
+	0x3F, 0x1F, 0x64, 0x3E, 0xBA, 0xF3, 0x01, 0x05, 0xE2, 0xFD, 0xC4,
+	0x00, 0xD2, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x08, 0x00, 0xE1, 0xFF,
+	0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD, 0xF6, 0x77,
+	0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC, 0xC8, 0x01,
+	0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3D,
+	0xFF, 0xCC, 0x01, 0x8F, 0xFC, 0xF8, 0x05, 0x9B, 0xF5, 0x96, 0x17,
+	0x02, 0x43, 0x5E, 0xF6, 0x5F, 0x03, 0xE4, 0xFE, 0x30, 0x00, 0x1B,
+	0x00, 0xE4, 0xFF, 0x08, 0x00, 0x03, 0x00, 0xFD, 0xFF, 0xD9, 0xFF,
+	0xB4, 0x00, 0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3, 0xFC, 0x3E, 0x5B,
+	0x1E, 0xDB, 0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3, 0x01, 0x36, 0xFF,
+	0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9C,
+	0x01, 0x05, 0xFD, 0xF1, 0x04, 0xF0, 0xF7, 0x2D, 0x10, 0x61, 0x46,
+	0x0D, 0xFA, 0x58, 0x01, 0x13, 0x00, 0x87, 0xFF, 0x6E, 0x00, 0xC4,
+	0xFF, 0x0E, 0x00, 0xFF, 0xFF, 0x14, 0x00, 0x9B, 0xFF, 0x31, 0x01,
+	0x2C, 0xFD, 0x15, 0x06, 0x41, 0xF2, 0x6A, 0x39, 0x0A, 0x26, 0x61,
+	0xF2, 0x17, 0x07, 0x31, 0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00,
+	0xFD, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x69, 0xFF, 0x58, 0x01, 0x9D,
+	0xFD, 0xB9, 0x03, 0x7B, 0xFA, 0x40, 0x09, 0x63, 0x48, 0xBF, 0xFE,
+	0x03, 0xFF, 0x5F, 0x01, 0xD4, 0xFE, 0xC5, 0x00, 0xA2, 0xFF, 0x16,
+	0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6A, 0xFF, 0x8E, 0x01, 0x99, 0xFC,
+	0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32, 0x82, 0x2D, 0x96, 0xF1, 0x21,
+	0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFD, 0xFF,
+	0x1D, 0x00, 0x88, 0xFF, 0x07, 0x01, 0x49, 0xFE, 0x67, 0x02, 0x13,
+	0xFD, 0xFF, 0x02, 0xF4, 0x48, 0x5F, 0x04, 0x7A, 0xFC, 0xB6, 0x02,
+	0x20, 0xFE, 0x1B, 0x01, 0x81, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFD,
+	0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xCA, 0x01, 0x46, 0xFC, 0x29, 0x07,
+	0xB3, 0xF1, 0xD1, 0x2B, 0x81, 0x34, 0x9C, 0xF1, 0xB8, 0x06, 0xB5,
+	0xFC, 0x7C, 0x01, 0x74, 0xFF, 0x22, 0x00, 0xFE, 0xFF, 0x15, 0x00,
+	0xAA, 0xFF, 0xB1, 0x00, 0xFE, 0xFE, 0x10, 0x01, 0x92, 0xFF, 0x94,
+	0xFD, 0x0D, 0x48, 0xCB, 0x0A, 0xE2, 0xF9, 0x04, 0x04, 0x77, 0xFD,
+	0x69, 0x01, 0x62, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36,
+	0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA, 0xF2,
+	0x46, 0x24, 0xC8, 0x3A, 0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD, 0x17,
+	0x01, 0xA8, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xCC, 0xFF,
+	0x5A, 0x00, 0xAF, 0xFF, 0xCA, 0xFF, 0xD8, 0x01, 0x1C, 0xF9, 0xB8,
+	0x45, 0xDA, 0x11, 0x60, 0xF7, 0x33, 0x05, 0xE7, 0xFC, 0xA9, 0x01,
+	0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37,
+	0xFF, 0xDF, 0x01, 0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C,
+	0x1F, 0x40, 0x85, 0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA,
+	0xFF, 0xF7, 0xFF, 0x04, 0x00, 0x06, 0x00, 0xEB, 0xFF, 0x09, 0x00,
+	0x54, 0x00, 0xA4, 0xFE, 0xC9, 0x03, 0xAA, 0xF5, 0x0C, 0x42, 0x56,
+	0x19, 0x1E, 0xF5, 0x2B, 0x06, 0x7A, 0xFC, 0xD4, 0x01, 0x3A, 0xFF,
+	0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE,
+	0x01, 0xB4, 0xFC, 0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44,
+	0x86, 0xF7, 0xB6, 0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9,
+	0xFF, 0x0A, 0x00, 0x01, 0x00, 0x06, 0x00, 0xC2, 0xFF, 0xE3, 0x00,
+	0xAE, 0xFD, 0x52, 0x05, 0x44, 0xF3, 0x2A, 0x3D, 0x06, 0x21, 0x47,
+	0xF3, 0xD8, 0x06, 0x3C, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00,
+	0xFD, 0xFF, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x86, 0x01, 0x36,
+	0xFD, 0x89, 0x04, 0xCD, 0xF8, 0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB,
+	0x91, 0x00, 0x83, 0x00, 0x4A, 0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11,
+	0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x88, 0xFF, 0x55, 0x01, 0xF2, 0xFC,
+	0x67, 0x06, 0xE4, 0xF1, 0x44, 0x37, 0xAA, 0x28, 0x05, 0xF2, 0x27,
+	0x07, 0x36, 0xFC, 0xDA, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF,
+	0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D, 0x01, 0xD6, 0xFD, 0x46,
+	0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48, 0x98, 0x00, 0x26, 0xFE,
+	0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0xFD,
+	0xFF, 0x2A, 0x00, 0x5D, 0xFF, 0xA7, 0x01, 0x75, 0xFC, 0x03, 0x07,
+	0x7D, 0xF1, 0x8A, 0x30, 0xFF, 0x2F, 0x7E, 0xF1, 0x0A, 0x07, 0x6E,
+	0xFC, 0xAC, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x1A, 0x00,
+	0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5, 0xFD, 0x05,
+	0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03, 0xE4, 0xFD,
+	0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x33,
+	0x00, 0x42, 0xFF, 0xD7, 0x01, 0x38, 0xFC, 0x29, 0x07, 0xF3, 0xF1,
+	0x3E, 0x29, 0xC6, 0x36, 0xD4, 0xF1, 0x77, 0x06, 0xE6, 0xFC, 0x5C,
+	0x01, 0x84, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x12, 0x00, 0xB6, 0xFF,
+	0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB, 0xFB, 0x69,
+	0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD, 0x81, 0x01,
+	0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37,
+	0xFF, 0xE6, 0x01, 0x3A, 0xFC, 0xE2, 0x06, 0x28, 0xF3, 0x9E, 0x21,
+	0xC0, 0x3C, 0x1F, 0xF3, 0x6C, 0x05, 0x9E, 0xFD, 0xED, 0x00, 0xBD,
+	0xFF, 0x07, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xD7, 0xFF, 0x3E, 0x00,
+	0xEA, 0xFF, 0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7, 0x99, 0x44, 0x68,
+	0x14, 0x8E, 0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA, 0x01, 0x43, 0xFF,
+	0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7,
+	0x01, 0x73, 0xFC, 0x3B, 0x06, 0xF5, 0xF4, 0xED, 0x19, 0xB7, 0x41,
+	0x71, 0xF5, 0xEB, 0x03, 0x90, 0xFE, 0x60, 0x00, 0x04, 0x00, 0xED,
+	0xFF, 0x06, 0x00, 0x04, 0x00, 0xF5, 0xFF, 0xEF, 0xFF, 0x88, 0x00,
+	0x49, 0xFE, 0x5D, 0x04, 0xB7, 0xF4, 0x7D, 0x40, 0xFD, 0x1B, 0x6C,
+	0xF4, 0x70, 0x06, 0x5F, 0xFC, 0xDE, 0x01, 0x37, 0xFF, 0x36, 0x00,
+	0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAD, 0x01, 0xDD,
+	0xFC, 0x48, 0x05, 0x30, 0xF7, 0x6B, 0x12, 0x7D, 0x45, 0xCF, 0xF8,
+	0x01, 0x02, 0xB2, 0xFF, 0xBD, 0xFF, 0x54, 0x00, 0xCE, 0xFF, 0x0C,
+	0x00, 0x00, 0x00, 0x0E, 0x00, 0xAC, 0xFF, 0x0E, 0x01, 0x66, 0xFD,
+	0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B, 0xB0, 0x23, 0xC4, 0xF2, 0xFF,
+	0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF,
+	0x00, 0x00, 0x29, 0x00, 0x60, 0xFF, 0x6E, 0x01, 0x6B, 0xFD, 0x1D,
+	0x04, 0xAF, 0xF9, 0x51, 0x0B, 0xEC, 0x47, 0x33, 0xFD, 0xC1, 0xFF,
+	0xF7, 0x00, 0x0C, 0xFF, 0xAA, 0x00, 0xAD, 0xFF, 0x14, 0x00, 0xFE,
+	0xFF, 0x21, 0x00, 0x77, 0xFF, 0x75, 0x01, 0xBF, 0xFC, 0xAB, 0x06,
+	0xA6, 0xF1, 0x05, 0x35, 0x40, 0x2B, 0xBF, 0xF1, 0x2A, 0x07, 0x42,
+	0xFC, 0xCE, 0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x00, 0x00,
+	0x20, 0x00, 0x7E, 0xFF, 0x21, 0x01, 0x12, 0xFE, 0xD1, 0x02, 0x47,
+	0xFC, 0xD7, 0x04, 0xF0, 0x48, 0x8D, 0x02, 0x45, 0xFD, 0x4D, 0x02,
+	0x56, 0xFE, 0x01, 0x01, 0x8B, 0xFF, 0x1D, 0x00, 0xFD, 0xFF, 0x2E,
+	0x00, 0x52, 0xFF, 0xBC, 0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E, 0xF1,
+	0x11, 0x2E, 0x6B, 0x32, 0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC, 0x94,
+	0x01, 0x67, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x17, 0x00, 0xA0, 0xFF,
+	0xCC, 0x00, 0xC6, 0xFE, 0x79, 0x01, 0xD2, 0xFE, 0x26, 0xFF, 0x7C,
+	0x48, 0xBE, 0x08, 0xAE, 0xFA, 0xA0, 0x03, 0xA9, 0xFD, 0x52, 0x01,
+	0x6B, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C,
+	0xFF, 0xE0, 0x01, 0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26,
+	0xF2, 0x38, 0x2A, 0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96,
+	0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x0F, 0x00, 0xC2, 0xFF, 0x75, 0x00,
+	0x7A, 0xFF, 0x2B, 0x00, 0x2D, 0x01, 0x61, 0xFA, 0x97, 0x46, 0xA0,
+	0x0F, 0x20, 0xF8, 0xDA, 0x04, 0x10, 0xFD, 0x97, 0x01, 0x50, 0xFF,
+	0x2E, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4,
+	0x01, 0x48, 0xFC, 0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E,
+	0xCF, 0xF3, 0xF3, 0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF,
+	0xFF, 0x03, 0x00, 0x08, 0x00, 0xE2, 0xFF, 0x21, 0x00, 0x23, 0x00,
+	0xFA, 0xFE, 0x3A, 0x03, 0x9D, 0xF6, 0x50, 0x43, 0x00, 0x17, 0xC6,
+	0xF5, 0xE6, 0x05, 0x97, 0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x34, 0x00,
+	0xFE, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93,
+	0xFC, 0xEF, 0x05, 0xB0, 0xF5, 0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6,
+	0x4D, 0x03, 0xEF, 0xFE, 0x2A, 0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08,
+	0x00, 0x03, 0x00, 0xFE, 0xFF, 0xD7, 0xFF, 0xBA, 0x00, 0xF4, 0xFD,
+	0xE5, 0x04, 0xE4, 0xF3, 0xCA, 0x3E, 0xA7, 0x1E, 0xCA, 0xF3, 0xAC,
+	0x06, 0x4A, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF,
+	0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99, 0x01, 0x0B, 0xFD, 0xE6,
+	0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46, 0x37, 0xFA, 0x42, 0x01,
+	0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3, 0xFF, 0x0F, 0x00, 0xFF,
+	0xFF, 0x15, 0x00, 0x98, 0xFF, 0x35, 0x01, 0x25, 0xFD, 0x1E, 0x06,
+	0x35, 0xF2, 0x2E, 0x39, 0x55, 0x26, 0x56, 0xF2, 0x1A, 0x07, 0x31,
+	0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00,
+	0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD, 0x03, 0x94,
+	0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE, 0x6C, 0x01,
+	0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0xFD, 0xFF, 0x26,
+	0x00, 0x69, 0xFF, 0x91, 0x01, 0x94, 0xFC, 0xE0, 0x06, 0x84, 0xF1,
+	0xAF, 0x32, 0xCA, 0x2D, 0x92, 0xF1, 0x1F, 0x07, 0x56, 0xFC, 0xBE,
+	0x01, 0x51, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x1D, 0x00, 0x8A, 0xFF,
+	0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6, 0x02, 0xF2,
+	0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE, 0x1E, 0x01,
+	0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x49,
+	0xFF, 0xCC, 0x01, 0x44, 0xFC, 0x29, 0x07, 0xB9, 0xF1, 0x89, 0x2B,
+	0xC3, 0x34, 0xA0, 0xF1, 0xB1, 0x06, 0xBA, 0xFC, 0x79, 0x01, 0x76,
+	0xFF, 0x21, 0x00, 0xFE, 0xFF, 0x14, 0x00, 0xAC, 0xFF, 0xAE, 0x00,
+	0x05, 0xFF, 0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD, 0xFD, 0x47, 0x0E,
+	0x0B, 0xC8, 0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C, 0x01, 0x61, 0xFF,
+	0x28, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5,
+	0x01, 0x33, 0xFC, 0x03, 0x07, 0xB7, 0xF2, 0xFC, 0x23, 0x03, 0x3B,
+	0x9E, 0xF2, 0xCB, 0x05, 0x5F, 0xFD, 0x12, 0x01, 0xAA, 0xFF, 0x0E,
+	0x00, 0x00, 0x00, 0x0C, 0x00, 0xCD, 0xFF, 0x57, 0x00, 0xB6, 0xFF,
+	0xBE, 0xFF, 0xED, 0x01, 0xF5, 0xF8, 0x9B, 0x45, 0x22, 0x12, 0x48,
+	0xF7, 0x3D, 0x05, 0xE2, 0xFC, 0xAB, 0x01, 0x49, 0xFF, 0x30, 0x00,
+	0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5C,
+	0xFC, 0x78, 0x06, 0x5A, 0xF4, 0x49, 0x1C, 0x4E, 0x40, 0x9E, 0xF4,
+	0x6D, 0x04, 0x3F, 0xFE, 0x8E, 0x00, 0xED, 0xFF, 0xF6, 0xFF, 0x04,
+	0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0x5A, 0x00, 0x9A, 0xFE,
+	0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41, 0xA1, 0x19, 0x09, 0xF5, 0x33,
+	0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF,
+	0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBC, 0x01, 0xB8, 0xFC, 0x9A,
+	0x05, 0x77, 0xF6, 0xB1, 0x14, 0x77, 0x44, 0xA9, 0xF7, 0xA2, 0x02,
+	0x54, 0xFF, 0xF1, 0xFF, 0x3A, 0x00, 0xD8, 0xFF, 0x0A, 0x00, 0x01,
+	0x00, 0x07, 0x00, 0xC0, 0xFF, 0xE8, 0x00, 0xA6, 0xFD, 0x5F, 0x05,
+	0x31, 0xF3, 0xF6, 0x3C, 0x52, 0x21, 0x37, 0xF3, 0xDD, 0x06, 0x3B,
+	0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00,
+	0x2B, 0x00, 0x58, 0xFF, 0x83, 0x01, 0x3C, 0xFD, 0x7E, 0x04, 0xE6,
+	0xF8, 0x72, 0x0D, 0x52, 0x47, 0xBE, 0xFB, 0x7A, 0x00, 0x90, 0x00,
+	0x43, 0xFF, 0x8F, 0x00, 0xB7, 0xFF, 0x11, 0x00, 0xFE, 0xFF, 0x1C,
+	0x00, 0x86, 0xFF, 0x59, 0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC, 0xF1,
+	0x04, 0x37, 0xF3, 0x28, 0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC, 0xD8,
+	0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x23, 0x00,
+	0x74, 0xFF, 0x3A, 0x01, 0xDD, 0xFD, 0x39, 0x03, 0x7B, 0xFB, 0xC1,
+	0x06, 0xC7, 0x48, 0xCF, 0x00, 0x0D, 0xFE, 0xE3, 0x01, 0x8E, 0xFE,
+	0xE7, 0x00, 0x95, 0xFF, 0x1A, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5C,
+	0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30,
+	0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C,
+	0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE7, 0x00,
+	0x8E, 0xFE, 0xE3, 0x01, 0x0D, 0xFE, 0xCF, 0x00, 0xC7, 0x48, 0xC1,
+	0x06, 0x7B, 0xFB, 0x39, 0x03, 0xDD, 0xFD, 0x3A, 0x01, 0x74, 0xFF,
+	0x23, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8,
+	0x01, 0x37, 0xFC, 0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37,
+	0xDC, 0xF1, 0x6F, 0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C,
+	0x00, 0xFE, 0xFF, 0x11, 0x00, 0xB7, 0xFF, 0x8F, 0x00, 0x43, 0xFF,
+	0x90, 0x00, 0x7A, 0x00, 0xBE, 0xFB, 0x52, 0x47, 0x72, 0x0D, 0xE6,
+	0xF8, 0x7E, 0x04, 0x3C, 0xFD, 0x83, 0x01, 0x58, 0xFF, 0x2B, 0x00,
+	0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B,
+	0xFC, 0xDD, 0x06, 0x37, 0xF3, 0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3,
+	0x5F, 0x05, 0xA6, 0xFD, 0xE8, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01,
+	0x00, 0x0A, 0x00, 0xD8, 0xFF, 0x3A, 0x00, 0xF1, 0xFF, 0x54, 0xFF,
+	0xA2, 0x02, 0xA9, 0xF7, 0x77, 0x44, 0xB1, 0x14, 0x77, 0xF6, 0x9A,
+	0x05, 0xB8, 0xFC, 0xBC, 0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF,
+	0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6, 0x01, 0x77, 0xFC, 0x33,
+	0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41, 0x8D, 0xF5, 0xDA, 0x03,
+	0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0x04,
+	0x00, 0xF6, 0xFF, 0xED, 0xFF, 0x8E, 0x00, 0x3F, 0xFE, 0x6D, 0x04,
+	0x9E, 0xF4, 0x4E, 0x40, 0x49, 0x1C, 0x5A, 0xF4, 0x78, 0x06, 0x5C,
+	0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF,
+	0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D, 0x05, 0x48,
+	0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01, 0xBE, 0xFF,
+	0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0E,
+	0x00, 0xAA, 0xFF, 0x12, 0x01, 0x5F, 0xFD, 0xCB, 0x05, 0x9E, 0xF2,
+	0x03, 0x3B, 0xFC, 0x23, 0xB7, 0xF2, 0x03, 0x07, 0x33, 0xFC, 0xE5,
+	0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x28, 0x00,
+	0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8, 0xF9, 0x0E,
+	0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01, 0x05, 0xFF,
+	0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x76,
+	0xFF, 0x79, 0x01, 0xBA, 0xFC, 0xB1, 0x06, 0xA0, 0xF1, 0xC3, 0x34,
+	0x89, 0x2B, 0xB9, 0xF1, 0x29, 0x07, 0x44, 0xFC, 0xCC, 0x01, 0x49,
+	0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F, 0xFF,
+	0x1E, 0x01, 0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC, 0x9B, 0x04, 0xF2,
+	0x48, 0xC6, 0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50, 0xFE, 0x04, 0x01,
+	0x8A, 0xFF, 0x1D, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBE,
+	0x01, 0x56, 0xFC, 0x1F, 0x07, 0x92, 0xF1, 0xCA, 0x2D, 0xAF, 0x32,
+	0x84, 0xF1, 0xE0, 0x06, 0x94, 0xFC, 0x91, 0x01, 0x69, 0xFF, 0x26,
+	0x00, 0xFD, 0xFF, 0x17, 0x00, 0xA1, 0xFF, 0xC9, 0x00, 0xCD, 0xFE,
+	0x6C, 0x01, 0xEA, 0xFE, 0xF3, 0xFE, 0x70, 0x48, 0xFF, 0x08, 0x94,
+	0xFA, 0xAD, 0x03, 0xA3, 0xFD, 0x55, 0x01, 0x6A, 0xFF, 0x26, 0x00,
+	0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x31,
+	0xFC, 0x1A, 0x07, 0x56, 0xF2, 0x55, 0x26, 0x2E, 0x39, 0x35, 0xF2,
+	0x1E, 0x06, 0x25, 0xFD, 0x35, 0x01, 0x98, 0xFF, 0x15, 0x00, 0xFF,
+	0xFF, 0x0F, 0x00, 0xC3, 0xFF, 0x71, 0x00, 0x81, 0xFF, 0x1F, 0x00,
+	0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46, 0xE7, 0x0F, 0x08, 0xF8, 0xE6,
+	0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F, 0xFF, 0x2E, 0x00, 0xFF, 0xFF,
+	0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x4A, 0xFC, 0xAC,
+	0x06, 0xCA, 0xF3, 0xA7, 0x1E, 0xCA, 0x3E, 0xE4, 0xF3, 0xE5, 0x04,
+	0xF4, 0xFD, 0xBA, 0x00, 0xD7, 0xFF, 0xFE, 0xFF, 0x03, 0x00, 0x08,
+	0x00, 0xE3, 0xFF, 0x1E, 0x00, 0x2A, 0x00, 0xEF, 0xFE, 0x4D, 0x03,
+	0x7D, 0xF6, 0x2A, 0x43, 0x4B, 0x17, 0xB0, 0xF5, 0xEF, 0x05, 0x93,
+	0xFC, 0xCB, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFE, 0xFF,
+	0x34, 0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x97, 0xFC, 0xE6, 0x05, 0xC6,
+	0xF5, 0x00, 0x17, 0x50, 0x43, 0x9D, 0xF6, 0x3A, 0x03, 0xFA, 0xFE,
+	0x23, 0x00, 0x21, 0x00, 0xE2, 0xFF, 0x08, 0x00, 0x03, 0x00, 0xFF,
+	0xFF, 0xD4, 0xFF, 0xBF, 0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF, 0xF3,
+	0x98, 0x3E, 0xF3, 0x1E, 0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC, 0xE4,
+	0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E, 0x00,
+	0x50, 0xFF, 0x97, 0x01, 0x10, 0xFD, 0xDA, 0x04, 0x20, 0xF8, 0xA0,
+	0x0F, 0x97, 0x46, 0x61, 0xFA, 0x2D, 0x01, 0x2B, 0x00, 0x7A, 0xFF,
+	0x75, 0x00, 0xC2, 0xFF, 0x0F, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x96,
+	0xFF, 0x39, 0x01, 0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38,
+	0xA0, 0x26, 0x4B, 0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C,
+	0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6B, 0xFF,
+	0x52, 0x01, 0xA9, 0xFD, 0xA0, 0x03, 0xAE, 0xFA, 0xBE, 0x08, 0x7C,
+	0x48, 0x26, 0xFF, 0xD2, 0xFE, 0x79, 0x01, 0xC6, 0xFE, 0xCC, 0x00,
+	0xA0, 0xFF, 0x17, 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94,
+	0x01, 0x90, 0xFC, 0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E,
+	0x8E, 0xF1, 0x1D, 0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E,
+	0x00, 0xFD, 0xFF, 0x1D, 0x00, 0x8B, 0xFF, 0x01, 0x01, 0x56, 0xFE,
+	0x4D, 0x02, 0x45, 0xFD, 0x8D, 0x02, 0xF0, 0x48, 0xD7, 0x04, 0x47,
+	0xFC, 0xD1, 0x02, 0x12, 0xFE, 0x21, 0x01, 0x7E, 0xFF, 0x20, 0x00,
+	0x00, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42,
+	0xFC, 0x2A, 0x07, 0xBF, 0xF1, 0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1,
+	0xAB, 0x06, 0xBF, 0xFC, 0x75, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE,
+	0xFF, 0x14, 0x00, 0xAD, 0xFF, 0xAA, 0x00, 0x0C, 0xFF, 0xF7, 0x00,
+	0xC1, 0xFF, 0x33, 0xFD, 0xEC, 0x47, 0x51, 0x0B, 0xAF, 0xF9, 0x1D,
+	0x04, 0x6B, 0xFD, 0x6E, 0x01, 0x60, 0xFF, 0x29, 0x00, 0x00, 0x00,
+	0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0xFF,
+	0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B, 0xAD, 0xF2, 0xBF, 0x05,
+	0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x0C,
+	0x00, 0xCE, 0xFF, 0x54, 0x00, 0xBD, 0xFF, 0xB2, 0xFF, 0x01, 0x02,
+	0xCF, 0xF8, 0x7D, 0x45, 0x6B, 0x12, 0x30, 0xF7, 0x48, 0x05, 0xDD,
+	0xFC, 0xAD, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF,
+	0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70, 0x06, 0x6C,
+	0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04, 0x49, 0xFE,
+	0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0x06, 0x00, 0xED,
+	0xFF, 0x04, 0x00, 0x60, 0x00, 0x90, 0xFE, 0xEB, 0x03, 0x71, 0xF5,
+	0xB7, 0x41, 0xED, 0x19, 0xF5, 0xF4, 0x3B, 0x06, 0x73, 0xFC, 0xD7,
+	0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00,
+	0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E, 0xF6, 0x68,
+	0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF, 0xEA, 0xFF,
+	0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0x07, 0x00, 0xBD,
+	0xFF, 0xED, 0x00, 0x9E, 0xFD, 0x6C, 0x05, 0x1F, 0xF3, 0xC0, 0x3C,
+	0x9E, 0x21, 0x28, 0xF3, 0xE2, 0x06, 0x3A, 0xFC, 0xE6, 0x01, 0x37,
+	0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF,
+	0x81, 0x01, 0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8, 0x2D, 0x0D, 0x69,
+	0x47, 0xEB, 0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C, 0xFF, 0x93, 0x00,
+	0xB6, 0xFF, 0x12, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x84, 0xFF, 0x5C,
+	0x01, 0xE6, 0xFC, 0x77, 0x06, 0xD4, 0xF1, 0xC6, 0x36, 0x3E, 0x29,
+	0xF3, 0xF1, 0x29, 0x07, 0x38, 0xFC, 0xD7, 0x01, 0x42, 0xFF, 0x33,
+	0x00, 0xFD, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x76, 0xFF, 0x37, 0x01,
+	0xE4, 0xFD, 0x2C, 0x03, 0x94, 0xFB, 0x83, 0x06, 0xCE, 0x48, 0x05,
+	0x01, 0xF5, 0xFD, 0xF0, 0x01, 0x87, 0xFE, 0xEA, 0x00, 0x94, 0xFF,
+	0x1A, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5A, 0xFF, 0xAC, 0x01, 0x6E,
+	0xFC, 0x0A, 0x07, 0x7E, 0xF1, 0xFF, 0x2F, 0x8A, 0x30, 0x7D, 0xF1,
+	0x03, 0x07, 0x75, 0xFC, 0xA7, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0xFD,
+	0xFF, 0x1A, 0x00, 0x96, 0xFF, 0xE3, 0x00, 0x95, 0xFE, 0xD5, 0x01,
+	0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48, 0x00, 0x07, 0x61, 0xFB, 0x46,
+	0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73, 0xFF, 0x23, 0x00, 0x00, 0x00,
+	0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xDA, 0x01, 0x36, 0xFC, 0x27,
+	0x07, 0x05, 0xF2, 0xAA, 0x28, 0x44, 0x37, 0xE4, 0xF1, 0x67, 0x06,
+	0xF2, 0xFC, 0x55, 0x01, 0x88, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x11,
+	0x00, 0xB9, 0xFF, 0x8C, 0x00, 0x4A, 0xFF, 0x83, 0x00, 0x91, 0x00,
+	0x91, 0xFB, 0x3D, 0x47, 0xB7, 0x0D, 0xCD, 0xF8, 0x89, 0x04, 0x36,
+	0xFD, 0x86, 0x01, 0x57, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD, 0xFF,
+	0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3C, 0xFC, 0xD8, 0x06, 0x47,
+	0xF3, 0x06, 0x21, 0x2A, 0x3D, 0x44, 0xF3, 0x52, 0x05, 0xAE, 0xFD,
+	0xE3, 0x00, 0xC2, 0xFF, 0x06, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xD9,
+	0xFF, 0x37, 0x00, 0xF7, 0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86, 0xF7,
+	0x53, 0x44, 0xFB, 0x14, 0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC, 0xBE,
+	0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00,
+	0x3A, 0xFF, 0xD4, 0x01, 0x7A, 0xFC, 0x2B, 0x06, 0x1E, 0xF5, 0x56,
+	0x19, 0x0C, 0x42, 0xAA, 0xF5, 0xC9, 0x03, 0xA4, 0xFE, 0x54, 0x00,
+	0x09, 0x00, 0xEB, 0xFF, 0x06, 0x00, 0x04, 0x00, 0xF7, 0xFF, 0xEA,
+	0xFF, 0x93, 0x00, 0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40,
+	0x94, 0x1C, 0x47, 0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37,
+	0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF,
+	0xA9, 0x01, 0xE7, 0xFC, 0x33, 0x05, 0x60, 0xF7, 0xDA, 0x11, 0xB8,
+	0x45, 0x1C, 0xF9, 0xD8, 0x01, 0xCA, 0xFF, 0xAF, 0xFF, 0x5A, 0x00,
+	0xCC, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17,
+	0x01, 0x57, 0xFD, 0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24,
+	0xAA, 0xF2, 0x06, 0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36,
+	0x00, 0xFD, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x62, 0xFF, 0x69, 0x01,
+	0x77, 0xFD, 0x04, 0x04, 0xE2, 0xF9, 0xCB, 0x0A, 0x0D, 0x48, 0x94,
+	0xFD, 0x92, 0xFF, 0x10, 0x01, 0xFE, 0xFE, 0xB1, 0x00, 0xAA, 0xFF,
+	0x15, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5,
+	0xFC, 0xB8, 0x06, 0x9C, 0xF1, 0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1,
+	0x29, 0x07, 0x46, 0xFC, 0xCA, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD,
+	0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81, 0xFF, 0x1B, 0x01, 0x20, 0xFE,
+	0xB6, 0x02, 0x7A, 0xFC, 0x5F, 0x04, 0xF4, 0x48, 0xFF, 0x02, 0x13,
+	0xFD, 0x67, 0x02, 0x49, 0xFE, 0x07, 0x01, 0x88, 0xFF, 0x1D, 0x00,
+	0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0, 0x01, 0x53, 0xFC, 0x21,
+	0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32, 0x86, 0xF1, 0xDB, 0x06,
+	0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x16,
+	0x00, 0xA2, 0xFF, 0xC5, 0x00, 0xD4, 0xFE, 0x5F, 0x01, 0x03, 0xFF,
+	0xBF, 0xFE, 0x63, 0x48, 0x40, 0x09, 0x7B, 0xFA, 0xB9, 0x03, 0x9D,
+	0xFD, 0x58, 0x01, 0x69, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFD, 0xFF,
+	0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17, 0x07, 0x61,
+	0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06, 0x2C, 0xFD,
+	0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0x0E, 0x00, 0xC4,
+	0xFF, 0x6E, 0x00, 0x87, 0xFF, 0x13, 0x00, 0x58, 0x01, 0x0D, 0xFA,
+	0x61, 0x46, 0x2D, 0x10, 0xF0, 0xF7, 0xF1, 0x04, 0x05, 0xFD, 0x9C,
+	0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00,
+	0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB, 0xF3, 0x5B,
+	0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD, 0xB4, 0x00,
+	0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0x08, 0x00, 0xE4, 0xFF, 0x1B,
+	0x00, 0x30, 0x00, 0xE4, 0xFE, 0x5F, 0x03, 0x5E, 0xF6, 0x02, 0x43,
+	0x96, 0x17, 0x9B, 0xF5, 0xF8, 0x05, 0x8F, 0xFC, 0xCC, 0x01, 0x3D,
+	0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF,
+	0xC8, 0x01, 0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5, 0xB6, 0x16, 0x77,
+	0x43, 0xBD, 0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D, 0x00, 0x25, 0x00,
+	0xE1, 0xFF, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xC4,
+	0x00, 0xE2, 0xFD, 0x01, 0x05, 0xBA, 0xF3, 0x64, 0x3E, 0x3F, 0x1F,
+	0xA8, 0xF3, 0xB8, 0x06, 0x46, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36,
+	0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x95, 0x01,
+	0x15, 0xFD, 0xCF, 0x04, 0x39, 0xF8, 0x59, 0x0F, 0xAF, 0x46, 0x8B,
+	0xFA, 0x17, 0x01, 0x38, 0x00, 0x73, 0xFF, 0x78, 0x00, 0xC0, 0xFF,
+	0x0F, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x94, 0xFF, 0x3D, 0x01, 0x18,
+	0xFD, 0x32, 0x06, 0x1F, 0xF2, 0xB5, 0x38, 0xEB, 0x26, 0x40, 0xF2,
+	0x1E, 0x07, 0x32, 0xFC, 0xDF, 0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD,
+	0xFF, 0x00, 0x00, 0x25, 0x00, 0x6C, 0xFF, 0x4F, 0x01, 0xB0, 0xFD,
+	0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08, 0x86, 0x48, 0x5A, 0xFF, 0xBA,
+	0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF, 0x00, 0x9E, 0xFF, 0x17, 0x00,
+	0xFD, 0xFF, 0x27, 0x00, 0x66, 0xFF, 0x97, 0x01, 0x8C, 0xFC, 0xEA,
+	0x06, 0x80, 0xF1, 0x26, 0x32, 0x58, 0x2E, 0x8B, 0xF1, 0x1B, 0x07,
+	0x5B, 0xFC, 0xBA, 0x01, 0x53, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x1C,
+	0x00, 0x8C, 0xFF, 0xFE, 0x00, 0x5D, 0xFE, 0x3F, 0x02, 0x5E, 0xFD,
+	0x54, 0x02, 0xEC, 0x48, 0x13, 0x05, 0x2E, 0xFC, 0xDE, 0x02, 0x0C,
+	0xFE, 0x24, 0x01, 0x7D, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFD, 0xFF,
+	0x31, 0x00, 0x47, 0xFF, 0xCF, 0x01, 0x40, 0xFC, 0x2A, 0x07, 0xC6,
+	0xF1, 0xF7, 0x2A, 0x46, 0x35, 0xAB, 0xF1, 0xA4, 0x06, 0xC4, 0xFC,
+	0x72, 0x01, 0x79, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x14, 0x00, 0xAE,
+	0xFF, 0xA7, 0x00, 0x12, 0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03, 0xFD,
+	0xDC, 0x47, 0x95, 0x0B, 0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD, 0x71,
+	0x01, 0x5F, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00,
+	0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC, 0xFB, 0x06, 0xD2, 0xF2, 0x64,
+	0x23, 0x73, 0x3B, 0xBC, 0xF2, 0xB4, 0x05, 0x6E, 0xFD, 0x09, 0x01,
+	0xAF, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xD0, 0xFF, 0x51,
+	0x00, 0xC3, 0xFF, 0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45,
+	0xB2, 0x12, 0x19, 0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47,
+	0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF,
+	0xDD, 0x01, 0x62, 0xFC, 0x69, 0x06, 0x7F, 0xF4, 0xB2, 0x1B, 0xAB,
+	0x40, 0xD0, 0xF4, 0x4E, 0x04, 0x53, 0xFE, 0x83, 0x00, 0xF2, 0xFF,
+	0xF4, 0xFF, 0x05, 0x00, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66,
+	0x00, 0x85, 0xFE, 0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A,
+	0xE1, 0xF4, 0x43, 0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35,
+	0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xB9, 0x01,
+	0xC1, 0xFC, 0x86, 0x05, 0xA5, 0xF6, 0x1E, 0x14, 0xBA, 0x44, 0xF0,
+	0xF7, 0x7B, 0x02, 0x6B, 0xFF, 0xE4, 0xFF, 0x41, 0x00, 0xD6, 0xFF,
+	0x0B, 0x00, 0x01, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96,
+	0xFD, 0x78, 0x05, 0x0E, 0xF3, 0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3,
+	0xE6, 0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD,
+	0xFF, 0x00, 0x00, 0x2B, 0x00, 0x5A, 0xFF, 0x7E, 0x01, 0x48, 0xFD,
+	0x66, 0x04, 0x18, 0xF9, 0xE8, 0x0C, 0x7C, 0x47, 0x19, 0xFC, 0x4D,
+	0x00, 0xA9, 0x00, 0x35, 0xFF, 0x96, 0x00, 0xB5, 0xFF, 0x12, 0x00,
+	0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60, 0x01, 0xE0, 0xFC, 0x7F,
+	0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29, 0xEB, 0xF1, 0x2A, 0x07,
+	0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x00,
+	0x00, 0x22, 0x00, 0x77, 0xFF, 0x34, 0x01, 0xEA, 0xFD, 0x1F, 0x03,
+	0xAE, 0xFB, 0x45, 0x06, 0xD5, 0x48, 0x3C, 0x01, 0xDC, 0xFD, 0xFD,
+	0x01, 0x80, 0xFE, 0xED, 0x00, 0x93, 0xFF, 0x1B, 0x00, 0xFD, 0xFF,
+	0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D, 0x07, 0x80,
+	0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06, 0x78, 0xFC,
+	0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x19, 0x00, 0x98,
+	0xFF, 0xE0, 0x00, 0x9C, 0xFE, 0xC8, 0x01, 0x3F, 0xFE, 0x62, 0x00,
+	0xB8, 0x48, 0x3F, 0x07, 0x47, 0xFB, 0x53, 0x03, 0xD0, 0xFD, 0x40,
+	0x01, 0x72, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00,
+	0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E, 0xF2, 0x60,
+	0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC, 0x51, 0x01,
+	0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0x11, 0x00, 0xBA, 0xFF, 0x89,
+	0x00, 0x51, 0xFF, 0x77, 0x00, 0xA7, 0x00, 0x64, 0xFB, 0x26, 0x47,
+	0xFC, 0x0D, 0xB4, 0xF8, 0x95, 0x04, 0x31, 0xFD, 0x88, 0x01, 0x56,
+	0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF,
+	0xE6, 0x01, 0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3, 0xBA, 0x20, 0x61,
+	0x3D, 0x56, 0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE, 0x00, 0xC5, 0xFF,
+	0x05, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDB, 0xFF, 0x34, 0x00, 0xFE,
+	0xFF, 0x3D, 0xFF, 0xC9, 0x02, 0x64, 0xF7, 0x2F, 0x44, 0x44, 0x15,
+	0x4A, 0xF6, 0xAD, 0x05, 0xAF, 0xFC, 0xC0, 0x01, 0x41, 0xFF, 0x32,
+	0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD3, 0x01,
+	0x7D, 0xFC, 0x23, 0x06, 0x32, 0xF5, 0x0C, 0x19, 0x38, 0x42, 0xC7,
+	0xF5, 0xB8, 0x03, 0xAF, 0xFE, 0x4E, 0x00, 0x0C, 0x00, 0xEA, 0xFF,
+	0x06, 0x00, 0x04, 0x00, 0xF8, 0xFF, 0xE7, 0xFF, 0x99, 0x00, 0x2C,
+	0xFE, 0x8C, 0x04, 0x6D, 0xF4, 0xF0, 0x3F, 0xE0, 0x1C, 0x34, 0xF4,
+	0x85, 0x06, 0x57, 0xFC, 0xE0, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE,
+	0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4A, 0xFF, 0xA7, 0x01, 0xEC, 0xFC,
+	0x28, 0x05, 0x77, 0xF7, 0x92, 0x11, 0xD7, 0x45, 0x43, 0xF9, 0xC3,
+	0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E, 0x00, 0xCB, 0xFF, 0x0D, 0x00,
+	0x00, 0x00, 0x10, 0x00, 0xA6, 0xFF, 0x1B, 0x01, 0x50, 0xFD, 0xE1,
+	0x05, 0x82, 0xF2, 0x8F, 0x3A, 0x92, 0x24, 0x9D, 0xF2, 0x09, 0x07,
+	0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00,
+	0x00, 0x28, 0x00, 0x63, 0xFF, 0x66, 0x01, 0x7D, 0xFD, 0xF8, 0x03,
+	0xFB, 0xF9, 0x89, 0x0A, 0x1D, 0x48, 0xC5, 0xFD, 0x7A, 0xFF, 0x1D,
+	0x01, 0xF7, 0xFE, 0xB4, 0x00, 0xA9, 0xFF, 0x15, 0x00, 0xFE, 0xFF,
+	0x23, 0x00, 0x72, 0xFF, 0x7F, 0x01, 0xB0, 0xFC, 0xBE, 0x06, 0x97,
+	0xF1, 0x3F, 0x34, 0x19, 0x2C, 0xAD, 0xF1, 0x28, 0x07, 0x48, 0xFC,
+	0xC9, 0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x1F,
+	0x00, 0x82, 0xFF, 0x18, 0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94, 0xFC,
+	0x24, 0x04, 0xF5, 0x48, 0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02, 0x42,
+	0xFE, 0x0B, 0x01, 0x87, 0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x2F, 0x00,
+	0x4F, 0xFF, 0xC2, 0x01, 0x51, 0xFC, 0x23, 0x07, 0x9A, 0xF1, 0x3A,
+	0x2D, 0x35, 0x33, 0x89, 0xF1, 0xD5, 0x06, 0x9D, 0xFC, 0x8B, 0x01,
+	0x6C, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC2,
+	0x00, 0xDB, 0xFE, 0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48,
+	0x81, 0x09, 0x61, 0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68,
+	0xFF, 0x26, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF,
+	0xE2, 0x01, 0x31, 0xFC, 0x15, 0x07, 0x6D, 0xF2, 0xBF, 0x25, 0xA5,
+	0x39, 0x4D, 0xF2, 0x0B, 0x06, 0x33, 0xFD, 0x2D, 0x01, 0x9D, 0xFF,
+	0x13, 0x00, 0xFF, 0xFF, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E,
+	0xFF, 0x06, 0x00, 0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10,
+	0xD7, 0xF7, 0xFC, 0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E,
+	0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01,
+	0x4E, 0xFC, 0xA0, 0x06, 0xED, 0xF3, 0x0F, 0x1E, 0x2D, 0x3F, 0x10,
+	0xF4, 0xC8, 0x04, 0x07, 0xFE, 0xAF, 0x00, 0xDC, 0xFF, 0xFC, 0xFF,
+	0x03, 0x00, 0x07, 0x00, 0xE5, 0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9,
+	0xFE, 0x71, 0x03, 0x3F, 0xF6, 0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5,
+	0x00, 0x06, 0x8C, 0xFC, 0xCE, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE,
+	0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC6, 0x01, 0x9F, 0xFC,
+	0xD3, 0x05, 0xF1, 0xF5, 0x6C, 0x16, 0x9E, 0x43, 0xDD, 0xF6, 0x15,
+	0x03, 0x10, 0xFF, 0x17, 0x00, 0x28, 0x00, 0xDF, 0xFF, 0x09, 0x00,
+	0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9, 0x00, 0xDA, 0xFD, 0x0F,
+	0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F, 0x97, 0xF3, 0xBD, 0x06,
+	0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF,
+	0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x92, 0x01, 0x1B, 0xFD, 0xC4, 0x04,
+	0x51, 0xF8, 0x13, 0x0F, 0xC8, 0x46, 0xB6, 0xFA, 0x01, 0x01, 0x44,
+	0x00, 0x6C, 0xFF, 0x7B, 0x00, 0xBF, 0xFF, 0x10, 0x00, 0xFF, 0xFF,
+	0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B, 0x06, 0x14,
+	0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07, 0x33, 0xFC,
+	0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x25,
+	0x00, 0x6D, 0xFF, 0x4C, 0x01, 0xB6, 0xFD, 0x86, 0x03, 0xE1, 0xFA,
+	0x3D, 0x08, 0x92, 0x48, 0x8E, 0xFF, 0xA1, 0xFE, 0x93, 0x01, 0xB8,
+	0xFE, 0xD3, 0x00, 0x9D, 0xFF, 0x18, 0x00, 0xFD, 0xFF, 0x28, 0x00,
+	0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E, 0xF1, 0xE3,
+	0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC, 0xB7, 0x01,
+	0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x1C, 0x00, 0x8D, 0xFF, 0xFA,
+	0x00, 0x64, 0xFE, 0x32, 0x02, 0x78, 0xFD, 0x1B, 0x02, 0xEA, 0x48,
+	0x50, 0x05, 0x14, 0xFC, 0xEB, 0x02, 0x05, 0xFE, 0x27, 0x01, 0x7C,
+	0xFF, 0x21, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF,
+	0xD1, 0x01, 0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1, 0xAE, 0x2A, 0x86,
+	0x35, 0xB1, 0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E, 0x01, 0x7B, 0xFF,
+	0x20, 0x00, 0xFE, 0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA4, 0x00, 0x19,
+	0xFF, 0xDD, 0x00, 0xF0, 0xFF, 0xD4, 0xFC, 0xC9, 0x47, 0xD8, 0x0B,
+	0x7C, 0xF9, 0x35, 0x04, 0x5F, 0xFD, 0x74, 0x01, 0x5E, 0xFF, 0x29,
+	0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01,
+	0x35, 0xFC, 0xF7, 0x06, 0xE0, 0xF2, 0x18, 0x23, 0xAB, 0x3B, 0xCC,
+	0xF2, 0xA8, 0x05, 0x76, 0xFD, 0x04, 0x01, 0xB1, 0xFF, 0x0C, 0x00,
+	0x00, 0x00, 0x0C, 0x00, 0xD1, 0xFF, 0x4E, 0x00, 0xCA, 0xFF, 0x9A,
+	0xFF, 0x2A, 0x02, 0x83, 0xF8, 0x3F, 0x45, 0xFB, 0x12, 0x01, 0xF7,
+	0x5D, 0x05, 0xD3, 0xFC, 0xB1, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF,
+	0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDC, 0x01, 0x64, 0xFC,
+	0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B, 0xD9, 0x40, 0xEA, 0xF4, 0x3E,
+	0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5, 0xFF, 0xF3, 0xFF, 0x05, 0x00,
+	0x05, 0x00, 0xEF, 0xFF, 0xFE, 0xFF, 0x6C, 0x00, 0x7B, 0xFE, 0x0C,
+	0x04, 0x3A, 0xF5, 0x5F, 0x41, 0x83, 0x1A, 0xCD, 0xF4, 0x4B, 0x06,
+	0x6D, 0xFC, 0xD9, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF,
+	0xFF, 0x31, 0x00, 0x44, 0xFF, 0xB7, 0x01, 0xC5, 0xFC, 0x7C, 0x05,
+	0xBC, 0xF6, 0xD5, 0x13, 0xDC, 0x44, 0x14, 0xF8, 0x67, 0x02, 0x77,
+	0xFF, 0xDD, 0xFF, 0x44, 0x00, 0xD5, 0xFF, 0x0B, 0x00, 0x01, 0x00,
+	0x09, 0x00, 0xB8, 0xFF, 0xF6, 0x00, 0x8D, 0xFD, 0x84, 0x05, 0xFD,
+	0xF2, 0x52, 0x3C, 0x35, 0x22, 0x0B, 0xF3, 0xEB, 0x06, 0x37, 0xFC,
+	0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2A,
+	0x00, 0x5B, 0xFF, 0x7C, 0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31, 0xF9,
+	0xA4, 0x0C, 0x90, 0x47, 0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00, 0x2E,
+	0xFF, 0x99, 0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFE, 0xFF, 0x1E, 0x00,
+	0x80, 0xFF, 0x64, 0x01, 0xDA, 0xFC, 0x87, 0x06, 0xC5, 0xF1, 0x46,
+	0x36, 0xD1, 0x29, 0xE3, 0xF1, 0x2A, 0x07, 0x3A, 0xFC, 0xD5, 0x01,
+	0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x78,
+	0xFF, 0x31, 0x01, 0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06,
+	0xDB, 0x48, 0x73, 0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1,
+	0x00, 0x91, 0xFF, 0x1B, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x58, 0xFF,
+	0xB1, 0x01, 0x67, 0xFC, 0x10, 0x07, 0x81, 0xF1, 0x73, 0x2F, 0x15,
+	0x31, 0x7C, 0xF1, 0xFB, 0x06, 0x7C, 0xFC, 0xA2, 0x01, 0x60, 0xFF,
+	0x29, 0x00, 0xFD, 0xFF, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3,
+	0xFE, 0xBB, 0x01, 0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07,
+	0x2E, 0xFB, 0x60, 0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24,
+	0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDC, 0x01,
+	0x34, 0xFC, 0x25, 0x07, 0x18, 0xF2, 0x15, 0x28, 0xBF, 0x37, 0xF7,
+	0xF1, 0x56, 0x06, 0xFE, 0xFC, 0x4D, 0x01, 0x8C, 0xFF, 0x19, 0x00,
+	0xFF, 0xFF, 0x10, 0x00, 0xBB, 0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A,
+	0x00, 0xBE, 0x00, 0x38, 0xFB, 0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8,
+	0xA1, 0x04, 0x2B, 0xFD, 0x8B, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF,
+	0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3F, 0xFC,
+	0xCE, 0x06, 0x66, 0xF3, 0x6F, 0x20, 0x96, 0x3D, 0x69, 0xF3, 0x38,
+	0x05, 0xBF, 0xFD, 0xD9, 0x00, 0xC7, 0xFF, 0x04, 0x00, 0x02, 0x00,
+	0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04, 0x00, 0x32, 0xFF, 0xDC,
+	0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15, 0x34, 0xF6, 0xB7, 0x05,
+	0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE,
+	0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xD2, 0x01, 0x81, 0xFC, 0x1A, 0x06,
+	0x47, 0xF5, 0xC1, 0x18, 0x60, 0x42, 0xE4, 0xF5, 0xA6, 0x03, 0xB9,
+	0xFE, 0x48, 0x00, 0x0F, 0x00, 0xE9, 0xFF, 0x07, 0x00, 0x04, 0x00,
+	0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B, 0x04, 0x55,
+	0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06, 0x55, 0xFC,
+	0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2F,
+	0x00, 0x4B, 0xFF, 0xA4, 0x01, 0xF1, 0xFC, 0x1D, 0x05, 0x8F, 0xF7,
+	0x4A, 0x11, 0xF2, 0x45, 0x6B, 0xF9, 0xAE, 0x01, 0xE2, 0xFF, 0xA2,
+	0xFF, 0x61, 0x00, 0xC9, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x11, 0x00,
+	0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74, 0xF2, 0x54,
+	0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC, 0xE4, 0x01,
+	0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x64,
+	0xFF, 0x63, 0x01, 0x84, 0xFD, 0xEB, 0x03, 0x14, 0xFA, 0x47, 0x0A,
+	0x2C, 0x48, 0xF6, 0xFD, 0x63, 0xFF, 0x2B, 0x01, 0xF0, 0xFE, 0xB8,
+	0x00, 0xA8, 0xFF, 0x15, 0x00, 0xFE, 0xFF, 0x23, 0x00, 0x71, 0xFF,
+	0x82, 0x01, 0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1, 0xFD, 0x33, 0x62,
+	0x2C, 0xA8, 0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7, 0x01, 0x4C, 0xFF,
+	0x30, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83, 0xFF, 0x14,
+	0x01, 0x2D, 0xFE, 0x9C, 0x02, 0xAD, 0xFC, 0xE9, 0x03, 0xF6, 0x48,
+	0x73, 0x03, 0xE0, 0xFC, 0x82, 0x02, 0x3B, 0xFE, 0x0E, 0x01, 0x86,
+	0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3, 0x01,
+	0x4E, 0xFC, 0x24, 0x07, 0x9E, 0xF1, 0xF2, 0x2C, 0x78, 0x33, 0x8C,
+	0xF1, 0xD0, 0x06, 0xA2, 0xFC, 0x88, 0x01, 0x6D, 0xFF, 0x24, 0x00,
+	0xFD, 0xFF, 0x16, 0x00, 0xA5, 0xFF, 0xBE, 0x00, 0xE2, 0xFE, 0x45,
+	0x01, 0x33, 0xFF, 0x5A, 0xFE, 0x48, 0x48, 0xC3, 0x09, 0x47, 0xFA,
+	0xD2, 0x03, 0x90, 0xFD, 0x5E, 0x01, 0x66, 0xFF, 0x27, 0x00, 0x00,
+	0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE3, 0x01, 0x31, 0xFC,
+	0x12, 0x07, 0x79, 0xF2, 0x73, 0x25, 0xDF, 0x39, 0x5A, 0xF2, 0x00,
+	0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F, 0xFF, 0x13, 0x00, 0x00, 0x00,
+	0x0E, 0x00, 0xC7, 0xFF, 0x68, 0x00, 0x95, 0xFF, 0xFA, 0xFF, 0x83,
+	0x01, 0xBB, 0xF9, 0x2B, 0x46, 0xBB, 0x10, 0xBF, 0xF7, 0x07, 0x05,
+	0xFB, 0xFC, 0xA0, 0x01, 0x4D, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE,
+	0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x50, 0xFC, 0x99, 0x06,
+	0xFE, 0xF3, 0xC3, 0x1D, 0x5E, 0x3F, 0x27, 0xF4, 0xB9, 0x04, 0x10,
+	0xFE, 0xA9, 0x00, 0xDF, 0xFF, 0xFB, 0xFF, 0x03, 0x00, 0x07, 0x00,
+	0xE6, 0xFF, 0x15, 0x00, 0x3C, 0x00, 0xCF, 0xFE, 0x83, 0x03, 0x20,
+	0xF6, 0xB2, 0x42, 0x2B, 0x18, 0x71, 0xF5, 0x09, 0x06, 0x88, 0xFC,
+	0xCF, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33,
+	0x00, 0x3F, 0xFF, 0xC5, 0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07, 0xF6,
+	0x22, 0x16, 0xC3, 0x43, 0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF, 0x11,
+	0x00, 0x2B, 0x00, 0xDE, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x02, 0x00,
+	0xCC, 0xFF, 0xCE, 0x00, 0xD1, 0xFD, 0x1D, 0x05, 0x91, 0xF3, 0xFE,
+	0x3D, 0xD7, 0x1F, 0x87, 0xF3, 0xC3, 0x06, 0x42, 0xFC, 0xE5, 0x01,
+	0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2D, 0x00, 0x53,
+	0xFF, 0x90, 0x01, 0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E,
+	0xE1, 0x46, 0xE1, 0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F,
+	0x00, 0xBE, 0xFF, 0x10, 0x00, 0xFF, 0xFF, 0x18, 0x00, 0x90, 0xFF,
+	0x45, 0x01, 0x0B, 0xFD, 0x44, 0x06, 0x0A, 0xF2, 0x3B, 0x38, 0x80,
+	0x27, 0x2B, 0xF2, 0x22, 0x07, 0x33, 0xFC, 0xDE, 0x01, 0x3E, 0xFF,
+	0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49,
+	0x01, 0xBC, 0xFD, 0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48,
+	0xC3, 0xFF, 0x89, 0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C,
+	0xFF, 0x18, 0x00, 0xFD, 0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9D, 0x01,
+	0x84, 0xFC, 0xF3, 0x06, 0x7D, 0xF1, 0x9E, 0x31, 0xE6, 0x2E, 0x85,
+	0xF1, 0x16, 0x07, 0x61, 0xFC, 0xB5, 0x01, 0x55, 0xFF, 0x2D, 0x00,
+	0xFD, 0xFF, 0x1C, 0x00, 0x8F, 0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25,
+	0x02, 0x91, 0xFD, 0xE3, 0x01, 0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB,
+	0xF8, 0x02, 0xFE, 0xFD, 0x2B, 0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00,
+	0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45, 0xFF, 0xD2, 0x01, 0x3D, 0xFC,
+	0x2B, 0x07, 0xD4, 0xF1, 0x64, 0x2A, 0xC6, 0x35, 0xB7, 0xF1, 0x96,
+	0x06, 0xCF, 0xFC, 0x6B, 0x01, 0x7D, 0xFF, 0x1F, 0x00, 0xFE, 0xFF,
+	0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20, 0xFF, 0xD0, 0x00, 0x07,
+	0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C, 0x63, 0xF9, 0x42, 0x04,
+	0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0xFD,
+	0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF3, 0x06,
+	0xEE, 0xF2, 0xCD, 0x22, 0xE4, 0x3B, 0xDC, 0xF2, 0x9C, 0x05, 0x7E,
+	0xFD, 0x00, 0x01, 0xB4, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0x0B, 0x00,
+	0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F, 0x02, 0x5E,
+	0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05, 0xCF, 0xFC,
+	0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36,
+	0x00, 0x38, 0xFF, 0xDB, 0x01, 0x67, 0xFC, 0x5A, 0x06, 0xA6, 0xF4,
+	0x1B, 0x1B, 0x07, 0x41, 0x04, 0xF5, 0x2D, 0x04, 0x67, 0xFE, 0x77,
+	0x00, 0xF8, 0xFF, 0xF2, 0xFF, 0x05, 0x00, 0x05, 0x00, 0xF0, 0xFF,
+	0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F, 0xF5, 0x32,
+	0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC, 0xDA, 0x01,
+	0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x31, 0x00, 0x45,
+	0xFF, 0xB5, 0x01, 0xCA, 0xFC, 0x72, 0x05, 0xD3, 0xF6, 0x8D, 0x13,
+	0xFD, 0x44, 0x39, 0xF8, 0x53, 0x02, 0x82, 0xFF, 0xD7, 0xFF, 0x47,
+	0x00, 0xD3, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xB6, 0xFF,
+	0xFB, 0x00, 0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2, 0x1C, 0x3C, 0x81,
+	0x22, 0xFC, 0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF,
+	0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2A, 0x00, 0x5C, 0xFF, 0x79,
+	0x01, 0x53, 0xFD, 0x4E, 0x04, 0x4A, 0xF9, 0x60, 0x0C, 0xA3, 0x47,
+	0x76, 0xFC, 0x1F, 0x00, 0xC3, 0x00, 0x27, 0xFF, 0x9D, 0x00, 0xB2,
+	0xFF, 0x13, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x7F, 0xFF, 0x67, 0x01,
+	0xD5, 0xFC, 0x8E, 0x06, 0xBE, 0xF1, 0x06, 0x36, 0x1A, 0x2A, 0xDC,
+	0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01, 0x44, 0xFF, 0x32, 0x00,
+	0xFD, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x79, 0xFF, 0x2E, 0x01, 0xF7,
+	0xFD, 0x05, 0x03, 0xE1, 0xFB, 0xCA, 0x05, 0xDF, 0x48, 0xAB, 0x01,
+	0xAA, 0xFD, 0x18, 0x02, 0x72, 0xFE, 0xF4, 0x00, 0x90, 0xFF, 0x1B,
+	0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x57, 0xFF, 0xB3, 0x01, 0x64, 0xFC,
+	0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F, 0x5A, 0x31, 0x7D, 0xF1, 0xF7,
+	0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61, 0xFF, 0x29, 0x00, 0xFD, 0xFF,
+	0x19, 0x00, 0x9A, 0xFF, 0xD9, 0x00, 0xAA, 0xFE, 0xAE, 0x01, 0x70,
+	0xFE, 0xF8, 0xFF, 0xA6, 0x48, 0xBE, 0x07, 0x14, 0xFB, 0x6D, 0x03,
+	0xC3, 0xFD, 0x46, 0x01, 0x70, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFD,
+	0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDD, 0x01, 0x34, 0xFC, 0x23, 0x07,
+	0x21, 0xF2, 0xCB, 0x27, 0xFE, 0x37, 0x00, 0xF2, 0x4D, 0x06, 0x04,
+	0xFD, 0x49, 0x01, 0x8E, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x10, 0x00,
+	0xBD, 0xFF, 0x82, 0x00, 0x5E, 0xFF, 0x5D, 0x00, 0xD4, 0x00, 0x0C,
+	0xFB, 0xF9, 0x46, 0x87, 0x0E, 0x82, 0xF8, 0xAD, 0x04, 0x26, 0xFD,
+	0x8D, 0x01, 0x54, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36,
+	0x00, 0x36, 0xFF, 0xE6, 0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76, 0xF3,
+	0x22, 0x20, 0xCA, 0x3D, 0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD, 0xD4,
+	0x00, 0xCA, 0xFF, 0x03, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDD, 0xFF,
+	0x2E, 0x00, 0x0A, 0x00, 0x27, 0xFF, 0xEF, 0x02, 0x20, 0xF7, 0xE7,
+	0x43, 0xD8, 0x15, 0x1E, 0xF6, 0xC0, 0x05, 0xA7, 0xFC, 0xC3, 0x01,
+	0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3B,
+	0xFF, 0xD1, 0x01, 0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18,
+	0x89, 0x42, 0x02, 0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12,
+	0x00, 0xE8, 0xFF, 0x07, 0x00, 0x03, 0x00, 0xFA, 0xFF, 0xE2, 0xFF,
+	0xA4, 0x00, 0x19, 0xFE, 0xAA, 0x04, 0x3E, 0xF4, 0x90, 0x3F, 0x78,
+	0x1D, 0x10, 0xF4, 0x93, 0x06, 0x52, 0xFC, 0xE1, 0x01, 0x36, 0xFF,
+	0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2,
+	0x01, 0xF6, 0xFC, 0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46,
+	0x93, 0xF9, 0x98, 0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8,
+	0xFF, 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0xA1, 0xFF, 0x24, 0x01,
+	0x41, 0xFD, 0xF6, 0x05, 0x67, 0xF2, 0x1A, 0x3A, 0x29, 0x25, 0x84,
+	0xF2, 0x0F, 0x07, 0x31, 0xFC, 0xE3, 0x01, 0x3A, 0xFF, 0x35, 0x00,
+	0xFD, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A,
+	0xFD, 0xDF, 0x03, 0x2E, 0xFA, 0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE,
+	0x4B, 0xFF, 0x38, 0x01, 0xE9, 0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16,
+	0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6F, 0xFF, 0x85, 0x01, 0xA6, 0xFC,
+	0xCA, 0x06, 0x8F, 0xF1, 0xBB, 0x33, 0xAB, 0x2C, 0xA3, 0xF1, 0x26,
+	0x07, 0x4C, 0xFC, 0xC5, 0x01, 0x4D, 0xFF, 0x30, 0x00, 0xFD, 0xFF,
+	0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F,
+	0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC,
+	0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0xFD,
+	0xFF, 0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07,
+	0x7E, 0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71,
+	0xFC, 0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x00, 0x00,
+	0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7,
+	0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02,
+	0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x02, 0x00, 0x05,
+	0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1, 0xFD, 0x4E, 0x05, 0x4A, 0xF3,
+	0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3, 0xD6, 0x06, 0x3D, 0xFC, 0xE6,
+	0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x36, 0x00,
+	0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD6, 0x06, 0x4C, 0xF3, 0xED,
+	0x20, 0x3D, 0x3D, 0x4A, 0xF3, 0x4E, 0x05, 0xB1, 0xFD, 0xE1, 0x00,
+	0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x84,
+	0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03,
+	0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11,
+	0x01, 0x84, 0xFF, 0x1E, 0x00, 0x16, 0x00, 0xA6, 0xFF, 0xBB, 0x00,
+	0xE9, 0xFE, 0x38, 0x01, 0x4B, 0xFF, 0x28, 0xFE, 0x3A, 0x48, 0x04,
+	0x0A, 0x2E, 0xFA, 0xDF, 0x03, 0x8A, 0xFD, 0x60, 0x01, 0x65, 0xFF,
+	0x27, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xC8, 0xFF, 0x64, 0x00, 0x9B,
+	0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93, 0xF9, 0x10, 0x46, 0x03, 0x11,
+	0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC, 0xA2, 0x01, 0x4C, 0xFF, 0x2F,
+	0x00, 0xFF, 0xFF, 0x07, 0x00, 0xE8, 0xFF, 0x12, 0x00, 0x42, 0x00,
+	0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42, 0x76, 0x18, 0x5C,
+	0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B, 0xFF, 0x34, 0x00,
+	0xFE, 0xFF, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4, 0x00, 0xC8,
+	0xFD, 0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20, 0x76, 0xF3,
+	0xC8, 0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD,
+	0xFF, 0xFF, 0xFF, 0x19, 0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04, 0xFD,
+	0x4D, 0x06, 0x00, 0xF2, 0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2, 0x23,
+	0x07, 0x34, 0xFC, 0xDD, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF,
+	0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F, 0x01, 0x80, 0xFC, 0xF7,
+	0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F, 0x83, 0xF1, 0x13, 0x07,
+	0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0xFD,
+	0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A, 0x07,
+	0xDC, 0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06, 0xD5,
+	0xFC, 0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0xFD, 0xFF,
+	0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC,
+	0xF2, 0x81, 0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD,
+	0xFB, 0x00, 0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x35,
+	0x00, 0x38, 0xFF, 0xDA, 0x01, 0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4,
+	0xCE, 0x1A, 0x32, 0x41, 0x1F, 0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71,
+	0x00, 0xFB, 0xFF, 0xF0, 0xFF, 0x05, 0x00, 0xFF, 0xFF, 0x31, 0x00,
+	0x46, 0xFF, 0xB3, 0x01, 0xCF, 0xFC, 0x67, 0x05, 0xEA, 0xF6, 0x44,
+	0x13, 0x1E, 0x45, 0x5E, 0xF8, 0x3F, 0x02, 0x8E, 0xFF, 0xD0, 0xFF,
+	0x4A, 0x00, 0xD2, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D,
+	0xFF, 0x76, 0x01, 0x59, 0xFD, 0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C,
+	0xB6, 0x47, 0xA4, 0xFC, 0x07, 0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0,
+	0x00, 0xB1, 0xFF, 0x13, 0x00, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF,
+	0x2B, 0x01, 0xFE, 0xFD, 0xF8, 0x02, 0xFB, 0xFB, 0x8D, 0x05, 0xE5,
+	0x48, 0xE3, 0x01, 0x91, 0xFD, 0x25, 0x02, 0x6B, 0xFE, 0xF7, 0x00,
+	0x8F, 0xFF, 0x1C, 0x00, 0x18, 0x00, 0x9C, 0xFF, 0xD6, 0x00, 0xB1,
+	0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3, 0xFF, 0x9C, 0x48, 0xFD, 0x07,
+	0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD, 0x49, 0x01, 0x6E, 0xFF, 0x24,
+	0x00, 0x00, 0x00, 0x10, 0x00, 0xBE, 0xFF, 0x7F, 0x00, 0x65, 0xFF,
+	0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46, 0xCD, 0x0E, 0x6A,
+	0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53, 0xFF, 0x2D, 0x00,
+	0xFF, 0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11, 0x00, 0x1B,
+	0xFF, 0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16, 0x07, 0xF6,
+	0xCA, 0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF,
+	0xFF, 0x03, 0x00, 0xFB, 0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10, 0xFE,
+	0xB9, 0x04, 0x27, 0xF4, 0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3, 0x99,
+	0x06, 0x50, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF,
+	0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28, 0x01, 0x3A, 0xFD, 0x00,
+	0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25, 0x79, 0xF2, 0x12, 0x07,
+	0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0xFD,
+	0xFF, 0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0, 0x06,
+	0x8C, 0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07, 0x4E,
+	0xFC, 0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0xFD, 0xFF,
+	0x30, 0x00, 0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8,
+	0xF1, 0x62, 0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC,
+	0x82, 0x01, 0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36,
+	0x00, 0x3A, 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2,
+	0xDD, 0x24, 0x54, 0x3A, 0x74, 0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20,
+	0x01, 0xA3, 0xFF, 0x11, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00,
+	0x37, 0xFF, 0xE1, 0x01, 0x55, 0xFC, 0x8C, 0x06, 0x22, 0xF4, 0x2C,
+	0x1D, 0xC0, 0x3F, 0x55, 0xF4, 0x9B, 0x04, 0x23, 0xFE, 0x9F, 0x00,
+	0xE4, 0xFF, 0xF9, 0xFF, 0x04, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40,
+	0xFF, 0xC1, 0x01, 0xAB, 0xFC, 0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15,
+	0x0B, 0x44, 0x42, 0xF7, 0xDC, 0x02, 0x32, 0xFF, 0x04, 0x00, 0x31,
+	0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x55, 0xFF,
+	0x8B, 0x01, 0x2B, 0xFD, 0xA1, 0x04, 0x9B, 0xF8, 0x42, 0x0E, 0x0F,
+	0x47, 0x38, 0xFB, 0xBE, 0x00, 0x6A, 0x00, 0x58, 0xFF, 0x85, 0x00,
+	0xBB, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x71, 0xFF, 0x43,
+	0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E, 0xFB, 0x7E, 0x07, 0xAF, 0x48,
+	0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01, 0xA3, 0xFE, 0xDD, 0x00, 0x99,
+	0xFF, 0x19, 0x00, 0x1B, 0x00, 0x91, 0xFF, 0xF1, 0x00, 0x79, 0xFE,
+	0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48, 0x07, 0x06, 0xC7,
+	0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78, 0xFF, 0x22, 0x00,
+	0x00, 0x00, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E, 0xFF, 0xB6,
+	0x00, 0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C, 0x31, 0xF9,
+	0x5A, 0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A, 0x00, 0x00,
+	0x00, 0x0B, 0x00, 0xD5, 0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77, 0xFF,
+	0x67, 0x02, 0x14, 0xF8, 0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6, 0x7C,
+	0x05, 0xC5, 0xFC, 0xB7, 0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF, 0xFF,
+	0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D, 0x00, 0x5D, 0xFE, 0x3E,
+	0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B, 0x93, 0xF4, 0x62, 0x06,
+	0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00,
+	0x00, 0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8, 0x05,
+	0xCC, 0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06, 0x35,
+	0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF,
+	0x20, 0x00, 0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1,
+	0xF1, 0x86, 0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC,
+	0xD1, 0x01, 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2D,
+	0x00, 0x54, 0xFF, 0xB7, 0x01, 0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1,
+	0x9F, 0x2E, 0xE3, 0x31, 0x7E, 0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A,
+	0x01, 0x64, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x34, 0x00,
+	0x3E, 0xFF, 0xDF, 0x01, 0x33, 0xFC, 0x20, 0x07, 0x35, 0xF2, 0x36,
+	0x27, 0x78, 0x38, 0x14, 0xF2, 0x3B, 0x06, 0x11, 0xFD, 0x41, 0x01,
+	0x92, 0xFF, 0x17, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36,
+	0xFF, 0xE5, 0x01, 0x44, 0xFC, 0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F,
+	0x31, 0x3E, 0xA5, 0xF3, 0x0F, 0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF,
+	0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF,
+	0xCE, 0x01, 0x8C, 0xFC, 0x00, 0x06, 0x86, 0xF5, 0xE0, 0x17, 0xDB,
+	0x42, 0x3F, 0xF6, 0x71, 0x03, 0xD9, 0xFE, 0x36, 0x00, 0x18, 0x00,
+	0xE5, 0xFF, 0x07, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9E,
+	0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7, 0xF7, 0x75, 0x10, 0x48, 0x46,
+	0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00, 0x8E, 0xFF, 0x6B, 0x00, 0xC6,
+	0xFF, 0x0E, 0x00, 0x00, 0x00, 0x26, 0x00, 0x68, 0xFF, 0x5B, 0x01,
+	0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09, 0x57, 0x48, 0x8D,
+	0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2, 0x00, 0xA4, 0xFF,
+	0x16, 0x00, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42, 0xFE, 0x74,
+	0x02, 0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04, 0x94, 0xFC,
+	0xA9, 0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F, 0x00, 0x00,
+	0x00, 0x15, 0x00, 0xA9, 0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D, 0x01,
+	0x7A, 0xFF, 0xC5, 0xFD, 0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9, 0xF8,
+	0x03, 0x7D, 0xFD, 0x66, 0x01, 0x63, 0xFF, 0x28, 0x00, 0x00, 0x00,
+	0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9, 0xFF, 0xD6, 0xFF, 0xC3,
+	0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11, 0x77, 0xF7, 0x28, 0x05,
+	0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x06,
+	0x00, 0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8, 0x03,
+	0xC7, 0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06, 0x7D,
+	0xFC, 0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x02, 0x00,
+	0x05, 0x00, 0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56,
+	0xF3, 0x61, 0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC,
+	0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x1A,
+	0x00, 0x8A, 0xFF, 0x51, 0x01, 0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1,
+	0x82, 0x37, 0x60, 0x28, 0x0E, 0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB,
+	0x01, 0x40, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x29, 0x00,
+	0x5F, 0xFF, 0xA5, 0x01, 0x78, 0xFC, 0xFF, 0x06, 0x7D, 0xF1, 0xCF,
+	0x30, 0xB8, 0x2F, 0x80, 0xF1, 0x0D, 0x07, 0x6A, 0xFC, 0xAE, 0x01,
+	0x59, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x33, 0x00, 0x43,
+	0xFF, 0xD6, 0x01, 0x39, 0xFC, 0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29,
+	0x85, 0x36, 0xCC, 0xF1, 0x7F, 0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82,
+	0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF,
+	0xE6, 0x01, 0x38, 0xFC, 0xE6, 0x06, 0x19, 0xF3, 0xEA, 0x21, 0x8A,
+	0x3C, 0x0E, 0xF3, 0x78, 0x05, 0x96, 0xFD, 0xF1, 0x00, 0xBB, 0xFF,
+	0x08, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD8,
+	0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1, 0xF4, 0x38, 0x1A, 0x8C, 0x41,
+	0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE, 0x66, 0x00, 0x01, 0x00, 0xEE,
+	0xFF, 0x06, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x47, 0xFF, 0xAF, 0x01,
+	0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12, 0x5C, 0x45, 0xA9,
+	0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51, 0x00, 0xD0, 0xFF,
+	0x0C, 0x00, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71, 0x01, 0x65,
+	0xFD, 0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47, 0x03, 0xFD,
+	0xD9, 0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE, 0xFF, 0x14,
+	0x00, 0x00, 0x00, 0x20, 0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C, 0xFE,
+	0xDE, 0x02, 0x2E, 0xFC, 0x13, 0x05, 0xEC, 0x48, 0x54, 0x02, 0x5E,
+	0xFD, 0x3F, 0x02, 0x5D, 0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C, 0x00,
+	0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF, 0xFE, 0x86, 0x01, 0xBA,
+	0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08, 0xC7, 0xFA, 0x93, 0x03,
+	0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0x0F,
+	0x00, 0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17, 0x01,
+	0x8B, 0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04, 0x15,
+	0xFD, 0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x08, 0x00,
+	0xE1, 0xFF, 0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD,
+	0xF6, 0x77, 0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC,
+	0xC8, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0xFD,
+	0xFF, 0xD9, 0xFF, 0xB4, 0x00, 0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3,
+	0xFC, 0x3E, 0x5B, 0x1E, 0xDB, 0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3,
+	0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x14, 0x00,
+	0x9B, 0xFF, 0x31, 0x01, 0x2C, 0xFD, 0x15, 0x06, 0x41, 0xF2, 0x6A,
+	0x39, 0x0A, 0x26, 0x61, 0xF2, 0x17, 0x07, 0x31, 0xFC, 0xE2, 0x01,
+	0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x25, 0x00, 0x6A,
+	0xFF, 0x8E, 0x01, 0x99, 0xFC, 0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32,
+	0x82, 0x2D, 0x96, 0xF1, 0x21, 0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50,
+	0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF,
+	0xCA, 0x01, 0x46, 0xFC, 0x29, 0x07, 0xB3, 0xF1, 0xD1, 0x2B, 0x81,
+	0x34, 0x9C, 0xF1, 0xB8, 0x06, 0xB5, 0xFC, 0x7C, 0x01, 0x74, 0xFF,
+	0x22, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5,
+	0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA, 0xF2, 0x46, 0x24, 0xC8, 0x3A,
+	0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD, 0x17, 0x01, 0xA8, 0xFF, 0x0F,
+	0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01,
+	0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C, 0x1F, 0x40, 0x85,
+	0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA, 0xFF, 0xF7, 0xFF,
+	0x04, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE, 0x01, 0xB4,
+	0xFC, 0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44, 0x86, 0xF7,
+	0xB6, 0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9, 0xFF, 0x0A,
+	0x00, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x86, 0x01, 0x36, 0xFD,
+	0x89, 0x04, 0xCD, 0xF8, 0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB, 0x91,
+	0x00, 0x83, 0x00, 0x4A, 0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11, 0x00,
+	0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D, 0x01, 0xD6, 0xFD, 0x46,
+	0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48, 0x98, 0x00, 0x26, 0xFE,
+	0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0x1A,
+	0x00, 0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5, 0xFD,
+	0x05, 0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03, 0xE4,
+	0xFD, 0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0x12, 0x00,
+	0xB6, 0xFF, 0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB,
+	0xFB, 0x69, 0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD,
+	0x81, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xD7,
+	0xFF, 0x3E, 0x00, 0xEA, 0xFF, 0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7,
+	0x99, 0x44, 0x68, 0x14, 0x8E, 0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA,
+	0x01, 0x43, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x04, 0x00, 0xF5, 0xFF,
+	0xEF, 0xFF, 0x88, 0x00, 0x49, 0xFE, 0x5D, 0x04, 0xB7, 0xF4, 0x7D,
+	0x40, 0xFD, 0x1B, 0x6C, 0xF4, 0x70, 0x06, 0x5F, 0xFC, 0xDE, 0x01,
+	0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAC,
+	0xFF, 0x0E, 0x01, 0x66, 0xFD, 0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B,
+	0xB0, 0x23, 0xC4, 0xF2, 0xFF, 0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38,
+	0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x21, 0x00, 0x77, 0xFF,
+	0x75, 0x01, 0xBF, 0xFC, 0xAB, 0x06, 0xA6, 0xF1, 0x05, 0x35, 0x40,
+	0x2B, 0xBF, 0xF1, 0x2A, 0x07, 0x42, 0xFC, 0xCE, 0x01, 0x48, 0xFF,
+	0x31, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2E, 0x00, 0x52, 0xFF, 0xBC,
+	0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E, 0xF1, 0x11, 0x2E, 0x6B, 0x32,
+	0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC, 0x94, 0x01, 0x67, 0xFF, 0x26,
+	0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE0, 0x01,
+	0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26, 0xF2, 0x38, 0x2A,
+	0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96, 0xFF, 0x16, 0x00,
+	0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x48,
+	0xFC, 0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E, 0xCF, 0xF3,
+	0xF3, 0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF, 0xFF, 0x03,
+	0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93, 0xFC,
+	0xEF, 0x05, 0xB0, 0xF5, 0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6, 0x4D,
+	0x03, 0xEF, 0xFE, 0x2A, 0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08, 0x00,
+	0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99, 0x01, 0x0B, 0xFD, 0xE6,
+	0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46, 0x37, 0xFA, 0x42, 0x01,
+	0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3, 0xFF, 0x0F, 0x00, 0x00,
+	0x00, 0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD, 0x03,
+	0x94, 0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE, 0x6C,
+	0x01, 0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0x1D, 0x00,
+	0x8A, 0xFF, 0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6,
+	0x02, 0xF2, 0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE,
+	0x1E, 0x01, 0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0xAC,
+	0xFF, 0xAE, 0x00, 0x05, 0xFF, 0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD,
+	0xFD, 0x47, 0x0E, 0x0B, 0xC8, 0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C,
+	0x01, 0x61, 0xFF, 0x28, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xCD, 0xFF,
+	0x57, 0x00, 0xB6, 0xFF, 0xBE, 0xFF, 0xED, 0x01, 0xF5, 0xF8, 0x9B,
+	0x45, 0x22, 0x12, 0x48, 0xF7, 0x3D, 0x05, 0xE2, 0xFC, 0xAB, 0x01,
+	0x49, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x06, 0x00, 0xEC, 0xFF, 0x06,
+	0x00, 0x5A, 0x00, 0x9A, 0xFE, 0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41,
+	0xA1, 0x19, 0x09, 0xF5, 0x33, 0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A,
+	0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF,
+	0xE8, 0x00, 0xA6, 0xFD, 0x5F, 0x05, 0x31, 0xF3, 0xF6, 0x3C, 0x52,
+	0x21, 0x37, 0xF3, 0xDD, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF,
+	0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x1C, 0x00, 0x86, 0xFF, 0x59,
+	0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC, 0xF1, 0x04, 0x37, 0xF3, 0x28,
+	0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC, 0xD8, 0x01, 0x41, 0xFF, 0x33,
+	0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01,
+	0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E,
+	0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00,
+	0xFD, 0xFF, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8, 0x01, 0x37,
+	0xFC, 0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37, 0xDC, 0xF1,
+	0x6F, 0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C, 0x00, 0xFE,
+	0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B, 0xFC,
+	0xDD, 0x06, 0x37, 0xF3, 0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3, 0x5F,
+	0x05, 0xA6, 0xFD, 0xE8, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01, 0x00,
+	0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6, 0x01, 0x77, 0xFC, 0x33,
+	0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41, 0x8D, 0xF5, 0xDA, 0x03,
+	0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0xFF,
+	0xFF, 0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D, 0x05,
+	0x48, 0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01, 0xBE,
+	0xFF, 0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0x00, 0x00,
+	0x28, 0x00, 0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8,
+	0xF9, 0x0E, 0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01,
+	0x05, 0xFF, 0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0x00, 0x00, 0x20,
+	0x00, 0x7F, 0xFF, 0x1E, 0x01, 0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC,
+	0x9B, 0x04, 0xF2, 0x48, 0xC6, 0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50,
+	0xFE, 0x04, 0x01, 0x8A, 0xFF, 0x1D, 0x00, 0x17, 0x00, 0xA1, 0xFF,
+	0xC9, 0x00, 0xCD, 0xFE, 0x6C, 0x01, 0xEA, 0xFE, 0xF3, 0xFE, 0x70,
+	0x48, 0xFF, 0x08, 0x94, 0xFA, 0xAD, 0x03, 0xA3, 0xFD, 0x55, 0x01,
+	0x6A, 0xFF, 0x26, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xC3, 0xFF, 0x71,
+	0x00, 0x81, 0xFF, 0x1F, 0x00, 0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46,
+	0xE7, 0x0F, 0x08, 0xF8, 0xE6, 0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F,
+	0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x08, 0x00, 0xE3, 0xFF, 0x1E, 0x00,
+	0x2A, 0x00, 0xEF, 0xFE, 0x4D, 0x03, 0x7D, 0xF6, 0x2A, 0x43, 0x4B,
+	0x17, 0xB0, 0xF5, 0xEF, 0x05, 0x93, 0xFC, 0xCB, 0x01, 0x3D, 0xFF,
+	0x34, 0x00, 0xFE, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0xD4, 0xFF, 0xBF,
+	0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF, 0xF3, 0x98, 0x3E, 0xF3, 0x1E,
+	0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36,
+	0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x16, 0x00, 0x96, 0xFF, 0x39, 0x01,
+	0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38, 0xA0, 0x26, 0x4B,
+	0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C, 0xFF, 0x35, 0x00,
+	0xFD, 0xFF, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94, 0x01, 0x90,
+	0xFC, 0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E, 0x8E, 0xF1,
+	0x1D, 0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E, 0x00, 0xFD,
+	0xFF, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42, 0xFC,
+	0x2A, 0x07, 0xBF, 0xF1, 0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1, 0xAB,
+	0x06, 0xBF, 0xFC, 0x75, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, 0xFF,
+	0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0xFF,
+	0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B, 0xAD, 0xF2, 0xBF, 0x05,
+	0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFE,
+	0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70, 0x06,
+	0x6C, 0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04, 0x49,
+	0xFE, 0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0xFF, 0xFF,
+	0x32, 0x00, 0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E,
+	0xF6, 0x68, 0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF,
+	0xEA, 0xFF, 0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x2B,
+	0x00, 0x59, 0xFF, 0x81, 0x01, 0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8,
+	0x2D, 0x0D, 0x69, 0x47, 0xEB, 0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C,
+	0xFF, 0x93, 0x00, 0xB6, 0xFF, 0x12, 0x00, 0x00, 0x00, 0x22, 0x00,
+	0x76, 0xFF, 0x37, 0x01, 0xE4, 0xFD, 0x2C, 0x03, 0x94, 0xFB, 0x83,
+	0x06, 0xCE, 0x48, 0x05, 0x01, 0xF5, 0xFD, 0xF0, 0x01, 0x87, 0xFE,
+	0xEA, 0x00, 0x94, 0xFF, 0x1A, 0x00, 0x1A, 0x00, 0x96, 0xFF, 0xE3,
+	0x00, 0x95, 0xFE, 0xD5, 0x01, 0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48,
+	0x00, 0x07, 0x61, 0xFB, 0x46, 0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73,
+	0xFF, 0x23, 0x00, 0x00, 0x00, 0x11, 0x00, 0xB9, 0xFF, 0x8C, 0x00,
+	0x4A, 0xFF, 0x83, 0x00, 0x91, 0x00, 0x91, 0xFB, 0x3D, 0x47, 0xB7,
+	0x0D, 0xCD, 0xF8, 0x89, 0x04, 0x36, 0xFD, 0x86, 0x01, 0x57, 0xFF,
+	0x2B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xD9, 0xFF, 0x37, 0x00, 0xF7,
+	0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86, 0xF7, 0x53, 0x44, 0xFB, 0x14,
+	0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC, 0xBE, 0x01, 0x42, 0xFF, 0x32,
+	0x00, 0xFF, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xEA, 0xFF, 0x93, 0x00,
+	0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40, 0x94, 0x1C, 0x47,
+	0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00,
+	0xFE, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17, 0x01, 0x57,
+	0xFD, 0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24, 0xAA, 0xF2,
+	0x06, 0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD,
+	0xFF, 0xFE, 0xFF, 0x22, 0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5, 0xFC,
+	0xB8, 0x06, 0x9C, 0xF1, 0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1, 0x29,
+	0x07, 0x46, 0xFC, 0xCA, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD, 0xFF,
+	0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0, 0x01, 0x53, 0xFC, 0x21,
+	0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32, 0x86, 0xF1, 0xDB, 0x06,
+	0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0xFD,
+	0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17, 0x07,
+	0x61, 0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06, 0x2C,
+	0xFD, 0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFE, 0xFF,
+	0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB,
+	0xF3, 0x5B, 0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD,
+	0xB4, 0x00, 0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0x33,
+	0x00, 0x3E, 0xFF, 0xC8, 0x01, 0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5,
+	0xB6, 0x16, 0x77, 0x43, 0xBD, 0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D,
+	0x00, 0x25, 0x00, 0xE1, 0xFF, 0x08, 0x00, 0xFF, 0xFF, 0x2D, 0x00,
+	0x51, 0xFF, 0x95, 0x01, 0x15, 0xFD, 0xCF, 0x04, 0x39, 0xF8, 0x59,
+	0x0F, 0xAF, 0x46, 0x8B, 0xFA, 0x17, 0x01, 0x38, 0x00, 0x73, 0xFF,
+	0x78, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x25, 0x00, 0x6C,
+	0xFF, 0x4F, 0x01, 0xB0, 0xFD, 0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08,
+	0x86, 0x48, 0x5A, 0xFF, 0xBA, 0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF,
+	0x00, 0x9E, 0xFF, 0x17, 0x00, 0x1C, 0x00, 0x8C, 0xFF, 0xFE, 0x00,
+	0x5D, 0xFE, 0x3F, 0x02, 0x5E, 0xFD, 0x54, 0x02, 0xEC, 0x48, 0x13,
+	0x05, 0x2E, 0xFC, 0xDE, 0x02, 0x0C, 0xFE, 0x24, 0x01, 0x7D, 0xFF,
+	0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0xAE, 0xFF, 0xA7, 0x00, 0x12,
+	0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03, 0xFD, 0xDC, 0x47, 0x95, 0x0B,
+	0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD, 0x71, 0x01, 0x5F, 0xFF, 0x29,
+	0x00, 0x00, 0x00, 0x0C, 0x00, 0xD0, 0xFF, 0x51, 0x00, 0xC3, 0xFF,
+	0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45, 0xB2, 0x12, 0x19,
+	0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47, 0xFF, 0x30, 0x00,
+	0xFF, 0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66, 0x00, 0x85,
+	0xFE, 0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A, 0xE1, 0xF4,
+	0x43, 0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE,
+	0xFF, 0x01, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96, 0xFD,
+	0x78, 0x05, 0x0E, 0xF3, 0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3, 0xE6,
+	0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF,
+	0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60, 0x01, 0xE0, 0xFC, 0x7F,
+	0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29, 0xEB, 0xF1, 0x2A, 0x07,
+	0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0xFD,
+	0xFF, 0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D, 0x07,
+	0x80, 0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06, 0x78,
+	0xFC, 0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0xFD, 0xFF,
+	0x34, 0x00, 0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E,
+	0xF2, 0x60, 0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC,
+	0x51, 0x01, 0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36,
+	0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3,
+	0xBA, 0x20, 0x61, 0x3D, 0x56, 0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE,
+	0x00, 0xC5, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x35, 0x00,
+	0x3A, 0xFF, 0xD3, 0x01, 0x7D, 0xFC, 0x23, 0x06, 0x32, 0xF5, 0x0C,
+	0x19, 0x38, 0x42, 0xC7, 0xF5, 0xB8, 0x03, 0xAF, 0xFE, 0x4E, 0x00,
+	0x0C, 0x00, 0xEA, 0xFF, 0x06, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4A,
+	0xFF, 0xA7, 0x01, 0xEC, 0xFC, 0x28, 0x05, 0x77, 0xF7, 0x92, 0x11,
+	0xD7, 0x45, 0x43, 0xF9, 0xC3, 0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E,
+	0x00, 0xCB, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x28, 0x00, 0x63, 0xFF,
+	0x66, 0x01, 0x7D, 0xFD, 0xF8, 0x03, 0xFB, 0xF9, 0x89, 0x0A, 0x1D,
+	0x48, 0xC5, 0xFD, 0x7A, 0xFF, 0x1D, 0x01, 0xF7, 0xFE, 0xB4, 0x00,
+	0xA9, 0xFF, 0x15, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x82, 0xFF, 0x18,
+	0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94, 0xFC, 0x24, 0x04, 0xF5, 0x48,
+	0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02, 0x42, 0xFE, 0x0B, 0x01, 0x87,
+	0xFF, 0x1E, 0x00, 0x16, 0x00, 0xA4, 0xFF, 0xC2, 0x00, 0xDB, 0xFE,
+	0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48, 0x81, 0x09, 0x61,
+	0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68, 0xFF, 0x26, 0x00,
+	0x00, 0x00, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E, 0xFF, 0x06,
+	0x00, 0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10, 0xD7, 0xF7,
+	0xFC, 0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF,
+	0xFF, 0x07, 0x00, 0xE5, 0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9, 0xFE,
+	0x71, 0x03, 0x3F, 0xF6, 0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5, 0x00,
+	0x06, 0x8C, 0xFC, 0xCE, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF,
+	0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9, 0x00, 0xDA, 0xFD, 0x0F,
+	0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F, 0x97, 0xF3, 0xBD, 0x06,
+	0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF,
+	0xFF, 0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B, 0x06,
+	0x14, 0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07, 0x33,
+	0xFC, 0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0xFD, 0xFF,
+	0x28, 0x00, 0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E,
+	0xF1, 0xE3, 0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC,
+	0xB7, 0x01, 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x32,
+	0x00, 0x46, 0xFF, 0xD1, 0x01, 0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1,
+	0xAE, 0x2A, 0x86, 0x35, 0xB1, 0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E,
+	0x01, 0x7B, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00,
+	0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF7, 0x06, 0xE0, 0xF2, 0x18,
+	0x23, 0xAB, 0x3B, 0xCC, 0xF2, 0xA8, 0x05, 0x76, 0xFD, 0x04, 0x01,
+	0xB1, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38,
+	0xFF, 0xDC, 0x01, 0x64, 0xFC, 0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B,
+	0xD9, 0x40, 0xEA, 0xF4, 0x3E, 0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5,
+	0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x44, 0xFF,
+	0xB7, 0x01, 0xC5, 0xFC, 0x7C, 0x05, 0xBC, 0xF6, 0xD5, 0x13, 0xDC,
+	0x44, 0x14, 0xF8, 0x67, 0x02, 0x77, 0xFF, 0xDD, 0xFF, 0x44, 0x00,
+	0xD5, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5B, 0xFF, 0x7C,
+	0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31, 0xF9, 0xA4, 0x0C, 0x90, 0x47,
+	0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00, 0x2E, 0xFF, 0x99, 0x00, 0xB3,
+	0xFF, 0x12, 0x00, 0x00, 0x00, 0x22, 0x00, 0x78, 0xFF, 0x31, 0x01,
+	0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06, 0xDB, 0x48, 0x73,
+	0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1, 0x00, 0x91, 0xFF,
+	0x1B, 0x00, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3, 0xFE, 0xBB,
+	0x01, 0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07, 0x2E, 0xFB,
+	0x60, 0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24, 0x00, 0x00,
+	0x00, 0x10, 0x00, 0xBB, 0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A, 0x00,
+	0xBE, 0x00, 0x38, 0xFB, 0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8, 0xA1,
+	0x04, 0x2B, 0xFD, 0x8B, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, 0xFF,
+	0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04, 0x00, 0x32, 0xFF, 0xDC,
+	0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15, 0x34, 0xF6, 0xB7, 0x05,
+	0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x04,
+	0x00, 0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B, 0x04,
+	0x55, 0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06, 0x55,
+	0xFC, 0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00, 0x00,
+	0x11, 0x00, 0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74,
+	0xF2, 0x54, 0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC,
+	0xE4, 0x01, 0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x23,
+	0x00, 0x71, 0xFF, 0x82, 0x01, 0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1,
+	0xFD, 0x33, 0x62, 0x2C, 0xA8, 0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7,
+	0x01, 0x4C, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2F, 0x00,
+	0x4E, 0xFF, 0xC3, 0x01, 0x4E, 0xFC, 0x24, 0x07, 0x9E, 0xF1, 0xF2,
+	0x2C, 0x78, 0x33, 0x8C, 0xF1, 0xD0, 0x06, 0xA2, 0xFC, 0x88, 0x01,
+	0x6D, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x35, 0x00, 0x3B,
+	0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x12, 0x07, 0x79, 0xF2, 0x73, 0x25,
+	0xDF, 0x39, 0x5A, 0xF2, 0x00, 0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F,
+	0xFF, 0x13, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF,
+	0xE2, 0x01, 0x50, 0xFC, 0x99, 0x06, 0xFE, 0xF3, 0xC3, 0x1D, 0x5E,
+	0x3F, 0x27, 0xF4, 0xB9, 0x04, 0x10, 0xFE, 0xA9, 0x00, 0xDF, 0xFF,
+	0xFB, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC5,
+	0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07, 0xF6, 0x22, 0x16, 0xC3, 0x43,
+	0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF, 0x11, 0x00, 0x2B, 0x00, 0xDE,
+	0xFF, 0x09, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0x90, 0x01,
+	0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E, 0xE1, 0x46, 0xE1,
+	0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F, 0x00, 0xBE, 0xFF,
+	0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49, 0x01, 0xBC,
+	0xFD, 0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48, 0xC3, 0xFF,
+	0x89, 0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C, 0xFF, 0x18,
+	0x00, 0x1C, 0x00, 0x8F, 0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25, 0x02,
+	0x91, 0xFD, 0xE3, 0x01, 0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB, 0xF8,
+	0x02, 0xFE, 0xFD, 0x2B, 0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00, 0x00,
+	0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20, 0xFF, 0xD0, 0x00, 0x07,
+	0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C, 0x63, 0xF9, 0x42, 0x04,
+	0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x0B,
+	0x00, 0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F, 0x02,
+	0x5E, 0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05, 0xCF,
+	0xFC, 0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x05, 0x00,
+	0xF0, 0xFF, 0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F,
+	0xF5, 0x32, 0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC,
+	0xDA, 0x01, 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A,
+	0x00, 0xB6, 0xFF, 0xFB, 0x00, 0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2,
+	0x1C, 0x3C, 0x81, 0x22, 0xFC, 0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6,
+	0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x1E, 0x00,
+	0x7F, 0xFF, 0x67, 0x01, 0xD5, 0xFC, 0x8E, 0x06, 0xBE, 0xF1, 0x06,
+	0x36, 0x1A, 0x2A, 0xDC, 0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01,
+	0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2C, 0x00, 0x57,
+	0xFF, 0xB3, 0x01, 0x64, 0xFC, 0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F,
+	0x5A, 0x31, 0x7D, 0xF1, 0xF7, 0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61,
+	0xFF, 0x29, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF,
+	0xDD, 0x01, 0x34, 0xFC, 0x23, 0x07, 0x21, 0xF2, 0xCB, 0x27, 0xFE,
+	0x37, 0x00, 0xF2, 0x4D, 0x06, 0x04, 0xFD, 0x49, 0x01, 0x8E, 0xFF,
+	0x19, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6,
+	0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76, 0xF3, 0x22, 0x20, 0xCA, 0x3D,
+	0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD, 0xD4, 0x00, 0xCA, 0xFF, 0x03,
+	0x00, 0x02, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD1, 0x01,
+	0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18, 0x89, 0x42, 0x02,
+	0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12, 0x00, 0xE8, 0xFF,
+	0x07, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2, 0x01, 0xF6,
+	0xFC, 0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46, 0x93, 0xF9,
+	0x98, 0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8, 0xFF, 0x0E,
+	0x00, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A, 0xFD,
+	0xDF, 0x03, 0x2E, 0xFA, 0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE, 0x4B,
+	0xFF, 0x38, 0x01, 0xE9, 0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16, 0x00,
+	0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F,
+	0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC,
+	0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x00,
+	0x00, 0xF4, 0xFF, 0x1A, 0x00, 0xFF, 0x00, 0x07, 0x03, 0x16, 0x06,
+	0x7C, 0x09, 0x2A, 0x0C, 0x2E, 0x0D, 0x2A, 0x0C, 0x7C, 0x09, 0x16,
+	0x06, 0x07, 0x03, 0xFF, 0x00, 0x1A, 0x00, 0xF4, 0xFF, 0xF2, 0xFF,
+	0xA0, 0xFF, 0x71, 0xFF, 0x71, 0x00, 0x86, 0x03, 0x73, 0x08, 0x88,
+	0x0D, 0x78, 0x10, 0xC9, 0x0F, 0xD5, 0x0B, 0x8B, 0x06, 0x28, 0x02,
+	0xDF, 0xFF, 0x6F, 0xFF, 0xC3, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDC,
+	0xFF, 0x80, 0xFF, 0x9A, 0xFF, 0x46, 0x01, 0x1E, 0x05, 0x5A, 0x0A,
+	0xED, 0x0E, 0xAA, 0x10, 0xAF, 0x0E, 0xFD, 0x09, 0xCB, 0x04, 0x18,
+	0x01, 0x8E, 0xFF, 0x85, 0xFF, 0xE1, 0xFF, 0xFC, 0xFF, 0xBD, 0xFF,
+	0x6D, 0xFF, 0xF6, 0xFF, 0x65, 0x02, 0xE5, 0x06, 0x2B, 0x0C, 0xF3,
+	0x0F, 0x60, 0x10, 0x3B, 0x0D, 0x16, 0x08, 0x3F, 0x03, 0x50, 0x00,
+	0x6E, 0xFF, 0xA7, 0xFF, 0xF5, 0xFF, 0xEF, 0xFF, 0x9A, 0xFF, 0x75,
+	0xFF, 0x91, 0x00, 0xC9, 0x03, 0xC8, 0x08, 0xCC, 0x0D, 0x89, 0x10,
+	0x9F, 0x0F, 0x85, 0x0B, 0x3B, 0x06, 0xF4, 0x01, 0xCD, 0xFF, 0x72,
+	0xFF, 0xC9, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD7, 0xFF, 0x7B, 0xFF,
+	0xA5, 0xFF, 0x73, 0x01, 0x6A, 0x05, 0xAD, 0x0A, 0x21, 0x0F, 0xA6,
+	0x10, 0x74, 0x0E, 0xA9, 0x09, 0x83, 0x04, 0xF0, 0x00, 0x85, 0xFF,
+	0x8B, 0xFF, 0xE5, 0xFF, 0xFA, 0xFF, 0xB7, 0xFF, 0x6C, 0xFF, 0x0C,
+	0x00, 0x9D, 0x02, 0x37, 0x07, 0x78, 0x0C, 0x15, 0x10, 0x47, 0x10,
+	0xF3, 0x0C, 0xC2, 0x07, 0x01, 0x03, 0x35, 0x00, 0x6D, 0xFF, 0xAD,
+	0xFF, 0xF7, 0xFF, 0xEB, 0xFF, 0x94, 0xFF, 0x7A, 0xFF, 0xB3, 0x00,
+	0x0D, 0x04, 0x1C, 0x09, 0x0D, 0x0E, 0x97, 0x10, 0x73, 0x0F, 0x35,
+	0x0B, 0xEB, 0x05, 0xC1, 0x01, 0xBD, 0xFF, 0x75, 0xFF, 0xCE, 0xFF,
+	0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0xFF, 0x77, 0xFF, 0xB3, 0xFF, 0xA1,
+	0x01, 0xB7, 0x05, 0xFF, 0x0A, 0x53, 0x0F, 0x9E, 0x10, 0x37, 0x0E,
+	0x55, 0x09, 0x3B, 0x04, 0xCB, 0x00, 0x7E, 0xFF, 0x90, 0xFF, 0xE9,
+	0xFF, 0xF8, 0xFF, 0xB1, 0xFF, 0x6C, 0xFF, 0x24, 0x00, 0xD8, 0x02,
+	0x8A, 0x07, 0xC2, 0x0C, 0x34, 0x10, 0x2A, 0x10, 0xAA, 0x0C, 0x6F,
+	0x07, 0xC4, 0x02, 0x1C, 0x00, 0x6C, 0xFF, 0xB3, 0xFF, 0xF9, 0xFF,
+	0xE8, 0xFF, 0x8E, 0xFF, 0x80, 0xFF, 0xD7, 0x00, 0x53, 0x04, 0x71,
+	0x09, 0x4C, 0x0E, 0xA1, 0x10, 0x43, 0x0F, 0xE3, 0x0A, 0x9D, 0x05,
+	0x91, 0x01, 0xAE, 0xFF, 0x79, 0xFF, 0xD4, 0xFF, 0x00, 0x00, 0xFF,
+	0xFF, 0xCD, 0xFF, 0x74, 0xFF, 0xC2, 0xFF, 0xD2, 0x01, 0x06, 0x06,
+	0x50, 0x0B, 0x82, 0x0F, 0x93, 0x10, 0xF8, 0x0D, 0x00, 0x09, 0xF6,
+	0x03, 0xA7, 0x00, 0x78, 0xFF, 0x96, 0xFF, 0xEC, 0xFF, 0xF6, 0xFF,
+	0xAB, 0xFF, 0x6D, 0xFF, 0x3E, 0x00, 0x15, 0x03, 0xDE, 0x07, 0x0B,
+	0x0D, 0x50, 0x10, 0x0A, 0x10, 0x5E, 0x0C, 0x1C, 0x07, 0x8A, 0x02,
+	0x04, 0x00, 0x6C, 0xFF, 0xB9, 0xFF, 0xFB, 0xFF, 0xE4, 0xFF, 0x89,
+	0xFF, 0x88, 0xFF, 0xFD, 0x00, 0x9B, 0x04, 0xC5, 0x09, 0x88, 0x0E,
+	0xA8, 0x10, 0x10, 0x0F, 0x91, 0x0A, 0x50, 0x05, 0x64, 0x01, 0xA1,
+	0xFF, 0x7D, 0xFF, 0xD9, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC7, 0xFF,
+	0x71, 0xFF, 0xD3, 0xFF, 0x05, 0x02, 0x55, 0x06, 0xA0, 0x0B, 0xAD,
+	0x0F, 0x84, 0x10, 0xB6, 0x0D, 0xAC, 0x08, 0xB3, 0x03, 0x86, 0x00,
+	0x74, 0xFF, 0x9C, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xA5, 0xFF, 0x6F,
+	0xFF, 0x5A, 0x00, 0x54, 0x03, 0x32, 0x08, 0x52, 0x0D, 0x68, 0x10,
+	0xE6, 0x0F, 0x11, 0x0C, 0xCA, 0x06, 0x52, 0x02, 0xEF, 0xFF, 0x6E,
+	0xFF, 0xBF, 0xFF, 0xFC, 0xFF, 0xDF, 0xFF, 0x84, 0xFF, 0x91, 0xFF,
+	0x25, 0x01, 0xE4, 0x04, 0x19, 0x0A, 0xC2, 0x0E, 0xAA, 0x10, 0xDA,
+	0x0E, 0x3E, 0x0A, 0x05, 0x05, 0x38, 0x01, 0x96, 0xFF, 0x81, 0xFF,
+	0xDD, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC1, 0xFF, 0x6E, 0xFF, 0xE6,
+	0xFF, 0x3A, 0x02, 0xA6, 0x06, 0xEF, 0x0B, 0xD6, 0x0F, 0x71, 0x10,
+	0x71, 0x0D, 0x57, 0x08, 0x71, 0x03, 0x67, 0x00, 0x70, 0xFF, 0xA2,
+	0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0x9F, 0xFF, 0x72, 0xFF, 0x78, 0x00,
+	0x95, 0x03, 0x86, 0x08, 0x98, 0x0D, 0x7C, 0x10, 0xC0, 0x0F, 0xC3,
+	0x0B, 0x79, 0x06, 0x1C, 0x02, 0xDB, 0xFF, 0x70, 0xFF, 0xC5, 0xFF,
+	0xFE, 0xFF, 0x00, 0x00, 0xDB, 0xFF, 0x7F, 0xFF, 0x9C, 0xFF, 0x50,
+	0x01, 0x2F, 0x05, 0x6C, 0x0A, 0xF9, 0x0E, 0xA9, 0x10, 0xA2, 0x0E,
+	0xEA, 0x09, 0xBB, 0x04, 0x0F, 0x01, 0x8C, 0xFF, 0x87, 0xFF, 0xE2,
+	0xFF, 0xFC, 0xFF, 0xBC, 0xFF, 0x6D, 0xFF, 0xFA, 0xFF, 0x71, 0x02,
+	0xF7, 0x06, 0x3C, 0x0C, 0xFB, 0x0F, 0x5B, 0x10, 0x2B, 0x0D, 0x03,
+	0x08, 0x31, 0x03, 0x4A, 0x00, 0x6E, 0xFF, 0xA8, 0xFF, 0xF5, 0xFF,
+	0xEE, 0xFF, 0x99, 0xFF, 0x76, 0xFF, 0x98, 0x00, 0xD8, 0x03, 0xDB,
+	0x08, 0xDB, 0x0D, 0x8D, 0x10, 0x96, 0x0F, 0x73, 0x0B, 0x29, 0x06,
+	0xE8, 0x01, 0xC9, 0xFF, 0x72, 0xFF, 0xCA, 0xFF, 0xFE, 0xFF, 0x00,
+	0x00, 0xD6, 0xFF, 0x7A, 0xFF, 0xA8, 0xFF, 0x7D, 0x01, 0x7B, 0x05,
+	0xBF, 0x0A, 0x2D, 0x0F, 0xA5, 0x10, 0x67, 0x0E, 0x96, 0x09, 0x73,
+	0x04, 0xE7, 0x00, 0x84, 0xFF, 0x8C, 0xFF, 0xE6, 0xFF, 0xFA, 0xFF,
+	0xB6, 0xFF, 0x6C, 0xFF, 0x11, 0x00, 0xAA, 0x02, 0x4A, 0x07, 0x88,
+	0x0C, 0x1C, 0x10, 0x41, 0x10, 0xE3, 0x0C, 0xAF, 0x07, 0xF3, 0x02,
+	0x2F, 0x00, 0x6C, 0xFF, 0xAE, 0xFF, 0xF7, 0xFF, 0xEA, 0xFF, 0x93,
+	0xFF, 0x7B, 0xFF, 0xBB, 0x00, 0x1C, 0x04, 0x2F, 0x09, 0x1B, 0x0E,
+	0x9A, 0x10, 0x68, 0x0F, 0x23, 0x0B, 0xDA, 0x05, 0xB7, 0x01, 0xB9,
+	0xFF, 0x76, 0xFF, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0xFF,
+	0x76, 0xFF, 0xB6, 0xFF, 0xAC, 0x01, 0xC8, 0x05, 0x11, 0x0B, 0x5E,
+	0x0F, 0x9C, 0x10, 0x29, 0x0E, 0x42, 0x09, 0x2C, 0x04, 0xC2, 0x00,
+	0x7D, 0xFF, 0x92, 0xFF, 0xEA, 0xFF, 0xF8, 0xFF, 0xB0, 0xFF, 0x6C,
+	0xFF, 0x29, 0x00, 0xE6, 0x02, 0x9D, 0x07, 0xD3, 0x0C, 0x3B, 0x10,
+	0x23, 0x10, 0x99, 0x0C, 0x5C, 0x07, 0xB7, 0x02, 0x16, 0x00, 0x6C,
+	0xFF, 0xB4, 0xFF, 0xF9, 0xFF, 0xE7, 0xFF, 0x8D, 0xFF, 0x82, 0xFF,
+	0xDF, 0x00, 0x63, 0x04, 0x84, 0x09, 0x59, 0x0E, 0xA3, 0x10, 0x38,
+	0x0F, 0xD1, 0x0A, 0x8C, 0x05, 0x87, 0x01, 0xAB, 0xFF, 0x79, 0xFF,
+	0xD5, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xCB, 0xFF, 0x73, 0xFF, 0xC6,
+	0xFF, 0xDD, 0x01, 0x17, 0x06, 0x62, 0x0B, 0x8C, 0x0F, 0x90, 0x10,
+	0xE9, 0x0D, 0xED, 0x08, 0xE7, 0x03, 0xA0, 0x00, 0x77, 0xFF, 0x97,
+	0xFF, 0xED, 0xFF, 0xF6, 0xFF, 0xA9, 0xFF, 0x6D, 0xFF, 0x44, 0x00,
+	0x23, 0x03, 0xF1, 0x07, 0x1B, 0x0D, 0x55, 0x10, 0x02, 0x10, 0x4D,
+	0x0C, 0x0A, 0x07, 0x7E, 0x02, 0xFF, 0xFF, 0x6D, 0xFF, 0xBA, 0xFF,
+	0xFB, 0xFF, 0xE3, 0xFF, 0x88, 0xFF, 0x8A, 0xFF, 0x06, 0x01, 0xAB,
+	0x04, 0xD8, 0x09, 0x95, 0x0E, 0xA9, 0x10, 0x05, 0x0F, 0x7F, 0x0A,
+	0x40, 0x05, 0x5A, 0x01, 0x9F, 0xFF, 0x7E, 0xFF, 0xDA, 0xFF, 0x00,
+	0x00, 0xFE, 0xFF, 0xC6, 0xFF, 0x70, 0xFF, 0xD7, 0xFF, 0x10, 0x02,
+	0x67, 0x06, 0xB1, 0x0B, 0xB7, 0x0F, 0x80, 0x10, 0xA7, 0x0D, 0x99,
+	0x08, 0xA4, 0x03, 0x7F, 0x00, 0x73, 0xFF, 0x9D, 0xFF, 0xF0, 0xFF,
+	0xF3, 0xFF, 0xA3, 0xFF, 0x70, 0xFF, 0x60, 0x00, 0x62, 0x03, 0x45,
+	0x08, 0x62, 0x0D, 0x6C, 0x10, 0xDE, 0x0F, 0x00, 0x0C, 0xB8, 0x06,
+	0x46, 0x02, 0xEA, 0xFF, 0x6E, 0xFF, 0xC0, 0xFF, 0xFD, 0xFF, 0x00,
+	0x00, 0xDE, 0xFF, 0x83, 0xFF, 0x94, 0xFF, 0x2F, 0x01, 0xF4, 0x04,
+	0x2B, 0x0A, 0xCE, 0x0E, 0xAA, 0x10, 0xCE, 0x0E, 0x2B, 0x0A, 0xF4,
+	0x04, 0x2F, 0x01, 0x94, 0xFF, 0x83, 0xFF, 0xDE, 0xFF, 0xFD, 0xFF,
+	0xC0, 0xFF, 0x6E, 0xFF, 0xEA, 0xFF, 0x46, 0x02, 0xB8, 0x06, 0x00,
+	0x0C, 0xDE, 0x0F, 0x6C, 0x10, 0x62, 0x0D, 0x45, 0x08, 0x62, 0x03,
+	0x60, 0x00, 0x70, 0xFF, 0xA3, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0x9D,
+	0xFF, 0x73, 0xFF, 0x7F, 0x00, 0xA4, 0x03, 0x99, 0x08, 0xA7, 0x0D,
+	0x80, 0x10, 0xB7, 0x0F, 0xB1, 0x0B, 0x67, 0x06, 0x10, 0x02, 0xD7,
+	0xFF, 0x70, 0xFF, 0xC6, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xDA, 0xFF,
+	0x7E, 0xFF, 0x9F, 0xFF, 0x5A, 0x01, 0x40, 0x05, 0x7F, 0x0A, 0x05,
+	0x0F, 0xA9, 0x10, 0x95, 0x0E, 0xD8, 0x09, 0xAB, 0x04, 0x06, 0x01,
+	0x8A, 0xFF, 0x88, 0xFF, 0xE3, 0xFF, 0xFB, 0xFF, 0xBA, 0xFF, 0x6D,
+	0xFF, 0xFF, 0xFF, 0x7E, 0x02, 0x0A, 0x07, 0x4D, 0x0C, 0x02, 0x10,
+	0x55, 0x10, 0x1B, 0x0D, 0xF1, 0x07, 0x23, 0x03, 0x44, 0x00, 0x6D,
+	0xFF, 0xA9, 0xFF, 0xF6, 0xFF, 0xED, 0xFF, 0x97, 0xFF, 0x77, 0xFF,
+	0xA0, 0x00, 0xE7, 0x03, 0xED, 0x08, 0xE9, 0x0D, 0x90, 0x10, 0x8C,
+	0x0F, 0x62, 0x0B, 0x17, 0x06, 0xDD, 0x01, 0xC6, 0xFF, 0x73, 0xFF,
+	0xCB, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD5, 0xFF, 0x79, 0xFF, 0xAB,
+	0xFF, 0x87, 0x01, 0x8C, 0x05, 0xD1, 0x0A, 0x38, 0x0F, 0xA3, 0x10,
+	0x59, 0x0E, 0x84, 0x09, 0x63, 0x04, 0xDF, 0x00, 0x82, 0xFF, 0x8D,
+	0xFF, 0xE7, 0xFF, 0xF9, 0xFF, 0xB4, 0xFF, 0x6C, 0xFF, 0x16, 0x00,
+	0xB7, 0x02, 0x5C, 0x07, 0x99, 0x0C, 0x23, 0x10, 0x3B, 0x10, 0xD3,
+	0x0C, 0x9D, 0x07, 0xE6, 0x02, 0x29, 0x00, 0x6C, 0xFF, 0xB0, 0xFF,
+	0xF8, 0xFF, 0xEA, 0xFF, 0x92, 0xFF, 0x7D, 0xFF, 0xC2, 0x00, 0x2C,
+	0x04, 0x42, 0x09, 0x29, 0x0E, 0x9C, 0x10, 0x5E, 0x0F, 0x11, 0x0B,
+	0xC8, 0x05, 0xAC, 0x01, 0xB6, 0xFF, 0x76, 0xFF, 0xD1, 0xFF, 0xFF,
+	0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0x76, 0xFF, 0xB9, 0xFF, 0xB7, 0x01,
+	0xDA, 0x05, 0x23, 0x0B, 0x68, 0x0F, 0x9A, 0x10, 0x1B, 0x0E, 0x2F,
+	0x09, 0x1C, 0x04, 0xBB, 0x00, 0x7B, 0xFF, 0x93, 0xFF, 0xEA, 0xFF,
+	0xF7, 0xFF, 0xAE, 0xFF, 0x6C, 0xFF, 0x2F, 0x00, 0xF3, 0x02, 0xAF,
+	0x07, 0xE3, 0x0C, 0x41, 0x10, 0x1C, 0x10, 0x88, 0x0C, 0x4A, 0x07,
+	0xAA, 0x02, 0x11, 0x00, 0x6C, 0xFF, 0xB6, 0xFF, 0xFA, 0xFF, 0xE6,
+	0xFF, 0x8C, 0xFF, 0x84, 0xFF, 0xE7, 0x00, 0x73, 0x04, 0x96, 0x09,
+	0x67, 0x0E, 0xA5, 0x10, 0x2D, 0x0F, 0xBF, 0x0A, 0x7B, 0x05, 0x7D,
+	0x01, 0xA8, 0xFF, 0x7A, 0xFF, 0xD6, 0xFF, 0x00, 0x00, 0xFE, 0xFF,
+	0xCA, 0xFF, 0x72, 0xFF, 0xC9, 0xFF, 0xE8, 0x01, 0x29, 0x06, 0x73,
+	0x0B, 0x96, 0x0F, 0x8D, 0x10, 0xDB, 0x0D, 0xDB, 0x08, 0xD8, 0x03,
+	0x98, 0x00, 0x76, 0xFF, 0x99, 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xA8,
+	0xFF, 0x6E, 0xFF, 0x4A, 0x00, 0x31, 0x03, 0x03, 0x08, 0x2B, 0x0D,
+	0x5B, 0x10, 0xFB, 0x0F, 0x3C, 0x0C, 0xF7, 0x06, 0x71, 0x02, 0xFA,
+	0xFF, 0x6D, 0xFF, 0xBC, 0xFF, 0xFC, 0xFF, 0xE2, 0xFF, 0x87, 0xFF,
+	0x8C, 0xFF, 0x0F, 0x01, 0xBB, 0x04, 0xEA, 0x09, 0xA2, 0x0E, 0xA9,
+	0x10, 0xF9, 0x0E, 0x6C, 0x0A, 0x2F, 0x05, 0x50, 0x01, 0x9C, 0xFF,
+	0x7F, 0xFF, 0xDB, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC5, 0xFF, 0x70,
+	0xFF, 0xDB, 0xFF, 0x1C, 0x02, 0x79, 0x06, 0xC3, 0x0B, 0xC0, 0x0F,
+	0x7C, 0x10, 0x98, 0x0D, 0x86, 0x08, 0x95, 0x03, 0x78, 0x00, 0x72,
+	0xFF, 0x9F, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xA2, 0xFF, 0x70, 0xFF,
+	0x67, 0x00, 0x71, 0x03, 0x57, 0x08, 0x71, 0x0D, 0x71, 0x10, 0xD6,
+	0x0F, 0xEF, 0x0B, 0xA6, 0x06, 0x3A, 0x02, 0xE6, 0xFF, 0x6E, 0xFF,
+	0xC1, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDD, 0xFF, 0x81, 0xFF, 0x96,
+	0xFF, 0x38, 0x01, 0x05, 0x05, 0x3E, 0x0A, 0xDA, 0x0E, 0xAA, 0x10,
+	0xC2, 0x0E, 0x19, 0x0A, 0xE4, 0x04, 0x25, 0x01, 0x91, 0xFF, 0x84,
+	0xFF, 0xDF, 0xFF, 0xFC, 0xFF, 0xBF, 0xFF, 0x6E, 0xFF, 0xEF, 0xFF,
+	0x52, 0x02, 0xCA, 0x06, 0x11, 0x0C, 0xE6, 0x0F, 0x68, 0x10, 0x52,
+	0x0D, 0x32, 0x08, 0x54, 0x03, 0x5A, 0x00, 0x6F, 0xFF, 0xA5, 0xFF,
+	0xF4, 0xFF, 0xF0, 0xFF, 0x9C, 0xFF, 0x74, 0xFF, 0x86, 0x00, 0xB3,
+	0x03, 0xAC, 0x08, 0xB6, 0x0D, 0x84, 0x10, 0xAD, 0x0F, 0xA0, 0x0B,
+	0x55, 0x06, 0x05, 0x02, 0xD3, 0xFF, 0x71, 0xFF, 0xC7, 0xFF, 0xFE,
+	0xFF, 0x00, 0x00, 0xD9, 0xFF, 0x7D, 0xFF, 0xA1, 0xFF, 0x64, 0x01,
+	0x50, 0x05, 0x91, 0x0A, 0x10, 0x0F, 0xA8, 0x10, 0x88, 0x0E, 0xC5,
+	0x09, 0x9B, 0x04, 0xFD, 0x00, 0x88, 0xFF, 0x89, 0xFF, 0xE4, 0xFF,
+	0xFB, 0xFF, 0xB9, 0xFF, 0x6C, 0xFF, 0x04, 0x00, 0x8A, 0x02, 0x1C,
+	0x07, 0x5E, 0x0C, 0x0A, 0x10, 0x50, 0x10, 0x0B, 0x0D, 0xDE, 0x07,
+	0x15, 0x03, 0x3E, 0x00, 0x6D, 0xFF, 0xAB, 0xFF, 0xF6, 0xFF, 0xEC,
+	0xFF, 0x96, 0xFF, 0x78, 0xFF, 0xA7, 0x00, 0xF6, 0x03, 0x00, 0x09,
+	0xF8, 0x0D, 0x93, 0x10, 0x82, 0x0F, 0x50, 0x0B, 0x06, 0x06, 0xD2,
+	0x01, 0xC2, 0xFF, 0x74, 0xFF, 0xCD, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+	0xD4, 0xFF, 0x79, 0xFF, 0xAE, 0xFF, 0x91, 0x01, 0x9D, 0x05, 0xE3,
+	0x0A, 0x43, 0x0F, 0xA1, 0x10, 0x4C, 0x0E, 0x71, 0x09, 0x53, 0x04,
+	0xD7, 0x00, 0x80, 0xFF, 0x8E, 0xFF, 0xE8, 0xFF, 0xF9, 0xFF, 0xB3,
+	0xFF, 0x6C, 0xFF, 0x1C, 0x00, 0xC4, 0x02, 0x6F, 0x07, 0xAA, 0x0C,
+	0x2A, 0x10, 0x34, 0x10, 0xC2, 0x0C, 0x8A, 0x07, 0xD8, 0x02, 0x24,
+	0x00, 0x6C, 0xFF, 0xB1, 0xFF, 0xF8, 0xFF, 0xE9, 0xFF, 0x90, 0xFF,
+	0x7E, 0xFF, 0xCB, 0x00, 0x3B, 0x04, 0x55, 0x09, 0x37, 0x0E, 0x9E,
+	0x10, 0x53, 0x0F, 0xFF, 0x0A, 0xB7, 0x05, 0xA1, 0x01, 0xB3, 0xFF,
+	0x77, 0xFF, 0xD2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCE, 0xFF, 0x75,
+	0xFF, 0xBD, 0xFF, 0xC1, 0x01, 0xEB, 0x05, 0x35, 0x0B, 0x73, 0x0F,
+	0x97, 0x10, 0x0D, 0x0E, 0x1C, 0x09, 0x0D, 0x04, 0xB3, 0x00, 0x7A,
+	0xFF, 0x94, 0xFF, 0xEB, 0xFF, 0xF7, 0xFF, 0xAD, 0xFF, 0x6D, 0xFF,
+	0x35, 0x00, 0x01, 0x03, 0xC2, 0x07, 0xF3, 0x0C, 0x47, 0x10, 0x15,
+	0x10, 0x78, 0x0C, 0x37, 0x07, 0x9D, 0x02, 0x0C, 0x00, 0x6C, 0xFF,
+	0xB7, 0xFF, 0xFA, 0xFF, 0xE5, 0xFF, 0x8B, 0xFF, 0x85, 0xFF, 0xF0,
+	0x00, 0x83, 0x04, 0xA9, 0x09, 0x74, 0x0E, 0xA6, 0x10, 0x21, 0x0F,
+	0xAD, 0x0A, 0x6A, 0x05, 0x73, 0x01, 0xA5, 0xFF, 0x7B, 0xFF, 0xD7,
+	0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC9, 0xFF, 0x72, 0xFF, 0xCD, 0xFF,
+	0xF4, 0x01, 0x3B, 0x06, 0x85, 0x0B, 0x9F, 0x0F, 0x89, 0x10, 0xCC,
+	0x0D, 0xC8, 0x08, 0xC9, 0x03, 0x91, 0x00, 0x75, 0xFF, 0x9A, 0xFF,
+	0xEF, 0xFF, 0xF5, 0xFF, 0xA7, 0xFF, 0x6E, 0xFF, 0x50, 0x00, 0x3F,
+	0x03, 0x16, 0x08, 0x3B, 0x0D, 0x60, 0x10, 0xF3, 0x0F, 0x2B, 0x0C,
+	0xE5, 0x06, 0x65, 0x02, 0xF6, 0xFF, 0x6D, 0xFF, 0xBD, 0xFF, 0xFC,
+	0xFF, 0xE1, 0xFF, 0x85, 0xFF, 0x8E, 0xFF, 0x18, 0x01, 0xCB, 0x04,
+	0xFD, 0x09, 0xAF, 0x0E, 0xAA, 0x10, 0xED, 0x0E, 0x5A, 0x0A, 0x1E,
+	0x05, 0x46, 0x01, 0x9A, 0xFF, 0x80, 0xFF, 0xDC, 0xFF, 0x00, 0x00,
+	0xFD, 0xFF, 0xC3, 0xFF, 0x6F, 0xFF, 0xDF, 0xFF, 0x28, 0x02, 0x8B,
+	0x06, 0xD5, 0x0B, 0xC9, 0x0F, 0x78, 0x10, 0x88, 0x0D, 0x73, 0x08,
+	0x86, 0x03, 0x71, 0x00, 0x71, 0xFF, 0xA0, 0xFF, 0xF2, 0xFF, 0xF2,
+	0xFF, 0xA1, 0xFF, 0x71, 0xFF, 0x6E, 0x00, 0x7F, 0x03, 0x6A, 0x08,
+	0x81, 0x0D, 0x76, 0x10, 0xCD, 0x0F, 0xDD, 0x0B, 0x94, 0x06, 0x2E,
+	0x02, 0xE1, 0xFF, 0x6F, 0xFF, 0xC3, 0xFF, 0xFD, 0xFF, 0x00, 0x00,
+	0xDC, 0xFF, 0x80, 0xFF, 0x98, 0xFF, 0x42, 0x01, 0x16, 0x05, 0x50,
+	0x0A, 0xE7, 0x0E, 0xAA, 0x10, 0xB5, 0x0E, 0x06, 0x0A, 0xD3, 0x04,
+	0x1C, 0x01, 0x8F, 0xFF, 0x85, 0xFF, 0xE0, 0xFF, 0xFC, 0xFF, 0xBE,
+	0xFF, 0x6D, 0xFF, 0xF3, 0xFF, 0x5E, 0x02, 0xDC, 0x06, 0x23, 0x0C,
+	0xEF, 0x0F, 0x63, 0x10, 0x43, 0x0D, 0x1F, 0x08, 0x46, 0x03, 0x53,
+	0x00, 0x6E, 0xFF, 0xA6, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0x9B, 0xFF,
+	0x75, 0xFF, 0x8D, 0x00, 0xC1, 0x03, 0xBE, 0x08, 0xC4, 0x0D, 0x88,
+	0x10, 0xA4, 0x0F, 0x8E, 0x0B, 0x43, 0x06, 0xF9, 0x01, 0xCF, 0xFF,
+	0x71, 0xFF, 0xC8, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD8, 0xFF, 0x7C,
+	0xFF, 0xA4, 0xFF, 0x6E, 0x01, 0x61, 0x05, 0xA3, 0x0A, 0x1C, 0x0F,
+	0xA7, 0x10, 0x7B, 0x0E, 0xB2, 0x09, 0x8B, 0x04, 0xF4, 0x00, 0x86,
+	0xFF, 0x8A, 0xFF, 0xE4, 0xFF, 0xFA, 0xFF, 0xB8, 0xFF, 0x6C, 0xFF,
+	0x09, 0x00, 0x97, 0x02, 0x2E, 0x07, 0x6F, 0x0C, 0x11, 0x10, 0x4A,
+	0x10, 0xFB, 0x0C, 0xCB, 0x07, 0x07, 0x03, 0x38, 0x00, 0x6D, 0xFF,
+	0xAC, 0xFF, 0xF7, 0xFF, 0xEC, 0xFF, 0x95, 0xFF, 0x79, 0xFF, 0xAF,
+	0x00, 0x05, 0x04, 0x13, 0x09, 0x06, 0x0E, 0x96, 0x10, 0x78, 0x0F,
+	0x3E, 0x0B, 0xF4, 0x05, 0xC7, 0x01, 0xBF, 0xFF, 0x74, 0xFF, 0xCE,
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0xFF, 0x78, 0xFF, 0xB1, 0xFF,
+	0x9C, 0x01, 0xAE, 0x05, 0xF6, 0x0A, 0x4E, 0x0F, 0x9F, 0x10, 0x3E,
+	0x0E, 0x5E, 0x09, 0x43, 0x04, 0xCF, 0x00, 0x7F, 0xFF, 0x90, 0xFF,
+	0xE8, 0xFF, 0xF9, 0xFF, 0xB2, 0xFF, 0x6C, 0xFF, 0x21, 0x00, 0xD2,
+	0x02, 0x81, 0x07, 0xBA, 0x0C, 0x31, 0x10, 0x2E, 0x10, 0xB2, 0x0C,
+	0x78, 0x07, 0xCB, 0x02, 0x1E, 0x00, 0x6C, 0xFF, 0xB2, 0xFF, 0xF9,
+	0xFF, 0xE8, 0xFF, 0x8F, 0xFF, 0x80, 0xFF, 0xD3, 0x00, 0x4B, 0x04,
+	0x67, 0x09, 0x45, 0x0E, 0xA0, 0x10, 0x48, 0x0F, 0xEC, 0x0A, 0xA6,
+	0x05, 0x97, 0x01, 0xB0, 0xFF, 0x78, 0xFF, 0xD3, 0xFF, 0x00, 0x00,
+	0xFF, 0xFF, 0xCD, 0xFF, 0x74, 0xFF, 0xC0, 0xFF, 0xCC, 0x01, 0xFD,
+	0x05, 0x47, 0x0B, 0x7D, 0x0F, 0x94, 0x10, 0xFF, 0x0D, 0x0A, 0x09,
+	0xFE, 0x03, 0xAB, 0x00, 0x79, 0xFF, 0x95, 0xFF, 0xEC, 0xFF, 0xF7,
+	0xFF, 0xAC, 0xFF, 0x6D, 0xFF, 0x3B, 0x00, 0x0E, 0x03, 0xD5, 0x07,
+	0x03, 0x0D, 0x4D, 0x10, 0x0E, 0x10, 0x67, 0x0C, 0x25, 0x07, 0x91,
+	0x02, 0x07, 0x00, 0x6C, 0xFF, 0xB8, 0xFF, 0xFB, 0xFF, 0xE4, 0xFF,
+	0x89, 0xFF, 0x87, 0xFF, 0xF9, 0x00, 0x93, 0x04, 0xBC, 0x09, 0x82,
+	0x0E, 0xA7, 0x10, 0x16, 0x0F, 0x9A, 0x0A, 0x59, 0x05, 0x69, 0x01,
+	0xA3, 0xFF, 0x7C, 0xFF, 0xD8, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC8,
+	0xFF, 0x71, 0xFF, 0xD1, 0xFF, 0xFF, 0x01, 0x4C, 0x06, 0x97, 0x0B,
+	0xA9, 0x0F, 0x86, 0x10, 0xBD, 0x0D, 0xB5, 0x08, 0xBA, 0x03, 0x8A,
+	0x00, 0x74, 0xFF, 0x9B, 0xFF, 0xEF, 0xFF, 0xF4, 0xFF, 0xA5, 0xFF,
+	0x6F, 0xFF, 0x57, 0x00, 0x4D, 0x03, 0x29, 0x08, 0x4B, 0x0D, 0x65,
+	0x10, 0xEB, 0x0F, 0x1A, 0x0C, 0xD3, 0x06, 0x58, 0x02, 0xF1, 0xFF,
+	0x6D, 0xFF, 0xBE, 0xFF, 0xFC, 0xFF, 0xE0, 0xFF, 0x84, 0xFF, 0x90,
+	0xFF, 0x21, 0x01, 0xDC, 0x04, 0x10, 0x0A, 0xBB, 0x0E, 0xAA, 0x10,
+	0xE1, 0x0E, 0x47, 0x0A, 0x0D, 0x05, 0x3D, 0x01, 0x97, 0xFF, 0x81,
+	0xFF, 0xDD, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC2, 0xFF, 0x6F, 0xFF,
+	0xE4, 0xFF, 0x34, 0x02, 0x9D, 0x06, 0xE6, 0x0B, 0xD1, 0x0F, 0x73,
+	0x10, 0x79, 0x0D, 0x61, 0x08, 0x78, 0x03, 0x6A, 0x00, 0x70, 0xFF,
+	0xA1, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0x9F, 0xFF, 0x72, 0xFF, 0x74,
+	0x00, 0x8E, 0x03, 0x7D, 0x08, 0x90, 0x0D, 0x7A, 0x10, 0xC4, 0x0F,
+	0xCC, 0x0B, 0x82, 0x06, 0x22, 0x02, 0xDD, 0xFF, 0x6F, 0xFF, 0xC4,
+	0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDB, 0xFF, 0x7F, 0xFF, 0x9B, 0xFF,
+	0x4B, 0x01, 0x26, 0x05, 0x63, 0x0A, 0xF3, 0x0E, 0xAA, 0x10, 0xA8,
+	0x0E, 0xF4, 0x09, 0xC3, 0x04, 0x13, 0x01, 0x8D, 0xFF, 0x86, 0xFF,
+	0xE1, 0xFF, 0xFC, 0xFF, 0xBC, 0xFF, 0x6D, 0xFF, 0xF8, 0xFF, 0x6B,
+	0x02, 0xEE, 0x06, 0x34, 0x0C, 0xF7, 0x0F, 0x5D, 0x10, 0x33, 0x0D,
+	0x0D, 0x08, 0x38, 0x03, 0x4D, 0x00, 0x6E, 0xFF, 0xA7, 0xFF, 0xF5,
+	0xFF, 0xEE, 0xFF, 0x99, 0xFF, 0x76, 0xFF, 0x94, 0x00, 0xD0, 0x03,
+	0xD1, 0x08, 0xD3, 0x0D, 0x8B, 0x10, 0x9A, 0x0F, 0x7C, 0x0B, 0x32,
+	0x06, 0xEE, 0x01, 0xCB, 0xFF, 0x72, 0xFF, 0xCA, 0xFF, 0xFE, 0xFF,
+	0x00, 0x00, 0xD6, 0xFF, 0x7B, 0xFF, 0xA7, 0xFF, 0x78, 0x01, 0x72,
+	0x05, 0xB6, 0x0A, 0x27, 0x0F, 0xA5, 0x10, 0x6E, 0x0E, 0xA0, 0x09,
+	0x7B, 0x04, 0xEC, 0x00, 0x85, 0xFF, 0x8B, 0xFF, 0xE5, 0xFF, 0xFA,
+	0xFF, 0xB6, 0xFF, 0x6C, 0xFF, 0x0E, 0x00, 0xA4, 0x02, 0x41, 0x07,
+	0x80, 0x0C, 0x19, 0x10, 0x44, 0x10, 0xEB, 0x0C, 0xB9, 0x07, 0xFA,
+	0x02, 0x32, 0x00, 0x6D, 0xFF, 0xAE, 0xFF, 0xF7, 0xFF, 0xEB, 0xFF,
+	0x93, 0xFF, 0x7B, 0xFF, 0xB7, 0x00, 0x15, 0x04, 0x26, 0x09, 0x14,
+	0x0E, 0x98, 0x10, 0x6D, 0x0F, 0x2C, 0x0B, 0xE3, 0x05, 0xBC, 0x01,
+	0xBB, 0xFF, 0x75, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1,
+	0xFF, 0x77, 0xFF, 0xB5, 0xFF, 0xA6, 0x01, 0xC0, 0x05, 0x08, 0x0B,
+	0x58, 0x0F, 0x9D, 0x10, 0x30, 0x0E, 0x4B, 0x09, 0x34, 0x04, 0xC6,
+	0x00, 0x7D, 0xFF, 0x91, 0xFF, 0xE9, 0xFF, 0xF8, 0xFF, 0xB0, 0xFF,
+	0x6C, 0xFF, 0x27, 0x00, 0xDF, 0x02, 0x94, 0x07, 0xCA, 0x0C, 0x37,
+	0x10, 0x27, 0x10, 0xA1, 0x0C, 0x65, 0x07, 0xBE, 0x02, 0x19, 0x00,
+	0x6C, 0xFF, 0xB4, 0xFF, 0xF9, 0xFF, 0xE7, 0xFF, 0x8E, 0xFF, 0x81,
+	0xFF, 0xDB, 0x00, 0x5B, 0x04, 0x7A, 0x09, 0x53, 0x0E, 0xA2, 0x10,
+	0x3D, 0x0F, 0xDA, 0x0A, 0x95, 0x05, 0x8C, 0x01, 0xAD, 0xFF, 0x79,
+	0xFF, 0xD4, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xCC, 0xFF, 0x73, 0xFF,
+	0xC4, 0xFF, 0xD7, 0x01, 0x0E, 0x06, 0x59, 0x0B, 0x87, 0x0F, 0x91,
+	0x10, 0xF0, 0x0D, 0xF7, 0x08, 0xEF, 0x03, 0xA3, 0x00, 0x78, 0xFF,
+	0x97, 0xFF, 0xED, 0xFF, 0xF6, 0xFF, 0xAA, 0xFF, 0x6D, 0xFF, 0x41,
+	0x00, 0x1C, 0x03, 0xE7, 0x07, 0x13, 0x0D, 0x52, 0x10, 0x06, 0x10,
+	0x56, 0x0C, 0x13, 0x07, 0x84, 0x02, 0x02, 0x00, 0x6D, 0xFF, 0xBA,
+	0xFF, 0xFB, 0xFF, 0xE3, 0xFF, 0x88, 0xFF, 0x89, 0xFF, 0x01, 0x01,
+	0xA3, 0x04, 0xCE, 0x09, 0x8F, 0x0E, 0xA8, 0x10, 0x0A, 0x0F, 0x88,
+	0x0A, 0x48, 0x05, 0x5F, 0x01, 0xA0, 0xFF, 0x7D, 0xFF, 0xD9, 0xFF,
+	0x00, 0x00, 0xFE, 0xFF, 0xC7, 0xFF, 0x70, 0xFF, 0xD5, 0xFF, 0x0B,
+	0x02, 0x5E, 0x06, 0xA9, 0x0B, 0xB2, 0x0F, 0x82, 0x10, 0xAE, 0x0D,
+	0xA2, 0x08, 0xAB, 0x03, 0x82, 0x00, 0x73, 0xFF, 0x9D, 0xFF, 0xF0,
+	0xFF, 0xF3, 0xFF, 0xA4, 0xFF, 0x6F, 0xFF, 0x5D, 0x00, 0x5B, 0x03,
+	0x3B, 0x08, 0x5A, 0x0D, 0x6A, 0x10, 0xE2, 0x0F, 0x09, 0x0C, 0xC1,
+	0x06, 0x4C, 0x02, 0xEC, 0xFF, 0x6E, 0xFF, 0xC0, 0xFF, 0xFC, 0xFF,
+	0xDF, 0xFF, 0x83, 0xFF, 0x93, 0xFF, 0x2A, 0x01, 0xEC, 0x04, 0x22,
+	0x0A, 0xC8, 0x0E, 0xAB, 0x10, 0xD4, 0x0E, 0x35, 0x0A, 0xFD, 0x04,
+	0x33, 0x01, 0x95, 0xFF, 0x82, 0xFF, 0xDE, 0xFF, 0x00, 0x00, 0xFD,
+	0xFF, 0xC1, 0xFF, 0x6E, 0xFF, 0xE8, 0xFF, 0x40, 0x02, 0xAF, 0x06,
+	0xF7, 0x0B, 0xDA, 0x0F, 0x6F, 0x10, 0x6A, 0x0D, 0x4E, 0x08, 0x6A,
+	0x03, 0x64, 0x00, 0x70, 0xFF, 0xA3, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF,
+	0x9E, 0xFF, 0x72, 0xFF, 0x7B, 0x00, 0x9C, 0x03, 0x90, 0x08, 0x9F,
+	0x0D, 0x7E, 0x10, 0xBB, 0x0F, 0xBA, 0x0B, 0x70, 0x06, 0x16, 0x02,
+	0xD9, 0xFF, 0x70, 0xFF, 0xC5, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xDA,
+	0xFF, 0x7E, 0xFF, 0x9D, 0xFF, 0x55, 0x01, 0x37, 0x05, 0x75, 0x0A,
+	0xFF, 0x0E, 0xA9, 0x10, 0x9C, 0x0E, 0xE1, 0x09, 0xB3, 0x04, 0x0A,
+	0x01, 0x8B, 0xFF, 0x87, 0xFF, 0xE2, 0xFF, 0xFB, 0xFF, 0xBB, 0xFF,
+	0x6D, 0xFF, 0xFD, 0xFF, 0x77, 0x02, 0x01, 0x07, 0x45, 0x0C, 0xFF,
+	0x0F, 0x58, 0x10, 0x23, 0x0D, 0xFA, 0x07, 0x2A, 0x03, 0x47, 0x00,
+	0x6E, 0xFF, 0xA9, 0xFF, 0xF5, 0xFF, 0xED, 0xFF, 0x98, 0xFF, 0x77,
+	0xFF, 0x9C, 0x00, 0xDF, 0x03, 0xE4, 0x08, 0xE2, 0x0D, 0x8E, 0x10,
+	0x91, 0x0F, 0x6B, 0x0B, 0x20, 0x06, 0xE3, 0x01, 0xC8, 0xFF, 0x73,
+	0xFF, 0xCB, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD5, 0xFF, 0x7A, 0xFF,
+	0xAA, 0xFF, 0x82, 0x01, 0x83, 0x05, 0xC8, 0x0A, 0x32, 0x0F, 0xA4,
+	0x10, 0x60, 0x0E, 0x8D, 0x09, 0x6B, 0x04, 0xE3, 0x00, 0x83, 0xFF,
+	0x8D, 0xFF, 0xE6, 0xFF, 0xFA, 0xFF, 0xB5, 0xFF, 0x6C, 0xFF, 0x14,
+	0x00, 0xB1, 0x02, 0x53, 0x07, 0x91, 0x0C, 0x20, 0x10, 0x3E, 0x10,
+	0xDB, 0x0C, 0xA6, 0x07, 0xEC, 0x02, 0x2C, 0x00, 0x6C, 0xFF, 0xAF,
+	0xFF, 0xF8, 0xFF, 0xEA, 0xFF, 0x92, 0xFF, 0x7C, 0xFF, 0xBE, 0x00,
+	0x24, 0x04, 0x38, 0x09, 0x22, 0x0E, 0x9B, 0x10, 0x63, 0x0F, 0x1A,
+	0x0B, 0xD1, 0x05, 0xB1, 0x01, 0xB8, 0xFF, 0x76, 0xFF, 0xD0, 0xFF,
+	0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0x76, 0xFF, 0xB8, 0xFF, 0xB1,
+	0x01, 0xD1, 0x05, 0x1A, 0x0B, 0x63, 0x0F, 0x9B, 0x10, 0x22, 0x0E,
+	0x38, 0x09, 0x24, 0x04, 0xBE, 0x00, 0x7C, 0xFF, 0x92, 0xFF, 0xEA,
+	0xFF, 0xF8, 0xFF, 0xAF, 0xFF, 0x6C, 0xFF, 0x2C, 0x00, 0xEC, 0x02,
+	0xA6, 0x07, 0xDB, 0x0C, 0x3E, 0x10, 0x20, 0x10, 0x91, 0x0C, 0x53,
+	0x07, 0xB1, 0x02, 0x14, 0x00, 0x6C, 0xFF, 0xB5, 0xFF, 0xFA, 0xFF,
+	0xE6, 0xFF, 0x8D, 0xFF, 0x83, 0xFF, 0xE3, 0x00, 0x6B, 0x04, 0x8D,
+	0x09, 0x60, 0x0E, 0xA4, 0x10, 0x32, 0x0F, 0xC8, 0x0A, 0x83, 0x05,
+	0x82, 0x01, 0xAA, 0xFF, 0x7A, 0xFF, 0xD5, 0xFF, 0x00, 0x00, 0xFF,
+	0xFF, 0xCB, 0xFF, 0x73, 0xFF, 0xC8, 0xFF, 0xE3, 0x01, 0x20, 0x06,
+	0x6B, 0x0B, 0x91, 0x0F, 0x8E, 0x10, 0xE2, 0x0D, 0xE4, 0x08, 0xDF,
+	0x03, 0x9C, 0x00, 0x77, 0xFF, 0x98, 0xFF, 0xED, 0xFF, 0xF5, 0xFF,
+	0xA9, 0xFF, 0x6E, 0xFF, 0x47, 0x00, 0x2A, 0x03, 0xFA, 0x07, 0x23,
+	0x0D, 0x58, 0x10, 0xFF, 0x0F, 0x45, 0x0C, 0x01, 0x07, 0x77, 0x02,
+	0xFD, 0xFF, 0x6D, 0xFF, 0xBB, 0xFF, 0xFB, 0xFF, 0xE2, 0xFF, 0x87,
+	0xFF, 0x8B, 0xFF, 0x0A, 0x01, 0xB3, 0x04, 0xE1, 0x09, 0x9C, 0x0E,
+	0xA9, 0x10, 0xFF, 0x0E, 0x75, 0x0A, 0x37, 0x05, 0x55, 0x01, 0x9D,
+	0xFF, 0x7E, 0xFF, 0xDA, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC5, 0xFF,
+	0x70, 0xFF, 0xD9, 0xFF, 0x16, 0x02, 0x70, 0x06, 0xBA, 0x0B, 0xBB,
+	0x0F, 0x7E, 0x10, 0x9F, 0x0D, 0x90, 0x08, 0x9C, 0x03, 0x7B, 0x00,
+	0x72, 0xFF, 0x9E, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xA3, 0xFF, 0x70,
+	0xFF, 0x64, 0x00, 0x6A, 0x03, 0x4E, 0x08, 0x6A, 0x0D, 0x6F, 0x10,
+	0xDA, 0x0F, 0xF7, 0x0B, 0xAF, 0x06, 0x40, 0x02, 0xE8, 0xFF, 0x6E,
+	0xFF, 0xC1, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDE, 0xFF, 0x82, 0xFF,
+	0x95, 0xFF, 0x33, 0x01, 0xFD, 0x04, 0x35, 0x0A, 0xD4, 0x0E, 0xAB,
+	0x10, 0xC8, 0x0E, 0x22, 0x0A, 0xEC, 0x04, 0x2A, 0x01, 0x93, 0xFF,
+	0x83, 0xFF, 0xDF, 0xFF, 0xFC, 0xFF, 0xC0, 0xFF, 0x6E, 0xFF, 0xEC,
+	0xFF, 0x4C, 0x02, 0xC1, 0x06, 0x09, 0x0C, 0xE2, 0x0F, 0x6A, 0x10,
+	0x5A, 0x0D, 0x3B, 0x08, 0x5B, 0x03, 0x5D, 0x00, 0x6F, 0xFF, 0xA4,
+	0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0x9D, 0xFF, 0x73, 0xFF, 0x82, 0x00,
+	0xAB, 0x03, 0xA2, 0x08, 0xAE, 0x0D, 0x82, 0x10, 0xB2, 0x0F, 0xA9,
+	0x0B, 0x5E, 0x06, 0x0B, 0x02, 0xD5, 0xFF, 0x70, 0xFF, 0xC7, 0xFF,
+	0xFE, 0xFF, 0x00, 0x00, 0xD9, 0xFF, 0x7D, 0xFF, 0xA0, 0xFF, 0x5F,
+	0x01, 0x48, 0x05, 0x88, 0x0A, 0x0A, 0x0F, 0xA8, 0x10, 0x8F, 0x0E,
+	0xCE, 0x09, 0xA3, 0x04, 0x01, 0x01, 0x89, 0xFF, 0x88, 0xFF, 0xE3,
+	0xFF, 0xFB, 0xFF, 0xBA, 0xFF, 0x6D, 0xFF, 0x02, 0x00, 0x84, 0x02,
+	0x13, 0x07, 0x56, 0x0C, 0x06, 0x10, 0x52, 0x10, 0x13, 0x0D, 0xE7,
+	0x07, 0x1C, 0x03, 0x41, 0x00, 0x6D, 0xFF, 0xAA, 0xFF, 0xF6, 0xFF,
+	0xED, 0xFF, 0x97, 0xFF, 0x78, 0xFF, 0xA3, 0x00, 0xEF, 0x03, 0xF7,
+	0x08, 0xF0, 0x0D, 0x91, 0x10, 0x87, 0x0F, 0x59, 0x0B, 0x0E, 0x06,
+	0xD7, 0x01, 0xC4, 0xFF, 0x73, 0xFF, 0xCC, 0xFF, 0xFF, 0xFF, 0x00,
+	0x00, 0xD4, 0xFF, 0x79, 0xFF, 0xAD, 0xFF, 0x8C, 0x01, 0x95, 0x05,
+	0xDA, 0x0A, 0x3D, 0x0F, 0xA2, 0x10, 0x53, 0x0E, 0x7A, 0x09, 0x5B,
+	0x04, 0xDB, 0x00, 0x81, 0xFF, 0x8E, 0xFF, 0xE7, 0xFF, 0xF9, 0xFF,
+	0xB4, 0xFF, 0x6C, 0xFF, 0x19, 0x00, 0xBE, 0x02, 0x65, 0x07, 0xA1,
+	0x0C, 0x27, 0x10, 0x37, 0x10, 0xCA, 0x0C, 0x94, 0x07, 0xDF, 0x02,
+	0x27, 0x00, 0x6C, 0xFF, 0xB0, 0xFF, 0xF8, 0xFF, 0xE9, 0xFF, 0x91,
+	0xFF, 0x7D, 0xFF, 0xC6, 0x00, 0x34, 0x04, 0x4B, 0x09, 0x30, 0x0E,
+	0x9D, 0x10, 0x58, 0x0F, 0x08, 0x0B, 0xC0, 0x05, 0xA6, 0x01, 0xB5,
+	0xFF, 0x77, 0xFF, 0xD1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF,
+	0x75, 0xFF, 0xBB, 0xFF, 0xBC, 0x01, 0xE3, 0x05, 0x2C, 0x0B, 0x6D,
+	0x0F, 0x98, 0x10, 0x14, 0x0E, 0x26, 0x09, 0x15, 0x04, 0xB7, 0x00,
+	0x7B, 0xFF, 0x93, 0xFF, 0xEB, 0xFF, 0xF7, 0xFF, 0xAE, 0xFF, 0x6D,
+	0xFF, 0x32, 0x00, 0xFA, 0x02, 0xB9, 0x07, 0xEB, 0x0C, 0x44, 0x10,
+	0x19, 0x10, 0x80, 0x0C, 0x41, 0x07, 0xA4, 0x02, 0x0E, 0x00, 0x6C,
+	0xFF, 0xB6, 0xFF, 0xFA, 0xFF, 0xE5, 0xFF, 0x8B, 0xFF, 0x85, 0xFF,
+	0xEC, 0x00, 0x7B, 0x04, 0xA0, 0x09, 0x6E, 0x0E, 0xA5, 0x10, 0x27,
+	0x0F, 0xB6, 0x0A, 0x72, 0x05, 0x78, 0x01, 0xA7, 0xFF, 0x7B, 0xFF,
+	0xD6, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xCA, 0xFF, 0x72, 0xFF, 0xCB,
+	0xFF, 0xEE, 0x01, 0x32, 0x06, 0x7C, 0x0B, 0x9A, 0x0F, 0x8B, 0x10,
+	0xD3, 0x0D, 0xD1, 0x08, 0xD0, 0x03, 0x94, 0x00, 0x76, 0xFF, 0x99,
+	0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xA7, 0xFF, 0x6E, 0xFF, 0x4D, 0x00,
+	0x38, 0x03, 0x0D, 0x08, 0x33, 0x0D, 0x5D, 0x10, 0xF7, 0x0F, 0x34,
+	0x0C, 0xEE, 0x06, 0x6B, 0x02, 0xF8, 0xFF, 0x6D, 0xFF, 0xBC, 0xFF,
+	0xFC, 0xFF, 0xE1, 0xFF, 0x86, 0xFF, 0x8D, 0xFF, 0x13, 0x01, 0xC3,
+	0x04, 0xF4, 0x09, 0xA8, 0x0E, 0xAA, 0x10, 0xF3, 0x0E, 0x63, 0x0A,
+	0x26, 0x05, 0x4B, 0x01, 0x9B, 0xFF, 0x7F, 0xFF, 0xDB, 0xFF, 0x00,
+	0x00, 0xFD, 0xFF, 0xC4, 0xFF, 0x6F, 0xFF, 0xDD, 0xFF, 0x22, 0x02,
+	0x82, 0x06, 0xCC, 0x0B, 0xC4, 0x0F, 0x7A, 0x10, 0x90, 0x0D, 0x7D,
+	0x08, 0x8E, 0x03, 0x74, 0x00, 0x72, 0xFF, 0x9F, 0xFF, 0xF1, 0xFF,
+	0xF2, 0xFF, 0xA1, 0xFF, 0x70, 0xFF, 0x6A, 0x00, 0x78, 0x03, 0x61,
+	0x08, 0x79, 0x0D, 0x73, 0x10, 0xD1, 0x0F, 0xE6, 0x0B, 0x9D, 0x06,
+	0x34, 0x02, 0xE4, 0xFF, 0x6F, 0xFF, 0xC2, 0xFF, 0xFD, 0xFF, 0x00,
+	0x00, 0xDD, 0xFF, 0x81, 0xFF, 0x97, 0xFF, 0x3D, 0x01, 0x0D, 0x05,
+	0x47, 0x0A, 0xE1, 0x0E, 0xAA, 0x10, 0xBB, 0x0E, 0x10, 0x0A, 0xDC,
+	0x04, 0x21, 0x01, 0x90, 0xFF, 0x84, 0xFF, 0xE0, 0xFF, 0xFC, 0xFF,
+	0xBE, 0xFF, 0x6D, 0xFF, 0xF1, 0xFF, 0x58, 0x02, 0xD3, 0x06, 0x1A,
+	0x0C, 0xEB, 0x0F, 0x65, 0x10, 0x4B, 0x0D, 0x29, 0x08, 0x4D, 0x03,
+	0x57, 0x00, 0x6F, 0xFF, 0xA5, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0x9B,
+	0xFF, 0x74, 0xFF, 0x8A, 0x00, 0xBA, 0x03, 0xB5, 0x08, 0xBD, 0x0D,
+	0x86, 0x10, 0xA9, 0x0F, 0x97, 0x0B, 0x4C, 0x06, 0xFF, 0x01, 0xD1,
+	0xFF, 0x71, 0xFF, 0xC8, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD8, 0xFF,
+	0x7C, 0xFF, 0xA3, 0xFF, 0x69, 0x01, 0x59, 0x05, 0x9A, 0x0A, 0x16,
+	0x0F, 0xA7, 0x10, 0x82, 0x0E, 0xBC, 0x09, 0x93, 0x04, 0xF9, 0x00,
+	0x87, 0xFF, 0x89, 0xFF, 0xE4, 0xFF, 0xFB, 0xFF, 0xB8, 0xFF, 0x6C,
+	0xFF, 0x07, 0x00, 0x91, 0x02, 0x25, 0x07, 0x67, 0x0C, 0x0E, 0x10,
+	0x4D, 0x10, 0x03, 0x0D, 0xD5, 0x07, 0x0E, 0x03, 0x3B, 0x00, 0x6D,
+	0xFF, 0xAC, 0xFF, 0xF7, 0xFF, 0xEC, 0xFF, 0x95, 0xFF, 0x79, 0xFF,
+	0xAB, 0x00, 0xFE, 0x03, 0x0A, 0x09, 0xFF, 0x0D, 0x94, 0x10, 0x7D,
+	0x0F, 0x47, 0x0B, 0xFD, 0x05, 0xCC, 0x01, 0xC0, 0xFF, 0x74, 0xFF,
+	0xCD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD3, 0xFF, 0x78, 0xFF, 0xB0,
+	0xFF, 0x97, 0x01, 0xA6, 0x05, 0xEC, 0x0A, 0x48, 0x0F, 0xA0, 0x10,
+	0x45, 0x0E, 0x67, 0x09, 0x4B, 0x04, 0xD3, 0x00, 0x80, 0xFF, 0x8F,
+	0xFF, 0xE8, 0xFF, 0xF9, 0xFF, 0xB2, 0xFF, 0x6C, 0xFF, 0x1E, 0x00,
+	0xCB, 0x02, 0x78, 0x07, 0xB2, 0x0C, 0x2E, 0x10, 0x31, 0x10, 0xBA,
+	0x0C, 0x81, 0x07, 0xD2, 0x02, 0x21, 0x00, 0x6C, 0xFF, 0xB2, 0xFF,
+	0xF9, 0xFF, 0xE8, 0xFF, 0x90, 0xFF, 0x7F, 0xFF, 0xCF, 0x00, 0x43,
+	0x04, 0x5E, 0x09, 0x3E, 0x0E, 0x9F, 0x10, 0x4E, 0x0F, 0xF6, 0x0A,
+	0xAE, 0x05, 0x9C, 0x01, 0xB1, 0xFF, 0x78, 0xFF, 0xD2, 0xFF, 0xFF,
+	0xFF, 0xFF, 0xFF, 0xCE, 0xFF, 0x74, 0xFF, 0xBF, 0xFF, 0xC7, 0x01,
+	0xF4, 0x05, 0x3E, 0x0B, 0x78, 0x0F, 0x96, 0x10, 0x06, 0x0E, 0x13,
+	0x09, 0x05, 0x04, 0xAF, 0x00, 0x79, 0xFF, 0x95, 0xFF, 0xEC, 0xFF,
+	0xF7, 0xFF, 0xAC, 0xFF, 0x6D, 0xFF, 0x38, 0x00, 0x07, 0x03, 0xCB,
+	0x07, 0xFB, 0x0C, 0x4A, 0x10, 0x11, 0x10, 0x6F, 0x0C, 0x2E, 0x07,
+	0x97, 0x02, 0x09, 0x00, 0x6C, 0xFF, 0xB8, 0xFF, 0xFA, 0xFF, 0xE4,
+	0xFF, 0x8A, 0xFF, 0x86, 0xFF, 0xF4, 0x00, 0x8B, 0x04, 0xB2, 0x09,
+	0x7B, 0x0E, 0xA7, 0x10, 0x1C, 0x0F, 0xA3, 0x0A, 0x61, 0x05, 0x6E,
+	0x01, 0xA4, 0xFF, 0x7C, 0xFF, 0xD8, 0xFF, 0x00, 0x00, 0xFE, 0xFF,
+	0xC8, 0xFF, 0x71, 0xFF, 0xCF, 0xFF, 0xF9, 0x01, 0x43, 0x06, 0x8E,
+	0x0B, 0xA4, 0x0F, 0x88, 0x10, 0xC4, 0x0D, 0xBE, 0x08, 0xC1, 0x03,
+	0x8D, 0x00, 0x75, 0xFF, 0x9B, 0xFF, 0xEF, 0xFF, 0xF4, 0xFF, 0xA6,
+	0xFF, 0x6E, 0xFF, 0x53, 0x00, 0x46, 0x03, 0x1F, 0x08, 0x43, 0x0D,
+	0x63, 0x10, 0xEF, 0x0F, 0x23, 0x0C, 0xDC, 0x06, 0x5E, 0x02, 0xF3,
+	0xFF, 0x6D, 0xFF, 0xBE, 0xFF, 0xFC, 0xFF, 0xE0, 0xFF, 0x85, 0xFF,
+	0x8F, 0xFF, 0x1C, 0x01, 0xD3, 0x04, 0x06, 0x0A, 0xB5, 0x0E, 0xAA,
+	0x10, 0xE7, 0x0E, 0x50, 0x0A, 0x16, 0x05, 0x42, 0x01, 0x98, 0xFF,
+	0x80, 0xFF, 0xDC, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC3, 0xFF, 0x6F,
+	0xFF, 0xE1, 0xFF, 0x2E, 0x02, 0x94, 0x06, 0xDD, 0x0B, 0xCD, 0x0F,
+	0x76, 0x10, 0x81, 0x0D, 0x6A, 0x08, 0x7F, 0x03, 0x6E, 0x00, 0x71,
+	0xFF, 0xA1, 0xFF, 0xF2, 0xFF, 0x00, 0x00, 0x15, 0x00, 0xD1, 0xFF,
+	0x8B, 0xFE, 0xBC, 0xFD, 0xE1, 0x00, 0x84, 0x09, 0xB0, 0x13, 0x47,
+	0x18, 0xB0, 0x13, 0x84, 0x09, 0xE1, 0x00, 0xBC, 0xFD, 0x8B, 0xFE,
+	0xD1, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xDA, 0x00, 0x30,
+	0x00, 0x5D, 0xFC, 0xB3, 0xFC, 0x35, 0x0A, 0xC2, 0x1C, 0x24, 0x20,
+	0x48, 0x10, 0x5D, 0xFF, 0x74, 0xFB, 0x3A, 0xFF, 0xFB, 0x00, 0x42,
+	0x00, 0xF8, 0xFF, 0xFA, 0xFF, 0x2C, 0x00, 0xF3, 0x00, 0xAD, 0xFF,
+	0xC5, 0xFB, 0x11, 0xFE, 0xAF, 0x0D, 0xEF, 0x1E, 0x68, 0x1E, 0xBC,
+	0x0C, 0xA7, 0xFD, 0xEA, 0xFB, 0xD3, 0xFF, 0xEE, 0x00, 0x24, 0x00,
+	0xFA, 0xFF, 0xF7, 0xFF, 0x4C, 0x00, 0xFB, 0x00, 0x0C, 0xFF, 0x5F,
+	0xFB, 0xE8, 0xFF, 0x3D, 0x11, 0x7E, 0x20, 0x13, 0x1C, 0x4C, 0x09,
+	0x6A, 0xFC, 0x8C, 0xFC, 0x4E, 0x00, 0xD1, 0x00, 0x0E, 0x00, 0xFD,
+	0xFF, 0xF7, 0xFF, 0x72, 0x00, 0xEC, 0x00, 0x55, 0xFE, 0x3D, 0xFB,
+	0x37, 0x02, 0xBE, 0x14, 0x5D, 0x21, 0x40, 0x19, 0x18, 0x06, 0xA2,
+	0xFB, 0x47, 0xFD, 0xA7, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0xFC, 0xFF, 0x9B, 0x00, 0xC0, 0x00, 0x92, 0xFD, 0x73,
+	0xFB, 0xF2, 0x04, 0x0E, 0x18, 0x81, 0x21, 0x0C, 0x16, 0x37, 0x03,
+	0x47, 0xFB, 0x0B, 0xFE, 0xDF, 0x00, 0x82, 0x00, 0xF9, 0xFF, 0xFE,
+	0xFF, 0x08, 0x00, 0xC3, 0x00, 0x74, 0x00, 0xD2, 0xFC, 0x10, 0xFC,
+	0x08, 0x08, 0x0A, 0x1B, 0xE9, 0x20, 0x9A, 0x12, 0xBE, 0x00, 0x49,
+	0xFB, 0xC8, 0xFE, 0xF9, 0x00, 0x5A, 0x00, 0xF7, 0xFF, 0xFC, 0xFF,
+	0x1B, 0x00, 0xE4, 0x00, 0x06, 0x00, 0x24, 0xFC, 0x1E, 0xFD, 0x65,
+	0x0B, 0x94, 0x1D, 0x9D, 0x1F, 0x0D, 0x0F, 0xB8, 0xFE, 0x96, 0xFB,
+	0x72, 0xFF, 0xF9, 0x00, 0x37, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x36,
+	0x00, 0xF8, 0x00, 0x78, 0xFF, 0x9B, 0xFB, 0xA6, 0xFE, 0xE9, 0x0E,
+	0x8D, 0x1F, 0xAA, 0x1D, 0x87, 0x0B, 0x2B, 0xFD, 0x1E, 0xFC, 0x02,
+	0x00, 0xE5, 0x00, 0x1C, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x58, 0x00,
+	0xF9, 0x00, 0xCF, 0xFE, 0x4A, 0xFB, 0xA7, 0x00, 0x77, 0x12, 0xE0,
+	0x20, 0x26, 0x1B, 0x28, 0x08, 0x18, 0xFC, 0xCB, 0xFC, 0x71, 0x00,
+	0xC5, 0x00, 0x08, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x80, 0x00, 0xE1,
+	0x00, 0x13, 0xFE, 0x45, 0xFB, 0x1D, 0x03, 0xEB, 0x15, 0x7F, 0x21,
+	0x2D, 0x18, 0x0E, 0x05, 0x77, 0xFB, 0x8B, 0xFD, 0xBE, 0x00, 0x9D,
+	0x00, 0xFD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xA9, 0x00,
+	0xAA, 0x00, 0x4F, 0xFD, 0x9D, 0xFB, 0xFA, 0x05, 0x22, 0x19, 0x62,
+	0x21, 0xE0, 0x14, 0x50, 0x02, 0x3E, 0xFB, 0x4E, 0xFE, 0xEB, 0x00,
+	0x73, 0x00, 0xF7, 0xFF, 0xFE, 0xFF, 0x0D, 0x00, 0xD0, 0x00, 0x52,
+	0x00, 0x93, 0xFC, 0x60, 0xFC, 0x2C, 0x09, 0xFA, 0x1B, 0x8A, 0x20,
+	0x60, 0x11, 0xFD, 0xFF, 0x5C, 0xFB, 0x06, 0xFF, 0xFB, 0x00, 0x4D,
+	0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x23, 0x00, 0xED, 0x00, 0xD9, 0xFF,
+	0xEF, 0xFB, 0x98, 0xFD, 0x99, 0x0C, 0x54, 0x1E, 0x02, 0x1F, 0xD2,
+	0x0D, 0x20, 0xFE, 0xC0, 0xFB, 0xA7, 0xFF, 0xF4, 0x00, 0x2D, 0x00,
+	0xF9, 0xFF, 0xF8, 0xFF, 0x41, 0x00, 0xFB, 0x00, 0x41, 0xFF, 0x78,
+	0xFB, 0x4A, 0xFF, 0x25, 0x10, 0x16, 0x20, 0xDA, 0x1C, 0x56, 0x0A,
+	0xBE, 0xFC, 0x56, 0xFC, 0x2C, 0x00, 0xDB, 0x00, 0x14, 0x00, 0xFD,
+	0xFF, 0xF7, 0xFF, 0x66, 0x00, 0xF4, 0x00, 0x8F, 0xFE, 0x3F, 0xFB,
+	0x75, 0x01, 0xAE, 0x13, 0x2C, 0x21, 0x2A, 0x1A, 0x0D, 0x07, 0xD4,
+	0xFB, 0x0C, 0xFD, 0x8F, 0x00, 0xB7, 0x00, 0x03, 0x00, 0xFF, 0xFF,
+	0x00, 0x00, 0xFA, 0xFF, 0x8E, 0x00, 0xD1, 0x00, 0xCF, 0xFD, 0x58,
+	0xFB, 0x10, 0x04, 0x10, 0x17, 0x8A, 0x21, 0x10, 0x17, 0x10, 0x04,
+	0x58, 0xFB, 0xCF, 0xFD, 0xD1, 0x00, 0x8E, 0x00, 0xFA, 0xFF, 0xFF,
+	0xFF, 0x03, 0x00, 0xB7, 0x00, 0x8F, 0x00, 0x0C, 0xFD, 0xD4, 0xFB,
+	0x0D, 0x07, 0x2A, 0x1A, 0x2C, 0x21, 0xAE, 0x13, 0x75, 0x01, 0x3F,
+	0xFB, 0x8F, 0xFE, 0xF4, 0x00, 0x66, 0x00, 0xF7, 0xFF, 0xFD, 0xFF,
+	0x14, 0x00, 0xDB, 0x00, 0x2C, 0x00, 0x56, 0xFC, 0xBE, 0xFC, 0x56,
+	0x0A, 0xDA, 0x1C, 0x16, 0x20, 0x25, 0x10, 0x4A, 0xFF, 0x78, 0xFB,
+	0x41, 0xFF, 0xFB, 0x00, 0x41, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x2D,
+	0x00, 0xF4, 0x00, 0xA7, 0xFF, 0xC0, 0xFB, 0x20, 0xFE, 0xD2, 0x0D,
+	0x02, 0x1F, 0x54, 0x1E, 0x99, 0x0C, 0x98, 0xFD, 0xEF, 0xFB, 0xD9,
+	0xFF, 0xED, 0x00, 0x23, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x4D, 0x00,
+	0xFB, 0x00, 0x06, 0xFF, 0x5C, 0xFB, 0xFD, 0xFF, 0x60, 0x11, 0x8A,
+	0x20, 0xFA, 0x1B, 0x2C, 0x09, 0x60, 0xFC, 0x93, 0xFC, 0x52, 0x00,
+	0xD0, 0x00, 0x0D, 0x00, 0xFE, 0xFF, 0xF7, 0xFF, 0x73, 0x00, 0xEB,
+	0x00, 0x4E, 0xFE, 0x3E, 0xFB, 0x50, 0x02, 0xE0, 0x14, 0x62, 0x21,
+	0x22, 0x19, 0xFA, 0x05, 0x9D, 0xFB, 0x4F, 0xFD, 0xAA, 0x00, 0xA9,
+	0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x9D, 0x00,
+	0xBE, 0x00, 0x8B, 0xFD, 0x77, 0xFB, 0x0E, 0x05, 0x2D, 0x18, 0x7F,
+	0x21, 0xEB, 0x15, 0x1D, 0x03, 0x45, 0xFB, 0x13, 0xFE, 0xE1, 0x00,
+	0x80, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x08, 0x00, 0xC5, 0x00, 0x71,
+	0x00, 0xCB, 0xFC, 0x18, 0xFC, 0x28, 0x08, 0x26, 0x1B, 0xE0, 0x20,
+	0x77, 0x12, 0xA7, 0x00, 0x4A, 0xFB, 0xCF, 0xFE, 0xF9, 0x00, 0x58,
+	0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1C, 0x00, 0xE5, 0x00, 0x02, 0x00,
+	0x1E, 0xFC, 0x2B, 0xFD, 0x87, 0x0B, 0xAA, 0x1D, 0x8D, 0x1F, 0xE9,
+	0x0E, 0xA6, 0xFE, 0x9B, 0xFB, 0x78, 0xFF, 0xF8, 0x00, 0x36, 0x00,
+	0xF9, 0xFF, 0xF8, 0xFF, 0x37, 0x00, 0xF9, 0x00, 0x72, 0xFF, 0x96,
+	0xFB, 0xB8, 0xFE, 0x0D, 0x0F, 0x9D, 0x1F, 0x94, 0x1D, 0x65, 0x0B,
+	0x1E, 0xFD, 0x24, 0xFC, 0x06, 0x00, 0xE4, 0x00, 0x1B, 0x00, 0xFC,
+	0xFF, 0xF7, 0xFF, 0x5A, 0x00, 0xF9, 0x00, 0xC8, 0xFE, 0x49, 0xFB,
+	0xBE, 0x00, 0x9A, 0x12, 0xE9, 0x20, 0x0A, 0x1B, 0x08, 0x08, 0x10,
+	0xFC, 0xD2, 0xFC, 0x74, 0x00, 0xC3, 0x00, 0x08, 0x00, 0xFE, 0xFF,
+	0xF9, 0xFF, 0x82, 0x00, 0xDF, 0x00, 0x0B, 0xFE, 0x47, 0xFB, 0x37,
+	0x03, 0x0C, 0x16, 0x81, 0x21, 0x0E, 0x18, 0xF2, 0x04, 0x73, 0xFB,
+	0x92, 0xFD, 0xC0, 0x00, 0x9B, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0xAB, 0x00, 0xA7, 0x00, 0x47, 0xFD, 0xA2, 0xFB,
+	0x18, 0x06, 0x40, 0x19, 0x5D, 0x21, 0xBE, 0x14, 0x37, 0x02, 0x3D,
+	0xFB, 0x55, 0xFE, 0xEC, 0x00, 0x72, 0x00, 0xF7, 0xFF, 0xFD, 0xFF,
+	0x0E, 0x00, 0xD1, 0x00, 0x4E, 0x00, 0x8C, 0xFC, 0x6A, 0xFC, 0x4C,
+	0x09, 0x13, 0x1C, 0x7E, 0x20, 0x3D, 0x11, 0xE8, 0xFF, 0x5F, 0xFB,
+	0x0C, 0xFF, 0xFB, 0x00, 0x4C, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x24,
+	0x00, 0xEE, 0x00, 0xD3, 0xFF, 0xEA, 0xFB, 0xA7, 0xFD, 0xBC, 0x0C,
+	0x68, 0x1E, 0xEF, 0x1E, 0xAF, 0x0D, 0x11, 0xFE, 0xC5, 0xFB, 0xAD,
+	0xFF, 0xF3, 0x00, 0x2C, 0x00, 0xFA, 0xFF, 0xF8, 0xFF, 0x42, 0x00,
+	0xFB, 0x00, 0x3A, 0xFF, 0x74, 0xFB, 0x5D, 0xFF, 0x48, 0x10, 0x24,
+	0x20, 0xC2, 0x1C, 0x35, 0x0A, 0xB3, 0xFC, 0x5D, 0xFC, 0x30, 0x00,
+	0xDA, 0x00, 0x13, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x67, 0x00, 0xF3,
+	0x00, 0x88, 0xFE, 0x3E, 0xFB, 0x8C, 0x01, 0xD0, 0x13, 0x33, 0x21,
+	0x0D, 0x1A, 0xEE, 0x06, 0xCD, 0xFB, 0x13, 0xFD, 0x92, 0x00, 0xB6,
+	0x00, 0x03, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFA, 0xFF, 0x90, 0x00,
+	0xCF, 0x00, 0xC7, 0xFD, 0x5B, 0xFB, 0x2B, 0x04, 0x31, 0x17, 0x8A,
+	0x21, 0xF0, 0x16, 0xF4, 0x03, 0x56, 0xFB, 0xD6, 0xFD, 0xD3, 0x00,
+	0x8D, 0x00, 0xFA, 0xFF, 0xFF, 0xFF, 0x04, 0x00, 0xB9, 0x00, 0x8C,
+	0x00, 0x05, 0xFD, 0xDB, 0xFB, 0x2C, 0x07, 0x47, 0x1A, 0x25, 0x21,
+	0x8B, 0x13, 0x5D, 0x01, 0x40, 0xFB, 0x97, 0xFE, 0xF5, 0x00, 0x64,
+	0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x15, 0x00, 0xDC, 0x00, 0x27, 0x00,
+	0x50, 0xFC, 0xCA, 0xFC, 0x78, 0x0A, 0xF2, 0x1C, 0x07, 0x20, 0x02,
+	0x10, 0x37, 0xFF, 0x7B, 0xFB, 0x47, 0xFF, 0xFB, 0x00, 0x40, 0x00,
+	0xF8, 0xFF, 0xF9, 0xFF, 0x2E, 0x00, 0xF5, 0x00, 0xA2, 0xFF, 0xBB,
+	0xFB, 0x31, 0xFE, 0xF5, 0x0D, 0x14, 0x1F, 0x3F, 0x1E, 0x77, 0x0C,
+	0x8A, 0xFD, 0xF5, 0xFB, 0xDE, 0xFF, 0xEC, 0x00, 0x22, 0x00, 0xFB,
+	0xFF, 0xF7, 0xFF, 0x4E, 0x00, 0xFB, 0x00, 0xFF, 0xFE, 0x59, 0xFB,
+	0x11, 0x00, 0x83, 0x11, 0x96, 0x20, 0xE0, 0x1B, 0x0B, 0x09, 0x56,
+	0xFC, 0x99, 0xFC, 0x56, 0x00, 0xCE, 0x00, 0x0D, 0x00, 0xFE, 0xFF,
+	0xF8, 0xFF, 0x75, 0x00, 0xEA, 0x00, 0x47, 0xFE, 0x3E, 0xFB, 0x69,
+	0x02, 0x02, 0x15, 0x66, 0x21, 0x04, 0x19, 0xDC, 0x05, 0x98, 0xFB,
+	0x56, 0xFD, 0xAD, 0x00, 0xA8, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00,
+	0x00, 0xFD, 0xFF, 0x9E, 0x00, 0xBC, 0x00, 0x83, 0xFD, 0x7B, 0xFB,
+	0x2B, 0x05, 0x4C, 0x18, 0x7C, 0x21, 0xCA, 0x15, 0x03, 0x03, 0x44,
+	0xFB, 0x1A, 0xFE, 0xE2, 0x00, 0x7E, 0x00, 0xF8, 0xFF, 0xFE, 0xFF,
+	0x09, 0x00, 0xC6, 0x00, 0x6D, 0x00, 0xC3, 0xFC, 0x20, 0xFC, 0x49,
+	0x08, 0x41, 0x1B, 0xD6, 0x20, 0x54, 0x12, 0x92, 0x00, 0x4C, 0xFB,
+	0xD6, 0xFE, 0xFA, 0x00, 0x57, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1D,
+	0x00, 0xE6, 0x00, 0xFD, 0xFF, 0x18, 0xFC, 0x38, 0xFD, 0xA9, 0x0B,
+	0xC0, 0x1D, 0x7C, 0x1F, 0xC6, 0x0E, 0x95, 0xFE, 0x9F, 0xFB, 0x7E,
+	0xFF, 0xF8, 0x00, 0x35, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x38, 0x00,
+	0xF9, 0x00, 0x6C, 0xFF, 0x92, 0xFB, 0xC9, 0xFE, 0x2F, 0x0F, 0xAD,
+	0x1F, 0x7D, 0x1D, 0x42, 0x0B, 0x12, 0xFD, 0x2A, 0xFC, 0x0B, 0x00,
+	0xE3, 0x00, 0x1A, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x5B, 0x00, 0xF8,
+	0x00, 0xC1, 0xFE, 0x47, 0xFB, 0xD4, 0x00, 0xBC, 0x12, 0xF3, 0x20,
+	0xEF, 0x1A, 0xE9, 0x07, 0x08, 0xFC, 0xD9, 0xFC, 0x78, 0x00, 0xC2,
+	0x00, 0x07, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, 0x83, 0x00, 0xDD, 0x00,
+	0x04, 0xFE, 0x49, 0xFB, 0x52, 0x03, 0x2D, 0x16, 0x83, 0x21, 0xEF,
+	0x17, 0xD5, 0x04, 0x6F, 0xFB, 0x9A, 0xFD, 0xC3, 0x00, 0x9A, 0x00,
+	0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xAD, 0x00, 0xA4,
+	0x00, 0x40, 0xFD, 0xA8, 0xFB, 0x36, 0x06, 0x5E, 0x19, 0x58, 0x21,
+	0x9C, 0x14, 0x1E, 0x02, 0x3D, 0xFB, 0x5D, 0xFE, 0xED, 0x00, 0x70,
+	0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x0F, 0x00, 0xD2, 0x00, 0x4A, 0x00,
+	0x85, 0xFC, 0x74, 0xFC, 0x6D, 0x09, 0x2D, 0x1C, 0x72, 0x20, 0x1A,
+	0x11, 0xD4, 0xFF, 0x61, 0xFB, 0x13, 0xFF, 0xFC, 0x00, 0x4A, 0x00,
+	0xF7, 0xFF, 0xFA, 0xFF, 0x25, 0x00, 0xEF, 0x00, 0xCE, 0xFF, 0xE4,
+	0xFB, 0xB5, 0xFD, 0xDE, 0x0C, 0x7C, 0x1E, 0xDD, 0x1E, 0x8C, 0x0D,
+	0x01, 0xFE, 0xCA, 0xFB, 0xB3, 0xFF, 0xF3, 0x00, 0x2B, 0x00, 0xFA,
+	0xFF, 0xF8, 0xFF, 0x44, 0x00, 0xFB, 0x00, 0x34, 0xFF, 0x71, 0xFB,
+	0x71, 0xFF, 0x6B, 0x10, 0x32, 0x20, 0xA9, 0x1C, 0x13, 0x0A, 0xA8,
+	0xFC, 0x63, 0xFC, 0x35, 0x00, 0xD9, 0x00, 0x12, 0x00, 0xFD, 0xFF,
+	0xF7, 0xFF, 0x69, 0x00, 0xF2, 0x00, 0x81, 0xFE, 0x3E, 0xFB, 0xA4,
+	0x01, 0xF2, 0x13, 0x3A, 0x21, 0xF0, 0x19, 0xCF, 0x06, 0xC7, 0xFB,
+	0x1B, 0xFD, 0x96, 0x00, 0xB4, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0x00,
+	0x00, 0xFB, 0xFF, 0x92, 0x00, 0xCD, 0x00, 0xC0, 0xFD, 0x5E, 0xFB,
+	0x47, 0x04, 0x51, 0x17, 0x8A, 0x21, 0xD0, 0x16, 0xD9, 0x03, 0x53,
+	0xFB, 0xDE, 0xFD, 0xD5, 0x00, 0x8B, 0x00, 0xFA, 0xFF, 0xFF, 0xFF,
+	0x04, 0x00, 0xBA, 0x00, 0x89, 0x00, 0xFD, 0xFC, 0xE2, 0xFB, 0x4B,
+	0x07, 0x63, 0x1A, 0x1D, 0x21, 0x69, 0x13, 0x46, 0x01, 0x41, 0xFB,
+	0x9E, 0xFE, 0xF5, 0x00, 0x63, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x16,
+	0x00, 0xDD, 0x00, 0x23, 0x00, 0x49, 0xFC, 0xD5, 0xFC, 0x99, 0x0A,
+	0x09, 0x1D, 0xF9, 0x1F, 0xDF, 0x0F, 0x24, 0xFF, 0x7F, 0xFB, 0x4D,
+	0xFF, 0xFB, 0x00, 0x3F, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x2F, 0x00,
+	0xF5, 0x00, 0x9C, 0xFF, 0xB6, 0xFB, 0x41, 0xFE, 0x17, 0x0E, 0x26,
+	0x1F, 0x2B, 0x1E, 0x54, 0x0C, 0x7C, 0xFD, 0xFA, 0xFB, 0xE3, 0xFF,
+	0xEB, 0x00, 0x21, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x50, 0x00, 0xFB,
+	0x00, 0xF8, 0xFE, 0x57, 0xFB, 0x26, 0x00, 0xA6, 0x11, 0xA1, 0x20,
+	0xC6, 0x1B, 0xEA, 0x08, 0x4D, 0xFC, 0xA0, 0xFC, 0x5A, 0x00, 0xCD,
+	0x00, 0x0C, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x77, 0x00, 0xE9, 0x00,
+	0x3F, 0xFE, 0x3F, 0xFB, 0x82, 0x02, 0x23, 0x15, 0x6B, 0x21, 0xE5,
+	0x18, 0xBE, 0x05, 0x93, 0xFB, 0x5E, 0xFD, 0xAF, 0x00, 0xA6, 0x00,
+	0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0xA0, 0x00, 0xB9,
+	0x00, 0x7C, 0xFD, 0x80, 0xFB, 0x48, 0x05, 0x6B, 0x18, 0x79, 0x21,
+	0xA9, 0x15, 0xE9, 0x02, 0x43, 0xFB, 0x21, 0xFE, 0xE3, 0x00, 0x7D,
+	0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x09, 0x00, 0xC7, 0x00, 0x69, 0x00,
+	0xBC, 0xFC, 0x29, 0xFC, 0x69, 0x08, 0x5C, 0x1B, 0xCC, 0x20, 0x32,
+	0x12, 0x7C, 0x00, 0x4E, 0xFB, 0xDD, 0xFE, 0xFA, 0x00, 0x56, 0x00,
+	0xF7, 0xFF, 0xFB, 0xFF, 0x1D, 0x00, 0xE7, 0x00, 0xF8, 0xFF, 0x12,
+	0xFC, 0x45, 0xFD, 0xCB, 0x0B, 0xD6, 0x1D, 0x6C, 0x1F, 0xA3, 0x0E,
+	0x84, 0xFE, 0xA4, 0xFB, 0x84, 0xFF, 0xF7, 0x00, 0x34, 0x00, 0xF9,
+	0xFF, 0xF8, 0xFF, 0x3A, 0x00, 0xFA, 0x00, 0x66, 0xFF, 0x8E, 0xFB,
+	0xDB, 0xFE, 0x53, 0x0F, 0xBD, 0x1F, 0x66, 0x1D, 0x21, 0x0B, 0x05,
+	0xFD, 0x30, 0xFC, 0x10, 0x00, 0xE2, 0x00, 0x19, 0x00, 0xFC, 0xFF,
+	0xF7, 0xFF, 0x5D, 0x00, 0xF8, 0x00, 0xBA, 0xFE, 0x46, 0xFB, 0xEA,
+	0x00, 0xDF, 0x12, 0xFC, 0x20, 0xD3, 0x1A, 0xC9, 0x07, 0x00, 0xFC,
+	0xE0, 0xFC, 0x7B, 0x00, 0xC0, 0x00, 0x07, 0x00, 0xFF, 0xFF, 0xF9,
+	0xFF, 0x85, 0x00, 0xDC, 0x00, 0xFC, 0xFD, 0x4A, 0xFB, 0x6C, 0x03,
+	0x4E, 0x16, 0x85, 0x21, 0xCF, 0x17, 0xB8, 0x04, 0x6C, 0xFB, 0xA2,
+	0xFD, 0xC5, 0x00, 0x98, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFF,
+	0x01, 0x00, 0xAE, 0x00, 0xA1, 0x00, 0x38, 0xFD, 0xAE, 0xFB, 0x54,
+	0x06, 0x7C, 0x19, 0x53, 0x21, 0x7B, 0x14, 0x05, 0x02, 0x3D, 0xFB,
+	0x64, 0xFE, 0xEE, 0x00, 0x6F, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x0F,
+	0x00, 0xD4, 0x00, 0x46, 0x00, 0x7E, 0xFC, 0x7E, 0xFC, 0x8E, 0x09,
+	0x46, 0x1C, 0x66, 0x20, 0xF7, 0x10, 0xC0, 0xFF, 0x64, 0xFB, 0x1A,
+	0xFF, 0xFC, 0x00, 0x49, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x26, 0x00,
+	0xF0, 0x00, 0xC9, 0xFF, 0xDF, 0xFB, 0xC4, 0xFD, 0x01, 0x0D, 0x90,
+	0x1E, 0xCA, 0x1E, 0x69, 0x0D, 0xF1, 0xFD, 0xCF, 0xFB, 0xB8, 0xFF,
+	0xF2, 0x00, 0x29, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x45, 0x00, 0xFC,
+	0x00, 0x2D, 0xFF, 0x6D, 0xFB, 0x84, 0xFF, 0x8E, 0x10, 0x3F, 0x20,
+	0x91, 0x1C, 0xF2, 0x09, 0x9D, 0xFC, 0x6A, 0xFC, 0x39, 0x00, 0xD7,
+	0x00, 0x12, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x6A, 0x00, 0xF1, 0x00,
+	0x7A, 0xFE, 0x3D, 0xFB, 0xBC, 0x01, 0x14, 0x14, 0x41, 0x21, 0xD4,
+	0x19, 0xB0, 0x06, 0xC0, 0xFB, 0x22, 0xFD, 0x99, 0x00, 0xB3, 0x00,
+	0x02, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFB, 0xFF, 0x93, 0x00, 0xCB,
+	0x00, 0xB8, 0xFD, 0x61, 0xFB, 0x63, 0x04, 0x71, 0x17, 0x89, 0x21,
+	0xB0, 0x16, 0xBD, 0x03, 0x51, 0xFB, 0xE6, 0xFD, 0xD7, 0x00, 0x8A,
+	0x00, 0xFA, 0xFF, 0xFF, 0xFF, 0x05, 0x00, 0xBC, 0x00, 0x86, 0x00,
+	0xF6, 0xFC, 0xE9, 0xFB, 0x6A, 0x07, 0x80, 0x1A, 0x15, 0x21, 0x47,
+	0x13, 0x2F, 0x01, 0x42, 0xFB, 0xA5, 0xFE, 0xF6, 0x00, 0x61, 0x00,
+	0xF7, 0xFF, 0xFC, 0xFF, 0x16, 0x00, 0xDF, 0x00, 0x1E, 0x00, 0x43,
+	0xFC, 0xE1, 0xFC, 0xBB, 0x0A, 0x21, 0x1D, 0xEA, 0x1F, 0xBC, 0x0F,
+	0x12, 0xFF, 0x82, 0xFB, 0x54, 0xFF, 0xFA, 0x00, 0x3D, 0x00, 0xF8,
+	0xFF, 0xF9, 0xFF, 0x30, 0x00, 0xF6, 0x00, 0x96, 0xFF, 0xB1, 0xFB,
+	0x51, 0xFE, 0x3A, 0x0E, 0x38, 0x1F, 0x16, 0x1E, 0x32, 0x0C, 0x6E,
+	0xFD, 0x00, 0xFC, 0xE8, 0xFF, 0xEA, 0x00, 0x20, 0x00, 0xFB, 0xFF,
+	0xF7, 0xFF, 0x51, 0x00, 0xFB, 0x00, 0xF1, 0xFE, 0x54, 0xFB, 0x3B,
+	0x00, 0xC9, 0x11, 0xAD, 0x20, 0xAC, 0x1B, 0xCA, 0x08, 0x44, 0xFC,
+	0xA7, 0xFC, 0x5E, 0x00, 0xCC, 0x00, 0x0B, 0x00, 0xFE, 0xFF, 0xF8,
+	0xFF, 0x78, 0x00, 0xE7, 0x00, 0x38, 0xFE, 0x40, 0xFB, 0x9B, 0x02,
+	0x45, 0x15, 0x6F, 0x21, 0xC7, 0x18, 0xA1, 0x05, 0x8E, 0xFB, 0x65,
+	0xFD, 0xB2, 0x00, 0xA5, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00,
+	0xFE, 0xFF, 0xA2, 0x00, 0xB7, 0x00, 0x74, 0xFD, 0x84, 0xFB, 0x66,
+	0x05, 0x8A, 0x18, 0x76, 0x21, 0x87, 0x15, 0xCF, 0x02, 0x41, 0xFB,
+	0x29, 0xFE, 0xE5, 0x00, 0x7B, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0A,
+	0x00, 0xC9, 0x00, 0x66, 0x00, 0xB5, 0xFC, 0x32, 0xFC, 0x89, 0x08,
+	0x77, 0x1B, 0xC2, 0x20, 0x0F, 0x12, 0x66, 0x00, 0x50, 0xFB, 0xE4,
+	0xFE, 0xFA, 0x00, 0x54, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1E, 0x00,
+	0xE8, 0x00, 0xF3, 0xFF, 0x0C, 0xFC, 0x53, 0xFD, 0xED, 0x0B, 0xEB,
+	0x1D, 0x5A, 0x1F, 0x80, 0x0E, 0x73, 0xFE, 0xA8, 0xFB, 0x8A, 0xFF,
+	0xF7, 0x00, 0x32, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x3B, 0x00, 0xFA,
+	0x00, 0x60, 0xFF, 0x8A, 0xFB, 0xED, 0xFE, 0x76, 0x0F, 0xCC, 0x1F,
+	0x4F, 0x1D, 0xFF, 0x0A, 0xF9, 0xFC, 0x36, 0xFC, 0x15, 0x00, 0xE1,
+	0x00, 0x18, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x5E, 0x00, 0xF7, 0x00,
+	0xB3, 0xFE, 0x44, 0xFB, 0x01, 0x01, 0x02, 0x13, 0x04, 0x21, 0xB8,
+	0x1A, 0xA9, 0x07, 0xF8, 0xFB, 0xE7, 0xFC, 0x7F, 0x00, 0xBF, 0x00,
+	0x06, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, 0x86, 0x00, 0xDA, 0x00, 0xF5,
+	0xFD, 0x4C, 0xFB, 0x87, 0x03, 0x6E, 0x16, 0x86, 0x21, 0xB0, 0x17,
+	0x9C, 0x04, 0x68, 0xFB, 0xA9, 0xFD, 0xC7, 0x00, 0x96, 0x00, 0xFB,
+	0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xB0, 0x00, 0x9F, 0x00,
+	0x31, 0xFD, 0xB4, 0xFB, 0x73, 0x06, 0x99, 0x19, 0x4D, 0x21, 0x59,
+	0x14, 0xED, 0x01, 0x3D, 0xFB, 0x6B, 0xFE, 0xEF, 0x00, 0x6D, 0x00,
+	0xF7, 0xFF, 0xFD, 0xFF, 0x10, 0x00, 0xD5, 0x00, 0x42, 0x00, 0x77,
+	0xFC, 0x88, 0xFC, 0xAF, 0x09, 0x5F, 0x1C, 0x59, 0x20, 0xD4, 0x10,
+	0xAC, 0xFF, 0x67, 0xFB, 0x20, 0xFF, 0xFC, 0x00, 0x48, 0x00, 0xF7,
+	0xFF, 0xFA, 0xFF, 0x27, 0x00, 0xF0, 0x00, 0xC3, 0xFF, 0xD9, 0xFB,
+	0xD3, 0xFD, 0x24, 0x0D, 0xA3, 0x1E, 0xB7, 0x1E, 0x46, 0x0D, 0xE2,
+	0xFD, 0xD4, 0xFB, 0xBE, 0xFF, 0xF1, 0x00, 0x28, 0x00, 0xFA, 0xFF,
+	0xF7, 0xFF, 0x46, 0x00, 0xFC, 0x00, 0x27, 0xFF, 0x6A, 0xFB, 0x98,
+	0xFF, 0xB1, 0x10, 0x4C, 0x20, 0x78, 0x1C, 0xD1, 0x09, 0x93, 0xFC,
+	0x71, 0xFC, 0x3D, 0x00, 0xD6, 0x00, 0x11, 0x00, 0xFD, 0xFF, 0xF7,
+	0xFF, 0x6C, 0x00, 0xF0, 0x00, 0x72, 0xFE, 0x3D, 0xFB, 0xD4, 0x01,
+	0x36, 0x14, 0x47, 0x21, 0xB6, 0x19, 0x91, 0x06, 0xBA, 0xFB, 0x29,
+	0xFD, 0x9C, 0x00, 0xB1, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0x00, 0x00,
+	0xFB, 0xFF, 0x95, 0x00, 0xC9, 0x00, 0xB1, 0xFD, 0x65, 0xFB, 0x80,
+	0x04, 0x90, 0x17, 0x88, 0x21, 0x8F, 0x16, 0xA2, 0x03, 0x4E, 0xFB,
+	0xED, 0xFD, 0xD9, 0x00, 0x88, 0x00, 0xF9, 0xFF, 0xFF, 0xFF, 0x05,
+	0x00, 0xBD, 0x00, 0x82, 0x00, 0xEF, 0xFC, 0xF0, 0xFB, 0x8A, 0x07,
+	0x9C, 0x1A, 0x0D, 0x21, 0x24, 0x13, 0x18, 0x01, 0x43, 0xFB, 0xAC,
+	0xFE, 0xF7, 0x00, 0x60, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x17, 0x00,
+	0xE0, 0x00, 0x1A, 0x00, 0x3D, 0xFC, 0xED, 0xFC, 0xDD, 0x0A, 0x38,
+	0x1D, 0xDB, 0x1F, 0x99, 0x0F, 0xFF, 0xFE, 0x86, 0xFB, 0x5A, 0xFF,
+	0xFA, 0x00, 0x3C, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x31, 0x00, 0xF6,
+	0x00, 0x90, 0xFF, 0xAD, 0xFB, 0x62, 0xFE, 0x5D, 0x0E, 0x49, 0x1F,
+	0x01, 0x1E, 0x10, 0x0C, 0x60, 0xFD, 0x06, 0xFC, 0xEE, 0xFF, 0xE9,
+	0x00, 0x1F, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x53, 0x00, 0xFB, 0x00,
+	0xEB, 0xFE, 0x52, 0xFB, 0x51, 0x00, 0xEC, 0x11, 0xB7, 0x20, 0x91,
+	0x1B, 0xA9, 0x08, 0x3B, 0xFC, 0xAE, 0xFC, 0x62, 0x00, 0xCA, 0x00,
+	0x0B, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7A, 0x00, 0xE6, 0x00, 0x30,
+	0xFE, 0x40, 0xFB, 0xB5, 0x02, 0x66, 0x15, 0x73, 0x21, 0xA9, 0x18,
+	0x83, 0x05, 0x89, 0xFB, 0x6D, 0xFD, 0xB4, 0x00, 0xA3, 0x00, 0xFE,
+	0xFF, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xA3, 0x00, 0xB4, 0x00,
+	0x6D, 0xFD, 0x89, 0xFB, 0x83, 0x05, 0xA9, 0x18, 0x73, 0x21, 0x66,
+	0x15, 0xB5, 0x02, 0x40, 0xFB, 0x30, 0xFE, 0xE6, 0x00, 0x7A, 0x00,
+	0xF8, 0xFF, 0xFE, 0xFF, 0x0B, 0x00, 0xCA, 0x00, 0x62, 0x00, 0xAE,
+	0xFC, 0x3B, 0xFC, 0xA9, 0x08, 0x91, 0x1B, 0xB7, 0x20, 0xEC, 0x11,
+	0x51, 0x00, 0x52, 0xFB, 0xEB, 0xFE, 0xFB, 0x00, 0x53, 0x00, 0xF7,
+	0xFF, 0xFB, 0xFF, 0x1F, 0x00, 0xE9, 0x00, 0xEE, 0xFF, 0x06, 0xFC,
+	0x60, 0xFD, 0x10, 0x0C, 0x01, 0x1E, 0x49, 0x1F, 0x5D, 0x0E, 0x62,
+	0xFE, 0xAD, 0xFB, 0x90, 0xFF, 0xF6, 0x00, 0x31, 0x00, 0xF9, 0xFF,
+	0xF8, 0xFF, 0x3C, 0x00, 0xFA, 0x00, 0x5A, 0xFF, 0x86, 0xFB, 0xFF,
+	0xFE, 0x99, 0x0F, 0xDB, 0x1F, 0x38, 0x1D, 0xDD, 0x0A, 0xED, 0xFC,
+	0x3D, 0xFC, 0x1A, 0x00, 0xE0, 0x00, 0x17, 0x00, 0xFC, 0xFF, 0xF7,
+	0xFF, 0x60, 0x00, 0xF7, 0x00, 0xAC, 0xFE, 0x43, 0xFB, 0x18, 0x01,
+	0x24, 0x13, 0x0D, 0x21, 0x9C, 0x1A, 0x8A, 0x07, 0xF0, 0xFB, 0xEF,
+	0xFC, 0x82, 0x00, 0xBD, 0x00, 0x05, 0x00, 0xFF, 0xFF, 0xF9, 0xFF,
+	0x88, 0x00, 0xD9, 0x00, 0xED, 0xFD, 0x4E, 0xFB, 0xA2, 0x03, 0x8F,
+	0x16, 0x88, 0x21, 0x90, 0x17, 0x80, 0x04, 0x65, 0xFB, 0xB1, 0xFD,
+	0xC9, 0x00, 0x95, 0x00, 0xFB, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x02,
+	0x00, 0xB1, 0x00, 0x9C, 0x00, 0x29, 0xFD, 0xBA, 0xFB, 0x91, 0x06,
+	0xB6, 0x19, 0x47, 0x21, 0x36, 0x14, 0xD4, 0x01, 0x3D, 0xFB, 0x72,
+	0xFE, 0xF0, 0x00, 0x6C, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x11, 0x00,
+	0xD6, 0x00, 0x3D, 0x00, 0x71, 0xFC, 0x93, 0xFC, 0xD1, 0x09, 0x78,
+	0x1C, 0x4C, 0x20, 0xB1, 0x10, 0x98, 0xFF, 0x6A, 0xFB, 0x27, 0xFF,
+	0xFC, 0x00, 0x46, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x28, 0x00, 0xF1,
+	0x00, 0xBE, 0xFF, 0xD4, 0xFB, 0xE2, 0xFD, 0x46, 0x0D, 0xB7, 0x1E,
+	0xA3, 0x1E, 0x24, 0x0D, 0xD3, 0xFD, 0xD9, 0xFB, 0xC3, 0xFF, 0xF0,
+	0x00, 0x27, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x48, 0x00, 0xFC, 0x00,
+	0x20, 0xFF, 0x67, 0xFB, 0xAC, 0xFF, 0xD4, 0x10, 0x59, 0x20, 0x5F,
+	0x1C, 0xAF, 0x09, 0x88, 0xFC, 0x77, 0xFC, 0x42, 0x00, 0xD5, 0x00,
+	0x10, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x6D, 0x00, 0xEF, 0x00, 0x6B,
+	0xFE, 0x3D, 0xFB, 0xED, 0x01, 0x59, 0x14, 0x4D, 0x21, 0x99, 0x19,
+	0x73, 0x06, 0xB4, 0xFB, 0x31, 0xFD, 0x9F, 0x00, 0xB0, 0x00, 0x01,
+	0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFB, 0xFF, 0x96, 0x00, 0xC7, 0x00,
+	0xA9, 0xFD, 0x68, 0xFB, 0x9C, 0x04, 0xB0, 0x17, 0x86, 0x21, 0x6E,
+	0x16, 0x87, 0x03, 0x4C, 0xFB, 0xF5, 0xFD, 0xDA, 0x00, 0x86, 0x00,
+	0xF9, 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0xBF, 0x00, 0x7F, 0x00, 0xE7,
+	0xFC, 0xF8, 0xFB, 0xA9, 0x07, 0xB8, 0x1A, 0x04, 0x21, 0x02, 0x13,
+	0x01, 0x01, 0x44, 0xFB, 0xB3, 0xFE, 0xF7, 0x00, 0x5E, 0x00, 0xF7,
+	0xFF, 0xFC, 0xFF, 0x18, 0x00, 0xE1, 0x00, 0x15, 0x00, 0x36, 0xFC,
+	0xF9, 0xFC, 0xFF, 0x0A, 0x4F, 0x1D, 0xCC, 0x1F, 0x76, 0x0F, 0xED,
+	0xFE, 0x8A, 0xFB, 0x60, 0xFF, 0xFA, 0x00, 0x3B, 0x00, 0xF8, 0xFF,
+	0xF9, 0xFF, 0x32, 0x00, 0xF7, 0x00, 0x8A, 0xFF, 0xA8, 0xFB, 0x73,
+	0xFE, 0x80, 0x0E, 0x5A, 0x1F, 0xEB, 0x1D, 0xED, 0x0B, 0x53, 0xFD,
+	0x0C, 0xFC, 0xF3, 0xFF, 0xE8, 0x00, 0x1E, 0x00, 0xFB, 0xFF, 0xF7,
+	0xFF, 0x54, 0x00, 0xFA, 0x00, 0xE4, 0xFE, 0x50, 0xFB, 0x66, 0x00,
+	0x0F, 0x12, 0xC2, 0x20, 0x77, 0x1B, 0x89, 0x08, 0x32, 0xFC, 0xB5,
+	0xFC, 0x66, 0x00, 0xC9, 0x00, 0x0A, 0x00, 0xFE, 0xFF, 0xF8, 0xFF,
+	0x7B, 0x00, 0xE5, 0x00, 0x29, 0xFE, 0x41, 0xFB, 0xCF, 0x02, 0x87,
+	0x15, 0x76, 0x21, 0x8A, 0x18, 0x66, 0x05, 0x84, 0xFB, 0x74, 0xFD,
+	0xB7, 0x00, 0xA2, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFE,
+	0xFF, 0xA5, 0x00, 0xB2, 0x00, 0x65, 0xFD, 0x8E, 0xFB, 0xA1, 0x05,
+	0xC7, 0x18, 0x6F, 0x21, 0x45, 0x15, 0x9B, 0x02, 0x40, 0xFB, 0x38,
+	0xFE, 0xE7, 0x00, 0x78, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0B, 0x00,
+	0xCC, 0x00, 0x5E, 0x00, 0xA7, 0xFC, 0x44, 0xFC, 0xCA, 0x08, 0xAC,
+	0x1B, 0xAD, 0x20, 0xC9, 0x11, 0x3B, 0x00, 0x54, 0xFB, 0xF1, 0xFE,
+	0xFB, 0x00, 0x51, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x20, 0x00, 0xEA,
+	0x00, 0xE8, 0xFF, 0x00, 0xFC, 0x6E, 0xFD, 0x32, 0x0C, 0x16, 0x1E,
+	0x38, 0x1F, 0x3A, 0x0E, 0x51, 0xFE, 0xB1, 0xFB, 0x96, 0xFF, 0xF6,
+	0x00, 0x30, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x3D, 0x00, 0xFA, 0x00,
+	0x54, 0xFF, 0x82, 0xFB, 0x12, 0xFF, 0xBC, 0x0F, 0xEA, 0x1F, 0x21,
+	0x1D, 0xBB, 0x0A, 0xE1, 0xFC, 0x43, 0xFC, 0x1E, 0x00, 0xDF, 0x00,
+	0x16, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x61, 0x00, 0xF6, 0x00, 0xA5,
+	0xFE, 0x42, 0xFB, 0x2F, 0x01, 0x47, 0x13, 0x15, 0x21, 0x80, 0x1A,
+	0x6A, 0x07, 0xE9, 0xFB, 0xF6, 0xFC, 0x86, 0x00, 0xBC, 0x00, 0x05,
+	0x00, 0xFF, 0xFF, 0xFA, 0xFF, 0x8A, 0x00, 0xD7, 0x00, 0xE6, 0xFD,
+	0x51, 0xFB, 0xBD, 0x03, 0xB0, 0x16, 0x89, 0x21, 0x71, 0x17, 0x63,
+	0x04, 0x61, 0xFB, 0xB8, 0xFD, 0xCB, 0x00, 0x93, 0x00, 0xFB, 0xFF,
+	0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00, 0xB3, 0x00, 0x99, 0x00, 0x22,
+	0xFD, 0xC0, 0xFB, 0xB0, 0x06, 0xD4, 0x19, 0x41, 0x21, 0x14, 0x14,
+	0xBC, 0x01, 0x3D, 0xFB, 0x7A, 0xFE, 0xF1, 0x00, 0x6A, 0x00, 0xF7,
+	0xFF, 0xFD, 0xFF, 0x12, 0x00, 0xD7, 0x00, 0x39, 0x00, 0x6A, 0xFC,
+	0x9D, 0xFC, 0xF2, 0x09, 0x91, 0x1C, 0x3F, 0x20, 0x8E, 0x10, 0x84,
+	0xFF, 0x6D, 0xFB, 0x2D, 0xFF, 0xFC, 0x00, 0x45, 0x00, 0xF7, 0xFF,
+	0xFA, 0xFF, 0x29, 0x00, 0xF2, 0x00, 0xB8, 0xFF, 0xCF, 0xFB, 0xF1,
+	0xFD, 0x69, 0x0D, 0xCA, 0x1E, 0x90, 0x1E, 0x01, 0x0D, 0xC4, 0xFD,
+	0xDF, 0xFB, 0xC9, 0xFF, 0xF0, 0x00, 0x26, 0x00, 0xFA, 0xFF, 0xF7,
+	0xFF, 0x49, 0x00, 0xFC, 0x00, 0x1A, 0xFF, 0x64, 0xFB, 0xC0, 0xFF,
+	0xF7, 0x10, 0x66, 0x20, 0x46, 0x1C, 0x8E, 0x09, 0x7E, 0xFC, 0x7E,
+	0xFC, 0x46, 0x00, 0xD4, 0x00, 0x0F, 0x00, 0xFD, 0xFF, 0xF7, 0xFF,
+	0x6F, 0x00, 0xEE, 0x00, 0x64, 0xFE, 0x3D, 0xFB, 0x05, 0x02, 0x7B,
+	0x14, 0x53, 0x21, 0x7C, 0x19, 0x54, 0x06, 0xAE, 0xFB, 0x38, 0xFD,
+	0xA1, 0x00, 0xAE, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFC,
+	0xFF, 0x98, 0x00, 0xC5, 0x00, 0xA2, 0xFD, 0x6C, 0xFB, 0xB8, 0x04,
+	0xCF, 0x17, 0x85, 0x21, 0x4E, 0x16, 0x6C, 0x03, 0x4A, 0xFB, 0xFC,
+	0xFD, 0xDC, 0x00, 0x85, 0x00, 0xF9, 0xFF, 0xFF, 0xFF, 0x07, 0x00,
+	0xC0, 0x00, 0x7B, 0x00, 0xE0, 0xFC, 0x00, 0xFC, 0xC9, 0x07, 0xD3,
+	0x1A, 0xFC, 0x20, 0xDF, 0x12, 0xEA, 0x00, 0x46, 0xFB, 0xBA, 0xFE,
+	0xF8, 0x00, 0x5D, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x19, 0x00, 0xE2,
+	0x00, 0x10, 0x00, 0x30, 0xFC, 0x05, 0xFD, 0x21, 0x0B, 0x66, 0x1D,
+	0xBD, 0x1F, 0x53, 0x0F, 0xDB, 0xFE, 0x8E, 0xFB, 0x66, 0xFF, 0xFA,
+	0x00, 0x3A, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x34, 0x00, 0xF7, 0x00,
+	0x84, 0xFF, 0xA4, 0xFB, 0x84, 0xFE, 0xA3, 0x0E, 0x6C, 0x1F, 0xD6,
+	0x1D, 0xCB, 0x0B, 0x45, 0xFD, 0x12, 0xFC, 0xF8, 0xFF, 0xE7, 0x00,
+	0x1D, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x56, 0x00, 0xFA, 0x00, 0xDD,
+	0xFE, 0x4E, 0xFB, 0x7C, 0x00, 0x32, 0x12, 0xCC, 0x20, 0x5C, 0x1B,
+	0x69, 0x08, 0x29, 0xFC, 0xBC, 0xFC, 0x69, 0x00, 0xC7, 0x00, 0x09,
+	0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7D, 0x00, 0xE3, 0x00, 0x21, 0xFE,
+	0x43, 0xFB, 0xE9, 0x02, 0xA9, 0x15, 0x79, 0x21, 0x6B, 0x18, 0x48,
+	0x05, 0x80, 0xFB, 0x7C, 0xFD, 0xB9, 0x00, 0xA0, 0x00, 0xFD, 0xFF,
+	0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xA6, 0x00, 0xAF, 0x00, 0x5E,
+	0xFD, 0x93, 0xFB, 0xBE, 0x05, 0xE5, 0x18, 0x6B, 0x21, 0x23, 0x15,
+	0x82, 0x02, 0x3F, 0xFB, 0x3F, 0xFE, 0xE9, 0x00, 0x77, 0x00, 0xF8,
+	0xFF, 0xFE, 0xFF, 0x0C, 0x00, 0xCD, 0x00, 0x5A, 0x00, 0xA0, 0xFC,
+	0x4D, 0xFC, 0xEA, 0x08, 0xC6, 0x1B, 0xA1, 0x20, 0xA6, 0x11, 0x26,
+	0x00, 0x57, 0xFB, 0xF8, 0xFE, 0xFB, 0x00, 0x50, 0x00, 0xF7, 0xFF,
+	0xFB, 0xFF, 0x21, 0x00, 0xEB, 0x00, 0xE3, 0xFF, 0xFA, 0xFB, 0x7C,
+	0xFD, 0x54, 0x0C, 0x2B, 0x1E, 0x26, 0x1F, 0x17, 0x0E, 0x41, 0xFE,
+	0xB6, 0xFB, 0x9C, 0xFF, 0xF5, 0x00, 0x2F, 0x00, 0xF9, 0xFF, 0xF8,
+	0xFF, 0x3F, 0x00, 0xFB, 0x00, 0x4D, 0xFF, 0x7F, 0xFB, 0x24, 0xFF,
+	0xDF, 0x0F, 0xF9, 0x1F, 0x09, 0x1D, 0x99, 0x0A, 0xD5, 0xFC, 0x49,
+	0xFC, 0x23, 0x00, 0xDD, 0x00, 0x16, 0x00, 0xFC, 0xFF, 0xF7, 0xFF,
+	0x63, 0x00, 0xF5, 0x00, 0x9E, 0xFE, 0x41, 0xFB, 0x46, 0x01, 0x69,
+	0x13, 0x1D, 0x21, 0x63, 0x1A, 0x4B, 0x07, 0xE2, 0xFB, 0xFD, 0xFC,
+	0x89, 0x00, 0xBA, 0x00, 0x04, 0x00, 0xFF, 0xFF, 0xFA, 0xFF, 0x8B,
+	0x00, 0xD5, 0x00, 0xDE, 0xFD, 0x53, 0xFB, 0xD9, 0x03, 0xD0, 0x16,
+	0x8A, 0x21, 0x51, 0x17, 0x47, 0x04, 0x5E, 0xFB, 0xC0, 0xFD, 0xCD,
+	0x00, 0x92, 0x00, 0xFB, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00,
+	0xB4, 0x00, 0x96, 0x00, 0x1B, 0xFD, 0xC7, 0xFB, 0xCF, 0x06, 0xF0,
+	0x19, 0x3A, 0x21, 0xF2, 0x13, 0xA4, 0x01, 0x3E, 0xFB, 0x81, 0xFE,
+	0xF2, 0x00, 0x69, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x12, 0x00, 0xD9,
+	0x00, 0x35, 0x00, 0x63, 0xFC, 0xA8, 0xFC, 0x13, 0x0A, 0xA9, 0x1C,
+	0x32, 0x20, 0x6B, 0x10, 0x71, 0xFF, 0x71, 0xFB, 0x34, 0xFF, 0xFB,
+	0x00, 0x44, 0x00, 0xF8, 0xFF, 0xFA, 0xFF, 0x2B, 0x00, 0xF3, 0x00,
+	0xB3, 0xFF, 0xCA, 0xFB, 0x01, 0xFE, 0x8C, 0x0D, 0xDD, 0x1E, 0x7C,
+	0x1E, 0xDE, 0x0C, 0xB5, 0xFD, 0xE4, 0xFB, 0xCE, 0xFF, 0xEF, 0x00,
+	0x25, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x4A, 0x00, 0xFC, 0x00, 0x13,
+	0xFF, 0x61, 0xFB, 0xD4, 0xFF, 0x1A, 0x11, 0x72, 0x20, 0x2D, 0x1C,
+	0x6D, 0x09, 0x74, 0xFC, 0x85, 0xFC, 0x4A, 0x00, 0xD2, 0x00, 0x0F,
+	0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x70, 0x00, 0xED, 0x00, 0x5D, 0xFE,
+	0x3D, 0xFB, 0x1E, 0x02, 0x9C, 0x14, 0x58, 0x21, 0x5E, 0x19, 0x36,
+	0x06, 0xA8, 0xFB, 0x40, 0xFD, 0xA4, 0x00, 0xAD, 0x00, 0x00, 0x00,
+	0xFF, 0xFF, 0x00, 0x00, 0xFC, 0xFF, 0x9A, 0x00, 0xC3, 0x00, 0x9A,
+	0xFD, 0x6F, 0xFB, 0xD5, 0x04, 0xEF, 0x17, 0x83, 0x21, 0x2D, 0x16,
+	0x52, 0x03, 0x49, 0xFB, 0x04, 0xFE, 0xDD, 0x00, 0x83, 0x00, 0xF9,
+	0xFF, 0xFF, 0xFF, 0x07, 0x00, 0xC2, 0x00, 0x78, 0x00, 0xD9, 0xFC,
+	0x08, 0xFC, 0xE9, 0x07, 0xEF, 0x1A, 0xF3, 0x20, 0xBC, 0x12, 0xD4,
+	0x00, 0x47, 0xFB, 0xC1, 0xFE, 0xF8, 0x00, 0x5B, 0x00, 0xF7, 0xFF,
+	0xFC, 0xFF, 0x1A, 0x00, 0xE3, 0x00, 0x0B, 0x00, 0x2A, 0xFC, 0x12,
+	0xFD, 0x42, 0x0B, 0x7D, 0x1D, 0xAD, 0x1F, 0x2F, 0x0F, 0xC9, 0xFE,
+	0x92, 0xFB, 0x6C, 0xFF, 0xF9, 0x00, 0x38, 0x00, 0xF8, 0xFF, 0xF9,
+	0xFF, 0x35, 0x00, 0xF8, 0x00, 0x7E, 0xFF, 0x9F, 0xFB, 0x95, 0xFE,
+	0xC6, 0x0E, 0x7C, 0x1F, 0xC0, 0x1D, 0xA9, 0x0B, 0x38, 0xFD, 0x18,
+	0xFC, 0xFD, 0xFF, 0xE6, 0x00, 0x1D, 0x00, 0xFB, 0xFF, 0xF7, 0xFF,
+	0x57, 0x00, 0xFA, 0x00, 0xD6, 0xFE, 0x4C, 0xFB, 0x92, 0x00, 0x54,
+	0x12, 0xD6, 0x20, 0x41, 0x1B, 0x49, 0x08, 0x20, 0xFC, 0xC3, 0xFC,
+	0x6D, 0x00, 0xC6, 0x00, 0x09, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7E,
+	0x00, 0xE2, 0x00, 0x1A, 0xFE, 0x44, 0xFB, 0x03, 0x03, 0xCA, 0x15,
+	0x7C, 0x21, 0x4C, 0x18, 0x2B, 0x05, 0x7B, 0xFB, 0x83, 0xFD, 0xBC,
+	0x00, 0x9E, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+	0xA8, 0x00, 0xAD, 0x00, 0x56, 0xFD, 0x98, 0xFB, 0xDC, 0x05, 0x04,
+	0x19, 0x66, 0x21, 0x02, 0x15, 0x69, 0x02, 0x3E, 0xFB, 0x47, 0xFE,
+	0xEA, 0x00, 0x75, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0D, 0x00, 0xCE,
+	0x00, 0x56, 0x00, 0x99, 0xFC, 0x56, 0xFC, 0x0B, 0x09, 0xE0, 0x1B,
+	0x96, 0x20, 0x83, 0x11, 0x11, 0x00, 0x59, 0xFB, 0xFF, 0xFE, 0xFB,
+	0x00, 0x4E, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x22, 0x00, 0xEC, 0x00,
+	0xDE, 0xFF, 0xF5, 0xFB, 0x8A, 0xFD, 0x77, 0x0C, 0x3F, 0x1E, 0x14,
+	0x1F, 0xF5, 0x0D, 0x31, 0xFE, 0xBB, 0xFB, 0xA2, 0xFF, 0xF5, 0x00,
+	0x2E, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x40, 0x00, 0xFB, 0x00, 0x47,
+	0xFF, 0x7B, 0xFB, 0x37, 0xFF, 0x02, 0x10, 0x07, 0x20, 0xF2, 0x1C,
+	0x78, 0x0A, 0xCA, 0xFC, 0x50, 0xFC, 0x27, 0x00, 0xDC, 0x00, 0x15,
+	0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x64, 0x00, 0xF5, 0x00, 0x97, 0xFE,
+	0x40, 0xFB, 0x5D, 0x01, 0x8B, 0x13, 0x25, 0x21, 0x47, 0x1A, 0x2C,
+	0x07, 0xDB, 0xFB, 0x05, 0xFD, 0x8C, 0x00, 0xB9, 0x00, 0x04, 0x00,
+	0xFF, 0xFF, 0xFA, 0xFF, 0x8D, 0x00, 0xD3, 0x00, 0xD6, 0xFD, 0x56,
+	0xFB, 0xF4, 0x03, 0xF0, 0x16, 0x8A, 0x21, 0x31, 0x17, 0x2B, 0x04,
+	0x5B, 0xFB, 0xC7, 0xFD, 0xCF, 0x00, 0x90, 0x00, 0xFA, 0xFF, 0x00,
+	0x00, 0xFF, 0xFF, 0x03, 0x00, 0xB6, 0x00, 0x92, 0x00, 0x13, 0xFD,
+	0xCD, 0xFB, 0xEE, 0x06, 0x0D, 0x1A, 0x33, 0x21, 0xD0, 0x13, 0x8C,
+	0x01, 0x3E, 0xFB, 0x88, 0xFE, 0xF3, 0x00, 0x67, 0x00, 0xF7, 0xFF,
+	0x06, 0x00, 0x1D, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0xA1, 0x02, 0xA6,
+	0xF8, 0x56, 0x02, 0xA5, 0x28, 0xA5, 0x28, 0x56, 0x02, 0xA6, 0xF8,
+	0xA1, 0x02, 0xFE, 0x00, 0x03, 0xFF, 0x1D, 0x00, 0x06, 0x00, 0x00,
+	0x00, 0x21, 0x00, 0xA6, 0xFF, 0x3F, 0xFF, 0x0B, 0x03, 0x42, 0xFE,
+	0x3E, 0xF8, 0x7F, 0x15, 0xAC, 0x30, 0x7F, 0x15, 0x3E, 0xF8, 0x42,
+	0xFE, 0x0B, 0x03, 0x3F, 0xFF, 0xA6, 0xFF, 0x21, 0x00, 0x00, 0x00,
+	0xFA, 0xFF, 0xCE, 0xFF, 0x14, 0x01, 0x00, 0xFD, 0x35, 0x06, 0xD5,
+	0xF4, 0xDA, 0x15, 0x92, 0x40, 0xAE, 0xFE, 0xF3, 0xFC, 0x68, 0x03,
+	0x86, 0xFD, 0x51, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xEC,
+	0xFF, 0xF9, 0xFF, 0xC6, 0x00, 0x55, 0xFD, 0x35, 0x06, 0x90, 0xF3,
+	0xE5, 0x1C, 0x6B, 0x3D, 0x71, 0xFA, 0x34, 0xFF, 0x46, 0x02, 0xFF,
+	0xFD, 0x2D, 0x01, 0x90, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDB, 0xFF,
+	0x2D, 0x00, 0x60, 0x00, 0xE1, 0xFD, 0xCE, 0x05, 0xED, 0xF2, 0xF3,
+	0x23, 0x20, 0x39, 0x22, 0xF7, 0x44, 0x01, 0x1F, 0x01, 0x89, 0xFE,
+	0xFB, 0x00, 0x9C, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC9, 0xFF, 0x68,
+	0x00, 0xE5, 0xFF, 0xA0, 0xFE, 0xFB, 0x04, 0x0C, 0xF3, 0xC5, 0x2A,
+	0xD8, 0x33, 0xC9, 0xF4, 0x0B, 0x03, 0x05, 0x00, 0x1A, 0xFF, 0xC1,
+	0x00, 0xAD, 0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB5, 0xFF, 0xA5, 0x00,
+	0x5C, 0xFF, 0x8C, 0xFF, 0xBF, 0x03, 0x06, 0xF4, 0x22, 0x31, 0xC8,
+	0x2D, 0x63, 0xF3, 0x76, 0x04, 0x08, 0xFF, 0xA7, 0xFF, 0x84, 0x00,
+	0xC0, 0xFF, 0x07, 0x00, 0x0C, 0x00, 0xA4, 0xFF, 0xE1, 0x00, 0xCB,
+	0xFE, 0x9B, 0x00, 0x21, 0x02, 0xEE, 0xF5, 0xCD, 0x36, 0x24, 0x27,
+	0xE1, 0xF2, 0x7A, 0x05, 0x33, 0xFE, 0x2A, 0x00, 0x47, 0x00, 0xD3,
+	0xFF, 0x04, 0x00, 0x0F, 0x00, 0x95, 0xFF, 0x17, 0x01, 0x3D, 0xFE,
+	0xBD, 0x01, 0x30, 0x00, 0xCC, 0xF8, 0x92, 0x3B, 0x2A, 0x20, 0x2E,
+	0xF3, 0x12, 0x06, 0x8F, 0xFD, 0x9A, 0x00, 0x10, 0x00, 0xE5, 0xFF,
+	0x02, 0x00, 0x10, 0x00, 0x8C, 0xFF, 0x42, 0x01, 0xBB, 0xFD, 0xE4,
+	0x02, 0x01, 0xFE, 0x9C, 0xFC, 0x45, 0x3F, 0x16, 0x19, 0x2D, 0xF4,
+	0x41, 0x06, 0x21, 0xFD, 0xF3, 0x00, 0xE0, 0xFF, 0xF4, 0xFF, 0x01,
+	0x00, 0x10, 0x00, 0x8B, 0xFF, 0x5D, 0x01, 0x4F, 0xFD, 0xFB, 0x03,
+	0xB2, 0xFB, 0x53, 0x01, 0xC2, 0x41, 0x24, 0x12, 0xBA, 0xF5, 0x0F,
+	0x06, 0xE9, 0xFC, 0x33, 0x01, 0xBB, 0xFF, 0x00, 0x00, 0x00, 0x00,
+	0x0D, 0x00, 0x93, 0xFF, 0x63, 0x01, 0x04, 0xFD, 0xEF, 0x04, 0x62,
+	0xF9, 0xD7, 0x06, 0xF2, 0x42, 0x8D, 0x0B, 0xB0, 0xF7, 0x87, 0x05,
+	0xE6, 0xFC, 0x58, 0x01, 0xA0, 0xFF, 0x09, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x07, 0x00, 0xA5, 0xFF, 0x52, 0x01, 0xE2, 0xFC, 0xAD, 0x05,
+	0x35, 0xF7, 0x08, 0x0D, 0xCB, 0x42, 0x81, 0x05, 0xE8, 0xF9, 0xBB,
+	0x04, 0x12, 0xFD, 0x64, 0x01, 0x90, 0xFF, 0x0E, 0x00, 0x00, 0x00,
+	0xFE, 0xFF, 0xC2, 0xFF, 0x27, 0x01, 0xF1, 0xFC, 0x22, 0x06, 0x54,
+	0xF5, 0xB8, 0x13, 0x4A, 0x41, 0x29, 0x00, 0x3C, 0xFC, 0xBD, 0x03,
+	0x66, 0xFD, 0x58, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xF1,
+	0xFF, 0xEB, 0xFF, 0xE1, 0x00, 0x35, 0xFD, 0x40, 0x06, 0xE4, 0xF3,
+	0xB7, 0x1A, 0x85, 0x3E, 0xA6, 0xFB, 0x86, 0xFE, 0xA0, 0x02, 0xD7,
+	0xFD, 0x39, 0x01, 0x8E, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xE1, 0xFF,
+	0x1C, 0x00, 0x82, 0x00, 0xB0, 0xFD, 0xF9, 0x05, 0x0C, 0xF3, 0xCB,
+	0x21, 0x8F, 0x3A, 0x0D, 0xF8, 0xA9, 0x00, 0x79, 0x01, 0x5D, 0xFE,
+	0x0B, 0x01, 0x98, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCE, 0xFF, 0x55,
+	0x00, 0x0D, 0x00, 0x60, 0xFE, 0x48, 0x05, 0xEC, 0xF2, 0xB6, 0x28,
+	0x91, 0x35, 0x68, 0xF5, 0x88, 0x02, 0x5A, 0x00, 0xED, 0xFE, 0xD4,
+	0x00, 0xA8, 0xFF, 0x0B, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0x92, 0x00,
+	0x87, 0xFF, 0x3F, 0xFF, 0x2B, 0x04, 0xA1, 0xF3, 0x3D, 0x2F, 0xB8,
+	0x2F, 0xB8, 0xF3, 0x11, 0x04, 0x52, 0xFF, 0x7C, 0xFF, 0x97, 0x00,
+	0xBA, 0xFF, 0x08, 0x00, 0x0B, 0x00, 0xA9, 0xFF, 0xCF, 0x00, 0xF8,
+	0xFE, 0x44, 0x00, 0xAA, 0x02, 0x3E, 0xF5, 0x24, 0x35, 0x3B, 0x29,
+	0xF2, 0xF2, 0x35, 0x05, 0x70, 0xFE, 0x03, 0x00, 0x5A, 0x00, 0xCD,
+	0xFF, 0x05, 0x00, 0x0E, 0x00, 0x99, 0xFF, 0x07, 0x01, 0x68, 0xFE,
+	0x63, 0x01, 0xD0, 0x00, 0xD0, 0xF7, 0x35, 0x3A, 0x55, 0x22, 0x02,
+	0xF3, 0xEF, 0x05, 0xBC, 0xFD, 0x7A, 0x00, 0x20, 0x00, 0xDF, 0xFF,
+	0x03, 0x00, 0x10, 0x00, 0x8E, 0xFF, 0x36, 0x01, 0xE1, 0xFD, 0x8A,
+	0x02, 0xB2, 0xFE, 0x56, 0xFB, 0x40, 0x3E, 0x42, 0x1B, 0xCE, 0xF3,
+	0x3E, 0x06, 0x3D, 0xFD, 0xDB, 0x00, 0xEE, 0xFF, 0xF0, 0xFF, 0x01,
+	0x00, 0x11, 0x00, 0x8A, 0xFF, 0x57, 0x01, 0x6D, 0xFD, 0xA8, 0x03,
+	0x69, 0xFC, 0xC8, 0xFF, 0x20, 0x41, 0x40, 0x14, 0x33, 0xF5, 0x28,
+	0x06, 0xF5, 0xFC, 0x22, 0x01, 0xC5, 0xFF, 0xFD, 0xFF, 0x00, 0x00,
+	0x0F, 0x00, 0x8F, 0xFF, 0x64, 0x01, 0x17, 0xFD, 0xA9, 0x04, 0x16,
+	0xFA, 0x10, 0x05, 0xB8, 0x42, 0x87, 0x0D, 0x0D, 0xF7, 0xB9, 0x05,
+	0xE2, 0xFC, 0x50, 0x01, 0xA7, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x0A, 0x00, 0x9E, 0xFF, 0x5A, 0x01, 0xE8, 0xFC, 0x7A, 0x05,
+	0xDA, 0xF7, 0x10, 0x0B, 0xFB, 0x42, 0x4B, 0x07, 0x35, 0xF9, 0x00,
+	0x05, 0x00, 0xFD, 0x63, 0x01, 0x94, 0xFF, 0x0D, 0x00, 0x00, 0x00,
+	0x01, 0x00, 0xB8, 0xFF, 0x37, 0x01, 0xE7, 0xFC, 0x07, 0x06, 0xDE,
+	0xF5, 0x9F, 0x11, 0xE4, 0x41, 0xB8, 0x01, 0x84, 0xFB, 0x0F, 0x04,
+	0x48, 0xFD, 0x5E, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF5,
+	0xFF, 0xDD, 0xFF, 0xF9, 0x00, 0x1B, 0xFD, 0x41, 0x06, 0x47, 0xF4,
+	0x8B, 0x18, 0x81, 0x3F, 0xF1, 0xFC, 0xD5, 0xFD, 0xFA, 0x02, 0xB2,
+	0xFD, 0x45, 0x01, 0x8C, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE6, 0xFF,
+	0x0C, 0x00, 0xA2, 0x00, 0x85, 0xFD, 0x1A, 0x06, 0x3C, 0xF3, 0x9F,
+	0x1F, 0xE6, 0x3B, 0x0E, 0xF9, 0x07, 0x00, 0xD4, 0x01, 0x33, 0xFE,
+	0x1B, 0x01, 0x94, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD4, 0xFF, 0x43,
+	0x00, 0x33, 0x00, 0x25, 0xFE, 0x89, 0x05, 0xE0, 0xF2, 0x9C, 0x26,
+	0x33, 0x37, 0x1E, 0xF6, 0xFD, 0x01, 0xB0, 0x00, 0xC0, 0xFE, 0xE6,
+	0x00, 0xA2, 0xFF, 0x0C, 0x00, 0x07, 0x00, 0xC1, 0xFF, 0x7F, 0x00,
+	0xB2, 0xFF, 0xF6, 0xFE, 0x8E, 0x04, 0x51, 0xF3, 0x49, 0x2D, 0x98,
+	0x31, 0x23, 0xF4, 0xA2, 0x03, 0xA0, 0xFF, 0x51, 0xFF, 0xAA, 0x00,
+	0xB4, 0xFF, 0x09, 0x00, 0x0A, 0x00, 0xAE, 0xFF, 0xBD, 0x00, 0x25,
+	0xFF, 0xF1, 0xFF, 0x2B, 0x03, 0xA5, 0xF4, 0x68, 0x33, 0x48, 0x2B,
+	0x17, 0xF3, 0xE7, 0x04, 0xB1, 0xFE, 0xDB, 0xFF, 0x6C, 0x00, 0xC7,
+	0xFF, 0x06, 0x00, 0x0D, 0x00, 0x9E, 0xFF, 0xF7, 0x00, 0x94, 0xFE,
+	0x09, 0x01, 0x6A, 0x01, 0xEB, 0xF6, 0xC1, 0x38, 0x7D, 0x24, 0xE8,
+	0xF2, 0xC1, 0x05, 0xEE, 0xFD, 0x57, 0x00, 0x31, 0x00, 0xDA, 0xFF,
+	0x03, 0x00, 0x10, 0x00, 0x91, 0xFF, 0x29, 0x01, 0x09, 0xFE, 0x2F,
+	0x02, 0x5F, 0xFF, 0x27, 0xFA, 0x20, 0x3D, 0x70, 0x1D, 0x7D, 0xF3,
+	0x31, 0x06, 0x5E, 0xFD, 0xBF, 0x00, 0xFD, 0xFF, 0xEB, 0xFF, 0x02,
+	0x00, 0x11, 0x00, 0x8B, 0xFF, 0x4E, 0x01, 0x8E, 0xFD, 0x52, 0x03,
+	0x20, 0xFD, 0x52, 0xFE, 0x60, 0x40, 0x63, 0x16, 0xB7, 0xF4, 0x39,
+	0x06, 0x05, 0xFD, 0x0F, 0x01, 0xD1, 0xFF, 0xF9, 0xFF, 0x00, 0x00,
+	0x10, 0x00, 0x8D, 0xFF, 0x62, 0x01, 0x2E, 0xFD, 0x5E, 0x04, 0xCC,
+	0xFA, 0x5B, 0x03, 0x5E, 0x42, 0x8E, 0x0F, 0x71, 0xF6, 0xE4, 0x05,
+	0xE2, 0xFC, 0x45, 0x01, 0xAF, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x0B, 0x00, 0x99, 0xFF, 0x60, 0x01, 0xF2, 0xFC, 0x40, 0x05,
+	0x85, 0xF8, 0x26, 0x09, 0x0C, 0x43, 0x26, 0x09, 0x85, 0xF8, 0x40,
+	0x05, 0xF2, 0xFC, 0x60, 0x01, 0x99, 0xFF, 0x0B, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0xAF, 0xFF, 0x45, 0x01, 0xE2, 0xFC, 0xE4, 0x05, 0x71,
+	0xF6, 0x8E, 0x0F, 0x5E, 0x42, 0x5B, 0x03, 0xCC, 0xFA, 0x5E, 0x04,
+	0x2E, 0xFD, 0x62, 0x01, 0x8D, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xF9,
+	0xFF, 0xD1, 0xFF, 0x0F, 0x01, 0x05, 0xFD, 0x39, 0x06, 0xB7, 0xF4,
+	0x63, 0x16, 0x60, 0x40, 0x52, 0xFE, 0x20, 0xFD, 0x52, 0x03, 0x8E,
+	0xFD, 0x4E, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xEB, 0xFF,
+	0xFD, 0xFF, 0xBF, 0x00, 0x5E, 0xFD, 0x31, 0x06, 0x7D, 0xF3, 0x70,
+	0x1D, 0x20, 0x3D, 0x27, 0xFA, 0x5F, 0xFF, 0x2F, 0x02, 0x09, 0xFE,
+	0x29, 0x01, 0x91, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDA, 0xFF, 0x31,
+	0x00, 0x57, 0x00, 0xEE, 0xFD, 0xC1, 0x05, 0xE8, 0xF2, 0x7D, 0x24,
+	0xC1, 0x38, 0xEB, 0xF6, 0x6A, 0x01, 0x09, 0x01, 0x94, 0xFE, 0xF7,
+	0x00, 0x9E, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC7, 0xFF, 0x6C, 0x00,
+	0xDB, 0xFF, 0xB1, 0xFE, 0xE7, 0x04, 0x17, 0xF3, 0x48, 0x2B, 0x68,
+	0x33, 0xA5, 0xF4, 0x2B, 0x03, 0xF1, 0xFF, 0x25, 0xFF, 0xBD, 0x00,
+	0xAE, 0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB4, 0xFF, 0xAA, 0x00, 0x51,
+	0xFF, 0xA0, 0xFF, 0xA2, 0x03, 0x23, 0xF4, 0x98, 0x31, 0x49, 0x2D,
+	0x51, 0xF3, 0x8E, 0x04, 0xF6, 0xFE, 0xB2, 0xFF, 0x7F, 0x00, 0xC1,
+	0xFF, 0x07, 0x00, 0x0C, 0x00, 0xA2, 0xFF, 0xE6, 0x00, 0xC0, 0xFE,
+	0xB0, 0x00, 0xFD, 0x01, 0x1E, 0xF6, 0x33, 0x37, 0x9C, 0x26, 0xE0,
+	0xF2, 0x89, 0x05, 0x25, 0xFE, 0x33, 0x00, 0x43, 0x00, 0xD4, 0xFF,
+	0x04, 0x00, 0x0F, 0x00, 0x94, 0xFF, 0x1B, 0x01, 0x33, 0xFE, 0xD4,
+	0x01, 0x07, 0x00, 0x0E, 0xF9, 0xE6, 0x3B, 0x9F, 0x1F, 0x3C, 0xF3,
+	0x1A, 0x06, 0x85, 0xFD, 0xA2, 0x00, 0x0C, 0x00, 0xE6, 0xFF, 0x02,
+	0x00, 0x11, 0x00, 0x8C, 0xFF, 0x45, 0x01, 0xB2, 0xFD, 0xFA, 0x02,
+	0xD5, 0xFD, 0xF1, 0xFC, 0x81, 0x3F, 0x8B, 0x18, 0x47, 0xF4, 0x41,
+	0x06, 0x1B, 0xFD, 0xF9, 0x00, 0xDD, 0xFF, 0xF5, 0xFF, 0x01, 0x00,
+	0x10, 0x00, 0x8B, 0xFF, 0x5E, 0x01, 0x48, 0xFD, 0x0F, 0x04, 0x84,
+	0xFB, 0xB8, 0x01, 0xE4, 0x41, 0x9F, 0x11, 0xDE, 0xF5, 0x07, 0x06,
+	0xE7, 0xFC, 0x37, 0x01, 0xB8, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x0D,
+	0x00, 0x94, 0xFF, 0x63, 0x01, 0x00, 0xFD, 0x00, 0x05, 0x35, 0xF9,
+	0x4B, 0x07, 0xFB, 0x42, 0x10, 0x0B, 0xDA, 0xF7, 0x7A, 0x05, 0xE8,
+	0xFC, 0x5A, 0x01, 0x9E, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x07, 0x00, 0xA7, 0xFF, 0x50, 0x01, 0xE2, 0xFC, 0xB9, 0x05, 0x0D,
+	0xF7, 0x87, 0x0D, 0xB8, 0x42, 0x10, 0x05, 0x16, 0xFA, 0xA9, 0x04,
+	0x17, 0xFD, 0x64, 0x01, 0x8F, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFD,
+	0xFF, 0xC5, 0xFF, 0x22, 0x01, 0xF5, 0xFC, 0x28, 0x06, 0x33, 0xF5,
+	0x40, 0x14, 0x20, 0x41, 0xC8, 0xFF, 0x69, 0xFC, 0xA8, 0x03, 0x6D,
+	0xFD, 0x57, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xF0, 0xFF,
+	0xEE, 0xFF, 0xDB, 0x00, 0x3D, 0xFD, 0x3E, 0x06, 0xCE, 0xF3, 0x42,
+	0x1B, 0x40, 0x3E, 0x56, 0xFB, 0xB2, 0xFE, 0x8A, 0x02, 0xE1, 0xFD,
+	0x36, 0x01, 0x8E, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDF, 0xFF, 0x20,
+	0x00, 0x7A, 0x00, 0xBC, 0xFD, 0xEF, 0x05, 0x02, 0xF3, 0x55, 0x22,
+	0x35, 0x3A, 0xD0, 0xF7, 0xD0, 0x00, 0x63, 0x01, 0x68, 0xFE, 0x07,
+	0x01, 0x99, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCD, 0xFF, 0x5A, 0x00,
+	0x03, 0x00, 0x70, 0xFE, 0x35, 0x05, 0xF2, 0xF2, 0x3B, 0x29, 0x24,
+	0x35, 0x3E, 0xF5, 0xAA, 0x02, 0x44, 0x00, 0xF8, 0xFE, 0xCF, 0x00,
+	0xA9, 0xFF, 0x0B, 0x00, 0x08, 0x00, 0xBA, 0xFF, 0x97, 0x00, 0x7C,
+	0xFF, 0x52, 0xFF, 0x11, 0x04, 0xB8, 0xF3, 0xB8, 0x2F, 0x3D, 0x2F,
+	0xA1, 0xF3, 0x2B, 0x04, 0x3F, 0xFF, 0x87, 0xFF, 0x92, 0x00, 0xBB,
+	0xFF, 0x08, 0x00, 0x0B, 0x00, 0xA8, 0xFF, 0xD4, 0x00, 0xED, 0xFE,
+	0x5A, 0x00, 0x88, 0x02, 0x68, 0xF5, 0x91, 0x35, 0xB6, 0x28, 0xEC,
+	0xF2, 0x48, 0x05, 0x60, 0xFE, 0x0D, 0x00, 0x55, 0x00, 0xCE, 0xFF,
+	0x05, 0x00, 0x0E, 0x00, 0x98, 0xFF, 0x0B, 0x01, 0x5D, 0xFE, 0x79,
+	0x01, 0xA9, 0x00, 0x0D, 0xF8, 0x8F, 0x3A, 0xCB, 0x21, 0x0C, 0xF3,
+	0xF9, 0x05, 0xB0, 0xFD, 0x82, 0x00, 0x1C, 0x00, 0xE1, 0xFF, 0x03,
+	0x00, 0x10, 0x00, 0x8E, 0xFF, 0x39, 0x01, 0xD7, 0xFD, 0xA0, 0x02,
+	0x86, 0xFE, 0xA6, 0xFB, 0x85, 0x3E, 0xB7, 0x1A, 0xE4, 0xF3, 0x40,
+	0x06, 0x35, 0xFD, 0xE1, 0x00, 0xEB, 0xFF, 0xF1, 0xFF, 0x01, 0x00,
+	0x11, 0x00, 0x8A, 0xFF, 0x58, 0x01, 0x66, 0xFD, 0xBD, 0x03, 0x3C,
+	0xFC, 0x29, 0x00, 0x4A, 0x41, 0xB8, 0x13, 0x54, 0xF5, 0x22, 0x06,
+	0xF1, 0xFC, 0x27, 0x01, 0xC2, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0x0E,
+	0x00, 0x90, 0xFF, 0x64, 0x01, 0x12, 0xFD, 0xBB, 0x04, 0xE8, 0xF9,
+	0x81, 0x05, 0xCB, 0x42, 0x08, 0x0D, 0x35, 0xF7, 0xAD, 0x05, 0xE2,
+	0xFC, 0x52, 0x01, 0xA5, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x09, 0x00, 0xA0, 0xFF, 0x58, 0x01, 0xE6, 0xFC, 0x87, 0x05, 0xB0,
+	0xF7, 0x8D, 0x0B, 0xF2, 0x42, 0xD7, 0x06, 0x62, 0xF9, 0xEF, 0x04,
+	0x04, 0xFD, 0x63, 0x01, 0x93, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0xBB, 0xFF, 0x33, 0x01, 0xE9, 0xFC, 0x0F, 0x06, 0xBA, 0xF5,
+	0x24, 0x12, 0xC2, 0x41, 0x53, 0x01, 0xB2, 0xFB, 0xFB, 0x03, 0x4F,
+	0xFD, 0x5D, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF4, 0xFF,
+	0xE0, 0xFF, 0xF3, 0x00, 0x21, 0xFD, 0x41, 0x06, 0x2D, 0xF4, 0x16,
+	0x19, 0x45, 0x3F, 0x9C, 0xFC, 0x01, 0xFE, 0xE4, 0x02, 0xBB, 0xFD,
+	0x42, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE5, 0xFF, 0x10,
+	0x00, 0x9A, 0x00, 0x8F, 0xFD, 0x12, 0x06, 0x2E, 0xF3, 0x2A, 0x20,
+	0x92, 0x3B, 0xCC, 0xF8, 0x30, 0x00, 0xBD, 0x01, 0x3D, 0xFE, 0x17,
+	0x01, 0x95, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD3, 0xFF, 0x47, 0x00,
+	0x2A, 0x00, 0x33, 0xFE, 0x7A, 0x05, 0xE1, 0xF2, 0x24, 0x27, 0xCD,
+	0x36, 0xEE, 0xF5, 0x21, 0x02, 0x9B, 0x00, 0xCB, 0xFE, 0xE1, 0x00,
+	0xA4, 0xFF, 0x0C, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0x84, 0x00, 0xA7,
+	0xFF, 0x08, 0xFF, 0x76, 0x04, 0x63, 0xF3, 0xC8, 0x2D, 0x22, 0x31,
+	0x06, 0xF4, 0xBF, 0x03, 0x8C, 0xFF, 0x5C, 0xFF, 0xA5, 0x00, 0xB5,
+	0xFF, 0x09, 0x00, 0x0A, 0x00, 0xAD, 0xFF, 0xC1, 0x00, 0x1A, 0xFF,
+	0x05, 0x00, 0x0B, 0x03, 0xC9, 0xF4, 0xD8, 0x33, 0xC5, 0x2A, 0x0C,
+	0xF3, 0xFB, 0x04, 0xA0, 0xFE, 0xE5, 0xFF, 0x68, 0x00, 0xC9, 0xFF,
+	0x06, 0x00, 0x0D, 0x00, 0x9C, 0xFF, 0xFB, 0x00, 0x89, 0xFE, 0x1F,
+	0x01, 0x44, 0x01, 0x22, 0xF7, 0x20, 0x39, 0xF3, 0x23, 0xED, 0xF2,
+	0xCE, 0x05, 0xE1, 0xFD, 0x60, 0x00, 0x2D, 0x00, 0xDB, 0xFF, 0x03,
+	0x00, 0x10, 0x00, 0x90, 0xFF, 0x2D, 0x01, 0xFF, 0xFD, 0x46, 0x02,
+	0x34, 0xFF, 0x71, 0xFA, 0x6B, 0x3D, 0xE5, 0x1C, 0x90, 0xF3, 0x35,
+	0x06, 0x55, 0xFD, 0xC6, 0x00, 0xF9, 0xFF, 0xEC, 0xFF, 0x01, 0x00,
+	0x11, 0x00, 0x8B, 0xFF, 0x51, 0x01, 0x86, 0xFD, 0x68, 0x03, 0xF3,
+	0xFC, 0xAE, 0xFE, 0x92, 0x40, 0xDA, 0x15, 0xD5, 0xF4, 0x35, 0x06,
+	0x00, 0xFD, 0x14, 0x01, 0xCE, 0xFF, 0xFA, 0xFF, 0x00, 0x00, 0x0F,
+	0x00, 0x8D, 0xFF, 0x63, 0x01, 0x28, 0xFD, 0x71, 0x04, 0x9E, 0xFA,
+	0xC7, 0x03, 0x79, 0x42, 0x0B, 0x0F, 0x97, 0xF6, 0xDA, 0x05, 0xE2,
+	0xFC, 0x48, 0x01, 0xAD, 0xFF, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x0B, 0x00, 0x9A, 0xFF, 0x5F, 0x01, 0xEF, 0xFC, 0x4F, 0x05, 0x5A,
+	0xF8, 0x9F, 0x09, 0x0A, 0x43, 0xAE, 0x08, 0xB1, 0xF8, 0x30, 0x05,
+	0xF5, 0xFC, 0x61, 0x01, 0x97, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x03,
+	0x00, 0xB1, 0xFF, 0x41, 0x01, 0xE3, 0xFC, 0xED, 0x05, 0x4C, 0xF6,
+	0x11, 0x10, 0x42, 0x42, 0xF1, 0x02, 0xFA, 0xFA, 0x4B, 0x04, 0x34,
+	0xFD, 0x61, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF8, 0xFF,
+	0xD4, 0xFF, 0x0A, 0x01, 0x0A, 0xFD, 0x3C, 0x06, 0x9A, 0xF4, 0xED,
+	0x16, 0x2A, 0x40, 0xF8, 0xFD, 0x4D, 0xFD, 0x3C, 0x03, 0x97, 0xFD,
+	0x4C, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xEA, 0xFF, 0x00,
+	0x00, 0xB8, 0x00, 0x67, 0xFD, 0x2C, 0x06, 0x6B, 0xF3, 0xFC, 0x1D,
+	0xD3, 0x3C, 0xDF, 0xF9, 0x89, 0xFF, 0x18, 0x02, 0x13, 0xFE, 0x26,
+	0x01, 0x92, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD9, 0xFF, 0x36, 0x00,
+	0x4E, 0x00, 0xFB, 0xFD, 0xB4, 0x05, 0xE4, 0xF2, 0x04, 0x25, 0x5F,
+	0x38, 0xB6, 0xF6, 0x90, 0x01, 0xF3, 0x00, 0x9F, 0xFE, 0xF3, 0x00,
+	0x9F, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC6, 0xFF, 0x71, 0x00, 0xD1,
+	0xFF, 0xC2, 0xFE, 0xD1, 0x04, 0x23, 0xF3, 0xC9, 0x2B, 0xF5, 0x32,
+	0x83, 0xF4, 0x49, 0x03, 0xDC, 0xFF, 0x30, 0xFF, 0xB8, 0x00, 0xB0,
+	0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB3, 0xFF, 0xAE, 0x00, 0x46, 0xFF,
+	0xB4, 0xFF, 0x85, 0x03, 0x42, 0xF4, 0x0E, 0x32, 0xCA, 0x2C, 0x41,
+	0xF3, 0xA5, 0x04, 0xE4, 0xFE, 0xBC, 0xFF, 0x7A, 0x00, 0xC3, 0xFF,
+	0x07, 0x00, 0x0D, 0x00, 0xA1, 0xFF, 0xEA, 0x00, 0xB5, 0xFE, 0xC6,
+	0x00, 0xD9, 0x01, 0x4F, 0xF6, 0x99, 0x37, 0x16, 0x26, 0xE0, 0xF2,
+	0x98, 0x05, 0x16, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0xD6, 0xFF, 0x04,
+	0x00, 0x0F, 0x00, 0x93, 0xFF, 0x1F, 0x01, 0x28, 0xFE, 0xEB, 0x01,
+	0xDD, 0xFF, 0x52, 0xF9, 0x36, 0x3C, 0x13, 0x1F, 0x4B, 0xF3, 0x20,
+	0x06, 0x7B, 0xFD, 0xA9, 0x00, 0x08, 0x00, 0xE7, 0xFF, 0x02, 0x00,
+	0x11, 0x00, 0x8C, 0xFF, 0x47, 0x01, 0xA9, 0xFD, 0x10, 0x03, 0xA8,
+	0xFD, 0x47, 0xFD, 0xBB, 0x3F, 0x01, 0x18, 0x62, 0xF4, 0x40, 0x06,
+	0x15, 0xFD, 0xFF, 0x00, 0xDA, 0xFF, 0xF6, 0xFF, 0x01, 0x00, 0x10,
+	0x00, 0x8B, 0xFF, 0x5F, 0x01, 0x41, 0xFD, 0x23, 0x04, 0x56, 0xFB,
+	0x1F, 0x02, 0x06, 0x42, 0x19, 0x11, 0x02, 0xF6, 0xFF, 0x05, 0xE5,
+	0xFC, 0x3B, 0x01, 0xB6, 0xFF, 0x02, 0x00, 0x00, 0x00, 0x0D, 0x00,
+	0x95, 0xFF, 0x62, 0x01, 0xFC, 0xFC, 0x10, 0x05, 0x09, 0xF9, 0xC1,
+	0x07, 0x03, 0x43, 0x94, 0x0A, 0x05, 0xF8, 0x6C, 0x05, 0xEA, 0xFC,
+	0x5C, 0x01, 0x9D, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
+	0x00, 0xA9, 0xFF, 0x4D, 0x01, 0xE1, 0xFC, 0xC4, 0x05, 0xE6, 0xF6,
+	0x08, 0x0E, 0xA5, 0x42, 0xA1, 0x04, 0x43, 0xFA, 0x97, 0x04, 0x1D,
+	0xFD, 0x64, 0x01, 0x8F, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFC, 0xFF,
+	0xC8, 0xFF, 0x1E, 0x01, 0xF8, 0xFC, 0x2D, 0x06, 0x13, 0xF5, 0xC8,
+	0x14, 0xF2, 0x40, 0x69, 0xFF, 0x97, 0xFC, 0x92, 0x03, 0x75, 0xFD,
+	0x55, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xEF, 0xFF, 0xF2,
+	0xFF, 0xD4, 0x00, 0x45, 0xFD, 0x3B, 0x06, 0xB8, 0xF3, 0xCE, 0x1B,
+	0xFB, 0x3D, 0x08, 0xFB, 0xDE, 0xFE, 0x73, 0x02, 0xEB, 0xFD, 0x33,
+	0x01, 0x8F, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDE, 0xFF, 0x25, 0x00,
+	0x71, 0x00, 0xC8, 0xFD, 0xE5, 0x05, 0xFA, 0xF2, 0xDF, 0x22, 0xDB,
+	0x39, 0x94, 0xF7, 0xF7, 0x00, 0x4C, 0x01, 0x73, 0xFE, 0x03, 0x01,
+	0x9A, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCC, 0xFF, 0x5E, 0x00, 0xF9,
+	0xFF, 0x80, 0xFE, 0x23, 0x05, 0xF9, 0xF2, 0xC0, 0x29, 0xB8, 0x34,
+	0x16, 0xF5, 0xCB, 0x02, 0x2F, 0x00, 0x03, 0xFF, 0xCA, 0x00, 0xAA,
+	0xFF, 0x0B, 0x00, 0x08, 0x00, 0xB8, 0xFF, 0x9B, 0x00, 0x72, 0xFF,
+	0x65, 0xFF, 0xF6, 0x03, 0xD1, 0xF3, 0x31, 0x30, 0xC1, 0x2E, 0x8B,
+	0xF3, 0x45, 0x04, 0x2D, 0xFF, 0x92, 0xFF, 0x8D, 0x00, 0xBD, 0xFF,
+	0x08, 0x00, 0x0C, 0x00, 0xA6, 0xFF, 0xD8, 0x00, 0xE2, 0xFE, 0x6F,
+	0x00, 0x66, 0x02, 0x93, 0xF5, 0xFB, 0x35, 0x31, 0x28, 0xE7, 0xF2,
+	0x59, 0x05, 0x51, 0xFE, 0x17, 0x00, 0x50, 0x00, 0xD0, 0xFF, 0x05,
+	0x00, 0x0E, 0x00, 0x97, 0xFF, 0x0F, 0x01, 0x53, 0xFE, 0x90, 0x01,
+	0x81, 0x00, 0x4B, 0xF8, 0xE6, 0x3A, 0x3F, 0x21, 0x16, 0xF3, 0x02,
+	0x06, 0xA5, 0xFD, 0x8A, 0x00, 0x18, 0x00, 0xE2, 0xFF, 0x02, 0x00,
+	0x10, 0x00, 0x8D, 0xFF, 0x3C, 0x01, 0xCE, 0xFD, 0xB7, 0x02, 0x5A,
+	0xFE, 0xF7, 0xFB, 0xC6, 0x3E, 0x2C, 0x1A, 0xFC, 0xF3, 0x41, 0x06,
+	0x2E, 0xFD, 0xE7, 0x00, 0xE7, 0xFF, 0xF2, 0xFF, 0x01, 0x00, 0x10,
+	0x00, 0x8B, 0xFF, 0x5A, 0x01, 0x5E, 0xFD, 0xD2, 0x03, 0x0E, 0xFC,
+	0x8B, 0x00, 0x75, 0x41, 0x32, 0x13, 0x75, 0xF5, 0x1C, 0x06, 0xEE,
+	0xFC, 0x2B, 0x01, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0E, 0x00,
+	0x91, 0xFF, 0x64, 0x01, 0x0D, 0xFD, 0xCD, 0x04, 0xBB, 0xF9, 0xF2,
+	0x05, 0xD9, 0x42, 0x88, 0x0C, 0x5E, 0xF7, 0xA1, 0x05, 0xE3, 0xFC,
+	0x54, 0x01, 0xA3, 0xFF, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
+	0x00, 0xA2, 0xFF, 0x56, 0x01, 0xE5, 0xFC, 0x94, 0x05, 0x87, 0xF7,
+	0x0A, 0x0C, 0xE6, 0x42, 0x64, 0x06, 0x8E, 0xF9, 0xDE, 0x04, 0x09,
+	0xFD, 0x64, 0x01, 0x92, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xBD, 0xFF, 0x2F, 0x01, 0xEC, 0xFC, 0x16, 0x06, 0x98, 0xF5, 0xAB,
+	0x12, 0x9C, 0x41, 0xEE, 0x00, 0xE0, 0xFB, 0xE6, 0x03, 0x57, 0xFD,
+	0x5B, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF3, 0xFF, 0xE4,
+	0xFF, 0xED, 0x00, 0x27, 0xFD, 0x41, 0x06, 0x14, 0xF4, 0xA1, 0x19,
+	0x06, 0x3F, 0x49, 0xFC, 0x2E, 0xFE, 0xCD, 0x02, 0xC4, 0xFD, 0x3F,
+	0x01, 0x8D, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE3, 0xFF, 0x14, 0x00,
+	0x92, 0x00, 0x9A, 0xFD, 0x0A, 0x06, 0x22, 0xF3, 0xB4, 0x20, 0x3C,
+	0x3B, 0x8B, 0xF8, 0x58, 0x00, 0xA7, 0x01, 0x48, 0xFE, 0x13, 0x01,
+	0x96, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD1, 0xFF, 0x4C, 0x00, 0x20,
+	0x00, 0x42, 0xFE, 0x6A, 0x05, 0xE3, 0xF2, 0xAB, 0x27, 0x66, 0x36,
+	0xC0, 0xF5, 0x44, 0x02, 0x85, 0x00, 0xD7, 0xFE, 0xDD, 0x00, 0xA5,
+	0xFF, 0x0C, 0x00, 0x07, 0x00, 0xBE, 0xFF, 0x89, 0x00, 0x9D, 0xFF,
+	0x1A, 0xFF, 0x5E, 0x04, 0x76, 0xF3, 0x45, 0x2E, 0xAA, 0x30, 0xEB,
+	0xF3, 0xDB, 0x03, 0x79, 0xFF, 0x67, 0xFF, 0xA0, 0x00, 0xB7, 0xFF,
+	0x09, 0x00, 0x0B, 0x00, 0xAC, 0xFF, 0xC6, 0x00, 0x0E, 0xFF, 0x1A,
+	0x00, 0xEB, 0x02, 0xEF, 0xF4, 0x49, 0x34, 0x43, 0x2A, 0x02, 0xF3,
+	0x0F, 0x05, 0x90, 0xFE, 0xEF, 0xFF, 0x63, 0x00, 0xCA, 0xFF, 0x06,
+	0x00, 0x0E, 0x00, 0x9B, 0xFF, 0xFF, 0x00, 0x7E, 0xFE, 0x36, 0x01,
+	0x1E, 0x01, 0x5B, 0xF7, 0x7E, 0x39, 0x69, 0x23, 0xF3, 0xF2, 0xD9,
+	0x05, 0xD4, 0xFD, 0x69, 0x00, 0x29, 0x00, 0xDD, 0xFF, 0x03, 0x00,
+	0x10, 0x00, 0x90, 0xFF, 0x30, 0x01, 0xF5, 0xFD, 0x5C, 0x02, 0x09,
+	0xFF, 0xBC, 0xFA, 0xB5, 0x3D, 0x5A, 0x1C, 0xA3, 0xF3, 0x38, 0x06,
+	0x4D, 0xFD, 0xCD, 0x00, 0xF5, 0xFF, 0xED, 0xFF, 0x01, 0x00, 0x11,
+	0x00, 0x8B, 0xFF, 0x53, 0x01, 0x7E, 0xFD, 0x7D, 0x03, 0xC5, 0xFC,
+	0x0B, 0xFF, 0xC3, 0x40, 0x51, 0x15, 0xF4, 0xF4, 0x31, 0x06, 0xFC,
+	0xFC, 0x19, 0x01, 0xCB, 0xFF, 0xFB, 0xFF, 0x00, 0x00, 0x0F, 0x00,
+	0x8E, 0xFF, 0x63, 0x01, 0x22, 0xFD, 0x84, 0x04, 0x71, 0xFA, 0x34,
+	0x04, 0x90, 0x42, 0x89, 0x0E, 0xBE, 0xF6, 0xCF, 0x05, 0xE1, 0xFC,
+	0x4A, 0x01, 0xAB, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B,
+	0x00, 0x9B, 0xFF, 0x5D, 0x01, 0xEC, 0xFC, 0x5D, 0x05, 0x2F, 0xF8,
+	0x19, 0x0A, 0x07, 0x43, 0x37, 0x08, 0xDD, 0xF8, 0x21, 0x05, 0xF8,
+	0xFC, 0x62, 0x01, 0x96, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x03, 0x00,
+	0xB4, 0xFF, 0x3E, 0x01, 0xE4, 0xFC, 0xF6, 0x05, 0x26, 0xF6, 0x95,
+	0x10, 0x26, 0x42, 0x87, 0x02, 0x28, 0xFB, 0x37, 0x04, 0x3B, 0xFD,
+	0x60, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF7, 0xFF, 0xD7,
+	0xFF, 0x04, 0x01, 0x0F, 0xFD, 0x3E, 0x06, 0x7D, 0xF4, 0x76, 0x17,
+	0xF4, 0x3F, 0x9F, 0xFD, 0x7B, 0xFD, 0x26, 0x03, 0xA0, 0xFD, 0x4A,
+	0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE9, 0xFF, 0x04, 0x00,
+	0xB1, 0x00, 0x71, 0xFD, 0x26, 0x06, 0x5A, 0xF3, 0x88, 0x1E, 0x87,
+	0x3C, 0x98, 0xF9, 0xB3, 0xFF, 0x02, 0x02, 0x1E, 0xFE, 0x22, 0x01,
+	0x93, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD7, 0xFF, 0x3A, 0x00, 0x45,
+	0x00, 0x09, 0xFE, 0xA7, 0x05, 0xE1, 0xF2, 0x8D, 0x25, 0xFD, 0x37,
+	0x82, 0xF6, 0xB5, 0x01, 0xDC, 0x00, 0xAA, 0xFE, 0xEE, 0x00, 0xA0,
+	0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC4, 0xFF, 0x76, 0x00, 0xC7, 0xFF,
+	0xD3, 0xFE, 0xBC, 0x04, 0x31, 0xF3, 0x4A, 0x2C, 0x83, 0x32, 0x61,
+	0xF4, 0x68, 0x03, 0xC8, 0xFF, 0x3B, 0xFF, 0xB3, 0x00, 0xB1, 0xFF,
+	0x0A, 0x00, 0x0A, 0x00, 0xB1, 0xFF, 0xB3, 0x00, 0x3B, 0xFF, 0xC8,
+	0xFF, 0x68, 0x03, 0x61, 0xF4, 0x83, 0x32, 0x4A, 0x2C, 0x31, 0xF3,
+	0xBC, 0x04, 0xD3, 0xFE, 0xC7, 0xFF, 0x76, 0x00, 0xC4, 0xFF, 0x06,
+	0x00, 0x0D, 0x00, 0xA0, 0xFF, 0xEE, 0x00, 0xAA, 0xFE, 0xDC, 0x00,
+	0xB5, 0x01, 0x82, 0xF6, 0xFD, 0x37, 0x8D, 0x25, 0xE1, 0xF2, 0xA7,
+	0x05, 0x09, 0xFE, 0x45, 0x00, 0x3A, 0x00, 0xD7, 0xFF, 0x04, 0x00,
+	0x0F, 0x00, 0x93, 0xFF, 0x22, 0x01, 0x1E, 0xFE, 0x02, 0x02, 0xB3,
+	0xFF, 0x98, 0xF9, 0x87, 0x3C, 0x88, 0x1E, 0x5A, 0xF3, 0x26, 0x06,
+	0x71, 0xFD, 0xB1, 0x00, 0x04, 0x00, 0xE9, 0xFF, 0x02, 0x00, 0x11,
+	0x00, 0x8B, 0xFF, 0x4A, 0x01, 0xA0, 0xFD, 0x26, 0x03, 0x7B, 0xFD,
+	0x9F, 0xFD, 0xF4, 0x3F, 0x76, 0x17, 0x7D, 0xF4, 0x3E, 0x06, 0x0F,
+	0xFD, 0x04, 0x01, 0xD7, 0xFF, 0xF7, 0xFF, 0x01, 0x00, 0x10, 0x00,
+	0x8C, 0xFF, 0x60, 0x01, 0x3B, 0xFD, 0x37, 0x04, 0x28, 0xFB, 0x87,
+	0x02, 0x26, 0x42, 0x95, 0x10, 0x26, 0xF6, 0xF6, 0x05, 0xE4, 0xFC,
+	0x3E, 0x01, 0xB4, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x96,
+	0xFF, 0x62, 0x01, 0xF8, 0xFC, 0x21, 0x05, 0xDD, 0xF8, 0x37, 0x08,
+	0x07, 0x43, 0x19, 0x0A, 0x2F, 0xF8, 0x5D, 0x05, 0xEC, 0xFC, 0x5D,
+	0x01, 0x9B, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00,
+	0xAB, 0xFF, 0x4A, 0x01, 0xE1, 0xFC, 0xCF, 0x05, 0xBE, 0xF6, 0x89,
+	0x0E, 0x90, 0x42, 0x34, 0x04, 0x71, 0xFA, 0x84, 0x04, 0x22, 0xFD,
+	0x63, 0x01, 0x8E, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFB, 0xFF, 0xCB,
+	0xFF, 0x19, 0x01, 0xFC, 0xFC, 0x31, 0x06, 0xF4, 0xF4, 0x51, 0x15,
+	0xC3, 0x40, 0x0B, 0xFF, 0xC5, 0xFC, 0x7D, 0x03, 0x7E, 0xFD, 0x53,
+	0x01, 0x8B, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xED, 0xFF, 0xF5, 0xFF,
+	0xCD, 0x00, 0x4D, 0xFD, 0x38, 0x06, 0xA3, 0xF3, 0x5A, 0x1C, 0xB5,
+	0x3D, 0xBC, 0xFA, 0x09, 0xFF, 0x5C, 0x02, 0xF5, 0xFD, 0x30, 0x01,
+	0x90, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDD, 0xFF, 0x29, 0x00, 0x69,
+	0x00, 0xD4, 0xFD, 0xD9, 0x05, 0xF3, 0xF2, 0x69, 0x23, 0x7E, 0x39,
+	0x5B, 0xF7, 0x1E, 0x01, 0x36, 0x01, 0x7E, 0xFE, 0xFF, 0x00, 0x9B,
+	0xFF, 0x0E, 0x00, 0x06, 0x00, 0xCA, 0xFF, 0x63, 0x00, 0xEF, 0xFF,
+	0x90, 0xFE, 0x0F, 0x05, 0x02, 0xF3, 0x43, 0x2A, 0x49, 0x34, 0xEF,
+	0xF4, 0xEB, 0x02, 0x1A, 0x00, 0x0E, 0xFF, 0xC6, 0x00, 0xAC, 0xFF,
+	0x0B, 0x00, 0x09, 0x00, 0xB7, 0xFF, 0xA0, 0x00, 0x67, 0xFF, 0x79,
+	0xFF, 0xDB, 0x03, 0xEB, 0xF3, 0xAA, 0x30, 0x45, 0x2E, 0x76, 0xF3,
+	0x5E, 0x04, 0x1A, 0xFF, 0x9D, 0xFF, 0x89, 0x00, 0xBE, 0xFF, 0x07,
+	0x00, 0x0C, 0x00, 0xA5, 0xFF, 0xDD, 0x00, 0xD7, 0xFE, 0x85, 0x00,
+	0x44, 0x02, 0xC0, 0xF5, 0x66, 0x36, 0xAB, 0x27, 0xE3, 0xF2, 0x6A,
+	0x05, 0x42, 0xFE, 0x20, 0x00, 0x4C, 0x00, 0xD1, 0xFF, 0x04, 0x00,
+	0x0F, 0x00, 0x96, 0xFF, 0x13, 0x01, 0x48, 0xFE, 0xA7, 0x01, 0x58,
+	0x00, 0x8B, 0xF8, 0x3C, 0x3B, 0xB4, 0x20, 0x22, 0xF3, 0x0A, 0x06,
+	0x9A, 0xFD, 0x92, 0x00, 0x14, 0x00, 0xE3, 0xFF, 0x02, 0x00, 0x10,
+	0x00, 0x8D, 0xFF, 0x3F, 0x01, 0xC4, 0xFD, 0xCD, 0x02, 0x2E, 0xFE,
+	0x49, 0xFC, 0x06, 0x3F, 0xA1, 0x19, 0x14, 0xF4, 0x41, 0x06, 0x27,
+	0xFD, 0xED, 0x00, 0xE4, 0xFF, 0xF3, 0xFF, 0x01, 0x00, 0x10, 0x00,
+	0x8B, 0xFF, 0x5B, 0x01, 0x57, 0xFD, 0xE6, 0x03, 0xE0, 0xFB, 0xEE,
+	0x00, 0x9C, 0x41, 0xAB, 0x12, 0x98, 0xF5, 0x16, 0x06, 0xEC, 0xFC,
+	0x2F, 0x01, 0xBD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x92,
+	0xFF, 0x64, 0x01, 0x09, 0xFD, 0xDE, 0x04, 0x8E, 0xF9, 0x64, 0x06,
+	0xE6, 0x42, 0x0A, 0x0C, 0x87, 0xF7, 0x94, 0x05, 0xE5, 0xFC, 0x56,
+	0x01, 0xA2, 0xFF, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
+	0xA3, 0xFF, 0x54, 0x01, 0xE3, 0xFC, 0xA1, 0x05, 0x5E, 0xF7, 0x88,
+	0x0C, 0xD9, 0x42, 0xF2, 0x05, 0xBB, 0xF9, 0xCD, 0x04, 0x0D, 0xFD,
+	0x64, 0x01, 0x91, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0,
+	0xFF, 0x2B, 0x01, 0xEE, 0xFC, 0x1C, 0x06, 0x75, 0xF5, 0x32, 0x13,
+	0x75, 0x41, 0x8B, 0x00, 0x0E, 0xFC, 0xD2, 0x03, 0x5E, 0xFD, 0x5A,
+	0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF2, 0xFF, 0xE7, 0xFF,
+	0xE7, 0x00, 0x2E, 0xFD, 0x41, 0x06, 0xFC, 0xF3, 0x2C, 0x1A, 0xC6,
+	0x3E, 0xF7, 0xFB, 0x5A, 0xFE, 0xB7, 0x02, 0xCE, 0xFD, 0x3C, 0x01,
+	0x8D, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE2, 0xFF, 0x18, 0x00, 0x8A,
+	0x00, 0xA5, 0xFD, 0x02, 0x06, 0x16, 0xF3, 0x3F, 0x21, 0xE6, 0x3A,
+	0x4B, 0xF8, 0x81, 0x00, 0x90, 0x01, 0x53, 0xFE, 0x0F, 0x01, 0x97,
+	0xFF, 0x0E, 0x00, 0x05, 0x00, 0xD0, 0xFF, 0x50, 0x00, 0x17, 0x00,
+	0x51, 0xFE, 0x59, 0x05, 0xE7, 0xF2, 0x31, 0x28, 0xFB, 0x35, 0x93,
+	0xF5, 0x66, 0x02, 0x6F, 0x00, 0xE2, 0xFE, 0xD8, 0x00, 0xA6, 0xFF,
+	0x0C, 0x00, 0x08, 0x00, 0xBD, 0xFF, 0x8D, 0x00, 0x92, 0xFF, 0x2D,
+	0xFF, 0x45, 0x04, 0x8B, 0xF3, 0xC1, 0x2E, 0x31, 0x30, 0xD1, 0xF3,
+	0xF6, 0x03, 0x65, 0xFF, 0x72, 0xFF, 0x9B, 0x00, 0xB8, 0xFF, 0x08,
+	0x00, 0x0B, 0x00, 0xAA, 0xFF, 0xCA, 0x00, 0x03, 0xFF, 0x2F, 0x00,
+	0xCB, 0x02, 0x16, 0xF5, 0xB8, 0x34, 0xC0, 0x29, 0xF9, 0xF2, 0x23,
+	0x05, 0x80, 0xFE, 0xF9, 0xFF, 0x5E, 0x00, 0xCC, 0xFF, 0x05, 0x00,
+	0x0E, 0x00, 0x9A, 0xFF, 0x03, 0x01, 0x73, 0xFE, 0x4C, 0x01, 0xF7,
+	0x00, 0x94, 0xF7, 0xDB, 0x39, 0xDF, 0x22, 0xFA, 0xF2, 0xE5, 0x05,
+	0xC8, 0xFD, 0x71, 0x00, 0x25, 0x00, 0xDE, 0xFF, 0x03, 0x00, 0x10,
+	0x00, 0x8F, 0xFF, 0x33, 0x01, 0xEB, 0xFD, 0x73, 0x02, 0xDE, 0xFE,
+	0x08, 0xFB, 0xFB, 0x3D, 0xCE, 0x1B, 0xB8, 0xF3, 0x3B, 0x06, 0x45,
+	0xFD, 0xD4, 0x00, 0xF2, 0xFF, 0xEF, 0xFF, 0x01, 0x00, 0x11, 0x00,
+	0x8A, 0xFF, 0x55, 0x01, 0x75, 0xFD, 0x92, 0x03, 0x97, 0xFC, 0x69,
+	0xFF, 0xF2, 0x40, 0xC8, 0x14, 0x13, 0xF5, 0x2D, 0x06, 0xF8, 0xFC,
+	0x1E, 0x01, 0xC8, 0xFF, 0xFC, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0x8F,
+	0xFF, 0x64, 0x01, 0x1D, 0xFD, 0x97, 0x04, 0x43, 0xFA, 0xA1, 0x04,
+	0xA5, 0x42, 0x08, 0x0E, 0xE6, 0xF6, 0xC4, 0x05, 0xE1, 0xFC, 0x4D,
+	0x01, 0xA9, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00,
+	0x9D, 0xFF, 0x5C, 0x01, 0xEA, 0xFC, 0x6C, 0x05, 0x05, 0xF8, 0x94,
+	0x0A, 0x03, 0x43, 0xC1, 0x07, 0x09, 0xF9, 0x10, 0x05, 0xFC, 0xFC,
+	0x62, 0x01, 0x95, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x02, 0x00, 0xB6,
+	0xFF, 0x3B, 0x01, 0xE5, 0xFC, 0xFF, 0x05, 0x02, 0xF6, 0x19, 0x11,
+	0x06, 0x42, 0x1F, 0x02, 0x56, 0xFB, 0x23, 0x04, 0x41, 0xFD, 0x5F,
+	0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF6, 0xFF, 0xDA, 0xFF,
+	0xFF, 0x00, 0x15, 0xFD, 0x40, 0x06, 0x62, 0xF4, 0x01, 0x18, 0xBB,
+	0x3F, 0x47, 0xFD, 0xA8, 0xFD, 0x10, 0x03, 0xA9, 0xFD, 0x47, 0x01,
+	0x8C, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE7, 0xFF, 0x08, 0x00, 0xA9,
+	0x00, 0x7B, 0xFD, 0x20, 0x06, 0x4B, 0xF3, 0x13, 0x1F, 0x36, 0x3C,
+	0x52, 0xF9, 0xDD, 0xFF, 0xEB, 0x01, 0x28, 0xFE, 0x1F, 0x01, 0x93,
+	0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD6, 0xFF, 0x3F, 0x00, 0x3C, 0x00,
+	0x16, 0xFE, 0x98, 0x05, 0xE0, 0xF2, 0x16, 0x26, 0x99, 0x37, 0x4F,
+	0xF6, 0xD9, 0x01, 0xC6, 0x00, 0xB5, 0xFE, 0xEA, 0x00, 0xA1, 0xFF,
+	0x0D, 0x00, 0x07, 0x00, 0xC3, 0xFF, 0x7A, 0x00, 0xBC, 0xFF, 0xE4,
+	0xFE, 0xA5, 0x04, 0x41, 0xF3, 0xCA, 0x2C, 0x0E, 0x32, 0x42, 0xF4,
+	0x85, 0x03, 0xB4, 0xFF, 0x46, 0xFF, 0xAE, 0x00, 0xB3, 0xFF, 0x09,
+	0x00, 0x0A, 0x00, 0xB0, 0xFF, 0xB8, 0x00, 0x30, 0xFF, 0xDC, 0xFF,
+	0x49, 0x03, 0x83, 0xF4, 0xF5, 0x32, 0xC9, 0x2B, 0x23, 0xF3, 0xD1,
+	0x04, 0xC2, 0xFE, 0xD1, 0xFF, 0x71, 0x00, 0xC6, 0xFF, 0x06, 0x00,
+	0x0D, 0x00, 0x9F, 0xFF, 0xF3, 0x00, 0x9F, 0xFE, 0xF3, 0x00, 0x90,
+	0x01, 0xB6, 0xF6, 0x5F, 0x38, 0x04, 0x25, 0xE4, 0xF2, 0xB4, 0x05,
+	0xFB, 0xFD, 0x4E, 0x00, 0x36, 0x00, 0xD9, 0xFF, 0x04, 0x00, 0x0F,
+	0x00, 0x92, 0xFF, 0x26, 0x01, 0x13, 0xFE, 0x18, 0x02, 0x89, 0xFF,
+	0xDF, 0xF9, 0xD3, 0x3C, 0xFC, 0x1D, 0x6B, 0xF3, 0x2C, 0x06, 0x67,
+	0xFD, 0xB8, 0x00, 0x00, 0x00, 0xEA, 0xFF, 0x02, 0x00, 0x11, 0x00,
+	0x8B, 0xFF, 0x4C, 0x01, 0x97, 0xFD, 0x3C, 0x03, 0x4D, 0xFD, 0xF8,
+	0xFD, 0x2A, 0x40, 0xED, 0x16, 0x9A, 0xF4, 0x3C, 0x06, 0x0A, 0xFD,
+	0x0A, 0x01, 0xD4, 0xFF, 0xF8, 0xFF, 0x01, 0x00, 0x10, 0x00, 0x8C,
+	0xFF, 0x61, 0x01, 0x34, 0xFD, 0x4B, 0x04, 0xFA, 0xFA, 0xF1, 0x02,
+	0x42, 0x42, 0x11, 0x10, 0x4C, 0xF6, 0xED, 0x05, 0xE3, 0xFC, 0x41,
+	0x01, 0xB1, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x97, 0xFF,
+	0x61, 0x01, 0xF5, 0xFC, 0x30, 0x05, 0xB1, 0xF8, 0xAE, 0x08, 0x0A,
+	0x43, 0x9F, 0x09, 0x5A, 0xF8, 0x4F, 0x05, 0xEF, 0xFC, 0x5F, 0x01,
+	0x9A, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0xAD,
+	0xFF, 0x48, 0x01, 0xE2, 0xFC, 0xDA, 0x05, 0x97, 0xF6, 0x0B, 0x0F,
+	0x79, 0x42, 0xC7, 0x03, 0x9E, 0xFA, 0x71, 0x04, 0x28, 0xFD, 0x63,
+	0x01, 0x8D, 0xFF, 0x0F, 0x00 
+};
+
+static u16
+coefficient_sizes[8 * 2] = {
+	/* Playback */
+	0x00C0, 0x5000, 0x0060, 0x2800, 0x0040, 0x0060, 0x1400, 0x0000,
+	/* capture */
+	0x0020, 0x1260, 0x0020, 0x1260, 0x0000, 0x0040, 0x1260, 0x0000,
+};
+
diff --git a/linux/sound/pci/oxygen/Makefile b/linux/sound/pci/oxygen/Makefile
new file mode 100644
index 0000000..acd8f15
--- /dev/null
+++ b/linux/sound/pci/oxygen/Makefile
@@ -0,0 +1,10 @@
+snd-oxygen-lib-objs := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o
+snd-hifier-objs := hifier.o
+snd-oxygen-objs := oxygen.o
+snd-virtuoso-objs := virtuoso.o xonar_lib.o \
+	xonar_pcm179x.o xonar_cs43xx.o xonar_wm87x6.o xonar_hdmi.o
+
+obj-$(CONFIG_SND_OXYGEN_LIB) += snd-oxygen-lib.o
+obj-$(CONFIG_SND_HIFIER) += snd-hifier.o
+obj-$(CONFIG_SND_OXYGEN) += snd-oxygen.o
+obj-$(CONFIG_SND_VIRTUOSO) += snd-virtuoso.o
diff --git a/linux/sound/pci/oxygen/ak4396.h b/linux/sound/pci/oxygen/ak4396.h
new file mode 100644
index 0000000..551c1cf
--- /dev/null
+++ b/linux/sound/pci/oxygen/ak4396.h
@@ -0,0 +1,44 @@
+#ifndef AK4396_H_INCLUDED
+#define AK4396_H_INCLUDED
+
+#define AK4396_WRITE		0x2000
+
+#define AK4396_CONTROL_1	0
+#define AK4396_CONTROL_2	1
+#define AK4396_CONTROL_3	2
+#define AK4396_LCH_ATT		3
+#define AK4396_RCH_ATT		4
+
+/* control 1 */
+#define AK4396_RSTN		0x01
+#define AK4396_DIF_MASK		0x0e
+#define AK4396_DIF_16_LSB	0x00
+#define AK4396_DIF_20_LSB	0x02
+#define AK4396_DIF_24_MSB	0x04
+#define AK4396_DIF_24_I2S	0x06
+#define AK4396_DIF_24_LSB	0x08
+#define AK4396_ACKS		0x80
+/* control 2 */
+#define AK4396_SMUTE		0x01
+#define AK4396_DEM_MASK		0x06
+#define AK4396_DEM_441		0x00
+#define AK4396_DEM_OFF		0x02
+#define AK4396_DEM_48		0x04
+#define AK4396_DEM_32		0x06
+#define AK4396_DFS_MASK		0x18
+#define AK4396_DFS_NORMAL	0x00
+#define AK4396_DFS_DOUBLE	0x08
+#define AK4396_DFS_QUAD		0x10
+#define AK4396_SLOW		0x20
+#define AK4396_DZFM		0x40
+#define AK4396_DZFE		0x80
+/* control 3 */
+#define AK4396_DZFB		0x04
+#define AK4396_DCKB		0x10
+#define AK4396_DCKS		0x20
+#define AK4396_DSDM		0x40
+#define AK4396_D_P_MASK		0x80
+#define AK4396_PCM		0x00
+#define AK4396_DSD		0x80
+
+#endif
diff --git a/linux/sound/pci/oxygen/cm9780.h b/linux/sound/pci/oxygen/cm9780.h
new file mode 100644
index 0000000..1445967
--- /dev/null
+++ b/linux/sound/pci/oxygen/cm9780.h
@@ -0,0 +1,63 @@
+#ifndef CM9780_H_INCLUDED
+#define CM9780_H_INCLUDED
+
+#define CM9780_JACK		0x62
+#define CM9780_MIXER		0x64
+#define CM9780_GPIO_SETUP	0x70
+#define CM9780_GPIO_STATUS	0x72
+
+/* jack control */
+#define CM9780_RSOE		0x0001
+#define CM9780_CBOE		0x0002
+#define CM9780_SSOE		0x0004
+#define CM9780_FROE		0x0008
+#define CM9780_HP2FMICOE	0x0010
+#define CM9780_CB2MICOE		0x0020
+#define CM9780_FMIC2LI		0x0040
+#define CM9780_FMIC2MIC		0x0080
+#define CM9780_HP2LI		0x0100
+#define CM9780_HP2MIC		0x0200
+#define CM9780_MIC2LI		0x0400
+#define CM9780_MIC2MIC		0x0800
+#define CM9780_LI2LI		0x1000
+#define CM9780_LI2MIC		0x2000
+#define CM9780_LO2LI		0x4000
+#define CM9780_LO2MIC		0x8000
+
+/* mixer control */
+#define CM9780_BSTSEL		0x0001
+#define CM9780_STRO_MIC		0x0002
+#define CM9780_SPDI_FREX	0x0004
+#define CM9780_SPDI_SSEX	0x0008
+#define CM9780_SPDI_CBEX	0x0010
+#define CM9780_SPDI_RSEX	0x0020
+#define CM9780_MIX2FR		0x0040
+#define CM9780_MIX2SS		0x0080
+#define CM9780_MIX2CB		0x0100
+#define CM9780_MIX2RS		0x0200
+#define CM9780_MIX2FR_EX	0x0400
+#define CM9780_MIX2SS_EX	0x0800
+#define CM9780_MIX2CB_EX	0x1000
+#define CM9780_MIX2RS_EX	0x2000
+#define CM9780_P47_IO		0x4000
+#define CM9780_PCBSW		0x8000
+
+/* GPIO setup */
+#define CM9780_GPI0EN		0x0001
+#define CM9780_GPI1EN		0x0002
+#define CM9780_SENSE_P		0x0004
+#define CM9780_LOCK_P		0x0008
+#define CM9780_GPIO0P		0x0010
+#define CM9780_GPIO1P		0x0020
+#define CM9780_GPIO0IO		0x0100
+#define CM9780_GPIO1IO		0x0200
+
+/* GPIO status */
+#define CM9780_GPO0		0x0001
+#define CM9780_GPO1		0x0002
+#define CM9780_GPIO0S		0x0010
+#define CM9780_GPIO1S		0x0020
+#define CM9780_GPII0S		0x0100
+#define CM9780_GPII1S		0x0200
+
+#endif
diff --git a/linux/sound/pci/oxygen/cs2000.h b/linux/sound/pci/oxygen/cs2000.h
new file mode 100644
index 0000000..c3501bd
--- /dev/null
+++ b/linux/sound/pci/oxygen/cs2000.h
@@ -0,0 +1,83 @@
+#ifndef CS2000_H_INCLUDED
+#define CS2000_H_INCLUDED
+
+#define CS2000_DEV_ID		0x01
+#define CS2000_DEV_CTRL		0x02
+#define CS2000_DEV_CFG_1	0x03
+#define CS2000_DEV_CFG_2	0x04
+#define CS2000_GLOBAL_CFG	0x05
+#define CS2000_RATIO_0		0x06 /* 32 bits, big endian */
+#define CS2000_RATIO_1		0x0a
+#define CS2000_RATIO_2		0x0e
+#define CS2000_RATIO_3		0x12
+#define CS2000_FUN_CFG_1	0x16
+#define CS2000_FUN_CFG_2	0x17
+#define CS2000_FUN_CFG_3	0x1e
+
+/* DEV_ID */
+#define CS2000_DEVICE_MASK		0xf8
+#define CS2000_REVISION_MASK		0x07
+
+/* DEV_CTRL */
+#define CS2000_UNLOCK			0x80
+#define CS2000_AUX_OUT_DIS		0x02
+#define CS2000_CLK_OUT_DIS		0x01
+
+/* DEV_CFG_1 */
+#define CS2000_R_MOD_SEL_MASK		0xe0
+#define CS2000_R_MOD_SEL_1		0x00
+#define CS2000_R_MOD_SEL_2		0x20
+#define CS2000_R_MOD_SEL_4		0x40
+#define CS2000_R_MOD_SEL_8		0x60
+#define CS2000_R_MOD_SEL_1_2		0x80
+#define CS2000_R_MOD_SEL_1_4		0xa0
+#define CS2000_R_MOD_SEL_1_8		0xc0
+#define CS2000_R_MOD_SEL_1_16		0xe0
+#define CS2000_R_SEL_MASK		0x18
+#define CS2000_R_SEL_SHIFT		3
+#define CS2000_AUX_OUT_SRC_MASK		0x06
+#define CS2000_AUX_OUT_SRC_REF_CLK	0x00
+#define CS2000_AUX_OUT_SRC_CLK_IN	0x02
+#define CS2000_AUX_OUT_SRC_CLK_OUT	0x04
+#define CS2000_AUX_OUT_SRC_PLL_LOCK	0x06
+#define CS2000_EN_DEV_CFG_1		0x01
+
+/* DEV_CFG_2 */
+#define CS2000_LOCK_CLK_MASK		0x06
+#define CS2000_LOCK_CLK_SHIFT		1
+#define CS2000_FRAC_N_SRC_MASK		0x01
+#define CS2000_FRAC_N_SRC_STATIC	0x00
+#define CS2000_FRAC_N_SRC_DYNAMIC	0x01
+
+/* GLOBAL_CFG */
+#define CS2000_FREEZE			0x08
+#define CS2000_EN_DEV_CFG_2		0x01
+
+/* FUN_CFG_1 */
+#define CS2000_CLK_SKIP_EN		0x80
+#define CS2000_AUX_LOCK_CFG_MASK	0x40
+#define CS2000_AUX_LOCK_CFG_PP_HIGH	0x00
+#define CS2000_AUX_LOCK_CFG_OD_LOW	0x40
+#define CS2000_REF_CLK_DIV_MASK		0x18
+#define CS2000_REF_CLK_DIV_4		0x00
+#define CS2000_REF_CLK_DIV_2		0x08
+#define CS2000_REF_CLK_DIV_1		0x10
+
+/* FUN_CFG_2 */
+#define CS2000_CLK_OUT_UNL		0x10
+#define CS2000_L_F_RATIO_CFG_MASK	0x08
+#define CS2000_L_F_RATIO_CFG_20_12	0x00
+#define CS2000_L_F_RATIO_CFG_12_20	0x08
+
+/* FUN_CFG_3 */
+#define CS2000_CLK_IN_BW_MASK		0x70
+#define CS2000_CLK_IN_BW_1		0x00
+#define CS2000_CLK_IN_BW_2		0x10
+#define CS2000_CLK_IN_BW_4		0x20
+#define CS2000_CLK_IN_BW_8		0x30
+#define CS2000_CLK_IN_BW_16		0x40
+#define CS2000_CLK_IN_BW_32		0x50
+#define CS2000_CLK_IN_BW_64		0x60
+#define CS2000_CLK_IN_BW_128		0x70
+
+#endif
diff --git a/linux/sound/pci/oxygen/cs4362a.h b/linux/sound/pci/oxygen/cs4362a.h
new file mode 100644
index 0000000..6a4fedf
--- /dev/null
+++ b/linux/sound/pci/oxygen/cs4362a.h
@@ -0,0 +1,69 @@
+/* register 01h */
+#define CS4362A_PDN		0x01
+#define CS4362A_DAC1_DIS	0x02
+#define CS4362A_DAC2_DIS	0x04
+#define CS4362A_DAC3_DIS	0x08
+#define CS4362A_MCLKDIV		0x20
+#define CS4362A_FREEZE		0x40
+#define CS4362A_CPEN		0x80
+/* register 02h */
+#define CS4362A_DIF_MASK	0x70
+#define CS4362A_DIF_LJUST	0x00
+#define CS4362A_DIF_I2S		0x10
+#define CS4362A_DIF_RJUST_16	0x20
+#define CS4362A_DIF_RJUST_24	0x30
+#define CS4362A_DIF_RJUST_20	0x40
+#define CS4362A_DIF_RJUST_18	0x50
+/* register 03h */
+#define CS4362A_MUTEC_MASK	0x03
+#define CS4362A_MUTEC_6		0x00
+#define CS4362A_MUTEC_1		0x01
+#define CS4362A_MUTEC_3		0x03
+#define CS4362A_AMUTE		0x04
+#define CS4362A_MUTEC_POL	0x08
+#define CS4362A_RMP_UP		0x10
+#define CS4362A_SNGLVOL		0x20
+#define CS4362A_ZERO_CROSS	0x40
+#define CS4362A_SOFT_RAMP	0x80
+/* register 04h */
+#define CS4362A_RMP_DN		0x01
+#define CS4362A_DEM_MASK	0x06
+#define CS4362A_DEM_NONE	0x00
+#define CS4362A_DEM_44100	0x02
+#define CS4362A_DEM_48000	0x04
+#define CS4362A_DEM_32000	0x06
+#define CS4362A_FILT_SEL	0x10
+/* register 05h */
+#define CS4362A_INV_A1		0x01
+#define CS4362A_INV_B1		0x02
+#define CS4362A_INV_A2		0x04
+#define CS4362A_INV_B2		0x08
+#define CS4362A_INV_A3		0x10
+#define CS4362A_INV_B3		0x20
+/* register 06h */
+#define CS4362A_FM_MASK		0x03
+#define CS4362A_FM_SINGLE	0x00
+#define CS4362A_FM_DOUBLE	0x01
+#define CS4362A_FM_QUAD		0x02
+#define CS4362A_FM_DSD		0x03
+#define CS4362A_ATAPI_MASK	0x7c
+#define CS4362A_ATAPI_B_MUTE	0x00
+#define CS4362A_ATAPI_B_R	0x04
+#define CS4362A_ATAPI_B_L	0x08
+#define CS4362A_ATAPI_B_LR	0x0c
+#define CS4362A_ATAPI_A_MUTE	0x00
+#define CS4362A_ATAPI_A_R	0x10
+#define CS4362A_ATAPI_A_L	0x20
+#define CS4362A_ATAPI_A_LR	0x30
+#define CS4362A_ATAPI_MIX_LR_VOL 0x40
+#define CS4362A_A_EQ_B		0x80
+/* register 07h */
+#define CS4362A_VOL_MASK		0x7f
+#define CS4362A_MUTE			0x80
+/* register 08h: like 07h */
+/* registers 09h..0Bh: like 06h..08h */
+/* registers 0Ch..0Eh: like 06h..08h */
+/* register 12h */
+#define CS4362A_REV_MASK	0x07
+#define CS4362A_PART_MASK	0xf8
+#define CS4362A_PART_CS4362A	0x50
diff --git a/linux/sound/pci/oxygen/cs4398.h b/linux/sound/pci/oxygen/cs4398.h
new file mode 100644
index 0000000..5faf5ef
--- /dev/null
+++ b/linux/sound/pci/oxygen/cs4398.h
@@ -0,0 +1,69 @@
+/* register 1 */
+#define CS4398_REV_MASK		0x07
+#define CS4398_PART_MASK	0xf8
+#define CS4398_PART_CS4398	0x70
+/* register 2 */
+#define CS4398_FM_MASK		0x03
+#define CS4398_FM_SINGLE	0x00
+#define CS4398_FM_DOUBLE	0x01
+#define CS4398_FM_QUAD		0x02
+#define CS4398_FM_DSD		0x03
+#define CS4398_DEM_MASK		0x0c
+#define CS4398_DEM_NONE		0x00
+#define CS4398_DEM_44100	0x04
+#define CS4398_DEM_48000	0x08
+#define CS4398_DEM_32000	0x0c
+#define CS4398_DIF_MASK		0x70
+#define CS4398_DIF_LJUST	0x00
+#define CS4398_DIF_I2S		0x10
+#define CS4398_DIF_RJUST_16	0x20
+#define CS4398_DIF_RJUST_24	0x30
+#define CS4398_DIF_RJUST_20	0x40
+#define CS4398_DIF_RJUST_18	0x50
+#define CS4398_DSD_SRC		0x80
+/* register 3 */
+#define CS4398_ATAPI_MASK	0x1f
+#define CS4398_ATAPI_B_MUTE	0x00
+#define CS4398_ATAPI_B_R	0x01
+#define CS4398_ATAPI_B_L	0x02
+#define CS4398_ATAPI_B_LR	0x03
+#define CS4398_ATAPI_A_MUTE	0x00
+#define CS4398_ATAPI_A_R	0x04
+#define CS4398_ATAPI_A_L	0x08
+#define CS4398_ATAPI_A_LR	0x0c
+#define CS4398_ATAPI_MIX_LR_VOL	0x10
+#define CS4398_INVERT_B		0x20
+#define CS4398_INVERT_A		0x40
+#define CS4398_VOL_B_EQ_A	0x80
+/* register 4 */
+#define CS4398_MUTEP_MASK	0x03
+#define CS4398_MUTEP_AUTO	0x00
+#define CS4398_MUTEP_LOW	0x02
+#define CS4398_MUTEP_HIGH	0x03
+#define CS4398_MUTE_B		0x08
+#define CS4398_MUTE_A		0x10
+#define CS4398_MUTEC_A_EQ_B	0x20
+#define CS4398_DAMUTE		0x40
+#define CS4398_PAMUTE		0x80
+/* register 5 */
+#define CS4398_VOL_A_MASK	0xff
+/* register 6 */
+#define CS4398_VOL_B_MASK	0xff
+/* register 7 */
+#define CS4398_DIR_DSD		0x01
+#define CS4398_FILT_SEL		0x04
+#define CS4398_RMP_DN		0x10
+#define CS4398_RMP_UP		0x20
+#define CS4398_ZERO_CROSS	0x40
+#define CS4398_SOFT_RAMP	0x80
+/* register 8 */
+#define CS4398_MCLKDIV3		0x08
+#define CS4398_MCLKDIV2		0x10
+#define CS4398_FREEZE		0x20
+#define CS4398_CPEN		0x40
+#define CS4398_PDN		0x80
+/* register 9 */
+#define CS4398_DSD_PM_EN	0x01
+#define CS4398_DSD_PM_MODE	0x02
+#define CS4398_INVALID_DSD	0x04
+#define CS4398_STATIC_DSD	0x08
diff --git a/linux/sound/pci/oxygen/hifier.c b/linux/sound/pci/oxygen/hifier.c
new file mode 100644
index 0000000..5a87d68
--- /dev/null
+++ b/linux/sound/pci/oxygen/hifier.c
@@ -0,0 +1,239 @@
+/*
+ * C-Media CMI8788 driver for the MediaTek/TempoTec HiFier Fantasia
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+/*
+ * CMI8788:
+ *
+ * SPI 0 -> AK4396
+ */
+
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+#include "ak4396.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("TempoTec HiFier driver");
+MODULE_LICENSE("GPL v2");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "card index");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "enable card");
+
+static DEFINE_PCI_DEVICE_TABLE(hifier_ids) = {
+	{ OXYGEN_PCI_SUBID(0x14c3, 0x1710) },
+	{ OXYGEN_PCI_SUBID(0x14c3, 0x1711) },
+	{ OXYGEN_PCI_SUBID_BROKEN_EEPROM },
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, hifier_ids);
+
+struct hifier_data {
+	u8 ak4396_regs[5];
+};
+
+static void ak4396_write(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct hifier_data *data = chip->model_data;
+
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER  |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (0 << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
+			 AK4396_WRITE | (reg << 8) | value);
+	data->ak4396_regs[reg] = value;
+}
+
+static void ak4396_write_cached(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct hifier_data *data = chip->model_data;
+
+	if (value != data->ak4396_regs[reg])
+		ak4396_write(chip, reg, value);
+}
+
+static void hifier_registers_init(struct oxygen *chip)
+{
+	struct hifier_data *data = chip->model_data;
+
+	ak4396_write(chip, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_RSTN);
+	ak4396_write(chip, AK4396_CONTROL_2,
+		     data->ak4396_regs[AK4396_CONTROL_2]);
+	ak4396_write(chip, AK4396_CONTROL_3, AK4396_PCM);
+	ak4396_write(chip, AK4396_LCH_ATT, chip->dac_volume[0]);
+	ak4396_write(chip, AK4396_RCH_ATT, chip->dac_volume[1]);
+}
+
+static void hifier_init(struct oxygen *chip)
+{
+	struct hifier_data *data = chip->model_data;
+
+	data->ak4396_regs[AK4396_CONTROL_2] =
+		AK4396_SMUTE | AK4396_DEM_OFF | AK4396_DFS_NORMAL;
+	hifier_registers_init(chip);
+
+	snd_component_add(chip->card, "AK4396");
+	snd_component_add(chip->card, "CS5340");
+}
+
+static void hifier_cleanup(struct oxygen *chip)
+{
+}
+
+static void hifier_resume(struct oxygen *chip)
+{
+	hifier_registers_init(chip);
+}
+
+static void set_ak4396_params(struct oxygen *chip,
+			       struct snd_pcm_hw_params *params)
+{
+	struct hifier_data *data = chip->model_data;
+	u8 value;
+
+	value = data->ak4396_regs[AK4396_CONTROL_2] & ~AK4396_DFS_MASK;
+	if (params_rate(params) <= 54000)
+		value |= AK4396_DFS_NORMAL;
+	else if (params_rate(params) <= 108000)
+		value |= AK4396_DFS_DOUBLE;
+	else
+		value |= AK4396_DFS_QUAD;
+
+	msleep(1); /* wait for the new MCLK to become stable */
+
+	if (value != data->ak4396_regs[AK4396_CONTROL_2]) {
+		ak4396_write(chip, AK4396_CONTROL_1,
+			     AK4396_DIF_24_MSB);
+		ak4396_write(chip, AK4396_CONTROL_2, value);
+		ak4396_write(chip, AK4396_CONTROL_1,
+			     AK4396_DIF_24_MSB | AK4396_RSTN);
+	}
+}
+
+static void update_ak4396_volume(struct oxygen *chip)
+{
+	ak4396_write_cached(chip, AK4396_LCH_ATT, chip->dac_volume[0]);
+	ak4396_write_cached(chip, AK4396_RCH_ATT, chip->dac_volume[1]);
+}
+
+static void update_ak4396_mute(struct oxygen *chip)
+{
+	struct hifier_data *data = chip->model_data;
+	u8 value;
+
+	value = data->ak4396_regs[AK4396_CONTROL_2] & ~AK4396_SMUTE;
+	if (chip->dac_mute)
+		value |= AK4396_SMUTE;
+	ak4396_write_cached(chip, AK4396_CONTROL_2, value);
+}
+
+static void set_cs5340_params(struct oxygen *chip,
+			      struct snd_pcm_hw_params *params)
+{
+}
+
+static const DECLARE_TLV_DB_LINEAR(ak4396_db_scale, TLV_DB_GAIN_MUTE, 0);
+
+static const struct oxygen_model model_hifier = {
+	.shortname = "C-Media CMI8787",
+	.longname = "C-Media Oxygen HD Audio",
+	.chip = "CMI8788",
+	.init = hifier_init,
+	.cleanup = hifier_cleanup,
+	.resume = hifier_resume,
+	.get_i2s_mclk = oxygen_default_i2s_mclk,
+	.set_dac_params = set_ak4396_params,
+	.set_adc_params = set_cs5340_params,
+	.update_dac_volume = update_ak4396_volume,
+	.update_dac_mute = update_ak4396_mute,
+	.dac_tlv = ak4396_db_scale,
+	.model_data_size = sizeof(struct hifier_data),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_1,
+	.dac_channels = 2,
+	.dac_volume_min = 0,
+	.dac_volume_max = 255,
+	.function_flags = OXYGEN_FUNCTION_SPI,
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+static int __devinit get_hifier_model(struct oxygen *chip,
+				      const struct pci_device_id *id)
+{
+	chip->model = model_hifier;
+	return 0;
+}
+
+static int __devinit hifier_probe(struct pci_dev *pci,
+				  const struct pci_device_id *pci_id)
+{
+	static int dev;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		++dev;
+		return -ENOENT;
+	}
+	err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE,
+			       hifier_ids, get_hifier_model);
+	if (err >= 0)
+		++dev;
+	return err;
+}
+
+static struct pci_driver hifier_driver = {
+	.name = "CMI8787HiFier",
+	.id_table = hifier_ids,
+	.probe = hifier_probe,
+	.remove = __devexit_p(oxygen_pci_remove),
+#ifdef CONFIG_PM
+	.suspend = oxygen_pci_suspend,
+	.resume = oxygen_pci_resume,
+#endif
+};
+
+static int __init alsa_card_hifier_init(void)
+{
+	return pci_register_driver(&hifier_driver);
+}
+
+static void __exit alsa_card_hifier_exit(void)
+{
+	pci_unregister_driver(&hifier_driver);
+}
+
+module_init(alsa_card_hifier_init)
+module_exit(alsa_card_hifier_exit)
diff --git a/linux/sound/pci/oxygen/oxygen.c b/linux/sound/pci/oxygen/oxygen.c
new file mode 100644
index 0000000..98a8eb3
--- /dev/null
+++ b/linux/sound/pci/oxygen/oxygen.c
@@ -0,0 +1,603 @@
+/*
+ * C-Media CMI8788 driver for C-Media's reference design and similar models
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+/*
+ * CMI8788:
+ *
+ * SPI 0 -> 1st AK4396 (front)
+ * SPI 1 -> 2nd AK4396 (surround)
+ * SPI 2 -> 3rd AK4396 (center/LFE)
+ * SPI 3 -> WM8785
+ * SPI 4 -> 4th AK4396 (back)
+ *
+ * GPIO 0 -> DFS0 of AK5385
+ * GPIO 1 -> DFS1 of AK5385
+ * GPIO 8 -> enable headphone amplifier on HT-Omega models
+ *
+ * CM9780:
+ *
+ * GPO 0 -> route line-in (0) or AC97 output (1) to ADC input
+ */
+
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <sound/ac97_codec.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+#include "ak4396.h"
+#include "wm8785.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("C-Media CMI8788 driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{C-Media,CMI8788}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "card index");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "enable card");
+
+enum {
+	MODEL_CMEDIA_REF,	/* C-Media's reference design */
+	MODEL_MERIDIAN,		/* AuzenTech X-Meridian */
+	MODEL_CLARO,		/* HT-Omega Claro */
+	MODEL_CLARO_HALO,	/* HT-Omega Claro halo */
+};
+
+static DEFINE_PCI_DEVICE_TABLE(oxygen_ids) = {
+	{ OXYGEN_PCI_SUBID(0x10b0, 0x0216), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x10b0, 0x0218), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x10b0, 0x0219), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x13f6, 0x0001), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x13f6, 0x0010), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x13f6, 0x8788), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x13f6, 0xffff), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x147a, 0xa017), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x1a58, 0x0910), .driver_data = MODEL_CMEDIA_REF },
+	{ OXYGEN_PCI_SUBID(0x415a, 0x5431), .driver_data = MODEL_MERIDIAN },
+	{ OXYGEN_PCI_SUBID(0x7284, 0x9761), .driver_data = MODEL_CLARO },
+	{ OXYGEN_PCI_SUBID(0x7284, 0x9781), .driver_data = MODEL_CLARO_HALO },
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, oxygen_ids);
+
+
+#define GPIO_AK5385_DFS_MASK	0x0003
+#define GPIO_AK5385_DFS_NORMAL	0x0000
+#define GPIO_AK5385_DFS_DOUBLE	0x0001
+#define GPIO_AK5385_DFS_QUAD	0x0002
+
+#define GPIO_CLARO_HP		0x0100
+
+struct generic_data {
+	u8 ak4396_regs[4][5];
+	u16 wm8785_regs[3];
+};
+
+static void ak4396_write(struct oxygen *chip, unsigned int codec,
+			 u8 reg, u8 value)
+{
+	/* maps ALSA channel pair number to SPI output */
+	static const u8 codec_spi_map[4] = {
+		0, 1, 2, 4
+	};
+	struct generic_data *data = chip->model_data;
+
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (codec_spi_map[codec] << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
+			 AK4396_WRITE | (reg << 8) | value);
+	data->ak4396_regs[codec][reg] = value;
+}
+
+static void ak4396_write_cached(struct oxygen *chip, unsigned int codec,
+				u8 reg, u8 value)
+{
+	struct generic_data *data = chip->model_data;
+
+	if (value != data->ak4396_regs[codec][reg])
+		ak4396_write(chip, codec, reg, value);
+}
+
+static void wm8785_write(struct oxygen *chip, u8 reg, unsigned int value)
+{
+	struct generic_data *data = chip->model_data;
+
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (3 << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_LO,
+			 (reg << 9) | value);
+	if (reg < ARRAY_SIZE(data->wm8785_regs))
+		data->wm8785_regs[reg] = value;
+}
+
+static void ak4396_registers_init(struct oxygen *chip)
+{
+	struct generic_data *data = chip->model_data;
+	unsigned int i;
+
+	for (i = 0; i < 4; ++i) {
+		ak4396_write(chip, i, AK4396_CONTROL_1,
+			     AK4396_DIF_24_MSB | AK4396_RSTN);
+		ak4396_write(chip, i, AK4396_CONTROL_2,
+			     data->ak4396_regs[0][AK4396_CONTROL_2]);
+		ak4396_write(chip, i, AK4396_CONTROL_3,
+			     AK4396_PCM);
+		ak4396_write(chip, i, AK4396_LCH_ATT,
+			     chip->dac_volume[i * 2]);
+		ak4396_write(chip, i, AK4396_RCH_ATT,
+			     chip->dac_volume[i * 2 + 1]);
+	}
+}
+
+static void ak4396_init(struct oxygen *chip)
+{
+	struct generic_data *data = chip->model_data;
+
+	data->ak4396_regs[0][AK4396_CONTROL_2] =
+		AK4396_SMUTE | AK4396_DEM_OFF | AK4396_DFS_NORMAL;
+	ak4396_registers_init(chip);
+	snd_component_add(chip->card, "AK4396");
+}
+
+static void ak5385_init(struct oxygen *chip)
+{
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_AK5385_DFS_MASK);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_AK5385_DFS_MASK);
+	snd_component_add(chip->card, "AK5385");
+}
+
+static void wm8785_registers_init(struct oxygen *chip)
+{
+	struct generic_data *data = chip->model_data;
+
+	wm8785_write(chip, WM8785_R7, 0);
+	wm8785_write(chip, WM8785_R0, data->wm8785_regs[0]);
+	wm8785_write(chip, WM8785_R2, data->wm8785_regs[2]);
+}
+
+static void wm8785_init(struct oxygen *chip)
+{
+	struct generic_data *data = chip->model_data;
+
+	data->wm8785_regs[0] =
+		WM8785_MCR_SLAVE | WM8785_OSR_SINGLE | WM8785_FORMAT_LJUST;
+	data->wm8785_regs[2] = WM8785_HPFR | WM8785_HPFL;
+	wm8785_registers_init(chip);
+	snd_component_add(chip->card, "WM8785");
+}
+
+static void generic_init(struct oxygen *chip)
+{
+	ak4396_init(chip);
+	wm8785_init(chip);
+}
+
+static void meridian_init(struct oxygen *chip)
+{
+	ak4396_init(chip);
+	ak5385_init(chip);
+}
+
+static void claro_enable_hp(struct oxygen *chip)
+{
+	msleep(300);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CLARO_HP);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_HP);
+}
+
+static void claro_init(struct oxygen *chip)
+{
+	ak4396_init(chip);
+	wm8785_init(chip);
+	claro_enable_hp(chip);
+}
+
+static void claro_halo_init(struct oxygen *chip)
+{
+	ak4396_init(chip);
+	ak5385_init(chip);
+	claro_enable_hp(chip);
+}
+
+static void generic_cleanup(struct oxygen *chip)
+{
+}
+
+static void claro_disable_hp(struct oxygen *chip)
+{
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_HP);
+}
+
+static void claro_cleanup(struct oxygen *chip)
+{
+	claro_disable_hp(chip);
+}
+
+static void claro_suspend(struct oxygen *chip)
+{
+	claro_disable_hp(chip);
+}
+
+static void generic_resume(struct oxygen *chip)
+{
+	ak4396_registers_init(chip);
+	wm8785_registers_init(chip);
+}
+
+static void meridian_resume(struct oxygen *chip)
+{
+	ak4396_registers_init(chip);
+}
+
+static void claro_resume(struct oxygen *chip)
+{
+	ak4396_registers_init(chip);
+	claro_enable_hp(chip);
+}
+
+static void set_ak4396_params(struct oxygen *chip,
+			      struct snd_pcm_hw_params *params)
+{
+	struct generic_data *data = chip->model_data;
+	unsigned int i;
+	u8 value;
+
+	value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_DFS_MASK;
+	if (params_rate(params) <= 54000)
+		value |= AK4396_DFS_NORMAL;
+	else if (params_rate(params) <= 108000)
+		value |= AK4396_DFS_DOUBLE;
+	else
+		value |= AK4396_DFS_QUAD;
+
+	msleep(1); /* wait for the new MCLK to become stable */
+
+	if (value != data->ak4396_regs[0][AK4396_CONTROL_2]) {
+		for (i = 0; i < 4; ++i) {
+			ak4396_write(chip, i, AK4396_CONTROL_1,
+				     AK4396_DIF_24_MSB);
+			ak4396_write(chip, i, AK4396_CONTROL_2, value);
+			ak4396_write(chip, i, AK4396_CONTROL_1,
+				     AK4396_DIF_24_MSB | AK4396_RSTN);
+		}
+	}
+}
+
+static void update_ak4396_volume(struct oxygen *chip)
+{
+	unsigned int i;
+
+	for (i = 0; i < 4; ++i) {
+		ak4396_write_cached(chip, i, AK4396_LCH_ATT,
+				    chip->dac_volume[i * 2]);
+		ak4396_write_cached(chip, i, AK4396_RCH_ATT,
+				    chip->dac_volume[i * 2 + 1]);
+	}
+}
+
+static void update_ak4396_mute(struct oxygen *chip)
+{
+	struct generic_data *data = chip->model_data;
+	unsigned int i;
+	u8 value;
+
+	value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_SMUTE;
+	if (chip->dac_mute)
+		value |= AK4396_SMUTE;
+	for (i = 0; i < 4; ++i)
+		ak4396_write_cached(chip, i, AK4396_CONTROL_2, value);
+}
+
+static void set_wm8785_params(struct oxygen *chip,
+			      struct snd_pcm_hw_params *params)
+{
+	struct generic_data *data = chip->model_data;
+	unsigned int value;
+
+	value = WM8785_MCR_SLAVE | WM8785_FORMAT_LJUST;
+	if (params_rate(params) <= 48000)
+		value |= WM8785_OSR_SINGLE;
+	else if (params_rate(params) <= 96000)
+		value |= WM8785_OSR_DOUBLE;
+	else
+		value |= WM8785_OSR_QUAD;
+	if (value != data->wm8785_regs[0]) {
+		wm8785_write(chip, WM8785_R7, 0);
+		wm8785_write(chip, WM8785_R0, value);
+		wm8785_write(chip, WM8785_R2, data->wm8785_regs[2]);
+	}
+}
+
+static void set_ak5385_params(struct oxygen *chip,
+			      struct snd_pcm_hw_params *params)
+{
+	unsigned int value;
+
+	if (params_rate(params) <= 54000)
+		value = GPIO_AK5385_DFS_NORMAL;
+	else if (params_rate(params) <= 108000)
+		value = GPIO_AK5385_DFS_DOUBLE;
+	else
+		value = GPIO_AK5385_DFS_QUAD;
+	oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+			      value, GPIO_AK5385_DFS_MASK);
+}
+
+static int rolloff_info(struct snd_kcontrol *ctl,
+			struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = {
+		"Sharp Roll-off", "Slow Roll-off"
+	};
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 2;
+	if (info->value.enumerated.item >= 2)
+		info->value.enumerated.item = 1;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int rolloff_get(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct generic_data *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		(data->ak4396_regs[0][AK4396_CONTROL_2] & AK4396_SLOW) != 0;
+	return 0;
+}
+
+static int rolloff_put(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct generic_data *data = chip->model_data;
+	unsigned int i;
+	int changed;
+	u8 reg;
+
+	mutex_lock(&chip->mutex);
+	reg = data->ak4396_regs[0][AK4396_CONTROL_2];
+	if (value->value.enumerated.item[0])
+		reg |= AK4396_SLOW;
+	else
+		reg &= ~AK4396_SLOW;
+	changed = reg != data->ak4396_regs[0][AK4396_CONTROL_2];
+	if (changed) {
+		for (i = 0; i < 4; ++i)
+			ak4396_write(chip, i, AK4396_CONTROL_2, reg);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new rolloff_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DAC Filter Playback Enum",
+	.info = rolloff_info,
+	.get = rolloff_get,
+	.put = rolloff_put,
+};
+
+static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = {
+		"None", "High-pass Filter"
+	};
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 2;
+	if (info->value.enumerated.item >= 2)
+		info->value.enumerated.item = 1;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct generic_data *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		(data->wm8785_regs[WM8785_R2] & WM8785_HPFR) != 0;
+	return 0;
+}
+
+static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct generic_data *data = chip->model_data;
+	unsigned int reg;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg = data->wm8785_regs[WM8785_R2] & ~(WM8785_HPFR | WM8785_HPFL);
+	if (value->value.enumerated.item[0])
+		reg |= WM8785_HPFR | WM8785_HPFL;
+	changed = reg != data->wm8785_regs[WM8785_R2];
+	if (changed)
+		wm8785_write(chip, WM8785_R2, reg);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new hpf_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "ADC Filter Capture Enum",
+	.info = hpf_info,
+	.get = hpf_get,
+	.put = hpf_put,
+};
+
+static int generic_mixer_init(struct oxygen *chip)
+{
+	return snd_ctl_add(chip->card, snd_ctl_new1(&rolloff_control, chip));
+}
+
+static int generic_wm8785_mixer_init(struct oxygen *chip)
+{
+	int err;
+
+	err = generic_mixer_init(chip);
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&hpf_control, chip));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static const DECLARE_TLV_DB_LINEAR(ak4396_db_scale, TLV_DB_GAIN_MUTE, 0);
+
+static const struct oxygen_model model_generic = {
+	.shortname = "C-Media CMI8788",
+	.longname = "C-Media Oxygen HD Audio",
+	.chip = "CMI8788",
+	.init = generic_init,
+	.mixer_init = generic_wm8785_mixer_init,
+	.cleanup = generic_cleanup,
+	.resume = generic_resume,
+	.get_i2s_mclk = oxygen_default_i2s_mclk,
+	.set_dac_params = set_ak4396_params,
+	.set_adc_params = set_wm8785_params,
+	.update_dac_volume = update_ak4396_volume,
+	.update_dac_mute = update_ak4396_mute,
+	.dac_tlv = ak4396_db_scale,
+	.model_data_size = sizeof(struct generic_data),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 PLAYBACK_2_TO_AC97_1 |
+			 CAPTURE_0_FROM_I2S_1 |
+			 CAPTURE_1_FROM_SPDIF |
+			 CAPTURE_2_FROM_AC97_1 |
+			 AC97_CD_INPUT,
+	.dac_channels = 8,
+	.dac_volume_min = 0,
+	.dac_volume_max = 255,
+	.function_flags = OXYGEN_FUNCTION_SPI |
+			  OXYGEN_FUNCTION_ENABLE_SPI_4_5,
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+static int __devinit get_oxygen_model(struct oxygen *chip,
+				      const struct pci_device_id *id)
+{
+	chip->model = model_generic;
+	switch (id->driver_data) {
+	case MODEL_MERIDIAN:
+		chip->model.init = meridian_init;
+		chip->model.mixer_init = generic_mixer_init;
+		chip->model.resume = meridian_resume;
+		chip->model.set_adc_params = set_ak5385_params;
+		chip->model.device_config = PLAYBACK_0_TO_I2S |
+					    PLAYBACK_1_TO_SPDIF |
+					    CAPTURE_0_FROM_I2S_2 |
+					    CAPTURE_1_FROM_SPDIF;
+		break;
+	case MODEL_CLARO:
+		chip->model.init = claro_init;
+		chip->model.cleanup = claro_cleanup;
+		chip->model.suspend = claro_suspend;
+		chip->model.resume = claro_resume;
+		break;
+	case MODEL_CLARO_HALO:
+		chip->model.init = claro_halo_init;
+		chip->model.mixer_init = generic_mixer_init;
+		chip->model.cleanup = claro_cleanup;
+		chip->model.suspend = claro_suspend;
+		chip->model.resume = claro_resume;
+		chip->model.set_adc_params = set_ak5385_params;
+		chip->model.device_config = PLAYBACK_0_TO_I2S |
+					    PLAYBACK_1_TO_SPDIF |
+					    CAPTURE_0_FROM_I2S_2 |
+					    CAPTURE_1_FROM_SPDIF;
+		break;
+	}
+	if (id->driver_data == MODEL_MERIDIAN ||
+	    id->driver_data == MODEL_CLARO_HALO) {
+		chip->model.misc_flags = OXYGEN_MISC_MIDI;
+		chip->model.device_config |= MIDI_OUTPUT | MIDI_INPUT;
+	}
+	return 0;
+}
+
+static int __devinit generic_oxygen_probe(struct pci_dev *pci,
+					  const struct pci_device_id *pci_id)
+{
+	static int dev;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		++dev;
+		return -ENOENT;
+	}
+	err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE,
+			       oxygen_ids, get_oxygen_model);
+	if (err >= 0)
+		++dev;
+	return err;
+}
+
+static struct pci_driver oxygen_driver = {
+	.name = "CMI8788",
+	.id_table = oxygen_ids,
+	.probe = generic_oxygen_probe,
+	.remove = __devexit_p(oxygen_pci_remove),
+#ifdef CONFIG_PM
+	.suspend = oxygen_pci_suspend,
+	.resume = oxygen_pci_resume,
+#endif
+};
+
+static int __init alsa_card_oxygen_init(void)
+{
+	return pci_register_driver(&oxygen_driver);
+}
+
+static void __exit alsa_card_oxygen_exit(void)
+{
+	pci_unregister_driver(&oxygen_driver);
+}
+
+module_init(alsa_card_oxygen_init)
+module_exit(alsa_card_oxygen_exit)
diff --git a/linux/sound/pci/oxygen/oxygen.h b/linux/sound/pci/oxygen/oxygen.h
new file mode 100644
index 0000000..7d5222c
--- /dev/null
+++ b/linux/sound/pci/oxygen/oxygen.h
@@ -0,0 +1,253 @@
+#ifndef OXYGEN_H_INCLUDED
+#define OXYGEN_H_INCLUDED
+
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include "oxygen_regs.h"
+
+/* 1 << PCM_x == OXYGEN_CHANNEL_x */
+#define PCM_A		0
+#define PCM_B		1
+#define PCM_C		2
+#define PCM_SPDIF	3
+#define PCM_MULTICH	4
+#define PCM_AC97	5
+#define PCM_COUNT	6
+
+#define OXYGEN_IO_SIZE	0x100
+
+#define OXYGEN_EEPROM_ID	0x434d	/* "CM" */
+
+/* model-specific configuration of outputs/inputs */
+#define PLAYBACK_0_TO_I2S	0x0001
+     /* PLAYBACK_0_TO_AC97_0		not implemented */
+#define PLAYBACK_1_TO_SPDIF	0x0004
+#define PLAYBACK_2_TO_AC97_1	0x0008
+#define CAPTURE_0_FROM_I2S_1	0x0010
+#define CAPTURE_0_FROM_I2S_2	0x0020
+     /* CAPTURE_0_FROM_AC97_0		not implemented */
+#define CAPTURE_1_FROM_SPDIF	0x0080
+#define CAPTURE_2_FROM_I2S_2	0x0100
+#define CAPTURE_2_FROM_AC97_1	0x0200
+     /* CAPTURE_3_FROM_I2S_3		not implemented */
+#define MIDI_OUTPUT		0x0800
+#define MIDI_INPUT		0x1000
+#define AC97_CD_INPUT		0x2000
+
+enum {
+	CONTROL_SPDIF_PCM,
+	CONTROL_SPDIF_INPUT_BITS,
+	CONTROL_MIC_CAPTURE_SWITCH,
+	CONTROL_LINE_CAPTURE_SWITCH,
+	CONTROL_CD_CAPTURE_SWITCH,
+	CONTROL_AUX_CAPTURE_SWITCH,
+	CONTROL_COUNT
+};
+
+#define OXYGEN_PCI_SUBID(sv, sd) \
+	.vendor = PCI_VENDOR_ID_CMEDIA, \
+	.device = 0x8788, \
+	.subvendor = sv, \
+	.subdevice = sd
+
+#define BROKEN_EEPROM_DRIVER_DATA ((unsigned long)-1)
+#define OXYGEN_PCI_SUBID_BROKEN_EEPROM \
+	OXYGEN_PCI_SUBID(PCI_VENDOR_ID_CMEDIA, 0x8788), \
+	.driver_data = BROKEN_EEPROM_DRIVER_DATA
+
+struct pci_dev;
+struct pci_device_id;
+struct snd_card;
+struct snd_pcm_substream;
+struct snd_pcm_hardware;
+struct snd_pcm_hw_params;
+struct snd_kcontrol_new;
+struct snd_rawmidi;
+struct oxygen;
+
+struct oxygen_model {
+	const char *shortname;
+	const char *longname;
+	const char *chip;
+	void (*init)(struct oxygen *chip);
+	int (*control_filter)(struct snd_kcontrol_new *template);
+	int (*mixer_init)(struct oxygen *chip);
+	void (*cleanup)(struct oxygen *chip);
+	void (*suspend)(struct oxygen *chip);
+	void (*resume)(struct oxygen *chip);
+	void (*pcm_hardware_filter)(unsigned int channel,
+				    struct snd_pcm_hardware *hardware);
+	unsigned int (*get_i2s_mclk)(struct oxygen *chip, unsigned int channel,
+				     struct snd_pcm_hw_params *hw_params);
+	void (*set_dac_params)(struct oxygen *chip,
+			       struct snd_pcm_hw_params *params);
+	void (*set_adc_params)(struct oxygen *chip,
+			       struct snd_pcm_hw_params *params);
+	void (*update_dac_volume)(struct oxygen *chip);
+	void (*update_dac_mute)(struct oxygen *chip);
+	void (*update_center_lfe_mix)(struct oxygen *chip, bool mixed);
+	void (*gpio_changed)(struct oxygen *chip);
+	void (*uart_input)(struct oxygen *chip);
+	void (*ac97_switch)(struct oxygen *chip,
+			    unsigned int reg, unsigned int mute);
+	const unsigned int *dac_tlv;
+	unsigned long private_data;
+	size_t model_data_size;
+	unsigned int device_config;
+	u8 dac_channels;
+	u8 dac_volume_min;
+	u8 dac_volume_max;
+	u8 misc_flags;
+	u8 function_flags;
+	u16 dac_i2s_format;
+	u16 adc_i2s_format;
+};
+
+struct oxygen {
+	unsigned long addr;
+	spinlock_t reg_lock;
+	struct mutex mutex;
+	struct snd_card *card;
+	struct pci_dev *pci;
+	struct snd_rawmidi *midi;
+	int irq;
+	void *model_data;
+	unsigned int interrupt_mask;
+	u8 dac_volume[8];
+	u8 dac_mute;
+	u8 pcm_active;
+	u8 pcm_running;
+	u8 dac_routing;
+	u8 spdif_playback_enable;
+	u8 revision;
+	u8 has_ac97_0;
+	u8 has_ac97_1;
+	u32 spdif_bits;
+	u32 spdif_pcm_bits;
+	struct snd_pcm_substream *streams[PCM_COUNT];
+	struct snd_kcontrol *controls[CONTROL_COUNT];
+	struct work_struct spdif_input_bits_work;
+	struct work_struct gpio_work;
+	wait_queue_head_t ac97_waitqueue;
+	union {
+		u8 _8[OXYGEN_IO_SIZE];
+		__le16 _16[OXYGEN_IO_SIZE / 2];
+		__le32 _32[OXYGEN_IO_SIZE / 4];
+	} saved_registers;
+	u16 saved_ac97_registers[2][0x40];
+	unsigned int uart_input_count;
+	u8 uart_input[32];
+	struct oxygen_model model;
+};
+
+/* oxygen_lib.c */
+
+int oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
+		     struct module *owner,
+		     const struct pci_device_id *ids,
+		     int (*get_model)(struct oxygen *chip,
+				      const struct pci_device_id *id
+				     )
+		    );
+void oxygen_pci_remove(struct pci_dev *pci);
+#ifdef CONFIG_PM
+int oxygen_pci_suspend(struct pci_dev *pci, pm_message_t state);
+int oxygen_pci_resume(struct pci_dev *pci);
+#endif
+void oxygen_pci_shutdown(struct pci_dev *pci);
+
+/* oxygen_mixer.c */
+
+int oxygen_mixer_init(struct oxygen *chip);
+void oxygen_update_dac_routing(struct oxygen *chip);
+void oxygen_update_spdif_source(struct oxygen *chip);
+
+/* oxygen_pcm.c */
+
+int oxygen_pcm_init(struct oxygen *chip);
+unsigned int oxygen_default_i2s_mclk(struct oxygen *chip, unsigned int channel,
+				     struct snd_pcm_hw_params *hw_params);
+
+/* oxygen_io.c */
+
+u8 oxygen_read8(struct oxygen *chip, unsigned int reg);
+u16 oxygen_read16(struct oxygen *chip, unsigned int reg);
+u32 oxygen_read32(struct oxygen *chip, unsigned int reg);
+void oxygen_write8(struct oxygen *chip, unsigned int reg, u8 value);
+void oxygen_write16(struct oxygen *chip, unsigned int reg, u16 value);
+void oxygen_write32(struct oxygen *chip, unsigned int reg, u32 value);
+void oxygen_write8_masked(struct oxygen *chip, unsigned int reg,
+			  u8 value, u8 mask);
+void oxygen_write16_masked(struct oxygen *chip, unsigned int reg,
+			   u16 value, u16 mask);
+void oxygen_write32_masked(struct oxygen *chip, unsigned int reg,
+			   u32 value, u32 mask);
+
+u16 oxygen_read_ac97(struct oxygen *chip, unsigned int codec,
+		     unsigned int index);
+void oxygen_write_ac97(struct oxygen *chip, unsigned int codec,
+		       unsigned int index, u16 data);
+void oxygen_write_ac97_masked(struct oxygen *chip, unsigned int codec,
+			      unsigned int index, u16 data, u16 mask);
+
+void oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data);
+void oxygen_write_i2c(struct oxygen *chip, u8 device, u8 map, u8 data);
+
+void oxygen_reset_uart(struct oxygen *chip);
+void oxygen_write_uart(struct oxygen *chip, u8 data);
+
+u16 oxygen_read_eeprom(struct oxygen *chip, unsigned int index);
+void oxygen_write_eeprom(struct oxygen *chip, unsigned int index, u16 value);
+
+static inline void oxygen_set_bits8(struct oxygen *chip,
+				    unsigned int reg, u8 value)
+{
+	oxygen_write8_masked(chip, reg, value, value);
+}
+
+static inline void oxygen_set_bits16(struct oxygen *chip,
+				     unsigned int reg, u16 value)
+{
+	oxygen_write16_masked(chip, reg, value, value);
+}
+
+static inline void oxygen_set_bits32(struct oxygen *chip,
+				     unsigned int reg, u32 value)
+{
+	oxygen_write32_masked(chip, reg, value, value);
+}
+
+static inline void oxygen_clear_bits8(struct oxygen *chip,
+				      unsigned int reg, u8 value)
+{
+	oxygen_write8_masked(chip, reg, 0, value);
+}
+
+static inline void oxygen_clear_bits16(struct oxygen *chip,
+				       unsigned int reg, u16 value)
+{
+	oxygen_write16_masked(chip, reg, 0, value);
+}
+
+static inline void oxygen_clear_bits32(struct oxygen *chip,
+				       unsigned int reg, u32 value)
+{
+	oxygen_write32_masked(chip, reg, 0, value);
+}
+
+static inline void oxygen_ac97_set_bits(struct oxygen *chip, unsigned int codec,
+					unsigned int index, u16 value)
+{
+	oxygen_write_ac97_masked(chip, codec, index, value, value);
+}
+
+static inline void oxygen_ac97_clear_bits(struct oxygen *chip,
+					  unsigned int codec,
+					  unsigned int index, u16 value)
+{
+	oxygen_write_ac97_masked(chip, codec, index, 0, value);
+}
+
+#endif
diff --git a/linux/sound/pci/oxygen/oxygen_io.c b/linux/sound/pci/oxygen/oxygen_io.c
new file mode 100644
index 0000000..09b2b2a
--- /dev/null
+++ b/linux/sound/pci/oxygen/oxygen_io.c
@@ -0,0 +1,278 @@
+/*
+ * C-Media CMI8788 driver - helper functions
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <sound/core.h>
+#include <sound/mpu401.h>
+#include <asm/io.h>
+#include "oxygen.h"
+
+u8 oxygen_read8(struct oxygen *chip, unsigned int reg)
+{
+	return inb(chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_read8);
+
+u16 oxygen_read16(struct oxygen *chip, unsigned int reg)
+{
+	return inw(chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_read16);
+
+u32 oxygen_read32(struct oxygen *chip, unsigned int reg)
+{
+	return inl(chip->addr + reg);
+}
+EXPORT_SYMBOL(oxygen_read32);
+
+void oxygen_write8(struct oxygen *chip, unsigned int reg, u8 value)
+{
+	outb(value, chip->addr + reg);
+	chip->saved_registers._8[reg] = value;
+}
+EXPORT_SYMBOL(oxygen_write8);
+
+void oxygen_write16(struct oxygen *chip, unsigned int reg, u16 value)
+{
+	outw(value, chip->addr + reg);
+	chip->saved_registers._16[reg / 2] = cpu_to_le16(value);
+}
+EXPORT_SYMBOL(oxygen_write16);
+
+void oxygen_write32(struct oxygen *chip, unsigned int reg, u32 value)
+{
+	outl(value, chip->addr + reg);
+	chip->saved_registers._32[reg / 4] = cpu_to_le32(value);
+}
+EXPORT_SYMBOL(oxygen_write32);
+
+void oxygen_write8_masked(struct oxygen *chip, unsigned int reg,
+			  u8 value, u8 mask)
+{
+	u8 tmp = inb(chip->addr + reg);
+	tmp &= ~mask;
+	tmp |= value & mask;
+	outb(tmp, chip->addr + reg);
+	chip->saved_registers._8[reg] = tmp;
+}
+EXPORT_SYMBOL(oxygen_write8_masked);
+
+void oxygen_write16_masked(struct oxygen *chip, unsigned int reg,
+			   u16 value, u16 mask)
+{
+	u16 tmp = inw(chip->addr + reg);
+	tmp &= ~mask;
+	tmp |= value & mask;
+	outw(tmp, chip->addr + reg);
+	chip->saved_registers._16[reg / 2] = cpu_to_le16(tmp);
+}
+EXPORT_SYMBOL(oxygen_write16_masked);
+
+void oxygen_write32_masked(struct oxygen *chip, unsigned int reg,
+			   u32 value, u32 mask)
+{
+	u32 tmp = inl(chip->addr + reg);
+	tmp &= ~mask;
+	tmp |= value & mask;
+	outl(tmp, chip->addr + reg);
+	chip->saved_registers._32[reg / 4] = cpu_to_le32(tmp);
+}
+EXPORT_SYMBOL(oxygen_write32_masked);
+
+static int oxygen_ac97_wait(struct oxygen *chip, unsigned int mask)
+{
+	u8 status = 0;
+
+	/*
+	 * Reading the status register also clears the bits, so we have to save
+	 * the read bits in status.
+	 */
+	wait_event_timeout(chip->ac97_waitqueue,
+			   ({ status |= oxygen_read8(chip, OXYGEN_AC97_INTERRUPT_STATUS);
+			      status & mask; }),
+			   msecs_to_jiffies(1) + 1);
+	/*
+	 * Check even after a timeout because this function should not require
+	 * the AC'97 interrupt to be enabled.
+	 */
+	status |= oxygen_read8(chip, OXYGEN_AC97_INTERRUPT_STATUS);
+	return status & mask ? 0 : -EIO;
+}
+
+/*
+ * About 10% of AC'97 register reads or writes fail to complete, but even those
+ * where the controller indicates completion aren't guaranteed to have actually
+ * happened.
+ *
+ * It's hard to assign blame to either the controller or the codec because both
+ * were made by C-Media ...
+ */
+
+void oxygen_write_ac97(struct oxygen *chip, unsigned int codec,
+		       unsigned int index, u16 data)
+{
+	unsigned int count, succeeded;
+	u32 reg;
+
+	reg = data;
+	reg |= index << OXYGEN_AC97_REG_ADDR_SHIFT;
+	reg |= OXYGEN_AC97_REG_DIR_WRITE;
+	reg |= codec << OXYGEN_AC97_REG_CODEC_SHIFT;
+	succeeded = 0;
+	for (count = 5; count > 0; --count) {
+		udelay(5);
+		oxygen_write32(chip, OXYGEN_AC97_REGS, reg);
+		/* require two "completed" writes, just to be sure */
+		if (oxygen_ac97_wait(chip, OXYGEN_AC97_INT_WRITE_DONE) >= 0 &&
+		    ++succeeded >= 2) {
+			chip->saved_ac97_registers[codec][index / 2] = data;
+			return;
+		}
+	}
+	snd_printk(KERN_ERR "AC'97 write timeout\n");
+}
+EXPORT_SYMBOL(oxygen_write_ac97);
+
+u16 oxygen_read_ac97(struct oxygen *chip, unsigned int codec,
+		     unsigned int index)
+{
+	unsigned int count;
+	unsigned int last_read = UINT_MAX;
+	u32 reg;
+
+	reg = index << OXYGEN_AC97_REG_ADDR_SHIFT;
+	reg |= OXYGEN_AC97_REG_DIR_READ;
+	reg |= codec << OXYGEN_AC97_REG_CODEC_SHIFT;
+	for (count = 5; count > 0; --count) {
+		udelay(5);
+		oxygen_write32(chip, OXYGEN_AC97_REGS, reg);
+		udelay(10);
+		if (oxygen_ac97_wait(chip, OXYGEN_AC97_INT_READ_DONE) >= 0) {
+			u16 value = oxygen_read16(chip, OXYGEN_AC97_REGS);
+			/* we require two consecutive reads of the same value */
+			if (value == last_read)
+				return value;
+			last_read = value;
+			/*
+			 * Invert the register value bits to make sure that two
+			 * consecutive unsuccessful reads do not return the same
+			 * value.
+			 */
+			reg ^= 0xffff;
+		}
+	}
+	snd_printk(KERN_ERR "AC'97 read timeout on codec %u\n", codec);
+	return 0;
+}
+EXPORT_SYMBOL(oxygen_read_ac97);
+
+void oxygen_write_ac97_masked(struct oxygen *chip, unsigned int codec,
+			      unsigned int index, u16 data, u16 mask)
+{
+	u16 value = oxygen_read_ac97(chip, codec, index);
+	value &= ~mask;
+	value |= data & mask;
+	oxygen_write_ac97(chip, codec, index, value);
+}
+EXPORT_SYMBOL(oxygen_write_ac97_masked);
+
+void oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data)
+{
+	unsigned int count;
+
+	/* should not need more than 7.68 us (24 * 320 ns) */
+	count = 10;
+	while ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) & OXYGEN_SPI_BUSY)
+	       && count > 0) {
+		udelay(1);
+		--count;
+	}
+
+	oxygen_write8(chip, OXYGEN_SPI_DATA1, data);
+	oxygen_write8(chip, OXYGEN_SPI_DATA2, data >> 8);
+	if (control & OXYGEN_SPI_DATA_LENGTH_3)
+		oxygen_write8(chip, OXYGEN_SPI_DATA3, data >> 16);
+	oxygen_write8(chip, OXYGEN_SPI_CONTROL, control);
+}
+EXPORT_SYMBOL(oxygen_write_spi);
+
+void oxygen_write_i2c(struct oxygen *chip, u8 device, u8 map, u8 data)
+{
+	/* should not need more than about 300 us */
+	msleep(1);
+
+	oxygen_write8(chip, OXYGEN_2WIRE_MAP, map);
+	oxygen_write8(chip, OXYGEN_2WIRE_DATA, data);
+	oxygen_write8(chip, OXYGEN_2WIRE_CONTROL,
+		      device | OXYGEN_2WIRE_DIR_WRITE);
+}
+EXPORT_SYMBOL(oxygen_write_i2c);
+
+static void _write_uart(struct oxygen *chip, unsigned int port, u8 data)
+{
+	if (oxygen_read8(chip, OXYGEN_MPU401 + 1) & MPU401_TX_FULL)
+		msleep(1);
+	oxygen_write8(chip, OXYGEN_MPU401 + port, data);
+}
+
+void oxygen_reset_uart(struct oxygen *chip)
+{
+	_write_uart(chip, 1, MPU401_RESET);
+	msleep(1); /* wait for ACK */
+	_write_uart(chip, 1, MPU401_ENTER_UART);
+}
+EXPORT_SYMBOL(oxygen_reset_uart);
+
+void oxygen_write_uart(struct oxygen *chip, u8 data)
+{
+	_write_uart(chip, 0, data);
+}
+EXPORT_SYMBOL(oxygen_write_uart);
+
+u16 oxygen_read_eeprom(struct oxygen *chip, unsigned int index)
+{
+	unsigned int timeout;
+
+	oxygen_write8(chip, OXYGEN_EEPROM_CONTROL,
+		      index | OXYGEN_EEPROM_DIR_READ);
+	for (timeout = 0; timeout < 100; ++timeout) {
+		udelay(1);
+		if (!(oxygen_read8(chip, OXYGEN_EEPROM_STATUS)
+		      & OXYGEN_EEPROM_BUSY))
+			break;
+	}
+	return oxygen_read16(chip, OXYGEN_EEPROM_DATA);
+}
+
+void oxygen_write_eeprom(struct oxygen *chip, unsigned int index, u16 value)
+{
+	unsigned int timeout;
+
+	oxygen_write16(chip, OXYGEN_EEPROM_DATA, value);
+	oxygen_write8(chip, OXYGEN_EEPROM_CONTROL,
+		      index | OXYGEN_EEPROM_DIR_WRITE);
+	for (timeout = 0; timeout < 10; ++timeout) {
+		msleep(1);
+		if (!(oxygen_read8(chip, OXYGEN_EEPROM_STATUS)
+		      & OXYGEN_EEPROM_BUSY))
+			return;
+	}
+	snd_printk(KERN_ERR "EEPROM write timeout\n");
+}
diff --git a/linux/sound/pci/oxygen/oxygen_lib.c b/linux/sound/pci/oxygen/oxygen_lib.c
new file mode 100644
index 0000000..e5ebe56
--- /dev/null
+++ b/linux/sound/pci/oxygen/oxygen_lib.c
@@ -0,0 +1,816 @@
+/*
+ * C-Media CMI8788 driver - main driver module
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/mpu401.h>
+#include <sound/pcm.h>
+#include "oxygen.h"
+#include "cm9780.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("C-Media CMI8788 helper library");
+MODULE_LICENSE("GPL v2");
+
+#define DRIVER "oxygen"
+
+static inline int oxygen_uart_input_ready(struct oxygen *chip)
+{
+	return !(oxygen_read8(chip, OXYGEN_MPU401 + 1) & MPU401_RX_EMPTY);
+}
+
+static void oxygen_read_uart(struct oxygen *chip)
+{
+	if (unlikely(!oxygen_uart_input_ready(chip))) {
+		/* no data, but read it anyway to clear the interrupt */
+		oxygen_read8(chip, OXYGEN_MPU401);
+		return;
+	}
+	do {
+		u8 data = oxygen_read8(chip, OXYGEN_MPU401);
+		if (data == MPU401_ACK)
+			continue;
+		if (chip->uart_input_count >= ARRAY_SIZE(chip->uart_input))
+			chip->uart_input_count = 0;
+		chip->uart_input[chip->uart_input_count++] = data;
+	} while (oxygen_uart_input_ready(chip));
+	if (chip->model.uart_input)
+		chip->model.uart_input(chip);
+}
+
+static irqreturn_t oxygen_interrupt(int dummy, void *dev_id)
+{
+	struct oxygen *chip = dev_id;
+	unsigned int status, clear, elapsed_streams, i;
+
+	status = oxygen_read16(chip, OXYGEN_INTERRUPT_STATUS);
+	if (!status)
+		return IRQ_NONE;
+
+	spin_lock(&chip->reg_lock);
+
+	clear = status & (OXYGEN_CHANNEL_A |
+			  OXYGEN_CHANNEL_B |
+			  OXYGEN_CHANNEL_C |
+			  OXYGEN_CHANNEL_SPDIF |
+			  OXYGEN_CHANNEL_MULTICH |
+			  OXYGEN_CHANNEL_AC97 |
+			  OXYGEN_INT_SPDIF_IN_DETECT |
+			  OXYGEN_INT_GPIO |
+			  OXYGEN_INT_AC97);
+	if (clear) {
+		if (clear & OXYGEN_INT_SPDIF_IN_DETECT)
+			chip->interrupt_mask &= ~OXYGEN_INT_SPDIF_IN_DETECT;
+		oxygen_write16(chip, OXYGEN_INTERRUPT_MASK,
+			       chip->interrupt_mask & ~clear);
+		oxygen_write16(chip, OXYGEN_INTERRUPT_MASK,
+			       chip->interrupt_mask);
+	}
+
+	elapsed_streams = status & chip->pcm_running;
+
+	spin_unlock(&chip->reg_lock);
+
+	for (i = 0; i < PCM_COUNT; ++i)
+		if ((elapsed_streams & (1 << i)) && chip->streams[i])
+			snd_pcm_period_elapsed(chip->streams[i]);
+
+	if (status & OXYGEN_INT_SPDIF_IN_DETECT) {
+		spin_lock(&chip->reg_lock);
+		i = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
+		if (i & (OXYGEN_SPDIF_SENSE_INT | OXYGEN_SPDIF_LOCK_INT |
+			 OXYGEN_SPDIF_RATE_INT)) {
+			/* write the interrupt bit(s) to clear */
+			oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, i);
+			schedule_work(&chip->spdif_input_bits_work);
+		}
+		spin_unlock(&chip->reg_lock);
+	}
+
+	if (status & OXYGEN_INT_GPIO)
+		schedule_work(&chip->gpio_work);
+
+	if (status & OXYGEN_INT_MIDI) {
+		if (chip->midi)
+			snd_mpu401_uart_interrupt(0, chip->midi->private_data);
+		else
+			oxygen_read_uart(chip);
+	}
+
+	if (status & OXYGEN_INT_AC97)
+		wake_up(&chip->ac97_waitqueue);
+
+	return IRQ_HANDLED;
+}
+
+static void oxygen_spdif_input_bits_changed(struct work_struct *work)
+{
+	struct oxygen *chip = container_of(work, struct oxygen,
+					   spdif_input_bits_work);
+	u32 reg;
+
+	/*
+	 * This function gets called when there is new activity on the SPDIF
+	 * input, or when we lose lock on the input signal, or when the rate
+	 * changes.
+	 */
+	msleep(1);
+	spin_lock_irq(&chip->reg_lock);
+	reg = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
+	if ((reg & (OXYGEN_SPDIF_SENSE_STATUS |
+		    OXYGEN_SPDIF_LOCK_STATUS))
+	    == OXYGEN_SPDIF_SENSE_STATUS) {
+		/*
+		 * If we detect activity on the SPDIF input but cannot lock to
+		 * a signal, the clock bit is likely to be wrong.
+		 */
+		reg ^= OXYGEN_SPDIF_IN_CLOCK_MASK;
+		oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, reg);
+		spin_unlock_irq(&chip->reg_lock);
+		msleep(1);
+		spin_lock_irq(&chip->reg_lock);
+		reg = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
+		if ((reg & (OXYGEN_SPDIF_SENSE_STATUS |
+			    OXYGEN_SPDIF_LOCK_STATUS))
+		    == OXYGEN_SPDIF_SENSE_STATUS) {
+			/* nothing detected with either clock; give up */
+			if ((reg & OXYGEN_SPDIF_IN_CLOCK_MASK)
+			    == OXYGEN_SPDIF_IN_CLOCK_192) {
+				/*
+				 * Reset clock to <= 96 kHz because this is
+				 * more likely to be received next time.
+				 */
+				reg &= ~OXYGEN_SPDIF_IN_CLOCK_MASK;
+				reg |= OXYGEN_SPDIF_IN_CLOCK_96;
+				oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, reg);
+			}
+		}
+	}
+	spin_unlock_irq(&chip->reg_lock);
+
+	if (chip->controls[CONTROL_SPDIF_INPUT_BITS]) {
+		spin_lock_irq(&chip->reg_lock);
+		chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_DETECT;
+		oxygen_write16(chip, OXYGEN_INTERRUPT_MASK,
+			       chip->interrupt_mask);
+		spin_unlock_irq(&chip->reg_lock);
+
+		/*
+		 * We don't actually know that any channel status bits have
+		 * changed, but let's send a notification just to be sure.
+		 */
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->controls[CONTROL_SPDIF_INPUT_BITS]->id);
+	}
+}
+
+static void oxygen_gpio_changed(struct work_struct *work)
+{
+	struct oxygen *chip = container_of(work, struct oxygen, gpio_work);
+
+	if (chip->model.gpio_changed)
+		chip->model.gpio_changed(chip);
+}
+
+#ifdef CONFIG_PROC_FS
+static void oxygen_proc_read(struct snd_info_entry *entry,
+			     struct snd_info_buffer *buffer)
+{
+	struct oxygen *chip = entry->private_data;
+	int i, j;
+
+	snd_iprintf(buffer, "CMI8788\n\n");
+	for (i = 0; i < OXYGEN_IO_SIZE; i += 0x10) {
+		snd_iprintf(buffer, "%02x:", i);
+		for (j = 0; j < 0x10; ++j)
+			snd_iprintf(buffer, " %02x", oxygen_read8(chip, i + j));
+		snd_iprintf(buffer, "\n");
+	}
+	if (mutex_lock_interruptible(&chip->mutex) < 0)
+		return;
+	if (chip->has_ac97_0) {
+		snd_iprintf(buffer, "\nAC97\n");
+		for (i = 0; i < 0x80; i += 0x10) {
+			snd_iprintf(buffer, "%02x:", i);
+			for (j = 0; j < 0x10; j += 2)
+				snd_iprintf(buffer, " %04x",
+					    oxygen_read_ac97(chip, 0, i + j));
+			snd_iprintf(buffer, "\n");
+		}
+	}
+	if (chip->has_ac97_1) {
+		snd_iprintf(buffer, "\nAC97 2\n");
+		for (i = 0; i < 0x80; i += 0x10) {
+			snd_iprintf(buffer, "%02x:", i);
+			for (j = 0; j < 0x10; j += 2)
+				snd_iprintf(buffer, " %04x",
+					    oxygen_read_ac97(chip, 1, i + j));
+			snd_iprintf(buffer, "\n");
+		}
+	}
+	mutex_unlock(&chip->mutex);
+}
+
+static void oxygen_proc_init(struct oxygen *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(chip->card, "cmi8788", &entry))
+		snd_info_set_text_ops(entry, chip, oxygen_proc_read);
+}
+#else
+#define oxygen_proc_init(chip)
+#endif
+
+static const struct pci_device_id *
+oxygen_search_pci_id(struct oxygen *chip, const struct pci_device_id ids[])
+{
+	u16 subdevice;
+
+	/*
+	 * Make sure the EEPROM pins are available, i.e., not used for SPI.
+	 * (This function is called before we initialize or use SPI.)
+	 */
+	oxygen_clear_bits8(chip, OXYGEN_FUNCTION,
+			   OXYGEN_FUNCTION_ENABLE_SPI_4_5);
+	/*
+	 * Read the subsystem device ID directly from the EEPROM, because the
+	 * chip didn't if the first EEPROM word was overwritten.
+	 */
+	subdevice = oxygen_read_eeprom(chip, 2);
+	/* use default ID if EEPROM is missing */
+	if (subdevice == 0xffff)
+		subdevice = 0x8788;
+	/*
+	 * We use only the subsystem device ID for searching because it is
+	 * unique even without the subsystem vendor ID, which may have been
+	 * overwritten in the EEPROM.
+	 */
+	for (; ids->vendor; ++ids)
+		if (ids->subdevice == subdevice &&
+		    ids->driver_data != BROKEN_EEPROM_DRIVER_DATA)
+			return ids;
+	return NULL;
+}
+
+static void oxygen_restore_eeprom(struct oxygen *chip,
+				  const struct pci_device_id *id)
+{
+	u16 eeprom_id;
+
+	eeprom_id = oxygen_read_eeprom(chip, 0);
+	if (eeprom_id != OXYGEN_EEPROM_ID &&
+	    (eeprom_id != 0xffff || id->subdevice != 0x8788)) {
+		/*
+		 * This function gets called only when a known card model has
+		 * been detected, i.e., we know there is a valid subsystem
+		 * product ID at index 2 in the EEPROM.  Therefore, we have
+		 * been able to deduce the correct subsystem vendor ID, and
+		 * this is enough information to restore the original EEPROM
+		 * contents.
+		 */
+		oxygen_write_eeprom(chip, 1, id->subvendor);
+		oxygen_write_eeprom(chip, 0, OXYGEN_EEPROM_ID);
+
+		oxygen_set_bits8(chip, OXYGEN_MISC,
+				 OXYGEN_MISC_WRITE_PCI_SUBID);
+		pci_write_config_word(chip->pci, PCI_SUBSYSTEM_VENDOR_ID,
+				      id->subvendor);
+		pci_write_config_word(chip->pci, PCI_SUBSYSTEM_ID,
+				      id->subdevice);
+		oxygen_clear_bits8(chip, OXYGEN_MISC,
+				   OXYGEN_MISC_WRITE_PCI_SUBID);
+
+		snd_printk(KERN_INFO "EEPROM ID restored\n");
+	}
+}
+
+static void configure_pcie_bridge(struct pci_dev *pci)
+{
+	enum { PEX811X, PI7C9X110 };
+	static const struct pci_device_id bridge_ids[] = {
+		{ PCI_VDEVICE(PLX, 0x8111), .driver_data = PEX811X },
+		{ PCI_VDEVICE(PLX, 0x8112), .driver_data = PEX811X },
+		{ PCI_DEVICE(0x12d8, 0xe110), .driver_data = PI7C9X110 },
+		{ }
+	};
+	struct pci_dev *bridge;
+	const struct pci_device_id *id;
+	u32 tmp;
+
+	if (!pci->bus || !pci->bus->self)
+		return;
+	bridge = pci->bus->self;
+
+	id = pci_match_id(bridge_ids, bridge);
+	if (!id)
+		return;
+
+	switch (id->driver_data) {
+	case PEX811X:	/* PLX PEX8111/PEX8112 PCIe/PCI bridge */
+		pci_read_config_dword(bridge, 0x48, &tmp);
+		tmp |= 1;	/* enable blind prefetching */
+		tmp |= 1 << 11;	/* enable beacon generation */
+		pci_write_config_dword(bridge, 0x48, tmp);
+
+		pci_write_config_dword(bridge, 0x84, 0x0c);
+		pci_read_config_dword(bridge, 0x88, &tmp);
+		tmp &= ~(7 << 27);
+		tmp |= 2 << 27;	/* set prefetch size to 128 bytes */
+		pci_write_config_dword(bridge, 0x88, tmp);
+		break;
+
+	case PI7C9X110:	/* Pericom PI7C9X110 PCIe/PCI bridge */
+		pci_read_config_dword(bridge, 0x40, &tmp);
+		tmp |= 1;	/* park the PCI arbiter to the sound chip */
+		pci_write_config_dword(bridge, 0x40, tmp);
+		break;
+	}
+}
+
+static void oxygen_init(struct oxygen *chip)
+{
+	unsigned int i;
+
+	chip->dac_routing = 1;
+	for (i = 0; i < 8; ++i)
+		chip->dac_volume[i] = chip->model.dac_volume_min;
+	chip->dac_mute = 1;
+	chip->spdif_playback_enable = 1;
+	chip->spdif_bits = OXYGEN_SPDIF_C | OXYGEN_SPDIF_ORIGINAL |
+		(IEC958_AES1_CON_PCM_CODER << OXYGEN_SPDIF_CATEGORY_SHIFT);
+	chip->spdif_pcm_bits = chip->spdif_bits;
+
+	if (oxygen_read8(chip, OXYGEN_REVISION) & OXYGEN_REVISION_2)
+		chip->revision = 2;
+	else
+		chip->revision = 1;
+
+	if (chip->revision == 1)
+		oxygen_set_bits8(chip, OXYGEN_MISC,
+				 OXYGEN_MISC_PCI_MEM_W_1_CLOCK);
+
+	i = oxygen_read16(chip, OXYGEN_AC97_CONTROL);
+	chip->has_ac97_0 = (i & OXYGEN_AC97_CODEC_0) != 0;
+	chip->has_ac97_1 = (i & OXYGEN_AC97_CODEC_1) != 0;
+
+	oxygen_write8_masked(chip, OXYGEN_FUNCTION,
+			     OXYGEN_FUNCTION_RESET_CODEC |
+			     chip->model.function_flags,
+			     OXYGEN_FUNCTION_RESET_CODEC |
+			     OXYGEN_FUNCTION_2WIRE_SPI_MASK |
+			     OXYGEN_FUNCTION_ENABLE_SPI_4_5);
+	oxygen_write8(chip, OXYGEN_DMA_STATUS, 0);
+	oxygen_write8(chip, OXYGEN_DMA_PAUSE, 0);
+	oxygen_write8(chip, OXYGEN_PLAY_CHANNELS,
+		      OXYGEN_PLAY_CHANNELS_2 |
+		      OXYGEN_DMA_A_BURST_8 |
+		      OXYGEN_DMA_MULTICH_BURST_8);
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0);
+	oxygen_write8_masked(chip, OXYGEN_MISC,
+			     chip->model.misc_flags,
+			     OXYGEN_MISC_WRITE_PCI_SUBID |
+			     OXYGEN_MISC_REC_C_FROM_SPDIF |
+			     OXYGEN_MISC_REC_B_FROM_AC97 |
+			     OXYGEN_MISC_REC_A_FROM_MULTICH |
+			     OXYGEN_MISC_MIDI);
+	oxygen_write8(chip, OXYGEN_REC_FORMAT,
+		      (OXYGEN_FORMAT_16 << OXYGEN_REC_FORMAT_A_SHIFT) |
+		      (OXYGEN_FORMAT_16 << OXYGEN_REC_FORMAT_B_SHIFT) |
+		      (OXYGEN_FORMAT_16 << OXYGEN_REC_FORMAT_C_SHIFT));
+	oxygen_write8(chip, OXYGEN_PLAY_FORMAT,
+		      (OXYGEN_FORMAT_16 << OXYGEN_SPDIF_FORMAT_SHIFT) |
+		      (OXYGEN_FORMAT_16 << OXYGEN_MULTICH_FORMAT_SHIFT));
+	oxygen_write8(chip, OXYGEN_REC_CHANNELS, OXYGEN_REC_CHANNELS_2_2_2);
+	oxygen_write16(chip, OXYGEN_I2S_MULTICH_FORMAT,
+		       OXYGEN_RATE_48000 | chip->model.dac_i2s_format |
+		       OXYGEN_I2S_MCLK_256 | OXYGEN_I2S_BITS_16 |
+		       OXYGEN_I2S_MASTER | OXYGEN_I2S_BCLK_64);
+	if (chip->model.device_config & CAPTURE_0_FROM_I2S_1)
+		oxygen_write16(chip, OXYGEN_I2S_A_FORMAT,
+			       OXYGEN_RATE_48000 | chip->model.adc_i2s_format |
+			       OXYGEN_I2S_MCLK_256 | OXYGEN_I2S_BITS_16 |
+			       OXYGEN_I2S_MASTER | OXYGEN_I2S_BCLK_64);
+	else
+		oxygen_write16(chip, OXYGEN_I2S_A_FORMAT,
+			       OXYGEN_I2S_MASTER | OXYGEN_I2S_MUTE_MCLK);
+	if (chip->model.device_config & (CAPTURE_0_FROM_I2S_2 |
+					 CAPTURE_2_FROM_I2S_2))
+		oxygen_write16(chip, OXYGEN_I2S_B_FORMAT,
+			       OXYGEN_RATE_48000 | chip->model.adc_i2s_format |
+			       OXYGEN_I2S_MCLK_256 | OXYGEN_I2S_BITS_16 |
+			       OXYGEN_I2S_MASTER | OXYGEN_I2S_BCLK_64);
+	else
+		oxygen_write16(chip, OXYGEN_I2S_B_FORMAT,
+			       OXYGEN_I2S_MASTER | OXYGEN_I2S_MUTE_MCLK);
+	oxygen_write16(chip, OXYGEN_I2S_C_FORMAT,
+		       OXYGEN_I2S_MASTER | OXYGEN_I2S_MUTE_MCLK);
+	oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
+			    OXYGEN_SPDIF_OUT_ENABLE |
+			    OXYGEN_SPDIF_LOOPBACK);
+	if (chip->model.device_config & CAPTURE_1_FROM_SPDIF)
+		oxygen_write32_masked(chip, OXYGEN_SPDIF_CONTROL,
+				      OXYGEN_SPDIF_SENSE_MASK |
+				      OXYGEN_SPDIF_LOCK_MASK |
+				      OXYGEN_SPDIF_RATE_MASK |
+				      OXYGEN_SPDIF_LOCK_PAR |
+				      OXYGEN_SPDIF_IN_CLOCK_96,
+				      OXYGEN_SPDIF_SENSE_MASK |
+				      OXYGEN_SPDIF_LOCK_MASK |
+				      OXYGEN_SPDIF_RATE_MASK |
+				      OXYGEN_SPDIF_SENSE_PAR |
+				      OXYGEN_SPDIF_LOCK_PAR |
+				      OXYGEN_SPDIF_IN_CLOCK_MASK);
+	else
+		oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
+				    OXYGEN_SPDIF_SENSE_MASK |
+				    OXYGEN_SPDIF_LOCK_MASK |
+				    OXYGEN_SPDIF_RATE_MASK);
+	oxygen_write32(chip, OXYGEN_SPDIF_OUTPUT_BITS, chip->spdif_bits);
+	oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS,
+		       OXYGEN_2WIRE_LENGTH_8 |
+		       OXYGEN_2WIRE_INTERRUPT_MASK |
+		       OXYGEN_2WIRE_SPEED_STANDARD);
+	oxygen_clear_bits8(chip, OXYGEN_MPU401_CONTROL, OXYGEN_MPU401_LOOPBACK);
+	oxygen_write8(chip, OXYGEN_GPI_INTERRUPT_MASK, 0);
+	oxygen_write16(chip, OXYGEN_GPIO_INTERRUPT_MASK, 0);
+	oxygen_write16(chip, OXYGEN_PLAY_ROUTING,
+		       OXYGEN_PLAY_MULTICH_I2S_DAC |
+		       OXYGEN_PLAY_SPDIF_SPDIF |
+		       (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		       (1 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		       (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		       (3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT));
+	oxygen_write8(chip, OXYGEN_REC_ROUTING,
+		      OXYGEN_REC_A_ROUTE_I2S_ADC_1 |
+		      OXYGEN_REC_B_ROUTE_I2S_ADC_2 |
+		      OXYGEN_REC_C_ROUTE_SPDIF);
+	oxygen_write8(chip, OXYGEN_ADC_MONITOR, 0);
+	oxygen_write8(chip, OXYGEN_A_MONITOR_ROUTING,
+		      (0 << OXYGEN_A_MONITOR_ROUTE_0_SHIFT) |
+		      (1 << OXYGEN_A_MONITOR_ROUTE_1_SHIFT) |
+		      (2 << OXYGEN_A_MONITOR_ROUTE_2_SHIFT) |
+		      (3 << OXYGEN_A_MONITOR_ROUTE_3_SHIFT));
+
+	if (chip->has_ac97_0 | chip->has_ac97_1)
+		oxygen_write8(chip, OXYGEN_AC97_INTERRUPT_MASK,
+			      OXYGEN_AC97_INT_READ_DONE |
+			      OXYGEN_AC97_INT_WRITE_DONE);
+	else
+		oxygen_write8(chip, OXYGEN_AC97_INTERRUPT_MASK, 0);
+	oxygen_write32(chip, OXYGEN_AC97_OUT_CONFIG, 0);
+	oxygen_write32(chip, OXYGEN_AC97_IN_CONFIG, 0);
+	if (!(chip->has_ac97_0 | chip->has_ac97_1))
+		oxygen_set_bits16(chip, OXYGEN_AC97_CONTROL,
+				  OXYGEN_AC97_CLOCK_DISABLE);
+	if (!chip->has_ac97_0) {
+		oxygen_set_bits16(chip, OXYGEN_AC97_CONTROL,
+				  OXYGEN_AC97_NO_CODEC_0);
+	} else {
+		oxygen_write_ac97(chip, 0, AC97_RESET, 0);
+		msleep(1);
+		oxygen_ac97_set_bits(chip, 0, CM9780_GPIO_SETUP,
+				     CM9780_GPIO0IO | CM9780_GPIO1IO);
+		oxygen_ac97_set_bits(chip, 0, CM9780_MIXER,
+				     CM9780_BSTSEL | CM9780_STRO_MIC |
+				     CM9780_MIX2FR | CM9780_PCBSW);
+		oxygen_ac97_set_bits(chip, 0, CM9780_JACK,
+				     CM9780_RSOE | CM9780_CBOE |
+				     CM9780_SSOE | CM9780_FROE |
+				     CM9780_MIC2MIC | CM9780_LI2LI);
+		oxygen_write_ac97(chip, 0, AC97_MASTER, 0x0000);
+		oxygen_write_ac97(chip, 0, AC97_PC_BEEP, 0x8000);
+		oxygen_write_ac97(chip, 0, AC97_MIC, 0x8808);
+		oxygen_write_ac97(chip, 0, AC97_LINE, 0x0808);
+		oxygen_write_ac97(chip, 0, AC97_CD, 0x8808);
+		oxygen_write_ac97(chip, 0, AC97_VIDEO, 0x8808);
+		oxygen_write_ac97(chip, 0, AC97_AUX, 0x8808);
+		oxygen_write_ac97(chip, 0, AC97_REC_GAIN, 0x8000);
+		oxygen_write_ac97(chip, 0, AC97_CENTER_LFE_MASTER, 0x8080);
+		oxygen_write_ac97(chip, 0, AC97_SURROUND_MASTER, 0x8080);
+		oxygen_ac97_clear_bits(chip, 0, CM9780_GPIO_STATUS,
+				       CM9780_GPO0);
+		/* power down unused ADCs and DACs */
+		oxygen_ac97_set_bits(chip, 0, AC97_POWERDOWN,
+				     AC97_PD_PR0 | AC97_PD_PR1);
+		oxygen_ac97_set_bits(chip, 0, AC97_EXTENDED_STATUS,
+				     AC97_EA_PRI | AC97_EA_PRJ | AC97_EA_PRK);
+	}
+	if (chip->has_ac97_1) {
+		oxygen_set_bits32(chip, OXYGEN_AC97_OUT_CONFIG,
+				  OXYGEN_AC97_CODEC1_SLOT3 |
+				  OXYGEN_AC97_CODEC1_SLOT4);
+		oxygen_write_ac97(chip, 1, AC97_RESET, 0);
+		msleep(1);
+		oxygen_write_ac97(chip, 1, AC97_MASTER, 0x0000);
+		oxygen_write_ac97(chip, 1, AC97_HEADPHONE, 0x8000);
+		oxygen_write_ac97(chip, 1, AC97_PC_BEEP, 0x8000);
+		oxygen_write_ac97(chip, 1, AC97_MIC, 0x8808);
+		oxygen_write_ac97(chip, 1, AC97_LINE, 0x8808);
+		oxygen_write_ac97(chip, 1, AC97_CD, 0x8808);
+		oxygen_write_ac97(chip, 1, AC97_VIDEO, 0x8808);
+		oxygen_write_ac97(chip, 1, AC97_AUX, 0x8808);
+		oxygen_write_ac97(chip, 1, AC97_PCM, 0x0808);
+		oxygen_write_ac97(chip, 1, AC97_REC_SEL, 0x0000);
+		oxygen_write_ac97(chip, 1, AC97_REC_GAIN, 0x0000);
+		oxygen_ac97_set_bits(chip, 1, 0x6a, 0x0040);
+	}
+}
+
+static void oxygen_shutdown(struct oxygen *chip)
+{
+	spin_lock_irq(&chip->reg_lock);
+	chip->interrupt_mask = 0;
+	chip->pcm_running = 0;
+	oxygen_write16(chip, OXYGEN_DMA_STATUS, 0);
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0);
+	spin_unlock_irq(&chip->reg_lock);
+}
+
+static void oxygen_card_free(struct snd_card *card)
+{
+	struct oxygen *chip = card->private_data;
+
+	oxygen_shutdown(chip);
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	flush_scheduled_work();
+	chip->model.cleanup(chip);
+	kfree(chip->model_data);
+	mutex_destroy(&chip->mutex);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+}
+
+int oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
+		     struct module *owner,
+		     const struct pci_device_id *ids,
+		     int (*get_model)(struct oxygen *chip,
+				      const struct pci_device_id *id
+				     )
+		    )
+{
+	struct snd_card *card;
+	struct oxygen *chip;
+	const struct pci_device_id *pci_id;
+	int err;
+
+	err = snd_card_create(index, id, owner, sizeof(*chip), &card);
+	if (err < 0)
+		return err;
+
+	chip = card->private_data;
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	spin_lock_init(&chip->reg_lock);
+	mutex_init(&chip->mutex);
+	INIT_WORK(&chip->spdif_input_bits_work,
+		  oxygen_spdif_input_bits_changed);
+	INIT_WORK(&chip->gpio_work, oxygen_gpio_changed);
+	init_waitqueue_head(&chip->ac97_waitqueue);
+
+	err = pci_enable_device(pci);
+	if (err < 0)
+		goto err_card;
+
+	err = pci_request_regions(pci, DRIVER);
+	if (err < 0) {
+		snd_printk(KERN_ERR "cannot reserve PCI resources\n");
+		goto err_pci_enable;
+	}
+
+	if (!(pci_resource_flags(pci, 0) & IORESOURCE_IO) ||
+	    pci_resource_len(pci, 0) < OXYGEN_IO_SIZE) {
+		snd_printk(KERN_ERR "invalid PCI I/O range\n");
+		err = -ENXIO;
+		goto err_pci_regions;
+	}
+	chip->addr = pci_resource_start(pci, 0);
+
+	pci_id = oxygen_search_pci_id(chip, ids);
+	if (!pci_id) {
+		err = -ENODEV;
+		goto err_pci_regions;
+	}
+	oxygen_restore_eeprom(chip, pci_id);
+	err = get_model(chip, pci_id);
+	if (err < 0)
+		goto err_pci_regions;
+
+	if (chip->model.model_data_size) {
+		chip->model_data = kzalloc(chip->model.model_data_size,
+					   GFP_KERNEL);
+		if (!chip->model_data) {
+			err = -ENOMEM;
+			goto err_pci_regions;
+		}
+	}
+
+	pci_set_master(pci);
+	snd_card_set_dev(card, &pci->dev);
+	card->private_free = oxygen_card_free;
+
+	configure_pcie_bridge(pci);
+	oxygen_init(chip);
+	chip->model.init(chip);
+
+	err = request_irq(pci->irq, oxygen_interrupt, IRQF_SHARED,
+			  DRIVER, chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR "cannot grab interrupt %d\n", pci->irq);
+		goto err_card;
+	}
+	chip->irq = pci->irq;
+
+	strcpy(card->driver, chip->model.chip);
+	strcpy(card->shortname, chip->model.shortname);
+	sprintf(card->longname, "%s (rev %u) at %#lx, irq %i",
+		chip->model.longname, chip->revision, chip->addr, chip->irq);
+	strcpy(card->mixername, chip->model.chip);
+	snd_component_add(card, chip->model.chip);
+
+	err = oxygen_pcm_init(chip);
+	if (err < 0)
+		goto err_card;
+
+	err = oxygen_mixer_init(chip);
+	if (err < 0)
+		goto err_card;
+
+	if (chip->model.device_config & (MIDI_OUTPUT | MIDI_INPUT)) {
+		unsigned int info_flags = MPU401_INFO_INTEGRATED;
+		if (chip->model.device_config & MIDI_OUTPUT)
+			info_flags |= MPU401_INFO_OUTPUT;
+		if (chip->model.device_config & MIDI_INPUT)
+			info_flags |= MPU401_INFO_INPUT;
+		err = snd_mpu401_uart_new(card, 0, MPU401_HW_CMIPCI,
+					  chip->addr + OXYGEN_MPU401,
+					  info_flags, 0, 0,
+					  &chip->midi);
+		if (err < 0)
+			goto err_card;
+	}
+
+	oxygen_proc_init(chip);
+
+	spin_lock_irq(&chip->reg_lock);
+	if (chip->model.device_config & CAPTURE_1_FROM_SPDIF)
+		chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_DETECT;
+	if (chip->has_ac97_0 | chip->has_ac97_1)
+		chip->interrupt_mask |= OXYGEN_INT_AC97;
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask);
+	spin_unlock_irq(&chip->reg_lock);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto err_card;
+
+	pci_set_drvdata(pci, card);
+	return 0;
+
+err_pci_regions:
+	pci_release_regions(pci);
+err_pci_enable:
+	pci_disable_device(pci);
+err_card:
+	snd_card_free(card);
+	return err;
+}
+EXPORT_SYMBOL(oxygen_pci_probe);
+
+void oxygen_pci_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+EXPORT_SYMBOL(oxygen_pci_remove);
+
+#ifdef CONFIG_PM
+int oxygen_pci_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct oxygen *chip = card->private_data;
+	unsigned int i, saved_interrupt_mask;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+	for (i = 0; i < PCM_COUNT; ++i)
+		if (chip->streams[i])
+			snd_pcm_suspend(chip->streams[i]);
+
+	if (chip->model.suspend)
+		chip->model.suspend(chip);
+
+	spin_lock_irq(&chip->reg_lock);
+	saved_interrupt_mask = chip->interrupt_mask;
+	chip->interrupt_mask = 0;
+	oxygen_write16(chip, OXYGEN_DMA_STATUS, 0);
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0);
+	spin_unlock_irq(&chip->reg_lock);
+
+	synchronize_irq(chip->irq);
+	flush_scheduled_work();
+	chip->interrupt_mask = saved_interrupt_mask;
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+EXPORT_SYMBOL(oxygen_pci_suspend);
+
+static const u32 registers_to_restore[OXYGEN_IO_SIZE / 32] = {
+	0xffffffff, 0x00ff077f, 0x00011d08, 0x007f00ff,
+	0x00300000, 0x00000fe4, 0x0ff7001f, 0x00000000
+};
+static const u32 ac97_registers_to_restore[2][0x40 / 32] = {
+	{ 0x18284fa2, 0x03060000 },
+	{ 0x00007fa6, 0x00200000 }
+};
+
+static inline int is_bit_set(const u32 *bitmap, unsigned int bit)
+{
+	return bitmap[bit / 32] & (1 << (bit & 31));
+}
+
+static void oxygen_restore_ac97(struct oxygen *chip, unsigned int codec)
+{
+	unsigned int i;
+
+	oxygen_write_ac97(chip, codec, AC97_RESET, 0);
+	msleep(1);
+	for (i = 1; i < 0x40; ++i)
+		if (is_bit_set(ac97_registers_to_restore[codec], i))
+			oxygen_write_ac97(chip, codec, i * 2,
+					  chip->saved_ac97_registers[codec][i]);
+}
+
+int oxygen_pci_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct oxygen *chip = card->private_data;
+	unsigned int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		snd_printk(KERN_ERR "cannot reenable device");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	oxygen_write16(chip, OXYGEN_DMA_STATUS, 0);
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0);
+	for (i = 0; i < OXYGEN_IO_SIZE; ++i)
+		if (is_bit_set(registers_to_restore, i))
+			oxygen_write8(chip, i, chip->saved_registers._8[i]);
+	if (chip->has_ac97_0)
+		oxygen_restore_ac97(chip, 0);
+	if (chip->has_ac97_1)
+		oxygen_restore_ac97(chip, 1);
+
+	if (chip->model.resume)
+		chip->model.resume(chip);
+
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+EXPORT_SYMBOL(oxygen_pci_resume);
+#endif /* CONFIG_PM */
+
+void oxygen_pci_shutdown(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct oxygen *chip = card->private_data;
+
+	oxygen_shutdown(chip);
+	chip->model.cleanup(chip);
+}
+EXPORT_SYMBOL(oxygen_pci_shutdown);
diff --git a/linux/sound/pci/oxygen/oxygen_mixer.c b/linux/sound/pci/oxygen/oxygen_mixer.c
new file mode 100644
index 0000000..2849b36
--- /dev/null
+++ b/linux/sound/pci/oxygen/oxygen_mixer.c
@@ -0,0 +1,1033 @@
+/*
+ * C-Media CMI8788 driver - mixer code
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/mutex.h>
+#include <sound/ac97_codec.h>
+#include <sound/asoundef.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+#include "cm9780.h"
+
+static int dac_volume_info(struct snd_kcontrol *ctl,
+			   struct snd_ctl_elem_info *info)
+{
+	struct oxygen *chip = ctl->private_data;
+
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = chip->model.dac_channels;
+	info->value.integer.min = chip->model.dac_volume_min;
+	info->value.integer.max = chip->model.dac_volume_max;
+	return 0;
+}
+
+static int dac_volume_get(struct snd_kcontrol *ctl,
+			  struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int i;
+
+	mutex_lock(&chip->mutex);
+	for (i = 0; i < chip->model.dac_channels; ++i)
+		value->value.integer.value[i] = chip->dac_volume[i];
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int dac_volume_put(struct snd_kcontrol *ctl,
+			  struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int i;
+	int changed;
+
+	changed = 0;
+	mutex_lock(&chip->mutex);
+	for (i = 0; i < chip->model.dac_channels; ++i)
+		if (value->value.integer.value[i] != chip->dac_volume[i]) {
+			chip->dac_volume[i] = value->value.integer.value[i];
+			changed = 1;
+		}
+	if (changed)
+		chip->model.update_dac_volume(chip);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int dac_mute_get(struct snd_kcontrol *ctl,
+			struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+
+	mutex_lock(&chip->mutex);
+	value->value.integer.value[0] = !chip->dac_mute;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int dac_mute_put(struct snd_kcontrol *ctl,
+			  struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	changed = !value->value.integer.value[0] != chip->dac_mute;
+	if (changed) {
+		chip->dac_mute = !value->value.integer.value[0];
+		chip->model.update_dac_mute(chip);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int upmix_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+	static const char *const names[5] = {
+		"Front",
+		"Front+Surround",
+		"Front+Surround+Back",
+		"Front+Surround+Center/LFE",
+		"Front+Surround+Center/LFE+Back",
+	};
+	struct oxygen *chip = ctl->private_data;
+	unsigned int count = chip->model.update_center_lfe_mix ? 5 : 3;
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = count;
+	if (info->value.enumerated.item >= count)
+		info->value.enumerated.item = count - 1;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int upmix_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+
+	mutex_lock(&chip->mutex);
+	value->value.enumerated.item[0] = chip->dac_routing;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+void oxygen_update_dac_routing(struct oxygen *chip)
+{
+	/* DAC 0: front, DAC 1: surround, DAC 2: center/LFE, DAC 3: back */
+	static const unsigned int reg_values[5] = {
+		/* stereo -> front */
+		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		(1 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		(2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		(3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
+		/* stereo -> front+surround */
+		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		(2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		(3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
+		/* stereo -> front+surround+back */
+		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		(2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
+		/* stereo -> front+surround+center/LFE */
+		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		(3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
+		/* stereo -> front+surround+center/LFE+back */
+		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+		(0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
+	};
+	u8 channels;
+	unsigned int reg_value;
+
+	channels = oxygen_read8(chip, OXYGEN_PLAY_CHANNELS) &
+		OXYGEN_PLAY_CHANNELS_MASK;
+	if (channels == OXYGEN_PLAY_CHANNELS_2)
+		reg_value = reg_values[chip->dac_routing];
+	else if (channels == OXYGEN_PLAY_CHANNELS_8)
+		/* in 7.1 mode, "rear" channels go to the "back" jack */
+		reg_value = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+			    (3 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+			    (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+			    (1 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT);
+	else
+		reg_value = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+			    (1 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+			    (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+			    (3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT);
+	oxygen_write16_masked(chip, OXYGEN_PLAY_ROUTING, reg_value,
+			      OXYGEN_PLAY_DAC0_SOURCE_MASK |
+			      OXYGEN_PLAY_DAC1_SOURCE_MASK |
+			      OXYGEN_PLAY_DAC2_SOURCE_MASK |
+			      OXYGEN_PLAY_DAC3_SOURCE_MASK);
+	if (chip->model.update_center_lfe_mix)
+		chip->model.update_center_lfe_mix(chip, chip->dac_routing > 2);
+}
+
+static int upmix_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int count = chip->model.update_center_lfe_mix ? 5 : 3;
+	int changed;
+
+	if (value->value.enumerated.item[0] >= count)
+		return -EINVAL;
+	mutex_lock(&chip->mutex);
+	changed = value->value.enumerated.item[0] != chip->dac_routing;
+	if (changed) {
+		chip->dac_routing = value->value.enumerated.item[0];
+		oxygen_update_dac_routing(chip);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int spdif_switch_get(struct snd_kcontrol *ctl,
+			    struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+
+	mutex_lock(&chip->mutex);
+	value->value.integer.value[0] = chip->spdif_playback_enable;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static unsigned int oxygen_spdif_rate(unsigned int oxygen_rate)
+{
+	switch (oxygen_rate) {
+	case OXYGEN_RATE_32000:
+		return IEC958_AES3_CON_FS_32000 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+	case OXYGEN_RATE_44100:
+		return IEC958_AES3_CON_FS_44100 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+	default: /* OXYGEN_RATE_48000 */
+		return IEC958_AES3_CON_FS_48000 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+	case OXYGEN_RATE_64000:
+		return 0xb << OXYGEN_SPDIF_CS_RATE_SHIFT;
+	case OXYGEN_RATE_88200:
+		return IEC958_AES3_CON_FS_88200 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+	case OXYGEN_RATE_96000:
+		return IEC958_AES3_CON_FS_96000 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+	case OXYGEN_RATE_176400:
+		return IEC958_AES3_CON_FS_176400 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+	case OXYGEN_RATE_192000:
+		return IEC958_AES3_CON_FS_192000 << OXYGEN_SPDIF_CS_RATE_SHIFT;
+	}
+}
+
+void oxygen_update_spdif_source(struct oxygen *chip)
+{
+	u32 old_control, new_control;
+	u16 old_routing, new_routing;
+	unsigned int oxygen_rate;
+
+	old_control = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
+	old_routing = oxygen_read16(chip, OXYGEN_PLAY_ROUTING);
+	if (chip->pcm_active & (1 << PCM_SPDIF)) {
+		new_control = old_control | OXYGEN_SPDIF_OUT_ENABLE;
+		new_routing = (old_routing & ~OXYGEN_PLAY_SPDIF_MASK)
+			| OXYGEN_PLAY_SPDIF_SPDIF;
+		oxygen_rate = (old_control >> OXYGEN_SPDIF_OUT_RATE_SHIFT)
+			& OXYGEN_I2S_RATE_MASK;
+		/* S/PDIF rate was already set by the caller */
+	} else if ((chip->pcm_active & (1 << PCM_MULTICH)) &&
+		   chip->spdif_playback_enable) {
+		new_routing = (old_routing & ~OXYGEN_PLAY_SPDIF_MASK)
+			| OXYGEN_PLAY_SPDIF_MULTICH_01;
+		oxygen_rate = oxygen_read16(chip, OXYGEN_I2S_MULTICH_FORMAT)
+			& OXYGEN_I2S_RATE_MASK;
+		new_control = (old_control & ~OXYGEN_SPDIF_OUT_RATE_MASK) |
+			(oxygen_rate << OXYGEN_SPDIF_OUT_RATE_SHIFT) |
+			OXYGEN_SPDIF_OUT_ENABLE;
+	} else {
+		new_control = old_control & ~OXYGEN_SPDIF_OUT_ENABLE;
+		new_routing = old_routing;
+		oxygen_rate = OXYGEN_RATE_44100;
+	}
+	if (old_routing != new_routing) {
+		oxygen_write32(chip, OXYGEN_SPDIF_CONTROL,
+			       new_control & ~OXYGEN_SPDIF_OUT_ENABLE);
+		oxygen_write16(chip, OXYGEN_PLAY_ROUTING, new_routing);
+	}
+	if (new_control & OXYGEN_SPDIF_OUT_ENABLE)
+		oxygen_write32(chip, OXYGEN_SPDIF_OUTPUT_BITS,
+			       oxygen_spdif_rate(oxygen_rate) |
+			       ((chip->pcm_active & (1 << PCM_SPDIF)) ?
+				chip->spdif_pcm_bits : chip->spdif_bits));
+	oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, new_control);
+}
+
+static int spdif_switch_put(struct snd_kcontrol *ctl,
+			    struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	changed = value->value.integer.value[0] != chip->spdif_playback_enable;
+	if (changed) {
+		chip->spdif_playback_enable = !!value->value.integer.value[0];
+		spin_lock_irq(&chip->reg_lock);
+		oxygen_update_spdif_source(chip);
+		spin_unlock_irq(&chip->reg_lock);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int spdif_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	info->count = 1;
+	return 0;
+}
+
+static void oxygen_to_iec958(u32 bits, struct snd_ctl_elem_value *value)
+{
+	value->value.iec958.status[0] =
+		bits & (OXYGEN_SPDIF_NONAUDIO | OXYGEN_SPDIF_C |
+			OXYGEN_SPDIF_PREEMPHASIS);
+	value->value.iec958.status[1] = /* category and original */
+		bits >> OXYGEN_SPDIF_CATEGORY_SHIFT;
+}
+
+static u32 iec958_to_oxygen(struct snd_ctl_elem_value *value)
+{
+	u32 bits;
+
+	bits = value->value.iec958.status[0] &
+		(OXYGEN_SPDIF_NONAUDIO | OXYGEN_SPDIF_C |
+		 OXYGEN_SPDIF_PREEMPHASIS);
+	bits |= value->value.iec958.status[1] << OXYGEN_SPDIF_CATEGORY_SHIFT;
+	if (bits & OXYGEN_SPDIF_NONAUDIO)
+		bits |= OXYGEN_SPDIF_V;
+	return bits;
+}
+
+static inline void write_spdif_bits(struct oxygen *chip, u32 bits)
+{
+	oxygen_write32_masked(chip, OXYGEN_SPDIF_OUTPUT_BITS, bits,
+			      OXYGEN_SPDIF_NONAUDIO |
+			      OXYGEN_SPDIF_C |
+			      OXYGEN_SPDIF_PREEMPHASIS |
+			      OXYGEN_SPDIF_CATEGORY_MASK |
+			      OXYGEN_SPDIF_ORIGINAL |
+			      OXYGEN_SPDIF_V);
+}
+
+static int spdif_default_get(struct snd_kcontrol *ctl,
+			     struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+
+	mutex_lock(&chip->mutex);
+	oxygen_to_iec958(chip->spdif_bits, value);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int spdif_default_put(struct snd_kcontrol *ctl,
+			     struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u32 new_bits;
+	int changed;
+
+	new_bits = iec958_to_oxygen(value);
+	mutex_lock(&chip->mutex);
+	changed = new_bits != chip->spdif_bits;
+	if (changed) {
+		chip->spdif_bits = new_bits;
+		if (!(chip->pcm_active & (1 << PCM_SPDIF)))
+			write_spdif_bits(chip, new_bits);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int spdif_mask_get(struct snd_kcontrol *ctl,
+			  struct snd_ctl_elem_value *value)
+{
+	value->value.iec958.status[0] = IEC958_AES0_NONAUDIO |
+		IEC958_AES0_CON_NOT_COPYRIGHT | IEC958_AES0_CON_EMPHASIS;
+	value->value.iec958.status[1] =
+		IEC958_AES1_CON_CATEGORY | IEC958_AES1_CON_ORIGINAL;
+	return 0;
+}
+
+static int spdif_pcm_get(struct snd_kcontrol *ctl,
+			 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+
+	mutex_lock(&chip->mutex);
+	oxygen_to_iec958(chip->spdif_pcm_bits, value);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int spdif_pcm_put(struct snd_kcontrol *ctl,
+			 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u32 new_bits;
+	int changed;
+
+	new_bits = iec958_to_oxygen(value);
+	mutex_lock(&chip->mutex);
+	changed = new_bits != chip->spdif_pcm_bits;
+	if (changed) {
+		chip->spdif_pcm_bits = new_bits;
+		if (chip->pcm_active & (1 << PCM_SPDIF))
+			write_spdif_bits(chip, new_bits);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int spdif_input_mask_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	value->value.iec958.status[0] = 0xff;
+	value->value.iec958.status[1] = 0xff;
+	value->value.iec958.status[2] = 0xff;
+	value->value.iec958.status[3] = 0xff;
+	return 0;
+}
+
+static int spdif_input_default_get(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u32 bits;
+
+	bits = oxygen_read32(chip, OXYGEN_SPDIF_INPUT_BITS);
+	value->value.iec958.status[0] = bits;
+	value->value.iec958.status[1] = bits >> 8;
+	value->value.iec958.status[2] = bits >> 16;
+	value->value.iec958.status[3] = bits >> 24;
+	return 0;
+}
+
+static int spdif_loopback_get(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+
+	value->value.integer.value[0] =
+		!!(oxygen_read32(chip, OXYGEN_SPDIF_CONTROL)
+		   & OXYGEN_SPDIF_LOOPBACK);
+	return 0;
+}
+
+static int spdif_loopback_put(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u32 oldreg, newreg;
+	int changed;
+
+	spin_lock_irq(&chip->reg_lock);
+	oldreg = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
+	if (value->value.integer.value[0])
+		newreg = oldreg | OXYGEN_SPDIF_LOOPBACK;
+	else
+		newreg = oldreg & ~OXYGEN_SPDIF_LOOPBACK;
+	changed = newreg != oldreg;
+	if (changed)
+		oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, newreg);
+	spin_unlock_irq(&chip->reg_lock);
+	return changed;
+}
+
+static int monitor_volume_info(struct snd_kcontrol *ctl,
+			       struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 1;
+	info->value.integer.min = 0;
+	info->value.integer.max = 1;
+	return 0;
+}
+
+static int monitor_get(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u8 bit = ctl->private_value;
+	int invert = ctl->private_value & (1 << 8);
+
+	value->value.integer.value[0] =
+		!!invert ^ !!(oxygen_read8(chip, OXYGEN_ADC_MONITOR) & bit);
+	return 0;
+}
+
+static int monitor_put(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u8 bit = ctl->private_value;
+	int invert = ctl->private_value & (1 << 8);
+	u8 oldreg, newreg;
+	int changed;
+
+	spin_lock_irq(&chip->reg_lock);
+	oldreg = oxygen_read8(chip, OXYGEN_ADC_MONITOR);
+	if ((!!value->value.integer.value[0] ^ !!invert) != 0)
+		newreg = oldreg | bit;
+	else
+		newreg = oldreg & ~bit;
+	changed = newreg != oldreg;
+	if (changed)
+		oxygen_write8(chip, OXYGEN_ADC_MONITOR, newreg);
+	spin_unlock_irq(&chip->reg_lock);
+	return changed;
+}
+
+static int ac97_switch_get(struct snd_kcontrol *ctl,
+			   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int codec = (ctl->private_value >> 24) & 1;
+	unsigned int index = ctl->private_value & 0xff;
+	unsigned int bitnr = (ctl->private_value >> 8) & 0xff;
+	int invert = ctl->private_value & (1 << 16);
+	u16 reg;
+
+	mutex_lock(&chip->mutex);
+	reg = oxygen_read_ac97(chip, codec, index);
+	mutex_unlock(&chip->mutex);
+	if (!(reg & (1 << bitnr)) ^ !invert)
+		value->value.integer.value[0] = 1;
+	else
+		value->value.integer.value[0] = 0;
+	return 0;
+}
+
+static void mute_ac97_ctl(struct oxygen *chip, unsigned int control)
+{
+	unsigned int priv_idx;
+	u16 value;
+
+	if (!chip->controls[control])
+		return;
+	priv_idx = chip->controls[control]->private_value & 0xff;
+	value = oxygen_read_ac97(chip, 0, priv_idx);
+	if (!(value & 0x8000)) {
+		oxygen_write_ac97(chip, 0, priv_idx, value | 0x8000);
+		if (chip->model.ac97_switch)
+			chip->model.ac97_switch(chip, priv_idx, 0x8000);
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->controls[control]->id);
+	}
+}
+
+static int ac97_switch_put(struct snd_kcontrol *ctl,
+			   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int codec = (ctl->private_value >> 24) & 1;
+	unsigned int index = ctl->private_value & 0xff;
+	unsigned int bitnr = (ctl->private_value >> 8) & 0xff;
+	int invert = ctl->private_value & (1 << 16);
+	u16 oldreg, newreg;
+	int change;
+
+	mutex_lock(&chip->mutex);
+	oldreg = oxygen_read_ac97(chip, codec, index);
+	newreg = oldreg;
+	if (!value->value.integer.value[0] ^ !invert)
+		newreg |= 1 << bitnr;
+	else
+		newreg &= ~(1 << bitnr);
+	change = newreg != oldreg;
+	if (change) {
+		oxygen_write_ac97(chip, codec, index, newreg);
+		if (codec == 0 && chip->model.ac97_switch)
+			chip->model.ac97_switch(chip, index, newreg & 0x8000);
+		if (index == AC97_LINE) {
+			oxygen_write_ac97_masked(chip, 0, CM9780_GPIO_STATUS,
+						 newreg & 0x8000 ?
+						 CM9780_GPO0 : 0, CM9780_GPO0);
+			if (!(newreg & 0x8000)) {
+				mute_ac97_ctl(chip, CONTROL_MIC_CAPTURE_SWITCH);
+				mute_ac97_ctl(chip, CONTROL_CD_CAPTURE_SWITCH);
+				mute_ac97_ctl(chip, CONTROL_AUX_CAPTURE_SWITCH);
+			}
+		} else if ((index == AC97_MIC || index == AC97_CD ||
+			    index == AC97_VIDEO || index == AC97_AUX) &&
+			   bitnr == 15 && !(newreg & 0x8000)) {
+			mute_ac97_ctl(chip, CONTROL_LINE_CAPTURE_SWITCH);
+			oxygen_write_ac97_masked(chip, 0, CM9780_GPIO_STATUS,
+						 CM9780_GPO0, CM9780_GPO0);
+		}
+	}
+	mutex_unlock(&chip->mutex);
+	return change;
+}
+
+static int ac97_volume_info(struct snd_kcontrol *ctl,
+			    struct snd_ctl_elem_info *info)
+{
+	int stereo = (ctl->private_value >> 16) & 1;
+
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = stereo ? 2 : 1;
+	info->value.integer.min = 0;
+	info->value.integer.max = 0x1f;
+	return 0;
+}
+
+static int ac97_volume_get(struct snd_kcontrol *ctl,
+			   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int codec = (ctl->private_value >> 24) & 1;
+	int stereo = (ctl->private_value >> 16) & 1;
+	unsigned int index = ctl->private_value & 0xff;
+	u16 reg;
+
+	mutex_lock(&chip->mutex);
+	reg = oxygen_read_ac97(chip, codec, index);
+	mutex_unlock(&chip->mutex);
+	value->value.integer.value[0] = 31 - (reg & 0x1f);
+	if (stereo)
+		value->value.integer.value[1] = 31 - ((reg >> 8) & 0x1f);
+	return 0;
+}
+
+static int ac97_volume_put(struct snd_kcontrol *ctl,
+			   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int codec = (ctl->private_value >> 24) & 1;
+	int stereo = (ctl->private_value >> 16) & 1;
+	unsigned int index = ctl->private_value & 0xff;
+	u16 oldreg, newreg;
+	int change;
+
+	mutex_lock(&chip->mutex);
+	oldreg = oxygen_read_ac97(chip, codec, index);
+	newreg = oldreg;
+	newreg = (newreg & ~0x1f) |
+		(31 - (value->value.integer.value[0] & 0x1f));
+	if (stereo)
+		newreg = (newreg & ~0x1f00) |
+			((31 - (value->value.integer.value[1] & 0x1f)) << 8);
+	else
+		newreg = (newreg & ~0x1f00) | ((newreg & 0x1f) << 8);
+	change = newreg != oldreg;
+	if (change)
+		oxygen_write_ac97(chip, codec, index, newreg);
+	mutex_unlock(&chip->mutex);
+	return change;
+}
+
+static int ac97_fp_rec_volume_info(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 2;
+	info->value.integer.min = 0;
+	info->value.integer.max = 7;
+	return 0;
+}
+
+static int ac97_fp_rec_volume_get(struct snd_kcontrol *ctl,
+				  struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 reg;
+
+	mutex_lock(&chip->mutex);
+	reg = oxygen_read_ac97(chip, 1, AC97_REC_GAIN);
+	mutex_unlock(&chip->mutex);
+	value->value.integer.value[0] = reg & 7;
+	value->value.integer.value[1] = (reg >> 8) & 7;
+	return 0;
+}
+
+static int ac97_fp_rec_volume_put(struct snd_kcontrol *ctl,
+				  struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 oldreg, newreg;
+	int change;
+
+	mutex_lock(&chip->mutex);
+	oldreg = oxygen_read_ac97(chip, 1, AC97_REC_GAIN);
+	newreg = oldreg & ~0x0707;
+	newreg = newreg | (value->value.integer.value[0] & 7);
+	newreg = newreg | ((value->value.integer.value[0] & 7) << 8);
+	change = newreg != oldreg;
+	if (change)
+		oxygen_write_ac97(chip, 1, AC97_REC_GAIN, newreg);
+	mutex_unlock(&chip->mutex);
+	return change;
+}
+
+#define AC97_SWITCH(xname, codec, index, bitnr, invert) { \
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name = xname, \
+		.info = snd_ctl_boolean_mono_info, \
+		.get = ac97_switch_get, \
+		.put = ac97_switch_put, \
+		.private_value = ((codec) << 24) | ((invert) << 16) | \
+				 ((bitnr) << 8) | (index), \
+	}
+#define AC97_VOLUME(xname, codec, index, stereo) { \
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name = xname, \
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+			  SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+		.info = ac97_volume_info, \
+		.get = ac97_volume_get, \
+		.put = ac97_volume_put, \
+		.tlv = { .p = ac97_db_scale, }, \
+		.private_value = ((codec) << 24) | ((stereo) << 16) | (index), \
+	}
+
+static DECLARE_TLV_DB_SCALE(monitor_db_scale, -600, 600, 0);
+static DECLARE_TLV_DB_SCALE(ac97_db_scale, -3450, 150, 0);
+static DECLARE_TLV_DB_SCALE(ac97_rec_db_scale, 0, 150, 0);
+
+static const struct snd_kcontrol_new controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Volume",
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = dac_volume_info,
+		.get = dac_volume_get,
+		.put = dac_volume_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = snd_ctl_boolean_mono_info,
+		.get = dac_mute_get,
+		.put = dac_mute_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Stereo Upmixing",
+		.info = upmix_info,
+		.get = upmix_get,
+		.put = upmix_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+		.info = snd_ctl_boolean_mono_info,
+		.get = spdif_switch_get,
+		.put = spdif_switch_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.device = 1,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+		.info = spdif_info,
+		.get = spdif_default_get,
+		.put = spdif_default_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.device = 1,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.info = spdif_info,
+		.get = spdif_mask_get,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.device = 1,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			  SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+		.info = spdif_info,
+		.get = spdif_pcm_get,
+		.put = spdif_pcm_put,
+	},
+};
+
+static const struct snd_kcontrol_new spdif_input_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.device = 1,
+		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK),
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.info = spdif_info,
+		.get = spdif_input_mask_get,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.device = 1,
+		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.info = spdif_info,
+		.get = spdif_input_default_get,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("Loopback ", NONE, SWITCH),
+		.info = snd_ctl_boolean_mono_info,
+		.get = spdif_loopback_get,
+		.put = spdif_loopback_put,
+	},
+};
+
+static const struct {
+	unsigned int pcm_dev;
+	struct snd_kcontrol_new controls[2];
+} monitor_controls[] = {
+	{
+		.pcm_dev = CAPTURE_0_FROM_I2S_1,
+		.controls = {
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Switch",
+				.info = snd_ctl_boolean_mono_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_A,
+			},
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Volume",
+				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+				.info = monitor_volume_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_A_HALF_VOL
+						| (1 << 8),
+				.tlv = { .p = monitor_db_scale, },
+			},
+		},
+	},
+	{
+		.pcm_dev = CAPTURE_0_FROM_I2S_2,
+		.controls = {
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Switch",
+				.info = snd_ctl_boolean_mono_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_B,
+			},
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Volume",
+				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+				.info = monitor_volume_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_B_HALF_VOL
+						| (1 << 8),
+				.tlv = { .p = monitor_db_scale, },
+			},
+		},
+	},
+	{
+		.pcm_dev = CAPTURE_2_FROM_I2S_2,
+		.controls = {
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Switch",
+				.index = 1,
+				.info = snd_ctl_boolean_mono_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_B,
+			},
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Analog Input Monitor Playback Volume",
+				.index = 1,
+				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+				.info = monitor_volume_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_B_HALF_VOL
+						| (1 << 8),
+				.tlv = { .p = monitor_db_scale, },
+			},
+		},
+	},
+	{
+		.pcm_dev = CAPTURE_1_FROM_SPDIF,
+		.controls = {
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Digital Input Monitor Playback Switch",
+				.info = snd_ctl_boolean_mono_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_C,
+			},
+			{
+				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+				.name = "Digital Input Monitor Playback Volume",
+				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+				.info = monitor_volume_info,
+				.get = monitor_get,
+				.put = monitor_put,
+				.private_value = OXYGEN_ADC_MONITOR_C_HALF_VOL
+						| (1 << 8),
+				.tlv = { .p = monitor_db_scale, },
+			},
+		},
+	},
+};
+
+static const struct snd_kcontrol_new ac97_controls[] = {
+	AC97_VOLUME("Mic Capture Volume", 0, AC97_MIC, 0),
+	AC97_SWITCH("Mic Capture Switch", 0, AC97_MIC, 15, 1),
+	AC97_SWITCH("Mic Boost (+20dB)", 0, AC97_MIC, 6, 0),
+	AC97_SWITCH("Line Capture Switch", 0, AC97_LINE, 15, 1),
+	AC97_VOLUME("CD Capture Volume", 0, AC97_CD, 1),
+	AC97_SWITCH("CD Capture Switch", 0, AC97_CD, 15, 1),
+	AC97_VOLUME("Aux Capture Volume", 0, AC97_AUX, 1),
+	AC97_SWITCH("Aux Capture Switch", 0, AC97_AUX, 15, 1),
+};
+
+static const struct snd_kcontrol_new ac97_fp_controls[] = {
+	AC97_VOLUME("Front Panel Playback Volume", 1, AC97_HEADPHONE, 1),
+	AC97_SWITCH("Front Panel Playback Switch", 1, AC97_HEADPHONE, 15, 1),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Front Panel Capture Volume",
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+		.info = ac97_fp_rec_volume_info,
+		.get = ac97_fp_rec_volume_get,
+		.put = ac97_fp_rec_volume_put,
+		.tlv = { .p = ac97_rec_db_scale, },
+	},
+	AC97_SWITCH("Front Panel Capture Switch", 1, AC97_REC_GAIN, 15, 1),
+};
+
+static void oxygen_any_ctl_free(struct snd_kcontrol *ctl)
+{
+	struct oxygen *chip = ctl->private_data;
+	unsigned int i;
+
+	/* I'm too lazy to write a function for each control :-) */
+	for (i = 0; i < ARRAY_SIZE(chip->controls); ++i)
+		chip->controls[i] = NULL;
+}
+
+static int add_controls(struct oxygen *chip,
+			const struct snd_kcontrol_new controls[],
+			unsigned int count)
+{
+	static const char *const known_ctl_names[CONTROL_COUNT] = {
+		[CONTROL_SPDIF_PCM] =
+			SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
+		[CONTROL_SPDIF_INPUT_BITS] =
+			SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+		[CONTROL_MIC_CAPTURE_SWITCH] = "Mic Capture Switch",
+		[CONTROL_LINE_CAPTURE_SWITCH] = "Line Capture Switch",
+		[CONTROL_CD_CAPTURE_SWITCH] = "CD Capture Switch",
+		[CONTROL_AUX_CAPTURE_SWITCH] = "Aux Capture Switch",
+	};
+	unsigned int i, j;
+	struct snd_kcontrol_new template;
+	struct snd_kcontrol *ctl;
+	int err;
+
+	for (i = 0; i < count; ++i) {
+		template = controls[i];
+		if (chip->model.control_filter) {
+			err = chip->model.control_filter(&template);
+			if (err < 0)
+				return err;
+			if (err == 1)
+				continue;
+		}
+		if (!strcmp(template.name, "Stereo Upmixing") &&
+		    chip->model.dac_channels == 2)
+			continue;
+		if (!strncmp(template.name, "CD Capture ", 11) &&
+		    !(chip->model.device_config & AC97_CD_INPUT))
+			continue;
+		if (!strcmp(template.name, "Master Playback Volume") &&
+		    chip->model.dac_tlv) {
+			template.tlv.p = chip->model.dac_tlv;
+			template.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+		}
+		ctl = snd_ctl_new1(&template, chip);
+		if (!ctl)
+			return -ENOMEM;
+		err = snd_ctl_add(chip->card, ctl);
+		if (err < 0)
+			return err;
+		for (j = 0; j < CONTROL_COUNT; ++j)
+			if (!strcmp(ctl->id.name, known_ctl_names[j])) {
+				chip->controls[j] = ctl;
+				ctl->private_free = oxygen_any_ctl_free;
+			}
+	}
+	return 0;
+}
+
+int oxygen_mixer_init(struct oxygen *chip)
+{
+	unsigned int i;
+	int err;
+
+	err = add_controls(chip, controls, ARRAY_SIZE(controls));
+	if (err < 0)
+		return err;
+	if (chip->model.device_config & CAPTURE_1_FROM_SPDIF) {
+		err = add_controls(chip, spdif_input_controls,
+				   ARRAY_SIZE(spdif_input_controls));
+		if (err < 0)
+			return err;
+	}
+	for (i = 0; i < ARRAY_SIZE(monitor_controls); ++i) {
+		if (!(chip->model.device_config & monitor_controls[i].pcm_dev))
+			continue;
+		err = add_controls(chip, monitor_controls[i].controls,
+				   ARRAY_SIZE(monitor_controls[i].controls));
+		if (err < 0)
+			return err;
+	}
+	if (chip->has_ac97_0) {
+		err = add_controls(chip, ac97_controls,
+				   ARRAY_SIZE(ac97_controls));
+		if (err < 0)
+			return err;
+	}
+	if (chip->has_ac97_1) {
+		err = add_controls(chip, ac97_fp_controls,
+				   ARRAY_SIZE(ac97_fp_controls));
+		if (err < 0)
+			return err;
+	}
+	return chip->model.mixer_init ? chip->model.mixer_init(chip) : 0;
+}
diff --git a/linux/sound/pci/oxygen/oxygen_pcm.c b/linux/sound/pci/oxygen/oxygen_pcm.c
new file mode 100644
index 0000000..8146674
--- /dev/null
+++ b/linux/sound/pci/oxygen/oxygen_pcm.c
@@ -0,0 +1,759 @@
+/*
+ * C-Media CMI8788 driver - PCM code
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/pci.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "oxygen.h"
+
+/* most DMA channels have a 16-bit counter for 32-bit words */
+#define BUFFER_BYTES_MAX		((1 << 16) * 4)
+/* the multichannel DMA channel has a 24-bit counter */
+#define BUFFER_BYTES_MAX_MULTICH	((1 << 24) * 4)
+
+#define PERIOD_BYTES_MIN		64
+
+#define DEFAULT_BUFFER_BYTES		(BUFFER_BYTES_MAX / 2)
+#define DEFAULT_BUFFER_BYTES_MULTICH	(1024 * 1024)
+
+static const struct snd_pcm_hardware oxygen_stereo_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE |
+		   SNDRV_PCM_FMTBIT_S32_LE,
+	.rates = SNDRV_PCM_RATE_32000 |
+		 SNDRV_PCM_RATE_44100 |
+		 SNDRV_PCM_RATE_48000 |
+		 SNDRV_PCM_RATE_64000 |
+		 SNDRV_PCM_RATE_88200 |
+		 SNDRV_PCM_RATE_96000 |
+		 SNDRV_PCM_RATE_176400 |
+		 SNDRV_PCM_RATE_192000,
+	.rate_min = 32000,
+	.rate_max = 192000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX,
+	.periods_min = 1,
+	.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+};
+static const struct snd_pcm_hardware oxygen_multichannel_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE |
+		   SNDRV_PCM_FMTBIT_S32_LE,
+	.rates = SNDRV_PCM_RATE_32000 |
+		 SNDRV_PCM_RATE_44100 |
+		 SNDRV_PCM_RATE_48000 |
+		 SNDRV_PCM_RATE_64000 |
+		 SNDRV_PCM_RATE_88200 |
+		 SNDRV_PCM_RATE_96000 |
+		 SNDRV_PCM_RATE_176400 |
+		 SNDRV_PCM_RATE_192000,
+	.rate_min = 32000,
+	.rate_max = 192000,
+	.channels_min = 2,
+	.channels_max = 8,
+	.buffer_bytes_max = BUFFER_BYTES_MAX_MULTICH,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX_MULTICH,
+	.periods_min = 1,
+	.periods_max = BUFFER_BYTES_MAX_MULTICH / PERIOD_BYTES_MIN,
+};
+static const struct snd_pcm_hardware oxygen_ac97_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_SYNC_START,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rates = SNDRV_PCM_RATE_48000,
+	.rate_min = 48000,
+	.rate_max = 48000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX,
+	.periods_min = 1,
+	.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+};
+
+static const struct snd_pcm_hardware *const oxygen_hardware[PCM_COUNT] = {
+	[PCM_A] = &oxygen_stereo_hardware,
+	[PCM_B] = &oxygen_stereo_hardware,
+	[PCM_C] = &oxygen_stereo_hardware,
+	[PCM_SPDIF] = &oxygen_stereo_hardware,
+	[PCM_MULTICH] = &oxygen_multichannel_hardware,
+	[PCM_AC97] = &oxygen_ac97_hardware,
+};
+
+static inline unsigned int
+oxygen_substream_channel(struct snd_pcm_substream *substream)
+{
+	return (unsigned int)(uintptr_t)substream->runtime->private_data;
+}
+
+static int oxygen_open(struct snd_pcm_substream *substream,
+		       unsigned int channel)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	runtime->private_data = (void *)(uintptr_t)channel;
+	if (channel == PCM_B && chip->has_ac97_1 &&
+	    (chip->model.device_config & CAPTURE_2_FROM_AC97_1))
+		runtime->hw = oxygen_ac97_hardware;
+	else
+		runtime->hw = *oxygen_hardware[channel];
+	switch (channel) {
+	case PCM_C:
+		runtime->hw.rates &= ~(SNDRV_PCM_RATE_32000 |
+				       SNDRV_PCM_RATE_64000);
+		runtime->hw.rate_min = 44100;
+		break;
+	case PCM_MULTICH:
+		runtime->hw.channels_max = chip->model.dac_channels;
+		break;
+	}
+	if (chip->model.pcm_hardware_filter)
+		chip->model.pcm_hardware_filter(channel, &runtime->hw);
+	err = snd_pcm_hw_constraint_step(runtime, 0,
+					 SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_constraint_step(runtime, 0,
+					 SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
+	if (err < 0)
+		return err;
+	if (runtime->hw.formats & SNDRV_PCM_FMTBIT_S32_LE) {
+		err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+		if (err < 0)
+			return err;
+	}
+	if (runtime->hw.channels_max > 2) {
+		err = snd_pcm_hw_constraint_step(runtime, 0,
+						 SNDRV_PCM_HW_PARAM_CHANNELS,
+						 2);
+		if (err < 0)
+			return err;
+	}
+	if (channel == PCM_MULTICH) {
+		err = snd_pcm_hw_constraint_minmax
+			(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 0, 8192000);
+		if (err < 0)
+			return err;
+	}
+	snd_pcm_set_sync(substream);
+	chip->streams[channel] = substream;
+
+	mutex_lock(&chip->mutex);
+	chip->pcm_active |= 1 << channel;
+	if (channel == PCM_SPDIF) {
+		chip->spdif_pcm_bits = chip->spdif_bits;
+		chip->controls[CONTROL_SPDIF_PCM]->vd[0].access &=
+			~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
+			       SNDRV_CTL_EVENT_MASK_INFO,
+			       &chip->controls[CONTROL_SPDIF_PCM]->id);
+	}
+	mutex_unlock(&chip->mutex);
+
+	return 0;
+}
+
+static int oxygen_rec_a_open(struct snd_pcm_substream *substream)
+{
+	return oxygen_open(substream, PCM_A);
+}
+
+static int oxygen_rec_b_open(struct snd_pcm_substream *substream)
+{
+	return oxygen_open(substream, PCM_B);
+}
+
+static int oxygen_rec_c_open(struct snd_pcm_substream *substream)
+{
+	return oxygen_open(substream, PCM_C);
+}
+
+static int oxygen_spdif_open(struct snd_pcm_substream *substream)
+{
+	return oxygen_open(substream, PCM_SPDIF);
+}
+
+static int oxygen_multich_open(struct snd_pcm_substream *substream)
+{
+	return oxygen_open(substream, PCM_MULTICH);
+}
+
+static int oxygen_ac97_open(struct snd_pcm_substream *substream)
+{
+	return oxygen_open(substream, PCM_AC97);
+}
+
+static int oxygen_close(struct snd_pcm_substream *substream)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	unsigned int channel = oxygen_substream_channel(substream);
+
+	mutex_lock(&chip->mutex);
+	chip->pcm_active &= ~(1 << channel);
+	if (channel == PCM_SPDIF) {
+		chip->controls[CONTROL_SPDIF_PCM]->vd[0].access |=
+			SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
+			       SNDRV_CTL_EVENT_MASK_INFO,
+			       &chip->controls[CONTROL_SPDIF_PCM]->id);
+	}
+	if (channel == PCM_SPDIF || channel == PCM_MULTICH)
+		oxygen_update_spdif_source(chip);
+	mutex_unlock(&chip->mutex);
+
+	chip->streams[channel] = NULL;
+	return 0;
+}
+
+static unsigned int oxygen_format(struct snd_pcm_hw_params *hw_params)
+{
+	if (params_format(hw_params) == SNDRV_PCM_FORMAT_S32_LE)
+		return OXYGEN_FORMAT_24;
+	else
+		return OXYGEN_FORMAT_16;
+}
+
+static unsigned int oxygen_rate(struct snd_pcm_hw_params *hw_params)
+{
+	switch (params_rate(hw_params)) {
+	case 32000:
+		return OXYGEN_RATE_32000;
+	case 44100:
+		return OXYGEN_RATE_44100;
+	default: /* 48000 */
+		return OXYGEN_RATE_48000;
+	case 64000:
+		return OXYGEN_RATE_64000;
+	case 88200:
+		return OXYGEN_RATE_88200;
+	case 96000:
+		return OXYGEN_RATE_96000;
+	case 176400:
+		return OXYGEN_RATE_176400;
+	case 192000:
+		return OXYGEN_RATE_192000;
+	}
+}
+
+unsigned int oxygen_default_i2s_mclk(struct oxygen *chip,
+				     unsigned int channel,
+				     struct snd_pcm_hw_params *hw_params)
+{
+	if (params_rate(hw_params) <= 96000)
+		return OXYGEN_I2S_MCLK_256;
+	else
+		return OXYGEN_I2S_MCLK_128;
+}
+EXPORT_SYMBOL(oxygen_default_i2s_mclk);
+
+static unsigned int oxygen_i2s_bits(struct snd_pcm_hw_params *hw_params)
+{
+	if (params_format(hw_params) == SNDRV_PCM_FORMAT_S32_LE)
+		return OXYGEN_I2S_BITS_24;
+	else
+		return OXYGEN_I2S_BITS_16;
+}
+
+static unsigned int oxygen_play_channels(struct snd_pcm_hw_params *hw_params)
+{
+	switch (params_channels(hw_params)) {
+	default: /* 2 */
+		return OXYGEN_PLAY_CHANNELS_2;
+	case 4:
+		return OXYGEN_PLAY_CHANNELS_4;
+	case 6:
+		return OXYGEN_PLAY_CHANNELS_6;
+	case 8:
+		return OXYGEN_PLAY_CHANNELS_8;
+	}
+}
+
+static const unsigned int channel_base_registers[PCM_COUNT] = {
+	[PCM_A] = OXYGEN_DMA_A_ADDRESS,
+	[PCM_B] = OXYGEN_DMA_B_ADDRESS,
+	[PCM_C] = OXYGEN_DMA_C_ADDRESS,
+	[PCM_SPDIF] = OXYGEN_DMA_SPDIF_ADDRESS,
+	[PCM_MULTICH] = OXYGEN_DMA_MULTICH_ADDRESS,
+	[PCM_AC97] = OXYGEN_DMA_AC97_ADDRESS,
+};
+
+static int oxygen_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *hw_params)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	unsigned int channel = oxygen_substream_channel(substream);
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+
+	oxygen_write32(chip, channel_base_registers[channel],
+		       (u32)substream->runtime->dma_addr);
+	if (channel == PCM_MULTICH) {
+		oxygen_write32(chip, OXYGEN_DMA_MULTICH_COUNT,
+			       params_buffer_bytes(hw_params) / 4 - 1);
+		oxygen_write32(chip, OXYGEN_DMA_MULTICH_TCOUNT,
+			       params_period_bytes(hw_params) / 4 - 1);
+	} else {
+		oxygen_write16(chip, channel_base_registers[channel] + 4,
+			       params_buffer_bytes(hw_params) / 4 - 1);
+		oxygen_write16(chip, channel_base_registers[channel] + 6,
+			       params_period_bytes(hw_params) / 4 - 1);
+	}
+	return 0;
+}
+
+static int oxygen_rec_a_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	int err;
+
+	err = oxygen_hw_params(substream, hw_params);
+	if (err < 0)
+		return err;
+
+	spin_lock_irq(&chip->reg_lock);
+	oxygen_write8_masked(chip, OXYGEN_REC_FORMAT,
+			     oxygen_format(hw_params) << OXYGEN_REC_FORMAT_A_SHIFT,
+			     OXYGEN_REC_FORMAT_A_MASK);
+	oxygen_write16_masked(chip, OXYGEN_I2S_A_FORMAT,
+			      oxygen_rate(hw_params) |
+			      chip->model.get_i2s_mclk(chip, PCM_A, hw_params) |
+			      chip->model.adc_i2s_format |
+			      oxygen_i2s_bits(hw_params),
+			      OXYGEN_I2S_RATE_MASK |
+			      OXYGEN_I2S_FORMAT_MASK |
+			      OXYGEN_I2S_MCLK_MASK |
+			      OXYGEN_I2S_BITS_MASK);
+	spin_unlock_irq(&chip->reg_lock);
+
+	mutex_lock(&chip->mutex);
+	chip->model.set_adc_params(chip, hw_params);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int oxygen_rec_b_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	int is_ac97;
+	int err;
+
+	err = oxygen_hw_params(substream, hw_params);
+	if (err < 0)
+		return err;
+
+	is_ac97 = chip->has_ac97_1 &&
+		(chip->model.device_config & CAPTURE_2_FROM_AC97_1);
+
+	spin_lock_irq(&chip->reg_lock);
+	oxygen_write8_masked(chip, OXYGEN_REC_FORMAT,
+			     oxygen_format(hw_params) << OXYGEN_REC_FORMAT_B_SHIFT,
+			     OXYGEN_REC_FORMAT_B_MASK);
+	if (!is_ac97)
+		oxygen_write16_masked(chip, OXYGEN_I2S_B_FORMAT,
+				      oxygen_rate(hw_params) |
+				      chip->model.get_i2s_mclk(chip, PCM_B,
+							       hw_params) |
+				      chip->model.adc_i2s_format |
+				      oxygen_i2s_bits(hw_params),
+				      OXYGEN_I2S_RATE_MASK |
+				      OXYGEN_I2S_FORMAT_MASK |
+				      OXYGEN_I2S_MCLK_MASK |
+				      OXYGEN_I2S_BITS_MASK);
+	spin_unlock_irq(&chip->reg_lock);
+
+	if (!is_ac97) {
+		mutex_lock(&chip->mutex);
+		chip->model.set_adc_params(chip, hw_params);
+		mutex_unlock(&chip->mutex);
+	}
+	return 0;
+}
+
+static int oxygen_rec_c_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	int err;
+
+	err = oxygen_hw_params(substream, hw_params);
+	if (err < 0)
+		return err;
+
+	spin_lock_irq(&chip->reg_lock);
+	oxygen_write8_masked(chip, OXYGEN_REC_FORMAT,
+			     oxygen_format(hw_params) << OXYGEN_REC_FORMAT_C_SHIFT,
+			     OXYGEN_REC_FORMAT_C_MASK);
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int oxygen_spdif_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	int err;
+
+	err = oxygen_hw_params(substream, hw_params);
+	if (err < 0)
+		return err;
+
+	mutex_lock(&chip->mutex);
+	spin_lock_irq(&chip->reg_lock);
+	oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
+			    OXYGEN_SPDIF_OUT_ENABLE);
+	oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT,
+			     oxygen_format(hw_params) << OXYGEN_SPDIF_FORMAT_SHIFT,
+			     OXYGEN_SPDIF_FORMAT_MASK);
+	oxygen_write32_masked(chip, OXYGEN_SPDIF_CONTROL,
+			      oxygen_rate(hw_params) << OXYGEN_SPDIF_OUT_RATE_SHIFT,
+			      OXYGEN_SPDIF_OUT_RATE_MASK);
+	oxygen_update_spdif_source(chip);
+	spin_unlock_irq(&chip->reg_lock);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int oxygen_multich_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *hw_params)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	int err;
+
+	err = oxygen_hw_params(substream, hw_params);
+	if (err < 0)
+		return err;
+
+	mutex_lock(&chip->mutex);
+	spin_lock_irq(&chip->reg_lock);
+	oxygen_write8_masked(chip, OXYGEN_PLAY_CHANNELS,
+			     oxygen_play_channels(hw_params),
+			     OXYGEN_PLAY_CHANNELS_MASK);
+	oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT,
+			     oxygen_format(hw_params) << OXYGEN_MULTICH_FORMAT_SHIFT,
+			     OXYGEN_MULTICH_FORMAT_MASK);
+	oxygen_write16_masked(chip, OXYGEN_I2S_MULTICH_FORMAT,
+			      oxygen_rate(hw_params) |
+			      chip->model.dac_i2s_format |
+			      chip->model.get_i2s_mclk(chip, PCM_MULTICH,
+						       hw_params) |
+			      oxygen_i2s_bits(hw_params),
+			      OXYGEN_I2S_RATE_MASK |
+			      OXYGEN_I2S_FORMAT_MASK |
+			      OXYGEN_I2S_MCLK_MASK |
+			      OXYGEN_I2S_BITS_MASK);
+	oxygen_update_spdif_source(chip);
+	spin_unlock_irq(&chip->reg_lock);
+
+	chip->model.set_dac_params(chip, hw_params);
+	oxygen_update_dac_routing(chip);
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int oxygen_hw_free(struct snd_pcm_substream *substream)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	unsigned int channel = oxygen_substream_channel(substream);
+	unsigned int channel_mask = 1 << channel;
+
+	spin_lock_irq(&chip->reg_lock);
+	chip->interrupt_mask &= ~channel_mask;
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask);
+
+	oxygen_set_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask);
+	oxygen_clear_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask);
+	spin_unlock_irq(&chip->reg_lock);
+
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int oxygen_spdif_hw_free(struct snd_pcm_substream *substream)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&chip->reg_lock);
+	oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL,
+			    OXYGEN_SPDIF_OUT_ENABLE);
+	spin_unlock_irq(&chip->reg_lock);
+	return oxygen_hw_free(substream);
+}
+
+static int oxygen_prepare(struct snd_pcm_substream *substream)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	unsigned int channel = oxygen_substream_channel(substream);
+	unsigned int channel_mask = 1 << channel;
+
+	spin_lock_irq(&chip->reg_lock);
+	oxygen_set_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask);
+	oxygen_clear_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask);
+
+	chip->interrupt_mask |= channel_mask;
+	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask);
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int oxygen_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *s;
+	unsigned int mask = 0;
+	int pausing;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		pausing = 0;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		pausing = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_pcm_group_for_each_entry(s, substream) {
+		if (snd_pcm_substream_chip(s) == chip) {
+			mask |= 1 << oxygen_substream_channel(s);
+			snd_pcm_trigger_done(s, substream);
+		}
+	}
+
+	spin_lock(&chip->reg_lock);
+	if (!pausing) {
+		if (cmd == SNDRV_PCM_TRIGGER_START)
+			chip->pcm_running |= mask;
+		else
+			chip->pcm_running &= ~mask;
+		oxygen_write8(chip, OXYGEN_DMA_STATUS, chip->pcm_running);
+	} else {
+		if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)
+			oxygen_set_bits8(chip, OXYGEN_DMA_PAUSE, mask);
+		else
+			oxygen_clear_bits8(chip, OXYGEN_DMA_PAUSE, mask);
+	}
+	spin_unlock(&chip->reg_lock);
+	return 0;
+}
+
+static snd_pcm_uframes_t oxygen_pointer(struct snd_pcm_substream *substream)
+{
+	struct oxygen *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int channel = oxygen_substream_channel(substream);
+	u32 curr_addr;
+
+	/* no spinlock, this read should be atomic */
+	curr_addr = oxygen_read32(chip, channel_base_registers[channel]);
+	return bytes_to_frames(runtime, curr_addr - (u32)runtime->dma_addr);
+}
+
+static struct snd_pcm_ops oxygen_rec_a_ops = {
+	.open      = oxygen_rec_a_open,
+	.close     = oxygen_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.hw_params = oxygen_rec_a_hw_params,
+	.hw_free   = oxygen_hw_free,
+	.prepare   = oxygen_prepare,
+	.trigger   = oxygen_trigger,
+	.pointer   = oxygen_pointer,
+};
+
+static struct snd_pcm_ops oxygen_rec_b_ops = {
+	.open      = oxygen_rec_b_open,
+	.close     = oxygen_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.hw_params = oxygen_rec_b_hw_params,
+	.hw_free   = oxygen_hw_free,
+	.prepare   = oxygen_prepare,
+	.trigger   = oxygen_trigger,
+	.pointer   = oxygen_pointer,
+};
+
+static struct snd_pcm_ops oxygen_rec_c_ops = {
+	.open      = oxygen_rec_c_open,
+	.close     = oxygen_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.hw_params = oxygen_rec_c_hw_params,
+	.hw_free   = oxygen_hw_free,
+	.prepare   = oxygen_prepare,
+	.trigger   = oxygen_trigger,
+	.pointer   = oxygen_pointer,
+};
+
+static struct snd_pcm_ops oxygen_spdif_ops = {
+	.open      = oxygen_spdif_open,
+	.close     = oxygen_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.hw_params = oxygen_spdif_hw_params,
+	.hw_free   = oxygen_spdif_hw_free,
+	.prepare   = oxygen_prepare,
+	.trigger   = oxygen_trigger,
+	.pointer   = oxygen_pointer,
+};
+
+static struct snd_pcm_ops oxygen_multich_ops = {
+	.open      = oxygen_multich_open,
+	.close     = oxygen_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.hw_params = oxygen_multich_hw_params,
+	.hw_free   = oxygen_hw_free,
+	.prepare   = oxygen_prepare,
+	.trigger   = oxygen_trigger,
+	.pointer   = oxygen_pointer,
+};
+
+static struct snd_pcm_ops oxygen_ac97_ops = {
+	.open      = oxygen_ac97_open,
+	.close     = oxygen_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.hw_params = oxygen_hw_params,
+	.hw_free   = oxygen_hw_free,
+	.prepare   = oxygen_prepare,
+	.trigger   = oxygen_trigger,
+	.pointer   = oxygen_pointer,
+};
+
+static void oxygen_pcm_free(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int oxygen_pcm_init(struct oxygen *chip)
+{
+	struct snd_pcm *pcm;
+	int outs, ins;
+	int err;
+
+	outs = !!(chip->model.device_config & PLAYBACK_0_TO_I2S);
+	ins = !!(chip->model.device_config & (CAPTURE_0_FROM_I2S_1 |
+					      CAPTURE_0_FROM_I2S_2));
+	if (outs | ins) {
+		err = snd_pcm_new(chip->card, "Multichannel",
+				  0, outs, ins, &pcm);
+		if (err < 0)
+			return err;
+		if (outs)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					&oxygen_multich_ops);
+		if (chip->model.device_config & CAPTURE_0_FROM_I2S_1)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+					&oxygen_rec_a_ops);
+		else if (chip->model.device_config & CAPTURE_0_FROM_I2S_2)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+					&oxygen_rec_b_ops);
+		pcm->private_data = chip;
+		pcm->private_free = oxygen_pcm_free;
+		strcpy(pcm->name, "Multichannel");
+		if (outs)
+			snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
+						      SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(chip->pci),
+						      DEFAULT_BUFFER_BYTES_MULTICH,
+						      BUFFER_BYTES_MAX_MULTICH);
+		if (ins)
+			snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+						      SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(chip->pci),
+						      DEFAULT_BUFFER_BYTES,
+						      BUFFER_BYTES_MAX);
+	}
+
+	outs = !!(chip->model.device_config & PLAYBACK_1_TO_SPDIF);
+	ins = !!(chip->model.device_config & CAPTURE_1_FROM_SPDIF);
+	if (outs | ins) {
+		err = snd_pcm_new(chip->card, "Digital", 1, outs, ins, &pcm);
+		if (err < 0)
+			return err;
+		if (outs)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					&oxygen_spdif_ops);
+		if (ins)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+					&oxygen_rec_c_ops);
+		pcm->private_data = chip;
+		pcm->private_free = oxygen_pcm_free;
+		strcpy(pcm->name, "Digital");
+		snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(chip->pci),
+						      DEFAULT_BUFFER_BYTES,
+						      BUFFER_BYTES_MAX);
+	}
+
+	if (chip->has_ac97_1) {
+		outs = !!(chip->model.device_config & PLAYBACK_2_TO_AC97_1);
+		ins = !!(chip->model.device_config & CAPTURE_2_FROM_AC97_1);
+	} else {
+		outs = 0;
+		ins = !!(chip->model.device_config & CAPTURE_2_FROM_I2S_2);
+	}
+	if (outs | ins) {
+		err = snd_pcm_new(chip->card, outs ? "AC97" : "Analog2",
+				  2, outs, ins, &pcm);
+		if (err < 0)
+			return err;
+		if (outs) {
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					&oxygen_ac97_ops);
+			oxygen_write8_masked(chip, OXYGEN_REC_ROUTING,
+					     OXYGEN_REC_B_ROUTE_AC97_1,
+					     OXYGEN_REC_B_ROUTE_MASK);
+		}
+		if (ins)
+			snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+					&oxygen_rec_b_ops);
+		pcm->private_data = chip;
+		pcm->private_free = oxygen_pcm_free;
+		strcpy(pcm->name, outs ? "Front Panel" : "Analog 2");
+		snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(chip->pci),
+						      DEFAULT_BUFFER_BYTES,
+						      BUFFER_BYTES_MAX);
+	}
+	return 0;
+}
diff --git a/linux/sound/pci/oxygen/oxygen_regs.h b/linux/sound/pci/oxygen/oxygen_regs.h
new file mode 100644
index 0000000..4dcd41b
--- /dev/null
+++ b/linux/sound/pci/oxygen/oxygen_regs.h
@@ -0,0 +1,455 @@
+#ifndef OXYGEN_REGS_H_INCLUDED
+#define OXYGEN_REGS_H_INCLUDED
+
+/* recording channel A */
+#define OXYGEN_DMA_A_ADDRESS		0x00	/* 32-bit base address */
+#define OXYGEN_DMA_A_COUNT		0x04	/* buffer counter (dwords) */
+#define OXYGEN_DMA_A_TCOUNT		0x06	/* interrupt counter (dwords) */
+
+/* recording channel B */
+#define OXYGEN_DMA_B_ADDRESS		0x08
+#define OXYGEN_DMA_B_COUNT		0x0c
+#define OXYGEN_DMA_B_TCOUNT		0x0e
+
+/* recording channel C */
+#define OXYGEN_DMA_C_ADDRESS		0x10
+#define OXYGEN_DMA_C_COUNT		0x14
+#define OXYGEN_DMA_C_TCOUNT		0x16
+
+/* SPDIF playback channel */
+#define OXYGEN_DMA_SPDIF_ADDRESS	0x18
+#define OXYGEN_DMA_SPDIF_COUNT		0x1c
+#define OXYGEN_DMA_SPDIF_TCOUNT		0x1e
+
+/* multichannel playback channel */
+#define OXYGEN_DMA_MULTICH_ADDRESS	0x20
+#define OXYGEN_DMA_MULTICH_COUNT	0x24	/* 24 bits */
+#define OXYGEN_DMA_MULTICH_TCOUNT	0x28	/* 24 bits */
+
+/* AC'97 (front panel) playback channel */
+#define OXYGEN_DMA_AC97_ADDRESS		0x30
+#define OXYGEN_DMA_AC97_COUNT		0x34
+#define OXYGEN_DMA_AC97_TCOUNT		0x36
+
+/* all registers 0x00..0x36 return current position on read */
+
+#define OXYGEN_DMA_STATUS		0x40	/* 1 = running, 0 = stop */
+#define  OXYGEN_CHANNEL_A		0x01
+#define  OXYGEN_CHANNEL_B		0x02
+#define  OXYGEN_CHANNEL_C		0x04
+#define  OXYGEN_CHANNEL_SPDIF		0x08
+#define  OXYGEN_CHANNEL_MULTICH		0x10
+#define  OXYGEN_CHANNEL_AC97		0x20
+
+#define OXYGEN_DMA_PAUSE		0x41	/* 1 = pause */
+/* OXYGEN_CHANNEL_* */
+
+#define OXYGEN_DMA_RESET		0x42
+/* OXYGEN_CHANNEL_* */
+
+#define OXYGEN_PLAY_CHANNELS		0x43
+#define  OXYGEN_PLAY_CHANNELS_MASK	0x03
+#define  OXYGEN_PLAY_CHANNELS_2		0x00
+#define  OXYGEN_PLAY_CHANNELS_4		0x01
+#define  OXYGEN_PLAY_CHANNELS_6		0x02
+#define  OXYGEN_PLAY_CHANNELS_8		0x03
+#define  OXYGEN_DMA_A_BURST_MASK	0x04
+#define  OXYGEN_DMA_A_BURST_8		0x00	/* dwords */
+#define  OXYGEN_DMA_A_BURST_16		0x04
+#define  OXYGEN_DMA_MULTICH_BURST_MASK	0x08
+#define  OXYGEN_DMA_MULTICH_BURST_8	0x00
+#define  OXYGEN_DMA_MULTICH_BURST_16	0x08
+
+#define OXYGEN_INTERRUPT_MASK		0x44
+/* OXYGEN_CHANNEL_* */
+#define  OXYGEN_INT_SPDIF_IN_DETECT	0x0100
+#define  OXYGEN_INT_MCU			0x0200
+#define  OXYGEN_INT_2WIRE		0x0400
+#define  OXYGEN_INT_GPIO		0x0800
+#define  OXYGEN_INT_MCB			0x2000
+#define  OXYGEN_INT_AC97		0x4000
+
+#define OXYGEN_INTERRUPT_STATUS		0x46
+/* OXYGEN_CHANNEL_* amd OXYGEN_INT_* */
+#define  OXYGEN_INT_MIDI		0x1000
+
+#define OXYGEN_MISC			0x48
+#define  OXYGEN_MISC_WRITE_PCI_SUBID	0x01
+#define  OXYGEN_MISC_LATENCY_3F		0x02
+#define  OXYGEN_MISC_REC_C_FROM_SPDIF	0x04
+#define  OXYGEN_MISC_REC_B_FROM_AC97	0x08
+#define  OXYGEN_MISC_REC_A_FROM_MULTICH	0x10
+#define  OXYGEN_MISC_PCI_MEM_W_1_CLOCK	0x20
+#define  OXYGEN_MISC_MIDI		0x40
+#define  OXYGEN_MISC_CRYSTAL_MASK	0x80
+#define  OXYGEN_MISC_CRYSTAL_24576	0x00
+#define  OXYGEN_MISC_CRYSTAL_27		0x80	/* MHz */
+
+#define OXYGEN_REC_FORMAT		0x4a
+#define  OXYGEN_REC_FORMAT_A_MASK	0x03
+#define  OXYGEN_REC_FORMAT_A_SHIFT	0
+#define  OXYGEN_REC_FORMAT_B_MASK	0x0c
+#define  OXYGEN_REC_FORMAT_B_SHIFT	2
+#define  OXYGEN_REC_FORMAT_C_MASK	0x30
+#define  OXYGEN_REC_FORMAT_C_SHIFT	4
+#define  OXYGEN_FORMAT_16		0x00
+#define  OXYGEN_FORMAT_24		0x01
+#define  OXYGEN_FORMAT_32		0x02
+
+#define OXYGEN_PLAY_FORMAT		0x4b
+#define  OXYGEN_SPDIF_FORMAT_MASK	0x03
+#define  OXYGEN_SPDIF_FORMAT_SHIFT	0
+#define  OXYGEN_MULTICH_FORMAT_MASK	0x0c
+#define  OXYGEN_MULTICH_FORMAT_SHIFT	2
+/* OXYGEN_FORMAT_* */
+
+#define OXYGEN_REC_CHANNELS		0x4c
+#define  OXYGEN_REC_CHANNELS_MASK	0x07
+#define  OXYGEN_REC_CHANNELS_2_2_2	0x00	/* DMA A, B, C */
+#define  OXYGEN_REC_CHANNELS_4_2_2	0x01
+#define  OXYGEN_REC_CHANNELS_6_0_2	0x02
+#define  OXYGEN_REC_CHANNELS_6_2_0	0x03
+#define  OXYGEN_REC_CHANNELS_8_0_0	0x04
+
+#define OXYGEN_FUNCTION			0x50
+#define  OXYGEN_FUNCTION_CLOCK_MASK	0x01
+#define  OXYGEN_FUNCTION_CLOCK_PLL	0x00
+#define  OXYGEN_FUNCTION_CLOCK_CRYSTAL	0x01
+#define  OXYGEN_FUNCTION_RESET_CODEC	0x02
+#define  OXYGEN_FUNCTION_RESET_POL	0x04
+#define  OXYGEN_FUNCTION_PWDN		0x08
+#define  OXYGEN_FUNCTION_PWDN_EN	0x10
+#define  OXYGEN_FUNCTION_PWDN_POL	0x20
+#define  OXYGEN_FUNCTION_2WIRE_SPI_MASK	0x40
+#define  OXYGEN_FUNCTION_SPI		0x00
+#define  OXYGEN_FUNCTION_2WIRE		0x40
+#define  OXYGEN_FUNCTION_ENABLE_SPI_4_5	0x80	/* 0 = EEPROM */
+
+#define OXYGEN_I2S_MULTICH_FORMAT	0x60
+#define  OXYGEN_I2S_RATE_MASK		0x0007	/* LRCK */
+#define  OXYGEN_RATE_32000		0x0000
+#define  OXYGEN_RATE_44100		0x0001
+#define  OXYGEN_RATE_48000		0x0002
+#define  OXYGEN_RATE_64000		0x0003
+#define  OXYGEN_RATE_88200		0x0004
+#define  OXYGEN_RATE_96000		0x0005
+#define  OXYGEN_RATE_176400		0x0006
+#define  OXYGEN_RATE_192000		0x0007
+#define  OXYGEN_I2S_FORMAT_MASK		0x0008
+#define  OXYGEN_I2S_FORMAT_I2S		0x0000
+#define  OXYGEN_I2S_FORMAT_LJUST	0x0008
+#define  OXYGEN_I2S_MCLK_MASK		0x0030	/* MCLK/LRCK */
+#define  OXYGEN_I2S_MCLK_128		0x0000
+#define  OXYGEN_I2S_MCLK_256		0x0010
+#define  OXYGEN_I2S_MCLK_512		0x0020
+#define  OXYGEN_I2S_BITS_MASK		0x00c0
+#define  OXYGEN_I2S_BITS_16		0x0000
+#define  OXYGEN_I2S_BITS_20		0x0040
+#define  OXYGEN_I2S_BITS_24		0x0080
+#define  OXYGEN_I2S_BITS_32		0x00c0
+#define  OXYGEN_I2S_MASTER		0x0100
+#define  OXYGEN_I2S_BCLK_MASK		0x0600	/* BCLK/LRCK */
+#define  OXYGEN_I2S_BCLK_64		0x0000
+#define  OXYGEN_I2S_BCLK_128		0x0200
+#define  OXYGEN_I2S_BCLK_256		0x0400
+#define  OXYGEN_I2S_MUTE_MCLK		0x0800
+
+#define OXYGEN_I2S_A_FORMAT		0x62
+#define OXYGEN_I2S_B_FORMAT		0x64
+#define OXYGEN_I2S_C_FORMAT		0x66
+/* like OXYGEN_I2S_MULTICH_FORMAT */
+
+#define OXYGEN_SPDIF_CONTROL		0x70
+#define  OXYGEN_SPDIF_OUT_ENABLE	0x00000002
+#define  OXYGEN_SPDIF_LOOPBACK		0x00000004	/* in to out */
+#define  OXYGEN_SPDIF_SENSE_MASK	0x00000008
+#define  OXYGEN_SPDIF_LOCK_MASK		0x00000010
+#define  OXYGEN_SPDIF_RATE_MASK		0x00000020
+#define  OXYGEN_SPDIF_SPDVALID		0x00000040
+#define  OXYGEN_SPDIF_SENSE_PAR		0x00000200
+#define  OXYGEN_SPDIF_LOCK_PAR		0x00000400
+#define  OXYGEN_SPDIF_SENSE_STATUS	0x00000800
+#define  OXYGEN_SPDIF_LOCK_STATUS	0x00001000
+#define  OXYGEN_SPDIF_SENSE_INT		0x00002000	/* r/wc */
+#define  OXYGEN_SPDIF_LOCK_INT		0x00004000	/* r/wc */
+#define  OXYGEN_SPDIF_RATE_INT		0x00008000	/* r/wc */
+#define  OXYGEN_SPDIF_IN_CLOCK_MASK	0x00010000
+#define  OXYGEN_SPDIF_IN_CLOCK_96	0x00000000	/* <= 96 kHz */
+#define  OXYGEN_SPDIF_IN_CLOCK_192	0x00010000	/* > 96 kHz */
+#define  OXYGEN_SPDIF_OUT_RATE_MASK	0x07000000
+#define  OXYGEN_SPDIF_OUT_RATE_SHIFT	24
+/* OXYGEN_RATE_* << OXYGEN_SPDIF_OUT_RATE_SHIFT */
+
+#define OXYGEN_SPDIF_OUTPUT_BITS	0x74
+#define  OXYGEN_SPDIF_NONAUDIO		0x00000002
+#define  OXYGEN_SPDIF_C			0x00000004
+#define  OXYGEN_SPDIF_PREEMPHASIS	0x00000008
+#define  OXYGEN_SPDIF_CATEGORY_MASK	0x000007f0
+#define  OXYGEN_SPDIF_CATEGORY_SHIFT	4
+#define  OXYGEN_SPDIF_ORIGINAL		0x00000800
+#define  OXYGEN_SPDIF_CS_RATE_MASK	0x0000f000
+#define  OXYGEN_SPDIF_CS_RATE_SHIFT	12
+#define  OXYGEN_SPDIF_V			0x00010000	/* 0 = valid */
+
+#define OXYGEN_SPDIF_INPUT_BITS		0x78
+/* 32 bits, IEC958_AES_* */
+
+#define OXYGEN_EEPROM_CONTROL		0x80
+#define  OXYGEN_EEPROM_ADDRESS_MASK	0x7f
+#define  OXYGEN_EEPROM_DIR_MASK		0x80
+#define  OXYGEN_EEPROM_DIR_READ		0x00
+#define  OXYGEN_EEPROM_DIR_WRITE	0x80
+
+#define OXYGEN_EEPROM_STATUS		0x81
+#define  OXYGEN_EEPROM_VALID		0x40
+#define  OXYGEN_EEPROM_BUSY		0x80
+
+#define OXYGEN_EEPROM_DATA		0x82	/* 16 bits */
+
+#define OXYGEN_2WIRE_CONTROL		0x90
+#define  OXYGEN_2WIRE_DIR_MASK		0x01
+#define  OXYGEN_2WIRE_DIR_WRITE		0x00
+#define  OXYGEN_2WIRE_DIR_READ		0x01
+#define  OXYGEN_2WIRE_ADDRESS_MASK	0xfe	/* slave device address */
+#define  OXYGEN_2WIRE_ADDRESS_SHIFT	1
+
+#define OXYGEN_2WIRE_MAP		0x91	/* address, 8 bits */
+#define OXYGEN_2WIRE_DATA		0x92	/* data, 16 bits */
+
+#define OXYGEN_2WIRE_BUS_STATUS		0x94
+#define  OXYGEN_2WIRE_BUSY		0x0001
+#define  OXYGEN_2WIRE_LENGTH_MASK	0x0002
+#define  OXYGEN_2WIRE_LENGTH_8		0x0000
+#define  OXYGEN_2WIRE_LENGTH_16		0x0002
+#define  OXYGEN_2WIRE_MANUAL_READ	0x0004	/* 0 = auto read */
+#define  OXYGEN_2WIRE_WRITE_MAP_ONLY	0x0008
+#define  OXYGEN_2WIRE_SLAVE_AD_MASK	0x0030	/* AD0, AD1 */
+#define  OXYGEN_2WIRE_INTERRUPT_MASK	0x0040	/* 0 = int. if not responding */
+#define  OXYGEN_2WIRE_SLAVE_NO_RESPONSE	0x0080
+#define  OXYGEN_2WIRE_SPEED_MASK	0x0100
+#define  OXYGEN_2WIRE_SPEED_STANDARD	0x0000
+#define  OXYGEN_2WIRE_SPEED_FAST	0x0100
+#define  OXYGEN_2WIRE_CLOCK_SYNC	0x0200
+#define  OXYGEN_2WIRE_BUS_RESET		0x0400
+
+#define OXYGEN_SPI_CONTROL		0x98
+#define  OXYGEN_SPI_BUSY		0x01	/* read */
+#define  OXYGEN_SPI_TRIGGER		0x01	/* write */
+#define  OXYGEN_SPI_DATA_LENGTH_MASK	0x02
+#define  OXYGEN_SPI_DATA_LENGTH_2	0x00
+#define  OXYGEN_SPI_DATA_LENGTH_3	0x02
+#define  OXYGEN_SPI_CLOCK_MASK		0xc0
+#define  OXYGEN_SPI_CLOCK_160		0x00	/* ns */
+#define  OXYGEN_SPI_CLOCK_320		0x40
+#define  OXYGEN_SPI_CLOCK_640		0x80
+#define  OXYGEN_SPI_CLOCK_1280		0xc0
+#define  OXYGEN_SPI_CODEC_MASK		0x70	/* 0..5 */
+#define  OXYGEN_SPI_CODEC_SHIFT		4
+#define  OXYGEN_SPI_CEN_MASK		0x80
+#define  OXYGEN_SPI_CEN_LATCH_CLOCK_LO	0x00
+#define  OXYGEN_SPI_CEN_LATCH_CLOCK_HI	0x80
+
+#define OXYGEN_SPI_DATA1		0x99
+#define OXYGEN_SPI_DATA2		0x9a
+#define OXYGEN_SPI_DATA3		0x9b
+
+#define OXYGEN_MPU401			0xa0
+
+#define OXYGEN_MPU401_CONTROL		0xa2
+#define  OXYGEN_MPU401_LOOPBACK		0x01	/* TXD to RXD */
+
+#define OXYGEN_GPI_DATA			0xa4
+/* bits 0..5 = pin XGPI0..XGPI5 */
+
+#define OXYGEN_GPI_INTERRUPT_MASK	0xa5
+/* bits 0..5, 1 = enable */
+
+#define OXYGEN_GPIO_DATA		0xa6
+/* bits 0..9 */
+
+#define OXYGEN_GPIO_CONTROL		0xa8
+/* bits 0..9, 0 = input, 1 = output */
+#define  OXYGEN_GPIO1_XSLAVE_RDY	0x8000
+
+#define OXYGEN_GPIO_INTERRUPT_MASK	0xaa
+/* bits 0..9, 1 = enable */
+
+#define OXYGEN_DEVICE_SENSE		0xac
+#define  OXYGEN_HEAD_PHONE_DETECT	0x01
+#define  OXYGEN_HEAD_PHONE_MASK		0x06
+#define  OXYGEN_HEAD_PHONE_PASSIVE_SPK	0x00
+#define  OXYGEN_HEAD_PHONE_HP		0x02
+#define  OXYGEN_HEAD_PHONE_ACTIVE_SPK	0x04
+
+#define OXYGEN_MCU_2WIRE_DATA		0xb0
+
+#define OXYGEN_MCU_2WIRE_MAP		0xb2
+
+#define OXYGEN_MCU_2WIRE_STATUS		0xb3
+#define  OXYGEN_MCU_2WIRE_BUSY		0x01
+#define  OXYGEN_MCU_2WIRE_LENGTH_MASK	0x06
+#define  OXYGEN_MCU_2WIRE_LENGTH_1	0x00
+#define  OXYGEN_MCU_2WIRE_LENGTH_2	0x02
+#define  OXYGEN_MCU_2WIRE_LENGTH_3	0x04
+#define  OXYGEN_MCU_2WIRE_WRITE		0x08	/* r/wc */
+#define  OXYGEN_MCU_2WIRE_READ		0x10	/* r/wc */
+#define  OXYGEN_MCU_2WIRE_DRV_XACT_FAIL	0x20	/* r/wc */
+#define  OXYGEN_MCU_2WIRE_RESET		0x40
+
+#define OXYGEN_MCU_2WIRE_CONTROL	0xb4
+#define  OXYGEN_MCU_2WIRE_DRV_ACK	0x01
+#define  OXYGEN_MCU_2WIRE_DRV_XACT	0x02
+#define  OXYGEN_MCU_2WIRE_INT_MASK	0x04
+#define  OXYGEN_MCU_2WIRE_SYNC_MASK	0x08
+#define  OXYGEN_MCU_2WIRE_SYNC_RDY_PIN	0x00
+#define  OXYGEN_MCU_2WIRE_SYNC_DATA	0x08
+#define  OXYGEN_MCU_2WIRE_ADDRESS_MASK	0x30
+#define  OXYGEN_MCU_2WIRE_ADDRESS_10	0x00
+#define  OXYGEN_MCU_2WIRE_ADDRESS_12	0x10
+#define  OXYGEN_MCU_2WIRE_ADDRESS_14	0x20
+#define  OXYGEN_MCU_2WIRE_ADDRESS_16	0x30
+#define  OXYGEN_MCU_2WIRE_INT_POL	0x40
+#define  OXYGEN_MCU_2WIRE_SYNC_ENABLE	0x80
+
+#define OXYGEN_PLAY_ROUTING		0xc0
+#define  OXYGEN_PLAY_MUTE01		0x0001
+#define  OXYGEN_PLAY_MUTE23		0x0002
+#define  OXYGEN_PLAY_MUTE45		0x0004
+#define  OXYGEN_PLAY_MUTE67		0x0008
+#define  OXYGEN_PLAY_MULTICH_MASK	0x0010
+#define  OXYGEN_PLAY_MULTICH_I2S_DAC	0x0000
+#define  OXYGEN_PLAY_MULTICH_AC97	0x0010
+#define  OXYGEN_PLAY_SPDIF_MASK		0x00e0
+#define  OXYGEN_PLAY_SPDIF_SPDIF	0x0000
+#define  OXYGEN_PLAY_SPDIF_MULTICH_01	0x0020
+#define  OXYGEN_PLAY_SPDIF_MULTICH_23	0x0040
+#define  OXYGEN_PLAY_SPDIF_MULTICH_45	0x0060
+#define  OXYGEN_PLAY_SPDIF_MULTICH_67	0x0080
+#define  OXYGEN_PLAY_SPDIF_REC_A	0x00a0
+#define  OXYGEN_PLAY_SPDIF_REC_B	0x00c0
+#define  OXYGEN_PLAY_SPDIF_I2S_ADC_3	0x00e0
+#define  OXYGEN_PLAY_DAC0_SOURCE_MASK	0x0300
+#define  OXYGEN_PLAY_DAC0_SOURCE_SHIFT	8
+#define  OXYGEN_PLAY_DAC1_SOURCE_MASK	0x0c00
+#define  OXYGEN_PLAY_DAC1_SOURCE_SHIFT	10
+#define  OXYGEN_PLAY_DAC2_SOURCE_MASK	0x3000
+#define  OXYGEN_PLAY_DAC2_SOURCE_SHIFT	12
+#define  OXYGEN_PLAY_DAC3_SOURCE_MASK	0xc000
+#define  OXYGEN_PLAY_DAC3_SOURCE_SHIFT	14
+
+#define OXYGEN_REC_ROUTING		0xc2
+#define  OXYGEN_MUTE_I2S_ADC_1		0x01
+#define  OXYGEN_MUTE_I2S_ADC_2		0x02
+#define  OXYGEN_MUTE_I2S_ADC_3		0x04
+#define  OXYGEN_REC_A_ROUTE_MASK	0x08
+#define  OXYGEN_REC_A_ROUTE_I2S_ADC_1	0x00
+#define  OXYGEN_REC_A_ROUTE_AC97_0	0x08
+#define  OXYGEN_REC_B_ROUTE_MASK	0x10
+#define  OXYGEN_REC_B_ROUTE_I2S_ADC_2	0x00
+#define  OXYGEN_REC_B_ROUTE_AC97_1	0x10
+#define  OXYGEN_REC_C_ROUTE_MASK	0x20
+#define  OXYGEN_REC_C_ROUTE_SPDIF	0x00
+#define  OXYGEN_REC_C_ROUTE_I2S_ADC_3	0x20
+
+#define OXYGEN_ADC_MONITOR		0xc3
+#define  OXYGEN_ADC_MONITOR_A		0x01
+#define  OXYGEN_ADC_MONITOR_A_HALF_VOL	0x02
+#define  OXYGEN_ADC_MONITOR_B		0x04
+#define  OXYGEN_ADC_MONITOR_B_HALF_VOL	0x08
+#define  OXYGEN_ADC_MONITOR_C		0x10
+#define  OXYGEN_ADC_MONITOR_C_HALF_VOL	0x20
+
+#define OXYGEN_A_MONITOR_ROUTING	0xc4
+#define  OXYGEN_A_MONITOR_ROUTE_0_MASK	0x03
+#define  OXYGEN_A_MONITOR_ROUTE_0_SHIFT	0
+#define  OXYGEN_A_MONITOR_ROUTE_1_MASK	0x0c
+#define  OXYGEN_A_MONITOR_ROUTE_1_SHIFT	2
+#define  OXYGEN_A_MONITOR_ROUTE_2_MASK	0x30
+#define  OXYGEN_A_MONITOR_ROUTE_2_SHIFT	4
+#define  OXYGEN_A_MONITOR_ROUTE_3_MASK	0xc0
+#define  OXYGEN_A_MONITOR_ROUTE_3_SHIFT	6
+
+#define OXYGEN_AC97_CONTROL		0xd0
+#define  OXYGEN_AC97_COLD_RESET		0x0001
+#define  OXYGEN_AC97_SUSPENDED		0x0002	/* read */
+#define  OXYGEN_AC97_RESUME		0x0002	/* write */
+#define  OXYGEN_AC97_CLOCK_DISABLE	0x0004
+#define  OXYGEN_AC97_NO_CODEC_0		0x0008
+#define  OXYGEN_AC97_CODEC_0		0x0010
+#define  OXYGEN_AC97_CODEC_1		0x0020
+
+#define OXYGEN_AC97_INTERRUPT_MASK	0xd2
+#define  OXYGEN_AC97_INT_READ_DONE	0x01
+#define  OXYGEN_AC97_INT_WRITE_DONE	0x02
+#define  OXYGEN_AC97_INT_CODEC_0	0x10
+#define  OXYGEN_AC97_INT_CODEC_1	0x20
+
+#define OXYGEN_AC97_INTERRUPT_STATUS	0xd3
+/* OXYGEN_AC97_INT_* */
+
+#define OXYGEN_AC97_OUT_CONFIG		0xd4
+#define  OXYGEN_AC97_CODEC1_SLOT3	0x00000001
+#define  OXYGEN_AC97_CODEC1_SLOT3_VSR	0x00000002
+#define  OXYGEN_AC97_CODEC1_SLOT4	0x00000010
+#define  OXYGEN_AC97_CODEC1_SLOT4_VSR	0x00000020
+#define  OXYGEN_AC97_CODEC0_FRONTL	0x00000100
+#define  OXYGEN_AC97_CODEC0_FRONTR	0x00000200
+#define  OXYGEN_AC97_CODEC0_SIDEL	0x00000400
+#define  OXYGEN_AC97_CODEC0_SIDER	0x00000800
+#define  OXYGEN_AC97_CODEC0_CENTER	0x00001000
+#define  OXYGEN_AC97_CODEC0_BASE	0x00002000
+#define  OXYGEN_AC97_CODEC0_REARL	0x00004000
+#define  OXYGEN_AC97_CODEC0_REARR	0x00008000
+
+#define OXYGEN_AC97_IN_CONFIG		0xd8
+#define  OXYGEN_AC97_CODEC1_LINEL	0x00000001
+#define  OXYGEN_AC97_CODEC1_LINEL_VSR	0x00000002
+#define  OXYGEN_AC97_CODEC1_LINEL_16	0x00000000
+#define  OXYGEN_AC97_CODEC1_LINEL_18	0x00000004
+#define  OXYGEN_AC97_CODEC1_LINEL_20	0x00000008
+#define  OXYGEN_AC97_CODEC1_LINER	0x00000010
+#define  OXYGEN_AC97_CODEC1_LINER_VSR	0x00000020
+#define  OXYGEN_AC97_CODEC1_LINER_16	0x00000000
+#define  OXYGEN_AC97_CODEC1_LINER_18	0x00000040
+#define  OXYGEN_AC97_CODEC1_LINER_20	0x00000080
+#define  OXYGEN_AC97_CODEC0_LINEL	0x00000100
+#define  OXYGEN_AC97_CODEC0_LINER	0x00000200
+
+#define OXYGEN_AC97_REGS		0xdc
+#define  OXYGEN_AC97_REG_DATA_MASK	0x0000ffff
+#define  OXYGEN_AC97_REG_ADDR_MASK	0x007f0000
+#define  OXYGEN_AC97_REG_ADDR_SHIFT	16
+#define  OXYGEN_AC97_REG_DIR_MASK	0x00800000
+#define  OXYGEN_AC97_REG_DIR_WRITE	0x00000000
+#define  OXYGEN_AC97_REG_DIR_READ	0x00800000
+#define  OXYGEN_AC97_REG_CODEC_MASK	0x01000000
+#define  OXYGEN_AC97_REG_CODEC_SHIFT	24
+
+#define OXYGEN_TEST			0xe0
+#define  OXYGEN_TEST_RAM_SUCCEEDED	0x01
+#define  OXYGEN_TEST_PLAYBACK_RAM	0x02
+#define  OXYGEN_TEST_RECORD_RAM		0x04
+#define  OXYGEN_TEST_PLL		0x08
+#define  OXYGEN_TEST_2WIRE_LOOPBACK	0x10
+
+#define OXYGEN_DMA_FLUSH		0xe1
+/* OXYGEN_CHANNEL_* */
+
+#define OXYGEN_CODEC_VERSION		0xe4
+#define  OXYGEN_CODEC_ID_MASK		0x07
+
+#define OXYGEN_REVISION			0xe6
+#define  OXYGEN_PACKAGE_ID_MASK		0x0007
+#define  OXYGEN_PACKAGE_ID_8786		0x0004
+#define  OXYGEN_PACKAGE_ID_8787		0x0006
+#define  OXYGEN_PACKAGE_ID_8788		0x0007
+#define  OXYGEN_REVISION_MASK		0xfff8
+#define  OXYGEN_REVISION_2		0x0008
+
+#define OXYGEN_OFFSIN_48K		0xe8
+#define OXYGEN_OFFSBASE_48K		0xe9
+#define  OXYGEN_OFFSBASE_MASK		0x0fff
+#define OXYGEN_OFFSIN_44K		0xec
+#define OXYGEN_OFFSBASE_44K		0xed
+
+#endif
diff --git a/linux/sound/pci/oxygen/pcm1796.h b/linux/sound/pci/oxygen/pcm1796.h
new file mode 100644
index 0000000..698bf46
--- /dev/null
+++ b/linux/sound/pci/oxygen/pcm1796.h
@@ -0,0 +1,58 @@
+#ifndef PCM1796_H_INCLUDED
+#define PCM1796_H_INCLUDED
+
+/* register 16 */
+#define PCM1796_ATL_MASK	0xff
+/* register 17 */
+#define PCM1796_ATR_MASK	0xff
+/* register 18 */
+#define PCM1796_MUTE		0x01
+#define PCM1796_DME		0x02
+#define PCM1796_DMF_MASK	0x0c
+#define PCM1796_DMF_DISABLED	0x00
+#define PCM1796_DMF_48		0x04
+#define PCM1796_DMF_441		0x08
+#define PCM1796_DMF_32		0x0c
+#define PCM1796_FMT_MASK	0x70
+#define PCM1796_FMT_16_RJUST	0x00
+#define PCM1796_FMT_20_RJUST	0x10
+#define PCM1796_FMT_24_RJUST	0x20
+#define PCM1796_FMT_24_LJUST	0x30
+#define PCM1796_FMT_16_I2S	0x40
+#define PCM1796_FMT_24_I2S	0x50
+#define PCM1796_ATLD		0x80
+/* register 19 */
+#define PCM1796_INZD		0x01
+#define PCM1796_FLT_MASK	0x02
+#define PCM1796_FLT_SHARP	0x00
+#define PCM1796_FLT_SLOW	0x02
+#define PCM1796_DFMS		0x04
+#define PCM1796_OPE		0x10
+#define PCM1796_ATS_MASK	0x60
+#define PCM1796_ATS_1		0x00
+#define PCM1796_ATS_2		0x20
+#define PCM1796_ATS_4		0x40
+#define PCM1796_ATS_8		0x60
+#define PCM1796_REV		0x80
+/* register 20 */
+#define PCM1796_OS_MASK		0x03
+#define PCM1796_OS_64		0x00
+#define PCM1796_OS_32		0x01
+#define PCM1796_OS_128		0x02
+#define PCM1796_CHSL_MASK	0x04
+#define PCM1796_CHSL_LEFT	0x00
+#define PCM1796_CHSL_RIGHT	0x04
+#define PCM1796_MONO		0x08
+#define PCM1796_DFTH		0x10
+#define PCM1796_DSD		0x20
+#define PCM1796_SRST		0x40
+/* register 21 */
+#define PCM1796_PCMZ		0x01
+#define PCM1796_DZ_MASK		0x06
+/* register 22 */
+#define PCM1796_ZFGL		0x01
+#define PCM1796_ZFGR		0x02
+/* register 23 */
+#define PCM1796_ID_MASK		0x1f
+
+#endif
diff --git a/linux/sound/pci/oxygen/virtuoso.c b/linux/sound/pci/oxygen/virtuoso.c
new file mode 100644
index 0000000..469010a
--- /dev/null
+++ b/linux/sound/pci/oxygen/virtuoso.c
@@ -0,0 +1,113 @@
+/*
+ * C-Media CMI8788 driver for Asus Xonar cards
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include "xonar.h"
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("Asus Virtuoso driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{Asus,AV66},{Asus,AV100},{Asus,AV200}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "card index");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "enable card");
+
+static DEFINE_PCI_DEVICE_TABLE(xonar_ids) = {
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8269) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8275) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x82b7) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8314) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x8327) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x834f) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x835c) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x835d) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x835e) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x838e) },
+	{ OXYGEN_PCI_SUBID_BROKEN_EEPROM },
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, xonar_ids);
+
+static int __devinit get_xonar_model(struct oxygen *chip,
+				     const struct pci_device_id *id)
+{
+	if (get_xonar_pcm179x_model(chip, id) >= 0)
+		return 0;
+	if (get_xonar_cs43xx_model(chip, id) >= 0)
+		return 0;
+	if (get_xonar_wm87x6_model(chip, id) >= 0)
+		return 0;
+	return -EINVAL;
+}
+
+static int __devinit xonar_probe(struct pci_dev *pci,
+				 const struct pci_device_id *pci_id)
+{
+	static int dev;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		++dev;
+		return -ENOENT;
+	}
+	err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE,
+			       xonar_ids, get_xonar_model);
+	if (err >= 0)
+		++dev;
+	return err;
+}
+
+static struct pci_driver xonar_driver = {
+	.name = "AV200",
+	.id_table = xonar_ids,
+	.probe = xonar_probe,
+	.remove = __devexit_p(oxygen_pci_remove),
+#ifdef CONFIG_PM
+	.suspend = oxygen_pci_suspend,
+	.resume = oxygen_pci_resume,
+#endif
+	.shutdown = oxygen_pci_shutdown,
+};
+
+static int __init alsa_card_xonar_init(void)
+{
+	return pci_register_driver(&xonar_driver);
+}
+
+static void __exit alsa_card_xonar_exit(void)
+{
+	pci_unregister_driver(&xonar_driver);
+}
+
+module_init(alsa_card_xonar_init)
+module_exit(alsa_card_xonar_exit)
diff --git a/linux/sound/pci/oxygen/wm8766.h b/linux/sound/pci/oxygen/wm8766.h
new file mode 100644
index 0000000..e0e849a
--- /dev/null
+++ b/linux/sound/pci/oxygen/wm8766.h
@@ -0,0 +1,73 @@
+#ifndef WM8766_H_INCLUDED
+#define WM8766_H_INCLUDED
+
+#define WM8766_LDA1		0x00
+#define WM8766_RDA1		0x01
+#define WM8766_DAC_CTRL		0x02
+#define WM8766_INT_CTRL		0x03
+#define WM8766_LDA2		0x04
+#define WM8766_RDA2		0x05
+#define WM8766_LDA3		0x06
+#define WM8766_RDA3		0x07
+#define WM8766_MASTDA		0x08
+#define WM8766_DAC_CTRL2	0x09
+#define WM8766_DAC_CTRL3	0x0a
+#define WM8766_MUTE1		0x0c
+#define WM8766_MUTE2		0x0f
+#define WM8766_RESET		0x1f
+
+/* LDAx/RDAx/MASTDA */
+#define WM8766_ATT_MASK		0x0ff
+#define WM8766_UPDATE		0x100
+/* DAC_CTRL */
+#define WM8766_MUTEALL		0x001
+#define WM8766_DEEMPALL		0x002
+#define WM8766_PWDN		0x004
+#define WM8766_ATC		0x008
+#define WM8766_IZD		0x010
+#define WM8766_PL_LEFT_MASK	0x060
+#define WM8766_PL_LEFT_MUTE	0x000
+#define WM8766_PL_LEFT_LEFT	0x020
+#define WM8766_PL_LEFT_RIGHT	0x040
+#define WM8766_PL_LEFT_LRMIX	0x060
+#define WM8766_PL_RIGHT_MASK	0x180
+#define WM8766_PL_RIGHT_MUTE	0x000
+#define WM8766_PL_RIGHT_LEFT	0x080
+#define WM8766_PL_RIGHT_RIGHT	0x100
+#define WM8766_PL_RIGHT_LRMIX	0x180
+/* INT_CTRL */
+#define WM8766_FMT_MASK		0x003
+#define WM8766_FMT_RJUST	0x000
+#define WM8766_FMT_LJUST	0x001
+#define WM8766_FMT_I2S		0x002
+#define WM8766_FMT_DSP		0x003
+#define WM8766_LRP		0x004
+#define WM8766_BCP		0x008
+#define WM8766_IWL_MASK		0x030
+#define WM8766_IWL_16		0x000
+#define WM8766_IWL_20		0x010
+#define WM8766_IWL_24		0x020
+#define WM8766_IWL_32		0x030
+#define WM8766_PHASE_MASK	0x1c0
+/* DAC_CTRL2 */
+#define WM8766_ZCD		0x001
+#define WM8766_DZFM_MASK	0x006
+#define WM8766_DMUTE_MASK	0x038
+#define WM8766_DEEMP_MASK	0x1c0
+/* DAC_CTRL3 */
+#define WM8766_DACPD_MASK	0x00e
+#define WM8766_PWRDNALL		0x010
+#define WM8766_MS		0x020
+#define WM8766_RATE_MASK	0x1c0
+#define WM8766_RATE_128		0x000
+#define WM8766_RATE_192		0x040
+#define WM8766_RATE_256		0x080
+#define WM8766_RATE_384		0x0c0
+#define WM8766_RATE_512		0x100
+#define WM8766_RATE_768		0x140
+/* MUTE1 */
+#define WM8766_MPD1		0x040
+/* MUTE2 */
+#define WM8766_MPD2		0x020
+
+#endif
diff --git a/linux/sound/pci/oxygen/wm8776.h b/linux/sound/pci/oxygen/wm8776.h
new file mode 100644
index 0000000..1a96f56
--- /dev/null
+++ b/linux/sound/pci/oxygen/wm8776.h
@@ -0,0 +1,177 @@
+#ifndef WM8776_H_INCLUDED
+#define WM8776_H_INCLUDED
+
+/*
+ * the following register names are from:
+ * wm8776.h  --  WM8776 ASoC driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define WM8776_HPLVOL		0x00
+#define WM8776_HPRVOL		0x01
+#define WM8776_HPMASTER		0x02
+#define WM8776_DACLVOL		0x03
+#define WM8776_DACRVOL		0x04
+#define WM8776_DACMASTER	0x05
+#define WM8776_PHASESWAP	0x06
+#define WM8776_DACCTRL1		0x07
+#define WM8776_DACMUTE		0x08
+#define WM8776_DACCTRL2		0x09
+#define WM8776_DACIFCTRL	0x0a
+#define WM8776_ADCIFCTRL	0x0b
+#define WM8776_MSTRCTRL		0x0c
+#define WM8776_PWRDOWN		0x0d
+#define WM8776_ADCLVOL		0x0e
+#define WM8776_ADCRVOL		0x0f
+#define WM8776_ALCCTRL1		0x10
+#define WM8776_ALCCTRL2		0x11
+#define WM8776_ALCCTRL3		0x12
+#define WM8776_NOISEGATE	0x13
+#define WM8776_LIMITER		0x14
+#define WM8776_ADCMUX		0x15
+#define WM8776_OUTMUX		0x16
+#define WM8776_RESET		0x17
+
+
+/* HPLVOL/HPRVOL/HPMASTER */
+#define WM8776_HPATT_MASK	0x07f
+#define WM8776_HPZCEN		0x080
+#define WM8776_UPDATE		0x100
+
+/* DACLVOL/DACRVOL/DACMASTER */
+#define WM8776_DATT_MASK	0x0ff
+/*#define WM8776_UPDATE		0x100*/
+
+/* PHASESWAP */
+#define WM8776_PH_MASK		0x003
+
+/* DACCTRL1 */
+#define WM8776_DZCEN		0x001
+#define WM8776_ATC		0x002
+#define WM8776_IZD		0x004
+#define WM8776_TOD		0x008
+#define WM8776_PL_LEFT_MASK	0x030
+#define WM8776_PL_LEFT_MUTE	0x000
+#define WM8776_PL_LEFT_LEFT	0x010
+#define WM8776_PL_LEFT_RIGHT	0x020
+#define WM8776_PL_LEFT_LRMIX	0x030
+#define WM8776_PL_RIGHT_MASK	0x0c0
+#define WM8776_PL_RIGHT_MUTE	0x000
+#define WM8776_PL_RIGHT_LEFT	0x040
+#define WM8776_PL_RIGHT_RIGHT	0x080
+#define WM8776_PL_RIGHT_LRMIX	0x0c0
+
+/* DACMUTE */
+#define WM8776_DMUTE		0x001
+
+/* DACCTRL2 */
+#define WM8776_DEEMPH		0x001
+#define WM8776_DZFM_MASK	0x006
+#define WM8776_DZFM_NONE	0x000
+#define WM8776_DZFM_LR		0x002
+#define WM8776_DZFM_BOTH	0x004
+#define WM8776_DZFM_EITHER	0x006
+
+/* DACIFCTRL */
+#define WM8776_DACFMT_MASK	0x003
+#define WM8776_DACFMT_RJUST	0x000
+#define WM8776_DACFMT_LJUST	0x001
+#define WM8776_DACFMT_I2S	0x002
+#define WM8776_DACFMT_DSP	0x003
+#define WM8776_DACLRP		0x004
+#define WM8776_DACBCP		0x008
+#define WM8776_DACWL_MASK	0x030
+#define WM8776_DACWL_16		0x000
+#define WM8776_DACWL_20		0x010
+#define WM8776_DACWL_24		0x020
+#define WM8776_DACWL_32		0x030
+
+/* ADCIFCTRL */
+#define WM8776_ADCFMT_MASK	0x003
+#define WM8776_ADCFMT_RJUST	0x000
+#define WM8776_ADCFMT_LJUST	0x001
+#define WM8776_ADCFMT_I2S	0x002
+#define WM8776_ADCFMT_DSP	0x003
+#define WM8776_ADCLRP		0x004
+#define WM8776_ADCBCP		0x008
+#define WM8776_ADCWL_MASK	0x030
+#define WM8776_ADCWL_16		0x000
+#define WM8776_ADCWL_20		0x010
+#define WM8776_ADCWL_24		0x020
+#define WM8776_ADCWL_32		0x030
+#define WM8776_ADCMCLK		0x040
+#define WM8776_ADCHPD		0x100
+
+/* MSTRCTRL */
+#define WM8776_ADCRATE_MASK	0x007
+#define WM8776_ADCRATE_256	0x002
+#define WM8776_ADCRATE_384	0x003
+#define WM8776_ADCRATE_512	0x004
+#define WM8776_ADCRATE_768	0x005
+#define WM8776_ADCOSR		0x008
+#define WM8776_DACRATE_MASK	0x070
+#define WM8776_DACRATE_128	0x000
+#define WM8776_DACRATE_192	0x010
+#define WM8776_DACRATE_256	0x020
+#define WM8776_DACRATE_384	0x030
+#define WM8776_DACRATE_512	0x040
+#define WM8776_DACRATE_768	0x050
+#define WM8776_DACMS		0x080
+#define WM8776_ADCMS		0x100
+
+/* PWRDOWN */
+#define WM8776_PDWN		0x001
+#define WM8776_ADCPD		0x002
+#define WM8776_DACPD		0x004
+#define WM8776_HPPD		0x008
+#define WM8776_AINPD		0x040
+
+/* ADCLVOL/ADCRVOL */
+#define WM8776_AGMASK		0x0ff
+#define WM8776_ZCA		0x100
+
+/* ALCCTRL1 */
+#define WM8776_LCT_MASK		0x00f
+#define WM8776_MAXGAIN_MASK	0x070
+#define WM8776_LCSEL_MASK	0x180
+#define WM8776_LCSEL_LIMITER	0x000
+#define WM8776_LCSEL_ALC_RIGHT 0x080
+#define WM8776_LCSEL_ALC_LEFT	0x100
+#define WM8776_LCSEL_ALC_STEREO	0x180
+
+/* ALCCTRL2 */
+#define WM8776_HLD_MASK		0x00f
+#define WM8776_ALCZC		0x080
+#define WM8776_LCEN		0x100
+
+/* ALCCTRL3 */
+#define WM8776_ATK_MASK		0x00f
+#define WM8776_DCY_MASK		0x0f0
+
+/* NOISEGATE */
+#define WM8776_NGAT		0x001
+#define WM8776_NGTH_MASK	0x01c
+
+/* LIMITER */
+#define WM8776_MAXATTEN_MASK	0x00f
+#define WM8776_TRANWIN_MASK	0x070
+
+/* ADCMUX */
+#define WM8776_AMX_MASK		0x01f
+#define WM8776_MUTERA		0x040
+#define WM8776_MUTELA		0x080
+#define WM8776_LRBOTH		0x100
+
+/* OUTMUX */
+#define WM8776_MX_DAC		0x001
+#define WM8776_MX_AUX		0x002
+#define WM8776_MX_BYPASS	0x004
+
+#endif
diff --git a/linux/sound/pci/oxygen/wm8785.h b/linux/sound/pci/oxygen/wm8785.h
new file mode 100644
index 0000000..8c23e31
--- /dev/null
+++ b/linux/sound/pci/oxygen/wm8785.h
@@ -0,0 +1,45 @@
+#ifndef WM8785_H_INCLUDED
+#define WM8785_H_INCLUDED
+
+#define WM8785_R0	0
+#define WM8785_R1	1
+#define WM8785_R2	2
+#define WM8785_R7	7
+
+/* R0 */
+#define WM8785_MCR_MASK		0x007
+#define WM8785_MCR_SLAVE	0x000
+#define WM8785_MCR_MASTER_128	0x001
+#define WM8785_MCR_MASTER_192	0x002
+#define WM8785_MCR_MASTER_256	0x003
+#define WM8785_MCR_MASTER_384	0x004
+#define WM8785_MCR_MASTER_512	0x005
+#define WM8785_MCR_MASTER_768	0x006
+#define WM8785_OSR_MASK		0x018
+#define WM8785_OSR_SINGLE	0x000
+#define WM8785_OSR_DOUBLE	0x008
+#define WM8785_OSR_QUAD		0x010
+#define WM8785_FORMAT_MASK	0x060
+#define WM8785_FORMAT_RJUST	0x000
+#define WM8785_FORMAT_LJUST	0x020
+#define WM8785_FORMAT_I2S	0x040
+#define WM8785_FORMAT_DSP	0x060
+/* R1 */
+#define WM8785_WL_MASK		0x003
+#define WM8785_WL_16		0x000
+#define WM8785_WL_20		0x001
+#define WM8785_WL_24		0x002
+#define WM8785_WL_32		0x003
+#define WM8785_LRP		0x004
+#define WM8785_BCLKINV		0x008
+#define WM8785_LRSWAP		0x010
+#define WM8785_DEVNO_MASK	0x0e0
+/* R2 */
+#define WM8785_HPFR		0x001
+#define WM8785_HPFL		0x002
+#define WM8785_SDODIS		0x004
+#define WM8785_PWRDNR		0x008
+#define WM8785_PWRDNL		0x010
+#define WM8785_TDM_MASK		0x1c0
+
+#endif
diff --git a/linux/sound/pci/oxygen/xonar.h b/linux/sound/pci/oxygen/xonar.h
new file mode 100644
index 0000000..b35343b
--- /dev/null
+++ b/linux/sound/pci/oxygen/xonar.h
@@ -0,0 +1,52 @@
+#ifndef XONAR_H_INCLUDED
+#define XONAR_H_INCLUDED
+
+#include "oxygen.h"
+
+struct xonar_generic {
+	unsigned int anti_pop_delay;
+	u16 output_enable_bit;
+	u8 ext_power_reg;
+	u8 ext_power_int_reg;
+	u8 ext_power_bit;
+	u8 has_power;
+};
+
+struct xonar_hdmi {
+	u8 params[5];
+};
+
+/* generic helper functions */
+
+void xonar_enable_output(struct oxygen *chip);
+void xonar_disable_output(struct oxygen *chip);
+void xonar_init_ext_power(struct oxygen *chip);
+void xonar_init_cs53x1(struct oxygen *chip);
+void xonar_set_cs53x1_params(struct oxygen *chip,
+			     struct snd_pcm_hw_params *params);
+int xonar_gpio_bit_switch_get(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_value *value);
+int xonar_gpio_bit_switch_put(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_value *value);
+
+/* model-specific card drivers */
+
+int get_xonar_pcm179x_model(struct oxygen *chip,
+			    const struct pci_device_id *id);
+int get_xonar_cs43xx_model(struct oxygen *chip,
+			   const struct pci_device_id *id);
+int get_xonar_wm87x6_model(struct oxygen *chip,
+			   const struct pci_device_id *id);
+
+/* HDMI helper functions */
+
+void xonar_hdmi_init(struct oxygen *chip, struct xonar_hdmi *data);
+void xonar_hdmi_cleanup(struct oxygen *chip);
+void xonar_hdmi_resume(struct oxygen *chip, struct xonar_hdmi *hdmi);
+void xonar_hdmi_pcm_hardware_filter(unsigned int channel,
+				    struct snd_pcm_hardware *hardware);
+void xonar_set_hdmi_params(struct oxygen *chip, struct xonar_hdmi *hdmi,
+			   struct snd_pcm_hw_params *params);
+void xonar_hdmi_uart_input(struct oxygen *chip);
+
+#endif
diff --git a/linux/sound/pci/oxygen/xonar_cs43xx.c b/linux/sound/pci/oxygen/xonar_cs43xx.c
new file mode 100644
index 0000000..aa27c31
--- /dev/null
+++ b/linux/sound/pci/oxygen/xonar_cs43xx.c
@@ -0,0 +1,429 @@
+/*
+ * card driver for models with CS4398/CS4362A DACs (Xonar D1/DX)
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Xonar D1/DX
+ * -----------
+ *
+ * CMI8788:
+ *
+ * I²C <-> CS4398 (front)
+ *     <-> CS4362A (surround, center/LFE, back)
+ *
+ * GPI 0 <- external power present (DX only)
+ *
+ * GPIO 0 -> enable output to speakers
+ * GPIO 1 -> enable front panel I/O
+ * GPIO 2 -> M0 of CS5361
+ * GPIO 3 -> M1 of CS5361
+ * GPIO 8 -> route input jack to line-in (0) or mic-in (1)
+ *
+ * CS4398:
+ *
+ * AD0 <- 1
+ * AD1 <- 1
+ *
+ * CS4362A:
+ *
+ * AD0 <- 0
+ *
+ * CM9780:
+ *
+ * GPO 0 -> route line-in (0) or AC97 output (1) to CS5361 input
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/ac97_codec.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "xonar.h"
+#include "cm9780.h"
+#include "cs4398.h"
+#include "cs4362a.h"
+
+#define GPI_EXT_POWER		0x01
+#define GPIO_D1_OUTPUT_ENABLE	0x0001
+#define GPIO_D1_FRONT_PANEL	0x0002
+#define GPIO_D1_INPUT_ROUTE	0x0100
+
+#define I2C_DEVICE_CS4398	0x9e	/* 10011, AD1=1, AD0=1, /W=0 */
+#define I2C_DEVICE_CS4362A	0x30	/* 001100, AD0=0, /W=0 */
+
+struct xonar_cs43xx {
+	struct xonar_generic generic;
+	u8 cs4398_regs[8];
+	u8 cs4362a_regs[15];
+};
+
+static void cs4398_write(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	oxygen_write_i2c(chip, I2C_DEVICE_CS4398, reg, value);
+	if (reg < ARRAY_SIZE(data->cs4398_regs))
+		data->cs4398_regs[reg] = value;
+}
+
+static void cs4398_write_cached(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	if (value != data->cs4398_regs[reg])
+		cs4398_write(chip, reg, value);
+}
+
+static void cs4362a_write(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	oxygen_write_i2c(chip, I2C_DEVICE_CS4362A, reg, value);
+	if (reg < ARRAY_SIZE(data->cs4362a_regs))
+		data->cs4362a_regs[reg] = value;
+}
+
+static void cs4362a_write_cached(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	if (value != data->cs4362a_regs[reg])
+		cs4362a_write(chip, reg, value);
+}
+
+static void cs43xx_registers_init(struct oxygen *chip)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+	unsigned int i;
+
+	/* set CPEN (control port mode) and power down */
+	cs4398_write(chip, 8, CS4398_CPEN | CS4398_PDN);
+	cs4362a_write(chip, 0x01, CS4362A_PDN | CS4362A_CPEN);
+	/* configure */
+	cs4398_write(chip, 2, data->cs4398_regs[2]);
+	cs4398_write(chip, 3, CS4398_ATAPI_B_R | CS4398_ATAPI_A_L);
+	cs4398_write(chip, 4, data->cs4398_regs[4]);
+	cs4398_write(chip, 5, data->cs4398_regs[5]);
+	cs4398_write(chip, 6, data->cs4398_regs[6]);
+	cs4398_write(chip, 7, data->cs4398_regs[7]);
+	cs4362a_write(chip, 0x02, CS4362A_DIF_LJUST);
+	cs4362a_write(chip, 0x03, CS4362A_MUTEC_6 | CS4362A_AMUTE |
+		      CS4362A_RMP_UP | CS4362A_ZERO_CROSS | CS4362A_SOFT_RAMP);
+	cs4362a_write(chip, 0x04, data->cs4362a_regs[0x04]);
+	cs4362a_write(chip, 0x05, 0);
+	for (i = 6; i <= 14; ++i)
+		cs4362a_write(chip, i, data->cs4362a_regs[i]);
+	/* clear power down */
+	cs4398_write(chip, 8, CS4398_CPEN);
+	cs4362a_write(chip, 0x01, CS4362A_CPEN);
+}
+
+static void xonar_d1_init(struct oxygen *chip)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	data->generic.anti_pop_delay = 800;
+	data->generic.output_enable_bit = GPIO_D1_OUTPUT_ENABLE;
+	data->cs4398_regs[2] =
+		CS4398_FM_SINGLE | CS4398_DEM_NONE | CS4398_DIF_LJUST;
+	data->cs4398_regs[4] = CS4398_MUTEP_LOW |
+		CS4398_MUTE_B | CS4398_MUTE_A | CS4398_PAMUTE;
+	data->cs4398_regs[5] = 60 * 2;
+	data->cs4398_regs[6] = 60 * 2;
+	data->cs4398_regs[7] = CS4398_RMP_DN | CS4398_RMP_UP |
+		CS4398_ZERO_CROSS | CS4398_SOFT_RAMP;
+	data->cs4362a_regs[4] = CS4362A_RMP_DN | CS4362A_DEM_NONE;
+	data->cs4362a_regs[6] = CS4362A_FM_SINGLE |
+		CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L;
+	data->cs4362a_regs[7] = 60 | CS4362A_MUTE;
+	data->cs4362a_regs[8] = 60 | CS4362A_MUTE;
+	data->cs4362a_regs[9] = data->cs4362a_regs[6];
+	data->cs4362a_regs[10] = 60 | CS4362A_MUTE;
+	data->cs4362a_regs[11] = 60 | CS4362A_MUTE;
+	data->cs4362a_regs[12] = data->cs4362a_regs[6];
+	data->cs4362a_regs[13] = 60 | CS4362A_MUTE;
+	data->cs4362a_regs[14] = 60 | CS4362A_MUTE;
+
+	oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS,
+		       OXYGEN_2WIRE_LENGTH_8 |
+		       OXYGEN_2WIRE_INTERRUPT_MASK |
+		       OXYGEN_2WIRE_SPEED_FAST);
+
+	cs43xx_registers_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
+			  GPIO_D1_FRONT_PANEL | GPIO_D1_INPUT_ROUTE);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA,
+			    GPIO_D1_FRONT_PANEL | GPIO_D1_INPUT_ROUTE);
+
+	oxygen_ac97_set_bits(chip, 0, CM9780_JACK, CM9780_FMIC2MIC);
+
+	xonar_init_cs53x1(chip);
+	xonar_enable_output(chip);
+
+	snd_component_add(chip->card, "CS4398");
+	snd_component_add(chip->card, "CS4362A");
+	snd_component_add(chip->card, "CS5361");
+}
+
+static void xonar_dx_init(struct oxygen *chip)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+
+	data->generic.ext_power_reg = OXYGEN_GPI_DATA;
+	data->generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK;
+	data->generic.ext_power_bit = GPI_EXT_POWER;
+	xonar_init_ext_power(chip);
+	xonar_d1_init(chip);
+}
+
+static void xonar_d1_cleanup(struct oxygen *chip)
+{
+	xonar_disable_output(chip);
+	cs4362a_write(chip, 0x01, CS4362A_PDN | CS4362A_CPEN);
+	oxygen_clear_bits8(chip, OXYGEN_FUNCTION, OXYGEN_FUNCTION_RESET_CODEC);
+}
+
+static void xonar_d1_suspend(struct oxygen *chip)
+{
+	xonar_d1_cleanup(chip);
+}
+
+static void xonar_d1_resume(struct oxygen *chip)
+{
+	oxygen_set_bits8(chip, OXYGEN_FUNCTION, OXYGEN_FUNCTION_RESET_CODEC);
+	msleep(1);
+	cs43xx_registers_init(chip);
+	xonar_enable_output(chip);
+}
+
+static void set_cs43xx_params(struct oxygen *chip,
+			      struct snd_pcm_hw_params *params)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+	u8 cs4398_fm, cs4362a_fm;
+
+	if (params_rate(params) <= 50000) {
+		cs4398_fm = CS4398_FM_SINGLE;
+		cs4362a_fm = CS4362A_FM_SINGLE;
+	} else if (params_rate(params) <= 100000) {
+		cs4398_fm = CS4398_FM_DOUBLE;
+		cs4362a_fm = CS4362A_FM_DOUBLE;
+	} else {
+		cs4398_fm = CS4398_FM_QUAD;
+		cs4362a_fm = CS4362A_FM_QUAD;
+	}
+	cs4398_fm |= CS4398_DEM_NONE | CS4398_DIF_LJUST;
+	cs4398_write_cached(chip, 2, cs4398_fm);
+	cs4362a_fm |= data->cs4362a_regs[6] & ~CS4362A_FM_MASK;
+	cs4362a_write_cached(chip, 6, cs4362a_fm);
+	cs4362a_write_cached(chip, 12, cs4362a_fm);
+	cs4362a_fm &= CS4362A_FM_MASK;
+	cs4362a_fm |= data->cs4362a_regs[9] & ~CS4362A_FM_MASK;
+	cs4362a_write_cached(chip, 9, cs4362a_fm);
+}
+
+static void update_cs4362a_volumes(struct oxygen *chip)
+{
+	unsigned int i;
+	u8 mute;
+
+	mute = chip->dac_mute ? CS4362A_MUTE : 0;
+	for (i = 0; i < 6; ++i)
+		cs4362a_write_cached(chip, 7 + i + i / 2,
+				     (127 - chip->dac_volume[2 + i]) | mute);
+}
+
+static void update_cs43xx_volume(struct oxygen *chip)
+{
+	cs4398_write_cached(chip, 5, (127 - chip->dac_volume[0]) * 2);
+	cs4398_write_cached(chip, 6, (127 - chip->dac_volume[1]) * 2);
+	update_cs4362a_volumes(chip);
+}
+
+static void update_cs43xx_mute(struct oxygen *chip)
+{
+	u8 reg;
+
+	reg = CS4398_MUTEP_LOW | CS4398_PAMUTE;
+	if (chip->dac_mute)
+		reg |= CS4398_MUTE_B | CS4398_MUTE_A;
+	cs4398_write_cached(chip, 4, reg);
+	update_cs4362a_volumes(chip);
+}
+
+static void update_cs43xx_center_lfe_mix(struct oxygen *chip, bool mixed)
+{
+	struct xonar_cs43xx *data = chip->model_data;
+	u8 reg;
+
+	reg = data->cs4362a_regs[9] & ~CS4362A_ATAPI_MASK;
+	if (mixed)
+		reg |= CS4362A_ATAPI_B_LR | CS4362A_ATAPI_A_LR;
+	else
+		reg |= CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L;
+	cs4362a_write_cached(chip, 9, reg);
+}
+
+static const struct snd_kcontrol_new front_panel_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Front Panel Switch",
+	.info = snd_ctl_boolean_mono_info,
+	.get = xonar_gpio_bit_switch_get,
+	.put = xonar_gpio_bit_switch_put,
+	.private_value = GPIO_D1_FRONT_PANEL,
+};
+
+static int rolloff_info(struct snd_kcontrol *ctl,
+			struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = {
+		"Fast Roll-off", "Slow Roll-off"
+	};
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 2;
+	if (info->value.enumerated.item >= 2)
+		info->value.enumerated.item = 1;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int rolloff_get(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_cs43xx *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		(data->cs4398_regs[7] & CS4398_FILT_SEL) != 0;
+	return 0;
+}
+
+static int rolloff_put(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_cs43xx *data = chip->model_data;
+	int changed;
+	u8 reg;
+
+	mutex_lock(&chip->mutex);
+	reg = data->cs4398_regs[7];
+	if (value->value.enumerated.item[0])
+		reg |= CS4398_FILT_SEL;
+	else
+		reg &= ~CS4398_FILT_SEL;
+	changed = reg != data->cs4398_regs[7];
+	if (changed) {
+		cs4398_write(chip, 7, reg);
+		if (reg & CS4398_FILT_SEL)
+			reg = data->cs4362a_regs[0x04] | CS4362A_FILT_SEL;
+		else
+			reg = data->cs4362a_regs[0x04] & ~CS4362A_FILT_SEL;
+		cs4362a_write(chip, 0x04, reg);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new rolloff_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DAC Filter Playback Enum",
+	.info = rolloff_info,
+	.get = rolloff_get,
+	.put = rolloff_put,
+};
+
+static void xonar_d1_line_mic_ac97_switch(struct oxygen *chip,
+					  unsigned int reg, unsigned int mute)
+{
+	if (reg == AC97_LINE) {
+		spin_lock_irq(&chip->reg_lock);
+		oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+				      mute ? GPIO_D1_INPUT_ROUTE : 0,
+				      GPIO_D1_INPUT_ROUTE);
+		spin_unlock_irq(&chip->reg_lock);
+	}
+}
+
+static const DECLARE_TLV_DB_SCALE(cs4362a_db_scale, -6000, 100, 0);
+
+static int xonar_d1_mixer_init(struct oxygen *chip)
+{
+	int err;
+
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&front_panel_switch, chip));
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&rolloff_control, chip));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static const struct oxygen_model model_xonar_d1 = {
+	.longname = "Asus Virtuoso 100",
+	.chip = "AV200",
+	.init = xonar_d1_init,
+	.mixer_init = xonar_d1_mixer_init,
+	.cleanup = xonar_d1_cleanup,
+	.suspend = xonar_d1_suspend,
+	.resume = xonar_d1_resume,
+	.get_i2s_mclk = oxygen_default_i2s_mclk,
+	.set_dac_params = set_cs43xx_params,
+	.set_adc_params = xonar_set_cs53x1_params,
+	.update_dac_volume = update_cs43xx_volume,
+	.update_dac_mute = update_cs43xx_mute,
+	.update_center_lfe_mix = update_cs43xx_center_lfe_mix,
+	.ac97_switch = xonar_d1_line_mic_ac97_switch,
+	.dac_tlv = cs4362a_db_scale,
+	.model_data_size = sizeof(struct xonar_cs43xx),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_2,
+	.dac_channels = 8,
+	.dac_volume_min = 127 - 60,
+	.dac_volume_max = 127,
+	.function_flags = OXYGEN_FUNCTION_2WIRE,
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+int __devinit get_xonar_cs43xx_model(struct oxygen *chip,
+				     const struct pci_device_id *id)
+{
+	switch (id->subdevice) {
+	case 0x834f:
+		chip->model = model_xonar_d1;
+		chip->model.shortname = "Xonar D1";
+		break;
+	case 0x8275:
+	case 0x8327:
+		chip->model = model_xonar_d1;
+		chip->model.shortname = "Xonar DX";
+		chip->model.init = xonar_dx_init;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
diff --git a/linux/sound/pci/oxygen/xonar_hdmi.c b/linux/sound/pci/oxygen/xonar_hdmi.c
new file mode 100644
index 0000000..b12db1f
--- /dev/null
+++ b/linux/sound/pci/oxygen/xonar_hdmi.c
@@ -0,0 +1,128 @@
+/*
+ * helper functions for HDMI models (Xonar HDAV1.3)
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/asoundef.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "xonar.h"
+
+static void hdmi_write_command(struct oxygen *chip, u8 command,
+			       unsigned int count, const u8 *params)
+{
+	unsigned int i;
+	u8 checksum;
+
+	oxygen_write_uart(chip, 0xfb);
+	oxygen_write_uart(chip, 0xef);
+	oxygen_write_uart(chip, command);
+	oxygen_write_uart(chip, count);
+	for (i = 0; i < count; ++i)
+		oxygen_write_uart(chip, params[i]);
+	checksum = 0xfb + 0xef + command + count;
+	for (i = 0; i < count; ++i)
+		checksum += params[i];
+	oxygen_write_uart(chip, checksum);
+}
+
+static void xonar_hdmi_init_commands(struct oxygen *chip,
+				     struct xonar_hdmi *hdmi)
+{
+	u8 param;
+
+	oxygen_reset_uart(chip);
+	param = 0;
+	hdmi_write_command(chip, 0x61, 1, &param);
+	param = 1;
+	hdmi_write_command(chip, 0x74, 1, &param);
+	hdmi_write_command(chip, 0x54, 5, hdmi->params);
+}
+
+void xonar_hdmi_init(struct oxygen *chip, struct xonar_hdmi *hdmi)
+{
+	hdmi->params[1] = IEC958_AES3_CON_FS_48000;
+	hdmi->params[4] = 1;
+	xonar_hdmi_init_commands(chip, hdmi);
+}
+
+void xonar_hdmi_cleanup(struct oxygen *chip)
+{
+	u8 param = 0;
+
+	hdmi_write_command(chip, 0x74, 1, &param);
+}
+
+void xonar_hdmi_resume(struct oxygen *chip, struct xonar_hdmi *hdmi)
+{
+	xonar_hdmi_init_commands(chip, hdmi);
+}
+
+void xonar_hdmi_pcm_hardware_filter(unsigned int channel,
+				    struct snd_pcm_hardware *hardware)
+{
+	if (channel == PCM_MULTICH) {
+		hardware->rates = SNDRV_PCM_RATE_44100 |
+				  SNDRV_PCM_RATE_48000 |
+				  SNDRV_PCM_RATE_96000 |
+				  SNDRV_PCM_RATE_192000;
+		hardware->rate_min = 44100;
+	}
+}
+
+void xonar_set_hdmi_params(struct oxygen *chip, struct xonar_hdmi *hdmi,
+			   struct snd_pcm_hw_params *params)
+{
+	hdmi->params[0] = 0; /* 1 = non-audio */
+	switch (params_rate(params)) {
+	case 44100:
+		hdmi->params[1] = IEC958_AES3_CON_FS_44100;
+		break;
+	case 48000:
+		hdmi->params[1] = IEC958_AES3_CON_FS_48000;
+		break;
+	default: /* 96000 */
+		hdmi->params[1] = IEC958_AES3_CON_FS_96000;
+		break;
+	case 192000:
+		hdmi->params[1] = IEC958_AES3_CON_FS_192000;
+		break;
+	}
+	hdmi->params[2] = params_channels(params) / 2 - 1;
+	if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE)
+		hdmi->params[3] = 0;
+	else
+		hdmi->params[3] = 0xc0;
+	hdmi->params[4] = 1; /* ? */
+	hdmi_write_command(chip, 0x54, 5, hdmi->params);
+}
+
+void xonar_hdmi_uart_input(struct oxygen *chip)
+{
+	if (chip->uart_input_count >= 2 &&
+	    chip->uart_input[chip->uart_input_count - 2] == 'O' &&
+	    chip->uart_input[chip->uart_input_count - 1] == 'K') {
+		printk(KERN_DEBUG "message from HDMI chip received:\n");
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+				     chip->uart_input, chip->uart_input_count);
+		chip->uart_input_count = 0;
+	}
+}
diff --git a/linux/sound/pci/oxygen/xonar_lib.c b/linux/sound/pci/oxygen/xonar_lib.c
new file mode 100644
index 0000000..b3ff713
--- /dev/null
+++ b/linux/sound/pci/oxygen/xonar_lib.c
@@ -0,0 +1,132 @@
+/*
+ * helper functions for Asus Xonar cards
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "xonar.h"
+
+
+#define GPIO_CS53x1_M_MASK	0x000c
+#define GPIO_CS53x1_M_SINGLE	0x0000
+#define GPIO_CS53x1_M_DOUBLE	0x0004
+#define GPIO_CS53x1_M_QUAD	0x0008
+
+
+void xonar_enable_output(struct oxygen *chip)
+{
+	struct xonar_generic *data = chip->model_data;
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, data->output_enable_bit);
+	msleep(data->anti_pop_delay);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, data->output_enable_bit);
+}
+
+void xonar_disable_output(struct oxygen *chip)
+{
+	struct xonar_generic *data = chip->model_data;
+
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, data->output_enable_bit);
+}
+
+static void xonar_ext_power_gpio_changed(struct oxygen *chip)
+{
+	struct xonar_generic *data = chip->model_data;
+	u8 has_power;
+
+	has_power = !!(oxygen_read8(chip, data->ext_power_reg)
+		       & data->ext_power_bit);
+	if (has_power != data->has_power) {
+		data->has_power = has_power;
+		if (has_power) {
+			snd_printk(KERN_NOTICE "power restored\n");
+		} else {
+			snd_printk(KERN_CRIT
+				   "Hey! Don't unplug the power cable!\n");
+			/* TODO: stop PCMs */
+		}
+	}
+}
+
+void xonar_init_ext_power(struct oxygen *chip)
+{
+	struct xonar_generic *data = chip->model_data;
+
+	oxygen_set_bits8(chip, data->ext_power_int_reg,
+			 data->ext_power_bit);
+	chip->interrupt_mask |= OXYGEN_INT_GPIO;
+	chip->model.gpio_changed = xonar_ext_power_gpio_changed;
+	data->has_power = !!(oxygen_read8(chip, data->ext_power_reg)
+			     & data->ext_power_bit);
+}
+
+void xonar_init_cs53x1(struct oxygen *chip)
+{
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CS53x1_M_MASK);
+	oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+			      GPIO_CS53x1_M_SINGLE, GPIO_CS53x1_M_MASK);
+}
+
+void xonar_set_cs53x1_params(struct oxygen *chip,
+			     struct snd_pcm_hw_params *params)
+{
+	unsigned int value;
+
+	if (params_rate(params) <= 54000)
+		value = GPIO_CS53x1_M_SINGLE;
+	else if (params_rate(params) <= 108000)
+		value = GPIO_CS53x1_M_DOUBLE;
+	else
+		value = GPIO_CS53x1_M_QUAD;
+	oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+			      value, GPIO_CS53x1_M_MASK);
+}
+
+int xonar_gpio_bit_switch_get(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 bit = ctl->private_value;
+
+	value->value.integer.value[0] =
+		!!(oxygen_read16(chip, OXYGEN_GPIO_DATA) & bit);
+	return 0;
+}
+
+int xonar_gpio_bit_switch_put(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 bit = ctl->private_value;
+	u16 old_bits, new_bits;
+	int changed;
+
+	spin_lock_irq(&chip->reg_lock);
+	old_bits = oxygen_read16(chip, OXYGEN_GPIO_DATA);
+	if (value->value.integer.value[0])
+		new_bits = old_bits | bit;
+	else
+		new_bits = old_bits & ~bit;
+	changed = new_bits != old_bits;
+	if (changed)
+		oxygen_write16(chip, OXYGEN_GPIO_DATA, new_bits);
+	spin_unlock_irq(&chip->reg_lock);
+	return changed;
+}
diff --git a/linux/sound/pci/oxygen/xonar_pcm179x.c b/linux/sound/pci/oxygen/xonar_pcm179x.c
new file mode 100644
index 0000000..d491fd6
--- /dev/null
+++ b/linux/sound/pci/oxygen/xonar_pcm179x.c
@@ -0,0 +1,1124 @@
+/*
+ * card driver for models with PCM1796 DACs (Xonar D2/D2X/HDAV1.3/ST/STX)
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Xonar D2/D2X
+ * ------------
+ *
+ * CMI8788:
+ *
+ * SPI 0 -> 1st PCM1796 (front)
+ * SPI 1 -> 2nd PCM1796 (surround)
+ * SPI 2 -> 3rd PCM1796 (center/LFE)
+ * SPI 4 -> 4th PCM1796 (back)
+ *
+ * GPIO 2 -> M0 of CS5381
+ * GPIO 3 -> M1 of CS5381
+ * GPIO 5 <- external power present (D2X only)
+ * GPIO 7 -> ALT
+ * GPIO 8 -> enable output to speakers
+ *
+ * CM9780:
+ *
+ * GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input
+ */
+
+/*
+ * Xonar HDAV1.3 (Deluxe)
+ * ----------------------
+ *
+ * CMI8788:
+ *
+ * I²C <-> PCM1796 (front)
+ *
+ * GPI 0 <- external power present
+ *
+ * GPIO 0 -> enable output to speakers
+ * GPIO 2 -> M0 of CS5381
+ * GPIO 3 -> M1 of CS5381
+ * GPIO 8 -> route input jack to line-in (0) or mic-in (1)
+ *
+ * TXD -> HDMI controller
+ * RXD <- HDMI controller
+ *
+ * PCM1796 front: AD1,0 <- 0,0
+ *
+ * CM9780:
+ *
+ * GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input
+ *
+ * no daughterboard
+ * ----------------
+ *
+ * GPIO 4 <- 1
+ *
+ * H6 daughterboard
+ * ----------------
+ *
+ * GPIO 4 <- 0
+ * GPIO 5 <- 0
+ *
+ * I²C <-> PCM1796 (surround)
+ *     <-> PCM1796 (center/LFE)
+ *     <-> PCM1796 (back)
+ *
+ * PCM1796 surround:   AD1,0 <- 0,1
+ * PCM1796 center/LFE: AD1,0 <- 1,0
+ * PCM1796 back:       AD1,0 <- 1,1
+ *
+ * unknown daughterboard
+ * ---------------------
+ *
+ * GPIO 4 <- 0
+ * GPIO 5 <- 1
+ *
+ * I²C <-> CS4362A (surround, center/LFE, back)
+ *
+ * CS4362A: AD0 <- 0
+ */
+
+/*
+ * Xonar Essence ST (Deluxe)/STX
+ * -----------------------------
+ *
+ * CMI8788:
+ *
+ * I²C <-> PCM1792A
+ *     <-> CS2000 (ST only)
+ *
+ * ADC1 MCLK -> REF_CLK of CS2000 (ST only)
+ *
+ * GPI 0 <- external power present (STX only)
+ *
+ * GPIO 0 -> enable output to speakers
+ * GPIO 1 -> route HP to front panel (0) or rear jack (1)
+ * GPIO 2 -> M0 of CS5381
+ * GPIO 3 -> M1 of CS5381
+ * GPIO 7 -> route output to speaker jacks (0) or HP (1)
+ * GPIO 8 -> route input jack to line-in (0) or mic-in (1)
+ *
+ * PCM1792A:
+ *
+ * AD1,0 <- 0,0
+ * SCK <- CLK_OUT of CS2000 (ST only)
+ *
+ * CS2000:
+ *
+ * AD0 <- 0
+ *
+ * CM9780:
+ *
+ * GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input
+ *
+ * H6 daughterboard
+ * ----------------
+ *
+ * GPIO 4 <- 0
+ * GPIO 5 <- 0
+ */
+
+/*
+ * Xonar HDAV1.3 Slim
+ * ------------------
+ *
+ * CMI8788:
+ *
+ * GPIO 1 -> enable output
+ *
+ * TXD -> HDMI controller
+ * RXD <- HDMI controller
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <sound/ac97_codec.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "xonar.h"
+#include "cm9780.h"
+#include "pcm1796.h"
+#include "cs2000.h"
+
+
+#define GPIO_D2X_EXT_POWER	0x0020
+#define GPIO_D2_ALT		0x0080
+#define GPIO_D2_OUTPUT_ENABLE	0x0100
+
+#define GPI_EXT_POWER		0x01
+#define GPIO_INPUT_ROUTE	0x0100
+
+#define GPIO_HDAV_OUTPUT_ENABLE	0x0001
+
+#define GPIO_DB_MASK		0x0030
+#define GPIO_DB_H6		0x0000
+
+#define GPIO_ST_OUTPUT_ENABLE	0x0001
+#define GPIO_ST_HP_REAR		0x0002
+#define GPIO_ST_HP		0x0080
+
+#define I2C_DEVICE_PCM1796(i)	(0x98 + ((i) << 1))	/* 10011, ii, /W=0 */
+#define I2C_DEVICE_CS2000	0x9c			/* 100111, 0, /W=0 */
+
+#define PCM1796_REG_BASE	16
+
+
+struct xonar_pcm179x {
+	struct xonar_generic generic;
+	unsigned int dacs;
+	u8 pcm1796_regs[4][5];
+	unsigned int current_rate;
+	bool os_128;
+	bool hp_active;
+	s8 hp_gain_offset;
+	bool has_cs2000;
+	u8 cs2000_fun_cfg_1;
+};
+
+struct xonar_hdav {
+	struct xonar_pcm179x pcm179x;
+	struct xonar_hdmi hdmi;
+};
+
+
+static inline void pcm1796_write_spi(struct oxygen *chip, unsigned int codec,
+				     u8 reg, u8 value)
+{
+	/* maps ALSA channel pair number to SPI output */
+	static const u8 codec_map[4] = {
+		0, 1, 2, 4
+	};
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER  |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (codec_map[codec] << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
+			 (reg << 8) | value);
+}
+
+static inline void pcm1796_write_i2c(struct oxygen *chip, unsigned int codec,
+				     u8 reg, u8 value)
+{
+	oxygen_write_i2c(chip, I2C_DEVICE_PCM1796(codec), reg, value);
+}
+
+static void pcm1796_write(struct oxygen *chip, unsigned int codec,
+			  u8 reg, u8 value)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	if ((chip->model.function_flags & OXYGEN_FUNCTION_2WIRE_SPI_MASK) ==
+	    OXYGEN_FUNCTION_SPI)
+		pcm1796_write_spi(chip, codec, reg, value);
+	else
+		pcm1796_write_i2c(chip, codec, reg, value);
+	if ((unsigned int)(reg - PCM1796_REG_BASE)
+	    < ARRAY_SIZE(data->pcm1796_regs[codec]))
+		data->pcm1796_regs[codec][reg - PCM1796_REG_BASE] = value;
+}
+
+static void pcm1796_write_cached(struct oxygen *chip, unsigned int codec,
+				 u8 reg, u8 value)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	if (value != data->pcm1796_regs[codec][reg - PCM1796_REG_BASE])
+		pcm1796_write(chip, codec, reg, value);
+}
+
+static void cs2000_write(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	oxygen_write_i2c(chip, I2C_DEVICE_CS2000, reg, value);
+	if (reg == CS2000_FUN_CFG_1)
+		data->cs2000_fun_cfg_1 = value;
+}
+
+static void cs2000_write_cached(struct oxygen *chip, u8 reg, u8 value)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	if (reg != CS2000_FUN_CFG_1 ||
+	    value != data->cs2000_fun_cfg_1)
+		cs2000_write(chip, reg, value);
+}
+
+static void pcm1796_registers_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int i;
+	s8 gain_offset;
+
+	gain_offset = data->hp_active ? data->hp_gain_offset : 0;
+	for (i = 0; i < data->dacs; ++i) {
+		/* set ATLD before ATL/ATR */
+		pcm1796_write(chip, i, 18,
+			      data->pcm1796_regs[0][18 - PCM1796_REG_BASE]);
+		pcm1796_write(chip, i, 16, chip->dac_volume[i * 2]
+			      + gain_offset);
+		pcm1796_write(chip, i, 17, chip->dac_volume[i * 2 + 1]
+			      + gain_offset);
+		pcm1796_write(chip, i, 19,
+			      data->pcm1796_regs[0][19 - PCM1796_REG_BASE]);
+		pcm1796_write(chip, i, 20,
+			      data->pcm1796_regs[0][20 - PCM1796_REG_BASE]);
+		pcm1796_write(chip, i, 21, 0);
+	}
+}
+
+static void pcm1796_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->pcm1796_regs[0][18 - PCM1796_REG_BASE] = PCM1796_MUTE |
+		PCM1796_DMF_DISABLED | PCM1796_FMT_24_LJUST | PCM1796_ATLD;
+	data->pcm1796_regs[0][19 - PCM1796_REG_BASE] =
+		PCM1796_FLT_SHARP | PCM1796_ATS_1;
+	data->pcm1796_regs[0][20 - PCM1796_REG_BASE] = PCM1796_OS_64;
+	pcm1796_registers_init(chip);
+	data->current_rate = 48000;
+}
+
+static void xonar_d2_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->generic.anti_pop_delay = 300;
+	data->generic.output_enable_bit = GPIO_D2_OUTPUT_ENABLE;
+	data->dacs = 4;
+
+	pcm1796_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_D2_ALT);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_D2_ALT);
+
+	oxygen_ac97_set_bits(chip, 0, CM9780_JACK, CM9780_FMIC2MIC);
+
+	xonar_init_cs53x1(chip);
+	xonar_enable_output(chip);
+
+	snd_component_add(chip->card, "PCM1796");
+	snd_component_add(chip->card, "CS5381");
+}
+
+static void xonar_d2x_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->generic.ext_power_reg = OXYGEN_GPIO_DATA;
+	data->generic.ext_power_int_reg = OXYGEN_GPIO_INTERRUPT_MASK;
+	data->generic.ext_power_bit = GPIO_D2X_EXT_POWER;
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_D2X_EXT_POWER);
+	xonar_init_ext_power(chip);
+	xonar_d2_init(chip);
+}
+
+static void xonar_hdav_init(struct oxygen *chip)
+{
+	struct xonar_hdav *data = chip->model_data;
+
+	oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS,
+		       OXYGEN_2WIRE_LENGTH_8 |
+		       OXYGEN_2WIRE_INTERRUPT_MASK |
+		       OXYGEN_2WIRE_SPEED_FAST);
+
+	data->pcm179x.generic.anti_pop_delay = 100;
+	data->pcm179x.generic.output_enable_bit = GPIO_HDAV_OUTPUT_ENABLE;
+	data->pcm179x.generic.ext_power_reg = OXYGEN_GPI_DATA;
+	data->pcm179x.generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK;
+	data->pcm179x.generic.ext_power_bit = GPI_EXT_POWER;
+	data->pcm179x.dacs = chip->model.private_data ? 4 : 1;
+
+	pcm1796_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_INPUT_ROUTE);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_INPUT_ROUTE);
+
+	xonar_init_cs53x1(chip);
+	xonar_init_ext_power(chip);
+	xonar_hdmi_init(chip, &data->hdmi);
+	xonar_enable_output(chip);
+
+	snd_component_add(chip->card, "PCM1796");
+	snd_component_add(chip->card, "CS5381");
+}
+
+static void xonar_st_init_i2c(struct oxygen *chip)
+{
+	oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS,
+		       OXYGEN_2WIRE_LENGTH_8 |
+		       OXYGEN_2WIRE_INTERRUPT_MASK |
+		       OXYGEN_2WIRE_SPEED_FAST);
+}
+
+static void xonar_st_init_common(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->generic.output_enable_bit = GPIO_ST_OUTPUT_ENABLE;
+	data->dacs = chip->model.private_data ? 4 : 1;
+	data->hp_gain_offset = 2*-18;
+
+	pcm1796_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
+			  GPIO_INPUT_ROUTE | GPIO_ST_HP_REAR | GPIO_ST_HP);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA,
+			    GPIO_INPUT_ROUTE | GPIO_ST_HP_REAR | GPIO_ST_HP);
+
+	xonar_init_cs53x1(chip);
+	xonar_enable_output(chip);
+
+	snd_component_add(chip->card, "PCM1792A");
+	snd_component_add(chip->card, "CS5381");
+}
+
+static void cs2000_registers_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	cs2000_write(chip, CS2000_GLOBAL_CFG, CS2000_FREEZE);
+	cs2000_write(chip, CS2000_DEV_CTRL, 0);
+	cs2000_write(chip, CS2000_DEV_CFG_1,
+		     CS2000_R_MOD_SEL_1 |
+		     (0 << CS2000_R_SEL_SHIFT) |
+		     CS2000_AUX_OUT_SRC_REF_CLK |
+		     CS2000_EN_DEV_CFG_1);
+	cs2000_write(chip, CS2000_DEV_CFG_2,
+		     (0 << CS2000_LOCK_CLK_SHIFT) |
+		     CS2000_FRAC_N_SRC_STATIC);
+	cs2000_write(chip, CS2000_RATIO_0 + 0, 0x00); /* 1.0 */
+	cs2000_write(chip, CS2000_RATIO_0 + 1, 0x10);
+	cs2000_write(chip, CS2000_RATIO_0 + 2, 0x00);
+	cs2000_write(chip, CS2000_RATIO_0 + 3, 0x00);
+	cs2000_write(chip, CS2000_FUN_CFG_1, data->cs2000_fun_cfg_1);
+	cs2000_write(chip, CS2000_FUN_CFG_2, 0);
+	cs2000_write(chip, CS2000_GLOBAL_CFG, CS2000_EN_DEV_CFG_2);
+}
+
+static void xonar_st_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->generic.anti_pop_delay = 100;
+	data->has_cs2000 = 1;
+	data->cs2000_fun_cfg_1 = CS2000_REF_CLK_DIV_1;
+
+	oxygen_write16(chip, OXYGEN_I2S_A_FORMAT,
+		       OXYGEN_RATE_48000 | OXYGEN_I2S_FORMAT_I2S |
+		       OXYGEN_I2S_MCLK_128 | OXYGEN_I2S_BITS_16 |
+		       OXYGEN_I2S_MASTER | OXYGEN_I2S_BCLK_64);
+
+	xonar_st_init_i2c(chip);
+	cs2000_registers_init(chip);
+	xonar_st_init_common(chip);
+
+	snd_component_add(chip->card, "CS2000");
+}
+
+static void xonar_stx_init(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	xonar_st_init_i2c(chip);
+	data->generic.anti_pop_delay = 800;
+	data->generic.ext_power_reg = OXYGEN_GPI_DATA;
+	data->generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK;
+	data->generic.ext_power_bit = GPI_EXT_POWER;
+	xonar_init_ext_power(chip);
+	xonar_st_init_common(chip);
+}
+
+static void xonar_d2_cleanup(struct oxygen *chip)
+{
+	xonar_disable_output(chip);
+}
+
+static void xonar_hdav_cleanup(struct oxygen *chip)
+{
+	xonar_hdmi_cleanup(chip);
+	xonar_disable_output(chip);
+	msleep(2);
+}
+
+static void xonar_st_cleanup(struct oxygen *chip)
+{
+	xonar_disable_output(chip);
+}
+
+static void xonar_d2_suspend(struct oxygen *chip)
+{
+	xonar_d2_cleanup(chip);
+}
+
+static void xonar_hdav_suspend(struct oxygen *chip)
+{
+	xonar_hdav_cleanup(chip);
+}
+
+static void xonar_st_suspend(struct oxygen *chip)
+{
+	xonar_st_cleanup(chip);
+}
+
+static void xonar_d2_resume(struct oxygen *chip)
+{
+	pcm1796_registers_init(chip);
+	xonar_enable_output(chip);
+}
+
+static void xonar_hdav_resume(struct oxygen *chip)
+{
+	struct xonar_hdav *data = chip->model_data;
+
+	pcm1796_registers_init(chip);
+	xonar_hdmi_resume(chip, &data->hdmi);
+	xonar_enable_output(chip);
+}
+
+static void xonar_stx_resume(struct oxygen *chip)
+{
+	pcm1796_registers_init(chip);
+	xonar_enable_output(chip);
+}
+
+static void xonar_st_resume(struct oxygen *chip)
+{
+	cs2000_registers_init(chip);
+	xonar_stx_resume(chip);
+}
+
+static unsigned int mclk_from_rate(struct oxygen *chip, unsigned int rate)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	if (rate <= 32000)
+		return OXYGEN_I2S_MCLK_512;
+	else if (rate <= 48000 && data->os_128)
+		return OXYGEN_I2S_MCLK_512;
+	else if (rate <= 96000)
+		return OXYGEN_I2S_MCLK_256;
+	else
+		return OXYGEN_I2S_MCLK_128;
+}
+
+static unsigned int get_pcm1796_i2s_mclk(struct oxygen *chip,
+					 unsigned int channel,
+					 struct snd_pcm_hw_params *params)
+{
+	if (channel == PCM_MULTICH)
+		return mclk_from_rate(chip, params_rate(params));
+	else
+		return oxygen_default_i2s_mclk(chip, channel, params);
+}
+
+static void update_pcm1796_oversampling(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int i;
+	u8 reg;
+
+	if (data->current_rate <= 32000)
+		reg = PCM1796_OS_128;
+	else if (data->current_rate <= 48000 && data->os_128)
+		reg = PCM1796_OS_128;
+	else if (data->current_rate <= 96000 || data->os_128)
+		reg = PCM1796_OS_64;
+	else
+		reg = PCM1796_OS_32;
+	for (i = 0; i < data->dacs; ++i)
+		pcm1796_write_cached(chip, i, 20, reg);
+}
+
+static void set_pcm1796_params(struct oxygen *chip,
+			       struct snd_pcm_hw_params *params)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+
+	data->current_rate = params_rate(params);
+	update_pcm1796_oversampling(chip);
+}
+
+static void update_pcm1796_volume(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int i;
+	s8 gain_offset;
+
+	gain_offset = data->hp_active ? data->hp_gain_offset : 0;
+	for (i = 0; i < data->dacs; ++i) {
+		pcm1796_write_cached(chip, i, 16, chip->dac_volume[i * 2]
+				     + gain_offset);
+		pcm1796_write_cached(chip, i, 17, chip->dac_volume[i * 2 + 1]
+				     + gain_offset);
+	}
+}
+
+static void update_pcm1796_mute(struct oxygen *chip)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int i;
+	u8 value;
+
+	value = PCM1796_DMF_DISABLED | PCM1796_FMT_24_LJUST | PCM1796_ATLD;
+	if (chip->dac_mute)
+		value |= PCM1796_MUTE;
+	for (i = 0; i < data->dacs; ++i)
+		pcm1796_write_cached(chip, i, 18, value);
+}
+
+static void update_cs2000_rate(struct oxygen *chip, unsigned int rate)
+{
+	struct xonar_pcm179x *data = chip->model_data;
+	u8 rate_mclk, reg;
+
+	switch (rate) {
+		/* XXX Why is the I2S A MCLK half the actual I2S MCLK? */
+	case 32000:
+		rate_mclk = OXYGEN_RATE_32000 | OXYGEN_I2S_MCLK_256;
+		break;
+	case 44100:
+		if (data->os_128)
+			rate_mclk = OXYGEN_RATE_44100 | OXYGEN_I2S_MCLK_256;
+		else
+			rate_mclk = OXYGEN_RATE_44100 | OXYGEN_I2S_MCLK_128;
+		break;
+	default: /* 48000 */
+		if (data->os_128)
+			rate_mclk = OXYGEN_RATE_48000 | OXYGEN_I2S_MCLK_256;
+		else
+			rate_mclk = OXYGEN_RATE_48000 | OXYGEN_I2S_MCLK_128;
+		break;
+	case 64000:
+		rate_mclk = OXYGEN_RATE_32000 | OXYGEN_I2S_MCLK_256;
+		break;
+	case 88200:
+		rate_mclk = OXYGEN_RATE_44100 | OXYGEN_I2S_MCLK_256;
+		break;
+	case 96000:
+		rate_mclk = OXYGEN_RATE_48000 | OXYGEN_I2S_MCLK_256;
+		break;
+	case 176400:
+		rate_mclk = OXYGEN_RATE_44100 | OXYGEN_I2S_MCLK_256;
+		break;
+	case 192000:
+		rate_mclk = OXYGEN_RATE_48000 | OXYGEN_I2S_MCLK_256;
+		break;
+	}
+	oxygen_write16_masked(chip, OXYGEN_I2S_A_FORMAT, rate_mclk,
+			      OXYGEN_I2S_RATE_MASK | OXYGEN_I2S_MCLK_MASK);
+	if ((rate_mclk & OXYGEN_I2S_MCLK_MASK) <= OXYGEN_I2S_MCLK_128)
+		reg = CS2000_REF_CLK_DIV_1;
+	else
+		reg = CS2000_REF_CLK_DIV_2;
+	cs2000_write_cached(chip, CS2000_FUN_CFG_1, reg);
+}
+
+static void set_st_params(struct oxygen *chip,
+			  struct snd_pcm_hw_params *params)
+{
+	update_cs2000_rate(chip, params_rate(params));
+	set_pcm1796_params(chip, params);
+}
+
+static void set_hdav_params(struct oxygen *chip,
+			    struct snd_pcm_hw_params *params)
+{
+	struct xonar_hdav *data = chip->model_data;
+
+	set_pcm1796_params(chip, params);
+	xonar_set_hdmi_params(chip, &data->hdmi, params);
+}
+
+static const struct snd_kcontrol_new alt_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Analog Loopback Switch",
+	.info = snd_ctl_boolean_mono_info,
+	.get = xonar_gpio_bit_switch_get,
+	.put = xonar_gpio_bit_switch_put,
+	.private_value = GPIO_D2_ALT,
+};
+
+static int rolloff_info(struct snd_kcontrol *ctl,
+			struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = {
+		"Sharp Roll-off", "Slow Roll-off"
+	};
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 2;
+	if (info->value.enumerated.item >= 2)
+		info->value.enumerated.item = 1;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int rolloff_get(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		(data->pcm1796_regs[0][19 - PCM1796_REG_BASE] &
+		 PCM1796_FLT_MASK) != PCM1796_FLT_SHARP;
+	return 0;
+}
+
+static int rolloff_put(struct snd_kcontrol *ctl,
+		       struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+	unsigned int i;
+	int changed;
+	u8 reg;
+
+	mutex_lock(&chip->mutex);
+	reg = data->pcm1796_regs[0][19 - PCM1796_REG_BASE];
+	reg &= ~PCM1796_FLT_MASK;
+	if (!value->value.enumerated.item[0])
+		reg |= PCM1796_FLT_SHARP;
+	else
+		reg |= PCM1796_FLT_SLOW;
+	changed = reg != data->pcm1796_regs[0][19 - PCM1796_REG_BASE];
+	if (changed) {
+		for (i = 0; i < data->dacs; ++i)
+			pcm1796_write(chip, i, 19, reg);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new rolloff_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DAC Filter Playback Enum",
+	.info = rolloff_info,
+	.get = rolloff_get,
+	.put = rolloff_put,
+};
+
+static int os_128_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = { "64x", "128x" };
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 2;
+	if (info->value.enumerated.item >= 2)
+		info->value.enumerated.item = 1;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int os_128_get(struct snd_kcontrol *ctl,
+		      struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+
+	value->value.enumerated.item[0] = data->os_128;
+	return 0;
+}
+
+static int os_128_put(struct snd_kcontrol *ctl,
+		      struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	changed = value->value.enumerated.item[0] != data->os_128;
+	if (changed) {
+		data->os_128 = value->value.enumerated.item[0];
+		if (data->has_cs2000)
+			update_cs2000_rate(chip, data->current_rate);
+		oxygen_write16_masked(chip, OXYGEN_I2S_MULTICH_FORMAT,
+				      mclk_from_rate(chip, data->current_rate),
+				      OXYGEN_I2S_MCLK_MASK);
+		update_pcm1796_oversampling(chip);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new os_128_control = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DAC Oversampling Playback Enum",
+	.info = os_128_info,
+	.get = os_128_get,
+	.put = os_128_put,
+};
+
+static int st_output_switch_info(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_info *info)
+{
+	static const char *const names[3] = {
+		"Speakers", "Headphones", "FP Headphones"
+	};
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 3;
+	if (info->value.enumerated.item >= 3)
+		info->value.enumerated.item = 2;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int st_output_switch_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u16 gpio;
+
+	gpio = oxygen_read16(chip, OXYGEN_GPIO_DATA);
+	if (!(gpio & GPIO_ST_HP))
+		value->value.enumerated.item[0] = 0;
+	else if (gpio & GPIO_ST_HP_REAR)
+		value->value.enumerated.item[0] = 1;
+	else
+		value->value.enumerated.item[0] = 2;
+	return 0;
+}
+
+
+static int st_output_switch_put(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+	u16 gpio_old, gpio;
+
+	mutex_lock(&chip->mutex);
+	gpio_old = oxygen_read16(chip, OXYGEN_GPIO_DATA);
+	gpio = gpio_old;
+	switch (value->value.enumerated.item[0]) {
+	case 0:
+		gpio &= ~(GPIO_ST_HP | GPIO_ST_HP_REAR);
+		break;
+	case 1:
+		gpio |= GPIO_ST_HP | GPIO_ST_HP_REAR;
+		break;
+	case 2:
+		gpio = (gpio | GPIO_ST_HP) & ~GPIO_ST_HP_REAR;
+		break;
+	}
+	oxygen_write16(chip, OXYGEN_GPIO_DATA, gpio);
+	data->hp_active = gpio & GPIO_ST_HP;
+	update_pcm1796_volume(chip);
+	mutex_unlock(&chip->mutex);
+	return gpio != gpio_old;
+}
+
+static int st_hp_volume_offset_info(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_info *info)
+{
+	static const char *const names[3] = {
+		"< 64 ohms", "64-300 ohms", "300-600 ohms"
+	};
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 3;
+	if (info->value.enumerated.item > 2)
+		info->value.enumerated.item = 2;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int st_hp_volume_offset_get(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+
+	mutex_lock(&chip->mutex);
+	if (data->hp_gain_offset < 2*-6)
+		value->value.enumerated.item[0] = 0;
+	else if (data->hp_gain_offset < 0)
+		value->value.enumerated.item[0] = 1;
+	else
+		value->value.enumerated.item[0] = 2;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+
+static int st_hp_volume_offset_put(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	static const s8 offsets[] = { 2*-18, 2*-6, 0 };
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_pcm179x *data = chip->model_data;
+	s8 offset;
+	int changed;
+
+	if (value->value.enumerated.item[0] > 2)
+		return -EINVAL;
+	offset = offsets[value->value.enumerated.item[0]];
+	mutex_lock(&chip->mutex);
+	changed = offset != data->hp_gain_offset;
+	if (changed) {
+		data->hp_gain_offset = offset;
+		update_pcm1796_volume(chip);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static const struct snd_kcontrol_new st_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Analog Output",
+		.info = st_output_switch_info,
+		.get = st_output_switch_get,
+		.put = st_output_switch_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphones Impedance Playback Enum",
+		.info = st_hp_volume_offset_info,
+		.get = st_hp_volume_offset_get,
+		.put = st_hp_volume_offset_put,
+	},
+};
+
+static void xonar_line_mic_ac97_switch(struct oxygen *chip,
+				       unsigned int reg, unsigned int mute)
+{
+	if (reg == AC97_LINE) {
+		spin_lock_irq(&chip->reg_lock);
+		oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+				      mute ? GPIO_INPUT_ROUTE : 0,
+				      GPIO_INPUT_ROUTE);
+		spin_unlock_irq(&chip->reg_lock);
+	}
+}
+
+static const DECLARE_TLV_DB_SCALE(pcm1796_db_scale, -6000, 50, 0);
+
+static int xonar_d2_control_filter(struct snd_kcontrol_new *template)
+{
+	if (!strncmp(template->name, "CD Capture ", 11))
+		/* CD in is actually connected to the video in pin */
+		template->private_value ^= AC97_CD ^ AC97_VIDEO;
+	return 0;
+}
+
+static int add_pcm1796_controls(struct oxygen *chip)
+{
+	int err;
+
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&rolloff_control, chip));
+	if (err < 0)
+		return err;
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&os_128_control, chip));
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int xonar_d2_mixer_init(struct oxygen *chip)
+{
+	int err;
+
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&alt_switch, chip));
+	if (err < 0)
+		return err;
+	err = add_pcm1796_controls(chip);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int xonar_hdav_mixer_init(struct oxygen *chip)
+{
+	return add_pcm1796_controls(chip);
+}
+
+static int xonar_st_mixer_init(struct oxygen *chip)
+{
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(st_controls); ++i) {
+		err = snd_ctl_add(chip->card,
+				  snd_ctl_new1(&st_controls[i], chip));
+		if (err < 0)
+			return err;
+	}
+	err = add_pcm1796_controls(chip);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static const struct oxygen_model model_xonar_d2 = {
+	.longname = "Asus Virtuoso 200",
+	.chip = "AV200",
+	.init = xonar_d2_init,
+	.control_filter = xonar_d2_control_filter,
+	.mixer_init = xonar_d2_mixer_init,
+	.cleanup = xonar_d2_cleanup,
+	.suspend = xonar_d2_suspend,
+	.resume = xonar_d2_resume,
+	.get_i2s_mclk = get_pcm1796_i2s_mclk,
+	.set_dac_params = set_pcm1796_params,
+	.set_adc_params = xonar_set_cs53x1_params,
+	.update_dac_volume = update_pcm1796_volume,
+	.update_dac_mute = update_pcm1796_mute,
+	.dac_tlv = pcm1796_db_scale,
+	.model_data_size = sizeof(struct xonar_pcm179x),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_2 |
+			 CAPTURE_1_FROM_SPDIF |
+			 MIDI_OUTPUT |
+			 MIDI_INPUT |
+			 AC97_CD_INPUT,
+	.dac_channels = 8,
+	.dac_volume_min = 255 - 2*60,
+	.dac_volume_max = 255,
+	.misc_flags = OXYGEN_MISC_MIDI,
+	.function_flags = OXYGEN_FUNCTION_SPI |
+			  OXYGEN_FUNCTION_ENABLE_SPI_4_5,
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+static const struct oxygen_model model_xonar_hdav = {
+	.longname = "Asus Virtuoso 200",
+	.chip = "AV200",
+	.init = xonar_hdav_init,
+	.mixer_init = xonar_hdav_mixer_init,
+	.cleanup = xonar_hdav_cleanup,
+	.suspend = xonar_hdav_suspend,
+	.resume = xonar_hdav_resume,
+	.pcm_hardware_filter = xonar_hdmi_pcm_hardware_filter,
+	.get_i2s_mclk = get_pcm1796_i2s_mclk,
+	.set_dac_params = set_hdav_params,
+	.set_adc_params = xonar_set_cs53x1_params,
+	.update_dac_volume = update_pcm1796_volume,
+	.update_dac_mute = update_pcm1796_mute,
+	.uart_input = xonar_hdmi_uart_input,
+	.ac97_switch = xonar_line_mic_ac97_switch,
+	.dac_tlv = pcm1796_db_scale,
+	.model_data_size = sizeof(struct xonar_hdav),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_2 |
+			 CAPTURE_1_FROM_SPDIF,
+	.dac_channels = 8,
+	.dac_volume_min = 255 - 2*60,
+	.dac_volume_max = 255,
+	.misc_flags = OXYGEN_MISC_MIDI,
+	.function_flags = OXYGEN_FUNCTION_2WIRE,
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+static const struct oxygen_model model_xonar_st = {
+	.longname = "Asus Virtuoso 100",
+	.chip = "AV200",
+	.init = xonar_st_init,
+	.mixer_init = xonar_st_mixer_init,
+	.cleanup = xonar_st_cleanup,
+	.suspend = xonar_st_suspend,
+	.resume = xonar_st_resume,
+	.get_i2s_mclk = get_pcm1796_i2s_mclk,
+	.set_dac_params = set_st_params,
+	.set_adc_params = xonar_set_cs53x1_params,
+	.update_dac_volume = update_pcm1796_volume,
+	.update_dac_mute = update_pcm1796_mute,
+	.ac97_switch = xonar_line_mic_ac97_switch,
+	.dac_tlv = pcm1796_db_scale,
+	.model_data_size = sizeof(struct xonar_pcm179x),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_2,
+	.dac_channels = 2,
+	.dac_volume_min = 255 - 2*60,
+	.dac_volume_max = 255,
+	.function_flags = OXYGEN_FUNCTION_2WIRE,
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+int __devinit get_xonar_pcm179x_model(struct oxygen *chip,
+				      const struct pci_device_id *id)
+{
+	switch (id->subdevice) {
+	case 0x8269:
+		chip->model = model_xonar_d2;
+		chip->model.shortname = "Xonar D2";
+		break;
+	case 0x82b7:
+		chip->model = model_xonar_d2;
+		chip->model.shortname = "Xonar D2X";
+		chip->model.init = xonar_d2x_init;
+		break;
+	case 0x8314:
+		chip->model = model_xonar_hdav;
+		oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_DB_MASK);
+		switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) & GPIO_DB_MASK) {
+		default:
+			chip->model.shortname = "Xonar HDAV1.3";
+			break;
+		case GPIO_DB_H6:
+			chip->model.shortname = "Xonar HDAV1.3+H6";
+			chip->model.private_data = 1;
+			break;
+		}
+		break;
+	case 0x835d:
+		chip->model = model_xonar_st;
+		oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_DB_MASK);
+		switch (oxygen_read16(chip, OXYGEN_GPIO_DATA) & GPIO_DB_MASK) {
+		default:
+			chip->model.shortname = "Xonar ST";
+			break;
+		case GPIO_DB_H6:
+			chip->model.shortname = "Xonar ST+H6";
+			chip->model.dac_channels = 8;
+			chip->model.private_data = 1;
+			break;
+		}
+		break;
+	case 0x835c:
+		chip->model = model_xonar_st;
+		chip->model.shortname = "Xonar STX";
+		chip->model.init = xonar_stx_init;
+		chip->model.resume = xonar_stx_resume;
+		chip->model.set_dac_params = set_pcm1796_params;
+		break;
+	case 0x835e:
+		snd_printk(KERN_ERR "the HDAV1.3 Slim is not supported\n");
+		return -ENODEV;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
diff --git a/linux/sound/pci/oxygen/xonar_wm87x6.c b/linux/sound/pci/oxygen/xonar_wm87x6.c
new file mode 100644
index 0000000..200f760
--- /dev/null
+++ b/linux/sound/pci/oxygen/xonar_wm87x6.c
@@ -0,0 +1,1106 @@
+/*
+ * card driver for models with WM8776/WM8766 DACs (Xonar DS)
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  This driver 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 driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Xonar DS
+ * --------
+ *
+ * CMI8788:
+ *
+ * SPI 0 -> WM8766 (surround, center/LFE, back)
+ * SPI 1 -> WM8776 (front, input)
+ *
+ * GPIO 4 <- headphone detect, 0 = plugged
+ * GPIO 6 -> route input jack to mic-in (0) or line-in (1)
+ * GPIO 7 -> enable output to front L/R speaker channels
+ * GPIO 8 -> enable output to other speaker channels and front panel headphone
+ *
+ * WM8766:
+ *
+ * input 1 <- line
+ * input 2 <- mic
+ * input 3 <- front mic
+ * input 4 <- aux
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "xonar.h"
+#include "wm8776.h"
+#include "wm8766.h"
+
+#define GPIO_DS_HP_DETECT	0x0010
+#define GPIO_DS_INPUT_ROUTE	0x0040
+#define GPIO_DS_OUTPUT_FRONTLR	0x0080
+#define GPIO_DS_OUTPUT_ENABLE	0x0100
+
+#define LC_CONTROL_LIMITER	0x40000000
+#define LC_CONTROL_ALC		0x20000000
+
+struct xonar_wm87x6 {
+	struct xonar_generic generic;
+	u16 wm8776_regs[0x17];
+	u16 wm8766_regs[0x10];
+	struct snd_kcontrol *line_adcmux_control;
+	struct snd_kcontrol *mic_adcmux_control;
+	struct snd_kcontrol *lc_controls[13];
+	struct snd_jack *hp_jack;
+};
+
+static void wm8776_write(struct oxygen *chip,
+			 unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (1 << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_LO,
+			 (reg << 9) | value);
+	if (reg < ARRAY_SIZE(data->wm8776_regs)) {
+		if (reg >= WM8776_HPLVOL && reg <= WM8776_DACMASTER)
+			value &= ~WM8776_UPDATE;
+		data->wm8776_regs[reg] = value;
+	}
+}
+
+static void wm8776_write_cached(struct oxygen *chip,
+				unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	if (reg >= ARRAY_SIZE(data->wm8776_regs) ||
+	    value != data->wm8776_regs[reg])
+		wm8776_write(chip, reg, value);
+}
+
+static void wm8766_write(struct oxygen *chip,
+			 unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (0 << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_LO,
+			 (reg << 9) | value);
+	if (reg < ARRAY_SIZE(data->wm8766_regs)) {
+		if ((reg >= WM8766_LDA1 && reg <= WM8766_RDA1) ||
+		    (reg >= WM8766_LDA2 && reg <= WM8766_MASTDA))
+			value &= ~WM8766_UPDATE;
+		data->wm8766_regs[reg] = value;
+	}
+}
+
+static void wm8766_write_cached(struct oxygen *chip,
+				unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	if (reg >= ARRAY_SIZE(data->wm8766_regs) ||
+	    value != data->wm8766_regs[reg])
+		wm8766_write(chip, reg, value);
+}
+
+static void wm8776_registers_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	wm8776_write(chip, WM8776_RESET, 0);
+	wm8776_write(chip, WM8776_DACCTRL1, WM8776_DZCEN |
+		     WM8776_PL_LEFT_LEFT | WM8776_PL_RIGHT_RIGHT);
+	wm8776_write(chip, WM8776_DACMUTE, chip->dac_mute ? WM8776_DMUTE : 0);
+	wm8776_write(chip, WM8776_DACIFCTRL,
+		     WM8776_DACFMT_LJUST | WM8776_DACWL_24);
+	wm8776_write(chip, WM8776_ADCIFCTRL,
+		     data->wm8776_regs[WM8776_ADCIFCTRL]);
+	wm8776_write(chip, WM8776_MSTRCTRL, data->wm8776_regs[WM8776_MSTRCTRL]);
+	wm8776_write(chip, WM8776_PWRDOWN, data->wm8776_regs[WM8776_PWRDOWN]);
+	wm8776_write(chip, WM8776_HPLVOL, data->wm8776_regs[WM8776_HPLVOL]);
+	wm8776_write(chip, WM8776_HPRVOL, data->wm8776_regs[WM8776_HPRVOL] |
+		     WM8776_UPDATE);
+	wm8776_write(chip, WM8776_ADCLVOL, data->wm8776_regs[WM8776_ADCLVOL]);
+	wm8776_write(chip, WM8776_ADCRVOL, data->wm8776_regs[WM8776_ADCRVOL]);
+	wm8776_write(chip, WM8776_ADCMUX, data->wm8776_regs[WM8776_ADCMUX]);
+	wm8776_write(chip, WM8776_DACLVOL, chip->dac_volume[0]);
+	wm8776_write(chip, WM8776_DACRVOL, chip->dac_volume[1] | WM8776_UPDATE);
+}
+
+static void wm8766_registers_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	wm8766_write(chip, WM8766_RESET, 0);
+	wm8766_write(chip, WM8766_DAC_CTRL, data->wm8766_regs[WM8766_DAC_CTRL]);
+	wm8766_write(chip, WM8766_INT_CTRL, WM8766_FMT_LJUST | WM8766_IWL_24);
+	wm8766_write(chip, WM8766_DAC_CTRL2,
+		     WM8766_ZCD | (chip->dac_mute ? WM8766_DMUTE_MASK : 0));
+	wm8766_write(chip, WM8766_LDA1, chip->dac_volume[2]);
+	wm8766_write(chip, WM8766_RDA1, chip->dac_volume[3]);
+	wm8766_write(chip, WM8766_LDA2, chip->dac_volume[4]);
+	wm8766_write(chip, WM8766_RDA2, chip->dac_volume[5]);
+	wm8766_write(chip, WM8766_LDA3, chip->dac_volume[6]);
+	wm8766_write(chip, WM8766_RDA3, chip->dac_volume[7] | WM8766_UPDATE);
+}
+
+static void wm8776_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	data->wm8776_regs[WM8776_HPLVOL] = (0x79 - 60) | WM8776_HPZCEN;
+	data->wm8776_regs[WM8776_HPRVOL] = (0x79 - 60) | WM8776_HPZCEN;
+	data->wm8776_regs[WM8776_ADCIFCTRL] =
+		WM8776_ADCFMT_LJUST | WM8776_ADCWL_24 | WM8776_ADCMCLK;
+	data->wm8776_regs[WM8776_MSTRCTRL] =
+		WM8776_ADCRATE_256 | WM8776_DACRATE_256;
+	data->wm8776_regs[WM8776_PWRDOWN] = WM8776_HPPD;
+	data->wm8776_regs[WM8776_ADCLVOL] = 0xa5 | WM8776_ZCA;
+	data->wm8776_regs[WM8776_ADCRVOL] = 0xa5 | WM8776_ZCA;
+	data->wm8776_regs[WM8776_ADCMUX] = 0x001;
+	wm8776_registers_init(chip);
+}
+
+static void wm8766_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	data->wm8766_regs[WM8766_DAC_CTRL] =
+		WM8766_PL_LEFT_LEFT | WM8766_PL_RIGHT_RIGHT;
+	wm8766_registers_init(chip);
+}
+
+static void xonar_ds_handle_hp_jack(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	bool hp_plugged;
+	unsigned int reg;
+
+	mutex_lock(&chip->mutex);
+
+	hp_plugged = !(oxygen_read16(chip, OXYGEN_GPIO_DATA) &
+		       GPIO_DS_HP_DETECT);
+
+	oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+			      hp_plugged ? 0 : GPIO_DS_OUTPUT_FRONTLR,
+			      GPIO_DS_OUTPUT_FRONTLR);
+
+	reg = data->wm8766_regs[WM8766_DAC_CTRL] & ~WM8766_MUTEALL;
+	if (hp_plugged)
+		reg |= WM8766_MUTEALL;
+	wm8766_write_cached(chip, WM8766_DAC_CTRL, reg);
+
+	snd_jack_report(data->hp_jack, hp_plugged ? SND_JACK_HEADPHONE : 0);
+
+	mutex_unlock(&chip->mutex);
+}
+
+static void xonar_ds_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	data->generic.anti_pop_delay = 300;
+	data->generic.output_enable_bit = GPIO_DS_OUTPUT_ENABLE;
+
+	wm8776_init(chip);
+	wm8766_init(chip);
+
+	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
+			  GPIO_DS_INPUT_ROUTE | GPIO_DS_OUTPUT_FRONTLR);
+	oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL,
+			    GPIO_DS_HP_DETECT);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_DS_INPUT_ROUTE);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_INTERRUPT_MASK, GPIO_DS_HP_DETECT);
+	chip->interrupt_mask |= OXYGEN_INT_GPIO;
+
+	xonar_enable_output(chip);
+
+	snd_jack_new(chip->card, "Headphone",
+		     SND_JACK_HEADPHONE, &data->hp_jack);
+	xonar_ds_handle_hp_jack(chip);
+
+	snd_component_add(chip->card, "WM8776");
+	snd_component_add(chip->card, "WM8766");
+}
+
+static void xonar_ds_cleanup(struct oxygen *chip)
+{
+	xonar_disable_output(chip);
+	wm8776_write(chip, WM8776_RESET, 0);
+}
+
+static void xonar_ds_suspend(struct oxygen *chip)
+{
+	xonar_ds_cleanup(chip);
+}
+
+static void xonar_ds_resume(struct oxygen *chip)
+{
+	wm8776_registers_init(chip);
+	wm8766_registers_init(chip);
+	xonar_enable_output(chip);
+	xonar_ds_handle_hp_jack(chip);
+}
+
+static void wm8776_adc_hardware_filter(unsigned int channel,
+				       struct snd_pcm_hardware *hardware)
+{
+	if (channel == PCM_A) {
+		hardware->rates = SNDRV_PCM_RATE_32000 |
+				  SNDRV_PCM_RATE_44100 |
+				  SNDRV_PCM_RATE_48000 |
+				  SNDRV_PCM_RATE_64000 |
+				  SNDRV_PCM_RATE_88200 |
+				  SNDRV_PCM_RATE_96000;
+		hardware->rate_max = 96000;
+	}
+}
+
+static void set_wm87x6_dac_params(struct oxygen *chip,
+				  struct snd_pcm_hw_params *params)
+{
+}
+
+static void set_wm8776_adc_params(struct oxygen *chip,
+				  struct snd_pcm_hw_params *params)
+{
+	u16 reg;
+
+	reg = WM8776_ADCRATE_256 | WM8776_DACRATE_256;
+	if (params_rate(params) > 48000)
+		reg |= WM8776_ADCOSR;
+	wm8776_write_cached(chip, WM8776_MSTRCTRL, reg);
+}
+
+static void update_wm8776_volume(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	u8 to_change;
+
+	if (chip->dac_volume[0] == chip->dac_volume[1]) {
+		if (chip->dac_volume[0] != data->wm8776_regs[WM8776_DACLVOL] ||
+		    chip->dac_volume[1] != data->wm8776_regs[WM8776_DACRVOL]) {
+			wm8776_write(chip, WM8776_DACMASTER,
+				     chip->dac_volume[0] | WM8776_UPDATE);
+			data->wm8776_regs[WM8776_DACLVOL] = chip->dac_volume[0];
+			data->wm8776_regs[WM8776_DACRVOL] = chip->dac_volume[0];
+		}
+	} else {
+		to_change = (chip->dac_volume[0] !=
+			     data->wm8776_regs[WM8776_DACLVOL]) << 0;
+		to_change |= (chip->dac_volume[1] !=
+			      data->wm8776_regs[WM8776_DACLVOL]) << 1;
+		if (to_change & 1)
+			wm8776_write(chip, WM8776_DACLVOL, chip->dac_volume[0] |
+				     ((to_change & 2) ? 0 : WM8776_UPDATE));
+		if (to_change & 2)
+			wm8776_write(chip, WM8776_DACRVOL,
+				     chip->dac_volume[1] | WM8776_UPDATE);
+	}
+}
+
+static void update_wm87x6_volume(struct oxygen *chip)
+{
+	static const u8 wm8766_regs[6] = {
+		WM8766_LDA1, WM8766_RDA1,
+		WM8766_LDA2, WM8766_RDA2,
+		WM8766_LDA3, WM8766_RDA3,
+	};
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int i;
+	u8 to_change;
+
+	update_wm8776_volume(chip);
+	if (chip->dac_volume[2] == chip->dac_volume[3] &&
+	    chip->dac_volume[2] == chip->dac_volume[4] &&
+	    chip->dac_volume[2] == chip->dac_volume[5] &&
+	    chip->dac_volume[2] == chip->dac_volume[6] &&
+	    chip->dac_volume[2] == chip->dac_volume[7]) {
+		to_change = 0;
+		for (i = 0; i < 6; ++i)
+			if (chip->dac_volume[2] !=
+			    data->wm8766_regs[wm8766_regs[i]])
+				to_change = 1;
+		if (to_change) {
+			wm8766_write(chip, WM8766_MASTDA,
+				     chip->dac_volume[2] | WM8766_UPDATE);
+			for (i = 0; i < 6; ++i)
+				data->wm8766_regs[wm8766_regs[i]] =
+					chip->dac_volume[2];
+		}
+	} else {
+		to_change = 0;
+		for (i = 0; i < 6; ++i)
+			to_change |= (chip->dac_volume[2 + i] !=
+				      data->wm8766_regs[wm8766_regs[i]]) << i;
+		for (i = 0; i < 6; ++i)
+			if (to_change & (1 << i))
+				wm8766_write(chip, wm8766_regs[i],
+					     chip->dac_volume[2 + i] |
+					     ((to_change & (0x3e << i))
+					      ? 0 : WM8766_UPDATE));
+	}
+}
+
+static void update_wm8776_mute(struct oxygen *chip)
+{
+	wm8776_write_cached(chip, WM8776_DACMUTE,
+			    chip->dac_mute ? WM8776_DMUTE : 0);
+}
+
+static void update_wm87x6_mute(struct oxygen *chip)
+{
+	update_wm8776_mute(chip);
+	wm8766_write_cached(chip, WM8766_DAC_CTRL2, WM8766_ZCD |
+			    (chip->dac_mute ? WM8766_DMUTE_MASK : 0));
+}
+
+static void update_wm8766_center_lfe_mix(struct oxygen *chip, bool mixed)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int reg;
+
+	/*
+	 * The WM8766 can mix left and right channels, but this setting
+	 * applies to all three stereo pairs.
+	 */
+	reg = data->wm8766_regs[WM8766_DAC_CTRL] &
+		~(WM8766_PL_LEFT_MASK | WM8766_PL_RIGHT_MASK);
+	if (mixed)
+		reg |= WM8766_PL_LEFT_LRMIX | WM8766_PL_RIGHT_LRMIX;
+	else
+		reg |= WM8766_PL_LEFT_LEFT | WM8766_PL_RIGHT_RIGHT;
+	wm8766_write_cached(chip, WM8766_DAC_CTRL, reg);
+}
+
+static void xonar_ds_gpio_changed(struct oxygen *chip)
+{
+	xonar_ds_handle_hp_jack(chip);
+}
+
+static int wm8776_bit_switch_get(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	u16 bit = ctl->private_value & 0xffff;
+	unsigned int reg_index = (ctl->private_value >> 16) & 0xff;
+	bool invert = (ctl->private_value >> 24) & 1;
+
+	value->value.integer.value[0] =
+		((data->wm8776_regs[reg_index] & bit) != 0) ^ invert;
+	return 0;
+}
+
+static int wm8776_bit_switch_put(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	u16 bit = ctl->private_value & 0xffff;
+	u16 reg_value;
+	unsigned int reg_index = (ctl->private_value >> 16) & 0xff;
+	bool invert = (ctl->private_value >> 24) & 1;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg_value = data->wm8776_regs[reg_index] & ~bit;
+	if (value->value.integer.value[0] ^ invert)
+		reg_value |= bit;
+	changed = reg_value != data->wm8776_regs[reg_index];
+	if (changed)
+		wm8776_write(chip, reg_index, reg_value);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_field_enum_info(struct snd_kcontrol *ctl,
+				  struct snd_ctl_elem_info *info)
+{
+	static const char *const hld[16] = {
+		"0 ms", "2.67 ms", "5.33 ms", "10.6 ms",
+		"21.3 ms", "42.7 ms", "85.3 ms", "171 ms",
+		"341 ms", "683 ms", "1.37 s", "2.73 s",
+		"5.46 s", "10.9 s", "21.8 s", "43.7 s",
+	};
+	static const char *const atk_lim[11] = {
+		"0.25 ms", "0.5 ms", "1 ms", "2 ms",
+		"4 ms", "8 ms", "16 ms", "32 ms",
+		"64 ms", "128 ms", "256 ms",
+	};
+	static const char *const atk_alc[11] = {
+		"8.40 ms", "16.8 ms", "33.6 ms", "67.2 ms",
+		"134 ms", "269 ms", "538 ms", "1.08 s",
+		"2.15 s", "4.3 s", "8.6 s",
+	};
+	static const char *const dcy_lim[11] = {
+		"1.2 ms", "2.4 ms", "4.8 ms", "9.6 ms",
+		"19.2 ms", "38.4 ms", "76.8 ms", "154 ms",
+		"307 ms", "614 ms", "1.23 s",
+	};
+	static const char *const dcy_alc[11] = {
+		"33.5 ms", "67.0 ms", "134 ms", "268 ms",
+		"536 ms", "1.07 s", "2.14 s", "4.29 s",
+		"8.58 s", "17.2 s", "34.3 s",
+	};
+	static const char *const tranwin[8] = {
+		"0 us", "62.5 us", "125 us", "250 us",
+		"500 us", "1 ms", "2 ms", "4 ms",
+	};
+	u8 max;
+	const char *const *names;
+
+	max = (ctl->private_value >> 12) & 0xf;
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = max + 1;
+	if (info->value.enumerated.item > max)
+		info->value.enumerated.item = max;
+	switch ((ctl->private_value >> 24) & 0x1f) {
+	case WM8776_ALCCTRL2:
+		names = hld;
+		break;
+	case WM8776_ALCCTRL3:
+		if (((ctl->private_value >> 20) & 0xf) == 0) {
+			if (ctl->private_value & LC_CONTROL_LIMITER)
+				names = atk_lim;
+			else
+				names = atk_alc;
+		} else {
+			if (ctl->private_value & LC_CONTROL_LIMITER)
+				names = dcy_lim;
+			else
+				names = dcy_alc;
+		}
+		break;
+	case WM8776_LIMITER:
+		names = tranwin;
+		break;
+	default:
+		return -ENXIO;
+	}
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int wm8776_field_volume_info(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 1;
+	info->value.integer.min = (ctl->private_value >> 8) & 0xf;
+	info->value.integer.max = (ctl->private_value >> 12) & 0xf;
+	return 0;
+}
+
+static void wm8776_field_set_from_ctl(struct snd_kcontrol *ctl)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int value, reg_index, mode;
+	u8 min, max, shift;
+	u16 mask, reg_value;
+	bool invert;
+
+	if ((data->wm8776_regs[WM8776_ALCCTRL1] & WM8776_LCSEL_MASK) ==
+	    WM8776_LCSEL_LIMITER)
+		mode = LC_CONTROL_LIMITER;
+	else
+		mode = LC_CONTROL_ALC;
+	if (!(ctl->private_value & mode))
+		return;
+
+	value = ctl->private_value & 0xf;
+	min = (ctl->private_value >> 8) & 0xf;
+	max = (ctl->private_value >> 12) & 0xf;
+	mask = (ctl->private_value >> 16) & 0xf;
+	shift = (ctl->private_value >> 20) & 0xf;
+	reg_index = (ctl->private_value >> 24) & 0x1f;
+	invert = (ctl->private_value >> 29) & 0x1;
+
+	if (invert)
+		value = max - (value - min);
+	reg_value = data->wm8776_regs[reg_index];
+	reg_value &= ~(mask << shift);
+	reg_value |= value << shift;
+	wm8776_write_cached(chip, reg_index, reg_value);
+}
+
+static int wm8776_field_set(struct snd_kcontrol *ctl, unsigned int value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u8 min, max;
+	int changed;
+
+	min = (ctl->private_value >> 8) & 0xf;
+	max = (ctl->private_value >> 12) & 0xf;
+	if (value < min || value > max)
+		return -EINVAL;
+	mutex_lock(&chip->mutex);
+	changed = value != (ctl->private_value & 0xf);
+	if (changed) {
+		ctl->private_value = (ctl->private_value & ~0xf) | value;
+		wm8776_field_set_from_ctl(ctl);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_field_enum_get(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	value->value.enumerated.item[0] = ctl->private_value & 0xf;
+	return 0;
+}
+
+static int wm8776_field_volume_get(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	value->value.integer.value[0] = ctl->private_value & 0xf;
+	return 0;
+}
+
+static int wm8776_field_enum_put(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	return wm8776_field_set(ctl, value->value.enumerated.item[0]);
+}
+
+static int wm8776_field_volume_put(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	return wm8776_field_set(ctl, value->value.integer.value[0]);
+}
+
+static int wm8776_hp_vol_info(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 2;
+	info->value.integer.min = 0x79 - 60;
+	info->value.integer.max = 0x7f;
+	return 0;
+}
+
+static int wm8776_hp_vol_get(struct snd_kcontrol *ctl,
+			     struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	mutex_lock(&chip->mutex);
+	value->value.integer.value[0] =
+		data->wm8776_regs[WM8776_HPLVOL] & WM8776_HPATT_MASK;
+	value->value.integer.value[1] =
+		data->wm8776_regs[WM8776_HPRVOL] & WM8776_HPATT_MASK;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int wm8776_hp_vol_put(struct snd_kcontrol *ctl,
+			     struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	u8 to_update;
+
+	mutex_lock(&chip->mutex);
+	to_update = (value->value.integer.value[0] !=
+		     (data->wm8776_regs[WM8776_HPLVOL] & WM8776_HPATT_MASK))
+		<< 0;
+	to_update |= (value->value.integer.value[1] !=
+		      (data->wm8776_regs[WM8776_HPRVOL] & WM8776_HPATT_MASK))
+		<< 1;
+	if (value->value.integer.value[0] == value->value.integer.value[1]) {
+		if (to_update) {
+			wm8776_write(chip, WM8776_HPMASTER,
+				     value->value.integer.value[0] |
+				     WM8776_HPZCEN | WM8776_UPDATE);
+			data->wm8776_regs[WM8776_HPLVOL] =
+				value->value.integer.value[0] | WM8776_HPZCEN;
+			data->wm8776_regs[WM8776_HPRVOL] =
+				value->value.integer.value[0] | WM8776_HPZCEN;
+		}
+	} else {
+		if (to_update & 1)
+			wm8776_write(chip, WM8776_HPLVOL,
+				     value->value.integer.value[0] |
+				     WM8776_HPZCEN |
+				     ((to_update & 2) ? 0 : WM8776_UPDATE));
+		if (to_update & 2)
+			wm8776_write(chip, WM8776_HPRVOL,
+				     value->value.integer.value[1] |
+				     WM8776_HPZCEN | WM8776_UPDATE);
+	}
+	mutex_unlock(&chip->mutex);
+	return to_update != 0;
+}
+
+static int wm8776_input_mux_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int mux_bit = ctl->private_value;
+
+	value->value.integer.value[0] =
+		!!(data->wm8776_regs[WM8776_ADCMUX] & mux_bit);
+	return 0;
+}
+
+static int wm8776_input_mux_put(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	struct snd_kcontrol *other_ctl;
+	unsigned int mux_bit = ctl->private_value;
+	u16 reg;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg = data->wm8776_regs[WM8776_ADCMUX];
+	if (value->value.integer.value[0]) {
+		reg |= mux_bit;
+		/* line-in and mic-in are exclusive */
+		mux_bit ^= 3;
+		if (reg & mux_bit) {
+			reg &= ~mux_bit;
+			if (mux_bit == 1)
+				other_ctl = data->line_adcmux_control;
+			else
+				other_ctl = data->mic_adcmux_control;
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &other_ctl->id);
+		}
+	} else
+		reg &= ~mux_bit;
+	changed = reg != data->wm8776_regs[WM8776_ADCMUX];
+	if (changed) {
+		oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+				      reg & 1 ? GPIO_DS_INPUT_ROUTE : 0,
+				      GPIO_DS_INPUT_ROUTE);
+		wm8776_write(chip, WM8776_ADCMUX, reg);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_input_vol_info(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 2;
+	info->value.integer.min = 0xa5;
+	info->value.integer.max = 0xff;
+	return 0;
+}
+
+static int wm8776_input_vol_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	mutex_lock(&chip->mutex);
+	value->value.integer.value[0] =
+		data->wm8776_regs[WM8776_ADCLVOL] & WM8776_AGMASK;
+	value->value.integer.value[1] =
+		data->wm8776_regs[WM8776_ADCRVOL] & WM8776_AGMASK;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int wm8776_input_vol_put(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	int changed = 0;
+
+	mutex_lock(&chip->mutex);
+	changed = (value->value.integer.value[0] !=
+		   (data->wm8776_regs[WM8776_ADCLVOL] & WM8776_AGMASK)) ||
+		  (value->value.integer.value[1] !=
+		   (data->wm8776_regs[WM8776_ADCRVOL] & WM8776_AGMASK));
+	wm8776_write_cached(chip, WM8776_ADCLVOL,
+			    value->value.integer.value[0] | WM8776_ZCA);
+	wm8776_write_cached(chip, WM8776_ADCRVOL,
+			    value->value.integer.value[1] | WM8776_ZCA);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_level_control_info(struct snd_kcontrol *ctl,
+				     struct snd_ctl_elem_info *info)
+{
+	static const char *const names[3] = {
+		"None", "Peak Limiter", "Automatic Level Control"
+	};
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 3;
+	if (info->value.enumerated.item >= 3)
+		info->value.enumerated.item = 2;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int wm8776_level_control_get(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	if (!(data->wm8776_regs[WM8776_ALCCTRL2] & WM8776_LCEN))
+		value->value.enumerated.item[0] = 0;
+	else if ((data->wm8776_regs[WM8776_ALCCTRL1] & WM8776_LCSEL_MASK) ==
+		 WM8776_LCSEL_LIMITER)
+		value->value.enumerated.item[0] = 1;
+	else
+		value->value.enumerated.item[0] = 2;
+	return 0;
+}
+
+static void activate_control(struct oxygen *chip,
+			     struct snd_kcontrol *ctl, unsigned int mode)
+{
+	unsigned int access;
+
+	if (ctl->private_value & mode)
+		access = 0;
+	else
+		access = SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	if ((ctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_INACTIVE) != access) {
+		ctl->vd[0].access ^= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+	}
+}
+
+static int wm8776_level_control_put(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int mode = 0, i;
+	u16 ctrl1, ctrl2;
+	int changed;
+
+	if (value->value.enumerated.item[0] >= 3)
+		return -EINVAL;
+	mutex_lock(&chip->mutex);
+	changed = value->value.enumerated.item[0] != ctl->private_value;
+	if (changed) {
+		ctl->private_value = value->value.enumerated.item[0];
+		ctrl1 = data->wm8776_regs[WM8776_ALCCTRL1];
+		ctrl2 = data->wm8776_regs[WM8776_ALCCTRL2];
+		switch (value->value.enumerated.item[0]) {
+		default:
+			wm8776_write_cached(chip, WM8776_ALCCTRL2,
+					    ctrl2 & ~WM8776_LCEN);
+			break;
+		case 1:
+			wm8776_write_cached(chip, WM8776_ALCCTRL1,
+					    (ctrl1 & ~WM8776_LCSEL_MASK) |
+					    WM8776_LCSEL_LIMITER);
+			wm8776_write_cached(chip, WM8776_ALCCTRL2,
+					    ctrl2 | WM8776_LCEN);
+			mode = LC_CONTROL_LIMITER;
+			break;
+		case 2:
+			wm8776_write_cached(chip, WM8776_ALCCTRL1,
+					    (ctrl1 & ~WM8776_LCSEL_MASK) |
+					    WM8776_LCSEL_ALC_STEREO);
+			wm8776_write_cached(chip, WM8776_ALCCTRL2,
+					    ctrl2 | WM8776_LCEN);
+			mode = LC_CONTROL_ALC;
+			break;
+		}
+		for (i = 0; i < ARRAY_SIZE(data->lc_controls); ++i)
+			activate_control(chip, data->lc_controls[i], mode);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = {
+		"None", "High-pass Filter"
+	};
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 2;
+	if (info->value.enumerated.item >= 2)
+		info->value.enumerated.item = 1;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		!(data->wm8776_regs[WM8776_ADCIFCTRL] & WM8776_ADCHPD);
+	return 0;
+}
+
+static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int reg;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg = data->wm8776_regs[WM8776_ADCIFCTRL] & ~WM8776_ADCHPD;
+	if (!value->value.enumerated.item[0])
+		reg |= WM8776_ADCHPD;
+	changed = reg != data->wm8776_regs[WM8776_ADCIFCTRL];
+	if (changed)
+		wm8776_write(chip, WM8776_ADCIFCTRL, reg);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+#define WM8776_BIT_SWITCH(xname, reg, bit, invert, flags) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.info = snd_ctl_boolean_mono_info, \
+	.get = wm8776_bit_switch_get, \
+	.put = wm8776_bit_switch_put, \
+	.private_value = ((reg) << 16) | (bit) | ((invert) << 24) | (flags), \
+}
+#define _WM8776_FIELD_CTL(xname, reg, shift, initval, min, max, mask, flags) \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.private_value = (initval) | ((min) << 8) | ((max) << 12) | \
+	((mask) << 16) | ((shift) << 20) | ((reg) << 24) | (flags)
+#define WM8776_FIELD_CTL_ENUM(xname, reg, shift, init, min, max, mask, flags) {\
+	_WM8776_FIELD_CTL(xname " Capture Enum", \
+			  reg, shift, init, min, max, mask, flags), \
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+		  SNDRV_CTL_ELEM_ACCESS_INACTIVE, \
+	.info = wm8776_field_enum_info, \
+	.get = wm8776_field_enum_get, \
+	.put = wm8776_field_enum_put, \
+}
+#define WM8776_FIELD_CTL_VOLUME(a, b, c, d, e, f, g, h, tlv_p) { \
+	_WM8776_FIELD_CTL(a " Capture Volume", b, c, d, e, f, g, h), \
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+		  SNDRV_CTL_ELEM_ACCESS_INACTIVE | \
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+	.info = wm8776_field_volume_info, \
+	.get = wm8776_field_volume_get, \
+	.put = wm8776_field_volume_put, \
+	.tlv = { .p = tlv_p }, \
+}
+
+static const DECLARE_TLV_DB_SCALE(wm87x6_dac_db_scale, -6000, 50, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_adc_db_scale, -2100, 50, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_hp_db_scale, -6000, 100, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_lct_db_scale, -1600, 100, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_db_scale, 0, 400, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_ngth_db_scale, -7800, 600, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_db_scale, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_db_scale, -2100, 400, 0);
+
+static const struct snd_kcontrol_new ds_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphone Playback Volume",
+		.info = wm8776_hp_vol_info,
+		.get = wm8776_hp_vol_get,
+		.put = wm8776_hp_vol_put,
+		.tlv = { .p = wm8776_hp_db_scale },
+	},
+	WM8776_BIT_SWITCH("Headphone Playback Switch",
+			  WM8776_PWRDOWN, WM8776_HPPD, 1, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Input Capture Volume",
+		.info = wm8776_input_vol_info,
+		.get = wm8776_input_vol_get,
+		.put = wm8776_input_vol_put,
+		.tlv = { .p = wm8776_adc_db_scale },
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Line Capture Switch",
+		.info = snd_ctl_boolean_mono_info,
+		.get = wm8776_input_mux_get,
+		.put = wm8776_input_mux_put,
+		.private_value = 1 << 0,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Mic Capture Switch",
+		.info = snd_ctl_boolean_mono_info,
+		.get = wm8776_input_mux_get,
+		.put = wm8776_input_mux_put,
+		.private_value = 1 << 1,
+	},
+	WM8776_BIT_SWITCH("Front Mic Capture Switch",
+			  WM8776_ADCMUX, 1 << 2, 0, 0),
+	WM8776_BIT_SWITCH("Aux Capture Switch",
+			  WM8776_ADCMUX, 1 << 3, 0, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "ADC Filter Capture Enum",
+		.info = hpf_info,
+		.get = hpf_get,
+		.put = hpf_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Level Control Capture Enum",
+		.info = wm8776_level_control_info,
+		.get = wm8776_level_control_get,
+		.put = wm8776_level_control_put,
+		.private_value = 0,
+	},
+};
+static const struct snd_kcontrol_new lc_controls[] = {
+	WM8776_FIELD_CTL_VOLUME("Limiter Threshold",
+				WM8776_ALCCTRL1, 0, 11, 0, 15, 0xf,
+				LC_CONTROL_LIMITER, wm8776_lct_db_scale),
+	WM8776_FIELD_CTL_ENUM("Limiter Attack Time",
+			      WM8776_ALCCTRL3, 0, 2, 0, 10, 0xf,
+			      LC_CONTROL_LIMITER),
+	WM8776_FIELD_CTL_ENUM("Limiter Decay Time",
+			      WM8776_ALCCTRL3, 4, 3, 0, 10, 0xf,
+			      LC_CONTROL_LIMITER),
+	WM8776_FIELD_CTL_ENUM("Limiter Transient Window",
+			      WM8776_LIMITER, 4, 2, 0, 7, 0x7,
+			      LC_CONTROL_LIMITER),
+	WM8776_FIELD_CTL_VOLUME("Limiter Maximum Attenuation",
+				WM8776_LIMITER, 0, 6, 3, 12, 0xf,
+				LC_CONTROL_LIMITER,
+				wm8776_maxatten_lim_db_scale),
+	WM8776_FIELD_CTL_VOLUME("ALC Target Level",
+				WM8776_ALCCTRL1, 0, 11, 0, 15, 0xf,
+				LC_CONTROL_ALC, wm8776_lct_db_scale),
+	WM8776_FIELD_CTL_ENUM("ALC Attack Time",
+			      WM8776_ALCCTRL3, 0, 2, 0, 10, 0xf,
+			      LC_CONTROL_ALC),
+	WM8776_FIELD_CTL_ENUM("ALC Decay Time",
+			      WM8776_ALCCTRL3, 4, 3, 0, 10, 0xf,
+			      LC_CONTROL_ALC),
+	WM8776_FIELD_CTL_VOLUME("ALC Maximum Gain",
+				WM8776_ALCCTRL1, 4, 7, 1, 7, 0x7,
+				LC_CONTROL_ALC, wm8776_maxgain_db_scale),
+	WM8776_FIELD_CTL_VOLUME("ALC Maximum Attenuation",
+				WM8776_LIMITER, 0, 10, 10, 15, 0xf,
+				LC_CONTROL_ALC, wm8776_maxatten_alc_db_scale),
+	WM8776_FIELD_CTL_ENUM("ALC Hold Time",
+			      WM8776_ALCCTRL2, 0, 0, 0, 15, 0xf,
+			      LC_CONTROL_ALC),
+	WM8776_BIT_SWITCH("Noise Gate Capture Switch",
+			  WM8776_NOISEGATE, WM8776_NGAT, 0,
+			  LC_CONTROL_ALC),
+	WM8776_FIELD_CTL_VOLUME("Noise Gate Threshold",
+				WM8776_NOISEGATE, 2, 0, 0, 7, 0x7,
+				LC_CONTROL_ALC, wm8776_ngth_db_scale),
+};
+
+static int xonar_ds_mixer_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int i;
+	struct snd_kcontrol *ctl;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(ds_controls); ++i) {
+		ctl = snd_ctl_new1(&ds_controls[i], chip);
+		if (!ctl)
+			return -ENOMEM;
+		err = snd_ctl_add(chip->card, ctl);
+		if (err < 0)
+			return err;
+		if (!strcmp(ctl->id.name, "Line Capture Switch"))
+			data->line_adcmux_control = ctl;
+		else if (!strcmp(ctl->id.name, "Mic Capture Switch"))
+			data->mic_adcmux_control = ctl;
+	}
+	if (!data->line_adcmux_control || !data->mic_adcmux_control)
+		return -ENXIO;
+	BUILD_BUG_ON(ARRAY_SIZE(lc_controls) != ARRAY_SIZE(data->lc_controls));
+	for (i = 0; i < ARRAY_SIZE(lc_controls); ++i) {
+		ctl = snd_ctl_new1(&lc_controls[i], chip);
+		if (!ctl)
+			return -ENOMEM;
+		err = snd_ctl_add(chip->card, ctl);
+		if (err < 0)
+			return err;
+		data->lc_controls[i] = ctl;
+	}
+	return 0;
+}
+
+static const struct oxygen_model model_xonar_ds = {
+	.shortname = "Xonar DS",
+	.longname = "Asus Virtuoso 66",
+	.chip = "AV200",
+	.init = xonar_ds_init,
+	.mixer_init = xonar_ds_mixer_init,
+	.cleanup = xonar_ds_cleanup,
+	.suspend = xonar_ds_suspend,
+	.resume = xonar_ds_resume,
+	.pcm_hardware_filter = wm8776_adc_hardware_filter,
+	.get_i2s_mclk = oxygen_default_i2s_mclk,
+	.set_dac_params = set_wm87x6_dac_params,
+	.set_adc_params = set_wm8776_adc_params,
+	.update_dac_volume = update_wm87x6_volume,
+	.update_dac_mute = update_wm87x6_mute,
+	.update_center_lfe_mix = update_wm8766_center_lfe_mix,
+	.gpio_changed = xonar_ds_gpio_changed,
+	.dac_tlv = wm87x6_dac_db_scale,
+	.model_data_size = sizeof(struct xonar_wm87x6),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_1,
+	.dac_channels = 8,
+	.dac_volume_min = 255 - 2*60,
+	.dac_volume_max = 255,
+	.function_flags = OXYGEN_FUNCTION_SPI,
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+int __devinit get_xonar_wm87x6_model(struct oxygen *chip,
+				     const struct pci_device_id *id)
+{
+	switch (id->subdevice) {
+	case 0x838e:
+		chip->model = model_xonar_ds;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
diff --git a/linux/sound/pci/pcxhr/Makefile b/linux/sound/pci/pcxhr/Makefile
new file mode 100644
index 0000000..b06128e
--- /dev/null
+++ b/linux/sound/pci/pcxhr/Makefile
@@ -0,0 +1,2 @@
+snd-pcxhr-objs := pcxhr.o pcxhr_hwdep.o pcxhr_mixer.o pcxhr_core.o pcxhr_mix22.o
+obj-$(CONFIG_SND_PCXHR) += snd-pcxhr.o
diff --git a/linux/sound/pci/pcxhr/pcxhr.c b/linux/sound/pci/pcxhr/pcxhr.c
new file mode 100644
index 0000000..95cfde2
--- /dev/null
+++ b/linux/sound/pci/pcxhr/pcxhr.c
@@ -0,0 +1,1628 @@
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * main file with alsa callbacks
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "pcxhr.h"
+#include "pcxhr_mixer.h"
+#include "pcxhr_hwdep.h"
+#include "pcxhr_core.h"
+#include "pcxhr_mix22.h"
+
+#define DRIVER_NAME "pcxhr"
+
+MODULE_AUTHOR("Markus Bollinger <bollinger@digigram.com>, "
+	      "Marc Titinger <titinger@digigram.com>");
+MODULE_DESCRIPTION("Digigram " DRIVER_NAME " " PCXHR_DRIVER_VERSION_STRING);
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Digigram," DRIVER_NAME "}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */
+static int mono[SNDRV_CARDS];				/* capture  mono only */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Digigram " DRIVER_NAME " soundcard");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Digigram " DRIVER_NAME " soundcard");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Digigram " DRIVER_NAME " soundcard");
+module_param_array(mono, bool, NULL, 0444);
+MODULE_PARM_DESC(mono, "Mono capture mode (default is stereo)");
+
+enum {
+	PCI_ID_VX882HR,
+	PCI_ID_PCX882HR,
+	PCI_ID_VX881HR,
+	PCI_ID_PCX881HR,
+	PCI_ID_VX882E,
+	PCI_ID_PCX882E,
+	PCI_ID_VX881E,
+	PCI_ID_PCX881E,
+	PCI_ID_VX1222HR,
+	PCI_ID_PCX1222HR,
+	PCI_ID_VX1221HR,
+	PCI_ID_PCX1221HR,
+	PCI_ID_VX1222E,
+	PCI_ID_PCX1222E,
+	PCI_ID_VX1221E,
+	PCI_ID_PCX1221E,
+	PCI_ID_VX222HR,
+	PCI_ID_VX222E,
+	PCI_ID_PCX22HR,
+	PCI_ID_PCX22E,
+	PCI_ID_VX222HRMIC,
+	PCI_ID_VX222E_MIC,
+	PCI_ID_PCX924HR,
+	PCI_ID_PCX924E,
+	PCI_ID_PCX924HRMIC,
+	PCI_ID_PCX924E_MIC,
+	PCI_ID_LAST
+};
+
+static DEFINE_PCI_DEVICE_TABLE(pcxhr_ids) = {
+	{ 0x10b5, 0x9656, 0x1369, 0xb001, 0, 0, PCI_ID_VX882HR, },
+	{ 0x10b5, 0x9656, 0x1369, 0xb101, 0, 0, PCI_ID_PCX882HR, },
+	{ 0x10b5, 0x9656, 0x1369, 0xb201, 0, 0, PCI_ID_VX881HR, },
+	{ 0x10b5, 0x9656, 0x1369, 0xb301, 0, 0, PCI_ID_PCX881HR, },
+	{ 0x10b5, 0x9056, 0x1369, 0xb021, 0, 0, PCI_ID_VX882E, },
+	{ 0x10b5, 0x9056, 0x1369, 0xb121, 0, 0, PCI_ID_PCX882E, },
+	{ 0x10b5, 0x9056, 0x1369, 0xb221, 0, 0, PCI_ID_VX881E, },
+	{ 0x10b5, 0x9056, 0x1369, 0xb321, 0, 0, PCI_ID_PCX881E, },
+	{ 0x10b5, 0x9656, 0x1369, 0xb401, 0, 0, PCI_ID_VX1222HR, },
+	{ 0x10b5, 0x9656, 0x1369, 0xb501, 0, 0, PCI_ID_PCX1222HR, },
+	{ 0x10b5, 0x9656, 0x1369, 0xb601, 0, 0, PCI_ID_VX1221HR, },
+	{ 0x10b5, 0x9656, 0x1369, 0xb701, 0, 0, PCI_ID_PCX1221HR, },
+	{ 0x10b5, 0x9056, 0x1369, 0xb421, 0, 0, PCI_ID_VX1222E, },
+	{ 0x10b5, 0x9056, 0x1369, 0xb521, 0, 0, PCI_ID_PCX1222E, },
+	{ 0x10b5, 0x9056, 0x1369, 0xb621, 0, 0, PCI_ID_VX1221E, },
+	{ 0x10b5, 0x9056, 0x1369, 0xb721, 0, 0, PCI_ID_PCX1221E, },
+	{ 0x10b5, 0x9056, 0x1369, 0xba01, 0, 0, PCI_ID_VX222HR, },
+	{ 0x10b5, 0x9056, 0x1369, 0xba21, 0, 0, PCI_ID_VX222E, },
+	{ 0x10b5, 0x9056, 0x1369, 0xbd01, 0, 0, PCI_ID_PCX22HR, },
+	{ 0x10b5, 0x9056, 0x1369, 0xbd21, 0, 0, PCI_ID_PCX22E, },
+	{ 0x10b5, 0x9056, 0x1369, 0xbc01, 0, 0, PCI_ID_VX222HRMIC, },
+	{ 0x10b5, 0x9056, 0x1369, 0xbc21, 0, 0, PCI_ID_VX222E_MIC, },
+	{ 0x10b5, 0x9056, 0x1369, 0xbb01, 0, 0, PCI_ID_PCX924HR, },
+	{ 0x10b5, 0x9056, 0x1369, 0xbb21, 0, 0, PCI_ID_PCX924E, },
+	{ 0x10b5, 0x9056, 0x1369, 0xbf01, 0, 0, PCI_ID_PCX924HRMIC, },
+	{ 0x10b5, 0x9056, 0x1369, 0xbf21, 0, 0, PCI_ID_PCX924E_MIC, },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, pcxhr_ids);
+
+struct board_parameters {
+	char* board_name;
+	short playback_chips;
+	short capture_chips;
+	short fw_file_set;
+	short firmware_num;
+};
+static struct board_parameters pcxhr_board_params[] = {
+[PCI_ID_VX882HR] =      { "VX882HR",      4, 4, 0, 41 },
+[PCI_ID_PCX882HR] =     { "PCX882HR",     4, 4, 0, 41 },
+[PCI_ID_VX881HR] =      { "VX881HR",      4, 4, 0, 41 },
+[PCI_ID_PCX881HR] =     { "PCX881HR",     4, 4, 0, 41 },
+[PCI_ID_VX882E] =       { "VX882e",       4, 4, 1, 41 },
+[PCI_ID_PCX882E] =      { "PCX882e",      4, 4, 1, 41 },
+[PCI_ID_VX881E] =       { "VX881e",       4, 4, 1, 41 },
+[PCI_ID_PCX881E] =      { "PCX881e",      4, 4, 1, 41 },
+[PCI_ID_VX1222HR] =     { "VX1222HR",     6, 1, 2, 42 },
+[PCI_ID_PCX1222HR] =    { "PCX1222HR",    6, 1, 2, 42 },
+[PCI_ID_VX1221HR] =     { "VX1221HR",     6, 1, 2, 42 },
+[PCI_ID_PCX1221HR] =    { "PCX1221HR",    6, 1, 2, 42 },
+[PCI_ID_VX1222E] =      { "VX1222e",      6, 1, 3, 42 },
+[PCI_ID_PCX1222E] =     { "PCX1222e",     6, 1, 3, 42 },
+[PCI_ID_VX1221E] =      { "VX1221e",      6, 1, 3, 42 },
+[PCI_ID_PCX1221E] =     { "PCX1221e",     6, 1, 3, 42 },
+[PCI_ID_VX222HR] =      { "VX222HR",      1, 1, 4, 44 },
+[PCI_ID_VX222E] =       { "VX222e",       1, 1, 4, 44 },
+[PCI_ID_PCX22HR] =      { "PCX22HR",      1, 0, 4, 44 },
+[PCI_ID_PCX22E] =       { "PCX22e",       1, 0, 4, 44 },
+[PCI_ID_VX222HRMIC] =   { "VX222HR-Mic",  1, 1, 5, 44 },
+[PCI_ID_VX222E_MIC] =   { "VX222e-Mic",   1, 1, 5, 44 },
+[PCI_ID_PCX924HR] =     { "PCX924HR",     1, 1, 5, 44 },
+[PCI_ID_PCX924E] =      { "PCX924e",      1, 1, 5, 44 },
+[PCI_ID_PCX924HRMIC] =  { "PCX924HR-Mic", 1, 1, 5, 44 },
+[PCI_ID_PCX924E_MIC] =  { "PCX924e-Mic",  1, 1, 5, 44 },
+};
+
+/* boards without hw AES1 and SRC onboard are all using fw_file_set==4 */
+/* VX222HR, VX222e, PCX22HR and PCX22e */
+#define PCXHR_BOARD_HAS_AES1(x) (x->fw_file_set != 4)
+/* some boards do not support 192kHz on digital AES input plugs */
+#define PCXHR_BOARD_AESIN_NO_192K(x) ((x->capture_chips == 0) || \
+				      (x->fw_file_set == 0)   || \
+				      (x->fw_file_set == 2))
+
+static int pcxhr_pll_freq_register(unsigned int freq, unsigned int* pllreg,
+				   unsigned int* realfreq)
+{
+	unsigned int reg;
+
+	if (freq < 6900 || freq > 110000)
+		return -EINVAL;
+	reg = (28224000 * 2) / freq;
+	reg = (reg - 1) / 2;
+	if (reg < 0x200)
+		*pllreg = reg + 0x800;
+	else if (reg < 0x400)
+		*pllreg = reg & 0x1ff;
+	else if (reg < 0x800) {
+		*pllreg = ((reg >> 1) & 0x1ff) + 0x200;
+		reg &= ~1;
+	} else {
+		*pllreg = ((reg >> 2) & 0x1ff) + 0x400;
+		reg &= ~3;
+	}
+	if (realfreq)
+		*realfreq = (28224000 / (reg + 1));
+	return 0;
+}
+
+
+#define PCXHR_FREQ_REG_MASK		0x1f
+#define PCXHR_FREQ_QUARTZ_48000		0x00
+#define PCXHR_FREQ_QUARTZ_24000		0x01
+#define PCXHR_FREQ_QUARTZ_12000		0x09
+#define PCXHR_FREQ_QUARTZ_32000		0x08
+#define PCXHR_FREQ_QUARTZ_16000		0x04
+#define PCXHR_FREQ_QUARTZ_8000		0x0c
+#define PCXHR_FREQ_QUARTZ_44100		0x02
+#define PCXHR_FREQ_QUARTZ_22050		0x0a
+#define PCXHR_FREQ_QUARTZ_11025		0x06
+#define PCXHR_FREQ_PLL			0x05
+#define PCXHR_FREQ_QUARTZ_192000	0x10
+#define PCXHR_FREQ_QUARTZ_96000		0x18
+#define PCXHR_FREQ_QUARTZ_176400	0x14
+#define PCXHR_FREQ_QUARTZ_88200		0x1c
+#define PCXHR_FREQ_QUARTZ_128000	0x12
+#define PCXHR_FREQ_QUARTZ_64000		0x1a
+
+#define PCXHR_FREQ_WORD_CLOCK		0x0f
+#define PCXHR_FREQ_SYNC_AES		0x0e
+#define PCXHR_FREQ_AES_1		0x07
+#define PCXHR_FREQ_AES_2		0x0b
+#define PCXHR_FREQ_AES_3		0x03
+#define PCXHR_FREQ_AES_4		0x0d
+
+static int pcxhr_get_clock_reg(struct pcxhr_mgr *mgr, unsigned int rate,
+			       unsigned int *reg, unsigned int *freq)
+{
+	unsigned int val, realfreq, pllreg;
+	struct pcxhr_rmh rmh;
+	int err;
+
+	realfreq = rate;
+	switch (mgr->use_clock_type) {
+	case PCXHR_CLOCK_TYPE_INTERNAL :	/* clock by quartz or pll */
+		switch (rate) {
+		case 48000 :	val = PCXHR_FREQ_QUARTZ_48000;	break;
+		case 24000 :	val = PCXHR_FREQ_QUARTZ_24000;	break;
+		case 12000 :	val = PCXHR_FREQ_QUARTZ_12000;	break;
+		case 32000 :	val = PCXHR_FREQ_QUARTZ_32000;	break;
+		case 16000 :	val = PCXHR_FREQ_QUARTZ_16000;	break;
+		case 8000 :	val = PCXHR_FREQ_QUARTZ_8000;	break;
+		case 44100 :	val = PCXHR_FREQ_QUARTZ_44100;	break;
+		case 22050 :	val = PCXHR_FREQ_QUARTZ_22050;	break;
+		case 11025 :	val = PCXHR_FREQ_QUARTZ_11025;	break;
+		case 192000 :	val = PCXHR_FREQ_QUARTZ_192000;	break;
+		case 96000 :	val = PCXHR_FREQ_QUARTZ_96000;	break;
+		case 176400 :	val = PCXHR_FREQ_QUARTZ_176400;	break;
+		case 88200 :	val = PCXHR_FREQ_QUARTZ_88200;	break;
+		case 128000 :	val = PCXHR_FREQ_QUARTZ_128000;	break;
+		case 64000 :	val = PCXHR_FREQ_QUARTZ_64000;	break;
+		default :
+			val = PCXHR_FREQ_PLL;
+			/* get the value for the pll register */
+			err = pcxhr_pll_freq_register(rate, &pllreg, &realfreq);
+			if (err)
+				return err;
+			pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE);
+			rmh.cmd[0] |= IO_NUM_REG_GENCLK;
+			rmh.cmd[1]  = pllreg & MASK_DSP_WORD;
+			rmh.cmd[2]  = pllreg >> 24;
+			rmh.cmd_len = 3;
+			err = pcxhr_send_msg(mgr, &rmh);
+			if (err < 0) {
+				snd_printk(KERN_ERR
+					   "error CMD_ACCESS_IO_WRITE "
+					   "for PLL register : %x!\n", err);
+				return err;
+			}
+		}
+		break;
+	case PCXHR_CLOCK_TYPE_WORD_CLOCK:
+		val = PCXHR_FREQ_WORD_CLOCK;
+		break;
+	case PCXHR_CLOCK_TYPE_AES_SYNC:
+		val = PCXHR_FREQ_SYNC_AES;
+		break;
+	case PCXHR_CLOCK_TYPE_AES_1:
+		val = PCXHR_FREQ_AES_1;
+		break;
+	case PCXHR_CLOCK_TYPE_AES_2:
+		val = PCXHR_FREQ_AES_2;
+		break;
+	case PCXHR_CLOCK_TYPE_AES_3:
+		val = PCXHR_FREQ_AES_3;
+		break;
+	case PCXHR_CLOCK_TYPE_AES_4:
+		val = PCXHR_FREQ_AES_4;
+		break;
+	default:
+		return -EINVAL;
+	}
+	*reg = val;
+	*freq = realfreq;
+	return 0;
+}
+
+
+static int pcxhr_sub_set_clock(struct pcxhr_mgr *mgr,
+			       unsigned int rate,
+			       int *changed)
+{
+	unsigned int val, realfreq, speed;
+	struct pcxhr_rmh rmh;
+	int err;
+
+	err = pcxhr_get_clock_reg(mgr, rate, &val, &realfreq);
+	if (err)
+		return err;
+
+	/* codec speed modes */
+	if (rate < 55000)
+		speed = 0;	/* single speed */
+	else if (rate < 100000)
+		speed = 1;	/* dual speed */
+	else
+		speed = 2;	/* quad speed */
+	if (mgr->codec_speed != speed) {
+		pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); /* mute outputs */
+		rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT;
+		if (DSP_EXT_CMD_SET(mgr)) {
+			rmh.cmd[1]  = 1;
+			rmh.cmd_len = 2;
+		}
+		err = pcxhr_send_msg(mgr, &rmh);
+		if (err)
+			return err;
+
+		pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); /* set speed ratio */
+		rmh.cmd[0] |= IO_NUM_SPEED_RATIO;
+		rmh.cmd[1] = speed;
+		rmh.cmd_len = 2;
+		err = pcxhr_send_msg(mgr, &rmh);
+		if (err)
+			return err;
+	}
+	/* set the new frequency */
+	snd_printdd("clock register : set %x\n", val);
+	err = pcxhr_write_io_num_reg_cont(mgr, PCXHR_FREQ_REG_MASK,
+					  val, changed);
+	if (err)
+		return err;
+
+	mgr->sample_rate_real = realfreq;
+	mgr->cur_clock_type = mgr->use_clock_type;
+
+	/* unmute after codec speed modes */
+	if (mgr->codec_speed != speed) {
+		pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); /* unmute outputs */
+		rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT;
+		if (DSP_EXT_CMD_SET(mgr)) {
+			rmh.cmd[1]  = 1;
+			rmh.cmd_len = 2;
+		}
+		err = pcxhr_send_msg(mgr, &rmh);
+		if (err)
+			return err;
+		mgr->codec_speed = speed;	/* save new codec speed */
+	}
+
+	snd_printdd("pcxhr_sub_set_clock to %dHz (realfreq=%d)\n",
+		    rate, realfreq);
+	return 0;
+}
+
+#define PCXHR_MODIFY_CLOCK_S_BIT	0x04
+
+#define PCXHR_IRQ_TIMER_FREQ		92000
+#define PCXHR_IRQ_TIMER_PERIOD		48
+
+int pcxhr_set_clock(struct pcxhr_mgr *mgr, unsigned int rate)
+{
+	struct pcxhr_rmh rmh;
+	int err, changed;
+
+	if (rate == 0)
+		return 0; /* nothing to do */
+
+	if (mgr->is_hr_stereo)
+		err = hr222_sub_set_clock(mgr, rate, &changed);
+	else
+		err = pcxhr_sub_set_clock(mgr, rate, &changed);
+
+	if (err)
+		return err;
+
+	if (changed) {
+		pcxhr_init_rmh(&rmh, CMD_MODIFY_CLOCK);
+		rmh.cmd[0] |= PCXHR_MODIFY_CLOCK_S_BIT; /* resync fifos  */
+		if (rate < PCXHR_IRQ_TIMER_FREQ)
+			rmh.cmd[1] = PCXHR_IRQ_TIMER_PERIOD;
+		else
+			rmh.cmd[1] = PCXHR_IRQ_TIMER_PERIOD * 2;
+		rmh.cmd[2] = rate;
+		rmh.cmd_len = 3;
+		err = pcxhr_send_msg(mgr, &rmh);
+		if (err)
+			return err;
+	}
+	return 0;
+}
+
+
+static int pcxhr_sub_get_external_clock(struct pcxhr_mgr *mgr,
+					enum pcxhr_clock_type clock_type,
+					int *sample_rate)
+{
+	struct pcxhr_rmh rmh;
+	unsigned char reg;
+	int err, rate;
+
+	switch (clock_type) {
+	case PCXHR_CLOCK_TYPE_WORD_CLOCK:
+		reg = REG_STATUS_WORD_CLOCK;
+		break;
+	case PCXHR_CLOCK_TYPE_AES_SYNC:
+		reg = REG_STATUS_AES_SYNC;
+		break;
+	case PCXHR_CLOCK_TYPE_AES_1:
+		reg = REG_STATUS_AES_1;
+		break;
+	case PCXHR_CLOCK_TYPE_AES_2:
+		reg = REG_STATUS_AES_2;
+		break;
+	case PCXHR_CLOCK_TYPE_AES_3:
+		reg = REG_STATUS_AES_3;
+		break;
+	case PCXHR_CLOCK_TYPE_AES_4:
+		reg = REG_STATUS_AES_4;
+		break;
+	default:
+		return -EINVAL;
+	}
+	pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ);
+	rmh.cmd_len = 2;
+	rmh.cmd[0] |= IO_NUM_REG_STATUS;
+	if (mgr->last_reg_stat != reg) {
+		rmh.cmd[1]  = reg;
+		err = pcxhr_send_msg(mgr, &rmh);
+		if (err)
+			return err;
+		udelay(100);	/* wait minimum 2 sample_frames at 32kHz ! */
+		mgr->last_reg_stat = reg;
+	}
+	rmh.cmd[1]  = REG_STATUS_CURRENT;
+	err = pcxhr_send_msg(mgr, &rmh);
+	if (err)
+		return err;
+	switch (rmh.stat[1] & 0x0f) {
+	case REG_STATUS_SYNC_32000 :	rate = 32000; break;
+	case REG_STATUS_SYNC_44100 :	rate = 44100; break;
+	case REG_STATUS_SYNC_48000 :	rate = 48000; break;
+	case REG_STATUS_SYNC_64000 :	rate = 64000; break;
+	case REG_STATUS_SYNC_88200 :	rate = 88200; break;
+	case REG_STATUS_SYNC_96000 :	rate = 96000; break;
+	case REG_STATUS_SYNC_128000 :	rate = 128000; break;
+	case REG_STATUS_SYNC_176400 :	rate = 176400; break;
+	case REG_STATUS_SYNC_192000 :	rate = 192000; break;
+	default: rate = 0;
+	}
+	snd_printdd("External clock is at %d Hz\n", rate);
+	*sample_rate = rate;
+	return 0;
+}
+
+
+int pcxhr_get_external_clock(struct pcxhr_mgr *mgr,
+			     enum pcxhr_clock_type clock_type,
+			     int *sample_rate)
+{
+	if (mgr->is_hr_stereo)
+		return hr222_get_external_clock(mgr, clock_type,
+						sample_rate);
+	else
+		return pcxhr_sub_get_external_clock(mgr, clock_type,
+						    sample_rate);
+}
+
+/*
+ *  start or stop playback/capture substream
+ */
+static int pcxhr_set_stream_state(struct pcxhr_stream *stream)
+{
+	int err;
+	struct snd_pcxhr *chip;
+	struct pcxhr_rmh rmh;
+	int stream_mask, start;
+
+	if (stream->status == PCXHR_STREAM_STATUS_SCHEDULE_RUN)
+		start = 1;
+	else {
+		if (stream->status != PCXHR_STREAM_STATUS_SCHEDULE_STOP) {
+			snd_printk(KERN_ERR "ERROR pcxhr_set_stream_state "
+				   "CANNOT be stopped\n");
+			return -EINVAL;
+		}
+		start = 0;
+	}
+	if (!stream->substream)
+		return -EINVAL;
+
+	stream->timer_abs_periods = 0;
+	stream->timer_period_frag = 0;	/* reset theoretical stream pos */
+	stream->timer_buf_periods = 0;
+	stream->timer_is_synced = 0;
+
+	stream_mask =
+	  stream->pipe->is_capture ? 1 : 1<<stream->substream->number;
+
+	pcxhr_init_rmh(&rmh, start ? CMD_START_STREAM : CMD_STOP_STREAM);
+	pcxhr_set_pipe_cmd_params(&rmh, stream->pipe->is_capture,
+				  stream->pipe->first_audio, 0, stream_mask);
+
+	chip = snd_pcm_substream_chip(stream->substream);
+
+	err = pcxhr_send_msg(chip->mgr, &rmh);
+	if (err)
+		snd_printk(KERN_ERR "ERROR pcxhr_set_stream_state err=%x;\n",
+			   err);
+	stream->status =
+	  start ? PCXHR_STREAM_STATUS_STARTED : PCXHR_STREAM_STATUS_STOPPED;
+	return err;
+}
+
+#define HEADER_FMT_BASE_LIN		0xfed00000
+#define HEADER_FMT_BASE_FLOAT		0xfad00000
+#define HEADER_FMT_INTEL		0x00008000
+#define HEADER_FMT_24BITS		0x00004000
+#define HEADER_FMT_16BITS		0x00002000
+#define HEADER_FMT_UPTO11		0x00000200
+#define HEADER_FMT_UPTO32		0x00000100
+#define HEADER_FMT_MONO			0x00000080
+
+static int pcxhr_set_format(struct pcxhr_stream *stream)
+{
+	int err, is_capture, sample_rate, stream_num;
+	struct snd_pcxhr *chip;
+	struct pcxhr_rmh rmh;
+	unsigned int header;
+
+	switch (stream->format) {
+	case SNDRV_PCM_FORMAT_U8:
+		header = HEADER_FMT_BASE_LIN;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		header = HEADER_FMT_BASE_LIN |
+			 HEADER_FMT_16BITS | HEADER_FMT_INTEL;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE:
+		header = HEADER_FMT_BASE_LIN | HEADER_FMT_16BITS;
+		break;
+	case SNDRV_PCM_FORMAT_S24_3LE:
+		header = HEADER_FMT_BASE_LIN |
+			 HEADER_FMT_24BITS | HEADER_FMT_INTEL;
+		break;
+	case SNDRV_PCM_FORMAT_S24_3BE:
+		header = HEADER_FMT_BASE_LIN | HEADER_FMT_24BITS;
+		break;
+	case SNDRV_PCM_FORMAT_FLOAT_LE:
+		header = HEADER_FMT_BASE_FLOAT | HEADER_FMT_INTEL;
+		break;
+	default:
+		snd_printk(KERN_ERR
+			   "error pcxhr_set_format() : unknown format\n");
+		return -EINVAL;
+	}
+	chip = snd_pcm_substream_chip(stream->substream);
+
+	sample_rate = chip->mgr->sample_rate;
+	if (sample_rate <= 32000 && sample_rate !=0) {
+		if (sample_rate <= 11025)
+			header |= HEADER_FMT_UPTO11;
+		else
+			header |= HEADER_FMT_UPTO32;
+	}
+	if (stream->channels == 1)
+		header |= HEADER_FMT_MONO;
+
+	is_capture = stream->pipe->is_capture;
+	stream_num = is_capture ? 0 : stream->substream->number;
+
+	pcxhr_init_rmh(&rmh, is_capture ?
+		       CMD_FORMAT_STREAM_IN : CMD_FORMAT_STREAM_OUT);
+	pcxhr_set_pipe_cmd_params(&rmh, is_capture, stream->pipe->first_audio,
+				  stream_num, 0);
+	if (is_capture) {
+		/* bug with old dsp versions: */
+		/* bit 12 also sets the format of the playback stream */
+		if (DSP_EXT_CMD_SET(chip->mgr))
+			rmh.cmd[0] |= 1<<10;
+		else
+			rmh.cmd[0] |= 1<<12;
+	}
+	rmh.cmd[1] = 0;
+	rmh.cmd_len = 2;
+	if (DSP_EXT_CMD_SET(chip->mgr)) {
+		/* add channels and set bit 19 if channels>2 */
+		rmh.cmd[1] = stream->channels;
+		if (!is_capture) {
+			/* playback : add channel mask to command */
+			rmh.cmd[2] = (stream->channels == 1) ? 0x01 : 0x03;
+			rmh.cmd_len = 3;
+		}
+	}
+	rmh.cmd[rmh.cmd_len++] = header >> 8;
+	rmh.cmd[rmh.cmd_len++] = (header & 0xff) << 16;
+	err = pcxhr_send_msg(chip->mgr, &rmh);
+	if (err)
+		snd_printk(KERN_ERR "ERROR pcxhr_set_format err=%x;\n", err);
+	return err;
+}
+
+static int pcxhr_update_r_buffer(struct pcxhr_stream *stream)
+{
+	int err, is_capture, stream_num;
+	struct pcxhr_rmh rmh;
+	struct snd_pcm_substream *subs = stream->substream;
+	struct snd_pcxhr *chip = snd_pcm_substream_chip(subs);
+
+	is_capture = (subs->stream == SNDRV_PCM_STREAM_CAPTURE);
+	stream_num = is_capture ? 0 : subs->number;
+
+	snd_printdd("pcxhr_update_r_buffer(pcm%c%d) : "
+		    "addr(%p) bytes(%zx) subs(%d)\n",
+		    is_capture ? 'c' : 'p',
+		    chip->chip_idx, (void *)(long)subs->runtime->dma_addr,
+		    subs->runtime->dma_bytes, subs->number);
+
+	pcxhr_init_rmh(&rmh, CMD_UPDATE_R_BUFFERS);
+	pcxhr_set_pipe_cmd_params(&rmh, is_capture, stream->pipe->first_audio,
+				  stream_num, 0);
+
+	/* max buffer size is 2 MByte */
+	snd_BUG_ON(subs->runtime->dma_bytes >= 0x200000);
+	/* size in bits */
+	rmh.cmd[1] = subs->runtime->dma_bytes * 8;
+	/* most significant byte */
+	rmh.cmd[2] = subs->runtime->dma_addr >> 24;
+	/* this is a circular buffer */
+	rmh.cmd[2] |= 1<<19;
+	/* least 3 significant bytes */
+	rmh.cmd[3] = subs->runtime->dma_addr & MASK_DSP_WORD;
+	rmh.cmd_len = 4;
+	err = pcxhr_send_msg(chip->mgr, &rmh);
+	if (err)
+		snd_printk(KERN_ERR
+			   "ERROR CMD_UPDATE_R_BUFFERS err=%x;\n", err);
+	return err;
+}
+
+
+#if 0
+static int pcxhr_pipe_sample_count(struct pcxhr_stream *stream,
+				   snd_pcm_uframes_t *sample_count)
+{
+	struct pcxhr_rmh rmh;
+	int err;
+	pcxhr_t *chip = snd_pcm_substream_chip(stream->substream);
+	pcxhr_init_rmh(&rmh, CMD_PIPE_SAMPLE_COUNT);
+	pcxhr_set_pipe_cmd_params(&rmh, stream->pipe->is_capture, 0, 0,
+				  1<<stream->pipe->first_audio);
+	err = pcxhr_send_msg(chip->mgr, &rmh);
+	if (err == 0) {
+		*sample_count = ((snd_pcm_uframes_t)rmh.stat[0]) << 24;
+		*sample_count += (snd_pcm_uframes_t)rmh.stat[1];
+	}
+	snd_printdd("PIPE_SAMPLE_COUNT = %lx\n", *sample_count);
+	return err;
+}
+#endif
+
+static inline int pcxhr_stream_scheduled_get_pipe(struct pcxhr_stream *stream,
+						  struct pcxhr_pipe **pipe)
+{
+	if (stream->status == PCXHR_STREAM_STATUS_SCHEDULE_RUN) {
+		*pipe = stream->pipe;
+		return 1;
+	}
+	return 0;
+}
+
+static void pcxhr_trigger_tasklet(unsigned long arg)
+{
+	unsigned long flags;
+	int i, j, err;
+	struct pcxhr_pipe *pipe;
+	struct snd_pcxhr *chip;
+	struct pcxhr_mgr *mgr = (struct pcxhr_mgr*)(arg);
+	int capture_mask = 0;
+	int playback_mask = 0;
+
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+	struct timeval my_tv1, my_tv2;
+	do_gettimeofday(&my_tv1);
+#endif
+	mutex_lock(&mgr->setup_mutex);
+
+	/* check the pipes concerned and build pipe_array */
+	for (i = 0; i < mgr->num_cards; i++) {
+		chip = mgr->chip[i];
+		for (j = 0; j < chip->nb_streams_capt; j++) {
+			if (pcxhr_stream_scheduled_get_pipe(&chip->capture_stream[j], &pipe))
+				capture_mask |= (1 << pipe->first_audio);
+		}
+		for (j = 0; j < chip->nb_streams_play; j++) {
+			if (pcxhr_stream_scheduled_get_pipe(&chip->playback_stream[j], &pipe)) {
+				playback_mask |= (1 << pipe->first_audio);
+				break;	/* add only once, as all playback
+					 * streams of one chip use the same pipe
+					 */
+			}
+		}
+	}
+	if (capture_mask == 0 && playback_mask == 0) {
+		mutex_unlock(&mgr->setup_mutex);
+		snd_printk(KERN_ERR "pcxhr_trigger_tasklet : no pipes\n");
+		return;
+	}
+
+	snd_printdd("pcxhr_trigger_tasklet : "
+		    "playback_mask=%x capture_mask=%x\n",
+		    playback_mask, capture_mask);
+
+	/* synchronous stop of all the pipes concerned */
+	err = pcxhr_set_pipe_state(mgr,  playback_mask, capture_mask, 0);
+	if (err) {
+		mutex_unlock(&mgr->setup_mutex);
+		snd_printk(KERN_ERR "pcxhr_trigger_tasklet : "
+			   "error stop pipes (P%x C%x)\n",
+			   playback_mask, capture_mask);
+		return;
+	}
+
+	/* the dsp lost format and buffer info with the stop pipe */
+	for (i = 0; i < mgr->num_cards; i++) {
+		struct pcxhr_stream *stream;
+		chip = mgr->chip[i];
+		for (j = 0; j < chip->nb_streams_capt; j++) {
+			stream = &chip->capture_stream[j];
+			if (pcxhr_stream_scheduled_get_pipe(stream, &pipe)) {
+				err = pcxhr_set_format(stream);
+				err = pcxhr_update_r_buffer(stream);
+			}
+		}
+		for (j = 0; j < chip->nb_streams_play; j++) {
+			stream = &chip->playback_stream[j];
+			if (pcxhr_stream_scheduled_get_pipe(stream, &pipe)) {
+				err = pcxhr_set_format(stream);
+				err = pcxhr_update_r_buffer(stream);
+			}
+		}
+	}
+	/* start all the streams */
+	for (i = 0; i < mgr->num_cards; i++) {
+		struct pcxhr_stream *stream;
+		chip = mgr->chip[i];
+		for (j = 0; j < chip->nb_streams_capt; j++) {
+			stream = &chip->capture_stream[j];
+			if (pcxhr_stream_scheduled_get_pipe(stream, &pipe))
+				err = pcxhr_set_stream_state(stream);
+		}
+		for (j = 0; j < chip->nb_streams_play; j++) {
+			stream = &chip->playback_stream[j];
+			if (pcxhr_stream_scheduled_get_pipe(stream, &pipe))
+				err = pcxhr_set_stream_state(stream);
+		}
+	}
+
+	/* synchronous start of all the pipes concerned */
+	err = pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 1);
+	if (err) {
+		mutex_unlock(&mgr->setup_mutex);
+		snd_printk(KERN_ERR "pcxhr_trigger_tasklet : "
+			   "error start pipes (P%x C%x)\n",
+			   playback_mask, capture_mask);
+		return;
+	}
+
+	/* put the streams into the running state now
+	 * (increment pointer by interrupt)
+	 */
+	spin_lock_irqsave(&mgr->lock, flags);
+	for ( i =0; i < mgr->num_cards; i++) {
+		struct pcxhr_stream *stream;
+		chip = mgr->chip[i];
+		for(j = 0; j < chip->nb_streams_capt; j++) {
+			stream = &chip->capture_stream[j];
+			if(stream->status == PCXHR_STREAM_STATUS_STARTED)
+				stream->status = PCXHR_STREAM_STATUS_RUNNING;
+		}
+		for (j = 0; j < chip->nb_streams_play; j++) {
+			stream = &chip->playback_stream[j];
+			if (stream->status == PCXHR_STREAM_STATUS_STARTED) {
+				/* playback will already have advanced ! */
+				stream->timer_period_frag += mgr->granularity;
+				stream->status = PCXHR_STREAM_STATUS_RUNNING;
+			}
+		}
+	}
+	spin_unlock_irqrestore(&mgr->lock, flags);
+
+	mutex_unlock(&mgr->setup_mutex);
+
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+	do_gettimeofday(&my_tv2);
+	snd_printdd("***TRIGGER TASKLET*** TIME = %ld (err = %x)\n",
+		    (long)(my_tv2.tv_usec - my_tv1.tv_usec), err);
+#endif
+}
+
+
+/*
+ *  trigger callback
+ */
+static int pcxhr_trigger(struct snd_pcm_substream *subs, int cmd)
+{
+	struct pcxhr_stream *stream;
+	struct snd_pcm_substream *s;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_printdd("SNDRV_PCM_TRIGGER_START\n");
+		if (snd_pcm_stream_linked(subs)) {
+			struct snd_pcxhr *chip = snd_pcm_substream_chip(subs);
+			snd_pcm_group_for_each_entry(s, subs) {
+				if (snd_pcm_substream_chip(s) != chip)
+					continue;
+				stream = s->runtime->private_data;
+				stream->status =
+					PCXHR_STREAM_STATUS_SCHEDULE_RUN;
+				snd_pcm_trigger_done(s, subs);
+			}
+			tasklet_schedule(&chip->mgr->trigger_taskq);
+		} else {
+			stream = subs->runtime->private_data;
+			snd_printdd("Only one Substream %c %d\n",
+				    stream->pipe->is_capture ? 'C' : 'P',
+				    stream->pipe->first_audio);
+			if (pcxhr_set_format(stream))
+				return -EINVAL;
+			if (pcxhr_update_r_buffer(stream))
+				return -EINVAL;
+
+			stream->status = PCXHR_STREAM_STATUS_SCHEDULE_RUN;
+			if (pcxhr_set_stream_state(stream))
+				return -EINVAL;
+			stream->status = PCXHR_STREAM_STATUS_RUNNING;
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_printdd("SNDRV_PCM_TRIGGER_STOP\n");
+		snd_pcm_group_for_each_entry(s, subs) {
+			stream = s->runtime->private_data;
+			stream->status = PCXHR_STREAM_STATUS_SCHEDULE_STOP;
+			if (pcxhr_set_stream_state(stream))
+				return -EINVAL;
+			snd_pcm_trigger_done(s, subs);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		/* TODO */
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+static int pcxhr_hardware_timer(struct pcxhr_mgr *mgr, int start)
+{
+	struct pcxhr_rmh rmh;
+	int err;
+
+	pcxhr_init_rmh(&rmh, CMD_SET_TIMER_INTERRUPT);
+	if (start) {
+		/* last dsp time invalid */
+		mgr->dsp_time_last = PCXHR_DSP_TIME_INVALID;
+		rmh.cmd[0] |= mgr->granularity;
+	}
+	err = pcxhr_send_msg(mgr, &rmh);
+	if (err < 0)
+		snd_printk(KERN_ERR "error pcxhr_hardware_timer err(%x)\n",
+			   err);
+	return err;
+}
+
+/*
+ *  prepare callback for all pcms
+ */
+static int pcxhr_prepare(struct snd_pcm_substream *subs)
+{
+	struct snd_pcxhr *chip = snd_pcm_substream_chip(subs);
+	struct pcxhr_mgr *mgr = chip->mgr;
+	int err = 0;
+
+	snd_printdd("pcxhr_prepare : period_size(%lx) periods(%x) buffer_size(%lx)\n",
+		    subs->runtime->period_size, subs->runtime->periods,
+		    subs->runtime->buffer_size);
+
+	mutex_lock(&mgr->setup_mutex);
+
+	do {
+		/* only the first stream can choose the sample rate */
+		/* set the clock only once (first stream) */
+		if (mgr->sample_rate != subs->runtime->rate) {
+			err = pcxhr_set_clock(mgr, subs->runtime->rate);
+			if (err)
+				break;
+			if (mgr->sample_rate == 0)
+				/* start the DSP-timer */
+				err = pcxhr_hardware_timer(mgr, 1);
+			mgr->sample_rate = subs->runtime->rate;
+		}
+	} while(0);	/* do only once (so we can use break instead of goto) */
+
+	mutex_unlock(&mgr->setup_mutex);
+
+	return err;
+}
+
+
+/*
+ *  HW_PARAMS callback for all pcms
+ */
+static int pcxhr_hw_params(struct snd_pcm_substream *subs,
+			   struct snd_pcm_hw_params *hw)
+{
+	struct snd_pcxhr *chip = snd_pcm_substream_chip(subs);
+	struct pcxhr_mgr *mgr = chip->mgr;
+	struct pcxhr_stream *stream = subs->runtime->private_data;
+	snd_pcm_format_t format;
+	int err;
+	int channels;
+
+	/* set up channels */
+	channels = params_channels(hw);
+
+	/*  set up format for the stream */
+	format = params_format(hw);
+
+	mutex_lock(&mgr->setup_mutex);
+
+	stream->channels = channels;
+	stream->format = format;
+
+	/* allocate buffer */
+	err = snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw));
+
+	mutex_unlock(&mgr->setup_mutex);
+
+	return err;
+}
+
+static int pcxhr_hw_free(struct snd_pcm_substream *subs)
+{
+	snd_pcm_lib_free_pages(subs);
+	return 0;
+}
+
+
+/*
+ *  CONFIGURATION SPACE for all pcms, mono pcm must update channels_max
+ */
+static struct snd_pcm_hardware pcxhr_caps =
+{
+	.info             = (SNDRV_PCM_INFO_MMAP |
+			     SNDRV_PCM_INFO_INTERLEAVED |
+			     SNDRV_PCM_INFO_MMAP_VALID |
+			     SNDRV_PCM_INFO_SYNC_START),
+	.formats	  = (SNDRV_PCM_FMTBIT_U8 |
+			     SNDRV_PCM_FMTBIT_S16_LE |
+			     SNDRV_PCM_FMTBIT_S16_BE |
+			     SNDRV_PCM_FMTBIT_S24_3LE |
+			     SNDRV_PCM_FMTBIT_S24_3BE |
+			     SNDRV_PCM_FMTBIT_FLOAT_LE),
+	.rates            = (SNDRV_PCM_RATE_CONTINUOUS |
+			     SNDRV_PCM_RATE_8000_192000),
+	.rate_min         = 8000,
+	.rate_max         = 192000,
+	.channels_min     = 1,
+	.channels_max     = 2,
+	.buffer_bytes_max = (32*1024),
+	/* 1 byte == 1 frame U8 mono (PCXHR_GRANULARITY is frames!) */
+	.period_bytes_min = (2*PCXHR_GRANULARITY),
+	.period_bytes_max = (16*1024),
+	.periods_min      = 2,
+	.periods_max      = (32*1024/PCXHR_GRANULARITY),
+};
+
+
+static int pcxhr_open(struct snd_pcm_substream *subs)
+{
+	struct snd_pcxhr       *chip = snd_pcm_substream_chip(subs);
+	struct pcxhr_mgr       *mgr = chip->mgr;
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct pcxhr_stream    *stream;
+	int err;
+
+	mutex_lock(&mgr->setup_mutex);
+
+	/* copy the struct snd_pcm_hardware struct */
+	runtime->hw = pcxhr_caps;
+
+	if( subs->stream == SNDRV_PCM_STREAM_PLAYBACK ) {
+		snd_printdd("pcxhr_open playback chip%d subs%d\n",
+			    chip->chip_idx, subs->number);
+		stream = &chip->playback_stream[subs->number];
+	} else {
+		snd_printdd("pcxhr_open capture chip%d subs%d\n",
+			    chip->chip_idx, subs->number);
+		if (mgr->mono_capture)
+			runtime->hw.channels_max = 1;
+		else
+			runtime->hw.channels_min = 2;
+		stream = &chip->capture_stream[subs->number];
+	}
+	if (stream->status != PCXHR_STREAM_STATUS_FREE){
+		/* streams in use */
+		snd_printk(KERN_ERR "pcxhr_open chip%d subs%d in use\n",
+			   chip->chip_idx, subs->number);
+		mutex_unlock(&mgr->setup_mutex);
+		return -EBUSY;
+	}
+
+	/* float format support is in some cases buggy on stereo cards */
+	if (mgr->is_hr_stereo)
+		runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_FLOAT_LE;
+
+	/* buffer-size should better be multiple of period-size */
+	err = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0) {
+		mutex_unlock(&mgr->setup_mutex);
+		return err;
+	}
+
+	/* if a sample rate is already used or fixed by external clock,
+	 * the stream cannot change
+	 */
+	if (mgr->sample_rate)
+		runtime->hw.rate_min = runtime->hw.rate_max = mgr->sample_rate;
+	else {
+		if (mgr->use_clock_type != PCXHR_CLOCK_TYPE_INTERNAL) {
+			int external_rate;
+			if (pcxhr_get_external_clock(mgr, mgr->use_clock_type,
+						     &external_rate) ||
+			    external_rate == 0) {
+				/* cannot detect the external clock rate */
+				mutex_unlock(&mgr->setup_mutex);
+				return -EBUSY;
+			}
+			runtime->hw.rate_min = external_rate;
+			runtime->hw.rate_max = external_rate;
+		}
+	}
+
+	stream->status      = PCXHR_STREAM_STATUS_OPEN;
+	stream->substream   = subs;
+	stream->channels    = 0; /* not configured yet */
+
+	runtime->private_data = stream;
+
+	/* better get a divisor of granularity values (96 or 192) */
+	snd_pcm_hw_constraint_step(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32);
+	snd_pcm_hw_constraint_step(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32);
+	snd_pcm_set_sync(subs);
+
+	mgr->ref_count_rate++;
+
+	mutex_unlock(&mgr->setup_mutex);
+	return 0;
+}
+
+
+static int pcxhr_close(struct snd_pcm_substream *subs)
+{
+	struct snd_pcxhr *chip = snd_pcm_substream_chip(subs);
+	struct pcxhr_mgr *mgr = chip->mgr;
+	struct pcxhr_stream *stream = subs->runtime->private_data;
+
+	mutex_lock(&mgr->setup_mutex);
+
+	snd_printdd("pcxhr_close chip%d subs%d\n",
+		    chip->chip_idx, subs->number);
+
+	/* sample rate released */
+	if (--mgr->ref_count_rate == 0) {
+		mgr->sample_rate = 0;	/* the sample rate is no more locked */
+		pcxhr_hardware_timer(mgr, 0);	/* stop the DSP-timer */
+	}
+
+	stream->status    = PCXHR_STREAM_STATUS_FREE;
+	stream->substream = NULL;
+
+	mutex_unlock(&mgr->setup_mutex);
+
+	return 0;
+}
+
+
+static snd_pcm_uframes_t pcxhr_stream_pointer(struct snd_pcm_substream *subs)
+{
+	unsigned long flags;
+	u_int32_t timer_period_frag;
+	int timer_buf_periods;
+	struct snd_pcxhr *chip = snd_pcm_substream_chip(subs);
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct pcxhr_stream *stream  = runtime->private_data;
+
+	spin_lock_irqsave(&chip->mgr->lock, flags);
+
+	/* get the period fragment and the nb of periods in the buffer */
+	timer_period_frag = stream->timer_period_frag;
+	timer_buf_periods = stream->timer_buf_periods;
+
+	spin_unlock_irqrestore(&chip->mgr->lock, flags);
+
+	return (snd_pcm_uframes_t)((timer_buf_periods * runtime->period_size) +
+				   timer_period_frag);
+}
+
+
+static struct snd_pcm_ops pcxhr_ops = {
+	.open      = pcxhr_open,
+	.close     = pcxhr_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.prepare   = pcxhr_prepare,
+	.hw_params = pcxhr_hw_params,
+	.hw_free   = pcxhr_hw_free,
+	.trigger   = pcxhr_trigger,
+	.pointer   = pcxhr_stream_pointer,
+};
+
+/*
+ */
+int pcxhr_create_pcm(struct snd_pcxhr *chip)
+{
+	int err;
+	struct snd_pcm *pcm;
+	char name[32];
+
+	sprintf(name, "pcxhr %d", chip->chip_idx);
+	if ((err = snd_pcm_new(chip->card, name, 0,
+			       chip->nb_streams_play,
+			       chip->nb_streams_capt, &pcm)) < 0) {
+		snd_printk(KERN_ERR "cannot create pcm %s\n", name);
+		return err;
+	}
+	pcm->private_data = chip;
+
+	if (chip->nb_streams_play)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcxhr_ops);
+	if (chip->nb_streams_capt)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcxhr_ops);
+
+	pcm->info_flags = 0;
+	strcpy(pcm->name, name);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->mgr->pci),
+					      32*1024, 32*1024);
+	chip->pcm = pcm;
+	return 0;
+}
+
+static int pcxhr_chip_free(struct snd_pcxhr *chip)
+{
+	kfree(chip);
+	return 0;
+}
+
+static int pcxhr_chip_dev_free(struct snd_device *device)
+{
+	struct snd_pcxhr *chip = device->device_data;
+	return pcxhr_chip_free(chip);
+}
+
+
+/*
+ */
+static int __devinit pcxhr_create(struct pcxhr_mgr *mgr,
+				  struct snd_card *card, int idx)
+{
+	int err;
+	struct snd_pcxhr *chip;
+	static struct snd_device_ops ops = {
+		.dev_free = pcxhr_chip_dev_free,
+	};
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (! chip) {
+		snd_printk(KERN_ERR "cannot allocate chip\n");
+		return -ENOMEM;
+	}
+
+	chip->card = card;
+	chip->chip_idx = idx;
+	chip->mgr = mgr;
+
+	if (idx < mgr->playback_chips)
+		/* stereo or mono streams */
+		chip->nb_streams_play = PCXHR_PLAYBACK_STREAMS;
+
+	if (idx < mgr->capture_chips) {
+		if (mgr->mono_capture)
+			chip->nb_streams_capt = 2;	/* 2 mono streams */
+		else
+			chip->nb_streams_capt = 1;	/* or 1 stereo stream */
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		pcxhr_chip_free(chip);
+		return err;
+	}
+
+	mgr->chip[idx] = chip;
+	snd_card_set_dev(card, &mgr->pci->dev);
+
+	return 0;
+}
+
+/* proc interface */
+static void pcxhr_proc_info(struct snd_info_entry *entry,
+			    struct snd_info_buffer *buffer)
+{
+	struct snd_pcxhr *chip = entry->private_data;
+	struct pcxhr_mgr *mgr = chip->mgr;
+
+	snd_iprintf(buffer, "\n%s\n", mgr->longname);
+
+	/* stats available when embedded DSP is running */
+	if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) {
+		struct pcxhr_rmh rmh;
+		short ver_maj = (mgr->dsp_version >> 16) & 0xff;
+		short ver_min = (mgr->dsp_version >> 8) & 0xff;
+		short ver_build = mgr->dsp_version & 0xff;
+		snd_iprintf(buffer, "module version %s\n",
+			    PCXHR_DRIVER_VERSION_STRING);
+		snd_iprintf(buffer, "dsp version %d.%d.%d\n",
+			    ver_maj, ver_min, ver_build);
+		if (mgr->board_has_analog)
+			snd_iprintf(buffer, "analog io available\n");
+		else
+			snd_iprintf(buffer, "digital only board\n");
+
+		/* calc cpu load of the dsp */
+		pcxhr_init_rmh(&rmh, CMD_GET_DSP_RESOURCES);
+		if( ! pcxhr_send_msg(mgr, &rmh) ) {
+			int cur = rmh.stat[0];
+			int ref = rmh.stat[1];
+			if (ref > 0) {
+				if (mgr->sample_rate_real != 0 &&
+				    mgr->sample_rate_real != 48000) {
+					ref = (ref * 48000) /
+					  mgr->sample_rate_real;
+					if (mgr->sample_rate_real >=
+					    PCXHR_IRQ_TIMER_FREQ)
+						ref *= 2;
+				}
+				cur = 100 - (100 * cur) / ref;
+				snd_iprintf(buffer, "cpu load    %d%%\n", cur);
+				snd_iprintf(buffer, "buffer pool %d/%d\n",
+					    rmh.stat[2], rmh.stat[3]);
+			}
+		}
+		snd_iprintf(buffer, "dma granularity : %d\n",
+			    mgr->granularity);
+		snd_iprintf(buffer, "dsp time errors : %d\n",
+			    mgr->dsp_time_err);
+		snd_iprintf(buffer, "dsp async pipe xrun errors : %d\n",
+			    mgr->async_err_pipe_xrun);
+		snd_iprintf(buffer, "dsp async stream xrun errors : %d\n",
+			    mgr->async_err_stream_xrun);
+		snd_iprintf(buffer, "dsp async last other error : %x\n",
+			    mgr->async_err_other_last);
+		/* debug zone dsp */
+		rmh.cmd[0] = 0x4200 + PCXHR_SIZE_MAX_STATUS;
+		rmh.cmd_len = 1;
+		rmh.stat_len = PCXHR_SIZE_MAX_STATUS;
+		rmh.dsp_stat = 0;
+		rmh.cmd_idx = CMD_LAST_INDEX;
+		if( ! pcxhr_send_msg(mgr, &rmh) ) {
+			int i;
+			if (rmh.stat_len > 8)
+				rmh.stat_len = 8;
+			for (i = 0; i < rmh.stat_len; i++)
+				snd_iprintf(buffer, "debug[%02d] = %06x\n",
+					    i,  rmh.stat[i]);
+		}
+	} else
+		snd_iprintf(buffer, "no firmware loaded\n");
+	snd_iprintf(buffer, "\n");
+}
+static void pcxhr_proc_sync(struct snd_info_entry *entry,
+			    struct snd_info_buffer *buffer)
+{
+	struct snd_pcxhr *chip = entry->private_data;
+	struct pcxhr_mgr *mgr = chip->mgr;
+	static const char *textsHR22[3] = {
+		"Internal", "AES Sync", "AES 1"
+	};
+	static const char *textsPCXHR[7] = {
+		"Internal", "Word", "AES Sync",
+		"AES 1", "AES 2", "AES 3", "AES 4"
+	};
+	const char **texts;
+	int max_clock;
+	if (mgr->is_hr_stereo) {
+		texts = textsHR22;
+		max_clock = HR22_CLOCK_TYPE_MAX;
+	} else {
+		texts = textsPCXHR;
+		max_clock = PCXHR_CLOCK_TYPE_MAX;
+	}
+
+	snd_iprintf(buffer, "\n%s\n", mgr->longname);
+	snd_iprintf(buffer, "Current Sample Clock\t: %s\n",
+		    texts[mgr->cur_clock_type]);
+	snd_iprintf(buffer, "Current Sample Rate\t= %d\n",
+		    mgr->sample_rate_real);
+	/* commands available when embedded DSP is running */
+	if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) {
+		int i, err, sample_rate;
+		for (i = 1; i <= max_clock; i++) {
+			err = pcxhr_get_external_clock(mgr, i, &sample_rate);
+			if (err)
+				break;
+			snd_iprintf(buffer, "%s Clock\t\t= %d\n",
+				    texts[i], sample_rate);
+		}
+	} else
+		snd_iprintf(buffer, "no firmware loaded\n");
+	snd_iprintf(buffer, "\n");
+}
+
+static void pcxhr_proc_gpio_read(struct snd_info_entry *entry,
+				 struct snd_info_buffer *buffer)
+{
+	struct snd_pcxhr *chip = entry->private_data;
+	struct pcxhr_mgr *mgr = chip->mgr;
+	/* commands available when embedded DSP is running */
+	if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) {
+		/* gpio ports on stereo boards only available */
+		int value = 0;
+		hr222_read_gpio(mgr, 1, &value);	/* GPI */
+		snd_iprintf(buffer, "GPI: 0x%x\n", value);
+		hr222_read_gpio(mgr, 0, &value);	/* GP0 */
+		snd_iprintf(buffer, "GPO: 0x%x\n", value);
+	} else
+		snd_iprintf(buffer, "no firmware loaded\n");
+	snd_iprintf(buffer, "\n");
+}
+static void pcxhr_proc_gpo_write(struct snd_info_entry *entry,
+				 struct snd_info_buffer *buffer)
+{
+	struct snd_pcxhr *chip = entry->private_data;
+	struct pcxhr_mgr *mgr = chip->mgr;
+	char line[64];
+	int value;
+	/* commands available when embedded DSP is running */
+	if (!(mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)))
+		return;
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		if (sscanf(line, "GPO: 0x%x", &value) != 1)
+			continue;
+		hr222_write_gpo(mgr, value);	/* GP0 */
+	}
+}
+
+static void __devinit pcxhr_proc_init(struct snd_pcxhr *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(chip->card, "info", &entry))
+		snd_info_set_text_ops(entry, chip, pcxhr_proc_info);
+	if (! snd_card_proc_new(chip->card, "sync", &entry))
+		snd_info_set_text_ops(entry, chip, pcxhr_proc_sync);
+	/* gpio available on stereo sound cards only */
+	if (chip->mgr->is_hr_stereo &&
+	    !snd_card_proc_new(chip->card, "gpio", &entry)) {
+		snd_info_set_text_ops(entry, chip, pcxhr_proc_gpio_read);
+		entry->c.text.write = pcxhr_proc_gpo_write;
+		entry->mode |= S_IWUSR;
+	}
+}
+/* end of proc interface */
+
+/*
+ * release all the cards assigned to a manager instance
+ */
+static int pcxhr_free(struct pcxhr_mgr *mgr)
+{
+	unsigned int i;
+
+	for (i = 0; i < mgr->num_cards; i++) {
+		if (mgr->chip[i])
+			snd_card_free(mgr->chip[i]->card);
+	}
+
+	/* reset board if some firmware was loaded */
+	if(mgr->dsp_loaded) {
+		pcxhr_reset_board(mgr);
+		snd_printdd("reset pcxhr !\n");
+	}
+
+	/* release irq  */
+	if (mgr->irq >= 0)
+		free_irq(mgr->irq, mgr);
+
+	pci_release_regions(mgr->pci);
+
+	/* free hostport purgebuffer */
+	if (mgr->hostport.area) {
+		snd_dma_free_pages(&mgr->hostport);
+		mgr->hostport.area = NULL;
+	}
+
+	kfree(mgr->prmh);
+
+	pci_disable_device(mgr->pci);
+	kfree(mgr);
+	return 0;
+}
+
+/*
+ *    probe function - creates the card manager
+ */
+static int __devinit pcxhr_probe(struct pci_dev *pci,
+				 const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct pcxhr_mgr *mgr;
+	unsigned int i;
+	int err;
+	size_t size;
+	char *card_name;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (! enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	/* enable PCI device */
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	pci_set_master(pci);
+
+	/* check if we can restrict PCI DMA transfers to 32 bits */
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(32)) < 0) {
+		snd_printk(KERN_ERR "architecture does not support "
+			   "32bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+
+	/* alloc card manager */
+	mgr = kzalloc(sizeof(*mgr), GFP_KERNEL);
+	if (! mgr) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	if (snd_BUG_ON(pci_id->driver_data >= PCI_ID_LAST)) {
+		kfree(mgr);
+		pci_disable_device(pci);
+		return -ENODEV;
+	}
+	card_name =
+		pcxhr_board_params[pci_id->driver_data].board_name;
+	mgr->playback_chips =
+		pcxhr_board_params[pci_id->driver_data].playback_chips;
+	mgr->capture_chips  =
+		pcxhr_board_params[pci_id->driver_data].capture_chips;
+	mgr->fw_file_set =
+		pcxhr_board_params[pci_id->driver_data].fw_file_set;
+	mgr->firmware_num  =
+		pcxhr_board_params[pci_id->driver_data].firmware_num;
+	mgr->mono_capture = mono[dev];
+	mgr->is_hr_stereo = (mgr->playback_chips == 1);
+	mgr->board_has_aes1 = PCXHR_BOARD_HAS_AES1(mgr);
+	mgr->board_aes_in_192k = !PCXHR_BOARD_AESIN_NO_192K(mgr);
+
+	if (mgr->is_hr_stereo)
+		mgr->granularity = PCXHR_GRANULARITY_HR22;
+	else
+		mgr->granularity = PCXHR_GRANULARITY;
+
+	/* resource assignment */
+	if ((err = pci_request_regions(pci, card_name)) < 0) {
+		kfree(mgr);
+		pci_disable_device(pci);
+		return err;
+	}
+	for (i = 0; i < 3; i++)
+		mgr->port[i] = pci_resource_start(pci, i);
+
+	mgr->pci = pci;
+	mgr->irq = -1;
+
+	if (request_irq(pci->irq, pcxhr_interrupt, IRQF_SHARED,
+			card_name, mgr)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		pcxhr_free(mgr);
+		return -EBUSY;
+	}
+	mgr->irq = pci->irq;
+
+	sprintf(mgr->shortname, "Digigram %s", card_name);
+	sprintf(mgr->longname, "%s at 0x%lx & 0x%lx, 0x%lx irq %i",
+		mgr->shortname,
+		mgr->port[0], mgr->port[1], mgr->port[2], mgr->irq);
+
+	/* ISR spinlock  */
+	spin_lock_init(&mgr->lock);
+	spin_lock_init(&mgr->msg_lock);
+
+	/* init setup mutex*/
+	mutex_init(&mgr->setup_mutex);
+
+	/* init taslket */
+	tasklet_init(&mgr->msg_taskq, pcxhr_msg_tasklet,
+		     (unsigned long) mgr);
+	tasklet_init(&mgr->trigger_taskq, pcxhr_trigger_tasklet,
+		     (unsigned long) mgr);
+
+	mgr->prmh = kmalloc(sizeof(*mgr->prmh) + 
+			    sizeof(u32) * (PCXHR_SIZE_MAX_LONG_STATUS -
+					   PCXHR_SIZE_MAX_STATUS),
+			    GFP_KERNEL);
+	if (! mgr->prmh) {
+		pcxhr_free(mgr);
+		return -ENOMEM;
+	}
+
+	for (i=0; i < PCXHR_MAX_CARDS; i++) {
+		struct snd_card *card;
+		char tmpid[16];
+		int idx;
+
+		if (i >= max(mgr->playback_chips, mgr->capture_chips))
+			break;
+		mgr->num_cards++;
+
+		if (index[dev] < 0)
+			idx = index[dev];
+		else
+			idx = index[dev] + i;
+
+		snprintf(tmpid, sizeof(tmpid), "%s-%d",
+			 id[dev] ? id[dev] : card_name, i);
+		err = snd_card_create(idx, tmpid, THIS_MODULE, 0, &card);
+
+		if (err < 0) {
+			snd_printk(KERN_ERR "cannot allocate the card %d\n", i);
+			pcxhr_free(mgr);
+			return err;
+		}
+
+		strcpy(card->driver, DRIVER_NAME);
+		sprintf(card->shortname, "%s [PCM #%d]", mgr->shortname, i);
+		sprintf(card->longname, "%s [PCM #%d]", mgr->longname, i);
+
+		if ((err = pcxhr_create(mgr, card, i)) < 0) {
+			snd_card_free(card);
+			pcxhr_free(mgr);
+			return err;
+		}
+
+		if (i == 0)
+			/* init proc interface only for chip0 */
+			pcxhr_proc_init(mgr->chip[i]);
+
+		if ((err = snd_card_register(card)) < 0) {
+			pcxhr_free(mgr);
+			return err;
+		}
+	}
+
+	/* create hostport purgebuffer */
+	size = PAGE_ALIGN(sizeof(struct pcxhr_hostport));
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				size, &mgr->hostport) < 0) {
+		pcxhr_free(mgr);
+		return -ENOMEM;
+	}
+	/* init purgebuffer */
+	memset(mgr->hostport.area, 0, size);
+
+	/* create a DSP loader */
+	err = pcxhr_setup_firmware(mgr);
+	if (err < 0) {
+		pcxhr_free(mgr);
+		return err;
+	}
+
+	pci_set_drvdata(pci, mgr);
+	dev++;
+	return 0;
+}
+
+static void __devexit pcxhr_remove(struct pci_dev *pci)
+{
+	pcxhr_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "Digigram pcxhr",
+	.id_table = pcxhr_ids,
+	.probe = pcxhr_probe,
+	.remove = __devexit_p(pcxhr_remove),
+};
+
+static int __init pcxhr_module_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit pcxhr_module_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(pcxhr_module_init)
+module_exit(pcxhr_module_exit)
diff --git a/linux/sound/pci/pcxhr/pcxhr.h b/linux/sound/pci/pcxhr/pcxhr.h
new file mode 100644
index 0000000..bda776c
--- /dev/null
+++ b/linux/sound/pci/pcxhr/pcxhr.h
@@ -0,0 +1,216 @@
+/*
+ * Driver for Digigram pcxhr soundcards
+ *
+ * main header file
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ *
+ *   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 __SOUND_PCXHR_H
+#define __SOUND_PCXHR_H
+
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <sound/pcm.h>
+
+#define PCXHR_DRIVER_VERSION		0x000906	/* 0.9.6 */
+#define PCXHR_DRIVER_VERSION_STRING	"0.9.6"		/* 0.9.6 */
+
+
+#define PCXHR_MAX_CARDS		6
+#define PCXHR_PLAYBACK_STREAMS	4
+
+#define PCXHR_GRANULARITY	96	/* min 96 and multiple of 48 */
+/* transfer granularity of pipes and the dsp time (MBOX4) */
+#define PCXHR_GRANULARITY_MIN	96
+/* TODO : granularity could be 64 or 128 */
+#define PCXHR_GRANULARITY_HR22	192	/* granularity for stereo cards */
+
+struct snd_pcxhr;
+struct pcxhr_mgr;
+
+struct pcxhr_stream;
+struct pcxhr_pipe;
+
+enum pcxhr_clock_type {
+	PCXHR_CLOCK_TYPE_INTERNAL = 0,
+	PCXHR_CLOCK_TYPE_WORD_CLOCK,
+	PCXHR_CLOCK_TYPE_AES_SYNC,
+	PCXHR_CLOCK_TYPE_AES_1,
+	PCXHR_CLOCK_TYPE_AES_2,
+	PCXHR_CLOCK_TYPE_AES_3,
+	PCXHR_CLOCK_TYPE_AES_4,
+	PCXHR_CLOCK_TYPE_MAX = PCXHR_CLOCK_TYPE_AES_4,
+	HR22_CLOCK_TYPE_INTERNAL = PCXHR_CLOCK_TYPE_INTERNAL,
+	HR22_CLOCK_TYPE_AES_SYNC,
+	HR22_CLOCK_TYPE_AES_1,
+	HR22_CLOCK_TYPE_MAX = HR22_CLOCK_TYPE_AES_1,
+};
+
+struct pcxhr_mgr {
+	unsigned int num_cards;
+	struct snd_pcxhr *chip[PCXHR_MAX_CARDS];
+
+	struct pci_dev *pci;
+
+	int irq;
+
+	int granularity;
+
+	/* card access with 1 mem bar and 2 io bar's */
+	unsigned long port[3];
+
+	/* share the name */
+	char shortname[32];		/* short name of this soundcard */
+	char longname[96];		/* name of this soundcard */
+
+	/* message tasklet */
+	struct tasklet_struct msg_taskq;
+	struct pcxhr_rmh *prmh;
+	/* trigger tasklet */
+	struct tasklet_struct trigger_taskq;
+
+	spinlock_t lock;		/* interrupt spinlock */
+	spinlock_t msg_lock;		/* message spinlock */
+
+	struct mutex setup_mutex;	/* mutex used in hw_params, open and close */
+	struct mutex mixer_mutex;	/* mutex for mixer */
+
+	/* hardware interface */
+	unsigned int dsp_loaded;	/* bit flags of loaded dsp indices */
+	unsigned int dsp_version;	/* read from embedded once firmware is loaded */
+	int playback_chips;
+	int capture_chips;
+	int fw_file_set;
+	int firmware_num;
+	unsigned int is_hr_stereo:1;
+	unsigned int board_has_aes1:1;	/* if 1 board has AES1 plug and SRC */
+	unsigned int board_has_analog:1; /* if 0 the board is digital only */
+	unsigned int board_has_mic:1; /* if 1 the board has microphone input */
+	unsigned int board_aes_in_192k:1;/* if 1 the aes input plugs do support 192kHz */
+	unsigned int mono_capture:1; /* if 1 the board does mono capture */
+
+	struct snd_dma_buffer hostport;
+
+	enum pcxhr_clock_type use_clock_type;	/* clock type selected by mixer */
+	enum pcxhr_clock_type cur_clock_type;	/* current clock type synced */
+	int sample_rate;
+	int ref_count_rate;
+	int timer_toggle;		/* timer interrupt toggles between the two values 0x200 and 0x300 */
+	int dsp_time_last;		/* the last dsp time (read by interrupt) */
+	int dsp_time_err;		/* dsp time errors */
+	unsigned int src_it_dsp;	/* dsp interrupt source */
+	unsigned int io_num_reg_cont;	/* backup of IO_NUM_REG_CONT */
+	unsigned int codec_speed;	/* speed mode of the codecs */
+	unsigned int sample_rate_real;	/* current real sample rate */
+	int last_reg_stat;
+	int async_err_stream_xrun;
+	int async_err_pipe_xrun;
+	int async_err_other_last;
+
+	unsigned char xlx_cfg;		/* copy of PCXHR_XLX_CFG register */
+	unsigned char xlx_selmic;	/* copy of PCXHR_XLX_SELMIC register */
+	unsigned char dsp_reset;	/* copy of PCXHR_DSP_RESET register */
+};
+
+
+enum pcxhr_stream_status {
+	PCXHR_STREAM_STATUS_FREE,
+	PCXHR_STREAM_STATUS_OPEN,
+	PCXHR_STREAM_STATUS_SCHEDULE_RUN,
+	PCXHR_STREAM_STATUS_STARTED,
+	PCXHR_STREAM_STATUS_RUNNING,
+	PCXHR_STREAM_STATUS_SCHEDULE_STOP,
+	PCXHR_STREAM_STATUS_STOPPED,
+	PCXHR_STREAM_STATUS_PAUSED
+};
+
+struct pcxhr_stream {
+	struct snd_pcm_substream *substream;
+	snd_pcm_format_t format;
+	struct pcxhr_pipe *pipe;
+
+	enum pcxhr_stream_status status;	/* free, open, running, draining, pause */
+
+	u_int64_t timer_abs_periods;	/* timer: samples elapsed since TRIGGER_START (multiple of period_size) */
+	u_int32_t timer_period_frag;	/* timer: samples elapsed since last call to snd_pcm_period_elapsed (0..period_size) */
+	u_int32_t timer_buf_periods;	/* nb of periods in the buffer that have already elapsed */
+	int timer_is_synced;		/* if(0) : timer needs to be resynced with real hardware pointer */
+
+	int channels;
+};
+
+
+enum pcxhr_pipe_status {
+	PCXHR_PIPE_UNDEFINED,
+	PCXHR_PIPE_DEFINED
+};
+
+struct pcxhr_pipe {
+	enum pcxhr_pipe_status status;
+	int is_capture;		/* this is a capture pipe */
+	int first_audio;	/* first audio num */
+};
+
+
+struct snd_pcxhr {
+	struct snd_card *card;
+	struct pcxhr_mgr *mgr;
+	int chip_idx;		/* zero based */
+
+	struct snd_pcm *pcm;		/* PCM */
+
+	struct pcxhr_pipe playback_pipe;	/* 1 stereo pipe only */
+	struct pcxhr_pipe capture_pipe[2];	/* 1 stereo or 2 mono pipes */
+
+	struct pcxhr_stream playback_stream[PCXHR_PLAYBACK_STREAMS];
+	struct pcxhr_stream capture_stream[2];	/* 1 stereo or 2 mono streams */
+	int nb_streams_play;
+	int nb_streams_capt;
+
+	int analog_playback_active[2];	/* Mixer : Master Playback !mute */
+	int analog_playback_volume[2];	/* Mixer : Master Playback Volume */
+	int analog_capture_volume[2];	/* Mixer : Master Capture Volume */
+	int digital_playback_active[PCXHR_PLAYBACK_STREAMS][2];
+	int digital_playback_volume[PCXHR_PLAYBACK_STREAMS][2];
+	int digital_capture_volume[2];	/* Mixer : Digital Capture Volume */
+	int monitoring_active[2];	/* Mixer : Monitoring Active */
+	int monitoring_volume[2];	/* Mixer : Monitoring Volume */
+	int audio_capture_source;	/* Mixer : Audio Capture Source */
+	int mic_volume;			/* used by cards with MIC only */
+	int mic_boost;			/* used by cards with MIC only */
+	int mic_active;			/* used by cards with MIC only */
+	int analog_capture_active;	/* used by cards with MIC only */
+	int phantom_power;		/* used by cards with MIC only */
+
+	unsigned char aes_bits[5];	/* Mixer : IEC958_AES bits */
+};
+
+struct pcxhr_hostport
+{
+	char purgebuffer[6];
+	char reserved[2];
+};
+
+/* exported */
+int pcxhr_create_pcm(struct snd_pcxhr *chip);
+int pcxhr_set_clock(struct pcxhr_mgr *mgr, unsigned int rate);
+int pcxhr_get_external_clock(struct pcxhr_mgr *mgr,
+			     enum pcxhr_clock_type clock_type,
+			     int *sample_rate);
+
+#endif /* __SOUND_PCXHR_H */
diff --git a/linux/sound/pci/pcxhr/pcxhr_core.c b/linux/sound/pci/pcxhr/pcxhr_core.c
new file mode 100644
index 0000000..833e718
--- /dev/null
+++ b/linux/sound/pci/pcxhr/pcxhr_core.c
@@ -0,0 +1,1305 @@
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * low level interface with interrupt and message handling implementation
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+#include <sound/core.h>
+#include "pcxhr.h"
+#include "pcxhr_mixer.h"
+#include "pcxhr_hwdep.h"
+#include "pcxhr_core.h"
+
+
+/* registers used on the PLX (port 1) */
+#define PCXHR_PLX_OFFSET_MIN	0x40
+#define PCXHR_PLX_MBOX0		0x40
+#define PCXHR_PLX_MBOX1		0x44
+#define PCXHR_PLX_MBOX2		0x48
+#define PCXHR_PLX_MBOX3		0x4C
+#define PCXHR_PLX_MBOX4		0x50
+#define PCXHR_PLX_MBOX5		0x54
+#define PCXHR_PLX_MBOX6		0x58
+#define PCXHR_PLX_MBOX7		0x5C
+#define PCXHR_PLX_L2PCIDB	0x64
+#define PCXHR_PLX_IRQCS		0x68
+#define PCXHR_PLX_CHIPSC	0x6C
+
+/* registers used on the DSP (port 2) */
+#define PCXHR_DSP_ICR		0x00
+#define PCXHR_DSP_CVR		0x04
+#define PCXHR_DSP_ISR		0x08
+#define PCXHR_DSP_IVR		0x0C
+#define PCXHR_DSP_RXH		0x14
+#define PCXHR_DSP_TXH		0x14
+#define PCXHR_DSP_RXM		0x18
+#define PCXHR_DSP_TXM		0x18
+#define PCXHR_DSP_RXL		0x1C
+#define PCXHR_DSP_TXL		0x1C
+#define PCXHR_DSP_RESET		0x20
+#define PCXHR_DSP_OFFSET_MAX	0x20
+
+/* access to the card */
+#define PCXHR_PLX 1
+#define PCXHR_DSP 2
+
+#if (PCXHR_DSP_OFFSET_MAX > PCXHR_PLX_OFFSET_MIN)
+#undef  PCXHR_REG_TO_PORT(x)
+#else
+#define PCXHR_REG_TO_PORT(x)	((x)>PCXHR_DSP_OFFSET_MAX ? PCXHR_PLX : PCXHR_DSP)
+#endif
+#define PCXHR_INPB(mgr,x)	inb((mgr)->port[PCXHR_REG_TO_PORT(x)] + (x))
+#define PCXHR_INPL(mgr,x)	inl((mgr)->port[PCXHR_REG_TO_PORT(x)] + (x))
+#define PCXHR_OUTPB(mgr,x,data)	outb((data), (mgr)->port[PCXHR_REG_TO_PORT(x)] + (x))
+#define PCXHR_OUTPL(mgr,x,data)	outl((data), (mgr)->port[PCXHR_REG_TO_PORT(x)] + (x))
+/* attention : access the PCXHR_DSP_* registers with inb and outb only ! */
+
+/* params used with PCXHR_PLX_MBOX0 */
+#define PCXHR_MBOX0_HF5			(1 << 0)
+#define PCXHR_MBOX0_HF4			(1 << 1)
+#define PCXHR_MBOX0_BOOT_HERE		(1 << 23)
+/* params used with PCXHR_PLX_IRQCS */
+#define PCXHR_IRQCS_ENABLE_PCIIRQ	(1 << 8)
+#define PCXHR_IRQCS_ENABLE_PCIDB	(1 << 9)
+#define PCXHR_IRQCS_ACTIVE_PCIDB	(1 << 13)
+/* params used with PCXHR_PLX_CHIPSC */
+#define PCXHR_CHIPSC_INIT_VALUE		0x100D767E
+#define PCXHR_CHIPSC_RESET_XILINX	(1 << 16)
+#define PCXHR_CHIPSC_GPI_USERI		(1 << 17)
+#define PCXHR_CHIPSC_DATA_CLK		(1 << 24)
+#define PCXHR_CHIPSC_DATA_IN		(1 << 26)
+
+/* params used with PCXHR_DSP_ICR */
+#define PCXHR_ICR_HI08_RREQ		0x01
+#define PCXHR_ICR_HI08_TREQ		0x02
+#define PCXHR_ICR_HI08_HDRQ		0x04
+#define PCXHR_ICR_HI08_HF0		0x08
+#define PCXHR_ICR_HI08_HF1		0x10
+#define PCXHR_ICR_HI08_HLEND		0x20
+#define PCXHR_ICR_HI08_INIT		0x80
+/* params used with PCXHR_DSP_CVR */
+#define PCXHR_CVR_HI08_HC		0x80
+/* params used with PCXHR_DSP_ISR */
+#define PCXHR_ISR_HI08_RXDF		0x01
+#define PCXHR_ISR_HI08_TXDE		0x02
+#define PCXHR_ISR_HI08_TRDY		0x04
+#define PCXHR_ISR_HI08_ERR		0x08
+#define PCXHR_ISR_HI08_CHK		0x10
+#define PCXHR_ISR_HI08_HREQ		0x80
+
+
+/* constants used for delay in msec */
+#define PCXHR_WAIT_DEFAULT		2
+#define PCXHR_WAIT_IT			25
+#define PCXHR_WAIT_IT_EXTRA		65
+
+/*
+ * pcxhr_check_reg_bit - wait for the specified bit is set/reset on a register
+ * @reg: register to check
+ * @mask: bit mask
+ * @bit: resultant bit to be checked
+ * @time: time-out of loop in msec
+ *
+ * returns zero if a bit matches, or a negative error code.
+ */
+static int pcxhr_check_reg_bit(struct pcxhr_mgr *mgr, unsigned int reg,
+			       unsigned char mask, unsigned char bit, int time,
+			       unsigned char* read)
+{
+	int i = 0;
+	unsigned long end_time = jiffies + (time * HZ + 999) / 1000;
+	do {
+		*read = PCXHR_INPB(mgr, reg);
+		if ((*read & mask) == bit) {
+			if (i > 100)
+				snd_printdd("ATTENTION! check_reg(%x) "
+					    "loopcount=%d\n",
+					    reg, i);
+			return 0;
+		}
+		i++;
+	} while (time_after_eq(end_time, jiffies));
+	snd_printk(KERN_ERR
+		   "pcxhr_check_reg_bit: timeout, reg=%x, mask=0x%x, val=%x\n",
+		   reg, mask, *read);
+	return -EIO;
+}
+
+/* constants used with pcxhr_check_reg_bit() */
+#define PCXHR_TIMEOUT_DSP		200
+
+
+#define PCXHR_MASK_EXTRA_INFO		0x0000FE
+#define PCXHR_MASK_IT_HF0		0x000100
+#define PCXHR_MASK_IT_HF1		0x000200
+#define PCXHR_MASK_IT_NO_HF0_HF1	0x000400
+#define PCXHR_MASK_IT_MANAGE_HF5	0x000800
+#define PCXHR_MASK_IT_WAIT		0x010000
+#define PCXHR_MASK_IT_WAIT_EXTRA	0x020000
+
+#define PCXHR_IT_SEND_BYTE_XILINX	(0x0000003C | PCXHR_MASK_IT_HF0)
+#define PCXHR_IT_TEST_XILINX		(0x0000003C | PCXHR_MASK_IT_HF1 | \
+					 PCXHR_MASK_IT_MANAGE_HF5)
+#define PCXHR_IT_DOWNLOAD_BOOT		(0x0000000C | PCXHR_MASK_IT_HF1 | \
+					 PCXHR_MASK_IT_MANAGE_HF5 | \
+					 PCXHR_MASK_IT_WAIT)
+#define PCXHR_IT_RESET_BOARD_FUNC	(0x0000000C | PCXHR_MASK_IT_HF0 | \
+					 PCXHR_MASK_IT_MANAGE_HF5 | \
+					 PCXHR_MASK_IT_WAIT_EXTRA)
+#define PCXHR_IT_DOWNLOAD_DSP		(0x0000000C | \
+					 PCXHR_MASK_IT_MANAGE_HF5 | \
+					 PCXHR_MASK_IT_WAIT)
+#define PCXHR_IT_DEBUG			(0x0000005A | PCXHR_MASK_IT_NO_HF0_HF1)
+#define PCXHR_IT_RESET_SEMAPHORE	(0x0000005C | PCXHR_MASK_IT_NO_HF0_HF1)
+#define PCXHR_IT_MESSAGE		(0x00000074 | PCXHR_MASK_IT_NO_HF0_HF1)
+#define PCXHR_IT_RESET_CHK		(0x00000076 | PCXHR_MASK_IT_NO_HF0_HF1)
+#define PCXHR_IT_UPDATE_RBUFFER		(0x00000078 | PCXHR_MASK_IT_NO_HF0_HF1)
+
+static int pcxhr_send_it_dsp(struct pcxhr_mgr *mgr,
+			     unsigned int itdsp, int atomic)
+{
+	int err;
+	unsigned char reg;
+
+	if (itdsp & PCXHR_MASK_IT_MANAGE_HF5) {
+		/* clear hf5 bit */
+		PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX0,
+			    PCXHR_INPL(mgr, PCXHR_PLX_MBOX0) &
+			    ~PCXHR_MBOX0_HF5);
+	}
+	if ((itdsp & PCXHR_MASK_IT_NO_HF0_HF1) == 0) {
+		reg = (PCXHR_ICR_HI08_RREQ |
+		       PCXHR_ICR_HI08_TREQ |
+		       PCXHR_ICR_HI08_HDRQ);
+		if (itdsp & PCXHR_MASK_IT_HF0)
+			reg |= PCXHR_ICR_HI08_HF0;
+		if (itdsp & PCXHR_MASK_IT_HF1)
+			reg |= PCXHR_ICR_HI08_HF1;
+		PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg);
+	}
+	reg = (unsigned char)(((itdsp & PCXHR_MASK_EXTRA_INFO) >> 1) |
+			      PCXHR_CVR_HI08_HC);
+	PCXHR_OUTPB(mgr, PCXHR_DSP_CVR, reg);
+	if (itdsp & PCXHR_MASK_IT_WAIT) {
+		if (atomic)
+			mdelay(PCXHR_WAIT_IT);
+		else
+			msleep(PCXHR_WAIT_IT);
+	}
+	if (itdsp & PCXHR_MASK_IT_WAIT_EXTRA) {
+		if (atomic)
+			mdelay(PCXHR_WAIT_IT_EXTRA);
+		else
+			msleep(PCXHR_WAIT_IT);
+	}
+	/* wait for CVR_HI08_HC == 0 */
+	err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_CVR,  PCXHR_CVR_HI08_HC, 0,
+				  PCXHR_TIMEOUT_DSP, &reg);
+	if (err) {
+		snd_printk(KERN_ERR "pcxhr_send_it_dsp : TIMEOUT CVR\n");
+		return err;
+	}
+	if (itdsp & PCXHR_MASK_IT_MANAGE_HF5) {
+		/* wait for hf5 bit */
+		err = pcxhr_check_reg_bit(mgr, PCXHR_PLX_MBOX0,
+					  PCXHR_MBOX0_HF5,
+					  PCXHR_MBOX0_HF5,
+					  PCXHR_TIMEOUT_DSP,
+					  &reg);
+		if (err) {
+			snd_printk(KERN_ERR
+				   "pcxhr_send_it_dsp : TIMEOUT HF5\n");
+			return err;
+		}
+	}
+	return 0; /* retry not handled here */
+}
+
+void pcxhr_reset_xilinx_com(struct pcxhr_mgr *mgr)
+{
+	/* reset second xilinx */
+	PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC,
+		    PCXHR_CHIPSC_INIT_VALUE & ~PCXHR_CHIPSC_RESET_XILINX);
+}
+
+static void pcxhr_enable_irq(struct pcxhr_mgr *mgr, int enable)
+{
+	unsigned int reg = PCXHR_INPL(mgr, PCXHR_PLX_IRQCS);
+	/* enable/disable interrupts */
+	if (enable)
+		reg |=  (PCXHR_IRQCS_ENABLE_PCIIRQ | PCXHR_IRQCS_ENABLE_PCIDB);
+	else
+		reg &= ~(PCXHR_IRQCS_ENABLE_PCIIRQ | PCXHR_IRQCS_ENABLE_PCIDB);
+	PCXHR_OUTPL(mgr, PCXHR_PLX_IRQCS, reg);
+}
+
+void pcxhr_reset_dsp(struct pcxhr_mgr *mgr)
+{
+	/* disable interrupts */
+	pcxhr_enable_irq(mgr, 0);
+
+	/* let's reset the DSP */
+	PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, 0);
+	msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */
+	PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, 3);
+	msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */
+
+	/* reset mailbox */
+	PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX0, 0);
+}
+
+void pcxhr_enable_dsp(struct pcxhr_mgr *mgr)
+{
+	/* enable interrupts */
+	pcxhr_enable_irq(mgr, 1);
+}
+
+/*
+ * load the xilinx image
+ */
+int pcxhr_load_xilinx_binary(struct pcxhr_mgr *mgr,
+			     const struct firmware *xilinx, int second)
+{
+	unsigned int i;
+	unsigned int chipsc;
+	unsigned char data;
+	unsigned char mask;
+	const unsigned char *image;
+
+	/* test first xilinx */
+	chipsc = PCXHR_INPL(mgr, PCXHR_PLX_CHIPSC);
+	/* REV01 cards do not support the PCXHR_CHIPSC_GPI_USERI bit anymore */
+	/* this bit will always be 1;
+	 * no possibility to test presence of first xilinx
+	 */
+	if(second) {
+		if ((chipsc & PCXHR_CHIPSC_GPI_USERI) == 0) {
+			snd_printk(KERN_ERR "error loading first xilinx\n");
+			return -EINVAL;
+		}
+		/* activate second xilinx */
+		chipsc |= PCXHR_CHIPSC_RESET_XILINX;
+		PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc);
+		msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */
+	}
+	image = xilinx->data;
+	for (i = 0; i < xilinx->size; i++, image++) {
+		data = *image;
+		mask = 0x80;
+		while (mask) {
+			chipsc &= ~(PCXHR_CHIPSC_DATA_CLK |
+				    PCXHR_CHIPSC_DATA_IN);
+			if (data & mask)
+				chipsc |= PCXHR_CHIPSC_DATA_IN;
+			PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc);
+			chipsc |= PCXHR_CHIPSC_DATA_CLK;
+			PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc);
+			mask >>= 1;
+		}
+		/* don't take too much time in this loop... */
+		cond_resched();
+	}
+	chipsc &= ~(PCXHR_CHIPSC_DATA_CLK | PCXHR_CHIPSC_DATA_IN);
+	PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc);
+	/* wait 2 msec (time to boot the xilinx before any access) */
+	msleep( PCXHR_WAIT_DEFAULT );
+	return 0;
+}
+
+/*
+ * send an executable file to the DSP
+ */
+static int pcxhr_download_dsp(struct pcxhr_mgr *mgr, const struct firmware *dsp)
+{
+	int err;
+	unsigned int i;
+	unsigned int len;
+	const unsigned char *data;
+	unsigned char dummy;
+	/* check the length of boot image */
+	if (dsp->size <= 0)
+		return -EINVAL;
+	if (dsp->size % 3)
+		return -EINVAL;
+	if (snd_BUG_ON(!dsp->data))
+		return -EINVAL;
+	/* transfert data buffer from PC to DSP */
+	for (i = 0; i < dsp->size; i += 3) {
+		data = dsp->data + i;
+		if (i == 0) {
+			/* test data header consistency */
+			len = (unsigned int)((data[0]<<16) +
+					     (data[1]<<8) +
+					     data[2]);
+			if (len && (dsp->size != (len + 2) * 3))
+				return -EINVAL;
+		}
+		/* wait DSP ready for new transfer */
+		err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR,
+					  PCXHR_ISR_HI08_TRDY,
+					  PCXHR_ISR_HI08_TRDY,
+					  PCXHR_TIMEOUT_DSP, &dummy);
+		if (err) {
+			snd_printk(KERN_ERR
+				   "dsp loading error at position %d\n", i);
+			return err;
+		}
+		/* send host data */
+		PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, data[0]);
+		PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, data[1]);
+		PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, data[2]);
+
+		/* don't take too much time in this loop... */
+		cond_resched();
+	}
+	/* give some time to boot the DSP */
+	msleep(PCXHR_WAIT_DEFAULT);
+	return 0;
+}
+
+/*
+ * load the eeprom image
+ */
+int pcxhr_load_eeprom_binary(struct pcxhr_mgr *mgr,
+			     const struct firmware *eeprom)
+{
+	int err;
+	unsigned char reg;
+
+	/* init value of the ICR register */
+	reg = PCXHR_ICR_HI08_RREQ | PCXHR_ICR_HI08_TREQ | PCXHR_ICR_HI08_HDRQ;
+	if (PCXHR_INPL(mgr, PCXHR_PLX_MBOX0) & PCXHR_MBOX0_BOOT_HERE) {
+		/* no need to load the eeprom binary,
+		 * but init the HI08 interface
+		 */
+		PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg | PCXHR_ICR_HI08_INIT);
+		msleep(PCXHR_WAIT_DEFAULT);
+		PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg);
+		msleep(PCXHR_WAIT_DEFAULT);
+		snd_printdd("no need to load eeprom boot\n");
+		return 0;
+	}
+	PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg);
+
+	err = pcxhr_download_dsp(mgr, eeprom);
+	if (err)
+		return err;
+	/* wait for chk bit */
+	return pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK,
+				   PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, &reg);
+}
+
+/*
+ * load the boot image
+ */
+int pcxhr_load_boot_binary(struct pcxhr_mgr *mgr, const struct firmware *boot)
+{
+	int err;
+	unsigned int physaddr = mgr->hostport.addr;
+	unsigned char dummy;
+
+	/* send the hostport address to the DSP (only the upper 24 bit !) */
+	if (snd_BUG_ON(physaddr & 0xff))
+		return -EINVAL;
+	PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX1, (physaddr >> 8));
+
+	err = pcxhr_send_it_dsp(mgr, PCXHR_IT_DOWNLOAD_BOOT, 0);
+	if (err)
+		return err;
+	/* clear hf5 bit */
+	PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX0,
+		    PCXHR_INPL(mgr, PCXHR_PLX_MBOX0) & ~PCXHR_MBOX0_HF5);
+
+	err = pcxhr_download_dsp(mgr, boot);
+	if (err)
+		return err;
+	/* wait for hf5 bit */
+	return pcxhr_check_reg_bit(mgr, PCXHR_PLX_MBOX0, PCXHR_MBOX0_HF5,
+				   PCXHR_MBOX0_HF5, PCXHR_TIMEOUT_DSP, &dummy);
+}
+
+/*
+ * load the final dsp image
+ */
+int pcxhr_load_dsp_binary(struct pcxhr_mgr *mgr, const struct firmware *dsp)
+{
+	int err;
+	unsigned char dummy;
+	err = pcxhr_send_it_dsp(mgr, PCXHR_IT_RESET_BOARD_FUNC, 0);
+	if (err)
+		return err;
+	err = pcxhr_send_it_dsp(mgr, PCXHR_IT_DOWNLOAD_DSP, 0);
+	if (err)
+		return err;
+	err = pcxhr_download_dsp(mgr, dsp);
+	if (err)
+		return err;
+	/* wait for chk bit */
+	return pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR,
+				   PCXHR_ISR_HI08_CHK,
+				   PCXHR_ISR_HI08_CHK,
+				   PCXHR_TIMEOUT_DSP, &dummy);
+}
+
+
+struct pcxhr_cmd_info {
+	u32 opcode;		/* command word */
+	u16 st_length;		/* status length */
+	u16 st_type;		/* status type (RMH_SSIZE_XXX) */
+};
+
+/* RMH status type */
+enum {
+	RMH_SSIZE_FIXED = 0,	/* status size fix (st_length = 0..x) */
+	RMH_SSIZE_ARG = 1,	/* status size given in the LSB byte */
+	RMH_SSIZE_MASK = 2,	/* status size given in bitmask */
+};
+
+/*
+ * Array of DSP commands
+ */
+static struct pcxhr_cmd_info pcxhr_dsp_cmds[] = {
+[CMD_VERSION] =				{ 0x010000, 1, RMH_SSIZE_FIXED },
+[CMD_SUPPORTED] =			{ 0x020000, 4, RMH_SSIZE_FIXED },
+[CMD_TEST_IT] =				{ 0x040000, 1, RMH_SSIZE_FIXED },
+[CMD_SEND_IRQA] =			{ 0x070001, 0, RMH_SSIZE_FIXED },
+[CMD_ACCESS_IO_WRITE] =			{ 0x090000, 1, RMH_SSIZE_ARG },
+[CMD_ACCESS_IO_READ] =			{ 0x094000, 1, RMH_SSIZE_ARG },
+[CMD_ASYNC] =				{ 0x0a0000, 1, RMH_SSIZE_ARG },
+[CMD_MODIFY_CLOCK] =			{ 0x0d0000, 0, RMH_SSIZE_FIXED },
+[CMD_RESYNC_AUDIO_INPUTS] =		{ 0x0e0000, 0, RMH_SSIZE_FIXED },
+[CMD_GET_DSP_RESOURCES] =		{ 0x100000, 4, RMH_SSIZE_FIXED },
+[CMD_SET_TIMER_INTERRUPT] =		{ 0x110000, 0, RMH_SSIZE_FIXED },
+[CMD_RES_PIPE] =			{ 0x400000, 0, RMH_SSIZE_FIXED },
+[CMD_FREE_PIPE] =			{ 0x410000, 0, RMH_SSIZE_FIXED },
+[CMD_CONF_PIPE] =			{ 0x422101, 0, RMH_SSIZE_FIXED },
+[CMD_STOP_PIPE] =			{ 0x470004, 0, RMH_SSIZE_FIXED },
+[CMD_PIPE_SAMPLE_COUNT] =		{ 0x49a000, 2, RMH_SSIZE_FIXED },
+[CMD_CAN_START_PIPE] =			{ 0x4b0000, 1, RMH_SSIZE_FIXED },
+[CMD_START_STREAM] =			{ 0x802000, 0, RMH_SSIZE_FIXED },
+[CMD_STREAM_OUT_LEVEL_ADJUST] =		{ 0x822000, 0, RMH_SSIZE_FIXED },
+[CMD_STOP_STREAM] =			{ 0x832000, 0, RMH_SSIZE_FIXED },
+[CMD_UPDATE_R_BUFFERS] =		{ 0x840000, 0, RMH_SSIZE_FIXED },
+[CMD_FORMAT_STREAM_OUT] =		{ 0x860000, 0, RMH_SSIZE_FIXED },
+[CMD_FORMAT_STREAM_IN] =		{ 0x870000, 0, RMH_SSIZE_FIXED },
+[CMD_STREAM_SAMPLE_COUNT] =		{ 0x902000, 2, RMH_SSIZE_FIXED },
+[CMD_AUDIO_LEVEL_ADJUST] =		{ 0xc22000, 0, RMH_SSIZE_FIXED },
+};
+
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+static char* cmd_names[] = {
+[CMD_VERSION] =				"CMD_VERSION",
+[CMD_SUPPORTED] =			"CMD_SUPPORTED",
+[CMD_TEST_IT] =				"CMD_TEST_IT",
+[CMD_SEND_IRQA] =			"CMD_SEND_IRQA",
+[CMD_ACCESS_IO_WRITE] =			"CMD_ACCESS_IO_WRITE",
+[CMD_ACCESS_IO_READ] =			"CMD_ACCESS_IO_READ",
+[CMD_ASYNC] =				"CMD_ASYNC",
+[CMD_MODIFY_CLOCK] =			"CMD_MODIFY_CLOCK",
+[CMD_RESYNC_AUDIO_INPUTS] =		"CMD_RESYNC_AUDIO_INPUTS",
+[CMD_GET_DSP_RESOURCES] =		"CMD_GET_DSP_RESOURCES",
+[CMD_SET_TIMER_INTERRUPT] =		"CMD_SET_TIMER_INTERRUPT",
+[CMD_RES_PIPE] =			"CMD_RES_PIPE",
+[CMD_FREE_PIPE] =			"CMD_FREE_PIPE",
+[CMD_CONF_PIPE] =			"CMD_CONF_PIPE",
+[CMD_STOP_PIPE] =			"CMD_STOP_PIPE",
+[CMD_PIPE_SAMPLE_COUNT] =		"CMD_PIPE_SAMPLE_COUNT",
+[CMD_CAN_START_PIPE] =			"CMD_CAN_START_PIPE",
+[CMD_START_STREAM] =			"CMD_START_STREAM",
+[CMD_STREAM_OUT_LEVEL_ADJUST] =		"CMD_STREAM_OUT_LEVEL_ADJUST",
+[CMD_STOP_STREAM] =			"CMD_STOP_STREAM",
+[CMD_UPDATE_R_BUFFERS] =		"CMD_UPDATE_R_BUFFERS",
+[CMD_FORMAT_STREAM_OUT] =		"CMD_FORMAT_STREAM_OUT",
+[CMD_FORMAT_STREAM_IN] =		"CMD_FORMAT_STREAM_IN",
+[CMD_STREAM_SAMPLE_COUNT] =		"CMD_STREAM_SAMPLE_COUNT",
+[CMD_AUDIO_LEVEL_ADJUST] =		"CMD_AUDIO_LEVEL_ADJUST",
+};
+#endif
+
+
+static int pcxhr_read_rmh_status(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh)
+{
+	int err;
+	int i;
+	u32 data;
+	u32 size_mask;
+	unsigned char reg;
+	int max_stat_len;
+
+	if (rmh->stat_len < PCXHR_SIZE_MAX_STATUS)
+		max_stat_len = PCXHR_SIZE_MAX_STATUS;
+	else	max_stat_len = rmh->stat_len;
+
+	for (i = 0; i < rmh->stat_len; i++) {
+		/* wait for receiver full */
+		err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR,
+					  PCXHR_ISR_HI08_RXDF,
+					  PCXHR_ISR_HI08_RXDF,
+					  PCXHR_TIMEOUT_DSP, &reg);
+		if (err) {
+			snd_printk(KERN_ERR "ERROR RMH stat: "
+				   "ISR:RXDF=1 (ISR = %x; i=%d )\n",
+				   reg, i);
+			return err;
+		}
+		/* read data */
+		data  = PCXHR_INPB(mgr, PCXHR_DSP_TXH) << 16;
+		data |= PCXHR_INPB(mgr, PCXHR_DSP_TXM) << 8;
+		data |= PCXHR_INPB(mgr, PCXHR_DSP_TXL);
+
+		/* need to update rmh->stat_len on the fly ?? */
+		if (!i) {
+			if (rmh->dsp_stat != RMH_SSIZE_FIXED) {
+				if (rmh->dsp_stat == RMH_SSIZE_ARG) {
+					rmh->stat_len = (data & 0x0000ff) + 1;
+					data &= 0xffff00;
+				} else {
+					/* rmh->dsp_stat == RMH_SSIZE_MASK */
+					rmh->stat_len = 1;
+					size_mask = data;
+					while (size_mask) {
+						if (size_mask & 1)
+							rmh->stat_len++;
+						size_mask >>= 1;
+					}
+				}
+			}
+		}
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+		if (rmh->cmd_idx < CMD_LAST_INDEX)
+			snd_printdd("    stat[%d]=%x\n", i, data);
+#endif
+		if (i < max_stat_len)
+			rmh->stat[i] = data;
+	}
+	if (rmh->stat_len > max_stat_len) {
+		snd_printdd("PCXHR : rmh->stat_len=%x too big\n",
+			    rmh->stat_len);
+		rmh->stat_len = max_stat_len;
+	}
+	return 0;
+}
+
+static int pcxhr_send_msg_nolock(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh)
+{
+	int err;
+	int i;
+	u32 data;
+	unsigned char reg;
+
+	if (snd_BUG_ON(rmh->cmd_len >= PCXHR_SIZE_MAX_CMD))
+		return -EINVAL;
+	err = pcxhr_send_it_dsp(mgr, PCXHR_IT_MESSAGE, 1);
+	if (err) {
+		snd_printk(KERN_ERR "pcxhr_send_message : ED_DSP_CRASHED\n");
+		return err;
+	}
+	/* wait for chk bit */
+	err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK,
+				  PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, &reg);
+	if (err)
+		return err;
+	/* reset irq chk */
+	err = pcxhr_send_it_dsp(mgr, PCXHR_IT_RESET_CHK, 1);
+	if (err)
+		return err;
+	/* wait for chk bit == 0*/
+	err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, 0,
+				  PCXHR_TIMEOUT_DSP, &reg);
+	if (err)
+		return err;
+
+	data = rmh->cmd[0];
+
+	if (rmh->cmd_len > 1)
+		data |= 0x008000;	/* MASK_MORE_THAN_1_WORD_COMMAND */
+	else
+		data &= 0xff7fff;	/* MASK_1_WORD_COMMAND */
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+	if (rmh->cmd_idx < CMD_LAST_INDEX)
+		snd_printdd("MSG cmd[0]=%x (%s)\n",
+			    data, cmd_names[rmh->cmd_idx]);
+#endif
+
+	err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_TRDY,
+				  PCXHR_ISR_HI08_TRDY, PCXHR_TIMEOUT_DSP, &reg);
+	if (err)
+		return err;
+	PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF);
+	PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF);
+	PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF));
+
+	if (rmh->cmd_len > 1) {
+		/* send length */
+		data = rmh->cmd_len - 1;
+		err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR,
+					  PCXHR_ISR_HI08_TRDY,
+					  PCXHR_ISR_HI08_TRDY,
+					  PCXHR_TIMEOUT_DSP, &reg);
+		if (err)
+			return err;
+		PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF);
+		PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF);
+		PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF));
+
+		for (i=1; i < rmh->cmd_len; i++) {
+			/* send other words */
+			data = rmh->cmd[i];
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+			if (rmh->cmd_idx < CMD_LAST_INDEX)
+				snd_printdd("    cmd[%d]=%x\n", i, data);
+#endif
+			err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR,
+						  PCXHR_ISR_HI08_TRDY,
+						  PCXHR_ISR_HI08_TRDY,
+						  PCXHR_TIMEOUT_DSP, &reg);
+			if (err)
+				return err;
+			PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF);
+			PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF);
+			PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF));
+		}
+	}
+	/* wait for chk bit */
+	err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK,
+				  PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, &reg);
+	if (err)
+		return err;
+	/* test status ISR */
+	if (reg & PCXHR_ISR_HI08_ERR) {
+		/* ERROR, wait for receiver full */
+		err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR,
+					  PCXHR_ISR_HI08_RXDF,
+					  PCXHR_ISR_HI08_RXDF,
+					  PCXHR_TIMEOUT_DSP, &reg);
+		if (err) {
+			snd_printk(KERN_ERR "ERROR RMH: ISR:RXDF=1 (ISR = %x)\n", reg);
+			return err;
+		}
+		/* read error code */
+		data  = PCXHR_INPB(mgr, PCXHR_DSP_TXH) << 16;
+		data |= PCXHR_INPB(mgr, PCXHR_DSP_TXM) << 8;
+		data |= PCXHR_INPB(mgr, PCXHR_DSP_TXL);
+		snd_printk(KERN_ERR "ERROR RMH(%d): 0x%x\n",
+			   rmh->cmd_idx, data);
+		err = -EINVAL;
+	} else {
+		/* read the response data */
+		err = pcxhr_read_rmh_status(mgr, rmh);
+	}
+	/* reset semaphore */
+	if (pcxhr_send_it_dsp(mgr, PCXHR_IT_RESET_SEMAPHORE, 1) < 0)
+		return -EIO;
+	return err;
+}
+
+
+/**
+ * pcxhr_init_rmh - initialize the RMH instance
+ * @rmh: the rmh pointer to be initialized
+ * @cmd: the rmh command to be set
+ */
+void pcxhr_init_rmh(struct pcxhr_rmh *rmh, int cmd)
+{
+	if (snd_BUG_ON(cmd >= CMD_LAST_INDEX))
+		return;
+	rmh->cmd[0] = pcxhr_dsp_cmds[cmd].opcode;
+	rmh->cmd_len = 1;
+	rmh->stat_len = pcxhr_dsp_cmds[cmd].st_length;
+	rmh->dsp_stat = pcxhr_dsp_cmds[cmd].st_type;
+	rmh->cmd_idx = cmd;
+}
+
+
+void pcxhr_set_pipe_cmd_params(struct pcxhr_rmh *rmh, int capture,
+			       unsigned int param1, unsigned int param2,
+			       unsigned int param3)
+{
+	snd_BUG_ON(param1 > MASK_FIRST_FIELD);
+	if (capture)
+		rmh->cmd[0] |= 0x800;		/* COMMAND_RECORD_MASK */
+	if (param1)
+		rmh->cmd[0] |= (param1 << FIELD_SIZE);
+	if (param2) {
+		snd_BUG_ON(param2 > MASK_FIRST_FIELD);
+		rmh->cmd[0] |= param2;
+	}
+	if(param3) {
+		snd_BUG_ON(param3 > MASK_DSP_WORD);
+		rmh->cmd[1] = param3;
+		rmh->cmd_len = 2;
+	}
+}
+
+/*
+ * pcxhr_send_msg - send a DSP message with spinlock
+ * @rmh: the rmh record to send and receive
+ *
+ * returns 0 if successful, or a negative error code.
+ */
+int pcxhr_send_msg(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh)
+{
+	unsigned long flags;
+	int err;
+	spin_lock_irqsave(&mgr->msg_lock, flags);
+	err = pcxhr_send_msg_nolock(mgr, rmh);
+	spin_unlock_irqrestore(&mgr->msg_lock, flags);
+	return err;
+}
+
+static inline int pcxhr_pipes_running(struct pcxhr_mgr *mgr)
+{
+	int start_mask = PCXHR_INPL(mgr, PCXHR_PLX_MBOX2);
+	/* least segnificant 12 bits are the pipe states
+	 * for the playback audios
+	 * next 12 bits are the pipe states for the capture audios
+	 * (PCXHR_PIPE_STATE_CAPTURE_OFFSET)
+	 */
+	start_mask &= 0xffffff;
+	snd_printdd("CMD_PIPE_STATE MBOX2=0x%06x\n", start_mask);
+	return start_mask;
+}
+
+#define PCXHR_PIPE_STATE_CAPTURE_OFFSET		12
+#define MAX_WAIT_FOR_DSP			20
+
+static int pcxhr_prepair_pipe_start(struct pcxhr_mgr *mgr,
+				    int audio_mask, int *retry)
+{
+	struct pcxhr_rmh rmh;
+	int err;
+	int audio = 0;
+
+	*retry = 0;
+	while (audio_mask) {
+		if (audio_mask & 1) {
+			pcxhr_init_rmh(&rmh, CMD_CAN_START_PIPE);
+			if (audio < PCXHR_PIPE_STATE_CAPTURE_OFFSET) {
+				/* can start playback pipe */
+				pcxhr_set_pipe_cmd_params(&rmh, 0, audio, 0, 0);
+			} else {
+				/* can start capture pipe */
+				pcxhr_set_pipe_cmd_params(&rmh, 1, audio -
+						PCXHR_PIPE_STATE_CAPTURE_OFFSET,
+						0, 0);
+			}
+			err = pcxhr_send_msg(mgr, &rmh);
+			if (err) {
+				snd_printk(KERN_ERR
+					   "error pipe start "
+					   "(CMD_CAN_START_PIPE) err=%x!\n",
+					   err);
+				return err;
+			}
+			/* if the pipe couldn't be prepaired for start,
+			 * retry it later
+			 */
+			if (rmh.stat[0] == 0)
+				*retry |= (1<<audio);
+		}
+		audio_mask>>=1;
+		audio++;
+	}
+	return 0;
+}
+
+static int pcxhr_stop_pipes(struct pcxhr_mgr *mgr, int audio_mask)
+{
+	struct pcxhr_rmh rmh;
+	int err;
+	int audio = 0;
+
+	while (audio_mask) {
+		if (audio_mask & 1) {
+			pcxhr_init_rmh(&rmh, CMD_STOP_PIPE);
+			if (audio < PCXHR_PIPE_STATE_CAPTURE_OFFSET) {
+				/* stop playback pipe */
+				pcxhr_set_pipe_cmd_params(&rmh, 0, audio, 0, 0);
+			} else {
+				/* stop capture pipe */
+				pcxhr_set_pipe_cmd_params(&rmh, 1, audio -
+						PCXHR_PIPE_STATE_CAPTURE_OFFSET,
+						0, 0);
+			}
+			err = pcxhr_send_msg(mgr, &rmh);
+			if (err) {
+				snd_printk(KERN_ERR
+					   "error pipe stop "
+					   "(CMD_STOP_PIPE) err=%x!\n", err);
+				return err;
+			}
+		}
+		audio_mask>>=1;
+		audio++;
+	}
+	return 0;
+}
+
+static int pcxhr_toggle_pipes(struct pcxhr_mgr *mgr, int audio_mask)
+{
+	struct pcxhr_rmh rmh;
+	int err;
+	int audio = 0;
+
+	while (audio_mask) {
+		if (audio_mask & 1) {
+			pcxhr_init_rmh(&rmh, CMD_CONF_PIPE);
+			if (audio < PCXHR_PIPE_STATE_CAPTURE_OFFSET)
+				pcxhr_set_pipe_cmd_params(&rmh, 0, 0, 0,
+							  1 << audio);
+			else
+				pcxhr_set_pipe_cmd_params(&rmh, 1, 0, 0,
+							  1 << (audio - PCXHR_PIPE_STATE_CAPTURE_OFFSET));
+			err = pcxhr_send_msg(mgr, &rmh);
+			if (err) {
+				snd_printk(KERN_ERR
+					   "error pipe start "
+					   "(CMD_CONF_PIPE) err=%x!\n", err);
+				return err;
+			}
+		}
+		audio_mask>>=1;
+		audio++;
+	}
+	/* now fire the interrupt on the card */
+	pcxhr_init_rmh(&rmh, CMD_SEND_IRQA);
+	err = pcxhr_send_msg(mgr, &rmh);
+	if (err) {
+		snd_printk(KERN_ERR
+			   "error pipe start (CMD_SEND_IRQA) err=%x!\n",
+			   err);
+		return err;
+	}
+	return 0;
+}
+
+
+
+int pcxhr_set_pipe_state(struct pcxhr_mgr *mgr, int playback_mask,
+			 int capture_mask, int start)
+{
+	int state, i, err;
+	int audio_mask;
+
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+	struct timeval my_tv1, my_tv2;
+	do_gettimeofday(&my_tv1);
+#endif
+	audio_mask = (playback_mask |
+		      (capture_mask << PCXHR_PIPE_STATE_CAPTURE_OFFSET));
+	/* current pipe state (playback + record) */
+	state = pcxhr_pipes_running(mgr);
+	snd_printdd("pcxhr_set_pipe_state %s (mask %x current %x)\n",
+		    start ? "START" : "STOP", audio_mask, state);
+	if (start) {
+		/* start only pipes that are not yet started */
+		audio_mask &= ~state;
+		state = audio_mask;
+		for (i = 0; i < MAX_WAIT_FOR_DSP; i++) {
+			err = pcxhr_prepair_pipe_start(mgr, state, &state);
+			if (err)
+				return err;
+			if (state == 0)
+				break;	/* success, all pipes prepaired */
+			mdelay(1);	/* wait 1 millisecond and retry */
+		}
+	} else {
+		audio_mask &= state;	/* stop only pipes that are started */
+	}
+	if (audio_mask == 0)
+		return 0;
+
+	err = pcxhr_toggle_pipes(mgr, audio_mask);
+	if (err)
+		return err;
+
+	i = 0;
+	while (1) {
+		state = pcxhr_pipes_running(mgr);
+		/* have all pipes the new state ? */
+		if ((state & audio_mask) == (start ? audio_mask : 0))
+			break;
+		if (++i >= MAX_WAIT_FOR_DSP * 100) {
+			snd_printk(KERN_ERR "error pipe start/stop\n");
+			return -EBUSY;
+		}
+		udelay(10);			/* wait 10 microseconds */
+	}
+	if (!start) {
+		err = pcxhr_stop_pipes(mgr, audio_mask);
+		if (err)
+			return err;
+	}
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+	do_gettimeofday(&my_tv2);
+	snd_printdd("***SET PIPE STATE*** TIME = %ld (err = %x)\n",
+		    (long)(my_tv2.tv_usec - my_tv1.tv_usec), err);
+#endif
+	return 0;
+}
+
+int pcxhr_write_io_num_reg_cont(struct pcxhr_mgr *mgr, unsigned int mask,
+				unsigned int value, int *changed)
+{
+	struct pcxhr_rmh rmh;
+	unsigned long flags;
+	int err;
+
+	spin_lock_irqsave(&mgr->msg_lock, flags);
+	if ((mgr->io_num_reg_cont & mask) == value) {
+		snd_printdd("IO_NUM_REG_CONT mask %x already is set to %x\n",
+			    mask, value);
+		if (changed)
+			*changed = 0;
+		spin_unlock_irqrestore(&mgr->msg_lock, flags);
+		return 0;	/* already programmed */
+	}
+	pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE);
+	rmh.cmd[0] |= IO_NUM_REG_CONT;
+	rmh.cmd[1]  = mask;
+	rmh.cmd[2]  = value;
+	rmh.cmd_len = 3;
+	err = pcxhr_send_msg_nolock(mgr, &rmh);
+	if (err == 0) {
+		mgr->io_num_reg_cont &= ~mask;
+		mgr->io_num_reg_cont |= value;
+		if (changed)
+			*changed = 1;
+	}
+	spin_unlock_irqrestore(&mgr->msg_lock, flags);
+	return err;
+}
+
+#define PCXHR_IRQ_TIMER		0x000300
+#define PCXHR_IRQ_FREQ_CHANGE	0x000800
+#define PCXHR_IRQ_TIME_CODE	0x001000
+#define PCXHR_IRQ_NOTIFY	0x002000
+#define PCXHR_IRQ_ASYNC		0x008000
+#define PCXHR_IRQ_MASK		0x00bb00
+#define PCXHR_FATAL_DSP_ERR	0xff0000
+
+enum pcxhr_async_err_src {
+	PCXHR_ERR_PIPE,
+	PCXHR_ERR_STREAM,
+	PCXHR_ERR_AUDIO
+};
+
+static int pcxhr_handle_async_err(struct pcxhr_mgr *mgr, u32 err,
+				  enum pcxhr_async_err_src err_src, int pipe,
+				  int is_capture)
+{
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+	static char* err_src_name[] = {
+		[PCXHR_ERR_PIPE]	= "Pipe",
+		[PCXHR_ERR_STREAM]	= "Stream",
+		[PCXHR_ERR_AUDIO]	= "Audio"
+	};
+#endif
+	if (err & 0xfff)
+		err &= 0xfff;
+	else
+		err = ((err >> 12) & 0xfff);
+	if (!err)
+		return 0;
+	snd_printdd("CMD_ASYNC : Error %s %s Pipe %d err=%x\n",
+		    err_src_name[err_src],
+		    is_capture ? "Record" : "Play", pipe, err);
+	if (err == 0xe01)
+		mgr->async_err_stream_xrun++;
+	else if (err == 0xe10)
+		mgr->async_err_pipe_xrun++;
+	else
+		mgr->async_err_other_last = (int)err;
+	return 1;
+}
+
+
+void pcxhr_msg_tasklet(unsigned long arg)
+{
+	struct pcxhr_mgr *mgr = (struct pcxhr_mgr *)(arg);
+	struct pcxhr_rmh *prmh = mgr->prmh;
+	int err;
+	int i, j;
+
+	if (mgr->src_it_dsp & PCXHR_IRQ_FREQ_CHANGE)
+		snd_printdd("TASKLET : PCXHR_IRQ_FREQ_CHANGE event occured\n");
+	if (mgr->src_it_dsp & PCXHR_IRQ_TIME_CODE)
+		snd_printdd("TASKLET : PCXHR_IRQ_TIME_CODE event occured\n");
+	if (mgr->src_it_dsp & PCXHR_IRQ_NOTIFY)
+		snd_printdd("TASKLET : PCXHR_IRQ_NOTIFY event occured\n");
+	if (mgr->src_it_dsp & (PCXHR_IRQ_FREQ_CHANGE | PCXHR_IRQ_TIME_CODE)) {
+		/* clear events FREQ_CHANGE and TIME_CODE */
+		pcxhr_init_rmh(prmh, CMD_TEST_IT);
+		err = pcxhr_send_msg(mgr, prmh);
+		snd_printdd("CMD_TEST_IT : err=%x, stat=%x\n",
+			    err, prmh->stat[0]);
+	}
+	if (mgr->src_it_dsp & PCXHR_IRQ_ASYNC) {
+		snd_printdd("TASKLET : PCXHR_IRQ_ASYNC event occured\n");
+
+		pcxhr_init_rmh(prmh, CMD_ASYNC);
+		prmh->cmd[0] |= 1;	/* add SEL_ASYNC_EVENTS */
+		/* this is the only one extra long response command */
+		prmh->stat_len = PCXHR_SIZE_MAX_LONG_STATUS;
+		err = pcxhr_send_msg(mgr, prmh);
+		if (err)
+			snd_printk(KERN_ERR "ERROR pcxhr_msg_tasklet=%x;\n",
+				   err);
+		i = 1;
+		while (i < prmh->stat_len) {
+			int nb_audio = ((prmh->stat[i] >> FIELD_SIZE) &
+					MASK_FIRST_FIELD);
+			int nb_stream = ((prmh->stat[i] >> (2*FIELD_SIZE)) &
+					 MASK_FIRST_FIELD);
+			int pipe = prmh->stat[i] & MASK_FIRST_FIELD;
+			int is_capture = prmh->stat[i] & 0x400000;
+			u32 err2;
+
+			if (prmh->stat[i] & 0x800000) {	/* if BIT_END */
+				snd_printdd("TASKLET : End%sPipe %d\n",
+					    is_capture ? "Record" : "Play",
+					    pipe);
+			}
+			i++;
+			err2 = prmh->stat[i] ? prmh->stat[i] : prmh->stat[i+1];
+			if (err2)
+				pcxhr_handle_async_err(mgr, err2,
+						       PCXHR_ERR_PIPE,
+						       pipe, is_capture);
+			i += 2;
+			for (j = 0; j < nb_stream; j++) {
+				err2 = prmh->stat[i] ?
+					prmh->stat[i] : prmh->stat[i+1];
+				if (err2)
+					pcxhr_handle_async_err(mgr, err2,
+							       PCXHR_ERR_STREAM,
+							       pipe,
+							       is_capture);
+				i += 2;
+			}
+			for (j = 0; j < nb_audio; j++) {
+				err2 = prmh->stat[i] ?
+					prmh->stat[i] : prmh->stat[i+1];
+				if (err2)
+					pcxhr_handle_async_err(mgr, err2,
+							       PCXHR_ERR_AUDIO,
+							       pipe,
+							       is_capture);
+				i += 2;
+			}
+		}
+	}
+}
+
+static u_int64_t pcxhr_stream_read_position(struct pcxhr_mgr *mgr,
+					    struct pcxhr_stream *stream)
+{
+	u_int64_t hw_sample_count;
+	struct pcxhr_rmh rmh;
+	int err, stream_mask;
+
+	stream_mask = stream->pipe->is_capture ? 1 : 1<<stream->substream->number;
+
+	/* get sample count for one stream */
+	pcxhr_init_rmh(&rmh, CMD_STREAM_SAMPLE_COUNT);
+	pcxhr_set_pipe_cmd_params(&rmh, stream->pipe->is_capture,
+				  stream->pipe->first_audio, 0, stream_mask);
+	/* rmh.stat_len = 2; */	/* 2 resp data for each stream of the pipe */
+
+	err = pcxhr_send_msg(mgr, &rmh);
+	if (err)
+		return 0;
+
+	hw_sample_count = ((u_int64_t)rmh.stat[0]) << 24;
+	hw_sample_count += (u_int64_t)rmh.stat[1];
+
+	snd_printdd("stream %c%d : abs samples real(%ld) timer(%ld)\n",
+		    stream->pipe->is_capture ? 'C' : 'P',
+		    stream->substream->number,
+		    (long unsigned int)hw_sample_count,
+		    (long unsigned int)(stream->timer_abs_periods +
+					stream->timer_period_frag +
+					mgr->granularity));
+	return hw_sample_count;
+}
+
+static void pcxhr_update_timer_pos(struct pcxhr_mgr *mgr,
+				   struct pcxhr_stream *stream,
+				   int samples_to_add)
+{
+	if (stream->substream &&
+	    (stream->status == PCXHR_STREAM_STATUS_RUNNING)) {
+		u_int64_t new_sample_count;
+		int elapsed = 0;
+		int hardware_read = 0;
+		struct snd_pcm_runtime *runtime = stream->substream->runtime;
+
+		if (samples_to_add < 0) {
+			stream->timer_is_synced = 0;
+			/* add default if no hardware_read possible */
+			samples_to_add = mgr->granularity;
+		}
+
+		if (!stream->timer_is_synced) {
+			if ((stream->timer_abs_periods != 0) ||
+			    ((stream->timer_period_frag + samples_to_add) >=
+			    runtime->period_size)) {
+				new_sample_count =
+				  pcxhr_stream_read_position(mgr, stream);
+				hardware_read = 1;
+				if (new_sample_count >= mgr->granularity) {
+					/* sub security offset because of
+					 * jitter and finer granularity of
+					 * dsp time (MBOX4)
+					 */
+					new_sample_count -= mgr->granularity;
+					stream->timer_is_synced = 1;
+				}
+			}
+		}
+		if (!hardware_read) {
+			/* if we didn't try to sync the position, increment it
+			 * by PCXHR_GRANULARITY every timer interrupt
+			 */
+			new_sample_count = stream->timer_abs_periods +
+				stream->timer_period_frag + samples_to_add;
+		}
+		while (1) {
+			u_int64_t new_elapse_pos = stream->timer_abs_periods +
+				runtime->period_size;
+			if (new_elapse_pos > new_sample_count)
+				break;
+			elapsed = 1;
+			stream->timer_buf_periods++;
+			if (stream->timer_buf_periods >= runtime->periods)
+				stream->timer_buf_periods = 0;
+			stream->timer_abs_periods = new_elapse_pos;
+		}
+		if (new_sample_count >= stream->timer_abs_periods) {
+			stream->timer_period_frag =
+				(u_int32_t)(new_sample_count -
+					    stream->timer_abs_periods);
+		} else {
+			snd_printk(KERN_ERR
+				   "ERROR new_sample_count too small ??? %ld\n",
+				   (long unsigned int)new_sample_count);
+		}
+
+		if (elapsed) {
+			spin_unlock(&mgr->lock);
+			snd_pcm_period_elapsed(stream->substream);
+			spin_lock(&mgr->lock);
+		}
+	}
+}
+
+irqreturn_t pcxhr_interrupt(int irq, void *dev_id)
+{
+	struct pcxhr_mgr *mgr = dev_id;
+	unsigned int reg;
+	int i, j;
+	struct snd_pcxhr *chip;
+
+	spin_lock(&mgr->lock);
+
+	reg = PCXHR_INPL(mgr, PCXHR_PLX_IRQCS);
+	if (! (reg & PCXHR_IRQCS_ACTIVE_PCIDB)) {
+		spin_unlock(&mgr->lock);
+		/* this device did not cause the interrupt */
+		return IRQ_NONE;
+	}
+
+	/* clear interrupt */
+	reg = PCXHR_INPL(mgr, PCXHR_PLX_L2PCIDB);
+	PCXHR_OUTPL(mgr, PCXHR_PLX_L2PCIDB, reg);
+
+	/* timer irq occured */
+	if (reg & PCXHR_IRQ_TIMER) {
+		int timer_toggle = reg & PCXHR_IRQ_TIMER;
+		/* is a 24 bit counter */
+		int dsp_time_new =
+			PCXHR_INPL(mgr, PCXHR_PLX_MBOX4) & PCXHR_DSP_TIME_MASK;
+		int dsp_time_diff = dsp_time_new - mgr->dsp_time_last;
+
+		if ((dsp_time_diff < 0) &&
+		    (mgr->dsp_time_last != PCXHR_DSP_TIME_INVALID)) {
+			snd_printdd("ERROR DSP TIME old(%d) new(%d) -> "
+				    "resynchronize all streams\n",
+				    mgr->dsp_time_last, dsp_time_new);
+			mgr->dsp_time_err++;
+		}
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+		if (dsp_time_diff == 0)
+			snd_printdd("ERROR DSP TIME NO DIFF time(%d)\n",
+				    dsp_time_new);
+		else if (dsp_time_diff >= (2*mgr->granularity))
+			snd_printdd("ERROR DSP TIME TOO BIG old(%d) add(%d)\n",
+				    mgr->dsp_time_last,
+				    dsp_time_new - mgr->dsp_time_last);
+		else if (dsp_time_diff % mgr->granularity)
+			snd_printdd("ERROR DSP TIME increased by %d\n",
+				    dsp_time_diff);
+#endif
+		mgr->dsp_time_last = dsp_time_new;
+
+		if (timer_toggle == mgr->timer_toggle) {
+			snd_printdd("ERROR TIMER TOGGLE\n");
+			mgr->dsp_time_err++;
+		}
+		mgr->timer_toggle = timer_toggle;
+
+		reg &= ~PCXHR_IRQ_TIMER;
+		for (i = 0; i < mgr->num_cards; i++) {
+			chip = mgr->chip[i];
+			for (j = 0; j < chip->nb_streams_capt; j++)
+				pcxhr_update_timer_pos(mgr,
+						&chip->capture_stream[j],
+						dsp_time_diff);
+		}
+		for (i = 0; i < mgr->num_cards; i++) {
+			chip = mgr->chip[i];
+			for (j = 0; j < chip->nb_streams_play; j++)
+				pcxhr_update_timer_pos(mgr,
+						&chip->playback_stream[j],
+						dsp_time_diff);
+		}
+	}
+	/* other irq's handled in the tasklet */
+	if (reg & PCXHR_IRQ_MASK) {
+		if (reg & PCXHR_IRQ_ASYNC) {
+			/* as we didn't request any async notifications,
+			 * some kind of xrun error will probably occured
+			 */
+			/* better resynchronize all streams next interrupt : */
+			mgr->dsp_time_last = PCXHR_DSP_TIME_INVALID;
+		}
+		mgr->src_it_dsp = reg;
+		tasklet_schedule(&mgr->msg_taskq);
+	}
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+	if (reg & PCXHR_FATAL_DSP_ERR)
+		snd_printdd("FATAL DSP ERROR : %x\n", reg);
+#endif
+	spin_unlock(&mgr->lock);
+	return IRQ_HANDLED;	/* this device caused the interrupt */
+}
diff --git a/linux/sound/pci/pcxhr/pcxhr_core.h b/linux/sound/pci/pcxhr/pcxhr_core.h
new file mode 100644
index 0000000..be01737
--- /dev/null
+++ b/linux/sound/pci/pcxhr/pcxhr_core.h
@@ -0,0 +1,203 @@
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * low level interface with interrupt and message handling
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ *
+ *   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 __SOUND_PCXHR_CORE_H
+#define __SOUND_PCXHR_CORE_H
+
+struct firmware;
+struct pcxhr_mgr;
+
+/* init and firmware download commands */
+void pcxhr_reset_xilinx_com(struct pcxhr_mgr *mgr);
+void pcxhr_reset_dsp(struct pcxhr_mgr *mgr);
+void pcxhr_enable_dsp(struct pcxhr_mgr *mgr);
+int pcxhr_load_xilinx_binary(struct pcxhr_mgr *mgr, const struct firmware *xilinx, int second);
+int pcxhr_load_eeprom_binary(struct pcxhr_mgr *mgr, const struct firmware *eeprom);
+int pcxhr_load_boot_binary(struct pcxhr_mgr *mgr, const struct firmware *boot);
+int pcxhr_load_dsp_binary(struct pcxhr_mgr *mgr, const struct firmware *dsp);
+
+/* DSP time available on MailBox4 register : 24 bit time samples() */
+#define PCXHR_DSP_TIME_MASK		0x00ffffff
+#define PCXHR_DSP_TIME_INVALID		0x10000000
+
+
+#define PCXHR_SIZE_MAX_CMD		8
+#define PCXHR_SIZE_MAX_STATUS		16
+#define PCXHR_SIZE_MAX_LONG_STATUS	256
+
+struct pcxhr_rmh {
+	u16	cmd_len;		/* length of the command to send (WORDs) */
+	u16	stat_len;		/* length of the status received (WORDs) */
+	u16	dsp_stat;		/* status type, RMP_SSIZE_XXX */
+	u16	cmd_idx;		/* index of the command */
+	u32	cmd[PCXHR_SIZE_MAX_CMD];
+	u32	stat[PCXHR_SIZE_MAX_STATUS];
+};
+
+enum {
+	CMD_VERSION,			/* cmd_len = 2	stat_len = 1 */
+	CMD_SUPPORTED,			/* cmd_len = 1	stat_len = 4 */
+	CMD_TEST_IT,			/* cmd_len = 1	stat_len = 1 */
+	CMD_SEND_IRQA,			/* cmd_len = 1	stat_len = 0 */
+	CMD_ACCESS_IO_WRITE,		/* cmd_len >= 1	stat_len >= 1 */
+	CMD_ACCESS_IO_READ,		/* cmd_len >= 1	stat_len >= 1 */
+	CMD_ASYNC,			/* cmd_len = 1	stat_len = 1 */
+	CMD_MODIFY_CLOCK,		/* cmd_len = 3	stat_len = 0 */
+	CMD_RESYNC_AUDIO_INPUTS,	/* cmd_len = 1	stat_len = 0 */
+	CMD_GET_DSP_RESOURCES,		/* cmd_len = 1	stat_len = 4 */
+	CMD_SET_TIMER_INTERRUPT,	/* cmd_len = 1	stat_len = 0 */
+	CMD_RES_PIPE,			/* cmd_len >=2	stat_len = 0 */
+	CMD_FREE_PIPE,			/* cmd_len = 1	stat_len = 0 */
+	CMD_CONF_PIPE,			/* cmd_len = 2	stat_len = 0 */
+	CMD_STOP_PIPE,			/* cmd_len = 1	stat_len = 0 */
+	CMD_PIPE_SAMPLE_COUNT,		/* cmd_len = 2	stat_len = 2 */
+	CMD_CAN_START_PIPE,		/* cmd_len >= 1	stat_len = 1 */
+	CMD_START_STREAM,		/* cmd_len = 2	stat_len = 0 */
+	CMD_STREAM_OUT_LEVEL_ADJUST,	/* cmd_len >= 1	stat_len = 0 */
+	CMD_STOP_STREAM,		/* cmd_len = 2	stat_len = 0 */
+	CMD_UPDATE_R_BUFFERS,		/* cmd_len = 4	stat_len = 0 */
+	CMD_FORMAT_STREAM_OUT,		/* cmd_len >= 2	stat_len = 0 */
+	CMD_FORMAT_STREAM_IN,		/* cmd_len >= 4	stat_len = 0 */
+	CMD_STREAM_SAMPLE_COUNT,	/* cmd_len = 2	stat_len = (2 * nb_stream) */
+	CMD_AUDIO_LEVEL_ADJUST,		/* cmd_len = 3	stat_len = 0 */
+	CMD_LAST_INDEX
+};
+
+#define MASK_DSP_WORD		0x00ffffff
+#define MASK_ALL_STREAM		0x00ffffff
+#define MASK_DSP_WORD_LEVEL	0x000001ff
+#define MASK_FIRST_FIELD	0x0000001f
+#define FIELD_SIZE		5
+
+/*
+ init the rmh struct; by default cmd_len is set to 1
+ */
+void pcxhr_init_rmh(struct pcxhr_rmh *rmh, int cmd);
+
+void pcxhr_set_pipe_cmd_params(struct pcxhr_rmh* rmh, int capture, unsigned int param1,
+			       unsigned int param2, unsigned int param3);
+
+#define DSP_EXT_CMD_SET(x) (x->dsp_version > 0x012800)
+
+/*
+ send the rmh
+ */
+int pcxhr_send_msg(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh);
+
+
+/* values used for CMD_ACCESS_IO_WRITE and CMD_ACCESS_IO_READ */
+#define IO_NUM_REG_CONT			0
+#define IO_NUM_REG_GENCLK		1
+#define IO_NUM_REG_MUTE_OUT		2
+#define IO_NUM_SPEED_RATIO		4
+#define IO_NUM_REG_STATUS		5
+#define IO_NUM_REG_CUER			10
+#define IO_NUM_UER_CHIP_REG		11
+#define IO_NUM_REG_CONFIG_SRC		12
+#define IO_NUM_REG_OUT_ANA_LEVEL	20
+#define IO_NUM_REG_IN_ANA_LEVEL		21
+
+
+#define REG_CONT_UNMUTE_INPUTS		0x020000
+
+/* parameters used with register IO_NUM_REG_STATUS */
+#define REG_STATUS_OPTIONS		0
+#define REG_STATUS_AES_SYNC		8
+#define REG_STATUS_AES_1		9
+#define REG_STATUS_AES_2		10
+#define REG_STATUS_AES_3		11
+#define REG_STATUS_AES_4		12
+#define REG_STATUS_WORD_CLOCK		13
+#define REG_STATUS_INTER_SYNC		14
+#define REG_STATUS_CURRENT		0x80
+/* results */
+#define REG_STATUS_OPT_NO_VIDEO_SIGNAL	0x01
+#define REG_STATUS_OPT_DAUGHTER_MASK	0x1c
+#define REG_STATUS_OPT_ANALOG_BOARD	0x00
+#define REG_STATUS_OPT_NO_DAUGHTER	0x1c
+#define REG_STATUS_OPT_COMPANION_MASK	0xe0
+#define REG_STATUS_OPT_NO_COMPANION	0xe0
+#define REG_STATUS_SYNC_32000		0x00
+#define REG_STATUS_SYNC_44100		0x01
+#define REG_STATUS_SYNC_48000		0x02
+#define REG_STATUS_SYNC_64000		0x03
+#define REG_STATUS_SYNC_88200		0x04
+#define REG_STATUS_SYNC_96000		0x05
+#define REG_STATUS_SYNC_128000		0x06
+#define REG_STATUS_SYNC_176400		0x07
+#define REG_STATUS_SYNC_192000		0x08
+
+int pcxhr_set_pipe_state(struct pcxhr_mgr *mgr, int playback_mask, int capture_mask, int start);
+
+int pcxhr_write_io_num_reg_cont(struct pcxhr_mgr *mgr, unsigned int mask,
+				unsigned int value, int *changed);
+
+/* codec parameters */
+#define CS8416_RUN		0x200401
+#define CS8416_FORMAT_DETECT	0x200b00
+#define CS8416_CSB0		0x201900
+#define CS8416_CSB1		0x201a00
+#define CS8416_CSB2		0x201b00
+#define CS8416_CSB3		0x201c00
+#define CS8416_CSB4		0x201d00
+#define CS8416_VERSION		0x207f00
+
+#define CS8420_DATA_FLOW_CTL	0x200301
+#define CS8420_CLOCK_SRC_CTL	0x200401
+#define CS8420_RECEIVER_ERRORS	0x201000
+#define CS8420_SRC_RATIO	0x201e00
+#define CS8420_CSB0		0x202000
+#define CS8420_CSB1		0x202100
+#define CS8420_CSB2		0x202200
+#define CS8420_CSB3		0x202300
+#define CS8420_CSB4		0x202400
+#define CS8420_VERSION		0x207f00
+
+#define CS4271_MODE_CTL_1	0x200101
+#define CS4271_DAC_CTL		0x200201
+#define CS4271_VOLMIX		0x200301
+#define CS4271_VOLMUTE_LEFT	0x200401
+#define CS4271_VOLMUTE_RIGHT	0x200501
+#define CS4271_ADC_CTL		0x200601
+#define CS4271_MODE_CTL_2	0x200701
+
+#define CHIP_SIG_AND_MAP_SPI	0xff7f00
+
+/* codec selection */
+#define CS4271_01_CS		0x160018
+#define CS4271_23_CS		0x160019
+#define CS4271_45_CS		0x16001a
+#define CS4271_67_CS		0x16001b
+#define CS4271_89_CS		0x16001c
+#define CS4271_AB_CS		0x16001d
+#define CS8420_01_CS		0x080090
+#define CS8420_23_CS		0x080092
+#define CS8420_45_CS		0x080094
+#define CS8420_67_CS		0x080096
+#define CS8416_01_CS		0x080098
+
+
+/* interrupt handling */
+irqreturn_t pcxhr_interrupt(int irq, void *dev_id);
+void pcxhr_msg_tasklet(unsigned long arg);
+
+#endif /* __SOUND_PCXHR_CORE_H */
diff --git a/linux/sound/pci/pcxhr/pcxhr_hwdep.c b/linux/sound/pci/pcxhr/pcxhr_hwdep.c
new file mode 100644
index 0000000..17cb123
--- /dev/null
+++ b/linux/sound/pci/pcxhr/pcxhr_hwdep.c
@@ -0,0 +1,502 @@
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * hwdep device manager
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ *
+ *   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/interrupt.h>
+#include <linux/vmalloc.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <sound/core.h>
+#include <sound/hwdep.h>
+#include "pcxhr.h"
+#include "pcxhr_mixer.h"
+#include "pcxhr_hwdep.h"
+#include "pcxhr_core.h"
+#include "pcxhr_mix22.h"
+
+
+#if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE)
+#if !defined(CONFIG_USE_PCXHRLOADER) && !defined(CONFIG_SND_PCXHR) /* built-in kernel */
+#define SND_PCXHR_FW_LOADER	/* use the standard firmware loader */
+#endif
+#endif
+
+
+static int pcxhr_sub_init(struct pcxhr_mgr *mgr);
+/*
+ * get basic information and init pcxhr card
+ */
+static int pcxhr_init_board(struct pcxhr_mgr *mgr)
+{
+	int err;
+	struct pcxhr_rmh rmh;
+	int card_streams;
+
+	/* calc the number of all streams used */
+	if (mgr->mono_capture)
+		card_streams = mgr->capture_chips * 2;
+	else
+		card_streams = mgr->capture_chips;
+	card_streams += mgr->playback_chips * PCXHR_PLAYBACK_STREAMS;
+
+	/* enable interrupts */
+	pcxhr_enable_dsp(mgr);
+
+	pcxhr_init_rmh(&rmh, CMD_SUPPORTED);
+	err = pcxhr_send_msg(mgr, &rmh);
+	if (err)
+		return err;
+	/* test 8 or 12 phys out */
+	if ((rmh.stat[0] & MASK_FIRST_FIELD) != mgr->playback_chips * 2)
+		return -EINVAL;
+	/* test 8 or 2 phys in */
+	if (((rmh.stat[0] >> (2 * FIELD_SIZE)) & MASK_FIRST_FIELD) <
+	    mgr->capture_chips * 2)
+		return -EINVAL;
+	/* test max nb substream per board */
+	if ((rmh.stat[1] & 0x5F) < card_streams)
+		return -EINVAL;
+	/* test max nb substream per pipe */
+	if (((rmh.stat[1] >> 7) & 0x5F) < PCXHR_PLAYBACK_STREAMS)
+		return -EINVAL;
+	snd_printdd("supported formats : playback=%x capture=%x\n",
+		    rmh.stat[2], rmh.stat[3]);
+
+	pcxhr_init_rmh(&rmh, CMD_VERSION);
+	/* firmware num for DSP */
+	rmh.cmd[0] |= mgr->firmware_num;
+	/* transfer granularity in samples (should be multiple of 48) */
+	rmh.cmd[1] = (1<<23) + mgr->granularity;
+	rmh.cmd_len = 2;
+	err = pcxhr_send_msg(mgr, &rmh);
+	if (err)
+		return err;
+	snd_printdd("PCXHR DSP version is %d.%d.%d\n", (rmh.stat[0]>>16)&0xff,
+		    (rmh.stat[0]>>8)&0xff, rmh.stat[0]&0xff);
+	mgr->dsp_version = rmh.stat[0];
+
+	if (mgr->is_hr_stereo)
+		err = hr222_sub_init(mgr);
+	else
+		err = pcxhr_sub_init(mgr);
+	return err;
+}
+
+static int pcxhr_sub_init(struct pcxhr_mgr *mgr)
+{
+	int err;
+	struct pcxhr_rmh rmh;
+
+	/* get options */
+	pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ);
+	rmh.cmd[0] |= IO_NUM_REG_STATUS;
+	rmh.cmd[1]  = REG_STATUS_OPTIONS;
+	rmh.cmd_len = 2;
+	err = pcxhr_send_msg(mgr, &rmh);
+	if (err)
+		return err;
+
+	if ((rmh.stat[1] & REG_STATUS_OPT_DAUGHTER_MASK) ==
+	    REG_STATUS_OPT_ANALOG_BOARD)
+		mgr->board_has_analog = 1;	/* analog addon board found */
+
+	/* unmute inputs */
+	err = pcxhr_write_io_num_reg_cont(mgr, REG_CONT_UNMUTE_INPUTS,
+					  REG_CONT_UNMUTE_INPUTS, NULL);
+	if (err)
+		return err;
+	/* unmute outputs (a write to IO_NUM_REG_MUTE_OUT mutes!) */
+	pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ);
+	rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT;
+	if (DSP_EXT_CMD_SET(mgr)) {
+		rmh.cmd[1]  = 1;	/* unmute digital plugs */
+		rmh.cmd_len = 2;
+	}
+	err = pcxhr_send_msg(mgr, &rmh);
+	return err;
+}
+
+void pcxhr_reset_board(struct pcxhr_mgr *mgr)
+{
+	struct pcxhr_rmh rmh;
+
+	if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) {
+		/* mute outputs */
+	    if (!mgr->is_hr_stereo) {
+		/* a read to IO_NUM_REG_MUTE_OUT register unmutes! */
+		pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE);
+		rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT;
+		pcxhr_send_msg(mgr, &rmh);
+		/* mute inputs */
+		pcxhr_write_io_num_reg_cont(mgr, REG_CONT_UNMUTE_INPUTS,
+					    0, NULL);
+	    }
+		/* stereo cards mute with reset of dsp */
+	}
+	/* reset pcxhr dsp */
+	if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_EPRM_INDEX))
+		pcxhr_reset_dsp(mgr);
+	/* reset second xilinx */
+	if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_XLX_COM_INDEX)) {
+		pcxhr_reset_xilinx_com(mgr);
+		mgr->dsp_loaded = 1;
+	}
+	return;
+}
+
+
+/*
+ *  allocate a playback/capture pipe (pcmp0/pcmc0)
+ */
+static int pcxhr_dsp_allocate_pipe(struct pcxhr_mgr *mgr,
+				   struct pcxhr_pipe *pipe,
+				   int is_capture, int pin)
+{
+	int stream_count, audio_count;
+	int err;
+	struct pcxhr_rmh rmh;
+
+	if (is_capture) {
+		stream_count = 1;
+		if (mgr->mono_capture)
+			audio_count = 1;
+		else
+			audio_count = 2;
+	} else {
+		stream_count = PCXHR_PLAYBACK_STREAMS;
+		audio_count = 2;	/* always stereo */
+	}
+	snd_printdd("snd_add_ref_pipe pin(%d) pcm%c0\n",
+		    pin, is_capture ? 'c' : 'p');
+	pipe->is_capture = is_capture;
+	pipe->first_audio = pin;
+	/* define pipe (P_PCM_ONLY_MASK (0x020000) is not necessary) */
+	pcxhr_init_rmh(&rmh, CMD_RES_PIPE);
+	pcxhr_set_pipe_cmd_params(&rmh, is_capture, pin,
+				  audio_count, stream_count);
+	rmh.cmd[1] |= 0x020000; /* add P_PCM_ONLY_MASK */
+	if (DSP_EXT_CMD_SET(mgr)) {
+		/* add channel mask to command */
+	  rmh.cmd[rmh.cmd_len++] = (audio_count == 1) ? 0x01 : 0x03;
+	}
+	err = pcxhr_send_msg(mgr, &rmh);
+	if (err < 0) {
+		snd_printk(KERN_ERR "error pipe allocation "
+			   "(CMD_RES_PIPE) err=%x!\n", err);
+		return err;
+	}
+	pipe->status = PCXHR_PIPE_DEFINED;
+
+	return 0;
+}
+
+/*
+ *  free playback/capture pipe (pcmp0/pcmc0)
+ */
+#if 0
+static int pcxhr_dsp_free_pipe( struct pcxhr_mgr *mgr, struct pcxhr_pipe *pipe)
+{
+	struct pcxhr_rmh rmh;
+	int capture_mask = 0;
+	int playback_mask = 0;
+	int err = 0;
+
+	if (pipe->is_capture)
+		capture_mask  = (1 << pipe->first_audio);
+	else
+		playback_mask = (1 << pipe->first_audio);
+
+	/* stop one pipe */
+	err = pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 0);
+	if (err < 0)
+		snd_printk(KERN_ERR "error stopping pipe!\n");
+	/* release the pipe */
+	pcxhr_init_rmh(&rmh, CMD_FREE_PIPE);
+	pcxhr_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->first_audio,
+				  0, 0);
+	err = pcxhr_send_msg(mgr, &rmh);
+	if (err < 0)
+		snd_printk(KERN_ERR "error pipe release "
+			   "(CMD_FREE_PIPE) err(%x)\n", err);
+	pipe->status = PCXHR_PIPE_UNDEFINED;
+	return err;
+}
+#endif
+
+
+static int pcxhr_config_pipes(struct pcxhr_mgr *mgr)
+{
+	int err, i, j;
+	struct snd_pcxhr *chip;
+	struct pcxhr_pipe *pipe;
+
+	/* allocate the pipes on the dsp */
+	for (i = 0; i < mgr->num_cards; i++) {
+		chip = mgr->chip[i];
+		if (chip->nb_streams_play) {
+			pipe = &chip->playback_pipe;
+			err = pcxhr_dsp_allocate_pipe( mgr, pipe, 0, i*2);
+			if (err)
+				return err;
+			for(j = 0; j < chip->nb_streams_play; j++)
+				chip->playback_stream[j].pipe = pipe;
+		}
+		for (j = 0; j < chip->nb_streams_capt; j++) {
+			pipe = &chip->capture_pipe[j];
+			err = pcxhr_dsp_allocate_pipe(mgr, pipe, 1, i*2 + j);
+			if (err)
+				return err;
+			chip->capture_stream[j].pipe = pipe;
+		}
+	}
+	return 0;
+}
+
+static int pcxhr_start_pipes(struct pcxhr_mgr *mgr)
+{
+	int i, j;
+	struct snd_pcxhr *chip;
+	int playback_mask = 0;
+	int capture_mask = 0;
+
+	/* start all the pipes on the dsp */
+	for (i = 0; i < mgr->num_cards; i++) {
+		chip = mgr->chip[i];
+		if (chip->nb_streams_play)
+			playback_mask |= 1 << chip->playback_pipe.first_audio;
+		for (j = 0; j < chip->nb_streams_capt; j++)
+			capture_mask |= 1 << chip->capture_pipe[j].first_audio;
+	}
+	return pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 1);
+}
+
+
+static int pcxhr_dsp_load(struct pcxhr_mgr *mgr, int index,
+			  const struct firmware *dsp)
+{
+	int err, card_index;
+
+	snd_printdd("loading dsp [%d] size = %Zd\n", index, dsp->size);
+
+	switch (index) {
+	case PCXHR_FIRMWARE_XLX_INT_INDEX:
+		pcxhr_reset_xilinx_com(mgr);
+		return pcxhr_load_xilinx_binary(mgr, dsp, 0);
+
+	case PCXHR_FIRMWARE_XLX_COM_INDEX:
+		pcxhr_reset_xilinx_com(mgr);
+		return pcxhr_load_xilinx_binary(mgr, dsp, 1);
+
+	case PCXHR_FIRMWARE_DSP_EPRM_INDEX:
+		pcxhr_reset_dsp(mgr);
+		return pcxhr_load_eeprom_binary(mgr, dsp);
+
+	case PCXHR_FIRMWARE_DSP_BOOT_INDEX:
+		return pcxhr_load_boot_binary(mgr, dsp);
+
+	case PCXHR_FIRMWARE_DSP_MAIN_INDEX:
+		err = pcxhr_load_dsp_binary(mgr, dsp);
+		if (err)
+			return err;
+		break;	/* continue with first init */
+	default:
+		snd_printk(KERN_ERR "wrong file index\n");
+		return -EFAULT;
+	} /* end of switch file index*/
+
+	/* first communication with embedded */
+	err = pcxhr_init_board(mgr);
+        if (err < 0) {
+		snd_printk(KERN_ERR "pcxhr could not be set up\n");
+		return err;
+	}
+	err = pcxhr_config_pipes(mgr);
+        if (err < 0) {
+		snd_printk(KERN_ERR "pcxhr pipes could not be set up\n");
+		return err;
+	}
+       	/* create devices and mixer in accordance with HW options*/
+        for (card_index = 0; card_index < mgr->num_cards; card_index++) {
+		struct snd_pcxhr *chip = mgr->chip[card_index];
+
+		if ((err = pcxhr_create_pcm(chip)) < 0)
+			return err;
+
+		if (card_index == 0) {
+			if ((err = pcxhr_create_mixer(chip->mgr)) < 0)
+				return err;
+		}
+		if ((err = snd_card_register(chip->card)) < 0)
+			return err;
+	}
+	err = pcxhr_start_pipes(mgr);
+        if (err < 0) {
+		snd_printk(KERN_ERR "pcxhr pipes could not be started\n");
+		return err;
+	}
+	snd_printdd("pcxhr firmware downloaded and successfully set up\n");
+
+	return 0;
+}
+
+/*
+ * fw loader entry
+ */
+#ifdef SND_PCXHR_FW_LOADER
+
+int pcxhr_setup_firmware(struct pcxhr_mgr *mgr)
+{
+	static char *fw_files[][5] = {
+	[0] = { "xlxint.dat", "xlxc882hr.dat",
+		"dspe882.e56", "dspb882hr.b56", "dspd882.d56" },
+	[1] = { "xlxint.dat", "xlxc882e.dat",
+		"dspe882.e56", "dspb882e.b56", "dspd882.d56" },
+	[2] = { "xlxint.dat", "xlxc1222hr.dat",
+		"dspe882.e56", "dspb1222hr.b56", "dspd1222.d56" },
+	[3] = { "xlxint.dat", "xlxc1222e.dat",
+		"dspe882.e56", "dspb1222e.b56", "dspd1222.d56" },
+	[4] = { NULL, "xlxc222.dat",
+		"dspe924.e56", "dspb924.b56", "dspd222.d56" },
+	[5] = { NULL, "xlxc924.dat",
+		"dspe924.e56", "dspb924.b56", "dspd222.d56" },
+	};
+	char path[32];
+
+	const struct firmware *fw_entry;
+	int i, err;
+	int fw_set = mgr->fw_file_set;
+
+	for (i = 0; i < 5; i++) {
+		if (!fw_files[fw_set][i])
+			continue;
+		sprintf(path, "pcxhr/%s", fw_files[fw_set][i]);
+		if (request_firmware(&fw_entry, path, &mgr->pci->dev)) {
+			snd_printk(KERN_ERR "pcxhr: can't load firmware %s\n",
+				   path);
+			return -ENOENT;
+		}
+		/* fake hwdep dsp record */
+		err = pcxhr_dsp_load(mgr, i, fw_entry);
+		release_firmware(fw_entry);
+		if (err < 0)
+			return err;
+		mgr->dsp_loaded |= 1 << i;
+	}
+	return 0;
+}
+
+MODULE_FIRMWARE("pcxhr/xlxint.dat");
+MODULE_FIRMWARE("pcxhr/xlxc882hr.dat");
+MODULE_FIRMWARE("pcxhr/xlxc882e.dat");
+MODULE_FIRMWARE("pcxhr/dspe882.e56");
+MODULE_FIRMWARE("pcxhr/dspb882hr.b56");
+MODULE_FIRMWARE("pcxhr/dspb882e.b56");
+MODULE_FIRMWARE("pcxhr/dspd882.d56");
+
+MODULE_FIRMWARE("pcxhr/xlxc1222hr.dat");
+MODULE_FIRMWARE("pcxhr/xlxc1222e.dat");
+MODULE_FIRMWARE("pcxhr/dspb1222hr.b56");
+MODULE_FIRMWARE("pcxhr/dspb1222e.b56");
+MODULE_FIRMWARE("pcxhr/dspd1222.d56");
+
+MODULE_FIRMWARE("pcxhr/xlxc222.dat");
+MODULE_FIRMWARE("pcxhr/xlxc924.dat");
+MODULE_FIRMWARE("pcxhr/dspe924.e56");
+MODULE_FIRMWARE("pcxhr/dspb924.b56");
+MODULE_FIRMWARE("pcxhr/dspd222.d56");
+
+
+#else /* old style firmware loading */
+
+/* pcxhr hwdep interface id string */
+#define PCXHR_HWDEP_ID       "pcxhr loader"
+
+
+static int pcxhr_hwdep_dsp_status(struct snd_hwdep *hw,
+				  struct snd_hwdep_dsp_status *info)
+{
+	struct pcxhr_mgr *mgr = hw->private_data;
+	sprintf(info->id, "pcxhr%d", mgr->fw_file_set);
+        info->num_dsps = PCXHR_FIRMWARE_FILES_MAX_INDEX;
+
+	if (hw->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX))
+		info->chip_ready = 1;
+
+	info->version = PCXHR_DRIVER_VERSION;
+	return 0;
+}
+
+static int pcxhr_hwdep_dsp_load(struct snd_hwdep *hw,
+				struct snd_hwdep_dsp_image *dsp)
+{
+	struct pcxhr_mgr *mgr = hw->private_data;
+	int err;
+	struct firmware fw;
+
+	fw.size = dsp->length;
+	fw.data = vmalloc(fw.size);
+	if (! fw.data) {
+		snd_printk(KERN_ERR "pcxhr: cannot allocate dsp image "
+			   "(%lu bytes)\n", (unsigned long)fw.size);
+		return -ENOMEM;
+	}
+	if (copy_from_user((void *)fw.data, dsp->image, dsp->length)) {
+		vfree(fw.data);
+		return -EFAULT;
+	}
+	err = pcxhr_dsp_load(mgr, dsp->index, &fw);
+	vfree(fw.data);
+	if (err < 0)
+		return err;
+	mgr->dsp_loaded |= 1 << dsp->index;
+	return 0;
+}
+
+int pcxhr_setup_firmware(struct pcxhr_mgr *mgr)
+{
+	int err;
+	struct snd_hwdep *hw;
+
+	/* only create hwdep interface for first cardX
+	 * (see "index" module parameter)
+	 */
+	err = snd_hwdep_new(mgr->chip[0]->card, PCXHR_HWDEP_ID, 0, &hw);
+	if (err < 0)
+		return err;
+
+	hw->iface = SNDRV_HWDEP_IFACE_PCXHR;
+	hw->private_data = mgr;
+	hw->ops.dsp_status = pcxhr_hwdep_dsp_status;
+	hw->ops.dsp_load = pcxhr_hwdep_dsp_load;
+	hw->exclusive = 1;
+	/* stereo cards don't need fw_file_0 -> dsp_loaded = 1 */
+	hw->dsp_loaded = mgr->is_hr_stereo ? 1 : 0;
+	mgr->dsp_loaded = 0;
+	sprintf(hw->name, PCXHR_HWDEP_ID);
+
+	err = snd_card_register(mgr->chip[0]->card);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+#endif /* SND_PCXHR_FW_LOADER */
diff --git a/linux/sound/pci/pcxhr/pcxhr_hwdep.h b/linux/sound/pci/pcxhr/pcxhr_hwdep.h
new file mode 100644
index 0000000..f561909
--- /dev/null
+++ b/linux/sound/pci/pcxhr/pcxhr_hwdep.h
@@ -0,0 +1,40 @@
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * definitions and makros for basic card access
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ *
+ *   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 __SOUND_PCXHR_HWDEP_H
+#define __SOUND_PCXHR_HWDEP_H
+
+
+/* firmware status codes  */
+#define PCXHR_FIRMWARE_XLX_INT_INDEX   0
+#define PCXHR_FIRMWARE_XLX_COM_INDEX   1
+#define PCXHR_FIRMWARE_DSP_EPRM_INDEX  2
+#define PCXHR_FIRMWARE_DSP_BOOT_INDEX  3
+#define PCXHR_FIRMWARE_DSP_MAIN_INDEX  4
+#define PCXHR_FIRMWARE_FILES_MAX_INDEX 5
+
+
+/* exported */
+int  pcxhr_setup_firmware(struct pcxhr_mgr *mgr);
+void pcxhr_reset_board(struct pcxhr_mgr *mgr);
+
+#endif /* __SOUND_PCXHR_HWDEP_H */
diff --git a/linux/sound/pci/pcxhr/pcxhr_mix22.c b/linux/sound/pci/pcxhr/pcxhr_mix22.c
new file mode 100644
index 0000000..1cb82c0
--- /dev/null
+++ b/linux/sound/pci/pcxhr/pcxhr_mix22.c
@@ -0,0 +1,852 @@
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * mixer interface for stereo cards
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/asoundef.h>
+#include "pcxhr.h"
+#include "pcxhr_core.h"
+#include "pcxhr_mix22.h"
+
+
+/* registers used on the DSP and Xilinx (port 2) : HR stereo cards only */
+#define PCXHR_DSP_RESET		0x20
+#define PCXHR_XLX_CFG		0x24
+#define PCXHR_XLX_RUER		0x28
+#define PCXHR_XLX_DATA		0x2C
+#define PCXHR_XLX_STATUS	0x30
+#define PCXHR_XLX_LOFREQ	0x34
+#define PCXHR_XLX_HIFREQ	0x38
+#define PCXHR_XLX_CSUER		0x3C
+#define PCXHR_XLX_SELMIC	0x40
+
+#define PCXHR_DSP 2
+
+/* byte access only ! */
+#define PCXHR_INPB(mgr, x)	inb((mgr)->port[PCXHR_DSP] + (x))
+#define PCXHR_OUTPB(mgr, x, data) outb((data), (mgr)->port[PCXHR_DSP] + (x))
+
+
+/* values for PCHR_DSP_RESET register */
+#define PCXHR_DSP_RESET_DSP	0x01
+#define PCXHR_DSP_RESET_MUTE	0x02
+#define PCXHR_DSP_RESET_CODEC	0x08
+#define PCXHR_DSP_RESET_GPO_OFFSET	5
+#define PCXHR_DSP_RESET_GPO_MASK	0x60
+
+/* values for PCHR_XLX_CFG register */
+#define PCXHR_CFG_SYNCDSP_MASK		0x80
+#define PCXHR_CFG_DEPENDENCY_MASK	0x60
+#define PCXHR_CFG_INDEPENDANT_SEL	0x00
+#define PCXHR_CFG_MASTER_SEL		0x40
+#define PCXHR_CFG_SLAVE_SEL		0x20
+#define PCXHR_CFG_DATA_UER1_SEL_MASK	0x10	/* 0 (UER0), 1(UER1) */
+#define PCXHR_CFG_DATAIN_SEL_MASK	0x08	/* 0 (ana), 1 (UER) */
+#define PCXHR_CFG_SRC_MASK		0x04	/* 0 (Bypass), 1 (SRC Actif) */
+#define PCXHR_CFG_CLOCK_UER1_SEL_MASK	0x02	/* 0 (UER0), 1(UER1) */
+#define PCXHR_CFG_CLOCKIN_SEL_MASK	0x01	/* 0 (internal), 1 (AES/EBU) */
+
+/* values for PCHR_XLX_DATA register */
+#define PCXHR_DATA_CODEC	0x80
+#define AKM_POWER_CONTROL_CMD	0xA007
+#define AKM_RESET_ON_CMD	0xA100
+#define AKM_RESET_OFF_CMD	0xA103
+#define AKM_CLOCK_INF_55K_CMD	0xA240
+#define AKM_CLOCK_SUP_55K_CMD	0xA24D
+#define AKM_MUTE_CMD		0xA38D
+#define AKM_UNMUTE_CMD		0xA30D
+#define AKM_LEFT_LEVEL_CMD	0xA600
+#define AKM_RIGHT_LEVEL_CMD	0xA700
+
+/* values for PCHR_XLX_STATUS register - READ */
+#define PCXHR_STAT_SRC_LOCK		0x01
+#define PCXHR_STAT_LEVEL_IN		0x02
+#define PCXHR_STAT_GPI_OFFSET		2
+#define PCXHR_STAT_GPI_MASK		0x0C
+#define PCXHR_STAT_MIC_CAPS		0x10
+/* values for PCHR_XLX_STATUS register - WRITE */
+#define PCXHR_STAT_FREQ_SYNC_MASK	0x01
+#define PCXHR_STAT_FREQ_UER1_MASK	0x02
+#define PCXHR_STAT_FREQ_SAVE_MASK	0x80
+
+/* values for PCHR_XLX_CSUER register */
+#define PCXHR_SUER1_BIT_U_READ_MASK	0x80
+#define PCXHR_SUER1_BIT_C_READ_MASK	0x40
+#define PCXHR_SUER1_DATA_PRESENT_MASK	0x20
+#define PCXHR_SUER1_CLOCK_PRESENT_MASK	0x10
+#define PCXHR_SUER_BIT_U_READ_MASK	0x08
+#define PCXHR_SUER_BIT_C_READ_MASK	0x04
+#define PCXHR_SUER_DATA_PRESENT_MASK	0x02
+#define PCXHR_SUER_CLOCK_PRESENT_MASK	0x01
+
+#define PCXHR_SUER_BIT_U_WRITE_MASK	0x02
+#define PCXHR_SUER_BIT_C_WRITE_MASK	0x01
+
+/* values for PCXHR_XLX_SELMIC register - WRITE */
+#define PCXHR_SELMIC_PREAMPLI_OFFSET	2
+#define PCXHR_SELMIC_PREAMPLI_MASK	0x0C
+#define PCXHR_SELMIC_PHANTOM_ALIM	0x80
+
+
+static const unsigned char g_hr222_p_level[] = {
+    0x00,   /* [000] -49.5 dB:	AKM[000] = -1.#INF dB	(mute) */
+    0x01,   /* [001] -49.0 dB:	AKM[001] = -48.131 dB	(diff=0.86920 dB) */
+    0x01,   /* [002] -48.5 dB:	AKM[001] = -48.131 dB	(diff=0.36920 dB) */
+    0x01,   /* [003] -48.0 dB:	AKM[001] = -48.131 dB	(diff=0.13080 dB) */
+    0x01,   /* [004] -47.5 dB:	AKM[001] = -48.131 dB	(diff=0.63080 dB) */
+    0x01,   /* [005] -46.5 dB:	AKM[001] = -48.131 dB	(diff=1.63080 dB) */
+    0x01,   /* [006] -47.0 dB:	AKM[001] = -48.131 dB	(diff=1.13080 dB) */
+    0x01,   /* [007] -46.0 dB:	AKM[001] = -48.131 dB	(diff=2.13080 dB) */
+    0x01,   /* [008] -45.5 dB:	AKM[001] = -48.131 dB	(diff=2.63080 dB) */
+    0x02,   /* [009] -45.0 dB:	AKM[002] = -42.110 dB	(diff=2.88980 dB) */
+    0x02,   /* [010] -44.5 dB:	AKM[002] = -42.110 dB	(diff=2.38980 dB) */
+    0x02,   /* [011] -44.0 dB:	AKM[002] = -42.110 dB	(diff=1.88980 dB) */
+    0x02,   /* [012] -43.5 dB:	AKM[002] = -42.110 dB	(diff=1.38980 dB) */
+    0x02,   /* [013] -43.0 dB:	AKM[002] = -42.110 dB	(diff=0.88980 dB) */
+    0x02,   /* [014] -42.5 dB:	AKM[002] = -42.110 dB	(diff=0.38980 dB) */
+    0x02,   /* [015] -42.0 dB:	AKM[002] = -42.110 dB	(diff=0.11020 dB) */
+    0x02,   /* [016] -41.5 dB:	AKM[002] = -42.110 dB	(diff=0.61020 dB) */
+    0x02,   /* [017] -41.0 dB:	AKM[002] = -42.110 dB	(diff=1.11020 dB) */
+    0x02,   /* [018] -40.5 dB:	AKM[002] = -42.110 dB	(diff=1.61020 dB) */
+    0x03,   /* [019] -40.0 dB:	AKM[003] = -38.588 dB	(diff=1.41162 dB) */
+    0x03,   /* [020] -39.5 dB:	AKM[003] = -38.588 dB	(diff=0.91162 dB) */
+    0x03,   /* [021] -39.0 dB:	AKM[003] = -38.588 dB	(diff=0.41162 dB) */
+    0x03,   /* [022] -38.5 dB:	AKM[003] = -38.588 dB	(diff=0.08838 dB) */
+    0x03,   /* [023] -38.0 dB:	AKM[003] = -38.588 dB	(diff=0.58838 dB) */
+    0x03,   /* [024] -37.5 dB:	AKM[003] = -38.588 dB	(diff=1.08838 dB) */
+    0x04,   /* [025] -37.0 dB:	AKM[004] = -36.090 dB	(diff=0.91040 dB) */
+    0x04,   /* [026] -36.5 dB:	AKM[004] = -36.090 dB	(diff=0.41040 dB) */
+    0x04,   /* [027] -36.0 dB:	AKM[004] = -36.090 dB	(diff=0.08960 dB) */
+    0x04,   /* [028] -35.5 dB:	AKM[004] = -36.090 dB	(diff=0.58960 dB) */
+    0x05,   /* [029] -35.0 dB:	AKM[005] = -34.151 dB	(diff=0.84860 dB) */
+    0x05,   /* [030] -34.5 dB:	AKM[005] = -34.151 dB	(diff=0.34860 dB) */
+    0x05,   /* [031] -34.0 dB:	AKM[005] = -34.151 dB	(diff=0.15140 dB) */
+    0x05,   /* [032] -33.5 dB:	AKM[005] = -34.151 dB	(diff=0.65140 dB) */
+    0x06,   /* [033] -33.0 dB:	AKM[006] = -32.568 dB	(diff=0.43222 dB) */
+    0x06,   /* [034] -32.5 dB:	AKM[006] = -32.568 dB	(diff=0.06778 dB) */
+    0x06,   /* [035] -32.0 dB:	AKM[006] = -32.568 dB	(diff=0.56778 dB) */
+    0x07,   /* [036] -31.5 dB:	AKM[007] = -31.229 dB	(diff=0.27116 dB) */
+    0x07,   /* [037] -31.0 dB:	AKM[007] = -31.229 dB	(diff=0.22884 dB) */
+    0x08,   /* [038] -30.5 dB:	AKM[008] = -30.069 dB	(diff=0.43100 dB) */
+    0x08,   /* [039] -30.0 dB:	AKM[008] = -30.069 dB	(diff=0.06900 dB) */
+    0x09,   /* [040] -29.5 dB:	AKM[009] = -29.046 dB	(diff=0.45405 dB) */
+    0x09,   /* [041] -29.0 dB:	AKM[009] = -29.046 dB	(diff=0.04595 dB) */
+    0x0a,   /* [042] -28.5 dB:	AKM[010] = -28.131 dB	(diff=0.36920 dB) */
+    0x0a,   /* [043] -28.0 dB:	AKM[010] = -28.131 dB	(diff=0.13080 dB) */
+    0x0b,   /* [044] -27.5 dB:	AKM[011] = -27.303 dB	(diff=0.19705 dB) */
+    0x0b,   /* [045] -27.0 dB:	AKM[011] = -27.303 dB	(diff=0.30295 dB) */
+    0x0c,   /* [046] -26.5 dB:	AKM[012] = -26.547 dB	(diff=0.04718 dB) */
+    0x0d,   /* [047] -26.0 dB:	AKM[013] = -25.852 dB	(diff=0.14806 dB) */
+    0x0e,   /* [048] -25.5 dB:	AKM[014] = -25.208 dB	(diff=0.29176 dB) */
+    0x0e,   /* [049] -25.0 dB:	AKM[014] = -25.208 dB	(diff=0.20824 dB) */
+    0x0f,   /* [050] -24.5 dB:	AKM[015] = -24.609 dB	(diff=0.10898 dB) */
+    0x10,   /* [051] -24.0 dB:	AKM[016] = -24.048 dB	(diff=0.04840 dB) */
+    0x11,   /* [052] -23.5 dB:	AKM[017] = -23.522 dB	(diff=0.02183 dB) */
+    0x12,   /* [053] -23.0 dB:	AKM[018] = -23.025 dB	(diff=0.02535 dB) */
+    0x13,   /* [054] -22.5 dB:	AKM[019] = -22.556 dB	(diff=0.05573 dB) */
+    0x14,   /* [055] -22.0 dB:	AKM[020] = -22.110 dB	(diff=0.11020 dB) */
+    0x15,   /* [056] -21.5 dB:	AKM[021] = -21.686 dB	(diff=0.18642 dB) */
+    0x17,   /* [057] -21.0 dB:	AKM[023] = -20.896 dB	(diff=0.10375 dB) */
+    0x18,   /* [058] -20.5 dB:	AKM[024] = -20.527 dB	(diff=0.02658 dB) */
+    0x1a,   /* [059] -20.0 dB:	AKM[026] = -19.831 dB	(diff=0.16866 dB) */
+    0x1b,   /* [060] -19.5 dB:	AKM[027] = -19.504 dB	(diff=0.00353 dB) */
+    0x1d,   /* [061] -19.0 dB:	AKM[029] = -18.883 dB	(diff=0.11716 dB) */
+    0x1e,   /* [062] -18.5 dB:	AKM[030] = -18.588 dB	(diff=0.08838 dB) */
+    0x20,   /* [063] -18.0 dB:	AKM[032] = -18.028 dB	(diff=0.02780 dB) */
+    0x22,   /* [064] -17.5 dB:	AKM[034] = -17.501 dB	(diff=0.00123 dB) */
+    0x24,   /* [065] -17.0 dB:	AKM[036] = -17.005 dB	(diff=0.00475 dB) */
+    0x26,   /* [066] -16.5 dB:	AKM[038] = -16.535 dB	(diff=0.03513 dB) */
+    0x28,   /* [067] -16.0 dB:	AKM[040] = -16.090 dB	(diff=0.08960 dB) */
+    0x2b,   /* [068] -15.5 dB:	AKM[043] = -15.461 dB	(diff=0.03857 dB) */
+    0x2d,   /* [069] -15.0 dB:	AKM[045] = -15.067 dB	(diff=0.06655 dB) */
+    0x30,   /* [070] -14.5 dB:	AKM[048] = -14.506 dB	(diff=0.00598 dB) */
+    0x33,   /* [071] -14.0 dB:	AKM[051] = -13.979 dB	(diff=0.02060 dB) */
+    0x36,   /* [072] -13.5 dB:	AKM[054] = -13.483 dB	(diff=0.01707 dB) */
+    0x39,   /* [073] -13.0 dB:	AKM[057] = -13.013 dB	(diff=0.01331 dB) */
+    0x3c,   /* [074] -12.5 dB:	AKM[060] = -12.568 dB	(diff=0.06778 dB) */
+    0x40,   /* [075] -12.0 dB:	AKM[064] = -12.007 dB	(diff=0.00720 dB) */
+    0x44,   /* [076] -11.5 dB:	AKM[068] = -11.481 dB	(diff=0.01937 dB) */
+    0x48,   /* [077] -11.0 dB:	AKM[072] = -10.984 dB	(diff=0.01585 dB) */
+    0x4c,   /* [078] -10.5 dB:	AKM[076] = -10.515 dB	(diff=0.01453 dB) */
+    0x51,   /* [079] -10.0 dB:	AKM[081] = -9.961 dB	(diff=0.03890 dB) */
+    0x55,   /* [080] -9.5 dB:	AKM[085] = -9.542 dB	(diff=0.04243 dB) */
+    0x5a,   /* [081] -9.0 dB:	AKM[090] = -9.046 dB	(diff=0.04595 dB) */
+    0x60,   /* [082] -8.5 dB:	AKM[096] = -8.485 dB	(diff=0.01462 dB) */
+    0x66,   /* [083] -8.0 dB:	AKM[102] = -7.959 dB	(diff=0.04120 dB) */
+    0x6c,   /* [084] -7.5 dB:	AKM[108] = -7.462 dB	(diff=0.03767 dB) */
+    0x72,   /* [085] -7.0 dB:	AKM[114] = -6.993 dB	(diff=0.00729 dB) */
+    0x79,   /* [086] -6.5 dB:	AKM[121] = -6.475 dB	(diff=0.02490 dB) */
+    0x80,   /* [087] -6.0 dB:	AKM[128] = -5.987 dB	(diff=0.01340 dB) */
+    0x87,   /* [088] -5.5 dB:	AKM[135] = -5.524 dB	(diff=0.02413 dB) */
+    0x8f,   /* [089] -5.0 dB:	AKM[143] = -5.024 dB	(diff=0.02408 dB) */
+    0x98,   /* [090] -4.5 dB:	AKM[152] = -4.494 dB	(diff=0.00607 dB) */
+    0xa1,   /* [091] -4.0 dB:	AKM[161] = -3.994 dB	(diff=0.00571 dB) */
+    0xaa,   /* [092] -3.5 dB:	AKM[170] = -3.522 dB	(diff=0.02183 dB) */
+    0xb5,   /* [093] -3.0 dB:	AKM[181] = -2.977 dB	(diff=0.02277 dB) */
+    0xbf,   /* [094] -2.5 dB:	AKM[191] = -2.510 dB	(diff=0.01014 dB) */
+    0xcb,   /* [095] -2.0 dB:	AKM[203] = -1.981 dB	(diff=0.01912 dB) */
+    0xd7,   /* [096] -1.5 dB:	AKM[215] = -1.482 dB	(diff=0.01797 dB) */
+    0xe3,   /* [097] -1.0 dB:	AKM[227] = -1.010 dB	(diff=0.01029 dB) */
+    0xf1,   /* [098] -0.5 dB:	AKM[241] = -0.490 dB	(diff=0.00954 dB) */
+    0xff,   /* [099] +0.0 dB:	AKM[255] = +0.000 dB	(diff=0.00000 dB) */
+};
+
+
+static void hr222_config_akm(struct pcxhr_mgr *mgr, unsigned short data)
+{
+	unsigned short mask = 0x8000;
+	/* activate access to codec registers */
+	PCXHR_INPB(mgr, PCXHR_XLX_HIFREQ);
+
+	while (mask) {
+		PCXHR_OUTPB(mgr, PCXHR_XLX_DATA,
+			    data & mask ? PCXHR_DATA_CODEC : 0);
+		mask >>= 1;
+	}
+	/* termiate access to codec registers */
+	PCXHR_INPB(mgr, PCXHR_XLX_RUER);
+}
+
+
+static int hr222_set_hw_playback_level(struct pcxhr_mgr *mgr,
+				       int idx, int level)
+{
+	unsigned short cmd;
+	if (idx > 1 ||
+	    level < 0 ||
+	    level >= ARRAY_SIZE(g_hr222_p_level))
+		return -EINVAL;
+
+	if (idx == 0)
+		cmd = AKM_LEFT_LEVEL_CMD;
+	else
+		cmd = AKM_RIGHT_LEVEL_CMD;
+
+	/* conversion from PmBoardCodedLevel to AKM nonlinear programming */
+	cmd += g_hr222_p_level[level];
+
+	hr222_config_akm(mgr, cmd);
+	return 0;
+}
+
+
+static int hr222_set_hw_capture_level(struct pcxhr_mgr *mgr,
+				      int level_l, int level_r, int level_mic)
+{
+	/* program all input levels at the same time */
+	unsigned int data;
+	int i;
+
+	if (!mgr->capture_chips)
+		return -EINVAL;	/* no PCX22 */
+
+	data  = ((level_mic & 0xff) << 24);	/* micro is mono, but apply */
+	data |= ((level_mic & 0xff) << 16);	/* level on both channels */
+	data |= ((level_r & 0xff) << 8);	/* line input right channel */
+	data |= (level_l & 0xff);		/* line input left channel */
+
+	PCXHR_INPB(mgr, PCXHR_XLX_DATA);	/* activate input codec */
+	/* send 32 bits (4 x 8 bits) */
+	for (i = 0; i < 32; i++, data <<= 1) {
+		PCXHR_OUTPB(mgr, PCXHR_XLX_DATA,
+			    (data & 0x80000000) ? PCXHR_DATA_CODEC : 0);
+	}
+	PCXHR_INPB(mgr, PCXHR_XLX_RUER);	/* close input level codec */
+	return 0;
+}
+
+static void hr222_micro_boost(struct pcxhr_mgr *mgr, int level);
+
+int hr222_sub_init(struct pcxhr_mgr *mgr)
+{
+	unsigned char reg;
+
+	mgr->board_has_analog = 1;	/* analog always available */
+	mgr->xlx_cfg = PCXHR_CFG_SYNCDSP_MASK;
+
+	reg = PCXHR_INPB(mgr, PCXHR_XLX_STATUS);
+	if (reg & PCXHR_STAT_MIC_CAPS)
+		mgr->board_has_mic = 1;	/* microphone available */
+	snd_printdd("MIC input available = %d\n", mgr->board_has_mic);
+
+	/* reset codec */
+	PCXHR_OUTPB(mgr, PCXHR_DSP_RESET,
+		    PCXHR_DSP_RESET_DSP);
+	msleep(5);
+	mgr->dsp_reset = PCXHR_DSP_RESET_DSP  |
+			 PCXHR_DSP_RESET_MUTE |
+			 PCXHR_DSP_RESET_CODEC;
+	PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, mgr->dsp_reset);
+	/* hr222_write_gpo(mgr, 0); does the same */
+	msleep(5);
+
+	/* config AKM */
+	hr222_config_akm(mgr, AKM_POWER_CONTROL_CMD);
+	hr222_config_akm(mgr, AKM_CLOCK_INF_55K_CMD);
+	hr222_config_akm(mgr, AKM_UNMUTE_CMD);
+	hr222_config_akm(mgr, AKM_RESET_OFF_CMD);
+
+	/* init micro boost */
+	hr222_micro_boost(mgr, 0);
+
+	return 0;
+}
+
+
+/* calc PLL register */
+/* TODO : there is a very similar fct in pcxhr.c */
+static int hr222_pll_freq_register(unsigned int freq,
+				   unsigned int *pllreg,
+				   unsigned int *realfreq)
+{
+	unsigned int reg;
+
+	if (freq < 6900 || freq > 219000)
+		return -EINVAL;
+	reg = (28224000 * 2) / freq;
+	reg = (reg - 1) / 2;
+	if (reg < 0x100)
+		*pllreg = reg + 0xC00;
+	else if (reg < 0x200)
+		*pllreg = reg + 0x800;
+	else if (reg < 0x400)
+		*pllreg = reg & 0x1ff;
+	else if (reg < 0x800) {
+		*pllreg = ((reg >> 1) & 0x1ff) + 0x200;
+		reg &= ~1;
+	} else {
+		*pllreg = ((reg >> 2) & 0x1ff) + 0x400;
+		reg &= ~3;
+	}
+	if (realfreq)
+		*realfreq = (28224000 / (reg + 1));
+	return 0;
+}
+
+int hr222_sub_set_clock(struct pcxhr_mgr *mgr,
+			unsigned int rate,
+			int *changed)
+{
+	unsigned int speed, pllreg = 0;
+	int err;
+	unsigned realfreq = rate;
+
+	switch (mgr->use_clock_type) {
+	case HR22_CLOCK_TYPE_INTERNAL:
+		err = hr222_pll_freq_register(rate, &pllreg, &realfreq);
+		if (err)
+			return err;
+
+		mgr->xlx_cfg &= ~(PCXHR_CFG_CLOCKIN_SEL_MASK |
+				  PCXHR_CFG_CLOCK_UER1_SEL_MASK);
+		break;
+	case HR22_CLOCK_TYPE_AES_SYNC:
+		mgr->xlx_cfg |= PCXHR_CFG_CLOCKIN_SEL_MASK;
+		mgr->xlx_cfg &= ~PCXHR_CFG_CLOCK_UER1_SEL_MASK;
+		break;
+	case HR22_CLOCK_TYPE_AES_1:
+		if (!mgr->board_has_aes1)
+			return -EINVAL;
+
+		mgr->xlx_cfg |= (PCXHR_CFG_CLOCKIN_SEL_MASK |
+				 PCXHR_CFG_CLOCK_UER1_SEL_MASK);
+		break;
+	default:
+		return -EINVAL;
+	}
+	hr222_config_akm(mgr, AKM_MUTE_CMD);
+
+	if (mgr->use_clock_type == HR22_CLOCK_TYPE_INTERNAL) {
+		PCXHR_OUTPB(mgr, PCXHR_XLX_HIFREQ, pllreg >> 8);
+		PCXHR_OUTPB(mgr, PCXHR_XLX_LOFREQ, pllreg & 0xff);
+	}
+
+	/* set clock source */
+	PCXHR_OUTPB(mgr, PCXHR_XLX_CFG, mgr->xlx_cfg);
+
+	/* codec speed modes */
+	speed = rate < 55000 ? 0 : 1;
+	if (mgr->codec_speed != speed) {
+		mgr->codec_speed = speed;
+		if (speed == 0)
+			hr222_config_akm(mgr, AKM_CLOCK_INF_55K_CMD);
+		else
+			hr222_config_akm(mgr, AKM_CLOCK_SUP_55K_CMD);
+	}
+
+	mgr->sample_rate_real = realfreq;
+	mgr->cur_clock_type = mgr->use_clock_type;
+
+	if (changed)
+		*changed = 1;
+
+	hr222_config_akm(mgr, AKM_UNMUTE_CMD);
+
+	snd_printdd("set_clock to %dHz (realfreq=%d pllreg=%x)\n",
+		    rate, realfreq, pllreg);
+	return 0;
+}
+
+int hr222_get_external_clock(struct pcxhr_mgr *mgr,
+			     enum pcxhr_clock_type clock_type,
+			     int *sample_rate)
+{
+	int rate, calc_rate = 0;
+	unsigned int ticks;
+	unsigned char mask, reg;
+
+	if (clock_type == HR22_CLOCK_TYPE_AES_SYNC) {
+
+		mask = (PCXHR_SUER_CLOCK_PRESENT_MASK |
+			PCXHR_SUER_DATA_PRESENT_MASK);
+		reg = PCXHR_STAT_FREQ_SYNC_MASK;
+
+	} else if (clock_type == HR22_CLOCK_TYPE_AES_1 && mgr->board_has_aes1) {
+
+		mask = (PCXHR_SUER1_CLOCK_PRESENT_MASK |
+			PCXHR_SUER1_DATA_PRESENT_MASK);
+		reg = PCXHR_STAT_FREQ_UER1_MASK;
+
+	} else {
+		snd_printdd("get_external_clock : type %d not supported\n",
+			    clock_type);
+		return -EINVAL; /* other clocks not supported */
+	}
+
+	if ((PCXHR_INPB(mgr, PCXHR_XLX_CSUER) & mask) != mask) {
+		snd_printdd("get_external_clock(%d) = 0 Hz\n", clock_type);
+		*sample_rate = 0;
+		return 0; /* no external clock locked */
+	}
+
+	PCXHR_OUTPB(mgr, PCXHR_XLX_STATUS, reg); /* calculate freq */
+
+	/* save the measured clock frequency */
+	reg |= PCXHR_STAT_FREQ_SAVE_MASK;
+
+	if (mgr->last_reg_stat != reg) {
+		udelay(500);	/* wait min 2 cycles of lowest freq (8000) */
+		mgr->last_reg_stat = reg;
+	}
+
+	PCXHR_OUTPB(mgr, PCXHR_XLX_STATUS, reg); /* save */
+
+	/* get the frequency */
+	ticks = (unsigned int)PCXHR_INPB(mgr, PCXHR_XLX_CFG);
+	ticks = (ticks & 0x03) << 8;
+	ticks |= (unsigned int)PCXHR_INPB(mgr, PCXHR_DSP_RESET);
+
+	if (ticks != 0)
+		calc_rate = 28224000 / ticks;
+	/* rounding */
+	if (calc_rate > 184200)
+		rate = 192000;
+	else if (calc_rate > 152200)
+		rate = 176400;
+	else if (calc_rate > 112000)
+		rate = 128000;
+	else if (calc_rate > 92100)
+		rate = 96000;
+	else if (calc_rate > 76100)
+		rate = 88200;
+	else if (calc_rate > 56000)
+		rate = 64000;
+	else if (calc_rate > 46050)
+		rate = 48000;
+	else if (calc_rate > 38050)
+		rate = 44100;
+	else if (calc_rate > 28000)
+		rate = 32000;
+	else if (calc_rate > 23025)
+		rate = 24000;
+	else if (calc_rate > 19025)
+		rate = 22050;
+	else if (calc_rate > 14000)
+		rate = 16000;
+	else if (calc_rate > 11512)
+		rate = 12000;
+	else if (calc_rate > 9512)
+		rate = 11025;
+	else if (calc_rate > 7000)
+		rate = 8000;
+	else
+		rate = 0;
+
+	snd_printdd("External clock is at %d Hz (measured %d Hz)\n",
+		    rate, calc_rate);
+	*sample_rate = rate;
+	return 0;
+}
+
+
+int hr222_read_gpio(struct pcxhr_mgr *mgr, int is_gpi, int *value)
+{
+	if (is_gpi) {
+		unsigned char reg = PCXHR_INPB(mgr, PCXHR_XLX_STATUS);
+		*value = (int)(reg & PCXHR_STAT_GPI_MASK) >>
+			      PCXHR_STAT_GPI_OFFSET;
+	} else {
+		*value = (int)(mgr->dsp_reset & PCXHR_DSP_RESET_GPO_MASK) >>
+			 PCXHR_DSP_RESET_GPO_OFFSET;
+	}
+	return 0;
+}
+
+
+int hr222_write_gpo(struct pcxhr_mgr *mgr, int value)
+{
+	unsigned char reg = mgr->dsp_reset & ~PCXHR_DSP_RESET_GPO_MASK;
+
+	reg |= (unsigned char)(value << PCXHR_DSP_RESET_GPO_OFFSET) &
+	       PCXHR_DSP_RESET_GPO_MASK;
+
+	PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, reg);
+	mgr->dsp_reset = reg;
+	return 0;
+}
+
+
+int hr222_update_analog_audio_level(struct snd_pcxhr *chip,
+				    int is_capture, int channel)
+{
+	snd_printdd("hr222_update_analog_audio_level(%s chan=%d)\n",
+		    is_capture ? "capture" : "playback", channel);
+	if (is_capture) {
+		int level_l, level_r, level_mic;
+		/* we have to update all levels */
+		if (chip->analog_capture_active) {
+			level_l = chip->analog_capture_volume[0];
+			level_r = chip->analog_capture_volume[1];
+		} else {
+			level_l = HR222_LINE_CAPTURE_LEVEL_MIN;
+			level_r = HR222_LINE_CAPTURE_LEVEL_MIN;
+		}
+		if (chip->mic_active)
+			level_mic = chip->mic_volume;
+		else
+			level_mic = HR222_MICRO_CAPTURE_LEVEL_MIN;
+		return hr222_set_hw_capture_level(chip->mgr,
+						 level_l, level_r, level_mic);
+	} else {
+		int vol;
+		if (chip->analog_playback_active[channel])
+			vol = chip->analog_playback_volume[channel];
+		else
+			vol = HR222_LINE_PLAYBACK_LEVEL_MIN;
+		return hr222_set_hw_playback_level(chip->mgr, channel, vol);
+	}
+}
+
+
+/*texts[5] = {"Line", "Digital", "Digi+SRC", "Mic", "Line+Mic"}*/
+#define SOURCE_LINE	0
+#define SOURCE_DIGITAL	1
+#define SOURCE_DIGISRC	2
+#define SOURCE_MIC	3
+#define SOURCE_LINEMIC	4
+
+int hr222_set_audio_source(struct snd_pcxhr *chip)
+{
+	int digital = 0;
+	/* default analog source */
+	chip->mgr->xlx_cfg &= ~(PCXHR_CFG_SRC_MASK |
+				PCXHR_CFG_DATAIN_SEL_MASK |
+				PCXHR_CFG_DATA_UER1_SEL_MASK);
+
+	if (chip->audio_capture_source == SOURCE_DIGISRC) {
+		chip->mgr->xlx_cfg |= PCXHR_CFG_SRC_MASK;
+		digital = 1;
+	} else {
+		if (chip->audio_capture_source == SOURCE_DIGITAL)
+			digital = 1;
+	}
+	if (digital) {
+		chip->mgr->xlx_cfg |=  PCXHR_CFG_DATAIN_SEL_MASK;
+		if (chip->mgr->board_has_aes1) {
+			/* get data from the AES1 plug */
+			chip->mgr->xlx_cfg |= PCXHR_CFG_DATA_UER1_SEL_MASK;
+		}
+		/* chip->mic_active = 0; */
+		/* chip->analog_capture_active = 0; */
+	} else {
+		int update_lvl = 0;
+		chip->analog_capture_active = 0;
+		chip->mic_active = 0;
+		if (chip->audio_capture_source == SOURCE_LINE ||
+		    chip->audio_capture_source == SOURCE_LINEMIC) {
+			if (chip->analog_capture_active == 0)
+				update_lvl = 1;
+			chip->analog_capture_active = 1;
+		}
+		if (chip->audio_capture_source == SOURCE_MIC ||
+		    chip->audio_capture_source == SOURCE_LINEMIC) {
+			if (chip->mic_active == 0)
+				update_lvl = 1;
+			chip->mic_active = 1;
+		}
+		if (update_lvl) {
+			/* capture: update all 3 mutes/unmutes with one call */
+			hr222_update_analog_audio_level(chip, 1, 0);
+		}
+	}
+	/* set the source infos (max 3 bits modified) */
+	PCXHR_OUTPB(chip->mgr, PCXHR_XLX_CFG, chip->mgr->xlx_cfg);
+	return 0;
+}
+
+
+int hr222_iec958_capture_byte(struct snd_pcxhr *chip,
+			     int aes_idx, unsigned char *aes_bits)
+{
+	unsigned char idx = (unsigned char)(aes_idx * 8);
+	unsigned char temp = 0;
+	unsigned char mask = chip->mgr->board_has_aes1 ?
+		PCXHR_SUER1_BIT_C_READ_MASK : PCXHR_SUER_BIT_C_READ_MASK;
+	int i;
+	for (i = 0; i < 8; i++) {
+		PCXHR_OUTPB(chip->mgr, PCXHR_XLX_RUER, idx++); /* idx < 192 */
+		temp <<= 1;
+		if (PCXHR_INPB(chip->mgr, PCXHR_XLX_CSUER) & mask)
+			temp |= 1;
+	}
+	snd_printdd("read iec958 AES %d byte %d = 0x%x\n",
+		    chip->chip_idx, aes_idx, temp);
+	*aes_bits = temp;
+	return 0;
+}
+
+
+int hr222_iec958_update_byte(struct snd_pcxhr *chip,
+			     int aes_idx, unsigned char aes_bits)
+{
+	int i;
+	unsigned char new_bits = aes_bits;
+	unsigned char old_bits = chip->aes_bits[aes_idx];
+	unsigned char idx = (unsigned char)(aes_idx * 8);
+	for (i = 0; i < 8; i++) {
+		if ((old_bits & 0x01) != (new_bits & 0x01)) {
+			/* idx < 192 */
+			PCXHR_OUTPB(chip->mgr, PCXHR_XLX_RUER, idx);
+			/* write C and U bit */
+			PCXHR_OUTPB(chip->mgr, PCXHR_XLX_CSUER, new_bits&0x01 ?
+				    PCXHR_SUER_BIT_C_WRITE_MASK : 0);
+		}
+		idx++;
+		old_bits >>= 1;
+		new_bits >>= 1;
+	}
+	chip->aes_bits[aes_idx] = aes_bits;
+	return 0;
+}
+
+static void hr222_micro_boost(struct pcxhr_mgr *mgr, int level)
+{
+	unsigned char boost_mask;
+	boost_mask = (unsigned char) (level << PCXHR_SELMIC_PREAMPLI_OFFSET);
+	if (boost_mask & (~PCXHR_SELMIC_PREAMPLI_MASK))
+		return; /* only values form 0 to 3 accepted */
+
+	mgr->xlx_selmic &= ~PCXHR_SELMIC_PREAMPLI_MASK;
+	mgr->xlx_selmic |= boost_mask;
+
+	PCXHR_OUTPB(mgr, PCXHR_XLX_SELMIC, mgr->xlx_selmic);
+
+	snd_printdd("hr222_micro_boost : set %x\n", boost_mask);
+}
+
+static void hr222_phantom_power(struct pcxhr_mgr *mgr, int power)
+{
+	if (power)
+		mgr->xlx_selmic |= PCXHR_SELMIC_PHANTOM_ALIM;
+	else
+		mgr->xlx_selmic &= ~PCXHR_SELMIC_PHANTOM_ALIM;
+
+	PCXHR_OUTPB(mgr, PCXHR_XLX_SELMIC, mgr->xlx_selmic);
+
+	snd_printdd("hr222_phantom_power : set %d\n", power);
+}
+
+
+/* mic level */
+static const DECLARE_TLV_DB_SCALE(db_scale_mic_hr222, -9850, 50, 650);
+
+static int hr222_mic_vol_info(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = HR222_MICRO_CAPTURE_LEVEL_MIN; /* -98 dB */
+	/* gains from 9 dB to 31.5 dB not recommended; use micboost instead */
+	uinfo->value.integer.max = HR222_MICRO_CAPTURE_LEVEL_MAX; /*  +7 dB */
+	return 0;
+}
+
+static int hr222_mic_vol_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&chip->mgr->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->mic_volume;
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int hr222_mic_vol_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	mutex_lock(&chip->mgr->mixer_mutex);
+	if (chip->mic_volume != ucontrol->value.integer.value[0]) {
+		changed = 1;
+		chip->mic_volume = ucontrol->value.integer.value[0];
+		hr222_update_analog_audio_level(chip, 1, 0);
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new hr222_control_mic_level = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.name =		"Mic Capture Volume",
+	.info =		hr222_mic_vol_info,
+	.get =		hr222_mic_vol_get,
+	.put =		hr222_mic_vol_put,
+	.tlv = { .p = db_scale_mic_hr222 },
+};
+
+
+/* mic boost level */
+static const DECLARE_TLV_DB_SCALE(db_scale_micboost_hr222, 0, 1800, 5400);
+
+static int hr222_mic_boost_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;	/*  0 dB */
+	uinfo->value.integer.max = 3;	/* 54 dB */
+	return 0;
+}
+
+static int hr222_mic_boost_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&chip->mgr->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->mic_boost;
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int hr222_mic_boost_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	mutex_lock(&chip->mgr->mixer_mutex);
+	if (chip->mic_boost != ucontrol->value.integer.value[0]) {
+		changed = 1;
+		chip->mic_boost = ucontrol->value.integer.value[0];
+		hr222_micro_boost(chip->mgr, chip->mic_boost);
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new hr222_control_mic_boost = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.name =		"MicBoost Capture Volume",
+	.info =		hr222_mic_boost_info,
+	.get =		hr222_mic_boost_get,
+	.put =		hr222_mic_boost_put,
+	.tlv = { .p = db_scale_micboost_hr222 },
+};
+
+
+/******************* Phantom power switch *******************/
+#define hr222_phantom_power_info	snd_ctl_boolean_mono_info
+
+static int hr222_phantom_power_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&chip->mgr->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->phantom_power;
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int hr222_phantom_power_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int power, changed = 0;
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	power = !!ucontrol->value.integer.value[0];
+	if (chip->phantom_power != power) {
+		hr222_phantom_power(chip->mgr, power);
+		chip->phantom_power = power;
+		changed = 1;
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new hr222_phantom_power_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Phantom Power Switch",
+	.info = hr222_phantom_power_info,
+	.get = hr222_phantom_power_get,
+	.put = hr222_phantom_power_put,
+};
+
+
+int hr222_add_mic_controls(struct snd_pcxhr *chip)
+{
+	int err;
+	if (!chip->mgr->board_has_mic)
+		return 0;
+
+	/* controls */
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&hr222_control_mic_level,
+						   chip));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&hr222_control_mic_boost,
+						   chip));
+	if (err < 0)
+		return err;
+
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&hr222_phantom_power_switch,
+						   chip));
+	return err;
+}
diff --git a/linux/sound/pci/pcxhr/pcxhr_mix22.h b/linux/sound/pci/pcxhr/pcxhr_mix22.h
new file mode 100644
index 0000000..5a37a00
--- /dev/null
+++ b/linux/sound/pci/pcxhr/pcxhr_mix22.h
@@ -0,0 +1,59 @@
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * low level interface with interrupt ans message handling
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ *
+ *   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 __SOUND_PCXHR_MIX22_H
+#define __SOUND_PCXHR_MIX22_H
+
+struct pcxhr_mgr;
+
+int hr222_sub_init(struct pcxhr_mgr *mgr);
+int hr222_sub_set_clock(struct pcxhr_mgr *mgr, unsigned int rate,
+			int *changed);
+int hr222_get_external_clock(struct pcxhr_mgr *mgr,
+			     enum pcxhr_clock_type clock_type,
+			     int *sample_rate);
+
+int hr222_read_gpio(struct pcxhr_mgr *mgr, int is_gpi, int *value);
+int hr222_write_gpo(struct pcxhr_mgr *mgr, int value);
+
+#define HR222_LINE_PLAYBACK_LEVEL_MIN		0	/* -25.5 dB */
+#define HR222_LINE_PLAYBACK_ZERO_LEVEL		51	/* 0.0 dB */
+#define HR222_LINE_PLAYBACK_LEVEL_MAX		99	/* +24.0 dB */
+
+#define HR222_LINE_CAPTURE_LEVEL_MIN		0	/* -111.5 dB */
+#define HR222_LINE_CAPTURE_ZERO_LEVEL		223	/* 0.0 dB */
+#define HR222_LINE_CAPTURE_LEVEL_MAX		255	/* +16 dB */
+#define HR222_MICRO_CAPTURE_LEVEL_MIN		0	/* -98.5 dB */
+#define HR222_MICRO_CAPTURE_LEVEL_MAX		210	/* +6.5 dB */
+
+int hr222_update_analog_audio_level(struct snd_pcxhr *chip,
+				    int is_capture,
+				    int channel);
+int hr222_set_audio_source(struct snd_pcxhr *chip);
+int hr222_iec958_capture_byte(struct snd_pcxhr *chip, int aes_idx,
+			      unsigned char *aes_bits);
+int hr222_iec958_update_byte(struct snd_pcxhr *chip, int aes_idx,
+			     unsigned char aes_bits);
+
+int hr222_add_mic_controls(struct snd_pcxhr *chip);
+
+#endif /* __SOUND_PCXHR_MIX22_H */
diff --git a/linux/sound/pci/pcxhr/pcxhr_mixer.c b/linux/sound/pci/pcxhr/pcxhr_mixer.c
new file mode 100644
index 0000000..fec0493
--- /dev/null
+++ b/linux/sound/pci/pcxhr/pcxhr_mixer.c
@@ -0,0 +1,1270 @@
+#define __NO_VERSION__
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * mixer callbacks
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include "pcxhr.h"
+#include "pcxhr_hwdep.h"
+#include "pcxhr_core.h"
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/asoundef.h>
+#include "pcxhr_mixer.h"
+#include "pcxhr_mix22.h"
+
+#define PCXHR_LINE_CAPTURE_LEVEL_MIN   0	/* -112.0 dB */
+#define PCXHR_LINE_CAPTURE_LEVEL_MAX   255	/* +15.5 dB */
+#define PCXHR_LINE_CAPTURE_ZERO_LEVEL  224	/* 0.0 dB ( 0 dBu -> 0 dBFS ) */
+
+#define PCXHR_LINE_PLAYBACK_LEVEL_MIN  0	/* -104.0 dB */
+#define PCXHR_LINE_PLAYBACK_LEVEL_MAX  128	/* +24.0 dB */
+#define PCXHR_LINE_PLAYBACK_ZERO_LEVEL 104	/* 0.0 dB ( 0 dBFS -> 0 dBu ) */
+
+static const DECLARE_TLV_DB_SCALE(db_scale_analog_capture, -11200, 50, 1550);
+static const DECLARE_TLV_DB_SCALE(db_scale_analog_playback, -10400, 100, 2400);
+
+static const DECLARE_TLV_DB_SCALE(db_scale_a_hr222_capture, -11150, 50, 1600);
+static const DECLARE_TLV_DB_SCALE(db_scale_a_hr222_playback, -2550, 50, 2400);
+
+static int pcxhr_update_analog_audio_level(struct snd_pcxhr *chip,
+					   int is_capture, int channel)
+{
+	int err, vol;
+	struct pcxhr_rmh rmh;
+
+	pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE);
+	if (is_capture) {
+		rmh.cmd[0] |= IO_NUM_REG_IN_ANA_LEVEL;
+		rmh.cmd[2] = chip->analog_capture_volume[channel];
+	} else {
+		rmh.cmd[0] |= IO_NUM_REG_OUT_ANA_LEVEL;
+		if (chip->analog_playback_active[channel])
+			vol = chip->analog_playback_volume[channel];
+		else
+			vol = PCXHR_LINE_PLAYBACK_LEVEL_MIN;
+		/* playback analog levels are inversed */
+		rmh.cmd[2] = PCXHR_LINE_PLAYBACK_LEVEL_MAX - vol;
+	}
+	rmh.cmd[1]  = 1 << ((2 * chip->chip_idx) + channel);	/* audio mask */
+	rmh.cmd_len = 3;
+	err = pcxhr_send_msg(chip->mgr, &rmh);
+	if (err < 0) {
+		snd_printk(KERN_DEBUG "error update_analog_audio_level card(%d)"
+			   " is_capture(%d) err(%x)\n",
+			   chip->chip_idx, is_capture, err);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * analog level control
+ */
+static int pcxhr_analog_vol_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	if (kcontrol->private_value == 0) {	/* playback */
+	    if (chip->mgr->is_hr_stereo) {
+		uinfo->value.integer.min =
+			HR222_LINE_PLAYBACK_LEVEL_MIN;	/* -25 dB */
+		uinfo->value.integer.max =
+			HR222_LINE_PLAYBACK_LEVEL_MAX;	/* +24 dB */
+	    } else {
+		uinfo->value.integer.min =
+			PCXHR_LINE_PLAYBACK_LEVEL_MIN;	/*-104 dB */
+		uinfo->value.integer.max =
+			PCXHR_LINE_PLAYBACK_LEVEL_MAX;	/* +24 dB */
+	    }
+	} else {				/* capture */
+	    if (chip->mgr->is_hr_stereo) {
+		uinfo->value.integer.min =
+			HR222_LINE_CAPTURE_LEVEL_MIN;	/*-112 dB */
+		uinfo->value.integer.max =
+			HR222_LINE_CAPTURE_LEVEL_MAX;	/* +15.5 dB */
+	    } else {
+		uinfo->value.integer.min =
+			PCXHR_LINE_CAPTURE_LEVEL_MIN;	/*-112 dB */
+		uinfo->value.integer.max =
+			PCXHR_LINE_CAPTURE_LEVEL_MAX;	/* +15.5 dB */
+	    }
+	}
+	return 0;
+}
+
+static int pcxhr_analog_vol_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&chip->mgr->mixer_mutex);
+	if (kcontrol->private_value == 0) {	/* playback */
+	  ucontrol->value.integer.value[0] = chip->analog_playback_volume[0];
+	  ucontrol->value.integer.value[1] = chip->analog_playback_volume[1];
+	} else {				/* capture */
+	  ucontrol->value.integer.value[0] = chip->analog_capture_volume[0];
+	  ucontrol->value.integer.value[1] = chip->analog_capture_volume[1];
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int pcxhr_analog_vol_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int is_capture, i;
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	is_capture = (kcontrol->private_value != 0);
+	for (i = 0; i < 2; i++) {
+		int  new_volume = ucontrol->value.integer.value[i];
+		int *stored_volume = is_capture ?
+			&chip->analog_capture_volume[i] :
+			&chip->analog_playback_volume[i];
+		if (is_capture) {
+			if (chip->mgr->is_hr_stereo) {
+				if (new_volume < HR222_LINE_CAPTURE_LEVEL_MIN ||
+				    new_volume > HR222_LINE_CAPTURE_LEVEL_MAX)
+					continue;
+			} else {
+				if (new_volume < PCXHR_LINE_CAPTURE_LEVEL_MIN ||
+				    new_volume > PCXHR_LINE_CAPTURE_LEVEL_MAX)
+					continue;
+			}
+		} else {
+			if (chip->mgr->is_hr_stereo) {
+				if (new_volume < HR222_LINE_PLAYBACK_LEVEL_MIN ||
+				    new_volume > HR222_LINE_PLAYBACK_LEVEL_MAX)
+					continue;
+			} else {
+				if (new_volume < PCXHR_LINE_PLAYBACK_LEVEL_MIN ||
+				    new_volume > PCXHR_LINE_PLAYBACK_LEVEL_MAX)
+					continue;
+			}
+		}
+		if (*stored_volume != new_volume) {
+			*stored_volume = new_volume;
+			changed = 1;
+			if (chip->mgr->is_hr_stereo)
+				hr222_update_analog_audio_level(chip,
+								is_capture, i);
+			else
+				pcxhr_update_analog_audio_level(chip,
+								is_capture, i);
+		}
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new pcxhr_control_analog_level = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	/* name will be filled later */
+	.info =		pcxhr_analog_vol_info,
+	.get =		pcxhr_analog_vol_get,
+	.put =		pcxhr_analog_vol_put,
+	/* tlv will be filled later */
+};
+
+/* shared */
+
+#define pcxhr_sw_info		snd_ctl_boolean_stereo_info
+
+static int pcxhr_audio_sw_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->analog_playback_active[0];
+	ucontrol->value.integer.value[1] = chip->analog_playback_active[1];
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int pcxhr_audio_sw_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int i, changed = 0;
+	mutex_lock(&chip->mgr->mixer_mutex);
+	for(i = 0; i < 2; i++) {
+		if (chip->analog_playback_active[i] !=
+		    ucontrol->value.integer.value[i]) {
+			chip->analog_playback_active[i] =
+				!!ucontrol->value.integer.value[i];
+			changed = 1;
+			/* update playback levels */
+			if (chip->mgr->is_hr_stereo)
+				hr222_update_analog_audio_level(chip, 0, i);
+			else
+				pcxhr_update_analog_audio_level(chip, 0, i);
+		}
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new pcxhr_control_output_switch = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Master Playback Switch",
+	.info =		pcxhr_sw_info,		/* shared */
+	.get =		pcxhr_audio_sw_get,
+	.put =		pcxhr_audio_sw_put
+};
+
+
+#define PCXHR_DIGITAL_LEVEL_MIN		0x000	/* -110 dB */
+#define PCXHR_DIGITAL_LEVEL_MAX		0x1ff	/* +18 dB */
+#define PCXHR_DIGITAL_ZERO_LEVEL	0x1b7	/*  0 dB */
+
+static const DECLARE_TLV_DB_SCALE(db_scale_digital, -10975, 25, 1800);
+
+#define MORE_THAN_ONE_STREAM_LEVEL	0x000001
+#define VALID_STREAM_PAN_LEVEL_MASK	0x800000
+#define VALID_STREAM_LEVEL_MASK		0x400000
+#define VALID_STREAM_LEVEL_1_MASK	0x200000
+#define VALID_STREAM_LEVEL_2_MASK	0x100000
+
+static int pcxhr_update_playback_stream_level(struct snd_pcxhr* chip, int idx)
+{
+	int err;
+	struct pcxhr_rmh rmh;
+	struct pcxhr_pipe *pipe = &chip->playback_pipe;
+	int left, right;
+
+	if (chip->digital_playback_active[idx][0])
+		left = chip->digital_playback_volume[idx][0];
+	else
+		left = PCXHR_DIGITAL_LEVEL_MIN;
+	if (chip->digital_playback_active[idx][1])
+		right = chip->digital_playback_volume[idx][1];
+	else
+		right = PCXHR_DIGITAL_LEVEL_MIN;
+
+	pcxhr_init_rmh(&rmh, CMD_STREAM_OUT_LEVEL_ADJUST);
+	/* add pipe and stream mask */
+	pcxhr_set_pipe_cmd_params(&rmh, 0, pipe->first_audio, 0, 1<<idx);
+	/* volume left->left / right->right panoramic level */
+	rmh.cmd[0] |= MORE_THAN_ONE_STREAM_LEVEL;
+	rmh.cmd[2]  = VALID_STREAM_PAN_LEVEL_MASK | VALID_STREAM_LEVEL_1_MASK;
+	rmh.cmd[2] |= (left << 10);
+	rmh.cmd[3]  = VALID_STREAM_PAN_LEVEL_MASK | VALID_STREAM_LEVEL_2_MASK;
+	rmh.cmd[3] |= right;
+	rmh.cmd_len = 4;
+
+	err = pcxhr_send_msg(chip->mgr, &rmh);
+	if (err < 0) {
+		snd_printk(KERN_DEBUG "error update_playback_stream_level "
+			   "card(%d) err(%x)\n", chip->chip_idx, err);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+#define AUDIO_IO_HAS_MUTE_LEVEL		0x400000
+#define AUDIO_IO_HAS_MUTE_MONITOR_1	0x200000
+#define VALID_AUDIO_IO_DIGITAL_LEVEL	0x000001
+#define VALID_AUDIO_IO_MONITOR_LEVEL	0x000002
+#define VALID_AUDIO_IO_MUTE_LEVEL	0x000004
+#define VALID_AUDIO_IO_MUTE_MONITOR_1	0x000008
+
+static int pcxhr_update_audio_pipe_level(struct snd_pcxhr *chip,
+					 int capture, int channel)
+{
+	int err;
+	struct pcxhr_rmh rmh;
+	struct pcxhr_pipe *pipe;
+
+	if (capture)
+		pipe = &chip->capture_pipe[0];
+	else
+		pipe = &chip->playback_pipe;
+
+	pcxhr_init_rmh(&rmh, CMD_AUDIO_LEVEL_ADJUST);
+	/* add channel mask */
+	pcxhr_set_pipe_cmd_params(&rmh, capture, 0, 0,
+				  1 << (channel + pipe->first_audio));
+	/* TODO : if mask (3 << pipe->first_audio) is used, left and right
+	 * channel will be programmed to the same params */
+	if (capture) {
+		rmh.cmd[0] |= VALID_AUDIO_IO_DIGITAL_LEVEL;
+		/* VALID_AUDIO_IO_MUTE_LEVEL not yet handled
+		 * (capture pipe level) */
+		rmh.cmd[2] = chip->digital_capture_volume[channel];
+	} else {
+		rmh.cmd[0] |=	VALID_AUDIO_IO_MONITOR_LEVEL |
+				VALID_AUDIO_IO_MUTE_MONITOR_1;
+		/* VALID_AUDIO_IO_DIGITAL_LEVEL and VALID_AUDIO_IO_MUTE_LEVEL
+		 * not yet handled (playback pipe level)
+		 */
+		rmh.cmd[2] = chip->monitoring_volume[channel] << 10;
+		if (chip->monitoring_active[channel] == 0)
+			rmh.cmd[2] |= AUDIO_IO_HAS_MUTE_MONITOR_1;
+	}
+	rmh.cmd_len = 3;
+
+	err = pcxhr_send_msg(chip->mgr, &rmh);
+	if (err < 0) {
+		snd_printk(KERN_DEBUG "error update_audio_level(%d) err=%x\n",
+			   chip->chip_idx, err);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/* shared */
+static int pcxhr_digital_vol_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = PCXHR_DIGITAL_LEVEL_MIN;   /* -109.5 dB */
+	uinfo->value.integer.max = PCXHR_DIGITAL_LEVEL_MAX;   /*   18.0 dB */
+	return 0;
+}
+
+
+static int pcxhr_pcm_vol_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);	/* index */
+	int *stored_volume;
+	int is_capture = kcontrol->private_value;
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	if (is_capture)		/* digital capture */
+		stored_volume = chip->digital_capture_volume;
+	else			/* digital playback */
+		stored_volume = chip->digital_playback_volume[idx];
+	ucontrol->value.integer.value[0] = stored_volume[0];
+	ucontrol->value.integer.value[1] = stored_volume[1];
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int pcxhr_pcm_vol_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);	/* index */
+	int changed = 0;
+	int is_capture = kcontrol->private_value;
+	int *stored_volume;
+	int i;
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	if (is_capture)		/* digital capture */
+		stored_volume = chip->digital_capture_volume;
+	else			/* digital playback */
+		stored_volume = chip->digital_playback_volume[idx];
+	for (i = 0; i < 2; i++) {
+		int vol = ucontrol->value.integer.value[i];
+		if (vol < PCXHR_DIGITAL_LEVEL_MIN ||
+		    vol > PCXHR_DIGITAL_LEVEL_MAX)
+			continue;
+		if (stored_volume[i] != vol) {
+			stored_volume[i] = vol;
+			changed = 1;
+			if (is_capture)	/* update capture volume */
+				pcxhr_update_audio_pipe_level(chip, 1, i);
+		}
+	}
+	if (!is_capture && changed)	/* update playback volume */
+		pcxhr_update_playback_stream_level(chip, idx);
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_pcxhr_pcm_vol =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	/* name will be filled later */
+	/* count will be filled later */
+	.info =		pcxhr_digital_vol_info,		/* shared */
+	.get =		pcxhr_pcm_vol_get,
+	.put =		pcxhr_pcm_vol_put,
+	.tlv = { .p = db_scale_digital },
+};
+
+
+static int pcxhr_pcm_sw_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->digital_playback_active[idx][0];
+	ucontrol->value.integer.value[1] = chip->digital_playback_active[idx][1];
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int pcxhr_pcm_sw_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); /* index */
+	int i, j;
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	j = idx;
+	for (i = 0; i < 2; i++) {
+		if (chip->digital_playback_active[j][i] !=
+		    ucontrol->value.integer.value[i]) {
+			chip->digital_playback_active[j][i] =
+				!!ucontrol->value.integer.value[i];
+			changed = 1;
+		}
+	}
+	if (changed)
+		pcxhr_update_playback_stream_level(chip, idx);
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new pcxhr_control_pcm_switch = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"PCM Playback Switch",
+	.count =	PCXHR_PLAYBACK_STREAMS,
+	.info =		pcxhr_sw_info,		/* shared */
+	.get =		pcxhr_pcm_sw_get,
+	.put =		pcxhr_pcm_sw_put
+};
+
+
+/*
+ * monitoring level control
+ */
+
+static int pcxhr_monitor_vol_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&chip->mgr->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->monitoring_volume[0];
+	ucontrol->value.integer.value[1] = chip->monitoring_volume[1];
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int pcxhr_monitor_vol_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int i;
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	for (i = 0; i < 2; i++) {
+		if (chip->monitoring_volume[i] !=
+		    ucontrol->value.integer.value[i]) {
+			chip->monitoring_volume[i] =
+				ucontrol->value.integer.value[i];
+			if (chip->monitoring_active[i])
+				/* update monitoring volume and mute */
+				/* do only when monitoring is unmuted */
+				pcxhr_update_audio_pipe_level(chip, 0, i);
+			changed = 1;
+		}
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new pcxhr_control_monitor_vol = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.name =         "Monitoring Playback Volume",
+	.info =		pcxhr_digital_vol_info,		/* shared */
+	.get =		pcxhr_monitor_vol_get,
+	.put =		pcxhr_monitor_vol_put,
+	.tlv = { .p = db_scale_digital },
+};
+
+/*
+ * monitoring switch control
+ */
+
+static int pcxhr_monitor_sw_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	mutex_lock(&chip->mgr->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->monitoring_active[0];
+	ucontrol->value.integer.value[1] = chip->monitoring_active[1];
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return 0;
+}
+
+static int pcxhr_monitor_sw_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int i;
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	for (i = 0; i < 2; i++) {
+		if (chip->monitoring_active[i] !=
+		    ucontrol->value.integer.value[i]) {
+			chip->monitoring_active[i] =
+				!!ucontrol->value.integer.value[i];
+			changed |= (1<<i); /* mask 0x01 and 0x02 */
+		}
+	}
+	if (changed & 0x01)
+		/* update left monitoring volume and mute */
+		pcxhr_update_audio_pipe_level(chip, 0, 0);
+	if (changed & 0x02)
+		/* update right monitoring volume and mute */
+		pcxhr_update_audio_pipe_level(chip, 0, 1);
+
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return (changed != 0);
+}
+
+static struct snd_kcontrol_new pcxhr_control_monitor_sw = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Monitoring Playback Switch",
+	.info =         pcxhr_sw_info,		/* shared */
+	.get =          pcxhr_monitor_sw_get,
+	.put =          pcxhr_monitor_sw_put
+};
+
+
+
+/*
+ * audio source select
+ */
+#define PCXHR_SOURCE_AUDIO01_UER	0x000100
+#define PCXHR_SOURCE_AUDIO01_SYNC	0x000200
+#define PCXHR_SOURCE_AUDIO23_UER	0x000400
+#define PCXHR_SOURCE_AUDIO45_UER	0x001000
+#define PCXHR_SOURCE_AUDIO67_UER	0x040000
+
+static int pcxhr_set_audio_source(struct snd_pcxhr* chip)
+{
+	struct pcxhr_rmh rmh;
+	unsigned int mask, reg;
+	unsigned int codec;
+	int err, changed;
+
+	switch (chip->chip_idx) {
+	case 0 : mask = PCXHR_SOURCE_AUDIO01_UER; codec = CS8420_01_CS; break;
+	case 1 : mask = PCXHR_SOURCE_AUDIO23_UER; codec = CS8420_23_CS; break;
+	case 2 : mask = PCXHR_SOURCE_AUDIO45_UER; codec = CS8420_45_CS; break;
+	case 3 : mask = PCXHR_SOURCE_AUDIO67_UER; codec = CS8420_67_CS; break;
+	default: return -EINVAL;
+	}
+	if (chip->audio_capture_source != 0) {
+		reg = mask;	/* audio source from digital plug */
+	} else {
+		reg = 0;	/* audio source from analog plug */
+	}
+	/* set the input source */
+	pcxhr_write_io_num_reg_cont(chip->mgr, mask, reg, &changed);
+	/* resync them (otherwise channel inversion possible) */
+	if (changed) {
+		pcxhr_init_rmh(&rmh, CMD_RESYNC_AUDIO_INPUTS);
+		rmh.cmd[0] |= (1 << chip->chip_idx);
+		err = pcxhr_send_msg(chip->mgr, &rmh);
+		if (err)
+			return err;
+	}
+	if (chip->mgr->board_aes_in_192k) {
+		int i;
+		unsigned int src_config = 0xC0;
+		/* update all src configs with one call */
+		for (i = 0; (i < 4) && (i < chip->mgr->capture_chips); i++) {
+			if (chip->mgr->chip[i]->audio_capture_source == 2)
+				src_config |= (1 << (3 - i));
+		}
+		/* set codec SRC on off */
+		pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE);
+		rmh.cmd_len = 2;
+		rmh.cmd[0] |= IO_NUM_REG_CONFIG_SRC;
+		rmh.cmd[1] = src_config;
+		err = pcxhr_send_msg(chip->mgr, &rmh);
+	} else {
+		int use_src = 0;
+		if (chip->audio_capture_source == 2)
+			use_src = 1;
+		/* set codec SRC on off */
+		pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE);
+		rmh.cmd_len = 3;
+		rmh.cmd[0] |= IO_NUM_UER_CHIP_REG;
+		rmh.cmd[1] = codec;
+		rmh.cmd[2] = ((CS8420_DATA_FLOW_CTL & CHIP_SIG_AND_MAP_SPI) |
+			      (use_src ? 0x41 : 0x54));
+		err = pcxhr_send_msg(chip->mgr, &rmh);
+		if (err)
+			return err;
+		rmh.cmd[2] = ((CS8420_CLOCK_SRC_CTL & CHIP_SIG_AND_MAP_SPI) |
+			      (use_src ? 0x41 : 0x49));
+		err = pcxhr_send_msg(chip->mgr, &rmh);
+	}
+	return err;
+}
+
+static int pcxhr_audio_src_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	static const char *texts[5] = {
+		"Line", "Digital", "Digi+SRC", "Mic", "Line+Mic"
+	};
+	int i;
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+
+	i = 2;			/* no SRC, no Mic available */
+	if (chip->mgr->board_has_aes1) {
+		i = 3;		/* SRC available */
+		if (chip->mgr->board_has_mic)
+			i = 5;	/* Mic and MicroMix available */
+	}
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = i;
+	if (uinfo->value.enumerated.item > (i-1))
+		uinfo->value.enumerated.item = i-1;
+	strcpy(uinfo->value.enumerated.name,
+		texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int pcxhr_audio_src_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = chip->audio_capture_source;
+	return 0;
+}
+
+static int pcxhr_audio_src_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int ret = 0;
+	int i = 2;		/* no SRC, no Mic available */
+	if (chip->mgr->board_has_aes1) {
+		i = 3;		/* SRC available */
+		if (chip->mgr->board_has_mic)
+			i = 5;	/* Mic and MicroMix available */
+	}
+	if (ucontrol->value.enumerated.item[0] >= i)
+		return -EINVAL;
+	mutex_lock(&chip->mgr->mixer_mutex);
+	if (chip->audio_capture_source != ucontrol->value.enumerated.item[0]) {
+		chip->audio_capture_source = ucontrol->value.enumerated.item[0];
+		if (chip->mgr->is_hr_stereo)
+			hr222_set_audio_source(chip);
+		else
+			pcxhr_set_audio_source(chip);
+		ret = 1;
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return ret;
+}
+
+static struct snd_kcontrol_new pcxhr_control_audio_src = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Capture Source",
+	.info =		pcxhr_audio_src_info,
+	.get =		pcxhr_audio_src_get,
+	.put =		pcxhr_audio_src_put,
+};
+
+
+/*
+ * clock type selection
+ * enum pcxhr_clock_type {
+ *	PCXHR_CLOCK_TYPE_INTERNAL = 0,
+ *	PCXHR_CLOCK_TYPE_WORD_CLOCK,
+ *	PCXHR_CLOCK_TYPE_AES_SYNC,
+ *	PCXHR_CLOCK_TYPE_AES_1,
+ *	PCXHR_CLOCK_TYPE_AES_2,
+ *	PCXHR_CLOCK_TYPE_AES_3,
+ *	PCXHR_CLOCK_TYPE_AES_4,
+ *	PCXHR_CLOCK_TYPE_MAX = PCXHR_CLOCK_TYPE_AES_4,
+ *	HR22_CLOCK_TYPE_INTERNAL = PCXHR_CLOCK_TYPE_INTERNAL,
+ *	HR22_CLOCK_TYPE_AES_SYNC,
+ *	HR22_CLOCK_TYPE_AES_1,
+ *	HR22_CLOCK_TYPE_MAX = HR22_CLOCK_TYPE_AES_1,
+ * };
+ */
+
+static int pcxhr_clock_type_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	static const char *textsPCXHR[7] = {
+		"Internal", "WordClock", "AES Sync",
+		"AES 1", "AES 2", "AES 3", "AES 4"
+	};
+	static const char *textsHR22[3] = {
+		"Internal", "AES Sync", "AES 1"
+	};
+	const char **texts;
+	struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol);
+	int clock_items = 2;	/* at least Internal and AES Sync clock */
+	if (mgr->board_has_aes1) {
+		clock_items += mgr->capture_chips;	/* add AES x */
+		if (!mgr->is_hr_stereo)
+			clock_items += 1;		/* add word clock */
+	}
+	if (mgr->is_hr_stereo) {
+		texts = textsHR22;
+		snd_BUG_ON(clock_items > (HR22_CLOCK_TYPE_MAX+1));
+	} else {
+		texts = textsPCXHR;
+		snd_BUG_ON(clock_items > (PCXHR_CLOCK_TYPE_MAX+1));
+	}
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = clock_items;
+	if (uinfo->value.enumerated.item >= clock_items)
+		uinfo->value.enumerated.item = clock_items-1;
+	strcpy(uinfo->value.enumerated.name,
+		texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int pcxhr_clock_type_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.enumerated.item[0] = mgr->use_clock_type;
+	return 0;
+}
+
+static int pcxhr_clock_type_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol);
+	int rate, ret = 0;
+	unsigned int clock_items = 2; /* at least Internal and AES Sync clock */
+	if (mgr->board_has_aes1) {
+		clock_items += mgr->capture_chips;	/* add AES x */
+		if (!mgr->is_hr_stereo)
+			clock_items += 1;		/* add word clock */
+	}
+	if (ucontrol->value.enumerated.item[0] >= clock_items)
+		return -EINVAL;
+	mutex_lock(&mgr->mixer_mutex);
+	if (mgr->use_clock_type != ucontrol->value.enumerated.item[0]) {
+		mutex_lock(&mgr->setup_mutex);
+		mgr->use_clock_type = ucontrol->value.enumerated.item[0];
+		rate = 0;
+		if (mgr->use_clock_type != PCXHR_CLOCK_TYPE_INTERNAL) {
+			pcxhr_get_external_clock(mgr, mgr->use_clock_type,
+						 &rate);
+		} else {
+			rate = mgr->sample_rate;
+			if (!rate)
+				rate = 48000;
+		}
+		if (rate) {
+			pcxhr_set_clock(mgr, rate);
+			if (mgr->sample_rate)
+				mgr->sample_rate = rate;
+		}
+		mutex_unlock(&mgr->setup_mutex);
+		ret = 1; /* return 1 even if the set was not done. ok ? */
+	}
+	mutex_unlock(&mgr->mixer_mutex);
+	return ret;
+}
+
+static struct snd_kcontrol_new pcxhr_control_clock_type = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Clock Mode",
+	.info =		pcxhr_clock_type_info,
+	.get =		pcxhr_clock_type_get,
+	.put =		pcxhr_clock_type_put,
+};
+
+/*
+ * clock rate control
+ * specific control that scans the sample rates on the external plugs
+ */
+static int pcxhr_clock_rate_info(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 3 + mgr->capture_chips;
+	uinfo->value.integer.min = 0;		/* clock not present */
+	uinfo->value.integer.max = 192000;	/* max sample rate 192 kHz */
+	return 0;
+}
+
+static int pcxhr_clock_rate_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct pcxhr_mgr *mgr = snd_kcontrol_chip(kcontrol);
+	int i, err, rate;
+
+	mutex_lock(&mgr->mixer_mutex);
+	for(i = 0; i < 3 + mgr->capture_chips; i++) {
+		if (i == PCXHR_CLOCK_TYPE_INTERNAL)
+			rate = mgr->sample_rate_real;
+		else {
+			err = pcxhr_get_external_clock(mgr, i, &rate);
+			if (err)
+				break;
+		}
+		ucontrol->value.integer.value[i] = rate;
+	}
+	mutex_unlock(&mgr->mixer_mutex);
+	return 0;
+}
+
+static struct snd_kcontrol_new pcxhr_control_clock_rate = {
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_CARD,
+	.name =		"Clock Rates",
+	.info =		pcxhr_clock_rate_info,
+	.get =		pcxhr_clock_rate_get,
+};
+
+/*
+ * IEC958 status bits
+ */
+static int pcxhr_iec958_info(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int pcxhr_iec958_capture_byte(struct snd_pcxhr *chip,
+				     int aes_idx, unsigned char *aes_bits)
+{
+	int i, err;
+	unsigned char temp;
+	struct pcxhr_rmh rmh;
+
+	pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ);
+	rmh.cmd[0] |= IO_NUM_UER_CHIP_REG;
+	switch (chip->chip_idx) {
+	  /* instead of CS8420_01_CS use CS8416_01_CS for AES SYNC plug */
+	case 0:	rmh.cmd[1] = CS8420_01_CS; break;
+	case 1:	rmh.cmd[1] = CS8420_23_CS; break;
+	case 2:	rmh.cmd[1] = CS8420_45_CS; break;
+	case 3:	rmh.cmd[1] = CS8420_67_CS; break;
+	default: return -EINVAL;
+	}
+	if (chip->mgr->board_aes_in_192k) {
+		switch (aes_idx) {
+		case 0:	rmh.cmd[2] = CS8416_CSB0; break;
+		case 1:	rmh.cmd[2] = CS8416_CSB1; break;
+		case 2:	rmh.cmd[2] = CS8416_CSB2; break;
+		case 3:	rmh.cmd[2] = CS8416_CSB3; break;
+		case 4:	rmh.cmd[2] = CS8416_CSB4; break;
+		default: return -EINVAL;
+		}
+	} else {
+		switch (aes_idx) {
+		  /* instead of CS8420_CSB0 use CS8416_CSBx for AES SYNC plug */
+		case 0:	rmh.cmd[2] = CS8420_CSB0; break;
+		case 1:	rmh.cmd[2] = CS8420_CSB1; break;
+		case 2:	rmh.cmd[2] = CS8420_CSB2; break;
+		case 3:	rmh.cmd[2] = CS8420_CSB3; break;
+		case 4:	rmh.cmd[2] = CS8420_CSB4; break;
+		default: return -EINVAL;
+		}
+	}
+	/* size and code the chip id for the fpga */
+	rmh.cmd[1] &= 0x0fffff;
+	/* chip signature + map for spi read */
+	rmh.cmd[2] &= CHIP_SIG_AND_MAP_SPI;
+	rmh.cmd_len = 3;
+	err = pcxhr_send_msg(chip->mgr, &rmh);
+	if (err)
+		return err;
+
+	if (chip->mgr->board_aes_in_192k) {
+		temp = (unsigned char)rmh.stat[1];
+	} else {
+		temp = 0;
+		/* reversed bit order (not with CS8416_01_CS) */
+		for (i = 0; i < 8; i++) {
+			temp <<= 1;
+			if (rmh.stat[1] & (1 << i))
+				temp |= 1;
+		}
+	}
+	snd_printdd("read iec958 AES %d byte %d = 0x%x\n",
+		    chip->chip_idx, aes_idx, temp);
+	*aes_bits = temp;
+	return 0;
+}
+
+static int pcxhr_iec958_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	unsigned char aes_bits;
+	int i, err;
+
+	mutex_lock(&chip->mgr->mixer_mutex);
+	for(i = 0; i < 5; i++) {
+		if (kcontrol->private_value == 0)	/* playback */
+			aes_bits = chip->aes_bits[i];
+		else {				/* capture */
+			if (chip->mgr->is_hr_stereo)
+				err = hr222_iec958_capture_byte(chip, i,
+								&aes_bits);
+			else
+				err = pcxhr_iec958_capture_byte(chip, i,
+								&aes_bits);
+			if (err)
+				break;
+		}
+		ucontrol->value.iec958.status[i] = aes_bits;
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+        return 0;
+}
+
+static int pcxhr_iec958_mask_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	int i;
+	for (i = 0; i < 5; i++)
+		ucontrol->value.iec958.status[i] = 0xff;
+        return 0;
+}
+
+static int pcxhr_iec958_update_byte(struct snd_pcxhr *chip,
+				    int aes_idx, unsigned char aes_bits)
+{
+	int i, err, cmd;
+	unsigned char new_bits = aes_bits;
+	unsigned char old_bits = chip->aes_bits[aes_idx];
+	struct pcxhr_rmh rmh;
+
+	for (i = 0; i < 8; i++) {
+		if ((old_bits & 0x01) != (new_bits & 0x01)) {
+			cmd = chip->chip_idx & 0x03;      /* chip index 0..3 */
+			if (chip->chip_idx > 3)
+				/* new bit used if chip_idx>3 (PCX1222HR) */
+				cmd |= 1 << 22;
+			cmd |= ((aes_idx << 3) + i) << 2; /* add bit offset */
+			cmd |= (new_bits & 0x01) << 23;   /* add bit value */
+			pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE);
+			rmh.cmd[0] |= IO_NUM_REG_CUER;
+			rmh.cmd[1] = cmd;
+			rmh.cmd_len = 2;
+			snd_printdd("write iec958 AES %d byte %d bit %d (cmd %x)\n",
+				    chip->chip_idx, aes_idx, i, cmd);
+			err = pcxhr_send_msg(chip->mgr, &rmh);
+			if (err)
+				return err;
+		}
+		old_bits >>= 1;
+		new_bits >>= 1;
+	}
+	chip->aes_bits[aes_idx] = aes_bits;
+	return 0;
+}
+
+static int pcxhr_iec958_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol);
+	int i, changed = 0;
+
+	/* playback */
+	mutex_lock(&chip->mgr->mixer_mutex);
+	for (i = 0; i < 5; i++) {
+		if (ucontrol->value.iec958.status[i] != chip->aes_bits[i]) {
+			if (chip->mgr->is_hr_stereo)
+				hr222_iec958_update_byte(chip, i,
+					ucontrol->value.iec958.status[i]);
+			else
+				pcxhr_iec958_update_byte(chip, i,
+					ucontrol->value.iec958.status[i]);
+			changed = 1;
+		}
+	}
+	mutex_unlock(&chip->mgr->mixer_mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = {
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+	.info =		pcxhr_iec958_info,
+	.get =		pcxhr_iec958_mask_get
+};
+static struct snd_kcontrol_new pcxhr_control_playback_iec958 = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =         pcxhr_iec958_info,
+	.get =          pcxhr_iec958_get,
+	.put =          pcxhr_iec958_put,
+	.private_value = 0 /* playback */
+};
+
+static struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = {
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK),
+	.info =		pcxhr_iec958_info,
+	.get =		pcxhr_iec958_mask_get
+};
+static struct snd_kcontrol_new pcxhr_control_capture_iec958 = {
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT),
+	.info =         pcxhr_iec958_info,
+	.get =          pcxhr_iec958_get,
+	.private_value = 1 /* capture */
+};
+
+static void pcxhr_init_audio_levels(struct snd_pcxhr *chip)
+{
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		if (chip->nb_streams_play) {
+			int j;
+			/* at boot time the digital volumes are unmuted 0dB */
+			for (j = 0; j < PCXHR_PLAYBACK_STREAMS; j++) {
+				chip->digital_playback_active[j][i] = 1;
+				chip->digital_playback_volume[j][i] =
+					PCXHR_DIGITAL_ZERO_LEVEL;
+			}
+			/* after boot, only two bits are set on the uer
+			 * interface
+			 */
+			chip->aes_bits[0] = (IEC958_AES0_PROFESSIONAL |
+					     IEC958_AES0_PRO_FS_48000);
+#ifdef CONFIG_SND_DEBUG
+			/* analog volumes for playback
+			 * (is LEVEL_MIN after boot)
+			 */
+			chip->analog_playback_active[i] = 1;
+			if (chip->mgr->is_hr_stereo)
+				chip->analog_playback_volume[i] =
+					HR222_LINE_PLAYBACK_ZERO_LEVEL;
+			else {
+				chip->analog_playback_volume[i] =
+					PCXHR_LINE_PLAYBACK_ZERO_LEVEL;
+				pcxhr_update_analog_audio_level(chip, 0, i);
+			}
+#endif
+			/* stereo cards need to be initialised after boot */
+			if (chip->mgr->is_hr_stereo)
+				hr222_update_analog_audio_level(chip, 0, i);
+		}
+		if (chip->nb_streams_capt) {
+			/* at boot time the digital volumes are unmuted 0dB */
+			chip->digital_capture_volume[i] =
+				PCXHR_DIGITAL_ZERO_LEVEL;
+			chip->analog_capture_active = 1;
+#ifdef CONFIG_SND_DEBUG
+			/* analog volumes for playback
+			 * (is LEVEL_MIN after boot)
+			 */
+			if (chip->mgr->is_hr_stereo)
+				chip->analog_capture_volume[i] =
+					HR222_LINE_CAPTURE_ZERO_LEVEL;
+			else {
+				chip->analog_capture_volume[i] =
+					PCXHR_LINE_CAPTURE_ZERO_LEVEL;
+				pcxhr_update_analog_audio_level(chip, 1, i);
+			}
+#endif
+			/* stereo cards need to be initialised after boot */
+			if (chip->mgr->is_hr_stereo)
+				hr222_update_analog_audio_level(chip, 1, i);
+		}
+	}
+
+	return;
+}
+
+
+int pcxhr_create_mixer(struct pcxhr_mgr *mgr)
+{
+	struct snd_pcxhr *chip;
+	int err, i;
+
+	mutex_init(&mgr->mixer_mutex); /* can be in another place */
+
+	for (i = 0; i < mgr->num_cards; i++) {
+		struct snd_kcontrol_new temp;
+		chip = mgr->chip[i];
+
+		if (chip->nb_streams_play) {
+			/* analog output level control */
+			temp = pcxhr_control_analog_level;
+			temp.name = "Master Playback Volume";
+			temp.private_value = 0; /* playback */
+			if (mgr->is_hr_stereo)
+				temp.tlv.p = db_scale_a_hr222_playback;
+			else
+				temp.tlv.p = db_scale_analog_playback;
+			err = snd_ctl_add(chip->card,
+					  snd_ctl_new1(&temp, chip));
+			if (err < 0)
+				return err;
+
+			/* output mute controls */
+			err = snd_ctl_add(chip->card,
+				snd_ctl_new1(&pcxhr_control_output_switch,
+					     chip));
+			if (err < 0)
+				return err;
+
+			temp = snd_pcxhr_pcm_vol;
+			temp.name = "PCM Playback Volume";
+			temp.count = PCXHR_PLAYBACK_STREAMS;
+			temp.private_value = 0; /* playback */
+			err = snd_ctl_add(chip->card,
+					  snd_ctl_new1(&temp, chip));
+			if (err < 0)
+				return err;
+
+			err = snd_ctl_add(chip->card,
+				snd_ctl_new1(&pcxhr_control_pcm_switch, chip));
+			if (err < 0)
+				return err;
+
+			/* IEC958 controls */
+			err = snd_ctl_add(chip->card,
+				snd_ctl_new1(&pcxhr_control_playback_iec958_mask,
+					     chip));
+			if (err < 0)
+				return err;
+
+			err = snd_ctl_add(chip->card,
+				snd_ctl_new1(&pcxhr_control_playback_iec958,
+					     chip));
+			if (err < 0)
+				return err;
+		}
+		if (chip->nb_streams_capt) {
+			/* analog input level control */
+			temp = pcxhr_control_analog_level;
+			temp.name = "Line Capture Volume";
+			temp.private_value = 1; /* capture */
+			if (mgr->is_hr_stereo)
+				temp.tlv.p = db_scale_a_hr222_capture;
+			else
+				temp.tlv.p = db_scale_analog_capture;
+
+			err = snd_ctl_add(chip->card,
+					  snd_ctl_new1(&temp, chip));
+			if (err < 0)
+				return err;
+
+			temp = snd_pcxhr_pcm_vol;
+			temp.name = "PCM Capture Volume";
+			temp.count = 1;
+			temp.private_value = 1; /* capture */
+
+			err = snd_ctl_add(chip->card,
+					  snd_ctl_new1(&temp, chip));
+			if (err < 0)
+				return err;
+
+			/* Audio source */
+			err = snd_ctl_add(chip->card,
+				snd_ctl_new1(&pcxhr_control_audio_src, chip));
+			if (err < 0)
+				return err;
+
+			/* IEC958 controls */
+			err = snd_ctl_add(chip->card,
+				snd_ctl_new1(&pcxhr_control_capture_iec958_mask,
+					     chip));
+			if (err < 0)
+				return err;
+
+			err = snd_ctl_add(chip->card,
+				snd_ctl_new1(&pcxhr_control_capture_iec958,
+					     chip));
+			if (err < 0)
+				return err;
+
+			if (mgr->is_hr_stereo) {
+				err = hr222_add_mic_controls(chip);
+				if (err < 0)
+					return err;
+			}
+		}
+		/* monitoring only if playback and capture device available */
+		if (chip->nb_streams_capt > 0 && chip->nb_streams_play > 0) {
+			/* monitoring */
+			err = snd_ctl_add(chip->card,
+				snd_ctl_new1(&pcxhr_control_monitor_vol, chip));
+			if (err < 0)
+				return err;
+
+			err = snd_ctl_add(chip->card,
+				snd_ctl_new1(&pcxhr_control_monitor_sw, chip));
+			if (err < 0)
+				return err;
+		}
+
+		if (i == 0) {
+			/* clock mode only one control per pcxhr */
+			err = snd_ctl_add(chip->card,
+				snd_ctl_new1(&pcxhr_control_clock_type, mgr));
+			if (err < 0)
+				return err;
+			/* non standard control used to scan
+			 * the external clock presence/frequencies
+			 */
+			err = snd_ctl_add(chip->card,
+				snd_ctl_new1(&pcxhr_control_clock_rate, mgr));
+			if (err < 0)
+				return err;
+		}
+
+		/* init values for the mixer data */
+		pcxhr_init_audio_levels(chip);
+	}
+
+	return 0;
+}
diff --git a/linux/sound/pci/pcxhr/pcxhr_mixer.h b/linux/sound/pci/pcxhr/pcxhr_mixer.h
new file mode 100644
index 0000000..4348d0e
--- /dev/null
+++ b/linux/sound/pci/pcxhr/pcxhr_mixer.h
@@ -0,0 +1,29 @@
+/*
+ * Driver for Digigram pcxhr compatible soundcards
+ *
+ * include file for mixer
+ *
+ * Copyright (c) 2004 by Digigram <alsa@digigram.com>
+ *
+ *   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 __SOUND_PCXHR_MIXER_H
+#define __SOUND_PCXHR_MIXER_H
+
+/* exported */
+int pcxhr_create_mixer(struct pcxhr_mgr *mgr);
+
+#endif /* __SOUND_PCXHR_MIXER_H */
diff --git a/linux/sound/pci/riptide/Makefile b/linux/sound/pci/riptide/Makefile
new file mode 100644
index 0000000..dcd2e64
--- /dev/null
+++ b/linux/sound/pci/riptide/Makefile
@@ -0,0 +1,3 @@
+snd-riptide-objs := riptide.o
+
+obj-$(CONFIG_SND_RIPTIDE) += snd-riptide.o
diff --git a/linux/sound/pci/riptide/riptide.c b/linux/sound/pci/riptide/riptide.c
new file mode 100644
index 0000000..ad5202e
--- /dev/null
+++ b/linux/sound/pci/riptide/riptide.c
@@ -0,0 +1,2222 @@
+/*
+ *   Driver for the Conexant Riptide Soundchip
+ *
+ *	Copyright (c) 2004 Peter Gruber <nokos@gmx.net>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+/*
+  History:
+   - 02/15/2004 first release
+   
+  This Driver is based on the OSS Driver version from Linuxant (riptide-0.6lnxtbeta03111100)
+  credits from the original files:
+  
+  MODULE NAME:        cnxt_rt.h                       
+  AUTHOR:             K. Lazarev  (Transcribed by KNL)
+  HISTORY:         Major Revision               Date        By
+            -----------------------------     --------     -----
+            Created                           02/1/2000     KNL
+
+  MODULE NAME:     int_mdl.c                       
+  AUTHOR:          Konstantin Lazarev    (Transcribed by KNL)
+  HISTORY:         Major Revision               Date        By
+            -----------------------------     --------     -----
+            Created                           10/01/99      KNL
+	    
+  MODULE NAME:        riptide.h                       
+  AUTHOR:             O. Druzhinin  (Transcribed by OLD)
+  HISTORY:         Major Revision               Date        By
+            -----------------------------     --------     -----
+            Created                           10/16/97      OLD
+
+  MODULE NAME:        Rp_Cmdif.cpp                       
+  AUTHOR:             O. Druzhinin  (Transcribed by OLD)
+                      K. Lazarev    (Transcribed by KNL)
+  HISTORY:         Major Revision               Date        By
+            -----------------------------     --------     -----
+            Adopted from NT4 driver            6/22/99      OLD
+            Ported to Linux                    9/01/99      KNL
+
+  MODULE NAME:        rt_hw.c                       
+  AUTHOR:             O. Druzhinin  (Transcribed by OLD)
+                      C. Lazarev    (Transcribed by CNL)
+  HISTORY:         Major Revision               Date        By
+            -----------------------------     --------     -----
+            Created                           11/18/97      OLD
+            Hardware functions for RipTide    11/24/97      CNL
+            (ES1) are coded
+            Hardware functions for RipTide    12/24/97      CNL
+            (A0) are coded
+            Hardware functions for RipTide    03/20/98      CNL
+            (A1) are coded
+            Boot loader is included           05/07/98      CNL
+            Redesigned for WDM                07/27/98      CNL
+            Redesigned for Linux              09/01/99      CNL
+
+  MODULE NAME:        rt_hw.h
+  AUTHOR:             C. Lazarev    (Transcribed by CNL)
+  HISTORY:         Major Revision               Date        By
+            -----------------------------     --------     -----
+            Created                           11/18/97      CNL
+
+  MODULE NAME:     rt_mdl.c                       
+  AUTHOR:          Konstantin Lazarev    (Transcribed by KNL)
+  HISTORY:         Major Revision               Date        By
+            -----------------------------     --------     -----
+            Created                           10/01/99      KNL
+
+  MODULE NAME:        mixer.h                        
+  AUTHOR:             K. Kenney
+  HISTORY:         Major Revision                   Date          By
+            -----------------------------          --------     -----
+            Created from MS W95 Sample             11/28/95      KRS
+            RipTide                                10/15/97      KRS
+            Adopted for Windows NT driver          01/20/98      CNL
+*/
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/gameport.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/kernel.h>
+#include <asm/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/ac97_codec.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+#define SUPPORT_JOYSTICK 1
+#endif
+
+MODULE_AUTHOR("Peter Gruber <nokos@gmx.net>");
+MODULE_DESCRIPTION("riptide");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Conexant,Riptide}}");
+MODULE_FIRMWARE("riptide.hex");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
+
+#ifdef SUPPORT_JOYSTICK
+static int joystick_port[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS - 1)] = 0x200 };
+#endif
+static int mpu_port[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS - 1)] = 0x330 };
+static int opl3_port[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS - 1)] = 0x388 };
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Riptide soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Riptide soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Riptide soundcard.");
+#ifdef SUPPORT_JOYSTICK
+module_param_array(joystick_port, int, NULL, 0444);
+MODULE_PARM_DESC(joystick_port, "Joystick port # for Riptide soundcard.");
+#endif
+module_param_array(mpu_port, int, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU401 port # for Riptide driver.");
+module_param_array(opl3_port, int, NULL, 0444);
+MODULE_PARM_DESC(opl3_port, "OPL3 port # for Riptide driver.");
+
+/*
+ */
+
+#define MPU401_HW_RIPTIDE MPU401_HW_MPU401
+#define OPL3_HW_RIPTIDE   OPL3_HW_OPL3
+
+#define PCI_EXT_CapId       0x40
+#define PCI_EXT_NextCapPrt  0x41
+#define PCI_EXT_PWMC        0x42
+#define PCI_EXT_PWSCR       0x44
+#define PCI_EXT_Data00      0x46
+#define PCI_EXT_PMSCR_BSE   0x47
+#define PCI_EXT_SB_Base     0x48
+#define PCI_EXT_FM_Base     0x4a
+#define PCI_EXT_MPU_Base    0x4C
+#define PCI_EXT_Game_Base   0x4E
+#define PCI_EXT_Legacy_Mask 0x50
+#define PCI_EXT_AsicRev     0x52
+#define PCI_EXT_Reserved3   0x53
+
+#define LEGACY_ENABLE_ALL      0x8000	/* legacy device options */
+#define LEGACY_ENABLE_SB       0x4000
+#define LEGACY_ENABLE_FM       0x2000
+#define LEGACY_ENABLE_MPU_INT  0x1000
+#define LEGACY_ENABLE_MPU      0x0800
+#define LEGACY_ENABLE_GAMEPORT 0x0400
+
+#define MAX_WRITE_RETRY  10	/* cmd interface limits */
+#define MAX_ERROR_COUNT  10
+#define CMDIF_TIMEOUT    50000
+#define RESET_TRIES      5
+
+#define READ_PORT_ULONG(p)     inl((unsigned long)&(p))
+#define WRITE_PORT_ULONG(p,x)  outl(x,(unsigned long)&(p))
+
+#define READ_AUDIO_CONTROL(p)     READ_PORT_ULONG(p->audio_control)
+#define WRITE_AUDIO_CONTROL(p,x)  WRITE_PORT_ULONG(p->audio_control,x)
+#define UMASK_AUDIO_CONTROL(p,x)  WRITE_PORT_ULONG(p->audio_control,READ_PORT_ULONG(p->audio_control)|x)
+#define MASK_AUDIO_CONTROL(p,x)   WRITE_PORT_ULONG(p->audio_control,READ_PORT_ULONG(p->audio_control)&x)
+#define READ_AUDIO_STATUS(p)      READ_PORT_ULONG(p->audio_status)
+
+#define SET_GRESET(p)     UMASK_AUDIO_CONTROL(p,0x0001)	/* global reset switch */
+#define UNSET_GRESET(p)   MASK_AUDIO_CONTROL(p,~0x0001)
+#define SET_AIE(p)        UMASK_AUDIO_CONTROL(p,0x0004)	/* interrupt enable */
+#define UNSET_AIE(p)      MASK_AUDIO_CONTROL(p,~0x0004)
+#define SET_AIACK(p)      UMASK_AUDIO_CONTROL(p,0x0008)	/* interrupt acknowledge */
+#define UNSET_AIACKT(p)   MASKAUDIO_CONTROL(p,~0x0008)
+#define SET_ECMDAE(p)     UMASK_AUDIO_CONTROL(p,0x0010)
+#define UNSET_ECMDAE(p)   MASK_AUDIO_CONTROL(p,~0x0010)
+#define SET_ECMDBE(p)     UMASK_AUDIO_CONTROL(p,0x0020)
+#define UNSET_ECMDBE(p)   MASK_AUDIO_CONTROL(p,~0x0020)
+#define SET_EDATAF(p)     UMASK_AUDIO_CONTROL(p,0x0040)
+#define UNSET_EDATAF(p)   MASK_AUDIO_CONTROL(p,~0x0040)
+#define SET_EDATBF(p)     UMASK_AUDIO_CONTROL(p,0x0080)
+#define UNSET_EDATBF(p)   MASK_AUDIO_CONTROL(p,~0x0080)
+#define SET_ESBIRQON(p)   UMASK_AUDIO_CONTROL(p,0x0100)
+#define UNSET_ESBIRQON(p) MASK_AUDIO_CONTROL(p,~0x0100)
+#define SET_EMPUIRQ(p)    UMASK_AUDIO_CONTROL(p,0x0200)
+#define UNSET_EMPUIRQ(p)  MASK_AUDIO_CONTROL(p,~0x0200)
+#define IS_CMDE(a)        (READ_PORT_ULONG(a->stat)&0x1)	/* cmd empty */
+#define IS_DATF(a)        (READ_PORT_ULONG(a->stat)&0x2)	/* data filled */
+#define IS_READY(p)       (READ_AUDIO_STATUS(p)&0x0001)
+#define IS_DLREADY(p)     (READ_AUDIO_STATUS(p)&0x0002)
+#define IS_DLERR(p)       (READ_AUDIO_STATUS(p)&0x0004)
+#define IS_GERR(p)        (READ_AUDIO_STATUS(p)&0x0008)	/* error ! */
+#define IS_CMDAEIRQ(p)    (READ_AUDIO_STATUS(p)&0x0010)
+#define IS_CMDBEIRQ(p)    (READ_AUDIO_STATUS(p)&0x0020)
+#define IS_DATAFIRQ(p)    (READ_AUDIO_STATUS(p)&0x0040)
+#define IS_DATBFIRQ(p)    (READ_AUDIO_STATUS(p)&0x0080)
+#define IS_EOBIRQ(p)      (READ_AUDIO_STATUS(p)&0x0100)	/* interrupt status */
+#define IS_EOSIRQ(p)      (READ_AUDIO_STATUS(p)&0x0200)
+#define IS_EOCIRQ(p)      (READ_AUDIO_STATUS(p)&0x0400)
+#define IS_UNSLIRQ(p)     (READ_AUDIO_STATUS(p)&0x0800)
+#define IS_SBIRQ(p)       (READ_AUDIO_STATUS(p)&0x1000)
+#define IS_MPUIRQ(p)      (READ_AUDIO_STATUS(p)&0x2000)
+
+#define RESP 0x00000001		/* command flags */
+#define PARM 0x00000002
+#define CMDA 0x00000004
+#define CMDB 0x00000008
+#define NILL 0x00000000
+
+#define LONG0(a)   ((u32)a)	/* shifts and masks */
+#define BYTE0(a)   (LONG0(a)&0xff)
+#define BYTE1(a)   (BYTE0(a)<<8)
+#define BYTE2(a)   (BYTE0(a)<<16)
+#define BYTE3(a)   (BYTE0(a)<<24)
+#define WORD0(a)   (LONG0(a)&0xffff)
+#define WORD1(a)   (WORD0(a)<<8)
+#define WORD2(a)   (WORD0(a)<<16)
+#define TRINIB0(a) (LONG0(a)&0xffffff)
+#define TRINIB1(a) (TRINIB0(a)<<8)
+
+#define RET(a)     ((union cmdret *)(a))
+
+#define SEND_GETV(p,b)             sendcmd(p,RESP,GETV,0,RET(b))	/* get version */
+#define SEND_GETC(p,b,c)           sendcmd(p,PARM|RESP,GETC,c,RET(b))
+#define SEND_GUNS(p,b)             sendcmd(p,RESP,GUNS,0,RET(b))
+#define SEND_SCID(p,b)             sendcmd(p,RESP,SCID,0,RET(b))
+#define SEND_RMEM(p,b,c,d)         sendcmd(p,PARM|RESP,RMEM|BYTE1(b),LONG0(c),RET(d))	/* memory access for firmware write */
+#define SEND_SMEM(p,b,c)           sendcmd(p,PARM,SMEM|BYTE1(b),LONG0(c),RET(0))	/* memory access for firmware write */
+#define SEND_WMEM(p,b,c)           sendcmd(p,PARM,WMEM|BYTE1(b),LONG0(c),RET(0))	/* memory access for firmware write */
+#define SEND_SDTM(p,b,c)           sendcmd(p,PARM|RESP,SDTM|TRINIB1(b),0,RET(c))	/* memory access for firmware write */
+#define SEND_GOTO(p,b)             sendcmd(p,PARM,GOTO,LONG0(b),RET(0))	/* memory access for firmware write */
+#define SEND_SETDPLL(p)	           sendcmd(p,0,ARM_SETDPLL,0,RET(0))
+#define SEND_SSTR(p,b,c)           sendcmd(p,PARM,SSTR|BYTE3(b),LONG0(c),RET(0))	/* start stream */
+#define SEND_PSTR(p,b)             sendcmd(p,PARM,PSTR,BYTE3(b),RET(0))	/* pause stream */
+#define SEND_KSTR(p,b)             sendcmd(p,PARM,KSTR,BYTE3(b),RET(0))	/* stop stream */
+#define SEND_KDMA(p)               sendcmd(p,0,KDMA,0,RET(0))	/* stop all dma */
+#define SEND_GPOS(p,b,c,d)         sendcmd(p,PARM|RESP,GPOS,BYTE3(c)|BYTE2(b),RET(d))	/* get position in dma */
+#define SEND_SETF(p,b,c,d,e,f,g)   sendcmd(p,PARM,SETF|WORD1(b)|BYTE3(c),d|BYTE1(e)|BYTE2(f)|BYTE3(g),RET(0))	/* set sample format at mixer */
+#define SEND_GSTS(p,b,c,d)         sendcmd(p,PARM|RESP,GSTS,BYTE3(c)|BYTE2(b),RET(d))
+#define SEND_NGPOS(p,b,c,d)        sendcmd(p,PARM|RESP,NGPOS,BYTE3(c)|BYTE2(b),RET(d))
+#define SEND_PSEL(p,b,c)           sendcmd(p,PARM,PSEL,BYTE2(b)|BYTE3(c),RET(0))	/* activate lbus path */
+#define SEND_PCLR(p,b,c)           sendcmd(p,PARM,PCLR,BYTE2(b)|BYTE3(c),RET(0))	/* deactivate lbus path */
+#define SEND_PLST(p,b)             sendcmd(p,PARM,PLST,BYTE3(b),RET(0))
+#define SEND_RSSV(p,b,c,d)         sendcmd(p,PARM|RESP,RSSV,BYTE2(b)|BYTE3(c),RET(d))
+#define SEND_LSEL(p,b,c,d,e,f,g,h) sendcmd(p,PARM,LSEL|BYTE1(b)|BYTE2(c)|BYTE3(d),BYTE0(e)|BYTE1(f)|BYTE2(g)|BYTE3(h),RET(0))	/* select paths for internal connections */
+#define SEND_SSRC(p,b,c,d,e)       sendcmd(p,PARM,SSRC|BYTE1(b)|WORD2(c),WORD0(d)|WORD2(e),RET(0))	/* configure source */
+#define SEND_SLST(p,b)             sendcmd(p,PARM,SLST,BYTE3(b),RET(0))
+#define SEND_RSRC(p,b,c)           sendcmd(p,RESP,RSRC|BYTE1(b),0,RET(c))	/* read source config */
+#define SEND_SSRB(p,b,c)           sendcmd(p,PARM,SSRB|BYTE1(b),WORD2(c),RET(0))
+#define SEND_SDGV(p,b,c,d,e)       sendcmd(p,PARM,SDGV|BYTE2(b)|BYTE3(c),WORD0(d)|WORD2(e),RET(0))	/* set digital mixer */
+#define SEND_RDGV(p,b,c,d)         sendcmd(p,PARM|RESP,RDGV|BYTE2(b)|BYTE3(c),0,RET(d))	/* read digital mixer */
+#define SEND_DLST(p,b)             sendcmd(p,PARM,DLST,BYTE3(b),RET(0))
+#define SEND_SACR(p,b,c)           sendcmd(p,PARM,SACR,WORD0(b)|WORD2(c),RET(0))	/* set AC97 register */
+#define SEND_RACR(p,b,c)           sendcmd(p,PARM|RESP,RACR,WORD2(b),RET(c))	/* get AC97 register */
+#define SEND_ALST(p,b)             sendcmd(p,PARM,ALST,BYTE3(b),RET(0))
+#define SEND_TXAC(p,b,c,d,e,f)     sendcmd(p,PARM,TXAC|BYTE1(b)|WORD2(c),WORD0(d)|BYTE2(e)|BYTE3(f),RET(0))
+#define SEND_RXAC(p,b,c,d)         sendcmd(p,PARM|RESP,RXAC,BYTE2(b)|BYTE3(c),RET(d))
+#define SEND_SI2S(p,b)             sendcmd(p,PARM,SI2S,WORD2(b),RET(0))
+
+#define EOB_STATUS         0x80000000	/* status flags : block boundary */
+#define EOS_STATUS         0x40000000	/*              : stoppped */
+#define EOC_STATUS         0x20000000	/*              : stream end */
+#define ERR_STATUS         0x10000000
+#define EMPTY_STATUS       0x08000000
+
+#define IEOB_ENABLE        0x1	/* enable interrupts for status notification above */
+#define IEOS_ENABLE        0x2
+#define IEOC_ENABLE        0x4
+#define RDONCE             0x8
+#define DESC_MAX_MASK      0xff
+
+#define ST_PLAY  0x1		/* stream states */
+#define ST_STOP  0x2
+#define ST_PAUSE 0x4
+
+#define I2S_INTDEC     3	/* config for I2S link */
+#define I2S_MERGER     0
+#define I2S_SPLITTER   0
+#define I2S_MIXER      7
+#define I2S_RATE       44100
+
+#define MODEM_INTDEC   4	/* config for modem link */
+#define MODEM_MERGER   3
+#define MODEM_SPLITTER 0
+#define MODEM_MIXER    11
+
+#define FM_INTDEC      3	/* config for FM/OPL3 link */
+#define FM_MERGER      0
+#define FM_SPLITTER    0
+#define FM_MIXER       9
+
+#define SPLIT_PATH  0x80	/* path splitting flag */
+
+enum FIRMWARE {
+	DATA_REC = 0, EXT_END_OF_FILE, EXT_SEG_ADDR_REC, EXT_GOTO_CMD_REC,
+	EXT_LIN_ADDR_REC,
+};
+
+enum CMDS {
+	GETV = 0x00, GETC, GUNS, SCID, RMEM =
+	    0x10, SMEM, WMEM, SDTM, GOTO, SSTR =
+	    0x20, PSTR, KSTR, KDMA, GPOS, SETF, GSTS, NGPOS, PSEL =
+	    0x30, PCLR, PLST, RSSV, LSEL, SSRC = 0x40, SLST, RSRC, SSRB, SDGV =
+	    0x50, RDGV, DLST, SACR = 0x60, RACR, ALST, TXAC, RXAC, SI2S =
+	    0x70, ARM_SETDPLL = 0x72,
+};
+
+enum E1SOURCE {
+	ARM2LBUS_FIFO0 = 0, ARM2LBUS_FIFO1, ARM2LBUS_FIFO2, ARM2LBUS_FIFO3,
+	ARM2LBUS_FIFO4, ARM2LBUS_FIFO5, ARM2LBUS_FIFO6, ARM2LBUS_FIFO7,
+	ARM2LBUS_FIFO8, ARM2LBUS_FIFO9, ARM2LBUS_FIFO10, ARM2LBUS_FIFO11,
+	ARM2LBUS_FIFO12, ARM2LBUS_FIFO13, ARM2LBUS_FIFO14, ARM2LBUS_FIFO15,
+	INTER0_OUT, INTER1_OUT, INTER2_OUT, INTER3_OUT, INTER4_OUT,
+	INTERM0_OUT, INTERM1_OUT, INTERM2_OUT, INTERM3_OUT, INTERM4_OUT,
+	INTERM5_OUT, INTERM6_OUT, DECIMM0_OUT, DECIMM1_OUT, DECIMM2_OUT,
+	DECIMM3_OUT, DECIM0_OUT, SR3_4_OUT, OPL3_SAMPLE, ASRC0, ASRC1,
+	ACLNK2PADC, ACLNK2MODEM0RX, ACLNK2MIC, ACLNK2MODEM1RX, ACLNK2HNDMIC,
+	DIGITAL_MIXER_OUT0, GAINFUNC0_OUT, GAINFUNC1_OUT, GAINFUNC2_OUT,
+	GAINFUNC3_OUT, GAINFUNC4_OUT, SOFTMODEMTX, SPLITTER0_OUTL,
+	SPLITTER0_OUTR, SPLITTER1_OUTL, SPLITTER1_OUTR, SPLITTER2_OUTL,
+	SPLITTER2_OUTR, SPLITTER3_OUTL, SPLITTER3_OUTR, MERGER0_OUT,
+	MERGER1_OUT, MERGER2_OUT, MERGER3_OUT, ARM2LBUS_FIFO_DIRECT, NO_OUT
+};
+
+enum E2SINK {
+	LBUS2ARM_FIFO0 = 0, LBUS2ARM_FIFO1, LBUS2ARM_FIFO2, LBUS2ARM_FIFO3,
+	LBUS2ARM_FIFO4, LBUS2ARM_FIFO5, LBUS2ARM_FIFO6, LBUS2ARM_FIFO7,
+	INTER0_IN, INTER1_IN, INTER2_IN, INTER3_IN, INTER4_IN, INTERM0_IN,
+	INTERM1_IN, INTERM2_IN, INTERM3_IN, INTERM4_IN, INTERM5_IN, INTERM6_IN,
+	DECIMM0_IN, DECIMM1_IN, DECIMM2_IN, DECIMM3_IN, DECIM0_IN, SR3_4_IN,
+	PDAC2ACLNK, MODEM0TX2ACLNK, MODEM1TX2ACLNK, HNDSPK2ACLNK,
+	DIGITAL_MIXER_IN0, DIGITAL_MIXER_IN1, DIGITAL_MIXER_IN2,
+	DIGITAL_MIXER_IN3, DIGITAL_MIXER_IN4, DIGITAL_MIXER_IN5,
+	DIGITAL_MIXER_IN6, DIGITAL_MIXER_IN7, DIGITAL_MIXER_IN8,
+	DIGITAL_MIXER_IN9, DIGITAL_MIXER_IN10, DIGITAL_MIXER_IN11,
+	GAINFUNC0_IN, GAINFUNC1_IN, GAINFUNC2_IN, GAINFUNC3_IN, GAINFUNC4_IN,
+	SOFTMODEMRX, SPLITTER0_IN, SPLITTER1_IN, SPLITTER2_IN, SPLITTER3_IN,
+	MERGER0_INL, MERGER0_INR, MERGER1_INL, MERGER1_INR, MERGER2_INL,
+	MERGER2_INR, MERGER3_INL, MERGER3_INR, E2SINK_MAX
+};
+
+enum LBUS_SINK {
+	LS_SRC_INTERPOLATOR = 0, LS_SRC_INTERPOLATORM, LS_SRC_DECIMATOR,
+	LS_SRC_DECIMATORM, LS_MIXER_IN, LS_MIXER_GAIN_FUNCTION,
+	LS_SRC_SPLITTER, LS_SRC_MERGER, LS_NONE1, LS_NONE2,
+};
+
+enum RT_CHANNEL_IDS {
+	M0TX = 0, M1TX, TAMTX, HSSPKR, PDAC, DSNDTX0, DSNDTX1, DSNDTX2,
+	DSNDTX3, DSNDTX4, DSNDTX5, DSNDTX6, DSNDTX7, WVSTRTX, COP3DTX, SPARE,
+	M0RX, HSMIC, M1RX, CLEANRX, MICADC, PADC, COPRX1, COPRX2,
+	CHANNEL_ID_COUNTER
+};
+
+enum { SB_CMD = 0, MODEM_CMD, I2S_CMD0, I2S_CMD1, FM_CMD, MAX_CMD };
+
+struct lbuspath {
+	unsigned char *noconv;
+	unsigned char *stereo;
+	unsigned char *mono;
+};
+
+struct cmdport {
+	u32 data1;		/* cmd,param */
+	u32 data2;		/* param */
+	u32 stat;		/* status */
+	u32 pad[5];
+};
+
+struct riptideport {
+	u32 audio_control;	/* status registers */
+	u32 audio_status;
+	u32 pad[2];
+	struct cmdport port[2];	/* command ports */
+};
+
+struct cmdif {
+	struct riptideport *hwport;
+	spinlock_t lock;
+	unsigned int cmdcnt;	/* cmd statistics */
+	unsigned int cmdtime;
+	unsigned int cmdtimemax;
+	unsigned int cmdtimemin;
+	unsigned int errcnt;
+	int is_reset;
+};
+
+struct riptide_firmware {
+	u16 ASIC;
+	u16 CODEC;
+	u16 AUXDSP;
+	u16 PROG;
+};
+
+union cmdret {
+	u8 retbytes[8];
+	u16 retwords[4];
+	u32 retlongs[2];
+};
+
+union firmware_version {
+	union cmdret ret;
+	struct riptide_firmware firmware;
+};
+
+#define get_pcmhwdev(substream) (struct pcmhw *)(substream->runtime->private_data)
+
+#define PLAYBACK_SUBSTREAMS 3
+struct snd_riptide {
+	struct snd_card *card;
+	struct pci_dev *pci;
+	const struct firmware *fw_entry;
+
+	struct cmdif *cif;
+
+	struct snd_pcm *pcm;
+	struct snd_pcm *pcm_i2s;
+	struct snd_rawmidi *rmidi;
+	struct snd_opl3 *opl3;
+	struct snd_ac97 *ac97;
+	struct snd_ac97_bus *ac97_bus;
+
+	struct snd_pcm_substream *playback_substream[PLAYBACK_SUBSTREAMS];
+	struct snd_pcm_substream *capture_substream;
+
+	int openstreams;
+
+	int irq;
+	unsigned long port;
+	unsigned short mpuaddr;
+	unsigned short opladdr;
+#ifdef SUPPORT_JOYSTICK
+	unsigned short gameaddr;
+#endif
+	struct resource *res_port;
+
+	unsigned short device_id;
+
+	union firmware_version firmware;
+
+	spinlock_t lock;
+	struct tasklet_struct riptide_tq;
+	struct snd_info_entry *proc_entry;
+
+	unsigned long received_irqs;
+	unsigned long handled_irqs;
+#ifdef CONFIG_PM
+	int in_suspend;
+#endif
+};
+
+struct sgd {			/* scatter gather desriptor */
+	u32 dwNextLink;
+	u32 dwSegPtrPhys;
+	u32 dwSegLen;
+	u32 dwStat_Ctl;
+};
+
+struct pcmhw {			/* pcm descriptor */
+	struct lbuspath paths;
+	unsigned char *lbuspath;
+	unsigned char source;
+	unsigned char intdec[2];
+	unsigned char mixer;
+	unsigned char id;
+	unsigned char state;
+	unsigned int rate;
+	unsigned int channels;
+	snd_pcm_format_t format;
+	struct snd_dma_buffer sgdlist;
+	struct sgd *sgdbuf;
+	unsigned int size;
+	unsigned int pages;
+	unsigned int oldpos;
+	unsigned int pointer;
+};
+
+#define CMDRET_ZERO (union cmdret){{(u32)0, (u32) 0}}
+
+static int sendcmd(struct cmdif *cif, u32 flags, u32 cmd, u32 parm,
+		   union cmdret *ret);
+static int getsourcesink(struct cmdif *cif, unsigned char source,
+			 unsigned char sink, unsigned char *a,
+			 unsigned char *b);
+static int snd_riptide_initialize(struct snd_riptide *chip);
+static int riptide_reset(struct cmdif *cif, struct snd_riptide *chip);
+
+/*
+ */
+
+static DEFINE_PCI_DEVICE_TABLE(snd_riptide_ids) = {
+	{ PCI_DEVICE(0x127a, 0x4310) },
+	{ PCI_DEVICE(0x127a, 0x4320) },
+	{ PCI_DEVICE(0x127a, 0x4330) },
+	{ PCI_DEVICE(0x127a, 0x4340) },
+	{0,},
+};
+
+#ifdef SUPPORT_JOYSTICK
+static DEFINE_PCI_DEVICE_TABLE(snd_riptide_joystick_ids) = {
+	{ PCI_DEVICE(0x127a, 0x4312) },
+	{ PCI_DEVICE(0x127a, 0x4322) },
+	{ PCI_DEVICE(0x127a, 0x4332) },
+	{ PCI_DEVICE(0x127a, 0x4342) },
+	{0,},
+};
+#endif
+
+MODULE_DEVICE_TABLE(pci, snd_riptide_ids);
+
+/*
+ */
+
+static unsigned char lbusin2out[E2SINK_MAX + 1][2] = {
+	{NO_OUT, LS_NONE1}, {NO_OUT, LS_NONE2}, {NO_OUT, LS_NONE1}, {NO_OUT,
+								     LS_NONE2},
+	{NO_OUT, LS_NONE1}, {NO_OUT, LS_NONE2}, {NO_OUT, LS_NONE1}, {NO_OUT,
+								     LS_NONE2},
+	{INTER0_OUT, LS_SRC_INTERPOLATOR}, {INTER1_OUT, LS_SRC_INTERPOLATOR},
+	{INTER2_OUT, LS_SRC_INTERPOLATOR}, {INTER3_OUT, LS_SRC_INTERPOLATOR},
+	{INTER4_OUT, LS_SRC_INTERPOLATOR}, {INTERM0_OUT, LS_SRC_INTERPOLATORM},
+	{INTERM1_OUT, LS_SRC_INTERPOLATORM}, {INTERM2_OUT,
+					      LS_SRC_INTERPOLATORM},
+	{INTERM3_OUT, LS_SRC_INTERPOLATORM}, {INTERM4_OUT,
+					      LS_SRC_INTERPOLATORM},
+	{INTERM5_OUT, LS_SRC_INTERPOLATORM}, {INTERM6_OUT,
+					      LS_SRC_INTERPOLATORM},
+	{DECIMM0_OUT, LS_SRC_DECIMATORM}, {DECIMM1_OUT, LS_SRC_DECIMATORM},
+	{DECIMM2_OUT, LS_SRC_DECIMATORM}, {DECIMM3_OUT, LS_SRC_DECIMATORM},
+	{DECIM0_OUT, LS_SRC_DECIMATOR}, {SR3_4_OUT, LS_NONE1}, {NO_OUT,
+								LS_NONE2},
+	{NO_OUT, LS_NONE1}, {NO_OUT, LS_NONE2}, {NO_OUT, LS_NONE1},
+	{DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN},
+	{DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN},
+	{DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN},
+	{DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN},
+	{DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN},
+	{DIGITAL_MIXER_OUT0, LS_MIXER_IN}, {DIGITAL_MIXER_OUT0, LS_MIXER_IN},
+	{GAINFUNC0_OUT, LS_MIXER_GAIN_FUNCTION}, {GAINFUNC1_OUT,
+						  LS_MIXER_GAIN_FUNCTION},
+	{GAINFUNC2_OUT, LS_MIXER_GAIN_FUNCTION}, {GAINFUNC3_OUT,
+						  LS_MIXER_GAIN_FUNCTION},
+	{GAINFUNC4_OUT, LS_MIXER_GAIN_FUNCTION}, {SOFTMODEMTX, LS_NONE1},
+	{SPLITTER0_OUTL, LS_SRC_SPLITTER}, {SPLITTER1_OUTL, LS_SRC_SPLITTER},
+	{SPLITTER2_OUTL, LS_SRC_SPLITTER}, {SPLITTER3_OUTL, LS_SRC_SPLITTER},
+	{MERGER0_OUT, LS_SRC_MERGER}, {MERGER0_OUT, LS_SRC_MERGER},
+	{MERGER1_OUT, LS_SRC_MERGER},
+	{MERGER1_OUT, LS_SRC_MERGER}, {MERGER2_OUT, LS_SRC_MERGER},
+	{MERGER2_OUT, LS_SRC_MERGER},
+	{MERGER3_OUT, LS_SRC_MERGER}, {MERGER3_OUT, LS_SRC_MERGER}, {NO_OUT,
+								     LS_NONE2},
+};
+
+static unsigned char lbus_play_opl3[] = {
+	DIGITAL_MIXER_IN0 + FM_MIXER, 0xff
+};
+static unsigned char lbus_play_modem[] = {
+	DIGITAL_MIXER_IN0 + MODEM_MIXER, 0xff
+};
+static unsigned char lbus_play_i2s[] = {
+	INTER0_IN + I2S_INTDEC, DIGITAL_MIXER_IN0 + I2S_MIXER, 0xff
+};
+static unsigned char lbus_play_out[] = {
+	PDAC2ACLNK, 0xff
+};
+static unsigned char lbus_play_outhp[] = {
+	HNDSPK2ACLNK, 0xff
+};
+static unsigned char lbus_play_noconv1[] = {
+	DIGITAL_MIXER_IN0, 0xff
+};
+static unsigned char lbus_play_stereo1[] = {
+	INTER0_IN, DIGITAL_MIXER_IN0, 0xff
+};
+static unsigned char lbus_play_mono1[] = {
+	INTERM0_IN, DIGITAL_MIXER_IN0, 0xff
+};
+static unsigned char lbus_play_noconv2[] = {
+	DIGITAL_MIXER_IN1, 0xff
+};
+static unsigned char lbus_play_stereo2[] = {
+	INTER1_IN, DIGITAL_MIXER_IN1, 0xff
+};
+static unsigned char lbus_play_mono2[] = {
+	INTERM1_IN, DIGITAL_MIXER_IN1, 0xff
+};
+static unsigned char lbus_play_noconv3[] = {
+	DIGITAL_MIXER_IN2, 0xff
+};
+static unsigned char lbus_play_stereo3[] = {
+	INTER2_IN, DIGITAL_MIXER_IN2, 0xff
+};
+static unsigned char lbus_play_mono3[] = {
+	INTERM2_IN, DIGITAL_MIXER_IN2, 0xff
+};
+static unsigned char lbus_rec_noconv1[] = {
+	LBUS2ARM_FIFO5, 0xff
+};
+static unsigned char lbus_rec_stereo1[] = {
+	DECIM0_IN, LBUS2ARM_FIFO5, 0xff
+};
+static unsigned char lbus_rec_mono1[] = {
+	DECIMM3_IN, LBUS2ARM_FIFO5, 0xff
+};
+
+static unsigned char play_ids[] = { 4, 1, 2, };
+static unsigned char play_sources[] = {
+	ARM2LBUS_FIFO4, ARM2LBUS_FIFO1, ARM2LBUS_FIFO2,
+};
+static struct lbuspath lbus_play_paths[] = {
+	{
+	 .noconv = lbus_play_noconv1,
+	 .stereo = lbus_play_stereo1,
+	 .mono = lbus_play_mono1,
+	 },
+	{
+	 .noconv = lbus_play_noconv2,
+	 .stereo = lbus_play_stereo2,
+	 .mono = lbus_play_mono2,
+	 },
+	{
+	 .noconv = lbus_play_noconv3,
+	 .stereo = lbus_play_stereo3,
+	 .mono = lbus_play_mono3,
+	 },
+};
+static struct lbuspath lbus_rec_path = {
+	.noconv = lbus_rec_noconv1,
+	.stereo = lbus_rec_stereo1,
+	.mono = lbus_rec_mono1,
+};
+
+#define FIRMWARE_VERSIONS 1
+static union firmware_version firmware_versions[] = {
+	{
+		.firmware = {
+			.ASIC = 3,
+			.CODEC = 2,
+			.AUXDSP = 3,
+			.PROG = 773,
+		},
+	},
+};
+
+static u32 atoh(const unsigned char *in, unsigned int len)
+{
+	u32 sum = 0;
+	unsigned int mult = 1;
+	unsigned char c;
+
+	while (len) {
+		int value;
+
+		c = in[len - 1];
+		value = hex_to_bin(c);
+		if (value >= 0)
+			sum += mult * value;
+		mult *= 16;
+		--len;
+	}
+	return sum;
+}
+
+static int senddata(struct cmdif *cif, const unsigned char *in, u32 offset)
+{
+	u32 addr;
+	u32 data;
+	u32 i;
+	const unsigned char *p;
+
+	i = atoh(&in[1], 2);
+	addr = offset + atoh(&in[3], 4);
+	if (SEND_SMEM(cif, 0, addr) != 0)
+		return -EACCES;
+	p = in + 9;
+	while (i) {
+		data = atoh(p, 8);
+		if (SEND_WMEM(cif, 2,
+			      ((data & 0x0f0f0f0f) << 4) | ((data & 0xf0f0f0f0)
+							    >> 4)))
+			return -EACCES;
+		i -= 4;
+		p += 8;
+	}
+	return 0;
+}
+
+static int loadfirmware(struct cmdif *cif, const unsigned char *img,
+			unsigned int size)
+{
+	const unsigned char *in;
+	u32 laddr, saddr, t, val;
+	int err = 0;
+
+	laddr = saddr = 0;
+	while (size > 0 && err == 0) {
+		in = img;
+		if (in[0] == ':') {
+			t = atoh(&in[7], 2);
+			switch (t) {
+			case DATA_REC:
+				err = senddata(cif, in, laddr + saddr);
+				break;
+			case EXT_SEG_ADDR_REC:
+				saddr = atoh(&in[9], 4) << 4;
+				break;
+			case EXT_LIN_ADDR_REC:
+				laddr = atoh(&in[9], 4) << 16;
+				break;
+			case EXT_GOTO_CMD_REC:
+				val = atoh(&in[9], 8);
+				if (SEND_GOTO(cif, val) != 0)
+					err = -EACCES;
+				break;
+			case EXT_END_OF_FILE:
+				size = 0;
+				break;
+			default:
+				break;
+			}
+			while (size > 0) {
+				size--;
+				if (*img++ == '\n')
+					break;
+			}
+		}
+	}
+	snd_printdd("load firmware return %d\n", err);
+	return err;
+}
+
+static void
+alloclbuspath(struct cmdif *cif, unsigned char source,
+	      unsigned char *path, unsigned char *mixer, unsigned char *s)
+{
+	while (*path != 0xff) {
+		unsigned char sink, type;
+
+		sink = *path & (~SPLIT_PATH);
+		if (sink != E2SINK_MAX) {
+			snd_printdd("alloc path 0x%x->0x%x\n", source, sink);
+			SEND_PSEL(cif, source, sink);
+			source = lbusin2out[sink][0];
+			type = lbusin2out[sink][1];
+			if (type == LS_MIXER_IN) {
+				if (mixer)
+					*mixer = sink - DIGITAL_MIXER_IN0;
+			}
+			if (type == LS_SRC_DECIMATORM ||
+			    type == LS_SRC_DECIMATOR ||
+			    type == LS_SRC_INTERPOLATORM ||
+			    type == LS_SRC_INTERPOLATOR) {
+				if (s) {
+					if (s[0] != 0xff)
+						s[1] = sink;
+					else
+						s[0] = sink;
+				}
+			}
+		}
+		if (*path++ & SPLIT_PATH) {
+			unsigned char *npath = path;
+
+			while (*npath != 0xff)
+				npath++;
+			alloclbuspath(cif, source + 1, ++npath, mixer, s);
+		}
+	}
+}
+
+static void
+freelbuspath(struct cmdif *cif, unsigned char source, unsigned char *path)
+{
+	while (*path != 0xff) {
+		unsigned char sink;
+
+		sink = *path & (~SPLIT_PATH);
+		if (sink != E2SINK_MAX) {
+			snd_printdd("free path 0x%x->0x%x\n", source, sink);
+			SEND_PCLR(cif, source, sink);
+			source = lbusin2out[sink][0];
+		}
+		if (*path++ & SPLIT_PATH) {
+			unsigned char *npath = path;
+
+			while (*npath != 0xff)
+				npath++;
+			freelbuspath(cif, source + 1, ++npath);
+		}
+	}
+}
+
+static int writearm(struct cmdif *cif, u32 addr, u32 data, u32 mask)
+{
+	union cmdret rptr = CMDRET_ZERO;
+	unsigned int i = MAX_WRITE_RETRY;
+	int flag = 1;
+
+	SEND_RMEM(cif, 0x02, addr, &rptr);
+	rptr.retlongs[0] &= (~mask);
+
+	while (--i) {
+		SEND_SMEM(cif, 0x01, addr);
+		SEND_WMEM(cif, 0x02, (rptr.retlongs[0] | data));
+		SEND_RMEM(cif, 0x02, addr, &rptr);
+		if ((rptr.retlongs[0] & data) == data) {
+			flag = 0;
+			break;
+		} else
+			rptr.retlongs[0] &= ~mask;
+	}
+	snd_printdd("send arm 0x%x 0x%x 0x%x return %d\n", addr, data, mask,
+		    flag);
+	return flag;
+}
+
+static int sendcmd(struct cmdif *cif, u32 flags, u32 cmd, u32 parm,
+		   union cmdret *ret)
+{
+	int i, j;
+	int err;
+	unsigned int time = 0;
+	unsigned long irqflags;
+	struct riptideport *hwport;
+	struct cmdport *cmdport = NULL;
+
+	if (snd_BUG_ON(!cif))
+		return -EINVAL;
+
+	hwport = cif->hwport;
+	if (cif->errcnt > MAX_ERROR_COUNT) {
+		if (cif->is_reset) {
+			snd_printk(KERN_ERR
+				   "Riptide: Too many failed cmds, reinitializing\n");
+			if (riptide_reset(cif, NULL) == 0) {
+				cif->errcnt = 0;
+				return -EIO;
+			}
+		}
+		snd_printk(KERN_ERR "Riptide: Initialization failed.\n");
+		return -EINVAL;
+	}
+	if (ret) {
+		ret->retlongs[0] = 0;
+		ret->retlongs[1] = 0;
+	}
+	i = 0;
+	spin_lock_irqsave(&cif->lock, irqflags);
+	while (i++ < CMDIF_TIMEOUT && !IS_READY(cif->hwport))
+		udelay(10);
+	if (i > CMDIF_TIMEOUT) {
+		err = -EBUSY;
+		goto errout;
+	}
+
+	err = 0;
+	for (j = 0, time = 0; time < CMDIF_TIMEOUT; j++, time += 2) {
+		cmdport = &(hwport->port[j % 2]);
+		if (IS_DATF(cmdport)) {	/* free pending data */
+			READ_PORT_ULONG(cmdport->data1);
+			READ_PORT_ULONG(cmdport->data2);
+		}
+		if (IS_CMDE(cmdport)) {
+			if (flags & PARM)	/* put data */
+				WRITE_PORT_ULONG(cmdport->data2, parm);
+			WRITE_PORT_ULONG(cmdport->data1, cmd);	/* write cmd */
+			if ((flags & RESP) && ret) {
+				while (!IS_DATF(cmdport) &&
+				       time < CMDIF_TIMEOUT) {
+					udelay(10);
+					time++;
+				}
+				if (time < CMDIF_TIMEOUT) {	/* read response */
+					ret->retlongs[0] =
+					    READ_PORT_ULONG(cmdport->data1);
+					ret->retlongs[1] =
+					    READ_PORT_ULONG(cmdport->data2);
+				} else {
+					err = -ENOSYS;
+					goto errout;
+				}
+			}
+			break;
+		}
+		udelay(20);
+	}
+	if (time == CMDIF_TIMEOUT) {
+		err = -ENODATA;
+		goto errout;
+	}
+	spin_unlock_irqrestore(&cif->lock, irqflags);
+
+	cif->cmdcnt++;		/* update command statistics */
+	cif->cmdtime += time;
+	if (time > cif->cmdtimemax)
+		cif->cmdtimemax = time;
+	if (time < cif->cmdtimemin)
+		cif->cmdtimemin = time;
+	if ((cif->cmdcnt) % 1000 == 0)
+		snd_printdd
+		    ("send cmd %d time: %d mintime: %d maxtime %d err: %d\n",
+		     cif->cmdcnt, cif->cmdtime, cif->cmdtimemin,
+		     cif->cmdtimemax, cif->errcnt);
+	return 0;
+
+      errout:
+	cif->errcnt++;
+	spin_unlock_irqrestore(&cif->lock, irqflags);
+	snd_printdd
+	    ("send cmd %d hw: 0x%x flag: 0x%x cmd: 0x%x parm: 0x%x ret: 0x%x 0x%x CMDE: %d DATF: %d failed %d\n",
+	     cif->cmdcnt, (int)((void *)&(cmdport->stat) - (void *)hwport),
+	     flags, cmd, parm, ret ? ret->retlongs[0] : 0,
+	     ret ? ret->retlongs[1] : 0, IS_CMDE(cmdport), IS_DATF(cmdport),
+	     err);
+	return err;
+}
+
+static int
+setmixer(struct cmdif *cif, short num, unsigned short rval, unsigned short lval)
+{
+	union cmdret rptr = CMDRET_ZERO;
+	int i = 0;
+
+	snd_printdd("sent mixer %d: 0x%d 0x%d\n", num, rval, lval);
+	do {
+		SEND_SDGV(cif, num, num, rval, lval);
+		SEND_RDGV(cif, num, num, &rptr);
+		if (rptr.retwords[0] == lval && rptr.retwords[1] == rval)
+			return 0;
+	} while (i++ < MAX_WRITE_RETRY);
+	snd_printdd("sent mixer failed\n");
+	return -EIO;
+}
+
+static int getpaths(struct cmdif *cif, unsigned char *o)
+{
+	unsigned char src[E2SINK_MAX];
+	unsigned char sink[E2SINK_MAX];
+	int i, j = 0;
+
+	for (i = 0; i < E2SINK_MAX; i++) {
+		getsourcesink(cif, i, i, &src[i], &sink[i]);
+		if (sink[i] < E2SINK_MAX) {
+			o[j++] = sink[i];
+			o[j++] = i;
+		}
+	}
+	return j;
+}
+
+static int
+getsourcesink(struct cmdif *cif, unsigned char source, unsigned char sink,
+	      unsigned char *a, unsigned char *b)
+{
+	union cmdret rptr = CMDRET_ZERO;
+
+	if (SEND_RSSV(cif, source, sink, &rptr) &&
+	    SEND_RSSV(cif, source, sink, &rptr))
+		return -EIO;
+	*a = rptr.retbytes[0];
+	*b = rptr.retbytes[1];
+	snd_printdd("getsourcesink 0x%x 0x%x\n", *a, *b);
+	return 0;
+}
+
+static int
+getsamplerate(struct cmdif *cif, unsigned char *intdec, unsigned int *rate)
+{
+	unsigned char *s;
+	unsigned int p[2] = { 0, 0 };
+	int i;
+	union cmdret rptr = CMDRET_ZERO;
+
+	s = intdec;
+	for (i = 0; i < 2; i++) {
+		if (*s != 0xff) {
+			if (SEND_RSRC(cif, *s, &rptr) &&
+			    SEND_RSRC(cif, *s, &rptr))
+				return -EIO;
+			p[i] += rptr.retwords[1];
+			p[i] *= rptr.retwords[2];
+			p[i] += rptr.retwords[3];
+			p[i] /= 65536;
+		}
+		s++;
+	}
+	if (p[0]) {
+		if (p[1] != p[0])
+			snd_printdd("rates differ %d %d\n", p[0], p[1]);
+		*rate = (unsigned int)p[0];
+	} else
+		*rate = (unsigned int)p[1];
+	snd_printdd("getsampleformat %d %d %d\n", intdec[0], intdec[1], *rate);
+	return 0;
+}
+
+static int
+setsampleformat(struct cmdif *cif,
+		unsigned char mixer, unsigned char id,
+		unsigned char channels, unsigned char format)
+{
+	unsigned char w, ch, sig, order;
+
+	snd_printdd
+	    ("setsampleformat mixer: %d id: %d channels: %d format: %d\n",
+	     mixer, id, channels, format);
+	ch = channels == 1;
+	w = snd_pcm_format_width(format) == 8;
+	sig = snd_pcm_format_unsigned(format) != 0;
+	order = snd_pcm_format_big_endian(format) != 0;
+
+	if (SEND_SETF(cif, mixer, w, ch, order, sig, id) &&
+	    SEND_SETF(cif, mixer, w, ch, order, sig, id)) {
+		snd_printdd("setsampleformat failed\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+static int
+setsamplerate(struct cmdif *cif, unsigned char *intdec, unsigned int rate)
+{
+	u32 D, M, N;
+	union cmdret rptr = CMDRET_ZERO;
+	int i;
+
+	snd_printdd("setsamplerate intdec: %d,%d rate: %d\n", intdec[0],
+		    intdec[1], rate);
+	D = 48000;
+	M = ((rate == 48000) ? 47999 : rate) * 65536;
+	N = M % D;
+	M /= D;
+	for (i = 0; i < 2; i++) {
+		if (*intdec != 0xff) {
+			do {
+				SEND_SSRC(cif, *intdec, D, M, N);
+				SEND_RSRC(cif, *intdec, &rptr);
+			} while (rptr.retwords[1] != D &&
+				 rptr.retwords[2] != M &&
+				 rptr.retwords[3] != N &&
+				 i++ < MAX_WRITE_RETRY);
+			if (i > MAX_WRITE_RETRY) {
+				snd_printdd("sent samplerate %d: %d failed\n",
+					    *intdec, rate);
+				return -EIO;
+			}
+		}
+		intdec++;
+	}
+	return 0;
+}
+
+static int
+getmixer(struct cmdif *cif, short num, unsigned short *rval,
+	 unsigned short *lval)
+{
+	union cmdret rptr = CMDRET_ZERO;
+
+	if (SEND_RDGV(cif, num, num, &rptr) && SEND_RDGV(cif, num, num, &rptr))
+		return -EIO;
+	*rval = rptr.retwords[0];
+	*lval = rptr.retwords[1];
+	snd_printdd("got mixer %d: 0x%d 0x%d\n", num, *rval, *lval);
+	return 0;
+}
+
+static void riptide_handleirq(unsigned long dev_id)
+{
+	struct snd_riptide *chip = (void *)dev_id;
+	struct cmdif *cif = chip->cif;
+	struct snd_pcm_substream *substream[PLAYBACK_SUBSTREAMS + 1];
+	struct snd_pcm_runtime *runtime;
+	struct pcmhw *data = NULL;
+	unsigned int pos, period_bytes;
+	struct sgd *c;
+	int i, j;
+	unsigned int flag;
+
+	if (!cif)
+		return;
+
+	for (i = 0; i < PLAYBACK_SUBSTREAMS; i++)
+		substream[i] = chip->playback_substream[i];
+	substream[i] = chip->capture_substream;
+	for (i = 0; i < PLAYBACK_SUBSTREAMS + 1; i++) {
+		if (substream[i] &&
+		    (runtime = substream[i]->runtime) &&
+		    (data = runtime->private_data) && data->state != ST_STOP) {
+			pos = 0;
+			for (j = 0; j < data->pages; j++) {
+				c = &data->sgdbuf[j];
+				flag = le32_to_cpu(c->dwStat_Ctl);
+				if (flag & EOB_STATUS)
+					pos += le32_to_cpu(c->dwSegLen);
+				if (flag & EOC_STATUS)
+					pos += le32_to_cpu(c->dwSegLen);
+				if ((flag & EOS_STATUS)
+				    && (data->state == ST_PLAY)) {
+					data->state = ST_STOP;
+					snd_printk(KERN_ERR
+						   "Riptide: DMA stopped unexpectedly\n");
+				}
+				c->dwStat_Ctl =
+				    cpu_to_le32(flag &
+						~(EOS_STATUS | EOB_STATUS |
+						  EOC_STATUS));
+			}
+			data->pointer += pos;
+			pos += data->oldpos;
+			if (data->state != ST_STOP) {
+				period_bytes =
+				    frames_to_bytes(runtime,
+						    runtime->period_size);
+				snd_printdd
+				    ("interrupt 0x%x after 0x%lx of 0x%lx frames in period\n",
+				     READ_AUDIO_STATUS(cif->hwport),
+				     bytes_to_frames(runtime, pos),
+				     runtime->period_size);
+				j = 0;
+				if (pos >= period_bytes) {
+					j++;
+					while (pos >= period_bytes)
+						pos -= period_bytes;
+				}
+				data->oldpos = pos;
+				if (j > 0)
+					snd_pcm_period_elapsed(substream[i]);
+			}
+		}
+	}
+}
+
+#ifdef CONFIG_PM
+static int riptide_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_riptide *chip = card->private_data;
+
+	chip->in_suspend = 1;
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_ac97_suspend(chip->ac97);
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int riptide_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_riptide *chip = card->private_data;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "riptide: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+	snd_riptide_initialize(chip);
+	snd_ac97_resume(chip->ac97);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	chip->in_suspend = 0;
+	return 0;
+}
+#endif
+
+static int try_to_load_firmware(struct cmdif *cif, struct snd_riptide *chip)
+{
+	union firmware_version firmware = { .ret = CMDRET_ZERO };
+	int i, timeout, err;
+
+	for (i = 0; i < 2; i++) {
+		WRITE_PORT_ULONG(cif->hwport->port[i].data1, 0);
+		WRITE_PORT_ULONG(cif->hwport->port[i].data2, 0);
+	}
+	SET_GRESET(cif->hwport);
+	udelay(100);
+	UNSET_GRESET(cif->hwport);
+	udelay(100);
+
+	for (timeout = 100000; --timeout; udelay(10)) {
+		if (IS_READY(cif->hwport) && !IS_GERR(cif->hwport))
+			break;
+	}
+	if (!timeout) {
+		snd_printk(KERN_ERR
+			   "Riptide: device not ready, audio status: 0x%x "
+			   "ready: %d gerr: %d\n",
+			   READ_AUDIO_STATUS(cif->hwport),
+			   IS_READY(cif->hwport), IS_GERR(cif->hwport));
+		return -EIO;
+	} else {
+		snd_printdd
+			("Riptide: audio status: 0x%x ready: %d gerr: %d\n",
+			 READ_AUDIO_STATUS(cif->hwport),
+			 IS_READY(cif->hwport), IS_GERR(cif->hwport));
+	}
+
+	SEND_GETV(cif, &firmware.ret);
+	snd_printdd("Firmware version: ASIC: %d CODEC %d AUXDSP %d PROG %d\n",
+		    firmware.firmware.ASIC, firmware.firmware.CODEC,
+		    firmware.firmware.AUXDSP, firmware.firmware.PROG);
+
+	if (!chip)
+		return 1;
+
+	for (i = 0; i < FIRMWARE_VERSIONS; i++) {
+		if (!memcmp(&firmware_versions[i], &firmware, sizeof(firmware)))
+			return 1; /* OK */
+
+	}
+
+	snd_printdd("Writing Firmware\n");
+	if (!chip->fw_entry) {
+		err = request_firmware(&chip->fw_entry, "riptide.hex",
+				       &chip->pci->dev);
+		if (err) {
+			snd_printk(KERN_ERR
+				   "Riptide: Firmware not available %d\n", err);
+			return -EIO;
+		}
+	}
+	err = loadfirmware(cif, chip->fw_entry->data, chip->fw_entry->size);
+	if (err) {
+		snd_printk(KERN_ERR
+			   "Riptide: Could not load firmware %d\n", err);
+		return err;
+	}
+
+	chip->firmware = firmware;
+
+	return 1; /* OK */
+}
+
+static int riptide_reset(struct cmdif *cif, struct snd_riptide *chip)
+{
+	union cmdret rptr = CMDRET_ZERO;
+	int err, tries;
+
+	if (!cif)
+		return -EINVAL;
+
+	cif->cmdcnt = 0;
+	cif->cmdtime = 0;
+	cif->cmdtimemax = 0;
+	cif->cmdtimemin = 0xffffffff;
+	cif->errcnt = 0;
+	cif->is_reset = 0;
+
+	tries = RESET_TRIES;
+	do {
+		err = try_to_load_firmware(cif, chip);
+		if (err < 0)
+			return err;
+	} while (!err && --tries);
+
+	SEND_SACR(cif, 0, AC97_RESET);
+	SEND_RACR(cif, AC97_RESET, &rptr);
+	snd_printdd("AC97: 0x%x 0x%x\n", rptr.retlongs[0], rptr.retlongs[1]);
+
+	SEND_PLST(cif, 0);
+	SEND_SLST(cif, 0);
+	SEND_DLST(cif, 0);
+	SEND_ALST(cif, 0);
+	SEND_KDMA(cif);
+
+	writearm(cif, 0x301F8, 1, 1);
+	writearm(cif, 0x301F4, 1, 1);
+
+	SEND_LSEL(cif, MODEM_CMD, 0, 0, MODEM_INTDEC, MODEM_MERGER,
+		  MODEM_SPLITTER, MODEM_MIXER);
+	setmixer(cif, MODEM_MIXER, 0x7fff, 0x7fff);
+	alloclbuspath(cif, ARM2LBUS_FIFO13, lbus_play_modem, NULL, NULL);
+
+	SEND_LSEL(cif, FM_CMD, 0, 0, FM_INTDEC, FM_MERGER, FM_SPLITTER,
+		  FM_MIXER);
+	setmixer(cif, FM_MIXER, 0x7fff, 0x7fff);
+	writearm(cif, 0x30648 + FM_MIXER * 4, 0x01, 0x00000005);
+	writearm(cif, 0x301A8, 0x02, 0x00000002);
+	writearm(cif, 0x30264, 0x08, 0xffffffff);
+	alloclbuspath(cif, OPL3_SAMPLE, lbus_play_opl3, NULL, NULL);
+
+	SEND_SSRC(cif, I2S_INTDEC, 48000,
+		  ((u32) I2S_RATE * 65536) / 48000,
+		  ((u32) I2S_RATE * 65536) % 48000);
+	SEND_LSEL(cif, I2S_CMD0, 0, 0, I2S_INTDEC, I2S_MERGER, I2S_SPLITTER,
+		  I2S_MIXER);
+	SEND_SI2S(cif, 1);
+	alloclbuspath(cif, ARM2LBUS_FIFO0, lbus_play_i2s, NULL, NULL);
+	alloclbuspath(cif, DIGITAL_MIXER_OUT0, lbus_play_out, NULL, NULL);
+	alloclbuspath(cif, DIGITAL_MIXER_OUT0, lbus_play_outhp, NULL, NULL);
+
+	SET_AIACK(cif->hwport);
+	SET_AIE(cif->hwport);
+	SET_AIACK(cif->hwport);
+	cif->is_reset = 1;
+
+	return 0;
+}
+
+static struct snd_pcm_hardware snd_riptide_playback = {
+	.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =
+	    SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8
+	    | SNDRV_PCM_FMTBIT_U16_LE,
+	.rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+	.rate_min = 5500,
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = (64 * 1024),
+	.period_bytes_min = PAGE_SIZE >> 1,
+	.period_bytes_max = PAGE_SIZE << 8,
+	.periods_min = 2,
+	.periods_max = 64,
+	.fifo_size = 0,
+};
+static struct snd_pcm_hardware snd_riptide_capture = {
+	.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =
+	    SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8
+	    | SNDRV_PCM_FMTBIT_U16_LE,
+	.rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
+	.rate_min = 5500,
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = (64 * 1024),
+	.period_bytes_min = PAGE_SIZE >> 1,
+	.period_bytes_max = PAGE_SIZE << 3,
+	.periods_min = 2,
+	.periods_max = 64,
+	.fifo_size = 0,
+};
+
+static snd_pcm_uframes_t snd_riptide_pointer(struct snd_pcm_substream
+					     *substream)
+{
+	struct snd_riptide *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pcmhw *data = get_pcmhwdev(substream);
+	struct cmdif *cif = chip->cif;
+	union cmdret rptr = CMDRET_ZERO;
+	snd_pcm_uframes_t ret;
+
+	SEND_GPOS(cif, 0, data->id, &rptr);
+	if (data->size && runtime->period_size) {
+		snd_printdd
+		    ("pointer stream %d position 0x%x(0x%x in buffer) bytes 0x%lx(0x%lx in period) frames\n",
+		     data->id, rptr.retlongs[1], rptr.retlongs[1] % data->size,
+		     bytes_to_frames(runtime, rptr.retlongs[1]),
+		     bytes_to_frames(runtime,
+				     rptr.retlongs[1]) % runtime->period_size);
+		if (rptr.retlongs[1] > data->pointer)
+			ret =
+			    bytes_to_frames(runtime,
+					    rptr.retlongs[1] % data->size);
+		else
+			ret =
+			    bytes_to_frames(runtime,
+					    data->pointer % data->size);
+	} else {
+		snd_printdd("stream not started or strange parms (%d %ld)\n",
+			    data->size, runtime->period_size);
+		ret = bytes_to_frames(runtime, 0);
+	}
+	return ret;
+}
+
+static int snd_riptide_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	int i, j;
+	struct snd_riptide *chip = snd_pcm_substream_chip(substream);
+	struct pcmhw *data = get_pcmhwdev(substream);
+	struct cmdif *cif = chip->cif;
+	union cmdret rptr = CMDRET_ZERO;
+
+	spin_lock(&chip->lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (!(data->state & ST_PLAY)) {
+			SEND_SSTR(cif, data->id, data->sgdlist.addr);
+			SET_AIE(cif->hwport);
+			data->state = ST_PLAY;
+			if (data->mixer != 0xff)
+				setmixer(cif, data->mixer, 0x7fff, 0x7fff);
+			chip->openstreams++;
+			data->oldpos = 0;
+			data->pointer = 0;
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		if (data->mixer != 0xff)
+			setmixer(cif, data->mixer, 0, 0);
+		setmixer(cif, data->mixer, 0, 0);
+		SEND_KSTR(cif, data->id);
+		data->state = ST_STOP;
+		chip->openstreams--;
+		j = 0;
+		do {
+			i = rptr.retlongs[1];
+			SEND_GPOS(cif, 0, data->id, &rptr);
+			udelay(1);
+		} while (i != rptr.retlongs[1] && j++ < MAX_WRITE_RETRY);
+		if (j > MAX_WRITE_RETRY)
+			snd_printk(KERN_ERR "Riptide: Could not stop stream!");
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (!(data->state & ST_PAUSE)) {
+			SEND_PSTR(cif, data->id);
+			data->state |= ST_PAUSE;
+			chip->openstreams--;
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (data->state & ST_PAUSE) {
+			SEND_SSTR(cif, data->id, data->sgdlist.addr);
+			data->state &= ~ST_PAUSE;
+			chip->openstreams++;
+		}
+		break;
+	default:
+		spin_unlock(&chip->lock);
+		return -EINVAL;
+	}
+	spin_unlock(&chip->lock);
+	return 0;
+}
+
+static int snd_riptide_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_riptide *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pcmhw *data = get_pcmhwdev(substream);
+	struct cmdif *cif = chip->cif;
+	unsigned char *lbuspath = NULL;
+	unsigned int rate, channels;
+	int err = 0;
+	snd_pcm_format_t format;
+
+	if (snd_BUG_ON(!cif || !data))
+		return -EINVAL;
+
+	snd_printdd("prepare id %d ch: %d f:0x%x r:%d\n", data->id,
+		    runtime->channels, runtime->format, runtime->rate);
+
+	spin_lock_irq(&chip->lock);
+	channels = runtime->channels;
+	format = runtime->format;
+	rate = runtime->rate;
+	switch (channels) {
+	case 1:
+		if (rate == 48000 && format == SNDRV_PCM_FORMAT_S16_LE)
+			lbuspath = data->paths.noconv;
+		else
+			lbuspath = data->paths.mono;
+		break;
+	case 2:
+		if (rate == 48000 && format == SNDRV_PCM_FORMAT_S16_LE)
+			lbuspath = data->paths.noconv;
+		else
+			lbuspath = data->paths.stereo;
+		break;
+	}
+	snd_printdd("use sgdlist at 0x%p\n",
+		    data->sgdlist.area);
+	if (data->sgdlist.area) {
+		unsigned int i, j, size, pages, f, pt, period;
+		struct sgd *c, *p = NULL;
+
+		size = frames_to_bytes(runtime, runtime->buffer_size);
+		period = frames_to_bytes(runtime, runtime->period_size);
+		f = PAGE_SIZE;
+		while ((size + (f >> 1) - 1) <= (f << 7) && (f << 1) > period)
+			f = f >> 1;
+		pages = (size + f - 1) / f;
+		data->size = size;
+		data->pages = pages;
+		snd_printdd
+		    ("create sgd size: 0x%x pages %d of size 0x%x for period 0x%x\n",
+		     size, pages, f, period);
+		pt = 0;
+		j = 0;
+		for (i = 0; i < pages; i++) {
+			unsigned int ofs, addr;
+			c = &data->sgdbuf[i];
+			if (p)
+				p->dwNextLink = cpu_to_le32(data->sgdlist.addr +
+							    (i *
+							     sizeof(struct
+								    sgd)));
+			c->dwNextLink = cpu_to_le32(data->sgdlist.addr);
+			ofs = j << PAGE_SHIFT;
+			addr = snd_pcm_sgbuf_get_addr(substream, ofs) + pt;
+			c->dwSegPtrPhys = cpu_to_le32(addr);
+			pt = (pt + f) % PAGE_SIZE;
+			if (pt == 0)
+				j++;
+			c->dwSegLen = cpu_to_le32(f);
+			c->dwStat_Ctl =
+			    cpu_to_le32(IEOB_ENABLE | IEOS_ENABLE |
+					IEOC_ENABLE);
+			p = c;
+			size -= f;
+		}
+		data->sgdbuf[i].dwSegLen = cpu_to_le32(size);
+	}
+	if (lbuspath && lbuspath != data->lbuspath) {
+		if (data->lbuspath)
+			freelbuspath(cif, data->source, data->lbuspath);
+		alloclbuspath(cif, data->source, lbuspath,
+			      &data->mixer, data->intdec);
+		data->lbuspath = lbuspath;
+		data->rate = 0;
+	}
+	if (data->rate != rate || data->format != format ||
+	    data->channels != channels) {
+		data->rate = rate;
+		data->format = format;
+		data->channels = channels;
+		if (setsampleformat
+		    (cif, data->mixer, data->id, channels, format)
+		    || setsamplerate(cif, data->intdec, rate))
+			err = -EIO;
+	}
+	spin_unlock_irq(&chip->lock);
+	return err;
+}
+
+static int
+snd_riptide_hw_params(struct snd_pcm_substream *substream,
+		      struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_riptide *chip = snd_pcm_substream_chip(substream);
+	struct pcmhw *data = get_pcmhwdev(substream);
+	struct snd_dma_buffer *sgdlist = &data->sgdlist;
+	int err;
+
+	snd_printdd("hw params id %d (sgdlist: 0x%p 0x%lx %d)\n", data->id,
+		    sgdlist->area, (unsigned long)sgdlist->addr,
+		    (int)sgdlist->bytes);
+	if (sgdlist->area)
+		snd_dma_free_pages(sgdlist);
+	if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+				       snd_dma_pci_data(chip->pci),
+				       sizeof(struct sgd) * (DESC_MAX_MASK + 1),
+				       sgdlist)) < 0) {
+		snd_printk(KERN_ERR "Riptide: failed to alloc %d dma bytes\n",
+			   (int)sizeof(struct sgd) * (DESC_MAX_MASK + 1));
+		return err;
+	}
+	data->sgdbuf = (struct sgd *)sgdlist->area;
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int snd_riptide_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_riptide *chip = snd_pcm_substream_chip(substream);
+	struct pcmhw *data = get_pcmhwdev(substream);
+	struct cmdif *cif = chip->cif;
+
+	if (cif && data) {
+		if (data->lbuspath)
+			freelbuspath(cif, data->source, data->lbuspath);
+		data->lbuspath = NULL;
+		data->source = 0xff;
+		data->intdec[0] = 0xff;
+		data->intdec[1] = 0xff;
+
+		if (data->sgdlist.area) {
+			snd_dma_free_pages(&data->sgdlist);
+			data->sgdlist.area = NULL;
+		}
+	}
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_riptide_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_riptide *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pcmhw *data;
+	int sub_num = substream->number;
+
+	chip->playback_substream[sub_num] = substream;
+	runtime->hw = snd_riptide_playback;
+
+	data = kzalloc(sizeof(struct pcmhw), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+	data->paths = lbus_play_paths[sub_num];
+	data->id = play_ids[sub_num];
+	data->source = play_sources[sub_num];
+	data->intdec[0] = 0xff;
+	data->intdec[1] = 0xff;
+	data->state = ST_STOP;
+	runtime->private_data = data;
+	return snd_pcm_hw_constraint_integer(runtime,
+					     SNDRV_PCM_HW_PARAM_PERIODS);
+}
+
+static int snd_riptide_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_riptide *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pcmhw *data;
+
+	chip->capture_substream = substream;
+	runtime->hw = snd_riptide_capture;
+
+	data = kzalloc(sizeof(struct pcmhw), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+	data->paths = lbus_rec_path;
+	data->id = PADC;
+	data->source = ACLNK2PADC;
+	data->intdec[0] = 0xff;
+	data->intdec[1] = 0xff;
+	data->state = ST_STOP;
+	runtime->private_data = data;
+	return snd_pcm_hw_constraint_integer(runtime,
+					     SNDRV_PCM_HW_PARAM_PERIODS);
+}
+
+static int snd_riptide_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_riptide *chip = snd_pcm_substream_chip(substream);
+	struct pcmhw *data = get_pcmhwdev(substream);
+	int sub_num = substream->number;
+
+	substream->runtime->private_data = NULL;
+	chip->playback_substream[sub_num] = NULL;
+	kfree(data);
+	return 0;
+}
+
+static int snd_riptide_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_riptide *chip = snd_pcm_substream_chip(substream);
+	struct pcmhw *data = get_pcmhwdev(substream);
+
+	substream->runtime->private_data = NULL;
+	chip->capture_substream = NULL;
+	kfree(data);
+	return 0;
+}
+
+static struct snd_pcm_ops snd_riptide_playback_ops = {
+	.open = snd_riptide_playback_open,
+	.close = snd_riptide_playback_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_riptide_hw_params,
+	.hw_free = snd_riptide_hw_free,
+	.prepare = snd_riptide_prepare,
+	.page = snd_pcm_sgbuf_ops_page,
+	.trigger = snd_riptide_trigger,
+	.pointer = snd_riptide_pointer,
+};
+static struct snd_pcm_ops snd_riptide_capture_ops = {
+	.open = snd_riptide_capture_open,
+	.close = snd_riptide_capture_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_riptide_hw_params,
+	.hw_free = snd_riptide_hw_free,
+	.prepare = snd_riptide_prepare,
+	.page = snd_pcm_sgbuf_ops_page,
+	.trigger = snd_riptide_trigger,
+	.pointer = snd_riptide_pointer,
+};
+
+static int __devinit
+snd_riptide_pcm(struct snd_riptide *chip, int device, struct snd_pcm **rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err =
+	     snd_pcm_new(chip->card, "RIPTIDE", device, PLAYBACK_SUBSTREAMS, 1,
+			 &pcm)) < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_riptide_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&snd_riptide_capture_ops);
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "RIPTIDE");
+	chip->pcm = pcm;
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+					      snd_dma_pci_data(chip->pci),
+					      64 * 1024, 128 * 1024);
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+static irqreturn_t
+snd_riptide_interrupt(int irq, void *dev_id)
+{
+	struct snd_riptide *chip = dev_id;
+	struct cmdif *cif = chip->cif;
+
+	if (cif) {
+		chip->received_irqs++;
+		if (IS_EOBIRQ(cif->hwport) || IS_EOSIRQ(cif->hwport) ||
+		    IS_EOCIRQ(cif->hwport)) {
+			chip->handled_irqs++;
+			tasklet_schedule(&chip->riptide_tq);
+		}
+		if (chip->rmidi && IS_MPUIRQ(cif->hwport)) {
+			chip->handled_irqs++;
+			snd_mpu401_uart_interrupt(irq,
+						  chip->rmidi->private_data);
+		}
+		SET_AIACK(cif->hwport);
+	}
+	return IRQ_HANDLED;
+}
+
+static void
+snd_riptide_codec_write(struct snd_ac97 *ac97, unsigned short reg,
+			unsigned short val)
+{
+	struct snd_riptide *chip = ac97->private_data;
+	struct cmdif *cif = chip->cif;
+	union cmdret rptr = CMDRET_ZERO;
+	int i = 0;
+
+	if (snd_BUG_ON(!cif))
+		return;
+
+	snd_printdd("Write AC97 reg 0x%x 0x%x\n", reg, val);
+	do {
+		SEND_SACR(cif, val, reg);
+		SEND_RACR(cif, reg, &rptr);
+	} while (rptr.retwords[1] != val && i++ < MAX_WRITE_RETRY);
+	if (i > MAX_WRITE_RETRY)
+		snd_printdd("Write AC97 reg failed\n");
+}
+
+static unsigned short snd_riptide_codec_read(struct snd_ac97 *ac97,
+					     unsigned short reg)
+{
+	struct snd_riptide *chip = ac97->private_data;
+	struct cmdif *cif = chip->cif;
+	union cmdret rptr = CMDRET_ZERO;
+
+	if (snd_BUG_ON(!cif))
+		return 0;
+
+	if (SEND_RACR(cif, reg, &rptr) != 0)
+		SEND_RACR(cif, reg, &rptr);
+	snd_printdd("Read AC97 reg 0x%x got 0x%x\n", reg, rptr.retwords[1]);
+	return rptr.retwords[1];
+}
+
+static int snd_riptide_initialize(struct snd_riptide *chip)
+{
+	struct cmdif *cif;
+	unsigned int device_id;
+	int err;
+
+	if (snd_BUG_ON(!chip))
+		return -EINVAL;
+
+	cif = chip->cif;
+	if (!cif) {
+		if ((cif = kzalloc(sizeof(struct cmdif), GFP_KERNEL)) == NULL)
+			return -ENOMEM;
+		cif->hwport = (struct riptideport *)chip->port;
+		spin_lock_init(&cif->lock);
+		chip->cif = cif;
+	}
+	cif->is_reset = 0;
+	if ((err = riptide_reset(cif, chip)) != 0)
+		return err;
+	device_id = chip->device_id;
+	switch (device_id) {
+	case 0x4310:
+	case 0x4320:
+	case 0x4330:
+		snd_printdd("Modem enable?\n");
+		SEND_SETDPLL(cif);
+		break;
+	}
+	snd_printdd("Enabling MPU IRQs\n");
+	if (chip->rmidi)
+		SET_EMPUIRQ(cif->hwport);
+	return err;
+}
+
+static int snd_riptide_free(struct snd_riptide *chip)
+{
+	struct cmdif *cif;
+
+	if (!chip)
+		return 0;
+
+	if ((cif = chip->cif)) {
+		SET_GRESET(cif->hwport);
+		udelay(100);
+		UNSET_GRESET(cif->hwport);
+		kfree(chip->cif);
+	}
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	if (chip->fw_entry)
+		release_firmware(chip->fw_entry);
+	release_and_free_resource(chip->res_port);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_riptide_dev_free(struct snd_device *device)
+{
+	struct snd_riptide *chip = device->device_data;
+
+	return snd_riptide_free(chip);
+}
+
+static int __devinit
+snd_riptide_create(struct snd_card *card, struct pci_dev *pci,
+		   struct snd_riptide **rchip)
+{
+	struct snd_riptide *chip;
+	struct riptideport *hwport;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free = snd_riptide_dev_free,
+	};
+
+	*rchip = NULL;
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	if (!(chip = kzalloc(sizeof(struct snd_riptide), GFP_KERNEL)))
+		return -ENOMEM;
+
+	spin_lock_init(&chip->lock);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	chip->openstreams = 0;
+	chip->port = pci_resource_start(pci, 0);
+	chip->received_irqs = 0;
+	chip->handled_irqs = 0;
+	chip->cif = NULL;
+	tasklet_init(&chip->riptide_tq, riptide_handleirq, (unsigned long)chip);
+
+	if ((chip->res_port =
+	     request_region(chip->port, 64, "RIPTIDE")) == NULL) {
+		snd_printk(KERN_ERR
+			   "Riptide: unable to grab region 0x%lx-0x%lx\n",
+			   chip->port, chip->port + 64 - 1);
+		snd_riptide_free(chip);
+		return -EBUSY;
+	}
+	hwport = (struct riptideport *)chip->port;
+	UNSET_AIE(hwport);
+
+	if (request_irq(pci->irq, snd_riptide_interrupt, IRQF_SHARED,
+			"RIPTIDE", chip)) {
+		snd_printk(KERN_ERR "Riptide: unable to grab IRQ %d\n",
+			   pci->irq);
+		snd_riptide_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+	chip->device_id = pci->device;
+	pci_set_master(pci);
+	if ((err = snd_riptide_initialize(chip)) < 0) {
+		snd_riptide_free(chip);
+		return err;
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_riptide_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rchip = chip;
+	return 0;
+}
+
+static void
+snd_riptide_proc_read(struct snd_info_entry *entry,
+		      struct snd_info_buffer *buffer)
+{
+	struct snd_riptide *chip = entry->private_data;
+	struct pcmhw *data;
+	int i;
+	struct cmdif *cif = NULL;
+	unsigned char p[256];
+	unsigned short rval = 0, lval = 0;
+	unsigned int rate;
+
+	if (!chip)
+		return;
+
+	snd_iprintf(buffer, "%s\n\n", chip->card->longname);
+	snd_iprintf(buffer, "Device ID: 0x%x\nReceived IRQs: (%ld)%ld\nPorts:",
+		    chip->device_id, chip->handled_irqs, chip->received_irqs);
+	for (i = 0; i < 64; i += 4)
+		snd_iprintf(buffer, "%c%02x: %08x",
+			    (i % 16) ? ' ' : '\n', i, inl(chip->port + i));
+	if ((cif = chip->cif)) {
+		snd_iprintf(buffer,
+			    "\nVersion: ASIC: %d CODEC: %d AUXDSP: %d PROG: %d",
+			    chip->firmware.firmware.ASIC,
+			    chip->firmware.firmware.CODEC,
+			    chip->firmware.firmware.AUXDSP,
+			    chip->firmware.firmware.PROG);
+		snd_iprintf(buffer, "\nDigital mixer:");
+		for (i = 0; i < 12; i++) {
+			getmixer(cif, i, &rval, &lval);
+			snd_iprintf(buffer, "\n %d: %d %d", i, rval, lval);
+		}
+		snd_iprintf(buffer,
+			    "\nARM Commands num: %d failed: %d time: %d max: %d min: %d",
+			    cif->cmdcnt, cif->errcnt,
+			    cif->cmdtime, cif->cmdtimemax, cif->cmdtimemin);
+	}
+	snd_iprintf(buffer, "\nOpen streams %d:\n", chip->openstreams);
+	for (i = 0; i < PLAYBACK_SUBSTREAMS; i++) {
+		if (chip->playback_substream[i]
+		    && chip->playback_substream[i]->runtime
+		    && (data =
+			chip->playback_substream[i]->runtime->private_data)) {
+			snd_iprintf(buffer,
+				    "stream: %d mixer: %d source: %d (%d,%d)\n",
+				    data->id, data->mixer, data->source,
+				    data->intdec[0], data->intdec[1]);
+			if (!(getsamplerate(cif, data->intdec, &rate)))
+				snd_iprintf(buffer, "rate: %d\n", rate);
+		}
+	}
+	if (chip->capture_substream
+	    && chip->capture_substream->runtime
+	    && (data = chip->capture_substream->runtime->private_data)) {
+		snd_iprintf(buffer,
+			    "stream: %d mixer: %d source: %d (%d,%d)\n",
+			    data->id, data->mixer,
+			    data->source, data->intdec[0], data->intdec[1]);
+		if (!(getsamplerate(cif, data->intdec, &rate)))
+			snd_iprintf(buffer, "rate: %d\n", rate);
+	}
+	snd_iprintf(buffer, "Paths:\n");
+	i = getpaths(cif, p);
+	while (i >= 2) {
+		i -= 2;
+		snd_iprintf(buffer, "%x->%x ", p[i], p[i + 1]);
+	}
+	snd_iprintf(buffer, "\n");
+}
+
+static void __devinit snd_riptide_proc_init(struct snd_riptide *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(chip->card, "riptide", &entry))
+		snd_info_set_text_ops(entry, chip, snd_riptide_proc_read);
+}
+
+static int __devinit snd_riptide_mixer(struct snd_riptide *chip)
+{
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	int err = 0;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_riptide_codec_write,
+		.read = snd_riptide_codec_read,
+	};
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.scaps = AC97_SCAP_SKIP_MODEM;
+
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &pbus)) < 0)
+		return err;
+
+	chip->ac97_bus = pbus;
+	ac97.pci = chip->pci;
+	if ((err = snd_ac97_mixer(pbus, &ac97, &chip->ac97)) < 0)
+		return err;
+	return err;
+}
+
+#ifdef SUPPORT_JOYSTICK
+
+static int __devinit
+snd_riptide_joystick_probe(struct pci_dev *pci, const struct pci_device_id *id)
+{
+	static int dev;
+	struct gameport *gameport;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	if (!joystick_port[dev++])
+		return 0;
+
+	gameport = gameport_allocate_port();
+	if (!gameport)
+		return -ENOMEM;
+	if (!request_region(joystick_port[dev], 8, "Riptide gameport")) {
+		snd_printk(KERN_WARNING
+			   "Riptide: cannot grab gameport 0x%x\n",
+			   joystick_port[dev]);
+		gameport_free_port(gameport);
+		return -EBUSY;
+	}
+
+	gameport->io = joystick_port[dev];
+	gameport_register_port(gameport);
+	pci_set_drvdata(pci, gameport);
+	return 0;
+}
+
+static void __devexit snd_riptide_joystick_remove(struct pci_dev *pci)
+{
+	struct gameport *gameport = pci_get_drvdata(pci);
+	if (gameport) {
+		release_region(gameport->io, 8);
+		gameport_unregister_port(gameport);
+		pci_set_drvdata(pci, NULL);
+	}
+}
+#endif
+
+static int __devinit
+snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_riptide *chip;
+	unsigned short val;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+	err = snd_riptide_create(card, pci, &chip);
+	if (err < 0)
+		goto error;
+	card->private_data = chip;
+	err = snd_riptide_pcm(chip, 0, NULL);
+	if (err < 0)
+		goto error;
+	err = snd_riptide_mixer(chip);
+	if (err < 0)
+		goto error;
+
+	val = LEGACY_ENABLE_ALL;
+	if (opl3_port[dev])
+		val |= LEGACY_ENABLE_FM;
+#ifdef SUPPORT_JOYSTICK
+	if (joystick_port[dev])
+		val |= LEGACY_ENABLE_GAMEPORT;
+#endif
+	if (mpu_port[dev])
+		val |= LEGACY_ENABLE_MPU_INT | LEGACY_ENABLE_MPU;
+	val |= (chip->irq << 4) & 0xf0;
+	pci_write_config_word(chip->pci, PCI_EXT_Legacy_Mask, val);
+	if (mpu_port[dev]) {
+		val = mpu_port[dev];
+		pci_write_config_word(chip->pci, PCI_EXT_MPU_Base, val);
+		err = snd_mpu401_uart_new(card, 0, MPU401_HW_RIPTIDE,
+					  val, 0, chip->irq, 0,
+					  &chip->rmidi);
+		if (err < 0)
+			snd_printk(KERN_WARNING
+				   "Riptide: Can't Allocate MPU at 0x%x\n",
+				   val);
+		else
+			chip->mpuaddr = val;
+	}
+	if (opl3_port[dev]) {
+		val = opl3_port[dev];
+		pci_write_config_word(chip->pci, PCI_EXT_FM_Base, val);
+		err = snd_opl3_create(card, val, val + 2,
+				      OPL3_HW_RIPTIDE, 0, &chip->opl3);
+		if (err < 0)
+			snd_printk(KERN_WARNING
+				   "Riptide: Can't Allocate OPL3 at 0x%x\n",
+				   val);
+		else {
+			chip->opladdr = val;
+			err = snd_opl3_hwdep_new(chip->opl3, 0, 1, NULL);
+			if (err < 0)
+				snd_printk(KERN_WARNING
+					   "Riptide: Can't Allocate OPL3-HWDEP\n");
+		}
+	}
+#ifdef SUPPORT_JOYSTICK
+	if (joystick_port[dev]) {
+		val = joystick_port[dev];
+		pci_write_config_word(chip->pci, PCI_EXT_Game_Base, val);
+		chip->gameaddr = val;
+	}
+#endif
+
+	strcpy(card->driver, "RIPTIDE");
+	strcpy(card->shortname, "Riptide");
+#ifdef SUPPORT_JOYSTICK
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s at 0x%lx, irq %i mpu 0x%x opl3 0x%x gameport 0x%x",
+		 card->shortname, chip->port, chip->irq, chip->mpuaddr,
+		 chip->opladdr, chip->gameaddr);
+#else
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s at 0x%lx, irq %i mpu 0x%x opl3 0x%x",
+		 card->shortname, chip->port, chip->irq, chip->mpuaddr,
+		 chip->opladdr);
+#endif
+	snd_riptide_proc_init(chip);
+	err = snd_card_register(card);
+	if (err < 0)
+		goto error;
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+
+ error:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit snd_card_riptide_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "RIPTIDE",
+	.id_table = snd_riptide_ids,
+	.probe = snd_card_riptide_probe,
+	.remove = __devexit_p(snd_card_riptide_remove),
+#ifdef CONFIG_PM
+	.suspend = riptide_suspend,
+	.resume = riptide_resume,
+#endif
+};
+
+#ifdef SUPPORT_JOYSTICK
+static struct pci_driver joystick_driver = {
+	.name = "Riptide Joystick",
+	.id_table = snd_riptide_joystick_ids,
+	.probe = snd_riptide_joystick_probe,
+	.remove = __devexit_p(snd_riptide_joystick_remove),
+};
+#endif
+
+static int __init alsa_card_riptide_init(void)
+{
+	int err;
+	err = pci_register_driver(&driver);
+	if (err < 0)
+		return err;
+#if defined(SUPPORT_JOYSTICK)
+	err = pci_register_driver(&joystick_driver);
+	/* On failure unregister formerly registered audio driver */
+	if (err < 0)
+		pci_unregister_driver(&driver);
+#endif
+	return err;
+}
+
+static void __exit alsa_card_riptide_exit(void)
+{
+	pci_unregister_driver(&driver);
+#if defined(SUPPORT_JOYSTICK)
+	pci_unregister_driver(&joystick_driver);
+#endif
+}
+
+module_init(alsa_card_riptide_init);
+module_exit(alsa_card_riptide_exit);
diff --git a/linux/sound/pci/rme32.c b/linux/sound/pci/rme32.c
new file mode 100644
index 0000000..3c04524
--- /dev/null
+++ b/linux/sound/pci/rme32.c
@@ -0,0 +1,2005 @@
+/*
+ *   ALSA driver for RME Digi32, Digi32/8 and Digi32 PRO audio interfaces
+ *
+ *      Copyright (c) 2002-2004 Martin Langer <martin-langer@gmx.de>,
+ *                              Pilo Chambert <pilo.c@wanadoo.fr>
+ *
+ *      Thanks to :        Anders Torger <torger@ludd.luth.se>,
+ *                         Henk Hesselink <henk@anda.nl>
+ *                         for writing the digi96-driver 
+ *                         and RME for all informations.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 
+ * 
+ * ****************************************************************************
+ * 
+ * Note #1 "Sek'd models" ................................... martin 2002-12-07
+ * 
+ * Identical soundcards by Sek'd were labeled:
+ * RME Digi 32     = Sek'd Prodif 32
+ * RME Digi 32 Pro = Sek'd Prodif 96
+ * RME Digi 32/8   = Sek'd Prodif Gold
+ * 
+ * ****************************************************************************
+ * 
+ * Note #2 "full duplex mode" ............................... martin 2002-12-07
+ * 
+ * Full duplex doesn't work. All cards (32, 32/8, 32Pro) are working identical
+ * in this mode. Rec data and play data are using the same buffer therefore. At
+ * first you have got the playing bits in the buffer and then (after playing
+ * them) they were overwitten by the captured sound of the CS8412/14. Both 
+ * modes (play/record) are running harmonically hand in hand in the same buffer
+ * and you have only one start bit plus one interrupt bit to control this 
+ * paired action.
+ * This is opposite to the latter rme96 where playing and capturing is totally
+ * separated and so their full duplex mode is supported by alsa (using two 
+ * start bits and two interrupts for two different buffers). 
+ * But due to the wrong sequence of playing and capturing ALSA shows no solved
+ * full duplex support for the rme32 at the moment. That's bad, but I'm not
+ * able to solve it. Are you motivated enough to solve this problem now? Your
+ * patch would be welcome!
+ * 
+ * ****************************************************************************
+ *
+ * "The story after the long seeking" -- tiwai
+ *
+ * Ok, the situation regarding the full duplex is now improved a bit.
+ * In the fullduplex mode (given by the module parameter), the hardware buffer
+ * is split to halves for read and write directions at the DMA pointer.
+ * That is, the half above the current DMA pointer is used for write, and
+ * the half below is used for read.  To mangle this strange behavior, an
+ * software intermediate buffer is introduced.  This is, of course, not good
+ * from the viewpoint of the data transfer efficiency.  However, this allows
+ * you to use arbitrary buffer sizes, instead of the fixed I/O buffer size.
+ *
+ * ****************************************************************************
+ */
+
+
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm-indirect.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static int fullduplex[SNDRV_CARDS]; // = {[0 ... (SNDRV_CARDS - 1)] = 1};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for RME Digi32 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for RME Digi32 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable RME Digi32 soundcard.");
+module_param_array(fullduplex, bool, NULL, 0444);
+MODULE_PARM_DESC(fullduplex, "Support full-duplex mode.");
+MODULE_AUTHOR("Martin Langer <martin-langer@gmx.de>, Pilo Chambert <pilo.c@wanadoo.fr>");
+MODULE_DESCRIPTION("RME Digi32, Digi32/8, Digi32 PRO");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{RME,Digi32}," "{RME,Digi32/8}," "{RME,Digi32 PRO}}");
+
+/* Defines for RME Digi32 series */
+#define RME32_SPDIF_NCHANNELS 2
+
+/* Playback and capture buffer size */
+#define RME32_BUFFER_SIZE 0x20000
+
+/* IO area size */
+#define RME32_IO_SIZE 0x30000
+
+/* IO area offsets */
+#define RME32_IO_DATA_BUFFER        0x0
+#define RME32_IO_CONTROL_REGISTER   0x20000
+#define RME32_IO_GET_POS            0x20000
+#define RME32_IO_CONFIRM_ACTION_IRQ 0x20004
+#define RME32_IO_RESET_POS          0x20100
+
+/* Write control register bits */
+#define RME32_WCR_START     (1 << 0)    /* startbit */
+#define RME32_WCR_MONO      (1 << 1)    /* 0=stereo, 1=mono
+                                           Setting the whole card to mono
+                                           doesn't seem to be very useful.
+                                           A software-solution can handle 
+                                           full-duplex with one direction in
+                                           stereo and the other way in mono. 
+                                           So, the hardware should work all 
+                                           the time in stereo! */
+#define RME32_WCR_MODE24    (1 << 2)    /* 0=16bit, 1=32bit */
+#define RME32_WCR_SEL       (1 << 3)    /* 0=input on output, 1=normal playback/capture */
+#define RME32_WCR_FREQ_0    (1 << 4)    /* frequency (play) */
+#define RME32_WCR_FREQ_1    (1 << 5)
+#define RME32_WCR_INP_0     (1 << 6)    /* input switch */
+#define RME32_WCR_INP_1     (1 << 7)
+#define RME32_WCR_RESET     (1 << 8)    /* Reset address */
+#define RME32_WCR_MUTE      (1 << 9)    /* digital mute for output */
+#define RME32_WCR_PRO       (1 << 10)   /* 1=professional, 0=consumer */
+#define RME32_WCR_DS_BM     (1 << 11)	/* 1=DoubleSpeed (only PRO-Version); 1=BlockMode (only Adat-Version) */
+#define RME32_WCR_ADAT      (1 << 12)	/* Adat Mode (only Adat-Version) */
+#define RME32_WCR_AUTOSYNC  (1 << 13)   /* AutoSync */
+#define RME32_WCR_PD        (1 << 14)	/* DAC Reset (only PRO-Version) */
+#define RME32_WCR_EMP       (1 << 15)	/* 1=Emphasis on (only PRO-Version) */
+
+#define RME32_WCR_BITPOS_FREQ_0 4
+#define RME32_WCR_BITPOS_FREQ_1 5
+#define RME32_WCR_BITPOS_INP_0 6
+#define RME32_WCR_BITPOS_INP_1 7
+
+/* Read control register bits */
+#define RME32_RCR_AUDIO_ADDR_MASK 0x1ffff
+#define RME32_RCR_LOCK      (1 << 23)   /* 1=locked, 0=not locked */
+#define RME32_RCR_ERF       (1 << 26)   /* 1=Error, 0=no Error */
+#define RME32_RCR_FREQ_0    (1 << 27)   /* CS841x frequency (record) */
+#define RME32_RCR_FREQ_1    (1 << 28)
+#define RME32_RCR_FREQ_2    (1 << 29)
+#define RME32_RCR_KMODE     (1 << 30)   /* card mode: 1=PLL, 0=quartz */
+#define RME32_RCR_IRQ       (1 << 31)   /* interrupt */
+
+#define RME32_RCR_BITPOS_F0 27
+#define RME32_RCR_BITPOS_F1 28
+#define RME32_RCR_BITPOS_F2 29
+
+/* Input types */
+#define RME32_INPUT_OPTICAL 0
+#define RME32_INPUT_COAXIAL 1
+#define RME32_INPUT_INTERNAL 2
+#define RME32_INPUT_XLR 3
+
+/* Clock modes */
+#define RME32_CLOCKMODE_SLAVE 0
+#define RME32_CLOCKMODE_MASTER_32 1
+#define RME32_CLOCKMODE_MASTER_44 2
+#define RME32_CLOCKMODE_MASTER_48 3
+
+/* Block sizes in bytes */
+#define RME32_BLOCK_SIZE 8192
+
+/* Software intermediate buffer (max) size */
+#define RME32_MID_BUFFER_SIZE (1024*1024)
+
+/* Hardware revisions */
+#define RME32_32_REVISION 192
+#define RME32_328_REVISION_OLD 100
+#define RME32_328_REVISION_NEW 101
+#define RME32_PRO_REVISION_WITH_8412 192
+#define RME32_PRO_REVISION_WITH_8414 150
+
+
+struct rme32 {
+	spinlock_t lock;
+	int irq;
+	unsigned long port;
+	void __iomem *iobase;
+
+	u32 wcreg;		/* cached write control register value */
+	u32 wcreg_spdif;	/* S/PDIF setup */
+	u32 wcreg_spdif_stream;	/* S/PDIF setup (temporary) */
+	u32 rcreg;		/* cached read control register value */
+
+	u8 rev;			/* card revision number */
+
+	struct snd_pcm_substream *playback_substream;
+	struct snd_pcm_substream *capture_substream;
+
+	int playback_frlog;	/* log2 of framesize */
+	int capture_frlog;
+
+	size_t playback_periodsize;	/* in bytes, zero if not used */
+	size_t capture_periodsize;	/* in bytes, zero if not used */
+
+	unsigned int fullduplex_mode;
+	int running;
+
+	struct snd_pcm_indirect playback_pcm;
+	struct snd_pcm_indirect capture_pcm;
+
+	struct snd_card *card;
+	struct snd_pcm *spdif_pcm;
+	struct snd_pcm *adat_pcm;
+	struct pci_dev *pci;
+	struct snd_kcontrol *spdif_ctl;
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_rme32_ids) = {
+	{PCI_VDEVICE(XILINX_RME, PCI_DEVICE_ID_RME_DIGI32), 0,},
+	{PCI_VDEVICE(XILINX_RME, PCI_DEVICE_ID_RME_DIGI32_8), 0,},
+	{PCI_VDEVICE(XILINX_RME, PCI_DEVICE_ID_RME_DIGI32_PRO), 0,},
+	{0,}
+};
+
+MODULE_DEVICE_TABLE(pci, snd_rme32_ids);
+
+#define RME32_ISWORKING(rme32) ((rme32)->wcreg & RME32_WCR_START)
+#define RME32_PRO_WITH_8414(rme32) ((rme32)->pci->device == PCI_DEVICE_ID_RME_DIGI32_PRO && (rme32)->rev == RME32_PRO_REVISION_WITH_8414)
+
+static int snd_rme32_playback_prepare(struct snd_pcm_substream *substream);
+
+static int snd_rme32_capture_prepare(struct snd_pcm_substream *substream);
+
+static int snd_rme32_pcm_trigger(struct snd_pcm_substream *substream, int cmd);
+
+static void snd_rme32_proc_init(struct rme32 * rme32);
+
+static int snd_rme32_create_switches(struct snd_card *card, struct rme32 * rme32);
+
+static inline unsigned int snd_rme32_pcm_byteptr(struct rme32 * rme32)
+{
+	return (readl(rme32->iobase + RME32_IO_GET_POS)
+		& RME32_RCR_AUDIO_ADDR_MASK);
+}
+
+/* silence callback for halfduplex mode */
+static int snd_rme32_playback_silence(struct snd_pcm_substream *substream, int channel,	/* not used (interleaved data) */
+				      snd_pcm_uframes_t pos,
+				      snd_pcm_uframes_t count)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	count <<= rme32->playback_frlog;
+	pos <<= rme32->playback_frlog;
+	memset_io(rme32->iobase + RME32_IO_DATA_BUFFER + pos, 0, count);
+	return 0;
+}
+
+/* copy callback for halfduplex mode */
+static int snd_rme32_playback_copy(struct snd_pcm_substream *substream, int channel,	/* not used (interleaved data) */
+				   snd_pcm_uframes_t pos,
+				   void __user *src, snd_pcm_uframes_t count)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	count <<= rme32->playback_frlog;
+	pos <<= rme32->playback_frlog;
+	if (copy_from_user_toio(rme32->iobase + RME32_IO_DATA_BUFFER + pos,
+			    src, count))
+		return -EFAULT;
+	return 0;
+}
+
+/* copy callback for halfduplex mode */
+static int snd_rme32_capture_copy(struct snd_pcm_substream *substream, int channel,	/* not used (interleaved data) */
+				  snd_pcm_uframes_t pos,
+				  void __user *dst, snd_pcm_uframes_t count)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	count <<= rme32->capture_frlog;
+	pos <<= rme32->capture_frlog;
+	if (copy_to_user_fromio(dst,
+			    rme32->iobase + RME32_IO_DATA_BUFFER + pos,
+			    count))
+		return -EFAULT;
+	return 0;
+}
+
+/*
+ * SPDIF I/O capabilities (half-duplex mode)
+ */
+static struct snd_pcm_hardware snd_rme32_spdif_info = {
+	.info =		(SNDRV_PCM_INFO_MMAP_IOMEM |
+			 SNDRV_PCM_INFO_MMAP_VALID |
+			 SNDRV_PCM_INFO_INTERLEAVED | 
+			 SNDRV_PCM_INFO_PAUSE |
+			 SNDRV_PCM_INFO_SYNC_START),
+	.formats =	(SNDRV_PCM_FMTBIT_S16_LE | 
+			 SNDRV_PCM_FMTBIT_S32_LE),
+	.rates =	(SNDRV_PCM_RATE_32000 |
+			 SNDRV_PCM_RATE_44100 | 
+			 SNDRV_PCM_RATE_48000),
+	.rate_min =	32000,
+	.rate_max =	48000,
+	.channels_min =	2,
+	.channels_max =	2,
+	.buffer_bytes_max = RME32_BUFFER_SIZE,
+	.period_bytes_min = RME32_BLOCK_SIZE,
+	.period_bytes_max = RME32_BLOCK_SIZE,
+	.periods_min =	RME32_BUFFER_SIZE / RME32_BLOCK_SIZE,
+	.periods_max =	RME32_BUFFER_SIZE / RME32_BLOCK_SIZE,
+	.fifo_size =	0,
+};
+
+/*
+ * ADAT I/O capabilities (half-duplex mode)
+ */
+static struct snd_pcm_hardware snd_rme32_adat_info =
+{
+	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
+			      SNDRV_PCM_INFO_MMAP_VALID |
+			      SNDRV_PCM_INFO_INTERLEAVED |
+			      SNDRV_PCM_INFO_PAUSE |
+			      SNDRV_PCM_INFO_SYNC_START),
+	.formats=            SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =             (SNDRV_PCM_RATE_44100 | 
+			      SNDRV_PCM_RATE_48000),
+	.rate_min =          44100,
+	.rate_max =          48000,
+	.channels_min =      8,
+	.channels_max =	     8,
+	.buffer_bytes_max =  RME32_BUFFER_SIZE,
+	.period_bytes_min =  RME32_BLOCK_SIZE,
+	.period_bytes_max =  RME32_BLOCK_SIZE,
+	.periods_min =	    RME32_BUFFER_SIZE / RME32_BLOCK_SIZE,
+	.periods_max =	    RME32_BUFFER_SIZE / RME32_BLOCK_SIZE,
+	.fifo_size =	    0,
+};
+
+/*
+ * SPDIF I/O capabilities (full-duplex mode)
+ */
+static struct snd_pcm_hardware snd_rme32_spdif_fd_info = {
+	.info =		(SNDRV_PCM_INFO_MMAP |
+			 SNDRV_PCM_INFO_MMAP_VALID |
+			 SNDRV_PCM_INFO_INTERLEAVED | 
+			 SNDRV_PCM_INFO_PAUSE |
+			 SNDRV_PCM_INFO_SYNC_START),
+	.formats =	(SNDRV_PCM_FMTBIT_S16_LE | 
+			 SNDRV_PCM_FMTBIT_S32_LE),
+	.rates =	(SNDRV_PCM_RATE_32000 |
+			 SNDRV_PCM_RATE_44100 | 
+			 SNDRV_PCM_RATE_48000),
+	.rate_min =	32000,
+	.rate_max =	48000,
+	.channels_min =	2,
+	.channels_max =	2,
+	.buffer_bytes_max = RME32_MID_BUFFER_SIZE,
+	.period_bytes_min = RME32_BLOCK_SIZE,
+	.period_bytes_max = RME32_BLOCK_SIZE,
+	.periods_min =	2,
+	.periods_max =	RME32_MID_BUFFER_SIZE / RME32_BLOCK_SIZE,
+	.fifo_size =	0,
+};
+
+/*
+ * ADAT I/O capabilities (full-duplex mode)
+ */
+static struct snd_pcm_hardware snd_rme32_adat_fd_info =
+{
+	.info =		     (SNDRV_PCM_INFO_MMAP |
+			      SNDRV_PCM_INFO_MMAP_VALID |
+			      SNDRV_PCM_INFO_INTERLEAVED |
+			      SNDRV_PCM_INFO_PAUSE |
+			      SNDRV_PCM_INFO_SYNC_START),
+	.formats=            SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =             (SNDRV_PCM_RATE_44100 | 
+			      SNDRV_PCM_RATE_48000),
+	.rate_min =          44100,
+	.rate_max =          48000,
+	.channels_min =      8,
+	.channels_max =	     8,
+	.buffer_bytes_max =  RME32_MID_BUFFER_SIZE,
+	.period_bytes_min =  RME32_BLOCK_SIZE,
+	.period_bytes_max =  RME32_BLOCK_SIZE,
+	.periods_min =	    2,
+	.periods_max =	    RME32_MID_BUFFER_SIZE / RME32_BLOCK_SIZE,
+	.fifo_size =	    0,
+};
+
+static void snd_rme32_reset_dac(struct rme32 *rme32)
+{
+        writel(rme32->wcreg | RME32_WCR_PD,
+               rme32->iobase + RME32_IO_CONTROL_REGISTER);
+        writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+}
+
+static int snd_rme32_playback_getrate(struct rme32 * rme32)
+{
+	int rate;
+
+	rate = ((rme32->wcreg >> RME32_WCR_BITPOS_FREQ_0) & 1) +
+	       (((rme32->wcreg >> RME32_WCR_BITPOS_FREQ_1) & 1) << 1);
+	switch (rate) {
+	case 1:
+		rate = 32000;
+		break;
+	case 2:
+		rate = 44100;
+		break;
+	case 3:
+		rate = 48000;
+		break;
+	default:
+		return -1;
+	}
+	return (rme32->wcreg & RME32_WCR_DS_BM) ? rate << 1 : rate;
+}
+
+static int snd_rme32_capture_getrate(struct rme32 * rme32, int *is_adat)
+{
+	int n;
+
+	*is_adat = 0;
+	if (rme32->rcreg & RME32_RCR_LOCK) { 
+                /* ADAT rate */
+                *is_adat = 1;
+	}
+	if (rme32->rcreg & RME32_RCR_ERF) {
+		return -1;
+	}
+
+        /* S/PDIF rate */
+	n = ((rme32->rcreg >> RME32_RCR_BITPOS_F0) & 1) +
+		(((rme32->rcreg >> RME32_RCR_BITPOS_F1) & 1) << 1) +
+		(((rme32->rcreg >> RME32_RCR_BITPOS_F2) & 1) << 2);
+
+	if (RME32_PRO_WITH_8414(rme32))
+		switch (n) {	/* supporting the CS8414 */
+		case 0:
+		case 1:
+		case 2:
+			return -1;
+		case 3:
+			return 96000;
+		case 4:
+			return 88200;
+		case 5:
+			return 48000;
+		case 6:
+			return 44100;
+		case 7:
+			return 32000;
+		default:
+			return -1;
+			break;
+		} 
+	else
+		switch (n) {	/* supporting the CS8412 */
+		case 0:
+			return -1;
+		case 1:
+			return 48000;
+		case 2:
+			return 44100;
+		case 3:
+			return 32000;
+		case 4:
+			return 48000;
+		case 5:
+			return 44100;
+		case 6:
+			return 44056;
+		case 7:
+			return 32000;
+		default:
+			break;
+		}
+	return -1;
+}
+
+static int snd_rme32_playback_setrate(struct rme32 * rme32, int rate)
+{
+        int ds;
+
+        ds = rme32->wcreg & RME32_WCR_DS_BM;
+	switch (rate) {
+	case 32000:
+		rme32->wcreg &= ~RME32_WCR_DS_BM;
+		rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) & 
+			~RME32_WCR_FREQ_1;
+		break;
+	case 44100:
+		rme32->wcreg &= ~RME32_WCR_DS_BM;
+		rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_1) & 
+			~RME32_WCR_FREQ_0;
+		break;
+	case 48000:
+		rme32->wcreg &= ~RME32_WCR_DS_BM;
+		rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) | 
+			RME32_WCR_FREQ_1;
+		break;
+	case 64000:
+		if (rme32->pci->device != PCI_DEVICE_ID_RME_DIGI32_PRO)
+			return -EINVAL;
+		rme32->wcreg |= RME32_WCR_DS_BM;
+		rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) & 
+			~RME32_WCR_FREQ_1;
+		break;
+	case 88200:
+		if (rme32->pci->device != PCI_DEVICE_ID_RME_DIGI32_PRO)
+			return -EINVAL;
+		rme32->wcreg |= RME32_WCR_DS_BM;
+		rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_1) & 
+			~RME32_WCR_FREQ_0;
+		break;
+	case 96000:
+		if (rme32->pci->device != PCI_DEVICE_ID_RME_DIGI32_PRO)
+			return -EINVAL;
+		rme32->wcreg |= RME32_WCR_DS_BM;
+		rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) | 
+			RME32_WCR_FREQ_1;
+		break;
+	default:
+		return -EINVAL;
+	}
+        if ((!ds && rme32->wcreg & RME32_WCR_DS_BM) ||
+            (ds && !(rme32->wcreg & RME32_WCR_DS_BM)))
+        {
+                /* change to/from double-speed: reset the DAC (if available) */
+                snd_rme32_reset_dac(rme32);
+        } else {
+                writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	}
+	return 0;
+}
+
+static int snd_rme32_setclockmode(struct rme32 * rme32, int mode)
+{
+	switch (mode) {
+	case RME32_CLOCKMODE_SLAVE:
+		/* AutoSync */
+		rme32->wcreg = (rme32->wcreg & ~RME32_WCR_FREQ_0) & 
+			~RME32_WCR_FREQ_1;
+		break;
+	case RME32_CLOCKMODE_MASTER_32:
+		/* Internal 32.0kHz */
+		rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) & 
+			~RME32_WCR_FREQ_1;
+		break;
+	case RME32_CLOCKMODE_MASTER_44:
+		/* Internal 44.1kHz */
+		rme32->wcreg = (rme32->wcreg & ~RME32_WCR_FREQ_0) | 
+			RME32_WCR_FREQ_1;
+		break;
+	case RME32_CLOCKMODE_MASTER_48:
+		/* Internal 48.0kHz */
+		rme32->wcreg = (rme32->wcreg | RME32_WCR_FREQ_0) | 
+			RME32_WCR_FREQ_1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	return 0;
+}
+
+static int snd_rme32_getclockmode(struct rme32 * rme32)
+{
+	return ((rme32->wcreg >> RME32_WCR_BITPOS_FREQ_0) & 1) +
+	    (((rme32->wcreg >> RME32_WCR_BITPOS_FREQ_1) & 1) << 1);
+}
+
+static int snd_rme32_setinputtype(struct rme32 * rme32, int type)
+{
+	switch (type) {
+	case RME32_INPUT_OPTICAL:
+		rme32->wcreg = (rme32->wcreg & ~RME32_WCR_INP_0) & 
+			~RME32_WCR_INP_1;
+		break;
+	case RME32_INPUT_COAXIAL:
+		rme32->wcreg = (rme32->wcreg | RME32_WCR_INP_0) & 
+			~RME32_WCR_INP_1;
+		break;
+	case RME32_INPUT_INTERNAL:
+		rme32->wcreg = (rme32->wcreg & ~RME32_WCR_INP_0) | 
+			RME32_WCR_INP_1;
+		break;
+	case RME32_INPUT_XLR:
+		rme32->wcreg = (rme32->wcreg | RME32_WCR_INP_0) | 
+			RME32_WCR_INP_1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	return 0;
+}
+
+static int snd_rme32_getinputtype(struct rme32 * rme32)
+{
+	return ((rme32->wcreg >> RME32_WCR_BITPOS_INP_0) & 1) +
+	    (((rme32->wcreg >> RME32_WCR_BITPOS_INP_1) & 1) << 1);
+}
+
+static void
+snd_rme32_setframelog(struct rme32 * rme32, int n_channels, int is_playback)
+{
+	int frlog;
+
+	if (n_channels == 2) {
+		frlog = 1;
+	} else {
+		/* assume 8 channels */
+		frlog = 3;
+	}
+	if (is_playback) {
+		frlog += (rme32->wcreg & RME32_WCR_MODE24) ? 2 : 1;
+		rme32->playback_frlog = frlog;
+	} else {
+		frlog += (rme32->wcreg & RME32_WCR_MODE24) ? 2 : 1;
+		rme32->capture_frlog = frlog;
+	}
+}
+
+static int snd_rme32_setformat(struct rme32 * rme32, int format)
+{
+	switch (format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		rme32->wcreg &= ~RME32_WCR_MODE24;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		rme32->wcreg |= RME32_WCR_MODE24;
+		break;
+	default:
+		return -EINVAL;
+	}
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	return 0;
+}
+
+static int
+snd_rme32_playback_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params)
+{
+	int err, rate, dummy;
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (rme32->fullduplex_mode) {
+		err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+		if (err < 0)
+			return err;
+	} else {
+		runtime->dma_area = (void __force *)(rme32->iobase +
+						     RME32_IO_DATA_BUFFER);
+		runtime->dma_addr = rme32->port + RME32_IO_DATA_BUFFER;
+		runtime->dma_bytes = RME32_BUFFER_SIZE;
+	}
+
+	spin_lock_irq(&rme32->lock);
+	if ((rme32->rcreg & RME32_RCR_KMODE) &&
+	    (rate = snd_rme32_capture_getrate(rme32, &dummy)) > 0) {
+		/* AutoSync */
+		if ((int)params_rate(params) != rate) {
+			spin_unlock_irq(&rme32->lock);
+			return -EIO;
+		}
+	} else if ((err = snd_rme32_playback_setrate(rme32, params_rate(params))) < 0) {
+		spin_unlock_irq(&rme32->lock);
+		return err;
+	}
+	if ((err = snd_rme32_setformat(rme32, params_format(params))) < 0) {
+		spin_unlock_irq(&rme32->lock);
+		return err;
+	}
+
+	snd_rme32_setframelog(rme32, params_channels(params), 1);
+	if (rme32->capture_periodsize != 0) {
+		if (params_period_size(params) << rme32->playback_frlog != rme32->capture_periodsize) {
+			spin_unlock_irq(&rme32->lock);
+			return -EBUSY;
+		}
+	}
+	rme32->playback_periodsize = params_period_size(params) << rme32->playback_frlog;
+	/* S/PDIF setup */
+	if ((rme32->wcreg & RME32_WCR_ADAT) == 0) {
+		rme32->wcreg &= ~(RME32_WCR_PRO | RME32_WCR_EMP);
+		rme32->wcreg |= rme32->wcreg_spdif_stream;
+		writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	}
+	spin_unlock_irq(&rme32->lock);
+
+	return 0;
+}
+
+static int
+snd_rme32_capture_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params)
+{
+	int err, isadat, rate;
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (rme32->fullduplex_mode) {
+		err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+		if (err < 0)
+			return err;
+	} else {
+		runtime->dma_area = (void __force *)rme32->iobase +
+					RME32_IO_DATA_BUFFER;
+		runtime->dma_addr = rme32->port + RME32_IO_DATA_BUFFER;
+		runtime->dma_bytes = RME32_BUFFER_SIZE;
+	}
+
+	spin_lock_irq(&rme32->lock);
+	/* enable AutoSync for record-preparing */
+	rme32->wcreg |= RME32_WCR_AUTOSYNC;
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+
+	if ((err = snd_rme32_setformat(rme32, params_format(params))) < 0) {
+		spin_unlock_irq(&rme32->lock);
+		return err;
+	}
+	if ((err = snd_rme32_playback_setrate(rme32, params_rate(params))) < 0) {
+		spin_unlock_irq(&rme32->lock);
+		return err;
+	}
+	if ((rate = snd_rme32_capture_getrate(rme32, &isadat)) > 0) {
+                if ((int)params_rate(params) != rate) {
+			spin_unlock_irq(&rme32->lock);
+                        return -EIO;                    
+                }
+                if ((isadat && runtime->hw.channels_min == 2) ||
+                    (!isadat && runtime->hw.channels_min == 8)) {
+			spin_unlock_irq(&rme32->lock);
+                        return -EIO;
+                }
+	}
+	/* AutoSync off for recording */
+	rme32->wcreg &= ~RME32_WCR_AUTOSYNC;
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+
+	snd_rme32_setframelog(rme32, params_channels(params), 0);
+	if (rme32->playback_periodsize != 0) {
+		if (params_period_size(params) << rme32->capture_frlog !=
+		    rme32->playback_periodsize) {
+			spin_unlock_irq(&rme32->lock);
+			return -EBUSY;
+		}
+	}
+	rme32->capture_periodsize =
+	    params_period_size(params) << rme32->capture_frlog;
+	spin_unlock_irq(&rme32->lock);
+
+	return 0;
+}
+
+static int snd_rme32_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	if (! rme32->fullduplex_mode)
+		return 0;
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static void snd_rme32_pcm_start(struct rme32 * rme32, int from_pause)
+{
+	if (!from_pause) {
+		writel(0, rme32->iobase + RME32_IO_RESET_POS);
+	}
+
+	rme32->wcreg |= RME32_WCR_START;
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+}
+
+static void snd_rme32_pcm_stop(struct rme32 * rme32, int to_pause)
+{
+	/*
+	 * Check if there is an unconfirmed IRQ, if so confirm it, or else
+	 * the hardware will not stop generating interrupts
+	 */
+	rme32->rcreg = readl(rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	if (rme32->rcreg & RME32_RCR_IRQ) {
+		writel(0, rme32->iobase + RME32_IO_CONFIRM_ACTION_IRQ);
+	}
+	rme32->wcreg &= ~RME32_WCR_START;
+	if (rme32->wcreg & RME32_WCR_SEL)
+		rme32->wcreg |= RME32_WCR_MUTE;
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	if (! to_pause)
+		writel(0, rme32->iobase + RME32_IO_RESET_POS);
+}
+
+static irqreturn_t snd_rme32_interrupt(int irq, void *dev_id)
+{
+	struct rme32 *rme32 = (struct rme32 *) dev_id;
+
+	rme32->rcreg = readl(rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	if (!(rme32->rcreg & RME32_RCR_IRQ)) {
+		return IRQ_NONE;
+	} else {
+		if (rme32->capture_substream) {
+			snd_pcm_period_elapsed(rme32->capture_substream);
+		}
+		if (rme32->playback_substream) {
+			snd_pcm_period_elapsed(rme32->playback_substream);
+		}
+		writel(0, rme32->iobase + RME32_IO_CONFIRM_ACTION_IRQ);
+	}
+	return IRQ_HANDLED;
+}
+
+static unsigned int period_bytes[] = { RME32_BLOCK_SIZE };
+
+
+static struct snd_pcm_hw_constraint_list hw_constraints_period_bytes = {
+	.count = ARRAY_SIZE(period_bytes),
+	.list = period_bytes,
+	.mask = 0
+};
+
+static void snd_rme32_set_buffer_constraint(struct rme32 *rme32, struct snd_pcm_runtime *runtime)
+{
+	if (! rme32->fullduplex_mode) {
+		snd_pcm_hw_constraint_minmax(runtime,
+					     SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+					     RME32_BUFFER_SIZE, RME32_BUFFER_SIZE);
+		snd_pcm_hw_constraint_list(runtime, 0,
+					   SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+					   &hw_constraints_period_bytes);
+	}
+}
+
+static int snd_rme32_playback_spdif_open(struct snd_pcm_substream *substream)
+{
+	int rate, dummy;
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	snd_pcm_set_sync(substream);
+
+	spin_lock_irq(&rme32->lock);
+	if (rme32->playback_substream != NULL) {
+		spin_unlock_irq(&rme32->lock);
+		return -EBUSY;
+	}
+	rme32->wcreg &= ~RME32_WCR_ADAT;
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	rme32->playback_substream = substream;
+	spin_unlock_irq(&rme32->lock);
+
+	if (rme32->fullduplex_mode)
+		runtime->hw = snd_rme32_spdif_fd_info;
+	else
+		runtime->hw = snd_rme32_spdif_info;
+	if (rme32->pci->device == PCI_DEVICE_ID_RME_DIGI32_PRO) {
+		runtime->hw.rates |= SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000;
+		runtime->hw.rate_max = 96000;
+	}
+	if ((rme32->rcreg & RME32_RCR_KMODE) &&
+	    (rate = snd_rme32_capture_getrate(rme32, &dummy)) > 0) {
+		/* AutoSync */
+		runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
+		runtime->hw.rate_min = rate;
+		runtime->hw.rate_max = rate;
+	}       
+
+	snd_rme32_set_buffer_constraint(rme32, runtime);
+
+	rme32->wcreg_spdif_stream = rme32->wcreg_spdif;
+	rme32->spdif_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(rme32->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &rme32->spdif_ctl->id);
+	return 0;
+}
+
+static int snd_rme32_capture_spdif_open(struct snd_pcm_substream *substream)
+{
+	int isadat, rate;
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	snd_pcm_set_sync(substream);
+
+	spin_lock_irq(&rme32->lock);
+        if (rme32->capture_substream != NULL) {
+		spin_unlock_irq(&rme32->lock);
+                return -EBUSY;
+        }
+	rme32->capture_substream = substream;
+	spin_unlock_irq(&rme32->lock);
+
+	if (rme32->fullduplex_mode)
+		runtime->hw = snd_rme32_spdif_fd_info;
+	else
+		runtime->hw = snd_rme32_spdif_info;
+	if (RME32_PRO_WITH_8414(rme32)) {
+		runtime->hw.rates |= SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000;
+		runtime->hw.rate_max = 96000;
+	}
+	if ((rate = snd_rme32_capture_getrate(rme32, &isadat)) > 0) {
+		if (isadat) {
+			return -EIO;
+		}
+		runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
+		runtime->hw.rate_min = rate;
+		runtime->hw.rate_max = rate;
+	}
+
+	snd_rme32_set_buffer_constraint(rme32, runtime);
+
+	return 0;
+}
+
+static int
+snd_rme32_playback_adat_open(struct snd_pcm_substream *substream)
+{
+	int rate, dummy;
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	
+	snd_pcm_set_sync(substream);
+
+	spin_lock_irq(&rme32->lock);	
+        if (rme32->playback_substream != NULL) {
+		spin_unlock_irq(&rme32->lock);
+                return -EBUSY;
+        }
+	rme32->wcreg |= RME32_WCR_ADAT;
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	rme32->playback_substream = substream;
+	spin_unlock_irq(&rme32->lock);
+	
+	if (rme32->fullduplex_mode)
+		runtime->hw = snd_rme32_adat_fd_info;
+	else
+		runtime->hw = snd_rme32_adat_info;
+	if ((rme32->rcreg & RME32_RCR_KMODE) &&
+	    (rate = snd_rme32_capture_getrate(rme32, &dummy)) > 0) {
+                /* AutoSync */
+                runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
+                runtime->hw.rate_min = rate;
+                runtime->hw.rate_max = rate;
+	}        
+
+	snd_rme32_set_buffer_constraint(rme32, runtime);
+	return 0;
+}
+
+static int
+snd_rme32_capture_adat_open(struct snd_pcm_substream *substream)
+{
+	int isadat, rate;
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (rme32->fullduplex_mode)
+		runtime->hw = snd_rme32_adat_fd_info;
+	else
+		runtime->hw = snd_rme32_adat_info;
+	if ((rate = snd_rme32_capture_getrate(rme32, &isadat)) > 0) {
+		if (!isadat) {
+			return -EIO;
+		}
+                runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
+                runtime->hw.rate_min = rate;
+                runtime->hw.rate_max = rate;
+        }
+
+	snd_pcm_set_sync(substream);
+        
+	spin_lock_irq(&rme32->lock);	
+	if (rme32->capture_substream != NULL) {
+		spin_unlock_irq(&rme32->lock);
+		return -EBUSY;
+        }
+	rme32->capture_substream = substream;
+	spin_unlock_irq(&rme32->lock);
+
+	snd_rme32_set_buffer_constraint(rme32, runtime);
+	return 0;
+}
+
+static int snd_rme32_playback_close(struct snd_pcm_substream *substream)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	int spdif = 0;
+
+	spin_lock_irq(&rme32->lock);
+	rme32->playback_substream = NULL;
+	rme32->playback_periodsize = 0;
+	spdif = (rme32->wcreg & RME32_WCR_ADAT) == 0;
+	spin_unlock_irq(&rme32->lock);
+	if (spdif) {
+		rme32->spdif_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(rme32->card, SNDRV_CTL_EVENT_MASK_VALUE |
+			       SNDRV_CTL_EVENT_MASK_INFO,
+			       &rme32->spdif_ctl->id);
+	}
+	return 0;
+}
+
+static int snd_rme32_capture_close(struct snd_pcm_substream *substream)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&rme32->lock);
+	rme32->capture_substream = NULL;
+	rme32->capture_periodsize = 0;
+	spin_unlock(&rme32->lock);
+	return 0;
+}
+
+static int snd_rme32_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&rme32->lock);
+	if (rme32->fullduplex_mode) {
+		memset(&rme32->playback_pcm, 0, sizeof(rme32->playback_pcm));
+		rme32->playback_pcm.hw_buffer_size = RME32_BUFFER_SIZE;
+		rme32->playback_pcm.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+	} else {
+		writel(0, rme32->iobase + RME32_IO_RESET_POS);
+	}
+	if (rme32->wcreg & RME32_WCR_SEL)
+		rme32->wcreg &= ~RME32_WCR_MUTE;
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	spin_unlock_irq(&rme32->lock);
+	return 0;
+}
+
+static int snd_rme32_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&rme32->lock);
+	if (rme32->fullduplex_mode) {
+		memset(&rme32->capture_pcm, 0, sizeof(rme32->capture_pcm));
+		rme32->capture_pcm.hw_buffer_size = RME32_BUFFER_SIZE;
+		rme32->capture_pcm.hw_queue_size = RME32_BUFFER_SIZE / 2;
+		rme32->capture_pcm.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream);
+	} else {
+		writel(0, rme32->iobase + RME32_IO_RESET_POS);
+	}
+	spin_unlock_irq(&rme32->lock);
+	return 0;
+}
+
+static int
+snd_rme32_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *s;
+
+	spin_lock(&rme32->lock);
+	snd_pcm_group_for_each_entry(s, substream) {
+		if (s != rme32->playback_substream &&
+		    s != rme32->capture_substream)
+			continue;
+		switch (cmd) {
+		case SNDRV_PCM_TRIGGER_START:
+			rme32->running |= (1 << s->stream);
+			if (rme32->fullduplex_mode) {
+				/* remember the current DMA position */
+				if (s == rme32->playback_substream) {
+					rme32->playback_pcm.hw_io =
+					rme32->playback_pcm.hw_data = snd_rme32_pcm_byteptr(rme32);
+				} else {
+					rme32->capture_pcm.hw_io =
+					rme32->capture_pcm.hw_data = snd_rme32_pcm_byteptr(rme32);
+				}
+			}
+			break;
+		case SNDRV_PCM_TRIGGER_STOP:
+			rme32->running &= ~(1 << s->stream);
+			break;
+		}
+		snd_pcm_trigger_done(s, substream);
+	}
+	
+	/* prefill playback buffer */
+	if (cmd == SNDRV_PCM_TRIGGER_START && rme32->fullduplex_mode) {
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s == rme32->playback_substream) {
+				s->ops->ack(s);
+				break;
+			}
+		}
+	}
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (rme32->running && ! RME32_ISWORKING(rme32))
+			snd_rme32_pcm_start(rme32, 0);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (! rme32->running && RME32_ISWORKING(rme32))
+			snd_rme32_pcm_stop(rme32, 0);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (rme32->running && RME32_ISWORKING(rme32))
+			snd_rme32_pcm_stop(rme32, 1);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (rme32->running && ! RME32_ISWORKING(rme32))
+			snd_rme32_pcm_start(rme32, 1);
+		break;
+	}
+	spin_unlock(&rme32->lock);
+	return 0;
+}
+
+/* pointer callback for halfduplex mode */
+static snd_pcm_uframes_t
+snd_rme32_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	return snd_rme32_pcm_byteptr(rme32) >> rme32->playback_frlog;
+}
+
+static snd_pcm_uframes_t
+snd_rme32_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	return snd_rme32_pcm_byteptr(rme32) >> rme32->capture_frlog;
+}
+
+
+/* ack and pointer callbacks for fullduplex mode */
+static void snd_rme32_pb_trans_copy(struct snd_pcm_substream *substream,
+				    struct snd_pcm_indirect *rec, size_t bytes)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	memcpy_toio(rme32->iobase + RME32_IO_DATA_BUFFER + rec->hw_data,
+		    substream->runtime->dma_area + rec->sw_data, bytes);
+}
+
+static int snd_rme32_playback_fd_ack(struct snd_pcm_substream *substream)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_indirect *rec, *cprec;
+
+	rec = &rme32->playback_pcm;
+	cprec = &rme32->capture_pcm;
+	spin_lock(&rme32->lock);
+	rec->hw_queue_size = RME32_BUFFER_SIZE;
+	if (rme32->running & (1 << SNDRV_PCM_STREAM_CAPTURE))
+		rec->hw_queue_size -= cprec->hw_ready;
+	spin_unlock(&rme32->lock);
+	snd_pcm_indirect_playback_transfer(substream, rec,
+					   snd_rme32_pb_trans_copy);
+	return 0;
+}
+
+static void snd_rme32_cp_trans_copy(struct snd_pcm_substream *substream,
+				    struct snd_pcm_indirect *rec, size_t bytes)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	memcpy_fromio(substream->runtime->dma_area + rec->sw_data,
+		      rme32->iobase + RME32_IO_DATA_BUFFER + rec->hw_data,
+		      bytes);
+}
+
+static int snd_rme32_capture_fd_ack(struct snd_pcm_substream *substream)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	snd_pcm_indirect_capture_transfer(substream, &rme32->capture_pcm,
+					  snd_rme32_cp_trans_copy);
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_rme32_playback_fd_pointer(struct snd_pcm_substream *substream)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	return snd_pcm_indirect_playback_pointer(substream, &rme32->playback_pcm,
+						 snd_rme32_pcm_byteptr(rme32));
+}
+
+static snd_pcm_uframes_t
+snd_rme32_capture_fd_pointer(struct snd_pcm_substream *substream)
+{
+	struct rme32 *rme32 = snd_pcm_substream_chip(substream);
+	return snd_pcm_indirect_capture_pointer(substream, &rme32->capture_pcm,
+						snd_rme32_pcm_byteptr(rme32));
+}
+
+/* for halfduplex mode */
+static struct snd_pcm_ops snd_rme32_playback_spdif_ops = {
+	.open =		snd_rme32_playback_spdif_open,
+	.close =	snd_rme32_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme32_playback_hw_params,
+	.hw_free =	snd_rme32_pcm_hw_free,
+	.prepare =	snd_rme32_playback_prepare,
+	.trigger =	snd_rme32_pcm_trigger,
+	.pointer =	snd_rme32_playback_pointer,
+	.copy =		snd_rme32_playback_copy,
+	.silence =	snd_rme32_playback_silence,
+	.mmap =		snd_pcm_lib_mmap_iomem,
+};
+
+static struct snd_pcm_ops snd_rme32_capture_spdif_ops = {
+	.open =		snd_rme32_capture_spdif_open,
+	.close =	snd_rme32_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme32_capture_hw_params,
+	.hw_free =	snd_rme32_pcm_hw_free,
+	.prepare =	snd_rme32_capture_prepare,
+	.trigger =	snd_rme32_pcm_trigger,
+	.pointer =	snd_rme32_capture_pointer,
+	.copy =		snd_rme32_capture_copy,
+	.mmap =		snd_pcm_lib_mmap_iomem,
+};
+
+static struct snd_pcm_ops snd_rme32_playback_adat_ops = {
+	.open =		snd_rme32_playback_adat_open,
+	.close =	snd_rme32_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme32_playback_hw_params,
+	.prepare =	snd_rme32_playback_prepare,
+	.trigger =	snd_rme32_pcm_trigger,
+	.pointer =	snd_rme32_playback_pointer,
+	.copy =		snd_rme32_playback_copy,
+	.silence =	snd_rme32_playback_silence,
+	.mmap =		snd_pcm_lib_mmap_iomem,
+};
+
+static struct snd_pcm_ops snd_rme32_capture_adat_ops = {
+	.open =		snd_rme32_capture_adat_open,
+	.close =	snd_rme32_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme32_capture_hw_params,
+	.prepare =	snd_rme32_capture_prepare,
+	.trigger =	snd_rme32_pcm_trigger,
+	.pointer =	snd_rme32_capture_pointer,
+	.copy =		snd_rme32_capture_copy,
+	.mmap =		snd_pcm_lib_mmap_iomem,
+};
+
+/* for fullduplex mode */
+static struct snd_pcm_ops snd_rme32_playback_spdif_fd_ops = {
+	.open =		snd_rme32_playback_spdif_open,
+	.close =	snd_rme32_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme32_playback_hw_params,
+	.hw_free =	snd_rme32_pcm_hw_free,
+	.prepare =	snd_rme32_playback_prepare,
+	.trigger =	snd_rme32_pcm_trigger,
+	.pointer =	snd_rme32_playback_fd_pointer,
+	.ack =		snd_rme32_playback_fd_ack,
+};
+
+static struct snd_pcm_ops snd_rme32_capture_spdif_fd_ops = {
+	.open =		snd_rme32_capture_spdif_open,
+	.close =	snd_rme32_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme32_capture_hw_params,
+	.hw_free =	snd_rme32_pcm_hw_free,
+	.prepare =	snd_rme32_capture_prepare,
+	.trigger =	snd_rme32_pcm_trigger,
+	.pointer =	snd_rme32_capture_fd_pointer,
+	.ack =		snd_rme32_capture_fd_ack,
+};
+
+static struct snd_pcm_ops snd_rme32_playback_adat_fd_ops = {
+	.open =		snd_rme32_playback_adat_open,
+	.close =	snd_rme32_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme32_playback_hw_params,
+	.prepare =	snd_rme32_playback_prepare,
+	.trigger =	snd_rme32_pcm_trigger,
+	.pointer =	snd_rme32_playback_fd_pointer,
+	.ack =		snd_rme32_playback_fd_ack,
+};
+
+static struct snd_pcm_ops snd_rme32_capture_adat_fd_ops = {
+	.open =		snd_rme32_capture_adat_open,
+	.close =	snd_rme32_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme32_capture_hw_params,
+	.prepare =	snd_rme32_capture_prepare,
+	.trigger =	snd_rme32_pcm_trigger,
+	.pointer =	snd_rme32_capture_fd_pointer,
+	.ack =		snd_rme32_capture_fd_ack,
+};
+
+static void snd_rme32_free(void *private_data)
+{
+	struct rme32 *rme32 = (struct rme32 *) private_data;
+
+	if (rme32 == NULL) {
+		return;
+	}
+	if (rme32->irq >= 0) {
+		snd_rme32_pcm_stop(rme32, 0);
+		free_irq(rme32->irq, (void *) rme32);
+		rme32->irq = -1;
+	}
+	if (rme32->iobase) {
+		iounmap(rme32->iobase);
+		rme32->iobase = NULL;
+	}
+	if (rme32->port) {
+		pci_release_regions(rme32->pci);
+		rme32->port = 0;
+	}
+	pci_disable_device(rme32->pci);
+}
+
+static void snd_rme32_free_spdif_pcm(struct snd_pcm *pcm)
+{
+	struct rme32 *rme32 = (struct rme32 *) pcm->private_data;
+	rme32->spdif_pcm = NULL;
+}
+
+static void
+snd_rme32_free_adat_pcm(struct snd_pcm *pcm)
+{
+	struct rme32 *rme32 = (struct rme32 *) pcm->private_data;
+	rme32->adat_pcm = NULL;
+}
+
+static int __devinit snd_rme32_create(struct rme32 * rme32)
+{
+	struct pci_dev *pci = rme32->pci;
+	int err;
+
+	rme32->irq = -1;
+	spin_lock_init(&rme32->lock);
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	if ((err = pci_request_regions(pci, "RME32")) < 0)
+		return err;
+	rme32->port = pci_resource_start(rme32->pci, 0);
+
+	rme32->iobase = ioremap_nocache(rme32->port, RME32_IO_SIZE);
+	if (!rme32->iobase) {
+		snd_printk(KERN_ERR "unable to remap memory region 0x%lx-0x%lx\n",
+			   rme32->port, rme32->port + RME32_IO_SIZE - 1);
+		return -ENOMEM;
+	}
+
+	if (request_irq(pci->irq, snd_rme32_interrupt, IRQF_SHARED,
+			"RME32", rme32)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		return -EBUSY;
+	}
+	rme32->irq = pci->irq;
+
+	/* read the card's revision number */
+	pci_read_config_byte(pci, 8, &rme32->rev);
+
+	/* set up ALSA pcm device for S/PDIF */
+	if ((err = snd_pcm_new(rme32->card, "Digi32 IEC958", 0, 1, 1, &rme32->spdif_pcm)) < 0) {
+		return err;
+	}
+	rme32->spdif_pcm->private_data = rme32;
+	rme32->spdif_pcm->private_free = snd_rme32_free_spdif_pcm;
+	strcpy(rme32->spdif_pcm->name, "Digi32 IEC958");
+	if (rme32->fullduplex_mode) {
+		snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				&snd_rme32_playback_spdif_fd_ops);
+		snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&snd_rme32_capture_spdif_fd_ops);
+		snd_pcm_lib_preallocate_pages_for_all(rme32->spdif_pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+						      snd_dma_continuous_data(GFP_KERNEL),
+						      0, RME32_MID_BUFFER_SIZE);
+		rme32->spdif_pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+	} else {
+		snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				&snd_rme32_playback_spdif_ops);
+		snd_pcm_set_ops(rme32->spdif_pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&snd_rme32_capture_spdif_ops);
+		rme32->spdif_pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+	}
+
+	/* set up ALSA pcm device for ADAT */
+	if ((pci->device == PCI_DEVICE_ID_RME_DIGI32) ||
+	    (pci->device == PCI_DEVICE_ID_RME_DIGI32_PRO)) {
+		/* ADAT is not available on DIGI32 and DIGI32 Pro */
+		rme32->adat_pcm = NULL;
+	}
+	else {
+		if ((err = snd_pcm_new(rme32->card, "Digi32 ADAT", 1,
+				       1, 1, &rme32->adat_pcm)) < 0)
+		{
+			return err;
+		}		
+		rme32->adat_pcm->private_data = rme32;
+		rme32->adat_pcm->private_free = snd_rme32_free_adat_pcm;
+		strcpy(rme32->adat_pcm->name, "Digi32 ADAT");
+		if (rme32->fullduplex_mode) {
+			snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_PLAYBACK, 
+					&snd_rme32_playback_adat_fd_ops);
+			snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_CAPTURE, 
+					&snd_rme32_capture_adat_fd_ops);
+			snd_pcm_lib_preallocate_pages_for_all(rme32->adat_pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+							      snd_dma_continuous_data(GFP_KERNEL),
+							      0, RME32_MID_BUFFER_SIZE);
+			rme32->adat_pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+		} else {
+			snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_PLAYBACK, 
+					&snd_rme32_playback_adat_ops);
+			snd_pcm_set_ops(rme32->adat_pcm, SNDRV_PCM_STREAM_CAPTURE, 
+					&snd_rme32_capture_adat_ops);
+			rme32->adat_pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
+		}
+	}
+
+
+	rme32->playback_periodsize = 0;
+	rme32->capture_periodsize = 0;
+
+	/* make sure playback/capture is stopped, if by some reason active */
+	snd_rme32_pcm_stop(rme32, 0);
+
+        /* reset DAC */
+        snd_rme32_reset_dac(rme32);
+
+	/* reset buffer pointer */
+	writel(0, rme32->iobase + RME32_IO_RESET_POS);
+
+	/* set default values in registers */
+	rme32->wcreg = RME32_WCR_SEL |	 /* normal playback */
+		RME32_WCR_INP_0 | /* input select */
+		RME32_WCR_MUTE;	 /* muting on */
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+
+
+	/* init switch interface */
+	if ((err = snd_rme32_create_switches(rme32->card, rme32)) < 0) {
+		return err;
+	}
+
+	/* init proc interface */
+	snd_rme32_proc_init(rme32);
+
+	rme32->capture_substream = NULL;
+	rme32->playback_substream = NULL;
+
+	return 0;
+}
+
+/*
+ * proc interface
+ */
+
+static void
+snd_rme32_proc_read(struct snd_info_entry * entry, struct snd_info_buffer *buffer)
+{
+	int n;
+	struct rme32 *rme32 = (struct rme32 *) entry->private_data;
+
+	rme32->rcreg = readl(rme32->iobase + RME32_IO_CONTROL_REGISTER);
+
+	snd_iprintf(buffer, rme32->card->longname);
+	snd_iprintf(buffer, " (index #%d)\n", rme32->card->number + 1);
+
+	snd_iprintf(buffer, "\nGeneral settings\n");
+	if (rme32->fullduplex_mode)
+		snd_iprintf(buffer, "  Full-duplex mode\n");
+	else
+		snd_iprintf(buffer, "  Half-duplex mode\n");
+	if (RME32_PRO_WITH_8414(rme32)) {
+		snd_iprintf(buffer, "  receiver: CS8414\n");
+	} else {
+		snd_iprintf(buffer, "  receiver: CS8412\n");
+	}
+	if (rme32->wcreg & RME32_WCR_MODE24) {
+		snd_iprintf(buffer, "  format: 24 bit");
+	} else {
+		snd_iprintf(buffer, "  format: 16 bit");
+	}
+	if (rme32->wcreg & RME32_WCR_MONO) {
+		snd_iprintf(buffer, ", Mono\n");
+	} else {
+		snd_iprintf(buffer, ", Stereo\n");
+	}
+
+	snd_iprintf(buffer, "\nInput settings\n");
+	switch (snd_rme32_getinputtype(rme32)) {
+	case RME32_INPUT_OPTICAL:
+		snd_iprintf(buffer, "  input: optical");
+		break;
+	case RME32_INPUT_COAXIAL:
+		snd_iprintf(buffer, "  input: coaxial");
+		break;
+	case RME32_INPUT_INTERNAL:
+		snd_iprintf(buffer, "  input: internal");
+		break;
+	case RME32_INPUT_XLR:
+		snd_iprintf(buffer, "  input: XLR");
+		break;
+	}
+	if (snd_rme32_capture_getrate(rme32, &n) < 0) {
+		snd_iprintf(buffer, "\n  sample rate: no valid signal\n");
+	} else {
+		if (n) {
+			snd_iprintf(buffer, " (8 channels)\n");
+		} else {
+			snd_iprintf(buffer, " (2 channels)\n");
+		}
+		snd_iprintf(buffer, "  sample rate: %d Hz\n",
+			    snd_rme32_capture_getrate(rme32, &n));
+	}
+
+	snd_iprintf(buffer, "\nOutput settings\n");
+	if (rme32->wcreg & RME32_WCR_SEL) {
+		snd_iprintf(buffer, "  output signal: normal playback");
+	} else {
+		snd_iprintf(buffer, "  output signal: same as input");
+	}
+	if (rme32->wcreg & RME32_WCR_MUTE) {
+		snd_iprintf(buffer, " (muted)\n");
+	} else {
+		snd_iprintf(buffer, "\n");
+	}
+
+	/* master output frequency */
+	if (!
+	    ((!(rme32->wcreg & RME32_WCR_FREQ_0))
+	     && (!(rme32->wcreg & RME32_WCR_FREQ_1)))) {
+		snd_iprintf(buffer, "  sample rate: %d Hz\n",
+			    snd_rme32_playback_getrate(rme32));
+	}
+	if (rme32->rcreg & RME32_RCR_KMODE) {
+		snd_iprintf(buffer, "  sample clock source: AutoSync\n");
+	} else {
+		snd_iprintf(buffer, "  sample clock source: Internal\n");
+	}
+	if (rme32->wcreg & RME32_WCR_PRO) {
+		snd_iprintf(buffer, "  format: AES/EBU (professional)\n");
+	} else {
+		snd_iprintf(buffer, "  format: IEC958 (consumer)\n");
+	}
+	if (rme32->wcreg & RME32_WCR_EMP) {
+		snd_iprintf(buffer, "  emphasis: on\n");
+	} else {
+		snd_iprintf(buffer, "  emphasis: off\n");
+	}
+}
+
+static void __devinit snd_rme32_proc_init(struct rme32 * rme32)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(rme32->card, "rme32", &entry))
+		snd_info_set_text_ops(entry, rme32, snd_rme32_proc_read);
+}
+
+/*
+ * control interface
+ */
+
+#define snd_rme32_info_loopback_control		snd_ctl_boolean_mono_info
+
+static int
+snd_rme32_get_loopback_control(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme32 *rme32 = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&rme32->lock);
+	ucontrol->value.integer.value[0] =
+	    rme32->wcreg & RME32_WCR_SEL ? 0 : 1;
+	spin_unlock_irq(&rme32->lock);
+	return 0;
+}
+static int
+snd_rme32_put_loopback_control(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme32 *rme32 = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+
+	val = ucontrol->value.integer.value[0] ? 0 : RME32_WCR_SEL;
+	spin_lock_irq(&rme32->lock);
+	val = (rme32->wcreg & ~RME32_WCR_SEL) | val;
+	change = val != rme32->wcreg;
+	if (ucontrol->value.integer.value[0])
+		val &= ~RME32_WCR_MUTE;
+	else
+		val |= RME32_WCR_MUTE;
+	rme32->wcreg = val;
+	writel(val, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	spin_unlock_irq(&rme32->lock);
+	return change;
+}
+
+static int
+snd_rme32_info_inputtype_control(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	struct rme32 *rme32 = snd_kcontrol_chip(kcontrol);
+	static char *texts[4] = { "Optical", "Coaxial", "Internal", "XLR" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	switch (rme32->pci->device) {
+	case PCI_DEVICE_ID_RME_DIGI32:
+	case PCI_DEVICE_ID_RME_DIGI32_8:
+		uinfo->value.enumerated.items = 3;
+		break;
+	case PCI_DEVICE_ID_RME_DIGI32_PRO:
+		uinfo->value.enumerated.items = 4;
+		break;
+	default:
+		snd_BUG();
+		break;
+	}
+	if (uinfo->value.enumerated.item >
+	    uinfo->value.enumerated.items - 1) {
+		uinfo->value.enumerated.item =
+		    uinfo->value.enumerated.items - 1;
+	}
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+static int
+snd_rme32_get_inputtype_control(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme32 *rme32 = snd_kcontrol_chip(kcontrol);
+	unsigned int items = 3;
+
+	spin_lock_irq(&rme32->lock);
+	ucontrol->value.enumerated.item[0] = snd_rme32_getinputtype(rme32);
+
+	switch (rme32->pci->device) {
+	case PCI_DEVICE_ID_RME_DIGI32:
+	case PCI_DEVICE_ID_RME_DIGI32_8:
+		items = 3;
+		break;
+	case PCI_DEVICE_ID_RME_DIGI32_PRO:
+		items = 4;
+		break;
+	default:
+		snd_BUG();
+		break;
+	}
+	if (ucontrol->value.enumerated.item[0] >= items) {
+		ucontrol->value.enumerated.item[0] = items - 1;
+	}
+
+	spin_unlock_irq(&rme32->lock);
+	return 0;
+}
+static int
+snd_rme32_put_inputtype_control(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme32 *rme32 = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change, items = 3;
+
+	switch (rme32->pci->device) {
+	case PCI_DEVICE_ID_RME_DIGI32:
+	case PCI_DEVICE_ID_RME_DIGI32_8:
+		items = 3;
+		break;
+	case PCI_DEVICE_ID_RME_DIGI32_PRO:
+		items = 4;
+		break;
+	default:
+		snd_BUG();
+		break;
+	}
+	val = ucontrol->value.enumerated.item[0] % items;
+
+	spin_lock_irq(&rme32->lock);
+	change = val != (unsigned int)snd_rme32_getinputtype(rme32);
+	snd_rme32_setinputtype(rme32, val);
+	spin_unlock_irq(&rme32->lock);
+	return change;
+}
+
+static int
+snd_rme32_info_clockmode_control(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = { "AutoSync", 
+				  "Internal 32.0kHz", 
+				  "Internal 44.1kHz", 
+				  "Internal 48.0kHz" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3) {
+		uinfo->value.enumerated.item = 3;
+	}
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+static int
+snd_rme32_get_clockmode_control(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme32 *rme32 = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&rme32->lock);
+	ucontrol->value.enumerated.item[0] = snd_rme32_getclockmode(rme32);
+	spin_unlock_irq(&rme32->lock);
+	return 0;
+}
+static int
+snd_rme32_put_clockmode_control(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme32 *rme32 = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+
+	val = ucontrol->value.enumerated.item[0] % 3;
+	spin_lock_irq(&rme32->lock);
+	change = val != (unsigned int)snd_rme32_getclockmode(rme32);
+	snd_rme32_setclockmode(rme32, val);
+	spin_unlock_irq(&rme32->lock);
+	return change;
+}
+
+static u32 snd_rme32_convert_from_aes(struct snd_aes_iec958 * aes)
+{
+	u32 val = 0;
+	val |= (aes->status[0] & IEC958_AES0_PROFESSIONAL) ? RME32_WCR_PRO : 0;
+	if (val & RME32_WCR_PRO)
+		val |= (aes->status[0] & IEC958_AES0_PRO_EMPHASIS_5015) ? RME32_WCR_EMP : 0;
+	else
+		val |= (aes->status[0] & IEC958_AES0_CON_EMPHASIS_5015) ? RME32_WCR_EMP : 0;
+	return val;
+}
+
+static void snd_rme32_convert_to_aes(struct snd_aes_iec958 * aes, u32 val)
+{
+	aes->status[0] = ((val & RME32_WCR_PRO) ? IEC958_AES0_PROFESSIONAL : 0);
+	if (val & RME32_WCR_PRO)
+		aes->status[0] |= (val & RME32_WCR_EMP) ? IEC958_AES0_PRO_EMPHASIS_5015 : 0;
+	else
+		aes->status[0] |= (val & RME32_WCR_EMP) ? IEC958_AES0_CON_EMPHASIS_5015 : 0;
+}
+
+static int snd_rme32_control_spdif_info(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_rme32_control_spdif_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme32 *rme32 = snd_kcontrol_chip(kcontrol);
+
+	snd_rme32_convert_to_aes(&ucontrol->value.iec958,
+				 rme32->wcreg_spdif);
+	return 0;
+}
+
+static int snd_rme32_control_spdif_put(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme32 *rme32 = snd_kcontrol_chip(kcontrol);
+	int change;
+	u32 val;
+
+	val = snd_rme32_convert_from_aes(&ucontrol->value.iec958);
+	spin_lock_irq(&rme32->lock);
+	change = val != rme32->wcreg_spdif;
+	rme32->wcreg_spdif = val;
+	spin_unlock_irq(&rme32->lock);
+	return change;
+}
+
+static int snd_rme32_control_spdif_stream_info(struct snd_kcontrol *kcontrol,
+					       struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_rme32_control_spdif_stream_get(struct snd_kcontrol *kcontrol,
+					      struct snd_ctl_elem_value *
+					      ucontrol)
+{
+	struct rme32 *rme32 = snd_kcontrol_chip(kcontrol);
+
+	snd_rme32_convert_to_aes(&ucontrol->value.iec958,
+				 rme32->wcreg_spdif_stream);
+	return 0;
+}
+
+static int snd_rme32_control_spdif_stream_put(struct snd_kcontrol *kcontrol,
+					      struct snd_ctl_elem_value *
+					      ucontrol)
+{
+	struct rme32 *rme32 = snd_kcontrol_chip(kcontrol);
+	int change;
+	u32 val;
+
+	val = snd_rme32_convert_from_aes(&ucontrol->value.iec958);
+	spin_lock_irq(&rme32->lock);
+	change = val != rme32->wcreg_spdif_stream;
+	rme32->wcreg_spdif_stream = val;
+	rme32->wcreg &= ~(RME32_WCR_PRO | RME32_WCR_EMP);
+	rme32->wcreg |= val;
+	writel(rme32->wcreg, rme32->iobase + RME32_IO_CONTROL_REGISTER);
+	spin_unlock_irq(&rme32->lock);
+	return change;
+}
+
+static int snd_rme32_control_spdif_mask_info(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_rme32_control_spdif_mask_get(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *
+					    ucontrol)
+{
+	ucontrol->value.iec958.status[0] = kcontrol->private_value;
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_rme32_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =	SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+		.info =	snd_rme32_control_spdif_info,
+		.get =	snd_rme32_control_spdif_get,
+		.put =	snd_rme32_control_spdif_put
+	},
+	{
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =	SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
+		.info =	snd_rme32_control_spdif_stream_info,
+		.get =	snd_rme32_control_spdif_stream_get,
+		.put =	snd_rme32_control_spdif_stream_put
+	},
+	{
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =	SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+		.info =	snd_rme32_control_spdif_mask_info,
+		.get =	snd_rme32_control_spdif_mask_get,
+		.private_value = IEC958_AES0_PROFESSIONAL | IEC958_AES0_CON_EMPHASIS
+	},
+	{
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name =	SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK),
+		.info =	snd_rme32_control_spdif_mask_info,
+		.get =	snd_rme32_control_spdif_mask_get,
+		.private_value = IEC958_AES0_PROFESSIONAL | IEC958_AES0_PRO_EMPHASIS
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =	"Input Connector",
+		.info =	snd_rme32_info_inputtype_control,
+		.get =	snd_rme32_get_inputtype_control,
+		.put =	snd_rme32_put_inputtype_control
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =	"Loopback Input",
+		.info =	snd_rme32_info_loopback_control,
+		.get =	snd_rme32_get_loopback_control,
+		.put =	snd_rme32_put_loopback_control
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =	"Sample Clock Source",
+		.info =	snd_rme32_info_clockmode_control,
+		.get =	snd_rme32_get_clockmode_control,
+		.put =	snd_rme32_put_clockmode_control
+	}
+};
+
+static int snd_rme32_create_switches(struct snd_card *card, struct rme32 * rme32)
+{
+	int idx, err;
+	struct snd_kcontrol *kctl;
+
+	for (idx = 0; idx < (int)ARRAY_SIZE(snd_rme32_controls); idx++) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_rme32_controls[idx], rme32))) < 0)
+			return err;
+		if (idx == 1)	/* IEC958 (S/PDIF) Stream */
+			rme32->spdif_ctl = kctl;
+	}
+
+	return 0;
+}
+
+/*
+ * Card initialisation
+ */
+
+static void snd_rme32_card_free(struct snd_card *card)
+{
+	snd_rme32_free(card->private_data);
+}
+
+static int __devinit
+snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct rme32 *rme32;
+	struct snd_card *card;
+	int err;
+
+	if (dev >= SNDRV_CARDS) {
+		return -ENODEV;
+	}
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct rme32), &card);
+	if (err < 0)
+		return err;
+	card->private_free = snd_rme32_card_free;
+	rme32 = (struct rme32 *) card->private_data;
+	rme32->card = card;
+	rme32->pci = pci;
+	snd_card_set_dev(card, &pci->dev);
+        if (fullduplex[dev])
+		rme32->fullduplex_mode = 1;
+	if ((err = snd_rme32_create(rme32)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "Digi32");
+	switch (rme32->pci->device) {
+	case PCI_DEVICE_ID_RME_DIGI32:
+		strcpy(card->shortname, "RME Digi32");
+		break;
+	case PCI_DEVICE_ID_RME_DIGI32_8:
+		strcpy(card->shortname, "RME Digi32/8");
+		break;
+	case PCI_DEVICE_ID_RME_DIGI32_PRO:
+		strcpy(card->shortname, "RME Digi32 PRO");
+		break;
+	}
+	sprintf(card->longname, "%s (Rev. %d) at 0x%lx, irq %d",
+		card->shortname, rme32->rev, rme32->port, rme32->irq);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_rme32_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name =		"RME Digi32",
+	.id_table =	snd_rme32_ids,
+	.probe =	snd_rme32_probe,
+	.remove =	__devexit_p(snd_rme32_remove),
+};
+
+static int __init alsa_card_rme32_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_rme32_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_rme32_init)
+module_exit(alsa_card_rme32_exit)
diff --git a/linux/sound/pci/rme96.c b/linux/sound/pci/rme96.c
new file mode 100644
index 0000000..d5f5b44
--- /dev/null
+++ b/linux/sound/pci/rme96.c
@@ -0,0 +1,2416 @@
+/*
+ *   ALSA driver for RME Digi96, Digi96/8 and Digi96/8 PRO/PAD/PST audio
+ *   interfaces 
+ *
+ *	Copyright (c) 2000, 2001 Anders Torger <torger@ludd.luth.se>
+ *    
+ *      Thanks to Henk Hesselink <henk@anda.nl> for the analog volume control
+ *      code.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+
+/* note, two last pcis should be equal, it is not a bug */
+
+MODULE_AUTHOR("Anders Torger <torger@ludd.luth.se>");
+MODULE_DESCRIPTION("RME Digi96, Digi96/8, Digi96/8 PRO, Digi96/8 PST, "
+		   "Digi96/8 PAD");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{RME,Digi96},"
+		"{RME,Digi96/8},"
+		"{RME,Digi96/8 PRO},"
+		"{RME,Digi96/8 PST},"
+		"{RME,Digi96/8 PAD}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for RME Digi96 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for RME Digi96 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable RME Digi96 soundcard.");
+
+/*
+ * Defines for RME Digi96 series, from internal RME reference documents
+ * dated 12.01.00
+ */
+
+#define RME96_SPDIF_NCHANNELS 2
+
+/* Playback and capture buffer size */
+#define RME96_BUFFER_SIZE 0x10000
+
+/* IO area size */
+#define RME96_IO_SIZE 0x60000
+
+/* IO area offsets */
+#define RME96_IO_PLAY_BUFFER      0x0
+#define RME96_IO_REC_BUFFER       0x10000
+#define RME96_IO_CONTROL_REGISTER 0x20000
+#define RME96_IO_ADDITIONAL_REG   0x20004
+#define RME96_IO_CONFIRM_PLAY_IRQ 0x20008
+#define RME96_IO_CONFIRM_REC_IRQ  0x2000C
+#define RME96_IO_SET_PLAY_POS     0x40000
+#define RME96_IO_RESET_PLAY_POS   0x4FFFC
+#define RME96_IO_SET_REC_POS      0x50000
+#define RME96_IO_RESET_REC_POS    0x5FFFC
+#define RME96_IO_GET_PLAY_POS     0x20000
+#define RME96_IO_GET_REC_POS      0x30000
+
+/* Write control register bits */
+#define RME96_WCR_START     (1 << 0)
+#define RME96_WCR_START_2   (1 << 1)
+#define RME96_WCR_GAIN_0    (1 << 2)
+#define RME96_WCR_GAIN_1    (1 << 3)
+#define RME96_WCR_MODE24    (1 << 4)
+#define RME96_WCR_MODE24_2  (1 << 5)
+#define RME96_WCR_BM        (1 << 6)
+#define RME96_WCR_BM_2      (1 << 7)
+#define RME96_WCR_ADAT      (1 << 8)
+#define RME96_WCR_FREQ_0    (1 << 9)
+#define RME96_WCR_FREQ_1    (1 << 10)
+#define RME96_WCR_DS        (1 << 11)
+#define RME96_WCR_PRO       (1 << 12)
+#define RME96_WCR_EMP       (1 << 13)
+#define RME96_WCR_SEL       (1 << 14)
+#define RME96_WCR_MASTER    (1 << 15)
+#define RME96_WCR_PD        (1 << 16)
+#define RME96_WCR_INP_0     (1 << 17)
+#define RME96_WCR_INP_1     (1 << 18)
+#define RME96_WCR_THRU_0    (1 << 19)
+#define RME96_WCR_THRU_1    (1 << 20)
+#define RME96_WCR_THRU_2    (1 << 21)
+#define RME96_WCR_THRU_3    (1 << 22)
+#define RME96_WCR_THRU_4    (1 << 23)
+#define RME96_WCR_THRU_5    (1 << 24)
+#define RME96_WCR_THRU_6    (1 << 25)
+#define RME96_WCR_THRU_7    (1 << 26)
+#define RME96_WCR_DOLBY     (1 << 27)
+#define RME96_WCR_MONITOR_0 (1 << 28)
+#define RME96_WCR_MONITOR_1 (1 << 29)
+#define RME96_WCR_ISEL      (1 << 30)
+#define RME96_WCR_IDIS      (1 << 31)
+
+#define RME96_WCR_BITPOS_GAIN_0 2
+#define RME96_WCR_BITPOS_GAIN_1 3
+#define RME96_WCR_BITPOS_FREQ_0 9
+#define RME96_WCR_BITPOS_FREQ_1 10
+#define RME96_WCR_BITPOS_INP_0 17
+#define RME96_WCR_BITPOS_INP_1 18
+#define RME96_WCR_BITPOS_MONITOR_0 28
+#define RME96_WCR_BITPOS_MONITOR_1 29
+
+/* Read control register bits */
+#define RME96_RCR_AUDIO_ADDR_MASK 0xFFFF
+#define RME96_RCR_IRQ_2     (1 << 16)
+#define RME96_RCR_T_OUT     (1 << 17)
+#define RME96_RCR_DEV_ID_0  (1 << 21)
+#define RME96_RCR_DEV_ID_1  (1 << 22)
+#define RME96_RCR_LOCK      (1 << 23)
+#define RME96_RCR_VERF      (1 << 26)
+#define RME96_RCR_F0        (1 << 27)
+#define RME96_RCR_F1        (1 << 28)
+#define RME96_RCR_F2        (1 << 29)
+#define RME96_RCR_AUTOSYNC  (1 << 30)
+#define RME96_RCR_IRQ       (1 << 31)
+
+#define RME96_RCR_BITPOS_F0 27
+#define RME96_RCR_BITPOS_F1 28
+#define RME96_RCR_BITPOS_F2 29
+
+/* Additonal register bits */
+#define RME96_AR_WSEL       (1 << 0)
+#define RME96_AR_ANALOG     (1 << 1)
+#define RME96_AR_FREQPAD_0  (1 << 2)
+#define RME96_AR_FREQPAD_1  (1 << 3)
+#define RME96_AR_FREQPAD_2  (1 << 4)
+#define RME96_AR_PD2        (1 << 5)
+#define RME96_AR_DAC_EN     (1 << 6)
+#define RME96_AR_CLATCH     (1 << 7)
+#define RME96_AR_CCLK       (1 << 8)
+#define RME96_AR_CDATA      (1 << 9)
+
+#define RME96_AR_BITPOS_F0 2
+#define RME96_AR_BITPOS_F1 3
+#define RME96_AR_BITPOS_F2 4
+
+/* Monitor tracks */
+#define RME96_MONITOR_TRACKS_1_2 0
+#define RME96_MONITOR_TRACKS_3_4 1
+#define RME96_MONITOR_TRACKS_5_6 2
+#define RME96_MONITOR_TRACKS_7_8 3
+
+/* Attenuation */
+#define RME96_ATTENUATION_0 0
+#define RME96_ATTENUATION_6 1
+#define RME96_ATTENUATION_12 2
+#define RME96_ATTENUATION_18 3
+
+/* Input types */
+#define RME96_INPUT_OPTICAL 0
+#define RME96_INPUT_COAXIAL 1
+#define RME96_INPUT_INTERNAL 2
+#define RME96_INPUT_XLR 3
+#define RME96_INPUT_ANALOG 4
+
+/* Clock modes */
+#define RME96_CLOCKMODE_SLAVE 0
+#define RME96_CLOCKMODE_MASTER 1
+#define RME96_CLOCKMODE_WORDCLOCK 2
+
+/* Block sizes in bytes */
+#define RME96_SMALL_BLOCK_SIZE 2048
+#define RME96_LARGE_BLOCK_SIZE 8192
+
+/* Volume control */
+#define RME96_AD1852_VOL_BITS 14
+#define RME96_AD1855_VOL_BITS 10
+
+
+struct rme96 {
+	spinlock_t    lock;
+	int irq;
+	unsigned long port;
+	void __iomem *iobase;
+	
+	u32 wcreg;    /* cached write control register value */
+	u32 wcreg_spdif;		/* S/PDIF setup */
+	u32 wcreg_spdif_stream;		/* S/PDIF setup (temporary) */
+	u32 rcreg;    /* cached read control register value */
+	u32 areg;     /* cached additional register value */
+	u16 vol[2]; /* cached volume of analog output */
+
+	u8 rev; /* card revision number */
+
+	struct snd_pcm_substream *playback_substream;
+	struct snd_pcm_substream *capture_substream;
+
+	int playback_frlog; /* log2 of framesize */
+	int capture_frlog;
+	
+        size_t playback_periodsize; /* in bytes, zero if not used */
+	size_t capture_periodsize; /* in bytes, zero if not used */
+
+	struct snd_card *card;
+	struct snd_pcm *spdif_pcm;
+	struct snd_pcm *adat_pcm; 
+	struct pci_dev     *pci;
+	struct snd_kcontrol   *spdif_ctl;
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_rme96_ids) = {
+	{ PCI_VDEVICE(XILINX, PCI_DEVICE_ID_RME_DIGI96), 0, },
+	{ PCI_VDEVICE(XILINX, PCI_DEVICE_ID_RME_DIGI96_8), 0, },
+	{ PCI_VDEVICE(XILINX, PCI_DEVICE_ID_RME_DIGI96_8_PRO), 0, },
+	{ PCI_VDEVICE(XILINX, PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST), 0, },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_rme96_ids);
+
+#define RME96_ISPLAYING(rme96) ((rme96)->wcreg & RME96_WCR_START)
+#define RME96_ISRECORDING(rme96) ((rme96)->wcreg & RME96_WCR_START_2)
+#define	RME96_HAS_ANALOG_IN(rme96) ((rme96)->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST)
+#define	RME96_HAS_ANALOG_OUT(rme96) ((rme96)->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PRO || \
+				     (rme96)->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST)
+#define	RME96_DAC_IS_1852(rme96) (RME96_HAS_ANALOG_OUT(rme96) && (rme96)->rev >= 4)
+#define	RME96_DAC_IS_1855(rme96) (((rme96)->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST && (rme96)->rev < 4) || \
+			          ((rme96)->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PRO && (rme96)->rev == 2))
+#define	RME96_185X_MAX_OUT(rme96) ((1 << (RME96_DAC_IS_1852(rme96) ? RME96_AD1852_VOL_BITS : RME96_AD1855_VOL_BITS)) - 1)
+
+static int
+snd_rme96_playback_prepare(struct snd_pcm_substream *substream);
+
+static int
+snd_rme96_capture_prepare(struct snd_pcm_substream *substream);
+
+static int
+snd_rme96_playback_trigger(struct snd_pcm_substream *substream, 
+			   int cmd);
+
+static int
+snd_rme96_capture_trigger(struct snd_pcm_substream *substream, 
+			  int cmd);
+
+static snd_pcm_uframes_t
+snd_rme96_playback_pointer(struct snd_pcm_substream *substream);
+
+static snd_pcm_uframes_t
+snd_rme96_capture_pointer(struct snd_pcm_substream *substream);
+
+static void __devinit 
+snd_rme96_proc_init(struct rme96 *rme96);
+
+static int
+snd_rme96_create_switches(struct snd_card *card,
+			  struct rme96 *rme96);
+
+static int
+snd_rme96_getinputtype(struct rme96 *rme96);
+
+static inline unsigned int
+snd_rme96_playback_ptr(struct rme96 *rme96)
+{
+	return (readl(rme96->iobase + RME96_IO_GET_PLAY_POS)
+		& RME96_RCR_AUDIO_ADDR_MASK) >> rme96->playback_frlog;
+}
+
+static inline unsigned int
+snd_rme96_capture_ptr(struct rme96 *rme96)
+{
+	return (readl(rme96->iobase + RME96_IO_GET_REC_POS)
+		& RME96_RCR_AUDIO_ADDR_MASK) >> rme96->capture_frlog;
+}
+
+static int
+snd_rme96_playback_silence(struct snd_pcm_substream *substream,
+			   int channel, /* not used (interleaved data) */
+			   snd_pcm_uframes_t pos,
+			   snd_pcm_uframes_t count)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	count <<= rme96->playback_frlog;
+	pos <<= rme96->playback_frlog;
+	memset_io(rme96->iobase + RME96_IO_PLAY_BUFFER + pos,
+		  0, count);
+	return 0;
+}
+
+static int
+snd_rme96_playback_copy(struct snd_pcm_substream *substream,
+			int channel, /* not used (interleaved data) */
+			snd_pcm_uframes_t pos,
+			void __user *src,
+			snd_pcm_uframes_t count)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	count <<= rme96->playback_frlog;
+	pos <<= rme96->playback_frlog;
+	copy_from_user_toio(rme96->iobase + RME96_IO_PLAY_BUFFER + pos, src,
+			    count);
+	return 0;
+}
+
+static int
+snd_rme96_capture_copy(struct snd_pcm_substream *substream,
+		       int channel, /* not used (interleaved data) */
+		       snd_pcm_uframes_t pos,
+		       void __user *dst,
+		       snd_pcm_uframes_t count)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	count <<= rme96->capture_frlog;
+	pos <<= rme96->capture_frlog;
+	copy_to_user_fromio(dst, rme96->iobase + RME96_IO_REC_BUFFER + pos,
+			    count);
+        return 0;
+}
+
+/*
+ * Digital output capabilities (S/PDIF)
+ */
+static struct snd_pcm_hardware snd_rme96_playback_spdif_info =
+{
+	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
+			      SNDRV_PCM_INFO_MMAP_VALID |
+			      SNDRV_PCM_INFO_INTERLEAVED |
+			      SNDRV_PCM_INFO_PAUSE),
+	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
+			      SNDRV_PCM_FMTBIT_S32_LE),
+	.rates =	     (SNDRV_PCM_RATE_32000 |
+			      SNDRV_PCM_RATE_44100 | 
+			      SNDRV_PCM_RATE_48000 | 
+			      SNDRV_PCM_RATE_64000 |
+			      SNDRV_PCM_RATE_88200 | 
+			      SNDRV_PCM_RATE_96000),
+	.rate_min =	     32000,
+	.rate_max =	     96000,
+	.channels_min =	     2,
+	.channels_max =	     2,
+	.buffer_bytes_max =  RME96_BUFFER_SIZE,
+	.period_bytes_min =  RME96_SMALL_BLOCK_SIZE,
+	.period_bytes_max =  RME96_LARGE_BLOCK_SIZE,
+	.periods_min =	     RME96_BUFFER_SIZE / RME96_LARGE_BLOCK_SIZE,
+	.periods_max =	     RME96_BUFFER_SIZE / RME96_SMALL_BLOCK_SIZE,
+	.fifo_size =	     0,
+};
+
+/*
+ * Digital input capabilities (S/PDIF)
+ */
+static struct snd_pcm_hardware snd_rme96_capture_spdif_info =
+{
+	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
+			      SNDRV_PCM_INFO_MMAP_VALID |
+			      SNDRV_PCM_INFO_INTERLEAVED |
+			      SNDRV_PCM_INFO_PAUSE),
+	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
+			      SNDRV_PCM_FMTBIT_S32_LE),
+	.rates =	     (SNDRV_PCM_RATE_32000 |
+			      SNDRV_PCM_RATE_44100 | 
+			      SNDRV_PCM_RATE_48000 | 
+			      SNDRV_PCM_RATE_64000 |
+			      SNDRV_PCM_RATE_88200 | 
+			      SNDRV_PCM_RATE_96000),
+	.rate_min =	     32000,
+	.rate_max =	     96000,
+	.channels_min =	     2,
+	.channels_max =	     2,
+	.buffer_bytes_max =  RME96_BUFFER_SIZE,
+	.period_bytes_min =  RME96_SMALL_BLOCK_SIZE,
+	.period_bytes_max =  RME96_LARGE_BLOCK_SIZE,
+	.periods_min =	     RME96_BUFFER_SIZE / RME96_LARGE_BLOCK_SIZE,
+	.periods_max =	     RME96_BUFFER_SIZE / RME96_SMALL_BLOCK_SIZE,
+	.fifo_size =	     0,
+};
+
+/*
+ * Digital output capabilities (ADAT)
+ */
+static struct snd_pcm_hardware snd_rme96_playback_adat_info =
+{
+	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
+			      SNDRV_PCM_INFO_MMAP_VALID |
+			      SNDRV_PCM_INFO_INTERLEAVED |
+			      SNDRV_PCM_INFO_PAUSE),
+	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
+			      SNDRV_PCM_FMTBIT_S32_LE),
+	.rates =             (SNDRV_PCM_RATE_44100 | 
+			      SNDRV_PCM_RATE_48000),
+	.rate_min =          44100,
+	.rate_max =          48000,
+	.channels_min =      8,
+	.channels_max =	     8,
+	.buffer_bytes_max =  RME96_BUFFER_SIZE,
+	.period_bytes_min =  RME96_SMALL_BLOCK_SIZE,
+	.period_bytes_max =  RME96_LARGE_BLOCK_SIZE,
+	.periods_min =	     RME96_BUFFER_SIZE / RME96_LARGE_BLOCK_SIZE,
+	.periods_max =	     RME96_BUFFER_SIZE / RME96_SMALL_BLOCK_SIZE,
+	.fifo_size =	     0,
+};
+
+/*
+ * Digital input capabilities (ADAT)
+ */
+static struct snd_pcm_hardware snd_rme96_capture_adat_info =
+{
+	.info =		     (SNDRV_PCM_INFO_MMAP_IOMEM |
+			      SNDRV_PCM_INFO_MMAP_VALID |
+			      SNDRV_PCM_INFO_INTERLEAVED |
+			      SNDRV_PCM_INFO_PAUSE),
+	.formats =	     (SNDRV_PCM_FMTBIT_S16_LE |
+			      SNDRV_PCM_FMTBIT_S32_LE),
+	.rates =	     (SNDRV_PCM_RATE_44100 | 
+			      SNDRV_PCM_RATE_48000),
+	.rate_min =          44100,
+	.rate_max =          48000,
+	.channels_min =      8,
+	.channels_max =	     8,
+	.buffer_bytes_max =  RME96_BUFFER_SIZE,
+	.period_bytes_min =  RME96_SMALL_BLOCK_SIZE,
+	.period_bytes_max =  RME96_LARGE_BLOCK_SIZE,
+	.periods_min =	     RME96_BUFFER_SIZE / RME96_LARGE_BLOCK_SIZE,
+	.periods_max =	     RME96_BUFFER_SIZE / RME96_SMALL_BLOCK_SIZE,
+	.fifo_size =         0,
+};
+
+/*
+ * The CDATA, CCLK and CLATCH bits can be used to write to the SPI interface
+ * of the AD1852 or AD1852 D/A converter on the board.  CDATA must be set up
+ * on the falling edge of CCLK and be stable on the rising edge.  The rising
+ * edge of CLATCH after the last data bit clocks in the whole data word.
+ * A fast processor could probably drive the SPI interface faster than the
+ * DAC can handle (3MHz for the 1855, unknown for the 1852).  The udelay(1)
+ * limits the data rate to 500KHz and only causes a delay of 33 microsecs.
+ *
+ * NOTE: increased delay from 1 to 10, since there where problems setting
+ * the volume.
+ */
+static void
+snd_rme96_write_SPI(struct rme96 *rme96, u16 val)
+{
+	int i;
+
+	for (i = 0; i < 16; i++) {
+		if (val & 0x8000) {
+			rme96->areg |= RME96_AR_CDATA;
+		} else {
+			rme96->areg &= ~RME96_AR_CDATA;
+		}
+		rme96->areg &= ~(RME96_AR_CCLK | RME96_AR_CLATCH);
+		writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
+		udelay(10);
+		rme96->areg |= RME96_AR_CCLK;
+		writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
+		udelay(10);
+		val <<= 1;
+	}
+	rme96->areg &= ~(RME96_AR_CCLK | RME96_AR_CDATA);
+	rme96->areg |= RME96_AR_CLATCH;
+	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
+	udelay(10);
+	rme96->areg &= ~RME96_AR_CLATCH;
+	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
+}
+
+static void
+snd_rme96_apply_dac_volume(struct rme96 *rme96)
+{
+	if (RME96_DAC_IS_1852(rme96)) {
+		snd_rme96_write_SPI(rme96, (rme96->vol[0] << 2) | 0x0);
+		snd_rme96_write_SPI(rme96, (rme96->vol[1] << 2) | 0x2);
+	} else if (RME96_DAC_IS_1855(rme96)) {
+		snd_rme96_write_SPI(rme96, (rme96->vol[0] & 0x3FF) | 0x000);
+		snd_rme96_write_SPI(rme96, (rme96->vol[1] & 0x3FF) | 0x400);
+	}
+}
+
+static void
+snd_rme96_reset_dac(struct rme96 *rme96)
+{
+	writel(rme96->wcreg | RME96_WCR_PD,
+	       rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+}
+
+static int
+snd_rme96_getmontracks(struct rme96 *rme96)
+{
+	return ((rme96->wcreg >> RME96_WCR_BITPOS_MONITOR_0) & 1) +
+		(((rme96->wcreg >> RME96_WCR_BITPOS_MONITOR_1) & 1) << 1);
+}
+
+static int
+snd_rme96_setmontracks(struct rme96 *rme96,
+		       int montracks)
+{
+	if (montracks & 1) {
+		rme96->wcreg |= RME96_WCR_MONITOR_0;
+	} else {
+		rme96->wcreg &= ~RME96_WCR_MONITOR_0;
+	}
+	if (montracks & 2) {
+		rme96->wcreg |= RME96_WCR_MONITOR_1;
+	} else {
+		rme96->wcreg &= ~RME96_WCR_MONITOR_1;
+	}
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	return 0;
+}
+
+static int
+snd_rme96_getattenuation(struct rme96 *rme96)
+{
+	return ((rme96->wcreg >> RME96_WCR_BITPOS_GAIN_0) & 1) +
+		(((rme96->wcreg >> RME96_WCR_BITPOS_GAIN_1) & 1) << 1);
+}
+
+static int
+snd_rme96_setattenuation(struct rme96 *rme96,
+			 int attenuation)
+{
+	switch (attenuation) {
+	case 0:
+		rme96->wcreg = (rme96->wcreg & ~RME96_WCR_GAIN_0) &
+			~RME96_WCR_GAIN_1;
+		break;
+	case 1:
+		rme96->wcreg = (rme96->wcreg | RME96_WCR_GAIN_0) &
+			~RME96_WCR_GAIN_1;
+		break;
+	case 2:
+		rme96->wcreg = (rme96->wcreg & ~RME96_WCR_GAIN_0) |
+			RME96_WCR_GAIN_1;
+		break;
+	case 3:
+		rme96->wcreg = (rme96->wcreg | RME96_WCR_GAIN_0) |
+			RME96_WCR_GAIN_1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	return 0;
+}
+
+static int
+snd_rme96_capture_getrate(struct rme96 *rme96,
+			  int *is_adat)
+{	
+	int n, rate;
+
+	*is_adat = 0;
+	if (rme96->areg & RME96_AR_ANALOG) {
+		/* Analog input, overrides S/PDIF setting */
+		n = ((rme96->areg >> RME96_AR_BITPOS_F0) & 1) +
+			(((rme96->areg >> RME96_AR_BITPOS_F1) & 1) << 1);
+		switch (n) {
+		case 1:
+			rate = 32000;
+			break;
+		case 2:
+			rate = 44100;
+			break;
+		case 3:
+			rate = 48000;
+			break;
+		default:
+			return -1;
+		}
+		return (rme96->areg & RME96_AR_BITPOS_F2) ? rate << 1 : rate;
+	}
+
+	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	if (rme96->rcreg & RME96_RCR_LOCK) {
+		/* ADAT rate */
+		*is_adat = 1;
+		if (rme96->rcreg & RME96_RCR_T_OUT) {
+			return 48000;
+		}
+		return 44100;
+	}
+
+	if (rme96->rcreg & RME96_RCR_VERF) {
+		return -1;
+	}
+	
+	/* S/PDIF rate */
+	n = ((rme96->rcreg >> RME96_RCR_BITPOS_F0) & 1) +
+		(((rme96->rcreg >> RME96_RCR_BITPOS_F1) & 1) << 1) +
+		(((rme96->rcreg >> RME96_RCR_BITPOS_F2) & 1) << 2);
+	
+	switch (n) {
+	case 0:		
+		if (rme96->rcreg & RME96_RCR_T_OUT) {
+			return 64000;
+		}
+		return -1;
+	case 3: return 96000;
+	case 4: return 88200;
+	case 5: return 48000;
+	case 6: return 44100;
+	case 7: return 32000;
+	default:
+		break;
+	}
+	return -1;
+}
+
+static int
+snd_rme96_playback_getrate(struct rme96 *rme96)
+{
+	int rate, dummy;
+
+	if (!(rme96->wcreg & RME96_WCR_MASTER) &&
+            snd_rme96_getinputtype(rme96) != RME96_INPUT_ANALOG &&
+	    (rate = snd_rme96_capture_getrate(rme96, &dummy)) > 0)
+	{
+	        /* slave clock */
+	        return rate;
+	}
+	rate = ((rme96->wcreg >> RME96_WCR_BITPOS_FREQ_0) & 1) +
+		(((rme96->wcreg >> RME96_WCR_BITPOS_FREQ_1) & 1) << 1);
+	switch (rate) {
+	case 1:
+		rate = 32000;
+		break;
+	case 2:
+		rate = 44100;
+		break;
+	case 3:
+		rate = 48000;
+		break;
+	default:
+		return -1;
+	}
+	return (rme96->wcreg & RME96_WCR_DS) ? rate << 1 : rate;
+}
+
+static int
+snd_rme96_playback_setrate(struct rme96 *rme96,
+			   int rate)
+{
+	int ds;
+
+	ds = rme96->wcreg & RME96_WCR_DS;
+	switch (rate) {
+	case 32000:
+		rme96->wcreg &= ~RME96_WCR_DS;
+		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) &
+			~RME96_WCR_FREQ_1;
+		break;
+	case 44100:
+		rme96->wcreg &= ~RME96_WCR_DS;
+		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_1) &
+			~RME96_WCR_FREQ_0;
+		break;
+	case 48000:
+		rme96->wcreg &= ~RME96_WCR_DS;
+		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) |
+			RME96_WCR_FREQ_1;
+		break;
+	case 64000:
+		rme96->wcreg |= RME96_WCR_DS;
+		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) &
+			~RME96_WCR_FREQ_1;
+		break;
+	case 88200:
+		rme96->wcreg |= RME96_WCR_DS;
+		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_1) &
+			~RME96_WCR_FREQ_0;
+		break;
+	case 96000:
+		rme96->wcreg |= RME96_WCR_DS;
+		rme96->wcreg = (rme96->wcreg | RME96_WCR_FREQ_0) |
+			RME96_WCR_FREQ_1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if ((!ds && rme96->wcreg & RME96_WCR_DS) ||
+	    (ds && !(rme96->wcreg & RME96_WCR_DS)))
+	{
+		/* change to/from double-speed: reset the DAC (if available) */
+		snd_rme96_reset_dac(rme96);
+	} else {
+		writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	}
+	return 0;
+}
+
+static int
+snd_rme96_capture_analog_setrate(struct rme96 *rme96,
+				 int rate)
+{
+	switch (rate) {
+	case 32000:
+		rme96->areg = ((rme96->areg | RME96_AR_FREQPAD_0) &
+			       ~RME96_AR_FREQPAD_1) & ~RME96_AR_FREQPAD_2;
+		break;
+	case 44100:
+		rme96->areg = ((rme96->areg & ~RME96_AR_FREQPAD_0) |
+			       RME96_AR_FREQPAD_1) & ~RME96_AR_FREQPAD_2;
+		break;
+	case 48000:
+		rme96->areg = ((rme96->areg | RME96_AR_FREQPAD_0) |
+			       RME96_AR_FREQPAD_1) & ~RME96_AR_FREQPAD_2;
+		break;
+	case 64000:
+		if (rme96->rev < 4) {
+			return -EINVAL;
+		}
+		rme96->areg = ((rme96->areg | RME96_AR_FREQPAD_0) &
+			       ~RME96_AR_FREQPAD_1) | RME96_AR_FREQPAD_2;
+		break;
+	case 88200:
+		if (rme96->rev < 4) {
+			return -EINVAL;
+		}
+		rme96->areg = ((rme96->areg & ~RME96_AR_FREQPAD_0) |
+			       RME96_AR_FREQPAD_1) | RME96_AR_FREQPAD_2;
+		break;
+	case 96000:
+		rme96->areg = ((rme96->areg | RME96_AR_FREQPAD_0) |
+			       RME96_AR_FREQPAD_1) | RME96_AR_FREQPAD_2;
+		break;
+	default:
+		return -EINVAL;
+	}
+	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
+	return 0;
+}
+
+static int
+snd_rme96_setclockmode(struct rme96 *rme96,
+		       int mode)
+{
+	switch (mode) {
+	case RME96_CLOCKMODE_SLAVE:
+	        /* AutoSync */ 
+		rme96->wcreg &= ~RME96_WCR_MASTER;
+		rme96->areg &= ~RME96_AR_WSEL;
+		break;
+	case RME96_CLOCKMODE_MASTER:
+	        /* Internal */
+		rme96->wcreg |= RME96_WCR_MASTER;
+		rme96->areg &= ~RME96_AR_WSEL;
+		break;
+	case RME96_CLOCKMODE_WORDCLOCK:
+		/* Word clock is a master mode */
+		rme96->wcreg |= RME96_WCR_MASTER; 
+		rme96->areg |= RME96_AR_WSEL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
+	return 0;
+}
+
+static int
+snd_rme96_getclockmode(struct rme96 *rme96)
+{
+	if (rme96->areg & RME96_AR_WSEL) {
+		return RME96_CLOCKMODE_WORDCLOCK;
+	}
+	return (rme96->wcreg & RME96_WCR_MASTER) ? RME96_CLOCKMODE_MASTER :
+		RME96_CLOCKMODE_SLAVE;
+}
+
+static int
+snd_rme96_setinputtype(struct rme96 *rme96,
+		       int type)
+{
+	int n;
+
+	switch (type) {
+	case RME96_INPUT_OPTICAL:
+		rme96->wcreg = (rme96->wcreg & ~RME96_WCR_INP_0) &
+			~RME96_WCR_INP_1;
+		break;
+	case RME96_INPUT_COAXIAL:
+		rme96->wcreg = (rme96->wcreg | RME96_WCR_INP_0) &
+			~RME96_WCR_INP_1;
+		break;
+	case RME96_INPUT_INTERNAL:
+		rme96->wcreg = (rme96->wcreg & ~RME96_WCR_INP_0) |
+			RME96_WCR_INP_1;
+		break;
+	case RME96_INPUT_XLR:
+		if ((rme96->pci->device != PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST &&
+		     rme96->pci->device != PCI_DEVICE_ID_RME_DIGI96_8_PRO) ||
+		    (rme96->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST &&
+		     rme96->rev > 4))
+		{
+			/* Only Digi96/8 PRO and Digi96/8 PAD supports XLR */
+			return -EINVAL;
+		}
+		rme96->wcreg = (rme96->wcreg | RME96_WCR_INP_0) |
+			RME96_WCR_INP_1;
+		break;
+	case RME96_INPUT_ANALOG:
+		if (!RME96_HAS_ANALOG_IN(rme96)) {
+			return -EINVAL;
+		}
+		rme96->areg |= RME96_AR_ANALOG;
+		writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
+		if (rme96->rev < 4) {
+			/*
+			 * Revision less than 004 does not support 64 and
+			 * 88.2 kHz
+			 */
+			if (snd_rme96_capture_getrate(rme96, &n) == 88200) {
+				snd_rme96_capture_analog_setrate(rme96, 44100);
+			}
+			if (snd_rme96_capture_getrate(rme96, &n) == 64000) {
+				snd_rme96_capture_analog_setrate(rme96, 32000);
+			}
+		}
+		return 0;
+	default:
+		return -EINVAL;
+	}
+	if (type != RME96_INPUT_ANALOG && RME96_HAS_ANALOG_IN(rme96)) {
+		rme96->areg &= ~RME96_AR_ANALOG;
+		writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
+	}
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	return 0;
+}
+
+static int
+snd_rme96_getinputtype(struct rme96 *rme96)
+{
+	if (rme96->areg & RME96_AR_ANALOG) {
+		return RME96_INPUT_ANALOG;
+	}
+	return ((rme96->wcreg >> RME96_WCR_BITPOS_INP_0) & 1) +
+		(((rme96->wcreg >> RME96_WCR_BITPOS_INP_1) & 1) << 1);
+}
+
+static void
+snd_rme96_setframelog(struct rme96 *rme96,
+		      int n_channels,
+		      int is_playback)
+{
+	int frlog;
+	
+	if (n_channels == 2) {
+		frlog = 1;
+	} else {
+		/* assume 8 channels */
+		frlog = 3;
+	}
+	if (is_playback) {
+		frlog += (rme96->wcreg & RME96_WCR_MODE24) ? 2 : 1;
+		rme96->playback_frlog = frlog;
+	} else {
+		frlog += (rme96->wcreg & RME96_WCR_MODE24_2) ? 2 : 1;
+		rme96->capture_frlog = frlog;
+	}
+}
+
+static int
+snd_rme96_playback_setformat(struct rme96 *rme96,
+			     int format)
+{
+	switch (format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		rme96->wcreg &= ~RME96_WCR_MODE24;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		rme96->wcreg |= RME96_WCR_MODE24;
+		break;
+	default:
+		return -EINVAL;
+	}
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	return 0;
+}
+
+static int
+snd_rme96_capture_setformat(struct rme96 *rme96,
+			    int format)
+{
+	switch (format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		rme96->wcreg &= ~RME96_WCR_MODE24_2;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		rme96->wcreg |= RME96_WCR_MODE24_2;
+		break;
+	default:
+		return -EINVAL;
+	}
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	return 0;
+}
+
+static void
+snd_rme96_set_period_properties(struct rme96 *rme96,
+				size_t period_bytes)
+{
+	switch (period_bytes) {
+	case RME96_LARGE_BLOCK_SIZE:
+		rme96->wcreg &= ~RME96_WCR_ISEL;
+		break;
+	case RME96_SMALL_BLOCK_SIZE:
+		rme96->wcreg |= RME96_WCR_ISEL;
+		break;
+	default:
+		snd_BUG();
+		break;
+	}
+	rme96->wcreg &= ~RME96_WCR_IDIS;
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+}
+
+static int
+snd_rme96_playback_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err, rate, dummy;
+
+	runtime->dma_area = (void __force *)(rme96->iobase +
+					     RME96_IO_PLAY_BUFFER);
+	runtime->dma_addr = rme96->port + RME96_IO_PLAY_BUFFER;
+	runtime->dma_bytes = RME96_BUFFER_SIZE;
+
+	spin_lock_irq(&rme96->lock);
+	if (!(rme96->wcreg & RME96_WCR_MASTER) &&
+            snd_rme96_getinputtype(rme96) != RME96_INPUT_ANALOG &&
+	    (rate = snd_rme96_capture_getrate(rme96, &dummy)) > 0)
+	{
+                /* slave clock */
+                if ((int)params_rate(params) != rate) {
+			spin_unlock_irq(&rme96->lock);
+			return -EIO;                    
+                }
+	} else if ((err = snd_rme96_playback_setrate(rme96, params_rate(params))) < 0) {
+		spin_unlock_irq(&rme96->lock);
+		return err;
+	}
+	if ((err = snd_rme96_playback_setformat(rme96, params_format(params))) < 0) {
+		spin_unlock_irq(&rme96->lock);
+		return err;
+	}
+	snd_rme96_setframelog(rme96, params_channels(params), 1);
+	if (rme96->capture_periodsize != 0) {
+		if (params_period_size(params) << rme96->playback_frlog !=
+		    rme96->capture_periodsize)
+		{
+			spin_unlock_irq(&rme96->lock);
+			return -EBUSY;
+		}
+	}
+	rme96->playback_periodsize =
+		params_period_size(params) << rme96->playback_frlog;
+	snd_rme96_set_period_properties(rme96, rme96->playback_periodsize);
+	/* S/PDIF setup */
+	if ((rme96->wcreg & RME96_WCR_ADAT) == 0) {
+		rme96->wcreg &= ~(RME96_WCR_PRO | RME96_WCR_DOLBY | RME96_WCR_EMP);
+		writel(rme96->wcreg |= rme96->wcreg_spdif_stream, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	}
+	spin_unlock_irq(&rme96->lock);
+		
+	return 0;
+}
+
+static int
+snd_rme96_capture_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err, isadat, rate;
+	
+	runtime->dma_area = (void __force *)(rme96->iobase +
+					     RME96_IO_REC_BUFFER);
+	runtime->dma_addr = rme96->port + RME96_IO_REC_BUFFER;
+	runtime->dma_bytes = RME96_BUFFER_SIZE;
+
+	spin_lock_irq(&rme96->lock);
+	if ((err = snd_rme96_capture_setformat(rme96, params_format(params))) < 0) {
+		spin_unlock_irq(&rme96->lock);
+		return err;
+	}
+	if (snd_rme96_getinputtype(rme96) == RME96_INPUT_ANALOG) {
+		if ((err = snd_rme96_capture_analog_setrate(rme96,
+							    params_rate(params))) < 0)
+		{
+			spin_unlock_irq(&rme96->lock);
+			return err;
+		}
+	} else if ((rate = snd_rme96_capture_getrate(rme96, &isadat)) > 0) {
+                if ((int)params_rate(params) != rate) {
+			spin_unlock_irq(&rme96->lock);
+			return -EIO;                    
+                }
+                if ((isadat && runtime->hw.channels_min == 2) ||
+                    (!isadat && runtime->hw.channels_min == 8))
+                {
+			spin_unlock_irq(&rme96->lock);
+			return -EIO;
+                }
+        }
+	snd_rme96_setframelog(rme96, params_channels(params), 0);
+	if (rme96->playback_periodsize != 0) {
+		if (params_period_size(params) << rme96->capture_frlog !=
+		    rme96->playback_periodsize)
+		{
+			spin_unlock_irq(&rme96->lock);
+			return -EBUSY;
+		}
+	}
+	rme96->capture_periodsize =
+		params_period_size(params) << rme96->capture_frlog;
+	snd_rme96_set_period_properties(rme96, rme96->capture_periodsize);
+	spin_unlock_irq(&rme96->lock);
+
+	return 0;
+}
+
+static void
+snd_rme96_playback_start(struct rme96 *rme96,
+			 int from_pause)
+{
+	if (!from_pause) {
+		writel(0, rme96->iobase + RME96_IO_RESET_PLAY_POS);
+	}
+
+	rme96->wcreg |= RME96_WCR_START;
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+}
+
+static void
+snd_rme96_capture_start(struct rme96 *rme96,
+			int from_pause)
+{
+	if (!from_pause) {
+		writel(0, rme96->iobase + RME96_IO_RESET_REC_POS);
+	}
+
+	rme96->wcreg |= RME96_WCR_START_2;
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+}
+
+static void
+snd_rme96_playback_stop(struct rme96 *rme96)
+{
+	/*
+	 * Check if there is an unconfirmed IRQ, if so confirm it, or else
+	 * the hardware will not stop generating interrupts
+	 */
+	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	if (rme96->rcreg & RME96_RCR_IRQ) {
+		writel(0, rme96->iobase + RME96_IO_CONFIRM_PLAY_IRQ);
+	}	
+	rme96->wcreg &= ~RME96_WCR_START;
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+}
+
+static void
+snd_rme96_capture_stop(struct rme96 *rme96)
+{
+	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	if (rme96->rcreg & RME96_RCR_IRQ_2) {
+		writel(0, rme96->iobase + RME96_IO_CONFIRM_REC_IRQ);
+	}	
+	rme96->wcreg &= ~RME96_WCR_START_2;
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+}
+
+static irqreturn_t
+snd_rme96_interrupt(int irq,
+		    void *dev_id)
+{
+	struct rme96 *rme96 = (struct rme96 *)dev_id;
+
+	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	/* fastpath out, to ease interrupt sharing */
+	if (!((rme96->rcreg & RME96_RCR_IRQ) ||
+	      (rme96->rcreg & RME96_RCR_IRQ_2)))
+	{
+		return IRQ_NONE;
+	}
+	
+	if (rme96->rcreg & RME96_RCR_IRQ) {
+		/* playback */
+                snd_pcm_period_elapsed(rme96->playback_substream);
+		writel(0, rme96->iobase + RME96_IO_CONFIRM_PLAY_IRQ);
+	}
+	if (rme96->rcreg & RME96_RCR_IRQ_2) {
+		/* capture */
+		snd_pcm_period_elapsed(rme96->capture_substream);		
+		writel(0, rme96->iobase + RME96_IO_CONFIRM_REC_IRQ);
+	}
+	return IRQ_HANDLED;
+}
+
+static unsigned int period_bytes[] = { RME96_SMALL_BLOCK_SIZE, RME96_LARGE_BLOCK_SIZE };
+
+static struct snd_pcm_hw_constraint_list hw_constraints_period_bytes = {
+	.count = ARRAY_SIZE(period_bytes),
+	.list = period_bytes,
+	.mask = 0
+};
+
+static void
+rme96_set_buffer_size_constraint(struct rme96 *rme96,
+				 struct snd_pcm_runtime *runtime)
+{
+	unsigned int size;
+
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+				     RME96_BUFFER_SIZE, RME96_BUFFER_SIZE);
+	if ((size = rme96->playback_periodsize) != 0 ||
+	    (size = rme96->capture_periodsize) != 0)
+		snd_pcm_hw_constraint_minmax(runtime,
+					     SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+					     size, size);
+	else
+		snd_pcm_hw_constraint_list(runtime, 0,
+					   SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+					   &hw_constraints_period_bytes);
+}
+
+static int
+snd_rme96_playback_spdif_open(struct snd_pcm_substream *substream)
+{
+        int rate, dummy;
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	spin_lock_irq(&rme96->lock);	
+        if (rme96->playback_substream != NULL) {
+		spin_unlock_irq(&rme96->lock);
+                return -EBUSY;
+        }
+	rme96->wcreg &= ~RME96_WCR_ADAT;
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	rme96->playback_substream = substream;
+	spin_unlock_irq(&rme96->lock);
+
+	runtime->hw = snd_rme96_playback_spdif_info;
+	if (!(rme96->wcreg & RME96_WCR_MASTER) &&
+            snd_rme96_getinputtype(rme96) != RME96_INPUT_ANALOG &&
+	    (rate = snd_rme96_capture_getrate(rme96, &dummy)) > 0)
+	{
+                /* slave clock */
+                runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
+                runtime->hw.rate_min = rate;
+                runtime->hw.rate_max = rate;
+	}        
+	rme96_set_buffer_size_constraint(rme96, runtime);
+
+	rme96->wcreg_spdif_stream = rme96->wcreg_spdif;
+	rme96->spdif_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(rme96->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &rme96->spdif_ctl->id);
+	return 0;
+}
+
+static int
+snd_rme96_capture_spdif_open(struct snd_pcm_substream *substream)
+{
+        int isadat, rate;
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = snd_rme96_capture_spdif_info;
+        if (snd_rme96_getinputtype(rme96) != RME96_INPUT_ANALOG &&
+            (rate = snd_rme96_capture_getrate(rme96, &isadat)) > 0)
+        {
+                if (isadat) {
+                        return -EIO;
+                }
+                runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
+                runtime->hw.rate_min = rate;
+                runtime->hw.rate_max = rate;
+        }
+        
+	spin_lock_irq(&rme96->lock);
+        if (rme96->capture_substream != NULL) {
+		spin_unlock_irq(&rme96->lock);
+                return -EBUSY;
+        }
+	rme96->capture_substream = substream;
+	spin_unlock_irq(&rme96->lock);
+	
+	rme96_set_buffer_size_constraint(rme96, runtime);
+	return 0;
+}
+
+static int
+snd_rme96_playback_adat_open(struct snd_pcm_substream *substream)
+{
+        int rate, dummy;
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;        
+	
+	spin_lock_irq(&rme96->lock);	
+        if (rme96->playback_substream != NULL) {
+		spin_unlock_irq(&rme96->lock);
+                return -EBUSY;
+        }
+	rme96->wcreg |= RME96_WCR_ADAT;
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	rme96->playback_substream = substream;
+	spin_unlock_irq(&rme96->lock);
+	
+	runtime->hw = snd_rme96_playback_adat_info;
+	if (!(rme96->wcreg & RME96_WCR_MASTER) &&
+            snd_rme96_getinputtype(rme96) != RME96_INPUT_ANALOG &&
+	    (rate = snd_rme96_capture_getrate(rme96, &dummy)) > 0)
+	{
+                /* slave clock */
+                runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
+                runtime->hw.rate_min = rate;
+                runtime->hw.rate_max = rate;
+	}        
+	rme96_set_buffer_size_constraint(rme96, runtime);
+	return 0;
+}
+
+static int
+snd_rme96_capture_adat_open(struct snd_pcm_substream *substream)
+{
+        int isadat, rate;
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = snd_rme96_capture_adat_info;
+        if (snd_rme96_getinputtype(rme96) == RME96_INPUT_ANALOG) {
+                /* makes no sense to use analog input. Note that analog
+                   expension cards AEB4/8-I are RME96_INPUT_INTERNAL */
+                return -EIO;
+        }
+        if ((rate = snd_rme96_capture_getrate(rme96, &isadat)) > 0) {
+                if (!isadat) {
+                        return -EIO;
+                }
+                runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
+                runtime->hw.rate_min = rate;
+                runtime->hw.rate_max = rate;
+        }
+        
+	spin_lock_irq(&rme96->lock);	
+        if (rme96->capture_substream != NULL) {
+		spin_unlock_irq(&rme96->lock);
+                return -EBUSY;
+        }
+	rme96->capture_substream = substream;
+	spin_unlock_irq(&rme96->lock);
+
+	rme96_set_buffer_size_constraint(rme96, runtime);
+	return 0;
+}
+
+static int
+snd_rme96_playback_close(struct snd_pcm_substream *substream)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	int spdif = 0;
+
+	spin_lock_irq(&rme96->lock);	
+	if (RME96_ISPLAYING(rme96)) {
+		snd_rme96_playback_stop(rme96);
+	}
+	rme96->playback_substream = NULL;
+	rme96->playback_periodsize = 0;
+	spdif = (rme96->wcreg & RME96_WCR_ADAT) == 0;
+	spin_unlock_irq(&rme96->lock);
+	if (spdif) {
+		rme96->spdif_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(rme96->card, SNDRV_CTL_EVENT_MASK_VALUE |
+			       SNDRV_CTL_EVENT_MASK_INFO, &rme96->spdif_ctl->id);
+	}
+	return 0;
+}
+
+static int
+snd_rme96_capture_close(struct snd_pcm_substream *substream)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	
+	spin_lock_irq(&rme96->lock);	
+	if (RME96_ISRECORDING(rme96)) {
+		snd_rme96_capture_stop(rme96);
+	}
+	rme96->capture_substream = NULL;
+	rme96->capture_periodsize = 0;
+	spin_unlock_irq(&rme96->lock);
+	return 0;
+}
+
+static int
+snd_rme96_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	
+	spin_lock_irq(&rme96->lock);	
+	if (RME96_ISPLAYING(rme96)) {
+		snd_rme96_playback_stop(rme96);
+	}
+	writel(0, rme96->iobase + RME96_IO_RESET_PLAY_POS);
+	spin_unlock_irq(&rme96->lock);
+	return 0;
+}
+
+static int
+snd_rme96_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	
+	spin_lock_irq(&rme96->lock);	
+	if (RME96_ISRECORDING(rme96)) {
+		snd_rme96_capture_stop(rme96);
+	}
+	writel(0, rme96->iobase + RME96_IO_RESET_REC_POS);
+	spin_unlock_irq(&rme96->lock);
+	return 0;
+}
+
+static int
+snd_rme96_playback_trigger(struct snd_pcm_substream *substream, 
+			   int cmd)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (!RME96_ISPLAYING(rme96)) {
+			if (substream != rme96->playback_substream) {
+				return -EBUSY;
+			}
+			snd_rme96_playback_start(rme96, 0);
+		}
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (RME96_ISPLAYING(rme96)) {
+			if (substream != rme96->playback_substream) {
+				return -EBUSY;
+			}
+			snd_rme96_playback_stop(rme96);
+		}
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (RME96_ISPLAYING(rme96)) {
+			snd_rme96_playback_stop(rme96);
+		}
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (!RME96_ISPLAYING(rme96)) {
+			snd_rme96_playback_start(rme96, 1);
+		}
+		break;
+		
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int
+snd_rme96_capture_trigger(struct snd_pcm_substream *substream, 
+			  int cmd)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (!RME96_ISRECORDING(rme96)) {
+			if (substream != rme96->capture_substream) {
+				return -EBUSY;
+			}
+			snd_rme96_capture_start(rme96, 0);
+		}
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (RME96_ISRECORDING(rme96)) {
+			if (substream != rme96->capture_substream) {
+				return -EBUSY;
+			}
+			snd_rme96_capture_stop(rme96);
+		}
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (RME96_ISRECORDING(rme96)) {
+			snd_rme96_capture_stop(rme96);
+		}
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (!RME96_ISRECORDING(rme96)) {
+			snd_rme96_capture_start(rme96, 1);
+		}
+		break;
+		
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_rme96_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	return snd_rme96_playback_ptr(rme96);
+}
+
+static snd_pcm_uframes_t
+snd_rme96_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct rme96 *rme96 = snd_pcm_substream_chip(substream);
+	return snd_rme96_capture_ptr(rme96);
+}
+
+static struct snd_pcm_ops snd_rme96_playback_spdif_ops = {
+	.open =		snd_rme96_playback_spdif_open,
+	.close =	snd_rme96_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme96_playback_hw_params,
+	.prepare =	snd_rme96_playback_prepare,
+	.trigger =	snd_rme96_playback_trigger,
+	.pointer =	snd_rme96_playback_pointer,
+	.copy =		snd_rme96_playback_copy,
+	.silence =	snd_rme96_playback_silence,
+	.mmap =		snd_pcm_lib_mmap_iomem,
+};
+
+static struct snd_pcm_ops snd_rme96_capture_spdif_ops = {
+	.open =		snd_rme96_capture_spdif_open,
+	.close =	snd_rme96_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme96_capture_hw_params,
+	.prepare =	snd_rme96_capture_prepare,
+	.trigger =	snd_rme96_capture_trigger,
+	.pointer =	snd_rme96_capture_pointer,
+	.copy =		snd_rme96_capture_copy,
+	.mmap =		snd_pcm_lib_mmap_iomem,
+};
+
+static struct snd_pcm_ops snd_rme96_playback_adat_ops = {
+	.open =		snd_rme96_playback_adat_open,
+	.close =	snd_rme96_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme96_playback_hw_params,
+	.prepare =	snd_rme96_playback_prepare,
+	.trigger =	snd_rme96_playback_trigger,
+	.pointer =	snd_rme96_playback_pointer,
+	.copy =		snd_rme96_playback_copy,
+	.silence =	snd_rme96_playback_silence,
+	.mmap =		snd_pcm_lib_mmap_iomem,
+};
+
+static struct snd_pcm_ops snd_rme96_capture_adat_ops = {
+	.open =		snd_rme96_capture_adat_open,
+	.close =	snd_rme96_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_rme96_capture_hw_params,
+	.prepare =	snd_rme96_capture_prepare,
+	.trigger =	snd_rme96_capture_trigger,
+	.pointer =	snd_rme96_capture_pointer,
+	.copy =		snd_rme96_capture_copy,
+	.mmap =		snd_pcm_lib_mmap_iomem,
+};
+
+static void
+snd_rme96_free(void *private_data)
+{
+	struct rme96 *rme96 = (struct rme96 *)private_data;
+
+	if (rme96 == NULL) {
+	        return;
+	}
+	if (rme96->irq >= 0) {
+		snd_rme96_playback_stop(rme96);
+		snd_rme96_capture_stop(rme96);
+		rme96->areg &= ~RME96_AR_DAC_EN;
+		writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
+		free_irq(rme96->irq, (void *)rme96);
+		rme96->irq = -1;
+	}
+	if (rme96->iobase) {
+		iounmap(rme96->iobase);
+		rme96->iobase = NULL;
+	}
+	if (rme96->port) {
+		pci_release_regions(rme96->pci);
+		rme96->port = 0;
+	}
+	pci_disable_device(rme96->pci);
+}
+
+static void
+snd_rme96_free_spdif_pcm(struct snd_pcm *pcm)
+{
+	struct rme96 *rme96 = pcm->private_data;
+	rme96->spdif_pcm = NULL;
+}
+
+static void
+snd_rme96_free_adat_pcm(struct snd_pcm *pcm)
+{
+	struct rme96 *rme96 = pcm->private_data;
+	rme96->adat_pcm = NULL;
+}
+
+static int __devinit
+snd_rme96_create(struct rme96 *rme96)
+{
+	struct pci_dev *pci = rme96->pci;
+	int err;
+
+	rme96->irq = -1;
+	spin_lock_init(&rme96->lock);
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	if ((err = pci_request_regions(pci, "RME96")) < 0)
+		return err;
+	rme96->port = pci_resource_start(rme96->pci, 0);
+
+	rme96->iobase = ioremap_nocache(rme96->port, RME96_IO_SIZE);
+	if (!rme96->iobase) {
+		snd_printk(KERN_ERR "unable to remap memory region 0x%lx-0x%lx\n", rme96->port, rme96->port + RME96_IO_SIZE - 1);
+		return -ENOMEM;
+	}
+
+	if (request_irq(pci->irq, snd_rme96_interrupt, IRQF_SHARED,
+			"RME96", rme96)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		return -EBUSY;
+	}
+	rme96->irq = pci->irq;
+
+	/* read the card's revision number */
+	pci_read_config_byte(pci, 8, &rme96->rev);	
+	
+	/* set up ALSA pcm device for S/PDIF */
+	if ((err = snd_pcm_new(rme96->card, "Digi96 IEC958", 0,
+			       1, 1, &rme96->spdif_pcm)) < 0)
+	{
+		return err;
+	}
+	rme96->spdif_pcm->private_data = rme96;
+	rme96->spdif_pcm->private_free = snd_rme96_free_spdif_pcm;
+	strcpy(rme96->spdif_pcm->name, "Digi96 IEC958");
+	snd_pcm_set_ops(rme96->spdif_pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_rme96_playback_spdif_ops);
+	snd_pcm_set_ops(rme96->spdif_pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_rme96_capture_spdif_ops);
+
+	rme96->spdif_pcm->info_flags = 0;
+
+	/* set up ALSA pcm device for ADAT */
+	if (pci->device == PCI_DEVICE_ID_RME_DIGI96) {
+		/* ADAT is not available on the base model */
+		rme96->adat_pcm = NULL;
+	} else {
+		if ((err = snd_pcm_new(rme96->card, "Digi96 ADAT", 1,
+				       1, 1, &rme96->adat_pcm)) < 0)
+		{
+			return err;
+		}		
+		rme96->adat_pcm->private_data = rme96;
+		rme96->adat_pcm->private_free = snd_rme96_free_adat_pcm;
+		strcpy(rme96->adat_pcm->name, "Digi96 ADAT");
+		snd_pcm_set_ops(rme96->adat_pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_rme96_playback_adat_ops);
+		snd_pcm_set_ops(rme96->adat_pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_rme96_capture_adat_ops);
+		
+		rme96->adat_pcm->info_flags = 0;
+	}
+
+	rme96->playback_periodsize = 0;
+	rme96->capture_periodsize = 0;
+	
+	/* make sure playback/capture is stopped, if by some reason active */
+	snd_rme96_playback_stop(rme96);
+	snd_rme96_capture_stop(rme96);
+	
+	/* set default values in registers */
+	rme96->wcreg =
+		RME96_WCR_FREQ_1 | /* set 44.1 kHz playback */
+		RME96_WCR_SEL |    /* normal playback */
+		RME96_WCR_MASTER | /* set to master clock mode */
+		RME96_WCR_INP_0;   /* set coaxial input */
+
+	rme96->areg = RME96_AR_FREQPAD_1; /* set 44.1 kHz analog capture */
+
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
+	
+	/* reset the ADC */
+	writel(rme96->areg | RME96_AR_PD2,
+	       rme96->iobase + RME96_IO_ADDITIONAL_REG);
+	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);	
+
+	/* reset and enable the DAC (order is important). */
+	snd_rme96_reset_dac(rme96);
+	rme96->areg |= RME96_AR_DAC_EN;
+	writel(rme96->areg, rme96->iobase + RME96_IO_ADDITIONAL_REG);
+
+	/* reset playback and record buffer pointers */
+	writel(0, rme96->iobase + RME96_IO_RESET_PLAY_POS);
+	writel(0, rme96->iobase + RME96_IO_RESET_REC_POS);
+
+	/* reset volume */
+	rme96->vol[0] = rme96->vol[1] = 0;
+	if (RME96_HAS_ANALOG_OUT(rme96)) {
+		snd_rme96_apply_dac_volume(rme96);
+	}
+	
+	/* init switch interface */
+	if ((err = snd_rme96_create_switches(rme96->card, rme96)) < 0) {
+		return err;
+	}
+
+        /* init proc interface */
+	snd_rme96_proc_init(rme96);
+	
+	return 0;
+}
+
+/*
+ * proc interface
+ */
+
+static void 
+snd_rme96_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	int n;
+	struct rme96 *rme96 = entry->private_data;
+	
+	rme96->rcreg = readl(rme96->iobase + RME96_IO_CONTROL_REGISTER);
+
+	snd_iprintf(buffer, rme96->card->longname);
+	snd_iprintf(buffer, " (index #%d)\n", rme96->card->number + 1);
+
+	snd_iprintf(buffer, "\nGeneral settings\n");
+	if (rme96->wcreg & RME96_WCR_IDIS) {
+		snd_iprintf(buffer, "  period size: N/A (interrupts "
+			    "disabled)\n");
+	} else if (rme96->wcreg & RME96_WCR_ISEL) {
+		snd_iprintf(buffer, "  period size: 2048 bytes\n");
+	} else {
+		snd_iprintf(buffer, "  period size: 8192 bytes\n");
+	}	
+	snd_iprintf(buffer, "\nInput settings\n");
+	switch (snd_rme96_getinputtype(rme96)) {
+	case RME96_INPUT_OPTICAL:
+		snd_iprintf(buffer, "  input: optical");
+		break;
+	case RME96_INPUT_COAXIAL:
+		snd_iprintf(buffer, "  input: coaxial");
+		break;
+	case RME96_INPUT_INTERNAL:
+		snd_iprintf(buffer, "  input: internal");
+		break;
+	case RME96_INPUT_XLR:
+		snd_iprintf(buffer, "  input: XLR");
+		break;
+	case RME96_INPUT_ANALOG:
+		snd_iprintf(buffer, "  input: analog");
+		break;
+	}
+	if (snd_rme96_capture_getrate(rme96, &n) < 0) {
+		snd_iprintf(buffer, "\n  sample rate: no valid signal\n");
+	} else {
+		if (n) {
+			snd_iprintf(buffer, " (8 channels)\n");
+		} else {
+			snd_iprintf(buffer, " (2 channels)\n");
+		}
+		snd_iprintf(buffer, "  sample rate: %d Hz\n",
+			    snd_rme96_capture_getrate(rme96, &n));
+	}
+	if (rme96->wcreg & RME96_WCR_MODE24_2) {
+		snd_iprintf(buffer, "  sample format: 24 bit\n");
+	} else {
+		snd_iprintf(buffer, "  sample format: 16 bit\n");
+	}
+	
+	snd_iprintf(buffer, "\nOutput settings\n");
+	if (rme96->wcreg & RME96_WCR_SEL) {
+		snd_iprintf(buffer, "  output signal: normal playback\n");
+	} else {
+		snd_iprintf(buffer, "  output signal: same as input\n");
+	}
+	snd_iprintf(buffer, "  sample rate: %d Hz\n",
+		    snd_rme96_playback_getrate(rme96));
+	if (rme96->wcreg & RME96_WCR_MODE24) {
+		snd_iprintf(buffer, "  sample format: 24 bit\n");
+	} else {
+		snd_iprintf(buffer, "  sample format: 16 bit\n");
+	}
+	if (rme96->areg & RME96_AR_WSEL) {
+		snd_iprintf(buffer, "  sample clock source: word clock\n");
+	} else if (rme96->wcreg & RME96_WCR_MASTER) {
+		snd_iprintf(buffer, "  sample clock source: internal\n");
+	} else if (snd_rme96_getinputtype(rme96) == RME96_INPUT_ANALOG) {
+		snd_iprintf(buffer, "  sample clock source: autosync (internal anyway due to analog input setting)\n");
+	} else if (snd_rme96_capture_getrate(rme96, &n) < 0) {
+		snd_iprintf(buffer, "  sample clock source: autosync (internal anyway due to no valid signal)\n");
+	} else {
+		snd_iprintf(buffer, "  sample clock source: autosync\n");
+	}
+	if (rme96->wcreg & RME96_WCR_PRO) {
+		snd_iprintf(buffer, "  format: AES/EBU (professional)\n");
+	} else {
+		snd_iprintf(buffer, "  format: IEC958 (consumer)\n");
+	}
+	if (rme96->wcreg & RME96_WCR_EMP) {
+		snd_iprintf(buffer, "  emphasis: on\n");
+	} else {
+		snd_iprintf(buffer, "  emphasis: off\n");
+	}
+	if (rme96->wcreg & RME96_WCR_DOLBY) {
+		snd_iprintf(buffer, "  non-audio (dolby): on\n");
+	} else {
+		snd_iprintf(buffer, "  non-audio (dolby): off\n");
+	}
+	if (RME96_HAS_ANALOG_IN(rme96)) {
+		snd_iprintf(buffer, "\nAnalog output settings\n");
+		switch (snd_rme96_getmontracks(rme96)) {
+		case RME96_MONITOR_TRACKS_1_2:
+			snd_iprintf(buffer, "  monitored ADAT tracks: 1+2\n");
+			break;
+		case RME96_MONITOR_TRACKS_3_4:
+			snd_iprintf(buffer, "  monitored ADAT tracks: 3+4\n");
+			break;
+		case RME96_MONITOR_TRACKS_5_6:
+			snd_iprintf(buffer, "  monitored ADAT tracks: 5+6\n");
+			break;
+		case RME96_MONITOR_TRACKS_7_8:
+			snd_iprintf(buffer, "  monitored ADAT tracks: 7+8\n");
+			break;
+		}
+		switch (snd_rme96_getattenuation(rme96)) {
+		case RME96_ATTENUATION_0:
+			snd_iprintf(buffer, "  attenuation: 0 dB\n");
+			break;
+		case RME96_ATTENUATION_6:
+			snd_iprintf(buffer, "  attenuation: -6 dB\n");
+			break;
+		case RME96_ATTENUATION_12:
+			snd_iprintf(buffer, "  attenuation: -12 dB\n");
+			break;
+		case RME96_ATTENUATION_18:
+			snd_iprintf(buffer, "  attenuation: -18 dB\n");
+			break;
+		}
+		snd_iprintf(buffer, "  volume left: %u\n", rme96->vol[0]);
+		snd_iprintf(buffer, "  volume right: %u\n", rme96->vol[1]);
+	}
+}
+
+static void __devinit 
+snd_rme96_proc_init(struct rme96 *rme96)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(rme96->card, "rme96", &entry))
+		snd_info_set_text_ops(entry, rme96, snd_rme96_proc_read);
+}
+
+/*
+ * control interface
+ */
+
+#define snd_rme96_info_loopback_control		snd_ctl_boolean_mono_info
+
+static int
+snd_rme96_get_loopback_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&rme96->lock);
+	ucontrol->value.integer.value[0] = rme96->wcreg & RME96_WCR_SEL ? 0 : 1;
+	spin_unlock_irq(&rme96->lock);
+	return 0;
+}
+static int
+snd_rme96_put_loopback_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+	
+	val = ucontrol->value.integer.value[0] ? 0 : RME96_WCR_SEL;
+	spin_lock_irq(&rme96->lock);
+	val = (rme96->wcreg & ~RME96_WCR_SEL) | val;
+	change = val != rme96->wcreg;
+	rme96->wcreg = val;
+	writel(val, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	spin_unlock_irq(&rme96->lock);
+	return change;
+}
+
+static int
+snd_rme96_info_inputtype_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *_texts[5] = { "Optical", "Coaxial", "Internal", "XLR", "Analog" };
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	char *texts[5] = { _texts[0], _texts[1], _texts[2], _texts[3], _texts[4] };
+	
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	switch (rme96->pci->device) {
+	case PCI_DEVICE_ID_RME_DIGI96:
+	case PCI_DEVICE_ID_RME_DIGI96_8:
+		uinfo->value.enumerated.items = 3;
+		break;
+	case PCI_DEVICE_ID_RME_DIGI96_8_PRO:
+		uinfo->value.enumerated.items = 4;
+		break;
+	case PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST:
+		if (rme96->rev > 4) {
+			/* PST */
+			uinfo->value.enumerated.items = 4;
+			texts[3] = _texts[4]; /* Analog instead of XLR */
+		} else {
+			/* PAD */
+			uinfo->value.enumerated.items = 5;
+		}
+		break;
+	default:
+		snd_BUG();
+		break;
+	}
+	if (uinfo->value.enumerated.item > uinfo->value.enumerated.items - 1) {
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	}
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+static int
+snd_rme96_get_inputtype_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	unsigned int items = 3;
+	
+	spin_lock_irq(&rme96->lock);
+	ucontrol->value.enumerated.item[0] = snd_rme96_getinputtype(rme96);
+	
+	switch (rme96->pci->device) {
+	case PCI_DEVICE_ID_RME_DIGI96:
+	case PCI_DEVICE_ID_RME_DIGI96_8:
+		items = 3;
+		break;
+	case PCI_DEVICE_ID_RME_DIGI96_8_PRO:
+		items = 4;
+		break;
+	case PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST:
+		if (rme96->rev > 4) {
+			/* for handling PST case, (INPUT_ANALOG is moved to INPUT_XLR */
+			if (ucontrol->value.enumerated.item[0] == RME96_INPUT_ANALOG) {
+				ucontrol->value.enumerated.item[0] = RME96_INPUT_XLR;
+			}
+			items = 4;
+		} else {
+			items = 5;
+		}
+		break;
+	default:
+		snd_BUG();
+		break;
+	}
+	if (ucontrol->value.enumerated.item[0] >= items) {
+		ucontrol->value.enumerated.item[0] = items - 1;
+	}
+	
+	spin_unlock_irq(&rme96->lock);
+	return 0;
+}
+static int
+snd_rme96_put_inputtype_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change, items = 3;
+	
+	switch (rme96->pci->device) {
+	case PCI_DEVICE_ID_RME_DIGI96:
+	case PCI_DEVICE_ID_RME_DIGI96_8:
+		items = 3;
+		break;
+	case PCI_DEVICE_ID_RME_DIGI96_8_PRO:
+		items = 4;
+		break;
+	case PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST:
+		if (rme96->rev > 4) {
+			items = 4;
+		} else {
+			items = 5;
+		}
+		break;
+	default:
+		snd_BUG();
+		break;
+	}
+	val = ucontrol->value.enumerated.item[0] % items;
+	
+	/* special case for PST */
+	if (rme96->pci->device == PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST && rme96->rev > 4) {
+		if (val == RME96_INPUT_XLR) {
+			val = RME96_INPUT_ANALOG;
+		}
+	}
+	
+	spin_lock_irq(&rme96->lock);
+	change = (int)val != snd_rme96_getinputtype(rme96);
+	snd_rme96_setinputtype(rme96, val);
+	spin_unlock_irq(&rme96->lock);
+	return change;
+}
+
+static int
+snd_rme96_info_clockmode_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[3] = { "AutoSync", "Internal", "Word" };
+	
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2) {
+		uinfo->value.enumerated.item = 2;
+	}
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+static int
+snd_rme96_get_clockmode_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&rme96->lock);
+	ucontrol->value.enumerated.item[0] = snd_rme96_getclockmode(rme96);
+	spin_unlock_irq(&rme96->lock);
+	return 0;
+}
+static int
+snd_rme96_put_clockmode_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+	
+	val = ucontrol->value.enumerated.item[0] % 3;
+	spin_lock_irq(&rme96->lock);
+	change = (int)val != snd_rme96_getclockmode(rme96);
+	snd_rme96_setclockmode(rme96, val);
+	spin_unlock_irq(&rme96->lock);
+	return change;
+}
+
+static int
+snd_rme96_info_attenuation_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = { "0 dB", "-6 dB", "-12 dB", "-18 dB" };
+	
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3) {
+		uinfo->value.enumerated.item = 3;
+	}
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+static int
+snd_rme96_get_attenuation_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&rme96->lock);
+	ucontrol->value.enumerated.item[0] = snd_rme96_getattenuation(rme96);
+	spin_unlock_irq(&rme96->lock);
+	return 0;
+}
+static int
+snd_rme96_put_attenuation_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+	
+	val = ucontrol->value.enumerated.item[0] % 4;
+	spin_lock_irq(&rme96->lock);
+
+	change = (int)val != snd_rme96_getattenuation(rme96);
+	snd_rme96_setattenuation(rme96, val);
+	spin_unlock_irq(&rme96->lock);
+	return change;
+}
+
+static int
+snd_rme96_info_montracks_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = { "1+2", "3+4", "5+6", "7+8" };
+	
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3) {
+		uinfo->value.enumerated.item = 3;
+	}
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+static int
+snd_rme96_get_montracks_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&rme96->lock);
+	ucontrol->value.enumerated.item[0] = snd_rme96_getmontracks(rme96);
+	spin_unlock_irq(&rme96->lock);
+	return 0;
+}
+static int
+snd_rme96_put_montracks_control(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+	
+	val = ucontrol->value.enumerated.item[0] % 4;
+	spin_lock_irq(&rme96->lock);
+	change = (int)val != snd_rme96_getmontracks(rme96);
+	snd_rme96_setmontracks(rme96, val);
+	spin_unlock_irq(&rme96->lock);
+	return change;
+}
+
+static u32 snd_rme96_convert_from_aes(struct snd_aes_iec958 *aes)
+{
+	u32 val = 0;
+	val |= (aes->status[0] & IEC958_AES0_PROFESSIONAL) ? RME96_WCR_PRO : 0;
+	val |= (aes->status[0] & IEC958_AES0_NONAUDIO) ? RME96_WCR_DOLBY : 0;
+	if (val & RME96_WCR_PRO)
+		val |= (aes->status[0] & IEC958_AES0_PRO_EMPHASIS_5015) ? RME96_WCR_EMP : 0;
+	else
+		val |= (aes->status[0] & IEC958_AES0_CON_EMPHASIS_5015) ? RME96_WCR_EMP : 0;
+	return val;
+}
+
+static void snd_rme96_convert_to_aes(struct snd_aes_iec958 *aes, u32 val)
+{
+	aes->status[0] = ((val & RME96_WCR_PRO) ? IEC958_AES0_PROFESSIONAL : 0) |
+			 ((val & RME96_WCR_DOLBY) ? IEC958_AES0_NONAUDIO : 0);
+	if (val & RME96_WCR_PRO)
+		aes->status[0] |= (val & RME96_WCR_EMP) ? IEC958_AES0_PRO_EMPHASIS_5015 : 0;
+	else
+		aes->status[0] |= (val & RME96_WCR_EMP) ? IEC958_AES0_CON_EMPHASIS_5015 : 0;
+}
+
+static int snd_rme96_control_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_rme96_control_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	
+	snd_rme96_convert_to_aes(&ucontrol->value.iec958, rme96->wcreg_spdif);
+	return 0;
+}
+
+static int snd_rme96_control_spdif_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	int change;
+	u32 val;
+	
+	val = snd_rme96_convert_from_aes(&ucontrol->value.iec958);
+	spin_lock_irq(&rme96->lock);
+	change = val != rme96->wcreg_spdif;
+	rme96->wcreg_spdif = val;
+	spin_unlock_irq(&rme96->lock);
+	return change;
+}
+
+static int snd_rme96_control_spdif_stream_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_rme96_control_spdif_stream_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	
+	snd_rme96_convert_to_aes(&ucontrol->value.iec958, rme96->wcreg_spdif_stream);
+	return 0;
+}
+
+static int snd_rme96_control_spdif_stream_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	int change;
+	u32 val;
+	
+	val = snd_rme96_convert_from_aes(&ucontrol->value.iec958);
+	spin_lock_irq(&rme96->lock);
+	change = val != rme96->wcreg_spdif_stream;
+	rme96->wcreg_spdif_stream = val;
+	rme96->wcreg &= ~(RME96_WCR_PRO | RME96_WCR_DOLBY | RME96_WCR_EMP);
+	rme96->wcreg |= val;
+	writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER);
+	spin_unlock_irq(&rme96->lock);
+	return change;
+}
+
+static int snd_rme96_control_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_rme96_control_spdif_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = kcontrol->private_value;
+	return 0;
+}
+
+static int
+snd_rme96_dac_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+	
+        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+        uinfo->count = 2;
+        uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = RME96_185X_MAX_OUT(rme96);
+        return 0;
+}
+
+static int
+snd_rme96_dac_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&rme96->lock);
+        u->value.integer.value[0] = rme96->vol[0];
+        u->value.integer.value[1] = rme96->vol[1];
+	spin_unlock_irq(&rme96->lock);
+
+        return 0;
+}
+
+static int
+snd_rme96_dac_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u)
+{
+	struct rme96 *rme96 = snd_kcontrol_chip(kcontrol);
+        int change = 0;
+	unsigned int vol, maxvol;
+
+
+	if (!RME96_HAS_ANALOG_OUT(rme96))
+		return -EINVAL;
+	maxvol = RME96_185X_MAX_OUT(rme96);
+	spin_lock_irq(&rme96->lock);
+	vol = u->value.integer.value[0];
+	if (vol != rme96->vol[0] && vol <= maxvol) {
+		rme96->vol[0] = vol;
+		change = 1;
+	}
+	vol = u->value.integer.value[1];
+	if (vol != rme96->vol[1] && vol <= maxvol) {
+		rme96->vol[1] = vol;
+		change = 1;
+	}
+	if (change)
+		snd_rme96_apply_dac_volume(rme96);
+	spin_unlock_irq(&rme96->lock);
+
+        return change;
+}
+
+static struct snd_kcontrol_new snd_rme96_controls[] = {
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =		snd_rme96_control_spdif_info,
+	.get =		snd_rme96_control_spdif_get,
+	.put =		snd_rme96_control_spdif_put
+},
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+	.info =		snd_rme96_control_spdif_stream_info,
+	.get =		snd_rme96_control_spdif_stream_get,
+	.put =		snd_rme96_control_spdif_stream_put
+},
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
+	.info =		snd_rme96_control_spdif_mask_info,
+	.get =		snd_rme96_control_spdif_mask_get,
+	.private_value = IEC958_AES0_NONAUDIO |
+			IEC958_AES0_PROFESSIONAL |
+			IEC958_AES0_CON_EMPHASIS
+},
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK),
+	.info =		snd_rme96_control_spdif_mask_info,
+	.get =		snd_rme96_control_spdif_mask_get,
+	.private_value = IEC958_AES0_NONAUDIO |
+			IEC958_AES0_PROFESSIONAL |
+			IEC958_AES0_PRO_EMPHASIS
+},
+{
+        .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Input Connector",
+	.info =         snd_rme96_info_inputtype_control, 
+	.get =          snd_rme96_get_inputtype_control,
+	.put =          snd_rme96_put_inputtype_control 
+},
+{
+        .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Loopback Input",
+	.info =         snd_rme96_info_loopback_control,
+	.get =          snd_rme96_get_loopback_control,
+	.put =          snd_rme96_put_loopback_control
+},
+{
+        .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Sample Clock Source",
+	.info =         snd_rme96_info_clockmode_control, 
+	.get =          snd_rme96_get_clockmode_control,
+	.put =          snd_rme96_put_clockmode_control
+},
+{
+        .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Monitor Tracks",
+	.info =         snd_rme96_info_montracks_control, 
+	.get =          snd_rme96_get_montracks_control,
+	.put =          snd_rme96_put_montracks_control
+},
+{
+        .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Attenuation",
+	.info =         snd_rme96_info_attenuation_control, 
+	.get =          snd_rme96_get_attenuation_control,
+	.put =          snd_rme96_put_attenuation_control
+},
+{
+        .iface =        SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "DAC Playback Volume",
+	.info =         snd_rme96_dac_volume_info,
+	.get =          snd_rme96_dac_volume_get,
+	.put =          snd_rme96_dac_volume_put
+}
+};
+
+static int
+snd_rme96_create_switches(struct snd_card *card,
+			  struct rme96 *rme96)
+{
+	int idx, err;
+	struct snd_kcontrol *kctl;
+
+	for (idx = 0; idx < 7; idx++) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_rme96_controls[idx], rme96))) < 0)
+			return err;
+		if (idx == 1)	/* IEC958 (S/PDIF) Stream */
+			rme96->spdif_ctl = kctl;
+	}
+
+	if (RME96_HAS_ANALOG_OUT(rme96)) {
+		for (idx = 7; idx < 10; idx++)
+			if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_rme96_controls[idx], rme96))) < 0)
+				return err;
+	}
+	
+	return 0;
+}
+
+/*
+ * Card initialisation
+ */
+
+static void snd_rme96_card_free(struct snd_card *card)
+{
+	snd_rme96_free(card->private_data);
+}
+
+static int __devinit
+snd_rme96_probe(struct pci_dev *pci,
+		const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct rme96 *rme96;
+	struct snd_card *card;
+	int err;
+	u8 val;
+
+	if (dev >= SNDRV_CARDS) {
+		return -ENODEV;
+	}
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct rme96), &card);
+	if (err < 0)
+		return err;
+	card->private_free = snd_rme96_card_free;
+	rme96 = card->private_data;
+	rme96->card = card;
+	rme96->pci = pci;
+	snd_card_set_dev(card, &pci->dev);
+	if ((err = snd_rme96_create(rme96)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	
+	strcpy(card->driver, "Digi96");
+	switch (rme96->pci->device) {
+	case PCI_DEVICE_ID_RME_DIGI96:
+		strcpy(card->shortname, "RME Digi96");
+		break;
+	case PCI_DEVICE_ID_RME_DIGI96_8:
+		strcpy(card->shortname, "RME Digi96/8");
+		break;
+	case PCI_DEVICE_ID_RME_DIGI96_8_PRO:
+		strcpy(card->shortname, "RME Digi96/8 PRO");
+		break;
+	case PCI_DEVICE_ID_RME_DIGI96_8_PAD_OR_PST:
+		pci_read_config_byte(rme96->pci, 8, &val);
+		if (val < 5) {
+			strcpy(card->shortname, "RME Digi96/8 PAD");
+		} else {
+			strcpy(card->shortname, "RME Digi96/8 PST");
+		}
+		break;
+	}
+	sprintf(card->longname, "%s at 0x%lx, irq %d", card->shortname,
+		rme96->port, rme96->irq);
+	
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;	
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_rme96_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "RME Digi96",
+	.id_table = snd_rme96_ids,
+	.probe = snd_rme96_probe,
+	.remove = __devexit_p(snd_rme96_remove),
+};
+
+static int __init alsa_card_rme96_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_rme96_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_rme96_init)
+module_exit(alsa_card_rme96_exit)
diff --git a/linux/sound/pci/rme9652/Makefile b/linux/sound/pci/rme9652/Makefile
new file mode 100644
index 0000000..dcba560
--- /dev/null
+++ b/linux/sound/pci/rme9652/Makefile
@@ -0,0 +1,13 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-rme9652-objs := rme9652.o
+snd-hdsp-objs := hdsp.o
+snd-hdspm-objs := hdspm.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_RME9652) += snd-rme9652.o
+obj-$(CONFIG_SND_HDSP) += snd-hdsp.o
+obj-$(CONFIG_SND_HDSPM) +=snd-hdspm.o
diff --git a/linux/sound/pci/rme9652/hdsp.c b/linux/sound/pci/rme9652/hdsp.c
new file mode 100644
index 0000000..0b720cf
--- /dev/null
+++ b/linux/sound/pci/rme9652/hdsp.c
@@ -0,0 +1,5233 @@
+/*
+ *   ALSA driver for RME Hammerfall DSP audio interface(s)
+ *
+ *      Copyright (c) 2002  Paul Davis
+ *                          Marcus Andersson
+ *                          Thomas Charbonnel
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/moduleparam.h>
+#include <linux/math64.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/asoundef.h>
+#include <sound/rawmidi.h>
+#include <sound/hwdep.h>
+#include <sound/initval.h>
+#include <sound/hdsp.h>
+
+#include <asm/byteorder.h>
+#include <asm/current.h>
+#include <asm/io.h>
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for RME Hammerfall DSP interface.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for RME Hammerfall DSP interface.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable/disable specific Hammerfall DSP soundcards.");
+MODULE_AUTHOR("Paul Davis <paul@linuxaudiosystems.com>, Marcus Andersson, Thomas Charbonnel <thomas@undata.org>");
+MODULE_DESCRIPTION("RME Hammerfall DSP");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{RME Hammerfall-DSP},"
+	        "{RME HDSP-9652},"
+		"{RME HDSP-9632}}");
+#ifdef HDSP_FW_LOADER
+MODULE_FIRMWARE("multiface_firmware.bin");
+MODULE_FIRMWARE("multiface_firmware_rev11.bin");
+MODULE_FIRMWARE("digiface_firmware.bin");
+MODULE_FIRMWARE("digiface_firmware_rev11.bin");
+#endif
+
+#define HDSP_MAX_CHANNELS        26
+#define HDSP_MAX_DS_CHANNELS     14
+#define HDSP_MAX_QS_CHANNELS     8
+#define DIGIFACE_SS_CHANNELS     26
+#define DIGIFACE_DS_CHANNELS     14
+#define MULTIFACE_SS_CHANNELS    18
+#define MULTIFACE_DS_CHANNELS    14
+#define H9652_SS_CHANNELS        26
+#define H9652_DS_CHANNELS        14
+/* This does not include possible Analog Extension Boards
+   AEBs are detected at card initialization
+*/
+#define H9632_SS_CHANNELS	 12
+#define H9632_DS_CHANNELS	 8
+#define H9632_QS_CHANNELS	 4
+
+/* Write registers. These are defined as byte-offsets from the iobase value.
+ */
+#define HDSP_resetPointer               0
+#define HDSP_freqReg			0
+#define HDSP_outputBufferAddress	32
+#define HDSP_inputBufferAddress		36
+#define HDSP_controlRegister		64
+#define HDSP_interruptConfirmation	96
+#define HDSP_outputEnable	  	128
+#define HDSP_control2Reg		256
+#define HDSP_midiDataOut0  		352
+#define HDSP_midiDataOut1  		356
+#define HDSP_fifoData  			368
+#define HDSP_inputEnable	 	384
+
+/* Read registers. These are defined as byte-offsets from the iobase value
+ */
+
+#define HDSP_statusRegister    0
+#define HDSP_timecode        128
+#define HDSP_status2Register 192
+#define HDSP_midiDataIn0     360
+#define HDSP_midiDataIn1     364
+#define HDSP_midiStatusOut0  384
+#define HDSP_midiStatusOut1  388
+#define HDSP_midiStatusIn0   392
+#define HDSP_midiStatusIn1   396
+#define HDSP_fifoStatus      400
+
+/* the meters are regular i/o-mapped registers, but offset
+   considerably from the rest. the peak registers are reset
+   when read; the least-significant 4 bits are full-scale counters;
+   the actual peak value is in the most-significant 24 bits.
+*/
+
+#define HDSP_playbackPeakLevel  4096  /* 26 * 32 bit values */
+#define HDSP_inputPeakLevel     4224  /* 26 * 32 bit values */
+#define HDSP_outputPeakLevel    4352  /* (26+2) * 32 bit values */
+#define HDSP_playbackRmsLevel   4612  /* 26 * 64 bit values */
+#define HDSP_inputRmsLevel      4868  /* 26 * 64 bit values */
+
+
+/* This is for H9652 cards
+   Peak values are read downward from the base
+   Rms values are read upward
+   There are rms values for the outputs too
+   26*3 values are read in ss mode
+   14*3 in ds mode, with no gap between values
+*/
+#define HDSP_9652_peakBase	7164
+#define HDSP_9652_rmsBase	4096
+
+/* c.f. the hdsp_9632_meters_t struct */
+#define HDSP_9632_metersBase	4096
+
+#define HDSP_IO_EXTENT     7168
+
+/* control2 register bits */
+
+#define HDSP_TMS                0x01
+#define HDSP_TCK                0x02
+#define HDSP_TDI                0x04
+#define HDSP_JTAG               0x08
+#define HDSP_PWDN               0x10
+#define HDSP_PROGRAM	        0x020
+#define HDSP_CONFIG_MODE_0	0x040
+#define HDSP_CONFIG_MODE_1	0x080
+#define HDSP_VERSION_BIT	0x100
+#define HDSP_BIGENDIAN_MODE     0x200
+#define HDSP_RD_MULTIPLE        0x400
+#define HDSP_9652_ENABLE_MIXER  0x800
+#define HDSP_TDO                0x10000000
+
+#define HDSP_S_PROGRAM     	(HDSP_PROGRAM|HDSP_CONFIG_MODE_0)
+#define HDSP_S_LOAD		(HDSP_PROGRAM|HDSP_CONFIG_MODE_1)
+
+/* Control Register bits */
+
+#define HDSP_Start                (1<<0)  /* start engine */
+#define HDSP_Latency0             (1<<1)  /* buffer size = 2^n where n is defined by Latency{2,1,0} */
+#define HDSP_Latency1             (1<<2)  /* [ see above ] */
+#define HDSP_Latency2             (1<<3)  /* [ see above ] */
+#define HDSP_ClockModeMaster      (1<<4)  /* 1=Master, 0=Slave/Autosync */
+#define HDSP_AudioInterruptEnable (1<<5)  /* what do you think ? */
+#define HDSP_Frequency0           (1<<6)  /* 0=44.1kHz/88.2kHz/176.4kHz 1=48kHz/96kHz/192kHz */
+#define HDSP_Frequency1           (1<<7)  /* 0=32kHz/64kHz/128kHz */
+#define HDSP_DoubleSpeed          (1<<8)  /* 0=normal speed, 1=double speed */
+#define HDSP_SPDIFProfessional    (1<<9)  /* 0=consumer, 1=professional */
+#define HDSP_SPDIFEmphasis        (1<<10) /* 0=none, 1=on */
+#define HDSP_SPDIFNonAudio        (1<<11) /* 0=off, 1=on */
+#define HDSP_SPDIFOpticalOut      (1<<12) /* 1=use 1st ADAT connector for SPDIF, 0=do not */
+#define HDSP_SyncRef2             (1<<13)
+#define HDSP_SPDIFInputSelect0    (1<<14)
+#define HDSP_SPDIFInputSelect1    (1<<15)
+#define HDSP_SyncRef0             (1<<16)
+#define HDSP_SyncRef1             (1<<17)
+#define HDSP_AnalogExtensionBoard (1<<18) /* For H9632 cards */
+#define HDSP_XLRBreakoutCable     (1<<20) /* For H9632 cards */
+#define HDSP_Midi0InterruptEnable (1<<22)
+#define HDSP_Midi1InterruptEnable (1<<23)
+#define HDSP_LineOut              (1<<24)
+#define HDSP_ADGain0		  (1<<25) /* From here : H9632 specific */
+#define HDSP_ADGain1		  (1<<26)
+#define HDSP_DAGain0		  (1<<27)
+#define HDSP_DAGain1		  (1<<28)
+#define HDSP_PhoneGain0		  (1<<29)
+#define HDSP_PhoneGain1		  (1<<30)
+#define HDSP_QuadSpeed	  	  (1<<31)
+
+#define HDSP_ADGainMask       (HDSP_ADGain0|HDSP_ADGain1)
+#define HDSP_ADGainMinus10dBV  HDSP_ADGainMask
+#define HDSP_ADGainPlus4dBu   (HDSP_ADGain0)
+#define HDSP_ADGainLowGain     0
+
+#define HDSP_DAGainMask         (HDSP_DAGain0|HDSP_DAGain1)
+#define HDSP_DAGainHighGain      HDSP_DAGainMask
+#define HDSP_DAGainPlus4dBu     (HDSP_DAGain0)
+#define HDSP_DAGainMinus10dBV    0
+
+#define HDSP_PhoneGainMask      (HDSP_PhoneGain0|HDSP_PhoneGain1)
+#define HDSP_PhoneGain0dB        HDSP_PhoneGainMask
+#define HDSP_PhoneGainMinus6dB  (HDSP_PhoneGain0)
+#define HDSP_PhoneGainMinus12dB  0
+
+#define HDSP_LatencyMask    (HDSP_Latency0|HDSP_Latency1|HDSP_Latency2)
+#define HDSP_FrequencyMask  (HDSP_Frequency0|HDSP_Frequency1|HDSP_DoubleSpeed|HDSP_QuadSpeed)
+
+#define HDSP_SPDIFInputMask    (HDSP_SPDIFInputSelect0|HDSP_SPDIFInputSelect1)
+#define HDSP_SPDIFInputADAT1    0
+#define HDSP_SPDIFInputCoaxial (HDSP_SPDIFInputSelect0)
+#define HDSP_SPDIFInputCdrom   (HDSP_SPDIFInputSelect1)
+#define HDSP_SPDIFInputAES     (HDSP_SPDIFInputSelect0|HDSP_SPDIFInputSelect1)
+
+#define HDSP_SyncRefMask        (HDSP_SyncRef0|HDSP_SyncRef1|HDSP_SyncRef2)
+#define HDSP_SyncRef_ADAT1       0
+#define HDSP_SyncRef_ADAT2      (HDSP_SyncRef0)
+#define HDSP_SyncRef_ADAT3      (HDSP_SyncRef1)
+#define HDSP_SyncRef_SPDIF      (HDSP_SyncRef0|HDSP_SyncRef1)
+#define HDSP_SyncRef_WORD       (HDSP_SyncRef2)
+#define HDSP_SyncRef_ADAT_SYNC  (HDSP_SyncRef0|HDSP_SyncRef2)
+
+/* Sample Clock Sources */
+
+#define HDSP_CLOCK_SOURCE_AUTOSYNC           0
+#define HDSP_CLOCK_SOURCE_INTERNAL_32KHZ     1
+#define HDSP_CLOCK_SOURCE_INTERNAL_44_1KHZ   2
+#define HDSP_CLOCK_SOURCE_INTERNAL_48KHZ     3
+#define HDSP_CLOCK_SOURCE_INTERNAL_64KHZ     4
+#define HDSP_CLOCK_SOURCE_INTERNAL_88_2KHZ   5
+#define HDSP_CLOCK_SOURCE_INTERNAL_96KHZ     6
+#define HDSP_CLOCK_SOURCE_INTERNAL_128KHZ    7
+#define HDSP_CLOCK_SOURCE_INTERNAL_176_4KHZ  8
+#define HDSP_CLOCK_SOURCE_INTERNAL_192KHZ    9
+
+/* Preferred sync reference choices - used by "pref_sync_ref" control switch */
+
+#define HDSP_SYNC_FROM_WORD      0
+#define HDSP_SYNC_FROM_SPDIF     1
+#define HDSP_SYNC_FROM_ADAT1     2
+#define HDSP_SYNC_FROM_ADAT_SYNC 3
+#define HDSP_SYNC_FROM_ADAT2     4
+#define HDSP_SYNC_FROM_ADAT3     5
+
+/* SyncCheck status */
+
+#define HDSP_SYNC_CHECK_NO_LOCK 0
+#define HDSP_SYNC_CHECK_LOCK    1
+#define HDSP_SYNC_CHECK_SYNC	2
+
+/* AutoSync references - used by "autosync_ref" control switch */
+
+#define HDSP_AUTOSYNC_FROM_WORD      0
+#define HDSP_AUTOSYNC_FROM_ADAT_SYNC 1
+#define HDSP_AUTOSYNC_FROM_SPDIF     2
+#define HDSP_AUTOSYNC_FROM_NONE	     3
+#define HDSP_AUTOSYNC_FROM_ADAT1     4
+#define HDSP_AUTOSYNC_FROM_ADAT2     5
+#define HDSP_AUTOSYNC_FROM_ADAT3     6
+
+/* Possible sources of S/PDIF input */
+
+#define HDSP_SPDIFIN_OPTICAL  0	/* optical  (ADAT1) */
+#define HDSP_SPDIFIN_COAXIAL  1	/* coaxial (RCA) */
+#define HDSP_SPDIFIN_INTERNAL 2	/* internal (CDROM) */
+#define HDSP_SPDIFIN_AES      3 /* xlr for H9632 (AES)*/
+
+#define HDSP_Frequency32KHz    HDSP_Frequency0
+#define HDSP_Frequency44_1KHz  HDSP_Frequency1
+#define HDSP_Frequency48KHz    (HDSP_Frequency1|HDSP_Frequency0)
+#define HDSP_Frequency64KHz    (HDSP_DoubleSpeed|HDSP_Frequency0)
+#define HDSP_Frequency88_2KHz  (HDSP_DoubleSpeed|HDSP_Frequency1)
+#define HDSP_Frequency96KHz    (HDSP_DoubleSpeed|HDSP_Frequency1|HDSP_Frequency0)
+/* For H9632 cards */
+#define HDSP_Frequency128KHz   (HDSP_QuadSpeed|HDSP_DoubleSpeed|HDSP_Frequency0)
+#define HDSP_Frequency176_4KHz (HDSP_QuadSpeed|HDSP_DoubleSpeed|HDSP_Frequency1)
+#define HDSP_Frequency192KHz   (HDSP_QuadSpeed|HDSP_DoubleSpeed|HDSP_Frequency1|HDSP_Frequency0)
+/* RME says n = 104857600000000, but in the windows MADI driver, I see:
+	return 104857600000000 / rate; // 100 MHz
+	return 110100480000000 / rate; // 105 MHz
+*/
+#define DDS_NUMERATOR 104857600000000ULL;  /*  =  2^20 * 10^8 */
+
+#define hdsp_encode_latency(x)       (((x)<<1) & HDSP_LatencyMask)
+#define hdsp_decode_latency(x)       (((x) & HDSP_LatencyMask)>>1)
+
+#define hdsp_encode_spdif_in(x) (((x)&0x3)<<14)
+#define hdsp_decode_spdif_in(x) (((x)>>14)&0x3)
+
+/* Status Register bits */
+
+#define HDSP_audioIRQPending    (1<<0)
+#define HDSP_Lock2              (1<<1)     /* this is for Digiface and H9652 */
+#define HDSP_spdifFrequency3	HDSP_Lock2 /* this is for H9632 only */
+#define HDSP_Lock1              (1<<2)
+#define HDSP_Lock0              (1<<3)
+#define HDSP_SPDIFSync          (1<<4)
+#define HDSP_TimecodeLock       (1<<5)
+#define HDSP_BufferPositionMask 0x000FFC0 /* Bit 6..15 : h/w buffer pointer */
+#define HDSP_Sync2              (1<<16)
+#define HDSP_Sync1              (1<<17)
+#define HDSP_Sync0              (1<<18)
+#define HDSP_DoubleSpeedStatus  (1<<19)
+#define HDSP_ConfigError        (1<<20)
+#define HDSP_DllError           (1<<21)
+#define HDSP_spdifFrequency0    (1<<22)
+#define HDSP_spdifFrequency1    (1<<23)
+#define HDSP_spdifFrequency2    (1<<24)
+#define HDSP_SPDIFErrorFlag     (1<<25)
+#define HDSP_BufferID           (1<<26)
+#define HDSP_TimecodeSync       (1<<27)
+#define HDSP_AEBO          	(1<<28) /* H9632 specific Analog Extension Boards */
+#define HDSP_AEBI		(1<<29) /* 0 = present, 1 = absent */
+#define HDSP_midi0IRQPending    (1<<30)
+#define HDSP_midi1IRQPending    (1<<31)
+
+#define HDSP_spdifFrequencyMask    (HDSP_spdifFrequency0|HDSP_spdifFrequency1|HDSP_spdifFrequency2)
+#define HDSP_spdifFrequencyMask_9632 (HDSP_spdifFrequency0|\
+				      HDSP_spdifFrequency1|\
+				      HDSP_spdifFrequency2|\
+				      HDSP_spdifFrequency3)
+
+#define HDSP_spdifFrequency32KHz   (HDSP_spdifFrequency0)
+#define HDSP_spdifFrequency44_1KHz (HDSP_spdifFrequency1)
+#define HDSP_spdifFrequency48KHz   (HDSP_spdifFrequency0|HDSP_spdifFrequency1)
+
+#define HDSP_spdifFrequency64KHz   (HDSP_spdifFrequency2)
+#define HDSP_spdifFrequency88_2KHz (HDSP_spdifFrequency0|HDSP_spdifFrequency2)
+#define HDSP_spdifFrequency96KHz   (HDSP_spdifFrequency2|HDSP_spdifFrequency1)
+
+/* This is for H9632 cards */
+#define HDSP_spdifFrequency128KHz   (HDSP_spdifFrequency0|\
+				     HDSP_spdifFrequency1|\
+				     HDSP_spdifFrequency2)
+#define HDSP_spdifFrequency176_4KHz HDSP_spdifFrequency3
+#define HDSP_spdifFrequency192KHz   (HDSP_spdifFrequency3|HDSP_spdifFrequency0)
+
+/* Status2 Register bits */
+
+#define HDSP_version0     (1<<0)
+#define HDSP_version1     (1<<1)
+#define HDSP_version2     (1<<2)
+#define HDSP_wc_lock      (1<<3)
+#define HDSP_wc_sync      (1<<4)
+#define HDSP_inp_freq0    (1<<5)
+#define HDSP_inp_freq1    (1<<6)
+#define HDSP_inp_freq2    (1<<7)
+#define HDSP_SelSyncRef0  (1<<8)
+#define HDSP_SelSyncRef1  (1<<9)
+#define HDSP_SelSyncRef2  (1<<10)
+
+#define HDSP_wc_valid (HDSP_wc_lock|HDSP_wc_sync)
+
+#define HDSP_systemFrequencyMask (HDSP_inp_freq0|HDSP_inp_freq1|HDSP_inp_freq2)
+#define HDSP_systemFrequency32   (HDSP_inp_freq0)
+#define HDSP_systemFrequency44_1 (HDSP_inp_freq1)
+#define HDSP_systemFrequency48   (HDSP_inp_freq0|HDSP_inp_freq1)
+#define HDSP_systemFrequency64   (HDSP_inp_freq2)
+#define HDSP_systemFrequency88_2 (HDSP_inp_freq0|HDSP_inp_freq2)
+#define HDSP_systemFrequency96   (HDSP_inp_freq1|HDSP_inp_freq2)
+/* FIXME : more values for 9632 cards ? */
+
+#define HDSP_SelSyncRefMask        (HDSP_SelSyncRef0|HDSP_SelSyncRef1|HDSP_SelSyncRef2)
+#define HDSP_SelSyncRef_ADAT1      0
+#define HDSP_SelSyncRef_ADAT2      (HDSP_SelSyncRef0)
+#define HDSP_SelSyncRef_ADAT3      (HDSP_SelSyncRef1)
+#define HDSP_SelSyncRef_SPDIF      (HDSP_SelSyncRef0|HDSP_SelSyncRef1)
+#define HDSP_SelSyncRef_WORD       (HDSP_SelSyncRef2)
+#define HDSP_SelSyncRef_ADAT_SYNC  (HDSP_SelSyncRef0|HDSP_SelSyncRef2)
+
+/* Card state flags */
+
+#define HDSP_InitializationComplete  (1<<0)
+#define HDSP_FirmwareLoaded	     (1<<1)
+#define HDSP_FirmwareCached	     (1<<2)
+
+/* FIFO wait times, defined in terms of 1/10ths of msecs */
+
+#define HDSP_LONG_WAIT	 5000
+#define HDSP_SHORT_WAIT  30
+
+#define UNITY_GAIN                       32768
+#define MINUS_INFINITY_GAIN              0
+
+/* the size of a substream (1 mono data stream) */
+
+#define HDSP_CHANNEL_BUFFER_SAMPLES  (16*1024)
+#define HDSP_CHANNEL_BUFFER_BYTES    (4*HDSP_CHANNEL_BUFFER_SAMPLES)
+
+/* the size of the area we need to allocate for DMA transfers. the
+   size is the same regardless of the number of channels - the
+   Multiface still uses the same memory area.
+
+   Note that we allocate 1 more channel than is apparently needed
+   because the h/w seems to write 1 byte beyond the end of the last
+   page. Sigh.
+*/
+
+#define HDSP_DMA_AREA_BYTES ((HDSP_MAX_CHANNELS+1) * HDSP_CHANNEL_BUFFER_BYTES)
+#define HDSP_DMA_AREA_KILOBYTES (HDSP_DMA_AREA_BYTES/1024)
+
+/* use hotplug firmware loader? */
+#if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE)
+#if !defined(HDSP_USE_HWDEP_LOADER)
+#define HDSP_FW_LOADER
+#endif
+#endif
+
+struct hdsp_9632_meters {
+    u32 input_peak[16];
+    u32 playback_peak[16];
+    u32 output_peak[16];
+    u32 xxx_peak[16];
+    u32 padding[64];
+    u32 input_rms_low[16];
+    u32 playback_rms_low[16];
+    u32 output_rms_low[16];
+    u32 xxx_rms_low[16];
+    u32 input_rms_high[16];
+    u32 playback_rms_high[16];
+    u32 output_rms_high[16];
+    u32 xxx_rms_high[16];
+};
+
+struct hdsp_midi {
+    struct hdsp             *hdsp;
+    int                      id;
+    struct snd_rawmidi           *rmidi;
+    struct snd_rawmidi_substream *input;
+    struct snd_rawmidi_substream *output;
+    char                     istimer; /* timer in use */
+    struct timer_list	     timer;
+    spinlock_t               lock;
+    int			     pending;
+};
+
+struct hdsp {
+	spinlock_t            lock;
+	struct snd_pcm_substream *capture_substream;
+	struct snd_pcm_substream *playback_substream;
+        struct hdsp_midi      midi[2];
+	struct tasklet_struct midi_tasklet;
+	int		      use_midi_tasklet;
+	int                   precise_ptr;
+	u32                   control_register;	     /* cached value */
+	u32                   control2_register;     /* cached value */
+	u32                   creg_spdif;
+	u32                   creg_spdif_stream;
+	int                   clock_source_locked;
+	char                 *card_name;	     /* digiface/multiface */
+	enum HDSP_IO_Type     io_type;               /* ditto, but for code use */
+        unsigned short        firmware_rev;
+	unsigned short	      state;		     /* stores state bits */
+	u32		      firmware_cache[24413]; /* this helps recover from accidental iobox power failure */
+	size_t                period_bytes; 	     /* guess what this is */
+	unsigned char	      max_channels;
+	unsigned char	      qs_in_channels;	     /* quad speed mode for H9632 */
+	unsigned char         ds_in_channels;
+	unsigned char         ss_in_channels;	    /* different for multiface/digiface */
+	unsigned char	      qs_out_channels;
+	unsigned char         ds_out_channels;
+	unsigned char         ss_out_channels;
+
+	struct snd_dma_buffer capture_dma_buf;
+	struct snd_dma_buffer playback_dma_buf;
+	unsigned char        *capture_buffer;	    /* suitably aligned address */
+	unsigned char        *playback_buffer;	    /* suitably aligned address */
+
+	pid_t                 capture_pid;
+	pid_t                 playback_pid;
+	int                   running;
+	int                   system_sample_rate;
+	char                 *channel_map;
+	int                   dev;
+	int                   irq;
+	unsigned long         port;
+        void __iomem         *iobase;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct snd_hwdep          *hwdep;
+	struct pci_dev       *pci;
+	struct snd_kcontrol *spdif_ctl;
+        unsigned short        mixer_matrix[HDSP_MATRIX_MIXER_SIZE];
+	unsigned int          dds_value; /* last value written to freq register */
+};
+
+/* These tables map the ALSA channels 1..N to the channels that we
+   need to use in order to find the relevant channel buffer. RME
+   refer to this kind of mapping as between "the ADAT channel and
+   the DMA channel." We index it using the logical audio channel,
+   and the value is the DMA channel (i.e. channel buffer number)
+   where the data for that channel can be read/written from/to.
+*/
+
+static char channel_map_df_ss[HDSP_MAX_CHANNELS] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+	18, 19, 20, 21, 22, 23, 24, 25
+};
+
+static char channel_map_mf_ss[HDSP_MAX_CHANNELS] = { /* Multiface */
+	/* Analog */
+	0, 1, 2, 3, 4, 5, 6, 7,
+	/* ADAT 2 */
+	16, 17, 18, 19, 20, 21, 22, 23,
+	/* SPDIF */
+	24, 25,
+	-1, -1, -1, -1, -1, -1, -1, -1
+};
+
+static char channel_map_ds[HDSP_MAX_CHANNELS] = {
+	/* ADAT channels are remapped */
+	1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23,
+	/* channels 12 and 13 are S/PDIF */
+	24, 25,
+	/* others don't exist */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+static char channel_map_H9632_ss[HDSP_MAX_CHANNELS] = {
+	/* ADAT channels */
+	0, 1, 2, 3, 4, 5, 6, 7,
+	/* SPDIF */
+	8, 9,
+	/* Analog */
+	10, 11,
+	/* AO4S-192 and AI4S-192 extension boards */
+	12, 13, 14, 15,
+	/* others don't exist */
+	-1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1
+};
+
+static char channel_map_H9632_ds[HDSP_MAX_CHANNELS] = {
+	/* ADAT */
+	1, 3, 5, 7,
+	/* SPDIF */
+	8, 9,
+	/* Analog */
+	10, 11,
+	/* AO4S-192 and AI4S-192 extension boards */
+	12, 13, 14, 15,
+	/* others don't exist */
+	-1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1
+};
+
+static char channel_map_H9632_qs[HDSP_MAX_CHANNELS] = {
+	/* ADAT is disabled in this mode */
+	/* SPDIF */
+	8, 9,
+	/* Analog */
+	10, 11,
+	/* AO4S-192 and AI4S-192 extension boards */
+	12, 13, 14, 15,
+	/* others don't exist */
+	-1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1
+};
+
+static int snd_hammerfall_get_buffer(struct pci_dev *pci, struct snd_dma_buffer *dmab, size_t size)
+{
+	dmab->dev.type = SNDRV_DMA_TYPE_DEV;
+	dmab->dev.dev = snd_dma_pci_data(pci);
+	if (snd_dma_get_reserved_buf(dmab, snd_dma_pci_buf_id(pci))) {
+		if (dmab->bytes >= size)
+			return 0;
+	}
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				size, dmab) < 0)
+		return -ENOMEM;
+	return 0;
+}
+
+static void snd_hammerfall_free_buffer(struct snd_dma_buffer *dmab, struct pci_dev *pci)
+{
+	if (dmab->area) {
+		dmab->dev.dev = NULL; /* make it anonymous */
+		snd_dma_reserve_buf(dmab, snd_dma_pci_buf_id(pci));
+	}
+}
+
+
+static DEFINE_PCI_DEVICE_TABLE(snd_hdsp_ids) = {
+	{
+		.vendor = PCI_VENDOR_ID_XILINX,
+		.device = PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP,
+		.subvendor = PCI_ANY_ID,
+		.subdevice = PCI_ANY_ID,
+	}, /* RME Hammerfall-DSP */
+	{ 0, },
+};
+
+MODULE_DEVICE_TABLE(pci, snd_hdsp_ids);
+
+/* prototypes */
+static int snd_hdsp_create_alsa_devices(struct snd_card *card, struct hdsp *hdsp);
+static int snd_hdsp_create_pcm(struct snd_card *card, struct hdsp *hdsp);
+static int snd_hdsp_enable_io (struct hdsp *hdsp);
+static void snd_hdsp_initialize_midi_flush (struct hdsp *hdsp);
+static void snd_hdsp_initialize_channels (struct hdsp *hdsp);
+static int hdsp_fifo_wait(struct hdsp *hdsp, int count, int timeout);
+static int hdsp_autosync_ref(struct hdsp *hdsp);
+static int snd_hdsp_set_defaults(struct hdsp *hdsp);
+static void snd_hdsp_9652_enable_mixer (struct hdsp *hdsp);
+
+static int hdsp_playback_to_output_key (struct hdsp *hdsp, int in, int out)
+{
+	switch (hdsp->io_type) {
+	case Multiface:
+	case Digiface:
+	default:
+		if (hdsp->firmware_rev == 0xa)
+			return (64 * out) + (32 + (in));
+		else
+			return (52 * out) + (26 + (in));
+	case H9632:
+		return (32 * out) + (16 + (in));
+	case H9652:
+		return (52 * out) + (26 + (in));
+	}
+}
+
+static int hdsp_input_to_output_key (struct hdsp *hdsp, int in, int out)
+{
+	switch (hdsp->io_type) {
+	case Multiface:
+	case Digiface:
+	default:
+		if (hdsp->firmware_rev == 0xa)
+			return (64 * out) + in;
+		else
+			return (52 * out) + in;
+	case H9632:
+		return (32 * out) + in;
+	case H9652:
+		return (52 * out) + in;
+	}
+}
+
+static void hdsp_write(struct hdsp *hdsp, int reg, int val)
+{
+	writel(val, hdsp->iobase + reg);
+}
+
+static unsigned int hdsp_read(struct hdsp *hdsp, int reg)
+{
+	return readl (hdsp->iobase + reg);
+}
+
+static int hdsp_check_for_iobox (struct hdsp *hdsp)
+{
+	if (hdsp->io_type == H9652 || hdsp->io_type == H9632) return 0;
+	if (hdsp_read (hdsp, HDSP_statusRegister) & HDSP_ConfigError) {
+		snd_printk ("Hammerfall-DSP: no Digiface or Multiface connected!\n");
+		hdsp->state &= ~HDSP_FirmwareLoaded;
+		return -EIO;
+	}
+	return 0;
+}
+
+static int hdsp_wait_for_iobox(struct hdsp *hdsp, unsigned int loops,
+			       unsigned int delay)
+{
+	unsigned int i;
+
+	if (hdsp->io_type == H9652 || hdsp->io_type == H9632)
+		return 0;
+
+	for (i = 0; i != loops; ++i) {
+		if (hdsp_read(hdsp, HDSP_statusRegister) & HDSP_ConfigError)
+			msleep(delay);
+		else {
+			snd_printd("Hammerfall-DSP: iobox found after %ums!\n",
+				   i * delay);
+			return 0;
+		}
+	}
+
+	snd_printk("Hammerfall-DSP: no Digiface or Multiface connected!\n");
+	hdsp->state &= ~HDSP_FirmwareLoaded;
+	return -EIO;
+}
+
+static int snd_hdsp_load_firmware_from_cache(struct hdsp *hdsp) {
+
+	int i;
+	unsigned long flags;
+
+	if ((hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DllError) != 0) {
+
+		snd_printk ("Hammerfall-DSP: loading firmware\n");
+
+		hdsp_write (hdsp, HDSP_control2Reg, HDSP_S_PROGRAM);
+		hdsp_write (hdsp, HDSP_fifoData, 0);
+
+		if (hdsp_fifo_wait (hdsp, 0, HDSP_LONG_WAIT)) {
+			snd_printk ("Hammerfall-DSP: timeout waiting for download preparation\n");
+			return -EIO;
+		}
+
+		hdsp_write (hdsp, HDSP_control2Reg, HDSP_S_LOAD);
+
+		for (i = 0; i < 24413; ++i) {
+			hdsp_write(hdsp, HDSP_fifoData, hdsp->firmware_cache[i]);
+			if (hdsp_fifo_wait (hdsp, 127, HDSP_LONG_WAIT)) {
+				snd_printk ("Hammerfall-DSP: timeout during firmware loading\n");
+				return -EIO;
+			}
+		}
+
+		ssleep(3);
+
+		if (hdsp_fifo_wait (hdsp, 0, HDSP_LONG_WAIT)) {
+			snd_printk ("Hammerfall-DSP: timeout at end of firmware loading\n");
+		    	return -EIO;
+		}
+
+#ifdef SNDRV_BIG_ENDIAN
+		hdsp->control2_register = HDSP_BIGENDIAN_MODE;
+#else
+		hdsp->control2_register = 0;
+#endif
+		hdsp_write (hdsp, HDSP_control2Reg, hdsp->control2_register);
+		snd_printk ("Hammerfall-DSP: finished firmware loading\n");
+
+	}
+	if (hdsp->state & HDSP_InitializationComplete) {
+		snd_printk(KERN_INFO "Hammerfall-DSP: firmware loaded from cache, restoring defaults\n");
+		spin_lock_irqsave(&hdsp->lock, flags);
+		snd_hdsp_set_defaults(hdsp);
+		spin_unlock_irqrestore(&hdsp->lock, flags);
+	}
+
+	hdsp->state |= HDSP_FirmwareLoaded;
+
+	return 0;
+}
+
+static int hdsp_get_iobox_version (struct hdsp *hdsp)
+{
+	if ((hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DllError) != 0) {
+
+		hdsp_write (hdsp, HDSP_control2Reg, HDSP_PROGRAM);
+		hdsp_write (hdsp, HDSP_fifoData, 0);
+		if (hdsp_fifo_wait (hdsp, 0, HDSP_SHORT_WAIT) < 0)
+			return -EIO;
+
+		hdsp_write (hdsp, HDSP_control2Reg, HDSP_S_LOAD);
+		hdsp_write (hdsp, HDSP_fifoData, 0);
+
+		if (hdsp_fifo_wait (hdsp, 0, HDSP_SHORT_WAIT)) {
+			hdsp->io_type = Multiface;
+			hdsp_write (hdsp, HDSP_control2Reg, HDSP_VERSION_BIT);
+			hdsp_write (hdsp, HDSP_control2Reg, HDSP_S_LOAD);
+			hdsp_fifo_wait (hdsp, 0, HDSP_SHORT_WAIT);
+		} else {
+			hdsp->io_type = Digiface;
+		}
+	} else {
+		/* firmware was already loaded, get iobox type */
+		if (hdsp_read(hdsp, HDSP_status2Register) & HDSP_version1)
+			hdsp->io_type = Multiface;
+		else
+			hdsp->io_type = Digiface;
+	}
+	return 0;
+}
+
+
+#ifdef HDSP_FW_LOADER
+static int hdsp_request_fw_loader(struct hdsp *hdsp);
+#endif
+
+static int hdsp_check_for_firmware (struct hdsp *hdsp, int load_on_demand)
+{
+	if (hdsp->io_type == H9652 || hdsp->io_type == H9632)
+		return 0;
+	if ((hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DllError) != 0) {
+		hdsp->state &= ~HDSP_FirmwareLoaded;
+		if (! load_on_demand)
+			return -EIO;
+		snd_printk(KERN_ERR "Hammerfall-DSP: firmware not present.\n");
+		/* try to load firmware */
+		if (! (hdsp->state & HDSP_FirmwareCached)) {
+#ifdef HDSP_FW_LOADER
+			if (! hdsp_request_fw_loader(hdsp))
+				return 0;
+#endif
+			snd_printk(KERN_ERR
+				   "Hammerfall-DSP: No firmware loaded nor "
+				   "cached, please upload firmware.\n");
+			return -EIO;
+		}
+		if (snd_hdsp_load_firmware_from_cache(hdsp) != 0) {
+			snd_printk(KERN_ERR
+				   "Hammerfall-DSP: Firmware loading from "
+				   "cache failed, please upload manually.\n");
+			return -EIO;
+		}
+	}
+	return 0;
+}
+
+
+static int hdsp_fifo_wait(struct hdsp *hdsp, int count, int timeout)
+{
+	int i;
+
+	/* the fifoStatus registers reports on how many words
+	   are available in the command FIFO.
+	*/
+
+	for (i = 0; i < timeout; i++) {
+
+		if ((int)(hdsp_read (hdsp, HDSP_fifoStatus) & 0xff) <= count)
+			return 0;
+
+		/* not very friendly, but we only do this during a firmware
+		   load and changing the mixer, so we just put up with it.
+		*/
+
+		udelay (100);
+	}
+
+	snd_printk ("Hammerfall-DSP: wait for FIFO status <= %d failed after %d iterations\n",
+		    count, timeout);
+	return -1;
+}
+
+static int hdsp_read_gain (struct hdsp *hdsp, unsigned int addr)
+{
+	if (addr >= HDSP_MATRIX_MIXER_SIZE)
+		return 0;
+
+	return hdsp->mixer_matrix[addr];
+}
+
+static int hdsp_write_gain(struct hdsp *hdsp, unsigned int addr, unsigned short data)
+{
+	unsigned int ad;
+
+	if (addr >= HDSP_MATRIX_MIXER_SIZE)
+		return -1;
+
+	if (hdsp->io_type == H9652 || hdsp->io_type == H9632) {
+
+		/* from martin bjornsen:
+
+		   "You can only write dwords to the
+		   mixer memory which contain two
+		   mixer values in the low and high
+		   word. So if you want to change
+		   value 0 you have to read value 1
+		   from the cache and write both to
+		   the first dword in the mixer
+		   memory."
+		*/
+
+		if (hdsp->io_type == H9632 && addr >= 512)
+			return 0;
+
+		if (hdsp->io_type == H9652 && addr >= 1352)
+			return 0;
+
+		hdsp->mixer_matrix[addr] = data;
+
+
+		/* `addr' addresses a 16-bit wide address, but
+		   the address space accessed via hdsp_write
+		   uses byte offsets. put another way, addr
+		   varies from 0 to 1351, but to access the
+		   corresponding memory location, we need
+		   to access 0 to 2703 ...
+		*/
+		ad = addr/2;
+
+		hdsp_write (hdsp, 4096 + (ad*4),
+			    (hdsp->mixer_matrix[(addr&0x7fe)+1] << 16) +
+			    hdsp->mixer_matrix[addr&0x7fe]);
+
+		return 0;
+
+	} else {
+
+		ad = (addr << 16) + data;
+
+		if (hdsp_fifo_wait(hdsp, 127, HDSP_LONG_WAIT))
+			return -1;
+
+		hdsp_write (hdsp, HDSP_fifoData, ad);
+		hdsp->mixer_matrix[addr] = data;
+
+	}
+
+	return 0;
+}
+
+static int snd_hdsp_use_is_exclusive(struct hdsp *hdsp)
+{
+	unsigned long flags;
+	int ret = 1;
+
+	spin_lock_irqsave(&hdsp->lock, flags);
+	if ((hdsp->playback_pid != hdsp->capture_pid) &&
+	    (hdsp->playback_pid >= 0) && (hdsp->capture_pid >= 0))
+		ret = 0;
+	spin_unlock_irqrestore(&hdsp->lock, flags);
+	return ret;
+}
+
+static int hdsp_spdif_sample_rate(struct hdsp *hdsp)
+{
+	unsigned int status = hdsp_read(hdsp, HDSP_statusRegister);
+	unsigned int rate_bits = (status & HDSP_spdifFrequencyMask);
+
+	/* For the 9632, the mask is different */
+	if (hdsp->io_type == H9632)
+		 rate_bits = (status & HDSP_spdifFrequencyMask_9632);
+
+	if (status & HDSP_SPDIFErrorFlag)
+		return 0;
+
+	switch (rate_bits) {
+	case HDSP_spdifFrequency32KHz: return 32000;
+	case HDSP_spdifFrequency44_1KHz: return 44100;
+	case HDSP_spdifFrequency48KHz: return 48000;
+	case HDSP_spdifFrequency64KHz: return 64000;
+	case HDSP_spdifFrequency88_2KHz: return 88200;
+	case HDSP_spdifFrequency96KHz: return 96000;
+	case HDSP_spdifFrequency128KHz:
+		if (hdsp->io_type == H9632) return 128000;
+		break;
+	case HDSP_spdifFrequency176_4KHz:
+		if (hdsp->io_type == H9632) return 176400;
+		break;
+	case HDSP_spdifFrequency192KHz:
+		if (hdsp->io_type == H9632) return 192000;
+		break;
+	default:
+		break;
+	}
+	snd_printk ("Hammerfall-DSP: unknown spdif frequency status; bits = 0x%x, status = 0x%x\n", rate_bits, status);
+	return 0;
+}
+
+static int hdsp_external_sample_rate(struct hdsp *hdsp)
+{
+	unsigned int status2 = hdsp_read(hdsp, HDSP_status2Register);
+	unsigned int rate_bits = status2 & HDSP_systemFrequencyMask;
+
+	/* For the 9632 card, there seems to be no bit for indicating external
+	 * sample rate greater than 96kHz. The card reports the corresponding
+	 * single speed. So the best means seems to get spdif rate when
+	 * autosync reference is spdif */
+	if (hdsp->io_type == H9632 &&
+	    hdsp_autosync_ref(hdsp) == HDSP_AUTOSYNC_FROM_SPDIF)
+		 return hdsp_spdif_sample_rate(hdsp);
+
+	switch (rate_bits) {
+	case HDSP_systemFrequency32:   return 32000;
+	case HDSP_systemFrequency44_1: return 44100;
+	case HDSP_systemFrequency48:   return 48000;
+	case HDSP_systemFrequency64:   return 64000;
+	case HDSP_systemFrequency88_2: return 88200;
+	case HDSP_systemFrequency96:   return 96000;
+	default:
+		return 0;
+	}
+}
+
+static void hdsp_compute_period_size(struct hdsp *hdsp)
+{
+	hdsp->period_bytes = 1 << ((hdsp_decode_latency(hdsp->control_register) + 8));
+}
+
+static snd_pcm_uframes_t hdsp_hw_pointer(struct hdsp *hdsp)
+{
+	int position;
+
+	position = hdsp_read(hdsp, HDSP_statusRegister);
+
+	if (!hdsp->precise_ptr)
+		return (position & HDSP_BufferID) ? (hdsp->period_bytes / 4) : 0;
+
+	position &= HDSP_BufferPositionMask;
+	position /= 4;
+	position &= (hdsp->period_bytes/2) - 1;
+	return position;
+}
+
+static void hdsp_reset_hw_pointer(struct hdsp *hdsp)
+{
+	hdsp_write (hdsp, HDSP_resetPointer, 0);
+	if (hdsp->io_type == H9632 && hdsp->firmware_rev >= 152)
+		/* HDSP_resetPointer = HDSP_freqReg, which is strange and
+		 * requires (?) to write again DDS value after a reset pointer
+		 * (at least, it works like this) */
+		hdsp_write (hdsp, HDSP_freqReg, hdsp->dds_value);
+}
+
+static void hdsp_start_audio(struct hdsp *s)
+{
+	s->control_register |= (HDSP_AudioInterruptEnable | HDSP_Start);
+	hdsp_write(s, HDSP_controlRegister, s->control_register);
+}
+
+static void hdsp_stop_audio(struct hdsp *s)
+{
+	s->control_register &= ~(HDSP_Start | HDSP_AudioInterruptEnable);
+	hdsp_write(s, HDSP_controlRegister, s->control_register);
+}
+
+static void hdsp_silence_playback(struct hdsp *hdsp)
+{
+	memset(hdsp->playback_buffer, 0, HDSP_DMA_AREA_BYTES);
+}
+
+static int hdsp_set_interrupt_interval(struct hdsp *s, unsigned int frames)
+{
+	int n;
+
+	spin_lock_irq(&s->lock);
+
+	frames >>= 7;
+	n = 0;
+	while (frames) {
+		n++;
+		frames >>= 1;
+	}
+
+	s->control_register &= ~HDSP_LatencyMask;
+	s->control_register |= hdsp_encode_latency(n);
+
+	hdsp_write(s, HDSP_controlRegister, s->control_register);
+
+	hdsp_compute_period_size(s);
+
+	spin_unlock_irq(&s->lock);
+
+	return 0;
+}
+
+static void hdsp_set_dds_value(struct hdsp *hdsp, int rate)
+{
+	u64 n;
+
+	if (rate >= 112000)
+		rate /= 4;
+	else if (rate >= 56000)
+		rate /= 2;
+
+	n = DDS_NUMERATOR;
+	n = div_u64(n, rate);
+	/* n should be less than 2^32 for being written to FREQ register */
+	snd_BUG_ON(n >> 32);
+	/* HDSP_freqReg and HDSP_resetPointer are the same, so keep the DDS
+	   value to write it after a reset */
+	hdsp->dds_value = n;
+	hdsp_write(hdsp, HDSP_freqReg, hdsp->dds_value);
+}
+
+static int hdsp_set_rate(struct hdsp *hdsp, int rate, int called_internally)
+{
+	int reject_if_open = 0;
+	int current_rate;
+	int rate_bits;
+
+	/* ASSUMPTION: hdsp->lock is either held, or
+	   there is no need for it (e.g. during module
+	   initialization).
+	*/
+
+	if (!(hdsp->control_register & HDSP_ClockModeMaster)) {
+		if (called_internally) {
+			/* request from ctl or card initialization */
+			snd_printk(KERN_ERR "Hammerfall-DSP: device is not running as a clock master: cannot set sample rate.\n");
+			return -1;
+		} else {
+			/* hw_param request while in AutoSync mode */
+			int external_freq = hdsp_external_sample_rate(hdsp);
+			int spdif_freq = hdsp_spdif_sample_rate(hdsp);
+
+			if ((spdif_freq == external_freq*2) && (hdsp_autosync_ref(hdsp) >= HDSP_AUTOSYNC_FROM_ADAT1))
+				snd_printk(KERN_INFO "Hammerfall-DSP: Detected ADAT in double speed mode\n");
+			else if (hdsp->io_type == H9632 && (spdif_freq == external_freq*4) && (hdsp_autosync_ref(hdsp) >= HDSP_AUTOSYNC_FROM_ADAT1))
+				snd_printk(KERN_INFO "Hammerfall-DSP: Detected ADAT in quad speed mode\n");
+			else if (rate != external_freq) {
+				snd_printk(KERN_INFO "Hammerfall-DSP: No AutoSync source for requested rate\n");
+				return -1;
+			}
+		}
+	}
+
+	current_rate = hdsp->system_sample_rate;
+
+	/* Changing from a "single speed" to a "double speed" rate is
+	   not allowed if any substreams are open. This is because
+	   such a change causes a shift in the location of
+	   the DMA buffers and a reduction in the number of available
+	   buffers.
+
+	   Note that a similar but essentially insoluble problem
+	   exists for externally-driven rate changes. All we can do
+	   is to flag rate changes in the read/write routines.  */
+
+	if (rate > 96000 && hdsp->io_type != H9632)
+		return -EINVAL;
+
+	switch (rate) {
+	case 32000:
+		if (current_rate > 48000)
+			reject_if_open = 1;
+		rate_bits = HDSP_Frequency32KHz;
+		break;
+	case 44100:
+		if (current_rate > 48000)
+			reject_if_open = 1;
+		rate_bits = HDSP_Frequency44_1KHz;
+		break;
+	case 48000:
+		if (current_rate > 48000)
+			reject_if_open = 1;
+		rate_bits = HDSP_Frequency48KHz;
+		break;
+	case 64000:
+		if (current_rate <= 48000 || current_rate > 96000)
+			reject_if_open = 1;
+		rate_bits = HDSP_Frequency64KHz;
+		break;
+	case 88200:
+		if (current_rate <= 48000 || current_rate > 96000)
+			reject_if_open = 1;
+		rate_bits = HDSP_Frequency88_2KHz;
+		break;
+	case 96000:
+		if (current_rate <= 48000 || current_rate > 96000)
+			reject_if_open = 1;
+		rate_bits = HDSP_Frequency96KHz;
+		break;
+	case 128000:
+		if (current_rate < 128000)
+			reject_if_open = 1;
+		rate_bits = HDSP_Frequency128KHz;
+		break;
+	case 176400:
+		if (current_rate < 128000)
+			reject_if_open = 1;
+		rate_bits = HDSP_Frequency176_4KHz;
+		break;
+	case 192000:
+		if (current_rate < 128000)
+			reject_if_open = 1;
+		rate_bits = HDSP_Frequency192KHz;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (reject_if_open && (hdsp->capture_pid >= 0 || hdsp->playback_pid >= 0)) {
+		snd_printk ("Hammerfall-DSP: cannot change speed mode (capture PID = %d, playback PID = %d)\n",
+			    hdsp->capture_pid,
+			    hdsp->playback_pid);
+		return -EBUSY;
+	}
+
+	hdsp->control_register &= ~HDSP_FrequencyMask;
+	hdsp->control_register |= rate_bits;
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+
+	/* For HDSP9632 rev 152, need to set DDS value in FREQ register */
+	if (hdsp->io_type == H9632 && hdsp->firmware_rev >= 152)
+		hdsp_set_dds_value(hdsp, rate);
+
+	if (rate >= 128000) {
+		hdsp->channel_map = channel_map_H9632_qs;
+	} else if (rate > 48000) {
+		if (hdsp->io_type == H9632)
+			hdsp->channel_map = channel_map_H9632_ds;
+		else
+			hdsp->channel_map = channel_map_ds;
+	} else {
+		switch (hdsp->io_type) {
+		case Multiface:
+			hdsp->channel_map = channel_map_mf_ss;
+			break;
+		case Digiface:
+		case H9652:
+			hdsp->channel_map = channel_map_df_ss;
+			break;
+		case H9632:
+			hdsp->channel_map = channel_map_H9632_ss;
+			break;
+		default:
+			/* should never happen */
+			break;
+		}
+	}
+
+	hdsp->system_sample_rate = rate;
+
+	return 0;
+}
+
+/*----------------------------------------------------------------------------
+   MIDI
+  ----------------------------------------------------------------------------*/
+
+static unsigned char snd_hdsp_midi_read_byte (struct hdsp *hdsp, int id)
+{
+	/* the hardware already does the relevant bit-mask with 0xff */
+	if (id)
+		return hdsp_read(hdsp, HDSP_midiDataIn1);
+	else
+		return hdsp_read(hdsp, HDSP_midiDataIn0);
+}
+
+static void snd_hdsp_midi_write_byte (struct hdsp *hdsp, int id, int val)
+{
+	/* the hardware already does the relevant bit-mask with 0xff */
+	if (id)
+		hdsp_write(hdsp, HDSP_midiDataOut1, val);
+	else
+		hdsp_write(hdsp, HDSP_midiDataOut0, val);
+}
+
+static int snd_hdsp_midi_input_available (struct hdsp *hdsp, int id)
+{
+	if (id)
+		return (hdsp_read(hdsp, HDSP_midiStatusIn1) & 0xff);
+	else
+		return (hdsp_read(hdsp, HDSP_midiStatusIn0) & 0xff);
+}
+
+static int snd_hdsp_midi_output_possible (struct hdsp *hdsp, int id)
+{
+	int fifo_bytes_used;
+
+	if (id)
+		fifo_bytes_used = hdsp_read(hdsp, HDSP_midiStatusOut1) & 0xff;
+	else
+		fifo_bytes_used = hdsp_read(hdsp, HDSP_midiStatusOut0) & 0xff;
+
+	if (fifo_bytes_used < 128)
+		return  128 - fifo_bytes_used;
+	else
+		return 0;
+}
+
+static void snd_hdsp_flush_midi_input (struct hdsp *hdsp, int id)
+{
+	while (snd_hdsp_midi_input_available (hdsp, id))
+		snd_hdsp_midi_read_byte (hdsp, id);
+}
+
+static int snd_hdsp_midi_output_write (struct hdsp_midi *hmidi)
+{
+	unsigned long flags;
+	int n_pending;
+	int to_write;
+	int i;
+	unsigned char buf[128];
+
+	/* Output is not interrupt driven */
+
+	spin_lock_irqsave (&hmidi->lock, flags);
+	if (hmidi->output) {
+		if (!snd_rawmidi_transmit_empty (hmidi->output)) {
+			if ((n_pending = snd_hdsp_midi_output_possible (hmidi->hdsp, hmidi->id)) > 0) {
+				if (n_pending > (int)sizeof (buf))
+					n_pending = sizeof (buf);
+
+				if ((to_write = snd_rawmidi_transmit (hmidi->output, buf, n_pending)) > 0) {
+					for (i = 0; i < to_write; ++i)
+						snd_hdsp_midi_write_byte (hmidi->hdsp, hmidi->id, buf[i]);
+				}
+			}
+		}
+	}
+	spin_unlock_irqrestore (&hmidi->lock, flags);
+	return 0;
+}
+
+static int snd_hdsp_midi_input_read (struct hdsp_midi *hmidi)
+{
+	unsigned char buf[128]; /* this buffer is designed to match the MIDI input FIFO size */
+	unsigned long flags;
+	int n_pending;
+	int i;
+
+	spin_lock_irqsave (&hmidi->lock, flags);
+	if ((n_pending = snd_hdsp_midi_input_available (hmidi->hdsp, hmidi->id)) > 0) {
+		if (hmidi->input) {
+			if (n_pending > (int)sizeof (buf))
+				n_pending = sizeof (buf);
+			for (i = 0; i < n_pending; ++i)
+				buf[i] = snd_hdsp_midi_read_byte (hmidi->hdsp, hmidi->id);
+			if (n_pending)
+				snd_rawmidi_receive (hmidi->input, buf, n_pending);
+		} else {
+			/* flush the MIDI input FIFO */
+			while (--n_pending)
+				snd_hdsp_midi_read_byte (hmidi->hdsp, hmidi->id);
+		}
+	}
+	hmidi->pending = 0;
+	if (hmidi->id)
+		hmidi->hdsp->control_register |= HDSP_Midi1InterruptEnable;
+	else
+		hmidi->hdsp->control_register |= HDSP_Midi0InterruptEnable;
+	hdsp_write(hmidi->hdsp, HDSP_controlRegister, hmidi->hdsp->control_register);
+	spin_unlock_irqrestore (&hmidi->lock, flags);
+	return snd_hdsp_midi_output_write (hmidi);
+}
+
+static void snd_hdsp_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct hdsp *hdsp;
+	struct hdsp_midi *hmidi;
+	unsigned long flags;
+	u32 ie;
+
+	hmidi = (struct hdsp_midi *) substream->rmidi->private_data;
+	hdsp = hmidi->hdsp;
+	ie = hmidi->id ? HDSP_Midi1InterruptEnable : HDSP_Midi0InterruptEnable;
+	spin_lock_irqsave (&hdsp->lock, flags);
+	if (up) {
+		if (!(hdsp->control_register & ie)) {
+			snd_hdsp_flush_midi_input (hdsp, hmidi->id);
+			hdsp->control_register |= ie;
+		}
+	} else {
+		hdsp->control_register &= ~ie;
+		tasklet_kill(&hdsp->midi_tasklet);
+	}
+
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	spin_unlock_irqrestore (&hdsp->lock, flags);
+}
+
+static void snd_hdsp_midi_output_timer(unsigned long data)
+{
+	struct hdsp_midi *hmidi = (struct hdsp_midi *) data;
+	unsigned long flags;
+
+	snd_hdsp_midi_output_write(hmidi);
+	spin_lock_irqsave (&hmidi->lock, flags);
+
+	/* this does not bump hmidi->istimer, because the
+	   kernel automatically removed the timer when it
+	   expired, and we are now adding it back, thus
+	   leaving istimer wherever it was set before.
+	*/
+
+	if (hmidi->istimer) {
+		hmidi->timer.expires = 1 + jiffies;
+		add_timer(&hmidi->timer);
+	}
+
+	spin_unlock_irqrestore (&hmidi->lock, flags);
+}
+
+static void snd_hdsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct hdsp_midi *hmidi;
+	unsigned long flags;
+
+	hmidi = (struct hdsp_midi *) substream->rmidi->private_data;
+	spin_lock_irqsave (&hmidi->lock, flags);
+	if (up) {
+		if (!hmidi->istimer) {
+			init_timer(&hmidi->timer);
+			hmidi->timer.function = snd_hdsp_midi_output_timer;
+			hmidi->timer.data = (unsigned long) hmidi;
+			hmidi->timer.expires = 1 + jiffies;
+			add_timer(&hmidi->timer);
+			hmidi->istimer++;
+		}
+	} else {
+		if (hmidi->istimer && --hmidi->istimer <= 0)
+			del_timer (&hmidi->timer);
+	}
+	spin_unlock_irqrestore (&hmidi->lock, flags);
+	if (up)
+		snd_hdsp_midi_output_write(hmidi);
+}
+
+static int snd_hdsp_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct hdsp_midi *hmidi;
+
+	hmidi = (struct hdsp_midi *) substream->rmidi->private_data;
+	spin_lock_irq (&hmidi->lock);
+	snd_hdsp_flush_midi_input (hmidi->hdsp, hmidi->id);
+	hmidi->input = substream;
+	spin_unlock_irq (&hmidi->lock);
+
+	return 0;
+}
+
+static int snd_hdsp_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct hdsp_midi *hmidi;
+
+	hmidi = (struct hdsp_midi *) substream->rmidi->private_data;
+	spin_lock_irq (&hmidi->lock);
+	hmidi->output = substream;
+	spin_unlock_irq (&hmidi->lock);
+
+	return 0;
+}
+
+static int snd_hdsp_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct hdsp_midi *hmidi;
+
+	snd_hdsp_midi_input_trigger (substream, 0);
+
+	hmidi = (struct hdsp_midi *) substream->rmidi->private_data;
+	spin_lock_irq (&hmidi->lock);
+	hmidi->input = NULL;
+	spin_unlock_irq (&hmidi->lock);
+
+	return 0;
+}
+
+static int snd_hdsp_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct hdsp_midi *hmidi;
+
+	snd_hdsp_midi_output_trigger (substream, 0);
+
+	hmidi = (struct hdsp_midi *) substream->rmidi->private_data;
+	spin_lock_irq (&hmidi->lock);
+	hmidi->output = NULL;
+	spin_unlock_irq (&hmidi->lock);
+
+	return 0;
+}
+
+static struct snd_rawmidi_ops snd_hdsp_midi_output =
+{
+	.open =		snd_hdsp_midi_output_open,
+	.close =	snd_hdsp_midi_output_close,
+	.trigger =	snd_hdsp_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_hdsp_midi_input =
+{
+	.open =		snd_hdsp_midi_input_open,
+	.close =	snd_hdsp_midi_input_close,
+	.trigger =	snd_hdsp_midi_input_trigger,
+};
+
+static int snd_hdsp_create_midi (struct snd_card *card, struct hdsp *hdsp, int id)
+{
+	char buf[32];
+
+	hdsp->midi[id].id = id;
+	hdsp->midi[id].rmidi = NULL;
+	hdsp->midi[id].input = NULL;
+	hdsp->midi[id].output = NULL;
+	hdsp->midi[id].hdsp = hdsp;
+	hdsp->midi[id].istimer = 0;
+	hdsp->midi[id].pending = 0;
+	spin_lock_init (&hdsp->midi[id].lock);
+
+	sprintf (buf, "%s MIDI %d", card->shortname, id+1);
+	if (snd_rawmidi_new (card, buf, id, 1, 1, &hdsp->midi[id].rmidi) < 0)
+		return -1;
+
+	sprintf(hdsp->midi[id].rmidi->name, "HDSP MIDI %d", id+1);
+	hdsp->midi[id].rmidi->private_data = &hdsp->midi[id];
+
+	snd_rawmidi_set_ops (hdsp->midi[id].rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_hdsp_midi_output);
+	snd_rawmidi_set_ops (hdsp->midi[id].rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_hdsp_midi_input);
+
+	hdsp->midi[id].rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+		SNDRV_RAWMIDI_INFO_INPUT |
+		SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	return 0;
+}
+
+/*-----------------------------------------------------------------------------
+  Control Interface
+  ----------------------------------------------------------------------------*/
+
+static u32 snd_hdsp_convert_from_aes(struct snd_aes_iec958 *aes)
+{
+	u32 val = 0;
+	val |= (aes->status[0] & IEC958_AES0_PROFESSIONAL) ? HDSP_SPDIFProfessional : 0;
+	val |= (aes->status[0] & IEC958_AES0_NONAUDIO) ? HDSP_SPDIFNonAudio : 0;
+	if (val & HDSP_SPDIFProfessional)
+		val |= (aes->status[0] & IEC958_AES0_PRO_EMPHASIS_5015) ? HDSP_SPDIFEmphasis : 0;
+	else
+		val |= (aes->status[0] & IEC958_AES0_CON_EMPHASIS_5015) ? HDSP_SPDIFEmphasis : 0;
+	return val;
+}
+
+static void snd_hdsp_convert_to_aes(struct snd_aes_iec958 *aes, u32 val)
+{
+	aes->status[0] = ((val & HDSP_SPDIFProfessional) ? IEC958_AES0_PROFESSIONAL : 0) |
+			 ((val & HDSP_SPDIFNonAudio) ? IEC958_AES0_NONAUDIO : 0);
+	if (val & HDSP_SPDIFProfessional)
+		aes->status[0] |= (val & HDSP_SPDIFEmphasis) ? IEC958_AES0_PRO_EMPHASIS_5015 : 0;
+	else
+		aes->status[0] |= (val & HDSP_SPDIFEmphasis) ? IEC958_AES0_CON_EMPHASIS_5015 : 0;
+}
+
+static int snd_hdsp_control_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_hdsp_control_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	snd_hdsp_convert_to_aes(&ucontrol->value.iec958, hdsp->creg_spdif);
+	return 0;
+}
+
+static int snd_hdsp_control_spdif_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	u32 val;
+
+	val = snd_hdsp_convert_from_aes(&ucontrol->value.iec958);
+	spin_lock_irq(&hdsp->lock);
+	change = val != hdsp->creg_spdif;
+	hdsp->creg_spdif = val;
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+static int snd_hdsp_control_spdif_stream_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_hdsp_control_spdif_stream_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	snd_hdsp_convert_to_aes(&ucontrol->value.iec958, hdsp->creg_spdif_stream);
+	return 0;
+}
+
+static int snd_hdsp_control_spdif_stream_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	u32 val;
+
+	val = snd_hdsp_convert_from_aes(&ucontrol->value.iec958);
+	spin_lock_irq(&hdsp->lock);
+	change = val != hdsp->creg_spdif_stream;
+	hdsp->creg_spdif_stream = val;
+	hdsp->control_register &= ~(HDSP_SPDIFProfessional | HDSP_SPDIFNonAudio | HDSP_SPDIFEmphasis);
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register |= val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+static int snd_hdsp_control_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_hdsp_control_spdif_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = kcontrol->private_value;
+	return 0;
+}
+
+#define HDSP_SPDIF_IN(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_spdif_in, \
+  .get = snd_hdsp_get_spdif_in, \
+  .put = snd_hdsp_put_spdif_in }
+
+static unsigned int hdsp_spdif_in(struct hdsp *hdsp)
+{
+	return hdsp_decode_spdif_in(hdsp->control_register & HDSP_SPDIFInputMask);
+}
+
+static int hdsp_set_spdif_input(struct hdsp *hdsp, int in)
+{
+	hdsp->control_register &= ~HDSP_SPDIFInputMask;
+	hdsp->control_register |= hdsp_encode_spdif_in(in);
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+static int snd_hdsp_info_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = {"Optical", "Coaxial", "Internal", "AES"};
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = ((hdsp->io_type == H9632) ? 4 : 3);
+	if (uinfo->value.enumerated.item > ((hdsp->io_type == H9632) ? 3 : 2))
+		uinfo->value.enumerated.item = ((hdsp->io_type == H9632) ? 3 : 2);
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdsp_get_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_spdif_in(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.enumerated.item[0] % ((hdsp->io_type == H9632) ? 4 : 3);
+	spin_lock_irq(&hdsp->lock);
+	change = val != hdsp_spdif_in(hdsp);
+	if (change)
+		hdsp_set_spdif_input(hdsp, val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_SPDIF_OUT(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_hdsp_info_spdif_bits, \
+  .get = snd_hdsp_get_spdif_out, .put = snd_hdsp_put_spdif_out }
+
+static int hdsp_spdif_out(struct hdsp *hdsp)
+{
+	return (hdsp->control_register & HDSP_SPDIFOpticalOut) ? 1 : 0;
+}
+
+static int hdsp_set_spdif_output(struct hdsp *hdsp, int out)
+{
+	if (out)
+		hdsp->control_register |= HDSP_SPDIFOpticalOut;
+	else
+		hdsp->control_register &= ~HDSP_SPDIFOpticalOut;
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+#define snd_hdsp_info_spdif_bits	snd_ctl_boolean_mono_info
+
+static int snd_hdsp_get_spdif_out(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = hdsp_spdif_out(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_spdif_out(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdsp->lock);
+	change = (int)val != hdsp_spdif_out(hdsp);
+	hdsp_set_spdif_output(hdsp, val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_SPDIF_PROFESSIONAL(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_hdsp_info_spdif_bits, \
+  .get = snd_hdsp_get_spdif_professional, .put = snd_hdsp_put_spdif_professional }
+
+static int hdsp_spdif_professional(struct hdsp *hdsp)
+{
+	return (hdsp->control_register & HDSP_SPDIFProfessional) ? 1 : 0;
+}
+
+static int hdsp_set_spdif_professional(struct hdsp *hdsp, int val)
+{
+	if (val)
+		hdsp->control_register |= HDSP_SPDIFProfessional;
+	else
+		hdsp->control_register &= ~HDSP_SPDIFProfessional;
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+static int snd_hdsp_get_spdif_professional(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = hdsp_spdif_professional(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_spdif_professional(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdsp->lock);
+	change = (int)val != hdsp_spdif_professional(hdsp);
+	hdsp_set_spdif_professional(hdsp, val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_SPDIF_EMPHASIS(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_hdsp_info_spdif_bits, \
+  .get = snd_hdsp_get_spdif_emphasis, .put = snd_hdsp_put_spdif_emphasis }
+
+static int hdsp_spdif_emphasis(struct hdsp *hdsp)
+{
+	return (hdsp->control_register & HDSP_SPDIFEmphasis) ? 1 : 0;
+}
+
+static int hdsp_set_spdif_emphasis(struct hdsp *hdsp, int val)
+{
+	if (val)
+		hdsp->control_register |= HDSP_SPDIFEmphasis;
+	else
+		hdsp->control_register &= ~HDSP_SPDIFEmphasis;
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+static int snd_hdsp_get_spdif_emphasis(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = hdsp_spdif_emphasis(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_spdif_emphasis(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdsp->lock);
+	change = (int)val != hdsp_spdif_emphasis(hdsp);
+	hdsp_set_spdif_emphasis(hdsp, val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_SPDIF_NON_AUDIO(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_hdsp_info_spdif_bits, \
+  .get = snd_hdsp_get_spdif_nonaudio, .put = snd_hdsp_put_spdif_nonaudio }
+
+static int hdsp_spdif_nonaudio(struct hdsp *hdsp)
+{
+	return (hdsp->control_register & HDSP_SPDIFNonAudio) ? 1 : 0;
+}
+
+static int hdsp_set_spdif_nonaudio(struct hdsp *hdsp, int val)
+{
+	if (val)
+		hdsp->control_register |= HDSP_SPDIFNonAudio;
+	else
+		hdsp->control_register &= ~HDSP_SPDIFNonAudio;
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+static int snd_hdsp_get_spdif_nonaudio(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = hdsp_spdif_nonaudio(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_spdif_nonaudio(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdsp->lock);
+	change = (int)val != hdsp_spdif_nonaudio(hdsp);
+	hdsp_set_spdif_nonaudio(hdsp, val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_SPDIF_SAMPLE_RATE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ, \
+  .info = snd_hdsp_info_spdif_sample_rate, \
+  .get = snd_hdsp_get_spdif_sample_rate \
+}
+
+static int snd_hdsp_info_spdif_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {"32000", "44100", "48000", "64000", "88200", "96000", "None", "128000", "176400", "192000"};
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = (hdsp->io_type == H9632) ? 10 : 7;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdsp_get_spdif_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	switch (hdsp_spdif_sample_rate(hdsp)) {
+	case 32000:
+		ucontrol->value.enumerated.item[0] = 0;
+		break;
+	case 44100:
+		ucontrol->value.enumerated.item[0] = 1;
+		break;
+	case 48000:
+		ucontrol->value.enumerated.item[0] = 2;
+		break;
+	case 64000:
+		ucontrol->value.enumerated.item[0] = 3;
+		break;
+	case 88200:
+		ucontrol->value.enumerated.item[0] = 4;
+		break;
+	case 96000:
+		ucontrol->value.enumerated.item[0] = 5;
+		break;
+	case 128000:
+		ucontrol->value.enumerated.item[0] = 7;
+		break;
+	case 176400:
+		ucontrol->value.enumerated.item[0] = 8;
+		break;
+	case 192000:
+		ucontrol->value.enumerated.item[0] = 9;
+		break;
+	default:
+		ucontrol->value.enumerated.item[0] = 6;
+	}
+	return 0;
+}
+
+#define HDSP_SYSTEM_SAMPLE_RATE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ, \
+  .info = snd_hdsp_info_system_sample_rate, \
+  .get = snd_hdsp_get_system_sample_rate \
+}
+
+static int snd_hdsp_info_system_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_hdsp_get_system_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp->system_sample_rate;
+	return 0;
+}
+
+#define HDSP_AUTOSYNC_SAMPLE_RATE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ, \
+  .info = snd_hdsp_info_autosync_sample_rate, \
+  .get = snd_hdsp_get_autosync_sample_rate \
+}
+
+static int snd_hdsp_info_autosync_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	static char *texts[] = {"32000", "44100", "48000", "64000", "88200", "96000", "None", "128000", "176400", "192000"};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = (hdsp->io_type == H9632) ? 10 : 7 ;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdsp_get_autosync_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	switch (hdsp_external_sample_rate(hdsp)) {
+	case 32000:
+		ucontrol->value.enumerated.item[0] = 0;
+		break;
+	case 44100:
+		ucontrol->value.enumerated.item[0] = 1;
+		break;
+	case 48000:
+		ucontrol->value.enumerated.item[0] = 2;
+		break;
+	case 64000:
+		ucontrol->value.enumerated.item[0] = 3;
+		break;
+	case 88200:
+		ucontrol->value.enumerated.item[0] = 4;
+		break;
+	case 96000:
+		ucontrol->value.enumerated.item[0] = 5;
+		break;
+	case 128000:
+		ucontrol->value.enumerated.item[0] = 7;
+		break;
+	case 176400:
+		ucontrol->value.enumerated.item[0] = 8;
+		break;
+	case 192000:
+		ucontrol->value.enumerated.item[0] = 9;
+		break;
+	default:
+		ucontrol->value.enumerated.item[0] = 6;
+	}
+	return 0;
+}
+
+#define HDSP_SYSTEM_CLOCK_MODE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ, \
+  .info = snd_hdsp_info_system_clock_mode, \
+  .get = snd_hdsp_get_system_clock_mode \
+}
+
+static int hdsp_system_clock_mode(struct hdsp *hdsp)
+{
+	if (hdsp->control_register & HDSP_ClockModeMaster)
+		return 0;
+	else if (hdsp_external_sample_rate(hdsp) != hdsp->system_sample_rate)
+			return 0;
+	return 1;
+}
+
+static int snd_hdsp_info_system_clock_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {"Master", "Slave" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdsp_get_system_clock_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_system_clock_mode(hdsp);
+	return 0;
+}
+
+#define HDSP_CLOCK_SOURCE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_clock_source, \
+  .get = snd_hdsp_get_clock_source, \
+  .put = snd_hdsp_put_clock_source \
+}
+
+static int hdsp_clock_source(struct hdsp *hdsp)
+{
+	if (hdsp->control_register & HDSP_ClockModeMaster) {
+		switch (hdsp->system_sample_rate) {
+		case 32000:
+			return 1;
+		case 44100:
+			return 2;
+		case 48000:
+			return 3;
+		case 64000:
+			return 4;
+		case 88200:
+			return 5;
+		case 96000:
+			return 6;
+		case 128000:
+			return 7;
+		case 176400:
+			return 8;
+		case 192000:
+			return 9;
+		default:
+			return 3;
+		}
+	} else {
+		return 0;
+	}
+}
+
+static int hdsp_set_clock_source(struct hdsp *hdsp, int mode)
+{
+	int rate;
+	switch (mode) {
+	case HDSP_CLOCK_SOURCE_AUTOSYNC:
+		if (hdsp_external_sample_rate(hdsp) != 0) {
+		    if (!hdsp_set_rate(hdsp, hdsp_external_sample_rate(hdsp), 1)) {
+			hdsp->control_register &= ~HDSP_ClockModeMaster;
+			hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+			return 0;
+		    }
+		}
+		return -1;
+	case HDSP_CLOCK_SOURCE_INTERNAL_32KHZ:
+		rate = 32000;
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_44_1KHZ:
+		rate = 44100;
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_48KHZ:
+		rate = 48000;
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_64KHZ:
+		rate = 64000;
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_88_2KHZ:
+		rate = 88200;
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_96KHZ:
+		rate = 96000;
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_128KHZ:
+		rate = 128000;
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_176_4KHZ:
+		rate = 176400;
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_192KHZ:
+		rate = 192000;
+		break;
+	default:
+		rate = 48000;
+	}
+	hdsp->control_register |= HDSP_ClockModeMaster;
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	hdsp_set_rate(hdsp, rate, 1);
+	return 0;
+}
+
+static int snd_hdsp_info_clock_source(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {"AutoSync", "Internal 32.0 kHz", "Internal 44.1 kHz", "Internal 48.0 kHz", "Internal 64.0 kHz", "Internal 88.2 kHz", "Internal 96.0 kHz", "Internal 128 kHz", "Internal 176.4 kHz", "Internal 192.0 KHz" };
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	if (hdsp->io_type == H9632)
+	    uinfo->value.enumerated.items = 10;
+	else
+	    uinfo->value.enumerated.items = 7;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdsp_get_clock_source(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_clock_source(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_clock_source(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.enumerated.item[0];
+	if (val < 0) val = 0;
+	if (hdsp->io_type == H9632) {
+		if (val > 9)
+			val = 9;
+	} else {
+		if (val > 6)
+			val = 6;
+	}
+	spin_lock_irq(&hdsp->lock);
+	if (val != hdsp_clock_source(hdsp))
+		change = (hdsp_set_clock_source(hdsp, val) == 0) ? 1 : 0;
+	else
+		change = 0;
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define snd_hdsp_info_clock_source_lock		snd_ctl_boolean_mono_info
+
+static int snd_hdsp_get_clock_source_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = hdsp->clock_source_locked;
+	return 0;
+}
+
+static int snd_hdsp_put_clock_source_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+
+	change = (int)ucontrol->value.integer.value[0] != hdsp->clock_source_locked;
+	if (change)
+		hdsp->clock_source_locked = !!ucontrol->value.integer.value[0];
+	return change;
+}
+
+#define HDSP_DA_GAIN(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_da_gain, \
+  .get = snd_hdsp_get_da_gain, \
+  .put = snd_hdsp_put_da_gain \
+}
+
+static int hdsp_da_gain(struct hdsp *hdsp)
+{
+	switch (hdsp->control_register & HDSP_DAGainMask) {
+	case HDSP_DAGainHighGain:
+		return 0;
+	case HDSP_DAGainPlus4dBu:
+		return 1;
+	case HDSP_DAGainMinus10dBV:
+		return 2;
+	default:
+		return 1;
+	}
+}
+
+static int hdsp_set_da_gain(struct hdsp *hdsp, int mode)
+{
+	hdsp->control_register &= ~HDSP_DAGainMask;
+	switch (mode) {
+	case 0:
+		hdsp->control_register |= HDSP_DAGainHighGain;
+		break;
+	case 1:
+		hdsp->control_register |= HDSP_DAGainPlus4dBu;
+		break;
+	case 2:
+		hdsp->control_register |= HDSP_DAGainMinus10dBV;
+		break;
+	default:
+		return -1;
+
+	}
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+static int snd_hdsp_info_da_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {"Hi Gain", "+4 dBu", "-10 dbV"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdsp_get_da_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_da_gain(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_da_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.enumerated.item[0];
+	if (val < 0) val = 0;
+	if (val > 2) val = 2;
+	spin_lock_irq(&hdsp->lock);
+	if (val != hdsp_da_gain(hdsp))
+		change = (hdsp_set_da_gain(hdsp, val) == 0) ? 1 : 0;
+	else
+		change = 0;
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_AD_GAIN(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_ad_gain, \
+  .get = snd_hdsp_get_ad_gain, \
+  .put = snd_hdsp_put_ad_gain \
+}
+
+static int hdsp_ad_gain(struct hdsp *hdsp)
+{
+	switch (hdsp->control_register & HDSP_ADGainMask) {
+	case HDSP_ADGainMinus10dBV:
+		return 0;
+	case HDSP_ADGainPlus4dBu:
+		return 1;
+	case HDSP_ADGainLowGain:
+		return 2;
+	default:
+		return 1;
+	}
+}
+
+static int hdsp_set_ad_gain(struct hdsp *hdsp, int mode)
+{
+	hdsp->control_register &= ~HDSP_ADGainMask;
+	switch (mode) {
+	case 0:
+		hdsp->control_register |= HDSP_ADGainMinus10dBV;
+		break;
+	case 1:
+		hdsp->control_register |= HDSP_ADGainPlus4dBu;
+		break;
+	case 2:
+		hdsp->control_register |= HDSP_ADGainLowGain;
+		break;
+	default:
+		return -1;
+
+	}
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+static int snd_hdsp_info_ad_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {"-10 dBV", "+4 dBu", "Lo Gain"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdsp_get_ad_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_ad_gain(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_ad_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.enumerated.item[0];
+	if (val < 0) val = 0;
+	if (val > 2) val = 2;
+	spin_lock_irq(&hdsp->lock);
+	if (val != hdsp_ad_gain(hdsp))
+		change = (hdsp_set_ad_gain(hdsp, val) == 0) ? 1 : 0;
+	else
+		change = 0;
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_PHONE_GAIN(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_phone_gain, \
+  .get = snd_hdsp_get_phone_gain, \
+  .put = snd_hdsp_put_phone_gain \
+}
+
+static int hdsp_phone_gain(struct hdsp *hdsp)
+{
+	switch (hdsp->control_register & HDSP_PhoneGainMask) {
+	case HDSP_PhoneGain0dB:
+		return 0;
+	case HDSP_PhoneGainMinus6dB:
+		return 1;
+	case HDSP_PhoneGainMinus12dB:
+		return 2;
+	default:
+		return 0;
+	}
+}
+
+static int hdsp_set_phone_gain(struct hdsp *hdsp, int mode)
+{
+	hdsp->control_register &= ~HDSP_PhoneGainMask;
+	switch (mode) {
+	case 0:
+		hdsp->control_register |= HDSP_PhoneGain0dB;
+		break;
+	case 1:
+		hdsp->control_register |= HDSP_PhoneGainMinus6dB;
+		break;
+	case 2:
+		hdsp->control_register |= HDSP_PhoneGainMinus12dB;
+		break;
+	default:
+		return -1;
+
+	}
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+static int snd_hdsp_info_phone_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {"0 dB", "-6 dB", "-12 dB"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdsp_get_phone_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_phone_gain(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_phone_gain(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.enumerated.item[0];
+	if (val < 0) val = 0;
+	if (val > 2) val = 2;
+	spin_lock_irq(&hdsp->lock);
+	if (val != hdsp_phone_gain(hdsp))
+		change = (hdsp_set_phone_gain(hdsp, val) == 0) ? 1 : 0;
+	else
+		change = 0;
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_XLR_BREAKOUT_CABLE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_xlr_breakout_cable, \
+  .get = snd_hdsp_get_xlr_breakout_cable, \
+  .put = snd_hdsp_put_xlr_breakout_cable \
+}
+
+static int hdsp_xlr_breakout_cable(struct hdsp *hdsp)
+{
+	if (hdsp->control_register & HDSP_XLRBreakoutCable)
+		return 1;
+	return 0;
+}
+
+static int hdsp_set_xlr_breakout_cable(struct hdsp *hdsp, int mode)
+{
+	if (mode)
+		hdsp->control_register |= HDSP_XLRBreakoutCable;
+	else
+		hdsp->control_register &= ~HDSP_XLRBreakoutCable;
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+#define snd_hdsp_info_xlr_breakout_cable	snd_ctl_boolean_mono_info
+
+static int snd_hdsp_get_xlr_breakout_cable(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_xlr_breakout_cable(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_xlr_breakout_cable(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdsp->lock);
+	change = (int)val != hdsp_xlr_breakout_cable(hdsp);
+	hdsp_set_xlr_breakout_cable(hdsp, val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+/* (De)activates old RME Analog Extension Board
+   These are connected to the internal ADAT connector
+   Switching this on desactivates external ADAT
+*/
+#define HDSP_AEB(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_aeb, \
+  .get = snd_hdsp_get_aeb, \
+  .put = snd_hdsp_put_aeb \
+}
+
+static int hdsp_aeb(struct hdsp *hdsp)
+{
+	if (hdsp->control_register & HDSP_AnalogExtensionBoard)
+		return 1;
+	return 0;
+}
+
+static int hdsp_set_aeb(struct hdsp *hdsp, int mode)
+{
+	if (mode)
+		hdsp->control_register |= HDSP_AnalogExtensionBoard;
+	else
+		hdsp->control_register &= ~HDSP_AnalogExtensionBoard;
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+#define snd_hdsp_info_aeb		snd_ctl_boolean_mono_info
+
+static int snd_hdsp_get_aeb(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_aeb(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_aeb(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdsp->lock);
+	change = (int)val != hdsp_aeb(hdsp);
+	hdsp_set_aeb(hdsp, val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_PREF_SYNC_REF(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_pref_sync_ref, \
+  .get = snd_hdsp_get_pref_sync_ref, \
+  .put = snd_hdsp_put_pref_sync_ref \
+}
+
+static int hdsp_pref_sync_ref(struct hdsp *hdsp)
+{
+	/* Notice that this looks at the requested sync source,
+	   not the one actually in use.
+	*/
+
+	switch (hdsp->control_register & HDSP_SyncRefMask) {
+	case HDSP_SyncRef_ADAT1:
+		return HDSP_SYNC_FROM_ADAT1;
+	case HDSP_SyncRef_ADAT2:
+		return HDSP_SYNC_FROM_ADAT2;
+	case HDSP_SyncRef_ADAT3:
+		return HDSP_SYNC_FROM_ADAT3;
+	case HDSP_SyncRef_SPDIF:
+		return HDSP_SYNC_FROM_SPDIF;
+	case HDSP_SyncRef_WORD:
+		return HDSP_SYNC_FROM_WORD;
+	case HDSP_SyncRef_ADAT_SYNC:
+		return HDSP_SYNC_FROM_ADAT_SYNC;
+	default:
+		return HDSP_SYNC_FROM_WORD;
+	}
+	return 0;
+}
+
+static int hdsp_set_pref_sync_ref(struct hdsp *hdsp, int pref)
+{
+	hdsp->control_register &= ~HDSP_SyncRefMask;
+	switch (pref) {
+	case HDSP_SYNC_FROM_ADAT1:
+		hdsp->control_register &= ~HDSP_SyncRefMask; /* clear SyncRef bits */
+		break;
+	case HDSP_SYNC_FROM_ADAT2:
+		hdsp->control_register |= HDSP_SyncRef_ADAT2;
+		break;
+	case HDSP_SYNC_FROM_ADAT3:
+		hdsp->control_register |= HDSP_SyncRef_ADAT3;
+		break;
+	case HDSP_SYNC_FROM_SPDIF:
+		hdsp->control_register |= HDSP_SyncRef_SPDIF;
+		break;
+	case HDSP_SYNC_FROM_WORD:
+		hdsp->control_register |= HDSP_SyncRef_WORD;
+		break;
+	case HDSP_SYNC_FROM_ADAT_SYNC:
+		hdsp->control_register |= HDSP_SyncRef_ADAT_SYNC;
+		break;
+	default:
+		return -1;
+	}
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+static int snd_hdsp_info_pref_sync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {"Word", "IEC958", "ADAT1", "ADAT Sync", "ADAT2", "ADAT3" };
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+
+	switch (hdsp->io_type) {
+	case Digiface:
+	case H9652:
+		uinfo->value.enumerated.items = 6;
+		break;
+	case Multiface:
+		uinfo->value.enumerated.items = 4;
+		break;
+	case H9632:
+		uinfo->value.enumerated.items = 3;
+		break;
+	default:
+		uinfo->value.enumerated.items = 0;
+		break;
+	}
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdsp_get_pref_sync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_pref_sync_ref(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_pref_sync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change, max;
+	unsigned int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+
+	switch (hdsp->io_type) {
+	case Digiface:
+	case H9652:
+		max = 6;
+		break;
+	case Multiface:
+		max = 4;
+		break;
+	case H9632:
+		max = 3;
+		break;
+	default:
+		return -EIO;
+	}
+
+	val = ucontrol->value.enumerated.item[0] % max;
+	spin_lock_irq(&hdsp->lock);
+	change = (int)val != hdsp_pref_sync_ref(hdsp);
+	hdsp_set_pref_sync_ref(hdsp, val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_AUTOSYNC_REF(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ, \
+  .info = snd_hdsp_info_autosync_ref, \
+  .get = snd_hdsp_get_autosync_ref, \
+}
+
+static int hdsp_autosync_ref(struct hdsp *hdsp)
+{
+	/* This looks at the autosync selected sync reference */
+	unsigned int status2 = hdsp_read(hdsp, HDSP_status2Register);
+
+	switch (status2 & HDSP_SelSyncRefMask) {
+	case HDSP_SelSyncRef_WORD:
+		return HDSP_AUTOSYNC_FROM_WORD;
+	case HDSP_SelSyncRef_ADAT_SYNC:
+		return HDSP_AUTOSYNC_FROM_ADAT_SYNC;
+	case HDSP_SelSyncRef_SPDIF:
+		return HDSP_AUTOSYNC_FROM_SPDIF;
+	case HDSP_SelSyncRefMask:
+		return HDSP_AUTOSYNC_FROM_NONE;
+	case HDSP_SelSyncRef_ADAT1:
+		return HDSP_AUTOSYNC_FROM_ADAT1;
+	case HDSP_SelSyncRef_ADAT2:
+		return HDSP_AUTOSYNC_FROM_ADAT2;
+	case HDSP_SelSyncRef_ADAT3:
+		return HDSP_AUTOSYNC_FROM_ADAT3;
+	default:
+		return HDSP_AUTOSYNC_FROM_WORD;
+	}
+	return 0;
+}
+
+static int snd_hdsp_info_autosync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {"Word", "ADAT Sync", "IEC958", "None", "ADAT1", "ADAT2", "ADAT3" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 7;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdsp_get_autosync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_autosync_ref(hdsp);
+	return 0;
+}
+
+#define HDSP_LINE_OUT(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_line_out, \
+  .get = snd_hdsp_get_line_out, \
+  .put = snd_hdsp_put_line_out \
+}
+
+static int hdsp_line_out(struct hdsp *hdsp)
+{
+	return (hdsp->control_register & HDSP_LineOut) ? 1 : 0;
+}
+
+static int hdsp_set_line_output(struct hdsp *hdsp, int out)
+{
+	if (out)
+		hdsp->control_register |= HDSP_LineOut;
+	else
+		hdsp->control_register &= ~HDSP_LineOut;
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	return 0;
+}
+
+#define snd_hdsp_info_line_out		snd_ctl_boolean_mono_info
+
+static int snd_hdsp_get_line_out(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdsp->lock);
+	ucontrol->value.integer.value[0] = hdsp_line_out(hdsp);
+	spin_unlock_irq(&hdsp->lock);
+	return 0;
+}
+
+static int snd_hdsp_put_line_out(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdsp->lock);
+	change = (int)val != hdsp_line_out(hdsp);
+	hdsp_set_line_output(hdsp, val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_PRECISE_POINTER(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_precise_pointer, \
+  .get = snd_hdsp_get_precise_pointer, \
+  .put = snd_hdsp_put_precise_pointer \
+}
+
+static int hdsp_set_precise_pointer(struct hdsp *hdsp, int precise)
+{
+	if (precise)
+		hdsp->precise_ptr = 1;
+	else
+		hdsp->precise_ptr = 0;
+	return 0;
+}
+
+#define snd_hdsp_info_precise_pointer		snd_ctl_boolean_mono_info
+
+static int snd_hdsp_get_precise_pointer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdsp->lock);
+	ucontrol->value.integer.value[0] = hdsp->precise_ptr;
+	spin_unlock_irq(&hdsp->lock);
+	return 0;
+}
+
+static int snd_hdsp_put_precise_pointer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdsp->lock);
+	change = (int)val != hdsp->precise_ptr;
+	hdsp_set_precise_pointer(hdsp, val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_USE_MIDI_TASKLET(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_use_midi_tasklet, \
+  .get = snd_hdsp_get_use_midi_tasklet, \
+  .put = snd_hdsp_put_use_midi_tasklet \
+}
+
+static int hdsp_set_use_midi_tasklet(struct hdsp *hdsp, int use_tasklet)
+{
+	if (use_tasklet)
+		hdsp->use_midi_tasklet = 1;
+	else
+		hdsp->use_midi_tasklet = 0;
+	return 0;
+}
+
+#define snd_hdsp_info_use_midi_tasklet		snd_ctl_boolean_mono_info
+
+static int snd_hdsp_get_use_midi_tasklet(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdsp->lock);
+	ucontrol->value.integer.value[0] = hdsp->use_midi_tasklet;
+	spin_unlock_irq(&hdsp->lock);
+	return 0;
+}
+
+static int snd_hdsp_put_use_midi_tasklet(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdsp->lock);
+	change = (int)val != hdsp->use_midi_tasklet;
+	hdsp_set_use_midi_tasklet(hdsp, val);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_MIXER(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
+  .name = xname, \
+  .index = xindex, \
+  .device = 0, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+		 SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_hdsp_info_mixer, \
+  .get = snd_hdsp_get_mixer, \
+  .put = snd_hdsp_put_mixer \
+}
+
+static int snd_hdsp_info_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 3;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 65536;
+	uinfo->value.integer.step = 1;
+	return 0;
+}
+
+static int snd_hdsp_get_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int source;
+	int destination;
+	int addr;
+
+	source = ucontrol->value.integer.value[0];
+	destination = ucontrol->value.integer.value[1];
+
+	if (source >= hdsp->max_channels)
+		addr = hdsp_playback_to_output_key(hdsp,source-hdsp->max_channels,destination);
+	else
+		addr = hdsp_input_to_output_key(hdsp,source, destination);
+
+	spin_lock_irq(&hdsp->lock);
+	ucontrol->value.integer.value[2] = hdsp_read_gain (hdsp, addr);
+	spin_unlock_irq(&hdsp->lock);
+	return 0;
+}
+
+static int snd_hdsp_put_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	int source;
+	int destination;
+	int gain;
+	int addr;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+
+	source = ucontrol->value.integer.value[0];
+	destination = ucontrol->value.integer.value[1];
+
+	if (source >= hdsp->max_channels)
+		addr = hdsp_playback_to_output_key(hdsp,source-hdsp->max_channels, destination);
+	else
+		addr = hdsp_input_to_output_key(hdsp,source, destination);
+
+	gain = ucontrol->value.integer.value[2];
+
+	spin_lock_irq(&hdsp->lock);
+	change = gain != hdsp_read_gain(hdsp, addr);
+	if (change)
+		hdsp_write_gain(hdsp, addr, gain);
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+#define HDSP_WC_SYNC_CHECK(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_hdsp_info_sync_check, \
+  .get = snd_hdsp_get_wc_sync_check \
+}
+
+static int snd_hdsp_info_sync_check(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = {"No Lock", "Lock", "Sync" };
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int hdsp_wc_sync_check(struct hdsp *hdsp)
+{
+	int status2 = hdsp_read(hdsp, HDSP_status2Register);
+	if (status2 & HDSP_wc_lock) {
+		if (status2 & HDSP_wc_sync)
+			return 2;
+		else
+			 return 1;
+	} else
+		return 0;
+	return 0;
+}
+
+static int snd_hdsp_get_wc_sync_check(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_wc_sync_check(hdsp);
+	return 0;
+}
+
+#define HDSP_SPDIF_SYNC_CHECK(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_hdsp_info_sync_check, \
+  .get = snd_hdsp_get_spdif_sync_check \
+}
+
+static int hdsp_spdif_sync_check(struct hdsp *hdsp)
+{
+	int status = hdsp_read(hdsp, HDSP_statusRegister);
+	if (status & HDSP_SPDIFErrorFlag)
+		return 0;
+	else {
+		if (status & HDSP_SPDIFSync)
+			return 2;
+		else
+			return 1;
+	}
+	return 0;
+}
+
+static int snd_hdsp_get_spdif_sync_check(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_spdif_sync_check(hdsp);
+	return 0;
+}
+
+#define HDSP_ADATSYNC_SYNC_CHECK(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_hdsp_info_sync_check, \
+  .get = snd_hdsp_get_adatsync_sync_check \
+}
+
+static int hdsp_adatsync_sync_check(struct hdsp *hdsp)
+{
+	int status = hdsp_read(hdsp, HDSP_statusRegister);
+	if (status & HDSP_TimecodeLock) {
+		if (status & HDSP_TimecodeSync)
+			return 2;
+		else
+			return 1;
+	} else
+		return 0;
+}
+
+static int snd_hdsp_get_adatsync_sync_check(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_adatsync_sync_check(hdsp);
+	return 0;
+}
+
+#define HDSP_ADAT_SYNC_CHECK \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_hdsp_info_sync_check, \
+  .get = snd_hdsp_get_adat_sync_check \
+}
+
+static int hdsp_adat_sync_check(struct hdsp *hdsp, int idx)
+{
+	int status = hdsp_read(hdsp, HDSP_statusRegister);
+
+	if (status & (HDSP_Lock0>>idx)) {
+		if (status & (HDSP_Sync0>>idx))
+			return 2;
+		else
+			return 1;
+	} else
+		return 0;
+}
+
+static int snd_hdsp_get_adat_sync_check(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	int offset;
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	offset = ucontrol->id.index - 1;
+	snd_BUG_ON(offset < 0);
+
+	switch (hdsp->io_type) {
+	case Digiface:
+	case H9652:
+		if (offset >= 3)
+			return -EINVAL;
+		break;
+	case Multiface:
+	case H9632:
+		if (offset >= 1)
+			return -EINVAL;
+		break;
+	default:
+		return -EIO;
+	}
+
+	ucontrol->value.enumerated.item[0] = hdsp_adat_sync_check(hdsp, offset);
+	return 0;
+}
+
+#define HDSP_DDS_OFFSET(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdsp_info_dds_offset, \
+  .get = snd_hdsp_get_dds_offset, \
+  .put = snd_hdsp_put_dds_offset \
+}
+
+static int hdsp_dds_offset(struct hdsp *hdsp)
+{
+	u64 n;
+	unsigned int dds_value = hdsp->dds_value;
+	int system_sample_rate = hdsp->system_sample_rate;
+
+	if (!dds_value)
+		return 0;
+
+	n = DDS_NUMERATOR;
+	/*
+	 * dds_value = n / rate
+	 * rate = n / dds_value
+	 */
+	n = div_u64(n, dds_value);
+	if (system_sample_rate >= 112000)
+		n *= 4;
+	else if (system_sample_rate >= 56000)
+		n *= 2;
+	return ((int)n) - system_sample_rate;
+}
+
+static int hdsp_set_dds_offset(struct hdsp *hdsp, int offset_hz)
+{
+	int rate = hdsp->system_sample_rate + offset_hz;
+	hdsp_set_dds_value(hdsp, rate);
+	return 0;
+}
+
+static int snd_hdsp_info_dds_offset(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = -5000;
+	uinfo->value.integer.max = 5000;
+	return 0;
+}
+
+static int snd_hdsp_get_dds_offset(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdsp_dds_offset(hdsp);
+	return 0;
+}
+
+static int snd_hdsp_put_dds_offset(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdsp *hdsp = snd_kcontrol_chip(kcontrol);
+	int change;
+	int val;
+
+	if (!snd_hdsp_use_is_exclusive(hdsp))
+		return -EBUSY;
+	val = ucontrol->value.enumerated.item[0];
+	spin_lock_irq(&hdsp->lock);
+	if (val != hdsp_dds_offset(hdsp))
+		change = (hdsp_set_dds_offset(hdsp, val) == 0) ? 1 : 0;
+	else
+		change = 0;
+	spin_unlock_irq(&hdsp->lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_hdsp_9632_controls[] = {
+HDSP_DA_GAIN("DA Gain", 0),
+HDSP_AD_GAIN("AD Gain", 0),
+HDSP_PHONE_GAIN("Phones Gain", 0),
+HDSP_XLR_BREAKOUT_CABLE("XLR Breakout Cable", 0),
+HDSP_DDS_OFFSET("DDS Sample Rate Offset", 0)
+};
+
+static struct snd_kcontrol_new snd_hdsp_controls[] = {
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =		snd_hdsp_control_spdif_info,
+	.get =		snd_hdsp_control_spdif_get,
+	.put =		snd_hdsp_control_spdif_put,
+},
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+	.info =		snd_hdsp_control_spdif_stream_info,
+	.get =		snd_hdsp_control_spdif_stream_get,
+	.put =		snd_hdsp_control_spdif_stream_put,
+},
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
+	.info =		snd_hdsp_control_spdif_mask_info,
+	.get =		snd_hdsp_control_spdif_mask_get,
+	.private_value = IEC958_AES0_NONAUDIO |
+  			 IEC958_AES0_PROFESSIONAL |
+			 IEC958_AES0_CON_EMPHASIS,
+},
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK),
+	.info =		snd_hdsp_control_spdif_mask_info,
+	.get =		snd_hdsp_control_spdif_mask_get,
+	.private_value = IEC958_AES0_NONAUDIO |
+			 IEC958_AES0_PROFESSIONAL |
+			 IEC958_AES0_PRO_EMPHASIS,
+},
+HDSP_MIXER("Mixer", 0),
+HDSP_SPDIF_IN("IEC958 Input Connector", 0),
+HDSP_SPDIF_OUT("IEC958 Output also on ADAT1", 0),
+HDSP_SPDIF_PROFESSIONAL("IEC958 Professional Bit", 0),
+HDSP_SPDIF_EMPHASIS("IEC958 Emphasis Bit", 0),
+HDSP_SPDIF_NON_AUDIO("IEC958 Non-audio Bit", 0),
+/* 'Sample Clock Source' complies with the alsa control naming scheme */
+HDSP_CLOCK_SOURCE("Sample Clock Source", 0),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Sample Clock Source Locking",
+	.info = snd_hdsp_info_clock_source_lock,
+	.get = snd_hdsp_get_clock_source_lock,
+	.put = snd_hdsp_put_clock_source_lock,
+},
+HDSP_SYSTEM_CLOCK_MODE("System Clock Mode", 0),
+HDSP_PREF_SYNC_REF("Preferred Sync Reference", 0),
+HDSP_AUTOSYNC_REF("AutoSync Reference", 0),
+HDSP_SPDIF_SAMPLE_RATE("SPDIF Sample Rate", 0),
+HDSP_SYSTEM_SAMPLE_RATE("System Sample Rate", 0),
+/* 'External Rate' complies with the alsa control naming scheme */
+HDSP_AUTOSYNC_SAMPLE_RATE("External Rate", 0),
+HDSP_WC_SYNC_CHECK("Word Clock Lock Status", 0),
+HDSP_SPDIF_SYNC_CHECK("SPDIF Lock Status", 0),
+HDSP_ADATSYNC_SYNC_CHECK("ADAT Sync Lock Status", 0),
+HDSP_LINE_OUT("Line Out", 0),
+HDSP_PRECISE_POINTER("Precise Pointer", 0),
+HDSP_USE_MIDI_TASKLET("Use Midi Tasklet", 0),
+};
+
+static struct snd_kcontrol_new snd_hdsp_96xx_aeb = HDSP_AEB("Analog Extension Board", 0);
+static struct snd_kcontrol_new snd_hdsp_adat_sync_check = HDSP_ADAT_SYNC_CHECK;
+
+static int snd_hdsp_create_controls(struct snd_card *card, struct hdsp *hdsp)
+{
+	unsigned int idx;
+	int err;
+	struct snd_kcontrol *kctl;
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_hdsp_controls); idx++) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_hdsp_controls[idx], hdsp))) < 0)
+			return err;
+		if (idx == 1)	/* IEC958 (S/PDIF) Stream */
+			hdsp->spdif_ctl = kctl;
+	}
+
+	/* ADAT SyncCheck status */
+	snd_hdsp_adat_sync_check.name = "ADAT Lock Status";
+	snd_hdsp_adat_sync_check.index = 1;
+	if ((err = snd_ctl_add (card, kctl = snd_ctl_new1(&snd_hdsp_adat_sync_check, hdsp))))
+		return err;
+	if (hdsp->io_type == Digiface || hdsp->io_type == H9652) {
+		for (idx = 1; idx < 3; ++idx) {
+			snd_hdsp_adat_sync_check.index = idx+1;
+			if ((err = snd_ctl_add (card, kctl = snd_ctl_new1(&snd_hdsp_adat_sync_check, hdsp))))
+				return err;
+		}
+	}
+
+	/* DA, AD and Phone gain and XLR breakout cable controls for H9632 cards */
+	if (hdsp->io_type == H9632) {
+		for (idx = 0; idx < ARRAY_SIZE(snd_hdsp_9632_controls); idx++) {
+			if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_hdsp_9632_controls[idx], hdsp))) < 0)
+				return err;
+		}
+	}
+
+	/* AEB control for H96xx card */
+	if (hdsp->io_type == H9632 || hdsp->io_type == H9652) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_hdsp_96xx_aeb, hdsp))) < 0)
+				return err;
+	}
+
+	return 0;
+}
+
+/*------------------------------------------------------------
+   /proc interface
+ ------------------------------------------------------------*/
+
+static void
+snd_hdsp_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct hdsp *hdsp = entry->private_data;
+	unsigned int status;
+	unsigned int status2;
+	char *pref_sync_ref;
+	char *autosync_ref;
+	char *system_clock_mode;
+	char *clock_source;
+	int x;
+
+	status = hdsp_read(hdsp, HDSP_statusRegister);
+	status2 = hdsp_read(hdsp, HDSP_status2Register);
+
+	snd_iprintf(buffer, "%s (Card #%d)\n", hdsp->card_name,
+		    hdsp->card->number + 1);
+	snd_iprintf(buffer, "Buffers: capture %p playback %p\n",
+		    hdsp->capture_buffer, hdsp->playback_buffer);
+	snd_iprintf(buffer, "IRQ: %d Registers bus: 0x%lx VM: 0x%lx\n",
+		    hdsp->irq, hdsp->port, (unsigned long)hdsp->iobase);
+	snd_iprintf(buffer, "Control register: 0x%x\n", hdsp->control_register);
+	snd_iprintf(buffer, "Control2 register: 0x%x\n",
+		    hdsp->control2_register);
+	snd_iprintf(buffer, "Status register: 0x%x\n", status);
+	snd_iprintf(buffer, "Status2 register: 0x%x\n", status2);
+
+	if (hdsp_check_for_iobox(hdsp)) {
+		snd_iprintf(buffer, "No I/O box connected.\n"
+			    "Please connect one and upload firmware.\n");
+		return;
+	}
+
+	if (hdsp_check_for_firmware(hdsp, 0)) {
+		if (hdsp->state & HDSP_FirmwareCached) {
+			if (snd_hdsp_load_firmware_from_cache(hdsp) != 0) {
+				snd_iprintf(buffer, "Firmware loading from "
+					    "cache failed, "
+					    "please upload manually.\n");
+				return;
+			}
+		} else {
+			int err = -EINVAL;
+#ifdef HDSP_FW_LOADER
+			err = hdsp_request_fw_loader(hdsp);
+#endif
+			if (err < 0) {
+				snd_iprintf(buffer,
+					    "No firmware loaded nor cached, "
+					    "please upload firmware.\n");
+				return;
+			}
+		}
+	}
+
+	snd_iprintf(buffer, "FIFO status: %d\n", hdsp_read(hdsp, HDSP_fifoStatus) & 0xff);
+	snd_iprintf(buffer, "MIDI1 Output status: 0x%x\n", hdsp_read(hdsp, HDSP_midiStatusOut0));
+	snd_iprintf(buffer, "MIDI1 Input status: 0x%x\n", hdsp_read(hdsp, HDSP_midiStatusIn0));
+	snd_iprintf(buffer, "MIDI2 Output status: 0x%x\n", hdsp_read(hdsp, HDSP_midiStatusOut1));
+	snd_iprintf(buffer, "MIDI2 Input status: 0x%x\n", hdsp_read(hdsp, HDSP_midiStatusIn1));
+	snd_iprintf(buffer, "Use Midi Tasklet: %s\n", hdsp->use_midi_tasklet ? "on" : "off");
+
+	snd_iprintf(buffer, "\n");
+
+	x = 1 << (6 + hdsp_decode_latency(hdsp->control_register & HDSP_LatencyMask));
+
+	snd_iprintf(buffer, "Buffer Size (Latency): %d samples (2 periods of %lu bytes)\n", x, (unsigned long) hdsp->period_bytes);
+	snd_iprintf(buffer, "Hardware pointer (frames): %ld\n", hdsp_hw_pointer(hdsp));
+	snd_iprintf(buffer, "Precise pointer: %s\n", hdsp->precise_ptr ? "on" : "off");
+	snd_iprintf(buffer, "Line out: %s\n", (hdsp->control_register & HDSP_LineOut) ? "on" : "off");
+
+	snd_iprintf(buffer, "Firmware version: %d\n", (status2&HDSP_version0)|(status2&HDSP_version1)<<1|(status2&HDSP_version2)<<2);
+
+	snd_iprintf(buffer, "\n");
+
+	switch (hdsp_clock_source(hdsp)) {
+	case HDSP_CLOCK_SOURCE_AUTOSYNC:
+		clock_source = "AutoSync";
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_32KHZ:
+		clock_source = "Internal 32 kHz";
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_44_1KHZ:
+		clock_source = "Internal 44.1 kHz";
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_48KHZ:
+		clock_source = "Internal 48 kHz";
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_64KHZ:
+		clock_source = "Internal 64 kHz";
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_88_2KHZ:
+		clock_source = "Internal 88.2 kHz";
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_96KHZ:
+		clock_source = "Internal 96 kHz";
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_128KHZ:
+		clock_source = "Internal 128 kHz";
+		break;
+	case HDSP_CLOCK_SOURCE_INTERNAL_176_4KHZ:
+		clock_source = "Internal 176.4 kHz";
+		break;
+		case HDSP_CLOCK_SOURCE_INTERNAL_192KHZ:
+		clock_source = "Internal 192 kHz";
+		break;
+	default:
+		clock_source = "Error";
+	}
+	snd_iprintf (buffer, "Sample Clock Source: %s\n", clock_source);
+
+	if (hdsp_system_clock_mode(hdsp))
+		system_clock_mode = "Slave";
+	else
+		system_clock_mode = "Master";
+
+	switch (hdsp_pref_sync_ref (hdsp)) {
+	case HDSP_SYNC_FROM_WORD:
+		pref_sync_ref = "Word Clock";
+		break;
+	case HDSP_SYNC_FROM_ADAT_SYNC:
+		pref_sync_ref = "ADAT Sync";
+		break;
+	case HDSP_SYNC_FROM_SPDIF:
+		pref_sync_ref = "SPDIF";
+		break;
+	case HDSP_SYNC_FROM_ADAT1:
+		pref_sync_ref = "ADAT1";
+		break;
+	case HDSP_SYNC_FROM_ADAT2:
+		pref_sync_ref = "ADAT2";
+		break;
+	case HDSP_SYNC_FROM_ADAT3:
+		pref_sync_ref = "ADAT3";
+		break;
+	default:
+		pref_sync_ref = "Word Clock";
+		break;
+	}
+	snd_iprintf (buffer, "Preferred Sync Reference: %s\n", pref_sync_ref);
+
+	switch (hdsp_autosync_ref (hdsp)) {
+	case HDSP_AUTOSYNC_FROM_WORD:
+		autosync_ref = "Word Clock";
+		break;
+	case HDSP_AUTOSYNC_FROM_ADAT_SYNC:
+		autosync_ref = "ADAT Sync";
+		break;
+	case HDSP_AUTOSYNC_FROM_SPDIF:
+		autosync_ref = "SPDIF";
+		break;
+	case HDSP_AUTOSYNC_FROM_NONE:
+		autosync_ref = "None";
+		break;
+	case HDSP_AUTOSYNC_FROM_ADAT1:
+		autosync_ref = "ADAT1";
+		break;
+	case HDSP_AUTOSYNC_FROM_ADAT2:
+		autosync_ref = "ADAT2";
+		break;
+	case HDSP_AUTOSYNC_FROM_ADAT3:
+		autosync_ref = "ADAT3";
+		break;
+	default:
+		autosync_ref = "---";
+		break;
+	}
+	snd_iprintf (buffer, "AutoSync Reference: %s\n", autosync_ref);
+
+	snd_iprintf (buffer, "AutoSync Frequency: %d\n", hdsp_external_sample_rate(hdsp));
+
+	snd_iprintf (buffer, "System Clock Mode: %s\n", system_clock_mode);
+
+	snd_iprintf (buffer, "System Clock Frequency: %d\n", hdsp->system_sample_rate);
+	snd_iprintf (buffer, "System Clock Locked: %s\n", hdsp->clock_source_locked ? "Yes" : "No");
+
+	snd_iprintf(buffer, "\n");
+
+	switch (hdsp_spdif_in(hdsp)) {
+	case HDSP_SPDIFIN_OPTICAL:
+		snd_iprintf(buffer, "IEC958 input: Optical\n");
+		break;
+	case HDSP_SPDIFIN_COAXIAL:
+		snd_iprintf(buffer, "IEC958 input: Coaxial\n");
+		break;
+	case HDSP_SPDIFIN_INTERNAL:
+		snd_iprintf(buffer, "IEC958 input: Internal\n");
+		break;
+	case HDSP_SPDIFIN_AES:
+		snd_iprintf(buffer, "IEC958 input: AES\n");
+		break;
+	default:
+		snd_iprintf(buffer, "IEC958 input: ???\n");
+		break;
+	}
+
+	if (hdsp->control_register & HDSP_SPDIFOpticalOut)
+		snd_iprintf(buffer, "IEC958 output: Coaxial & ADAT1\n");
+	else
+		snd_iprintf(buffer, "IEC958 output: Coaxial only\n");
+
+	if (hdsp->control_register & HDSP_SPDIFProfessional)
+		snd_iprintf(buffer, "IEC958 quality: Professional\n");
+	else
+		snd_iprintf(buffer, "IEC958 quality: Consumer\n");
+
+	if (hdsp->control_register & HDSP_SPDIFEmphasis)
+		snd_iprintf(buffer, "IEC958 emphasis: on\n");
+	else
+		snd_iprintf(buffer, "IEC958 emphasis: off\n");
+
+	if (hdsp->control_register & HDSP_SPDIFNonAudio)
+		snd_iprintf(buffer, "IEC958 NonAudio: on\n");
+	else
+		snd_iprintf(buffer, "IEC958 NonAudio: off\n");
+	if ((x = hdsp_spdif_sample_rate (hdsp)) != 0)
+		snd_iprintf (buffer, "IEC958 sample rate: %d\n", x);
+	else
+		snd_iprintf (buffer, "IEC958 sample rate: Error flag set\n");
+
+	snd_iprintf(buffer, "\n");
+
+	/* Sync Check */
+	x = status & HDSP_Sync0;
+	if (status & HDSP_Lock0)
+		snd_iprintf(buffer, "ADAT1: %s\n", x ? "Sync" : "Lock");
+	else
+		snd_iprintf(buffer, "ADAT1: No Lock\n");
+
+	switch (hdsp->io_type) {
+	case Digiface:
+	case H9652:
+		x = status & HDSP_Sync1;
+		if (status & HDSP_Lock1)
+			snd_iprintf(buffer, "ADAT2: %s\n", x ? "Sync" : "Lock");
+		else
+			snd_iprintf(buffer, "ADAT2: No Lock\n");
+		x = status & HDSP_Sync2;
+		if (status & HDSP_Lock2)
+			snd_iprintf(buffer, "ADAT3: %s\n", x ? "Sync" : "Lock");
+		else
+			snd_iprintf(buffer, "ADAT3: No Lock\n");
+		break;
+	default:
+		/* relax */
+		break;
+	}
+
+	x = status & HDSP_SPDIFSync;
+	if (status & HDSP_SPDIFErrorFlag)
+		snd_iprintf (buffer, "SPDIF: No Lock\n");
+	else
+		snd_iprintf (buffer, "SPDIF: %s\n", x ? "Sync" : "Lock");
+
+	x = status2 & HDSP_wc_sync;
+	if (status2 & HDSP_wc_lock)
+		snd_iprintf (buffer, "Word Clock: %s\n", x ? "Sync" : "Lock");
+	else
+		snd_iprintf (buffer, "Word Clock: No Lock\n");
+
+	x = status & HDSP_TimecodeSync;
+	if (status & HDSP_TimecodeLock)
+		snd_iprintf(buffer, "ADAT Sync: %s\n", x ? "Sync" : "Lock");
+	else
+		snd_iprintf(buffer, "ADAT Sync: No Lock\n");
+
+	snd_iprintf(buffer, "\n");
+
+	/* Informations about H9632 specific controls */
+	if (hdsp->io_type == H9632) {
+		char *tmp;
+
+		switch (hdsp_ad_gain(hdsp)) {
+		case 0:
+			tmp = "-10 dBV";
+			break;
+		case 1:
+			tmp = "+4 dBu";
+			break;
+		default:
+			tmp = "Lo Gain";
+			break;
+		}
+		snd_iprintf(buffer, "AD Gain : %s\n", tmp);
+
+		switch (hdsp_da_gain(hdsp)) {
+		case 0:
+			tmp = "Hi Gain";
+			break;
+		case 1:
+			tmp = "+4 dBu";
+			break;
+		default:
+			tmp = "-10 dBV";
+			break;
+		}
+		snd_iprintf(buffer, "DA Gain : %s\n", tmp);
+
+		switch (hdsp_phone_gain(hdsp)) {
+		case 0:
+			tmp = "0 dB";
+			break;
+		case 1:
+			tmp = "-6 dB";
+			break;
+		default:
+			tmp = "-12 dB";
+			break;
+		}
+		snd_iprintf(buffer, "Phones Gain : %s\n", tmp);
+
+		snd_iprintf(buffer, "XLR Breakout Cable : %s\n", hdsp_xlr_breakout_cable(hdsp) ? "yes" : "no");
+
+		if (hdsp->control_register & HDSP_AnalogExtensionBoard)
+			snd_iprintf(buffer, "AEB : on (ADAT1 internal)\n");
+		else
+			snd_iprintf(buffer, "AEB : off (ADAT1 external)\n");
+		snd_iprintf(buffer, "\n");
+	}
+
+}
+
+static void snd_hdsp_proc_init(struct hdsp *hdsp)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(hdsp->card, "hdsp", &entry))
+		snd_info_set_text_ops(entry, hdsp, snd_hdsp_proc_read);
+}
+
+static void snd_hdsp_free_buffers(struct hdsp *hdsp)
+{
+	snd_hammerfall_free_buffer(&hdsp->capture_dma_buf, hdsp->pci);
+	snd_hammerfall_free_buffer(&hdsp->playback_dma_buf, hdsp->pci);
+}
+
+static int __devinit snd_hdsp_initialize_memory(struct hdsp *hdsp)
+{
+	unsigned long pb_bus, cb_bus;
+
+	if (snd_hammerfall_get_buffer(hdsp->pci, &hdsp->capture_dma_buf, HDSP_DMA_AREA_BYTES) < 0 ||
+	    snd_hammerfall_get_buffer(hdsp->pci, &hdsp->playback_dma_buf, HDSP_DMA_AREA_BYTES) < 0) {
+		if (hdsp->capture_dma_buf.area)
+			snd_dma_free_pages(&hdsp->capture_dma_buf);
+		printk(KERN_ERR "%s: no buffers available\n", hdsp->card_name);
+		return -ENOMEM;
+	}
+
+	/* Align to bus-space 64K boundary */
+
+	cb_bus = ALIGN(hdsp->capture_dma_buf.addr, 0x10000ul);
+	pb_bus = ALIGN(hdsp->playback_dma_buf.addr, 0x10000ul);
+
+	/* Tell the card where it is */
+
+	hdsp_write(hdsp, HDSP_inputBufferAddress, cb_bus);
+	hdsp_write(hdsp, HDSP_outputBufferAddress, pb_bus);
+
+	hdsp->capture_buffer = hdsp->capture_dma_buf.area + (cb_bus - hdsp->capture_dma_buf.addr);
+	hdsp->playback_buffer = hdsp->playback_dma_buf.area + (pb_bus - hdsp->playback_dma_buf.addr);
+
+	return 0;
+}
+
+static int snd_hdsp_set_defaults(struct hdsp *hdsp)
+{
+	unsigned int i;
+
+	/* ASSUMPTION: hdsp->lock is either held, or
+	   there is no need to hold it (e.g. during module
+	   initialization).
+	 */
+
+	/* set defaults:
+
+	   SPDIF Input via Coax
+	   Master clock mode
+	   maximum latency (7 => 2^7 = 8192 samples, 64Kbyte buffer,
+	                    which implies 2 4096 sample, 32Kbyte periods).
+           Enable line out.
+	 */
+
+	hdsp->control_register = HDSP_ClockModeMaster |
+		                 HDSP_SPDIFInputCoaxial |
+		                 hdsp_encode_latency(7) |
+		                 HDSP_LineOut;
+
+
+	hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+
+#ifdef SNDRV_BIG_ENDIAN
+	hdsp->control2_register = HDSP_BIGENDIAN_MODE;
+#else
+	hdsp->control2_register = 0;
+#endif
+	if (hdsp->io_type == H9652)
+	        snd_hdsp_9652_enable_mixer (hdsp);
+	else
+		hdsp_write (hdsp, HDSP_control2Reg, hdsp->control2_register);
+
+	hdsp_reset_hw_pointer(hdsp);
+	hdsp_compute_period_size(hdsp);
+
+	/* silence everything */
+
+	for (i = 0; i < HDSP_MATRIX_MIXER_SIZE; ++i)
+		hdsp->mixer_matrix[i] = MINUS_INFINITY_GAIN;
+
+	for (i = 0; i < ((hdsp->io_type == H9652 || hdsp->io_type == H9632) ? 1352 : HDSP_MATRIX_MIXER_SIZE); ++i) {
+		if (hdsp_write_gain (hdsp, i, MINUS_INFINITY_GAIN))
+			return -EIO;
+	}
+
+	/* H9632 specific defaults */
+	if (hdsp->io_type == H9632) {
+		hdsp->control_register |= (HDSP_DAGainPlus4dBu | HDSP_ADGainPlus4dBu | HDSP_PhoneGain0dB);
+		hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+	}
+
+	/* set a default rate so that the channel map is set up.
+	 */
+
+	hdsp_set_rate(hdsp, 48000, 1);
+
+	return 0;
+}
+
+static void hdsp_midi_tasklet(unsigned long arg)
+{
+	struct hdsp *hdsp = (struct hdsp *)arg;
+
+	if (hdsp->midi[0].pending)
+		snd_hdsp_midi_input_read (&hdsp->midi[0]);
+	if (hdsp->midi[1].pending)
+		snd_hdsp_midi_input_read (&hdsp->midi[1]);
+}
+
+static irqreturn_t snd_hdsp_interrupt(int irq, void *dev_id)
+{
+	struct hdsp *hdsp = (struct hdsp *) dev_id;
+	unsigned int status;
+	int audio;
+	int midi0;
+	int midi1;
+	unsigned int midi0status;
+	unsigned int midi1status;
+	int schedule = 0;
+
+	status = hdsp_read(hdsp, HDSP_statusRegister);
+
+	audio = status & HDSP_audioIRQPending;
+	midi0 = status & HDSP_midi0IRQPending;
+	midi1 = status & HDSP_midi1IRQPending;
+
+	if (!audio && !midi0 && !midi1)
+		return IRQ_NONE;
+
+	hdsp_write(hdsp, HDSP_interruptConfirmation, 0);
+
+	midi0status = hdsp_read (hdsp, HDSP_midiStatusIn0) & 0xff;
+	midi1status = hdsp_read (hdsp, HDSP_midiStatusIn1) & 0xff;
+
+	if (!(hdsp->state & HDSP_InitializationComplete))
+		return IRQ_HANDLED;
+
+	if (audio) {
+		if (hdsp->capture_substream)
+			snd_pcm_period_elapsed(hdsp->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream);
+
+		if (hdsp->playback_substream)
+			snd_pcm_period_elapsed(hdsp->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream);
+	}
+
+	if (midi0 && midi0status) {
+		if (hdsp->use_midi_tasklet) {
+			/* we disable interrupts for this input until processing is done */
+			hdsp->control_register &= ~HDSP_Midi0InterruptEnable;
+			hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+			hdsp->midi[0].pending = 1;
+			schedule = 1;
+		} else {
+			snd_hdsp_midi_input_read (&hdsp->midi[0]);
+		}
+	}
+	if (hdsp->io_type != Multiface && hdsp->io_type != H9632 && midi1 && midi1status) {
+		if (hdsp->use_midi_tasklet) {
+			/* we disable interrupts for this input until processing is done */
+			hdsp->control_register &= ~HDSP_Midi1InterruptEnable;
+			hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register);
+			hdsp->midi[1].pending = 1;
+			schedule = 1;
+		} else {
+			snd_hdsp_midi_input_read (&hdsp->midi[1]);
+		}
+	}
+	if (hdsp->use_midi_tasklet && schedule)
+		tasklet_schedule(&hdsp->midi_tasklet);
+	return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_hdsp_hw_pointer(struct snd_pcm_substream *substream)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+	return hdsp_hw_pointer(hdsp);
+}
+
+static char *hdsp_channel_buffer_location(struct hdsp *hdsp,
+					     int stream,
+					     int channel)
+
+{
+	int mapped_channel;
+
+        if (snd_BUG_ON(channel < 0 || channel >= hdsp->max_channels))
+		return NULL;
+
+	if ((mapped_channel = hdsp->channel_map[channel]) < 0)
+		return NULL;
+
+	if (stream == SNDRV_PCM_STREAM_CAPTURE)
+		return hdsp->capture_buffer + (mapped_channel * HDSP_CHANNEL_BUFFER_BYTES);
+	else
+		return hdsp->playback_buffer + (mapped_channel * HDSP_CHANNEL_BUFFER_BYTES);
+}
+
+static int snd_hdsp_playback_copy(struct snd_pcm_substream *substream, int channel,
+				  snd_pcm_uframes_t pos, void __user *src, snd_pcm_uframes_t count)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+	char *channel_buf;
+
+	if (snd_BUG_ON(pos + count > HDSP_CHANNEL_BUFFER_BYTES / 4))
+		return -EINVAL;
+
+	channel_buf = hdsp_channel_buffer_location (hdsp, substream->pstr->stream, channel);
+	if (snd_BUG_ON(!channel_buf))
+		return -EIO;
+	if (copy_from_user(channel_buf + pos * 4, src, count * 4))
+		return -EFAULT;
+	return count;
+}
+
+static int snd_hdsp_capture_copy(struct snd_pcm_substream *substream, int channel,
+				 snd_pcm_uframes_t pos, void __user *dst, snd_pcm_uframes_t count)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+	char *channel_buf;
+
+	if (snd_BUG_ON(pos + count > HDSP_CHANNEL_BUFFER_BYTES / 4))
+		return -EINVAL;
+
+	channel_buf = hdsp_channel_buffer_location (hdsp, substream->pstr->stream, channel);
+	if (snd_BUG_ON(!channel_buf))
+		return -EIO;
+	if (copy_to_user(dst, channel_buf + pos * 4, count * 4))
+		return -EFAULT;
+	return count;
+}
+
+static int snd_hdsp_hw_silence(struct snd_pcm_substream *substream, int channel,
+				  snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+	char *channel_buf;
+
+	channel_buf = hdsp_channel_buffer_location (hdsp, substream->pstr->stream, channel);
+	if (snd_BUG_ON(!channel_buf))
+		return -EIO;
+	memset(channel_buf + pos * 4, 0, count * 4);
+	return count;
+}
+
+static int snd_hdsp_reset(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *other;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		other = hdsp->capture_substream;
+	else
+		other = hdsp->playback_substream;
+	if (hdsp->running)
+		runtime->status->hw_ptr = hdsp_hw_pointer(hdsp);
+	else
+		runtime->status->hw_ptr = 0;
+	if (other) {
+		struct snd_pcm_substream *s;
+		struct snd_pcm_runtime *oruntime = other->runtime;
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s == other) {
+				oruntime->status->hw_ptr = runtime->status->hw_ptr;
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+static int snd_hdsp_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+	int err;
+	pid_t this_pid;
+	pid_t other_pid;
+
+	if (hdsp_check_for_iobox (hdsp))
+		return -EIO;
+
+	if (hdsp_check_for_firmware(hdsp, 1))
+		return -EIO;
+
+	spin_lock_irq(&hdsp->lock);
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		hdsp->control_register &= ~(HDSP_SPDIFProfessional | HDSP_SPDIFNonAudio | HDSP_SPDIFEmphasis);
+		hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register |= hdsp->creg_spdif_stream);
+		this_pid = hdsp->playback_pid;
+		other_pid = hdsp->capture_pid;
+	} else {
+		this_pid = hdsp->capture_pid;
+		other_pid = hdsp->playback_pid;
+	}
+
+	if ((other_pid > 0) && (this_pid != other_pid)) {
+
+		/* The other stream is open, and not by the same
+		   task as this one. Make sure that the parameters
+		   that matter are the same.
+		 */
+
+		if (params_rate(params) != hdsp->system_sample_rate) {
+			spin_unlock_irq(&hdsp->lock);
+			_snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE);
+			return -EBUSY;
+		}
+
+		if (params_period_size(params) != hdsp->period_bytes / 4) {
+			spin_unlock_irq(&hdsp->lock);
+			_snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+			return -EBUSY;
+		}
+
+		/* We're fine. */
+
+		spin_unlock_irq(&hdsp->lock);
+ 		return 0;
+
+	} else {
+		spin_unlock_irq(&hdsp->lock);
+	}
+
+	/* how to make sure that the rate matches an externally-set one ?
+	 */
+
+	spin_lock_irq(&hdsp->lock);
+	if (! hdsp->clock_source_locked) {
+		if ((err = hdsp_set_rate(hdsp, params_rate(params), 0)) < 0) {
+			spin_unlock_irq(&hdsp->lock);
+			_snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE);
+			return err;
+		}
+	}
+	spin_unlock_irq(&hdsp->lock);
+
+	if ((err = hdsp_set_interrupt_interval(hdsp, params_period_size(params))) < 0) {
+		_snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+		return err;
+	}
+
+	return 0;
+}
+
+static int snd_hdsp_channel_info(struct snd_pcm_substream *substream,
+				    struct snd_pcm_channel_info *info)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+	int mapped_channel;
+
+	if (snd_BUG_ON(info->channel >= hdsp->max_channels))
+		return -EINVAL;
+
+	if ((mapped_channel = hdsp->channel_map[info->channel]) < 0)
+		return -EINVAL;
+
+	info->offset = mapped_channel * HDSP_CHANNEL_BUFFER_BYTES;
+	info->first = 0;
+	info->step = 32;
+	return 0;
+}
+
+static int snd_hdsp_ioctl(struct snd_pcm_substream *substream,
+			     unsigned int cmd, void *arg)
+{
+	switch (cmd) {
+	case SNDRV_PCM_IOCTL1_RESET:
+		return snd_hdsp_reset(substream);
+	case SNDRV_PCM_IOCTL1_CHANNEL_INFO:
+		return snd_hdsp_channel_info(substream, arg);
+	default:
+		break;
+	}
+
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static int snd_hdsp_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *other;
+	int running;
+
+	if (hdsp_check_for_iobox (hdsp))
+		return -EIO;
+
+	if (hdsp_check_for_firmware(hdsp, 0)) /* no auto-loading in trigger */
+		return -EIO;
+
+	spin_lock(&hdsp->lock);
+	running = hdsp->running;
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		running |= 1 << substream->stream;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		running &= ~(1 << substream->stream);
+		break;
+	default:
+		snd_BUG();
+		spin_unlock(&hdsp->lock);
+		return -EINVAL;
+	}
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		other = hdsp->capture_substream;
+	else
+		other = hdsp->playback_substream;
+
+	if (other) {
+		struct snd_pcm_substream *s;
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s == other) {
+				snd_pcm_trigger_done(s, substream);
+				if (cmd == SNDRV_PCM_TRIGGER_START)
+					running |= 1 << s->stream;
+				else
+					running &= ~(1 << s->stream);
+				goto _ok;
+			}
+		}
+		if (cmd == SNDRV_PCM_TRIGGER_START) {
+			if (!(running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) &&
+			    substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+				hdsp_silence_playback(hdsp);
+		} else {
+			if (running &&
+			    substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+				hdsp_silence_playback(hdsp);
+		}
+	} else {
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+				hdsp_silence_playback(hdsp);
+	}
+ _ok:
+	snd_pcm_trigger_done(substream, substream);
+	if (!hdsp->running && running)
+		hdsp_start_audio(hdsp);
+	else if (hdsp->running && !running)
+		hdsp_stop_audio(hdsp);
+	hdsp->running = running;
+	spin_unlock(&hdsp->lock);
+
+	return 0;
+}
+
+static int snd_hdsp_prepare(struct snd_pcm_substream *substream)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+	int result = 0;
+
+	if (hdsp_check_for_iobox (hdsp))
+		return -EIO;
+
+	if (hdsp_check_for_firmware(hdsp, 1))
+		return -EIO;
+
+	spin_lock_irq(&hdsp->lock);
+	if (!hdsp->running)
+		hdsp_reset_hw_pointer(hdsp);
+	spin_unlock_irq(&hdsp->lock);
+	return result;
+}
+
+static struct snd_pcm_hardware snd_hdsp_playback_subinfo =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_NONINTERLEAVED |
+				 SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_DOUBLE),
+#ifdef SNDRV_BIG_ENDIAN
+	.formats =		SNDRV_PCM_FMTBIT_S32_BE,
+#else
+	.formats =		SNDRV_PCM_FMTBIT_S32_LE,
+#endif
+	.rates =		(SNDRV_PCM_RATE_32000 |
+				 SNDRV_PCM_RATE_44100 |
+				 SNDRV_PCM_RATE_48000 |
+				 SNDRV_PCM_RATE_64000 |
+				 SNDRV_PCM_RATE_88200 |
+				 SNDRV_PCM_RATE_96000),
+	.rate_min =		32000,
+	.rate_max =		96000,
+	.channels_min =		14,
+	.channels_max =		HDSP_MAX_CHANNELS,
+	.buffer_bytes_max =	HDSP_CHANNEL_BUFFER_BYTES * HDSP_MAX_CHANNELS,
+	.period_bytes_min =	(64 * 4) * 10,
+	.period_bytes_max =	(8192 * 4) * HDSP_MAX_CHANNELS,
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0
+};
+
+static struct snd_pcm_hardware snd_hdsp_capture_subinfo =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_NONINTERLEAVED |
+				 SNDRV_PCM_INFO_SYNC_START),
+#ifdef SNDRV_BIG_ENDIAN
+	.formats =		SNDRV_PCM_FMTBIT_S32_BE,
+#else
+	.formats =		SNDRV_PCM_FMTBIT_S32_LE,
+#endif
+	.rates =		(SNDRV_PCM_RATE_32000 |
+				 SNDRV_PCM_RATE_44100 |
+				 SNDRV_PCM_RATE_48000 |
+				 SNDRV_PCM_RATE_64000 |
+				 SNDRV_PCM_RATE_88200 |
+				 SNDRV_PCM_RATE_96000),
+	.rate_min =		32000,
+	.rate_max =		96000,
+	.channels_min =		14,
+	.channels_max =		HDSP_MAX_CHANNELS,
+	.buffer_bytes_max =	HDSP_CHANNEL_BUFFER_BYTES * HDSP_MAX_CHANNELS,
+	.period_bytes_min =	(64 * 4) * 10,
+	.period_bytes_max =	(8192 * 4) * HDSP_MAX_CHANNELS,
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0
+};
+
+static unsigned int hdsp_period_sizes[] = { 64, 128, 256, 512, 1024, 2048, 4096, 8192 };
+
+static struct snd_pcm_hw_constraint_list hdsp_hw_constraints_period_sizes = {
+	.count = ARRAY_SIZE(hdsp_period_sizes),
+	.list = hdsp_period_sizes,
+	.mask = 0
+};
+
+static unsigned int hdsp_9632_sample_rates[] = { 32000, 44100, 48000, 64000, 88200, 96000, 128000, 176400, 192000 };
+
+static struct snd_pcm_hw_constraint_list hdsp_hw_constraints_9632_sample_rates = {
+	.count = ARRAY_SIZE(hdsp_9632_sample_rates),
+	.list = hdsp_9632_sample_rates,
+	.mask = 0
+};
+
+static int snd_hdsp_hw_rule_in_channels(struct snd_pcm_hw_params *params,
+					struct snd_pcm_hw_rule *rule)
+{
+	struct hdsp *hdsp = rule->private;
+	struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	if (hdsp->io_type == H9632) {
+		unsigned int list[3];
+		list[0] = hdsp->qs_in_channels;
+		list[1] = hdsp->ds_in_channels;
+		list[2] = hdsp->ss_in_channels;
+		return snd_interval_list(c, 3, list, 0);
+	} else {
+		unsigned int list[2];
+		list[0] = hdsp->ds_in_channels;
+		list[1] = hdsp->ss_in_channels;
+		return snd_interval_list(c, 2, list, 0);
+	}
+}
+
+static int snd_hdsp_hw_rule_out_channels(struct snd_pcm_hw_params *params,
+					struct snd_pcm_hw_rule *rule)
+{
+	unsigned int list[3];
+	struct hdsp *hdsp = rule->private;
+	struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	if (hdsp->io_type == H9632) {
+		list[0] = hdsp->qs_out_channels;
+		list[1] = hdsp->ds_out_channels;
+		list[2] = hdsp->ss_out_channels;
+		return snd_interval_list(c, 3, list, 0);
+	} else {
+		list[0] = hdsp->ds_out_channels;
+		list[1] = hdsp->ss_out_channels;
+	}
+	return snd_interval_list(c, 2, list, 0);
+}
+
+static int snd_hdsp_hw_rule_in_channels_rate(struct snd_pcm_hw_params *params,
+					     struct snd_pcm_hw_rule *rule)
+{
+	struct hdsp *hdsp = rule->private;
+	struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	if (r->min > 96000 && hdsp->io_type == H9632) {
+		struct snd_interval t = {
+			.min = hdsp->qs_in_channels,
+			.max = hdsp->qs_in_channels,
+			.integer = 1,
+		};
+		return snd_interval_refine(c, &t);
+	} else if (r->min > 48000 && r->max <= 96000) {
+		struct snd_interval t = {
+			.min = hdsp->ds_in_channels,
+			.max = hdsp->ds_in_channels,
+			.integer = 1,
+		};
+		return snd_interval_refine(c, &t);
+	} else if (r->max < 64000) {
+		struct snd_interval t = {
+			.min = hdsp->ss_in_channels,
+			.max = hdsp->ss_in_channels,
+			.integer = 1,
+		};
+		return snd_interval_refine(c, &t);
+	}
+	return 0;
+}
+
+static int snd_hdsp_hw_rule_out_channels_rate(struct snd_pcm_hw_params *params,
+					     struct snd_pcm_hw_rule *rule)
+{
+	struct hdsp *hdsp = rule->private;
+	struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	if (r->min > 96000 && hdsp->io_type == H9632) {
+		struct snd_interval t = {
+			.min = hdsp->qs_out_channels,
+			.max = hdsp->qs_out_channels,
+			.integer = 1,
+		};
+		return snd_interval_refine(c, &t);
+	} else if (r->min > 48000 && r->max <= 96000) {
+		struct snd_interval t = {
+			.min = hdsp->ds_out_channels,
+			.max = hdsp->ds_out_channels,
+			.integer = 1,
+		};
+		return snd_interval_refine(c, &t);
+	} else if (r->max < 64000) {
+		struct snd_interval t = {
+			.min = hdsp->ss_out_channels,
+			.max = hdsp->ss_out_channels,
+			.integer = 1,
+		};
+		return snd_interval_refine(c, &t);
+	}
+	return 0;
+}
+
+static int snd_hdsp_hw_rule_rate_out_channels(struct snd_pcm_hw_params *params,
+					     struct snd_pcm_hw_rule *rule)
+{
+	struct hdsp *hdsp = rule->private;
+	struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	if (c->min >= hdsp->ss_out_channels) {
+		struct snd_interval t = {
+			.min = 32000,
+			.max = 48000,
+			.integer = 1,
+		};
+		return snd_interval_refine(r, &t);
+	} else if (c->max <= hdsp->qs_out_channels && hdsp->io_type == H9632) {
+		struct snd_interval t = {
+			.min = 128000,
+			.max = 192000,
+			.integer = 1,
+		};
+		return snd_interval_refine(r, &t);
+	} else if (c->max <= hdsp->ds_out_channels) {
+		struct snd_interval t = {
+			.min = 64000,
+			.max = 96000,
+			.integer = 1,
+		};
+		return snd_interval_refine(r, &t);
+	}
+	return 0;
+}
+
+static int snd_hdsp_hw_rule_rate_in_channels(struct snd_pcm_hw_params *params,
+					     struct snd_pcm_hw_rule *rule)
+{
+	struct hdsp *hdsp = rule->private;
+	struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	if (c->min >= hdsp->ss_in_channels) {
+		struct snd_interval t = {
+			.min = 32000,
+			.max = 48000,
+			.integer = 1,
+		};
+		return snd_interval_refine(r, &t);
+	} else if (c->max <= hdsp->qs_in_channels && hdsp->io_type == H9632) {
+		struct snd_interval t = {
+			.min = 128000,
+			.max = 192000,
+			.integer = 1,
+		};
+		return snd_interval_refine(r, &t);
+	} else if (c->max <= hdsp->ds_in_channels) {
+		struct snd_interval t = {
+			.min = 64000,
+			.max = 96000,
+			.integer = 1,
+		};
+		return snd_interval_refine(r, &t);
+	}
+	return 0;
+}
+
+static int snd_hdsp_playback_open(struct snd_pcm_substream *substream)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (hdsp_check_for_iobox (hdsp))
+		return -EIO;
+
+	if (hdsp_check_for_firmware(hdsp, 1))
+		return -EIO;
+
+	spin_lock_irq(&hdsp->lock);
+
+	snd_pcm_set_sync(substream);
+
+        runtime->hw = snd_hdsp_playback_subinfo;
+	runtime->dma_area = hdsp->playback_buffer;
+	runtime->dma_bytes = HDSP_DMA_AREA_BYTES;
+
+	hdsp->playback_pid = current->pid;
+	hdsp->playback_substream = substream;
+
+	spin_unlock_irq(&hdsp->lock);
+
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hdsp_hw_constraints_period_sizes);
+	if (hdsp->clock_source_locked) {
+		runtime->hw.rate_min = runtime->hw.rate_max = hdsp->system_sample_rate;
+	} else if (hdsp->io_type == H9632) {
+		runtime->hw.rate_max = 192000;
+		runtime->hw.rates = SNDRV_PCM_RATE_KNOT;
+		snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hdsp_hw_constraints_9632_sample_rates);
+	}
+	if (hdsp->io_type == H9632) {
+		runtime->hw.channels_min = hdsp->qs_out_channels;
+		runtime->hw.channels_max = hdsp->ss_out_channels;
+	}
+
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+			     snd_hdsp_hw_rule_out_channels, hdsp,
+			     SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+			     snd_hdsp_hw_rule_out_channels_rate, hdsp,
+			     SNDRV_PCM_HW_PARAM_RATE, -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+			     snd_hdsp_hw_rule_rate_out_channels, hdsp,
+			     SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+
+	hdsp->creg_spdif_stream = hdsp->creg_spdif;
+	hdsp->spdif_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(hdsp->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &hdsp->spdif_ctl->id);
+	return 0;
+}
+
+static int snd_hdsp_playback_release(struct snd_pcm_substream *substream)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&hdsp->lock);
+
+	hdsp->playback_pid = -1;
+	hdsp->playback_substream = NULL;
+
+	spin_unlock_irq(&hdsp->lock);
+
+	hdsp->spdif_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(hdsp->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &hdsp->spdif_ctl->id);
+	return 0;
+}
+
+
+static int snd_hdsp_capture_open(struct snd_pcm_substream *substream)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (hdsp_check_for_iobox (hdsp))
+		return -EIO;
+
+	if (hdsp_check_for_firmware(hdsp, 1))
+		return -EIO;
+
+	spin_lock_irq(&hdsp->lock);
+
+	snd_pcm_set_sync(substream);
+
+	runtime->hw = snd_hdsp_capture_subinfo;
+	runtime->dma_area = hdsp->capture_buffer;
+	runtime->dma_bytes = HDSP_DMA_AREA_BYTES;
+
+	hdsp->capture_pid = current->pid;
+	hdsp->capture_substream = substream;
+
+	spin_unlock_irq(&hdsp->lock);
+
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hdsp_hw_constraints_period_sizes);
+	if (hdsp->io_type == H9632) {
+		runtime->hw.channels_min = hdsp->qs_in_channels;
+		runtime->hw.channels_max = hdsp->ss_in_channels;
+		runtime->hw.rate_max = 192000;
+		runtime->hw.rates = SNDRV_PCM_RATE_KNOT;
+		snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hdsp_hw_constraints_9632_sample_rates);
+	}
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+			     snd_hdsp_hw_rule_in_channels, hdsp,
+			     SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+			     snd_hdsp_hw_rule_in_channels_rate, hdsp,
+			     SNDRV_PCM_HW_PARAM_RATE, -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+			     snd_hdsp_hw_rule_rate_in_channels, hdsp,
+			     SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	return 0;
+}
+
+static int snd_hdsp_capture_release(struct snd_pcm_substream *substream)
+{
+	struct hdsp *hdsp = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&hdsp->lock);
+
+	hdsp->capture_pid = -1;
+	hdsp->capture_substream = NULL;
+
+	spin_unlock_irq(&hdsp->lock);
+	return 0;
+}
+
+/* helper functions for copying meter values */
+static inline int copy_u32_le(void __user *dest, void __iomem *src)
+{
+	u32 val = readl(src);
+	return copy_to_user(dest, &val, 4);
+}
+
+static inline int copy_u64_le(void __user *dest, void __iomem *src_low, void __iomem *src_high)
+{
+	u32 rms_low, rms_high;
+	u64 rms;
+	rms_low = readl(src_low);
+	rms_high = readl(src_high);
+	rms = ((u64)rms_high << 32) | rms_low;
+	return copy_to_user(dest, &rms, 8);
+}
+
+static inline int copy_u48_le(void __user *dest, void __iomem *src_low, void __iomem *src_high)
+{
+	u32 rms_low, rms_high;
+	u64 rms;
+	rms_low = readl(src_low) & 0xffffff00;
+	rms_high = readl(src_high) & 0xffffff00;
+	rms = ((u64)rms_high << 32) | rms_low;
+	return copy_to_user(dest, &rms, 8);
+}
+
+static int hdsp_9652_get_peak(struct hdsp *hdsp, struct hdsp_peak_rms __user *peak_rms)
+{
+	int doublespeed = 0;
+	int i, j, channels, ofs;
+
+	if (hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DoubleSpeedStatus)
+		doublespeed = 1;
+	channels = doublespeed ? 14 : 26;
+	for (i = 0, j = 0; i < 26; ++i) {
+		if (doublespeed && (i & 4))
+			continue;
+		ofs = HDSP_9652_peakBase - j * 4;
+		if (copy_u32_le(&peak_rms->input_peaks[i], hdsp->iobase + ofs))
+			return -EFAULT;
+		ofs -= channels * 4;
+		if (copy_u32_le(&peak_rms->playback_peaks[i], hdsp->iobase + ofs))
+			return -EFAULT;
+		ofs -= channels * 4;
+		if (copy_u32_le(&peak_rms->output_peaks[i], hdsp->iobase + ofs))
+			return -EFAULT;
+		ofs = HDSP_9652_rmsBase + j * 8;
+		if (copy_u48_le(&peak_rms->input_rms[i], hdsp->iobase + ofs,
+				hdsp->iobase + ofs + 4))
+			return -EFAULT;
+		ofs += channels * 8;
+		if (copy_u48_le(&peak_rms->playback_rms[i], hdsp->iobase + ofs,
+				hdsp->iobase + ofs + 4))
+			return -EFAULT;
+		ofs += channels * 8;
+		if (copy_u48_le(&peak_rms->output_rms[i], hdsp->iobase + ofs,
+				hdsp->iobase + ofs + 4))
+			return -EFAULT;
+		j++;
+	}
+	return 0;
+}
+
+static int hdsp_9632_get_peak(struct hdsp *hdsp, struct hdsp_peak_rms __user *peak_rms)
+{
+	int i, j;
+	struct hdsp_9632_meters __iomem *m;
+	int doublespeed = 0;
+
+	if (hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DoubleSpeedStatus)
+		doublespeed = 1;
+	m = (struct hdsp_9632_meters __iomem *)(hdsp->iobase+HDSP_9632_metersBase);
+	for (i = 0, j = 0; i < 16; ++i, ++j) {
+		if (copy_u32_le(&peak_rms->input_peaks[i], &m->input_peak[j]))
+			return -EFAULT;
+		if (copy_u32_le(&peak_rms->playback_peaks[i], &m->playback_peak[j]))
+			return -EFAULT;
+		if (copy_u32_le(&peak_rms->output_peaks[i], &m->output_peak[j]))
+			return -EFAULT;
+		if (copy_u64_le(&peak_rms->input_rms[i], &m->input_rms_low[j],
+				&m->input_rms_high[j]))
+			return -EFAULT;
+		if (copy_u64_le(&peak_rms->playback_rms[i], &m->playback_rms_low[j],
+				&m->playback_rms_high[j]))
+			return -EFAULT;
+		if (copy_u64_le(&peak_rms->output_rms[i], &m->output_rms_low[j],
+				&m->output_rms_high[j]))
+			return -EFAULT;
+		if (doublespeed && i == 3) i += 4;
+	}
+	return 0;
+}
+
+static int hdsp_get_peak(struct hdsp *hdsp, struct hdsp_peak_rms __user *peak_rms)
+{
+	int i;
+
+	for (i = 0; i < 26; i++) {
+		if (copy_u32_le(&peak_rms->playback_peaks[i],
+				hdsp->iobase + HDSP_playbackPeakLevel + i * 4))
+			return -EFAULT;
+		if (copy_u32_le(&peak_rms->input_peaks[i],
+				hdsp->iobase + HDSP_inputPeakLevel + i * 4))
+			return -EFAULT;
+	}
+	for (i = 0; i < 28; i++) {
+		if (copy_u32_le(&peak_rms->output_peaks[i],
+				hdsp->iobase + HDSP_outputPeakLevel + i * 4))
+			return -EFAULT;
+	}
+	for (i = 0; i < 26; ++i) {
+		if (copy_u64_le(&peak_rms->playback_rms[i],
+				hdsp->iobase + HDSP_playbackRmsLevel + i * 8 + 4,
+				hdsp->iobase + HDSP_playbackRmsLevel + i * 8))
+			return -EFAULT;
+		if (copy_u64_le(&peak_rms->input_rms[i],
+				hdsp->iobase + HDSP_inputRmsLevel + i * 8 + 4,
+				hdsp->iobase + HDSP_inputRmsLevel + i * 8))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+static int snd_hdsp_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct hdsp *hdsp = hw->private_data;
+	void __user *argp = (void __user *)arg;
+	int err;
+
+	switch (cmd) {
+	case SNDRV_HDSP_IOCTL_GET_PEAK_RMS: {
+		struct hdsp_peak_rms __user *peak_rms = (struct hdsp_peak_rms __user *)arg;
+
+		err = hdsp_check_for_iobox(hdsp);
+		if (err < 0)
+			return err;
+
+		err = hdsp_check_for_firmware(hdsp, 1);
+		if (err < 0)
+			return err;
+
+		if (!(hdsp->state & HDSP_FirmwareLoaded)) {
+			snd_printk(KERN_ERR "Hammerfall-DSP: firmware needs to be uploaded to the card.\n");
+			return -EINVAL;
+		}
+
+		switch (hdsp->io_type) {
+		case H9652:
+			return hdsp_9652_get_peak(hdsp, peak_rms);
+		case H9632:
+			return hdsp_9632_get_peak(hdsp, peak_rms);
+		default:
+			return hdsp_get_peak(hdsp, peak_rms);
+		}
+	}
+	case SNDRV_HDSP_IOCTL_GET_CONFIG_INFO: {
+		struct hdsp_config_info info;
+		unsigned long flags;
+		int i;
+
+		err = hdsp_check_for_iobox(hdsp);
+		if (err < 0)
+			return err;
+
+		err = hdsp_check_for_firmware(hdsp, 1);
+		if (err < 0)
+			return err;
+
+		memset(&info, 0, sizeof(info));
+		spin_lock_irqsave(&hdsp->lock, flags);
+		info.pref_sync_ref = (unsigned char)hdsp_pref_sync_ref(hdsp);
+		info.wordclock_sync_check = (unsigned char)hdsp_wc_sync_check(hdsp);
+		if (hdsp->io_type != H9632)
+		    info.adatsync_sync_check = (unsigned char)hdsp_adatsync_sync_check(hdsp);
+		info.spdif_sync_check = (unsigned char)hdsp_spdif_sync_check(hdsp);
+		for (i = 0; i < ((hdsp->io_type != Multiface && hdsp->io_type != H9632) ? 3 : 1); ++i)
+			info.adat_sync_check[i] = (unsigned char)hdsp_adat_sync_check(hdsp, i);
+		info.spdif_in = (unsigned char)hdsp_spdif_in(hdsp);
+		info.spdif_out = (unsigned char)hdsp_spdif_out(hdsp);
+		info.spdif_professional = (unsigned char)hdsp_spdif_professional(hdsp);
+		info.spdif_emphasis = (unsigned char)hdsp_spdif_emphasis(hdsp);
+		info.spdif_nonaudio = (unsigned char)hdsp_spdif_nonaudio(hdsp);
+		info.spdif_sample_rate = hdsp_spdif_sample_rate(hdsp);
+		info.system_sample_rate = hdsp->system_sample_rate;
+		info.autosync_sample_rate = hdsp_external_sample_rate(hdsp);
+		info.system_clock_mode = (unsigned char)hdsp_system_clock_mode(hdsp);
+		info.clock_source = (unsigned char)hdsp_clock_source(hdsp);
+		info.autosync_ref = (unsigned char)hdsp_autosync_ref(hdsp);
+		info.line_out = (unsigned char)hdsp_line_out(hdsp);
+		if (hdsp->io_type == H9632) {
+			info.da_gain = (unsigned char)hdsp_da_gain(hdsp);
+			info.ad_gain = (unsigned char)hdsp_ad_gain(hdsp);
+			info.phone_gain = (unsigned char)hdsp_phone_gain(hdsp);
+			info.xlr_breakout_cable = (unsigned char)hdsp_xlr_breakout_cable(hdsp);
+
+		}
+		if (hdsp->io_type == H9632 || hdsp->io_type == H9652)
+			info.analog_extension_board = (unsigned char)hdsp_aeb(hdsp);
+		spin_unlock_irqrestore(&hdsp->lock, flags);
+		if (copy_to_user(argp, &info, sizeof(info)))
+			return -EFAULT;
+		break;
+	}
+	case SNDRV_HDSP_IOCTL_GET_9632_AEB: {
+		struct hdsp_9632_aeb h9632_aeb;
+
+		if (hdsp->io_type != H9632) return -EINVAL;
+		h9632_aeb.aebi = hdsp->ss_in_channels - H9632_SS_CHANNELS;
+		h9632_aeb.aebo = hdsp->ss_out_channels - H9632_SS_CHANNELS;
+		if (copy_to_user(argp, &h9632_aeb, sizeof(h9632_aeb)))
+			return -EFAULT;
+		break;
+	}
+	case SNDRV_HDSP_IOCTL_GET_VERSION: {
+		struct hdsp_version hdsp_version;
+		int err;
+
+		if (hdsp->io_type == H9652 || hdsp->io_type == H9632) return -EINVAL;
+		if (hdsp->io_type == Undefined) {
+			if ((err = hdsp_get_iobox_version(hdsp)) < 0)
+				return err;
+		}
+		hdsp_version.io_type = hdsp->io_type;
+		hdsp_version.firmware_rev = hdsp->firmware_rev;
+		if ((err = copy_to_user(argp, &hdsp_version, sizeof(hdsp_version))))
+		    	return -EFAULT;
+		break;
+	}
+	case SNDRV_HDSP_IOCTL_UPLOAD_FIRMWARE: {
+		struct hdsp_firmware __user *firmware;
+		u32 __user *firmware_data;
+		int err;
+
+		if (hdsp->io_type == H9652 || hdsp->io_type == H9632) return -EINVAL;
+		/* SNDRV_HDSP_IOCTL_GET_VERSION must have been called */
+		if (hdsp->io_type == Undefined) return -EINVAL;
+
+		if (hdsp->state & (HDSP_FirmwareCached | HDSP_FirmwareLoaded))
+			return -EBUSY;
+
+		snd_printk(KERN_INFO "Hammerfall-DSP: initializing firmware upload\n");
+		firmware = (struct hdsp_firmware __user *)argp;
+
+		if (get_user(firmware_data, &firmware->firmware_data))
+			return -EFAULT;
+
+		if (hdsp_check_for_iobox (hdsp))
+			return -EIO;
+
+		if (copy_from_user(hdsp->firmware_cache, firmware_data, sizeof(hdsp->firmware_cache)) != 0)
+			return -EFAULT;
+
+		hdsp->state |= HDSP_FirmwareCached;
+
+		if ((err = snd_hdsp_load_firmware_from_cache(hdsp)) < 0)
+			return err;
+
+		if (!(hdsp->state & HDSP_InitializationComplete)) {
+			if ((err = snd_hdsp_enable_io(hdsp)) < 0)
+				return err;
+
+			snd_hdsp_initialize_channels(hdsp);
+			snd_hdsp_initialize_midi_flush(hdsp);
+
+			if ((err = snd_hdsp_create_alsa_devices(hdsp->card, hdsp)) < 0) {
+				snd_printk(KERN_ERR "Hammerfall-DSP: error creating alsa devices\n");
+				return err;
+			}
+		}
+		break;
+	}
+	case SNDRV_HDSP_IOCTL_GET_MIXER: {
+		struct hdsp_mixer __user *mixer = (struct hdsp_mixer __user *)argp;
+		if (copy_to_user(mixer->matrix, hdsp->mixer_matrix, sizeof(unsigned short)*HDSP_MATRIX_MIXER_SIZE))
+			return -EFAULT;
+		break;
+	}
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct snd_pcm_ops snd_hdsp_playback_ops = {
+	.open =		snd_hdsp_playback_open,
+	.close =	snd_hdsp_playback_release,
+	.ioctl =	snd_hdsp_ioctl,
+	.hw_params =	snd_hdsp_hw_params,
+	.prepare =	snd_hdsp_prepare,
+	.trigger =	snd_hdsp_trigger,
+	.pointer =	snd_hdsp_hw_pointer,
+	.copy =		snd_hdsp_playback_copy,
+	.silence =	snd_hdsp_hw_silence,
+};
+
+static struct snd_pcm_ops snd_hdsp_capture_ops = {
+	.open =		snd_hdsp_capture_open,
+	.close =	snd_hdsp_capture_release,
+	.ioctl =	snd_hdsp_ioctl,
+	.hw_params =	snd_hdsp_hw_params,
+	.prepare =	snd_hdsp_prepare,
+	.trigger =	snd_hdsp_trigger,
+	.pointer =	snd_hdsp_hw_pointer,
+	.copy =		snd_hdsp_capture_copy,
+};
+
+static int snd_hdsp_create_hwdep(struct snd_card *card, struct hdsp *hdsp)
+{
+	struct snd_hwdep *hw;
+	int err;
+
+	if ((err = snd_hwdep_new(card, "HDSP hwdep", 0, &hw)) < 0)
+		return err;
+
+	hdsp->hwdep = hw;
+	hw->private_data = hdsp;
+	strcpy(hw->name, "HDSP hwdep interface");
+
+	hw->ops.ioctl = snd_hdsp_hwdep_ioctl;
+
+	return 0;
+}
+
+static int snd_hdsp_create_pcm(struct snd_card *card, struct hdsp *hdsp)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(card, hdsp->card_name, 0, 1, 1, &pcm)) < 0)
+		return err;
+
+	hdsp->pcm = pcm;
+	pcm->private_data = hdsp;
+	strcpy(pcm->name, hdsp->card_name);
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_hdsp_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_hdsp_capture_ops);
+
+	pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+
+	return 0;
+}
+
+static void snd_hdsp_9652_enable_mixer (struct hdsp *hdsp)
+{
+        hdsp->control2_register |= HDSP_9652_ENABLE_MIXER;
+	hdsp_write (hdsp, HDSP_control2Reg, hdsp->control2_register);
+}
+
+static int snd_hdsp_enable_io (struct hdsp *hdsp)
+{
+	int i;
+
+	if (hdsp_fifo_wait (hdsp, 0, 100)) {
+		snd_printk(KERN_ERR "Hammerfall-DSP: enable_io fifo_wait failed\n");
+		return -EIO;
+	}
+
+	for (i = 0; i < hdsp->max_channels; ++i) {
+		hdsp_write (hdsp, HDSP_inputEnable + (4 * i), 1);
+		hdsp_write (hdsp, HDSP_outputEnable + (4 * i), 1);
+	}
+
+	return 0;
+}
+
+static void snd_hdsp_initialize_channels(struct hdsp *hdsp)
+{
+	int status, aebi_channels, aebo_channels;
+
+	switch (hdsp->io_type) {
+	case Digiface:
+		hdsp->card_name = "RME Hammerfall DSP + Digiface";
+		hdsp->ss_in_channels = hdsp->ss_out_channels = DIGIFACE_SS_CHANNELS;
+		hdsp->ds_in_channels = hdsp->ds_out_channels = DIGIFACE_DS_CHANNELS;
+		break;
+
+	case H9652:
+		hdsp->card_name = "RME Hammerfall HDSP 9652";
+		hdsp->ss_in_channels = hdsp->ss_out_channels = H9652_SS_CHANNELS;
+		hdsp->ds_in_channels = hdsp->ds_out_channels = H9652_DS_CHANNELS;
+		break;
+
+	case H9632:
+		status = hdsp_read(hdsp, HDSP_statusRegister);
+		/* HDSP_AEBx bits are low when AEB are connected */
+		aebi_channels = (status & HDSP_AEBI) ? 0 : 4;
+		aebo_channels = (status & HDSP_AEBO) ? 0 : 4;
+		hdsp->card_name = "RME Hammerfall HDSP 9632";
+		hdsp->ss_in_channels = H9632_SS_CHANNELS+aebi_channels;
+		hdsp->ds_in_channels = H9632_DS_CHANNELS+aebi_channels;
+		hdsp->qs_in_channels = H9632_QS_CHANNELS+aebi_channels;
+		hdsp->ss_out_channels = H9632_SS_CHANNELS+aebo_channels;
+		hdsp->ds_out_channels = H9632_DS_CHANNELS+aebo_channels;
+		hdsp->qs_out_channels = H9632_QS_CHANNELS+aebo_channels;
+		break;
+
+	case Multiface:
+		hdsp->card_name = "RME Hammerfall DSP + Multiface";
+		hdsp->ss_in_channels = hdsp->ss_out_channels = MULTIFACE_SS_CHANNELS;
+		hdsp->ds_in_channels = hdsp->ds_out_channels = MULTIFACE_DS_CHANNELS;
+		break;
+
+	default:
+ 		/* should never get here */
+		break;
+	}
+}
+
+static void snd_hdsp_initialize_midi_flush (struct hdsp *hdsp)
+{
+	snd_hdsp_flush_midi_input (hdsp, 0);
+	snd_hdsp_flush_midi_input (hdsp, 1);
+}
+
+static int snd_hdsp_create_alsa_devices(struct snd_card *card, struct hdsp *hdsp)
+{
+	int err;
+
+	if ((err = snd_hdsp_create_pcm(card, hdsp)) < 0) {
+		snd_printk(KERN_ERR "Hammerfall-DSP: Error creating pcm interface\n");
+		return err;
+	}
+
+
+	if ((err = snd_hdsp_create_midi(card, hdsp, 0)) < 0) {
+		snd_printk(KERN_ERR "Hammerfall-DSP: Error creating first midi interface\n");
+		return err;
+	}
+
+	if (hdsp->io_type == Digiface || hdsp->io_type == H9652) {
+		if ((err = snd_hdsp_create_midi(card, hdsp, 1)) < 0) {
+			snd_printk(KERN_ERR "Hammerfall-DSP: Error creating second midi interface\n");
+			return err;
+		}
+	}
+
+	if ((err = snd_hdsp_create_controls(card, hdsp)) < 0) {
+		snd_printk(KERN_ERR "Hammerfall-DSP: Error creating ctl interface\n");
+		return err;
+	}
+
+	snd_hdsp_proc_init(hdsp);
+
+	hdsp->system_sample_rate = -1;
+	hdsp->playback_pid = -1;
+	hdsp->capture_pid = -1;
+	hdsp->capture_substream = NULL;
+	hdsp->playback_substream = NULL;
+
+	if ((err = snd_hdsp_set_defaults(hdsp)) < 0) {
+		snd_printk(KERN_ERR "Hammerfall-DSP: Error setting default values\n");
+		return err;
+	}
+
+	if (!(hdsp->state & HDSP_InitializationComplete)) {
+		strcpy(card->shortname, "Hammerfall DSP");
+		sprintf(card->longname, "%s at 0x%lx, irq %d", hdsp->card_name,
+			hdsp->port, hdsp->irq);
+
+		if ((err = snd_card_register(card)) < 0) {
+			snd_printk(KERN_ERR "Hammerfall-DSP: error registering card\n");
+			return err;
+		}
+		hdsp->state |= HDSP_InitializationComplete;
+	}
+
+	return 0;
+}
+
+#ifdef HDSP_FW_LOADER
+/* load firmware via hotplug fw loader */
+static int hdsp_request_fw_loader(struct hdsp *hdsp)
+{
+	const char *fwfile;
+	const struct firmware *fw;
+	int err;
+
+	if (hdsp->io_type == H9652 || hdsp->io_type == H9632)
+		return 0;
+	if (hdsp->io_type == Undefined) {
+		if ((err = hdsp_get_iobox_version(hdsp)) < 0)
+			return err;
+		if (hdsp->io_type == H9652 || hdsp->io_type == H9632)
+			return 0;
+	}
+
+	/* caution: max length of firmware filename is 30! */
+	switch (hdsp->io_type) {
+	case Multiface:
+		if (hdsp->firmware_rev == 0xa)
+			fwfile = "multiface_firmware.bin";
+		else
+			fwfile = "multiface_firmware_rev11.bin";
+		break;
+	case Digiface:
+		if (hdsp->firmware_rev == 0xa)
+			fwfile = "digiface_firmware.bin";
+		else
+			fwfile = "digiface_firmware_rev11.bin";
+		break;
+	default:
+		snd_printk(KERN_ERR "Hammerfall-DSP: invalid io_type %d\n", hdsp->io_type);
+		return -EINVAL;
+	}
+
+	if (request_firmware(&fw, fwfile, &hdsp->pci->dev)) {
+		snd_printk(KERN_ERR "Hammerfall-DSP: cannot load firmware %s\n", fwfile);
+		return -ENOENT;
+	}
+	if (fw->size < sizeof(hdsp->firmware_cache)) {
+		snd_printk(KERN_ERR "Hammerfall-DSP: too short firmware size %d (expected %d)\n",
+			   (int)fw->size, (int)sizeof(hdsp->firmware_cache));
+		release_firmware(fw);
+		return -EINVAL;
+	}
+
+	memcpy(hdsp->firmware_cache, fw->data, sizeof(hdsp->firmware_cache));
+
+	release_firmware(fw);
+
+	hdsp->state |= HDSP_FirmwareCached;
+
+	if ((err = snd_hdsp_load_firmware_from_cache(hdsp)) < 0)
+		return err;
+
+	if (!(hdsp->state & HDSP_InitializationComplete)) {
+		if ((err = snd_hdsp_enable_io(hdsp)) < 0)
+			return err;
+
+		if ((err = snd_hdsp_create_hwdep(hdsp->card, hdsp)) < 0) {
+			snd_printk(KERN_ERR "Hammerfall-DSP: error creating hwdep device\n");
+			return err;
+		}
+		snd_hdsp_initialize_channels(hdsp);
+		snd_hdsp_initialize_midi_flush(hdsp);
+		if ((err = snd_hdsp_create_alsa_devices(hdsp->card, hdsp)) < 0) {
+			snd_printk(KERN_ERR "Hammerfall-DSP: error creating alsa devices\n");
+			return err;
+		}
+	}
+	return 0;
+}
+#endif
+
+static int __devinit snd_hdsp_create(struct snd_card *card,
+				     struct hdsp *hdsp)
+{
+	struct pci_dev *pci = hdsp->pci;
+	int err;
+	int is_9652 = 0;
+	int is_9632 = 0;
+
+	hdsp->irq = -1;
+	hdsp->state = 0;
+	hdsp->midi[0].rmidi = NULL;
+	hdsp->midi[1].rmidi = NULL;
+	hdsp->midi[0].input = NULL;
+	hdsp->midi[1].input = NULL;
+	hdsp->midi[0].output = NULL;
+	hdsp->midi[1].output = NULL;
+	hdsp->midi[0].pending = 0;
+	hdsp->midi[1].pending = 0;
+	spin_lock_init(&hdsp->midi[0].lock);
+	spin_lock_init(&hdsp->midi[1].lock);
+	hdsp->iobase = NULL;
+	hdsp->control_register = 0;
+	hdsp->control2_register = 0;
+	hdsp->io_type = Undefined;
+	hdsp->max_channels = 26;
+
+	hdsp->card = card;
+
+	spin_lock_init(&hdsp->lock);
+
+	tasklet_init(&hdsp->midi_tasklet, hdsp_midi_tasklet, (unsigned long)hdsp);
+
+	pci_read_config_word(hdsp->pci, PCI_CLASS_REVISION, &hdsp->firmware_rev);
+	hdsp->firmware_rev &= 0xff;
+
+	/* From Martin Bjoernsen :
+	    "It is important that the card's latency timer register in
+	    the PCI configuration space is set to a value much larger
+	    than 0 by the computer's BIOS or the driver.
+	    The windows driver always sets this 8 bit register [...]
+	    to its maximum 255 to avoid problems with some computers."
+	*/
+	pci_write_config_byte(hdsp->pci, PCI_LATENCY_TIMER, 0xFF);
+
+	strcpy(card->driver, "H-DSP");
+	strcpy(card->mixername, "Xilinx FPGA");
+
+	if (hdsp->firmware_rev < 0xa)
+		return -ENODEV;
+	else if (hdsp->firmware_rev < 0x64)
+		hdsp->card_name = "RME Hammerfall DSP";
+	else if (hdsp->firmware_rev < 0x96) {
+		hdsp->card_name = "RME HDSP 9652";
+		is_9652 = 1;
+	} else {
+		hdsp->card_name = "RME HDSP 9632";
+		hdsp->max_channels = 16;
+		is_9632 = 1;
+	}
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	pci_set_master(hdsp->pci);
+
+	if ((err = pci_request_regions(pci, "hdsp")) < 0)
+		return err;
+	hdsp->port = pci_resource_start(pci, 0);
+	if ((hdsp->iobase = ioremap_nocache(hdsp->port, HDSP_IO_EXTENT)) == NULL) {
+		snd_printk(KERN_ERR "Hammerfall-DSP: unable to remap region 0x%lx-0x%lx\n", hdsp->port, hdsp->port + HDSP_IO_EXTENT - 1);
+		return -EBUSY;
+	}
+
+	if (request_irq(pci->irq, snd_hdsp_interrupt, IRQF_SHARED,
+			"hdsp", hdsp)) {
+		snd_printk(KERN_ERR "Hammerfall-DSP: unable to use IRQ %d\n", pci->irq);
+		return -EBUSY;
+	}
+
+	hdsp->irq = pci->irq;
+	hdsp->precise_ptr = 0;
+	hdsp->use_midi_tasklet = 1;
+	hdsp->dds_value = 0;
+
+	if ((err = snd_hdsp_initialize_memory(hdsp)) < 0)
+		return err;
+
+	if (!is_9652 && !is_9632) {
+		/* we wait a maximum of 10 seconds to let freshly
+		 * inserted cardbus cards do their hardware init */
+		err = hdsp_wait_for_iobox(hdsp, 1000, 10);
+
+		if (err < 0)
+			return err;
+
+		if ((hdsp_read (hdsp, HDSP_statusRegister) & HDSP_DllError) != 0) {
+#ifdef HDSP_FW_LOADER
+			if ((err = hdsp_request_fw_loader(hdsp)) < 0)
+				/* we don't fail as this can happen
+				   if userspace is not ready for
+				   firmware upload
+				*/
+				snd_printk(KERN_ERR "Hammerfall-DSP: couldn't get firmware from userspace. try using hdsploader\n");
+			else
+				/* init is complete, we return */
+				return 0;
+#endif
+			/* we defer initialization */
+			snd_printk(KERN_INFO "Hammerfall-DSP: card initialization pending : waiting for firmware\n");
+			if ((err = snd_hdsp_create_hwdep(card, hdsp)) < 0)
+				return err;
+			return 0;
+		} else {
+			snd_printk(KERN_INFO "Hammerfall-DSP: Firmware already present, initializing card.\n");
+			if (hdsp_read(hdsp, HDSP_status2Register) & HDSP_version1)
+				hdsp->io_type = Multiface;
+			else
+				hdsp->io_type = Digiface;
+		}
+	}
+
+	if ((err = snd_hdsp_enable_io(hdsp)) != 0)
+		return err;
+
+	if (is_9652)
+	        hdsp->io_type = H9652;
+
+	if (is_9632)
+		hdsp->io_type = H9632;
+
+	if ((err = snd_hdsp_create_hwdep(card, hdsp)) < 0)
+		return err;
+
+	snd_hdsp_initialize_channels(hdsp);
+	snd_hdsp_initialize_midi_flush(hdsp);
+
+	hdsp->state |= HDSP_FirmwareLoaded;
+
+	if ((err = snd_hdsp_create_alsa_devices(card, hdsp)) < 0)
+		return err;
+
+	return 0;
+}
+
+static int snd_hdsp_free(struct hdsp *hdsp)
+{
+	if (hdsp->port) {
+		/* stop the audio, and cancel all interrupts */
+		tasklet_kill(&hdsp->midi_tasklet);
+		hdsp->control_register &= ~(HDSP_Start|HDSP_AudioInterruptEnable|HDSP_Midi0InterruptEnable|HDSP_Midi1InterruptEnable);
+		hdsp_write (hdsp, HDSP_controlRegister, hdsp->control_register);
+	}
+
+	if (hdsp->irq >= 0)
+		free_irq(hdsp->irq, (void *)hdsp);
+
+	snd_hdsp_free_buffers(hdsp);
+
+	if (hdsp->iobase)
+		iounmap(hdsp->iobase);
+
+	if (hdsp->port)
+		pci_release_regions(hdsp->pci);
+
+	pci_disable_device(hdsp->pci);
+	return 0;
+}
+
+static void snd_hdsp_card_free(struct snd_card *card)
+{
+	struct hdsp *hdsp = card->private_data;
+
+	if (hdsp)
+		snd_hdsp_free(hdsp);
+}
+
+static int __devinit snd_hdsp_probe(struct pci_dev *pci,
+				    const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct hdsp *hdsp;
+	struct snd_card *card;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct hdsp), &card);
+	if (err < 0)
+		return err;
+
+	hdsp = card->private_data;
+	card->private_free = snd_hdsp_card_free;
+	hdsp->dev = dev;
+	hdsp->pci = pci;
+	snd_card_set_dev(card, &pci->dev);
+
+	if ((err = snd_hdsp_create(card, hdsp)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->shortname, "Hammerfall DSP");
+	sprintf(card->longname, "%s at 0x%lx, irq %d", hdsp->card_name,
+		hdsp->port, hdsp->irq);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_hdsp_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name =     "RME Hammerfall DSP",
+	.id_table = snd_hdsp_ids,
+	.probe =    snd_hdsp_probe,
+	.remove = __devexit_p(snd_hdsp_remove),
+};
+
+static int __init alsa_card_hdsp_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_hdsp_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_hdsp_init)
+module_exit(alsa_card_hdsp_exit)
diff --git a/linux/sound/pci/rme9652/hdspm.c b/linux/sound/pci/rme9652/hdspm.c
new file mode 100644
index 0000000..0c98ef9
--- /dev/null
+++ b/linux/sound/pci/rme9652/hdspm.c
@@ -0,0 +1,4558 @@
+/*
+ *   ALSA driver for RME Hammerfall DSP MADI audio interface(s)
+ *
+ *      Copyright (c) 2003 Winfried Ritsch (IEM)
+ *      code based on hdsp.c   Paul Davis
+ *                             Marcus Andersson
+ *                             Thomas Charbonnel
+ *      Modified 2006-06-01 for AES32 support by Remy Bruno
+ *                                               <remy.bruno@trinnov.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/math64.h>
+#include <asm/io.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/asoundef.h>
+#include <sound/rawmidi.h>
+#include <sound/hwdep.h>
+#include <sound/initval.h>
+
+#include <sound/hdspm.h>
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	  /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	  /* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */
+
+/* Disable precise pointer at start */
+static int precise_ptr[SNDRV_CARDS];
+
+/* Send all playback to line outs */
+static int line_outs_monitor[SNDRV_CARDS];
+
+/* Enable Analog Outs on Channel 63/64 by default */
+static int enable_monitor[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for RME HDSPM interface.");
+
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for RME HDSPM interface.");
+
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable/disable specific HDSPM soundcards.");
+
+module_param_array(precise_ptr, bool, NULL, 0444);
+MODULE_PARM_DESC(precise_ptr, "Enable or disable precise pointer.");
+
+module_param_array(line_outs_monitor, bool, NULL, 0444);
+MODULE_PARM_DESC(line_outs_monitor,
+		 "Send playback streams to analog outs by default.");
+
+module_param_array(enable_monitor, bool, NULL, 0444);
+MODULE_PARM_DESC(enable_monitor,
+		 "Enable Analog Out on Channel 63/64 by default.");
+
+MODULE_AUTHOR
+      ("Winfried Ritsch <ritsch_AT_iem.at>, "
+       "Paul Davis <paul@linuxaudiosystems.com>, "
+       "Marcus Andersson, Thomas Charbonnel <thomas@undata.org>, "
+       "Remy Bruno <remy.bruno@trinnov.com>");
+MODULE_DESCRIPTION("RME HDSPM");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{RME HDSPM-MADI}}");
+
+/* --- Write registers. --- 
+  These are defined as byte-offsets from the iobase value.  */
+
+#define HDSPM_controlRegister	     64
+#define HDSPM_interruptConfirmation  96
+#define HDSPM_control2Reg	     256  /* not in specs ???????? */
+#define HDSPM_freqReg                256  /* for AES32 */
+#define HDSPM_midiDataOut0  	     352  /* just believe in old code */
+#define HDSPM_midiDataOut1  	     356
+#define HDSPM_eeprom_wr		     384  /* for AES32 */
+
+/* DMA enable for 64 channels, only Bit 0 is relevant */
+#define HDSPM_outputEnableBase       512  /* 512-767  input  DMA */ 
+#define HDSPM_inputEnableBase        768  /* 768-1023 output DMA */
+
+/* 16 page addresses for each of the 64 channels DMA buffer in and out 
+   (each 64k=16*4k) Buffer must be 4k aligned (which is default i386 ????) */
+#define HDSPM_pageAddressBufferOut       8192
+#define HDSPM_pageAddressBufferIn        (HDSPM_pageAddressBufferOut+64*16*4)
+
+#define HDSPM_MADI_mixerBase    32768	/* 32768-65535 for 2x64x64 Fader */
+
+#define HDSPM_MATRIX_MIXER_SIZE  8192	/* = 2*64*64 * 4 Byte => 32kB */
+
+/* --- Read registers. ---
+   These are defined as byte-offsets from the iobase value */
+#define HDSPM_statusRegister    0
+/*#define HDSPM_statusRegister2  96 */
+/* after RME Windows driver sources, status2 is 4-byte word # 48 = word at
+ * offset 192, for AES32 *and* MADI
+ * => need to check that offset 192 is working on MADI */
+#define HDSPM_statusRegister2  192
+#define HDSPM_timecodeRegister 128
+
+#define HDSPM_midiDataIn0     360
+#define HDSPM_midiDataIn1     364
+
+/* status is data bytes in MIDI-FIFO (0-128) */
+#define HDSPM_midiStatusOut0  384	
+#define HDSPM_midiStatusOut1  388	
+#define HDSPM_midiStatusIn0   392	
+#define HDSPM_midiStatusIn1   396	
+
+
+/* the meters are regular i/o-mapped registers, but offset
+   considerably from the rest. the peak registers are reset
+   when read; the least-significant 4 bits are full-scale counters; 
+   the actual peak value is in the most-significant 24 bits.
+*/
+#define HDSPM_MADI_peakrmsbase 	4096	/* 4096-8191 2x64x32Bit Meters */
+
+/* --- Control Register bits --------- */
+#define HDSPM_Start                (1<<0) /* start engine */
+
+#define HDSPM_Latency0             (1<<1) /* buffer size = 2^n */
+#define HDSPM_Latency1             (1<<2) /* where n is defined */
+#define HDSPM_Latency2             (1<<3) /* by Latency{2,1,0} */
+
+#define HDSPM_ClockModeMaster      (1<<4) /* 1=Master, 0=Slave/Autosync */
+
+#define HDSPM_AudioInterruptEnable (1<<5) /* what do you think ? */
+
+#define HDSPM_Frequency0  (1<<6)  /* 0=44.1kHz/88.2kHz 1=48kHz/96kHz */
+#define HDSPM_Frequency1  (1<<7)  /* 0=32kHz/64kHz */
+#define HDSPM_DoubleSpeed (1<<8)  /* 0=normal speed, 1=double speed */
+#define HDSPM_QuadSpeed   (1<<31) /* quad speed bit */
+
+#define HDSPM_Professional (1<<9) /* Professional */ /* AES32 ONLY */
+#define HDSPM_TX_64ch     (1<<10) /* Output 64channel MODE=1,
+				     56channelMODE=0 */ /* MADI ONLY*/
+#define HDSPM_Emphasis    (1<<10) /* Emphasis */ /* AES32 ONLY */
+
+#define HDSPM_AutoInp     (1<<11) /* Auto Input (takeover) == Safe Mode, 
+                                     0=off, 1=on  */ /* MADI ONLY */
+#define HDSPM_Dolby       (1<<11) /* Dolby = "NonAudio" ?? */ /* AES32 ONLY */
+
+#define HDSPM_InputSelect0 (1<<14) /* Input select 0= optical, 1=coax
+				    * -- MADI ONLY
+				    */
+#define HDSPM_InputSelect1 (1<<15) /* should be 0 */
+
+#define HDSPM_SyncRef0     (1<<16) /* 0=WOrd, 1=MADI */
+#define HDSPM_SyncRef1     (1<<17) /* for AES32: SyncRefN codes the AES # */
+#define HDSPM_SyncRef2     (1<<13)
+#define HDSPM_SyncRef3     (1<<25)
+
+#define HDSPM_SMUX         (1<<18) /* Frame ??? */ /* MADI ONY */
+#define HDSPM_clr_tms      (1<<19) /* clear track marker, do not use 
+                                      AES additional bits in
+				      lower 5 Audiodatabits ??? */
+#define HDSPM_taxi_reset   (1<<20) /* ??? */ /* MADI ONLY ? */
+#define HDSPM_WCK48        (1<<20) /* Frame ??? = HDSPM_SMUX */ /* AES32 ONLY */
+
+#define HDSPM_Midi0InterruptEnable (1<<22)
+#define HDSPM_Midi1InterruptEnable (1<<23)
+
+#define HDSPM_LineOut (1<<24) /* Analog Out on channel 63/64 on=1, mute=0 */
+
+#define HDSPM_DS_DoubleWire (1<<26) /* AES32 ONLY */
+#define HDSPM_QS_DoubleWire (1<<27) /* AES32 ONLY */
+#define HDSPM_QS_QuadWire   (1<<28) /* AES32 ONLY */
+
+#define HDSPM_wclk_sel (1<<30)
+
+/* --- bit helper defines */
+#define HDSPM_LatencyMask    (HDSPM_Latency0|HDSPM_Latency1|HDSPM_Latency2)
+#define HDSPM_FrequencyMask  (HDSPM_Frequency0|HDSPM_Frequency1|\
+			      HDSPM_DoubleSpeed|HDSPM_QuadSpeed)
+#define HDSPM_InputMask      (HDSPM_InputSelect0|HDSPM_InputSelect1)
+#define HDSPM_InputOptical   0
+#define HDSPM_InputCoaxial   (HDSPM_InputSelect0)
+#define HDSPM_SyncRefMask    (HDSPM_SyncRef0|HDSPM_SyncRef1|\
+			      HDSPM_SyncRef2|HDSPM_SyncRef3)
+#define HDSPM_SyncRef_Word   0
+#define HDSPM_SyncRef_MADI   (HDSPM_SyncRef0)
+
+#define HDSPM_SYNC_FROM_WORD 0	/* Preferred sync reference */
+#define HDSPM_SYNC_FROM_MADI 1	/* choices - used by "pref_sync_ref" */
+
+#define HDSPM_Frequency32KHz    HDSPM_Frequency0
+#define HDSPM_Frequency44_1KHz  HDSPM_Frequency1
+#define HDSPM_Frequency48KHz   (HDSPM_Frequency1|HDSPM_Frequency0)
+#define HDSPM_Frequency64KHz   (HDSPM_DoubleSpeed|HDSPM_Frequency0)
+#define HDSPM_Frequency88_2KHz (HDSPM_DoubleSpeed|HDSPM_Frequency1)
+#define HDSPM_Frequency96KHz   (HDSPM_DoubleSpeed|HDSPM_Frequency1|\
+				HDSPM_Frequency0)
+#define HDSPM_Frequency128KHz   (HDSPM_QuadSpeed|HDSPM_Frequency0)
+#define HDSPM_Frequency176_4KHz   (HDSPM_QuadSpeed|HDSPM_Frequency1)
+#define HDSPM_Frequency192KHz   (HDSPM_QuadSpeed|HDSPM_Frequency1|\
+				 HDSPM_Frequency0)
+
+/* --- for internal discrimination */
+#define HDSPM_CLOCK_SOURCE_AUTOSYNC          0	/* Sample Clock Sources */
+#define HDSPM_CLOCK_SOURCE_INTERNAL_32KHZ    1
+#define HDSPM_CLOCK_SOURCE_INTERNAL_44_1KHZ  2
+#define HDSPM_CLOCK_SOURCE_INTERNAL_48KHZ    3
+#define HDSPM_CLOCK_SOURCE_INTERNAL_64KHZ    4
+#define HDSPM_CLOCK_SOURCE_INTERNAL_88_2KHZ  5
+#define HDSPM_CLOCK_SOURCE_INTERNAL_96KHZ    6
+#define HDSPM_CLOCK_SOURCE_INTERNAL_128KHZ   7
+#define HDSPM_CLOCK_SOURCE_INTERNAL_176_4KHZ 8
+#define HDSPM_CLOCK_SOURCE_INTERNAL_192KHZ   9
+
+/* Synccheck Status */
+#define HDSPM_SYNC_CHECK_NO_LOCK 0
+#define HDSPM_SYNC_CHECK_LOCK    1
+#define HDSPM_SYNC_CHECK_SYNC	 2
+
+/* AutoSync References - used by "autosync_ref" control switch */
+#define HDSPM_AUTOSYNC_FROM_WORD      0
+#define HDSPM_AUTOSYNC_FROM_MADI      1
+#define HDSPM_AUTOSYNC_FROM_NONE      2
+
+/* Possible sources of MADI input */
+#define HDSPM_OPTICAL 0		/* optical   */
+#define HDSPM_COAXIAL 1		/* BNC */
+
+#define hdspm_encode_latency(x)       (((x)<<1) & HDSPM_LatencyMask)
+#define hdspm_decode_latency(x)       (((x) & HDSPM_LatencyMask)>>1)
+
+#define hdspm_encode_in(x) (((x)&0x3)<<14)
+#define hdspm_decode_in(x) (((x)>>14)&0x3)
+
+/* --- control2 register bits --- */
+#define HDSPM_TMS             (1<<0)
+#define HDSPM_TCK             (1<<1)
+#define HDSPM_TDI             (1<<2)
+#define HDSPM_JTAG            (1<<3)
+#define HDSPM_PWDN            (1<<4)
+#define HDSPM_PROGRAM	      (1<<5)
+#define HDSPM_CONFIG_MODE_0   (1<<6)
+#define HDSPM_CONFIG_MODE_1   (1<<7)
+/*#define HDSPM_VERSION_BIT     (1<<8) not defined any more*/
+#define HDSPM_BIGENDIAN_MODE  (1<<9)
+#define HDSPM_RD_MULTIPLE     (1<<10)
+
+/* --- Status Register bits --- */ /* MADI ONLY */ /* Bits defined here and
+     that do not conflict with specific bits for AES32 seem to be valid also
+     for the AES32
+ */
+#define HDSPM_audioIRQPending    (1<<0)	/* IRQ is high and pending */
+#define HDSPM_RX_64ch            (1<<1)	/* Input 64chan. MODE=1, 56chn MODE=0 */
+#define HDSPM_AB_int             (1<<2)	/* InputChannel Opt=0, Coax=1
+					 * (like inp0)
+					 */
+#define HDSPM_madiLock           (1<<3)	/* MADI Locked =1, no=0 */
+
+#define HDSPM_BufferPositionMask 0x000FFC0 /* Bit 6..15 : h/w buffer pointer */
+                                           /* since 64byte accurate last 6 bits 
+                                              are not used */
+
+#define HDSPM_madiSync          (1<<18) /* MADI is in sync */
+#define HDSPM_DoubleSpeedStatus (1<<19) /* (input) card in double speed */
+
+#define HDSPM_madiFreq0         (1<<22)	/* system freq 0=error */
+#define HDSPM_madiFreq1         (1<<23)	/* 1=32, 2=44.1 3=48 */
+#define HDSPM_madiFreq2         (1<<24)	/* 4=64, 5=88.2 6=96 */
+#define HDSPM_madiFreq3         (1<<25)	/* 7=128, 8=176.4 9=192 */
+
+#define HDSPM_BufferID          (1<<26)	/* (Double)Buffer ID toggles with
+					 * Interrupt
+					 */
+#define HDSPM_midi0IRQPending   (1<<30)	/* MIDI IRQ is pending  */
+#define HDSPM_midi1IRQPending   (1<<31)	/* and aktiv */
+
+/* --- status bit helpers */
+#define HDSPM_madiFreqMask  (HDSPM_madiFreq0|HDSPM_madiFreq1|\
+			     HDSPM_madiFreq2|HDSPM_madiFreq3)
+#define HDSPM_madiFreq32    (HDSPM_madiFreq0)
+#define HDSPM_madiFreq44_1  (HDSPM_madiFreq1)
+#define HDSPM_madiFreq48    (HDSPM_madiFreq0|HDSPM_madiFreq1)
+#define HDSPM_madiFreq64    (HDSPM_madiFreq2)
+#define HDSPM_madiFreq88_2  (HDSPM_madiFreq0|HDSPM_madiFreq2)
+#define HDSPM_madiFreq96    (HDSPM_madiFreq1|HDSPM_madiFreq2)
+#define HDSPM_madiFreq128   (HDSPM_madiFreq0|HDSPM_madiFreq1|HDSPM_madiFreq2)
+#define HDSPM_madiFreq176_4 (HDSPM_madiFreq3)
+#define HDSPM_madiFreq192   (HDSPM_madiFreq3|HDSPM_madiFreq0)
+
+/* Status2 Register bits */ /* MADI ONLY */
+
+#define HDSPM_version0 (1<<0)	/* not realy defined but I guess */
+#define HDSPM_version1 (1<<1)	/* in former cards it was ??? */
+#define HDSPM_version2 (1<<2)
+
+#define HDSPM_wcLock (1<<3)	/* Wordclock is detected and locked */
+#define HDSPM_wcSync (1<<4)	/* Wordclock is in sync with systemclock */
+
+#define HDSPM_wc_freq0 (1<<5)	/* input freq detected via autosync  */
+#define HDSPM_wc_freq1 (1<<6)	/* 001=32, 010==44.1, 011=48, */
+#define HDSPM_wc_freq2 (1<<7)	/* 100=64, 101=88.2, 110=96, */
+/* missing Bit   for               111=128, 1000=176.4, 1001=192 */
+
+#define HDSPM_SelSyncRef0 (1<<8)	/* Sync Source in slave mode */
+#define HDSPM_SelSyncRef1 (1<<9)	/* 000=word, 001=MADI, */
+#define HDSPM_SelSyncRef2 (1<<10)	/* 111=no valid signal */
+
+#define HDSPM_wc_valid (HDSPM_wcLock|HDSPM_wcSync)
+
+#define HDSPM_wcFreqMask  (HDSPM_wc_freq0|HDSPM_wc_freq1|HDSPM_wc_freq2)
+#define HDSPM_wcFreq32    (HDSPM_wc_freq0)
+#define HDSPM_wcFreq44_1  (HDSPM_wc_freq1)
+#define HDSPM_wcFreq48    (HDSPM_wc_freq0|HDSPM_wc_freq1)
+#define HDSPM_wcFreq64    (HDSPM_wc_freq2)
+#define HDSPM_wcFreq88_2  (HDSPM_wc_freq0|HDSPM_wc_freq2)
+#define HDSPM_wcFreq96    (HDSPM_wc_freq1|HDSPM_wc_freq2)
+
+
+#define HDSPM_SelSyncRefMask       (HDSPM_SelSyncRef0|HDSPM_SelSyncRef1|\
+				    HDSPM_SelSyncRef2)
+#define HDSPM_SelSyncRef_WORD      0
+#define HDSPM_SelSyncRef_MADI      (HDSPM_SelSyncRef0)
+#define HDSPM_SelSyncRef_NVALID    (HDSPM_SelSyncRef0|HDSPM_SelSyncRef1|\
+				    HDSPM_SelSyncRef2)
+
+/*
+   For AES32, bits for status, status2 and timecode are different
+*/
+/* status */
+#define HDSPM_AES32_wcLock	0x0200000
+#define HDSPM_AES32_wcFreq_bit  22
+/* (status >> HDSPM_AES32_wcFreq_bit) & 0xF gives WC frequency (cf function 
+  HDSPM_bit2freq */
+#define HDSPM_AES32_syncref_bit  16
+/* (status >> HDSPM_AES32_syncref_bit) & 0xF gives sync source */
+
+#define HDSPM_AES32_AUTOSYNC_FROM_WORD 0
+#define HDSPM_AES32_AUTOSYNC_FROM_AES1 1
+#define HDSPM_AES32_AUTOSYNC_FROM_AES2 2
+#define HDSPM_AES32_AUTOSYNC_FROM_AES3 3
+#define HDSPM_AES32_AUTOSYNC_FROM_AES4 4
+#define HDSPM_AES32_AUTOSYNC_FROM_AES5 5
+#define HDSPM_AES32_AUTOSYNC_FROM_AES6 6
+#define HDSPM_AES32_AUTOSYNC_FROM_AES7 7
+#define HDSPM_AES32_AUTOSYNC_FROM_AES8 8
+#define HDSPM_AES32_AUTOSYNC_FROM_NONE 9
+
+/*  status2 */
+/* HDSPM_LockAES_bit is given by HDSPM_LockAES >> (AES# - 1) */
+#define HDSPM_LockAES   0x80
+#define HDSPM_LockAES1  0x80
+#define HDSPM_LockAES2  0x40
+#define HDSPM_LockAES3  0x20
+#define HDSPM_LockAES4  0x10
+#define HDSPM_LockAES5  0x8
+#define HDSPM_LockAES6  0x4
+#define HDSPM_LockAES7  0x2
+#define HDSPM_LockAES8  0x1
+/*
+   Timecode
+   After windows driver sources, bits 4*i to 4*i+3 give the input frequency on
+   AES i+1
+ bits 3210
+      0001  32kHz
+      0010  44.1kHz
+      0011  48kHz
+      0100  64kHz
+      0101  88.2kHz
+      0110  96kHz
+      0111  128kHz
+      1000  176.4kHz
+      1001  192kHz
+  NB: Timecode register doesn't seem to work on AES32 card revision 230
+*/
+
+/* Mixer Values */
+#define UNITY_GAIN          32768	/* = 65536/2 */
+#define MINUS_INFINITY_GAIN 0
+
+/* Number of channels for different Speed Modes */
+#define MADI_SS_CHANNELS       64
+#define MADI_DS_CHANNELS       32
+#define MADI_QS_CHANNELS       16
+
+/* the size of a substream (1 mono data stream) */
+#define HDSPM_CHANNEL_BUFFER_SAMPLES  (16*1024)
+#define HDSPM_CHANNEL_BUFFER_BYTES    (4*HDSPM_CHANNEL_BUFFER_SAMPLES)
+
+/* the size of the area we need to allocate for DMA transfers. the
+   size is the same regardless of the number of channels, and
+   also the latency to use. 
+   for one direction !!!
+*/
+#define HDSPM_DMA_AREA_BYTES (HDSPM_MAX_CHANNELS * HDSPM_CHANNEL_BUFFER_BYTES)
+#define HDSPM_DMA_AREA_KILOBYTES (HDSPM_DMA_AREA_BYTES/1024)
+
+/* revisions >= 230 indicate AES32 card */
+#define HDSPM_AESREVISION 230
+
+/* speed factor modes */
+#define HDSPM_SPEED_SINGLE 0
+#define HDSPM_SPEED_DOUBLE 1
+#define HDSPM_SPEED_QUAD   2
+/* names for speed modes */
+static char *hdspm_speed_names[] = { "single", "double", "quad" };
+
+struct hdspm_midi {
+	struct hdspm *hdspm;
+	int id;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *input;
+	struct snd_rawmidi_substream *output;
+	char istimer;		/* timer in use */
+	struct timer_list timer;
+	spinlock_t lock;
+	int pending;
+};
+
+struct hdspm {
+        spinlock_t lock;
+	/* only one playback and/or capture stream */
+        struct snd_pcm_substream *capture_substream;
+        struct snd_pcm_substream *playback_substream;
+
+	char *card_name;	     /* for procinfo */
+	unsigned short firmware_rev; /* dont know if relevant (yes if AES32)*/
+
+	unsigned char is_aes32;    /* indicates if card is AES32 */
+
+	int precise_ptr;	/* use precise pointers, to be tested */
+	int monitor_outs;	/* set up monitoring outs init flag */
+
+	u32 control_register;	/* cached value */
+	u32 control2_register;	/* cached value */
+
+	struct hdspm_midi midi[2];
+	struct tasklet_struct midi_tasklet;
+
+	size_t period_bytes;
+	unsigned char ss_channels;	/* channels of card in single speed */
+	unsigned char ds_channels;	/* Double Speed */
+	unsigned char qs_channels;	/* Quad Speed */
+
+	unsigned char *playback_buffer;	/* suitably aligned address */
+	unsigned char *capture_buffer;	/* suitably aligned address */
+
+	pid_t capture_pid;	/* process id which uses capture */
+	pid_t playback_pid;	/* process id which uses capture */
+	int running;		/* running status */
+
+	int last_external_sample_rate;	/* samplerate mystic ... */
+	int last_internal_sample_rate;
+	int system_sample_rate;
+
+	char *channel_map;	/* channel map for DS and Quadspeed */
+
+	int dev;		/* Hardware vars... */
+	int irq;
+	unsigned long port;
+	void __iomem *iobase;
+
+	int irq_count;		/* for debug */
+
+	struct snd_card *card;	/* one card */
+	struct snd_pcm *pcm;		/* has one pcm */
+	struct snd_hwdep *hwdep;	/* and a hwdep for additional ioctl */
+	struct pci_dev *pci;	/* and an pci info */
+
+	/* Mixer vars */
+	/* fast alsa mixer */
+	struct snd_kcontrol *playback_mixer_ctls[HDSPM_MAX_CHANNELS];
+	/* but input to much, so not used */
+	struct snd_kcontrol *input_mixer_ctls[HDSPM_MAX_CHANNELS];
+	/* full mixer accessable over mixer ioctl or hwdep-device */
+	struct hdspm_mixer *mixer;
+
+};
+
+/* These tables map the ALSA channels 1..N to the channels that we
+   need to use in order to find the relevant channel buffer. RME
+   refer to this kind of mapping as between "the ADAT channel and
+   the DMA channel." We index it using the logical audio channel,
+   and the value is the DMA channel (i.e. channel buffer number)
+   where the data for that channel can be read/written from/to.
+*/
+
+static char channel_map_madi_ss[HDSPM_MAX_CHANNELS] = {
+   0, 1, 2, 3, 4, 5, 6, 7,
+   8, 9, 10, 11, 12, 13, 14, 15,
+   16, 17, 18, 19, 20, 21, 22, 23,
+   24, 25, 26, 27, 28, 29, 30, 31,
+   32, 33, 34, 35, 36, 37, 38, 39,
+   40, 41, 42, 43, 44, 45, 46, 47,
+   48, 49, 50, 51, 52, 53, 54, 55,
+   56, 57, 58, 59, 60, 61, 62, 63
+};
+
+
+static DEFINE_PCI_DEVICE_TABLE(snd_hdspm_ids) = {
+	{
+	 .vendor = PCI_VENDOR_ID_XILINX,
+	 .device = PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI,
+	 .subvendor = PCI_ANY_ID,
+	 .subdevice = PCI_ANY_ID,
+	 .class = 0,
+	 .class_mask = 0,
+	 .driver_data = 0},
+	{0,}
+};
+
+MODULE_DEVICE_TABLE(pci, snd_hdspm_ids);
+
+/* prototypes */
+static int __devinit snd_hdspm_create_alsa_devices(struct snd_card *card,
+						   struct hdspm * hdspm);
+static int __devinit snd_hdspm_create_pcm(struct snd_card *card,
+					  struct hdspm * hdspm);
+
+static inline void snd_hdspm_initialize_midi_flush(struct hdspm * hdspm);
+static int hdspm_update_simple_mixer_controls(struct hdspm * hdspm);
+static int hdspm_autosync_ref(struct hdspm * hdspm);
+static int snd_hdspm_set_defaults(struct hdspm * hdspm);
+static void hdspm_set_sgbuf(struct hdspm * hdspm,
+			    struct snd_pcm_substream *substream,
+			     unsigned int reg, int channels);
+
+static inline int HDSPM_bit2freq(int n)
+{
+	static const int bit2freq_tab[] = {
+		0, 32000, 44100, 48000, 64000, 88200,
+		96000, 128000, 176400, 192000 };
+	if (n < 1 || n > 9)
+		return 0;
+	return bit2freq_tab[n];
+}
+
+/* Write/read to/from HDSPM with Adresses in Bytes
+   not words but only 32Bit writes are allowed */
+
+static inline void hdspm_write(struct hdspm * hdspm, unsigned int reg,
+			       unsigned int val)
+{
+	writel(val, hdspm->iobase + reg);
+}
+
+static inline unsigned int hdspm_read(struct hdspm * hdspm, unsigned int reg)
+{
+	return readl(hdspm->iobase + reg);
+}
+
+/* for each output channel (chan) I have an Input (in) and Playback (pb) Fader 
+   mixer is write only on hardware so we have to cache him for read 
+   each fader is a u32, but uses only the first 16 bit */
+
+static inline int hdspm_read_in_gain(struct hdspm * hdspm, unsigned int chan,
+				     unsigned int in)
+{
+	if (chan >= HDSPM_MIXER_CHANNELS || in >= HDSPM_MIXER_CHANNELS)
+		return 0;
+
+	return hdspm->mixer->ch[chan].in[in];
+}
+
+static inline int hdspm_read_pb_gain(struct hdspm * hdspm, unsigned int chan,
+				     unsigned int pb)
+{
+	if (chan >= HDSPM_MIXER_CHANNELS || pb >= HDSPM_MIXER_CHANNELS)
+		return 0;
+	return hdspm->mixer->ch[chan].pb[pb];
+}
+
+static int hdspm_write_in_gain(struct hdspm *hdspm, unsigned int chan,
+				      unsigned int in, unsigned short data)
+{
+	if (chan >= HDSPM_MIXER_CHANNELS || in >= HDSPM_MIXER_CHANNELS)
+		return -1;
+
+	hdspm_write(hdspm,
+		    HDSPM_MADI_mixerBase +
+		    ((in + 128 * chan) * sizeof(u32)),
+		    (hdspm->mixer->ch[chan].in[in] = data & 0xFFFF));
+	return 0;
+}
+
+static int hdspm_write_pb_gain(struct hdspm *hdspm, unsigned int chan,
+				      unsigned int pb, unsigned short data)
+{
+	if (chan >= HDSPM_MIXER_CHANNELS || pb >= HDSPM_MIXER_CHANNELS)
+		return -1;
+
+	hdspm_write(hdspm,
+		    HDSPM_MADI_mixerBase +
+		    ((64 + pb + 128 * chan) * sizeof(u32)),
+		    (hdspm->mixer->ch[chan].pb[pb] = data & 0xFFFF));
+	return 0;
+}
+
+
+/* enable DMA for specific channels, now available for DSP-MADI */
+static inline void snd_hdspm_enable_in(struct hdspm * hdspm, int i, int v)
+{
+	hdspm_write(hdspm, HDSPM_inputEnableBase + (4 * i), v);
+}
+
+static inline void snd_hdspm_enable_out(struct hdspm * hdspm, int i, int v)
+{
+	hdspm_write(hdspm, HDSPM_outputEnableBase + (4 * i), v);
+}
+
+/* check if same process is writing and reading */
+static int snd_hdspm_use_is_exclusive(struct hdspm *hdspm)
+{
+	unsigned long flags;
+	int ret = 1;
+
+	spin_lock_irqsave(&hdspm->lock, flags);
+	if ((hdspm->playback_pid != hdspm->capture_pid) &&
+	    (hdspm->playback_pid >= 0) && (hdspm->capture_pid >= 0)) {
+		ret = 0;
+	}
+	spin_unlock_irqrestore(&hdspm->lock, flags);
+	return ret;
+}
+
+/* check for external sample rate */
+static int hdspm_external_sample_rate(struct hdspm *hdspm)
+{
+	if (hdspm->is_aes32) {
+		unsigned int status2 = hdspm_read(hdspm, HDSPM_statusRegister2);
+		unsigned int status = hdspm_read(hdspm, HDSPM_statusRegister);
+		unsigned int timecode =
+			hdspm_read(hdspm, HDSPM_timecodeRegister);
+
+		int syncref = hdspm_autosync_ref(hdspm);
+
+		if (syncref == HDSPM_AES32_AUTOSYNC_FROM_WORD &&
+				status & HDSPM_AES32_wcLock)
+			return HDSPM_bit2freq((status >> HDSPM_AES32_wcFreq_bit)
+					      & 0xF);
+		if (syncref >= HDSPM_AES32_AUTOSYNC_FROM_AES1 &&
+			syncref <= HDSPM_AES32_AUTOSYNC_FROM_AES8 &&
+			status2 & (HDSPM_LockAES >>
+			          (syncref - HDSPM_AES32_AUTOSYNC_FROM_AES1)))
+			return HDSPM_bit2freq((timecode >>
+			  (4*(syncref-HDSPM_AES32_AUTOSYNC_FROM_AES1))) & 0xF);
+		return 0;
+	} else {
+		unsigned int status2 = hdspm_read(hdspm, HDSPM_statusRegister2);
+		unsigned int status = hdspm_read(hdspm, HDSPM_statusRegister);
+		unsigned int rate_bits;
+		int rate = 0;
+
+		/* if wordclock has synced freq and wordclock is valid */
+		if ((status2 & HDSPM_wcLock) != 0 &&
+				(status & HDSPM_SelSyncRef0) == 0) {
+
+			rate_bits = status2 & HDSPM_wcFreqMask;
+
+			switch (rate_bits) {
+			case HDSPM_wcFreq32:
+				rate = 32000;
+				break;
+			case HDSPM_wcFreq44_1:
+				rate = 44100;
+				break;
+			case HDSPM_wcFreq48:
+				rate = 48000;
+				break;
+			case HDSPM_wcFreq64:
+				rate = 64000;
+				break;
+			case HDSPM_wcFreq88_2:
+				rate = 88200;
+				break;
+			case HDSPM_wcFreq96:
+				rate = 96000;
+				break;
+				/* Quadspeed Bit missing ???? */
+			default:
+				rate = 0;
+				break;
+			}
+		}
+
+		/* if rate detected and Syncref is Word than have it,
+		 * word has priority to MADI
+		 */
+		if (rate != 0 &&
+	            (status2 & HDSPM_SelSyncRefMask) == HDSPM_SelSyncRef_WORD)
+			return rate;
+
+		/* maby a madi input (which is taken if sel sync is madi) */
+		if (status & HDSPM_madiLock) {
+			rate_bits = status & HDSPM_madiFreqMask;
+
+			switch (rate_bits) {
+			case HDSPM_madiFreq32:
+				rate = 32000;
+				break;
+			case HDSPM_madiFreq44_1:
+				rate = 44100;
+				break;
+			case HDSPM_madiFreq48:
+				rate = 48000;
+				break;
+			case HDSPM_madiFreq64:
+				rate = 64000;
+				break;
+			case HDSPM_madiFreq88_2:
+				rate = 88200;
+				break;
+			case HDSPM_madiFreq96:
+				rate = 96000;
+				break;
+			case HDSPM_madiFreq128:
+				rate = 128000;
+				break;
+			case HDSPM_madiFreq176_4:
+				rate = 176400;
+				break;
+			case HDSPM_madiFreq192:
+				rate = 192000;
+				break;
+			default:
+				rate = 0;
+				break;
+			}
+		}
+		return rate;
+	}
+}
+
+/* Latency function */
+static inline void hdspm_compute_period_size(struct hdspm * hdspm)
+{
+	hdspm->period_bytes =
+	    1 << ((hdspm_decode_latency(hdspm->control_register) + 8));
+}
+
+static snd_pcm_uframes_t hdspm_hw_pointer(struct hdspm * hdspm)
+{
+	int position;
+
+	position = hdspm_read(hdspm, HDSPM_statusRegister);
+
+	if (!hdspm->precise_ptr)
+		return (position & HDSPM_BufferID) ?
+			(hdspm->period_bytes / 4) : 0;
+
+	/* hwpointer comes in bytes and is 64Bytes accurate (by docu since
+	   PCI Burst)
+	   i have experimented that it is at most 64 Byte to much for playing 
+	   so substraction of 64 byte should be ok for ALSA, but use it only
+	   for application where you know what you do since if you come to
+	   near with record pointer it can be a disaster */
+
+	position &= HDSPM_BufferPositionMask;
+	position = ((position - 64) % (2 * hdspm->period_bytes)) / 4;
+
+	return position;
+}
+
+
+static inline void hdspm_start_audio(struct hdspm * s)
+{
+	s->control_register |= (HDSPM_AudioInterruptEnable | HDSPM_Start);
+	hdspm_write(s, HDSPM_controlRegister, s->control_register);
+}
+
+static inline void hdspm_stop_audio(struct hdspm * s)
+{
+	s->control_register &= ~(HDSPM_Start | HDSPM_AudioInterruptEnable);
+	hdspm_write(s, HDSPM_controlRegister, s->control_register);
+}
+
+/* should I silence all or only opened ones ? doit all for first even is 4MB*/
+static void hdspm_silence_playback(struct hdspm *hdspm)
+{
+	int i;
+	int n = hdspm->period_bytes;
+	void *buf = hdspm->playback_buffer;
+
+	if (buf == NULL)
+		return;
+
+	for (i = 0; i < HDSPM_MAX_CHANNELS; i++) {
+		memset(buf, 0, n);
+		buf += HDSPM_CHANNEL_BUFFER_BYTES;
+	}
+}
+
+static int hdspm_set_interrupt_interval(struct hdspm * s, unsigned int frames)
+{
+	int n;
+
+	spin_lock_irq(&s->lock);
+
+	frames >>= 7;
+	n = 0;
+	while (frames) {
+		n++;
+		frames >>= 1;
+	}
+	s->control_register &= ~HDSPM_LatencyMask;
+	s->control_register |= hdspm_encode_latency(n);
+
+	hdspm_write(s, HDSPM_controlRegister, s->control_register);
+
+	hdspm_compute_period_size(s);
+
+	spin_unlock_irq(&s->lock);
+
+	return 0;
+}
+
+static void hdspm_set_dds_value(struct hdspm *hdspm, int rate)
+{
+	u64 n;
+	
+	if (rate >= 112000)
+		rate /= 4;
+	else if (rate >= 56000)
+		rate /= 2;
+
+	/* RME says n = 104857600000000, but in the windows MADI driver, I see:
+//	return 104857600000000 / rate; // 100 MHz
+	return 110100480000000 / rate; // 105 MHz
+        */	   
+	/* n = 104857600000000ULL; */ /*  =  2^20 * 10^8 */
+	n = 110100480000000ULL;    /* Value checked for AES32 and MADI */
+	n = div_u64(n, rate);
+	/* n should be less than 2^32 for being written to FREQ register */
+	snd_BUG_ON(n >> 32);
+	hdspm_write(hdspm, HDSPM_freqReg, (u32)n);
+}
+
+/* dummy set rate lets see what happens */
+static int hdspm_set_rate(struct hdspm * hdspm, int rate, int called_internally)
+{
+	int current_rate;
+	int rate_bits;
+	int not_set = 0;
+	int current_speed, target_speed;
+
+	/* ASSUMPTION: hdspm->lock is either set, or there is no need for
+	   it (e.g. during module initialization).
+	 */
+
+	if (!(hdspm->control_register & HDSPM_ClockModeMaster)) {
+
+	        /* SLAVE --- */ 
+		if (called_internally) {
+
+        	  /* request from ctl or card initialization 
+	             just make a warning an remember setting 
+		     for future master mode switching */
+    
+			snd_printk(KERN_WARNING "HDSPM: "
+				   "Warning: device is not running "
+				   "as a clock master.\n");
+			not_set = 1;
+		} else {
+
+			/* hw_param request while in AutoSync mode */
+			int external_freq =
+			    hdspm_external_sample_rate(hdspm);
+
+			if (hdspm_autosync_ref(hdspm) ==
+			    HDSPM_AUTOSYNC_FROM_NONE) {
+
+				snd_printk(KERN_WARNING "HDSPM: "
+					   "Detected no Externel Sync \n");
+				not_set = 1;
+
+			} else if (rate != external_freq) {
+
+				snd_printk(KERN_WARNING "HDSPM: "
+					   "Warning: No AutoSync source for "
+					   "requested rate\n");
+				not_set = 1;
+			}
+		}
+	}
+
+	current_rate = hdspm->system_sample_rate;
+
+	/* Changing between Singe, Double and Quad speed is not
+	   allowed if any substreams are open. This is because such a change
+	   causes a shift in the location of the DMA buffers and a reduction
+	   in the number of available buffers.
+
+	   Note that a similar but essentially insoluble problem exists for
+	   externally-driven rate changes. All we can do is to flag rate
+	   changes in the read/write routines.  
+	 */
+
+	if (current_rate <= 48000)
+		current_speed = HDSPM_SPEED_SINGLE;
+	else if (current_rate <= 96000)
+		current_speed = HDSPM_SPEED_DOUBLE;
+	else
+		current_speed = HDSPM_SPEED_QUAD;
+
+	if (rate <= 48000)
+		target_speed = HDSPM_SPEED_SINGLE;
+	else if (rate <= 96000)
+		target_speed = HDSPM_SPEED_DOUBLE;
+	else
+		target_speed = HDSPM_SPEED_QUAD;
+
+	switch (rate) {
+	case 32000:
+		rate_bits = HDSPM_Frequency32KHz;
+		break;
+	case 44100:
+		rate_bits = HDSPM_Frequency44_1KHz;
+		break;
+	case 48000:
+		rate_bits = HDSPM_Frequency48KHz;
+		break;
+	case 64000:
+		rate_bits = HDSPM_Frequency64KHz;
+		break;
+	case 88200:
+		rate_bits = HDSPM_Frequency88_2KHz;
+		break;
+	case 96000:
+		rate_bits = HDSPM_Frequency96KHz;
+		break;
+	case 128000:
+		rate_bits = HDSPM_Frequency128KHz;
+		break;
+	case 176400:
+		rate_bits = HDSPM_Frequency176_4KHz;
+		break;
+	case 192000:
+		rate_bits = HDSPM_Frequency192KHz;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (current_speed != target_speed
+	    && (hdspm->capture_pid >= 0 || hdspm->playback_pid >= 0)) {
+		snd_printk
+		    (KERN_ERR "HDSPM: "
+		     "cannot change from %s speed to %s speed mode "
+		     "(capture PID = %d, playback PID = %d)\n",
+		     hdspm_speed_names[current_speed],
+		     hdspm_speed_names[target_speed],
+		     hdspm->capture_pid, hdspm->playback_pid);
+		return -EBUSY;
+	}
+
+	hdspm->control_register &= ~HDSPM_FrequencyMask;
+	hdspm->control_register |= rate_bits;
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+	/* For AES32, need to set DDS value in FREQ register
+	   For MADI, also apparently */
+	hdspm_set_dds_value(hdspm, rate);
+	
+	if (hdspm->is_aes32 && rate != current_rate)
+		hdspm_write(hdspm, HDSPM_eeprom_wr, 0);
+	
+	/* For AES32 and for MADI (at least rev 204), channel_map needs to
+	 * always be channel_map_madi_ss, whatever the sample rate */
+	hdspm->channel_map = channel_map_madi_ss;
+
+	hdspm->system_sample_rate = rate;
+
+	if (not_set != 0)
+		return -1;
+
+	return 0;
+}
+
+/* mainly for init to 0 on load */
+static void all_in_all_mixer(struct hdspm * hdspm, int sgain)
+{
+	int i, j;
+	unsigned int gain;
+
+	if (sgain > UNITY_GAIN)
+		gain = UNITY_GAIN;
+	else if (sgain < 0)
+		gain = 0;
+	else
+		gain = sgain;
+
+	for (i = 0; i < HDSPM_MIXER_CHANNELS; i++)
+		for (j = 0; j < HDSPM_MIXER_CHANNELS; j++) {
+			hdspm_write_in_gain(hdspm, i, j, gain);
+			hdspm_write_pb_gain(hdspm, i, j, gain);
+		}
+}
+
+/*----------------------------------------------------------------------------
+   MIDI
+  ----------------------------------------------------------------------------*/
+
+static inline unsigned char snd_hdspm_midi_read_byte (struct hdspm *hdspm,
+						      int id)
+{
+	/* the hardware already does the relevant bit-mask with 0xff */
+	if (id)
+		return hdspm_read(hdspm, HDSPM_midiDataIn1);
+	else
+		return hdspm_read(hdspm, HDSPM_midiDataIn0);
+}
+
+static inline void snd_hdspm_midi_write_byte (struct hdspm *hdspm, int id,
+					      int val)
+{
+	/* the hardware already does the relevant bit-mask with 0xff */
+	if (id)
+		hdspm_write(hdspm, HDSPM_midiDataOut1, val);
+	else
+		hdspm_write(hdspm, HDSPM_midiDataOut0, val);
+}
+
+static inline int snd_hdspm_midi_input_available (struct hdspm *hdspm, int id)
+{
+	if (id)
+		return (hdspm_read(hdspm, HDSPM_midiStatusIn1) & 0xff);
+	else
+		return (hdspm_read(hdspm, HDSPM_midiStatusIn0) & 0xff);
+}
+
+static inline int snd_hdspm_midi_output_possible (struct hdspm *hdspm, int id)
+{
+	int fifo_bytes_used;
+
+	if (id)
+		fifo_bytes_used = hdspm_read(hdspm, HDSPM_midiStatusOut1);
+	else
+		fifo_bytes_used = hdspm_read(hdspm, HDSPM_midiStatusOut0);
+	fifo_bytes_used &= 0xff;
+
+	if (fifo_bytes_used < 128)
+		return  128 - fifo_bytes_used;
+	else
+		return 0;
+}
+
+static void snd_hdspm_flush_midi_input(struct hdspm *hdspm, int id)
+{
+	while (snd_hdspm_midi_input_available (hdspm, id))
+		snd_hdspm_midi_read_byte (hdspm, id);
+}
+
+static int snd_hdspm_midi_output_write (struct hdspm_midi *hmidi)
+{
+	unsigned long flags;
+	int n_pending;
+	int to_write;
+	int i;
+	unsigned char buf[128];
+
+	/* Output is not interrupt driven */
+		
+	spin_lock_irqsave (&hmidi->lock, flags);
+	if (hmidi->output &&
+	    !snd_rawmidi_transmit_empty (hmidi->output)) {
+		n_pending = snd_hdspm_midi_output_possible (hmidi->hdspm,
+							    hmidi->id);
+		if (n_pending > 0) {
+			if (n_pending > (int)sizeof (buf))
+				n_pending = sizeof (buf);
+		
+			to_write = snd_rawmidi_transmit (hmidi->output, buf,
+							 n_pending);
+			if (to_write > 0) {
+				for (i = 0; i < to_write; ++i) 
+					snd_hdspm_midi_write_byte (hmidi->hdspm,
+								   hmidi->id,
+								   buf[i]);
+			}
+		}
+	}
+	spin_unlock_irqrestore (&hmidi->lock, flags);
+	return 0;
+}
+
+static int snd_hdspm_midi_input_read (struct hdspm_midi *hmidi)
+{
+	unsigned char buf[128]; /* this buffer is designed to match the MIDI
+				 * input FIFO size
+				 */
+	unsigned long flags;
+	int n_pending;
+	int i;
+
+	spin_lock_irqsave (&hmidi->lock, flags);
+	n_pending = snd_hdspm_midi_input_available (hmidi->hdspm, hmidi->id);
+	if (n_pending > 0) {
+		if (hmidi->input) {
+			if (n_pending > (int)sizeof (buf))
+				n_pending = sizeof (buf);
+			for (i = 0; i < n_pending; ++i)
+				buf[i] = snd_hdspm_midi_read_byte (hmidi->hdspm,
+								   hmidi->id);
+			if (n_pending)
+				snd_rawmidi_receive (hmidi->input, buf,
+						     n_pending);
+		} else {
+			/* flush the MIDI input FIFO */
+			while (n_pending--)
+				snd_hdspm_midi_read_byte (hmidi->hdspm,
+							  hmidi->id);
+		}
+	}
+	hmidi->pending = 0;
+	if (hmidi->id)
+		hmidi->hdspm->control_register |= HDSPM_Midi1InterruptEnable;
+	else
+		hmidi->hdspm->control_register |= HDSPM_Midi0InterruptEnable;
+	hdspm_write(hmidi->hdspm, HDSPM_controlRegister,
+		    hmidi->hdspm->control_register);
+	spin_unlock_irqrestore (&hmidi->lock, flags);
+	return snd_hdspm_midi_output_write (hmidi);
+}
+
+static void
+snd_hdspm_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct hdspm *hdspm;
+	struct hdspm_midi *hmidi;
+	unsigned long flags;
+	u32 ie;
+
+	hmidi = substream->rmidi->private_data;
+	hdspm = hmidi->hdspm;
+	ie = hmidi->id ?
+		HDSPM_Midi1InterruptEnable : HDSPM_Midi0InterruptEnable;
+	spin_lock_irqsave (&hdspm->lock, flags);
+	if (up) {
+		if (!(hdspm->control_register & ie)) {
+			snd_hdspm_flush_midi_input (hdspm, hmidi->id);
+			hdspm->control_register |= ie;
+		}
+	} else {
+		hdspm->control_register &= ~ie;
+	}
+
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+	spin_unlock_irqrestore (&hdspm->lock, flags);
+}
+
+static void snd_hdspm_midi_output_timer(unsigned long data)
+{
+	struct hdspm_midi *hmidi = (struct hdspm_midi *) data;
+	unsigned long flags;
+	
+	snd_hdspm_midi_output_write(hmidi);
+	spin_lock_irqsave (&hmidi->lock, flags);
+
+	/* this does not bump hmidi->istimer, because the
+	   kernel automatically removed the timer when it
+	   expired, and we are now adding it back, thus
+	   leaving istimer wherever it was set before.  
+	*/
+
+	if (hmidi->istimer) {
+		hmidi->timer.expires = 1 + jiffies;
+		add_timer(&hmidi->timer);
+	}
+
+	spin_unlock_irqrestore (&hmidi->lock, flags);
+}
+
+static void
+snd_hdspm_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct hdspm_midi *hmidi;
+	unsigned long flags;
+
+	hmidi = substream->rmidi->private_data;
+	spin_lock_irqsave (&hmidi->lock, flags);
+	if (up) {
+		if (!hmidi->istimer) {
+			init_timer(&hmidi->timer);
+			hmidi->timer.function = snd_hdspm_midi_output_timer;
+			hmidi->timer.data = (unsigned long) hmidi;
+			hmidi->timer.expires = 1 + jiffies;
+			add_timer(&hmidi->timer);
+			hmidi->istimer++;
+		}
+	} else {
+		if (hmidi->istimer && --hmidi->istimer <= 0)
+			del_timer (&hmidi->timer);
+	}
+	spin_unlock_irqrestore (&hmidi->lock, flags);
+	if (up)
+		snd_hdspm_midi_output_write(hmidi);
+}
+
+static int snd_hdspm_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct hdspm_midi *hmidi;
+
+	hmidi = substream->rmidi->private_data;
+	spin_lock_irq (&hmidi->lock);
+	snd_hdspm_flush_midi_input (hmidi->hdspm, hmidi->id);
+	hmidi->input = substream;
+	spin_unlock_irq (&hmidi->lock);
+
+	return 0;
+}
+
+static int snd_hdspm_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct hdspm_midi *hmidi;
+
+	hmidi = substream->rmidi->private_data;
+	spin_lock_irq (&hmidi->lock);
+	hmidi->output = substream;
+	spin_unlock_irq (&hmidi->lock);
+
+	return 0;
+}
+
+static int snd_hdspm_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct hdspm_midi *hmidi;
+
+	snd_hdspm_midi_input_trigger (substream, 0);
+
+	hmidi = substream->rmidi->private_data;
+	spin_lock_irq (&hmidi->lock);
+	hmidi->input = NULL;
+	spin_unlock_irq (&hmidi->lock);
+
+	return 0;
+}
+
+static int snd_hdspm_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct hdspm_midi *hmidi;
+
+	snd_hdspm_midi_output_trigger (substream, 0);
+
+	hmidi = substream->rmidi->private_data;
+	spin_lock_irq (&hmidi->lock);
+	hmidi->output = NULL;
+	spin_unlock_irq (&hmidi->lock);
+
+	return 0;
+}
+
+static struct snd_rawmidi_ops snd_hdspm_midi_output =
+{
+	.open =		snd_hdspm_midi_output_open,
+	.close =	snd_hdspm_midi_output_close,
+	.trigger =	snd_hdspm_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_hdspm_midi_input =
+{
+	.open =		snd_hdspm_midi_input_open,
+	.close =	snd_hdspm_midi_input_close,
+	.trigger =	snd_hdspm_midi_input_trigger,
+};
+
+static int __devinit snd_hdspm_create_midi (struct snd_card *card,
+					    struct hdspm *hdspm, int id)
+{
+	int err;
+	char buf[32];
+
+	hdspm->midi[id].id = id;
+	hdspm->midi[id].hdspm = hdspm;
+	spin_lock_init (&hdspm->midi[id].lock);
+
+	sprintf (buf, "%s MIDI %d", card->shortname, id+1);
+	err = snd_rawmidi_new (card, buf, id, 1, 1, &hdspm->midi[id].rmidi);
+	if (err < 0)
+		return err;
+
+	sprintf(hdspm->midi[id].rmidi->name, "HDSPM MIDI %d", id+1);
+	hdspm->midi[id].rmidi->private_data = &hdspm->midi[id];
+
+	snd_rawmidi_set_ops(hdspm->midi[id].rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+			    &snd_hdspm_midi_output);
+	snd_rawmidi_set_ops(hdspm->midi[id].rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+			    &snd_hdspm_midi_input);
+
+	hdspm->midi[id].rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+		SNDRV_RAWMIDI_INFO_INPUT |
+		SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	return 0;
+}
+
+
+static void hdspm_midi_tasklet(unsigned long arg)
+{
+	struct hdspm *hdspm = (struct hdspm *)arg;
+	
+	if (hdspm->midi[0].pending)
+		snd_hdspm_midi_input_read (&hdspm->midi[0]);
+	if (hdspm->midi[1].pending)
+		snd_hdspm_midi_input_read (&hdspm->midi[1]);
+} 
+
+
+/*-----------------------------------------------------------------------------
+  Status Interface
+  ----------------------------------------------------------------------------*/
+
+/* get the system sample rate which is set */
+
+#define HDSPM_SYSTEM_SAMPLE_RATE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ, \
+  .info = snd_hdspm_info_system_sample_rate, \
+  .get = snd_hdspm_get_system_sample_rate \
+}
+
+static int snd_hdspm_info_system_sample_rate(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_hdspm_get_system_sample_rate(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *
+					    ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdspm->system_sample_rate;
+	return 0;
+}
+
+#define HDSPM_AUTOSYNC_SAMPLE_RATE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ, \
+  .info = snd_hdspm_info_autosync_sample_rate, \
+  .get = snd_hdspm_get_autosync_sample_rate \
+}
+
+static int snd_hdspm_info_autosync_sample_rate(struct snd_kcontrol *kcontrol,
+					       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "32000", "44100", "48000",
+		"64000", "88200", "96000",
+		"128000", "176400", "192000",
+		"None"
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 10;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+		    uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdspm_get_autosync_sample_rate(struct snd_kcontrol *kcontrol,
+					      struct snd_ctl_elem_value *
+					      ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	switch (hdspm_external_sample_rate(hdspm)) {
+	case 32000:
+		ucontrol->value.enumerated.item[0] = 0;
+		break;
+	case 44100:
+		ucontrol->value.enumerated.item[0] = 1;
+		break;
+	case 48000:
+		ucontrol->value.enumerated.item[0] = 2;
+		break;
+	case 64000:
+		ucontrol->value.enumerated.item[0] = 3;
+		break;
+	case 88200:
+		ucontrol->value.enumerated.item[0] = 4;
+		break;
+	case 96000:
+		ucontrol->value.enumerated.item[0] = 5;
+		break;
+	case 128000:
+		ucontrol->value.enumerated.item[0] = 6;
+		break;
+	case 176400:
+		ucontrol->value.enumerated.item[0] = 7;
+		break;
+	case 192000:
+		ucontrol->value.enumerated.item[0] = 8;
+		break;
+
+	default:
+		ucontrol->value.enumerated.item[0] = 9;
+	}
+	return 0;
+}
+
+#define HDSPM_SYSTEM_CLOCK_MODE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ, \
+  .info = snd_hdspm_info_system_clock_mode, \
+  .get = snd_hdspm_get_system_clock_mode, \
+}
+
+
+
+static int hdspm_system_clock_mode(struct hdspm * hdspm)
+{
+        /* Always reflect the hardware info, rme is never wrong !!!! */
+
+	if (hdspm->control_register & HDSPM_ClockModeMaster)
+		return 0;
+	return 1;
+}
+
+static int snd_hdspm_info_system_clock_mode(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "Master", "Slave" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+		    uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_hdspm_get_system_clock_mode(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] =
+	    hdspm_system_clock_mode(hdspm);
+	return 0;
+}
+
+#define HDSPM_CLOCK_SOURCE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_clock_source, \
+  .get = snd_hdspm_get_clock_source, \
+  .put = snd_hdspm_put_clock_source \
+}
+
+static int hdspm_clock_source(struct hdspm * hdspm)
+{
+	if (hdspm->control_register & HDSPM_ClockModeMaster) {
+		switch (hdspm->system_sample_rate) {
+		case 32000:
+			return 1;
+		case 44100:
+			return 2;
+		case 48000:
+			return 3;
+		case 64000:
+			return 4;
+		case 88200:
+			return 5;
+		case 96000:
+			return 6;
+		case 128000:
+			return 7;
+		case 176400:
+			return 8;
+		case 192000:
+			return 9;
+		default:
+			return 3;
+		}
+	} else {
+		return 0;
+	}
+}
+
+static int hdspm_set_clock_source(struct hdspm * hdspm, int mode)
+{
+	int rate;
+	switch (mode) {
+
+	case HDSPM_CLOCK_SOURCE_AUTOSYNC:
+		if (hdspm_external_sample_rate(hdspm) != 0) {
+			hdspm->control_register &= ~HDSPM_ClockModeMaster;
+			hdspm_write(hdspm, HDSPM_controlRegister,
+				    hdspm->control_register);
+			return 0;
+		}
+		return -1;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_32KHZ:
+		rate = 32000;
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_44_1KHZ:
+		rate = 44100;
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_48KHZ:
+		rate = 48000;
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_64KHZ:
+		rate = 64000;
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_88_2KHZ:
+		rate = 88200;
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_96KHZ:
+		rate = 96000;
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_128KHZ:
+		rate = 128000;
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_176_4KHZ:
+		rate = 176400;
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_192KHZ:
+		rate = 192000;
+		break;
+
+	default:
+		rate = 44100;
+	}
+	hdspm->control_register |= HDSPM_ClockModeMaster;
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+	hdspm_set_rate(hdspm, rate, 1);
+	return 0;
+}
+
+static int snd_hdspm_info_clock_source(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "AutoSync",
+		"Internal 32.0 kHz", "Internal 44.1 kHz",
+		    "Internal 48.0 kHz",
+		"Internal 64.0 kHz", "Internal 88.2 kHz",
+		    "Internal 96.0 kHz",
+		"Internal 128.0 kHz", "Internal 176.4 kHz",
+		    "Internal 192.0 kHz"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 10;
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+		    uinfo->value.enumerated.items - 1;
+
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int snd_hdspm_get_clock_source(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdspm_clock_source(hdspm);
+	return 0;
+}
+
+static int snd_hdspm_put_clock_source(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	int val;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+	val = ucontrol->value.enumerated.item[0];
+	if (val < 0)
+		val = 0;
+	if (val > 9)
+		val = 9;
+	spin_lock_irq(&hdspm->lock);
+	if (val != hdspm_clock_source(hdspm))
+		change = (hdspm_set_clock_source(hdspm, val) == 0) ? 1 : 0;
+	else
+		change = 0;
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_PREF_SYNC_REF(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_pref_sync_ref, \
+  .get = snd_hdspm_get_pref_sync_ref, \
+  .put = snd_hdspm_put_pref_sync_ref \
+}
+
+static int hdspm_pref_sync_ref(struct hdspm * hdspm)
+{
+	/* Notice that this looks at the requested sync source,
+	   not the one actually in use.
+	 */
+	if (hdspm->is_aes32) {
+		switch (hdspm->control_register & HDSPM_SyncRefMask) {
+		/* number gives AES index, except for 0 which
+		   corresponds to WordClock */
+		case 0: return 0;
+		case HDSPM_SyncRef0: return 1;
+		case HDSPM_SyncRef1: return 2;
+		case HDSPM_SyncRef1+HDSPM_SyncRef0: return 3;
+		case HDSPM_SyncRef2: return 4;
+		case HDSPM_SyncRef2+HDSPM_SyncRef0: return 5;
+		case HDSPM_SyncRef2+HDSPM_SyncRef1: return 6;
+		case HDSPM_SyncRef2+HDSPM_SyncRef1+HDSPM_SyncRef0: return 7;
+		case HDSPM_SyncRef3: return 8;
+		}
+	} else {
+		switch (hdspm->control_register & HDSPM_SyncRefMask) {
+		case HDSPM_SyncRef_Word:
+			return HDSPM_SYNC_FROM_WORD;
+		case HDSPM_SyncRef_MADI:
+			return HDSPM_SYNC_FROM_MADI;
+		}
+	}
+
+	return HDSPM_SYNC_FROM_WORD;
+}
+
+static int hdspm_set_pref_sync_ref(struct hdspm * hdspm, int pref)
+{
+	hdspm->control_register &= ~HDSPM_SyncRefMask;
+
+	if (hdspm->is_aes32) {
+		switch (pref) {
+		case 0:
+		       hdspm->control_register |= 0;
+		       break;
+		case 1:
+		       hdspm->control_register |= HDSPM_SyncRef0;
+		       break;
+		case 2:
+		       hdspm->control_register |= HDSPM_SyncRef1;
+		       break;
+		case 3:
+		       hdspm->control_register |= HDSPM_SyncRef1+HDSPM_SyncRef0;
+		       break;
+		case 4:
+		       hdspm->control_register |= HDSPM_SyncRef2;
+		       break;
+		case 5:
+		       hdspm->control_register |= HDSPM_SyncRef2+HDSPM_SyncRef0;
+		       break;
+		case 6:
+		       hdspm->control_register |= HDSPM_SyncRef2+HDSPM_SyncRef1;
+		       break;
+		case 7:
+		       hdspm->control_register |=
+			       HDSPM_SyncRef2+HDSPM_SyncRef1+HDSPM_SyncRef0;
+		       break;
+		case 8:
+		       hdspm->control_register |= HDSPM_SyncRef3;
+		       break;
+		default:
+		       return -1;
+		}
+	} else {
+		switch (pref) {
+		case HDSPM_SYNC_FROM_MADI:
+			hdspm->control_register |= HDSPM_SyncRef_MADI;
+			break;
+		case HDSPM_SYNC_FROM_WORD:
+			hdspm->control_register |= HDSPM_SyncRef_Word;
+			break;
+		default:
+			return -1;
+		}
+	}
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+	return 0;
+}
+
+static int snd_hdspm_info_pref_sync_ref(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	if (hdspm->is_aes32) {
+		static char *texts[] = { "Word", "AES1", "AES2", "AES3",
+			"AES4", "AES5",	"AES6", "AES7", "AES8" };
+
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+		uinfo->count = 1;
+
+		uinfo->value.enumerated.items = 9;
+
+		if (uinfo->value.enumerated.item >=
+		    uinfo->value.enumerated.items)
+			uinfo->value.enumerated.item =
+				uinfo->value.enumerated.items - 1;
+		strcpy(uinfo->value.enumerated.name,
+				texts[uinfo->value.enumerated.item]);
+	} else {
+		static char *texts[] = { "Word", "MADI" };
+
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+		uinfo->count = 1;
+
+		uinfo->value.enumerated.items = 2;
+
+		if (uinfo->value.enumerated.item >=
+		    uinfo->value.enumerated.items)
+			uinfo->value.enumerated.item =
+				uinfo->value.enumerated.items - 1;
+		strcpy(uinfo->value.enumerated.name,
+				texts[uinfo->value.enumerated.item]);
+	}
+	return 0;
+}
+
+static int snd_hdspm_get_pref_sync_ref(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdspm_pref_sync_ref(hdspm);
+	return 0;
+}
+
+static int snd_hdspm_put_pref_sync_ref(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change, max;
+	unsigned int val;
+
+	max = hdspm->is_aes32 ? 9 : 2;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+
+	val = ucontrol->value.enumerated.item[0] % max;
+
+	spin_lock_irq(&hdspm->lock);
+	change = (int) val != hdspm_pref_sync_ref(hdspm);
+	hdspm_set_pref_sync_ref(hdspm, val);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_AUTOSYNC_REF(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ, \
+  .info = snd_hdspm_info_autosync_ref, \
+  .get = snd_hdspm_get_autosync_ref, \
+}
+
+static int hdspm_autosync_ref(struct hdspm * hdspm)
+{
+	if (hdspm->is_aes32) {
+		unsigned int status = hdspm_read(hdspm, HDSPM_statusRegister);
+		unsigned int syncref = (status >> HDSPM_AES32_syncref_bit) &
+			0xF;
+		if (syncref == 0)
+			return HDSPM_AES32_AUTOSYNC_FROM_WORD;
+		if (syncref <= 8)
+			return syncref;
+		return HDSPM_AES32_AUTOSYNC_FROM_NONE;
+	} else {
+		/* This looks at the autosync selected sync reference */
+		unsigned int status2 = hdspm_read(hdspm, HDSPM_statusRegister2);
+
+		switch (status2 & HDSPM_SelSyncRefMask) {
+		case HDSPM_SelSyncRef_WORD:
+			return HDSPM_AUTOSYNC_FROM_WORD;
+		case HDSPM_SelSyncRef_MADI:
+			return HDSPM_AUTOSYNC_FROM_MADI;
+		case HDSPM_SelSyncRef_NVALID:
+			return HDSPM_AUTOSYNC_FROM_NONE;
+		default:
+			return 0;
+		}
+
+		return 0;
+	}
+}
+
+static int snd_hdspm_info_autosync_ref(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	if (hdspm->is_aes32) {
+		static char *texts[] = { "WordClock", "AES1", "AES2", "AES3",
+			"AES4",	"AES5", "AES6", "AES7", "AES8", "None"};
+
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+		uinfo->count = 1;
+		uinfo->value.enumerated.items = 10;
+		if (uinfo->value.enumerated.item >=
+		    uinfo->value.enumerated.items)
+			uinfo->value.enumerated.item =
+				uinfo->value.enumerated.items - 1;
+		strcpy(uinfo->value.enumerated.name,
+				texts[uinfo->value.enumerated.item]);
+	} else {
+		static char *texts[] = { "WordClock", "MADI", "None" };
+
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+		uinfo->count = 1;
+		uinfo->value.enumerated.items = 3;
+		if (uinfo->value.enumerated.item >=
+		    uinfo->value.enumerated.items)
+			uinfo->value.enumerated.item =
+				uinfo->value.enumerated.items - 1;
+		strcpy(uinfo->value.enumerated.name,
+				texts[uinfo->value.enumerated.item]);
+	}
+	return 0;
+}
+
+static int snd_hdspm_get_autosync_ref(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdspm_autosync_ref(hdspm);
+	return 0;
+}
+
+#define HDSPM_LINE_OUT(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_line_out, \
+  .get = snd_hdspm_get_line_out, \
+  .put = snd_hdspm_put_line_out \
+}
+
+static int hdspm_line_out(struct hdspm * hdspm)
+{
+	return (hdspm->control_register & HDSPM_LineOut) ? 1 : 0;
+}
+
+
+static int hdspm_set_line_output(struct hdspm * hdspm, int out)
+{
+	if (out)
+		hdspm->control_register |= HDSPM_LineOut;
+	else
+		hdspm->control_register &= ~HDSPM_LineOut;
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+	return 0;
+}
+
+#define snd_hdspm_info_line_out		snd_ctl_boolean_mono_info
+
+static int snd_hdspm_get_line_out(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdspm->lock);
+	ucontrol->value.integer.value[0] = hdspm_line_out(hdspm);
+	spin_unlock_irq(&hdspm->lock);
+	return 0;
+}
+
+static int snd_hdspm_put_line_out(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdspm->lock);
+	change = (int) val != hdspm_line_out(hdspm);
+	hdspm_set_line_output(hdspm, val);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_TX_64(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_tx_64, \
+  .get = snd_hdspm_get_tx_64, \
+  .put = snd_hdspm_put_tx_64 \
+}
+
+static int hdspm_tx_64(struct hdspm * hdspm)
+{
+	return (hdspm->control_register & HDSPM_TX_64ch) ? 1 : 0;
+}
+
+static int hdspm_set_tx_64(struct hdspm * hdspm, int out)
+{
+	if (out)
+		hdspm->control_register |= HDSPM_TX_64ch;
+	else
+		hdspm->control_register &= ~HDSPM_TX_64ch;
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+	return 0;
+}
+
+#define snd_hdspm_info_tx_64		snd_ctl_boolean_mono_info
+
+static int snd_hdspm_get_tx_64(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdspm->lock);
+	ucontrol->value.integer.value[0] = hdspm_tx_64(hdspm);
+	spin_unlock_irq(&hdspm->lock);
+	return 0;
+}
+
+static int snd_hdspm_put_tx_64(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdspm->lock);
+	change = (int) val != hdspm_tx_64(hdspm);
+	hdspm_set_tx_64(hdspm, val);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_C_TMS(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_c_tms, \
+  .get = snd_hdspm_get_c_tms, \
+  .put = snd_hdspm_put_c_tms \
+}
+
+static int hdspm_c_tms(struct hdspm * hdspm)
+{
+	return (hdspm->control_register & HDSPM_clr_tms) ? 1 : 0;
+}
+
+static int hdspm_set_c_tms(struct hdspm * hdspm, int out)
+{
+	if (out)
+		hdspm->control_register |= HDSPM_clr_tms;
+	else
+		hdspm->control_register &= ~HDSPM_clr_tms;
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+	return 0;
+}
+
+#define snd_hdspm_info_c_tms		snd_ctl_boolean_mono_info
+
+static int snd_hdspm_get_c_tms(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdspm->lock);
+	ucontrol->value.integer.value[0] = hdspm_c_tms(hdspm);
+	spin_unlock_irq(&hdspm->lock);
+	return 0;
+}
+
+static int snd_hdspm_put_c_tms(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdspm->lock);
+	change = (int) val != hdspm_c_tms(hdspm);
+	hdspm_set_c_tms(hdspm, val);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_SAFE_MODE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_safe_mode, \
+  .get = snd_hdspm_get_safe_mode, \
+  .put = snd_hdspm_put_safe_mode \
+}
+
+static int hdspm_safe_mode(struct hdspm * hdspm)
+{
+	return (hdspm->control_register & HDSPM_AutoInp) ? 1 : 0;
+}
+
+static int hdspm_set_safe_mode(struct hdspm * hdspm, int out)
+{
+	if (out)
+		hdspm->control_register |= HDSPM_AutoInp;
+	else
+		hdspm->control_register &= ~HDSPM_AutoInp;
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+	return 0;
+}
+
+#define snd_hdspm_info_safe_mode	snd_ctl_boolean_mono_info
+
+static int snd_hdspm_get_safe_mode(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdspm->lock);
+	ucontrol->value.integer.value[0] = hdspm_safe_mode(hdspm);
+	spin_unlock_irq(&hdspm->lock);
+	return 0;
+}
+
+static int snd_hdspm_put_safe_mode(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdspm->lock);
+	change = (int) val != hdspm_safe_mode(hdspm);
+	hdspm_set_safe_mode(hdspm, val);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_EMPHASIS(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_emphasis, \
+  .get = snd_hdspm_get_emphasis, \
+  .put = snd_hdspm_put_emphasis \
+}
+
+static int hdspm_emphasis(struct hdspm * hdspm)
+{
+	return (hdspm->control_register & HDSPM_Emphasis) ? 1 : 0;
+}
+
+static int hdspm_set_emphasis(struct hdspm * hdspm, int emp)
+{
+	if (emp)
+		hdspm->control_register |= HDSPM_Emphasis;
+	else
+		hdspm->control_register &= ~HDSPM_Emphasis;
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+	return 0;
+}
+
+#define snd_hdspm_info_emphasis		snd_ctl_boolean_mono_info
+
+static int snd_hdspm_get_emphasis(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdspm->lock);
+	ucontrol->value.enumerated.item[0] = hdspm_emphasis(hdspm);
+	spin_unlock_irq(&hdspm->lock);
+	return 0;
+}
+
+static int snd_hdspm_put_emphasis(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdspm->lock);
+	change = (int) val != hdspm_emphasis(hdspm);
+	hdspm_set_emphasis(hdspm, val);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_DOLBY(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_dolby, \
+  .get = snd_hdspm_get_dolby, \
+  .put = snd_hdspm_put_dolby \
+}
+
+static int hdspm_dolby(struct hdspm * hdspm)
+{
+	return (hdspm->control_register & HDSPM_Dolby) ? 1 : 0;
+}
+
+static int hdspm_set_dolby(struct hdspm * hdspm, int dol)
+{
+	if (dol)
+		hdspm->control_register |= HDSPM_Dolby;
+	else
+		hdspm->control_register &= ~HDSPM_Dolby;
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+	return 0;
+}
+
+#define snd_hdspm_info_dolby		snd_ctl_boolean_mono_info
+
+static int snd_hdspm_get_dolby(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdspm->lock);
+	ucontrol->value.enumerated.item[0] = hdspm_dolby(hdspm);
+	spin_unlock_irq(&hdspm->lock);
+	return 0;
+}
+
+static int snd_hdspm_put_dolby(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdspm->lock);
+	change = (int) val != hdspm_dolby(hdspm);
+	hdspm_set_dolby(hdspm, val);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_PROFESSIONAL(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_professional, \
+  .get = snd_hdspm_get_professional, \
+  .put = snd_hdspm_put_professional \
+}
+
+static int hdspm_professional(struct hdspm * hdspm)
+{
+	return (hdspm->control_register & HDSPM_Professional) ? 1 : 0;
+}
+
+static int hdspm_set_professional(struct hdspm * hdspm, int dol)
+{
+	if (dol)
+		hdspm->control_register |= HDSPM_Professional;
+	else
+		hdspm->control_register &= ~HDSPM_Professional;
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+	return 0;
+}
+
+#define snd_hdspm_info_professional	snd_ctl_boolean_mono_info
+
+static int snd_hdspm_get_professional(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdspm->lock);
+	ucontrol->value.enumerated.item[0] = hdspm_professional(hdspm);
+	spin_unlock_irq(&hdspm->lock);
+	return 0;
+}
+
+static int snd_hdspm_put_professional(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdspm->lock);
+	change = (int) val != hdspm_professional(hdspm);
+	hdspm_set_professional(hdspm, val);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_INPUT_SELECT(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_input_select, \
+  .get = snd_hdspm_get_input_select, \
+  .put = snd_hdspm_put_input_select \
+}
+
+static int hdspm_input_select(struct hdspm * hdspm)
+{
+	return (hdspm->control_register & HDSPM_InputSelect0) ? 1 : 0;
+}
+
+static int hdspm_set_input_select(struct hdspm * hdspm, int out)
+{
+	if (out)
+		hdspm->control_register |= HDSPM_InputSelect0;
+	else
+		hdspm->control_register &= ~HDSPM_InputSelect0;
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+	return 0;
+}
+
+static int snd_hdspm_info_input_select(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "optical", "coaxial" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+		    uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int snd_hdspm_get_input_select(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdspm->lock);
+	ucontrol->value.enumerated.item[0] = hdspm_input_select(hdspm);
+	spin_unlock_irq(&hdspm->lock);
+	return 0;
+}
+
+static int snd_hdspm_put_input_select(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdspm->lock);
+	change = (int) val != hdspm_input_select(hdspm);
+	hdspm_set_input_select(hdspm, val);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_DS_WIRE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_ds_wire, \
+  .get = snd_hdspm_get_ds_wire, \
+  .put = snd_hdspm_put_ds_wire \
+}
+
+static int hdspm_ds_wire(struct hdspm * hdspm)
+{
+	return (hdspm->control_register & HDSPM_DS_DoubleWire) ? 1 : 0;
+}
+
+static int hdspm_set_ds_wire(struct hdspm * hdspm, int ds)
+{
+	if (ds)
+		hdspm->control_register |= HDSPM_DS_DoubleWire;
+	else
+		hdspm->control_register &= ~HDSPM_DS_DoubleWire;
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+	return 0;
+}
+
+static int snd_hdspm_info_ds_wire(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "Single", "Double" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+		    uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int snd_hdspm_get_ds_wire(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdspm->lock);
+	ucontrol->value.enumerated.item[0] = hdspm_ds_wire(hdspm);
+	spin_unlock_irq(&hdspm->lock);
+	return 0;
+}
+
+static int snd_hdspm_put_ds_wire(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&hdspm->lock);
+	change = (int) val != hdspm_ds_wire(hdspm);
+	hdspm_set_ds_wire(hdspm, val);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_QS_WIRE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .info = snd_hdspm_info_qs_wire, \
+  .get = snd_hdspm_get_qs_wire, \
+  .put = snd_hdspm_put_qs_wire \
+}
+
+static int hdspm_qs_wire(struct hdspm * hdspm)
+{
+	if (hdspm->control_register & HDSPM_QS_DoubleWire)
+		return 1;
+	if (hdspm->control_register & HDSPM_QS_QuadWire)
+		return 2;
+	return 0;
+}
+
+static int hdspm_set_qs_wire(struct hdspm * hdspm, int mode)
+{
+	hdspm->control_register &= ~(HDSPM_QS_DoubleWire | HDSPM_QS_QuadWire);
+	switch (mode) {
+	case 0:
+		break;
+	case 1:
+		hdspm->control_register |= HDSPM_QS_DoubleWire;
+		break;
+	case 2:
+		hdspm->control_register |= HDSPM_QS_QuadWire;
+		break;
+	}
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+	return 0;
+}
+
+static int snd_hdspm_info_qs_wire(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "Single", "Double", "Quad" };
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+		    uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int snd_hdspm_get_qs_wire(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&hdspm->lock);
+	ucontrol->value.enumerated.item[0] = hdspm_qs_wire(hdspm);
+	spin_unlock_irq(&hdspm->lock);
+	return 0;
+}
+
+static int snd_hdspm_put_qs_wire(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	int val;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0];
+	if (val < 0)
+		val = 0;
+	if (val > 2)
+		val = 2;
+	spin_lock_irq(&hdspm->lock);
+	change = val != hdspm_qs_wire(hdspm);
+	hdspm_set_qs_wire(hdspm, val);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+/*           Simple Mixer
+  deprecated since to much faders ???
+  MIXER interface says output (source, destination, value)
+   where source > MAX_channels are playback channels 
+   on MADICARD 
+  - playback mixer matrix: [channelout+64] [output] [value]
+  - input(thru) mixer matrix: [channelin] [output] [value]
+  (better do 2 kontrols for separation ?)
+*/
+
+#define HDSPM_MIXER(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
+  .name = xname, \
+  .index = xindex, \
+  .device = 0, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+		 SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_hdspm_info_mixer, \
+  .get = snd_hdspm_get_mixer, \
+  .put = snd_hdspm_put_mixer \
+}
+
+static int snd_hdspm_info_mixer(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 3;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 65535;
+	uinfo->value.integer.step = 1;
+	return 0;
+}
+
+static int snd_hdspm_get_mixer(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int source;
+	int destination;
+
+	source = ucontrol->value.integer.value[0];
+	if (source < 0)
+		source = 0;
+	else if (source >= 2 * HDSPM_MAX_CHANNELS)
+		source = 2 * HDSPM_MAX_CHANNELS - 1;
+
+	destination = ucontrol->value.integer.value[1];
+	if (destination < 0)
+		destination = 0;
+	else if (destination >= HDSPM_MAX_CHANNELS)
+		destination = HDSPM_MAX_CHANNELS - 1;
+
+	spin_lock_irq(&hdspm->lock);
+	if (source >= HDSPM_MAX_CHANNELS)
+		ucontrol->value.integer.value[2] =
+		    hdspm_read_pb_gain(hdspm, destination,
+				       source - HDSPM_MAX_CHANNELS);
+	else
+		ucontrol->value.integer.value[2] =
+		    hdspm_read_in_gain(hdspm, destination, source);
+
+	spin_unlock_irq(&hdspm->lock);
+
+	return 0;
+}
+
+static int snd_hdspm_put_mixer(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	int source;
+	int destination;
+	int gain;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+
+	source = ucontrol->value.integer.value[0];
+	destination = ucontrol->value.integer.value[1];
+
+	if (source < 0 || source >= 2 * HDSPM_MAX_CHANNELS)
+		return -1;
+	if (destination < 0 || destination >= HDSPM_MAX_CHANNELS)
+		return -1;
+
+	gain = ucontrol->value.integer.value[2];
+
+	spin_lock_irq(&hdspm->lock);
+
+	if (source >= HDSPM_MAX_CHANNELS)
+		change = gain != hdspm_read_pb_gain(hdspm, destination,
+						    source -
+						    HDSPM_MAX_CHANNELS);
+	else
+		change = gain != hdspm_read_in_gain(hdspm, destination,
+						    source);
+
+	if (change) {
+		if (source >= HDSPM_MAX_CHANNELS)
+			hdspm_write_pb_gain(hdspm, destination,
+					    source - HDSPM_MAX_CHANNELS,
+					    gain);
+		else
+			hdspm_write_in_gain(hdspm, destination, source,
+					    gain);
+	}
+	spin_unlock_irq(&hdspm->lock);
+
+	return change;
+}
+
+/* The simple mixer control(s) provide gain control for the
+   basic 1:1 mappings of playback streams to output
+   streams. 
+*/
+
+#define HDSPM_PLAYBACK_MIXER \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE | \
+		 SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_hdspm_info_playback_mixer, \
+  .get = snd_hdspm_get_playback_mixer, \
+  .put = snd_hdspm_put_playback_mixer \
+}
+
+static int snd_hdspm_info_playback_mixer(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 65536;
+	uinfo->value.integer.step = 1;
+	return 0;
+}
+
+static int snd_hdspm_get_playback_mixer(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int channel;
+	int mapped_channel;
+
+	channel = ucontrol->id.index - 1;
+
+	if (snd_BUG_ON(channel < 0 || channel >= HDSPM_MAX_CHANNELS))
+		return -EINVAL;
+
+	mapped_channel = hdspm->channel_map[channel];
+	if (mapped_channel < 0)
+		return -EINVAL;
+
+	spin_lock_irq(&hdspm->lock);
+	ucontrol->value.integer.value[0] =
+	    hdspm_read_pb_gain(hdspm, mapped_channel, mapped_channel);
+	spin_unlock_irq(&hdspm->lock);
+
+	/*
+	snd_printdd("get pb mixer index %d, channel %d, mapped_channel %d, "
+		    "value %d\n",
+		    ucontrol->id.index, channel, mapped_channel,
+		    ucontrol->value.integer.value[0]); 
+	*/
+	return 0;
+}
+
+static int snd_hdspm_put_playback_mixer(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+	int change;
+	int channel;
+	int mapped_channel;
+	int gain;
+
+	if (!snd_hdspm_use_is_exclusive(hdspm))
+		return -EBUSY;
+
+	channel = ucontrol->id.index - 1;
+
+	if (snd_BUG_ON(channel < 0 || channel >= HDSPM_MAX_CHANNELS))
+		return -EINVAL;
+
+	mapped_channel = hdspm->channel_map[channel];
+	if (mapped_channel < 0)
+		return -EINVAL;
+
+	gain = ucontrol->value.integer.value[0];
+
+	spin_lock_irq(&hdspm->lock);
+	change =
+	    gain != hdspm_read_pb_gain(hdspm, mapped_channel,
+				       mapped_channel);
+	if (change)
+		hdspm_write_pb_gain(hdspm, mapped_channel, mapped_channel,
+				    gain);
+	spin_unlock_irq(&hdspm->lock);
+	return change;
+}
+
+#define HDSPM_WC_SYNC_CHECK(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_hdspm_info_sync_check, \
+  .get = snd_hdspm_get_wc_sync_check \
+}
+
+static int snd_hdspm_info_sync_check(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[] = { "No Lock", "Lock", "Sync" };
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item =
+		    uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int hdspm_wc_sync_check(struct hdspm * hdspm)
+{
+	if (hdspm->is_aes32) {
+		int status = hdspm_read(hdspm, HDSPM_statusRegister);
+		if (status & HDSPM_AES32_wcLock) {
+			/* I don't know how to differenciate sync from lock.
+			   Doing as if sync for now */
+			return 2;
+		}
+		return 0;
+	} else {
+		int status2 = hdspm_read(hdspm, HDSPM_statusRegister2);
+		if (status2 & HDSPM_wcLock) {
+			if (status2 & HDSPM_wcSync)
+				return 2;
+			else
+				return 1;
+		}
+		return 0;
+	}
+}
+
+static int snd_hdspm_get_wc_sync_check(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = hdspm_wc_sync_check(hdspm);
+	return 0;
+}
+
+
+#define HDSPM_MADI_SYNC_CHECK(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_hdspm_info_sync_check, \
+  .get = snd_hdspm_get_madisync_sync_check \
+}
+
+static int hdspm_madisync_sync_check(struct hdspm * hdspm)
+{
+	int status = hdspm_read(hdspm, HDSPM_statusRegister);
+	if (status & HDSPM_madiLock) {
+		if (status & HDSPM_madiSync)
+			return 2;
+		else
+			return 1;
+	}
+	return 0;
+}
+
+static int snd_hdspm_get_madisync_sync_check(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_value *
+					     ucontrol)
+{
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] =
+	    hdspm_madisync_sync_check(hdspm);
+	return 0;
+}
+
+
+#define HDSPM_AES_SYNC_CHECK(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+  .name = xname, \
+  .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_hdspm_info_sync_check, \
+  .get = snd_hdspm_get_aes_sync_check \
+}
+
+static int hdspm_aes_sync_check(struct hdspm * hdspm, int idx)
+{
+	int status2 = hdspm_read(hdspm, HDSPM_statusRegister2);
+	if (status2 & (HDSPM_LockAES >> idx)) {
+		/* I don't know how to differenciate sync from lock.
+		   Doing as if sync for now */
+		return 2;
+	}
+	return 0;
+}
+
+static int snd_hdspm_get_aes_sync_check(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	int offset;
+	struct hdspm *hdspm = snd_kcontrol_chip(kcontrol);
+
+	offset = ucontrol->id.index - 1;
+	if (offset < 0 || offset >= 8)
+		return -EINVAL;
+
+	ucontrol->value.enumerated.item[0] =
+		hdspm_aes_sync_check(hdspm, offset);
+	return 0;
+}
+
+
+static struct snd_kcontrol_new snd_hdspm_controls_madi[] = {
+
+	HDSPM_MIXER("Mixer", 0),
+/* 'Sample Clock Source' complies with the alsa control naming scheme */
+	HDSPM_CLOCK_SOURCE("Sample Clock Source", 0),
+
+	HDSPM_SYSTEM_CLOCK_MODE("System Clock Mode", 0),
+	HDSPM_PREF_SYNC_REF("Preferred Sync Reference", 0),
+	HDSPM_AUTOSYNC_REF("AutoSync Reference", 0),
+	HDSPM_SYSTEM_SAMPLE_RATE("System Sample Rate", 0),
+/* 'External Rate' complies with the alsa control naming scheme */
+	HDSPM_AUTOSYNC_SAMPLE_RATE("External Rate", 0),
+	HDSPM_WC_SYNC_CHECK("Word Clock Lock Status", 0),
+	HDSPM_MADI_SYNC_CHECK("MADI Sync Lock Status", 0),
+	HDSPM_LINE_OUT("Line Out", 0),
+	HDSPM_TX_64("TX 64 channels mode", 0),
+	HDSPM_C_TMS("Clear Track Marker", 0),
+	HDSPM_SAFE_MODE("Safe Mode", 0),
+	HDSPM_INPUT_SELECT("Input Select", 0),
+};
+
+static struct snd_kcontrol_new snd_hdspm_controls_aes32[] = {
+
+	HDSPM_MIXER("Mixer", 0),
+/* 'Sample Clock Source' complies with the alsa control naming scheme */
+	HDSPM_CLOCK_SOURCE("Sample Clock Source", 0),
+
+	HDSPM_SYSTEM_CLOCK_MODE("System Clock Mode", 0),
+	HDSPM_PREF_SYNC_REF("Preferred Sync Reference", 0),
+	HDSPM_AUTOSYNC_REF("AutoSync Reference", 0),
+	HDSPM_SYSTEM_SAMPLE_RATE("System Sample Rate", 0),
+/* 'External Rate' complies with the alsa control naming scheme */
+	HDSPM_AUTOSYNC_SAMPLE_RATE("External Rate", 0),
+	HDSPM_WC_SYNC_CHECK("Word Clock Lock Status", 0),
+/*	HDSPM_AES_SYNC_CHECK("AES Lock Status", 0),*/ /* created in snd_hdspm_create_controls() */
+	HDSPM_LINE_OUT("Line Out", 0),
+	HDSPM_EMPHASIS("Emphasis", 0),
+	HDSPM_DOLBY("Non Audio", 0),
+	HDSPM_PROFESSIONAL("Professional", 0),
+	HDSPM_C_TMS("Clear Track Marker", 0),
+	HDSPM_DS_WIRE("Double Speed Wire Mode", 0),
+	HDSPM_QS_WIRE("Quad Speed Wire Mode", 0),
+};
+
+static struct snd_kcontrol_new snd_hdspm_playback_mixer = HDSPM_PLAYBACK_MIXER;
+
+
+static int hdspm_update_simple_mixer_controls(struct hdspm * hdspm)
+{
+	int i;
+
+	for (i = hdspm->ds_channels; i < hdspm->ss_channels; ++i) {
+		if (hdspm->system_sample_rate > 48000) {
+			hdspm->playback_mixer_ctls[i]->vd[0].access =
+			    SNDRV_CTL_ELEM_ACCESS_INACTIVE |
+			    SNDRV_CTL_ELEM_ACCESS_READ |
+			    SNDRV_CTL_ELEM_ACCESS_VOLATILE;
+		} else {
+			hdspm->playback_mixer_ctls[i]->vd[0].access =
+			    SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			    SNDRV_CTL_ELEM_ACCESS_VOLATILE;
+		}
+		snd_ctl_notify(hdspm->card, SNDRV_CTL_EVENT_MASK_VALUE |
+			       SNDRV_CTL_EVENT_MASK_INFO,
+			       &hdspm->playback_mixer_ctls[i]->id);
+	}
+
+	return 0;
+}
+
+
+static int snd_hdspm_create_controls(struct snd_card *card, struct hdspm * hdspm)
+{
+	unsigned int idx, limit;
+	int err;
+	struct snd_kcontrol *kctl;
+
+	/* add control list first */
+	if (hdspm->is_aes32) {
+		struct snd_kcontrol_new aes_sync_ctl =
+			HDSPM_AES_SYNC_CHECK("AES Lock Status", 0);
+
+		for (idx = 0; idx < ARRAY_SIZE(snd_hdspm_controls_aes32);
+		     idx++) {
+			err = snd_ctl_add(card,
+					  snd_ctl_new1(&snd_hdspm_controls_aes32[idx],
+						       hdspm));
+			if (err < 0)
+				return err;
+		}
+		for (idx = 1; idx <= 8; idx++) {
+			aes_sync_ctl.index = idx;
+			err = snd_ctl_add(card,
+					  snd_ctl_new1(&aes_sync_ctl, hdspm));
+			if (err < 0)
+				return err;
+		}
+	} else {
+		for (idx = 0; idx < ARRAY_SIZE(snd_hdspm_controls_madi);
+		     idx++) {
+			err = snd_ctl_add(card,
+					  snd_ctl_new1(&snd_hdspm_controls_madi[idx],
+						       hdspm));
+			if (err < 0)
+				return err;
+		}
+	}
+
+	/* Channel playback mixer as default control 
+	   Note: the whole matrix would be 128*HDSPM_MIXER_CHANNELS Faders,
+	   thats too * big for any alsamixer they are accesible via special
+	   IOCTL on hwdep and the mixer 2dimensional mixer control
+	*/
+
+	snd_hdspm_playback_mixer.name = "Chn";
+	limit = HDSPM_MAX_CHANNELS;
+
+	/* The index values are one greater than the channel ID so that
+	 * alsamixer will display them correctly. We want to use the index
+	 * for fast lookup of the relevant channel, but if we use it at all,
+	 * most ALSA software does the wrong thing with it ...
+	 */
+
+	for (idx = 0; idx < limit; ++idx) {
+		snd_hdspm_playback_mixer.index = idx + 1;
+		kctl = snd_ctl_new1(&snd_hdspm_playback_mixer, hdspm);
+		err = snd_ctl_add(card, kctl);
+		if (err < 0)
+			return err;
+		hdspm->playback_mixer_ctls[idx] = kctl;
+	}
+
+	return 0;
+}
+
+/*------------------------------------------------------------
+   /proc interface 
+ ------------------------------------------------------------*/
+
+static void
+snd_hdspm_proc_read_madi(struct snd_info_entry * entry,
+			 struct snd_info_buffer *buffer)
+{
+	struct hdspm *hdspm = entry->private_data;
+	unsigned int status;
+	unsigned int status2;
+	char *pref_sync_ref;
+	char *autosync_ref;
+	char *system_clock_mode;
+	char *clock_source;
+	char *insel;
+	char *syncref;
+	int x, x2;
+
+	status = hdspm_read(hdspm, HDSPM_statusRegister);
+	status2 = hdspm_read(hdspm, HDSPM_statusRegister2);
+
+	snd_iprintf(buffer, "%s (Card #%d) Rev.%x Status2first3bits: %x\n",
+		    hdspm->card_name, hdspm->card->number + 1,
+		    hdspm->firmware_rev,
+		    (status2 & HDSPM_version0) |
+		    (status2 & HDSPM_version1) | (status2 &
+						  HDSPM_version2));
+
+	snd_iprintf(buffer, "IRQ: %d Registers bus: 0x%lx VM: 0x%lx\n",
+		    hdspm->irq, hdspm->port, (unsigned long)hdspm->iobase);
+
+	snd_iprintf(buffer, "--- System ---\n");
+
+	snd_iprintf(buffer,
+		    "IRQ Pending: Audio=%d, MIDI0=%d, MIDI1=%d, IRQcount=%d\n",
+		    status & HDSPM_audioIRQPending,
+		    (status & HDSPM_midi0IRQPending) ? 1 : 0,
+		    (status & HDSPM_midi1IRQPending) ? 1 : 0,
+		    hdspm->irq_count);
+	snd_iprintf(buffer,
+		    "HW pointer: id = %d, rawptr = %d (%d->%d) "
+		    "estimated= %ld (bytes)\n",
+		    ((status & HDSPM_BufferID) ? 1 : 0),
+		    (status & HDSPM_BufferPositionMask),
+		    (status & HDSPM_BufferPositionMask) %
+		    (2 * (int)hdspm->period_bytes),
+		    ((status & HDSPM_BufferPositionMask) - 64) %
+		    (2 * (int)hdspm->period_bytes),
+		    (long) hdspm_hw_pointer(hdspm) * 4);
+
+	snd_iprintf(buffer,
+		    "MIDI FIFO: Out1=0x%x, Out2=0x%x, In1=0x%x, In2=0x%x \n",
+		    hdspm_read(hdspm, HDSPM_midiStatusOut0) & 0xFF,
+		    hdspm_read(hdspm, HDSPM_midiStatusOut1) & 0xFF,
+		    hdspm_read(hdspm, HDSPM_midiStatusIn0) & 0xFF,
+		    hdspm_read(hdspm, HDSPM_midiStatusIn1) & 0xFF);
+	snd_iprintf(buffer,
+		    "Register: ctrl1=0x%x, ctrl2=0x%x, status1=0x%x, "
+		    "status2=0x%x\n",
+		    hdspm->control_register, hdspm->control2_register,
+		    status, status2);
+
+	snd_iprintf(buffer, "--- Settings ---\n");
+
+	x = 1 << (6 + hdspm_decode_latency(hdspm->control_register &
+					   HDSPM_LatencyMask));
+
+	snd_iprintf(buffer,
+		    "Size (Latency): %d samples (2 periods of %lu bytes)\n",
+		    x, (unsigned long) hdspm->period_bytes);
+
+	snd_iprintf(buffer, "Line out: %s,   Precise Pointer: %s\n",
+		    (hdspm->control_register & HDSPM_LineOut) ? "on " : "off",
+		    (hdspm->precise_ptr) ? "on" : "off");
+
+	switch (hdspm->control_register & HDSPM_InputMask) {
+	case HDSPM_InputOptical:
+		insel = "Optical";
+		break;
+	case HDSPM_InputCoaxial:
+		insel = "Coaxial";
+		break;
+	default:
+		insel = "Unknown";
+	}
+
+	switch (hdspm->control_register & HDSPM_SyncRefMask) {
+	case HDSPM_SyncRef_Word:
+		syncref = "WordClock";
+		break;
+	case HDSPM_SyncRef_MADI:
+		syncref = "MADI";
+		break;
+	default:
+		syncref = "Unknown";
+	}
+	snd_iprintf(buffer, "Inputsel = %s, SyncRef = %s\n", insel,
+		    syncref);
+
+	snd_iprintf(buffer,
+		    "ClearTrackMarker = %s, Transmit in %s Channel Mode, "
+		    "Auto Input %s\n",
+		    (hdspm->
+		     control_register & HDSPM_clr_tms) ? "on" : "off",
+		    (hdspm->
+		     control_register & HDSPM_TX_64ch) ? "64" : "56",
+		    (hdspm->
+		     control_register & HDSPM_AutoInp) ? "on" : "off");
+
+	switch (hdspm_clock_source(hdspm)) {
+	case HDSPM_CLOCK_SOURCE_AUTOSYNC:
+		clock_source = "AutoSync";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_32KHZ:
+		clock_source = "Internal 32 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_44_1KHZ:
+		clock_source = "Internal 44.1 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_48KHZ:
+		clock_source = "Internal 48 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_64KHZ:
+		clock_source = "Internal 64 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_88_2KHZ:
+		clock_source = "Internal 88.2 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_96KHZ:
+		clock_source = "Internal 96 kHz";
+		break;
+	default:
+		clock_source = "Error";
+	}
+	snd_iprintf(buffer, "Sample Clock Source: %s\n", clock_source);
+	if (!(hdspm->control_register & HDSPM_ClockModeMaster))
+		system_clock_mode = "Slave";
+	else
+		system_clock_mode = "Master";
+	snd_iprintf(buffer, "System Clock Mode: %s\n", system_clock_mode);
+
+	switch (hdspm_pref_sync_ref(hdspm)) {
+	case HDSPM_SYNC_FROM_WORD:
+		pref_sync_ref = "Word Clock";
+		break;
+	case HDSPM_SYNC_FROM_MADI:
+		pref_sync_ref = "MADI Sync";
+		break;
+	default:
+		pref_sync_ref = "XXXX Clock";
+		break;
+	}
+	snd_iprintf(buffer, "Preferred Sync Reference: %s\n",
+		    pref_sync_ref);
+
+	snd_iprintf(buffer, "System Clock Frequency: %d\n",
+		    hdspm->system_sample_rate);
+
+
+	snd_iprintf(buffer, "--- Status:\n");
+
+	x = status & HDSPM_madiSync;
+	x2 = status2 & HDSPM_wcSync;
+
+	snd_iprintf(buffer, "Inputs MADI=%s, WordClock=%s\n",
+		    (status & HDSPM_madiLock) ? (x ? "Sync" : "Lock") :
+		    "NoLock",
+		    (status2 & HDSPM_wcLock) ? (x2 ? "Sync" : "Lock") :
+		    "NoLock");
+
+	switch (hdspm_autosync_ref(hdspm)) {
+	case HDSPM_AUTOSYNC_FROM_WORD:
+		autosync_ref = "Word Clock";
+		break;
+	case HDSPM_AUTOSYNC_FROM_MADI:
+		autosync_ref = "MADI Sync";
+		break;
+	case HDSPM_AUTOSYNC_FROM_NONE:
+		autosync_ref = "Input not valid";
+		break;
+	default:
+		autosync_ref = "---";
+		break;
+	}
+	snd_iprintf(buffer,
+		    "AutoSync: Reference= %s, Freq=%d (MADI = %d, Word = %d)\n",
+		    autosync_ref, hdspm_external_sample_rate(hdspm),
+		    (status & HDSPM_madiFreqMask) >> 22,
+		    (status2 & HDSPM_wcFreqMask) >> 5);
+
+	snd_iprintf(buffer, "Input: %s, Mode=%s\n",
+		    (status & HDSPM_AB_int) ? "Coax" : "Optical",
+		    (status & HDSPM_RX_64ch) ? "64 channels" :
+		    "56 channels");
+
+	snd_iprintf(buffer, "\n");
+}
+
+static void
+snd_hdspm_proc_read_aes32(struct snd_info_entry * entry,
+			  struct snd_info_buffer *buffer)
+{
+	struct hdspm *hdspm = entry->private_data;
+	unsigned int status;
+	unsigned int status2;
+	unsigned int timecode;
+	int pref_syncref;
+	char *autosync_ref;
+	char *system_clock_mode;
+	char *clock_source;
+	int x;
+
+	status = hdspm_read(hdspm, HDSPM_statusRegister);
+	status2 = hdspm_read(hdspm, HDSPM_statusRegister2);
+	timecode = hdspm_read(hdspm, HDSPM_timecodeRegister);
+
+	snd_iprintf(buffer, "%s (Card #%d) Rev.%x\n",
+		    hdspm->card_name, hdspm->card->number + 1,
+		    hdspm->firmware_rev);
+
+	snd_iprintf(buffer, "IRQ: %d Registers bus: 0x%lx VM: 0x%lx\n",
+		    hdspm->irq, hdspm->port, (unsigned long)hdspm->iobase);
+
+	snd_iprintf(buffer, "--- System ---\n");
+
+	snd_iprintf(buffer,
+		    "IRQ Pending: Audio=%d, MIDI0=%d, MIDI1=%d, IRQcount=%d\n",
+		    status & HDSPM_audioIRQPending,
+		    (status & HDSPM_midi0IRQPending) ? 1 : 0,
+		    (status & HDSPM_midi1IRQPending) ? 1 : 0,
+		    hdspm->irq_count);
+	snd_iprintf(buffer,
+		    "HW pointer: id = %d, rawptr = %d (%d->%d) "
+		    "estimated= %ld (bytes)\n",
+		    ((status & HDSPM_BufferID) ? 1 : 0),
+		    (status & HDSPM_BufferPositionMask),
+		    (status & HDSPM_BufferPositionMask) %
+		    (2 * (int)hdspm->period_bytes),
+		    ((status & HDSPM_BufferPositionMask) - 64) %
+		    (2 * (int)hdspm->period_bytes),
+		    (long) hdspm_hw_pointer(hdspm) * 4);
+
+	snd_iprintf(buffer,
+		    "MIDI FIFO: Out1=0x%x, Out2=0x%x, In1=0x%x, In2=0x%x \n",
+		    hdspm_read(hdspm, HDSPM_midiStatusOut0) & 0xFF,
+		    hdspm_read(hdspm, HDSPM_midiStatusOut1) & 0xFF,
+		    hdspm_read(hdspm, HDSPM_midiStatusIn0) & 0xFF,
+		    hdspm_read(hdspm, HDSPM_midiStatusIn1) & 0xFF);
+	snd_iprintf(buffer,
+		    "Register: ctrl1=0x%x, status1=0x%x, status2=0x%x, "
+		    "timecode=0x%x\n",
+		    hdspm->control_register,
+		    status, status2, timecode);
+
+	snd_iprintf(buffer, "--- Settings ---\n");
+
+	x = 1 << (6 + hdspm_decode_latency(hdspm->control_register &
+					   HDSPM_LatencyMask));
+
+	snd_iprintf(buffer,
+		    "Size (Latency): %d samples (2 periods of %lu bytes)\n",
+		    x, (unsigned long) hdspm->period_bytes);
+
+	snd_iprintf(buffer, "Line out: %s,   Precise Pointer: %s\n",
+		    (hdspm->
+		     control_register & HDSPM_LineOut) ? "on " : "off",
+		    (hdspm->precise_ptr) ? "on" : "off");
+
+	snd_iprintf(buffer,
+		    "ClearTrackMarker %s, Emphasis %s, Dolby %s\n",
+		    (hdspm->
+		     control_register & HDSPM_clr_tms) ? "on" : "off",
+		    (hdspm->
+		     control_register & HDSPM_Emphasis) ? "on" : "off",
+		    (hdspm->
+		     control_register & HDSPM_Dolby) ? "on" : "off");
+
+	switch (hdspm_clock_source(hdspm)) {
+	case HDSPM_CLOCK_SOURCE_AUTOSYNC:
+		clock_source = "AutoSync";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_32KHZ:
+		clock_source = "Internal 32 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_44_1KHZ:
+		clock_source = "Internal 44.1 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_48KHZ:
+		clock_source = "Internal 48 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_64KHZ:
+		clock_source = "Internal 64 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_88_2KHZ:
+		clock_source = "Internal 88.2 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_96KHZ:
+		clock_source = "Internal 96 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_128KHZ:
+		clock_source = "Internal 128 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_176_4KHZ:
+		clock_source = "Internal 176.4 kHz";
+		break;
+	case HDSPM_CLOCK_SOURCE_INTERNAL_192KHZ:
+		clock_source = "Internal 192 kHz";
+		break;
+	default:
+		clock_source = "Error";
+	}
+	snd_iprintf(buffer, "Sample Clock Source: %s\n", clock_source);
+	if (!(hdspm->control_register & HDSPM_ClockModeMaster))
+		system_clock_mode = "Slave";
+	else
+		system_clock_mode = "Master";
+	snd_iprintf(buffer, "System Clock Mode: %s\n", system_clock_mode);
+
+	pref_syncref = hdspm_pref_sync_ref(hdspm);
+	if (pref_syncref == 0)
+		snd_iprintf(buffer, "Preferred Sync Reference: Word Clock\n");
+	else
+		snd_iprintf(buffer, "Preferred Sync Reference: AES%d\n",
+				pref_syncref);
+
+	snd_iprintf(buffer, "System Clock Frequency: %d\n",
+		    hdspm->system_sample_rate);
+
+	snd_iprintf(buffer, "Double speed: %s\n",
+			hdspm->control_register & HDSPM_DS_DoubleWire?
+			"Double wire" : "Single wire");
+	snd_iprintf(buffer, "Quad speed: %s\n",
+			hdspm->control_register & HDSPM_QS_DoubleWire?
+			"Double wire" :
+			hdspm->control_register & HDSPM_QS_QuadWire?
+			"Quad wire" : "Single wire");
+
+	snd_iprintf(buffer, "--- Status:\n");
+
+	snd_iprintf(buffer, "Word: %s  Frequency: %d\n",
+		    (status & HDSPM_AES32_wcLock)? "Sync   " : "No Lock",
+		    HDSPM_bit2freq((status >> HDSPM_AES32_wcFreq_bit) & 0xF));
+
+	for (x = 0; x < 8; x++) {
+		snd_iprintf(buffer, "AES%d: %s  Frequency: %d\n",
+			    x+1,
+			    (status2 & (HDSPM_LockAES >> x)) ?
+			    "Sync   ": "No Lock",
+			    HDSPM_bit2freq((timecode >> (4*x)) & 0xF));
+	}
+
+	switch (hdspm_autosync_ref(hdspm)) {
+	case HDSPM_AES32_AUTOSYNC_FROM_NONE: autosync_ref="None"; break;
+	case HDSPM_AES32_AUTOSYNC_FROM_WORD: autosync_ref="Word Clock"; break;
+	case HDSPM_AES32_AUTOSYNC_FROM_AES1: autosync_ref="AES1"; break;
+	case HDSPM_AES32_AUTOSYNC_FROM_AES2: autosync_ref="AES2"; break;
+	case HDSPM_AES32_AUTOSYNC_FROM_AES3: autosync_ref="AES3"; break;
+	case HDSPM_AES32_AUTOSYNC_FROM_AES4: autosync_ref="AES4"; break;
+	case HDSPM_AES32_AUTOSYNC_FROM_AES5: autosync_ref="AES5"; break;
+	case HDSPM_AES32_AUTOSYNC_FROM_AES6: autosync_ref="AES6"; break;
+	case HDSPM_AES32_AUTOSYNC_FROM_AES7: autosync_ref="AES7"; break;
+	case HDSPM_AES32_AUTOSYNC_FROM_AES8: autosync_ref="AES8"; break;
+	default: autosync_ref = "---"; break;
+	}
+	snd_iprintf(buffer, "AutoSync ref = %s\n", autosync_ref);
+
+	snd_iprintf(buffer, "\n");
+}
+
+#ifdef CONFIG_SND_DEBUG
+static void
+snd_hdspm_proc_read_debug(struct snd_info_entry * entry,
+			  struct snd_info_buffer *buffer)
+{
+	struct hdspm *hdspm = entry->private_data;
+
+	int j,i;
+
+	for (i = 0; i < 256 /* 1024*64 */; i += j) {
+		snd_iprintf(buffer, "0x%08X: ", i);
+		for (j = 0; j < 16; j += 4)
+			snd_iprintf(buffer, "%08X ", hdspm_read(hdspm, i + j));
+		snd_iprintf(buffer, "\n");
+	}
+}
+#endif
+
+
+
+static void __devinit snd_hdspm_proc_init(struct hdspm * hdspm)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(hdspm->card, "hdspm", &entry))
+		snd_info_set_text_ops(entry, hdspm,
+				      hdspm->is_aes32 ?
+				      snd_hdspm_proc_read_aes32 :
+				      snd_hdspm_proc_read_madi);
+#ifdef CONFIG_SND_DEBUG
+	/* debug file to read all hdspm registers */
+	if (!snd_card_proc_new(hdspm->card, "debug", &entry))
+		snd_info_set_text_ops(entry, hdspm,
+				snd_hdspm_proc_read_debug);
+#endif
+}
+
+/*------------------------------------------------------------
+   hdspm intitialize 
+ ------------------------------------------------------------*/
+
+static int snd_hdspm_set_defaults(struct hdspm * hdspm)
+{
+	unsigned int i;
+
+	/* ASSUMPTION: hdspm->lock is either held, or there is no need to
+	   hold it (e.g. during module initialization).
+	 */
+
+	/* set defaults:       */
+
+	if (hdspm->is_aes32)
+		hdspm->control_register =
+			HDSPM_ClockModeMaster |	/* Master Cloack Mode on */
+			hdspm_encode_latency(7) | /* latency maximum =
+						   * 8192 samples
+						   */
+			HDSPM_SyncRef0 |	/* AES1 is syncclock */
+			HDSPM_LineOut |	/* Analog output in */
+			HDSPM_Professional;  /* Professional mode */
+	else
+		hdspm->control_register =
+			HDSPM_ClockModeMaster |	/* Master Cloack Mode on */
+			hdspm_encode_latency(7) | /* latency maximum =
+						   * 8192 samples
+						   */
+			HDSPM_InputCoaxial |	/* Input Coax not Optical */
+			HDSPM_SyncRef_MADI |	/* Madi is syncclock */
+			HDSPM_LineOut |	/* Analog output in */
+			HDSPM_TX_64ch |	/* transmit in 64ch mode */
+			HDSPM_AutoInp;	/* AutoInput chossing (takeover) */
+
+	/* ! HDSPM_Frequency0|HDSPM_Frequency1 = 44.1khz */
+	/* !  HDSPM_DoubleSpeed HDSPM_QuadSpeed = normal speed */
+	/* ! HDSPM_clr_tms = do not clear bits in track marks */
+
+	hdspm_write(hdspm, HDSPM_controlRegister, hdspm->control_register);
+
+        if (!hdspm->is_aes32) {
+		/* No control2 register for AES32 */
+#ifdef SNDRV_BIG_ENDIAN
+		hdspm->control2_register = HDSPM_BIGENDIAN_MODE;
+#else
+		hdspm->control2_register = 0;
+#endif
+
+		hdspm_write(hdspm, HDSPM_control2Reg, hdspm->control2_register);
+	}
+	hdspm_compute_period_size(hdspm);
+
+	/* silence everything */
+
+	all_in_all_mixer(hdspm, 0 * UNITY_GAIN);
+
+	if (line_outs_monitor[hdspm->dev]) {
+
+		snd_printk(KERN_INFO "HDSPM: "
+			   "sending all playback streams to line outs.\n");
+
+		for (i = 0; i < HDSPM_MIXER_CHANNELS; i++) {
+			if (hdspm_write_pb_gain(hdspm, i, i, UNITY_GAIN))
+				return -EIO;
+		}
+	}
+
+	/* set a default rate so that the channel map is set up. */
+	hdspm->channel_map = channel_map_madi_ss;
+	hdspm_set_rate(hdspm, 44100, 1);
+
+	return 0;
+}
+
+
+/*------------------------------------------------------------
+   interrupt 
+ ------------------------------------------------------------*/
+
+static irqreturn_t snd_hdspm_interrupt(int irq, void *dev_id)
+{
+	struct hdspm *hdspm = (struct hdspm *) dev_id;
+	unsigned int status;
+	int audio;
+	int midi0;
+	int midi1;
+	unsigned int midi0status;
+	unsigned int midi1status;
+	int schedule = 0;
+
+	status = hdspm_read(hdspm, HDSPM_statusRegister);
+
+	audio = status & HDSPM_audioIRQPending;
+	midi0 = status & HDSPM_midi0IRQPending;
+	midi1 = status & HDSPM_midi1IRQPending;
+
+	if (!audio && !midi0 && !midi1)
+		return IRQ_NONE;
+
+	hdspm_write(hdspm, HDSPM_interruptConfirmation, 0);
+	hdspm->irq_count++;
+
+	midi0status = hdspm_read(hdspm, HDSPM_midiStatusIn0) & 0xff;
+	midi1status = hdspm_read(hdspm, HDSPM_midiStatusIn1) & 0xff;
+
+	if (audio) {
+
+		if (hdspm->capture_substream)
+			snd_pcm_period_elapsed(hdspm->capture_substream);
+
+		if (hdspm->playback_substream)
+			snd_pcm_period_elapsed(hdspm->playback_substream);
+	}
+
+	if (midi0 && midi0status) {
+		/* we disable interrupts for this input until processing
+		 * is done
+		 */
+		hdspm->control_register &= ~HDSPM_Midi0InterruptEnable;
+		hdspm_write(hdspm, HDSPM_controlRegister,
+			    hdspm->control_register);
+		hdspm->midi[0].pending = 1;
+		schedule = 1;
+	}
+	if (midi1 && midi1status) {
+		/* we disable interrupts for this input until processing
+		 * is done
+		 */
+		hdspm->control_register &= ~HDSPM_Midi1InterruptEnable;
+		hdspm_write(hdspm, HDSPM_controlRegister,
+			    hdspm->control_register);
+		hdspm->midi[1].pending = 1;
+		schedule = 1;
+	}
+	if (schedule)
+		tasklet_schedule(&hdspm->midi_tasklet);
+	return IRQ_HANDLED;
+}
+
+/*------------------------------------------------------------
+   pcm interface 
+  ------------------------------------------------------------*/
+
+
+static snd_pcm_uframes_t snd_hdspm_hw_pointer(struct snd_pcm_substream *
+					      substream)
+{
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+	return hdspm_hw_pointer(hdspm);
+}
+
+static char *hdspm_channel_buffer_location(struct hdspm * hdspm,
+					   int stream, int channel)
+{
+	int mapped_channel;
+
+	if (snd_BUG_ON(channel < 0 || channel >= HDSPM_MAX_CHANNELS))
+		return NULL;
+
+	mapped_channel = hdspm->channel_map[channel];
+	if (mapped_channel < 0)
+		return NULL;
+
+	if (stream == SNDRV_PCM_STREAM_CAPTURE)
+		return hdspm->capture_buffer +
+		    mapped_channel * HDSPM_CHANNEL_BUFFER_BYTES;
+	else
+		return hdspm->playback_buffer +
+		    mapped_channel * HDSPM_CHANNEL_BUFFER_BYTES;
+}
+
+
+/* dont know why need it ??? */
+static int snd_hdspm_playback_copy(struct snd_pcm_substream *substream,
+				   int channel, snd_pcm_uframes_t pos,
+				   void __user *src, snd_pcm_uframes_t count)
+{
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+	char *channel_buf;
+
+	if (snd_BUG_ON(pos + count > HDSPM_CHANNEL_BUFFER_BYTES / 4))
+		return -EINVAL;
+
+	channel_buf =
+		hdspm_channel_buffer_location(hdspm, substream->pstr->stream,
+					      channel);
+
+	if (snd_BUG_ON(!channel_buf))
+		return -EIO;
+
+	return copy_from_user(channel_buf + pos * 4, src, count * 4);
+}
+
+static int snd_hdspm_capture_copy(struct snd_pcm_substream *substream,
+				  int channel, snd_pcm_uframes_t pos,
+				  void __user *dst, snd_pcm_uframes_t count)
+{
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+	char *channel_buf;
+
+	if (snd_BUG_ON(pos + count > HDSPM_CHANNEL_BUFFER_BYTES / 4))
+		return -EINVAL;
+
+	channel_buf =
+		hdspm_channel_buffer_location(hdspm, substream->pstr->stream,
+					      channel);
+	if (snd_BUG_ON(!channel_buf))
+		return -EIO;
+	return copy_to_user(dst, channel_buf + pos * 4, count * 4);
+}
+
+static int snd_hdspm_hw_silence(struct snd_pcm_substream *substream,
+				int channel, snd_pcm_uframes_t pos,
+				snd_pcm_uframes_t count)
+{
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+	char *channel_buf;
+
+	channel_buf =
+		hdspm_channel_buffer_location(hdspm, substream->pstr->stream,
+					      channel);
+	if (snd_BUG_ON(!channel_buf))
+		return -EIO;
+	memset(channel_buf + pos * 4, 0, count * 4);
+	return 0;
+}
+
+static int snd_hdspm_reset(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *other;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		other = hdspm->capture_substream;
+	else
+		other = hdspm->playback_substream;
+
+	if (hdspm->running)
+		runtime->status->hw_ptr = hdspm_hw_pointer(hdspm);
+	else
+		runtime->status->hw_ptr = 0;
+	if (other) {
+		struct snd_pcm_substream *s;
+		struct snd_pcm_runtime *oruntime = other->runtime;
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s == other) {
+				oruntime->status->hw_ptr =
+				    runtime->status->hw_ptr;
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+static int snd_hdspm_hw_params(struct snd_pcm_substream *substream,
+			       struct snd_pcm_hw_params *params)
+{
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+	int err;
+	int i;
+	pid_t this_pid;
+	pid_t other_pid;
+
+	spin_lock_irq(&hdspm->lock);
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		this_pid = hdspm->playback_pid;
+		other_pid = hdspm->capture_pid;
+	} else {
+		this_pid = hdspm->capture_pid;
+		other_pid = hdspm->playback_pid;
+	}
+
+	if (other_pid > 0 && this_pid != other_pid) {
+
+		/* The other stream is open, and not by the same
+		   task as this one. Make sure that the parameters
+		   that matter are the same.
+		 */
+
+		if (params_rate(params) != hdspm->system_sample_rate) {
+			spin_unlock_irq(&hdspm->lock);
+			_snd_pcm_hw_param_setempty(params,
+						   SNDRV_PCM_HW_PARAM_RATE);
+			return -EBUSY;
+		}
+
+		if (params_period_size(params) != hdspm->period_bytes / 4) {
+			spin_unlock_irq(&hdspm->lock);
+			_snd_pcm_hw_param_setempty(params,
+					   SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+			return -EBUSY;
+		}
+
+	}
+	/* We're fine. */
+	spin_unlock_irq(&hdspm->lock);
+
+	/* how to make sure that the rate matches an externally-set one ?   */
+
+	spin_lock_irq(&hdspm->lock);
+	err = hdspm_set_rate(hdspm, params_rate(params), 0);
+	if (err < 0) {
+		spin_unlock_irq(&hdspm->lock);
+		_snd_pcm_hw_param_setempty(params,
+					   SNDRV_PCM_HW_PARAM_RATE);
+		return err;
+	}
+	spin_unlock_irq(&hdspm->lock);
+
+	err = hdspm_set_interrupt_interval(hdspm,
+					   params_period_size(params));
+	if (err < 0) {
+		_snd_pcm_hw_param_setempty(params,
+					   SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+		return err;
+	}
+
+	/* Memory allocation, takashi's method, dont know if we should
+	 * spinlock
+	 */
+	/* malloc all buffer even if not enabled to get sure */
+	/* Update for MADI rev 204: we need to allocate for all channels,
+	 * otherwise it doesn't work at 96kHz */
+	err =
+	    snd_pcm_lib_malloc_pages(substream, HDSPM_DMA_AREA_BYTES);
+	if (err < 0)
+		return err;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+
+		hdspm_set_sgbuf(hdspm, substream, HDSPM_pageAddressBufferOut,
+				params_channels(params));
+
+		for (i = 0; i < params_channels(params); ++i)
+			snd_hdspm_enable_out(hdspm, i, 1);
+
+		hdspm->playback_buffer =
+		    (unsigned char *) substream->runtime->dma_area;
+		snd_printdd("Allocated sample buffer for playback at %p\n",
+				hdspm->playback_buffer);
+	} else {
+		hdspm_set_sgbuf(hdspm, substream, HDSPM_pageAddressBufferIn,
+				params_channels(params));
+
+		for (i = 0; i < params_channels(params); ++i)
+			snd_hdspm_enable_in(hdspm, i, 1);
+
+		hdspm->capture_buffer =
+		    (unsigned char *) substream->runtime->dma_area;
+		snd_printdd("Allocated sample buffer for capture at %p\n",
+				hdspm->capture_buffer);
+	}
+	/*
+	   snd_printdd("Allocated sample buffer for %s at 0x%08X\n",
+	   substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+	   "playback" : "capture",
+	   snd_pcm_sgbuf_get_addr(substream, 0));
+	 */
+	/*
+	snd_printdd("set_hwparams: %s %d Hz, %d channels, bs = %d\n",
+			substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+			  "playback" : "capture",
+			params_rate(params), params_channels(params),
+			params_buffer_size(params));
+	*/
+	return 0;
+}
+
+static int snd_hdspm_hw_free(struct snd_pcm_substream *substream)
+{
+	int i;
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+
+		/* params_channels(params) should be enough, 
+		   but to get sure in case of error */
+		for (i = 0; i < HDSPM_MAX_CHANNELS; ++i)
+			snd_hdspm_enable_out(hdspm, i, 0);
+
+		hdspm->playback_buffer = NULL;
+	} else {
+		for (i = 0; i < HDSPM_MAX_CHANNELS; ++i)
+			snd_hdspm_enable_in(hdspm, i, 0);
+
+		hdspm->capture_buffer = NULL;
+
+	}
+
+	snd_pcm_lib_free_pages(substream);
+
+	return 0;
+}
+
+static int snd_hdspm_channel_info(struct snd_pcm_substream *substream,
+				  struct snd_pcm_channel_info * info)
+{
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+	int mapped_channel;
+
+	if (snd_BUG_ON(info->channel >= HDSPM_MAX_CHANNELS))
+		return -EINVAL;
+
+	mapped_channel = hdspm->channel_map[info->channel];
+	if (mapped_channel < 0)
+		return -EINVAL;
+
+	info->offset = mapped_channel * HDSPM_CHANNEL_BUFFER_BYTES;
+	info->first = 0;
+	info->step = 32;
+	return 0;
+}
+
+static int snd_hdspm_ioctl(struct snd_pcm_substream *substream,
+			   unsigned int cmd, void *arg)
+{
+	switch (cmd) {
+	case SNDRV_PCM_IOCTL1_RESET:
+		return snd_hdspm_reset(substream);
+
+	case SNDRV_PCM_IOCTL1_CHANNEL_INFO:
+	{
+		struct snd_pcm_channel_info *info = arg;
+		return snd_hdspm_channel_info(substream, info);
+	}
+	default:
+		break;
+	}
+
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static int snd_hdspm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *other;
+	int running;
+
+	spin_lock(&hdspm->lock);
+	running = hdspm->running;
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		running |= 1 << substream->stream;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		running &= ~(1 << substream->stream);
+		break;
+	default:
+		snd_BUG();
+		spin_unlock(&hdspm->lock);
+		return -EINVAL;
+	}
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		other = hdspm->capture_substream;
+	else
+		other = hdspm->playback_substream;
+
+	if (other) {
+		struct snd_pcm_substream *s;
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s == other) {
+				snd_pcm_trigger_done(s, substream);
+				if (cmd == SNDRV_PCM_TRIGGER_START)
+					running |= 1 << s->stream;
+				else
+					running &= ~(1 << s->stream);
+				goto _ok;
+			}
+		}
+		if (cmd == SNDRV_PCM_TRIGGER_START) {
+			if (!(running & (1 << SNDRV_PCM_STREAM_PLAYBACK))
+			    && substream->stream ==
+			    SNDRV_PCM_STREAM_CAPTURE)
+				hdspm_silence_playback(hdspm);
+		} else {
+			if (running &&
+			    substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+				hdspm_silence_playback(hdspm);
+		}
+	} else {
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			hdspm_silence_playback(hdspm);
+	}
+      _ok:
+	snd_pcm_trigger_done(substream, substream);
+	if (!hdspm->running && running)
+		hdspm_start_audio(hdspm);
+	else if (hdspm->running && !running)
+		hdspm_stop_audio(hdspm);
+	hdspm->running = running;
+	spin_unlock(&hdspm->lock);
+
+	return 0;
+}
+
+static int snd_hdspm_prepare(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static unsigned int period_sizes[] =
+    { 64, 128, 256, 512, 1024, 2048, 4096, 8192 };
+
+static struct snd_pcm_hardware snd_hdspm_playback_subinfo = {
+	.info = (SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_NONINTERLEAVED |
+		 SNDRV_PCM_INFO_SYNC_START | SNDRV_PCM_INFO_DOUBLE),
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+	.rates = (SNDRV_PCM_RATE_32000 |
+		  SNDRV_PCM_RATE_44100 |
+		  SNDRV_PCM_RATE_48000 |
+		  SNDRV_PCM_RATE_64000 |
+		  SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
+		  SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000 ),
+	.rate_min = 32000,
+	.rate_max = 192000,
+	.channels_min = 1,
+	.channels_max = HDSPM_MAX_CHANNELS,
+	.buffer_bytes_max =
+	    HDSPM_CHANNEL_BUFFER_BYTES * HDSPM_MAX_CHANNELS,
+	.period_bytes_min = (64 * 4),
+	.period_bytes_max = (8192 * 4) * HDSPM_MAX_CHANNELS,
+	.periods_min = 2,
+	.periods_max = 2,
+	.fifo_size = 0
+};
+
+static struct snd_pcm_hardware snd_hdspm_capture_subinfo = {
+	.info = (SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_NONINTERLEAVED |
+		 SNDRV_PCM_INFO_SYNC_START),
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+	.rates = (SNDRV_PCM_RATE_32000 |
+		  SNDRV_PCM_RATE_44100 |
+		  SNDRV_PCM_RATE_48000 |
+		  SNDRV_PCM_RATE_64000 |
+		  SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
+		  SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000),
+	.rate_min = 32000,
+	.rate_max = 192000,
+	.channels_min = 1,
+	.channels_max = HDSPM_MAX_CHANNELS,
+	.buffer_bytes_max =
+	    HDSPM_CHANNEL_BUFFER_BYTES * HDSPM_MAX_CHANNELS,
+	.period_bytes_min = (64 * 4),
+	.period_bytes_max = (8192 * 4) * HDSPM_MAX_CHANNELS,
+	.periods_min = 2,
+	.periods_max = 2,
+	.fifo_size = 0
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_period_sizes = {
+	.count = ARRAY_SIZE(period_sizes),
+	.list = period_sizes,
+	.mask = 0
+};
+
+
+static int snd_hdspm_hw_rule_channels_rate(struct snd_pcm_hw_params *params,
+					   struct snd_pcm_hw_rule * rule)
+{
+	struct hdspm *hdspm = rule->private;
+	struct snd_interval *c =
+	    hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *r =
+	    hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+
+	if (r->min > 48000 && r->max <= 96000) {
+		struct snd_interval t = {
+			.min = hdspm->ds_channels,
+			.max = hdspm->ds_channels,
+			.integer = 1,
+		};
+		return snd_interval_refine(c, &t);
+	} else if (r->max < 64000) {
+		struct snd_interval t = {
+			.min = hdspm->ss_channels,
+			.max = hdspm->ss_channels,
+			.integer = 1,
+		};
+		return snd_interval_refine(c, &t);
+	}
+	return 0;
+}
+
+static int snd_hdspm_hw_rule_rate_channels(struct snd_pcm_hw_params *params,
+					   struct snd_pcm_hw_rule * rule)
+{
+	struct hdspm *hdspm = rule->private;
+	struct snd_interval *c =
+	    hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *r =
+	    hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+
+	if (c->min >= hdspm->ss_channels) {
+		struct snd_interval t = {
+			.min = 32000,
+			.max = 48000,
+			.integer = 1,
+		};
+		return snd_interval_refine(r, &t);
+	} else if (c->max <= hdspm->ds_channels) {
+		struct snd_interval t = {
+			.min = 64000,
+			.max = 96000,
+			.integer = 1,
+		};
+
+		return snd_interval_refine(r, &t);
+	}
+	return 0;
+}
+
+static int snd_hdspm_hw_rule_channels(struct snd_pcm_hw_params *params,
+				      struct snd_pcm_hw_rule *rule)
+{
+	unsigned int list[3];
+	struct hdspm *hdspm = rule->private;
+	struct snd_interval *c = hw_param_interval(params,
+			SNDRV_PCM_HW_PARAM_CHANNELS);
+	if (hdspm->is_aes32) {
+		list[0] = hdspm->qs_channels;
+		list[1] = hdspm->ds_channels;
+		list[2] = hdspm->ss_channels;
+		return snd_interval_list(c, 3, list, 0);
+	} else {
+		list[0] = hdspm->ds_channels;
+		list[1] = hdspm->ss_channels;
+		return snd_interval_list(c, 2, list, 0);
+	}
+}
+
+
+static unsigned int hdspm_aes32_sample_rates[] = {
+	32000, 44100, 48000, 64000, 88200, 96000, 128000, 176400, 192000
+};
+
+static struct snd_pcm_hw_constraint_list
+hdspm_hw_constraints_aes32_sample_rates = {
+	.count = ARRAY_SIZE(hdspm_aes32_sample_rates),
+	.list = hdspm_aes32_sample_rates,
+	.mask = 0
+};
+
+static int snd_hdspm_playback_open(struct snd_pcm_substream *substream)
+{
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	spin_lock_irq(&hdspm->lock);
+
+	snd_pcm_set_sync(substream);
+
+	runtime->hw = snd_hdspm_playback_subinfo;
+
+	if (hdspm->capture_substream == NULL)
+		hdspm_stop_audio(hdspm);
+
+	hdspm->playback_pid = current->pid;
+	hdspm->playback_substream = substream;
+
+	spin_unlock_irq(&hdspm->lock);
+
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+
+	snd_pcm_hw_constraint_list(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+				   &hw_constraints_period_sizes);
+
+	if (hdspm->is_aes32) {
+		snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				&hdspm_hw_constraints_aes32_sample_rates);
+	} else {
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				     snd_hdspm_hw_rule_channels, hdspm,
+				     SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				    snd_hdspm_hw_rule_channels_rate, hdspm,
+				    SNDRV_PCM_HW_PARAM_RATE, -1);
+
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				    snd_hdspm_hw_rule_rate_channels, hdspm,
+				    SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	}
+	return 0;
+}
+
+static int snd_hdspm_playback_release(struct snd_pcm_substream *substream)
+{
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&hdspm->lock);
+
+	hdspm->playback_pid = -1;
+	hdspm->playback_substream = NULL;
+
+	spin_unlock_irq(&hdspm->lock);
+
+	return 0;
+}
+
+
+static int snd_hdspm_capture_open(struct snd_pcm_substream *substream)
+{
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	spin_lock_irq(&hdspm->lock);
+	snd_pcm_set_sync(substream);
+	runtime->hw = snd_hdspm_capture_subinfo;
+
+	if (hdspm->playback_substream == NULL)
+		hdspm_stop_audio(hdspm);
+
+	hdspm->capture_pid = current->pid;
+	hdspm->capture_substream = substream;
+
+	spin_unlock_irq(&hdspm->lock);
+
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	snd_pcm_hw_constraint_list(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+				   &hw_constraints_period_sizes);
+	if (hdspm->is_aes32) {
+		snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				&hdspm_hw_constraints_aes32_sample_rates);
+	} else {
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				     snd_hdspm_hw_rule_channels, hdspm,
+				     SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				    snd_hdspm_hw_rule_channels_rate, hdspm,
+				    SNDRV_PCM_HW_PARAM_RATE, -1);
+
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				    snd_hdspm_hw_rule_rate_channels, hdspm,
+				    SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	}
+	return 0;
+}
+
+static int snd_hdspm_capture_release(struct snd_pcm_substream *substream)
+{
+	struct hdspm *hdspm = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&hdspm->lock);
+
+	hdspm->capture_pid = -1;
+	hdspm->capture_substream = NULL;
+
+	spin_unlock_irq(&hdspm->lock);
+	return 0;
+}
+
+static int snd_hdspm_hwdep_ioctl(struct snd_hwdep * hw, struct file *file,
+				 unsigned int cmd, unsigned long arg)
+{
+	struct hdspm *hdspm = hw->private_data;
+	struct hdspm_mixer_ioctl mixer;
+	struct hdspm_config_info info;
+	struct hdspm_version hdspm_version;
+	struct hdspm_peak_rms_ioctl rms;
+
+	switch (cmd) {
+
+	case SNDRV_HDSPM_IOCTL_GET_PEAK_RMS:
+		if (copy_from_user(&rms, (void __user *)arg, sizeof(rms)))
+			return -EFAULT;
+		/* maybe there is a chance to memorymap in future
+		 * so dont touch just copy
+		 */
+		if(copy_to_user_fromio((void __user *)rms.peak,
+				       hdspm->iobase+HDSPM_MADI_peakrmsbase,
+				       sizeof(struct hdspm_peak_rms)) != 0 )
+			return -EFAULT;
+
+		break;
+		
+
+	case SNDRV_HDSPM_IOCTL_GET_CONFIG_INFO:
+
+		memset(&info, 0, sizeof(info));
+		spin_lock_irq(&hdspm->lock);
+		info.pref_sync_ref = hdspm_pref_sync_ref(hdspm);
+		info.wordclock_sync_check = hdspm_wc_sync_check(hdspm);
+
+		info.system_sample_rate = hdspm->system_sample_rate;
+		info.autosync_sample_rate =
+		    hdspm_external_sample_rate(hdspm);
+		info.system_clock_mode = hdspm_system_clock_mode(hdspm);
+		info.clock_source = hdspm_clock_source(hdspm);
+		info.autosync_ref = hdspm_autosync_ref(hdspm);
+		info.line_out = hdspm_line_out(hdspm);
+		info.passthru = 0;
+		spin_unlock_irq(&hdspm->lock);
+		if (copy_to_user((void __user *) arg, &info, sizeof(info)))
+			return -EFAULT;
+		break;
+
+	case SNDRV_HDSPM_IOCTL_GET_VERSION:
+		hdspm_version.firmware_rev = hdspm->firmware_rev;
+		if (copy_to_user((void __user *) arg, &hdspm_version,
+				 sizeof(hdspm_version)))
+			return -EFAULT;
+		break;
+
+	case SNDRV_HDSPM_IOCTL_GET_MIXER:
+		if (copy_from_user(&mixer, (void __user *)arg, sizeof(mixer)))
+			return -EFAULT;
+		if (copy_to_user((void __user *)mixer.mixer, hdspm->mixer,
+				 sizeof(struct hdspm_mixer)))
+			return -EFAULT;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct snd_pcm_ops snd_hdspm_playback_ops = {
+	.open = snd_hdspm_playback_open,
+	.close = snd_hdspm_playback_release,
+	.ioctl = snd_hdspm_ioctl,
+	.hw_params = snd_hdspm_hw_params,
+	.hw_free = snd_hdspm_hw_free,
+	.prepare = snd_hdspm_prepare,
+	.trigger = snd_hdspm_trigger,
+	.pointer = snd_hdspm_hw_pointer,
+	.copy = snd_hdspm_playback_copy,
+	.silence = snd_hdspm_hw_silence,
+	.page = snd_pcm_sgbuf_ops_page,
+};
+
+static struct snd_pcm_ops snd_hdspm_capture_ops = {
+	.open = snd_hdspm_capture_open,
+	.close = snd_hdspm_capture_release,
+	.ioctl = snd_hdspm_ioctl,
+	.hw_params = snd_hdspm_hw_params,
+	.hw_free = snd_hdspm_hw_free,
+	.prepare = snd_hdspm_prepare,
+	.trigger = snd_hdspm_trigger,
+	.pointer = snd_hdspm_hw_pointer,
+	.copy = snd_hdspm_capture_copy,
+	.page = snd_pcm_sgbuf_ops_page,
+};
+
+static int __devinit snd_hdspm_create_hwdep(struct snd_card *card,
+					    struct hdspm * hdspm)
+{
+	struct snd_hwdep *hw;
+	int err;
+
+	err = snd_hwdep_new(card, "HDSPM hwdep", 0, &hw);
+	if (err < 0)
+		return err;
+
+	hdspm->hwdep = hw;
+	hw->private_data = hdspm;
+	strcpy(hw->name, "HDSPM hwdep interface");
+
+	hw->ops.ioctl = snd_hdspm_hwdep_ioctl;
+
+	return 0;
+}
+
+
+/*------------------------------------------------------------
+   memory interface 
+ ------------------------------------------------------------*/
+static int __devinit snd_hdspm_preallocate_memory(struct hdspm * hdspm)
+{
+	int err;
+	struct snd_pcm *pcm;
+	size_t wanted;
+
+	pcm = hdspm->pcm;
+
+	wanted = HDSPM_DMA_AREA_BYTES;
+
+	err =
+	     snd_pcm_lib_preallocate_pages_for_all(pcm,
+	     					   SNDRV_DMA_TYPE_DEV_SG,
+						   snd_dma_pci_data(hdspm->pci),
+						   wanted,
+						   wanted);
+	if (err < 0) {
+		snd_printdd("Could not preallocate %zd Bytes\n", wanted);
+
+		return err;
+	} else
+		snd_printdd(" Preallocated %zd Bytes\n", wanted);
+
+	return 0;
+}
+
+static void hdspm_set_sgbuf(struct hdspm * hdspm,
+			    struct snd_pcm_substream *substream,
+			     unsigned int reg, int channels)
+{
+	int i;
+	for (i = 0; i < (channels * 16); i++)
+		hdspm_write(hdspm, reg + 4 * i,
+			    snd_pcm_sgbuf_get_addr(substream, 4096 * i));
+}
+
+/* ------------- ALSA Devices ---------------------------- */
+static int __devinit snd_hdspm_create_pcm(struct snd_card *card,
+					  struct hdspm * hdspm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(card, hdspm->card_name, 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	hdspm->pcm = pcm;
+	pcm->private_data = hdspm;
+	strcpy(pcm->name, hdspm->card_name);
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_hdspm_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&snd_hdspm_capture_ops);
+
+	pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+
+	err = snd_hdspm_preallocate_memory(hdspm);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static inline void snd_hdspm_initialize_midi_flush(struct hdspm * hdspm)
+{
+	snd_hdspm_flush_midi_input(hdspm, 0);
+	snd_hdspm_flush_midi_input(hdspm, 1);
+}
+
+static int __devinit snd_hdspm_create_alsa_devices(struct snd_card *card,
+						   struct hdspm * hdspm)
+{
+	int err;
+
+	snd_printdd("Create card...\n");
+	err = snd_hdspm_create_pcm(card, hdspm);
+	if (err < 0)
+		return err;
+
+	err = snd_hdspm_create_midi(card, hdspm, 0);
+	if (err < 0)
+		return err;
+
+	err = snd_hdspm_create_midi(card, hdspm, 1);
+	if (err < 0)
+		return err;
+
+	err = snd_hdspm_create_controls(card, hdspm);
+	if (err < 0)
+		return err;
+
+	err = snd_hdspm_create_hwdep(card, hdspm);
+	if (err < 0)
+		return err;
+
+	snd_printdd("proc init...\n");
+	snd_hdspm_proc_init(hdspm);
+
+	hdspm->system_sample_rate = -1;
+	hdspm->last_external_sample_rate = -1;
+	hdspm->last_internal_sample_rate = -1;
+	hdspm->playback_pid = -1;
+	hdspm->capture_pid = -1;
+	hdspm->capture_substream = NULL;
+	hdspm->playback_substream = NULL;
+
+	snd_printdd("Set defaults...\n");
+	err = snd_hdspm_set_defaults(hdspm);
+	if (err < 0)
+		return err;
+
+	snd_printdd("Update mixer controls...\n");
+	hdspm_update_simple_mixer_controls(hdspm);
+
+	snd_printdd("Initializeing complete ???\n");
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_printk(KERN_ERR "HDSPM: error registering card\n");
+		return err;
+	}
+
+	snd_printdd("... yes now\n");
+
+	return 0;
+}
+
+static int __devinit snd_hdspm_create(struct snd_card *card,
+				      struct hdspm *hdspm,
+				      int precise_ptr, int enable_monitor)
+{
+	struct pci_dev *pci = hdspm->pci;
+	int err;
+	unsigned long io_extent;
+
+	hdspm->irq = -1;
+
+	spin_lock_init(&hdspm->midi[0].lock);
+	spin_lock_init(&hdspm->midi[1].lock);
+
+	hdspm->card = card;
+
+	spin_lock_init(&hdspm->lock);
+
+	tasklet_init(&hdspm->midi_tasklet,
+		     hdspm_midi_tasklet, (unsigned long) hdspm);
+
+	pci_read_config_word(hdspm->pci,
+			     PCI_CLASS_REVISION, &hdspm->firmware_rev);
+
+	hdspm->is_aes32 = (hdspm->firmware_rev >= HDSPM_AESREVISION);
+
+	strcpy(card->mixername, "Xilinx FPGA");
+	if (hdspm->is_aes32) {
+		strcpy(card->driver, "HDSPAES32");
+		hdspm->card_name = "RME HDSPM AES32";
+	} else {
+		strcpy(card->driver, "HDSPM");
+		hdspm->card_name = "RME HDSPM MADI";
+	}
+
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+
+	pci_set_master(hdspm->pci);
+
+	err = pci_request_regions(pci, "hdspm");
+	if (err < 0)
+		return err;
+
+	hdspm->port = pci_resource_start(pci, 0);
+	io_extent = pci_resource_len(pci, 0);
+
+	snd_printdd("grabbed memory region 0x%lx-0x%lx\n",
+		   hdspm->port, hdspm->port + io_extent - 1);
+
+
+	hdspm->iobase = ioremap_nocache(hdspm->port, io_extent);
+	if (!hdspm->iobase) {
+		snd_printk(KERN_ERR "HDSPM: "
+			   "unable to remap region 0x%lx-0x%lx\n",
+			   hdspm->port, hdspm->port + io_extent - 1);
+		return -EBUSY;
+	}
+	snd_printdd("remapped region (0x%lx) 0x%lx-0x%lx\n",
+		   (unsigned long)hdspm->iobase, hdspm->port,
+		   hdspm->port + io_extent - 1);
+
+	if (request_irq(pci->irq, snd_hdspm_interrupt,
+			IRQF_SHARED, "hdspm", hdspm)) {
+		snd_printk(KERN_ERR "HDSPM: unable to use IRQ %d\n", pci->irq);
+		return -EBUSY;
+	}
+
+	snd_printdd("use IRQ %d\n", pci->irq);
+
+	hdspm->irq = pci->irq;
+	hdspm->precise_ptr = precise_ptr;
+
+	hdspm->monitor_outs = enable_monitor;
+
+	snd_printdd("kmalloc Mixer memory of %zd Bytes\n",
+		   sizeof(struct hdspm_mixer));
+	hdspm->mixer = kzalloc(sizeof(struct hdspm_mixer), GFP_KERNEL);
+	if (!hdspm->mixer) {
+		snd_printk(KERN_ERR "HDSPM: "
+			   "unable to kmalloc Mixer memory of %d Bytes\n",
+			   (int)sizeof(struct hdspm_mixer));
+		return err;
+	}
+
+	hdspm->ss_channels = MADI_SS_CHANNELS;
+	hdspm->ds_channels = MADI_DS_CHANNELS;
+	hdspm->qs_channels = MADI_QS_CHANNELS;
+
+	snd_printdd("create alsa devices.\n");
+	err = snd_hdspm_create_alsa_devices(card, hdspm);
+	if (err < 0)
+		return err;
+
+	snd_hdspm_initialize_midi_flush(hdspm);
+
+	return 0;
+}
+
+static int snd_hdspm_free(struct hdspm * hdspm)
+{
+
+	if (hdspm->port) {
+
+		/* stop th audio, and cancel all interrupts */
+		hdspm->control_register &=
+		    ~(HDSPM_Start | HDSPM_AudioInterruptEnable |
+		      HDSPM_Midi0InterruptEnable | HDSPM_Midi1InterruptEnable);
+		hdspm_write(hdspm, HDSPM_controlRegister,
+			    hdspm->control_register);
+	}
+
+	if (hdspm->irq >= 0)
+		free_irq(hdspm->irq, (void *) hdspm);
+
+	kfree(hdspm->mixer);
+
+	if (hdspm->iobase)
+		iounmap(hdspm->iobase);
+
+	if (hdspm->port)
+		pci_release_regions(hdspm->pci);
+
+	pci_disable_device(hdspm->pci);
+	return 0;
+}
+
+static void snd_hdspm_card_free(struct snd_card *card)
+{
+	struct hdspm *hdspm = card->private_data;
+
+	if (hdspm)
+		snd_hdspm_free(hdspm);
+}
+
+static int __devinit snd_hdspm_probe(struct pci_dev *pci,
+				     const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct hdspm *hdspm;
+	struct snd_card *card;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev],
+			      THIS_MODULE, sizeof(struct hdspm), &card);
+	if (err < 0)
+		return err;
+
+	hdspm = card->private_data;
+	card->private_free = snd_hdspm_card_free;
+	hdspm->dev = dev;
+	hdspm->pci = pci;
+
+	snd_card_set_dev(card, &pci->dev);
+
+	err = snd_hdspm_create(card, hdspm, precise_ptr[dev],
+			       enable_monitor[dev]);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->shortname, "HDSPM MADI");
+	sprintf(card->longname, "%s at 0x%lx, irq %d", hdspm->card_name,
+		hdspm->port, hdspm->irq);
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	pci_set_drvdata(pci, card);
+
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_hdspm_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "RME Hammerfall DSP MADI",
+	.id_table = snd_hdspm_ids,
+	.probe = snd_hdspm_probe,
+	.remove = __devexit_p(snd_hdspm_remove),
+};
+
+
+static int __init alsa_card_hdspm_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_hdspm_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_hdspm_init)
+module_exit(alsa_card_hdspm_exit)
diff --git a/linux/sound/pci/rme9652/rme9652.c b/linux/sound/pci/rme9652/rme9652.c
new file mode 100644
index 0000000..c492af5
--- /dev/null
+++ b/linux/sound/pci/rme9652/rme9652.c
@@ -0,0 +1,2652 @@
+/*
+ *   ALSA driver for RME Digi9652 audio interfaces 
+ *
+ *	Copyright (c) 1999 IEM - Winfried Ritsch
+ *      Copyright (c) 1999-2001  Paul Davis
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+
+#include <asm/current.h>
+#include <asm/io.h>
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static int precise_ptr[SNDRV_CARDS];			/* Enable precise pointer */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for RME Digi9652 (Hammerfall) soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for RME Digi9652 (Hammerfall) soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable/disable specific RME96{52,36} soundcards.");
+module_param_array(precise_ptr, bool, NULL, 0444);
+MODULE_PARM_DESC(precise_ptr, "Enable precise pointer (doesn't work reliably).");
+MODULE_AUTHOR("Paul Davis <pbd@op.net>, Winfried Ritsch");
+MODULE_DESCRIPTION("RME Digi9652/Digi9636");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{RME,Hammerfall},"
+		"{RME,Hammerfall-Light}}");
+
+/* The Hammerfall has two sets of 24 ADAT + 2 S/PDIF channels, one for
+   capture, one for playback. Both the ADAT and S/PDIF channels appear
+   to the host CPU in the same block of memory. There is no functional
+   difference between them in terms of access.
+   
+   The Hammerfall Light is identical to the Hammerfall, except that it
+   has 2 sets 18 channels (16 ADAT + 2 S/PDIF) for capture and playback.
+*/
+
+#define RME9652_NCHANNELS       26
+#define RME9636_NCHANNELS       18
+
+/* Preferred sync source choices - used by "sync_pref" control switch */
+
+#define RME9652_SYNC_FROM_SPDIF 0
+#define RME9652_SYNC_FROM_ADAT1 1
+#define RME9652_SYNC_FROM_ADAT2 2
+#define RME9652_SYNC_FROM_ADAT3 3
+
+/* Possible sources of S/PDIF input */
+
+#define RME9652_SPDIFIN_OPTICAL 0	/* optical (ADAT1) */
+#define RME9652_SPDIFIN_COAXIAL 1	/* coaxial (RCA) */
+#define RME9652_SPDIFIN_INTERN  2	/* internal (CDROM) */
+
+/* ------------- Status-Register bits --------------------- */
+
+#define RME9652_IRQ	   (1<<0)	/* IRQ is High if not reset by irq_clear */
+#define RME9652_lock_2	   (1<<1)	/* ADAT 3-PLL: 1=locked, 0=unlocked */
+#define RME9652_lock_1	   (1<<2)	/* ADAT 2-PLL: 1=locked, 0=unlocked */
+#define RME9652_lock_0	   (1<<3)	/* ADAT 1-PLL: 1=locked, 0=unlocked */
+#define RME9652_fs48	   (1<<4)	/* sample rate is 0=44.1/88.2,1=48/96 Khz */
+#define RME9652_wsel_rd	   (1<<5)	/* if Word-Clock is used and valid then 1 */
+                                        /* bits 6-15 encode h/w buffer pointer position */
+#define RME9652_sync_2	   (1<<16)	/* if ADAT-IN 3 in sync to system clock */
+#define RME9652_sync_1	   (1<<17)	/* if ADAT-IN 2 in sync to system clock */
+#define RME9652_sync_0	   (1<<18)	/* if ADAT-IN 1 in sync to system clock */
+#define RME9652_DS_rd	   (1<<19)	/* 1=Double Speed Mode, 0=Normal Speed */
+#define RME9652_tc_busy	   (1<<20)	/* 1=time-code copy in progress (960ms) */
+#define RME9652_tc_out	   (1<<21)	/* time-code out bit */
+#define RME9652_F_0	   (1<<22)	/* 000=64kHz, 100=88.2kHz, 011=96kHz  */
+#define RME9652_F_1	   (1<<23)	/* 111=32kHz, 110=44.1kHz, 101=48kHz, */
+#define RME9652_F_2	   (1<<24)	/* external Crystal Chip if ERF=1 */
+#define RME9652_ERF	   (1<<25)	/* Error-Flag of SDPIF Receiver (1=No Lock) */
+#define RME9652_buffer_id  (1<<26)	/* toggles by each interrupt on rec/play */
+#define RME9652_tc_valid   (1<<27)	/* 1 = a signal is detected on time-code input */
+#define RME9652_SPDIF_READ (1<<28)      /* byte available from Rev 1.5+ S/PDIF interface */
+
+#define RME9652_sync	  (RME9652_sync_0|RME9652_sync_1|RME9652_sync_2)
+#define RME9652_lock	  (RME9652_lock_0|RME9652_lock_1|RME9652_lock_2)
+#define RME9652_F	  (RME9652_F_0|RME9652_F_1|RME9652_F_2)
+#define rme9652_decode_spdif_rate(x) ((x)>>22)
+
+/* Bit 6..15 : h/w buffer pointer */
+
+#define RME9652_buf_pos	  0x000FFC0
+
+/* Bits 31,30,29 are bits 5,4,3 of h/w pointer position on later
+   Rev G EEPROMS and Rev 1.5 cards or later.
+*/ 
+
+#define RME9652_REV15_buf_pos(x) ((((x)&0xE0000000)>>26)|((x)&RME9652_buf_pos))
+
+/* amount of io space we remap for register access. i'm not sure we
+   even need this much, but 1K is nice round number :)
+*/
+
+#define RME9652_IO_EXTENT     1024
+
+#define RME9652_init_buffer       0
+#define RME9652_play_buffer       32	/* holds ptr to 26x64kBit host RAM */
+#define RME9652_rec_buffer        36	/* holds ptr to 26x64kBit host RAM */
+#define RME9652_control_register  64
+#define RME9652_irq_clear         96
+#define RME9652_time_code         100	/* useful if used with alesis adat */
+#define RME9652_thru_base         128	/* 132...228 Thru for 26 channels */
+
+/* Read-only registers */
+
+/* Writing to any of the register locations writes to the status
+   register. We'll use the first location as our point of access.
+*/
+
+#define RME9652_status_register    0
+
+/* --------- Control-Register Bits ---------------- */
+
+
+#define RME9652_start_bit	   (1<<0)	/* start record/play */
+                                                /* bits 1-3 encode buffersize/latency */
+#define RME9652_Master		   (1<<4)	/* Clock Mode Master=1,Slave/Auto=0 */
+#define RME9652_IE		   (1<<5)	/* Interrupt Enable */
+#define RME9652_freq		   (1<<6)       /* samplerate 0=44.1/88.2, 1=48/96 kHz */
+#define RME9652_freq1		   (1<<7)       /* if 0, 32kHz, else always 1 */
+#define RME9652_DS                 (1<<8)	/* Doule Speed 0=44.1/48, 1=88.2/96 Khz */
+#define RME9652_PRO		   (1<<9)	/* S/PDIF out: 0=consumer, 1=professional */
+#define RME9652_EMP		   (1<<10)	/*  Emphasis 0=None, 1=ON */
+#define RME9652_Dolby		   (1<<11)	/*  Non-audio bit 1=set, 0=unset */
+#define RME9652_opt_out	           (1<<12)	/* Use 1st optical OUT as SPDIF: 1=yes,0=no */
+#define RME9652_wsel		   (1<<13)	/* use Wordclock as sync (overwrites master) */
+#define RME9652_inp_0		   (1<<14)	/* SPDIF-IN: 00=optical (ADAT1),     */
+#define RME9652_inp_1		   (1<<15)	/* 01=koaxial (Cinch), 10=Internal CDROM */
+#define RME9652_SyncPref_ADAT2	   (1<<16)
+#define RME9652_SyncPref_ADAT3	   (1<<17)
+#define RME9652_SPDIF_RESET        (1<<18)      /* Rev 1.5+: h/w S/PDIF receiver */
+#define RME9652_SPDIF_SELECT       (1<<19)
+#define RME9652_SPDIF_CLOCK        (1<<20)
+#define RME9652_SPDIF_WRITE        (1<<21)
+#define RME9652_ADAT1_INTERNAL     (1<<22)      /* Rev 1.5+: if set, internal CD connector carries ADAT */
+
+/* buffersize = 512Bytes * 2^n, where n is made from Bit2 ... Bit0 */
+
+#define RME9652_latency            0x0e
+#define rme9652_encode_latency(x)  (((x)&0x7)<<1)
+#define rme9652_decode_latency(x)  (((x)>>1)&0x7)
+#define rme9652_running_double_speed(s) ((s)->control_register & RME9652_DS)
+#define RME9652_inp                (RME9652_inp_0|RME9652_inp_1)
+#define rme9652_encode_spdif_in(x) (((x)&0x3)<<14)
+#define rme9652_decode_spdif_in(x) (((x)>>14)&0x3)
+
+#define RME9652_SyncPref_Mask      (RME9652_SyncPref_ADAT2|RME9652_SyncPref_ADAT3)
+#define RME9652_SyncPref_ADAT1	   0
+#define RME9652_SyncPref_SPDIF	   (RME9652_SyncPref_ADAT2|RME9652_SyncPref_ADAT3)
+
+/* the size of a substream (1 mono data stream) */
+
+#define RME9652_CHANNEL_BUFFER_SAMPLES  (16*1024)
+#define RME9652_CHANNEL_BUFFER_BYTES    (4*RME9652_CHANNEL_BUFFER_SAMPLES)
+
+/* the size of the area we need to allocate for DMA transfers. the
+   size is the same regardless of the number of channels - the 
+   9636 still uses the same memory area.
+
+   Note that we allocate 1 more channel than is apparently needed
+   because the h/w seems to write 1 byte beyond the end of the last
+   page. Sigh.
+*/
+
+#define RME9652_DMA_AREA_BYTES ((RME9652_NCHANNELS+1) * RME9652_CHANNEL_BUFFER_BYTES)
+#define RME9652_DMA_AREA_KILOBYTES (RME9652_DMA_AREA_BYTES/1024)
+
+struct snd_rme9652 {
+	int dev;
+
+	spinlock_t lock;
+	int irq;
+	unsigned long port;
+	void __iomem *iobase;
+	
+	int precise_ptr;
+
+	u32 control_register;	/* cached value */
+	u32 thru_bits;		/* thru 1=on, 0=off channel 1=Bit1... channel 26= Bit26 */
+
+	u32 creg_spdif;
+	u32 creg_spdif_stream;
+
+	char *card_name;		/* hammerfall or hammerfall light names */
+
+        size_t hw_offsetmask;     	/* &-with status register to get real hw_offset */
+	size_t prev_hw_offset;		/* previous hw offset */
+	size_t max_jitter;		/* maximum jitter in frames for 
+					   hw pointer */
+	size_t period_bytes;		/* guess what this is */
+
+	unsigned char ds_channels;
+	unsigned char ss_channels;	/* different for hammerfall/hammerfall-light */
+
+	struct snd_dma_buffer playback_dma_buf;
+	struct snd_dma_buffer capture_dma_buf;
+
+	unsigned char *capture_buffer;	/* suitably aligned address */
+	unsigned char *playback_buffer;	/* suitably aligned address */
+
+	pid_t capture_pid;
+	pid_t playback_pid;
+
+	struct snd_pcm_substream *capture_substream;
+	struct snd_pcm_substream *playback_substream;
+	int running;
+
+        int passthru;                   /* non-zero if doing pass-thru */
+        int hw_rev;                     /* h/w rev * 10 (i.e. 1.5 has hw_rev = 15) */
+
+	int last_spdif_sample_rate;	/* so that we can catch externally ... */
+	int last_adat_sample_rate;	/* ... induced rate changes            */
+
+        char *channel_map;
+
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct pci_dev *pci;
+	struct snd_kcontrol *spdif_ctl;
+
+};
+
+/* These tables map the ALSA channels 1..N to the channels that we
+   need to use in order to find the relevant channel buffer. RME
+   refer to this kind of mapping as between "the ADAT channel and
+   the DMA channel." We index it using the logical audio channel,
+   and the value is the DMA channel (i.e. channel buffer number)
+   where the data for that channel can be read/written from/to.
+*/
+
+static char channel_map_9652_ss[26] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+	18, 19, 20, 21, 22, 23, 24, 25
+};
+
+static char channel_map_9636_ss[26] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 
+	/* channels 16 and 17 are S/PDIF */
+	24, 25,
+	/* channels 18-25 don't exist */
+	-1, -1, -1, -1, -1, -1, -1, -1
+};
+
+static char channel_map_9652_ds[26] = {
+	/* ADAT channels are remapped */
+	1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23,
+	/* channels 12 and 13 are S/PDIF */
+	24, 25,
+	/* others don't exist */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+static char channel_map_9636_ds[26] = {
+	/* ADAT channels are remapped */
+	1, 3, 5, 7, 9, 11, 13, 15,
+	/* channels 8 and 9 are S/PDIF */
+	24, 25
+	/* others don't exist */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+static int snd_hammerfall_get_buffer(struct pci_dev *pci, struct snd_dma_buffer *dmab, size_t size)
+{
+	dmab->dev.type = SNDRV_DMA_TYPE_DEV;
+	dmab->dev.dev = snd_dma_pci_data(pci);
+	if (snd_dma_get_reserved_buf(dmab, snd_dma_pci_buf_id(pci))) {
+		if (dmab->bytes >= size)
+			return 0;
+	}
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci),
+				size, dmab) < 0)
+		return -ENOMEM;
+	return 0;
+}
+
+static void snd_hammerfall_free_buffer(struct snd_dma_buffer *dmab, struct pci_dev *pci)
+{
+	if (dmab->area) {
+		dmab->dev.dev = NULL; /* make it anonymous */
+		snd_dma_reserve_buf(dmab, snd_dma_pci_buf_id(pci));
+	}
+}
+
+
+static DEFINE_PCI_DEVICE_TABLE(snd_rme9652_ids) = {
+	{
+		.vendor	   = 0x10ee,
+		.device	   = 0x3fc4,
+		.subvendor = PCI_ANY_ID,
+		.subdevice = PCI_ANY_ID,
+	},	/* RME Digi9652 */
+	{ 0, },
+};
+
+MODULE_DEVICE_TABLE(pci, snd_rme9652_ids);
+
+static inline void rme9652_write(struct snd_rme9652 *rme9652, int reg, int val)
+{
+	writel(val, rme9652->iobase + reg);
+}
+
+static inline unsigned int rme9652_read(struct snd_rme9652 *rme9652, int reg)
+{
+	return readl(rme9652->iobase + reg);
+}
+
+static inline int snd_rme9652_use_is_exclusive(struct snd_rme9652 *rme9652)
+{
+	unsigned long flags;
+	int ret = 1;
+
+	spin_lock_irqsave(&rme9652->lock, flags);
+	if ((rme9652->playback_pid != rme9652->capture_pid) &&
+	    (rme9652->playback_pid >= 0) && (rme9652->capture_pid >= 0)) {
+		ret = 0;
+	}
+	spin_unlock_irqrestore(&rme9652->lock, flags);
+	return ret;
+}
+
+static inline int rme9652_adat_sample_rate(struct snd_rme9652 *rme9652)
+{
+	if (rme9652_running_double_speed(rme9652)) {
+		return (rme9652_read(rme9652, RME9652_status_register) &
+			RME9652_fs48) ? 96000 : 88200;
+	} else {
+		return (rme9652_read(rme9652, RME9652_status_register) &
+			RME9652_fs48) ? 48000 : 44100;
+	}
+}
+
+static inline void rme9652_compute_period_size(struct snd_rme9652 *rme9652)
+{
+	unsigned int i;
+
+	i = rme9652->control_register & RME9652_latency;
+	rme9652->period_bytes = 1 << ((rme9652_decode_latency(i) + 8));
+	rme9652->hw_offsetmask = 
+		(rme9652->period_bytes * 2 - 1) & RME9652_buf_pos;
+	rme9652->max_jitter = 80;
+}
+
+static snd_pcm_uframes_t rme9652_hw_pointer(struct snd_rme9652 *rme9652)
+{
+	int status;
+	unsigned int offset, frag;
+	snd_pcm_uframes_t period_size = rme9652->period_bytes / 4;
+	snd_pcm_sframes_t delta;
+
+	status = rme9652_read(rme9652, RME9652_status_register);
+	if (!rme9652->precise_ptr)
+		return (status & RME9652_buffer_id) ? period_size : 0;
+	offset = status & RME9652_buf_pos;
+
+	/* The hardware may give a backward movement for up to 80 frames
+           Martin Kirst <martin.kirst@freenet.de> knows the details.
+	*/
+
+	delta = rme9652->prev_hw_offset - offset;
+	delta &= 0xffff;
+	if (delta <= (snd_pcm_sframes_t)rme9652->max_jitter * 4)
+		offset = rme9652->prev_hw_offset;
+	else
+		rme9652->prev_hw_offset = offset;
+	offset &= rme9652->hw_offsetmask;
+	offset /= 4;
+	frag = status & RME9652_buffer_id;
+
+	if (offset < period_size) {
+		if (offset > rme9652->max_jitter) {
+			if (frag)
+				printk(KERN_ERR "Unexpected hw_pointer position (bufid == 0): status: %x offset: %d\n", status, offset);
+		} else if (!frag)
+			return 0;
+		offset -= rme9652->max_jitter;
+		if ((int)offset < 0)
+			offset += period_size * 2;
+	} else {
+		if (offset > period_size + rme9652->max_jitter) {
+			if (!frag)
+				printk(KERN_ERR "Unexpected hw_pointer position (bufid == 1): status: %x offset: %d\n", status, offset);
+		} else if (frag)
+			return period_size;
+		offset -= rme9652->max_jitter;
+	}
+
+	return offset;
+}
+
+static inline void rme9652_reset_hw_pointer(struct snd_rme9652 *rme9652)
+{
+	int i;
+
+	/* reset the FIFO pointer to zero. We do this by writing to 8
+	   registers, each of which is a 32bit wide register, and set
+	   them all to zero. Note that s->iobase is a pointer to
+	   int32, not pointer to char.  
+	*/
+
+	for (i = 0; i < 8; i++) {
+		rme9652_write(rme9652, i * 4, 0);
+		udelay(10);
+	}
+	rme9652->prev_hw_offset = 0;
+}
+
+static inline void rme9652_start(struct snd_rme9652 *s)
+{
+	s->control_register |= (RME9652_IE | RME9652_start_bit);
+	rme9652_write(s, RME9652_control_register, s->control_register);
+}
+
+static inline void rme9652_stop(struct snd_rme9652 *s)
+{
+	s->control_register &= ~(RME9652_start_bit | RME9652_IE);
+	rme9652_write(s, RME9652_control_register, s->control_register);
+}
+
+static int rme9652_set_interrupt_interval(struct snd_rme9652 *s,
+					  unsigned int frames)
+{
+	int restart = 0;
+	int n;
+
+	spin_lock_irq(&s->lock);
+
+	if ((restart = s->running)) {
+		rme9652_stop(s);
+	}
+
+	frames >>= 7;
+	n = 0;
+	while (frames) {
+		n++;
+		frames >>= 1;
+	}
+
+	s->control_register &= ~RME9652_latency;
+	s->control_register |= rme9652_encode_latency(n);
+
+	rme9652_write(s, RME9652_control_register, s->control_register);
+
+	rme9652_compute_period_size(s);
+
+	if (restart)
+		rme9652_start(s);
+
+	spin_unlock_irq(&s->lock);
+
+	return 0;
+}
+
+static int rme9652_set_rate(struct snd_rme9652 *rme9652, int rate)
+{
+	int restart;
+	int reject_if_open = 0;
+	int xrate;
+
+	if (!snd_rme9652_use_is_exclusive (rme9652)) {
+		return -EBUSY;
+	}
+
+	/* Changing from a "single speed" to a "double speed" rate is
+	   not allowed if any substreams are open. This is because
+	   such a change causes a shift in the location of 
+	   the DMA buffers and a reduction in the number of available
+	   buffers. 
+
+	   Note that a similar but essentially insoluble problem
+	   exists for externally-driven rate changes. All we can do
+	   is to flag rate changes in the read/write routines.
+	 */
+
+	spin_lock_irq(&rme9652->lock);
+	xrate = rme9652_adat_sample_rate(rme9652);
+
+	switch (rate) {
+	case 44100:
+		if (xrate > 48000) {
+			reject_if_open = 1;
+		}
+		rate = 0;
+		break;
+	case 48000:
+		if (xrate > 48000) {
+			reject_if_open = 1;
+		}
+		rate = RME9652_freq;
+		break;
+	case 88200:
+		if (xrate < 48000) {
+			reject_if_open = 1;
+		}
+		rate = RME9652_DS;
+		break;
+	case 96000:
+		if (xrate < 48000) {
+			reject_if_open = 1;
+		}
+		rate = RME9652_DS | RME9652_freq;
+		break;
+	default:
+		spin_unlock_irq(&rme9652->lock);
+		return -EINVAL;
+	}
+
+	if (reject_if_open && (rme9652->capture_pid >= 0 || rme9652->playback_pid >= 0)) {
+		spin_unlock_irq(&rme9652->lock);
+		return -EBUSY;
+	}
+
+	if ((restart = rme9652->running)) {
+		rme9652_stop(rme9652);
+	}
+	rme9652->control_register &= ~(RME9652_freq | RME9652_DS);
+	rme9652->control_register |= rate;
+	rme9652_write(rme9652, RME9652_control_register, rme9652->control_register);
+
+	if (restart) {
+		rme9652_start(rme9652);
+	}
+
+	if (rate & RME9652_DS) {
+		if (rme9652->ss_channels == RME9652_NCHANNELS) {
+			rme9652->channel_map = channel_map_9652_ds;
+		} else {
+			rme9652->channel_map = channel_map_9636_ds;
+		}
+	} else {
+		if (rme9652->ss_channels == RME9652_NCHANNELS) {
+			rme9652->channel_map = channel_map_9652_ss;
+		} else {
+			rme9652->channel_map = channel_map_9636_ss;
+		}
+	}
+
+	spin_unlock_irq(&rme9652->lock);
+	return 0;
+}
+
+static void rme9652_set_thru(struct snd_rme9652 *rme9652, int channel, int enable)
+{
+	int i;
+
+	rme9652->passthru = 0;
+
+	if (channel < 0) {
+
+		/* set thru for all channels */
+
+		if (enable) {
+			for (i = 0; i < RME9652_NCHANNELS; i++) {
+				rme9652->thru_bits |= (1 << i);
+				rme9652_write(rme9652, RME9652_thru_base + i * 4, 1);
+			}
+		} else {
+			for (i = 0; i < RME9652_NCHANNELS; i++) {
+				rme9652->thru_bits &= ~(1 << i);
+				rme9652_write(rme9652, RME9652_thru_base + i * 4, 0);
+			}
+		}
+
+	} else {
+		int mapped_channel;
+
+		mapped_channel = rme9652->channel_map[channel];
+
+		if (enable) {
+			rme9652->thru_bits |= (1 << mapped_channel);
+		} else {
+			rme9652->thru_bits &= ~(1 << mapped_channel);
+		}
+
+		rme9652_write(rme9652,
+			       RME9652_thru_base + mapped_channel * 4,
+			       enable ? 1 : 0);			       
+	}
+}
+
+static int rme9652_set_passthru(struct snd_rme9652 *rme9652, int onoff)
+{
+	if (onoff) {
+		rme9652_set_thru(rme9652, -1, 1);
+
+		/* we don't want interrupts, so do a
+		   custom version of rme9652_start().
+		*/
+
+		rme9652->control_register =
+			RME9652_inp_0 | 
+			rme9652_encode_latency(7) |
+			RME9652_start_bit;
+
+		rme9652_reset_hw_pointer(rme9652);
+
+		rme9652_write(rme9652, RME9652_control_register,
+			      rme9652->control_register);
+		rme9652->passthru = 1;
+	} else {
+		rme9652_set_thru(rme9652, -1, 0);
+		rme9652_stop(rme9652);		
+		rme9652->passthru = 0;
+	}
+
+	return 0;
+}
+
+static void rme9652_spdif_set_bit (struct snd_rme9652 *rme9652, int mask, int onoff)
+{
+	if (onoff) 
+		rme9652->control_register |= mask;
+	else 
+		rme9652->control_register &= ~mask;
+		
+	rme9652_write(rme9652, RME9652_control_register, rme9652->control_register);
+}
+
+static void rme9652_spdif_write_byte (struct snd_rme9652 *rme9652, const int val)
+{
+	long mask;
+	long i;
+
+	for (i = 0, mask = 0x80; i < 8; i++, mask >>= 1) {
+		if (val & mask)
+			rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_WRITE, 1);
+		else 
+			rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_WRITE, 0);
+
+		rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_CLOCK, 1);
+		rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_CLOCK, 0);
+	}
+}
+
+static int rme9652_spdif_read_byte (struct snd_rme9652 *rme9652)
+{
+	long mask;
+	long val;
+	long i;
+
+	val = 0;
+
+	for (i = 0, mask = 0x80;  i < 8; i++, mask >>= 1) {
+		rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_CLOCK, 1);
+		if (rme9652_read (rme9652, RME9652_status_register) & RME9652_SPDIF_READ)
+			val |= mask;
+		rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_CLOCK, 0);
+	}
+
+	return val;
+}
+
+static void rme9652_write_spdif_codec (struct snd_rme9652 *rme9652, const int address, const int data)
+{
+	rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 1);
+	rme9652_spdif_write_byte (rme9652, 0x20);
+	rme9652_spdif_write_byte (rme9652, address);
+	rme9652_spdif_write_byte (rme9652, data);
+	rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 0);
+}
+
+
+static int rme9652_spdif_read_codec (struct snd_rme9652 *rme9652, const int address)
+{
+	int ret;
+
+	rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 1);
+	rme9652_spdif_write_byte (rme9652, 0x20);
+	rme9652_spdif_write_byte (rme9652, address);
+	rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 0);
+	rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 1);
+
+	rme9652_spdif_write_byte (rme9652, 0x21);
+	ret = rme9652_spdif_read_byte (rme9652);
+	rme9652_spdif_set_bit (rme9652, RME9652_SPDIF_SELECT, 0);
+
+	return ret;
+}
+
+static void rme9652_initialize_spdif_receiver (struct snd_rme9652 *rme9652)
+{
+	/* XXX what unsets this ? */
+
+	rme9652->control_register |= RME9652_SPDIF_RESET;
+
+	rme9652_write_spdif_codec (rme9652, 4, 0x40);
+	rme9652_write_spdif_codec (rme9652, 17, 0x13);
+	rme9652_write_spdif_codec (rme9652, 6, 0x02);
+}
+
+static inline int rme9652_spdif_sample_rate(struct snd_rme9652 *s)
+{
+	unsigned int rate_bits;
+
+	if (rme9652_read(s, RME9652_status_register) & RME9652_ERF) {
+		return -1;	/* error condition */
+	}
+	
+	if (s->hw_rev == 15) {
+
+		int x, y, ret;
+		
+		x = rme9652_spdif_read_codec (s, 30);
+
+		if (x != 0) 
+			y = 48000 * 64 / x;
+		else
+			y = 0;
+
+		if      (y > 30400 && y < 33600)  ret = 32000; 
+		else if (y > 41900 && y < 46000)  ret = 44100;
+		else if (y > 46000 && y < 50400)  ret = 48000;
+		else if (y > 60800 && y < 67200)  ret = 64000;
+		else if (y > 83700 && y < 92000)  ret = 88200;
+		else if (y > 92000 && y < 100000) ret = 96000;
+		else                              ret = 0;
+		return ret;
+	}
+
+	rate_bits = rme9652_read(s, RME9652_status_register) & RME9652_F;
+
+	switch (rme9652_decode_spdif_rate(rate_bits)) {
+	case 0x7:
+		return 32000;
+		break;
+
+	case 0x6:
+		return 44100;
+		break;
+
+	case 0x5:
+		return 48000;
+		break;
+
+	case 0x4:
+		return 88200;
+		break;
+
+	case 0x3:
+		return 96000;
+		break;
+
+	case 0x0:
+		return 64000;
+		break;
+
+	default:
+		snd_printk(KERN_ERR "%s: unknown S/PDIF input rate (bits = 0x%x)\n",
+			   s->card_name, rate_bits);
+		return 0;
+		break;
+	}
+}
+
+/*-----------------------------------------------------------------------------
+  Control Interface
+  ----------------------------------------------------------------------------*/
+
+static u32 snd_rme9652_convert_from_aes(struct snd_aes_iec958 *aes)
+{
+	u32 val = 0;
+	val |= (aes->status[0] & IEC958_AES0_PROFESSIONAL) ? RME9652_PRO : 0;
+	val |= (aes->status[0] & IEC958_AES0_NONAUDIO) ? RME9652_Dolby : 0;
+	if (val & RME9652_PRO)
+		val |= (aes->status[0] & IEC958_AES0_PRO_EMPHASIS_5015) ? RME9652_EMP : 0;
+	else
+		val |= (aes->status[0] & IEC958_AES0_CON_EMPHASIS_5015) ? RME9652_EMP : 0;
+	return val;
+}
+
+static void snd_rme9652_convert_to_aes(struct snd_aes_iec958 *aes, u32 val)
+{
+	aes->status[0] = ((val & RME9652_PRO) ? IEC958_AES0_PROFESSIONAL : 0) |
+			 ((val & RME9652_Dolby) ? IEC958_AES0_NONAUDIO : 0);
+	if (val & RME9652_PRO)
+		aes->status[0] |= (val & RME9652_EMP) ? IEC958_AES0_PRO_EMPHASIS_5015 : 0;
+	else
+		aes->status[0] |= (val & RME9652_EMP) ? IEC958_AES0_CON_EMPHASIS_5015 : 0;
+}
+
+static int snd_rme9652_control_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_rme9652_control_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	
+	snd_rme9652_convert_to_aes(&ucontrol->value.iec958, rme9652->creg_spdif);
+	return 0;
+}
+
+static int snd_rme9652_control_spdif_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	int change;
+	u32 val;
+	
+	val = snd_rme9652_convert_from_aes(&ucontrol->value.iec958);
+	spin_lock_irq(&rme9652->lock);
+	change = val != rme9652->creg_spdif;
+	rme9652->creg_spdif = val;
+	spin_unlock_irq(&rme9652->lock);
+	return change;
+}
+
+static int snd_rme9652_control_spdif_stream_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_rme9652_control_spdif_stream_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	
+	snd_rme9652_convert_to_aes(&ucontrol->value.iec958, rme9652->creg_spdif_stream);
+	return 0;
+}
+
+static int snd_rme9652_control_spdif_stream_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	int change;
+	u32 val;
+	
+	val = snd_rme9652_convert_from_aes(&ucontrol->value.iec958);
+	spin_lock_irq(&rme9652->lock);
+	change = val != rme9652->creg_spdif_stream;
+	rme9652->creg_spdif_stream = val;
+	rme9652->control_register &= ~(RME9652_PRO | RME9652_Dolby | RME9652_EMP);
+	rme9652_write(rme9652, RME9652_control_register, rme9652->control_register |= val);
+	spin_unlock_irq(&rme9652->lock);
+	return change;
+}
+
+static int snd_rme9652_control_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_rme9652_control_spdif_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = kcontrol->private_value;
+	return 0;
+}
+
+#define RME9652_ADAT1_IN(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_rme9652_info_adat1_in, \
+  .get = snd_rme9652_get_adat1_in, \
+  .put = snd_rme9652_put_adat1_in }
+
+static unsigned int rme9652_adat1_in(struct snd_rme9652 *rme9652)
+{
+	if (rme9652->control_register & RME9652_ADAT1_INTERNAL)
+		return 1; 
+	return 0;
+}
+
+static int rme9652_set_adat1_input(struct snd_rme9652 *rme9652, int internal)
+{
+	int restart = 0;
+
+	if (internal) {
+		rme9652->control_register |= RME9652_ADAT1_INTERNAL;
+	} else {
+		rme9652->control_register &= ~RME9652_ADAT1_INTERNAL;
+	}
+
+	/* XXX do we actually need to stop the card when we do this ? */
+
+	if ((restart = rme9652->running)) {
+		rme9652_stop(rme9652);
+	}
+
+	rme9652_write(rme9652, RME9652_control_register, rme9652->control_register);
+
+	if (restart) {
+		rme9652_start(rme9652);
+	}
+
+	return 0;
+}
+
+static int snd_rme9652_info_adat1_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = {"ADAT1", "Internal"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_rme9652_get_adat1_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&rme9652->lock);
+	ucontrol->value.enumerated.item[0] = rme9652_adat1_in(rme9652);
+	spin_unlock_irq(&rme9652->lock);
+	return 0;
+}
+
+static int snd_rme9652_put_adat1_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+	
+	if (!snd_rme9652_use_is_exclusive(rme9652))
+		return -EBUSY;
+	val = ucontrol->value.enumerated.item[0] % 2;
+	spin_lock_irq(&rme9652->lock);
+	change = val != rme9652_adat1_in(rme9652);
+	if (change)
+		rme9652_set_adat1_input(rme9652, val);
+	spin_unlock_irq(&rme9652->lock);
+	return change;
+}
+
+#define RME9652_SPDIF_IN(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_rme9652_info_spdif_in, \
+  .get = snd_rme9652_get_spdif_in, .put = snd_rme9652_put_spdif_in }
+
+static unsigned int rme9652_spdif_in(struct snd_rme9652 *rme9652)
+{
+	return rme9652_decode_spdif_in(rme9652->control_register &
+				       RME9652_inp);
+}
+
+static int rme9652_set_spdif_input(struct snd_rme9652 *rme9652, int in)
+{
+	int restart = 0;
+
+	rme9652->control_register &= ~RME9652_inp;
+	rme9652->control_register |= rme9652_encode_spdif_in(in);
+
+	if ((restart = rme9652->running)) {
+		rme9652_stop(rme9652);
+	}
+
+	rme9652_write(rme9652, RME9652_control_register, rme9652->control_register);
+
+	if (restart) {
+		rme9652_start(rme9652);
+	}
+
+	return 0;
+}
+
+static int snd_rme9652_info_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[3] = {"ADAT1", "Coaxial", "Internal"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_rme9652_get_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&rme9652->lock);
+	ucontrol->value.enumerated.item[0] = rme9652_spdif_in(rme9652);
+	spin_unlock_irq(&rme9652->lock);
+	return 0;
+}
+
+static int snd_rme9652_put_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+	
+	if (!snd_rme9652_use_is_exclusive(rme9652))
+		return -EBUSY;
+	val = ucontrol->value.enumerated.item[0] % 3;
+	spin_lock_irq(&rme9652->lock);
+	change = val != rme9652_spdif_in(rme9652);
+	if (change)
+		rme9652_set_spdif_input(rme9652, val);
+	spin_unlock_irq(&rme9652->lock);
+	return change;
+}
+
+#define RME9652_SPDIF_OUT(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_rme9652_info_spdif_out, \
+  .get = snd_rme9652_get_spdif_out, .put = snd_rme9652_put_spdif_out }
+
+static int rme9652_spdif_out(struct snd_rme9652 *rme9652)
+{
+	return (rme9652->control_register & RME9652_opt_out) ? 1 : 0;
+}
+
+static int rme9652_set_spdif_output(struct snd_rme9652 *rme9652, int out)
+{
+	int restart = 0;
+
+	if (out) {
+		rme9652->control_register |= RME9652_opt_out;
+	} else {
+		rme9652->control_register &= ~RME9652_opt_out;
+	}
+
+	if ((restart = rme9652->running)) {
+		rme9652_stop(rme9652);
+	}
+
+	rme9652_write(rme9652, RME9652_control_register, rme9652->control_register);
+
+	if (restart) {
+		rme9652_start(rme9652);
+	}
+
+	return 0;
+}
+
+#define snd_rme9652_info_spdif_out	snd_ctl_boolean_mono_info
+
+static int snd_rme9652_get_spdif_out(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&rme9652->lock);
+	ucontrol->value.integer.value[0] = rme9652_spdif_out(rme9652);
+	spin_unlock_irq(&rme9652->lock);
+	return 0;
+}
+
+static int snd_rme9652_put_spdif_out(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+	
+	if (!snd_rme9652_use_is_exclusive(rme9652))
+		return -EBUSY;
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&rme9652->lock);
+	change = (int)val != rme9652_spdif_out(rme9652);
+	rme9652_set_spdif_output(rme9652, val);
+	spin_unlock_irq(&rme9652->lock);
+	return change;
+}
+
+#define RME9652_SYNC_MODE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_rme9652_info_sync_mode, \
+  .get = snd_rme9652_get_sync_mode, .put = snd_rme9652_put_sync_mode }
+
+static int rme9652_sync_mode(struct snd_rme9652 *rme9652)
+{
+	if (rme9652->control_register & RME9652_wsel) {
+		return 2;
+	} else if (rme9652->control_register & RME9652_Master) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+static int rme9652_set_sync_mode(struct snd_rme9652 *rme9652, int mode)
+{
+	int restart = 0;
+
+	switch (mode) {
+	case 0:
+		rme9652->control_register &=
+		    ~(RME9652_Master | RME9652_wsel);
+		break;
+	case 1:
+		rme9652->control_register =
+		    (rme9652->control_register & ~RME9652_wsel) | RME9652_Master;
+		break;
+	case 2:
+		rme9652->control_register |=
+		    (RME9652_Master | RME9652_wsel);
+		break;
+	}
+
+	if ((restart = rme9652->running)) {
+		rme9652_stop(rme9652);
+	}
+
+	rme9652_write(rme9652, RME9652_control_register, rme9652->control_register);
+
+	if (restart) {
+		rme9652_start(rme9652);
+	}
+
+	return 0;
+}
+
+static int snd_rme9652_info_sync_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[3] = {"AutoSync", "Master", "Word Clock"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 3;
+	if (uinfo->value.enumerated.item > 2)
+		uinfo->value.enumerated.item = 2;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_rme9652_get_sync_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&rme9652->lock);
+	ucontrol->value.enumerated.item[0] = rme9652_sync_mode(rme9652);
+	spin_unlock_irq(&rme9652->lock);
+	return 0;
+}
+
+static int snd_rme9652_put_sync_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+	
+	val = ucontrol->value.enumerated.item[0] % 3;
+	spin_lock_irq(&rme9652->lock);
+	change = (int)val != rme9652_sync_mode(rme9652);
+	rme9652_set_sync_mode(rme9652, val);
+	spin_unlock_irq(&rme9652->lock);
+	return change;
+}
+
+#define RME9652_SYNC_PREF(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_rme9652_info_sync_pref, \
+  .get = snd_rme9652_get_sync_pref, .put = snd_rme9652_put_sync_pref }
+
+static int rme9652_sync_pref(struct snd_rme9652 *rme9652)
+{
+	switch (rme9652->control_register & RME9652_SyncPref_Mask) {
+	case RME9652_SyncPref_ADAT1:
+		return RME9652_SYNC_FROM_ADAT1;
+	case RME9652_SyncPref_ADAT2:
+		return RME9652_SYNC_FROM_ADAT2;
+	case RME9652_SyncPref_ADAT3:
+		return RME9652_SYNC_FROM_ADAT3;
+	case RME9652_SyncPref_SPDIF:
+		return RME9652_SYNC_FROM_SPDIF;
+	}
+	/* Not reachable */
+	return 0;
+}
+
+static int rme9652_set_sync_pref(struct snd_rme9652 *rme9652, int pref)
+{
+	int restart;
+
+	rme9652->control_register &= ~RME9652_SyncPref_Mask;
+	switch (pref) {
+	case RME9652_SYNC_FROM_ADAT1:
+		rme9652->control_register |= RME9652_SyncPref_ADAT1;
+		break;
+	case RME9652_SYNC_FROM_ADAT2:
+		rme9652->control_register |= RME9652_SyncPref_ADAT2;
+		break;
+	case RME9652_SYNC_FROM_ADAT3:
+		rme9652->control_register |= RME9652_SyncPref_ADAT3;
+		break;
+	case RME9652_SYNC_FROM_SPDIF:
+		rme9652->control_register |= RME9652_SyncPref_SPDIF;
+		break;
+	}
+
+	if ((restart = rme9652->running)) {
+		rme9652_stop(rme9652);
+	}
+
+	rme9652_write(rme9652, RME9652_control_register, rme9652->control_register);
+
+	if (restart) {
+		rme9652_start(rme9652);
+	}
+
+	return 0;
+}
+
+static int snd_rme9652_info_sync_pref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = {"IEC958 In", "ADAT1 In", "ADAT2 In", "ADAT3 In"};
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = rme9652->ss_channels == RME9652_NCHANNELS ? 4 : 3;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_rme9652_get_sync_pref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&rme9652->lock);
+	ucontrol->value.enumerated.item[0] = rme9652_sync_pref(rme9652);
+	spin_unlock_irq(&rme9652->lock);
+	return 0;
+}
+
+static int snd_rme9652_put_sync_pref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	int change, max;
+	unsigned int val;
+	
+	if (!snd_rme9652_use_is_exclusive(rme9652))
+		return -EBUSY;
+	max = rme9652->ss_channels == RME9652_NCHANNELS ? 4 : 3;
+	val = ucontrol->value.enumerated.item[0] % max;
+	spin_lock_irq(&rme9652->lock);
+	change = (int)val != rme9652_sync_pref(rme9652);
+	rme9652_set_sync_pref(rme9652, val);
+	spin_unlock_irq(&rme9652->lock);
+	return change;
+}
+
+static int snd_rme9652_info_thru(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = rme9652->ss_channels;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_rme9652_get_thru(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	unsigned int k;
+	u32 thru_bits = rme9652->thru_bits;
+
+	for (k = 0; k < rme9652->ss_channels; ++k) {
+		ucontrol->value.integer.value[k] = !!(thru_bits & (1 << k));
+	}
+	return 0;
+}
+
+static int snd_rme9652_put_thru(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int chn;
+	u32 thru_bits = 0;
+
+	if (!snd_rme9652_use_is_exclusive(rme9652))
+		return -EBUSY;
+
+	for (chn = 0; chn < rme9652->ss_channels; ++chn) {
+		if (ucontrol->value.integer.value[chn])
+			thru_bits |= 1 << chn;
+	}
+	
+	spin_lock_irq(&rme9652->lock);
+	change = thru_bits ^ rme9652->thru_bits;
+	if (change) {
+		for (chn = 0; chn < rme9652->ss_channels; ++chn) {
+			if (!(change & (1 << chn)))
+				continue;
+			rme9652_set_thru(rme9652,chn,thru_bits&(1<<chn));
+		}
+	}
+	spin_unlock_irq(&rme9652->lock);
+	return !!change;
+}
+
+#define RME9652_PASSTHRU(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_rme9652_info_passthru, \
+  .put = snd_rme9652_put_passthru, \
+  .get = snd_rme9652_get_passthru }
+
+#define snd_rme9652_info_passthru	snd_ctl_boolean_mono_info
+
+static int snd_rme9652_get_passthru(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&rme9652->lock);
+	ucontrol->value.integer.value[0] = rme9652->passthru;
+	spin_unlock_irq(&rme9652->lock);
+	return 0;
+}
+
+static int snd_rme9652_put_passthru(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	int change;
+	unsigned int val;
+	int err = 0;
+
+	if (!snd_rme9652_use_is_exclusive(rme9652))
+		return -EBUSY;
+
+	val = ucontrol->value.integer.value[0] & 1;
+	spin_lock_irq(&rme9652->lock);
+	change = (ucontrol->value.integer.value[0] != rme9652->passthru);
+	if (change)
+		err = rme9652_set_passthru(rme9652, val);
+	spin_unlock_irq(&rme9652->lock);
+	return err ? err : change;
+}
+
+/* Read-only switches */
+
+#define RME9652_SPDIF_RATE(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_rme9652_info_spdif_rate, \
+  .get = snd_rme9652_get_spdif_rate }
+
+static int snd_rme9652_info_spdif_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 96000;
+	return 0;
+}
+
+static int snd_rme9652_get_spdif_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&rme9652->lock);
+	ucontrol->value.integer.value[0] = rme9652_spdif_sample_rate(rme9652);
+	spin_unlock_irq(&rme9652->lock);
+	return 0;
+}
+
+#define RME9652_ADAT_SYNC(xname, xindex, xidx) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_rme9652_info_adat_sync, \
+  .get = snd_rme9652_get_adat_sync, .private_value = xidx }
+
+static int snd_rme9652_info_adat_sync(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = {"No Lock", "Lock", "No Lock Sync", "Lock Sync"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+		uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_rme9652_get_adat_sync(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	unsigned int mask1, mask2, val;
+	
+	switch (kcontrol->private_value) {
+	case 0: mask1 = RME9652_lock_0; mask2 = RME9652_sync_0; break;	
+	case 1: mask1 = RME9652_lock_1; mask2 = RME9652_sync_1; break;	
+	case 2: mask1 = RME9652_lock_2; mask2 = RME9652_sync_2; break;	
+	default: return -EINVAL;
+	}
+	val = rme9652_read(rme9652, RME9652_status_register);
+	ucontrol->value.enumerated.item[0] = (val & mask1) ? 1 : 0;
+	ucontrol->value.enumerated.item[0] |= (val & mask2) ? 2 : 0;
+	return 0;
+}
+
+#define RME9652_TC_VALID(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+  .info = snd_rme9652_info_tc_valid, \
+  .get = snd_rme9652_get_tc_valid }
+
+#define snd_rme9652_info_tc_valid	snd_ctl_boolean_mono_info
+
+static int snd_rme9652_get_tc_valid(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_rme9652 *rme9652 = snd_kcontrol_chip(kcontrol);
+	
+	ucontrol->value.integer.value[0] = 
+		(rme9652_read(rme9652, RME9652_status_register) & RME9652_tc_valid) ? 1 : 0;
+	return 0;
+}
+
+#ifdef ALSA_HAS_STANDARD_WAY_OF_RETURNING_TIMECODE
+
+/* FIXME: this routine needs a port to the new control API --jk */
+
+static int snd_rme9652_get_tc_value(void *private_data,
+				    snd_kswitch_t *kswitch,
+				    snd_switch_t *uswitch)
+{
+	struct snd_rme9652 *s = (struct snd_rme9652 *) private_data;
+	u32 value;
+	int i;
+
+	uswitch->type = SNDRV_SW_TYPE_DWORD;
+
+	if ((rme9652_read(s, RME9652_status_register) &
+	     RME9652_tc_valid) == 0) {
+		uswitch->value.data32[0] = 0;
+		return 0;
+	}
+
+	/* timecode request */
+
+	rme9652_write(s, RME9652_time_code, 0);
+
+	/* XXX bug alert: loop-based timing !!!! */
+
+	for (i = 0; i < 50; i++) {
+		if (!(rme9652_read(s, i * 4) & RME9652_tc_busy))
+			break;
+	}
+
+	if (!(rme9652_read(s, i * 4) & RME9652_tc_busy)) {
+		return -EIO;
+	}
+
+	value = 0;
+
+	for (i = 0; i < 32; i++) {
+		value >>= 1;
+
+		if (rme9652_read(s, i * 4) & RME9652_tc_out)
+			value |= 0x80000000;
+	}
+
+	if (value > 2 * 60 * 48000) {
+		value -= 2 * 60 * 48000;
+	} else {
+		value = 0;
+	}
+
+	uswitch->value.data32[0] = value;
+
+	return 0;
+}
+
+#endif				/* ALSA_HAS_STANDARD_WAY_OF_RETURNING_TIMECODE */
+
+static struct snd_kcontrol_new snd_rme9652_controls[] = {
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =		snd_rme9652_control_spdif_info,
+	.get =		snd_rme9652_control_spdif_get,
+	.put =		snd_rme9652_control_spdif_put,
+},
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+	.info =		snd_rme9652_control_spdif_stream_info,
+	.get =		snd_rme9652_control_spdif_stream_get,
+	.put =		snd_rme9652_control_spdif_stream_put,
+},
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
+	.info =		snd_rme9652_control_spdif_mask_info,
+	.get =		snd_rme9652_control_spdif_mask_get,
+	.private_value = IEC958_AES0_NONAUDIO |
+			IEC958_AES0_PROFESSIONAL |
+			IEC958_AES0_CON_EMPHASIS,	                                                                                      
+},
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =		SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK),
+	.info =		snd_rme9652_control_spdif_mask_info,
+	.get =		snd_rme9652_control_spdif_mask_get,
+	.private_value = IEC958_AES0_NONAUDIO |
+			IEC958_AES0_PROFESSIONAL |
+			IEC958_AES0_PRO_EMPHASIS,
+},
+RME9652_SPDIF_IN("IEC958 Input Connector", 0),
+RME9652_SPDIF_OUT("IEC958 Output also on ADAT1", 0),
+RME9652_SYNC_MODE("Sync Mode", 0),
+RME9652_SYNC_PREF("Preferred Sync Source", 0),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Channels Thru",
+	.index = 0,
+	.info = snd_rme9652_info_thru,
+	.get = snd_rme9652_get_thru,
+	.put = snd_rme9652_put_thru,
+},
+RME9652_SPDIF_RATE("IEC958 Sample Rate", 0),
+RME9652_ADAT_SYNC("ADAT1 Sync Check", 0, 0),
+RME9652_ADAT_SYNC("ADAT2 Sync Check", 0, 1),
+RME9652_TC_VALID("Timecode Valid", 0),
+RME9652_PASSTHRU("Passthru", 0)
+};
+
+static struct snd_kcontrol_new snd_rme9652_adat3_check =
+RME9652_ADAT_SYNC("ADAT3 Sync Check", 0, 2);
+
+static struct snd_kcontrol_new snd_rme9652_adat1_input =
+RME9652_ADAT1_IN("ADAT1 Input Source", 0);
+
+static int snd_rme9652_create_controls(struct snd_card *card, struct snd_rme9652 *rme9652)
+{
+	unsigned int idx;
+	int err;
+	struct snd_kcontrol *kctl;
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_rme9652_controls); idx++) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_rme9652_controls[idx], rme9652))) < 0)
+			return err;
+		if (idx == 1)	/* IEC958 (S/PDIF) Stream */
+			rme9652->spdif_ctl = kctl;
+	}
+
+	if (rme9652->ss_channels == RME9652_NCHANNELS)
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_rme9652_adat3_check, rme9652))) < 0)
+			return err;
+
+	if (rme9652->hw_rev >= 15)
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_rme9652_adat1_input, rme9652))) < 0)
+			return err;
+
+	return 0;
+}
+
+/*------------------------------------------------------------
+   /proc interface 
+ ------------------------------------------------------------*/
+
+static void
+snd_rme9652_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_rme9652 *rme9652 = (struct snd_rme9652 *) entry->private_data;
+	u32 thru_bits = rme9652->thru_bits;
+	int show_auto_sync_source = 0;
+	int i;
+	unsigned int status;
+	int x;
+
+	status = rme9652_read(rme9652, RME9652_status_register);
+
+	snd_iprintf(buffer, "%s (Card #%d)\n", rme9652->card_name, rme9652->card->number + 1);
+	snd_iprintf(buffer, "Buffers: capture %p playback %p\n",
+		    rme9652->capture_buffer, rme9652->playback_buffer);
+	snd_iprintf(buffer, "IRQ: %d Registers bus: 0x%lx VM: 0x%lx\n",
+		    rme9652->irq, rme9652->port, (unsigned long)rme9652->iobase);
+	snd_iprintf(buffer, "Control register: %x\n", rme9652->control_register);
+
+	snd_iprintf(buffer, "\n");
+
+	x = 1 << (6 + rme9652_decode_latency(rme9652->control_register & 
+					     RME9652_latency));
+
+	snd_iprintf(buffer, "Latency: %d samples (2 periods of %lu bytes)\n", 
+		    x, (unsigned long) rme9652->period_bytes);
+	snd_iprintf(buffer, "Hardware pointer (frames): %ld\n",
+		    rme9652_hw_pointer(rme9652));
+	snd_iprintf(buffer, "Passthru: %s\n",
+		    rme9652->passthru ? "yes" : "no");
+
+	if ((rme9652->control_register & (RME9652_Master | RME9652_wsel)) == 0) {
+		snd_iprintf(buffer, "Clock mode: autosync\n");
+		show_auto_sync_source = 1;
+	} else if (rme9652->control_register & RME9652_wsel) {
+		if (status & RME9652_wsel_rd) {
+			snd_iprintf(buffer, "Clock mode: word clock\n");
+		} else {
+			snd_iprintf(buffer, "Clock mode: word clock (no signal)\n");
+		}
+	} else {
+		snd_iprintf(buffer, "Clock mode: master\n");
+	}
+
+	if (show_auto_sync_source) {
+		switch (rme9652->control_register & RME9652_SyncPref_Mask) {
+		case RME9652_SyncPref_ADAT1:
+			snd_iprintf(buffer, "Pref. sync source: ADAT1\n");
+			break;
+		case RME9652_SyncPref_ADAT2:
+			snd_iprintf(buffer, "Pref. sync source: ADAT2\n");
+			break;
+		case RME9652_SyncPref_ADAT3:
+			snd_iprintf(buffer, "Pref. sync source: ADAT3\n");
+			break;
+		case RME9652_SyncPref_SPDIF:
+			snd_iprintf(buffer, "Pref. sync source: IEC958\n");
+			break;
+		default:
+			snd_iprintf(buffer, "Pref. sync source: ???\n");
+		}
+	}
+
+	if (rme9652->hw_rev >= 15)
+		snd_iprintf(buffer, "\nADAT1 Input source: %s\n",
+			    (rme9652->control_register & RME9652_ADAT1_INTERNAL) ?
+			    "Internal" : "ADAT1 optical");
+
+	snd_iprintf(buffer, "\n");
+
+	switch (rme9652_decode_spdif_in(rme9652->control_register & 
+					RME9652_inp)) {
+	case RME9652_SPDIFIN_OPTICAL:
+		snd_iprintf(buffer, "IEC958 input: ADAT1\n");
+		break;
+	case RME9652_SPDIFIN_COAXIAL:
+		snd_iprintf(buffer, "IEC958 input: Coaxial\n");
+		break;
+	case RME9652_SPDIFIN_INTERN:
+		snd_iprintf(buffer, "IEC958 input: Internal\n");
+		break;
+	default:
+		snd_iprintf(buffer, "IEC958 input: ???\n");
+		break;
+	}
+
+	if (rme9652->control_register & RME9652_opt_out) {
+		snd_iprintf(buffer, "IEC958 output: Coaxial & ADAT1\n");
+	} else {
+		snd_iprintf(buffer, "IEC958 output: Coaxial only\n");
+	}
+
+	if (rme9652->control_register & RME9652_PRO) {
+		snd_iprintf(buffer, "IEC958 quality: Professional\n");
+	} else {
+		snd_iprintf(buffer, "IEC958 quality: Consumer\n");
+	}
+
+	if (rme9652->control_register & RME9652_EMP) {
+		snd_iprintf(buffer, "IEC958 emphasis: on\n");
+	} else {
+		snd_iprintf(buffer, "IEC958 emphasis: off\n");
+	}
+
+	if (rme9652->control_register & RME9652_Dolby) {
+		snd_iprintf(buffer, "IEC958 Dolby: on\n");
+	} else {
+		snd_iprintf(buffer, "IEC958 Dolby: off\n");
+	}
+
+	i = rme9652_spdif_sample_rate(rme9652);
+
+	if (i < 0) {
+		snd_iprintf(buffer,
+			    "IEC958 sample rate: error flag set\n");
+	} else if (i == 0) {
+		snd_iprintf(buffer, "IEC958 sample rate: undetermined\n");
+	} else {
+		snd_iprintf(buffer, "IEC958 sample rate: %d\n", i);
+	}
+
+	snd_iprintf(buffer, "\n");
+
+	snd_iprintf(buffer, "ADAT Sample rate: %dHz\n",
+		    rme9652_adat_sample_rate(rme9652));
+
+	/* Sync Check */
+
+	x = status & RME9652_sync_0;
+	if (status & RME9652_lock_0) {
+		snd_iprintf(buffer, "ADAT1: %s\n", x ? "Sync" : "Lock");
+	} else {
+		snd_iprintf(buffer, "ADAT1: No Lock\n");
+	}
+
+	x = status & RME9652_sync_1;
+	if (status & RME9652_lock_1) {
+		snd_iprintf(buffer, "ADAT2: %s\n", x ? "Sync" : "Lock");
+	} else {
+		snd_iprintf(buffer, "ADAT2: No Lock\n");
+	}
+
+	x = status & RME9652_sync_2;
+	if (status & RME9652_lock_2) {
+		snd_iprintf(buffer, "ADAT3: %s\n", x ? "Sync" : "Lock");
+	} else {
+		snd_iprintf(buffer, "ADAT3: No Lock\n");
+	}
+
+	snd_iprintf(buffer, "\n");
+
+	snd_iprintf(buffer, "Timecode signal: %s\n",
+		    (status & RME9652_tc_valid) ? "yes" : "no");
+
+	/* thru modes */
+
+	snd_iprintf(buffer, "Punch Status:\n\n");
+
+	for (i = 0; i < rme9652->ss_channels; i++) {
+		if (thru_bits & (1 << i)) {
+			snd_iprintf(buffer, "%2d:  on ", i + 1);
+		} else {
+			snd_iprintf(buffer, "%2d: off ", i + 1);
+		}
+
+		if (((i + 1) % 8) == 0) {
+			snd_iprintf(buffer, "\n");
+		}
+	}
+
+	snd_iprintf(buffer, "\n");
+}
+
+static void __devinit snd_rme9652_proc_init(struct snd_rme9652 *rme9652)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(rme9652->card, "rme9652", &entry))
+		snd_info_set_text_ops(entry, rme9652, snd_rme9652_proc_read);
+}
+
+static void snd_rme9652_free_buffers(struct snd_rme9652 *rme9652)
+{
+	snd_hammerfall_free_buffer(&rme9652->capture_dma_buf, rme9652->pci);
+	snd_hammerfall_free_buffer(&rme9652->playback_dma_buf, rme9652->pci);
+}
+
+static int snd_rme9652_free(struct snd_rme9652 *rme9652)
+{
+	if (rme9652->irq >= 0)
+		rme9652_stop(rme9652);
+	snd_rme9652_free_buffers(rme9652);
+
+	if (rme9652->irq >= 0)
+		free_irq(rme9652->irq, (void *)rme9652);
+	if (rme9652->iobase)
+		iounmap(rme9652->iobase);
+	if (rme9652->port)
+		pci_release_regions(rme9652->pci);
+
+	pci_disable_device(rme9652->pci);
+	return 0;
+}
+
+static int __devinit snd_rme9652_initialize_memory(struct snd_rme9652 *rme9652)
+{
+	unsigned long pb_bus, cb_bus;
+
+	if (snd_hammerfall_get_buffer(rme9652->pci, &rme9652->capture_dma_buf, RME9652_DMA_AREA_BYTES) < 0 ||
+	    snd_hammerfall_get_buffer(rme9652->pci, &rme9652->playback_dma_buf, RME9652_DMA_AREA_BYTES) < 0) {
+		if (rme9652->capture_dma_buf.area)
+			snd_dma_free_pages(&rme9652->capture_dma_buf);
+		printk(KERN_ERR "%s: no buffers available\n", rme9652->card_name);
+		return -ENOMEM;
+	}
+
+	/* Align to bus-space 64K boundary */
+
+	cb_bus = ALIGN(rme9652->capture_dma_buf.addr, 0x10000ul);
+	pb_bus = ALIGN(rme9652->playback_dma_buf.addr, 0x10000ul);
+
+	/* Tell the card where it is */
+
+	rme9652_write(rme9652, RME9652_rec_buffer, cb_bus);
+	rme9652_write(rme9652, RME9652_play_buffer, pb_bus);
+
+	rme9652->capture_buffer = rme9652->capture_dma_buf.area + (cb_bus - rme9652->capture_dma_buf.addr);
+	rme9652->playback_buffer = rme9652->playback_dma_buf.area + (pb_bus - rme9652->playback_dma_buf.addr);
+
+	return 0;
+}
+
+static void snd_rme9652_set_defaults(struct snd_rme9652 *rme9652)
+{
+	unsigned int k;
+
+	/* ASSUMPTION: rme9652->lock is either held, or
+	   there is no need to hold it (e.g. during module
+	   initialization).
+	 */
+
+	/* set defaults:
+
+	   SPDIF Input via Coax 
+	   autosync clock mode
+	   maximum latency (7 = 8192 samples, 64Kbyte buffer,
+	   which implies 2 4096 sample, 32Kbyte periods).
+	   
+	   if rev 1.5, initialize the S/PDIF receiver.
+
+	 */
+
+	rme9652->control_register =
+	    RME9652_inp_0 | rme9652_encode_latency(7);
+
+	rme9652_write(rme9652, RME9652_control_register, rme9652->control_register);
+
+	rme9652_reset_hw_pointer(rme9652);
+	rme9652_compute_period_size(rme9652);
+
+	/* default: thru off for all channels */
+
+	for (k = 0; k < RME9652_NCHANNELS; ++k)
+		rme9652_write(rme9652, RME9652_thru_base + k * 4, 0);
+
+	rme9652->thru_bits = 0;
+	rme9652->passthru = 0;
+
+	/* set a default rate so that the channel map is set up */
+
+	rme9652_set_rate(rme9652, 48000);
+}
+
+static irqreturn_t snd_rme9652_interrupt(int irq, void *dev_id)
+{
+	struct snd_rme9652 *rme9652 = (struct snd_rme9652 *) dev_id;
+
+	if (!(rme9652_read(rme9652, RME9652_status_register) & RME9652_IRQ)) {
+		return IRQ_NONE;
+	}
+
+	rme9652_write(rme9652, RME9652_irq_clear, 0);
+
+	if (rme9652->capture_substream) {
+		snd_pcm_period_elapsed(rme9652->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream);
+	}
+
+	if (rme9652->playback_substream) {
+		snd_pcm_period_elapsed(rme9652->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream);
+	}
+	return IRQ_HANDLED;
+}
+
+static snd_pcm_uframes_t snd_rme9652_hw_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+	return rme9652_hw_pointer(rme9652);
+}
+
+static char *rme9652_channel_buffer_location(struct snd_rme9652 *rme9652,
+					     int stream,
+					     int channel)
+
+{
+	int mapped_channel;
+
+	if (snd_BUG_ON(channel < 0 || channel >= RME9652_NCHANNELS))
+		return NULL;
+        
+	if ((mapped_channel = rme9652->channel_map[channel]) < 0) {
+		return NULL;
+	}
+	
+	if (stream == SNDRV_PCM_STREAM_CAPTURE) {
+		return rme9652->capture_buffer +
+			(mapped_channel * RME9652_CHANNEL_BUFFER_BYTES);
+	} else {
+		return rme9652->playback_buffer +
+			(mapped_channel * RME9652_CHANNEL_BUFFER_BYTES);
+	}
+}
+
+static int snd_rme9652_playback_copy(struct snd_pcm_substream *substream, int channel,
+				     snd_pcm_uframes_t pos, void __user *src, snd_pcm_uframes_t count)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+	char *channel_buf;
+
+	if (snd_BUG_ON(pos + count > RME9652_CHANNEL_BUFFER_BYTES / 4))
+		return -EINVAL;
+
+	channel_buf = rme9652_channel_buffer_location (rme9652,
+						       substream->pstr->stream,
+						       channel);
+	if (snd_BUG_ON(!channel_buf))
+		return -EIO;
+	if (copy_from_user(channel_buf + pos * 4, src, count * 4))
+		return -EFAULT;
+	return count;
+}
+
+static int snd_rme9652_capture_copy(struct snd_pcm_substream *substream, int channel,
+				    snd_pcm_uframes_t pos, void __user *dst, snd_pcm_uframes_t count)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+	char *channel_buf;
+
+	if (snd_BUG_ON(pos + count > RME9652_CHANNEL_BUFFER_BYTES / 4))
+		return -EINVAL;
+
+	channel_buf = rme9652_channel_buffer_location (rme9652,
+						       substream->pstr->stream,
+						       channel);
+	if (snd_BUG_ON(!channel_buf))
+		return -EIO;
+	if (copy_to_user(dst, channel_buf + pos * 4, count * 4))
+		return -EFAULT;
+	return count;
+}
+
+static int snd_rme9652_hw_silence(struct snd_pcm_substream *substream, int channel,
+				  snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+	char *channel_buf;
+
+	channel_buf = rme9652_channel_buffer_location (rme9652,
+						       substream->pstr->stream,
+						       channel);
+	if (snd_BUG_ON(!channel_buf))
+		return -EIO;
+	memset(channel_buf + pos * 4, 0, count * 4);
+	return count;
+}
+
+static int snd_rme9652_reset(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *other;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		other = rme9652->capture_substream;
+	else
+		other = rme9652->playback_substream;
+	if (rme9652->running)
+		runtime->status->hw_ptr = rme9652_hw_pointer(rme9652);
+	else
+		runtime->status->hw_ptr = 0;
+	if (other) {
+		struct snd_pcm_substream *s;
+		struct snd_pcm_runtime *oruntime = other->runtime;
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s == other) {
+				oruntime->status->hw_ptr = runtime->status->hw_ptr;
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+static int snd_rme9652_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+	int err;
+	pid_t this_pid;
+	pid_t other_pid;
+
+	spin_lock_irq(&rme9652->lock);
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		rme9652->control_register &= ~(RME9652_PRO | RME9652_Dolby | RME9652_EMP);
+		rme9652_write(rme9652, RME9652_control_register, rme9652->control_register |= rme9652->creg_spdif_stream);
+		this_pid = rme9652->playback_pid;
+		other_pid = rme9652->capture_pid;
+	} else {
+		this_pid = rme9652->capture_pid;
+		other_pid = rme9652->playback_pid;
+	}
+
+	if ((other_pid > 0) && (this_pid != other_pid)) {
+
+		/* The other stream is open, and not by the same
+		   task as this one. Make sure that the parameters
+		   that matter are the same.
+		 */
+
+		if ((int)params_rate(params) !=
+		    rme9652_adat_sample_rate(rme9652)) {
+			spin_unlock_irq(&rme9652->lock);
+			_snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE);
+			return -EBUSY;
+		}
+
+		if (params_period_size(params) != rme9652->period_bytes / 4) {
+			spin_unlock_irq(&rme9652->lock);
+			_snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+			return -EBUSY;
+		}
+
+		/* We're fine. */
+
+		spin_unlock_irq(&rme9652->lock);
+ 		return 0;
+
+	} else {
+		spin_unlock_irq(&rme9652->lock);
+	}
+
+	/* how to make sure that the rate matches an externally-set one ?
+	 */
+
+	if ((err = rme9652_set_rate(rme9652, params_rate(params))) < 0) {
+		_snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE);
+		return err;
+	}
+
+	if ((err = rme9652_set_interrupt_interval(rme9652, params_period_size(params))) < 0) {
+		_snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+		return err;
+	}
+
+	return 0;
+}
+
+static int snd_rme9652_channel_info(struct snd_pcm_substream *substream,
+				    struct snd_pcm_channel_info *info)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+	int chn;
+
+	if (snd_BUG_ON(info->channel >= RME9652_NCHANNELS))
+		return -EINVAL;
+
+	if ((chn = rme9652->channel_map[info->channel]) < 0) {
+		return -EINVAL;
+	}
+
+	info->offset = chn * RME9652_CHANNEL_BUFFER_BYTES;
+	info->first = 0;
+	info->step = 32;
+	return 0;
+}
+
+static int snd_rme9652_ioctl(struct snd_pcm_substream *substream,
+			     unsigned int cmd, void *arg)
+{
+	switch (cmd) {
+	case SNDRV_PCM_IOCTL1_RESET:
+	{
+		return snd_rme9652_reset(substream);
+	}
+	case SNDRV_PCM_IOCTL1_CHANNEL_INFO:
+	{
+		struct snd_pcm_channel_info *info = arg;
+		return snd_rme9652_channel_info(substream, info);
+	}
+	default:
+		break;
+	}
+
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static void rme9652_silence_playback(struct snd_rme9652 *rme9652)
+{
+	memset(rme9652->playback_buffer, 0, RME9652_DMA_AREA_BYTES);
+}
+
+static int snd_rme9652_trigger(struct snd_pcm_substream *substream,
+			       int cmd)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *other;
+	int running;
+	spin_lock(&rme9652->lock);
+	running = rme9652->running;
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		running |= 1 << substream->stream;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		running &= ~(1 << substream->stream);
+		break;
+	default:
+		snd_BUG();
+		spin_unlock(&rme9652->lock);
+		return -EINVAL;
+	}
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		other = rme9652->capture_substream;
+	else
+		other = rme9652->playback_substream;
+
+	if (other) {
+		struct snd_pcm_substream *s;
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s == other) {
+				snd_pcm_trigger_done(s, substream);
+				if (cmd == SNDRV_PCM_TRIGGER_START)
+					running |= 1 << s->stream;
+				else
+					running &= ~(1 << s->stream);
+				goto _ok;
+			}
+		}
+		if (cmd == SNDRV_PCM_TRIGGER_START) {
+			if (!(running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) &&
+			    substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+				rme9652_silence_playback(rme9652);
+		} else {
+			if (running &&
+			    substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+				rme9652_silence_playback(rme9652);
+		}
+	} else {
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 
+			rme9652_silence_playback(rme9652);
+	}
+ _ok:
+	snd_pcm_trigger_done(substream, substream);
+	if (!rme9652->running && running)
+		rme9652_start(rme9652);
+	else if (rme9652->running && !running)
+		rme9652_stop(rme9652);
+	rme9652->running = running;
+	spin_unlock(&rme9652->lock);
+
+	return 0;
+}
+
+static int snd_rme9652_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+	int result = 0;
+
+	spin_lock_irqsave(&rme9652->lock, flags);
+	if (!rme9652->running)
+		rme9652_reset_hw_pointer(rme9652);
+	spin_unlock_irqrestore(&rme9652->lock, flags);
+	return result;
+}
+
+static struct snd_pcm_hardware snd_rme9652_playback_subinfo =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_NONINTERLEAVED |
+				 SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_DOUBLE),
+	.formats =		SNDRV_PCM_FMTBIT_S32_LE,
+	.rates =		(SNDRV_PCM_RATE_44100 | 
+				 SNDRV_PCM_RATE_48000 | 
+				 SNDRV_PCM_RATE_88200 | 
+				 SNDRV_PCM_RATE_96000),
+	.rate_min =		44100,
+	.rate_max =		96000,
+	.channels_min =		10,
+	.channels_max =		26,
+	.buffer_bytes_max =	RME9652_CHANNEL_BUFFER_BYTES * 26,
+	.period_bytes_min =	(64 * 4) * 10,
+	.period_bytes_max =	(8192 * 4) * 26,
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_rme9652_capture_subinfo =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_NONINTERLEAVED |
+				 SNDRV_PCM_INFO_SYNC_START),
+	.formats =		SNDRV_PCM_FMTBIT_S32_LE,
+	.rates =		(SNDRV_PCM_RATE_44100 | 
+				 SNDRV_PCM_RATE_48000 | 
+				 SNDRV_PCM_RATE_88200 | 
+				 SNDRV_PCM_RATE_96000),
+	.rate_min =		44100,
+	.rate_max =		96000,
+	.channels_min =		10,
+	.channels_max =		26,
+	.buffer_bytes_max =	RME9652_CHANNEL_BUFFER_BYTES *26,
+	.period_bytes_min =	(64 * 4) * 10,
+	.period_bytes_max =	(8192 * 4) * 26,
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		0,
+};
+
+static unsigned int period_sizes[] = { 64, 128, 256, 512, 1024, 2048, 4096, 8192 };
+
+static struct snd_pcm_hw_constraint_list hw_constraints_period_sizes = {
+	.count = ARRAY_SIZE(period_sizes),
+	.list = period_sizes,
+	.mask = 0
+};
+
+static int snd_rme9652_hw_rule_channels(struct snd_pcm_hw_params *params,
+					struct snd_pcm_hw_rule *rule)
+{
+	struct snd_rme9652 *rme9652 = rule->private;
+	struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	unsigned int list[2] = { rme9652->ds_channels, rme9652->ss_channels };
+	return snd_interval_list(c, 2, list, 0);
+}
+
+static int snd_rme9652_hw_rule_channels_rate(struct snd_pcm_hw_params *params,
+					     struct snd_pcm_hw_rule *rule)
+{
+	struct snd_rme9652 *rme9652 = rule->private;
+	struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	if (r->min > 48000) {
+		struct snd_interval t = {
+			.min = rme9652->ds_channels,
+			.max = rme9652->ds_channels,
+			.integer = 1,
+		};
+		return snd_interval_refine(c, &t);
+	} else if (r->max < 88200) {
+		struct snd_interval t = {
+			.min = rme9652->ss_channels,
+			.max = rme9652->ss_channels,
+			.integer = 1,
+		};
+		return snd_interval_refine(c, &t);
+	}
+	return 0;
+}
+
+static int snd_rme9652_hw_rule_rate_channels(struct snd_pcm_hw_params *params,
+					     struct snd_pcm_hw_rule *rule)
+{
+	struct snd_rme9652 *rme9652 = rule->private;
+	struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	if (c->min >= rme9652->ss_channels) {
+		struct snd_interval t = {
+			.min = 44100,
+			.max = 48000,
+			.integer = 1,
+		};
+		return snd_interval_refine(r, &t);
+	} else if (c->max <= rme9652->ds_channels) {
+		struct snd_interval t = {
+			.min = 88200,
+			.max = 96000,
+			.integer = 1,
+		};
+		return snd_interval_refine(r, &t);
+	}
+	return 0;
+}
+
+static int snd_rme9652_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	spin_lock_irq(&rme9652->lock);
+
+	snd_pcm_set_sync(substream);
+
+        runtime->hw = snd_rme9652_playback_subinfo;
+	runtime->dma_area = rme9652->playback_buffer;
+	runtime->dma_bytes = RME9652_DMA_AREA_BYTES;
+
+	if (rme9652->capture_substream == NULL) {
+		rme9652_stop(rme9652);
+		rme9652_set_thru(rme9652, -1, 0);
+	}
+
+	rme9652->playback_pid = current->pid;
+	rme9652->playback_substream = substream;
+
+	spin_unlock_irq(&rme9652->lock);
+
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hw_constraints_period_sizes);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+			     snd_rme9652_hw_rule_channels, rme9652,
+			     SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+			     snd_rme9652_hw_rule_channels_rate, rme9652,
+			     SNDRV_PCM_HW_PARAM_RATE, -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+			     snd_rme9652_hw_rule_rate_channels, rme9652,
+			     SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+
+	rme9652->creg_spdif_stream = rme9652->creg_spdif;
+	rme9652->spdif_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(rme9652->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &rme9652->spdif_ctl->id);
+	return 0;
+}
+
+static int snd_rme9652_playback_release(struct snd_pcm_substream *substream)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&rme9652->lock);
+
+	rme9652->playback_pid = -1;
+	rme9652->playback_substream = NULL;
+
+	spin_unlock_irq(&rme9652->lock);
+
+	rme9652->spdif_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(rme9652->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &rme9652->spdif_ctl->id);
+	return 0;
+}
+
+
+static int snd_rme9652_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	spin_lock_irq(&rme9652->lock);
+
+	snd_pcm_set_sync(substream);
+
+	runtime->hw = snd_rme9652_capture_subinfo;
+	runtime->dma_area = rme9652->capture_buffer;
+	runtime->dma_bytes = RME9652_DMA_AREA_BYTES;
+
+	if (rme9652->playback_substream == NULL) {
+		rme9652_stop(rme9652);
+		rme9652_set_thru(rme9652, -1, 0);
+	}
+
+	rme9652->capture_pid = current->pid;
+	rme9652->capture_substream = substream;
+
+	spin_unlock_irq(&rme9652->lock);
+
+	snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, &hw_constraints_period_sizes);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+			     snd_rme9652_hw_rule_channels, rme9652,
+			     SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+			     snd_rme9652_hw_rule_channels_rate, rme9652,
+			     SNDRV_PCM_HW_PARAM_RATE, -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+			     snd_rme9652_hw_rule_rate_channels, rme9652,
+			     SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	return 0;
+}
+
+static int snd_rme9652_capture_release(struct snd_pcm_substream *substream)
+{
+	struct snd_rme9652 *rme9652 = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&rme9652->lock);
+
+	rme9652->capture_pid = -1;
+	rme9652->capture_substream = NULL;
+
+	spin_unlock_irq(&rme9652->lock);
+	return 0;
+}
+
+static struct snd_pcm_ops snd_rme9652_playback_ops = {
+	.open =		snd_rme9652_playback_open,
+	.close =	snd_rme9652_playback_release,
+	.ioctl =	snd_rme9652_ioctl,
+	.hw_params =	snd_rme9652_hw_params,
+	.prepare =	snd_rme9652_prepare,
+	.trigger =	snd_rme9652_trigger,
+	.pointer =	snd_rme9652_hw_pointer,
+	.copy =		snd_rme9652_playback_copy,
+	.silence =	snd_rme9652_hw_silence,
+};
+
+static struct snd_pcm_ops snd_rme9652_capture_ops = {
+	.open =		snd_rme9652_capture_open,
+	.close =	snd_rme9652_capture_release,
+	.ioctl =	snd_rme9652_ioctl,
+	.hw_params =	snd_rme9652_hw_params,
+	.prepare =	snd_rme9652_prepare,
+	.trigger =	snd_rme9652_trigger,
+	.pointer =	snd_rme9652_hw_pointer,
+	.copy =		snd_rme9652_capture_copy,
+};
+
+static int __devinit snd_rme9652_create_pcm(struct snd_card *card,
+					 struct snd_rme9652 *rme9652)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(card,
+			       rme9652->card_name,
+			       0, 1, 1, &pcm)) < 0) {
+		return err;
+	}
+
+	rme9652->pcm = pcm;
+	pcm->private_data = rme9652;
+	strcpy(pcm->name, rme9652->card_name);
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_rme9652_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_rme9652_capture_ops);
+
+	pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+
+	return 0;
+}
+
+static int __devinit snd_rme9652_create(struct snd_card *card,
+				     struct snd_rme9652 *rme9652,
+				     int precise_ptr)
+{
+	struct pci_dev *pci = rme9652->pci;
+	int err;
+	int status;
+	unsigned short rev;
+
+	rme9652->irq = -1;
+	rme9652->card = card;
+
+	pci_read_config_word(rme9652->pci, PCI_CLASS_REVISION, &rev);
+
+	switch (rev & 0xff) {
+	case 3:
+	case 4:
+	case 8:
+	case 9:
+		break;
+
+	default:
+		/* who knows? */
+		return -ENODEV;
+	}
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	spin_lock_init(&rme9652->lock);
+
+	if ((err = pci_request_regions(pci, "rme9652")) < 0)
+		return err;
+	rme9652->port = pci_resource_start(pci, 0);
+	rme9652->iobase = ioremap_nocache(rme9652->port, RME9652_IO_EXTENT);
+	if (rme9652->iobase == NULL) {
+		snd_printk(KERN_ERR "unable to remap region 0x%lx-0x%lx\n", rme9652->port, rme9652->port + RME9652_IO_EXTENT - 1);
+		return -EBUSY;
+	}
+	
+	if (request_irq(pci->irq, snd_rme9652_interrupt, IRQF_SHARED,
+			"rme9652", rme9652)) {
+		snd_printk(KERN_ERR "unable to request IRQ %d\n", pci->irq);
+		return -EBUSY;
+	}
+	rme9652->irq = pci->irq;
+	rme9652->precise_ptr = precise_ptr;
+
+	/* Determine the h/w rev level of the card. This seems like
+	   a particularly kludgy way to encode it, but its what RME
+	   chose to do, so we follow them ...
+	*/
+
+	status = rme9652_read(rme9652, RME9652_status_register);
+	if (rme9652_decode_spdif_rate(status&RME9652_F) == 1) {
+		rme9652->hw_rev = 15;
+	} else {
+		rme9652->hw_rev = 11;
+	}
+
+	/* Differentiate between the standard Hammerfall, and the
+	   "Light", which does not have the expansion board. This
+	   method comes from information received from Mathhias
+	   Clausen at RME. Display the EEPROM and h/w revID where
+	   relevant.  
+	*/
+
+	switch (rev) {
+	case 8: /* original eprom */
+		strcpy(card->driver, "RME9636");
+		if (rme9652->hw_rev == 15) {
+			rme9652->card_name = "RME Digi9636 (Rev 1.5)";
+		} else {
+			rme9652->card_name = "RME Digi9636";
+		}
+		rme9652->ss_channels = RME9636_NCHANNELS;
+		break;
+	case 9: /* W36_G EPROM */
+		strcpy(card->driver, "RME9636");
+		rme9652->card_name = "RME Digi9636 (Rev G)";
+		rme9652->ss_channels = RME9636_NCHANNELS;
+		break;
+	case 4: /* W52_G EPROM */
+		strcpy(card->driver, "RME9652");
+		rme9652->card_name = "RME Digi9652 (Rev G)";
+		rme9652->ss_channels = RME9652_NCHANNELS;
+		break;
+	case 3: /* original eprom */
+		strcpy(card->driver, "RME9652");
+		if (rme9652->hw_rev == 15) {
+			rme9652->card_name = "RME Digi9652 (Rev 1.5)";
+		} else {
+			rme9652->card_name = "RME Digi9652";
+		}
+		rme9652->ss_channels = RME9652_NCHANNELS;
+		break;
+	}
+
+	rme9652->ds_channels = (rme9652->ss_channels - 2) / 2 + 2;
+
+	pci_set_master(rme9652->pci);
+
+	if ((err = snd_rme9652_initialize_memory(rme9652)) < 0) {
+		return err;
+	}
+
+	if ((err = snd_rme9652_create_pcm(card, rme9652)) < 0) {
+		return err;
+	}
+
+	if ((err = snd_rme9652_create_controls(card, rme9652)) < 0) {
+		return err;
+	}
+
+	snd_rme9652_proc_init(rme9652);
+
+	rme9652->last_spdif_sample_rate = -1;
+	rme9652->last_adat_sample_rate = -1;
+	rme9652->playback_pid = -1;
+	rme9652->capture_pid = -1;
+	rme9652->capture_substream = NULL;
+	rme9652->playback_substream = NULL;
+
+	snd_rme9652_set_defaults(rme9652);
+
+	if (rme9652->hw_rev == 15) {
+		rme9652_initialize_spdif_receiver (rme9652);
+	}
+
+	return 0;
+}
+
+static void snd_rme9652_card_free(struct snd_card *card)
+{
+	struct snd_rme9652 *rme9652 = (struct snd_rme9652 *) card->private_data;
+
+	if (rme9652)
+		snd_rme9652_free(rme9652);
+}
+
+static int __devinit snd_rme9652_probe(struct pci_dev *pci,
+				       const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_rme9652 *rme9652;
+	struct snd_card *card;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_rme9652), &card);
+
+	if (err < 0)
+		return err;
+
+	rme9652 = (struct snd_rme9652 *) card->private_data;
+	card->private_free = snd_rme9652_card_free;
+	rme9652->dev = dev;
+	rme9652->pci = pci;
+	snd_card_set_dev(card, &pci->dev);
+
+	if ((err = snd_rme9652_create(card, rme9652, precise_ptr[dev])) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->shortname, rme9652->card_name);
+
+	sprintf(card->longname, "%s at 0x%lx, irq %d",
+		card->shortname, rme9652->port, rme9652->irq);
+
+	
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_rme9652_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name	  = "RME Digi9652 (Hammerfall)",
+	.id_table = snd_rme9652_ids,
+	.probe	  = snd_rme9652_probe,
+	.remove	  = __devexit_p(snd_rme9652_remove),
+};
+
+static int __init alsa_card_hammerfall_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_hammerfall_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_hammerfall_init)
+module_exit(alsa_card_hammerfall_exit)
diff --git a/linux/sound/pci/sis7019.c b/linux/sound/pci/sis7019.c
new file mode 100644
index 0000000..1b8f674
--- /dev/null
+++ b/linux/sound/pci/sis7019.c
@@ -0,0 +1,1461 @@
+/*
+ *  Driver for SiS7019 Audio Accelerator
+ *
+ *  Copyright (C) 2004-2007, David Dillow
+ *  Written by David Dillow <dave@thedillows.org>
+ *  Inspired by the Trident 4D-WaveDX/NX driver.
+ *
+ *  All rights reserved.
+ *
+ *  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, version 2.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include "sis7019.h"
+
+MODULE_AUTHOR("David Dillow <dave@thedillows.org>");
+MODULE_DESCRIPTION("SiS7019");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{SiS,SiS7019 Audio Accelerator}}");
+
+static int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+static int enable = 1;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for SiS7019 Audio Accelerator.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for SiS7019 Audio Accelerator.");
+module_param(enable, bool, 0444);
+MODULE_PARM_DESC(enable, "Enable SiS7019 Audio Accelerator.");
+
+static DEFINE_PCI_DEVICE_TABLE(snd_sis7019_ids) = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_SI, 0x7019) },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_sis7019_ids);
+
+/* There are three timing modes for the voices.
+ *
+ * For both playback and capture, when the buffer is one or two periods long,
+ * we use the hardware's built-in Mid-Loop Interrupt and End-Loop Interrupt
+ * to let us know when the periods have ended.
+ *
+ * When performing playback with more than two periods per buffer, we set
+ * the "Stop Sample Offset" and tell the hardware to interrupt us when we
+ * reach it. We then update the offset and continue on until we are
+ * interrupted for the next period.
+ *
+ * Capture channels do not have a SSO, so we allocate a playback channel to
+ * use as a timer for the capture periods. We use the SSO on the playback
+ * channel to clock out virtual periods, and adjust the virtual period length
+ * to maintain synchronization. This algorithm came from the Trident driver.
+ *
+ * FIXME: It'd be nice to make use of some of the synth features in the
+ * hardware, but a woeful lack of documentation is a significant roadblock.
+ */
+struct voice {
+	u16 flags;
+#define 	VOICE_IN_USE		1
+#define 	VOICE_CAPTURE		2
+#define 	VOICE_SSO_TIMING	4
+#define 	VOICE_SYNC_TIMING	8
+	u16 sync_cso;
+	u16 period_size;
+	u16 buffer_size;
+	u16 sync_period_size;
+	u16 sync_buffer_size;
+	u32 sso;
+	u32 vperiod;
+	struct snd_pcm_substream *substream;
+	struct voice *timing;
+	void __iomem *ctrl_base;
+	void __iomem *wave_base;
+	void __iomem *sync_base;
+	int num;
+};
+
+/* We need four pages to store our wave parameters during a suspend. If
+ * we're not doing power management, we still need to allocate a page
+ * for the silence buffer.
+ */
+#ifdef CONFIG_PM
+#define SIS_SUSPEND_PAGES	4
+#else
+#define SIS_SUSPEND_PAGES	1
+#endif
+
+struct sis7019 {
+	unsigned long ioport;
+	void __iomem *ioaddr;
+	int irq;
+	int codecs_present;
+
+	struct pci_dev *pci;
+	struct snd_pcm *pcm;
+	struct snd_card *card;
+	struct snd_ac97 *ac97[3];
+
+	/* Protect against more than one thread hitting the AC97
+	 * registers (in a more polite manner than pounding the hardware
+	 * semaphore)
+	 */
+	struct mutex ac97_mutex;
+
+	/* voice_lock protects allocation/freeing of the voice descriptions
+	 */
+	spinlock_t voice_lock;
+
+	struct voice voices[64];
+	struct voice capture_voice;
+
+	/* Allocate pages to store the internal wave state during
+	 * suspends. When we're operating, this can be used as a silence
+	 * buffer for a timing channel.
+	 */
+	void *suspend_state[SIS_SUSPEND_PAGES];
+
+	int silence_users;
+	dma_addr_t silence_dma_addr;
+};
+
+#define SIS_PRIMARY_CODEC_PRESENT	0x0001
+#define SIS_SECONDARY_CODEC_PRESENT	0x0002
+#define SIS_TERTIARY_CODEC_PRESENT	0x0004
+
+/* The HW offset parameters (Loop End, Stop Sample, End Sample) have a
+ * documented range of 8-0xfff8 samples. Given that they are 0-based,
+ * that places our period/buffer range at 9-0xfff9 samples. That makes the
+ * max buffer size 0xfff9 samples * 2 channels * 2 bytes per sample, and
+ * max samples / min samples gives us the max periods in a buffer.
+ *
+ * We'll add a constraint upon open that limits the period and buffer sample
+ * size to values that are legal for the hardware.
+ */
+static struct snd_pcm_hardware sis_playback_hw_info = {
+	.info = (SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 SNDRV_PCM_INFO_SYNC_START |
+		 SNDRV_PCM_INFO_RESUME),
+	.formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+		    SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS,
+	.rate_min = 4000,
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = (0xfff9 * 4),
+	.period_bytes_min = 9,
+	.period_bytes_max = (0xfff9 * 4),
+	.periods_min = 1,
+	.periods_max = (0xfff9 / 9),
+};
+
+static struct snd_pcm_hardware sis_capture_hw_info = {
+	.info = (SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 SNDRV_PCM_INFO_SYNC_START |
+		 SNDRV_PCM_INFO_RESUME),
+	.formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |
+		    SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates = SNDRV_PCM_RATE_48000,
+	.rate_min = 4000,
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = (0xfff9 * 4),
+	.period_bytes_min = 9,
+	.period_bytes_max = (0xfff9 * 4),
+	.periods_min = 1,
+	.periods_max = (0xfff9 / 9),
+};
+
+static void sis_update_sso(struct voice *voice, u16 period)
+{
+	void __iomem *base = voice->ctrl_base;
+
+	voice->sso += period;
+	if (voice->sso >= voice->buffer_size)
+		voice->sso -= voice->buffer_size;
+
+	/* Enforce the documented hardware minimum offset */
+	if (voice->sso < 8)
+		voice->sso = 8;
+
+	/* The SSO is in the upper 16 bits of the register. */
+	writew(voice->sso & 0xffff, base + SIS_PLAY_DMA_SSO_ESO + 2);
+}
+
+static void sis_update_voice(struct voice *voice)
+{
+	if (voice->flags & VOICE_SSO_TIMING) {
+		sis_update_sso(voice, voice->period_size);
+	} else if (voice->flags & VOICE_SYNC_TIMING) {
+		int sync;
+
+		/* If we've not hit the end of the virtual period, update
+		 * our records and keep going.
+		 */
+		if (voice->vperiod > voice->period_size) {
+			voice->vperiod -= voice->period_size;
+			if (voice->vperiod < voice->period_size)
+				sis_update_sso(voice, voice->vperiod);
+			else
+				sis_update_sso(voice, voice->period_size);
+			return;
+		}
+
+		/* Calculate our relative offset between the target and
+		 * the actual CSO value. Since we're operating in a loop,
+		 * if the value is more than half way around, we can
+		 * consider ourselves wrapped.
+		 */
+		sync = voice->sync_cso;
+		sync -= readw(voice->sync_base + SIS_CAPTURE_DMA_FORMAT_CSO);
+		if (sync > (voice->sync_buffer_size / 2))
+			sync -= voice->sync_buffer_size;
+
+		/* If sync is positive, then we interrupted too early, and
+		 * we'll need to come back in a few samples and try again.
+		 * There's a minimum wait, as it takes some time for the DMA
+		 * engine to startup, etc...
+		 */
+		if (sync > 0) {
+			if (sync < 16)
+				sync = 16;
+			sis_update_sso(voice, sync);
+			return;
+		}
+
+		/* Ok, we interrupted right on time, or (hopefully) just
+		 * a bit late. We'll adjst our next waiting period based
+		 * on how close we got.
+		 *
+		 * We need to stay just behind the actual channel to ensure
+		 * it really is past a period when we get our interrupt --
+		 * otherwise we'll fall into the early code above and have
+		 * a minimum wait time, which makes us quite late here,
+		 * eating into the user's time to refresh the buffer, esp.
+		 * if using small periods.
+		 *
+		 * If we're less than 9 samples behind, we're on target.
+		 * Otherwise, shorten the next vperiod by the amount we've
+		 * been delayed.
+		 */
+		if (sync > -9)
+			voice->vperiod = voice->sync_period_size + 1;
+		else
+			voice->vperiod = voice->sync_period_size + sync + 10;
+
+		if (voice->vperiod < voice->buffer_size) {
+			sis_update_sso(voice, voice->vperiod);
+			voice->vperiod = 0;
+		} else
+			sis_update_sso(voice, voice->period_size);
+
+		sync = voice->sync_cso + voice->sync_period_size;
+		if (sync >= voice->sync_buffer_size)
+			sync -= voice->sync_buffer_size;
+		voice->sync_cso = sync;
+	}
+
+	snd_pcm_period_elapsed(voice->substream);
+}
+
+static void sis_voice_irq(u32 status, struct voice *voice)
+{
+	int bit;
+
+	while (status) {
+		bit = __ffs(status);
+		status >>= bit + 1;
+		voice += bit;
+		sis_update_voice(voice);
+		voice++;
+	}
+}
+
+static irqreturn_t sis_interrupt(int irq, void *dev)
+{
+	struct sis7019 *sis = dev;
+	unsigned long io = sis->ioport;
+	struct voice *voice;
+	u32 intr, status;
+
+	/* We only use the DMA interrupts, and we don't enable any other
+	 * source of interrupts. But, it is possible to see an interupt
+	 * status that didn't actually interrupt us, so eliminate anything
+	 * we're not expecting to avoid falsely claiming an IRQ, and an
+	 * ensuing endless loop.
+	 */
+	intr = inl(io + SIS_GISR);
+	intr &= SIS_GISR_AUDIO_PLAY_DMA_IRQ_STATUS |
+		SIS_GISR_AUDIO_RECORD_DMA_IRQ_STATUS;
+	if (!intr)
+		return IRQ_NONE;
+
+	do {
+		status = inl(io + SIS_PISR_A);
+		if (status) {
+			sis_voice_irq(status, sis->voices);
+			outl(status, io + SIS_PISR_A);
+		}
+
+		status = inl(io + SIS_PISR_B);
+		if (status) {
+			sis_voice_irq(status, &sis->voices[32]);
+			outl(status, io + SIS_PISR_B);
+		}
+
+		status = inl(io + SIS_RISR);
+		if (status) {
+			voice = &sis->capture_voice;
+			if (!voice->timing)
+				snd_pcm_period_elapsed(voice->substream);
+
+			outl(status, io + SIS_RISR);
+		}
+
+		outl(intr, io + SIS_GISR);
+		intr = inl(io + SIS_GISR);
+		intr &= SIS_GISR_AUDIO_PLAY_DMA_IRQ_STATUS |
+			SIS_GISR_AUDIO_RECORD_DMA_IRQ_STATUS;
+	} while (intr);
+
+	return IRQ_HANDLED;
+}
+
+static u32 sis_rate_to_delta(unsigned int rate)
+{
+	u32 delta;
+
+	/* This was copied from the trident driver, but it seems its gotten
+	 * around a bit... nevertheless, it works well.
+	 *
+	 * We special case 44100 and 8000 since rounding with the equation
+	 * does not give us an accurate enough value. For 11025 and 22050
+	 * the equation gives us the best answer. All other frequencies will
+	 * also use the equation. JDW
+	 */
+	if (rate == 44100)
+		delta = 0xeb3;
+	else if (rate == 8000)
+		delta = 0x2ab;
+	else if (rate == 48000)
+		delta = 0x1000;
+	else
+		delta = (((rate << 12) + 24000) / 48000) & 0x0000ffff;
+	return delta;
+}
+
+static void __sis_map_silence(struct sis7019 *sis)
+{
+	/* Helper function: must hold sis->voice_lock on entry */
+	if (!sis->silence_users)
+		sis->silence_dma_addr = pci_map_single(sis->pci,
+						sis->suspend_state[0],
+						4096, PCI_DMA_TODEVICE);
+	sis->silence_users++;
+}
+
+static void __sis_unmap_silence(struct sis7019 *sis)
+{
+	/* Helper function: must hold sis->voice_lock on entry */
+	sis->silence_users--;
+	if (!sis->silence_users)
+		pci_unmap_single(sis->pci, sis->silence_dma_addr, 4096,
+					PCI_DMA_TODEVICE);
+}
+
+static void sis_free_voice(struct sis7019 *sis, struct voice *voice)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&sis->voice_lock, flags);
+	if (voice->timing) {
+		__sis_unmap_silence(sis);
+		voice->timing->flags &= ~(VOICE_IN_USE | VOICE_SSO_TIMING |
+						VOICE_SYNC_TIMING);
+		voice->timing = NULL;
+	}
+	voice->flags &= ~(VOICE_IN_USE | VOICE_SSO_TIMING | VOICE_SYNC_TIMING);
+	spin_unlock_irqrestore(&sis->voice_lock, flags);
+}
+
+static struct voice *__sis_alloc_playback_voice(struct sis7019 *sis)
+{
+	/* Must hold the voice_lock on entry */
+	struct voice *voice;
+	int i;
+
+	for (i = 0; i < 64; i++) {
+		voice = &sis->voices[i];
+		if (voice->flags & VOICE_IN_USE)
+			continue;
+		voice->flags |= VOICE_IN_USE;
+		goto found_one;
+	}
+	voice = NULL;
+
+found_one:
+	return voice;
+}
+
+static struct voice *sis_alloc_playback_voice(struct sis7019 *sis)
+{
+	struct voice *voice;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sis->voice_lock, flags);
+	voice = __sis_alloc_playback_voice(sis);
+	spin_unlock_irqrestore(&sis->voice_lock, flags);
+
+	return voice;
+}
+
+static int sis_alloc_timing_voice(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	struct sis7019 *sis = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct voice *voice = runtime->private_data;
+	unsigned int period_size, buffer_size;
+	unsigned long flags;
+	int needed;
+
+	/* If there are one or two periods per buffer, we don't need a
+	 * timing voice, as we can use the capture channel's interrupts
+	 * to clock out the periods.
+	 */
+	period_size = params_period_size(hw_params);
+	buffer_size = params_buffer_size(hw_params);
+	needed = (period_size != buffer_size &&
+			period_size != (buffer_size / 2));
+
+	if (needed && !voice->timing) {
+		spin_lock_irqsave(&sis->voice_lock, flags);
+		voice->timing = __sis_alloc_playback_voice(sis);
+		if (voice->timing)
+			__sis_map_silence(sis);
+		spin_unlock_irqrestore(&sis->voice_lock, flags);
+		if (!voice->timing)
+			return -ENOMEM;
+		voice->timing->substream = substream;
+	} else if (!needed && voice->timing) {
+		sis_free_voice(sis, voice);
+		voice->timing = NULL;
+	}
+
+	return 0;
+}
+
+static int sis_playback_open(struct snd_pcm_substream *substream)
+{
+	struct sis7019 *sis = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct voice *voice;
+
+	voice = sis_alloc_playback_voice(sis);
+	if (!voice)
+		return -EAGAIN;
+
+	voice->substream = substream;
+	runtime->private_data = voice;
+	runtime->hw = sis_playback_hw_info;
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+						9, 0xfff9);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+						9, 0xfff9);
+	snd_pcm_set_sync(substream);
+	return 0;
+}
+
+static int sis_substream_close(struct snd_pcm_substream *substream)
+{
+	struct sis7019 *sis = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct voice *voice = runtime->private_data;
+
+	sis_free_voice(sis, voice);
+	return 0;
+}
+
+static int sis_playback_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int sis_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int sis_pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct voice *voice = runtime->private_data;
+	void __iomem *ctrl_base = voice->ctrl_base;
+	void __iomem *wave_base = voice->wave_base;
+	u32 format, dma_addr, control, sso_eso, delta, reg;
+	u16 leo;
+
+	/* We rely on the PCM core to ensure that the parameters for this
+	 * substream do not change on us while we're programming the HW.
+	 */
+	format = 0;
+	if (snd_pcm_format_width(runtime->format) == 8)
+		format |= SIS_PLAY_DMA_FORMAT_8BIT;
+	if (!snd_pcm_format_signed(runtime->format))
+		format |= SIS_PLAY_DMA_FORMAT_UNSIGNED;
+	if (runtime->channels == 1)
+		format |= SIS_PLAY_DMA_FORMAT_MONO;
+
+	/* The baseline setup is for a single period per buffer, and
+	 * we add bells and whistles as needed from there.
+	 */
+	dma_addr = runtime->dma_addr;
+	leo = runtime->buffer_size - 1;
+	control = leo | SIS_PLAY_DMA_LOOP | SIS_PLAY_DMA_INTR_AT_LEO;
+	sso_eso = leo;
+
+	if (runtime->period_size == (runtime->buffer_size / 2)) {
+		control |= SIS_PLAY_DMA_INTR_AT_MLP;
+	} else if (runtime->period_size != runtime->buffer_size) {
+		voice->flags |= VOICE_SSO_TIMING;
+		voice->sso = runtime->period_size - 1;
+		voice->period_size = runtime->period_size;
+		voice->buffer_size = runtime->buffer_size;
+
+		control &= ~SIS_PLAY_DMA_INTR_AT_LEO;
+		control |= SIS_PLAY_DMA_INTR_AT_SSO;
+		sso_eso |= (runtime->period_size - 1) << 16;
+	}
+
+	delta = sis_rate_to_delta(runtime->rate);
+
+	/* Ok, we're ready to go, set up the channel.
+	 */
+	writel(format, ctrl_base + SIS_PLAY_DMA_FORMAT_CSO);
+	writel(dma_addr, ctrl_base + SIS_PLAY_DMA_BASE);
+	writel(control, ctrl_base + SIS_PLAY_DMA_CONTROL);
+	writel(sso_eso, ctrl_base + SIS_PLAY_DMA_SSO_ESO);
+
+	for (reg = 0; reg < SIS_WAVE_SIZE; reg += 4)
+		writel(0, wave_base + reg);
+
+	writel(SIS_WAVE_GENERAL_WAVE_VOLUME, wave_base + SIS_WAVE_GENERAL);
+	writel(delta << 16, wave_base + SIS_WAVE_GENERAL_ARTICULATION);
+	writel(SIS_WAVE_CHANNEL_CONTROL_FIRST_SAMPLE |
+			SIS_WAVE_CHANNEL_CONTROL_AMP_ENABLE |
+			SIS_WAVE_CHANNEL_CONTROL_INTERPOLATE_ENABLE,
+			wave_base + SIS_WAVE_CHANNEL_CONTROL);
+
+	/* Force PCI writes to post. */
+	readl(ctrl_base);
+
+	return 0;
+}
+
+static int sis_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct sis7019 *sis = snd_pcm_substream_chip(substream);
+	unsigned long io = sis->ioport;
+	struct snd_pcm_substream *s;
+	struct voice *voice;
+	void *chip;
+	int starting;
+	u32 record = 0;
+	u32 play[2] = { 0, 0 };
+
+	/* No locks needed, as the PCM core will hold the locks on the
+	 * substreams, and the HW will only start/stop the indicated voices
+	 * without changing the state of the others.
+	 */
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		starting = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		starting = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_pcm_group_for_each_entry(s, substream) {
+		/* Make sure it is for us... */
+		chip = snd_pcm_substream_chip(s);
+		if (chip != sis)
+			continue;
+
+		voice = s->runtime->private_data;
+		if (voice->flags & VOICE_CAPTURE) {
+			record |= 1 << voice->num;
+			voice = voice->timing;
+		}
+
+		/* voice could be NULL if this a recording stream, and it
+		 * doesn't have an external timing channel.
+		 */
+		if (voice)
+			play[voice->num / 32] |= 1 << (voice->num & 0x1f);
+
+		snd_pcm_trigger_done(s, substream);
+	}
+
+	if (starting) {
+		if (record)
+			outl(record, io + SIS_RECORD_START_REG);
+		if (play[0])
+			outl(play[0], io + SIS_PLAY_START_A_REG);
+		if (play[1])
+			outl(play[1], io + SIS_PLAY_START_B_REG);
+	} else {
+		if (record)
+			outl(record, io + SIS_RECORD_STOP_REG);
+		if (play[0])
+			outl(play[0], io + SIS_PLAY_STOP_A_REG);
+		if (play[1])
+			outl(play[1], io + SIS_PLAY_STOP_B_REG);
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t sis_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct voice *voice = runtime->private_data;
+	u32 cso;
+
+	cso = readl(voice->ctrl_base + SIS_PLAY_DMA_FORMAT_CSO);
+	cso &= 0xffff;
+	return cso;
+}
+
+static int sis_capture_open(struct snd_pcm_substream *substream)
+{
+	struct sis7019 *sis = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct voice *voice = &sis->capture_voice;
+	unsigned long flags;
+
+	/* FIXME: The driver only supports recording from one channel
+	 * at the moment, but it could support more.
+	 */
+	spin_lock_irqsave(&sis->voice_lock, flags);
+	if (voice->flags & VOICE_IN_USE)
+		voice = NULL;
+	else
+		voice->flags |= VOICE_IN_USE;
+	spin_unlock_irqrestore(&sis->voice_lock, flags);
+
+	if (!voice)
+		return -EAGAIN;
+
+	voice->substream = substream;
+	runtime->private_data = voice;
+	runtime->hw = sis_capture_hw_info;
+	runtime->hw.rates = sis->ac97[0]->rates[AC97_RATES_ADC];
+	snd_pcm_limit_hw_rates(runtime);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+						9, 0xfff9);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+						9, 0xfff9);
+	snd_pcm_set_sync(substream);
+	return 0;
+}
+
+static int sis_capture_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	struct sis7019 *sis = snd_pcm_substream_chip(substream);
+	int rc;
+
+	rc = snd_ac97_set_rate(sis->ac97[0], AC97_PCM_LR_ADC_RATE,
+						params_rate(hw_params));
+	if (rc)
+		goto out;
+
+	rc = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	if (rc < 0)
+		goto out;
+
+	rc = sis_alloc_timing_voice(substream, hw_params);
+
+out:
+	return rc;
+}
+
+static void sis_prepare_timing_voice(struct voice *voice,
+					struct snd_pcm_substream *substream)
+{
+	struct sis7019 *sis = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct voice *timing = voice->timing;
+	void __iomem *play_base = timing->ctrl_base;
+	void __iomem *wave_base = timing->wave_base;
+	u16 buffer_size, period_size;
+	u32 format, control, sso_eso, delta;
+	u32 vperiod, sso, reg;
+
+	/* Set our initial buffer and period as large as we can given a
+	 * single page of silence.
+	 */
+	buffer_size = 4096 / runtime->channels;
+	buffer_size /= snd_pcm_format_size(runtime->format, 1);
+	period_size = buffer_size;
+
+	/* Initially, we want to interrupt just a bit behind the end of
+	 * the period we're clocking out. 12 samples seems to give a good
+	 * delay.
+	 *
+	 * We want to spread our interrupts throughout the virtual period,
+	 * so that we don't end up with two interrupts back to back at the
+	 * end -- this helps minimize the effects of any jitter. Adjust our
+	 * clocking period size so that the last period is at least a fourth
+	 * of a full period.
+	 *
+	 * This is all moot if we don't need to use virtual periods.
+	 */
+	vperiod = runtime->period_size + 12;
+	if (vperiod > period_size) {
+		u16 tail = vperiod % period_size;
+		u16 quarter_period = period_size / 4;
+
+		if (tail && tail < quarter_period) {
+			u16 loops = vperiod / period_size;
+
+			tail = quarter_period - tail;
+			tail += loops - 1;
+			tail /= loops;
+			period_size -= tail;
+		}
+
+		sso = period_size - 1;
+	} else {
+		/* The initial period will fit inside the buffer, so we
+		 * don't need to use virtual periods -- disable them.
+		 */
+		period_size = runtime->period_size;
+		sso = vperiod - 1;
+		vperiod = 0;
+	}
+
+	/* The interrupt handler implements the timing syncronization, so
+	 * setup its state.
+	 */
+	timing->flags |= VOICE_SYNC_TIMING;
+	timing->sync_base = voice->ctrl_base;
+	timing->sync_cso = runtime->period_size;
+	timing->sync_period_size = runtime->period_size;
+	timing->sync_buffer_size = runtime->buffer_size;
+	timing->period_size = period_size;
+	timing->buffer_size = buffer_size;
+	timing->sso = sso;
+	timing->vperiod = vperiod;
+
+	/* Using unsigned samples with the all-zero silence buffer
+	 * forces the output to the lower rail, killing playback.
+	 * So ignore unsigned vs signed -- it doesn't change the timing.
+	 */
+	format = 0;
+	if (snd_pcm_format_width(runtime->format) == 8)
+		format = SIS_CAPTURE_DMA_FORMAT_8BIT;
+	if (runtime->channels == 1)
+		format |= SIS_CAPTURE_DMA_FORMAT_MONO;
+
+	control = timing->buffer_size - 1;
+	control |= SIS_PLAY_DMA_LOOP | SIS_PLAY_DMA_INTR_AT_SSO;
+	sso_eso = timing->buffer_size - 1;
+	sso_eso |= timing->sso << 16;
+
+	delta = sis_rate_to_delta(runtime->rate);
+
+	/* We've done the math, now configure the channel.
+	 */
+	writel(format, play_base + SIS_PLAY_DMA_FORMAT_CSO);
+	writel(sis->silence_dma_addr, play_base + SIS_PLAY_DMA_BASE);
+	writel(control, play_base + SIS_PLAY_DMA_CONTROL);
+	writel(sso_eso, play_base + SIS_PLAY_DMA_SSO_ESO);
+
+	for (reg = 0; reg < SIS_WAVE_SIZE; reg += 4)
+		writel(0, wave_base + reg);
+
+	writel(SIS_WAVE_GENERAL_WAVE_VOLUME, wave_base + SIS_WAVE_GENERAL);
+	writel(delta << 16, wave_base + SIS_WAVE_GENERAL_ARTICULATION);
+	writel(SIS_WAVE_CHANNEL_CONTROL_FIRST_SAMPLE |
+			SIS_WAVE_CHANNEL_CONTROL_AMP_ENABLE |
+			SIS_WAVE_CHANNEL_CONTROL_INTERPOLATE_ENABLE,
+			wave_base + SIS_WAVE_CHANNEL_CONTROL);
+}
+
+static int sis_pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct voice *voice = runtime->private_data;
+	void __iomem *rec_base = voice->ctrl_base;
+	u32 format, dma_addr, control;
+	u16 leo;
+
+	/* We rely on the PCM core to ensure that the parameters for this
+	 * substream do not change on us while we're programming the HW.
+	 */
+	format = 0;
+	if (snd_pcm_format_width(runtime->format) == 8)
+		format = SIS_CAPTURE_DMA_FORMAT_8BIT;
+	if (!snd_pcm_format_signed(runtime->format))
+		format |= SIS_CAPTURE_DMA_FORMAT_UNSIGNED;
+	if (runtime->channels == 1)
+		format |= SIS_CAPTURE_DMA_FORMAT_MONO;
+
+	dma_addr = runtime->dma_addr;
+	leo = runtime->buffer_size - 1;
+	control = leo | SIS_CAPTURE_DMA_LOOP;
+
+	/* If we've got more than two periods per buffer, then we have
+	 * use a timing voice to clock out the periods. Otherwise, we can
+	 * use the capture channel's interrupts.
+	 */
+	if (voice->timing) {
+		sis_prepare_timing_voice(voice, substream);
+	} else {
+		control |= SIS_CAPTURE_DMA_INTR_AT_LEO;
+		if (runtime->period_size != runtime->buffer_size)
+			control |= SIS_CAPTURE_DMA_INTR_AT_MLP;
+	}
+
+	writel(format, rec_base + SIS_CAPTURE_DMA_FORMAT_CSO);
+	writel(dma_addr, rec_base + SIS_CAPTURE_DMA_BASE);
+	writel(control, rec_base + SIS_CAPTURE_DMA_CONTROL);
+
+	/* Force the writes to post. */
+	readl(rec_base);
+
+	return 0;
+}
+
+static struct snd_pcm_ops sis_playback_ops = {
+	.open = sis_playback_open,
+	.close = sis_substream_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = sis_playback_hw_params,
+	.hw_free = sis_hw_free,
+	.prepare = sis_pcm_playback_prepare,
+	.trigger = sis_pcm_trigger,
+	.pointer = sis_pcm_pointer,
+};
+
+static struct snd_pcm_ops sis_capture_ops = {
+	.open = sis_capture_open,
+	.close = sis_substream_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = sis_capture_hw_params,
+	.hw_free = sis_hw_free,
+	.prepare = sis_pcm_capture_prepare,
+	.trigger = sis_pcm_trigger,
+	.pointer = sis_pcm_pointer,
+};
+
+static int __devinit sis_pcm_create(struct sis7019 *sis)
+{
+	struct snd_pcm *pcm;
+	int rc;
+
+	/* We have 64 voices, and the driver currently records from
+	 * only one channel, though that could change in the future.
+	 */
+	rc = snd_pcm_new(sis->card, "SiS7019", 0, 64, 1, &pcm);
+	if (rc)
+		return rc;
+
+	pcm->private_data = sis;
+	strcpy(pcm->name, "SiS7019");
+	sis->pcm = pcm;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &sis_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &sis_capture_ops);
+
+	/* Try to preallocate some memory, but it's not the end of the
+	 * world if this fails.
+	 */
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+				snd_dma_pci_data(sis->pci), 64*1024, 128*1024);
+
+	return 0;
+}
+
+static unsigned short sis_ac97_rw(struct sis7019 *sis, int codec, u32 cmd)
+{
+	unsigned long io = sis->ioport;
+	unsigned short val = 0xffff;
+	u16 status;
+	u16 rdy;
+	int count;
+	static const u16 codec_ready[3] = {
+		SIS_AC97_STATUS_CODEC_READY,
+		SIS_AC97_STATUS_CODEC2_READY,
+		SIS_AC97_STATUS_CODEC3_READY,
+	};
+
+	rdy = codec_ready[codec];
+
+
+	/* Get the AC97 semaphore -- software first, so we don't spin
+	 * pounding out IO reads on the hardware semaphore...
+	 */
+	mutex_lock(&sis->ac97_mutex);
+
+	count = 0xffff;
+	while ((inw(io + SIS_AC97_SEMA) & SIS_AC97_SEMA_BUSY) && --count)
+		udelay(1);
+
+	if (!count)
+		goto timeout;
+
+	/* ... and wait for any outstanding commands to complete ...
+	 */
+	count = 0xffff;
+	do {
+		status = inw(io + SIS_AC97_STATUS);
+		if ((status & rdy) && !(status & SIS_AC97_STATUS_BUSY))
+			break;
+
+		udelay(1);
+	} while (--count);
+
+	if (!count)
+		goto timeout_sema;
+
+	/* ... before sending our command and waiting for it to finish ...
+	 */
+	outl(cmd, io + SIS_AC97_CMD);
+	udelay(10);
+
+	count = 0xffff;
+	while ((inw(io + SIS_AC97_STATUS) & SIS_AC97_STATUS_BUSY) && --count)
+		udelay(1);
+
+	/* ... and reading the results (if any).
+	 */
+	val = inl(io + SIS_AC97_CMD) >> 16;
+
+timeout_sema:
+	outl(SIS_AC97_SEMA_RELEASE, io + SIS_AC97_SEMA);
+timeout:
+	mutex_unlock(&sis->ac97_mutex);
+
+	if (!count) {
+		printk(KERN_ERR "sis7019: ac97 codec %d timeout cmd 0x%08x\n",
+					codec, cmd);
+	}
+
+	return val;
+}
+
+static void sis_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+				unsigned short val)
+{
+	static const u32 cmd[3] = {
+		SIS_AC97_CMD_CODEC_WRITE,
+		SIS_AC97_CMD_CODEC2_WRITE,
+		SIS_AC97_CMD_CODEC3_WRITE,
+	};
+	sis_ac97_rw(ac97->private_data, ac97->num,
+			(val << 16) | (reg << 8) | cmd[ac97->num]);
+}
+
+static unsigned short sis_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	static const u32 cmd[3] = {
+		SIS_AC97_CMD_CODEC_READ,
+		SIS_AC97_CMD_CODEC2_READ,
+		SIS_AC97_CMD_CODEC3_READ,
+	};
+	return sis_ac97_rw(ac97->private_data, ac97->num,
+					(reg << 8) | cmd[ac97->num]);
+}
+
+static int __devinit sis_mixer_create(struct sis7019 *sis)
+{
+	struct snd_ac97_bus *bus;
+	struct snd_ac97_template ac97;
+	static struct snd_ac97_bus_ops ops = {
+		.write = sis_ac97_write,
+		.read = sis_ac97_read,
+	};
+	int rc;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = sis;
+
+	rc = snd_ac97_bus(sis->card, 0, &ops, NULL, &bus);
+	if (!rc && sis->codecs_present & SIS_PRIMARY_CODEC_PRESENT)
+		rc = snd_ac97_mixer(bus, &ac97, &sis->ac97[0]);
+	ac97.num = 1;
+	if (!rc && (sis->codecs_present & SIS_SECONDARY_CODEC_PRESENT))
+		rc = snd_ac97_mixer(bus, &ac97, &sis->ac97[1]);
+	ac97.num = 2;
+	if (!rc && (sis->codecs_present & SIS_TERTIARY_CODEC_PRESENT))
+		rc = snd_ac97_mixer(bus, &ac97, &sis->ac97[2]);
+
+	/* If we return an error here, then snd_card_free() should
+	 * free up any ac97 codecs that got created, as well as the bus.
+	 */
+	return rc;
+}
+
+static void sis_free_suspend(struct sis7019 *sis)
+{
+	int i;
+
+	for (i = 0; i < SIS_SUSPEND_PAGES; i++)
+		kfree(sis->suspend_state[i]);
+}
+
+static int sis_chip_free(struct sis7019 *sis)
+{
+	/* Reset the chip, and disable all interrputs.
+	 */
+	outl(SIS_GCR_SOFTWARE_RESET, sis->ioport + SIS_GCR);
+	udelay(25);
+	outl(0, sis->ioport + SIS_GCR);
+	outl(0, sis->ioport + SIS_GIER);
+
+	/* Now, free everything we allocated.
+	 */
+	if (sis->irq >= 0)
+		free_irq(sis->irq, sis);
+
+	if (sis->ioaddr)
+		iounmap(sis->ioaddr);
+
+	pci_release_regions(sis->pci);
+	pci_disable_device(sis->pci);
+
+	sis_free_suspend(sis);
+	return 0;
+}
+
+static int sis_dev_free(struct snd_device *dev)
+{
+	struct sis7019 *sis = dev->device_data;
+	return sis_chip_free(sis);
+}
+
+static int sis_chip_init(struct sis7019 *sis)
+{
+	unsigned long io = sis->ioport;
+	void __iomem *ioaddr = sis->ioaddr;
+	u16 status;
+	int count;
+	int i;
+
+	/* Reset the audio controller
+	 */
+	outl(SIS_GCR_SOFTWARE_RESET, io + SIS_GCR);
+	udelay(25);
+	outl(0, io + SIS_GCR);
+
+	/* Get the AC-link semaphore, and reset the codecs
+	 */
+	count = 0xffff;
+	while ((inw(io + SIS_AC97_SEMA) & SIS_AC97_SEMA_BUSY) && --count)
+		udelay(1);
+
+	if (!count)
+		return -EIO;
+
+	outl(SIS_AC97_CMD_CODEC_COLD_RESET, io + SIS_AC97_CMD);
+	udelay(250);
+
+	count = 0xffff;
+	while ((inw(io + SIS_AC97_STATUS) & SIS_AC97_STATUS_BUSY) && --count)
+		udelay(1);
+
+	/* Now that we've finished the reset, find out what's attached.
+	 */
+	status = inl(io + SIS_AC97_STATUS);
+	if (status & SIS_AC97_STATUS_CODEC_READY)
+		sis->codecs_present |= SIS_PRIMARY_CODEC_PRESENT;
+	if (status & SIS_AC97_STATUS_CODEC2_READY)
+		sis->codecs_present |= SIS_SECONDARY_CODEC_PRESENT;
+	if (status & SIS_AC97_STATUS_CODEC3_READY)
+		sis->codecs_present |= SIS_TERTIARY_CODEC_PRESENT;
+
+	/* All done, let go of the semaphore, and check for errors
+	 */
+	outl(SIS_AC97_SEMA_RELEASE, io + SIS_AC97_SEMA);
+	if (!sis->codecs_present || !count)
+		return -EIO;
+
+	/* Let the hardware know that the audio driver is alive,
+	 * and enable PCM slots on the AC-link for L/R playback (3 & 4) and
+	 * record channels. We're going to want to use Variable Rate Audio
+	 * for recording, to avoid needlessly resampling from 48kHZ.
+	 */
+	outl(SIS_AC97_CONF_AUDIO_ALIVE, io + SIS_AC97_CONF);
+	outl(SIS_AC97_CONF_AUDIO_ALIVE | SIS_AC97_CONF_PCM_LR_ENABLE |
+		SIS_AC97_CONF_PCM_CAP_MIC_ENABLE |
+		SIS_AC97_CONF_PCM_CAP_LR_ENABLE |
+		SIS_AC97_CONF_CODEC_VRA_ENABLE, io + SIS_AC97_CONF);
+
+	/* All AC97 PCM slots should be sourced from sub-mixer 0.
+	 */
+	outl(0, io + SIS_AC97_PSR);
+
+	/* There is only one valid DMA setup for a PCI environment.
+	 */
+	outl(SIS_DMA_CSR_PCI_SETTINGS, io + SIS_DMA_CSR);
+
+	/* Reset the syncronization groups for all of the channels
+	 * to be asyncronous. If we start doing SPDIF or 5.1 sound, etc.
+	 * we'll need to change how we handle these. Until then, we just
+	 * assign sub-mixer 0 to all playback channels, and avoid any
+	 * attenuation on the audio.
+	 */
+	outl(0, io + SIS_PLAY_SYNC_GROUP_A);
+	outl(0, io + SIS_PLAY_SYNC_GROUP_B);
+	outl(0, io + SIS_PLAY_SYNC_GROUP_C);
+	outl(0, io + SIS_PLAY_SYNC_GROUP_D);
+	outl(0, io + SIS_MIXER_SYNC_GROUP);
+
+	for (i = 0; i < 64; i++) {
+		writel(i, SIS_MIXER_START_ADDR(ioaddr, i));
+		writel(SIS_MIXER_RIGHT_NO_ATTEN | SIS_MIXER_LEFT_NO_ATTEN |
+				SIS_MIXER_DEST_0, SIS_MIXER_ADDR(ioaddr, i));
+	}
+
+	/* Don't attenuate any audio set for the wave amplifier.
+	 *
+	 * FIXME: Maximum attenuation is set for the music amp, which will
+	 * need to change if we start using the synth engine.
+	 */
+	outl(0xffff0000, io + SIS_WEVCR);
+
+	/* Ensure that the wave engine is in normal operating mode.
+	 */
+	outl(0, io + SIS_WECCR);
+
+	/* Go ahead and enable the DMA interrupts. They won't go live
+	 * until we start a channel.
+	 */
+	outl(SIS_GIER_AUDIO_PLAY_DMA_IRQ_ENABLE |
+		SIS_GIER_AUDIO_RECORD_DMA_IRQ_ENABLE, io + SIS_GIER);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int sis_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct sis7019 *sis = card->private_data;
+	void __iomem *ioaddr = sis->ioaddr;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(sis->pcm);
+	if (sis->codecs_present & SIS_PRIMARY_CODEC_PRESENT)
+		snd_ac97_suspend(sis->ac97[0]);
+	if (sis->codecs_present & SIS_SECONDARY_CODEC_PRESENT)
+		snd_ac97_suspend(sis->ac97[1]);
+	if (sis->codecs_present & SIS_TERTIARY_CODEC_PRESENT)
+		snd_ac97_suspend(sis->ac97[2]);
+
+	/* snd_pcm_suspend_all() stopped all channels, so we're quiescent.
+	 */
+	if (sis->irq >= 0) {
+		free_irq(sis->irq, sis);
+		sis->irq = -1;
+	}
+
+	/* Save the internal state away
+	 */
+	for (i = 0; i < 4; i++) {
+		memcpy_fromio(sis->suspend_state[i], ioaddr, 4096);
+		ioaddr += 4096;
+	}
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int sis_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct sis7019 *sis = card->private_data;
+	void __iomem *ioaddr = sis->ioaddr;
+	int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "sis7019: unable to re-enable device\n");
+		goto error;
+	}
+
+	if (sis_chip_init(sis)) {
+		printk(KERN_ERR "sis7019: unable to re-init controller\n");
+		goto error;
+	}
+
+	if (request_irq(pci->irq, sis_interrupt, IRQF_DISABLED|IRQF_SHARED,
+				card->shortname, sis)) {
+		printk(KERN_ERR "sis7019: unable to regain IRQ %d\n", pci->irq);
+		goto error;
+	}
+
+	/* Restore saved state, then clear out the page we use for the
+	 * silence buffer.
+	 */
+	for (i = 0; i < 4; i++) {
+		memcpy_toio(ioaddr, sis->suspend_state[i], 4096);
+		ioaddr += 4096;
+	}
+
+	memset(sis->suspend_state[0], 0, 4096);
+
+	sis->irq = pci->irq;
+	pci_set_master(pci);
+
+	if (sis->codecs_present & SIS_PRIMARY_CODEC_PRESENT)
+		snd_ac97_resume(sis->ac97[0]);
+	if (sis->codecs_present & SIS_SECONDARY_CODEC_PRESENT)
+		snd_ac97_resume(sis->ac97[1]);
+	if (sis->codecs_present & SIS_TERTIARY_CODEC_PRESENT)
+		snd_ac97_resume(sis->ac97[2]);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+
+error:
+	snd_card_disconnect(card);
+	return -EIO;
+}
+#endif /* CONFIG_PM */
+
+static int sis_alloc_suspend(struct sis7019 *sis)
+{
+	int i;
+
+	/* We need 16K to store the internal wave engine state during a
+	 * suspend, but we don't need it to be contiguous, so play nice
+	 * with the memory system. We'll also use this area for a silence
+	 * buffer.
+	 */
+	for (i = 0; i < SIS_SUSPEND_PAGES; i++) {
+		sis->suspend_state[i] = kmalloc(4096, GFP_KERNEL);
+		if (!sis->suspend_state[i])
+			return -ENOMEM;
+	}
+	memset(sis->suspend_state[0], 0, 4096);
+
+	return 0;
+}
+
+static int __devinit sis_chip_create(struct snd_card *card,
+					struct pci_dev *pci)
+{
+	struct sis7019 *sis = card->private_data;
+	struct voice *voice;
+	static struct snd_device_ops ops = {
+		.dev_free = sis_dev_free,
+	};
+	int rc;
+	int i;
+
+	rc = pci_enable_device(pci);
+	if (rc)
+		goto error_out;
+
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(30)) < 0) {
+		printk(KERN_ERR "sis7019: architecture does not support "
+					"30-bit PCI busmaster DMA");
+		goto error_out_enabled;
+	}
+
+	memset(sis, 0, sizeof(*sis));
+	mutex_init(&sis->ac97_mutex);
+	spin_lock_init(&sis->voice_lock);
+	sis->card = card;
+	sis->pci = pci;
+	sis->irq = -1;
+	sis->ioport = pci_resource_start(pci, 0);
+
+	rc = pci_request_regions(pci, "SiS7019");
+	if (rc) {
+		printk(KERN_ERR "sis7019: unable request regions\n");
+		goto error_out_enabled;
+	}
+
+	rc = -EIO;
+	sis->ioaddr = ioremap_nocache(pci_resource_start(pci, 1), 0x4000);
+	if (!sis->ioaddr) {
+		printk(KERN_ERR "sis7019: unable to remap MMIO, aborting\n");
+		goto error_out_cleanup;
+	}
+
+	rc = sis_alloc_suspend(sis);
+	if (rc < 0) {
+		printk(KERN_ERR "sis7019: unable to allocate state storage\n");
+		goto error_out_cleanup;
+	}
+
+	rc = sis_chip_init(sis);
+	if (rc)
+		goto error_out_cleanup;
+
+	if (request_irq(pci->irq, sis_interrupt, IRQF_DISABLED|IRQF_SHARED,
+				card->shortname, sis)) {
+		printk(KERN_ERR "unable to allocate irq %d\n", sis->irq);
+		goto error_out_cleanup;
+	}
+
+	sis->irq = pci->irq;
+	pci_set_master(pci);
+
+	for (i = 0; i < 64; i++) {
+		voice = &sis->voices[i];
+		voice->num = i;
+		voice->ctrl_base = SIS_PLAY_DMA_ADDR(sis->ioaddr, i);
+		voice->wave_base = SIS_WAVE_ADDR(sis->ioaddr, i);
+	}
+
+	voice = &sis->capture_voice;
+	voice->flags = VOICE_CAPTURE;
+	voice->num = SIS_CAPTURE_CHAN_AC97_PCM_IN;
+	voice->ctrl_base = SIS_CAPTURE_DMA_ADDR(sis->ioaddr, voice->num);
+
+	rc = snd_device_new(card, SNDRV_DEV_LOWLEVEL, sis, &ops);
+	if (rc)
+		goto error_out_cleanup;
+
+	snd_card_set_dev(card, &pci->dev);
+
+	return 0;
+
+error_out_cleanup:
+	sis_chip_free(sis);
+
+error_out_enabled:
+	pci_disable_device(pci);
+
+error_out:
+	return rc;
+}
+
+static int __devinit snd_sis7019_probe(struct pci_dev *pci,
+					const struct pci_device_id *pci_id)
+{
+	struct snd_card *card;
+	struct sis7019 *sis;
+	int rc;
+
+	rc = -ENOENT;
+	if (!enable)
+		goto error_out;
+
+	rc = snd_card_create(index, id, THIS_MODULE, sizeof(*sis), &card);
+	if (rc < 0)
+		goto error_out;
+
+	strcpy(card->driver, "SiS7019");
+	strcpy(card->shortname, "SiS7019");
+	rc = sis_chip_create(card, pci);
+	if (rc)
+		goto card_error_out;
+
+	sis = card->private_data;
+
+	rc = sis_mixer_create(sis);
+	if (rc)
+		goto card_error_out;
+
+	rc = sis_pcm_create(sis);
+	if (rc)
+		goto card_error_out;
+
+	snprintf(card->longname, sizeof(card->longname),
+			"%s Audio Accelerator with %s at 0x%lx, irq %d",
+			card->shortname, snd_ac97_get_short_name(sis->ac97[0]),
+			sis->ioport, sis->irq);
+
+	rc = snd_card_register(card);
+	if (rc)
+		goto card_error_out;
+
+	pci_set_drvdata(pci, card);
+	return 0;
+
+card_error_out:
+	snd_card_free(card);
+
+error_out:
+	return rc;
+}
+
+static void __devexit snd_sis7019_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver sis7019_driver = {
+	.name = "SiS7019",
+	.id_table = snd_sis7019_ids,
+	.probe = snd_sis7019_probe,
+	.remove = __devexit_p(snd_sis7019_remove),
+
+#ifdef CONFIG_PM
+	.suspend = sis_suspend,
+	.resume = sis_resume,
+#endif
+};
+
+static int __init sis7019_init(void)
+{
+	return pci_register_driver(&sis7019_driver);
+}
+
+static void __exit sis7019_exit(void)
+{
+	pci_unregister_driver(&sis7019_driver);
+}
+
+module_init(sis7019_init);
+module_exit(sis7019_exit);
diff --git a/linux/sound/pci/sis7019.h b/linux/sound/pci/sis7019.h
new file mode 100644
index 0000000..bc8c768
--- /dev/null
+++ b/linux/sound/pci/sis7019.h
@@ -0,0 +1,342 @@
+#ifndef __sis7019_h__
+#define __sis7019_h__
+
+/*
+ *  Definitions for SiS7019 Audio Accelerator
+ *
+ *  Copyright (C) 2004-2007, David Dillow
+ *  Written by David Dillow <dave@thedillows.org>
+ *  Inspired by the Trident 4D-WaveDX/NX driver.
+ *
+ *  All rights reserved.
+ *
+ *  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, version 2.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+/* General Control Register */
+#define SIS_GCR		0x00
+#define		SIS_GCR_MACRO_POWER_DOWN		0x80000000
+#define		SIS_GCR_MODEM_ENABLE			0x00010000
+#define		SIS_GCR_SOFTWARE_RESET			0x00000001
+
+/* General Interrupt Enable Register */
+#define SIS_GIER	0x04
+#define		SIS_GIER_MODEM_TIMER_IRQ_ENABLE		0x00100000
+#define		SIS_GIER_MODEM_RX_DMA_IRQ_ENABLE	0x00080000
+#define		SIS_GIER_MODEM_TX_DMA_IRQ_ENABLE	0x00040000
+#define		SIS_GIER_AC97_GPIO1_IRQ_ENABLE		0x00020000
+#define		SIS_GIER_AC97_GPIO0_IRQ_ENABLE		0x00010000
+#define		SIS_GIER_AC97_SAMPLE_TIMER_IRQ_ENABLE	0x00000010
+#define		SIS_GIER_AUDIO_GLOBAL_TIMER_IRQ_ENABLE	0x00000008
+#define		SIS_GIER_AUDIO_RECORD_DMA_IRQ_ENABLE	0x00000004
+#define		SIS_GIER_AUDIO_PLAY_DMA_IRQ_ENABLE	0x00000002
+#define		SIS_GIER_AUDIO_WAVE_ENGINE_IRQ_ENABLE	0x00000001
+
+/* General Interrupt Status Register */
+#define SIS_GISR	0x08
+#define		SIS_GISR_MODEM_TIMER_IRQ_STATUS		0x00100000
+#define		SIS_GISR_MODEM_RX_DMA_IRQ_STATUS	0x00080000
+#define		SIS_GISR_MODEM_TX_DMA_IRQ_STATUS	0x00040000
+#define		SIS_GISR_AC97_GPIO1_IRQ_STATUS		0x00020000
+#define		SIS_GISR_AC97_GPIO0_IRQ_STATUS		0x00010000
+#define		SIS_GISR_AC97_SAMPLE_TIMER_IRQ_STATUS	0x00000010
+#define		SIS_GISR_AUDIO_GLOBAL_TIMER_IRQ_STATUS	0x00000008
+#define		SIS_GISR_AUDIO_RECORD_DMA_IRQ_STATUS	0x00000004
+#define		SIS_GISR_AUDIO_PLAY_DMA_IRQ_STATUS	0x00000002
+#define		SIS_GISR_AUDIO_WAVE_ENGINE_IRQ_STATUS	0x00000001
+
+/* DMA Control Register */
+#define SIS_DMA_CSR	0x10
+#define		SIS_DMA_CSR_PCI_SETTINGS		0x0000001d
+#define		SIS_DMA_CSR_CONCURRENT_ENABLE		0x00000200
+#define		SIS_DMA_CSR_PIPELINE_ENABLE		0x00000100
+#define		SIS_DMA_CSR_RX_DRAIN_ENABLE		0x00000010
+#define		SIS_DMA_CSR_RX_FILL_ENABLE		0x00000008
+#define		SIS_DMA_CSR_TX_DRAIN_ENABLE		0x00000004
+#define		SIS_DMA_CSR_TX_LOWPRI_FILL_ENABLE	0x00000002
+#define		SIS_DMA_CSR_TX_HIPRI_FILL_ENABLE	0x00000001
+
+/* Playback Channel Start Registers */
+#define SIS_PLAY_START_A_REG	0x14
+#define SIS_PLAY_START_B_REG	0x18
+
+/* Playback Channel Stop Registers */
+#define SIS_PLAY_STOP_A_REG	0x1c
+#define SIS_PLAY_STOP_B_REG	0x20
+
+/* Recording Channel Start Register */
+#define SIS_RECORD_START_REG	0x24
+
+/* Recording Channel Stop Register */
+#define SIS_RECORD_STOP_REG	0x28
+
+/* Playback Interrupt Status Registers */
+#define SIS_PISR_A	0x2c
+#define SIS_PISR_B	0x30
+
+/* Recording Interrupt Status Register */
+#define SIS_RISR	0x34
+
+/* AC97 AC-link Playback Source Register */
+#define SIS_AC97_PSR	0x40
+#define		SIS_AC97_PSR_MODEM_HEADSET_SRC_MIXER	0x0f000000
+#define		SIS_AC97_PSR_MODEM_LINE2_SRC_MIXER	0x00f00000
+#define		SIS_AC97_PSR_MODEM_LINE1_SRC_MIXER	0x000f0000
+#define		SIS_AC97_PSR_PCM_LFR_SRC_MIXER		0x0000f000
+#define		SIS_AC97_PSR_PCM_SURROUND_SRC_MIXER	0x00000f00
+#define		SIS_AC97_PSR_PCM_CENTER_SRC_MIXER	0x000000f0
+#define		SIS_AC97_PSR_PCM_LR_SRC_MIXER		0x0000000f
+
+/* AC97 AC-link Command Register */
+#define SIS_AC97_CMD	0x50
+#define 	SIS_AC97_CMD_DATA_MASK			0xffff0000
+#define		SIS_AC97_CMD_REG_MASK			0x0000ff00
+#define		SIS_AC97_CMD_CODEC3_READ		0x0000000d
+#define		SIS_AC97_CMD_CODEC3_WRITE		0x0000000c
+#define		SIS_AC97_CMD_CODEC2_READ		0x0000000b
+#define		SIS_AC97_CMD_CODEC2_WRITE		0x0000000a
+#define		SIS_AC97_CMD_CODEC_READ			0x00000009
+#define		SIS_AC97_CMD_CODEC_WRITE		0x00000008
+#define		SIS_AC97_CMD_CODEC_WARM_RESET		0x00000005
+#define		SIS_AC97_CMD_CODEC_COLD_RESET		0x00000004
+#define		SIS_AC97_CMD_DONE			0x00000000
+
+/* AC97 AC-link Semaphore Register */
+#define SIS_AC97_SEMA	0x54
+#define		SIS_AC97_SEMA_BUSY			0x00000001
+#define		SIS_AC97_SEMA_RELEASE			0x00000000
+
+/* AC97 AC-link Status Register */
+#define SIS_AC97_STATUS	0x58
+#define		SIS_AC97_STATUS_AUDIO_D2_INACT_SECS	0x03f00000
+#define		SIS_AC97_STATUS_MODEM_ALIVE		0x00002000
+#define		SIS_AC97_STATUS_AUDIO_ALIVE		0x00001000
+#define		SIS_AC97_STATUS_CODEC3_READY		0x00000400
+#define		SIS_AC97_STATUS_CODEC2_READY		0x00000200
+#define		SIS_AC97_STATUS_CODEC_READY		0x00000100
+#define		SIS_AC97_STATUS_WARM_RESET		0x00000080
+#define		SIS_AC97_STATUS_COLD_RESET		0x00000040
+#define		SIS_AC97_STATUS_POWERED_DOWN		0x00000020
+#define		SIS_AC97_STATUS_NORMAL			0x00000010
+#define		SIS_AC97_STATUS_READ_EXPIRED		0x00000004
+#define		SIS_AC97_STATUS_SEMAPHORE		0x00000002
+#define		SIS_AC97_STATUS_BUSY			0x00000001
+
+/* AC97 AC-link Audio Configuration Register */
+#define SIS_AC97_CONF	0x5c
+#define		SIS_AC97_CONF_AUDIO_ALIVE		0x80000000
+#define		SIS_AC97_CONF_WARM_RESET_ENABLE		0x40000000
+#define		SIS_AC97_CONF_PR6_ENABLE		0x20000000
+#define		SIS_AC97_CONF_PR5_ENABLE		0x10000000
+#define		SIS_AC97_CONF_PR4_ENABLE		0x08000000
+#define		SIS_AC97_CONF_PR3_ENABLE		0x04000000
+#define		SIS_AC97_CONF_PR2_PR7_ENABLE		0x02000000
+#define		SIS_AC97_CONF_PR0_PR1_ENABLE		0x01000000
+#define		SIS_AC97_CONF_AUTO_PM_ENABLE		0x00800000
+#define		SIS_AC97_CONF_PCM_LFE_ENABLE		0x00080000
+#define		SIS_AC97_CONF_PCM_SURROUND_ENABLE	0x00040000
+#define		SIS_AC97_CONF_PCM_CENTER_ENABLE		0x00020000
+#define		SIS_AC97_CONF_PCM_LR_ENABLE		0x00010000
+#define		SIS_AC97_CONF_PCM_CAP_MIC_ENABLE	0x00002000
+#define		SIS_AC97_CONF_PCM_CAP_LR_ENABLE		0x00001000
+#define		SIS_AC97_CONF_PCM_CAP_MIC_FROM_CODEC3	0x00000200
+#define		SIS_AC97_CONF_PCM_CAP_LR_FROM_CODEC3	0x00000100
+#define		SIS_AC97_CONF_CODEC3_PM_VRM		0x00000080
+#define		SIS_AC97_CONF_CODEC_PM_VRM		0x00000040
+#define		SIS_AC97_CONF_CODEC3_VRA_ENABLE		0x00000020
+#define		SIS_AC97_CONF_CODEC_VRA_ENABLE		0x00000010
+#define		SIS_AC97_CONF_CODEC3_PM_EAC		0x00000008
+#define		SIS_AC97_CONF_CODEC_PM_EAC		0x00000004
+#define		SIS_AC97_CONF_CODEC3_EXISTS		0x00000002
+#define		SIS_AC97_CONF_CODEC_EXISTS		0x00000001
+
+/* Playback Channel Sync Group registers */
+#define SIS_PLAY_SYNC_GROUP_A	0x80
+#define SIS_PLAY_SYNC_GROUP_B	0x84
+#define SIS_PLAY_SYNC_GROUP_C	0x88
+#define SIS_PLAY_SYNC_GROUP_D	0x8c
+#define SIS_MIXER_SYNC_GROUP	0x90
+
+/* Wave Engine Config and Control Register */
+#define SIS_WECCR	0xa0
+#define		SIS_WECCR_TESTMODE_MASK			0x00300000
+#define			SIS_WECCR_TESTMODE_NORMAL		0x00000000
+#define			SIS_WECCR_TESTMODE_BYPASS_NSO_ALPHA	0x00100000
+#define			SIS_WECCR_TESTMODE_BYPASS_FC		0x00200000
+#define			SIS_WECCR_TESTMODE_BYPASS_WOL		0x00300000
+#define		SIS_WECCR_RESONANCE_DELAY_MASK		0x00060000
+#define			SIS_WECCR_RESONANCE_DELAY_NONE		0x00000000
+#define			SIS_WECCR_RESONANCE_DELAY_FC_1F00	0x00020000
+#define			SIS_WECCR_RESONANCE_DELAY_FC_1E00	0x00040000
+#define			SIS_WECCR_RESONANCE_DELAY_FC_1C00	0x00060000
+#define		SIS_WECCR_IGNORE_CHANNEL_PARMS		0x00010000
+#define		SIS_WECCR_COMMAND_CHANNEL_ID_MASK	0x0003ff00
+#define		SIS_WECCR_COMMAND_MASK			0x00000007
+#define			SIS_WECCR_COMMAND_NONE			0x00000000
+#define			SIS_WECCR_COMMAND_DONE			0x00000000
+#define			SIS_WECCR_COMMAND_PAUSE			0x00000001
+#define			SIS_WECCR_COMMAND_TOGGLE_VEG		0x00000002
+#define			SIS_WECCR_COMMAND_TOGGLE_MEG		0x00000003
+#define			SIS_WECCR_COMMAND_TOGGLE_VEG_MEG	0x00000004
+
+/* Wave Engine Volume Control Register */
+#define SIS_WEVCR	0xa4
+#define		SIS_WEVCR_LEFT_MUSIC_ATTENUATION_MASK	0xff000000
+#define		SIS_WEVCR_RIGHT_MUSIC_ATTENUATION_MASK	0x00ff0000
+#define		SIS_WEVCR_LEFT_WAVE_ATTENUATION_MASK	0x0000ff00
+#define		SIS_WEVCR_RIGHT_WAVE_ATTENUATION_MASK	0x000000ff
+
+/* Wave Engine Interrupt Status Registers */
+#define SIS_WEISR_A	0xa8
+#define SIS_WEISR_B	0xac
+
+
+/* Playback DMA parameters (parameter RAM) */
+#define SIS_PLAY_DMA_OFFSET	0x0000
+#define SIS_PLAY_DMA_SIZE	0x10
+#define SIS_PLAY_DMA_ADDR(addr, num) \
+	((num * SIS_PLAY_DMA_SIZE) + (addr) + SIS_PLAY_DMA_OFFSET)
+
+#define SIS_PLAY_DMA_FORMAT_CSO	0x00
+#define		SIS_PLAY_DMA_FORMAT_UNSIGNED	0x00080000
+#define		SIS_PLAY_DMA_FORMAT_8BIT	0x00040000
+#define		SIS_PLAY_DMA_FORMAT_MONO	0x00020000
+#define		SIS_PLAY_DMA_CSO_MASK		0x0000ffff
+#define SIS_PLAY_DMA_BASE	0x04
+#define SIS_PLAY_DMA_CONTROL	0x08
+#define		SIS_PLAY_DMA_STOP_AT_SSO	0x04000000
+#define		SIS_PLAY_DMA_RELEASE		0x02000000
+#define		SIS_PLAY_DMA_LOOP		0x01000000
+#define		SIS_PLAY_DMA_INTR_AT_SSO	0x00080000
+#define		SIS_PLAY_DMA_INTR_AT_ESO	0x00040000
+#define		SIS_PLAY_DMA_INTR_AT_LEO	0x00020000
+#define		SIS_PLAY_DMA_INTR_AT_MLP	0x00010000
+#define		SIS_PLAY_DMA_LEO_MASK		0x0000ffff
+#define SIS_PLAY_DMA_SSO_ESO	0x0c
+#define		SIS_PLAY_DMA_SSO_MASK		0xffff0000
+#define		SIS_PLAY_DMA_ESO_MASK		0x0000ffff
+
+/* Capture DMA parameters (parameter RAM) */
+#define SIS_CAPTURE_DMA_OFFSET	0x0800
+#define SIS_CAPTURE_DMA_SIZE	0x10
+#define SIS_CAPTURE_DMA_ADDR(addr, num) \
+	((num * SIS_CAPTURE_DMA_SIZE) + (addr) + SIS_CAPTURE_DMA_OFFSET)
+
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_0	0
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_1	1
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_2	2
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_3	3
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_4	4
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_5	5
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_6	6
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_7	7
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_8	8
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_9	9
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_10	10
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_11	11
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_12	12
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_13	13
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_14	14
+#define	SIS_CAPTURE_CHAN_MIXER_ROUTE_BACK_15	15
+#define	SIS_CAPTURE_CHAN_AC97_PCM_IN		16
+#define	SIS_CAPTURE_CHAN_AC97_MIC_IN		17
+#define	SIS_CAPTURE_CHAN_AC97_LINE1_IN		18
+#define	SIS_CAPTURE_CHAN_AC97_LINE2_IN		19
+#define	SIS_CAPTURE_CHAN_AC97_HANDSE_IN		20
+
+#define SIS_CAPTURE_DMA_FORMAT_CSO	0x00
+#define		SIS_CAPTURE_DMA_MONO_MODE_MASK	0xc0000000
+#define		SIS_CAPTURE_DMA_MONO_MODE_AVG	0x00000000
+#define		SIS_CAPTURE_DMA_MONO_MODE_LEFT	0x40000000
+#define		SIS_CAPTURE_DMA_MONO_MODE_RIGHT	0x80000000
+#define		SIS_CAPTURE_DMA_FORMAT_UNSIGNED	0x00080000
+#define		SIS_CAPTURE_DMA_FORMAT_8BIT	0x00040000
+#define		SIS_CAPTURE_DMA_FORMAT_MONO	0x00020000
+#define		SIS_CAPTURE_DMA_CSO_MASK		0x0000ffff
+#define SIS_CAPTURE_DMA_BASE		0x04
+#define SIS_CAPTURE_DMA_CONTROL		0x08
+#define		SIS_CAPTURE_DMA_STOP_AT_SSO	0x04000000
+#define		SIS_CAPTURE_DMA_RELEASE		0x02000000
+#define		SIS_CAPTURE_DMA_LOOP		0x01000000
+#define		SIS_CAPTURE_DMA_INTR_AT_LEO	0x00020000
+#define		SIS_CAPTURE_DMA_INTR_AT_MLP	0x00010000
+#define		SIS_CAPTURE_DMA_LEO_MASK		0x0000ffff
+#define SIS_CAPTURE_DMA_RESERVED	0x0c
+
+
+/* Mixer routing list start pointer (parameter RAM) */
+#define SIS_MIXER_START_OFFSET	0x1000
+#define SIS_MIXER_START_SIZE	0x04
+#define SIS_MIXER_START_ADDR(addr, num) \
+	((num * SIS_MIXER_START_SIZE) + (addr) + SIS_MIXER_START_OFFSET)
+
+#define SIS_MIXER_START_MASK	0x0000007f
+
+/* Mixer routing table (parameter RAM) */
+#define SIS_MIXER_OFFSET	0x1400
+#define SIS_MIXER_SIZE		0x04
+#define SIS_MIXER_ADDR(addr, num) \
+	((num * SIS_MIXER_SIZE) + (addr) + SIS_MIXER_OFFSET)
+
+#define SIS_MIXER_RIGHT_ATTENUTATION_MASK	0xff000000
+#define 	SIS_MIXER_RIGHT_NO_ATTEN		0xff000000
+#define SIS_MIXER_LEFT_ATTENUTATION_MASK	0x00ff0000
+#define 	SIS_MIXER_LEFT_NO_ATTEN			0x00ff0000
+#define SIS_MIXER_NEXT_ENTRY_MASK		0x00007f00
+#define 	SIS_MIXER_NEXT_ENTRY_NONE		0x00000000
+#define SIS_MIXER_DEST_MASK			0x0000007f
+#define 	SIS_MIXER_DEST_0			0x00000020
+#define 	SIS_MIXER_DEST_1			0x00000021
+#define 	SIS_MIXER_DEST_2			0x00000022
+#define 	SIS_MIXER_DEST_3			0x00000023
+#define 	SIS_MIXER_DEST_4			0x00000024
+#define 	SIS_MIXER_DEST_5			0x00000025
+#define 	SIS_MIXER_DEST_6			0x00000026
+#define 	SIS_MIXER_DEST_7			0x00000027
+#define 	SIS_MIXER_DEST_8			0x00000028
+#define 	SIS_MIXER_DEST_9			0x00000029
+#define 	SIS_MIXER_DEST_10			0x0000002a
+#define 	SIS_MIXER_DEST_11			0x0000002b
+#define 	SIS_MIXER_DEST_12			0x0000002c
+#define 	SIS_MIXER_DEST_13			0x0000002d
+#define 	SIS_MIXER_DEST_14			0x0000002e
+#define 	SIS_MIXER_DEST_15			0x0000002f
+
+/* Wave Engine Control Parameters (parameter RAM) */
+#define SIS_WAVE_OFFSET		0x2000
+#define SIS_WAVE_SIZE		0x40
+#define SIS_WAVE_ADDR(addr, num) \
+	((num * SIS_WAVE_SIZE) + (addr) + SIS_WAVE_OFFSET)
+
+#define SIS_WAVE_GENERAL		0x00
+#define		SIS_WAVE_GENERAL_WAVE_VOLUME			0x80000000
+#define		SIS_WAVE_GENERAL_MUSIC_VOLUME			0x00000000
+#define		SIS_WAVE_GENERAL_VOLUME_MASK			0x7f000000
+#define SIS_WAVE_GENERAL_ARTICULATION	0x04
+#define		SIS_WAVE_GENERAL_ARTICULATION_DELTA_MASK	0x3fff0000
+#define SIS_WAVE_ARTICULATION		0x08
+#define SIS_WAVE_TIMER			0x0c
+#define SIS_WAVE_GENERATOR		0x10
+#define SIS_WAVE_CHANNEL_CONTROL	0x14
+#define		SIS_WAVE_CHANNEL_CONTROL_FIRST_SAMPLE		0x80000000
+#define		SIS_WAVE_CHANNEL_CONTROL_AMP_ENABLE		0x40000000
+#define		SIS_WAVE_CHANNEL_CONTROL_FILTER_ENABLE		0x20000000
+#define		SIS_WAVE_CHANNEL_CONTROL_INTERPOLATE_ENABLE	0x10000000
+#define SIS_WAVE_LFO_EG_CONTROL		0x18
+#define SIS_WAVE_LFO_EG_CONTROL_2	0x1c
+#define SIS_WAVE_LFO_EG_CONTROL_3	0x20
+#define SIS_WAVE_LFO_EG_CONTROL_4	0x24
+
+#endif /* __sis7019_h__ */
diff --git a/linux/sound/pci/sonicvibes.c b/linux/sound/pci/sonicvibes.c
new file mode 100644
index 0000000..337b9fa
--- /dev/null
+++ b/linux/sound/pci/sonicvibes.c
@@ -0,0 +1,1550 @@
+/*
+ *  Driver for S3 SonicVibes soundcard
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *  BUGS:
+ *    It looks like 86c617 rev 3 doesn't supports DDMA buffers above 16MB?
+ *    Driver sometimes hangs... Nobody knows why at this moment...
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/moduleparam.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("S3 SonicVibes PCI");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{S3,SonicVibes PCI}}");
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+#define SUPPORT_JOYSTICK 1
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static int reverb[SNDRV_CARDS];
+static int mge[SNDRV_CARDS];
+static unsigned int dmaio = 0x7a00;	/* DDMA i/o address */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for S3 SonicVibes soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for S3 SonicVibes soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable S3 SonicVibes soundcard.");
+module_param_array(reverb, bool, NULL, 0444);
+MODULE_PARM_DESC(reverb, "Enable reverb (SRAM is present) for S3 SonicVibes soundcard.");
+module_param_array(mge, bool, NULL, 0444);
+MODULE_PARM_DESC(mge, "MIC Gain Enable for S3 SonicVibes soundcard.");
+module_param(dmaio, uint, 0444);
+MODULE_PARM_DESC(dmaio, "DDMA i/o base address for S3 SonicVibes soundcard.");
+
+/*
+ * Enhanced port direct registers
+ */
+
+#define SV_REG(sonic, x) ((sonic)->enh_port + SV_REG_##x)
+
+#define SV_REG_CONTROL	0x00	/* R/W: CODEC/Mixer control register */
+#define   SV_ENHANCED	  0x01	/* audio mode select - enhanced mode */
+#define   SV_TEST	  0x02	/* test bit */
+#define   SV_REVERB	  0x04	/* reverb enable */
+#define   SV_WAVETABLE	  0x08	/* wavetable active / FM active if not set */
+#define   SV_INTA	  0x20	/* INTA driving - should be always 1 */
+#define   SV_RESET	  0x80	/* reset chip */
+#define SV_REG_IRQMASK	0x01	/* R/W: CODEC/Mixer interrupt mask register */
+#define   SV_DMAA_MASK	  0x01	/* mask DMA-A interrupt */
+#define   SV_DMAC_MASK	  0x04	/* mask DMA-C interrupt */
+#define   SV_SPEC_MASK	  0x08	/* special interrupt mask - should be always masked */
+#define   SV_UD_MASK	  0x40	/* Up/Down button interrupt mask */
+#define   SV_MIDI_MASK	  0x80	/* mask MIDI interrupt */
+#define SV_REG_STATUS	0x02	/* R/O: CODEC/Mixer status register */
+#define   SV_DMAA_IRQ	  0x01	/* DMA-A interrupt */
+#define   SV_DMAC_IRQ	  0x04	/* DMA-C interrupt */
+#define   SV_SPEC_IRQ	  0x08	/* special interrupt */
+#define   SV_UD_IRQ	  0x40	/* Up/Down interrupt */
+#define   SV_MIDI_IRQ	  0x80	/* MIDI interrupt */
+#define SV_REG_INDEX	0x04	/* R/W: CODEC/Mixer index address register */
+#define   SV_MCE          0x40	/* mode change enable */
+#define   SV_TRD	  0x80	/* DMA transfer request disabled */
+#define SV_REG_DATA	0x05	/* R/W: CODEC/Mixer index data register */
+
+/*
+ * Enhanced port indirect registers
+ */
+
+#define SV_IREG_LEFT_ADC	0x00	/* Left ADC Input Control */
+#define SV_IREG_RIGHT_ADC	0x01	/* Right ADC Input Control */
+#define SV_IREG_LEFT_AUX1	0x02	/* Left AUX1 Input Control */
+#define SV_IREG_RIGHT_AUX1	0x03	/* Right AUX1 Input Control */
+#define SV_IREG_LEFT_CD		0x04	/* Left CD Input Control */
+#define SV_IREG_RIGHT_CD	0x05	/* Right CD Input Control */
+#define SV_IREG_LEFT_LINE	0x06	/* Left Line Input Control */
+#define SV_IREG_RIGHT_LINE	0x07	/* Right Line Input Control */
+#define SV_IREG_MIC		0x08	/* MIC Input Control */
+#define SV_IREG_GAME_PORT	0x09	/* Game Port Control */
+#define SV_IREG_LEFT_SYNTH	0x0a	/* Left Synth Input Control */
+#define SV_IREG_RIGHT_SYNTH	0x0b	/* Right Synth Input Control */
+#define SV_IREG_LEFT_AUX2	0x0c	/* Left AUX2 Input Control */
+#define SV_IREG_RIGHT_AUX2	0x0d	/* Right AUX2 Input Control */
+#define SV_IREG_LEFT_ANALOG	0x0e	/* Left Analog Mixer Output Control */
+#define SV_IREG_RIGHT_ANALOG	0x0f	/* Right Analog Mixer Output Control */
+#define SV_IREG_LEFT_PCM	0x10	/* Left PCM Input Control */
+#define SV_IREG_RIGHT_PCM	0x11	/* Right PCM Input Control */
+#define SV_IREG_DMA_DATA_FMT	0x12	/* DMA Data Format */
+#define SV_IREG_PC_ENABLE	0x13	/* Playback/Capture Enable Register */
+#define SV_IREG_UD_BUTTON	0x14	/* Up/Down Button Register */
+#define SV_IREG_REVISION	0x15	/* Revision */
+#define SV_IREG_ADC_OUTPUT_CTRL	0x16	/* ADC Output Control */
+#define SV_IREG_DMA_A_UPPER	0x18	/* DMA A Upper Base Count */
+#define SV_IREG_DMA_A_LOWER	0x19	/* DMA A Lower Base Count */
+#define SV_IREG_DMA_C_UPPER	0x1c	/* DMA C Upper Base Count */
+#define SV_IREG_DMA_C_LOWER	0x1d	/* DMA C Lower Base Count */
+#define SV_IREG_PCM_RATE_LOW	0x1e	/* PCM Sampling Rate Low Byte */
+#define SV_IREG_PCM_RATE_HIGH	0x1f	/* PCM Sampling Rate High Byte */
+#define SV_IREG_SYNTH_RATE_LOW	0x20	/* Synthesizer Sampling Rate Low Byte */
+#define SV_IREG_SYNTH_RATE_HIGH 0x21	/* Synthesizer Sampling Rate High Byte */
+#define SV_IREG_ADC_CLOCK	0x22	/* ADC Clock Source Selection */
+#define SV_IREG_ADC_ALT_RATE	0x23	/* ADC Alternative Sampling Rate Selection */
+#define SV_IREG_ADC_PLL_M	0x24	/* ADC PLL M Register */
+#define SV_IREG_ADC_PLL_N	0x25	/* ADC PLL N Register */
+#define SV_IREG_SYNTH_PLL_M	0x26	/* Synthesizer PLL M Register */
+#define SV_IREG_SYNTH_PLL_N	0x27	/* Synthesizer PLL N Register */
+#define SV_IREG_MPU401		0x2a	/* MPU-401 UART Operation */
+#define SV_IREG_DRIVE_CTRL	0x2b	/* Drive Control */
+#define SV_IREG_SRS_SPACE	0x2c	/* SRS Space Control */
+#define SV_IREG_SRS_CENTER	0x2d	/* SRS Center Control */
+#define SV_IREG_WAVE_SOURCE	0x2e	/* Wavetable Sample Source Select */
+#define SV_IREG_ANALOG_POWER	0x30	/* Analog Power Down Control */
+#define SV_IREG_DIGITAL_POWER	0x31	/* Digital Power Down Control */
+
+#define SV_IREG_ADC_PLL		SV_IREG_ADC_PLL_M
+#define SV_IREG_SYNTH_PLL	SV_IREG_SYNTH_PLL_M
+
+/*
+ *  DMA registers
+ */
+
+#define SV_DMA_ADDR0		0x00
+#define SV_DMA_ADDR1		0x01
+#define SV_DMA_ADDR2		0x02
+#define SV_DMA_ADDR3		0x03
+#define SV_DMA_COUNT0		0x04
+#define SV_DMA_COUNT1		0x05
+#define SV_DMA_COUNT2		0x06
+#define SV_DMA_MODE		0x0b
+#define SV_DMA_RESET		0x0d
+#define SV_DMA_MASK		0x0f
+
+/*
+ *  Record sources
+ */
+
+#define SV_RECSRC_RESERVED	(0x00<<5)
+#define SV_RECSRC_CD		(0x01<<5)
+#define SV_RECSRC_DAC		(0x02<<5)
+#define SV_RECSRC_AUX2		(0x03<<5)
+#define SV_RECSRC_LINE		(0x04<<5)
+#define SV_RECSRC_AUX1		(0x05<<5)
+#define SV_RECSRC_MIC		(0x06<<5)
+#define SV_RECSRC_OUT		(0x07<<5)
+
+/*
+ *  constants
+ */
+
+#define SV_FULLRATE		48000
+#define SV_REFFREQUENCY		24576000
+#define SV_ADCMULT		512
+
+#define SV_MODE_PLAY		1
+#define SV_MODE_CAPTURE		2
+
+/*
+
+ */
+
+struct sonicvibes {
+	unsigned long dma1size;
+	unsigned long dma2size;
+	int irq;
+
+	unsigned long sb_port;
+	unsigned long enh_port;
+	unsigned long synth_port;
+	unsigned long midi_port;
+	unsigned long game_port;
+	unsigned int dmaa_port;
+	struct resource *res_dmaa;
+	unsigned int dmac_port;
+	struct resource *res_dmac;
+
+	unsigned char enable;
+	unsigned char irqmask;
+	unsigned char revision;
+	unsigned char format;
+	unsigned char srs_space;
+	unsigned char srs_center;
+	unsigned char mpu_switch;
+	unsigned char wave_source;
+
+	unsigned int mode;
+
+	struct pci_dev *pci;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *playback_substream;
+	struct snd_pcm_substream *capture_substream;
+	struct snd_rawmidi *rmidi;
+	struct snd_hwdep *fmsynth;	/* S3FM */
+
+	spinlock_t reg_lock;
+
+	unsigned int p_dma_size;
+	unsigned int c_dma_size;
+
+	struct snd_kcontrol *master_mute;
+	struct snd_kcontrol *master_volume;
+
+#ifdef SUPPORT_JOYSTICK
+	struct gameport *gameport;
+#endif
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_sonic_ids) = {
+	{ PCI_VDEVICE(S3, 0xca00), 0, },
+        { 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_sonic_ids);
+
+static struct snd_ratden sonicvibes_adc_clock = {
+	.num_min = 4000 * 65536,
+	.num_max = 48000UL * 65536,
+	.num_step = 1,
+	.den = 65536,
+};
+static struct snd_pcm_hw_constraint_ratdens snd_sonicvibes_hw_constraints_adc_clock = {
+	.nrats = 1,
+	.rats = &sonicvibes_adc_clock,
+};
+
+/*
+ *  common I/O routines
+ */
+
+static inline void snd_sonicvibes_setdmaa(struct sonicvibes * sonic,
+					  unsigned int addr,
+					  unsigned int count)
+{
+	count--;
+	outl(addr, sonic->dmaa_port + SV_DMA_ADDR0);
+	outl(count, sonic->dmaa_port + SV_DMA_COUNT0);
+	outb(0x18, sonic->dmaa_port + SV_DMA_MODE);
+#if 0
+	printk(KERN_DEBUG "program dmaa: addr = 0x%x, paddr = 0x%x\n",
+	       addr, inl(sonic->dmaa_port + SV_DMA_ADDR0));
+#endif
+}
+
+static inline void snd_sonicvibes_setdmac(struct sonicvibes * sonic,
+					  unsigned int addr,
+					  unsigned int count)
+{
+	/* note: dmac is working in word mode!!! */
+	count >>= 1;
+	count--;
+	outl(addr, sonic->dmac_port + SV_DMA_ADDR0);
+	outl(count, sonic->dmac_port + SV_DMA_COUNT0);
+	outb(0x14, sonic->dmac_port + SV_DMA_MODE);
+#if 0
+	printk(KERN_DEBUG "program dmac: addr = 0x%x, paddr = 0x%x\n",
+	       addr, inl(sonic->dmac_port + SV_DMA_ADDR0));
+#endif
+}
+
+static inline unsigned int snd_sonicvibes_getdmaa(struct sonicvibes * sonic)
+{
+	return (inl(sonic->dmaa_port + SV_DMA_COUNT0) & 0xffffff) + 1;
+}
+
+static inline unsigned int snd_sonicvibes_getdmac(struct sonicvibes * sonic)
+{
+	/* note: dmac is working in word mode!!! */
+	return ((inl(sonic->dmac_port + SV_DMA_COUNT0) & 0xffffff) + 1) << 1;
+}
+
+static void snd_sonicvibes_out1(struct sonicvibes * sonic,
+				unsigned char reg,
+				unsigned char value)
+{
+	outb(reg, SV_REG(sonic, INDEX));
+	udelay(10);
+	outb(value, SV_REG(sonic, DATA));
+	udelay(10);
+}
+
+static void snd_sonicvibes_out(struct sonicvibes * sonic,
+			       unsigned char reg,
+			       unsigned char value)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&sonic->reg_lock, flags);
+	outb(reg, SV_REG(sonic, INDEX));
+	udelay(10);
+	outb(value, SV_REG(sonic, DATA));
+	udelay(10);
+	spin_unlock_irqrestore(&sonic->reg_lock, flags);
+}
+
+static unsigned char snd_sonicvibes_in1(struct sonicvibes * sonic, unsigned char reg)
+{
+	unsigned char value;
+
+	outb(reg, SV_REG(sonic, INDEX));
+	udelay(10);
+	value = inb(SV_REG(sonic, DATA));
+	udelay(10);
+	return value;
+}
+
+static unsigned char snd_sonicvibes_in(struct sonicvibes * sonic, unsigned char reg)
+{
+	unsigned long flags;
+	unsigned char value;
+
+	spin_lock_irqsave(&sonic->reg_lock, flags);
+	outb(reg, SV_REG(sonic, INDEX));
+	udelay(10);
+	value = inb(SV_REG(sonic, DATA));
+	udelay(10);
+	spin_unlock_irqrestore(&sonic->reg_lock, flags);
+	return value;
+}
+
+#if 0
+static void snd_sonicvibes_debug(struct sonicvibes * sonic)
+{
+	printk(KERN_DEBUG
+	       "SV REGS:          INDEX = 0x%02x  ", inb(SV_REG(sonic, INDEX)));
+	printk("                 STATUS = 0x%02x\n", inb(SV_REG(sonic, STATUS)));
+	printk(KERN_DEBUG
+	       "  0x00: left input      = 0x%02x  ", snd_sonicvibes_in(sonic, 0x00));
+	printk("  0x20: synth rate low  = 0x%02x\n", snd_sonicvibes_in(sonic, 0x20));
+	printk(KERN_DEBUG
+	       "  0x01: right input     = 0x%02x  ", snd_sonicvibes_in(sonic, 0x01));
+	printk("  0x21: synth rate high = 0x%02x\n", snd_sonicvibes_in(sonic, 0x21));
+	printk(KERN_DEBUG
+	       "  0x02: left AUX1       = 0x%02x  ", snd_sonicvibes_in(sonic, 0x02));
+	printk("  0x22: ADC clock       = 0x%02x\n", snd_sonicvibes_in(sonic, 0x22));
+	printk(KERN_DEBUG
+	       "  0x03: right AUX1      = 0x%02x  ", snd_sonicvibes_in(sonic, 0x03));
+	printk("  0x23: ADC alt rate    = 0x%02x\n", snd_sonicvibes_in(sonic, 0x23));
+	printk(KERN_DEBUG
+	       "  0x04: left CD         = 0x%02x  ", snd_sonicvibes_in(sonic, 0x04));
+	printk("  0x24: ADC pll M       = 0x%02x\n", snd_sonicvibes_in(sonic, 0x24));
+	printk(KERN_DEBUG
+	       "  0x05: right CD        = 0x%02x  ", snd_sonicvibes_in(sonic, 0x05));
+	printk("  0x25: ADC pll N       = 0x%02x\n", snd_sonicvibes_in(sonic, 0x25));
+	printk(KERN_DEBUG
+	       "  0x06: left line       = 0x%02x  ", snd_sonicvibes_in(sonic, 0x06));
+	printk("  0x26: Synth pll M     = 0x%02x\n", snd_sonicvibes_in(sonic, 0x26));
+	printk(KERN_DEBUG
+	       "  0x07: right line      = 0x%02x  ", snd_sonicvibes_in(sonic, 0x07));
+	printk("  0x27: Synth pll N     = 0x%02x\n", snd_sonicvibes_in(sonic, 0x27));
+	printk(KERN_DEBUG
+	       "  0x08: MIC             = 0x%02x  ", snd_sonicvibes_in(sonic, 0x08));
+	printk("  0x28: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x28));
+	printk(KERN_DEBUG
+	       "  0x09: Game port       = 0x%02x  ", snd_sonicvibes_in(sonic, 0x09));
+	printk("  0x29: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x29));
+	printk(KERN_DEBUG
+	       "  0x0a: left synth      = 0x%02x  ", snd_sonicvibes_in(sonic, 0x0a));
+	printk("  0x2a: MPU401          = 0x%02x\n", snd_sonicvibes_in(sonic, 0x2a));
+	printk(KERN_DEBUG
+	       "  0x0b: right synth     = 0x%02x  ", snd_sonicvibes_in(sonic, 0x0b));
+	printk("  0x2b: drive ctrl      = 0x%02x\n", snd_sonicvibes_in(sonic, 0x2b));
+	printk(KERN_DEBUG
+	       "  0x0c: left AUX2       = 0x%02x  ", snd_sonicvibes_in(sonic, 0x0c));
+	printk("  0x2c: SRS space       = 0x%02x\n", snd_sonicvibes_in(sonic, 0x2c));
+	printk(KERN_DEBUG
+	       "  0x0d: right AUX2      = 0x%02x  ", snd_sonicvibes_in(sonic, 0x0d));
+	printk("  0x2d: SRS center      = 0x%02x\n", snd_sonicvibes_in(sonic, 0x2d));
+	printk(KERN_DEBUG
+	       "  0x0e: left analog     = 0x%02x  ", snd_sonicvibes_in(sonic, 0x0e));
+	printk("  0x2e: wave source     = 0x%02x\n", snd_sonicvibes_in(sonic, 0x2e));
+	printk(KERN_DEBUG
+	       "  0x0f: right analog    = 0x%02x  ", snd_sonicvibes_in(sonic, 0x0f));
+	printk("  0x2f: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x2f));
+	printk(KERN_DEBUG
+	       "  0x10: left PCM        = 0x%02x  ", snd_sonicvibes_in(sonic, 0x10));
+	printk("  0x30: analog power    = 0x%02x\n", snd_sonicvibes_in(sonic, 0x30));
+	printk(KERN_DEBUG
+	       "  0x11: right PCM       = 0x%02x  ", snd_sonicvibes_in(sonic, 0x11));
+	printk("  0x31: analog power    = 0x%02x\n", snd_sonicvibes_in(sonic, 0x31));
+	printk(KERN_DEBUG
+	       "  0x12: DMA data format = 0x%02x  ", snd_sonicvibes_in(sonic, 0x12));
+	printk("  0x32: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x32));
+	printk(KERN_DEBUG
+	       "  0x13: P/C enable      = 0x%02x  ", snd_sonicvibes_in(sonic, 0x13));
+	printk("  0x33: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x33));
+	printk(KERN_DEBUG
+	       "  0x14: U/D button      = 0x%02x  ", snd_sonicvibes_in(sonic, 0x14));
+	printk("  0x34: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x34));
+	printk(KERN_DEBUG
+	       "  0x15: revision        = 0x%02x  ", snd_sonicvibes_in(sonic, 0x15));
+	printk("  0x35: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x35));
+	printk(KERN_DEBUG
+	       "  0x16: ADC output ctrl = 0x%02x  ", snd_sonicvibes_in(sonic, 0x16));
+	printk("  0x36: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x36));
+	printk(KERN_DEBUG
+	       "  0x17: ---             = 0x%02x  ", snd_sonicvibes_in(sonic, 0x17));
+	printk("  0x37: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x37));
+	printk(KERN_DEBUG
+	       "  0x18: DMA A upper cnt = 0x%02x  ", snd_sonicvibes_in(sonic, 0x18));
+	printk("  0x38: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x38));
+	printk(KERN_DEBUG
+	       "  0x19: DMA A lower cnt = 0x%02x  ", snd_sonicvibes_in(sonic, 0x19));
+	printk("  0x39: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x39));
+	printk(KERN_DEBUG
+	       "  0x1a: ---             = 0x%02x  ", snd_sonicvibes_in(sonic, 0x1a));
+	printk("  0x3a: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x3a));
+	printk(KERN_DEBUG
+	       "  0x1b: ---             = 0x%02x  ", snd_sonicvibes_in(sonic, 0x1b));
+	printk("  0x3b: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x3b));
+	printk(KERN_DEBUG
+	       "  0x1c: DMA C upper cnt = 0x%02x  ", snd_sonicvibes_in(sonic, 0x1c));
+	printk("  0x3c: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x3c));
+	printk(KERN_DEBUG
+	       "  0x1d: DMA C upper cnt = 0x%02x  ", snd_sonicvibes_in(sonic, 0x1d));
+	printk("  0x3d: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x3d));
+	printk(KERN_DEBUG
+	       "  0x1e: PCM rate low    = 0x%02x  ", snd_sonicvibes_in(sonic, 0x1e));
+	printk("  0x3e: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x3e));
+	printk(KERN_DEBUG
+	       "  0x1f: PCM rate high   = 0x%02x  ", snd_sonicvibes_in(sonic, 0x1f));
+	printk("  0x3f: ---             = 0x%02x\n", snd_sonicvibes_in(sonic, 0x3f));
+}
+
+#endif
+
+static void snd_sonicvibes_setfmt(struct sonicvibes * sonic,
+                                  unsigned char mask,
+                                  unsigned char value)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&sonic->reg_lock, flags);
+	outb(SV_MCE | SV_IREG_DMA_DATA_FMT, SV_REG(sonic, INDEX));
+	if (mask) {
+		sonic->format = inb(SV_REG(sonic, DATA));
+		udelay(10);
+	}
+	sonic->format = (sonic->format & mask) | value;
+	outb(sonic->format, SV_REG(sonic, DATA));
+	udelay(10);
+	outb(0, SV_REG(sonic, INDEX));
+	udelay(10);
+	spin_unlock_irqrestore(&sonic->reg_lock, flags);
+}
+
+static void snd_sonicvibes_pll(unsigned int rate,
+			       unsigned int *res_r,
+			       unsigned int *res_m,
+			       unsigned int *res_n)
+{
+	unsigned int r, m = 0, n = 0;
+	unsigned int xm, xn, xr, xd, metric = ~0U;
+
+	if (rate < 625000 / SV_ADCMULT)
+		rate = 625000 / SV_ADCMULT;
+	if (rate > 150000000 / SV_ADCMULT)
+		rate = 150000000 / SV_ADCMULT;
+	/* slight violation of specs, needed for continuous sampling rates */
+	for (r = 0; rate < 75000000 / SV_ADCMULT; r += 0x20, rate <<= 1);
+	for (xn = 3; xn < 33; xn++)	/* 35 */
+		for (xm = 3; xm < 257; xm++) {
+			xr = ((SV_REFFREQUENCY / SV_ADCMULT) * xm) / xn;
+			if (xr >= rate)
+				xd = xr - rate;
+			else
+				xd = rate - xr;
+			if (xd < metric) {
+				metric = xd;
+				m = xm - 2;
+				n = xn - 2;
+			}
+		}
+	*res_r = r;
+	*res_m = m;
+	*res_n = n;
+#if 0
+	printk(KERN_DEBUG "metric = %i, xm = %i, xn = %i\n", metric, xm, xn);
+	printk(KERN_DEBUG "pll: m = 0x%x, r = 0x%x, n = 0x%x\n", reg, m, r, n);
+#endif
+}
+
+static void snd_sonicvibes_setpll(struct sonicvibes * sonic,
+                                  unsigned char reg,
+                                  unsigned int rate)
+{
+	unsigned long flags;
+	unsigned int r, m, n;
+
+	snd_sonicvibes_pll(rate, &r, &m, &n);
+	if (sonic != NULL) {
+		spin_lock_irqsave(&sonic->reg_lock, flags);
+		snd_sonicvibes_out1(sonic, reg, m);
+		snd_sonicvibes_out1(sonic, reg + 1, r | n);
+		spin_unlock_irqrestore(&sonic->reg_lock, flags);
+	}
+}
+
+static void snd_sonicvibes_set_adc_rate(struct sonicvibes * sonic, unsigned int rate)
+{
+	unsigned long flags;
+	unsigned int div;
+	unsigned char clock;
+
+	div = 48000 / rate;
+	if (div > 8)
+		div = 8;
+	if ((48000 / div) == rate) {	/* use the alternate clock */
+		clock = 0x10;
+	} else {			/* use the PLL source */
+		clock = 0x00;
+		snd_sonicvibes_setpll(sonic, SV_IREG_ADC_PLL, rate);
+	}
+	spin_lock_irqsave(&sonic->reg_lock, flags);
+	snd_sonicvibes_out1(sonic, SV_IREG_ADC_ALT_RATE, (div - 1) << 4);
+	snd_sonicvibes_out1(sonic, SV_IREG_ADC_CLOCK, clock);
+	spin_unlock_irqrestore(&sonic->reg_lock, flags);
+}
+
+static int snd_sonicvibes_hw_constraint_dac_rate(struct snd_pcm_hw_params *params,
+						 struct snd_pcm_hw_rule *rule)
+{
+	unsigned int rate, div, r, m, n;
+
+	if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min == 
+	    hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->max) {
+		rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min;
+		div = 48000 / rate;
+		if (div > 8)
+			div = 8;
+		if ((48000 / div) == rate) {
+			params->rate_num = rate;
+			params->rate_den = 1;
+		} else {
+			snd_sonicvibes_pll(rate, &r, &m, &n);
+			snd_BUG_ON(SV_REFFREQUENCY % 16);
+			snd_BUG_ON(SV_ADCMULT % 512);
+			params->rate_num = (SV_REFFREQUENCY/16) * (n+2) * r;
+			params->rate_den = (SV_ADCMULT/512) * (m+2);
+		}
+	}
+	return 0;
+}
+
+static void snd_sonicvibes_set_dac_rate(struct sonicvibes * sonic, unsigned int rate)
+{
+	unsigned int div;
+	unsigned long flags;
+
+	div = (rate * 65536 + SV_FULLRATE / 2) / SV_FULLRATE;
+	if (div > 65535)
+		div = 65535;
+	spin_lock_irqsave(&sonic->reg_lock, flags);
+	snd_sonicvibes_out1(sonic, SV_IREG_PCM_RATE_HIGH, div >> 8);
+	snd_sonicvibes_out1(sonic, SV_IREG_PCM_RATE_LOW, div);
+	spin_unlock_irqrestore(&sonic->reg_lock, flags);
+}
+
+static int snd_sonicvibes_trigger(struct sonicvibes * sonic, int what, int cmd)
+{
+	int result = 0;
+
+	spin_lock(&sonic->reg_lock);
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		if (!(sonic->enable & what)) {
+			sonic->enable |= what;
+			snd_sonicvibes_out1(sonic, SV_IREG_PC_ENABLE, sonic->enable);
+		}
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		if (sonic->enable & what) {
+			sonic->enable &= ~what;
+			snd_sonicvibes_out1(sonic, SV_IREG_PC_ENABLE, sonic->enable);
+		}
+	} else {
+		result = -EINVAL;
+	}
+	spin_unlock(&sonic->reg_lock);
+	return result;
+}
+
+static irqreturn_t snd_sonicvibes_interrupt(int irq, void *dev_id)
+{
+	struct sonicvibes *sonic = dev_id;
+	unsigned char status;
+
+	status = inb(SV_REG(sonic, STATUS));
+	if (!(status & (SV_DMAA_IRQ | SV_DMAC_IRQ | SV_MIDI_IRQ)))
+		return IRQ_NONE;
+	if (status == 0xff) {	/* failure */
+		outb(sonic->irqmask = ~0, SV_REG(sonic, IRQMASK));
+		snd_printk(KERN_ERR "IRQ failure - interrupts disabled!!\n");
+		return IRQ_HANDLED;
+	}
+	if (sonic->pcm) {
+		if (status & SV_DMAA_IRQ)
+			snd_pcm_period_elapsed(sonic->playback_substream);
+		if (status & SV_DMAC_IRQ)
+			snd_pcm_period_elapsed(sonic->capture_substream);
+	}
+	if (sonic->rmidi) {
+		if (status & SV_MIDI_IRQ)
+			snd_mpu401_uart_interrupt(irq, sonic->rmidi->private_data);
+	}
+	if (status & SV_UD_IRQ) {
+		unsigned char udreg;
+		int vol, oleft, oright, mleft, mright;
+
+		spin_lock(&sonic->reg_lock);
+		udreg = snd_sonicvibes_in1(sonic, SV_IREG_UD_BUTTON);
+		vol = udreg & 0x3f;
+		if (!(udreg & 0x40))
+			vol = -vol;
+		oleft = mleft = snd_sonicvibes_in1(sonic, SV_IREG_LEFT_ANALOG);
+		oright = mright = snd_sonicvibes_in1(sonic, SV_IREG_RIGHT_ANALOG);
+		oleft &= 0x1f;
+		oright &= 0x1f;
+		oleft += vol;
+		if (oleft < 0)
+			oleft = 0;
+		if (oleft > 0x1f)
+			oleft = 0x1f;
+		oright += vol;
+		if (oright < 0)
+			oright = 0;
+		if (oright > 0x1f)
+			oright = 0x1f;
+		if (udreg & 0x80) {
+			mleft ^= 0x80;
+			mright ^= 0x80;
+		}
+		oleft |= mleft & 0x80;
+		oright |= mright & 0x80;
+		snd_sonicvibes_out1(sonic, SV_IREG_LEFT_ANALOG, oleft);
+		snd_sonicvibes_out1(sonic, SV_IREG_RIGHT_ANALOG, oright);
+		spin_unlock(&sonic->reg_lock);
+		snd_ctl_notify(sonic->card, SNDRV_CTL_EVENT_MASK_VALUE, &sonic->master_mute->id);
+		snd_ctl_notify(sonic->card, SNDRV_CTL_EVENT_MASK_VALUE, &sonic->master_volume->id);
+	}
+	return IRQ_HANDLED;
+}
+
+/*
+ *  PCM part
+ */
+
+static int snd_sonicvibes_playback_trigger(struct snd_pcm_substream *substream,
+					   int cmd)
+{
+	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
+	return snd_sonicvibes_trigger(sonic, 1, cmd);
+}
+
+static int snd_sonicvibes_capture_trigger(struct snd_pcm_substream *substream,
+					  int cmd)
+{
+	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
+	return snd_sonicvibes_trigger(sonic, 2, cmd);
+}
+
+static int snd_sonicvibes_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_sonicvibes_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_sonicvibes_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned char fmt = 0;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	sonic->p_dma_size = size;
+	count--;
+	if (runtime->channels > 1)
+		fmt |= 1;
+	if (snd_pcm_format_width(runtime->format) == 16)
+		fmt |= 2;
+	snd_sonicvibes_setfmt(sonic, ~3, fmt);
+	snd_sonicvibes_set_dac_rate(sonic, runtime->rate);
+	spin_lock_irq(&sonic->reg_lock);
+	snd_sonicvibes_setdmaa(sonic, runtime->dma_addr, size);
+	snd_sonicvibes_out1(sonic, SV_IREG_DMA_A_UPPER, count >> 8);
+	snd_sonicvibes_out1(sonic, SV_IREG_DMA_A_LOWER, count);
+	spin_unlock_irq(&sonic->reg_lock);
+	return 0;
+}
+
+static int snd_sonicvibes_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned char fmt = 0;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+	sonic->c_dma_size = size;
+	count >>= 1;
+	count--;
+	if (runtime->channels > 1)
+		fmt |= 0x10;
+	if (snd_pcm_format_width(runtime->format) == 16)
+		fmt |= 0x20;
+	snd_sonicvibes_setfmt(sonic, ~0x30, fmt);
+	snd_sonicvibes_set_adc_rate(sonic, runtime->rate);
+	spin_lock_irq(&sonic->reg_lock);
+	snd_sonicvibes_setdmac(sonic, runtime->dma_addr, size);
+	snd_sonicvibes_out1(sonic, SV_IREG_DMA_C_UPPER, count >> 8);
+	snd_sonicvibes_out1(sonic, SV_IREG_DMA_C_LOWER, count);
+	spin_unlock_irq(&sonic->reg_lock);
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_sonicvibes_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(sonic->enable & 1))
+		return 0;
+	ptr = sonic->p_dma_size - snd_sonicvibes_getdmaa(sonic);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_sonicvibes_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
+	size_t ptr;
+	if (!(sonic->enable & 2))
+		return 0;
+	ptr = sonic->c_dma_size - snd_sonicvibes_getdmac(sonic);
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static struct snd_pcm_hardware snd_sonicvibes_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	32,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_sonicvibes_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	32,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static int snd_sonicvibes_playback_open(struct snd_pcm_substream *substream)
+{
+	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	sonic->mode |= SV_MODE_PLAY;
+	sonic->playback_substream = substream;
+	runtime->hw = snd_sonicvibes_playback;
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, snd_sonicvibes_hw_constraint_dac_rate, NULL, SNDRV_PCM_HW_PARAM_RATE, -1);
+	return 0;
+}
+
+static int snd_sonicvibes_capture_open(struct snd_pcm_substream *substream)
+{
+	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	sonic->mode |= SV_MODE_CAPTURE;
+	sonic->capture_substream = substream;
+	runtime->hw = snd_sonicvibes_capture;
+	snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				      &snd_sonicvibes_hw_constraints_adc_clock);
+	return 0;
+}
+
+static int snd_sonicvibes_playback_close(struct snd_pcm_substream *substream)
+{
+	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
+
+	sonic->playback_substream = NULL;
+	sonic->mode &= ~SV_MODE_PLAY;
+	return 0;
+}
+
+static int snd_sonicvibes_capture_close(struct snd_pcm_substream *substream)
+{
+	struct sonicvibes *sonic = snd_pcm_substream_chip(substream);
+
+	sonic->capture_substream = NULL;
+	sonic->mode &= ~SV_MODE_CAPTURE;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_sonicvibes_playback_ops = {
+	.open =		snd_sonicvibes_playback_open,
+	.close =	snd_sonicvibes_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_sonicvibes_hw_params,
+	.hw_free =	snd_sonicvibes_hw_free,
+	.prepare =	snd_sonicvibes_playback_prepare,
+	.trigger =	snd_sonicvibes_playback_trigger,
+	.pointer =	snd_sonicvibes_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_sonicvibes_capture_ops = {
+	.open =		snd_sonicvibes_capture_open,
+	.close =	snd_sonicvibes_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_sonicvibes_hw_params,
+	.hw_free =	snd_sonicvibes_hw_free,
+	.prepare =	snd_sonicvibes_capture_prepare,
+	.trigger =	snd_sonicvibes_capture_trigger,
+	.pointer =	snd_sonicvibes_capture_pointer,
+};
+
+static int __devinit snd_sonicvibes_pcm(struct sonicvibes * sonic, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(sonic->card, "s3_86c617", device, 1, 1, &pcm)) < 0)
+		return err;
+	if (snd_BUG_ON(!pcm))
+		return -EINVAL;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sonicvibes_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sonicvibes_capture_ops);
+
+	pcm->private_data = sonic;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "S3 SonicVibes");
+	sonic->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(sonic->pci), 64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+/*
+ *  Mixer part
+ */
+
+#define SONICVIBES_MUX(xname, xindex) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_sonicvibes_info_mux, \
+  .get = snd_sonicvibes_get_mux, .put = snd_sonicvibes_put_mux }
+
+static int snd_sonicvibes_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[7] = {
+		"CD", "PCM", "Aux1", "Line", "Aux0", "Mic", "Mix"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 2;
+	uinfo->value.enumerated.items = 7;
+	if (uinfo->value.enumerated.item >= 7)
+		uinfo->value.enumerated.item = 6;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_sonicvibes_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
+	
+	spin_lock_irq(&sonic->reg_lock);
+	ucontrol->value.enumerated.item[0] = ((snd_sonicvibes_in1(sonic, SV_IREG_LEFT_ADC) & SV_RECSRC_OUT) >> 5) - 1;
+	ucontrol->value.enumerated.item[1] = ((snd_sonicvibes_in1(sonic, SV_IREG_RIGHT_ADC) & SV_RECSRC_OUT) >> 5) - 1;
+	spin_unlock_irq(&sonic->reg_lock);
+	return 0;
+}
+
+static int snd_sonicvibes_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
+	unsigned short left, right, oval1, oval2;
+	int change;
+	
+	if (ucontrol->value.enumerated.item[0] >= 7 ||
+	    ucontrol->value.enumerated.item[1] >= 7)
+		return -EINVAL;
+	left = (ucontrol->value.enumerated.item[0] + 1) << 5;
+	right = (ucontrol->value.enumerated.item[1] + 1) << 5;
+	spin_lock_irq(&sonic->reg_lock);
+	oval1 = snd_sonicvibes_in1(sonic, SV_IREG_LEFT_ADC);
+	oval2 = snd_sonicvibes_in1(sonic, SV_IREG_RIGHT_ADC);
+	left = (oval1 & ~SV_RECSRC_OUT) | left;
+	right = (oval2 & ~SV_RECSRC_OUT) | right;
+	change = left != oval1 || right != oval2;
+	snd_sonicvibes_out1(sonic, SV_IREG_LEFT_ADC, left);
+	snd_sonicvibes_out1(sonic, SV_IREG_RIGHT_ADC, right);
+	spin_unlock_irq(&sonic->reg_lock);
+	return change;
+}
+
+#define SONICVIBES_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_sonicvibes_info_single, \
+  .get = snd_sonicvibes_get_single, .put = snd_sonicvibes_put_single, \
+  .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
+
+static int snd_sonicvibes_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_sonicvibes_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	
+	spin_lock_irq(&sonic->reg_lock);
+	ucontrol->value.integer.value[0] = (snd_sonicvibes_in1(sonic, reg)>> shift) & mask;
+	spin_unlock_irq(&sonic->reg_lock);
+	if (invert)
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_sonicvibes_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val, oval;
+	
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+	spin_lock_irq(&sonic->reg_lock);
+	oval = snd_sonicvibes_in1(sonic, reg);
+	val = (oval & ~(mask << shift)) | val;
+	change = val != oval;
+	snd_sonicvibes_out1(sonic, reg, val);
+	spin_unlock_irq(&sonic->reg_lock);
+	return change;
+}
+
+#define SONICVIBES_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_sonicvibes_info_double, \
+  .get = snd_sonicvibes_get_double, .put = snd_sonicvibes_put_double, \
+  .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
+
+static int snd_sonicvibes_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_sonicvibes_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	
+	spin_lock_irq(&sonic->reg_lock);
+	ucontrol->value.integer.value[0] = (snd_sonicvibes_in1(sonic, left_reg) >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (snd_sonicvibes_in1(sonic, right_reg) >> shift_right) & mask;
+	spin_unlock_irq(&sonic->reg_lock);
+	if (invert) {
+		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
+	}
+	return 0;
+}
+
+static int snd_sonicvibes_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2, oval1, oval2;
+	
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irq(&sonic->reg_lock);
+	oval1 = snd_sonicvibes_in1(sonic, left_reg);
+	oval2 = snd_sonicvibes_in1(sonic, right_reg);
+	val1 = (oval1 & ~(mask << shift_left)) | val1;
+	val2 = (oval2 & ~(mask << shift_right)) | val2;
+	change = val1 != oval1 || val2 != oval2;
+	snd_sonicvibes_out1(sonic, left_reg, val1);
+	snd_sonicvibes_out1(sonic, right_reg, val2);
+	spin_unlock_irq(&sonic->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_sonicvibes_controls[] __devinitdata = {
+SONICVIBES_DOUBLE("Capture Volume", 0, SV_IREG_LEFT_ADC, SV_IREG_RIGHT_ADC, 0, 0, 15, 0),
+SONICVIBES_DOUBLE("Aux Playback Switch", 0, SV_IREG_LEFT_AUX1, SV_IREG_RIGHT_AUX1, 7, 7, 1, 1),
+SONICVIBES_DOUBLE("Aux Playback Volume", 0, SV_IREG_LEFT_AUX1, SV_IREG_RIGHT_AUX1, 0, 0, 31, 1),
+SONICVIBES_DOUBLE("CD Playback Switch", 0, SV_IREG_LEFT_CD, SV_IREG_RIGHT_CD, 7, 7, 1, 1),
+SONICVIBES_DOUBLE("CD Playback Volume", 0, SV_IREG_LEFT_CD, SV_IREG_RIGHT_CD, 0, 0, 31, 1),
+SONICVIBES_DOUBLE("Line Playback Switch", 0, SV_IREG_LEFT_LINE, SV_IREG_RIGHT_LINE, 7, 7, 1, 1),
+SONICVIBES_DOUBLE("Line Playback Volume", 0, SV_IREG_LEFT_LINE, SV_IREG_RIGHT_LINE, 0, 0, 31, 1),
+SONICVIBES_SINGLE("Mic Playback Switch", 0, SV_IREG_MIC, 7, 1, 1),
+SONICVIBES_SINGLE("Mic Playback Volume", 0, SV_IREG_MIC, 0, 15, 1),
+SONICVIBES_SINGLE("Mic Boost", 0, SV_IREG_LEFT_ADC, 4, 1, 0),
+SONICVIBES_DOUBLE("Synth Playback Switch", 0, SV_IREG_LEFT_SYNTH, SV_IREG_RIGHT_SYNTH, 7, 7, 1, 1),
+SONICVIBES_DOUBLE("Synth Playback Volume", 0, SV_IREG_LEFT_SYNTH, SV_IREG_RIGHT_SYNTH, 0, 0, 31, 1),
+SONICVIBES_DOUBLE("Aux Playback Switch", 1, SV_IREG_LEFT_AUX2, SV_IREG_RIGHT_AUX2, 7, 7, 1, 1),
+SONICVIBES_DOUBLE("Aux Playback Volume", 1, SV_IREG_LEFT_AUX2, SV_IREG_RIGHT_AUX2, 0, 0, 31, 1),
+SONICVIBES_DOUBLE("Master Playback Switch", 0, SV_IREG_LEFT_ANALOG, SV_IREG_RIGHT_ANALOG, 7, 7, 1, 1),
+SONICVIBES_DOUBLE("Master Playback Volume", 0, SV_IREG_LEFT_ANALOG, SV_IREG_RIGHT_ANALOG, 0, 0, 31, 1),
+SONICVIBES_DOUBLE("PCM Playback Switch", 0, SV_IREG_LEFT_PCM, SV_IREG_RIGHT_PCM, 7, 7, 1, 1),
+SONICVIBES_DOUBLE("PCM Playback Volume", 0, SV_IREG_LEFT_PCM, SV_IREG_RIGHT_PCM, 0, 0, 63, 1),
+SONICVIBES_SINGLE("Loopback Capture Switch", 0, SV_IREG_ADC_OUTPUT_CTRL, 0, 1, 0),
+SONICVIBES_SINGLE("Loopback Capture Volume", 0, SV_IREG_ADC_OUTPUT_CTRL, 2, 63, 1),
+SONICVIBES_MUX("Capture Source", 0)
+};
+
+static void snd_sonicvibes_master_free(struct snd_kcontrol *kcontrol)
+{
+	struct sonicvibes *sonic = snd_kcontrol_chip(kcontrol);
+	sonic->master_mute = NULL;
+	sonic->master_volume = NULL;
+}
+
+static int __devinit snd_sonicvibes_mixer(struct sonicvibes * sonic)
+{
+	struct snd_card *card;
+	struct snd_kcontrol *kctl;
+	unsigned int idx;
+	int err;
+
+	if (snd_BUG_ON(!sonic || !sonic->card))
+		return -EINVAL;
+	card = sonic->card;
+	strcpy(card->mixername, "S3 SonicVibes");
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_sonicvibes_controls); idx++) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_sonicvibes_controls[idx], sonic))) < 0)
+			return err;
+		switch (idx) {
+		case 0:
+		case 1: kctl->private_free = snd_sonicvibes_master_free; break;
+		}
+	}
+	return 0;
+}
+
+/*
+
+ */
+
+static void snd_sonicvibes_proc_read(struct snd_info_entry *entry, 
+				     struct snd_info_buffer *buffer)
+{
+	struct sonicvibes *sonic = entry->private_data;
+	unsigned char tmp;
+
+	tmp = sonic->srs_space & 0x0f;
+	snd_iprintf(buffer, "SRS 3D           : %s\n",
+		    sonic->srs_space & 0x80 ? "off" : "on");
+	snd_iprintf(buffer, "SRS Space        : %s\n",
+		    tmp == 0x00 ? "100%" :
+		    tmp == 0x01 ? "75%" :
+		    tmp == 0x02 ? "50%" :
+		    tmp == 0x03 ? "25%" : "0%");
+	tmp = sonic->srs_center & 0x0f;
+	snd_iprintf(buffer, "SRS Center       : %s\n",
+		    tmp == 0x00 ? "100%" :
+		    tmp == 0x01 ? "75%" :
+		    tmp == 0x02 ? "50%" :
+		    tmp == 0x03 ? "25%" : "0%");
+	tmp = sonic->wave_source & 0x03;
+	snd_iprintf(buffer, "WaveTable Source : %s\n",
+		    tmp == 0x00 ? "on-board ROM" :
+		    tmp == 0x01 ? "PCI bus" : "on-board ROM + PCI bus");
+	tmp = sonic->mpu_switch;
+	snd_iprintf(buffer, "Onboard synth    : %s\n", tmp & 0x01 ? "on" : "off");
+	snd_iprintf(buffer, "Ext. Rx to synth : %s\n", tmp & 0x02 ? "on" : "off");
+	snd_iprintf(buffer, "MIDI to ext. Tx  : %s\n", tmp & 0x04 ? "on" : "off");
+}
+
+static void __devinit snd_sonicvibes_proc_init(struct sonicvibes * sonic)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(sonic->card, "sonicvibes", &entry))
+		snd_info_set_text_ops(entry, sonic, snd_sonicvibes_proc_read);
+}
+
+/*
+
+ */
+
+#ifdef SUPPORT_JOYSTICK
+static struct snd_kcontrol_new snd_sonicvibes_game_control __devinitdata =
+SONICVIBES_SINGLE("Joystick Speed", 0, SV_IREG_GAME_PORT, 1, 15, 0);
+
+static int __devinit snd_sonicvibes_create_gameport(struct sonicvibes *sonic)
+{
+	struct gameport *gp;
+
+	sonic->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "sonicvibes: cannot allocate memory for gameport\n");
+		return -ENOMEM;
+	}
+
+	gameport_set_name(gp, "SonicVibes Gameport");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(sonic->pci));
+	gameport_set_dev_parent(gp, &sonic->pci->dev);
+	gp->io = sonic->game_port;
+
+	gameport_register_port(gp);
+
+	snd_ctl_add(sonic->card, snd_ctl_new1(&snd_sonicvibes_game_control, sonic));
+
+	return 0;
+}
+
+static void snd_sonicvibes_free_gameport(struct sonicvibes *sonic)
+{
+	if (sonic->gameport) {
+		gameport_unregister_port(sonic->gameport);
+		sonic->gameport = NULL;
+	}
+}
+#else
+static inline int snd_sonicvibes_create_gameport(struct sonicvibes *sonic) { return -ENOSYS; }
+static inline void snd_sonicvibes_free_gameport(struct sonicvibes *sonic) { }
+#endif
+
+static int snd_sonicvibes_free(struct sonicvibes *sonic)
+{
+	snd_sonicvibes_free_gameport(sonic);
+	pci_write_config_dword(sonic->pci, 0x40, sonic->dmaa_port);
+	pci_write_config_dword(sonic->pci, 0x48, sonic->dmac_port);
+	if (sonic->irq >= 0)
+		free_irq(sonic->irq, sonic);
+	release_and_free_resource(sonic->res_dmaa);
+	release_and_free_resource(sonic->res_dmac);
+	pci_release_regions(sonic->pci);
+	pci_disable_device(sonic->pci);
+	kfree(sonic);
+	return 0;
+}
+
+static int snd_sonicvibes_dev_free(struct snd_device *device)
+{
+	struct sonicvibes *sonic = device->device_data;
+	return snd_sonicvibes_free(sonic);
+}
+
+static int __devinit snd_sonicvibes_create(struct snd_card *card,
+					struct pci_dev *pci,
+					int reverb,
+					int mge,
+					struct sonicvibes ** rsonic)
+{
+	struct sonicvibes *sonic;
+	unsigned int dmaa, dmac;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_sonicvibes_dev_free,
+	};
+
+	*rsonic = NULL;
+	/* enable PCI device */
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	/* check, if we can restrict PCI DMA transfers to 24 bits */
+        if (pci_set_dma_mask(pci, DMA_BIT_MASK(24)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(24)) < 0) {
+		snd_printk(KERN_ERR "architecture does not support 24bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+                return -ENXIO;
+        }
+
+	sonic = kzalloc(sizeof(*sonic), GFP_KERNEL);
+	if (sonic == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	spin_lock_init(&sonic->reg_lock);
+	sonic->card = card;
+	sonic->pci = pci;
+	sonic->irq = -1;
+
+	if ((err = pci_request_regions(pci, "S3 SonicVibes")) < 0) {
+		kfree(sonic);
+		pci_disable_device(pci);
+		return err;
+	}
+
+	sonic->sb_port = pci_resource_start(pci, 0);
+	sonic->enh_port = pci_resource_start(pci, 1);
+	sonic->synth_port = pci_resource_start(pci, 2);
+	sonic->midi_port = pci_resource_start(pci, 3);
+	sonic->game_port = pci_resource_start(pci, 4);
+
+	if (request_irq(pci->irq, snd_sonicvibes_interrupt, IRQF_SHARED,
+			"S3 SonicVibes", sonic)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_sonicvibes_free(sonic);
+		return -EBUSY;
+	}
+	sonic->irq = pci->irq;
+
+	pci_read_config_dword(pci, 0x40, &dmaa);
+	pci_read_config_dword(pci, 0x48, &dmac);
+	dmaio &= ~0x0f;
+	dmaa &= ~0x0f;
+	dmac &= ~0x0f;
+	if (!dmaa) {
+		dmaa = dmaio;
+		dmaio += 0x10;
+		snd_printk(KERN_INFO "BIOS did not allocate DDMA channel A i/o, allocated at 0x%x\n", dmaa);
+	}
+	if (!dmac) {
+		dmac = dmaio;
+		dmaio += 0x10;
+		snd_printk(KERN_INFO "BIOS did not allocate DDMA channel C i/o, allocated at 0x%x\n", dmac);
+	}
+	pci_write_config_dword(pci, 0x40, dmaa);
+	pci_write_config_dword(pci, 0x48, dmac);
+
+	if ((sonic->res_dmaa = request_region(dmaa, 0x10, "S3 SonicVibes DDMA-A")) == NULL) {
+		snd_sonicvibes_free(sonic);
+		snd_printk(KERN_ERR "unable to grab DDMA-A port at 0x%x-0x%x\n", dmaa, dmaa + 0x10 - 1);
+		return -EBUSY;
+	}
+	if ((sonic->res_dmac = request_region(dmac, 0x10, "S3 SonicVibes DDMA-C")) == NULL) {
+		snd_sonicvibes_free(sonic);
+		snd_printk(KERN_ERR "unable to grab DDMA-C port at 0x%x-0x%x\n", dmac, dmac + 0x10 - 1);
+		return -EBUSY;
+	}
+
+	pci_read_config_dword(pci, 0x40, &sonic->dmaa_port);
+	pci_read_config_dword(pci, 0x48, &sonic->dmac_port);
+	sonic->dmaa_port &= ~0x0f;
+	sonic->dmac_port &= ~0x0f;
+	pci_write_config_dword(pci, 0x40, sonic->dmaa_port | 9);	/* enable + enhanced */
+	pci_write_config_dword(pci, 0x48, sonic->dmac_port | 9);	/* enable */
+	/* ok.. initialize S3 SonicVibes chip */
+	outb(SV_RESET, SV_REG(sonic, CONTROL));		/* reset chip */
+	udelay(100);
+	outb(0, SV_REG(sonic, CONTROL));	/* release reset */
+	udelay(100);
+	outb(SV_ENHANCED | SV_INTA | (reverb ? SV_REVERB : 0), SV_REG(sonic, CONTROL));
+	inb(SV_REG(sonic, STATUS));	/* clear IRQs */
+#if 1
+	snd_sonicvibes_out(sonic, SV_IREG_DRIVE_CTRL, 0);	/* drive current 16mA */
+#else
+	snd_sonicvibes_out(sonic, SV_IREG_DRIVE_CTRL, 0x40);	/* drive current 8mA */
+#endif
+	snd_sonicvibes_out(sonic, SV_IREG_PC_ENABLE, sonic->enable = 0);	/* disable playback & capture */
+	outb(sonic->irqmask = ~(SV_DMAA_MASK | SV_DMAC_MASK | SV_UD_MASK), SV_REG(sonic, IRQMASK));
+	inb(SV_REG(sonic, STATUS));	/* clear IRQs */
+	snd_sonicvibes_out(sonic, SV_IREG_ADC_CLOCK, 0);	/* use PLL as clock source */
+	snd_sonicvibes_out(sonic, SV_IREG_ANALOG_POWER, 0);	/* power up analog parts */
+	snd_sonicvibes_out(sonic, SV_IREG_DIGITAL_POWER, 0);	/* power up digital parts */
+	snd_sonicvibes_setpll(sonic, SV_IREG_ADC_PLL, 8000);
+	snd_sonicvibes_out(sonic, SV_IREG_SRS_SPACE, sonic->srs_space = 0x80);	/* SRS space off */
+	snd_sonicvibes_out(sonic, SV_IREG_SRS_CENTER, sonic->srs_center = 0x00);/* SRS center off */
+	snd_sonicvibes_out(sonic, SV_IREG_MPU401, sonic->mpu_switch = 0x05);	/* MPU-401 switch */
+	snd_sonicvibes_out(sonic, SV_IREG_WAVE_SOURCE, sonic->wave_source = 0x00);	/* onboard ROM */
+	snd_sonicvibes_out(sonic, SV_IREG_PCM_RATE_LOW, (8000 * 65536 / SV_FULLRATE) & 0xff);
+	snd_sonicvibes_out(sonic, SV_IREG_PCM_RATE_HIGH, ((8000 * 65536 / SV_FULLRATE) >> 8) & 0xff);
+	snd_sonicvibes_out(sonic, SV_IREG_LEFT_ADC, mge ? 0xd0 : 0xc0);
+	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_ADC, 0xc0);
+	snd_sonicvibes_out(sonic, SV_IREG_LEFT_AUX1, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_AUX1, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_LEFT_CD, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_CD, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_LEFT_LINE, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_LINE, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_MIC, 0x8f);
+	snd_sonicvibes_out(sonic, SV_IREG_LEFT_SYNTH, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_SYNTH, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_LEFT_AUX2, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_AUX2, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_LEFT_ANALOG, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_ANALOG, 0x9f);
+	snd_sonicvibes_out(sonic, SV_IREG_LEFT_PCM, 0xbf);
+	snd_sonicvibes_out(sonic, SV_IREG_RIGHT_PCM, 0xbf);
+	snd_sonicvibes_out(sonic, SV_IREG_ADC_OUTPUT_CTRL, 0xfc);
+#if 0
+	snd_sonicvibes_debug(sonic);
+#endif
+	sonic->revision = snd_sonicvibes_in(sonic, SV_IREG_REVISION);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, sonic, &ops)) < 0) {
+		snd_sonicvibes_free(sonic);
+		return err;
+	}
+
+	snd_sonicvibes_proc_init(sonic);
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rsonic = sonic;
+	return 0;
+}
+
+/*
+ *  MIDI section
+ */
+
+static struct snd_kcontrol_new snd_sonicvibes_midi_controls[] __devinitdata = {
+SONICVIBES_SINGLE("SonicVibes Wave Source RAM", 0, SV_IREG_WAVE_SOURCE, 0, 1, 0),
+SONICVIBES_SINGLE("SonicVibes Wave Source RAM+ROM", 0, SV_IREG_WAVE_SOURCE, 1, 1, 0),
+SONICVIBES_SINGLE("SonicVibes Onboard Synth", 0, SV_IREG_MPU401, 0, 1, 0),
+SONICVIBES_SINGLE("SonicVibes External Rx to Synth", 0, SV_IREG_MPU401, 1, 1, 0),
+SONICVIBES_SINGLE("SonicVibes External Tx", 0, SV_IREG_MPU401, 2, 1, 0)
+};
+
+static int snd_sonicvibes_midi_input_open(struct snd_mpu401 * mpu)
+{
+	struct sonicvibes *sonic = mpu->private_data;
+	outb(sonic->irqmask &= ~SV_MIDI_MASK, SV_REG(sonic, IRQMASK));
+	return 0;
+}
+
+static void snd_sonicvibes_midi_input_close(struct snd_mpu401 * mpu)
+{
+	struct sonicvibes *sonic = mpu->private_data;
+	outb(sonic->irqmask |= SV_MIDI_MASK, SV_REG(sonic, IRQMASK));
+}
+
+static int __devinit snd_sonicvibes_midi(struct sonicvibes * sonic,
+					 struct snd_rawmidi *rmidi)
+{
+	struct snd_mpu401 * mpu = rmidi->private_data;
+	struct snd_card *card = sonic->card;
+	struct snd_rawmidi_str *dir;
+	unsigned int idx;
+	int err;
+
+	mpu->private_data = sonic;
+	mpu->open_input = snd_sonicvibes_midi_input_open;
+	mpu->close_input = snd_sonicvibes_midi_input_close;
+	dir = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+	for (idx = 0; idx < ARRAY_SIZE(snd_sonicvibes_midi_controls); idx++)
+		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_sonicvibes_midi_controls[idx], sonic))) < 0)
+			return err;
+	return 0;
+}
+
+static int __devinit snd_sonic_probe(struct pci_dev *pci,
+				     const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct sonicvibes *sonic;
+	struct snd_rawmidi *midi_uart;
+	struct snd_opl3 *opl3;
+	int idx, err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+ 
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+	for (idx = 0; idx < 5; idx++) {
+		if (pci_resource_start(pci, idx) == 0 ||
+		    !(pci_resource_flags(pci, idx) & IORESOURCE_IO)) {
+			snd_card_free(card);
+			return -ENODEV;
+		}
+	}
+	if ((err = snd_sonicvibes_create(card, pci,
+					 reverb[dev] ? 1 : 0,
+					 mge[dev] ? 1 : 0,
+					 &sonic)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "SonicVibes");
+	strcpy(card->shortname, "S3 SonicVibes");
+	sprintf(card->longname, "%s rev %i at 0x%llx, irq %i",
+		card->shortname,
+		sonic->revision,
+		(unsigned long long)pci_resource_start(pci, 1),
+		sonic->irq);
+
+	if ((err = snd_sonicvibes_pcm(sonic, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_sonicvibes_mixer(sonic)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_SONICVIBES,
+				       sonic->midi_port, MPU401_INFO_INTEGRATED,
+				       sonic->irq, 0,
+				       &midi_uart)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	snd_sonicvibes_midi(sonic, midi_uart);
+	if ((err = snd_opl3_create(card, sonic->synth_port,
+				   sonic->synth_port + 2,
+				   OPL3_HW_OPL3_SV, 1, &opl3)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	snd_sonicvibes_create_gameport(sonic);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_sonic_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "S3 SonicVibes",
+	.id_table = snd_sonic_ids,
+	.probe = snd_sonic_probe,
+	.remove = __devexit_p(snd_sonic_remove),
+};
+
+static int __init alsa_card_sonicvibes_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_sonicvibes_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_sonicvibes_init)
+module_exit(alsa_card_sonicvibes_exit)
diff --git a/linux/sound/pci/trident/Makefile b/linux/sound/pci/trident/Makefile
new file mode 100644
index 0000000..88676b5
--- /dev/null
+++ b/linux/sound/pci/trident/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-trident-objs := trident.o trident_main.o trident_memory.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_TRIDENT) += snd-trident.o
diff --git a/linux/sound/pci/trident/trident.c b/linux/sound/pci/trident/trident.c
new file mode 100644
index 0000000..6d05818
--- /dev/null
+++ b/linux/sound/pci/trident/trident.c
@@ -0,0 +1,196 @@
+/*
+ *  Driver for Trident 4DWave DX/NX & SiS SI7018 Audio PCI soundcard
+ *
+ *  Driver was originated by Trident <audio@tridentmicro.com>
+ *  			     Fri Feb 19 15:55:28 MST 1999
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/trident.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, <audio@tridentmicro.com>");
+MODULE_DESCRIPTION("Trident 4D-WaveDX/NX & SiS SI7018");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Trident,4DWave DX},"
+		"{Trident,4DWave NX},"
+		"{SiS,SI7018 PCI Audio},"
+		"{Best Union,Miss Melody 4DWave PCI},"
+		"{HIS,4DWave PCI},"
+		"{Warpspeed,ONSpeed 4DWave PCI},"
+		"{Aztech Systems,PCI 64-Q3D},"
+		"{Addonics,SV 750},"
+		"{CHIC,True Sound 4Dwave},"
+		"{Shark,Predator4D-PCI},"
+		"{Jaton,SonicWave 4D},"
+		"{Hoontech,SoundTrack Digital 4DWave NX}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 32};
+static int wavetable_size[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8192};
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Trident 4DWave PCI soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Trident 4DWave PCI soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Trident 4DWave PCI soundcard.");
+module_param_array(pcm_channels, int, NULL, 0444);
+MODULE_PARM_DESC(pcm_channels, "Number of hardware channels assigned for PCM.");
+module_param_array(wavetable_size, int, NULL, 0444);
+MODULE_PARM_DESC(wavetable_size, "Maximum memory size in kB for wavetable synth.");
+
+static DEFINE_PCI_DEVICE_TABLE(snd_trident_ids) = {
+	{PCI_DEVICE(PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_TRIDENT_4DWAVE_DX), 
+		PCI_CLASS_MULTIMEDIA_AUDIO << 8, 0xffff00, 0},
+	{PCI_DEVICE(PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_TRIDENT_4DWAVE_NX), 
+		0, 0, 0},
+	{PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_7018), 0, 0, 0},
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_trident_ids);
+
+static int __devinit snd_trident_probe(struct pci_dev *pci,
+				       const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_trident *trident;
+	const char *str;
+	int err, pcm_dev = 0;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	if ((err = snd_trident_create(card, pci,
+				      pcm_channels[dev],
+				      ((pci->vendor << 16) | pci->device) == TRIDENT_DEVICE_ID_SI7018 ? 1 : 2,
+				      wavetable_size[dev],
+				      &trident)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = trident;
+
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:
+		str = "TRID4DWAVEDX";
+		break;
+	case TRIDENT_DEVICE_ID_NX:
+		str = "TRID4DWAVENX";
+		break;
+	case TRIDENT_DEVICE_ID_SI7018:
+		str = "SI7018";
+		break;
+	default:
+		str = "Unknown";
+	}
+	strcpy(card->driver, str);
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		strcpy(card->shortname, "SiS ");
+	} else {
+		strcpy(card->shortname, "Trident ");
+	}
+	strcat(card->shortname, card->driver);
+	sprintf(card->longname, "%s PCI Audio at 0x%lx, irq %d",
+		card->shortname, trident->port, trident->irq);
+
+	if ((err = snd_trident_pcm(trident, pcm_dev++, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:
+	case TRIDENT_DEVICE_ID_NX:
+		if ((err = snd_trident_foldback_pcm(trident, pcm_dev++, NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+		break;
+	}
+	if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		if ((err = snd_trident_spdif_pcm(trident, pcm_dev++, NULL)) < 0) {
+			snd_card_free(card);
+			return err;
+		}
+	}
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018 &&
+	    (err = snd_mpu401_uart_new(card, 0, MPU401_HW_TRID4DWAVE,
+				       trident->midi_port,
+				       MPU401_INFO_INTEGRATED,
+				       trident->irq, 0, &trident->rmidi)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	snd_trident_create_gameport(trident);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_trident_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "Trident4DWaveAudio",
+	.id_table = snd_trident_ids,
+	.probe = snd_trident_probe,
+	.remove = __devexit_p(snd_trident_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_trident_suspend,
+	.resume = snd_trident_resume,
+#endif
+};
+
+static int __init alsa_card_trident_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_trident_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_trident_init)
+module_exit(alsa_card_trident_exit)
diff --git a/linux/sound/pci/trident/trident_main.c b/linux/sound/pci/trident/trident_main.c
new file mode 100644
index 0000000..2870a4f
--- /dev/null
+++ b/linux/sound/pci/trident/trident_main.c
@@ -0,0 +1,3981 @@
+/*
+ *  Maintained by Jaroslav Kysela <perex@perex.cz>
+ *  Originated by audio@tridentmicro.com
+ *  Fri Feb 19 15:55:28 MST 1999
+ *  Routines for control of Trident 4DWave (DX and NX) chip
+ *
+ *  BUGS:
+ *
+ *  TODO:
+ *    ---
+ *
+ *   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
+ *
+ *
+ *  SiS7018 S/PDIF support by Thomas Winischhofer <thomas@winischhofer.net>
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/gameport.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/trident.h>
+#include <sound/asoundef.h>
+
+#include <asm/io.h>
+
+static int snd_trident_pcm_mixer_build(struct snd_trident *trident,
+				       struct snd_trident_voice * voice,
+				       struct snd_pcm_substream *substream);
+static int snd_trident_pcm_mixer_free(struct snd_trident *trident,
+				      struct snd_trident_voice * voice,
+				      struct snd_pcm_substream *substream);
+static irqreturn_t snd_trident_interrupt(int irq, void *dev_id);
+static int snd_trident_sis_reset(struct snd_trident *trident);
+
+static void snd_trident_clear_voices(struct snd_trident * trident,
+				     unsigned short v_min, unsigned short v_max);
+static int snd_trident_free(struct snd_trident *trident);
+
+/*
+ *  common I/O routines
+ */
+
+
+#if 0
+static void snd_trident_print_voice_regs(struct snd_trident *trident, int voice)
+{
+	unsigned int val, tmp;
+
+	printk(KERN_DEBUG "Trident voice %i:\n", voice);
+	outb(voice, TRID_REG(trident, T4D_LFO_GC_CIR));
+	val = inl(TRID_REG(trident, CH_LBA));
+	printk(KERN_DEBUG "LBA: 0x%x\n", val);
+	val = inl(TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC));
+	printk(KERN_DEBUG "GVSel: %i\n", val >> 31);
+	printk(KERN_DEBUG "Pan: 0x%x\n", (val >> 24) & 0x7f);
+	printk(KERN_DEBUG "Vol: 0x%x\n", (val >> 16) & 0xff);
+	printk(KERN_DEBUG "CTRL: 0x%x\n", (val >> 12) & 0x0f);
+	printk(KERN_DEBUG "EC: 0x%x\n", val & 0x0fff);
+	if (trident->device != TRIDENT_DEVICE_ID_NX) {
+		val = inl(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS));
+		printk(KERN_DEBUG "CSO: 0x%x\n", val >> 16);
+		printk("Alpha: 0x%x\n", (val >> 4) & 0x0fff);
+		printk(KERN_DEBUG "FMS: 0x%x\n", val & 0x0f);
+		val = inl(TRID_REG(trident, CH_DX_ESO_DELTA));
+		printk(KERN_DEBUG "ESO: 0x%x\n", val >> 16);
+		printk(KERN_DEBUG "Delta: 0x%x\n", val & 0xffff);
+		val = inl(TRID_REG(trident, CH_DX_FMC_RVOL_CVOL));
+	} else {		// TRIDENT_DEVICE_ID_NX
+		val = inl(TRID_REG(trident, CH_NX_DELTA_CSO));
+		tmp = (val >> 24) & 0xff;
+		printk(KERN_DEBUG "CSO: 0x%x\n", val & 0x00ffffff);
+		val = inl(TRID_REG(trident, CH_NX_DELTA_ESO));
+		tmp |= (val >> 16) & 0xff00;
+		printk(KERN_DEBUG "Delta: 0x%x\n", tmp);
+		printk(KERN_DEBUG "ESO: 0x%x\n", val & 0x00ffffff);
+		val = inl(TRID_REG(trident, CH_NX_ALPHA_FMS_FMC_RVOL_CVOL));
+		printk(KERN_DEBUG "Alpha: 0x%x\n", val >> 20);
+		printk(KERN_DEBUG "FMS: 0x%x\n", (val >> 16) & 0x0f);
+	}
+	printk(KERN_DEBUG "FMC: 0x%x\n", (val >> 14) & 3);
+	printk(KERN_DEBUG "RVol: 0x%x\n", (val >> 7) & 0x7f);
+	printk(KERN_DEBUG "CVol: 0x%x\n", val & 0x7f);
+}
+#endif
+
+/*---------------------------------------------------------------------------
+   unsigned short snd_trident_codec_read(struct snd_ac97 *ac97, unsigned short reg)
+  
+   Description: This routine will do all of the reading from the external
+                CODEC (AC97).
+  
+   Parameters:  ac97 - ac97 codec structure
+                reg - CODEC register index, from AC97 Hal.
+ 
+   returns:     16 bit value read from the AC97.
+  
+  ---------------------------------------------------------------------------*/
+static unsigned short snd_trident_codec_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	unsigned int data = 0, treg;
+	unsigned short count = 0xffff;
+	unsigned long flags;
+	struct snd_trident *trident = ac97->private_data;
+
+	spin_lock_irqsave(&trident->reg_lock, flags);
+	if (trident->device == TRIDENT_DEVICE_ID_DX) {
+		data = (DX_AC97_BUSY_READ | (reg & 0x000000ff));
+		outl(data, TRID_REG(trident, DX_ACR1_AC97_R));
+		do {
+			data = inl(TRID_REG(trident, DX_ACR1_AC97_R));
+			if ((data & DX_AC97_BUSY_READ) == 0)
+				break;
+		} while (--count);
+	} else if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		data = (NX_AC97_BUSY_READ | (reg & 0x000000ff));
+		treg = ac97->num == 0 ? NX_ACR2_AC97_R_PRIMARY : NX_ACR3_AC97_R_SECONDARY;
+		outl(data, TRID_REG(trident, treg));
+		do {
+			data = inl(TRID_REG(trident, treg));
+			if ((data & 0x00000C00) == 0)
+				break;
+		} while (--count);
+	} else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		data = SI_AC97_BUSY_READ | SI_AC97_AUDIO_BUSY | (reg & 0x000000ff);
+		if (ac97->num == 1)
+			data |= SI_AC97_SECONDARY;
+		outl(data, TRID_REG(trident, SI_AC97_READ));
+		do {
+			data = inl(TRID_REG(trident, SI_AC97_READ));
+			if ((data & (SI_AC97_BUSY_READ)) == 0)
+				break;
+		} while (--count);
+	}
+
+	if (count == 0 && !trident->ac97_detect) {
+		snd_printk(KERN_ERR "ac97 codec read TIMEOUT [0x%x/0x%x]!!!\n",
+			   reg, data);
+		data = 0;
+	}
+
+	spin_unlock_irqrestore(&trident->reg_lock, flags);
+	return ((unsigned short) (data >> 16));
+}
+
+/*---------------------------------------------------------------------------
+   void snd_trident_codec_write(struct snd_ac97 *ac97, unsigned short reg,
+   unsigned short wdata)
+  
+   Description: This routine will do all of the writing to the external
+                CODEC (AC97).
+  
+   Parameters:	ac97 - ac97 codec structure
+   	        reg - CODEC register index, from AC97 Hal.
+                data  - Lower 16 bits are the data to write to CODEC.
+  
+   returns:     TRUE if everything went ok, else FALSE.
+  
+  ---------------------------------------------------------------------------*/
+static void snd_trident_codec_write(struct snd_ac97 *ac97, unsigned short reg,
+				    unsigned short wdata)
+{
+	unsigned int address, data;
+	unsigned short count = 0xffff;
+	unsigned long flags;
+	struct snd_trident *trident = ac97->private_data;
+
+	data = ((unsigned long) wdata) << 16;
+
+	spin_lock_irqsave(&trident->reg_lock, flags);
+	if (trident->device == TRIDENT_DEVICE_ID_DX) {
+		address = DX_ACR0_AC97_W;
+
+		/* read AC-97 write register status */
+		do {
+			if ((inw(TRID_REG(trident, address)) & DX_AC97_BUSY_WRITE) == 0)
+				break;
+		} while (--count);
+
+		data |= (DX_AC97_BUSY_WRITE | (reg & 0x000000ff));
+	} else if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		address = NX_ACR1_AC97_W;
+
+		/* read AC-97 write register status */
+		do {
+			if ((inw(TRID_REG(trident, address)) & NX_AC97_BUSY_WRITE) == 0)
+				break;
+		} while (--count);
+
+		data |= (NX_AC97_BUSY_WRITE | (ac97->num << 8) | (reg & 0x000000ff));
+	} else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		address = SI_AC97_WRITE;
+
+		/* read AC-97 write register status */
+		do {
+			if ((inw(TRID_REG(trident, address)) & (SI_AC97_BUSY_WRITE)) == 0)
+				break;
+		} while (--count);
+
+		data |= SI_AC97_BUSY_WRITE | SI_AC97_AUDIO_BUSY | (reg & 0x000000ff);
+		if (ac97->num == 1)
+			data |= SI_AC97_SECONDARY;
+	} else {
+		address = 0;	/* keep GCC happy */
+		count = 0;	/* return */
+	}
+
+	if (count == 0) {
+		spin_unlock_irqrestore(&trident->reg_lock, flags);
+		return;
+	}
+	outl(data, TRID_REG(trident, address));
+	spin_unlock_irqrestore(&trident->reg_lock, flags);
+}
+
+/*---------------------------------------------------------------------------
+   void snd_trident_enable_eso(struct snd_trident *trident)
+  
+   Description: This routine will enable end of loop interrupts.
+                End of loop interrupts will occur when a running
+                channel reaches ESO.
+                Also enables middle of loop interrupts.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_enable_eso(struct snd_trident * trident)
+{
+	unsigned int val;
+
+	val = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
+	val |= ENDLP_IE;
+	val |= MIDLP_IE;
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018)
+		val |= BANK_B_EN;
+	outl(val, TRID_REG(trident, T4D_LFO_GC_CIR));
+}
+
+/*---------------------------------------------------------------------------
+   void snd_trident_disable_eso(struct snd_trident *trident)
+  
+   Description: This routine will disable end of loop interrupts.
+                End of loop interrupts will occur when a running
+                channel reaches ESO.
+                Also disables middle of loop interrupts.
+  
+   Parameters:  
+                trident - pointer to target device class for 4DWave.
+  
+   returns:     TRUE if everything went ok, else FALSE.
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_disable_eso(struct snd_trident * trident)
+{
+	unsigned int tmp;
+
+	tmp = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
+	tmp &= ~ENDLP_IE;
+	tmp &= ~MIDLP_IE;
+	outl(tmp, TRID_REG(trident, T4D_LFO_GC_CIR));
+}
+
+/*---------------------------------------------------------------------------
+   void snd_trident_start_voice(struct snd_trident * trident, unsigned int voice)
+
+    Description: Start a voice, any channel 0 thru 63.
+                 This routine automatically handles the fact that there are
+                 more than 32 channels available.
+
+    Parameters : voice - Voice number 0 thru n.
+                 trident - pointer to target device class for 4DWave.
+
+    Return Value: None.
+
+  ---------------------------------------------------------------------------*/
+
+void snd_trident_start_voice(struct snd_trident * trident, unsigned int voice)
+{
+	unsigned int mask = 1 << (voice & 0x1f);
+	unsigned int reg = (voice & 0x20) ? T4D_START_B : T4D_START_A;
+
+	outl(mask, TRID_REG(trident, reg));
+}
+
+EXPORT_SYMBOL(snd_trident_start_voice);
+
+/*---------------------------------------------------------------------------
+   void snd_trident_stop_voice(struct snd_trident * trident, unsigned int voice)
+
+    Description: Stop a voice, any channel 0 thru 63.
+                 This routine automatically handles the fact that there are
+                 more than 32 channels available.
+
+    Parameters : voice - Voice number 0 thru n.
+                 trident - pointer to target device class for 4DWave.
+
+    Return Value: None.
+
+  ---------------------------------------------------------------------------*/
+
+void snd_trident_stop_voice(struct snd_trident * trident, unsigned int voice)
+{
+	unsigned int mask = 1 << (voice & 0x1f);
+	unsigned int reg = (voice & 0x20) ? T4D_STOP_B : T4D_STOP_A;
+
+	outl(mask, TRID_REG(trident, reg));
+}
+
+EXPORT_SYMBOL(snd_trident_stop_voice);
+
+/*---------------------------------------------------------------------------
+    int snd_trident_allocate_pcm_channel(struct snd_trident *trident)
+  
+    Description: Allocate hardware channel in Bank B (32-63).
+  
+    Parameters :  trident - pointer to target device class for 4DWave.
+  
+    Return Value: hardware channel - 32-63 or -1 when no channel is available
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_pcm_channel(struct snd_trident * trident)
+{
+	int idx;
+
+	if (trident->ChanPCMcnt >= trident->ChanPCM)
+		return -1;
+	for (idx = 31; idx >= 0; idx--) {
+		if (!(trident->ChanMap[T4D_BANK_B] & (1 << idx))) {
+			trident->ChanMap[T4D_BANK_B] |= 1 << idx;
+			trident->ChanPCMcnt++;
+			return idx + 32;
+		}
+	}
+	return -1;
+}
+
+/*---------------------------------------------------------------------------
+    void snd_trident_free_pcm_channel(int channel)
+  
+    Description: Free hardware channel in Bank B (32-63)
+  
+    Parameters :  trident - pointer to target device class for 4DWave.
+	          channel - hardware channel number 0-63
+  
+    Return Value: none
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_free_pcm_channel(struct snd_trident *trident, int channel)
+{
+	if (channel < 32 || channel > 63)
+		return;
+	channel &= 0x1f;
+	if (trident->ChanMap[T4D_BANK_B] & (1 << channel)) {
+		trident->ChanMap[T4D_BANK_B] &= ~(1 << channel);
+		trident->ChanPCMcnt--;
+	}
+}
+
+/*---------------------------------------------------------------------------
+    unsigned int snd_trident_allocate_synth_channel(void)
+  
+    Description: Allocate hardware channel in Bank A (0-31).
+  
+    Parameters :  trident - pointer to target device class for 4DWave.
+  
+    Return Value: hardware channel - 0-31 or -1 when no channel is available
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_synth_channel(struct snd_trident * trident)
+{
+	int idx;
+
+	for (idx = 31; idx >= 0; idx--) {
+		if (!(trident->ChanMap[T4D_BANK_A] & (1 << idx))) {
+			trident->ChanMap[T4D_BANK_A] |= 1 << idx;
+			trident->synth.ChanSynthCount++;
+			return idx;
+		}
+	}
+	return -1;
+}
+
+/*---------------------------------------------------------------------------
+    void snd_trident_free_synth_channel( int channel )
+  
+    Description: Free hardware channel in Bank B (0-31).
+  
+    Parameters :  trident - pointer to target device class for 4DWave.
+	          channel - hardware channel number 0-63
+  
+    Return Value: none
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_free_synth_channel(struct snd_trident *trident, int channel)
+{
+	if (channel < 0 || channel > 31)
+		return;
+	channel &= 0x1f;
+	if (trident->ChanMap[T4D_BANK_A] & (1 << channel)) {
+		trident->ChanMap[T4D_BANK_A] &= ~(1 << channel);
+		trident->synth.ChanSynthCount--;
+	}
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_voice_regs
+  
+   Description: This routine will complete and write the 5 hardware channel
+                registers to hardware.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                Each register field.
+  
+  ---------------------------------------------------------------------------*/
+
+void snd_trident_write_voice_regs(struct snd_trident * trident,
+				  struct snd_trident_voice * voice)
+{
+	unsigned int FmcRvolCvol;
+	unsigned int regs[5];
+
+	regs[1] = voice->LBA;
+	regs[4] = (voice->GVSel << 31) |
+		  ((voice->Pan & 0x0000007f) << 24) |
+		  ((voice->CTRL & 0x0000000f) << 12);
+	FmcRvolCvol = ((voice->FMC & 3) << 14) |
+	              ((voice->RVol & 0x7f) << 7) |
+	              (voice->CVol & 0x7f);
+
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_SI7018:
+		regs[4] |= voice->number > 31 ?
+				(voice->Vol & 0x000003ff) :
+				((voice->Vol & 0x00003fc) << (16-2)) |
+				(voice->EC & 0x00000fff);
+		regs[0] = (voice->CSO << 16) | ((voice->Alpha & 0x00000fff) << 4) |
+			(voice->FMS & 0x0000000f);
+		regs[2] = (voice->ESO << 16) | (voice->Delta & 0x0ffff);
+		regs[3] = (voice->Attribute << 16) | FmcRvolCvol;
+		break;
+	case TRIDENT_DEVICE_ID_DX:
+		regs[4] |= ((voice->Vol & 0x000003fc) << (16-2)) |
+			   (voice->EC & 0x00000fff);
+		regs[0] = (voice->CSO << 16) | ((voice->Alpha & 0x00000fff) << 4) |
+			(voice->FMS & 0x0000000f);
+		regs[2] = (voice->ESO << 16) | (voice->Delta & 0x0ffff);
+		regs[3] = FmcRvolCvol;
+		break;
+	case TRIDENT_DEVICE_ID_NX:
+		regs[4] |= ((voice->Vol & 0x000003fc) << (16-2)) |
+			   (voice->EC & 0x00000fff);
+		regs[0] = (voice->Delta << 24) | (voice->CSO & 0x00ffffff);
+		regs[2] = ((voice->Delta << 16) & 0xff000000) |
+			(voice->ESO & 0x00ffffff);
+		regs[3] = (voice->Alpha << 20) |
+			((voice->FMS & 0x0000000f) << 16) | FmcRvolCvol;
+		break;
+	default:
+		snd_BUG();
+		return;
+	}
+
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	outl(regs[0], TRID_REG(trident, CH_START + 0));
+	outl(regs[1], TRID_REG(trident, CH_START + 4));
+	outl(regs[2], TRID_REG(trident, CH_START + 8));
+	outl(regs[3], TRID_REG(trident, CH_START + 12));
+	outl(regs[4], TRID_REG(trident, CH_START + 16));
+
+#if 0
+	printk(KERN_DEBUG "written %i channel:\n", voice->number);
+	printk(KERN_DEBUG "  regs[0] = 0x%x/0x%x\n",
+	       regs[0], inl(TRID_REG(trident, CH_START + 0)));
+	printk(KERN_DEBUG "  regs[1] = 0x%x/0x%x\n",
+	       regs[1], inl(TRID_REG(trident, CH_START + 4)));
+	printk(KERN_DEBUG "  regs[2] = 0x%x/0x%x\n",
+	       regs[2], inl(TRID_REG(trident, CH_START + 8)));
+	printk(KERN_DEBUG "  regs[3] = 0x%x/0x%x\n",
+	       regs[3], inl(TRID_REG(trident, CH_START + 12)));
+	printk(KERN_DEBUG "  regs[4] = 0x%x/0x%x\n",
+	       regs[4], inl(TRID_REG(trident, CH_START + 16)));
+#endif
+}
+
+EXPORT_SYMBOL(snd_trident_write_voice_regs);
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_cso_reg
+  
+   Description: This routine will write the new CSO offset
+                register to hardware.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                CSO - new CSO value
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_cso_reg(struct snd_trident * trident,
+				      struct snd_trident_voice * voice,
+				      unsigned int CSO)
+{
+	voice->CSO = CSO;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	if (trident->device != TRIDENT_DEVICE_ID_NX) {
+		outw(voice->CSO, TRID_REG(trident, CH_DX_CSO_ALPHA_FMS) + 2);
+	} else {
+		outl((voice->Delta << 24) |
+		     (voice->CSO & 0x00ffffff), TRID_REG(trident, CH_NX_DELTA_CSO));
+	}
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_eso_reg
+  
+   Description: This routine will write the new ESO offset
+                register to hardware.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                ESO - new ESO value
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_eso_reg(struct snd_trident * trident,
+				      struct snd_trident_voice * voice,
+				      unsigned int ESO)
+{
+	voice->ESO = ESO;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	if (trident->device != TRIDENT_DEVICE_ID_NX) {
+		outw(voice->ESO, TRID_REG(trident, CH_DX_ESO_DELTA) + 2);
+	} else {
+		outl(((voice->Delta << 16) & 0xff000000) | (voice->ESO & 0x00ffffff),
+		     TRID_REG(trident, CH_NX_DELTA_ESO));
+	}
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_vol_reg
+  
+   Description: This routine will write the new voice volume
+                register to hardware.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                Vol - new voice volume
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_vol_reg(struct snd_trident * trident,
+				      struct snd_trident_voice * voice,
+				      unsigned int Vol)
+{
+	voice->Vol = Vol;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:
+	case TRIDENT_DEVICE_ID_NX:
+		outb(voice->Vol >> 2, TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 2));
+		break;
+	case TRIDENT_DEVICE_ID_SI7018:
+		/* printk(KERN_DEBUG "voice->Vol = 0x%x\n", voice->Vol); */
+		outw((voice->CTRL << 12) | voice->Vol,
+		     TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC));
+		break;
+	}
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_pan_reg
+  
+   Description: This routine will write the new voice pan
+                register to hardware.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                Pan - new pan value
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_pan_reg(struct snd_trident * trident,
+				      struct snd_trident_voice * voice,
+				      unsigned int Pan)
+{
+	voice->Pan = Pan;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	outb(((voice->GVSel & 0x01) << 7) | (voice->Pan & 0x7f),
+	     TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC + 3));
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_rvol_reg
+  
+   Description: This routine will write the new reverb volume
+                register to hardware.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                RVol - new reverb volume
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_rvol_reg(struct snd_trident * trident,
+				       struct snd_trident_voice * voice,
+				       unsigned int RVol)
+{
+	voice->RVol = RVol;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	outw(((voice->FMC & 0x0003) << 14) | ((voice->RVol & 0x007f) << 7) |
+	     (voice->CVol & 0x007f),
+	     TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ?
+		      CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_write_cvol_reg
+  
+   Description: This routine will write the new chorus volume
+                register to hardware.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                voice - synthesizer voice structure
+                CVol - new chorus volume
+  
+  ---------------------------------------------------------------------------*/
+
+static void snd_trident_write_cvol_reg(struct snd_trident * trident,
+				       struct snd_trident_voice * voice,
+				       unsigned int CVol)
+{
+	voice->CVol = CVol;
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+	outw(((voice->FMC & 0x0003) << 14) | ((voice->RVol & 0x007f) << 7) |
+	     (voice->CVol & 0x007f),
+	     TRID_REG(trident, trident->device == TRIDENT_DEVICE_ID_NX ?
+		      CH_NX_ALPHA_FMS_FMC_RVOL_CVOL : CH_DX_FMC_RVOL_CVOL));
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_convert_rate
+
+   Description: This routine converts rate in HZ to hardware delta value.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                rate - Real or Virtual channel number.
+  
+   Returns:     Delta value.
+  
+  ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_convert_rate(unsigned int rate)
+{
+	unsigned int delta;
+
+	// We special case 44100 and 8000 since rounding with the equation
+	// does not give us an accurate enough value. For 11025 and 22050
+	// the equation gives us the best answer. All other frequencies will
+	// also use the equation. JDW
+	if (rate == 44100)
+		delta = 0xeb3;
+	else if (rate == 8000)
+		delta = 0x2ab;
+	else if (rate == 48000)
+		delta = 0x1000;
+	else
+		delta = (((rate << 12) + 24000) / 48000) & 0x0000ffff;
+	return delta;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_convert_adc_rate
+
+   Description: This routine converts rate in HZ to hardware delta value.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                rate - Real or Virtual channel number.
+  
+   Returns:     Delta value.
+  
+  ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_convert_adc_rate(unsigned int rate)
+{
+	unsigned int delta;
+
+	// We special case 44100 and 8000 since rounding with the equation
+	// does not give us an accurate enough value. For 11025 and 22050
+	// the equation gives us the best answer. All other frequencies will
+	// also use the equation. JDW
+	if (rate == 44100)
+		delta = 0x116a;
+	else if (rate == 8000)
+		delta = 0x6000;
+	else if (rate == 48000)
+		delta = 0x1000;
+	else
+		delta = ((48000 << 12) / rate) & 0x0000ffff;
+	return delta;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spurious_threshold
+
+   Description: This routine converts rate in HZ to spurious threshold.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                rate - Real or Virtual channel number.
+  
+   Returns:     Delta value.
+  
+  ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_spurious_threshold(unsigned int rate,
+						   unsigned int period_size)
+{
+	unsigned int res = (rate * period_size) / 48000;
+	if (res < 64)
+		res = res / 2;
+	else
+		res -= 32;
+	return res;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_control_mode
+
+   Description: This routine returns a control mode for a PCM channel.
+  
+   Parameters:  trident - pointer to target device class for 4DWave.
+                substream  - PCM substream
+  
+   Returns:     Control value.
+  
+  ---------------------------------------------------------------------------*/
+static unsigned int snd_trident_control_mode(struct snd_pcm_substream *substream)
+{
+	unsigned int CTRL;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	/* set ctrl mode
+	   CTRL default: 8-bit (unsigned) mono, loop mode enabled
+	 */
+	CTRL = 0x00000001;
+	if (snd_pcm_format_width(runtime->format) == 16)
+		CTRL |= 0x00000008;	// 16-bit data
+	if (snd_pcm_format_signed(runtime->format))
+		CTRL |= 0x00000002;	// signed data
+	if (runtime->channels > 1)
+		CTRL |= 0x00000004;	// stereo data
+	return CTRL;
+}
+
+/*
+ *  PCM part
+ */
+
+/*---------------------------------------------------------------------------
+   snd_trident_ioctl
+  
+   Description: Device I/O control handler for playback/capture parameters.
+  
+   Parameters:   substream  - PCM substream class
+                cmd     - what ioctl message to process
+                arg     - additional message infoarg     
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_ioctl(struct snd_pcm_substream *substream,
+			     unsigned int cmd,
+			     void *arg)
+{
+	/* FIXME: it seems that with small periods the behaviour of
+	          trident hardware is unpredictable and interrupt generator
+	          is broken */
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_allocate_pcm_mem
+  
+   Description: Allocate PCM ring buffer for given substream
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_pcm_mem(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	if (trident->tlb.entries) {
+		if (err > 0) { /* change */
+			if (voice->memblk)
+				snd_trident_free_pages(trident, voice->memblk);
+			voice->memblk = snd_trident_alloc_pages(trident, substream);
+			if (voice->memblk == NULL)
+				return -ENOMEM;
+		}
+	}
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_allocate_evoice
+  
+   Description: Allocate extra voice as interrupt generator
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_allocate_evoice(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	struct snd_trident_voice *evoice = voice->extra;
+
+	/* voice management */
+
+	if (params_buffer_size(hw_params) / 2 != params_period_size(hw_params)) {
+		if (evoice == NULL) {
+			evoice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+			if (evoice == NULL)
+				return -ENOMEM;
+			voice->extra = evoice;
+			evoice->substream = substream;
+		}
+	} else {
+		if (evoice != NULL) {
+			snd_trident_free_voice(trident, evoice);
+			voice->extra = evoice = NULL;
+		}
+	}
+
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_hw_params
+  
+   Description: Set the hardware parameters for the playback device.
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	int err;
+
+	err = snd_trident_allocate_pcm_mem(substream, hw_params);
+	if (err >= 0)
+		err = snd_trident_allocate_evoice(substream, hw_params);
+	return err;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_playback_hw_free
+  
+   Description: Release the hardware resources for the playback device.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	struct snd_trident_voice *evoice = voice ? voice->extra : NULL;
+
+	if (trident->tlb.entries) {
+		if (voice && voice->memblk) {
+			snd_trident_free_pages(trident, voice->memblk);
+			voice->memblk = NULL;
+		}
+	}
+	snd_pcm_lib_free_pages(substream);
+	if (evoice != NULL) {
+		snd_trident_free_voice(trident, evoice);
+		voice->extra = NULL;
+	}
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_playback_prepare
+  
+   Description: Prepare playback device for playback.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	struct snd_trident_voice *evoice = voice->extra;
+	struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[substream->number];
+
+	spin_lock_irq(&trident->reg_lock);	
+
+	/* set delta (rate) value */
+	voice->Delta = snd_trident_convert_rate(runtime->rate);
+	voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+
+	/* set Loop Begin Address */
+	if (voice->memblk)
+		voice->LBA = voice->memblk->offset;
+	else
+		voice->LBA = runtime->dma_addr;
+ 
+	voice->CSO = 0;
+	voice->ESO = runtime->buffer_size - 1;	/* in samples */
+	voice->CTRL = snd_trident_control_mode(substream);
+	voice->FMC = 3;
+	voice->GVSel = 1;
+	voice->EC = 0;
+	voice->Alpha = 0;
+	voice->FMS = 0;
+	voice->Vol = mix->vol;
+	voice->RVol = mix->rvol;
+	voice->CVol = mix->cvol;
+	voice->Pan = mix->pan;
+	voice->Attribute = 0;
+#if 0
+	voice->Attribute = (1<<(30-16))|(2<<(26-16))|
+			   (0<<(24-16))|(0x1f<<(19-16));
+#else
+	voice->Attribute = 0;
+#endif
+
+	snd_trident_write_voice_regs(trident, voice);
+
+	if (evoice != NULL) {
+		evoice->Delta = voice->Delta;
+		evoice->spurious_threshold = voice->spurious_threshold;
+		evoice->LBA = voice->LBA;
+		evoice->CSO = 0;
+		evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
+		evoice->CTRL = voice->CTRL;
+		evoice->FMC = 3;
+		evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
+		evoice->EC = 0;
+		evoice->Alpha = 0;
+		evoice->FMS = 0;
+		evoice->Vol = 0x3ff;			/* mute */
+		evoice->RVol = evoice->CVol = 0x7f;	/* mute */
+		evoice->Pan = 0x7f;			/* mute */
+#if 0
+		evoice->Attribute = (1<<(30-16))|(2<<(26-16))|
+				    (0<<(24-16))|(0x1f<<(19-16));
+#else
+		evoice->Attribute = 0;
+#endif
+		snd_trident_write_voice_regs(trident, evoice);
+		evoice->isync2 = 1;
+		evoice->isync_mark = runtime->period_size;
+		evoice->ESO = (runtime->period_size * 2) - 1;
+	}
+
+	spin_unlock_irq(&trident->reg_lock);
+
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_capture_hw_params
+  
+   Description: Set the hardware parameters for the capture device.
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_capture_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_trident_allocate_pcm_mem(substream, hw_params);
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_capture_prepare
+  
+   Description: Prepare capture device for playback.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	unsigned int val, ESO_bytes;
+
+	spin_lock_irq(&trident->reg_lock);
+
+	// Initialize the channel and set channel Mode
+	outb(0, TRID_REG(trident, LEGACY_DMAR15));
+
+	// Set DMA channel operation mode register
+	outb(0x54, TRID_REG(trident, LEGACY_DMAR11));
+
+	// Set channel buffer Address, DMAR0 expects contiguous PCI memory area	
+	voice->LBA = runtime->dma_addr;
+	outl(voice->LBA, TRID_REG(trident, LEGACY_DMAR0));
+	if (voice->memblk)
+		voice->LBA = voice->memblk->offset;
+
+	// set ESO
+	ESO_bytes = snd_pcm_lib_buffer_bytes(substream) - 1;
+	outb((ESO_bytes & 0x00ff0000) >> 16, TRID_REG(trident, LEGACY_DMAR6));
+	outw((ESO_bytes & 0x0000ffff), TRID_REG(trident, LEGACY_DMAR4));
+	ESO_bytes++;
+
+	// Set channel sample rate, 4.12 format
+	val = (((unsigned int) 48000L << 12) + (runtime->rate/2)) / runtime->rate;
+	outw(val, TRID_REG(trident, T4D_SBDELTA_DELTA_R));
+
+	// Set channel interrupt blk length
+	if (snd_pcm_format_width(runtime->format) == 16) {
+		val = (unsigned short) ((ESO_bytes >> 1) - 1);
+	} else {
+		val = (unsigned short) (ESO_bytes - 1);
+	}
+
+	outl((val << 16) | val, TRID_REG(trident, T4D_SBBL_SBCL));
+
+	// Right now, set format and start to run captureing, 
+	// continuous run loop enable.
+	trident->bDMAStart = 0x19;	// 0001 1001b
+
+	if (snd_pcm_format_width(runtime->format) == 16)
+		trident->bDMAStart |= 0x80;
+	if (snd_pcm_format_signed(runtime->format))
+		trident->bDMAStart |= 0x20;
+	if (runtime->channels > 1)
+		trident->bDMAStart |= 0x40;
+
+	// Prepare capture intr channel
+
+	voice->Delta = snd_trident_convert_rate(runtime->rate);
+	voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+	voice->isync = 1;
+	voice->isync_mark = runtime->period_size;
+	voice->isync_max = runtime->buffer_size;
+
+	// Set voice parameters
+	voice->CSO = 0;
+	voice->ESO = voice->isync_ESO = (runtime->period_size * 2) + 6 - 1;
+	voice->CTRL = snd_trident_control_mode(substream);
+	voice->FMC = 3;
+	voice->RVol = 0x7f;
+	voice->CVol = 0x7f;
+	voice->GVSel = 1;
+	voice->Pan = 0x7f;		/* mute */
+	voice->Vol = 0x3ff;		/* mute */
+	voice->EC = 0;
+	voice->Alpha = 0;
+	voice->FMS = 0;
+	voice->Attribute = 0;
+
+	snd_trident_write_voice_regs(trident, voice);
+
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_si7018_capture_hw_params
+  
+   Description: Set the hardware parameters for the capture device.
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_si7018_capture_hw_params(struct snd_pcm_substream *substream,
+						struct snd_pcm_hw_params *hw_params)
+{
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+
+	return snd_trident_allocate_evoice(substream, hw_params);
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_si7018_capture_hw_free
+  
+   Description: Release the hardware resources for the capture device.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_si7018_capture_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	struct snd_trident_voice *evoice = voice ? voice->extra : NULL;
+
+	snd_pcm_lib_free_pages(substream);
+	if (evoice != NULL) {
+		snd_trident_free_voice(trident, evoice);
+		voice->extra = NULL;
+	}
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_si7018_capture_prepare
+  
+   Description: Prepare capture device for playback.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_si7018_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	struct snd_trident_voice *evoice = voice->extra;
+
+	spin_lock_irq(&trident->reg_lock);
+
+	voice->LBA = runtime->dma_addr;
+	voice->Delta = snd_trident_convert_adc_rate(runtime->rate);
+	voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+
+	// Set voice parameters
+	voice->CSO = 0;
+	voice->ESO = runtime->buffer_size - 1;		/* in samples */
+	voice->CTRL = snd_trident_control_mode(substream);
+	voice->FMC = 0;
+	voice->RVol = 0;
+	voice->CVol = 0;
+	voice->GVSel = 1;
+	voice->Pan = T4D_DEFAULT_PCM_PAN;
+	voice->Vol = 0;
+	voice->EC = 0;
+	voice->Alpha = 0;
+	voice->FMS = 0;
+
+	voice->Attribute = (2 << (30-16)) |
+			   (2 << (26-16)) |
+			   (2 << (24-16)) |
+			   (1 << (23-16));
+
+	snd_trident_write_voice_regs(trident, voice);
+
+	if (evoice != NULL) {
+		evoice->Delta = snd_trident_convert_rate(runtime->rate);
+		evoice->spurious_threshold = voice->spurious_threshold;
+		evoice->LBA = voice->LBA;
+		evoice->CSO = 0;
+		evoice->ESO = (runtime->period_size * 2) + 20 - 1; /* in samples, 20 means correction */
+		evoice->CTRL = voice->CTRL;
+		evoice->FMC = 3;
+		evoice->GVSel = 0;
+		evoice->EC = 0;
+		evoice->Alpha = 0;
+		evoice->FMS = 0;
+		evoice->Vol = 0x3ff;			/* mute */
+		evoice->RVol = evoice->CVol = 0x7f;	/* mute */
+		evoice->Pan = 0x7f;			/* mute */
+		evoice->Attribute = 0;
+		snd_trident_write_voice_regs(trident, evoice);
+		evoice->isync2 = 1;
+		evoice->isync_mark = runtime->period_size;
+		evoice->ESO = (runtime->period_size * 2) - 1;
+	}
+	
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_foldback_prepare
+  
+   Description: Prepare foldback capture device for playback.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_foldback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	struct snd_trident_voice *evoice = voice->extra;
+
+	spin_lock_irq(&trident->reg_lock);
+
+	/* Set channel buffer Address */
+	if (voice->memblk)
+		voice->LBA = voice->memblk->offset;
+	else
+		voice->LBA = runtime->dma_addr;
+
+	/* set target ESO for channel */
+	voice->ESO = runtime->buffer_size - 1;	/* in samples */
+
+	/* set sample rate */
+	voice->Delta = 0x1000;
+	voice->spurious_threshold = snd_trident_spurious_threshold(48000, runtime->period_size);
+
+	voice->CSO = 0;
+	voice->CTRL = snd_trident_control_mode(substream);
+	voice->FMC = 3;
+	voice->RVol = 0x7f;
+	voice->CVol = 0x7f;
+	voice->GVSel = 1;
+	voice->Pan = 0x7f;	/* mute */
+	voice->Vol = 0x3ff;	/* mute */
+	voice->EC = 0;
+	voice->Alpha = 0;
+	voice->FMS = 0;
+	voice->Attribute = 0;
+
+	/* set up capture channel */
+	outb(((voice->number & 0x3f) | 0x80), TRID_REG(trident, T4D_RCI + voice->foldback_chan));
+
+	snd_trident_write_voice_regs(trident, voice);
+
+	if (evoice != NULL) {
+		evoice->Delta = voice->Delta;
+		evoice->spurious_threshold = voice->spurious_threshold;
+		evoice->LBA = voice->LBA;
+		evoice->CSO = 0;
+		evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
+		evoice->CTRL = voice->CTRL;
+		evoice->FMC = 3;
+		evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
+		evoice->EC = 0;
+		evoice->Alpha = 0;
+		evoice->FMS = 0;
+		evoice->Vol = 0x3ff;			/* mute */
+		evoice->RVol = evoice->CVol = 0x7f;	/* mute */
+		evoice->Pan = 0x7f;			/* mute */
+		evoice->Attribute = 0;
+		snd_trident_write_voice_regs(trident, evoice);
+		evoice->isync2 = 1;
+		evoice->isync_mark = runtime->period_size;
+		evoice->ESO = (runtime->period_size * 2) - 1;
+	}
+
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif_hw_params
+  
+   Description: Set the hardware parameters for the spdif device.
+  
+   Parameters:  substream  - PCM substream class
+		hw_params  - hardware parameters
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	unsigned int old_bits = 0, change = 0;
+	int err;
+
+	err = snd_trident_allocate_pcm_mem(substream, hw_params);
+	if (err < 0)
+		return err;
+
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		err = snd_trident_allocate_evoice(substream, hw_params);
+		if (err < 0)
+			return err;
+	}
+
+	/* prepare SPDIF channel */
+	spin_lock_irq(&trident->reg_lock);
+	old_bits = trident->spdif_pcm_bits;
+	if (old_bits & IEC958_AES0_PROFESSIONAL)
+		trident->spdif_pcm_bits &= ~IEC958_AES0_PRO_FS;
+	else
+		trident->spdif_pcm_bits &= ~(IEC958_AES3_CON_FS << 24);
+	if (params_rate(hw_params) >= 48000) {
+		trident->spdif_pcm_ctrl = 0x3c;	// 48000 Hz
+		trident->spdif_pcm_bits |=
+			trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
+				IEC958_AES0_PRO_FS_48000 :
+				(IEC958_AES3_CON_FS_48000 << 24);
+	}
+	else if (params_rate(hw_params) >= 44100) {
+		trident->spdif_pcm_ctrl = 0x3e;	// 44100 Hz
+		trident->spdif_pcm_bits |=
+			trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
+				IEC958_AES0_PRO_FS_44100 :
+				(IEC958_AES3_CON_FS_44100 << 24);
+	}
+	else {
+		trident->spdif_pcm_ctrl = 0x3d;	// 32000 Hz
+		trident->spdif_pcm_bits |=
+			trident->spdif_bits & IEC958_AES0_PROFESSIONAL ?
+				IEC958_AES0_PRO_FS_32000 :
+				(IEC958_AES3_CON_FS_32000 << 24);
+	}
+	change = old_bits != trident->spdif_pcm_bits;
+	spin_unlock_irq(&trident->reg_lock);
+
+	if (change)
+		snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE, &trident->spdif_pcm_ctl->id);
+
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif_prepare
+  
+   Description: Prepare SPDIF device for playback.
+  
+   Parameters:  substream  - PCM substream class
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	struct snd_trident_voice *evoice = voice->extra;
+	struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[substream->number];
+	unsigned int RESO, LBAO;
+	unsigned int temp;
+
+	spin_lock_irq(&trident->reg_lock);
+
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+
+		/* set delta (rate) value */
+		voice->Delta = snd_trident_convert_rate(runtime->rate);
+		voice->spurious_threshold = snd_trident_spurious_threshold(runtime->rate, runtime->period_size);
+
+		/* set Loop Back Address */
+		LBAO = runtime->dma_addr;
+		if (voice->memblk)
+			voice->LBA = voice->memblk->offset;
+		else
+			voice->LBA = LBAO;
+
+		voice->isync = 1;
+		voice->isync3 = 1;
+		voice->isync_mark = runtime->period_size;
+		voice->isync_max = runtime->buffer_size;
+
+		/* set target ESO for channel */
+		RESO = runtime->buffer_size - 1;
+		voice->ESO = voice->isync_ESO = (runtime->period_size * 2) + 6 - 1;
+
+		/* set ctrl mode */
+		voice->CTRL = snd_trident_control_mode(substream);
+
+		voice->FMC = 3;
+		voice->RVol = 0x7f;
+		voice->CVol = 0x7f;
+		voice->GVSel = 1;
+		voice->Pan = 0x7f;
+		voice->Vol = 0x3ff;
+		voice->EC = 0;
+		voice->CSO = 0;
+		voice->Alpha = 0;
+		voice->FMS = 0;
+		voice->Attribute = 0;
+
+		/* prepare surrogate IRQ channel */
+		snd_trident_write_voice_regs(trident, voice);
+
+		outw((RESO & 0xffff), TRID_REG(trident, NX_SPESO));
+		outb((RESO >> 16), TRID_REG(trident, NX_SPESO + 2));
+		outl((LBAO & 0xfffffffc), TRID_REG(trident, NX_SPLBA));
+		outw((voice->CSO & 0xffff), TRID_REG(trident, NX_SPCTRL_SPCSO));
+		outb((voice->CSO >> 16), TRID_REG(trident, NX_SPCTRL_SPCSO + 2));
+
+		/* set SPDIF setting */
+		outb(trident->spdif_pcm_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+		outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
+
+	} else {	/* SiS */
+	
+		/* set delta (rate) value */
+		voice->Delta = 0x800;
+		voice->spurious_threshold = snd_trident_spurious_threshold(48000, runtime->period_size);
+
+		/* set Loop Begin Address */
+		if (voice->memblk)
+			voice->LBA = voice->memblk->offset;
+		else
+			voice->LBA = runtime->dma_addr;
+
+		voice->CSO = 0;
+		voice->ESO = runtime->buffer_size - 1;	/* in samples */
+		voice->CTRL = snd_trident_control_mode(substream);
+		voice->FMC = 3;
+		voice->GVSel = 1;
+		voice->EC = 0;
+		voice->Alpha = 0;
+		voice->FMS = 0;
+		voice->Vol = mix->vol;
+		voice->RVol = mix->rvol;
+		voice->CVol = mix->cvol;
+		voice->Pan = mix->pan;
+		voice->Attribute = (1<<(30-16))|(7<<(26-16))|
+				   (0<<(24-16))|(0<<(19-16));
+
+		snd_trident_write_voice_regs(trident, voice);
+
+		if (evoice != NULL) {
+			evoice->Delta = voice->Delta;
+			evoice->spurious_threshold = voice->spurious_threshold;
+			evoice->LBA = voice->LBA;
+			evoice->CSO = 0;
+			evoice->ESO = (runtime->period_size * 2) + 4 - 1; /* in samples */
+			evoice->CTRL = voice->CTRL;
+			evoice->FMC = 3;
+			evoice->GVSel = trident->device == TRIDENT_DEVICE_ID_SI7018 ? 0 : 1;
+			evoice->EC = 0;
+			evoice->Alpha = 0;
+			evoice->FMS = 0;
+			evoice->Vol = 0x3ff;			/* mute */
+			evoice->RVol = evoice->CVol = 0x7f;	/* mute */
+			evoice->Pan = 0x7f;			/* mute */
+			evoice->Attribute = 0;
+			snd_trident_write_voice_regs(trident, evoice);
+			evoice->isync2 = 1;
+			evoice->isync_mark = runtime->period_size;
+			evoice->ESO = (runtime->period_size * 2) - 1;
+		}
+
+		outl(trident->spdif_pcm_bits, TRID_REG(trident, SI_SPDIF_CS));
+		temp = inl(TRID_REG(trident, T4D_LFO_GC_CIR));
+		temp &= ~(1<<19);
+		outl(temp, TRID_REG(trident, T4D_LFO_GC_CIR));
+		temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+		temp |= SPDIF_EN;
+		outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+	}
+
+	spin_unlock_irq(&trident->reg_lock);
+
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_trigger
+  
+   Description: Start/stop devices
+  
+   Parameters:  substream  - PCM substream class
+   		cmd	- trigger command (STOP, GO)
+  
+   Returns:     Error status
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_trigger(struct snd_pcm_substream *substream,
+			       int cmd)
+				    
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_substream *s;
+	unsigned int what, whati, capture_flag, spdif_flag;
+	struct snd_trident_voice *voice, *evoice;
+	unsigned int val, go;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		go = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		go = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+	what = whati = capture_flag = spdif_flag = 0;
+	spin_lock(&trident->reg_lock);
+	val = inl(TRID_REG(trident, T4D_STIMER)) & 0x00ffffff;
+	snd_pcm_group_for_each_entry(s, substream) {
+		if ((struct snd_trident *) snd_pcm_substream_chip(s) == trident) {
+			voice = s->runtime->private_data;
+			evoice = voice->extra;
+			what |= 1 << (voice->number & 0x1f);
+			if (evoice == NULL) {
+				whati |= 1 << (voice->number & 0x1f);
+			} else {
+				what |= 1 << (evoice->number & 0x1f);
+				whati |= 1 << (evoice->number & 0x1f);
+				if (go)
+					evoice->stimer = val;
+			}
+			if (go) {
+				voice->running = 1;
+				voice->stimer = val;
+			} else {
+				voice->running = 0;
+			}
+			snd_pcm_trigger_done(s, substream);
+			if (voice->capture)
+				capture_flag = 1;
+			if (voice->spdif)
+				spdif_flag = 1;
+		}
+	}
+	if (spdif_flag) {
+		if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+			outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
+			val = trident->spdif_pcm_ctrl;
+			if (!go)
+				val &= ~(0x28);
+			outb(val, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+		} else {
+			outl(trident->spdif_pcm_bits, TRID_REG(trident, SI_SPDIF_CS));
+			val = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) | SPDIF_EN;
+			outl(val, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+		}
+	}
+	if (!go)
+		outl(what, TRID_REG(trident, T4D_STOP_B));
+	val = inl(TRID_REG(trident, T4D_AINTEN_B));
+	if (go) {
+		val |= whati;
+	} else {
+		val &= ~whati;
+	}
+	outl(val, TRID_REG(trident, T4D_AINTEN_B));
+	if (go) {
+		outl(what, TRID_REG(trident, T4D_START_B));
+
+		if (capture_flag && trident->device != TRIDENT_DEVICE_ID_SI7018)
+			outb(trident->bDMAStart, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
+	} else {
+		if (capture_flag && trident->device != TRIDENT_DEVICE_ID_SI7018)
+			outb(0x00, TRID_REG(trident, T4D_SBCTRL_SBE2R_SBDD));
+	}
+	spin_unlock(&trident->reg_lock);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_playback_pointer
+  
+   Description: This routine return the playback position
+                
+   Parameters:	substream  - PCM substream class
+
+   Returns:     position of buffer
+  
+  ---------------------------------------------------------------------------*/
+
+static snd_pcm_uframes_t snd_trident_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	unsigned int cso;
+
+	if (!voice->running)
+		return 0;
+
+	spin_lock(&trident->reg_lock);
+
+	outb(voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
+
+	if (trident->device != TRIDENT_DEVICE_ID_NX) {
+		cso = inw(TRID_REG(trident, CH_DX_CSO_ALPHA_FMS + 2));
+	} else {		// ID_4DWAVE_NX
+		cso = (unsigned int) inl(TRID_REG(trident, CH_NX_DELTA_CSO)) & 0x00ffffff;
+	}
+
+	spin_unlock(&trident->reg_lock);
+
+	if (cso >= runtime->buffer_size)
+		cso = 0;
+
+	return cso;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_capture_pointer
+  
+   Description: This routine return the capture position
+                
+   Parameters:   pcm1    - PCM device class
+
+   Returns:     position of buffer
+  
+  ---------------------------------------------------------------------------*/
+
+static snd_pcm_uframes_t snd_trident_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	unsigned int result;
+
+	if (!voice->running)
+		return 0;
+
+	result = inw(TRID_REG(trident, T4D_SBBL_SBCL));
+	if (runtime->channels > 1)
+		result >>= 1;
+	if (result > 0)
+		result = runtime->buffer_size - result;
+
+	return result;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif_pointer
+  
+   Description: This routine return the SPDIF playback position
+                
+   Parameters:	substream  - PCM substream class
+
+   Returns:     position of buffer
+  
+  ---------------------------------------------------------------------------*/
+
+static snd_pcm_uframes_t snd_trident_spdif_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+	unsigned int result;
+
+	if (!voice->running)
+		return 0;
+
+	result = inl(TRID_REG(trident, NX_SPCTRL_SPCSO)) & 0x00ffffff;
+
+	return result;
+}
+
+/*
+ *  Playback support device description
+ */
+
+static struct snd_pcm_hardware snd_trident_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
+	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+				 SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(256*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(256*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *  Capture support device description
+ */
+
+static struct snd_pcm_hardware snd_trident_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
+	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
+				 SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U16_LE),
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *  Foldback capture support device description
+ */
+
+static struct snd_pcm_hardware snd_trident_foldback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+/*
+ *  SPDIF playback support device description
+ */
+
+static struct snd_pcm_hardware snd_trident_spdif =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+				 SNDRV_PCM_RATE_48000),
+	.rate_min =		32000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_trident_spdif_7018 =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_SYNC_START |
+				 SNDRV_PCM_INFO_PAUSE /* | SNDRV_PCM_INFO_RESUME */),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		1,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static void snd_trident_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+	struct snd_trident_voice *voice = runtime->private_data;
+	struct snd_trident *trident;
+
+	if (voice) {
+		trident = voice->trident;
+		snd_trident_free_voice(trident, voice);
+	}
+}
+
+static int snd_trident_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice;
+
+	voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+	if (voice == NULL)
+		return -EAGAIN;
+	snd_trident_pcm_mixer_build(trident, voice, substream);
+	voice->substream = substream;
+	runtime->private_data = voice;
+	runtime->private_free = snd_trident_pcm_free_substream;
+	runtime->hw = snd_trident_playback;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_playback_close
+  
+   Description: This routine will close the 4DWave playback device. For now 
+                we will simply free the dma transfer buffer.
+                
+   Parameters:	substream  - PCM substream class
+
+  ---------------------------------------------------------------------------*/
+static int snd_trident_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_trident_voice *voice = runtime->private_data;
+
+	snd_trident_pcm_mixer_free(trident, voice, substream);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif_open
+  
+   Description: This routine will open the 4DWave SPDIF device.
+
+   Parameters:	substream  - PCM substream class
+
+   Returns:     status  - success or failure flag
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_open(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_trident_voice *voice;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	
+	voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+	if (voice == NULL)
+		return -EAGAIN;
+	voice->spdif = 1;
+	voice->substream = substream;
+	spin_lock_irq(&trident->reg_lock);
+	trident->spdif_pcm_bits = trident->spdif_bits;
+	spin_unlock_irq(&trident->reg_lock);
+
+	runtime->private_data = voice;
+	runtime->private_free = snd_trident_pcm_free_substream;
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		runtime->hw = snd_trident_spdif;
+	} else {
+		runtime->hw = snd_trident_spdif_7018;
+	}
+
+	trident->spdif_pcm_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &trident->spdif_pcm_ctl->id);
+
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+	return 0;
+}
+
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif_close
+  
+   Description: This routine will close the 4DWave SPDIF device.
+                
+   Parameters:	substream  - PCM substream class
+
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_close(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	unsigned int temp;
+
+	spin_lock_irq(&trident->reg_lock);
+	// restore default SPDIF setting
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+		outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+		outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+	} else {
+		outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+		temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+		if (trident->spdif_ctrl) {
+			temp |= SPDIF_EN;
+		} else {
+			temp &= ~SPDIF_EN;
+		}
+		outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+	}
+	spin_unlock_irq(&trident->reg_lock);
+	trident->spdif_pcm_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(trident->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &trident->spdif_pcm_ctl->id);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_capture_open
+  
+   Description: This routine will open the 4DWave capture device.
+
+   Parameters:	substream  - PCM substream class
+
+   Returns:     status  - success or failure flag
+
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_trident_voice *voice;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+	if (voice == NULL)
+		return -EAGAIN;
+	voice->capture = 1;
+	voice->substream = substream;
+	runtime->private_data = voice;
+	runtime->private_free = snd_trident_pcm_free_substream;
+	runtime->hw = snd_trident_capture;
+	snd_pcm_set_sync(substream);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_capture_close
+  
+   Description: This routine will close the 4DWave capture device. For now 
+                we will simply free the dma transfer buffer.
+                
+   Parameters:	substream  - PCM substream class
+
+  ---------------------------------------------------------------------------*/
+static int snd_trident_capture_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_foldback_open
+  
+   Description: This routine will open the 4DWave foldback capture device.
+
+   Parameters:	substream  - PCM substream class
+
+   Returns:     status  - success or failure flag
+
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_foldback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_trident_voice *voice;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_PCM, 0, 0);
+	if (voice == NULL)
+		return -EAGAIN;
+	voice->foldback_chan = substream->number;
+	voice->substream = substream;
+	runtime->private_data = voice;
+	runtime->private_free = snd_trident_pcm_free_substream;
+	runtime->hw = snd_trident_foldback;
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0, 64*1024);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_foldback_close
+  
+   Description: This routine will close the 4DWave foldback capture device. 
+		For now we will simply free the dma transfer buffer.
+                
+   Parameters:	substream  - PCM substream class
+
+  ---------------------------------------------------------------------------*/
+static int snd_trident_foldback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_trident *trident = snd_pcm_substream_chip(substream);
+	struct snd_trident_voice *voice;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	voice = runtime->private_data;
+	
+	/* stop capture channel */
+	spin_lock_irq(&trident->reg_lock);
+	outb(0x00, TRID_REG(trident, T4D_RCI + voice->foldback_chan));
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   PCM operations
+  ---------------------------------------------------------------------------*/
+
+static struct snd_pcm_ops snd_trident_playback_ops = {
+	.open =		snd_trident_playback_open,
+	.close =	snd_trident_playback_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_playback_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_trident_nx_playback_ops = {
+	.open =		snd_trident_playback_open,
+	.close =	snd_trident_playback_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_playback_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+static struct snd_pcm_ops snd_trident_capture_ops = {
+	.open =		snd_trident_capture_open,
+	.close =	snd_trident_capture_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_capture_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_capture_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_capture_pointer,
+};
+
+static struct snd_pcm_ops snd_trident_si7018_capture_ops = {
+	.open =		snd_trident_capture_open,
+	.close =	snd_trident_capture_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_si7018_capture_hw_params,
+	.hw_free =	snd_trident_si7018_capture_hw_free,
+	.prepare =	snd_trident_si7018_capture_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_trident_foldback_ops = {
+	.open =		snd_trident_foldback_open,
+	.close =	snd_trident_foldback_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_foldback_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_trident_nx_foldback_ops = {
+	.open =		snd_trident_foldback_open,
+	.close =	snd_trident_foldback_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_foldback_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+static struct snd_pcm_ops snd_trident_spdif_ops = {
+	.open =		snd_trident_spdif_open,
+	.close =	snd_trident_spdif_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_spdif_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_spdif_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_spdif_pointer,
+};
+
+static struct snd_pcm_ops snd_trident_spdif_7018_ops = {
+	.open =		snd_trident_spdif_open,
+	.close =	snd_trident_spdif_close,
+	.ioctl =	snd_trident_ioctl,
+	.hw_params =	snd_trident_spdif_hw_params,
+	.hw_free =	snd_trident_hw_free,
+	.prepare =	snd_trident_spdif_prepare,
+	.trigger =	snd_trident_trigger,
+	.pointer =	snd_trident_playback_pointer,
+};
+
+/*---------------------------------------------------------------------------
+   snd_trident_pcm
+  
+   Description: This routine registers the 4DWave device for PCM support.
+                
+   Parameters:  trident - pointer to target device class for 4DWave.
+
+   Returns:     None
+  
+  ---------------------------------------------------------------------------*/
+
+int __devinit snd_trident_pcm(struct snd_trident * trident,
+			      int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(trident->card, "trident_dx_nx", device, trident->ChanPCM, 1, &pcm)) < 0)
+		return err;
+
+	pcm->private_data = trident;
+
+	if (trident->tlb.entries) {
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_nx_playback_ops);
+	} else {
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_playback_ops);
+	}
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			trident->device != TRIDENT_DEVICE_ID_SI7018 ?
+			&snd_trident_capture_ops :
+			&snd_trident_si7018_capture_ops);
+
+	pcm->info_flags = 0;
+	pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+	strcpy(pcm->name, "Trident 4DWave");
+	trident->pcm = pcm;
+
+	if (trident->tlb.entries) {
+		struct snd_pcm_substream *substream;
+		for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next)
+			snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG,
+						      snd_dma_pci_data(trident->pci),
+						      64*1024, 128*1024);
+		snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+					      SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci),
+					      64*1024, 128*1024);
+	} else {
+		snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(trident->pci), 64*1024, 128*1024);
+	}
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_foldback_pcm
+  
+   Description: This routine registers the 4DWave device for foldback PCM support.
+                
+   Parameters:  trident - pointer to target device class for 4DWave.
+
+   Returns:     None
+  
+  ---------------------------------------------------------------------------*/
+
+int __devinit snd_trident_foldback_pcm(struct snd_trident * trident,
+				       int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *foldback;
+	int err;
+	int num_chan = 3;
+	struct snd_pcm_substream *substream;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if (trident->device == TRIDENT_DEVICE_ID_NX)
+		num_chan = 4;
+	if ((err = snd_pcm_new(trident->card, "trident_dx_nx", device, 0, num_chan, &foldback)) < 0)
+		return err;
+
+	foldback->private_data = trident;
+	if (trident->tlb.entries)
+		snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_nx_foldback_ops);
+	else
+		snd_pcm_set_ops(foldback, SNDRV_PCM_STREAM_CAPTURE, &snd_trident_foldback_ops);
+	foldback->info_flags = 0;
+	strcpy(foldback->name, "Trident 4DWave");
+	substream = foldback->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+	strcpy(substream->name, "Front Mixer");
+	substream = substream->next;
+	strcpy(substream->name, "Reverb Mixer");
+	substream = substream->next;
+	strcpy(substream->name, "Chorus Mixer");
+	if (num_chan == 4) {
+		substream = substream->next;
+		strcpy(substream->name, "Second AC'97 ADC");
+	}
+	trident->foldback = foldback;
+
+	if (trident->tlb.entries)
+		snd_pcm_lib_preallocate_pages_for_all(foldback, SNDRV_DMA_TYPE_DEV_SG,
+						      snd_dma_pci_data(trident->pci), 0, 128*1024);
+	else
+		snd_pcm_lib_preallocate_pages_for_all(foldback, SNDRV_DMA_TYPE_DEV,
+						      snd_dma_pci_data(trident->pci), 64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = foldback;
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_spdif
+  
+   Description: This routine registers the 4DWave-NX device for SPDIF support.
+                
+   Parameters:  trident - pointer to target device class for 4DWave-NX.
+
+   Returns:     None
+  
+  ---------------------------------------------------------------------------*/
+
+int __devinit snd_trident_spdif_pcm(struct snd_trident * trident,
+				    int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *spdif;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(trident->card, "trident_dx_nx IEC958", device, 1, 0, &spdif)) < 0)
+		return err;
+
+	spdif->private_data = trident;
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+		snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_ops);
+	} else {
+		snd_pcm_set_ops(spdif, SNDRV_PCM_STREAM_PLAYBACK, &snd_trident_spdif_7018_ops);
+	}
+	spdif->info_flags = 0;
+	strcpy(spdif->name, "Trident 4DWave IEC958");
+	trident->spdif = spdif;
+
+	snd_pcm_lib_preallocate_pages_for_all(spdif, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci), 64*1024, 128*1024);
+
+	if (rpcm)
+		*rpcm = spdif;
+	return 0;
+}
+
+/*
+ *  Mixer part
+ */
+
+
+/*---------------------------------------------------------------------------
+    snd_trident_spdif_control
+
+    Description: enable/disable S/PDIF out from ac97 mixer
+  ---------------------------------------------------------------------------*/
+
+#define snd_trident_spdif_control_info	snd_ctl_boolean_mono_info
+
+static int snd_trident_spdif_control_get(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+
+	spin_lock_irq(&trident->reg_lock);
+	val = trident->spdif_ctrl;
+	ucontrol->value.integer.value[0] = val == kcontrol->private_value;
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+static int snd_trident_spdif_control_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+	int change;
+
+	val = ucontrol->value.integer.value[0] ? (unsigned char) kcontrol->private_value : 0x00;
+	spin_lock_irq(&trident->reg_lock);
+	/* S/PDIF C Channel bits 0-31 : 48khz, SCMS disabled */
+	change = trident->spdif_ctrl != val;
+	trident->spdif_ctrl = val;
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+		if ((inb(TRID_REG(trident, NX_SPCTRL_SPCSO + 3)) & 0x10) == 0) {
+			outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+			outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+		}
+	} else {
+		if (trident->spdif == NULL) {
+			unsigned int temp;
+			outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+			temp = inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & ~SPDIF_EN;
+			if (val)
+				temp |= SPDIF_EN;
+			outl(temp, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+		}
+	}
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_trident_spdif_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH),
+	.info =		snd_trident_spdif_control_info,
+	.get =		snd_trident_spdif_control_get,
+	.put =		snd_trident_spdif_control_put,
+	.private_value = 0x28,
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_spdif_default
+
+    Description: put/get the S/PDIF default settings
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_default_info(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_trident_spdif_default_get(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&trident->reg_lock);
+	ucontrol->value.iec958.status[0] = (trident->spdif_bits >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (trident->spdif_bits >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (trident->spdif_bits >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (trident->spdif_bits >> 24) & 0xff;
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+static int snd_trident_spdif_default_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+
+	val = (ucontrol->value.iec958.status[0] << 0) |
+	      (ucontrol->value.iec958.status[1] << 8) |
+	      (ucontrol->value.iec958.status[2] << 16) |
+	      (ucontrol->value.iec958.status[3] << 24);
+	spin_lock_irq(&trident->reg_lock);
+	change = trident->spdif_bits != val;
+	trident->spdif_bits = val;
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+		if ((inb(TRID_REG(trident, NX_SPCTRL_SPCSO + 3)) & 0x10) == 0)
+			outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+	} else {
+		if (trident->spdif == NULL)
+			outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+	}
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_trident_spdif_default __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =		snd_trident_spdif_default_info,
+	.get =		snd_trident_spdif_default_get,
+	.put =		snd_trident_spdif_default_put
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_spdif_mask
+
+    Description: put/get the S/PDIF mask
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_mask_info(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_trident_spdif_mask_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0xff;
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_trident_spdif_mask __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
+	.info =		snd_trident_spdif_mask_info,
+	.get =		snd_trident_spdif_mask_get,
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_spdif_stream
+
+    Description: put/get the S/PDIF stream settings
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_spdif_stream_info(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_trident_spdif_stream_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&trident->reg_lock);
+	ucontrol->value.iec958.status[0] = (trident->spdif_pcm_bits >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (trident->spdif_pcm_bits >> 8) & 0xff;
+	ucontrol->value.iec958.status[2] = (trident->spdif_pcm_bits >> 16) & 0xff;
+	ucontrol->value.iec958.status[3] = (trident->spdif_pcm_bits >> 24) & 0xff;
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+static int snd_trident_spdif_stream_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+
+	val = (ucontrol->value.iec958.status[0] << 0) |
+	      (ucontrol->value.iec958.status[1] << 8) |
+	      (ucontrol->value.iec958.status[2] << 16) |
+	      (ucontrol->value.iec958.status[3] << 24);
+	spin_lock_irq(&trident->reg_lock);
+	change = trident->spdif_pcm_bits != val;
+	trident->spdif_pcm_bits = val;
+	if (trident->spdif != NULL) {
+		if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+			outl(trident->spdif_pcm_bits, TRID_REG(trident, NX_SPCSTATUS));
+		} else {
+			outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+		}
+	}
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_trident_spdif_stream __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+	.info =		snd_trident_spdif_stream_info,
+	.get =		snd_trident_spdif_stream_get,
+	.put =		snd_trident_spdif_stream_put
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_ac97_control
+
+    Description: enable/disable rear path for ac97
+  ---------------------------------------------------------------------------*/
+
+#define snd_trident_ac97_control_info	snd_ctl_boolean_mono_info
+
+static int snd_trident_ac97_control_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+
+	spin_lock_irq(&trident->reg_lock);
+	val = trident->ac97_ctrl = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	ucontrol->value.integer.value[0] = (val & (1 << kcontrol->private_value)) ? 1 : 0;
+	spin_unlock_irq(&trident->reg_lock);
+	return 0;
+}
+
+static int snd_trident_ac97_control_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+	int change = 0;
+
+	spin_lock_irq(&trident->reg_lock);
+	val = trident->ac97_ctrl = inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	val &= ~(1 << kcontrol->private_value);
+	if (ucontrol->value.integer.value[0])
+		val |= 1 << kcontrol->private_value;
+	change = val != trident->ac97_ctrl;
+	trident->ac97_ctrl = val;
+	outl(trident->ac97_ctrl = val, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_trident_ac97_rear_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Rear Path",
+	.info =		snd_trident_ac97_control_info,
+	.get =		snd_trident_ac97_control_get,
+	.put =		snd_trident_ac97_control_put,
+	.private_value = 4,
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_vol_control
+
+    Description: wave & music volume control
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_vol_control_info(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	return 0;
+}
+
+static int snd_trident_vol_control_get(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+
+	val = trident->musicvol_wavevol;
+	ucontrol->value.integer.value[0] = 255 - ((val >> kcontrol->private_value) & 0xff);
+	ucontrol->value.integer.value[1] = 255 - ((val >> (kcontrol->private_value + 8)) & 0xff);
+	return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_gvol, -6375, 25, 0);
+
+static int snd_trident_vol_control_put(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change = 0;
+
+	spin_lock_irq(&trident->reg_lock);
+	val = trident->musicvol_wavevol;
+	val &= ~(0xffff << kcontrol->private_value);
+	val |= ((255 - (ucontrol->value.integer.value[0] & 0xff)) |
+	        ((255 - (ucontrol->value.integer.value[1] & 0xff)) << 8)) << kcontrol->private_value;
+	change = val != trident->musicvol_wavevol;
+	outl(trident->musicvol_wavevol = val, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_trident_vol_music_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Music Playback Volume",
+	.info =		snd_trident_vol_control_info,
+	.get =		snd_trident_vol_control_get,
+	.put =		snd_trident_vol_control_put,
+	.private_value = 16,
+	.tlv = { .p = db_scale_gvol },
+};
+
+static struct snd_kcontrol_new snd_trident_vol_wave_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "Wave Playback Volume",
+	.info =		snd_trident_vol_control_info,
+	.get =		snd_trident_vol_control_get,
+	.put =		snd_trident_vol_control_put,
+	.private_value = 0,
+	.tlv = { .p = db_scale_gvol },
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_pcm_vol_control
+
+    Description: PCM front volume control
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_vol_control_info(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018)
+		uinfo->value.integer.max = 1023;
+	return 0;
+}
+
+static int snd_trident_pcm_vol_control_get(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		ucontrol->value.integer.value[0] = 1023 - mix->vol;
+	} else {
+		ucontrol->value.integer.value[0] = 255 - (mix->vol>>2);
+	}
+	return 0;
+}
+
+static int snd_trident_pcm_vol_control_put(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+	unsigned int val;
+	int change = 0;
+
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		val = 1023 - (ucontrol->value.integer.value[0] & 1023);
+	} else {
+		val = (255 - (ucontrol->value.integer.value[0] & 255)) << 2;
+	}
+	spin_lock_irq(&trident->reg_lock);
+	change = val != mix->vol;
+	mix->vol = val;
+	if (mix->voice != NULL)
+		snd_trident_write_vol_reg(trident, mix->voice, val);
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_trident_pcm_vol_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "PCM Front Playback Volume",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.count =	32,
+	.info =		snd_trident_pcm_vol_control_info,
+	.get =		snd_trident_pcm_vol_control_get,
+	.put =		snd_trident_pcm_vol_control_put,
+	/* FIXME: no tlv yet */
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_pcm_pan_control
+
+    Description: PCM front pan control
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_pan_control_info(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 127;
+	return 0;
+}
+
+static int snd_trident_pcm_pan_control_get(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+	ucontrol->value.integer.value[0] = mix->pan;
+	if (ucontrol->value.integer.value[0] & 0x40) {
+		ucontrol->value.integer.value[0] = (0x3f - (ucontrol->value.integer.value[0] & 0x3f));
+	} else {
+		ucontrol->value.integer.value[0] |= 0x40;
+	}
+	return 0;
+}
+
+static int snd_trident_pcm_pan_control_put(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+	unsigned char val;
+	int change = 0;
+
+	if (ucontrol->value.integer.value[0] & 0x40)
+		val = ucontrol->value.integer.value[0] & 0x3f;
+	else
+		val = (0x3f - (ucontrol->value.integer.value[0] & 0x3f)) | 0x40;
+	spin_lock_irq(&trident->reg_lock);
+	change = val != mix->pan;
+	mix->pan = val;
+	if (mix->voice != NULL)
+		snd_trident_write_pan_reg(trident, mix->voice, val);
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_trident_pcm_pan_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "PCM Pan Playback Control",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.count =	32,
+	.info =		snd_trident_pcm_pan_control_info,
+	.get =		snd_trident_pcm_pan_control_get,
+	.put =		snd_trident_pcm_pan_control_put,
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_pcm_rvol_control
+
+    Description: PCM reverb volume control
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_rvol_control_info(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 127;
+	return 0;
+}
+
+static int snd_trident_pcm_rvol_control_get(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+	ucontrol->value.integer.value[0] = 127 - mix->rvol;
+	return 0;
+}
+
+static int snd_trident_pcm_rvol_control_put(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+	unsigned short val;
+	int change = 0;
+
+	val = 0x7f - (ucontrol->value.integer.value[0] & 0x7f);
+	spin_lock_irq(&trident->reg_lock);
+	change = val != mix->rvol;
+	mix->rvol = val;
+	if (mix->voice != NULL)
+		snd_trident_write_rvol_reg(trident, mix->voice, val);
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_crvol, -3175, 25, 1);
+
+static struct snd_kcontrol_new snd_trident_pcm_rvol_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "PCM Reverb Playback Volume",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.count = 	32,
+	.info =		snd_trident_pcm_rvol_control_info,
+	.get =		snd_trident_pcm_rvol_control_get,
+	.put =		snd_trident_pcm_rvol_control_put,
+	.tlv = { .p = db_scale_crvol },
+};
+
+/*---------------------------------------------------------------------------
+    snd_trident_pcm_cvol_control
+
+    Description: PCM chorus volume control
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_pcm_cvol_control_info(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 127;
+	return 0;
+}
+
+static int snd_trident_pcm_cvol_control_get(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+
+	ucontrol->value.integer.value[0] = 127 - mix->cvol;
+	return 0;
+}
+
+static int snd_trident_pcm_cvol_control_put(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_trident *trident = snd_kcontrol_chip(kcontrol);
+	struct snd_trident_pcm_mixer *mix = &trident->pcm_mixer[snd_ctl_get_ioffnum(kcontrol, &ucontrol->id)];
+	unsigned short val;
+	int change = 0;
+
+	val = 0x7f - (ucontrol->value.integer.value[0] & 0x7f);
+	spin_lock_irq(&trident->reg_lock);
+	change = val != mix->cvol;
+	mix->cvol = val;
+	if (mix->voice != NULL)
+		snd_trident_write_cvol_reg(trident, mix->voice, val);
+	spin_unlock_irq(&trident->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_trident_pcm_cvol_control __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =         "PCM Chorus Playback Volume",
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.count =	32,
+	.info =		snd_trident_pcm_cvol_control_info,
+	.get =		snd_trident_pcm_cvol_control_get,
+	.put =		snd_trident_pcm_cvol_control_put,
+	.tlv = { .p = db_scale_crvol },
+};
+
+static void snd_trident_notify_pcm_change1(struct snd_card *card,
+					   struct snd_kcontrol *kctl,
+					   int num, int activate)
+{
+	struct snd_ctl_elem_id id;
+
+	if (! kctl)
+		return;
+	if (activate)
+		kctl->vd[num].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	else
+		kctl->vd[num].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO,
+		       snd_ctl_build_ioff(&id, kctl, num));
+}
+
+static void snd_trident_notify_pcm_change(struct snd_trident *trident,
+					  struct snd_trident_pcm_mixer *tmix,
+					  int num, int activate)
+{
+	snd_trident_notify_pcm_change1(trident->card, trident->ctl_vol, num, activate);
+	snd_trident_notify_pcm_change1(trident->card, trident->ctl_pan, num, activate);
+	snd_trident_notify_pcm_change1(trident->card, trident->ctl_rvol, num, activate);
+	snd_trident_notify_pcm_change1(trident->card, trident->ctl_cvol, num, activate);
+}
+
+static int snd_trident_pcm_mixer_build(struct snd_trident *trident,
+				       struct snd_trident_voice *voice,
+				       struct snd_pcm_substream *substream)
+{
+	struct snd_trident_pcm_mixer *tmix;
+
+	if (snd_BUG_ON(!trident || !voice || !substream))
+		return -EINVAL;
+	tmix = &trident->pcm_mixer[substream->number];
+	tmix->voice = voice;
+	tmix->vol = T4D_DEFAULT_PCM_VOL;
+	tmix->pan = T4D_DEFAULT_PCM_PAN;
+	tmix->rvol = T4D_DEFAULT_PCM_RVOL;
+	tmix->cvol = T4D_DEFAULT_PCM_CVOL;
+	snd_trident_notify_pcm_change(trident, tmix, substream->number, 1);
+	return 0;
+}
+
+static int snd_trident_pcm_mixer_free(struct snd_trident *trident, struct snd_trident_voice *voice, struct snd_pcm_substream *substream)
+{
+	struct snd_trident_pcm_mixer *tmix;
+
+	if (snd_BUG_ON(!trident || !substream))
+		return -EINVAL;
+	tmix = &trident->pcm_mixer[substream->number];
+	tmix->voice = NULL;
+	snd_trident_notify_pcm_change(trident, tmix, substream->number, 0);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_mixer
+  
+   Description: This routine registers the 4DWave device for mixer support.
+                
+   Parameters:  trident - pointer to target device class for 4DWave.
+
+   Returns:     None
+  
+  ---------------------------------------------------------------------------*/
+
+static int __devinit snd_trident_mixer(struct snd_trident * trident, int pcm_spdif_device)
+{
+	struct snd_ac97_template _ac97;
+	struct snd_card *card = trident->card;
+	struct snd_kcontrol *kctl;
+	struct snd_ctl_elem_value *uctl;
+	int idx, err, retries = 2;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_trident_codec_write,
+		.read = snd_trident_codec_read,
+	};
+
+	uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
+	if (!uctl)
+		return -ENOMEM;
+
+	if ((err = snd_ac97_bus(trident->card, 0, &ops, NULL, &trident->ac97_bus)) < 0)
+		goto __out;
+
+	memset(&_ac97, 0, sizeof(_ac97));
+	_ac97.private_data = trident;
+	trident->ac97_detect = 1;
+
+      __again:
+	if ((err = snd_ac97_mixer(trident->ac97_bus, &_ac97, &trident->ac97)) < 0) {
+		if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+			if ((err = snd_trident_sis_reset(trident)) < 0)
+				goto __out;
+			if (retries-- > 0)
+				goto __again;
+			err = -EIO;
+		}
+		goto __out;
+	}
+	
+	/* secondary codec? */
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018 &&
+	    (inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_PRIMARY_READY) != 0) {
+		_ac97.num = 1;
+		err = snd_ac97_mixer(trident->ac97_bus, &_ac97, &trident->ac97_sec);
+		if (err < 0)
+			snd_printk(KERN_ERR "SI7018: the secondary codec - invalid access\n");
+#if 0	// only for my testing purpose --jk
+		{
+			struct snd_ac97 *mc97;
+			err = snd_ac97_modem(trident->card, &_ac97, &mc97);
+			if (err < 0)
+				snd_printk(KERN_ERR "snd_ac97_modem returned error %i\n", err);
+		}
+#endif
+	}
+	
+	trident->ac97_detect = 0;
+
+	if (trident->device != TRIDENT_DEVICE_ID_SI7018) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_vol_wave_control, trident))) < 0)
+			goto __out;
+		kctl->put(kctl, uctl);
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_vol_music_control, trident))) < 0)
+			goto __out;
+		kctl->put(kctl, uctl);
+		outl(trident->musicvol_wavevol = 0x00000000, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+	} else {
+		outl(trident->musicvol_wavevol = 0xffff0000, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+	}
+
+	for (idx = 0; idx < 32; idx++) {
+		struct snd_trident_pcm_mixer *tmix;
+		
+		tmix = &trident->pcm_mixer[idx];
+		tmix->voice = NULL;
+	}
+	if ((trident->ctl_vol = snd_ctl_new1(&snd_trident_pcm_vol_control, trident)) == NULL)
+		goto __nomem;
+	if ((err = snd_ctl_add(card, trident->ctl_vol)))
+		goto __out;
+		
+	if ((trident->ctl_pan = snd_ctl_new1(&snd_trident_pcm_pan_control, trident)) == NULL)
+		goto __nomem;
+	if ((err = snd_ctl_add(card, trident->ctl_pan)))
+		goto __out;
+
+	if ((trident->ctl_rvol = snd_ctl_new1(&snd_trident_pcm_rvol_control, trident)) == NULL)
+		goto __nomem;
+	if ((err = snd_ctl_add(card, trident->ctl_rvol)))
+		goto __out;
+
+	if ((trident->ctl_cvol = snd_ctl_new1(&snd_trident_pcm_cvol_control, trident)) == NULL)
+		goto __nomem;
+	if ((err = snd_ctl_add(card, trident->ctl_cvol)))
+		goto __out;
+
+	if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_trident_ac97_rear_control, trident))) < 0)
+			goto __out;
+		kctl->put(kctl, uctl);
+	}
+	if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018) {
+
+		kctl = snd_ctl_new1(&snd_trident_spdif_control, trident);
+		if (kctl == NULL) {
+			err = -ENOMEM;
+			goto __out;
+		}
+		if (trident->ac97->ext_id & AC97_EI_SPDIF)
+			kctl->id.index++;
+		if (trident->ac97_sec && (trident->ac97_sec->ext_id & AC97_EI_SPDIF))
+			kctl->id.index++;
+		idx = kctl->id.index;
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			goto __out;
+		kctl->put(kctl, uctl);
+
+		kctl = snd_ctl_new1(&snd_trident_spdif_default, trident);
+		if (kctl == NULL) {
+			err = -ENOMEM;
+			goto __out;
+		}
+		kctl->id.index = idx;
+		kctl->id.device = pcm_spdif_device;
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			goto __out;
+
+		kctl = snd_ctl_new1(&snd_trident_spdif_mask, trident);
+		if (kctl == NULL) {
+			err = -ENOMEM;
+			goto __out;
+		}
+		kctl->id.index = idx;
+		kctl->id.device = pcm_spdif_device;
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			goto __out;
+
+		kctl = snd_ctl_new1(&snd_trident_spdif_stream, trident);
+		if (kctl == NULL) {
+			err = -ENOMEM;
+			goto __out;
+		}
+		kctl->id.index = idx;
+		kctl->id.device = pcm_spdif_device;
+		if ((err = snd_ctl_add(card, kctl)) < 0)
+			goto __out;
+		trident->spdif_pcm_ctl = kctl;
+	}
+
+	err = 0;
+	goto __out;
+
+ __nomem:
+	err = -ENOMEM;
+
+ __out:
+	kfree(uctl);
+
+	return err;
+}
+
+/*
+ * gameport interface
+ */
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+
+static unsigned char snd_trident_gameport_read(struct gameport *gameport)
+{
+	struct snd_trident *chip = gameport_get_port_data(gameport);
+
+	if (snd_BUG_ON(!chip))
+		return 0;
+	return inb(TRID_REG(chip, GAMEPORT_LEGACY));
+}
+
+static void snd_trident_gameport_trigger(struct gameport *gameport)
+{
+	struct snd_trident *chip = gameport_get_port_data(gameport);
+
+	if (snd_BUG_ON(!chip))
+		return;
+	outb(0xff, TRID_REG(chip, GAMEPORT_LEGACY));
+}
+
+static int snd_trident_gameport_cooked_read(struct gameport *gameport, int *axes, int *buttons)
+{
+	struct snd_trident *chip = gameport_get_port_data(gameport);
+	int i;
+
+	if (snd_BUG_ON(!chip))
+		return 0;
+
+	*buttons = (~inb(TRID_REG(chip, GAMEPORT_LEGACY)) >> 4) & 0xf;
+
+	for (i = 0; i < 4; i++) {
+		axes[i] = inw(TRID_REG(chip, GAMEPORT_AXES + i * 2));
+		if (axes[i] == 0xffff) axes[i] = -1;
+	}
+        
+        return 0;
+}
+
+static int snd_trident_gameport_open(struct gameport *gameport, int mode)
+{
+	struct snd_trident *chip = gameport_get_port_data(gameport);
+
+	if (snd_BUG_ON(!chip))
+		return 0;
+
+	switch (mode) {
+		case GAMEPORT_MODE_COOKED:
+			outb(GAMEPORT_MODE_ADC, TRID_REG(chip, GAMEPORT_GCR));
+			msleep(20);
+			return 0;
+		case GAMEPORT_MODE_RAW:
+			outb(0, TRID_REG(chip, GAMEPORT_GCR));
+			return 0;
+		default:
+			return -1;
+	}
+}
+
+int __devinit snd_trident_create_gameport(struct snd_trident *chip)
+{
+	struct gameport *gp;
+
+	chip->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "trident: cannot allocate memory for gameport\n");
+		return -ENOMEM;
+	}
+
+	gameport_set_name(gp, "Trident 4DWave");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
+	gameport_set_dev_parent(gp, &chip->pci->dev);
+
+	gameport_set_port_data(gp, chip);
+	gp->fuzz = 64;
+	gp->read = snd_trident_gameport_read;
+	gp->trigger = snd_trident_gameport_trigger;
+	gp->cooked_read = snd_trident_gameport_cooked_read;
+	gp->open = snd_trident_gameport_open;
+
+	gameport_register_port(gp);
+
+	return 0;
+}
+
+static inline void snd_trident_free_gameport(struct snd_trident *chip)
+{
+	if (chip->gameport) {
+		gameport_unregister_port(chip->gameport);
+		chip->gameport = NULL;
+	}
+}
+#else
+int __devinit snd_trident_create_gameport(struct snd_trident *chip) { return -ENOSYS; }
+static inline void snd_trident_free_gameport(struct snd_trident *chip) { }
+#endif /* CONFIG_GAMEPORT */
+
+/*
+ * delay for 1 tick
+ */
+static inline void do_delay(struct snd_trident *chip)
+{
+	schedule_timeout_uninterruptible(1);
+}
+
+/*
+ *  SiS reset routine
+ */
+
+static int snd_trident_sis_reset(struct snd_trident *trident)
+{
+	unsigned long end_time;
+	unsigned int i;
+	int r;
+
+	r = trident->in_suspend ? 0 : 2;	/* count of retries */
+      __si7018_retry:
+	pci_write_config_byte(trident->pci, 0x46, 0x04);	/* SOFTWARE RESET */
+	udelay(100);
+	pci_write_config_byte(trident->pci, 0x46, 0x00);
+	udelay(100);
+	/* disable AC97 GPIO interrupt */
+	outb(0x00, TRID_REG(trident, SI_AC97_GPIO));
+	/* initialize serial interface, force cold reset */
+	i = PCMOUT|SURROUT|CENTEROUT|LFEOUT|SECONDARY_ID|COLD_RESET;
+	outl(i, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+	udelay(1000);
+	/* remove cold reset */
+	i &= ~COLD_RESET;
+	outl(i, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+	udelay(2000);
+	/* wait, until the codec is ready */
+	end_time = (jiffies + (HZ * 3) / 4) + 1;
+	do {
+		if ((inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_PRIMARY_READY) != 0)
+			goto __si7018_ok;
+		do_delay(trident);
+	} while (time_after_eq(end_time, jiffies));
+	snd_printk(KERN_ERR "AC'97 codec ready error [0x%x]\n", inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)));
+	if (r-- > 0) {
+		end_time = jiffies + HZ;
+		do {
+			do_delay(trident);
+		} while (time_after_eq(end_time, jiffies));
+		goto __si7018_retry;
+	}
+      __si7018_ok:
+	/* wait for the second codec */
+	do {
+		if ((inl(TRID_REG(trident, SI_SERIAL_INTF_CTRL)) & SI_AC97_SECONDARY_READY) != 0)
+			break;
+		do_delay(trident);
+	} while (time_after_eq(end_time, jiffies));
+	/* enable 64 channel mode */
+	outl(BANK_B_EN, TRID_REG(trident, T4D_LFO_GC_CIR));
+	return 0;
+}
+
+/*  
+ *  /proc interface
+ */
+
+static void snd_trident_proc_read(struct snd_info_entry *entry, 
+				  struct snd_info_buffer *buffer)
+{
+	struct snd_trident *trident = entry->private_data;
+	char *s;
+
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_SI7018:
+		s = "SiS 7018 Audio";
+		break;
+	case TRIDENT_DEVICE_ID_DX:
+		s = "Trident 4DWave PCI DX";
+		break;
+	case TRIDENT_DEVICE_ID_NX:
+		s = "Trident 4DWave PCI NX";
+		break;
+	default:
+		s = "???";
+	}
+	snd_iprintf(buffer, "%s\n\n", s);
+	snd_iprintf(buffer, "Spurious IRQs    : %d\n", trident->spurious_irq_count);
+	snd_iprintf(buffer, "Spurious IRQ dlta: %d\n", trident->spurious_irq_max_delta);
+	if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018)
+		snd_iprintf(buffer, "IEC958 Mixer Out : %s\n", trident->spdif_ctrl == 0x28 ? "on" : "off");
+	if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		snd_iprintf(buffer, "Rear Speakers    : %s\n", trident->ac97_ctrl & 0x00000010 ? "on" : "off");
+		if (trident->tlb.entries) {
+			snd_iprintf(buffer,"\nVirtual Memory\n");
+			snd_iprintf(buffer, "Memory Maximum : %d\n", trident->tlb.memhdr->size);
+			snd_iprintf(buffer, "Memory Used    : %d\n", trident->tlb.memhdr->used);
+			snd_iprintf(buffer, "Memory Free    : %d\n", snd_util_mem_avail(trident->tlb.memhdr));
+		}
+	}
+}
+
+static void __devinit snd_trident_proc_init(struct snd_trident * trident)
+{
+	struct snd_info_entry *entry;
+	const char *s = "trident";
+	
+	if (trident->device == TRIDENT_DEVICE_ID_SI7018)
+		s = "sis7018";
+	if (! snd_card_proc_new(trident->card, s, &entry))
+		snd_info_set_text_ops(entry, trident, snd_trident_proc_read);
+}
+
+static int snd_trident_dev_free(struct snd_device *device)
+{
+	struct snd_trident *trident = device->device_data;
+	return snd_trident_free(trident);
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_tlb_alloc
+  
+   Description: Allocate and set up the TLB page table on 4D NX.
+		Each entry has 4 bytes (physical PCI address).
+                
+   Parameters:  trident - pointer to target device class for 4DWave.
+
+   Returns:     0 or negative error code
+  
+  ---------------------------------------------------------------------------*/
+
+static int __devinit snd_trident_tlb_alloc(struct snd_trident *trident)
+{
+	int i;
+
+	/* TLB array must be aligned to 16kB !!! so we allocate
+	   32kB region and correct offset when necessary */
+
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci),
+				2 * SNDRV_TRIDENT_MAX_PAGES * 4, &trident->tlb.buffer) < 0) {
+		snd_printk(KERN_ERR "trident: unable to allocate TLB buffer\n");
+		return -ENOMEM;
+	}
+	trident->tlb.entries = (unsigned int*)ALIGN((unsigned long)trident->tlb.buffer.area, SNDRV_TRIDENT_MAX_PAGES * 4);
+	trident->tlb.entries_dmaaddr = ALIGN(trident->tlb.buffer.addr, SNDRV_TRIDENT_MAX_PAGES * 4);
+	/* allocate shadow TLB page table (virtual addresses) */
+	trident->tlb.shadow_entries = vmalloc(SNDRV_TRIDENT_MAX_PAGES*sizeof(unsigned long));
+	if (trident->tlb.shadow_entries == NULL) {
+		snd_printk(KERN_ERR "trident: unable to allocate shadow TLB entries\n");
+		return -ENOMEM;
+	}
+	/* allocate and setup silent page and initialise TLB entries */
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci),
+				SNDRV_TRIDENT_PAGE_SIZE, &trident->tlb.silent_page) < 0) {
+		snd_printk(KERN_ERR "trident: unable to allocate silent page\n");
+		return -ENOMEM;
+	}
+	memset(trident->tlb.silent_page.area, 0, SNDRV_TRIDENT_PAGE_SIZE);
+	for (i = 0; i < SNDRV_TRIDENT_MAX_PAGES; i++) {
+		trident->tlb.entries[i] = cpu_to_le32(trident->tlb.silent_page.addr & ~(SNDRV_TRIDENT_PAGE_SIZE-1));
+		trident->tlb.shadow_entries[i] = (unsigned long)trident->tlb.silent_page.area;
+	}
+
+	/* use emu memory block manager code to manage tlb page allocation */
+	trident->tlb.memhdr = snd_util_memhdr_new(SNDRV_TRIDENT_PAGE_SIZE * SNDRV_TRIDENT_MAX_PAGES);
+	if (trident->tlb.memhdr == NULL)
+		return -ENOMEM;
+
+	trident->tlb.memhdr->block_extra_size = sizeof(struct snd_trident_memblk_arg);
+	return 0;
+}
+
+/*
+ * initialize 4D DX chip
+ */
+
+static void snd_trident_stop_all_voices(struct snd_trident *trident)
+{
+	outl(0xffffffff, TRID_REG(trident, T4D_STOP_A));
+	outl(0xffffffff, TRID_REG(trident, T4D_STOP_B));
+	outl(0, TRID_REG(trident, T4D_AINTEN_A));
+	outl(0, TRID_REG(trident, T4D_AINTEN_B));
+}
+
+static int snd_trident_4d_dx_init(struct snd_trident *trident)
+{
+	struct pci_dev *pci = trident->pci;
+	unsigned long end_time;
+
+	/* reset the legacy configuration and whole audio/wavetable block */
+	pci_write_config_dword(pci, 0x40, 0);	/* DDMA */
+	pci_write_config_byte(pci, 0x44, 0);	/* ports */
+	pci_write_config_byte(pci, 0x45, 0);	/* Legacy DMA */
+	pci_write_config_byte(pci, 0x46, 4); /* reset */
+	udelay(100);
+	pci_write_config_byte(pci, 0x46, 0); /* release reset */
+	udelay(100);
+	
+	/* warm reset of the AC'97 codec */
+	outl(0x00000001, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+	udelay(100);
+	outl(0x00000000, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+	/* DAC on, disable SB IRQ and try to force ADC valid signal */
+	trident->ac97_ctrl = 0x0000004a;
+	outl(trident->ac97_ctrl, TRID_REG(trident, DX_ACR2_AC97_COM_STAT));
+	/* wait, until the codec is ready */
+	end_time = (jiffies + (HZ * 3) / 4) + 1;
+	do {
+		if ((inl(TRID_REG(trident, DX_ACR2_AC97_COM_STAT)) & 0x0010) != 0)
+			goto __dx_ok;
+		do_delay(trident);
+	} while (time_after_eq(end_time, jiffies));
+	snd_printk(KERN_ERR "AC'97 codec ready error\n");
+	return -EIO;
+
+ __dx_ok:
+	snd_trident_stop_all_voices(trident);
+
+	return 0;
+}
+
+/*
+ * initialize 4D NX chip
+ */
+static int snd_trident_4d_nx_init(struct snd_trident *trident)
+{
+	struct pci_dev *pci = trident->pci;
+	unsigned long end_time;
+
+	/* reset the legacy configuration and whole audio/wavetable block */
+	pci_write_config_dword(pci, 0x40, 0);	/* DDMA */
+	pci_write_config_byte(pci, 0x44, 0);	/* ports */
+	pci_write_config_byte(pci, 0x45, 0);	/* Legacy DMA */
+
+	pci_write_config_byte(pci, 0x46, 1); /* reset */
+	udelay(100);
+	pci_write_config_byte(pci, 0x46, 0); /* release reset */
+	udelay(100);
+
+	/* warm reset of the AC'97 codec */
+	outl(0x00000001, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	udelay(100);
+	outl(0x00000000, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	/* wait, until the codec is ready */
+	end_time = (jiffies + (HZ * 3) / 4) + 1;
+	do {
+		if ((inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)) & 0x0008) != 0)
+			goto __nx_ok;
+		do_delay(trident);
+	} while (time_after_eq(end_time, jiffies));
+	snd_printk(KERN_ERR "AC'97 codec ready error [0x%x]\n", inl(TRID_REG(trident, NX_ACR0_AC97_COM_STAT)));
+	return -EIO;
+
+ __nx_ok:
+	/* DAC on */
+	trident->ac97_ctrl = 0x00000002;
+	outl(trident->ac97_ctrl, TRID_REG(trident, NX_ACR0_AC97_COM_STAT));
+	/* disable SB IRQ */
+	outl(NX_SB_IRQ_DISABLE, TRID_REG(trident, T4D_MISCINT));
+
+	snd_trident_stop_all_voices(trident);
+
+	if (trident->tlb.entries != NULL) {
+		unsigned int i;
+		/* enable virtual addressing via TLB */
+		i = trident->tlb.entries_dmaaddr;
+		i |= 0x00000001;
+		outl(i, TRID_REG(trident, NX_TLBC));
+	} else {
+		outl(0, TRID_REG(trident, NX_TLBC));
+	}
+	/* initialize S/PDIF */
+	outl(trident->spdif_bits, TRID_REG(trident, NX_SPCSTATUS));
+	outb(trident->spdif_ctrl, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+
+	return 0;
+}
+
+/*
+ * initialize sis7018 chip
+ */
+static int snd_trident_sis_init(struct snd_trident *trident)
+{
+	int err;
+
+	if ((err = snd_trident_sis_reset(trident)) < 0)
+		return err;
+
+	snd_trident_stop_all_voices(trident);
+
+	/* initialize S/PDIF */
+	outl(trident->spdif_bits, TRID_REG(trident, SI_SPDIF_CS));
+
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_create
+  
+   Description: This routine will create the device specific class for
+                the 4DWave card. It will also perform basic initialization.
+                
+   Parameters:  card  - which card to create
+                pci   - interface to PCI bus resource info
+                dma1ptr - playback dma buffer
+                dma2ptr - capture dma buffer
+                irqptr  -  interrupt resource info
+
+   Returns:     4DWave device class private data
+  
+  ---------------------------------------------------------------------------*/
+
+int __devinit snd_trident_create(struct snd_card *card,
+		       struct pci_dev *pci,
+		       int pcm_streams,
+		       int pcm_spdif_device,
+		       int max_wavetable_size,
+		       struct snd_trident ** rtrident)
+{
+	struct snd_trident *trident;
+	int i, err;
+	struct snd_trident_voice *voice;
+	struct snd_trident_pcm_mixer *tmix;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_trident_dev_free,
+	};
+
+	*rtrident = NULL;
+
+	/* enable PCI device */
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	/* check, if we can restrict PCI DMA transfers to 30 bits */
+	if (pci_set_dma_mask(pci, DMA_BIT_MASK(30)) < 0 ||
+	    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(30)) < 0) {
+		snd_printk(KERN_ERR "architecture does not support 30bit PCI busmaster DMA\n");
+		pci_disable_device(pci);
+		return -ENXIO;
+	}
+	
+	trident = kzalloc(sizeof(*trident), GFP_KERNEL);
+	if (trident == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	trident->device = (pci->vendor << 16) | pci->device;
+	trident->card = card;
+	trident->pci = pci;
+	spin_lock_init(&trident->reg_lock);
+	spin_lock_init(&trident->event_lock);
+	spin_lock_init(&trident->voice_alloc);
+	if (pcm_streams < 1)
+		pcm_streams = 1;
+	if (pcm_streams > 32)
+		pcm_streams = 32;
+	trident->ChanPCM = pcm_streams;
+	if (max_wavetable_size < 0 )
+		max_wavetable_size = 0;
+	trident->synth.max_size = max_wavetable_size * 1024;
+	trident->irq = -1;
+
+	trident->midi_port = TRID_REG(trident, T4D_MPU401_BASE);
+	pci_set_master(pci);
+
+	if ((err = pci_request_regions(pci, "Trident Audio")) < 0) {
+		kfree(trident);
+		pci_disable_device(pci);
+		return err;
+	}
+	trident->port = pci_resource_start(pci, 0);
+
+	if (request_irq(pci->irq, snd_trident_interrupt, IRQF_SHARED,
+			"Trident Audio", trident)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_trident_free(trident);
+		return -EBUSY;
+	}
+	trident->irq = pci->irq;
+
+	/* allocate 16k-aligned TLB for NX cards */
+	trident->tlb.entries = NULL;
+	trident->tlb.buffer.area = NULL;
+	if (trident->device == TRIDENT_DEVICE_ID_NX) {
+		if ((err = snd_trident_tlb_alloc(trident)) < 0) {
+			snd_trident_free(trident);
+			return err;
+		}
+	}
+
+	trident->spdif_bits = trident->spdif_pcm_bits = SNDRV_PCM_DEFAULT_CON_SPDIF;
+
+	/* initialize chip */
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:
+		err = snd_trident_4d_dx_init(trident);
+		break;
+	case TRIDENT_DEVICE_ID_NX:
+		err = snd_trident_4d_nx_init(trident);
+		break;
+	case TRIDENT_DEVICE_ID_SI7018:
+		err = snd_trident_sis_init(trident);
+		break;
+	default:
+		snd_BUG();
+		break;
+	}
+	if (err < 0) {
+		snd_trident_free(trident);
+		return err;
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, trident, &ops)) < 0) {
+		snd_trident_free(trident);
+		return err;
+	}
+
+	if ((err = snd_trident_mixer(trident, pcm_spdif_device)) < 0)
+		return err;
+	
+	/* initialise synth voices */
+	for (i = 0; i < 64; i++) {
+		voice = &trident->synth.voices[i];
+		voice->number = i;
+		voice->trident = trident;
+	}
+	/* initialize pcm mixer entries */
+	for (i = 0; i < 32; i++) {
+		tmix = &trident->pcm_mixer[i];
+		tmix->vol = T4D_DEFAULT_PCM_VOL;
+		tmix->pan = T4D_DEFAULT_PCM_PAN;
+		tmix->rvol = T4D_DEFAULT_PCM_RVOL;
+		tmix->cvol = T4D_DEFAULT_PCM_CVOL;
+	}
+
+	snd_trident_enable_eso(trident);
+
+	snd_trident_proc_init(trident);
+	snd_card_set_dev(card, &pci->dev);
+	*rtrident = trident;
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_free
+  
+   Description: This routine will free the device specific class for
+                the 4DWave card. 
+                
+   Parameters:  trident  - device specific private data for 4DWave card
+
+   Returns:     None.
+  
+  ---------------------------------------------------------------------------*/
+
+static int snd_trident_free(struct snd_trident *trident)
+{
+	snd_trident_free_gameport(trident);
+	snd_trident_disable_eso(trident);
+	// Disable S/PDIF out
+	if (trident->device == TRIDENT_DEVICE_ID_NX)
+		outb(0x00, TRID_REG(trident, NX_SPCTRL_SPCSO + 3));
+	else if (trident->device == TRIDENT_DEVICE_ID_SI7018) {
+		outl(0, TRID_REG(trident, SI_SERIAL_INTF_CTRL));
+	}
+	if (trident->irq >= 0)
+		free_irq(trident->irq, trident);
+	if (trident->tlb.buffer.area) {
+		outl(0, TRID_REG(trident, NX_TLBC));
+		if (trident->tlb.memhdr)
+			snd_util_memhdr_free(trident->tlb.memhdr);
+		if (trident->tlb.silent_page.area)
+			snd_dma_free_pages(&trident->tlb.silent_page);
+		vfree(trident->tlb.shadow_entries);
+		snd_dma_free_pages(&trident->tlb.buffer);
+	}
+	pci_release_regions(trident->pci);
+	pci_disable_device(trident->pci);
+	kfree(trident);
+	return 0;
+}
+
+/*---------------------------------------------------------------------------
+   snd_trident_interrupt
+  
+   Description: ISR for Trident 4DWave device
+                
+   Parameters:  trident  - device specific private data for 4DWave card
+
+   Problems:    It seems that Trident chips generates interrupts more than
+                one time in special cases. The spurious interrupts are
+                detected via sample timer (T4D_STIMER) and computing
+                corresponding delta value. The limits are detected with
+                the method try & fail so it is possible that it won't
+                work on all computers. [jaroslav]
+
+   Returns:     None.
+  
+  ---------------------------------------------------------------------------*/
+
+static irqreturn_t snd_trident_interrupt(int irq, void *dev_id)
+{
+	struct snd_trident *trident = dev_id;
+	unsigned int audio_int, chn_int, stimer, channel, mask, tmp;
+	int delta;
+	struct snd_trident_voice *voice;
+
+	audio_int = inl(TRID_REG(trident, T4D_MISCINT));
+	if ((audio_int & (ADDRESS_IRQ|MPU401_IRQ)) == 0)
+		return IRQ_NONE;
+	if (audio_int & ADDRESS_IRQ) {
+		// get interrupt status for all channels
+		spin_lock(&trident->reg_lock);
+		stimer = inl(TRID_REG(trident, T4D_STIMER)) & 0x00ffffff;
+		chn_int = inl(TRID_REG(trident, T4D_AINT_A));
+		if (chn_int == 0)
+			goto __skip1;
+		outl(chn_int, TRID_REG(trident, T4D_AINT_A));	/* ack */
+	      __skip1:
+		chn_int = inl(TRID_REG(trident, T4D_AINT_B));
+		if (chn_int == 0)
+			goto __skip2;
+		for (channel = 63; channel >= 32; channel--) {
+			mask = 1 << (channel&0x1f);
+			if ((chn_int & mask) == 0)
+				continue;
+			voice = &trident->synth.voices[channel];
+			if (!voice->pcm || voice->substream == NULL) {
+				outl(mask, TRID_REG(trident, T4D_STOP_B));
+				continue;
+			}
+			delta = (int)stimer - (int)voice->stimer;
+			if (delta < 0)
+				delta = -delta;
+			if ((unsigned int)delta < voice->spurious_threshold) {
+				/* do some statistics here */
+				trident->spurious_irq_count++;
+				if (trident->spurious_irq_max_delta < (unsigned int)delta)
+					trident->spurious_irq_max_delta = delta;
+				continue;
+			}
+			voice->stimer = stimer;
+			if (voice->isync) {
+				if (!voice->isync3) {
+					tmp = inw(TRID_REG(trident, T4D_SBBL_SBCL));
+					if (trident->bDMAStart & 0x40)
+						tmp >>= 1;
+					if (tmp > 0)
+						tmp = voice->isync_max - tmp;
+				} else {
+					tmp = inl(TRID_REG(trident, NX_SPCTRL_SPCSO)) & 0x00ffffff;
+				}
+				if (tmp < voice->isync_mark) {
+					if (tmp > 0x10)
+						tmp = voice->isync_ESO - 7;
+					else
+						tmp = voice->isync_ESO + 2;
+					/* update ESO for IRQ voice to preserve sync */
+					snd_trident_stop_voice(trident, voice->number);
+					snd_trident_write_eso_reg(trident, voice, tmp);
+					snd_trident_start_voice(trident, voice->number);
+				}
+			} else if (voice->isync2) {
+				voice->isync2 = 0;
+				/* write original ESO and update CSO for IRQ voice to preserve sync */
+				snd_trident_stop_voice(trident, voice->number);
+				snd_trident_write_cso_reg(trident, voice, voice->isync_mark);
+				snd_trident_write_eso_reg(trident, voice, voice->ESO);
+				snd_trident_start_voice(trident, voice->number);
+			}
+#if 0
+			if (voice->extra) {
+				/* update CSO for extra voice to preserve sync */
+				snd_trident_stop_voice(trident, voice->extra->number);
+				snd_trident_write_cso_reg(trident, voice->extra, 0);
+				snd_trident_start_voice(trident, voice->extra->number);
+			}
+#endif
+			spin_unlock(&trident->reg_lock);
+			snd_pcm_period_elapsed(voice->substream);
+			spin_lock(&trident->reg_lock);
+		}
+		outl(chn_int, TRID_REG(trident, T4D_AINT_B));	/* ack */
+	      __skip2:
+		spin_unlock(&trident->reg_lock);
+	}
+	if (audio_int & MPU401_IRQ) {
+		if (trident->rmidi) {
+			snd_mpu401_uart_interrupt(irq, trident->rmidi->private_data);
+		} else {
+			inb(TRID_REG(trident, T4D_MPUR0));
+		}
+	}
+	// outl((ST_TARGET_REACHED | MIXER_OVERFLOW | MIXER_UNDERFLOW), TRID_REG(trident, T4D_MISCINT));
+	return IRQ_HANDLED;
+}
+
+struct snd_trident_voice *snd_trident_alloc_voice(struct snd_trident * trident, int type, int client, int port)
+{
+	struct snd_trident_voice *pvoice;
+	unsigned long flags;
+	int idx;
+
+	spin_lock_irqsave(&trident->voice_alloc, flags);
+	if (type == SNDRV_TRIDENT_VOICE_TYPE_PCM) {
+		idx = snd_trident_allocate_pcm_channel(trident);
+		if(idx < 0) {
+			spin_unlock_irqrestore(&trident->voice_alloc, flags);
+			return NULL;
+		}
+		pvoice = &trident->synth.voices[idx];
+		pvoice->use = 1;
+		pvoice->pcm = 1;
+		pvoice->capture = 0;
+		pvoice->spdif = 0;
+		pvoice->memblk = NULL;
+		pvoice->substream = NULL;
+		spin_unlock_irqrestore(&trident->voice_alloc, flags);
+		return pvoice;
+	}
+	if (type == SNDRV_TRIDENT_VOICE_TYPE_SYNTH) {
+		idx = snd_trident_allocate_synth_channel(trident);
+		if(idx < 0) {
+			spin_unlock_irqrestore(&trident->voice_alloc, flags);
+			return NULL;
+		}
+		pvoice = &trident->synth.voices[idx];
+		pvoice->use = 1;
+		pvoice->synth = 1;
+		pvoice->client = client;
+		pvoice->port = port;
+		pvoice->memblk = NULL;
+		spin_unlock_irqrestore(&trident->voice_alloc, flags);
+		return pvoice;
+	}
+	if (type == SNDRV_TRIDENT_VOICE_TYPE_MIDI) {
+	}
+	spin_unlock_irqrestore(&trident->voice_alloc, flags);
+	return NULL;
+}
+
+EXPORT_SYMBOL(snd_trident_alloc_voice);
+
+void snd_trident_free_voice(struct snd_trident * trident, struct snd_trident_voice *voice)
+{
+	unsigned long flags;
+	void (*private_free)(struct snd_trident_voice *);
+	void *private_data;
+
+	if (voice == NULL || !voice->use)
+		return;
+	snd_trident_clear_voices(trident, voice->number, voice->number);
+	spin_lock_irqsave(&trident->voice_alloc, flags);
+	private_free = voice->private_free;
+	private_data = voice->private_data;
+	voice->private_free = NULL;
+	voice->private_data = NULL;
+	if (voice->pcm)
+		snd_trident_free_pcm_channel(trident, voice->number);
+	if (voice->synth)
+		snd_trident_free_synth_channel(trident, voice->number);
+	voice->use = voice->pcm = voice->synth = voice->midi = 0;
+	voice->capture = voice->spdif = 0;
+	voice->sample_ops = NULL;
+	voice->substream = NULL;
+	voice->extra = NULL;
+	spin_unlock_irqrestore(&trident->voice_alloc, flags);
+	if (private_free)
+		private_free(voice);
+}
+
+EXPORT_SYMBOL(snd_trident_free_voice);
+
+static void snd_trident_clear_voices(struct snd_trident * trident, unsigned short v_min, unsigned short v_max)
+{
+	unsigned int i, val, mask[2] = { 0, 0 };
+
+	if (snd_BUG_ON(v_min > 63 || v_max > 63))
+		return;
+	for (i = v_min; i <= v_max; i++)
+		mask[i >> 5] |= 1 << (i & 0x1f);
+	if (mask[0]) {
+		outl(mask[0], TRID_REG(trident, T4D_STOP_A));
+		val = inl(TRID_REG(trident, T4D_AINTEN_A));
+		outl(val & ~mask[0], TRID_REG(trident, T4D_AINTEN_A));
+	}
+	if (mask[1]) {
+		outl(mask[1], TRID_REG(trident, T4D_STOP_B));
+		val = inl(TRID_REG(trident, T4D_AINTEN_B));
+		outl(val & ~mask[1], TRID_REG(trident, T4D_AINTEN_B));
+	}
+}
+
+#ifdef CONFIG_PM
+int snd_trident_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_trident *trident = card->private_data;
+
+	trident->in_suspend = 1;
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(trident->pcm);
+	snd_pcm_suspend_all(trident->foldback);
+	snd_pcm_suspend_all(trident->spdif);
+
+	snd_ac97_suspend(trident->ac97);
+	snd_ac97_suspend(trident->ac97_sec);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+int snd_trident_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_trident *trident = card->private_data;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "trident: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	switch (trident->device) {
+	case TRIDENT_DEVICE_ID_DX:
+		snd_trident_4d_dx_init(trident);
+		break;
+	case TRIDENT_DEVICE_ID_NX:
+		snd_trident_4d_nx_init(trident);
+		break;
+	case TRIDENT_DEVICE_ID_SI7018:
+		snd_trident_sis_init(trident);
+		break;
+	}
+
+	snd_ac97_resume(trident->ac97);
+	snd_ac97_resume(trident->ac97_sec);
+
+	/* restore some registers */
+	outl(trident->musicvol_wavevol, TRID_REG(trident, T4D_MUSICVOL_WAVEVOL));
+
+	snd_trident_enable_eso(trident);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	trident->in_suspend = 0;
+	return 0;
+}
+#endif /* CONFIG_PM */
diff --git a/linux/sound/pci/trident/trident_memory.c b/linux/sound/pci/trident/trident_memory.c
new file mode 100644
index 0000000..f9779e2
--- /dev/null
+++ b/linux/sound/pci/trident/trident_memory.c
@@ -0,0 +1,315 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ *  Copyright (c) by Scott McNab <sdm@fractalgraphics.com.au>
+ *
+ *  Trident 4DWave-NX memory page allocation (TLB area)
+ *  Trident chip can handle only 16MByte of the memory at the same time.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/trident.h>
+
+/* page arguments of these two macros are Trident page (4096 bytes), not like
+ * aligned pages in others
+ */
+#define __set_tlb_bus(trident,page,ptr,addr) \
+	do { (trident)->tlb.entries[page] = cpu_to_le32((addr) & ~(SNDRV_TRIDENT_PAGE_SIZE-1)); \
+	     (trident)->tlb.shadow_entries[page] = (ptr); } while (0)
+#define __tlb_to_ptr(trident,page) \
+	(void*)((trident)->tlb.shadow_entries[page])
+#define __tlb_to_addr(trident,page) \
+	(dma_addr_t)le32_to_cpu((trident->tlb.entries[page]) & ~(SNDRV_TRIDENT_PAGE_SIZE - 1))
+
+#if PAGE_SIZE == 4096
+/* page size == SNDRV_TRIDENT_PAGE_SIZE */
+#define ALIGN_PAGE_SIZE		PAGE_SIZE	/* minimum page size for allocation */
+#define MAX_ALIGN_PAGES		SNDRV_TRIDENT_MAX_PAGES	/* maxmium aligned pages */
+/* fill TLB entrie(s) corresponding to page with ptr */
+#define set_tlb_bus(trident,page,ptr,addr) __set_tlb_bus(trident,page,ptr,addr)
+/* fill TLB entrie(s) corresponding to page with silence pointer */
+#define set_silent_tlb(trident,page)	__set_tlb_bus(trident, page, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr)
+/* get aligned page from offset address */
+#define get_aligned_page(offset)	((offset) >> 12)
+/* get offset address from aligned page */
+#define aligned_page_offset(page)	((page) << 12)
+/* get buffer address from aligned page */
+#define page_to_ptr(trident,page)	__tlb_to_ptr(trident, page)
+/* get PCI physical address from aligned page */
+#define page_to_addr(trident,page)	__tlb_to_addr(trident, page)
+
+#elif PAGE_SIZE == 8192
+/* page size == SNDRV_TRIDENT_PAGE_SIZE x 2*/
+#define ALIGN_PAGE_SIZE		PAGE_SIZE
+#define MAX_ALIGN_PAGES		(SNDRV_TRIDENT_MAX_PAGES / 2)
+#define get_aligned_page(offset)	((offset) >> 13)
+#define aligned_page_offset(page)	((page) << 13)
+#define page_to_ptr(trident,page)	__tlb_to_ptr(trident, (page) << 1)
+#define page_to_addr(trident,page)	__tlb_to_addr(trident, (page) << 1)
+
+/* fill TLB entries -- we need to fill two entries */
+static inline void set_tlb_bus(struct snd_trident *trident, int page,
+			       unsigned long ptr, dma_addr_t addr)
+{
+	page <<= 1;
+	__set_tlb_bus(trident, page, ptr, addr);
+	__set_tlb_bus(trident, page+1, ptr + SNDRV_TRIDENT_PAGE_SIZE, addr + SNDRV_TRIDENT_PAGE_SIZE);
+}
+static inline void set_silent_tlb(struct snd_trident *trident, int page)
+{
+	page <<= 1;
+	__set_tlb_bus(trident, page, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr);
+	__set_tlb_bus(trident, page+1, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr);
+}
+
+#else
+/* arbitrary size */
+#define UNIT_PAGES		(PAGE_SIZE / SNDRV_TRIDENT_PAGE_SIZE)
+#define ALIGN_PAGE_SIZE		(SNDRV_TRIDENT_PAGE_SIZE * UNIT_PAGES)
+#define MAX_ALIGN_PAGES		(SNDRV_TRIDENT_MAX_PAGES / UNIT_PAGES)
+/* Note: if alignment doesn't match to the maximum size, the last few blocks
+ * become unusable.  To use such blocks, you'll need to check the validity
+ * of accessing page in set_tlb_bus and set_silent_tlb.  search_empty()
+ * should also check it, too.
+ */
+#define get_aligned_page(offset)	((offset) / ALIGN_PAGE_SIZE)
+#define aligned_page_offset(page)	((page) * ALIGN_PAGE_SIZE)
+#define page_to_ptr(trident,page)	__tlb_to_ptr(trident, (page) * UNIT_PAGES)
+#define page_to_addr(trident,page)	__tlb_to_addr(trident, (page) * UNIT_PAGES)
+
+/* fill TLB entries -- UNIT_PAGES entries must be filled */
+static inline void set_tlb_bus(struct snd_trident *trident, int page,
+			       unsigned long ptr, dma_addr_t addr)
+{
+	int i;
+	page *= UNIT_PAGES;
+	for (i = 0; i < UNIT_PAGES; i++, page++) {
+		__set_tlb_bus(trident, page, ptr, addr);
+		ptr += SNDRV_TRIDENT_PAGE_SIZE;
+		addr += SNDRV_TRIDENT_PAGE_SIZE;
+	}
+}
+static inline void set_silent_tlb(struct snd_trident *trident, int page)
+{
+	int i;
+	page *= UNIT_PAGES;
+	for (i = 0; i < UNIT_PAGES; i++, page++)
+		__set_tlb_bus(trident, page, (unsigned long)trident->tlb.silent_page.area, trident->tlb.silent_page.addr);
+}
+
+#endif /* PAGE_SIZE */
+
+/* calculate buffer pointer from offset address */
+static inline void *offset_ptr(struct snd_trident *trident, int offset)
+{
+	char *ptr;
+	ptr = page_to_ptr(trident, get_aligned_page(offset));
+	ptr += offset % ALIGN_PAGE_SIZE;
+	return (void*)ptr;
+}
+
+/* first and last (aligned) pages of memory block */
+#define firstpg(blk)	(((struct snd_trident_memblk_arg *)snd_util_memblk_argptr(blk))->first_page)
+#define lastpg(blk)	(((struct snd_trident_memblk_arg *)snd_util_memblk_argptr(blk))->last_page)
+
+/*
+ * search empty pages which may contain given size
+ */
+static struct snd_util_memblk *
+search_empty(struct snd_util_memhdr *hdr, int size)
+{
+	struct snd_util_memblk *blk, *prev;
+	int page, psize;
+	struct list_head *p;
+
+	psize = get_aligned_page(size + ALIGN_PAGE_SIZE -1);
+	prev = NULL;
+	page = 0;
+	list_for_each(p, &hdr->block) {
+		blk = list_entry(p, struct snd_util_memblk, list);
+		if (page + psize <= firstpg(blk))
+			goto __found_pages;
+		page = lastpg(blk) + 1;
+	}
+	if (page + psize > MAX_ALIGN_PAGES)
+		return NULL;
+
+__found_pages:
+	/* create a new memory block */
+	blk = __snd_util_memblk_new(hdr, psize * ALIGN_PAGE_SIZE, p->prev);
+	if (blk == NULL)
+		return NULL;
+	blk->offset = aligned_page_offset(page); /* set aligned offset */
+	firstpg(blk) = page;
+	lastpg(blk) = page + psize - 1;
+	return blk;
+}
+
+
+/*
+ * check if the given pointer is valid for pages
+ */
+static int is_valid_page(unsigned long ptr)
+{
+	if (ptr & ~0x3fffffffUL) {
+		snd_printk(KERN_ERR "max memory size is 1GB!!\n");
+		return 0;
+	}
+	if (ptr & (SNDRV_TRIDENT_PAGE_SIZE-1)) {
+		snd_printk(KERN_ERR "page is not aligned\n");
+		return 0;
+	}
+	return 1;
+}
+
+/*
+ * page allocation for DMA (Scatter-Gather version)
+ */
+static struct snd_util_memblk *
+snd_trident_alloc_sg_pages(struct snd_trident *trident,
+			   struct snd_pcm_substream *substream)
+{
+	struct snd_util_memhdr *hdr;
+	struct snd_util_memblk *blk;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int idx, page;
+
+	if (snd_BUG_ON(runtime->dma_bytes <= 0 ||
+		       runtime->dma_bytes > SNDRV_TRIDENT_MAX_PAGES *
+					SNDRV_TRIDENT_PAGE_SIZE))
+		return NULL;
+	hdr = trident->tlb.memhdr;
+	if (snd_BUG_ON(!hdr))
+		return NULL;
+
+	
+
+	mutex_lock(&hdr->block_mutex);
+	blk = search_empty(hdr, runtime->dma_bytes);
+	if (blk == NULL) {
+		mutex_unlock(&hdr->block_mutex);
+		return NULL;
+	}
+			   
+	/* set TLB entries */
+	idx = 0;
+	for (page = firstpg(blk); page <= lastpg(blk); page++, idx++) {
+		unsigned long ofs = idx << PAGE_SHIFT;
+		dma_addr_t addr = snd_pcm_sgbuf_get_addr(substream, ofs);
+		unsigned long ptr = (unsigned long)
+			snd_pcm_sgbuf_get_ptr(substream, ofs);
+		if (! is_valid_page(addr)) {
+			__snd_util_mem_free(hdr, blk);
+			mutex_unlock(&hdr->block_mutex);
+			return NULL;
+		}
+		set_tlb_bus(trident, page, ptr, addr);
+	}
+	mutex_unlock(&hdr->block_mutex);
+	return blk;
+}
+
+/*
+ * page allocation for DMA (contiguous version)
+ */
+static struct snd_util_memblk *
+snd_trident_alloc_cont_pages(struct snd_trident *trident,
+			     struct snd_pcm_substream *substream)
+{
+	struct snd_util_memhdr *hdr;
+	struct snd_util_memblk *blk;
+	int page;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	dma_addr_t addr;
+	unsigned long ptr;
+
+	if (snd_BUG_ON(runtime->dma_bytes <= 0 ||
+		       runtime->dma_bytes > SNDRV_TRIDENT_MAX_PAGES *
+					SNDRV_TRIDENT_PAGE_SIZE))
+		return NULL;
+	hdr = trident->tlb.memhdr;
+	if (snd_BUG_ON(!hdr))
+		return NULL;
+
+	mutex_lock(&hdr->block_mutex);
+	blk = search_empty(hdr, runtime->dma_bytes);
+	if (blk == NULL) {
+		mutex_unlock(&hdr->block_mutex);
+		return NULL;
+	}
+			   
+	/* set TLB entries */
+	addr = runtime->dma_addr;
+	ptr = (unsigned long)runtime->dma_area;
+	for (page = firstpg(blk); page <= lastpg(blk); page++,
+	     ptr += SNDRV_TRIDENT_PAGE_SIZE, addr += SNDRV_TRIDENT_PAGE_SIZE) {
+		if (! is_valid_page(addr)) {
+			__snd_util_mem_free(hdr, blk);
+			mutex_unlock(&hdr->block_mutex);
+			return NULL;
+		}
+		set_tlb_bus(trident, page, ptr, addr);
+	}
+	mutex_unlock(&hdr->block_mutex);
+	return blk;
+}
+
+/*
+ * page allocation for DMA
+ */
+struct snd_util_memblk *
+snd_trident_alloc_pages(struct snd_trident *trident,
+			struct snd_pcm_substream *substream)
+{
+	if (snd_BUG_ON(!trident || !substream))
+		return NULL;
+	if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_SG)
+		return snd_trident_alloc_sg_pages(trident, substream);
+	else
+		return snd_trident_alloc_cont_pages(trident, substream);
+}
+
+
+/*
+ * release DMA buffer from page table
+ */
+int snd_trident_free_pages(struct snd_trident *trident,
+			   struct snd_util_memblk *blk)
+{
+	struct snd_util_memhdr *hdr;
+	int page;
+
+	if (snd_BUG_ON(!trident || !blk))
+		return -EINVAL;
+
+	hdr = trident->tlb.memhdr;
+	mutex_lock(&hdr->block_mutex);
+	/* reset TLB entries */
+	for (page = firstpg(blk); page <= lastpg(blk); page++)
+		set_silent_tlb(trident, page);
+	/* free memory block */
+	__snd_util_mem_free(hdr, blk);
+	mutex_unlock(&hdr->block_mutex);
+	return 0;
+}
diff --git a/linux/sound/pci/via82xx.c b/linux/sound/pci/via82xx.c
new file mode 100644
index 0000000..8c5f8b5
--- /dev/null
+++ b/linux/sound/pci/via82xx.c
@@ -0,0 +1,2635 @@
+/*
+ *   ALSA driver for VIA VT82xx (South Bridge)
+ *
+ *   VT82C686A/B/C, VT8233A/C, VT8235
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *	                   Tjeerd.Mulder <Tjeerd.Mulder@fujitsu-siemens.com>
+ *                    2002 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ * Changes:
+ *
+ * Dec. 19, 2002	Takashi Iwai <tiwai@suse.de>
+ *	- use the DSX channels for the first pcm playback.
+ *	  (on VIA8233, 8233C and 8235 only)
+ *	  this will allow you play simultaneously up to 4 streams.
+ *	  multi-channel playback is assigned to the second device
+ *	  on these chips.
+ *	- support the secondary capture (on VIA8233/C,8235)
+ *	- SPDIF support
+ *	  the DSX3 channel can be used for SPDIF output.
+ *	  on VIA8233A, this channel is assigned to the second pcm
+ *	  playback.
+ *	  the card config of alsa-lib will assign the correct
+ *	  device for applications.
+ *	- clean up the code, separate low-level initialization
+ *	  routines for each chipset.
+ *
+ * Sep. 26, 2005	Karsten Wiese <annabellesgarden@yahoo.de>
+ *	- Optimize position calculation for the 823x chips. 
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/gameport.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+#include <sound/ac97_codec.h>
+#include <sound/mpu401.h>
+#include <sound/initval.h>
+
+#if 0
+#define POINTER_DEBUG
+#endif
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("VIA VT82xx audio");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{VIA,VT82C686A/B/C,pci},{VIA,VT8233A/C,8235}}");
+
+#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))
+#define SUPPORT_JOYSTICK 1
+#endif
+
+static int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+static long mpu_port;
+#ifdef SUPPORT_JOYSTICK
+static int joystick;
+#endif
+static int ac97_clock = 48000;
+static char *ac97_quirk;
+static int dxs_support;
+static int dxs_init_volume = 31;
+static int nodelay;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for VIA 82xx bridge.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for VIA 82xx bridge.");
+module_param(mpu_port, long, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 port. (VT82C686x only)");
+#ifdef SUPPORT_JOYSTICK
+module_param(joystick, bool, 0444);
+MODULE_PARM_DESC(joystick, "Enable joystick. (VT82C686x only)");
+#endif
+module_param(ac97_clock, int, 0444);
+MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (default 48000Hz).");
+module_param(ac97_quirk, charp, 0444);
+MODULE_PARM_DESC(ac97_quirk, "AC'97 workaround for strange hardware.");
+module_param(dxs_support, int, 0444);
+MODULE_PARM_DESC(dxs_support, "Support for DXS channels (0 = auto, 1 = enable, 2 = disable, 3 = 48k only, 4 = no VRA, 5 = enable any sample rate)");
+module_param(dxs_init_volume, int, 0644);
+MODULE_PARM_DESC(dxs_init_volume, "initial DXS volume (0-31)");
+module_param(nodelay, int, 0444);
+MODULE_PARM_DESC(nodelay, "Disable 500ms init delay");
+
+/* just for backward compatibility */
+static int enable;
+module_param(enable, bool, 0444);
+
+
+/* revision numbers for via686 */
+#define VIA_REV_686_A		0x10
+#define VIA_REV_686_B		0x11
+#define VIA_REV_686_C		0x12
+#define VIA_REV_686_D		0x13
+#define VIA_REV_686_E		0x14
+#define VIA_REV_686_H		0x20
+
+/* revision numbers for via8233 */
+#define VIA_REV_PRE_8233	0x10	/* not in market */
+#define VIA_REV_8233C		0x20	/* 2 rec, 4 pb, 1 multi-pb */
+#define VIA_REV_8233		0x30	/* 2 rec, 4 pb, 1 multi-pb, spdif */
+#define VIA_REV_8233A		0x40	/* 1 rec, 1 multi-pb, spdf */
+#define VIA_REV_8235		0x50	/* 2 rec, 4 pb, 1 multi-pb, spdif */
+#define VIA_REV_8237		0x60
+#define VIA_REV_8251		0x70
+
+/*
+ *  Direct registers
+ */
+
+#define VIAREG(via, x) ((via)->port + VIA_REG_##x)
+#define VIADEV_REG(viadev, x) ((viadev)->port + VIA_REG_##x)
+
+/* common offsets */
+#define VIA_REG_OFFSET_STATUS		0x00	/* byte - channel status */
+#define   VIA_REG_STAT_ACTIVE		0x80	/* RO */
+#define   VIA8233_SHADOW_STAT_ACTIVE	0x08	/* RO */
+#define   VIA_REG_STAT_PAUSED		0x40	/* RO */
+#define   VIA_REG_STAT_TRIGGER_QUEUED	0x08	/* RO */
+#define   VIA_REG_STAT_STOPPED		0x04	/* RWC */
+#define   VIA_REG_STAT_EOL		0x02	/* RWC */
+#define   VIA_REG_STAT_FLAG		0x01	/* RWC */
+#define VIA_REG_OFFSET_CONTROL		0x01	/* byte - channel control */
+#define   VIA_REG_CTRL_START		0x80	/* WO */
+#define   VIA_REG_CTRL_TERMINATE	0x40	/* WO */
+#define   VIA_REG_CTRL_AUTOSTART	0x20
+#define   VIA_REG_CTRL_PAUSE		0x08	/* RW */
+#define   VIA_REG_CTRL_INT_STOP		0x04		
+#define   VIA_REG_CTRL_INT_EOL		0x02
+#define   VIA_REG_CTRL_INT_FLAG		0x01
+#define   VIA_REG_CTRL_RESET		0x01	/* RW - probably reset? undocumented */
+#define   VIA_REG_CTRL_INT (VIA_REG_CTRL_INT_FLAG | VIA_REG_CTRL_INT_EOL | VIA_REG_CTRL_AUTOSTART)
+#define VIA_REG_OFFSET_TYPE		0x02	/* byte - channel type (686 only) */
+#define   VIA_REG_TYPE_AUTOSTART	0x80	/* RW - autostart at EOL */
+#define   VIA_REG_TYPE_16BIT		0x20	/* RW */
+#define   VIA_REG_TYPE_STEREO		0x10	/* RW */
+#define   VIA_REG_TYPE_INT_LLINE	0x00
+#define   VIA_REG_TYPE_INT_LSAMPLE	0x04
+#define   VIA_REG_TYPE_INT_LESSONE	0x08
+#define   VIA_REG_TYPE_INT_MASK		0x0c
+#define   VIA_REG_TYPE_INT_EOL		0x02
+#define   VIA_REG_TYPE_INT_FLAG		0x01
+#define VIA_REG_OFFSET_TABLE_PTR	0x04	/* dword - channel table pointer */
+#define VIA_REG_OFFSET_CURR_PTR		0x04	/* dword - channel current pointer */
+#define VIA_REG_OFFSET_STOP_IDX		0x08	/* dword - stop index, channel type, sample rate */
+#define   VIA8233_REG_TYPE_16BIT	0x00200000	/* RW */
+#define   VIA8233_REG_TYPE_STEREO	0x00100000	/* RW */
+#define VIA_REG_OFFSET_CURR_COUNT	0x0c	/* dword - channel current count (24 bit) */
+#define VIA_REG_OFFSET_CURR_INDEX	0x0f	/* byte - channel current index (for via8233 only) */
+
+#define DEFINE_VIA_REGSET(name,val) \
+enum {\
+	VIA_REG_##name##_STATUS		= (val),\
+	VIA_REG_##name##_CONTROL	= (val) + 0x01,\
+	VIA_REG_##name##_TYPE		= (val) + 0x02,\
+	VIA_REG_##name##_TABLE_PTR	= (val) + 0x04,\
+	VIA_REG_##name##_CURR_PTR	= (val) + 0x04,\
+	VIA_REG_##name##_STOP_IDX	= (val) + 0x08,\
+	VIA_REG_##name##_CURR_COUNT	= (val) + 0x0c,\
+}
+
+/* playback block */
+DEFINE_VIA_REGSET(PLAYBACK, 0x00);
+DEFINE_VIA_REGSET(CAPTURE, 0x10);
+DEFINE_VIA_REGSET(FM, 0x20);
+
+/* AC'97 */
+#define VIA_REG_AC97			0x80	/* dword */
+#define   VIA_REG_AC97_CODEC_ID_MASK	(3<<30)
+#define   VIA_REG_AC97_CODEC_ID_SHIFT	30
+#define   VIA_REG_AC97_CODEC_ID_PRIMARY	0x00
+#define   VIA_REG_AC97_CODEC_ID_SECONDARY 0x01
+#define   VIA_REG_AC97_SECONDARY_VALID	(1<<27)
+#define   VIA_REG_AC97_PRIMARY_VALID	(1<<25)
+#define   VIA_REG_AC97_BUSY		(1<<24)
+#define   VIA_REG_AC97_READ		(1<<23)
+#define   VIA_REG_AC97_CMD_SHIFT	16
+#define   VIA_REG_AC97_CMD_MASK		0x7e
+#define   VIA_REG_AC97_DATA_SHIFT	0
+#define   VIA_REG_AC97_DATA_MASK	0xffff
+
+#define VIA_REG_SGD_SHADOW		0x84	/* dword */
+/* via686 */
+#define   VIA_REG_SGD_STAT_PB_FLAG	(1<<0)
+#define   VIA_REG_SGD_STAT_CP_FLAG	(1<<1)
+#define   VIA_REG_SGD_STAT_FM_FLAG	(1<<2)
+#define   VIA_REG_SGD_STAT_PB_EOL	(1<<4)
+#define   VIA_REG_SGD_STAT_CP_EOL	(1<<5)
+#define   VIA_REG_SGD_STAT_FM_EOL	(1<<6)
+#define   VIA_REG_SGD_STAT_PB_STOP	(1<<8)
+#define   VIA_REG_SGD_STAT_CP_STOP	(1<<9)
+#define   VIA_REG_SGD_STAT_FM_STOP	(1<<10)
+#define   VIA_REG_SGD_STAT_PB_ACTIVE	(1<<12)
+#define   VIA_REG_SGD_STAT_CP_ACTIVE	(1<<13)
+#define   VIA_REG_SGD_STAT_FM_ACTIVE	(1<<14)
+/* via8233 */
+#define   VIA8233_REG_SGD_STAT_FLAG	(1<<0)
+#define   VIA8233_REG_SGD_STAT_EOL	(1<<1)
+#define   VIA8233_REG_SGD_STAT_STOP	(1<<2)
+#define   VIA8233_REG_SGD_STAT_ACTIVE	(1<<3)
+#define VIA8233_INTR_MASK(chan) ((VIA8233_REG_SGD_STAT_FLAG|VIA8233_REG_SGD_STAT_EOL) << ((chan) * 4))
+#define   VIA8233_REG_SGD_CHAN_SDX	0
+#define   VIA8233_REG_SGD_CHAN_MULTI	4
+#define   VIA8233_REG_SGD_CHAN_REC	6
+#define   VIA8233_REG_SGD_CHAN_REC1	7
+
+#define VIA_REG_GPI_STATUS		0x88
+#define VIA_REG_GPI_INTR		0x8c
+
+/* multi-channel and capture registers for via8233 */
+DEFINE_VIA_REGSET(MULTPLAY, 0x40);
+DEFINE_VIA_REGSET(CAPTURE_8233, 0x60);
+
+/* via8233-specific registers */
+#define VIA_REG_OFS_PLAYBACK_VOLUME_L	0x02	/* byte */
+#define VIA_REG_OFS_PLAYBACK_VOLUME_R	0x03	/* byte */
+#define VIA_REG_OFS_MULTPLAY_FORMAT	0x02	/* byte - format and channels */
+#define   VIA_REG_MULTPLAY_FMT_8BIT	0x00
+#define   VIA_REG_MULTPLAY_FMT_16BIT	0x80
+#define   VIA_REG_MULTPLAY_FMT_CH_MASK	0x70	/* # channels << 4 (valid = 1,2,4,6) */
+#define VIA_REG_OFS_CAPTURE_FIFO	0x02	/* byte - bit 6 = fifo  enable */
+#define   VIA_REG_CAPTURE_FIFO_ENABLE	0x40
+
+#define VIA_DXS_MAX_VOLUME		31	/* max. volume (attenuation) of reg 0x32/33 */
+
+#define VIA_REG_CAPTURE_CHANNEL		0x63	/* byte - input select */
+#define   VIA_REG_CAPTURE_CHANNEL_MIC	0x4
+#define   VIA_REG_CAPTURE_CHANNEL_LINE	0
+#define   VIA_REG_CAPTURE_SELECT_CODEC	0x03	/* recording source codec (0 = primary) */
+
+#define VIA_TBL_BIT_FLAG	0x40000000
+#define VIA_TBL_BIT_EOL		0x80000000
+
+/* pci space */
+#define VIA_ACLINK_STAT		0x40
+#define  VIA_ACLINK_C11_READY	0x20
+#define  VIA_ACLINK_C10_READY	0x10
+#define  VIA_ACLINK_C01_READY	0x04 /* secondary codec ready */
+#define  VIA_ACLINK_LOWPOWER	0x02 /* low-power state */
+#define  VIA_ACLINK_C00_READY	0x01 /* primary codec ready */
+#define VIA_ACLINK_CTRL		0x41
+#define  VIA_ACLINK_CTRL_ENABLE	0x80 /* 0: disable, 1: enable */
+#define  VIA_ACLINK_CTRL_RESET	0x40 /* 0: assert, 1: de-assert */
+#define  VIA_ACLINK_CTRL_SYNC	0x20 /* 0: release SYNC, 1: force SYNC hi */
+#define  VIA_ACLINK_CTRL_SDO	0x10 /* 0: release SDO, 1: force SDO hi */
+#define  VIA_ACLINK_CTRL_VRA	0x08 /* 0: disable VRA, 1: enable VRA */
+#define  VIA_ACLINK_CTRL_PCM	0x04 /* 0: disable PCM, 1: enable PCM */
+#define  VIA_ACLINK_CTRL_FM	0x02 /* via686 only */
+#define  VIA_ACLINK_CTRL_SB	0x01 /* via686 only */
+#define  VIA_ACLINK_CTRL_INIT	(VIA_ACLINK_CTRL_ENABLE|\
+				 VIA_ACLINK_CTRL_RESET|\
+				 VIA_ACLINK_CTRL_PCM|\
+				 VIA_ACLINK_CTRL_VRA)
+#define VIA_FUNC_ENABLE		0x42
+#define  VIA_FUNC_MIDI_PNP	0x80 /* FIXME: it's 0x40 in the datasheet! */
+#define  VIA_FUNC_MIDI_IRQMASK	0x40 /* FIXME: not documented! */
+#define  VIA_FUNC_RX2C_WRITE	0x20
+#define  VIA_FUNC_SB_FIFO_EMPTY	0x10
+#define  VIA_FUNC_ENABLE_GAME	0x08
+#define  VIA_FUNC_ENABLE_FM	0x04
+#define  VIA_FUNC_ENABLE_MIDI	0x02
+#define  VIA_FUNC_ENABLE_SB	0x01
+#define VIA_PNP_CONTROL		0x43
+#define VIA_FM_NMI_CTRL		0x48
+#define VIA8233_VOLCHG_CTRL	0x48
+#define VIA8233_SPDIF_CTRL	0x49
+#define  VIA8233_SPDIF_DX3	0x08
+#define  VIA8233_SPDIF_SLOT_MASK	0x03
+#define  VIA8233_SPDIF_SLOT_1011	0x00
+#define  VIA8233_SPDIF_SLOT_34		0x01
+#define  VIA8233_SPDIF_SLOT_78		0x02
+#define  VIA8233_SPDIF_SLOT_69		0x03
+
+/*
+ */
+
+#define VIA_DXS_AUTO	0
+#define VIA_DXS_ENABLE	1
+#define VIA_DXS_DISABLE	2
+#define VIA_DXS_48K	3
+#define VIA_DXS_NO_VRA	4
+#define VIA_DXS_SRC	5
+
+
+/*
+ * pcm stream
+ */
+
+struct snd_via_sg_table {
+	unsigned int offset;
+	unsigned int size;
+} ;
+
+#define VIA_TABLE_SIZE	255
+#define VIA_MAX_BUFSIZE	(1<<24)
+
+struct viadev {
+	unsigned int reg_offset;
+	unsigned long port;
+	int direction;	/* playback = 0, capture = 1 */
+        struct snd_pcm_substream *substream;
+	int running;
+	unsigned int tbl_entries; /* # descriptors */
+	struct snd_dma_buffer table;
+	struct snd_via_sg_table *idx_table;
+	/* for recovery from the unexpected pointer */
+	unsigned int lastpos;
+	unsigned int fragsize;
+	unsigned int bufsize;
+	unsigned int bufsize2;
+	int hwptr_done;		/* processed frame position in the buffer */
+	int in_interrupt;
+	int shadow_shift;
+};
+
+
+enum { TYPE_CARD_VIA686 = 1, TYPE_CARD_VIA8233 };
+enum { TYPE_VIA686, TYPE_VIA8233, TYPE_VIA8233A };
+
+#define VIA_MAX_DEVS	7	/* 4 playback, 1 multi, 2 capture */
+
+struct via_rate_lock {
+	spinlock_t lock;
+	int rate;
+	int used;
+};
+
+struct via82xx {
+	int irq;
+
+	unsigned long port;
+	struct resource *mpu_res;
+	int chip_type;
+	unsigned char revision;
+
+	unsigned char old_legacy;
+	unsigned char old_legacy_cfg;
+#ifdef CONFIG_PM
+	unsigned char legacy_saved;
+	unsigned char legacy_cfg_saved;
+	unsigned char spdif_ctrl_saved;
+	unsigned char capture_src_saved[2];
+	unsigned int mpu_port_saved;
+#endif
+
+	unsigned char playback_volume[4][2]; /* for VIA8233/C/8235; default = 0 */
+	unsigned char playback_volume_c[2]; /* for VIA8233/C/8235; default = 0 */
+
+	unsigned int intr_mask; /* SGD_SHADOW mask to check interrupts */
+
+	struct pci_dev *pci;
+	struct snd_card *card;
+
+	unsigned int num_devs;
+	unsigned int playback_devno, multi_devno, capture_devno;
+	struct viadev devs[VIA_MAX_DEVS];
+	struct via_rate_lock rates[2]; /* playback and capture */
+	unsigned int dxs_fixed: 1;	/* DXS channel accepts only 48kHz */
+	unsigned int no_vra: 1;		/* no need to set VRA on DXS channels */
+	unsigned int dxs_src: 1;	/* use full SRC capabilities of DXS */
+	unsigned int spdif_on: 1;	/* only spdif rates work to external DACs */
+
+	struct snd_pcm *pcms[2];
+	struct snd_rawmidi *rmidi;
+	struct snd_kcontrol *dxs_controls[4];
+
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97 *ac97;
+	unsigned int ac97_clock;
+	unsigned int ac97_secondary;	/* secondary AC'97 codec is present */
+
+	spinlock_t reg_lock;
+	struct snd_info_entry *proc_entry;
+
+#ifdef SUPPORT_JOYSTICK
+	struct gameport *gameport;
+#endif
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_via82xx_ids) = {
+	/* 0x1106, 0x3058 */
+	{ PCI_VDEVICE(VIA, PCI_DEVICE_ID_VIA_82C686_5), TYPE_CARD_VIA686, },	/* 686A */
+	/* 0x1106, 0x3059 */
+	{ PCI_VDEVICE(VIA, PCI_DEVICE_ID_VIA_8233_5), TYPE_CARD_VIA8233, },	/* VT8233 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_via82xx_ids);
+
+/*
+ */
+
+/*
+ * allocate and initialize the descriptor buffers
+ * periods = number of periods
+ * fragsize = period size in bytes
+ */
+static int build_via_table(struct viadev *dev, struct snd_pcm_substream *substream,
+			   struct pci_dev *pci,
+			   unsigned int periods, unsigned int fragsize)
+{
+	unsigned int i, idx, ofs, rest;
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+
+	if (dev->table.area == NULL) {
+		/* the start of each lists must be aligned to 8 bytes,
+		 * but the kernel pages are much bigger, so we don't care
+		 */
+		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+					PAGE_ALIGN(VIA_TABLE_SIZE * 2 * 8),
+					&dev->table) < 0)
+			return -ENOMEM;
+	}
+	if (! dev->idx_table) {
+		dev->idx_table = kmalloc(sizeof(*dev->idx_table) * VIA_TABLE_SIZE, GFP_KERNEL);
+		if (! dev->idx_table)
+			return -ENOMEM;
+	}
+
+	/* fill the entries */
+	idx = 0;
+	ofs = 0;
+	for (i = 0; i < periods; i++) {
+		rest = fragsize;
+		/* fill descriptors for a period.
+		 * a period can be split to several descriptors if it's
+		 * over page boundary.
+		 */
+		do {
+			unsigned int r;
+			unsigned int flag;
+			unsigned int addr;
+
+			if (idx >= VIA_TABLE_SIZE) {
+				snd_printk(KERN_ERR "via82xx: too much table size!\n");
+				return -EINVAL;
+			}
+			addr = snd_pcm_sgbuf_get_addr(substream, ofs);
+			((u32 *)dev->table.area)[idx << 1] = cpu_to_le32(addr);
+			r = snd_pcm_sgbuf_get_chunk_size(substream, ofs, rest);
+			rest -= r;
+			if (! rest) {
+				if (i == periods - 1)
+					flag = VIA_TBL_BIT_EOL; /* buffer boundary */
+				else
+					flag = VIA_TBL_BIT_FLAG; /* period boundary */
+			} else
+				flag = 0; /* period continues to the next */
+			/*
+			printk(KERN_DEBUG "via: tbl %d: at %d  size %d "
+			       "(rest %d)\n", idx, ofs, r, rest);
+			*/
+			((u32 *)dev->table.area)[(idx<<1) + 1] = cpu_to_le32(r | flag);
+			dev->idx_table[idx].offset = ofs;
+			dev->idx_table[idx].size = r;
+			ofs += r;
+			idx++;
+		} while (rest > 0);
+	}
+	dev->tbl_entries = idx;
+	dev->bufsize = periods * fragsize;
+	dev->bufsize2 = dev->bufsize / 2;
+	dev->fragsize = fragsize;
+	return 0;
+}
+
+
+static int clean_via_table(struct viadev *dev, struct snd_pcm_substream *substream,
+			   struct pci_dev *pci)
+{
+	if (dev->table.area) {
+		snd_dma_free_pages(&dev->table);
+		dev->table.area = NULL;
+	}
+	kfree(dev->idx_table);
+	dev->idx_table = NULL;
+	return 0;
+}
+
+/*
+ *  Basic I/O
+ */
+
+static inline unsigned int snd_via82xx_codec_xread(struct via82xx *chip)
+{
+	return inl(VIAREG(chip, AC97));
+}
+ 
+static inline void snd_via82xx_codec_xwrite(struct via82xx *chip, unsigned int val)
+{
+	outl(val, VIAREG(chip, AC97));
+}
+ 
+static int snd_via82xx_codec_ready(struct via82xx *chip, int secondary)
+{
+	unsigned int timeout = 1000;	/* 1ms */
+	unsigned int val;
+	
+	while (timeout-- > 0) {
+		udelay(1);
+		if (!((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_BUSY))
+			return val & 0xffff;
+	}
+	snd_printk(KERN_ERR "codec_ready: codec %i is not ready [0x%x]\n",
+		   secondary, snd_via82xx_codec_xread(chip));
+	return -EIO;
+}
+ 
+static int snd_via82xx_codec_valid(struct via82xx *chip, int secondary)
+{
+	unsigned int timeout = 1000;	/* 1ms */
+	unsigned int val, val1;
+	unsigned int stat = !secondary ? VIA_REG_AC97_PRIMARY_VALID :
+					 VIA_REG_AC97_SECONDARY_VALID;
+	
+	while (timeout-- > 0) {
+		val = snd_via82xx_codec_xread(chip);
+		val1 = val & (VIA_REG_AC97_BUSY | stat);
+		if (val1 == stat)
+			return val & 0xffff;
+		udelay(1);
+	}
+	return -EIO;
+}
+ 
+static void snd_via82xx_codec_wait(struct snd_ac97 *ac97)
+{
+	struct via82xx *chip = ac97->private_data;
+	int err;
+	err = snd_via82xx_codec_ready(chip, ac97->num);
+	/* here we need to wait fairly for long time.. */
+	if (!nodelay)
+		msleep(500);
+}
+
+static void snd_via82xx_codec_write(struct snd_ac97 *ac97,
+				    unsigned short reg,
+				    unsigned short val)
+{
+	struct via82xx *chip = ac97->private_data;
+	unsigned int xval;
+
+	xval = !ac97->num ? VIA_REG_AC97_CODEC_ID_PRIMARY : VIA_REG_AC97_CODEC_ID_SECONDARY;
+	xval <<= VIA_REG_AC97_CODEC_ID_SHIFT;
+	xval |= reg << VIA_REG_AC97_CMD_SHIFT;
+	xval |= val << VIA_REG_AC97_DATA_SHIFT;
+	snd_via82xx_codec_xwrite(chip, xval);
+	snd_via82xx_codec_ready(chip, ac97->num);
+}
+
+static unsigned short snd_via82xx_codec_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct via82xx *chip = ac97->private_data;
+	unsigned int xval, val = 0xffff;
+	int again = 0;
+
+	xval = ac97->num << VIA_REG_AC97_CODEC_ID_SHIFT;
+	xval |= ac97->num ? VIA_REG_AC97_SECONDARY_VALID : VIA_REG_AC97_PRIMARY_VALID;
+	xval |= VIA_REG_AC97_READ;
+	xval |= (reg & 0x7f) << VIA_REG_AC97_CMD_SHIFT;
+      	while (1) {
+      		if (again++ > 3) {
+			snd_printk(KERN_ERR "codec_read: codec %i is not valid [0x%x]\n",
+				   ac97->num, snd_via82xx_codec_xread(chip));
+		      	return 0xffff;
+		}
+		snd_via82xx_codec_xwrite(chip, xval);
+		udelay (20);
+		if (snd_via82xx_codec_valid(chip, ac97->num) >= 0) {
+			udelay(25);
+			val = snd_via82xx_codec_xread(chip);
+			break;
+		}
+	}
+	return val & 0xffff;
+}
+
+static void snd_via82xx_channel_reset(struct via82xx *chip, struct viadev *viadev)
+{
+	outb(VIA_REG_CTRL_PAUSE | VIA_REG_CTRL_TERMINATE | VIA_REG_CTRL_RESET,
+	     VIADEV_REG(viadev, OFFSET_CONTROL));
+	inb(VIADEV_REG(viadev, OFFSET_CONTROL));
+	udelay(50);
+	/* disable interrupts */
+	outb(0x00, VIADEV_REG(viadev, OFFSET_CONTROL));
+	/* clear interrupts */
+	outb(0x03, VIADEV_REG(viadev, OFFSET_STATUS));
+	outb(0x00, VIADEV_REG(viadev, OFFSET_TYPE)); /* for via686 */
+	// outl(0, VIADEV_REG(viadev, OFFSET_CURR_PTR));
+	viadev->lastpos = 0;
+	viadev->hwptr_done = 0;
+}
+
+
+/*
+ *  Interrupt handler
+ *  Used for 686 and 8233A
+ */
+static irqreturn_t snd_via686_interrupt(int irq, void *dev_id)
+{
+	struct via82xx *chip = dev_id;
+	unsigned int status;
+	unsigned int i;
+
+	status = inl(VIAREG(chip, SGD_SHADOW));
+	if (! (status & chip->intr_mask)) {
+		if (chip->rmidi)
+			/* check mpu401 interrupt */
+			return snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+		return IRQ_NONE;
+	}
+
+	/* check status for each stream */
+	spin_lock(&chip->reg_lock);
+	for (i = 0; i < chip->num_devs; i++) {
+		struct viadev *viadev = &chip->devs[i];
+		unsigned char c_status = inb(VIADEV_REG(viadev, OFFSET_STATUS));
+		if (! (c_status & (VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG|VIA_REG_STAT_STOPPED)))
+			continue;
+		if (viadev->substream && viadev->running) {
+			/*
+			 * Update hwptr_done based on 'period elapsed'
+			 * interrupts. We'll use it, when the chip returns 0 
+			 * for OFFSET_CURR_COUNT.
+			 */
+			if (c_status & VIA_REG_STAT_EOL)
+				viadev->hwptr_done = 0;
+			else
+				viadev->hwptr_done += viadev->fragsize;
+			viadev->in_interrupt = c_status;
+			spin_unlock(&chip->reg_lock);
+			snd_pcm_period_elapsed(viadev->substream);
+			spin_lock(&chip->reg_lock);
+			viadev->in_interrupt = 0;
+		}
+		outb(c_status, VIADEV_REG(viadev, OFFSET_STATUS)); /* ack */
+	}
+	spin_unlock(&chip->reg_lock);
+	return IRQ_HANDLED;
+}
+
+/*
+ *  Interrupt handler
+ */
+static irqreturn_t snd_via8233_interrupt(int irq, void *dev_id)
+{
+	struct via82xx *chip = dev_id;
+	unsigned int status;
+	unsigned int i;
+	int irqreturn = 0;
+
+	/* check status for each stream */
+	spin_lock(&chip->reg_lock);
+	status = inl(VIAREG(chip, SGD_SHADOW));
+
+	for (i = 0; i < chip->num_devs; i++) {
+		struct viadev *viadev = &chip->devs[i];
+		struct snd_pcm_substream *substream;
+		unsigned char c_status, shadow_status;
+
+		shadow_status = (status >> viadev->shadow_shift) &
+			(VIA8233_SHADOW_STAT_ACTIVE|VIA_REG_STAT_EOL|
+			 VIA_REG_STAT_FLAG);
+		c_status = shadow_status & (VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG);
+		if (!c_status)
+			continue;
+
+		substream = viadev->substream;
+		if (substream && viadev->running) {
+			/*
+			 * Update hwptr_done based on 'period elapsed'
+			 * interrupts. We'll use it, when the chip returns 0 
+			 * for OFFSET_CURR_COUNT.
+			 */
+			if (c_status & VIA_REG_STAT_EOL)
+				viadev->hwptr_done = 0;
+			else
+				viadev->hwptr_done += viadev->fragsize;
+			viadev->in_interrupt = c_status;
+			if (shadow_status & VIA8233_SHADOW_STAT_ACTIVE)
+				viadev->in_interrupt |= VIA_REG_STAT_ACTIVE;
+			spin_unlock(&chip->reg_lock);
+
+			snd_pcm_period_elapsed(substream);
+
+			spin_lock(&chip->reg_lock);
+			viadev->in_interrupt = 0;
+		}
+		outb(c_status, VIADEV_REG(viadev, OFFSET_STATUS)); /* ack */
+		irqreturn = 1;
+	}
+	spin_unlock(&chip->reg_lock);
+	return IRQ_RETVAL(irqreturn);
+}
+
+/*
+ *  PCM callbacks
+ */
+
+/*
+ * trigger callback
+ */
+static int snd_via82xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	unsigned char val;
+
+	if (chip->chip_type != TYPE_VIA686)
+		val = VIA_REG_CTRL_INT;
+	else
+		val = 0;
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		val |= VIA_REG_CTRL_START;
+		viadev->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		val = VIA_REG_CTRL_TERMINATE;
+		viadev->running = 0;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		val |= VIA_REG_CTRL_PAUSE;
+		viadev->running = 0;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		viadev->running = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	outb(val, VIADEV_REG(viadev, OFFSET_CONTROL));
+	if (cmd == SNDRV_PCM_TRIGGER_STOP)
+		snd_via82xx_channel_reset(chip, viadev);
+	return 0;
+}
+
+
+/*
+ * pointer callbacks
+ */
+
+/*
+ * calculate the linear position at the given sg-buffer index and the rest count
+ */
+
+#define check_invalid_pos(viadev,pos) \
+	((pos) < viadev->lastpos && ((pos) >= viadev->bufsize2 ||\
+				     viadev->lastpos < viadev->bufsize2))
+
+static inline unsigned int calc_linear_pos(struct viadev *viadev, unsigned int idx,
+					   unsigned int count)
+{
+	unsigned int size, base, res;
+
+	size = viadev->idx_table[idx].size;
+	base = viadev->idx_table[idx].offset;
+	res = base + size - count;
+	if (res >= viadev->bufsize)
+		res -= viadev->bufsize;
+
+	/* check the validity of the calculated position */
+	if (size < count) {
+		snd_printd(KERN_ERR "invalid via82xx_cur_ptr (size = %d, count = %d)\n",
+			   (int)size, (int)count);
+		res = viadev->lastpos;
+	} else {
+		if (! count) {
+			/* Some mobos report count = 0 on the DMA boundary,
+			 * i.e. count = size indeed.
+			 * Let's check whether this step is above the expected size.
+			 */
+			int delta = res - viadev->lastpos;
+			if (delta < 0)
+				delta += viadev->bufsize;
+			if ((unsigned int)delta > viadev->fragsize)
+				res = base;
+		}
+		if (check_invalid_pos(viadev, res)) {
+#ifdef POINTER_DEBUG
+			printk(KERN_DEBUG "fail: idx = %i/%i, lastpos = 0x%x, "
+			       "bufsize2 = 0x%x, offsize = 0x%x, size = 0x%x, "
+			       "count = 0x%x\n", idx, viadev->tbl_entries,
+			       viadev->lastpos, viadev->bufsize2,
+			       viadev->idx_table[idx].offset,
+			       viadev->idx_table[idx].size, count);
+#endif
+			/* count register returns full size when end of buffer is reached */
+			res = base + size;
+			if (check_invalid_pos(viadev, res)) {
+				snd_printd(KERN_ERR "invalid via82xx_cur_ptr (2), "
+					   "using last valid pointer\n");
+				res = viadev->lastpos;
+			}
+		}
+	}
+	return res;
+}
+
+/*
+ * get the current pointer on via686
+ */
+static snd_pcm_uframes_t snd_via686_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	unsigned int idx, ptr, count, res;
+
+	if (snd_BUG_ON(!viadev->tbl_entries))
+		return 0;
+	if (!(inb(VIADEV_REG(viadev, OFFSET_STATUS)) & VIA_REG_STAT_ACTIVE))
+		return 0;
+
+	spin_lock(&chip->reg_lock);
+	count = inl(VIADEV_REG(viadev, OFFSET_CURR_COUNT)) & 0xffffff;
+	/* The via686a does not have the current index register,
+	 * so we need to calculate the index from CURR_PTR.
+	 */
+	ptr = inl(VIADEV_REG(viadev, OFFSET_CURR_PTR));
+	if (ptr <= (unsigned int)viadev->table.addr)
+		idx = 0;
+	else /* CURR_PTR holds the address + 8 */
+		idx = ((ptr - (unsigned int)viadev->table.addr) / 8 - 1) % viadev->tbl_entries;
+	res = calc_linear_pos(viadev, idx, count);
+	viadev->lastpos = res; /* remember the last position */
+	spin_unlock(&chip->reg_lock);
+
+	return bytes_to_frames(substream->runtime, res);
+}
+
+/*
+ * get the current pointer on via823x
+ */
+static snd_pcm_uframes_t snd_via8233_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	unsigned int idx, count, res;
+	int status;
+	
+	if (snd_BUG_ON(!viadev->tbl_entries))
+		return 0;
+
+	spin_lock(&chip->reg_lock);
+	count = inl(VIADEV_REG(viadev, OFFSET_CURR_COUNT));
+	status = viadev->in_interrupt;
+	if (!status)
+		status = inb(VIADEV_REG(viadev, OFFSET_STATUS));
+
+	/* An apparent bug in the 8251 is worked around by sending a 
+	 * REG_CTRL_START. */
+	if (chip->revision == VIA_REV_8251 && (status & VIA_REG_STAT_EOL))
+		snd_via82xx_pcm_trigger(substream, SNDRV_PCM_TRIGGER_START);
+
+	if (!(status & VIA_REG_STAT_ACTIVE)) {
+		res = 0;
+		goto unlock;
+	}
+	if (count & 0xffffff) {
+		idx = count >> 24;
+		if (idx >= viadev->tbl_entries) {
+#ifdef POINTER_DEBUG
+			printk(KERN_DEBUG "fail: invalid idx = %i/%i\n", idx,
+			       viadev->tbl_entries);
+#endif
+			res = viadev->lastpos;
+		} else {
+			count &= 0xffffff;
+			res = calc_linear_pos(viadev, idx, count);
+		}
+	} else {
+		res = viadev->hwptr_done;
+		if (!viadev->in_interrupt) {
+			if (status & VIA_REG_STAT_EOL) {
+				res = 0;
+			} else
+				if (status & VIA_REG_STAT_FLAG) {
+					res += viadev->fragsize;
+				}
+		}
+	}			    
+unlock:
+	viadev->lastpos = res;
+	spin_unlock(&chip->reg_lock);
+
+	return bytes_to_frames(substream->runtime, res);
+}
+
+
+/*
+ * hw_params callback:
+ * allocate the buffer and build up the buffer description table
+ */
+static int snd_via82xx_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	err = build_via_table(viadev, substream, chip->pci,
+			      params_periods(hw_params),
+			      params_period_bytes(hw_params));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/*
+ * hw_free callback:
+ * clean up the buffer description table and release the buffer
+ */
+static int snd_via82xx_hw_free(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+
+	clean_via_table(viadev, substream, chip->pci);
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+
+/*
+ * set up the table pointer
+ */
+static void snd_via82xx_set_table_ptr(struct via82xx *chip, struct viadev *viadev)
+{
+	snd_via82xx_codec_ready(chip, 0);
+	outl((u32)viadev->table.addr, VIADEV_REG(viadev, OFFSET_TABLE_PTR));
+	udelay(20);
+	snd_via82xx_codec_ready(chip, 0);
+}
+
+/*
+ * prepare callback for playback and capture on via686
+ */
+static void via686_setup_format(struct via82xx *chip, struct viadev *viadev,
+				struct snd_pcm_runtime *runtime)
+{
+	snd_via82xx_channel_reset(chip, viadev);
+	/* this must be set after channel_reset */
+	snd_via82xx_set_table_ptr(chip, viadev);
+	outb(VIA_REG_TYPE_AUTOSTART |
+	     (runtime->format == SNDRV_PCM_FORMAT_S16_LE ? VIA_REG_TYPE_16BIT : 0) |
+	     (runtime->channels > 1 ? VIA_REG_TYPE_STEREO : 0) |
+	     ((viadev->reg_offset & 0x10) == 0 ? VIA_REG_TYPE_INT_LSAMPLE : 0) |
+	     VIA_REG_TYPE_INT_EOL |
+	     VIA_REG_TYPE_INT_FLAG, VIADEV_REG(viadev, OFFSET_TYPE));
+}
+
+static int snd_via686_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE, runtime->rate);
+	snd_ac97_set_rate(chip->ac97, AC97_SPDIF, runtime->rate);
+	via686_setup_format(chip, viadev, runtime);
+	return 0;
+}
+
+static int snd_via686_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	snd_ac97_set_rate(chip->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate);
+	via686_setup_format(chip, viadev, runtime);
+	return 0;
+}
+
+/*
+ * lock the current rate
+ */
+static int via_lock_rate(struct via_rate_lock *rec, int rate)
+{
+	int changed = 0;
+
+	spin_lock_irq(&rec->lock);
+	if (rec->rate != rate) {
+		if (rec->rate && rec->used > 1) /* already set */
+			changed = -EINVAL;
+		else {
+			rec->rate = rate;
+			changed = 1;
+		}
+	}
+	spin_unlock_irq(&rec->lock);
+	return changed;
+}
+
+/*
+ * prepare callback for DSX playback on via823x
+ */
+static int snd_via8233_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int ac97_rate = chip->dxs_src ? 48000 : runtime->rate;
+	int rate_changed;
+	u32 rbits;
+
+	if ((rate_changed = via_lock_rate(&chip->rates[0], ac97_rate)) < 0)
+		return rate_changed;
+	if (rate_changed)
+		snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE,
+				  chip->no_vra ? 48000 : runtime->rate);
+	if (chip->spdif_on && viadev->reg_offset == 0x30)
+		snd_ac97_set_rate(chip->ac97, AC97_SPDIF, runtime->rate);
+
+	if (runtime->rate == 48000)
+		rbits = 0xfffff;
+	else
+		rbits = (0x100000 / 48000) * runtime->rate +
+			((0x100000 % 48000) * runtime->rate) / 48000;
+	snd_BUG_ON(rbits & ~0xfffff);
+	snd_via82xx_channel_reset(chip, viadev);
+	snd_via82xx_set_table_ptr(chip, viadev);
+	outb(chip->playback_volume[viadev->reg_offset / 0x10][0],
+	     VIADEV_REG(viadev, OFS_PLAYBACK_VOLUME_L));
+	outb(chip->playback_volume[viadev->reg_offset / 0x10][1],
+	     VIADEV_REG(viadev, OFS_PLAYBACK_VOLUME_R));
+	outl((runtime->format == SNDRV_PCM_FORMAT_S16_LE ? VIA8233_REG_TYPE_16BIT : 0) | /* format */
+	     (runtime->channels > 1 ? VIA8233_REG_TYPE_STEREO : 0) | /* stereo */
+	     rbits | /* rate */
+	     0xff000000,    /* STOP index is never reached */
+	     VIADEV_REG(viadev, OFFSET_STOP_IDX));
+	udelay(20);
+	snd_via82xx_codec_ready(chip, 0);
+	return 0;
+}
+
+/*
+ * prepare callback for multi-channel playback on via823x
+ */
+static int snd_via8233_multi_prepare(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int slots;
+	int fmt;
+
+	if (via_lock_rate(&chip->rates[0], runtime->rate) < 0)
+		return -EINVAL;
+	snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE, runtime->rate);
+	snd_ac97_set_rate(chip->ac97, AC97_PCM_SURR_DAC_RATE, runtime->rate);
+	snd_ac97_set_rate(chip->ac97, AC97_PCM_LFE_DAC_RATE, runtime->rate);
+	snd_ac97_set_rate(chip->ac97, AC97_SPDIF, runtime->rate);
+	snd_via82xx_channel_reset(chip, viadev);
+	snd_via82xx_set_table_ptr(chip, viadev);
+
+	fmt = (runtime->format == SNDRV_PCM_FORMAT_S16_LE) ?
+		VIA_REG_MULTPLAY_FMT_16BIT : VIA_REG_MULTPLAY_FMT_8BIT;
+	fmt |= runtime->channels << 4;
+	outb(fmt, VIADEV_REG(viadev, OFS_MULTPLAY_FORMAT));
+#if 0
+	if (chip->revision == VIA_REV_8233A)
+		slots = 0;
+	else
+#endif
+	{
+		/* set sample number to slot 3, 4, 7, 8, 6, 9 (for VIA8233/C,8235) */
+		/* corresponding to FL, FR, RL, RR, C, LFE ?? */
+		switch (runtime->channels) {
+		case 1: slots = (1<<0) | (1<<4); break;
+		case 2: slots = (1<<0) | (2<<4); break;
+		case 3: slots = (1<<0) | (2<<4) | (5<<8); break;
+		case 4: slots = (1<<0) | (2<<4) | (3<<8) | (4<<12); break;
+		case 5: slots = (1<<0) | (2<<4) | (3<<8) | (4<<12) | (5<<16); break;
+		case 6: slots = (1<<0) | (2<<4) | (3<<8) | (4<<12) | (5<<16) | (6<<20); break;
+		default: slots = 0; break;
+		}
+	}
+	/* STOP index is never reached */
+	outl(0xff000000 | slots, VIADEV_REG(viadev, OFFSET_STOP_IDX));
+	udelay(20);
+	snd_via82xx_codec_ready(chip, 0);
+	return 0;
+}
+
+/*
+ * prepare callback for capture on via823x
+ */
+static int snd_via8233_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	if (via_lock_rate(&chip->rates[1], runtime->rate) < 0)
+		return -EINVAL;
+	snd_ac97_set_rate(chip->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate);
+	snd_via82xx_channel_reset(chip, viadev);
+	snd_via82xx_set_table_ptr(chip, viadev);
+	outb(VIA_REG_CAPTURE_FIFO_ENABLE, VIADEV_REG(viadev, OFS_CAPTURE_FIFO));
+	outl((runtime->format == SNDRV_PCM_FORMAT_S16_LE ? VIA8233_REG_TYPE_16BIT : 0) |
+	     (runtime->channels > 1 ? VIA8233_REG_TYPE_STEREO : 0) |
+	     0xff000000,    /* STOP index is never reached */
+	     VIADEV_REG(viadev, OFFSET_STOP_IDX));
+	udelay(20);
+	snd_via82xx_codec_ready(chip, 0);
+	return 0;
+}
+
+
+/*
+ * pcm hardware definition, identical for both playback and capture
+ */
+static struct snd_pcm_hardware snd_via82xx_hw =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 /* SNDRV_PCM_INFO_RESUME | */
+				 SNDRV_PCM_INFO_PAUSE),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_48000,
+	.rate_min =		48000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	VIA_MAX_BUFSIZE,
+	.period_bytes_min =	32,
+	.period_bytes_max =	VIA_MAX_BUFSIZE / 2,
+	.periods_min =		2,
+	.periods_max =		VIA_TABLE_SIZE / 2,
+	.fifo_size =		0,
+};
+
+
+/*
+ * open callback skeleton
+ */
+static int snd_via82xx_pcm_open(struct via82xx *chip, struct viadev *viadev,
+				struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+	struct via_rate_lock *ratep;
+
+	runtime->hw = snd_via82xx_hw;
+	
+	/* set the hw rate condition */
+	ratep = &chip->rates[viadev->direction];
+	spin_lock_irq(&ratep->lock);
+	ratep->used++;
+	if (chip->spdif_on && viadev->reg_offset == 0x30) {
+		/* DXS#3 and spdif is on */
+		runtime->hw.rates = chip->ac97->rates[AC97_RATES_SPDIF];
+		snd_pcm_limit_hw_rates(runtime);
+	} else if (chip->dxs_fixed && viadev->reg_offset < 0x40) {
+		/* fixed DXS playback rate */
+		runtime->hw.rates = SNDRV_PCM_RATE_48000;
+		runtime->hw.rate_min = runtime->hw.rate_max = 48000;
+	} else if (chip->dxs_src && viadev->reg_offset < 0x40) {
+		/* use full SRC capabilities of DXS */
+		runtime->hw.rates = (SNDRV_PCM_RATE_CONTINUOUS |
+				     SNDRV_PCM_RATE_8000_48000);
+		runtime->hw.rate_min = 8000;
+		runtime->hw.rate_max = 48000;
+	} else if (! ratep->rate) {
+		int idx = viadev->direction ? AC97_RATES_ADC : AC97_RATES_FRONT_DAC;
+		runtime->hw.rates = chip->ac97->rates[idx];
+		snd_pcm_limit_hw_rates(runtime);
+	} else {
+		/* a fixed rate */
+		runtime->hw.rates = SNDRV_PCM_RATE_KNOT;
+		runtime->hw.rate_max = runtime->hw.rate_min = ratep->rate;
+	}
+	spin_unlock_irq(&ratep->lock);
+
+	/* we may remove following constaint when we modify table entries
+	   in interrupt */
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+
+	runtime->private_data = viadev;
+	viadev->substream = substream;
+
+	return 0;
+}
+
+
+/*
+ * open callback for playback on via686
+ */
+static int snd_via686_playback_open(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = &chip->devs[chip->playback_devno + substream->number];
+	int err;
+
+	if ((err = snd_via82xx_pcm_open(chip, viadev, substream)) < 0)
+		return err;
+	return 0;
+}
+
+/*
+ * open callback for playback on via823x DXS
+ */
+static int snd_via8233_playback_open(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev;
+	unsigned int stream;
+	int err;
+
+	viadev = &chip->devs[chip->playback_devno + substream->number];
+	if ((err = snd_via82xx_pcm_open(chip, viadev, substream)) < 0)
+		return err;
+	stream = viadev->reg_offset / 0x10;
+	if (chip->dxs_controls[stream]) {
+		chip->playback_volume[stream][0] =
+				VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31);
+		chip->playback_volume[stream][1] =
+				VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31);
+		chip->dxs_controls[stream]->vd[0].access &=
+			~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
+			       SNDRV_CTL_EVENT_MASK_INFO,
+			       &chip->dxs_controls[stream]->id);
+	}
+	return 0;
+}
+
+/*
+ * open callback for playback on via823x multi-channel
+ */
+static int snd_via8233_multi_open(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = &chip->devs[chip->multi_devno];
+	int err;
+	/* channels constraint for VIA8233A
+	 * 3 and 5 channels are not supported
+	 */
+	static unsigned int channels[] = {
+		1, 2, 4, 6
+	};
+	static struct snd_pcm_hw_constraint_list hw_constraints_channels = {
+		.count = ARRAY_SIZE(channels),
+		.list = channels,
+		.mask = 0,
+	};
+
+	if ((err = snd_via82xx_pcm_open(chip, viadev, substream)) < 0)
+		return err;
+	substream->runtime->hw.channels_max = 6;
+	if (chip->revision == VIA_REV_8233A)
+		snd_pcm_hw_constraint_list(substream->runtime, 0,
+					   SNDRV_PCM_HW_PARAM_CHANNELS,
+					   &hw_constraints_channels);
+	return 0;
+}
+
+/*
+ * open callback for capture on via686 and via823x
+ */
+static int snd_via82xx_capture_open(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = &chip->devs[chip->capture_devno + substream->pcm->device];
+
+	return snd_via82xx_pcm_open(chip, viadev, substream);
+}
+
+/*
+ * close callback
+ */
+static int snd_via82xx_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	struct via_rate_lock *ratep;
+
+	/* release the rate lock */
+	ratep = &chip->rates[viadev->direction];
+	spin_lock_irq(&ratep->lock);
+	ratep->used--;
+	if (! ratep->used)
+		ratep->rate = 0;
+	spin_unlock_irq(&ratep->lock);
+	if (! ratep->rate) {
+		if (! viadev->direction) {
+			snd_ac97_update_power(chip->ac97,
+					      AC97_PCM_FRONT_DAC_RATE, 0);
+			snd_ac97_update_power(chip->ac97,
+					      AC97_PCM_SURR_DAC_RATE, 0);
+			snd_ac97_update_power(chip->ac97,
+					      AC97_PCM_LFE_DAC_RATE, 0);
+		} else
+			snd_ac97_update_power(chip->ac97,
+					      AC97_PCM_LR_ADC_RATE, 0);
+	}
+	viadev->substream = NULL;
+	return 0;
+}
+
+static int snd_via8233_playback_close(struct snd_pcm_substream *substream)
+{
+	struct via82xx *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	unsigned int stream;
+
+	stream = viadev->reg_offset / 0x10;
+	if (chip->dxs_controls[stream]) {
+		chip->dxs_controls[stream]->vd[0].access |=
+			SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO,
+			       &chip->dxs_controls[stream]->id);
+	}
+	return snd_via82xx_pcm_close(substream);
+}
+
+
+/* via686 playback callbacks */
+static struct snd_pcm_ops snd_via686_playback_ops = {
+	.open =		snd_via686_playback_open,
+	.close =	snd_via82xx_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_via82xx_hw_params,
+	.hw_free =	snd_via82xx_hw_free,
+	.prepare =	snd_via686_playback_prepare,
+	.trigger =	snd_via82xx_pcm_trigger,
+	.pointer =	snd_via686_pcm_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+/* via686 capture callbacks */
+static struct snd_pcm_ops snd_via686_capture_ops = {
+	.open =		snd_via82xx_capture_open,
+	.close =	snd_via82xx_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_via82xx_hw_params,
+	.hw_free =	snd_via82xx_hw_free,
+	.prepare =	snd_via686_capture_prepare,
+	.trigger =	snd_via82xx_pcm_trigger,
+	.pointer =	snd_via686_pcm_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+/* via823x DSX playback callbacks */
+static struct snd_pcm_ops snd_via8233_playback_ops = {
+	.open =		snd_via8233_playback_open,
+	.close =	snd_via8233_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_via82xx_hw_params,
+	.hw_free =	snd_via82xx_hw_free,
+	.prepare =	snd_via8233_playback_prepare,
+	.trigger =	snd_via82xx_pcm_trigger,
+	.pointer =	snd_via8233_pcm_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+/* via823x multi-channel playback callbacks */
+static struct snd_pcm_ops snd_via8233_multi_ops = {
+	.open =		snd_via8233_multi_open,
+	.close =	snd_via82xx_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_via82xx_hw_params,
+	.hw_free =	snd_via82xx_hw_free,
+	.prepare =	snd_via8233_multi_prepare,
+	.trigger =	snd_via82xx_pcm_trigger,
+	.pointer =	snd_via8233_pcm_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+/* via823x capture callbacks */
+static struct snd_pcm_ops snd_via8233_capture_ops = {
+	.open =		snd_via82xx_capture_open,
+	.close =	snd_via82xx_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_via82xx_hw_params,
+	.hw_free =	snd_via82xx_hw_free,
+	.prepare =	snd_via8233_capture_prepare,
+	.trigger =	snd_via82xx_pcm_trigger,
+	.pointer =	snd_via8233_pcm_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+
+static void init_viadev(struct via82xx *chip, int idx, unsigned int reg_offset,
+			int shadow_pos, int direction)
+{
+	chip->devs[idx].reg_offset = reg_offset;
+	chip->devs[idx].shadow_shift = shadow_pos * 4;
+	chip->devs[idx].direction = direction;
+	chip->devs[idx].port = chip->port + reg_offset;
+}
+
+/*
+ * create pcm instances for VIA8233, 8233C and 8235 (not 8233A)
+ */
+static int __devinit snd_via8233_pcm_new(struct via82xx *chip)
+{
+	struct snd_pcm *pcm;
+	int i, err;
+
+	chip->playback_devno = 0;	/* x 4 */
+	chip->multi_devno = 4;		/* x 1 */
+	chip->capture_devno = 5;	/* x 2 */
+	chip->num_devs = 7;
+	chip->intr_mask = 0x33033333; /* FLAG|EOL for rec0-1, mc, sdx0-3 */
+
+	/* PCM #0:  4 DSX playbacks and 1 capture */
+	err = snd_pcm_new(chip->card, chip->card->shortname, 0, 4, 1, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via8233_capture_ops);
+	pcm->private_data = chip;
+	strcpy(pcm->name, chip->card->shortname);
+	chip->pcms[0] = pcm;
+	/* set up playbacks */
+	for (i = 0; i < 4; i++)
+		init_viadev(chip, i, 0x10 * i, i, 0);
+	/* capture */
+	init_viadev(chip, chip->capture_devno, VIA_REG_CAPTURE_8233_STATUS, 6, 1);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+					      snd_dma_pci_data(chip->pci),
+					      64*1024, VIA_MAX_BUFSIZE);
+
+	/* PCM #1:  multi-channel playback and 2nd capture */
+	err = snd_pcm_new(chip->card, chip->card->shortname, 1, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_multi_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via8233_capture_ops);
+	pcm->private_data = chip;
+	strcpy(pcm->name, chip->card->shortname);
+	chip->pcms[1] = pcm;
+	/* set up playback */
+	init_viadev(chip, chip->multi_devno, VIA_REG_MULTPLAY_STATUS, 4, 0);
+	/* set up capture */
+	init_viadev(chip, chip->capture_devno + 1, VIA_REG_CAPTURE_8233_STATUS + 0x10, 7, 1);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+					      snd_dma_pci_data(chip->pci),
+					      64*1024, VIA_MAX_BUFSIZE);
+	return 0;
+}
+
+/*
+ * create pcm instances for VIA8233A
+ */
+static int __devinit snd_via8233a_pcm_new(struct via82xx *chip)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	chip->multi_devno = 0;
+	chip->playback_devno = 1;
+	chip->capture_devno = 2;
+	chip->num_devs = 3;
+	chip->intr_mask = 0x03033000; /* FLAG|EOL for rec0, mc, sdx3 */
+
+	/* PCM #0:  multi-channel playback and capture */
+	err = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_multi_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via8233_capture_ops);
+	pcm->private_data = chip;
+	strcpy(pcm->name, chip->card->shortname);
+	chip->pcms[0] = pcm;
+	/* set up playback */
+	init_viadev(chip, chip->multi_devno, VIA_REG_MULTPLAY_STATUS, 4, 0);
+	/* capture */
+	init_viadev(chip, chip->capture_devno, VIA_REG_CAPTURE_8233_STATUS, 6, 1);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+					      snd_dma_pci_data(chip->pci),
+					      64*1024, VIA_MAX_BUFSIZE);
+
+	/* SPDIF supported? */
+	if (! ac97_can_spdif(chip->ac97))
+		return 0;
+
+	/* PCM #1:  DXS3 playback (for spdif) */
+	err = snd_pcm_new(chip->card, chip->card->shortname, 1, 1, 0, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via8233_playback_ops);
+	pcm->private_data = chip;
+	strcpy(pcm->name, chip->card->shortname);
+	chip->pcms[1] = pcm;
+	/* set up playback */
+	init_viadev(chip, chip->playback_devno, 0x30, 3, 0);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+					      snd_dma_pci_data(chip->pci),
+					      64*1024, VIA_MAX_BUFSIZE);
+	return 0;
+}
+
+/*
+ * create a pcm instance for via686a/b
+ */
+static int __devinit snd_via686_pcm_new(struct via82xx *chip)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	chip->playback_devno = 0;
+	chip->capture_devno = 1;
+	chip->num_devs = 2;
+	chip->intr_mask = 0x77; /* FLAG | EOL for PB, CP, FM */
+
+	err = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via686_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via686_capture_ops);
+	pcm->private_data = chip;
+	strcpy(pcm->name, chip->card->shortname);
+	chip->pcms[0] = pcm;
+	init_viadev(chip, 0, VIA_REG_PLAYBACK_STATUS, 0, 0);
+	init_viadev(chip, 1, VIA_REG_CAPTURE_STATUS, 0, 1);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+					      snd_dma_pci_data(chip->pci),
+					      64*1024, VIA_MAX_BUFSIZE);
+	return 0;
+}
+
+
+/*
+ *  Mixer part
+ */
+
+static int snd_via8233_capture_source_info(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	/* formerly they were "Line" and "Mic", but it looks like that they
+	 * have nothing to do with the actual physical connections...
+	 */
+	static char *texts[2] = {
+		"Input1", "Input2"
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item >= 2)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_via8233_capture_source_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct via82xx *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long port = chip->port + (kcontrol->id.index ? (VIA_REG_CAPTURE_CHANNEL + 0x10) : VIA_REG_CAPTURE_CHANNEL);
+	ucontrol->value.enumerated.item[0] = inb(port) & VIA_REG_CAPTURE_CHANNEL_MIC ? 1 : 0;
+	return 0;
+}
+
+static int snd_via8233_capture_source_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct via82xx *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long port = chip->port + (kcontrol->id.index ? (VIA_REG_CAPTURE_CHANNEL + 0x10) : VIA_REG_CAPTURE_CHANNEL);
+	u8 val, oval;
+
+	spin_lock_irq(&chip->reg_lock);
+	oval = inb(port);
+	val = oval & ~VIA_REG_CAPTURE_CHANNEL_MIC;
+	if (ucontrol->value.enumerated.item[0])
+		val |= VIA_REG_CAPTURE_CHANNEL_MIC;
+	if (val != oval)
+		outb(val, port);
+	spin_unlock_irq(&chip->reg_lock);
+	return val != oval;
+}
+
+static struct snd_kcontrol_new snd_via8233_capture_source __devinitdata = {
+	.name = "Input Source Select",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info = snd_via8233_capture_source_info,
+	.get = snd_via8233_capture_source_get,
+	.put = snd_via8233_capture_source_put,
+};
+
+#define snd_via8233_dxs3_spdif_info	snd_ctl_boolean_mono_info
+
+static int snd_via8233_dxs3_spdif_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct via82xx *chip = snd_kcontrol_chip(kcontrol);
+	u8 val;
+
+	pci_read_config_byte(chip->pci, VIA8233_SPDIF_CTRL, &val);
+	ucontrol->value.integer.value[0] = (val & VIA8233_SPDIF_DX3) ? 1 : 0;
+	return 0;
+}
+
+static int snd_via8233_dxs3_spdif_put(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct via82xx *chip = snd_kcontrol_chip(kcontrol);
+	u8 val, oval;
+
+	pci_read_config_byte(chip->pci, VIA8233_SPDIF_CTRL, &oval);
+	val = oval & ~VIA8233_SPDIF_DX3;
+	if (ucontrol->value.integer.value[0])
+		val |= VIA8233_SPDIF_DX3;
+	/* save the spdif flag for rate filtering */
+	chip->spdif_on = ucontrol->value.integer.value[0] ? 1 : 0;
+	if (val != oval) {
+		pci_write_config_byte(chip->pci, VIA8233_SPDIF_CTRL, val);
+		return 1;
+	}
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_via8233_dxs3_spdif_control __devinitdata = {
+	.name = SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH),
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info = snd_via8233_dxs3_spdif_info,
+	.get = snd_via8233_dxs3_spdif_get,
+	.put = snd_via8233_dxs3_spdif_put,
+};
+
+static int snd_via8233_dxs_volume_info(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = VIA_DXS_MAX_VOLUME;
+	return 0;
+}
+
+static int snd_via8233_dxs_volume_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct via82xx *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = kcontrol->id.subdevice;
+
+	ucontrol->value.integer.value[0] = VIA_DXS_MAX_VOLUME - chip->playback_volume[idx][0];
+	ucontrol->value.integer.value[1] = VIA_DXS_MAX_VOLUME - chip->playback_volume[idx][1];
+	return 0;
+}
+
+static int snd_via8233_pcmdxs_volume_get(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct via82xx *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = VIA_DXS_MAX_VOLUME - chip->playback_volume_c[0];
+	ucontrol->value.integer.value[1] = VIA_DXS_MAX_VOLUME - chip->playback_volume_c[1];
+	return 0;
+}
+
+static int snd_via8233_dxs_volume_put(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct via82xx *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = kcontrol->id.subdevice;
+	unsigned long port = chip->port + 0x10 * idx;
+	unsigned char val;
+	int i, change = 0;
+
+	for (i = 0; i < 2; i++) {
+		val = ucontrol->value.integer.value[i];
+		if (val > VIA_DXS_MAX_VOLUME)
+			val = VIA_DXS_MAX_VOLUME;
+		val = VIA_DXS_MAX_VOLUME - val;
+		change |= val != chip->playback_volume[idx][i];
+		if (change) {
+			chip->playback_volume[idx][i] = val;
+			outb(val, port + VIA_REG_OFS_PLAYBACK_VOLUME_L + i);
+		}
+	}
+	return change;
+}
+
+static int snd_via8233_pcmdxs_volume_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct via82xx *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int idx;
+	unsigned char val;
+	int i, change = 0;
+
+	for (i = 0; i < 2; i++) {
+		val = ucontrol->value.integer.value[i];
+		if (val > VIA_DXS_MAX_VOLUME)
+			val = VIA_DXS_MAX_VOLUME;
+		val = VIA_DXS_MAX_VOLUME - val;
+		if (val != chip->playback_volume_c[i]) {
+			change = 1;
+			chip->playback_volume_c[i] = val;
+			for (idx = 0; idx < 4; idx++) {
+				unsigned long port = chip->port + 0x10 * idx;
+				chip->playback_volume[idx][i] = val;
+				outb(val, port + VIA_REG_OFS_PLAYBACK_VOLUME_L + i);
+			}
+		}
+	}
+	return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_dxs, -4650, 150, 1);
+
+static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control __devinitdata = {
+	.name = "PCM Playback Volume",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.info = snd_via8233_dxs_volume_info,
+	.get = snd_via8233_pcmdxs_volume_get,
+	.put = snd_via8233_pcmdxs_volume_put,
+	.tlv = { .p = db_scale_dxs }
+};
+
+static struct snd_kcontrol_new snd_via8233_dxs_volume_control __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.device = 0,
+	/* .subdevice set later */
+	.name = "PCM Playback Volume",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+		  SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.info = snd_via8233_dxs_volume_info,
+	.get = snd_via8233_dxs_volume_get,
+	.put = snd_via8233_dxs_volume_put,
+	.tlv = { .p = db_scale_dxs }
+};
+
+/*
+ */
+
+static void snd_via82xx_mixer_free_ac97_bus(struct snd_ac97_bus *bus)
+{
+	struct via82xx *chip = bus->private_data;
+	chip->ac97_bus = NULL;
+}
+
+static void snd_via82xx_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+	struct via82xx *chip = ac97->private_data;
+	chip->ac97 = NULL;
+}
+
+static struct ac97_quirk ac97_quirks[] = {
+	{
+		.subvendor = 0x1106,
+		.subdevice = 0x4161,
+		.codec_id = 0x56494161, /* VT1612A */
+		.name = "Soltek SL-75DRV5",
+		.type = AC97_TUNE_NONE
+	},
+	{	/* FIXME: which codec? */
+		.subvendor = 0x1106,
+		.subdevice = 0x4161,
+		.name = "ASRock K7VT2",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x110a,
+		.subdevice = 0x0079,
+		.name = "Fujitsu Siemens D1289",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1019,
+		.subdevice = 0x0a81,
+		.name = "ECS K7VTA3",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1019,
+		.subdevice = 0x0a85,
+		.name = "ECS L7VMM2",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1019,
+		.subdevice = 0x1841,
+		.name = "ECS K7VTA3",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{
+		.subvendor = 0x1849,
+		.subdevice = 0x3059,
+		.name = "ASRock K7VM2",
+		.type = AC97_TUNE_HP_ONLY	/* VT1616 */
+	},
+	{
+		.subvendor = 0x14cd,
+		.subdevice = 0x7002,
+		.name = "Unknown",
+		.type = AC97_TUNE_ALC_JACK
+	},
+	{
+		.subvendor = 0x1071,
+		.subdevice = 0x8590,
+		.name = "Mitac Mobo",
+		.type = AC97_TUNE_ALC_JACK
+	},
+	{
+		.subvendor = 0x161f,
+		.subdevice = 0x202b,
+		.name = "Arima Notebook",
+		.type = AC97_TUNE_HP_ONLY,
+	},
+	{
+		.subvendor = 0x161f,
+		.subdevice = 0x2032,
+		.name = "Targa Traveller 811",
+		.type = AC97_TUNE_HP_ONLY,
+	},
+	{
+		.subvendor = 0x161f,
+		.subdevice = 0x2032,
+		.name = "m680x",
+		.type = AC97_TUNE_HP_ONLY, /* http://launchpad.net/bugs/38546 */
+	},
+	{
+		.subvendor = 0x1297,
+		.subdevice = 0xa232,
+		.name = "Shuttle AK32VN",
+		.type = AC97_TUNE_HP_ONLY
+	},
+	{ } /* terminator */
+};
+
+static int __devinit snd_via82xx_mixer_new(struct via82xx *chip, const char *quirk_override)
+{
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_via82xx_codec_write,
+		.read = snd_via82xx_codec_read,
+		.wait = snd_via82xx_codec_wait,
+	};
+
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus)) < 0)
+		return err;
+	chip->ac97_bus->private_free = snd_via82xx_mixer_free_ac97_bus;
+	chip->ac97_bus->clock = chip->ac97_clock;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.private_free = snd_via82xx_mixer_free_ac97;
+	ac97.pci = chip->pci;
+	ac97.scaps = AC97_SCAP_SKIP_MODEM | AC97_SCAP_POWER_SAVE;
+	if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97)) < 0)
+		return err;
+
+	snd_ac97_tune_hardware(chip->ac97, ac97_quirks, quirk_override);
+
+	if (chip->chip_type != TYPE_VIA686) {
+		/* use slot 10/11 */
+		snd_ac97_update_bits(chip->ac97, AC97_EXTENDED_STATUS, 0x03 << 4, 0x03 << 4);
+	}
+
+	return 0;
+}
+
+#ifdef SUPPORT_JOYSTICK
+#define JOYSTICK_ADDR	0x200
+static int __devinit snd_via686_create_gameport(struct via82xx *chip, unsigned char *legacy)
+{
+	struct gameport *gp;
+	struct resource *r;
+
+	if (!joystick)
+		return -ENODEV;
+
+	r = request_region(JOYSTICK_ADDR, 8, "VIA686 gameport");
+	if (!r) {
+		printk(KERN_WARNING "via82xx: cannot reserve joystick port 0x%#x\n",
+		       JOYSTICK_ADDR);
+		return -EBUSY;
+	}
+
+	chip->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "via82xx: cannot allocate memory for gameport\n");
+		release_and_free_resource(r);
+		return -ENOMEM;
+	}
+
+	gameport_set_name(gp, "VIA686 Gameport");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
+	gameport_set_dev_parent(gp, &chip->pci->dev);
+	gp->io = JOYSTICK_ADDR;
+	gameport_set_port_data(gp, r);
+
+	/* Enable legacy joystick port */
+	*legacy |= VIA_FUNC_ENABLE_GAME;
+	pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, *legacy);
+
+	gameport_register_port(chip->gameport);
+
+	return 0;
+}
+
+static void snd_via686_free_gameport(struct via82xx *chip)
+{
+	if (chip->gameport) {
+		struct resource *r = gameport_get_port_data(chip->gameport);
+
+		gameport_unregister_port(chip->gameport);
+		chip->gameport = NULL;
+		release_and_free_resource(r);
+	}
+}
+#else
+static inline int snd_via686_create_gameport(struct via82xx *chip, unsigned char *legacy)
+{
+	return -ENOSYS;
+}
+static inline void snd_via686_free_gameport(struct via82xx *chip) { }
+#endif
+
+
+/*
+ *
+ */
+
+static int __devinit snd_via8233_init_misc(struct via82xx *chip)
+{
+	int i, err, caps;
+	unsigned char val;
+
+	caps = chip->chip_type == TYPE_VIA8233A ? 1 : 2;
+	for (i = 0; i < caps; i++) {
+		snd_via8233_capture_source.index = i;
+		err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_via8233_capture_source, chip));
+		if (err < 0)
+			return err;
+	}
+	if (ac97_can_spdif(chip->ac97)) {
+		err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_via8233_dxs3_spdif_control, chip));
+		if (err < 0)
+			return err;
+	}
+	if (chip->chip_type != TYPE_VIA8233A) {
+		/* when no h/w PCM volume control is found, use DXS volume control
+		 * as the PCM vol control
+		 */
+		struct snd_ctl_elem_id sid;
+		memset(&sid, 0, sizeof(sid));
+		strcpy(sid.name, "PCM Playback Volume");
+		sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+		if (! snd_ctl_find_id(chip->card, &sid)) {
+			snd_printd(KERN_INFO "Using DXS as PCM Playback\n");
+			err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_via8233_pcmdxs_volume_control, chip));
+			if (err < 0)
+				return err;
+		}
+		else /* Using DXS when PCM emulation is enabled is really weird */
+		{
+			for (i = 0; i < 4; ++i) {
+				struct snd_kcontrol *kctl;
+
+				kctl = snd_ctl_new1(
+					&snd_via8233_dxs_volume_control, chip);
+				if (!kctl)
+					return -ENOMEM;
+				kctl->id.subdevice = i;
+				err = snd_ctl_add(chip->card, kctl);
+				if (err < 0)
+					return err;
+				chip->dxs_controls[i] = kctl;
+			}
+		}
+	}
+	/* select spdif data slot 10/11 */
+	pci_read_config_byte(chip->pci, VIA8233_SPDIF_CTRL, &val);
+	val = (val & ~VIA8233_SPDIF_SLOT_MASK) | VIA8233_SPDIF_SLOT_1011;
+	val &= ~VIA8233_SPDIF_DX3; /* SPDIF off as default */
+	pci_write_config_byte(chip->pci, VIA8233_SPDIF_CTRL, val);
+
+	return 0;
+}
+
+static int __devinit snd_via686_init_misc(struct via82xx *chip)
+{
+	unsigned char legacy, legacy_cfg;
+	int rev_h = 0;
+
+	legacy = chip->old_legacy;
+	legacy_cfg = chip->old_legacy_cfg;
+	legacy |= VIA_FUNC_MIDI_IRQMASK;	/* FIXME: correct? (disable MIDI) */
+	legacy &= ~VIA_FUNC_ENABLE_GAME;	/* disable joystick */
+	if (chip->revision >= VIA_REV_686_H) {
+		rev_h = 1;
+		if (mpu_port >= 0x200) {	/* force MIDI */
+			mpu_port &= 0xfffc;
+			pci_write_config_dword(chip->pci, 0x18, mpu_port | 0x01);
+#ifdef CONFIG_PM
+			chip->mpu_port_saved = mpu_port;
+#endif
+		} else {
+			mpu_port = pci_resource_start(chip->pci, 2);
+		}
+	} else {
+		switch (mpu_port) {	/* force MIDI */
+		case 0x300:
+		case 0x310:
+		case 0x320:
+		case 0x330:
+			legacy_cfg &= ~(3 << 2);
+			legacy_cfg |= (mpu_port & 0x0030) >> 2;
+			break;
+		default:			/* no, use BIOS settings */
+			if (legacy & VIA_FUNC_ENABLE_MIDI)
+				mpu_port = 0x300 + ((legacy_cfg & 0x000c) << 2);
+			break;
+		}
+	}
+	if (mpu_port >= 0x200 &&
+	    (chip->mpu_res = request_region(mpu_port, 2, "VIA82xx MPU401"))
+	    != NULL) {
+		if (rev_h)
+			legacy |= VIA_FUNC_MIDI_PNP;	/* enable PCI I/O 2 */
+		legacy |= VIA_FUNC_ENABLE_MIDI;
+	} else {
+		if (rev_h)
+			legacy &= ~VIA_FUNC_MIDI_PNP;	/* disable PCI I/O 2 */
+		legacy &= ~VIA_FUNC_ENABLE_MIDI;
+		mpu_port = 0;
+	}
+
+	pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, legacy);
+	pci_write_config_byte(chip->pci, VIA_PNP_CONTROL, legacy_cfg);
+	if (chip->mpu_res) {
+		if (snd_mpu401_uart_new(chip->card, 0, MPU401_HW_VIA686A,
+					mpu_port, MPU401_INFO_INTEGRATED,
+					chip->irq, 0, &chip->rmidi) < 0) {
+			printk(KERN_WARNING "unable to initialize MPU-401"
+			       " at 0x%lx, skipping\n", mpu_port);
+			legacy &= ~VIA_FUNC_ENABLE_MIDI;
+		} else {
+			legacy &= ~VIA_FUNC_MIDI_IRQMASK;	/* enable MIDI interrupt */
+		}
+		pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, legacy);
+	}
+
+	snd_via686_create_gameport(chip, &legacy);
+
+#ifdef CONFIG_PM
+	chip->legacy_saved = legacy;
+	chip->legacy_cfg_saved = legacy_cfg;
+#endif
+
+	return 0;
+}
+
+
+/*
+ * proc interface
+ */
+static void snd_via82xx_proc_read(struct snd_info_entry *entry,
+				  struct snd_info_buffer *buffer)
+{
+	struct via82xx *chip = entry->private_data;
+	int i;
+	
+	snd_iprintf(buffer, "%s\n\n", chip->card->longname);
+	for (i = 0; i < 0xa0; i += 4) {
+		snd_iprintf(buffer, "%02x: %08x\n", i, inl(chip->port + i));
+	}
+}
+
+static void __devinit snd_via82xx_proc_init(struct via82xx *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(chip->card, "via82xx", &entry))
+		snd_info_set_text_ops(entry, chip, snd_via82xx_proc_read);
+}
+
+/*
+ *
+ */
+
+static int snd_via82xx_chip_init(struct via82xx *chip)
+{
+	unsigned int val;
+	unsigned long end_time;
+	unsigned char pval;
+
+#if 0 /* broken on K7M? */
+	if (chip->chip_type == TYPE_VIA686)
+		/* disable all legacy ports */
+		pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, 0);
+#endif
+	pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval);
+	if (! (pval & VIA_ACLINK_C00_READY)) { /* codec not ready? */
+		/* deassert ACLink reset, force SYNC */
+		pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL,
+				      VIA_ACLINK_CTRL_ENABLE |
+				      VIA_ACLINK_CTRL_RESET |
+				      VIA_ACLINK_CTRL_SYNC);
+		udelay(100);
+#if 1 /* FIXME: should we do full reset here for all chip models? */
+		pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, 0x00);
+		udelay(100);
+#else
+		/* deassert ACLink reset, force SYNC (warm AC'97 reset) */
+		pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL,
+				      VIA_ACLINK_CTRL_RESET|VIA_ACLINK_CTRL_SYNC);
+		udelay(2);
+#endif
+		/* ACLink on, deassert ACLink reset, VSR, SGD data out */
+		/* note - FM data out has trouble with non VRA codecs !! */
+		pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, VIA_ACLINK_CTRL_INIT);
+		udelay(100);
+	}
+	
+	/* Make sure VRA is enabled, in case we didn't do a
+	 * complete codec reset, above */
+	pci_read_config_byte(chip->pci, VIA_ACLINK_CTRL, &pval);
+	if ((pval & VIA_ACLINK_CTRL_INIT) != VIA_ACLINK_CTRL_INIT) {
+		/* ACLink on, deassert ACLink reset, VSR, SGD data out */
+		/* note - FM data out has trouble with non VRA codecs !! */
+		pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, VIA_ACLINK_CTRL_INIT);
+		udelay(100);
+	}
+
+	/* wait until codec ready */
+	end_time = jiffies + msecs_to_jiffies(750);
+	do {
+		pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval);
+		if (pval & VIA_ACLINK_C00_READY) /* primary codec ready */
+			break;
+		schedule_timeout_uninterruptible(1);
+	} while (time_before(jiffies, end_time));
+
+	if ((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_BUSY)
+		snd_printk(KERN_ERR "AC'97 codec is not ready [0x%x]\n", val);
+
+#if 0 /* FIXME: we don't support the second codec yet so skip the detection now.. */
+	snd_via82xx_codec_xwrite(chip, VIA_REG_AC97_READ |
+				 VIA_REG_AC97_SECONDARY_VALID |
+				 (VIA_REG_AC97_CODEC_ID_SECONDARY << VIA_REG_AC97_CODEC_ID_SHIFT));
+	end_time = jiffies + msecs_to_jiffies(750);
+	snd_via82xx_codec_xwrite(chip, VIA_REG_AC97_READ |
+				 VIA_REG_AC97_SECONDARY_VALID |
+				 (VIA_REG_AC97_CODEC_ID_SECONDARY << VIA_REG_AC97_CODEC_ID_SHIFT));
+	do {
+		if ((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_SECONDARY_VALID) {
+			chip->ac97_secondary = 1;
+			goto __ac97_ok2;
+		}
+		schedule_timeout_uninterruptible(1);
+	} while (time_before(jiffies, end_time));
+	/* This is ok, the most of motherboards have only one codec */
+
+      __ac97_ok2:
+#endif
+
+	if (chip->chip_type == TYPE_VIA686) {
+		/* route FM trap to IRQ, disable FM trap */
+		pci_write_config_byte(chip->pci, VIA_FM_NMI_CTRL, 0);
+		/* disable all GPI interrupts */
+		outl(0, VIAREG(chip, GPI_INTR));
+	}
+
+	if (chip->chip_type != TYPE_VIA686) {
+		/* Workaround for Award BIOS bug:
+		 * DXS channels don't work properly with VRA if MC97 is disabled.
+		 */
+		struct pci_dev *pci;
+		pci = pci_get_device(0x1106, 0x3068, NULL); /* MC97 */
+		if (pci) {
+			unsigned char data;
+			pci_read_config_byte(pci, 0x44, &data);
+			pci_write_config_byte(pci, 0x44, data | 0x40);
+			pci_dev_put(pci);
+		}
+	}
+
+	if (chip->chip_type != TYPE_VIA8233A) {
+		int i, idx;
+		for (idx = 0; idx < 4; idx++) {
+			unsigned long port = chip->port + 0x10 * idx;
+			for (i = 0; i < 2; i++) {
+				chip->playback_volume[idx][i]=chip->playback_volume_c[i];
+				outb(chip->playback_volume_c[i],
+				     port + VIA_REG_OFS_PLAYBACK_VOLUME_L + i);
+			}
+		}
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+static int snd_via82xx_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct via82xx *chip = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	for (i = 0; i < 2; i++)
+		snd_pcm_suspend_all(chip->pcms[i]);
+	for (i = 0; i < chip->num_devs; i++)
+		snd_via82xx_channel_reset(chip, &chip->devs[i]);
+	synchronize_irq(chip->irq);
+	snd_ac97_suspend(chip->ac97);
+
+	/* save misc values */
+	if (chip->chip_type != TYPE_VIA686) {
+		pci_read_config_byte(chip->pci, VIA8233_SPDIF_CTRL, &chip->spdif_ctrl_saved);
+		chip->capture_src_saved[0] = inb(chip->port + VIA_REG_CAPTURE_CHANNEL);
+		chip->capture_src_saved[1] = inb(chip->port + VIA_REG_CAPTURE_CHANNEL + 0x10);
+	}
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_via82xx_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct via82xx *chip = card->private_data;
+	int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "via82xx: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_via82xx_chip_init(chip);
+
+	if (chip->chip_type == TYPE_VIA686) {
+		if (chip->mpu_port_saved)
+			pci_write_config_dword(chip->pci, 0x18, chip->mpu_port_saved | 0x01);
+		pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, chip->legacy_saved);
+		pci_write_config_byte(chip->pci, VIA_PNP_CONTROL, chip->legacy_cfg_saved);
+	} else {
+		pci_write_config_byte(chip->pci, VIA8233_SPDIF_CTRL, chip->spdif_ctrl_saved);
+		outb(chip->capture_src_saved[0], chip->port + VIA_REG_CAPTURE_CHANNEL);
+		outb(chip->capture_src_saved[1], chip->port + VIA_REG_CAPTURE_CHANNEL + 0x10);
+	}
+
+	snd_ac97_resume(chip->ac97);
+
+	for (i = 0; i < chip->num_devs; i++)
+		snd_via82xx_channel_reset(chip, &chip->devs[i]);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static int snd_via82xx_free(struct via82xx *chip)
+{
+	unsigned int i;
+
+	if (chip->irq < 0)
+		goto __end_hw;
+	/* disable interrupts */
+	for (i = 0; i < chip->num_devs; i++)
+		snd_via82xx_channel_reset(chip, &chip->devs[i]);
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+ __end_hw:
+	release_and_free_resource(chip->mpu_res);
+	pci_release_regions(chip->pci);
+
+	if (chip->chip_type == TYPE_VIA686) {
+		snd_via686_free_gameport(chip);
+		pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE, chip->old_legacy);
+		pci_write_config_byte(chip->pci, VIA_PNP_CONTROL, chip->old_legacy_cfg);
+	}
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_via82xx_dev_free(struct snd_device *device)
+{
+	struct via82xx *chip = device->device_data;
+	return snd_via82xx_free(chip);
+}
+
+static int __devinit snd_via82xx_create(struct snd_card *card,
+					struct pci_dev *pci,
+					int chip_type,
+					int revision,
+					unsigned int ac97_clock,
+					struct via82xx ** r_via)
+{
+	struct via82xx *chip;
+	int err;
+        static struct snd_device_ops ops = {
+		.dev_free =	snd_via82xx_dev_free,
+        };
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	if ((chip = kzalloc(sizeof(*chip), GFP_KERNEL)) == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	chip->chip_type = chip_type;
+	chip->revision = revision;
+
+	spin_lock_init(&chip->reg_lock);
+	spin_lock_init(&chip->rates[0].lock);
+	spin_lock_init(&chip->rates[1].lock);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	pci_read_config_byte(pci, VIA_FUNC_ENABLE, &chip->old_legacy);
+	pci_read_config_byte(pci, VIA_PNP_CONTROL, &chip->old_legacy_cfg);
+	pci_write_config_byte(chip->pci, VIA_FUNC_ENABLE,
+			      chip->old_legacy & ~(VIA_FUNC_ENABLE_SB|VIA_FUNC_ENABLE_FM));
+
+	if ((err = pci_request_regions(pci, card->driver)) < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+	chip->port = pci_resource_start(pci, 0);
+	if (request_irq(pci->irq,
+			chip_type == TYPE_VIA8233 ?
+			snd_via8233_interrupt :	snd_via686_interrupt,
+			IRQF_SHARED,
+			card->driver, chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_via82xx_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+	if (ac97_clock >= 8000 && ac97_clock <= 48000)
+		chip->ac97_clock = ac97_clock;
+	synchronize_irq(chip->irq);
+
+	if ((err = snd_via82xx_chip_init(chip)) < 0) {
+		snd_via82xx_free(chip);
+		return err;
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_via82xx_free(chip);
+		return err;
+	}
+
+	/* The 8233 ac97 controller does not implement the master bit
+	 * in the pci command register. IMHO this is a violation of the PCI spec.
+	 * We call pci_set_master here because it does not hurt. */
+	pci_set_master(pci);
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*r_via = chip;
+	return 0;
+}
+
+struct via823x_info {
+	int revision;
+	char *name;
+	int type;
+};
+static struct via823x_info via823x_cards[] __devinitdata = {
+	{ VIA_REV_PRE_8233, "VIA 8233-Pre", TYPE_VIA8233 },
+	{ VIA_REV_8233C, "VIA 8233C", TYPE_VIA8233 },
+	{ VIA_REV_8233, "VIA 8233", TYPE_VIA8233 },
+	{ VIA_REV_8233A, "VIA 8233A", TYPE_VIA8233A },
+	{ VIA_REV_8235, "VIA 8235", TYPE_VIA8233 },
+	{ VIA_REV_8237, "VIA 8237", TYPE_VIA8233 },
+	{ VIA_REV_8251, "VIA 8251", TYPE_VIA8233 },
+};
+
+/*
+ * auto detection of DXS channel supports.
+ */
+
+static struct snd_pci_quirk dxs_whitelist[] __devinitdata = {
+	SND_PCI_QUIRK(0x1005, 0x4710, "Avance Logic Mobo", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK(0x1019, 0x0996, "ESC Mobo", VIA_DXS_48K),
+	SND_PCI_QUIRK(0x1019, 0x0a81, "ECS K7VTA3 v8.0", VIA_DXS_NO_VRA),
+	SND_PCI_QUIRK(0x1019, 0x0a85, "ECS L7VMM2", VIA_DXS_NO_VRA),
+	SND_PCI_QUIRK_VENDOR(0x1019, "ESC K8", VIA_DXS_SRC),
+	SND_PCI_QUIRK(0x1019, 0xaa01, "ESC K8T890-A", VIA_DXS_SRC),
+	SND_PCI_QUIRK(0x1025, 0x0033, "Acer Inspire 1353LM", VIA_DXS_NO_VRA),
+	SND_PCI_QUIRK(0x1025, 0x0046, "Acer Aspire 1524 WLMi", VIA_DXS_SRC),
+	SND_PCI_QUIRK_VENDOR(0x1043, "ASUS A7/A8", VIA_DXS_NO_VRA),
+	SND_PCI_QUIRK_VENDOR(0x1071, "Diverse Notebook", VIA_DXS_NO_VRA),
+	SND_PCI_QUIRK(0x10cf, 0x118e, "FSC Laptop", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK_VENDOR(0x1106, "ASRock", VIA_DXS_SRC),
+	SND_PCI_QUIRK(0x1297, 0xa231, "Shuttle AK31v2", VIA_DXS_SRC),
+	SND_PCI_QUIRK(0x1297, 0xa232, "Shuttle", VIA_DXS_SRC),
+	SND_PCI_QUIRK(0x1297, 0xc160, "Shuttle Sk41G", VIA_DXS_SRC),
+	SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte GA-7VAXP", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK(0x1462, 0x3800, "MSI KT266", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK(0x1462, 0x7120, "MSI KT4V", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK(0x1462, 0x7142, "MSI K8MM-V", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK_VENDOR(0x1462, "MSI Mobo", VIA_DXS_SRC),
+	SND_PCI_QUIRK(0x147b, 0x1401, "ABIT KD7(-RAID)", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK(0x147b, 0x1411, "ABIT VA-20", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK(0x147b, 0x1413, "ABIT KV8 Pro", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK(0x147b, 0x1415, "ABIT AV8", VIA_DXS_NO_VRA),
+	SND_PCI_QUIRK(0x14ff, 0x0403, "Twinhead mobo", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK(0x14ff, 0x0408, "Twinhead laptop", VIA_DXS_SRC),
+	SND_PCI_QUIRK(0x1558, 0x4701, "Clevo D470", VIA_DXS_SRC),
+	SND_PCI_QUIRK(0x1584, 0x8120, "Diverse Laptop", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK(0x1584, 0x8123, "Targa/Uniwill", VIA_DXS_NO_VRA),
+	SND_PCI_QUIRK(0x161f, 0x202b, "Amira Notebook", VIA_DXS_NO_VRA),
+	SND_PCI_QUIRK(0x161f, 0x2032, "m680x machines", VIA_DXS_48K),
+	SND_PCI_QUIRK(0x1631, 0xe004, "PB EasyNote 3174", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK(0x1695, 0x3005, "EPoX EP-8K9A", VIA_DXS_ENABLE),
+	SND_PCI_QUIRK_VENDOR(0x1695, "EPoX mobo", VIA_DXS_SRC),
+	SND_PCI_QUIRK_VENDOR(0x16f3, "Jetway K8", VIA_DXS_SRC),
+	SND_PCI_QUIRK_VENDOR(0x1734, "FSC Laptop", VIA_DXS_SRC),
+	SND_PCI_QUIRK(0x1849, 0x3059, "ASRock K7VM2", VIA_DXS_NO_VRA),
+	SND_PCI_QUIRK_VENDOR(0x1849, "ASRock mobo", VIA_DXS_SRC),
+	SND_PCI_QUIRK(0x1919, 0x200a, "Soltek SL-K8",  VIA_DXS_NO_VRA),
+	SND_PCI_QUIRK(0x4005, 0x4710, "MSI K7T266", VIA_DXS_SRC),
+	{ } /* terminator */
+};
+
+static int __devinit check_dxs_list(struct pci_dev *pci, int revision)
+{
+	const struct snd_pci_quirk *w;
+
+	w = snd_pci_quirk_lookup(pci, dxs_whitelist);
+	if (w) {
+		snd_printdd(KERN_INFO "via82xx: DXS white list for %s found\n",
+			    w->name);
+		return w->value;
+	}
+
+	/* for newer revision, default to DXS_SRC */
+	if (revision >= VIA_REV_8235)
+		return VIA_DXS_SRC;
+
+	/*
+	 * not detected, try 48k rate only to be sure.
+	 */
+	printk(KERN_INFO "via82xx: Assuming DXS channels with 48k fixed sample rate.\n");
+	printk(KERN_INFO "         Please try dxs_support=5 option\n");
+	printk(KERN_INFO "         and report if it works on your machine.\n");
+	printk(KERN_INFO "         For more details, read ALSA-Configuration.txt.\n");
+	return VIA_DXS_48K;
+};
+
+static int __devinit snd_via82xx_probe(struct pci_dev *pci,
+				       const struct pci_device_id *pci_id)
+{
+	struct snd_card *card;
+	struct via82xx *chip;
+	int chip_type = 0, card_type;
+	unsigned int i;
+	int err;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	card_type = pci_id->driver_data;
+	switch (card_type) {
+	case TYPE_CARD_VIA686:
+		strcpy(card->driver, "VIA686A");
+		sprintf(card->shortname, "VIA 82C686A/B rev%x", pci->revision);
+		chip_type = TYPE_VIA686;
+		break;
+	case TYPE_CARD_VIA8233:
+		chip_type = TYPE_VIA8233;
+		sprintf(card->shortname, "VIA 823x rev%x", pci->revision);
+		for (i = 0; i < ARRAY_SIZE(via823x_cards); i++) {
+			if (pci->revision == via823x_cards[i].revision) {
+				chip_type = via823x_cards[i].type;
+				strcpy(card->shortname, via823x_cards[i].name);
+				break;
+			}
+		}
+		if (chip_type != TYPE_VIA8233A) {
+			if (dxs_support == VIA_DXS_AUTO)
+				dxs_support = check_dxs_list(pci, pci->revision);
+			/* force to use VIA8233 or 8233A model according to
+			 * dxs_support module option
+			 */
+			if (dxs_support == VIA_DXS_DISABLE)
+				chip_type = TYPE_VIA8233A;
+			else
+				chip_type = TYPE_VIA8233;
+		}
+		if (chip_type == TYPE_VIA8233A)
+			strcpy(card->driver, "VIA8233A");
+		else if (pci->revision >= VIA_REV_8237)
+			strcpy(card->driver, "VIA8237"); /* no slog assignment */
+		else
+			strcpy(card->driver, "VIA8233");
+		break;
+	default:
+		snd_printk(KERN_ERR "invalid card type %d\n", card_type);
+		err = -EINVAL;
+		goto __error;
+	}
+		
+	if ((err = snd_via82xx_create(card, pci, chip_type, pci->revision,
+				      ac97_clock, &chip)) < 0)
+		goto __error;
+	card->private_data = chip;
+	if ((err = snd_via82xx_mixer_new(chip, ac97_quirk)) < 0)
+		goto __error;
+
+	if (chip_type == TYPE_VIA686) {
+		if ((err = snd_via686_pcm_new(chip)) < 0 ||
+		    (err = snd_via686_init_misc(chip)) < 0)
+			goto __error;
+	} else {
+		if (chip_type == TYPE_VIA8233A) {
+			if ((err = snd_via8233a_pcm_new(chip)) < 0)
+				goto __error;
+			// chip->dxs_fixed = 1; /* FIXME: use 48k for DXS #3? */
+		} else {
+			if ((err = snd_via8233_pcm_new(chip)) < 0)
+				goto __error;
+			if (dxs_support == VIA_DXS_48K)
+				chip->dxs_fixed = 1;
+			else if (dxs_support == VIA_DXS_NO_VRA)
+				chip->no_vra = 1;
+			else if (dxs_support == VIA_DXS_SRC) {
+				chip->no_vra = 1;
+				chip->dxs_src = 1;
+			}
+		}
+		if ((err = snd_via8233_init_misc(chip)) < 0)
+			goto __error;
+	}
+
+	/* disable interrupts */
+	for (i = 0; i < chip->num_devs; i++)
+		snd_via82xx_channel_reset(chip, &chip->devs[i]);
+
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s with %s at %#lx, irq %d", card->shortname,
+		 snd_ac97_get_short_name(chip->ac97), chip->port, chip->irq);
+
+	snd_via82xx_proc_init(chip);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	return 0;
+
+ __error:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit snd_via82xx_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "VIA 82xx Audio",
+	.id_table = snd_via82xx_ids,
+	.probe = snd_via82xx_probe,
+	.remove = __devexit_p(snd_via82xx_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_via82xx_suspend,
+	.resume = snd_via82xx_resume,
+#endif
+};
+
+static int __init alsa_card_via82xx_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_via82xx_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_via82xx_init)
+module_exit(alsa_card_via82xx_exit)
diff --git a/linux/sound/pci/via82xx_modem.c b/linux/sound/pci/via82xx_modem.c
new file mode 100644
index 0000000..f7e8bbb
--- /dev/null
+++ b/linux/sound/pci/via82xx_modem.c
@@ -0,0 +1,1248 @@
+/*
+ *   ALSA modem driver for VIA VT82xx (South Bridge)
+ *
+ *   VT82C686A/B/C, VT8233A/C, VT8235
+ *
+ *	Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ *	                   Tjeerd.Mulder <Tjeerd.Mulder@fujitsu-siemens.com>
+ *                    2002 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ * Changes:
+ *
+ * Sep. 2,  2004  Sasha Khapyorsky <sashak@alsa-project.org>
+ *      Modified from original audio driver 'via82xx.c' to support AC97
+ *      modems.
+ */
+
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+
+#if 0
+#define POINTER_DEBUG
+#endif
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("VIA VT82xx modem");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{VIA,VT82C686A/B/C modem,pci}}");
+
+static int index = -2; /* Exclude the first card */
+static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
+static int ac97_clock = 48000;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for VIA 82xx bridge.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for VIA 82xx bridge.");
+module_param(ac97_clock, int, 0444);
+MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (default 48000Hz).");
+
+/* just for backward compatibility */
+static int enable;
+module_param(enable, bool, 0444);
+
+
+/*
+ *  Direct registers
+ */
+
+#define VIAREG(via, x) ((via)->port + VIA_REG_##x)
+#define VIADEV_REG(viadev, x) ((viadev)->port + VIA_REG_##x)
+
+/* common offsets */
+#define VIA_REG_OFFSET_STATUS		0x00	/* byte - channel status */
+#define   VIA_REG_STAT_ACTIVE		0x80	/* RO */
+#define   VIA_REG_STAT_PAUSED		0x40	/* RO */
+#define   VIA_REG_STAT_TRIGGER_QUEUED	0x08	/* RO */
+#define   VIA_REG_STAT_STOPPED		0x04	/* RWC */
+#define   VIA_REG_STAT_EOL		0x02	/* RWC */
+#define   VIA_REG_STAT_FLAG		0x01	/* RWC */
+#define VIA_REG_OFFSET_CONTROL		0x01	/* byte - channel control */
+#define   VIA_REG_CTRL_START		0x80	/* WO */
+#define   VIA_REG_CTRL_TERMINATE	0x40	/* WO */
+#define   VIA_REG_CTRL_AUTOSTART	0x20
+#define   VIA_REG_CTRL_PAUSE		0x08	/* RW */
+#define   VIA_REG_CTRL_INT_STOP		0x04		
+#define   VIA_REG_CTRL_INT_EOL		0x02
+#define   VIA_REG_CTRL_INT_FLAG		0x01
+#define   VIA_REG_CTRL_RESET		0x01	/* RW - probably reset? undocumented */
+#define   VIA_REG_CTRL_INT (VIA_REG_CTRL_INT_FLAG | VIA_REG_CTRL_INT_EOL | VIA_REG_CTRL_AUTOSTART)
+#define VIA_REG_OFFSET_TYPE		0x02	/* byte - channel type (686 only) */
+#define   VIA_REG_TYPE_AUTOSTART	0x80	/* RW - autostart at EOL */
+#define   VIA_REG_TYPE_16BIT		0x20	/* RW */
+#define   VIA_REG_TYPE_STEREO		0x10	/* RW */
+#define   VIA_REG_TYPE_INT_LLINE	0x00
+#define   VIA_REG_TYPE_INT_LSAMPLE	0x04
+#define   VIA_REG_TYPE_INT_LESSONE	0x08
+#define   VIA_REG_TYPE_INT_MASK		0x0c
+#define   VIA_REG_TYPE_INT_EOL		0x02
+#define   VIA_REG_TYPE_INT_FLAG		0x01
+#define VIA_REG_OFFSET_TABLE_PTR	0x04	/* dword - channel table pointer */
+#define VIA_REG_OFFSET_CURR_PTR		0x04	/* dword - channel current pointer */
+#define VIA_REG_OFFSET_STOP_IDX		0x08	/* dword - stop index, channel type, sample rate */
+#define VIA_REG_OFFSET_CURR_COUNT	0x0c	/* dword - channel current count (24 bit) */
+#define VIA_REG_OFFSET_CURR_INDEX	0x0f	/* byte - channel current index (for via8233 only) */
+
+#define DEFINE_VIA_REGSET(name,val) \
+enum {\
+	VIA_REG_##name##_STATUS		= (val),\
+	VIA_REG_##name##_CONTROL	= (val) + 0x01,\
+	VIA_REG_##name##_TYPE		= (val) + 0x02,\
+	VIA_REG_##name##_TABLE_PTR	= (val) + 0x04,\
+	VIA_REG_##name##_CURR_PTR	= (val) + 0x04,\
+	VIA_REG_##name##_STOP_IDX	= (val) + 0x08,\
+	VIA_REG_##name##_CURR_COUNT	= (val) + 0x0c,\
+}
+
+/* modem block */
+DEFINE_VIA_REGSET(MO, 0x40);
+DEFINE_VIA_REGSET(MI, 0x50);
+
+/* AC'97 */
+#define VIA_REG_AC97			0x80	/* dword */
+#define   VIA_REG_AC97_CODEC_ID_MASK	(3<<30)
+#define   VIA_REG_AC97_CODEC_ID_SHIFT	30
+#define   VIA_REG_AC97_CODEC_ID_PRIMARY	0x00
+#define   VIA_REG_AC97_CODEC_ID_SECONDARY 0x01
+#define   VIA_REG_AC97_SECONDARY_VALID	(1<<27)
+#define   VIA_REG_AC97_PRIMARY_VALID	(1<<25)
+#define   VIA_REG_AC97_BUSY		(1<<24)
+#define   VIA_REG_AC97_READ		(1<<23)
+#define   VIA_REG_AC97_CMD_SHIFT	16
+#define   VIA_REG_AC97_CMD_MASK		0x7e
+#define   VIA_REG_AC97_DATA_SHIFT	0
+#define   VIA_REG_AC97_DATA_MASK	0xffff
+
+#define VIA_REG_SGD_SHADOW		0x84	/* dword */
+#define   VIA_REG_SGD_STAT_PB_FLAG	(1<<0)
+#define   VIA_REG_SGD_STAT_CP_FLAG	(1<<1)
+#define   VIA_REG_SGD_STAT_FM_FLAG	(1<<2)
+#define   VIA_REG_SGD_STAT_PB_EOL	(1<<4)
+#define   VIA_REG_SGD_STAT_CP_EOL	(1<<5)
+#define   VIA_REG_SGD_STAT_FM_EOL	(1<<6)
+#define   VIA_REG_SGD_STAT_PB_STOP	(1<<8)
+#define   VIA_REG_SGD_STAT_CP_STOP	(1<<9)
+#define   VIA_REG_SGD_STAT_FM_STOP	(1<<10)
+#define   VIA_REG_SGD_STAT_PB_ACTIVE	(1<<12)
+#define   VIA_REG_SGD_STAT_CP_ACTIVE	(1<<13)
+#define   VIA_REG_SGD_STAT_FM_ACTIVE	(1<<14)
+#define   VIA_REG_SGD_STAT_MR_FLAG      (1<<16)
+#define   VIA_REG_SGD_STAT_MW_FLAG      (1<<17)
+#define   VIA_REG_SGD_STAT_MR_EOL       (1<<20)
+#define   VIA_REG_SGD_STAT_MW_EOL       (1<<21)
+#define   VIA_REG_SGD_STAT_MR_STOP      (1<<24)
+#define   VIA_REG_SGD_STAT_MW_STOP      (1<<25)
+#define   VIA_REG_SGD_STAT_MR_ACTIVE    (1<<28)
+#define   VIA_REG_SGD_STAT_MW_ACTIVE    (1<<29)
+
+#define VIA_REG_GPI_STATUS		0x88
+#define VIA_REG_GPI_INTR		0x8c
+
+#define VIA_TBL_BIT_FLAG	0x40000000
+#define VIA_TBL_BIT_EOL		0x80000000
+
+/* pci space */
+#define VIA_ACLINK_STAT		0x40
+#define  VIA_ACLINK_C11_READY	0x20
+#define  VIA_ACLINK_C10_READY	0x10
+#define  VIA_ACLINK_C01_READY	0x04 /* secondary codec ready */
+#define  VIA_ACLINK_LOWPOWER	0x02 /* low-power state */
+#define  VIA_ACLINK_C00_READY	0x01 /* primary codec ready */
+#define VIA_ACLINK_CTRL		0x41
+#define  VIA_ACLINK_CTRL_ENABLE	0x80 /* 0: disable, 1: enable */
+#define  VIA_ACLINK_CTRL_RESET	0x40 /* 0: assert, 1: de-assert */
+#define  VIA_ACLINK_CTRL_SYNC	0x20 /* 0: release SYNC, 1: force SYNC hi */
+#define  VIA_ACLINK_CTRL_SDO	0x10 /* 0: release SDO, 1: force SDO hi */
+#define  VIA_ACLINK_CTRL_VRA	0x08 /* 0: disable VRA, 1: enable VRA */
+#define  VIA_ACLINK_CTRL_PCM	0x04 /* 0: disable PCM, 1: enable PCM */
+#define  VIA_ACLINK_CTRL_FM	0x02 /* via686 only */
+#define  VIA_ACLINK_CTRL_SB	0x01 /* via686 only */
+#define  VIA_ACLINK_CTRL_INIT	(VIA_ACLINK_CTRL_ENABLE|\
+				 VIA_ACLINK_CTRL_RESET|\
+				 VIA_ACLINK_CTRL_PCM)
+#define VIA_FUNC_ENABLE		0x42
+#define  VIA_FUNC_MIDI_PNP	0x80 /* FIXME: it's 0x40 in the datasheet! */
+#define  VIA_FUNC_MIDI_IRQMASK	0x40 /* FIXME: not documented! */
+#define  VIA_FUNC_RX2C_WRITE	0x20
+#define  VIA_FUNC_SB_FIFO_EMPTY	0x10
+#define  VIA_FUNC_ENABLE_GAME	0x08
+#define  VIA_FUNC_ENABLE_FM	0x04
+#define  VIA_FUNC_ENABLE_MIDI	0x02
+#define  VIA_FUNC_ENABLE_SB	0x01
+#define VIA_PNP_CONTROL		0x43
+#define VIA_MC97_CTRL		0x44
+#define  VIA_MC97_CTRL_ENABLE   0x80
+#define  VIA_MC97_CTRL_SECONDARY 0x40
+#define  VIA_MC97_CTRL_INIT     (VIA_MC97_CTRL_ENABLE|\
+                                 VIA_MC97_CTRL_SECONDARY)
+
+
+/*
+ * pcm stream
+ */
+
+struct snd_via_sg_table {
+	unsigned int offset;
+	unsigned int size;
+} ;
+
+#define VIA_TABLE_SIZE	255
+
+struct viadev {
+	unsigned int reg_offset;
+	unsigned long port;
+	int direction;	/* playback = 0, capture = 1 */
+        struct snd_pcm_substream *substream;
+	int running;
+	unsigned int tbl_entries; /* # descriptors */
+	struct snd_dma_buffer table;
+	struct snd_via_sg_table *idx_table;
+	/* for recovery from the unexpected pointer */
+	unsigned int lastpos;
+	unsigned int bufsize;
+	unsigned int bufsize2;
+};
+
+enum { TYPE_CARD_VIA82XX_MODEM = 1 };
+
+#define VIA_MAX_MODEM_DEVS	2
+
+struct via82xx_modem {
+	int irq;
+
+	unsigned long port;
+
+	unsigned int intr_mask; /* SGD_SHADOW mask to check interrupts */
+
+	struct pci_dev *pci;
+	struct snd_card *card;
+
+	unsigned int num_devs;
+	unsigned int playback_devno, capture_devno;
+	struct viadev devs[VIA_MAX_MODEM_DEVS];
+
+	struct snd_pcm *pcms[2];
+
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97 *ac97;
+	unsigned int ac97_clock;
+	unsigned int ac97_secondary;	/* secondary AC'97 codec is present */
+
+	spinlock_t reg_lock;
+	struct snd_info_entry *proc_entry;
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_via82xx_modem_ids) = {
+	{ PCI_VDEVICE(VIA, 0x3068), TYPE_CARD_VIA82XX_MODEM, },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_via82xx_modem_ids);
+
+/*
+ */
+
+/*
+ * allocate and initialize the descriptor buffers
+ * periods = number of periods
+ * fragsize = period size in bytes
+ */
+static int build_via_table(struct viadev *dev, struct snd_pcm_substream *substream,
+			   struct pci_dev *pci,
+			   unsigned int periods, unsigned int fragsize)
+{
+	unsigned int i, idx, ofs, rest;
+	struct via82xx_modem *chip = snd_pcm_substream_chip(substream);
+
+	if (dev->table.area == NULL) {
+		/* the start of each lists must be aligned to 8 bytes,
+		 * but the kernel pages are much bigger, so we don't care
+		 */
+		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+					PAGE_ALIGN(VIA_TABLE_SIZE * 2 * 8),
+					&dev->table) < 0)
+			return -ENOMEM;
+	}
+	if (! dev->idx_table) {
+		dev->idx_table = kmalloc(sizeof(*dev->idx_table) * VIA_TABLE_SIZE, GFP_KERNEL);
+		if (! dev->idx_table)
+			return -ENOMEM;
+	}
+
+	/* fill the entries */
+	idx = 0;
+	ofs = 0;
+	for (i = 0; i < periods; i++) {
+		rest = fragsize;
+		/* fill descriptors for a period.
+		 * a period can be split to several descriptors if it's
+		 * over page boundary.
+		 */
+		do {
+			unsigned int r;
+			unsigned int flag;
+			unsigned int addr;
+
+			if (idx >= VIA_TABLE_SIZE) {
+				snd_printk(KERN_ERR "via82xx: too much table size!\n");
+				return -EINVAL;
+			}
+			addr = snd_pcm_sgbuf_get_addr(substream, ofs);
+			((u32 *)dev->table.area)[idx << 1] = cpu_to_le32(addr);
+			r = PAGE_SIZE - (ofs % PAGE_SIZE);
+			if (rest < r)
+				r = rest;
+			rest -= r;
+			if (! rest) {
+				if (i == periods - 1)
+					flag = VIA_TBL_BIT_EOL; /* buffer boundary */
+				else
+					flag = VIA_TBL_BIT_FLAG; /* period boundary */
+			} else
+				flag = 0; /* period continues to the next */
+			/*
+			printk(KERN_DEBUG "via: tbl %d: at %d  size %d "
+			       "(rest %d)\n", idx, ofs, r, rest);
+			*/
+			((u32 *)dev->table.area)[(idx<<1) + 1] = cpu_to_le32(r | flag);
+			dev->idx_table[idx].offset = ofs;
+			dev->idx_table[idx].size = r;
+			ofs += r;
+			idx++;
+		} while (rest > 0);
+	}
+	dev->tbl_entries = idx;
+	dev->bufsize = periods * fragsize;
+	dev->bufsize2 = dev->bufsize / 2;
+	return 0;
+}
+
+
+static int clean_via_table(struct viadev *dev, struct snd_pcm_substream *substream,
+			   struct pci_dev *pci)
+{
+	if (dev->table.area) {
+		snd_dma_free_pages(&dev->table);
+		dev->table.area = NULL;
+	}
+	kfree(dev->idx_table);
+	dev->idx_table = NULL;
+	return 0;
+}
+
+/*
+ *  Basic I/O
+ */
+
+static inline unsigned int snd_via82xx_codec_xread(struct via82xx_modem *chip)
+{
+	return inl(VIAREG(chip, AC97));
+}
+ 
+static inline void snd_via82xx_codec_xwrite(struct via82xx_modem *chip, unsigned int val)
+{
+	outl(val, VIAREG(chip, AC97));
+}
+ 
+static int snd_via82xx_codec_ready(struct via82xx_modem *chip, int secondary)
+{
+	unsigned int timeout = 1000;	/* 1ms */
+	unsigned int val;
+	
+	while (timeout-- > 0) {
+		udelay(1);
+		if (!((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_BUSY))
+			return val & 0xffff;
+	}
+	snd_printk(KERN_ERR "codec_ready: codec %i is not ready [0x%x]\n",
+		   secondary, snd_via82xx_codec_xread(chip));
+	return -EIO;
+}
+ 
+static int snd_via82xx_codec_valid(struct via82xx_modem *chip, int secondary)
+{
+	unsigned int timeout = 1000;	/* 1ms */
+	unsigned int val, val1;
+	unsigned int stat = !secondary ? VIA_REG_AC97_PRIMARY_VALID :
+					 VIA_REG_AC97_SECONDARY_VALID;
+	
+	while (timeout-- > 0) {
+		val = snd_via82xx_codec_xread(chip);
+		val1 = val & (VIA_REG_AC97_BUSY | stat);
+		if (val1 == stat)
+			return val & 0xffff;
+		udelay(1);
+	}
+	return -EIO;
+}
+ 
+static void snd_via82xx_codec_wait(struct snd_ac97 *ac97)
+{
+	struct via82xx_modem *chip = ac97->private_data;
+	int err;
+	err = snd_via82xx_codec_ready(chip, ac97->num);
+	/* here we need to wait fairly for long time.. */
+	msleep(500);
+}
+
+static void snd_via82xx_codec_write(struct snd_ac97 *ac97,
+				    unsigned short reg,
+				    unsigned short val)
+{
+	struct via82xx_modem *chip = ac97->private_data;
+	unsigned int xval;
+	if(reg == AC97_GPIO_STATUS) {
+		outl(val, VIAREG(chip, GPI_STATUS));
+		return;
+	}	
+	xval = !ac97->num ? VIA_REG_AC97_CODEC_ID_PRIMARY : VIA_REG_AC97_CODEC_ID_SECONDARY;
+	xval <<= VIA_REG_AC97_CODEC_ID_SHIFT;
+	xval |= reg << VIA_REG_AC97_CMD_SHIFT;
+	xval |= val << VIA_REG_AC97_DATA_SHIFT;
+	snd_via82xx_codec_xwrite(chip, xval);
+	snd_via82xx_codec_ready(chip, ac97->num);
+}
+
+static unsigned short snd_via82xx_codec_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	struct via82xx_modem *chip = ac97->private_data;
+	unsigned int xval, val = 0xffff;
+	int again = 0;
+
+	xval = ac97->num << VIA_REG_AC97_CODEC_ID_SHIFT;
+	xval |= ac97->num ? VIA_REG_AC97_SECONDARY_VALID : VIA_REG_AC97_PRIMARY_VALID;
+	xval |= VIA_REG_AC97_READ;
+	xval |= (reg & 0x7f) << VIA_REG_AC97_CMD_SHIFT;
+      	while (1) {
+      		if (again++ > 3) {
+			snd_printk(KERN_ERR "codec_read: codec %i is not valid [0x%x]\n",
+				   ac97->num, snd_via82xx_codec_xread(chip));
+		      	return 0xffff;
+		}
+		snd_via82xx_codec_xwrite(chip, xval);
+		udelay (20);
+		if (snd_via82xx_codec_valid(chip, ac97->num) >= 0) {
+			udelay(25);
+			val = snd_via82xx_codec_xread(chip);
+			break;
+		}
+	}
+	return val & 0xffff;
+}
+
+static void snd_via82xx_channel_reset(struct via82xx_modem *chip, struct viadev *viadev)
+{
+	outb(VIA_REG_CTRL_PAUSE | VIA_REG_CTRL_TERMINATE | VIA_REG_CTRL_RESET,
+	     VIADEV_REG(viadev, OFFSET_CONTROL));
+	inb(VIADEV_REG(viadev, OFFSET_CONTROL));
+	udelay(50);
+	/* disable interrupts */
+	outb(0x00, VIADEV_REG(viadev, OFFSET_CONTROL));
+	/* clear interrupts */
+	outb(0x03, VIADEV_REG(viadev, OFFSET_STATUS));
+	outb(0x00, VIADEV_REG(viadev, OFFSET_TYPE)); /* for via686 */
+	// outl(0, VIADEV_REG(viadev, OFFSET_CURR_PTR));
+	viadev->lastpos = 0;
+}
+
+
+/*
+ *  Interrupt handler
+ */
+
+static irqreturn_t snd_via82xx_interrupt(int irq, void *dev_id)
+{
+	struct via82xx_modem *chip = dev_id;
+	unsigned int status;
+	unsigned int i;
+
+	status = inl(VIAREG(chip, SGD_SHADOW));
+	if (! (status & chip->intr_mask)) {
+		return IRQ_NONE;
+	}
+// _skip_sgd:
+
+	/* check status for each stream */
+	spin_lock(&chip->reg_lock);
+	for (i = 0; i < chip->num_devs; i++) {
+		struct viadev *viadev = &chip->devs[i];
+		unsigned char c_status = inb(VIADEV_REG(viadev, OFFSET_STATUS));
+		c_status &= (VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG|VIA_REG_STAT_STOPPED);
+		if (! c_status)
+			continue;
+		if (viadev->substream && viadev->running) {
+			spin_unlock(&chip->reg_lock);
+			snd_pcm_period_elapsed(viadev->substream);
+			spin_lock(&chip->reg_lock);
+		}
+		outb(c_status, VIADEV_REG(viadev, OFFSET_STATUS)); /* ack */
+	}
+	spin_unlock(&chip->reg_lock);
+	return IRQ_HANDLED;
+}
+
+/*
+ *  PCM callbacks
+ */
+
+/*
+ * trigger callback
+ */
+static int snd_via82xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct via82xx_modem *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	unsigned char val = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		val |= VIA_REG_CTRL_START;
+		viadev->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		val = VIA_REG_CTRL_TERMINATE;
+		viadev->running = 0;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		val |= VIA_REG_CTRL_PAUSE;
+		viadev->running = 0;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		viadev->running = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	outb(val, VIADEV_REG(viadev, OFFSET_CONTROL));
+	if (cmd == SNDRV_PCM_TRIGGER_STOP)
+		snd_via82xx_channel_reset(chip, viadev);
+	return 0;
+}
+
+/*
+ * pointer callbacks
+ */
+
+/*
+ * calculate the linear position at the given sg-buffer index and the rest count
+ */
+
+#define check_invalid_pos(viadev,pos) \
+	((pos) < viadev->lastpos && ((pos) >= viadev->bufsize2 ||\
+				     viadev->lastpos < viadev->bufsize2))
+
+static inline unsigned int calc_linear_pos(struct viadev *viadev, unsigned int idx,
+					   unsigned int count)
+{
+	unsigned int size, res;
+
+	size = viadev->idx_table[idx].size;
+	res = viadev->idx_table[idx].offset + size - count;
+
+	/* check the validity of the calculated position */
+	if (size < count) {
+		snd_printd(KERN_ERR "invalid via82xx_cur_ptr (size = %d, count = %d)\n",
+			   (int)size, (int)count);
+		res = viadev->lastpos;
+	} else if (check_invalid_pos(viadev, res)) {
+#ifdef POINTER_DEBUG
+		printk(KERN_DEBUG "fail: idx = %i/%i, lastpos = 0x%x, "
+		       "bufsize2 = 0x%x, offsize = 0x%x, size = 0x%x, "
+		       "count = 0x%x\n", idx, viadev->tbl_entries, viadev->lastpos,
+		       viadev->bufsize2, viadev->idx_table[idx].offset,
+		       viadev->idx_table[idx].size, count);
+#endif
+		if (count && size < count) {
+			snd_printd(KERN_ERR "invalid via82xx_cur_ptr, "
+				   "using last valid pointer\n");
+			res = viadev->lastpos;
+		} else {
+			if (! count)
+				/* bogus count 0 on the DMA boundary? */
+				res = viadev->idx_table[idx].offset;
+			else
+				/* count register returns full size
+				 * when end of buffer is reached
+				 */
+				res = viadev->idx_table[idx].offset + size;
+			if (check_invalid_pos(viadev, res)) {
+				snd_printd(KERN_ERR "invalid via82xx_cur_ptr (2), "
+					   "using last valid pointer\n");
+				res = viadev->lastpos;
+			}
+		}
+	}
+	viadev->lastpos = res; /* remember the last position */
+	if (res >= viadev->bufsize)
+		res -= viadev->bufsize;
+	return res;
+}
+
+/*
+ * get the current pointer on via686
+ */
+static snd_pcm_uframes_t snd_via686_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct via82xx_modem *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	unsigned int idx, ptr, count, res;
+
+	if (snd_BUG_ON(!viadev->tbl_entries))
+		return 0;
+	if (!(inb(VIADEV_REG(viadev, OFFSET_STATUS)) & VIA_REG_STAT_ACTIVE))
+		return 0;
+
+	spin_lock(&chip->reg_lock);
+	count = inl(VIADEV_REG(viadev, OFFSET_CURR_COUNT)) & 0xffffff;
+	/* The via686a does not have the current index register,
+	 * so we need to calculate the index from CURR_PTR.
+	 */
+	ptr = inl(VIADEV_REG(viadev, OFFSET_CURR_PTR));
+	if (ptr <= (unsigned int)viadev->table.addr)
+		idx = 0;
+	else /* CURR_PTR holds the address + 8 */
+		idx = ((ptr - (unsigned int)viadev->table.addr) / 8 - 1) %
+			viadev->tbl_entries;
+	res = calc_linear_pos(viadev, idx, count);
+	spin_unlock(&chip->reg_lock);
+
+	return bytes_to_frames(substream->runtime, res);
+}
+
+/*
+ * hw_params callback:
+ * allocate the buffer and build up the buffer description table
+ */
+static int snd_via82xx_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	struct via82xx_modem *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	err = build_via_table(viadev, substream, chip->pci,
+			      params_periods(hw_params),
+			      params_period_bytes(hw_params));
+	if (err < 0)
+		return err;
+
+	snd_ac97_write(chip->ac97, AC97_LINE1_RATE, params_rate(hw_params));
+	snd_ac97_write(chip->ac97, AC97_LINE1_LEVEL, 0);
+
+	return 0;
+}
+
+/*
+ * hw_free callback:
+ * clean up the buffer description table and release the buffer
+ */
+static int snd_via82xx_hw_free(struct snd_pcm_substream *substream)
+{
+	struct via82xx_modem *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+
+	clean_via_table(viadev, substream, chip->pci);
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+
+/*
+ * set up the table pointer
+ */
+static void snd_via82xx_set_table_ptr(struct via82xx_modem *chip, struct viadev *viadev)
+{
+	snd_via82xx_codec_ready(chip, chip->ac97_secondary);
+	outl((u32)viadev->table.addr, VIADEV_REG(viadev, OFFSET_TABLE_PTR));
+	udelay(20);
+	snd_via82xx_codec_ready(chip, chip->ac97_secondary);
+}
+
+/*
+ * prepare callback for playback and capture
+ */
+static int snd_via82xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct via82xx_modem *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = substream->runtime->private_data;
+
+	snd_via82xx_channel_reset(chip, viadev);
+	/* this must be set after channel_reset */
+	snd_via82xx_set_table_ptr(chip, viadev);
+	outb(VIA_REG_TYPE_AUTOSTART|VIA_REG_TYPE_INT_EOL|VIA_REG_TYPE_INT_FLAG,
+	     VIADEV_REG(viadev, OFFSET_TYPE));
+	return 0;
+}
+
+/*
+ * pcm hardware definition, identical for both playback and capture
+ */
+static struct snd_pcm_hardware snd_via82xx_hw =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 /* SNDRV_PCM_INFO_RESUME | */
+				 SNDRV_PCM_INFO_PAUSE),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_KNOT,
+	.rate_min =		8000,
+	.rate_max =		16000,
+	.channels_min =		1,
+	.channels_max =		1,
+	.buffer_bytes_max =	128 * 1024,
+	.period_bytes_min =	32,
+	.period_bytes_max =	128 * 1024,
+	.periods_min =		2,
+	.periods_max =		VIA_TABLE_SIZE / 2,
+	.fifo_size =		0,
+};
+
+
+/*
+ * open callback skeleton
+ */
+static int snd_via82xx_modem_pcm_open(struct via82xx_modem *chip, struct viadev *viadev,
+				      struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+        static unsigned int rates[] = { 8000,  9600, 12000, 16000 };
+        static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+                .count = ARRAY_SIZE(rates),
+                .list = rates,
+                .mask = 0,
+        };
+
+	runtime->hw = snd_via82xx_hw;
+	
+        if ((err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+					      &hw_constraints_rates)) < 0)
+                return err;
+
+	/* we may remove following constaint when we modify table entries
+	   in interrupt */
+	if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+
+	runtime->private_data = viadev;
+	viadev->substream = substream;
+
+	return 0;
+}
+
+
+/*
+ * open callback for playback
+ */
+static int snd_via82xx_playback_open(struct snd_pcm_substream *substream)
+{
+	struct via82xx_modem *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = &chip->devs[chip->playback_devno + substream->number];
+
+	return snd_via82xx_modem_pcm_open(chip, viadev, substream);
+}
+
+/*
+ * open callback for capture
+ */
+static int snd_via82xx_capture_open(struct snd_pcm_substream *substream)
+{
+	struct via82xx_modem *chip = snd_pcm_substream_chip(substream);
+	struct viadev *viadev = &chip->devs[chip->capture_devno + substream->pcm->device];
+
+	return snd_via82xx_modem_pcm_open(chip, viadev, substream);
+}
+
+/*
+ * close callback
+ */
+static int snd_via82xx_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct viadev *viadev = substream->runtime->private_data;
+
+	viadev->substream = NULL;
+	return 0;
+}
+
+
+/* via686 playback callbacks */
+static struct snd_pcm_ops snd_via686_playback_ops = {
+	.open =		snd_via82xx_playback_open,
+	.close =	snd_via82xx_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_via82xx_hw_params,
+	.hw_free =	snd_via82xx_hw_free,
+	.prepare =	snd_via82xx_pcm_prepare,
+	.trigger =	snd_via82xx_pcm_trigger,
+	.pointer =	snd_via686_pcm_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+/* via686 capture callbacks */
+static struct snd_pcm_ops snd_via686_capture_ops = {
+	.open =		snd_via82xx_capture_open,
+	.close =	snd_via82xx_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_via82xx_hw_params,
+	.hw_free =	snd_via82xx_hw_free,
+	.prepare =	snd_via82xx_pcm_prepare,
+	.trigger =	snd_via82xx_pcm_trigger,
+	.pointer =	snd_via686_pcm_pointer,
+	.page =		snd_pcm_sgbuf_ops_page,
+};
+
+
+static void init_viadev(struct via82xx_modem *chip, int idx, unsigned int reg_offset,
+			int direction)
+{
+	chip->devs[idx].reg_offset = reg_offset;
+	chip->devs[idx].direction = direction;
+	chip->devs[idx].port = chip->port + reg_offset;
+}
+
+/*
+ * create a pcm instance for via686a/b
+ */
+static int __devinit snd_via686_pcm_new(struct via82xx_modem *chip)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	chip->playback_devno = 0;
+	chip->capture_devno = 1;
+	chip->num_devs = 2;
+	chip->intr_mask = 0x330000; /* FLAGS | EOL for MR, MW */
+
+	err = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via686_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via686_capture_ops);
+	pcm->dev_class = SNDRV_PCM_CLASS_MODEM;
+	pcm->private_data = chip;
+	strcpy(pcm->name, chip->card->shortname);
+	chip->pcms[0] = pcm;
+	init_viadev(chip, 0, VIA_REG_MO_STATUS, 0);
+	init_viadev(chip, 1, VIA_REG_MI_STATUS, 1);
+
+	if ((err = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+							 snd_dma_pci_data(chip->pci),
+							 64*1024, 128*1024)) < 0)
+		return err;
+
+	return 0;
+}
+
+
+/*
+ *  Mixer part
+ */
+
+
+static void snd_via82xx_mixer_free_ac97_bus(struct snd_ac97_bus *bus)
+{
+	struct via82xx_modem *chip = bus->private_data;
+	chip->ac97_bus = NULL;
+}
+
+static void snd_via82xx_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+	struct via82xx_modem *chip = ac97->private_data;
+	chip->ac97 = NULL;
+}
+
+
+static int __devinit snd_via82xx_mixer_new(struct via82xx_modem *chip)
+{
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_via82xx_codec_write,
+		.read = snd_via82xx_codec_read,
+		.wait = snd_via82xx_codec_wait,
+	};
+
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus)) < 0)
+		return err;
+	chip->ac97_bus->private_free = snd_via82xx_mixer_free_ac97_bus;
+	chip->ac97_bus->clock = chip->ac97_clock;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.private_free = snd_via82xx_mixer_free_ac97;
+	ac97.pci = chip->pci;
+	ac97.scaps = AC97_SCAP_SKIP_AUDIO | AC97_SCAP_POWER_SAVE;
+	ac97.num = chip->ac97_secondary;
+
+	if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97)) < 0)
+		return err;
+
+	return 0;
+}
+
+
+/*
+ * proc interface
+ */
+static void snd_via82xx_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct via82xx_modem *chip = entry->private_data;
+	int i;
+	
+	snd_iprintf(buffer, "%s\n\n", chip->card->longname);
+	for (i = 0; i < 0xa0; i += 4) {
+		snd_iprintf(buffer, "%02x: %08x\n", i, inl(chip->port + i));
+	}
+}
+
+static void __devinit snd_via82xx_proc_init(struct via82xx_modem *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(chip->card, "via82xx", &entry))
+		snd_info_set_text_ops(entry, chip, snd_via82xx_proc_read);
+}
+
+/*
+ *
+ */
+
+static int snd_via82xx_chip_init(struct via82xx_modem *chip)
+{
+	unsigned int val;
+	unsigned long end_time;
+	unsigned char pval;
+
+	pci_read_config_byte(chip->pci, VIA_MC97_CTRL, &pval);
+	if((pval & VIA_MC97_CTRL_INIT) != VIA_MC97_CTRL_INIT) {
+		pci_write_config_byte(chip->pci, 0x44, pval|VIA_MC97_CTRL_INIT);
+		udelay(100);
+	}
+
+	pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval);
+	if (! (pval & VIA_ACLINK_C00_READY)) { /* codec not ready? */
+		/* deassert ACLink reset, force SYNC */
+		pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL,
+				      VIA_ACLINK_CTRL_ENABLE |
+				      VIA_ACLINK_CTRL_RESET |
+				      VIA_ACLINK_CTRL_SYNC);
+		udelay(100);
+#if 1 /* FIXME: should we do full reset here for all chip models? */
+		pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, 0x00);
+		udelay(100);
+#else
+		/* deassert ACLink reset, force SYNC (warm AC'97 reset) */
+		pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL,
+				      VIA_ACLINK_CTRL_RESET|VIA_ACLINK_CTRL_SYNC);
+		udelay(2);
+#endif
+		/* ACLink on, deassert ACLink reset, VSR, SGD data out */
+		pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, VIA_ACLINK_CTRL_INIT);
+		udelay(100);
+	}
+	
+	pci_read_config_byte(chip->pci, VIA_ACLINK_CTRL, &pval);
+	if ((pval & VIA_ACLINK_CTRL_INIT) != VIA_ACLINK_CTRL_INIT) {
+		/* ACLink on, deassert ACLink reset, VSR, SGD data out */
+		pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, VIA_ACLINK_CTRL_INIT);
+		udelay(100);
+	}
+
+	/* wait until codec ready */
+	end_time = jiffies + msecs_to_jiffies(750);
+	do {
+		pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval);
+		if (pval & VIA_ACLINK_C00_READY) /* primary codec ready */
+			break;
+		schedule_timeout_uninterruptible(1);
+	} while (time_before(jiffies, end_time));
+
+	if ((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_BUSY)
+		snd_printk(KERN_ERR "AC'97 codec is not ready [0x%x]\n", val);
+
+	snd_via82xx_codec_xwrite(chip, VIA_REG_AC97_READ |
+				 VIA_REG_AC97_SECONDARY_VALID |
+				 (VIA_REG_AC97_CODEC_ID_SECONDARY << VIA_REG_AC97_CODEC_ID_SHIFT));
+	end_time = jiffies + msecs_to_jiffies(750);
+	snd_via82xx_codec_xwrite(chip, VIA_REG_AC97_READ |
+				 VIA_REG_AC97_SECONDARY_VALID |
+				 (VIA_REG_AC97_CODEC_ID_SECONDARY << VIA_REG_AC97_CODEC_ID_SHIFT));
+	do {
+		if ((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_SECONDARY_VALID) {
+			chip->ac97_secondary = 1;
+			goto __ac97_ok2;
+		}
+		schedule_timeout_uninterruptible(1);
+	} while (time_before(jiffies, end_time));
+	/* This is ok, the most of motherboards have only one codec */
+
+      __ac97_ok2:
+
+	/* route FM trap to IRQ, disable FM trap */
+	// pci_write_config_byte(chip->pci, VIA_FM_NMI_CTRL, 0);
+	/* disable all GPI interrupts */
+	outl(0, VIAREG(chip, GPI_INTR));
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+static int snd_via82xx_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct via82xx_modem *chip = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	for (i = 0; i < 2; i++)
+		snd_pcm_suspend_all(chip->pcms[i]);
+	for (i = 0; i < chip->num_devs; i++)
+		snd_via82xx_channel_reset(chip, &chip->devs[i]);
+	synchronize_irq(chip->irq);
+	snd_ac97_suspend(chip->ac97);
+
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+static int snd_via82xx_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct via82xx_modem *chip = card->private_data;
+	int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "via82xx-modem: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+
+	snd_via82xx_chip_init(chip);
+
+	snd_ac97_resume(chip->ac97);
+
+	for (i = 0; i < chip->num_devs; i++)
+		snd_via82xx_channel_reset(chip, &chip->devs[i]);
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static int snd_via82xx_free(struct via82xx_modem *chip)
+{
+	unsigned int i;
+
+	if (chip->irq < 0)
+		goto __end_hw;
+	/* disable interrupts */
+	for (i = 0; i < chip->num_devs; i++)
+		snd_via82xx_channel_reset(chip, &chip->devs[i]);
+
+      __end_hw:
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_via82xx_dev_free(struct snd_device *device)
+{
+	struct via82xx_modem *chip = device->device_data;
+	return snd_via82xx_free(chip);
+}
+
+static int __devinit snd_via82xx_create(struct snd_card *card,
+					struct pci_dev *pci,
+					int chip_type,
+					int revision,
+					unsigned int ac97_clock,
+					struct via82xx_modem ** r_via)
+{
+	struct via82xx_modem *chip;
+	int err;
+        static struct snd_device_ops ops = {
+		.dev_free =	snd_via82xx_dev_free,
+        };
+
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	if ((chip = kzalloc(sizeof(*chip), GFP_KERNEL)) == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&chip->reg_lock);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	if ((err = pci_request_regions(pci, card->driver)) < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+	chip->port = pci_resource_start(pci, 0);
+	if (request_irq(pci->irq, snd_via82xx_interrupt, IRQF_SHARED,
+			card->driver, chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_via82xx_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+	if (ac97_clock >= 8000 && ac97_clock <= 48000)
+		chip->ac97_clock = ac97_clock;
+	synchronize_irq(chip->irq);
+
+	if ((err = snd_via82xx_chip_init(chip)) < 0) {
+		snd_via82xx_free(chip);
+		return err;
+	}
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_via82xx_free(chip);
+		return err;
+	}
+
+	/* The 8233 ac97 controller does not implement the master bit
+	 * in the pci command register. IMHO this is a violation of the PCI spec.
+	 * We call pci_set_master here because it does not hurt. */
+	pci_set_master(pci);
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*r_via = chip;
+	return 0;
+}
+
+
+static int __devinit snd_via82xx_probe(struct pci_dev *pci,
+				       const struct pci_device_id *pci_id)
+{
+	struct snd_card *card;
+	struct via82xx_modem *chip;
+	int chip_type = 0, card_type;
+	unsigned int i;
+	int err;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	card_type = pci_id->driver_data;
+	switch (card_type) {
+	case TYPE_CARD_VIA82XX_MODEM:
+		strcpy(card->driver, "VIA82XX-MODEM");
+		sprintf(card->shortname, "VIA 82XX modem");
+		break;
+	default:
+		snd_printk(KERN_ERR "invalid card type %d\n", card_type);
+		err = -EINVAL;
+		goto __error;
+	}
+		
+	if ((err = snd_via82xx_create(card, pci, chip_type, pci->revision,
+				      ac97_clock, &chip)) < 0)
+		goto __error;
+	card->private_data = chip;
+	if ((err = snd_via82xx_mixer_new(chip)) < 0)
+		goto __error;
+
+	if ((err = snd_via686_pcm_new(chip)) < 0 )
+		goto __error;
+
+	/* disable interrupts */
+	for (i = 0; i < chip->num_devs; i++)
+		snd_via82xx_channel_reset(chip, &chip->devs[i]);
+
+	sprintf(card->longname, "%s at 0x%lx, irq %d",
+		card->shortname, chip->port, chip->irq);
+
+	snd_via82xx_proc_init(chip);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	return 0;
+
+ __error:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit snd_via82xx_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "VIA 82xx Modem",
+	.id_table = snd_via82xx_modem_ids,
+	.probe = snd_via82xx_probe,
+	.remove = __devexit_p(snd_via82xx_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_via82xx_suspend,
+	.resume = snd_via82xx_resume,
+#endif
+};
+
+static int __init alsa_card_via82xx_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_via82xx_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_via82xx_init)
+module_exit(alsa_card_via82xx_exit)
diff --git a/linux/sound/pci/vx222/Makefile b/linux/sound/pci/vx222/Makefile
new file mode 100644
index 0000000..a4d08d4
--- /dev/null
+++ b/linux/sound/pci/vx222/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-vx222-objs := vx222.o vx222_ops.o
+
+obj-$(CONFIG_SND_VX222) += snd-vx222.o
diff --git a/linux/sound/pci/vx222/vx222.c b/linux/sound/pci/vx222/vx222.c
new file mode 100644
index 0000000..99a9a81
--- /dev/null
+++ b/linux/sound/pci/vx222/vx222.c
@@ -0,0 +1,314 @@
+/*
+ * Driver for Digigram VX222 V2/Mic PCI soundcards
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include "vx222.h"
+
+#define CARD_NAME "VX222"
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("Digigram VX222 V2/Mic");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Digigram," CARD_NAME "}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static int mic[SNDRV_CARDS]; /* microphone */
+static int ibl[SNDRV_CARDS]; /* microphone */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Digigram " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Digigram " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Digigram " CARD_NAME " soundcard.");
+module_param_array(mic, bool, NULL, 0444);
+MODULE_PARM_DESC(mic, "Enable Microphone.");
+module_param_array(ibl, int, NULL, 0444);
+MODULE_PARM_DESC(ibl, "Capture IBL size.");
+
+/*
+ */
+
+enum {
+	VX_PCI_VX222_OLD,
+	VX_PCI_VX222_NEW
+};
+
+static DEFINE_PCI_DEVICE_TABLE(snd_vx222_ids) = {
+	{ 0x10b5, 0x9050, 0x1369, PCI_ANY_ID, 0, 0, VX_PCI_VX222_OLD, },   /* PLX */
+	{ 0x10b5, 0x9030, 0x1369, PCI_ANY_ID, 0, 0, VX_PCI_VX222_NEW, },   /* PLX */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_vx222_ids);
+
+
+/*
+ */
+
+static const DECLARE_TLV_DB_SCALE(db_scale_old_vol, -11350, 50, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_akm, -7350, 50, 0);
+
+static struct snd_vx_hardware vx222_old_hw = {
+
+	.name = "VX222/Old",
+	.type = VX_TYPE_BOARD,
+	/* hw specs */
+	.num_codecs = 1,
+	.num_ins = 1,
+	.num_outs = 1,
+	.output_level_max = VX_ANALOG_OUT_LEVEL_MAX,
+	.output_level_db_scale = db_scale_old_vol,
+};
+
+static struct snd_vx_hardware vx222_v2_hw = {
+
+	.name = "VX222/v2",
+	.type = VX_TYPE_V2,
+	/* hw specs */
+	.num_codecs = 1,
+	.num_ins = 1,
+	.num_outs = 1,
+	.output_level_max = VX2_AKM_LEVEL_MAX,
+	.output_level_db_scale = db_scale_akm,
+};
+
+static struct snd_vx_hardware vx222_mic_hw = {
+
+	.name = "VX222/Mic",
+	.type = VX_TYPE_MIC,
+	/* hw specs */
+	.num_codecs = 1,
+	.num_ins = 1,
+	.num_outs = 1,
+	.output_level_max = VX2_AKM_LEVEL_MAX,
+	.output_level_db_scale = db_scale_akm,
+};
+
+
+/*
+ */
+static int snd_vx222_free(struct vx_core *chip)
+{
+	struct snd_vx222 *vx = (struct snd_vx222 *)chip;
+
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void*)chip);
+	if (vx->port[0])
+		pci_release_regions(vx->pci);
+	pci_disable_device(vx->pci);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_vx222_dev_free(struct snd_device *device)
+{
+	struct vx_core *chip = device->device_data;
+	return snd_vx222_free(chip);
+}
+
+
+static int __devinit snd_vx222_create(struct snd_card *card, struct pci_dev *pci,
+				      struct snd_vx_hardware *hw,
+				      struct snd_vx222 **rchip)
+{
+	struct vx_core *chip;
+	struct snd_vx222 *vx;
+	int i, err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_vx222_dev_free,
+	};
+	struct snd_vx_ops *vx_ops;
+
+	/* enable PCI device */
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+	pci_set_master(pci);
+
+	vx_ops = hw->type == VX_TYPE_BOARD ? &vx222_old_ops : &vx222_ops;
+	chip = snd_vx_create(card, hw, vx_ops,
+			     sizeof(struct snd_vx222) - sizeof(struct vx_core));
+	if (! chip) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	vx = (struct snd_vx222 *)chip;
+	vx->pci = pci;
+
+	if ((err = pci_request_regions(pci, CARD_NAME)) < 0) {
+		snd_vx222_free(chip);
+		return err;
+	}
+	for (i = 0; i < 2; i++)
+		vx->port[i] = pci_resource_start(pci, i + 1);
+
+	if (request_irq(pci->irq, snd_vx_irq_handler, IRQF_SHARED,
+			CARD_NAME, chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_vx222_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_vx222_free(chip);
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rchip = vx;
+	return 0;
+}
+
+
+static int __devinit snd_vx222_probe(struct pci_dev *pci,
+				     const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_vx_hardware *hw;
+	struct snd_vx222 *vx;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	switch ((int)pci_id->driver_data) {
+	case VX_PCI_VX222_OLD:
+		hw = &vx222_old_hw;
+		break;
+	case VX_PCI_VX222_NEW:
+	default:
+		if (mic[dev])
+			hw = &vx222_mic_hw;
+		else
+			hw = &vx222_v2_hw;
+		break;
+	}
+	if ((err = snd_vx222_create(card, pci, hw, &vx)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = vx;
+	vx->core.ibl.size = ibl[dev];
+
+	sprintf(card->longname, "%s at 0x%lx & 0x%lx, irq %i",
+		card->shortname, vx->port[0], vx->port[1], vx->core.irq);
+	snd_printdd("%s at 0x%lx & 0x%lx, irq %i\n",
+		    card->shortname, vx->port[0], vx->port[1], vx->core.irq);
+
+#ifdef SND_VX_FW_LOADER
+	vx->core.dev = &pci->dev;
+#endif
+
+	if ((err = snd_vx_setup_firmware(&vx->core)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_vx222_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_vx222_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_vx222 *vx = card->private_data;
+	int err;
+
+	err = snd_vx_suspend(&vx->core, state);
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return err;
+}
+
+static int snd_vx222_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_vx222 *vx = card->private_data;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "vx222: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+	return snd_vx_resume(&vx->core);
+}
+#endif
+
+static struct pci_driver driver = {
+	.name = "Digigram VX222",
+	.id_table = snd_vx222_ids,
+	.probe = snd_vx222_probe,
+	.remove = __devexit_p(snd_vx222_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_vx222_suspend,
+	.resume = snd_vx222_resume,
+#endif
+};
+
+static int __init alsa_card_vx222_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_vx222_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_vx222_init)
+module_exit(alsa_card_vx222_exit)
diff --git a/linux/sound/pci/vx222/vx222.h b/linux/sound/pci/vx222/vx222.h
new file mode 100644
index 0000000..2f0d78f
--- /dev/null
+++ b/linux/sound/pci/vx222/vx222.h
@@ -0,0 +1,114 @@
+/*
+ * Driver for Digigram VX222 PCI soundcards
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#ifndef __VX222_H
+#define __VX222_H
+
+#include <sound/vx_core.h>
+
+struct snd_vx222 {
+
+	struct vx_core core;
+
+	/* h/w config; for PLX and for DSP */
+	struct pci_dev *pci;
+	unsigned long port[2];
+
+	unsigned int regCDSP;	/* current CDSP register */
+	unsigned int regCFG;	/* current CFG register */
+	unsigned int regSELMIC;	/* current SELMIC reg. (for VX222 Mic) */
+
+	int input_level[2];	/* input level for vx222 mic */
+	int mic_level;		/* mic level for vx222 mic */
+};
+
+/* we use a lookup table with 148 values, see vx_mixer.c */
+#define VX2_AKM_LEVEL_MAX	0x93
+
+extern struct snd_vx_ops vx222_ops;
+extern struct snd_vx_ops vx222_old_ops;
+
+/* Offset of registers with base equal to portDSP. */
+#define VX_RESET_DMA_REGISTER_OFFSET    0x00000008
+
+/* Constants used to access the INTCSR register. */
+#define VX_INTCSR_VALUE                 0x00000001
+#define VX_PCI_INTERRUPT_MASK           0x00000040
+
+/* Constants used to access the CDSP register (0x20). */
+#define VX_CDSP_TEST1_MASK              0x00000080
+#define VX_CDSP_TOR1_MASK               0x00000040
+#define VX_CDSP_TOR2_MASK               0x00000020
+#define VX_CDSP_RESERVED0_0_MASK        0x00000010
+#define VX_CDSP_CODEC_RESET_MASK        0x00000008
+#define VX_CDSP_VALID_IRQ_MASK          0x00000004
+#define VX_CDSP_TEST0_MASK              0x00000002
+#define VX_CDSP_DSP_RESET_MASK          0x00000001
+
+#define VX_CDSP_GPIO_OUT_MASK           0x00000060
+#define VX_GPIO_OUT_BIT_OFFSET          5               // transform output to bit 0 and 1
+
+/* Constants used to access the CFG register (0x24). */
+#define VX_CFG_SYNCDSP_MASK             0x00000080
+#define VX_CFG_RESERVED0_0_MASK         0x00000040
+#define VX_CFG_RESERVED1_0_MASK         0x00000020
+#define VX_CFG_RESERVED2_0_MASK         0x00000010
+#define VX_CFG_DATAIN_SEL_MASK          0x00000008     // 0 (ana), 1 (UER)
+#define VX_CFG_RESERVED3_0_MASK         0x00000004
+#define VX_CFG_RESERVED4_0_MASK         0x00000002
+#define VX_CFG_CLOCKIN_SEL_MASK         0x00000001     // 0 (internal), 1 (AES/EBU)
+
+/* Constants used to access the STATUS register (0x30). */
+#define VX_STATUS_DATA_XICOR_MASK       0x00000080
+#define VX_STATUS_VAL_TEST1_MASK        0x00000040
+#define VX_STATUS_VAL_TEST0_MASK        0x00000020
+#define VX_STATUS_RESERVED0_MASK        0x00000010
+#define VX_STATUS_VAL_TOR1_MASK         0x00000008
+#define VX_STATUS_VAL_TOR0_MASK         0x00000004
+#define VX_STATUS_LEVEL_IN_MASK         0x00000002    // 6 dBu (0), 22 dBu (1)
+#define VX_STATUS_MEMIRQ_MASK           0x00000001
+
+#define VX_STATUS_GPIO_IN_MASK          0x0000000C
+#define VX_GPIO_IN_BIT_OFFSET           0             // leave input as bit 2 and 3
+
+/* Constants used to access the MICRO INPUT SELECT register (0x40). */
+#define MICRO_SELECT_INPUT_NORM        0x00
+#define MICRO_SELECT_INPUT_MUTE        0x01
+#define MICRO_SELECT_INPUT_LIMIT       0x02
+#define MICRO_SELECT_INPUT_MASK        0x03
+
+#define MICRO_SELECT_PREAMPLI_G_0      0x00
+#define MICRO_SELECT_PREAMPLI_G_1      0x04
+#define MICRO_SELECT_PREAMPLI_G_2      0x08
+#define MICRO_SELECT_PREAMPLI_G_3      0x0C
+#define MICRO_SELECT_PREAMPLI_MASK     0x0C
+#define MICRO_SELECT_PREAMPLI_OFFSET   2
+
+#define MICRO_SELECT_RAISE_COMPR       0x10
+
+#define MICRO_SELECT_NOISE_T_52DB      0x00
+#define MICRO_SELECT_NOISE_T_42DB      0x20
+#define MICRO_SELECT_NOISE_T_32DB      0x40
+#define MICRO_SELECT_NOISE_T_MASK      0x60
+
+#define MICRO_SELECT_PHANTOM_ALIM      0x80
+
+
+#endif /* __VX222_H */
diff --git a/linux/sound/pci/vx222/vx222_ops.c b/linux/sound/pci/vx222/vx222_ops.c
new file mode 100644
index 0000000..a69e774
--- /dev/null
+++ b/linux/sound/pci/vx222/vx222_ops.c
@@ -0,0 +1,1031 @@
+/*
+ * Driver for Digigram VX222 V2/Mic soundcards
+ *
+ * VX222-specific low-level routines
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <asm/io.h>
+#include "vx222.h"
+
+
+static int vx2_reg_offset[VX_REG_MAX] = {
+	[VX_ICR]    = 0x00,
+	[VX_CVR]    = 0x04,
+	[VX_ISR]    = 0x08,
+	[VX_IVR]    = 0x0c,
+	[VX_RXH]    = 0x14,
+	[VX_RXM]    = 0x18,
+	[VX_RXL]    = 0x1c,
+	[VX_DMA]    = 0x10,
+	[VX_CDSP]   = 0x20,
+	[VX_CFG]    = 0x24,
+	[VX_RUER]   = 0x28,
+	[VX_DATA]   = 0x2c,
+	[VX_STATUS] = 0x30,
+	[VX_LOFREQ] = 0x34,
+	[VX_HIFREQ] = 0x38,
+	[VX_CSUER]  = 0x3c,
+	[VX_SELMIC] = 0x40,
+	[VX_COMPOT] = 0x44, // Write: POTENTIOMETER ; Read: COMPRESSION LEVEL activate
+	[VX_SCOMPR] = 0x48, // Read: COMPRESSION THRESHOLD activate
+	[VX_GLIMIT] = 0x4c, // Read: LEVEL LIMITATION activate
+	[VX_INTCSR] = 0x4c, // VX_INTCSR_REGISTER_OFFSET
+	[VX_CNTRL]  = 0x50,		// VX_CNTRL_REGISTER_OFFSET
+	[VX_GPIOC]  = 0x54,		// VX_GPIOC (new with PLX9030)
+};
+
+static int vx2_reg_index[VX_REG_MAX] = {
+	[VX_ICR]	= 1,
+	[VX_CVR]	= 1,
+	[VX_ISR]	= 1,
+	[VX_IVR]	= 1,
+	[VX_RXH]	= 1,
+	[VX_RXM]	= 1,
+	[VX_RXL]	= 1,
+	[VX_DMA]	= 1,
+	[VX_CDSP]	= 1,
+	[VX_CFG]	= 1,
+	[VX_RUER]	= 1,
+	[VX_DATA]	= 1,
+	[VX_STATUS]	= 1,
+	[VX_LOFREQ]	= 1,
+	[VX_HIFREQ]	= 1,
+	[VX_CSUER]	= 1,
+	[VX_SELMIC]	= 1,
+	[VX_COMPOT]	= 1,
+	[VX_SCOMPR]	= 1,
+	[VX_GLIMIT]	= 1,
+	[VX_INTCSR]	= 0,	/* on the PLX */
+	[VX_CNTRL]	= 0,	/* on the PLX */
+	[VX_GPIOC]	= 0,	/* on the PLX */
+};
+
+static inline unsigned long vx2_reg_addr(struct vx_core *_chip, int reg)
+{
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+	return chip->port[vx2_reg_index[reg]] + vx2_reg_offset[reg];
+}
+
+/**
+ * snd_vx_inb - read a byte from the register
+ * @offset: register enum
+ */
+static unsigned char vx2_inb(struct vx_core *chip, int offset)
+{
+	return inb(vx2_reg_addr(chip, offset));
+}
+
+/**
+ * snd_vx_outb - write a byte on the register
+ * @offset: the register offset
+ * @val: the value to write
+ */
+static void vx2_outb(struct vx_core *chip, int offset, unsigned char val)
+{
+	outb(val, vx2_reg_addr(chip, offset));
+	/*
+	printk(KERN_DEBUG "outb: %x -> %x\n", val, vx2_reg_addr(chip, offset));
+	*/
+}
+
+/**
+ * snd_vx_inl - read a 32bit word from the register
+ * @offset: register enum
+ */
+static unsigned int vx2_inl(struct vx_core *chip, int offset)
+{
+	return inl(vx2_reg_addr(chip, offset));
+}
+
+/**
+ * snd_vx_outl - write a 32bit word on the register
+ * @offset: the register enum
+ * @val: the value to write
+ */
+static void vx2_outl(struct vx_core *chip, int offset, unsigned int val)
+{
+	/*
+	printk(KERN_DEBUG "outl: %x -> %x\n", val, vx2_reg_addr(chip, offset));
+	*/
+	outl(val, vx2_reg_addr(chip, offset));
+}
+
+/*
+ * redefine macros to call directly
+ */
+#undef vx_inb
+#define vx_inb(chip,reg)	vx2_inb((struct vx_core*)(chip), VX_##reg)
+#undef vx_outb
+#define vx_outb(chip,reg,val)	vx2_outb((struct vx_core*)(chip), VX_##reg, val)
+#undef vx_inl
+#define vx_inl(chip,reg)	vx2_inl((struct vx_core*)(chip), VX_##reg)
+#undef vx_outl
+#define vx_outl(chip,reg,val)	vx2_outl((struct vx_core*)(chip), VX_##reg, val)
+
+
+/*
+ * vx_reset_dsp - reset the DSP
+ */
+
+#define XX_DSP_RESET_WAIT_TIME		2	/* ms */
+
+static void vx2_reset_dsp(struct vx_core *_chip)
+{
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+
+	/* set the reset dsp bit to 0 */
+	vx_outl(chip, CDSP, chip->regCDSP & ~VX_CDSP_DSP_RESET_MASK);
+
+	mdelay(XX_DSP_RESET_WAIT_TIME);
+
+	chip->regCDSP |= VX_CDSP_DSP_RESET_MASK;
+	/* set the reset dsp bit to 1 */
+	vx_outl(chip, CDSP, chip->regCDSP);
+}
+
+
+static int vx2_test_xilinx(struct vx_core *_chip)
+{
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+	unsigned int data;
+
+	snd_printdd("testing xilinx...\n");
+	/* This test uses several write/read sequences on TEST0 and TEST1 bits
+	 * to figure out whever or not the xilinx was correctly loaded
+	 */
+
+	/* We write 1 on CDSP.TEST0. We should get 0 on STATUS.TEST0. */
+	vx_outl(chip, CDSP, chip->regCDSP | VX_CDSP_TEST0_MASK);
+	vx_inl(chip, ISR);
+	data = vx_inl(chip, STATUS);
+	if ((data & VX_STATUS_VAL_TEST0_MASK) == VX_STATUS_VAL_TEST0_MASK) {
+		snd_printdd("bad!\n");
+		return -ENODEV;
+	}
+
+	/* We write 0 on CDSP.TEST0. We should get 1 on STATUS.TEST0. */
+	vx_outl(chip, CDSP, chip->regCDSP & ~VX_CDSP_TEST0_MASK);
+	vx_inl(chip, ISR);
+	data = vx_inl(chip, STATUS);
+	if (! (data & VX_STATUS_VAL_TEST0_MASK)) {
+		snd_printdd("bad! #2\n");
+		return -ENODEV;
+	}
+
+	if (_chip->type == VX_TYPE_BOARD) {
+		/* not implemented on VX_2_BOARDS */
+		/* We write 1 on CDSP.TEST1. We should get 0 on STATUS.TEST1. */
+		vx_outl(chip, CDSP, chip->regCDSP | VX_CDSP_TEST1_MASK);
+		vx_inl(chip, ISR);
+		data = vx_inl(chip, STATUS);
+		if ((data & VX_STATUS_VAL_TEST1_MASK) == VX_STATUS_VAL_TEST1_MASK) {
+			snd_printdd("bad! #3\n");
+			return -ENODEV;
+		}
+
+		/* We write 0 on CDSP.TEST1. We should get 1 on STATUS.TEST1. */
+		vx_outl(chip, CDSP, chip->regCDSP & ~VX_CDSP_TEST1_MASK);
+		vx_inl(chip, ISR);
+		data = vx_inl(chip, STATUS);
+		if (! (data & VX_STATUS_VAL_TEST1_MASK)) {
+			snd_printdd("bad! #4\n");
+			return -ENODEV;
+		}
+	}
+	snd_printdd("ok, xilinx fine.\n");
+	return 0;
+}
+
+
+/**
+ * vx_setup_pseudo_dma - set up the pseudo dma read/write mode.
+ * @do_write: 0 = read, 1 = set up for DMA write
+ */
+static void vx2_setup_pseudo_dma(struct vx_core *chip, int do_write)
+{
+	/* Interrupt mode and HREQ pin enabled for host transmit data transfers
+	 * (in case of the use of the pseudo-dma facility).
+	 */
+	vx_outl(chip, ICR, do_write ? ICR_TREQ : ICR_RREQ);
+
+	/* Reset the pseudo-dma register (in case of the use of the
+	 * pseudo-dma facility).
+	 */
+	vx_outl(chip, RESET_DMA, 0);
+}
+
+/*
+ * vx_release_pseudo_dma - disable the pseudo-DMA mode
+ */
+static inline void vx2_release_pseudo_dma(struct vx_core *chip)
+{
+	/* HREQ pin disabled. */
+	vx_outl(chip, ICR, 0);
+}
+
+
+
+/* pseudo-dma write */
+static void vx2_dma_write(struct vx_core *chip, struct snd_pcm_runtime *runtime,
+			  struct vx_pipe *pipe, int count)
+{
+	unsigned long port = vx2_reg_addr(chip, VX_DMA);
+	int offset = pipe->hw_ptr;
+	u32 *addr = (u32 *)(runtime->dma_area + offset);
+
+	if (snd_BUG_ON(count % 4))
+		return;
+
+	vx2_setup_pseudo_dma(chip, 1);
+
+	/* Transfer using pseudo-dma.
+	 */
+	if (offset + count > pipe->buffer_bytes) {
+		int length = pipe->buffer_bytes - offset;
+		count -= length;
+		length >>= 2; /* in 32bit words */
+		/* Transfer using pseudo-dma. */
+		while (length-- > 0) {
+			outl(cpu_to_le32(*addr), port);
+			addr++;
+		}
+		addr = (u32 *)runtime->dma_area;
+		pipe->hw_ptr = 0;
+	}
+	pipe->hw_ptr += count;
+	count >>= 2; /* in 32bit words */
+	/* Transfer using pseudo-dma. */
+	while (count-- > 0) {
+		outl(cpu_to_le32(*addr), port);
+		addr++;
+	}
+
+	vx2_release_pseudo_dma(chip);
+}
+
+
+/* pseudo dma read */
+static void vx2_dma_read(struct vx_core *chip, struct snd_pcm_runtime *runtime,
+			 struct vx_pipe *pipe, int count)
+{
+	int offset = pipe->hw_ptr;
+	u32 *addr = (u32 *)(runtime->dma_area + offset);
+	unsigned long port = vx2_reg_addr(chip, VX_DMA);
+
+	if (snd_BUG_ON(count % 4))
+		return;
+
+	vx2_setup_pseudo_dma(chip, 0);
+	/* Transfer using pseudo-dma.
+	 */
+	if (offset + count > pipe->buffer_bytes) {
+		int length = pipe->buffer_bytes - offset;
+		count -= length;
+		length >>= 2; /* in 32bit words */
+		/* Transfer using pseudo-dma. */
+		while (length-- > 0)
+			*addr++ = le32_to_cpu(inl(port));
+		addr = (u32 *)runtime->dma_area;
+		pipe->hw_ptr = 0;
+	}
+	pipe->hw_ptr += count;
+	count >>= 2; /* in 32bit words */
+	/* Transfer using pseudo-dma. */
+	while (count-- > 0)
+		*addr++ = le32_to_cpu(inl(port));
+
+	vx2_release_pseudo_dma(chip);
+}
+
+#define VX_XILINX_RESET_MASK        0x40000000
+#define VX_USERBIT0_MASK            0x00000004
+#define VX_USERBIT1_MASK            0x00000020
+#define VX_CNTRL_REGISTER_VALUE     0x00172012
+
+/*
+ * transfer counts bits to PLX
+ */
+static int put_xilinx_data(struct vx_core *chip, unsigned int port, unsigned int counts, unsigned char data)
+{
+	unsigned int i;
+
+	for (i = 0; i < counts; i++) {
+		unsigned int val;
+
+		/* set the clock bit to 0. */
+		val = VX_CNTRL_REGISTER_VALUE & ~VX_USERBIT0_MASK;
+		vx2_outl(chip, port, val);
+		vx2_inl(chip, port);
+		udelay(1);
+
+		if (data & (1 << i))
+			val |= VX_USERBIT1_MASK;
+		else
+			val &= ~VX_USERBIT1_MASK;
+		vx2_outl(chip, port, val);
+		vx2_inl(chip, port);
+
+		/* set the clock bit to 1. */
+		val |= VX_USERBIT0_MASK;
+		vx2_outl(chip, port, val);
+		vx2_inl(chip, port);
+		udelay(1);
+	}
+	return 0;
+}
+
+/*
+ * load the xilinx image
+ */
+static int vx2_load_xilinx_binary(struct vx_core *chip, const struct firmware *xilinx)
+{
+	unsigned int i;
+	unsigned int port;
+	const unsigned char *image;
+
+	/* XILINX reset (wait at least 1 millisecond between reset on and off). */
+	vx_outl(chip, CNTRL, VX_CNTRL_REGISTER_VALUE | VX_XILINX_RESET_MASK);
+	vx_inl(chip, CNTRL);
+	msleep(10);
+	vx_outl(chip, CNTRL, VX_CNTRL_REGISTER_VALUE);
+	vx_inl(chip, CNTRL);
+	msleep(10);
+
+	if (chip->type == VX_TYPE_BOARD)
+		port = VX_CNTRL;
+	else
+		port = VX_GPIOC; /* VX222 V2 and VX222_MIC_BOARD with new PLX9030 use this register */
+
+	image = xilinx->data;
+	for (i = 0; i < xilinx->size; i++, image++) {
+		if (put_xilinx_data(chip, port, 8, *image) < 0)
+			return -EINVAL;
+		/* don't take too much time in this loop... */
+		cond_resched();
+	}
+	put_xilinx_data(chip, port, 4, 0xff); /* end signature */
+
+	msleep(200);
+
+	/* test after loading (is buggy with VX222) */
+	if (chip->type != VX_TYPE_BOARD) {
+		/* Test if load successful: test bit 8 of register GPIOC (VX222: use CNTRL) ! */
+		i = vx_inl(chip, GPIOC);
+		if (i & 0x0100)
+			return 0;
+		snd_printk(KERN_ERR "vx222: xilinx test failed after load, GPIOC=0x%x\n", i);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+	
+/*
+ * load the boot/dsp images
+ */
+static int vx2_load_dsp(struct vx_core *vx, int index, const struct firmware *dsp)
+{
+	int err;
+
+	switch (index) {
+	case 1:
+		/* xilinx image */
+		if ((err = vx2_load_xilinx_binary(vx, dsp)) < 0)
+			return err;
+		if ((err = vx2_test_xilinx(vx)) < 0)
+			return err;
+		return 0;
+	case 2:
+		/* DSP boot */
+		return snd_vx_dsp_boot(vx, dsp);
+	case 3:
+		/* DSP image */
+		return snd_vx_dsp_load(vx, dsp);
+	default:
+		snd_BUG();
+		return -EINVAL;
+	}
+}
+
+
+/*
+ * vx_test_and_ack - test and acknowledge interrupt
+ *
+ * called from irq hander, too
+ *
+ * spinlock held!
+ */
+static int vx2_test_and_ack(struct vx_core *chip)
+{
+	/* not booted yet? */
+	if (! (chip->chip_status & VX_STAT_XILINX_LOADED))
+		return -ENXIO;
+
+	if (! (vx_inl(chip, STATUS) & VX_STATUS_MEMIRQ_MASK))
+		return -EIO;
+	
+	/* ok, interrupts generated, now ack it */
+	/* set ACQUIT bit up and down */
+	vx_outl(chip, STATUS, 0);
+	/* useless read just to spend some time and maintain
+	 * the ACQUIT signal up for a while ( a bus cycle )
+	 */
+	vx_inl(chip, STATUS);
+	/* ack */
+	vx_outl(chip, STATUS, VX_STATUS_MEMIRQ_MASK);
+	/* useless read just to spend some time and maintain
+	 * the ACQUIT signal up for a while ( a bus cycle ) */
+	vx_inl(chip, STATUS);
+	/* clear */
+	vx_outl(chip, STATUS, 0);
+
+	return 0;
+}
+
+
+/*
+ * vx_validate_irq - enable/disable IRQ
+ */
+static void vx2_validate_irq(struct vx_core *_chip, int enable)
+{
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+
+	/* Set the interrupt enable bit to 1 in CDSP register */
+	if (enable) {
+		/* Set the PCI interrupt enable bit to 1.*/
+		vx_outl(chip, INTCSR, VX_INTCSR_VALUE|VX_PCI_INTERRUPT_MASK);
+		chip->regCDSP |= VX_CDSP_VALID_IRQ_MASK;
+	} else {
+		/* Set the PCI interrupt enable bit to 0. */
+		vx_outl(chip, INTCSR, VX_INTCSR_VALUE&~VX_PCI_INTERRUPT_MASK);
+		chip->regCDSP &= ~VX_CDSP_VALID_IRQ_MASK;
+	}
+	vx_outl(chip, CDSP, chip->regCDSP);
+}
+
+
+/*
+ * write an AKM codec data (24bit)
+ */
+static void vx2_write_codec_reg(struct vx_core *chip, unsigned int data)
+{
+	unsigned int i;
+
+	vx_inl(chip, HIFREQ);
+
+	/* We have to send 24 bits (3 x 8 bits). Start with most signif. Bit */
+	for (i = 0; i < 24; i++, data <<= 1)
+		vx_outl(chip, DATA, ((data & 0x800000) ? VX_DATA_CODEC_MASK : 0));
+	/* Terminate access to codec registers */
+	vx_inl(chip, RUER);
+}
+
+
+#define AKM_CODEC_POWER_CONTROL_CMD 0xA007
+#define AKM_CODEC_RESET_ON_CMD      0xA100
+#define AKM_CODEC_RESET_OFF_CMD     0xA103
+#define AKM_CODEC_CLOCK_FORMAT_CMD  0xA240
+#define AKM_CODEC_MUTE_CMD          0xA38D
+#define AKM_CODEC_UNMUTE_CMD        0xA30D
+#define AKM_CODEC_LEFT_LEVEL_CMD    0xA400
+#define AKM_CODEC_RIGHT_LEVEL_CMD   0xA500
+
+static const u8 vx2_akm_gains_lut[VX2_AKM_LEVEL_MAX+1] = {
+    0x7f,       // [000] =  +0.000 dB  ->  AKM(0x7f) =  +0.000 dB  error(+0.000 dB)
+    0x7d,       // [001] =  -0.500 dB  ->  AKM(0x7d) =  -0.572 dB  error(-0.072 dB)
+    0x7c,       // [002] =  -1.000 dB  ->  AKM(0x7c) =  -0.873 dB  error(+0.127 dB)
+    0x7a,       // [003] =  -1.500 dB  ->  AKM(0x7a) =  -1.508 dB  error(-0.008 dB)
+    0x79,       // [004] =  -2.000 dB  ->  AKM(0x79) =  -1.844 dB  error(+0.156 dB)
+    0x77,       // [005] =  -2.500 dB  ->  AKM(0x77) =  -2.557 dB  error(-0.057 dB)
+    0x76,       // [006] =  -3.000 dB  ->  AKM(0x76) =  -2.937 dB  error(+0.063 dB)
+    0x75,       // [007] =  -3.500 dB  ->  AKM(0x75) =  -3.334 dB  error(+0.166 dB)
+    0x73,       // [008] =  -4.000 dB  ->  AKM(0x73) =  -4.188 dB  error(-0.188 dB)
+    0x72,       // [009] =  -4.500 dB  ->  AKM(0x72) =  -4.648 dB  error(-0.148 dB)
+    0x71,       // [010] =  -5.000 dB  ->  AKM(0x71) =  -5.134 dB  error(-0.134 dB)
+    0x70,       // [011] =  -5.500 dB  ->  AKM(0x70) =  -5.649 dB  error(-0.149 dB)
+    0x6f,       // [012] =  -6.000 dB  ->  AKM(0x6f) =  -6.056 dB  error(-0.056 dB)
+    0x6d,       // [013] =  -6.500 dB  ->  AKM(0x6d) =  -6.631 dB  error(-0.131 dB)
+    0x6c,       // [014] =  -7.000 dB  ->  AKM(0x6c) =  -6.933 dB  error(+0.067 dB)
+    0x6a,       // [015] =  -7.500 dB  ->  AKM(0x6a) =  -7.571 dB  error(-0.071 dB)
+    0x69,       // [016] =  -8.000 dB  ->  AKM(0x69) =  -7.909 dB  error(+0.091 dB)
+    0x67,       // [017] =  -8.500 dB  ->  AKM(0x67) =  -8.626 dB  error(-0.126 dB)
+    0x66,       // [018] =  -9.000 dB  ->  AKM(0x66) =  -9.008 dB  error(-0.008 dB)
+    0x65,       // [019] =  -9.500 dB  ->  AKM(0x65) =  -9.407 dB  error(+0.093 dB)
+    0x64,       // [020] = -10.000 dB  ->  AKM(0x64) =  -9.826 dB  error(+0.174 dB)
+    0x62,       // [021] = -10.500 dB  ->  AKM(0x62) = -10.730 dB  error(-0.230 dB)
+    0x61,       // [022] = -11.000 dB  ->  AKM(0x61) = -11.219 dB  error(-0.219 dB)
+    0x60,       // [023] = -11.500 dB  ->  AKM(0x60) = -11.738 dB  error(-0.238 dB)
+    0x5f,       // [024] = -12.000 dB  ->  AKM(0x5f) = -12.149 dB  error(-0.149 dB)
+    0x5e,       // [025] = -12.500 dB  ->  AKM(0x5e) = -12.434 dB  error(+0.066 dB)
+    0x5c,       // [026] = -13.000 dB  ->  AKM(0x5c) = -13.033 dB  error(-0.033 dB)
+    0x5b,       // [027] = -13.500 dB  ->  AKM(0x5b) = -13.350 dB  error(+0.150 dB)
+    0x59,       // [028] = -14.000 dB  ->  AKM(0x59) = -14.018 dB  error(-0.018 dB)
+    0x58,       // [029] = -14.500 dB  ->  AKM(0x58) = -14.373 dB  error(+0.127 dB)
+    0x56,       // [030] = -15.000 dB  ->  AKM(0x56) = -15.130 dB  error(-0.130 dB)
+    0x55,       // [031] = -15.500 dB  ->  AKM(0x55) = -15.534 dB  error(-0.034 dB)
+    0x54,       // [032] = -16.000 dB  ->  AKM(0x54) = -15.958 dB  error(+0.042 dB)
+    0x53,       // [033] = -16.500 dB  ->  AKM(0x53) = -16.404 dB  error(+0.096 dB)
+    0x52,       // [034] = -17.000 dB  ->  AKM(0x52) = -16.874 dB  error(+0.126 dB)
+    0x51,       // [035] = -17.500 dB  ->  AKM(0x51) = -17.371 dB  error(+0.129 dB)
+    0x50,       // [036] = -18.000 dB  ->  AKM(0x50) = -17.898 dB  error(+0.102 dB)
+    0x4e,       // [037] = -18.500 dB  ->  AKM(0x4e) = -18.605 dB  error(-0.105 dB)
+    0x4d,       // [038] = -19.000 dB  ->  AKM(0x4d) = -18.905 dB  error(+0.095 dB)
+    0x4b,       // [039] = -19.500 dB  ->  AKM(0x4b) = -19.538 dB  error(-0.038 dB)
+    0x4a,       // [040] = -20.000 dB  ->  AKM(0x4a) = -19.872 dB  error(+0.128 dB)
+    0x48,       // [041] = -20.500 dB  ->  AKM(0x48) = -20.583 dB  error(-0.083 dB)
+    0x47,       // [042] = -21.000 dB  ->  AKM(0x47) = -20.961 dB  error(+0.039 dB)
+    0x46,       // [043] = -21.500 dB  ->  AKM(0x46) = -21.356 dB  error(+0.144 dB)
+    0x44,       // [044] = -22.000 dB  ->  AKM(0x44) = -22.206 dB  error(-0.206 dB)
+    0x43,       // [045] = -22.500 dB  ->  AKM(0x43) = -22.664 dB  error(-0.164 dB)
+    0x42,       // [046] = -23.000 dB  ->  AKM(0x42) = -23.147 dB  error(-0.147 dB)
+    0x41,       // [047] = -23.500 dB  ->  AKM(0x41) = -23.659 dB  error(-0.159 dB)
+    0x40,       // [048] = -24.000 dB  ->  AKM(0x40) = -24.203 dB  error(-0.203 dB)
+    0x3f,       // [049] = -24.500 dB  ->  AKM(0x3f) = -24.635 dB  error(-0.135 dB)
+    0x3e,       // [050] = -25.000 dB  ->  AKM(0x3e) = -24.935 dB  error(+0.065 dB)
+    0x3c,       // [051] = -25.500 dB  ->  AKM(0x3c) = -25.569 dB  error(-0.069 dB)
+    0x3b,       // [052] = -26.000 dB  ->  AKM(0x3b) = -25.904 dB  error(+0.096 dB)
+    0x39,       // [053] = -26.500 dB  ->  AKM(0x39) = -26.615 dB  error(-0.115 dB)
+    0x38,       // [054] = -27.000 dB  ->  AKM(0x38) = -26.994 dB  error(+0.006 dB)
+    0x37,       // [055] = -27.500 dB  ->  AKM(0x37) = -27.390 dB  error(+0.110 dB)
+    0x36,       // [056] = -28.000 dB  ->  AKM(0x36) = -27.804 dB  error(+0.196 dB)
+    0x34,       // [057] = -28.500 dB  ->  AKM(0x34) = -28.699 dB  error(-0.199 dB)
+    0x33,       // [058] = -29.000 dB  ->  AKM(0x33) = -29.183 dB  error(-0.183 dB)
+    0x32,       // [059] = -29.500 dB  ->  AKM(0x32) = -29.696 dB  error(-0.196 dB)
+    0x31,       // [060] = -30.000 dB  ->  AKM(0x31) = -30.241 dB  error(-0.241 dB)
+    0x31,       // [061] = -30.500 dB  ->  AKM(0x31) = -30.241 dB  error(+0.259 dB)
+    0x30,       // [062] = -31.000 dB  ->  AKM(0x30) = -30.823 dB  error(+0.177 dB)
+    0x2e,       // [063] = -31.500 dB  ->  AKM(0x2e) = -31.610 dB  error(-0.110 dB)
+    0x2d,       // [064] = -32.000 dB  ->  AKM(0x2d) = -31.945 dB  error(+0.055 dB)
+    0x2b,       // [065] = -32.500 dB  ->  AKM(0x2b) = -32.659 dB  error(-0.159 dB)
+    0x2a,       // [066] = -33.000 dB  ->  AKM(0x2a) = -33.038 dB  error(-0.038 dB)
+    0x29,       // [067] = -33.500 dB  ->  AKM(0x29) = -33.435 dB  error(+0.065 dB)
+    0x28,       // [068] = -34.000 dB  ->  AKM(0x28) = -33.852 dB  error(+0.148 dB)
+    0x27,       // [069] = -34.500 dB  ->  AKM(0x27) = -34.289 dB  error(+0.211 dB)
+    0x25,       // [070] = -35.000 dB  ->  AKM(0x25) = -35.235 dB  error(-0.235 dB)
+    0x24,       // [071] = -35.500 dB  ->  AKM(0x24) = -35.750 dB  error(-0.250 dB)
+    0x24,       // [072] = -36.000 dB  ->  AKM(0x24) = -35.750 dB  error(+0.250 dB)
+    0x23,       // [073] = -36.500 dB  ->  AKM(0x23) = -36.297 dB  error(+0.203 dB)
+    0x22,       // [074] = -37.000 dB  ->  AKM(0x22) = -36.881 dB  error(+0.119 dB)
+    0x21,       // [075] = -37.500 dB  ->  AKM(0x21) = -37.508 dB  error(-0.008 dB)
+    0x20,       // [076] = -38.000 dB  ->  AKM(0x20) = -38.183 dB  error(-0.183 dB)
+    0x1f,       // [077] = -38.500 dB  ->  AKM(0x1f) = -38.726 dB  error(-0.226 dB)
+    0x1e,       // [078] = -39.000 dB  ->  AKM(0x1e) = -39.108 dB  error(-0.108 dB)
+    0x1d,       // [079] = -39.500 dB  ->  AKM(0x1d) = -39.507 dB  error(-0.007 dB)
+    0x1c,       // [080] = -40.000 dB  ->  AKM(0x1c) = -39.926 dB  error(+0.074 dB)
+    0x1b,       // [081] = -40.500 dB  ->  AKM(0x1b) = -40.366 dB  error(+0.134 dB)
+    0x1a,       // [082] = -41.000 dB  ->  AKM(0x1a) = -40.829 dB  error(+0.171 dB)
+    0x19,       // [083] = -41.500 dB  ->  AKM(0x19) = -41.318 dB  error(+0.182 dB)
+    0x18,       // [084] = -42.000 dB  ->  AKM(0x18) = -41.837 dB  error(+0.163 dB)
+    0x17,       // [085] = -42.500 dB  ->  AKM(0x17) = -42.389 dB  error(+0.111 dB)
+    0x16,       // [086] = -43.000 dB  ->  AKM(0x16) = -42.978 dB  error(+0.022 dB)
+    0x15,       // [087] = -43.500 dB  ->  AKM(0x15) = -43.610 dB  error(-0.110 dB)
+    0x14,       // [088] = -44.000 dB  ->  AKM(0x14) = -44.291 dB  error(-0.291 dB)
+    0x14,       // [089] = -44.500 dB  ->  AKM(0x14) = -44.291 dB  error(+0.209 dB)
+    0x13,       // [090] = -45.000 dB  ->  AKM(0x13) = -45.031 dB  error(-0.031 dB)
+    0x12,       // [091] = -45.500 dB  ->  AKM(0x12) = -45.840 dB  error(-0.340 dB)
+    0x12,       // [092] = -46.000 dB  ->  AKM(0x12) = -45.840 dB  error(+0.160 dB)
+    0x11,       // [093] = -46.500 dB  ->  AKM(0x11) = -46.731 dB  error(-0.231 dB)
+    0x11,       // [094] = -47.000 dB  ->  AKM(0x11) = -46.731 dB  error(+0.269 dB)
+    0x10,       // [095] = -47.500 dB  ->  AKM(0x10) = -47.725 dB  error(-0.225 dB)
+    0x10,       // [096] = -48.000 dB  ->  AKM(0x10) = -47.725 dB  error(+0.275 dB)
+    0x0f,       // [097] = -48.500 dB  ->  AKM(0x0f) = -48.553 dB  error(-0.053 dB)
+    0x0e,       // [098] = -49.000 dB  ->  AKM(0x0e) = -49.152 dB  error(-0.152 dB)
+    0x0d,       // [099] = -49.500 dB  ->  AKM(0x0d) = -49.796 dB  error(-0.296 dB)
+    0x0d,       // [100] = -50.000 dB  ->  AKM(0x0d) = -49.796 dB  error(+0.204 dB)
+    0x0c,       // [101] = -50.500 dB  ->  AKM(0x0c) = -50.491 dB  error(+0.009 dB)
+    0x0b,       // [102] = -51.000 dB  ->  AKM(0x0b) = -51.247 dB  error(-0.247 dB)
+    0x0b,       // [103] = -51.500 dB  ->  AKM(0x0b) = -51.247 dB  error(+0.253 dB)
+    0x0a,       // [104] = -52.000 dB  ->  AKM(0x0a) = -52.075 dB  error(-0.075 dB)
+    0x0a,       // [105] = -52.500 dB  ->  AKM(0x0a) = -52.075 dB  error(+0.425 dB)
+    0x09,       // [106] = -53.000 dB  ->  AKM(0x09) = -52.990 dB  error(+0.010 dB)
+    0x09,       // [107] = -53.500 dB  ->  AKM(0x09) = -52.990 dB  error(+0.510 dB)
+    0x08,       // [108] = -54.000 dB  ->  AKM(0x08) = -54.013 dB  error(-0.013 dB)
+    0x08,       // [109] = -54.500 dB  ->  AKM(0x08) = -54.013 dB  error(+0.487 dB)
+    0x07,       // [110] = -55.000 dB  ->  AKM(0x07) = -55.173 dB  error(-0.173 dB)
+    0x07,       // [111] = -55.500 dB  ->  AKM(0x07) = -55.173 dB  error(+0.327 dB)
+    0x06,       // [112] = -56.000 dB  ->  AKM(0x06) = -56.512 dB  error(-0.512 dB)
+    0x06,       // [113] = -56.500 dB  ->  AKM(0x06) = -56.512 dB  error(-0.012 dB)
+    0x06,       // [114] = -57.000 dB  ->  AKM(0x06) = -56.512 dB  error(+0.488 dB)
+    0x05,       // [115] = -57.500 dB  ->  AKM(0x05) = -58.095 dB  error(-0.595 dB)
+    0x05,       // [116] = -58.000 dB  ->  AKM(0x05) = -58.095 dB  error(-0.095 dB)
+    0x05,       // [117] = -58.500 dB  ->  AKM(0x05) = -58.095 dB  error(+0.405 dB)
+    0x05,       // [118] = -59.000 dB  ->  AKM(0x05) = -58.095 dB  error(+0.905 dB)
+    0x04,       // [119] = -59.500 dB  ->  AKM(0x04) = -60.034 dB  error(-0.534 dB)
+    0x04,       // [120] = -60.000 dB  ->  AKM(0x04) = -60.034 dB  error(-0.034 dB)
+    0x04,       // [121] = -60.500 dB  ->  AKM(0x04) = -60.034 dB  error(+0.466 dB)
+    0x04,       // [122] = -61.000 dB  ->  AKM(0x04) = -60.034 dB  error(+0.966 dB)
+    0x03,       // [123] = -61.500 dB  ->  AKM(0x03) = -62.532 dB  error(-1.032 dB)
+    0x03,       // [124] = -62.000 dB  ->  AKM(0x03) = -62.532 dB  error(-0.532 dB)
+    0x03,       // [125] = -62.500 dB  ->  AKM(0x03) = -62.532 dB  error(-0.032 dB)
+    0x03,       // [126] = -63.000 dB  ->  AKM(0x03) = -62.532 dB  error(+0.468 dB)
+    0x03,       // [127] = -63.500 dB  ->  AKM(0x03) = -62.532 dB  error(+0.968 dB)
+    0x03,       // [128] = -64.000 dB  ->  AKM(0x03) = -62.532 dB  error(+1.468 dB)
+    0x02,       // [129] = -64.500 dB  ->  AKM(0x02) = -66.054 dB  error(-1.554 dB)
+    0x02,       // [130] = -65.000 dB  ->  AKM(0x02) = -66.054 dB  error(-1.054 dB)
+    0x02,       // [131] = -65.500 dB  ->  AKM(0x02) = -66.054 dB  error(-0.554 dB)
+    0x02,       // [132] = -66.000 dB  ->  AKM(0x02) = -66.054 dB  error(-0.054 dB)
+    0x02,       // [133] = -66.500 dB  ->  AKM(0x02) = -66.054 dB  error(+0.446 dB)
+    0x02,       // [134] = -67.000 dB  ->  AKM(0x02) = -66.054 dB  error(+0.946 dB)
+    0x02,       // [135] = -67.500 dB  ->  AKM(0x02) = -66.054 dB  error(+1.446 dB)
+    0x02,       // [136] = -68.000 dB  ->  AKM(0x02) = -66.054 dB  error(+1.946 dB)
+    0x02,       // [137] = -68.500 dB  ->  AKM(0x02) = -66.054 dB  error(+2.446 dB)
+    0x02,       // [138] = -69.000 dB  ->  AKM(0x02) = -66.054 dB  error(+2.946 dB)
+    0x01,       // [139] = -69.500 dB  ->  AKM(0x01) = -72.075 dB  error(-2.575 dB)
+    0x01,       // [140] = -70.000 dB  ->  AKM(0x01) = -72.075 dB  error(-2.075 dB)
+    0x01,       // [141] = -70.500 dB  ->  AKM(0x01) = -72.075 dB  error(-1.575 dB)
+    0x01,       // [142] = -71.000 dB  ->  AKM(0x01) = -72.075 dB  error(-1.075 dB)
+    0x01,       // [143] = -71.500 dB  ->  AKM(0x01) = -72.075 dB  error(-0.575 dB)
+    0x01,       // [144] = -72.000 dB  ->  AKM(0x01) = -72.075 dB  error(-0.075 dB)
+    0x01,       // [145] = -72.500 dB  ->  AKM(0x01) = -72.075 dB  error(+0.425 dB)
+    0x01,       // [146] = -73.000 dB  ->  AKM(0x01) = -72.075 dB  error(+0.925 dB)
+    0x00};      // [147] = -73.500 dB  ->  AKM(0x00) =  mute       error(+infini)
+
+/*
+ * pseudo-codec write entry
+ */
+static void vx2_write_akm(struct vx_core *chip, int reg, unsigned int data)
+{
+	unsigned int val;
+
+	if (reg == XX_CODEC_DAC_CONTROL_REGISTER) {
+		vx2_write_codec_reg(chip, data ? AKM_CODEC_MUTE_CMD : AKM_CODEC_UNMUTE_CMD);
+		return;
+	}
+
+	/* `data' is a value between 0x0 and VX2_AKM_LEVEL_MAX = 0x093, in the case of the AKM codecs, we need
+	   a look up table, as there is no linear matching between the driver codec values
+	   and the real dBu value
+	*/
+	if (snd_BUG_ON(data >= sizeof(vx2_akm_gains_lut)))
+		return;
+
+	switch (reg) {
+	case XX_CODEC_LEVEL_LEFT_REGISTER:
+		val = AKM_CODEC_LEFT_LEVEL_CMD;
+		break;
+	case XX_CODEC_LEVEL_RIGHT_REGISTER:
+		val = AKM_CODEC_RIGHT_LEVEL_CMD;
+		break;
+	default:
+		snd_BUG();
+		return;
+	}
+	val |= vx2_akm_gains_lut[data];
+
+	vx2_write_codec_reg(chip, val);
+}
+
+
+/*
+ * write codec bit for old VX222 board
+ */
+static void vx2_old_write_codec_bit(struct vx_core *chip, int codec, unsigned int data)
+{
+	int i;
+
+	/* activate access to codec registers */
+	vx_inl(chip, HIFREQ);
+
+	for (i = 0; i < 24; i++, data <<= 1)
+		vx_outl(chip, DATA, ((data & 0x800000) ? VX_DATA_CODEC_MASK : 0));
+
+	/* Terminate access to codec registers */
+	vx_inl(chip, RUER);
+}
+
+
+/*
+ * reset codec bit
+ */
+static void vx2_reset_codec(struct vx_core *_chip)
+{
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+
+	/* Set the reset CODEC bit to 0. */
+	vx_outl(chip, CDSP, chip->regCDSP &~ VX_CDSP_CODEC_RESET_MASK);
+	vx_inl(chip, CDSP);
+	msleep(10);
+	/* Set the reset CODEC bit to 1. */
+	chip->regCDSP |= VX_CDSP_CODEC_RESET_MASK;
+	vx_outl(chip, CDSP, chip->regCDSP);
+	vx_inl(chip, CDSP);
+	if (_chip->type == VX_TYPE_BOARD) {
+		msleep(1);
+		return;
+	}
+
+	msleep(5);  /* additionnel wait time for AKM's */
+
+	vx2_write_codec_reg(_chip, AKM_CODEC_POWER_CONTROL_CMD); /* DAC power up, ADC power up, Vref power down */
+	
+	vx2_write_codec_reg(_chip, AKM_CODEC_CLOCK_FORMAT_CMD); /* default */
+	vx2_write_codec_reg(_chip, AKM_CODEC_MUTE_CMD); /* Mute = ON ,Deemphasis = OFF */
+	vx2_write_codec_reg(_chip, AKM_CODEC_RESET_OFF_CMD); /* DAC and ADC normal operation */
+
+	if (_chip->type == VX_TYPE_MIC) {
+		/* set up the micro input selector */
+		chip->regSELMIC =  MICRO_SELECT_INPUT_NORM |
+			MICRO_SELECT_PREAMPLI_G_0 |
+			MICRO_SELECT_NOISE_T_52DB;
+
+		/* reset phantom power supply */
+		chip->regSELMIC &= ~MICRO_SELECT_PHANTOM_ALIM;
+
+		vx_outl(_chip, SELMIC, chip->regSELMIC);
+	}
+}
+
+
+/*
+ * change the audio source
+ */
+static void vx2_change_audio_source(struct vx_core *_chip, int src)
+{
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+
+	switch (src) {
+	case VX_AUDIO_SRC_DIGITAL:
+		chip->regCFG |= VX_CFG_DATAIN_SEL_MASK;
+		break;
+	default:
+		chip->regCFG &= ~VX_CFG_DATAIN_SEL_MASK;
+		break;
+	}
+	vx_outl(chip, CFG, chip->regCFG);
+}
+
+
+/*
+ * set the clock source
+ */
+static void vx2_set_clock_source(struct vx_core *_chip, int source)
+{
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+
+	if (source == INTERNAL_QUARTZ)
+		chip->regCFG &= ~VX_CFG_CLOCKIN_SEL_MASK;
+	else
+		chip->regCFG |= VX_CFG_CLOCKIN_SEL_MASK;
+	vx_outl(chip, CFG, chip->regCFG);
+}
+
+/*
+ * reset the board
+ */
+static void vx2_reset_board(struct vx_core *_chip, int cold_reset)
+{
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+
+	/* initialize the register values */
+	chip->regCDSP = VX_CDSP_CODEC_RESET_MASK | VX_CDSP_DSP_RESET_MASK ;
+	chip->regCFG = 0;
+}
+
+
+
+/*
+ * input level controls for VX222 Mic
+ */
+
+/* Micro level is specified to be adjustable from -96dB to 63 dB (board coded 0x00 ... 318),
+ * 318 = 210 + 36 + 36 + 36   (210 = +9dB variable) (3 * 36 = 3 steps of 18dB pre ampli)
+ * as we will mute if less than -110dB, so let's simply use line input coded levels and add constant offset !
+ */
+#define V2_MICRO_LEVEL_RANGE        (318 - 255)
+
+static void vx2_set_input_level(struct snd_vx222 *chip)
+{
+	int i, miclevel, preamp;
+	unsigned int data;
+
+	miclevel = chip->mic_level;
+	miclevel += V2_MICRO_LEVEL_RANGE; /* add 318 - 0xff */
+	preamp = 0;
+        while (miclevel > 210) { /* limitation to +9dB of 3310 real gain */
+		preamp++;	/* raise pre ampli + 18dB */
+		miclevel -= (18 * 2);   /* lower level 18 dB (*2 because of 0.5 dB steps !) */
+        }
+	if (snd_BUG_ON(preamp >= 4))
+		return;
+
+	/* set pre-amp level */
+	chip->regSELMIC &= ~MICRO_SELECT_PREAMPLI_MASK;
+	chip->regSELMIC |= (preamp << MICRO_SELECT_PREAMPLI_OFFSET) & MICRO_SELECT_PREAMPLI_MASK;
+	vx_outl(chip, SELMIC, chip->regSELMIC);
+
+	data = (unsigned int)miclevel << 16 |
+		(unsigned int)chip->input_level[1] << 8 |
+		(unsigned int)chip->input_level[0];
+	vx_inl(chip, DATA); /* Activate input level programming */
+
+	/* We have to send 32 bits (4 x 8 bits) */
+	for (i = 0; i < 32; i++, data <<= 1)
+		vx_outl(chip, DATA, ((data & 0x80000000) ? VX_DATA_CODEC_MASK : 0));
+
+	vx_inl(chip, RUER); /* Terminate input level programming */
+}
+
+
+#define MIC_LEVEL_MAX	0xff
+
+static const DECLARE_TLV_DB_SCALE(db_scale_mic, -6450, 50, 0);
+
+/*
+ * controls API for input levels
+ */
+
+/* input levels */
+static int vx_input_level_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = MIC_LEVEL_MAX;
+	return 0;
+}
+
+static int vx_input_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *_chip = snd_kcontrol_chip(kcontrol);
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+	mutex_lock(&_chip->mixer_mutex);
+	ucontrol->value.integer.value[0] = chip->input_level[0];
+	ucontrol->value.integer.value[1] = chip->input_level[1];
+	mutex_unlock(&_chip->mixer_mutex);
+	return 0;
+}
+
+static int vx_input_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *_chip = snd_kcontrol_chip(kcontrol);
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+	if (ucontrol->value.integer.value[0] < 0 ||
+	    ucontrol->value.integer.value[0] > MIC_LEVEL_MAX)
+		return -EINVAL;
+	if (ucontrol->value.integer.value[1] < 0 ||
+	    ucontrol->value.integer.value[1] > MIC_LEVEL_MAX)
+		return -EINVAL;
+	mutex_lock(&_chip->mixer_mutex);
+	if (chip->input_level[0] != ucontrol->value.integer.value[0] ||
+	    chip->input_level[1] != ucontrol->value.integer.value[1]) {
+		chip->input_level[0] = ucontrol->value.integer.value[0];
+		chip->input_level[1] = ucontrol->value.integer.value[1];
+		vx2_set_input_level(chip);
+		mutex_unlock(&_chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&_chip->mixer_mutex);
+	return 0;
+}
+
+/* mic level */
+static int vx_mic_level_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = MIC_LEVEL_MAX;
+	return 0;
+}
+
+static int vx_mic_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *_chip = snd_kcontrol_chip(kcontrol);
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+	ucontrol->value.integer.value[0] = chip->mic_level;
+	return 0;
+}
+
+static int vx_mic_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *_chip = snd_kcontrol_chip(kcontrol);
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+	if (ucontrol->value.integer.value[0] < 0 ||
+	    ucontrol->value.integer.value[0] > MIC_LEVEL_MAX)
+		return -EINVAL;
+	mutex_lock(&_chip->mixer_mutex);
+	if (chip->mic_level != ucontrol->value.integer.value[0]) {
+		chip->mic_level = ucontrol->value.integer.value[0];
+		vx2_set_input_level(chip);
+		mutex_unlock(&_chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&_chip->mixer_mutex);
+	return 0;
+}
+
+static struct snd_kcontrol_new vx_control_input_level = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.name =		"Capture Volume",
+	.info =		vx_input_level_info,
+	.get =		vx_input_level_get,
+	.put =		vx_input_level_put,
+	.tlv = { .p = db_scale_mic },
+};
+
+static struct snd_kcontrol_new vx_control_mic_level = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.name =		"Mic Capture Volume",
+	.info =		vx_mic_level_info,
+	.get =		vx_mic_level_get,
+	.put =		vx_mic_level_put,
+	.tlv = { .p = db_scale_mic },
+};
+
+/*
+ * FIXME: compressor/limiter implementation is missing yet...
+ */
+
+static int vx2_add_mic_controls(struct vx_core *_chip)
+{
+	struct snd_vx222 *chip = (struct snd_vx222 *)_chip;
+	int err;
+
+	if (_chip->type != VX_TYPE_MIC)
+		return 0;
+
+	/* mute input levels */
+	chip->input_level[0] = chip->input_level[1] = 0;
+	chip->mic_level = 0;
+	vx2_set_input_level(chip);
+
+	/* controls */
+	if ((err = snd_ctl_add(_chip->card, snd_ctl_new1(&vx_control_input_level, chip))) < 0)
+		return err;
+	if ((err = snd_ctl_add(_chip->card, snd_ctl_new1(&vx_control_mic_level, chip))) < 0)
+		return err;
+
+	return 0;
+}
+
+
+/*
+ * callbacks
+ */
+struct snd_vx_ops vx222_ops = {
+	.in8 = vx2_inb,
+	.in32 = vx2_inl,
+	.out8 = vx2_outb,
+	.out32 = vx2_outl,
+	.test_and_ack = vx2_test_and_ack,
+	.validate_irq = vx2_validate_irq,
+	.akm_write = vx2_write_akm,
+	.reset_codec = vx2_reset_codec,
+	.change_audio_source = vx2_change_audio_source,
+	.set_clock_source = vx2_set_clock_source,
+	.load_dsp = vx2_load_dsp,
+	.reset_dsp = vx2_reset_dsp,
+	.reset_board = vx2_reset_board,
+	.dma_write = vx2_dma_write,
+	.dma_read = vx2_dma_read,
+	.add_controls = vx2_add_mic_controls,
+};
+
+/* for old VX222 board */
+struct snd_vx_ops vx222_old_ops = {
+	.in8 = vx2_inb,
+	.in32 = vx2_inl,
+	.out8 = vx2_outb,
+	.out32 = vx2_outl,
+	.test_and_ack = vx2_test_and_ack,
+	.validate_irq = vx2_validate_irq,
+	.write_codec = vx2_old_write_codec_bit,
+	.reset_codec = vx2_reset_codec,
+	.change_audio_source = vx2_change_audio_source,
+	.set_clock_source = vx2_set_clock_source,
+	.load_dsp = vx2_load_dsp,
+	.reset_dsp = vx2_reset_dsp,
+	.reset_board = vx2_reset_board,
+	.dma_write = vx2_dma_write,
+	.dma_read = vx2_dma_read,
+};
+
diff --git a/linux/sound/pci/ymfpci/Makefile b/linux/sound/pci/ymfpci/Makefile
new file mode 100644
index 0000000..bd3d514
--- /dev/null
+++ b/linux/sound/pci/ymfpci/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-ymfpci-objs := ymfpci.o ymfpci_main.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_YMFPCI) += snd-ymfpci.o
diff --git a/linux/sound/pci/ymfpci/ymfpci.c b/linux/sound/pci/ymfpci/ymfpci.c
new file mode 100644
index 0000000..80c6821
--- /dev/null
+++ b/linux/sound/pci/ymfpci/ymfpci.c
@@ -0,0 +1,369 @@
+/*
+ *  The driver for the Yamaha's DS1/DS1E cards
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/ymfpci.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Yamaha DS-1 PCI");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Yamaha,YMF724},"
+		"{Yamaha,YMF724F},"
+		"{Yamaha,YMF740},"
+		"{Yamaha,YMF740C},"
+		"{Yamaha,YMF744},"
+		"{Yamaha,YMF754}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+static long fm_port[SNDRV_CARDS];
+static long mpu_port[SNDRV_CARDS];
+#ifdef SUPPORT_JOYSTICK
+static long joystick_port[SNDRV_CARDS];
+#endif
+static int rear_switch[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the Yamaha DS-1 PCI soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the Yamaha DS-1 PCI soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Yamaha DS-1 soundcard.");
+module_param_array(mpu_port, long, NULL, 0444);
+MODULE_PARM_DESC(mpu_port, "MPU-401 Port.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM OPL-3 Port.");
+#ifdef SUPPORT_JOYSTICK
+module_param_array(joystick_port, long, NULL, 0444);
+MODULE_PARM_DESC(joystick_port, "Joystick port address");
+#endif
+module_param_array(rear_switch, bool, NULL, 0444);
+MODULE_PARM_DESC(rear_switch, "Enable shared rear/line-in switch");
+
+static DEFINE_PCI_DEVICE_TABLE(snd_ymfpci_ids) = {
+	{ PCI_VDEVICE(YAMAHA, 0x0004), 0, },   /* YMF724 */
+	{ PCI_VDEVICE(YAMAHA, 0x000d), 0, },   /* YMF724F */
+	{ PCI_VDEVICE(YAMAHA, 0x000a), 0, },   /* YMF740 */
+	{ PCI_VDEVICE(YAMAHA, 0x000c), 0, },   /* YMF740C */
+	{ PCI_VDEVICE(YAMAHA, 0x0010), 0, },   /* YMF744 */
+	{ PCI_VDEVICE(YAMAHA, 0x0012), 0, },   /* YMF754 */
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_ymfpci_ids);
+
+#ifdef SUPPORT_JOYSTICK
+static int __devinit snd_ymfpci_create_gameport(struct snd_ymfpci *chip, int dev,
+						int legacy_ctrl, int legacy_ctrl2)
+{
+	struct gameport *gp;
+	struct resource *r = NULL;
+	int io_port = joystick_port[dev];
+
+	if (!io_port)
+		return -ENODEV;
+
+	if (chip->pci->device >= 0x0010) { /* YMF 744/754 */
+
+		if (io_port == 1) {
+			/* auto-detect */
+			if (!(io_port = pci_resource_start(chip->pci, 2)))
+				return -ENODEV;
+		}
+	} else {
+		if (io_port == 1) {
+			/* auto-detect */
+			for (io_port = 0x201; io_port <= 0x205; io_port++) {
+				if (io_port == 0x203)
+					continue;
+				if ((r = request_region(io_port, 1, "YMFPCI gameport")) != NULL)
+					break;
+			}
+			if (!r) {
+				printk(KERN_ERR "ymfpci: no gameport ports available\n");
+				return -EBUSY;
+			}
+		}
+		switch (io_port) {
+		case 0x201: legacy_ctrl2 |= 0 << 6; break;
+		case 0x202: legacy_ctrl2 |= 1 << 6; break;
+		case 0x204: legacy_ctrl2 |= 2 << 6; break;
+		case 0x205: legacy_ctrl2 |= 3 << 6; break;
+		default:
+			printk(KERN_ERR "ymfpci: invalid joystick port %#x", io_port);
+			return -EINVAL;
+		}
+	}
+
+	if (!r && !(r = request_region(io_port, 1, "YMFPCI gameport"))) {
+		printk(KERN_ERR "ymfpci: joystick port %#x is in use.\n", io_port);
+		return -EBUSY;
+	}
+
+	chip->gameport = gp = gameport_allocate_port();
+	if (!gp) {
+		printk(KERN_ERR "ymfpci: cannot allocate memory for gameport\n");
+		release_and_free_resource(r);
+		return -ENOMEM;
+	}
+
+
+	gameport_set_name(gp, "Yamaha YMF Gameport");
+	gameport_set_phys(gp, "pci%s/gameport0", pci_name(chip->pci));
+	gameport_set_dev_parent(gp, &chip->pci->dev);
+	gp->io = io_port;
+	gameport_set_port_data(gp, r);
+
+	if (chip->pci->device >= 0x0010) /* YMF 744/754 */
+		pci_write_config_word(chip->pci, PCIR_DSXG_JOYBASE, io_port);
+
+	pci_write_config_word(chip->pci, PCIR_DSXG_LEGACY, legacy_ctrl | YMFPCI_LEGACY_JPEN);
+	pci_write_config_word(chip->pci, PCIR_DSXG_ELEGACY, legacy_ctrl2);
+
+	gameport_register_port(chip->gameport);
+
+	return 0;
+}
+
+void snd_ymfpci_free_gameport(struct snd_ymfpci *chip)
+{
+	if (chip->gameport) {
+		struct resource *r = gameport_get_port_data(chip->gameport);
+
+		gameport_unregister_port(chip->gameport);
+		chip->gameport = NULL;
+
+		release_and_free_resource(r);
+	}
+}
+#else
+static inline int snd_ymfpci_create_gameport(struct snd_ymfpci *chip, int dev, int l, int l2) { return -ENOSYS; }
+void snd_ymfpci_free_gameport(struct snd_ymfpci *chip) { }
+#endif /* SUPPORT_JOYSTICK */
+
+static int __devinit snd_card_ymfpci_probe(struct pci_dev *pci,
+					   const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct resource *fm_res = NULL;
+	struct resource *mpu_res = NULL;
+	struct snd_ymfpci *chip;
+	struct snd_opl3 *opl3;
+	const char *str, *model;
+	int err;
+	u16 legacy_ctrl, legacy_ctrl2, old_legacy_ctrl;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	switch (pci_id->device) {
+	case 0x0004: str = "YMF724";  model = "DS-1"; break;
+	case 0x000d: str = "YMF724F"; model = "DS-1"; break;
+	case 0x000a: str = "YMF740";  model = "DS-1L"; break;
+	case 0x000c: str = "YMF740C"; model = "DS-1L"; break;
+	case 0x0010: str = "YMF744";  model = "DS-1S"; break;
+	case 0x0012: str = "YMF754";  model = "DS-1E"; break;
+	default: model = str = "???"; break;
+	}
+
+	legacy_ctrl = 0;
+	legacy_ctrl2 = 0x0800;	/* SBEN = 0, SMOD = 01, LAD = 0 */
+
+	if (pci_id->device >= 0x0010) { /* YMF 744/754 */
+		if (fm_port[dev] == 1) {
+			/* auto-detect */
+			fm_port[dev] = pci_resource_start(pci, 1);
+		}
+		if (fm_port[dev] > 0 &&
+		    (fm_res = request_region(fm_port[dev], 4, "YMFPCI OPL3")) != NULL) {
+			legacy_ctrl |= YMFPCI_LEGACY_FMEN;
+			pci_write_config_word(pci, PCIR_DSXG_FMBASE, fm_port[dev]);
+		}
+		if (mpu_port[dev] == 1) {
+			/* auto-detect */
+			mpu_port[dev] = pci_resource_start(pci, 1) + 0x20;
+		}
+		if (mpu_port[dev] > 0 &&
+		    (mpu_res = request_region(mpu_port[dev], 2, "YMFPCI MPU401")) != NULL) {
+			legacy_ctrl |= YMFPCI_LEGACY_MEN;
+			pci_write_config_word(pci, PCIR_DSXG_MPU401BASE, mpu_port[dev]);
+		}
+	} else {
+		switch (fm_port[dev]) {
+		case 0x388: legacy_ctrl2 |= 0; break;
+		case 0x398: legacy_ctrl2 |= 1; break;
+		case 0x3a0: legacy_ctrl2 |= 2; break;
+		case 0x3a8: legacy_ctrl2 |= 3; break;
+		default: fm_port[dev] = 0; break;
+		}
+		if (fm_port[dev] > 0 &&
+		    (fm_res = request_region(fm_port[dev], 4, "YMFPCI OPL3")) != NULL) {
+			legacy_ctrl |= YMFPCI_LEGACY_FMEN;
+		} else {
+			legacy_ctrl2 &= ~YMFPCI_LEGACY2_FMIO;
+			fm_port[dev] = 0;
+		}
+		switch (mpu_port[dev]) {
+		case 0x330: legacy_ctrl2 |= 0 << 4; break;
+		case 0x300: legacy_ctrl2 |= 1 << 4; break;
+		case 0x332: legacy_ctrl2 |= 2 << 4; break;
+		case 0x334: legacy_ctrl2 |= 3 << 4; break;
+		default: mpu_port[dev] = 0; break;
+		}
+		if (mpu_port[dev] > 0 &&
+		    (mpu_res = request_region(mpu_port[dev], 2, "YMFPCI MPU401")) != NULL) {
+			legacy_ctrl |= YMFPCI_LEGACY_MEN;
+		} else {
+			legacy_ctrl2 &= ~YMFPCI_LEGACY2_MPUIO;
+			mpu_port[dev] = 0;
+		}
+	}
+	if (mpu_res) {
+		legacy_ctrl |= YMFPCI_LEGACY_MIEN;
+		legacy_ctrl2 |= YMFPCI_LEGACY2_IMOD;
+	}
+	pci_read_config_word(pci, PCIR_DSXG_LEGACY, &old_legacy_ctrl);
+	pci_write_config_word(pci, PCIR_DSXG_LEGACY, legacy_ctrl);
+	pci_write_config_word(pci, PCIR_DSXG_ELEGACY, legacy_ctrl2);
+	if ((err = snd_ymfpci_create(card, pci,
+				     old_legacy_ctrl,
+			 	     &chip)) < 0) {
+		snd_card_free(card);
+		release_and_free_resource(mpu_res);
+		release_and_free_resource(fm_res);
+		return err;
+	}
+	chip->fm_res = fm_res;
+	chip->mpu_res = mpu_res;
+	card->private_data = chip;
+
+	strcpy(card->driver, str);
+	sprintf(card->shortname, "Yamaha %s (%s)", model, str);
+	sprintf(card->longname, "%s at 0x%lx, irq %i",
+		card->shortname,
+		chip->reg_area_phys,
+		chip->irq);
+	if ((err = snd_ymfpci_pcm(chip, 0, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_ymfpci_pcm_spdif(chip, 1, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_ymfpci_pcm_4ch(chip, 2, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_ymfpci_pcm2(chip, 3, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_ymfpci_mixer(chip, rear_switch[dev])) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if ((err = snd_ymfpci_timer(chip, 0)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	if (chip->mpu_res) {
+		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_YMFPCI,
+					       mpu_port[dev],
+					       MPU401_INFO_INTEGRATED,
+					       pci->irq, 0, &chip->rawmidi)) < 0) {
+			printk(KERN_WARNING "ymfpci: cannot initialize MPU401 at 0x%lx, skipping...\n", mpu_port[dev]);
+			legacy_ctrl &= ~YMFPCI_LEGACY_MIEN; /* disable MPU401 irq */
+			pci_write_config_word(pci, PCIR_DSXG_LEGACY, legacy_ctrl);
+		}
+	}
+	if (chip->fm_res) {
+		if ((err = snd_opl3_create(card,
+					   fm_port[dev],
+					   fm_port[dev] + 2,
+					   OPL3_HW_OPL3, 1, &opl3)) < 0) {
+			printk(KERN_WARNING "ymfpci: cannot initialize FM OPL3 at 0x%lx, skipping...\n", fm_port[dev]);
+			legacy_ctrl &= ~YMFPCI_LEGACY_FMEN;
+			pci_write_config_word(pci, PCIR_DSXG_LEGACY, legacy_ctrl);
+		} else if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) {
+			snd_card_free(card);
+			snd_printk(KERN_ERR "cannot create opl3 hwdep\n");
+			return err;
+		}
+	}
+
+	snd_ymfpci_create_gameport(chip, dev, legacy_ctrl, legacy_ctrl2);
+
+	if ((err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+}
+
+static void __devexit snd_card_ymfpci_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+static struct pci_driver driver = {
+	.name = "Yamaha DS-1 PCI",
+	.id_table = snd_ymfpci_ids,
+	.probe = snd_card_ymfpci_probe,
+	.remove = __devexit_p(snd_card_ymfpci_remove),
+#ifdef CONFIG_PM
+	.suspend = snd_ymfpci_suspend,
+	.resume = snd_ymfpci_resume,
+#endif
+};
+
+static int __init alsa_card_ymfpci_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_ymfpci_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_ymfpci_init)
+module_exit(alsa_card_ymfpci_exit)
diff --git a/linux/sound/pci/ymfpci/ymfpci_main.c b/linux/sound/pci/ymfpci/ymfpci_main.c
new file mode 100644
index 0000000..5518371
--- /dev/null
+++ b/linux/sound/pci/ymfpci/ymfpci_main.c
@@ -0,0 +1,2441 @@
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Routines for control of YMF724/740/744/754 chips
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+#include <sound/ymfpci.h>
+#include <sound/asoundef.h>
+#include <sound/mpu401.h>
+
+#include <asm/io.h>
+#include <asm/byteorder.h>
+
+/*
+ *  common I/O routines
+ */
+
+static void snd_ymfpci_irq_wait(struct snd_ymfpci *chip);
+
+static inline u8 snd_ymfpci_readb(struct snd_ymfpci *chip, u32 offset)
+{
+	return readb(chip->reg_area_virt + offset);
+}
+
+static inline void snd_ymfpci_writeb(struct snd_ymfpci *chip, u32 offset, u8 val)
+{
+	writeb(val, chip->reg_area_virt + offset);
+}
+
+static inline u16 snd_ymfpci_readw(struct snd_ymfpci *chip, u32 offset)
+{
+	return readw(chip->reg_area_virt + offset);
+}
+
+static inline void snd_ymfpci_writew(struct snd_ymfpci *chip, u32 offset, u16 val)
+{
+	writew(val, chip->reg_area_virt + offset);
+}
+
+static inline u32 snd_ymfpci_readl(struct snd_ymfpci *chip, u32 offset)
+{
+	return readl(chip->reg_area_virt + offset);
+}
+
+static inline void snd_ymfpci_writel(struct snd_ymfpci *chip, u32 offset, u32 val)
+{
+	writel(val, chip->reg_area_virt + offset);
+}
+
+static int snd_ymfpci_codec_ready(struct snd_ymfpci *chip, int secondary)
+{
+	unsigned long end_time;
+	u32 reg = secondary ? YDSXGR_SECSTATUSADR : YDSXGR_PRISTATUSADR;
+	
+	end_time = jiffies + msecs_to_jiffies(750);
+	do {
+		if ((snd_ymfpci_readw(chip, reg) & 0x8000) == 0)
+			return 0;
+		schedule_timeout_uninterruptible(1);
+	} while (time_before(jiffies, end_time));
+	snd_printk(KERN_ERR "codec_ready: codec %i is not ready [0x%x]\n", secondary, snd_ymfpci_readw(chip, reg));
+	return -EBUSY;
+}
+
+static void snd_ymfpci_codec_write(struct snd_ac97 *ac97, u16 reg, u16 val)
+{
+	struct snd_ymfpci *chip = ac97->private_data;
+	u32 cmd;
+	
+	snd_ymfpci_codec_ready(chip, 0);
+	cmd = ((YDSXG_AC97WRITECMD | reg) << 16) | val;
+	snd_ymfpci_writel(chip, YDSXGR_AC97CMDDATA, cmd);
+}
+
+static u16 snd_ymfpci_codec_read(struct snd_ac97 *ac97, u16 reg)
+{
+	struct snd_ymfpci *chip = ac97->private_data;
+
+	if (snd_ymfpci_codec_ready(chip, 0))
+		return ~0;
+	snd_ymfpci_writew(chip, YDSXGR_AC97CMDADR, YDSXG_AC97READCMD | reg);
+	if (snd_ymfpci_codec_ready(chip, 0))
+		return ~0;
+	if (chip->device_id == PCI_DEVICE_ID_YAMAHA_744 && chip->rev < 2) {
+		int i;
+		for (i = 0; i < 600; i++)
+			snd_ymfpci_readw(chip, YDSXGR_PRISTATUSDATA);
+	}
+	return snd_ymfpci_readw(chip, YDSXGR_PRISTATUSDATA);
+}
+
+/*
+ *  Misc routines
+ */
+
+static u32 snd_ymfpci_calc_delta(u32 rate)
+{
+	switch (rate) {
+	case 8000:	return 0x02aaab00;
+	case 11025:	return 0x03accd00;
+	case 16000:	return 0x05555500;
+	case 22050:	return 0x07599a00;
+	case 32000:	return 0x0aaaab00;
+	case 44100:	return 0x0eb33300;
+	default:	return ((rate << 16) / 375) << 5;
+	}
+}
+
+static u32 def_rate[8] = {
+	100, 2000, 8000, 11025, 16000, 22050, 32000, 48000
+};
+
+static u32 snd_ymfpci_calc_lpfK(u32 rate)
+{
+	u32 i;
+	static u32 val[8] = {
+		0x00570000, 0x06AA0000, 0x18B20000, 0x20930000,
+		0x2B9A0000, 0x35A10000, 0x3EAA0000, 0x40000000
+	};
+	
+	if (rate == 44100)
+		return 0x40000000;	/* FIXME: What's the right value? */
+	for (i = 0; i < 8; i++)
+		if (rate <= def_rate[i])
+			return val[i];
+	return val[0];
+}
+
+static u32 snd_ymfpci_calc_lpfQ(u32 rate)
+{
+	u32 i;
+	static u32 val[8] = {
+		0x35280000, 0x34A70000, 0x32020000, 0x31770000,
+		0x31390000, 0x31C90000, 0x33D00000, 0x40000000
+	};
+	
+	if (rate == 44100)
+		return 0x370A0000;
+	for (i = 0; i < 8; i++)
+		if (rate <= def_rate[i])
+			return val[i];
+	return val[0];
+}
+
+/*
+ *  Hardware start management
+ */
+
+static void snd_ymfpci_hw_start(struct snd_ymfpci *chip)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->start_count++ > 0)
+		goto __end;
+	snd_ymfpci_writel(chip, YDSXGR_MODE,
+			  snd_ymfpci_readl(chip, YDSXGR_MODE) | 3);
+	chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT) & 1;
+      __end:
+      	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+static void snd_ymfpci_hw_stop(struct snd_ymfpci *chip)
+{
+	unsigned long flags;
+	long timeout = 1000;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (--chip->start_count > 0)
+		goto __end;
+	snd_ymfpci_writel(chip, YDSXGR_MODE,
+			  snd_ymfpci_readl(chip, YDSXGR_MODE) & ~3);
+	while (timeout-- > 0) {
+		if ((snd_ymfpci_readl(chip, YDSXGR_STATUS) & 2) == 0)
+			break;
+	}
+	if (atomic_read(&chip->interrupt_sleep_count)) {
+		atomic_set(&chip->interrupt_sleep_count, 0);
+		wake_up(&chip->interrupt_sleep);
+	}
+      __end:
+      	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+/*
+ *  Playback voice management
+ */
+
+static int voice_alloc(struct snd_ymfpci *chip,
+		       enum snd_ymfpci_voice_type type, int pair,
+		       struct snd_ymfpci_voice **rvoice)
+{
+	struct snd_ymfpci_voice *voice, *voice2;
+	int idx;
+	
+	*rvoice = NULL;
+	for (idx = 0; idx < YDSXG_PLAYBACK_VOICES; idx += pair ? 2 : 1) {
+		voice = &chip->voices[idx];
+		voice2 = pair ? &chip->voices[idx+1] : NULL;
+		if (voice->use || (voice2 && voice2->use))
+			continue;
+		voice->use = 1;
+		if (voice2)
+			voice2->use = 1;
+		switch (type) {
+		case YMFPCI_PCM:
+			voice->pcm = 1;
+			if (voice2)
+				voice2->pcm = 1;
+			break;
+		case YMFPCI_SYNTH:
+			voice->synth = 1;
+			break;
+		case YMFPCI_MIDI:
+			voice->midi = 1;
+			break;
+		}
+		snd_ymfpci_hw_start(chip);
+		if (voice2)
+			snd_ymfpci_hw_start(chip);
+		*rvoice = voice;
+		return 0;
+	}
+	return -ENOMEM;
+}
+
+static int snd_ymfpci_voice_alloc(struct snd_ymfpci *chip,
+				  enum snd_ymfpci_voice_type type, int pair,
+				  struct snd_ymfpci_voice **rvoice)
+{
+	unsigned long flags;
+	int result;
+	
+	if (snd_BUG_ON(!rvoice))
+		return -EINVAL;
+	if (snd_BUG_ON(pair && type != YMFPCI_PCM))
+		return -EINVAL;
+	
+	spin_lock_irqsave(&chip->voice_lock, flags);
+	for (;;) {
+		result = voice_alloc(chip, type, pair, rvoice);
+		if (result == 0 || type != YMFPCI_PCM)
+			break;
+		/* TODO: synth/midi voice deallocation */
+		break;
+	}
+	spin_unlock_irqrestore(&chip->voice_lock, flags);	
+	return result;		
+}
+
+static int snd_ymfpci_voice_free(struct snd_ymfpci *chip, struct snd_ymfpci_voice *pvoice)
+{
+	unsigned long flags;
+	
+	if (snd_BUG_ON(!pvoice))
+		return -EINVAL;
+	snd_ymfpci_hw_stop(chip);
+	spin_lock_irqsave(&chip->voice_lock, flags);
+	if (pvoice->number == chip->src441_used) {
+		chip->src441_used = -1;
+		pvoice->ypcm->use_441_slot = 0;
+	}
+	pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0;
+	pvoice->ypcm = NULL;
+	pvoice->interrupt = NULL;
+	spin_unlock_irqrestore(&chip->voice_lock, flags);
+	return 0;
+}
+
+/*
+ *  PCM part
+ */
+
+static void snd_ymfpci_pcm_interrupt(struct snd_ymfpci *chip, struct snd_ymfpci_voice *voice)
+{
+	struct snd_ymfpci_pcm *ypcm;
+	u32 pos, delta;
+	
+	if ((ypcm = voice->ypcm) == NULL)
+		return;
+	if (ypcm->substream == NULL)
+		return;
+	spin_lock(&chip->reg_lock);
+	if (ypcm->running) {
+		pos = le32_to_cpu(voice->bank[chip->active_bank].start);
+		if (pos < ypcm->last_pos)
+			delta = pos + (ypcm->buffer_size - ypcm->last_pos);
+		else
+			delta = pos - ypcm->last_pos;
+		ypcm->period_pos += delta;
+		ypcm->last_pos = pos;
+		if (ypcm->period_pos >= ypcm->period_size) {
+			/*
+			printk(KERN_DEBUG
+			       "done - active_bank = 0x%x, start = 0x%x\n",
+			       chip->active_bank,
+			       voice->bank[chip->active_bank].start);
+			*/
+			ypcm->period_pos %= ypcm->period_size;
+			spin_unlock(&chip->reg_lock);
+			snd_pcm_period_elapsed(ypcm->substream);
+			spin_lock(&chip->reg_lock);
+		}
+
+		if (unlikely(ypcm->update_pcm_vol)) {
+			unsigned int subs = ypcm->substream->number;
+			unsigned int next_bank = 1 - chip->active_bank;
+			struct snd_ymfpci_playback_bank *bank;
+			u32 volume;
+			
+			bank = &voice->bank[next_bank];
+			volume = cpu_to_le32(chip->pcm_mixer[subs].left << 15);
+			bank->left_gain_end = volume;
+			if (ypcm->output_rear)
+				bank->eff2_gain_end = volume;
+			if (ypcm->voices[1])
+				bank = &ypcm->voices[1]->bank[next_bank];
+			volume = cpu_to_le32(chip->pcm_mixer[subs].right << 15);
+			bank->right_gain_end = volume;
+			if (ypcm->output_rear)
+				bank->eff3_gain_end = volume;
+			ypcm->update_pcm_vol--;
+		}
+	}
+	spin_unlock(&chip->reg_lock);
+}
+
+static void snd_ymfpci_pcm_capture_interrupt(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm = runtime->private_data;
+	struct snd_ymfpci *chip = ypcm->chip;
+	u32 pos, delta;
+	
+	spin_lock(&chip->reg_lock);
+	if (ypcm->running) {
+		pos = le32_to_cpu(chip->bank_capture[ypcm->capture_bank_number][chip->active_bank]->start) >> ypcm->shift;
+		if (pos < ypcm->last_pos)
+			delta = pos + (ypcm->buffer_size - ypcm->last_pos);
+		else
+			delta = pos - ypcm->last_pos;
+		ypcm->period_pos += delta;
+		ypcm->last_pos = pos;
+		if (ypcm->period_pos >= ypcm->period_size) {
+			ypcm->period_pos %= ypcm->period_size;
+			/*
+			printk(KERN_DEBUG
+			       "done - active_bank = 0x%x, start = 0x%x\n",
+			       chip->active_bank,
+			       voice->bank[chip->active_bank].start);
+			*/
+			spin_unlock(&chip->reg_lock);
+			snd_pcm_period_elapsed(substream);
+			spin_lock(&chip->reg_lock);
+		}
+	}
+	spin_unlock(&chip->reg_lock);
+}
+
+static int snd_ymfpci_playback_trigger(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data;
+	struct snd_kcontrol *kctl = NULL;
+	int result = 0;
+
+	spin_lock(&chip->reg_lock);
+	if (ypcm->voices[0] == NULL) {
+		result = -EINVAL;
+		goto __unlock;
+	}
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		chip->ctrl_playback[ypcm->voices[0]->number + 1] = cpu_to_le32(ypcm->voices[0]->bank_addr);
+		if (ypcm->voices[1] != NULL && !ypcm->use_441_slot)
+			chip->ctrl_playback[ypcm->voices[1]->number + 1] = cpu_to_le32(ypcm->voices[1]->bank_addr);
+		ypcm->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (substream->pcm == chip->pcm && !ypcm->use_441_slot) {
+			kctl = chip->pcm_mixer[substream->number].ctl;
+			kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		}
+		/* fall through */
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		chip->ctrl_playback[ypcm->voices[0]->number + 1] = 0;
+		if (ypcm->voices[1] != NULL && !ypcm->use_441_slot)
+			chip->ctrl_playback[ypcm->voices[1]->number + 1] = 0;
+		ypcm->running = 0;
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+      __unlock:
+	spin_unlock(&chip->reg_lock);
+	if (kctl)
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id);
+	return result;
+}
+static int snd_ymfpci_capture_trigger(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data;
+	int result = 0;
+	u32 tmp;
+
+	spin_lock(&chip->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		tmp = snd_ymfpci_readl(chip, YDSXGR_MAPOFREC) | (1 << ypcm->capture_bank_number);
+		snd_ymfpci_writel(chip, YDSXGR_MAPOFREC, tmp);
+		ypcm->running = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		tmp = snd_ymfpci_readl(chip, YDSXGR_MAPOFREC) & ~(1 << ypcm->capture_bank_number);
+		snd_ymfpci_writel(chip, YDSXGR_MAPOFREC, tmp);
+		ypcm->running = 0;
+		break;
+	default:
+		result = -EINVAL;
+		break;
+	}
+	spin_unlock(&chip->reg_lock);
+	return result;
+}
+
+static int snd_ymfpci_pcm_voice_alloc(struct snd_ymfpci_pcm *ypcm, int voices)
+{
+	int err;
+
+	if (ypcm->voices[1] != NULL && voices < 2) {
+		snd_ymfpci_voice_free(ypcm->chip, ypcm->voices[1]);
+		ypcm->voices[1] = NULL;
+	}
+	if (voices == 1 && ypcm->voices[0] != NULL)
+		return 0;		/* already allocated */
+	if (voices == 2 && ypcm->voices[0] != NULL && ypcm->voices[1] != NULL)
+		return 0;		/* already allocated */
+	if (voices > 1) {
+		if (ypcm->voices[0] != NULL && ypcm->voices[1] == NULL) {
+			snd_ymfpci_voice_free(ypcm->chip, ypcm->voices[0]);
+			ypcm->voices[0] = NULL;
+		}		
+	}
+	err = snd_ymfpci_voice_alloc(ypcm->chip, YMFPCI_PCM, voices > 1, &ypcm->voices[0]);
+	if (err < 0)
+		return err;
+	ypcm->voices[0]->ypcm = ypcm;
+	ypcm->voices[0]->interrupt = snd_ymfpci_pcm_interrupt;
+	if (voices > 1) {
+		ypcm->voices[1] = &ypcm->chip->voices[ypcm->voices[0]->number + 1];
+		ypcm->voices[1]->ypcm = ypcm;
+	}
+	return 0;
+}
+
+static void snd_ymfpci_pcm_init_voice(struct snd_ymfpci_pcm *ypcm, unsigned int voiceidx,
+				      struct snd_pcm_runtime *runtime,
+				      int has_pcm_volume)
+{
+	struct snd_ymfpci_voice *voice = ypcm->voices[voiceidx];
+	u32 format;
+	u32 delta = snd_ymfpci_calc_delta(runtime->rate);
+	u32 lpfQ = snd_ymfpci_calc_lpfQ(runtime->rate);
+	u32 lpfK = snd_ymfpci_calc_lpfK(runtime->rate);
+	struct snd_ymfpci_playback_bank *bank;
+	unsigned int nbank;
+	u32 vol_left, vol_right;
+	u8 use_left, use_right;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!voice))
+		return;
+	if (runtime->channels == 1) {
+		use_left = 1;
+		use_right = 1;
+	} else {
+		use_left = (voiceidx & 1) == 0;
+		use_right = !use_left;
+	}
+	if (has_pcm_volume) {
+		vol_left = cpu_to_le32(ypcm->chip->pcm_mixer
+				       [ypcm->substream->number].left << 15);
+		vol_right = cpu_to_le32(ypcm->chip->pcm_mixer
+					[ypcm->substream->number].right << 15);
+	} else {
+		vol_left = cpu_to_le32(0x40000000);
+		vol_right = cpu_to_le32(0x40000000);
+	}
+	spin_lock_irqsave(&ypcm->chip->voice_lock, flags);
+	format = runtime->channels == 2 ? 0x00010000 : 0;
+	if (snd_pcm_format_width(runtime->format) == 8)
+		format |= 0x80000000;
+	else if (ypcm->chip->device_id == PCI_DEVICE_ID_YAMAHA_754 &&
+		 runtime->rate == 44100 && runtime->channels == 2 &&
+		 voiceidx == 0 && (ypcm->chip->src441_used == -1 ||
+				   ypcm->chip->src441_used == voice->number)) {
+		ypcm->chip->src441_used = voice->number;
+		ypcm->use_441_slot = 1;
+		format |= 0x10000000;
+	}
+	if (ypcm->chip->src441_used == voice->number &&
+	    (format & 0x10000000) == 0) {
+		ypcm->chip->src441_used = -1;
+		ypcm->use_441_slot = 0;
+	}
+	if (runtime->channels == 2 && (voiceidx & 1) != 0)
+		format |= 1;
+	spin_unlock_irqrestore(&ypcm->chip->voice_lock, flags);
+	for (nbank = 0; nbank < 2; nbank++) {
+		bank = &voice->bank[nbank];
+		memset(bank, 0, sizeof(*bank));
+		bank->format = cpu_to_le32(format);
+		bank->base = cpu_to_le32(runtime->dma_addr);
+		bank->loop_end = cpu_to_le32(ypcm->buffer_size);
+		bank->lpfQ = cpu_to_le32(lpfQ);
+		bank->delta =
+		bank->delta_end = cpu_to_le32(delta);
+		bank->lpfK =
+		bank->lpfK_end = cpu_to_le32(lpfK);
+		bank->eg_gain =
+		bank->eg_gain_end = cpu_to_le32(0x40000000);
+
+		if (ypcm->output_front) {
+			if (use_left) {
+				bank->left_gain =
+				bank->left_gain_end = vol_left;
+			}
+			if (use_right) {
+				bank->right_gain =
+				bank->right_gain_end = vol_right;
+			}
+		}
+		if (ypcm->output_rear) {
+		        if (!ypcm->swap_rear) {
+        			if (use_left) {
+        				bank->eff2_gain =
+        				bank->eff2_gain_end = vol_left;
+        			}
+        			if (use_right) {
+        				bank->eff3_gain =
+        				bank->eff3_gain_end = vol_right;
+        			}
+		        } else {
+        			/* The SPDIF out channels seem to be swapped, so we have
+        			 * to swap them here, too.  The rear analog out channels
+        			 * will be wrong, but otherwise AC3 would not work.
+        			 */
+        			if (use_left) {
+        				bank->eff3_gain =
+        				bank->eff3_gain_end = vol_left;
+        			}
+        			if (use_right) {
+        				bank->eff2_gain =
+        				bank->eff2_gain_end = vol_right;
+        			}
+        		}
+                }
+	}
+}
+
+static int __devinit snd_ymfpci_ac3_init(struct snd_ymfpci *chip)
+{
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+				4096, &chip->ac3_tmp_base) < 0)
+		return -ENOMEM;
+
+	chip->bank_effect[3][0]->base =
+	chip->bank_effect[3][1]->base = cpu_to_le32(chip->ac3_tmp_base.addr);
+	chip->bank_effect[3][0]->loop_end =
+	chip->bank_effect[3][1]->loop_end = cpu_to_le32(1024);
+	chip->bank_effect[4][0]->base =
+	chip->bank_effect[4][1]->base = cpu_to_le32(chip->ac3_tmp_base.addr + 2048);
+	chip->bank_effect[4][0]->loop_end =
+	chip->bank_effect[4][1]->loop_end = cpu_to_le32(1024);
+
+	spin_lock_irq(&chip->reg_lock);
+	snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT,
+			  snd_ymfpci_readl(chip, YDSXGR_MAPOFEFFECT) | 3 << 3);
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_ymfpci_ac3_done(struct snd_ymfpci *chip)
+{
+	spin_lock_irq(&chip->reg_lock);
+	snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT,
+			  snd_ymfpci_readl(chip, YDSXGR_MAPOFEFFECT) & ~(3 << 3));
+	spin_unlock_irq(&chip->reg_lock);
+	// snd_ymfpci_irq_wait(chip);
+	if (chip->ac3_tmp_base.area) {
+		snd_dma_free_pages(&chip->ac3_tmp_base);
+		chip->ac3_tmp_base.area = NULL;
+	}
+	return 0;
+}
+
+static int snd_ymfpci_playback_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm = runtime->private_data;
+	int err;
+
+	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
+		return err;
+	if ((err = snd_ymfpci_pcm_voice_alloc(ypcm, params_channels(hw_params))) < 0)
+		return err;
+	return 0;
+}
+
+static int snd_ymfpci_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm;
+	
+	if (runtime->private_data == NULL)
+		return 0;
+	ypcm = runtime->private_data;
+
+	/* wait, until the PCI operations are not finished */
+	snd_ymfpci_irq_wait(chip);
+	snd_pcm_lib_free_pages(substream);
+	if (ypcm->voices[1]) {
+		snd_ymfpci_voice_free(chip, ypcm->voices[1]);
+		ypcm->voices[1] = NULL;
+	}
+	if (ypcm->voices[0]) {
+		snd_ymfpci_voice_free(chip, ypcm->voices[0]);
+		ypcm->voices[0] = NULL;
+	}
+	return 0;
+}
+
+static int snd_ymfpci_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm = runtime->private_data;
+	struct snd_kcontrol *kctl;
+	unsigned int nvoice;
+
+	ypcm->period_size = runtime->period_size;
+	ypcm->buffer_size = runtime->buffer_size;
+	ypcm->period_pos = 0;
+	ypcm->last_pos = 0;
+	for (nvoice = 0; nvoice < runtime->channels; nvoice++)
+		snd_ymfpci_pcm_init_voice(ypcm, nvoice, runtime,
+					  substream->pcm == chip->pcm);
+
+	if (substream->pcm == chip->pcm && !ypcm->use_441_slot) {
+		kctl = chip->pcm_mixer[substream->number].ctl;
+		kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id);
+	}
+	return 0;
+}
+
+static int snd_ymfpci_capture_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_ymfpci_capture_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+
+	/* wait, until the PCI operations are not finished */
+	snd_ymfpci_irq_wait(chip);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_ymfpci_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm = runtime->private_data;
+	struct snd_ymfpci_capture_bank * bank;
+	int nbank;
+	u32 rate, format;
+
+	ypcm->period_size = runtime->period_size;
+	ypcm->buffer_size = runtime->buffer_size;
+	ypcm->period_pos = 0;
+	ypcm->last_pos = 0;
+	ypcm->shift = 0;
+	rate = ((48000 * 4096) / runtime->rate) - 1;
+	format = 0;
+	if (runtime->channels == 2) {
+		format |= 2;
+		ypcm->shift++;
+	}
+	if (snd_pcm_format_width(runtime->format) == 8)
+		format |= 1;
+	else
+		ypcm->shift++;
+	switch (ypcm->capture_bank_number) {
+	case 0:
+		snd_ymfpci_writel(chip, YDSXGR_RECFORMAT, format);
+		snd_ymfpci_writel(chip, YDSXGR_RECSLOTSR, rate);
+		break;
+	case 1:
+		snd_ymfpci_writel(chip, YDSXGR_ADCFORMAT, format);
+		snd_ymfpci_writel(chip, YDSXGR_ADCSLOTSR, rate);
+		break;
+	}
+	for (nbank = 0; nbank < 2; nbank++) {
+		bank = chip->bank_capture[ypcm->capture_bank_number][nbank];
+		bank->base = cpu_to_le32(runtime->dma_addr);
+		bank->loop_end = cpu_to_le32(ypcm->buffer_size << ypcm->shift);
+		bank->start = 0;
+		bank->num_of_loops = 0;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_ymfpci_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm = runtime->private_data;
+	struct snd_ymfpci_voice *voice = ypcm->voices[0];
+
+	if (!(ypcm->running && voice))
+		return 0;
+	return le32_to_cpu(voice->bank[chip->active_bank].start);
+}
+
+static snd_pcm_uframes_t snd_ymfpci_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm = runtime->private_data;
+
+	if (!ypcm->running)
+		return 0;
+	return le32_to_cpu(chip->bank_capture[ypcm->capture_bank_number][chip->active_bank]->start) >> ypcm->shift;
+}
+
+static void snd_ymfpci_irq_wait(struct snd_ymfpci *chip)
+{
+	wait_queue_t wait;
+	int loops = 4;
+
+	while (loops-- > 0) {
+		if ((snd_ymfpci_readl(chip, YDSXGR_MODE) & 3) == 0)
+		 	continue;
+		init_waitqueue_entry(&wait, current);
+		add_wait_queue(&chip->interrupt_sleep, &wait);
+		atomic_inc(&chip->interrupt_sleep_count);
+		schedule_timeout_uninterruptible(msecs_to_jiffies(50));
+		remove_wait_queue(&chip->interrupt_sleep, &wait);
+	}
+}
+
+static irqreturn_t snd_ymfpci_interrupt(int irq, void *dev_id)
+{
+	struct snd_ymfpci *chip = dev_id;
+	u32 status, nvoice, mode;
+	struct snd_ymfpci_voice *voice;
+
+	status = snd_ymfpci_readl(chip, YDSXGR_STATUS);
+	if (status & 0x80000000) {
+		chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT) & 1;
+		spin_lock(&chip->voice_lock);
+		for (nvoice = 0; nvoice < YDSXG_PLAYBACK_VOICES; nvoice++) {
+			voice = &chip->voices[nvoice];
+			if (voice->interrupt)
+				voice->interrupt(chip, voice);
+		}
+		for (nvoice = 0; nvoice < YDSXG_CAPTURE_VOICES; nvoice++) {
+			if (chip->capture_substream[nvoice])
+				snd_ymfpci_pcm_capture_interrupt(chip->capture_substream[nvoice]);
+		}
+#if 0
+		for (nvoice = 0; nvoice < YDSXG_EFFECT_VOICES; nvoice++) {
+			if (chip->effect_substream[nvoice])
+				snd_ymfpci_pcm_effect_interrupt(chip->effect_substream[nvoice]);
+		}
+#endif
+		spin_unlock(&chip->voice_lock);
+		spin_lock(&chip->reg_lock);
+		snd_ymfpci_writel(chip, YDSXGR_STATUS, 0x80000000);
+		mode = snd_ymfpci_readl(chip, YDSXGR_MODE) | 2;
+		snd_ymfpci_writel(chip, YDSXGR_MODE, mode);
+		spin_unlock(&chip->reg_lock);
+
+		if (atomic_read(&chip->interrupt_sleep_count)) {
+			atomic_set(&chip->interrupt_sleep_count, 0);
+			wake_up(&chip->interrupt_sleep);
+		}
+	}
+
+	status = snd_ymfpci_readw(chip, YDSXGR_INTFLAG);
+	if (status & 1) {
+		if (chip->timer)
+			snd_timer_interrupt(chip->timer, chip->timer_ticks);
+	}
+	snd_ymfpci_writew(chip, YDSXGR_INTFLAG, status);
+
+	if (chip->rawmidi)
+		snd_mpu401_uart_interrupt(irq, chip->rawmidi->private_data);
+	return IRQ_HANDLED;
+}
+
+static struct snd_pcm_hardware snd_ymfpci_playback =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_MMAP_VALID | 
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		8000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	256 * 1024, /* FIXME: enough? */
+	.period_bytes_min =	64,
+	.period_bytes_max =	256 * 1024, /* FIXME: enough? */
+	.periods_min =		3,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static struct snd_pcm_hardware snd_ymfpci_capture =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_PAUSE |
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+	.rate_min =		8000,
+	.rate_max =		48000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	256 * 1024, /* FIXME: enough? */
+	.period_bytes_min =	64,
+	.period_bytes_max =	256 * 1024, /* FIXME: enough? */
+	.periods_min =		3,
+	.periods_max =		1024,
+	.fifo_size =		0,
+};
+
+static void snd_ymfpci_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+	kfree(runtime->private_data);
+}
+
+static int snd_ymfpci_playback_open_1(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm;
+
+	ypcm = kzalloc(sizeof(*ypcm), GFP_KERNEL);
+	if (ypcm == NULL)
+		return -ENOMEM;
+	ypcm->chip = chip;
+	ypcm->type = PLAYBACK_VOICE;
+	ypcm->substream = substream;
+	runtime->hw = snd_ymfpci_playback;
+	runtime->private_data = ypcm;
+	runtime->private_free = snd_ymfpci_pcm_free_substream;
+	/* FIXME? True value is 256/48 = 5.33333 ms */
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 5333, UINT_MAX);
+	return 0;
+}
+
+/* call with spinlock held */
+static void ymfpci_open_extension(struct snd_ymfpci *chip)
+{
+	if (! chip->rear_opened) {
+		if (! chip->spdif_opened) /* set AC3 */
+			snd_ymfpci_writel(chip, YDSXGR_MODE,
+					  snd_ymfpci_readl(chip, YDSXGR_MODE) | (1 << 30));
+		/* enable second codec (4CHEN) */
+		snd_ymfpci_writew(chip, YDSXGR_SECCONFIG,
+				  (snd_ymfpci_readw(chip, YDSXGR_SECCONFIG) & ~0x0330) | 0x0010);
+	}
+}
+
+/* call with spinlock held */
+static void ymfpci_close_extension(struct snd_ymfpci *chip)
+{
+	if (! chip->rear_opened) {
+		if (! chip->spdif_opened)
+			snd_ymfpci_writel(chip, YDSXGR_MODE,
+					  snd_ymfpci_readl(chip, YDSXGR_MODE) & ~(1 << 30));
+		snd_ymfpci_writew(chip, YDSXGR_SECCONFIG,
+				  (snd_ymfpci_readw(chip, YDSXGR_SECCONFIG) & ~0x0330) & ~0x0010);
+	}
+}
+
+static int snd_ymfpci_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm;
+	int err;
+	
+	if ((err = snd_ymfpci_playback_open_1(substream)) < 0)
+		return err;
+	ypcm = runtime->private_data;
+	ypcm->output_front = 1;
+	ypcm->output_rear = chip->mode_dup4ch ? 1 : 0;
+	ypcm->swap_rear = 0;
+	spin_lock_irq(&chip->reg_lock);
+	if (ypcm->output_rear) {
+		ymfpci_open_extension(chip);
+		chip->rear_opened++;
+	}
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_ymfpci_playback_spdif_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm;
+	int err;
+	
+	if ((err = snd_ymfpci_playback_open_1(substream)) < 0)
+		return err;
+	ypcm = runtime->private_data;
+	ypcm->output_front = 0;
+	ypcm->output_rear = 1;
+	ypcm->swap_rear = 1;
+	spin_lock_irq(&chip->reg_lock);
+	snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTCTRL,
+			  snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) | 2);
+	ymfpci_open_extension(chip);
+	chip->spdif_pcm_bits = chip->spdif_bits;
+	snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_pcm_bits);
+	chip->spdif_opened++;
+	spin_unlock_irq(&chip->reg_lock);
+
+	chip->spdif_pcm_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &chip->spdif_pcm_ctl->id);
+	return 0;
+}
+
+static int snd_ymfpci_playback_4ch_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm;
+	int err;
+	
+	if ((err = snd_ymfpci_playback_open_1(substream)) < 0)
+		return err;
+	ypcm = runtime->private_data;
+	ypcm->output_front = 0;
+	ypcm->output_rear = 1;
+	ypcm->swap_rear = 0;
+	spin_lock_irq(&chip->reg_lock);
+	ymfpci_open_extension(chip);
+	chip->rear_opened++;
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_ymfpci_capture_open(struct snd_pcm_substream *substream,
+				   u32 capture_bank_number)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm;
+
+	ypcm = kzalloc(sizeof(*ypcm), GFP_KERNEL);
+	if (ypcm == NULL)
+		return -ENOMEM;
+	ypcm->chip = chip;
+	ypcm->type = capture_bank_number + CAPTURE_REC;
+	ypcm->substream = substream;	
+	ypcm->capture_bank_number = capture_bank_number;
+	chip->capture_substream[capture_bank_number] = substream;
+	runtime->hw = snd_ymfpci_capture;
+	/* FIXME? True value is 256/48 = 5.33333 ms */
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 5333, UINT_MAX);
+	runtime->private_data = ypcm;
+	runtime->private_free = snd_ymfpci_pcm_free_substream;
+	snd_ymfpci_hw_start(chip);
+	return 0;
+}
+
+static int snd_ymfpci_capture_rec_open(struct snd_pcm_substream *substream)
+{
+	return snd_ymfpci_capture_open(substream, 0);
+}
+
+static int snd_ymfpci_capture_ac97_open(struct snd_pcm_substream *substream)
+{
+	return snd_ymfpci_capture_open(substream, 1);
+}
+
+static int snd_ymfpci_playback_close_1(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int snd_ymfpci_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data;
+
+	spin_lock_irq(&chip->reg_lock);
+	if (ypcm->output_rear && chip->rear_opened > 0) {
+		chip->rear_opened--;
+		ymfpci_close_extension(chip);
+	}
+	spin_unlock_irq(&chip->reg_lock);
+	return snd_ymfpci_playback_close_1(substream);
+}
+
+static int snd_ymfpci_playback_spdif_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&chip->reg_lock);
+	chip->spdif_opened = 0;
+	ymfpci_close_extension(chip);
+	snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTCTRL,
+			  snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) & ~2);
+	snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_bits);
+	spin_unlock_irq(&chip->reg_lock);
+	chip->spdif_pcm_ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
+		       SNDRV_CTL_EVENT_MASK_INFO, &chip->spdif_pcm_ctl->id);
+	return snd_ymfpci_playback_close_1(substream);
+}
+
+static int snd_ymfpci_playback_4ch_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+
+	spin_lock_irq(&chip->reg_lock);
+	if (chip->rear_opened > 0) {
+		chip->rear_opened--;
+		ymfpci_close_extension(chip);
+	}
+	spin_unlock_irq(&chip->reg_lock);
+	return snd_ymfpci_playback_close_1(substream);
+}
+
+static int snd_ymfpci_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_ymfpci *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ymfpci_pcm *ypcm = runtime->private_data;
+
+	if (ypcm != NULL) {
+		chip->capture_substream[ypcm->capture_bank_number] = NULL;
+		snd_ymfpci_hw_stop(chip);
+	}
+	return 0;
+}
+
+static struct snd_pcm_ops snd_ymfpci_playback_ops = {
+	.open =			snd_ymfpci_playback_open,
+	.close =		snd_ymfpci_playback_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_ymfpci_playback_hw_params,
+	.hw_free =		snd_ymfpci_playback_hw_free,
+	.prepare =		snd_ymfpci_playback_prepare,
+	.trigger =		snd_ymfpci_playback_trigger,
+	.pointer =		snd_ymfpci_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_ymfpci_capture_rec_ops = {
+	.open =			snd_ymfpci_capture_rec_open,
+	.close =		snd_ymfpci_capture_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_ymfpci_capture_hw_params,
+	.hw_free =		snd_ymfpci_capture_hw_free,
+	.prepare =		snd_ymfpci_capture_prepare,
+	.trigger =		snd_ymfpci_capture_trigger,
+	.pointer =		snd_ymfpci_capture_pointer,
+};
+
+int __devinit snd_ymfpci_pcm(struct snd_ymfpci *chip, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(chip->card, "YMFPCI", device, 32, 1, &pcm)) < 0)
+		return err;
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ymfpci_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ymfpci_capture_rec_ops);
+
+	/* global setup */
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "YMFPCI");
+	chip->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci), 64*1024, 256*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_ymfpci_capture_ac97_ops = {
+	.open =			snd_ymfpci_capture_ac97_open,
+	.close =		snd_ymfpci_capture_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_ymfpci_capture_hw_params,
+	.hw_free =		snd_ymfpci_capture_hw_free,
+	.prepare =		snd_ymfpci_capture_prepare,
+	.trigger =		snd_ymfpci_capture_trigger,
+	.pointer =		snd_ymfpci_capture_pointer,
+};
+
+int __devinit snd_ymfpci_pcm2(struct snd_ymfpci *chip, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(chip->card, "YMFPCI - PCM2", device, 0, 1, &pcm)) < 0)
+		return err;
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ymfpci_capture_ac97_ops);
+
+	/* global setup */
+	pcm->info_flags = 0;
+	sprintf(pcm->name, "YMFPCI - %s",
+		chip->device_id == PCI_DEVICE_ID_YAMAHA_754 ? "Direct Recording" : "AC'97");
+	chip->pcm2 = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci), 64*1024, 256*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_ymfpci_playback_spdif_ops = {
+	.open =			snd_ymfpci_playback_spdif_open,
+	.close =		snd_ymfpci_playback_spdif_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_ymfpci_playback_hw_params,
+	.hw_free =		snd_ymfpci_playback_hw_free,
+	.prepare =		snd_ymfpci_playback_prepare,
+	.trigger =		snd_ymfpci_playback_trigger,
+	.pointer =		snd_ymfpci_playback_pointer,
+};
+
+int __devinit snd_ymfpci_pcm_spdif(struct snd_ymfpci *chip, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(chip->card, "YMFPCI - IEC958", device, 1, 0, &pcm)) < 0)
+		return err;
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ymfpci_playback_spdif_ops);
+
+	/* global setup */
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "YMFPCI - IEC958");
+	chip->pcm_spdif = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci), 64*1024, 256*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+static struct snd_pcm_ops snd_ymfpci_playback_4ch_ops = {
+	.open =			snd_ymfpci_playback_4ch_open,
+	.close =		snd_ymfpci_playback_4ch_close,
+	.ioctl =		snd_pcm_lib_ioctl,
+	.hw_params =		snd_ymfpci_playback_hw_params,
+	.hw_free =		snd_ymfpci_playback_hw_free,
+	.prepare =		snd_ymfpci_playback_prepare,
+	.trigger =		snd_ymfpci_playback_trigger,
+	.pointer =		snd_ymfpci_playback_pointer,
+};
+
+int __devinit snd_ymfpci_pcm_4ch(struct snd_ymfpci *chip, int device, struct snd_pcm ** rpcm)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if (rpcm)
+		*rpcm = NULL;
+	if ((err = snd_pcm_new(chip->card, "YMFPCI - Rear", device, 1, 0, &pcm)) < 0)
+		return err;
+	pcm->private_data = chip;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ymfpci_playback_4ch_ops);
+
+	/* global setup */
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "YMFPCI - Rear PCM");
+	chip->pcm_4ch = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      snd_dma_pci_data(chip->pci), 64*1024, 256*1024);
+
+	if (rpcm)
+		*rpcm = pcm;
+	return 0;
+}
+
+static int snd_ymfpci_spdif_default_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_ymfpci_spdif_default_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&chip->reg_lock);
+	ucontrol->value.iec958.status[0] = (chip->spdif_bits >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (chip->spdif_bits >> 8) & 0xff;
+	ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_48000;
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_ymfpci_spdif_default_put(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+
+	val = ((ucontrol->value.iec958.status[0] & 0x3e) << 0) |
+	      (ucontrol->value.iec958.status[1] << 8);
+	spin_lock_irq(&chip->reg_lock);
+	change = chip->spdif_bits != val;
+	chip->spdif_bits = val;
+	if ((snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) & 1) && chip->pcm_spdif == NULL)
+		snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_bits);
+	spin_unlock_irq(&chip->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_ymfpci_spdif_default __devinitdata =
+{
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+	.info =		snd_ymfpci_spdif_default_info,
+	.get =		snd_ymfpci_spdif_default_get,
+	.put =		snd_ymfpci_spdif_default_put
+};
+
+static int snd_ymfpci_spdif_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_ymfpci_spdif_mask_get(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&chip->reg_lock);
+	ucontrol->value.iec958.status[0] = 0x3e;
+	ucontrol->value.iec958.status[1] = 0xff;
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_ymfpci_spdif_mask __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READ,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
+	.info =		snd_ymfpci_spdif_mask_info,
+	.get =		snd_ymfpci_spdif_mask_get,
+};
+
+static int snd_ymfpci_spdif_stream_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int snd_ymfpci_spdif_stream_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+
+	spin_lock_irq(&chip->reg_lock);
+	ucontrol->value.iec958.status[0] = (chip->spdif_pcm_bits >> 0) & 0xff;
+	ucontrol->value.iec958.status[1] = (chip->spdif_pcm_bits >> 8) & 0xff;
+	ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_48000;
+	spin_unlock_irq(&chip->reg_lock);
+	return 0;
+}
+
+static int snd_ymfpci_spdif_stream_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int val;
+	int change;
+
+	val = ((ucontrol->value.iec958.status[0] & 0x3e) << 0) |
+	      (ucontrol->value.iec958.status[1] << 8);
+	spin_lock_irq(&chip->reg_lock);
+	change = chip->spdif_pcm_bits != val;
+	chip->spdif_pcm_bits = val;
+	if ((snd_ymfpci_readw(chip, YDSXGR_SPDIFOUTCTRL) & 2))
+		snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_pcm_bits);
+	spin_unlock_irq(&chip->reg_lock);
+	return change;
+}
+
+static struct snd_kcontrol_new snd_ymfpci_spdif_stream __devinitdata =
+{
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.iface =	SNDRV_CTL_ELEM_IFACE_PCM,
+	.name =         SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM),
+	.info =		snd_ymfpci_spdif_stream_info,
+	.get =		snd_ymfpci_spdif_stream_get,
+	.put =		snd_ymfpci_spdif_stream_put
+};
+
+static int snd_ymfpci_drec_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *info)
+{
+	static char *texts[3] = {"AC'97", "IEC958", "ZV Port"};
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 3;
+	if (info->value.enumerated.item > 2)
+		info->value.enumerated.item = 2;
+	strcpy(info->value.enumerated.name, texts[info->value.enumerated.item]);
+	return 0;
+}
+
+static int snd_ymfpci_drec_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *value)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	u16 reg;
+
+	spin_lock_irq(&chip->reg_lock);
+	reg = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL);
+	spin_unlock_irq(&chip->reg_lock);
+	if (!(reg & 0x100))
+		value->value.enumerated.item[0] = 0;
+	else
+		value->value.enumerated.item[0] = 1 + ((reg & 0x200) != 0);
+	return 0;
+}
+
+static int snd_ymfpci_drec_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *value)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	u16 reg, old_reg;
+
+	spin_lock_irq(&chip->reg_lock);
+	old_reg = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL);
+	if (value->value.enumerated.item[0] == 0)
+		reg = old_reg & ~0x100;
+	else
+		reg = (old_reg & ~0x300) | 0x100 | ((value->value.enumerated.item[0] == 2) << 9);
+	snd_ymfpci_writew(chip, YDSXGR_GLOBALCTRL, reg);
+	spin_unlock_irq(&chip->reg_lock);
+	return reg != old_reg;
+}
+
+static struct snd_kcontrol_new snd_ymfpci_drec_source __devinitdata = {
+	.access =	SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Direct Recording Source",
+	.info =		snd_ymfpci_drec_source_info,
+	.get =		snd_ymfpci_drec_source_get,
+	.put =		snd_ymfpci_drec_source_put
+};
+
+/*
+ *  Mixer controls
+ */
+
+#define YMFPCI_SINGLE(xname, xindex, reg, shift) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .info = snd_ymfpci_info_single, \
+  .get = snd_ymfpci_get_single, .put = snd_ymfpci_put_single, \
+  .private_value = ((reg) | ((shift) << 16)) }
+
+#define snd_ymfpci_info_single		snd_ctl_boolean_mono_info
+
+static int snd_ymfpci_get_single(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xffff;
+	unsigned int shift = (kcontrol->private_value >> 16) & 0xff;
+	unsigned int mask = 1;
+	
+	switch (reg) {
+	case YDSXGR_SPDIFOUTCTRL: break;
+	case YDSXGR_SPDIFINCTRL: break;
+	default: return -EINVAL;
+	}
+	ucontrol->value.integer.value[0] =
+		(snd_ymfpci_readl(chip, reg) >> shift) & mask;
+	return 0;
+}
+
+static int snd_ymfpci_put_single(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xffff;
+	unsigned int shift = (kcontrol->private_value >> 16) & 0xff;
+ 	unsigned int mask = 1;
+	int change;
+	unsigned int val, oval;
+	
+	switch (reg) {
+	case YDSXGR_SPDIFOUTCTRL: break;
+	case YDSXGR_SPDIFINCTRL: break;
+	default: return -EINVAL;
+	}
+	val = (ucontrol->value.integer.value[0] & mask);
+	val <<= shift;
+	spin_lock_irq(&chip->reg_lock);
+	oval = snd_ymfpci_readl(chip, reg);
+	val = (oval & ~(mask << shift)) | val;
+	change = val != oval;
+	snd_ymfpci_writel(chip, reg, val);
+	spin_unlock_irq(&chip->reg_lock);
+	return change;
+}
+
+static const DECLARE_TLV_DB_LINEAR(db_scale_native, TLV_DB_GAIN_MUTE, 0);
+
+#define YMFPCI_DOUBLE(xname, xindex, reg) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
+  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+  .info = snd_ymfpci_info_double, \
+  .get = snd_ymfpci_get_double, .put = snd_ymfpci_put_double, \
+  .private_value = reg, \
+  .tlv = { .p = db_scale_native } }
+
+static int snd_ymfpci_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	unsigned int reg = kcontrol->private_value;
+
+	if (reg < 0x80 || reg >= 0xc0)
+		return -EINVAL;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 16383;
+	return 0;
+}
+
+static int snd_ymfpci_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = kcontrol->private_value;
+	unsigned int shift_left = 0, shift_right = 16, mask = 16383;
+	unsigned int val;
+	
+	if (reg < 0x80 || reg >= 0xc0)
+		return -EINVAL;
+	spin_lock_irq(&chip->reg_lock);
+	val = snd_ymfpci_readl(chip, reg);
+	spin_unlock_irq(&chip->reg_lock);
+	ucontrol->value.integer.value[0] = (val >> shift_left) & mask;
+	ucontrol->value.integer.value[1] = (val >> shift_right) & mask;
+	return 0;
+}
+
+static int snd_ymfpci_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = kcontrol->private_value;
+	unsigned int shift_left = 0, shift_right = 16, mask = 16383;
+	int change;
+	unsigned int val1, val2, oval;
+	
+	if (reg < 0x80 || reg >= 0xc0)
+		return -EINVAL;
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+	spin_lock_irq(&chip->reg_lock);
+	oval = snd_ymfpci_readl(chip, reg);
+	val1 = (oval & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
+	change = val1 != oval;
+	snd_ymfpci_writel(chip, reg, val1);
+	spin_unlock_irq(&chip->reg_lock);
+	return change;
+}
+
+static int snd_ymfpci_put_nativedacvol(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = YDSXGR_NATIVEDACOUTVOL;
+	unsigned int reg2 = YDSXGR_BUF441OUTVOL;
+	int change;
+	unsigned int value, oval;
+	
+	value = ucontrol->value.integer.value[0] & 0x3fff;
+	value |= (ucontrol->value.integer.value[1] & 0x3fff) << 16;
+	spin_lock_irq(&chip->reg_lock);
+	oval = snd_ymfpci_readl(chip, reg);
+	change = value != oval;
+	snd_ymfpci_writel(chip, reg, value);
+	snd_ymfpci_writel(chip, reg2, value);
+	spin_unlock_irq(&chip->reg_lock);
+	return change;
+}
+
+/*
+ * 4ch duplication
+ */
+#define snd_ymfpci_info_dup4ch		snd_ctl_boolean_mono_info
+
+static int snd_ymfpci_get_dup4ch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = chip->mode_dup4ch;
+	return 0;
+}
+
+static int snd_ymfpci_put_dup4ch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	int change;
+	change = (ucontrol->value.integer.value[0] != chip->mode_dup4ch);
+	if (change)
+		chip->mode_dup4ch = !!ucontrol->value.integer.value[0];
+	return change;
+}
+
+
+static struct snd_kcontrol_new snd_ymfpci_controls[] __devinitdata = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Wave Playback Volume",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+	.info = snd_ymfpci_info_double,
+	.get = snd_ymfpci_get_double,
+	.put = snd_ymfpci_put_nativedacvol,
+	.private_value = YDSXGR_NATIVEDACOUTVOL,
+	.tlv = { .p = db_scale_native },
+},
+YMFPCI_DOUBLE("Wave Capture Volume", 0, YDSXGR_NATIVEDACLOOPVOL),
+YMFPCI_DOUBLE("Digital Capture Volume", 0, YDSXGR_NATIVEDACINVOL),
+YMFPCI_DOUBLE("Digital Capture Volume", 1, YDSXGR_NATIVEADCINVOL),
+YMFPCI_DOUBLE("ADC Playback Volume", 0, YDSXGR_PRIADCOUTVOL),
+YMFPCI_DOUBLE("ADC Capture Volume", 0, YDSXGR_PRIADCLOOPVOL),
+YMFPCI_DOUBLE("ADC Playback Volume", 1, YDSXGR_SECADCOUTVOL),
+YMFPCI_DOUBLE("ADC Capture Volume", 1, YDSXGR_SECADCLOOPVOL),
+YMFPCI_DOUBLE("FM Legacy Volume", 0, YDSXGR_LEGACYOUTVOL),
+YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("AC97 ", PLAYBACK,VOLUME), 0, YDSXGR_ZVOUTVOL),
+YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("", CAPTURE,VOLUME), 0, YDSXGR_ZVLOOPVOL),
+YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("AC97 ",PLAYBACK,VOLUME), 1, YDSXGR_SPDIFOUTVOL),
+YMFPCI_DOUBLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,VOLUME), 1, YDSXGR_SPDIFLOOPVOL),
+YMFPCI_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), 0, YDSXGR_SPDIFOUTCTRL, 0),
+YMFPCI_SINGLE(SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH), 0, YDSXGR_SPDIFINCTRL, 0),
+YMFPCI_SINGLE(SNDRV_CTL_NAME_IEC958("Loop",NONE,NONE), 0, YDSXGR_SPDIFINCTRL, 4),
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "4ch Duplication",
+	.info = snd_ymfpci_info_dup4ch,
+	.get = snd_ymfpci_get_dup4ch,
+	.put = snd_ymfpci_put_dup4ch,
+},
+};
+
+
+/*
+ * GPIO
+ */
+
+static int snd_ymfpci_get_gpio_out(struct snd_ymfpci *chip, int pin)
+{
+	u16 reg, mode;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	reg = snd_ymfpci_readw(chip, YDSXGR_GPIOFUNCENABLE);
+	reg &= ~(1 << (pin + 8));
+	reg |= (1 << pin);
+	snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg);
+	/* set the level mode for input line */
+	mode = snd_ymfpci_readw(chip, YDSXGR_GPIOTYPECONFIG);
+	mode &= ~(3 << (pin * 2));
+	snd_ymfpci_writew(chip, YDSXGR_GPIOTYPECONFIG, mode);
+	snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg | (1 << (pin + 8)));
+	mode = snd_ymfpci_readw(chip, YDSXGR_GPIOINSTATUS);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return (mode >> pin) & 1;
+}
+
+static int snd_ymfpci_set_gpio_out(struct snd_ymfpci *chip, int pin, int enable)
+{
+	u16 reg;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	reg = snd_ymfpci_readw(chip, YDSXGR_GPIOFUNCENABLE);
+	reg &= ~(1 << pin);
+	reg &= ~(1 << (pin + 8));
+	snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg);
+	snd_ymfpci_writew(chip, YDSXGR_GPIOOUTCTRL, enable << pin);
+	snd_ymfpci_writew(chip, YDSXGR_GPIOFUNCENABLE, reg | (1 << (pin + 8)));
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	return 0;
+}
+
+#define snd_ymfpci_gpio_sw_info		snd_ctl_boolean_mono_info
+
+static int snd_ymfpci_gpio_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	int pin = (int)kcontrol->private_value;
+	ucontrol->value.integer.value[0] = snd_ymfpci_get_gpio_out(chip, pin);
+	return 0;
+}
+
+static int snd_ymfpci_gpio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	int pin = (int)kcontrol->private_value;
+
+	if (snd_ymfpci_get_gpio_out(chip, pin) != ucontrol->value.integer.value[0]) {
+		snd_ymfpci_set_gpio_out(chip, pin, !!ucontrol->value.integer.value[0]);
+		ucontrol->value.integer.value[0] = snd_ymfpci_get_gpio_out(chip, pin);
+		return 1;
+	}
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_ymfpci_rear_shared __devinitdata = {
+	.name = "Shared Rear/Line-In Switch",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info = snd_ymfpci_gpio_sw_info,
+	.get = snd_ymfpci_gpio_sw_get,
+	.put = snd_ymfpci_gpio_sw_put,
+	.private_value = 2,
+};
+
+/*
+ * PCM voice volume
+ */
+
+static int snd_ymfpci_pcm_vol_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0x8000;
+	return 0;
+}
+
+static int snd_ymfpci_pcm_vol_get(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int subs = kcontrol->id.subdevice;
+
+	ucontrol->value.integer.value[0] = chip->pcm_mixer[subs].left;
+	ucontrol->value.integer.value[1] = chip->pcm_mixer[subs].right;
+	return 0;
+}
+
+static int snd_ymfpci_pcm_vol_put(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ymfpci *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int subs = kcontrol->id.subdevice;
+	struct snd_pcm_substream *substream;
+	unsigned long flags;
+
+	if (ucontrol->value.integer.value[0] != chip->pcm_mixer[subs].left ||
+	    ucontrol->value.integer.value[1] != chip->pcm_mixer[subs].right) {
+		chip->pcm_mixer[subs].left = ucontrol->value.integer.value[0];
+		chip->pcm_mixer[subs].right = ucontrol->value.integer.value[1];
+		if (chip->pcm_mixer[subs].left > 0x8000)
+			chip->pcm_mixer[subs].left = 0x8000;
+		if (chip->pcm_mixer[subs].right > 0x8000)
+			chip->pcm_mixer[subs].right = 0x8000;
+
+		substream = (struct snd_pcm_substream *)kcontrol->private_value;
+		spin_lock_irqsave(&chip->voice_lock, flags);
+		if (substream->runtime && substream->runtime->private_data) {
+			struct snd_ymfpci_pcm *ypcm = substream->runtime->private_data;
+			if (!ypcm->use_441_slot)
+				ypcm->update_pcm_vol = 2;
+		}
+		spin_unlock_irqrestore(&chip->voice_lock, flags);
+		return 1;
+	}
+	return 0;
+}
+
+static struct snd_kcontrol_new snd_ymfpci_pcm_volume __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name = "PCM Playback Volume",
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		SNDRV_CTL_ELEM_ACCESS_INACTIVE,
+	.info = snd_ymfpci_pcm_vol_info,
+	.get = snd_ymfpci_pcm_vol_get,
+	.put = snd_ymfpci_pcm_vol_put,
+};
+
+
+/*
+ *  Mixer routines
+ */
+
+static void snd_ymfpci_mixer_free_ac97_bus(struct snd_ac97_bus *bus)
+{
+	struct snd_ymfpci *chip = bus->private_data;
+	chip->ac97_bus = NULL;
+}
+
+static void snd_ymfpci_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+	struct snd_ymfpci *chip = ac97->private_data;
+	chip->ac97 = NULL;
+}
+
+int __devinit snd_ymfpci_mixer(struct snd_ymfpci *chip, int rear_switch)
+{
+	struct snd_ac97_template ac97;
+	struct snd_kcontrol *kctl;
+	struct snd_pcm_substream *substream;
+	unsigned int idx;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_ymfpci_codec_write,
+		.read = snd_ymfpci_codec_read,
+	};
+
+	if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus)) < 0)
+		return err;
+	chip->ac97_bus->private_free = snd_ymfpci_mixer_free_ac97_bus;
+	chip->ac97_bus->no_vra = 1; /* YMFPCI doesn't need VRA */
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.private_data = chip;
+	ac97.private_free = snd_ymfpci_mixer_free_ac97;
+	if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97)) < 0)
+		return err;
+
+	/* to be sure */
+	snd_ac97_update_bits(chip->ac97, AC97_EXTENDED_STATUS,
+			     AC97_EA_VRA|AC97_EA_VRM, 0);
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_ymfpci_controls); idx++) {
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_ymfpci_controls[idx], chip))) < 0)
+			return err;
+	}
+
+	/* add S/PDIF control */
+	if (snd_BUG_ON(!chip->pcm_spdif))
+		return -ENXIO;
+	if ((err = snd_ctl_add(chip->card, kctl = snd_ctl_new1(&snd_ymfpci_spdif_default, chip))) < 0)
+		return err;
+	kctl->id.device = chip->pcm_spdif->device;
+	if ((err = snd_ctl_add(chip->card, kctl = snd_ctl_new1(&snd_ymfpci_spdif_mask, chip))) < 0)
+		return err;
+	kctl->id.device = chip->pcm_spdif->device;
+	if ((err = snd_ctl_add(chip->card, kctl = snd_ctl_new1(&snd_ymfpci_spdif_stream, chip))) < 0)
+		return err;
+	kctl->id.device = chip->pcm_spdif->device;
+	chip->spdif_pcm_ctl = kctl;
+
+	/* direct recording source */
+	if (chip->device_id == PCI_DEVICE_ID_YAMAHA_754 &&
+	    (err = snd_ctl_add(chip->card, kctl = snd_ctl_new1(&snd_ymfpci_drec_source, chip))) < 0)
+		return err;
+
+	/*
+	 * shared rear/line-in
+	 */
+	if (rear_switch) {
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_ymfpci_rear_shared, chip))) < 0)
+			return err;
+	}
+
+	/* per-voice volume */
+	substream = chip->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+	for (idx = 0; idx < 32; ++idx) {
+		kctl = snd_ctl_new1(&snd_ymfpci_pcm_volume, chip);
+		if (!kctl)
+			return -ENOMEM;
+		kctl->id.device = chip->pcm->device;
+		kctl->id.subdevice = idx;
+		kctl->private_value = (unsigned long)substream;
+		if ((err = snd_ctl_add(chip->card, kctl)) < 0)
+			return err;
+		chip->pcm_mixer[idx].left = 0x8000;
+		chip->pcm_mixer[idx].right = 0x8000;
+		chip->pcm_mixer[idx].ctl = kctl;
+		substream = substream->next;
+	}
+
+	return 0;
+}
+
+
+/*
+ * timer
+ */
+
+static int snd_ymfpci_timer_start(struct snd_timer *timer)
+{
+	struct snd_ymfpci *chip;
+	unsigned long flags;
+	unsigned int count;
+
+	chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (timer->sticks > 1) {
+		chip->timer_ticks = timer->sticks;
+		count = timer->sticks - 1;
+	} else {
+		/*
+		 * Divisor 1 is not allowed; fake it by using divisor 2 and
+		 * counting two ticks for each interrupt.
+		 */
+		chip->timer_ticks = 2;
+		count = 2 - 1;
+	}
+	snd_ymfpci_writew(chip, YDSXGR_TIMERCOUNT, count);
+	snd_ymfpci_writeb(chip, YDSXGR_TIMERCTRL, 0x03);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_ymfpci_timer_stop(struct snd_timer *timer)
+{
+	struct snd_ymfpci *chip;
+	unsigned long flags;
+
+	chip = snd_timer_chip(timer);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_ymfpci_writeb(chip, YDSXGR_TIMERCTRL, 0x00);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+static int snd_ymfpci_timer_precise_resolution(struct snd_timer *timer,
+					       unsigned long *num, unsigned long *den)
+{
+	*num = 1;
+	*den = 96000;
+	return 0;
+}
+
+static struct snd_timer_hardware snd_ymfpci_timer_hw = {
+	.flags = SNDRV_TIMER_HW_AUTO,
+	.resolution = 10417, /* 1 / 96 kHz = 10.41666...us */
+	.ticks = 0x10000,
+	.start = snd_ymfpci_timer_start,
+	.stop = snd_ymfpci_timer_stop,
+	.precise_resolution = snd_ymfpci_timer_precise_resolution,
+};
+
+int __devinit snd_ymfpci_timer(struct snd_ymfpci *chip, int device)
+{
+	struct snd_timer *timer = NULL;
+	struct snd_timer_id tid;
+	int err;
+
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = chip->card->number;
+	tid.device = device;
+	tid.subdevice = 0;
+	if ((err = snd_timer_new(chip->card, "YMFPCI", &tid, &timer)) >= 0) {
+		strcpy(timer->name, "YMFPCI timer");
+		timer->private_data = chip;
+		timer->hw = snd_ymfpci_timer_hw;
+	}
+	chip->timer = timer;
+	return err;
+}
+
+
+/*
+ *  proc interface
+ */
+
+static void snd_ymfpci_proc_read(struct snd_info_entry *entry, 
+				 struct snd_info_buffer *buffer)
+{
+	struct snd_ymfpci *chip = entry->private_data;
+	int i;
+	
+	snd_iprintf(buffer, "YMFPCI\n\n");
+	for (i = 0; i <= YDSXGR_WORKBASE; i += 4)
+		snd_iprintf(buffer, "%04x: %04x\n", i, snd_ymfpci_readl(chip, i));
+}
+
+static int __devinit snd_ymfpci_proc_init(struct snd_card *card, struct snd_ymfpci *chip)
+{
+	struct snd_info_entry *entry;
+	
+	if (! snd_card_proc_new(card, "ymfpci", &entry))
+		snd_info_set_text_ops(entry, chip, snd_ymfpci_proc_read);
+	return 0;
+}
+
+/*
+ *  initialization routines
+ */
+
+static void snd_ymfpci_aclink_reset(struct pci_dev * pci)
+{
+	u8 cmd;
+
+	pci_read_config_byte(pci, PCIR_DSXG_CTRL, &cmd);
+#if 0 // force to reset
+	if (cmd & 0x03) {
+#endif
+		pci_write_config_byte(pci, PCIR_DSXG_CTRL, cmd & 0xfc);
+		pci_write_config_byte(pci, PCIR_DSXG_CTRL, cmd | 0x03);
+		pci_write_config_byte(pci, PCIR_DSXG_CTRL, cmd & 0xfc);
+		pci_write_config_word(pci, PCIR_DSXG_PWRCTRL1, 0);
+		pci_write_config_word(pci, PCIR_DSXG_PWRCTRL2, 0);
+#if 0
+	}
+#endif
+}
+
+static void snd_ymfpci_enable_dsp(struct snd_ymfpci *chip)
+{
+	snd_ymfpci_writel(chip, YDSXGR_CONFIG, 0x00000001);
+}
+
+static void snd_ymfpci_disable_dsp(struct snd_ymfpci *chip)
+{
+	u32 val;
+	int timeout = 1000;
+
+	val = snd_ymfpci_readl(chip, YDSXGR_CONFIG);
+	if (val)
+		snd_ymfpci_writel(chip, YDSXGR_CONFIG, 0x00000000);
+	while (timeout-- > 0) {
+		val = snd_ymfpci_readl(chip, YDSXGR_STATUS);
+		if ((val & 0x00000002) == 0)
+			break;
+	}
+}
+
+static int snd_ymfpci_request_firmware(struct snd_ymfpci *chip)
+{
+	int err, is_1e;
+	const char *name;
+
+	err = request_firmware(&chip->dsp_microcode, "yamaha/ds1_dsp.fw",
+			       &chip->pci->dev);
+	if (err >= 0) {
+		if (chip->dsp_microcode->size != YDSXG_DSPLENGTH) {
+			snd_printk(KERN_ERR "DSP microcode has wrong size\n");
+			err = -EINVAL;
+		}
+	}
+	if (err < 0)
+		return err;
+	is_1e = chip->device_id == PCI_DEVICE_ID_YAMAHA_724F ||
+		chip->device_id == PCI_DEVICE_ID_YAMAHA_740C ||
+		chip->device_id == PCI_DEVICE_ID_YAMAHA_744 ||
+		chip->device_id == PCI_DEVICE_ID_YAMAHA_754;
+	name = is_1e ? "yamaha/ds1e_ctrl.fw" : "yamaha/ds1_ctrl.fw";
+	err = request_firmware(&chip->controller_microcode, name,
+			       &chip->pci->dev);
+	if (err >= 0) {
+		if (chip->controller_microcode->size != YDSXG_CTRLLENGTH) {
+			snd_printk(KERN_ERR "controller microcode"
+				   " has wrong size\n");
+			err = -EINVAL;
+		}
+	}
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+MODULE_FIRMWARE("yamaha/ds1_dsp.fw");
+MODULE_FIRMWARE("yamaha/ds1_ctrl.fw");
+MODULE_FIRMWARE("yamaha/ds1e_ctrl.fw");
+
+static void snd_ymfpci_download_image(struct snd_ymfpci *chip)
+{
+	int i;
+	u16 ctrl;
+	const __le32 *inst;
+
+	snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0x00000000);
+	snd_ymfpci_disable_dsp(chip);
+	snd_ymfpci_writel(chip, YDSXGR_MODE, 0x00010000);
+	snd_ymfpci_writel(chip, YDSXGR_MODE, 0x00000000);
+	snd_ymfpci_writel(chip, YDSXGR_MAPOFREC, 0x00000000);
+	snd_ymfpci_writel(chip, YDSXGR_MAPOFEFFECT, 0x00000000);
+	snd_ymfpci_writel(chip, YDSXGR_PLAYCTRLBASE, 0x00000000);
+	snd_ymfpci_writel(chip, YDSXGR_RECCTRLBASE, 0x00000000);
+	snd_ymfpci_writel(chip, YDSXGR_EFFCTRLBASE, 0x00000000);
+	ctrl = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL);
+	snd_ymfpci_writew(chip, YDSXGR_GLOBALCTRL, ctrl & ~0x0007);
+
+	/* setup DSP instruction code */
+	inst = (const __le32 *)chip->dsp_microcode->data;
+	for (i = 0; i < YDSXG_DSPLENGTH / 4; i++)
+		snd_ymfpci_writel(chip, YDSXGR_DSPINSTRAM + (i << 2),
+				  le32_to_cpu(inst[i]));
+
+	/* setup control instruction code */
+	inst = (const __le32 *)chip->controller_microcode->data;
+	for (i = 0; i < YDSXG_CTRLLENGTH / 4; i++)
+		snd_ymfpci_writel(chip, YDSXGR_CTRLINSTRAM + (i << 2),
+				  le32_to_cpu(inst[i]));
+
+	snd_ymfpci_enable_dsp(chip);
+}
+
+static int __devinit snd_ymfpci_memalloc(struct snd_ymfpci *chip)
+{
+	long size, playback_ctrl_size;
+	int voice, bank, reg;
+	u8 *ptr;
+	dma_addr_t ptr_addr;
+
+	playback_ctrl_size = 4 + 4 * YDSXG_PLAYBACK_VOICES;
+	chip->bank_size_playback = snd_ymfpci_readl(chip, YDSXGR_PLAYCTRLSIZE) << 2;
+	chip->bank_size_capture = snd_ymfpci_readl(chip, YDSXGR_RECCTRLSIZE) << 2;
+	chip->bank_size_effect = snd_ymfpci_readl(chip, YDSXGR_EFFCTRLSIZE) << 2;
+	chip->work_size = YDSXG_DEFAULT_WORK_SIZE;
+	
+	size = ALIGN(playback_ctrl_size, 0x100) +
+	       ALIGN(chip->bank_size_playback * 2 * YDSXG_PLAYBACK_VOICES, 0x100) +
+	       ALIGN(chip->bank_size_capture * 2 * YDSXG_CAPTURE_VOICES, 0x100) +
+	       ALIGN(chip->bank_size_effect * 2 * YDSXG_EFFECT_VOICES, 0x100) +
+	       chip->work_size;
+	/* work_ptr must be aligned to 256 bytes, but it's already
+	   covered with the kernel page allocation mechanism */
+	if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+				size, &chip->work_ptr) < 0) 
+		return -ENOMEM;
+	ptr = chip->work_ptr.area;
+	ptr_addr = chip->work_ptr.addr;
+	memset(ptr, 0, size);	/* for sure */
+
+	chip->bank_base_playback = ptr;
+	chip->bank_base_playback_addr = ptr_addr;
+	chip->ctrl_playback = (u32 *)ptr;
+	chip->ctrl_playback[0] = cpu_to_le32(YDSXG_PLAYBACK_VOICES);
+	ptr += ALIGN(playback_ctrl_size, 0x100);
+	ptr_addr += ALIGN(playback_ctrl_size, 0x100);
+	for (voice = 0; voice < YDSXG_PLAYBACK_VOICES; voice++) {
+		chip->voices[voice].number = voice;
+		chip->voices[voice].bank = (struct snd_ymfpci_playback_bank *)ptr;
+		chip->voices[voice].bank_addr = ptr_addr;
+		for (bank = 0; bank < 2; bank++) {
+			chip->bank_playback[voice][bank] = (struct snd_ymfpci_playback_bank *)ptr;
+			ptr += chip->bank_size_playback;
+			ptr_addr += chip->bank_size_playback;
+		}
+	}
+	ptr = (char *)ALIGN((unsigned long)ptr, 0x100);
+	ptr_addr = ALIGN(ptr_addr, 0x100);
+	chip->bank_base_capture = ptr;
+	chip->bank_base_capture_addr = ptr_addr;
+	for (voice = 0; voice < YDSXG_CAPTURE_VOICES; voice++)
+		for (bank = 0; bank < 2; bank++) {
+			chip->bank_capture[voice][bank] = (struct snd_ymfpci_capture_bank *)ptr;
+			ptr += chip->bank_size_capture;
+			ptr_addr += chip->bank_size_capture;
+		}
+	ptr = (char *)ALIGN((unsigned long)ptr, 0x100);
+	ptr_addr = ALIGN(ptr_addr, 0x100);
+	chip->bank_base_effect = ptr;
+	chip->bank_base_effect_addr = ptr_addr;
+	for (voice = 0; voice < YDSXG_EFFECT_VOICES; voice++)
+		for (bank = 0; bank < 2; bank++) {
+			chip->bank_effect[voice][bank] = (struct snd_ymfpci_effect_bank *)ptr;
+			ptr += chip->bank_size_effect;
+			ptr_addr += chip->bank_size_effect;
+		}
+	ptr = (char *)ALIGN((unsigned long)ptr, 0x100);
+	ptr_addr = ALIGN(ptr_addr, 0x100);
+	chip->work_base = ptr;
+	chip->work_base_addr = ptr_addr;
+	
+	snd_BUG_ON(ptr + chip->work_size !=
+		   chip->work_ptr.area + chip->work_ptr.bytes);
+
+	snd_ymfpci_writel(chip, YDSXGR_PLAYCTRLBASE, chip->bank_base_playback_addr);
+	snd_ymfpci_writel(chip, YDSXGR_RECCTRLBASE, chip->bank_base_capture_addr);
+	snd_ymfpci_writel(chip, YDSXGR_EFFCTRLBASE, chip->bank_base_effect_addr);
+	snd_ymfpci_writel(chip, YDSXGR_WORKBASE, chip->work_base_addr);
+	snd_ymfpci_writel(chip, YDSXGR_WORKSIZE, chip->work_size >> 2);
+
+	/* S/PDIF output initialization */
+	chip->spdif_bits = chip->spdif_pcm_bits = SNDRV_PCM_DEFAULT_CON_SPDIF & 0xffff;
+	snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTCTRL, 0);
+	snd_ymfpci_writew(chip, YDSXGR_SPDIFOUTSTATUS, chip->spdif_bits);
+
+	/* S/PDIF input initialization */
+	snd_ymfpci_writew(chip, YDSXGR_SPDIFINCTRL, 0);
+
+	/* digital mixer setup */
+	for (reg = 0x80; reg < 0xc0; reg += 4)
+		snd_ymfpci_writel(chip, reg, 0);
+	snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0x3fff3fff);
+	snd_ymfpci_writel(chip, YDSXGR_BUF441OUTVOL, 0x3fff3fff);
+	snd_ymfpci_writel(chip, YDSXGR_ZVOUTVOL, 0x3fff3fff);
+	snd_ymfpci_writel(chip, YDSXGR_SPDIFOUTVOL, 0x3fff3fff);
+	snd_ymfpci_writel(chip, YDSXGR_NATIVEADCINVOL, 0x3fff3fff);
+	snd_ymfpci_writel(chip, YDSXGR_NATIVEDACINVOL, 0x3fff3fff);
+	snd_ymfpci_writel(chip, YDSXGR_PRIADCLOOPVOL, 0x3fff3fff);
+	snd_ymfpci_writel(chip, YDSXGR_LEGACYOUTVOL, 0x3fff3fff);
+	
+	return 0;
+}
+
+static int snd_ymfpci_free(struct snd_ymfpci *chip)
+{
+	u16 ctrl;
+
+	if (snd_BUG_ON(!chip))
+		return -EINVAL;
+
+	if (chip->res_reg_area) {	/* don't touch busy hardware */
+		snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0);
+		snd_ymfpci_writel(chip, YDSXGR_BUF441OUTVOL, 0);
+		snd_ymfpci_writel(chip, YDSXGR_LEGACYOUTVOL, 0);
+		snd_ymfpci_writel(chip, YDSXGR_STATUS, ~0);
+		snd_ymfpci_disable_dsp(chip);
+		snd_ymfpci_writel(chip, YDSXGR_PLAYCTRLBASE, 0);
+		snd_ymfpci_writel(chip, YDSXGR_RECCTRLBASE, 0);
+		snd_ymfpci_writel(chip, YDSXGR_EFFCTRLBASE, 0);
+		snd_ymfpci_writel(chip, YDSXGR_WORKBASE, 0);
+		snd_ymfpci_writel(chip, YDSXGR_WORKSIZE, 0);
+		ctrl = snd_ymfpci_readw(chip, YDSXGR_GLOBALCTRL);
+		snd_ymfpci_writew(chip, YDSXGR_GLOBALCTRL, ctrl & ~0x0007);
+	}
+
+	snd_ymfpci_ac3_done(chip);
+
+	/* Set PCI device to D3 state */
+#if 0
+	/* FIXME: temporarily disabled, otherwise we cannot fire up
+	 * the chip again unless reboot.  ACPI bug?
+	 */
+	pci_set_power_state(chip->pci, 3);
+#endif
+
+#ifdef CONFIG_PM
+	vfree(chip->saved_regs);
+#endif
+	if (chip->irq >= 0)
+		free_irq(chip->irq, chip);
+	release_and_free_resource(chip->mpu_res);
+	release_and_free_resource(chip->fm_res);
+	snd_ymfpci_free_gameport(chip);
+	if (chip->reg_area_virt)
+		iounmap(chip->reg_area_virt);
+	if (chip->work_ptr.area)
+		snd_dma_free_pages(&chip->work_ptr);
+	
+	release_and_free_resource(chip->res_reg_area);
+
+	pci_write_config_word(chip->pci, 0x40, chip->old_legacy_ctrl);
+	
+	pci_disable_device(chip->pci);
+	release_firmware(chip->dsp_microcode);
+	release_firmware(chip->controller_microcode);
+	kfree(chip);
+	return 0;
+}
+
+static int snd_ymfpci_dev_free(struct snd_device *device)
+{
+	struct snd_ymfpci *chip = device->device_data;
+	return snd_ymfpci_free(chip);
+}
+
+#ifdef CONFIG_PM
+static int saved_regs_index[] = {
+	/* spdif */
+	YDSXGR_SPDIFOUTCTRL,
+	YDSXGR_SPDIFOUTSTATUS,
+	YDSXGR_SPDIFINCTRL,
+	/* volumes */
+	YDSXGR_PRIADCLOOPVOL,
+	YDSXGR_NATIVEDACINVOL,
+	YDSXGR_NATIVEDACOUTVOL,
+	YDSXGR_BUF441OUTVOL,
+	YDSXGR_NATIVEADCINVOL,
+	YDSXGR_SPDIFLOOPVOL,
+	YDSXGR_SPDIFOUTVOL,
+	YDSXGR_ZVOUTVOL,
+	YDSXGR_LEGACYOUTVOL,
+	/* address bases */
+	YDSXGR_PLAYCTRLBASE,
+	YDSXGR_RECCTRLBASE,
+	YDSXGR_EFFCTRLBASE,
+	YDSXGR_WORKBASE,
+	/* capture set up */
+	YDSXGR_MAPOFREC,
+	YDSXGR_RECFORMAT,
+	YDSXGR_RECSLOTSR,
+	YDSXGR_ADCFORMAT,
+	YDSXGR_ADCSLOTSR,
+};
+#define YDSXGR_NUM_SAVED_REGS	ARRAY_SIZE(saved_regs_index)
+
+int snd_ymfpci_suspend(struct pci_dev *pci, pm_message_t state)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_ymfpci *chip = card->private_data;
+	unsigned int i;
+	
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	snd_pcm_suspend_all(chip->pcm2);
+	snd_pcm_suspend_all(chip->pcm_spdif);
+	snd_pcm_suspend_all(chip->pcm_4ch);
+	snd_ac97_suspend(chip->ac97);
+	for (i = 0; i < YDSXGR_NUM_SAVED_REGS; i++)
+		chip->saved_regs[i] = snd_ymfpci_readl(chip, saved_regs_index[i]);
+	chip->saved_ydsxgr_mode = snd_ymfpci_readl(chip, YDSXGR_MODE);
+	snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0);
+	snd_ymfpci_writel(chip, YDSXGR_BUF441OUTVOL, 0);
+	snd_ymfpci_disable_dsp(chip);
+	pci_disable_device(pci);
+	pci_save_state(pci);
+	pci_set_power_state(pci, pci_choose_state(pci, state));
+	return 0;
+}
+
+int snd_ymfpci_resume(struct pci_dev *pci)
+{
+	struct snd_card *card = pci_get_drvdata(pci);
+	struct snd_ymfpci *chip = card->private_data;
+	unsigned int i;
+
+	pci_set_power_state(pci, PCI_D0);
+	pci_restore_state(pci);
+	if (pci_enable_device(pci) < 0) {
+		printk(KERN_ERR "ymfpci: pci_enable_device failed, "
+		       "disabling device\n");
+		snd_card_disconnect(card);
+		return -EIO;
+	}
+	pci_set_master(pci);
+	snd_ymfpci_aclink_reset(pci);
+	snd_ymfpci_codec_ready(chip, 0);
+	snd_ymfpci_download_image(chip);
+	udelay(100);
+
+	for (i = 0; i < YDSXGR_NUM_SAVED_REGS; i++)
+		snd_ymfpci_writel(chip, saved_regs_index[i], chip->saved_regs[i]);
+
+	snd_ac97_resume(chip->ac97);
+
+	/* start hw again */
+	if (chip->start_count > 0) {
+		spin_lock_irq(&chip->reg_lock);
+		snd_ymfpci_writel(chip, YDSXGR_MODE, chip->saved_ydsxgr_mode);
+		chip->active_bank = snd_ymfpci_readl(chip, YDSXGR_CTRLSELECT);
+		spin_unlock_irq(&chip->reg_lock);
+	}
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+int __devinit snd_ymfpci_create(struct snd_card *card,
+				struct pci_dev * pci,
+				unsigned short old_legacy_ctrl,
+				struct snd_ymfpci ** rchip)
+{
+	struct snd_ymfpci *chip;
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_ymfpci_dev_free,
+	};
+	
+	*rchip = NULL;
+
+	/* enable PCI device */
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+	chip->old_legacy_ctrl = old_legacy_ctrl;
+	spin_lock_init(&chip->reg_lock);
+	spin_lock_init(&chip->voice_lock);
+	init_waitqueue_head(&chip->interrupt_sleep);
+	atomic_set(&chip->interrupt_sleep_count, 0);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+	chip->device_id = pci->device;
+	chip->rev = pci->revision;
+	chip->reg_area_phys = pci_resource_start(pci, 0);
+	chip->reg_area_virt = ioremap_nocache(chip->reg_area_phys, 0x8000);
+	pci_set_master(pci);
+	chip->src441_used = -1;
+
+	if ((chip->res_reg_area = request_mem_region(chip->reg_area_phys, 0x8000, "YMFPCI")) == NULL) {
+		snd_printk(KERN_ERR "unable to grab memory region 0x%lx-0x%lx\n", chip->reg_area_phys, chip->reg_area_phys + 0x8000 - 1);
+		snd_ymfpci_free(chip);
+		return -EBUSY;
+	}
+	if (request_irq(pci->irq, snd_ymfpci_interrupt, IRQF_SHARED,
+			"YMFPCI", chip)) {
+		snd_printk(KERN_ERR "unable to grab IRQ %d\n", pci->irq);
+		snd_ymfpci_free(chip);
+		return -EBUSY;
+	}
+	chip->irq = pci->irq;
+
+	snd_ymfpci_aclink_reset(pci);
+	if (snd_ymfpci_codec_ready(chip, 0) < 0) {
+		snd_ymfpci_free(chip);
+		return -EIO;
+	}
+
+	err = snd_ymfpci_request_firmware(chip);
+	if (err < 0) {
+		snd_printk(KERN_ERR "firmware request failed: %d\n", err);
+		snd_ymfpci_free(chip);
+		return err;
+	}
+	snd_ymfpci_download_image(chip);
+
+	udelay(100); /* seems we need a delay after downloading image.. */
+
+	if (snd_ymfpci_memalloc(chip) < 0) {
+		snd_ymfpci_free(chip);
+		return -EIO;
+	}
+
+	if ((err = snd_ymfpci_ac3_init(chip)) < 0) {
+		snd_ymfpci_free(chip);
+		return err;
+	}
+
+#ifdef CONFIG_PM
+	chip->saved_regs = vmalloc(YDSXGR_NUM_SAVED_REGS * sizeof(u32));
+	if (chip->saved_regs == NULL) {
+		snd_ymfpci_free(chip);
+		return -ENOMEM;
+	}
+#endif
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_ymfpci_free(chip);
+		return err;
+	}
+
+	snd_ymfpci_proc_init(card, chip);
+
+	snd_card_set_dev(card, &pci->dev);
+
+	*rchip = chip;
+	return 0;
+}
diff --git a/linux/sound/pcmcia/Kconfig b/linux/sound/pcmcia/Kconfig
new file mode 100644
index 0000000..7fbb190
--- /dev/null
+++ b/linux/sound/pcmcia/Kconfig
@@ -0,0 +1,33 @@
+# ALSA PCMCIA drivers
+
+menuconfig SND_PCMCIA
+	bool "PCMCIA sound devices"
+	depends on PCMCIA
+	default y
+	help
+	  Support for sound devices connected via the PCMCIA bus.
+
+if SND_PCMCIA && PCMCIA
+
+config SND_VXPOCKET
+	tristate "Digigram VXpocket"
+	select SND_VX_LIB
+	help
+	  Say Y here to include support for Digigram VXpocket and
+	  VXpocket 440 soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-vxpocket.
+
+config SND_PDAUDIOCF
+	tristate "Sound Core PDAudioCF"
+	select SND_PCM
+	help
+	  Say Y here to include support for Sound Core PDAudioCF
+	  soundcards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-pdaudiocf.
+
+endif	# SND_PCMCIA
+
diff --git a/linux/sound/pcmcia/Makefile b/linux/sound/pcmcia/Makefile
new file mode 100644
index 0000000..beef2e3
--- /dev/null
+++ b/linux/sound/pcmcia/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+obj-$(CONFIG_SND) += vx/ pdaudiocf/
diff --git a/linux/sound/pcmcia/pdaudiocf/Makefile b/linux/sound/pcmcia/pdaudiocf/Makefile
new file mode 100644
index 0000000..e892d72
--- /dev/null
+++ b/linux/sound/pcmcia/pdaudiocf/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2004 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-pdaudiocf-objs := pdaudiocf.o pdaudiocf_core.o pdaudiocf_irq.o pdaudiocf_pcm.o
+
+obj-$(CONFIG_SND_PDAUDIOCF) += snd-pdaudiocf.o
diff --git a/linux/sound/pcmcia/pdaudiocf/pdaudiocf.c b/linux/sound/pcmcia/pdaudiocf/pdaudiocf.c
new file mode 100644
index 0000000..8cc4733
--- /dev/null
+++ b/linux/sound/pcmcia/pdaudiocf/pdaudiocf.c
@@ -0,0 +1,312 @@
+/*
+ * Driver for Sound Core PDAudioCF soundcard
+ *
+ * Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/core.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <pcmcia/ciscode.h>
+#include <pcmcia/cisreg.h>
+#include "pdaudiocf.h"
+#include <sound/initval.h>
+#include <linux/init.h>
+
+/*
+ */
+
+#define CARD_NAME	"PDAudio-CF"
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Sound Core " CARD_NAME);
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Sound Core," CARD_NAME "}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable switches */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+
+/*
+ */
+
+static struct snd_card *card_list[SNDRV_CARDS];
+
+/*
+ * prototypes
+ */
+static int pdacf_config(struct pcmcia_device *link);
+static void snd_pdacf_detach(struct pcmcia_device *p_dev);
+
+static void pdacf_release(struct pcmcia_device *link)
+{
+	pcmcia_disable_device(link);
+}
+
+/*
+ * destructor
+ */
+static int snd_pdacf_free(struct snd_pdacf *pdacf)
+{
+	struct pcmcia_device *link = pdacf->p_dev;
+
+	pdacf_release(link);
+
+	card_list[pdacf->index] = NULL;
+	pdacf->card = NULL;
+
+	kfree(pdacf);
+	return 0;
+}
+
+static int snd_pdacf_dev_free(struct snd_device *device)
+{
+	struct snd_pdacf *chip = device->device_data;
+	return snd_pdacf_free(chip);
+}
+
+/*
+ * snd_pdacf_attach - attach callback for cs
+ */
+static int snd_pdacf_probe(struct pcmcia_device *link)
+{
+	int i, err;
+	struct snd_pdacf *pdacf;
+	struct snd_card *card;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_pdacf_dev_free,
+	};
+
+	snd_printdd(KERN_DEBUG "pdacf_attach called\n");
+	/* find an empty slot from the card list */
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (! card_list[i])
+			break;
+	}
+	if (i >= SNDRV_CARDS) {
+		snd_printk(KERN_ERR "pdacf: too many cards found\n");
+		return -EINVAL;
+	}
+	if (! enable[i])
+		return -ENODEV; /* disabled explicitly */
+
+	/* ok, create a card instance */
+	err = snd_card_create(index[i], id[i], THIS_MODULE, 0, &card);
+	if (err < 0) {
+		snd_printk(KERN_ERR "pdacf: cannot create a card instance\n");
+		return err;
+	}
+
+	pdacf = snd_pdacf_create(card);
+	if (!pdacf) {
+		snd_card_free(card);
+		return -ENOMEM;
+	}
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pdacf, &ops);
+	if (err < 0) {
+		kfree(pdacf);
+		snd_card_free(card);
+		return err;
+	}
+
+	snd_card_set_dev(card, &link->dev);
+
+	pdacf->index = i;
+	card_list[i] = card;
+
+	pdacf->p_dev = link;
+	link->priv = pdacf;
+
+	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
+	link->resource[0]->end = 16;
+
+	link->config_flags = CONF_ENABLE_IRQ | CONF_ENABLE_PULSE_IRQ;
+	link->config_index = 1;
+	link->config_regs = PRESENT_OPTION;
+
+	return pdacf_config(link);
+}
+
+
+/**
+ * snd_pdacf_assign_resources - initialize the hardware and card instance.
+ * @port: i/o port for the card
+ * @irq: irq number for the card
+ *
+ * this function assigns the specified port and irq, boot the card,
+ * create pcm and control instances, and initialize the rest hardware.
+ *
+ * returns 0 if successful, or a negative error code.
+ */
+static int snd_pdacf_assign_resources(struct snd_pdacf *pdacf, int port, int irq)
+{
+	int err;
+	struct snd_card *card = pdacf->card;
+
+	snd_printdd(KERN_DEBUG "pdacf assign resources: port = 0x%x, irq = %d\n", port, irq);
+	pdacf->port = port;
+	pdacf->irq = irq;
+	pdacf->chip_status |= PDAUDIOCF_STAT_IS_CONFIGURED;
+
+	err = snd_pdacf_ak4117_create(pdacf);
+	if (err < 0)
+		return err;	
+
+	strcpy(card->driver, "PDAudio-CF");
+	sprintf(card->shortname, "Core Sound %s", card->driver);
+	sprintf(card->longname, "%s at 0x%x, irq %i",
+		card->shortname, port, irq);
+
+	err = snd_pdacf_pcm_new(pdacf);
+	if (err < 0)
+		return err;
+
+	if ((err = snd_card_register(card)) < 0)
+		return err;
+
+	return 0;
+}
+
+
+/*
+ * snd_pdacf_detach - detach callback for cs
+ */
+static void snd_pdacf_detach(struct pcmcia_device *link)
+{
+	struct snd_pdacf *chip = link->priv;
+
+	snd_printdd(KERN_DEBUG "pdacf_detach called\n");
+
+	if (chip->chip_status & PDAUDIOCF_STAT_IS_CONFIGURED)
+		snd_pdacf_powerdown(chip);
+	chip->chip_status |= PDAUDIOCF_STAT_IS_STALE; /* to be sure */
+	snd_card_disconnect(chip->card);
+	snd_card_free_when_closed(chip->card);
+}
+
+/*
+ * configuration callback
+ */
+
+static int pdacf_config(struct pcmcia_device *link)
+{
+	struct snd_pdacf *pdacf = link->priv;
+	int ret;
+
+	snd_printdd(KERN_DEBUG "pdacf_config called\n");
+	link->config_index = 0x5;
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_ENABLE_PULSE_IRQ;
+
+	ret = pcmcia_request_io(link);
+	if (ret)
+		goto failed;
+
+	ret = pcmcia_request_exclusive_irq(link, pdacf_interrupt);
+	if (ret)
+		goto failed;
+
+	ret = pcmcia_enable_device(link);
+	if (ret)
+		goto failed;
+
+	if (snd_pdacf_assign_resources(pdacf, link->resource[0]->start,
+					link->irq) < 0)
+		goto failed;
+
+	return 0;
+
+failed:
+	pcmcia_disable_device(link);
+	return -ENODEV;
+}
+
+#ifdef CONFIG_PM
+
+static int pdacf_suspend(struct pcmcia_device *link)
+{
+	struct snd_pdacf *chip = link->priv;
+
+	snd_printdd(KERN_DEBUG "SUSPEND\n");
+	if (chip) {
+		snd_printdd(KERN_DEBUG "snd_pdacf_suspend calling\n");
+		snd_pdacf_suspend(chip, PMSG_SUSPEND);
+	}
+
+	return 0;
+}
+
+static int pdacf_resume(struct pcmcia_device *link)
+{
+	struct snd_pdacf *chip = link->priv;
+
+	snd_printdd(KERN_DEBUG "RESUME\n");
+	if (pcmcia_dev_present(link)) {
+		if (chip) {
+			snd_printdd(KERN_DEBUG "calling snd_pdacf_resume\n");
+			snd_pdacf_resume(chip);
+		}
+	}
+	snd_printdd(KERN_DEBUG "resume done!\n");
+
+	return 0;
+}
+
+#endif
+
+/*
+ * Module entry points
+ */
+static struct pcmcia_device_id snd_pdacf_ids[] = {
+	/* this is too general PCMCIA_DEVICE_MANF_CARD(0x015d, 0x4c45), */
+	PCMCIA_DEVICE_PROD_ID12("Core Sound","PDAudio-CF",0x396d19d2,0x71717b49),
+	PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, snd_pdacf_ids);
+
+static struct pcmcia_driver pdacf_cs_driver = {
+	.owner		= THIS_MODULE,
+	.name		= "snd-pdaudiocf",
+	.probe		= snd_pdacf_probe,
+	.remove		= snd_pdacf_detach,
+	.id_table	= snd_pdacf_ids,
+#ifdef CONFIG_PM
+	.suspend	= pdacf_suspend,
+	.resume		= pdacf_resume,
+#endif
+
+};
+
+static int __init init_pdacf(void)
+{
+	return pcmcia_register_driver(&pdacf_cs_driver);
+}
+
+static void __exit exit_pdacf(void)
+{
+	pcmcia_unregister_driver(&pdacf_cs_driver);
+}
+
+module_init(init_pdacf);
+module_exit(exit_pdacf);
diff --git a/linux/sound/pcmcia/pdaudiocf/pdaudiocf.h b/linux/sound/pcmcia/pdaudiocf/pdaudiocf.h
new file mode 100644
index 0000000..bd26e09
--- /dev/null
+++ b/linux/sound/pcmcia/pdaudiocf/pdaudiocf.h
@@ -0,0 +1,142 @@
+/*
+ * Driver for Sound Cors PDAudioCF soundcard
+ *
+ * Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#ifndef __PDAUDIOCF_H
+#define __PDAUDIOCF_H
+
+#include <sound/pcm.h>
+#include <asm/io.h>
+#include <linux/interrupt.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+#include <sound/ak4117.h>
+
+/* PDAUDIOCF registers */
+#define PDAUDIOCF_REG_MD	0x00	/* music data, R/O */
+#define PDAUDIOCF_REG_WDP	0x02	/* write data pointer / 2, R/O */
+#define PDAUDIOCF_REG_RDP	0x04	/* read data pointer / 2, R/O */
+#define PDAUDIOCF_REG_TCR	0x06	/* test control register W/O */
+#define PDAUDIOCF_REG_SCR	0x08	/* status and control, R/W (see bit description) */
+#define PDAUDIOCF_REG_ISR	0x0a	/* interrupt status, R/O */
+#define PDAUDIOCF_REG_IER	0x0c	/* interrupt enable, R/W */
+#define PDAUDIOCF_REG_AK_IFR	0x0e	/* AK interface register, R/W */
+
+/* PDAUDIOCF_REG_TCR */
+#define PDAUDIOCF_ELIMAKMBIT	(1<<0)	/* simulate AKM music data */
+#define PDAUDIOCF_TESTDATASEL	(1<<1)	/* test data selection, 0 = 0x55, 1 = pseudo-random */
+
+/* PDAUDIOCF_REG_SCR */
+#define PDAUDIOCF_AK_SBP	(1<<0)	/* serial port busy flag */
+#define PDAUDIOCF_RST		(1<<2)	/* FPGA, AKM + SRAM buffer reset */
+#define PDAUDIOCF_PDN		(1<<3)	/* power down bit */
+#define PDAUDIOCF_CLKDIV0	(1<<4)	/* choose 24.576Mhz clock divided by 1,2,3 or 4 */
+#define PDAUDIOCF_CLKDIV1	(1<<5)
+#define PDAUDIOCF_RECORD	(1<<6)	/* start capturing to SRAM */
+#define PDAUDIOCF_AK_SDD	(1<<7)	/* music data detected */
+#define PDAUDIOCF_RED_LED_OFF	(1<<8)	/* red LED off override */
+#define PDAUDIOCF_BLUE_LED_OFF	(1<<9)	/* blue LED off override */
+#define PDAUDIOCF_DATAFMT0	(1<<10)	/* data format bits: 00 = 16-bit, 01 = 18-bit */
+#define PDAUDIOCF_DATAFMT1	(1<<11)	/* 10 = 20-bit, 11 = 24-bit, all right justified */
+#define PDAUDIOCF_FPGAREV(x)	((x>>12)&0x0f) /* FPGA revision */
+
+/* PDAUDIOCF_REG_ISR */
+#define PDAUDIOCF_IRQLVL	(1<<0)	/* Buffer level IRQ */
+#define PDAUDIOCF_IRQOVR	(1<<1)	/* Overrun IRQ */
+#define PDAUDIOCF_IRQAKM	(1<<2)	/* AKM IRQ */
+
+/* PDAUDIOCF_REG_IER */
+#define PDAUDIOCF_IRQLVLEN0	(1<<0)	/* fill threshold levels; 00 = none, 01 = 1/8th of buffer */
+#define PDAUDIOCF_IRQLVLEN1	(1<<1)	/* 10 = 1/4th of buffer, 11 = 1/2th of buffer */
+#define PDAUDIOCF_IRQOVREN	(1<<2)	/* enable overrun IRQ */
+#define PDAUDIOCF_IRQAKMEN	(1<<3)	/* enable AKM IRQ */
+#define PDAUDIOCF_BLUEDUTY0	(1<<8)	/* blue LED duty cycle; 00 = 100%, 01 = 50% */
+#define PDAUDIOCF_BLUEDUTY1	(1<<9)	/* 02 = 25%, 11 = 12% */
+#define PDAUDIOCF_REDDUTY0	(1<<10)	/* red LED duty cycle; 00 = 100%, 01 = 50% */
+#define PDAUDIOCF_REDDUTY1	(1<<11)	/* 02 = 25%, 11 = 12% */
+#define PDAUDIOCF_BLUESDD	(1<<12)	/* blue LED against SDD bit */
+#define PDAUDIOCF_BLUEMODULATE	(1<<13)	/* save power when 100% duty cycle selected */
+#define PDAUDIOCF_REDMODULATE	(1<<14)	/* save power when 100% duty cycle selected */
+#define PDAUDIOCF_HALFRATE	(1<<15)	/* slow both LED blinks by half (also spdif detect rate) */
+
+/* chip status */
+#define PDAUDIOCF_STAT_IS_STALE	(1<<0)
+#define PDAUDIOCF_STAT_IS_CONFIGURED (1<<1)
+#define PDAUDIOCF_STAT_IS_SUSPENDED (1<<2)
+
+struct snd_pdacf {
+	struct snd_card *card;
+	int index;
+
+	unsigned long port;
+	int irq;
+
+	spinlock_t reg_lock;
+	unsigned short regmap[8];
+	unsigned short suspend_reg_scr;
+	struct tasklet_struct tq;
+
+	spinlock_t ak4117_lock;
+	struct ak4117 *ak4117;
+
+	unsigned int chip_status;
+
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *pcm_substream;
+	unsigned int pcm_running: 1;
+	unsigned int pcm_channels;
+	unsigned int pcm_swab;
+	unsigned int pcm_little;
+	unsigned int pcm_frame;
+	unsigned int pcm_sample;
+	unsigned int pcm_xor;
+	unsigned int pcm_size;
+	unsigned int pcm_period;
+	unsigned int pcm_tdone;
+	unsigned int pcm_hwptr;
+	void *pcm_area;
+	
+	/* pcmcia stuff */
+	struct pcmcia_device	*p_dev;
+};
+
+static inline void pdacf_reg_write(struct snd_pdacf *chip, unsigned char reg, unsigned short val)
+{
+	outw(chip->regmap[reg>>1] = val, chip->port + reg);
+}
+
+static inline unsigned short pdacf_reg_read(struct snd_pdacf *chip, unsigned char reg)
+{
+	return inw(chip->port + reg);
+}
+
+struct snd_pdacf *snd_pdacf_create(struct snd_card *card);
+int snd_pdacf_ak4117_create(struct snd_pdacf *pdacf);
+void snd_pdacf_powerdown(struct snd_pdacf *chip);
+#ifdef CONFIG_PM
+int snd_pdacf_suspend(struct snd_pdacf *chip, pm_message_t state);
+int snd_pdacf_resume(struct snd_pdacf *chip);
+#endif
+int snd_pdacf_pcm_new(struct snd_pdacf *chip);
+irqreturn_t pdacf_interrupt(int irq, void *dev);
+void pdacf_tasklet(unsigned long private_data);
+void pdacf_reinit(struct snd_pdacf *chip, int resume);
+
+#endif /* __PDAUDIOCF_H */
diff --git a/linux/sound/pcmcia/pdaudiocf/pdaudiocf_core.c b/linux/sound/pcmcia/pdaudiocf/pdaudiocf_core.c
new file mode 100644
index 0000000..9dce0bd
--- /dev/null
+++ b/linux/sound/pcmcia/pdaudiocf/pdaudiocf_core.c
@@ -0,0 +1,298 @@
+/*
+ * Driver for Sound Core PDAudioCF soundcard
+ *
+ * Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include "pdaudiocf.h"
+#include <sound/initval.h>
+
+/*
+ *
+ */
+static unsigned char pdacf_ak4117_read(void *private_data, unsigned char reg)
+{
+	struct snd_pdacf *chip = private_data;
+	unsigned long timeout;
+	unsigned long flags;
+	unsigned char res;
+
+	spin_lock_irqsave(&chip->ak4117_lock, flags);
+	timeout = 1000;
+	while (pdacf_reg_read(chip, PDAUDIOCF_REG_SCR) & PDAUDIOCF_AK_SBP) {
+		udelay(5);
+		if (--timeout == 0) {
+			spin_unlock_irqrestore(&chip->ak4117_lock, flags);
+			snd_printk(KERN_ERR "AK4117 ready timeout (read)\n");
+			return 0;
+		}
+	}
+	pdacf_reg_write(chip, PDAUDIOCF_REG_AK_IFR, (u16)reg << 8);
+	timeout = 1000;
+	while (pdacf_reg_read(chip, PDAUDIOCF_REG_SCR) & PDAUDIOCF_AK_SBP) {
+		udelay(5);
+		if (--timeout == 0) {
+			spin_unlock_irqrestore(&chip->ak4117_lock, flags);
+			snd_printk(KERN_ERR "AK4117 read timeout (read2)\n");
+			return 0;
+		}
+	}
+	res = (unsigned char)pdacf_reg_read(chip, PDAUDIOCF_REG_AK_IFR);
+	spin_unlock_irqrestore(&chip->ak4117_lock, flags);
+	return res;
+}
+
+static void pdacf_ak4117_write(void *private_data, unsigned char reg, unsigned char val)
+{
+	struct snd_pdacf *chip = private_data;
+	unsigned long timeout;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->ak4117_lock, flags);
+	timeout = 1000;
+	while (inw(chip->port + PDAUDIOCF_REG_SCR) & PDAUDIOCF_AK_SBP) {
+		udelay(5);
+		if (--timeout == 0) {
+			spin_unlock_irqrestore(&chip->ak4117_lock, flags);
+			snd_printk(KERN_ERR "AK4117 ready timeout (write)\n");
+			return;
+		}
+	}
+	outw((u16)reg << 8 | val | (1<<13), chip->port + PDAUDIOCF_REG_AK_IFR);
+	spin_unlock_irqrestore(&chip->ak4117_lock, flags);
+}
+
+#if 0
+void pdacf_dump(struct snd_pdacf *chip)
+{
+	printk(KERN_DEBUG "PDAUDIOCF DUMP (0x%lx):\n", chip->port);
+	printk(KERN_DEBUG "WPD         : 0x%x\n",
+	       inw(chip->port + PDAUDIOCF_REG_WDP));
+	printk(KERN_DEBUG "RDP         : 0x%x\n",
+	       inw(chip->port + PDAUDIOCF_REG_RDP));
+	printk(KERN_DEBUG "TCR         : 0x%x\n",
+	       inw(chip->port + PDAUDIOCF_REG_TCR));
+	printk(KERN_DEBUG "SCR         : 0x%x\n",
+	       inw(chip->port + PDAUDIOCF_REG_SCR));
+	printk(KERN_DEBUG "ISR         : 0x%x\n",
+	       inw(chip->port + PDAUDIOCF_REG_ISR));
+	printk(KERN_DEBUG "IER         : 0x%x\n",
+	       inw(chip->port + PDAUDIOCF_REG_IER));
+	printk(KERN_DEBUG "AK_IFR      : 0x%x\n",
+	       inw(chip->port + PDAUDIOCF_REG_AK_IFR));
+}
+#endif
+
+static int pdacf_reset(struct snd_pdacf *chip, int powerdown)
+{
+	u16 val;
+	
+	val = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR);
+	val |= PDAUDIOCF_PDN;
+	val &= ~PDAUDIOCF_RECORD;		/* for sure */
+	pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val);
+	udelay(5);
+	val |= PDAUDIOCF_RST;
+	pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val);
+	udelay(200);
+	val &= ~PDAUDIOCF_RST;
+	pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val);
+	udelay(5);
+	if (!powerdown) {
+		val &= ~PDAUDIOCF_PDN;
+		pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val);
+		udelay(200);
+	}
+	return 0;
+}
+
+void pdacf_reinit(struct snd_pdacf *chip, int resume)
+{
+	pdacf_reset(chip, 0);
+	if (resume)
+		pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, chip->suspend_reg_scr);
+	snd_ak4117_reinit(chip->ak4117);
+	pdacf_reg_write(chip, PDAUDIOCF_REG_TCR, chip->regmap[PDAUDIOCF_REG_TCR>>1]);
+	pdacf_reg_write(chip, PDAUDIOCF_REG_IER, chip->regmap[PDAUDIOCF_REG_IER>>1]);
+}
+
+static void pdacf_proc_read(struct snd_info_entry * entry,
+                            struct snd_info_buffer *buffer)
+{
+	struct snd_pdacf *chip = entry->private_data;
+	u16 tmp;
+
+	snd_iprintf(buffer, "PDAudioCF\n\n");
+	tmp = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR);
+	snd_iprintf(buffer, "FPGA revision      : 0x%x\n", PDAUDIOCF_FPGAREV(tmp));
+	                                   
+}
+
+static void pdacf_proc_init(struct snd_pdacf *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (! snd_card_proc_new(chip->card, "pdaudiocf", &entry))
+		snd_info_set_text_ops(entry, chip, pdacf_proc_read);
+}
+
+struct snd_pdacf *snd_pdacf_create(struct snd_card *card)
+{
+	struct snd_pdacf *chip;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return NULL;
+	chip->card = card;
+	spin_lock_init(&chip->reg_lock);
+	spin_lock_init(&chip->ak4117_lock);
+	tasklet_init(&chip->tq, pdacf_tasklet, (unsigned long)chip);
+	card->private_data = chip;
+
+	pdacf_proc_init(chip);
+	return chip;
+}
+
+static void snd_pdacf_ak4117_change(struct ak4117 *ak4117, unsigned char c0, unsigned char c1)
+{
+	struct snd_pdacf *chip = ak4117->change_callback_private;
+	unsigned long flags;
+	u16 val;
+
+	if (!(c0 & AK4117_UNLCK))
+		return;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = chip->regmap[PDAUDIOCF_REG_SCR>>1];
+	if (ak4117->rcs0 & AK4117_UNLCK)
+		val |= PDAUDIOCF_BLUE_LED_OFF;
+	else
+		val &= ~PDAUDIOCF_BLUE_LED_OFF;
+	pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+}
+
+int snd_pdacf_ak4117_create(struct snd_pdacf *chip)
+{
+	int err;
+	u16 val;
+	/* design note: if we unmask PLL unlock, parity, valid, audio or auto bit interrupts */
+	/* from AK4117 then INT1 pin from AK4117 will be high all time, because PCMCIA interrupts are */
+	/* egde based and FPGA does logical OR for all interrupt sources, we cannot use these */
+	/* high-rate sources */
+	static unsigned char pgm[5] = {
+		AK4117_XTL_24_576M | AK4117_EXCT,				/* AK4117_REG_PWRDN */
+		AK4117_CM_PLL_XTAL | AK4117_PKCS_128fs | AK4117_XCKS_128fs,	/* AK4117_REQ_CLOCK */
+		AK4117_EFH_1024LRCLK | AK4117_DIF_24R | AK4117_IPS,		/* AK4117_REG_IO */
+		0xff,								/* AK4117_REG_INT0_MASK */
+		AK4117_MAUTO | AK4117_MAUD | AK4117_MULK | AK4117_MPAR | AK4117_MV, /* AK4117_REG_INT1_MASK */
+	};
+
+	err = pdacf_reset(chip, 0);
+	if (err < 0)
+		return err;
+	err = snd_ak4117_create(chip->card, pdacf_ak4117_read, pdacf_ak4117_write, pgm, chip, &chip->ak4117);
+	if (err < 0)
+		return err;
+
+	val = pdacf_reg_read(chip, PDAUDIOCF_REG_TCR);
+#if 1 /* normal operation */
+	val &= ~(PDAUDIOCF_ELIMAKMBIT|PDAUDIOCF_TESTDATASEL);
+#else /* debug */
+	val |= PDAUDIOCF_ELIMAKMBIT;
+	val &= ~PDAUDIOCF_TESTDATASEL;
+#endif
+	pdacf_reg_write(chip, PDAUDIOCF_REG_TCR, val);
+	
+	/* setup the FPGA to match AK4117 setup */
+	val = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR);
+	val &= ~(PDAUDIOCF_CLKDIV0 | PDAUDIOCF_CLKDIV1);		/* use 24.576Mhz clock */
+	val &= ~(PDAUDIOCF_RED_LED_OFF|PDAUDIOCF_BLUE_LED_OFF);
+	val |= PDAUDIOCF_DATAFMT0 | PDAUDIOCF_DATAFMT1;			/* 24-bit data */
+	pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val);
+
+	/* setup LEDs and IRQ */
+	val = pdacf_reg_read(chip, PDAUDIOCF_REG_IER);
+	val &= ~(PDAUDIOCF_IRQLVLEN0 | PDAUDIOCF_IRQLVLEN1);
+	val &= ~(PDAUDIOCF_BLUEDUTY0 | PDAUDIOCF_REDDUTY0 | PDAUDIOCF_REDDUTY1);
+	val |= PDAUDIOCF_BLUEDUTY1 | PDAUDIOCF_HALFRATE;
+	val |= PDAUDIOCF_IRQOVREN | PDAUDIOCF_IRQAKMEN;
+	pdacf_reg_write(chip, PDAUDIOCF_REG_IER, val);
+
+	chip->ak4117->change_callback_private = chip;
+	chip->ak4117->change_callback = snd_pdacf_ak4117_change;
+
+	/* update LED status */
+	snd_pdacf_ak4117_change(chip->ak4117, AK4117_UNLCK, 0);
+
+	return 0;
+}
+
+void snd_pdacf_powerdown(struct snd_pdacf *chip)
+{
+	u16 val;
+
+	val = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR);
+	chip->suspend_reg_scr = val;
+	val |= PDAUDIOCF_RED_LED_OFF | PDAUDIOCF_BLUE_LED_OFF;
+	pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val);
+	/* disable interrupts, but use direct write to preserve old register value in chip->regmap */
+	val = inw(chip->port + PDAUDIOCF_REG_IER);
+	val &= ~(PDAUDIOCF_IRQOVREN|PDAUDIOCF_IRQAKMEN|PDAUDIOCF_IRQLVLEN0|PDAUDIOCF_IRQLVLEN1);
+	outw(val, chip->port + PDAUDIOCF_REG_IER);
+	pdacf_reset(chip, 1);
+}
+
+#ifdef CONFIG_PM
+
+int snd_pdacf_suspend(struct snd_pdacf *chip, pm_message_t state)
+{
+	u16 val;
+	
+	snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(chip->pcm);
+	/* disable interrupts, but use direct write to preserve old register value in chip->regmap */
+	val = inw(chip->port + PDAUDIOCF_REG_IER);
+	val &= ~(PDAUDIOCF_IRQOVREN|PDAUDIOCF_IRQAKMEN|PDAUDIOCF_IRQLVLEN0|PDAUDIOCF_IRQLVLEN1);
+	outw(val, chip->port + PDAUDIOCF_REG_IER);
+	chip->chip_status |= PDAUDIOCF_STAT_IS_SUSPENDED;	/* ignore interrupts from now */
+	snd_pdacf_powerdown(chip);
+	return 0;
+}
+
+static inline int check_signal(struct snd_pdacf *chip)
+{
+	return (chip->ak4117->rcs0 & AK4117_UNLCK) == 0;
+}
+
+int snd_pdacf_resume(struct snd_pdacf *chip)
+{
+	int timeout = 40;
+
+	pdacf_reinit(chip, 1);
+	/* wait for AK4117's PLL */
+	while (timeout-- > 0 &&
+	       (snd_ak4117_external_rate(chip->ak4117) <= 0 || !check_signal(chip)))
+		mdelay(1);
+	chip->chip_status &= ~PDAUDIOCF_STAT_IS_SUSPENDED;
+	snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
+	return 0;
+}
+#endif
diff --git a/linux/sound/pcmcia/pdaudiocf/pdaudiocf_irq.c b/linux/sound/pcmcia/pdaudiocf/pdaudiocf_irq.c
new file mode 100644
index 0000000..dcd3220
--- /dev/null
+++ b/linux/sound/pcmcia/pdaudiocf/pdaudiocf_irq.c
@@ -0,0 +1,325 @@
+/*
+ * Driver for Sound Core PDAudioCF soundcard
+ *
+ * Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/core.h>
+#include "pdaudiocf.h"
+#include <sound/initval.h>
+#include <asm/irq_regs.h>
+
+/*
+ *
+ */
+irqreturn_t pdacf_interrupt(int irq, void *dev)
+{
+	struct snd_pdacf *chip = dev;
+	unsigned short stat;
+
+	if ((chip->chip_status & (PDAUDIOCF_STAT_IS_STALE|
+				  PDAUDIOCF_STAT_IS_CONFIGURED|
+				  PDAUDIOCF_STAT_IS_SUSPENDED)) != PDAUDIOCF_STAT_IS_CONFIGURED)
+		return IRQ_HANDLED;	/* IRQ_NONE here? */
+
+	stat = inw(chip->port + PDAUDIOCF_REG_ISR);
+	if (stat & (PDAUDIOCF_IRQLVL|PDAUDIOCF_IRQOVR)) {
+		if (stat & PDAUDIOCF_IRQOVR)	/* should never happen */
+			snd_printk(KERN_ERR "PDAUDIOCF SRAM buffer overrun detected!\n");
+		if (chip->pcm_substream)
+			tasklet_schedule(&chip->tq);
+		if (!(stat & PDAUDIOCF_IRQAKM))
+			stat |= PDAUDIOCF_IRQAKM;	/* check rate */
+	}
+	if (get_irq_regs() != NULL)
+		snd_ak4117_check_rate_and_errors(chip->ak4117, 0);
+	return IRQ_HANDLED;
+}
+
+static inline void pdacf_transfer_mono16(u16 *dst, u16 xor, unsigned int size, unsigned long rdp_port)
+{
+	while (size-- > 0) {
+		*dst++ = inw(rdp_port) ^ xor;
+		inw(rdp_port);
+	}
+}
+
+static inline void pdacf_transfer_mono32(u32 *dst, u32 xor, unsigned int size, unsigned long rdp_port)
+{
+	register u16 val1, val2;
+
+	while (size-- > 0) {
+		val1 = inw(rdp_port);
+		val2 = inw(rdp_port);
+		inw(rdp_port);
+		*dst++ = ((((u32)val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor;
+	}
+}
+
+static inline void pdacf_transfer_stereo16(u16 *dst, u16 xor, unsigned int size, unsigned long rdp_port)
+{
+	while (size-- > 0) {
+		*dst++ = inw(rdp_port) ^ xor;
+		*dst++ = inw(rdp_port) ^ xor;
+	}
+}
+
+static inline void pdacf_transfer_stereo32(u32 *dst, u32 xor, unsigned int size, unsigned long rdp_port)
+{
+	register u16 val1, val2, val3;
+
+	while (size-- > 0) {
+		val1 = inw(rdp_port);
+		val2 = inw(rdp_port);
+		val3 = inw(rdp_port);
+		*dst++ = ((((u32)val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor;
+		*dst++ = (((u32)val3 << 16) | (val2 & 0xff00)) ^ xor;
+	}
+}
+
+static inline void pdacf_transfer_mono16sw(u16 *dst, u16 xor, unsigned int size, unsigned long rdp_port)
+{
+	while (size-- > 0) {
+		*dst++ = swab16(inw(rdp_port) ^ xor);
+		inw(rdp_port);
+	}
+}
+
+static inline void pdacf_transfer_mono32sw(u32 *dst, u32 xor, unsigned int size, unsigned long rdp_port)
+{
+	register u16 val1, val2;
+
+	while (size-- > 0) {
+		val1 = inw(rdp_port);
+		val2 = inw(rdp_port);
+		inw(rdp_port);
+		*dst++ = swab32((((val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor);
+	}
+}
+
+static inline void pdacf_transfer_stereo16sw(u16 *dst, u16 xor, unsigned int size, unsigned long rdp_port)
+{
+	while (size-- > 0) {
+		*dst++ = swab16(inw(rdp_port) ^ xor);
+		*dst++ = swab16(inw(rdp_port) ^ xor);
+	}
+}
+
+static inline void pdacf_transfer_stereo32sw(u32 *dst, u32 xor, unsigned int size, unsigned long rdp_port)
+{
+	register u16 val1, val2, val3;
+
+	while (size-- > 0) {
+		val1 = inw(rdp_port);
+		val2 = inw(rdp_port);
+		val3 = inw(rdp_port);
+		*dst++ = swab32((((val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor);
+		*dst++ = swab32((((u32)val3 << 16) | (val2 & 0xff00)) ^ xor);
+	}
+}
+
+static inline void pdacf_transfer_mono24le(u8 *dst, u16 xor, unsigned int size, unsigned long rdp_port)
+{
+	register u16 val1, val2;
+	register u32 xval1;
+
+	while (size-- > 0) {
+		val1 = inw(rdp_port);
+		val2 = inw(rdp_port);
+		inw(rdp_port);
+		xval1 = (((val2 & 0xff) << 8) | (val1 << 16)) ^ xor;
+		*dst++ = (u8)(xval1 >> 8);
+		*dst++ = (u8)(xval1 >> 16);
+		*dst++ = (u8)(xval1 >> 24);
+	}
+}
+
+static inline void pdacf_transfer_mono24be(u8 *dst, u16 xor, unsigned int size, unsigned long rdp_port)
+{
+	register u16 val1, val2;
+	register u32 xval1;
+
+	while (size-- > 0) {
+		val1 = inw(rdp_port);
+		val2 = inw(rdp_port);
+		inw(rdp_port);
+		xval1 = (((val2 & 0xff) << 8) | (val1 << 16)) ^ xor;
+		*dst++ = (u8)(xval1 >> 24);
+		*dst++ = (u8)(xval1 >> 16);
+		*dst++ = (u8)(xval1 >> 8);
+	}
+}
+
+static inline void pdacf_transfer_stereo24le(u8 *dst, u32 xor, unsigned int size, unsigned long rdp_port)
+{
+	register u16 val1, val2, val3;
+	register u32 xval1, xval2;
+
+	while (size-- > 0) {
+		val1 = inw(rdp_port);
+		val2 = inw(rdp_port);
+		val3 = inw(rdp_port);
+		xval1 = ((((u32)val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor;
+		xval2 = (((u32)val3 << 16) | (val2 & 0xff00)) ^ xor;
+		*dst++ = (u8)(xval1 >> 8);
+		*dst++ = (u8)(xval1 >> 16);
+		*dst++ = (u8)(xval1 >> 24);
+		*dst++ = (u8)(xval2 >> 8);
+		*dst++ = (u8)(xval2 >> 16);
+		*dst++ = (u8)(xval2 >> 24);
+	}
+}
+
+static inline void pdacf_transfer_stereo24be(u8 *dst, u32 xor, unsigned int size, unsigned long rdp_port)
+{
+	register u16 val1, val2, val3;
+	register u32 xval1, xval2;
+
+	while (size-- > 0) {
+		val1 = inw(rdp_port);
+		val2 = inw(rdp_port);
+		val3 = inw(rdp_port);
+		xval1 = ((((u32)val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor;
+		xval2 = (((u32)val3 << 16) | (val2 & 0xff00)) ^ xor;
+		*dst++ = (u8)(xval1 >> 24);
+		*dst++ = (u8)(xval1 >> 16);
+		*dst++ = (u8)(xval1 >> 8);
+		*dst++ = (u8)(xval2 >> 24);
+		*dst++ = (u8)(xval2 >> 16);
+		*dst++ = (u8)(xval2 >> 8);
+	}
+}
+
+static void pdacf_transfer(struct snd_pdacf *chip, unsigned int size, unsigned int off)
+{
+	unsigned long rdp_port = chip->port + PDAUDIOCF_REG_MD;
+	unsigned int xor = chip->pcm_xor;
+
+	if (chip->pcm_sample == 3) {
+		if (chip->pcm_little) {
+			if (chip->pcm_channels == 1) {
+				pdacf_transfer_mono24le((char *)chip->pcm_area + (off * 3), xor, size, rdp_port);
+			} else {
+				pdacf_transfer_stereo24le((char *)chip->pcm_area + (off * 6), xor, size, rdp_port);
+			}
+		} else {
+			if (chip->pcm_channels == 1) {
+				pdacf_transfer_mono24be((char *)chip->pcm_area + (off * 3), xor, size, rdp_port);
+			} else {
+				pdacf_transfer_stereo24be((char *)chip->pcm_area + (off * 6), xor, size, rdp_port);
+			}			
+		}
+		return;
+	}
+	if (chip->pcm_swab == 0) {
+		if (chip->pcm_channels == 1) {
+			if (chip->pcm_frame == 2) {
+				pdacf_transfer_mono16((u16 *)chip->pcm_area + off, xor, size, rdp_port);
+			} else {
+				pdacf_transfer_mono32((u32 *)chip->pcm_area + off, xor, size, rdp_port);
+			}
+		} else {
+			if (chip->pcm_frame == 2) {
+				pdacf_transfer_stereo16((u16 *)chip->pcm_area + (off * 2), xor, size, rdp_port);
+			} else {
+				pdacf_transfer_stereo32((u32 *)chip->pcm_area + (off * 2), xor, size, rdp_port);
+			}
+		}
+	} else {
+		if (chip->pcm_channels == 1) {
+			if (chip->pcm_frame == 2) {
+				pdacf_transfer_mono16sw((u16 *)chip->pcm_area + off, xor, size, rdp_port);
+			} else {
+				pdacf_transfer_mono32sw((u32 *)chip->pcm_area + off, xor, size, rdp_port);
+			}
+		} else {
+			if (chip->pcm_frame == 2) {
+				pdacf_transfer_stereo16sw((u16 *)chip->pcm_area + (off * 2), xor, size, rdp_port);
+			} else {
+				pdacf_transfer_stereo32sw((u32 *)chip->pcm_area + (off * 2), xor, size, rdp_port);
+			}
+		}
+	}
+}
+
+void pdacf_tasklet(unsigned long private_data)
+{
+	struct snd_pdacf *chip = (struct snd_pdacf *) private_data;
+	int size, off, cont, rdp, wdp;
+
+	if ((chip->chip_status & (PDAUDIOCF_STAT_IS_STALE|PDAUDIOCF_STAT_IS_CONFIGURED)) != PDAUDIOCF_STAT_IS_CONFIGURED)
+		return;
+	
+	if (chip->pcm_substream == NULL || chip->pcm_substream->runtime == NULL || !snd_pcm_running(chip->pcm_substream))
+		return;
+
+	rdp = inw(chip->port + PDAUDIOCF_REG_RDP);
+	wdp = inw(chip->port + PDAUDIOCF_REG_WDP);
+	/* printk(KERN_DEBUG "TASKLET: rdp = %x, wdp = %x\n", rdp, wdp); */
+	size = wdp - rdp;
+	if (size < 0)
+		size += 0x10000;
+	if (size == 0)
+		size = 0x10000;
+	size /= chip->pcm_frame;
+	if (size > 64)
+		size -= 32;
+
+#if 0
+	chip->pcm_hwptr += size;
+	chip->pcm_hwptr %= chip->pcm_size;
+	chip->pcm_tdone += size;
+	if (chip->pcm_frame == 2) {
+		unsigned long rdp_port = chip->port + PDAUDIOCF_REG_MD;
+		while (size-- > 0) {
+			inw(rdp_port);
+			inw(rdp_port);
+		}
+	} else {
+		unsigned long rdp_port = chip->port + PDAUDIOCF_REG_MD;
+		while (size-- > 0) {
+			inw(rdp_port);
+			inw(rdp_port);
+			inw(rdp_port);
+		}
+	}
+#else
+	off = chip->pcm_hwptr + chip->pcm_tdone;
+	off %= chip->pcm_size;
+	chip->pcm_tdone += size;
+	while (size > 0) {
+		cont = chip->pcm_size - off;
+		if (cont > size)
+			cont = size;
+		pdacf_transfer(chip, cont, off);
+		off += cont;
+		off %= chip->pcm_size;
+		size -= cont;
+	}
+#endif
+	spin_lock(&chip->reg_lock);
+	while (chip->pcm_tdone >= chip->pcm_period) {
+		chip->pcm_hwptr += chip->pcm_period;
+		chip->pcm_hwptr %= chip->pcm_size;
+		chip->pcm_tdone -= chip->pcm_period;
+		spin_unlock(&chip->reg_lock);
+		snd_pcm_period_elapsed(chip->pcm_substream);
+		spin_lock(&chip->reg_lock);
+	}
+	spin_unlock(&chip->reg_lock);
+	/* printk(KERN_DEBUG "TASKLET: end\n"); */
+}
diff --git a/linux/sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c b/linux/sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c
new file mode 100644
index 0000000..43f995a
--- /dev/null
+++ b/linux/sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c
@@ -0,0 +1,307 @@
+/*
+ * Driver for Sound Core PDAudioCF soundcards
+ *
+ * PCM part
+ *
+ * Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/asoundef.h>
+#include "pdaudiocf.h"
+
+
+/*
+ * clear the SRAM contents
+ */
+static int pdacf_pcm_clear_sram(struct snd_pdacf *chip)
+{
+	int max_loop = 64 * 1024;
+
+	while (inw(chip->port + PDAUDIOCF_REG_RDP) != inw(chip->port + PDAUDIOCF_REG_WDP)) {
+		if (max_loop-- < 0)
+			return -EIO;
+		inw(chip->port + PDAUDIOCF_REG_MD);
+	}
+	return 0;
+}
+
+/*
+ * pdacf_pcm_trigger - trigger callback for capture
+ */
+static int pdacf_pcm_trigger(struct snd_pcm_substream *subs, int cmd)
+{
+	struct snd_pdacf *chip = snd_pcm_substream_chip(subs);
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	int inc, ret = 0, rate;
+	unsigned short mask, val, tmp;
+
+	if (chip->chip_status & PDAUDIOCF_STAT_IS_STALE)
+		return -EBUSY;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		chip->pcm_hwptr = 0;
+		chip->pcm_tdone = 0;
+		/* fall thru */
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		mask = 0;
+		val = PDAUDIOCF_RECORD;
+		inc = 1;
+		rate = snd_ak4117_check_rate_and_errors(chip->ak4117, AK4117_CHECK_NO_STAT|AK4117_CHECK_NO_RATE);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		mask = PDAUDIOCF_RECORD;
+		val = 0;
+		inc = -1;
+		rate = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+	spin_lock(&chip->reg_lock);
+	chip->pcm_running += inc;
+	tmp = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR);
+	if (chip->pcm_running) {
+		if ((chip->ak4117->rcs0 & AK4117_UNLCK) || runtime->rate != rate) {
+			chip->pcm_running -= inc;
+			ret = -EIO;
+			goto __end;
+		}
+	}
+	tmp &= ~mask;
+	tmp |= val;
+	pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, tmp);
+      __end:
+	spin_unlock(&chip->reg_lock);
+	snd_ak4117_check_rate_and_errors(chip->ak4117, AK4117_CHECK_NO_RATE);
+	return ret;
+}
+
+/*
+ * pdacf_pcm_hw_params - hw_params callback for playback and capture
+ */
+static int pdacf_pcm_hw_params(struct snd_pcm_substream *subs,
+				     struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_alloc_vmalloc_32_buffer
+					(subs, params_buffer_bytes(hw_params));
+}
+
+/*
+ * pdacf_pcm_hw_free - hw_free callback for playback and capture
+ */
+static int pdacf_pcm_hw_free(struct snd_pcm_substream *subs)
+{
+	return snd_pcm_lib_free_vmalloc_buffer(subs);
+}
+
+/*
+ * pdacf_pcm_prepare - prepare callback for playback and capture
+ */
+static int pdacf_pcm_prepare(struct snd_pcm_substream *subs)
+{
+	struct snd_pdacf *chip = snd_pcm_substream_chip(subs);
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	u16 val, nval, aval;
+
+	if (chip->chip_status & PDAUDIOCF_STAT_IS_STALE)
+		return -EBUSY;
+
+	chip->pcm_channels = runtime->channels;
+
+	chip->pcm_little = snd_pcm_format_little_endian(runtime->format) > 0;
+#ifdef SNDRV_LITTLE_ENDIAN
+	chip->pcm_swab = snd_pcm_format_big_endian(runtime->format) > 0;
+#else
+	chip->pcm_swab = chip->pcm_little;
+#endif
+
+	if (snd_pcm_format_unsigned(runtime->format))
+		chip->pcm_xor = 0x80008000;
+
+	if (pdacf_pcm_clear_sram(chip) < 0)
+		return -EIO;
+	
+	val = nval = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR);
+	nval &= ~(PDAUDIOCF_DATAFMT0|PDAUDIOCF_DATAFMT1);
+	switch (runtime->format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+	case SNDRV_PCM_FORMAT_S16_BE:
+		break;
+	default: /* 24-bit */
+		nval |= PDAUDIOCF_DATAFMT0 | PDAUDIOCF_DATAFMT1;
+		break;
+	}
+	aval = 0;
+	chip->pcm_sample = 4;
+	switch (runtime->format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+	case SNDRV_PCM_FORMAT_S16_BE:
+		aval = AK4117_DIF_16R;
+		chip->pcm_frame = 2;
+		chip->pcm_sample = 2;
+		break;
+	case SNDRV_PCM_FORMAT_S24_3LE:
+	case SNDRV_PCM_FORMAT_S24_3BE:
+		chip->pcm_sample = 3;
+		/* fall through */
+	default: /* 24-bit */
+		aval = AK4117_DIF_24R;
+		chip->pcm_frame = 3;
+		chip->pcm_xor &= 0xffff0000;
+		break;
+	}
+
+	if (val != nval) {
+		snd_ak4117_reg_write(chip->ak4117, AK4117_REG_IO, AK4117_DIF2|AK4117_DIF1|AK4117_DIF0, aval);
+		pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, nval);
+	}
+
+	val = pdacf_reg_read(chip,  PDAUDIOCF_REG_IER);
+	val &= ~(PDAUDIOCF_IRQLVLEN1);
+	val |= PDAUDIOCF_IRQLVLEN0;
+	pdacf_reg_write(chip, PDAUDIOCF_REG_IER, val);
+
+	chip->pcm_size = runtime->buffer_size;
+	chip->pcm_period = runtime->period_size;
+	chip->pcm_area = runtime->dma_area;
+
+	return 0;
+}
+
+
+/*
+ * capture hw information
+ */
+
+static struct snd_pcm_hardware pdacf_pcm_capture_hw = {
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_BATCH),
+	.formats =		SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |
+				SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |
+				SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE,
+	.rates =		SNDRV_PCM_RATE_32000 |
+				SNDRV_PCM_RATE_44100 |
+				SNDRV_PCM_RATE_48000 |
+				SNDRV_PCM_RATE_88200 |
+				SNDRV_PCM_RATE_96000 |
+				SNDRV_PCM_RATE_176400 |
+				SNDRV_PCM_RATE_192000,
+	.rate_min =		32000,
+	.rate_max =		192000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	(512*1024),
+	.period_bytes_min =	8*1024,
+	.period_bytes_max =	(64*1024),
+	.periods_min =		2,
+	.periods_max =		128,
+	.fifo_size =		0,
+};
+
+
+/*
+ * pdacf_pcm_capture_open - open callback for capture
+ */
+static int pdacf_pcm_capture_open(struct snd_pcm_substream *subs)
+{
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct snd_pdacf *chip = snd_pcm_substream_chip(subs);
+
+	if (chip->chip_status & PDAUDIOCF_STAT_IS_STALE)
+		return -EBUSY;
+
+	runtime->hw = pdacf_pcm_capture_hw;
+	runtime->private_data = chip;
+	chip->pcm_substream = subs;
+
+	return 0;
+}
+
+/*
+ * pdacf_pcm_capture_close - close callback for capture
+ */
+static int pdacf_pcm_capture_close(struct snd_pcm_substream *subs)
+{
+	struct snd_pdacf *chip = snd_pcm_substream_chip(subs);
+
+	if (!chip)
+		return -EINVAL;
+	pdacf_reinit(chip, 0);
+	chip->pcm_substream = NULL;
+	return 0;
+}
+
+
+/*
+ * pdacf_pcm_capture_pointer - pointer callback for capture
+ */
+static snd_pcm_uframes_t pdacf_pcm_capture_pointer(struct snd_pcm_substream *subs)
+{
+	struct snd_pdacf *chip = snd_pcm_substream_chip(subs);
+	return chip->pcm_hwptr;
+}
+
+/*
+ * operators for PCM capture
+ */
+static struct snd_pcm_ops pdacf_pcm_capture_ops = {
+	.open =		pdacf_pcm_capture_open,
+	.close =	pdacf_pcm_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	pdacf_pcm_hw_params,
+	.hw_free =	pdacf_pcm_hw_free,
+	.prepare =	pdacf_pcm_prepare,
+	.trigger =	pdacf_pcm_trigger,
+	.pointer =	pdacf_pcm_capture_pointer,
+	.page =		snd_pcm_lib_get_vmalloc_page,
+	.mmap =		snd_pcm_lib_mmap_vmalloc,
+};
+
+
+/*
+ * snd_pdacf_pcm_new - create and initialize a pcm
+ */
+int snd_pdacf_pcm_new(struct snd_pdacf *chip)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(chip->card, "PDAudioCF", 0, 0, 1, &pcm);
+	if (err < 0)
+		return err;
+		
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pdacf_pcm_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, chip->card->shortname);
+	chip->pcm = pcm;
+	
+	err = snd_ak4117_build(chip->ak4117, pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
diff --git a/linux/sound/pcmcia/vx/Makefile b/linux/sound/pcmcia/vx/Makefile
new file mode 100644
index 0000000..2bb42ea
--- /dev/null
+++ b/linux/sound/pcmcia/vx/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-vxpocket-objs := vxpocket.o vxp_ops.o vxp_mixer.o
+
+obj-$(CONFIG_SND_VXPOCKET) += snd-vxpocket.o
diff --git a/linux/sound/pcmcia/vx/vxp_mixer.c b/linux/sound/pcmcia/vx/vxp_mixer.c
new file mode 100644
index 0000000..a4a6642
--- /dev/null
+++ b/linux/sound/pcmcia/vx/vxp_mixer.c
@@ -0,0 +1,151 @@
+/*
+ * Driver for Digigram VXpocket soundcards
+ *
+ * VX-pocket mixer
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include "vxpocket.h"
+
+#define MIC_LEVEL_MIN	0
+#define MIC_LEVEL_MAX	8
+
+/*
+ * mic level control (for VXPocket)
+ */
+static int vx_mic_level_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = MIC_LEVEL_MAX;
+	return 0;
+}
+
+static int vx_mic_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *_chip = snd_kcontrol_chip(kcontrol);
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+	ucontrol->value.integer.value[0] = chip->mic_level;
+	return 0;
+}
+
+static int vx_mic_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *_chip = snd_kcontrol_chip(kcontrol);
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+	unsigned int val = ucontrol->value.integer.value[0];
+
+	if (val > MIC_LEVEL_MAX)
+		return -EINVAL;
+	mutex_lock(&_chip->mixer_mutex);
+	if (chip->mic_level != ucontrol->value.integer.value[0]) {
+		vx_set_mic_level(_chip, ucontrol->value.integer.value[0]);
+		chip->mic_level = ucontrol->value.integer.value[0];
+		mutex_unlock(&_chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&_chip->mixer_mutex);
+	return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_mic, -21, 3, 0);
+
+static struct snd_kcontrol_new vx_control_mic_level = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access =	(SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			 SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.name =		"Mic Capture Volume",
+	.info =		vx_mic_level_info,
+	.get =		vx_mic_level_get,
+	.put =		vx_mic_level_put,
+	.tlv = { .p = db_scale_mic },
+};
+
+/*
+ * mic boost level control (for VXP440)
+ */
+#define vx_mic_boost_info		snd_ctl_boolean_mono_info
+
+static int vx_mic_boost_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *_chip = snd_kcontrol_chip(kcontrol);
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+	ucontrol->value.integer.value[0] = chip->mic_level;
+	return 0;
+}
+
+static int vx_mic_boost_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct vx_core *_chip = snd_kcontrol_chip(kcontrol);
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+	int val = !!ucontrol->value.integer.value[0];
+	mutex_lock(&_chip->mixer_mutex);
+	if (chip->mic_level != val) {
+		vx_set_mic_boost(_chip, val);
+		chip->mic_level = val;
+		mutex_unlock(&_chip->mixer_mutex);
+		return 1;
+	}
+	mutex_unlock(&_chip->mixer_mutex);
+	return 0;
+}
+
+static struct snd_kcontrol_new vx_control_mic_boost = {
+	.iface =	SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name =		"Mic Boost",
+	.info =		vx_mic_boost_info,
+	.get =		vx_mic_boost_get,
+	.put =		vx_mic_boost_put,
+};
+
+
+int vxp_add_mic_controls(struct vx_core *_chip)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+	int err;
+
+	/* mute input levels */
+	chip->mic_level = 0;
+	switch (_chip->type) {
+	case VX_TYPE_VXPOCKET:
+		vx_set_mic_level(_chip, 0);
+		break;
+	case VX_TYPE_VXP440:
+		vx_set_mic_boost(_chip, 0);
+		break;
+	}
+
+	/* mic level */
+	switch (_chip->type) {
+	case VX_TYPE_VXPOCKET:
+		if ((err = snd_ctl_add(_chip->card, snd_ctl_new1(&vx_control_mic_level, chip))) < 0)
+			return err;
+		break;
+	case VX_TYPE_VXP440:
+		if ((err = snd_ctl_add(_chip->card, snd_ctl_new1(&vx_control_mic_boost, chip))) < 0)
+			return err;
+		break;
+	}
+
+	return 0;
+}
+
diff --git a/linux/sound/pcmcia/vx/vxp_ops.c b/linux/sound/pcmcia/vx/vxp_ops.c
new file mode 100644
index 0000000..989e04a
--- /dev/null
+++ b/linux/sound/pcmcia/vx/vxp_ops.c
@@ -0,0 +1,614 @@
+/*
+ * Driver for Digigram VXpocket soundcards
+ *
+ * lowlevel routines for VXpocket soundcards
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <sound/core.h>
+#include <asm/io.h>
+#include "vxpocket.h"
+
+
+static int vxp_reg_offset[VX_REG_MAX] = {
+	[VX_ICR]	= 0x00,		// ICR
+	[VX_CVR]	= 0x01,		// CVR
+	[VX_ISR]	= 0x02,		// ISR
+	[VX_IVR]	= 0x03,		// IVR
+	[VX_RXH]	= 0x05,		// RXH
+	[VX_RXM]	= 0x06,		// RXM
+	[VX_RXL]	= 0x07,		// RXL
+	[VX_DMA]	= 0x04,		// DMA
+	[VX_CDSP]	= 0x08,		// CDSP
+	[VX_LOFREQ]	= 0x09,		// LFREQ
+	[VX_HIFREQ]	= 0x0a,		// HFREQ
+	[VX_DATA]	= 0x0b,		// DATA
+	[VX_MICRO]	= 0x0c,		// MICRO
+	[VX_DIALOG]	= 0x0d,		// DIALOG
+	[VX_CSUER]	= 0x0e,		// CSUER
+	[VX_RUER]	= 0x0f,		// RUER
+};
+
+
+static inline unsigned long vxp_reg_addr(struct vx_core *_chip, int reg)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+	return chip->port + vxp_reg_offset[reg];
+}
+
+/*
+ * snd_vx_inb - read a byte from the register
+ * @offset: register offset
+ */
+static unsigned char vxp_inb(struct vx_core *chip, int offset)
+{
+	return inb(vxp_reg_addr(chip, offset));
+}
+
+/*
+ * snd_vx_outb - write a byte on the register
+ * @offset: the register offset
+ * @val: the value to write
+ */
+static void vxp_outb(struct vx_core *chip, int offset, unsigned char val)
+{
+	outb(val, vxp_reg_addr(chip, offset));
+}
+
+/*
+ * redefine macros to call directly
+ */
+#undef vx_inb
+#define vx_inb(chip,reg)	vxp_inb((struct vx_core *)(chip), VX_##reg)
+#undef vx_outb
+#define vx_outb(chip,reg,val)	vxp_outb((struct vx_core *)(chip), VX_##reg,val)
+
+
+/*
+ * vx_check_magic - check the magic word on xilinx
+ *
+ * returns zero if a magic word is detected, or a negative error code.
+ */
+static int vx_check_magic(struct vx_core *chip)
+{
+	unsigned long end_time = jiffies + HZ / 5;
+	int c;
+	do {
+		c = vx_inb(chip, CDSP);
+		if (c == CDSP_MAGIC)
+			return 0;
+		msleep(10);
+	} while (time_after_eq(end_time, jiffies));
+	snd_printk(KERN_ERR "cannot find xilinx magic word (%x)\n", c);
+	return -EIO;
+}
+
+
+/*
+ * vx_reset_dsp - reset the DSP
+ */
+
+#define XX_DSP_RESET_WAIT_TIME		2	/* ms */
+
+static void vxp_reset_dsp(struct vx_core *_chip)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+
+	/* set the reset dsp bit to 1 */
+	vx_outb(chip, CDSP, chip->regCDSP | VXP_CDSP_DSP_RESET_MASK);
+	vx_inb(chip, CDSP);
+	mdelay(XX_DSP_RESET_WAIT_TIME);
+	/* reset the bit */
+	chip->regCDSP &= ~VXP_CDSP_DSP_RESET_MASK;
+	vx_outb(chip, CDSP, chip->regCDSP);
+	vx_inb(chip, CDSP);
+	mdelay(XX_DSP_RESET_WAIT_TIME);
+}
+
+/*
+ * reset codec bit
+ */
+static void vxp_reset_codec(struct vx_core *_chip)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+
+	/* Set the reset CODEC bit to 1. */
+	vx_outb(chip, CDSP, chip->regCDSP | VXP_CDSP_CODEC_RESET_MASK);
+	vx_inb(chip, CDSP);
+	msleep(10);
+	/* Set the reset CODEC bit to 0. */
+	chip->regCDSP &= ~VXP_CDSP_CODEC_RESET_MASK;
+	vx_outb(chip, CDSP, chip->regCDSP);
+	vx_inb(chip, CDSP);
+	msleep(1);
+}
+
+/*
+ * vx_load_xilinx_binary - load the xilinx binary image
+ * the binary image is the binary array converted from the bitstream file.
+ */
+static int vxp_load_xilinx_binary(struct vx_core *_chip, const struct firmware *fw)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+	unsigned int i;
+	int c;
+	int regCSUER, regRUER;
+	const unsigned char *image;
+	unsigned char data;
+
+	/* Switch to programmation mode */
+	chip->regDIALOG |= VXP_DLG_XILINX_REPROG_MASK;
+	vx_outb(chip, DIALOG, chip->regDIALOG);
+
+	/* Save register CSUER and RUER */
+	regCSUER = vx_inb(chip, CSUER);
+	regRUER = vx_inb(chip, RUER);
+
+	/* reset HF0 and HF1 */
+	vx_outb(chip, ICR, 0);
+
+	/* Wait for answer HF2 equal to 1 */
+	snd_printdd(KERN_DEBUG "check ISR_HF2\n");
+	if (vx_check_isr(_chip, ISR_HF2, ISR_HF2, 20) < 0)
+		goto _error;
+
+	/* set HF1 for loading xilinx binary */
+	vx_outb(chip, ICR, ICR_HF1);
+	image = fw->data;
+	for (i = 0; i < fw->size; i++, image++) {
+		data = *image;
+		if (vx_wait_isr_bit(_chip, ISR_TX_EMPTY) < 0)
+			goto _error;
+		vx_outb(chip, TXL, data);
+		/* wait for reading */
+		if (vx_wait_for_rx_full(_chip) < 0)
+			goto _error;
+		c = vx_inb(chip, RXL);
+		if (c != (int)data)
+			snd_printk(KERN_ERR "vxpocket: load xilinx mismatch at %d: 0x%x != 0x%x\n", i, c, (int)data);
+        }
+
+	/* reset HF1 */
+	vx_outb(chip, ICR, 0);
+
+	/* wait for HF3 */
+	if (vx_check_isr(_chip, ISR_HF3, ISR_HF3, 20) < 0)
+		goto _error;
+
+	/* read the number of bytes received */
+	if (vx_wait_for_rx_full(_chip) < 0)
+		goto _error;
+
+	c = (int)vx_inb(chip, RXH) << 16;
+	c |= (int)vx_inb(chip, RXM) << 8;
+	c |= vx_inb(chip, RXL);
+
+	snd_printdd(KERN_DEBUG "xilinx: dsp size received 0x%x, orig 0x%Zx\n", c, fw->size);
+
+	vx_outb(chip, ICR, ICR_HF0);
+
+	/* TEMPO 250ms : wait until Xilinx is downloaded */
+	msleep(300);
+
+	/* test magical word */
+	if (vx_check_magic(_chip) < 0)
+		goto _error;
+
+	/* Restore register 0x0E and 0x0F (thus replacing COR and FCSR) */
+	vx_outb(chip, CSUER, regCSUER);
+	vx_outb(chip, RUER, regRUER);
+
+	/* Reset the Xilinx's signal enabling IO access */
+	chip->regDIALOG |= VXP_DLG_XILINX_REPROG_MASK;
+	vx_outb(chip, DIALOG, chip->regDIALOG);
+	vx_inb(chip, DIALOG);
+	msleep(10);
+	chip->regDIALOG &= ~VXP_DLG_XILINX_REPROG_MASK;
+	vx_outb(chip, DIALOG, chip->regDIALOG);
+	vx_inb(chip, DIALOG);
+
+	/* Reset of the Codec */
+	vxp_reset_codec(_chip);
+	vx_reset_dsp(_chip);
+
+	return 0;
+
+ _error:
+	vx_outb(chip, CSUER, regCSUER);
+	vx_outb(chip, RUER, regRUER);
+	chip->regDIALOG &= ~VXP_DLG_XILINX_REPROG_MASK;
+	vx_outb(chip, DIALOG, chip->regDIALOG);
+	return -EIO;
+}
+
+
+/*
+ * vxp_load_dsp - load_dsp callback
+ */
+static int vxp_load_dsp(struct vx_core *vx, int index, const struct firmware *fw)
+{
+	int err;
+
+	switch (index) {
+	case 0:
+		/* xilinx boot */
+		if ((err = vx_check_magic(vx)) < 0)
+			return err;
+		if ((err = snd_vx_load_boot_image(vx, fw)) < 0)
+			return err;
+		return 0;
+	case 1:
+		/* xilinx image */
+		return vxp_load_xilinx_binary(vx, fw);
+	case 2:
+		/* DSP boot */
+		return snd_vx_dsp_boot(vx, fw);
+	case 3:
+		/* DSP image */
+		return snd_vx_dsp_load(vx, fw);
+	default:
+		snd_BUG();
+		return -EINVAL;
+	}
+}
+		
+
+/*
+ * vx_test_and_ack - test and acknowledge interrupt
+ *
+ * called from irq hander, too
+ *
+ * spinlock held!
+ */
+static int vxp_test_and_ack(struct vx_core *_chip)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+
+	/* not booted yet? */
+	if (! (_chip->chip_status & VX_STAT_XILINX_LOADED))
+		return -ENXIO;
+
+	if (! (vx_inb(chip, DIALOG) & VXP_DLG_MEMIRQ_MASK))
+		return -EIO;
+	
+	/* ok, interrupts generated, now ack it */
+	/* set ACQUIT bit up and down */
+	vx_outb(chip, DIALOG, chip->regDIALOG | VXP_DLG_ACK_MEMIRQ_MASK);
+	/* useless read just to spend some time and maintain
+	 * the ACQUIT signal up for a while ( a bus cycle )
+	 */
+	vx_inb(chip, DIALOG);
+	vx_outb(chip, DIALOG, chip->regDIALOG & ~VXP_DLG_ACK_MEMIRQ_MASK);
+
+	return 0;
+}
+
+
+/*
+ * vx_validate_irq - enable/disable IRQ
+ */
+static void vxp_validate_irq(struct vx_core *_chip, int enable)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+
+	/* Set the interrupt enable bit to 1 in CDSP register */
+	if (enable)
+		chip->regCDSP |= VXP_CDSP_VALID_IRQ_MASK;
+	else
+		chip->regCDSP &= ~VXP_CDSP_VALID_IRQ_MASK;
+	vx_outb(chip, CDSP, chip->regCDSP);
+}
+
+/*
+ * vx_setup_pseudo_dma - set up the pseudo dma read/write mode.
+ * @do_write: 0 = read, 1 = set up for DMA write
+ */
+static void vx_setup_pseudo_dma(struct vx_core *_chip, int do_write)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+
+	/* Interrupt mode and HREQ pin enabled for host transmit / receive data transfers */
+	vx_outb(chip, ICR, do_write ? ICR_TREQ : ICR_RREQ);
+	/* Reset the pseudo-dma register */
+	vx_inb(chip, ISR);
+	vx_outb(chip, ISR, 0);
+
+	/* Select DMA in read/write transfer mode and in 16-bit accesses */
+	chip->regDIALOG |= VXP_DLG_DMA16_SEL_MASK;
+	chip->regDIALOG |= do_write ? VXP_DLG_DMAWRITE_SEL_MASK : VXP_DLG_DMAREAD_SEL_MASK;
+	vx_outb(chip, DIALOG, chip->regDIALOG);
+
+}
+
+/*
+ * vx_release_pseudo_dma - disable the pseudo-DMA mode
+ */
+static void vx_release_pseudo_dma(struct vx_core *_chip)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+
+	/* Disable DMA and 16-bit accesses */
+	chip->regDIALOG &= ~(VXP_DLG_DMAWRITE_SEL_MASK|
+			     VXP_DLG_DMAREAD_SEL_MASK|
+			     VXP_DLG_DMA16_SEL_MASK);
+	vx_outb(chip, DIALOG, chip->regDIALOG);
+	/* HREQ pin disabled. */
+	vx_outb(chip, ICR, 0);
+}
+
+/*
+ * vx_pseudo_dma_write - write bulk data on pseudo-DMA mode
+ * @count: data length to transfer in bytes
+ *
+ * data size must be aligned to 6 bytes to ensure the 24bit alignment on DSP.
+ * NB: call with a certain lock!
+ */
+static void vxp_dma_write(struct vx_core *chip, struct snd_pcm_runtime *runtime,
+			  struct vx_pipe *pipe, int count)
+{
+	long port = vxp_reg_addr(chip, VX_DMA);
+	int offset = pipe->hw_ptr;
+	unsigned short *addr = (unsigned short *)(runtime->dma_area + offset);
+
+	vx_setup_pseudo_dma(chip, 1);
+	if (offset + count > pipe->buffer_bytes) {
+		int length = pipe->buffer_bytes - offset;
+		count -= length;
+		length >>= 1; /* in 16bit words */
+		/* Transfer using pseudo-dma. */
+		while (length-- > 0) {
+			outw(cpu_to_le16(*addr), port);
+			addr++;
+		}
+		addr = (unsigned short *)runtime->dma_area;
+		pipe->hw_ptr = 0;
+	}
+	pipe->hw_ptr += count;
+	count >>= 1; /* in 16bit words */
+	/* Transfer using pseudo-dma. */
+	while (count-- > 0) {
+		outw(cpu_to_le16(*addr), port);
+		addr++;
+	}
+	vx_release_pseudo_dma(chip);
+}
+
+
+/*
+ * vx_pseudo_dma_read - read bulk data on pseudo DMA mode
+ * @offset: buffer offset in bytes
+ * @count: data length to transfer in bytes
+ *
+ * the read length must be aligned to 6 bytes, as well as write.
+ * NB: call with a certain lock!
+ */
+static void vxp_dma_read(struct vx_core *chip, struct snd_pcm_runtime *runtime,
+			 struct vx_pipe *pipe, int count)
+{
+	struct snd_vxpocket *pchip = (struct snd_vxpocket *)chip;
+	long port = vxp_reg_addr(chip, VX_DMA);
+	int offset = pipe->hw_ptr;
+	unsigned short *addr = (unsigned short *)(runtime->dma_area + offset);
+
+	if (snd_BUG_ON(count % 2))
+		return;
+	vx_setup_pseudo_dma(chip, 0);
+	if (offset + count > pipe->buffer_bytes) {
+		int length = pipe->buffer_bytes - offset;
+		count -= length;
+		length >>= 1; /* in 16bit words */
+		/* Transfer using pseudo-dma. */
+		while (length-- > 0)
+			*addr++ = le16_to_cpu(inw(port));
+		addr = (unsigned short *)runtime->dma_area;
+		pipe->hw_ptr = 0;
+	}
+	pipe->hw_ptr += count;
+	count >>= 1; /* in 16bit words */
+	/* Transfer using pseudo-dma. */
+	while (count-- > 1)
+		*addr++ = le16_to_cpu(inw(port));
+	/* Disable DMA */
+	pchip->regDIALOG &= ~VXP_DLG_DMAREAD_SEL_MASK;
+	vx_outb(chip, DIALOG, pchip->regDIALOG);
+	/* Read the last word (16 bits) */
+	*addr = le16_to_cpu(inw(port));
+	/* Disable 16-bit accesses */
+	pchip->regDIALOG &= ~VXP_DLG_DMA16_SEL_MASK;
+	vx_outb(chip, DIALOG, pchip->regDIALOG);
+	/* HREQ pin disabled. */
+	vx_outb(chip, ICR, 0);
+}
+
+
+/*
+ * write a codec data (24bit)
+ */
+static void vxp_write_codec_reg(struct vx_core *chip, int codec, unsigned int data)
+{
+	int i;
+
+	/* Activate access to the corresponding codec register */
+	if (! codec)
+		vx_inb(chip, LOFREQ);
+	else
+		vx_inb(chip, CODEC2);
+		
+	/* We have to send 24 bits (3 x 8 bits). Start with most signif. Bit */
+	for (i = 0; i < 24; i++, data <<= 1)
+		vx_outb(chip, DATA, ((data & 0x800000) ? VX_DATA_CODEC_MASK : 0));
+	
+	/* Terminate access to codec registers */
+	vx_inb(chip, HIFREQ);
+}
+
+
+/*
+ * vx_set_mic_boost - set mic boost level (on vxp440 only)
+ * @boost: 0 = 20dB, 1 = +38dB
+ */
+void vx_set_mic_boost(struct vx_core *chip, int boost)
+{
+	struct snd_vxpocket *pchip = (struct snd_vxpocket *)chip;
+	unsigned long flags;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	if (pchip->regCDSP & P24_CDSP_MICS_SEL_MASK) {
+		if (boost) {
+			/* boost: 38 dB */
+			pchip->regCDSP &= ~P24_CDSP_MIC20_SEL_MASK;
+			pchip->regCDSP |=  P24_CDSP_MIC38_SEL_MASK;
+		} else {
+			/* minimum value: 20 dB */
+			pchip->regCDSP |=  P24_CDSP_MIC20_SEL_MASK;
+			pchip->regCDSP &= ~P24_CDSP_MIC38_SEL_MASK;
+                }
+		vx_outb(chip, CDSP, pchip->regCDSP);
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+/*
+ * remap the linear value (0-8) to the actual value (0-15)
+ */
+static int vx_compute_mic_level(int level)
+{
+	switch (level) {
+	case 5: level = 6 ; break;
+	case 6: level = 8 ; break;
+	case 7: level = 11; break;
+	case 8: level = 15; break;
+	default: break ;
+	}
+	return level;
+}
+
+/*
+ * vx_set_mic_level - set mic level (on vxpocket only)
+ * @level: the mic level = 0 - 8 (max)
+ */
+void vx_set_mic_level(struct vx_core *chip, int level)
+{
+	struct snd_vxpocket *pchip = (struct snd_vxpocket *)chip;
+	unsigned long flags;
+
+	if (chip->chip_status & VX_STAT_IS_STALE)
+		return;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	if (pchip->regCDSP & VXP_CDSP_MIC_SEL_MASK) {
+		level = vx_compute_mic_level(level);
+		vx_outb(chip, MICRO, level);
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+
+/*
+ * change the input audio source
+ */
+static void vxp_change_audio_source(struct vx_core *_chip, int src)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+
+	switch (src) {
+	case VX_AUDIO_SRC_DIGITAL:
+		chip->regCDSP |= VXP_CDSP_DATAIN_SEL_MASK;
+		vx_outb(chip, CDSP, chip->regCDSP);
+		break;
+	case VX_AUDIO_SRC_LINE:
+		chip->regCDSP &= ~VXP_CDSP_DATAIN_SEL_MASK;
+		if (_chip->type == VX_TYPE_VXP440)
+			chip->regCDSP &= ~P24_CDSP_MICS_SEL_MASK;
+		else
+			chip->regCDSP &= ~VXP_CDSP_MIC_SEL_MASK;
+		vx_outb(chip, CDSP, chip->regCDSP);
+		break;
+	case VX_AUDIO_SRC_MIC:
+		chip->regCDSP &= ~VXP_CDSP_DATAIN_SEL_MASK;
+		/* reset mic levels */
+		if (_chip->type == VX_TYPE_VXP440) {
+			chip->regCDSP &= ~P24_CDSP_MICS_SEL_MASK;
+			if (chip->mic_level)
+				chip->regCDSP |=  P24_CDSP_MIC38_SEL_MASK;
+			else
+				chip->regCDSP |= P24_CDSP_MIC20_SEL_MASK;
+			vx_outb(chip, CDSP, chip->regCDSP);
+		} else {
+			chip->regCDSP |= VXP_CDSP_MIC_SEL_MASK;
+			vx_outb(chip, CDSP, chip->regCDSP);
+			vx_outb(chip, MICRO, vx_compute_mic_level(chip->mic_level));
+		}
+		break;
+	}
+}
+
+/*
+ * change the clock source
+ * source = INTERNAL_QUARTZ or UER_SYNC
+ */
+static void vxp_set_clock_source(struct vx_core *_chip, int source)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+
+	if (source == INTERNAL_QUARTZ)
+		chip->regCDSP &= ~VXP_CDSP_CLOCKIN_SEL_MASK;
+	else
+		chip->regCDSP |= VXP_CDSP_CLOCKIN_SEL_MASK;
+	vx_outb(chip, CDSP, chip->regCDSP);
+}
+
+
+/*
+ * reset the board
+ */
+static void vxp_reset_board(struct vx_core *_chip, int cold_reset)
+{
+	struct snd_vxpocket *chip = (struct snd_vxpocket *)_chip;
+
+	chip->regCDSP = 0;
+	chip->regDIALOG = 0;
+}
+
+
+/*
+ * callbacks
+ */
+/* exported */
+struct snd_vx_ops snd_vxpocket_ops = {
+	.in8 = vxp_inb,
+	.out8 = vxp_outb,
+	.test_and_ack = vxp_test_and_ack,
+	.validate_irq = vxp_validate_irq,
+	.write_codec = vxp_write_codec_reg,
+	.reset_codec = vxp_reset_codec,
+	.change_audio_source = vxp_change_audio_source,
+	.set_clock_source = vxp_set_clock_source,
+	.load_dsp = vxp_load_dsp,
+	.add_controls = vxp_add_mic_controls,
+	.reset_dsp = vxp_reset_dsp,
+	.reset_board = vxp_reset_board,
+	.dma_write = vxp_dma_write,
+	.dma_read = vxp_dma_read,
+};
diff --git a/linux/sound/pcmcia/vx/vxpocket.c b/linux/sound/pcmcia/vx/vxpocket.c
new file mode 100644
index 0000000..80000d6
--- /dev/null
+++ b/linux/sound/pcmcia/vx/vxpocket.c
@@ -0,0 +1,382 @@
+/*
+ * Driver for Digigram VXpocket V2/440 soundcards
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "vxpocket.h"
+#include <pcmcia/ciscode.h>
+#include <pcmcia/cisreg.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+/*
+ */
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("Digigram VXPocket");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Digigram,VXPocket},{Digigram,VXPocket440}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable switches */
+static int ibl[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for VXPocket soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for VXPocket soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable VXPocket soundcard.");
+module_param_array(ibl, int, NULL, 0444);
+MODULE_PARM_DESC(ibl, "Capture IBL size for VXPocket soundcard.");
+ 
+
+/*
+ */
+
+static unsigned int card_alloc;
+
+
+/*
+ */
+static void vxpocket_release(struct pcmcia_device *link)
+{
+	pcmcia_disable_device(link);
+}
+
+/*
+ * destructor, called from snd_card_free_when_closed()
+ */
+static int snd_vxpocket_dev_free(struct snd_device *device)
+{
+	struct vx_core *chip = device->device_data;
+
+	snd_vx_free_firmware(chip);
+	kfree(chip);
+	return 0;
+}
+
+
+/*
+ * Hardware information
+ */
+
+/* VX-pocket V2
+ *
+ * 1 DSP, 1 sync UER
+ * 1 programmable clock (NIY)
+ * 1 stereo analog input (line/micro)
+ * 1 stereo analog output
+ * Only output levels can be modified
+ */
+
+static const DECLARE_TLV_DB_SCALE(db_scale_old_vol, -11350, 50, 0);
+
+static struct snd_vx_hardware vxpocket_hw = {
+	.name = "VXPocket",
+	.type = VX_TYPE_VXPOCKET,
+
+	/* hardware specs */
+	.num_codecs = 1,
+	.num_ins = 1,
+	.num_outs = 1,
+	.output_level_max = VX_ANALOG_OUT_LEVEL_MAX,
+	.output_level_db_scale = db_scale_old_vol,
+};	
+
+/* VX-pocket 440
+ *
+ * 1 DSP, 1 sync UER, 1 sync World Clock (NIY)
+ * SMPTE (NIY)
+ * 2 stereo analog input (line/micro)
+ * 2 stereo analog output
+ * Only output levels can be modified
+ * UER, but only for the first two inputs and outputs.
+ */
+
+static struct snd_vx_hardware vxp440_hw = {
+	.name = "VXPocket440",
+	.type = VX_TYPE_VXP440,
+
+	/* hardware specs */
+	.num_codecs = 2,
+	.num_ins = 2,
+	.num_outs = 2,
+	.output_level_max = VX_ANALOG_OUT_LEVEL_MAX,
+	.output_level_db_scale = db_scale_old_vol,
+};	
+
+
+/*
+ * create vxpocket instance
+ */
+static int snd_vxpocket_new(struct snd_card *card, int ibl,
+			    struct pcmcia_device *link,
+			    struct snd_vxpocket **chip_ret)
+{
+	struct vx_core *chip;
+	struct snd_vxpocket *vxp;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_vxpocket_dev_free,
+	};
+	int err;
+
+	chip = snd_vx_create(card, &vxpocket_hw, &snd_vxpocket_ops,
+			     sizeof(struct snd_vxpocket) - sizeof(struct vx_core));
+	if (!chip)
+		return -ENOMEM;
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		kfree(chip);
+		return err;
+	}
+	chip->ibl.size = ibl;
+
+	vxp = (struct snd_vxpocket *)chip;
+
+	vxp->p_dev = link;
+	link->priv = chip;
+
+	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
+	link->resource[0]->end = 16;
+
+	link->config_flags |= CONF_ENABLE_IRQ;
+	link->config_index = 1;
+	link->config_regs = PRESENT_OPTION;
+
+	*chip_ret = vxp;
+	return 0;
+}
+
+
+/**
+ * snd_vxpocket_assign_resources - initialize the hardware and card instance.
+ * @port: i/o port for the card
+ * @irq: irq number for the card
+ *
+ * this function assigns the specified port and irq, boot the card,
+ * create pcm and control instances, and initialize the rest hardware.
+ *
+ * returns 0 if successful, or a negative error code.
+ */
+static int snd_vxpocket_assign_resources(struct vx_core *chip, int port, int irq)
+{
+	int err;
+	struct snd_card *card = chip->card;
+	struct snd_vxpocket *vxp = (struct snd_vxpocket *)chip;
+
+	snd_printdd(KERN_DEBUG "vxpocket assign resources: port = 0x%x, irq = %d\n", port, irq);
+	vxp->port = port;
+
+	sprintf(card->shortname, "Digigram %s", card->driver);
+	sprintf(card->longname, "%s at 0x%x, irq %i",
+		card->shortname, port, irq);
+
+	chip->irq = irq;
+
+	if ((err = snd_vx_setup_firmware(chip)) < 0)
+		return err;
+
+	return 0;
+}
+
+
+/*
+ * configuration callback
+ */
+
+static int vxpocket_config(struct pcmcia_device *link)
+{
+	struct vx_core *chip = link->priv;
+	int ret;
+
+	snd_printdd(KERN_DEBUG "vxpocket_config called\n");
+
+	/* redefine hardware record according to the VERSION1 string */
+	if (!strcmp(link->prod_id[1], "VX-POCKET")) {
+		snd_printdd("VX-pocket is detected\n");
+	} else {
+		snd_printdd("VX-pocket 440 is detected\n");
+		/* overwrite the hardware information */
+		chip->hw = &vxp440_hw;
+		chip->type = vxp440_hw.type;
+		strcpy(chip->card->driver, vxp440_hw.name);
+	}
+
+	ret = pcmcia_request_io(link);
+	if (ret)
+		goto failed;
+
+	ret = pcmcia_request_exclusive_irq(link, snd_vx_irq_handler);
+	if (ret)
+		goto failed;
+
+	ret = pcmcia_enable_device(link);
+	if (ret)
+		goto failed;
+
+	chip->dev = &link->dev;
+	snd_card_set_dev(chip->card, chip->dev);
+
+	if (snd_vxpocket_assign_resources(chip, link->resource[0]->start,
+						link->irq) < 0)
+		goto failed;
+
+	return 0;
+
+failed:
+	pcmcia_disable_device(link);
+	return -ENODEV;
+}
+
+#ifdef CONFIG_PM
+
+static int vxp_suspend(struct pcmcia_device *link)
+{
+	struct vx_core *chip = link->priv;
+
+	snd_printdd(KERN_DEBUG "SUSPEND\n");
+	if (chip) {
+		snd_printdd(KERN_DEBUG "snd_vx_suspend calling\n");
+		snd_vx_suspend(chip, PMSG_SUSPEND);
+	}
+
+	return 0;
+}
+
+static int vxp_resume(struct pcmcia_device *link)
+{
+	struct vx_core *chip = link->priv;
+
+	snd_printdd(KERN_DEBUG "RESUME\n");
+	if (pcmcia_dev_present(link)) {
+		//struct snd_vxpocket *vxp = (struct snd_vxpocket *)chip;
+		if (chip) {
+			snd_printdd(KERN_DEBUG "calling snd_vx_resume\n");
+			snd_vx_resume(chip);
+		}
+	}
+	snd_printdd(KERN_DEBUG "resume done!\n");
+
+	return 0;
+}
+
+#endif
+
+
+/*
+ */
+static int vxpocket_probe(struct pcmcia_device *p_dev)
+{
+	struct snd_card *card;
+	struct snd_vxpocket *vxp;
+	int i, err;
+
+	/* find an empty slot from the card list */
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (!(card_alloc & (1 << i)))
+			break;
+	}
+	if (i >= SNDRV_CARDS) {
+		snd_printk(KERN_ERR "vxpocket: too many cards found\n");
+		return -EINVAL;
+	}
+	if (! enable[i])
+		return -ENODEV; /* disabled explicitly */
+
+	/* ok, create a card instance */
+	err = snd_card_create(index[i], id[i], THIS_MODULE, 0, &card);
+	if (err < 0) {
+		snd_printk(KERN_ERR "vxpocket: cannot create a card instance\n");
+		return err;
+	}
+
+	err = snd_vxpocket_new(card, ibl[i], p_dev, &vxp);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	card->private_data = vxp;
+
+	vxp->index = i;
+	card_alloc |= 1 << i;
+
+	vxp->p_dev = p_dev;
+
+	return vxpocket_config(p_dev);
+}
+
+static void vxpocket_detach(struct pcmcia_device *link)
+{
+	struct snd_vxpocket *vxp;
+	struct vx_core *chip;
+
+	if (! link)
+		return;
+
+	vxp = link->priv;
+	chip = (struct vx_core *)vxp;
+	card_alloc &= ~(1 << vxp->index);
+
+	chip->chip_status |= VX_STAT_IS_STALE; /* to be sure */
+	snd_card_disconnect(chip->card);
+	vxpocket_release(link);
+	snd_card_free_when_closed(chip->card);
+}
+
+/*
+ * Module entry points
+ */
+
+static struct pcmcia_device_id vxp_ids[] = {
+	PCMCIA_DEVICE_MANF_CARD(0x01f1, 0x0100),
+	PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, vxp_ids);
+
+static struct pcmcia_driver vxp_cs_driver = {
+	.owner		= THIS_MODULE,
+	.name		= "snd-vxpocket",
+	.probe		= vxpocket_probe,
+	.remove		= vxpocket_detach,
+	.id_table	= vxp_ids,
+#ifdef CONFIG_PM
+	.suspend	= vxp_suspend,
+	.resume		= vxp_resume,
+#endif
+};
+
+static int __init init_vxpocket(void)
+{
+	return pcmcia_register_driver(&vxp_cs_driver);
+}
+
+static void __exit exit_vxpocket(void)
+{
+	pcmcia_unregister_driver(&vxp_cs_driver);
+}
+
+module_init(init_vxpocket);
+module_exit(exit_vxpocket);
diff --git a/linux/sound/pcmcia/vx/vxpocket.h b/linux/sound/pcmcia/vx/vxpocket.h
new file mode 100644
index 0000000..13d658c
--- /dev/null
+++ b/linux/sound/pcmcia/vx/vxpocket.h
@@ -0,0 +1,90 @@
+/*
+ * Driver for Digigram VXpocket soundcards
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#ifndef __VXPOCKET_H
+#define __VXPOCKET_H
+
+#include <sound/vx_core.h>
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+struct snd_vxpocket {
+
+	struct vx_core core;
+
+	unsigned long port;
+
+	int mic_level;	/* analog mic level (or boost) */
+
+	unsigned int regCDSP;	/* current CDSP register */
+	unsigned int regDIALOG;	/* current DIALOG register */
+
+	int index;	/* card index */
+
+	/* pcmcia stuff */
+	struct pcmcia_device	*p_dev;
+};
+
+extern struct snd_vx_ops snd_vxpocket_ops;
+
+void vx_set_mic_boost(struct vx_core *chip, int boost);
+void vx_set_mic_level(struct vx_core *chip, int level);
+
+int vxp_add_mic_controls(struct vx_core *chip);
+
+/* Constants used to access the CDSP register (0x08). */
+#define CDSP_MAGIC	0xA7	/* magic value (for read) */
+/* for write */
+#define VXP_CDSP_CLOCKIN_SEL_MASK	0x80	/* 0 (internal), 1 (AES/EBU) */
+#define VXP_CDSP_DATAIN_SEL_MASK	0x40	/* 0 (analog), 1 (UER) */
+#define VXP_CDSP_SMPTE_SEL_MASK		0x20
+#define VXP_CDSP_RESERVED_MASK		0x10
+#define VXP_CDSP_MIC_SEL_MASK		0x08
+#define VXP_CDSP_VALID_IRQ_MASK		0x04
+#define VXP_CDSP_CODEC_RESET_MASK	0x02
+#define VXP_CDSP_DSP_RESET_MASK		0x01
+/* VXPOCKET 240/440 */
+#define P24_CDSP_MICS_SEL_MASK		0x18
+#define P24_CDSP_MIC20_SEL_MASK		0x10
+#define P24_CDSP_MIC38_SEL_MASK		0x08
+
+/* Constants used to access the MEMIRQ register (0x0C). */
+#define P44_MEMIRQ_MASTER_SLAVE_SEL_MASK 0x08
+#define P44_MEMIRQ_SYNCED_ALONE_SEL_MASK 0x04
+#define P44_MEMIRQ_WCLK_OUT_IN_SEL_MASK  0x02 /* Not used */
+#define P44_MEMIRQ_WCLK_UER_SEL_MASK     0x01 /* Not used */
+
+/* Micro levels (0x0C) */
+
+/* Constants used to access the DIALOG register (0x0D). */
+#define VXP_DLG_XILINX_REPROG_MASK	0x80	/* W */
+#define VXP_DLG_DATA_XICOR_MASK		0x80	/* R */
+#define VXP_DLG_RESERVED4_0_MASK	0x40
+#define VXP_DLG_RESERVED2_0_MASK	0x20
+#define VXP_DLG_RESERVED1_0_MASK	0x10
+#define VXP_DLG_DMAWRITE_SEL_MASK	0x08	/* W */
+#define VXP_DLG_DMAREAD_SEL_MASK	0x04	/* W */
+#define VXP_DLG_MEMIRQ_MASK		0x02	/* R */
+#define VXP_DLG_DMA16_SEL_MASK		0x02	/* W */
+#define VXP_DLG_ACK_MEMIRQ_MASK		0x01	/* R/W */
+
+
+#endif /* __VXPOCKET_H */
diff --git a/linux/sound/ppc/Kconfig b/linux/sound/ppc/Kconfig
new file mode 100644
index 0000000..0519c60
--- /dev/null
+++ b/linux/sound/ppc/Kconfig
@@ -0,0 +1,52 @@
+# ALSA PowerMac drivers
+
+menuconfig SND_PPC
+	bool "PowerPC sound devices"
+	depends on PPC
+	default y
+	help
+	  Support for sound devices specific to PowerPC architectures.
+
+if SND_PPC
+
+config SND_POWERMAC
+	tristate "PowerMac (AWACS, DACA, Burgundy, Tumbler, Keywest)"
+	depends on I2C && INPUT && PPC_PMAC
+	select SND_PCM
+	select SND_VMASTER
+	help
+	  Say Y here to include support for the integrated sound device.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-powermac.
+
+config SND_POWERMAC_AUTO_DRC
+	bool "Toggle DRC automatically at headphone/line plug-in"
+	depends on SND_POWERMAC
+	default y
+	help
+	  Say Y here to enable the automatic toggle of DRC (dynamic
+	  range compression) on Tumbler/Snapper.
+	  If this feature is enabled, DRC is turned off when the
+	  headphone/line jack is plugged, and turned on when unplugged.
+
+	  Note that you can turn on/off DRC manually even without this
+	  option.
+
+config SND_PS3
+	tristate "PS3 Audio support"
+	depends on PS3_PS3AV
+	select SND_PCM
+	default m
+	help
+	  Say Y here to include support for audio on the PS3
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd_ps3.
+
+config SND_PS3_DEFAULT_START_DELAY
+	int "Startup delay time in ms"
+	depends on SND_PS3
+	default "2000"
+
+endif	# SND_PPC
diff --git a/linux/sound/ppc/Makefile b/linux/sound/ppc/Makefile
new file mode 100644
index 0000000..679c45a
--- /dev/null
+++ b/linux/sound/ppc/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-powermac-objs := powermac.o pmac.o awacs.o burgundy.o daca.o tumbler.o keywest.o beep.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_POWERMAC)	+= snd-powermac.o
+obj-$(CONFIG_SND_PS3)		+= snd_ps3.o
diff --git a/linux/sound/ppc/awacs.c b/linux/sound/ppc/awacs.c
new file mode 100644
index 0000000..b366793
--- /dev/null
+++ b/linux/sound/ppc/awacs.c
@@ -0,0 +1,1146 @@
+/*
+ * PMac AWACS lowlevel functions
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ * code based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#include <asm/io.h>
+#include <asm/nvram.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "pmac.h"
+
+
+#ifdef CONFIG_ADB_CUDA
+#define PMAC_AMP_AVAIL
+#endif
+
+#ifdef PMAC_AMP_AVAIL
+struct awacs_amp {
+	unsigned char amp_master;
+	unsigned char amp_vol[2][2];
+	unsigned char amp_tone[2];
+};
+
+#define CHECK_CUDA_AMP() (sys_ctrler == SYS_CTRLER_CUDA)
+
+#endif /* PMAC_AMP_AVAIL */
+
+
+static void snd_pmac_screamer_wait(struct snd_pmac *chip)
+{
+	long timeout = 2000;
+	while (!(in_le32(&chip->awacs->codec_stat) & MASK_VALID)) {
+		mdelay(1);
+		if (! --timeout) {
+			snd_printd("snd_pmac_screamer_wait timeout\n");
+			break;
+		}
+	}
+}
+
+/*
+ * write AWACS register
+ */
+static void
+snd_pmac_awacs_write(struct snd_pmac *chip, int val)
+{
+	long timeout = 5000000;
+
+	if (chip->model == PMAC_SCREAMER)
+		snd_pmac_screamer_wait(chip);
+	out_le32(&chip->awacs->codec_ctrl, val | (chip->subframe << 22));
+	while (in_le32(&chip->awacs->codec_ctrl) & MASK_NEWECMD) {
+		if (! --timeout) {
+			snd_printd("snd_pmac_awacs_write timeout\n");
+			break;
+		}
+	}
+}
+
+static void
+snd_pmac_awacs_write_reg(struct snd_pmac *chip, int reg, int val)
+{
+	snd_pmac_awacs_write(chip, val | (reg << 12));
+	chip->awacs_reg[reg] = val;
+}
+
+static void
+snd_pmac_awacs_write_noreg(struct snd_pmac *chip, int reg, int val)
+{
+	snd_pmac_awacs_write(chip, val | (reg << 12));
+}
+
+#ifdef CONFIG_PM
+/* Recalibrate chip */
+static void screamer_recalibrate(struct snd_pmac *chip)
+{
+	if (chip->model != PMAC_SCREAMER)
+		return;
+
+	/* Sorry for the horrible delays... I hope to get that improved
+	 * by making the whole PM process asynchronous in a future version
+	 */
+	snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]);
+	if (chip->manufacturer == 0x1)
+		/* delay for broken crystal part */
+		msleep(750);
+	snd_pmac_awacs_write_noreg(chip, 1,
+				   chip->awacs_reg[1] | MASK_RECALIBRATE |
+				   MASK_CMUTE | MASK_AMUTE);
+	snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]);
+	snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]);
+}
+
+#else
+#define screamer_recalibrate(chip) /* NOP */
+#endif
+
+
+/*
+ * additional callback to set the pcm format
+ */
+static void snd_pmac_awacs_set_format(struct snd_pmac *chip)
+{
+	chip->awacs_reg[1] &= ~MASK_SAMPLERATE;
+	chip->awacs_reg[1] |= chip->rate_index << 3;
+	snd_pmac_awacs_write_reg(chip, 1, chip->awacs_reg[1]);
+}
+
+
+/*
+ * AWACS volume callbacks
+ */
+/*
+ * volumes: 0-15 stereo
+ */
+static int snd_pmac_awacs_info_volume(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 15;
+	return 0;
+}
+
+static int snd_pmac_awacs_get_volume(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int lshift = (kcontrol->private_value >> 8) & 0xff;
+	int inverted = (kcontrol->private_value >> 16) & 1;
+	unsigned long flags;
+	int vol[2];
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	vol[0] = (chip->awacs_reg[reg] >> lshift) & 0xf;
+	vol[1] = chip->awacs_reg[reg] & 0xf;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (inverted) {
+		vol[0] = 0x0f - vol[0];
+		vol[1] = 0x0f - vol[1];
+	}
+	ucontrol->value.integer.value[0] = vol[0];
+	ucontrol->value.integer.value[1] = vol[1];
+	return 0;
+}
+
+static int snd_pmac_awacs_put_volume(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int lshift = (kcontrol->private_value >> 8) & 0xff;
+	int inverted = (kcontrol->private_value >> 16) & 1;
+	int val, oldval;
+	unsigned long flags;
+	unsigned int vol[2];
+
+	vol[0] = ucontrol->value.integer.value[0];
+	vol[1] = ucontrol->value.integer.value[1];
+	if (vol[0] > 0x0f || vol[1] > 0x0f)
+		return -EINVAL;
+	if (inverted) {
+		vol[0] = 0x0f - vol[0];
+		vol[1] = 0x0f - vol[1];
+	}
+	vol[0] &= 0x0f;
+	vol[1] &= 0x0f;
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	oldval = chip->awacs_reg[reg];
+	val = oldval & ~(0xf | (0xf << lshift));
+	val |= vol[0] << lshift;
+	val |= vol[1];
+	if (oldval != val)
+		snd_pmac_awacs_write_reg(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return oldval != reg;
+}
+
+
+#define AWACS_VOLUME(xname, xreg, xshift, xinverted) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \
+  .info = snd_pmac_awacs_info_volume, \
+  .get = snd_pmac_awacs_get_volume, \
+  .put = snd_pmac_awacs_put_volume, \
+  .private_value = (xreg) | ((xshift) << 8) | ((xinverted) << 16) }
+
+/*
+ * mute master/ogain for AWACS: mono
+ */
+static int snd_pmac_awacs_get_switch(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int invert = (kcontrol->private_value >> 16) & 1;
+	int val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = (chip->awacs_reg[reg] >> shift) & 1;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (invert)
+		val = 1 - val;
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int snd_pmac_awacs_put_switch(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int invert = (kcontrol->private_value >> 16) & 1;
+	int mask = 1 << shift;
+	int val, changed;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val = chip->awacs_reg[reg] & ~mask;
+	if (ucontrol->value.integer.value[0] != invert)
+		val |= mask;
+	changed = chip->awacs_reg[reg] != val;
+	if (changed)
+		snd_pmac_awacs_write_reg(chip, reg, val);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return changed;
+}
+
+#define AWACS_SWITCH(xname, xreg, xshift, xinvert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \
+  .info = snd_pmac_boolean_mono_info, \
+  .get = snd_pmac_awacs_get_switch, \
+  .put = snd_pmac_awacs_put_switch, \
+  .private_value = (xreg) | ((xshift) << 8) | ((xinvert) << 16) }
+
+
+#ifdef PMAC_AMP_AVAIL
+/*
+ * controls for perch/whisper extension cards, e.g. G3 desktop
+ *
+ * TDA7433 connected via i2c address 0x45 (= 0x8a),
+ * accessed through cuda
+ */
+static void awacs_set_cuda(int reg, int val)
+{
+	struct adb_request req;
+	cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, 0x8a,
+			reg, val);
+	while (! req.complete)
+		cuda_poll();
+}
+
+/*
+ * level = 0 - 14, 7 = 0 dB
+ */
+static void awacs_amp_set_tone(struct awacs_amp *amp, int bass, int treble)
+{
+	amp->amp_tone[0] = bass;
+	amp->amp_tone[1] = treble;
+	if (bass > 7)
+		bass = (14 - bass) + 8;
+	if (treble > 7)
+		treble = (14 - treble) + 8;
+	awacs_set_cuda(2, (bass << 4) | treble);
+}
+
+/*
+ * vol = 0 - 31 (attenuation), 32 = mute bit, stereo
+ */
+static int awacs_amp_set_vol(struct awacs_amp *amp, int index,
+			     int lvol, int rvol, int do_check)
+{
+	if (do_check && amp->amp_vol[index][0] == lvol &&
+			amp->amp_vol[index][1] == rvol)
+		return 0;
+	awacs_set_cuda(3 + index, lvol);
+	awacs_set_cuda(5 + index, rvol);
+	amp->amp_vol[index][0] = lvol;
+	amp->amp_vol[index][1] = rvol;
+	return 1;
+}
+
+/*
+ * 0 = -79 dB, 79 = 0 dB, 99 = +20 dB
+ */
+static void awacs_amp_set_master(struct awacs_amp *amp, int vol)
+{
+	amp->amp_master = vol;
+	if (vol <= 79)
+		vol = 32 + (79 - vol);
+	else
+		vol = 32 - (vol - 79);
+	awacs_set_cuda(1, vol);
+}
+
+static void awacs_amp_free(struct snd_pmac *chip)
+{
+	struct awacs_amp *amp = chip->mixer_data;
+	if (!amp)
+		return;
+	kfree(amp);
+	chip->mixer_data = NULL;
+	chip->mixer_free = NULL;
+}
+
+
+/*
+ * mixer controls
+ */
+static int snd_pmac_awacs_info_volume_amp(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 31;
+	return 0;
+}
+
+static int snd_pmac_awacs_get_volume_amp(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	struct awacs_amp *amp = chip->mixer_data;
+
+	ucontrol->value.integer.value[0] = 31 - (amp->amp_vol[index][0] & 31);
+	ucontrol->value.integer.value[1] = 31 - (amp->amp_vol[index][1] & 31);
+	return 0;
+}
+
+static int snd_pmac_awacs_put_volume_amp(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	int vol[2];
+	struct awacs_amp *amp = chip->mixer_data;
+
+	vol[0] = (31 - (ucontrol->value.integer.value[0] & 31))
+		| (amp->amp_vol[index][0] & 32);
+	vol[1] = (31 - (ucontrol->value.integer.value[1] & 31))
+		| (amp->amp_vol[index][1] & 32);
+	return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1);
+}
+
+static int snd_pmac_awacs_get_switch_amp(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	struct awacs_amp *amp = chip->mixer_data;
+
+	ucontrol->value.integer.value[0] = (amp->amp_vol[index][0] & 32)
+					? 0 : 1;
+	ucontrol->value.integer.value[1] = (amp->amp_vol[index][1] & 32)
+					? 0 : 1;
+	return 0;
+}
+
+static int snd_pmac_awacs_put_switch_amp(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	int vol[2];
+	struct awacs_amp *amp = chip->mixer_data;
+
+	vol[0] = (ucontrol->value.integer.value[0] ? 0 : 32)
+		| (amp->amp_vol[index][0] & 31);
+	vol[1] = (ucontrol->value.integer.value[1] ? 0 : 32)
+		| (amp->amp_vol[index][1] & 31);
+	return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1);
+}
+
+static int snd_pmac_awacs_info_tone_amp(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 14;
+	return 0;
+}
+
+static int snd_pmac_awacs_get_tone_amp(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	struct awacs_amp *amp = chip->mixer_data;
+
+	ucontrol->value.integer.value[0] = amp->amp_tone[index];
+	return 0;
+}
+
+static int snd_pmac_awacs_put_tone_amp(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	struct awacs_amp *amp = chip->mixer_data;
+	unsigned int val;
+
+	val = ucontrol->value.integer.value[0];
+	if (val > 14)
+		return -EINVAL;
+	if (val != amp->amp_tone[index]) {
+		amp->amp_tone[index] = val;
+		awacs_amp_set_tone(amp, amp->amp_tone[0], amp->amp_tone[1]);
+		return 1;
+	}
+	return 0;
+}
+
+static int snd_pmac_awacs_info_master_amp(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 99;
+	return 0;
+}
+
+static int snd_pmac_awacs_get_master_amp(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct awacs_amp *amp = chip->mixer_data;
+
+	ucontrol->value.integer.value[0] = amp->amp_master;
+	return 0;
+}
+
+static int snd_pmac_awacs_put_master_amp(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct awacs_amp *amp = chip->mixer_data;
+	unsigned int val;
+
+	val = ucontrol->value.integer.value[0];
+	if (val > 99)
+		return -EINVAL;
+	if (val != amp->amp_master) {
+		amp->amp_master = val;
+		awacs_amp_set_master(amp, amp->amp_master);
+		return 1;
+	}
+	return 0;
+}
+
+#define AMP_CH_SPK	0
+#define AMP_CH_HD	1
+
+static struct snd_kcontrol_new snd_pmac_awacs_amp_vol[] __devinitdata = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Speaker Playback Volume",
+	  .info = snd_pmac_awacs_info_volume_amp,
+	  .get = snd_pmac_awacs_get_volume_amp,
+	  .put = snd_pmac_awacs_put_volume_amp,
+	  .private_value = AMP_CH_SPK,
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Headphone Playback Volume",
+	  .info = snd_pmac_awacs_info_volume_amp,
+	  .get = snd_pmac_awacs_get_volume_amp,
+	  .put = snd_pmac_awacs_put_volume_amp,
+	  .private_value = AMP_CH_HD,
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Tone Control - Bass",
+	  .info = snd_pmac_awacs_info_tone_amp,
+	  .get = snd_pmac_awacs_get_tone_amp,
+	  .put = snd_pmac_awacs_put_tone_amp,
+	  .private_value = 0,
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Tone Control - Treble",
+	  .info = snd_pmac_awacs_info_tone_amp,
+	  .get = snd_pmac_awacs_get_tone_amp,
+	  .put = snd_pmac_awacs_put_tone_amp,
+	  .private_value = 1,
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Amp Master Playback Volume",
+	  .info = snd_pmac_awacs_info_master_amp,
+	  .get = snd_pmac_awacs_get_master_amp,
+	  .put = snd_pmac_awacs_put_master_amp,
+	},
+};
+
+static struct snd_kcontrol_new snd_pmac_awacs_amp_hp_sw __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Headphone Playback Switch",
+	.info = snd_pmac_boolean_stereo_info,
+	.get = snd_pmac_awacs_get_switch_amp,
+	.put = snd_pmac_awacs_put_switch_amp,
+	.private_value = AMP_CH_HD,
+};
+
+static struct snd_kcontrol_new snd_pmac_awacs_amp_spk_sw __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Speaker Playback Switch",
+	.info = snd_pmac_boolean_stereo_info,
+	.get = snd_pmac_awacs_get_switch_amp,
+	.put = snd_pmac_awacs_put_switch_amp,
+	.private_value = AMP_CH_SPK,
+};
+
+#endif /* PMAC_AMP_AVAIL */
+
+
+/*
+ * mic boost for screamer
+ */
+static int snd_pmac_screamer_mic_boost_info(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 3;
+	return 0;
+}
+
+static int snd_pmac_screamer_mic_boost_get(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int val = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->awacs_reg[6] & MASK_MIC_BOOST)
+		val |= 2;
+	if (chip->awacs_reg[0] & MASK_GAINLINE)
+		val |= 1;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int snd_pmac_screamer_mic_boost_put(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	int changed = 0;
+	int val0, val6;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	val0 = chip->awacs_reg[0] & ~MASK_GAINLINE;
+	val6 = chip->awacs_reg[6] & ~MASK_MIC_BOOST;
+	if (ucontrol->value.integer.value[0] & 1)
+		val0 |= MASK_GAINLINE;
+	if (ucontrol->value.integer.value[0] & 2)
+		val6 |= MASK_MIC_BOOST;
+	if (val0 != chip->awacs_reg[0]) {
+		snd_pmac_awacs_write_reg(chip, 0, val0);
+		changed = 1;
+	}
+	if (val6 != chip->awacs_reg[6]) {
+		snd_pmac_awacs_write_reg(chip, 6, val6);
+		changed = 1;
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return changed;
+}
+
+/*
+ * lists of mixer elements
+ */
+static struct snd_kcontrol_new snd_pmac_awacs_mixers[] __devinitdata = {
+	AWACS_SWITCH("Master Capture Switch", 1, SHIFT_LOOPTHRU, 0),
+	AWACS_VOLUME("Master Capture Volume", 0, 4, 0),
+/*	AWACS_SWITCH("Unknown Playback Switch", 6, SHIFT_PAROUT0, 0), */
+};
+
+static struct snd_kcontrol_new snd_pmac_screamer_mixers_beige[] __devinitdata = {
+	AWACS_VOLUME("Master Playback Volume", 2, 6, 1),
+	AWACS_VOLUME("Play-through Playback Volume", 5, 6, 1),
+	AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0),
+	AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_LINE, 0),
+};
+
+static struct snd_kcontrol_new snd_pmac_screamer_mixers_lo[] __devinitdata = {
+	AWACS_VOLUME("Line out Playback Volume", 2, 6, 1),
+};
+
+static struct snd_kcontrol_new snd_pmac_screamer_mixers_imac[] __devinitdata = {
+	AWACS_VOLUME("Play-through Playback Volume", 5, 6, 1),
+	AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0),
+};
+
+static struct snd_kcontrol_new snd_pmac_screamer_mixers_g4agp[] __devinitdata = {
+	AWACS_VOLUME("Line out Playback Volume", 2, 6, 1),
+	AWACS_VOLUME("Master Playback Volume", 5, 6, 1),
+	AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0),
+	AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0),
+};
+
+static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac7500[] __devinitdata = {
+	AWACS_VOLUME("Line out Playback Volume", 2, 6, 1),
+	AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0),
+	AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0),
+};
+
+static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac5500[] __devinitdata = {
+	AWACS_VOLUME("Headphone Playback Volume", 2, 6, 1),
+};
+
+static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac[] __devinitdata = {
+	AWACS_VOLUME("Master Playback Volume", 2, 6, 1),
+	AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0),
+};
+
+/* FIXME: is this correct order?
+ * screamer (powerbook G3 pismo) seems to have different bits...
+ */
+static struct snd_kcontrol_new snd_pmac_awacs_mixers2[] __devinitdata = {
+	AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_LINE, 0),
+	AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_MIC, 0),
+};
+
+static struct snd_kcontrol_new snd_pmac_screamer_mixers2[] __devinitdata = {
+	AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0),
+	AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_LINE, 0),
+};
+
+static struct snd_kcontrol_new snd_pmac_awacs_mixers2_pmac5500[] __devinitdata = {
+	AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0),
+};
+
+static struct snd_kcontrol_new snd_pmac_awacs_master_sw __devinitdata =
+AWACS_SWITCH("Master Playback Switch", 1, SHIFT_HDMUTE, 1);
+
+static struct snd_kcontrol_new snd_pmac_awacs_master_sw_imac __devinitdata =
+AWACS_SWITCH("Line out Playback Switch", 1, SHIFT_HDMUTE, 1);
+
+static struct snd_kcontrol_new snd_pmac_awacs_master_sw_pmac5500 __devinitdata =
+AWACS_SWITCH("Headphone Playback Switch", 1, SHIFT_HDMUTE, 1);
+
+static struct snd_kcontrol_new snd_pmac_awacs_mic_boost[] __devinitdata = {
+	AWACS_SWITCH("Mic Boost Capture Switch", 0, SHIFT_GAINLINE, 0),
+};
+
+static struct snd_kcontrol_new snd_pmac_screamer_mic_boost[] __devinitdata = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Mic Boost Capture Volume",
+	  .info = snd_pmac_screamer_mic_boost_info,
+	  .get = snd_pmac_screamer_mic_boost_get,
+	  .put = snd_pmac_screamer_mic_boost_put,
+	},
+};
+
+static struct snd_kcontrol_new snd_pmac_awacs_mic_boost_pmac7500[] __devinitdata =
+{
+	AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0),
+};
+
+static struct snd_kcontrol_new snd_pmac_screamer_mic_boost_beige[] __devinitdata =
+{
+	AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0),
+	AWACS_SWITCH("CD Boost Capture Switch", 6, SHIFT_MIC_BOOST, 0),
+};
+
+static struct snd_kcontrol_new snd_pmac_screamer_mic_boost_imac[] __devinitdata =
+{
+	AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0),
+	AWACS_SWITCH("Mic Boost Capture Switch", 6, SHIFT_MIC_BOOST, 0),
+};
+
+static struct snd_kcontrol_new snd_pmac_awacs_speaker_vol[] __devinitdata = {
+	AWACS_VOLUME("Speaker Playback Volume", 4, 6, 1),
+};
+
+static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw __devinitdata =
+AWACS_SWITCH("Speaker Playback Switch", 1, SHIFT_SPKMUTE, 1);
+
+static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw_imac1 __devinitdata =
+AWACS_SWITCH("Speaker Playback Switch", 1, SHIFT_PAROUT1, 1);
+
+static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw_imac2 __devinitdata =
+AWACS_SWITCH("Speaker Playback Switch", 1, SHIFT_PAROUT1, 0);
+
+
+/*
+ * add new mixer elements to the card
+ */
+static int build_mixers(struct snd_pmac *chip, int nums,
+			struct snd_kcontrol_new *mixers)
+{
+	int i, err;
+
+	for (i = 0; i < nums; i++) {
+		err = snd_ctl_add(chip->card, snd_ctl_new1(&mixers[i], chip));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+
+/*
+ * restore all registers
+ */
+static void awacs_restore_all_regs(struct snd_pmac *chip)
+{
+	snd_pmac_awacs_write_noreg(chip, 0, chip->awacs_reg[0]);
+	snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]);
+	snd_pmac_awacs_write_noreg(chip, 2, chip->awacs_reg[2]);
+	snd_pmac_awacs_write_noreg(chip, 4, chip->awacs_reg[4]);
+	if (chip->model == PMAC_SCREAMER) {
+		snd_pmac_awacs_write_noreg(chip, 5, chip->awacs_reg[5]);
+		snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]);
+		snd_pmac_awacs_write_noreg(chip, 7, chip->awacs_reg[7]);
+	}
+}
+
+#ifdef CONFIG_PM
+static void snd_pmac_awacs_suspend(struct snd_pmac *chip)
+{
+	snd_pmac_awacs_write_noreg(chip, 1, (chip->awacs_reg[1]
+					     | MASK_AMUTE | MASK_CMUTE));
+}
+
+static void snd_pmac_awacs_resume(struct snd_pmac *chip)
+{
+	if (of_machine_is_compatible("PowerBook3,1")
+	    || of_machine_is_compatible("PowerBook3,2")) {
+		msleep(100);
+		snd_pmac_awacs_write_reg(chip, 1,
+			chip->awacs_reg[1] & ~MASK_PAROUT);
+		msleep(300);
+	}
+
+	awacs_restore_all_regs(chip);
+	if (chip->model == PMAC_SCREAMER) {
+		/* reset power bits in reg 6 */
+		mdelay(5);
+		snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]);
+	}
+	screamer_recalibrate(chip);
+#ifdef PMAC_AMP_AVAIL
+	if (chip->mixer_data) {
+		struct awacs_amp *amp = chip->mixer_data;
+		awacs_amp_set_vol(amp, 0,
+				  amp->amp_vol[0][0], amp->amp_vol[0][1], 0);
+		awacs_amp_set_vol(amp, 1,
+				  amp->amp_vol[1][0], amp->amp_vol[1][1], 0);
+		awacs_amp_set_tone(amp, amp->amp_tone[0], amp->amp_tone[1]);
+		awacs_amp_set_master(amp, amp->amp_master);
+	}
+#endif
+}
+#endif /* CONFIG_PM */
+
+#define IS_PM7500 (of_machine_is_compatible("AAPL,7500") \
+		|| of_machine_is_compatible("AAPL,8500") \
+		|| of_machine_is_compatible("AAPL,9500"))
+#define IS_PM5500 (of_machine_is_compatible("AAPL,e411"))
+#define IS_BEIGE (of_machine_is_compatible("AAPL,Gossamer"))
+#define IS_IMAC1 (of_machine_is_compatible("PowerMac2,1"))
+#define IS_IMAC2 (of_machine_is_compatible("PowerMac2,2") \
+		|| of_machine_is_compatible("PowerMac4,1"))
+#define IS_G4AGP (of_machine_is_compatible("PowerMac3,1"))
+#define IS_LOMBARD (of_machine_is_compatible("PowerBook1,1"))
+
+static int imac1, imac2;
+
+#ifdef PMAC_SUPPORT_AUTOMUTE
+/*
+ * auto-mute stuffs
+ */
+static int snd_pmac_awacs_detect_headphone(struct snd_pmac *chip)
+{
+	return (in_le32(&chip->awacs->codec_stat) & chip->hp_stat_mask) ? 1 : 0;
+}
+
+#ifdef PMAC_AMP_AVAIL
+static int toggle_amp_mute(struct awacs_amp *amp, int index, int mute)
+{
+	int vol[2];
+	vol[0] = amp->amp_vol[index][0] & 31;
+	vol[1] = amp->amp_vol[index][1] & 31;
+	if (mute) {
+		vol[0] |= 32;
+		vol[1] |= 32;
+	}
+	return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1);
+}
+#endif
+
+static void snd_pmac_awacs_update_automute(struct snd_pmac *chip, int do_notify)
+{
+	if (chip->auto_mute) {
+#ifdef PMAC_AMP_AVAIL
+		if (chip->mixer_data) {
+			struct awacs_amp *amp = chip->mixer_data;
+			int changed;
+			if (snd_pmac_awacs_detect_headphone(chip)) {
+				changed = toggle_amp_mute(amp, AMP_CH_HD, 0);
+				changed |= toggle_amp_mute(amp, AMP_CH_SPK, 1);
+			} else {
+				changed = toggle_amp_mute(amp, AMP_CH_HD, 1);
+				changed |= toggle_amp_mute(amp, AMP_CH_SPK, 0);
+			}
+			if (do_notify && ! changed)
+				return;
+		} else
+#endif
+		{
+			int reg = chip->awacs_reg[1]
+				| (MASK_HDMUTE | MASK_SPKMUTE);
+			if (imac1) {
+				reg &= ~MASK_SPKMUTE;
+				reg |= MASK_PAROUT1;
+			} else if (imac2) {
+				reg &= ~MASK_SPKMUTE;
+				reg &= ~MASK_PAROUT1;
+			}
+			if (snd_pmac_awacs_detect_headphone(chip))
+				reg &= ~MASK_HDMUTE;
+			else if (imac1)
+				reg &= ~MASK_PAROUT1;
+			else if (imac2)
+				reg |= MASK_PAROUT1;
+			else
+				reg &= ~MASK_SPKMUTE;
+			if (do_notify && reg == chip->awacs_reg[1])
+				return;
+			snd_pmac_awacs_write_reg(chip, 1, reg);
+		}
+		if (do_notify) {
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->master_sw_ctl->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->speaker_sw_ctl->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->hp_detect_ctl->id);
+		}
+	}
+}
+#endif /* PMAC_SUPPORT_AUTOMUTE */
+
+
+/*
+ * initialize chip
+ */
+int __devinit
+snd_pmac_awacs_init(struct snd_pmac *chip)
+{
+	int pm7500 = IS_PM7500;
+	int pm5500 = IS_PM5500;
+	int beige = IS_BEIGE;
+	int g4agp = IS_G4AGP;
+	int lombard = IS_LOMBARD;
+	int imac;
+	int err, vol;
+	struct snd_kcontrol *vmaster_sw, *vmaster_vol;
+	struct snd_kcontrol *master_vol, *speaker_vol;
+
+	imac1 = IS_IMAC1;
+	imac2 = IS_IMAC2;
+	imac = imac1 || imac2;
+	/* looks like MASK_GAINLINE triggers something, so we set here
+	 * as start-up
+	 */
+	chip->awacs_reg[0] = MASK_MUX_CD | 0xff | MASK_GAINLINE;
+	chip->awacs_reg[1] = MASK_CMUTE | MASK_AMUTE;
+	/* FIXME: Only machines with external SRS module need MASK_PAROUT */
+	if (chip->has_iic || chip->device_id == 0x5 ||
+	    /* chip->_device_id == 0x8 || */
+	    chip->device_id == 0xb)
+		chip->awacs_reg[1] |= MASK_PAROUT;
+	/* get default volume from nvram */
+	// vol = (~nvram_read_byte(0x1308) & 7) << 1;
+	// vol = ((pmac_xpram_read( 8 ) & 7 ) << 1 );
+	vol = 0x0f; /* no, on alsa, muted as default */
+	vol = vol + (vol << 6);
+	chip->awacs_reg[2] = vol;
+	chip->awacs_reg[4] = vol;
+	if (chip->model == PMAC_SCREAMER) {
+		/* FIXME: screamer has loopthru vol control */
+		chip->awacs_reg[5] = vol;
+		/* FIXME: maybe should be vol << 3 for PCMCIA speaker */
+		chip->awacs_reg[6] = MASK_MIC_BOOST;
+		chip->awacs_reg[7] = 0;
+	}
+
+	awacs_restore_all_regs(chip);
+	chip->manufacturer = (in_le32(&chip->awacs->codec_stat) >> 8) & 0xf;
+	screamer_recalibrate(chip);
+
+	chip->revision = (in_le32(&chip->awacs->codec_stat) >> 12) & 0xf;
+#ifdef PMAC_AMP_AVAIL
+	if (chip->revision == 3 && chip->has_iic && CHECK_CUDA_AMP()) {
+		struct awacs_amp *amp = kzalloc(sizeof(*amp), GFP_KERNEL);
+		if (! amp)
+			return -ENOMEM;
+		chip->mixer_data = amp;
+		chip->mixer_free = awacs_amp_free;
+		/* mute and zero vol */
+		awacs_amp_set_vol(amp, 0, 63, 63, 0);
+		awacs_amp_set_vol(amp, 1, 63, 63, 0);
+		awacs_amp_set_tone(amp, 7, 7); /* 0 dB */
+		awacs_amp_set_master(amp, 79); /* 0 dB */
+	}
+#endif /* PMAC_AMP_AVAIL */
+
+	if (chip->hp_stat_mask == 0) {
+		/* set headphone-jack detection bit */
+		switch (chip->model) {
+		case PMAC_AWACS:
+			chip->hp_stat_mask = pm7500 || pm5500 ? MASK_HDPCONN
+				: MASK_LOCONN;
+			break;
+		case PMAC_SCREAMER:
+			switch (chip->device_id) {
+			case 0x08:
+			case 0x0B:
+				chip->hp_stat_mask = imac
+					? MASK_LOCONN_IMAC |
+					MASK_HDPLCONN_IMAC |
+					MASK_HDPRCONN_IMAC
+					: MASK_HDPCONN;
+				break;
+			case 0x00:
+			case 0x05:
+				chip->hp_stat_mask = MASK_LOCONN;
+				break;
+			default:
+				chip->hp_stat_mask = MASK_HDPCONN;
+				break;
+			}
+			break;
+		default:
+			snd_BUG();
+			break;
+		}
+	}
+
+	/*
+	 * build mixers
+	 */
+	strcpy(chip->card->mixername, "PowerMac AWACS");
+
+	err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mixers),
+				snd_pmac_awacs_mixers);
+	if (err < 0)
+		return err;
+	if (beige || g4agp)
+		;
+	else if (chip->model == PMAC_SCREAMER || pm5500)
+		err = build_mixers(chip, ARRAY_SIZE(snd_pmac_screamer_mixers2),
+				   snd_pmac_screamer_mixers2);
+	else if (!pm7500)
+		err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mixers2),
+				   snd_pmac_awacs_mixers2);
+	if (err < 0)
+		return err;
+	if (pm5500) {
+		err = build_mixers(chip,
+				   ARRAY_SIZE(snd_pmac_awacs_mixers2_pmac5500),
+				   snd_pmac_awacs_mixers2_pmac5500);
+		if (err < 0)
+			return err;
+	}
+	if (pm7500)
+		err = build_mixers(chip,
+				   ARRAY_SIZE(snd_pmac_awacs_mixers_pmac7500),
+				   snd_pmac_awacs_mixers_pmac7500);
+	else if (pm5500)
+		err = snd_ctl_add(chip->card,
+		    (master_vol = snd_ctl_new1(snd_pmac_awacs_mixers_pmac5500,
+						chip)));
+	else if (beige)
+		err = build_mixers(chip,
+				   ARRAY_SIZE(snd_pmac_screamer_mixers_beige),
+				   snd_pmac_screamer_mixers_beige);
+	else if (imac || lombard) {
+		err = snd_ctl_add(chip->card,
+		    (master_vol = snd_ctl_new1(snd_pmac_screamer_mixers_lo,
+						chip)));
+		if (err < 0)
+			return err;
+		err = build_mixers(chip,
+				   ARRAY_SIZE(snd_pmac_screamer_mixers_imac),
+				   snd_pmac_screamer_mixers_imac);
+	} else if (g4agp)
+		err = build_mixers(chip,
+				   ARRAY_SIZE(snd_pmac_screamer_mixers_g4agp),
+				   snd_pmac_screamer_mixers_g4agp);
+	else
+		err = build_mixers(chip,
+				   ARRAY_SIZE(snd_pmac_awacs_mixers_pmac),
+				   snd_pmac_awacs_mixers_pmac);
+	if (err < 0)
+		return err;
+	chip->master_sw_ctl = snd_ctl_new1((pm7500 || imac || g4agp || lombard)
+			? &snd_pmac_awacs_master_sw_imac
+			: pm5500
+			? &snd_pmac_awacs_master_sw_pmac5500
+			: &snd_pmac_awacs_master_sw, chip);
+	err = snd_ctl_add(chip->card, chip->master_sw_ctl);
+	if (err < 0)
+		return err;
+#ifdef PMAC_AMP_AVAIL
+	if (chip->mixer_data) {
+		/* use amplifier.  the signal is connected from route A
+		 * to the amp.  the amp has its headphone and speaker
+		 * volumes and mute switches, so we use them instead of
+		 * screamer registers.
+		 * in this case, it seems the route C is not used.
+		 */
+		err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_amp_vol),
+					snd_pmac_awacs_amp_vol);
+		if (err < 0)
+			return err;
+		/* overwrite */
+		chip->master_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_amp_hp_sw,
+							chip);
+		err = snd_ctl_add(chip->card, chip->master_sw_ctl);
+		if (err < 0)
+			return err;
+		chip->speaker_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_amp_spk_sw,
+							chip);
+		err = snd_ctl_add(chip->card, chip->speaker_sw_ctl);
+		if (err < 0)
+			return err;
+	} else
+#endif /* PMAC_AMP_AVAIL */
+	{
+		/* route A = headphone, route C = speaker */
+		err = snd_ctl_add(chip->card,
+		    (speaker_vol = snd_ctl_new1(snd_pmac_awacs_speaker_vol,
+						chip)));
+		if (err < 0)
+			return err;
+		chip->speaker_sw_ctl = snd_ctl_new1(imac1
+				? &snd_pmac_awacs_speaker_sw_imac1
+				: imac2
+				? &snd_pmac_awacs_speaker_sw_imac2
+				: &snd_pmac_awacs_speaker_sw, chip);
+		err = snd_ctl_add(chip->card, chip->speaker_sw_ctl);
+		if (err < 0)
+			return err;
+	}
+
+	if (pm5500 || imac || lombard) {
+		vmaster_sw = snd_ctl_make_virtual_master(
+			"Master Playback Switch", (unsigned int *) NULL);
+		err = snd_ctl_add_slave_uncached(vmaster_sw,
+						 chip->master_sw_ctl);
+		if (err < 0)
+			return err;
+		err = snd_ctl_add_slave_uncached(vmaster_sw,
+						  chip->speaker_sw_ctl);
+		if (err < 0)
+			return err;
+		err = snd_ctl_add(chip->card, vmaster_sw);
+		if (err < 0)
+			return err;
+		vmaster_vol = snd_ctl_make_virtual_master(
+			"Master Playback Volume", (unsigned int *) NULL);
+		err = snd_ctl_add_slave(vmaster_vol, master_vol);
+		if (err < 0)
+			return err;
+		err = snd_ctl_add_slave(vmaster_vol, speaker_vol);
+		if (err < 0)
+			return err;
+		err = snd_ctl_add(chip->card, vmaster_vol);
+		if (err < 0)
+			return err;
+	}
+
+	if (beige || g4agp)
+		err = build_mixers(chip,
+				ARRAY_SIZE(snd_pmac_screamer_mic_boost_beige),
+				snd_pmac_screamer_mic_boost_beige);
+	else if (imac)
+		err = build_mixers(chip,
+				ARRAY_SIZE(snd_pmac_screamer_mic_boost_imac),
+				snd_pmac_screamer_mic_boost_imac);
+	else if (chip->model == PMAC_SCREAMER)
+		err = build_mixers(chip,
+				ARRAY_SIZE(snd_pmac_screamer_mic_boost),
+				snd_pmac_screamer_mic_boost);
+	else if (pm7500)
+		err = build_mixers(chip,
+				ARRAY_SIZE(snd_pmac_awacs_mic_boost_pmac7500),
+				snd_pmac_awacs_mic_boost_pmac7500);
+	else
+		err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mic_boost),
+				snd_pmac_awacs_mic_boost);
+	if (err < 0)
+		return err;
+
+	/*
+	 * set lowlevel callbacks
+	 */
+	chip->set_format = snd_pmac_awacs_set_format;
+#ifdef CONFIG_PM
+	chip->suspend = snd_pmac_awacs_suspend;
+	chip->resume = snd_pmac_awacs_resume;
+#endif
+#ifdef PMAC_SUPPORT_AUTOMUTE
+	err = snd_pmac_add_automute(chip);
+	if (err < 0)
+		return err;
+	chip->detect_headphone = snd_pmac_awacs_detect_headphone;
+	chip->update_automute = snd_pmac_awacs_update_automute;
+	snd_pmac_awacs_update_automute(chip, 0); /* update the status only */
+#endif
+	if (chip->model == PMAC_SCREAMER) {
+		snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]);
+		snd_pmac_awacs_write_noreg(chip, 0, chip->awacs_reg[0]);
+	}
+
+	return 0;
+}
diff --git a/linux/sound/ppc/awacs.h b/linux/sound/ppc/awacs.h
new file mode 100644
index 0000000..c33e6a5
--- /dev/null
+++ b/linux/sound/ppc/awacs.h
@@ -0,0 +1,205 @@
+/*
+ * Driver for PowerMac AWACS onboard soundchips
+ * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de>
+ *   based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#ifndef __AWACS_H
+#define __AWACS_H
+
+/*******************************/
+/* AWACs Audio Register Layout */
+/*******************************/
+
+struct awacs_regs {
+    unsigned	control;	/* Audio control register */
+    unsigned	pad0[3];
+    unsigned	codec_ctrl;	/* Codec control register */
+    unsigned	pad1[3];
+    unsigned	codec_stat;	/* Codec status register */
+    unsigned	pad2[3];
+    unsigned	clip_count;	/* Clipping count register */
+    unsigned	pad3[3];
+    unsigned	byteswap;	/* Data is little-endian if 1 */
+};
+
+/*******************/
+/* Audio Bit Masks */
+/*******************/
+
+/* Audio Control Reg Bit Masks */
+/* ----- ------- --- --- ----- */
+#define MASK_ISFSEL	(0xf)		/* Input SubFrame Select */
+#define MASK_OSFSEL	(0xf << 4)	/* Output SubFrame Select */
+#define MASK_RATE	(0x7 << 8)	/* Sound Rate */
+#define MASK_CNTLERR	(0x1 << 11)	/* Error */
+#define MASK_PORTCHG	(0x1 << 12)	/* Port Change */
+#define MASK_IEE	(0x1 << 13)	/* Enable Interrupt on Error */
+#define MASK_IEPC	(0x1 << 14)	/* Enable Interrupt on Port Change */
+#define MASK_SSFSEL	(0x3 << 15)	/* Status SubFrame Select */
+
+/* Audio Codec Control Reg Bit Masks */
+/* ----- ----- ------- --- --- ----- */
+#define MASK_NEWECMD	(0x1 << 24)	/* Lock: don't write to reg when 1 */
+#define MASK_EMODESEL	(0x3 << 22)	/* Send info out on which frame? */
+#define MASK_EXMODEADDR	(0x3ff << 12)	/* Extended Mode Address -- 10 bits */
+#define MASK_EXMODEDATA	(0xfff)		/* Extended Mode Data -- 12 bits */
+
+/* Audio Codec Control Address Values / Masks */
+/* ----- ----- ------- ------- ------ - ----- */
+#define MASK_ADDR0	(0x0 << 12)	/* Expanded Data Mode Address 0 */
+#define MASK_ADDR_MUX	MASK_ADDR0	/* Mux Control */
+#define MASK_ADDR_GAIN	MASK_ADDR0
+
+#define MASK_ADDR1	(0x1 << 12)	/* Expanded Data Mode Address 1 */
+#define MASK_ADDR_MUTE	MASK_ADDR1
+#define MASK_ADDR_RATE	MASK_ADDR1
+
+#define MASK_ADDR2	(0x2 << 12)	/* Expanded Data Mode Address 2 */
+#define MASK_ADDR_VOLA	MASK_ADDR2	/* Volume Control A -- Headphones */
+#define MASK_ADDR_VOLHD MASK_ADDR2
+
+#define MASK_ADDR4	(0x4 << 12)	/* Expanded Data Mode Address 4 */
+#define MASK_ADDR_VOLC	MASK_ADDR4	/* Volume Control C -- Speaker */
+#define MASK_ADDR_VOLSPK MASK_ADDR4
+
+/* additional registers of screamer */
+#define MASK_ADDR5	(0x5 << 12)	/* Expanded Data Mode Address 5 */
+#define MASK_ADDR6	(0x6 << 12)	/* Expanded Data Mode Address 6 */
+#define MASK_ADDR7	(0x7 << 12)	/* Expanded Data Mode Address 7 */
+
+/* Address 0 Bit Masks & Macros */
+/* ------- - --- ----- - ------ */
+#define MASK_GAINRIGHT	(0xf)		/* Gain Right Mask */
+#define MASK_GAINLEFT	(0xf << 4)	/* Gain Left Mask */
+#define MASK_GAINLINE	(0x1 << 8)	/* Disable Mic preamp */
+#define MASK_GAINMIC	(0x0 << 8)	/* Enable Mic preamp */
+#define MASK_MUX_CD	(0x1 << 9)	/* Select CD in MUX */
+#define MASK_MUX_MIC	(0x1 << 10)	/* Select Mic in MUX */
+#define MASK_MUX_AUDIN	(0x1 << 11)	/* Select Audio In in MUX */
+#define MASK_MUX_LINE	MASK_MUX_AUDIN
+#define SHIFT_GAINLINE	8
+#define SHIFT_MUX_CD	9
+#define SHIFT_MUX_MIC	10
+#define SHIFT_MUX_LINE	11
+
+#define GAINRIGHT(x)	((x) & MASK_GAINRIGHT)
+#define GAINLEFT(x)	(((x) << 4) & MASK_GAINLEFT)
+
+/* Address 1 Bit Masks */
+/* ------- - --- ----- */
+#define MASK_ADDR1RES1	(0x3)		/* Reserved */
+#define MASK_RECALIBRATE (0x1 << 2)	/* Recalibrate */
+#define MASK_SAMPLERATE	(0x7 << 3)	/* Sample Rate: */
+#define MASK_LOOPTHRU	(0x1 << 6)	/* Loopthrough Enable */
+#define SHIFT_LOOPTHRU	6
+#define MASK_CMUTE	(0x1 << 7)	/* Output C (Speaker) Mute when 1 */
+#define MASK_SPKMUTE	MASK_CMUTE
+#define SHIFT_SPKMUTE	7
+#define MASK_ADDR1RES2	(0x1 << 8)	/* Reserved */
+#define MASK_AMUTE	(0x1 << 9)	/* Output A (Headphone) Mute when 1 */
+#define MASK_HDMUTE	MASK_AMUTE
+#define SHIFT_HDMUTE	9
+#define MASK_PAROUT	(0x3 << 10)	/* Parallel Out (???) */
+#define MASK_PAROUT0	(0x1 << 10)	/* Parallel Out (???) */
+#define MASK_PAROUT1	(0x1 << 11)	/* Parallel Out (enable speaker) */
+#define SHIFT_PAROUT	10
+#define SHIFT_PAROUT0	10
+#define SHIFT_PAROUT1	11
+
+#define SAMPLERATE_48000	(0x0 << 3)	/* 48 or 44.1 kHz */
+#define SAMPLERATE_32000	(0x1 << 3)	/* 32 or 29.4 kHz */
+#define SAMPLERATE_24000	(0x2 << 3)	/* 24 or 22.05 kHz */
+#define SAMPLERATE_19200	(0x3 << 3)	/* 19.2 or 17.64 kHz */
+#define SAMPLERATE_16000	(0x4 << 3)	/* 16 or 14.7 kHz */
+#define SAMPLERATE_12000	(0x5 << 3)	/* 12 or 11.025 kHz */
+#define SAMPLERATE_9600		(0x6 << 3)	/* 9.6 or 8.82 kHz */
+#define SAMPLERATE_8000		(0x7 << 3)	/* 8 or 7.35 kHz */
+
+/* Address 2 & 4 Bit Masks & Macros */
+/* ------- - - - --- ----- - ------ */
+#define MASK_OUTVOLRIGHT (0xf)		/* Output Right Volume */
+#define MASK_ADDR2RES1	(0x2 << 4)	/* Reserved */
+#define MASK_ADDR4RES1	MASK_ADDR2RES1
+#define MASK_OUTVOLLEFT	(0xf << 6)	/* Output Left Volume */
+#define MASK_ADDR2RES2	(0x2 << 10)	/* Reserved */
+#define MASK_ADDR4RES2	MASK_ADDR2RES2
+
+#define VOLRIGHT(x)	(((~(x)) & MASK_OUTVOLRIGHT))
+#define VOLLEFT(x)	(((~(x)) << 6) & MASK_OUTVOLLEFT)
+
+/* address 6 */
+#define MASK_MIC_BOOST  (0x4)		/* screamer mic boost */
+#define SHIFT_MIC_BOOST	2
+
+/* Audio Codec Status Reg Bit Masks */
+/* ----- ----- ------ --- --- ----- */
+#define MASK_EXTEND	(0x1 << 23)	/* Extend */
+#define MASK_VALID	(0x1 << 22)	/* Valid Data? */
+#define MASK_OFLEFT	(0x1 << 21)	/* Overflow Left */
+#define MASK_OFRIGHT	(0x1 << 20)	/* Overflow Right */
+#define MASK_ERRCODE	(0xf << 16)	/* Error Code */
+#define MASK_REVISION	(0xf << 12)	/* Revision Number */
+#define MASK_MFGID	(0xf << 8)	/* Mfg. ID */
+#define MASK_CODSTATRES	(0xf << 4)	/* bits 4 - 7 reserved */
+#define MASK_INSENSE	(0xf)		/* port sense bits: */
+#define MASK_HDPCONN		8	/* headphone plugged in */
+#define MASK_LOCONN		4	/* line-out plugged in */
+#define MASK_LICONN		2	/* line-in plugged in */
+#define MASK_MICCONN		1	/* microphone plugged in */
+#define MASK_LICONN_IMAC	8	/* line-in plugged in */
+#define MASK_HDPRCONN_IMAC	4	/* headphone right plugged in */
+#define MASK_HDPLCONN_IMAC	2	/* headphone left plugged in */
+#define MASK_LOCONN_IMAC	1	/* line-out plugged in */
+
+/* Clipping Count Reg Bit Masks */
+/* -------- ----- --- --- ----- */
+#define MASK_CLIPLEFT	(0xff << 7)	/* Clipping Count, Left Channel */
+#define MASK_CLIPRIGHT	(0xff)		/* Clipping Count, Right Channel */
+
+/* DBDMA ChannelStatus Bit Masks */
+/* ----- ------------- --- ----- */
+#define MASK_CSERR	(0x1 << 7)	/* Error */
+#define MASK_EOI	(0x1 << 6)	/* End of Input --
+					   only for Input Channel */
+#define MASK_CSUNUSED	(0x1f << 1)	/* bits 1-5 not used */
+#define MASK_WAIT	(0x1)		/* Wait */
+
+/* Various Rates */
+/* ------- ----- */
+#define RATE_48000	(0x0 << 8)	/* 48 kHz */
+#define RATE_44100	(0x0 << 8)	/* 44.1 kHz */
+#define RATE_32000	(0x1 << 8)	/* 32 kHz */
+#define RATE_29400	(0x1 << 8)	/* 29.4 kHz */
+#define RATE_24000	(0x2 << 8)	/* 24 kHz */
+#define RATE_22050	(0x2 << 8)	/* 22.05 kHz */
+#define RATE_19200	(0x3 << 8)	/* 19.2 kHz */
+#define RATE_17640	(0x3 << 8)	/* 17.64 kHz */
+#define RATE_16000	(0x4 << 8)	/* 16 kHz */
+#define RATE_14700	(0x4 << 8)	/* 14.7 kHz */
+#define RATE_12000	(0x5 << 8)	/* 12 kHz */
+#define RATE_11025	(0x5 << 8)	/* 11.025 kHz */
+#define RATE_9600	(0x6 << 8)	/* 9.6 kHz */
+#define RATE_8820	(0x6 << 8)	/* 8.82 kHz */
+#define RATE_8000	(0x7 << 8)	/* 8 kHz */
+#define RATE_7350	(0x7 << 8)	/* 7.35 kHz */
+
+#define RATE_LOW	1	/* HIGH = 48kHz, etc;  LOW = 44.1kHz, etc. */
+
+
+#endif /* __AWACS_H */
diff --git a/linux/sound/ppc/beep.c b/linux/sound/ppc/beep.c
new file mode 100644
index 0000000..a9d3507
--- /dev/null
+++ b/linux/sound/ppc/beep.c
@@ -0,0 +1,285 @@
+/*
+ * Beep using pcm
+ *
+ * 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 <asm/io.h>
+#include <asm/irq.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include "pmac.h"
+
+struct pmac_beep {
+	int running;		/* boolean */
+	int volume;		/* mixer volume: 0-100 */
+	int volume_play;	/* currently playing volume */
+	int hz;
+	int nsamples;
+	short *buf;		/* allocated wave buffer */
+	dma_addr_t addr;	/* physical address of buffer */
+	struct input_dev *dev;
+};
+
+/*
+ * stop beep if running
+ */
+void snd_pmac_beep_stop(struct snd_pmac *chip)
+{
+	struct pmac_beep *beep = chip->beep;
+	if (beep && beep->running) {
+		beep->running = 0;
+		snd_pmac_beep_dma_stop(chip);
+	}
+}
+
+/*
+ * Stuff for outputting a beep.  The values range from -327 to +327
+ * so we can multiply by an amplitude in the range 0..100 to get a
+ * signed short value to put in the output buffer.
+ */
+static short beep_wform[256] = {
+	0,	40,	79,	117,	153,	187,	218,	245,
+	269,	288,	304,	316,	323,	327,	327,	324,
+	318,	310,	299,	288,	275,	262,	249,	236,
+	224,	213,	204,	196,	190,	186,	183,	182,
+	182,	183,	186,	189,	192,	196,	200,	203,
+	206,	208,	209,	209,	209,	207,	204,	201,
+	197,	193,	188,	183,	179,	174,	170,	166,
+	163,	161,	160,	159,	159,	160,	161,	162,
+	164,	166,	168,	169,	171,	171,	171,	170,
+	169,	167,	163,	159,	155,	150,	144,	139,
+	133,	128,	122,	117,	113,	110,	107,	105,
+	103,	103,	103,	103,	104,	104,	105,	105,
+	105,	103,	101,	97,	92,	86,	78,	68,
+	58,	45,	32,	18,	3,	-11,	-26,	-41,
+	-55,	-68,	-79,	-88,	-95,	-100,	-102,	-102,
+	-99,	-93,	-85,	-75,	-62,	-48,	-33,	-16,
+	0,	16,	33,	48,	62,	75,	85,	93,
+	99,	102,	102,	100,	95,	88,	79,	68,
+	55,	41,	26,	11,	-3,	-18,	-32,	-45,
+	-58,	-68,	-78,	-86,	-92,	-97,	-101,	-103,
+	-105,	-105,	-105,	-104,	-104,	-103,	-103,	-103,
+	-103,	-105,	-107,	-110,	-113,	-117,	-122,	-128,
+	-133,	-139,	-144,	-150,	-155,	-159,	-163,	-167,
+	-169,	-170,	-171,	-171,	-171,	-169,	-168,	-166,
+	-164,	-162,	-161,	-160,	-159,	-159,	-160,	-161,
+	-163,	-166,	-170,	-174,	-179,	-183,	-188,	-193,
+	-197,	-201,	-204,	-207,	-209,	-209,	-209,	-208,
+	-206,	-203,	-200,	-196,	-192,	-189,	-186,	-183,
+	-182,	-182,	-183,	-186,	-190,	-196,	-204,	-213,
+	-224,	-236,	-249,	-262,	-275,	-288,	-299,	-310,
+	-318,	-324,	-327,	-327,	-323,	-316,	-304,	-288,
+	-269,	-245,	-218,	-187,	-153,	-117,	-79,	-40,
+};
+
+#define BEEP_SRATE	22050	/* 22050 Hz sample rate */
+#define BEEP_BUFLEN	512
+#define BEEP_VOLUME	15	/* 0 - 100 */
+
+static int snd_pmac_beep_event(struct input_dev *dev, unsigned int type,
+			       unsigned int code, int hz)
+{
+	struct snd_pmac *chip;
+	struct pmac_beep *beep;
+	unsigned long flags;
+	int beep_speed = 0;
+	int srate;
+	int period, ncycles, nsamples;
+	int i, j, f;
+	short *p;
+
+	if (type != EV_SND)
+		return -1;
+
+	switch (code) {
+	case SND_BELL: if (hz) hz = 1000;
+	case SND_TONE: break;
+	default: return -1;
+	}
+
+	chip = input_get_drvdata(dev);
+	if (! chip || (beep = chip->beep) == NULL)
+		return -1;
+
+	if (! hz) {
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		if (beep->running)
+			snd_pmac_beep_stop(chip);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return 0;
+	}
+
+	beep_speed = snd_pmac_rate_index(chip, &chip->playback, BEEP_SRATE);
+	srate = chip->freq_table[beep_speed];
+
+	if (hz <= srate / BEEP_BUFLEN || hz > srate / 2)
+		hz = 1000;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->playback.running || chip->capture.running || beep->running) {
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+		return 0;
+	}
+	beep->running = 1;
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	if (hz == beep->hz && beep->volume == beep->volume_play) {
+		nsamples = beep->nsamples;
+	} else {
+		period = srate * 256 / hz;	/* fixed point */
+		ncycles = BEEP_BUFLEN * 256 / period;
+		nsamples = (period * ncycles) >> 8;
+		f = ncycles * 65536 / nsamples;
+		j = 0;
+		p = beep->buf;
+		for (i = 0; i < nsamples; ++i, p += 2) {
+			p[0] = p[1] = beep_wform[j >> 8] * beep->volume;
+			j = (j + f) & 0xffff;
+		}
+		beep->hz = hz;
+		beep->volume_play = beep->volume;
+		beep->nsamples = nsamples;
+	}
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_pmac_beep_dma_start(chip, beep->nsamples * 4, beep->addr, beep_speed);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return 0;
+}
+
+/*
+ * beep volume mixer
+ */
+
+static int snd_pmac_info_beep(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 100;
+	return 0;
+}
+
+static int snd_pmac_get_beep(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	if (snd_BUG_ON(!chip->beep))
+		return -ENXIO;
+	ucontrol->value.integer.value[0] = chip->beep->volume;
+	return 0;
+}
+
+static int snd_pmac_put_beep(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int oval, nval;
+	if (snd_BUG_ON(!chip->beep))
+		return -ENXIO;
+	oval = chip->beep->volume;
+	nval = ucontrol->value.integer.value[0];
+	if (nval > 100)
+		return -EINVAL;
+	chip->beep->volume = nval;
+	return oval != chip->beep->volume;
+}
+
+static struct snd_kcontrol_new snd_pmac_beep_mixer = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Beep Playback Volume",
+	.info = snd_pmac_info_beep,
+	.get = snd_pmac_get_beep,
+	.put = snd_pmac_put_beep,
+};
+
+/* Initialize beep stuff */
+int __devinit snd_pmac_attach_beep(struct snd_pmac *chip)
+{
+	struct pmac_beep *beep;
+	struct input_dev *input_dev;
+	struct snd_kcontrol *beep_ctl;
+	void *dmabuf;
+	int err = -ENOMEM;
+
+	beep = kzalloc(sizeof(*beep), GFP_KERNEL);
+	if (! beep)
+		return -ENOMEM;
+	dmabuf = dma_alloc_coherent(&chip->pdev->dev, BEEP_BUFLEN * 4,
+				    &beep->addr, GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (! dmabuf || ! input_dev)
+		goto fail1;
+
+	/* FIXME: set more better values */
+	input_dev->name = "PowerMac Beep";
+	input_dev->phys = "powermac/beep";
+	input_dev->id.bustype = BUS_ADB;
+	input_dev->id.vendor = 0x001f;
+	input_dev->id.product = 0x0001;
+	input_dev->id.version = 0x0100;
+
+	input_dev->evbit[0] = BIT_MASK(EV_SND);
+	input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+	input_dev->event = snd_pmac_beep_event;
+	input_dev->dev.parent = &chip->pdev->dev;
+	input_set_drvdata(input_dev, chip);
+
+	beep->dev = input_dev;
+	beep->buf = dmabuf;
+	beep->volume = BEEP_VOLUME;
+	beep->running = 0;
+
+	beep_ctl = snd_ctl_new1(&snd_pmac_beep_mixer, chip);
+	err = snd_ctl_add(chip->card, beep_ctl);
+	if (err < 0)
+		goto fail1;
+
+	chip->beep = beep;
+
+	err = input_register_device(beep->dev);
+	if (err)
+		goto fail2;
+ 
+ 	return 0;
+ 
+ fail2:	snd_ctl_remove(chip->card, beep_ctl);
+ fail1:	input_free_device(input_dev);
+	if (dmabuf)
+		dma_free_coherent(&chip->pdev->dev, BEEP_BUFLEN * 4,
+				  dmabuf, beep->addr);
+	kfree(beep);
+	return err;
+}
+
+void snd_pmac_detach_beep(struct snd_pmac *chip)
+{
+	if (chip->beep) {
+		input_unregister_device(chip->beep->dev);
+		dma_free_coherent(&chip->pdev->dev, BEEP_BUFLEN * 4,
+				  chip->beep->buf, chip->beep->addr);
+		kfree(chip->beep);
+		chip->beep = NULL;
+	}
+}
diff --git a/linux/sound/ppc/burgundy.c b/linux/sound/ppc/burgundy.c
new file mode 100644
index 0000000..00e2d51
--- /dev/null
+++ b/linux/sound/ppc/burgundy.c
@@ -0,0 +1,732 @@
+/*
+ * PMac Burgundy lowlevel functions
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ * code based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <asm/io.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include "pmac.h"
+#include "burgundy.h"
+
+
+/* Waits for busy flag to clear */
+static inline void
+snd_pmac_burgundy_busy_wait(struct snd_pmac *chip)
+{
+	int timeout = 50;
+	while ((in_le32(&chip->awacs->codec_ctrl) & MASK_NEWECMD) && timeout--)
+		udelay(1);
+	if (timeout < 0)
+		printk(KERN_DEBUG "burgundy_busy_wait: timeout\n");
+}
+
+static inline void
+snd_pmac_burgundy_extend_wait(struct snd_pmac *chip)
+{
+	int timeout;
+	timeout = 50;
+	while (!(in_le32(&chip->awacs->codec_stat) & MASK_EXTEND) && timeout--)
+		udelay(1);
+	if (timeout < 0)
+		printk(KERN_DEBUG "burgundy_extend_wait: timeout #1\n");
+	timeout = 50;
+	while ((in_le32(&chip->awacs->codec_stat) & MASK_EXTEND) && timeout--)
+		udelay(1);
+	if (timeout < 0)
+		printk(KERN_DEBUG "burgundy_extend_wait: timeout #2\n");
+}
+
+static void
+snd_pmac_burgundy_wcw(struct snd_pmac *chip, unsigned addr, unsigned val)
+{
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x200c00 + (val & 0xff));
+	snd_pmac_burgundy_busy_wait(chip);
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x200d00 +((val>>8) & 0xff));
+	snd_pmac_burgundy_busy_wait(chip);
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x200e00 +((val>>16) & 0xff));
+	snd_pmac_burgundy_busy_wait(chip);
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x200f00 +((val>>24) & 0xff));
+	snd_pmac_burgundy_busy_wait(chip);
+}
+
+static unsigned
+snd_pmac_burgundy_rcw(struct snd_pmac *chip, unsigned addr)
+{
+	unsigned val = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x100000);
+	snd_pmac_burgundy_busy_wait(chip);
+	snd_pmac_burgundy_extend_wait(chip);
+	val += (in_le32(&chip->awacs->codec_stat) >> 4) & 0xff;
+
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x100100);
+	snd_pmac_burgundy_busy_wait(chip);
+	snd_pmac_burgundy_extend_wait(chip);
+	val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<8;
+
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x100200);
+	snd_pmac_burgundy_busy_wait(chip);
+	snd_pmac_burgundy_extend_wait(chip);
+	val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<16;
+
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x100300);
+	snd_pmac_burgundy_busy_wait(chip);
+	snd_pmac_burgundy_extend_wait(chip);
+	val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<24;
+
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	return val;
+}
+
+static void
+snd_pmac_burgundy_wcb(struct snd_pmac *chip, unsigned int addr,
+		      unsigned int val)
+{
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x300000 + (val & 0xff));
+	snd_pmac_burgundy_busy_wait(chip);
+}
+
+static unsigned
+snd_pmac_burgundy_rcb(struct snd_pmac *chip, unsigned int addr)
+{
+	unsigned val = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+
+	out_le32(&chip->awacs->codec_ctrl, addr + 0x100000);
+	snd_pmac_burgundy_busy_wait(chip);
+	snd_pmac_burgundy_extend_wait(chip);
+	val += (in_le32(&chip->awacs->codec_stat) >> 4) & 0xff;
+
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	return val;
+}
+
+#define BASE2ADDR(base)	((base) << 12)
+#define ADDR2BASE(addr)	((addr) >> 12)
+
+/*
+ * Burgundy volume: 0 - 100, stereo, word reg
+ */
+static void
+snd_pmac_burgundy_write_volume(struct snd_pmac *chip, unsigned int address,
+			       long *volume, int shift)
+{
+	int hardvolume, lvolume, rvolume;
+
+	if (volume[0] < 0 || volume[0] > 100 ||
+	    volume[1] < 0 || volume[1] > 100)
+		return; /* -EINVAL */
+	lvolume = volume[0] ? volume[0] + BURGUNDY_VOLUME_OFFSET : 0;
+	rvolume = volume[1] ? volume[1] + BURGUNDY_VOLUME_OFFSET : 0;
+
+	hardvolume = lvolume + (rvolume << shift);
+	if (shift == 8)
+		hardvolume |= hardvolume << 16;
+
+	snd_pmac_burgundy_wcw(chip, address, hardvolume);
+}
+
+static void
+snd_pmac_burgundy_read_volume(struct snd_pmac *chip, unsigned int address,
+			      long *volume, int shift)
+{
+	int wvolume;
+
+	wvolume = snd_pmac_burgundy_rcw(chip, address);
+
+	volume[0] = wvolume & 0xff;
+	if (volume[0] >= BURGUNDY_VOLUME_OFFSET)
+		volume[0] -= BURGUNDY_VOLUME_OFFSET;
+	else
+		volume[0] = 0;
+	volume[1] = (wvolume >> shift) & 0xff;
+	if (volume[1] >= BURGUNDY_VOLUME_OFFSET)
+		volume[1] -= BURGUNDY_VOLUME_OFFSET;
+	else
+		volume[1] = 0;
+}
+
+static int snd_pmac_burgundy_info_volume(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 100;
+	return 0;
+}
+
+static int snd_pmac_burgundy_get_volume(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff);
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	snd_pmac_burgundy_read_volume(chip, addr,
+				      ucontrol->value.integer.value, shift);
+	return 0;
+}
+
+static int snd_pmac_burgundy_put_volume(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff);
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	long nvoices[2];
+
+	snd_pmac_burgundy_write_volume(chip, addr,
+				       ucontrol->value.integer.value, shift);
+	snd_pmac_burgundy_read_volume(chip, addr, nvoices, shift);
+	return (nvoices[0] != ucontrol->value.integer.value[0] ||
+		nvoices[1] != ucontrol->value.integer.value[1]);
+}
+
+#define BURGUNDY_VOLUME_W(xname, xindex, addr, shift) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\
+  .info = snd_pmac_burgundy_info_volume,\
+  .get = snd_pmac_burgundy_get_volume,\
+  .put = snd_pmac_burgundy_put_volume,\
+  .private_value = ((ADDR2BASE(addr) & 0xff) | ((shift) << 8)) }
+
+/*
+ * Burgundy volume: 0 - 100, stereo, 2-byte reg
+ */
+static void
+snd_pmac_burgundy_write_volume_2b(struct snd_pmac *chip, unsigned int address,
+				  long *volume, int off)
+{
+	int lvolume, rvolume;
+
+	off |= off << 2;
+	lvolume = volume[0] ? volume[0] + BURGUNDY_VOLUME_OFFSET : 0;
+	rvolume = volume[1] ? volume[1] + BURGUNDY_VOLUME_OFFSET : 0;
+
+	snd_pmac_burgundy_wcb(chip, address + off, lvolume);
+	snd_pmac_burgundy_wcb(chip, address + off + 0x500, rvolume);
+}
+
+static void
+snd_pmac_burgundy_read_volume_2b(struct snd_pmac *chip, unsigned int address,
+				 long *volume, int off)
+{
+	volume[0] = snd_pmac_burgundy_rcb(chip, address + off);
+	if (volume[0] >= BURGUNDY_VOLUME_OFFSET)
+		volume[0] -= BURGUNDY_VOLUME_OFFSET;
+	else
+		volume[0] = 0;
+	volume[1] = snd_pmac_burgundy_rcb(chip, address + off + 0x100);
+	if (volume[1] >= BURGUNDY_VOLUME_OFFSET)
+		volume[1] -= BURGUNDY_VOLUME_OFFSET;
+	else
+		volume[1] = 0;
+}
+
+static int snd_pmac_burgundy_info_volume_2b(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 100;
+	return 0;
+}
+
+static int snd_pmac_burgundy_get_volume_2b(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff);
+	int off = kcontrol->private_value & 0x300;
+	snd_pmac_burgundy_read_volume_2b(chip, addr,
+			ucontrol->value.integer.value, off);
+	return 0;
+}
+
+static int snd_pmac_burgundy_put_volume_2b(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff);
+	int off = kcontrol->private_value & 0x300;
+	long nvoices[2];
+
+	snd_pmac_burgundy_write_volume_2b(chip, addr,
+			ucontrol->value.integer.value, off);
+	snd_pmac_burgundy_read_volume_2b(chip, addr, nvoices, off);
+	return (nvoices[0] != ucontrol->value.integer.value[0] ||
+		nvoices[1] != ucontrol->value.integer.value[1]);
+}
+
+#define BURGUNDY_VOLUME_2B(xname, xindex, addr, off) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\
+  .info = snd_pmac_burgundy_info_volume_2b,\
+  .get = snd_pmac_burgundy_get_volume_2b,\
+  .put = snd_pmac_burgundy_put_volume_2b,\
+  .private_value = ((ADDR2BASE(addr) & 0xff) | ((off) << 8)) }
+
+/*
+ * Burgundy gain/attenuation: 0 - 15, mono/stereo, byte reg
+ */
+static int snd_pmac_burgundy_info_gain(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = stereo + 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 15;
+	return 0;
+}
+
+static int snd_pmac_burgundy_get_gain(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff);
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	int atten = (kcontrol->private_value >> 25) & 1;
+	int oval;
+
+	oval = snd_pmac_burgundy_rcb(chip, addr);
+	if (atten)
+		oval = ~oval & 0xff;
+	ucontrol->value.integer.value[0] = oval & 0xf;
+	if (stereo)
+		ucontrol->value.integer.value[1] = (oval >> 4) & 0xf;
+	return 0;
+}
+
+static int snd_pmac_burgundy_put_gain(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff);
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	int atten = (kcontrol->private_value >> 25) & 1;
+	int oval, val;
+
+	oval = snd_pmac_burgundy_rcb(chip, addr);
+	if (atten)
+		oval = ~oval & 0xff;
+	val = ucontrol->value.integer.value[0];
+	if (stereo)
+		val |= ucontrol->value.integer.value[1] << 4;
+	else
+		val |= ucontrol->value.integer.value[0] << 4;
+	if (atten)
+		val = ~val & 0xff;
+	snd_pmac_burgundy_wcb(chip, addr, val);
+	return val != oval;
+}
+
+#define BURGUNDY_VOLUME_B(xname, xindex, addr, stereo, atten) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\
+  .info = snd_pmac_burgundy_info_gain,\
+  .get = snd_pmac_burgundy_get_gain,\
+  .put = snd_pmac_burgundy_put_gain,\
+  .private_value = (ADDR2BASE(addr) | ((stereo) << 24) | ((atten) << 25)) }
+
+/*
+ * Burgundy switch: 0/1, mono/stereo, word reg
+ */
+static int snd_pmac_burgundy_info_switch_w(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = stereo + 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_pmac_burgundy_get_switch_w(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff);
+	int lmask = 1 << (kcontrol->private_value & 0xff);
+	int rmask = 1 << ((kcontrol->private_value >> 8) & 0xff);
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	int val = snd_pmac_burgundy_rcw(chip, addr);
+	ucontrol->value.integer.value[0] = (val & lmask) ? 1 : 0;
+	if (stereo)
+		ucontrol->value.integer.value[1] = (val & rmask) ? 1 : 0;
+	return 0;
+}
+
+static int snd_pmac_burgundy_put_switch_w(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff);
+	int lmask = 1 << (kcontrol->private_value & 0xff);
+	int rmask = 1 << ((kcontrol->private_value >> 8) & 0xff);
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	int val, oval;
+	oval = snd_pmac_burgundy_rcw(chip, addr);
+	val = oval & ~(lmask | (stereo ? rmask : 0));
+	if (ucontrol->value.integer.value[0])
+		val |= lmask;
+	if (stereo && ucontrol->value.integer.value[1])
+		val |= rmask;
+	snd_pmac_burgundy_wcw(chip, addr, val);
+	return val != oval;
+}
+
+#define BURGUNDY_SWITCH_W(xname, xindex, addr, lbit, rbit, stereo) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\
+  .info = snd_pmac_burgundy_info_switch_w,\
+  .get = snd_pmac_burgundy_get_switch_w,\
+  .put = snd_pmac_burgundy_put_switch_w,\
+  .private_value = ((lbit) | ((rbit) << 8)\
+		| (ADDR2BASE(addr) << 16) | ((stereo) << 24)) }
+
+/*
+ * Burgundy switch: 0/1, mono/stereo, byte reg, bit mask
+ */
+static int snd_pmac_burgundy_info_switch_b(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = stereo + 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_pmac_burgundy_get_switch_b(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff);
+	int lmask = kcontrol->private_value & 0xff;
+	int rmask = (kcontrol->private_value >> 8) & 0xff;
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	int val = snd_pmac_burgundy_rcb(chip, addr);
+	ucontrol->value.integer.value[0] = (val & lmask) ? 1 : 0;
+	if (stereo)
+		ucontrol->value.integer.value[1] = (val & rmask) ? 1 : 0;
+	return 0;
+}
+
+static int snd_pmac_burgundy_put_switch_b(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff);
+	int lmask = kcontrol->private_value & 0xff;
+	int rmask = (kcontrol->private_value >> 8) & 0xff;
+	int stereo = (kcontrol->private_value >> 24) & 1;
+	int val, oval;
+	oval = snd_pmac_burgundy_rcb(chip, addr);
+	val = oval & ~(lmask | rmask);
+	if (ucontrol->value.integer.value[0])
+		val |= lmask;
+	if (stereo && ucontrol->value.integer.value[1])
+		val |= rmask;
+	snd_pmac_burgundy_wcb(chip, addr, val);
+	return val != oval;
+}
+
+#define BURGUNDY_SWITCH_B(xname, xindex, addr, lmask, rmask, stereo) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\
+  .info = snd_pmac_burgundy_info_switch_b,\
+  .get = snd_pmac_burgundy_get_switch_b,\
+  .put = snd_pmac_burgundy_put_switch_b,\
+  .private_value = ((lmask) | ((rmask) << 8)\
+		| (ADDR2BASE(addr) << 16) | ((stereo) << 24)) }
+
+/*
+ * Burgundy mixers
+ */
+static struct snd_kcontrol_new snd_pmac_burgundy_mixers[] __devinitdata = {
+	BURGUNDY_VOLUME_W("Master Playback Volume", 0,
+			MASK_ADDR_BURGUNDY_MASTER_VOLUME, 8),
+	BURGUNDY_VOLUME_W("CD Capture Volume", 0,
+			MASK_ADDR_BURGUNDY_VOLCD, 16),
+	BURGUNDY_VOLUME_2B("Input Capture Volume", 0,
+			MASK_ADDR_BURGUNDY_VOLMIX01, 2),
+	BURGUNDY_VOLUME_2B("Mixer Playback Volume", 0,
+			MASK_ADDR_BURGUNDY_VOLMIX23, 0),
+	BURGUNDY_VOLUME_B("CD Gain Capture Volume", 0,
+			MASK_ADDR_BURGUNDY_GAINCD, 1, 0),
+	BURGUNDY_SWITCH_W("Master Capture Switch", 0,
+			MASK_ADDR_BURGUNDY_OUTPUTENABLES, 24, 0, 0),
+	BURGUNDY_SWITCH_W("CD Capture Switch", 0,
+			MASK_ADDR_BURGUNDY_CAPTURESELECTS, 0, 16, 1),
+	BURGUNDY_SWITCH_W("CD Playback Switch", 0,
+			MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 0, 16, 1),
+/*	BURGUNDY_SWITCH_W("Loop Capture Switch", 0,
+ *		MASK_ADDR_BURGUNDY_CAPTURESELECTS, 8, 24, 1),
+ *	BURGUNDY_SWITCH_B("Mixer out Capture Switch", 0,
+ *		MASK_ADDR_BURGUNDY_HOSTIFAD, 0x02, 0, 0),
+ *	BURGUNDY_SWITCH_B("Mixer Capture Switch", 0,
+ *		MASK_ADDR_BURGUNDY_HOSTIFAD, 0x01, 0, 0),
+ *	BURGUNDY_SWITCH_B("PCM out Capture Switch", 0,
+ *		MASK_ADDR_BURGUNDY_HOSTIFEH, 0x02, 0, 0),
+ */	BURGUNDY_SWITCH_B("PCM Capture Switch", 0,
+			MASK_ADDR_BURGUNDY_HOSTIFEH, 0x01, 0, 0)
+};
+static struct snd_kcontrol_new snd_pmac_burgundy_mixers_imac[] __devinitdata = {
+	BURGUNDY_VOLUME_W("Line in Capture Volume", 0,
+			MASK_ADDR_BURGUNDY_VOLLINE, 16),
+	BURGUNDY_VOLUME_W("Mic Capture Volume", 0,
+			MASK_ADDR_BURGUNDY_VOLMIC, 16),
+	BURGUNDY_VOLUME_B("Line in Gain Capture Volume", 0,
+			MASK_ADDR_BURGUNDY_GAINLINE, 1, 0),
+	BURGUNDY_VOLUME_B("Mic Gain Capture Volume", 0,
+			MASK_ADDR_BURGUNDY_GAINMIC, 1, 0),
+	BURGUNDY_VOLUME_B("Speaker Playback Volume", 0,
+			MASK_ADDR_BURGUNDY_ATTENSPEAKER, 1, 1),
+	BURGUNDY_VOLUME_B("Line out Playback Volume", 0,
+			MASK_ADDR_BURGUNDY_ATTENLINEOUT, 1, 1),
+	BURGUNDY_VOLUME_B("Headphone Playback Volume", 0,
+			MASK_ADDR_BURGUNDY_ATTENHP, 1, 1),
+	BURGUNDY_SWITCH_W("Line in Capture Switch", 0,
+			MASK_ADDR_BURGUNDY_CAPTURESELECTS, 1, 17, 1),
+	BURGUNDY_SWITCH_W("Mic Capture Switch", 0,
+			MASK_ADDR_BURGUNDY_CAPTURESELECTS, 2, 18, 1),
+	BURGUNDY_SWITCH_W("Line in Playback Switch", 0,
+			MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 1, 17, 1),
+	BURGUNDY_SWITCH_W("Mic Playback Switch", 0,
+			MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 2, 18, 1),
+	BURGUNDY_SWITCH_B("Mic Boost Capture Switch", 0,
+			MASK_ADDR_BURGUNDY_INPBOOST, 0x40, 0x80, 1)
+};
+static struct snd_kcontrol_new snd_pmac_burgundy_mixers_pmac[] __devinitdata = {
+	BURGUNDY_VOLUME_W("Line in Capture Volume", 0,
+			MASK_ADDR_BURGUNDY_VOLMIC, 16),
+	BURGUNDY_VOLUME_B("Line in Gain Capture Volume", 0,
+			MASK_ADDR_BURGUNDY_GAINMIC, 1, 0),
+	BURGUNDY_VOLUME_B("Speaker Playback Volume", 0,
+			MASK_ADDR_BURGUNDY_ATTENMONO, 0, 1),
+	BURGUNDY_VOLUME_B("Line out Playback Volume", 0,
+			MASK_ADDR_BURGUNDY_ATTENSPEAKER, 1, 1),
+	BURGUNDY_SWITCH_W("Line in Capture Switch", 0,
+			MASK_ADDR_BURGUNDY_CAPTURESELECTS, 2, 18, 1),
+	BURGUNDY_SWITCH_W("Line in Playback Switch", 0,
+			MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 2, 18, 1),
+/*	BURGUNDY_SWITCH_B("Line in Boost Capture Switch", 0,
+ *		MASK_ADDR_BURGUNDY_INPBOOST, 0x40, 0x80, 1) */
+};
+static struct snd_kcontrol_new snd_pmac_burgundy_master_sw_imac __devinitdata =
+BURGUNDY_SWITCH_B("Master Playback Switch", 0,
+	MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+	BURGUNDY_OUTPUT_LEFT | BURGUNDY_LINEOUT_LEFT | BURGUNDY_HP_LEFT,
+	BURGUNDY_OUTPUT_RIGHT | BURGUNDY_LINEOUT_RIGHT | BURGUNDY_HP_RIGHT, 1);
+static struct snd_kcontrol_new snd_pmac_burgundy_master_sw_pmac __devinitdata =
+BURGUNDY_SWITCH_B("Master Playback Switch", 0,
+	MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+	BURGUNDY_OUTPUT_INTERN
+	| BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1);
+static struct snd_kcontrol_new snd_pmac_burgundy_speaker_sw_imac __devinitdata =
+BURGUNDY_SWITCH_B("Speaker Playback Switch", 0,
+	MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+	BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1);
+static struct snd_kcontrol_new snd_pmac_burgundy_speaker_sw_pmac __devinitdata =
+BURGUNDY_SWITCH_B("Speaker Playback Switch", 0,
+	MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+	BURGUNDY_OUTPUT_INTERN, 0, 0);
+static struct snd_kcontrol_new snd_pmac_burgundy_line_sw_imac __devinitdata =
+BURGUNDY_SWITCH_B("Line out Playback Switch", 0,
+	MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+	BURGUNDY_LINEOUT_LEFT, BURGUNDY_LINEOUT_RIGHT, 1);
+static struct snd_kcontrol_new snd_pmac_burgundy_line_sw_pmac __devinitdata =
+BURGUNDY_SWITCH_B("Line out Playback Switch", 0,
+	MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+	BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1);
+static struct snd_kcontrol_new snd_pmac_burgundy_hp_sw_imac __devinitdata =
+BURGUNDY_SWITCH_B("Headphone Playback Switch", 0,
+	MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+	BURGUNDY_HP_LEFT, BURGUNDY_HP_RIGHT, 1);
+
+
+#ifdef PMAC_SUPPORT_AUTOMUTE
+/*
+ * auto-mute stuffs
+ */
+static int snd_pmac_burgundy_detect_headphone(struct snd_pmac *chip)
+{
+	return (in_le32(&chip->awacs->codec_stat) & chip->hp_stat_mask) ? 1 : 0;
+}
+
+static void snd_pmac_burgundy_update_automute(struct snd_pmac *chip, int do_notify)
+{
+	if (chip->auto_mute) {
+		int imac = of_machine_is_compatible("iMac");
+		int reg, oreg;
+		reg = oreg = snd_pmac_burgundy_rcb(chip,
+				MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES);
+		reg &= imac ? ~(BURGUNDY_OUTPUT_LEFT | BURGUNDY_OUTPUT_RIGHT
+				| BURGUNDY_HP_LEFT | BURGUNDY_HP_RIGHT)
+			: ~(BURGUNDY_OUTPUT_LEFT | BURGUNDY_OUTPUT_RIGHT
+				| BURGUNDY_OUTPUT_INTERN);
+		if (snd_pmac_burgundy_detect_headphone(chip))
+			reg |= imac ? (BURGUNDY_HP_LEFT | BURGUNDY_HP_RIGHT)
+				: (BURGUNDY_OUTPUT_LEFT
+					| BURGUNDY_OUTPUT_RIGHT);
+		else
+			reg |= imac ? (BURGUNDY_OUTPUT_LEFT
+					| BURGUNDY_OUTPUT_RIGHT)
+				: (BURGUNDY_OUTPUT_INTERN);
+		if (do_notify && reg == oreg)
+			return;
+		snd_pmac_burgundy_wcb(chip,
+				MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, reg);
+		if (do_notify) {
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->master_sw_ctl->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->speaker_sw_ctl->id);
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->hp_detect_ctl->id);
+		}
+	}
+}
+#endif /* PMAC_SUPPORT_AUTOMUTE */
+
+
+/*
+ * initialize burgundy
+ */
+int __devinit snd_pmac_burgundy_init(struct snd_pmac *chip)
+{
+	int imac = of_machine_is_compatible("iMac");
+	int i, err;
+
+	/* Checks to see the chip is alive and kicking */
+	if ((in_le32(&chip->awacs->codec_ctrl) & MASK_ERRCODE) == 0xf0000) {
+		printk(KERN_WARNING "pmac burgundy: disabled by MacOS :-(\n");
+		return 1;
+	}
+
+	snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_OUTPUTENABLES,
+			   DEF_BURGUNDY_OUTPUTENABLES);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+			   DEF_BURGUNDY_MORE_OUTPUTENABLES);
+	snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_OUTPUTSELECTS,
+			   DEF_BURGUNDY_OUTPUTSELECTS);
+
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_INPSEL21,
+			   DEF_BURGUNDY_INPSEL21);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_INPSEL3,
+			   imac ? DEF_BURGUNDY_INPSEL3_IMAC
+			   : DEF_BURGUNDY_INPSEL3_PMAC);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINCD,
+			   DEF_BURGUNDY_GAINCD);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINLINE,
+			   DEF_BURGUNDY_GAINLINE);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINMIC,
+			   DEF_BURGUNDY_GAINMIC);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINMODEM,
+			   DEF_BURGUNDY_GAINMODEM);
+
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENSPEAKER,
+			   DEF_BURGUNDY_ATTENSPEAKER);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENLINEOUT,
+			   DEF_BURGUNDY_ATTENLINEOUT);
+	snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENHP,
+			   DEF_BURGUNDY_ATTENHP);
+
+	snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_MASTER_VOLUME,
+			   DEF_BURGUNDY_MASTER_VOLUME);
+	snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLCD,
+			   DEF_BURGUNDY_VOLCD);
+	snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLLINE,
+			   DEF_BURGUNDY_VOLLINE);
+	snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLMIC,
+			   DEF_BURGUNDY_VOLMIC);
+
+	if (chip->hp_stat_mask == 0) {
+		/* set headphone-jack detection bit */
+		if (imac)
+			chip->hp_stat_mask = BURGUNDY_HPDETECT_IMAC_UPPER
+				| BURGUNDY_HPDETECT_IMAC_LOWER
+				| BURGUNDY_HPDETECT_IMAC_SIDE;
+		else
+			chip->hp_stat_mask = BURGUNDY_HPDETECT_PMAC_BACK;
+	}
+	/*
+	 * build burgundy mixers
+	 */
+	strcpy(chip->card->mixername, "PowerMac Burgundy");
+
+	for (i = 0; i < ARRAY_SIZE(snd_pmac_burgundy_mixers); i++) {
+		err = snd_ctl_add(chip->card,
+		    snd_ctl_new1(&snd_pmac_burgundy_mixers[i], chip));
+		if (err < 0)
+			return err;
+	}
+	for (i = 0; i < (imac ? ARRAY_SIZE(snd_pmac_burgundy_mixers_imac)
+			: ARRAY_SIZE(snd_pmac_burgundy_mixers_pmac)); i++) {
+		err = snd_ctl_add(chip->card,
+		    snd_ctl_new1(imac ? &snd_pmac_burgundy_mixers_imac[i]
+		    : &snd_pmac_burgundy_mixers_pmac[i], chip));
+		if (err < 0)
+			return err;
+	}
+	chip->master_sw_ctl = snd_ctl_new1(imac
+			? &snd_pmac_burgundy_master_sw_imac
+			: &snd_pmac_burgundy_master_sw_pmac, chip);
+	err = snd_ctl_add(chip->card, chip->master_sw_ctl);
+	if (err < 0)
+		return err;
+	chip->master_sw_ctl = snd_ctl_new1(imac
+			? &snd_pmac_burgundy_line_sw_imac
+			: &snd_pmac_burgundy_line_sw_pmac, chip);
+	err = snd_ctl_add(chip->card, chip->master_sw_ctl);
+	if (err < 0)
+		return err;
+	if (imac) {
+		chip->master_sw_ctl = snd_ctl_new1(
+				&snd_pmac_burgundy_hp_sw_imac, chip);
+		err = snd_ctl_add(chip->card, chip->master_sw_ctl);
+		if (err < 0)
+			return err;
+	}
+	chip->speaker_sw_ctl = snd_ctl_new1(imac
+			? &snd_pmac_burgundy_speaker_sw_imac
+			: &snd_pmac_burgundy_speaker_sw_pmac, chip);
+	err = snd_ctl_add(chip->card, chip->speaker_sw_ctl);
+	if (err < 0)
+		return err;
+#ifdef PMAC_SUPPORT_AUTOMUTE
+	err = snd_pmac_add_automute(chip);
+	if (err < 0)
+		return err;
+
+	chip->detect_headphone = snd_pmac_burgundy_detect_headphone;
+	chip->update_automute = snd_pmac_burgundy_update_automute;
+	snd_pmac_burgundy_update_automute(chip, 0); /* update the status only */
+#endif
+
+	return 0;
+}
diff --git a/linux/sound/ppc/burgundy.h b/linux/sound/ppc/burgundy.h
new file mode 100644
index 0000000..7a7f9cf
--- /dev/null
+++ b/linux/sound/ppc/burgundy.h
@@ -0,0 +1,114 @@
+/*
+ * Driver for PowerMac Burgundy onboard soundchips
+ * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de>
+ *   based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#ifndef __BURGUNDY_H
+#define __BURGUNDY_H
+
+#define MASK_ADDR_BURGUNDY_INPBOOST (0x10 << 12)
+#define MASK_ADDR_BURGUNDY_INPSEL21 (0x11 << 12)
+#define MASK_ADDR_BURGUNDY_INPSEL3 (0x12 << 12)
+
+#define MASK_ADDR_BURGUNDY_GAINCH1 (0x13 << 12)
+#define MASK_ADDR_BURGUNDY_GAINCH2 (0x14 << 12)
+#define MASK_ADDR_BURGUNDY_GAINCH3 (0x15 << 12)
+#define MASK_ADDR_BURGUNDY_GAINCH4 (0x16 << 12)
+
+#define MASK_ADDR_BURGUNDY_VOLCH1 (0x20 << 12)
+#define MASK_ADDR_BURGUNDY_VOLCH2 (0x21 << 12)
+#define MASK_ADDR_BURGUNDY_VOLCH3 (0x22 << 12)
+#define MASK_ADDR_BURGUNDY_VOLCH4 (0x23 << 12)
+
+#define MASK_ADDR_BURGUNDY_CAPTURESELECTS (0x2A << 12)
+#define MASK_ADDR_BURGUNDY_OUTPUTSELECTS (0x2B << 12)
+#define MASK_ADDR_BURGUNDY_VOLMIX01 (0x2D << 12)
+#define MASK_ADDR_BURGUNDY_VOLMIX23 (0x2E << 12)
+#define MASK_ADDR_BURGUNDY_OUTPUTENABLES (0x2F << 12)
+
+#define MASK_ADDR_BURGUNDY_MASTER_VOLUME (0x30 << 12)
+
+#define MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES (0x60 << 12)
+
+#define MASK_ADDR_BURGUNDY_ATTENSPEAKER (0x62 << 12)
+#define MASK_ADDR_BURGUNDY_ATTENLINEOUT (0x63 << 12)
+#define MASK_ADDR_BURGUNDY_ATTENHP (0x64 << 12)
+#define MASK_ADDR_BURGUNDY_ATTENMONO (0x65 << 12)
+
+#define MASK_ADDR_BURGUNDY_HOSTIFAD (0x78 << 12)
+#define MASK_ADDR_BURGUNDY_HOSTIFEH (0x79 << 12)
+
+#define MASK_ADDR_BURGUNDY_VOLCD (MASK_ADDR_BURGUNDY_VOLCH1)
+#define MASK_ADDR_BURGUNDY_VOLLINE (MASK_ADDR_BURGUNDY_VOLCH2)
+#define MASK_ADDR_BURGUNDY_VOLMIC (MASK_ADDR_BURGUNDY_VOLCH3)
+#define MASK_ADDR_BURGUNDY_VOLMODEM (MASK_ADDR_BURGUNDY_VOLCH4)
+
+#define MASK_ADDR_BURGUNDY_GAINCD (MASK_ADDR_BURGUNDY_GAINCH1)
+#define MASK_ADDR_BURGUNDY_GAINLINE (MASK_ADDR_BURGUNDY_GAINCH2)
+#define MASK_ADDR_BURGUNDY_GAINMIC (MASK_ADDR_BURGUNDY_GAINCH3)
+#define MASK_ADDR_BURGUNDY_GAINMODEM (MASK_ADDR_BURGUNDY_VOLCH4)
+
+
+/* These are all default values for the burgundy */
+#define DEF_BURGUNDY_INPSEL21 (0xAA)
+#define DEF_BURGUNDY_INPSEL3_IMAC (0x0A)
+#define DEF_BURGUNDY_INPSEL3_PMAC (0x05)
+
+#define DEF_BURGUNDY_GAINCD (0x33)
+#define DEF_BURGUNDY_GAINLINE (0x44)
+#define DEF_BURGUNDY_GAINMIC (0x44)
+#define DEF_BURGUNDY_GAINMODEM (0x06)
+
+/* Remember: lowest volume here is 0x9B (155) */
+#define DEF_BURGUNDY_VOLCD (0xCCCCCCCC)
+#define DEF_BURGUNDY_VOLLINE (0x00000000)
+#define DEF_BURGUNDY_VOLMIC (0x00000000)
+#define DEF_BURGUNDY_VOLMODEM (0xCCCCCCCC)
+
+#define DEF_BURGUNDY_OUTPUTSELECTS (0x010F010F)
+#define DEF_BURGUNDY_OUTPUTENABLES (0x0100000A)
+
+/* #define DEF_BURGUNDY_MASTER_VOLUME (0xFFFFFFFF) */ /* too loud */
+#define DEF_BURGUNDY_MASTER_VOLUME (0xDDDDDDDD)
+
+#define DEF_BURGUNDY_MORE_OUTPUTENABLES (0x7E)
+
+#define DEF_BURGUNDY_ATTENSPEAKER (0x44)
+#define DEF_BURGUNDY_ATTENLINEOUT (0xCC)
+#define DEF_BURGUNDY_ATTENHP (0xCC)
+
+/* MORE_OUTPUTENABLES bits */
+#define BURGUNDY_OUTPUT_LEFT	0x02
+#define BURGUNDY_OUTPUT_RIGHT	0x04
+#define BURGUNDY_LINEOUT_LEFT	0x08
+#define BURGUNDY_LINEOUT_RIGHT	0x10
+#define BURGUNDY_HP_LEFT	0x20
+#define BURGUNDY_HP_RIGHT	0x40
+#define BURGUNDY_OUTPUT_INTERN	0x80
+
+/* Headphone detection bits */
+#define BURGUNDY_HPDETECT_PMAC_BACK	0x04
+#define BURGUNDY_HPDETECT_IMAC_SIDE	0x04
+#define BURGUNDY_HPDETECT_IMAC_UPPER	0x08
+#define BURGUNDY_HPDETECT_IMAC_LOWER	0x01
+
+/* Volume offset */
+#define BURGUNDY_VOLUME_OFFSET	155
+
+#endif /* __BURGUNDY_H */
diff --git a/linux/sound/ppc/daca.c b/linux/sound/ppc/daca.c
new file mode 100644
index 0000000..24200b7
--- /dev/null
+++ b/linux/sound/ppc/daca.c
@@ -0,0 +1,282 @@
+/*
+ * PMac DACA lowlevel functions
+ *
+ * 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/init.h>
+#include <linux/i2c.h>
+#include <linux/kmod.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include "pmac.h"
+
+/* i2c address */
+#define DACA_I2C_ADDR	0x4d
+
+/* registers */
+#define DACA_REG_SR	0x01
+#define DACA_REG_AVOL	0x02
+#define DACA_REG_GCFG	0x03
+
+/* maximum volume value */
+#define DACA_VOL_MAX	0x38
+
+
+struct pmac_daca {
+	struct pmac_keywest i2c;
+	int left_vol, right_vol;
+	unsigned int deemphasis : 1;
+	unsigned int amp_on : 1;
+};
+
+
+/*
+ * initialize / detect DACA
+ */
+static int daca_init_client(struct pmac_keywest *i2c)
+{
+	unsigned short wdata = 0x00;
+	/* SR: no swap, 1bit delay, 32-48kHz */
+	/* GCFG: power amp inverted, DAC on */
+	if (i2c_smbus_write_byte_data(i2c->client, DACA_REG_SR, 0x08) < 0 ||
+	    i2c_smbus_write_byte_data(i2c->client, DACA_REG_GCFG, 0x05) < 0)
+		return -EINVAL;
+	return i2c_smbus_write_block_data(i2c->client, DACA_REG_AVOL,
+					  2, (unsigned char*)&wdata);
+}
+
+/*
+ * update volume
+ */
+static int daca_set_volume(struct pmac_daca *mix)
+{
+	unsigned char data[2];
+  
+	if (! mix->i2c.client)
+		return -ENODEV;
+  
+	if (mix->left_vol > DACA_VOL_MAX)
+		data[0] = DACA_VOL_MAX;
+	else
+		data[0] = mix->left_vol;
+	if (mix->right_vol > DACA_VOL_MAX)
+		data[1] = DACA_VOL_MAX;
+	else
+		data[1] = mix->right_vol;
+	data[1] |= mix->deemphasis ? 0x40 : 0;
+	if (i2c_smbus_write_block_data(mix->i2c.client, DACA_REG_AVOL,
+				       2, data) < 0) {
+		snd_printk(KERN_ERR "failed to set volume \n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/* deemphasis switch */
+#define daca_info_deemphasis		snd_ctl_boolean_mono_info
+
+static int daca_get_deemphasis(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_daca *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->deemphasis ? 1 : 0;
+	return 0;
+}
+
+static int daca_put_deemphasis(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_daca *mix;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	change = mix->deemphasis != ucontrol->value.integer.value[0];
+	if (change) {
+		mix->deemphasis = !!ucontrol->value.integer.value[0];
+		daca_set_volume(mix);
+	}
+	return change;
+}
+
+/* output volume */
+static int daca_info_volume(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = DACA_VOL_MAX;
+	return 0;
+}
+
+static int daca_get_volume(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_daca *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->left_vol;
+	ucontrol->value.integer.value[1] = mix->right_vol;
+	return 0;
+}
+
+static int daca_put_volume(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_daca *mix;
+	unsigned int vol[2];
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	vol[0] = ucontrol->value.integer.value[0];
+	vol[1] = ucontrol->value.integer.value[1];
+	if (vol[0] > DACA_VOL_MAX || vol[1] > DACA_VOL_MAX)
+		return -EINVAL;
+	change = mix->left_vol != vol[0] ||
+		mix->right_vol != vol[1];
+	if (change) {
+		mix->left_vol = vol[0];
+		mix->right_vol = vol[1];
+		daca_set_volume(mix);
+	}
+	return change;
+}
+
+/* amplifier switch */
+#define daca_info_amp	daca_info_deemphasis
+
+static int daca_get_amp(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_daca *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->amp_on ? 1 : 0;
+	return 0;
+}
+
+static int daca_put_amp(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_daca *mix;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	change = mix->amp_on != ucontrol->value.integer.value[0];
+	if (change) {
+		mix->amp_on = !!ucontrol->value.integer.value[0];
+		i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_GCFG,
+					  mix->amp_on ? 0x05 : 0x04);
+	}
+	return change;
+}
+
+static struct snd_kcontrol_new daca_mixers[] = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Deemphasis Switch",
+	  .info = daca_info_deemphasis,
+	  .get = daca_get_deemphasis,
+	  .put = daca_put_deemphasis
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Master Playback Volume",
+	  .info = daca_info_volume,
+	  .get = daca_get_volume,
+	  .put = daca_put_volume
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Power Amplifier Switch",
+	  .info = daca_info_amp,
+	  .get = daca_get_amp,
+	  .put = daca_put_amp
+	},
+};
+
+
+#ifdef CONFIG_PM
+static void daca_resume(struct snd_pmac *chip)
+{
+	struct pmac_daca *mix = chip->mixer_data;
+	i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_SR, 0x08);
+	i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_GCFG,
+				  mix->amp_on ? 0x05 : 0x04);
+	daca_set_volume(mix);
+}
+#endif /* CONFIG_PM */
+
+
+static void daca_cleanup(struct snd_pmac *chip)
+{
+	struct pmac_daca *mix = chip->mixer_data;
+	if (! mix)
+		return;
+	snd_pmac_keywest_cleanup(&mix->i2c);
+	kfree(mix);
+	chip->mixer_data = NULL;
+}
+
+/* exported */
+int __devinit snd_pmac_daca_init(struct snd_pmac *chip)
+{
+	int i, err;
+	struct pmac_daca *mix;
+
+	request_module("i2c-powermac");
+
+	mix = kzalloc(sizeof(*mix), GFP_KERNEL);
+	if (! mix)
+		return -ENOMEM;
+	chip->mixer_data = mix;
+	chip->mixer_free = daca_cleanup;
+	mix->amp_on = 1; /* default on */
+
+	mix->i2c.addr = DACA_I2C_ADDR;
+	mix->i2c.init_client = daca_init_client;
+	mix->i2c.name = "DACA";
+	if ((err = snd_pmac_keywest_init(&mix->i2c)) < 0)
+		return err;
+
+	/*
+	 * build mixers
+	 */
+	strcpy(chip->card->mixername, "PowerMac DACA");
+
+	for (i = 0; i < ARRAY_SIZE(daca_mixers); i++) {
+		if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&daca_mixers[i], chip))) < 0)
+			return err;
+	}
+
+#ifdef CONFIG_PM
+	chip->resume = daca_resume;
+#endif
+
+	return 0;
+}
diff --git a/linux/sound/ppc/keywest.c b/linux/sound/ppc/keywest.c
new file mode 100644
index 0000000..8f064c7
--- /dev/null
+++ b/linux/sound/ppc/keywest.c
@@ -0,0 +1,148 @@
+/*
+ * common keywest i2c layer
+ *
+ * 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/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include "pmac.h"
+
+/*
+ * we have to keep a static variable here since i2c attach_adapter
+ * callback cannot pass a private data.
+ */
+static struct pmac_keywest *keywest_ctx;
+
+
+static int keywest_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	i2c_set_clientdata(client, keywest_ctx);
+	return 0;
+}
+
+/*
+ * This is kind of a hack, best would be to turn powermac to fixed i2c
+ * bus numbers and declare the sound device as part of platform
+ * initialization
+ */
+static int keywest_attach_adapter(struct i2c_adapter *adapter)
+{
+	struct i2c_board_info info;
+
+	if (! keywest_ctx)
+		return -EINVAL;
+
+	if (strncmp(adapter->name, "mac-io", 6))
+		return 0; /* ignored */
+
+	memset(&info, 0, sizeof(struct i2c_board_info));
+	strlcpy(info.type, "keywest", I2C_NAME_SIZE);
+	info.addr = keywest_ctx->addr;
+	keywest_ctx->client = i2c_new_device(adapter, &info);
+	if (!keywest_ctx->client)
+		return -ENODEV;
+	/*
+	 * We know the driver is already loaded, so the device should be
+	 * already bound. If not it means binding failed, and then there
+	 * is no point in keeping the device instantiated.
+	 */
+	if (!keywest_ctx->client->driver) {
+		i2c_unregister_device(keywest_ctx->client);
+		keywest_ctx->client = NULL;
+		return -ENODEV;
+	}
+	
+	/*
+	 * Let i2c-core delete that device on driver removal.
+	 * This is safe because i2c-core holds the core_lock mutex for us.
+	 */
+	list_add_tail(&keywest_ctx->client->detected,
+		      &keywest_ctx->client->driver->clients);
+	return 0;
+}
+
+static int keywest_remove(struct i2c_client *client)
+{
+	i2c_set_clientdata(client, NULL);
+	if (! keywest_ctx)
+		return 0;
+	if (client == keywest_ctx->client)
+		keywest_ctx->client = NULL;
+
+	return 0;
+}
+
+
+static const struct i2c_device_id keywest_i2c_id[] = {
+	{ "keywest", 0 },
+	{ }
+};
+
+static struct i2c_driver keywest_driver = {
+	.driver = {
+		.name = "PMac Keywest Audio",
+	},
+	.attach_adapter = keywest_attach_adapter,
+	.probe = keywest_probe,
+	.remove = keywest_remove,
+	.id_table = keywest_i2c_id,
+};
+
+/* exported */
+void snd_pmac_keywest_cleanup(struct pmac_keywest *i2c)
+{
+	if (keywest_ctx && keywest_ctx == i2c) {
+		i2c_del_driver(&keywest_driver);
+		keywest_ctx = NULL;
+	}
+}
+
+int __devinit snd_pmac_tumbler_post_init(void)
+{
+	int err;
+	
+	if (!keywest_ctx || !keywest_ctx->client)
+		return -ENXIO;
+
+	if ((err = keywest_ctx->init_client(keywest_ctx)) < 0) {
+		snd_printk(KERN_ERR "tumbler: %i :cannot initialize the MCS\n", err);
+		return err;
+	}
+	return 0;
+}
+
+/* exported */
+int __devinit snd_pmac_keywest_init(struct pmac_keywest *i2c)
+{
+	int err;
+
+	if (keywest_ctx)
+		return -EBUSY;
+
+	keywest_ctx = i2c;
+
+	if ((err = i2c_add_driver(&keywest_driver))) {
+		snd_printk(KERN_ERR "cannot register keywest i2c driver\n");
+		return err;
+	}
+	return 0;
+}
diff --git a/linux/sound/ppc/pmac.c b/linux/sound/ppc/pmac.c
new file mode 100644
index 0000000..b47cfd4
--- /dev/null
+++ b/linux/sound/ppc/pmac.c
@@ -0,0 +1,1409 @@
+/*
+ * PMac DBDMA lowlevel functions
+ *
+ * Copyright (c) by Takashi Iwai <tiwai@suse.de>
+ * code based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include "pmac.h"
+#include <sound/pcm_params.h>
+#include <asm/pmac_feature.h>
+#include <asm/pci-bridge.h>
+
+
+/* fixed frequency table for awacs, screamer, burgundy, DACA (44100 max) */
+static int awacs_freqs[8] = {
+	44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350
+};
+/* fixed frequency table for tumbler */
+static int tumbler_freqs[1] = {
+	44100
+};
+
+
+/*
+ * we will allocate a single 'emergency' dbdma cmd block to use if the
+ * tx status comes up "DEAD".  This happens on some PowerComputing Pmac
+ * clones, either owing to a bug in dbdma or some interaction between
+ * IDE and sound.  However, this measure would deal with DEAD status if
+ * it appeared elsewhere.
+ */
+static struct pmac_dbdma emergency_dbdma;
+static int emergency_in_use;
+
+
+/*
+ * allocate DBDMA command arrays
+ */
+static int snd_pmac_dbdma_alloc(struct snd_pmac *chip, struct pmac_dbdma *rec, int size)
+{
+	unsigned int rsize = sizeof(struct dbdma_cmd) * (size + 1);
+
+	rec->space = dma_alloc_coherent(&chip->pdev->dev, rsize,
+					&rec->dma_base, GFP_KERNEL);
+	if (rec->space == NULL)
+		return -ENOMEM;
+	rec->size = size;
+	memset(rec->space, 0, rsize);
+	rec->cmds = (void __iomem *)DBDMA_ALIGN(rec->space);
+	rec->addr = rec->dma_base + (unsigned long)((char *)rec->cmds - (char *)rec->space);
+
+	return 0;
+}
+
+static void snd_pmac_dbdma_free(struct snd_pmac *chip, struct pmac_dbdma *rec)
+{
+	if (rec->space) {
+		unsigned int rsize = sizeof(struct dbdma_cmd) * (rec->size + 1);
+
+		dma_free_coherent(&chip->pdev->dev, rsize, rec->space, rec->dma_base);
+	}
+}
+
+
+/*
+ * pcm stuff
+ */
+
+/*
+ * look up frequency table
+ */
+
+unsigned int snd_pmac_rate_index(struct snd_pmac *chip, struct pmac_stream *rec, unsigned int rate)
+{
+	int i, ok, found;
+
+	ok = rec->cur_freqs;
+	if (rate > chip->freq_table[0])
+		return 0;
+	found = 0;
+	for (i = 0; i < chip->num_freqs; i++, ok >>= 1) {
+		if (! (ok & 1)) continue;
+		found = i;
+		if (rate >= chip->freq_table[i])
+			break;
+	}
+	return found;
+}
+
+/*
+ * check whether another stream is active
+ */
+static inline int another_stream(int stream)
+{
+	return (stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+}
+
+/*
+ * allocate buffers
+ */
+static int snd_pmac_pcm_hw_params(struct snd_pcm_substream *subs,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw_params));
+}
+
+/*
+ * release buffers
+ */
+static int snd_pmac_pcm_hw_free(struct snd_pcm_substream *subs)
+{
+	snd_pcm_lib_free_pages(subs);
+	return 0;
+}
+
+/*
+ * get a stream of the opposite direction
+ */
+static struct pmac_stream *snd_pmac_get_stream(struct snd_pmac *chip, int stream)
+{
+	switch (stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		return &chip->playback;
+	case SNDRV_PCM_STREAM_CAPTURE:
+		return &chip->capture;
+	default:
+		snd_BUG();
+		return NULL;
+	}
+}
+
+/*
+ * wait while run status is on
+ */
+static inline void
+snd_pmac_wait_ack(struct pmac_stream *rec)
+{
+	int timeout = 50000;
+	while ((in_le32(&rec->dma->status) & RUN) && timeout-- > 0)
+		udelay(1);
+}
+
+/*
+ * set the format and rate to the chip.
+ * call the lowlevel function if defined (e.g. for AWACS).
+ */
+static void snd_pmac_pcm_set_format(struct snd_pmac *chip)
+{
+	/* set up frequency and format */
+	out_le32(&chip->awacs->control, chip->control_mask | (chip->rate_index << 8));
+	out_le32(&chip->awacs->byteswap, chip->format == SNDRV_PCM_FORMAT_S16_LE ? 1 : 0);
+	if (chip->set_format)
+		chip->set_format(chip);
+}
+
+/*
+ * stop the DMA transfer
+ */
+static inline void snd_pmac_dma_stop(struct pmac_stream *rec)
+{
+	out_le32(&rec->dma->control, (RUN|WAKE|FLUSH|PAUSE) << 16);
+	snd_pmac_wait_ack(rec);
+}
+
+/*
+ * set the command pointer address
+ */
+static inline void snd_pmac_dma_set_command(struct pmac_stream *rec, struct pmac_dbdma *cmd)
+{
+	out_le32(&rec->dma->cmdptr, cmd->addr);
+}
+
+/*
+ * start the DMA
+ */
+static inline void snd_pmac_dma_run(struct pmac_stream *rec, int status)
+{
+	out_le32(&rec->dma->control, status | (status << 16));
+}
+
+
+/*
+ * prepare playback/capture stream
+ */
+static int snd_pmac_pcm_prepare(struct snd_pmac *chip, struct pmac_stream *rec, struct snd_pcm_substream *subs)
+{
+	int i;
+	volatile struct dbdma_cmd __iomem *cp;
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	int rate_index;
+	long offset;
+	struct pmac_stream *astr;
+
+	rec->dma_size = snd_pcm_lib_buffer_bytes(subs);
+	rec->period_size = snd_pcm_lib_period_bytes(subs);
+	rec->nperiods = rec->dma_size / rec->period_size;
+	rec->cur_period = 0;
+	rate_index = snd_pmac_rate_index(chip, rec, runtime->rate);
+
+	/* set up constraints */
+	astr = snd_pmac_get_stream(chip, another_stream(rec->stream));
+	if (! astr)
+		return -EINVAL;
+	astr->cur_freqs = 1 << rate_index;
+	astr->cur_formats = 1 << runtime->format;
+	chip->rate_index = rate_index;
+	chip->format = runtime->format;
+
+	/* We really want to execute a DMA stop command, after the AWACS
+	 * is initialized.
+	 * For reasons I don't understand, it stops the hissing noise
+	 * common to many PowerBook G3 systems and random noise otherwise
+	 * captured on iBook2's about every third time. -ReneR
+	 */
+	spin_lock_irq(&chip->reg_lock);
+	snd_pmac_dma_stop(rec);
+	st_le16(&chip->extra_dma.cmds->command, DBDMA_STOP);
+	snd_pmac_dma_set_command(rec, &chip->extra_dma);
+	snd_pmac_dma_run(rec, RUN);
+	spin_unlock_irq(&chip->reg_lock);
+	mdelay(5);
+	spin_lock_irq(&chip->reg_lock);
+	/* continuous DMA memory type doesn't provide the physical address,
+	 * so we need to resolve the address here...
+	 */
+	offset = runtime->dma_addr;
+	for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) {
+		st_le32(&cp->phy_addr, offset);
+		st_le16(&cp->req_count, rec->period_size);
+		/*st_le16(&cp->res_count, 0);*/
+		st_le16(&cp->xfer_status, 0);
+		offset += rec->period_size;
+	}
+	/* make loop */
+	st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS);
+	st_le32(&cp->cmd_dep, rec->cmd.addr);
+
+	snd_pmac_dma_stop(rec);
+	snd_pmac_dma_set_command(rec, &rec->cmd);
+	spin_unlock_irq(&chip->reg_lock);
+
+	return 0;
+}
+
+
+/*
+ * PCM trigger/stop
+ */
+static int snd_pmac_pcm_trigger(struct snd_pmac *chip, struct pmac_stream *rec,
+				struct snd_pcm_substream *subs, int cmd)
+{
+	volatile struct dbdma_cmd __iomem *cp;
+	int i, command;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (rec->running)
+			return -EBUSY;
+		command = (subs->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+			   OUTPUT_MORE : INPUT_MORE) + INTR_ALWAYS;
+		spin_lock(&chip->reg_lock);
+		snd_pmac_beep_stop(chip);
+		snd_pmac_pcm_set_format(chip);
+		for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++)
+			out_le16(&cp->command, command);
+		snd_pmac_dma_set_command(rec, &rec->cmd);
+		(void)in_le32(&rec->dma->status);
+		snd_pmac_dma_run(rec, RUN|WAKE);
+		rec->running = 1;
+		spin_unlock(&chip->reg_lock);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		spin_lock(&chip->reg_lock);
+		rec->running = 0;
+		/*printk(KERN_DEBUG "stopped!!\n");*/
+		snd_pmac_dma_stop(rec);
+		for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++)
+			out_le16(&cp->command, DBDMA_STOP);
+		spin_unlock(&chip->reg_lock);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * return the current pointer
+ */
+inline
+static snd_pcm_uframes_t snd_pmac_pcm_pointer(struct snd_pmac *chip,
+					      struct pmac_stream *rec,
+					      struct snd_pcm_substream *subs)
+{
+	int count = 0;
+
+#if 1 /* hmm.. how can we get the current dma pointer?? */
+	int stat;
+	volatile struct dbdma_cmd __iomem *cp = &rec->cmd.cmds[rec->cur_period];
+	stat = ld_le16(&cp->xfer_status);
+	if (stat & (ACTIVE|DEAD)) {
+		count = in_le16(&cp->res_count);
+		if (count)
+			count = rec->period_size - count;
+	}
+#endif
+	count += rec->cur_period * rec->period_size;
+	/*printk(KERN_DEBUG "pointer=%d\n", count);*/
+	return bytes_to_frames(subs->runtime, count);
+}
+
+/*
+ * playback
+ */
+
+static int snd_pmac_playback_prepare(struct snd_pcm_substream *subs)
+{
+	struct snd_pmac *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_prepare(chip, &chip->playback, subs);
+}
+
+static int snd_pmac_playback_trigger(struct snd_pcm_substream *subs,
+				     int cmd)
+{
+	struct snd_pmac *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_trigger(chip, &chip->playback, subs, cmd);
+}
+
+static snd_pcm_uframes_t snd_pmac_playback_pointer(struct snd_pcm_substream *subs)
+{
+	struct snd_pmac *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_pointer(chip, &chip->playback, subs);
+}
+
+
+/*
+ * capture
+ */
+
+static int snd_pmac_capture_prepare(struct snd_pcm_substream *subs)
+{
+	struct snd_pmac *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_prepare(chip, &chip->capture, subs);
+}
+
+static int snd_pmac_capture_trigger(struct snd_pcm_substream *subs,
+				    int cmd)
+{
+	struct snd_pmac *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_trigger(chip, &chip->capture, subs, cmd);
+}
+
+static snd_pcm_uframes_t snd_pmac_capture_pointer(struct snd_pcm_substream *subs)
+{
+	struct snd_pmac *chip = snd_pcm_substream_chip(subs);
+	return snd_pmac_pcm_pointer(chip, &chip->capture, subs);
+}
+
+
+/*
+ * Handle DEAD DMA transfers:
+ * if the TX status comes up "DEAD" - reported on some Power Computing machines
+ * we need to re-start the dbdma - but from a different physical start address
+ * and with a different transfer length.  It would get very messy to do this
+ * with the normal dbdma_cmd blocks - we would have to re-write the buffer start
+ * addresses each time.  So, we will keep a single dbdma_cmd block which can be
+ * fiddled with.
+ * When DEAD status is first reported the content of the faulted dbdma block is
+ * copied into the emergency buffer and we note that the buffer is in use.
+ * we then bump the start physical address by the amount that was successfully
+ * output before it died.
+ * On any subsequent DEAD result we just do the bump-ups (we know that we are
+ * already using the emergency dbdma_cmd).
+ * CHECK: this just tries to "do it".  It is possible that we should abandon
+ * xfers when the number of residual bytes gets below a certain value - I can
+ * see that this might cause a loop-forever if a too small transfer causes
+ * DEAD status.  However this is a TODO for now - we'll see what gets reported.
+ * When we get a successful transfer result with the emergency buffer we just
+ * pretend that it completed using the original dmdma_cmd and carry on.  The
+ * 'next_cmd' field will already point back to the original loop of blocks.
+ */
+static inline void snd_pmac_pcm_dead_xfer(struct pmac_stream *rec,
+					  volatile struct dbdma_cmd __iomem *cp)
+{
+	unsigned short req, res ;
+	unsigned int phy ;
+
+	/* printk(KERN_WARNING "snd-powermac: DMA died - patching it up!\n"); */
+
+	/* to clear DEAD status we must first clear RUN
+	   set it to quiescent to be on the safe side */
+	(void)in_le32(&rec->dma->status);
+	out_le32(&rec->dma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+
+	if (!emergency_in_use) { /* new problem */
+		memcpy((void *)emergency_dbdma.cmds, (void *)cp,
+		       sizeof(struct dbdma_cmd));
+		emergency_in_use = 1;
+		st_le16(&cp->xfer_status, 0);
+		st_le16(&cp->req_count, rec->period_size);
+		cp = emergency_dbdma.cmds;
+	}
+
+	/* now bump the values to reflect the amount
+	   we haven't yet shifted */
+	req = ld_le16(&cp->req_count);
+	res = ld_le16(&cp->res_count);
+	phy = ld_le32(&cp->phy_addr);
+	phy += (req - res);
+	st_le16(&cp->req_count, res);
+	st_le16(&cp->res_count, 0);
+	st_le16(&cp->xfer_status, 0);
+	st_le32(&cp->phy_addr, phy);
+
+	st_le32(&cp->cmd_dep, rec->cmd.addr
+		+ sizeof(struct dbdma_cmd)*((rec->cur_period+1)%rec->nperiods));
+
+	st_le16(&cp->command, OUTPUT_MORE | BR_ALWAYS | INTR_ALWAYS);
+
+	/* point at our patched up command block */
+	out_le32(&rec->dma->cmdptr, emergency_dbdma.addr);
+
+	/* we must re-start the controller */
+	(void)in_le32(&rec->dma->status);
+	/* should complete clearing the DEAD status */
+	out_le32(&rec->dma->control, ((RUN|WAKE) << 16) + (RUN|WAKE));
+}
+
+/*
+ * update playback/capture pointer from interrupts
+ */
+static void snd_pmac_pcm_update(struct snd_pmac *chip, struct pmac_stream *rec)
+{
+	volatile struct dbdma_cmd __iomem *cp;
+	int c;
+	int stat;
+
+	spin_lock(&chip->reg_lock);
+	if (rec->running) {
+		for (c = 0; c < rec->nperiods; c++) { /* at most all fragments */
+
+			if (emergency_in_use)   /* already using DEAD xfer? */
+				cp = emergency_dbdma.cmds;
+			else
+				cp = &rec->cmd.cmds[rec->cur_period];
+
+			stat = ld_le16(&cp->xfer_status);
+
+			if (stat & DEAD) {
+				snd_pmac_pcm_dead_xfer(rec, cp);
+				break; /* this block is still going */
+			}
+
+			if (emergency_in_use)
+				emergency_in_use = 0 ; /* done that */
+
+			if (! (stat & ACTIVE))
+				break;
+
+			/*printk(KERN_DEBUG "update frag %d\n", rec->cur_period);*/
+			st_le16(&cp->xfer_status, 0);
+			st_le16(&cp->req_count, rec->period_size);
+			/*st_le16(&cp->res_count, 0);*/
+			rec->cur_period++;
+			if (rec->cur_period >= rec->nperiods) {
+				rec->cur_period = 0;
+			}
+
+			spin_unlock(&chip->reg_lock);
+			snd_pcm_period_elapsed(rec->substream);
+			spin_lock(&chip->reg_lock);
+		}
+	}
+	spin_unlock(&chip->reg_lock);
+}
+
+
+/*
+ * hw info
+ */
+
+static struct snd_pcm_hardware snd_pmac_playback =
+{
+	.info =			(SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		7350,
+	.rate_max =		44100,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	131072,
+	.period_bytes_min =	256,
+	.period_bytes_max =	16384,
+	.periods_min =		3,
+	.periods_max =		PMAC_MAX_FRAGS,
+};
+
+static struct snd_pcm_hardware snd_pmac_capture =
+{
+	.info =			(SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_RESUME),
+	.formats =		SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S16_LE,
+	.rates =		SNDRV_PCM_RATE_8000_44100,
+	.rate_min =		7350,
+	.rate_max =		44100,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	131072,
+	.period_bytes_min =	256,
+	.period_bytes_max =	16384,
+	.periods_min =		3,
+	.periods_max =		PMAC_MAX_FRAGS,
+};
+
+
+#if 0 // NYI
+static int snd_pmac_hw_rule_rate(struct snd_pcm_hw_params *params,
+				 struct snd_pcm_hw_rule *rule)
+{
+	struct snd_pmac *chip = rule->private;
+	struct pmac_stream *rec = snd_pmac_get_stream(chip, rule->deps[0]);
+	int i, freq_table[8], num_freqs;
+
+	if (! rec)
+		return -EINVAL;
+	num_freqs = 0;
+	for (i = chip->num_freqs - 1; i >= 0; i--) {
+		if (rec->cur_freqs & (1 << i))
+			freq_table[num_freqs++] = chip->freq_table[i];
+	}
+
+	return snd_interval_list(hw_param_interval(params, rule->var),
+				 num_freqs, freq_table, 0);
+}
+
+static int snd_pmac_hw_rule_format(struct snd_pcm_hw_params *params,
+				   struct snd_pcm_hw_rule *rule)
+{
+	struct snd_pmac *chip = rule->private;
+	struct pmac_stream *rec = snd_pmac_get_stream(chip, rule->deps[0]);
+
+	if (! rec)
+		return -EINVAL;
+	return snd_mask_refine_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT),
+				   rec->cur_formats);
+}
+#endif // NYI
+
+static int snd_pmac_pcm_open(struct snd_pmac *chip, struct pmac_stream *rec,
+			     struct snd_pcm_substream *subs)
+{
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	int i;
+
+	/* look up frequency table and fill bit mask */
+	runtime->hw.rates = 0;
+	for (i = 0; i < chip->num_freqs; i++)
+		if (chip->freqs_ok & (1 << i))
+			runtime->hw.rates |=
+				snd_pcm_rate_to_rate_bit(chip->freq_table[i]);
+
+	/* check for minimum and maximum rates */
+	for (i = 0; i < chip->num_freqs; i++) {
+		if (chip->freqs_ok & (1 << i)) {
+			runtime->hw.rate_max = chip->freq_table[i];
+			break;
+		}
+	}
+	for (i = chip->num_freqs - 1; i >= 0; i--) {
+		if (chip->freqs_ok & (1 << i)) {
+			runtime->hw.rate_min = chip->freq_table[i];
+			break;
+		}
+	}
+	runtime->hw.formats = chip->formats_ok;
+	if (chip->can_capture) {
+		if (! chip->can_duplex)
+			runtime->hw.info |= SNDRV_PCM_INFO_HALF_DUPLEX;
+		runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX;
+	}
+	runtime->private_data = rec;
+	rec->substream = subs;
+
+#if 0 /* FIXME: still under development.. */
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+			    snd_pmac_hw_rule_rate, chip, rec->stream, -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
+			    snd_pmac_hw_rule_format, chip, rec->stream, -1);
+#endif
+
+	runtime->hw.periods_max = rec->cmd.size - 1;
+
+	/* constraints to fix choppy sound */
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	return 0;
+}
+
+static int snd_pmac_pcm_close(struct snd_pmac *chip, struct pmac_stream *rec,
+			      struct snd_pcm_substream *subs)
+{
+	struct pmac_stream *astr;
+
+	snd_pmac_dma_stop(rec);
+
+	astr = snd_pmac_get_stream(chip, another_stream(rec->stream));
+	if (! astr)
+		return -EINVAL;
+
+	/* reset constraints */
+	astr->cur_freqs = chip->freqs_ok;
+	astr->cur_formats = chip->formats_ok;
+
+	return 0;
+}
+
+static int snd_pmac_playback_open(struct snd_pcm_substream *subs)
+{
+	struct snd_pmac *chip = snd_pcm_substream_chip(subs);
+
+	subs->runtime->hw = snd_pmac_playback;
+	return snd_pmac_pcm_open(chip, &chip->playback, subs);
+}
+
+static int snd_pmac_capture_open(struct snd_pcm_substream *subs)
+{
+	struct snd_pmac *chip = snd_pcm_substream_chip(subs);
+
+	subs->runtime->hw = snd_pmac_capture;
+	return snd_pmac_pcm_open(chip, &chip->capture, subs);
+}
+
+static int snd_pmac_playback_close(struct snd_pcm_substream *subs)
+{
+	struct snd_pmac *chip = snd_pcm_substream_chip(subs);
+
+	return snd_pmac_pcm_close(chip, &chip->playback, subs);
+}
+
+static int snd_pmac_capture_close(struct snd_pcm_substream *subs)
+{
+	struct snd_pmac *chip = snd_pcm_substream_chip(subs);
+
+	return snd_pmac_pcm_close(chip, &chip->capture, subs);
+}
+
+/*
+ */
+
+static struct snd_pcm_ops snd_pmac_playback_ops = {
+	.open =		snd_pmac_playback_open,
+	.close =	snd_pmac_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_pmac_pcm_hw_params,
+	.hw_free =	snd_pmac_pcm_hw_free,
+	.prepare =	snd_pmac_playback_prepare,
+	.trigger =	snd_pmac_playback_trigger,
+	.pointer =	snd_pmac_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_pmac_capture_ops = {
+	.open =		snd_pmac_capture_open,
+	.close =	snd_pmac_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_pmac_pcm_hw_params,
+	.hw_free =	snd_pmac_pcm_hw_free,
+	.prepare =	snd_pmac_capture_prepare,
+	.trigger =	snd_pmac_capture_trigger,
+	.pointer =	snd_pmac_capture_pointer,
+};
+
+int __devinit snd_pmac_pcm_new(struct snd_pmac *chip)
+{
+	struct snd_pcm *pcm;
+	int err;
+	int num_captures = 1;
+
+	if (! chip->can_capture)
+		num_captures = 0;
+	err = snd_pcm_new(chip->card, chip->card->driver, 0, 1, num_captures, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_pmac_playback_ops);
+	if (chip->can_capture)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_pmac_capture_ops);
+
+	pcm->private_data = chip;
+	pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+	strcpy(pcm->name, chip->card->shortname);
+	chip->pcm = pcm;
+
+	chip->formats_ok = SNDRV_PCM_FMTBIT_S16_BE;
+	if (chip->can_byte_swap)
+		chip->formats_ok |= SNDRV_PCM_FMTBIT_S16_LE;
+
+	chip->playback.cur_formats = chip->formats_ok;
+	chip->capture.cur_formats = chip->formats_ok;
+	chip->playback.cur_freqs = chip->freqs_ok;
+	chip->capture.cur_freqs = chip->freqs_ok;
+
+	/* preallocate 64k buffer */
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      &chip->pdev->dev,
+					      64 * 1024, 64 * 1024);
+
+	return 0;
+}
+
+
+static void snd_pmac_dbdma_reset(struct snd_pmac *chip)
+{
+	out_le32(&chip->playback.dma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16);
+	snd_pmac_wait_ack(&chip->playback);
+	out_le32(&chip->capture.dma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16);
+	snd_pmac_wait_ack(&chip->capture);
+}
+
+
+/*
+ * handling beep
+ */
+void snd_pmac_beep_dma_start(struct snd_pmac *chip, int bytes, unsigned long addr, int speed)
+{
+	struct pmac_stream *rec = &chip->playback;
+
+	snd_pmac_dma_stop(rec);
+	st_le16(&chip->extra_dma.cmds->req_count, bytes);
+	st_le16(&chip->extra_dma.cmds->xfer_status, 0);
+	st_le32(&chip->extra_dma.cmds->cmd_dep, chip->extra_dma.addr);
+	st_le32(&chip->extra_dma.cmds->phy_addr, addr);
+	st_le16(&chip->extra_dma.cmds->command, OUTPUT_MORE + BR_ALWAYS);
+	out_le32(&chip->awacs->control,
+		 (in_le32(&chip->awacs->control) & ~0x1f00)
+		 | (speed << 8));
+	out_le32(&chip->awacs->byteswap, 0);
+	snd_pmac_dma_set_command(rec, &chip->extra_dma);
+	snd_pmac_dma_run(rec, RUN);
+}
+
+void snd_pmac_beep_dma_stop(struct snd_pmac *chip)
+{
+	snd_pmac_dma_stop(&chip->playback);
+	st_le16(&chip->extra_dma.cmds->command, DBDMA_STOP);
+	snd_pmac_pcm_set_format(chip); /* reset format */
+}
+
+
+/*
+ * interrupt handlers
+ */
+static irqreturn_t
+snd_pmac_tx_intr(int irq, void *devid)
+{
+	struct snd_pmac *chip = devid;
+	snd_pmac_pcm_update(chip, &chip->playback);
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t
+snd_pmac_rx_intr(int irq, void *devid)
+{
+	struct snd_pmac *chip = devid;
+	snd_pmac_pcm_update(chip, &chip->capture);
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t
+snd_pmac_ctrl_intr(int irq, void *devid)
+{
+	struct snd_pmac *chip = devid;
+	int ctrl = in_le32(&chip->awacs->control);
+
+	/*printk(KERN_DEBUG "pmac: control interrupt.. 0x%x\n", ctrl);*/
+	if (ctrl & MASK_PORTCHG) {
+		/* do something when headphone is plugged/unplugged? */
+		if (chip->update_automute)
+			chip->update_automute(chip, 1);
+	}
+	if (ctrl & MASK_CNTLERR) {
+		int err = (in_le32(&chip->awacs->codec_stat) & MASK_ERRCODE) >> 16;
+		if (err && chip->model <= PMAC_SCREAMER)
+			snd_printk(KERN_DEBUG "error %x\n", err);
+	}
+	/* Writing 1s to the CNTLERR and PORTCHG bits clears them... */
+	out_le32(&chip->awacs->control, ctrl);
+	return IRQ_HANDLED;
+}
+
+
+/*
+ * a wrapper to feature call for compatibility
+ */
+static void snd_pmac_sound_feature(struct snd_pmac *chip, int enable)
+{
+	if (ppc_md.feature_call)
+		ppc_md.feature_call(PMAC_FTR_SOUND_CHIP_ENABLE, chip->node, 0, enable);
+}
+
+/*
+ * release resources
+ */
+
+static int snd_pmac_free(struct snd_pmac *chip)
+{
+	/* stop sounds */
+	if (chip->initialized) {
+		snd_pmac_dbdma_reset(chip);
+		/* disable interrupts from awacs interface */
+		out_le32(&chip->awacs->control, in_le32(&chip->awacs->control) & 0xfff);
+	}
+
+	if (chip->node)
+		snd_pmac_sound_feature(chip, 0);
+
+	/* clean up mixer if any */
+	if (chip->mixer_free)
+		chip->mixer_free(chip);
+
+	snd_pmac_detach_beep(chip);
+
+	/* release resources */
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void*)chip);
+	if (chip->tx_irq >= 0)
+		free_irq(chip->tx_irq, (void*)chip);
+	if (chip->rx_irq >= 0)
+		free_irq(chip->rx_irq, (void*)chip);
+	snd_pmac_dbdma_free(chip, &chip->playback.cmd);
+	snd_pmac_dbdma_free(chip, &chip->capture.cmd);
+	snd_pmac_dbdma_free(chip, &chip->extra_dma);
+	snd_pmac_dbdma_free(chip, &emergency_dbdma);
+	if (chip->macio_base)
+		iounmap(chip->macio_base);
+	if (chip->latch_base)
+		iounmap(chip->latch_base);
+	if (chip->awacs)
+		iounmap(chip->awacs);
+	if (chip->playback.dma)
+		iounmap(chip->playback.dma);
+	if (chip->capture.dma)
+		iounmap(chip->capture.dma);
+
+	if (chip->node) {
+		int i;
+		for (i = 0; i < 3; i++) {
+			if (chip->requested & (1 << i))
+				release_mem_region(chip->rsrc[i].start,
+						   chip->rsrc[i].end -
+						   chip->rsrc[i].start + 1);
+		}
+	}
+
+	if (chip->pdev)
+		pci_dev_put(chip->pdev);
+	of_node_put(chip->node);
+	kfree(chip);
+	return 0;
+}
+
+
+/*
+ * free the device
+ */
+static int snd_pmac_dev_free(struct snd_device *device)
+{
+	struct snd_pmac *chip = device->device_data;
+	return snd_pmac_free(chip);
+}
+
+
+/*
+ * check the machine support byteswap (little-endian)
+ */
+
+static void __devinit detect_byte_swap(struct snd_pmac *chip)
+{
+	struct device_node *mio;
+
+	/* if seems that Keylargo can't byte-swap  */
+	for (mio = chip->node->parent; mio; mio = mio->parent) {
+		if (strcmp(mio->name, "mac-io") == 0) {
+			if (of_device_is_compatible(mio, "Keylargo"))
+				chip->can_byte_swap = 0;
+			break;
+		}
+	}
+
+	/* it seems the Pismo & iBook can't byte-swap in hardware. */
+	if (of_machine_is_compatible("PowerBook3,1") ||
+	    of_machine_is_compatible("PowerBook2,1"))
+		chip->can_byte_swap = 0 ;
+
+	if (of_machine_is_compatible("PowerBook2,1"))
+		chip->can_duplex = 0;
+}
+
+
+/*
+ * detect a sound chip
+ */
+static int __devinit snd_pmac_detect(struct snd_pmac *chip)
+{
+	struct device_node *sound;
+	struct device_node *dn;
+	const unsigned int *prop;
+	unsigned int l;
+	struct macio_chip* macio;
+
+	if (!machine_is(powermac))
+		return -ENODEV;
+
+	chip->subframe = 0;
+	chip->revision = 0;
+	chip->freqs_ok = 0xff; /* all ok */
+	chip->model = PMAC_AWACS;
+	chip->can_byte_swap = 1;
+	chip->can_duplex = 1;
+	chip->can_capture = 1;
+	chip->num_freqs = ARRAY_SIZE(awacs_freqs);
+	chip->freq_table = awacs_freqs;
+	chip->pdev = NULL;
+
+	chip->control_mask = MASK_IEPC | MASK_IEE | 0x11; /* default */
+
+	/* check machine type */
+	if (of_machine_is_compatible("AAPL,3400/2400")
+	    || of_machine_is_compatible("AAPL,3500"))
+		chip->is_pbook_3400 = 1;
+	else if (of_machine_is_compatible("PowerBook1,1")
+		 || of_machine_is_compatible("AAPL,PowerBook1998"))
+		chip->is_pbook_G3 = 1;
+	chip->node = of_find_node_by_name(NULL, "awacs");
+	sound = of_node_get(chip->node);
+
+	/*
+	 * powermac G3 models have a node called "davbus"
+	 * with a child called "sound".
+	 */
+	if (!chip->node)
+		chip->node = of_find_node_by_name(NULL, "davbus");
+	/*
+	 * if we didn't find a davbus device, try 'i2s-a' since
+	 * this seems to be what iBooks have
+	 */
+	if (! chip->node) {
+		chip->node = of_find_node_by_name(NULL, "i2s-a");
+		if (chip->node && chip->node->parent &&
+		    chip->node->parent->parent) {
+			if (of_device_is_compatible(chip->node->parent->parent,
+						 "K2-Keylargo"))
+				chip->is_k2 = 1;
+		}
+	}
+	if (! chip->node)
+		return -ENODEV;
+
+	if (!sound) {
+		sound = of_find_node_by_name(NULL, "sound");
+		while (sound && sound->parent != chip->node)
+			sound = of_find_node_by_name(sound, "sound");
+	}
+	if (! sound) {
+		of_node_put(chip->node);
+		chip->node = NULL;
+		return -ENODEV;
+	}
+	prop = of_get_property(sound, "sub-frame", NULL);
+	if (prop && *prop < 16)
+		chip->subframe = *prop;
+	prop = of_get_property(sound, "layout-id", NULL);
+	if (prop) {
+		/* partly deprecate snd-powermac, for those machines
+		 * that have a layout-id property for now */
+		printk(KERN_INFO "snd-powermac no longer handles any "
+				 "machines with a layout-id property "
+				 "in the device-tree, use snd-aoa.\n");
+		of_node_put(sound);
+		of_node_put(chip->node);
+		chip->node = NULL;
+		return -ENODEV;
+	}
+	/* This should be verified on older screamers */
+	if (of_device_is_compatible(sound, "screamer")) {
+		chip->model = PMAC_SCREAMER;
+		// chip->can_byte_swap = 0; /* FIXME: check this */
+	}
+	if (of_device_is_compatible(sound, "burgundy")) {
+		chip->model = PMAC_BURGUNDY;
+		chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */
+	}
+	if (of_device_is_compatible(sound, "daca")) {
+		chip->model = PMAC_DACA;
+		chip->can_capture = 0;  /* no capture */
+		chip->can_duplex = 0;
+		// chip->can_byte_swap = 0; /* FIXME: check this */
+		chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */
+	}
+	if (of_device_is_compatible(sound, "tumbler")) {
+		chip->model = PMAC_TUMBLER;
+		chip->can_capture = of_machine_is_compatible("PowerMac4,2")
+				|| of_machine_is_compatible("PowerBook4,1");
+		chip->can_duplex = 0;
+		// chip->can_byte_swap = 0; /* FIXME: check this */
+		chip->num_freqs = ARRAY_SIZE(tumbler_freqs);
+		chip->freq_table = tumbler_freqs;
+		chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */
+	}
+	if (of_device_is_compatible(sound, "snapper")) {
+		chip->model = PMAC_SNAPPER;
+		// chip->can_byte_swap = 0; /* FIXME: check this */
+		chip->num_freqs = ARRAY_SIZE(tumbler_freqs);
+		chip->freq_table = tumbler_freqs;
+		chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */
+	}
+	prop = of_get_property(sound, "device-id", NULL);
+	if (prop)
+		chip->device_id = *prop;
+	dn = of_find_node_by_name(NULL, "perch");
+	chip->has_iic = (dn != NULL);
+	of_node_put(dn);
+
+	/* We need the PCI device for DMA allocations, let's use a crude method
+	 * for now ...
+	 */
+	macio = macio_find(chip->node, macio_unknown);
+	if (macio == NULL)
+		printk(KERN_WARNING "snd-powermac: can't locate macio !\n");
+	else {
+		struct pci_dev *pdev = NULL;
+
+		for_each_pci_dev(pdev) {
+			struct device_node *np = pci_device_to_OF_node(pdev);
+			if (np && np == macio->of_node) {
+				chip->pdev = pdev;
+				break;
+			}
+		}
+	}
+	if (chip->pdev == NULL)
+		printk(KERN_WARNING "snd-powermac: can't locate macio PCI"
+		       " device !\n");
+
+	detect_byte_swap(chip);
+
+	/* look for a property saying what sample rates
+	   are available */
+	prop = of_get_property(sound, "sample-rates", &l);
+	if (! prop)
+		prop = of_get_property(sound, "output-frame-rates", &l);
+	if (prop) {
+		int i;
+		chip->freqs_ok = 0;
+		for (l /= sizeof(int); l > 0; --l) {
+			unsigned int r = *prop++;
+			/* Apple 'Fixed' format */
+			if (r >= 0x10000)
+				r >>= 16;
+			for (i = 0; i < chip->num_freqs; ++i) {
+				if (r == chip->freq_table[i]) {
+					chip->freqs_ok |= (1 << i);
+					break;
+				}
+			}
+		}
+	} else {
+		/* assume only 44.1khz */
+		chip->freqs_ok = 1;
+	}
+
+	of_node_put(sound);
+	return 0;
+}
+
+#ifdef PMAC_SUPPORT_AUTOMUTE
+/*
+ * auto-mute
+ */
+static int pmac_auto_mute_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	ucontrol->value.integer.value[0] = chip->auto_mute;
+	return 0;
+}
+
+static int pmac_auto_mute_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	if (ucontrol->value.integer.value[0] != chip->auto_mute) {
+		chip->auto_mute = !!ucontrol->value.integer.value[0];
+		if (chip->update_automute)
+			chip->update_automute(chip, 1);
+		return 1;
+	}
+	return 0;
+}
+
+static int pmac_hp_detect_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	if (chip->detect_headphone)
+		ucontrol->value.integer.value[0] = chip->detect_headphone(chip);
+	else
+		ucontrol->value.integer.value[0] = 0;
+	return 0;
+}
+
+static struct snd_kcontrol_new auto_mute_controls[] __devinitdata = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Auto Mute Switch",
+	  .info = snd_pmac_boolean_mono_info,
+	  .get = pmac_auto_mute_get,
+	  .put = pmac_auto_mute_put,
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Headphone Detection",
+	  .access = SNDRV_CTL_ELEM_ACCESS_READ,
+	  .info = snd_pmac_boolean_mono_info,
+	  .get = pmac_hp_detect_get,
+	},
+};
+
+int __devinit snd_pmac_add_automute(struct snd_pmac *chip)
+{
+	int err;
+	chip->auto_mute = 1;
+	err = snd_ctl_add(chip->card, snd_ctl_new1(&auto_mute_controls[0], chip));
+	if (err < 0) {
+		printk(KERN_ERR "snd-powermac: Failed to add automute control\n");
+		return err;
+	}
+	chip->hp_detect_ctl = snd_ctl_new1(&auto_mute_controls[1], chip);
+	return snd_ctl_add(chip->card, chip->hp_detect_ctl);
+}
+#endif /* PMAC_SUPPORT_AUTOMUTE */
+
+/*
+ * create and detect a pmac chip record
+ */
+int __devinit snd_pmac_new(struct snd_card *card, struct snd_pmac **chip_return)
+{
+	struct snd_pmac *chip;
+	struct device_node *np;
+	int i, err;
+	unsigned int irq;
+	unsigned long ctrl_addr, txdma_addr, rxdma_addr;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_pmac_dev_free,
+	};
+
+	*chip_return = NULL;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+	chip->card = card;
+
+	spin_lock_init(&chip->reg_lock);
+	chip->irq = chip->tx_irq = chip->rx_irq = -1;
+
+	chip->playback.stream = SNDRV_PCM_STREAM_PLAYBACK;
+	chip->capture.stream = SNDRV_PCM_STREAM_CAPTURE;
+
+	if ((err = snd_pmac_detect(chip)) < 0)
+		goto __error;
+
+	if (snd_pmac_dbdma_alloc(chip, &chip->playback.cmd, PMAC_MAX_FRAGS + 1) < 0 ||
+	    snd_pmac_dbdma_alloc(chip, &chip->capture.cmd, PMAC_MAX_FRAGS + 1) < 0 ||
+	    snd_pmac_dbdma_alloc(chip, &chip->extra_dma, 2) < 0 ||
+	    snd_pmac_dbdma_alloc(chip, &emergency_dbdma, 2) < 0) {
+		err = -ENOMEM;
+		goto __error;
+	}
+
+	np = chip->node;
+	chip->requested = 0;
+	if (chip->is_k2) {
+		static char *rnames[] = {
+			"Sound Control", "Sound DMA" };
+		for (i = 0; i < 2; i ++) {
+			if (of_address_to_resource(np->parent, i,
+						   &chip->rsrc[i])) {
+				printk(KERN_ERR "snd: can't translate rsrc "
+				       " %d (%s)\n", i, rnames[i]);
+				err = -ENODEV;
+				goto __error;
+			}
+			if (request_mem_region(chip->rsrc[i].start,
+					       chip->rsrc[i].end -
+					       chip->rsrc[i].start + 1,
+					       rnames[i]) == NULL) {
+				printk(KERN_ERR "snd: can't request rsrc "
+				       " %d (%s: %pR)\n",
+				       i, rnames[i], &chip->rsrc[i]);
+				err = -ENODEV;
+				goto __error;
+			}
+			chip->requested |= (1 << i);
+		}
+		ctrl_addr = chip->rsrc[0].start;
+		txdma_addr = chip->rsrc[1].start;
+		rxdma_addr = txdma_addr + 0x100;
+	} else {
+		static char *rnames[] = {
+			"Sound Control", "Sound Tx DMA", "Sound Rx DMA" };
+		for (i = 0; i < 3; i ++) {
+			if (of_address_to_resource(np, i,
+						   &chip->rsrc[i])) {
+				printk(KERN_ERR "snd: can't translate rsrc "
+				       " %d (%s)\n", i, rnames[i]);
+				err = -ENODEV;
+				goto __error;
+			}
+			if (request_mem_region(chip->rsrc[i].start,
+					       chip->rsrc[i].end -
+					       chip->rsrc[i].start + 1,
+					       rnames[i]) == NULL) {
+				printk(KERN_ERR "snd: can't request rsrc "
+				       " %d (%s: %pR)\n",
+				       i, rnames[i], &chip->rsrc[i]);
+				err = -ENODEV;
+				goto __error;
+			}
+			chip->requested |= (1 << i);
+		}
+		ctrl_addr = chip->rsrc[0].start;
+		txdma_addr = chip->rsrc[1].start;
+		rxdma_addr = chip->rsrc[2].start;
+	}
+
+	chip->awacs = ioremap(ctrl_addr, 0x1000);
+	chip->playback.dma = ioremap(txdma_addr, 0x100);
+	chip->capture.dma = ioremap(rxdma_addr, 0x100);
+	if (chip->model <= PMAC_BURGUNDY) {
+		irq = irq_of_parse_and_map(np, 0);
+		if (request_irq(irq, snd_pmac_ctrl_intr, 0,
+				"PMac", (void*)chip)) {
+			snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n",
+				   irq);
+			err = -EBUSY;
+			goto __error;
+		}
+		chip->irq = irq;
+	}
+	irq = irq_of_parse_and_map(np, 1);
+	if (request_irq(irq, snd_pmac_tx_intr, 0, "PMac Output", (void*)chip)){
+		snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n", irq);
+		err = -EBUSY;
+		goto __error;
+	}
+	chip->tx_irq = irq;
+	irq = irq_of_parse_and_map(np, 2);
+	if (request_irq(irq, snd_pmac_rx_intr, 0, "PMac Input", (void*)chip)) {
+		snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n", irq);
+		err = -EBUSY;
+		goto __error;
+	}
+	chip->rx_irq = irq;
+
+	snd_pmac_sound_feature(chip, 1);
+
+	/* reset & enable interrupts */
+	if (chip->model <= PMAC_BURGUNDY)
+		out_le32(&chip->awacs->control, chip->control_mask);
+
+	/* Powerbooks have odd ways of enabling inputs such as
+	   an expansion-bay CD or sound from an internal modem
+	   or a PC-card modem. */
+	if (chip->is_pbook_3400) {
+		/* Enable CD and PC-card sound inputs. */
+		/* This is done by reading from address
+		 * f301a000, + 0x10 to enable the expansion-bay
+		 * CD sound input, + 0x80 to enable the PC-card
+		 * sound input.  The 0x100 enables the SCSI bus
+		 * terminator power.
+		 */
+		chip->latch_base = ioremap (0xf301a000, 0x1000);
+		in_8(chip->latch_base + 0x190);
+	} else if (chip->is_pbook_G3) {
+		struct device_node* mio;
+		for (mio = chip->node->parent; mio; mio = mio->parent) {
+			if (strcmp(mio->name, "mac-io") == 0) {
+				struct resource r;
+				if (of_address_to_resource(mio, 0, &r) == 0)
+					chip->macio_base =
+						ioremap(r.start, 0x40);
+				break;
+			}
+		}
+		/* Enable CD sound input. */
+		/* The relevant bits for writing to this byte are 0x8f.
+		 * I haven't found out what the 0x80 bit does.
+		 * For the 0xf bits, writing 3 or 7 enables the CD
+		 * input, any other value disables it.  Values
+		 * 1, 3, 5, 7 enable the microphone.  Values 0, 2,
+		 * 4, 6, 8 - f enable the input from the modem.
+		 */
+		if (chip->macio_base)
+			out_8(chip->macio_base + 0x37, 3);
+	}
+
+	/* Reset dbdma channels */
+	snd_pmac_dbdma_reset(chip);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0)
+		goto __error;
+
+	*chip_return = chip;
+	return 0;
+
+ __error:
+	snd_pmac_free(chip);
+	return err;
+}
+
+
+/*
+ * sleep notify for powerbook
+ */
+
+#ifdef CONFIG_PM
+
+/*
+ * Save state when going to sleep, restore it afterwards.
+ */
+
+void snd_pmac_suspend(struct snd_pmac *chip)
+{
+	unsigned long flags;
+
+	snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
+	if (chip->suspend)
+		chip->suspend(chip);
+	snd_pcm_suspend_all(chip->pcm);
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	snd_pmac_beep_stop(chip);
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	if (chip->irq >= 0)
+		disable_irq(chip->irq);
+	if (chip->tx_irq >= 0)
+		disable_irq(chip->tx_irq);
+	if (chip->rx_irq >= 0)
+		disable_irq(chip->rx_irq);
+	snd_pmac_sound_feature(chip, 0);
+}
+
+void snd_pmac_resume(struct snd_pmac *chip)
+{
+	snd_pmac_sound_feature(chip, 1);
+	if (chip->resume)
+		chip->resume(chip);
+	/* enable CD sound input */
+	if (chip->macio_base && chip->is_pbook_G3)
+		out_8(chip->macio_base + 0x37, 3);
+	else if (chip->is_pbook_3400)
+		in_8(chip->latch_base + 0x190);
+
+	snd_pmac_pcm_set_format(chip);
+
+	if (chip->irq >= 0)
+		enable_irq(chip->irq);
+	if (chip->tx_irq >= 0)
+		enable_irq(chip->tx_irq);
+	if (chip->rx_irq >= 0)
+		enable_irq(chip->rx_irq);
+
+	snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
+}
+
+#endif /* CONFIG_PM */
+
diff --git a/linux/sound/ppc/pmac.h b/linux/sound/ppc/pmac.h
new file mode 100644
index 0000000..25c512c
--- /dev/null
+++ b/linux/sound/ppc/pmac.h
@@ -0,0 +1,210 @@
+/*
+ * Driver for PowerMac onboard soundchips
+ * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de>
+ *   based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+
+#ifndef __PMAC_H
+#define __PMAC_H
+
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include "awacs.h"
+
+#include <linux/adb.h>
+#ifdef CONFIG_ADB_CUDA
+#include <linux/cuda.h>
+#endif
+#ifdef CONFIG_ADB_PMU
+#include <linux/pmu.h>
+#endif
+#include <linux/nvram.h>
+#include <linux/tty.h>
+#include <linux/vt_kern.h>
+#include <asm/dbdma.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+
+/* maximum number of fragments */
+#define PMAC_MAX_FRAGS		32
+
+
+#define PMAC_SUPPORT_AUTOMUTE
+
+/*
+ * DBDMA space
+ */
+struct pmac_dbdma {
+	dma_addr_t dma_base;
+	dma_addr_t addr;
+	struct dbdma_cmd __iomem *cmds;
+	void *space;
+	int size;
+};
+
+/*
+ * playback/capture stream
+ */
+struct pmac_stream {
+	int running;	/* boolean */
+
+	int stream;	/* PLAYBACK/CAPTURE */
+
+	int dma_size; /* in bytes */
+	int period_size; /* in bytes */
+	int buffer_size; /* in kbytes */
+	int nperiods, cur_period;
+
+	struct pmac_dbdma cmd;
+	volatile struct dbdma_regs __iomem *dma;
+
+	struct snd_pcm_substream *substream;
+
+	unsigned int cur_freqs;		/* currently available frequencies */
+	unsigned int cur_formats;	/* currently available formats */
+};
+
+
+/*
+ */
+
+enum snd_pmac_model {
+	PMAC_AWACS, PMAC_SCREAMER, PMAC_BURGUNDY, PMAC_DACA, PMAC_TUMBLER,
+	PMAC_SNAPPER
+};
+
+struct snd_pmac {
+	struct snd_card *card;
+
+	/* h/w info */
+	struct device_node *node;
+	struct pci_dev *pdev;
+	unsigned int revision;
+	unsigned int manufacturer;
+	unsigned int subframe;
+	unsigned int device_id;
+	enum snd_pmac_model model;
+
+	unsigned int has_iic : 1;
+	unsigned int is_pbook_3400 : 1;
+	unsigned int is_pbook_G3 : 1;
+	unsigned int is_k2 : 1;
+
+	unsigned int can_byte_swap : 1;
+	unsigned int can_duplex : 1;
+	unsigned int can_capture : 1;
+
+	unsigned int auto_mute : 1;
+	unsigned int initialized : 1;
+	unsigned int feature_is_set : 1;
+
+	unsigned int requested;
+	struct resource rsrc[3];
+
+	int num_freqs;
+	int *freq_table;
+	unsigned int freqs_ok;		/* bit flags */
+	unsigned int formats_ok;	/* pcm hwinfo */
+	int active;
+	int rate_index;
+	int format;			/* current format */
+
+	spinlock_t reg_lock;
+	volatile struct awacs_regs __iomem *awacs;
+	int awacs_reg[8]; /* register cache */
+	unsigned int hp_stat_mask;
+
+	unsigned char __iomem *latch_base;
+	unsigned char __iomem *macio_base;
+
+	struct pmac_stream playback;
+	struct pmac_stream capture;
+
+	struct pmac_dbdma extra_dma;
+
+	int irq, tx_irq, rx_irq;
+
+	struct snd_pcm *pcm;
+
+	struct pmac_beep *beep;
+
+	unsigned int control_mask;	/* control mask */
+
+	/* mixer stuffs */
+	void *mixer_data;
+	void (*mixer_free)(struct snd_pmac *);
+	struct snd_kcontrol *master_sw_ctl;
+	struct snd_kcontrol *speaker_sw_ctl;
+	struct snd_kcontrol *drc_sw_ctl;	/* only used for tumbler -ReneR */
+	struct snd_kcontrol *hp_detect_ctl;
+	struct snd_kcontrol *lineout_sw_ctl;
+
+	/* lowlevel callbacks */
+	void (*set_format)(struct snd_pmac *chip);
+	void (*update_automute)(struct snd_pmac *chip, int do_notify);
+	int (*detect_headphone)(struct snd_pmac *chip);
+#ifdef CONFIG_PM
+	void (*suspend)(struct snd_pmac *chip);
+	void (*resume)(struct snd_pmac *chip);
+#endif
+
+};
+
+
+/* exported functions */
+int snd_pmac_new(struct snd_card *card, struct snd_pmac **chip_return);
+int snd_pmac_pcm_new(struct snd_pmac *chip);
+int snd_pmac_attach_beep(struct snd_pmac *chip);
+void snd_pmac_detach_beep(struct snd_pmac *chip);
+void snd_pmac_beep_stop(struct snd_pmac *chip);
+unsigned int snd_pmac_rate_index(struct snd_pmac *chip, struct pmac_stream *rec, unsigned int rate);
+
+void snd_pmac_beep_dma_start(struct snd_pmac *chip, int bytes, unsigned long addr, int speed);
+void snd_pmac_beep_dma_stop(struct snd_pmac *chip);
+
+#ifdef CONFIG_PM
+void snd_pmac_suspend(struct snd_pmac *chip);
+void snd_pmac_resume(struct snd_pmac *chip);
+#endif
+
+/* initialize mixer */
+int snd_pmac_awacs_init(struct snd_pmac *chip);
+int snd_pmac_burgundy_init(struct snd_pmac *chip);
+int snd_pmac_daca_init(struct snd_pmac *chip);
+int snd_pmac_tumbler_init(struct snd_pmac *chip);
+int snd_pmac_tumbler_post_init(void);
+
+/* i2c functions */
+struct pmac_keywest {
+	int addr;
+	struct i2c_client *client;
+	int id;
+	int (*init_client)(struct pmac_keywest *i2c);
+	char *name;
+};
+
+int snd_pmac_keywest_init(struct pmac_keywest *i2c);
+void snd_pmac_keywest_cleanup(struct pmac_keywest *i2c);
+
+/* misc */
+#define snd_pmac_boolean_stereo_info	snd_ctl_boolean_stereo_info
+#define snd_pmac_boolean_mono_info	snd_ctl_boolean_mono_info
+
+int snd_pmac_add_automute(struct snd_pmac *chip);
+
+#endif /* __PMAC_H */
diff --git a/linux/sound/ppc/powermac.c b/linux/sound/ppc/powermac.c
new file mode 100644
index 0000000..a2b69b8
--- /dev/null
+++ b/linux/sound/ppc/powermac.c
@@ -0,0 +1,195 @@
+/*
+ * Driver for PowerMac AWACS
+ * Copyright (c) 2001 by Takashi Iwai <tiwai@suse.de>
+ *   based on dmasound.c.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include "pmac.h"
+#include "awacs.h"
+#include "burgundy.h"
+
+#define CHIP_NAME "PMac"
+
+MODULE_DESCRIPTION("PowerMac");
+MODULE_SUPPORTED_DEVICE("{{Apple,PowerMac}}");
+MODULE_LICENSE("GPL");
+
+static int index = SNDRV_DEFAULT_IDX1;		/* Index 0-MAX */
+static char *id = SNDRV_DEFAULT_STR1;		/* ID for this card */
+static int enable_beep = 1;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for " CHIP_NAME " soundchip.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for " CHIP_NAME " soundchip.");
+module_param(enable_beep, bool, 0444);
+MODULE_PARM_DESC(enable_beep, "Enable beep using PCM.");
+
+static struct platform_device *device;
+
+
+/*
+ */
+
+static int __devinit snd_pmac_probe(struct platform_device *devptr)
+{
+	struct snd_card *card;
+	struct snd_pmac *chip;
+	char *name_ext;
+	int err;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	if ((err = snd_pmac_new(card, &chip)) < 0)
+		goto __error;
+	card->private_data = chip;
+
+	switch (chip->model) {
+	case PMAC_BURGUNDY:
+		strcpy(card->driver, "PMac Burgundy");
+		strcpy(card->shortname, "PowerMac Burgundy");
+		sprintf(card->longname, "%s (Dev %d) Sub-frame %d",
+			card->shortname, chip->device_id, chip->subframe);
+		if ((err = snd_pmac_burgundy_init(chip)) < 0)
+			goto __error;
+		break;
+	case PMAC_DACA:
+		strcpy(card->driver, "PMac DACA");
+		strcpy(card->shortname, "PowerMac DACA");
+		sprintf(card->longname, "%s (Dev %d) Sub-frame %d",
+			card->shortname, chip->device_id, chip->subframe);
+		if ((err = snd_pmac_daca_init(chip)) < 0)
+			goto __error;
+		break;
+	case PMAC_TUMBLER:
+	case PMAC_SNAPPER:
+		name_ext = chip->model == PMAC_TUMBLER ? "Tumbler" : "Snapper";
+		sprintf(card->driver, "PMac %s", name_ext);
+		sprintf(card->shortname, "PowerMac %s", name_ext);
+		sprintf(card->longname, "%s (Dev %d) Sub-frame %d",
+			card->shortname, chip->device_id, chip->subframe);
+		if ( snd_pmac_tumbler_init(chip) < 0 || snd_pmac_tumbler_post_init() < 0)
+			goto __error;
+		break;
+	case PMAC_AWACS:
+	case PMAC_SCREAMER:
+		name_ext = chip->model == PMAC_SCREAMER ? "Screamer" : "AWACS";
+		sprintf(card->driver, "PMac %s", name_ext);
+		sprintf(card->shortname, "PowerMac %s", name_ext);
+		if (chip->is_pbook_3400)
+			name_ext = " [PB3400]";
+		else if (chip->is_pbook_G3)
+			name_ext = " [PBG3]";
+		else
+			name_ext = "";
+		sprintf(card->longname, "%s%s Rev %d",
+			card->shortname, name_ext, chip->revision);
+		if ((err = snd_pmac_awacs_init(chip)) < 0)
+			goto __error;
+		break;
+	default:
+		snd_printk(KERN_ERR "unsupported hardware %d\n", chip->model);
+		err = -EINVAL;
+		goto __error;
+	}
+
+	if ((err = snd_pmac_pcm_new(chip)) < 0)
+		goto __error;
+
+	chip->initialized = 1;
+	if (enable_beep)
+		snd_pmac_attach_beep(chip);
+
+	snd_card_set_dev(card, &devptr->dev);
+
+	if ((err = snd_card_register(card)) < 0)
+		goto __error;
+
+	platform_set_drvdata(devptr, card);
+	return 0;
+
+__error:
+	snd_card_free(card);
+	return err;
+}
+
+
+static int __devexit snd_pmac_remove(struct platform_device *devptr)
+{
+	snd_card_free(platform_get_drvdata(devptr));
+	platform_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_pmac_driver_suspend(struct platform_device *devptr, pm_message_t state)
+{
+	struct snd_card *card = platform_get_drvdata(devptr);
+	snd_pmac_suspend(card->private_data);
+	return 0;
+}
+
+static int snd_pmac_driver_resume(struct platform_device *devptr)
+{
+	struct snd_card *card = platform_get_drvdata(devptr);
+	snd_pmac_resume(card->private_data);
+	return 0;
+}
+#endif
+
+#define SND_PMAC_DRIVER		"snd_powermac"
+
+static struct platform_driver snd_pmac_driver = {
+	.probe		= snd_pmac_probe,
+	.remove		= __devexit_p(snd_pmac_remove),
+#ifdef CONFIG_PM
+	.suspend	= snd_pmac_driver_suspend,
+	.resume		= snd_pmac_driver_resume,
+#endif
+	.driver		= {
+		.name	= SND_PMAC_DRIVER
+	},
+};
+
+static int __init alsa_card_pmac_init(void)
+{
+	int err;
+
+	if ((err = platform_driver_register(&snd_pmac_driver)) < 0)
+		return err;
+	device = platform_device_register_simple(SND_PMAC_DRIVER, -1, NULL, 0);
+	return 0;
+
+}
+
+static void __exit alsa_card_pmac_exit(void)
+{
+	if (!IS_ERR(device))
+		platform_device_unregister(device);
+	platform_driver_unregister(&snd_pmac_driver);
+}
+
+module_init(alsa_card_pmac_init)
+module_exit(alsa_card_pmac_exit)
diff --git a/linux/sound/ppc/snd_ps3.c b/linux/sound/ppc/snd_ps3.c
new file mode 100644
index 0000000..581a670
--- /dev/null
+++ b/linux/sound/ppc/snd_ps3.c
@@ -0,0 +1,1159 @@
+/*
+ * Audio support for PS3
+ * Copyright (C) 2007 Sony Computer Entertainment Inc.
+ * All rights reserved.
+ * Copyright 2006, 2007 Sony Corporation
+ *
+ * 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; version 2 of the Licence.
+ *
+ * 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/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+#include <sound/asound.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/memalloc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include <asm/dma.h>
+#include <asm/firmware.h>
+#include <asm/lv1call.h>
+#include <asm/ps3.h>
+#include <asm/ps3av.h>
+
+#include "snd_ps3.h"
+#include "snd_ps3_reg.h"
+
+
+/*
+ * global
+ */
+static struct snd_ps3_card_info the_card;
+
+static int snd_ps3_start_delay = CONFIG_SND_PS3_DEFAULT_START_DELAY;
+
+module_param_named(start_delay, snd_ps3_start_delay, uint, 0644);
+MODULE_PARM_DESC(start_delay, "time to insert silent data in milisec");
+
+static int index = SNDRV_DEFAULT_IDX1;
+static char *id = SNDRV_DEFAULT_STR1;
+
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for PS3 soundchip.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for PS3 soundchip.");
+
+
+/*
+ * PS3 audio register access
+ */
+static inline u32 read_reg(unsigned int reg)
+{
+	return in_be32(the_card.mapped_mmio_vaddr + reg);
+}
+static inline void write_reg(unsigned int reg, u32 val)
+{
+	out_be32(the_card.mapped_mmio_vaddr + reg, val);
+}
+static inline void update_reg(unsigned int reg, u32 or_val)
+{
+	u32 newval = read_reg(reg) | or_val;
+	write_reg(reg, newval);
+}
+static inline void update_mask_reg(unsigned int reg, u32 mask, u32 or_val)
+{
+	u32 newval = (read_reg(reg) & mask) | or_val;
+	write_reg(reg, newval);
+}
+
+/*
+ * ALSA defs
+ */
+static const struct snd_pcm_hardware snd_ps3_pcm_hw = {
+	.info = (SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_NONINTERLEAVED |
+		 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats = (SNDRV_PCM_FMTBIT_S16_BE |
+		    SNDRV_PCM_FMTBIT_S24_BE),
+	.rates = (SNDRV_PCM_RATE_44100 |
+		  SNDRV_PCM_RATE_48000 |
+		  SNDRV_PCM_RATE_88200 |
+		  SNDRV_PCM_RATE_96000),
+	.rate_min = 44100,
+	.rate_max = 96000,
+
+	.channels_min = 2, /* stereo only */
+	.channels_max = 2,
+
+	.buffer_bytes_max = PS3_AUDIO_FIFO_SIZE * 64,
+
+	/* interrupt by four stages */
+	.period_bytes_min = PS3_AUDIO_FIFO_STAGE_SIZE * 4,
+	.period_bytes_max = PS3_AUDIO_FIFO_STAGE_SIZE * 4,
+
+	.periods_min = 16,
+	.periods_max = 32, /* buffer_size_max/ period_bytes_max */
+
+	.fifo_size = PS3_AUDIO_FIFO_SIZE
+};
+
+static int snd_ps3_verify_dma_stop(struct snd_ps3_card_info *card,
+				   int count, int force_stop)
+{
+	int dma_ch, done, retries, stop_forced = 0;
+	uint32_t status;
+
+	for (dma_ch = 0; dma_ch < 8; dma_ch++) {
+		retries = count;
+		do {
+			status = read_reg(PS3_AUDIO_KICK(dma_ch)) &
+				PS3_AUDIO_KICK_STATUS_MASK;
+			switch (status) {
+			case PS3_AUDIO_KICK_STATUS_DONE:
+			case PS3_AUDIO_KICK_STATUS_NOTIFY:
+			case PS3_AUDIO_KICK_STATUS_CLEAR:
+			case PS3_AUDIO_KICK_STATUS_ERROR:
+				done = 1;
+				break;
+			default:
+				done = 0;
+				udelay(10);
+			}
+		} while (!done && --retries);
+		if (!retries && force_stop) {
+			pr_info("%s: DMA ch %d is not stopped.",
+				__func__, dma_ch);
+			/* last resort. force to stop dma.
+			 *  NOTE: this cause DMA done interrupts
+			 */
+			update_reg(PS3_AUDIO_CONFIG, PS3_AUDIO_CONFIG_CLEAR);
+			stop_forced = 1;
+		}
+	}
+	return stop_forced;
+}
+
+/*
+ * wait for all dma is done.
+ * NOTE: caller should reset card->running before call.
+ *       If not, the interrupt handler will re-start DMA,
+ *       then DMA is never stopped.
+ */
+static void snd_ps3_wait_for_dma_stop(struct snd_ps3_card_info *card)
+{
+	int stop_forced;
+	/*
+	 * wait for the last dma is done
+	 */
+
+	/*
+	 * expected maximum DMA done time is 5.7ms + something (DMA itself).
+	 * 5.7ms is from 16bit/sample 2ch 44.1Khz; the time next
+	 * DMA kick event would occur.
+	 */
+	stop_forced = snd_ps3_verify_dma_stop(card, 700, 1);
+
+	/*
+	 * clear outstanding interrupts.
+	 */
+	update_reg(PS3_AUDIO_INTR_0, 0);
+	update_reg(PS3_AUDIO_AX_IS, 0);
+
+	/*
+	 *revert CLEAR bit since it will not reset automatically after DMA stop
+	 */
+	if (stop_forced)
+		update_mask_reg(PS3_AUDIO_CONFIG, ~PS3_AUDIO_CONFIG_CLEAR, 0);
+	/* ensure the hardware sees changes */
+	wmb();
+}
+
+static void snd_ps3_kick_dma(struct snd_ps3_card_info *card)
+{
+
+	update_reg(PS3_AUDIO_KICK(0), PS3_AUDIO_KICK_REQUEST);
+	/* ensure the hardware sees the change */
+	wmb();
+}
+
+/*
+ * convert virtual addr to ioif bus addr.
+ */
+static dma_addr_t v_to_bus(struct snd_ps3_card_info *card, void *paddr, int ch)
+{
+	return card->dma_start_bus_addr[ch] +
+		(paddr - card->dma_start_vaddr[ch]);
+};
+
+
+/*
+ * increment ring buffer pointer.
+ * NOTE: caller must hold write spinlock
+ */
+static void snd_ps3_bump_buffer(struct snd_ps3_card_info *card,
+				enum snd_ps3_ch ch, size_t byte_count,
+				int stage)
+{
+	if (!stage)
+		card->dma_last_transfer_vaddr[ch] =
+			card->dma_next_transfer_vaddr[ch];
+	card->dma_next_transfer_vaddr[ch] += byte_count;
+	if ((card->dma_start_vaddr[ch] + (card->dma_buffer_size / 2)) <=
+	    card->dma_next_transfer_vaddr[ch]) {
+		card->dma_next_transfer_vaddr[ch] = card->dma_start_vaddr[ch];
+	}
+}
+/*
+ * setup dmac to send data to audio and attenuate samples on the ring buffer
+ */
+static int snd_ps3_program_dma(struct snd_ps3_card_info *card,
+			       enum snd_ps3_dma_filltype filltype)
+{
+	/* this dmac does not support over 4G */
+	uint32_t dma_addr;
+	int fill_stages, dma_ch, stage;
+	enum snd_ps3_ch ch;
+	uint32_t ch0_kick_event = 0; /* initialize to mute gcc */
+	void *start_vaddr;
+	unsigned long irqsave;
+	int silent = 0;
+
+	switch (filltype) {
+	case SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL:
+		silent = 1;
+		/* intentionally fall thru */
+	case SND_PS3_DMA_FILLTYPE_FIRSTFILL:
+		ch0_kick_event = PS3_AUDIO_KICK_EVENT_ALWAYS;
+		break;
+
+	case SND_PS3_DMA_FILLTYPE_SILENT_RUNNING:
+		silent = 1;
+		/* intentionally fall thru */
+	case SND_PS3_DMA_FILLTYPE_RUNNING:
+		ch0_kick_event = PS3_AUDIO_KICK_EVENT_SERIALOUT0_EMPTY;
+		break;
+	}
+
+	snd_ps3_verify_dma_stop(card, 700, 0);
+	fill_stages = 4;
+	spin_lock_irqsave(&card->dma_lock, irqsave);
+	for (ch = 0; ch < 2; ch++) {
+		start_vaddr = card->dma_next_transfer_vaddr[0];
+		for (stage = 0; stage < fill_stages; stage++) {
+			dma_ch = stage * 2 + ch;
+			if (silent)
+				dma_addr = card->null_buffer_start_dma_addr;
+			else
+				dma_addr =
+				v_to_bus(card,
+					 card->dma_next_transfer_vaddr[ch],
+					 ch);
+
+			write_reg(PS3_AUDIO_SOURCE(dma_ch),
+				  (PS3_AUDIO_SOURCE_TARGET_SYSTEM_MEMORY |
+				   dma_addr));
+
+			/* dst: fixed to 3wire#0 */
+			if (ch == 0)
+				write_reg(PS3_AUDIO_DEST(dma_ch),
+					  (PS3_AUDIO_DEST_TARGET_AUDIOFIFO |
+					   PS3_AUDIO_AO_3W_LDATA(0)));
+			else
+				write_reg(PS3_AUDIO_DEST(dma_ch),
+					  (PS3_AUDIO_DEST_TARGET_AUDIOFIFO |
+					   PS3_AUDIO_AO_3W_RDATA(0)));
+
+			/* count always 1 DMA block (1/2 stage = 128 bytes) */
+			write_reg(PS3_AUDIO_DMASIZE(dma_ch), 0);
+			/* bump pointer if needed */
+			if (!silent)
+				snd_ps3_bump_buffer(card, ch,
+						    PS3_AUDIO_DMAC_BLOCK_SIZE,
+						    stage);
+
+			/* kick event  */
+			if (dma_ch == 0)
+				write_reg(PS3_AUDIO_KICK(dma_ch),
+					  ch0_kick_event);
+			else
+				write_reg(PS3_AUDIO_KICK(dma_ch),
+					  PS3_AUDIO_KICK_EVENT_AUDIO_DMA(dma_ch
+									 - 1) |
+					  PS3_AUDIO_KICK_REQUEST);
+		}
+	}
+	/* ensure the hardware sees the change */
+	wmb();
+	spin_unlock_irqrestore(&card->dma_lock, irqsave);
+
+	return 0;
+}
+
+/*
+ * Interrupt handler
+ */
+static irqreturn_t snd_ps3_interrupt(int irq, void *dev_id)
+{
+
+	uint32_t port_intr;
+	int underflow_occured = 0;
+	struct snd_ps3_card_info *card = dev_id;
+
+	if (!card->running) {
+		update_reg(PS3_AUDIO_AX_IS, 0);
+		update_reg(PS3_AUDIO_INTR_0, 0);
+		return IRQ_HANDLED;
+	}
+
+	port_intr = read_reg(PS3_AUDIO_AX_IS);
+	/*
+	 *serial buffer empty detected (every 4 times),
+	 *program next dma and kick it
+	 */
+	if (port_intr & PS3_AUDIO_AX_IE_ASOBEIE(0)) {
+		write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBEIE(0));
+		if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) {
+			write_reg(PS3_AUDIO_AX_IS, port_intr);
+			underflow_occured = 1;
+		}
+		if (card->silent) {
+			/* we are still in silent time */
+			snd_ps3_program_dma(card,
+				(underflow_occured) ?
+				SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL :
+				SND_PS3_DMA_FILLTYPE_SILENT_RUNNING);
+			snd_ps3_kick_dma(card);
+			card->silent--;
+		} else {
+			snd_ps3_program_dma(card,
+				(underflow_occured) ?
+				SND_PS3_DMA_FILLTYPE_FIRSTFILL :
+				SND_PS3_DMA_FILLTYPE_RUNNING);
+			snd_ps3_kick_dma(card);
+			snd_pcm_period_elapsed(card->substream);
+		}
+	} else if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) {
+		write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBUIE(0));
+		/*
+		 * serial out underflow, but buffer empty not detected.
+		 * in this case, fill fifo with 0 to recover.  After
+		 * filling dummy data, serial automatically start to
+		 * consume them and then will generate normal buffer
+		 * empty interrupts.
+		 * If both buffer underflow and buffer empty are occured,
+		 * it is better to do nomal data transfer than empty one
+		 */
+		snd_ps3_program_dma(card,
+				    SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL);
+		snd_ps3_kick_dma(card);
+		snd_ps3_program_dma(card,
+				    SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL);
+		snd_ps3_kick_dma(card);
+	}
+	/* clear interrupt cause */
+	return IRQ_HANDLED;
+};
+
+/*
+ * audio mute on/off
+ * mute_on : 0 output enabled
+ *           1 mute
+ */
+static int snd_ps3_mute(int mute_on)
+{
+	return ps3av_audio_mute(mute_on);
+}
+
+/*
+ * av setting
+ * NOTE: calling this function may generate audio interrupt.
+ */
+static int snd_ps3_change_avsetting(struct snd_ps3_card_info *card)
+{
+	int ret, retries, i;
+	pr_debug("%s: start\n", __func__);
+
+	ret = ps3av_set_audio_mode(card->avs.avs_audio_ch,
+				  card->avs.avs_audio_rate,
+				  card->avs.avs_audio_width,
+				  card->avs.avs_audio_format,
+				  card->avs.avs_audio_source);
+	/*
+	 * Reset the following unwanted settings:
+	 */
+
+	/* disable all 3wire buffers */
+	update_mask_reg(PS3_AUDIO_AO_3WMCTRL,
+			~(PS3_AUDIO_AO_3WMCTRL_ASOEN(0) |
+			  PS3_AUDIO_AO_3WMCTRL_ASOEN(1) |
+			  PS3_AUDIO_AO_3WMCTRL_ASOEN(2) |
+			  PS3_AUDIO_AO_3WMCTRL_ASOEN(3)),
+			0);
+	wmb();	/* ensure the hardware sees the change */
+	/* wait for actually stopped */
+	retries = 1000;
+	while ((read_reg(PS3_AUDIO_AO_3WMCTRL) &
+		(PS3_AUDIO_AO_3WMCTRL_ASORUN(0) |
+		 PS3_AUDIO_AO_3WMCTRL_ASORUN(1) |
+		 PS3_AUDIO_AO_3WMCTRL_ASORUN(2) |
+		 PS3_AUDIO_AO_3WMCTRL_ASORUN(3))) &&
+	       --retries) {
+		udelay(1);
+	}
+
+	/* reset buffer pointer */
+	for (i = 0; i < 4; i++) {
+		update_reg(PS3_AUDIO_AO_3WCTRL(i),
+			   PS3_AUDIO_AO_3WCTRL_ASOBRST_RESET);
+		udelay(10);
+	}
+	wmb(); /* ensure the hardware actually start resetting */
+
+	/* enable 3wire#0 buffer */
+	update_reg(PS3_AUDIO_AO_3WMCTRL, PS3_AUDIO_AO_3WMCTRL_ASOEN(0));
+
+
+	/* In 24bit mode,ALSA inserts a zero byte at first byte of per sample */
+	update_mask_reg(PS3_AUDIO_AO_3WCTRL(0),
+			~PS3_AUDIO_AO_3WCTRL_ASODF,
+			PS3_AUDIO_AO_3WCTRL_ASODF_LSB);
+	update_mask_reg(PS3_AUDIO_AO_SPDCTRL(0),
+			~PS3_AUDIO_AO_SPDCTRL_SPODF,
+			PS3_AUDIO_AO_SPDCTRL_SPODF_LSB);
+	/* ensure all the setting above is written back to register */
+	wmb();
+	/* avsetting driver altered AX_IE, caller must reset it if you want */
+	pr_debug("%s: end\n", __func__);
+	return ret;
+}
+
+/*
+ *  set sampling rate according to the substream
+ */
+static int snd_ps3_set_avsetting(struct snd_pcm_substream *substream)
+{
+	struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);
+	struct snd_ps3_avsetting_info avs;
+	int ret;
+
+	avs = card->avs;
+
+	pr_debug("%s: called freq=%d width=%d\n", __func__,
+		 substream->runtime->rate,
+		 snd_pcm_format_width(substream->runtime->format));
+
+	pr_debug("%s: before freq=%d width=%d\n", __func__,
+		 card->avs.avs_audio_rate, card->avs.avs_audio_width);
+
+	/* sample rate */
+	switch (substream->runtime->rate) {
+	case 44100:
+		avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_44K;
+		break;
+	case 48000:
+		avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K;
+		break;
+	case 88200:
+		avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_88K;
+		break;
+	case 96000:
+		avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_96K;
+		break;
+	default:
+		pr_info("%s: invalid rate %d\n", __func__,
+			substream->runtime->rate);
+		return 1;
+	}
+
+	/* width */
+	switch (snd_pcm_format_width(substream->runtime->format)) {
+	case 16:
+		avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16;
+		break;
+	case 24:
+		avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_24;
+		break;
+	default:
+		pr_info("%s: invalid width %d\n", __func__,
+			snd_pcm_format_width(substream->runtime->format));
+		return 1;
+	}
+
+	memcpy(avs.avs_cs_info, ps3av_mode_cs_info, 8);
+
+	if (memcmp(&card->avs, &avs, sizeof(avs))) {
+		pr_debug("%s: after freq=%d width=%d\n", __func__,
+			 card->avs.avs_audio_rate, card->avs.avs_audio_width);
+
+		card->avs = avs;
+		snd_ps3_change_avsetting(card);
+		ret = 0;
+	} else
+		ret = 1;
+
+	/* check CS non-audio bit and mute accordingly */
+	if (avs.avs_cs_info[0] & 0x02)
+		ps3av_audio_mute_analog(1); /* mute if non-audio */
+	else
+		ps3av_audio_mute_analog(0);
+
+	return ret;
+}
+
+/*
+ * PCM operators
+ */
+static int snd_ps3_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);
+	int pcm_index;
+
+	pcm_index = substream->pcm->device;
+	/* to retrieve substream/runtime in interrupt handler */
+	card->substream = substream;
+
+	runtime->hw = snd_ps3_pcm_hw;
+
+	card->start_delay = snd_ps3_start_delay;
+
+	/* mute off */
+	snd_ps3_mute(0); /* this function sleep */
+
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+				   PS3_AUDIO_FIFO_STAGE_SIZE * 4 * 2);
+	return 0;
+};
+
+static int snd_ps3_pcm_close(struct snd_pcm_substream *substream)
+{
+	/* mute on */
+	snd_ps3_mute(1);
+	return 0;
+};
+
+static int snd_ps3_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	size_t size;
+
+	/* alloc transport buffer */
+	size = params_buffer_bytes(hw_params);
+	snd_pcm_lib_malloc_pages(substream, size);
+	return 0;
+};
+
+static int snd_ps3_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	int ret;
+	ret = snd_pcm_lib_free_pages(substream);
+	return ret;
+};
+
+static int snd_ps3_delay_to_bytes(struct snd_pcm_substream *substream,
+				  unsigned int delay_ms)
+{
+	int ret;
+	int rate ;
+
+	rate = substream->runtime->rate;
+	ret = snd_pcm_format_size(substream->runtime->format,
+				  rate * delay_ms / 1000)
+		* substream->runtime->channels;
+
+	pr_debug("%s: time=%d rate=%d bytes=%ld, frames=%d, ret=%d\n",
+		 __func__,
+		 delay_ms,
+		 rate,
+		 snd_pcm_format_size(substream->runtime->format, rate),
+		 rate * delay_ms / 1000,
+		 ret);
+
+	return ret;
+};
+
+static int snd_ps3_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);
+	unsigned long irqsave;
+
+	if (!snd_ps3_set_avsetting(substream)) {
+		/* some parameter changed */
+		write_reg(PS3_AUDIO_AX_IE,
+			  PS3_AUDIO_AX_IE_ASOBEIE(0) |
+			  PS3_AUDIO_AX_IE_ASOBUIE(0));
+		/*
+		 * let SPDIF device re-lock with SPDIF signal,
+		 * start with some silence
+		 */
+		card->silent = snd_ps3_delay_to_bytes(substream,
+						      card->start_delay) /
+			(PS3_AUDIO_FIFO_STAGE_SIZE * 4); /* every 4 times */
+	}
+
+	/* restart ring buffer pointer */
+	spin_lock_irqsave(&card->dma_lock, irqsave);
+	{
+		card->dma_buffer_size = runtime->dma_bytes;
+
+		card->dma_last_transfer_vaddr[SND_PS3_CH_L] =
+			card->dma_next_transfer_vaddr[SND_PS3_CH_L] =
+			card->dma_start_vaddr[SND_PS3_CH_L] =
+			runtime->dma_area;
+		card->dma_start_bus_addr[SND_PS3_CH_L] = runtime->dma_addr;
+
+		card->dma_last_transfer_vaddr[SND_PS3_CH_R] =
+			card->dma_next_transfer_vaddr[SND_PS3_CH_R] =
+			card->dma_start_vaddr[SND_PS3_CH_R] =
+			runtime->dma_area + (runtime->dma_bytes / 2);
+		card->dma_start_bus_addr[SND_PS3_CH_R] =
+			runtime->dma_addr + (runtime->dma_bytes / 2);
+
+		pr_debug("%s: vaddr=%p bus=%#llx\n", __func__,
+			 card->dma_start_vaddr[SND_PS3_CH_L],
+			 card->dma_start_bus_addr[SND_PS3_CH_L]);
+
+	}
+	spin_unlock_irqrestore(&card->dma_lock, irqsave);
+
+	/* ensure the hardware sees the change */
+	mb();
+
+	return 0;
+};
+
+static int snd_ps3_pcm_trigger(struct snd_pcm_substream *substream,
+			       int cmd)
+{
+	struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* clear outstanding interrupts  */
+		update_reg(PS3_AUDIO_AX_IS, 0);
+
+		spin_lock(&card->dma_lock);
+		{
+			card->running = 1;
+		}
+		spin_unlock(&card->dma_lock);
+
+		snd_ps3_program_dma(card,
+				    SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL);
+		snd_ps3_kick_dma(card);
+		while (read_reg(PS3_AUDIO_KICK(7)) &
+		       PS3_AUDIO_KICK_STATUS_MASK) {
+			udelay(1);
+		}
+		snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_RUNNING);
+		snd_ps3_kick_dma(card);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		spin_lock(&card->dma_lock);
+		{
+			card->running = 0;
+		}
+		spin_unlock(&card->dma_lock);
+		snd_ps3_wait_for_dma_stop(card);
+		break;
+	default:
+		break;
+
+	}
+
+	return ret;
+};
+
+/*
+ * report current pointer
+ */
+static snd_pcm_uframes_t snd_ps3_pcm_pointer(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);
+	size_t bytes;
+	snd_pcm_uframes_t ret;
+
+	spin_lock(&card->dma_lock);
+	{
+		bytes = (size_t)(card->dma_last_transfer_vaddr[SND_PS3_CH_L] -
+				 card->dma_start_vaddr[SND_PS3_CH_L]);
+	}
+	spin_unlock(&card->dma_lock);
+
+	ret = bytes_to_frames(substream->runtime, bytes * 2);
+
+	return ret;
+};
+
+/*
+ * SPDIF status bits controls
+ */
+static int snd_ps3_spdif_mask_info(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+/* FIXME: ps3av_set_audio_mode() assumes only consumer mode */
+static int snd_ps3_spdif_cmask_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	memset(ucontrol->value.iec958.status, 0xff, 8);
+	return 0;
+}
+
+static int snd_ps3_spdif_pmask_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	return 0;
+}
+
+static int snd_ps3_spdif_default_get(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	memcpy(ucontrol->value.iec958.status, ps3av_mode_cs_info, 8);
+	return 0;
+}
+
+static int snd_ps3_spdif_default_put(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	if (memcmp(ps3av_mode_cs_info, ucontrol->value.iec958.status, 8)) {
+		memcpy(ps3av_mode_cs_info, ucontrol->value.iec958.status, 8);
+		return 1;
+	}
+	return 0;
+}
+
+static struct snd_kcontrol_new spdif_ctls[] = {
+	{
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+		.info = snd_ps3_spdif_mask_info,
+		.get = snd_ps3_spdif_cmask_get,
+	},
+	{
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK),
+		.info = snd_ps3_spdif_mask_info,
+		.get = snd_ps3_spdif_pmask_get,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+		.info = snd_ps3_spdif_mask_info,
+		.get = snd_ps3_spdif_default_get,
+		.put = snd_ps3_spdif_default_put,
+	},
+};
+
+static struct snd_pcm_ops snd_ps3_pcm_spdif_ops = {
+	.open = snd_ps3_pcm_open,
+	.close = snd_ps3_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_ps3_pcm_hw_params,
+	.hw_free = snd_ps3_pcm_hw_free,
+	.prepare = snd_ps3_pcm_prepare,
+	.trigger = snd_ps3_pcm_trigger,
+	.pointer = snd_ps3_pcm_pointer,
+};
+
+
+static int __devinit snd_ps3_map_mmio(void)
+{
+	the_card.mapped_mmio_vaddr =
+		ioremap(the_card.ps3_dev->m_region->bus_addr,
+			the_card.ps3_dev->m_region->len);
+
+	if (!the_card.mapped_mmio_vaddr) {
+		pr_info("%s: ioremap 0 failed p=%#lx l=%#lx \n",
+		       __func__, the_card.ps3_dev->m_region->lpar_addr,
+		       the_card.ps3_dev->m_region->len);
+		return -ENXIO;
+	}
+
+	return 0;
+};
+
+static void snd_ps3_unmap_mmio(void)
+{
+	iounmap(the_card.mapped_mmio_vaddr);
+	the_card.mapped_mmio_vaddr = NULL;
+}
+
+static int __devinit snd_ps3_allocate_irq(void)
+{
+	int ret;
+	u64 lpar_addr, lpar_size;
+	u64 __iomem *mapped;
+
+	/* FIXME: move this to device_init (H/W probe) */
+
+	/* get irq outlet */
+	ret = lv1_gpu_device_map(1, &lpar_addr, &lpar_size);
+	if (ret) {
+		pr_info("%s: device map 1 failed %d\n", __func__,
+			ret);
+		return -ENXIO;
+	}
+
+	mapped = ioremap(lpar_addr, lpar_size);
+	if (!mapped) {
+		pr_info("%s: ioremap 1 failed \n", __func__);
+		return -ENXIO;
+	}
+
+	the_card.audio_irq_outlet = in_be64(mapped);
+
+	iounmap(mapped);
+	ret = lv1_gpu_device_unmap(1);
+	if (ret)
+		pr_info("%s: unmap 1 failed\n", __func__);
+
+	/* irq */
+	ret = ps3_irq_plug_setup(PS3_BINDING_CPU_ANY,
+				 the_card.audio_irq_outlet,
+				 &the_card.irq_no);
+	if (ret) {
+		pr_info("%s:ps3_alloc_irq failed (%d)\n", __func__, ret);
+		return ret;
+	}
+
+	ret = request_irq(the_card.irq_no, snd_ps3_interrupt, IRQF_DISABLED,
+			  SND_PS3_DRIVER_NAME, &the_card);
+	if (ret) {
+		pr_info("%s: request_irq failed (%d)\n", __func__, ret);
+		goto cleanup_irq;
+	}
+
+	return 0;
+
+ cleanup_irq:
+	ps3_irq_plug_destroy(the_card.irq_no);
+	return ret;
+};
+
+static void snd_ps3_free_irq(void)
+{
+	free_irq(the_card.irq_no, &the_card);
+	ps3_irq_plug_destroy(the_card.irq_no);
+}
+
+static void __devinit snd_ps3_audio_set_base_addr(uint64_t ioaddr_start)
+{
+	uint64_t val;
+	int ret;
+
+	val = (ioaddr_start & (0x0fUL << 32)) >> (32 - 20) |
+		(0x03UL << 24) |
+		(0x0fUL << 12) |
+		(PS3_AUDIO_IOID);
+
+	ret = lv1_gpu_attribute(0x100, 0x007, val, 0, 0);
+	if (ret)
+		pr_info("%s: gpu_attribute failed %d\n", __func__,
+			ret);
+}
+
+static void __devinit snd_ps3_audio_fixup(struct snd_ps3_card_info *card)
+{
+	/*
+	 * avsetting driver seems to never change the followings
+	 * so, init them here once
+	 */
+
+	/* no dma interrupt needed */
+	write_reg(PS3_AUDIO_INTR_EN_0, 0);
+
+	/* use every 4 buffer empty interrupt */
+	update_mask_reg(PS3_AUDIO_AX_IC,
+			PS3_AUDIO_AX_IC_AASOIMD_MASK,
+			PS3_AUDIO_AX_IC_AASOIMD_EVERY4);
+
+	/* enable 3wire clocks */
+	update_mask_reg(PS3_AUDIO_AO_3WMCTRL,
+			~(PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_DISABLED |
+			  PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_DISABLED),
+			0);
+	update_reg(PS3_AUDIO_AO_3WMCTRL,
+		   PS3_AUDIO_AO_3WMCTRL_ASOPLRCK_DEFAULT);
+}
+
+static int __devinit snd_ps3_init_avsetting(struct snd_ps3_card_info *card)
+{
+	int ret;
+	pr_debug("%s: start\n", __func__);
+	card->avs.avs_audio_ch = PS3AV_CMD_AUDIO_NUM_OF_CH_2;
+	card->avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K;
+	card->avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16;
+	card->avs.avs_audio_format = PS3AV_CMD_AUDIO_FORMAT_PCM;
+	card->avs.avs_audio_source = PS3AV_CMD_AUDIO_SOURCE_SERIAL;
+	memcpy(card->avs.avs_cs_info, ps3av_mode_cs_info, 8);
+
+	ret = snd_ps3_change_avsetting(card);
+
+	snd_ps3_audio_fixup(card);
+
+	/* to start to generate SPDIF signal, fill data */
+	snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL);
+	snd_ps3_kick_dma(card);
+	pr_debug("%s: end\n", __func__);
+	return ret;
+}
+
+static int __devinit snd_ps3_driver_probe(struct ps3_system_bus_device *dev)
+{
+	int i, ret;
+	u64 lpar_addr, lpar_size;
+
+	BUG_ON(!firmware_has_feature(FW_FEATURE_PS3_LV1));
+	BUG_ON(dev->match_id != PS3_MATCH_ID_SOUND);
+
+	the_card.ps3_dev = dev;
+
+	ret = ps3_open_hv_device(dev);
+
+	if (ret)
+		return -ENXIO;
+
+	/* setup MMIO */
+	ret = lv1_gpu_device_map(2, &lpar_addr, &lpar_size);
+	if (ret) {
+		pr_info("%s: device map 2 failed %d\n", __func__, ret);
+		goto clean_open;
+	}
+	ps3_mmio_region_init(dev, dev->m_region, lpar_addr, lpar_size,
+		PAGE_SHIFT);
+
+	ret = snd_ps3_map_mmio();
+	if (ret)
+		goto clean_dev_map;
+
+	/* setup DMA area */
+	ps3_dma_region_init(dev, dev->d_region,
+			    PAGE_SHIFT, /* use system page size */
+			    0, /* dma type; not used */
+			    NULL,
+			    _ALIGN_UP(SND_PS3_DMA_REGION_SIZE, PAGE_SIZE));
+	dev->d_region->ioid = PS3_AUDIO_IOID;
+
+	ret = ps3_dma_region_create(dev->d_region);
+	if (ret) {
+		pr_info("%s: region_create\n", __func__);
+		goto clean_mmio;
+	}
+
+	snd_ps3_audio_set_base_addr(dev->d_region->bus_addr);
+
+	/* CONFIG_SND_PS3_DEFAULT_START_DELAY */
+	the_card.start_delay = snd_ps3_start_delay;
+
+	/* irq */
+	if (snd_ps3_allocate_irq()) {
+		ret = -ENXIO;
+		goto clean_dma_region;
+	}
+
+	/* create card instance */
+	ret = snd_card_create(index, id, THIS_MODULE, 0, &the_card.card);
+	if (ret < 0)
+		goto clean_irq;
+
+	strcpy(the_card.card->driver, "PS3");
+	strcpy(the_card.card->shortname, "PS3");
+	strcpy(the_card.card->longname, "PS3 sound");
+
+	/* create control elements */
+	for (i = 0; i < ARRAY_SIZE(spdif_ctls); i++) {
+		ret = snd_ctl_add(the_card.card,
+				  snd_ctl_new1(&spdif_ctls[i], &the_card));
+		if (ret < 0)
+			goto clean_card;
+	}
+
+	/* create PCM devices instance */
+	/* NOTE:this driver works assuming pcm:substream = 1:1 */
+	ret = snd_pcm_new(the_card.card,
+			  "SPDIF",
+			  0, /* instance index, will be stored pcm.device*/
+			  1, /* output substream */
+			  0, /* input substream */
+			  &(the_card.pcm));
+	if (ret)
+		goto clean_card;
+
+	the_card.pcm->private_data = &the_card;
+	strcpy(the_card.pcm->name, "SPDIF");
+
+	/* set pcm ops */
+	snd_pcm_set_ops(the_card.pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_ps3_pcm_spdif_ops);
+
+	the_card.pcm->info_flags = SNDRV_PCM_INFO_NONINTERLEAVED;
+	/* pre-alloc PCM DMA buffer*/
+	ret = snd_pcm_lib_preallocate_pages_for_all(the_card.pcm,
+					SNDRV_DMA_TYPE_DEV,
+					&dev->core,
+					SND_PS3_PCM_PREALLOC_SIZE,
+					SND_PS3_PCM_PREALLOC_SIZE);
+	if (ret < 0) {
+		pr_info("%s: prealloc failed\n", __func__);
+		goto clean_card;
+	}
+
+	/*
+	 * allocate null buffer
+	 * its size should be lager than PS3_AUDIO_FIFO_STAGE_SIZE * 2
+	 * PAGE_SIZE is enogh
+	 */
+	the_card.null_buffer_start_vaddr =
+		dma_alloc_coherent(&the_card.ps3_dev->core,
+				   PAGE_SIZE,
+				   &the_card.null_buffer_start_dma_addr,
+				   GFP_KERNEL);
+	if (!the_card.null_buffer_start_vaddr) {
+		pr_info("%s: nullbuffer alloc failed\n", __func__);
+		goto clean_preallocate;
+	}
+	pr_debug("%s: null vaddr=%p dma=%#llx\n", __func__,
+		 the_card.null_buffer_start_vaddr,
+		 the_card.null_buffer_start_dma_addr);
+	/* set default sample rate/word width */
+	snd_ps3_init_avsetting(&the_card);
+
+	/* register the card */
+	snd_card_set_dev(the_card.card, &dev->core);
+	ret = snd_card_register(the_card.card);
+	if (ret < 0)
+		goto clean_dma_map;
+
+	pr_info("%s started. start_delay=%dms\n",
+		the_card.card->longname, the_card.start_delay);
+	return 0;
+
+clean_dma_map:
+	dma_free_coherent(&the_card.ps3_dev->core,
+			  PAGE_SIZE,
+			  the_card.null_buffer_start_vaddr,
+			  the_card.null_buffer_start_dma_addr);
+clean_preallocate:
+	snd_pcm_lib_preallocate_free_for_all(the_card.pcm);
+clean_card:
+	snd_card_free(the_card.card);
+clean_irq:
+	snd_ps3_free_irq();
+clean_dma_region:
+	ps3_dma_region_free(dev->d_region);
+clean_mmio:
+	snd_ps3_unmap_mmio();
+clean_dev_map:
+	lv1_gpu_device_unmap(2);
+clean_open:
+	ps3_close_hv_device(dev);
+	/*
+	 * there is no destructor function to pcm.
+	 * midlayer automatically releases if the card removed
+	 */
+	return ret;
+}; /* snd_ps3_probe */
+
+/* called when module removal */
+static int snd_ps3_driver_remove(struct ps3_system_bus_device *dev)
+{
+	int ret;
+	pr_info("%s:start id=%d\n", __func__,  dev->match_id);
+	if (dev->match_id != PS3_MATCH_ID_SOUND)
+		return -ENXIO;
+
+	/*
+	 * ctl and preallocate buffer will be freed in
+	 * snd_card_free
+	 */
+	ret = snd_card_free(the_card.card);
+	if (ret)
+		pr_info("%s: ctl freecard=%d\n", __func__, ret);
+
+	dma_free_coherent(&dev->core,
+			  PAGE_SIZE,
+			  the_card.null_buffer_start_vaddr,
+			  the_card.null_buffer_start_dma_addr);
+
+	ps3_dma_region_free(dev->d_region);
+
+	snd_ps3_free_irq();
+	snd_ps3_unmap_mmio();
+
+	lv1_gpu_device_unmap(2);
+	ps3_close_hv_device(dev);
+	pr_info("%s:end id=%d\n", __func__, dev->match_id);
+	return 0;
+} /* snd_ps3_remove */
+
+static struct ps3_system_bus_driver snd_ps3_bus_driver_info = {
+	.match_id = PS3_MATCH_ID_SOUND,
+	.probe = snd_ps3_driver_probe,
+	.remove = snd_ps3_driver_remove,
+	.shutdown = snd_ps3_driver_remove,
+	.core = {
+		.name = SND_PS3_DRIVER_NAME,
+		.owner = THIS_MODULE,
+	},
+};
+
+
+/*
+ * module/subsystem initialize/terminate
+ */
+static int __init snd_ps3_init(void)
+{
+	int ret;
+
+	if (!firmware_has_feature(FW_FEATURE_PS3_LV1))
+		return -ENXIO;
+
+	memset(&the_card, 0, sizeof(the_card));
+	spin_lock_init(&the_card.dma_lock);
+
+	/* register systembus DRIVER, this calls our probe() func */
+	ret = ps3_system_bus_driver_register(&snd_ps3_bus_driver_info);
+
+	return ret;
+}
+module_init(snd_ps3_init);
+
+static void __exit snd_ps3_exit(void)
+{
+	ps3_system_bus_driver_unregister(&snd_ps3_bus_driver_info);
+}
+module_exit(snd_ps3_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PS3 sound driver");
+MODULE_AUTHOR("Sony Computer Entertainment Inc.");
+MODULE_ALIAS(PS3_MODULE_ALIAS_SOUND);
diff --git a/linux/sound/ppc/snd_ps3.h b/linux/sound/ppc/snd_ps3.h
new file mode 100644
index 0000000..326fb29
--- /dev/null
+++ b/linux/sound/ppc/snd_ps3.h
@@ -0,0 +1,136 @@
+/*
+ * Audio support for PS3
+ * Copyright (C) 2007 Sony Computer Entertainment Inc.
+ * All rights reserved.
+ * Copyright 2006, 2007 Sony Corporation
+ *
+ * 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; version 2 of the Licence.
+ *
+ * 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 !defined(_SND_PS3_H_)
+#define _SND_PS3_H_
+
+#include <linux/irqreturn.h>
+
+#define SND_PS3_DRIVER_NAME "snd_ps3"
+
+enum snd_ps3_out_channel {
+	SND_PS3_OUT_SPDIF_0,
+	SND_PS3_OUT_SPDIF_1,
+	SND_PS3_OUT_SERIAL_0,
+	SND_PS3_OUT_DEVS
+};
+
+enum snd_ps3_dma_filltype {
+	SND_PS3_DMA_FILLTYPE_FIRSTFILL,
+	SND_PS3_DMA_FILLTYPE_RUNNING,
+	SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL,
+	SND_PS3_DMA_FILLTYPE_SILENT_RUNNING
+};
+
+enum snd_ps3_ch {
+	SND_PS3_CH_L = 0,
+	SND_PS3_CH_R = 1,
+	SND_PS3_CH_MAX = 2
+};
+
+struct snd_ps3_avsetting_info {
+	uint32_t avs_audio_ch;     /* fixed */
+	uint32_t avs_audio_rate;
+	uint32_t avs_audio_width;
+	uint32_t avs_audio_format; /* fixed */
+	uint32_t avs_audio_source; /* fixed */
+	unsigned char avs_cs_info[8];
+};
+/*
+ * PS3 audio 'card' instance
+ * there should be only ONE hardware.
+ */
+struct snd_ps3_card_info {
+	struct ps3_system_bus_device *ps3_dev;
+	struct snd_card *card;
+
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+
+	/* hvc info */
+	u64 audio_lpar_addr;
+	u64 audio_lpar_size;
+
+	/* registers */
+	void __iomem *mapped_mmio_vaddr;
+
+	/* irq */
+	u64 audio_irq_outlet;
+	unsigned int irq_no;
+
+	/* remember avsetting */
+	struct snd_ps3_avsetting_info avs;
+
+	/* dma buffer management */
+	spinlock_t dma_lock;
+		/* dma_lock start */
+		void * dma_start_vaddr[2]; /* 0 for L, 1 for R */
+		dma_addr_t dma_start_bus_addr[2];
+		size_t dma_buffer_size;
+		void * dma_last_transfer_vaddr[2];
+		void * dma_next_transfer_vaddr[2];
+		int    silent;
+		/* dma_lock end */
+
+	int running;
+
+	/* null buffer */
+	void *null_buffer_start_vaddr;
+	dma_addr_t null_buffer_start_dma_addr;
+
+	/* start delay */
+	unsigned int start_delay;
+
+};
+
+
+/* PS3 audio DMAC block size in bytes */
+#define PS3_AUDIO_DMAC_BLOCK_SIZE (128)
+/* one stage (stereo)  of audio FIFO in bytes */
+#define PS3_AUDIO_FIFO_STAGE_SIZE (256)
+/* how many stages the fifo have */
+#define PS3_AUDIO_FIFO_STAGE_COUNT (8)
+/* fifo size 128 bytes * 8 stages * stereo (2ch) */
+#define PS3_AUDIO_FIFO_SIZE \
+	(PS3_AUDIO_FIFO_STAGE_SIZE * PS3_AUDIO_FIFO_STAGE_COUNT)
+
+/* PS3 audio DMAC max block count in one dma shot = 128 (0x80) blocks*/
+#define PS3_AUDIO_DMAC_MAX_BLOCKS  (PS3_AUDIO_DMASIZE_BLOCKS_MASK + 1)
+
+#define PS3_AUDIO_NORMAL_DMA_START_CH (0)
+#define PS3_AUDIO_NORMAL_DMA_COUNT    (8)
+#define PS3_AUDIO_NULL_DMA_START_CH \
+	(PS3_AUDIO_NORMAL_DMA_START_CH + PS3_AUDIO_NORMAL_DMA_COUNT)
+#define PS3_AUDIO_NULL_DMA_COUNT      (2)
+
+#define SND_PS3_MAX_VOL (0x0F)
+#define SND_PS3_MIN_VOL (0x00)
+#define SND_PS3_MIN_ATT SND_PS3_MIN_VOL
+#define SND_PS3_MAX_ATT SND_PS3_MAX_VOL
+
+#define SND_PS3_PCM_PREALLOC_SIZE \
+	(PS3_AUDIO_DMAC_BLOCK_SIZE * PS3_AUDIO_DMAC_MAX_BLOCKS * 4)
+
+#define SND_PS3_DMA_REGION_SIZE \
+	(SND_PS3_PCM_PREALLOC_SIZE + PAGE_SIZE)
+
+#define PS3_AUDIO_IOID       (1UL)
+
+#endif /* _SND_PS3_H_ */
diff --git a/linux/sound/ppc/snd_ps3_reg.h b/linux/sound/ppc/snd_ps3_reg.h
new file mode 100644
index 0000000..03fdee4
--- /dev/null
+++ b/linux/sound/ppc/snd_ps3_reg.h
@@ -0,0 +1,891 @@
+/*
+ * Audio support for PS3
+ * Copyright (C) 2007 Sony Computer Entertainment Inc.
+ * Copyright 2006, 2007 Sony Corporation
+ * All rights reserved.
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ */
+
+/*
+ * interrupt / configure registers
+ */
+
+#define PS3_AUDIO_INTR_0                 (0x00000100)
+#define PS3_AUDIO_INTR_EN_0              (0x00000140)
+#define PS3_AUDIO_CONFIG                 (0x00000200)
+
+/*
+ * DMAC registers
+ * n:0..9
+ */
+#define PS3_AUDIO_DMAC_REGBASE(x)         (0x0000210 + 0x20 * (x))
+
+#define PS3_AUDIO_KICK(n)                 (PS3_AUDIO_DMAC_REGBASE(n) + 0x00)
+#define PS3_AUDIO_SOURCE(n)               (PS3_AUDIO_DMAC_REGBASE(n) + 0x04)
+#define PS3_AUDIO_DEST(n)                 (PS3_AUDIO_DMAC_REGBASE(n) + 0x08)
+#define PS3_AUDIO_DMASIZE(n)              (PS3_AUDIO_DMAC_REGBASE(n) + 0x0C)
+
+/*
+ * mute control
+ */
+#define PS3_AUDIO_AX_MCTRL                (0x00004000)
+#define PS3_AUDIO_AX_ISBP                 (0x00004004)
+#define PS3_AUDIO_AX_AOBP                 (0x00004008)
+#define PS3_AUDIO_AX_IC                   (0x00004010)
+#define PS3_AUDIO_AX_IE                   (0x00004014)
+#define PS3_AUDIO_AX_IS                   (0x00004018)
+
+/*
+ * three wire serial
+ * n:0..3
+ */
+#define PS3_AUDIO_AO_MCTRL                (0x00006000)
+#define PS3_AUDIO_AO_3WMCTRL              (0x00006004)
+
+#define PS3_AUDIO_AO_3WCTRL(n)            (0x00006200 + 0x200 * (n))
+
+/*
+ * S/PDIF
+ * n:0..1
+ * x:0..11
+ * y:0..5
+ */
+#define PS3_AUDIO_AO_SPD_REGBASE(n)       (0x00007200 + 0x200 * (n))
+
+#define PS3_AUDIO_AO_SPDCTRL(n) \
+	(PS3_AUDIO_AO_SPD_REGBASE(n) + 0x00)
+#define PS3_AUDIO_AO_SPDUB(n, x) \
+	(PS3_AUDIO_AO_SPD_REGBASE(n) + 0x04 + 0x04 * (x))
+#define PS3_AUDIO_AO_SPDCS(n, y) \
+	(PS3_AUDIO_AO_SPD_REGBASE(n) + 0x34 + 0x04 * (y))
+
+
+/*
+  PS3_AUDIO_INTR_0 register tells an interrupt handler which audio
+  DMA channel triggered the interrupt.  The interrupt status for a channel
+  can be cleared by writing a '1' to the corresponding bit.  A new interrupt
+  cannot be generated until the previous interrupt has been cleared.
+
+  Note that the status reported by PS3_AUDIO_INTR_0 is independent of the
+  value of PS3_AUDIO_INTR_EN_0.
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0 0 0 0 0 0 0 0 0 0 0 0 0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C| INTR_0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+#define PS3_AUDIO_INTR_0_CHAN(n)	(1 << ((n) * 2))
+#define PS3_AUDIO_INTR_0_CHAN9     PS3_AUDIO_INTR_0_CHAN(9)
+#define PS3_AUDIO_INTR_0_CHAN8     PS3_AUDIO_INTR_0_CHAN(8)
+#define PS3_AUDIO_INTR_0_CHAN7     PS3_AUDIO_INTR_0_CHAN(7)
+#define PS3_AUDIO_INTR_0_CHAN6     PS3_AUDIO_INTR_0_CHAN(6)
+#define PS3_AUDIO_INTR_0_CHAN5     PS3_AUDIO_INTR_0_CHAN(5)
+#define PS3_AUDIO_INTR_0_CHAN4     PS3_AUDIO_INTR_0_CHAN(4)
+#define PS3_AUDIO_INTR_0_CHAN3     PS3_AUDIO_INTR_0_CHAN(3)
+#define PS3_AUDIO_INTR_0_CHAN2     PS3_AUDIO_INTR_0_CHAN(2)
+#define PS3_AUDIO_INTR_0_CHAN1     PS3_AUDIO_INTR_0_CHAN(1)
+#define PS3_AUDIO_INTR_0_CHAN0     PS3_AUDIO_INTR_0_CHAN(0)
+
+/*
+  The PS3_AUDIO_INTR_EN_0 register specifies which DMA channels can generate
+  an interrupt to the PU.  Each bit of PS3_AUDIO_INTR_EN_0 is ANDed with the
+  corresponding bit in PS3_AUDIO_INTR_0.  The resulting bits are OR'd together
+  to generate the Audio interrupt.
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0 0 0 0 0 0 0 0 0 0 0 0 0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C| INTR_EN_0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+
+  Bit assignments are same as PS3_AUDIO_INTR_0
+*/
+
+/*
+  PS3_AUDIO_CONFIG
+  31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 C|0 0 0 0 0 0 0 0| CONFIG
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+
+*/
+
+/* The CLEAR field cancels all pending transfers, and stops any running DMA
+   transfers.  Any interrupts associated with the canceled transfers
+   will occur as if the transfer had finished.
+   Since this bit is designed to recover from DMA related issues
+   which are caused by unpredictable situations, it is prefered to wait
+   for normal DMA transfer end without using this bit.
+*/
+#define PS3_AUDIO_CONFIG_CLEAR          (1 << 8)  /* RWIVF */
+
+/*
+  PS3_AUDIO_AX_MCTRL: Audio Port Mute Control Register
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|A|A|A|0 0 0 0 0 0 0|S|S|A|A|A|A| AX_MCTRL
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+
+/* 3 Wire Audio Serial Output Channel Mutes (0..3)  */
+#define PS3_AUDIO_AX_MCTRL_ASOMT(n)     (1 << (3 - (n)))  /* RWIVF */
+#define PS3_AUDIO_AX_MCTRL_ASO3MT       (1 << 0)          /* RWIVF */
+#define PS3_AUDIO_AX_MCTRL_ASO2MT       (1 << 1)          /* RWIVF */
+#define PS3_AUDIO_AX_MCTRL_ASO1MT       (1 << 2)          /* RWIVF */
+#define PS3_AUDIO_AX_MCTRL_ASO0MT       (1 << 3)          /* RWIVF */
+
+/* S/PDIF mutes (0,1)*/
+#define PS3_AUDIO_AX_MCTRL_SPOMT(n)     (1 << (5 - (n)))  /* RWIVF */
+#define PS3_AUDIO_AX_MCTRL_SPO1MT       (1 << 4)          /* RWIVF */
+#define PS3_AUDIO_AX_MCTRL_SPO0MT       (1 << 5)          /* RWIVF */
+
+/* All 3 Wire Serial Outputs Mute */
+#define PS3_AUDIO_AX_MCTRL_AASOMT       (1 << 13)         /* RWIVF */
+
+/* All S/PDIF Mute */
+#define PS3_AUDIO_AX_MCTRL_ASPOMT       (1 << 14)         /* RWIVF */
+
+/* All Audio Outputs Mute */
+#define PS3_AUDIO_AX_MCTRL_AAOMT        (1 << 15)         /* RWIVF */
+
+/*
+  S/PDIF Outputs Buffer Read/Write Pointer Register
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0 0 0 0 0 0 0 0|0|SPO0B|0|SPO1B|0 0 0 0 0 0 0 0|0|SPO0B|0|SPO1B| AX_ISBP
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+
+*/
+/*
+ S/PDIF Output Channel Read Buffer Numbers
+ Buffer number is  value of field.
+ Indicates current read access buffer ID from Audio Data
+ Transfer controller of S/PDIF Output
+*/
+
+#define PS3_AUDIO_AX_ISBP_SPOBRN_MASK(n) (0x7 << 4 * (1 - (n))) /* R-IUF */
+#define PS3_AUDIO_AX_ISBP_SPO1BRN_MASK		(0x7 << 0) /* R-IUF */
+#define PS3_AUDIO_AX_ISBP_SPO0BRN_MASK		(0x7 << 4) /* R-IUF */
+
+/*
+S/PDIF Output Channel Buffer Write Numbers
+Indicates current write access buffer ID from bus master.
+*/
+#define PS3_AUDIO_AX_ISBP_SPOBWN_MASK(n) (0x7 <<  4 * (5 - (n))) /* R-IUF */
+#define PS3_AUDIO_AX_ISBP_SPO1BWN_MASK		(0x7 << 16) /* R-IUF */
+#define PS3_AUDIO_AX_ISBP_SPO0BWN_MASK		(0x7 << 20) /* R-IUF */
+
+/*
+  3 Wire Audio Serial Outputs Buffer Read/Write
+  Pointer Register
+  Buffer number is  value of field
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0|ASO0B|0|ASO1B|0|ASO2B|0|ASO3B|0|ASO0B|0|ASO1B|0|ASO2B|0|ASO3B| AX_AOBP
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+
+/*
+3 Wire Audio Serial Output Channel Buffer Read Numbers
+Indicates current read access buffer Id from Audio Data Transfer
+Controller of 3 Wire Audio Serial Output Channels
+*/
+#define PS3_AUDIO_AX_AOBP_ASOBRN_MASK(n) (0x7 << 4 * (3 - (n))) /* R-IUF */
+
+#define PS3_AUDIO_AX_AOBP_ASO3BRN_MASK	(0x7 << 0) /* R-IUF */
+#define PS3_AUDIO_AX_AOBP_ASO2BRN_MASK	(0x7 << 4) /* R-IUF */
+#define PS3_AUDIO_AX_AOBP_ASO1BRN_MASK	(0x7 << 8) /* R-IUF */
+#define PS3_AUDIO_AX_AOBP_ASO0BRN_MASK	(0x7 << 12) /* R-IUF */
+
+/*
+3 Wire Audio Serial Output Channel Buffer Write Numbers
+Indicates current write access buffer ID from bus master.
+*/
+#define PS3_AUDIO_AX_AOBP_ASOBWN_MASK(n) (0x7 << 4 * (7 - (n))) /* R-IUF */
+
+#define PS3_AUDIO_AX_AOBP_ASO3BWN_MASK        (0x7 << 16) /* R-IUF */
+#define PS3_AUDIO_AX_AOBP_ASO2BWN_MASK        (0x7 << 20) /* R-IUF */
+#define PS3_AUDIO_AX_AOBP_ASO1BWN_MASK        (0x7 << 24) /* R-IUF */
+#define PS3_AUDIO_AX_AOBP_ASO0BWN_MASK        (0x7 << 28) /* R-IUF */
+
+
+
+/*
+Audio Port Interrupt Condition Register
+For the fields in this register, the following values apply:
+0 = Interrupt is generated every interrupt event.
+1 = Interrupt is generated every 2 interrupt events.
+2 = Interrupt is generated every 4 interrupt events.
+3 = Reserved
+
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0 0 0 0 0 0 0 0|0 0|SPO|0 0|SPO|0 0|AAS|0 0 0 0 0 0 0 0 0 0 0 0| AX_IC
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+/*
+All 3-Wire Audio Serial Outputs Interrupt Mode
+Configures the Interrupt and Signal Notification
+condition of all 3-wire Audio Serial Outputs.
+*/
+#define PS3_AUDIO_AX_IC_AASOIMD_MASK          (0x3 << 12) /* RWIVF */
+#define PS3_AUDIO_AX_IC_AASOIMD_EVERY1        (0x0 << 12) /* RWI-V */
+#define PS3_AUDIO_AX_IC_AASOIMD_EVERY2        (0x1 << 12) /* RW--V */
+#define PS3_AUDIO_AX_IC_AASOIMD_EVERY4        (0x2 << 12) /* RW--V */
+
+/*
+S/PDIF Output Channel Interrupt Modes
+Configures the Interrupt and signal Notification
+conditions of S/PDIF output channels.
+*/
+#define PS3_AUDIO_AX_IC_SPO1IMD_MASK          (0x3 << 16) /* RWIVF */
+#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY1        (0x0 << 16) /* RWI-V */
+#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY2        (0x1 << 16) /* RW--V */
+#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY4        (0x2 << 16) /* RW--V */
+
+#define PS3_AUDIO_AX_IC_SPO0IMD_MASK          (0x3 << 20) /* RWIVF */
+#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY1        (0x0 << 20) /* RWI-V */
+#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY2        (0x1 << 20) /* RW--V */
+#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY4        (0x2 << 20) /* RW--V */
+
+/*
+Audio Port interrupt Enable Register
+Configures whether to enable or disable each Interrupt Generation.
+
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0 0 0 0 0 0 0 0|S|S|0 0|A|A|A|A|0 0 0 0|S|S|0 0|S|S|0 0|A|A|A|A| AX_IE
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+
+*/
+
+/*
+3 Wire Audio Serial Output Channel Buffer Underflow
+Interrupt Enables
+Select enable/disable of Buffer Underflow Interrupts for
+3-Wire Audio Serial Output Channels
+DISABLED=Interrupt generation disabled.
+*/
+#define PS3_AUDIO_AX_IE_ASOBUIE(n)      (1 << (3 - (n))) /* RWIVF */
+#define PS3_AUDIO_AX_IE_ASO3BUIE        (1 << 0) /* RWIVF */
+#define PS3_AUDIO_AX_IE_ASO2BUIE        (1 << 1) /* RWIVF */
+#define PS3_AUDIO_AX_IE_ASO1BUIE        (1 << 2) /* RWIVF */
+#define PS3_AUDIO_AX_IE_ASO0BUIE        (1 << 3) /* RWIVF */
+
+/* S/PDIF Output Channel Buffer Underflow Interrupt Enables */
+
+#define PS3_AUDIO_AX_IE_SPOBUIE(n)      (1 << (7 - (n))) /* RWIVF */
+#define PS3_AUDIO_AX_IE_SPO1BUIE        (1 << 6) /* RWIVF */
+#define PS3_AUDIO_AX_IE_SPO0BUIE        (1 << 7) /* RWIVF */
+
+/* S/PDIF Output Channel One Block Transfer Completion Interrupt Enables */
+
+#define PS3_AUDIO_AX_IE_SPOBTCIE(n)     (1 << (11 - (n))) /* RWIVF */
+#define PS3_AUDIO_AX_IE_SPO1BTCIE       (1 << 10) /* RWIVF */
+#define PS3_AUDIO_AX_IE_SPO0BTCIE       (1 << 11) /* RWIVF */
+
+/* 3-Wire Audio Serial Output Channel Buffer Empty Interrupt Enables */
+
+#define PS3_AUDIO_AX_IE_ASOBEIE(n)      (1 << (19 - (n))) /* RWIVF */
+#define PS3_AUDIO_AX_IE_ASO3BEIE        (1 << 16) /* RWIVF */
+#define PS3_AUDIO_AX_IE_ASO2BEIE        (1 << 17) /* RWIVF */
+#define PS3_AUDIO_AX_IE_ASO1BEIE        (1 << 18) /* RWIVF */
+#define PS3_AUDIO_AX_IE_ASO0BEIE        (1 << 19) /* RWIVF */
+
+/* S/PDIF Output Channel Buffer Empty Interrupt Enables */
+
+#define PS3_AUDIO_AX_IE_SPOBEIE(n)      (1 << (23 - (n))) /* RWIVF */
+#define PS3_AUDIO_AX_IE_SPO1BEIE        (1 << 22) /* RWIVF */
+#define PS3_AUDIO_AX_IE_SPO0BEIE        (1 << 23) /* RWIVF */
+
+/*
+Audio Port Interrupt Status Register
+Indicates Interrupt status, which interrupt has occured, and can clear
+each interrupt in this register.
+Writing 1b to a field containing 1b clears field and de-asserts interrupt.
+Writing 0b to a field has no effect.
+Field vaules are the following:
+0 - Interrupt hasn't occured.
+1 - Interrupt has occured.
+
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0 0 0 0 0 0 0 0|S|S|0 0|A|A|A|A|0 0 0 0|S|S|0 0|S|S|0 0|A|A|A|A| AX_IS
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+
+ Bit assignment are same as AX_IE
+*/
+
+/*
+Audio Output Master Control Register
+Configures Master Clock and other master Audio Output Settings
+
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0|SCKSE|0|SCKSE|  MR0  |  MR1  |MCL|MCL|0 0 0 0|0 0 0 0 0 0 0 0| AO_MCTRL
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+
+/*
+MCLK Output Control
+Controls mclko[1] output.
+0 - Disable output (fixed at High)
+1 - Output clock produced by clock selected
+with scksel1 by mr1
+2 - Reserved
+3 - Reserved
+*/
+
+#define PS3_AUDIO_AO_MCTRL_MCLKC1_MASK		(0x3 << 12) /* RWIVF */
+#define PS3_AUDIO_AO_MCTRL_MCLKC1_DISABLED	(0x0 << 12) /* RWI-V */
+#define PS3_AUDIO_AO_MCTRL_MCLKC1_ENABLED	(0x1 << 12) /* RW--V */
+#define PS3_AUDIO_AO_MCTRL_MCLKC1_RESVD2	(0x2 << 12) /* RW--V */
+#define PS3_AUDIO_AO_MCTRL_MCLKC1_RESVD3	(0x3 << 12) /* RW--V */
+
+/*
+MCLK Output Control
+Controls mclko[0] output.
+0 - Disable output (fixed at High)
+1 - Output clock produced by clock selected
+with SCKSEL0 by MR0
+2 - Reserved
+3 - Reserved
+*/
+#define PS3_AUDIO_AO_MCTRL_MCLKC0_MASK		(0x3 << 14) /* RWIVF */
+#define PS3_AUDIO_AO_MCTRL_MCLKC0_DISABLED	(0x0 << 14) /* RWI-V */
+#define PS3_AUDIO_AO_MCTRL_MCLKC0_ENABLED	(0x1 << 14) /* RW--V */
+#define PS3_AUDIO_AO_MCTRL_MCLKC0_RESVD2	(0x2 << 14) /* RW--V */
+#define PS3_AUDIO_AO_MCTRL_MCLKC0_RESVD3	(0x3 << 14) /* RW--V */
+/*
+Master Clock Rate 1
+Sets the divide ration of Master Clock1 (clock output from
+mclko[1] for the input clock selected by scksel1.
+*/
+#define PS3_AUDIO_AO_MCTRL_MR1_MASK	(0xf << 16)
+#define PS3_AUDIO_AO_MCTRL_MR1_DEFAULT	(0x0 << 16) /* RWI-V */
+/*
+Master Clock Rate 0
+Sets the divide ratio of Master Clock0 (clock output from
+mclko[0] for the input clock selected by scksel0).
+*/
+#define PS3_AUDIO_AO_MCTRL_MR0_MASK	(0xf << 20) /* RWIVF */
+#define PS3_AUDIO_AO_MCTRL_MR0_DEFAULT	(0x0 << 20) /* RWI-V */
+/*
+System Clock Select 0/1
+Selects the system clock to be used as Master Clock 0/1
+Input the system clock that is appropriate for the sampling
+rate.
+*/
+#define PS3_AUDIO_AO_MCTRL_SCKSEL1_MASK		(0x7 << 24) /* RWIVF */
+#define PS3_AUDIO_AO_MCTRL_SCKSEL1_DEFAULT	(0x2 << 24) /* RWI-V */
+
+#define PS3_AUDIO_AO_MCTRL_SCKSEL0_MASK		(0x7 << 28) /* RWIVF */
+#define PS3_AUDIO_AO_MCTRL_SCKSEL0_DEFAULT	(0x2 << 28) /* RWI-V */
+
+
+/*
+3-Wire Audio Output Master Control Register
+Configures clock, 3-Wire Audio Serial Output Enable, and
+other 3-Wire Audio Serial Output Master Settings
+
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |A|A|A|A|0 0 0|A| ASOSR |0 0 0 0|A|A|A|A|A|A|0|1|0 0 0 0 0 0 0 0| AO_3WMCTRL
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+
+
+/*
+LRCKO Polarity
+0 - Reserved
+1 - default
+*/
+#define PS3_AUDIO_AO_3WMCTRL_ASOPLRCK 		(1 << 8) /* RWIVF */
+#define PS3_AUDIO_AO_3WMCTRL_ASOPLRCK_DEFAULT	(1 << 8) /* RW--V */
+
+/* LRCK Output Disable */
+
+#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD		(1 << 10) /* RWIVF */
+#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_ENABLED	(0 << 10) /* RW--V */
+#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_DISABLED	(1 << 10) /* RWI-V */
+
+/* Bit Clock Output Disable */
+
+#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD		(1 << 11) /* RWIVF */
+#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_ENABLED	(0 << 11) /* RW--V */
+#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_DISABLED	(1 << 11) /* RWI-V */
+
+/*
+3-Wire Audio Serial Output Channel 0-3 Operational
+Status.  Each bit becomes 1 after each 3-Wire Audio
+Serial Output Channel N is in action by setting 1 to
+asoen.
+Each bit becomes 0 after each 3-Wire Audio Serial Output
+Channel N is out of action by setting 0 to asoen.
+*/
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN(n)		(1 << (15 - (n))) /* R-IVF */
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(n)	(0 << (15 - (n))) /* R-I-V */
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(n)	(1 << (15 - (n))) /* R---V */
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN0		\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN(0)
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN0_STOPPED	\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(0)
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN0_RUNNING	\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(0)
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN1		\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN(1)
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN1_STOPPED	\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(1)
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN1_RUNNING	\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(1)
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN2		\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN(2)
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN2_STOPPED	\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(2)
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN2_RUNNING	\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(2)
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN3		\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN(3)
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN3_STOPPED	\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(3)
+#define PS3_AUDIO_AO_3WMCTRL_ASORUN3_RUNNING	\
+	PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(3)
+
+/*
+Sampling Rate
+Specifies the divide ratio of the bit clock (clock output
+from bclko) used by the 3-wire Audio Output Clock, whcih
+is applied to the master clock selected by mcksel.
+Data output is synchronized with this clock.
+*/
+#define PS3_AUDIO_AO_3WMCTRL_ASOSR_MASK		(0xf << 20) /* RWIVF */
+#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV2		(0x1 << 20) /* RWI-V */
+#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV4		(0x2 << 20) /* RW--V */
+#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV8		(0x4 << 20) /* RW--V */
+#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV12	(0x6 << 20) /* RW--V */
+
+/*
+Master Clock Select
+0 - Master Clock 0
+1 - Master Clock 1
+*/
+#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL		(1 << 24) /* RWIVF */
+#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL_CLK0	(0 << 24) /* RWI-V */
+#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL_CLK1	(1 << 24) /* RW--V */
+
+/*
+Enables and disables 4ch 3-Wire Audio Serial Output
+operation.  Each Bit from 0 to 3 corresponds to an
+output channel, which means that each output channel
+can be enabled or disabled individually.  When
+multiple channels are enabled at the same time, output
+operations are performed in synchronization.
+Bit 0 - Output Channel 0 (SDOUT[0])
+Bit 1 - Output Channel 1 (SDOUT[1])
+Bit 2 - Output Channel 2 (SDOUT[2])
+Bit 3 - Output Channel 3 (SDOUT[3])
+*/
+#define PS3_AUDIO_AO_3WMCTRL_ASOEN(n)		(1 << (31 - (n))) /* RWIVF */
+#define PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(n)	(0 << (31 - (n))) /* RWI-V */
+#define PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(n)	(1 << (31 - (n))) /* RW--V */
+
+#define PS3_AUDIO_AO_3WMCTRL_ASOEN0 \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN(0) /* RWIVF */
+#define PS3_AUDIO_AO_3WMCTRL_ASOEN0_DISABLED \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(0) /* RWI-V */
+#define PS3_AUDIO_AO_3WMCTRL_ASOEN0_ENABLED \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(0) /* RW--V */
+#define PS3_AUDIO_A1_3WMCTRL_ASOEN0 \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN(1) /* RWIVF */
+#define PS3_AUDIO_A1_3WMCTRL_ASOEN0_DISABLED \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(1) /* RWI-V */
+#define PS3_AUDIO_A1_3WMCTRL_ASOEN0_ENABLED \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(1) /* RW--V */
+#define PS3_AUDIO_A2_3WMCTRL_ASOEN0 \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN(2) /* RWIVF */
+#define PS3_AUDIO_A2_3WMCTRL_ASOEN0_DISABLED \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(2) /* RWI-V */
+#define PS3_AUDIO_A2_3WMCTRL_ASOEN0_ENABLED \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(2) /* RW--V */
+#define PS3_AUDIO_A3_3WMCTRL_ASOEN0 \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN(3) /* RWIVF */
+#define PS3_AUDIO_A3_3WMCTRL_ASOEN0_DISABLED \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(3) /* RWI-V */
+#define PS3_AUDIO_A3_3WMCTRL_ASOEN0_ENABLED \
+	PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(3) /* RW--V */
+
+/*
+3-Wire Audio Serial output Channel 0-3 Control Register
+Configures settings for 3-Wire Serial Audio Output Channel 0-3
+
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|A|0 0 0 0|A|0|ASO|0 0 0|0|0|0|0|0| AO_3WCTRL
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+
+*/
+/*
+Data Bit Mode
+Specifies the number of data bits
+0 - 16 bits
+1 - reserved
+2 - 20 bits
+3 - 24 bits
+*/
+#define PS3_AUDIO_AO_3WCTRL_ASODB_MASK	(0x3 << 8) /* RWIVF */
+#define PS3_AUDIO_AO_3WCTRL_ASODB_16BIT	(0x0 << 8) /* RWI-V */
+#define PS3_AUDIO_AO_3WCTRL_ASODB_RESVD	(0x1 << 8) /* RWI-V */
+#define PS3_AUDIO_AO_3WCTRL_ASODB_20BIT	(0x2 << 8) /* RW--V */
+#define PS3_AUDIO_AO_3WCTRL_ASODB_24BIT	(0x3 << 8) /* RW--V */
+/*
+Data Format Mode
+Specifies the data format where (LSB side or MSB) the data(in 20 bit
+or 24 bit resolution mode) is put in a 32 bit field.
+0 - Data put on LSB side
+1 - Data put on MSB side
+*/
+#define PS3_AUDIO_AO_3WCTRL_ASODF 	(1 << 11) /* RWIVF */
+#define PS3_AUDIO_AO_3WCTRL_ASODF_LSB	(0 << 11) /* RWI-V */
+#define PS3_AUDIO_AO_3WCTRL_ASODF_MSB	(1 << 11) /* RW--V */
+/*
+Buffer Reset
+Performs buffer reset.  Writing 1 to this bit initializes the
+corresponding 3-Wire Audio Output buffers(both L and R).
+*/
+#define PS3_AUDIO_AO_3WCTRL_ASOBRST 		(1 << 16) /* CWIVF */
+#define PS3_AUDIO_AO_3WCTRL_ASOBRST_IDLE	(0 << 16) /* -WI-V */
+#define PS3_AUDIO_AO_3WCTRL_ASOBRST_RESET	(1 << 16) /* -W--T */
+
+/*
+S/PDIF Audio Output Channel 0/1 Control Register
+Configures settings for S/PDIF Audio Output Channel 0/1.
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |S|0 0 0|S|0 0|S| SPOSR |0 0|SPO|0 0 0 0|S|0|SPO|0 0 0 0 0 0 0|S| AO_SPDCTRL
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+/*
+Buffer reset.  Writing 1 to this bit initializes the
+corresponding S/PDIF output buffer pointer.
+*/
+#define PS3_AUDIO_AO_SPDCTRL_SPOBRST		(1 << 0) /* CWIVF */
+#define PS3_AUDIO_AO_SPDCTRL_SPOBRST_IDLE	(0 << 0) /* -WI-V */
+#define PS3_AUDIO_AO_SPDCTRL_SPOBRST_RESET	(1 << 0) /* -W--T */
+
+/*
+Data Bit Mode
+Specifies number of data bits
+0 - 16 bits
+1 - Reserved
+2 - 20 bits
+3 - 24 bits
+*/
+#define PS3_AUDIO_AO_SPDCTRL_SPODB_MASK		(0x3 << 8) /* RWIVF */
+#define PS3_AUDIO_AO_SPDCTRL_SPODB_16BIT	(0x0 << 8) /* RWI-V */
+#define PS3_AUDIO_AO_SPDCTRL_SPODB_RESVD	(0x1 << 8) /* RW--V */
+#define PS3_AUDIO_AO_SPDCTRL_SPODB_20BIT	(0x2 << 8) /* RW--V */
+#define PS3_AUDIO_AO_SPDCTRL_SPODB_24BIT	(0x3 << 8) /* RW--V */
+/*
+Data format Mode
+Specifies the data format, where (LSB side or MSB)
+the data(in 20 or 24 bit resolution) is put in the
+32 bit field.
+0 - LSB Side
+1 - MSB Side
+*/
+#define PS3_AUDIO_AO_SPDCTRL_SPODF	(1 << 11) /* RWIVF */
+#define PS3_AUDIO_AO_SPDCTRL_SPODF_LSB	(0 << 11) /* RWI-V */
+#define PS3_AUDIO_AO_SPDCTRL_SPODF_MSB	(1 << 11) /* RW--V */
+/*
+Source Select
+Specifies the source of the S/PDIF output.  When 0, output
+operation is controlled by 3wen[0] of AO_3WMCTRL register.
+The SR must have the same setting as the a0_3wmctrl reg.
+0 - 3-Wire Audio OUT Ch0 Buffer
+1 - S/PDIF buffer
+*/
+#define PS3_AUDIO_AO_SPDCTRL_SPOSS_MASK		(0x3 << 16) /* RWIVF */
+#define PS3_AUDIO_AO_SPDCTRL_SPOSS_3WEN		(0x0 << 16) /* RWI-V */
+#define PS3_AUDIO_AO_SPDCTRL_SPOSS_SPDIF	(0x1 << 16) /* RW--V */
+/*
+Sampling Rate
+Specifies the divide ratio of the bit clock (clock output
+from bclko) used by the S/PDIF Output Clock, which
+is applied to the master clock selected by mcksel.
+*/
+#define PS3_AUDIO_AO_SPDCTRL_SPOSR		(0xf << 20) /* RWIVF */
+#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV2		(0x1 << 20) /* RWI-V */
+#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV4		(0x2 << 20) /* RW--V */
+#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV8		(0x4 << 20) /* RW--V */
+#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV12	(0x6 << 20) /* RW--V */
+/*
+Master Clock Select
+0 - Master Clock 0
+1 - Master Clock 1
+*/
+#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL		(1 << 24) /* RWIVF */
+#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL_CLK0	(0 << 24) /* RWI-V */
+#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL_CLK1	(1 << 24) /* RW--V */
+
+/*
+S/PDIF Output Channel Operational Status
+This bit becomes 1 after S/PDIF Output Channel is in
+action by setting 1 to spoen.  This bit becomes 0
+after S/PDIF Output Channel is out of action by setting
+0 to spoen.
+*/
+#define PS3_AUDIO_AO_SPDCTRL_SPORUN		(1 << 27) /* R-IVF */
+#define PS3_AUDIO_AO_SPDCTRL_SPORUN_STOPPED	(0 << 27) /* R-I-V */
+#define PS3_AUDIO_AO_SPDCTRL_SPORUN_RUNNING	(1 << 27) /* R---V */
+
+/*
+S/PDIF Audio Output Channel Output Enable
+Enables and disables output operation.  This bit is used
+only when sposs = 1
+*/
+#define PS3_AUDIO_AO_SPDCTRL_SPOEN		(1 << 31) /* RWIVF */
+#define PS3_AUDIO_AO_SPDCTRL_SPOEN_DISABLED	(0 << 31) /* RWI-V */
+#define PS3_AUDIO_AO_SPDCTRL_SPOEN_ENABLED	(1 << 31) /* RW--V */
+
+/*
+S/PDIF Audio Output Channel Channel Status
+Setting Registers.
+Configures channel status bit settings for each block
+(192 bits).
+Output is performed from the MSB(AO_SPDCS0 register bit 31).
+The same value is added for subframes within the same frame.
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |                             SPOCS                             | AO_SPDCS
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+
+S/PDIF Audio Output Channel User Bit Setting
+Configures user bit settings for each block (384 bits).
+Output is performed from the MSB(ao_spdub0 register bit 31).
+
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |                             SPOUB                             | AO_SPDUB
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+/*****************************************************************************
+ *
+ * DMAC register
+ *
+ *****************************************************************************/
+/*
+The PS3_AUDIO_KICK register is used to initiate a DMA transfer and monitor
+its status
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0 0 0 0 0|STATU|0 0 0|  EVENT  |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|R| KICK
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+/*
+The REQUEST field is written to ACTIVE to initiate a DMA request when EVENT
+occurs.
+It will return to the DONE state when the request is completed.
+The registers for a DMA channel should only be written if REQUEST is IDLE.
+*/
+
+#define PS3_AUDIO_KICK_REQUEST                (1 << 0) /* RWIVF */
+#define PS3_AUDIO_KICK_REQUEST_IDLE           (0 << 0) /* RWI-V */
+#define PS3_AUDIO_KICK_REQUEST_ACTIVE         (1 << 0) /* -W--T */
+
+/*
+ *The EVENT field is used to set the event in which
+ *the DMA request becomes active.
+ */
+#define PS3_AUDIO_KICK_EVENT_MASK             (0x1f << 16) /* RWIVF */
+#define PS3_AUDIO_KICK_EVENT_ALWAYS           (0x00 << 16) /* RWI-V */
+#define PS3_AUDIO_KICK_EVENT_SERIALOUT0_EMPTY (0x01 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SERIALOUT0_UNDERFLOW	(0x02 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SERIALOUT1_EMPTY		(0x03 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SERIALOUT1_UNDERFLOW	(0x04 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SERIALOUT2_EMPTY		(0x05 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SERIALOUT2_UNDERFLOW	(0x06 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SERIALOUT3_EMPTY		(0x07 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SERIALOUT3_UNDERFLOW	(0x08 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SPDIF0_BLOCKTRANSFERCOMPLETE \
+	(0x09 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SPDIF0_UNDERFLOW		(0x0A << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SPDIF0_EMPTY		(0x0B << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SPDIF1_BLOCKTRANSFERCOMPLETE \
+	(0x0C << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SPDIF1_UNDERFLOW		(0x0D << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_SPDIF1_EMPTY		(0x0E << 16) /* RW--V */
+
+#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA(n) \
+	((0x13 + (n)) << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA0         (0x13 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA1         (0x14 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA2         (0x15 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA3         (0x16 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA4         (0x17 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA5         (0x18 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA6         (0x19 << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA7         (0x1A << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA8         (0x1B << 16) /* RW--V */
+#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA9         (0x1C << 16) /* RW--V */
+
+/*
+The STATUS field can be used to monitor the progress of a DMA request.
+DONE indicates the previous request has completed.
+EVENT indicates that the DMA engine is waiting for the EVENT to occur.
+PENDING indicates that the DMA engine has not started processing this
+request, but the EVENT has occured.
+DMA indicates that the data transfer is in progress.
+NOTIFY indicates that the notifier signalling end of transfer is being written.
+CLEAR indicated that the previous transfer was cleared.
+ERROR indicates the previous transfer requested an unsupported
+source/destination combination.
+*/
+
+#define PS3_AUDIO_KICK_STATUS_MASK	(0x7 << 24) /* R-IVF */
+#define PS3_AUDIO_KICK_STATUS_DONE	(0x0 << 24) /* R-I-V */
+#define PS3_AUDIO_KICK_STATUS_EVENT	(0x1 << 24) /* R---V */
+#define PS3_AUDIO_KICK_STATUS_PENDING	(0x2 << 24) /* R---V */
+#define PS3_AUDIO_KICK_STATUS_DMA	(0x3 << 24) /* R---V */
+#define PS3_AUDIO_KICK_STATUS_NOTIFY	(0x4 << 24) /* R---V */
+#define PS3_AUDIO_KICK_STATUS_CLEAR	(0x5 << 24) /* R---V */
+#define PS3_AUDIO_KICK_STATUS_ERROR	(0x6 << 24) /* R---V */
+
+/*
+The PS3_AUDIO_SOURCE register specifies the source address for transfers.
+
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |                      START                      |0 0 0 0 0|TAR| SOURCE
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+
+/*
+The Audio DMA engine uses 128-byte transfers, thus the address must be aligned
+to a 128 byte boundary.  The low seven bits are assumed to be 0.
+*/
+
+#define PS3_AUDIO_SOURCE_START_MASK	(0x01FFFFFF << 7) /* RWIUF */
+
+/*
+The TARGET field specifies the memory space containing the source address.
+*/
+
+#define PS3_AUDIO_SOURCE_TARGET_MASK 		(3 << 0) /* RWIVF */
+#define PS3_AUDIO_SOURCE_TARGET_SYSTEM_MEMORY	(2 << 0) /* RW--V */
+
+/*
+The PS3_AUDIO_DEST register specifies the destination address for transfers.
+
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |                      START                      |0 0 0 0 0|TAR| DEST
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+
+/*
+The Audio DMA engine uses 128-byte transfers, thus the address must be aligned
+to a 128 byte boundary.  The low seven bits are assumed to be 0.
+*/
+
+#define PS3_AUDIO_DEST_START_MASK	(0x01FFFFFF << 7) /* RWIUF */
+
+/*
+The TARGET field specifies the memory space containing the destination address
+AUDIOFIFO = Audio WriteData FIFO,
+*/
+
+#define PS3_AUDIO_DEST_TARGET_MASK		(3 << 0) /* RWIVF */
+#define PS3_AUDIO_DEST_TARGET_AUDIOFIFO		(1 << 0) /* RW--V */
+
+/*
+PS3_AUDIO_DMASIZE specifies the number of 128-byte blocks + 1 to transfer.
+So a value of 0 means 128-bytes will get transfered.
+
+
+ 31            24 23           16 15            8 7             0
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+ |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|   BLOCKS    | DMASIZE
+ +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+
+*/
+
+
+#define PS3_AUDIO_DMASIZE_BLOCKS_MASK 	(0x7f << 0) /* RWIUF */
+
+/*
+ * source/destination address for internal fifos
+ */
+#define PS3_AUDIO_AO_3W_LDATA(n)	(0x1000 + (0x100 * (n)))
+#define PS3_AUDIO_AO_3W_RDATA(n)	(0x1080 + (0x100 * (n)))
+
+#define PS3_AUDIO_AO_SPD_DATA(n)	(0x2000 + (0x400 * (n)))
+
+
+/*
+ * field attiribute
+ *
+ *	Read
+ *	  ' ' = Other Information
+ *	  '-' = Field is part of a write-only register
+ *	  'C' = Value read is always the same, constant value line follows (C)
+ *	  'R' = Value is read
+ *
+ *	Write
+ *	  ' ' = Other Information
+ *	  '-' = Must not be written (D), value ignored when written (R,A,F)
+ *	  'W' = Can be written
+ *
+ *	Internal State
+ *	  ' ' = Other Information
+ *	  '-' = No internal state
+ *	  'X' = Internal state, initial value is unknown
+ *	  'I' = Internal state, initial value is known and follows (I)
+ *
+ *	Declaration/Size
+ *	  ' ' = Other Information
+ *	  '-' = Does Not Apply
+ *	  'V' = Type is void
+ *	  'U' = Type is unsigned integer
+ *	  'S' = Type is signed integer
+ *	  'F' = Type is IEEE floating point
+ *	  '1' = Byte size (008)
+ *	  '2' = Short size (016)
+ *	  '3' = Three byte size (024)
+ *	  '4' = Word size (032)
+ *	  '8' = Double size (064)
+ *
+ *	Define Indicator
+ *	  ' ' = Other Information
+ *	  'D' = Device
+ *	  'M' = Memory
+ *	  'R' = Register
+ *	  'A' = Array of Registers
+ *	  'F' = Field
+ *	  'V' = Value
+ *	  'T' = Task
+ */
+
diff --git a/linux/sound/ppc/tumbler.c b/linux/sound/ppc/tumbler.c
new file mode 100644
index 0000000..961d982
--- /dev/null
+++ b/linux/sound/ppc/tumbler.c
@@ -0,0 +1,1495 @@
+/*
+ * PMac Tumbler/Snapper lowlevel functions
+ *
+ * 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
+ *
+ *   Rene Rebe <rene.rebe@gmx.net>:
+ *     * update from shadow registers on wakeup and headphone plug
+ *     * automatically toggle DRC on headphone plug
+ *	
+ */
+
+
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/kmod.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/machdep.h>
+#include <asm/pmac_feature.h>
+#include "pmac.h"
+#include "tumbler_volume.h"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(fmt...) printk(KERN_DEBUG fmt)
+#else
+#define DBG(fmt...)
+#endif
+
+#define IS_G4DA (of_machine_is_compatible("PowerMac3,4"))
+
+/* i2c address for tumbler */
+#define TAS_I2C_ADDR	0x34
+
+/* registers */
+#define TAS_REG_MCS	0x01	/* main control */
+#define TAS_REG_DRC	0x02
+#define TAS_REG_VOL	0x04
+#define TAS_REG_TREBLE	0x05
+#define TAS_REG_BASS	0x06
+#define TAS_REG_INPUT1	0x07
+#define TAS_REG_INPUT2	0x08
+
+/* tas3001c */
+#define TAS_REG_PCM	TAS_REG_INPUT1
+ 
+/* tas3004 */
+#define TAS_REG_LMIX	TAS_REG_INPUT1
+#define TAS_REG_RMIX	TAS_REG_INPUT2
+#define TAS_REG_MCS2	0x43		/* main control 2 */
+#define TAS_REG_ACS	0x40		/* analog control */
+
+/* mono volumes for tas3001c/tas3004 */
+enum {
+	VOL_IDX_PCM_MONO, /* tas3001c only */
+	VOL_IDX_BASS, VOL_IDX_TREBLE,
+	VOL_IDX_LAST_MONO
+};
+
+/* stereo volumes for tas3004 */
+enum {
+	VOL_IDX_PCM, VOL_IDX_PCM2, VOL_IDX_ADC,
+	VOL_IDX_LAST_MIX
+};
+
+struct pmac_gpio {
+	unsigned int addr;
+	u8 active_val;
+	u8 inactive_val;
+	u8 active_state;
+};
+
+struct pmac_tumbler {
+	struct pmac_keywest i2c;
+	struct pmac_gpio audio_reset;
+	struct pmac_gpio amp_mute;
+	struct pmac_gpio line_mute;
+	struct pmac_gpio line_detect;
+	struct pmac_gpio hp_mute;
+	struct pmac_gpio hp_detect;
+	int headphone_irq;
+	int lineout_irq;
+	unsigned int save_master_vol[2];
+	unsigned int master_vol[2];
+	unsigned int save_master_switch[2];
+	unsigned int master_switch[2];
+	unsigned int mono_vol[VOL_IDX_LAST_MONO];
+	unsigned int mix_vol[VOL_IDX_LAST_MIX][2]; /* stereo volumes for tas3004 */
+	int drc_range;
+	int drc_enable;
+	int capture_source;
+	int anded_reset;
+	int auto_mute_notify;
+	int reset_on_sleep;
+	u8  acs;
+};
+
+
+/*
+ */
+
+static int send_init_client(struct pmac_keywest *i2c, unsigned int *regs)
+{
+	while (*regs > 0) {
+		int err, count = 10;
+		do {
+			err = i2c_smbus_write_byte_data(i2c->client,
+							regs[0], regs[1]);
+			if (err >= 0)
+				break;
+			DBG("(W) i2c error %d\n", err);
+			mdelay(10);
+		} while (count--);
+		if (err < 0)
+			return -ENXIO;
+		regs += 2;
+	}
+	return 0;
+}
+
+
+static int tumbler_init_client(struct pmac_keywest *i2c)
+{
+	static unsigned int regs[] = {
+		/* normal operation, SCLK=64fps, i2s output, i2s input, 16bit width */
+		TAS_REG_MCS, (1<<6)|(2<<4)|(2<<2)|0,
+		0, /* terminator */
+	};
+	DBG("(I) tumbler init client\n");
+	return send_init_client(i2c, regs);
+}
+
+static int snapper_init_client(struct pmac_keywest *i2c)
+{
+	static unsigned int regs[] = {
+		/* normal operation, SCLK=64fps, i2s output, 16bit width */
+		TAS_REG_MCS, (1<<6)|(2<<4)|0,
+		/* normal operation, all-pass mode */
+		TAS_REG_MCS2, (1<<1),
+		/* normal output, no deemphasis, A input, power-up, line-in */
+		TAS_REG_ACS, 0,
+		0, /* terminator */
+	};
+	DBG("(I) snapper init client\n");
+	return send_init_client(i2c, regs);
+}
+	
+/*
+ * gpio access
+ */
+#define do_gpio_write(gp, val) \
+	pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, (gp)->addr, val)
+#define do_gpio_read(gp) \
+	pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, (gp)->addr, 0)
+#define tumbler_gpio_free(gp) /* NOP */
+
+static void write_audio_gpio(struct pmac_gpio *gp, int active)
+{
+	if (! gp->addr)
+		return;
+	active = active ? gp->active_val : gp->inactive_val;
+	do_gpio_write(gp, active);
+	DBG("(I) gpio %x write %d\n", gp->addr, active);
+}
+
+static int check_audio_gpio(struct pmac_gpio *gp)
+{
+	int ret;
+
+	if (! gp->addr)
+		return 0;
+
+	ret = do_gpio_read(gp);
+
+	return (ret & 0x1) == (gp->active_val & 0x1);
+}
+
+static int read_audio_gpio(struct pmac_gpio *gp)
+{
+	int ret;
+	if (! gp->addr)
+		return 0;
+	ret = do_gpio_read(gp);
+	ret = (ret & 0x02) !=0;
+	return ret == gp->active_state;
+}
+
+/*
+ * update master volume
+ */
+static int tumbler_set_master_volume(struct pmac_tumbler *mix)
+{
+	unsigned char block[6];
+	unsigned int left_vol, right_vol;
+  
+	if (! mix->i2c.client)
+		return -ENODEV;
+  
+	if (! mix->master_switch[0])
+		left_vol = 0;
+	else {
+		left_vol = mix->master_vol[0];
+		if (left_vol >= ARRAY_SIZE(master_volume_table))
+			left_vol = ARRAY_SIZE(master_volume_table) - 1;
+		left_vol = master_volume_table[left_vol];
+	}
+	if (! mix->master_switch[1])
+		right_vol = 0;
+	else {
+		right_vol = mix->master_vol[1];
+		if (right_vol >= ARRAY_SIZE(master_volume_table))
+			right_vol = ARRAY_SIZE(master_volume_table) - 1;
+		right_vol = master_volume_table[right_vol];
+	}
+
+	block[0] = (left_vol >> 16) & 0xff;
+	block[1] = (left_vol >> 8)  & 0xff;
+	block[2] = (left_vol >> 0)  & 0xff;
+
+	block[3] = (right_vol >> 16) & 0xff;
+	block[4] = (right_vol >> 8)  & 0xff;
+	block[5] = (right_vol >> 0)  & 0xff;
+  
+	if (i2c_smbus_write_i2c_block_data(mix->i2c.client, TAS_REG_VOL, 6,
+					   block) < 0) {
+		snd_printk(KERN_ERR "failed to set volume \n");
+		return -EINVAL;
+	}
+	DBG("(I) succeeded to set volume (%u, %u)\n", left_vol, right_vol);
+	return 0;
+}
+
+
+/* output volume */
+static int tumbler_info_master_volume(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = ARRAY_SIZE(master_volume_table) - 1;
+	return 0;
+}
+
+static int tumbler_get_master_volume(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix = chip->mixer_data;
+
+	ucontrol->value.integer.value[0] = mix->master_vol[0];
+	ucontrol->value.integer.value[1] = mix->master_vol[1];
+	return 0;
+}
+
+static int tumbler_put_master_volume(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix = chip->mixer_data;
+	unsigned int vol[2];
+	int change;
+
+	vol[0] = ucontrol->value.integer.value[0];
+	vol[1] = ucontrol->value.integer.value[1];
+	if (vol[0] >= ARRAY_SIZE(master_volume_table) ||
+	    vol[1] >= ARRAY_SIZE(master_volume_table))
+		return -EINVAL;
+	change = mix->master_vol[0] != vol[0] ||
+		mix->master_vol[1] != vol[1];
+	if (change) {
+		mix->master_vol[0] = vol[0];
+		mix->master_vol[1] = vol[1];
+		tumbler_set_master_volume(mix);
+	}
+	return change;
+}
+
+/* output switch */
+static int tumbler_get_master_switch(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix = chip->mixer_data;
+
+	ucontrol->value.integer.value[0] = mix->master_switch[0];
+	ucontrol->value.integer.value[1] = mix->master_switch[1];
+	return 0;
+}
+
+static int tumbler_put_master_switch(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix = chip->mixer_data;
+	int change;
+
+	change = mix->master_switch[0] != ucontrol->value.integer.value[0] ||
+		mix->master_switch[1] != ucontrol->value.integer.value[1];
+	if (change) {
+		mix->master_switch[0] = !!ucontrol->value.integer.value[0];
+		mix->master_switch[1] = !!ucontrol->value.integer.value[1];
+		tumbler_set_master_volume(mix);
+	}
+	return change;
+}
+
+
+/*
+ * TAS3001c dynamic range compression
+ */
+
+#define TAS3001_DRC_MAX		0x5f
+
+static int tumbler_set_drc(struct pmac_tumbler *mix)
+{
+	unsigned char val[2];
+
+	if (! mix->i2c.client)
+		return -ENODEV;
+  
+	if (mix->drc_enable) {
+		val[0] = 0xc1; /* enable, 3:1 compression */
+		if (mix->drc_range > TAS3001_DRC_MAX)
+			val[1] = 0xf0;
+		else if (mix->drc_range < 0)
+			val[1] = 0x91;
+		else
+			val[1] = mix->drc_range + 0x91;
+	} else {
+		val[0] = 0;
+		val[1] = 0;
+	}
+
+	if (i2c_smbus_write_i2c_block_data(mix->i2c.client, TAS_REG_DRC,
+					   2, val) < 0) {
+		snd_printk(KERN_ERR "failed to set DRC\n");
+		return -EINVAL;
+	}
+	DBG("(I) succeeded to set DRC (%u, %u)\n", val[0], val[1]);
+	return 0;
+}
+
+/*
+ * TAS3004
+ */
+
+#define TAS3004_DRC_MAX		0xef
+
+static int snapper_set_drc(struct pmac_tumbler *mix)
+{
+	unsigned char val[6];
+
+	if (! mix->i2c.client)
+		return -ENODEV;
+  
+	if (mix->drc_enable)
+		val[0] = 0x50; /* 3:1 above threshold */
+	else
+		val[0] = 0x51; /* disabled */
+	val[1] = 0x02; /* 1:1 below threshold */
+	if (mix->drc_range > 0xef)
+		val[2] = 0xef;
+	else if (mix->drc_range < 0)
+		val[2] = 0x00;
+	else
+		val[2] = mix->drc_range;
+	val[3] = 0xb0;
+	val[4] = 0x60;
+	val[5] = 0xa0;
+
+	if (i2c_smbus_write_i2c_block_data(mix->i2c.client, TAS_REG_DRC,
+					   6, val) < 0) {
+		snd_printk(KERN_ERR "failed to set DRC\n");
+		return -EINVAL;
+	}
+	DBG("(I) succeeded to set DRC (%u, %u)\n", val[0], val[1]);
+	return 0;
+}
+
+static int tumbler_info_drc_value(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max =
+		chip->model == PMAC_TUMBLER ? TAS3001_DRC_MAX : TAS3004_DRC_MAX;
+	return 0;
+}
+
+static int tumbler_get_drc_value(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->drc_range;
+	return 0;
+}
+
+static int tumbler_put_drc_value(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix;
+	unsigned int val;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	val = ucontrol->value.integer.value[0];
+	if (chip->model == PMAC_TUMBLER) {
+		if (val > TAS3001_DRC_MAX)
+			return -EINVAL;
+	} else {
+		if (val > TAS3004_DRC_MAX)
+			return -EINVAL;
+	}
+	change = mix->drc_range != val;
+	if (change) {
+		mix->drc_range = val;
+		if (chip->model == PMAC_TUMBLER)
+			tumbler_set_drc(mix);
+		else
+			snapper_set_drc(mix);
+	}
+	return change;
+}
+
+static int tumbler_get_drc_switch(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->drc_enable;
+	return 0;
+}
+
+static int tumbler_put_drc_switch(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	change = mix->drc_enable != ucontrol->value.integer.value[0];
+	if (change) {
+		mix->drc_enable = !!ucontrol->value.integer.value[0];
+		if (chip->model == PMAC_TUMBLER)
+			tumbler_set_drc(mix);
+		else
+			snapper_set_drc(mix);
+	}
+	return change;
+}
+
+
+/*
+ * mono volumes
+ */
+
+struct tumbler_mono_vol {
+	int index;
+	int reg;
+	int bytes;
+	unsigned int max;
+	unsigned int *table;
+};
+
+static int tumbler_set_mono_volume(struct pmac_tumbler *mix,
+				   struct tumbler_mono_vol *info)
+{
+	unsigned char block[4];
+	unsigned int vol;
+	int i;
+  
+	if (! mix->i2c.client)
+		return -ENODEV;
+  
+	vol = mix->mono_vol[info->index];
+	if (vol >= info->max)
+		vol = info->max - 1;
+	vol = info->table[vol];
+	for (i = 0; i < info->bytes; i++)
+		block[i] = (vol >> ((info->bytes - i - 1) * 8)) & 0xff;
+	if (i2c_smbus_write_i2c_block_data(mix->i2c.client, info->reg,
+					   info->bytes, block) < 0) {
+		snd_printk(KERN_ERR "failed to set mono volume %d\n",
+			   info->index);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int tumbler_info_mono(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_info *uinfo)
+{
+	struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = info->max - 1;
+	return 0;
+}
+
+static int tumbler_get_mono(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value;
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->mono_vol[info->index];
+	return 0;
+}
+
+static int tumbler_put_mono(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value;
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix;
+	unsigned int vol;
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	vol = ucontrol->value.integer.value[0];
+	if (vol >= info->max)
+		return -EINVAL;
+	change = mix->mono_vol[info->index] != vol;
+	if (change) {
+		mix->mono_vol[info->index] = vol;
+		tumbler_set_mono_volume(mix, info);
+	}
+	return change;
+}
+
+/* TAS3001c mono volumes */
+static struct tumbler_mono_vol tumbler_pcm_vol_info = {
+	.index = VOL_IDX_PCM_MONO,
+	.reg = TAS_REG_PCM,
+	.bytes = 3,
+	.max = ARRAY_SIZE(mixer_volume_table),
+	.table = mixer_volume_table,
+};
+
+static struct tumbler_mono_vol tumbler_bass_vol_info = {
+	.index = VOL_IDX_BASS,
+	.reg = TAS_REG_BASS,
+	.bytes = 1,
+	.max = ARRAY_SIZE(bass_volume_table),
+	.table = bass_volume_table,
+};
+
+static struct tumbler_mono_vol tumbler_treble_vol_info = {
+	.index = VOL_IDX_TREBLE,
+	.reg = TAS_REG_TREBLE,
+	.bytes = 1,
+	.max = ARRAY_SIZE(treble_volume_table),
+	.table = treble_volume_table,
+};
+
+/* TAS3004 mono volumes */
+static struct tumbler_mono_vol snapper_bass_vol_info = {
+	.index = VOL_IDX_BASS,
+	.reg = TAS_REG_BASS,
+	.bytes = 1,
+	.max = ARRAY_SIZE(snapper_bass_volume_table),
+	.table = snapper_bass_volume_table,
+};
+
+static struct tumbler_mono_vol snapper_treble_vol_info = {
+	.index = VOL_IDX_TREBLE,
+	.reg = TAS_REG_TREBLE,
+	.bytes = 1,
+	.max = ARRAY_SIZE(snapper_treble_volume_table),
+	.table = snapper_treble_volume_table,
+};
+
+
+#define DEFINE_MONO(xname,type) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
+	.name = xname, \
+	.info = tumbler_info_mono, \
+	.get = tumbler_get_mono, \
+	.put = tumbler_put_mono, \
+	.private_value = (unsigned long)(&tumbler_##type##_vol_info), \
+}
+
+#define DEFINE_SNAPPER_MONO(xname,type) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
+	.name = xname, \
+	.info = tumbler_info_mono, \
+	.get = tumbler_get_mono, \
+	.put = tumbler_put_mono, \
+	.private_value = (unsigned long)(&snapper_##type##_vol_info), \
+}
+
+
+/*
+ * snapper mixer volumes
+ */
+
+static int snapper_set_mix_vol1(struct pmac_tumbler *mix, int idx, int ch, int reg)
+{
+	int i, j, vol;
+	unsigned char block[9];
+
+	vol = mix->mix_vol[idx][ch];
+	if (vol >= ARRAY_SIZE(mixer_volume_table)) {
+		vol = ARRAY_SIZE(mixer_volume_table) - 1;
+		mix->mix_vol[idx][ch] = vol;
+	}
+
+	for (i = 0; i < 3; i++) {
+		vol = mix->mix_vol[i][ch];
+		vol = mixer_volume_table[vol];
+		for (j = 0; j < 3; j++)
+			block[i * 3 + j] = (vol >> ((2 - j) * 8)) & 0xff;
+	}
+	if (i2c_smbus_write_i2c_block_data(mix->i2c.client, reg,
+					   9, block) < 0) {
+		snd_printk(KERN_ERR "failed to set mono volume %d\n", reg);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int snapper_set_mix_vol(struct pmac_tumbler *mix, int idx)
+{
+	if (! mix->i2c.client)
+		return -ENODEV;
+	if (snapper_set_mix_vol1(mix, idx, 0, TAS_REG_LMIX) < 0 ||
+	    snapper_set_mix_vol1(mix, idx, 1, TAS_REG_RMIX) < 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int snapper_info_mix(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = ARRAY_SIZE(mixer_volume_table) - 1;
+	return 0;
+}
+
+static int snapper_get_mix(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	int idx = (int)kcontrol->private_value;
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	ucontrol->value.integer.value[0] = mix->mix_vol[idx][0];
+	ucontrol->value.integer.value[1] = mix->mix_vol[idx][1];
+	return 0;
+}
+
+static int snapper_put_mix(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	int idx = (int)kcontrol->private_value;
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix;
+	unsigned int vol[2];
+	int change;
+
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	vol[0] = ucontrol->value.integer.value[0];
+	vol[1] = ucontrol->value.integer.value[1];
+	if (vol[0] >= ARRAY_SIZE(mixer_volume_table) ||
+	    vol[1] >= ARRAY_SIZE(mixer_volume_table))
+		return -EINVAL;
+	change = mix->mix_vol[idx][0] != vol[0] ||
+		mix->mix_vol[idx][1] != vol[1];
+	if (change) {
+		mix->mix_vol[idx][0] = vol[0];
+		mix->mix_vol[idx][1] = vol[1];
+		snapper_set_mix_vol(mix, idx);
+	}
+	return change;
+}
+
+
+/*
+ * mute switches. FIXME: Turn that into software mute when both outputs are muted
+ * to avoid codec reset on ibook M7
+ */
+
+enum { TUMBLER_MUTE_HP, TUMBLER_MUTE_AMP, TUMBLER_MUTE_LINE };
+
+static int tumbler_get_mute_switch(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix;
+	struct pmac_gpio *gp;
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	switch(kcontrol->private_value) {
+	case TUMBLER_MUTE_HP:
+		gp = &mix->hp_mute;	break;
+	case TUMBLER_MUTE_AMP:
+		gp = &mix->amp_mute;	break;
+	case TUMBLER_MUTE_LINE:
+		gp = &mix->line_mute;	break;
+	default:
+		gp = NULL;
+	}
+	if (gp == NULL)
+		return -EINVAL;
+	ucontrol->value.integer.value[0] = !check_audio_gpio(gp);
+	return 0;
+}
+
+static int tumbler_put_mute_switch(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix;
+	struct pmac_gpio *gp;
+	int val;
+#ifdef PMAC_SUPPORT_AUTOMUTE
+	if (chip->update_automute && chip->auto_mute)
+		return 0; /* don't touch in the auto-mute mode */
+#endif	
+	if (! (mix = chip->mixer_data))
+		return -ENODEV;
+	switch(kcontrol->private_value) {
+	case TUMBLER_MUTE_HP:
+		gp = &mix->hp_mute;	break;
+	case TUMBLER_MUTE_AMP:
+		gp = &mix->amp_mute;	break;
+	case TUMBLER_MUTE_LINE:
+		gp = &mix->line_mute;	break;
+	default:
+		gp = NULL;
+	}
+	if (gp == NULL)
+		return -EINVAL;
+	val = ! check_audio_gpio(gp);
+	if (val != ucontrol->value.integer.value[0]) {
+		write_audio_gpio(gp, ! ucontrol->value.integer.value[0]);
+		return 1;
+	}
+	return 0;
+}
+
+static int snapper_set_capture_source(struct pmac_tumbler *mix)
+{
+	if (! mix->i2c.client)
+		return -ENODEV;
+	if (mix->capture_source)
+		mix->acs |= 2;
+	else
+		mix->acs &= ~2;
+	return i2c_smbus_write_byte_data(mix->i2c.client, TAS_REG_ACS, mix->acs);
+}
+
+static int snapper_info_capture_source(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[2] = {
+		"Line", "Mic"
+	};
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int snapper_get_capture_source(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix = chip->mixer_data;
+
+	ucontrol->value.enumerated.item[0] = mix->capture_source;
+	return 0;
+}
+
+static int snapper_put_capture_source(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pmac *chip = snd_kcontrol_chip(kcontrol);
+	struct pmac_tumbler *mix = chip->mixer_data;
+	int change;
+
+	change = ucontrol->value.enumerated.item[0] != mix->capture_source;
+	if (change) {
+		mix->capture_source = !!ucontrol->value.enumerated.item[0];
+		snapper_set_capture_source(mix);
+	}
+	return change;
+}
+
+#define DEFINE_SNAPPER_MIX(xname,idx,ofs) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
+	.name = xname, \
+	.info = snapper_info_mix, \
+	.get = snapper_get_mix, \
+	.put = snapper_put_mix, \
+	.index = idx,\
+	.private_value = ofs, \
+}
+
+
+/*
+ */
+static struct snd_kcontrol_new tumbler_mixers[] __devinitdata = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Master Playback Volume",
+	  .info = tumbler_info_master_volume,
+	  .get = tumbler_get_master_volume,
+	  .put = tumbler_put_master_volume
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Master Playback Switch",
+	  .info = snd_pmac_boolean_stereo_info,
+	  .get = tumbler_get_master_switch,
+	  .put = tumbler_put_master_switch
+	},
+	DEFINE_MONO("Tone Control - Bass", bass),
+	DEFINE_MONO("Tone Control - Treble", treble),
+	DEFINE_MONO("PCM Playback Volume", pcm),
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "DRC Range",
+	  .info = tumbler_info_drc_value,
+	  .get = tumbler_get_drc_value,
+	  .put = tumbler_put_drc_value
+	},
+};
+
+static struct snd_kcontrol_new snapper_mixers[] __devinitdata = {
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Master Playback Volume",
+	  .info = tumbler_info_master_volume,
+	  .get = tumbler_get_master_volume,
+	  .put = tumbler_put_master_volume
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Master Playback Switch",
+	  .info = snd_pmac_boolean_stereo_info,
+	  .get = tumbler_get_master_switch,
+	  .put = tumbler_put_master_switch
+	},
+	DEFINE_SNAPPER_MIX("PCM Playback Volume", 0, VOL_IDX_PCM),
+	/* Alternative PCM is assigned to Mic analog loopback on iBook G4 */
+	DEFINE_SNAPPER_MIX("Mic Playback Volume", 0, VOL_IDX_PCM2),
+	DEFINE_SNAPPER_MIX("Monitor Mix Volume", 0, VOL_IDX_ADC),
+	DEFINE_SNAPPER_MONO("Tone Control - Bass", bass),
+	DEFINE_SNAPPER_MONO("Tone Control - Treble", treble),
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "DRC Range",
+	  .info = tumbler_info_drc_value,
+	  .get = tumbler_get_drc_value,
+	  .put = tumbler_put_drc_value
+	},
+	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	  .name = "Input Source", /* FIXME: "Capture Source" doesn't work properly */
+	  .info = snapper_info_capture_source,
+	  .get = snapper_get_capture_source,
+	  .put = snapper_put_capture_source
+	},
+};
+
+static struct snd_kcontrol_new tumbler_hp_sw __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Headphone Playback Switch",
+	.info = snd_pmac_boolean_mono_info,
+	.get = tumbler_get_mute_switch,
+	.put = tumbler_put_mute_switch,
+	.private_value = TUMBLER_MUTE_HP,
+};
+static struct snd_kcontrol_new tumbler_speaker_sw __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Speaker Playback Switch",
+	.info = snd_pmac_boolean_mono_info,
+	.get = tumbler_get_mute_switch,
+	.put = tumbler_put_mute_switch,
+	.private_value = TUMBLER_MUTE_AMP,
+};
+static struct snd_kcontrol_new tumbler_lineout_sw __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Line Out Playback Switch",
+	.info = snd_pmac_boolean_mono_info,
+	.get = tumbler_get_mute_switch,
+	.put = tumbler_put_mute_switch,
+	.private_value = TUMBLER_MUTE_LINE,
+};
+static struct snd_kcontrol_new tumbler_drc_sw __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DRC Switch",
+	.info = snd_pmac_boolean_mono_info,
+	.get = tumbler_get_drc_switch,
+	.put = tumbler_put_drc_switch
+};
+
+
+#ifdef PMAC_SUPPORT_AUTOMUTE
+/*
+ * auto-mute stuffs
+ */
+static int tumbler_detect_headphone(struct snd_pmac *chip)
+{
+	struct pmac_tumbler *mix = chip->mixer_data;
+	int detect = 0;
+
+	if (mix->hp_detect.addr)
+		detect |= read_audio_gpio(&mix->hp_detect);
+	return detect;
+}
+
+static int tumbler_detect_lineout(struct snd_pmac *chip)
+{
+	struct pmac_tumbler *mix = chip->mixer_data;
+	int detect = 0;
+
+	if (mix->line_detect.addr)
+		detect |= read_audio_gpio(&mix->line_detect);
+	return detect;
+}
+
+static void check_mute(struct snd_pmac *chip, struct pmac_gpio *gp, int val, int do_notify,
+		       struct snd_kcontrol *sw)
+{
+	if (check_audio_gpio(gp) != val) {
+		write_audio_gpio(gp, val);
+		if (do_notify)
+			snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &sw->id);
+	}
+}
+
+static struct work_struct device_change;
+static struct snd_pmac *device_change_chip;
+
+static void device_change_handler(struct work_struct *work)
+{
+	struct snd_pmac *chip = device_change_chip;
+	struct pmac_tumbler *mix;
+	int headphone, lineout;
+
+	if (!chip)
+		return;
+
+	mix = chip->mixer_data;
+	if (snd_BUG_ON(!mix))
+		return;
+
+	headphone = tumbler_detect_headphone(chip);
+	lineout = tumbler_detect_lineout(chip);
+
+	DBG("headphone: %d, lineout: %d\n", headphone, lineout);
+
+	if (headphone || lineout) {
+		/* unmute headphone/lineout & mute speaker */
+		if (headphone)
+			check_mute(chip, &mix->hp_mute, 0, mix->auto_mute_notify,
+				   chip->master_sw_ctl);
+		if (lineout && mix->line_mute.addr != 0)
+			check_mute(chip, &mix->line_mute, 0, mix->auto_mute_notify,
+				   chip->lineout_sw_ctl);
+		if (mix->anded_reset)
+			msleep(10);
+		check_mute(chip, &mix->amp_mute, 1, mix->auto_mute_notify,
+			   chip->speaker_sw_ctl);
+	} else {
+		/* unmute speaker, mute others */
+		check_mute(chip, &mix->amp_mute, 0, mix->auto_mute_notify,
+			   chip->speaker_sw_ctl);
+		if (mix->anded_reset)
+			msleep(10);
+		check_mute(chip, &mix->hp_mute, 1, mix->auto_mute_notify,
+			   chip->master_sw_ctl);
+		if (mix->line_mute.addr != 0)
+			check_mute(chip, &mix->line_mute, 1, mix->auto_mute_notify,
+				   chip->lineout_sw_ctl);
+	}
+	if (mix->auto_mute_notify)
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &chip->hp_detect_ctl->id);
+
+#ifdef CONFIG_SND_POWERMAC_AUTO_DRC
+	mix->drc_enable = ! (headphone || lineout);
+	if (mix->auto_mute_notify)
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &chip->drc_sw_ctl->id);
+	if (chip->model == PMAC_TUMBLER)
+		tumbler_set_drc(mix);
+	else
+		snapper_set_drc(mix);
+#endif
+
+	/* reset the master volume so the correct amplification is applied */
+	tumbler_set_master_volume(mix);
+}
+
+static void tumbler_update_automute(struct snd_pmac *chip, int do_notify)
+{
+	if (chip->auto_mute) {
+		struct pmac_tumbler *mix;
+		mix = chip->mixer_data;
+		if (snd_BUG_ON(!mix))
+			return;
+		mix->auto_mute_notify = do_notify;
+		schedule_work(&device_change);
+	}
+}
+#endif /* PMAC_SUPPORT_AUTOMUTE */
+
+
+/* interrupt - headphone plug changed */
+static irqreturn_t headphone_intr(int irq, void *devid)
+{
+	struct snd_pmac *chip = devid;
+	if (chip->update_automute && chip->initialized) {
+		chip->update_automute(chip, 1);
+		return IRQ_HANDLED;
+	}
+	return IRQ_NONE;
+}
+
+/* look for audio-gpio device */
+static struct device_node *find_audio_device(const char *name)
+{
+	struct device_node *gpiop;
+	struct device_node *np;
+  
+	gpiop = of_find_node_by_name(NULL, "gpio");
+	if (! gpiop)
+		return NULL;
+  
+	for (np = of_get_next_child(gpiop, NULL); np;
+			np = of_get_next_child(gpiop, np)) {
+		const char *property = of_get_property(np, "audio-gpio", NULL);
+		if (property && strcmp(property, name) == 0)
+			break;
+	}  
+	of_node_put(gpiop);
+	return np;
+}
+
+/* look for audio-gpio device */
+static struct device_node *find_compatible_audio_device(const char *name)
+{
+	struct device_node *gpiop;
+	struct device_node *np;
+  
+	gpiop = of_find_node_by_name(NULL, "gpio");
+	if (!gpiop)
+		return NULL;
+  
+	for (np = of_get_next_child(gpiop, NULL); np;
+			np = of_get_next_child(gpiop, np)) {
+		if (of_device_is_compatible(np, name))
+			break;
+	}  
+	of_node_put(gpiop);
+	return np;
+}
+
+/* find an audio device and get its address */
+static long tumbler_find_device(const char *device, const char *platform,
+				struct pmac_gpio *gp, int is_compatible)
+{
+	struct device_node *node;
+	const u32 *base;
+	u32 addr;
+	long ret;
+
+	if (is_compatible)
+		node = find_compatible_audio_device(device);
+	else
+		node = find_audio_device(device);
+	if (! node) {
+		DBG("(W) cannot find audio device %s !\n", device);
+		snd_printdd("cannot find device %s\n", device);
+		return -ENODEV;
+	}
+
+	base = of_get_property(node, "AAPL,address", NULL);
+	if (! base) {
+		base = of_get_property(node, "reg", NULL);
+		if (!base) {
+			DBG("(E) cannot find address for device %s !\n", device);
+			snd_printd("cannot find address for device %s\n", device);
+			of_node_put(node);
+			return -ENODEV;
+		}
+		addr = *base;
+		if (addr < 0x50)
+			addr += 0x50;
+	} else
+		addr = *base;
+
+	gp->addr = addr & 0x0000ffff;
+	/* Try to find the active state, default to 0 ! */
+	base = of_get_property(node, "audio-gpio-active-state", NULL);
+	if (base) {
+		gp->active_state = *base;
+		gp->active_val = (*base) ? 0x5 : 0x4;
+		gp->inactive_val = (*base) ? 0x4 : 0x5;
+	} else {
+		const u32 *prop = NULL;
+		gp->active_state = IS_G4DA
+				&& !strncmp(device, "keywest-gpio1", 13);
+		gp->active_val = 0x4;
+		gp->inactive_val = 0x5;
+		/* Here are some crude hacks to extract the GPIO polarity and
+		 * open collector informations out of the do-platform script
+		 * as we don't yet have an interpreter for these things
+		 */
+		if (platform)
+			prop = of_get_property(node, platform, NULL);
+		if (prop) {
+			if (prop[3] == 0x9 && prop[4] == 0x9) {
+				gp->active_val = 0xd;
+				gp->inactive_val = 0xc;
+			}
+			if (prop[3] == 0x1 && prop[4] == 0x1) {
+				gp->active_val = 0x5;
+				gp->inactive_val = 0x4;
+			}
+		}
+	}
+
+	DBG("(I) GPIO device %s found, offset: %x, active state: %d !\n",
+	    device, gp->addr, gp->active_state);
+
+	ret = irq_of_parse_and_map(node, 0);
+	of_node_put(node);
+	return ret;
+}
+
+/* reset audio */
+static void tumbler_reset_audio(struct snd_pmac *chip)
+{
+	struct pmac_tumbler *mix = chip->mixer_data;
+
+	if (mix->anded_reset) {
+		DBG("(I) codec anded reset !\n");
+		write_audio_gpio(&mix->hp_mute, 0);
+		write_audio_gpio(&mix->amp_mute, 0);
+		msleep(200);
+		write_audio_gpio(&mix->hp_mute, 1);
+		write_audio_gpio(&mix->amp_mute, 1);
+		msleep(100);
+		write_audio_gpio(&mix->hp_mute, 0);
+		write_audio_gpio(&mix->amp_mute, 0);
+		msleep(100);
+	} else {
+		DBG("(I) codec normal reset !\n");
+
+		write_audio_gpio(&mix->audio_reset, 0);
+		msleep(200);
+		write_audio_gpio(&mix->audio_reset, 1);
+		msleep(100);
+		write_audio_gpio(&mix->audio_reset, 0);
+		msleep(100);
+	}
+}
+
+#ifdef CONFIG_PM
+/* suspend mixer */
+static void tumbler_suspend(struct snd_pmac *chip)
+{
+	struct pmac_tumbler *mix = chip->mixer_data;
+
+	if (mix->headphone_irq >= 0)
+		disable_irq(mix->headphone_irq);
+	if (mix->lineout_irq >= 0)
+		disable_irq(mix->lineout_irq);
+	mix->save_master_switch[0] = mix->master_switch[0];
+	mix->save_master_switch[1] = mix->master_switch[1];
+	mix->save_master_vol[0] = mix->master_vol[0];
+	mix->save_master_vol[1] = mix->master_vol[1];
+	mix->master_switch[0] = mix->master_switch[1] = 0;
+	tumbler_set_master_volume(mix);
+	if (!mix->anded_reset) {
+		write_audio_gpio(&mix->amp_mute, 1);
+		write_audio_gpio(&mix->hp_mute, 1);
+	}
+	if (chip->model == PMAC_SNAPPER) {
+		mix->acs |= 1;
+		i2c_smbus_write_byte_data(mix->i2c.client, TAS_REG_ACS, mix->acs);
+	}
+	if (mix->anded_reset) {
+		write_audio_gpio(&mix->amp_mute, 1);
+		write_audio_gpio(&mix->hp_mute, 1);
+	} else
+		write_audio_gpio(&mix->audio_reset, 1);
+}
+
+/* resume mixer */
+static void tumbler_resume(struct snd_pmac *chip)
+{
+	struct pmac_tumbler *mix = chip->mixer_data;
+
+	mix->acs &= ~1;
+	mix->master_switch[0] = mix->save_master_switch[0];
+	mix->master_switch[1] = mix->save_master_switch[1];
+	mix->master_vol[0] = mix->save_master_vol[0];
+	mix->master_vol[1] = mix->save_master_vol[1];
+	tumbler_reset_audio(chip);
+	if (mix->i2c.client && mix->i2c.init_client) {
+		if (mix->i2c.init_client(&mix->i2c) < 0)
+			printk(KERN_ERR "tumbler_init_client error\n");
+	} else
+		printk(KERN_ERR "tumbler: i2c is not initialized\n");
+	if (chip->model == PMAC_TUMBLER) {
+		tumbler_set_mono_volume(mix, &tumbler_pcm_vol_info);
+		tumbler_set_mono_volume(mix, &tumbler_bass_vol_info);
+		tumbler_set_mono_volume(mix, &tumbler_treble_vol_info);
+		tumbler_set_drc(mix);
+	} else {
+		snapper_set_mix_vol(mix, VOL_IDX_PCM);
+		snapper_set_mix_vol(mix, VOL_IDX_PCM2);
+		snapper_set_mix_vol(mix, VOL_IDX_ADC);
+		tumbler_set_mono_volume(mix, &snapper_bass_vol_info);
+		tumbler_set_mono_volume(mix, &snapper_treble_vol_info);
+		snapper_set_drc(mix);
+		snapper_set_capture_source(mix);
+	}
+	tumbler_set_master_volume(mix);
+	if (chip->update_automute)
+		chip->update_automute(chip, 0);
+	if (mix->headphone_irq >= 0) {
+		unsigned char val;
+
+		enable_irq(mix->headphone_irq);
+		/* activate headphone status interrupts */
+		val = do_gpio_read(&mix->hp_detect);
+		do_gpio_write(&mix->hp_detect, val | 0x80);
+	}
+	if (mix->lineout_irq >= 0)
+		enable_irq(mix->lineout_irq);
+}
+#endif
+
+/* initialize tumbler */
+static int __devinit tumbler_init(struct snd_pmac *chip)
+{
+	int irq;
+	struct pmac_tumbler *mix = chip->mixer_data;
+
+	if (tumbler_find_device("audio-hw-reset",
+				"platform-do-hw-reset",
+				&mix->audio_reset, 0) < 0)
+		tumbler_find_device("hw-reset",
+				    "platform-do-hw-reset",
+				    &mix->audio_reset, 1);
+	if (tumbler_find_device("amp-mute",
+				"platform-do-amp-mute",
+				&mix->amp_mute, 0) < 0)
+		tumbler_find_device("amp-mute",
+				    "platform-do-amp-mute",
+				    &mix->amp_mute, 1);
+	if (tumbler_find_device("headphone-mute",
+				"platform-do-headphone-mute",
+				&mix->hp_mute, 0) < 0)
+		tumbler_find_device("headphone-mute",
+				    "platform-do-headphone-mute",
+				    &mix->hp_mute, 1);
+	if (tumbler_find_device("line-output-mute",
+				"platform-do-lineout-mute",
+				&mix->line_mute, 0) < 0)
+		tumbler_find_device("line-output-mute",
+				   "platform-do-lineout-mute",
+				    &mix->line_mute, 1);
+	irq = tumbler_find_device("headphone-detect",
+				  NULL, &mix->hp_detect, 0);
+	if (irq <= NO_IRQ)
+		irq = tumbler_find_device("headphone-detect",
+					  NULL, &mix->hp_detect, 1);
+	if (irq <= NO_IRQ)
+		irq = tumbler_find_device("keywest-gpio15",
+					  NULL, &mix->hp_detect, 1);
+	mix->headphone_irq = irq;
+ 	irq = tumbler_find_device("line-output-detect",
+				  NULL, &mix->line_detect, 0);
+ 	if (irq <= NO_IRQ)
+		irq = tumbler_find_device("line-output-detect",
+					  NULL, &mix->line_detect, 1);
+	if (IS_G4DA && irq <= NO_IRQ)
+		irq = tumbler_find_device("keywest-gpio16",
+					  NULL, &mix->line_detect, 1);
+	mix->lineout_irq = irq;
+
+	tumbler_reset_audio(chip);
+  
+	return 0;
+}
+
+static void tumbler_cleanup(struct snd_pmac *chip)
+{
+	struct pmac_tumbler *mix = chip->mixer_data;
+	if (! mix)
+		return;
+
+	if (mix->headphone_irq >= 0)
+		free_irq(mix->headphone_irq, chip);
+	if (mix->lineout_irq >= 0)
+		free_irq(mix->lineout_irq, chip);
+	tumbler_gpio_free(&mix->audio_reset);
+	tumbler_gpio_free(&mix->amp_mute);
+	tumbler_gpio_free(&mix->hp_mute);
+	tumbler_gpio_free(&mix->hp_detect);
+	snd_pmac_keywest_cleanup(&mix->i2c);
+	kfree(mix);
+	chip->mixer_data = NULL;
+}
+
+/* exported */
+int __devinit snd_pmac_tumbler_init(struct snd_pmac *chip)
+{
+	int i, err;
+	struct pmac_tumbler *mix;
+	const u32 *paddr;
+	struct device_node *tas_node, *np;
+	char *chipname;
+
+	request_module("i2c-powermac");
+
+	mix = kzalloc(sizeof(*mix), GFP_KERNEL);
+	if (! mix)
+		return -ENOMEM;
+	mix->headphone_irq = -1;
+
+	chip->mixer_data = mix;
+	chip->mixer_free = tumbler_cleanup;
+	mix->anded_reset = 0;
+	mix->reset_on_sleep = 1;
+
+	for (np = chip->node->child; np; np = np->sibling) {
+		if (!strcmp(np->name, "sound")) {
+			if (of_get_property(np, "has-anded-reset", NULL))
+				mix->anded_reset = 1;
+			if (of_get_property(np, "layout-id", NULL))
+				mix->reset_on_sleep = 0;
+			break;
+		}
+	}
+	if ((err = tumbler_init(chip)) < 0)
+		return err;
+
+	/* set up TAS */
+	tas_node = of_find_node_by_name(NULL, "deq");
+	if (tas_node == NULL)
+		tas_node = of_find_node_by_name(NULL, "codec");
+	if (tas_node == NULL)
+		return -ENODEV;
+
+	paddr = of_get_property(tas_node, "i2c-address", NULL);
+	if (paddr == NULL)
+		paddr = of_get_property(tas_node, "reg", NULL);
+	if (paddr)
+		mix->i2c.addr = (*paddr) >> 1;
+	else
+		mix->i2c.addr = TAS_I2C_ADDR;
+	of_node_put(tas_node);
+
+	DBG("(I) TAS i2c address is: %x\n", mix->i2c.addr);
+
+	if (chip->model == PMAC_TUMBLER) {
+		mix->i2c.init_client = tumbler_init_client;
+		mix->i2c.name = "TAS3001c";
+		chipname = "Tumbler";
+	} else {
+		mix->i2c.init_client = snapper_init_client;
+		mix->i2c.name = "TAS3004";
+		chipname = "Snapper";
+	}
+
+	if ((err = snd_pmac_keywest_init(&mix->i2c)) < 0)
+		return err;
+
+	/*
+	 * build mixers
+	 */
+	sprintf(chip->card->mixername, "PowerMac %s", chipname);
+
+	if (chip->model == PMAC_TUMBLER) {
+		for (i = 0; i < ARRAY_SIZE(tumbler_mixers); i++) {
+			if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&tumbler_mixers[i], chip))) < 0)
+				return err;
+		}
+	} else {
+		for (i = 0; i < ARRAY_SIZE(snapper_mixers); i++) {
+			if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snapper_mixers[i], chip))) < 0)
+				return err;
+		}
+	}
+	chip->master_sw_ctl = snd_ctl_new1(&tumbler_hp_sw, chip);
+	if ((err = snd_ctl_add(chip->card, chip->master_sw_ctl)) < 0)
+		return err;
+	chip->speaker_sw_ctl = snd_ctl_new1(&tumbler_speaker_sw, chip);
+	if ((err = snd_ctl_add(chip->card, chip->speaker_sw_ctl)) < 0)
+		return err;
+	if (mix->line_mute.addr != 0) {
+		chip->lineout_sw_ctl = snd_ctl_new1(&tumbler_lineout_sw, chip);
+		if ((err = snd_ctl_add(chip->card, chip->lineout_sw_ctl)) < 0)
+			return err;
+	}
+	chip->drc_sw_ctl = snd_ctl_new1(&tumbler_drc_sw, chip);
+	if ((err = snd_ctl_add(chip->card, chip->drc_sw_ctl)) < 0)
+		return err;
+
+	/* set initial DRC range to 60% */
+	if (chip->model == PMAC_TUMBLER)
+		mix->drc_range = (TAS3001_DRC_MAX * 6) / 10;
+	else
+		mix->drc_range = (TAS3004_DRC_MAX * 6) / 10;
+	mix->drc_enable = 1; /* will be changed later if AUTO_DRC is set */
+	if (chip->model == PMAC_TUMBLER)
+		tumbler_set_drc(mix);
+	else
+		snapper_set_drc(mix);
+
+#ifdef CONFIG_PM
+	chip->suspend = tumbler_suspend;
+	chip->resume = tumbler_resume;
+#endif
+
+	INIT_WORK(&device_change, device_change_handler);
+	device_change_chip = chip;
+
+#ifdef PMAC_SUPPORT_AUTOMUTE
+	if ((mix->headphone_irq >=0 || mix->lineout_irq >= 0)
+	    && (err = snd_pmac_add_automute(chip)) < 0)
+		return err;
+	chip->detect_headphone = tumbler_detect_headphone;
+	chip->update_automute = tumbler_update_automute;
+	tumbler_update_automute(chip, 0); /* update the status only */
+
+	/* activate headphone status interrupts */
+  	if (mix->headphone_irq >= 0) {
+		unsigned char val;
+		if ((err = request_irq(mix->headphone_irq, headphone_intr, 0,
+				       "Sound Headphone Detection", chip)) < 0)
+			return 0;
+		/* activate headphone status interrupts */
+		val = do_gpio_read(&mix->hp_detect);
+		do_gpio_write(&mix->hp_detect, val | 0x80);
+	}
+  	if (mix->lineout_irq >= 0) {
+		unsigned char val;
+		if ((err = request_irq(mix->lineout_irq, headphone_intr, 0,
+				       "Sound Lineout Detection", chip)) < 0)
+			return 0;
+		/* activate headphone status interrupts */
+		val = do_gpio_read(&mix->line_detect);
+		do_gpio_write(&mix->line_detect, val | 0x80);
+	}
+#endif
+
+	return 0;
+}
diff --git a/linux/sound/ppc/tumbler_volume.h b/linux/sound/ppc/tumbler_volume.h
new file mode 100644
index 0000000..ef8d85d
--- /dev/null
+++ b/linux/sound/ppc/tumbler_volume.h
@@ -0,0 +1,250 @@
+/* volume tables, taken from TAS3001c data manual */
+/* volume gain values */
+/* 0 = -70 dB, 175 = 18.0 dB in 0.5 dB step */
+static unsigned int master_volume_table[] = {
+	0x00000015, 0x00000016,	0x00000017,
+	0x00000019, 0x0000001a,	0x0000001c,
+	0x0000001d, 0x0000001f,	0x00000021,
+	0x00000023, 0x00000025,	0x00000027,
+	0x00000029, 0x0000002c,	0x0000002e,
+	0x00000031, 0x00000034,	0x00000037,
+	0x0000003a, 0x0000003e,	0x00000042,
+	0x00000045, 0x0000004a,	0x0000004e,
+	0x00000053, 0x00000057,	0x0000005d,
+	0x00000062, 0x00000068,	0x0000006e,
+	0x00000075, 0x0000007b,	0x00000083,
+	0x0000008b, 0x00000093,	0x0000009b,
+	0x000000a5, 0x000000ae,	0x000000b9,
+	0x000000c4, 0x000000cf,	0x000000dc,
+	0x000000e9, 0x000000f6,	0x00000105,
+	0x00000114, 0x00000125,	0x00000136,
+	0x00000148, 0x0000015c,	0x00000171,
+	0x00000186, 0x0000019e,	0x000001b6,
+	0x000001d0, 0x000001eb,	0x00000209,
+	0x00000227, 0x00000248,	0x0000026b,
+	0x0000028f, 0x000002b6,	0x000002df,
+	0x0000030b, 0x00000339,	0x0000036a,
+	0x0000039e, 0x000003d5,	0x0000040f,
+	0x0000044c, 0x0000048d,	0x000004d2,
+	0x0000051c, 0x00000569,	0x000005bb,
+	0x00000612, 0x0000066e,	0x000006d0,
+	0x00000737, 0x000007a5,	0x00000818,
+	0x00000893, 0x00000915,	0x0000099f,
+	0x00000a31, 0x00000acc,	0x00000b6f,
+	0x00000c1d, 0x00000cd5,	0x00000d97,
+	0x00000e65, 0x00000f40,	0x00001027,
+	0x0000111c, 0x00001220,	0x00001333,
+	0x00001456, 0x0000158a,	0x000016d1,
+	0x0000182b, 0x0000199a,	0x00001b1e,
+	0x00001cb9, 0x00001e6d,	0x0000203a,
+	0x00002223, 0x00002429,	0x0000264e,
+	0x00002893, 0x00002afa,	0x00002d86,
+	0x00003039, 0x00003314,	0x0000361b,
+	0x00003950, 0x00003cb5,	0x0000404e,
+	0x0000441d, 0x00004827,	0x00004c6d,
+	0x000050f4, 0x000055c0,	0x00005ad5,
+	0x00006037, 0x000065ea,	0x00006bf4,
+	0x0000725a, 0x00007920,	0x0000804e,
+	0x000087e8, 0x00008ff6,	0x0000987d,
+	0x0000a186, 0x0000ab19,	0x0000b53c,
+	0x0000bff9, 0x0000cb59,	0x0000d766,
+	0x0000e429, 0x0000f1ae,	0x00010000,
+	0x00010f2b, 0x00011f3d,	0x00013042,
+	0x00014249, 0x00015562,	0x0001699c,
+	0x00017f09, 0x000195bc,	0x0001adc6,
+	0x0001c73d, 0x0001e237,	0x0001feca,
+	0x00021d0e, 0x00023d1d,	0x00025f12,
+	0x0002830b, 0x0002a925,	0x0002d182,
+	0x0002fc42, 0x0003298b,	0x00035983,
+	0x00038c53, 0x0003c225,	0x0003fb28,
+	0x0004378b, 0x00047783,	0x0004bb44,
+	0x0005030a, 0x00054f10,	0x00059f98,
+	0x0005f4e5, 0x00064f40,	0x0006aef6,
+	0x00071457, 0x00077fbb,	0x0007f17b,
+};
+
+/* treble table for TAS3001c */
+/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */
+static unsigned int treble_volume_table[] = {
+	0x96, 0x95, 0x94,
+	0x93, 0x92, 0x91,
+	0x90, 0x8f, 0x8e,
+	0x8d, 0x8c, 0x8b,
+	0x8a, 0x89, 0x88,
+	0x87, 0x86, 0x85,
+	0x84, 0x83, 0x82,
+	0x81, 0x80, 0x7f,
+	0x7e, 0x7d, 0x7c,
+	0x7b, 0x7a, 0x79,
+	0x78, 0x77, 0x76,
+	0x75, 0x74, 0x73,
+	0x72, 0x71, 0x70,
+	0x6e, 0x6d, 0x6c,
+	0x6b, 0x69, 0x68,
+	0x66, 0x65, 0x63,
+	0x62, 0x60, 0x5e,
+	0x5c, 0x5a, 0x57,
+	0x55, 0x52, 0x4f,
+	0x4c, 0x49, 0x45,
+	0x42, 0x3e, 0x3a,
+	0x36, 0x32, 0x2d,
+	0x28, 0x22, 0x1c,
+	0x16, 0x10, 0x09,
+	0x01,
+};
+
+/* bass table for TAS3001c */
+/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */
+static unsigned int bass_volume_table[] = {
+	0x86, 0x82, 0x7f,
+	0x7d, 0x7a, 0x78,
+	0x76, 0x74, 0x72,
+	0x70, 0x6e, 0x6d,
+	0x6b, 0x69, 0x66,
+	0x64, 0x61, 0x5f,
+	0x5d, 0x5c, 0x5a,
+	0x59, 0x58, 0x56,
+	0x55, 0x54, 0x53,
+	0x51, 0x4f, 0x4d,
+	0x4b, 0x49, 0x46,
+	0x44, 0x42, 0x40,
+	0x3e, 0x3c, 0x3b,
+	0x39, 0x38, 0x36,
+	0x35, 0x33, 0x31,
+	0x30, 0x2e, 0x2c,
+	0x2b, 0x29, 0x28,
+	0x26, 0x25, 0x23,
+	0x21, 0x1f, 0x1c,
+	0x19, 0x18, 0x17,
+	0x16, 0x14, 0x13,
+	0x12, 0x10, 0x0f,
+	0x0d, 0x0b, 0x0a,
+	0x08, 0x06, 0x03,
+	0x01,
+};
+
+/* mixer (pcm) volume table */
+/* 0 = -70 dB, 175 = 18.0 dB in 0.5 dB step */
+static unsigned int mixer_volume_table[] = {
+	0x00014b, 0x00015f, 0x000174,
+	0x00018a, 0x0001a1, 0x0001ba,
+	0x0001d4, 0x0001f0, 0x00020d,
+	0x00022c, 0x00024d, 0x000270,
+	0x000295, 0x0002bc, 0x0002e6,
+	0x000312, 0x000340, 0x000372,
+	0x0003a6, 0x0003dd, 0x000418,
+	0x000456, 0x000498, 0x0004de,
+	0x000528, 0x000576, 0x0005c9,
+	0x000620, 0x00067d, 0x0006e0,
+	0x000748, 0x0007b7, 0x00082c,
+	0x0008a8, 0x00092b, 0x0009b6,
+	0x000a49, 0x000ae5, 0x000b8b,
+	0x000c3a, 0x000cf3, 0x000db8,
+	0x000e88, 0x000f64, 0x00104e,
+	0x001145, 0x00124b, 0x001361,
+	0x001487, 0x0015be, 0x001708,
+	0x001865, 0x0019d8, 0x001b60,
+	0x001cff, 0x001eb7, 0x002089,
+	0x002276, 0x002481, 0x0026ab,
+	0x0028f5, 0x002b63, 0x002df5,
+	0x0030ae, 0x003390, 0x00369e,
+	0x0039db, 0x003d49, 0x0040ea,
+	0x0044c3, 0x0048d6, 0x004d27,
+	0x0051b9, 0x005691, 0x005bb2,
+	0x006121, 0x0066e3, 0x006cfb,
+	0x007370, 0x007a48, 0x008186,
+	0x008933, 0x009154, 0x0099f1,
+	0x00a310, 0x00acba, 0x00b6f6,
+	0x00c1cd, 0x00cd49, 0x00d973,
+	0x00e655, 0x00f3fb, 0x010270,
+	0x0111c0, 0x0121f9, 0x013328,
+	0x01455b, 0x0158a2, 0x016d0e,
+	0x0182af, 0x019999, 0x01b1de,
+	0x01cb94, 0x01e6cf, 0x0203a7,
+	0x022235, 0x024293, 0x0264db,
+	0x02892c, 0x02afa3, 0x02d862,
+	0x03038a, 0x033142, 0x0361af,
+	0x0394fa, 0x03cb50, 0x0404de,
+	0x0441d5, 0x048268, 0x04c6d0,
+	0x050f44, 0x055c04, 0x05ad50,
+	0x06036e, 0x065ea5, 0x06bf44,
+	0x07259d, 0x079207, 0x0804dc,
+	0x087e80, 0x08ff59, 0x0987d5,
+	0x0a1866, 0x0ab189, 0x0b53be,
+	0x0bff91, 0x0cb591, 0x0d765a,
+	0x0e4290, 0x0f1adf, 0x100000,
+	0x10f2b4, 0x11f3c9, 0x13041a,
+	0x14248e, 0x15561a, 0x1699c0,
+	0x17f094, 0x195bb8, 0x1adc61,
+	0x1c73d5, 0x1e236d, 0x1fec98,
+	0x21d0d9, 0x23d1cd, 0x25f125,
+	0x2830af, 0x2a9254, 0x2d1818,
+	0x2fc420, 0x3298b0, 0x35982f,
+	0x38c528, 0x3c224c, 0x3fb278,
+	0x437880, 0x477828, 0x4bb446,
+	0x5030a1, 0x54f106, 0x59f980,
+	0x5f4e52, 0x64f403, 0x6aef5d,
+	0x714575, 0x77fbaa, 0x7f17af,
+};
+
+
+/* treble table for TAS3004 */
+/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */
+static unsigned int snapper_treble_volume_table[] = {
+	0x96, 0x95, 0x94,
+	0x93, 0x92, 0x91,
+	0x90, 0x8f, 0x8e,
+	0x8d, 0x8c, 0x8b,
+	0x8a, 0x89, 0x88,
+	0x87, 0x86, 0x85,
+	0x84, 0x83, 0x82,
+	0x81, 0x80, 0x7f,
+	0x7e, 0x7d, 0x7c,
+	0x7b, 0x7a, 0x79,
+	0x78, 0x77, 0x76,
+	0x75, 0x74, 0x73,
+	0x72, 0x71, 0x70,
+	0x6f, 0x6d, 0x6c,
+	0x6b, 0x69, 0x68,
+	0x67, 0x65, 0x63,
+	0x62, 0x60, 0x5d,
+	0x5b, 0x59, 0x56,
+	0x53, 0x51, 0x4d,
+	0x4a, 0x47, 0x43,
+	0x3f, 0x3b, 0x36,
+	0x31, 0x2c, 0x26,
+	0x20, 0x1a, 0x13,
+	0x08, 0x04, 0x01,
+	0x01,
+};
+
+/* bass table for TAS3004 */
+/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */
+static unsigned int snapper_bass_volume_table[] = {
+	0x96, 0x95, 0x94,
+	0x93, 0x92, 0x91,
+	0x90, 0x8f, 0x8e,
+	0x8d, 0x8c, 0x8b,
+	0x8a, 0x89, 0x88,
+	0x87, 0x86, 0x85,
+	0x84, 0x83, 0x82,
+	0x81, 0x80, 0x7f,
+	0x7e, 0x7d, 0x7c,
+	0x7b, 0x7a, 0x79,
+	0x78, 0x77, 0x76,
+	0x75, 0x74, 0x73,
+	0x72, 0x71, 0x6f,
+	0x6e, 0x6d, 0x6b,
+	0x6a, 0x69, 0x67,
+	0x66, 0x65, 0x63,
+	0x62, 0x61, 0x5f,
+	0x5d, 0x5b, 0x58,
+	0x55, 0x52, 0x4f,
+	0x4c, 0x49, 0x46,
+	0x43, 0x3f, 0x3b,
+	0x37, 0x33, 0x2e,
+	0x29, 0x24, 0x1e,
+	0x18, 0x11, 0x0a,
+	0x01,
+};
+
diff --git a/linux/sound/sh/Kconfig b/linux/sound/sh/Kconfig
new file mode 100644
index 0000000..61139f3
--- /dev/null
+++ b/linux/sound/sh/Kconfig
@@ -0,0 +1,31 @@
+# ALSA SH drivers
+
+menuconfig SND_SUPERH
+	bool "SUPERH sound devices"
+	depends on SUPERH
+	default y
+	help
+	  Support for sound devices specific to SUPERH architectures.
+	  Drivers that are implemented on ASoC can be found in
+	  "ALSA for SoC audio support" section.
+
+if SND_SUPERH
+
+config SND_AICA
+	tristate "Dreamcast Yamaha AICA sound"
+	depends on SH_DREAMCAST
+	select SND_PCM
+	select G2_DMA
+	help
+	  ALSA Sound driver for the SEGA Dreamcast console.
+
+config SND_SH_DAC_AUDIO
+	tristate "SuperH DAC audio support"
+	depends on SND
+	depends on CPU_SH3 && HIGH_RES_TIMERS
+	select SND_PCM
+	help
+	  Say Y here to include support for the on-chip DAC.
+
+endif	# SND_SUPERH
+
diff --git a/linux/sound/sh/Makefile b/linux/sound/sh/Makefile
new file mode 100644
index 0000000..7d09b51
--- /dev/null
+++ b/linux/sound/sh/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for ALSA
+#
+
+snd-aica-objs := aica.o
+snd-sh_dac_audio-objs := sh_dac_audio.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_AICA) += snd-aica.o
+obj-$(CONFIG_SND_SH_DAC_AUDIO) += snd-sh_dac_audio.o
diff --git a/linux/sound/sh/aica.c b/linux/sound/sh/aica.c
new file mode 100644
index 0000000..94c6ea7
--- /dev/null
+++ b/linux/sound/sh/aica.c
@@ -0,0 +1,689 @@
+/*
+* This code is licenced under 
+* the General Public Licence
+* version 2
+*
+* Copyright Adrian McMenamin 2005, 2006, 2007
+* <adrian@mcmen.demon.co.uk>
+* Requires firmware (BSD licenced) available from:
+* http://linuxdc.cvs.sourceforge.net/linuxdc/linux-sh-dc/sound/oss/aica/firmware/
+* or the maintainer
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of version 2 of the GNU General Public License as published by
+* the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*
+*/
+
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <mach/sysasic.h>
+#include "aica.h"
+
+MODULE_AUTHOR("Adrian McMenamin <adrian@mcmen.demon.co.uk>");
+MODULE_DESCRIPTION("Dreamcast AICA sound (pcm) driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Yamaha/SEGA, AICA}}");
+MODULE_FIRMWARE("aica_firmware.bin");
+
+/* module parameters */
+#define CARD_NAME "AICA"
+static int index = -1;
+static char *id;
+static int enable = 1;
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param(enable, bool, 0644);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+
+/* Use workqueue */
+static struct workqueue_struct *aica_queue;
+
+/* Simple platform device */
+static struct platform_device *pd;
+static struct resource aica_memory_space[2] = {
+	{
+	 .name = "AICA ARM CONTROL",
+	 .start = ARM_RESET_REGISTER,
+	 .flags = IORESOURCE_MEM,
+	 .end = ARM_RESET_REGISTER + 3,
+	 },
+	{
+	 .name = "AICA Sound RAM",
+	 .start = SPU_MEMORY_BASE,
+	 .flags = IORESOURCE_MEM,
+	 .end = SPU_MEMORY_BASE + 0x200000 - 1,
+	 },
+};
+
+/* SPU specific functions */
+/* spu_write_wait - wait for G2-SH FIFO to clear */
+static void spu_write_wait(void)
+{
+	int time_count;
+	time_count = 0;
+	while (1) {
+		if (!(readl(G2_FIFO) & 0x11))
+			break;
+		/* To ensure hardware failure doesn't wedge kernel */
+		time_count++;
+		if (time_count > 0x10000) {
+			snd_printk
+			    ("WARNING: G2 FIFO appears to be blocked.\n");
+			break;
+		}
+	}
+}
+
+/* spu_memset - write to memory in SPU address space */
+static void spu_memset(u32 toi, u32 what, int length)
+{
+	int i;
+	unsigned long flags;
+	if (snd_BUG_ON(length % 4))
+		return;
+	for (i = 0; i < length; i++) {
+		if (!(i % 8))
+			spu_write_wait();
+		local_irq_save(flags);
+		writel(what, toi + SPU_MEMORY_BASE);
+		local_irq_restore(flags);
+		toi++;
+	}
+}
+
+/* spu_memload - write to SPU address space */
+static void spu_memload(u32 toi, void *from, int length)
+{
+	unsigned long flags;
+	u32 *froml = from;
+	u32 __iomem *to = (u32 __iomem *) (SPU_MEMORY_BASE + toi);
+	int i;
+	u32 val;
+	length = DIV_ROUND_UP(length, 4);
+	spu_write_wait();
+	for (i = 0; i < length; i++) {
+		if (!(i % 8))
+			spu_write_wait();
+		val = *froml;
+		local_irq_save(flags);
+		writel(val, to);
+		local_irq_restore(flags);
+		froml++;
+		to++;
+	}
+}
+
+/* spu_disable - set spu registers to stop sound output */
+static void spu_disable(void)
+{
+	int i;
+	unsigned long flags;
+	u32 regval;
+	spu_write_wait();
+	regval = readl(ARM_RESET_REGISTER);
+	regval |= 1;
+	spu_write_wait();
+	local_irq_save(flags);
+	writel(regval, ARM_RESET_REGISTER);
+	local_irq_restore(flags);
+	for (i = 0; i < 64; i++) {
+		spu_write_wait();
+		regval = readl(SPU_REGISTER_BASE + (i * 0x80));
+		regval = (regval & ~0x4000) | 0x8000;
+		spu_write_wait();
+		local_irq_save(flags);
+		writel(regval, SPU_REGISTER_BASE + (i * 0x80));
+		local_irq_restore(flags);
+	}
+}
+
+/* spu_enable - set spu registers to enable sound output */
+static void spu_enable(void)
+{
+	unsigned long flags;
+	u32 regval = readl(ARM_RESET_REGISTER);
+	regval &= ~1;
+	spu_write_wait();
+	local_irq_save(flags);
+	writel(regval, ARM_RESET_REGISTER);
+	local_irq_restore(flags);
+}
+
+/* 
+ * Halt the sound processor, clear the memory,
+ * load some default ARM7 code, and then restart ARM7
+*/
+static void spu_reset(void)
+{
+	unsigned long flags;
+	spu_disable();
+	spu_memset(0, 0, 0x200000 / 4);
+	/* Put ARM7 in endless loop */
+	local_irq_save(flags);
+	__raw_writel(0xea000002, SPU_MEMORY_BASE);
+	local_irq_restore(flags);
+	spu_enable();
+}
+
+/* aica_chn_start - write to spu to start playback */
+static void aica_chn_start(void)
+{
+	unsigned long flags;
+	spu_write_wait();
+	local_irq_save(flags);
+	writel(AICA_CMD_KICK | AICA_CMD_START, (u32 *) AICA_CONTROL_POINT);
+	local_irq_restore(flags);
+}
+
+/* aica_chn_halt - write to spu to halt playback */
+static void aica_chn_halt(void)
+{
+	unsigned long flags;
+	spu_write_wait();
+	local_irq_save(flags);
+	writel(AICA_CMD_KICK | AICA_CMD_STOP, (u32 *) AICA_CONTROL_POINT);
+	local_irq_restore(flags);
+}
+
+/* ALSA code below */
+static struct snd_pcm_hardware snd_pcm_aica_playback_hw = {
+	.info = (SNDRV_PCM_INFO_NONINTERLEAVED),
+	.formats =
+	    (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |
+	     SNDRV_PCM_FMTBIT_IMA_ADPCM),
+	.rates = SNDRV_PCM_RATE_8000_48000,
+	.rate_min = 8000,
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.buffer_bytes_max = AICA_BUFFER_SIZE,
+	.period_bytes_min = AICA_PERIOD_SIZE,
+	.period_bytes_max = AICA_PERIOD_SIZE,
+	.periods_min = AICA_PERIOD_NUMBER,
+	.periods_max = AICA_PERIOD_NUMBER,
+};
+
+static int aica_dma_transfer(int channels, int buffer_size,
+			     struct snd_pcm_substream *substream)
+{
+	int q, err, period_offset;
+	struct snd_card_aica *dreamcastcard;
+	struct snd_pcm_runtime *runtime;
+	unsigned long flags;
+	err = 0;
+	dreamcastcard = substream->pcm->private_data;
+	period_offset = dreamcastcard->clicks;
+	period_offset %= (AICA_PERIOD_NUMBER / channels);
+	runtime = substream->runtime;
+	for (q = 0; q < channels; q++) {
+		local_irq_save(flags);
+		err = dma_xfer(AICA_DMA_CHANNEL,
+			       (unsigned long) (runtime->dma_area +
+						(AICA_BUFFER_SIZE * q) /
+						channels +
+						AICA_PERIOD_SIZE *
+						period_offset),
+			       AICA_CHANNEL0_OFFSET + q * CHANNEL_OFFSET +
+			       AICA_PERIOD_SIZE * period_offset,
+			       buffer_size / channels, AICA_DMA_MODE);
+		if (unlikely(err < 0)) {
+			local_irq_restore(flags);
+			break;
+		}
+		dma_wait_for_completion(AICA_DMA_CHANNEL);
+		local_irq_restore(flags);
+	}
+	return err;
+}
+
+static void startup_aica(struct snd_card_aica *dreamcastcard)
+{
+	spu_memload(AICA_CHANNEL0_CONTROL_OFFSET,
+		    dreamcastcard->channel, sizeof(struct aica_channel));
+	aica_chn_start();
+}
+
+static void run_spu_dma(struct work_struct *work)
+{
+	int buffer_size;
+	struct snd_pcm_runtime *runtime;
+	struct snd_card_aica *dreamcastcard;
+	dreamcastcard =
+	    container_of(work, struct snd_card_aica, spu_dma_work);
+	runtime = dreamcastcard->substream->runtime;
+	if (unlikely(dreamcastcard->dma_check == 0)) {
+		buffer_size =
+		    frames_to_bytes(runtime, runtime->buffer_size);
+		if (runtime->channels > 1)
+			dreamcastcard->channel->flags |= 0x01;
+		aica_dma_transfer(runtime->channels, buffer_size,
+				  dreamcastcard->substream);
+		startup_aica(dreamcastcard);
+		dreamcastcard->clicks =
+		    buffer_size / (AICA_PERIOD_SIZE * runtime->channels);
+		return;
+	} else {
+		aica_dma_transfer(runtime->channels,
+				  AICA_PERIOD_SIZE * runtime->channels,
+				  dreamcastcard->substream);
+		snd_pcm_period_elapsed(dreamcastcard->substream);
+		dreamcastcard->clicks++;
+		if (unlikely(dreamcastcard->clicks >= AICA_PERIOD_NUMBER))
+			dreamcastcard->clicks %= AICA_PERIOD_NUMBER;
+		mod_timer(&dreamcastcard->timer, jiffies + 1);
+	}
+}
+
+static void aica_period_elapsed(unsigned long timer_var)
+{
+	/*timer function - so cannot sleep */
+	int play_period;
+	struct snd_pcm_runtime *runtime;
+	struct snd_pcm_substream *substream;
+	struct snd_card_aica *dreamcastcard;
+	substream = (struct snd_pcm_substream *) timer_var;
+	runtime = substream->runtime;
+	dreamcastcard = substream->pcm->private_data;
+	/* Have we played out an additional period? */
+	play_period =
+	    frames_to_bytes(runtime,
+			    readl
+			    (AICA_CONTROL_CHANNEL_SAMPLE_NUMBER)) /
+	    AICA_PERIOD_SIZE;
+	if (play_period == dreamcastcard->current_period) {
+		/* reschedule the timer */
+		mod_timer(&(dreamcastcard->timer), jiffies + 1);
+		return;
+	}
+	if (runtime->channels > 1)
+		dreamcastcard->current_period = play_period;
+	if (unlikely(dreamcastcard->dma_check == 0))
+		dreamcastcard->dma_check = 1;
+	queue_work(aica_queue, &(dreamcastcard->spu_dma_work));
+}
+
+static void spu_begin_dma(struct snd_pcm_substream *substream)
+{
+	struct snd_card_aica *dreamcastcard;
+	struct snd_pcm_runtime *runtime;
+	runtime = substream->runtime;
+	dreamcastcard = substream->pcm->private_data;
+	/*get the queue to do the work */
+	queue_work(aica_queue, &(dreamcastcard->spu_dma_work));
+	/* Timer may already be running */
+	if (unlikely(dreamcastcard->timer.data)) {
+		mod_timer(&dreamcastcard->timer, jiffies + 4);
+		return;
+	}
+	init_timer(&(dreamcastcard->timer));
+	dreamcastcard->timer.data = (unsigned long) substream;
+	dreamcastcard->timer.function = aica_period_elapsed;
+	dreamcastcard->timer.expires = jiffies + 4;
+	add_timer(&(dreamcastcard->timer));
+}
+
+static int snd_aicapcm_pcm_open(struct snd_pcm_substream
+				*substream)
+{
+	struct snd_pcm_runtime *runtime;
+	struct aica_channel *channel;
+	struct snd_card_aica *dreamcastcard;
+	if (!enable)
+		return -ENOENT;
+	dreamcastcard = substream->pcm->private_data;
+	channel = kmalloc(sizeof(struct aica_channel), GFP_KERNEL);
+	if (!channel)
+		return -ENOMEM;
+	/* set defaults for channel */
+	channel->sfmt = SM_8BIT;
+	channel->cmd = AICA_CMD_START;
+	channel->vol = dreamcastcard->master_volume;
+	channel->pan = 0x80;
+	channel->pos = 0;
+	channel->flags = 0;	/* default to mono */
+	dreamcastcard->channel = channel;
+	runtime = substream->runtime;
+	runtime->hw = snd_pcm_aica_playback_hw;
+	spu_enable();
+	dreamcastcard->clicks = 0;
+	dreamcastcard->current_period = 0;
+	dreamcastcard->dma_check = 0;
+	return 0;
+}
+
+static int snd_aicapcm_pcm_close(struct snd_pcm_substream
+				 *substream)
+{
+	struct snd_card_aica *dreamcastcard = substream->pcm->private_data;
+	flush_workqueue(aica_queue);
+	if (dreamcastcard->timer.data)
+		del_timer(&dreamcastcard->timer);
+	kfree(dreamcastcard->channel);
+	spu_disable();
+	return 0;
+}
+
+static int snd_aicapcm_pcm_hw_free(struct snd_pcm_substream
+				   *substream)
+{
+	/* Free the DMA buffer */
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_aicapcm_pcm_hw_params(struct snd_pcm_substream
+				     *substream, struct snd_pcm_hw_params
+				     *hw_params)
+{
+	/* Allocate a DMA buffer using ALSA built-ins */
+	return
+	    snd_pcm_lib_malloc_pages(substream,
+				     params_buffer_bytes(hw_params));
+}
+
+static int snd_aicapcm_pcm_prepare(struct snd_pcm_substream
+				   *substream)
+{
+	struct snd_card_aica *dreamcastcard = substream->pcm->private_data;
+	if ((substream->runtime)->format == SNDRV_PCM_FORMAT_S16_LE)
+		dreamcastcard->channel->sfmt = SM_16BIT;
+	dreamcastcard->channel->freq = substream->runtime->rate;
+	dreamcastcard->substream = substream;
+	return 0;
+}
+
+static int snd_aicapcm_pcm_trigger(struct snd_pcm_substream
+				   *substream, int cmd)
+{
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		spu_begin_dma(substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		aica_chn_halt();
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static unsigned long snd_aicapcm_pcm_pointer(struct snd_pcm_substream
+					     *substream)
+{
+	return readl(AICA_CONTROL_CHANNEL_SAMPLE_NUMBER);
+}
+
+static struct snd_pcm_ops snd_aicapcm_playback_ops = {
+	.open = snd_aicapcm_pcm_open,
+	.close = snd_aicapcm_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_aicapcm_pcm_hw_params,
+	.hw_free = snd_aicapcm_pcm_hw_free,
+	.prepare = snd_aicapcm_pcm_prepare,
+	.trigger = snd_aicapcm_pcm_trigger,
+	.pointer = snd_aicapcm_pcm_pointer,
+};
+
+/* TO DO: set up to handle more than one pcm instance */
+static int __init snd_aicapcmchip(struct snd_card_aica
+				  *dreamcastcard, int pcm_index)
+{
+	struct snd_pcm *pcm;
+	int err;
+	/* AICA has no capture ability */
+	err =
+	    snd_pcm_new(dreamcastcard->card, "AICA PCM", pcm_index, 1, 0,
+			&pcm);
+	if (unlikely(err < 0))
+		return err;
+	pcm->private_data = dreamcastcard;
+	strcpy(pcm->name, "AICA PCM");
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_aicapcm_playback_ops);
+	/* Allocate the DMA buffers */
+	err =
+	    snd_pcm_lib_preallocate_pages_for_all(pcm,
+						  SNDRV_DMA_TYPE_CONTINUOUS,
+						  snd_dma_continuous_data
+						  (GFP_KERNEL),
+						  AICA_BUFFER_SIZE,
+						  AICA_BUFFER_SIZE);
+	return err;
+}
+
+/* Mixer controls */
+#define aica_pcmswitch_info		snd_ctl_boolean_mono_info
+
+static int aica_pcmswitch_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = 1;	/* TO DO: Fix me */
+	return 0;
+}
+
+static int aica_pcmswitch_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	if (ucontrol->value.integer.value[0] == 1)
+		return 0;	/* TO DO: Fix me */
+	else
+		aica_chn_halt();
+	return 0;
+}
+
+static int aica_pcmvolume_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0xFF;
+	return 0;
+}
+
+static int aica_pcmvolume_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_card_aica *dreamcastcard;
+	dreamcastcard = kcontrol->private_data;
+	if (unlikely(!dreamcastcard->channel))
+		return -ETXTBSY;	/* we've not yet been set up */
+	ucontrol->value.integer.value[0] = dreamcastcard->channel->vol;
+	return 0;
+}
+
+static int aica_pcmvolume_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_card_aica *dreamcastcard;
+	unsigned int vol;
+	dreamcastcard = kcontrol->private_data;
+	if (unlikely(!dreamcastcard->channel))
+		return -ETXTBSY;
+	vol = ucontrol->value.integer.value[0];
+	if (vol > 0xff)
+		return -EINVAL;
+	if (unlikely(dreamcastcard->channel->vol == vol))
+		return 0;
+	dreamcastcard->channel->vol = ucontrol->value.integer.value[0];
+	dreamcastcard->master_volume = ucontrol->value.integer.value[0];
+	spu_memload(AICA_CHANNEL0_CONTROL_OFFSET,
+		    dreamcastcard->channel, sizeof(struct aica_channel));
+	return 1;
+}
+
+static struct snd_kcontrol_new snd_aica_pcmswitch_control __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PCM Playback Switch",
+	.index = 0,
+	.info = aica_pcmswitch_info,
+	.get = aica_pcmswitch_get,
+	.put = aica_pcmswitch_put
+};
+
+static struct snd_kcontrol_new snd_aica_pcmvolume_control __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "PCM Playback Volume",
+	.index = 0,
+	.info = aica_pcmvolume_info,
+	.get = aica_pcmvolume_get,
+	.put = aica_pcmvolume_put
+};
+
+static int load_aica_firmware(void)
+{
+	int err;
+	const struct firmware *fw_entry;
+	spu_reset();
+	err = request_firmware(&fw_entry, "aica_firmware.bin", &pd->dev);
+	if (unlikely(err))
+		return err;
+	/* write firmware into memory */
+	spu_disable();
+	spu_memload(0, fw_entry->data, fw_entry->size);
+	spu_enable();
+	release_firmware(fw_entry);
+	return err;
+}
+
+static int __devinit add_aicamixer_controls(struct snd_card_aica
+					    *dreamcastcard)
+{
+	int err;
+	err = snd_ctl_add
+	    (dreamcastcard->card,
+	     snd_ctl_new1(&snd_aica_pcmvolume_control, dreamcastcard));
+	if (unlikely(err < 0))
+		return err;
+	err = snd_ctl_add
+	    (dreamcastcard->card,
+	     snd_ctl_new1(&snd_aica_pcmswitch_control, dreamcastcard));
+	if (unlikely(err < 0))
+		return err;
+	return 0;
+}
+
+static int __devexit snd_aica_remove(struct platform_device *devptr)
+{
+	struct snd_card_aica *dreamcastcard;
+	dreamcastcard = platform_get_drvdata(devptr);
+	if (unlikely(!dreamcastcard))
+		return -ENODEV;
+	snd_card_free(dreamcastcard->card);
+	kfree(dreamcastcard);
+	platform_set_drvdata(devptr, NULL);
+	return 0;
+}
+
+static int __devinit snd_aica_probe(struct platform_device *devptr)
+{
+	int err;
+	struct snd_card_aica *dreamcastcard;
+	dreamcastcard = kmalloc(sizeof(struct snd_card_aica), GFP_KERNEL);
+	if (unlikely(!dreamcastcard))
+		return -ENOMEM;
+	err = snd_card_create(index, SND_AICA_DRIVER, THIS_MODULE, 0,
+			      &dreamcastcard->card);
+	if (unlikely(err < 0)) {
+		kfree(dreamcastcard);
+		return err;
+	}
+	strcpy(dreamcastcard->card->driver, "snd_aica");
+	strcpy(dreamcastcard->card->shortname, SND_AICA_DRIVER);
+	strcpy(dreamcastcard->card->longname,
+	       "Yamaha AICA Super Intelligent Sound Processor for SEGA Dreamcast");
+	/* Prepare to use the queue */
+	INIT_WORK(&(dreamcastcard->spu_dma_work), run_spu_dma);
+	/* Load the PCM 'chip' */
+	err = snd_aicapcmchip(dreamcastcard, 0);
+	if (unlikely(err < 0))
+		goto freedreamcast;
+	snd_card_set_dev(dreamcastcard->card, &devptr->dev);
+	dreamcastcard->timer.data = 0;
+	dreamcastcard->channel = NULL;
+	/* Add basic controls */
+	err = add_aicamixer_controls(dreamcastcard);
+	if (unlikely(err < 0))
+		goto freedreamcast;
+	/* Register the card with ALSA subsystem */
+	err = snd_card_register(dreamcastcard->card);
+	if (unlikely(err < 0))
+		goto freedreamcast;
+	platform_set_drvdata(devptr, dreamcastcard);
+	aica_queue = create_workqueue(CARD_NAME);
+	if (unlikely(!aica_queue))
+		goto freedreamcast;
+	snd_printk
+	    ("ALSA Driver for Yamaha AICA Super Intelligent Sound Processor\n");
+	return 0;
+      freedreamcast:
+	snd_card_free(dreamcastcard->card);
+	kfree(dreamcastcard);
+	return err;
+}
+
+static struct platform_driver snd_aica_driver = {
+	.probe = snd_aica_probe,
+	.remove = __devexit_p(snd_aica_remove),
+	.driver = {
+		   .name = SND_AICA_DRIVER},
+};
+
+static int __init aica_init(void)
+{
+	int err;
+	err = platform_driver_register(&snd_aica_driver);
+	if (unlikely(err < 0))
+		return err;
+	pd = platform_device_register_simple(SND_AICA_DRIVER, -1,
+					     aica_memory_space, 2);
+	if (IS_ERR(pd)) {
+		platform_driver_unregister(&snd_aica_driver);
+		return PTR_ERR(pd);
+	}
+	/* Load the firmware */
+	return load_aica_firmware();
+}
+
+static void __exit aica_exit(void)
+{
+	/* Destroy the aica kernel thread            *
+	 * being extra cautious to check if it exists*/
+	if (likely(aica_queue))
+		destroy_workqueue(aica_queue);
+	platform_device_unregister(pd);
+	platform_driver_unregister(&snd_aica_driver);
+	/* Kill any sound still playing and reset ARM7 to safe state */
+	spu_reset();
+}
+
+module_init(aica_init);
+module_exit(aica_exit);
diff --git a/linux/sound/sh/aica.h b/linux/sound/sh/aica.h
new file mode 100644
index 0000000..d098baa
--- /dev/null
+++ b/linux/sound/sh/aica.h
@@ -0,0 +1,81 @@
+/* aica.h
+ * Header file for ALSA driver for
+ * Sega Dreamcast Yamaha AICA sound
+ * Copyright Adrian McMenamin
+ * <adrian@mcmen.demon.co.uk>
+ * 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as published by
+ * the Free Software Foundation.
+ *
+ * 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
+ *
+ */
+
+/* SPU memory and register constants etc */
+#define G2_FIFO 0xa05f688c
+#define SPU_MEMORY_BASE 0xA0800000
+#define ARM_RESET_REGISTER 0xA0702C00
+#define SPU_REGISTER_BASE 0xA0700000
+
+/* AICA channels stuff */
+#define AICA_CONTROL_POINT 0xA0810000
+#define AICA_CONTROL_CHANNEL_SAMPLE_NUMBER 0xA0810008
+#define AICA_CHANNEL0_CONTROL_OFFSET 0x10004
+
+/* Command values */
+#define AICA_CMD_KICK 0x80000000
+#define AICA_CMD_NONE 0
+#define AICA_CMD_START 1
+#define AICA_CMD_STOP 2
+#define AICA_CMD_VOL 3
+
+/* Sound modes */
+#define SM_8BIT		1
+#define SM_16BIT	0
+#define SM_ADPCM	2
+
+/* Buffer and period size */
+#define AICA_BUFFER_SIZE 0x8000
+#define AICA_PERIOD_SIZE 0x800
+#define AICA_PERIOD_NUMBER 16
+
+#define AICA_CHANNEL0_OFFSET 0x11000
+#define AICA_CHANNEL1_OFFSET 0x21000
+#define CHANNEL_OFFSET 0x10000
+
+#define AICA_DMA_CHANNEL 5
+#define AICA_DMA_MODE 5
+
+#define SND_AICA_DRIVER "AICA"
+
+struct aica_channel {
+	uint32_t cmd;		/* Command ID           */
+	uint32_t pos;		/* Sample position      */
+	uint32_t length;	/* Sample length        */
+	uint32_t freq;		/* Frequency            */
+	uint32_t vol;		/* Volume 0-255         */
+	uint32_t pan;		/* Pan 0-255            */
+	uint32_t sfmt;		/* Sound format         */
+	uint32_t flags;		/* Bit flags            */
+};
+
+struct snd_card_aica {
+	struct work_struct spu_dma_work;
+	struct snd_card *card;
+	struct aica_channel *channel;
+	struct snd_pcm_substream *substream;
+	int clicks;
+	int current_period;
+	struct timer_list timer;
+	int master_volume;
+	int dma_check;
+};
diff --git a/linux/sound/sh/sh_dac_audio.c b/linux/sound/sh/sh_dac_audio.c
new file mode 100644
index 0000000..68e0dee
--- /dev/null
+++ b/linux/sound/sh/sh_dac_audio.c
@@ -0,0 +1,454 @@
+/*
+ * sh_dac_audio.c - SuperH DAC audio driver for ALSA
+ *
+ * Copyright (c) 2009 by Rafael Ignacio Zurita <rizurita@yahoo.com>
+ *
+ *
+ * Based on sh_dac_audio.c (Copyright (C) 2004, 2005 by Andriy Skulysh)
+ *
+ *   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/hrtimer.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/sh_dac_audio.h>
+#include <asm/clock.h>
+#include <asm/hd64461.h>
+#include <mach/hp6xx.h>
+#include <cpu/dac.h>
+
+MODULE_AUTHOR("Rafael Ignacio Zurita <rizurita@yahoo.com>");
+MODULE_DESCRIPTION("SuperH DAC audio driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{SuperH DAC audio support}}");
+
+/* Module Parameters */
+static int index = SNDRV_DEFAULT_IDX1;
+static char *id = SNDRV_DEFAULT_STR1;
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for SuperH DAC audio.");
+module_param(id, charp, 0444);
+MODULE_PARM_DESC(id, "ID string for SuperH DAC audio.");
+
+/* main struct */
+struct snd_sh_dac {
+	struct snd_card *card;
+	struct snd_pcm_substream *substream;
+	struct hrtimer hrtimer;
+	ktime_t wakeups_per_second;
+
+	int rate;
+	int empty;
+	char *data_buffer, *buffer_begin, *buffer_end;
+	int processed; /* bytes proccesed, to compare with period_size */
+	int buffer_size;
+	struct dac_audio_pdata *pdata;
+};
+
+
+static void dac_audio_start_timer(struct snd_sh_dac *chip)
+{
+	hrtimer_start(&chip->hrtimer, chip->wakeups_per_second,
+		      HRTIMER_MODE_REL);
+}
+
+static void dac_audio_stop_timer(struct snd_sh_dac *chip)
+{
+	hrtimer_cancel(&chip->hrtimer);
+}
+
+static void dac_audio_reset(struct snd_sh_dac *chip)
+{
+	dac_audio_stop_timer(chip);
+	chip->buffer_begin = chip->buffer_end = chip->data_buffer;
+	chip->processed = 0;
+	chip->empty = 1;
+}
+
+static void dac_audio_set_rate(struct snd_sh_dac *chip)
+{
+	chip->wakeups_per_second = ktime_set(0, 1000000000 / chip->rate);
+}
+
+
+/* PCM INTERFACE */
+
+static struct snd_pcm_hardware snd_sh_dac_pcm_hw = {
+	.info			= (SNDRV_PCM_INFO_MMAP |
+					SNDRV_PCM_INFO_MMAP_VALID |
+					SNDRV_PCM_INFO_INTERLEAVED |
+					SNDRV_PCM_INFO_HALF_DUPLEX),
+	.formats		= SNDRV_PCM_FMTBIT_U8,
+	.rates			= SNDRV_PCM_RATE_8000,
+	.rate_min		= 8000,
+	.rate_max		= 8000,
+	.channels_min		= 1,
+	.channels_max		= 1,
+	.buffer_bytes_max	= (48*1024),
+	.period_bytes_min	= 1,
+	.period_bytes_max	= (48*1024),
+	.periods_min		= 1,
+	.periods_max		= 1024,
+};
+
+static int snd_sh_dac_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = snd_sh_dac_pcm_hw;
+
+	chip->substream = substream;
+	chip->buffer_begin = chip->buffer_end = chip->data_buffer;
+	chip->processed = 0;
+	chip->empty = 1;
+
+	chip->pdata->start(chip->pdata);
+
+	return 0;
+}
+
+static int snd_sh_dac_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
+
+	chip->substream = NULL;
+
+	dac_audio_stop_timer(chip);
+	chip->pdata->stop(chip->pdata);
+
+	return 0;
+}
+
+static int snd_sh_dac_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+			params_buffer_bytes(hw_params));
+}
+
+static int snd_sh_dac_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_sh_dac_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = chip->substream->runtime;
+
+	chip->buffer_size = runtime->buffer_size;
+	memset(chip->data_buffer, 0, chip->pdata->buffer_size);
+
+	return 0;
+}
+
+static int snd_sh_dac_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		dac_audio_start_timer(chip);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		chip->buffer_begin = chip->buffer_end = chip->data_buffer;
+		chip->processed = 0;
+		chip->empty = 1;
+		dac_audio_stop_timer(chip);
+		break;
+	default:
+		 return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int snd_sh_dac_pcm_copy(struct snd_pcm_substream *substream, int channel,
+	snd_pcm_uframes_t pos, void __user *src, snd_pcm_uframes_t count)
+{
+	/* channel is not used (interleaved data) */
+	struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	ssize_t b_count = frames_to_bytes(runtime , count);
+	ssize_t b_pos = frames_to_bytes(runtime , pos);
+
+	if (count < 0)
+		return -EINVAL;
+
+	if (!count)
+		return 0;
+
+	memcpy_toio(chip->data_buffer + b_pos, src, b_count);
+	chip->buffer_end = chip->data_buffer + b_pos + b_count;
+
+	if (chip->empty) {
+		chip->empty = 0;
+		dac_audio_start_timer(chip);
+	}
+
+	return 0;
+}
+
+static int snd_sh_dac_pcm_silence(struct snd_pcm_substream *substream,
+				  int channel, snd_pcm_uframes_t pos,
+				  snd_pcm_uframes_t count)
+{
+	/* channel is not used (interleaved data) */
+	struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	ssize_t b_count = frames_to_bytes(runtime , count);
+	ssize_t b_pos = frames_to_bytes(runtime , pos);
+
+	if (count < 0)
+		return -EINVAL;
+
+	if (!count)
+		return 0;
+
+	memset_io(chip->data_buffer + b_pos, 0, b_count);
+	chip->buffer_end = chip->data_buffer + b_pos + b_count;
+
+	if (chip->empty) {
+		chip->empty = 0;
+		dac_audio_start_timer(chip);
+	}
+
+	return 0;
+}
+
+static
+snd_pcm_uframes_t snd_sh_dac_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_sh_dac *chip = snd_pcm_substream_chip(substream);
+	int pointer = chip->buffer_begin - chip->data_buffer;
+
+	return pointer;
+}
+
+/* pcm ops */
+static struct snd_pcm_ops snd_sh_dac_pcm_ops = {
+	.open		= snd_sh_dac_pcm_open,
+	.close		= snd_sh_dac_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= snd_sh_dac_pcm_hw_params,
+	.hw_free	= snd_sh_dac_pcm_hw_free,
+	.prepare	= snd_sh_dac_pcm_prepare,
+	.trigger	= snd_sh_dac_pcm_trigger,
+	.pointer	= snd_sh_dac_pcm_pointer,
+	.copy		= snd_sh_dac_pcm_copy,
+	.silence	= snd_sh_dac_pcm_silence,
+	.mmap		= snd_pcm_lib_mmap_iomem,
+};
+
+static int __devinit snd_sh_dac_pcm(struct snd_sh_dac *chip, int device)
+{
+	int err;
+	struct snd_pcm *pcm;
+
+	/* device should be always 0 for us */
+	err = snd_pcm_new(chip->card, "SH_DAC PCM", device, 1, 0, &pcm);
+	if (err < 0)
+		return err;
+
+	pcm->private_data = chip;
+	strcpy(pcm->name, "SH_DAC PCM");
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sh_dac_pcm_ops);
+
+	/* buffer size=48K */
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+					  snd_dma_continuous_data(GFP_KERNEL),
+							48 * 1024,
+							48 * 1024);
+
+	return 0;
+}
+/* END OF PCM INTERFACE */
+
+
+/* driver .remove  --  destructor */
+static int snd_sh_dac_remove(struct platform_device *devptr)
+{
+	snd_card_free(platform_get_drvdata(devptr));
+	platform_set_drvdata(devptr, NULL);
+
+	return 0;
+}
+
+/* free -- it has been defined by create */
+static int snd_sh_dac_free(struct snd_sh_dac *chip)
+{
+	/* release the data */
+	kfree(chip->data_buffer);
+	kfree(chip);
+
+	return 0;
+}
+
+static int snd_sh_dac_dev_free(struct snd_device *device)
+{
+	struct snd_sh_dac *chip = device->device_data;
+
+	return snd_sh_dac_free(chip);
+}
+
+static enum hrtimer_restart sh_dac_audio_timer(struct hrtimer *handle)
+{
+	struct snd_sh_dac *chip = container_of(handle, struct snd_sh_dac,
+					       hrtimer);
+	struct snd_pcm_runtime *runtime = chip->substream->runtime;
+	ssize_t b_ps = frames_to_bytes(runtime, runtime->period_size);
+
+	if (!chip->empty) {
+		sh_dac_output(*chip->buffer_begin, chip->pdata->channel);
+		chip->buffer_begin++;
+
+		chip->processed++;
+		if (chip->processed >= b_ps) {
+			chip->processed -= b_ps;
+			snd_pcm_period_elapsed(chip->substream);
+		}
+
+		if (chip->buffer_begin == (chip->data_buffer +
+					   chip->buffer_size - 1))
+			chip->buffer_begin = chip->data_buffer;
+
+		if (chip->buffer_begin == chip->buffer_end)
+			chip->empty = 1;
+
+	}
+
+	if (!chip->empty)
+		hrtimer_start(&chip->hrtimer, chip->wakeups_per_second,
+			      HRTIMER_MODE_REL);
+
+	return HRTIMER_NORESTART;
+}
+
+/* create  --  chip-specific constructor for the cards components */
+static int __devinit snd_sh_dac_create(struct snd_card *card,
+				       struct platform_device *devptr,
+				       struct snd_sh_dac **rchip)
+{
+	struct snd_sh_dac *chip;
+	int err;
+
+	static struct snd_device_ops ops = {
+		   .dev_free = snd_sh_dac_dev_free,
+	};
+
+	*rchip = NULL;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+
+	chip->card = card;
+
+	hrtimer_init(&chip->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	chip->hrtimer.function = sh_dac_audio_timer;
+
+	dac_audio_reset(chip);
+	chip->rate = 8000;
+	dac_audio_set_rate(chip);
+
+	chip->pdata = devptr->dev.platform_data;
+
+	chip->data_buffer = kmalloc(chip->pdata->buffer_size, GFP_KERNEL);
+	if (chip->data_buffer == NULL) {
+		kfree(chip);
+		return -ENOMEM;
+	}
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		snd_sh_dac_free(chip);
+		return err;
+	}
+
+	*rchip = chip;
+
+	return 0;
+}
+
+/* driver .probe  --  constructor */
+static int __devinit snd_sh_dac_probe(struct platform_device *devptr)
+{
+	struct snd_sh_dac *chip;
+	struct snd_card *card;
+	int err;
+
+	err = snd_card_create(index, id, THIS_MODULE, 0, &card);
+	if (err < 0) {
+			snd_printk(KERN_ERR "cannot allocate the card\n");
+			return err;
+	}
+
+	err = snd_sh_dac_create(card, devptr, &chip);
+	if (err < 0)
+		goto probe_error;
+
+	err = snd_sh_dac_pcm(chip, 0);
+	if (err < 0)
+		goto probe_error;
+
+	strcpy(card->driver, "snd_sh_dac");
+	strcpy(card->shortname, "SuperH DAC audio driver");
+	printk(KERN_INFO "%s %s", card->longname, card->shortname);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto probe_error;
+
+	snd_printk("ALSA driver for SuperH DAC audio");
+
+	platform_set_drvdata(devptr, card);
+	return 0;
+
+probe_error:
+	snd_card_free(card);
+	return err;
+}
+
+/*
+ * "driver" definition
+ */
+static struct platform_driver driver = {
+	.probe	= snd_sh_dac_probe,
+	.remove = snd_sh_dac_remove,
+	.driver = {
+		.name = "dac_audio",
+	},
+};
+
+static int __init sh_dac_init(void)
+{
+	return platform_driver_register(&driver);
+}
+
+static void __exit sh_dac_exit(void)
+{
+	platform_driver_unregister(&driver);
+}
+
+module_init(sh_dac_init);
+module_exit(sh_dac_exit);
diff --git a/linux/sound/soc/Kconfig b/linux/sound/soc/Kconfig
new file mode 100644
index 0000000..3e598e7
--- /dev/null
+++ b/linux/sound/soc/Kconfig
@@ -0,0 +1,48 @@
+#
+# SoC audio configuration
+#
+
+menuconfig SND_SOC
+	tristate "ALSA for SoC audio support"
+	select SND_PCM
+	select AC97_BUS if SND_SOC_AC97_BUS
+	select SND_JACK if INPUT=y || INPUT=SND
+	---help---
+
+	  If you want ASoC support, you should say Y here and also to the
+	  specific driver for your SoC platform below.
+	  
+	  ASoC provides power efficient ALSA support for embedded battery powered
+	  SoC based systems like PDA's, Phones and Personal Media Players.
+
+	  This ASoC audio support can also be built as a module.  If so, the module
+	  will be called snd-soc-core.
+
+if SND_SOC
+
+config SND_SOC_AC97_BUS
+	bool
+
+# All the supported SoCs
+source "sound/soc/atmel/Kconfig"
+source "sound/soc/au1x/Kconfig"
+source "sound/soc/blackfin/Kconfig"
+source "sound/soc/davinci/Kconfig"
+source "sound/soc/ep93xx/Kconfig"
+source "sound/soc/fsl/Kconfig"
+source "sound/soc/imx/Kconfig"
+source "sound/soc/jz4740/Kconfig"
+source "sound/soc/nuc900/Kconfig"
+source "sound/soc/omap/Kconfig"
+source "sound/soc/kirkwood/Kconfig"
+source "sound/soc/pxa/Kconfig"
+source "sound/soc/s3c24xx/Kconfig"
+source "sound/soc/s6000/Kconfig"
+source "sound/soc/sh/Kconfig"
+source "sound/soc/txx9/Kconfig"
+
+# Supported codecs
+source "sound/soc/codecs/Kconfig"
+
+endif	# SND_SOC
+
diff --git a/linux/sound/soc/Makefile b/linux/sound/soc/Makefile
new file mode 100644
index 0000000..eb18344
--- /dev/null
+++ b/linux/sound/soc/Makefile
@@ -0,0 +1,20 @@
+snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-cache.o soc-utils.o
+
+obj-$(CONFIG_SND_SOC)	+= snd-soc-core.o
+obj-$(CONFIG_SND_SOC)	+= codecs/
+obj-$(CONFIG_SND_SOC)	+= atmel/
+obj-$(CONFIG_SND_SOC)	+= au1x/
+obj-$(CONFIG_SND_SOC)	+= blackfin/
+obj-$(CONFIG_SND_SOC)	+= davinci/
+obj-$(CONFIG_SND_SOC)	+= ep93xx/
+obj-$(CONFIG_SND_SOC)	+= fsl/
+obj-$(CONFIG_SND_SOC)   += imx/
+obj-$(CONFIG_SND_SOC)	+= jz4740/
+obj-$(CONFIG_SND_SOC)	+= nuc900/
+obj-$(CONFIG_SND_SOC)	+= omap/
+obj-$(CONFIG_SND_SOC)	+= kirkwood/
+obj-$(CONFIG_SND_SOC)	+= pxa/
+obj-$(CONFIG_SND_SOC)	+= s3c24xx/
+obj-$(CONFIG_SND_SOC)	+= s6000/
+obj-$(CONFIG_SND_SOC)	+= sh/
+obj-$(CONFIG_SND_SOC)	+= txx9/
diff --git a/linux/sound/soc/atmel/Kconfig b/linux/sound/soc/atmel/Kconfig
new file mode 100644
index 0000000..bee3c94
--- /dev/null
+++ b/linux/sound/soc/atmel/Kconfig
@@ -0,0 +1,52 @@
+config SND_ATMEL_SOC
+	tristate "SoC Audio for the Atmel System-on-Chip"
+	depends on ARCH_AT91 || AVR32
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the ATMEL SSC interface. You will also need
+	  to select the audio interfaces to support below.
+
+config SND_ATMEL_SOC_SSC
+	tristate
+	depends on SND_ATMEL_SOC
+	help
+	  Say Y or M if you want to add support for codecs the
+	  ATMEL SSC interface. You will also needs to select the individual
+	  machine drivers to support below.
+
+config SND_AT91_SOC_SAM9G20_WM8731
+	tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board"
+	depends on ATMEL_SSC && ARCH_AT91SAM9G20 && SND_ATMEL_SOC && \
+                   AT91_PROGRAMMABLE_CLOCKS
+	select SND_ATMEL_SOC_SSC
+	select SND_SOC_WM8731
+	help
+	  Say Y if you want to add support for SoC audio on WM8731-based
+	  AT91sam9g20 evaluation board.
+
+config SND_AT32_SOC_PLAYPAQ
+        tristate "SoC Audio support for PlayPaq with WM8510"
+        depends on SND_ATMEL_SOC && BOARD_PLAYPAQ && AT91_PROGRAMMABLE_CLOCKS
+        select SND_ATMEL_SOC_SSC
+        select SND_SOC_WM8510
+        help
+          Say Y or M here if you want to add support for SoC audio
+          on the LRS PlayPaq.
+
+config SND_AT32_SOC_PLAYPAQ_SLAVE
+        bool "Run CODEC on PlayPaq in slave mode"
+        depends on SND_AT32_SOC_PLAYPAQ
+        default n
+        help
+          Say Y if you want to run with the AT32 SSC generating the BCLK
+          and FRAME signals on the PlayPaq.  Unless you want to play
+          with the AT32 as the SSC master, you probably want to say N here,
+          as this will give you better sound quality.
+
+config SND_AT91_SOC_AFEB9260
+	tristate "SoC Audio support for AFEB9260 board"
+	depends on ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC
+	select SND_ATMEL_SOC_SSC
+	select SND_SOC_TLV320AIC23
+	help
+	  Say Y here to support sound on AFEB9260 board.
diff --git a/linux/sound/soc/atmel/Makefile b/linux/sound/soc/atmel/Makefile
new file mode 100644
index 0000000..e7ea56b
--- /dev/null
+++ b/linux/sound/soc/atmel/Makefile
@@ -0,0 +1,16 @@
+# AT91 Platform Support
+snd-soc-atmel-pcm-objs := atmel-pcm.o
+snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o
+
+obj-$(CONFIG_SND_ATMEL_SOC) += snd-soc-atmel-pcm.o
+obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o
+
+# AT91 Machine Support
+snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o
+
+# AT32 Machine Support
+snd-soc-playpaq-objs := playpaq_wm8510.o
+
+obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o
+obj-$(CONFIG_SND_AT32_SOC_PLAYPAQ) += snd-soc-playpaq.o
+obj-$(CONFIG_SND_AT91_SOC_AFEB9260) += snd-soc-afeb9260.o
diff --git a/linux/sound/soc/atmel/atmel-pcm.c b/linux/sound/soc/atmel/atmel-pcm.c
new file mode 100644
index 0000000..d0e7532
--- /dev/null
+++ b/linux/sound/soc/atmel/atmel-pcm.c
@@ -0,0 +1,510 @@
+/*
+ * atmel-pcm.c  --  ALSA PCM interface for the Atmel atmel SoC.
+ *
+ *  Copyright (C) 2005 SAN People
+ *  Copyright (C) 2008 Atmel
+ *
+ * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
+ *
+ * Based on at91-pcm. by:
+ * Frank Mandarino <fmandarino@endrelia.com>
+ * Copyright 2006 Endrelia Technologies Inc.
+ *
+ * Based on pxa2xx-pcm.c by:
+ *
+ * Author:	Nicolas Pitre
+ * Created:	Nov 30, 2004
+ * Copyright:	(C) 2004 MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/atmel_pdc.h>
+#include <linux/atmel-ssc.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "atmel-pcm.h"
+
+
+/*--------------------------------------------------------------------------*\
+ * Hardware definition
+\*--------------------------------------------------------------------------*/
+/* TODO: These values were taken from the AT91 platform driver, check
+ *	 them against real values for AT32
+ */
+static const struct snd_pcm_hardware atmel_pcm_hardware = {
+	.info			= SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_PAUSE,
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 8192,
+	.periods_min		= 2,
+	.periods_max		= 1024,
+	.buffer_bytes_max	= 32 * 1024,
+};
+
+
+/*--------------------------------------------------------------------------*\
+ * Data types
+\*--------------------------------------------------------------------------*/
+struct atmel_runtime_data {
+	struct atmel_pcm_dma_params *params;
+	dma_addr_t dma_buffer;		/* physical address of dma buffer */
+	dma_addr_t dma_buffer_end;	/* first address beyond DMA buffer */
+	size_t period_size;
+
+	dma_addr_t period_ptr;		/* physical address of next period */
+
+	/* PDC register save */
+	u32 pdc_xpr_save;
+	u32 pdc_xcr_save;
+	u32 pdc_xnpr_save;
+	u32 pdc_xncr_save;
+};
+
+
+/*--------------------------------------------------------------------------*\
+ * Helper functions
+\*--------------------------------------------------------------------------*/
+static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
+	int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = atmel_pcm_hardware.buffer_bytes_max;
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+	buf->area = dma_alloc_coherent(pcm->card->dev, size,
+					  &buf->addr, GFP_KERNEL);
+	pr_debug("atmel-pcm:"
+		"preallocate_dma_buffer: area=%p, addr=%p, size=%d\n",
+		(void *) buf->area,
+		(void *) buf->addr,
+		size);
+
+	if (!buf->area)
+		return -ENOMEM;
+
+	buf->bytes = size;
+	return 0;
+}
+/*--------------------------------------------------------------------------*\
+ * ISR
+\*--------------------------------------------------------------------------*/
+static void atmel_pcm_dma_irq(u32 ssc_sr,
+	struct snd_pcm_substream *substream)
+{
+	struct atmel_runtime_data *prtd = substream->runtime->private_data;
+	struct atmel_pcm_dma_params *params = prtd->params;
+	static int count;
+
+	count++;
+
+	if (ssc_sr & params->mask->ssc_endbuf) {
+		pr_warning("atmel-pcm: buffer %s on %s"
+				" (SSC_SR=%#x, count=%d)\n",
+				substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+				? "underrun" : "overrun",
+				params->name, ssc_sr, count);
+
+		/* re-start the PDC */
+		ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+			   params->mask->pdc_disable);
+		prtd->period_ptr += prtd->period_size;
+		if (prtd->period_ptr >= prtd->dma_buffer_end)
+			prtd->period_ptr = prtd->dma_buffer;
+
+		ssc_writex(params->ssc->regs, params->pdc->xpr,
+			   prtd->period_ptr);
+		ssc_writex(params->ssc->regs, params->pdc->xcr,
+			   prtd->period_size / params->pdc_xfer_size);
+		ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+			   params->mask->pdc_enable);
+	}
+
+	if (ssc_sr & params->mask->ssc_endx) {
+		/* Load the PDC next pointer and counter registers */
+		prtd->period_ptr += prtd->period_size;
+		if (prtd->period_ptr >= prtd->dma_buffer_end)
+			prtd->period_ptr = prtd->dma_buffer;
+
+		ssc_writex(params->ssc->regs, params->pdc->xnpr,
+			   prtd->period_ptr);
+		ssc_writex(params->ssc->regs, params->pdc->xncr,
+			   prtd->period_size / params->pdc_xfer_size);
+	}
+
+	snd_pcm_period_elapsed(substream);
+}
+
+
+/*--------------------------------------------------------------------------*\
+ * PCM operations
+\*--------------------------------------------------------------------------*/
+static int atmel_pcm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct atmel_runtime_data *prtd = runtime->private_data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	/* this may get called several times by oss emulation
+	 * with different params */
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+	runtime->dma_bytes = params_buffer_bytes(params);
+
+	prtd->params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+	prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
+
+	prtd->dma_buffer = runtime->dma_addr;
+	prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
+	prtd->period_size = params_period_bytes(params);
+
+	pr_debug("atmel-pcm: "
+		"hw_params: DMA for %s initialized "
+		"(dma_bytes=%u, period_size=%u)\n",
+		prtd->params->name,
+		runtime->dma_bytes,
+		prtd->period_size);
+	return 0;
+}
+
+static int atmel_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct atmel_runtime_data *prtd = substream->runtime->private_data;
+	struct atmel_pcm_dma_params *params = prtd->params;
+
+	if (params != NULL) {
+		ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
+			   params->mask->pdc_disable);
+		prtd->params->dma_intr_handler = NULL;
+	}
+
+	return 0;
+}
+
+static int atmel_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct atmel_runtime_data *prtd = substream->runtime->private_data;
+	struct atmel_pcm_dma_params *params = prtd->params;
+
+	ssc_writex(params->ssc->regs, SSC_IDR,
+		   params->mask->ssc_endx | params->mask->ssc_endbuf);
+	ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+		   params->mask->pdc_disable);
+	return 0;
+}
+
+static int atmel_pcm_trigger(struct snd_pcm_substream *substream,
+	int cmd)
+{
+	struct snd_pcm_runtime *rtd = substream->runtime;
+	struct atmel_runtime_data *prtd = rtd->private_data;
+	struct atmel_pcm_dma_params *params = prtd->params;
+	int ret = 0;
+
+	pr_debug("atmel-pcm:buffer_size = %ld,"
+		"dma_area = %p, dma_bytes = %u\n",
+		rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		prtd->period_ptr = prtd->dma_buffer;
+
+		ssc_writex(params->ssc->regs, params->pdc->xpr,
+			   prtd->period_ptr);
+		ssc_writex(params->ssc->regs, params->pdc->xcr,
+			   prtd->period_size / params->pdc_xfer_size);
+
+		prtd->period_ptr += prtd->period_size;
+		ssc_writex(params->ssc->regs, params->pdc->xnpr,
+			   prtd->period_ptr);
+		ssc_writex(params->ssc->regs, params->pdc->xncr,
+			   prtd->period_size / params->pdc_xfer_size);
+
+		pr_debug("atmel-pcm: trigger: "
+			"period_ptr=%lx, xpr=%u, "
+			"xcr=%u, xnpr=%u, xncr=%u\n",
+			(unsigned long)prtd->period_ptr,
+			ssc_readx(params->ssc->regs, params->pdc->xpr),
+			ssc_readx(params->ssc->regs, params->pdc->xcr),
+			ssc_readx(params->ssc->regs, params->pdc->xnpr),
+			ssc_readx(params->ssc->regs, params->pdc->xncr));
+
+		ssc_writex(params->ssc->regs, SSC_IER,
+			   params->mask->ssc_endx | params->mask->ssc_endbuf);
+		ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
+			   params->mask->pdc_enable);
+
+		pr_debug("sr=%u imr=%u\n",
+			ssc_readx(params->ssc->regs, SSC_SR),
+			ssc_readx(params->ssc->regs, SSC_IER));
+		break;		/* SNDRV_PCM_TRIGGER_START */
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+			   params->mask->pdc_disable);
+		break;
+
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
+			   params->mask->pdc_enable);
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static snd_pcm_uframes_t atmel_pcm_pointer(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct atmel_runtime_data *prtd = runtime->private_data;
+	struct atmel_pcm_dma_params *params = prtd->params;
+	dma_addr_t ptr;
+	snd_pcm_uframes_t x;
+
+	ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);
+	x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
+
+	if (x == runtime->buffer_size)
+		x = 0;
+
+	return x;
+}
+
+static int atmel_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct atmel_runtime_data *prtd;
+	int ret = 0;
+
+	snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);
+
+	/* ensure that buffer size is a multiple of period size */
+	ret = snd_pcm_hw_constraint_integer(runtime,
+						SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0)
+		goto out;
+
+	prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL);
+	if (prtd == NULL) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	runtime->private_data = prtd;
+
+ out:
+	return ret;
+}
+
+static int atmel_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct atmel_runtime_data *prtd = substream->runtime->private_data;
+
+	kfree(prtd);
+	return 0;
+}
+
+static int atmel_pcm_mmap(struct snd_pcm_substream *substream,
+	struct vm_area_struct *vma)
+{
+	return remap_pfn_range(vma, vma->vm_start,
+		       substream->dma_buffer.addr >> PAGE_SHIFT,
+		       vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+static struct snd_pcm_ops atmel_pcm_ops = {
+	.open		= atmel_pcm_open,
+	.close		= atmel_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= atmel_pcm_hw_params,
+	.hw_free	= atmel_pcm_hw_free,
+	.prepare	= atmel_pcm_prepare,
+	.trigger	= atmel_pcm_trigger,
+	.pointer	= atmel_pcm_pointer,
+	.mmap		= atmel_pcm_mmap,
+};
+
+
+/*--------------------------------------------------------------------------*\
+ * ASoC platform driver
+\*--------------------------------------------------------------------------*/
+static u64 atmel_pcm_dmamask = 0xffffffff;
+
+static int atmel_pcm_new(struct snd_card *card,
+	struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+	int ret = 0;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &atmel_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = 0xffffffff;
+
+	if (dai->driver->playback.channels_min) {
+		ret = atmel_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_PLAYBACK);
+		if (ret)
+			goto out;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		pr_debug("at32-pcm:"
+				"Allocating PCM capture DMA buffer\n");
+		ret = atmel_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_CAPTURE);
+		if (ret)
+			goto out;
+	}
+ out:
+	return ret;
+}
+
+static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+		dma_free_coherent(pcm->card->dev, buf->bytes,
+				  buf->area, buf->addr);
+		buf->area = NULL;
+	}
+}
+
+#ifdef CONFIG_PM
+static int atmel_pcm_suspend(struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = dai->runtime;
+	struct atmel_runtime_data *prtd;
+	struct atmel_pcm_dma_params *params;
+
+	if (!runtime)
+		return 0;
+
+	prtd = runtime->private_data;
+	params = prtd->params;
+
+	/* disable the PDC and save the PDC registers */
+
+	ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_disable);
+
+	prtd->pdc_xpr_save = ssc_readx(params->ssc->regs, params->pdc->xpr);
+	prtd->pdc_xcr_save = ssc_readx(params->ssc->regs, params->pdc->xcr);
+	prtd->pdc_xnpr_save = ssc_readx(params->ssc->regs, params->pdc->xnpr);
+	prtd->pdc_xncr_save = ssc_readx(params->ssc->regs, params->pdc->xncr);
+
+	return 0;
+}
+
+static int atmel_pcm_resume(struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = dai->runtime;
+	struct atmel_runtime_data *prtd;
+	struct atmel_pcm_dma_params *params;
+
+	if (!runtime)
+		return 0;
+
+	prtd = runtime->private_data;
+	params = prtd->params;
+
+	/* restore the PDC registers and enable the PDC */
+	ssc_writex(params->ssc->regs, params->pdc->xpr, prtd->pdc_xpr_save);
+	ssc_writex(params->ssc->regs, params->pdc->xcr, prtd->pdc_xcr_save);
+	ssc_writex(params->ssc->regs, params->pdc->xnpr, prtd->pdc_xnpr_save);
+	ssc_writex(params->ssc->regs, params->pdc->xncr, prtd->pdc_xncr_save);
+
+	ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_enable);
+	return 0;
+}
+#else
+#define atmel_pcm_suspend	NULL
+#define atmel_pcm_resume	NULL
+#endif
+
+static struct snd_soc_platform_driver atmel_soc_platform = {
+	.ops		= &atmel_pcm_ops,
+	.pcm_new	= atmel_pcm_new,
+	.pcm_free	= atmel_pcm_free_dma_buffers,
+	.suspend	= atmel_pcm_suspend,
+	.resume		= atmel_pcm_resume,
+};
+
+static int __devinit atmel_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &atmel_soc_platform);
+}
+
+static int __devexit atmel_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver atmel_pcm_driver = {
+	.driver = {
+			.name = "atmel-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = atmel_soc_platform_probe,
+	.remove = __devexit_p(atmel_soc_platform_remove),
+};
+
+static int __init snd_atmel_pcm_init(void)
+{
+	return platform_driver_register(&atmel_pcm_driver);
+}
+module_init(snd_atmel_pcm_init);
+
+static void __exit snd_atmel_pcm_exit(void)
+{
+	platform_driver_unregister(&atmel_pcm_driver);
+}
+module_exit(snd_atmel_pcm_exit);
+
+MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
+MODULE_DESCRIPTION("Atmel PCM module");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/atmel/atmel-pcm.h b/linux/sound/soc/atmel/atmel-pcm.h
new file mode 100644
index 0000000..2597329
--- /dev/null
+++ b/linux/sound/soc/atmel/atmel-pcm.h
@@ -0,0 +1,83 @@
+/*
+ * at91-pcm.h - ALSA PCM interface for the Atmel AT91 SoC.
+ *
+ *  Copyright (C) 2005 SAN People
+ *  Copyright (C) 2008 Atmel
+ *
+ * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
+ *
+ * Based on at91-pcm. by:
+ * Frank Mandarino <fmandarino@endrelia.com>
+ * Copyright 2006 Endrelia Technologies Inc.
+ *
+ * Based on pxa2xx-pcm.c by:
+ *
+ * Author:	Nicolas Pitre
+ * Created:	Nov 30, 2004
+ * Copyright:	(C) 2004 MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _ATMEL_PCM_H
+#define _ATMEL_PCM_H
+
+#include <linux/atmel-ssc.h>
+
+/*
+ * Registers and status bits that are required by the PCM driver.
+ */
+struct atmel_pdc_regs {
+	unsigned int	xpr;		/* PDC recv/trans pointer */
+	unsigned int	xcr;		/* PDC recv/trans counter */
+	unsigned int	xnpr;		/* PDC next recv/trans pointer */
+	unsigned int	xncr;		/* PDC next recv/trans counter */
+	unsigned int	ptcr;		/* PDC transfer control */
+};
+
+struct atmel_ssc_mask {
+	u32	ssc_enable;		/* SSC recv/trans enable */
+	u32	ssc_disable;		/* SSC recv/trans disable */
+	u32	ssc_endx;		/* SSC ENDTX or ENDRX */
+	u32	ssc_endbuf;		/* SSC TXBUFE or RXBUFF */
+	u32	pdc_enable;		/* PDC recv/trans enable */
+	u32	pdc_disable;		/* PDC recv/trans disable */
+};
+
+/*
+ * This structure, shared between the PCM driver and the interface,
+ * contains all information required by the PCM driver to perform the
+ * PDC DMA operation.  All fields except dma_intr_handler() are initialized
+ * by the interface.  The dms_intr_handler() pointer is set by the PCM
+ * driver and called by the interface SSC interrupt handler if it is
+ * non-NULL.
+ */
+struct atmel_pcm_dma_params {
+	char *name;			/* stream identifier */
+	int pdc_xfer_size;		/* PDC counter increment in bytes */
+	struct ssc_device *ssc;		/* SSC device for stream */
+	struct atmel_pdc_regs *pdc;	/* PDC receive or transmit registers */
+	struct atmel_ssc_mask *mask;	/* SSC & PDC status bits */
+	struct snd_pcm_substream *substream;
+	void (*dma_intr_handler)(u32, struct snd_pcm_substream *);
+};
+
+/*
+ * SSC register access (since ssc_writel() / ssc_readl() require literal name)
+ */
+#define ssc_readx(base, reg)            (__raw_readl((base) + (reg)))
+#define ssc_writex(base, reg, value)    __raw_writel((value), (base) + (reg))
+
+#endif /* _ATMEL_PCM_H */
diff --git a/linux/sound/soc/atmel/atmel_ssc_dai.c b/linux/sound/soc/atmel/atmel_ssc_dai.c
new file mode 100644
index 0000000..5d230ce
--- /dev/null
+++ b/linux/sound/soc/atmel/atmel_ssc_dai.c
@@ -0,0 +1,878 @@
+/*
+ * atmel_ssc_dai.c  --  ALSA SoC ATMEL SSC Audio Layer Platform driver
+ *
+ * Copyright (C) 2005 SAN People
+ * Copyright (C) 2008 Atmel
+ *
+ * Author: Sedji Gaouaou <sedji.gaouaou@atmel.com>
+ *         ATMEL CORP.
+ *
+ * Based on at91-ssc.c by
+ * Frank Mandarino <fmandarino@endrelia.com>
+ * Based on pxa2xx Platform drivers by
+ * Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/atmel_pdc.h>
+
+#include <linux/atmel-ssc.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+
+#include "atmel-pcm.h"
+#include "atmel_ssc_dai.h"
+
+
+#if defined(CONFIG_ARCH_AT91SAM9260) || defined(CONFIG_ARCH_AT91SAM9G20)
+#define NUM_SSC_DEVICES		1
+#else
+#define NUM_SSC_DEVICES		3
+#endif
+
+/*
+ * SSC PDC registers required by the PCM DMA engine.
+ */
+static struct atmel_pdc_regs pdc_tx_reg = {
+	.xpr		= ATMEL_PDC_TPR,
+	.xcr		= ATMEL_PDC_TCR,
+	.xnpr		= ATMEL_PDC_TNPR,
+	.xncr		= ATMEL_PDC_TNCR,
+};
+
+static struct atmel_pdc_regs pdc_rx_reg = {
+	.xpr		= ATMEL_PDC_RPR,
+	.xcr		= ATMEL_PDC_RCR,
+	.xnpr		= ATMEL_PDC_RNPR,
+	.xncr		= ATMEL_PDC_RNCR,
+};
+
+/*
+ * SSC & PDC status bits for transmit and receive.
+ */
+static struct atmel_ssc_mask ssc_tx_mask = {
+	.ssc_enable	= SSC_BIT(CR_TXEN),
+	.ssc_disable	= SSC_BIT(CR_TXDIS),
+	.ssc_endx	= SSC_BIT(SR_ENDTX),
+	.ssc_endbuf	= SSC_BIT(SR_TXBUFE),
+	.pdc_enable	= ATMEL_PDC_TXTEN,
+	.pdc_disable	= ATMEL_PDC_TXTDIS,
+};
+
+static struct atmel_ssc_mask ssc_rx_mask = {
+	.ssc_enable	= SSC_BIT(CR_RXEN),
+	.ssc_disable	= SSC_BIT(CR_RXDIS),
+	.ssc_endx	= SSC_BIT(SR_ENDRX),
+	.ssc_endbuf	= SSC_BIT(SR_RXBUFF),
+	.pdc_enable	= ATMEL_PDC_RXTEN,
+	.pdc_disable	= ATMEL_PDC_RXTDIS,
+};
+
+
+/*
+ * DMA parameters.
+ */
+static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = {
+	{{
+	.name		= "SSC0 PCM out",
+	.pdc		= &pdc_tx_reg,
+	.mask		= &ssc_tx_mask,
+	},
+	{
+	.name		= "SSC0 PCM in",
+	.pdc		= &pdc_rx_reg,
+	.mask		= &ssc_rx_mask,
+	} },
+#if NUM_SSC_DEVICES == 3
+	{{
+	.name		= "SSC1 PCM out",
+	.pdc		= &pdc_tx_reg,
+	.mask		= &ssc_tx_mask,
+	},
+	{
+	.name		= "SSC1 PCM in",
+	.pdc		= &pdc_rx_reg,
+	.mask		= &ssc_rx_mask,
+	} },
+	{{
+	.name		= "SSC2 PCM out",
+	.pdc		= &pdc_tx_reg,
+	.mask		= &ssc_tx_mask,
+	},
+	{
+	.name		= "SSC2 PCM in",
+	.pdc		= &pdc_rx_reg,
+	.mask		= &ssc_rx_mask,
+	} },
+#endif
+};
+
+
+static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = {
+	{
+	.name		= "ssc0",
+	.lock		= __SPIN_LOCK_UNLOCKED(ssc_info[0].lock),
+	.dir_mask	= SSC_DIR_MASK_UNUSED,
+	.initialized	= 0,
+	},
+#if NUM_SSC_DEVICES == 3
+	{
+	.name		= "ssc1",
+	.lock		= __SPIN_LOCK_UNLOCKED(ssc_info[1].lock),
+	.dir_mask	= SSC_DIR_MASK_UNUSED,
+	.initialized	= 0,
+	},
+	{
+	.name		= "ssc2",
+	.lock		= __SPIN_LOCK_UNLOCKED(ssc_info[2].lock),
+	.dir_mask	= SSC_DIR_MASK_UNUSED,
+	.initialized	= 0,
+	},
+#endif
+};
+
+
+/*
+ * SSC interrupt handler.  Passes PDC interrupts to the DMA
+ * interrupt handler in the PCM driver.
+ */
+static irqreturn_t atmel_ssc_interrupt(int irq, void *dev_id)
+{
+	struct atmel_ssc_info *ssc_p = dev_id;
+	struct atmel_pcm_dma_params *dma_params;
+	u32 ssc_sr;
+	u32 ssc_substream_mask;
+	int i;
+
+	ssc_sr = (unsigned long)ssc_readl(ssc_p->ssc->regs, SR)
+			& (unsigned long)ssc_readl(ssc_p->ssc->regs, IMR);
+
+	/*
+	 * Loop through the substreams attached to this SSC.  If
+	 * a DMA-related interrupt occurred on that substream, call
+	 * the DMA interrupt handler function, if one has been
+	 * registered in the dma_params structure by the PCM driver.
+	 */
+	for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) {
+		dma_params = ssc_p->dma_params[i];
+
+		if ((dma_params != NULL) &&
+			(dma_params->dma_intr_handler != NULL)) {
+			ssc_substream_mask = (dma_params->mask->ssc_endx |
+					dma_params->mask->ssc_endbuf);
+			if (ssc_sr & ssc_substream_mask) {
+				dma_params->dma_intr_handler(ssc_sr,
+						dma_params->
+						substream);
+			}
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+
+/*-------------------------------------------------------------------------*\
+ * DAI functions
+\*-------------------------------------------------------------------------*/
+/*
+ * Startup.  Only that one substream allowed in each direction.
+ */
+static int atmel_ssc_startup(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
+	int dir_mask;
+
+	pr_debug("atmel_ssc_startup: SSC_SR=0x%u\n",
+		ssc_readl(ssc_p->ssc->regs, SR));
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dir_mask = SSC_DIR_MASK_PLAYBACK;
+	else
+		dir_mask = SSC_DIR_MASK_CAPTURE;
+
+	spin_lock_irq(&ssc_p->lock);
+	if (ssc_p->dir_mask & dir_mask) {
+		spin_unlock_irq(&ssc_p->lock);
+		return -EBUSY;
+	}
+	ssc_p->dir_mask |= dir_mask;
+	spin_unlock_irq(&ssc_p->lock);
+
+	return 0;
+}
+
+/*
+ * Shutdown.  Clear DMA parameters and shutdown the SSC if there
+ * are no other substreams open.
+ */
+static void atmel_ssc_shutdown(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
+	struct atmel_pcm_dma_params *dma_params;
+	int dir, dir_mask;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dir = 0;
+	else
+		dir = 1;
+
+	dma_params = ssc_p->dma_params[dir];
+
+	if (dma_params != NULL) {
+		ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable);
+		pr_debug("atmel_ssc_shutdown: %s disabled SSC_SR=0x%08x\n",
+			(dir ? "receive" : "transmit"),
+			ssc_readl(ssc_p->ssc->regs, SR));
+
+		dma_params->ssc = NULL;
+		dma_params->substream = NULL;
+		ssc_p->dma_params[dir] = NULL;
+	}
+
+	dir_mask = 1 << dir;
+
+	spin_lock_irq(&ssc_p->lock);
+	ssc_p->dir_mask &= ~dir_mask;
+	if (!ssc_p->dir_mask) {
+		if (ssc_p->initialized) {
+			/* Shutdown the SSC clock. */
+			pr_debug("atmel_ssc_dau: Stopping clock\n");
+			clk_disable(ssc_p->ssc->clk);
+
+			free_irq(ssc_p->ssc->irq, ssc_p);
+			ssc_p->initialized = 0;
+		}
+
+		/* Reset the SSC */
+		ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
+		/* Clear the SSC dividers */
+		ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0;
+	}
+	spin_unlock_irq(&ssc_p->lock);
+}
+
+
+/*
+ * Record the DAI format for use in hw_params().
+ */
+static int atmel_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+		unsigned int fmt)
+{
+	struct atmel_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
+
+	ssc_p->daifmt = fmt;
+	return 0;
+}
+
+/*
+ * Record SSC clock dividers for use in hw_params().
+ */
+static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+	int div_id, int div)
+{
+	struct atmel_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
+
+	switch (div_id) {
+	case ATMEL_SSC_CMR_DIV:
+		/*
+		 * The same master clock divider is used for both
+		 * transmit and receive, so if a value has already
+		 * been set, it must match this value.
+		 */
+		if (ssc_p->cmr_div == 0)
+			ssc_p->cmr_div = div;
+		else
+			if (div != ssc_p->cmr_div)
+				return -EBUSY;
+		break;
+
+	case ATMEL_SSC_TCMR_PERIOD:
+		ssc_p->tcmr_period = div;
+		break;
+
+	case ATMEL_SSC_RCMR_PERIOD:
+		ssc_p->rcmr_period = div;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * Configure the SSC.
+ */
+static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
+	int id = dai->id;
+	struct atmel_ssc_info *ssc_p = &ssc_info[id];
+	struct atmel_pcm_dma_params *dma_params;
+	int dir, channels, bits;
+	u32 tfmr, rfmr, tcmr, rcmr;
+	int start_event;
+	int ret;
+
+	/*
+	 * Currently, there is only one set of dma params for
+	 * each direction.  If more are added, this code will
+	 * have to be changed to select the proper set.
+	 */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dir = 0;
+	else
+		dir = 1;
+
+	dma_params = &ssc_dma_params[id][dir];
+	dma_params->ssc = ssc_p->ssc;
+	dma_params->substream = substream;
+
+	ssc_p->dma_params[dir] = dma_params;
+
+	/*
+	 * The snd_soc_pcm_stream->dma_data field is only used to communicate
+	 * the appropriate DMA parameters to the pcm driver hw_params()
+	 * function.  It should not be used for other purposes
+	 * as it is common to all substreams.
+	 */
+	snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_params);
+
+	channels = params_channels(params);
+
+	/*
+	 * Determine sample size in bits and the PDC increment.
+	 */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		bits = 8;
+		dma_params->pdc_xfer_size = 1;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		bits = 16;
+		dma_params->pdc_xfer_size = 2;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		bits = 24;
+		dma_params->pdc_xfer_size = 4;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		bits = 32;
+		dma_params->pdc_xfer_size = 4;
+		break;
+	default:
+		printk(KERN_WARNING "atmel_ssc_dai: unsupported PCM format");
+		return -EINVAL;
+	}
+
+	/*
+	 * The SSC only supports up to 16-bit samples in I2S format, due
+	 * to the size of the Frame Mode Register FSLEN field.
+	 */
+	if ((ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S
+		&& bits > 16) {
+		printk(KERN_WARNING
+				"atmel_ssc_dai: sample size %d"
+				"is too large for I2S\n", bits);
+		return -EINVAL;
+	}
+
+	/*
+	 * Compute SSC register settings.
+	 */
+	switch (ssc_p->daifmt
+		& (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
+
+	case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
+		/*
+		 * I2S format, SSC provides BCLK and LRC clocks.
+		 *
+		 * The SSC transmit and receive clocks are generated
+		 * from the MCK divider, and the BCLK signal
+		 * is output on the SSC TK line.
+		 */
+		rcmr =	  SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period)
+			| SSC_BF(RCMR_STTDLY, START_DELAY)
+			| SSC_BF(RCMR_START, SSC_START_FALLING_RF)
+			| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
+			| SSC_BF(RCMR_CKO, SSC_CKO_NONE)
+			| SSC_BF(RCMR_CKS, SSC_CKS_DIV);
+
+		rfmr =	  SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+			| SSC_BF(RFMR_FSOS, SSC_FSOS_NEGATIVE)
+			| SSC_BF(RFMR_FSLEN, (bits - 1))
+			| SSC_BF(RFMR_DATNB, (channels - 1))
+			| SSC_BIT(RFMR_MSBF)
+			| SSC_BF(RFMR_LOOP, 0)
+			| SSC_BF(RFMR_DATLEN, (bits - 1));
+
+		tcmr =	  SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
+			| SSC_BF(TCMR_STTDLY, START_DELAY)
+			| SSC_BF(TCMR_START, SSC_START_FALLING_RF)
+			| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
+			| SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS)
+			| SSC_BF(TCMR_CKS, SSC_CKS_DIV);
+
+		tfmr =	  SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+			| SSC_BF(TFMR_FSDEN, 0)
+			| SSC_BF(TFMR_FSOS, SSC_FSOS_NEGATIVE)
+			| SSC_BF(TFMR_FSLEN, (bits - 1))
+			| SSC_BF(TFMR_DATNB, (channels - 1))
+			| SSC_BIT(TFMR_MSBF)
+			| SSC_BF(TFMR_DATDEF, 0)
+			| SSC_BF(TFMR_DATLEN, (bits - 1));
+		break;
+
+	case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
+		/*
+		 * I2S format, CODEC supplies BCLK and LRC clocks.
+		 *
+		 * The SSC transmit clock is obtained from the BCLK signal on
+		 * on the TK line, and the SSC receive clock is
+		 * generated from the transmit clock.
+		 *
+		 *  For single channel data, one sample is transferred
+		 * on the falling edge of the LRC clock.
+		 * For two channel data, one sample is
+		 * transferred on both edges of the LRC clock.
+		 */
+		start_event = ((channels == 1)
+				? SSC_START_FALLING_RF
+				: SSC_START_EDGE_RF);
+
+		rcmr =	  SSC_BF(RCMR_PERIOD, 0)
+			| SSC_BF(RCMR_STTDLY, START_DELAY)
+			| SSC_BF(RCMR_START, start_event)
+			| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
+			| SSC_BF(RCMR_CKO, SSC_CKO_NONE)
+			| SSC_BF(RCMR_CKS, SSC_CKS_CLOCK);
+
+		rfmr =	  SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+			| SSC_BF(RFMR_FSOS, SSC_FSOS_NONE)
+			| SSC_BF(RFMR_FSLEN, 0)
+			| SSC_BF(RFMR_DATNB, 0)
+			| SSC_BIT(RFMR_MSBF)
+			| SSC_BF(RFMR_LOOP, 0)
+			| SSC_BF(RFMR_DATLEN, (bits - 1));
+
+		tcmr =	  SSC_BF(TCMR_PERIOD, 0)
+			| SSC_BF(TCMR_STTDLY, START_DELAY)
+			| SSC_BF(TCMR_START, start_event)
+			| SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
+			| SSC_BF(TCMR_CKO, SSC_CKO_NONE)
+			| SSC_BF(TCMR_CKS, SSC_CKS_PIN);
+
+		tfmr =	  SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+			| SSC_BF(TFMR_FSDEN, 0)
+			| SSC_BF(TFMR_FSOS, SSC_FSOS_NONE)
+			| SSC_BF(TFMR_FSLEN, 0)
+			| SSC_BF(TFMR_DATNB, 0)
+			| SSC_BIT(TFMR_MSBF)
+			| SSC_BF(TFMR_DATDEF, 0)
+			| SSC_BF(TFMR_DATLEN, (bits - 1));
+		break;
+
+	case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
+		/*
+		 * DSP/PCM Mode A format, SSC provides BCLK and LRC clocks.
+		 *
+		 * The SSC transmit and receive clocks are generated from the
+		 * MCK divider, and the BCLK signal is output
+		 * on the SSC TK line.
+		 */
+		rcmr =	  SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period)
+			| SSC_BF(RCMR_STTDLY, 1)
+			| SSC_BF(RCMR_START, SSC_START_RISING_RF)
+			| SSC_BF(RCMR_CKI, SSC_CKI_RISING)
+			| SSC_BF(RCMR_CKO, SSC_CKO_NONE)
+			| SSC_BF(RCMR_CKS, SSC_CKS_DIV);
+
+		rfmr =	  SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+			| SSC_BF(RFMR_FSOS, SSC_FSOS_POSITIVE)
+			| SSC_BF(RFMR_FSLEN, 0)
+			| SSC_BF(RFMR_DATNB, (channels - 1))
+			| SSC_BIT(RFMR_MSBF)
+			| SSC_BF(RFMR_LOOP, 0)
+			| SSC_BF(RFMR_DATLEN, (bits - 1));
+
+		tcmr =	  SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
+			| SSC_BF(TCMR_STTDLY, 1)
+			| SSC_BF(TCMR_START, SSC_START_RISING_RF)
+			| SSC_BF(TCMR_CKI, SSC_CKI_RISING)
+			| SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS)
+			| SSC_BF(TCMR_CKS, SSC_CKS_DIV);
+
+		tfmr =	  SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
+			| SSC_BF(TFMR_FSDEN, 0)
+			| SSC_BF(TFMR_FSOS, SSC_FSOS_POSITIVE)
+			| SSC_BF(TFMR_FSLEN, 0)
+			| SSC_BF(TFMR_DATNB, (channels - 1))
+			| SSC_BIT(TFMR_MSBF)
+			| SSC_BF(TFMR_DATDEF, 0)
+			| SSC_BF(TFMR_DATLEN, (bits - 1));
+		break;
+
+	case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
+	default:
+		printk(KERN_WARNING "atmel_ssc_dai: unsupported DAI format 0x%x\n",
+			ssc_p->daifmt);
+		return -EINVAL;
+	}
+	pr_debug("atmel_ssc_hw_params: "
+			"RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n",
+			rcmr, rfmr, tcmr, tfmr);
+
+	if (!ssc_p->initialized) {
+
+		/* Enable PMC peripheral clock for this SSC */
+		pr_debug("atmel_ssc_dai: Starting clock\n");
+		clk_enable(ssc_p->ssc->clk);
+
+		/* Reset the SSC and its PDC registers */
+		ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
+
+		ssc_writel(ssc_p->ssc->regs, PDC_RPR, 0);
+		ssc_writel(ssc_p->ssc->regs, PDC_RCR, 0);
+		ssc_writel(ssc_p->ssc->regs, PDC_RNPR, 0);
+		ssc_writel(ssc_p->ssc->regs, PDC_RNCR, 0);
+
+		ssc_writel(ssc_p->ssc->regs, PDC_TPR, 0);
+		ssc_writel(ssc_p->ssc->regs, PDC_TCR, 0);
+		ssc_writel(ssc_p->ssc->regs, PDC_TNPR, 0);
+		ssc_writel(ssc_p->ssc->regs, PDC_TNCR, 0);
+
+		ret = request_irq(ssc_p->ssc->irq, atmel_ssc_interrupt, 0,
+				ssc_p->name, ssc_p);
+		if (ret < 0) {
+			printk(KERN_WARNING
+					"atmel_ssc_dai: request_irq failure\n");
+			pr_debug("Atmel_ssc_dai: Stoping clock\n");
+			clk_disable(ssc_p->ssc->clk);
+			return ret;
+		}
+
+		ssc_p->initialized = 1;
+	}
+
+	/* set SSC clock mode register */
+	ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->cmr_div);
+
+	/* set receive clock mode and format */
+	ssc_writel(ssc_p->ssc->regs, RCMR, rcmr);
+	ssc_writel(ssc_p->ssc->regs, RFMR, rfmr);
+
+	/* set transmit clock mode and format */
+	ssc_writel(ssc_p->ssc->regs, TCMR, tcmr);
+	ssc_writel(ssc_p->ssc->regs, TFMR, tfmr);
+
+	pr_debug("atmel_ssc_dai,hw_params: SSC initialized\n");
+	return 0;
+}
+
+
+static int atmel_ssc_prepare(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
+	struct atmel_pcm_dma_params *dma_params;
+	int dir;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dir = 0;
+	else
+		dir = 1;
+
+	dma_params = ssc_p->dma_params[dir];
+
+	ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_enable);
+
+	pr_debug("%s enabled SSC_SR=0x%08x\n",
+			dir ? "receive" : "transmit",
+			ssc_readl(ssc_p->ssc->regs, SR));
+	return 0;
+}
+
+
+#ifdef CONFIG_PM
+static int atmel_ssc_suspend(struct snd_soc_dai *cpu_dai)
+{
+	struct atmel_ssc_info *ssc_p;
+
+	if (!cpu_dai->active)
+		return 0;
+
+	ssc_p = &ssc_info[cpu_dai->id];
+
+	/* Save the status register before disabling transmit and receive */
+	ssc_p->ssc_state.ssc_sr = ssc_readl(ssc_p->ssc->regs, SR);
+	ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_TXDIS) | SSC_BIT(CR_RXDIS));
+
+	/* Save the current interrupt mask, then disable unmasked interrupts */
+	ssc_p->ssc_state.ssc_imr = ssc_readl(ssc_p->ssc->regs, IMR);
+	ssc_writel(ssc_p->ssc->regs, IDR, ssc_p->ssc_state.ssc_imr);
+
+	ssc_p->ssc_state.ssc_cmr = ssc_readl(ssc_p->ssc->regs, CMR);
+	ssc_p->ssc_state.ssc_rcmr = ssc_readl(ssc_p->ssc->regs, RCMR);
+	ssc_p->ssc_state.ssc_rfmr = ssc_readl(ssc_p->ssc->regs, RFMR);
+	ssc_p->ssc_state.ssc_tcmr = ssc_readl(ssc_p->ssc->regs, TCMR);
+	ssc_p->ssc_state.ssc_tfmr = ssc_readl(ssc_p->ssc->regs, TFMR);
+
+	return 0;
+}
+
+
+
+static int atmel_ssc_resume(struct snd_soc_dai *cpu_dai)
+{
+	struct atmel_ssc_info *ssc_p;
+	u32 cr;
+
+	if (!cpu_dai->active)
+		return 0;
+
+	ssc_p = &ssc_info[cpu_dai->id];
+
+	/* restore SSC register settings */
+	ssc_writel(ssc_p->ssc->regs, TFMR, ssc_p->ssc_state.ssc_tfmr);
+	ssc_writel(ssc_p->ssc->regs, TCMR, ssc_p->ssc_state.ssc_tcmr);
+	ssc_writel(ssc_p->ssc->regs, RFMR, ssc_p->ssc_state.ssc_rfmr);
+	ssc_writel(ssc_p->ssc->regs, RCMR, ssc_p->ssc_state.ssc_rcmr);
+	ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->ssc_state.ssc_cmr);
+
+	/* re-enable interrupts */
+	ssc_writel(ssc_p->ssc->regs, IER, ssc_p->ssc_state.ssc_imr);
+
+	/* Re-enable recieve and transmit as appropriate */
+	cr = 0;
+	cr |=
+	    (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_RXEN)) ? SSC_BIT(CR_RXEN) : 0;
+	cr |=
+	    (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_TXEN)) ? SSC_BIT(CR_TXEN) : 0;
+	ssc_writel(ssc_p->ssc->regs, CR, cr);
+
+	return 0;
+}
+#else /* CONFIG_PM */
+#  define atmel_ssc_suspend	NULL
+#  define atmel_ssc_resume	NULL
+#endif /* CONFIG_PM */
+
+static int atmel_ssc_probe(struct snd_soc_dai *dai)
+{
+	struct atmel_ssc_info *ssc_p = &ssc_info[dai->id];
+	int ret = 0;
+
+	snd_soc_dai_set_drvdata(dai, ssc_p);
+
+	/*
+	 * Request SSC device
+	 */
+	ssc_p->ssc = ssc_request(dai->id);
+	if (IS_ERR(ssc_p->ssc)) {
+		printk(KERN_ERR "ASoC: Failed to request SSC %d\n", dai->id);
+		ret = PTR_ERR(ssc_p->ssc);
+	}
+
+	return ret;
+}
+
+static int atmel_ssc_remove(struct snd_soc_dai *dai)
+{
+	struct atmel_ssc_info *ssc_p = snd_soc_dai_get_drvdata(dai);
+
+	ssc_free(ssc_p->ssc);
+	return 0;
+}
+
+#define ATMEL_SSC_RATES (SNDRV_PCM_RATE_8000_96000)
+
+#define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8     | SNDRV_PCM_FMTBIT_S16_LE |\
+			  SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops atmel_ssc_dai_ops = {
+	.startup	= atmel_ssc_startup,
+	.shutdown	= atmel_ssc_shutdown,
+	.prepare	= atmel_ssc_prepare,
+	.hw_params	= atmel_ssc_hw_params,
+	.set_fmt	= atmel_ssc_set_dai_fmt,
+	.set_clkdiv	= atmel_ssc_set_dai_clkdiv,
+};
+
+static struct snd_soc_dai_driver atmel_ssc_dai[NUM_SSC_DEVICES] = {
+	{
+		.name = "atmel-ssc-dai.0",
+		.probe = atmel_ssc_probe,
+		.remove = atmel_ssc_remove,
+		.suspend = atmel_ssc_suspend,
+		.resume = atmel_ssc_resume,
+		.playback = {
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = ATMEL_SSC_RATES,
+			.formats = ATMEL_SSC_FORMATS,},
+		.capture = {
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = ATMEL_SSC_RATES,
+			.formats = ATMEL_SSC_FORMATS,},
+		.ops = &atmel_ssc_dai_ops,
+	},
+#if NUM_SSC_DEVICES == 3
+	{
+		.name = "atmel-ssc-dai.1",
+		.probe = atmel_ssc_probe,
+		.remove = atmel_ssc_remove,
+		.suspend = atmel_ssc_suspend,
+		.resume = atmel_ssc_resume,
+		.playback = {
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = ATMEL_SSC_RATES,
+			.formats = ATMEL_SSC_FORMATS,},
+		.capture = {
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = ATMEL_SSC_RATES,
+			.formats = ATMEL_SSC_FORMATS,},
+		.ops = &atmel_ssc_dai_ops,
+	},
+	{
+		.name = "atmel-ssc-dai.2",
+		.probe = atmel_ssc_probe,
+		.remove = atmel_ssc_remove,
+		.suspend = atmel_ssc_suspend,
+		.resume = atmel_ssc_resume,
+		.playback = {
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = ATMEL_SSC_RATES,
+			.formats = ATMEL_SSC_FORMATS,},
+		.capture = {
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = ATMEL_SSC_RATES,
+			.formats = ATMEL_SSC_FORMATS,},
+		.ops = &atmel_ssc_dai_ops,
+	},
+#endif
+};
+
+static __devinit int asoc_ssc_probe(struct platform_device *pdev)
+{
+	BUG_ON(pdev->id < 0);
+	BUG_ON(pdev->id >= ARRAY_SIZE(atmel_ssc_dai));
+	return snd_soc_register_dai(&pdev->dev, &atmel_ssc_dai[pdev->id]);
+}
+
+static int __devexit asoc_ssc_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dai(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver asoc_ssc_driver = {
+	.driver = {
+			.name = "atmel-ssc-dai",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = asoc_ssc_probe,
+	.remove = __devexit_p(asoc_ssc_remove),
+};
+
+/**
+ * atmel_ssc_set_audio - Allocate the specified SSC for audio use.
+ */
+int atmel_ssc_set_audio(int ssc_id)
+{
+	struct ssc_device *ssc;
+	static struct platform_device *dma_pdev;
+	struct platform_device *ssc_pdev;
+	int ret;
+
+	if (ssc_id < 0 || ssc_id >= ARRAY_SIZE(atmel_ssc_dai))
+		return -EINVAL;
+
+	/* Allocate a dummy device for DMA if we don't have one already */
+	if (!dma_pdev) {
+		dma_pdev = platform_device_alloc("atmel-pcm-audio", -1);
+		if (!dma_pdev)
+			return -ENOMEM;
+
+		ret = platform_device_add(dma_pdev);
+		if (ret < 0) {
+			platform_device_put(dma_pdev);
+			dma_pdev = NULL;
+			return ret;
+		}
+	}
+
+	ssc_pdev = platform_device_alloc("atmel-ssc-dai", ssc_id);
+	if (!ssc_pdev) {
+		ssc_free(ssc);
+		return -ENOMEM;
+	}
+
+	/* If we can grab the SSC briefly to parent the DAI device off it */
+	ssc = ssc_request(ssc_id);
+	if (IS_ERR(ssc))
+		pr_warn("Unable to parent ASoC SSC DAI on SSC: %ld\n",
+			PTR_ERR(ssc));
+	else
+		ssc_pdev->dev.parent = &(ssc->pdev->dev);
+	ssc_free(ssc);
+
+	ret = platform_device_add(ssc_pdev);
+	if (ret < 0)
+		platform_device_put(ssc_pdev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(atmel_ssc_set_audio);
+
+static int __init snd_atmel_ssc_init(void)
+{
+	return platform_driver_register(&asoc_ssc_driver);
+}
+module_init(snd_atmel_ssc_init);
+
+static void __exit snd_atmel_ssc_exit(void)
+{
+	platform_driver_unregister(&asoc_ssc_driver);
+}
+module_exit(snd_atmel_ssc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com");
+MODULE_DESCRIPTION("ATMEL SSC ASoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/atmel/atmel_ssc_dai.h b/linux/sound/soc/atmel/atmel_ssc_dai.h
new file mode 100644
index 0000000..5d4f0f9
--- /dev/null
+++ b/linux/sound/soc/atmel/atmel_ssc_dai.h
@@ -0,0 +1,122 @@
+/*
+ * atmel_ssc_dai.h - ALSA SSC interface for the Atmel  SoC
+ *
+ * Copyright (C) 2005 SAN People
+ * Copyright (C) 2008 Atmel
+ *
+ * Author: Sedji Gaouaou <sedji.gaouaou@atmel.com>
+ *         ATMEL CORP.
+ *
+ * Based on at91-ssc.c by
+ * Frank Mandarino <fmandarino@endrelia.com>
+ * Based on pxa2xx Platform drivers by
+ * Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * 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 _ATMEL_SSC_DAI_H
+#define _ATMEL_SSC_DAI_H
+
+#include <linux/types.h>
+#include <linux/atmel-ssc.h>
+
+#include "atmel-pcm.h"
+
+/* SSC system clock ids */
+#define ATMEL_SYSCLK_MCK	0 /* SSC uses AT91 MCK as system clock */
+
+/* SSC divider ids */
+#define ATMEL_SSC_CMR_DIV	0 /* MCK divider for BCLK */
+#define ATMEL_SSC_TCMR_PERIOD	1 /* BCLK divider for transmit FS */
+#define ATMEL_SSC_RCMR_PERIOD	2 /* BCLK divider for receive FS */
+/*
+ * SSC direction masks
+ */
+#define SSC_DIR_MASK_UNUSED	0
+#define SSC_DIR_MASK_PLAYBACK	1
+#define SSC_DIR_MASK_CAPTURE	2
+
+/*
+ * SSC register values that Atmel left out of <linux/atmel-ssc.h>.  These
+ * are expected to be used with SSC_BF
+ */
+/* START bit field values */
+#define SSC_START_CONTINUOUS	0
+#define SSC_START_TX_RX		1
+#define SSC_START_LOW_RF	2
+#define SSC_START_HIGH_RF	3
+#define SSC_START_FALLING_RF	4
+#define SSC_START_RISING_RF	5
+#define SSC_START_LEVEL_RF	6
+#define SSC_START_EDGE_RF	7
+#define SSS_START_COMPARE_0	8
+
+/* CKI bit field values */
+#define SSC_CKI_FALLING		0
+#define SSC_CKI_RISING		1
+
+/* CKO bit field values */
+#define SSC_CKO_NONE		0
+#define SSC_CKO_CONTINUOUS	1
+#define SSC_CKO_TRANSFER	2
+
+/* CKS bit field values */
+#define SSC_CKS_DIV		0
+#define SSC_CKS_CLOCK		1
+#define SSC_CKS_PIN		2
+
+/* FSEDGE bit field values */
+#define SSC_FSEDGE_POSITIVE	0
+#define SSC_FSEDGE_NEGATIVE	1
+
+/* FSOS bit field values */
+#define SSC_FSOS_NONE		0
+#define SSC_FSOS_NEGATIVE	1
+#define SSC_FSOS_POSITIVE	2
+#define SSC_FSOS_LOW		3
+#define SSC_FSOS_HIGH		4
+#define SSC_FSOS_TOGGLE		5
+
+#define START_DELAY		1
+
+struct atmel_ssc_state {
+	u32 ssc_cmr;
+	u32 ssc_rcmr;
+	u32 ssc_rfmr;
+	u32 ssc_tcmr;
+	u32 ssc_tfmr;
+	u32 ssc_sr;
+	u32 ssc_imr;
+};
+
+
+struct atmel_ssc_info {
+	char *name;
+	struct ssc_device *ssc;
+	spinlock_t lock;	/* lock for dir_mask */
+	unsigned short dir_mask;	/* 0=unused, 1=playback, 2=capture */
+	unsigned short initialized;	/* true if SSC has been initialized */
+	unsigned short daifmt;
+	unsigned short cmr_div;
+	unsigned short tcmr_period;
+	unsigned short rcmr_period;
+	struct atmel_pcm_dma_params *dma_params[2];
+	struct atmel_ssc_state ssc_state;
+};
+
+int atmel_ssc_set_audio(int ssc);
+
+#endif /* _AT91_SSC_DAI_H */
diff --git a/linux/sound/soc/atmel/playpaq_wm8510.c b/linux/sound/soc/atmel/playpaq_wm8510.c
new file mode 100644
index 0000000..5f4e59f
--- /dev/null
+++ b/linux/sound/soc/atmel/playpaq_wm8510.c
@@ -0,0 +1,471 @@
+/* sound/soc/at32/playpaq_wm8510.c
+ * ASoC machine driver for PlayPaq using WM8510 codec
+ *
+ * Copyright (C) 2008 Long Range Systems
+ *    Geoffrey Wossum <gwossum@acm.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This code is largely inspired by sound/soc/at91/eti_b1_wm8731.c
+ *
+ * NOTE: If you don't have the AT32 enhanced portmux configured (which
+ * isn't currently in the mainline or Atmel patched kernel), you will
+ * need to set the MCLK pin (PA30) to peripheral A in your board initialization
+ * code.  Something like:
+ *	at32_select_periph(GPIO_PIN_PA(30), GPIO_PERIPH_A, 0);
+ *
+ */
+
+/* #define DEBUG */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <mach/at32ap700x.h>
+#include <mach/portmux.h>
+
+#include "../codecs/wm8510.h"
+#include "atmel-pcm.h"
+#include "atmel_ssc_dai.h"
+
+
+/*-------------------------------------------------------------------------*\
+ * constants
+\*-------------------------------------------------------------------------*/
+#define MCLK_PIN		GPIO_PIN_PA(30)
+#define MCLK_PERIPH		GPIO_PERIPH_A
+
+
+/*-------------------------------------------------------------------------*\
+ * data types
+\*-------------------------------------------------------------------------*/
+/* SSC clocking data */
+struct ssc_clock_data {
+	/* CMR div */
+	unsigned int cmr_div;
+
+	/* Frame period (as needed by xCMR.PERIOD) */
+	unsigned int period;
+
+	/* The SSC clock rate these settings where calculated for */
+	unsigned long ssc_rate;
+};
+
+
+/*-------------------------------------------------------------------------*\
+ * module data
+\*-------------------------------------------------------------------------*/
+static struct clk *_gclk0;
+static struct clk *_pll0;
+
+#define CODEC_CLK (_gclk0)
+
+
+/*-------------------------------------------------------------------------*\
+ * Sound SOC operations
+\*-------------------------------------------------------------------------*/
+#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *cpu_dai)
+{
+	struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssc_device *ssc = ssc_p->ssc;
+	struct ssc_clock_data cd;
+	unsigned int rate, width_bits, channels;
+	unsigned int bitrate, ssc_div;
+	unsigned actual_rate;
+
+
+	/*
+	 * Figure out required bitrate
+	 */
+	rate = params_rate(params);
+	channels = params_channels(params);
+	width_bits = snd_pcm_format_physical_width(params_format(params));
+	bitrate = rate * width_bits * channels;
+
+
+	/*
+	 * Figure out required SSC divider and period for required bitrate
+	 */
+	cd.ssc_rate = clk_get_rate(ssc->clk);
+	ssc_div = cd.ssc_rate / bitrate;
+	cd.cmr_div = ssc_div / 2;
+	if (ssc_div & 1) {
+		/* round cmr_div up */
+		cd.cmr_div++;
+	}
+	cd.period = width_bits - 1;
+
+
+	/*
+	 * Find actual rate, compare to requested rate
+	 */
+	actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1));
+	pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n",
+		 rate, actual_rate);
+
+
+	return cd;
+}
+#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
+
+
+
+static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssc_device *ssc = ssc_p->ssc;
+	unsigned int pll_out = 0, bclk = 0, mclk_div = 0;
+	int ret;
+
+
+	/* Due to difficulties with getting the correct clocks from the AT32's
+	 * PLL0, we're going to let the CODEC be in charge of all the clocks
+	 */
+#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+	const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+#else
+	struct ssc_clock_data cd;
+	const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBS_CFS);
+#endif
+
+	if (ssc == NULL) {
+		pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n");
+		return -EINVAL;
+	}
+
+
+	/*
+	 * Figure out PLL and BCLK dividers for WM8510
+	 */
+	switch (params_rate(params)) {
+	case 48000:
+		pll_out = 24576000;
+		mclk_div = WM8510_MCLKDIV_2;
+		bclk = WM8510_BCLKDIV_8;
+		break;
+
+	case 44100:
+		pll_out = 22579200;
+		mclk_div = WM8510_MCLKDIV_2;
+		bclk = WM8510_BCLKDIV_8;
+		break;
+
+	case 22050:
+		pll_out = 22579200;
+		mclk_div = WM8510_MCLKDIV_4;
+		bclk = WM8510_BCLKDIV_8;
+		break;
+
+	case 16000:
+		pll_out = 24576000;
+		mclk_div = WM8510_MCLKDIV_6;
+		bclk = WM8510_BCLKDIV_8;
+		break;
+
+	case 11025:
+		pll_out = 22579200;
+		mclk_div = WM8510_MCLKDIV_8;
+		bclk = WM8510_BCLKDIV_8;
+		break;
+
+	case 8000:
+		pll_out = 24576000;
+		mclk_div = WM8510_MCLKDIV_12;
+		bclk = WM8510_BCLKDIV_8;
+		break;
+
+	default:
+		pr_warning("playpaq_wm8510: Unsupported sample rate %d\n",
+			   params_rate(params));
+		return -EINVAL;
+	}
+
+
+	/*
+	 * set CPU and CODEC DAI configuration
+	 */
+	ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+	if (ret < 0) {
+		pr_warning("playpaq_wm8510: "
+			   "Failed to set CODEC DAI format (%d)\n",
+			   ret);
+		return ret;
+	}
+	ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+	if (ret < 0) {
+		pr_warning("playpaq_wm8510: "
+			   "Failed to set CPU DAI format (%d)\n",
+			   ret);
+		return ret;
+	}
+
+
+	/*
+	 * Set CPU clock configuration
+	 */
+#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+	cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai);
+	pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n",
+		 cd.cmr_div, cd.period);
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);
+	if (ret < 0) {
+		pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
+			   ret);
+		return ret;
+	}
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,
+					  cd.period);
+	if (ret < 0) {
+		pr_warning("playpaq_wm8510: "
+			   "Failed to set CPU transmit period (%d)\n",
+			   ret);
+		return ret;
+	}
+#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
+
+
+	/*
+	 * Set CODEC clock configuration
+	 */
+	pr_debug("playpaq_wm8510: "
+		 "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n",
+		 clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div);
+
+
+#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
+	if (ret < 0) {
+		pr_warning
+		    ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n",
+		     ret);
+		return ret;
+	}
+#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
+
+
+	ret = snd_soc_dai_set_pll(codec_dai, 0, 0,
+					 clk_get_rate(CODEC_CLK), pll_out);
+	if (ret < 0) {
+		pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n",
+			   ret);
+		return ret;
+	}
+
+
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
+	if (ret < 0) {
+		pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n",
+			   ret);
+		return ret;
+	}
+
+
+	return 0;
+}
+
+
+
+static struct snd_soc_ops playpaq_wm8510_ops = {
+	.hw_params = playpaq_wm8510_hw_params,
+};
+
+
+
+static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("Int Mic", NULL),
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+
+
+static const struct snd_soc_dapm_route intercon[] = {
+	/* speaker connected to SPKOUT */
+	{"Ext Spk", NULL, "SPKOUTP"},
+	{"Ext Spk", NULL, "SPKOUTN"},
+
+	{"Mic Bias", NULL, "Int Mic"},
+	{"MICN", NULL, "Mic Bias"},
+	{"MICP", NULL, "Mic Bias"},
+};
+
+
+
+static int playpaq_wm8510_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int i;
+
+	/*
+	 * Add DAPM widgets
+	 */
+	for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)
+		snd_soc_dapm_new_control(codec, &playpaq_dapm_widgets[i]);
+
+
+
+	/*
+	 * Setup audio path interconnects
+	 */
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+
+
+	/* always connected pins */
+	snd_soc_dapm_enable_pin(codec, "Int Mic");
+	snd_soc_dapm_enable_pin(codec, "Ext Spk");
+	snd_soc_dapm_sync(codec);
+
+
+
+	/* Make CSB show PLL rate */
+	snd_soc_dai_set_clkdiv(rtd->codec_dai, WM8510_OPCLKDIV,
+				       WM8510_OPCLKDIV_1 | 4);
+
+	return 0;
+}
+
+
+
+static struct snd_soc_dai_link playpaq_wm8510_dai = {
+	.name = "WM8510",
+	.stream_name = "WM8510 PCM",
+	.cpu_dai_name= "atmel-ssc-dai.0",
+	.platform_name = "atmel-pcm-audio",
+	.codec_name = "wm8510-codec.0-0x1a",
+	.codec_dai_name = "wm8510-hifi",
+	.init = playpaq_wm8510_init,
+	.ops = &playpaq_wm8510_ops,
+};
+
+
+
+static struct snd_soc_card snd_soc_playpaq = {
+	.name = "LRS_PlayPaq_WM8510",
+	.dai_link = &playpaq_wm8510_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *playpaq_snd_device;
+
+
+static int __init playpaq_asoc_init(void)
+{
+	int ret = 0;
+
+	/*
+	 * Configure MCLK for WM8510
+	 */
+	_gclk0 = clk_get(NULL, "gclk0");
+	if (IS_ERR(_gclk0)) {
+		_gclk0 = NULL;
+		goto err_gclk0;
+	}
+	_pll0 = clk_get(NULL, "pll0");
+	if (IS_ERR(_pll0)) {
+		_pll0 = NULL;
+		goto err_pll0;
+	}
+	if (clk_set_parent(_gclk0, _pll0)) {
+		pr_warning("snd-soc-playpaq: "
+			   "Failed to set PLL0 as parent for DAC clock\n");
+		goto err_set_clk;
+	}
+	clk_set_rate(CODEC_CLK, 12000000);
+	clk_enable(CODEC_CLK);
+
+#if defined CONFIG_AT32_ENHANCED_PORTMUX
+	at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);
+#endif
+
+
+	/*
+	 * Create and register platform device
+	 */
+	playpaq_snd_device = platform_device_alloc("soc-audio", 0);
+	if (playpaq_snd_device == NULL) {
+		ret = -ENOMEM;
+		goto err_device_alloc;
+	}
+
+	platform_set_drvdata(playpaq_snd_device, &snd_soc_playpaq);
+
+	ret = platform_device_add(playpaq_snd_device);
+	if (ret) {
+		pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
+			   ret);
+		goto err_device_add;
+	}
+
+	return 0;
+
+
+err_device_add:
+	if (playpaq_snd_device != NULL) {
+		platform_device_put(playpaq_snd_device);
+		playpaq_snd_device = NULL;
+	}
+err_device_alloc:
+err_set_clk:
+	if (_pll0 != NULL) {
+		clk_put(_pll0);
+		_pll0 = NULL;
+	}
+err_pll0:
+	if (_gclk0 != NULL) {
+		clk_put(_gclk0);
+		_gclk0 = NULL;
+	}
+	return ret;
+}
+
+
+static void __exit playpaq_asoc_exit(void)
+{
+	if (_gclk0 != NULL) {
+		clk_put(_gclk0);
+		_gclk0 = NULL;
+	}
+	if (_pll0 != NULL) {
+		clk_put(_pll0);
+		_pll0 = NULL;
+	}
+
+#if defined CONFIG_AT32_ENHANCED_PORTMUX
+	at32_free_pin(MCLK_PIN);
+#endif
+
+	platform_device_unregister(playpaq_snd_device);
+	playpaq_snd_device = NULL;
+}
+
+module_init(playpaq_asoc_init);
+module_exit(playpaq_asoc_exit);
+
+MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
+MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/atmel/sam9g20_wm8731.c b/linux/sound/soc/atmel/sam9g20_wm8731.c
new file mode 100644
index 0000000..e521ada
--- /dev/null
+++ b/linux/sound/soc/atmel/sam9g20_wm8731.c
@@ -0,0 +1,280 @@
+/*
+ * sam9g20_wm8731  --  SoC audio for AT91SAM9G20-based
+ * 			ATMEL AT91SAM9G20ek board.
+ *
+ *  Copyright (C) 2005 SAN People
+ *  Copyright (C) 2008 Atmel
+ *
+ * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
+ *
+ * Based on ati_b1_wm8731.c by:
+ * Frank Mandarino <fmandarino@endrelia.com>
+ * Copyright 2006 Endrelia Technologies Inc.
+ * Based on corgi.c by:
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+
+#include <linux/atmel-ssc.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+
+#include "../codecs/wm8731.h"
+#include "atmel-pcm.h"
+#include "atmel_ssc_dai.h"
+
+#define MCLK_RATE 12000000
+
+/*
+ * As shipped the board does not have inputs.  However, it is relatively
+ * straightforward to modify the board to hook them up so support is left
+ * in the driver.
+ */
+#undef ENABLE_MIC_INPUT
+
+static struct clk *mclk;
+
+static int at91sam9g20ek_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops at91sam9g20ek_ops = {
+	.hw_params = at91sam9g20ek_hw_params,
+};
+
+static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card,
+					enum snd_soc_bias_level level)
+{
+	static int mclk_on;
+	int ret = 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+		if (!mclk_on)
+			ret = clk_enable(mclk);
+		if (ret == 0)
+			mclk_on = 1;
+		break;
+
+	case SND_SOC_BIAS_OFF:
+	case SND_SOC_BIAS_STANDBY:
+		if (mclk_on)
+			clk_disable(mclk);
+		mclk_on = 0;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("Int Mic", NULL),
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+
+	/* speaker connected to LHPOUT */
+	{"Ext Spk", NULL, "LHPOUT"},
+
+	/* mic is connected to Mic Jack, with WM8731 Mic Bias */
+	{"MICIN", NULL, "Mic Bias"},
+	{"Mic Bias", NULL, "Int Mic"},
+};
+
+/*
+ * Logic for a wm8731 as connected on a at91sam9g20ek board.
+ */
+static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	int ret;
+
+	printk(KERN_DEBUG
+			"at91sam9g20ek_wm8731 "
+			": at91sam9g20ek_wm8731_init() called\n");
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
+		MCLK_RATE, SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "Failed to set WM8731 SYSCLK: %d\n", ret);
+		return ret;
+	}
+
+	/* Add specific widgets */
+	snd_soc_dapm_new_controls(codec, at91sam9g20ek_dapm_widgets,
+				  ARRAY_SIZE(at91sam9g20ek_dapm_widgets));
+	/* Set up specific audio path interconnects */
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	/* not connected */
+	snd_soc_dapm_nc_pin(codec, "RLINEIN");
+	snd_soc_dapm_nc_pin(codec, "LLINEIN");
+
+#ifdef ENABLE_MIC_INPUT
+	snd_soc_dapm_enable_pin(codec, "Int Mic");
+#else
+	snd_soc_dapm_nc_pin(codec, "Int Mic");
+#endif
+
+	/* always connected */
+	snd_soc_dapm_enable_pin(codec, "Ext Spk");
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_dai_link at91sam9g20ek_dai = {
+	.name = "WM8731",
+	.stream_name = "WM8731 PCM",
+	.cpu_dai_name = "atmel-ssc-dai.0",
+	.codec_dai_name = "wm8731-hifi",
+	.init = at91sam9g20ek_wm8731_init,
+	.platform_name = "atmel-pcm-audio",
+	.codec_name = "wm8731-codec.0-001b",
+	.ops = &at91sam9g20ek_ops,
+};
+
+static struct snd_soc_card snd_soc_at91sam9g20ek = {
+	.name = "AT91SAMG20-EK",
+	.dai_link = &at91sam9g20ek_dai,
+	.num_links = 1,
+	.set_bias_level = at91sam9g20ek_set_bias_level,
+};
+
+static struct platform_device *at91sam9g20ek_snd_device;
+
+static int __init at91sam9g20ek_init(void)
+{
+	struct clk *pllb;
+	int ret;
+
+	if (!(machine_is_at91sam9g20ek() || machine_is_at91sam9g20ek_2mmc()))
+		return -ENODEV;
+
+	ret = atmel_ssc_set_audio(0);
+	if (ret != 0) {
+		pr_err("Failed to set SSC 0 for audio: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * Codec MCLK is supplied by PCK0 - set it up.
+	 */
+	mclk = clk_get(NULL, "pck0");
+	if (IS_ERR(mclk)) {
+		printk(KERN_ERR "ASoC: Failed to get MCLK\n");
+		ret = PTR_ERR(mclk);
+		goto err;
+	}
+
+	pllb = clk_get(NULL, "pllb");
+	if (IS_ERR(pllb)) {
+		printk(KERN_ERR "ASoC: Failed to get PLLB\n");
+		ret = PTR_ERR(pllb);
+		goto err_mclk;
+	}
+	ret = clk_set_parent(mclk, pllb);
+	clk_put(pllb);
+	if (ret != 0) {
+		printk(KERN_ERR "ASoC: Failed to set MCLK parent\n");
+		goto err_mclk;
+	}
+
+	clk_set_rate(mclk, MCLK_RATE);
+
+	at91sam9g20ek_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!at91sam9g20ek_snd_device) {
+		printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+		ret = -ENOMEM;
+		goto err_mclk;
+	}
+
+	platform_set_drvdata(at91sam9g20ek_snd_device,
+			&snd_soc_at91sam9g20ek);
+
+	ret = platform_device_add(at91sam9g20ek_snd_device);
+	if (ret) {
+		printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+		goto err_device_add;
+	}
+
+	return ret;
+
+err_device_add:
+	platform_device_put(at91sam9g20ek_snd_device);
+err_mclk:
+	clk_put(mclk);
+	mclk = NULL;
+err:
+	return ret;
+}
+
+static void __exit at91sam9g20ek_exit(void)
+{
+	platform_device_unregister(at91sam9g20ek_snd_device);
+	at91sam9g20ek_snd_device = NULL;
+	clk_put(mclk);
+	mclk = NULL;
+}
+
+module_init(at91sam9g20ek_init);
+module_exit(at91sam9g20ek_exit);
+
+/* Module information */
+MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
+MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/atmel/snd-soc-afeb9260.c b/linux/sound/soc/atmel/snd-soc-afeb9260.c
new file mode 100644
index 0000000..86e0f85
--- /dev/null
+++ b/linux/sound/soc/atmel/snd-soc-afeb9260.c
@@ -0,0 +1,185 @@
+/*
+ * afeb9260.c  --  SoC audio for AFEB9260
+ *
+ * Copyright (C) 2009 Sergey Lapin <slapin@ossfans.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+
+#include <linux/atmel-ssc.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <linux/gpio.h>
+
+#include "../codecs/tlv320aic23.h"
+#include "atmel-pcm.h"
+#include "atmel_ssc_dai.h"
+
+#define CODEC_CLOCK 	12000000
+
+static int afeb9260_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int err;
+
+	/* Set codec DAI configuration */
+	err = snd_soc_dai_set_fmt(codec_dai,
+				  SND_SOC_DAIFMT_I2S|
+				  SND_SOC_DAIFMT_NB_IF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (err < 0) {
+		printk(KERN_ERR "can't set codec DAI configuration\n");
+		return err;
+	}
+
+	/* Set cpu DAI configuration */
+	err = snd_soc_dai_set_fmt(cpu_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_IF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (err < 0) {
+		printk(KERN_ERR "can't set cpu DAI configuration\n");
+		return err;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	err =
+	    snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN);
+
+	if (err < 0) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return err;
+	}
+
+	return err;
+}
+
+static struct snd_soc_ops afeb9260_ops = {
+	.hw_params = afeb9260_hw_params,
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"Headphone Jack", NULL, "LHPOUT"},
+	{"Headphone Jack", NULL, "RHPOUT"},
+
+	{"LLINEIN", NULL, "Line In"},
+	{"RLINEIN", NULL, "Line In"},
+
+	{"MICIN", NULL, "Mic Jack"},
+};
+
+static int afeb9260_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	/* Add afeb9260 specific widgets */
+	snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets,
+				  ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+	/* Set up afeb9260 specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+	snd_soc_dapm_enable_pin(codec, "Line In");
+	snd_soc_dapm_enable_pin(codec, "Mic Jack");
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link afeb9260_dai = {
+	.name = "TLV320AIC23",
+	.stream_name = "AIC23",
+	.cpu_dai_name = "atmel-ssc-dai.0",
+	.codec_dai_name = "tlv320aic23-hifi",
+	.platform_name = "atmel_pcm-audio",
+	.codec_name = "tlv320aic23-codec.0-0x1a",
+	.init = afeb9260_tlv320aic23_init,
+	.ops = &afeb9260_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_machine_afeb9260 = {
+	.name = "AFEB9260",
+	.dai_link = &afeb9260_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *afeb9260_snd_device;
+
+static int __init afeb9260_soc_init(void)
+{
+	int err;
+	struct device *dev;
+
+	if (!(machine_is_afeb9260()))
+		return -ENODEV;
+
+
+	afeb9260_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!afeb9260_snd_device) {
+		printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(afeb9260_snd_device, &snd_soc_machine_afeb9260);
+	err = platform_device_add(afeb9260_snd_device);
+	if (err)
+		goto err1;
+
+	dev = &afeb9260_snd_device->dev;
+
+	return 0;
+err1:
+	platform_device_put(afeb9260_snd_device);
+	return err;
+}
+
+static void __exit afeb9260_soc_exit(void)
+{
+	platform_device_unregister(afeb9260_snd_device);
+}
+
+module_init(afeb9260_soc_init);
+module_exit(afeb9260_soc_exit);
+
+MODULE_AUTHOR("Sergey Lapin <slapin@ossfans.org>");
+MODULE_DESCRIPTION("ALSA SoC for AFEB9260");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/au1x/Kconfig b/linux/sound/soc/au1x/Kconfig
new file mode 100644
index 0000000..4b67140
--- /dev/null
+++ b/linux/sound/soc/au1x/Kconfig
@@ -0,0 +1,34 @@
+##
+## Au1200/Au1550 PSC + DBDMA
+##
+config SND_SOC_AU1XPSC
+	tristate "SoC Audio for Au1200/Au1250/Au1550"
+	depends on SOC_AU1200 || SOC_AU1550
+	help
+	  This option enables support for the Programmable Serial
+	  Controllers in AC97 and I2S mode, and the Descriptor-Based DMA
+	  Controller (DBDMA) as found on the Au1200/Au1250/Au1550 SoC.
+
+config SND_SOC_AU1XPSC_I2S
+	tristate
+
+config SND_SOC_AU1XPSC_AC97
+	tristate
+	select AC97_BUS
+	select SND_AC97_CODEC
+	select SND_SOC_AC97_BUS
+
+
+##
+## Boards
+##
+config SND_SOC_DB1200
+	tristate "DB1200 AC97+I2S audio support"
+	depends on SND_SOC_AU1XPSC
+	select SND_SOC_AU1XPSC_AC97
+	select SND_SOC_AC97_CODEC
+	select SND_SOC_AU1XPSC_I2S
+	select SND_SOC_WM8731
+	help
+	  Select this option to enable audio (AC97 or I2S) on the
+	  Alchemy/AMD/RMI DB1200 demoboard.
diff --git a/linux/sound/soc/au1x/Makefile b/linux/sound/soc/au1x/Makefile
new file mode 100644
index 0000000..1687307
--- /dev/null
+++ b/linux/sound/soc/au1x/Makefile
@@ -0,0 +1,13 @@
+# Au1200/Au1550 PSC audio
+snd-soc-au1xpsc-dbdma-objs := dbdma2.o
+snd-soc-au1xpsc-i2s-objs := psc-i2s.o
+snd-soc-au1xpsc-ac97-objs := psc-ac97.o
+
+obj-$(CONFIG_SND_SOC_AU1XPSC) += snd-soc-au1xpsc-dbdma.o
+obj-$(CONFIG_SND_SOC_AU1XPSC_I2S) += snd-soc-au1xpsc-i2s.o
+obj-$(CONFIG_SND_SOC_AU1XPSC_AC97) += snd-soc-au1xpsc-ac97.o
+
+# Boards
+snd-soc-db1200-objs := db1200.o
+
+obj-$(CONFIG_SND_SOC_DB1200) += snd-soc-db1200.o
diff --git a/linux/sound/soc/au1x/db1200.c b/linux/sound/soc/au1x/db1200.c
new file mode 100644
index 0000000..b62fcd3
--- /dev/null
+++ b/linux/sound/soc/au1x/db1200.c
@@ -0,0 +1,130 @@
+/*
+ * DB1200 ASoC audio fabric support code.
+ *
+ * (c) 2008-9 Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+#include <asm/mach-au1x00/au1xxx_dbdma.h>
+#include <asm/mach-db1x00/bcsr.h>
+
+#include "../codecs/wm8731.h"
+#include "psc.h"
+
+/*-------------------------  AC97 PART  ---------------------------*/
+
+static struct snd_soc_dai_link db1200_ac97_dai = {
+	.name		= "AC97",
+	.stream_name	= "AC97 HiFi",
+	.codec_dai_name	= "ac97-hifi",
+	.cpu_dai_name	= "au1xpsc_ac97.1",
+	.platform_name	= "au1xpsc-pcm.1",
+	.codec_name	= "ac97-codec.1",
+};
+
+static struct snd_soc_card db1200_ac97_machine = {
+	.name		= "DB1200_AC97",
+	.dai_link	= &db1200_ac97_dai,
+	.num_links	= 1,
+};
+
+/*-------------------------  I2S PART  ---------------------------*/
+
+static int db1200_i2s_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* WM8731 has its own 12MHz crystal */
+	snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
+				12000000, SND_SOC_CLOCK_IN);
+
+	/* codec is bitclock and lrclk master */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_LEFT_J |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		goto out;
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_LEFT_J |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		goto out;
+
+	ret = 0;
+out:
+	return ret;
+}
+
+static struct snd_soc_ops db1200_i2s_wm8731_ops = {
+	.startup	= db1200_i2s_startup,
+};
+
+static struct snd_soc_dai_link db1200_i2s_dai = {
+	.name		= "WM8731",
+	.stream_name	= "WM8731 PCM",
+	.codec_dai_name	= "wm8731-hifi",
+	.cpu_dai_name	= "au1xpsc_i2s.1",
+	.platform_name	= "au1xpsc-pcm.1",
+	.codec_name	= "wm8731-codec.0-001b",
+	.ops		= &db1200_i2s_wm8731_ops,
+};
+
+static struct snd_soc_card db1200_i2s_machine = {
+	.name		= "DB1200_I2S",
+	.dai_link	= &db1200_i2s_dai,
+	.num_links	= 1,
+};
+
+/*-------------------------  COMMON PART  ---------------------------*/
+
+static struct platform_device *db1200_asoc_dev;
+
+static int __init db1200_audio_load(void)
+{
+	int ret;
+
+	ret = -ENOMEM;
+	db1200_asoc_dev = platform_device_alloc("soc-audio", 1); /* PSC1 */
+	if (!db1200_asoc_dev)
+		goto out;
+
+	/* DB1200 board setup set PSC1MUX to preferred audio device */
+	if (bcsr_read(BCSR_RESETS) & BCSR_RESETS_PSC1MUX)
+		platform_set_drvdata(db1200_asoc_dev, &db1200_i2s_machine);
+	else
+		platform_set_drvdata(db1200_asoc_dev, &db1200_ac97_machine);
+
+	ret = platform_device_add(db1200_asoc_dev);
+
+	if (ret) {
+		platform_device_put(db1200_asoc_dev);
+		db1200_asoc_dev = NULL;
+	}
+out:
+	return ret;
+}
+
+static void __exit db1200_audio_unload(void)
+{
+	platform_device_unregister(db1200_asoc_dev);
+}
+
+module_init(db1200_audio_load);
+module_exit(db1200_audio_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("DB1200 ASoC audio support");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/linux/sound/soc/au1x/dbdma2.c b/linux/sound/soc/au1x/dbdma2.c
new file mode 100644
index 0000000..10fdd28
--- /dev/null
+++ b/linux/sound/soc/au1x/dbdma2.c
@@ -0,0 +1,460 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ *	Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * DMA glue for Au1x-PSC audio.
+ *
+ */
+
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_dbdma.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+
+#include "psc.h"
+
+/*#define PCM_DEBUG*/
+
+#define MSG(x...)	printk(KERN_INFO "au1xpsc_pcm: " x)
+#ifdef PCM_DEBUG
+#define DBG		MSG
+#else
+#define DBG(x...)	do {} while (0)
+#endif
+
+struct au1xpsc_audio_dmadata {
+	/* DDMA control data */
+	unsigned int ddma_id;		/* DDMA direction ID for this PSC */
+	u32 ddma_chan;			/* DDMA context */
+
+	/* PCM context (for irq handlers) */
+	struct snd_pcm_substream *substream;
+	unsigned long curr_period;	/* current segment DDMA is working on */
+	unsigned long q_period;		/* queue period(s) */
+	dma_addr_t dma_area;		/* address of queued DMA area */
+	dma_addr_t dma_area_s;		/* start address of DMA area */
+	unsigned long pos;		/* current byte position being played */
+	unsigned long periods;		/* number of SG segments in total */
+	unsigned long period_bytes;	/* size in bytes of one SG segment */
+
+	/* runtime data */
+	int msbits;
+};
+
+/*
+ * These settings are somewhat okay, at least on my machine audio plays
+ * almost skip-free. Especially the 64kB buffer seems to help a LOT.
+ */
+#define AU1XPSC_PERIOD_MIN_BYTES	1024
+#define AU1XPSC_BUFFER_MIN_BYTES	65536
+
+#define AU1XPSC_PCM_FMTS					\
+	(SNDRV_PCM_FMTBIT_S8     | SNDRV_PCM_FMTBIT_U8 |	\
+	 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |	\
+	 SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE |	\
+	 SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE |	\
+	 SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE |	\
+	 0)
+
+/* PCM hardware DMA capabilities - platform specific */
+static const struct snd_pcm_hardware au1xpsc_pcm_hardware = {
+	.info		  = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+			    SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH,
+	.formats	  = AU1XPSC_PCM_FMTS,
+	.period_bytes_min = AU1XPSC_PERIOD_MIN_BYTES,
+	.period_bytes_max = 4096 * 1024 - 1,
+	.periods_min	  = 2,
+	.periods_max	  = 4096,	/* 2 to as-much-as-you-like */
+	.buffer_bytes_max = 4096 * 1024 - 1,
+	.fifo_size	  = 16,		/* fifo entries of AC97/I2S PSC */
+};
+
+static void au1x_pcm_queue_tx(struct au1xpsc_audio_dmadata *cd)
+{
+	au1xxx_dbdma_put_source(cd->ddma_chan, cd->dma_area,
+				cd->period_bytes, DDMA_FLAGS_IE);
+
+	/* update next-to-queue period */
+	++cd->q_period;
+	cd->dma_area += cd->period_bytes;
+	if (cd->q_period >= cd->periods) {
+		cd->q_period = 0;
+		cd->dma_area = cd->dma_area_s;
+	}
+}
+
+static void au1x_pcm_queue_rx(struct au1xpsc_audio_dmadata *cd)
+{
+	au1xxx_dbdma_put_dest(cd->ddma_chan, cd->dma_area,
+			      cd->period_bytes, DDMA_FLAGS_IE);
+
+	/* update next-to-queue period */
+	++cd->q_period;
+	cd->dma_area += cd->period_bytes;
+	if (cd->q_period >= cd->periods) {
+		cd->q_period = 0;
+		cd->dma_area = cd->dma_area_s;
+	}
+}
+
+static void au1x_pcm_dmatx_cb(int irq, void *dev_id)
+{
+	struct au1xpsc_audio_dmadata *cd = dev_id;
+
+	cd->pos += cd->period_bytes;
+	if (++cd->curr_period >= cd->periods) {
+		cd->pos = 0;
+		cd->curr_period = 0;
+	}
+	snd_pcm_period_elapsed(cd->substream);
+	au1x_pcm_queue_tx(cd);
+}
+
+static void au1x_pcm_dmarx_cb(int irq, void *dev_id)
+{
+	struct au1xpsc_audio_dmadata *cd = dev_id;
+
+	cd->pos += cd->period_bytes;
+	if (++cd->curr_period >= cd->periods) {
+		cd->pos = 0;
+		cd->curr_period = 0;
+	}
+	snd_pcm_period_elapsed(cd->substream);
+	au1x_pcm_queue_rx(cd);
+}
+
+static void au1x_pcm_dbdma_free(struct au1xpsc_audio_dmadata *pcd)
+{
+	if (pcd->ddma_chan) {
+		au1xxx_dbdma_stop(pcd->ddma_chan);
+		au1xxx_dbdma_reset(pcd->ddma_chan);
+		au1xxx_dbdma_chan_free(pcd->ddma_chan);
+		pcd->ddma_chan = 0;
+		pcd->msbits = 0;
+	}
+}
+
+/* in case of missing DMA ring or changed TX-source / RX-dest bit widths,
+ * allocate (or reallocate) a 2-descriptor DMA ring with bit depth according
+ * to ALSA-supplied sample depth.  This is due to limitations in the dbdma api
+ * (cannot adjust source/dest widths of already allocated descriptor ring).
+ */
+static int au1x_pcm_dbdma_realloc(struct au1xpsc_audio_dmadata *pcd,
+				 int stype, int msbits)
+{
+	/* DMA only in 8/16/32 bit widths */
+	if (msbits == 24)
+		msbits = 32;
+
+	/* check current config: correct bits and descriptors allocated? */
+	if ((pcd->ddma_chan) && (msbits == pcd->msbits))
+		goto out;	/* all ok! */
+
+	au1x_pcm_dbdma_free(pcd);
+
+	if (stype == PCM_RX)
+		pcd->ddma_chan = au1xxx_dbdma_chan_alloc(pcd->ddma_id,
+					DSCR_CMD0_ALWAYS,
+					au1x_pcm_dmarx_cb, (void *)pcd);
+	else
+		pcd->ddma_chan = au1xxx_dbdma_chan_alloc(DSCR_CMD0_ALWAYS,
+					pcd->ddma_id,
+					au1x_pcm_dmatx_cb, (void *)pcd);
+
+	if (!pcd->ddma_chan)
+		return -ENOMEM;
+
+	au1xxx_dbdma_set_devwidth(pcd->ddma_chan, msbits);
+	au1xxx_dbdma_ring_alloc(pcd->ddma_chan, 2);
+
+	pcd->msbits = msbits;
+
+	au1xxx_dbdma_stop(pcd->ddma_chan);
+	au1xxx_dbdma_reset(pcd->ddma_chan);
+
+out:
+	return 0;
+}
+
+static inline struct au1xpsc_audio_dmadata *to_dmadata(struct snd_pcm_substream *ss)
+{
+	struct snd_soc_pcm_runtime *rtd = ss->private_data;
+	struct au1xpsc_audio_dmadata *pcd =
+				snd_soc_platform_get_drvdata(rtd->platform);
+	return &pcd[SUBSTREAM_TYPE(ss)];
+}
+
+static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct au1xpsc_audio_dmadata *pcd;
+	int stype, ret;
+
+	ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+	if (ret < 0)
+		goto out;
+
+	stype = SUBSTREAM_TYPE(substream);
+	pcd = to_dmadata(substream);
+
+	DBG("runtime->dma_area = 0x%08lx dma_addr_t = 0x%08lx dma_size = %d "
+	    "runtime->min_align %d\n",
+		(unsigned long)runtime->dma_area,
+		(unsigned long)runtime->dma_addr, runtime->dma_bytes,
+		runtime->min_align);
+
+	DBG("bits %d  frags %d  frag_bytes %d  is_rx %d\n", params->msbits,
+		params_periods(params), params_period_bytes(params), stype);
+
+	ret = au1x_pcm_dbdma_realloc(pcd, stype, params->msbits);
+	if (ret) {
+		MSG("DDMA channel (re)alloc failed!\n");
+		goto out;
+	}
+
+	pcd->substream = substream;
+	pcd->period_bytes = params_period_bytes(params);
+	pcd->periods = params_periods(params);
+	pcd->dma_area_s = pcd->dma_area = runtime->dma_addr;
+	pcd->q_period = 0;
+	pcd->curr_period = 0;
+	pcd->pos = 0;
+
+	ret = 0;
+out:
+	return ret;
+}
+
+static int au1xpsc_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int au1xpsc_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct au1xpsc_audio_dmadata *pcd = to_dmadata(substream);
+
+	au1xxx_dbdma_reset(pcd->ddma_chan);
+
+	if (SUBSTREAM_TYPE(substream) == PCM_RX) {
+		au1x_pcm_queue_rx(pcd);
+		au1x_pcm_queue_rx(pcd);
+	} else {
+		au1x_pcm_queue_tx(pcd);
+		au1x_pcm_queue_tx(pcd);
+	}
+
+	return 0;
+}
+
+static int au1xpsc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	u32 c = to_dmadata(substream)->ddma_chan;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		au1xxx_dbdma_start(c);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		au1xxx_dbdma_stop(c);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t
+au1xpsc_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	return bytes_to_frames(substream->runtime, to_dmadata(substream)->pos);
+}
+
+static int au1xpsc_pcm_open(struct snd_pcm_substream *substream)
+{
+	snd_soc_set_runtime_hwparams(substream, &au1xpsc_pcm_hardware);
+	return 0;
+}
+
+static int au1xpsc_pcm_close(struct snd_pcm_substream *substream)
+{
+	au1x_pcm_dbdma_free(to_dmadata(substream));
+	return 0;
+}
+
+static struct snd_pcm_ops au1xpsc_pcm_ops = {
+	.open		= au1xpsc_pcm_open,
+	.close		= au1xpsc_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= au1xpsc_pcm_hw_params,
+	.hw_free	= au1xpsc_pcm_hw_free,
+	.prepare	= au1xpsc_pcm_prepare,
+	.trigger	= au1xpsc_pcm_trigger,
+	.pointer	= au1xpsc_pcm_pointer,
+};
+
+static void au1xpsc_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int au1xpsc_pcm_new(struct snd_card *card,
+			   struct snd_soc_dai *dai,
+			   struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+		card->dev, AU1XPSC_BUFFER_MIN_BYTES, (4096 * 1024) - 1);
+
+	return 0;
+}
+
+/* au1xpsc audio platform */
+struct snd_soc_platform_driver au1xpsc_soc_platform = {
+	.ops		= &au1xpsc_pcm_ops,
+	.pcm_new	= au1xpsc_pcm_new,
+	.pcm_free	= au1xpsc_pcm_free_dma_buffers,
+};
+
+static int __devinit au1xpsc_pcm_drvprobe(struct platform_device *pdev)
+{
+	struct au1xpsc_audio_dmadata *dmadata;
+	struct resource *r;
+	int ret;
+
+	dmadata = kzalloc(2 * sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL);
+	if (!dmadata)
+		return -ENOMEM;
+
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!r) {
+		ret = -ENODEV;
+		goto out1;
+	}
+	dmadata[PCM_TX].ddma_id = r->start;
+
+	/* RX DMA */
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!r) {
+		ret = -ENODEV;
+		goto out1;
+	}
+	dmadata[PCM_RX].ddma_id = r->start;
+
+	platform_set_drvdata(pdev, dmadata);
+
+	ret = snd_soc_register_platform(&pdev->dev, &au1xpsc_soc_platform);
+	if (!ret)
+		return ret;
+
+out1:
+	kfree(dmadata);
+	return ret;
+}
+
+static int __devexit au1xpsc_pcm_drvremove(struct platform_device *pdev)
+{
+	struct au1xpsc_audio_dmadata *dmadata = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_platform(&pdev->dev);
+	kfree(dmadata);
+
+	return 0;
+}
+
+static struct platform_driver au1xpsc_pcm_driver = {
+	.driver	= {
+		.name	= "au1xpsc-pcm",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= au1xpsc_pcm_drvprobe,
+	.remove		= __devexit_p(au1xpsc_pcm_drvremove),
+};
+
+static int __init au1xpsc_audio_dbdma_load(void)
+{
+	return platform_driver_register(&au1xpsc_pcm_driver);
+}
+
+static void __exit au1xpsc_audio_dbdma_unload(void)
+{
+	platform_driver_unregister(&au1xpsc_pcm_driver);
+}
+
+module_init(au1xpsc_audio_dbdma_load);
+module_exit(au1xpsc_audio_dbdma_unload);
+
+
+struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev)
+{
+	struct resource *res, *r;
+	struct platform_device *pd;
+	int id[2];
+	int ret;
+
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!r)
+		return NULL;
+	id[0] = r->start;
+
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!r)
+		return NULL;
+	id[1] = r->start;
+
+	res = kzalloc(sizeof(struct resource) * 2, GFP_KERNEL);
+	if (!res)
+		return NULL;
+
+	res[0].start = res[0].end = id[0];
+	res[1].start = res[1].end = id[1];
+	res[0].flags = res[1].flags = IORESOURCE_DMA;
+
+	pd = platform_device_alloc("au1xpsc-pcm", pdev->id);
+	if (!pd)
+		goto out;
+
+	pd->resource = res;
+	pd->num_resources = 2;
+
+	ret = platform_device_add(pd);
+	if (!ret)
+		return pd;
+
+	platform_device_put(pd);
+out:
+	kfree(res);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(au1xpsc_pcm_add);
+
+void au1xpsc_pcm_destroy(struct platform_device *dmapd)
+{
+	if (dmapd)
+		platform_device_unregister(dmapd);
+}
+EXPORT_SYMBOL_GPL(au1xpsc_pcm_destroy);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au12x0/Au1550 PSC Audio DMA driver");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/linux/sound/soc/au1x/psc-ac97.c b/linux/sound/soc/au1x/psc-ac97.c
new file mode 100644
index 0000000..d0db66f
--- /dev/null
+++ b/linux/sound/soc/au1x/psc-ac97.c
@@ -0,0 +1,516 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2009 MSC Vertriebsges.m.b.H.,
+ *	Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Au1xxx-PSC AC97 glue.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/suspend.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+
+#include "psc.h"
+
+/* how often to retry failed codec register reads/writes */
+#define AC97_RW_RETRIES	5
+
+#define AC97_DIR	\
+	(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
+
+#define AC97_RATES	\
+	SNDRV_PCM_RATE_8000_48000
+
+#define AC97_FMTS	\
+	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3BE)
+
+#define AC97PCR_START(stype)	\
+	((stype) == PCM_TX ? PSC_AC97PCR_TS : PSC_AC97PCR_RS)
+#define AC97PCR_STOP(stype)	\
+	((stype) == PCM_TX ? PSC_AC97PCR_TP : PSC_AC97PCR_RP)
+#define AC97PCR_CLRFIFO(stype)	\
+	((stype) == PCM_TX ? PSC_AC97PCR_TC : PSC_AC97PCR_RC)
+
+#define AC97STAT_BUSY(stype)	\
+	((stype) == PCM_TX ? PSC_AC97STAT_TB : PSC_AC97STAT_RB)
+
+/* instance data. There can be only one, MacLeod!!!! */
+static struct au1xpsc_audio_data *au1xpsc_ac97_workdata;
+
+#if 0
+
+/* this could theoretically work, but ac97->bus->card->private_data can be NULL
+ * when snd_ac97_mixer() is called; I don't know if the rest further down the
+ * chain are always valid either.
+ */
+static inline struct au1xpsc_audio_data *ac97_to_pscdata(struct snd_ac97 *x)
+{
+	struct snd_soc_card *c = x->bus->card->private_data;
+	return snd_soc_dai_get_drvdata(c->rtd->cpu_dai);
+}
+
+#else
+
+#define ac97_to_pscdata(x)	au1xpsc_ac97_workdata
+
+#endif
+
+/* AC97 controller reads codec register */
+static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97,
+					unsigned short reg)
+{
+	struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
+	unsigned short retry, tmo;
+	unsigned long data;
+
+	au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
+	au_sync();
+
+	retry = AC97_RW_RETRIES;
+	do {
+		mutex_lock(&pscdata->lock);
+
+		au_writel(PSC_AC97CDC_RD | PSC_AC97CDC_INDX(reg),
+			  AC97_CDC(pscdata));
+		au_sync();
+
+		tmo = 20;
+		do {
+			udelay(21);
+			if (au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)
+				break;
+		} while (--tmo);
+
+		data = au_readl(AC97_CDC(pscdata));
+
+		au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
+		au_sync();
+
+		mutex_unlock(&pscdata->lock);
+
+		if (reg != ((data >> 16) & 0x7f))
+			tmo = 1;	/* wrong register, try again */
+
+	} while (--retry && !tmo);
+
+	return retry ? data & 0xffff : 0xffff;
+}
+
+/* AC97 controller writes to codec register */
+static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+				unsigned short val)
+{
+	struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
+	unsigned int tmo, retry;
+
+	au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
+	au_sync();
+
+	retry = AC97_RW_RETRIES;
+	do {
+		mutex_lock(&pscdata->lock);
+
+		au_writel(PSC_AC97CDC_INDX(reg) | (val & 0xffff),
+			  AC97_CDC(pscdata));
+		au_sync();
+
+		tmo = 20;
+		do {
+			udelay(21);
+			if (au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)
+				break;
+		} while (--tmo);
+
+		au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
+		au_sync();
+
+		mutex_unlock(&pscdata->lock);
+	} while (--retry && !tmo);
+}
+
+/* AC97 controller asserts a warm reset */
+static void au1xpsc_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+	struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
+
+	au_writel(PSC_AC97RST_SNC, AC97_RST(pscdata));
+	au_sync();
+	msleep(10);
+	au_writel(0, AC97_RST(pscdata));
+	au_sync();
+}
+
+static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+	struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97);
+	int i;
+
+	/* disable PSC during cold reset */
+	au_writel(0, AC97_CFG(au1xpsc_ac97_workdata));
+	au_sync();
+	au_writel(PSC_CTRL_DISABLE, PSC_CTRL(pscdata));
+	au_sync();
+
+	/* issue cold reset */
+	au_writel(PSC_AC97RST_RST, AC97_RST(pscdata));
+	au_sync();
+	msleep(500);
+	au_writel(0, AC97_RST(pscdata));
+	au_sync();
+
+	/* enable PSC */
+	au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata));
+	au_sync();
+
+	/* wait for PSC to indicate it's ready */
+	i = 1000;
+	while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_SR)) && (--i))
+		msleep(1);
+
+	if (i == 0) {
+		printk(KERN_ERR "au1xpsc-ac97: PSC not ready!\n");
+		return;
+	}
+
+	/* enable the ac97 function */
+	au_writel(pscdata->cfg | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
+	au_sync();
+
+	/* wait for AC97 core to become ready */
+	i = 1000;
+	while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && (--i))
+		msleep(1);
+	if (i == 0)
+		printk(KERN_ERR "au1xpsc-ac97: AC97 ctrl not ready\n");
+}
+
+/* AC97 controller operations */
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read		= au1xpsc_ac97_read,
+	.write		= au1xpsc_ac97_write,
+	.reset		= au1xpsc_ac97_cold_reset,
+	.warm_reset	= au1xpsc_ac97_warm_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
+	unsigned long r, ro, stat;
+	int chans, t, stype = SUBSTREAM_TYPE(substream);
+
+	chans = params_channels(params);
+
+	r = ro = au_readl(AC97_CFG(pscdata));
+	stat = au_readl(AC97_STAT(pscdata));
+
+	/* already active? */
+	if (stat & (PSC_AC97STAT_TB | PSC_AC97STAT_RB)) {
+		/* reject parameters not currently set up */
+		if ((PSC_AC97CFG_GET_LEN(r) != params->msbits) ||
+		    (pscdata->rate != params_rate(params)))
+			return -EINVAL;
+	} else {
+
+		/* set sample bitdepth: REG[24:21]=(BITS-2)/2 */
+		r &= ~PSC_AC97CFG_LEN_MASK;
+		r |= PSC_AC97CFG_SET_LEN(params->msbits);
+
+		/* channels: enable slots for front L/R channel */
+		if (stype == PCM_TX) {
+			r &= ~PSC_AC97CFG_TXSLOT_MASK;
+			r |= PSC_AC97CFG_TXSLOT_ENA(3);
+			r |= PSC_AC97CFG_TXSLOT_ENA(4);
+		} else {
+			r &= ~PSC_AC97CFG_RXSLOT_MASK;
+			r |= PSC_AC97CFG_RXSLOT_ENA(3);
+			r |= PSC_AC97CFG_RXSLOT_ENA(4);
+		}
+
+		/* do we need to poke the hardware? */
+		if (!(r ^ ro))
+			goto out;
+
+		/* ac97 engine is about to be disabled */
+		mutex_lock(&pscdata->lock);
+
+		/* disable AC97 device controller first... */
+		au_writel(r & ~PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
+		au_sync();
+
+		/* ...wait for it... */
+		t = 100;
+		while ((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR) && --t)
+			msleep(1);
+
+		if (!t)
+			printk(KERN_ERR "PSC-AC97: can't disable!\n");
+
+		/* ...write config... */
+		au_writel(r, AC97_CFG(pscdata));
+		au_sync();
+
+		/* ...enable the AC97 controller again... */
+		au_writel(r | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
+		au_sync();
+
+		/* ...and wait for ready bit */
+		t = 100;
+		while ((!(au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && --t)
+			msleep(1);
+
+		if (!t)
+			printk(KERN_ERR "PSC-AC97: can't enable!\n");
+
+		mutex_unlock(&pscdata->lock);
+
+		pscdata->cfg = r;
+		pscdata->rate = params_rate(params);
+	}
+
+out:
+	return 0;
+}
+
+static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream,
+				int cmd, struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
+	int ret, stype = SUBSTREAM_TYPE(substream);
+
+	ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		au_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata));
+		au_sync();
+		au_writel(AC97PCR_START(stype), AC97_PCR(pscdata));
+		au_sync();
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		au_writel(AC97PCR_STOP(stype), AC97_PCR(pscdata));
+		au_sync();
+
+		while (au_readl(AC97_STAT(pscdata)) & AC97STAT_BUSY(stype))
+			asm volatile ("nop");
+
+		au_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata));
+		au_sync();
+
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+
+static int au1xpsc_ac97_probe(struct snd_soc_dai *dai)
+{
+	return au1xpsc_ac97_workdata ? 0 : -ENODEV;
+}
+
+static struct snd_soc_dai_ops au1xpsc_ac97_dai_ops = {
+	.trigger	= au1xpsc_ac97_trigger,
+	.hw_params	= au1xpsc_ac97_hw_params,
+};
+
+static const struct snd_soc_dai_driver au1xpsc_ac97_dai_template = {
+	.ac97_control		= 1,
+	.probe			= au1xpsc_ac97_probe,
+	.playback = {
+		.rates		= AC97_RATES,
+		.formats	= AC97_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.capture = {
+		.rates		= AC97_RATES,
+		.formats	= AC97_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.ops = &au1xpsc_ac97_dai_ops,
+};
+
+static int __devinit au1xpsc_ac97_drvprobe(struct platform_device *pdev)
+{
+	int ret;
+	struct resource *r;
+	unsigned long sel;
+	struct au1xpsc_audio_data *wd;
+
+	wd = kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL);
+	if (!wd)
+		return -ENOMEM;
+
+	mutex_init(&wd->lock);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r) {
+		ret = -ENODEV;
+		goto out0;
+	}
+
+	ret = -EBUSY;
+	if (!request_mem_region(r->start, resource_size(r), pdev->name))
+		goto out0;
+
+	wd->mmio = ioremap(r->start, resource_size(r));
+	if (!wd->mmio)
+		goto out1;
+
+	/* configuration: max dma trigger threshold, enable ac97 */
+	wd->cfg = PSC_AC97CFG_RT_FIFO8 | PSC_AC97CFG_TT_FIFO8 |
+		  PSC_AC97CFG_DE_ENABLE;
+
+	/* preserve PSC clock source set up by platform	 */
+	sel = au_readl(PSC_SEL(wd)) & PSC_SEL_CLK_MASK;
+	au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+	au_sync();
+	au_writel(0, PSC_SEL(wd));
+	au_sync();
+	au_writel(PSC_SEL_PS_AC97MODE | sel, PSC_SEL(wd));
+	au_sync();
+
+	/* name the DAI like this device instance ("au1xpsc-ac97.PSCINDEX") */
+	memcpy(&wd->dai_drv, &au1xpsc_ac97_dai_template,
+	       sizeof(struct snd_soc_dai_driver));
+	wd->dai_drv.name = dev_name(&pdev->dev);
+
+	platform_set_drvdata(pdev, wd);
+
+	ret = snd_soc_register_dai(&pdev->dev, &wd->dai_drv);
+	if (ret)
+		goto out1;
+
+	wd->dmapd = au1xpsc_pcm_add(pdev);
+	if (wd->dmapd) {
+		au1xpsc_ac97_workdata = wd;
+		return 0;
+	}
+
+	snd_soc_unregister_dai(&pdev->dev);
+out1:
+	release_mem_region(r->start, resource_size(r));
+out0:
+	kfree(wd);
+	return ret;
+}
+
+static int __devexit au1xpsc_ac97_drvremove(struct platform_device *pdev)
+{
+	struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);
+	struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	if (wd->dmapd)
+		au1xpsc_pcm_destroy(wd->dmapd);
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	/* disable PSC completely */
+	au_writel(0, AC97_CFG(wd));
+	au_sync();
+	au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+	au_sync();
+
+	iounmap(wd->mmio);
+	release_mem_region(r->start, resource_size(r));
+	kfree(wd);
+
+	au1xpsc_ac97_workdata = NULL;	/* MDEV */
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int au1xpsc_ac97_drvsuspend(struct device *dev)
+{
+	struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
+
+	/* save interesting registers and disable PSC */
+	wd->pm[0] = au_readl(PSC_SEL(wd));
+
+	au_writel(0, AC97_CFG(wd));
+	au_sync();
+	au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+	au_sync();
+
+	return 0;
+}
+
+static int au1xpsc_ac97_drvresume(struct device *dev)
+{
+	struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
+
+	/* restore PSC clock config */
+	au_writel(wd->pm[0] | PSC_SEL_PS_AC97MODE, PSC_SEL(wd));
+	au_sync();
+
+	/* after this point the ac97 core will cold-reset the codec.
+	 * During cold-reset the PSC is reinitialized and the last
+	 * configuration set up in hw_params() is restored.
+	 */
+	return 0;
+}
+
+static struct dev_pm_ops au1xpscac97_pmops = {
+	.suspend	= au1xpsc_ac97_drvsuspend,
+	.resume		= au1xpsc_ac97_drvresume,
+};
+
+#define AU1XPSCAC97_PMOPS &au1xpscac97_pmops
+
+#else
+
+#define AU1XPSCAC97_PMOPS NULL
+
+#endif
+
+static struct platform_driver au1xpsc_ac97_driver = {
+	.driver	= {
+		.name	= "au1xpsc_ac97",
+		.owner	= THIS_MODULE,
+		.pm	= AU1XPSCAC97_PMOPS,
+	},
+	.probe		= au1xpsc_ac97_drvprobe,
+	.remove		= __devexit_p(au1xpsc_ac97_drvremove),
+};
+
+static int __init au1xpsc_ac97_load(void)
+{
+	au1xpsc_ac97_workdata = NULL;
+	return platform_driver_register(&au1xpsc_ac97_driver);
+}
+
+static void __exit au1xpsc_ac97_unload(void)
+{
+	platform_driver_unregister(&au1xpsc_ac97_driver);
+}
+
+module_init(au1xpsc_ac97_load);
+module_exit(au1xpsc_ac97_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au12x0/Au1550 PSC AC97 ALSA ASoC audio driver");
+MODULE_AUTHOR("Manuel Lauss");
+
diff --git a/linux/sound/soc/au1x/psc-i2s.c b/linux/sound/soc/au1x/psc-i2s.c
new file mode 100644
index 0000000..fca0912
--- /dev/null
+++ b/linux/sound/soc/au1x/psc-i2s.c
@@ -0,0 +1,440 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ *	Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Au1xxx-PSC I2S glue.
+ *
+ * NOTE: so far only PSC slave mode (bit- and frameclock) is supported.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_psc.h>
+
+#include "psc.h"
+
+/* supported I2S DAI hardware formats */
+#define AU1XPSC_I2S_DAIFMT \
+	(SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J |	\
+	 SND_SOC_DAIFMT_NB_NF)
+
+/* supported I2S direction */
+#define AU1XPSC_I2S_DIR \
+	(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
+
+#define AU1XPSC_I2S_RATES \
+	SNDRV_PCM_RATE_8000_192000
+
+#define AU1XPSC_I2S_FMTS \
+	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+#define I2SSTAT_BUSY(stype)	\
+	((stype) == PCM_TX ? PSC_I2SSTAT_TB : PSC_I2SSTAT_RB)
+#define I2SPCR_START(stype)	\
+	((stype) == PCM_TX ? PSC_I2SPCR_TS : PSC_I2SPCR_RS)
+#define I2SPCR_STOP(stype)	\
+	((stype) == PCM_TX ? PSC_I2SPCR_TP : PSC_I2SPCR_RP)
+#define I2SPCR_CLRFIFO(stype)	\
+	((stype) == PCM_TX ? PSC_I2SPCR_TC : PSC_I2SPCR_RC)
+
+
+static int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+			       unsigned int fmt)
+{
+	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(cpu_dai);
+	unsigned long ct;
+	int ret;
+
+	ret = -EINVAL;
+
+	ct = pscdata->cfg;
+
+	ct &= ~(PSC_I2SCFG_XM | PSC_I2SCFG_MLJ);	/* left-justified */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		ct |= PSC_I2SCFG_XM;	/* enable I2S mode */
+		break;
+	case SND_SOC_DAIFMT_MSB:
+		break;
+	case SND_SOC_DAIFMT_LSB:
+		ct |= PSC_I2SCFG_MLJ;	/* LSB (right-) justified */
+		break;
+	default:
+		goto out;
+	}
+
+	ct &= ~(PSC_I2SCFG_BI | PSC_I2SCFG_WI);		/* IB-IF */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		ct |= PSC_I2SCFG_BI | PSC_I2SCFG_WI;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		ct |= PSC_I2SCFG_BI;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		ct |= PSC_I2SCFG_WI;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		break;
+	default:
+		goto out;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:	/* CODEC master */
+		ct |= PSC_I2SCFG_MS;	/* PSC I2S slave mode */
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:	/* CODEC slave */
+		ct &= ~PSC_I2SCFG_MS;	/* PSC I2S Master mode */
+		break;
+	default:
+		goto out;
+	}
+
+	pscdata->cfg = ct;
+	ret = 0;
+out:
+	return ret;
+}
+
+static int au1xpsc_i2s_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
+
+	int cfgbits;
+	unsigned long stat;
+
+	/* check if the PSC is already streaming data */
+	stat = au_readl(I2S_STAT(pscdata));
+	if (stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB)) {
+		/* reject parameters not currently set up in hardware */
+		cfgbits = au_readl(I2S_CFG(pscdata));
+		if ((PSC_I2SCFG_GET_LEN(cfgbits) != params->msbits) ||
+		    (params_rate(params) != pscdata->rate))
+			return -EINVAL;
+	} else {
+		/* set sample bitdepth */
+		pscdata->cfg &= ~(0x1f << 4);
+		pscdata->cfg |= PSC_I2SCFG_SET_LEN(params->msbits);
+		/* remember current rate for other stream */
+		pscdata->rate = params_rate(params);
+	}
+	return 0;
+}
+
+/* Configure PSC late:  on my devel systems the codec  is I2S master and
+ * supplies the i2sbitclock __AND__ i2sMclk (!) to the PSC unit.  ASoC
+ * uses aggressive PM and  switches the codec off  when it is not in use
+ * which also means the PSC unit doesn't get any clocks and is therefore
+ * dead. That's why this chunk here gets called from the trigger callback
+ * because I can be reasonably certain the codec is driving the clocks.
+ */
+static int au1xpsc_i2s_configure(struct au1xpsc_audio_data *pscdata)
+{
+	unsigned long tmo;
+
+	/* bring PSC out of sleep, and configure I2S unit */
+	au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata));
+	au_sync();
+
+	tmo = 1000000;
+	while (!(au_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_SR) && tmo)
+		tmo--;
+
+	if (!tmo)
+		goto psc_err;
+
+	au_writel(0, I2S_CFG(pscdata));
+	au_sync();
+	au_writel(pscdata->cfg | PSC_I2SCFG_DE_ENABLE, I2S_CFG(pscdata));
+	au_sync();
+
+	/* wait for I2S controller to become ready */
+	tmo = 1000000;
+	while (!(au_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_DR) && tmo)
+		tmo--;
+
+	if (tmo)
+		return 0;
+
+psc_err:
+	au_writel(0, I2S_CFG(pscdata));
+	au_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));
+	au_sync();
+	return -ETIMEDOUT;
+}
+
+static int au1xpsc_i2s_start(struct au1xpsc_audio_data *pscdata, int stype)
+{
+	unsigned long tmo, stat;
+	int ret;
+
+	ret = 0;
+
+	/* if both TX and RX are idle, configure the PSC  */
+	stat = au_readl(I2S_STAT(pscdata));
+	if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {
+		ret = au1xpsc_i2s_configure(pscdata);
+		if (ret)
+			goto out;
+	}
+
+	au_writel(I2SPCR_CLRFIFO(stype), I2S_PCR(pscdata));
+	au_sync();
+	au_writel(I2SPCR_START(stype), I2S_PCR(pscdata));
+	au_sync();
+
+	/* wait for start confirmation */
+	tmo = 1000000;
+	while (!(au_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)
+		tmo--;
+
+	if (!tmo) {
+		au_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));
+		au_sync();
+		ret = -ETIMEDOUT;
+	}
+out:
+	return ret;
+}
+
+static int au1xpsc_i2s_stop(struct au1xpsc_audio_data *pscdata, int stype)
+{
+	unsigned long tmo, stat;
+
+	au_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));
+	au_sync();
+
+	/* wait for stop confirmation */
+	tmo = 1000000;
+	while ((au_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)
+		tmo--;
+
+	/* if both TX and RX are idle, disable PSC */
+	stat = au_readl(I2S_STAT(pscdata));
+	if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {
+		au_writel(0, I2S_CFG(pscdata));
+		au_sync();
+		au_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));
+		au_sync();
+	}
+	return 0;
+}
+
+static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+			       struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
+	int ret, stype = SUBSTREAM_TYPE(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		ret = au1xpsc_i2s_start(pscdata, stype);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		ret = au1xpsc_i2s_stop(pscdata, stype);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+
+static struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = {
+	.trigger	= au1xpsc_i2s_trigger,
+	.hw_params	= au1xpsc_i2s_hw_params,
+	.set_fmt	= au1xpsc_i2s_set_fmt,
+};
+
+static const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = {
+	.playback = {
+		.rates		= AU1XPSC_I2S_RATES,
+		.formats	= AU1XPSC_I2S_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 8,	/* 2 without external help */
+	},
+	.capture = {
+		.rates		= AU1XPSC_I2S_RATES,
+		.formats	= AU1XPSC_I2S_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 8,	/* 2 without external help */
+	},
+	.ops = &au1xpsc_i2s_dai_ops,
+};
+
+static int __devinit au1xpsc_i2s_drvprobe(struct platform_device *pdev)
+{
+	struct resource *r;
+	unsigned long sel;
+	int ret;
+	struct au1xpsc_audio_data *wd;
+
+	wd = kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL);
+	if (!wd)
+		return -ENOMEM;
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r) {
+		ret = -ENODEV;
+		goto out0;
+	}
+
+	ret = -EBUSY;
+	if (!request_mem_region(r->start, resource_size(r), pdev->name))
+		goto out0;
+
+	wd->mmio = ioremap(r->start, resource_size(r));
+	if (!wd->mmio)
+		goto out1;
+
+	/* preserve PSC clock source set up by platform (dev.platform_data
+	 * is already occupied by soc layer)
+	 */
+	sel = au_readl(PSC_SEL(wd)) & PSC_SEL_CLK_MASK;
+	au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+	au_sync();
+	au_writel(PSC_SEL_PS_I2SMODE | sel, PSC_SEL(wd));
+	au_writel(0, I2S_CFG(wd));
+	au_sync();
+
+	/* preconfigure: set max rx/tx fifo depths */
+	wd->cfg |= PSC_I2SCFG_RT_FIFO8 | PSC_I2SCFG_TT_FIFO8;
+
+	/* don't wait for I2S core to become ready now; clocks may not
+	 * be running yet; depending on clock input for PSC a wait might
+	 * time out.
+	 */
+
+	/* name the DAI like this device instance ("au1xpsc-i2s.PSCINDEX") */
+	memcpy(&wd->dai_drv, &au1xpsc_i2s_dai_template,
+	       sizeof(struct snd_soc_dai_driver));
+	wd->dai_drv.name = dev_name(&pdev->dev);
+
+	platform_set_drvdata(pdev, wd);
+
+	ret = snd_soc_register_dai(&pdev->dev, &wd->dai_drv);
+	if (ret)
+		goto out1;
+
+	/* finally add the DMA device for this PSC */
+	wd->dmapd = au1xpsc_pcm_add(pdev);
+	if (wd->dmapd)
+		return 0;
+
+	snd_soc_unregister_dai(&pdev->dev);
+out1:
+	release_mem_region(r->start, resource_size(r));
+out0:
+	kfree(wd);
+	return ret;
+}
+
+static int __devexit au1xpsc_i2s_drvremove(struct platform_device *pdev)
+{
+	struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);
+	struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	if (wd->dmapd)
+		au1xpsc_pcm_destroy(wd->dmapd);
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	au_writel(0, I2S_CFG(wd));
+	au_sync();
+	au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+	au_sync();
+
+	iounmap(wd->mmio);
+	release_mem_region(r->start, resource_size(r));
+	kfree(wd);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int au1xpsc_i2s_drvsuspend(struct device *dev)
+{
+	struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
+
+	/* save interesting register and disable PSC */
+	wd->pm[0] = au_readl(PSC_SEL(wd));
+
+	au_writel(0, I2S_CFG(wd));
+	au_sync();
+	au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+	au_sync();
+
+	return 0;
+}
+
+static int au1xpsc_i2s_drvresume(struct device *dev)
+{
+	struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
+
+	/* select I2S mode and PSC clock */
+	au_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
+	au_sync();
+	au_writel(0, PSC_SEL(wd));
+	au_sync();
+	au_writel(wd->pm[0], PSC_SEL(wd));
+	au_sync();
+
+	return 0;
+}
+
+static struct dev_pm_ops au1xpsci2s_pmops = {
+	.suspend	= au1xpsc_i2s_drvsuspend,
+	.resume		= au1xpsc_i2s_drvresume,
+};
+
+#define AU1XPSCI2S_PMOPS &au1xpsci2s_pmops
+
+#else
+
+#define AU1XPSCI2S_PMOPS NULL
+
+#endif
+
+static struct platform_driver au1xpsc_i2s_driver = {
+	.driver		= {
+		.name	= "au1xpsc_i2s",
+		.owner	= THIS_MODULE,
+		.pm	= AU1XPSCI2S_PMOPS,
+	},
+	.probe		= au1xpsc_i2s_drvprobe,
+	.remove		= __devexit_p(au1xpsc_i2s_drvremove),
+};
+
+static int __init au1xpsc_i2s_load(void)
+{
+	return platform_driver_register(&au1xpsc_i2s_driver);
+}
+
+static void __exit au1xpsc_i2s_unload(void)
+{
+	platform_driver_unregister(&au1xpsc_i2s_driver);
+}
+
+module_init(au1xpsc_i2s_load);
+module_exit(au1xpsc_i2s_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au12x0/Au1550 PSC I2S ALSA ASoC audio driver");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/linux/sound/soc/au1x/psc.h b/linux/sound/soc/au1x/psc.h
new file mode 100644
index 0000000..b30eadd
--- /dev/null
+++ b/linux/sound/soc/au1x/psc.h
@@ -0,0 +1,52 @@
+/*
+ * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ *
+ * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ *	Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _AU1X_PCM_H
+#define _AU1X_PCM_H
+
+/* DBDMA helpers */
+extern struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev);
+extern void au1xpsc_pcm_destroy(struct platform_device *dmapd);
+
+struct au1xpsc_audio_data {
+	void __iomem *mmio;
+
+	unsigned long cfg;
+	unsigned long rate;
+
+	struct snd_soc_dai_driver dai_drv;
+
+	unsigned long pm[2];
+	struct mutex lock;
+	struct platform_device *dmapd;
+};
+
+#define PCM_TX	0
+#define PCM_RX	1
+
+#define SUBSTREAM_TYPE(substream) \
+	((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
+
+/* easy access macros */
+#define PSC_CTRL(x)	((unsigned long)((x)->mmio) + PSC_CTRL_OFFSET)
+#define PSC_SEL(x)	((unsigned long)((x)->mmio) + PSC_SEL_OFFSET)
+#define I2S_STAT(x)	((unsigned long)((x)->mmio) + PSC_I2SSTAT_OFFSET)
+#define I2S_CFG(x)	((unsigned long)((x)->mmio) + PSC_I2SCFG_OFFSET)
+#define I2S_PCR(x)	((unsigned long)((x)->mmio) + PSC_I2SPCR_OFFSET)
+#define AC97_CFG(x)	((unsigned long)((x)->mmio) + PSC_AC97CFG_OFFSET)
+#define AC97_CDC(x)	((unsigned long)((x)->mmio) + PSC_AC97CDC_OFFSET)
+#define AC97_EVNT(x)	((unsigned long)((x)->mmio) + PSC_AC97EVNT_OFFSET)
+#define AC97_PCR(x)	((unsigned long)((x)->mmio) + PSC_AC97PCR_OFFSET)
+#define AC97_RST(x)	((unsigned long)((x)->mmio) + PSC_AC97RST_OFFSET)
+#define AC97_STAT(x)	((unsigned long)((x)->mmio) + PSC_AC97STAT_OFFSET)
+
+#endif
diff --git a/linux/sound/soc/blackfin/Kconfig b/linux/sound/soc/blackfin/Kconfig
new file mode 100644
index 0000000..3abeedd
--- /dev/null
+++ b/linux/sound/soc/blackfin/Kconfig
@@ -0,0 +1,144 @@
+config SND_BF5XX_I2S
+	tristate "SoC I2S Audio for the ADI BF5xx chip"
+	depends on BLACKFIN
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the Blackfin SPORT (synchronous serial ports) interface in I2S
+	  mode (supports single stereo In/Out).
+	  You will also need to select the audio interfaces to support below.
+
+config SND_BF5XX_SOC_SSM2602
+	tristate "SoC SSM2602 Audio support for BF52x ezkit"
+	depends on SND_BF5XX_I2S
+	select SND_BF5XX_SOC_I2S
+	select SND_SOC_SSM2602
+	select I2C
+	help
+	  Say Y if you want to add support for SoC audio on BF527-EZKIT.
+
+config SND_BF5XX_SOC_AD73311
+	tristate "SoC AD73311 Audio support for Blackfin"
+	depends on SND_BF5XX_I2S
+	select SND_BF5XX_SOC_I2S
+	select SND_SOC_AD73311
+	help
+	  Say Y if you want to add support for AD73311 codec on Blackfin.
+
+config SND_BFIN_AD73311_SE
+	int "PF pin for AD73311L Chip Select"
+	depends on SND_BF5XX_SOC_AD73311
+	default 4
+	help
+	  Enter the GPIO used to control AD73311's SE pin. Acceptable
+	  values are 0 to 7
+
+config SND_BF5XX_TDM
+	tristate "SoC I2S(TDM mode) Audio for the ADI BF5xx chip"
+	depends on (BLACKFIN && SND_SOC)
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the Blackfin SPORT (synchronous serial ports) interface in TDM
+	  mode.
+	  You will also need to select the audio interfaces to support below.
+
+config SND_BF5XX_SOC_AD1836
+	tristate "SoC AD1836 Audio support for BF5xx"
+	depends on SND_BF5XX_TDM
+	select SND_BF5XX_SOC_TDM
+	select SND_SOC_AD1836
+	help
+	  Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT.
+
+config SND_BF5XX_SOC_AD193X
+	tristate "SoC AD193X Audio support for Blackfin"
+	depends on SND_BF5XX_TDM
+	select SND_BF5XX_SOC_TDM
+	select SND_SOC_AD193X
+	help
+	  Say Y if you want to add support for AD193X codec on Blackfin.
+	  This driver supports AD1936, AD1937, AD1938 and AD1939.
+
+config SND_BF5XX_AC97
+	tristate "SoC AC97 Audio for the ADI BF5xx chip"
+	depends on BLACKFIN
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the Blackfin SPORT (synchronous serial ports) interface in slot 16
+	  mode (pseudo AC97 interface).
+	  You will also need to select the audio interfaces to support below.
+
+	  Note:
+	  AC97 codecs which do not implement the slot-16 mode will not function
+	  properly with this driver. This driver is known to work with the
+	  Analog Devices line of AC97 codecs.
+
+config SND_BF5XX_MMAP_SUPPORT
+	bool "Enable MMAP Support"
+	depends on SND_BF5XX_AC97
+	default y
+	help
+	  Say y if you want AC97 driver to support mmap mode.
+	  We introduce an intermediate buffer to simulate mmap.
+
+config SND_BF5XX_MULTICHAN_SUPPORT
+	bool "Enable Multichannel Support"
+	depends on SND_BF5XX_AC97
+	default n
+	help
+	  Say y if you want AC97 driver to support up to 5.1 channel audio.
+	  this mode will consume much more memory for DMA.
+
+config SND_BF5XX_HAVE_COLD_RESET
+	bool "BOARD has COLD Reset GPIO"
+	depends on SND_BF5XX_AC97
+	default y if BFIN548_EZKIT
+	default n if !BFIN548_EZKIT
+
+config SND_BF5XX_RESET_GPIO_NUM
+	int "Set a GPIO for cold reset"
+	depends on SND_BF5XX_HAVE_COLD_RESET
+	range 0 159
+	default 19 if BFIN548_EZKIT
+	default 5 if BFIN537_STAMP
+	default 0
+	help
+	  Set the correct GPIO for RESET the sound chip.
+
+config SND_BF5XX_SOC_AD1980
+	tristate "SoC AD1980/1 Audio support for BF5xx (Obsolete)"
+	depends on SND_BF5XX_AC97
+	select SND_BF5XX_SOC_AC97
+	select SND_SOC_AD1980
+	help
+	  Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT.
+
+	  Warning:
+	  Because Analog Devices Inc. discontinued the ad1980 sound chip since
+	  Sep. 2009, this ad1980 driver is not maintained, tested and supported
+	  by ADI now.
+
+config SND_BF5XX_SOC_SPORT
+	tristate
+
+config SND_BF5XX_SOC_I2S
+	tristate
+	select SND_BF5XX_SOC_SPORT
+
+config SND_BF5XX_SOC_TDM
+	tristate
+	select SND_BF5XX_SOC_SPORT
+
+config SND_BF5XX_SOC_AC97
+	tristate
+	select AC97_BUS
+	select SND_SOC_AC97_BUS
+	select SND_BF5XX_SOC_SPORT
+
+config SND_BF5XX_SPORT_NUM
+	int "Set a SPORT for Sound chip"
+	depends on (SND_BF5XX_I2S || SND_BF5XX_AC97 || SND_BF5XX_TDM)
+	range 0 3 if BF54x
+	range 0 1 if !BF54x
+	default 0
+	help
+	  Set the correct SPORT for sound chip.
diff --git a/linux/sound/soc/blackfin/Makefile b/linux/sound/soc/blackfin/Makefile
new file mode 100644
index 0000000..49af3f3
--- /dev/null
+++ b/linux/sound/soc/blackfin/Makefile
@@ -0,0 +1,29 @@
+# Blackfin Platform Support
+snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o
+snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o
+snd-bf5xx-tdm-objs := bf5xx-tdm-pcm.o
+snd-soc-bf5xx-sport-objs := bf5xx-sport.o
+snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o
+snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o
+snd-soc-bf5xx-tdm-objs := bf5xx-tdm.o
+
+obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o
+obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o
+obj-$(CONFIG_SND_BF5XX_TDM) += snd-bf5xx-tdm.o
+obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o
+obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o
+obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o
+obj-$(CONFIG_SND_BF5XX_SOC_TDM) += snd-soc-bf5xx-tdm.o
+
+# Blackfin Machine Support
+snd-ad1836-objs := bf5xx-ad1836.o
+snd-ad1980-objs := bf5xx-ad1980.o
+snd-ssm2602-objs := bf5xx-ssm2602.o
+snd-ad73311-objs := bf5xx-ad73311.o
+snd-ad193x-objs := bf5xx-ad193x.o
+
+obj-$(CONFIG_SND_BF5XX_SOC_AD1836) += snd-ad1836.o
+obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o
+obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o
+obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o
+obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o
diff --git a/linux/sound/soc/blackfin/bf5xx-ac97-pcm.c b/linux/sound/soc/blackfin/bf5xx-ac97-pcm.c
new file mode 100644
index 0000000..5a2fd8a
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-ac97-pcm.c
@@ -0,0 +1,483 @@
+/*
+ * File:         sound/soc/blackfin/bf5xx-ac97-pcm.c
+ * Author:       Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created:      Tue June 06 2008
+ * Description:  DMA Driver for AC97 sound chip
+ *
+ * Modified:
+ *               Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/gfp.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+
+#include "bf5xx-ac97-pcm.h"
+#include "bf5xx-ac97.h"
+#include "bf5xx-sport.h"
+
+static unsigned int ac97_chan_mask[] = {
+	SP_FL, /* Mono */
+	SP_STEREO, /* Stereo */
+	SP_2DOT1, /* 2.1*/
+	SP_QUAD,/*Quadraquic*/
+	SP_FL | SP_FR | SP_FC | SP_SL | SP_SR,/*5 channels */
+	SP_5DOT1, /* 5.1 */
+};
+
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+static void bf5xx_mmap_copy(struct snd_pcm_substream *substream,
+	 snd_pcm_uframes_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+	unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1];
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		bf5xx_pcm_to_ac97((struct ac97_frame *)sport->tx_dma_buf +
+		sport->tx_pos, (__u16 *)runtime->dma_area + sport->tx_pos *
+		runtime->channels, count, chan_mask);
+		sport->tx_pos += runtime->period_size;
+		if (sport->tx_pos >= runtime->buffer_size)
+			sport->tx_pos %= runtime->buffer_size;
+		sport->tx_delay_pos = sport->tx_pos;
+	} else {
+		bf5xx_ac97_to_pcm((struct ac97_frame *)sport->rx_dma_buf +
+		sport->rx_pos, (__u16 *)runtime->dma_area + sport->rx_pos *
+		runtime->channels, count);
+		sport->rx_pos += runtime->period_size;
+		if (sport->rx_pos >= runtime->buffer_size)
+			sport->rx_pos %= runtime->buffer_size;
+	}
+}
+#endif
+
+static void bf5xx_dma_irq(void *data)
+{
+	struct snd_pcm_substream *pcm = data;
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+	struct snd_pcm_runtime *runtime = pcm->runtime;
+	struct sport_device *sport = runtime->private_data;
+	bf5xx_mmap_copy(pcm, runtime->period_size);
+	if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (sport->once == 0) {
+			snd_pcm_period_elapsed(pcm);
+			bf5xx_mmap_copy(pcm, runtime->period_size);
+			sport->once = 1;
+		}
+	}
+#endif
+	snd_pcm_period_elapsed(pcm);
+}
+
+/* The memory size for pure pcm data is 128*1024 = 0x20000 bytes.
+ * The total rx/tx buffer is for ac97 frame to hold all pcm data
+ * is  0x20000 * sizeof(struct ac97_frame) / 4.
+ */
+static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
+	.info			= SNDRV_PCM_INFO_INTERLEAVED |
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+				   SNDRV_PCM_INFO_MMAP |
+				   SNDRV_PCM_INFO_MMAP_VALID |
+#endif
+				   SNDRV_PCM_INFO_BLOCK_TRANSFER,
+
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 0x10000,
+	.periods_min		= 1,
+	.periods_max		= PAGE_SIZE/32,
+	.buffer_bytes_max	= 0x20000, /* 128 kbytes */
+	.fifo_size		= 16,
+};
+
+static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	size_t size = bf5xx_pcm_hardware.buffer_bytes_max
+			* sizeof(struct ac97_frame) / 4;
+
+	snd_pcm_lib_malloc_pages(substream, size);
+
+	return 0;
+}
+
+static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		sport->once = 0;
+		if (runtime->dma_area)
+			memset(runtime->dma_area, 0, runtime->buffer_size);
+		memset(sport->tx_dma_buf, 0, runtime->buffer_size *
+			sizeof(struct ac97_frame));
+	} else
+		memset(sport->rx_dma_buf, 0, runtime->buffer_size *
+			sizeof(struct ac97_frame));
+#endif
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+
+	/* An intermediate buffer is introduced for implementing mmap for
+	 * SPORT working in TMD mode(include AC97).
+	 */
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
+		sport_config_tx_dma(sport, sport->tx_dma_buf, runtime->periods,
+			runtime->period_size * sizeof(struct ac97_frame));
+	} else {
+		sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
+		sport_config_rx_dma(sport, sport->rx_dma_buf, runtime->periods,
+			runtime->period_size * sizeof(struct ac97_frame));
+	}
+#else
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
+		sport_config_tx_dma(sport, runtime->dma_area, runtime->periods,
+			runtime->period_size * sizeof(struct ac97_frame));
+	} else {
+		sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
+		sport_config_rx_dma(sport, runtime->dma_area, runtime->periods,
+			runtime->period_size * sizeof(struct ac97_frame));
+	}
+#endif
+	return 0;
+}
+
+static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+	int ret = 0;
+
+	pr_debug("%s enter\n", __func__);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+			bf5xx_mmap_copy(substream, runtime->period_size);
+			sport->tx_delay_pos = 0;
+#endif
+			sport_tx_start(sport);
+		} else
+			sport_rx_start(sport);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+			sport->tx_pos = 0;
+#endif
+			sport_tx_stop(sport);
+		} else {
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+			sport->rx_pos = 0;
+#endif
+			sport_rx_stop(sport);
+		}
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+
+static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+	unsigned int curr;
+
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		curr = sport->tx_delay_pos;
+	else
+		curr = sport->rx_pos;
+#else
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		curr = sport_curr_offset_tx(sport) / sizeof(struct ac97_frame);
+	else
+		curr = sport_curr_offset_rx(sport) / sizeof(struct ac97_frame);
+
+#endif
+	return curr;
+}
+
+static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int ret;
+
+	pr_debug("%s enter\n", __func__);
+	snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
+
+	ret = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0)
+		goto out;
+
+	if (sport_handle != NULL)
+		runtime->private_data = sport_handle;
+	else {
+		pr_err("sport_handle is NULL\n");
+		return -1;
+	}
+	return 0;
+
+ out:
+	return ret;
+}
+
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
+	struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	size_t size = vma->vm_end - vma->vm_start;
+	vma->vm_start = (unsigned long)runtime->dma_area;
+	vma->vm_end = vma->vm_start + size;
+	vma->vm_flags |=  VM_SHARED;
+	return 0 ;
+}
+#else
+static	int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
+		    snd_pcm_uframes_t pos,
+		    void __user *buf, snd_pcm_uframes_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int chan_mask = ac97_chan_mask[runtime->channels - 1];
+	pr_debug("%s copy pos:0x%lx count:0x%lx\n",
+			substream->stream ? "Capture" : "Playback", pos, count);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		bf5xx_pcm_to_ac97((struct ac97_frame *)runtime->dma_area + pos,
+			(__u16 *)buf, count, chan_mask);
+	else
+		bf5xx_ac97_to_pcm((struct ac97_frame *)runtime->dma_area + pos,
+			(__u16 *)buf, count);
+	return 0;
+}
+#endif
+
+static struct snd_pcm_ops bf5xx_pcm_ac97_ops = {
+	.open		= bf5xx_pcm_open,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= bf5xx_pcm_hw_params,
+	.hw_free	= bf5xx_pcm_hw_free,
+	.prepare	= bf5xx_pcm_prepare,
+	.trigger	= bf5xx_pcm_trigger,
+	.pointer	= bf5xx_pcm_pointer,
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+	.mmap		= bf5xx_pcm_mmap,
+#else
+	.copy		= bf5xx_pcm_copy,
+#endif
+};
+
+static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = bf5xx_pcm_hardware.buffer_bytes_max
+			* sizeof(struct ac97_frame) / 4;
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+	buf->area = dma_alloc_coherent(pcm->card->dev, size,
+			&buf->addr, GFP_KERNEL);
+	if (!buf->area) {
+		pr_err("Failed to allocate dma memory\n");
+		pr_err("Please increase uncached DMA memory region\n");
+		return -ENOMEM;
+	}
+	buf->bytes = size;
+
+	pr_debug("%s, area:%p, size:0x%08lx\n", __func__,
+			buf->area, buf->bytes);
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+		sport_handle->tx_buf = buf->area;
+	else
+		sport_handle->rx_buf = buf->area;
+
+/*
+ * Need to allocate local buffer when enable
+ * MMAP for SPORT working in TMD mode (include AC97).
+ */
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (!sport_handle->tx_dma_buf) {
+			sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \
+				size, &sport_handle->tx_dma_phy, GFP_KERNEL);
+			if (!sport_handle->tx_dma_buf) {
+				pr_err("Failed to allocate memory for tx dma buf - Please increase uncached DMA memory region\n");
+				return -ENOMEM;
+			} else
+				memset(sport_handle->tx_dma_buf, 0, size);
+		} else
+			memset(sport_handle->tx_dma_buf, 0, size);
+	} else {
+		if (!sport_handle->rx_dma_buf) {
+			sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \
+				size, &sport_handle->rx_dma_phy, GFP_KERNEL);
+			if (!sport_handle->rx_dma_buf) {
+				pr_err("Failed to allocate memory for rx dma buf - Please increase uncached DMA memory region\n");
+				return -ENOMEM;
+			} else
+				memset(sport_handle->rx_dma_buf, 0, size);
+		} else
+			memset(sport_handle->rx_dma_buf, 0, size);
+	}
+#endif
+	return 0;
+}
+
+static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+	size_t size = bf5xx_pcm_hardware.buffer_bytes_max *
+		sizeof(struct ac97_frame) / 4;
+#endif
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+		dma_free_coherent(NULL, buf->bytes, buf->area, 0);
+		buf->area = NULL;
+#if defined(CONFIG_SND_BF5XX_MMAP_SUPPORT)
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (sport_handle->tx_dma_buf)
+			dma_free_coherent(NULL, size, \
+				sport_handle->tx_dma_buf, 0);
+		sport_handle->tx_dma_buf = NULL;
+	} else {
+
+		if (sport_handle->rx_dma_buf)
+			dma_free_coherent(NULL, size, \
+				sport_handle->rx_dma_buf, 0);
+		sport_handle->rx_dma_buf = NULL;
+	}
+#endif
+	}
+	if (sport_handle)
+		sport_done(sport_handle);
+}
+
+static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
+
+int bf5xx_pcm_ac97_new(struct snd_card *card, struct snd_soc_dai *dai,
+	struct snd_pcm *pcm)
+{
+	int ret = 0;
+
+	pr_debug("%s enter\n", __func__);
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &bf5xx_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+	if (dai->driver->playback.channels_min) {
+		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_PLAYBACK);
+		if (ret)
+			goto out;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_CAPTURE);
+		if (ret)
+			goto out;
+	}
+ out:
+	return ret;
+}
+
+static struct snd_soc_platform_driver bf5xx_ac97_soc_platform = {
+	.ops			= &bf5xx_pcm_ac97_ops,
+	.pcm_new	= bf5xx_pcm_ac97_new,
+	.pcm_free	= bf5xx_pcm_free_dma_buffers,
+};
+
+static int __devinit bf5xx_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &bf5xx_ac97_soc_platform);
+}
+
+static int __devexit bf5xx_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver bf5xx_pcm_driver = {
+	.driver = {
+			.name = "bf5xx-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = bf5xx_soc_platform_probe,
+	.remove = __devexit_p(bf5xx_soc_platform_remove),
+};
+
+static int __init snd_bf5xx_pcm_init(void)
+{
+	return platform_driver_register(&bf5xx_pcm_driver);
+}
+module_init(snd_bf5xx_pcm_init);
+
+static void __exit snd_bf5xx_pcm_exit(void)
+{
+	platform_driver_unregister(&bf5xx_pcm_driver);
+}
+module_exit(snd_bf5xx_pcm_exit);
+
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/blackfin/bf5xx-ac97-pcm.h b/linux/sound/soc/blackfin/bf5xx-ac97-pcm.h
new file mode 100644
index 0000000..d324d58
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-ac97-pcm.h
@@ -0,0 +1,26 @@
+/*
+ * linux/sound/arm/bf5xx-ac97-pcm.h -- ALSA PCM interface for the Blackfin
+ *
+ * Copyright 2007 Analog Device Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_AC97_PCM_H
+#define _BF5XX_AC97_PCM_H
+
+struct bf5xx_pcm_dma_params {
+	char *name;			/* stream identifier */
+};
+
+struct bf5xx_gpio {
+	u32 sys;
+	u32 rx;
+	u32 tx;
+	u32 clk;
+	u32 frm;
+};
+
+#endif
diff --git a/linux/sound/soc/blackfin/bf5xx-ac97.c b/linux/sound/soc/blackfin/bf5xx-ac97.c
new file mode 100644
index 0000000..c5f856e
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-ac97.c
@@ -0,0 +1,453 @@
+/*
+ * bf5xx-ac97.c -- AC97 support for the ADI blackfin chip.
+ *
+ * Author:	Roy Huang
+ * Created:	11th. June 2007
+ * Copyright:	Analog Device Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/irq.h>
+#include <asm/portmux.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+
+#include "bf5xx-sport.h"
+#include "bf5xx-ac97.h"
+
+/* Anomaly notes:
+ *  05000250 -	AD1980 is running in TDM mode and RFS/TFS are generated by SPORT
+ *		contrtoller. But, RFSDIV and TFSDIV are always set to 16*16-1,
+ *		while the max AC97 data size is 13*16. The DIV is always larger
+ *		than data size. AD73311 and ad2602 are not running in TDM mode.
+ *		AD1836 and AD73322 depend on external RFS/TFS only. So, this
+ *		anomaly does not affect blackfin sound drivers.
+*/
+
+static int *cmd_count;
+static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
+
+#define SPORT_REQ(x) \
+	[x] = {P_SPORT##x##_TFS, P_SPORT##x##_DTPRI, P_SPORT##x##_TSCLK, \
+	       P_SPORT##x##_RFS, P_SPORT##x##_DRPRI, P_SPORT##x##_RSCLK, 0}
+static u16 sport_req[][7] = {
+#ifdef SPORT0_TCR1
+	SPORT_REQ(0),
+#endif
+#ifdef SPORT1_TCR1
+	SPORT_REQ(1),
+#endif
+#ifdef SPORT2_TCR1
+	SPORT_REQ(2),
+#endif
+#ifdef SPORT3_TCR1
+	SPORT_REQ(3),
+#endif
+};
+
+#define SPORT_PARAMS(x) \
+	[x] = { \
+		.dma_rx_chan = CH_SPORT##x##_RX, \
+		.dma_tx_chan = CH_SPORT##x##_TX, \
+		.err_irq     = IRQ_SPORT##x##_ERROR, \
+		.regs        = (struct sport_register *)SPORT##x##_TCR1, \
+	}
+static struct sport_param sport_params[4] = {
+#ifdef SPORT0_TCR1
+	SPORT_PARAMS(0),
+#endif
+#ifdef SPORT1_TCR1
+	SPORT_PARAMS(1),
+#endif
+#ifdef SPORT2_TCR1
+	SPORT_PARAMS(2),
+#endif
+#ifdef SPORT3_TCR1
+	SPORT_PARAMS(3),
+#endif
+};
+
+void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u16 *src,
+		size_t count, unsigned int chan_mask)
+{
+	while (count--) {
+		dst->ac97_tag = TAG_VALID;
+		if (chan_mask & SP_FL) {
+			dst->ac97_pcm_r = *src++;
+			dst->ac97_tag |= TAG_PCM_RIGHT;
+		}
+		if (chan_mask & SP_FR) {
+			dst->ac97_pcm_l = *src++;
+			dst->ac97_tag |= TAG_PCM_LEFT;
+
+		}
+#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
+		if (chan_mask & SP_SR) {
+			dst->ac97_sl = *src++;
+			dst->ac97_tag |= TAG_PCM_SL;
+		}
+		if (chan_mask & SP_SL) {
+			dst->ac97_sr = *src++;
+			dst->ac97_tag |= TAG_PCM_SR;
+		}
+		if (chan_mask & SP_LFE) {
+			dst->ac97_lfe = *src++;
+			dst->ac97_tag |= TAG_PCM_LFE;
+		}
+		if (chan_mask & SP_FC) {
+			dst->ac97_center = *src++;
+			dst->ac97_tag |= TAG_PCM_CENTER;
+		}
+#endif
+		dst++;
+	}
+}
+EXPORT_SYMBOL(bf5xx_pcm_to_ac97);
+
+void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u16 *dst,
+		size_t count)
+{
+	while (count--) {
+		*(dst++) = src->ac97_pcm_l;
+		*(dst++) = src->ac97_pcm_r;
+		src++;
+	}
+}
+EXPORT_SYMBOL(bf5xx_ac97_to_pcm);
+
+static unsigned int sport_tx_curr_frag(struct sport_device *sport)
+{
+	return sport->tx_curr_frag = sport_curr_offset_tx(sport) /
+			sport->tx_fragsize;
+}
+
+static void enqueue_cmd(struct snd_ac97 *ac97, __u16 addr, __u16 data)
+{
+	struct sport_device *sport = sport_handle;
+	int nextfrag = sport_tx_curr_frag(sport);
+	struct ac97_frame *nextwrite;
+
+	sport_incfrag(sport, &nextfrag, 1);
+
+	nextwrite = (struct ac97_frame *)(sport->tx_buf +
+			nextfrag * sport->tx_fragsize);
+	pr_debug("sport->tx_buf:%p, nextfrag:0x%x nextwrite:%p, cmd_count:%d\n",
+		sport->tx_buf, nextfrag, nextwrite, cmd_count[nextfrag]);
+	nextwrite[cmd_count[nextfrag]].ac97_tag |= TAG_CMD;
+	nextwrite[cmd_count[nextfrag]].ac97_addr = addr;
+	nextwrite[cmd_count[nextfrag]].ac97_data = data;
+	++cmd_count[nextfrag];
+	pr_debug("ac97_sport: Inserting %02x/%04x into fragment %d\n",
+			addr >> 8, data, nextfrag);
+}
+
+static unsigned short bf5xx_ac97_read(struct snd_ac97 *ac97,
+	unsigned short reg)
+{
+	struct ac97_frame out_frame[2], in_frame[2];
+
+	pr_debug("%s enter 0x%x\n", __func__, reg);
+
+	/* When dma descriptor is enabled, the register should not be read */
+	if (sport_handle->tx_run || sport_handle->rx_run) {
+		pr_err("Could you send a mail to cliff.cai@analog.com "
+				"to report this?\n");
+		return -EFAULT;
+	}
+
+	memset(&out_frame, 0, 2 * sizeof(struct ac97_frame));
+	memset(&in_frame, 0, 2 * sizeof(struct ac97_frame));
+	out_frame[0].ac97_tag = TAG_VALID | TAG_CMD;
+	out_frame[0].ac97_addr = ((reg << 8) | 0x8000);
+	sport_send_and_recv(sport_handle, (unsigned char *)&out_frame,
+			(unsigned char *)&in_frame,
+			2 * sizeof(struct ac97_frame));
+	return in_frame[1].ac97_data;
+}
+
+void bf5xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+	unsigned short val)
+{
+	pr_debug("%s enter 0x%x:0x%04x\n", __func__, reg, val);
+
+	if (sport_handle->tx_run) {
+		enqueue_cmd(ac97, (reg << 8), val); /* write */
+		enqueue_cmd(ac97, (reg << 8) | 0x8000, 0); /* read back */
+	} else {
+		struct ac97_frame frame;
+		memset(&frame, 0, sizeof(struct ac97_frame));
+		frame.ac97_tag = TAG_VALID | TAG_CMD;
+		frame.ac97_addr = (reg << 8);
+		frame.ac97_data = val;
+		sport_send_and_recv(sport_handle, (unsigned char *)&frame, \
+				NULL, sizeof(struct ac97_frame));
+	}
+}
+
+static void bf5xx_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+#if defined(CONFIG_BF54x) || defined(CONFIG_BF561) || \
+ (defined(BF537_FAMILY) && (CONFIG_SND_BF5XX_SPORT_NUM == 1))
+
+#define CONCAT(a, b, c) a ## b ## c
+#define BFIN_SPORT_RFS(x) CONCAT(P_SPORT, x, _RFS)
+
+	u16 per = BFIN_SPORT_RFS(CONFIG_SND_BF5XX_SPORT_NUM);
+	u16 gpio = P_IDENT(BFIN_SPORT_RFS(CONFIG_SND_BF5XX_SPORT_NUM));
+
+	pr_debug("%s enter\n", __func__);
+
+	peripheral_free(per);
+	gpio_request(gpio, "bf5xx-ac97");
+	gpio_direction_output(gpio, 1);
+	udelay(2);
+	gpio_set_value(gpio, 0);
+	udelay(1);
+	gpio_free(gpio);
+	peripheral_request(per, "soc-audio");
+#else
+	pr_info("%s: Not implemented\n", __func__);
+#endif
+}
+
+static void bf5xx_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+	pr_debug("%s enter\n", __func__);
+
+	/* It is specified for bf548-ezkit */
+	gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 0);
+	/* Keep reset pin low for 1 ms */
+	mdelay(1);
+	gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 1);
+	/* Wait for bit clock recover */
+	mdelay(1);
+#else
+	pr_info("%s: Not implemented\n", __func__);
+#endif
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read	= bf5xx_ac97_read,
+	.write	= bf5xx_ac97_write,
+	.warm_reset	= bf5xx_ac97_warm_reset,
+	.reset	= bf5xx_ac97_cold_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+#ifdef CONFIG_PM
+static int bf5xx_ac97_suspend(struct snd_soc_dai *dai)
+{
+	struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
+
+	pr_debug("%s : sport %d\n", __func__, dai->id);
+	if (!dai->active)
+		return 0;
+	if (dai->capture.active)
+		sport_rx_stop(sport);
+	if (dai->playback.active)
+		sport_tx_stop(sport);
+	return 0;
+}
+
+static int bf5xx_ac97_resume(struct snd_soc_dai *dai)
+{
+	int ret;
+	struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
+
+	pr_debug("%s : sport %d\n", __func__, dai->id);
+	if (!dai->active)
+		return 0;
+
+#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
+	ret = sport_set_multichannel(sport, 16, 0x3FF, 1);
+#else
+	ret = sport_set_multichannel(sport, 16, 0x1F, 1);
+#endif
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		return -EBUSY;
+	}
+
+	ret = sport_config_rx(sport, IRFS, 0xF, 0, (16*16-1));
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		return -EBUSY;
+	}
+
+	ret = sport_config_tx(sport, ITFS, 0xF, 0, (16*16-1));
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+#else
+#define bf5xx_ac97_suspend	NULL
+#define bf5xx_ac97_resume	NULL
+#endif
+
+static int bf5xx_ac97_probe(struct snd_soc_dai *dai)
+{
+	int ret = 0;
+	cmd_count = (int *)get_zeroed_page(GFP_KERNEL);
+	if (cmd_count == NULL)
+		return -ENOMEM;
+
+	if (peripheral_request_list(sport_req[sport_num], "soc-audio")) {
+		pr_err("Requesting Peripherals failed\n");
+		ret =  -EFAULT;
+		goto peripheral_err;
+	}
+
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+	/* Request PB3 as reset pin */
+	if (gpio_request(CONFIG_SND_BF5XX_RESET_GPIO_NUM, "SND_AD198x RESET")) {
+		pr_err("Failed to request GPIO_%d for reset\n",
+				CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+		ret =  -1;
+		goto gpio_err;
+	}
+	gpio_direction_output(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 1);
+#endif
+	sport_handle = sport_init(&sport_params[sport_num], 2, \
+			sizeof(struct ac97_frame), NULL);
+	if (!sport_handle) {
+		ret = -ENODEV;
+		goto sport_err;
+	}
+	/*SPORT works in TDM mode to simulate AC97 transfers*/
+#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
+	ret = sport_set_multichannel(sport_handle, 16, 0x3FF, 1);
+#else
+	ret = sport_set_multichannel(sport_handle, 16, 0x1F, 1);
+#endif
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		ret = -EBUSY;
+		goto sport_config_err;
+	}
+
+	ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1));
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		ret = -EBUSY;
+		goto sport_config_err;
+	}
+
+	ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1));
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		ret = -EBUSY;
+		goto sport_config_err;
+	}
+
+	return 0;
+
+sport_config_err:
+	kfree(sport_handle);
+sport_err:
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+	gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+gpio_err:
+#endif
+	peripheral_free_list(sport_req[sport_num]);
+peripheral_err:
+	free_page((unsigned long)cmd_count);
+	cmd_count = NULL;
+
+	return ret;
+}
+
+static int bf5xx_ac97_remove(struct snd_soc_dai *dai)
+{
+	free_page((unsigned long)cmd_count);
+	cmd_count = NULL;
+	peripheral_free_list(sport_req[sport_num]);
+#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
+	gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM);
+#endif
+	return 0;
+}
+
+struct snd_soc_dai_driver bfin_ac97_dai = {
+	.ac97_control = 1,
+	.probe = bf5xx_ac97_probe,
+	.remove = bf5xx_ac97_remove,
+	.suspend = bf5xx_ac97_suspend,
+	.resume = bf5xx_ac97_resume,
+	.playback = {
+		.stream_name = "AC97 Playback",
+		.channels_min = 2,
+#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
+		.channels_max = 6,
+#else
+		.channels_max = 2,
+#endif
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE, },
+	.capture = {
+		.stream_name = "AC97 Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE, },
+};
+EXPORT_SYMBOL_GPL(bfin_ac97_dai);
+
+static __devinit int asoc_bfin_ac97_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_dai(&pdev->dev, &bfin_ac97_dai);
+}
+
+static int __devexit asoc_bfin_ac97_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dai(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver asoc_bfin_ac97_driver = {
+	.driver = {
+			.name = "bfin-ac97",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = asoc_bfin_ac97_probe,
+	.remove = __devexit_p(asoc_bfin_ac97_remove),
+};
+
+static int __init bfin_ac97_init(void)
+{
+	return platform_driver_register(&asoc_bfin_ac97_driver);
+}
+module_init(bfin_ac97_init);
+
+static void __exit bfin_ac97_exit(void)
+{
+	platform_driver_unregister(&asoc_bfin_ac97_driver);
+}
+module_exit(bfin_ac97_exit);
+
+
+MODULE_AUTHOR("Roy Huang");
+MODULE_DESCRIPTION("AC97 driver for ADI Blackfin");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/blackfin/bf5xx-ac97.h b/linux/sound/soc/blackfin/bf5xx-ac97.h
new file mode 100644
index 0000000..15c635e
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-ac97.h
@@ -0,0 +1,59 @@
+/*
+ * sound/soc/blackfin/bf5xx-ac97.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_AC97_H
+#define _BF5XX_AC97_H
+
+extern struct snd_ac97_bus_ops bf5xx_ac97_ops;
+extern struct snd_ac97 *ac97;
+/* Frame format in memory, only support stereo currently */
+struct ac97_frame {
+	u16 ac97_tag;		/* slot 0 */
+	u16 ac97_addr;		/* slot 1 */
+	u16 ac97_data;		/* slot 2 */
+	u16 ac97_pcm_l;		/*slot 3:front left*/
+	u16 ac97_pcm_r;		/*slot 4:front left*/
+#if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
+	u16 ac97_mdm_l1;
+	u16 ac97_center;	/*slot 6:center*/
+	u16 ac97_sl;		/*slot 7:surround left*/
+	u16 ac97_sr;		/*slot 8:surround right*/
+	u16 ac97_lfe;		/*slot 9:lfe*/
+#endif
+} __attribute__ ((packed));
+
+/* Speaker location */
+#define SP_FL		0x0001
+#define SP_FR		0x0010
+#define SP_FC		0x0002
+#define SP_LFE		0x0020
+#define SP_SL		0x0004
+#define SP_SR		0x0040
+
+#define SP_STEREO	(SP_FL | SP_FR)
+#define SP_2DOT1	(SP_FL | SP_FR | SP_LFE)
+#define SP_QUAD		(SP_FL | SP_FR | SP_SL | SP_SR)
+#define SP_5DOT1	(SP_FL | SP_FR | SP_FC | SP_LFE | SP_SL | SP_SR)
+
+#define TAG_VALID		0x8000
+#define TAG_CMD			0x6000
+#define TAG_PCM_LEFT		0x1000
+#define TAG_PCM_RIGHT		0x0800
+#define TAG_PCM_MDM_L1		0x0400
+#define TAG_PCM_CENTER		0x0200
+#define TAG_PCM_SL		0x0100
+#define TAG_PCM_SR		0x0080
+#define TAG_PCM_LFE		0x0040
+
+void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u16 *src, \
+		size_t count, unsigned int chan_mask);
+
+void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u16 *dst, \
+		size_t count);
+
+#endif
diff --git a/linux/sound/soc/blackfin/bf5xx-ad1836.c b/linux/sound/soc/blackfin/bf5xx-ad1836.c
new file mode 100644
index 0000000..2394bff
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-ad1836.c
@@ -0,0 +1,130 @@
+/*
+ * File:         sound/soc/blackfin/bf5xx-ad1836.c
+ * Author:       Barry Song <Barry.Song@analog.com>
+ *
+ * Created:      Aug 4 2009
+ * Description:  Board driver for ad1836 sound chip
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+
+#include <asm/blackfin.h>
+#include <asm/cacheflush.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <asm/portmux.h>
+
+#include "../codecs/ad1836.h"
+#include "bf5xx-sport.h"
+
+#include "bf5xx-tdm-pcm.h"
+#include "bf5xx-tdm.h"
+
+static struct snd_soc_card bf5xx_ad1836;
+
+static int bf5xx_ad1836_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	snd_soc_dai_set_drvdata(cpu_dai, sport_handle);
+	return 0;
+}
+
+static int bf5xx_ad1836_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	unsigned int channel_map[] = {0, 4, 1, 5, 2, 6, 3, 7};
+	int ret = 0;
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
+		SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A |
+		SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI channel mapping */
+	ret = snd_soc_dai_set_channel_map(cpu_dai, ARRAY_SIZE(channel_map),
+		channel_map, ARRAY_SIZE(channel_map), channel_map);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops bf5xx_ad1836_ops = {
+	.startup = bf5xx_ad1836_startup,
+	.hw_params = bf5xx_ad1836_hw_params,
+};
+
+static struct snd_soc_dai_link bf5xx_ad1836_dai = {
+	.name = "ad1836",
+	.stream_name = "AD1836",
+	.cpu_dai_name = "bf5xx-tdm",
+	.codec_dai_name = "ad1836-hifi",
+	.platform_name = "bf5xx-tdm-pcm-audio",
+	.codec_name = "ad1836-codec.0",
+	.ops = &bf5xx_ad1836_ops,
+};
+
+static struct snd_soc_card bf5xx_ad1836 = {
+	.name = "bf5xx_ad1836",
+	.dai_link = &bf5xx_ad1836_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *bfxx_ad1836_snd_device;
+
+static int __init bf5xx_ad1836_init(void)
+{
+	int ret;
+
+	bfxx_ad1836_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!bfxx_ad1836_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(bfxx_ad1836_snd_device, &bf5xx_ad1836);
+	ret = platform_device_add(bfxx_ad1836_snd_device);
+
+	if (ret)
+		platform_device_put(bfxx_ad1836_snd_device);
+
+	return ret;
+}
+
+static void __exit bf5xx_ad1836_exit(void)
+{
+	platform_device_unregister(bfxx_ad1836_snd_device);
+}
+
+module_init(bf5xx_ad1836_init);
+module_exit(bf5xx_ad1836_exit);
+
+/* Module information */
+MODULE_AUTHOR("Barry Song");
+MODULE_DESCRIPTION("ALSA SoC AD1836 board driver");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/blackfin/bf5xx-ad193x.c b/linux/sound/soc/blackfin/bf5xx-ad193x.c
new file mode 100644
index 0000000..e4a6253
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-ad193x.c
@@ -0,0 +1,144 @@
+/*
+ * File:         sound/soc/blackfin/bf5xx-ad193x.c
+ * Author:       Barry Song <Barry.Song@analog.com>
+ *
+ * Created:      Thur June 4 2009
+ * Description:  Board driver for ad193x sound chip
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+
+#include <asm/blackfin.h>
+#include <asm/cacheflush.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <asm/portmux.h>
+
+#include "../codecs/ad193x.h"
+#include "bf5xx-sport.h"
+
+#include "bf5xx-tdm-pcm.h"
+#include "bf5xx-tdm.h"
+
+static struct snd_soc_card bf5xx_ad193x;
+
+static int bf5xx_ad193x_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	snd_soc_dai_set_drvdata(cpu_dai, sport_handle);
+	return 0;
+}
+
+static int bf5xx_ad193x_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	unsigned int channel_map[] = {0, 1, 2, 3, 4, 5, 6, 7};
+	int ret = 0;
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
+		SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A |
+		SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set codec DAI slots, 8 channels, all channels are enabled */
+	ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xFF, 0xFF, 8, 32);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI channel mapping */
+	ret = snd_soc_dai_set_channel_map(cpu_dai, ARRAY_SIZE(channel_map),
+		channel_map, ARRAY_SIZE(channel_map), channel_map);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops bf5xx_ad193x_ops = {
+	.startup = bf5xx_ad193x_startup,
+	.hw_params = bf5xx_ad193x_hw_params,
+};
+
+static struct snd_soc_dai_link bf5xx_ad193x_dai = {
+	.name = "ad193x",
+	.stream_name = "AD193X",
+	.cpu_dai_name = "bf5xx-tdm",
+	.codec_dai_name ="ad193x-hifi",
+	.platform_name = "bf5xx-tdm-pcm-audio",
+	.codec_name = "ad193x-codec.5",
+	.ops = &bf5xx_ad193x_ops,
+};
+
+static struct snd_soc_card bf5xx_ad193x = {
+	.name = "bf5xx_ad193x",
+	.dai_link = &bf5xx_ad193x_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *bfxx_ad193x_snd_device;
+
+static int __init bf5xx_ad193x_init(void)
+{
+	int ret;
+
+	bfxx_ad193x_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!bfxx_ad193x_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(bfxx_ad193x_snd_device, &bf5xx_ad193x);
+	ret = platform_device_add(bfxx_ad193x_snd_device);
+
+	if (ret)
+		platform_device_put(bfxx_ad193x_snd_device);
+
+	return ret;
+}
+
+static void __exit bf5xx_ad193x_exit(void)
+{
+	platform_device_unregister(bfxx_ad193x_snd_device);
+}
+
+module_init(bf5xx_ad193x_init);
+module_exit(bf5xx_ad193x_exit);
+
+/* Module information */
+MODULE_AUTHOR("Barry Song");
+MODULE_DESCRIPTION("ALSA SoC AD193X board driver");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/blackfin/bf5xx-ad1980.c b/linux/sound/soc/blackfin/bf5xx-ad1980.c
new file mode 100644
index 0000000..d57c9c9
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-ad1980.c
@@ -0,0 +1,116 @@
+/*
+ * File:         sound/soc/blackfin/bf5xx-ad1980.c
+ * Author:       Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created:      Tue June 06 2008
+ * Description:  Board driver for AD1980/1 audio codec
+ *
+ * Modified:
+ *               Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * WARNING:
+ *
+ * Because Analog Devices Inc. discontinued the ad1980 sound chip since
+ * Sep. 2009, this ad1980 driver is not maintained, tested and supported
+ * by ADI now.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <asm/dma.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <linux/gpio.h>
+#include <asm/portmux.h>
+
+#include "../codecs/ad1980.h"
+#include "bf5xx-sport.h"
+#include "bf5xx-ac97-pcm.h"
+#include "bf5xx-ac97.h"
+
+static struct snd_soc_card bf5xx_board;
+
+static int bf5xx_board_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	pr_debug("%s enter\n", __func__);
+	snd_soc_dai_set_drvdata(cpu_dai, sport_handle);
+	return 0;
+}
+
+static struct snd_soc_ops bf5xx_board_ops = {
+	.startup = bf5xx_board_startup,
+};
+
+static struct snd_soc_dai_link bf5xx_board_dai = {
+	.name = "AC97",
+	.stream_name = "AC97 HiFi",
+	.cpu_dai_name = "bfin-ac97",
+	.codec_dai_name = "ad1980-hifi",
+	.platform_name = "bfin-pcm-audio",
+	.codec_name = "ad1980-codec",
+	.ops = &bf5xx_board_ops,
+};
+
+static struct snd_soc_card bf5xx_board = {
+	.name = "bf5xx-board",
+	.dai_link = &bf5xx_board_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *bf5xx_board_snd_device;
+
+static int __init bf5xx_board_init(void)
+{
+	int ret;
+
+	bf5xx_board_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!bf5xx_board_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(bf5xx_board_snd_device, &bf5xx_board);
+	ret = platform_device_add(bf5xx_board_snd_device);
+
+	if (ret)
+		platform_device_put(bf5xx_board_snd_device);
+
+	return ret;
+}
+
+static void __exit bf5xx_board_exit(void)
+{
+	platform_device_unregister(bf5xx_board_snd_device);
+}
+
+module_init(bf5xx_board_init);
+module_exit(bf5xx_board_exit);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ALSA SoC AD1980/1 BF5xx board (Obsolete)");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/blackfin/bf5xx-ad73311.c b/linux/sound/soc/blackfin/bf5xx-ad73311.c
new file mode 100644
index 0000000..900ced5
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-ad73311.c
@@ -0,0 +1,234 @@
+/*
+ * File:         sound/soc/blackfin/bf5xx-ad73311.c
+ * Author:       Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created:      Thur Sep 25 2008
+ * Description:  Board driver for ad73311 sound chip
+ *
+ * Modified:
+ *               Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+
+#include <asm/blackfin.h>
+#include <asm/cacheflush.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <asm/portmux.h>
+
+#include "../codecs/ad73311.h"
+#include "bf5xx-sport.h"
+#include "bf5xx-i2s-pcm.h"
+
+#if CONFIG_SND_BF5XX_SPORT_NUM == 0
+#define bfin_write_SPORT_TCR1	bfin_write_SPORT0_TCR1
+#define bfin_read_SPORT_TCR1	bfin_read_SPORT0_TCR1
+#define bfin_write_SPORT_TCR2	bfin_write_SPORT0_TCR2
+#define bfin_write_SPORT_TX16	bfin_write_SPORT0_TX16
+#define bfin_read_SPORT_STAT	bfin_read_SPORT0_STAT
+#else
+#define bfin_write_SPORT_TCR1	bfin_write_SPORT1_TCR1
+#define bfin_read_SPORT_TCR1	bfin_read_SPORT1_TCR1
+#define bfin_write_SPORT_TCR2	bfin_write_SPORT1_TCR2
+#define bfin_write_SPORT_TX16	bfin_write_SPORT1_TX16
+#define bfin_read_SPORT_STAT	bfin_read_SPORT1_STAT
+#endif
+
+#define GPIO_SE CONFIG_SND_BFIN_AD73311_SE
+
+static struct snd_soc_card bf5xx_ad73311;
+
+static int snd_ad73311_startup(void)
+{
+	pr_debug("%s enter\n", __func__);
+
+	/* Pull up SE pin on AD73311L */
+	gpio_set_value(GPIO_SE, 1);
+	return 0;
+}
+
+static int snd_ad73311_configure(void)
+{
+	unsigned short ctrl_regs[6];
+	unsigned short status = 0;
+	int count = 0;
+
+	/* DMCLK = MCLK = 16.384 MHz
+	 * SCLK = DMCLK/8 = 2.048 MHz
+	 * Sample Rate = DMCLK/2048  = 8 KHz
+	 */
+	ctrl_regs[0] = AD_CONTROL | AD_WRITE | CTRL_REG_B | REGB_MCDIV(0) | \
+			REGB_SCDIV(0) | REGB_DIRATE(0);
+	ctrl_regs[1] = AD_CONTROL | AD_WRITE | CTRL_REG_C | REGC_PUDEV | \
+			REGC_PUADC | REGC_PUDAC | REGC_PUREF | REGC_REFUSE ;
+	ctrl_regs[2] = AD_CONTROL | AD_WRITE | CTRL_REG_D | REGD_OGS(2) | \
+			REGD_IGS(2);
+	ctrl_regs[3] = AD_CONTROL | AD_WRITE | CTRL_REG_E | REGE_DA(0x1f);
+	ctrl_regs[4] = AD_CONTROL | AD_WRITE | CTRL_REG_F | REGF_SEEN ;
+	ctrl_regs[5] = AD_CONTROL | AD_WRITE | CTRL_REG_A | REGA_MODE_DATA;
+
+	local_irq_disable();
+	snd_ad73311_startup();
+	udelay(1);
+
+	bfin_write_SPORT_TCR1(TFSR);
+	bfin_write_SPORT_TCR2(0xF);
+	SSYNC();
+
+	/* SPORT Tx Register is a 8 x 16 FIFO, all the data can be put to
+	 * FIFO before enable SPORT to transfer the data
+	 */
+	for (count = 0; count < 6; count++)
+		bfin_write_SPORT_TX16(ctrl_regs[count]);
+	SSYNC();
+	bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() | TSPEN);
+	SSYNC();
+
+	/* When TUVF is set, the data is already send out */
+	while (!(status & TUVF) && ++count < 10000) {
+		udelay(1);
+		status = bfin_read_SPORT_STAT();
+		SSYNC();
+	}
+	bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() & ~TSPEN);
+	SSYNC();
+	local_irq_enable();
+
+	if (count >= 10000) {
+		printk(KERN_ERR "ad73311: failed to configure codec\n");
+		return -1;
+	}
+	return 0;
+}
+
+static int bf5xx_probe(struct platform_device *pdev)
+{
+	int err;
+	if (gpio_request(GPIO_SE, "AD73311_SE")) {
+		printk(KERN_ERR "%s: Failed ro request GPIO_%d\n", __func__, GPIO_SE);
+		return -EBUSY;
+	}
+
+	gpio_direction_output(GPIO_SE, 0);
+
+	err = snd_ad73311_configure();
+	if (err < 0)
+		return -EFAULT;
+
+	return 0;
+}
+
+static int bf5xx_ad73311_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	pr_debug("%s enter\n", __func__);
+	snd_soc_dai_set_drvdata(cpu_dai, sport_handle);
+	return 0;
+}
+
+static int bf5xx_ad73311_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret = 0;
+
+	pr_debug("%s rate %d format %x\n", __func__, params_rate(params),
+		params_format(params));
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+
+static struct snd_soc_ops bf5xx_ad73311_ops = {
+	.startup = bf5xx_ad73311_startup,
+	.hw_params = bf5xx_ad73311_hw_params,
+};
+
+static struct snd_soc_dai_link bf5xx_ad73311_dai = {
+	.name = "ad73311",
+	.stream_name = "AD73311",
+	.cpu_dai_name = "bf5xx-i2s",
+	.codec_dai_name = "ad73311-hifi",
+	.platform_name = "bfin-pcm-audio",
+	.codec_name = "ad73311-codec",
+	.ops = &bf5xx_ad73311_ops,
+};
+
+static struct snd_soc_card bf5xx_ad73311 = {
+	.name = "bf5xx_ad73311",
+	.probe = bf5xx_probe,
+	.dai_link = &bf5xx_ad73311_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *bf5xx_ad73311_snd_device;
+
+static int __init bf5xx_ad73311_init(void)
+{
+	int ret;
+
+	pr_debug("%s enter\n", __func__);
+	bf5xx_ad73311_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!bf5xx_ad73311_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(bf5xx_ad73311_snd_device, &bf5xx_ad73311);
+	ret = platform_device_add(bf5xx_ad73311_snd_device);
+
+	if (ret)
+		platform_device_put(bf5xx_ad73311_snd_device);
+
+	return ret;
+}
+
+static void __exit bf5xx_ad73311_exit(void)
+{
+	pr_debug("%s enter\n", __func__);
+	platform_device_unregister(bf5xx_ad73311_snd_device);
+}
+
+module_init(bf5xx_ad73311_init);
+module_exit(bf5xx_ad73311_exit);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ALSA SoC AD73311 Blackfin");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/blackfin/bf5xx-i2s-pcm.c b/linux/sound/soc/blackfin/bf5xx-i2s-pcm.c
new file mode 100644
index 0000000..890a0dc
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-i2s-pcm.c
@@ -0,0 +1,317 @@
+/*
+ * File:         sound/soc/blackfin/bf5xx-i2s-pcm.c
+ * Author:       Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created:      Tue June 06 2008
+ * Description:  DMA driver for i2s codec
+ *
+ * Modified:
+ *               Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/gfp.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+
+#include "bf5xx-i2s-pcm.h"
+#include "bf5xx-sport.h"
+
+static void bf5xx_dma_irq(void *data)
+{
+	struct snd_pcm_substream *pcm = data;
+	snd_pcm_period_elapsed(pcm);
+}
+
+static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
+	.info			= SNDRV_PCM_INFO_INTERLEAVED |
+				   SNDRV_PCM_INFO_MMAP |
+				   SNDRV_PCM_INFO_MMAP_VALID |
+				   SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
+				   SNDRV_PCM_FMTBIT_S24_LE |
+				   SNDRV_PCM_FMTBIT_S32_LE,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 0x10000,
+	.periods_min		= 1,
+	.periods_max		= PAGE_SIZE/32,
+	.buffer_bytes_max	= 0x20000, /* 128 kbytes */
+	.fifo_size		= 16,
+};
+
+static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
+	snd_pcm_lib_malloc_pages(substream, size);
+
+	return 0;
+}
+
+static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_free_pages(substream);
+
+	return 0;
+}
+
+static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+	int period_bytes = frames_to_bytes(runtime, runtime->period_size);
+
+	pr_debug("%s enter\n", __func__);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
+		sport_config_tx_dma(sport, runtime->dma_area,
+			runtime->periods, period_bytes);
+	} else {
+		sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
+		sport_config_rx_dma(sport, runtime->dma_area,
+			runtime->periods, period_bytes);
+	}
+
+	return 0;
+}
+
+static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+	int ret = 0;
+
+	pr_debug("%s enter\n", __func__);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			sport_tx_start(sport);
+		else
+			sport_rx_start(sport);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			sport_tx_stop(sport);
+		else
+			sport_rx_stop(sport);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+	unsigned int diff;
+	snd_pcm_uframes_t frames;
+	pr_debug("%s enter\n", __func__);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		diff = sport_curr_offset_tx(sport);
+		frames = bytes_to_frames(substream->runtime, diff);
+	} else {
+		diff = sport_curr_offset_rx(sport);
+		frames = bytes_to_frames(substream->runtime, diff);
+	}
+	return frames;
+}
+
+static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int ret;
+
+	pr_debug("%s enter\n", __func__);
+	snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
+
+	ret = snd_pcm_hw_constraint_integer(runtime, \
+			SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0)
+		goto out;
+
+	if (sport_handle != NULL)
+		runtime->private_data = sport_handle;
+	else {
+		pr_err("sport_handle is NULL\n");
+		return -1;
+	}
+	return 0;
+
+ out:
+	return ret;
+}
+
+static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
+	struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	size_t size = vma->vm_end - vma->vm_start;
+	vma->vm_start = (unsigned long)runtime->dma_area;
+	vma->vm_end = vma->vm_start + size;
+	vma->vm_flags |=  VM_SHARED;
+
+	return 0 ;
+}
+
+static struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
+	.open		= bf5xx_pcm_open,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= bf5xx_pcm_hw_params,
+	.hw_free	= bf5xx_pcm_hw_free,
+	.prepare	= bf5xx_pcm_prepare,
+	.trigger	= bf5xx_pcm_trigger,
+	.pointer	= bf5xx_pcm_pointer,
+	.mmap		= bf5xx_pcm_mmap,
+};
+
+static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+	buf->area = dma_alloc_coherent(pcm->card->dev, size,
+			&buf->addr, GFP_KERNEL);
+	if (!buf->area) {
+		pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
+		return -ENOMEM;
+	}
+	buf->bytes = size;
+
+	pr_debug("%s, area:%p, size:0x%08lx\n", __func__,
+		buf->area, buf->bytes);
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+		sport_handle->tx_buf = buf->area;
+	else
+		sport_handle->rx_buf = buf->area;
+
+	return 0;
+}
+
+static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+		dma_free_coherent(NULL, buf->bytes, buf->area, 0);
+		buf->area = NULL;
+	}
+	if (sport_handle)
+		sport_done(sport_handle);
+}
+
+static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
+
+int bf5xx_pcm_i2s_new(struct snd_card *card, struct snd_soc_dai *dai,
+	struct snd_pcm *pcm)
+{
+	int ret = 0;
+
+	pr_debug("%s enter\n", __func__);
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &bf5xx_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+	if (dai->driver->playback.channels_min) {
+		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_PLAYBACK);
+		if (ret)
+			goto out;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_CAPTURE);
+		if (ret)
+			goto out;
+	}
+ out:
+	return ret;
+}
+
+static struct snd_soc_platform_driver bf5xx_i2s_soc_platform = {
+	.ops		= &bf5xx_pcm_i2s_ops,
+	.pcm_new	= bf5xx_pcm_i2s_new,
+	.pcm_free	= bf5xx_pcm_free_dma_buffers,
+};
+
+static int __devinit bfin_i2s_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &bf5xx_i2s_soc_platform);
+}
+
+static int __devexit bfin_i2s_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver bfin_i2s_pcm_driver = {
+	.driver = {
+			.name = "bfin-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = bfin_i2s_soc_platform_probe,
+	.remove = __devexit_p(bfin_i2s_soc_platform_remove),
+};
+
+static int __init snd_bfin_i2s_pcm_init(void)
+{
+	return platform_driver_register(&bfin_i2s_pcm_driver);
+}
+module_init(snd_bfin_i2s_pcm_init);
+
+static void __exit snd_bfin_i2s_pcm_exit(void)
+{
+	platform_driver_unregister(&bfin_i2s_pcm_driver);
+}
+module_exit(snd_bfin_i2s_pcm_exit);
+
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/blackfin/bf5xx-i2s-pcm.h b/linux/sound/soc/blackfin/bf5xx-i2s-pcm.h
new file mode 100644
index 0000000..0c2c5a6
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-i2s-pcm.h
@@ -0,0 +1,26 @@
+/*
+ * linux/sound/arm/bf5xx-i2s-pcm.h -- ALSA PCM interface for the Blackfin
+ *
+ * Copyright 2007 Analog Device Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_I2S_PCM_H
+#define _BF5XX_I2S_PCM_H
+
+struct bf5xx_pcm_dma_params {
+	char *name;			/* stream identifier */
+};
+
+struct bf5xx_gpio {
+	u32 sys;
+	u32 rx;
+	u32 tx;
+	u32 clk;
+	u32 frm;
+};
+
+#endif
diff --git a/linux/sound/soc/blackfin/bf5xx-i2s.c b/linux/sound/soc/blackfin/bf5xx-i2s.c
new file mode 100644
index 0000000..d453b1e
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-i2s.c
@@ -0,0 +1,334 @@
+/*
+ * File:         sound/soc/blackfin/bf5xx-i2s.c
+ * Author:       Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created:      Tue June 06 2008
+ * Description:  Blackfin I2S CPU DAI driver
+ *
+ * Modified:
+ *               Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/irq.h>
+#include <asm/portmux.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+
+#include "bf5xx-sport.h"
+
+struct bf5xx_i2s_port {
+	u16 tcr1;
+	u16 rcr1;
+	u16 tcr2;
+	u16 rcr2;
+	int configured;
+};
+
+static struct bf5xx_i2s_port bf5xx_i2s;
+static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
+
+static struct sport_param sport_params[2] = {
+	{
+		.dma_rx_chan	= CH_SPORT0_RX,
+		.dma_tx_chan	= CH_SPORT0_TX,
+		.err_irq	= IRQ_SPORT0_ERROR,
+		.regs		= (struct sport_register *)SPORT0_TCR1,
+	},
+	{
+		.dma_rx_chan	= CH_SPORT1_RX,
+		.dma_tx_chan	= CH_SPORT1_TX,
+		.err_irq	= IRQ_SPORT1_ERROR,
+		.regs		= (struct sport_register *)SPORT1_TCR1,
+	}
+};
+
+/*
+ * Setting the TFS pin selector for SPORT 0 based on whether the selected
+ * port id F or G. If the port is F then no conflict should exist for the
+ * TFS. When Port G is selected and EMAC then there is a conflict between
+ * the PHY interrupt line and TFS.  Current settings prevent the conflict
+ * by ignoring the TFS pin when Port G is selected. This allows both
+ * codecs and EMAC using Port G concurrently.
+ */
+#ifdef CONFIG_BF527_SPORT0_PORTG
+#define LOCAL_SPORT0_TFS (0)
+#else
+#define LOCAL_SPORT0_TFS (P_SPORT0_TFS)
+#endif
+
+static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS,
+		P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0},
+		{P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI,
+		P_SPORT1_RSCLK, P_SPORT1_TFS, 0} };
+
+static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+		unsigned int fmt)
+{
+	int ret = 0;
+
+	/* interface format:support I2S,slave mode */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		bf5xx_i2s.tcr1 |= TFSR | TCKFE;
+		bf5xx_i2s.rcr1 |= RFSR | RCKFE;
+		bf5xx_i2s.tcr2 |= TSFSE;
+		bf5xx_i2s.rcr2 |= RSFSE;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		bf5xx_i2s.tcr1 |= TFSR;
+		bf5xx_i2s.rcr1 |= RFSR;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		ret = -EINVAL;
+		break;
+	default:
+		printk(KERN_ERR "%s: Unknown DAI format type\n", __func__);
+		ret = -EINVAL;
+		break;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+	case SND_SOC_DAIFMT_CBM_CFS:
+	case SND_SOC_DAIFMT_CBS_CFM:
+		ret = -EINVAL;
+		break;
+	default:
+		printk(KERN_ERR "%s: Unknown DAI master type\n", __func__);
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	int ret = 0;
+
+	bf5xx_i2s.tcr2 &= ~0x1f;
+	bf5xx_i2s.rcr2 &= ~0x1f;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		bf5xx_i2s.tcr2 |= 15;
+		bf5xx_i2s.rcr2 |= 15;
+		sport_handle->wdsize = 2;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		bf5xx_i2s.tcr2 |= 23;
+		bf5xx_i2s.rcr2 |= 23;
+		sport_handle->wdsize = 3;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		bf5xx_i2s.tcr2 |= 31;
+		bf5xx_i2s.rcr2 |= 31;
+		sport_handle->wdsize = 4;
+		break;
+	}
+
+	if (!bf5xx_i2s.configured) {
+		/*
+		 * TX and RX are not independent,they are enabled at the
+		 * same time, even if only one side is running. So, we
+		 * need to configure both of them at the time when the first
+		 * stream is opened.
+		 *
+		 * CPU DAI:slave mode.
+		 */
+		bf5xx_i2s.configured = 1;
+		ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1,
+				      bf5xx_i2s.rcr2, 0, 0);
+		if (ret) {
+			pr_err("SPORT is busy!\n");
+			return -EBUSY;
+		}
+
+		ret = sport_config_tx(sport_handle, bf5xx_i2s.tcr1,
+				      bf5xx_i2s.tcr2, 0, 0);
+		if (ret) {
+			pr_err("SPORT is busy!\n");
+			return -EBUSY;
+		}
+	}
+
+	return 0;
+}
+
+static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	pr_debug("%s enter\n", __func__);
+	/* No active stream, SPORT is allowed to be configured again. */
+	if (!dai->active)
+		bf5xx_i2s.configured = 0;
+}
+
+static int bf5xx_i2s_probe(struct snd_soc_dai *dai)
+{
+	pr_debug("%s enter\n", __func__);
+	if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) {
+		pr_err("Requesting Peripherals failed\n");
+		return -EFAULT;
+	}
+
+	/* request DMA for SPORT */
+	sport_handle = sport_init(&sport_params[sport_num], 4, \
+			2 * sizeof(u32), NULL);
+	if (!sport_handle) {
+		peripheral_free_list(&sport_req[sport_num][0]);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int bf5xx_i2s_remove(struct snd_soc_dai *dai)
+{
+	pr_debug("%s enter\n", __func__);
+	peripheral_free_list(&sport_req[sport_num][0]);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int bf5xx_i2s_suspend(struct snd_soc_dai *dai)
+{
+
+	pr_debug("%s : sport %d\n", __func__, dai->id);
+
+	if (dai->capture_active)
+		sport_rx_stop(sport_handle);
+	if (dai->playback_active)
+		sport_tx_stop(sport_handle);
+	return 0;
+}
+
+static int bf5xx_i2s_resume(struct snd_soc_dai *dai)
+{
+	int ret;
+
+	pr_debug("%s : sport %d\n", __func__, dai->id);
+
+	ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1,
+				      bf5xx_i2s.rcr2, 0, 0);
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		return -EBUSY;
+	}
+
+	ret = sport_config_tx(sport_handle, bf5xx_i2s.tcr1,
+				      bf5xx_i2s.tcr2, 0, 0);
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+#else
+#define bf5xx_i2s_suspend	NULL
+#define bf5xx_i2s_resume	NULL
+#endif
+
+#define BF5XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+		SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
+		SNDRV_PCM_RATE_96000)
+
+#define BF5XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\
+	SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops bf5xx_i2s_dai_ops = {
+	.shutdown	= bf5xx_i2s_shutdown,
+	.hw_params	= bf5xx_i2s_hw_params,
+	.set_fmt	= bf5xx_i2s_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver bf5xx_i2s_dai = {
+	.probe = bf5xx_i2s_probe,
+	.remove = bf5xx_i2s_remove,
+	.suspend = bf5xx_i2s_suspend,
+	.resume = bf5xx_i2s_resume,
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = BF5XX_I2S_RATES,
+		.formats = BF5XX_I2S_FORMATS,},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = BF5XX_I2S_RATES,
+		.formats = BF5XX_I2S_FORMATS,},
+	.ops = &bf5xx_i2s_dai_ops,
+};
+
+static int bfin_i2s_drv_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_dai(&pdev->dev, &bf5xx_i2s_dai);
+}
+
+static int __devexit bfin_i2s_drv_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dai(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver bfin_i2s_driver = {
+	.probe = bfin_i2s_drv_probe,
+	.remove = __devexit_p(bfin_i2s_drv_remove),
+
+	.driver = {
+		.name = "bf5xx-i2s",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init bfin_i2s_init(void)
+{
+	return platform_driver_register(&bfin_i2s_driver);
+}
+
+static void __exit bfin_i2s_exit(void)
+{
+	platform_driver_unregister(&bfin_i2s_driver);
+}
+
+module_init(bfin_i2s_init);
+module_exit(bfin_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("I2S driver for ADI Blackfin");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/blackfin/bf5xx-sport.c b/linux/sound/soc/blackfin/bf5xx-sport.c
new file mode 100644
index 0000000..99051ff
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-sport.c
@@ -0,0 +1,1014 @@
+/*
+ * File:         bf5xx_sport.c
+ * Based on:
+ * Author:       Roy Huang <roy.huang@analog.com>
+ *
+ * Created:      Tue Sep 21 10:52:42 CEST 2004
+ * Description:
+ *               Blackfin SPORT Driver
+ *
+ *               Copyright 2004-2007 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/gpio.h>
+#include <linux/bug.h>
+#include <asm/portmux.h>
+#include <asm/dma.h>
+#include <asm/blackfin.h>
+#include <asm/cacheflush.h>
+
+#include "bf5xx-sport.h"
+/* delay between frame sync pulse and first data bit in multichannel mode */
+#define FRAME_DELAY (1<<12)
+
+struct sport_device *sport_handle;
+EXPORT_SYMBOL(sport_handle);
+/* note: multichannel is in units of 8 channels,
+ * tdm_count is # channels NOT / 8 ! */
+int sport_set_multichannel(struct sport_device *sport,
+		int tdm_count, u32 mask, int packed)
+{
+	pr_debug("%s tdm_count=%d mask:0x%08x packed=%d\n", __func__,
+			tdm_count, mask, packed);
+
+	if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
+		return -EBUSY;
+
+	if (tdm_count & 0x7)
+		return -EINVAL;
+
+	if (tdm_count > 32)
+		return -EINVAL; /* Only support less than 32 channels now */
+
+	if (tdm_count) {
+		sport->regs->mcmc1 = ((tdm_count>>3)-1) << 12;
+		sport->regs->mcmc2 = FRAME_DELAY | MCMEN | \
+				(packed ? (MCDTXPE|MCDRXPE) : 0);
+
+		sport->regs->mtcs0 = mask;
+		sport->regs->mrcs0 = mask;
+		sport->regs->mtcs1 = 0;
+		sport->regs->mrcs1 = 0;
+		sport->regs->mtcs2 = 0;
+		sport->regs->mrcs2 = 0;
+		sport->regs->mtcs3 = 0;
+		sport->regs->mrcs3 = 0;
+	} else {
+		sport->regs->mcmc1 = 0;
+		sport->regs->mcmc2 = 0;
+
+		sport->regs->mtcs0 = 0;
+		sport->regs->mrcs0 = 0;
+	}
+
+	sport->regs->mtcs1 = 0; sport->regs->mtcs2 = 0; sport->regs->mtcs3 = 0;
+	sport->regs->mrcs1 = 0; sport->regs->mrcs2 = 0; sport->regs->mrcs3 = 0;
+
+	SSYNC();
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_set_multichannel);
+
+int sport_config_rx(struct sport_device *sport, unsigned int rcr1,
+		unsigned int rcr2, unsigned int clkdiv, unsigned int fsdiv)
+{
+	if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
+		return -EBUSY;
+
+	sport->regs->rcr1 = rcr1;
+	sport->regs->rcr2 = rcr2;
+	sport->regs->rclkdiv = clkdiv;
+	sport->regs->rfsdiv = fsdiv;
+
+	SSYNC();
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_config_rx);
+
+int sport_config_tx(struct sport_device *sport, unsigned int tcr1,
+		unsigned int tcr2, unsigned int clkdiv, unsigned int fsdiv)
+{
+	if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
+		return -EBUSY;
+
+	sport->regs->tcr1 = tcr1;
+	sport->regs->tcr2 = tcr2;
+	sport->regs->tclkdiv = clkdiv;
+	sport->regs->tfsdiv = fsdiv;
+
+	SSYNC();
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_config_tx);
+
+static void setup_desc(struct dmasg *desc, void *buf, int fragcount,
+		size_t fragsize, unsigned int cfg,
+		unsigned int x_count, unsigned int ycount, size_t wdsize)
+{
+
+	int i;
+
+	for (i = 0; i < fragcount; ++i) {
+		desc[i].next_desc_addr  = &(desc[i + 1]);
+		desc[i].start_addr = (unsigned long)buf + i*fragsize;
+		desc[i].cfg = cfg;
+		desc[i].x_count = x_count;
+		desc[i].x_modify = wdsize;
+		desc[i].y_count = ycount;
+		desc[i].y_modify = wdsize;
+	}
+
+	/* make circular */
+	desc[fragcount-1].next_desc_addr = desc;
+
+	pr_debug("setup desc: desc0=%p, next0=%p, desc1=%p,"
+		"next1=%p\nx_count=%x,y_count=%x,addr=0x%lx,cfs=0x%x\n",
+		desc, desc[0].next_desc_addr,
+		desc+1, desc[1].next_desc_addr,
+		desc[0].x_count, desc[0].y_count,
+		desc[0].start_addr, desc[0].cfg);
+}
+
+static int sport_start(struct sport_device *sport)
+{
+	enable_dma(sport->dma_rx_chan);
+	enable_dma(sport->dma_tx_chan);
+	sport->regs->rcr1 |= RSPEN;
+	sport->regs->tcr1 |= TSPEN;
+	SSYNC();
+
+	return 0;
+}
+
+static int sport_stop(struct sport_device *sport)
+{
+	sport->regs->tcr1 &= ~TSPEN;
+	sport->regs->rcr1 &= ~RSPEN;
+	SSYNC();
+
+	disable_dma(sport->dma_rx_chan);
+	disable_dma(sport->dma_tx_chan);
+	return 0;
+}
+
+static inline int sport_hook_rx_dummy(struct sport_device *sport)
+{
+	struct dmasg *desc, temp_desc;
+	unsigned long flags;
+
+	BUG_ON(sport->dummy_rx_desc == NULL);
+	BUG_ON(sport->curr_rx_desc == sport->dummy_rx_desc);
+
+	/* Maybe the dummy buffer descriptor ring is damaged */
+	sport->dummy_rx_desc->next_desc_addr = sport->dummy_rx_desc + 1;
+
+	local_irq_save(flags);
+	desc = get_dma_next_desc_ptr(sport->dma_rx_chan);
+	/* Copy the descriptor which will be damaged to backup */
+	temp_desc = *desc;
+	desc->x_count = sport->dummy_count / 2;
+	desc->y_count = 0;
+	desc->next_desc_addr = sport->dummy_rx_desc;
+	local_irq_restore(flags);
+	/* Waiting for dummy buffer descriptor is already hooked*/
+	while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) -
+			sizeof(struct dmasg)) != sport->dummy_rx_desc)
+		continue;
+	sport->curr_rx_desc = sport->dummy_rx_desc;
+	/* Restore the damaged descriptor */
+	*desc = temp_desc;
+
+	return 0;
+}
+
+static inline int sport_rx_dma_start(struct sport_device *sport, int dummy)
+{
+	if (dummy) {
+		sport->dummy_rx_desc->next_desc_addr = sport->dummy_rx_desc;
+		sport->curr_rx_desc = sport->dummy_rx_desc;
+	} else
+		sport->curr_rx_desc = sport->dma_rx_desc;
+
+	set_dma_next_desc_addr(sport->dma_rx_chan, sport->curr_rx_desc);
+	set_dma_x_count(sport->dma_rx_chan, 0);
+	set_dma_x_modify(sport->dma_rx_chan, 0);
+	set_dma_config(sport->dma_rx_chan, (DMAFLOW_LARGE | NDSIZE_9 | \
+				WDSIZE_32 | WNR));
+	set_dma_curr_addr(sport->dma_rx_chan, sport->curr_rx_desc->start_addr);
+	SSYNC();
+
+	return 0;
+}
+
+static inline int sport_tx_dma_start(struct sport_device *sport, int dummy)
+{
+	if (dummy) {
+		sport->dummy_tx_desc->next_desc_addr = sport->dummy_tx_desc;
+		sport->curr_tx_desc = sport->dummy_tx_desc;
+	} else
+		sport->curr_tx_desc = sport->dma_tx_desc;
+
+	set_dma_next_desc_addr(sport->dma_tx_chan, sport->curr_tx_desc);
+	set_dma_x_count(sport->dma_tx_chan, 0);
+	set_dma_x_modify(sport->dma_tx_chan, 0);
+	set_dma_config(sport->dma_tx_chan,
+			(DMAFLOW_LARGE | NDSIZE_9 | WDSIZE_32));
+	set_dma_curr_addr(sport->dma_tx_chan, sport->curr_tx_desc->start_addr);
+	SSYNC();
+
+	return 0;
+}
+
+int sport_rx_start(struct sport_device *sport)
+{
+	unsigned long flags;
+	pr_debug("%s enter\n", __func__);
+	if (sport->rx_run)
+		return -EBUSY;
+	if (sport->tx_run) {
+		/* tx is running, rx is not running */
+		BUG_ON(sport->dma_rx_desc == NULL);
+		BUG_ON(sport->curr_rx_desc != sport->dummy_rx_desc);
+		local_irq_save(flags);
+		while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) -
+			sizeof(struct dmasg)) != sport->dummy_rx_desc)
+			continue;
+		sport->dummy_rx_desc->next_desc_addr = sport->dma_rx_desc;
+		local_irq_restore(flags);
+		sport->curr_rx_desc = sport->dma_rx_desc;
+	} else {
+		sport_tx_dma_start(sport, 1);
+		sport_rx_dma_start(sport, 0);
+		sport_start(sport);
+	}
+
+	sport->rx_run = 1;
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_rx_start);
+
+int sport_rx_stop(struct sport_device *sport)
+{
+	pr_debug("%s enter\n", __func__);
+
+	if (!sport->rx_run)
+		return 0;
+	if (sport->tx_run) {
+		/* TX dma is still running, hook the dummy buffer */
+		sport_hook_rx_dummy(sport);
+	} else {
+		/* Both rx and tx dma will be stopped */
+		sport_stop(sport);
+		sport->curr_rx_desc = NULL;
+		sport->curr_tx_desc = NULL;
+	}
+
+	sport->rx_run = 0;
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_rx_stop);
+
+static inline int sport_hook_tx_dummy(struct sport_device *sport)
+{
+	struct dmasg *desc, temp_desc;
+	unsigned long flags;
+
+	BUG_ON(sport->dummy_tx_desc == NULL);
+	BUG_ON(sport->curr_tx_desc == sport->dummy_tx_desc);
+
+	sport->dummy_tx_desc->next_desc_addr = sport->dummy_tx_desc + 1;
+
+	/* Shorten the time on last normal descriptor */
+	local_irq_save(flags);
+	desc = get_dma_next_desc_ptr(sport->dma_tx_chan);
+	/* Store the descriptor which will be damaged */
+	temp_desc = *desc;
+	desc->x_count = sport->dummy_count / 2;
+	desc->y_count = 0;
+	desc->next_desc_addr = sport->dummy_tx_desc;
+	local_irq_restore(flags);
+	/* Waiting for dummy buffer descriptor is already hooked*/
+	while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) - \
+			sizeof(struct dmasg)) != sport->dummy_tx_desc)
+		continue;
+	sport->curr_tx_desc = sport->dummy_tx_desc;
+	/* Restore the damaged descriptor */
+	*desc = temp_desc;
+
+	return 0;
+}
+
+int sport_tx_start(struct sport_device *sport)
+{
+	unsigned long flags;
+	pr_debug("%s: tx_run:%d, rx_run:%d\n", __func__,
+			sport->tx_run, sport->rx_run);
+	if (sport->tx_run)
+		return -EBUSY;
+	if (sport->rx_run) {
+		BUG_ON(sport->dma_tx_desc == NULL);
+		BUG_ON(sport->curr_tx_desc != sport->dummy_tx_desc);
+		/* Hook the normal buffer descriptor */
+		local_irq_save(flags);
+		while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) -
+			sizeof(struct dmasg)) != sport->dummy_tx_desc)
+			continue;
+		sport->dummy_tx_desc->next_desc_addr = sport->dma_tx_desc;
+		local_irq_restore(flags);
+		sport->curr_tx_desc = sport->dma_tx_desc;
+	} else {
+
+		sport_tx_dma_start(sport, 0);
+		/* Let rx dma run the dummy buffer */
+		sport_rx_dma_start(sport, 1);
+		sport_start(sport);
+	}
+	sport->tx_run = 1;
+	return 0;
+}
+EXPORT_SYMBOL(sport_tx_start);
+
+int sport_tx_stop(struct sport_device *sport)
+{
+	if (!sport->tx_run)
+		return 0;
+	if (sport->rx_run) {
+		/* RX is still running, hook the dummy buffer */
+		sport_hook_tx_dummy(sport);
+	} else {
+		/* Both rx and tx dma stopped */
+		sport_stop(sport);
+		sport->curr_rx_desc = NULL;
+		sport->curr_tx_desc = NULL;
+	}
+
+	sport->tx_run = 0;
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_tx_stop);
+
+static inline int compute_wdsize(size_t wdsize)
+{
+	switch (wdsize) {
+	case 1:
+		return WDSIZE_8;
+	case 2:
+		return WDSIZE_16;
+	case 4:
+	default:
+		return WDSIZE_32;
+	}
+}
+
+int sport_config_rx_dma(struct sport_device *sport, void *buf,
+		int fragcount, size_t fragsize)
+{
+	unsigned int x_count;
+	unsigned int y_count;
+	unsigned int cfg;
+	dma_addr_t addr;
+
+	pr_debug("%s buf:%p, frag:%d, fragsize:0x%lx\n", __func__, \
+			buf, fragcount, fragsize);
+
+	x_count = fragsize / sport->wdsize;
+	y_count = 0;
+
+	/* for fragments larger than 64k words we use 2d dma,
+	 * denote fragecount as two numbers' mutliply and both of them
+	 * are less than 64k.*/
+	if (x_count >= 0x10000) {
+		int i, count = x_count;
+
+		for (i = 16; i > 0; i--) {
+			x_count = 1 << i;
+			if ((count & (x_count - 1)) == 0) {
+				y_count = count >> i;
+				if (y_count < 0x10000)
+					break;
+			}
+		}
+		if (i == 0)
+			return -EINVAL;
+	}
+	pr_debug("%s(x_count:0x%x, y_count:0x%x)\n", __func__,
+			x_count, y_count);
+
+	if (sport->dma_rx_desc)
+		dma_free_coherent(NULL, sport->rx_desc_bytes,
+					sport->dma_rx_desc, 0);
+
+	/* Allocate a new descritor ring as current one. */
+	sport->dma_rx_desc = dma_alloc_coherent(NULL, \
+			fragcount * sizeof(struct dmasg), &addr, 0);
+	sport->rx_desc_bytes = fragcount * sizeof(struct dmasg);
+
+	if (!sport->dma_rx_desc) {
+		pr_err("Failed to allocate memory for rx desc\n");
+		return -ENOMEM;
+	}
+
+	sport->rx_buf = buf;
+	sport->rx_fragsize = fragsize;
+	sport->rx_frags = fragcount;
+
+	cfg     = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | WNR | \
+		  (DESC_ELEMENT_COUNT << 8); /* large descriptor mode */
+
+	if (y_count != 0)
+		cfg |= DMA2D;
+
+	setup_desc(sport->dma_rx_desc, buf, fragcount, fragsize,
+			cfg|DMAEN, x_count, y_count, sport->wdsize);
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_config_rx_dma);
+
+int sport_config_tx_dma(struct sport_device *sport, void *buf, \
+		int fragcount, size_t fragsize)
+{
+	unsigned int x_count;
+	unsigned int y_count;
+	unsigned int cfg;
+	dma_addr_t addr;
+
+	pr_debug("%s buf:%p, fragcount:%d, fragsize:0x%lx\n",
+			__func__, buf, fragcount, fragsize);
+
+	x_count = fragsize/sport->wdsize;
+	y_count = 0;
+
+	/* for fragments larger than 64k words we use 2d dma,
+	 * denote fragecount as two numbers' mutliply and both of them
+	 * are less than 64k.*/
+	if (x_count >= 0x10000) {
+		int i, count = x_count;
+
+		for (i = 16; i > 0; i--) {
+			x_count = 1 << i;
+			if ((count & (x_count - 1)) == 0) {
+				y_count = count >> i;
+				if (y_count < 0x10000)
+					break;
+			}
+		}
+		if (i == 0)
+			return -EINVAL;
+	}
+	pr_debug("%s x_count:0x%x, y_count:0x%x\n", __func__,
+			x_count, y_count);
+
+
+	if (sport->dma_tx_desc) {
+		dma_free_coherent(NULL, sport->tx_desc_bytes, \
+				sport->dma_tx_desc, 0);
+	}
+
+	sport->dma_tx_desc = dma_alloc_coherent(NULL, \
+			fragcount * sizeof(struct dmasg), &addr, 0);
+	sport->tx_desc_bytes = fragcount * sizeof(struct dmasg);
+	if (!sport->dma_tx_desc) {
+		pr_err("Failed to allocate memory for tx desc\n");
+		return -ENOMEM;
+	}
+
+	sport->tx_buf = buf;
+	sport->tx_fragsize = fragsize;
+	sport->tx_frags = fragcount;
+	cfg     = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | \
+		  (DESC_ELEMENT_COUNT << 8); /* large descriptor mode */
+
+	if (y_count != 0)
+		cfg |= DMA2D;
+
+	setup_desc(sport->dma_tx_desc, buf, fragcount, fragsize,
+			cfg|DMAEN, x_count, y_count, sport->wdsize);
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_config_tx_dma);
+
+/* setup dummy dma descriptor ring, which don't generate interrupts,
+ * the x_modify is set to 0 */
+static int sport_config_rx_dummy(struct sport_device *sport)
+{
+	struct dmasg *desc;
+	unsigned config;
+
+	pr_debug("%s entered\n", __func__);
+	if (L1_DATA_A_LENGTH)
+		desc = l1_data_sram_zalloc(2 * sizeof(*desc));
+	else {
+		dma_addr_t addr;
+		desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0);
+		memset(desc, 0, 2 * sizeof(*desc));
+	}
+	if (desc == NULL) {
+		pr_err("Failed to allocate memory for dummy rx desc\n");
+		return -ENOMEM;
+	}
+	sport->dummy_rx_desc = desc;
+	desc->start_addr = (unsigned long)sport->dummy_buf;
+	config = DMAFLOW_LARGE | NDSIZE_9 | compute_wdsize(sport->wdsize)
+		 | WNR | DMAEN;
+	desc->cfg = config;
+	desc->x_count = sport->dummy_count/sport->wdsize;
+	desc->x_modify = sport->wdsize;
+	desc->y_count = 0;
+	desc->y_modify = 0;
+	memcpy(desc+1, desc, sizeof(*desc));
+	desc->next_desc_addr = desc + 1;
+	desc[1].next_desc_addr = desc;
+	return 0;
+}
+
+static int sport_config_tx_dummy(struct sport_device *sport)
+{
+	struct dmasg *desc;
+	unsigned int config;
+
+	pr_debug("%s entered\n", __func__);
+
+	if (L1_DATA_A_LENGTH)
+		desc = l1_data_sram_zalloc(2 * sizeof(*desc));
+	else {
+		dma_addr_t addr;
+		desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0);
+		memset(desc, 0, 2 * sizeof(*desc));
+	}
+	if (!desc) {
+		pr_err("Failed to allocate memory for dummy tx desc\n");
+		return -ENOMEM;
+	}
+	sport->dummy_tx_desc = desc;
+	desc->start_addr = (unsigned long)sport->dummy_buf + \
+		sport->dummy_count;
+	config = DMAFLOW_LARGE | NDSIZE_9 |
+		 compute_wdsize(sport->wdsize) | DMAEN;
+	desc->cfg = config;
+	desc->x_count = sport->dummy_count/sport->wdsize;
+	desc->x_modify = sport->wdsize;
+	desc->y_count = 0;
+	desc->y_modify = 0;
+	memcpy(desc+1, desc, sizeof(*desc));
+	desc->next_desc_addr = desc + 1;
+	desc[1].next_desc_addr = desc;
+	return 0;
+}
+
+unsigned long sport_curr_offset_rx(struct sport_device *sport)
+{
+	unsigned long curr = get_dma_curr_addr(sport->dma_rx_chan);
+
+	return (unsigned char *)curr - sport->rx_buf;
+}
+EXPORT_SYMBOL(sport_curr_offset_rx);
+
+unsigned long sport_curr_offset_tx(struct sport_device *sport)
+{
+	unsigned long curr = get_dma_curr_addr(sport->dma_tx_chan);
+
+	return (unsigned char *)curr - sport->tx_buf;
+}
+EXPORT_SYMBOL(sport_curr_offset_tx);
+
+void sport_incfrag(struct sport_device *sport, int *frag, int tx)
+{
+	++(*frag);
+	if (tx == 1 && *frag == sport->tx_frags)
+		*frag = 0;
+
+	if (tx == 0 && *frag == sport->rx_frags)
+		*frag = 0;
+}
+EXPORT_SYMBOL(sport_incfrag);
+
+void sport_decfrag(struct sport_device *sport, int *frag, int tx)
+{
+	--(*frag);
+	if (tx == 1 && *frag == 0)
+		*frag = sport->tx_frags;
+
+	if (tx == 0 && *frag == 0)
+		*frag = sport->rx_frags;
+}
+EXPORT_SYMBOL(sport_decfrag);
+
+static int sport_check_status(struct sport_device *sport,
+		unsigned int *sport_stat,
+		unsigned int *rx_stat,
+		unsigned int *tx_stat)
+{
+	int status = 0;
+
+	if (sport_stat) {
+		SSYNC();
+		status = sport->regs->stat;
+		if (status & (TOVF|TUVF|ROVF|RUVF))
+			sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF));
+		SSYNC();
+		*sport_stat = status;
+	}
+
+	if (rx_stat) {
+		SSYNC();
+		status = get_dma_curr_irqstat(sport->dma_rx_chan);
+		if (status & (DMA_DONE|DMA_ERR))
+			clear_dma_irqstat(sport->dma_rx_chan);
+		SSYNC();
+		*rx_stat = status;
+	}
+
+	if (tx_stat) {
+		SSYNC();
+		status = get_dma_curr_irqstat(sport->dma_tx_chan);
+		if (status & (DMA_DONE|DMA_ERR))
+			clear_dma_irqstat(sport->dma_tx_chan);
+		SSYNC();
+		*tx_stat = status;
+	}
+
+	return 0;
+}
+
+int  sport_dump_stat(struct sport_device *sport, char *buf, size_t len)
+{
+	int ret;
+
+	ret = snprintf(buf, len,
+			"sts: 0x%04x\n"
+			"rx dma %d sts: 0x%04x tx dma %d sts: 0x%04x\n",
+			sport->regs->stat,
+			sport->dma_rx_chan,
+			get_dma_curr_irqstat(sport->dma_rx_chan),
+			sport->dma_tx_chan,
+			get_dma_curr_irqstat(sport->dma_tx_chan));
+	buf += ret;
+	len -= ret;
+
+	ret += snprintf(buf, len,
+			"curr_rx_desc:0x%p, curr_tx_desc:0x%p\n"
+			"dma_rx_desc:0x%p, dma_tx_desc:0x%p\n"
+			"dummy_rx_desc:0x%p, dummy_tx_desc:0x%p\n",
+			sport->curr_rx_desc, sport->curr_tx_desc,
+			sport->dma_rx_desc, sport->dma_tx_desc,
+			sport->dummy_rx_desc, sport->dummy_tx_desc);
+
+	return ret;
+}
+
+static irqreturn_t rx_handler(int irq, void *dev_id)
+{
+	unsigned int rx_stat;
+	struct sport_device *sport = dev_id;
+
+	pr_debug("%s enter\n", __func__);
+	sport_check_status(sport, NULL, &rx_stat, NULL);
+	if (!(rx_stat & DMA_DONE))
+		pr_err("rx dma is already stopped\n");
+
+	if (sport->rx_callback) {
+		sport->rx_callback(sport->rx_data);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static irqreturn_t tx_handler(int irq, void *dev_id)
+{
+	unsigned int tx_stat;
+	struct sport_device *sport = dev_id;
+	pr_debug("%s enter\n", __func__);
+	sport_check_status(sport, NULL, NULL, &tx_stat);
+	if (!(tx_stat & DMA_DONE)) {
+		pr_err("tx dma is already stopped\n");
+		return IRQ_HANDLED;
+	}
+	if (sport->tx_callback) {
+		sport->tx_callback(sport->tx_data);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static irqreturn_t err_handler(int irq, void *dev_id)
+{
+	unsigned int status = 0;
+	struct sport_device *sport = dev_id;
+
+	pr_debug("%s\n", __func__);
+	if (sport_check_status(sport, &status, NULL, NULL)) {
+		pr_err("error checking status ??");
+		return IRQ_NONE;
+	}
+
+	if (status & (TOVF|TUVF|ROVF|RUVF)) {
+		pr_info("sport status error:%s%s%s%s\n",
+				status & TOVF ? " TOVF" : "",
+				status & TUVF ? " TUVF" : "",
+				status & ROVF ? " ROVF" : "",
+				status & RUVF ? " RUVF" : "");
+		if (status & TOVF || status & TUVF) {
+			disable_dma(sport->dma_tx_chan);
+			if (sport->tx_run)
+				sport_tx_dma_start(sport, 0);
+			else
+				sport_tx_dma_start(sport, 1);
+			enable_dma(sport->dma_tx_chan);
+		} else {
+			disable_dma(sport->dma_rx_chan);
+			if (sport->rx_run)
+				sport_rx_dma_start(sport, 0);
+			else
+				sport_rx_dma_start(sport, 1);
+			enable_dma(sport->dma_rx_chan);
+		}
+	}
+	status = sport->regs->stat;
+	if (status & (TOVF|TUVF|ROVF|RUVF))
+		sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF));
+	SSYNC();
+
+	if (sport->err_callback)
+		sport->err_callback(sport->err_data);
+
+	return IRQ_HANDLED;
+}
+
+int sport_set_rx_callback(struct sport_device *sport,
+		       void (*rx_callback)(void *), void *rx_data)
+{
+	BUG_ON(rx_callback == NULL);
+	sport->rx_callback = rx_callback;
+	sport->rx_data = rx_data;
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_set_rx_callback);
+
+int sport_set_tx_callback(struct sport_device *sport,
+		void (*tx_callback)(void *), void *tx_data)
+{
+	BUG_ON(tx_callback == NULL);
+	sport->tx_callback = tx_callback;
+	sport->tx_data = tx_data;
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_set_tx_callback);
+
+int sport_set_err_callback(struct sport_device *sport,
+		void (*err_callback)(void *), void *err_data)
+{
+	BUG_ON(err_callback == NULL);
+	sport->err_callback = err_callback;
+	sport->err_data = err_data;
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_set_err_callback);
+
+struct sport_device *sport_init(struct sport_param *param, unsigned wdsize,
+		unsigned dummy_count, void *private_data)
+{
+	int ret;
+	struct sport_device *sport;
+	pr_debug("%s enter\n", __func__);
+	BUG_ON(param == NULL);
+	BUG_ON(wdsize == 0 || dummy_count == 0);
+	sport = kmalloc(sizeof(struct sport_device), GFP_KERNEL);
+	if (!sport) {
+		pr_err("Failed to allocate for sport device\n");
+		return NULL;
+	}
+
+	memset(sport, 0, sizeof(struct sport_device));
+	sport->dma_rx_chan = param->dma_rx_chan;
+	sport->dma_tx_chan = param->dma_tx_chan;
+	sport->err_irq = param->err_irq;
+	sport->regs = param->regs;
+	sport->private_data = private_data;
+
+	if (request_dma(sport->dma_rx_chan, "SPORT RX Data") == -EBUSY) {
+		pr_err("Failed to request RX dma %d\n", \
+				sport->dma_rx_chan);
+		goto __init_err1;
+	}
+	if (set_dma_callback(sport->dma_rx_chan, rx_handler, sport) != 0) {
+		pr_err("Failed to request RX irq %d\n", \
+				sport->dma_rx_chan);
+		goto __init_err2;
+	}
+
+	if (request_dma(sport->dma_tx_chan, "SPORT TX Data") == -EBUSY) {
+		pr_err("Failed to request TX dma %d\n", \
+				sport->dma_tx_chan);
+		goto __init_err2;
+	}
+
+	if (set_dma_callback(sport->dma_tx_chan, tx_handler, sport) != 0) {
+		pr_err("Failed to request TX irq %d\n", \
+				sport->dma_tx_chan);
+		goto __init_err3;
+	}
+
+	if (request_irq(sport->err_irq, err_handler, IRQF_SHARED, "SPORT err",
+			sport) < 0) {
+		pr_err("Failed to request err irq:%d\n", \
+				sport->err_irq);
+		goto __init_err3;
+	}
+
+	pr_err("dma rx:%d tx:%d, err irq:%d, regs:%p\n",
+			sport->dma_rx_chan, sport->dma_tx_chan,
+			sport->err_irq, sport->regs);
+
+	sport->wdsize = wdsize;
+	sport->dummy_count = dummy_count;
+
+	if (L1_DATA_A_LENGTH)
+		sport->dummy_buf = l1_data_sram_zalloc(dummy_count * 2);
+	else
+		sport->dummy_buf = kzalloc(dummy_count * 2, GFP_KERNEL);
+	if (sport->dummy_buf == NULL) {
+		pr_err("Failed to allocate dummy buffer\n");
+		goto __error;
+	}
+
+	ret = sport_config_rx_dummy(sport);
+	if (ret) {
+		pr_err("Failed to config rx dummy ring\n");
+		goto __error;
+	}
+	ret = sport_config_tx_dummy(sport);
+	if (ret) {
+		pr_err("Failed to config tx dummy ring\n");
+		goto __error;
+	}
+
+	return sport;
+__error:
+	free_irq(sport->err_irq, sport);
+__init_err3:
+	free_dma(sport->dma_tx_chan);
+__init_err2:
+	free_dma(sport->dma_rx_chan);
+__init_err1:
+	kfree(sport);
+	return NULL;
+}
+EXPORT_SYMBOL(sport_init);
+
+void sport_done(struct sport_device *sport)
+{
+	if (sport == NULL)
+		return;
+
+	sport_stop(sport);
+	if (sport->dma_rx_desc)
+		dma_free_coherent(NULL, sport->rx_desc_bytes,
+			sport->dma_rx_desc, 0);
+	if (sport->dma_tx_desc)
+		dma_free_coherent(NULL, sport->tx_desc_bytes,
+			sport->dma_tx_desc, 0);
+
+#if L1_DATA_A_LENGTH != 0
+	l1_data_sram_free(sport->dummy_rx_desc);
+	l1_data_sram_free(sport->dummy_tx_desc);
+	l1_data_sram_free(sport->dummy_buf);
+#else
+	dma_free_coherent(NULL, 2*sizeof(struct dmasg),
+		sport->dummy_rx_desc, 0);
+	dma_free_coherent(NULL, 2*sizeof(struct dmasg),
+		sport->dummy_tx_desc, 0);
+	kfree(sport->dummy_buf);
+#endif
+	free_dma(sport->dma_rx_chan);
+	free_dma(sport->dma_tx_chan);
+	free_irq(sport->err_irq, sport);
+
+	kfree(sport);
+		sport = NULL;
+}
+EXPORT_SYMBOL(sport_done);
+
+/*
+* It is only used to send several bytes when dma is not enabled
+ * sport controller is configured but not enabled.
+ * Multichannel cannot works with pio mode */
+/* Used by ac97 to write and read codec register */
+int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \
+		u8 *in_data, int len)
+{
+	unsigned short dma_config;
+	unsigned short status;
+	unsigned long flags;
+	unsigned long wait = 0;
+
+	pr_debug("%s enter, out_data:%p, in_data:%p len:%d\n", \
+			__func__, out_data, in_data, len);
+	pr_debug("tcr1:0x%04x, tcr2:0x%04x, tclkdiv:0x%04x, tfsdiv:0x%04x\n"
+			"mcmc1:0x%04x, mcmc2:0x%04x\n",
+			sport->regs->tcr1, sport->regs->tcr2,
+			sport->regs->tclkdiv, sport->regs->tfsdiv,
+			sport->regs->mcmc1, sport->regs->mcmc2);
+	flush_dcache_range((unsigned)out_data, (unsigned)(out_data + len));
+
+	/* Enable tx dma */
+	dma_config = (RESTART | WDSIZE_16 | DI_EN);
+	set_dma_start_addr(sport->dma_tx_chan, (unsigned long)out_data);
+	set_dma_x_count(sport->dma_tx_chan, len/2);
+	set_dma_x_modify(sport->dma_tx_chan, 2);
+	set_dma_config(sport->dma_tx_chan, dma_config);
+	enable_dma(sport->dma_tx_chan);
+
+	if (in_data != NULL) {
+		invalidate_dcache_range((unsigned)in_data, \
+				(unsigned)(in_data + len));
+		/* Enable rx dma */
+		dma_config = (RESTART | WDSIZE_16 | WNR | DI_EN);
+		set_dma_start_addr(sport->dma_rx_chan, (unsigned long)in_data);
+		set_dma_x_count(sport->dma_rx_chan, len/2);
+		set_dma_x_modify(sport->dma_rx_chan, 2);
+		set_dma_config(sport->dma_rx_chan, dma_config);
+		enable_dma(sport->dma_rx_chan);
+	}
+
+	local_irq_save(flags);
+	sport->regs->tcr1 |= TSPEN;
+	sport->regs->rcr1 |= RSPEN;
+	SSYNC();
+
+	status = get_dma_curr_irqstat(sport->dma_tx_chan);
+	while (status & DMA_RUN) {
+		udelay(1);
+		status = get_dma_curr_irqstat(sport->dma_tx_chan);
+		pr_debug("DMA status:0x%04x\n", status);
+		if (wait++ > 100)
+			goto __over;
+	}
+	status = sport->regs->stat;
+	wait = 0;
+
+	while (!(status & TXHRE)) {
+		pr_debug("sport status:0x%04x\n", status);
+		udelay(1);
+		status = *(unsigned short *)&sport->regs->stat;
+		if (wait++ > 1000)
+			goto __over;
+	}
+	/* Wait for the last byte sent out */
+	udelay(20);
+	pr_debug("sport status:0x%04x\n", status);
+
+__over:
+	sport->regs->tcr1 &= ~TSPEN;
+	sport->regs->rcr1 &= ~RSPEN;
+	SSYNC();
+	disable_dma(sport->dma_tx_chan);
+	/* Clear the status */
+	clear_dma_irqstat(sport->dma_tx_chan);
+	if (in_data != NULL) {
+		disable_dma(sport->dma_rx_chan);
+		clear_dma_irqstat(sport->dma_rx_chan);
+	}
+	SSYNC();
+	local_irq_restore(flags);
+
+	return 0;
+}
+EXPORT_SYMBOL(sport_send_and_recv);
+
+MODULE_AUTHOR("Roy Huang");
+MODULE_DESCRIPTION("SPORT driver for ADI Blackfin");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/blackfin/bf5xx-sport.h b/linux/sound/soc/blackfin/bf5xx-sport.h
new file mode 100644
index 0000000..a86e8cc
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-sport.h
@@ -0,0 +1,168 @@
+/*
+ * File:         bf5xx_ac97_sport.h
+ * Based on:
+ * Author:       Roy Huang <roy.huang@analog.com>
+ *
+ * Created:
+ * Description:
+ *
+ *               Copyright 2004-2007 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#ifndef __BF5XX_SPORT_H__
+#define __BF5XX_SPORT_H__
+
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <asm/dma.h>
+#include <asm/bfin_sport.h>
+
+#define DESC_ELEMENT_COUNT 9
+
+struct sport_device {
+	int dma_rx_chan;
+	int dma_tx_chan;
+	int err_irq;
+	struct sport_register *regs;
+
+	unsigned char *rx_buf;
+	unsigned char *tx_buf;
+	unsigned int rx_fragsize;
+	unsigned int tx_fragsize;
+	unsigned int rx_frags;
+	unsigned int tx_frags;
+	unsigned int wdsize;
+
+	/* for dummy dma transfer */
+	void *dummy_buf;
+	unsigned int dummy_count;
+
+	/* DMA descriptor ring head of current audio stream*/
+	struct dmasg *dma_rx_desc;
+	struct dmasg *dma_tx_desc;
+	unsigned int rx_desc_bytes;
+	unsigned int tx_desc_bytes;
+
+	unsigned int rx_run:1; /* rx is running */
+	unsigned int tx_run:1; /* tx is running */
+
+	struct dmasg *dummy_rx_desc;
+	struct dmasg *dummy_tx_desc;
+
+	struct dmasg *curr_rx_desc;
+	struct dmasg *curr_tx_desc;
+
+	int rx_curr_frag;
+	int tx_curr_frag;
+
+	unsigned int rcr1;
+	unsigned int rcr2;
+	int rx_tdm_count;
+
+	unsigned int tcr1;
+	unsigned int tcr2;
+	int tx_tdm_count;
+
+	void (*rx_callback)(void *data);
+	void *rx_data;
+	void (*tx_callback)(void *data);
+	void *tx_data;
+	void (*err_callback)(void *data);
+	void *err_data;
+	unsigned char *tx_dma_buf;
+	unsigned char *rx_dma_buf;
+#ifdef CONFIG_SND_BF5XX_MMAP_SUPPORT
+	dma_addr_t tx_dma_phy;
+	dma_addr_t rx_dma_phy;
+	int tx_pos;/*pcm sample count*/
+	int rx_pos;
+	unsigned int tx_buffer_size;
+	unsigned int rx_buffer_size;
+	int tx_delay_pos;
+	int once;
+#endif
+	void *private_data;
+};
+
+extern struct sport_device *sport_handle;
+
+struct sport_param {
+	int dma_rx_chan;
+	int dma_tx_chan;
+	int err_irq;
+	struct sport_register *regs;
+};
+
+struct sport_device *sport_init(struct sport_param *param, unsigned wdsize,
+		unsigned dummy_count, void *private_data);
+
+void sport_done(struct sport_device *sport);
+
+/* first use these ...*/
+
+/* note: multichannel is in units of 8 channels, tdm_count is number of channels
+ *  NOT / 8 ! all channels are enabled by default */
+int sport_set_multichannel(struct sport_device *sport, int tdm_count,
+		u32 mask, int packed);
+
+int sport_config_rx(struct sport_device *sport,
+		unsigned int rcr1, unsigned int rcr2,
+		unsigned int clkdiv, unsigned int fsdiv);
+
+int sport_config_tx(struct sport_device *sport,
+		unsigned int tcr1, unsigned int tcr2,
+		unsigned int clkdiv, unsigned int fsdiv);
+
+/* ... then these: */
+
+/* buffer size (in bytes) == fragcount * fragsize_bytes */
+
+/* this is not a very general api, it sets the dma to 2d autobuffer mode */
+
+int sport_config_rx_dma(struct sport_device *sport, void *buf,
+		int fragcount, size_t fragsize_bytes);
+
+int sport_config_tx_dma(struct sport_device *sport, void *buf,
+		int fragcount, size_t fragsize_bytes);
+
+int sport_tx_start(struct sport_device *sport);
+int sport_tx_stop(struct sport_device *sport);
+int sport_rx_start(struct sport_device *sport);
+int sport_rx_stop(struct sport_device *sport);
+
+/* for use in interrupt handler */
+unsigned long sport_curr_offset_rx(struct sport_device *sport);
+unsigned long sport_curr_offset_tx(struct sport_device *sport);
+
+void sport_incfrag(struct sport_device *sport, int *frag, int tx);
+void sport_decfrag(struct sport_device *sport, int *frag, int tx);
+
+int sport_set_rx_callback(struct sport_device *sport,
+		       void (*rx_callback)(void *), void *rx_data);
+int sport_set_tx_callback(struct sport_device *sport,
+		       void (*tx_callback)(void *), void *tx_data);
+int sport_set_err_callback(struct sport_device *sport,
+		       void (*err_callback)(void *), void *err_data);
+
+int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \
+		u8 *in_data, int len);
+#endif /* BF53X_SPORT_H */
diff --git a/linux/sound/soc/blackfin/bf5xx-ssm2602.c b/linux/sound/soc/blackfin/bf5xx-ssm2602.c
new file mode 100644
index 0000000..36f2769
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-ssm2602.c
@@ -0,0 +1,166 @@
+/*
+ * File:         sound/soc/blackfin/bf5xx-ssm2602.c
+ * Author:       Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created:      Tue June 06 2008
+ * Description:  board driver for SSM2602 sound chip
+ *
+ * Modified:
+ *               Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+
+#include <asm/dma.h>
+#include <asm/portmux.h>
+#include <linux/gpio.h>
+#include "../codecs/ssm2602.h"
+#include "bf5xx-sport.h"
+#include "bf5xx-i2s-pcm.h"
+
+static struct snd_soc_card bf5xx_ssm2602;
+
+static int bf5xx_ssm2602_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	pr_debug("%s enter\n", __func__);
+	snd_soc_dai_set_drvdata(cpu_dai, sport_handle);
+	return 0;
+}
+
+static int bf5xx_ssm2602_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int clk = 0;
+	int ret = 0;
+
+	pr_debug("%s rate %d format %x\n", __func__, params_rate(params),
+		params_format(params));
+	/*
+	 * If you are using a crystal source which frequency is not 12MHz
+	 * then modify the below case statement with frequency of the crystal.
+	 *
+	 * If you are using the SPORT to generate clocking then this is
+	 * where to do it.
+	 */
+
+	switch (params_rate(params)) {
+	case 8000:
+	case 16000:
+	case 48000:
+	case 96000:
+	case 11025:
+	case 22050:
+	case 44100:
+		clk = 12000000;
+		break;
+	}
+
+	/*
+	 * CODEC is master for BCLK and LRC in this configuration.
+	 */
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, SSM2602_SYSCLK, clk,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops bf5xx_ssm2602_ops = {
+	.startup = bf5xx_ssm2602_startup,
+	.hw_params = bf5xx_ssm2602_hw_params,
+};
+
+static struct snd_soc_dai_link bf5xx_ssm2602_dai = {
+	.name = "ssm2602",
+	.stream_name = "SSM2602",
+	.cpu_dai_name = "bf5xx-i2s",
+	.codec_dai_name = "ssm2602-hifi",
+	.platform_name = "bf5xx-pcm-audio",
+	.codec_name = "ssm2602-codec.0-0x1b",
+	.ops = &bf5xx_ssm2602_ops,
+};
+
+static struct snd_soc_card bf5xx_ssm2602 = {
+	.name = "bf5xx_ssm2602",
+	.dai_link = &bf5xx_ssm2602_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *bf5xx_ssm2602_snd_device;
+
+static int __init bf5xx_ssm2602_init(void)
+{
+	int ret;
+
+	pr_debug("%s enter\n", __func__);
+	bf5xx_ssm2602_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!bf5xx_ssm2602_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(bf5xx_ssm2602_snd_device, &bf5xx_ssm2602);
+	ret = platform_device_add(bf5xx_ssm2602_snd_device);
+
+	if (ret)
+		platform_device_put(bf5xx_ssm2602_snd_device);
+
+	return ret;
+}
+
+static void __exit bf5xx_ssm2602_exit(void)
+{
+	pr_debug("%s enter\n", __func__);
+	platform_device_unregister(bf5xx_ssm2602_snd_device);
+}
+
+module_init(bf5xx_ssm2602_init);
+module_exit(bf5xx_ssm2602_exit);
+
+/* Module information */
+MODULE_AUTHOR("Cliff Cai");
+MODULE_DESCRIPTION("ALSA SoC SSM2602 BF527-EZKIT");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/blackfin/bf5xx-tdm-pcm.c b/linux/sound/soc/blackfin/bf5xx-tdm-pcm.c
new file mode 100644
index 0000000..74cf759
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-tdm-pcm.c
@@ -0,0 +1,351 @@
+/*
+ * File:         sound/soc/blackfin/bf5xx-tdm-pcm.c
+ * Author:       Barry Song <Barry.Song@analog.com>
+ *
+ * Created:      Tue June 06 2009
+ * Description:  DMA driver for tdm codec
+ *
+ * Modified:
+ *               Copyright 2009 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/gfp.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+
+#include "bf5xx-tdm-pcm.h"
+#include "bf5xx-tdm.h"
+#include "bf5xx-sport.h"
+
+#define PCM_BUFFER_MAX  0x8000
+#define FRAGMENT_SIZE_MIN  (4*1024)
+#define FRAGMENTS_MIN  2
+#define FRAGMENTS_MAX  32
+
+static void bf5xx_dma_irq(void *data)
+{
+	struct snd_pcm_substream *pcm = data;
+	snd_pcm_period_elapsed(pcm);
+}
+
+static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
+	.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_RESUME),
+	.formats =          SNDRV_PCM_FMTBIT_S32_LE,
+	.rates =            SNDRV_PCM_RATE_48000,
+	.channels_min =     2,
+	.channels_max =     8,
+	.buffer_bytes_max = PCM_BUFFER_MAX,
+	.period_bytes_min = FRAGMENT_SIZE_MIN,
+	.period_bytes_max = PCM_BUFFER_MAX/2,
+	.periods_min =      FRAGMENTS_MIN,
+	.periods_max =      FRAGMENTS_MAX,
+};
+
+static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
+	snd_pcm_lib_malloc_pages(substream, size * 4);
+
+	return 0;
+}
+
+static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_free_pages(substream);
+
+	return 0;
+}
+
+static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+	int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size);
+
+	fragsize_bytes /= runtime->channels;
+	/* inflate the fragsize to match the dma width of SPORT */
+	fragsize_bytes *= 8;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
+		sport_config_tx_dma(sport, runtime->dma_area,
+			runtime->periods, fragsize_bytes);
+	} else {
+		sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
+		sport_config_rx_dma(sport, runtime->dma_area,
+			runtime->periods, fragsize_bytes);
+	}
+
+	return 0;
+}
+
+static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			sport_tx_start(sport);
+		else
+			sport_rx_start(sport);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			sport_tx_stop(sport);
+		else
+			sport_rx_stop(sport);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+	unsigned int diff;
+	snd_pcm_uframes_t frames;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		diff = sport_curr_offset_tx(sport);
+		frames = diff / (8*4); /* 32 bytes per frame */
+	} else {
+		diff = sport_curr_offset_rx(sport);
+		frames = diff / (8*4);
+	}
+	return frames;
+}
+
+static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int ret = 0;
+
+	snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
+
+	ret = snd_pcm_hw_constraint_integer(runtime,
+		SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0)
+		goto out;
+
+	if (sport_handle != NULL)
+		runtime->private_data = sport_handle;
+	else {
+		pr_err("sport_handle is NULL\n");
+		ret = -ENODEV;
+	}
+out:
+	return ret;
+}
+
+static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
+	snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct sport_device *sport = runtime->private_data;
+	struct bf5xx_tdm_port *tdm_port = sport->private_data;
+	unsigned int *src;
+	unsigned int *dst;
+	int i;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		src = buf;
+		dst = (unsigned int *)substream->runtime->dma_area;
+
+		dst += pos * 8;
+		while (count--) {
+			for (i = 0; i < substream->runtime->channels; i++)
+				*(dst + tdm_port->tx_map[i]) = *src++;
+			dst += 8;
+		}
+	} else {
+		src = (unsigned int *)substream->runtime->dma_area;
+		dst = buf;
+
+		src += pos * 8;
+		while (count--) {
+			for (i = 0; i < substream->runtime->channels; i++)
+				*dst++ = *(src + tdm_port->rx_map[i]);
+			src += 8;
+		}
+	}
+
+	return 0;
+}
+
+static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
+	int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
+{
+	unsigned char *buf = substream->runtime->dma_area;
+	buf += pos * 8 * 4;
+	memset(buf, '\0', count * 8 * 4);
+
+	return 0;
+}
+
+
+struct snd_pcm_ops bf5xx_pcm_tdm_ops = {
+	.open           = bf5xx_pcm_open,
+	.ioctl          = snd_pcm_lib_ioctl,
+	.hw_params      = bf5xx_pcm_hw_params,
+	.hw_free        = bf5xx_pcm_hw_free,
+	.prepare        = bf5xx_pcm_prepare,
+	.trigger        = bf5xx_pcm_trigger,
+	.pointer        = bf5xx_pcm_pointer,
+	.copy           = bf5xx_pcm_copy,
+	.silence        = bf5xx_pcm_silence,
+};
+
+static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+	buf->area = dma_alloc_coherent(pcm->card->dev, size * 4,
+		&buf->addr, GFP_KERNEL);
+	if (!buf->area) {
+		pr_err("Failed to allocate dma memory - Please increase uncached DMA memory region\n");
+		return -ENOMEM;
+	}
+	buf->bytes = size;
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+		sport_handle->tx_buf = buf->area;
+	else
+		sport_handle->rx_buf = buf->area;
+
+	return 0;
+}
+
+static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+		dma_free_coherent(NULL, buf->bytes, buf->area, 0);
+		buf->area = NULL;
+	}
+	if (sport_handle)
+		sport_done(sport_handle);
+}
+
+static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
+
+static int bf5xx_pcm_tdm_new(struct snd_card *card, struct snd_soc_dai *dai,
+	struct snd_pcm *pcm)
+{
+	int ret = 0;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &bf5xx_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+	if (dai->driver->playback.channels_min) {
+		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_PLAYBACK);
+		if (ret)
+			goto out;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_CAPTURE);
+		if (ret)
+			goto out;
+	}
+out:
+	return ret;
+}
+
+static struct snd_soc_platform_driver bf5xx_tdm_soc_platform = {
+	.ops        = &bf5xx_pcm_tdm_ops,
+	.pcm_new        = bf5xx_pcm_tdm_new,
+	.pcm_free       = bf5xx_pcm_free_dma_buffers,
+};
+
+static int __devinit bf5xx_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &bf5xx_tdm_soc_platform);
+}
+
+static int __devexit bf5xx_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver bfin_tdm_driver = {
+	.driver = {
+			.name = "bf5xx-tdm-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = bf5xx_soc_platform_probe,
+	.remove = __devexit_p(bf5xx_soc_platform_remove),
+};
+
+static int __init snd_bfin_tdm_init(void)
+{
+	return platform_driver_register(&bfin_tdm_driver);
+}
+module_init(snd_bfin_tdm_init);
+
+static void __exit snd_bfin_tdm_exit(void)
+{
+	platform_driver_unregister(&bfin_tdm_driver);
+}
+module_exit(snd_bfin_tdm_exit);
+
+MODULE_AUTHOR("Barry Song");
+MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/blackfin/bf5xx-tdm-pcm.h b/linux/sound/soc/blackfin/bf5xx-tdm-pcm.h
new file mode 100644
index 0000000..7f8cc01
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-tdm-pcm.h
@@ -0,0 +1,18 @@
+/*
+ * sound/soc/blackfin/bf5xx-tdm-pcm.h -- ALSA PCM interface for the Blackfin
+ *
+ * Copyright 2009 Analog Device Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_TDM_PCM_H
+#define _BF5XX_TDM_PCM_H
+
+struct bf5xx_pcm_dma_params {
+	char *name;                     /* stream identifier */
+};
+
+#endif
diff --git a/linux/sound/soc/blackfin/bf5xx-tdm.c b/linux/sound/soc/blackfin/bf5xx-tdm.c
new file mode 100644
index 0000000..1251239
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-tdm.c
@@ -0,0 +1,367 @@
+/*
+ * File:         sound/soc/blackfin/bf5xx-tdm.c
+ * Author:       Barry Song <Barry.Song@analog.com>
+ *
+ * Created:      Thurs June 04 2009
+ * Description:  Blackfin I2S(TDM) CPU DAI driver
+ *              Even though TDM mode can be as part of I2S DAI, but there
+ *              are so much difference in configuration and data flow,
+ *              it's very ugly to integrate I2S and TDM into a module
+ *
+ * Modified:
+ *               Copyright 2009 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <asm/irq.h>
+#include <asm/portmux.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+
+#include "bf5xx-sport.h"
+#include "bf5xx-tdm.h"
+
+static struct bf5xx_tdm_port bf5xx_tdm;
+static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
+
+static struct sport_param sport_params[2] = {
+	{
+		.dma_rx_chan    = CH_SPORT0_RX,
+		.dma_tx_chan    = CH_SPORT0_TX,
+		.err_irq        = IRQ_SPORT0_ERROR,
+		.regs           = (struct sport_register *)SPORT0_TCR1,
+	},
+	{
+		.dma_rx_chan    = CH_SPORT1_RX,
+		.dma_tx_chan    = CH_SPORT1_TX,
+		.err_irq        = IRQ_SPORT1_ERROR,
+		.regs           = (struct sport_register *)SPORT1_TCR1,
+	}
+};
+
+/*
+ * Setting the TFS pin selector for SPORT 0 based on whether the selected
+ * port id F or G. If the port is F then no conflict should exist for the
+ * TFS. When Port G is selected and EMAC then there is a conflict between
+ * the PHY interrupt line and TFS.  Current settings prevent the conflict
+ * by ignoring the TFS pin when Port G is selected. This allows both
+ * codecs and EMAC using Port G concurrently.
+ */
+#ifdef CONFIG_BF527_SPORT0_PORTG
+#define LOCAL_SPORT0_TFS (0)
+#else
+#define LOCAL_SPORT0_TFS (P_SPORT0_TFS)
+#endif
+
+static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS,
+	P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0},
+	   {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI,
+		   P_SPORT1_RSCLK, P_SPORT1_TFS, 0} };
+
+static int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+	unsigned int fmt)
+{
+	int ret = 0;
+
+	/* interface format:support TDM,slave mode */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+		break;
+	default:
+		printk(KERN_ERR "%s: Unknown DAI format type\n", __func__);
+		ret = -EINVAL;
+		break;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+	case SND_SOC_DAIFMT_CBM_CFS:
+	case SND_SOC_DAIFMT_CBS_CFM:
+		ret = -EINVAL;
+		break;
+	default:
+		printk(KERN_ERR "%s: Unknown DAI master type\n", __func__);
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int bf5xx_tdm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	int ret = 0;
+
+	bf5xx_tdm.tcr2 &= ~0x1f;
+	bf5xx_tdm.rcr2 &= ~0x1f;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S32_LE:
+		bf5xx_tdm.tcr2 |= 31;
+		bf5xx_tdm.rcr2 |= 31;
+		sport_handle->wdsize = 4;
+		break;
+		/* at present, we only support 32bit transfer */
+	default:
+		pr_err("not supported PCM format yet\n");
+		return -EINVAL;
+		break;
+	}
+
+	if (!bf5xx_tdm.configured) {
+		/*
+		 * TX and RX are not independent,they are enabled at the
+		 * same time, even if only one side is running. So, we
+		 * need to configure both of them at the time when the first
+		 * stream is opened.
+		 *
+		 * CPU DAI:slave mode.
+		 */
+		ret = sport_config_rx(sport_handle, bf5xx_tdm.rcr1,
+			bf5xx_tdm.rcr2, 0, 0);
+		if (ret) {
+			pr_err("SPORT is busy!\n");
+			return -EBUSY;
+		}
+
+		ret = sport_config_tx(sport_handle, bf5xx_tdm.tcr1,
+			bf5xx_tdm.tcr2, 0, 0);
+		if (ret) {
+			pr_err("SPORT is busy!\n");
+			return -EBUSY;
+		}
+
+		bf5xx_tdm.configured = 1;
+	}
+
+	return 0;
+}
+
+static void bf5xx_tdm_shutdown(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	/* No active stream, SPORT is allowed to be configured again. */
+	if (!dai->active)
+		bf5xx_tdm.configured = 0;
+}
+
+static int bf5xx_tdm_set_channel_map(struct snd_soc_dai *dai,
+		unsigned int tx_num, unsigned int *tx_slot,
+		unsigned int rx_num, unsigned int *rx_slot)
+{
+	int i;
+	unsigned int slot;
+	unsigned int tx_mapped = 0, rx_mapped = 0;
+
+	if ((tx_num > BFIN_TDM_DAI_MAX_SLOTS) ||
+			(rx_num > BFIN_TDM_DAI_MAX_SLOTS))
+		return -EINVAL;
+
+	for (i = 0; i < tx_num; i++) {
+		slot = tx_slot[i];
+		if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
+				(!(tx_mapped & (1 << slot)))) {
+			bf5xx_tdm.tx_map[i] = slot;
+			tx_mapped |= 1 << slot;
+		} else
+			return -EINVAL;
+	}
+	for (i = 0; i < rx_num; i++) {
+		slot = rx_slot[i];
+		if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
+				(!(rx_mapped & (1 << slot)))) {
+			bf5xx_tdm.rx_map[i] = slot;
+			rx_mapped |= 1 << slot;
+		} else
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int bf5xx_tdm_suspend(struct snd_soc_dai *dai)
+{
+	struct sport_device *sport = dai->private_data;
+
+	if (!dai->active)
+		return 0;
+	if (dai->capture_active)
+		sport_rx_stop(sport);
+	if (dai->playback_active)
+		sport_tx_stop(sport);
+	return 0;
+}
+
+static int bf5xx_tdm_resume(struct snd_soc_dai *dai)
+{
+	int ret;
+	struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
+
+	if (!dai->active)
+		return 0;
+
+	ret = sport_set_multichannel(sport, 8, 0xFF, 1);
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		ret = -EBUSY;
+	}
+
+	ret = sport_config_rx(sport, IRFS, 0x1F, 0, 0);
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		ret = -EBUSY;
+	}
+
+	ret = sport_config_tx(sport, ITFS, 0x1F, 0, 0);
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		ret = -EBUSY;
+	}
+
+	return 0;
+}
+
+#else
+#define bf5xx_tdm_suspend      NULL
+#define bf5xx_tdm_resume       NULL
+#endif
+
+static struct snd_soc_dai_ops bf5xx_tdm_dai_ops = {
+	.hw_params      = bf5xx_tdm_hw_params,
+	.set_fmt        = bf5xx_tdm_set_dai_fmt,
+	.shutdown       = bf5xx_tdm_shutdown,
+	.set_channel_map   = bf5xx_tdm_set_channel_map,
+};
+
+static struct snd_soc_dai_driver bf5xx_tdm_dai = {
+	.suspend = bf5xx_tdm_suspend,
+	.resume = bf5xx_tdm_resume,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 8,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE,},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 8,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE,},
+	.ops = &bf5xx_tdm_dai_ops,
+};
+
+static int __devinit bfin_tdm_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) {
+		pr_err("Requesting Peripherals failed\n");
+		return -EFAULT;
+	}
+
+	/* request DMA for SPORT */
+	sport_handle = sport_init(&sport_params[sport_num], 4, \
+		8 * sizeof(u32), NULL);
+	if (!sport_handle) {
+		peripheral_free_list(&sport_req[sport_num][0]);
+		return -ENODEV;
+	}
+
+	/* SPORT works in TDM mode */
+	ret = sport_set_multichannel(sport_handle, 8, 0xFF, 1);
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		ret = -EBUSY;
+		goto sport_config_err;
+	}
+
+	ret = sport_config_rx(sport_handle, IRFS, 0x1F, 0, 0);
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		ret = -EBUSY;
+		goto sport_config_err;
+	}
+
+	ret = sport_config_tx(sport_handle, ITFS, 0x1F, 0, 0);
+	if (ret) {
+		pr_err("SPORT is busy!\n");
+		ret = -EBUSY;
+		goto sport_config_err;
+	}
+
+	ret = snd_soc_register_dai(&pdev->dev, &bf5xx_tdm_dai);
+	if (ret) {
+		pr_err("Failed to register DAI: %d\n", ret);
+		goto sport_config_err;
+	}
+
+	sport_handle->private_data = &bf5xx_tdm;
+	return 0;
+
+sport_config_err:
+	peripheral_free_list(&sport_req[sport_num][0]);
+	return ret;
+}
+
+static int __devexit bfin_tdm_remove(struct platform_device *pdev)
+{
+	peripheral_free_list(&sport_req[sport_num][0]);
+	snd_soc_unregister_dai(&pdev->dev);
+
+	return 0;
+}
+
+static struct platform_driver bfin_tdm_driver = {
+	.probe  = bfin_tdm_probe,
+	.remove = __devexit_p(bfin_tdm_remove),
+	.driver = {
+		.name   = "bfin-tdm",
+		.owner  = THIS_MODULE,
+	},
+};
+
+static int __init bfin_tdm_init(void)
+{
+	return platform_driver_register(&bfin_tdm_driver);
+}
+module_init(bfin_tdm_init);
+
+static void __exit bfin_tdm_exit(void)
+{
+	platform_driver_unregister(&bfin_tdm_driver);
+}
+module_exit(bfin_tdm_exit);
+
+/* Module information */
+MODULE_AUTHOR("Barry Song");
+MODULE_DESCRIPTION("TDM driver for ADI Blackfin");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/blackfin/bf5xx-tdm.h b/linux/sound/soc/blackfin/bf5xx-tdm.h
new file mode 100644
index 0000000..e986a3e
--- /dev/null
+++ b/linux/sound/soc/blackfin/bf5xx-tdm.h
@@ -0,0 +1,23 @@
+/*
+ * sound/soc/blackfin/bf5xx-tdm.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BF5XX_TDM_H
+#define _BF5XX_TDM_H
+
+#define BFIN_TDM_DAI_MAX_SLOTS 8
+struct bf5xx_tdm_port {
+	u16 tcr1;
+	u16 rcr1;
+	u16 tcr2;
+	u16 rcr2;
+	unsigned int tx_map[BFIN_TDM_DAI_MAX_SLOTS];
+	unsigned int rx_map[BFIN_TDM_DAI_MAX_SLOTS];
+	int configured;
+};
+
+#endif
diff --git a/linux/sound/soc/codecs/88pm860x-codec.c b/linux/sound/soc/codecs/88pm860x-codec.c
new file mode 100644
index 0000000..01d19e9
--- /dev/null
+++ b/linux/sound/soc/codecs/88pm860x-codec.c
@@ -0,0 +1,1486 @@
+/*
+ * 88pm860x-codec.c -- 88PM860x ALSA SoC Audio Driver
+ *
+ * Copyright 2010 Marvell International Ltd.
+ * Author: Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/88pm860x.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <sound/jack.h>
+
+#include "88pm860x-codec.h"
+
+#define MAX_NAME_LEN		20
+#define REG_CACHE_SIZE		0x40
+#define REG_CACHE_BASE		0xb0
+
+/* Status Register 1 (0x01) */
+#define REG_STATUS_1		0x01
+#define MIC_STATUS		(1 << 7)
+#define HOOK_STATUS		(1 << 6)
+#define HEADSET_STATUS		(1 << 5)
+
+/* Mic Detection Register (0x37) */
+#define REG_MIC_DET		0x37
+#define CONTINUOUS_POLLING	(3 << 1)
+#define EN_MIC_DET		(1 << 0)
+#define MICDET_MASK		0x07
+
+/* Headset Detection Register (0x38) */
+#define REG_HS_DET		0x38
+#define EN_HS_DET		(1 << 0)
+
+/* Misc2 Register (0x42) */
+#define REG_MISC2		0x42
+#define AUDIO_PLL		(1 << 5)
+#define AUDIO_SECTION_RESET	(1 << 4)
+#define AUDIO_SECTION_ON	(1 << 3)
+
+/* PCM Interface Register 2 (0xb1) */
+#define PCM_INF2_BCLK		(1 << 6)	/* Bit clock polarity */
+#define PCM_INF2_FS		(1 << 5)	/* Frame Sync polarity */
+#define PCM_INF2_MASTER		(1 << 4)	/* Master / Slave */
+#define PCM_INF2_18WL		(1 << 3)	/* 18 / 16 bits */
+#define PCM_GENERAL_I2S		0
+#define PCM_EXACT_I2S		1
+#define PCM_LEFT_I2S		2
+#define PCM_RIGHT_I2S		3
+#define PCM_SHORT_FS		4
+#define PCM_LONG_FS		5
+#define PCM_MODE_MASK		7
+
+/* I2S Interface Register 4 (0xbe) */
+#define I2S_EQU_BYP		(1 << 6)
+
+/* DAC Offset Register (0xcb) */
+#define DAC_MUTE		(1 << 7)
+#define MUTE_LEFT		(1 << 6)
+#define MUTE_RIGHT		(1 << 2)
+
+/* ADC Analog Register 1 (0xd0) */
+#define REG_ADC_ANA_1		0xd0
+#define MIC1BIAS_MASK		0x60
+
+/* Earpiece/Speaker Control Register 2 (0xda) */
+#define REG_EAR2		0xda
+#define RSYNC_CHANGE		(1 << 2)
+
+/* Audio Supplies Register 2 (0xdc) */
+#define REG_SUPPLIES2		0xdc
+#define LDO15_READY		(1 << 4)
+#define LDO15_EN		(1 << 3)
+#define CPUMP_READY		(1 << 2)
+#define CPUMP_EN		(1 << 1)
+#define AUDIO_EN		(1 << 0)
+#define SUPPLY_MASK		(LDO15_EN | CPUMP_EN | AUDIO_EN)
+
+/* Audio Enable Register 1 (0xdd) */
+#define ADC_MOD_RIGHT		(1 << 1)
+#define ADC_MOD_LEFT		(1 << 0)
+
+/* Audio Enable Register 2 (0xde) */
+#define ADC_LEFT		(1 << 5)
+#define ADC_RIGHT		(1 << 4)
+
+/* DAC Enable Register 2 (0xe1) */
+#define DAC_LEFT		(1 << 5)
+#define DAC_RIGHT		(1 << 4)
+#define MODULATOR		(1 << 3)
+
+/* Shorts Register (0xeb) */
+#define REG_SHORTS		0xeb
+#define CLR_SHORT_LO2		(1 << 7)
+#define SHORT_LO2		(1 << 6)
+#define CLR_SHORT_LO1		(1 << 5)
+#define SHORT_LO1		(1 << 4)
+#define CLR_SHORT_HS2		(1 << 3)
+#define SHORT_HS2		(1 << 2)
+#define CLR_SHORT_HS1		(1 << 1)
+#define SHORT_HS1		(1 << 0)
+
+/*
+ * This widget should be just after DAC & PGA in DAPM power-on sequence and
+ * before DAC & PGA in DAPM power-off sequence.
+ */
+#define PM860X_DAPM_OUTPUT(wname, wevent)	\
+{	.id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, \
+	.shift = 0, .invert = 0, .kcontrols = NULL, \
+	.num_kcontrols = 0, .event = wevent, \
+	.event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD, }
+
+struct pm860x_det {
+	struct snd_soc_jack	*hp_jack;
+	struct snd_soc_jack	*mic_jack;
+	int			hp_det;
+	int			mic_det;
+	int			hook_det;
+	int			hs_shrt;
+	int			lo_shrt;
+};
+
+struct pm860x_priv {
+	unsigned int		sysclk;
+	unsigned int		pcmclk;
+	unsigned int		dir;
+	unsigned int		filter;
+	struct snd_soc_codec	*codec;
+	struct i2c_client	*i2c;
+	struct pm860x_chip	*chip;
+	struct pm860x_det	det;
+
+	int			irq[4];
+	unsigned char		name[4][MAX_NAME_LEN];
+	unsigned char		reg_cache[REG_CACHE_SIZE];
+};
+
+/* -9450dB to 0dB in 150dB steps ( mute instead of -9450dB) */
+static const DECLARE_TLV_DB_SCALE(dpga_tlv, -9450, 150, 1);
+
+/* -9dB to 0db in 3dB steps */
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -900, 300, 0);
+
+/* {-23, -17, -13.5, -11, -9, -6, -3, 0}dB */
+static const unsigned int mic_tlv[] = {
+	TLV_DB_RANGE_HEAD(5),
+	0, 0, TLV_DB_SCALE_ITEM(-2300, 0, 0),
+	1, 1, TLV_DB_SCALE_ITEM(-1700, 0, 0),
+	2, 2, TLV_DB_SCALE_ITEM(-1350, 0, 0),
+	3, 3, TLV_DB_SCALE_ITEM(-1100, 0, 0),
+	4, 7, TLV_DB_SCALE_ITEM(-900, 300, 0),
+};
+
+/* {0, 0, 0, -6, 0, 6, 12, 18}dB */
+static const unsigned int aux_tlv[] = {
+	TLV_DB_RANGE_HEAD(2),
+	0, 2, TLV_DB_SCALE_ITEM(0, 0, 0),
+	3, 7, TLV_DB_SCALE_ITEM(-600, 600, 0),
+};
+
+/* {-16, -13, -10, -7, -5.2, -3,3, -2.2, 0}dB, mute instead of -16dB */
+static const unsigned int out_tlv[] = {
+	TLV_DB_RANGE_HEAD(4),
+	0, 3, TLV_DB_SCALE_ITEM(-1600, 300, 1),
+	4, 4, TLV_DB_SCALE_ITEM(-520, 0, 0),
+	5, 5, TLV_DB_SCALE_ITEM(-330, 0, 0),
+	6, 7, TLV_DB_SCALE_ITEM(-220, 220, 0),
+};
+
+static const unsigned int st_tlv[] = {
+	TLV_DB_RANGE_HEAD(8),
+	0, 1, TLV_DB_SCALE_ITEM(-12041, 602, 0),
+	2, 3, TLV_DB_SCALE_ITEM(-11087, 250, 0),
+	4, 5, TLV_DB_SCALE_ITEM(-10643, 158, 0),
+	6, 7, TLV_DB_SCALE_ITEM(-10351, 116, 0),
+	8, 9, TLV_DB_SCALE_ITEM(-10133, 92, 0),
+	10, 13, TLV_DB_SCALE_ITEM(-9958, 70, 0),
+	14, 17, TLV_DB_SCALE_ITEM(-9689, 53, 0),
+	18, 271, TLV_DB_SCALE_ITEM(-9484, 37, 0),
+};
+
+/* Sidetone Gain = M * 2^(-5-N) */
+struct st_gain {
+	unsigned int	db;
+	unsigned int	m;
+	unsigned int	n;
+};
+
+static struct st_gain st_table[] = {
+	{-12041,  1, 15}, {-11439,  1, 14}, {-11087,  3, 15}, {-10837,  1, 13},
+	{-10643,  5, 15}, {-10485,  3, 14}, {-10351,  7, 15}, {-10235,  1, 12},
+	{-10133,  9, 15}, {-10041,  5, 14}, { -9958, 11, 15}, { -9883,  3, 13},
+	{ -9813, 13, 15}, { -9749,  7, 14}, { -9689, 15, 15}, { -9633,  1, 11},
+	{ -9580, 17, 15}, { -9531,  9, 14}, { -9484, 19, 15}, { -9439,  5, 13},
+	{ -9397, 21, 15}, { -9356, 11, 14}, { -9318, 23, 15}, { -9281,  3, 12},
+	{ -9245, 25, 15}, { -9211, 13, 14}, { -9178, 27, 15}, { -9147,  7, 13},
+	{ -9116, 29, 15}, { -9087, 15, 14}, { -9058, 31, 15}, { -9031,  1, 10},
+	{ -8978, 17, 14}, { -8929,  9, 13}, { -8882, 19, 14}, { -8837,  5, 12},
+	{ -8795, 21, 14}, { -8754, 11, 13}, { -8716, 23, 14}, { -8679,  3, 11},
+	{ -8643, 25, 14}, { -8609, 13, 13}, { -8576, 27, 14}, { -8545,  7, 12},
+	{ -8514, 29, 14}, { -8485, 15, 13}, { -8456, 31, 14}, { -8429,  1,  9},
+	{ -8376, 17, 13}, { -8327,  9, 12}, { -8280, 19, 13}, { -8235,  5, 11},
+	{ -8193, 21, 13}, { -8152, 11, 12}, { -8114, 23, 13}, { -8077,  3, 10},
+	{ -8041, 25, 13}, { -8007, 13, 12}, { -7974, 27, 13}, { -7943,  7, 11},
+	{ -7912, 29, 13}, { -7883, 15, 12}, { -7854, 31, 13}, { -7827,  1,  8},
+	{ -7774, 17, 12}, { -7724,  9, 11}, { -7678, 19, 12}, { -7633,  5, 10},
+	{ -7591, 21, 12}, { -7550, 11, 11}, { -7512, 23, 12}, { -7475,  3,  9},
+	{ -7439, 25, 12}, { -7405, 13, 11}, { -7372, 27, 12}, { -7341,  7, 10},
+	{ -7310, 29, 12}, { -7281, 15, 11}, { -7252, 31, 12}, { -7225,  1,  7},
+	{ -7172, 17, 11}, { -7122,  9, 10}, { -7075, 19, 11}, { -7031,  5,  9},
+	{ -6989, 21, 11}, { -6948, 11, 10}, { -6910, 23, 11}, { -6873,  3,  8},
+	{ -6837, 25, 11}, { -6803, 13, 10}, { -6770, 27, 11}, { -6739,  7,  9},
+	{ -6708, 29, 11}, { -6679, 15, 10}, { -6650, 31, 11}, { -6623,  1,  6},
+	{ -6570, 17, 10}, { -6520,  9,  9}, { -6473, 19, 10}, { -6429,  5,  8},
+	{ -6386, 21, 10}, { -6346, 11,  9}, { -6307, 23, 10}, { -6270,  3,  7},
+	{ -6235, 25, 10}, { -6201, 13,  9}, { -6168, 27, 10}, { -6137,  7,  8},
+	{ -6106, 29, 10}, { -6077, 15,  9}, { -6048, 31, 10}, { -6021,  1,  5},
+	{ -5968, 17,  9}, { -5918,  9,  8}, { -5871, 19,  9}, { -5827,  5,  7},
+	{ -5784, 21,  9}, { -5744, 11,  8}, { -5705, 23,  9}, { -5668,  3,  6},
+	{ -5633, 25,  9}, { -5599, 13,  8}, { -5566, 27,  9}, { -5535,  7,  7},
+	{ -5504, 29,  9}, { -5475, 15,  8}, { -5446, 31,  9}, { -5419,  1,  4},
+	{ -5366, 17,  8}, { -5316,  9,  7}, { -5269, 19,  8}, { -5225,  5,  6},
+	{ -5182, 21,  8}, { -5142, 11,  7}, { -5103, 23,  8}, { -5066,  3,  5},
+	{ -5031, 25,  8}, { -4997, 13,  7}, { -4964, 27,  8}, { -4932,  7,  6},
+	{ -4902, 29,  8}, { -4873, 15,  7}, { -4844, 31,  8}, { -4816,  1,  3},
+	{ -4764, 17,  7}, { -4714,  9,  6}, { -4667, 19,  7}, { -4623,  5,  5},
+	{ -4580, 21,  7}, { -4540, 11,  6}, { -4501, 23,  7}, { -4464,  3,  4},
+	{ -4429, 25,  7}, { -4395, 13,  6}, { -4362, 27,  7}, { -4330,  7,  5},
+	{ -4300, 29,  7}, { -4270, 15,  6}, { -4242, 31,  7}, { -4214,  1,  2},
+	{ -4162, 17,  6}, { -4112,  9,  5}, { -4065, 19,  6}, { -4021,  5,  4},
+	{ -3978, 21,  6}, { -3938, 11,  5}, { -3899, 23,  6}, { -3862,  3,  3},
+	{ -3827, 25,  6}, { -3793, 13,  5}, { -3760, 27,  6}, { -3728,  7,  4},
+	{ -3698, 29,  6}, { -3668, 15,  5}, { -3640, 31,  6}, { -3612,  1,  1},
+	{ -3560, 17,  5}, { -3510,  9,  4}, { -3463, 19,  5}, { -3419,  5,  3},
+	{ -3376, 21,  5}, { -3336, 11,  4}, { -3297, 23,  5}, { -3260,  3,  2},
+	{ -3225, 25,  5}, { -3191, 13,  4}, { -3158, 27,  5}, { -3126,  7,  3},
+	{ -3096, 29,  5}, { -3066, 15,  4}, { -3038, 31,  5}, { -3010,  1,  0},
+	{ -2958, 17,  4}, { -2908,  9,  3}, { -2861, 19,  4}, { -2816,  5,  2},
+	{ -2774, 21,  4}, { -2734, 11,  3}, { -2695, 23,  4}, { -2658,  3,  1},
+	{ -2623, 25,  4}, { -2589, 13,  3}, { -2556, 27,  4}, { -2524,  7,  2},
+	{ -2494, 29,  4}, { -2464, 15,  3}, { -2436, 31,  4}, { -2408,  2,  0},
+	{ -2356, 17,  3}, { -2306,  9,  2}, { -2259, 19,  3}, { -2214,  5,  1},
+	{ -2172, 21,  3}, { -2132, 11,  2}, { -2093, 23,  3}, { -2056,  3,  0},
+	{ -2021, 25,  3}, { -1987, 13,  2}, { -1954, 27,  3}, { -1922,  7,  1},
+	{ -1892, 29,  3}, { -1862, 15,  2}, { -1834, 31,  3}, { -1806,  4,  0},
+	{ -1754, 17,  2}, { -1704,  9,  1}, { -1657, 19,  2}, { -1612,  5,  0},
+	{ -1570, 21,  2}, { -1530, 11,  1}, { -1491, 23,  2}, { -1454,  6,  0},
+	{ -1419, 25,  2}, { -1384, 13,  1}, { -1352, 27,  2}, { -1320,  7,  0},
+	{ -1290, 29,  2}, { -1260, 15,  1}, { -1232, 31,  2}, { -1204,  8,  0},
+	{ -1151, 17,  1}, { -1102,  9,  0}, { -1055, 19,  1}, { -1010, 10,  0},
+	{  -968, 21,  1}, {  -928, 11,  0}, {  -889, 23,  1}, {  -852, 12,  0},
+	{  -816, 25,  1}, {  -782, 13,  0}, {  -750, 27,  1}, {  -718, 14,  0},
+	{  -688, 29,  1}, {  -658, 15,  0}, {  -630, 31,  1}, {  -602, 16,  0},
+	{  -549, 17,  0}, {  -500, 18,  0}, {  -453, 19,  0}, {  -408, 20,  0},
+	{  -366, 21,  0}, {  -325, 22,  0}, {  -287, 23,  0}, {  -250, 24,  0},
+	{  -214, 25,  0}, {  -180, 26,  0}, {  -148, 27,  0}, {  -116, 28,  0},
+	{   -86, 29,  0}, {   -56, 30,  0}, {   -28, 31,  0}, {     0,  0,  0},
+};
+
+static int pm860x_volatile(unsigned int reg)
+{
+	BUG_ON(reg >= REG_CACHE_SIZE);
+
+	switch (reg) {
+	case PM860X_AUDIO_SUPPLIES_2:
+		return 1;
+	}
+
+	return 0;
+}
+
+static unsigned int pm860x_read_reg_cache(struct snd_soc_codec *codec,
+					  unsigned int reg)
+{
+	unsigned char *cache = codec->reg_cache;
+
+	BUG_ON(reg >= REG_CACHE_SIZE);
+
+	if (pm860x_volatile(reg))
+		return cache[reg];
+
+	reg += REG_CACHE_BASE;
+
+	return pm860x_reg_read(codec->control_data, reg);
+}
+
+static int pm860x_write_reg_cache(struct snd_soc_codec *codec,
+				  unsigned int reg, unsigned int value)
+{
+	unsigned char *cache = codec->reg_cache;
+
+	BUG_ON(reg >= REG_CACHE_SIZE);
+
+	if (!pm860x_volatile(reg))
+		cache[reg] = (unsigned char)value;
+
+	reg += REG_CACHE_BASE;
+
+	return pm860x_reg_write(codec->control_data, reg, value);
+}
+
+static int snd_soc_get_volsw_2r_st(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int reg2 = mc->rreg;
+	int val[2], val2[2], i;
+
+	val[0] = snd_soc_read(codec, reg) & 0x3f;
+	val[1] = (snd_soc_read(codec, PM860X_SIDETONE_SHIFT) >> 4) & 0xf;
+	val2[0] = snd_soc_read(codec, reg2) & 0x3f;
+	val2[1] = (snd_soc_read(codec, PM860X_SIDETONE_SHIFT)) & 0xf;
+
+	for (i = 0; i < ARRAY_SIZE(st_table); i++) {
+		if ((st_table[i].m == val[0]) && (st_table[i].n == val[1]))
+			ucontrol->value.integer.value[0] = i;
+		if ((st_table[i].m == val2[0]) && (st_table[i].n == val2[1]))
+			ucontrol->value.integer.value[1] = i;
+	}
+	return 0;
+}
+
+static int snd_soc_put_volsw_2r_st(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int reg2 = mc->rreg;
+	int err;
+	unsigned int val, val2;
+
+	val = ucontrol->value.integer.value[0];
+	val2 = ucontrol->value.integer.value[1];
+
+	err = snd_soc_update_bits(codec, reg, 0x3f, st_table[val].m);
+	if (err < 0)
+		return err;
+	err = snd_soc_update_bits(codec, PM860X_SIDETONE_SHIFT, 0xf0,
+				  st_table[val].n << 4);
+	if (err < 0)
+		return err;
+
+	err = snd_soc_update_bits(codec, reg2, 0x3f, st_table[val2].m);
+	if (err < 0)
+		return err;
+	err = snd_soc_update_bits(codec, PM860X_SIDETONE_SHIFT, 0x0f,
+				  st_table[val2].n);
+	return err;
+}
+
+static int snd_soc_get_volsw_2r_out(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int reg2 = mc->rreg;
+	unsigned int shift = mc->shift;
+	int max = mc->max, val, val2;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	val = snd_soc_read(codec, reg) >> shift;
+	val2 = snd_soc_read(codec, reg2) >> shift;
+	ucontrol->value.integer.value[0] = (max - val) & mask;
+	ucontrol->value.integer.value[1] = (max - val2) & mask;
+
+	return 0;
+}
+
+static int snd_soc_put_volsw_2r_out(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int reg2 = mc->rreg;
+	unsigned int shift = mc->shift;
+	int max = mc->max;
+	unsigned int mask = (1 << fls(max)) - 1;
+	int err;
+	unsigned int val, val2, val_mask;
+
+	val_mask = mask << shift;
+	val = ((max - ucontrol->value.integer.value[0]) & mask);
+	val2 = ((max - ucontrol->value.integer.value[1]) & mask);
+
+	val = val << shift;
+	val2 = val2 << shift;
+
+	err = snd_soc_update_bits(codec, reg, val_mask, val);
+	if (err < 0)
+		return err;
+
+	err = snd_soc_update_bits(codec, reg2, val_mask, val2);
+	return err;
+}
+
+/* DAPM Widget Events */
+/*
+ * A lot registers are belong to RSYNC domain. It requires enabling RSYNC bit
+ * after updating these registers. Otherwise, these updated registers won't
+ * be effective.
+ */
+static int pm860x_rsync_event(struct snd_soc_dapm_widget *w,
+			      struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+
+	/*
+	 * In order to avoid current on the load, mute power-on and power-off
+	 * should be transients.
+	 * Unmute by DAC_MUTE. It should be unmuted when DAPM sequence is
+	 * finished.
+	 */
+	snd_soc_update_bits(codec, PM860X_DAC_OFFSET, DAC_MUTE, 0);
+	snd_soc_update_bits(codec, PM860X_EAR_CTRL_2,
+			    RSYNC_CHANGE, RSYNC_CHANGE);
+	return 0;
+}
+
+static int pm860x_dac_event(struct snd_soc_dapm_widget *w,
+			    struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	unsigned int dac = 0;
+	int data;
+
+	if (!strcmp(w->name, "Left DAC"))
+		dac = DAC_LEFT;
+	if (!strcmp(w->name, "Right DAC"))
+		dac = DAC_RIGHT;
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		if (dac) {
+			/* Auto mute in power-on sequence. */
+			dac |= MODULATOR;
+			snd_soc_update_bits(codec, PM860X_DAC_OFFSET,
+					    DAC_MUTE, DAC_MUTE);
+			snd_soc_update_bits(codec, PM860X_EAR_CTRL_2,
+					    RSYNC_CHANGE, RSYNC_CHANGE);
+			/* update dac */
+			snd_soc_update_bits(codec, PM860X_DAC_EN_2,
+					    dac, dac);
+		}
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		if (dac) {
+			/* Auto mute in power-off sequence. */
+			snd_soc_update_bits(codec, PM860X_DAC_OFFSET,
+					    DAC_MUTE, DAC_MUTE);
+			snd_soc_update_bits(codec, PM860X_EAR_CTRL_2,
+					    RSYNC_CHANGE, RSYNC_CHANGE);
+			/* update dac */
+			data = snd_soc_read(codec, PM860X_DAC_EN_2);
+			data &= ~dac;
+			if (!(data & (DAC_LEFT | DAC_RIGHT)))
+				data &= ~MODULATOR;
+			snd_soc_write(codec, PM860X_DAC_EN_2, data);
+		}
+		break;
+	}
+	return 0;
+}
+
+static const char *pm860x_opamp_texts[] = {"-50%", "-25%", "0%", "75%"};
+
+static const char *pm860x_pa_texts[] = {"-33%", "0%", "33%", "66%"};
+
+static const struct soc_enum pm860x_hs1_opamp_enum =
+	SOC_ENUM_SINGLE(PM860X_HS1_CTRL, 5, 4, pm860x_opamp_texts);
+
+static const struct soc_enum pm860x_hs2_opamp_enum =
+	SOC_ENUM_SINGLE(PM860X_HS2_CTRL, 5, 4, pm860x_opamp_texts);
+
+static const struct soc_enum pm860x_hs1_pa_enum =
+	SOC_ENUM_SINGLE(PM860X_HS1_CTRL, 3, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_hs2_pa_enum =
+	SOC_ENUM_SINGLE(PM860X_HS2_CTRL, 3, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_lo1_opamp_enum =
+	SOC_ENUM_SINGLE(PM860X_LO1_CTRL, 5, 4, pm860x_opamp_texts);
+
+static const struct soc_enum pm860x_lo2_opamp_enum =
+	SOC_ENUM_SINGLE(PM860X_LO2_CTRL, 5, 4, pm860x_opamp_texts);
+
+static const struct soc_enum pm860x_lo1_pa_enum =
+	SOC_ENUM_SINGLE(PM860X_LO1_CTRL, 3, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_lo2_pa_enum =
+	SOC_ENUM_SINGLE(PM860X_LO2_CTRL, 3, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_spk_pa_enum =
+	SOC_ENUM_SINGLE(PM860X_EAR_CTRL_1, 5, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_ear_pa_enum =
+	SOC_ENUM_SINGLE(PM860X_EAR_CTRL_2, 0, 4, pm860x_pa_texts);
+
+static const struct soc_enum pm860x_spk_ear_opamp_enum =
+	SOC_ENUM_SINGLE(PM860X_EAR_CTRL_1, 3, 4, pm860x_opamp_texts);
+
+static const struct snd_kcontrol_new pm860x_snd_controls[] = {
+	SOC_DOUBLE_R_TLV("ADC Capture Volume", PM860X_ADC_ANA_2,
+			PM860X_ADC_ANA_3, 6, 3, 0, adc_tlv),
+	SOC_DOUBLE_TLV("AUX Capture Volume", PM860X_ADC_ANA_3, 0, 3, 7, 0,
+			aux_tlv),
+	SOC_SINGLE_TLV("MIC1 Capture Volume", PM860X_ADC_ANA_2, 0, 7, 0,
+			mic_tlv),
+	SOC_SINGLE_TLV("MIC3 Capture Volume", PM860X_ADC_ANA_2, 3, 7, 0,
+			mic_tlv),
+	SOC_DOUBLE_R_EXT_TLV("Sidetone Volume", PM860X_SIDETONE_L_GAIN,
+			     PM860X_SIDETONE_R_GAIN, 0, ARRAY_SIZE(st_table)-1,
+			     0, snd_soc_get_volsw_2r_st,
+			     snd_soc_put_volsw_2r_st, st_tlv),
+	SOC_SINGLE_TLV("Speaker Playback Volume", PM860X_EAR_CTRL_1,
+			0, 7, 0, out_tlv),
+	SOC_DOUBLE_R_TLV("Line Playback Volume", PM860X_LO1_CTRL,
+			 PM860X_LO2_CTRL, 0, 7, 0, out_tlv),
+	SOC_DOUBLE_R_TLV("Headset Playback Volume", PM860X_HS1_CTRL,
+			 PM860X_HS2_CTRL, 0, 7, 0, out_tlv),
+	SOC_DOUBLE_R_EXT_TLV("Hifi Left Playback Volume",
+			     PM860X_HIFIL_GAIN_LEFT,
+			     PM860X_HIFIL_GAIN_RIGHT, 0, 63, 0,
+			     snd_soc_get_volsw_2r_out,
+			     snd_soc_put_volsw_2r_out, dpga_tlv),
+	SOC_DOUBLE_R_EXT_TLV("Hifi Right Playback Volume",
+			     PM860X_HIFIR_GAIN_LEFT,
+			     PM860X_HIFIR_GAIN_RIGHT, 0, 63, 0,
+			     snd_soc_get_volsw_2r_out,
+			     snd_soc_put_volsw_2r_out, dpga_tlv),
+	SOC_DOUBLE_R_EXT_TLV("Lofi Playback Volume", PM860X_LOFI_GAIN_LEFT,
+			     PM860X_LOFI_GAIN_RIGHT, 0, 63, 0,
+			     snd_soc_get_volsw_2r_out,
+			     snd_soc_put_volsw_2r_out, dpga_tlv),
+	SOC_ENUM("Headset1 Operational Amplifier Current",
+		 pm860x_hs1_opamp_enum),
+	SOC_ENUM("Headset2 Operational Amplifier Current",
+		 pm860x_hs2_opamp_enum),
+	SOC_ENUM("Headset1 Amplifier Current", pm860x_hs1_pa_enum),
+	SOC_ENUM("Headset2 Amplifier Current", pm860x_hs2_pa_enum),
+	SOC_ENUM("Lineout1 Operational Amplifier Current",
+		 pm860x_lo1_opamp_enum),
+	SOC_ENUM("Lineout2 Operational Amplifier Current",
+		 pm860x_lo2_opamp_enum),
+	SOC_ENUM("Lineout1 Amplifier Current", pm860x_lo1_pa_enum),
+	SOC_ENUM("Lineout2 Amplifier Current", pm860x_lo2_pa_enum),
+	SOC_ENUM("Speaker Operational Amplifier Current",
+		 pm860x_spk_ear_opamp_enum),
+	SOC_ENUM("Speaker Amplifier Current", pm860x_spk_pa_enum),
+	SOC_ENUM("Earpiece Amplifier Current", pm860x_ear_pa_enum),
+};
+
+/*
+ * DAPM Controls
+ */
+
+/* PCM Switch / PCM Interface */
+static const struct snd_kcontrol_new pcm_switch_controls =
+	SOC_DAPM_SINGLE("Switch", PM860X_ADC_EN_2, 0, 1, 0);
+
+/* AUX1 Switch */
+static const struct snd_kcontrol_new aux1_switch_controls =
+	SOC_DAPM_SINGLE("Switch", PM860X_ANA_TO_ANA, 4, 1, 0);
+
+/* AUX2 Switch */
+static const struct snd_kcontrol_new aux2_switch_controls =
+	SOC_DAPM_SINGLE("Switch", PM860X_ANA_TO_ANA, 5, 1, 0);
+
+/* Left Ex. PA Switch */
+static const struct snd_kcontrol_new lepa_switch_controls =
+	SOC_DAPM_SINGLE("Switch", PM860X_DAC_EN_2, 2, 1, 0);
+
+/* Right Ex. PA Switch */
+static const struct snd_kcontrol_new repa_switch_controls =
+	SOC_DAPM_SINGLE("Switch", PM860X_DAC_EN_2, 1, 1, 0);
+
+/* PCM Mux / Mux7 */
+static const char *aif1_text[] = {
+	"PCM L", "PCM R",
+};
+
+static const struct soc_enum aif1_enum =
+	SOC_ENUM_SINGLE(PM860X_PCM_IFACE_3, 6, 2, aif1_text);
+
+static const struct snd_kcontrol_new aif1_mux =
+	SOC_DAPM_ENUM("PCM Mux", aif1_enum);
+
+/* I2S Mux / Mux9 */
+static const char *i2s_din_text[] = {
+	"DIN", "DIN1",
+};
+
+static const struct soc_enum i2s_din_enum =
+	SOC_ENUM_SINGLE(PM860X_I2S_IFACE_3, 1, 2, i2s_din_text);
+
+static const struct snd_kcontrol_new i2s_din_mux =
+	SOC_DAPM_ENUM("I2S DIN Mux", i2s_din_enum);
+
+/* I2S Mic Mux / Mux8 */
+static const char *i2s_mic_text[] = {
+	"Ex PA", "ADC",
+};
+
+static const struct soc_enum i2s_mic_enum =
+	SOC_ENUM_SINGLE(PM860X_I2S_IFACE_3, 4, 2, i2s_mic_text);
+
+static const struct snd_kcontrol_new i2s_mic_mux =
+	SOC_DAPM_ENUM("I2S Mic Mux", i2s_mic_enum);
+
+/* ADCL Mux / Mux2 */
+static const char *adcl_text[] = {
+	"ADCR", "ADCL",
+};
+
+static const struct soc_enum adcl_enum =
+	SOC_ENUM_SINGLE(PM860X_PCM_IFACE_3, 4, 2, adcl_text);
+
+static const struct snd_kcontrol_new adcl_mux =
+	SOC_DAPM_ENUM("ADC Left Mux", adcl_enum);
+
+/* ADCR Mux / Mux3 */
+static const char *adcr_text[] = {
+	"ADCL", "ADCR",
+};
+
+static const struct soc_enum adcr_enum =
+	SOC_ENUM_SINGLE(PM860X_PCM_IFACE_3, 2, 2, adcr_text);
+
+static const struct snd_kcontrol_new adcr_mux =
+	SOC_DAPM_ENUM("ADC Right Mux", adcr_enum);
+
+/* ADCR EC Mux / Mux6 */
+static const char *adcr_ec_text[] = {
+	"ADCR", "EC",
+};
+
+static const struct soc_enum adcr_ec_enum =
+	SOC_ENUM_SINGLE(PM860X_ADC_EN_2, 3, 2, adcr_ec_text);
+
+static const struct snd_kcontrol_new adcr_ec_mux =
+	SOC_DAPM_ENUM("ADCR EC Mux", adcr_ec_enum);
+
+/* EC Mux / Mux4 */
+static const char *ec_text[] = {
+	"Left", "Right", "Left + Right",
+};
+
+static const struct soc_enum ec_enum =
+	SOC_ENUM_SINGLE(PM860X_EC_PATH, 1, 3, ec_text);
+
+static const struct snd_kcontrol_new ec_mux =
+	SOC_DAPM_ENUM("EC Mux", ec_enum);
+
+static const char *dac_text[] = {
+	"No input", "Right", "Left", "No input",
+};
+
+/* DAC Headset 1 Mux / Mux10 */
+static const struct soc_enum dac_hs1_enum =
+	SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 0, 4, dac_text);
+
+static const struct snd_kcontrol_new dac_hs1_mux =
+	SOC_DAPM_ENUM("DAC HS1 Mux", dac_hs1_enum);
+
+/* DAC Headset 2 Mux / Mux11 */
+static const struct soc_enum dac_hs2_enum =
+	SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 2, 4, dac_text);
+
+static const struct snd_kcontrol_new dac_hs2_mux =
+	SOC_DAPM_ENUM("DAC HS2 Mux", dac_hs2_enum);
+
+/* DAC Lineout 1 Mux / Mux12 */
+static const struct soc_enum dac_lo1_enum =
+	SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 4, 4, dac_text);
+
+static const struct snd_kcontrol_new dac_lo1_mux =
+	SOC_DAPM_ENUM("DAC LO1 Mux", dac_lo1_enum);
+
+/* DAC Lineout 2 Mux / Mux13 */
+static const struct soc_enum dac_lo2_enum =
+	SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 6, 4, dac_text);
+
+static const struct snd_kcontrol_new dac_lo2_mux =
+	SOC_DAPM_ENUM("DAC LO2 Mux", dac_lo2_enum);
+
+/* DAC Spearker Earphone Mux / Mux14 */
+static const struct soc_enum dac_spk_ear_enum =
+	SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_2, 0, 4, dac_text);
+
+static const struct snd_kcontrol_new dac_spk_ear_mux =
+	SOC_DAPM_ENUM("DAC SP Mux", dac_spk_ear_enum);
+
+/* Headset 1 Mux / Mux15 */
+static const char *in_text[] = {
+	"Digital", "Analog",
+};
+
+static const struct soc_enum hs1_enum =
+	SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 0, 2, in_text);
+
+static const struct snd_kcontrol_new hs1_mux =
+	SOC_DAPM_ENUM("Headset1 Mux", hs1_enum);
+
+/* Headset 2 Mux / Mux16 */
+static const struct soc_enum hs2_enum =
+	SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 1, 2, in_text);
+
+static const struct snd_kcontrol_new hs2_mux =
+	SOC_DAPM_ENUM("Headset2 Mux", hs2_enum);
+
+/* Lineout 1 Mux / Mux17 */
+static const struct soc_enum lo1_enum =
+	SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 2, 2, in_text);
+
+static const struct snd_kcontrol_new lo1_mux =
+	SOC_DAPM_ENUM("Lineout1 Mux", lo1_enum);
+
+/* Lineout 2 Mux / Mux18 */
+static const struct soc_enum lo2_enum =
+	SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 3, 2, in_text);
+
+static const struct snd_kcontrol_new lo2_mux =
+	SOC_DAPM_ENUM("Lineout2 Mux", lo2_enum);
+
+/* Speaker Earpiece Demux */
+static const char *spk_text[] = {
+	"Earpiece", "Speaker",
+};
+
+static const struct soc_enum spk_enum =
+	SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 6, 2, spk_text);
+
+static const struct snd_kcontrol_new spk_demux =
+	SOC_DAPM_ENUM("Speaker Earpiece Demux", spk_enum);
+
+/* MIC Mux / Mux1 */
+static const char *mic_text[] = {
+	"Mic 1", "Mic 2",
+};
+
+static const struct soc_enum mic_enum =
+	SOC_ENUM_SINGLE(PM860X_ADC_ANA_4, 4, 2, mic_text);
+
+static const struct snd_kcontrol_new mic_mux =
+	SOC_DAPM_ENUM("MIC Mux", mic_enum);
+
+static const struct snd_soc_dapm_widget pm860x_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("PCM SDI", "PCM Playback", 0,
+			    PM860X_ADC_EN_2, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("PCM SDO", "PCM Capture", 0,
+			     PM860X_PCM_IFACE_3, 1, 1),
+
+
+	SND_SOC_DAPM_AIF_IN("I2S DIN", "I2S Playback", 0,
+			    PM860X_DAC_EN_2, 0, 0),
+	SND_SOC_DAPM_AIF_IN("I2S DIN1", "I2S Playback", 0,
+			    PM860X_DAC_EN_2, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("I2S DOUT", "I2S Capture", 0,
+			     PM860X_I2S_IFACE_3, 5, 1),
+	SND_SOC_DAPM_MUX("I2S Mic Mux", SND_SOC_NOPM, 0, 0, &i2s_mic_mux),
+	SND_SOC_DAPM_MUX("ADC Left Mux", SND_SOC_NOPM, 0, 0, &adcl_mux),
+	SND_SOC_DAPM_MUX("ADC Right Mux", SND_SOC_NOPM, 0, 0, &adcr_mux),
+	SND_SOC_DAPM_MUX("EC Mux", SND_SOC_NOPM, 0, 0, &ec_mux),
+	SND_SOC_DAPM_MUX("ADCR EC Mux", SND_SOC_NOPM, 0, 0, &adcr_ec_mux),
+	SND_SOC_DAPM_SWITCH("Left EPA", SND_SOC_NOPM, 0, 0,
+			    &lepa_switch_controls),
+	SND_SOC_DAPM_SWITCH("Right EPA", SND_SOC_NOPM, 0, 0,
+			    &repa_switch_controls),
+
+	SND_SOC_DAPM_REG(snd_soc_dapm_supply, "Left ADC MOD", PM860X_ADC_EN_1,
+			 0, 1, 1, 0),
+	SND_SOC_DAPM_REG(snd_soc_dapm_supply, "Right ADC MOD", PM860X_ADC_EN_1,
+			 1, 1, 1, 0),
+	SND_SOC_DAPM_ADC("Left ADC", NULL, PM860X_ADC_EN_2, 5, 0),
+	SND_SOC_DAPM_ADC("Right ADC", NULL, PM860X_ADC_EN_2, 4, 0),
+
+	SND_SOC_DAPM_SWITCH("AUX1 Switch", SND_SOC_NOPM, 0, 0,
+			    &aux1_switch_controls),
+	SND_SOC_DAPM_SWITCH("AUX2 Switch", SND_SOC_NOPM, 0, 0,
+			    &aux2_switch_controls),
+
+	SND_SOC_DAPM_MUX("MIC Mux", SND_SOC_NOPM, 0, 0, &mic_mux),
+	SND_SOC_DAPM_MICBIAS("Mic1 Bias", PM860X_ADC_ANA_1, 2, 0),
+	SND_SOC_DAPM_MICBIAS("Mic3 Bias", PM860X_ADC_ANA_1, 7, 0),
+	SND_SOC_DAPM_PGA("MIC1 Volume", PM860X_ADC_EN_1, 2, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("MIC3 Volume", PM860X_ADC_EN_1, 3, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("AUX1 Volume", PM860X_ADC_EN_1, 4, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("AUX2 Volume", PM860X_ADC_EN_1, 5, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Sidetone PGA", PM860X_ADC_EN_2, 1, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Lofi PGA", PM860X_ADC_EN_2, 2, 0, NULL, 0),
+
+	SND_SOC_DAPM_INPUT("AUX1"),
+	SND_SOC_DAPM_INPUT("AUX2"),
+	SND_SOC_DAPM_INPUT("MIC1P"),
+	SND_SOC_DAPM_INPUT("MIC1N"),
+	SND_SOC_DAPM_INPUT("MIC2P"),
+	SND_SOC_DAPM_INPUT("MIC2N"),
+	SND_SOC_DAPM_INPUT("MIC3P"),
+	SND_SOC_DAPM_INPUT("MIC3N"),
+
+	SND_SOC_DAPM_DAC_E("Left DAC", NULL, SND_SOC_NOPM, 0, 0,
+			   pm860x_dac_event,
+			   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_DAC_E("Right DAC", NULL, SND_SOC_NOPM, 0, 0,
+			   pm860x_dac_event,
+			   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+
+	SND_SOC_DAPM_MUX("I2S DIN Mux", SND_SOC_NOPM, 0, 0, &i2s_din_mux),
+	SND_SOC_DAPM_MUX("DAC HS1 Mux", SND_SOC_NOPM, 0, 0, &dac_hs1_mux),
+	SND_SOC_DAPM_MUX("DAC HS2 Mux", SND_SOC_NOPM, 0, 0, &dac_hs2_mux),
+	SND_SOC_DAPM_MUX("DAC LO1 Mux", SND_SOC_NOPM, 0, 0, &dac_lo1_mux),
+	SND_SOC_DAPM_MUX("DAC LO2 Mux", SND_SOC_NOPM, 0, 0, &dac_lo2_mux),
+	SND_SOC_DAPM_MUX("DAC SP Mux", SND_SOC_NOPM, 0, 0, &dac_spk_ear_mux),
+	SND_SOC_DAPM_MUX("Headset1 Mux", SND_SOC_NOPM, 0, 0, &hs1_mux),
+	SND_SOC_DAPM_MUX("Headset2 Mux", SND_SOC_NOPM, 0, 0, &hs2_mux),
+	SND_SOC_DAPM_MUX("Lineout1 Mux", SND_SOC_NOPM, 0, 0, &lo1_mux),
+	SND_SOC_DAPM_MUX("Lineout2 Mux", SND_SOC_NOPM, 0, 0, &lo2_mux),
+	SND_SOC_DAPM_MUX("Speaker Earpiece Demux", SND_SOC_NOPM, 0, 0,
+			 &spk_demux),
+
+
+	SND_SOC_DAPM_PGA("Headset1 PGA", PM860X_DAC_EN_1, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Headset2 PGA", PM860X_DAC_EN_1, 1, 0, NULL, 0),
+	SND_SOC_DAPM_OUTPUT("HS1"),
+	SND_SOC_DAPM_OUTPUT("HS2"),
+	SND_SOC_DAPM_PGA("Lineout1 PGA", PM860X_DAC_EN_1, 2, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Lineout2 PGA", PM860X_DAC_EN_1, 3, 0, NULL, 0),
+	SND_SOC_DAPM_OUTPUT("LINEOUT1"),
+	SND_SOC_DAPM_OUTPUT("LINEOUT2"),
+	SND_SOC_DAPM_PGA("Earpiece PGA", PM860X_DAC_EN_1, 4, 0, NULL, 0),
+	SND_SOC_DAPM_OUTPUT("EARP"),
+	SND_SOC_DAPM_OUTPUT("EARN"),
+	SND_SOC_DAPM_PGA("Speaker PGA", PM860X_DAC_EN_1, 5, 0, NULL, 0),
+	SND_SOC_DAPM_OUTPUT("LSP"),
+	SND_SOC_DAPM_OUTPUT("LSN"),
+	SND_SOC_DAPM_REG(snd_soc_dapm_supply, "VCODEC", PM860X_AUDIO_SUPPLIES_2,
+			 0, SUPPLY_MASK, SUPPLY_MASK, 0),
+
+	PM860X_DAPM_OUTPUT("RSYNC", pm860x_rsync_event),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* supply */
+	{"Left DAC", NULL, "VCODEC"},
+	{"Right DAC", NULL, "VCODEC"},
+	{"Left ADC", NULL, "VCODEC"},
+	{"Right ADC", NULL, "VCODEC"},
+	{"Left ADC", NULL, "Left ADC MOD"},
+	{"Right ADC", NULL, "Right ADC MOD"},
+
+	/* PCM/AIF1 Inputs */
+	{"PCM SDO", NULL, "ADC Left Mux"},
+	{"PCM SDO", NULL, "ADCR EC Mux"},
+
+	/* PCM/AFI2 Outputs */
+	{"Lofi PGA", NULL, "PCM SDI"},
+	{"Lofi PGA", NULL, "Sidetone PGA"},
+	{"Left DAC", NULL, "Lofi PGA"},
+	{"Right DAC", NULL, "Lofi PGA"},
+
+	/* I2S/AIF2 Inputs */
+	{"MIC Mux", "Mic 1", "MIC1P"},
+	{"MIC Mux", "Mic 1", "MIC1N"},
+	{"MIC Mux", "Mic 2", "MIC2P"},
+	{"MIC Mux", "Mic 2", "MIC2N"},
+	{"MIC1 Volume", NULL, "MIC Mux"},
+	{"MIC3 Volume", NULL, "MIC3P"},
+	{"MIC3 Volume", NULL, "MIC3N"},
+	{"Left ADC", NULL, "MIC1 Volume"},
+	{"Right ADC", NULL, "MIC3 Volume"},
+	{"ADC Left Mux", "ADCR", "Right ADC"},
+	{"ADC Left Mux", "ADCL", "Left ADC"},
+	{"ADC Right Mux", "ADCL", "Left ADC"},
+	{"ADC Right Mux", "ADCR", "Right ADC"},
+	{"Left EPA", "Switch", "Left DAC"},
+	{"Right EPA", "Switch", "Right DAC"},
+	{"EC Mux", "Left", "Left DAC"},
+	{"EC Mux", "Right", "Right DAC"},
+	{"EC Mux", "Left + Right", "Left DAC"},
+	{"EC Mux", "Left + Right", "Right DAC"},
+	{"ADCR EC Mux", "ADCR", "ADC Right Mux"},
+	{"ADCR EC Mux", "EC", "EC Mux"},
+	{"I2S Mic Mux", "Ex PA", "Left EPA"},
+	{"I2S Mic Mux", "Ex PA", "Right EPA"},
+	{"I2S Mic Mux", "ADC", "ADC Left Mux"},
+	{"I2S Mic Mux", "ADC", "ADCR EC Mux"},
+	{"I2S DOUT", NULL, "I2S Mic Mux"},
+
+	/* I2S/AIF2 Outputs */
+	{"I2S DIN Mux", "DIN", "I2S DIN"},
+	{"I2S DIN Mux", "DIN1", "I2S DIN1"},
+	{"Left DAC", NULL, "I2S DIN Mux"},
+	{"Right DAC", NULL, "I2S DIN Mux"},
+	{"DAC HS1 Mux", "Left", "Left DAC"},
+	{"DAC HS1 Mux", "Right", "Right DAC"},
+	{"DAC HS2 Mux", "Left", "Left DAC"},
+	{"DAC HS2 Mux", "Right", "Right DAC"},
+	{"DAC LO1 Mux", "Left", "Left DAC"},
+	{"DAC LO1 Mux", "Right", "Right DAC"},
+	{"DAC LO2 Mux", "Left", "Left DAC"},
+	{"DAC LO2 Mux", "Right", "Right DAC"},
+	{"Headset1 Mux", "Digital", "DAC HS1 Mux"},
+	{"Headset2 Mux", "Digital", "DAC HS2 Mux"},
+	{"Lineout1 Mux", "Digital", "DAC LO1 Mux"},
+	{"Lineout2 Mux", "Digital", "DAC LO2 Mux"},
+	{"Headset1 PGA", NULL, "Headset1 Mux"},
+	{"Headset2 PGA", NULL, "Headset2 Mux"},
+	{"Lineout1 PGA", NULL, "Lineout1 Mux"},
+	{"Lineout2 PGA", NULL, "Lineout2 Mux"},
+	{"DAC SP Mux", "Left", "Left DAC"},
+	{"DAC SP Mux", "Right", "Right DAC"},
+	{"Speaker Earpiece Demux", "Speaker", "DAC SP Mux"},
+	{"Speaker PGA", NULL, "Speaker Earpiece Demux"},
+	{"Earpiece PGA", NULL, "Speaker Earpiece Demux"},
+
+	{"RSYNC", NULL, "Headset1 PGA"},
+	{"RSYNC", NULL, "Headset2 PGA"},
+	{"RSYNC", NULL, "Lineout1 PGA"},
+	{"RSYNC", NULL, "Lineout2 PGA"},
+	{"RSYNC", NULL, "Speaker PGA"},
+	{"RSYNC", NULL, "Speaker PGA"},
+	{"RSYNC", NULL, "Earpiece PGA"},
+	{"RSYNC", NULL, "Earpiece PGA"},
+
+	{"HS1", NULL, "RSYNC"},
+	{"HS2", NULL, "RSYNC"},
+	{"LINEOUT1", NULL, "RSYNC"},
+	{"LINEOUT2", NULL, "RSYNC"},
+	{"LSP", NULL, "RSYNC"},
+	{"LSN", NULL, "RSYNC"},
+	{"EARP", NULL, "RSYNC"},
+	{"EARN", NULL, "RSYNC"},
+};
+
+/*
+ * Use MUTE_LEFT & MUTE_RIGHT to implement digital mute.
+ * These bits can also be used to mute.
+ */
+static int pm860x_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	int data = 0, mask = MUTE_LEFT | MUTE_RIGHT;
+
+	if (mute)
+		data = mask;
+	snd_soc_update_bits(codec, PM860X_DAC_OFFSET, mask, data);
+	snd_soc_update_bits(codec, PM860X_EAR_CTRL_2,
+			    RSYNC_CHANGE, RSYNC_CHANGE);
+	return 0;
+}
+
+static int pm860x_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	unsigned char inf = 0, mask = 0;
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		inf &= ~PCM_INF2_18WL;
+		break;
+	case SNDRV_PCM_FORMAT_S18_3LE:
+		inf |= PCM_INF2_18WL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	mask |= PCM_INF2_18WL;
+	snd_soc_update_bits(codec, PM860X_PCM_IFACE_2, mask, inf);
+
+	/* sample rate */
+	switch (params_rate(params)) {
+	case 8000:
+		inf = 0;
+		break;
+	case 16000:
+		inf = 3;
+		break;
+	case 32000:
+		inf = 6;
+		break;
+	case 48000:
+		inf = 8;
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_soc_update_bits(codec, PM860X_PCM_RATE, 0x0f, inf);
+
+	return 0;
+}
+
+static int pm860x_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai,
+				  unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+	unsigned char inf = 0, mask = 0;
+	int ret = -EINVAL;
+
+	mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+	case SND_SOC_DAIFMT_CBM_CFS:
+		if (pm860x->dir == PM860X_CLK_DIR_OUT) {
+			inf |= PCM_INF2_MASTER;
+			ret = 0;
+		}
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		if (pm860x->dir == PM860X_CLK_DIR_IN) {
+			inf &= ~PCM_INF2_MASTER;
+			ret = 0;
+		}
+		break;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		inf |= PCM_EXACT_I2S;
+		ret = 0;
+		break;
+	}
+	mask |= PCM_MODE_MASK;
+	if (ret)
+		return ret;
+	snd_soc_update_bits(codec, PM860X_PCM_IFACE_2, mask, inf);
+	return 0;
+}
+
+static int pm860x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+				 int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+
+	if (dir == PM860X_CLK_DIR_OUT)
+		pm860x->dir = PM860X_CLK_DIR_OUT;
+	else {
+		pm860x->dir = PM860X_CLK_DIR_IN;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int pm860x_i2s_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	unsigned char inf;
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		inf = 0;
+		break;
+	case SNDRV_PCM_FORMAT_S18_3LE:
+		inf = PCM_INF2_18WL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_soc_update_bits(codec, PM860X_I2S_IFACE_2, PCM_INF2_18WL, inf);
+
+	/* sample rate */
+	switch (params_rate(params)) {
+	case 8000:
+		inf = 0;
+		break;
+	case 11025:
+		inf = 1;
+		break;
+	case 16000:
+		inf = 3;
+		break;
+	case 22050:
+		inf = 4;
+		break;
+	case 32000:
+		inf = 6;
+		break;
+	case 44100:
+		inf = 7;
+		break;
+	case 48000:
+		inf = 8;
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_soc_update_bits(codec, PM860X_I2S_IFACE_4, 0xf, inf);
+
+	return 0;
+}
+
+static int pm860x_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai,
+				  unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+	unsigned char inf = 0, mask = 0;
+
+	mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		if (pm860x->dir == PM860X_CLK_DIR_OUT)
+			inf |= PCM_INF2_MASTER;
+		else
+			return -EINVAL;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		if (pm860x->dir == PM860X_CLK_DIR_IN)
+			inf &= ~PCM_INF2_MASTER;
+		else
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		inf |= PCM_EXACT_I2S;
+		break;
+	default:
+		return -EINVAL;
+	}
+	mask |= PCM_MODE_MASK;
+	snd_soc_update_bits(codec, PM860X_I2S_IFACE_2, mask, inf);
+	return 0;
+}
+
+static int pm860x_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	int data;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Enable Audio PLL & Audio section */
+			data = AUDIO_PLL | AUDIO_SECTION_RESET
+				| AUDIO_SECTION_ON;
+			pm860x_reg_write(codec->control_data, REG_MISC2, data);
+		}
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		data = AUDIO_PLL | AUDIO_SECTION_RESET | AUDIO_SECTION_ON;
+		pm860x_set_bits(codec->control_data, REG_MISC2, data, 0);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+static struct snd_soc_dai_ops pm860x_pcm_dai_ops = {
+	.digital_mute	= pm860x_digital_mute,
+	.hw_params	= pm860x_pcm_hw_params,
+	.set_fmt	= pm860x_pcm_set_dai_fmt,
+	.set_sysclk	= pm860x_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_ops pm860x_i2s_dai_ops = {
+	.digital_mute	= pm860x_digital_mute,
+	.hw_params	= pm860x_i2s_hw_params,
+	.set_fmt	= pm860x_i2s_set_dai_fmt,
+	.set_sysclk	= pm860x_set_dai_sysclk,
+};
+
+#define PM860X_RATES	(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |	\
+			 SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_driver pm860x_dai[] = {
+	{
+		/* DAI PCM */
+		.name	= "88pm860x-pcm",
+		.id	= 1,
+		.playback = {
+			.stream_name	= "PCM Playback",
+			.channels_min	= 2,
+			.channels_max	= 2,
+			.rates		= PM860X_RATES,
+			.formats	= SNDRV_PCM_FORMAT_S16_LE | \
+					  SNDRV_PCM_FORMAT_S18_3LE,
+		},
+		.capture = {
+			.stream_name	= "PCM Capture",
+			.channels_min	= 2,
+			.channels_max	= 2,
+			.rates		= PM860X_RATES,
+			.formats	= SNDRV_PCM_FORMAT_S16_LE | \
+					  SNDRV_PCM_FORMAT_S18_3LE,
+		},
+		.ops	= &pm860x_pcm_dai_ops,
+	}, {
+		/* DAI I2S */
+		.name	= "88pm860x-i2s",
+		.id	= 2,
+		.playback = {
+			.stream_name	= "I2S Playback",
+			.channels_min	= 2,
+			.channels_max	= 2,
+			.rates		= SNDRV_PCM_RATE_8000_48000,
+			.formats	= SNDRV_PCM_FORMAT_S16_LE | \
+					  SNDRV_PCM_FORMAT_S18_3LE,
+		},
+		.capture = {
+			.stream_name	= "I2S Capture",
+			.channels_min	= 2,
+			.channels_max	= 2,
+			.rates		= SNDRV_PCM_RATE_8000_48000,
+			.formats	= SNDRV_PCM_FORMAT_S16_LE | \
+					  SNDRV_PCM_FORMAT_S18_3LE,
+		},
+		.ops	= &pm860x_i2s_dai_ops,
+	},
+};
+
+static irqreturn_t pm860x_codec_handler(int irq, void *data)
+{
+	struct pm860x_priv *pm860x = data;
+	int status, shrt, report = 0, mic_report = 0;
+	int mask;
+
+	status = pm860x_reg_read(pm860x->i2c, REG_STATUS_1);
+	shrt = pm860x_reg_read(pm860x->i2c, REG_SHORTS);
+	mask = pm860x->det.hs_shrt | pm860x->det.hook_det | pm860x->det.lo_shrt
+		| pm860x->det.hp_det;
+
+	if ((pm860x->det.hp_det & SND_JACK_HEADPHONE)
+		&& (status & HEADSET_STATUS))
+		report |= SND_JACK_HEADPHONE;
+
+	if ((pm860x->det.mic_det & SND_JACK_MICROPHONE)
+		&& (status & MIC_STATUS))
+		mic_report |= SND_JACK_MICROPHONE;
+
+	if (pm860x->det.hs_shrt && (shrt & (SHORT_HS1 | SHORT_HS2)))
+		report |= pm860x->det.hs_shrt;
+
+	if (pm860x->det.hook_det && (status & HOOK_STATUS))
+		report |= pm860x->det.hook_det;
+
+	if (pm860x->det.lo_shrt && (shrt & (SHORT_LO1 | SHORT_LO2)))
+		report |= pm860x->det.lo_shrt;
+
+	if (report)
+		snd_soc_jack_report(pm860x->det.hp_jack, report, mask);
+	if (mic_report)
+		snd_soc_jack_report(pm860x->det.mic_jack, SND_JACK_MICROPHONE,
+				    SND_JACK_MICROPHONE);
+
+	dev_dbg(pm860x->codec->dev, "headphone report:0x%x, mask:%x\n",
+		report, mask);
+	dev_dbg(pm860x->codec->dev, "microphone report:0x%x\n", mic_report);
+	return IRQ_HANDLED;
+}
+
+int pm860x_hs_jack_detect(struct snd_soc_codec *codec,
+			  struct snd_soc_jack *jack,
+			  int det, int hook, int hs_shrt, int lo_shrt)
+{
+	struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+	int data;
+
+	pm860x->det.hp_jack = jack;
+	pm860x->det.hp_det = det;
+	pm860x->det.hook_det = hook;
+	pm860x->det.hs_shrt = hs_shrt;
+	pm860x->det.lo_shrt = lo_shrt;
+
+	if (det & SND_JACK_HEADPHONE)
+		pm860x_set_bits(codec->control_data, REG_HS_DET,
+				EN_HS_DET, EN_HS_DET);
+	/* headset short detect */
+	if (hs_shrt) {
+		data = CLR_SHORT_HS2 | CLR_SHORT_HS1;
+		pm860x_set_bits(codec->control_data, REG_SHORTS, data, data);
+	}
+	/* Lineout short detect */
+	if (lo_shrt) {
+		data = CLR_SHORT_LO2 | CLR_SHORT_LO1;
+		pm860x_set_bits(codec->control_data, REG_SHORTS, data, data);
+	}
+
+	/* sync status */
+	pm860x_codec_handler(0, pm860x);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pm860x_hs_jack_detect);
+
+int pm860x_mic_jack_detect(struct snd_soc_codec *codec,
+			   struct snd_soc_jack *jack, int det)
+{
+	struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+
+	pm860x->det.mic_jack = jack;
+	pm860x->det.mic_det = det;
+
+	if (det & SND_JACK_MICROPHONE)
+		pm860x_set_bits(codec->control_data, REG_MIC_DET,
+				MICDET_MASK, MICDET_MASK);
+
+	/* sync status */
+	pm860x_codec_handler(0, pm860x);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pm860x_mic_jack_detect);
+
+static int pm860x_probe(struct snd_soc_codec *codec)
+{
+	struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+	int i, ret;
+
+	pm860x->codec = codec;
+
+	codec->control_data = pm860x->i2c;
+
+	for (i = 0; i < 4; i++) {
+		ret = request_threaded_irq(pm860x->irq[i], NULL,
+					   pm860x_codec_handler, IRQF_ONESHOT,
+					   pm860x->name[i], pm860x);
+		if (ret < 0) {
+			dev_err(codec->dev, "Failed to request IRQ!\n");
+			goto out_irq;
+		}
+	}
+
+	pm860x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	ret = pm860x_bulk_read(codec->control_data, REG_CACHE_BASE,
+			       REG_CACHE_SIZE, codec->reg_cache);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to fill register cache: %d\n",
+			ret);
+		goto out_codec;
+	}
+
+	snd_soc_add_controls(codec, pm860x_snd_controls,
+			     ARRAY_SIZE(pm860x_snd_controls));
+	snd_soc_dapm_new_controls(codec, pm860x_dapm_widgets,
+				  ARRAY_SIZE(pm860x_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+	return 0;
+
+out_codec:
+	i = 3;
+out_irq:
+	for (; i >= 0; i--)
+		free_irq(pm860x->irq[i], pm860x);
+	return -EINVAL;
+}
+
+static int pm860x_remove(struct snd_soc_codec *codec)
+{
+	struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec);
+	int i;
+
+	for (i = 3; i >= 0; i--)
+		free_irq(pm860x->irq[i], pm860x);
+	pm860x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_pm860x = {
+	.probe		= pm860x_probe,
+	.remove		= pm860x_remove,
+	.read		= pm860x_read_reg_cache,
+	.write		= pm860x_write_reg_cache,
+	.reg_cache_size	= REG_CACHE_SIZE,
+	.reg_word_size	= sizeof(u8),
+	.set_bias_level	= pm860x_set_bias_level,
+};
+
+static int __devinit pm860x_codec_probe(struct platform_device *pdev)
+{
+	struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+	struct pm860x_priv *pm860x;
+	struct resource *res;
+	int i, ret;
+
+	pm860x = kzalloc(sizeof(struct pm860x_priv), GFP_KERNEL);
+	if (pm860x == NULL)
+		return -ENOMEM;
+
+	pm860x->chip = chip;
+	pm860x->i2c = (chip->id == CHIP_PM8607) ? chip->client
+			: chip->companion;
+	platform_set_drvdata(pdev, pm860x);
+
+	for (i = 0; i < 4; i++) {
+		res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
+		if (!res) {
+			dev_err(&pdev->dev, "Failed to get IRQ resources\n");
+			goto out;
+		}
+		pm860x->irq[i] = res->start + chip->irq_base;
+		strncpy(pm860x->name[i], res->name, MAX_NAME_LEN);
+	}
+
+	ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_pm860x,
+				     pm860x_dai, ARRAY_SIZE(pm860x_dai));
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register codec\n");
+		goto out;
+	}
+	return ret;
+
+out:
+	platform_set_drvdata(pdev, NULL);
+	kfree(pm860x);
+	return -EINVAL;
+}
+
+static int __devexit pm860x_codec_remove(struct platform_device *pdev)
+{
+	struct pm860x_priv *pm860x = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_codec(&pdev->dev);
+	platform_set_drvdata(pdev, NULL);
+	kfree(pm860x);
+	return 0;
+}
+
+static struct platform_driver pm860x_codec_driver = {
+	.driver	= {
+		.name	= "88pm860x-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe	= pm860x_codec_probe,
+	.remove	= __devexit_p(pm860x_codec_remove),
+};
+
+static __init int pm860x_init(void)
+{
+	return platform_driver_register(&pm860x_codec_driver);
+}
+module_init(pm860x_init);
+
+static __exit void pm860x_exit(void)
+{
+	platform_driver_unregister(&pm860x_codec_driver);
+}
+module_exit(pm860x_exit);
+
+MODULE_DESCRIPTION("ASoC 88PM860x driver");
+MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:88pm860x-codec");
+
diff --git a/linux/sound/soc/codecs/88pm860x-codec.h b/linux/sound/soc/codecs/88pm860x-codec.h
new file mode 100644
index 0000000..3364ba4
--- /dev/null
+++ b/linux/sound/soc/codecs/88pm860x-codec.h
@@ -0,0 +1,97 @@
+/*
+ * 88pm860x-codec.h -- 88PM860x ALSA SoC Audio Driver
+ *
+ * Copyright 2010 Marvell International Ltd.
+ *	Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __88PM860X_H
+#define __88PM860X_H
+
+/* The offset of these registers are 0xb0 */
+#define PM860X_PCM_IFACE_1		0x00
+#define PM860X_PCM_IFACE_2		0x01
+#define PM860X_PCM_IFACE_3		0x02
+#define PM860X_PCM_RATE			0x03
+#define PM860X_EC_PATH			0x04
+#define PM860X_SIDETONE_L_GAIN		0x05
+#define PM860X_SIDETONE_R_GAIN		0x06
+#define PM860X_SIDETONE_SHIFT		0x07
+#define PM860X_ADC_OFFSET_1		0x08
+#define PM860X_ADC_OFFSET_2		0x09
+#define PM860X_DMIC_DELAY		0x0a
+
+#define PM860X_I2S_IFACE_1		0x0b
+#define PM860X_I2S_IFACE_2		0x0c
+#define PM860X_I2S_IFACE_3		0x0d
+#define PM860X_I2S_IFACE_4		0x0e
+#define PM860X_EQUALIZER_N0_1		0x0f
+#define PM860X_EQUALIZER_N0_2		0x10
+#define PM860X_EQUALIZER_N1_1		0x11
+#define PM860X_EQUALIZER_N1_2		0x12
+#define PM860X_EQUALIZER_D1_1		0x13
+#define PM860X_EQUALIZER_D1_2		0x14
+#define PM860X_LOFI_GAIN_LEFT		0x15
+#define PM860X_LOFI_GAIN_RIGHT		0x16
+#define PM860X_HIFIL_GAIN_LEFT		0x17
+#define PM860X_HIFIL_GAIN_RIGHT		0x18
+#define PM860X_HIFIR_GAIN_LEFT		0x19
+#define PM860X_HIFIR_GAIN_RIGHT		0x1a
+#define PM860X_DAC_OFFSET		0x1b
+#define PM860X_OFFSET_LEFT_1		0x1c
+#define PM860X_OFFSET_LEFT_2		0x1d
+#define PM860X_OFFSET_RIGHT_1		0x1e
+#define PM860X_OFFSET_RIGHT_2		0x1f
+#define PM860X_ADC_ANA_1		0x20
+#define PM860X_ADC_ANA_2		0x21
+#define PM860X_ADC_ANA_3		0x22
+#define PM860X_ADC_ANA_4		0x23
+#define PM860X_ANA_TO_ANA		0x24
+#define PM860X_HS1_CTRL			0x25
+#define PM860X_HS2_CTRL			0x26
+#define PM860X_LO1_CTRL			0x27
+#define PM860X_LO2_CTRL			0x28
+#define PM860X_EAR_CTRL_1		0x29
+#define PM860X_EAR_CTRL_2		0x2a
+#define PM860X_AUDIO_SUPPLIES_1		0x2b
+#define PM860X_AUDIO_SUPPLIES_2		0x2c
+#define PM860X_ADC_EN_1			0x2d
+#define PM860X_ADC_EN_2			0x2e
+#define PM860X_DAC_EN_1			0x2f
+#define PM860X_DAC_EN_2			0x31
+#define PM860X_AUDIO_CAL_1		0x32
+#define PM860X_AUDIO_CAL_2		0x33
+#define PM860X_AUDIO_CAL_3		0x34
+#define PM860X_AUDIO_CAL_4		0x35
+#define PM860X_AUDIO_CAL_5		0x36
+#define PM860X_ANA_INPUT_SEL_1		0x37
+#define PM860X_ANA_INPUT_SEL_2		0x38
+
+#define PM860X_PCM_IFACE_4		0x39
+#define PM860X_I2S_IFACE_5		0x3a
+
+#define PM860X_SHORTS			0x3b
+#define PM860X_PLL_ADJ_1		0x3c
+#define PM860X_PLL_ADJ_2		0x3d
+
+/* bits definition */
+#define PM860X_CLK_DIR_IN		0
+#define PM860X_CLK_DIR_OUT		1
+
+#define PM860X_DET_HEADSET		(1 << 0)
+#define PM860X_DET_MIC			(1 << 1)
+#define PM860X_DET_HOOK			(1 << 2)
+#define PM860X_SHORT_HEADSET		(1 << 3)
+#define PM860X_SHORT_LINEOUT		(1 << 4)
+#define PM860X_DET_MASK			0x1F
+
+extern int pm860x_hs_jack_detect(struct snd_soc_codec *, struct snd_soc_jack *,
+				 int, int, int, int);
+extern int pm860x_mic_jack_detect(struct snd_soc_codec *, struct snd_soc_jack *,
+				  int);
+
+#endif	/* __88PM860X_H */
diff --git a/linux/sound/soc/codecs/Kconfig b/linux/sound/soc/codecs/Kconfig
new file mode 100644
index 0000000..25c9f53
--- /dev/null
+++ b/linux/sound/soc/codecs/Kconfig
@@ -0,0 +1,325 @@
+# Helper to resolve issues with configs that have SPI enabled but I2C
+# modular, meaning we can't build the codec driver in with I2C support.
+# We use an ordered list of conditional defaults to pick the appropriate
+# setting - SPI can't be modular so that case doesn't need to be covered.
+config SND_SOC_I2C_AND_SPI
+	tristate
+	default m if I2C=m
+	default y if I2C=y
+	default y if SPI_MASTER=y
+
+config SND_SOC_ALL_CODECS
+	tristate "Build all ASoC CODEC drivers"
+	select SND_SOC_88PM860X if MFD_88PM860X
+	select SND_SOC_L3
+	select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS
+	select SND_SOC_AD1836 if SPI_MASTER
+	select SND_SOC_AD193X if SND_SOC_I2C_AND_SPI
+	select SND_SOC_AD1980 if SND_SOC_AC97_BUS
+	select SND_SOC_ADS117X
+	select SND_SOC_AD73311 if I2C
+	select SND_SOC_AK4104 if SPI_MASTER
+	select SND_SOC_AK4535 if I2C
+	select SND_SOC_AK4642 if I2C
+	select SND_SOC_AK4671 if I2C
+	select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
+	select SND_SOC_CS42L51 if I2C
+	select SND_SOC_CS4270 if I2C
+	select SND_SOC_CX20442
+	select SND_SOC_DA7210 if I2C
+	select SND_SOC_JZ4740_CODEC if SOC_JZ4740
+	select SND_SOC_MAX98088 if I2C
+	select SND_SOC_MAX9877 if I2C
+	select SND_SOC_PCM3008
+	select SND_SOC_SPDIF
+	select SND_SOC_SSM2602 if I2C
+	select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
+	select SND_SOC_TLV320AIC23 if I2C
+	select SND_SOC_TLV320AIC26 if SPI_MASTER
+	select SND_SOC_TLV320AIC3X if I2C
+	select SND_SOC_TPA6130A2 if I2C
+	select SND_SOC_TLV320DAC33 if I2C
+	select SND_SOC_TWL4030 if TWL4030_CORE
+	select SND_SOC_TWL6040 if TWL4030_CORE
+	select SND_SOC_UDA134X
+	select SND_SOC_UDA1380 if I2C
+	select SND_SOC_WL1271BT
+	select SND_SOC_WL1273 if WL1273_CORE
+	select SND_SOC_WM2000 if I2C
+	select SND_SOC_WM8350 if MFD_WM8350
+	select SND_SOC_WM8400 if MFD_WM8400
+	select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI
+	select SND_SOC_WM8523 if I2C
+	select SND_SOC_WM8580 if I2C
+	select SND_SOC_WM8711 if SND_SOC_I2C_AND_SPI
+	select SND_SOC_WM8727
+	select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI
+	select SND_SOC_WM8731 if SND_SOC_I2C_AND_SPI
+	select SND_SOC_WM8741 if SND_SOC_I2C_AND_SPI
+	select SND_SOC_WM8750 if SND_SOC_I2C_AND_SPI
+	select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI
+	select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI
+	select SND_SOC_WM8804 if SND_SOC_I2C_AND_SPI
+	select SND_SOC_WM8900 if I2C
+	select SND_SOC_WM8903 if I2C
+	select SND_SOC_WM8904 if I2C
+	select SND_SOC_WM8940 if I2C
+	select SND_SOC_WM8955 if I2C
+	select SND_SOC_WM8960 if I2C
+	select SND_SOC_WM8961 if I2C
+	select SND_SOC_WM8962 if I2C
+	select SND_SOC_WM8971 if I2C
+	select SND_SOC_WM8974 if I2C
+	select SND_SOC_WM8978 if I2C
+	select SND_SOC_WM8985 if SND_SOC_I2C_AND_SPI
+	select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI
+	select SND_SOC_WM8990 if I2C
+	select SND_SOC_WM8993 if I2C
+	select SND_SOC_WM8994 if MFD_WM8994
+	select SND_SOC_WM9081 if I2C
+	select SND_SOC_WM9090 if I2C
+	select SND_SOC_WM9705 if SND_SOC_AC97_BUS
+	select SND_SOC_WM9712 if SND_SOC_AC97_BUS
+	select SND_SOC_WM9713 if SND_SOC_AC97_BUS
+        help
+          Normally ASoC codec drivers are only built if a machine driver which
+          uses them is also built since they are only usable with a machine
+          driver.  Selecting this option will allow these drivers to be built
+          without an explicit machine driver for test and development purposes.
+
+	  Support for the bus types used to access the codecs to be built must
+	  be selected separately.
+
+          If unsure select "N".
+
+config SND_SOC_88PM860X
+	tristate
+
+config SND_SOC_WM_HUBS
+	tristate
+	default y if SND_SOC_WM8993=y || SND_SOC_WM8994=y
+	default m if SND_SOC_WM8993=m || SND_SOC_WM8994=m
+
+config SND_SOC_AC97_CODEC
+	tristate
+	select SND_AC97_CODEC
+
+config SND_SOC_AD1836
+	tristate
+
+config SND_SOC_AD193X
+	tristate
+
+config SND_SOC_AD1980
+	tristate
+
+config SND_SOC_AD73311
+	tristate
+	
+config SND_SOC_ADS117X
+	tristate
+
+config SND_SOC_AK4104
+	tristate
+
+config SND_SOC_AK4535
+	tristate
+
+config SND_SOC_AK4642
+	tristate
+
+config SND_SOC_AK4671
+	tristate
+
+config SND_SOC_CQ0093VC
+	tristate
+
+config SND_SOC_CS42L51
+	tristate
+
+# Cirrus Logic CS4270 Codec
+config SND_SOC_CS4270
+	tristate
+
+# Cirrus Logic CS4270 Codec VD = 3.3V Errata
+# Select if you are affected by the errata where the part will not function
+# if MCLK divide-by-1.5 is selected and VD is set to 3.3V.  The driver will
+# not select any sample rates that require MCLK to be divided by 1.5.
+config SND_SOC_CS4270_VD33_ERRATA
+	bool
+	depends on SND_SOC_CS4270
+
+config SND_SOC_CX20442
+	tristate
+
+config SND_SOC_JZ4740_CODEC
+	tristate
+
+config SND_SOC_L3
+       tristate
+
+config SND_SOC_DA7210
+        tristate
+
+config SND_SOC_MAX98088
+       tristate
+
+config SND_SOC_PCM3008
+       tristate
+
+config SND_SOC_SPDIF
+	tristate
+
+config SND_SOC_SSM2602
+	tristate
+
+config SND_SOC_STAC9766
+	tristate
+
+config SND_SOC_TLV320AIC23
+	tristate
+
+config SND_SOC_TLV320AIC26
+	tristate "TI TLV320AIC26 Codec support" if SND_SOC_OF_SIMPLE
+	depends on SPI
+
+config SND_SOC_TLV320AIC3X
+	tristate
+
+config SND_SOC_TLV320DAC33
+	tristate
+
+config SND_SOC_TWL4030
+	select TWL4030_CODEC
+	tristate
+
+config SND_SOC_TWL6040
+	tristate
+
+config SND_SOC_UDA134X
+       tristate
+
+config SND_SOC_UDA1380
+        tristate
+
+config SND_SOC_WL1273
+	tristate
+
+config SND_SOC_WL1271BT
+	tristate "WL1271 Bluetooth Codec support"
+	default n
+
+config SND_SOC_WM8350
+	tristate
+
+config SND_SOC_WM8400
+	tristate
+
+config SND_SOC_WM8510
+	tristate
+
+config SND_SOC_WM8523
+	tristate
+
+config SND_SOC_WM8580
+	tristate
+
+config SND_SOC_WM8711
+	tristate
+
+config SND_SOC_WM8727
+	tristate
+
+config SND_SOC_WM8728
+	tristate
+
+config SND_SOC_WM8731
+	tristate
+
+config SND_SOC_WM8741
+	tristate
+
+config SND_SOC_WM8750
+	tristate
+
+config SND_SOC_WM8753
+	tristate
+
+config SND_SOC_WM8776
+	tristate
+
+config SND_SOC_WM8804
+	tristate
+
+config SND_SOC_WM8900
+	tristate
+
+config SND_SOC_WM8903
+	tristate
+
+config SND_SOC_WM8904
+	tristate
+
+config SND_SOC_WM8940
+        tristate
+
+config SND_SOC_WM8955
+	tristate
+
+config SND_SOC_WM8960
+	tristate
+
+config SND_SOC_WM8961
+	tristate
+
+config SND_SOC_WM8962
+	tristate
+
+config SND_SOC_WM8971
+	tristate
+
+config SND_SOC_WM8974
+	tristate
+
+config SND_SOC_WM8978
+	tristate
+
+config SND_SOC_WM8985
+	tristate
+
+config SND_SOC_WM8988
+	tristate
+
+config SND_SOC_WM8990
+	tristate
+
+config SND_SOC_WM8993
+	tristate
+
+config SND_SOC_WM8994
+	tristate
+
+config SND_SOC_WM9081
+	tristate
+
+config SND_SOC_WM9705
+	tristate
+
+config SND_SOC_WM9712
+	tristate
+
+config SND_SOC_WM9713
+	tristate
+
+# Amp
+config SND_SOC_MAX9877
+	tristate
+
+config SND_SOC_TPA6130A2
+	tristate
+
+config SND_SOC_WM2000
+	tristate
+
+config SND_SOC_WM9090
+	tristate
diff --git a/linux/sound/soc/codecs/Makefile b/linux/sound/soc/codecs/Makefile
new file mode 100644
index 0000000..ecf5f6f
--- /dev/null
+++ b/linux/sound/soc/codecs/Makefile
@@ -0,0 +1,149 @@
+snd-soc-88pm860x-objs := 88pm860x-codec.o
+snd-soc-ac97-objs := ac97.o
+snd-soc-ad1836-objs := ad1836.o
+snd-soc-ad193x-objs := ad193x.o
+snd-soc-ad1980-objs := ad1980.o
+snd-soc-ad73311-objs := ad73311.o
+snd-soc-ads117x-objs := ads117x.o
+snd-soc-ak4104-objs := ak4104.o
+snd-soc-ak4535-objs := ak4535.o
+snd-soc-ak4642-objs := ak4642.o
+snd-soc-ak4671-objs := ak4671.o
+snd-soc-cq93vc-objs := cq93vc.o
+snd-soc-cs42l51-objs := cs42l51.o
+snd-soc-cs4270-objs := cs4270.o
+snd-soc-cx20442-objs := cx20442.o
+snd-soc-da7210-objs := da7210.o
+snd-soc-l3-objs := l3.o
+snd-soc-max98088-objs := max98088.o
+snd-soc-pcm3008-objs := pcm3008.o
+snd-soc-spdif-objs := spdif_transciever.o
+snd-soc-ssm2602-objs := ssm2602.o
+snd-soc-stac9766-objs := stac9766.o
+snd-soc-tlv320aic23-objs := tlv320aic23.o
+snd-soc-tlv320aic26-objs := tlv320aic26.o
+snd-soc-tlv320aic3x-objs := tlv320aic3x.o
+snd-soc-tlv320dac33-objs := tlv320dac33.o
+snd-soc-twl4030-objs := twl4030.o
+snd-soc-twl6040-objs := twl6040.o
+snd-soc-uda134x-objs := uda134x.o
+snd-soc-uda1380-objs := uda1380.o
+snd-soc-wl1271bt-objs := wl1271bt.o
+snd-soc-wl1273-objs := wl1273.o
+snd-soc-wm8350-objs := wm8350.o
+snd-soc-wm8400-objs := wm8400.o
+snd-soc-wm8510-objs := wm8510.o
+snd-soc-wm8523-objs := wm8523.o
+snd-soc-wm8580-objs := wm8580.o
+snd-soc-wm8711-objs := wm8711.o
+snd-soc-wm8727-objs := wm8727.o
+snd-soc-wm8728-objs := wm8728.o
+snd-soc-wm8731-objs := wm8731.o
+snd-soc-wm8741-objs := wm8741.o
+snd-soc-wm8750-objs := wm8750.o
+snd-soc-wm8753-objs := wm8753.o
+snd-soc-wm8776-objs := wm8776.o
+snd-soc-wm8804-objs := wm8804.o
+snd-soc-wm8900-objs := wm8900.o
+snd-soc-wm8903-objs := wm8903.o
+snd-soc-wm8904-objs := wm8904.o
+snd-soc-wm8940-objs := wm8940.o
+snd-soc-wm8955-objs := wm8955.o
+snd-soc-wm8960-objs := wm8960.o
+snd-soc-wm8961-objs := wm8961.o
+snd-soc-wm8962-objs := wm8962.o
+snd-soc-wm8971-objs := wm8971.o
+snd-soc-wm8974-objs := wm8974.o
+snd-soc-wm8978-objs := wm8978.o
+snd-soc-wm8985-objs := wm8985.o
+snd-soc-wm8988-objs := wm8988.o
+snd-soc-wm8990-objs := wm8990.o
+snd-soc-wm8993-objs := wm8993.o
+snd-soc-wm8994-objs := wm8994.o
+snd-soc-wm9081-objs := wm9081.o
+snd-soc-wm9705-objs := wm9705.o
+snd-soc-wm9712-objs := wm9712.o
+snd-soc-wm9713-objs := wm9713.o
+snd-soc-wm-hubs-objs := wm_hubs.o
+snd-soc-jz4740-codec-objs := jz4740.o
+
+# Amp
+snd-soc-max9877-objs := max9877.o
+snd-soc-tpa6130a2-objs := tpa6130a2.o
+snd-soc-wm2000-objs := wm2000.o
+snd-soc-wm9090-objs := wm9090.o
+
+obj-$(CONFIG_SND_SOC_88PM860X)	+= snd-soc-88pm860x.o
+obj-$(CONFIG_SND_SOC_AC97_CODEC)	+= snd-soc-ac97.o
+obj-$(CONFIG_SND_SOC_AD1836)	+= snd-soc-ad1836.o
+obj-$(CONFIG_SND_SOC_AD193X)	+= snd-soc-ad193x.o
+obj-$(CONFIG_SND_SOC_AD1980)	+= snd-soc-ad1980.o
+obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
+obj-$(CONFIG_SND_SOC_ADS117X)	+= snd-soc-ads117x.o
+obj-$(CONFIG_SND_SOC_AK4104)	+= snd-soc-ak4104.o
+obj-$(CONFIG_SND_SOC_AK4535)	+= snd-soc-ak4535.o
+obj-$(CONFIG_SND_SOC_AK4642)	+= snd-soc-ak4642.o
+obj-$(CONFIG_SND_SOC_AK4671)	+= snd-soc-ak4671.o
+obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
+obj-$(CONFIG_SND_SOC_CS42L51)	+= snd-soc-cs42l51.o
+obj-$(CONFIG_SND_SOC_CS4270)	+= snd-soc-cs4270.o
+obj-$(CONFIG_SND_SOC_CX20442)	+= snd-soc-cx20442.o
+obj-$(CONFIG_SND_SOC_DA7210)	+= snd-soc-da7210.o
+obj-$(CONFIG_SND_SOC_L3)	+= snd-soc-l3.o
+obj-$(CONFIG_SND_SOC_JZ4740_CODEC)	+= snd-soc-jz4740-codec.o
+obj-$(CONFIG_SND_SOC_MAX98088)	+= snd-soc-max98088.o
+obj-$(CONFIG_SND_SOC_PCM3008)	+= snd-soc-pcm3008.o
+obj-$(CONFIG_SND_SOC_SPDIF)	+= snd-soc-spdif.o
+obj-$(CONFIG_SND_SOC_SSM2602)	+= snd-soc-ssm2602.o
+obj-$(CONFIG_SND_SOC_STAC9766)	+= snd-soc-stac9766.o
+obj-$(CONFIG_SND_SOC_TLV320AIC23)	+= snd-soc-tlv320aic23.o
+obj-$(CONFIG_SND_SOC_TLV320AIC26)	+= snd-soc-tlv320aic26.o
+obj-$(CONFIG_SND_SOC_TLV320AIC3X)	+= snd-soc-tlv320aic3x.o
+obj-$(CONFIG_SND_SOC_TLV320DAC33)	+= snd-soc-tlv320dac33.o
+obj-$(CONFIG_SND_SOC_TWL4030)	+= snd-soc-twl4030.o
+obj-$(CONFIG_SND_SOC_TWL6040)	+= snd-soc-twl6040.o
+obj-$(CONFIG_SND_SOC_UDA134X)	+= snd-soc-uda134x.o
+obj-$(CONFIG_SND_SOC_UDA1380)	+= snd-soc-uda1380.o
+obj-$(CONFIG_SND_SOC_WL1271BT)   += snd-soc-wl1271bt.o
+obj-$(CONFIG_SND_SOC_WL1273)	+= snd-soc-wl1273.o
+obj-$(CONFIG_SND_SOC_WM8350)	+= snd-soc-wm8350.o
+obj-$(CONFIG_SND_SOC_WM8400)	+= snd-soc-wm8400.o
+obj-$(CONFIG_SND_SOC_WM8510)	+= snd-soc-wm8510.o
+obj-$(CONFIG_SND_SOC_WM8523)	+= snd-soc-wm8523.o
+obj-$(CONFIG_SND_SOC_WM8580)	+= snd-soc-wm8580.o
+obj-$(CONFIG_SND_SOC_WM8711)	+= snd-soc-wm8711.o
+obj-$(CONFIG_SND_SOC_WM8727)	+= snd-soc-wm8727.o
+obj-$(CONFIG_SND_SOC_WM8728)	+= snd-soc-wm8728.o
+obj-$(CONFIG_SND_SOC_WM8731)	+= snd-soc-wm8731.o
+obj-$(CONFIG_SND_SOC_WM8741)	+= snd-soc-wm8741.o
+obj-$(CONFIG_SND_SOC_WM8750)	+= snd-soc-wm8750.o
+obj-$(CONFIG_SND_SOC_WM8753)	+= snd-soc-wm8753.o
+obj-$(CONFIG_SND_SOC_WM8776)	+= snd-soc-wm8776.o
+obj-$(CONFIG_SND_SOC_WM8804)	+= snd-soc-wm8804.o
+obj-$(CONFIG_SND_SOC_WM8900)	+= snd-soc-wm8900.o
+obj-$(CONFIG_SND_SOC_WM8903)	+= snd-soc-wm8903.o
+obj-$(CONFIG_SND_SOC_WM8904)	+= snd-soc-wm8904.o
+obj-$(CONFIG_SND_SOC_WM8940)	+= snd-soc-wm8940.o
+obj-$(CONFIG_SND_SOC_WM8955)	+= snd-soc-wm8955.o
+obj-$(CONFIG_SND_SOC_WM8960)	+= snd-soc-wm8960.o
+obj-$(CONFIG_SND_SOC_WM8961)	+= snd-soc-wm8961.o
+obj-$(CONFIG_SND_SOC_WM8962)	+= snd-soc-wm8962.o
+obj-$(CONFIG_SND_SOC_WM8971)	+= snd-soc-wm8971.o
+obj-$(CONFIG_SND_SOC_WM8974)	+= snd-soc-wm8974.o
+obj-$(CONFIG_SND_SOC_WM8978)	+= snd-soc-wm8978.o
+obj-$(CONFIG_SND_SOC_WM8985)	+= snd-soc-wm8985.o
+obj-$(CONFIG_SND_SOC_WM8988)	+= snd-soc-wm8988.o
+obj-$(CONFIG_SND_SOC_WM8990)	+= snd-soc-wm8990.o
+obj-$(CONFIG_SND_SOC_WM8993)	+= snd-soc-wm8993.o
+obj-$(CONFIG_SND_SOC_WM8994)	+= snd-soc-wm8994.o
+obj-$(CONFIG_SND_SOC_WM9081)	+= snd-soc-wm9081.o
+obj-$(CONFIG_SND_SOC_WM9705)	+= snd-soc-wm9705.o
+obj-$(CONFIG_SND_SOC_WM9712)	+= snd-soc-wm9712.o
+obj-$(CONFIG_SND_SOC_WM9713)	+= snd-soc-wm9713.o
+obj-$(CONFIG_SND_SOC_WM_HUBS)	+= snd-soc-wm-hubs.o
+
+# Amp
+obj-$(CONFIG_SND_SOC_MAX9877)	+= snd-soc-max9877.o
+obj-$(CONFIG_SND_SOC_TPA6130A2)	+= snd-soc-tpa6130a2.o
+obj-$(CONFIG_SND_SOC_WM2000)	+= snd-soc-wm2000.o
+obj-$(CONFIG_SND_SOC_WM9090)	+= snd-soc-wm9090.o
diff --git a/linux/sound/soc/codecs/ac97.c b/linux/sound/soc/codecs/ac97.c
new file mode 100644
index 0000000..3c08793
--- /dev/null
+++ b/linux/sound/soc/codecs/ac97.c
@@ -0,0 +1,165 @@
+/*
+ * ac97.c  --  ALSA Soc AC97 codec support
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ *  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.
+ *
+ * Generic AC97 support.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+static int ac97_prepare(struct snd_pcm_substream *substream,
+			struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		  AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE;
+	return snd_ac97_set_rate(codec->ac97, reg, runtime->rate);
+}
+
+#define STD_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+		SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\
+		SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops ac97_dai_ops = {
+	.prepare	= ac97_prepare,
+};
+
+static struct snd_soc_dai_driver ac97_dai = {
+	.name = "ac97-hifi",
+	.ac97_control = 1,
+	.playback = {
+		.stream_name = "AC97 Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = STD_AC97_RATES,
+		.formats = SND_SOC_STD_AC97_FMTS,},
+	.capture = {
+		.stream_name = "AC97 Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = STD_AC97_RATES,
+		.formats = SND_SOC_STD_AC97_FMTS,},
+	.ops = &ac97_dai_ops,
+};
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	return soc_ac97_ops.read(codec->ac97, reg);
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int val)
+{
+	soc_ac97_ops.write(codec->ac97, reg, val);
+	return 0;
+}
+
+static int ac97_soc_probe(struct snd_soc_codec *codec)
+{
+	struct snd_ac97_bus *ac97_bus;
+	struct snd_ac97_template ac97_template;
+	int ret;
+
+	/* add codec as bus device for standard ac97 */
+	ret = snd_ac97_bus(codec->card->snd_card, 0, &soc_ac97_ops, NULL, &ac97_bus);
+	if (ret < 0)
+		return ret;
+
+	memset(&ac97_template, 0, sizeof(struct snd_ac97_template));
+	ret = snd_ac97_mixer(ac97_bus, &ac97_template, &codec->ac97);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int ac97_soc_remove(struct snd_soc_codec *codec)
+{
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int ac97_soc_suspend(struct snd_soc_codec *codec, pm_message_t msg)
+{
+	snd_ac97_suspend(codec->ac97);
+
+	return 0;
+}
+
+static int ac97_soc_resume(struct snd_soc_codec *codec)
+{
+	snd_ac97_resume(codec->ac97);
+
+	return 0;
+}
+#else
+#define ac97_soc_suspend NULL
+#define ac97_soc_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_ac97 = {
+	.write =	ac97_write,
+	.read =		ac97_read,
+	.probe = 	ac97_soc_probe,
+	.remove = 	ac97_soc_remove,
+	.suspend =	ac97_soc_suspend,
+	.resume =	ac97_soc_resume,
+};
+
+static __devinit int ac97_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_ac97, &ac97_dai, 1);
+}
+
+static int __devexit ac97_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver ac97_codec_driver = {
+	.driver = {
+		.name = "ac97-codec",
+		.owner = THIS_MODULE,
+	},
+
+	.probe = ac97_probe,
+	.remove = __devexit_p(ac97_remove),
+};
+
+static int __init ac97_init(void)
+{
+	return platform_driver_register(&ac97_codec_driver);
+}
+module_init(ac97_init);
+
+static void __exit ac97_exit(void)
+{
+	platform_driver_unregister(&ac97_codec_driver);
+}
+module_exit(ac97_exit);
+
+MODULE_DESCRIPTION("Soc Generic AC97 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ac97-codec");
diff --git a/linux/sound/soc/codecs/ad1836.c b/linux/sound/soc/codecs/ad1836.c
new file mode 100644
index 0000000..d272534
--- /dev/null
+++ b/linux/sound/soc/codecs/ad1836.c
@@ -0,0 +1,339 @@
+/*
+ * File:         sound/soc/codecs/ad1836.c
+ * Author:       Barry Song <Barry.Song@analog.com>
+ *
+ * Created:      Aug 04 2009
+ * Description:  Driver for AD1836 sound chip
+ *
+ * Modified:
+ *               Copyright 2009 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/soc-dapm.h>
+#include <linux/spi/spi.h>
+#include "ad1836.h"
+
+/* codec private data */
+struct ad1836_priv {
+	enum snd_soc_control_type control_type;
+	void *control_data;
+};
+
+/*
+ * AD1836 volume/mute/de-emphasis etc. controls
+ */
+static const char *ad1836_deemp[] = {"None", "44.1kHz", "32kHz", "48kHz"};
+
+static const struct soc_enum ad1836_deemp_enum =
+	SOC_ENUM_SINGLE(AD1836_DAC_CTRL1, 8, 4, ad1836_deemp);
+
+static const struct snd_kcontrol_new ad1836_snd_controls[] = {
+	/* DAC volume control */
+	SOC_DOUBLE_R("DAC1 Volume", AD1836_DAC_L1_VOL,
+			AD1836_DAC_R1_VOL, 0, 0x3FF, 0),
+	SOC_DOUBLE_R("DAC2 Volume", AD1836_DAC_L2_VOL,
+			AD1836_DAC_R2_VOL, 0, 0x3FF, 0),
+	SOC_DOUBLE_R("DAC3 Volume", AD1836_DAC_L3_VOL,
+			AD1836_DAC_R3_VOL, 0, 0x3FF, 0),
+
+	/* ADC switch control */
+	SOC_DOUBLE("ADC1 Switch", AD1836_ADC_CTRL2, AD1836_ADCL1_MUTE,
+		AD1836_ADCR1_MUTE, 1, 1),
+	SOC_DOUBLE("ADC2 Switch", AD1836_ADC_CTRL2, AD1836_ADCL2_MUTE,
+		AD1836_ADCR2_MUTE, 1, 1),
+
+	/* DAC switch control */
+	SOC_DOUBLE("DAC1 Switch", AD1836_DAC_CTRL2, AD1836_DACL1_MUTE,
+		AD1836_DACR1_MUTE, 1, 1),
+	SOC_DOUBLE("DAC2 Switch", AD1836_DAC_CTRL2, AD1836_DACL2_MUTE,
+		AD1836_DACR2_MUTE, 1, 1),
+	SOC_DOUBLE("DAC3 Switch", AD1836_DAC_CTRL2, AD1836_DACL3_MUTE,
+		AD1836_DACR3_MUTE, 1, 1),
+
+	/* ADC high-pass filter */
+	SOC_SINGLE("ADC High Pass Filter Switch", AD1836_ADC_CTRL1,
+			AD1836_ADC_HIGHPASS_FILTER, 1, 0),
+
+	/* DAC de-emphasis */
+	SOC_ENUM("Playback Deemphasis", ad1836_deemp_enum),
+};
+
+static const struct snd_soc_dapm_widget ad1836_dapm_widgets[] = {
+	SND_SOC_DAPM_DAC("DAC", "Playback", AD1836_DAC_CTRL1,
+				AD1836_DAC_POWERDOWN, 1),
+	SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1836_ADC_CTRL1,
+				AD1836_ADC_POWERDOWN, 1, NULL, 0),
+	SND_SOC_DAPM_OUTPUT("DAC1OUT"),
+	SND_SOC_DAPM_OUTPUT("DAC2OUT"),
+	SND_SOC_DAPM_OUTPUT("DAC3OUT"),
+	SND_SOC_DAPM_INPUT("ADC1IN"),
+	SND_SOC_DAPM_INPUT("ADC2IN"),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+	{ "DAC", NULL, "ADC_PWR" },
+	{ "ADC", NULL, "ADC_PWR" },
+	{ "DAC1OUT", "DAC1 Switch", "DAC" },
+	{ "DAC2OUT", "DAC2 Switch", "DAC" },
+	{ "DAC3OUT", "DAC3 Switch", "DAC" },
+	{ "ADC", "ADC1 Switch", "ADC1IN" },
+	{ "ADC", "ADC2 Switch", "ADC2IN" },
+};
+
+/*
+ * DAI ops entries
+ */
+
+static int ad1836_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	/* at present, we support adc aux mode to interface with
+	 * blackfin sport tdm mode
+	 */
+	case SND_SOC_DAIFMT_DSP_A:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_IB_IF:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	/* ALCLK,ABCLK are both output, AD1836 can only be master */
+	case SND_SOC_DAIFMT_CBM_CFM:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int ad1836_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params,
+		struct snd_soc_dai *dai)
+{
+	int word_len = 0;
+
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		word_len = 3;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		word_len = 1;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+	case SNDRV_PCM_FORMAT_S32_LE:
+		word_len = 0;
+		break;
+	}
+
+	snd_soc_update_bits(codec, AD1836_DAC_CTRL1,
+		AD1836_DAC_WORD_LEN_MASK, word_len);
+
+	snd_soc_update_bits(codec, AD1836_ADC_CTRL2,
+		AD1836_ADC_WORD_LEN_MASK, word_len);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int ad1836_soc_suspend(struct snd_soc_codec *codec,
+		pm_message_t state)
+{
+	/* reset clock control mode */
+	u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
+	adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK;
+
+	return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
+}
+
+static int ad1836_soc_resume(struct snd_soc_codec *codec)
+{
+	/* restore clock control mode */
+	u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
+	adc_ctrl2 |= AD1836_ADC_AUX;
+
+	return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
+}
+#else
+#define ad1836_soc_suspend NULL
+#define ad1836_soc_resume  NULL
+#endif
+
+static struct snd_soc_dai_ops ad1836_dai_ops = {
+	.hw_params = ad1836_hw_params,
+	.set_fmt = ad1836_set_dai_fmt,
+};
+
+/* codec DAI instance */
+static struct snd_soc_dai_driver ad1836_dai = {
+	.name = "ad1836-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 6,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 4,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
+	},
+	.ops = &ad1836_dai_ops,
+};
+
+static int ad1836_probe(struct snd_soc_codec *codec)
+{
+	struct ad1836_priv *ad1836 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	codec->control_data = ad1836->control_data;
+	ret = snd_soc_codec_set_cache_io(codec, 4, 12, SND_SOC_SPI);
+	if (ret < 0) {
+		dev_err(codec->dev, "failed to set cache I/O: %d\n",
+				ret);
+		kfree(ad1836);
+		return ret;
+	}
+
+	/* default setting for ad1836 */
+	/* de-emphasis: 48kHz, power-on dac */
+	snd_soc_write(codec, AD1836_DAC_CTRL1, 0x300);
+	/* unmute dac channels */
+	snd_soc_write(codec, AD1836_DAC_CTRL2, 0x0);
+	/* high-pass filter enable, power-on adc */
+	snd_soc_write(codec, AD1836_ADC_CTRL1, 0x100);
+	/* unmute adc channles, adc aux mode */
+	snd_soc_write(codec, AD1836_ADC_CTRL2, 0x180);
+	/* left/right diff:PGA/MUX */
+	snd_soc_write(codec, AD1836_ADC_CTRL3, 0x3A);
+	/* volume */
+	snd_soc_write(codec, AD1836_DAC_L1_VOL, 0x3FF);
+	snd_soc_write(codec, AD1836_DAC_R1_VOL, 0x3FF);
+	snd_soc_write(codec, AD1836_DAC_L2_VOL, 0x3FF);
+	snd_soc_write(codec, AD1836_DAC_R2_VOL, 0x3FF);
+	snd_soc_write(codec, AD1836_DAC_L3_VOL, 0x3FF);
+	snd_soc_write(codec, AD1836_DAC_R3_VOL, 0x3FF);
+
+	snd_soc_add_controls(codec, ad1836_snd_controls,
+			     ARRAY_SIZE(ad1836_snd_controls));
+	snd_soc_dapm_new_controls(codec, ad1836_dapm_widgets,
+				  ARRAY_SIZE(ad1836_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
+
+	return ret;
+}
+
+/* power down chip */
+static int ad1836_remove(struct snd_soc_codec *codec)
+{
+	/* reset clock control mode */
+	u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2);
+	adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK;
+
+	return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2);
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ad1836 = {
+	.probe = 	ad1836_probe,
+	.remove = 	ad1836_remove,
+	.suspend =      ad1836_soc_suspend,
+	.resume =       ad1836_soc_resume,
+	.reg_cache_size = AD1836_NUM_REGS,
+	.reg_word_size = sizeof(u16),
+};
+
+static int __devinit ad1836_spi_probe(struct spi_device *spi)
+{
+	struct ad1836_priv *ad1836;
+	int ret;
+
+	ad1836 = kzalloc(sizeof(struct ad1836_priv), GFP_KERNEL);
+	if (ad1836 == NULL)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, ad1836);
+	ad1836->control_data = spi;
+	ad1836->control_type = SND_SOC_SPI;
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_dev_ad1836, &ad1836_dai, 1);
+	if (ret < 0)
+		kfree(ad1836);
+	return ret;
+}
+
+static int __devexit ad1836_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver ad1836_spi_driver = {
+	.driver = {
+		.name	= "ad1836-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= ad1836_spi_probe,
+	.remove		= __devexit_p(ad1836_spi_remove),
+};
+
+static int __init ad1836_init(void)
+{
+	int ret;
+
+	ret = spi_register_driver(&ad1836_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register ad1836 SPI driver: %d\n",
+				ret);
+	}
+
+	return ret;
+}
+module_init(ad1836_init);
+
+static void __exit ad1836_exit(void)
+{
+	spi_unregister_driver(&ad1836_spi_driver);
+}
+module_exit(ad1836_exit);
+
+MODULE_DESCRIPTION("ASoC ad1836 driver");
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/ad1836.h b/linux/sound/soc/codecs/ad1836.h
new file mode 100644
index 0000000..8455967
--- /dev/null
+++ b/linux/sound/soc/codecs/ad1836.h
@@ -0,0 +1,63 @@
+/*
+ * File:         sound/soc/codecs/ad1836.h
+ * Based on:
+ * Author:       Barry Song <Barry.Song@analog.com>
+ *
+ * Created:      Aug 04, 2009
+ * Description:  definitions for AD1836 registers
+ *
+ * Modified:
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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.
+ */
+
+#ifndef __AD1836_H__
+#define __AD1836_H__
+
+#define AD1836_DAC_CTRL1               0
+#define AD1836_DAC_POWERDOWN           2
+#define AD1836_DAC_SERFMT_MASK	       0xE0
+#define AD1836_DAC_SERFMT_PCK256       (0x4 << 5)
+#define AD1836_DAC_SERFMT_PCK128       (0x5 << 5)
+#define AD1836_DAC_WORD_LEN_MASK       0x18
+
+#define AD1836_DAC_CTRL2               1
+#define AD1836_DACL1_MUTE              0
+#define AD1836_DACR1_MUTE              1
+#define AD1836_DACL2_MUTE              2
+#define AD1836_DACR2_MUTE              3
+#define AD1836_DACL3_MUTE              4
+#define AD1836_DACR3_MUTE              5
+
+#define AD1836_DAC_L1_VOL              2
+#define AD1836_DAC_R1_VOL              3
+#define AD1836_DAC_L2_VOL              4
+#define AD1836_DAC_R2_VOL              5
+#define AD1836_DAC_L3_VOL              6
+#define AD1836_DAC_R3_VOL              7
+
+#define AD1836_ADC_CTRL1               12
+#define AD1836_ADC_POWERDOWN           7
+#define AD1836_ADC_HIGHPASS_FILTER     8
+
+#define AD1836_ADC_CTRL2               13
+#define AD1836_ADCL1_MUTE 		0
+#define AD1836_ADCR1_MUTE 		1
+#define AD1836_ADCL2_MUTE 		2
+#define AD1836_ADCR2_MUTE 		3
+#define AD1836_ADC_WORD_LEN_MASK       0x30
+#define AD1836_ADC_SERFMT_MASK	       (7 << 6)
+#define AD1836_ADC_SERFMT_PCK256       (0x4 << 6)
+#define AD1836_ADC_SERFMT_PCK128       (0x5 << 6)
+#define AD1836_ADC_AUX                 (0x6 << 6)
+
+#define AD1836_ADC_CTRL3               14
+
+#define AD1836_NUM_REGS                16
+
+#endif
diff --git a/linux/sound/soc/codecs/ad193x.c b/linux/sound/soc/codecs/ad193x.c
new file mode 100644
index 0000000..fa2834c
--- /dev/null
+++ b/linux/sound/soc/codecs/ad193x.c
@@ -0,0 +1,523 @@
+/*
+ * AD193X Audio Codec driver supporting AD1936/7/8/9
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/soc-dapm.h>
+#include "ad193x.h"
+
+/* codec private data */
+struct ad193x_priv {
+	u8 reg_cache[AD193X_NUM_REGS];
+	enum snd_soc_control_type bus_type;
+	void *control_data;
+	int sysclk;
+};
+
+/* ad193x register cache & default register settings */
+static const u8 ad193x_reg[AD193X_NUM_REGS] = {
+	0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0,
+};
+
+/*
+ * AD193X volume/mute/de-emphasis etc. controls
+ */
+static const char *ad193x_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"};
+
+static const struct soc_enum ad193x_deemp_enum =
+	SOC_ENUM_SINGLE(AD193X_DAC_CTRL2, 1, 4, ad193x_deemp);
+
+static const struct snd_kcontrol_new ad193x_snd_controls[] = {
+	/* DAC volume control */
+	SOC_DOUBLE_R("DAC1 Volume", AD193X_DAC_L1_VOL,
+			AD193X_DAC_R1_VOL, 0, 0xFF, 1),
+	SOC_DOUBLE_R("DAC2 Volume", AD193X_DAC_L2_VOL,
+			AD193X_DAC_R2_VOL, 0, 0xFF, 1),
+	SOC_DOUBLE_R("DAC3 Volume", AD193X_DAC_L3_VOL,
+			AD193X_DAC_R3_VOL, 0, 0xFF, 1),
+	SOC_DOUBLE_R("DAC4 Volume", AD193X_DAC_L4_VOL,
+			AD193X_DAC_R4_VOL, 0, 0xFF, 1),
+
+	/* ADC switch control */
+	SOC_DOUBLE("ADC1 Switch", AD193X_ADC_CTRL0, AD193X_ADCL1_MUTE,
+		AD193X_ADCR1_MUTE, 1, 1),
+	SOC_DOUBLE("ADC2 Switch", AD193X_ADC_CTRL0, AD193X_ADCL2_MUTE,
+		AD193X_ADCR2_MUTE, 1, 1),
+
+	/* DAC switch control */
+	SOC_DOUBLE("DAC1 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL1_MUTE,
+		AD193X_DACR1_MUTE, 1, 1),
+	SOC_DOUBLE("DAC2 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL2_MUTE,
+		AD193X_DACR2_MUTE, 1, 1),
+	SOC_DOUBLE("DAC3 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL3_MUTE,
+		AD193X_DACR3_MUTE, 1, 1),
+	SOC_DOUBLE("DAC4 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL4_MUTE,
+		AD193X_DACR4_MUTE, 1, 1),
+
+	/* ADC high-pass filter */
+	SOC_SINGLE("ADC High Pass Filter Switch", AD193X_ADC_CTRL0,
+			AD193X_ADC_HIGHPASS_FILTER, 1, 0),
+
+	/* DAC de-emphasis */
+	SOC_ENUM("Playback Deemphasis", ad193x_deemp_enum),
+};
+
+static const struct snd_soc_dapm_widget ad193x_dapm_widgets[] = {
+	SND_SOC_DAPM_DAC("DAC", "Playback", AD193X_DAC_CTRL0, 0, 1),
+	SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_SUPPLY("PLL_PWR", AD193X_PLL_CLK_CTRL0, 0, 1, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("ADC_PWR", AD193X_ADC_CTRL0, 0, 1, NULL, 0),
+	SND_SOC_DAPM_OUTPUT("DAC1OUT"),
+	SND_SOC_DAPM_OUTPUT("DAC2OUT"),
+	SND_SOC_DAPM_OUTPUT("DAC3OUT"),
+	SND_SOC_DAPM_OUTPUT("DAC4OUT"),
+	SND_SOC_DAPM_INPUT("ADC1IN"),
+	SND_SOC_DAPM_INPUT("ADC2IN"),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+	{ "DAC", NULL, "PLL_PWR" },
+	{ "ADC", NULL, "PLL_PWR" },
+	{ "DAC", NULL, "ADC_PWR" },
+	{ "ADC", NULL, "ADC_PWR" },
+	{ "DAC1OUT", "DAC1 Switch", "DAC" },
+	{ "DAC2OUT", "DAC2 Switch", "DAC" },
+	{ "DAC3OUT", "DAC3 Switch", "DAC" },
+	{ "DAC4OUT", "DAC4 Switch", "DAC" },
+	{ "ADC", "ADC1 Switch", "ADC1IN" },
+	{ "ADC", "ADC2 Switch", "ADC2IN" },
+};
+
+/*
+ * DAI ops entries
+ */
+
+static int ad193x_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	int reg;
+
+	reg = snd_soc_read(codec, AD193X_DAC_CTRL2);
+	reg = (mute > 0) ? reg | AD193X_DAC_MASTER_MUTE : reg &
+		(~AD193X_DAC_MASTER_MUTE);
+	snd_soc_write(codec, AD193X_DAC_CTRL2, reg);
+
+	return 0;
+}
+
+static int ad193x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+			       unsigned int rx_mask, int slots, int width)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	int dac_reg = snd_soc_read(codec, AD193X_DAC_CTRL1);
+	int adc_reg = snd_soc_read(codec, AD193X_ADC_CTRL2);
+
+	dac_reg &= ~AD193X_DAC_CHAN_MASK;
+	adc_reg &= ~AD193X_ADC_CHAN_MASK;
+
+	switch (slots) {
+	case 2:
+		dac_reg |= AD193X_DAC_2_CHANNELS << AD193X_DAC_CHAN_SHFT;
+		adc_reg |= AD193X_ADC_2_CHANNELS << AD193X_ADC_CHAN_SHFT;
+		break;
+	case 4:
+		dac_reg |= AD193X_DAC_4_CHANNELS << AD193X_DAC_CHAN_SHFT;
+		adc_reg |= AD193X_ADC_4_CHANNELS << AD193X_ADC_CHAN_SHFT;
+		break;
+	case 8:
+		dac_reg |= AD193X_DAC_8_CHANNELS << AD193X_DAC_CHAN_SHFT;
+		adc_reg |= AD193X_ADC_8_CHANNELS << AD193X_ADC_CHAN_SHFT;
+		break;
+	case 16:
+		dac_reg |= AD193X_DAC_16_CHANNELS << AD193X_DAC_CHAN_SHFT;
+		adc_reg |= AD193X_ADC_16_CHANNELS << AD193X_ADC_CHAN_SHFT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, AD193X_DAC_CTRL1, dac_reg);
+	snd_soc_write(codec, AD193X_ADC_CTRL2, adc_reg);
+
+	return 0;
+}
+
+static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	int adc_reg1, adc_reg2, dac_reg;
+
+	adc_reg1 = snd_soc_read(codec, AD193X_ADC_CTRL1);
+	adc_reg2 = snd_soc_read(codec, AD193X_ADC_CTRL2);
+	dac_reg = snd_soc_read(codec, AD193X_DAC_CTRL1);
+
+	/* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S
+	 * with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A)
+	 */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		adc_reg1 &= ~AD193X_ADC_SERFMT_MASK;
+		adc_reg1 |= AD193X_ADC_SERFMT_TDM;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		adc_reg1 &= ~AD193X_ADC_SERFMT_MASK;
+		adc_reg1 |= AD193X_ADC_SERFMT_AUX;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */
+		adc_reg2 &= ~AD193X_ADC_LEFT_HIGH;
+		adc_reg2 &= ~AD193X_ADC_BCLK_INV;
+		dac_reg &= ~AD193X_DAC_LEFT_HIGH;
+		dac_reg &= ~AD193X_DAC_BCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */
+		adc_reg2 |= AD193X_ADC_LEFT_HIGH;
+		adc_reg2 &= ~AD193X_ADC_BCLK_INV;
+		dac_reg |= AD193X_DAC_LEFT_HIGH;
+		dac_reg &= ~AD193X_DAC_BCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */
+		adc_reg2 &= ~AD193X_ADC_LEFT_HIGH;
+		adc_reg2 |= AD193X_ADC_BCLK_INV;
+		dac_reg &= ~AD193X_DAC_LEFT_HIGH;
+		dac_reg |= AD193X_DAC_BCLK_INV;
+		break;
+
+	case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */
+		adc_reg2 |= AD193X_ADC_LEFT_HIGH;
+		adc_reg2 |= AD193X_ADC_BCLK_INV;
+		dac_reg |= AD193X_DAC_LEFT_HIGH;
+		dac_reg |= AD193X_DAC_BCLK_INV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */
+		adc_reg2 |= AD193X_ADC_LCR_MASTER;
+		adc_reg2 |= AD193X_ADC_BCLK_MASTER;
+		dac_reg |= AD193X_DAC_LCR_MASTER;
+		dac_reg |= AD193X_DAC_BCLK_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */
+		adc_reg2 |= AD193X_ADC_LCR_MASTER;
+		adc_reg2 &= ~AD193X_ADC_BCLK_MASTER;
+		dac_reg |= AD193X_DAC_LCR_MASTER;
+		dac_reg &= ~AD193X_DAC_BCLK_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */
+		adc_reg2 &= ~AD193X_ADC_LCR_MASTER;
+		adc_reg2 |= AD193X_ADC_BCLK_MASTER;
+		dac_reg &= ~AD193X_DAC_LCR_MASTER;
+		dac_reg |= AD193X_DAC_BCLK_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */
+		adc_reg2 &= ~AD193X_ADC_LCR_MASTER;
+		adc_reg2 &= ~AD193X_ADC_BCLK_MASTER;
+		dac_reg &= ~AD193X_DAC_LCR_MASTER;
+		dac_reg &= ~AD193X_DAC_BCLK_MASTER;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, AD193X_ADC_CTRL1, adc_reg1);
+	snd_soc_write(codec, AD193X_ADC_CTRL2, adc_reg2);
+	snd_soc_write(codec, AD193X_DAC_CTRL1, dac_reg);
+
+	return 0;
+}
+
+static int ad193x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
+	switch (freq) {
+	case 12288000:
+	case 18432000:
+	case 24576000:
+	case 36864000:
+		ad193x->sysclk = freq;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int ad193x_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params,
+		struct snd_soc_dai *dai)
+{
+	int word_len = 0, reg = 0, master_rate = 0;
+
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		word_len = 3;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		word_len = 1;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+	case SNDRV_PCM_FORMAT_S32_LE:
+		word_len = 0;
+		break;
+	}
+
+	switch (ad193x->sysclk) {
+	case 12288000:
+		master_rate = AD193X_PLL_INPUT_256;
+		break;
+	case 18432000:
+		master_rate = AD193X_PLL_INPUT_384;
+		break;
+	case 24576000:
+		master_rate = AD193X_PLL_INPUT_512;
+		break;
+	case 36864000:
+		master_rate = AD193X_PLL_INPUT_768;
+		break;
+	}
+
+	reg = snd_soc_read(codec, AD193X_PLL_CLK_CTRL0);
+	reg = (reg & AD193X_PLL_INPUT_MASK) | master_rate;
+	snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, reg);
+
+	reg = snd_soc_read(codec, AD193X_DAC_CTRL2);
+	reg = (reg & (~AD193X_DAC_WORD_LEN_MASK)) | word_len;
+	snd_soc_write(codec, AD193X_DAC_CTRL2, reg);
+
+	reg = snd_soc_read(codec, AD193X_ADC_CTRL1);
+	reg = (reg & (~AD193X_ADC_WORD_LEN_MASK)) | word_len;
+	snd_soc_write(codec, AD193X_ADC_CTRL1, reg);
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops ad193x_dai_ops = {
+	.hw_params = ad193x_hw_params,
+	.digital_mute = ad193x_mute,
+	.set_tdm_slot = ad193x_set_tdm_slot,
+	.set_sysclk	= ad193x_set_dai_sysclk,
+	.set_fmt = ad193x_set_dai_fmt,
+};
+
+/* codec DAI instance */
+static struct snd_soc_dai_driver ad193x_dai = {
+	.name = "ad193x-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 8,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 4,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
+	},
+	.ops = &ad193x_dai_ops,
+};
+
+static int ad193x_probe(struct snd_soc_codec *codec)
+{
+	struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	codec->control_data = ad193x->control_data;
+	if (ad193x->bus_type == SND_SOC_I2C)
+		ret = snd_soc_codec_set_cache_io(codec, 8, 8, ad193x->bus_type);
+	else
+		ret = snd_soc_codec_set_cache_io(codec, 16, 8, ad193x->bus_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "failed to set cache I/O: %d\n",
+				ret);
+		kfree(ad193x);
+		return ret;
+	}
+
+	/* default setting for ad193x */
+
+	/* unmute dac channels */
+	snd_soc_write(codec, AD193X_DAC_CHNL_MUTE, 0x0);
+	/* de-emphasis: 48kHz, powedown dac */
+	snd_soc_write(codec, AD193X_DAC_CTRL2, 0x1A);
+	/* powerdown dac, dac in tdm mode */
+	snd_soc_write(codec, AD193X_DAC_CTRL0, 0x41);
+	/* high-pass filter enable */
+	snd_soc_write(codec, AD193X_ADC_CTRL0, 0x3);
+	/* sata delay=1, adc aux mode */
+	snd_soc_write(codec, AD193X_ADC_CTRL1, 0x43);
+	/* pll input: mclki/xi */
+	snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */
+	snd_soc_write(codec, AD193X_PLL_CLK_CTRL1, 0x04);
+
+	snd_soc_add_controls(codec, ad193x_snd_controls,
+			     ARRAY_SIZE(ad193x_snd_controls));
+	snd_soc_dapm_new_controls(codec, ad193x_dapm_widgets,
+				  ARRAY_SIZE(ad193x_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
+
+	return ret;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ad193x = {
+	.probe = 	ad193x_probe,
+	.reg_cache_default = ad193x_reg,
+	.reg_cache_size = AD193X_NUM_REGS,
+	.reg_word_size = sizeof(u16),
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit ad193x_spi_probe(struct spi_device *spi)
+{
+	struct ad193x_priv *ad193x;
+	int ret;
+
+	ad193x = kzalloc(sizeof(struct ad193x_priv), GFP_KERNEL);
+	if (ad193x == NULL)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, ad193x);
+	ad193x->control_data = spi;
+	ad193x->bus_type = SND_SOC_SPI;
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_dev_ad193x, &ad193x_dai, 1);
+	if (ret < 0)
+		kfree(ad193x);
+	return ret;
+}
+
+static int __devexit ad193x_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver ad193x_spi_driver = {
+	.driver = {
+		.name	= "ad193x-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= ad193x_spi_probe,
+	.remove		= __devexit_p(ad193x_spi_remove),
+};
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static const struct i2c_device_id ad193x_id[] = {
+	{ "ad1936", 0 },
+	{ "ad1937", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ad193x_id);
+
+static int __devinit ad193x_i2c_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct ad193x_priv *ad193x;
+	int ret;
+
+	ad193x = kzalloc(sizeof(struct ad193x_priv), GFP_KERNEL);
+	if (ad193x == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, ad193x);
+	ad193x->control_data = client;
+	ad193x->bus_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&client->dev,
+			&soc_codec_dev_ad193x, &ad193x_dai, 1);
+	if (ret < 0)
+		kfree(ad193x);
+	return ret;
+}
+
+static int __devexit ad193x_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static struct i2c_driver ad193x_i2c_driver = {
+	.driver = {
+		.name = "ad193x-codec",
+	},
+	.probe    = ad193x_i2c_probe,
+	.remove   = __devexit_p(ad193x_i2c_remove),
+	.id_table = ad193x_id,
+};
+#endif
+
+static int __init ad193x_modinit(void)
+{
+	int ret;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret =  i2c_add_driver(&ad193x_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register AD193X I2C driver: %d\n",
+				ret);
+	}
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&ad193x_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register AD193X SPI driver: %d\n",
+				ret);
+	}
+#endif
+	return ret;
+}
+module_init(ad193x_modinit);
+
+static void __exit ad193x_modexit(void)
+{
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&ad193x_spi_driver);
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&ad193x_i2c_driver);
+#endif
+}
+module_exit(ad193x_modexit);
+
+MODULE_DESCRIPTION("ASoC ad193x driver");
+MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/ad193x.h b/linux/sound/soc/codecs/ad193x.h
new file mode 100644
index 0000000..9747b54
--- /dev/null
+++ b/linux/sound/soc/codecs/ad193x.h
@@ -0,0 +1,83 @@
+/*
+ * AD193X Audio Codec driver
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef __AD193X_H__
+#define __AD193X_H__
+
+#define AD193X_PLL_CLK_CTRL0    0x800
+#define AD193X_PLL_POWERDOWN           0x01
+#define AD193X_PLL_INPUT_MASK   (~0x6)
+#define AD193X_PLL_INPUT_256    (0 << 1)
+#define AD193X_PLL_INPUT_384    (1 << 1)
+#define AD193X_PLL_INPUT_512    (2 << 1)
+#define AD193X_PLL_INPUT_768    (3 << 1)
+#define AD193X_PLL_CLK_CTRL1    0x801
+#define AD193X_DAC_CTRL0        0x802
+#define AD193X_DAC_POWERDOWN           0x01
+#define AD193X_DAC_SERFMT_MASK		0xC0
+#define AD193X_DAC_SERFMT_STEREO	(0 << 6)
+#define AD193X_DAC_SERFMT_TDM		(1 << 6)
+#define AD193X_DAC_CTRL1        0x803
+#define AD193X_DAC_2_CHANNELS   0
+#define AD193X_DAC_4_CHANNELS   1
+#define AD193X_DAC_8_CHANNELS   2
+#define AD193X_DAC_16_CHANNELS  3
+#define AD193X_DAC_CHAN_SHFT    1
+#define AD193X_DAC_CHAN_MASK    (3 << AD193X_DAC_CHAN_SHFT)
+#define AD193X_DAC_LCR_MASTER   (1 << 4)
+#define AD193X_DAC_BCLK_MASTER  (1 << 5)
+#define AD193X_DAC_LEFT_HIGH    (1 << 3)
+#define AD193X_DAC_BCLK_INV     (1 << 7)
+#define AD193X_DAC_CTRL2        0x804
+#define AD193X_DAC_WORD_LEN_MASK	0xC
+#define AD193X_DAC_MASTER_MUTE  1
+#define AD193X_DAC_CHNL_MUTE    0x805
+#define AD193X_DACL1_MUTE       0
+#define AD193X_DACR1_MUTE       1
+#define AD193X_DACL2_MUTE       2
+#define AD193X_DACR2_MUTE       3
+#define AD193X_DACL3_MUTE       4
+#define AD193X_DACR3_MUTE       5
+#define AD193X_DACL4_MUTE       6
+#define AD193X_DACR4_MUTE       7
+#define AD193X_DAC_L1_VOL       0x806
+#define AD193X_DAC_R1_VOL       0x807
+#define AD193X_DAC_L2_VOL       0x808
+#define AD193X_DAC_R2_VOL       0x809
+#define AD193X_DAC_L3_VOL       0x80a
+#define AD193X_DAC_R3_VOL       0x80b
+#define AD193X_DAC_L4_VOL       0x80c
+#define AD193X_DAC_R4_VOL       0x80d
+#define AD193X_ADC_CTRL0        0x80e
+#define AD193X_ADC_POWERDOWN           0x01
+#define AD193X_ADC_HIGHPASS_FILTER	1
+#define AD193X_ADCL1_MUTE 		2
+#define AD193X_ADCR1_MUTE 		3
+#define AD193X_ADCL2_MUTE 		4
+#define AD193X_ADCR2_MUTE 		5
+#define AD193X_ADC_CTRL1        0x80f
+#define AD193X_ADC_SERFMT_MASK		0x60
+#define AD193X_ADC_SERFMT_STEREO	(0 << 5)
+#define AD193X_ADC_SERFMT_TDM		(1 << 2)
+#define AD193X_ADC_SERFMT_AUX		(2 << 5)
+#define AD193X_ADC_WORD_LEN_MASK	0x3
+#define AD193X_ADC_CTRL2        0x810
+#define AD193X_ADC_2_CHANNELS   0
+#define AD193X_ADC_4_CHANNELS   1
+#define AD193X_ADC_8_CHANNELS   2
+#define AD193X_ADC_16_CHANNELS  3
+#define AD193X_ADC_CHAN_SHFT    4
+#define AD193X_ADC_CHAN_MASK    (3 << AD193X_ADC_CHAN_SHFT)
+#define AD193X_ADC_LCR_MASTER   (1 << 3)
+#define AD193X_ADC_BCLK_MASTER  (1 << 6)
+#define AD193X_ADC_LEFT_HIGH    (1 << 2)
+#define AD193X_ADC_BCLK_INV     (1 << 1)
+
+#define AD193X_NUM_REGS          17
+
+#endif
diff --git a/linux/sound/soc/codecs/ad1980.c b/linux/sound/soc/codecs/ad1980.c
new file mode 100644
index 0000000..410ccd5
--- /dev/null
+++ b/linux/sound/soc/codecs/ad1980.c
@@ -0,0 +1,292 @@
+/*
+ * ad1980.c  --  ALSA Soc AD1980 codec support
+ *
+ * Copyright:	Analog Device Inc.
+ * Author:	Roy Huang <roy.huang@analog.com>
+ * 		Cliff Cai <cliff.cai@analog.com>
+ *
+ *  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.
+ */
+
+/*
+ * WARNING:
+ *
+ * Because Analog Devices Inc. discontinued the ad1980 sound chip since
+ * Sep. 2009, this ad1980 driver is not maintained, tested and supported
+ * by ADI now.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "ad1980.h"
+
+/*
+ * AD1980 register cache
+ */
+static const u16 ad1980_reg[] = {
+	0x0090, 0x8000, 0x8000, 0x8000, /* 0 - 6  */
+	0x0000, 0x0000, 0x8008, 0x8008, /* 8 - e  */
+	0x8808, 0x8808, 0x0000, 0x8808, /* 10 - 16 */
+	0x8808, 0x0000, 0x8000, 0x0000, /* 18 - 1e */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 20 - 26 */
+	0x03c7, 0x0000, 0xbb80, 0xbb80, /* 28 - 2e */
+	0xbb80, 0xbb80, 0x0000, 0x8080, /* 30 - 36 */
+	0x8080, 0x2000, 0x0000, 0x0000, /* 38 - 3e */
+	0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+	0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+	0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+	0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+	0x8080, 0x0000, 0x0000, 0x0000, /* 60 - 66 */
+	0x0000, 0x0000, 0x0000, 0x0000, /* reserved */
+	0x0000, 0x0000, 0x1001, 0x0000, /* 70 - 76 */
+	0x0000, 0x0000, 0x4144, 0x5370  /* 78 - 7e */
+};
+
+static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line",
+		"Stereo Mix", "Mono Mix", "Phone"};
+
+static const struct soc_enum ad1980_cap_src =
+	SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 7, ad1980_rec_sel);
+
+static const struct snd_kcontrol_new ad1980_snd_ac97_controls[] = {
+SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1),
+
+SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
+
+SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
+SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1),
+
+SOC_DOUBLE("PCM Capture Volume", AC97_REC_GAIN, 8, 0, 31, 0),
+SOC_SINGLE("PCM Capture Switch", AC97_REC_GAIN, 15, 1, 1),
+
+SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+SOC_SINGLE("Phone Capture Volume", AC97_PHONE, 0, 31, 1),
+SOC_SINGLE("Phone Capture Switch", AC97_PHONE, 15, 1, 1),
+
+SOC_SINGLE("Mic Volume", AC97_MIC, 0, 31, 1),
+SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1),
+
+SOC_SINGLE("Stereo Mic Switch", AC97_AD_MISC, 6, 1, 0),
+SOC_DOUBLE("Line HP Swap Switch", AC97_AD_MISC, 10, 5, 1, 0),
+
+SOC_DOUBLE("Surround Playback Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1),
+SOC_DOUBLE("Surround Playback Switch", AC97_SURROUND_MASTER, 15, 7, 1, 1),
+
+SOC_DOUBLE("Center/LFE Playback Volume", AC97_CENTER_LFE_MASTER, 8, 0, 31, 1),
+SOC_DOUBLE("Center/LFE Playback Switch", AC97_CENTER_LFE_MASTER, 15, 7, 1, 1),
+
+SOC_ENUM("Capture Source", ad1980_cap_src),
+
+SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0),
+};
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+
+	switch (reg) {
+	case AC97_RESET:
+	case AC97_INT_PAGING:
+	case AC97_POWERDOWN:
+	case AC97_EXTENDED_STATUS:
+	case AC97_VENDOR_ID1:
+	case AC97_VENDOR_ID2:
+		return soc_ac97_ops.read(codec->ac97, reg);
+	default:
+		reg = reg >> 1;
+
+		if (reg >= ARRAY_SIZE(ad1980_reg))
+			return -EINVAL;
+
+		return cache[reg];
+	}
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int val)
+{
+	u16 *cache = codec->reg_cache;
+
+	soc_ac97_ops.write(codec->ac97, reg, val);
+	reg = reg >> 1;
+	if (reg < ARRAY_SIZE(ad1980_reg))
+		cache[reg] = val;
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver ad1980_dai = {
+	.name = "ad1980-hifi",
+	.ac97_control = 1,
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 6,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SND_SOC_STD_AC97_FMTS, },
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SND_SOC_STD_AC97_FMTS, },
+};
+EXPORT_SYMBOL_GPL(ad1980_dai);
+
+static int ad1980_reset(struct snd_soc_codec *codec, int try_warm)
+{
+	u16 retry_cnt = 0;
+
+retry:
+	if (try_warm && soc_ac97_ops.warm_reset) {
+		soc_ac97_ops.warm_reset(codec->ac97);
+		if (ac97_read(codec, AC97_RESET) == 0x0090)
+			return 1;
+	}
+
+	soc_ac97_ops.reset(codec->ac97);
+	/* Set bit 16slot in register 74h, then every slot will has only 16
+	 * bits. This command is sent out in 20bit mode, in which case the
+	 * first nibble of data is eaten by the addr. (Tag is always 16 bit)*/
+	ac97_write(codec, AC97_AD_SERIAL_CFG, 0x9900);
+
+	if (ac97_read(codec, AC97_RESET)  != 0x0090)
+		goto err;
+	return 0;
+
+err:
+	while (retry_cnt++ < 10)
+		goto retry;
+
+	printk(KERN_ERR "AD1980 AC97 reset failed\n");
+	return -EIO;
+}
+
+static int ad1980_soc_probe(struct snd_soc_codec *codec)
+{
+	int ret;
+	u16 vendor_id2;
+	u16 ext_status;
+
+	printk(KERN_INFO "AD1980 SoC Audio Codec\n");
+
+	ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+	if (ret < 0) {
+		printk(KERN_ERR "ad1980: failed to register AC97 codec\n");
+		return ret;
+	}
+
+	ret = ad1980_reset(codec, 0);
+	if (ret < 0) {
+		printk(KERN_ERR "Failed to reset AD1980: AC97 link error\n");
+		goto reset_err;
+	}
+
+	/* Read out vendor ID to make sure it is ad1980 */
+	if (ac97_read(codec, AC97_VENDOR_ID1) != 0x4144)
+		goto reset_err;
+
+	vendor_id2 = ac97_read(codec, AC97_VENDOR_ID2);
+
+	if (vendor_id2 != 0x5370) {
+		if (vendor_id2 != 0x5374)
+			goto reset_err;
+		else
+			printk(KERN_WARNING "ad1980: "
+				"Found AD1981 - only 2/2 IN/OUT Channels "
+				"supported\n");
+	}
+
+	/* unmute captures and playbacks volume */
+	ac97_write(codec, AC97_MASTER, 0x0000);
+	ac97_write(codec, AC97_PCM, 0x0000);
+	ac97_write(codec, AC97_REC_GAIN, 0x0000);
+	ac97_write(codec, AC97_CENTER_LFE_MASTER, 0x0000);
+	ac97_write(codec, AC97_SURROUND_MASTER, 0x0000);
+
+	/*power on LFE/CENTER/Surround DACs*/
+	ext_status = ac97_read(codec, AC97_EXTENDED_STATUS);
+	ac97_write(codec, AC97_EXTENDED_STATUS, ext_status&~0x3800);
+
+	snd_soc_add_controls(codec, ad1980_snd_ac97_controls,
+				ARRAY_SIZE(ad1980_snd_ac97_controls));
+
+	return 0;
+
+reset_err:
+	snd_soc_free_ac97_codec(codec);
+	return ret;
+}
+
+static int ad1980_soc_remove(struct snd_soc_codec *codec)
+{
+	snd_soc_free_ac97_codec(codec);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ad1980 = {
+	.probe = 	ad1980_soc_probe,
+	.remove = 	ad1980_soc_remove,
+	.reg_cache_size = ARRAY_SIZE(ad1980_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = ad1980_reg,
+	.reg_cache_step = 2,
+	.write = ac97_write,
+	.read = ac97_read,
+};
+
+static __devinit int ad1980_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_ad1980, &ad1980_dai, 1);
+}
+
+static int __devexit ad1980_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver ad1980_codec_driver = {
+	.driver = {
+			.name = "ad1980-codec",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = ad1980_probe,
+	.remove = __devexit_p(ad1980_remove),
+};
+
+static int __init ad1980_init(void)
+{
+	return platform_driver_register(&ad1980_codec_driver);
+}
+module_init(ad1980_init);
+
+static void __exit ad1980_exit(void)
+{
+	platform_driver_unregister(&ad1980_codec_driver);
+}
+module_exit(ad1980_exit);
+
+MODULE_DESCRIPTION("ASoC ad1980 driver (Obsolete)");
+MODULE_AUTHOR("Roy Huang, Cliff Cai");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/ad1980.h b/linux/sound/soc/codecs/ad1980.h
new file mode 100644
index 0000000..eb0af44
--- /dev/null
+++ b/linux/sound/soc/codecs/ad1980.h
@@ -0,0 +1,26 @@
+/*
+ * ad1980.h  --  ad1980 Soc Audio driver
+ *
+ * WARNING:
+ *
+ * Because Analog Devices Inc. discontinued the ad1980 sound chip since
+ * Sep. 2009, this ad1980 driver is not maintained, tested and supported
+ * by ADI now.
+ */
+
+#ifndef _AD1980_H
+#define _AD1980_H
+/* Bit definition of Power-Down Control/Status Register */
+#define ADC		0x0001
+#define DAC		0x0002
+#define ANL		0x0004
+#define REF		0x0008
+#define PR0		0x0100
+#define PR1		0x0200
+#define PR2		0x0400
+#define PR3		0x0800
+#define PR4		0x1000
+#define PR5		0x2000
+#define PR6		0x4000
+
+#endif
diff --git a/linux/sound/soc/codecs/ad73311.c b/linux/sound/soc/codecs/ad73311.c
new file mode 100644
index 0000000..de799cd
--- /dev/null
+++ b/linux/sound/soc/codecs/ad73311.c
@@ -0,0 +1,80 @@
+/*
+ * ad73311.c  --  ALSA Soc AD73311 codec support
+ *
+ * Copyright:	Analog Device Inc.
+ * Author:	Cliff Cai <cliff.cai@analog.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "ad73311.h"
+
+static struct snd_soc_dai_driver ad73311_dai = {
+	.name = "ad73311-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE, },
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE, },
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_ad73311;
+
+static int ad73311_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_ad73311, &ad73311_dai, 1);
+}
+
+static int __devexit ad73311_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver ad73311_codec_driver = {
+	.driver = {
+			.name = "ad73311-codec",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = ad73311_probe,
+	.remove = __devexit_p(ad73311_remove),
+};
+
+static int __init ad73311_init(void)
+{
+	return platform_driver_register(&ad73311_codec_driver);
+}
+module_init(ad73311_init);
+
+static void __exit ad73311_exit(void)
+{
+	platform_driver_unregister(&ad73311_codec_driver);
+}
+module_exit(ad73311_exit);
+
+MODULE_DESCRIPTION("ASoC ad73311 driver");
+MODULE_AUTHOR("Cliff Cai ");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/ad73311.h b/linux/sound/soc/codecs/ad73311.h
new file mode 100644
index 0000000..4b353ee
--- /dev/null
+++ b/linux/sound/soc/codecs/ad73311.h
@@ -0,0 +1,88 @@
+/*
+ * File:         sound/soc/codec/ad73311.h
+ * Based on:
+ * Author:       Cliff Cai <cliff.cai@analog.com>
+ *
+ * Created:      Thur Sep 25, 2008
+ * Description:  definitions for AD73311 registers
+ *
+ *
+ * Modified:
+ *               Copyright 2006 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __AD73311_H__
+#define __AD73311_H__
+
+#define AD_CONTROL	0x8000
+#define AD_DATA		0x0000
+#define AD_READ		0x4000
+#define AD_WRITE	0x0000
+
+/* Control register A */
+#define CTRL_REG_A	(0 << 8)
+
+#define REGA_MODE_PRO	0x00
+#define REGA_MODE_DATA	0x01
+#define REGA_MODE_MIXED	0x03
+#define REGA_DLB		0x04
+#define REGA_SLB		0x08
+#define REGA_DEVC(x)		((x & 0x7) << 4)
+#define REGA_RESET		0x80
+
+/* Control register B */
+#define CTRL_REG_B	(1 << 8)
+
+#define REGB_DIRATE(x)	(x & 0x3)
+#define REGB_SCDIV(x)	((x & 0x3) << 2)
+#define REGB_MCDIV(x)	((x & 0x7) << 4)
+#define REGB_CEE		(1 << 7)
+
+/* Control register C */
+#define CTRL_REG_C	(2 << 8)
+
+#define REGC_PUDEV		(1 << 0)
+#define REGC_PUADC		(1 << 3)
+#define REGC_PUDAC		(1 << 4)
+#define REGC_PUREF		(1 << 5)
+#define REGC_REFUSE		(1 << 6)
+
+/* Control register D */
+#define CTRL_REG_D	(3 << 8)
+
+#define REGD_IGS(x)		(x & 0x7)
+#define REGD_RMOD		(1 << 3)
+#define REGD_OGS(x)		((x & 0x7) << 4)
+#define REGD_MUTE		(1 << 7)
+
+/* Control register E */
+#define CTRL_REG_E	(4 << 8)
+
+#define REGE_DA(x)		(x & 0x1f)
+#define REGE_IBYP		(1 << 5)
+
+/* Control register F */
+#define CTRL_REG_F	(5 << 8)
+
+#define REGF_SEEN		(1 << 5)
+#define REGF_INV		(1 << 6)
+#define REGF_ALB		(1 << 7)
+
+#endif
diff --git a/linux/sound/soc/codecs/ads117x.c b/linux/sound/soc/codecs/ads117x.c
new file mode 100644
index 0000000..8402854
--- /dev/null
+++ b/linux/sound/soc/codecs/ads117x.c
@@ -0,0 +1,74 @@
+/*
+ * ads117x.c  --  Driver for ads1174/8 ADC chips
+ *
+ * Copyright 2009 ShotSpotter Inc.
+ * Author: Graeme Gregory <gg@slimlogic.co.uk>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#define ADS117X_RATES (SNDRV_PCM_RATE_8000_48000)
+#define ADS117X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE)
+
+static struct snd_soc_dai_driver ads117x_dai = {
+/* ADC */
+	.name = "ads117x-hifi",
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 32,
+		.rates = ADS117X_RATES,
+		.formats = ADS117X_FORMATS,},
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_ads117x;
+
+static __devinit int ads117x_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_ads117x, &ads117x_dai, 1);
+}
+
+static int __devexit ads117x_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver ads117x_codec_driver = {
+	.driver = {
+			.name = "ads117x-codec",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = ads117x_probe,
+	.remove = __devexit_p(ads117x_remove),
+};
+
+static int __init ads117x_init(void)
+{
+	return platform_driver_register(&ads117x_codec_driver);
+}
+module_init(ads117x_init);
+
+static void __exit ads117x_exit(void)
+{
+	platform_driver_unregister(&ads117x_codec_driver);
+}
+module_exit(ads117x_exit);
+
+MODULE_DESCRIPTION("ASoC ads117x driver");
+MODULE_AUTHOR("Graeme Gregory");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/ads117x.h b/linux/sound/soc/codecs/ads117x.h
new file mode 100644
index 0000000..3ce0286
--- /dev/null
+++ b/linux/sound/soc/codecs/ads117x.h
@@ -0,0 +1,13 @@
+/*
+ * ads117x.h  --  Driver for ads1174/8 ADC chips
+ *
+ * Copyright 2009 ShotSpotter Inc.
+ * Author: Graeme Gregory <gg@slimlogic.co.uk>
+ *
+ *  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.
+ */
+extern struct snd_soc_dai_driver ads117x_dai;
+extern struct snd_soc_codec_driver soc_codec_dev_ads117x;
diff --git a/linux/sound/soc/codecs/ak4104.c b/linux/sound/soc/codecs/ak4104.c
new file mode 100644
index 0000000..c27f8f5
--- /dev/null
+++ b/linux/sound/soc/codecs/ak4104.c
@@ -0,0 +1,311 @@
+/*
+ * AK4104 ALSA SoC (ASoC) driver
+ *
+ * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <linux/spi/spi.h>
+#include <sound/asoundef.h>
+
+/* AK4104 registers addresses */
+#define AK4104_REG_CONTROL1		0x00
+#define AK4104_REG_RESERVED		0x01
+#define AK4104_REG_CONTROL2		0x02
+#define AK4104_REG_TX			0x03
+#define AK4104_REG_CHN_STATUS(x)	((x) + 0x04)
+#define AK4104_NUM_REGS			10
+
+#define AK4104_REG_MASK			0x1f
+#define AK4104_READ			0xc0
+#define AK4104_WRITE			0xe0
+#define AK4104_RESERVED_VAL		0x5b
+
+/* Bit masks for AK4104 registers */
+#define AK4104_CONTROL1_RSTN		(1 << 0)
+#define AK4104_CONTROL1_PW		(1 << 1)
+#define AK4104_CONTROL1_DIF0		(1 << 2)
+#define AK4104_CONTROL1_DIF1		(1 << 3)
+
+#define AK4104_CONTROL2_SEL0		(1 << 0)
+#define AK4104_CONTROL2_SEL1		(1 << 1)
+#define AK4104_CONTROL2_MODE		(1 << 2)
+
+#define AK4104_TX_TXE			(1 << 0)
+#define AK4104_TX_V			(1 << 1)
+
+#define DRV_NAME "ak4104-codec"
+
+struct ak4104_private {
+	enum snd_soc_control_type control_type;
+	void *control_data;
+};
+
+static int ak4104_fill_cache(struct snd_soc_codec *codec)
+{
+	int i;
+	u8 *reg_cache = codec->reg_cache;
+	struct spi_device *spi = codec->control_data;
+
+	for (i = 0; i < codec->driver->reg_cache_size; i++) {
+		int ret = spi_w8r8(spi, i | AK4104_READ);
+		if (ret < 0) {
+			dev_err(&spi->dev, "SPI write failure\n");
+			return ret;
+		}
+
+		reg_cache[i] = ret;
+	}
+
+	return 0;
+}
+
+static unsigned int ak4104_read_reg_cache(struct snd_soc_codec *codec,
+					  unsigned int reg)
+{
+	u8 *reg_cache = codec->reg_cache;
+
+	if (reg >= codec->driver->reg_cache_size)
+		return -EINVAL;
+
+	return reg_cache[reg];
+}
+
+static int ak4104_spi_write(struct snd_soc_codec *codec, unsigned int reg,
+			    unsigned int value)
+{
+	u8 *cache = codec->reg_cache;
+	struct spi_device *spi = codec->control_data;
+
+	if (reg >= codec->driver->reg_cache_size)
+		return -EINVAL;
+
+	/* only write to the hardware if value has changed */
+	if (cache[reg] != value) {
+		u8 tmp[2] = { (reg & AK4104_REG_MASK) | AK4104_WRITE, value };
+
+		if (spi_write(spi, tmp, sizeof(tmp))) {
+			dev_err(&spi->dev, "SPI write failed\n");
+			return -EIO;
+		}
+
+		cache[reg] = value;
+	}
+
+	return 0;
+}
+
+static int ak4104_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			      unsigned int format)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	int val = 0;
+
+	val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1);
+	if (val < 0)
+		return val;
+
+	val &= ~(AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1);
+
+	/* set DAI format */
+	switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		val |= AK4104_CONTROL1_DIF0;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		val |= AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1;
+		break;
+	default:
+		dev_err(codec->dev, "invalid dai format\n");
+		return -EINVAL;
+	}
+
+	/* This device can only be slave */
+	if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+		return -EINVAL;
+
+	return ak4104_spi_write(codec, AK4104_REG_CONTROL1, val);
+}
+
+static int ak4104_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	int val = 0;
+
+	/* set the IEC958 bits: consumer mode, no copyright bit */
+	val |= IEC958_AES0_CON_NOT_COPYRIGHT;
+	ak4104_spi_write(codec, AK4104_REG_CHN_STATUS(0), val);
+
+	val = 0;
+
+	switch (params_rate(params)) {
+	case 44100:
+		val |= IEC958_AES3_CON_FS_44100;
+		break;
+	case 48000:
+		val |= IEC958_AES3_CON_FS_48000;
+		break;
+	case 32000:
+		val |= IEC958_AES3_CON_FS_32000;
+		break;
+	default:
+		dev_err(codec->dev, "unsupported sampling rate\n");
+		return -EINVAL;
+	}
+
+	return ak4104_spi_write(codec, AK4104_REG_CHN_STATUS(3), val);
+}
+
+static struct snd_soc_dai_ops ak4101_dai_ops = {
+	.hw_params = ak4104_hw_params,
+	.set_fmt = ak4104_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver ak4104_dai = {
+	.name = "ak4104-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_192000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE  |
+			   SNDRV_PCM_FMTBIT_S24_3LE |
+			   SNDRV_PCM_FMTBIT_S24_LE
+	},
+	.ops = &ak4101_dai_ops,
+};
+
+static int ak4104_probe(struct snd_soc_codec *codec)
+{
+	struct ak4104_private *ak4104 = snd_soc_codec_get_drvdata(codec);
+	int ret, val;
+
+	codec->control_data = ak4104->control_data;
+
+	/* read all regs and fill the cache */
+	ret = ak4104_fill_cache(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "failed to fill register cache\n");
+		return ret;
+	}
+
+	/* read the 'reserved' register - according to the datasheet, it
+	 * should contain 0x5b. Not a good way to verify the presence of
+	 * the device, but there is no hardware ID register. */
+	if (ak4104_read_reg_cache(codec, AK4104_REG_RESERVED) !=
+					 AK4104_RESERVED_VAL)
+		return -ENODEV;
+
+	/* set power-up and non-reset bits */
+	val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1);
+	val |= AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN;
+	ret = ak4104_spi_write(codec, AK4104_REG_CONTROL1, val);
+	if (ret < 0)
+		return ret;
+
+	/* enable transmitter */
+	val = ak4104_read_reg_cache(codec, AK4104_REG_TX);
+	val |= AK4104_TX_TXE;
+	ret = ak4104_spi_write(codec, AK4104_REG_TX, val);
+	if (ret < 0)
+		return ret;
+
+	dev_info(codec->dev, "SPI device initialized\n");
+	return 0;
+}
+
+static int ak4104_remove(struct snd_soc_codec *codec)
+{
+	int val, ret;
+
+	val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1);
+	if (val < 0)
+		return val;
+
+	/* clear power-up and non-reset bits */
+	val &= ~(AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN);
+	ret = ak4104_spi_write(codec, AK4104_REG_CONTROL1, val);
+
+	return ret;
+}
+
+static struct snd_soc_codec_driver soc_codec_device_ak4104 = {
+	.probe =	ak4104_probe,
+	.remove =	ak4104_remove,
+	.reg_cache_size = AK4104_NUM_REGS,
+	.reg_word_size = sizeof(u16),
+};
+
+static int ak4104_spi_probe(struct spi_device *spi)
+{
+	struct ak4104_private *ak4104;
+	int ret;
+
+	spi->bits_per_word = 8;
+	spi->mode = SPI_MODE_0;
+	ret = spi_setup(spi);
+	if (ret < 0)
+		return ret;
+
+	ak4104 = kzalloc(sizeof(struct ak4104_private), GFP_KERNEL);
+	if (ak4104 == NULL)
+		return -ENOMEM;
+
+	ak4104->control_data = spi;
+	ak4104->control_type = SND_SOC_SPI;
+	spi_set_drvdata(spi, ak4104);
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_device_ak4104, &ak4104_dai, 1);
+	if (ret < 0)
+		kfree(ak4104);
+	return ret;
+}
+
+static int __devexit ak4104_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver ak4104_spi_driver = {
+	.driver  = {
+		.name   = DRV_NAME,
+		.owner  = THIS_MODULE,
+	},
+	.probe  = ak4104_spi_probe,
+	.remove = __devexit_p(ak4104_spi_remove),
+};
+
+static int __init ak4104_init(void)
+{
+	pr_info("Asahi Kasei AK4104 ALSA SoC Codec Driver\n");
+	return spi_register_driver(&ak4104_spi_driver);
+}
+module_init(ak4104_init);
+
+static void __exit ak4104_exit(void)
+{
+	spi_unregister_driver(&ak4104_spi_driver);
+}
+module_exit(ak4104_exit);
+
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_DESCRIPTION("Asahi Kasei AK4104 ALSA SoC driver");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/codecs/ak4535.c b/linux/sound/soc/codecs/ak4535.c
new file mode 100644
index 0000000..cd88c8f
--- /dev/null
+++ b/linux/sound/soc/codecs/ak4535.c
@@ -0,0 +1,555 @@
+/*
+ * ak4535.c  --  AK4535 ALSA Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "ak4535.h"
+
+#define AK4535_VERSION "0.3"
+
+/* codec private data */
+struct ak4535_priv {
+	unsigned int sysclk;
+	enum snd_soc_control_type control_type;
+	void *control_data;
+};
+
+/*
+ * ak4535 register cache
+ */
+static const u16 ak4535_reg[AK4535_CACHEREGNUM] = {
+    0x0000, 0x0080, 0x0000, 0x0003,
+    0x0002, 0x0000, 0x0011, 0x0001,
+    0x0000, 0x0040, 0x0036, 0x0010,
+    0x0000, 0x0000, 0x0057, 0x0000,
+};
+
+/*
+ * read ak4535 register cache
+ */
+static inline unsigned int ak4535_read_reg_cache(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+	if (reg >= AK4535_CACHEREGNUM)
+		return -1;
+	return cache[reg];
+}
+
+/*
+ * write ak4535 register cache
+ */
+static inline void ak4535_write_reg_cache(struct snd_soc_codec *codec,
+	u16 reg, unsigned int value)
+{
+	u16 *cache = codec->reg_cache;
+	if (reg >= AK4535_CACHEREGNUM)
+		return;
+	cache[reg] = value;
+}
+
+/*
+ * write to the AK4535 register space
+ */
+static int ak4535_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int value)
+{
+	u8 data[2];
+
+	/* data is
+	 *   D15..D8 AK4535 register offset
+	 *   D7...D0 register data
+	 */
+	data[0] = reg & 0xff;
+	data[1] = value & 0xff;
+
+	ak4535_write_reg_cache(codec, reg, value);
+	if (codec->hw_write(codec->control_data, data, 2) == 2)
+		return 0;
+	else
+		return -EIO;
+}
+
+static int ak4535_sync(struct snd_soc_codec *codec)
+{
+	u16 *cache = codec->reg_cache;
+	int i, r = 0;
+
+	for (i = 0; i < AK4535_CACHEREGNUM; i++)
+		r |= ak4535_write(codec, i, cache[i]);
+
+	return r;
+};
+
+static const char *ak4535_mono_gain[] = {"+6dB", "-17dB"};
+static const char *ak4535_mono_out[] = {"(L + R)/2", "Hi-Z"};
+static const char *ak4535_hp_out[] = {"Stereo", "Mono"};
+static const char *ak4535_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"};
+static const char *ak4535_mic_select[] = {"Internal", "External"};
+
+static const struct soc_enum ak4535_enum[] = {
+	SOC_ENUM_SINGLE(AK4535_SIG1, 7, 2, ak4535_mono_gain),
+	SOC_ENUM_SINGLE(AK4535_SIG1, 6, 2, ak4535_mono_out),
+	SOC_ENUM_SINGLE(AK4535_MODE2, 2, 2, ak4535_hp_out),
+	SOC_ENUM_SINGLE(AK4535_DAC, 0, 4, ak4535_deemp),
+	SOC_ENUM_SINGLE(AK4535_MIC, 1, 2, ak4535_mic_select),
+};
+
+static const struct snd_kcontrol_new ak4535_snd_controls[] = {
+	SOC_SINGLE("ALC2 Switch", AK4535_SIG1, 1, 1, 0),
+	SOC_ENUM("Mono 1 Output", ak4535_enum[1]),
+	SOC_ENUM("Mono 1 Gain", ak4535_enum[0]),
+	SOC_ENUM("Headphone Output", ak4535_enum[2]),
+	SOC_ENUM("Playback Deemphasis", ak4535_enum[3]),
+	SOC_SINGLE("Bass Volume", AK4535_DAC, 2, 3, 0),
+	SOC_SINGLE("Mic Boost (+20dB) Switch", AK4535_MIC, 0, 1, 0),
+	SOC_ENUM("Mic Select", ak4535_enum[4]),
+	SOC_SINGLE("ALC Operation Time", AK4535_TIMER, 0, 3, 0),
+	SOC_SINGLE("ALC Recovery Time", AK4535_TIMER, 2, 3, 0),
+	SOC_SINGLE("ALC ZC Time", AK4535_TIMER, 4, 3, 0),
+	SOC_SINGLE("ALC 1 Switch", AK4535_ALC1, 5, 1, 0),
+	SOC_SINGLE("ALC 2 Switch", AK4535_ALC1, 6, 1, 0),
+	SOC_SINGLE("ALC Volume", AK4535_ALC2, 0, 127, 0),
+	SOC_SINGLE("Capture Volume", AK4535_PGA, 0, 127, 0),
+	SOC_SINGLE("Left Playback Volume", AK4535_LATT, 0, 127, 1),
+	SOC_SINGLE("Right Playback Volume", AK4535_RATT, 0, 127, 1),
+	SOC_SINGLE("AUX Bypass Volume", AK4535_VOL, 0, 15, 0),
+	SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0),
+};
+
+/* Mono 1 Mixer */
+static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0),
+	SOC_DAPM_SINGLE("Mono Playback Switch", AK4535_SIG1, 5, 1, 0),
+};
+
+/* Stereo Mixer */
+static const struct snd_kcontrol_new ak4535_stereo_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG2, 4, 1, 0),
+	SOC_DAPM_SINGLE("Playback Switch", AK4535_SIG2, 7, 1, 0),
+	SOC_DAPM_SINGLE("Aux Bypass Switch", AK4535_SIG2, 5, 1, 0),
+};
+
+/* Input Mixer */
+static const struct snd_kcontrol_new ak4535_input_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Mic Capture Switch", AK4535_MIC, 2, 1, 0),
+	SOC_DAPM_SINGLE("Aux Capture Switch", AK4535_MIC, 5, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new ak4535_input_mux_control =
+	SOC_DAPM_ENUM("Input Select", ak4535_enum[4]);
+
+/* HP L switch */
+static const struct snd_kcontrol_new ak4535_hpl_control =
+	SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 1, 1, 1);
+
+/* HP R switch */
+static const struct snd_kcontrol_new ak4535_hpr_control =
+	SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 0, 1, 1);
+
+/* mono 2 switch */
+static const struct snd_kcontrol_new ak4535_mono2_control =
+	SOC_DAPM_SINGLE("Switch", AK4535_SIG1, 0, 1, 0);
+
+/* Line out switch */
+static const struct snd_kcontrol_new ak4535_line_control =
+	SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 6, 1, 0);
+
+/* ak4535 dapm widgets */
+static const struct snd_soc_dapm_widget ak4535_dapm_widgets[] = {
+	SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0,
+		&ak4535_stereo_mixer_controls[0],
+		ARRAY_SIZE(ak4535_stereo_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0,
+		&ak4535_mono1_mixer_controls[0],
+		ARRAY_SIZE(ak4535_mono1_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0,
+		&ak4535_input_mixer_controls[0],
+		ARRAY_SIZE(ak4535_input_mixer_controls)),
+	SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0,
+		&ak4535_input_mux_control),
+	SND_SOC_DAPM_DAC("DAC", "Playback", AK4535_PM2, 0, 0),
+	SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0,
+		&ak4535_mono2_control),
+	/* speaker powersave bit */
+	SND_SOC_DAPM_PGA("Speaker Enable", AK4535_MODE2, 0, 0, NULL, 0),
+	SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0,
+		&ak4535_line_control),
+	SND_SOC_DAPM_SWITCH("Left HP Enable", SND_SOC_NOPM, 0, 0,
+		&ak4535_hpl_control),
+	SND_SOC_DAPM_SWITCH("Right HP Enable", SND_SOC_NOPM, 0, 0,
+		&ak4535_hpr_control),
+	SND_SOC_DAPM_OUTPUT("LOUT"),
+	SND_SOC_DAPM_OUTPUT("HPL"),
+	SND_SOC_DAPM_OUTPUT("ROUT"),
+	SND_SOC_DAPM_OUTPUT("HPR"),
+	SND_SOC_DAPM_OUTPUT("SPP"),
+	SND_SOC_DAPM_OUTPUT("SPN"),
+	SND_SOC_DAPM_OUTPUT("MOUT1"),
+	SND_SOC_DAPM_OUTPUT("MOUT2"),
+	SND_SOC_DAPM_OUTPUT("MICOUT"),
+	SND_SOC_DAPM_ADC("ADC", "Capture", AK4535_PM1, 0, 0),
+	SND_SOC_DAPM_PGA("Spk Amp", AK4535_PM2, 3, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("HP R Amp", AK4535_PM2, 1, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("HP L Amp", AK4535_PM2, 2, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Mic", AK4535_PM1, 1, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Line Out", AK4535_PM1, 4, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Mono Out", AK4535_PM1, 3, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("AUX In", AK4535_PM1, 2, 0, NULL, 0),
+
+	SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4535_MIC, 3, 0),
+	SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4535_MIC, 4, 0),
+	SND_SOC_DAPM_INPUT("MICIN"),
+	SND_SOC_DAPM_INPUT("MICEXT"),
+	SND_SOC_DAPM_INPUT("AUX"),
+	SND_SOC_DAPM_INPUT("MIN"),
+	SND_SOC_DAPM_INPUT("AIN"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/*stereo mixer */
+	{"Stereo Mixer", "Playback Switch", "DAC"},
+	{"Stereo Mixer", "Mic Sidetone Switch", "Mic"},
+	{"Stereo Mixer", "Aux Bypass Switch", "AUX In"},
+
+	/* mono1 mixer */
+	{"Mono1 Mixer", "Mic Sidetone Switch", "Mic"},
+	{"Mono1 Mixer", "Mono Playback Switch", "DAC"},
+
+	/* Mic */
+	{"Mic", NULL, "AIN"},
+	{"Input Mux", "Internal", "Mic Int Bias"},
+	{"Input Mux", "External", "Mic Ext Bias"},
+	{"Mic Int Bias", NULL, "MICIN"},
+	{"Mic Ext Bias", NULL, "MICEXT"},
+	{"MICOUT", NULL, "Input Mux"},
+
+	/* line out */
+	{"LOUT", NULL, "Line Out Enable"},
+	{"ROUT", NULL, "Line Out Enable"},
+	{"Line Out Enable", "Switch", "Line Out"},
+	{"Line Out", NULL, "Stereo Mixer"},
+
+	/* mono1 out */
+	{"MOUT1", NULL, "Mono Out"},
+	{"Mono Out", NULL, "Mono1 Mixer"},
+
+	/* left HP */
+	{"HPL", NULL, "Left HP Enable"},
+	{"Left HP Enable", "Switch", "HP L Amp"},
+	{"HP L Amp", NULL, "Stereo Mixer"},
+
+	/* right HP */
+	{"HPR", NULL, "Right HP Enable"},
+	{"Right HP Enable", "Switch", "HP R Amp"},
+	{"HP R Amp", NULL, "Stereo Mixer"},
+
+	/* speaker */
+	{"SPP", NULL, "Speaker Enable"},
+	{"SPN", NULL, "Speaker Enable"},
+	{"Speaker Enable", "Switch", "Spk Amp"},
+	{"Spk Amp", NULL, "MIN"},
+
+	/* mono 2 */
+	{"MOUT2", NULL, "Mono 2 Enable"},
+	{"Mono 2 Enable", "Switch", "Stereo Mixer"},
+
+	/* Aux In */
+	{"Aux In", NULL, "AUX"},
+
+	/* ADC */
+	{"ADC", NULL, "Input Mixer"},
+	{"Input Mixer", "Mic Capture Switch", "Mic"},
+	{"Input Mixer", "Aux Capture Switch", "Aux In"},
+};
+
+static int ak4535_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, ak4535_dapm_widgets,
+				  ARRAY_SIZE(ak4535_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+static int ak4535_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+	int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct ak4535_priv *ak4535 = snd_soc_codec_get_drvdata(codec);
+
+	ak4535->sysclk = freq;
+	return 0;
+}
+
+static int ak4535_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct ak4535_priv *ak4535 = snd_soc_codec_get_drvdata(codec);
+	u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5);
+	int rate = params_rate(params), fs = 256;
+
+	if (rate)
+		fs = ak4535->sysclk / rate;
+
+	/* set fs */
+	switch (fs) {
+	case 1024:
+		mode2 |= (0x2 << 5);
+		break;
+	case 512:
+		mode2 |= (0x1 << 5);
+		break;
+	case 256:
+		break;
+	}
+
+	/* set rate */
+	ak4535_write(codec, AK4535_MODE2, mode2);
+	return 0;
+}
+
+static int ak4535_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u8 mode1 = 0;
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		mode1 = 0x0002;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		mode1 = 0x0001;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* use 32 fs for BCLK to save power */
+	mode1 |= 0x4;
+
+	ak4535_write(codec, AK4535_MODE1, mode1);
+	return 0;
+}
+
+static int ak4535_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC) & 0xffdf;
+	if (!mute)
+		ak4535_write(codec, AK4535_DAC, mute_reg);
+	else
+		ak4535_write(codec, AK4535_DAC, mute_reg | 0x20);
+	return 0;
+}
+
+static int ak4535_set_bias_level(struct snd_soc_codec *codec,
+	enum snd_soc_bias_level level)
+{
+	u16 i, mute_reg;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC) & 0xffdf;
+		ak4535_write(codec, AK4535_DAC, mute_reg);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC) & 0xffdf;
+		ak4535_write(codec, AK4535_DAC, mute_reg | 0x20);
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		i = ak4535_read_reg_cache(codec, AK4535_PM1);
+		ak4535_write(codec, AK4535_PM1, i | 0x80);
+		i = ak4535_read_reg_cache(codec, AK4535_PM2);
+		ak4535_write(codec, AK4535_PM2, i & (~0x80));
+		break;
+	case SND_SOC_BIAS_OFF:
+		i = ak4535_read_reg_cache(codec, AK4535_PM1);
+		ak4535_write(codec, AK4535_PM1, i & (~0x80));
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define AK4535_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+		SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops ak4535_dai_ops = {
+	.hw_params	= ak4535_hw_params,
+	.set_fmt	= ak4535_set_dai_fmt,
+	.digital_mute	= ak4535_mute,
+	.set_sysclk	= ak4535_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver ak4535_dai = {
+	.name = "ak4535-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = AK4535_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = AK4535_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.ops = &ak4535_dai_ops,
+};
+
+static int ak4535_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int ak4535_resume(struct snd_soc_codec *codec)
+{
+	ak4535_sync(codec);
+	ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	return 0;
+}
+
+static int ak4535_probe(struct snd_soc_codec *codec)
+{
+	struct ak4535_priv *ak4535 = snd_soc_codec_get_drvdata(codec);
+
+	printk(KERN_INFO "AK4535 Audio Codec %s", AK4535_VERSION);
+
+	codec->control_data = ak4535->control_data;
+
+	/* power on device */
+	ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	snd_soc_add_controls(codec, ak4535_snd_controls,
+				ARRAY_SIZE(ak4535_snd_controls));
+	ak4535_add_widgets(codec);
+
+	return 0;
+}
+
+/* power down chip */
+static int ak4535_remove(struct snd_soc_codec *codec)
+{
+	ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ak4535 = {
+	.probe =	ak4535_probe,
+	.remove =	ak4535_remove,
+	.suspend =	ak4535_suspend,
+	.resume =	ak4535_resume,
+	.read = ak4535_read_reg_cache,
+	.write = ak4535_write,
+	.set_bias_level = ak4535_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(ak4535_reg),
+	.reg_word_size = sizeof(u8),
+	.reg_cache_default = ak4535_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int ak4535_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct ak4535_priv *ak4535;
+	int ret;
+
+	ak4535 = kzalloc(sizeof(struct ak4535_priv), GFP_KERNEL);
+	if (ak4535 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, ak4535);
+	ak4535->control_data = i2c;
+	ak4535->control_type = SND_SOC_I2C;
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_ak4535, &ak4535_dai, 1);
+	if (ret < 0)
+		kfree(ak4535);
+	return ret;
+}
+
+static __devexit int ak4535_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id ak4535_i2c_id[] = {
+	{ "ak4535", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ak4535_i2c_id);
+
+static struct i2c_driver ak4535_i2c_driver = {
+	.driver = {
+		.name = "ak4535-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    ak4535_i2c_probe,
+	.remove =   __devexit_p(ak4535_i2c_remove),
+	.id_table = ak4535_i2c_id,
+};
+#endif
+
+static int __init ak4535_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&ak4535_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register AK4535 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(ak4535_modinit);
+
+static void __exit ak4535_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&ak4535_i2c_driver);
+#endif
+}
+module_exit(ak4535_exit);
+
+MODULE_DESCRIPTION("Soc AK4535 driver");
+MODULE_AUTHOR("Richard Purdie");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/ak4535.h b/linux/sound/soc/codecs/ak4535.h
new file mode 100644
index 0000000..0431e5f
--- /dev/null
+++ b/linux/sound/soc/codecs/ak4535.h
@@ -0,0 +1,39 @@
+/*
+ * ak4535.h  --  AK4535 Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AK4535_H
+#define _AK4535_H
+
+/* AK4535 register space */
+
+#define AK4535_PM1		0x0
+#define AK4535_PM2		0x1
+#define AK4535_SIG1		0x2
+#define AK4535_SIG2		0x3
+#define AK4535_MODE1		0x4
+#define AK4535_MODE2		0x5
+#define AK4535_DAC		0x6
+#define AK4535_MIC		0x7
+#define AK4535_TIMER		0x8
+#define AK4535_ALC1		0x9
+#define AK4535_ALC2		0xa
+#define AK4535_PGA		0xb
+#define AK4535_LATT		0xc
+#define AK4535_RATT		0xd
+#define AK4535_VOL		0xe
+#define AK4535_STATUS		0xf
+
+#define AK4535_CACHEREGNUM 	0x10
+
+#endif
diff --git a/linux/sound/soc/codecs/ak4642.c b/linux/sound/soc/codecs/ak4642.c
new file mode 100644
index 0000000..90c90b7
--- /dev/null
+++ b/linux/sound/soc/codecs/ak4642.c
@@ -0,0 +1,540 @@
+/*
+ * ak4642.c  --  AK4642/AK4643 ALSA Soc Audio driver
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ * Based on wm8731.c by Richard Purdie
+ * Based on ak4535.c by Richard Purdie
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* ** CAUTION **
+ *
+ * This is very simple driver.
+ * It can use headphone output / stereo input only
+ *
+ * AK4642 is not tested.
+ * AK4643 is tested.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#define AK4642_VERSION "0.0.1"
+
+#define PW_MGMT1	0x00
+#define PW_MGMT2	0x01
+#define SG_SL1		0x02
+#define SG_SL2		0x03
+#define MD_CTL1		0x04
+#define MD_CTL2		0x05
+#define TIMER		0x06
+#define ALC_CTL1	0x07
+#define ALC_CTL2	0x08
+#define L_IVC		0x09
+#define L_DVC		0x0a
+#define ALC_CTL3	0x0b
+#define R_IVC		0x0c
+#define R_DVC		0x0d
+#define MD_CTL3		0x0e
+#define MD_CTL4		0x0f
+#define PW_MGMT3	0x10
+#define DF_S		0x11
+#define FIL3_0		0x12
+#define FIL3_1		0x13
+#define FIL3_2		0x14
+#define FIL3_3		0x15
+#define EQ_0		0x16
+#define EQ_1		0x17
+#define EQ_2		0x18
+#define EQ_3		0x19
+#define EQ_4		0x1a
+#define EQ_5		0x1b
+#define FIL1_0		0x1c
+#define FIL1_1		0x1d
+#define FIL1_2		0x1e
+#define FIL1_3		0x1f
+#define PW_MGMT4	0x20
+#define MD_CTL5		0x21
+#define LO_MS		0x22
+#define HP_MS		0x23
+#define SPK_MS		0x24
+
+#define AK4642_CACHEREGNUM 	0x25
+
+/* PW_MGMT1*/
+#define PMVCM		(1 << 6) /* VCOM Power Management */
+#define PMMIN		(1 << 5) /* MIN Input Power Management */
+#define PMDAC		(1 << 2) /* DAC Power Management */
+#define PMADL		(1 << 0) /* MIC Amp Lch and ADC Lch Power Management */
+
+/* PW_MGMT2 */
+#define HPMTN		(1 << 6)
+#define PMHPL		(1 << 5)
+#define PMHPR		(1 << 4)
+#define MS		(1 << 3) /* master/slave select */
+#define MCKO		(1 << 1)
+#define PMPLL		(1 << 0)
+
+#define PMHP_MASK	(PMHPL | PMHPR)
+#define PMHP		PMHP_MASK
+
+/* PW_MGMT3 */
+#define PMADR		(1 << 0) /* MIC L / ADC R Power Management */
+
+/* SG_SL1 */
+#define MINS		(1 << 6) /* Switch from MIN to Speaker */
+#define DACL		(1 << 4) /* Switch from DAC to Stereo or Receiver */
+#define PMMP		(1 << 2) /* MPWR pin Power Management */
+#define MGAIN0		(1 << 0) /* MIC amp gain*/
+
+/* TIMER */
+#define ZTM(param)	((param & 0x3) << 4) /* ALC Zoro Crossing TimeOut */
+#define WTM(param)	(((param & 0x4) << 4) | ((param & 0x3) << 2))
+
+/* ALC_CTL1 */
+#define ALC		(1 << 5) /* ALC Enable */
+#define LMTH0		(1 << 0) /* ALC Limiter / Recovery Level */
+
+/* MD_CTL1 */
+#define PLL3		(1 << 7)
+#define PLL2		(1 << 6)
+#define PLL1		(1 << 5)
+#define PLL0		(1 << 4)
+#define PLL_MASK	(PLL3 | PLL2 | PLL1 | PLL0)
+
+#define BCKO_MASK	(1 << 3)
+#define BCKO_64		BCKO_MASK
+
+/* MD_CTL2 */
+#define FS0		(1 << 0)
+#define FS1		(1 << 1)
+#define FS2		(1 << 2)
+#define FS3		(1 << 5)
+#define FS_MASK		(FS0 | FS1 | FS2 | FS3)
+
+/* MD_CTL3 */
+#define BST1		(1 << 3)
+
+/* MD_CTL4 */
+#define DACH		(1 << 0)
+
+/*
+ * Playback Volume (table 39)
+ *
+ * max : 0x00 : +12.0 dB
+ *       ( 0.5 dB step )
+ * min : 0xFE : -115.0 dB
+ * mute: 0xFF
+ */
+static const DECLARE_TLV_DB_SCALE(out_tlv, -11500, 50, 1);
+
+static const struct snd_kcontrol_new ak4642_snd_controls[] = {
+
+	SOC_DOUBLE_R_TLV("Digital Playback Volume", L_DVC, R_DVC,
+			 0, 0xFF, 1, out_tlv),
+};
+
+
+/* codec private data */
+struct ak4642_priv {
+	unsigned int sysclk;
+	enum snd_soc_control_type control_type;
+	void *control_data;
+};
+
+/*
+ * ak4642 register cache
+ */
+static const u16 ak4642_reg[AK4642_CACHEREGNUM] = {
+	0x0000, 0x0000, 0x0001, 0x0000,
+	0x0002, 0x0000, 0x0000, 0x0000,
+	0x00e1, 0x00e1, 0x0018, 0x0000,
+	0x00e1, 0x0018, 0x0011, 0x0008,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000,
+};
+
+/*
+ * read ak4642 register cache
+ */
+static inline unsigned int ak4642_read_reg_cache(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+	if (reg >= AK4642_CACHEREGNUM)
+		return -1;
+	return cache[reg];
+}
+
+/*
+ * write ak4642 register cache
+ */
+static inline void ak4642_write_reg_cache(struct snd_soc_codec *codec,
+	u16 reg, unsigned int value)
+{
+	u16 *cache = codec->reg_cache;
+	if (reg >= AK4642_CACHEREGNUM)
+		return;
+
+	cache[reg] = value;
+}
+
+/*
+ * write to the AK4642 register space
+ */
+static int ak4642_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int value)
+{
+	u8 data[2];
+
+	/* data is
+	 *   D15..D8 AK4642 register offset
+	 *   D7...D0 register data
+	 */
+	data[0] = reg & 0xff;
+	data[1] = value & 0xff;
+
+	if (codec->hw_write(codec->control_data, data, 2) == 2) {
+		ak4642_write_reg_cache(codec, reg, value);
+		return 0;
+	} else
+		return -EIO;
+}
+
+static int ak4642_sync(struct snd_soc_codec *codec)
+{
+	u16 *cache = codec->reg_cache;
+	int i, r = 0;
+
+	for (i = 0; i < AK4642_CACHEREGNUM; i++)
+		r |= ak4642_write(codec, i, cache[i]);
+
+	return r;
+};
+
+static int ak4642_dai_startup(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	struct snd_soc_codec *codec = dai->codec;
+
+	if (is_play) {
+		/*
+		 * start headphone output
+		 *
+		 * PLL, Master Mode
+		 * Audio I/F Format :MSB justified (ADC & DAC)
+		 * Bass Boost Level : Middle
+		 *
+		 * This operation came from example code of
+		 * "ASAHI KASEI AK4642" (japanese) manual p97.
+		 */
+		snd_soc_update_bits(codec, MD_CTL4, DACH, DACH);
+		snd_soc_update_bits(codec, MD_CTL3, BST1, BST1);
+		ak4642_write(codec, L_IVC, 0x91); /* volume */
+		ak4642_write(codec, R_IVC, 0x91); /* volume */
+		snd_soc_update_bits(codec, PW_MGMT1, PMVCM | PMMIN | PMDAC,
+						     PMVCM | PMMIN | PMDAC);
+		snd_soc_update_bits(codec, PW_MGMT2, PMHP_MASK,	PMHP);
+		snd_soc_update_bits(codec, PW_MGMT2, HPMTN,	HPMTN);
+	} else {
+		/*
+		 * start stereo input
+		 *
+		 * PLL Master Mode
+		 * Audio I/F Format:MSB justified (ADC & DAC)
+		 * Pre MIC AMP:+20dB
+		 * MIC Power On
+		 * ALC setting:Refer to Table 35
+		 * ALC bit=“1”
+		 *
+		 * This operation came from example code of
+		 * "ASAHI KASEI AK4642" (japanese) manual p94.
+		 */
+		ak4642_write(codec, SG_SL1, PMMP | MGAIN0);
+		ak4642_write(codec, TIMER, ZTM(0x3) | WTM(0x3));
+		ak4642_write(codec, ALC_CTL1, ALC | LMTH0);
+		snd_soc_update_bits(codec, PW_MGMT1, PMVCM | PMADL,
+						     PMVCM | PMADL);
+		snd_soc_update_bits(codec, PW_MGMT3, PMADR, PMADR);
+	}
+
+	return 0;
+}
+
+static void ak4642_dai_shutdown(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	struct snd_soc_codec *codec = dai->codec;
+
+	if (is_play) {
+		/* stop headphone output */
+		snd_soc_update_bits(codec, PW_MGMT2, HPMTN,	0);
+		snd_soc_update_bits(codec, PW_MGMT2, PMHP_MASK,	0);
+		snd_soc_update_bits(codec, PW_MGMT1, PMMIN | PMDAC, 0);
+		snd_soc_update_bits(codec, MD_CTL3, BST1, 0);
+		snd_soc_update_bits(codec, MD_CTL4, DACH, 0);
+	} else {
+		/* stop stereo input */
+		snd_soc_update_bits(codec, PW_MGMT1, PMADL, 0);
+		snd_soc_update_bits(codec, PW_MGMT3, PMADR, 0);
+		snd_soc_update_bits(codec, ALC_CTL1, ALC, 0);
+	}
+}
+
+static int ak4642_dai_set_sysclk(struct snd_soc_dai *codec_dai,
+	int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u8 pll;
+
+	switch (freq) {
+	case 11289600:
+		pll = PLL2;
+		break;
+	case 12288000:
+		pll = PLL2 | PLL0;
+		break;
+	case 12000000:
+		pll = PLL2 | PLL1;
+		break;
+	case 24000000:
+		pll = PLL2 | PLL1 | PLL0;
+		break;
+	case 13500000:
+		pll = PLL3 | PLL2;
+		break;
+	case 27000000:
+		pll = PLL3 | PLL2 | PLL0;
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_soc_update_bits(codec, MD_CTL1, PLL_MASK, pll);
+
+	return 0;
+}
+
+static int ak4642_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u8 data;
+	u8 bcko;
+
+	data = MCKO | PMPLL; /* use MCKO */
+	bcko = 0;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		data |= MS;
+		bcko = BCKO_64;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_soc_update_bits(codec, PW_MGMT2, MS, data);
+	snd_soc_update_bits(codec, MD_CTL1, BCKO_MASK, bcko);
+
+	return 0;
+}
+
+static int ak4642_dai_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u8 rate;
+
+	switch (params_rate(params)) {
+	case 7350:
+		rate = FS2;
+		break;
+	case 8000:
+		rate = 0;
+		break;
+	case 11025:
+		rate = FS2 | FS0;
+		break;
+	case 12000:
+		rate = FS0;
+		break;
+	case 14700:
+		rate = FS2 | FS1;
+		break;
+	case 16000:
+		rate = FS1;
+		break;
+	case 22050:
+		rate = FS2 | FS1 | FS0;
+		break;
+	case 24000:
+		rate = FS1 | FS0;
+		break;
+	case 29400:
+		rate = FS3 | FS2 | FS1;
+		break;
+	case 32000:
+		rate = FS3 | FS1;
+		break;
+	case 44100:
+		rate = FS3 | FS2 | FS1 | FS0;
+		break;
+	case 48000:
+		rate = FS3 | FS1 | FS0;
+		break;
+	default:
+		return -EINVAL;
+		break;
+	}
+	snd_soc_update_bits(codec, MD_CTL2, FS_MASK, rate);
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops ak4642_dai_ops = {
+	.startup	= ak4642_dai_startup,
+	.shutdown	= ak4642_dai_shutdown,
+	.set_sysclk	= ak4642_dai_set_sysclk,
+	.set_fmt	= ak4642_dai_set_fmt,
+	.hw_params	= ak4642_dai_hw_params,
+};
+
+static struct snd_soc_dai_driver ak4642_dai = {
+	.name = "ak4642-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE },
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE },
+	.ops = &ak4642_dai_ops,
+	.symmetric_rates = 1,
+};
+
+static int ak4642_resume(struct snd_soc_codec *codec)
+{
+	ak4642_sync(codec);
+	return 0;
+}
+
+
+static int ak4642_probe(struct snd_soc_codec *codec)
+{
+	struct ak4642_priv *ak4642 = snd_soc_codec_get_drvdata(codec);
+
+	dev_info(codec->dev, "AK4642 Audio Codec %s", AK4642_VERSION);
+
+	codec->hw_write		= (hw_write_t)i2c_master_send;
+	codec->control_data	= ak4642->control_data;
+
+	snd_soc_add_controls(codec, ak4642_snd_controls,
+			     ARRAY_SIZE(ak4642_snd_controls));
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ak4642 = {
+	.probe			= ak4642_probe,
+	.resume			= ak4642_resume,
+	.read			= ak4642_read_reg_cache,
+	.write			= ak4642_write,
+	.reg_cache_size		= ARRAY_SIZE(ak4642_reg),
+	.reg_word_size		= sizeof(u8),
+	.reg_cache_default	= ak4642_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int ak4642_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct ak4642_priv *ak4642;
+	int ret;
+
+	ak4642 = kzalloc(sizeof(struct ak4642_priv), GFP_KERNEL);
+	if (!ak4642)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, ak4642);
+	ak4642->control_data = i2c;
+	ak4642->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_ak4642, &ak4642_dai, 1);
+	if (ret < 0)
+		kfree(ak4642);
+	return ret;
+}
+
+static __devexit int ak4642_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id ak4642_i2c_id[] = {
+	{ "ak4642", 0 },
+	{ "ak4643", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ak4642_i2c_id);
+
+static struct i2c_driver ak4642_i2c_driver = {
+	.driver = {
+		.name = "ak4642-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe		= ak4642_i2c_probe,
+	.remove		= __devexit_p(ak4642_i2c_remove),
+	.id_table	= ak4642_i2c_id,
+};
+#endif
+
+static int __init ak4642_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&ak4642_i2c_driver);
+#endif
+	return ret;
+
+}
+module_init(ak4642_modinit);
+
+static void __exit ak4642_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&ak4642_i2c_driver);
+#endif
+
+}
+module_exit(ak4642_exit);
+
+MODULE_DESCRIPTION("Soc AK4642 driver");
+MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/ak4671.c b/linux/sound/soc/codecs/ak4671.c
new file mode 100644
index 0000000..24f5f49
--- /dev/null
+++ b/linux/sound/soc/codecs/ak4671.c
@@ -0,0 +1,734 @@
+/*
+ * ak4671.c  --  audio driver for AK4671
+ *
+ * Copyright (C) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim@samsung.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "ak4671.h"
+
+
+/* codec private data */
+struct ak4671_priv {
+	enum snd_soc_control_type control_type;
+	void *control_data;
+	u8 reg_cache[AK4671_CACHEREGNUM];
+};
+
+/* ak4671 register cache & default register settings */
+static const u8 ak4671_reg[AK4671_CACHEREGNUM] = {
+	0x00,	/* AK4671_AD_DA_POWER_MANAGEMENT	(0x00)	*/
+	0xf6,	/* AK4671_PLL_MODE_SELECT0		(0x01)	*/
+	0x00,	/* AK4671_PLL_MODE_SELECT1		(0x02)	*/
+	0x02,	/* AK4671_FORMAT_SELECT			(0x03)	*/
+	0x00,	/* AK4671_MIC_SIGNAL_SELECT		(0x04)	*/
+	0x55,	/* AK4671_MIC_AMP_GAIN			(0x05)	*/
+	0x00,	/* AK4671_MIXING_POWER_MANAGEMENT0	(0x06)	*/
+	0x00,	/* AK4671_MIXING_POWER_MANAGEMENT1	(0x07)	*/
+	0xb5,	/* AK4671_OUTPUT_VOLUME_CONTROL		(0x08)	*/
+	0x00,	/* AK4671_LOUT1_SIGNAL_SELECT		(0x09)	*/
+	0x00,	/* AK4671_ROUT1_SIGNAL_SELECT		(0x0a)	*/
+	0x00,	/* AK4671_LOUT2_SIGNAL_SELECT		(0x0b)	*/
+	0x00,	/* AK4671_ROUT2_SIGNAL_SELECT		(0x0c)	*/
+	0x00,	/* AK4671_LOUT3_SIGNAL_SELECT		(0x0d)	*/
+	0x00,	/* AK4671_ROUT3_SIGNAL_SELECT		(0x0e)	*/
+	0x00,	/* AK4671_LOUT1_POWER_MANAGERMENT	(0x0f)	*/
+	0x00,	/* AK4671_LOUT2_POWER_MANAGERMENT	(0x10)	*/
+	0x80,	/* AK4671_LOUT3_POWER_MANAGERMENT	(0x11)	*/
+	0x91,	/* AK4671_LCH_INPUT_VOLUME_CONTROL	(0x12)	*/
+	0x91,	/* AK4671_RCH_INPUT_VOLUME_CONTROL	(0x13)	*/
+	0xe1,	/* AK4671_ALC_REFERENCE_SELECT		(0x14)	*/
+	0x00,	/* AK4671_DIGITAL_MIXING_CONTROL	(0x15)	*/
+	0x00,	/* AK4671_ALC_TIMER_SELECT		(0x16)	*/
+	0x00,	/* AK4671_ALC_MODE_CONTROL		(0x17)	*/
+	0x02,	/* AK4671_MODE_CONTROL1			(0x18)	*/
+	0x01,	/* AK4671_MODE_CONTROL2			(0x19)	*/
+	0x18,	/* AK4671_LCH_OUTPUT_VOLUME_CONTROL	(0x1a)	*/
+	0x18,	/* AK4671_RCH_OUTPUT_VOLUME_CONTROL	(0x1b)	*/
+	0x00,	/* AK4671_SIDETONE_A_CONTROL		(0x1c)	*/
+	0x02,	/* AK4671_DIGITAL_FILTER_SELECT		(0x1d)	*/
+	0x00,	/* AK4671_FIL3_COEFFICIENT0		(0x1e)	*/
+	0x00,	/* AK4671_FIL3_COEFFICIENT1		(0x1f)	*/
+	0x00,	/* AK4671_FIL3_COEFFICIENT2		(0x20)	*/
+	0x00,	/* AK4671_FIL3_COEFFICIENT3		(0x21)	*/
+	0x00,	/* AK4671_EQ_COEFFICIENT0		(0x22)	*/
+	0x00,	/* AK4671_EQ_COEFFICIENT1		(0x23)	*/
+	0x00,	/* AK4671_EQ_COEFFICIENT2		(0x24)	*/
+	0x00,	/* AK4671_EQ_COEFFICIENT3		(0x25)	*/
+	0x00,	/* AK4671_EQ_COEFFICIENT4		(0x26)	*/
+	0x00,	/* AK4671_EQ_COEFFICIENT5		(0x27)	*/
+	0xa9,	/* AK4671_FIL1_COEFFICIENT0		(0x28)	*/
+	0x1f,	/* AK4671_FIL1_COEFFICIENT1		(0x29)	*/
+	0xad,	/* AK4671_FIL1_COEFFICIENT2		(0x2a)	*/
+	0x20,	/* AK4671_FIL1_COEFFICIENT3		(0x2b)	*/
+	0x00,	/* AK4671_FIL2_COEFFICIENT0		(0x2c)	*/
+	0x00,	/* AK4671_FIL2_COEFFICIENT1		(0x2d)	*/
+	0x00,	/* AK4671_FIL2_COEFFICIENT2		(0x2e)	*/
+	0x00,	/* AK4671_FIL2_COEFFICIENT3		(0x2f)	*/
+	0x00,	/* AK4671_DIGITAL_FILTER_SELECT2	(0x30)	*/
+	0x00,	/* this register not used			*/
+	0x00,	/* AK4671_E1_COEFFICIENT0		(0x32)	*/
+	0x00,	/* AK4671_E1_COEFFICIENT1		(0x33)	*/
+	0x00,	/* AK4671_E1_COEFFICIENT2		(0x34)	*/
+	0x00,	/* AK4671_E1_COEFFICIENT3		(0x35)	*/
+	0x00,	/* AK4671_E1_COEFFICIENT4		(0x36)	*/
+	0x00,	/* AK4671_E1_COEFFICIENT5		(0x37)	*/
+	0x00,	/* AK4671_E2_COEFFICIENT0		(0x38)	*/
+	0x00,	/* AK4671_E2_COEFFICIENT1		(0x39)	*/
+	0x00,	/* AK4671_E2_COEFFICIENT2		(0x3a)	*/
+	0x00,	/* AK4671_E2_COEFFICIENT3		(0x3b)	*/
+	0x00,	/* AK4671_E2_COEFFICIENT4		(0x3c)	*/
+	0x00,	/* AK4671_E2_COEFFICIENT5		(0x3d)	*/
+	0x00,	/* AK4671_E3_COEFFICIENT0		(0x3e)	*/
+	0x00,	/* AK4671_E3_COEFFICIENT1		(0x3f)	*/
+	0x00,	/* AK4671_E3_COEFFICIENT2		(0x40)	*/
+	0x00,	/* AK4671_E3_COEFFICIENT3		(0x41)	*/
+	0x00,	/* AK4671_E3_COEFFICIENT4		(0x42)	*/
+	0x00,	/* AK4671_E3_COEFFICIENT5		(0x43)	*/
+	0x00,	/* AK4671_E4_COEFFICIENT0		(0x44)	*/
+	0x00,	/* AK4671_E4_COEFFICIENT1		(0x45)	*/
+	0x00,	/* AK4671_E4_COEFFICIENT2		(0x46)	*/
+	0x00,	/* AK4671_E4_COEFFICIENT3		(0x47)	*/
+	0x00,	/* AK4671_E4_COEFFICIENT4		(0x48)	*/
+	0x00,	/* AK4671_E4_COEFFICIENT5		(0x49)	*/
+	0x00,	/* AK4671_E5_COEFFICIENT0		(0x4a)	*/
+	0x00,	/* AK4671_E5_COEFFICIENT1		(0x4b)	*/
+	0x00,	/* AK4671_E5_COEFFICIENT2		(0x4c)	*/
+	0x00,	/* AK4671_E5_COEFFICIENT3		(0x4d)	*/
+	0x00,	/* AK4671_E5_COEFFICIENT4		(0x4e)	*/
+	0x00,	/* AK4671_E5_COEFFICIENT5		(0x4f)	*/
+	0x88,	/* AK4671_EQ_CONTROL_250HZ_100HZ	(0x50)	*/
+	0x88,	/* AK4671_EQ_CONTROL_3500HZ_1KHZ	(0x51)	*/
+	0x08,	/* AK4671_EQ_CONTRO_10KHZ		(0x52)	*/
+	0x00,	/* AK4671_PCM_IF_CONTROL0		(0x53)	*/
+	0x00,	/* AK4671_PCM_IF_CONTROL1		(0x54)	*/
+	0x00,	/* AK4671_PCM_IF_CONTROL2		(0x55)	*/
+	0x18,	/* AK4671_DIGITAL_VOLUME_B_CONTROL	(0x56)	*/
+	0x18,	/* AK4671_DIGITAL_VOLUME_C_CONTROL	(0x57)	*/
+	0x00,	/* AK4671_SIDETONE_VOLUME_CONTROL	(0x58)	*/
+	0x00,	/* AK4671_DIGITAL_MIXING_CONTROL2	(0x59)	*/
+	0x00,	/* AK4671_SAR_ADC_CONTROL		(0x5a)	*/
+};
+
+/*
+ * LOUT1/ROUT1 output volume control:
+ * from -24 to 6 dB in 6 dB steps (mute instead of -30 dB)
+ */
+static DECLARE_TLV_DB_SCALE(out1_tlv, -3000, 600, 1);
+
+/*
+ * LOUT2/ROUT2 output volume control:
+ * from -33 to 6 dB in 3 dB steps (mute instead of -33 dB)
+ */
+static DECLARE_TLV_DB_SCALE(out2_tlv, -3300, 300, 1);
+
+/*
+ * LOUT3/ROUT3 output volume control:
+ * from -6 to 3 dB in 3 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(out3_tlv, -600, 300, 0);
+
+/*
+ * Mic amp gain control:
+ * from -15 to 30 dB in 3 dB steps
+ * REVISIT: The actual min value(0x01) is -12 dB and the reg value 0x00 is not
+ * available
+ */
+static DECLARE_TLV_DB_SCALE(mic_amp_tlv, -1500, 300, 0);
+
+static const struct snd_kcontrol_new ak4671_snd_controls[] = {
+	/* Common playback gain controls */
+	SOC_SINGLE_TLV("Line Output1 Playback Volume",
+			AK4671_OUTPUT_VOLUME_CONTROL, 0, 0x6, 0, out1_tlv),
+	SOC_SINGLE_TLV("Headphone Output2 Playback Volume",
+			AK4671_OUTPUT_VOLUME_CONTROL, 4, 0xd, 0, out2_tlv),
+	SOC_SINGLE_TLV("Line Output3 Playback Volume",
+			AK4671_LOUT3_POWER_MANAGERMENT, 6, 0x3, 0, out3_tlv),
+
+	/* Common capture gain controls */
+	SOC_DOUBLE_TLV("Mic Amp Capture Volume",
+			AK4671_MIC_AMP_GAIN, 0, 4, 0xf, 0, mic_amp_tlv),
+};
+
+/* event handlers */
+static int ak4671_out2_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	u8 reg;
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		reg = snd_soc_read(codec, AK4671_LOUT2_POWER_MANAGERMENT);
+		reg |= AK4671_MUTEN;
+		snd_soc_write(codec, AK4671_LOUT2_POWER_MANAGERMENT, reg);
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		reg = snd_soc_read(codec, AK4671_LOUT2_POWER_MANAGERMENT);
+		reg &= ~AK4671_MUTEN;
+		snd_soc_write(codec, AK4671_LOUT2_POWER_MANAGERMENT, reg);
+		break;
+	}
+
+	return 0;
+}
+
+/* Output Mixers */
+static const struct snd_kcontrol_new ak4671_lout1_mixer_controls[] = {
+	SOC_DAPM_SINGLE("DACL", AK4671_LOUT1_SIGNAL_SELECT, 0, 1, 0),
+	SOC_DAPM_SINGLE("LINL1", AK4671_LOUT1_SIGNAL_SELECT, 1, 1, 0),
+	SOC_DAPM_SINGLE("LINL2", AK4671_LOUT1_SIGNAL_SELECT, 2, 1, 0),
+	SOC_DAPM_SINGLE("LINL3", AK4671_LOUT1_SIGNAL_SELECT, 3, 1, 0),
+	SOC_DAPM_SINGLE("LINL4", AK4671_LOUT1_SIGNAL_SELECT, 4, 1, 0),
+	SOC_DAPM_SINGLE("LOOPL", AK4671_LOUT1_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_rout1_mixer_controls[] = {
+	SOC_DAPM_SINGLE("DACR", AK4671_ROUT1_SIGNAL_SELECT, 0, 1, 0),
+	SOC_DAPM_SINGLE("RINR1", AK4671_ROUT1_SIGNAL_SELECT, 1, 1, 0),
+	SOC_DAPM_SINGLE("RINR2", AK4671_ROUT1_SIGNAL_SELECT, 2, 1, 0),
+	SOC_DAPM_SINGLE("RINR3", AK4671_ROUT1_SIGNAL_SELECT, 3, 1, 0),
+	SOC_DAPM_SINGLE("RINR4", AK4671_ROUT1_SIGNAL_SELECT, 4, 1, 0),
+	SOC_DAPM_SINGLE("LOOPR", AK4671_ROUT1_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_lout2_mixer_controls[] = {
+	SOC_DAPM_SINGLE("DACHL", AK4671_LOUT2_SIGNAL_SELECT, 0, 1, 0),
+	SOC_DAPM_SINGLE("LINH1", AK4671_LOUT2_SIGNAL_SELECT, 1, 1, 0),
+	SOC_DAPM_SINGLE("LINH2", AK4671_LOUT2_SIGNAL_SELECT, 2, 1, 0),
+	SOC_DAPM_SINGLE("LINH3", AK4671_LOUT2_SIGNAL_SELECT, 3, 1, 0),
+	SOC_DAPM_SINGLE("LINH4", AK4671_LOUT2_SIGNAL_SELECT, 4, 1, 0),
+	SOC_DAPM_SINGLE("LOOPHL", AK4671_LOUT2_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_rout2_mixer_controls[] = {
+	SOC_DAPM_SINGLE("DACHR", AK4671_ROUT2_SIGNAL_SELECT, 0, 1, 0),
+	SOC_DAPM_SINGLE("RINH1", AK4671_ROUT2_SIGNAL_SELECT, 1, 1, 0),
+	SOC_DAPM_SINGLE("RINH2", AK4671_ROUT2_SIGNAL_SELECT, 2, 1, 0),
+	SOC_DAPM_SINGLE("RINH3", AK4671_ROUT2_SIGNAL_SELECT, 3, 1, 0),
+	SOC_DAPM_SINGLE("RINH4", AK4671_ROUT2_SIGNAL_SELECT, 4, 1, 0),
+	SOC_DAPM_SINGLE("LOOPHR", AK4671_ROUT2_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_lout3_mixer_controls[] = {
+	SOC_DAPM_SINGLE("DACSL", AK4671_LOUT3_SIGNAL_SELECT, 0, 1, 0),
+	SOC_DAPM_SINGLE("LINS1", AK4671_LOUT3_SIGNAL_SELECT, 1, 1, 0),
+	SOC_DAPM_SINGLE("LINS2", AK4671_LOUT3_SIGNAL_SELECT, 2, 1, 0),
+	SOC_DAPM_SINGLE("LINS3", AK4671_LOUT3_SIGNAL_SELECT, 3, 1, 0),
+	SOC_DAPM_SINGLE("LINS4", AK4671_LOUT3_SIGNAL_SELECT, 4, 1, 0),
+	SOC_DAPM_SINGLE("LOOPSL", AK4671_LOUT3_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_rout3_mixer_controls[] = {
+	SOC_DAPM_SINGLE("DACSR", AK4671_ROUT3_SIGNAL_SELECT, 0, 1, 0),
+	SOC_DAPM_SINGLE("RINS1", AK4671_ROUT3_SIGNAL_SELECT, 1, 1, 0),
+	SOC_DAPM_SINGLE("RINS2", AK4671_ROUT3_SIGNAL_SELECT, 2, 1, 0),
+	SOC_DAPM_SINGLE("RINS3", AK4671_ROUT3_SIGNAL_SELECT, 3, 1, 0),
+	SOC_DAPM_SINGLE("RINS4", AK4671_ROUT3_SIGNAL_SELECT, 4, 1, 0),
+	SOC_DAPM_SINGLE("LOOPSR", AK4671_ROUT3_SIGNAL_SELECT, 5, 1, 0),
+};
+
+/* Input MUXs */
+static const char *ak4671_lin_mux_texts[] =
+		{"LIN1", "LIN2", "LIN3", "LIN4"};
+static const struct soc_enum ak4671_lin_mux_enum =
+	SOC_ENUM_SINGLE(AK4671_MIC_SIGNAL_SELECT, 0,
+			ARRAY_SIZE(ak4671_lin_mux_texts),
+			ak4671_lin_mux_texts);
+static const struct snd_kcontrol_new ak4671_lin_mux_control =
+	SOC_DAPM_ENUM("Route", ak4671_lin_mux_enum);
+
+static const char *ak4671_rin_mux_texts[] =
+		{"RIN1", "RIN2", "RIN3", "RIN4"};
+static const struct soc_enum ak4671_rin_mux_enum =
+	SOC_ENUM_SINGLE(AK4671_MIC_SIGNAL_SELECT, 2,
+			ARRAY_SIZE(ak4671_rin_mux_texts),
+			ak4671_rin_mux_texts);
+static const struct snd_kcontrol_new ak4671_rin_mux_control =
+	SOC_DAPM_ENUM("Route", ak4671_rin_mux_enum);
+
+static const struct snd_soc_dapm_widget ak4671_dapm_widgets[] = {
+	/* Inputs */
+	SND_SOC_DAPM_INPUT("LIN1"),
+	SND_SOC_DAPM_INPUT("RIN1"),
+	SND_SOC_DAPM_INPUT("LIN2"),
+	SND_SOC_DAPM_INPUT("RIN2"),
+	SND_SOC_DAPM_INPUT("LIN3"),
+	SND_SOC_DAPM_INPUT("RIN3"),
+	SND_SOC_DAPM_INPUT("LIN4"),
+	SND_SOC_DAPM_INPUT("RIN4"),
+
+	/* Outputs */
+	SND_SOC_DAPM_OUTPUT("LOUT1"),
+	SND_SOC_DAPM_OUTPUT("ROUT1"),
+	SND_SOC_DAPM_OUTPUT("LOUT2"),
+	SND_SOC_DAPM_OUTPUT("ROUT2"),
+	SND_SOC_DAPM_OUTPUT("LOUT3"),
+	SND_SOC_DAPM_OUTPUT("ROUT3"),
+
+	/* DAC */
+	SND_SOC_DAPM_DAC("DAC Left", "Left HiFi Playback",
+			AK4671_AD_DA_POWER_MANAGEMENT, 6, 0),
+	SND_SOC_DAPM_DAC("DAC Right", "Right HiFi Playback",
+			AK4671_AD_DA_POWER_MANAGEMENT, 7, 0),
+
+	/* ADC */
+	SND_SOC_DAPM_ADC("ADC Left", "Left HiFi Capture",
+			AK4671_AD_DA_POWER_MANAGEMENT, 4, 0),
+	SND_SOC_DAPM_ADC("ADC Right", "Right HiFi Capture",
+			AK4671_AD_DA_POWER_MANAGEMENT, 5, 0),
+
+	/* PGA */
+	SND_SOC_DAPM_PGA("LOUT2 Mix Amp",
+			AK4671_LOUT2_POWER_MANAGERMENT, 5, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("ROUT2 Mix Amp",
+			AK4671_LOUT2_POWER_MANAGERMENT, 6, 0, NULL, 0),
+
+	SND_SOC_DAPM_PGA("LIN1 Mixing Circuit",
+			AK4671_MIXING_POWER_MANAGEMENT1, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("RIN1 Mixing Circuit",
+			AK4671_MIXING_POWER_MANAGEMENT1, 1, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("LIN2 Mixing Circuit",
+			AK4671_MIXING_POWER_MANAGEMENT1, 2, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("RIN2 Mixing Circuit",
+			AK4671_MIXING_POWER_MANAGEMENT1, 3, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("LIN3 Mixing Circuit",
+			AK4671_MIXING_POWER_MANAGEMENT1, 4, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("RIN3 Mixing Circuit",
+			AK4671_MIXING_POWER_MANAGEMENT1, 5, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("LIN4 Mixing Circuit",
+			AK4671_MIXING_POWER_MANAGEMENT1, 6, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("RIN4 Mixing Circuit",
+			AK4671_MIXING_POWER_MANAGEMENT1, 7, 0, NULL, 0),
+
+	/* Output Mixers */
+	SND_SOC_DAPM_MIXER("LOUT1 Mixer", AK4671_LOUT1_POWER_MANAGERMENT, 0, 0,
+			&ak4671_lout1_mixer_controls[0],
+			ARRAY_SIZE(ak4671_lout1_mixer_controls)),
+	SND_SOC_DAPM_MIXER("ROUT1 Mixer", AK4671_LOUT1_POWER_MANAGERMENT, 1, 0,
+			&ak4671_rout1_mixer_controls[0],
+			ARRAY_SIZE(ak4671_rout1_mixer_controls)),
+	SND_SOC_DAPM_MIXER_E("LOUT2 Mixer", AK4671_LOUT2_POWER_MANAGERMENT,
+			0, 0, &ak4671_lout2_mixer_controls[0],
+			ARRAY_SIZE(ak4671_lout2_mixer_controls),
+			ak4671_out2_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_MIXER_E("ROUT2 Mixer", AK4671_LOUT2_POWER_MANAGERMENT,
+			1, 0, &ak4671_rout2_mixer_controls[0],
+			ARRAY_SIZE(ak4671_rout2_mixer_controls),
+			ak4671_out2_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_MIXER("LOUT3 Mixer", AK4671_LOUT3_POWER_MANAGERMENT, 0, 0,
+			&ak4671_lout3_mixer_controls[0],
+			ARRAY_SIZE(ak4671_lout3_mixer_controls)),
+	SND_SOC_DAPM_MIXER("ROUT3 Mixer", AK4671_LOUT3_POWER_MANAGERMENT, 1, 0,
+			&ak4671_rout3_mixer_controls[0],
+			ARRAY_SIZE(ak4671_rout3_mixer_controls)),
+
+	/* Input MUXs */
+	SND_SOC_DAPM_MUX("LIN MUX", AK4671_AD_DA_POWER_MANAGEMENT, 2, 0,
+			&ak4671_lin_mux_control),
+	SND_SOC_DAPM_MUX("RIN MUX", AK4671_AD_DA_POWER_MANAGEMENT, 3, 0,
+			&ak4671_rin_mux_control),
+
+	/* Mic Power */
+	SND_SOC_DAPM_MICBIAS("Mic Bias", AK4671_AD_DA_POWER_MANAGEMENT, 1, 0),
+
+	/* Supply */
+	SND_SOC_DAPM_SUPPLY("PMPLL", AK4671_PLL_MODE_SELECT1, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+	{"DAC Left", "NULL", "PMPLL"},
+	{"DAC Right", "NULL", "PMPLL"},
+	{"ADC Left", "NULL", "PMPLL"},
+	{"ADC Right", "NULL", "PMPLL"},
+
+	/* Outputs */
+	{"LOUT1", "NULL", "LOUT1 Mixer"},
+	{"ROUT1", "NULL", "ROUT1 Mixer"},
+	{"LOUT2", "NULL", "LOUT2 Mix Amp"},
+	{"ROUT2", "NULL", "ROUT2 Mix Amp"},
+	{"LOUT3", "NULL", "LOUT3 Mixer"},
+	{"ROUT3", "NULL", "ROUT3 Mixer"},
+
+	{"LOUT1 Mixer", "DACL", "DAC Left"},
+	{"ROUT1 Mixer", "DACR", "DAC Right"},
+	{"LOUT2 Mixer", "DACHL", "DAC Left"},
+	{"ROUT2 Mixer", "DACHR", "DAC Right"},
+	{"LOUT2 Mix Amp", "NULL", "LOUT2 Mixer"},
+	{"ROUT2 Mix Amp", "NULL", "ROUT2 Mixer"},
+	{"LOUT3 Mixer", "DACSL", "DAC Left"},
+	{"ROUT3 Mixer", "DACSR", "DAC Right"},
+
+	/* Inputs */
+	{"LIN MUX", "LIN1", "LIN1"},
+	{"LIN MUX", "LIN2", "LIN2"},
+	{"LIN MUX", "LIN3", "LIN3"},
+	{"LIN MUX", "LIN4", "LIN4"},
+
+	{"RIN MUX", "RIN1", "RIN1"},
+	{"RIN MUX", "RIN2", "RIN2"},
+	{"RIN MUX", "RIN3", "RIN3"},
+	{"RIN MUX", "RIN4", "RIN4"},
+
+	{"LIN1", NULL, "Mic Bias"},
+	{"RIN1", NULL, "Mic Bias"},
+	{"LIN2", NULL, "Mic Bias"},
+	{"RIN2", NULL, "Mic Bias"},
+
+	{"ADC Left", "NULL", "LIN MUX"},
+	{"ADC Right", "NULL", "RIN MUX"},
+
+	/* Analog Loops */
+	{"LIN1 Mixing Circuit", "NULL", "LIN1"},
+	{"RIN1 Mixing Circuit", "NULL", "RIN1"},
+	{"LIN2 Mixing Circuit", "NULL", "LIN2"},
+	{"RIN2 Mixing Circuit", "NULL", "RIN2"},
+	{"LIN3 Mixing Circuit", "NULL", "LIN3"},
+	{"RIN3 Mixing Circuit", "NULL", "RIN3"},
+	{"LIN4 Mixing Circuit", "NULL", "LIN4"},
+	{"RIN4 Mixing Circuit", "NULL", "RIN4"},
+
+	{"LOUT1 Mixer", "LINL1", "LIN1 Mixing Circuit"},
+	{"ROUT1 Mixer", "RINR1", "RIN1 Mixing Circuit"},
+	{"LOUT2 Mixer", "LINH1", "LIN1 Mixing Circuit"},
+	{"ROUT2 Mixer", "RINH1", "RIN1 Mixing Circuit"},
+	{"LOUT3 Mixer", "LINS1", "LIN1 Mixing Circuit"},
+	{"ROUT3 Mixer", "RINS1", "RIN1 Mixing Circuit"},
+
+	{"LOUT1 Mixer", "LINL2", "LIN2 Mixing Circuit"},
+	{"ROUT1 Mixer", "RINR2", "RIN2 Mixing Circuit"},
+	{"LOUT2 Mixer", "LINH2", "LIN2 Mixing Circuit"},
+	{"ROUT2 Mixer", "RINH2", "RIN2 Mixing Circuit"},
+	{"LOUT3 Mixer", "LINS2", "LIN2 Mixing Circuit"},
+	{"ROUT3 Mixer", "RINS2", "RIN2 Mixing Circuit"},
+
+	{"LOUT1 Mixer", "LINL3", "LIN3 Mixing Circuit"},
+	{"ROUT1 Mixer", "RINR3", "RIN3 Mixing Circuit"},
+	{"LOUT2 Mixer", "LINH3", "LIN3 Mixing Circuit"},
+	{"ROUT2 Mixer", "RINH3", "RIN3 Mixing Circuit"},
+	{"LOUT3 Mixer", "LINS3", "LIN3 Mixing Circuit"},
+	{"ROUT3 Mixer", "RINS3", "RIN3 Mixing Circuit"},
+
+	{"LOUT1 Mixer", "LINL4", "LIN4 Mixing Circuit"},
+	{"ROUT1 Mixer", "RINR4", "RIN4 Mixing Circuit"},
+	{"LOUT2 Mixer", "LINH4", "LIN4 Mixing Circuit"},
+	{"ROUT2 Mixer", "RINH4", "RIN4 Mixing Circuit"},
+	{"LOUT3 Mixer", "LINS4", "LIN4 Mixing Circuit"},
+	{"ROUT3 Mixer", "RINS4", "RIN4 Mixing Circuit"},
+};
+
+static int ak4671_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, ak4671_dapm_widgets,
+				  ARRAY_SIZE(ak4671_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	return 0;
+}
+
+static int ak4671_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params,
+		struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u8 fs;
+
+	fs = snd_soc_read(codec, AK4671_PLL_MODE_SELECT0);
+	fs &= ~AK4671_FS;
+
+	switch (params_rate(params)) {
+	case 8000:
+		fs |= AK4671_FS_8KHZ;
+		break;
+	case 12000:
+		fs |= AK4671_FS_12KHZ;
+		break;
+	case 16000:
+		fs |= AK4671_FS_16KHZ;
+		break;
+	case 24000:
+		fs |= AK4671_FS_24KHZ;
+		break;
+	case 11025:
+		fs |= AK4671_FS_11_025KHZ;
+		break;
+	case 22050:
+		fs |= AK4671_FS_22_05KHZ;
+		break;
+	case 32000:
+		fs |= AK4671_FS_32KHZ;
+		break;
+	case 44100:
+		fs |= AK4671_FS_44_1KHZ;
+		break;
+	case 48000:
+		fs |= AK4671_FS_48KHZ;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, AK4671_PLL_MODE_SELECT0, fs);
+
+	return 0;
+}
+
+static int ak4671_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+		unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u8 pll;
+
+	pll = snd_soc_read(codec, AK4671_PLL_MODE_SELECT0);
+	pll &= ~AK4671_PLL;
+
+	switch (freq) {
+	case 11289600:
+		pll |= AK4671_PLL_11_2896MHZ;
+		break;
+	case 12000000:
+		pll |= AK4671_PLL_12MHZ;
+		break;
+	case 12288000:
+		pll |= AK4671_PLL_12_288MHZ;
+		break;
+	case 13000000:
+		pll |= AK4671_PLL_13MHZ;
+		break;
+	case 13500000:
+		pll |= AK4671_PLL_13_5MHZ;
+		break;
+	case 19200000:
+		pll |= AK4671_PLL_19_2MHZ;
+		break;
+	case 24000000:
+		pll |= AK4671_PLL_24MHZ;
+		break;
+	case 26000000:
+		pll |= AK4671_PLL_26MHZ;
+		break;
+	case 27000000:
+		pll |= AK4671_PLL_27MHZ;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, AK4671_PLL_MODE_SELECT0, pll);
+
+	return 0;
+}
+
+static int ak4671_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u8 mode;
+	u8 format;
+
+	/* set master/slave audio interface */
+	mode = snd_soc_read(codec, AK4671_PLL_MODE_SELECT1);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		mode |= AK4671_M_S;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		mode &= ~(AK4671_M_S);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	format = snd_soc_read(codec, AK4671_FORMAT_SELECT);
+	format &= ~AK4671_DIF;
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		format |= AK4671_DIF_I2S_MODE;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		format |= AK4671_DIF_MSB_MODE;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		format |= AK4671_DIF_DSP_MODE;
+		format |= AK4671_BCKP;
+		format |= AK4671_MSBS;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* set mode and format */
+	snd_soc_write(codec, AK4671_PLL_MODE_SELECT1, mode);
+	snd_soc_write(codec, AK4671_FORMAT_SELECT, format);
+
+	return 0;
+}
+
+static int ak4671_set_bias_level(struct snd_soc_codec *codec,
+		enum snd_soc_bias_level level)
+{
+	u8 reg;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+	case SND_SOC_BIAS_STANDBY:
+		reg = snd_soc_read(codec, AK4671_AD_DA_POWER_MANAGEMENT);
+		snd_soc_write(codec, AK4671_AD_DA_POWER_MANAGEMENT,
+				reg | AK4671_PMVCM);
+		break;
+	case SND_SOC_BIAS_OFF:
+		snd_soc_write(codec, AK4671_AD_DA_POWER_MANAGEMENT, 0x00);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define AK4671_RATES		(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+				SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+				SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+				SNDRV_PCM_RATE_48000)
+
+#define AK4671_FORMATS		SNDRV_PCM_FMTBIT_S16_LE
+
+static struct snd_soc_dai_ops ak4671_dai_ops = {
+	.hw_params	= ak4671_hw_params,
+	.set_sysclk	= ak4671_set_dai_sysclk,
+	.set_fmt	= ak4671_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver ak4671_dai = {
+	.name = "ak4671-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = AK4671_RATES,
+		.formats = AK4671_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = AK4671_RATES,
+		.formats = AK4671_FORMATS,},
+	.ops = &ak4671_dai_ops,
+};
+
+static int ak4671_probe(struct snd_soc_codec *codec)
+{
+	struct ak4671_priv *ak4671 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	codec->hw_write = (hw_write_t)i2c_master_send;
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 8, ak4671->control_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	snd_soc_add_controls(codec, ak4671_snd_controls,
+			     ARRAY_SIZE(ak4671_snd_controls));
+	ak4671_add_widgets(codec);
+
+	ak4671_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return ret;
+}
+
+static int ak4671_remove(struct snd_soc_codec *codec)
+{
+	ak4671_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ak4671 = {
+	.probe = ak4671_probe,
+	.remove = ak4671_remove,
+	.set_bias_level = ak4671_set_bias_level,
+	.reg_cache_size = AK4671_CACHEREGNUM,
+	.reg_word_size = sizeof(u8),
+	.reg_cache_default = ak4671_reg,
+};
+
+static int __devinit ak4671_i2c_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct ak4671_priv *ak4671;
+	int ret;
+
+	ak4671 = kzalloc(sizeof(struct ak4671_priv), GFP_KERNEL);
+	if (ak4671 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, ak4671);
+	ak4671->control_data = client;
+	ak4671->control_type = SND_SOC_I2C;
+
+	ret = snd_soc_register_codec(&client->dev,
+			&soc_codec_dev_ak4671, &ak4671_dai, 1);
+	if (ret < 0)
+		kfree(ak4671);
+	return ret;
+}
+
+static __devexit int ak4671_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id ak4671_i2c_id[] = {
+	{ "ak4671", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ak4671_i2c_id);
+
+static struct i2c_driver ak4671_i2c_driver = {
+	.driver = {
+		.name = "ak4671-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe = ak4671_i2c_probe,
+	.remove = __devexit_p(ak4671_i2c_remove),
+	.id_table = ak4671_i2c_id,
+};
+
+static int __init ak4671_modinit(void)
+{
+	return i2c_add_driver(&ak4671_i2c_driver);
+}
+module_init(ak4671_modinit);
+
+static void __exit ak4671_exit(void)
+{
+	i2c_del_driver(&ak4671_i2c_driver);
+}
+module_exit(ak4671_exit);
+
+MODULE_DESCRIPTION("ASoC AK4671 codec driver");
+MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/ak4671.h b/linux/sound/soc/codecs/ak4671.h
new file mode 100644
index 0000000..61cb7ab
--- /dev/null
+++ b/linux/sound/soc/codecs/ak4671.h
@@ -0,0 +1,153 @@
+/*
+ * ak4671.h  --  audio driver for AK4671
+ *
+ * Copyright (C) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim@samsung.com>
+ *
+ *  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.
+ *
+ */
+
+#ifndef _AK4671_H
+#define _AK4671_H
+
+#define AK4671_AD_DA_POWER_MANAGEMENT		0x00
+#define AK4671_PLL_MODE_SELECT0			0x01
+#define AK4671_PLL_MODE_SELECT1			0x02
+#define AK4671_FORMAT_SELECT			0x03
+#define AK4671_MIC_SIGNAL_SELECT		0x04
+#define AK4671_MIC_AMP_GAIN			0x05
+#define AK4671_MIXING_POWER_MANAGEMENT0		0x06
+#define AK4671_MIXING_POWER_MANAGEMENT1		0x07
+#define AK4671_OUTPUT_VOLUME_CONTROL		0x08
+#define AK4671_LOUT1_SIGNAL_SELECT		0x09
+#define AK4671_ROUT1_SIGNAL_SELECT		0x0a
+#define AK4671_LOUT2_SIGNAL_SELECT		0x0b
+#define AK4671_ROUT2_SIGNAL_SELECT		0x0c
+#define AK4671_LOUT3_SIGNAL_SELECT		0x0d
+#define AK4671_ROUT3_SIGNAL_SELECT		0x0e
+#define AK4671_LOUT1_POWER_MANAGERMENT		0x0f
+#define AK4671_LOUT2_POWER_MANAGERMENT		0x10
+#define AK4671_LOUT3_POWER_MANAGERMENT		0x11
+#define AK4671_LCH_INPUT_VOLUME_CONTROL		0x12
+#define AK4671_RCH_INPUT_VOLUME_CONTROL		0x13
+#define AK4671_ALC_REFERENCE_SELECT		0x14
+#define AK4671_DIGITAL_MIXING_CONTROL		0x15
+#define AK4671_ALC_TIMER_SELECT			0x16
+#define AK4671_ALC_MODE_CONTROL			0x17
+#define AK4671_MODE_CONTROL1			0x18
+#define AK4671_MODE_CONTROL2			0x19
+#define AK4671_LCH_OUTPUT_VOLUME_CONTROL	0x1a
+#define AK4671_RCH_OUTPUT_VOLUME_CONTROL	0x1b
+#define AK4671_SIDETONE_A_CONTROL		0x1c
+#define AK4671_DIGITAL_FILTER_SELECT		0x1d
+#define AK4671_FIL3_COEFFICIENT0		0x1e
+#define AK4671_FIL3_COEFFICIENT1		0x1f
+#define AK4671_FIL3_COEFFICIENT2		0x20
+#define AK4671_FIL3_COEFFICIENT3		0x21
+#define AK4671_EQ_COEFFICIENT0			0x22
+#define AK4671_EQ_COEFFICIENT1			0x23
+#define AK4671_EQ_COEFFICIENT2			0x24
+#define AK4671_EQ_COEFFICIENT3			0x25
+#define AK4671_EQ_COEFFICIENT4			0x26
+#define AK4671_EQ_COEFFICIENT5			0x27
+#define AK4671_FIL1_COEFFICIENT0		0x28
+#define AK4671_FIL1_COEFFICIENT1		0x29
+#define AK4671_FIL1_COEFFICIENT2		0x2a
+#define AK4671_FIL1_COEFFICIENT3		0x2b
+#define AK4671_FIL2_COEFFICIENT0		0x2c
+#define AK4671_FIL2_COEFFICIENT1		0x2d
+#define AK4671_FIL2_COEFFICIENT2		0x2e
+#define AK4671_FIL2_COEFFICIENT3		0x2f
+#define AK4671_DIGITAL_FILTER_SELECT2		0x30
+#define AK4671_E1_COEFFICIENT0			0x32
+#define AK4671_E1_COEFFICIENT1			0x33
+#define AK4671_E1_COEFFICIENT2			0x34
+#define AK4671_E1_COEFFICIENT3			0x35
+#define AK4671_E1_COEFFICIENT4			0x36
+#define AK4671_E1_COEFFICIENT5			0x37
+#define AK4671_E2_COEFFICIENT0			0x38
+#define AK4671_E2_COEFFICIENT1			0x39
+#define AK4671_E2_COEFFICIENT2			0x3a
+#define AK4671_E2_COEFFICIENT3			0x3b
+#define AK4671_E2_COEFFICIENT4			0x3c
+#define AK4671_E2_COEFFICIENT5			0x3d
+#define AK4671_E3_COEFFICIENT0			0x3e
+#define AK4671_E3_COEFFICIENT1			0x3f
+#define AK4671_E3_COEFFICIENT2			0x40
+#define AK4671_E3_COEFFICIENT3			0x41
+#define AK4671_E3_COEFFICIENT4			0x42
+#define AK4671_E3_COEFFICIENT5			0x43
+#define AK4671_E4_COEFFICIENT0			0x44
+#define AK4671_E4_COEFFICIENT1			0x45
+#define AK4671_E4_COEFFICIENT2			0x46
+#define AK4671_E4_COEFFICIENT3			0x47
+#define AK4671_E4_COEFFICIENT4			0x48
+#define AK4671_E4_COEFFICIENT5			0x49
+#define AK4671_E5_COEFFICIENT0			0x4a
+#define AK4671_E5_COEFFICIENT1			0x4b
+#define AK4671_E5_COEFFICIENT2			0x4c
+#define AK4671_E5_COEFFICIENT3			0x4d
+#define AK4671_E5_COEFFICIENT4			0x4e
+#define AK4671_E5_COEFFICIENT5			0x4f
+#define AK4671_EQ_CONTROL_250HZ_100HZ		0x50
+#define AK4671_EQ_CONTROL_3500HZ_1KHZ		0x51
+#define AK4671_EQ_CONTRO_10KHZ			0x52
+#define AK4671_PCM_IF_CONTROL0			0x53
+#define AK4671_PCM_IF_CONTROL1			0x54
+#define AK4671_PCM_IF_CONTROL2			0x55
+#define AK4671_DIGITAL_VOLUME_B_CONTROL		0x56
+#define AK4671_DIGITAL_VOLUME_C_CONTROL		0x57
+#define AK4671_SIDETONE_VOLUME_CONTROL		0x58
+#define AK4671_DIGITAL_MIXING_CONTROL2		0x59
+#define AK4671_SAR_ADC_CONTROL			0x5a
+
+#define AK4671_CACHEREGNUM			(AK4671_SAR_ADC_CONTROL + 1)
+
+/* Bitfield Definitions */
+
+/* AK4671_AD_DA_POWER_MANAGEMENT (0x00) Fields */
+#define AK4671_PMVCM				0x01
+
+/* AK4671_PLL_MODE_SELECT0 (0x01) Fields */
+#define AK4671_PLL				0x0f
+#define AK4671_PLL_11_2896MHZ			(4 << 0)
+#define AK4671_PLL_12_288MHZ			(5 << 0)
+#define AK4671_PLL_12MHZ			(6 << 0)
+#define AK4671_PLL_24MHZ			(7 << 0)
+#define AK4671_PLL_19_2MHZ			(8 << 0)
+#define AK4671_PLL_13_5MHZ			(12 << 0)
+#define AK4671_PLL_27MHZ			(13 << 0)
+#define AK4671_PLL_13MHZ			(14 << 0)
+#define AK4671_PLL_26MHZ			(15 << 0)
+#define AK4671_FS				0xf0
+#define AK4671_FS_8KHZ				(0 << 4)
+#define AK4671_FS_12KHZ				(1 << 4)
+#define AK4671_FS_16KHZ				(2 << 4)
+#define AK4671_FS_24KHZ				(3 << 4)
+#define AK4671_FS_11_025KHZ			(5 << 4)
+#define AK4671_FS_22_05KHZ			(7 << 4)
+#define AK4671_FS_32KHZ				(10 << 4)
+#define AK4671_FS_48KHZ				(11 << 4)
+#define AK4671_FS_44_1KHZ			(15 << 4)
+
+/* AK4671_PLL_MODE_SELECT1 (0x02) Fields */
+#define AK4671_PMPLL				0x01
+#define AK4671_M_S				0x02
+
+/* AK4671_FORMAT_SELECT (0x03) Fields */
+#define AK4671_DIF				0x03
+#define AK4671_DIF_DSP_MODE			(0 << 0)
+#define AK4671_DIF_MSB_MODE			(2 << 0)
+#define AK4671_DIF_I2S_MODE			(3 << 0)
+#define AK4671_BCKP				0x04
+#define AK4671_MSBS				0x08
+#define AK4671_SDOD				0x10
+
+/* AK4671_LOUT2_POWER_MANAGEMENT (0x10) Fields */
+#define AK4671_MUTEN				0x04
+
+#endif
diff --git a/linux/sound/soc/codecs/cq93vc.c b/linux/sound/soc/codecs/cq93vc.c
new file mode 100644
index 0000000..8236439
--- /dev/null
+++ b/linux/sound/soc/codecs/cq93vc.c
@@ -0,0 +1,225 @@
+/*
+ * ALSA SoC CQ0093 Voice Codec Driver for DaVinci platforms
+ *
+ * Copyright (C) 2010 Texas Instruments, Inc
+ *
+ * Author: Miguel Aguilar <miguel.aguilar@ridgerun.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/mfd/davinci_voicecodec.h>
+#include <linux/spi/spi.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include <mach/dm365.h>
+
+static inline unsigned int cq93vc_read(struct snd_soc_codec *codec,
+						unsigned int reg)
+{
+	struct davinci_vc *davinci_vc = codec->control_data;
+
+	return readl(davinci_vc->base + reg);
+}
+
+static inline int cq93vc_write(struct snd_soc_codec *codec, unsigned int reg,
+		       unsigned int value)
+{
+	struct davinci_vc *davinci_vc = codec->control_data;
+
+	writel(value, davinci_vc->base + reg);
+
+	return 0;
+}
+
+static const struct snd_kcontrol_new cq93vc_snd_controls[] = {
+	SOC_SINGLE("PGA Capture Volume", DAVINCI_VC_REG05, 0, 0x03, 0),
+	SOC_SINGLE("Mono DAC Playback Volume", DAVINCI_VC_REG09, 0, 0x3f, 0),
+};
+
+static int cq93vc_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u8 reg = cq93vc_read(codec, DAVINCI_VC_REG09) & ~DAVINCI_VC_REG09_MUTE;
+
+	if (mute)
+		cq93vc_write(codec, DAVINCI_VC_REG09,
+			     reg | DAVINCI_VC_REG09_MUTE);
+	else
+		cq93vc_write(codec, DAVINCI_VC_REG09, reg);
+
+	return 0;
+}
+
+static int cq93vc_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+				 int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct davinci_vc *davinci_vc = codec->control_data;
+
+	switch (freq) {
+	case 22579200:
+	case 27000000:
+	case 33868800:
+		davinci_vc->cq93vc.sysclk = freq;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int cq93vc_set_bias_level(struct snd_soc_codec *codec,
+				enum snd_soc_bias_level level)
+{
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		cq93vc_write(codec, DAVINCI_VC_REG12,
+			     DAVINCI_VC_REG12_POWER_ALL_ON);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		cq93vc_write(codec, DAVINCI_VC_REG12,
+			     DAVINCI_VC_REG12_POWER_ALL_OFF);
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* force all power off */
+		cq93vc_write(codec, DAVINCI_VC_REG12,
+			     DAVINCI_VC_REG12_POWER_ALL_OFF);
+		break;
+	}
+	codec->bias_level = level;
+
+	return 0;
+}
+
+#define CQ93VC_RATES	(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000)
+#define CQ93VC_FORMATS	(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE)
+
+static struct snd_soc_dai_ops cq93vc_dai_ops = {
+	.digital_mute	= cq93vc_mute,
+	.set_sysclk	= cq93vc_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver cq93vc_dai = {
+	.name = "cq93vc-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = CQ93VC_RATES,
+		.formats = CQ93VC_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = CQ93VC_RATES,
+		.formats = CQ93VC_FORMATS,},
+	.ops = &cq93vc_dai_ops,
+};
+
+static int cq93vc_resume(struct snd_soc_codec *codec)
+{
+	cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static int cq93vc_probe(struct snd_soc_codec *codec)
+{
+	struct davinci_vc *davinci_vc = codec->dev->platform_data;
+
+	davinci_vc->cq93vc.codec = codec;
+	codec->control_data = davinci_vc;
+
+	/* Set controls */
+	snd_soc_add_controls(codec, cq93vc_snd_controls,
+			     ARRAY_SIZE(cq93vc_snd_controls));
+
+	/* Off, with power on */
+	cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static int cq93vc_remove(struct snd_soc_codec *codec)
+{
+	cq93vc_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_cq93vc = {
+	.read = cq93vc_read,
+	.write = cq93vc_write,
+	.set_bias_level = cq93vc_set_bias_level,
+	.probe = cq93vc_probe,
+	.remove = cq93vc_remove,
+	.resume = cq93vc_resume,
+};
+
+static int cq93vc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_cq93vc, &cq93vc_dai, 1);
+}
+
+static int cq93vc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver cq93vc_codec_driver = {
+	.driver = {
+			.name = "cq93vc-codec",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = cq93vc_platform_probe,
+	.remove = __devexit_p(cq93vc_platform_remove),
+};
+
+static int __init cq93vc_init(void)
+{
+	return platform_driver_register(&cq93vc_codec_driver);
+}
+module_init(cq93vc_init);
+
+static void __exit cq93vc_exit(void)
+{
+	platform_driver_unregister(&cq93vc_codec_driver);
+}
+module_exit(cq93vc_exit);
+
+MODULE_DESCRIPTION("Texas Instruments DaVinci ASoC CQ0093 Voice Codec Driver");
+MODULE_AUTHOR("Miguel Aguilar");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/cs4270.c b/linux/sound/soc/codecs/cs4270.c
new file mode 100644
index 0000000..6d4bdc6
--- /dev/null
+++ b/linux/sound/soc/codecs/cs4270.c
@@ -0,0 +1,826 @@
+/*
+ * CS4270 ALSA SoC (ASoC) codec driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2009 Freescale Semiconductor, Inc.  This file is licensed
+ * under the terms of the GNU General Public License version 2.  This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ *
+ * This is an ASoC device driver for the Cirrus Logic CS4270 codec.
+ *
+ * Current features/limitations:
+ *
+ * - Software mode is supported.  Stand-alone mode is not supported.
+ * - Only I2C is supported, not SPI
+ * - Support for master and slave mode
+ * - The machine driver's 'startup' function must call
+ *   cs4270_set_dai_sysclk() with the value of MCLK.
+ * - Only I2S and left-justified modes are supported
+ * - Power management is supported
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+
+/*
+ * The codec isn't really big-endian or little-endian, since the I2S
+ * interface requires data to be sent serially with the MSbit first.
+ * However, to support BE and LE I2S devices, we specify both here.  That
+ * way, ALSA will always match the bit patterns.
+ */
+#define CS4270_FORMATS (SNDRV_PCM_FMTBIT_S8      | \
+			SNDRV_PCM_FMTBIT_S16_LE  | SNDRV_PCM_FMTBIT_S16_BE  | \
+			SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \
+			SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \
+			SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \
+			SNDRV_PCM_FMTBIT_S24_LE  | SNDRV_PCM_FMTBIT_S24_BE)
+
+/* CS4270 registers addresses */
+#define CS4270_CHIPID	0x01	/* Chip ID */
+#define CS4270_PWRCTL	0x02	/* Power Control */
+#define CS4270_MODE	0x03	/* Mode Control */
+#define CS4270_FORMAT	0x04	/* Serial Format, ADC/DAC Control */
+#define CS4270_TRANS	0x05	/* Transition Control */
+#define CS4270_MUTE	0x06	/* Mute Control */
+#define CS4270_VOLA	0x07	/* DAC Channel A Volume Control */
+#define CS4270_VOLB	0x08	/* DAC Channel B Volume Control */
+
+#define CS4270_FIRSTREG	0x01
+#define CS4270_LASTREG	0x08
+#define CS4270_NUMREGS	(CS4270_LASTREG - CS4270_FIRSTREG + 1)
+#define CS4270_I2C_INCR	0x80
+
+/* Bit masks for the CS4270 registers */
+#define CS4270_CHIPID_ID	0xF0
+#define CS4270_CHIPID_REV	0x0F
+#define CS4270_PWRCTL_FREEZE	0x80
+#define CS4270_PWRCTL_PDN_ADC	0x20
+#define CS4270_PWRCTL_PDN_DAC	0x02
+#define CS4270_PWRCTL_PDN	0x01
+#define CS4270_PWRCTL_PDN_ALL	\
+	(CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC | CS4270_PWRCTL_PDN)
+#define CS4270_MODE_SPEED_MASK	0x30
+#define CS4270_MODE_1X		0x00
+#define CS4270_MODE_2X		0x10
+#define CS4270_MODE_4X		0x20
+#define CS4270_MODE_SLAVE	0x30
+#define CS4270_MODE_DIV_MASK	0x0E
+#define CS4270_MODE_DIV1	0x00
+#define CS4270_MODE_DIV15	0x02
+#define CS4270_MODE_DIV2	0x04
+#define CS4270_MODE_DIV3	0x06
+#define CS4270_MODE_DIV4	0x08
+#define CS4270_MODE_POPGUARD	0x01
+#define CS4270_FORMAT_FREEZE_A	0x80
+#define CS4270_FORMAT_FREEZE_B	0x40
+#define CS4270_FORMAT_LOOPBACK	0x20
+#define CS4270_FORMAT_DAC_MASK	0x18
+#define CS4270_FORMAT_DAC_LJ	0x00
+#define CS4270_FORMAT_DAC_I2S	0x08
+#define CS4270_FORMAT_DAC_RJ16	0x18
+#define CS4270_FORMAT_DAC_RJ24	0x10
+#define CS4270_FORMAT_ADC_MASK	0x01
+#define CS4270_FORMAT_ADC_LJ	0x00
+#define CS4270_FORMAT_ADC_I2S	0x01
+#define CS4270_TRANS_ONE_VOL	0x80
+#define CS4270_TRANS_SOFT	0x40
+#define CS4270_TRANS_ZERO	0x20
+#define CS4270_TRANS_INV_ADC_A	0x08
+#define CS4270_TRANS_INV_ADC_B	0x10
+#define CS4270_TRANS_INV_DAC_A	0x02
+#define CS4270_TRANS_INV_DAC_B	0x04
+#define CS4270_TRANS_DEEMPH	0x01
+#define CS4270_MUTE_AUTO	0x20
+#define CS4270_MUTE_ADC_A	0x08
+#define CS4270_MUTE_ADC_B	0x10
+#define CS4270_MUTE_POLARITY	0x04
+#define CS4270_MUTE_DAC_A	0x01
+#define CS4270_MUTE_DAC_B	0x02
+
+static const char *supply_names[] = {
+	"va", "vd", "vlc"
+};
+
+/* Private data for the CS4270 */
+struct cs4270_private {
+	enum snd_soc_control_type control_type;
+	void *control_data;
+	u8 reg_cache[CS4270_NUMREGS];
+	unsigned int mclk; /* Input frequency of the MCLK pin */
+	unsigned int mode; /* The mode (I2S or left-justified) */
+	unsigned int slave_mode;
+	unsigned int manual_mute;
+
+	/* power domain regulators */
+	struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
+};
+
+/**
+ * struct cs4270_mode_ratios - clock ratio tables
+ * @ratio: the ratio of MCLK to the sample rate
+ * @speed_mode: the Speed Mode bits to set in the Mode Control register for
+ *              this ratio
+ * @mclk: the Ratio Select bits to set in the Mode Control register for this
+ *        ratio
+ *
+ * The data for this chart is taken from Table 5 of the CS4270 reference
+ * manual.
+ *
+ * This table is used to determine how to program the Mode Control register.
+ * It is also used by cs4270_set_dai_sysclk() to tell ALSA which sampling
+ * rates the CS4270 currently supports.
+ *
+ * @speed_mode is the corresponding bit pattern to be written to the
+ * MODE bits of the Mode Control Register
+ *
+ * @mclk is the corresponding bit pattern to be wirten to the MCLK bits of
+ * the Mode Control Register.
+ *
+ * In situations where a single ratio is represented by multiple speed
+ * modes, we favor the slowest speed.  E.g, for a ratio of 128, we pick
+ * double-speed instead of quad-speed.  However, the CS4270 errata states
+ * that divide-By-1.5 can cause failures, so we avoid that mode where
+ * possible.
+ *
+ * Errata: There is an errata for the CS4270 where divide-by-1.5 does not
+ * work if Vd is 3.3V.  If this effects you, select the
+ * CONFIG_SND_SOC_CS4270_VD33_ERRATA Kconfig option, and the driver will
+ * never select any sample rates that require divide-by-1.5.
+ */
+struct cs4270_mode_ratios {
+	unsigned int ratio;
+	u8 speed_mode;
+	u8 mclk;
+};
+
+static struct cs4270_mode_ratios cs4270_mode_ratios[] = {
+	{64, CS4270_MODE_4X, CS4270_MODE_DIV1},
+#ifndef CONFIG_SND_SOC_CS4270_VD33_ERRATA
+	{96, CS4270_MODE_4X, CS4270_MODE_DIV15},
+#endif
+	{128, CS4270_MODE_2X, CS4270_MODE_DIV1},
+	{192, CS4270_MODE_4X, CS4270_MODE_DIV3},
+	{256, CS4270_MODE_1X, CS4270_MODE_DIV1},
+	{384, CS4270_MODE_2X, CS4270_MODE_DIV3},
+	{512, CS4270_MODE_1X, CS4270_MODE_DIV2},
+	{768, CS4270_MODE_1X, CS4270_MODE_DIV3},
+	{1024, CS4270_MODE_1X, CS4270_MODE_DIV4}
+};
+
+/* The number of MCLK/LRCK ratios supported by the CS4270 */
+#define NUM_MCLK_RATIOS		ARRAY_SIZE(cs4270_mode_ratios)
+
+/**
+ * cs4270_set_dai_sysclk - determine the CS4270 samples rates.
+ * @codec_dai: the codec DAI
+ * @clk_id: the clock ID (ignored)
+ * @freq: the MCLK input frequency
+ * @dir: the clock direction (ignored)
+ *
+ * This function is used to tell the codec driver what the input MCLK
+ * frequency is.
+ *
+ * The value of MCLK is used to determine which sample rates are supported
+ * by the CS4270.  The ratio of MCLK / Fs must be equal to one of nine
+ * supported values - 64, 96, 128, 192, 256, 384, 512, 768, and 1024.
+ *
+ * This function calculates the nine ratios and determines which ones match
+ * a standard sample rate.  If there's a match, then it is added to the list
+ * of supported sample rates.
+ *
+ * This function must be called by the machine driver's 'startup' function,
+ * otherwise the list of supported sample rates will not be available in
+ * time for ALSA.
+ *
+ * For setups with variable MCLKs, pass 0 as 'freq' argument. This will cause
+ * theoretically possible sample rates to be enabled. Call it again with a
+ * proper value set one the external clock is set (most probably you would do
+ * that from a machine's driver 'hw_param' hook.
+ */
+static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+				 int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+
+	cs4270->mclk = freq;
+	return 0;
+}
+
+/**
+ * cs4270_set_dai_fmt - configure the codec for the selected audio format
+ * @codec_dai: the codec DAI
+ * @format: a SND_SOC_DAIFMT_x value indicating the data format
+ *
+ * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the
+ * codec accordingly.
+ *
+ * Currently, this function only supports SND_SOC_DAIFMT_I2S and
+ * SND_SOC_DAIFMT_LEFT_J.  The CS4270 codec also supports right-justified
+ * data for playback only, but ASoC currently does not support different
+ * formats for playback vs. record.
+ */
+static int cs4270_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			      unsigned int format)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	/* set DAI format */
+	switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_LEFT_J:
+		cs4270->mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
+		break;
+	default:
+		dev_err(codec->dev, "invalid dai format\n");
+		ret = -EINVAL;
+	}
+
+	/* set master/slave audio interface */
+	switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		cs4270->slave_mode = 1;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		cs4270->slave_mode = 0;
+		break;
+	default:
+		/* all other modes are unsupported by the hardware */
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+/**
+ * cs4270_fill_cache - pre-fill the CS4270 register cache.
+ * @codec: the codec for this CS4270
+ *
+ * This function fills in the CS4270 register cache by reading the register
+ * values from the hardware.
+ *
+ * This CS4270 registers are cached to avoid excessive I2C I/O operations.
+ * After the initial read to pre-fill the cache, the CS4270 never updates
+ * the register values, so we won't have a cache coherency problem.
+ *
+ * We use the auto-increment feature of the CS4270 to read all registers in
+ * one shot.
+ */
+static int cs4270_fill_cache(struct snd_soc_codec *codec)
+{
+	u8 *cache = codec->reg_cache;
+	struct i2c_client *i2c_client = codec->control_data;
+	s32 length;
+
+	length = i2c_smbus_read_i2c_block_data(i2c_client,
+		CS4270_FIRSTREG | CS4270_I2C_INCR, CS4270_NUMREGS, cache);
+
+	if (length != CS4270_NUMREGS) {
+		dev_err(codec->dev, "i2c read failure, addr=0x%x\n",
+		       i2c_client->addr);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+/**
+ * cs4270_read_reg_cache - read from the CS4270 register cache.
+ * @codec: the codec for this CS4270
+ * @reg: the register to read
+ *
+ * This function returns the value for a given register.  It reads only from
+ * the register cache, not the hardware itself.
+ *
+ * This CS4270 registers are cached to avoid excessive I2C I/O operations.
+ * After the initial read to pre-fill the cache, the CS4270 never updates
+ * the register values, so we won't have a cache coherency problem.
+ */
+static unsigned int cs4270_read_reg_cache(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	u8 *cache = codec->reg_cache;
+
+	if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG))
+		return -EIO;
+
+	return cache[reg - CS4270_FIRSTREG];
+}
+
+/**
+ * cs4270_i2c_write - write to a CS4270 register via the I2C bus.
+ * @codec: the codec for this CS4270
+ * @reg: the register to write
+ * @value: the value to write to the register
+ *
+ * This function writes the given value to the given CS4270 register, and
+ * also updates the register cache.
+ *
+ * Note that we don't use the hw_write function pointer of snd_soc_codec.
+ * That's because it's too clunky: the hw_write_t prototype does not match
+ * i2c_smbus_write_byte_data(), and it's just another layer of overhead.
+ */
+static int cs4270_i2c_write(struct snd_soc_codec *codec, unsigned int reg,
+			    unsigned int value)
+{
+	u8 *cache = codec->reg_cache;
+
+	if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG))
+		return -EIO;
+
+	/* Only perform an I2C operation if the new value is different */
+	if (cache[reg - CS4270_FIRSTREG] != value) {
+		struct i2c_client *client = codec->control_data;
+		if (i2c_smbus_write_byte_data(client, reg, value)) {
+			dev_err(codec->dev, "i2c write failed\n");
+			return -EIO;
+		}
+
+		/* We've written to the hardware, so update the cache */
+		cache[reg - CS4270_FIRSTREG] = value;
+	}
+
+	return 0;
+}
+
+/**
+ * cs4270_hw_params - program the CS4270 with the given hardware parameters.
+ * @substream: the audio stream
+ * @params: the hardware parameters to set
+ * @dai: the SOC DAI (ignored)
+ *
+ * This function programs the hardware with the values provided.
+ * Specifically, the sample rate and the data format.
+ *
+ * The .ops functions are used to provide board-specific data, like input
+ * frequencies, to this driver.  This function takes that information,
+ * combines it with the hardware parameters provided, and programs the
+ * hardware accordingly.
+ */
+static int cs4270_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+	unsigned int i;
+	unsigned int rate;
+	unsigned int ratio;
+	int reg;
+
+	/* Figure out which MCLK/LRCK ratio to use */
+
+	rate = params_rate(params);	/* Sampling rate, in Hz */
+	ratio = cs4270->mclk / rate;	/* MCLK/LRCK ratio */
+
+	for (i = 0; i < NUM_MCLK_RATIOS; i++) {
+		if (cs4270_mode_ratios[i].ratio == ratio)
+			break;
+	}
+
+	if (i == NUM_MCLK_RATIOS) {
+		/* We did not find a matching ratio */
+		dev_err(codec->dev, "could not find matching ratio\n");
+		return -EINVAL;
+	}
+
+	/* Set the sample rate */
+
+	reg = snd_soc_read(codec, CS4270_MODE);
+	reg &= ~(CS4270_MODE_SPEED_MASK | CS4270_MODE_DIV_MASK);
+	reg |= cs4270_mode_ratios[i].mclk;
+
+	if (cs4270->slave_mode)
+		reg |= CS4270_MODE_SLAVE;
+	else
+		reg |= cs4270_mode_ratios[i].speed_mode;
+
+	ret = snd_soc_write(codec, CS4270_MODE, reg);
+	if (ret < 0) {
+		dev_err(codec->dev, "i2c write failed\n");
+		return ret;
+	}
+
+	/* Set the DAI format */
+
+	reg = snd_soc_read(codec, CS4270_FORMAT);
+	reg &= ~(CS4270_FORMAT_DAC_MASK | CS4270_FORMAT_ADC_MASK);
+
+	switch (cs4270->mode) {
+	case SND_SOC_DAIFMT_I2S:
+		reg |= CS4270_FORMAT_DAC_I2S | CS4270_FORMAT_ADC_I2S;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		reg |= CS4270_FORMAT_DAC_LJ | CS4270_FORMAT_ADC_LJ;
+		break;
+	default:
+		dev_err(codec->dev, "unknown dai format\n");
+		return -EINVAL;
+	}
+
+	ret = snd_soc_write(codec, CS4270_FORMAT, reg);
+	if (ret < 0) {
+		dev_err(codec->dev, "i2c write failed\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+/**
+ * cs4270_dai_mute - enable/disable the CS4270 external mute
+ * @dai: the SOC DAI
+ * @mute: 0 = disable mute, 1 = enable mute
+ *
+ * This function toggles the mute bits in the MUTE register.  The CS4270's
+ * mute capability is intended for external muting circuitry, so if the
+ * board does not have the MUTEA or MUTEB pins connected to such circuitry,
+ * then this function will do nothing.
+ */
+static int cs4270_dai_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+	int reg6;
+
+	reg6 = snd_soc_read(codec, CS4270_MUTE);
+
+	if (mute)
+		reg6 |= CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B;
+	else {
+		reg6 &= ~(CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B);
+		reg6 |= cs4270->manual_mute;
+	}
+
+	return snd_soc_write(codec, CS4270_MUTE, reg6);
+}
+
+/**
+ * cs4270_soc_put_mute - put callback for the 'Master Playback switch'
+ * 			 alsa control.
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * This function basically passes the arguments on to the generic
+ * snd_soc_put_volsw() function and saves the mute information in
+ * our private data structure. This is because we want to prevent
+ * cs4270_dai_mute() neglecting the user's decision to manually
+ * mute the codec's output.
+ *
+ * Returns 0 for success.
+ */
+static int cs4270_soc_put_mute(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+	int left = !ucontrol->value.integer.value[0];
+	int right = !ucontrol->value.integer.value[1];
+
+	cs4270->manual_mute = (left ? CS4270_MUTE_DAC_A : 0) |
+			      (right ? CS4270_MUTE_DAC_B : 0);
+
+	return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
+/* A list of non-DAPM controls that the CS4270 supports */
+static const struct snd_kcontrol_new cs4270_snd_controls[] = {
+	SOC_DOUBLE_R("Master Playback Volume",
+		CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1),
+	SOC_SINGLE("Digital Sidetone Switch", CS4270_FORMAT, 5, 1, 0),
+	SOC_SINGLE("Soft Ramp Switch", CS4270_TRANS, 6, 1, 0),
+	SOC_SINGLE("Zero Cross Switch", CS4270_TRANS, 5, 1, 0),
+	SOC_SINGLE("De-emphasis filter", CS4270_TRANS, 0, 1, 0),
+	SOC_SINGLE("Popguard Switch", CS4270_MODE, 0, 1, 1),
+	SOC_SINGLE("Auto-Mute Switch", CS4270_MUTE, 5, 1, 0),
+	SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 1),
+	SOC_DOUBLE_EXT("Master Playback Switch", CS4270_MUTE, 0, 1, 1, 1,
+		snd_soc_get_volsw, cs4270_soc_put_mute),
+};
+
+static struct snd_soc_dai_ops cs4270_dai_ops = {
+	.hw_params	= cs4270_hw_params,
+	.set_sysclk	= cs4270_set_dai_sysclk,
+	.set_fmt	= cs4270_set_dai_fmt,
+	.digital_mute	= cs4270_dai_mute,
+};
+
+static struct snd_soc_dai_driver cs4270_dai = {
+	.name = "cs4270-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.rate_min = 4000,
+		.rate_max = 216000,
+		.formats = CS4270_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.rate_min = 4000,
+		.rate_max = 216000,
+		.formats = CS4270_FORMATS,
+	},
+	.ops = &cs4270_dai_ops,
+};
+
+/**
+ * cs4270_probe - ASoC probe function
+ * @pdev: platform device
+ *
+ * This function is called when ASoC has all the pieces it needs to
+ * instantiate a sound driver.
+ */
+static int cs4270_probe(struct snd_soc_codec *codec)
+{
+	struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+	int i, ret, reg;
+
+	codec->control_data = cs4270->control_data;
+
+	/* The I2C interface is set up, so pre-fill our register cache */
+
+	ret = cs4270_fill_cache(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "failed to fill register cache\n");
+		return ret;
+	}
+
+	/* Disable auto-mute.  This feature appears to be buggy.  In some
+	 * situations, auto-mute will not deactivate when it should, so we want
+	 * this feature disabled by default.  An application (e.g. alsactl) can
+	 * re-enabled it by using the controls.
+	 */
+
+	reg = cs4270_read_reg_cache(codec, CS4270_MUTE);
+	reg &= ~CS4270_MUTE_AUTO;
+	ret = cs4270_i2c_write(codec, CS4270_MUTE, reg);
+	if (ret < 0) {
+		dev_err(codec->dev, "i2c write failed\n");
+		return ret;
+	}
+
+	/* Disable automatic volume control.  The hardware enables, and it
+	 * causes volume change commands to be delayed, sometimes until after
+	 * playback has started.  An application (e.g. alsactl) can
+	 * re-enabled it by using the controls.
+	 */
+
+	reg = cs4270_read_reg_cache(codec, CS4270_TRANS);
+	reg &= ~(CS4270_TRANS_SOFT | CS4270_TRANS_ZERO);
+	ret = cs4270_i2c_write(codec, CS4270_TRANS, reg);
+	if (ret < 0) {
+		dev_err(codec->dev, "i2c write failed\n");
+		return ret;
+	}
+
+	/* Add the non-DAPM controls */
+	ret = snd_soc_add_controls(codec, cs4270_snd_controls,
+				ARRAY_SIZE(cs4270_snd_controls));
+	if (ret < 0) {
+		dev_err(codec->dev, "failed to add controls\n");
+		return ret;
+	}
+
+	/* get the power supply regulators */
+	for (i = 0; i < ARRAY_SIZE(supply_names); i++)
+		cs4270->supplies[i].supply = supply_names[i];
+
+	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(cs4270->supplies),
+				 cs4270->supplies);
+	if (ret < 0)
+		return ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(cs4270->supplies),
+				    cs4270->supplies);
+	if (ret < 0)
+		goto error_free_regulators;
+
+	return 0;
+
+error_free_regulators:
+	regulator_bulk_free(ARRAY_SIZE(cs4270->supplies),
+			    cs4270->supplies);
+
+	return ret;
+}
+
+/**
+ * cs4270_remove - ASoC remove function
+ * @pdev: platform device
+ *
+ * This function is the counterpart to cs4270_probe().
+ */
+static int cs4270_remove(struct snd_soc_codec *codec)
+{
+	struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+
+	regulator_bulk_disable(ARRAY_SIZE(cs4270->supplies), cs4270->supplies);
+	regulator_bulk_free(ARRAY_SIZE(cs4270->supplies), cs4270->supplies);
+
+	return 0;
+};
+
+#ifdef CONFIG_PM
+
+/* This suspend/resume implementation can handle both - a simple standby
+ * where the codec remains powered, and a full suspend, where the voltage
+ * domain the codec is connected to is teared down and/or any other hardware
+ * reset condition is asserted.
+ *
+ * The codec's own power saving features are enabled in the suspend callback,
+ * and all registers are written back to the hardware when resuming.
+ */
+
+static int cs4270_soc_suspend(struct snd_soc_codec *codec, pm_message_t mesg)
+{
+	struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+	int reg, ret;
+
+	reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL;
+	if (reg < 0)
+		return reg;
+
+	ret = snd_soc_write(codec, CS4270_PWRCTL, reg);
+	if (ret < 0)
+		return ret;
+
+	regulator_bulk_disable(ARRAY_SIZE(cs4270->supplies),
+			       cs4270->supplies);
+
+	return 0;
+}
+
+static int cs4270_soc_resume(struct snd_soc_codec *codec)
+{
+	struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec);
+	struct i2c_client *i2c_client = codec->control_data;
+	int reg;
+
+	regulator_bulk_enable(ARRAY_SIZE(cs4270->supplies),
+			      cs4270->supplies);
+
+	/* In case the device was put to hard reset during sleep, we need to
+	 * wait 500ns here before any I2C communication. */
+	ndelay(500);
+
+	/* first restore the entire register cache ... */
+	for (reg = CS4270_FIRSTREG; reg <= CS4270_LASTREG; reg++) {
+		u8 val = snd_soc_read(codec, reg);
+
+		if (i2c_smbus_write_byte_data(i2c_client, reg, val)) {
+			dev_err(codec->dev, "i2c write failed\n");
+			return -EIO;
+		}
+	}
+
+	/* ... then disable the power-down bits */
+	reg = snd_soc_read(codec, CS4270_PWRCTL);
+	reg &= ~CS4270_PWRCTL_PDN_ALL;
+
+	return snd_soc_write(codec, CS4270_PWRCTL, reg);
+}
+#else
+#define cs4270_soc_suspend	NULL
+#define cs4270_soc_resume	NULL
+#endif /* CONFIG_PM */
+
+/*
+ * ASoC codec device structure
+ *
+ * Assign this variable to the codec_dev field of the machine driver's
+ * snd_soc_device structure.
+ */
+static struct snd_soc_codec_driver soc_codec_device_cs4270 = {
+	.probe =	cs4270_probe,
+	.remove =	cs4270_remove,
+	.suspend =	cs4270_soc_suspend,
+	.resume =	cs4270_soc_resume,
+	.read = cs4270_read_reg_cache,
+	.write = cs4270_i2c_write,
+	.reg_cache_size = CS4270_NUMREGS,
+	.reg_word_size = sizeof(u8),
+};
+
+/**
+ * cs4270_i2c_probe - initialize the I2C interface of the CS4270
+ * @i2c_client: the I2C client object
+ * @id: the I2C device ID (ignored)
+ *
+ * This function is called whenever the I2C subsystem finds a device that
+ * matches the device ID given via a prior call to i2c_add_driver().
+ */
+static int cs4270_i2c_probe(struct i2c_client *i2c_client,
+	const struct i2c_device_id *id)
+{
+	struct cs4270_private *cs4270;
+	int ret;
+
+	/* Verify that we have a CS4270 */
+
+	ret = i2c_smbus_read_byte_data(i2c_client, CS4270_CHIPID);
+	if (ret < 0) {
+		dev_err(&i2c_client->dev, "failed to read i2c at addr %X\n",
+		       i2c_client->addr);
+		return ret;
+	}
+	/* The top four bits of the chip ID should be 1100. */
+	if ((ret & 0xF0) != 0xC0) {
+		dev_err(&i2c_client->dev, "device at addr %X is not a CS4270\n",
+		       i2c_client->addr);
+		return -ENODEV;
+	}
+
+	dev_info(&i2c_client->dev, "found device at i2c address %X\n",
+		i2c_client->addr);
+	dev_info(&i2c_client->dev, "hardware revision %X\n", ret & 0xF);
+
+	cs4270 = kzalloc(sizeof(struct cs4270_private), GFP_KERNEL);
+	if (!cs4270) {
+		dev_err(&i2c_client->dev, "could not allocate codec\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(i2c_client, cs4270);
+	cs4270->control_data = i2c_client;
+	cs4270->control_type = SND_SOC_I2C;
+
+	ret = snd_soc_register_codec(&i2c_client->dev,
+			&soc_codec_device_cs4270, &cs4270_dai, 1);
+	if (ret < 0)
+		kfree(cs4270);
+	return ret;
+}
+
+/**
+ * cs4270_i2c_remove - remove an I2C device
+ * @i2c_client: the I2C client object
+ *
+ * This function is the counterpart to cs4270_i2c_probe().
+ */
+static int cs4270_i2c_remove(struct i2c_client *i2c_client)
+{
+	snd_soc_unregister_codec(&i2c_client->dev);
+	kfree(i2c_get_clientdata(i2c_client));
+	return 0;
+}
+
+/*
+ * cs4270_id - I2C device IDs supported by this driver
+ */
+static struct i2c_device_id cs4270_id[] = {
+	{"cs4270", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, cs4270_id);
+
+/*
+ * cs4270_i2c_driver - I2C device identification
+ *
+ * This structure tells the I2C subsystem how to identify and support a
+ * given I2C device type.
+ */
+static struct i2c_driver cs4270_i2c_driver = {
+	.driver = {
+		.name = "cs4270-codec",
+		.owner = THIS_MODULE,
+	},
+	.id_table = cs4270_id,
+	.probe = cs4270_i2c_probe,
+	.remove = cs4270_i2c_remove,
+};
+
+static int __init cs4270_init(void)
+{
+	pr_info("Cirrus Logic CS4270 ALSA SoC Codec Driver\n");
+
+	return i2c_add_driver(&cs4270_i2c_driver);
+}
+module_init(cs4270_init);
+
+static void __exit cs4270_exit(void)
+{
+	i2c_del_driver(&cs4270_i2c_driver);
+}
+module_exit(cs4270_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Cirrus Logic CS4270 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/cs42l51.c b/linux/sound/soc/codecs/cs42l51.c
new file mode 100644
index 0000000..cb086ea
--- /dev/null
+++ b/linux/sound/soc/codecs/cs42l51.c
@@ -0,0 +1,654 @@
+/*
+ * cs42l51.c
+ *
+ * ASoC Driver for Cirrus Logic CS42L51 codecs
+ *
+ * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ *
+ * Based on cs4270.c - Copyright (c) Freescale Semiconductor
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * For now:
+ *  - Only I2C is support. Not SPI
+ *  - master mode *NOT* supported
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm.h>
+#include <linux/i2c.h>
+
+#include "cs42l51.h"
+
+enum master_slave_mode {
+	MODE_SLAVE,
+	MODE_SLAVE_AUTO,
+	MODE_MASTER,
+};
+
+struct cs42l51_private {
+	enum snd_soc_control_type control_type;
+	void *control_data;
+	unsigned int mclk;
+	unsigned int audio_mode;	/* The mode (I2S or left-justified) */
+	enum master_slave_mode func;
+	u8 reg_cache[CS42L51_NUMREGS];
+};
+
+#define CS42L51_FORMATS ( \
+		SNDRV_PCM_FMTBIT_S16_LE  | SNDRV_PCM_FMTBIT_S16_BE  | \
+		SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \
+		SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \
+		SNDRV_PCM_FMTBIT_S24_LE  | SNDRV_PCM_FMTBIT_S24_BE)
+
+static int cs42l51_fill_cache(struct snd_soc_codec *codec)
+{
+	u8 *cache = codec->reg_cache + 1;
+	struct i2c_client *i2c_client = codec->control_data;
+	s32 length;
+
+	length = i2c_smbus_read_i2c_block_data(i2c_client,
+			CS42L51_FIRSTREG | 0x80, CS42L51_NUMREGS, cache);
+	if (length != CS42L51_NUMREGS) {
+		dev_err(&i2c_client->dev,
+				"I2C read failure, addr=0x%x (ret=%d vs %d)\n",
+				i2c_client->addr, length, CS42L51_NUMREGS);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3;
+
+	switch (value) {
+	default:
+	case 0:
+		ucontrol->value.integer.value[0] = 0;
+		break;
+	/* same value : (L+R)/2 and (R+L)/2 */
+	case 1:
+	case 2:
+		ucontrol->value.integer.value[0] = 1;
+		break;
+	case 3:
+		ucontrol->value.integer.value[0] = 2;
+		break;
+	}
+
+	return 0;
+}
+
+#define CHAN_MIX_NORMAL	0x00
+#define CHAN_MIX_BOTH	0x55
+#define CHAN_MIX_SWAP	0xFF
+
+static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned char val;
+
+	switch (ucontrol->value.integer.value[0]) {
+	default:
+	case 0:
+		val = CHAN_MIX_NORMAL;
+		break;
+	case 1:
+		val = CHAN_MIX_BOTH;
+		break;
+	case 2:
+		val = CHAN_MIX_SWAP;
+		break;
+	}
+
+	snd_soc_write(codec, CS42L51_PCM_MIXER, val);
+
+	return 1;
+}
+
+static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0);
+static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0);
+/* This is a lie. after -102 db, it stays at -102 */
+/* maybe a range would be better */
+static const DECLARE_TLV_DB_SCALE(aout_tlv, -11550, 50, 0);
+
+static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0);
+static const char *chan_mix[] = {
+	"L R",
+	"L+R",
+	"R L",
+};
+
+static const struct soc_enum cs42l51_chan_mix =
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(chan_mix), chan_mix);
+
+static const struct snd_kcontrol_new cs42l51_snd_controls[] = {
+	SOC_DOUBLE_R_SX_TLV("PCM Playback Volume",
+			CS42L51_PCMA_VOL, CS42L51_PCMB_VOL,
+			7, 0xffffff99, 0x18, adc_pcm_tlv),
+	SOC_DOUBLE_R("PCM Playback Switch",
+			CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1),
+	SOC_DOUBLE_R_SX_TLV("Analog Playback Volume",
+			CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL,
+			8, 0xffffff19, 0x18, aout_tlv),
+	SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume",
+			CS42L51_ADCA_VOL, CS42L51_ADCB_VOL,
+			7, 0xffffff99, 0x18, adc_pcm_tlv),
+	SOC_DOUBLE_R("ADC Mixer Switch",
+			CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1),
+	SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0),
+	SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0),
+	SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0),
+	SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0),
+	SOC_DOUBLE_TLV("Mic Boost Volume",
+			CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv),
+	SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv),
+	SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv),
+	SOC_ENUM_EXT("PCM channel mixer",
+			cs42l51_chan_mix,
+			cs42l51_get_chan_mix, cs42l51_set_chan_mix),
+};
+
+/*
+ * to power down, one must:
+ * 1.) Enable the PDN bit
+ * 2.) enable power-down for the select channels
+ * 3.) disable the PDN bit.
+ */
+static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	unsigned long value;
+
+	value = snd_soc_read(w->codec, CS42L51_POWER_CTL1);
+	value &= ~CS42L51_POWER_CTL1_PDN;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMD:
+		value |= CS42L51_POWER_CTL1_PDN;
+		break;
+	default:
+	case SND_SOC_DAPM_POST_PMD:
+		break;
+	}
+	snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1,
+		CS42L51_POWER_CTL1_PDN, value);
+
+	return 0;
+}
+
+static const char *cs42l51_dac_names[] = {"Direct PCM",
+	"DSP PCM", "ADC"};
+static const struct soc_enum cs42l51_dac_mux_enum =
+	SOC_ENUM_SINGLE(CS42L51_DAC_CTL, 6, 3, cs42l51_dac_names);
+static const struct snd_kcontrol_new cs42l51_dac_mux_controls =
+	SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum);
+
+static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left",
+	"MIC Left", "MIC+preamp Left"};
+static const struct soc_enum cs42l51_adcl_mux_enum =
+	SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 4, 4, cs42l51_adcl_names);
+static const struct snd_kcontrol_new cs42l51_adcl_mux_controls =
+	SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum);
+
+static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right",
+	"MIC Right", "MIC+preamp Right"};
+static const struct soc_enum cs42l51_adcr_mux_enum =
+	SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 6, 4, cs42l51_adcr_names);
+static const struct snd_kcontrol_new cs42l51_adcr_mux_controls =
+	SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum);
+
+static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = {
+	SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1),
+	SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0,
+		cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+	SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0,
+		cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+	SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture",
+		CS42L51_POWER_CTL1, 1, 1,
+		cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+	SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture",
+		CS42L51_POWER_CTL1, 2, 1,
+		cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+	SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback",
+		CS42L51_POWER_CTL1, 5, 1,
+		cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+	SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback",
+		CS42L51_POWER_CTL1, 6, 1,
+		cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+
+	/* analog/mic */
+	SND_SOC_DAPM_INPUT("AIN1L"),
+	SND_SOC_DAPM_INPUT("AIN1R"),
+	SND_SOC_DAPM_INPUT("AIN2L"),
+	SND_SOC_DAPM_INPUT("AIN2R"),
+	SND_SOC_DAPM_INPUT("MICL"),
+	SND_SOC_DAPM_INPUT("MICR"),
+
+	SND_SOC_DAPM_MIXER("Mic Preamp Left",
+		CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0),
+	SND_SOC_DAPM_MIXER("Mic Preamp Right",
+		CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0),
+
+	/* HP */
+	SND_SOC_DAPM_OUTPUT("HPL"),
+	SND_SOC_DAPM_OUTPUT("HPR"),
+
+	/* mux */
+	SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0,
+		&cs42l51_dac_mux_controls),
+	SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0,
+		&cs42l51_adcl_mux_controls),
+	SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0,
+		&cs42l51_adcr_mux_controls),
+};
+
+static const struct snd_soc_dapm_route cs42l51_routes[] = {
+	{"HPL", NULL, "Left DAC"},
+	{"HPR", NULL, "Right DAC"},
+
+	{"Left ADC", NULL, "Left PGA"},
+	{"Right ADC", NULL, "Right PGA"},
+
+	{"Mic Preamp Left",  NULL,  "MICL"},
+	{"Mic Preamp Right", NULL,  "MICR"},
+
+	{"PGA-ADC Mux Left",  "AIN1 Left",        "AIN1L" },
+	{"PGA-ADC Mux Left",  "AIN2 Left",        "AIN2L" },
+	{"PGA-ADC Mux Left",  "MIC Left",         "MICL"  },
+	{"PGA-ADC Mux Left",  "MIC+preamp Left",  "Mic Preamp Left" },
+	{"PGA-ADC Mux Right", "AIN1 Right",       "AIN1R" },
+	{"PGA-ADC Mux Right", "AIN2 Right",       "AIN2R" },
+	{"PGA-ADC Mux Right", "MIC Right",        "MICR" },
+	{"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" },
+
+	{"Left PGA", NULL, "PGA-ADC Mux Left"},
+	{"Right PGA", NULL, "PGA-ADC Mux Right"},
+};
+
+static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int format)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+		cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
+		break;
+	default:
+		dev_err(codec->dev, "invalid DAI format\n");
+		ret = -EINVAL;
+	}
+
+	switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		cs42l51->func = MODE_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		cs42l51->func = MODE_SLAVE_AUTO;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+struct cs42l51_ratios {
+	unsigned int ratio;
+	unsigned char speed_mode;
+	unsigned char mclk;
+};
+
+static struct cs42l51_ratios slave_ratios[] = {
+	{  512, CS42L51_QSM_MODE, 0 }, {  768, CS42L51_QSM_MODE, 0 },
+	{ 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 },
+	{ 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 },
+	{  256, CS42L51_HSM_MODE, 0 }, {  384, CS42L51_HSM_MODE, 0 },
+	{  512, CS42L51_HSM_MODE, 0 }, {  768, CS42L51_HSM_MODE, 0 },
+	{ 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 },
+	{  128, CS42L51_SSM_MODE, 0 }, {  192, CS42L51_SSM_MODE, 0 },
+	{  256, CS42L51_SSM_MODE, 0 }, {  384, CS42L51_SSM_MODE, 0 },
+	{  512, CS42L51_SSM_MODE, 0 }, {  768, CS42L51_SSM_MODE, 0 },
+	{  128, CS42L51_DSM_MODE, 0 }, {  192, CS42L51_DSM_MODE, 0 },
+	{  256, CS42L51_DSM_MODE, 0 }, {  384, CS42L51_DSM_MODE, 0 },
+};
+
+static struct cs42l51_ratios slave_auto_ratios[] = {
+	{ 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 },
+	{ 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 },
+	{  512, CS42L51_HSM_MODE, 0 }, {  768, CS42L51_HSM_MODE, 0 },
+	{ 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 },
+	{  256, CS42L51_SSM_MODE, 0 }, {  384, CS42L51_SSM_MODE, 0 },
+	{  512, CS42L51_SSM_MODE, 1 }, {  768, CS42L51_SSM_MODE, 1 },
+	{  128, CS42L51_DSM_MODE, 0 }, {  192, CS42L51_DSM_MODE, 0 },
+	{  256, CS42L51_DSM_MODE, 1 }, {  384, CS42L51_DSM_MODE, 1 },
+};
+
+static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+
+	cs42l51->mclk = freq;
+	return 0;
+}
+
+static int cs42l51_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params,
+		struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+	unsigned int i;
+	unsigned int rate;
+	unsigned int ratio;
+	struct cs42l51_ratios *ratios = NULL;
+	int nr_ratios = 0;
+	int intf_ctl, power_ctl, fmt;
+
+	switch (cs42l51->func) {
+	case MODE_MASTER:
+		return -EINVAL;
+	case MODE_SLAVE:
+		ratios = slave_ratios;
+		nr_ratios = ARRAY_SIZE(slave_ratios);
+		break;
+	case MODE_SLAVE_AUTO:
+		ratios = slave_auto_ratios;
+		nr_ratios = ARRAY_SIZE(slave_auto_ratios);
+		break;
+	}
+
+	/* Figure out which MCLK/LRCK ratio to use */
+	rate = params_rate(params);     /* Sampling rate, in Hz */
+	ratio = cs42l51->mclk / rate;    /* MCLK/LRCK ratio */
+	for (i = 0; i < nr_ratios; i++) {
+		if (ratios[i].ratio == ratio)
+			break;
+	}
+
+	if (i == nr_ratios) {
+		/* We did not find a matching ratio */
+		dev_err(codec->dev, "could not find matching ratio\n");
+		return -EINVAL;
+	}
+
+	intf_ctl = snd_soc_read(codec, CS42L51_INTF_CTL);
+	power_ctl = snd_soc_read(codec, CS42L51_MIC_POWER_CTL);
+
+	intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S
+			| CS42L51_INTF_CTL_DAC_FORMAT(7));
+	power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3)
+			| CS42L51_MIC_POWER_CTL_MCLK_DIV2);
+
+	switch (cs42l51->func) {
+	case MODE_MASTER:
+		intf_ctl |= CS42L51_INTF_CTL_MASTER;
+		power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
+		break;
+	case MODE_SLAVE:
+		power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
+		break;
+	case MODE_SLAVE_AUTO:
+		power_ctl |= CS42L51_MIC_POWER_CTL_AUTO;
+		break;
+	}
+
+	switch (cs42l51->audio_mode) {
+	case SND_SOC_DAIFMT_I2S:
+		intf_ctl |= CS42L51_INTF_CTL_ADC_I2S;
+		intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S);
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24);
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		switch (params_format(params)) {
+		case SNDRV_PCM_FORMAT_S16_LE:
+		case SNDRV_PCM_FORMAT_S16_BE:
+			fmt = CS42L51_DAC_DIF_RJ16;
+			break;
+		case SNDRV_PCM_FORMAT_S18_3LE:
+		case SNDRV_PCM_FORMAT_S18_3BE:
+			fmt = CS42L51_DAC_DIF_RJ18;
+			break;
+		case SNDRV_PCM_FORMAT_S20_3LE:
+		case SNDRV_PCM_FORMAT_S20_3BE:
+			fmt = CS42L51_DAC_DIF_RJ20;
+			break;
+		case SNDRV_PCM_FORMAT_S24_LE:
+		case SNDRV_PCM_FORMAT_S24_BE:
+			fmt = CS42L51_DAC_DIF_RJ24;
+			break;
+		default:
+			dev_err(codec->dev, "unknown format\n");
+			return -EINVAL;
+		}
+		intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt);
+		break;
+	default:
+		dev_err(codec->dev, "unknown format\n");
+		return -EINVAL;
+	}
+
+	if (ratios[i].mclk)
+		power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2;
+
+	ret = snd_soc_write(codec, CS42L51_INTF_CTL, intf_ctl);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_write(codec, CS42L51_MIC_POWER_CTL, power_ctl);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	int reg;
+	int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE;
+
+	reg = snd_soc_read(codec, CS42L51_DAC_OUT_CTL);
+
+	if (mute)
+		reg |= mask;
+	else
+		reg &= ~mask;
+
+	return snd_soc_write(codec, CS42L51_DAC_OUT_CTL, reg);
+}
+
+static struct snd_soc_dai_ops cs42l51_dai_ops = {
+	.hw_params      = cs42l51_hw_params,
+	.set_sysclk     = cs42l51_set_dai_sysclk,
+	.set_fmt        = cs42l51_set_dai_fmt,
+	.digital_mute   = cs42l51_dai_mute,
+};
+
+static struct snd_soc_dai_driver cs42l51_dai = {
+	.name = "cs42l51-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = CS42L51_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = CS42L51_FORMATS,
+	},
+	.ops = &cs42l51_dai_ops,
+};
+
+static int cs42l51_probe(struct snd_soc_codec *codec)
+{
+	struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+	int ret, reg;
+
+	codec->control_data = cs42l51->control_data;
+
+	ret = cs42l51_fill_cache(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "failed to fill register cache\n");
+		return ret;
+	}
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 8, cs42l51->control_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * DAC configuration
+	 * - Use signal processor
+	 * - auto mute
+	 * - vol changes immediate
+	 * - no de-emphasize
+	 */
+	reg = CS42L51_DAC_CTL_DATA_SEL(1)
+		| CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0);
+	ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg);
+	if (ret < 0)
+		return ret;
+
+	snd_soc_add_controls(codec, cs42l51_snd_controls,
+		ARRAY_SIZE(cs42l51_snd_controls));
+	snd_soc_dapm_new_controls(codec, cs42l51_dapm_widgets,
+		ARRAY_SIZE(cs42l51_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, cs42l51_routes,
+		ARRAY_SIZE(cs42l51_routes));
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_device_cs42l51 = {
+	.probe =	cs42l51_probe,
+	.reg_cache_size = CS42L51_NUMREGS,
+	.reg_word_size = sizeof(u8),
+};
+
+static int cs42l51_i2c_probe(struct i2c_client *i2c_client,
+	const struct i2c_device_id *id)
+{
+	struct cs42l51_private *cs42l51;
+	int ret;
+
+	/* Verify that we have a CS42L51 */
+	ret = i2c_smbus_read_byte_data(i2c_client, CS42L51_CHIP_REV_ID);
+	if (ret < 0) {
+		dev_err(&i2c_client->dev, "failed to read I2C\n");
+		goto error;
+	}
+
+	if ((ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) &&
+	    (ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) {
+		dev_err(&i2c_client->dev, "Invalid chip id\n");
+		ret = -ENODEV;
+		goto error;
+	}
+
+	dev_info(&i2c_client->dev, "found device cs42l51 rev %d\n",
+				ret & 7);
+
+	cs42l51 = kzalloc(sizeof(struct cs42l51_private), GFP_KERNEL);
+	if (!cs42l51) {
+		dev_err(&i2c_client->dev, "could not allocate codec\n");
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(i2c_client, cs42l51);
+	cs42l51->control_data = i2c_client;
+	cs42l51->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c_client->dev,
+			&soc_codec_device_cs42l51, &cs42l51_dai, 1);
+	if (ret < 0)
+		kfree(cs42l51);
+error:
+	return ret;
+}
+
+static int cs42l51_i2c_remove(struct i2c_client *client)
+{
+	struct cs42l51_private *cs42l51 = i2c_get_clientdata(client);
+
+	snd_soc_unregister_codec(&client->dev);
+	kfree(cs42l51);
+	return 0;
+}
+
+static const struct i2c_device_id cs42l51_id[] = {
+	{"cs42l51", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, cs42l51_id);
+
+static struct i2c_driver cs42l51_i2c_driver = {
+	.driver = {
+		.name = "cs42l51-codec",
+		.owner = THIS_MODULE,
+	},
+	.id_table = cs42l51_id,
+	.probe = cs42l51_i2c_probe,
+	.remove = cs42l51_i2c_remove,
+};
+
+static int __init cs42l51_init(void)
+{
+	int ret;
+
+	ret = i2c_add_driver(&cs42l51_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "%s: can't add i2c driver\n", __func__);
+		return ret;
+	}
+	return 0;
+}
+module_init(cs42l51_init);
+
+static void __exit cs42l51_exit(void)
+{
+	i2c_del_driver(&cs42l51_i2c_driver);
+}
+module_exit(cs42l51_exit);
+
+MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
+MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/cs42l51.h b/linux/sound/soc/codecs/cs42l51.h
new file mode 100644
index 0000000..2beeb17
--- /dev/null
+++ b/linux/sound/soc/codecs/cs42l51.h
@@ -0,0 +1,161 @@
+/*
+ * cs42l51.h
+ *
+ * ASoC Driver for Cirrus Logic CS42L51 codecs
+ *
+ * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ *
+ * 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.
+ */
+#ifndef _CS42L51_H
+#define _CS42L51_H
+
+#define CS42L51_CHIP_ID			0x1B
+#define CS42L51_CHIP_REV_A		0x00
+#define CS42L51_CHIP_REV_B		0x01
+
+#define CS42L51_CHIP_REV_ID		0x01
+#define CS42L51_MK_CHIP_REV(a, b)	((a)<<3|(b))
+
+#define CS42L51_POWER_CTL1		0x02
+#define CS42L51_POWER_CTL1_PDN_DACB	(1<<6)
+#define CS42L51_POWER_CTL1_PDN_DACA	(1<<5)
+#define CS42L51_POWER_CTL1_PDN_PGAB	(1<<4)
+#define CS42L51_POWER_CTL1_PDN_PGAA	(1<<3)
+#define CS42L51_POWER_CTL1_PDN_ADCB	(1<<2)
+#define CS42L51_POWER_CTL1_PDN_ADCA	(1<<1)
+#define CS42L51_POWER_CTL1_PDN		(1<<0)
+
+#define CS42L51_MIC_POWER_CTL		0x03
+#define CS42L51_MIC_POWER_CTL_AUTO	(1<<7)
+#define CS42L51_MIC_POWER_CTL_SPEED(x)	(((x)&3)<<5)
+#define CS42L51_QSM_MODE		3
+#define CS42L51_HSM_MODE		2
+#define	CS42L51_SSM_MODE		1
+#define CS42L51_DSM_MODE		0
+#define CS42L51_MIC_POWER_CTL_3ST_SP	(1<<4)
+#define CS42L51_MIC_POWER_CTL_PDN_MICB	(1<<3)
+#define CS42L51_MIC_POWER_CTL_PDN_MICA	(1<<2)
+#define CS42L51_MIC_POWER_CTL_PDN_BIAS	(1<<1)
+#define CS42L51_MIC_POWER_CTL_MCLK_DIV2	(1<<0)
+
+#define CS42L51_INTF_CTL		0x04
+#define CS42L51_INTF_CTL_LOOPBACK	(1<<7)
+#define CS42L51_INTF_CTL_MASTER		(1<<6)
+#define CS42L51_INTF_CTL_DAC_FORMAT(x)	(((x)&7)<<3)
+#define CS42L51_DAC_DIF_LJ24		0x00
+#define CS42L51_DAC_DIF_I2S		0x01
+#define CS42L51_DAC_DIF_RJ24		0x02
+#define CS42L51_DAC_DIF_RJ20		0x03
+#define CS42L51_DAC_DIF_RJ18		0x04
+#define CS42L51_DAC_DIF_RJ16		0x05
+#define CS42L51_INTF_CTL_ADC_I2S	(1<<2)
+#define CS42L51_INTF_CTL_DIGMIX		(1<<1)
+#define CS42L51_INTF_CTL_MICMIX		(1<<0)
+
+#define CS42L51_MIC_CTL			0x05
+#define CS42L51_MIC_CTL_ADC_SNGVOL	(1<<7)
+#define CS42L51_MIC_CTL_ADCD_DBOOST	(1<<6)
+#define CS42L51_MIC_CTL_ADCA_DBOOST	(1<<5)
+#define CS42L51_MIC_CTL_MICBIAS_SEL	(1<<4)
+#define CS42L51_MIC_CTL_MICBIAS_LVL(x)	(((x)&3)<<2)
+#define CS42L51_MIC_CTL_MICB_BOOST	(1<<1)
+#define CS42L51_MIC_CTL_MICA_BOOST	(1<<0)
+
+#define CS42L51_ADC_CTL			0x06
+#define CS42L51_ADC_CTL_ADCB_HPFEN	(1<<7)
+#define CS42L51_ADC_CTL_ADCB_HPFRZ	(1<<6)
+#define CS42L51_ADC_CTL_ADCA_HPFEN	(1<<5)
+#define CS42L51_ADC_CTL_ADCA_HPFRZ	(1<<4)
+#define CS42L51_ADC_CTL_SOFTB		(1<<3)
+#define CS42L51_ADC_CTL_ZCROSSB		(1<<2)
+#define CS42L51_ADC_CTL_SOFTA		(1<<1)
+#define CS42L51_ADC_CTL_ZCROSSA		(1<<0)
+
+#define CS42L51_ADC_INPUT		0x07
+#define CS42L51_ADC_INPUT_AINB_MUX(x)	(((x)&3)<<6)
+#define CS42L51_ADC_INPUT_AINA_MUX(x)	(((x)&3)<<4)
+#define CS42L51_ADC_INPUT_INV_ADCB	(1<<3)
+#define CS42L51_ADC_INPUT_INV_ADCA	(1<<2)
+#define CS42L51_ADC_INPUT_ADCB_MUTE	(1<<1)
+#define CS42L51_ADC_INPUT_ADCA_MUTE	(1<<0)
+
+#define CS42L51_DAC_OUT_CTL		0x08
+#define CS42L51_DAC_OUT_CTL_HP_GAIN(x)	(((x)&7)<<5)
+#define CS42L51_DAC_OUT_CTL_DAC_SNGVOL	(1<<4)
+#define CS42L51_DAC_OUT_CTL_INV_PCMB	(1<<3)
+#define CS42L51_DAC_OUT_CTL_INV_PCMA	(1<<2)
+#define CS42L51_DAC_OUT_CTL_DACB_MUTE	(1<<1)
+#define CS42L51_DAC_OUT_CTL_DACA_MUTE	(1<<0)
+
+#define CS42L51_DAC_CTL			0x09
+#define CS42L51_DAC_CTL_DATA_SEL(x)	(((x)&3)<<6)
+#define CS42L51_DAC_CTL_FREEZE		(1<<5)
+#define CS42L51_DAC_CTL_DEEMPH		(1<<3)
+#define CS42L51_DAC_CTL_AMUTE		(1<<2)
+#define CS42L51_DAC_CTL_DACSZ(x)	(((x)&3)<<0)
+
+#define CS42L51_ALC_PGA_CTL		0x0A
+#define CS42L51_ALC_PGB_CTL		0x0B
+#define CS42L51_ALC_PGX_ALCX_SRDIS	(1<<7)
+#define CS42L51_ALC_PGX_ALCX_ZCDIS	(1<<6)
+#define CS42L51_ALC_PGX_PGX_VOL(x)	(((x)&0x1f)<<0)
+
+#define CS42L51_ADCA_ATT		0x0C
+#define CS42L51_ADCB_ATT		0x0D
+
+#define CS42L51_ADCA_VOL		0x0E
+#define CS42L51_ADCB_VOL		0x0F
+#define CS42L51_PCMA_VOL		0x10
+#define CS42L51_PCMB_VOL		0x11
+#define CS42L51_MIX_MUTE_ADCMIX		(1<<7)
+#define CS42L51_MIX_VOLUME(x)		(((x)&0x7f)<<0)
+
+#define CS42L51_BEEP_FREQ		0x12
+#define CS42L51_BEEP_VOL		0x13
+#define CS42L51_BEEP_CONF		0x14
+
+#define CS42L51_TONE_CTL		0x15
+#define CS42L51_TONE_CTL_TREB(x)	(((x)&0xf)<<4)
+#define CS42L51_TONE_CTL_BASS(x)	(((x)&0xf)<<0)
+
+#define CS42L51_AOUTA_VOL		0x16
+#define CS42L51_AOUTB_VOL		0x17
+#define CS42L51_PCM_MIXER		0x18
+#define CS42L51_LIMIT_THRES_DIS		0x19
+#define CS42L51_LIMIT_REL		0x1A
+#define CS42L51_LIMIT_ATT		0x1B
+#define CS42L51_ALC_EN			0x1C
+#define CS42L51_ALC_REL			0x1D
+#define CS42L51_ALC_THRES		0x1E
+#define CS42L51_NOISE_CONF		0x1F
+
+#define CS42L51_STATUS			0x20
+#define CS42L51_STATUS_SP_CLKERR	(1<<6)
+#define CS42L51_STATUS_SPEA_OVFL	(1<<5)
+#define CS42L51_STATUS_SPEB_OVFL	(1<<4)
+#define CS42L51_STATUS_PCMA_OVFL	(1<<3)
+#define CS42L51_STATUS_PCMB_OVFL	(1<<2)
+#define CS42L51_STATUS_ADCA_OVFL	(1<<1)
+#define CS42L51_STATUS_ADCB_OVFL	(1<<0)
+
+#define CS42L51_CHARGE_FREQ		0x21
+
+#define CS42L51_FIRSTREG	0x01
+/*
+ * Hack: with register 0x21, it makes 33 registers. Looks like someone in the
+ * i2c layer doesn't like i2c smbus block read of 33 regs. Workaround by using
+ * 32 regs
+ */
+#define CS42L51_LASTREG		0x20
+#define CS42L51_NUMREGS		(CS42L51_LASTREG - CS42L51_FIRSTREG + 1)
+
+#endif
diff --git a/linux/sound/soc/codecs/cx20442.c b/linux/sound/soc/codecs/cx20442.c
new file mode 100644
index 0000000..e8d27c8
--- /dev/null
+++ b/linux/sound/soc/codecs/cx20442.c
@@ -0,0 +1,415 @@
+/*
+ * cx20442.c  --  CX20442 ALSA Soc Audio driver
+ *
+ * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
+ *
+ * Initially based on sound/soc/codecs/wm8400.c
+ * Copyright 2008, 2009 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/tty.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/soc-dapm.h>
+
+#include "cx20442.h"
+
+
+struct cx20442_priv {
+	enum snd_soc_control_type control_type;
+	void *control_data;
+	u8 reg_cache[1];
+};
+
+#define CX20442_PM		0x0
+
+#define CX20442_TELIN		0
+#define CX20442_TELOUT		1
+#define CX20442_MIC		2
+#define CX20442_SPKOUT		3
+#define CX20442_AGC		4
+
+static const struct snd_soc_dapm_widget cx20442_dapm_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("TELOUT"),
+	SND_SOC_DAPM_OUTPUT("SPKOUT"),
+	SND_SOC_DAPM_OUTPUT("AGCOUT"),
+
+	SND_SOC_DAPM_MIXER("SPKOUT Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_PGA("TELOUT Amp", CX20442_PM, CX20442_TELOUT, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("SPKOUT Amp", CX20442_PM, CX20442_SPKOUT, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("SPKOUT AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
+
+	SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
+
+	SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_MICBIAS("TELIN Bias", CX20442_PM, CX20442_TELIN, 0),
+	SND_SOC_DAPM_MICBIAS("MIC Bias", CX20442_PM, CX20442_MIC, 0),
+
+	SND_SOC_DAPM_PGA("MIC AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
+
+	SND_SOC_DAPM_INPUT("TELIN"),
+	SND_SOC_DAPM_INPUT("MIC"),
+	SND_SOC_DAPM_INPUT("AGCIN"),
+};
+
+static const struct snd_soc_dapm_route cx20442_audio_map[] = {
+	{"TELOUT", NULL, "TELOUT Amp"},
+
+	{"SPKOUT", NULL, "SPKOUT Mixer"},
+	{"SPKOUT Mixer", NULL, "SPKOUT Amp"},
+
+	{"TELOUT Amp", NULL, "DAC"},
+	{"SPKOUT Amp", NULL, "DAC"},
+
+	{"SPKOUT Mixer", NULL, "SPKOUT AGC"},
+	{"SPKOUT AGC", NULL, "AGCIN"},
+
+	{"AGCOUT", NULL, "MIC AGC"},
+	{"MIC AGC", NULL, "MIC"},
+
+	{"MIC Bias", NULL, "MIC"},
+	{"Input Mixer", NULL, "MIC Bias"},
+
+	{"TELIN Bias", NULL, "TELIN"},
+	{"Input Mixer", NULL, "TELIN Bias"},
+
+	{"ADC", NULL, "Input Mixer"},
+};
+
+static int cx20442_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, cx20442_dapm_widgets,
+				  ARRAY_SIZE(cx20442_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, cx20442_audio_map,
+				ARRAY_SIZE(cx20442_audio_map));
+
+	return 0;
+}
+
+static unsigned int cx20442_read_reg_cache(struct snd_soc_codec *codec,
+							unsigned int reg)
+{
+	u8 *reg_cache = codec->reg_cache;
+
+	if (reg >= codec->driver->reg_cache_size)
+		return -EINVAL;
+
+	return reg_cache[reg];
+}
+
+enum v253_vls {
+	V253_VLS_NONE = 0,
+	V253_VLS_T,
+	V253_VLS_L,
+	V253_VLS_LT,
+	V253_VLS_S,
+	V253_VLS_ST,
+	V253_VLS_M,
+	V253_VLS_MST,
+	V253_VLS_S1,
+	V253_VLS_S1T,
+	V253_VLS_MS1T,
+	V253_VLS_M1,
+	V253_VLS_M1ST,
+	V253_VLS_M1S1T,
+	V253_VLS_H,
+	V253_VLS_HT,
+	V253_VLS_MS,
+	V253_VLS_MS1,
+	V253_VLS_M1S,
+	V253_VLS_M1S1,
+	V253_VLS_TEST,
+};
+
+static int cx20442_pm_to_v253_vls(u8 value)
+{
+	switch (value & ~(1 << CX20442_AGC)) {
+	case 0:
+		return V253_VLS_T;
+	case (1 << CX20442_SPKOUT):
+	case (1 << CX20442_MIC):
+	case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
+		return V253_VLS_M1S1;
+	case (1 << CX20442_TELOUT):
+	case (1 << CX20442_TELIN):
+	case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN):
+		return V253_VLS_L;
+	case (1 << CX20442_TELOUT) | (1 << CX20442_MIC):
+		return V253_VLS_NONE;
+	}
+	return -EINVAL;
+}
+static int cx20442_pm_to_v253_vsp(u8 value)
+{
+	switch (value & ~(1 << CX20442_AGC)) {
+	case (1 << CX20442_SPKOUT):
+	case (1 << CX20442_MIC):
+	case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
+		return (bool)(value & (1 << CX20442_AGC));
+	}
+	return (value & (1 << CX20442_AGC)) ? -EINVAL : 0;
+}
+
+static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg,
+							unsigned int value)
+{
+	struct cx20442_priv *cx20442 = snd_soc_codec_get_drvdata(codec);
+	u8 *reg_cache = codec->reg_cache;
+	int vls, vsp, old, len;
+	char buf[18];
+
+	if (reg >= codec->driver->reg_cache_size)
+		return -EINVAL;
+
+	/* hw_write and control_data pointers required for talking to the modem
+	 * are expected to be set by the line discipline initialization code */
+	if (!codec->hw_write || !cx20442->control_data)
+		return -EIO;
+
+	old = reg_cache[reg];
+	reg_cache[reg] = value;
+
+	vls = cx20442_pm_to_v253_vls(value);
+	if (vls < 0)
+		return vls;
+
+	vsp = cx20442_pm_to_v253_vsp(value);
+	if (vsp < 0)
+		return vsp;
+
+	if ((vls == V253_VLS_T) ||
+			(vls == cx20442_pm_to_v253_vls(old))) {
+		if (vsp == cx20442_pm_to_v253_vsp(old))
+			return 0;
+		len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp);
+	} else if (vsp == cx20442_pm_to_v253_vsp(old))
+		len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls);
+	else
+		len = snprintf(buf, ARRAY_SIZE(buf),
+					"at+vls=%d;+vsp=%d\r", vls, vsp);
+
+	if (unlikely(len > (ARRAY_SIZE(buf) - 1)))
+		return -ENOMEM;
+
+	dev_dbg(codec->dev, "%s: %s\n", __func__, buf);
+	if (codec->hw_write(cx20442->control_data, buf, len) != len)
+		return -EIO;
+
+	return 0;
+}
+
+
+/*
+ * Line discpline related code
+ *
+ * Any of the callback functions below can be used in two ways:
+ * 1) registerd by a machine driver as one of line discipline operations,
+ * 2) called from a machine's provided line discipline callback function
+ *    in case when extra machine specific code must be run as well.
+ */
+
+/* Modem init: echo off, digital speaker off, quiet off, voice mode */
+static const char *v253_init = "ate0m0q0+fclass=8\r";
+
+/* Line discipline .open() */
+static int v253_open(struct tty_struct *tty)
+{
+	int ret, len = strlen(v253_init);
+
+	/* Doesn't make sense without write callback */
+	if (!tty->ops->write)
+		return -EINVAL;
+
+	/* Won't work if no codec pointer has been passed by a card driver */
+	if (!tty->disc_data)
+		return -ENODEV;
+
+	if (tty->ops->write(tty, v253_init, len) != len) {
+		ret = -EIO;
+		goto err;
+	}
+	/* Actual setup will be performed after the modem responds. */
+	return 0;
+err:
+	tty->disc_data = NULL;
+	return ret;
+}
+
+/* Line discipline .close() */
+static void v253_close(struct tty_struct *tty)
+{
+	struct snd_soc_codec *codec = tty->disc_data;
+	struct cx20442_priv *cx20442;
+
+	tty->disc_data = NULL;
+
+	if (!codec)
+		return;
+
+	cx20442 = snd_soc_codec_get_drvdata(codec);
+
+	/* Prevent the codec driver from further accessing the modem */
+	codec->hw_write = NULL;
+	cx20442->control_data = NULL;
+	codec->pop_time = 0;
+}
+
+/* Line discipline .hangup() */
+static int v253_hangup(struct tty_struct *tty)
+{
+	v253_close(tty);
+	return 0;
+}
+
+/* Line discipline .receive_buf() */
+static void v253_receive(struct tty_struct *tty,
+				const unsigned char *cp, char *fp, int count)
+{
+	struct snd_soc_codec *codec = tty->disc_data;
+	struct cx20442_priv *cx20442;
+
+	if (!codec)
+		return;
+
+	cx20442 = snd_soc_codec_get_drvdata(codec);
+
+	if (!cx20442->control_data) {
+		/* First modem response, complete setup procedure */
+
+		/* Set up codec driver access to modem controls */
+		cx20442->control_data = tty;
+		codec->hw_write = (hw_write_t)tty->ops->write;
+		codec->pop_time = 1;
+	}
+}
+
+/* Line discipline .write_wakeup() */
+static void v253_wakeup(struct tty_struct *tty)
+{
+}
+
+struct tty_ldisc_ops v253_ops = {
+	.magic = TTY_LDISC_MAGIC,
+	.name = "cx20442",
+	.owner = THIS_MODULE,
+	.open = v253_open,
+	.close = v253_close,
+	.hangup = v253_hangup,
+	.receive_buf = v253_receive,
+	.write_wakeup = v253_wakeup,
+};
+EXPORT_SYMBOL_GPL(v253_ops);
+
+
+/*
+ * Codec DAI
+ */
+
+static struct snd_soc_dai_driver cx20442_dai = {
+	.name = "cx20442-voice",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+};
+
+static int cx20442_codec_probe(struct snd_soc_codec *codec)
+{
+	struct cx20442_priv *cx20442;
+
+	cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL);
+	if (cx20442 == NULL)
+		return -ENOMEM;
+	snd_soc_codec_set_drvdata(codec, cx20442);
+
+	cx20442_add_widgets(codec);
+
+	cx20442->control_data = NULL;
+	codec->hw_write = NULL;
+	codec->pop_time = 0;
+
+	return 0;
+}
+
+/* power down chip */
+static int cx20442_codec_remove(struct snd_soc_codec *codec)
+{
+	struct cx20442_priv *cx20442 = snd_soc_codec_get_drvdata(codec);
+
+	if (cx20442->control_data) {
+			struct tty_struct *tty = cx20442->control_data;
+			tty_hangup(tty);
+	}
+
+	kfree(cx20442);
+	return 0;
+}
+
+static struct snd_soc_codec_driver cx20442_codec_dev = {
+	.probe = 	cx20442_codec_probe,
+	.remove = 	cx20442_codec_remove,
+	.reg_cache_size = 1,
+	.reg_word_size = sizeof(u8),
+	.read = cx20442_read_reg_cache,
+	.write = cx20442_write,
+};
+
+static int cx20442_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&cx20442_codec_dev, &cx20442_dai, 1);
+}
+
+static int __exit cx20442_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver cx20442_platform_driver = {
+	.driver = {
+		.name = "cx20442-codec",
+		.owner = THIS_MODULE,
+		},
+	.probe = cx20442_platform_probe,
+	.remove = __exit_p(cx20442_platform_remove),
+};
+
+static int __init cx20442_init(void)
+{
+	return platform_driver_register(&cx20442_platform_driver);
+}
+module_init(cx20442_init);
+
+static void __exit cx20442_exit(void)
+{
+	platform_driver_unregister(&cx20442_platform_driver);
+}
+module_exit(cx20442_exit);
+
+MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver");
+MODULE_AUTHOR("Janusz Krzysztofik");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cx20442-codec");
diff --git a/linux/sound/soc/codecs/cx20442.h b/linux/sound/soc/codecs/cx20442.h
new file mode 100644
index 0000000..c7a7c79
--- /dev/null
+++ b/linux/sound/soc/codecs/cx20442.h
@@ -0,0 +1,18 @@
+/*
+ * cx20442.h  --  audio driver for CX20442
+ *
+ * Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
+ *
+ *  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.
+ *
+ */
+
+#ifndef _CX20442_CODEC_H
+#define _CX20442_CODEC_H
+
+extern struct tty_ldisc_ops v253_ops;
+
+#endif
diff --git a/linux/sound/soc/codecs/da7210.c b/linux/sound/soc/codecs/da7210.c
new file mode 100644
index 0000000..58bb9b9
--- /dev/null
+++ b/linux/sound/soc/codecs/da7210.c
@@ -0,0 +1,626 @@
+/*
+ * DA7210 ALSA Soc codec driver
+ *
+ * Copyright (c) 2009 Dialog Semiconductor
+ * Written by David Chen <Dajun.chen@diasemi.com>
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Cleanups by Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ * Tested on SuperH Ecovec24 board with S16/S24 LE in 48KHz using I2S
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+/* DA7210 register space */
+#define DA7210_STATUS			0x02
+#define DA7210_STARTUP1			0x03
+#define DA7210_MIC_L			0x07
+#define DA7210_MIC_R			0x08
+#define DA7210_INMIX_L			0x0D
+#define DA7210_INMIX_R			0x0E
+#define DA7210_ADC_HPF			0x0F
+#define DA7210_ADC			0x10
+#define DA7210_DAC_HPF			0x14
+#define DA7210_DAC_L			0x15
+#define DA7210_DAC_R			0x16
+#define DA7210_DAC_SEL			0x17
+#define DA7210_OUTMIX_L			0x1C
+#define DA7210_OUTMIX_R			0x1D
+#define DA7210_HP_L_VOL			0x21
+#define DA7210_HP_R_VOL			0x22
+#define DA7210_HP_CFG			0x23
+#define DA7210_DAI_SRC_SEL		0x25
+#define DA7210_DAI_CFG1			0x26
+#define DA7210_DAI_CFG3			0x28
+#define DA7210_PLL_DIV1			0x29
+#define DA7210_PLL_DIV2			0x2A
+#define DA7210_PLL_DIV3			0x2B
+#define DA7210_PLL			0x2C
+#define DA7210_A_HID_UNLOCK		0x8A
+#define DA7210_A_TEST_UNLOCK		0x8B
+#define DA7210_A_PLL1			0x90
+#define DA7210_A_CP_MODE		0xA7
+
+/* STARTUP1 bit fields */
+#define DA7210_SC_MST_EN		(1 << 0)
+
+/* MIC_L bit fields */
+#define DA7210_MICBIAS_EN		(1 << 6)
+#define DA7210_MIC_L_EN			(1 << 7)
+
+/* MIC_R bit fields */
+#define DA7210_MIC_R_EN			(1 << 7)
+
+/* INMIX_L bit fields */
+#define DA7210_IN_L_EN			(1 << 7)
+
+/* INMIX_R bit fields */
+#define DA7210_IN_R_EN			(1 << 7)
+
+/* ADC bit fields */
+#define DA7210_ADC_L_EN			(1 << 3)
+#define DA7210_ADC_R_EN			(1 << 7)
+
+/* DAC/ADC HPF fields */
+#define DA7210_VOICE_F0_MASK		(0x7 << 4)
+#define DA7210_VOICE_F0_25		(1 << 4)
+#define DA7210_VOICE_EN			(1 << 7)
+
+/* DAC_SEL bit fields */
+#define DA7210_DAC_L_SRC_DAI_L		(4 << 0)
+#define DA7210_DAC_L_EN			(1 << 3)
+#define DA7210_DAC_R_SRC_DAI_R		(5 << 4)
+#define DA7210_DAC_R_EN			(1 << 7)
+
+/* OUTMIX_L bit fields */
+#define DA7210_OUT_L_EN			(1 << 7)
+
+/* OUTMIX_R bit fields */
+#define DA7210_OUT_R_EN			(1 << 7)
+
+/* HP_CFG bit fields */
+#define DA7210_HP_2CAP_MODE		(1 << 1)
+#define DA7210_HP_SENSE_EN		(1 << 2)
+#define DA7210_HP_L_EN			(1 << 3)
+#define DA7210_HP_MODE			(1 << 6)
+#define DA7210_HP_R_EN			(1 << 7)
+
+/* DAI_SRC_SEL bit fields */
+#define DA7210_DAI_OUT_L_SRC		(6 << 0)
+#define DA7210_DAI_OUT_R_SRC		(7 << 4)
+
+/* DAI_CFG1 bit fields */
+#define DA7210_DAI_WORD_S16_LE		(0 << 0)
+#define DA7210_DAI_WORD_S24_LE		(2 << 0)
+#define DA7210_DAI_FLEN_64BIT		(1 << 2)
+#define DA7210_DAI_MODE_MASTER		(1 << 7)
+
+/* DAI_CFG3 bit fields */
+#define DA7210_DAI_FORMAT_I2SMODE	(0 << 0)
+#define DA7210_DAI_OE			(1 << 3)
+#define DA7210_DAI_EN			(1 << 7)
+
+/*PLL_DIV3 bit fields */
+#define DA7210_MCLK_RANGE_10_20_MHZ	(1 << 4)
+#define DA7210_PLL_BYP			(1 << 6)
+
+/* PLL bit fields */
+#define DA7210_PLL_FS_MASK		(0xF << 0)
+#define DA7210_PLL_FS_8000		(0x1 << 0)
+#define DA7210_PLL_FS_11025		(0x2 << 0)
+#define DA7210_PLL_FS_12000		(0x3 << 0)
+#define DA7210_PLL_FS_16000		(0x5 << 0)
+#define DA7210_PLL_FS_22050		(0x6 << 0)
+#define DA7210_PLL_FS_24000		(0x7 << 0)
+#define DA7210_PLL_FS_32000		(0x9 << 0)
+#define DA7210_PLL_FS_44100		(0xA << 0)
+#define DA7210_PLL_FS_48000		(0xB << 0)
+#define DA7210_PLL_FS_88200		(0xE << 0)
+#define DA7210_PLL_FS_96000		(0xF << 0)
+#define DA7210_PLL_EN			(0x1 << 7)
+
+#define DA7210_VERSION "0.0.1"
+
+/*
+ * Playback Volume
+ *
+ * max		: 0x3F (+15.0 dB)
+ *		   (1.5 dB step)
+ * min		: 0x11 (-54.0 dB)
+ * mute		: 0x10
+ * reserved	: 0x00 - 0x0F
+ *
+ * ** FIXME **
+ *
+ * Reserved area are considered as "mute".
+ * -> min = -79.5 dB
+ */
+static const DECLARE_TLV_DB_SCALE(hp_out_tlv, -7950, 150, 1);
+
+static const struct snd_kcontrol_new da7210_snd_controls[] = {
+
+	SOC_DOUBLE_R_TLV("HeadPhone Playback Volume",
+			 DA7210_HP_L_VOL, DA7210_HP_R_VOL,
+			 0, 0x3F, 0, hp_out_tlv),
+};
+
+/* Codec private data */
+struct da7210_priv {
+	enum snd_soc_control_type control_type;
+	void *control_data;
+};
+
+/*
+ * Register cache
+ */
+static const u8 da7210_reg[] = {
+	0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* R0  - R7  */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,	/* R8  - RF  */
+	0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x10, 0x54,	/* R10 - R17 */
+	0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* R18 - R1F */
+	0x00, 0x00, 0x00, 0x02, 0x00, 0x76, 0x00, 0x00,	/* R20 - R27 */
+	0x04, 0x00, 0x00, 0x30, 0x2A, 0x00, 0x40, 0x00,	/* R28 - R2F */
+	0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00,	/* R30 - R37 */
+	0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00,	/* R38 - R3F */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* R40 - R4F */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* R48 - R4F */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* R50 - R57 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* R58 - R5F */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* R60 - R67 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* R68 - R6F */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* R70 - R77 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x54, 0x00,	/* R78 - R7F */
+	0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00,	/* R80 - R87 */
+	0x00,						/* R88       */
+};
+
+/*
+ * Read da7210 register cache
+ */
+static inline u32 da7210_read_reg_cache(struct snd_soc_codec *codec, u32 reg)
+{
+	u8 *cache = codec->reg_cache;
+	BUG_ON(reg >= ARRAY_SIZE(da7210_reg));
+	return cache[reg];
+}
+
+/*
+ * Write to the da7210 register space
+ */
+static int da7210_write(struct snd_soc_codec *codec, u32 reg, u32 value)
+{
+	u8 *cache = codec->reg_cache;
+	u8 data[2];
+
+	BUG_ON(codec->driver->volatile_register);
+
+	data[0] = reg & 0xff;
+	data[1] = value & 0xff;
+
+	if (reg >= codec->driver->reg_cache_size)
+		return -EIO;
+
+	if (2 != codec->hw_write(codec->control_data, data, 2))
+		return -EIO;
+
+	cache[reg] = value;
+	return 0;
+}
+
+/*
+ * Read from the da7210 register space.
+ */
+static inline u32 da7210_read(struct snd_soc_codec *codec, u32 reg)
+{
+	if (DA7210_STATUS == reg)
+		return i2c_smbus_read_byte_data(codec->control_data, reg);
+
+	return da7210_read_reg_cache(codec, reg);
+}
+
+static int da7210_startup(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	struct snd_soc_codec *codec = dai->codec;
+
+	if (is_play) {
+		/* Enable Out */
+		snd_soc_update_bits(codec, DA7210_OUTMIX_L, 0x1F, 0x10);
+		snd_soc_update_bits(codec, DA7210_OUTMIX_R, 0x1F, 0x10);
+
+	} else {
+		/* Volume 7 */
+		snd_soc_update_bits(codec, DA7210_MIC_L, 0x7, 0x7);
+		snd_soc_update_bits(codec, DA7210_MIC_R, 0x7, 0x7);
+
+		/* Enable Mic */
+		snd_soc_update_bits(codec, DA7210_INMIX_L, 0x1F, 0x1);
+		snd_soc_update_bits(codec, DA7210_INMIX_R, 0x1F, 0x1);
+	}
+
+	return 0;
+}
+
+/*
+ * Set PCM DAI word length.
+ */
+static int da7210_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	u32 dai_cfg1;
+	u32 hpf_reg, hpf_mask, hpf_value;
+	u32 fs, bypass;
+
+	/* set DAI source to Left and Right ADC */
+	da7210_write(codec, DA7210_DAI_SRC_SEL,
+		     DA7210_DAI_OUT_R_SRC | DA7210_DAI_OUT_L_SRC);
+
+	/* Enable DAI */
+	da7210_write(codec, DA7210_DAI_CFG3, DA7210_DAI_OE | DA7210_DAI_EN);
+
+	dai_cfg1 = 0xFC & da7210_read(codec, DA7210_DAI_CFG1);
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		dai_cfg1 |= DA7210_DAI_WORD_S16_LE;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		dai_cfg1 |= DA7210_DAI_WORD_S24_LE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1);
+
+	hpf_reg = (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) ?
+		DA7210_DAC_HPF : DA7210_ADC_HPF;
+
+	switch (params_rate(params)) {
+	case 8000:
+		fs		= DA7210_PLL_FS_8000;
+		hpf_mask	= DA7210_VOICE_F0_MASK	| DA7210_VOICE_EN;
+		hpf_value	= DA7210_VOICE_F0_25	| DA7210_VOICE_EN;
+		bypass		= DA7210_PLL_BYP;
+		break;
+	case 11025:
+		fs		= DA7210_PLL_FS_11025;
+		hpf_mask	= DA7210_VOICE_F0_MASK	| DA7210_VOICE_EN;
+		hpf_value	= DA7210_VOICE_F0_25	| DA7210_VOICE_EN;
+		bypass		= 0;
+		break;
+	case 12000:
+		fs		= DA7210_PLL_FS_12000;
+		hpf_mask	= DA7210_VOICE_F0_MASK	| DA7210_VOICE_EN;
+		hpf_value	= DA7210_VOICE_F0_25	| DA7210_VOICE_EN;
+		bypass		= DA7210_PLL_BYP;
+		break;
+	case 16000:
+		fs		= DA7210_PLL_FS_16000;
+		hpf_mask	= DA7210_VOICE_F0_MASK	| DA7210_VOICE_EN;
+		hpf_value	= DA7210_VOICE_F0_25	| DA7210_VOICE_EN;
+		bypass		= DA7210_PLL_BYP;
+		break;
+	case 22050:
+		fs		= DA7210_PLL_FS_22050;
+		hpf_mask	= DA7210_VOICE_EN;
+		hpf_value	= 0;
+		bypass		= 0;
+		break;
+	case 32000:
+		fs		= DA7210_PLL_FS_32000;
+		hpf_mask	= DA7210_VOICE_EN;
+		hpf_value	= 0;
+		bypass		= DA7210_PLL_BYP;
+		break;
+	case 44100:
+		fs		= DA7210_PLL_FS_44100;
+		hpf_mask	= DA7210_VOICE_EN;
+		hpf_value	= 0;
+		bypass		= 0;
+		break;
+	case 48000:
+		fs		= DA7210_PLL_FS_48000;
+		hpf_mask	= DA7210_VOICE_EN;
+		hpf_value	= 0;
+		bypass		= DA7210_PLL_BYP;
+		break;
+	case 88200:
+		fs		= DA7210_PLL_FS_88200;
+		hpf_mask	= DA7210_VOICE_EN;
+		hpf_value	= 0;
+		bypass		= 0;
+		break;
+	case 96000:
+		fs		= DA7210_PLL_FS_96000;
+		hpf_mask	= DA7210_VOICE_EN;
+		hpf_value	= 0;
+		bypass		= DA7210_PLL_BYP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Disable active mode */
+	snd_soc_update_bits(codec, DA7210_STARTUP1, DA7210_SC_MST_EN, 0);
+
+	snd_soc_update_bits(codec, hpf_reg, hpf_mask, hpf_value);
+	snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_FS_MASK, fs);
+	snd_soc_update_bits(codec, DA7210_PLL_DIV3, DA7210_PLL_BYP, bypass);
+
+	/* Enable active mode */
+	snd_soc_update_bits(codec, DA7210_STARTUP1,
+			    DA7210_SC_MST_EN, DA7210_SC_MST_EN);
+
+	return 0;
+}
+
+/*
+ * Set DAI mode and Format
+ */
+static int da7210_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u32 dai_cfg1;
+	u32 dai_cfg3;
+
+	dai_cfg1 = 0x7f & da7210_read(codec, DA7210_DAI_CFG1);
+	dai_cfg3 = 0xfc & da7210_read(codec, DA7210_DAI_CFG3);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		dai_cfg1 |= DA7210_DAI_MODE_MASTER;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* FIXME
+	 *
+	 * It support I2S only now
+	 */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		dai_cfg3 |= DA7210_DAI_FORMAT_I2SMODE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* FIXME
+	 *
+	 * It support 64bit data transmission only now
+	 */
+	dai_cfg1 |= DA7210_DAI_FLEN_64BIT;
+
+	da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1);
+	da7210_write(codec, DA7210_DAI_CFG3, dai_cfg3);
+
+	return 0;
+}
+
+#define DA7210_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+/* DAI operations */
+static struct snd_soc_dai_ops da7210_dai_ops = {
+	.startup	= da7210_startup,
+	.hw_params	= da7210_hw_params,
+	.set_fmt	= da7210_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver da7210_dai = {
+	.name = "da7210-hifi",
+	/* playback capabilities */
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = DA7210_FORMATS,
+	},
+	/* capture capabilities */
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = DA7210_FORMATS,
+	},
+	.ops = &da7210_dai_ops,
+	.symmetric_rates = 1,
+};
+
+static int da7210_probe(struct snd_soc_codec *codec)
+{
+	struct da7210_priv *da7210 = snd_soc_codec_get_drvdata(codec);
+
+	dev_info(codec->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION);
+
+	codec->control_data	= da7210->control_data;
+	codec->hw_write		= (hw_write_t)i2c_master_send;
+
+	/* FIXME
+	 *
+	 * This driver use fixed value here
+	 * And below settings expects MCLK = 12.288MHz
+	 *
+	 * When you select different MCLK, please check...
+	 *      DA7210_PLL_DIV1 val
+	 *      DA7210_PLL_DIV2 val
+	 *      DA7210_PLL_DIV3 val
+	 *      DA7210_PLL_DIV3 :: DA7210_MCLK_RANGExxx
+	 */
+
+	/*
+	 * make sure that DA7210 use bypass mode before start up
+	 */
+	da7210_write(codec, DA7210_STARTUP1, 0);
+	da7210_write(codec, DA7210_PLL_DIV3,
+		     DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP);
+
+	/*
+	 * ADC settings
+	 */
+
+	/* Enable Left & Right MIC PGA and Mic Bias */
+	da7210_write(codec, DA7210_MIC_L, DA7210_MIC_L_EN | DA7210_MICBIAS_EN);
+	da7210_write(codec, DA7210_MIC_R, DA7210_MIC_R_EN);
+
+	/* Enable Left and Right input PGA */
+	da7210_write(codec, DA7210_INMIX_L, DA7210_IN_L_EN);
+	da7210_write(codec, DA7210_INMIX_R, DA7210_IN_R_EN);
+
+	/* Enable Left and Right ADC */
+	da7210_write(codec, DA7210_ADC, DA7210_ADC_L_EN | DA7210_ADC_R_EN);
+
+	/*
+	 * DAC settings
+	 */
+
+	/* Enable Left and Right DAC */
+	da7210_write(codec, DA7210_DAC_SEL,
+		     DA7210_DAC_L_SRC_DAI_L | DA7210_DAC_L_EN |
+		     DA7210_DAC_R_SRC_DAI_R | DA7210_DAC_R_EN);
+
+	/* Enable Left and Right out PGA */
+	da7210_write(codec, DA7210_OUTMIX_L, DA7210_OUT_L_EN);
+	da7210_write(codec, DA7210_OUTMIX_R, DA7210_OUT_R_EN);
+
+	/* Enable Left and Right HeadPhone PGA */
+	da7210_write(codec, DA7210_HP_CFG,
+		     DA7210_HP_2CAP_MODE | DA7210_HP_SENSE_EN |
+		     DA7210_HP_L_EN | DA7210_HP_MODE | DA7210_HP_R_EN);
+
+	/* Diable PLL and bypass it */
+	da7210_write(codec, DA7210_PLL, DA7210_PLL_FS_48000);
+
+	/*
+	 * If 48kHz sound came, it use bypass mode,
+	 * and when it is 44.1kHz, it use PLL.
+	 *
+	 * This time, this driver sets PLL always ON
+	 * and controls bypass/PLL mode by switching
+	 * DA7210_PLL_DIV3 :: DA7210_PLL_BYP bit.
+	 *   see da7210_hw_params
+	 */
+	da7210_write(codec, DA7210_PLL_DIV1, 0xE5); /* MCLK = 12.288MHz */
+	da7210_write(codec, DA7210_PLL_DIV2, 0x99);
+	da7210_write(codec, DA7210_PLL_DIV3, 0x0A |
+		     DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP);
+	snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_EN, DA7210_PLL_EN);
+
+	/* As suggested by Dialog */
+	da7210_write(codec, DA7210_A_HID_UNLOCK,	0x8B); /* unlock */
+	da7210_write(codec, DA7210_A_TEST_UNLOCK,	0xB4);
+	da7210_write(codec, DA7210_A_PLL1,		0x01);
+	da7210_write(codec, DA7210_A_CP_MODE,		0x7C);
+	da7210_write(codec, DA7210_A_HID_UNLOCK,	0x00); /* re-lock */
+	da7210_write(codec, DA7210_A_TEST_UNLOCK,	0x00);
+
+	/* Activate all enabled subsystem */
+	da7210_write(codec, DA7210_STARTUP1, DA7210_SC_MST_EN);
+
+	snd_soc_add_controls(codec, da7210_snd_controls,
+			     ARRAY_SIZE(da7210_snd_controls));
+
+	dev_info(codec->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_da7210 = {
+	.probe			= da7210_probe,
+	.read			= da7210_read,
+	.write			= da7210_write,
+	.reg_cache_size		= ARRAY_SIZE(da7210_reg),
+	.reg_word_size		= sizeof(u8),
+	.reg_cache_default	= da7210_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int __devinit da7210_i2c_probe(struct i2c_client *i2c,
+			   	      const struct i2c_device_id *id)
+{
+	struct da7210_priv *da7210;
+	int ret;
+
+	da7210 = kzalloc(sizeof(struct da7210_priv), GFP_KERNEL);
+	if (!da7210)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, da7210);
+	da7210->control_data = i2c;
+	da7210->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_da7210, &da7210_dai, 1);
+	if (ret < 0)
+		kfree(da7210);
+
+	return ret;
+}
+
+static int __devexit da7210_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id da7210_i2c_id[] = {
+	{ "da7210", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, da7210_i2c_id);
+
+/* I2C codec control layer */
+static struct i2c_driver da7210_i2c_driver = {
+	.driver = {
+		.name = "da7210-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe		= da7210_i2c_probe,
+	.remove		= __devexit_p(da7210_i2c_remove),
+	.id_table	= da7210_i2c_id,
+};
+#endif
+
+static int __init da7210_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&da7210_i2c_driver);
+#endif
+	return ret;
+}
+module_init(da7210_modinit);
+
+static void __exit da7210_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&da7210_i2c_driver);
+#endif
+}
+module_exit(da7210_exit);
+
+MODULE_DESCRIPTION("ASoC DA7210 driver");
+MODULE_AUTHOR("David Chen, Kuninori Morimoto");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/jz4740.c b/linux/sound/soc/codecs/jz4740.c
new file mode 100644
index 0000000..16253ec
--- /dev/null
+++ b/linux/sound/soc/codecs/jz4740.c
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc-dapm.h>
+#include <sound/soc.h>
+
+#define JZ4740_REG_CODEC_1 0x0
+#define JZ4740_REG_CODEC_2 0x1
+
+#define JZ4740_CODEC_1_LINE_ENABLE BIT(29)
+#define JZ4740_CODEC_1_MIC_ENABLE BIT(28)
+#define JZ4740_CODEC_1_SW1_ENABLE BIT(27)
+#define JZ4740_CODEC_1_ADC_ENABLE BIT(26)
+#define JZ4740_CODEC_1_SW2_ENABLE BIT(25)
+#define JZ4740_CODEC_1_DAC_ENABLE BIT(24)
+#define JZ4740_CODEC_1_VREF_DISABLE BIT(20)
+#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19)
+#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18)
+#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17)
+#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16)
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14)
+#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13)
+#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12)
+#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10))
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9)
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8)
+#define JZ4740_CODEC_1_SUSPEND BIT(1)
+#define JZ4740_CODEC_1_RESET BIT(0)
+
+#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29
+#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28
+#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27
+#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26
+#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25
+#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_MASK		0x1f0000
+#define JZ4740_CODEC_2_SAMPLE_RATE_MASK			0x000f00
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK		0x000030
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK	0x000003
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET		16
+#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET		 8
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET	 4
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET	 0
+
+static const uint32_t jz4740_codec_regs[] = {
+	0x021b2302, 0x00170803,
+};
+
+struct jz4740_codec {
+	void __iomem *base;
+	struct resource *mem;
+};
+
+static unsigned int jz4740_codec_read(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	struct jz4740_codec *jz4740_codec = snd_soc_codec_get_drvdata(codec);
+	return readl(jz4740_codec->base + (reg << 2));
+}
+
+static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int val)
+{
+	struct jz4740_codec *jz4740_codec = snd_soc_codec_get_drvdata(codec);
+	u32 *cache = codec->reg_cache;
+
+	cache[reg] = val;
+	writel(val, jz4740_codec->base + (reg << 2));
+
+	return 0;
+}
+
+static const struct snd_kcontrol_new jz4740_codec_controls[] = {
+	SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2,
+			JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0),
+	SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2,
+			JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0),
+	SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1,
+			JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1),
+	SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2,
+			JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_output_controls[] = {
+	SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1,
+			JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0),
+	SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1,
+			JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_input_controls[] = {
+	SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1,
+			JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0),
+	SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1,
+			JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = {
+	SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1,
+			JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0),
+	SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1,
+			JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0),
+
+	SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1,
+			JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1,
+			jz4740_codec_output_controls,
+			ARRAY_SIZE(jz4740_codec_output_controls)),
+
+	SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0,
+			jz4740_codec_input_controls,
+			ARRAY_SIZE(jz4740_codec_input_controls)),
+	SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_OUTPUT("LOUT"),
+	SND_SOC_DAPM_OUTPUT("ROUT"),
+
+	SND_SOC_DAPM_INPUT("MIC"),
+	SND_SOC_DAPM_INPUT("LIN"),
+	SND_SOC_DAPM_INPUT("RIN"),
+};
+
+static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = {
+	{"Line Input", NULL, "LIN"},
+	{"Line Input", NULL, "RIN"},
+
+	{"Input Mixer", "Line Capture Switch", "Line Input"},
+	{"Input Mixer", "Mic Capture Switch", "MIC"},
+
+	{"ADC", NULL, "Input Mixer"},
+
+	{"Output Mixer", "Bypass Switch", "Input Mixer"},
+	{"Output Mixer", "DAC Switch", "DAC"},
+
+	{"LOUT", NULL, "Output Mixer"},
+	{"ROUT", NULL, "Output Mixer"},
+};
+
+static int jz4740_codec_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	uint32_t val;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec =rtd->codec;
+
+	switch (params_rate(params)) {
+	case 8000:
+		val = 0;
+		break;
+	case 11025:
+		val = 1;
+		break;
+	case 12000:
+		val = 2;
+		break;
+	case 16000:
+		val = 3;
+		break;
+	case 22050:
+		val = 4;
+		break;
+	case 24000:
+		val = 5;
+		break;
+	case 32000:
+		val = 6;
+		break;
+	case 44100:
+		val = 7;
+		break;
+	case 48000:
+		val = 8;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET;
+
+	snd_soc_update_bits(codec, JZ4740_REG_CODEC_2,
+				JZ4740_CODEC_2_SAMPLE_RATE_MASK, val);
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops jz4740_codec_dai_ops = {
+	.hw_params = jz4740_codec_hw_params,
+};
+
+static struct snd_soc_dai_driver jz4740_codec_dai = {
+	.name = "jz4740-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+	},
+	.ops = &jz4740_codec_dai_ops,
+	.symmetric_rates = 1,
+};
+
+static void jz4740_codec_wakeup(struct snd_soc_codec *codec)
+{
+	int i;
+	uint32_t *cache = codec->reg_cache;
+
+	snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+		JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET);
+	udelay(2);
+
+	snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+		JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0);
+
+	for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i)
+		jz4740_codec_write(codec, i, cache[i]);
+}
+
+static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec,
+	enum snd_soc_bias_level level)
+{
+	unsigned int mask;
+	unsigned int value;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		mask = JZ4740_CODEC_1_VREF_DISABLE |
+				JZ4740_CODEC_1_VREF_AMP_DISABLE |
+				JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+		value = 0;
+
+		snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		/* The only way to clear the suspend flag is to reset the codec */
+		if (codec->bias_level == SND_SOC_BIAS_OFF)
+			jz4740_codec_wakeup(codec);
+
+		mask = JZ4740_CODEC_1_VREF_DISABLE |
+			JZ4740_CODEC_1_VREF_AMP_DISABLE |
+			JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+		value = JZ4740_CODEC_1_VREF_DISABLE |
+			JZ4740_CODEC_1_VREF_AMP_DISABLE |
+			JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+
+		snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+		break;
+	case SND_SOC_BIAS_OFF:
+		mask = JZ4740_CODEC_1_SUSPEND;
+		value = JZ4740_CODEC_1_SUSPEND;
+
+		snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+		break;
+	default:
+		break;
+	}
+
+	codec->bias_level = level;
+
+	return 0;
+}
+
+static int jz4740_codec_dev_probe(struct snd_soc_codec *codec)
+{
+	snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+			JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE);
+
+	snd_soc_add_controls(codec, jz4740_codec_controls,
+		ARRAY_SIZE(jz4740_codec_controls));
+
+	snd_soc_dapm_new_controls(codec, jz4740_codec_dapm_widgets,
+		ARRAY_SIZE(jz4740_codec_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, jz4740_codec_dapm_routes,
+		ARRAY_SIZE(jz4740_codec_dapm_routes));
+
+	snd_soc_dapm_new_widgets(codec);
+
+	jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static int jz4740_codec_dev_remove(struct snd_soc_codec *codec)
+{
+	jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int jz4740_codec_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static int jz4740_codec_resume(struct snd_soc_codec *codec)
+{
+	return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+}
+
+#else
+#define jz4740_codec_suspend NULL
+#define jz4740_codec_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_jz4740_codec = {
+	.probe = jz4740_codec_dev_probe,
+	.remove = jz4740_codec_dev_remove,
+	.suspend = jz4740_codec_suspend,
+	.resume = jz4740_codec_resume,
+	.read = jz4740_codec_read,
+	.write = jz4740_codec_write,
+	.set_bias_level = jz4740_codec_set_bias_level,
+	.reg_cache_default	= jz4740_codec_regs,
+	.reg_word_size = sizeof(u32),
+	.reg_cache_size	= 2,
+};
+
+static int __devinit jz4740_codec_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct jz4740_codec *jz4740_codec;
+	struct resource *mem;
+
+	jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL);
+	if (!jz4740_codec)
+		return -ENOMEM;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem) {
+		dev_err(&pdev->dev, "Failed to get mmio memory resource\n");
+		ret = -ENOENT;
+		goto err_free_codec;
+	}
+
+	mem = request_mem_region(mem->start, resource_size(mem), pdev->name);
+	if (!mem) {
+		dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+		ret = -EBUSY;
+		goto err_free_codec;
+	}
+
+	jz4740_codec->base = ioremap(mem->start, resource_size(mem));
+	if (!jz4740_codec->base) {
+		dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+		ret = -EBUSY;
+		goto err_release_mem_region;
+	}
+	jz4740_codec->mem = mem;
+
+	platform_set_drvdata(pdev, jz4740_codec);
+
+	ret = snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_jz4740_codec, &jz4740_codec_dai, 1);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register codec\n");
+		goto err_iounmap;
+	}
+
+	return 0;
+
+err_iounmap:
+	iounmap(jz4740_codec->base);
+err_release_mem_region:
+	release_mem_region(mem->start, resource_size(mem));
+err_free_codec:
+	kfree(jz4740_codec);
+
+	return ret;
+}
+
+static int __devexit jz4740_codec_remove(struct platform_device *pdev)
+{
+	struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev);
+	struct resource *mem = jz4740_codec->mem;
+
+	snd_soc_unregister_codec(&pdev->dev);
+
+	iounmap(jz4740_codec->base);
+	release_mem_region(mem->start, resource_size(mem));
+
+	platform_set_drvdata(pdev, NULL);
+	kfree(jz4740_codec);
+
+	return 0;
+}
+
+static struct platform_driver jz4740_codec_driver = {
+	.probe = jz4740_codec_probe,
+	.remove = __devexit_p(jz4740_codec_remove),
+	.driver = {
+		.name = "jz4740-codec",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init jz4740_codec_init(void)
+{
+	return platform_driver_register(&jz4740_codec_driver);
+}
+module_init(jz4740_codec_init);
+
+static void __exit jz4740_codec_exit(void)
+{
+	platform_driver_unregister(&jz4740_codec_driver);
+}
+module_exit(jz4740_codec_exit);
+
+MODULE_DESCRIPTION("JZ4740 SoC internal codec driver");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:jz4740-codec");
diff --git a/linux/sound/soc/codecs/l3.c b/linux/sound/soc/codecs/l3.c
new file mode 100644
index 0000000..5353af5
--- /dev/null
+++ b/linux/sound/soc/codecs/l3.c
@@ -0,0 +1,91 @@
+/*
+ * L3 code
+ *
+ *  Copyright (C) 2008, Christian Pellegrin <chripell@evolware.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *
+ * based on:
+ *
+ * L3 bus algorithm module.
+ *
+ *  Copyright (C) 2001 Russell King, All Rights Reserved.
+ *
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include <sound/l3.h>
+
+/*
+ * Send one byte of data to the chip.  Data is latched into the chip on
+ * the rising edge of the clock.
+ */
+static void sendbyte(struct l3_pins *adap, unsigned int byte)
+{
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		adap->setclk(0);
+		udelay(adap->data_hold);
+		adap->setdat(byte & 1);
+		udelay(adap->data_setup);
+		adap->setclk(1);
+		udelay(adap->clock_high);
+		byte >>= 1;
+	}
+}
+
+/*
+ * Send a set of bytes to the chip.  We need to pulse the MODE line
+ * between each byte, but never at the start nor at the end of the
+ * transfer.
+ */
+static void sendbytes(struct l3_pins *adap, const u8 *buf,
+		      int len)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		if (i) {
+			udelay(adap->mode_hold);
+			adap->setmode(0);
+			udelay(adap->mode);
+		}
+		adap->setmode(1);
+		udelay(adap->mode_setup);
+		sendbyte(adap, buf[i]);
+	}
+}
+
+int l3_write(struct l3_pins *adap, u8 addr, u8 *data, int len)
+{
+	adap->setclk(1);
+	adap->setdat(1);
+	adap->setmode(1);
+	udelay(adap->mode);
+
+	adap->setmode(0);
+	udelay(adap->mode_setup);
+	sendbyte(adap, addr);
+	udelay(adap->mode_hold);
+
+	sendbytes(adap, data, len);
+
+	adap->setclk(1);
+	adap->setdat(1);
+	adap->setmode(0);
+
+	return len;
+}
+EXPORT_SYMBOL_GPL(l3_write);
+
+MODULE_DESCRIPTION("L3 bit-banging driver");
+MODULE_AUTHOR("Christian Pellegrin <chripell@evolware.org>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/max98088.c b/linux/sound/soc/codecs/max98088.c
new file mode 100644
index 0000000..6447dbb
--- /dev/null
+++ b/linux/sound/soc/codecs/max98088.c
@@ -0,0 +1,2107 @@
+/*
+ * max98088.c -- MAX98088 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/slab.h>
+#include <asm/div64.h>
+#include <sound/max98088.h>
+#include "max98088.h"
+
+enum max98088_type {
+       MAX98088,
+       MAX98089,
+};
+
+struct max98088_cdata {
+       unsigned int rate;
+       unsigned int fmt;
+       int eq_sel;
+};
+
+struct max98088_priv {
+       enum max98088_type devtype;
+       void *control_data;
+       struct max98088_pdata *pdata;
+       unsigned int sysclk;
+       struct max98088_cdata dai[2];
+       int eq_textcnt;
+       const char **eq_texts;
+       struct soc_enum eq_enum;
+       u8 ina_state;
+       u8 inb_state;
+       unsigned int ex_mode;
+       unsigned int digmic;
+       unsigned int mic1pre;
+       unsigned int mic2pre;
+       unsigned int extmic_mode;
+};
+
+static const u8 max98088_reg[M98088_REG_CNT] = {
+       0x00, /* 00 IRQ status */
+       0x00, /* 01 MIC status */
+       0x00, /* 02 jack status */
+       0x00, /* 03 battery voltage */
+       0x00, /* 04 */
+       0x00, /* 05 */
+       0x00, /* 06 */
+       0x00, /* 07 */
+       0x00, /* 08 */
+       0x00, /* 09 */
+       0x00, /* 0A */
+       0x00, /* 0B */
+       0x00, /* 0C */
+       0x00, /* 0D */
+       0x00, /* 0E */
+       0x00, /* 0F interrupt enable */
+
+       0x00, /* 10 master clock */
+       0x00, /* 11 DAI1 clock mode */
+       0x00, /* 12 DAI1 clock control */
+       0x00, /* 13 DAI1 clock control */
+       0x00, /* 14 DAI1 format */
+       0x00, /* 15 DAI1 clock */
+       0x00, /* 16 DAI1 config */
+       0x00, /* 17 DAI1 TDM */
+       0x00, /* 18 DAI1 filters */
+       0x00, /* 19 DAI2 clock mode */
+       0x00, /* 1A DAI2 clock control */
+       0x00, /* 1B DAI2 clock control */
+       0x00, /* 1C DAI2 format */
+       0x00, /* 1D DAI2 clock */
+       0x00, /* 1E DAI2 config */
+       0x00, /* 1F DAI2 TDM */
+
+       0x00, /* 20 DAI2 filters */
+       0x00, /* 21 data config */
+       0x00, /* 22 DAC mixer */
+       0x00, /* 23 left ADC mixer */
+       0x00, /* 24 right ADC mixer */
+       0x00, /* 25 left HP mixer */
+       0x00, /* 26 right HP mixer */
+       0x00, /* 27 HP control */
+       0x00, /* 28 left REC mixer */
+       0x00, /* 29 right REC mixer */
+       0x00, /* 2A REC control */
+       0x00, /* 2B left SPK mixer */
+       0x00, /* 2C right SPK mixer */
+       0x00, /* 2D SPK control */
+       0x00, /* 2E sidetone */
+       0x00, /* 2F DAI1 playback level */
+
+       0x00, /* 30 DAI1 playback level */
+       0x00, /* 31 DAI2 playback level */
+       0x00, /* 32 DAI2 playbakc level */
+       0x00, /* 33 left ADC level */
+       0x00, /* 34 right ADC level */
+       0x00, /* 35 MIC1 level */
+       0x00, /* 36 MIC2 level */
+       0x00, /* 37 INA level */
+       0x00, /* 38 INB level */
+       0x00, /* 39 left HP volume */
+       0x00, /* 3A right HP volume */
+       0x00, /* 3B left REC volume */
+       0x00, /* 3C right REC volume */
+       0x00, /* 3D left SPK volume */
+       0x00, /* 3E right SPK volume */
+       0x00, /* 3F MIC config */
+
+       0x00, /* 40 MIC threshold */
+       0x00, /* 41 excursion limiter filter */
+       0x00, /* 42 excursion limiter threshold */
+       0x00, /* 43 ALC */
+       0x00, /* 44 power limiter threshold */
+       0x00, /* 45 power limiter config */
+       0x00, /* 46 distortion limiter config */
+       0x00, /* 47 audio input */
+       0x00, /* 48 microphone */
+       0x00, /* 49 level control */
+       0x00, /* 4A bypass switches */
+       0x00, /* 4B jack detect */
+       0x00, /* 4C input enable */
+       0x00, /* 4D output enable */
+       0xF0, /* 4E bias control */
+       0x00, /* 4F DAC power */
+
+       0x0F, /* 50 DAC power */
+       0x00, /* 51 system */
+       0x00, /* 52 DAI1 EQ1 */
+       0x00, /* 53 DAI1 EQ1 */
+       0x00, /* 54 DAI1 EQ1 */
+       0x00, /* 55 DAI1 EQ1 */
+       0x00, /* 56 DAI1 EQ1 */
+       0x00, /* 57 DAI1 EQ1 */
+       0x00, /* 58 DAI1 EQ1 */
+       0x00, /* 59 DAI1 EQ1 */
+       0x00, /* 5A DAI1 EQ1 */
+       0x00, /* 5B DAI1 EQ1 */
+       0x00, /* 5C DAI1 EQ2 */
+       0x00, /* 5D DAI1 EQ2 */
+       0x00, /* 5E DAI1 EQ2 */
+       0x00, /* 5F DAI1 EQ2 */
+
+       0x00, /* 60 DAI1 EQ2 */
+       0x00, /* 61 DAI1 EQ2 */
+       0x00, /* 62 DAI1 EQ2 */
+       0x00, /* 63 DAI1 EQ2 */
+       0x00, /* 64 DAI1 EQ2 */
+       0x00, /* 65 DAI1 EQ2 */
+       0x00, /* 66 DAI1 EQ3 */
+       0x00, /* 67 DAI1 EQ3 */
+       0x00, /* 68 DAI1 EQ3 */
+       0x00, /* 69 DAI1 EQ3 */
+       0x00, /* 6A DAI1 EQ3 */
+       0x00, /* 6B DAI1 EQ3 */
+       0x00, /* 6C DAI1 EQ3 */
+       0x00, /* 6D DAI1 EQ3 */
+       0x00, /* 6E DAI1 EQ3 */
+       0x00, /* 6F DAI1 EQ3 */
+
+       0x00, /* 70 DAI1 EQ4 */
+       0x00, /* 71 DAI1 EQ4 */
+       0x00, /* 72 DAI1 EQ4 */
+       0x00, /* 73 DAI1 EQ4 */
+       0x00, /* 74 DAI1 EQ4 */
+       0x00, /* 75 DAI1 EQ4 */
+       0x00, /* 76 DAI1 EQ4 */
+       0x00, /* 77 DAI1 EQ4 */
+       0x00, /* 78 DAI1 EQ4 */
+       0x00, /* 79 DAI1 EQ4 */
+       0x00, /* 7A DAI1 EQ5 */
+       0x00, /* 7B DAI1 EQ5 */
+       0x00, /* 7C DAI1 EQ5 */
+       0x00, /* 7D DAI1 EQ5 */
+       0x00, /* 7E DAI1 EQ5 */
+       0x00, /* 7F DAI1 EQ5 */
+
+       0x00, /* 80 DAI1 EQ5 */
+       0x00, /* 81 DAI1 EQ5 */
+       0x00, /* 82 DAI1 EQ5 */
+       0x00, /* 83 DAI1 EQ5 */
+       0x00, /* 84 DAI2 EQ1 */
+       0x00, /* 85 DAI2 EQ1 */
+       0x00, /* 86 DAI2 EQ1 */
+       0x00, /* 87 DAI2 EQ1 */
+       0x00, /* 88 DAI2 EQ1 */
+       0x00, /* 89 DAI2 EQ1 */
+       0x00, /* 8A DAI2 EQ1 */
+       0x00, /* 8B DAI2 EQ1 */
+       0x00, /* 8C DAI2 EQ1 */
+       0x00, /* 8D DAI2 EQ1 */
+       0x00, /* 8E DAI2 EQ2 */
+       0x00, /* 8F DAI2 EQ2 */
+
+       0x00, /* 90 DAI2 EQ2 */
+       0x00, /* 91 DAI2 EQ2 */
+       0x00, /* 92 DAI2 EQ2 */
+       0x00, /* 93 DAI2 EQ2 */
+       0x00, /* 94 DAI2 EQ2 */
+       0x00, /* 95 DAI2 EQ2 */
+       0x00, /* 96 DAI2 EQ2 */
+       0x00, /* 97 DAI2 EQ2 */
+       0x00, /* 98 DAI2 EQ3 */
+       0x00, /* 99 DAI2 EQ3 */
+       0x00, /* 9A DAI2 EQ3 */
+       0x00, /* 9B DAI2 EQ3 */
+       0x00, /* 9C DAI2 EQ3 */
+       0x00, /* 9D DAI2 EQ3 */
+       0x00, /* 9E DAI2 EQ3 */
+       0x00, /* 9F DAI2 EQ3 */
+
+       0x00, /* A0 DAI2 EQ3 */
+       0x00, /* A1 DAI2 EQ3 */
+       0x00, /* A2 DAI2 EQ4 */
+       0x00, /* A3 DAI2 EQ4 */
+       0x00, /* A4 DAI2 EQ4 */
+       0x00, /* A5 DAI2 EQ4 */
+       0x00, /* A6 DAI2 EQ4 */
+       0x00, /* A7 DAI2 EQ4 */
+       0x00, /* A8 DAI2 EQ4 */
+       0x00, /* A9 DAI2 EQ4 */
+       0x00, /* AA DAI2 EQ4 */
+       0x00, /* AB DAI2 EQ4 */
+       0x00, /* AC DAI2 EQ5 */
+       0x00, /* AD DAI2 EQ5 */
+       0x00, /* AE DAI2 EQ5 */
+       0x00, /* AF DAI2 EQ5 */
+
+       0x00, /* B0 DAI2 EQ5 */
+       0x00, /* B1 DAI2 EQ5 */
+       0x00, /* B2 DAI2 EQ5 */
+       0x00, /* B3 DAI2 EQ5 */
+       0x00, /* B4 DAI2 EQ5 */
+       0x00, /* B5 DAI2 EQ5 */
+       0x00, /* B6 DAI1 biquad */
+       0x00, /* B7 DAI1 biquad */
+       0x00, /* B8 DAI1 biquad */
+       0x00, /* B9 DAI1 biquad */
+       0x00, /* BA DAI1 biquad */
+       0x00, /* BB DAI1 biquad */
+       0x00, /* BC DAI1 biquad */
+       0x00, /* BD DAI1 biquad */
+       0x00, /* BE DAI1 biquad */
+       0x00, /* BF DAI1 biquad */
+
+       0x00, /* C0 DAI2 biquad */
+       0x00, /* C1 DAI2 biquad */
+       0x00, /* C2 DAI2 biquad */
+       0x00, /* C3 DAI2 biquad */
+       0x00, /* C4 DAI2 biquad */
+       0x00, /* C5 DAI2 biquad */
+       0x00, /* C6 DAI2 biquad */
+       0x00, /* C7 DAI2 biquad */
+       0x00, /* C8 DAI2 biquad */
+       0x00, /* C9 DAI2 biquad */
+       0x00, /* CA */
+       0x00, /* CB */
+       0x00, /* CC */
+       0x00, /* CD */
+       0x00, /* CE */
+       0x00, /* CF */
+
+       0x00, /* D0 */
+       0x00, /* D1 */
+       0x00, /* D2 */
+       0x00, /* D3 */
+       0x00, /* D4 */
+       0x00, /* D5 */
+       0x00, /* D6 */
+       0x00, /* D7 */
+       0x00, /* D8 */
+       0x00, /* D9 */
+       0x00, /* DA */
+       0x70, /* DB */
+       0x00, /* DC */
+       0x00, /* DD */
+       0x00, /* DE */
+       0x00, /* DF */
+
+       0x00, /* E0 */
+       0x00, /* E1 */
+       0x00, /* E2 */
+       0x00, /* E3 */
+       0x00, /* E4 */
+       0x00, /* E5 */
+       0x00, /* E6 */
+       0x00, /* E7 */
+       0x00, /* E8 */
+       0x00, /* E9 */
+       0x00, /* EA */
+       0x00, /* EB */
+       0x00, /* EC */
+       0x00, /* ED */
+       0x00, /* EE */
+       0x00, /* EF */
+
+       0x00, /* F0 */
+       0x00, /* F1 */
+       0x00, /* F2 */
+       0x00, /* F3 */
+       0x00, /* F4 */
+       0x00, /* F5 */
+       0x00, /* F6 */
+       0x00, /* F7 */
+       0x00, /* F8 */
+       0x00, /* F9 */
+       0x00, /* FA */
+       0x00, /* FB */
+       0x00, /* FC */
+       0x00, /* FD */
+       0x00, /* FE */
+       0x00, /* FF */
+};
+
+static struct {
+       int readable;
+       int writable;
+       int vol;
+} max98088_access[M98088_REG_CNT] = {
+       { 0xFF, 0xFF, 1 }, /* 00 IRQ status */
+       { 0xFF, 0x00, 1 }, /* 01 MIC status */
+       { 0xFF, 0x00, 1 }, /* 02 jack status */
+       { 0x1F, 0x1F, 1 }, /* 03 battery voltage */
+       { 0xFF, 0xFF, 0 }, /* 04 */
+       { 0xFF, 0xFF, 0 }, /* 05 */
+       { 0xFF, 0xFF, 0 }, /* 06 */
+       { 0xFF, 0xFF, 0 }, /* 07 */
+       { 0xFF, 0xFF, 0 }, /* 08 */
+       { 0xFF, 0xFF, 0 }, /* 09 */
+       { 0xFF, 0xFF, 0 }, /* 0A */
+       { 0xFF, 0xFF, 0 }, /* 0B */
+       { 0xFF, 0xFF, 0 }, /* 0C */
+       { 0xFF, 0xFF, 0 }, /* 0D */
+       { 0xFF, 0xFF, 0 }, /* 0E */
+       { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */
+
+       { 0xFF, 0xFF, 0 }, /* 10 master clock */
+       { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */
+       { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */
+       { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */
+       { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */
+       { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */
+       { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */
+       { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */
+       { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */
+       { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */
+
+       { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */
+       { 0xFF, 0xFF, 0 }, /* 21 data config */
+       { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */
+       { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 27 HP control */
+       { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 2A REC control */
+       { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2D SPK control */
+       { 0xFF, 0xFF, 0 }, /* 2E sidetone */
+       { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */
+
+       { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */
+       { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */
+       { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */
+       { 0xFF, 0xFF, 0 }, /* 33 left ADC level */
+       { 0xFF, 0xFF, 0 }, /* 34 right ADC level */
+       { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */
+       { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */
+       { 0xFF, 0xFF, 0 }, /* 37 INA level */
+       { 0xFF, 0xFF, 0 }, /* 38 INB level */
+       { 0xFF, 0xFF, 0 }, /* 39 left HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3A right HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3B left REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3C right REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3F MIC config */
+
+       { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */
+       { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */
+       { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 43 ALC */
+       { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 45 power limiter config */
+       { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */
+       { 0xFF, 0xFF, 0 }, /* 47 audio input */
+       { 0xFF, 0xFF, 0 }, /* 48 microphone */
+       { 0xFF, 0xFF, 0 }, /* 49 level control */
+       { 0xFF, 0xFF, 0 }, /* 4A bypass switches */
+       { 0xFF, 0xFF, 0 }, /* 4B jack detect */
+       { 0xFF, 0xFF, 0 }, /* 4C input enable */
+       { 0xFF, 0xFF, 0 }, /* 4D output enable */
+       { 0xFF, 0xFF, 0 }, /* 4E bias control */
+       { 0xFF, 0xFF, 0 }, /* 4F DAC power */
+
+       { 0xFF, 0xFF, 0 }, /* 50 DAC power */
+       { 0xFF, 0xFF, 0 }, /* 51 system */
+       { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */
+
+       { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */
+       { 0x00, 0x00, 0 }, /* CA */
+       { 0x00, 0x00, 0 }, /* CB */
+       { 0x00, 0x00, 0 }, /* CC */
+       { 0x00, 0x00, 0 }, /* CD */
+       { 0x00, 0x00, 0 }, /* CE */
+       { 0x00, 0x00, 0 }, /* CF */
+
+       { 0x00, 0x00, 0 }, /* D0 */
+       { 0x00, 0x00, 0 }, /* D1 */
+       { 0x00, 0x00, 0 }, /* D2 */
+       { 0x00, 0x00, 0 }, /* D3 */
+       { 0x00, 0x00, 0 }, /* D4 */
+       { 0x00, 0x00, 0 }, /* D5 */
+       { 0x00, 0x00, 0 }, /* D6 */
+       { 0x00, 0x00, 0 }, /* D7 */
+       { 0x00, 0x00, 0 }, /* D8 */
+       { 0x00, 0x00, 0 }, /* D9 */
+       { 0x00, 0x00, 0 }, /* DA */
+       { 0x00, 0x00, 0 }, /* DB */
+       { 0x00, 0x00, 0 }, /* DC */
+       { 0x00, 0x00, 0 }, /* DD */
+       { 0x00, 0x00, 0 }, /* DE */
+       { 0x00, 0x00, 0 }, /* DF */
+
+       { 0x00, 0x00, 0 }, /* E0 */
+       { 0x00, 0x00, 0 }, /* E1 */
+       { 0x00, 0x00, 0 }, /* E2 */
+       { 0x00, 0x00, 0 }, /* E3 */
+       { 0x00, 0x00, 0 }, /* E4 */
+       { 0x00, 0x00, 0 }, /* E5 */
+       { 0x00, 0x00, 0 }, /* E6 */
+       { 0x00, 0x00, 0 }, /* E7 */
+       { 0x00, 0x00, 0 }, /* E8 */
+       { 0x00, 0x00, 0 }, /* E9 */
+       { 0x00, 0x00, 0 }, /* EA */
+       { 0x00, 0x00, 0 }, /* EB */
+       { 0x00, 0x00, 0 }, /* EC */
+       { 0x00, 0x00, 0 }, /* ED */
+       { 0x00, 0x00, 0 }, /* EE */
+       { 0x00, 0x00, 0 }, /* EF */
+
+       { 0x00, 0x00, 0 }, /* F0 */
+       { 0x00, 0x00, 0 }, /* F1 */
+       { 0x00, 0x00, 0 }, /* F2 */
+       { 0x00, 0x00, 0 }, /* F3 */
+       { 0x00, 0x00, 0 }, /* F4 */
+       { 0x00, 0x00, 0 }, /* F5 */
+       { 0x00, 0x00, 0 }, /* F6 */
+       { 0x00, 0x00, 0 }, /* F7 */
+       { 0x00, 0x00, 0 }, /* F8 */
+       { 0x00, 0x00, 0 }, /* F9 */
+       { 0x00, 0x00, 0 }, /* FA */
+       { 0x00, 0x00, 0 }, /* FB */
+       { 0x00, 0x00, 0 }, /* FC */
+       { 0x00, 0x00, 0 }, /* FD */
+       { 0x00, 0x00, 0 }, /* FE */
+       { 0xFF, 0x00, 1 }, /* FF */
+};
+
+static int max98088_volatile_register(unsigned int reg)
+{
+       return max98088_access[reg].vol;
+}
+
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+static void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+                   unsigned int band, u16 *coefs)
+{
+       unsigned int eq_reg;
+       unsigned int i;
+
+       BUG_ON(band > 4);
+       BUG_ON(dai > 1);
+
+       /* Load the base register address */
+       eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE;
+
+       /* Add the band address offset, note adjustment for word address */
+       eq_reg += band * (M98088_COEFS_PER_BAND << 1);
+
+       /* Step through the registers and coefs */
+       for (i = 0; i < M98088_COEFS_PER_BAND; i++) {
+               snd_soc_write(codec, eq_reg++, M98088_BYTE1(coefs[i]));
+               snd_soc_write(codec, eq_reg++, M98088_BYTE0(coefs[i]));
+       }
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max98088_exmode_texts[] = {
+       "Off", "100Hz", "400Hz", "600Hz", "800Hz", "1000Hz", "200-400Hz",
+       "400-600Hz", "400-800Hz",
+};
+
+static const unsigned int max98088_exmode_values[] = {
+       0x00, 0x43, 0x10, 0x20, 0x30, 0x40, 0x11, 0x22, 0x32
+};
+
+static const struct soc_enum max98088_exmode_enum =
+       SOC_VALUE_ENUM_SINGLE(M98088_REG_41_SPKDHP, 0, 127,
+                             ARRAY_SIZE(max98088_exmode_texts),
+                             max98088_exmode_texts,
+                             max98088_exmode_values);
+static const struct snd_kcontrol_new max98088_exmode_controls =
+       SOC_DAPM_VALUE_ENUM("Route", max98088_exmode_enum);
+
+static const char *max98088_ex_thresh[] = { /* volts PP */
+       "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"};
+static const struct soc_enum max98088_ex_thresh_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8,
+               max98088_ex_thresh),
+};
+
+static const char *max98088_fltr_mode[] = {"Voice", "Music" };
+static const struct soc_enum max98088_filter_mode_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode),
+};
+
+static const char *max98088_extmic_text[] = { "None", "MIC1", "MIC2" };
+
+static const struct soc_enum max98088_extmic_enum =
+       SOC_ENUM_SINGLE(M98088_REG_48_CFG_MIC, 0, 3, max98088_extmic_text);
+
+static const struct snd_kcontrol_new max98088_extmic_mux =
+       SOC_DAPM_ENUM("External MIC Mux", max98088_extmic_enum);
+
+static const char *max98088_dai1_fltr[] = {
+       "Off", "fc=258/fs=16k", "fc=500/fs=16k",
+       "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"};
+static const struct soc_enum max98088_dai1_dac_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr),
+};
+static const struct soc_enum max98088_dai1_adc_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr),
+};
+
+static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int sel = ucontrol->value.integer.value[0];
+
+       max98088->mic1pre = sel;
+       snd_soc_update_bits(codec, M98088_REG_35_LVL_MIC1, M98088_MICPRE_MASK,
+               (1+sel)<<M98088_MICPRE_SHIFT);
+
+       return 0;
+}
+
+static int max98088_mic1pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       ucontrol->value.integer.value[0] = max98088->mic1pre;
+       return 0;
+}
+
+static int max98088_mic2pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int sel = ucontrol->value.integer.value[0];
+
+       max98088->mic2pre = sel;
+       snd_soc_update_bits(codec, M98088_REG_36_LVL_MIC2, M98088_MICPRE_MASK,
+               (1+sel)<<M98088_MICPRE_SHIFT);
+
+       return 0;
+}
+
+static int max98088_mic2pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       ucontrol->value.integer.value[0] = max98088->mic2pre;
+       return 0;
+}
+
+static const unsigned int max98088_micboost_tlv[] = {
+       TLV_DB_RANGE_HEAD(2),
+       0, 1, TLV_DB_SCALE_ITEM(0, 2000, 0),
+       2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0),
+};
+
+static const struct snd_kcontrol_new max98088_snd_controls[] = {
+
+       SOC_DOUBLE_R("Headphone Volume", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 0, 31, 0),
+       SOC_DOUBLE_R("Speaker Volume", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 0, 31, 0),
+       SOC_DOUBLE_R("Receiver Volume", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 0, 31, 0),
+
+       SOC_DOUBLE_R("Headphone Switch", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 7, 1, 1),
+       SOC_DOUBLE_R("Speaker Switch", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 7, 1, 1),
+       SOC_DOUBLE_R("Receiver Switch", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 7, 1, 1),
+
+       SOC_SINGLE("MIC1 Volume", M98088_REG_35_LVL_MIC1, 0, 31, 1),
+       SOC_SINGLE("MIC2 Volume", M98088_REG_36_LVL_MIC2, 0, 31, 1),
+
+       SOC_SINGLE_EXT_TLV("MIC1 Boost Volume",
+                       M98088_REG_35_LVL_MIC1, 5, 2, 0,
+                       max98088_mic1pre_get, max98088_mic1pre_set,
+                       max98088_micboost_tlv),
+       SOC_SINGLE_EXT_TLV("MIC2 Boost Volume",
+                       M98088_REG_36_LVL_MIC2, 5, 2, 0,
+                       max98088_mic2pre_get, max98088_mic2pre_set,
+                       max98088_micboost_tlv),
+
+       SOC_SINGLE("INA Volume", M98088_REG_37_LVL_INA, 0, 7, 1),
+       SOC_SINGLE("INB Volume", M98088_REG_38_LVL_INB, 0, 7, 1),
+
+       SOC_SINGLE("ADCL Volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0),
+       SOC_SINGLE("ADCR Volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0),
+
+       SOC_SINGLE("ADCL Boost Volume", M98088_REG_33_LVL_ADC_L, 4, 3, 0),
+       SOC_SINGLE("ADCR Boost Volume", M98088_REG_34_LVL_ADC_R, 4, 3, 0),
+
+       SOC_SINGLE("EQ1 Switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
+       SOC_SINGLE("EQ2 Switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
+
+       SOC_ENUM("EX Limiter Threshold", max98088_ex_thresh_enum),
+
+       SOC_ENUM("DAI1 Filter Mode", max98088_filter_mode_enum),
+       SOC_ENUM("DAI1 DAC Filter", max98088_dai1_dac_filter_enum),
+       SOC_ENUM("DAI1 ADC Filter", max98088_dai1_adc_filter_enum),
+       SOC_SINGLE("DAI2 DC Block Switch", M98088_REG_20_DAI2_FILTERS,
+               0, 1, 0),
+
+       SOC_SINGLE("ALC Switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0),
+       SOC_SINGLE("ALC Threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0),
+       SOC_SINGLE("ALC Multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0),
+       SOC_SINGLE("ALC Release Time", M98088_REG_43_SPKALC_COMP, 4, 7, 0),
+
+       SOC_SINGLE("PWR Limiter Threshold", M98088_REG_44_PWRLMT_CFG,
+               4, 15, 0),
+       SOC_SINGLE("PWR Limiter Weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0),
+       SOC_SINGLE("PWR Limiter Time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0),
+       SOC_SINGLE("PWR Limiter Time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0),
+
+       SOC_SINGLE("THD Limiter Threshold", M98088_REG_46_THDLMT_CFG, 4, 15, 0),
+       SOC_SINGLE("THD Limiter Time", M98088_REG_46_THDLMT_CFG, 0, 7, 0),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0),
+};
+
+/* Left earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_left_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0),
+};
+
+/* Right earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static int max98088_mic_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               if (w->reg == M98088_REG_35_LVL_MIC1) {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic1pre)<<M98088_MICPRE_SHIFT);
+               } else {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic2pre)<<M98088_MICPRE_SHIFT);
+               }
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK, 0);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * The line inputs are 2-channel stereo inputs with the left
+ * and right channels sharing a common PGA power control signal.
+ */
+static int max98088_line_pga(struct snd_soc_dapm_widget *w,
+                            int event, int line, u8 channel)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       u8 *state;
+
+       BUG_ON(!((channel == 1) || (channel == 2)));
+
+       switch (line) {
+       case LINE_INA:
+               state = &max98088->ina_state;
+               break;
+       case LINE_INB:
+               state = &max98088->inb_state;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               *state |= channel;
+               snd_soc_update_bits(codec, w->reg,
+                       (1 << w->shift), (1 << w->shift));
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               *state &= ~channel;
+               if (*state == 0) {
+                       snd_soc_update_bits(codec, w->reg,
+                               (1 << w->shift), 0);
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int max98088_pga_ina1_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INA, 1);
+}
+
+static int max98088_pga_ina2_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INA, 2);
+}
+
+static int max98088_pga_inb1_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INB, 1);
+}
+
+static int max98088_pga_inb2_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INB, 2);
+}
+
+static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = {
+
+       SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0),
+       SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0),
+
+       SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+       SND_SOC_DAPM_DAC("DACL2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+
+       SND_SOC_DAPM_PGA("HP Left Out", M98088_REG_4D_PWR_EN_OUT,
+               7, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("HP Right Out", M98088_REG_4D_PWR_EN_OUT,
+               6, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT,
+               5, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT,
+               4, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT,
+               3, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT,
+               2, 0, NULL, 0),
+
+       SND_SOC_DAPM_MUX("External MIC", SND_SOC_NOPM, 0, 0,
+               &max98088_extmic_mux),
+
+       SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_left_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_right_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_PGA_E("MIC1 Input", M98088_REG_35_LVL_MIC1,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("MIC2 Input", M98088_REG_36_LVL_MIC2,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_ina1_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_ina2_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_inb1_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_inb2_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_MICBIAS("MICBIAS", M98088_REG_4C_PWR_EN_IN, 3, 0),
+
+       SND_SOC_DAPM_MUX("EX Limiter Mode", SND_SOC_NOPM, 0, 0,
+               &max98088_exmode_controls),
+
+       SND_SOC_DAPM_OUTPUT("HPL"),
+       SND_SOC_DAPM_OUTPUT("HPR"),
+       SND_SOC_DAPM_OUTPUT("SPKL"),
+       SND_SOC_DAPM_OUTPUT("SPKR"),
+       SND_SOC_DAPM_OUTPUT("RECL"),
+       SND_SOC_DAPM_OUTPUT("RECR"),
+
+       SND_SOC_DAPM_INPUT("MIC1"),
+       SND_SOC_DAPM_INPUT("MIC2"),
+       SND_SOC_DAPM_INPUT("INA1"),
+       SND_SOC_DAPM_INPUT("INA2"),
+       SND_SOC_DAPM_INPUT("INB1"),
+       SND_SOC_DAPM_INPUT("INB2"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+       /* Left headphone output mixer */
+       {"Left HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left HP Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right headphone output mixer */
+       {"Right HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right HP Mixer", "Left DAC2 Switch", "DACL2"  },
+       {"Right HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Left speaker output mixer */
+       {"Left SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right speaker output mixer */
+       {"Right SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Left REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Right REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       {"HP Left Out", NULL, "Left HP Mixer"},
+       {"HP Right Out", NULL, "Right HP Mixer"},
+       {"SPK Left Out", NULL, "Left SPK Mixer"},
+       {"SPK Right Out", NULL, "Right SPK Mixer"},
+       {"REC Left Out", NULL, "Left REC Mixer"},
+       {"REC Right Out", NULL, "Right REC Mixer"},
+
+       {"HPL", NULL, "HP Left Out"},
+       {"HPR", NULL, "HP Right Out"},
+       {"SPKL", NULL, "SPK Left Out"},
+       {"SPKR", NULL, "SPK Right Out"},
+       {"RECL", NULL, "REC Left Out"},
+       {"RECR", NULL, "REC Right Out"},
+
+       /* Left ADC input mixer */
+       {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right ADC input mixer */
+       {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Inputs */
+       {"ADCL", NULL, "Left ADC Mixer"},
+       {"ADCR", NULL, "Right ADC Mixer"},
+       {"INA1 Input", NULL, "INA1"},
+       {"INA2 Input", NULL, "INA2"},
+       {"INB1 Input", NULL, "INB1"},
+       {"INB2 Input", NULL, "INB2"},
+       {"MIC1 Input", NULL, "MIC1"},
+       {"MIC2 Input", NULL, "MIC2"},
+};
+
+static int max98088_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, max98088_dapm_widgets,
+                                 ARRAY_SIZE(max98088_dapm_widgets));
+
+       snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+       snd_soc_add_controls(codec, max98088_snd_controls,
+                            ARRAY_SIZE(max98088_snd_controls));
+
+       snd_soc_dapm_new_widgets(codec);
+       return 0;
+}
+
+/* codec mclk clock divider coefficients */
+static const struct {
+       u32 rate;
+       u8  sr;
+} rate_table[] = {
+       {8000,  0x10},
+       {11025, 0x20},
+       {16000, 0x30},
+       {22050, 0x40},
+       {24000, 0x50},
+       {32000, 0x60},
+       {44100, 0x70},
+       {48000, 0x80},
+       {88200, 0x90},
+       {96000, 0xA0},
+};
+
+static inline int rate_value(int rate, u8 *value)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+               if (rate_table[i].rate >= rate) {
+                       *value = rate_table[i].sr;
+                       return 0;
+               }
+       }
+       *value = rate_table[0].sr;
+       return -EINVAL;
+}
+
+static int max98088_dai1_hw_params(struct snd_pcm_substream *substream,
+                                  struct snd_pcm_hw_params *params,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       unsigned long long ni;
+       unsigned int rate;
+       u8 regval;
+
+       cdata = &max98088->dai[0];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate_value(rate, &regval))
+               return -EINVAL;
+
+       snd_soc_update_bits(codec, M98088_REG_11_DAI1_CLKMODE,
+               M98088_CLKMODE_MASK, regval);
+       cdata->rate = rate;
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_14_DAI1_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0) {
+                       dev_err(codec->dev, "Invalid system clock frequency\n");
+                       return -EINVAL;
+               }
+               ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate;
+               do_div(ni, (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                       (ni >> 8) & 0x7F);
+               snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                       ni & 0xFF);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN,
+               M98088_SHDNRUN);
+
+       return 0;
+}
+
+static int max98088_dai2_hw_params(struct snd_pcm_substream *substream,
+                                  struct snd_pcm_hw_params *params,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       unsigned long long ni;
+       unsigned int rate;
+       u8 regval;
+
+       cdata = &max98088->dai[1];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate_value(rate, &regval))
+               return -EINVAL;
+
+       snd_soc_update_bits(codec, M98088_REG_19_DAI2_CLKMODE,
+               M98088_CLKMODE_MASK, regval);
+       cdata->rate = rate;
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_1C_DAI2_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0) {
+                       dev_err(codec->dev, "Invalid system clock frequency\n");
+                       return -EINVAL;
+               }
+               ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate;
+               do_div(ni, (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                       (ni >> 8) & 0x7F);
+               snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                       ni & 0xFF);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN,
+               M98088_SHDNRUN);
+
+       return 0;
+}
+
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
+                                  int clk_id, unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       /* Requested clock frequency is already setup */
+       if (freq == max98088->sysclk)
+               return 0;
+
+       max98088->sysclk = freq; /* remember current sysclk */
+
+       /* Setup clocks for slave mode, and using the PLL
+        * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+        *         0x02 (when master clk is 20MHz to 30MHz)..
+        */
+       if ((freq >= 10000000) && (freq < 20000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x10);
+       } else if ((freq >= 20000000) && (freq < 30000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x20);
+       } else {
+               dev_err(codec->dev, "Invalid master clock frequency\n");
+               return -EINVAL;
+       }
+
+       if (snd_soc_read(codec, M98088_REG_51_PWR_SYS)  & M98088_SHDNRUN) {
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN, 0);
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN, M98088_SHDNRUN);
+       }
+
+       dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+       max98088->sysclk = freq;
+       return 0;
+}
+
+static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       u8 reg15val;
+       u8 reg14val = 0;
+
+       cdata = &max98088->dai[0];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* Slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* Set to master mode */
+                       reg14val |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg14val |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg14val |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg14val |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg14val |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_MAS | M98088_DAI_DLY | M98088_DAI_BCI |
+                       M98088_DAI_WCI, reg14val);
+
+               reg15val = M98088_DAI_BSEL64;
+               if (max98088->digmic)
+                       reg15val |= M98088_DAI_OSR64;
+               snd_soc_write(codec, M98088_REG_15_DAI1_CLOCK, reg15val);
+       }
+
+       return 0;
+}
+
+static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       u8 reg1Cval = 0;
+
+       cdata = &max98088->dai[1];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* Slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* Set to master mode */
+                       reg1Cval |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg1Cval |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg1Cval |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg1Cval |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg1Cval |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_MAS | M98088_DAI_DLY | M98088_DAI_BCI |
+                       M98088_DAI_WCI, reg1Cval);
+
+               snd_soc_write(codec, M98088_REG_1D_DAI2_CLOCK,
+                       M98088_DAI_BSEL64);
+       }
+
+       return 0;
+}
+
+static void max98088_sync_cache(struct snd_soc_codec *codec)
+{
+       u16 *reg_cache = codec->reg_cache;
+       int i;
+
+       if (!codec->cache_sync)
+               return;
+
+       codec->cache_only = 0;
+
+       /* write back cached values if they're writeable and
+        * different from the hardware default.
+        */
+       for (i = 1; i < codec->driver->reg_cache_size; i++) {
+               if (!max98088_access[i].writable)
+                       continue;
+
+               if (reg_cache[i] == max98088_reg[i])
+                       continue;
+
+               snd_soc_write(codec, i, reg_cache[i]);
+       }
+
+       codec->cache_sync = 0;
+}
+
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
+                                  enum snd_soc_bias_level level)
+{
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               if (codec->bias_level == SND_SOC_BIAS_OFF)
+                       max98088_sync_cache(codec);
+
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, M98088_MBEN);
+               break;
+
+       case SND_SOC_BIAS_OFF:
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, 0);
+               codec->cache_sync = 1;
+               break;
+       }
+       codec->bias_level = level;
+       return 0;
+}
+
+#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98088_dai1_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai1_set_fmt,
+       .hw_params = max98088_dai1_hw_params,
+};
+
+static struct snd_soc_dai_ops max98088_dai2_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai2_set_fmt,
+       .hw_params = max98088_dai2_hw_params,
+};
+
+static struct snd_soc_dai_driver max98088_dai[] = {
+{
+       .name = "HiFi",
+       .playback = {
+               .stream_name = "HiFi Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .capture = {
+               .stream_name = "HiFi Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+        .ops = &max98088_dai1_ops,
+},
+{
+       .name = "Aux",
+       .playback = {
+               .stream_name = "Aux Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .ops = &max98088_dai2_ops,
+}
+};
+
+static int max98088_get_channel(const char *name)
+{
+       if (strcmp(name, "EQ1 Mode") == 0)
+               return 0;
+       if (strcmp(name, "EQ2 Mode") == 0)
+               return 1;
+       return -EINVAL;
+}
+
+static void max98088_setup_eq1(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       if (!pdata || !max98088->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+       sel = cdata->eq_sel;
+
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq_cfgcnt; i++) {
+               if (strcmp(pdata->eq_cfg[i].name, max98088->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq_cfg[best].name,
+               pdata->eq_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0);
+
+       coef_set = &pdata->eq_cfg[sel];
+
+       m98088_eq_band(codec, 0, 0, coef_set->band1);
+       m98088_eq_band(codec, 0, 1, coef_set->band2);
+       m98088_eq_band(codec, 0, 2, coef_set->band3);
+       m98088_eq_band(codec, 0, 3, coef_set->band4);
+       m98088_eq_band(codec, 0, 4, coef_set->band5);
+
+       /* Restore the original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save);
+}
+
+static void max98088_setup_eq2(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       if (!pdata || !max98088->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->eq_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq_cfgcnt; i++) {
+               if (strcmp(pdata->eq_cfg[i].name, max98088->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq_cfg[best].name,
+               pdata->eq_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0);
+
+       coef_set = &pdata->eq_cfg[sel];
+
+       m98088_eq_band(codec, 1, 0, coef_set->band1);
+       m98088_eq_band(codec, 1, 1, coef_set->band2);
+       m98088_eq_band(codec, 1, 2, coef_set->band3);
+       m98088_eq_band(codec, 1, 3, coef_set->band4);
+       m98088_eq_band(codec, 1, 4, coef_set->band5);
+
+       /* Restore the original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN,
+               save);
+}
+
+static int max98088_put_eq_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       int channel = max98088_get_channel(kcontrol->id.name);
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[channel];
+
+       if (sel >= pdata->eq_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+
+       switch (channel) {
+       case 0:
+               max98088_setup_eq1(codec);
+               break;
+       case 1:
+               max98088_setup_eq2(codec);
+               break;
+       }
+
+       return 0;
+}
+
+static int max98088_get_eq_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       int channel = max98088_get_channel(kcontrol->id.name);
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[channel];
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static void max98088_handle_eq_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *cfg;
+       unsigned int cfgcnt;
+       int i, j;
+       const char **t;
+       int ret;
+
+       struct snd_kcontrol_new controls[] = {
+               SOC_ENUM_EXT("EQ1 Mode",
+                       max98088->eq_enum,
+                       max98088_get_eq_enum,
+                       max98088_put_eq_enum),
+               SOC_ENUM_EXT("EQ2 Mode",
+                       max98088->eq_enum,
+                       max98088_get_eq_enum,
+                       max98088_put_eq_enum),
+       };
+
+       cfg = pdata->eq_cfg;
+       cfgcnt = pdata->eq_cfgcnt;
+
+       /* Setup an array of texts for the equalizer enum.
+        * This is based on Mark Brown's equalizer driver code.
+        */
+       max98088->eq_textcnt = 0;
+       max98088->eq_texts = NULL;
+       for (i = 0; i < cfgcnt; i++) {
+               for (j = 0; j < max98088->eq_textcnt; j++) {
+                       if (strcmp(cfg[i].name, max98088->eq_texts[j]) == 0)
+                               break;
+               }
+
+               if (j != max98088->eq_textcnt)
+                       continue;
+
+               /* Expand the array */
+               t = krealloc(max98088->eq_texts,
+                            sizeof(char *) * (max98088->eq_textcnt + 1),
+                            GFP_KERNEL);
+               if (t == NULL)
+                       continue;
+
+               /* Store the new entry */
+               t[max98088->eq_textcnt] = cfg[i].name;
+               max98088->eq_textcnt++;
+               max98088->eq_texts = t;
+       }
+
+       /* Now point the soc_enum to .texts array items */
+       max98088->eq_enum.texts = max98088->eq_texts;
+       max98088->eq_enum.max = max98088->eq_textcnt;
+
+       ret = snd_soc_add_controls(codec, controls, ARRAY_SIZE(controls));
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       u8 regval = 0;
+
+       if (!pdata) {
+               dev_dbg(codec->dev, "No platform data\n");
+               return;
+       }
+
+       /* Configure mic for analog/digital mic mode */
+       if (pdata->digmic_left_mode)
+               regval |= M98088_DIGMIC_L;
+
+       if (pdata->digmic_right_mode)
+               regval |= M98088_DIGMIC_R;
+
+       max98088->digmic = (regval ? 1 : 0);
+
+       snd_soc_write(codec, M98088_REG_48_CFG_MIC, regval);
+
+       /* Configure receiver output */
+       regval = ((pdata->receiver_mode) ? M98088_REC_LINEMODE : 0);
+       snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL,
+               M98088_REC_LINEMODE_MASK, regval);
+
+       /* Configure equalizers */
+       if (pdata->eq_cfgcnt)
+               max98088_handle_eq_pdata(codec);
+}
+
+#ifdef CONFIG_PM
+static int max98088_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static int max98088_resume(struct snd_soc_codec *codec)
+{
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       return 0;
+}
+#else
+#define max98088_suspend NULL
+#define max98088_resume NULL
+#endif
+
+static int max98088_probe(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       int ret = 0;
+
+       codec->cache_sync = 1;
+
+       ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+               return ret;
+       }
+
+       /* initalize private data */
+
+       max98088->sysclk = (unsigned)-1;
+       max98088->eq_textcnt = 0;
+
+       cdata = &max98088->dai[0];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_sel = 0;
+
+       cdata = &max98088->dai[1];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_sel = 0;
+
+       max98088->ina_state = 0;
+       max98088->inb_state = 0;
+       max98088->ex_mode = 0;
+       max98088->digmic = 0;
+       max98088->mic1pre = 0;
+       max98088->mic2pre = 0;
+
+       ret = snd_soc_read(codec, M98088_REG_FF_REV_ID);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to read device revision: %d\n",
+                       ret);
+               goto err_access;
+       }
+       dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+       snd_soc_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV);
+
+       /* initialize registers cache to hardware default */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       snd_soc_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00);
+
+       snd_soc_write(codec, M98088_REG_22_MIX_DAC,
+               M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL|
+               M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR);
+
+       snd_soc_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0);
+       snd_soc_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F);
+
+       snd_soc_write(codec, M98088_REG_16_DAI1_IOCFG,
+               M98088_S1NORMAL|M98088_SDATA);
+
+       snd_soc_write(codec, M98088_REG_1E_DAI2_IOCFG,
+               M98088_S2NORMAL|M98088_SDATA);
+
+       max98088_handle_pdata(codec);
+
+       max98088_add_widgets(codec);
+
+err_access:
+       return ret;
+}
+
+static int max98088_remove(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+       kfree(max98088->eq_texts);
+
+       return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_max98088 = {
+       .probe   = max98088_probe,
+       .remove  = max98088_remove,
+       .suspend = max98088_suspend,
+       .resume  = max98088_resume,
+       .set_bias_level = max98088_set_bias_level,
+       .reg_cache_size = ARRAY_SIZE(max98088_reg),
+       .reg_word_size = sizeof(u8),
+       .reg_cache_default = max98088_reg,
+       .volatile_register = max98088_volatile_register,
+};
+
+static int max98088_i2c_probe(struct i2c_client *i2c,
+                            const struct i2c_device_id *id)
+{
+       struct max98088_priv *max98088;
+       int ret;
+
+       max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL);
+       if (max98088 == NULL)
+               return -ENOMEM;
+
+       max98088->devtype = id->driver_data;
+
+       i2c_set_clientdata(i2c, max98088);
+       max98088->control_data = i2c;
+       max98088->pdata = i2c->dev.platform_data;
+
+       ret = snd_soc_register_codec(&i2c->dev,
+                       &soc_codec_dev_max98088, &max98088_dai[0], 2);
+       if (ret < 0)
+               kfree(max98088);
+       return ret;
+}
+
+static int __devexit max98088_i2c_remove(struct i2c_client *client)
+{
+       snd_soc_unregister_codec(&client->dev);
+       kfree(i2c_get_clientdata(client));
+       return 0;
+}
+
+static const struct i2c_device_id max98088_i2c_id[] = {
+       { "max98088", MAX98088 },
+       { "max98089", MAX98089 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, max98088_i2c_id);
+
+static struct i2c_driver max98088_i2c_driver = {
+       .driver = {
+               .name = "max98088",
+               .owner = THIS_MODULE,
+       },
+       .probe  = max98088_i2c_probe,
+       .remove = __devexit_p(max98088_i2c_remove),
+       .id_table = max98088_i2c_id,
+};
+
+static int __init max98088_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&max98088_i2c_driver);
+       if (ret)
+               pr_err("Failed to register max98088 I2C driver: %d\n", ret);
+
+       return ret;
+}
+module_init(max98088_init);
+
+static void __exit max98088_exit(void)
+{
+       i2c_del_driver(&max98088_i2c_driver);
+}
+module_exit(max98088_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98088 driver");
+MODULE_AUTHOR("Peter Hsiang, Jesse Marroquin");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/max98088.h b/linux/sound/soc/codecs/max98088.h
new file mode 100644
index 0000000..56554c7
--- /dev/null
+++ b/linux/sound/soc/codecs/max98088.h
@@ -0,0 +1,193 @@
+/*
+ * max98088.h -- MAX98088 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _MAX98088_H
+#define _MAX98088_H
+
+/*
+ * MAX98088 Registers Definition
+ */
+#define M98088_REG_00_IRQ_STATUS            0x00
+#define M98088_REG_01_MIC_STATUS            0x01
+#define M98088_REG_02_JACK_STAUS            0x02
+#define M98088_REG_03_BATTERY_VOLTAGE       0x03
+#define M98088_REG_0F_IRQ_ENABLE            0x0F
+#define M98088_REG_10_SYS_CLK               0x10
+#define M98088_REG_11_DAI1_CLKMODE          0x11
+#define M98088_REG_12_DAI1_CLKCFG_HI        0x12
+#define M98088_REG_13_DAI1_CLKCFG_LO        0x13
+#define M98088_REG_14_DAI1_FORMAT           0x14
+#define M98088_REG_15_DAI1_CLOCK            0x15
+#define M98088_REG_16_DAI1_IOCFG            0x16
+#define M98088_REG_17_DAI1_TDM              0x17
+#define M98088_REG_18_DAI1_FILTERS          0x18
+#define M98088_REG_19_DAI2_CLKMODE          0x19
+#define M98088_REG_1A_DAI2_CLKCFG_HI        0x1A
+#define M98088_REG_1B_DAI2_CLKCFG_LO        0x1B
+#define M98088_REG_1C_DAI2_FORMAT           0x1C
+#define M98088_REG_1D_DAI2_CLOCK            0x1D
+#define M98088_REG_1E_DAI2_IOCFG            0x1E
+#define M98088_REG_1F_DAI2_TDM              0x1F
+#define M98088_REG_20_DAI2_FILTERS          0x20
+#define M98088_REG_21_SRC                   0x21
+#define M98088_REG_22_MIX_DAC               0x22
+#define M98088_REG_23_MIX_ADC_LEFT          0x23
+#define M98088_REG_24_MIX_ADC_RIGHT         0x24
+#define M98088_REG_25_MIX_HP_LEFT           0x25
+#define M98088_REG_26_MIX_HP_RIGHT          0x26
+#define M98088_REG_27_MIX_HP_CNTL           0x27
+#define M98088_REG_28_MIX_REC_LEFT          0x28
+#define M98088_REG_29_MIX_REC_RIGHT         0x29
+#define M98088_REG_2A_MIC_REC_CNTL          0x2A
+#define M98088_REG_2B_MIX_SPK_LEFT          0x2B
+#define M98088_REG_2C_MIX_SPK_RIGHT         0x2C
+#define M98088_REG_2D_MIX_SPK_CNTL          0x2D
+#define M98088_REG_2E_LVL_SIDETONE          0x2E
+#define M98088_REG_2F_LVL_DAI1_PLAY         0x2F
+#define M98088_REG_30_LVL_DAI1_PLAY_EQ      0x30
+#define M98088_REG_31_LVL_DAI2_PLAY         0x31
+#define M98088_REG_32_LVL_DAI2_PLAY_EQ      0x32
+#define M98088_REG_33_LVL_ADC_L             0x33
+#define M98088_REG_34_LVL_ADC_R             0x34
+#define M98088_REG_35_LVL_MIC1              0x35
+#define M98088_REG_36_LVL_MIC2              0x36
+#define M98088_REG_37_LVL_INA               0x37
+#define M98088_REG_38_LVL_INB               0x38
+#define M98088_REG_39_LVL_HP_L              0x39
+#define M98088_REG_3A_LVL_HP_R              0x3A
+#define M98088_REG_3B_LVL_REC_L             0x3B
+#define M98088_REG_3C_LVL_REC_R             0x3C
+#define M98088_REG_3D_LVL_SPK_L             0x3D
+#define M98088_REG_3E_LVL_SPK_R             0x3E
+#define M98088_REG_3F_MICAGC_CFG            0x3F
+#define M98088_REG_40_MICAGC_THRESH         0x40
+#define M98088_REG_41_SPKDHP                0x41
+#define M98088_REG_42_SPKDHP_THRESH         0x42
+#define M98088_REG_43_SPKALC_COMP           0x43
+#define M98088_REG_44_PWRLMT_CFG            0x44
+#define M98088_REG_45_PWRLMT_TIME           0x45
+#define M98088_REG_46_THDLMT_CFG            0x46
+#define M98088_REG_47_CFG_AUDIO_IN          0x47
+#define M98088_REG_48_CFG_MIC               0x48
+#define M98088_REG_49_CFG_LEVEL             0x49
+#define M98088_REG_4A_CFG_BYPASS            0x4A
+#define M98088_REG_4B_CFG_JACKDET           0x4B
+#define M98088_REG_4C_PWR_EN_IN             0x4C
+#define M98088_REG_4D_PWR_EN_OUT            0x4D
+#define M98088_REG_4E_BIAS_CNTL             0x4E
+#define M98088_REG_4F_DAC_BIAS1             0x4F
+#define M98088_REG_50_DAC_BIAS2             0x50
+#define M98088_REG_51_PWR_SYS               0x51
+#define M98088_REG_52_DAI1_EQ_BASE          0x52
+#define M98088_REG_84_DAI2_EQ_BASE          0x84
+#define M98088_REG_B6_DAI1_BIQUAD_BASE      0xB6
+#define M98088_REG_C0_DAI2_BIQUAD_BASE      0xC0
+#define M98088_REG_FF_REV_ID                0xFF
+
+#define M98088_REG_CNT                      (0xFF+1)
+
+/* MAX98088 Registers Bit Fields */
+
+/* M98088_REG_11_DAI1_CLKMODE, M98088_REG_19_DAI2_CLKMODE */
+       #define M98088_CLKMODE_MASK             0xFF
+
+/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
+       #define M98088_DAI_MAS                  (1<<7)
+       #define M98088_DAI_WCI                  (1<<6)
+       #define M98088_DAI_BCI                  (1<<5)
+       #define M98088_DAI_DLY                  (1<<4)
+       #define M98088_DAI_TDM                  (1<<2)
+       #define M98088_DAI_FSW                  (1<<1)
+       #define M98088_DAI_WS                   (1<<0)
+
+/* M98088_REG_15_DAI1_CLOCK, M98088_REG_1D_DAI2_CLOCK */
+       #define M98088_DAI_BSEL64               (1<<0)
+       #define M98088_DAI_OSR64                (1<<6)
+
+/* M98088_REG_16_DAI1_IOCFG, M98088_REG_1E_DAI2_IOCFG */
+       #define M98088_S1NORMAL                 (1<<6)
+       #define M98088_S2NORMAL                 (2<<6)
+       #define M98088_SDATA                    (3<<0)
+
+/* M98088_REG_18_DAI1_FILTERS, M98088_REG_20_DAI2_FILTERS */
+       #define M98088_DAI_DHF                  (1<<3)
+
+/* M98088_REG_22_MIX_DAC */
+       #define M98088_DAI1L_TO_DACL            (1<<7)
+       #define M98088_DAI1R_TO_DACL            (1<<6)
+       #define M98088_DAI2L_TO_DACL            (1<<5)
+       #define M98088_DAI2R_TO_DACL            (1<<4)
+       #define M98088_DAI1L_TO_DACR            (1<<3)
+       #define M98088_DAI1R_TO_DACR            (1<<2)
+       #define M98088_DAI2L_TO_DACR            (1<<1)
+       #define M98088_DAI2R_TO_DACR            (1<<0)
+
+/* M98088_REG_2A_MIC_REC_CNTL */
+       #define M98088_REC_LINEMODE             (1<<7)
+       #define M98088_REC_LINEMODE_MASK        (1<<7)
+
+/* M98088_REG_35_LVL_MIC1, M98088_REG_36_LVL_MIC2 */
+       #define M98088_MICPRE_MASK              (3<<5)
+       #define M98088_MICPRE_SHIFT             5
+
+/* M98088_REG_3A_LVL_HP_R */
+       #define M98088_HP_MUTE                  (1<<7)
+
+/* M98088_REG_3C_LVL_REC_R */
+       #define M98088_REC_MUTE                 (1<<7)
+
+/* M98088_REG_3E_LVL_SPK_R */
+       #define M98088_SP_MUTE                  (1<<7)
+
+/* M98088_REG_48_CFG_MIC */
+       #define M98088_EXTMIC_MASK              (3<<0)
+       #define M98088_DIGMIC_L                 (1<<5)
+       #define M98088_DIGMIC_R                 (1<<4)
+
+/* M98088_REG_49_CFG_LEVEL */
+       #define M98088_VSEN                     (1<<6)
+       #define M98088_ZDEN                     (1<<5)
+       #define M98088_EQ2EN                    (1<<1)
+       #define M98088_EQ1EN                    (1<<0)
+
+/* M98088_REG_4C_PWR_EN_IN */
+       #define M98088_INAEN                    (1<<7)
+       #define M98088_INBEN                    (1<<6)
+       #define M98088_MBEN                     (1<<3)
+       #define M98088_ADLEN                    (1<<1)
+       #define M98088_ADREN                    (1<<0)
+
+/* M98088_REG_4D_PWR_EN_OUT */
+       #define M98088_HPLEN                    (1<<7)
+       #define M98088_HPREN                    (1<<6)
+       #define M98088_HPEN                     ((1<<7)|(1<<6))
+       #define M98088_SPLEN                    (1<<5)
+       #define M98088_SPREN                    (1<<4)
+       #define M98088_RECEN                    (1<<3)
+       #define M98088_DALEN                    (1<<1)
+       #define M98088_DAREN                    (1<<0)
+
+/* M98088_REG_51_PWR_SYS */
+       #define M98088_SHDNRUN                  (1<<7)
+       #define M98088_PERFMODE                 (1<<3)
+       #define M98088_HPPLYBACK                (1<<2)
+       #define M98088_PWRSV8K                  (1<<1)
+       #define M98088_PWRSV                    (1<<0)
+
+/* Line inputs */
+#define LINE_INA  0
+#define LINE_INB  1
+
+#define M98088_COEFS_PER_BAND               5
+
+#define M98088_BYTE1(w) ((w >> 8) & 0xff)
+#define M98088_BYTE0(w) (w & 0xff)
+
+#endif
diff --git a/linux/sound/soc/codecs/max9877.c b/linux/sound/soc/codecs/max9877.c
new file mode 100644
index 0000000..9e7e964
--- /dev/null
+++ b/linux/sound/soc/codecs/max9877.c
@@ -0,0 +1,308 @@
+/*
+ * max9877.c  --  amp driver for max9877
+ *
+ * Copyright (C) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim@samsung.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "max9877.h"
+
+static struct i2c_client *i2c;
+
+static u8 max9877_regs[5] = { 0x40, 0x00, 0x00, 0x00, 0x49 };
+
+static void max9877_write_regs(void)
+{
+	unsigned int i;
+	u8 data[6];
+
+	data[0] = MAX9877_INPUT_MODE;
+	for (i = 0; i < ARRAY_SIZE(max9877_regs); i++)
+		data[i + 1] = max9877_regs[i];
+
+	if (i2c_master_send(i2c, data, 6) != 6)
+		dev_err(&i2c->dev, "i2c write failed\n");
+}
+
+static int max9877_get_reg(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int shift = mc->shift;
+	unsigned int mask = mc->max;
+	unsigned int invert = mc->invert;
+
+	ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask;
+
+	if (invert)
+		ucontrol->value.integer.value[0] =
+			mask - ucontrol->value.integer.value[0];
+
+	return 0;
+}
+
+static int max9877_set_reg(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int shift = mc->shift;
+	unsigned int mask = mc->max;
+	unsigned int invert = mc->invert;
+	unsigned int val = (ucontrol->value.integer.value[0] & mask);
+
+	if (invert)
+		val = mask - val;
+
+	if (((max9877_regs[reg] >> shift) & mask) == val)
+		return 0;
+
+	max9877_regs[reg] &= ~(mask << shift);
+	max9877_regs[reg] |= val << shift;
+	max9877_write_regs();
+
+	return 1;
+}
+
+static int max9877_get_2reg(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int reg2 = mc->rreg;
+	unsigned int shift = mc->shift;
+	unsigned int mask = mc->max;
+
+	ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask;
+	ucontrol->value.integer.value[1] = (max9877_regs[reg2] >> shift) & mask;
+
+	return 0;
+}
+
+static int max9877_set_2reg(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int reg2 = mc->rreg;
+	unsigned int shift = mc->shift;
+	unsigned int mask = mc->max;
+	unsigned int val = (ucontrol->value.integer.value[0] & mask);
+	unsigned int val2 = (ucontrol->value.integer.value[1] & mask);
+	unsigned int change = 1;
+
+	if (((max9877_regs[reg] >> shift) & mask) == val)
+		change = 0;
+
+	if (((max9877_regs[reg2] >> shift) & mask) == val2)
+		change = 0;
+
+	if (change) {
+		max9877_regs[reg] &= ~(mask << shift);
+		max9877_regs[reg] |= val << shift;
+		max9877_regs[reg2] &= ~(mask << shift);
+		max9877_regs[reg2] |= val2 << shift;
+		max9877_write_regs();
+	}
+
+	return change;
+}
+
+static int max9877_get_out_mode(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	u8 value = max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK;
+
+	if (value)
+		value -= 1;
+
+	ucontrol->value.integer.value[0] = value;
+	return 0;
+}
+
+static int max9877_set_out_mode(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	u8 value = ucontrol->value.integer.value[0];
+
+	value += 1;
+
+	if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK) == value)
+		return 0;
+
+	max9877_regs[MAX9877_OUTPUT_MODE] &= ~MAX9877_OUTMODE_MASK;
+	max9877_regs[MAX9877_OUTPUT_MODE] |= value;
+	max9877_write_regs();
+	return 1;
+}
+
+static int max9877_get_osc_mode(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	u8 value = (max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK);
+
+	value = value >> MAX9877_OSC_OFFSET;
+
+	ucontrol->value.integer.value[0] = value;
+	return 0;
+}
+
+static int max9877_set_osc_mode(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	u8 value = ucontrol->value.integer.value[0];
+
+	value = value << MAX9877_OSC_OFFSET;
+	if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK) == value)
+		return 0;
+
+	max9877_regs[MAX9877_OUTPUT_MODE] &= ~MAX9877_OSC_MASK;
+	max9877_regs[MAX9877_OUTPUT_MODE] |= value;
+	max9877_write_regs();
+	return 1;
+}
+
+static const unsigned int max9877_pgain_tlv[] = {
+	TLV_DB_RANGE_HEAD(2),
+	0, 1, TLV_DB_SCALE_ITEM(0, 900, 0),
+	2, 2, TLV_DB_SCALE_ITEM(2000, 0, 0),
+};
+
+static const unsigned int max9877_output_tlv[] = {
+	TLV_DB_RANGE_HEAD(4),
+	0, 7, TLV_DB_SCALE_ITEM(-7900, 400, 1),
+	8, 15, TLV_DB_SCALE_ITEM(-4700, 300, 0),
+	16, 23, TLV_DB_SCALE_ITEM(-2300, 200, 0),
+	24, 31, TLV_DB_SCALE_ITEM(-700, 100, 0),
+};
+
+static const char *max9877_out_mode[] = {
+	"INA -> SPK",
+	"INA -> HP",
+	"INA -> SPK and HP",
+	"INB -> SPK",
+	"INB -> HP",
+	"INB -> SPK and HP",
+	"INA + INB -> SPK",
+	"INA + INB -> HP",
+	"INA + INB -> SPK and HP",
+};
+
+static const char *max9877_osc_mode[] = {
+	"1176KHz",
+	"1100KHz",
+	"700KHz",
+};
+
+static const struct soc_enum max9877_enum[] = {
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_out_mode), max9877_out_mode),
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_osc_mode), max9877_osc_mode),
+};
+
+static const struct snd_kcontrol_new max9877_controls[] = {
+	SOC_SINGLE_EXT_TLV("MAX9877 PGAINA Playback Volume",
+			MAX9877_INPUT_MODE, 0, 2, 0,
+			max9877_get_reg, max9877_set_reg, max9877_pgain_tlv),
+	SOC_SINGLE_EXT_TLV("MAX9877 PGAINB Playback Volume",
+			MAX9877_INPUT_MODE, 2, 2, 0,
+			max9877_get_reg, max9877_set_reg, max9877_pgain_tlv),
+	SOC_SINGLE_EXT_TLV("MAX9877 Amp Speaker Playback Volume",
+			MAX9877_SPK_VOLUME, 0, 31, 0,
+			max9877_get_reg, max9877_set_reg, max9877_output_tlv),
+	SOC_DOUBLE_R_EXT_TLV("MAX9877 Amp HP Playback Volume",
+			MAX9877_HPL_VOLUME, MAX9877_HPR_VOLUME, 0, 31, 0,
+			max9877_get_2reg, max9877_set_2reg, max9877_output_tlv),
+	SOC_SINGLE_EXT("MAX9877 INB Stereo Switch",
+			MAX9877_INPUT_MODE, 4, 1, 1,
+			max9877_get_reg, max9877_set_reg),
+	SOC_SINGLE_EXT("MAX9877 INA Stereo Switch",
+			MAX9877_INPUT_MODE, 5, 1, 1,
+			max9877_get_reg, max9877_set_reg),
+	SOC_SINGLE_EXT("MAX9877 Zero-crossing detection Switch",
+			MAX9877_INPUT_MODE, 6, 1, 0,
+			max9877_get_reg, max9877_set_reg),
+	SOC_SINGLE_EXT("MAX9877 Bypass Mode Switch",
+			MAX9877_OUTPUT_MODE, 6, 1, 0,
+			max9877_get_reg, max9877_set_reg),
+	SOC_SINGLE_EXT("MAX9877 Shutdown Mode Switch",
+			MAX9877_OUTPUT_MODE, 7, 1, 1,
+			max9877_get_reg, max9877_set_reg),
+	SOC_ENUM_EXT("MAX9877 Output Mode", max9877_enum[0],
+			max9877_get_out_mode, max9877_set_out_mode),
+	SOC_ENUM_EXT("MAX9877 Oscillator Mode", max9877_enum[1],
+			max9877_get_osc_mode, max9877_set_osc_mode),
+};
+
+/* This function is called from ASoC machine driver */
+int max9877_add_controls(struct snd_soc_codec *codec)
+{
+	return snd_soc_add_controls(codec, max9877_controls,
+			ARRAY_SIZE(max9877_controls));
+}
+EXPORT_SYMBOL_GPL(max9877_add_controls);
+
+static int __devinit max9877_i2c_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	i2c = client;
+
+	max9877_write_regs();
+
+	return 0;
+}
+
+static __devexit int max9877_i2c_remove(struct i2c_client *client)
+{
+	i2c = NULL;
+
+	return 0;
+}
+
+static const struct i2c_device_id max9877_i2c_id[] = {
+	{ "max9877", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, max9877_i2c_id);
+
+static struct i2c_driver max9877_i2c_driver = {
+	.driver = {
+		.name = "max9877",
+		.owner = THIS_MODULE,
+	},
+	.probe = max9877_i2c_probe,
+	.remove = __devexit_p(max9877_i2c_remove),
+	.id_table = max9877_i2c_id,
+};
+
+static int __init max9877_init(void)
+{
+	return i2c_add_driver(&max9877_i2c_driver);
+}
+module_init(max9877_init);
+
+static void __exit max9877_exit(void)
+{
+	i2c_del_driver(&max9877_i2c_driver);
+}
+module_exit(max9877_exit);
+
+MODULE_DESCRIPTION("ASoC MAX9877 amp driver");
+MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/max9877.h b/linux/sound/soc/codecs/max9877.h
new file mode 100644
index 0000000..6da7229
--- /dev/null
+++ b/linux/sound/soc/codecs/max9877.h
@@ -0,0 +1,37 @@
+/*
+ * max9877.h  --  amp driver for max9877
+ *
+ * Copyright (C) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim@samsung.com>
+ *
+ *  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.
+ *
+ */
+
+#ifndef _MAX9877_H
+#define _MAX9877_H
+
+#define MAX9877_INPUT_MODE		0x00
+#define MAX9877_SPK_VOLUME		0x01
+#define MAX9877_HPL_VOLUME		0x02
+#define MAX9877_HPR_VOLUME		0x03
+#define MAX9877_OUTPUT_MODE		0x04
+
+/* MAX9877_INPUT_MODE */
+#define MAX9877_INB			(1 << 4)
+#define MAX9877_INA			(1 << 5)
+#define MAX9877_ZCD			(1 << 6)
+
+/* MAX9877_OUTPUT_MODE */
+#define MAX9877_OUTMODE_MASK		(15 << 0)
+#define MAX9877_OSC_MASK		(3 << 4)
+#define MAX9877_OSC_OFFSET		4
+#define MAX9877_BYPASS			(1 << 6)
+#define MAX9877_SHDN			(1 << 7)
+
+extern int max9877_add_controls(struct snd_soc_codec *codec);
+
+#endif
diff --git a/linux/sound/soc/codecs/pcm3008.c b/linux/sound/soc/codecs/pcm3008.c
new file mode 100644
index 0000000..bd8f26e
--- /dev/null
+++ b/linux/sound/soc/codecs/pcm3008.c
@@ -0,0 +1,188 @@
+/*
+ * ALSA Soc PCM3008 codec support
+ *
+ * Author:	Hugo Villeneuve
+ * Copyright (C) 2008 Lyrtech inc
+ *
+ * Based on AC97 Soc codec, original copyright follow:
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ *
+ *  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.
+ *
+ * Generic PCM3008 support.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "pcm3008.h"
+
+#define PCM3008_VERSION "0.2"
+
+#define PCM3008_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |	\
+		       SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_driver pcm3008_dai = {
+	.name = "pcm3008-hifi",
+	.playback = {
+		.stream_name = "PCM3008 Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = PCM3008_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.capture = {
+		.stream_name = "PCM3008 Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = PCM3008_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+};
+
+static void pcm3008_gpio_free(struct pcm3008_setup_data *setup)
+{
+	gpio_free(setup->dem0_pin);
+	gpio_free(setup->dem1_pin);
+	gpio_free(setup->pdad_pin);
+	gpio_free(setup->pdda_pin);
+}
+
+static int pcm3008_soc_probe(struct snd_soc_codec *codec)
+{
+	struct pcm3008_setup_data *setup = codec->dev->platform_data;
+	int ret = 0;
+
+	printk(KERN_INFO "PCM3008 SoC Audio Codec %s\n", PCM3008_VERSION);
+
+	/* DEM1  DEM0  DE-EMPHASIS_MODE
+	 * Low   Low   De-emphasis 44.1 kHz ON
+	 * Low   High  De-emphasis OFF
+	 * High  Low   De-emphasis 48 kHz ON
+	 * High  High  De-emphasis 32 kHz ON
+	 */
+
+	/* Configure DEM0 GPIO (turning OFF DAC De-emphasis). */
+	ret = gpio_request(setup->dem0_pin, "codec_dem0");
+	if (ret == 0)
+		ret = gpio_direction_output(setup->dem0_pin, 1);
+	if (ret != 0)
+		goto gpio_err;
+
+	/* Configure DEM1 GPIO (turning OFF DAC De-emphasis). */
+	ret = gpio_request(setup->dem1_pin, "codec_dem1");
+	if (ret == 0)
+		ret = gpio_direction_output(setup->dem1_pin, 0);
+	if (ret != 0)
+		goto gpio_err;
+
+	/* Configure PDAD GPIO. */
+	ret = gpio_request(setup->pdad_pin, "codec_pdad");
+	if (ret == 0)
+		ret = gpio_direction_output(setup->pdad_pin, 1);
+	if (ret != 0)
+		goto gpio_err;
+
+	/* Configure PDDA GPIO. */
+	ret = gpio_request(setup->pdda_pin, "codec_pdda");
+	if (ret == 0)
+		ret = gpio_direction_output(setup->pdda_pin, 1);
+	if (ret != 0)
+		goto gpio_err;
+
+	return ret;
+
+gpio_err:
+	pcm3008_gpio_free(setup);
+
+	return ret;
+}
+
+static int pcm3008_soc_remove(struct snd_soc_codec *codec)
+{
+	struct pcm3008_setup_data *setup = codec->dev->platform_data;
+
+	pcm3008_gpio_free(setup);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int pcm3008_soc_suspend(struct snd_soc_codec *codec, pm_message_t msg)
+{
+	struct pcm3008_setup_data *setup = codec->dev->platform_data;
+
+	gpio_set_value(setup->pdad_pin, 0);
+	gpio_set_value(setup->pdda_pin, 0);
+
+	return 0;
+}
+
+static int pcm3008_soc_resume(struct snd_soc_codec *codec)
+{
+	struct pcm3008_setup_data *setup = codec->dev->platform_data;
+
+	gpio_set_value(setup->pdad_pin, 1);
+	gpio_set_value(setup->pdda_pin, 1);
+
+	return 0;
+}
+#else
+#define pcm3008_soc_suspend NULL
+#define pcm3008_soc_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_pcm3008 = {
+	.probe = 	pcm3008_soc_probe,
+	.remove = 	pcm3008_soc_remove,
+	.suspend =	pcm3008_soc_suspend,
+	.resume =	pcm3008_soc_resume,
+};
+
+static int __devinit pcm3008_codec_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_pcm3008, &pcm3008_dai, 1);
+}
+
+static int __devexit pcm3008_codec_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+MODULE_ALIAS("platform:pcm3008-codec");
+
+static struct platform_driver pcm3008_codec_driver = {
+	.probe		= pcm3008_codec_probe,
+	.remove		= __devexit_p(pcm3008_codec_remove),
+	.driver		= {
+		.name	= "pcm3008-codec",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init pcm3008_modinit(void)
+{
+	return platform_driver_register(&pcm3008_codec_driver);
+}
+module_init(pcm3008_modinit);
+
+static void __exit pcm3008_exit(void)
+{
+	platform_driver_unregister(&pcm3008_codec_driver);
+}
+module_exit(pcm3008_exit);
+
+MODULE_DESCRIPTION("Soc PCM3008 driver");
+MODULE_AUTHOR("Hugo Villeneuve");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/pcm3008.h b/linux/sound/soc/codecs/pcm3008.h
new file mode 100644
index 0000000..7e5489a
--- /dev/null
+++ b/linux/sound/soc/codecs/pcm3008.h
@@ -0,0 +1,22 @@
+/*
+ * PCM3008 ALSA SoC Layer
+ *
+ * Author:	Hugo Villeneuve
+ * Copyright (C) 2008 Lyrtech inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_SND_SOC_PCM3008_H
+#define __LINUX_SND_SOC_PCM3008_H
+
+struct pcm3008_setup_data {
+	unsigned dem0_pin;
+	unsigned dem1_pin;
+	unsigned pdad_pin;
+	unsigned pdda_pin;
+};
+
+#endif
diff --git a/linux/sound/soc/codecs/spdif_transciever.c b/linux/sound/soc/codecs/spdif_transciever.c
new file mode 100644
index 0000000..4c32b54
--- /dev/null
+++ b/linux/sound/soc/codecs/spdif_transciever.c
@@ -0,0 +1,76 @@
+/*
+ * ALSA SoC SPDIF DIT driver
+ *
+ *  This driver is used by controllers which can operate in DIT (SPDI/F) where
+ *  no codec is needed.  This file provides stub codec that can be used
+ *  in these configurations. TI DaVinci Audio controller uses this driver.
+ *
+ * Author:      Steve Chen,  <schen@mvista.com>
+ * Copyright:   (C) 2009 MontaVista Software, Inc., <source@mvista.com>
+ * Copyright:   (C) 2009  Texas Instruments, India
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+
+MODULE_LICENSE("GPL");
+
+#define STUB_RATES	SNDRV_PCM_RATE_8000_96000
+#define STUB_FORMATS	SNDRV_PCM_FMTBIT_S16_LE
+
+
+static struct snd_soc_codec_driver soc_codec_spdif_dit;
+
+static struct snd_soc_dai_driver dit_stub_dai = {
+	.name		= "dit-hifi",
+	.playback 	= {
+		.stream_name	= "Playback",
+		.channels_min	= 1,
+		.channels_max	= 384,
+		.rates		= STUB_RATES,
+		.formats	= STUB_FORMATS,
+	},
+};
+
+static int spdif_dit_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev, &soc_codec_spdif_dit,
+			&dit_stub_dai, 1);
+}
+
+static int spdif_dit_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver spdif_dit_driver = {
+	.probe		= spdif_dit_probe,
+	.remove		= spdif_dit_remove,
+	.driver		= {
+		.name	= "spdif-dit",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init dit_modinit(void)
+{
+	return platform_driver_register(&spdif_dit_driver);
+}
+
+static void __exit dit_exit(void)
+{
+	platform_driver_unregister(&spdif_dit_driver);
+}
+
+module_init(dit_modinit);
+module_exit(dit_exit);
+
diff --git a/linux/sound/soc/codecs/ssm2602.c b/linux/sound/soc/codecs/ssm2602.c
new file mode 100644
index 0000000..6f38d61
--- /dev/null
+++ b/linux/sound/soc/codecs/ssm2602.c
@@ -0,0 +1,688 @@
+/*
+ * File:         sound/soc/codecs/ssm2602.c
+ * Author:       Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created:      Tue June 06 2008
+ * Description:  Driver for ssm2602 sound chip
+ *
+ * Modified:
+ *               Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "ssm2602.h"
+
+#define SSM2602_VERSION "0.1"
+
+/* codec private data */
+struct ssm2602_priv {
+	unsigned int sysclk;
+	enum snd_soc_control_type control_type;
+	void *control_data;
+	struct snd_pcm_substream *master_substream;
+	struct snd_pcm_substream *slave_substream;
+};
+
+/*
+ * ssm2602 register cache
+ * We can't read the ssm2602 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u16 ssm2602_reg[SSM2602_CACHEREGNUM] = {
+	0x0017, 0x0017, 0x0079, 0x0079,
+	0x0000, 0x0000, 0x0000, 0x000a,
+	0x0000, 0x0000
+};
+
+/*
+ * read ssm2602 register cache
+ */
+static inline unsigned int ssm2602_read_reg_cache(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+	if (reg == SSM2602_RESET)
+		return 0;
+	if (reg >= SSM2602_CACHEREGNUM)
+		return -1;
+	return cache[reg];
+}
+
+/*
+ * write ssm2602 register cache
+ */
+static inline void ssm2602_write_reg_cache(struct snd_soc_codec *codec,
+	u16 reg, unsigned int value)
+{
+	u16 *cache = codec->reg_cache;
+	if (reg >= SSM2602_CACHEREGNUM)
+		return;
+	cache[reg] = value;
+}
+
+/*
+ * write to the ssm2602 register space
+ */
+static int ssm2602_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int value)
+{
+	u8 data[2];
+
+	/* data is
+	 *   D15..D9 ssm2602 register offset
+	 *   D8...D0 register data
+	 */
+	data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+	data[1] = value & 0x00ff;
+
+	ssm2602_write_reg_cache(codec, reg, value);
+	if (codec->hw_write(codec->control_data, data, 2) == 2)
+		return 0;
+	else
+		return -EIO;
+}
+
+#define ssm2602_reset(c)	ssm2602_write(c, SSM2602_RESET, 0)
+
+/*Appending several "None"s just for OSS mixer use*/
+static const char *ssm2602_input_select[] = {
+	"Line", "Mic", "None", "None", "None",
+	"None", "None", "None",
+};
+
+static const char *ssm2602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum ssm2602_enum[] = {
+	SOC_ENUM_SINGLE(SSM2602_APANA, 2, 2, ssm2602_input_select),
+	SOC_ENUM_SINGLE(SSM2602_APDIGI, 1, 4, ssm2602_deemph),
+};
+
+static const struct snd_kcontrol_new ssm2602_snd_controls[] = {
+
+SOC_DOUBLE_R("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V,
+	0, 127, 0),
+SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V,
+	7, 1, 0),
+
+SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0),
+SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1),
+
+SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0),
+SOC_SINGLE("Mic Boost2 (+20dB)", SSM2602_APANA, 7, 1, 0),
+SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1),
+
+SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1),
+
+SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1),
+SOC_SINGLE("Store DC Offset Switch", SSM2602_APDIGI, 4, 1, 0),
+
+SOC_ENUM("Capture Source", ssm2602_enum[0]),
+
+SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]),
+};
+
+/* Output Mixer */
+static const struct snd_kcontrol_new ssm2602_output_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0),
+SOC_DAPM_SINGLE("Mic Sidetone Switch", SSM2602_APANA, 5, 1, 0),
+SOC_DAPM_SINGLE("HiFi Playback Switch", SSM2602_APANA, 4, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new ssm2602_input_mux_controls =
+SOC_DAPM_ENUM("Input Select", ssm2602_enum[0]);
+
+static const struct snd_soc_dapm_widget ssm2602_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Output Mixer", SSM2602_PWR, 4, 1,
+	&ssm2602_output_mixer_controls[0],
+	ARRAY_SIZE(ssm2602_output_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2602_PWR, 3, 1),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("LHPOUT"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("RHPOUT"),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2602_PWR, 2, 1),
+SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &ssm2602_input_mux_controls),
+SND_SOC_DAPM_PGA("Line Input", SSM2602_PWR, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", SSM2602_PWR, 1, 1),
+SND_SOC_DAPM_INPUT("MICIN"),
+SND_SOC_DAPM_INPUT("RLINEIN"),
+SND_SOC_DAPM_INPUT("LLINEIN"),
+};
+
+static const struct snd_soc_dapm_route audio_conn[] = {
+	/* output mixer */
+	{"Output Mixer", "Line Bypass Switch", "Line Input"},
+	{"Output Mixer", "HiFi Playback Switch", "DAC"},
+	{"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
+
+	/* outputs */
+	{"RHPOUT", NULL, "Output Mixer"},
+	{"ROUT", NULL, "Output Mixer"},
+	{"LHPOUT", NULL, "Output Mixer"},
+	{"LOUT", NULL, "Output Mixer"},
+
+	/* input mux */
+	{"Input Mux", "Line", "Line Input"},
+	{"Input Mux", "Mic", "Mic Bias"},
+	{"ADC", NULL, "Input Mux"},
+
+	/* inputs */
+	{"Line Input", NULL, "LLINEIN"},
+	{"Line Input", NULL, "RLINEIN"},
+	{"Mic Bias", NULL, "MICIN"},
+};
+
+static int ssm2602_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, ssm2602_dapm_widgets,
+				  ARRAY_SIZE(ssm2602_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_conn, ARRAY_SIZE(audio_conn));
+
+	return 0;
+}
+
+struct _coeff_div {
+	u32 mclk;
+	u32 rate;
+	u16 fs;
+	u8 sr:4;
+	u8 bosr:1;
+	u8 usb:1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+	/* 48k */
+	{12288000, 48000, 256, 0x0, 0x0, 0x0},
+	{18432000, 48000, 384, 0x0, 0x1, 0x0},
+	{12000000, 48000, 250, 0x0, 0x0, 0x1},
+
+	/* 32k */
+	{12288000, 32000, 384, 0x6, 0x0, 0x0},
+	{18432000, 32000, 576, 0x6, 0x1, 0x0},
+	{12000000, 32000, 375, 0x6, 0x0, 0x1},
+
+	/* 8k */
+	{12288000, 8000, 1536, 0x3, 0x0, 0x0},
+	{18432000, 8000, 2304, 0x3, 0x1, 0x0},
+	{11289600, 8000, 1408, 0xb, 0x0, 0x0},
+	{16934400, 8000, 2112, 0xb, 0x1, 0x0},
+	{12000000, 8000, 1500, 0x3, 0x0, 0x1},
+
+	/* 96k */
+	{12288000, 96000, 128, 0x7, 0x0, 0x0},
+	{18432000, 96000, 192, 0x7, 0x1, 0x0},
+	{12000000, 96000, 125, 0x7, 0x0, 0x1},
+
+	/* 44.1k */
+	{11289600, 44100, 256, 0x8, 0x0, 0x0},
+	{16934400, 44100, 384, 0x8, 0x1, 0x0},
+	{12000000, 44100, 272, 0x8, 0x1, 0x1},
+
+	/* 88.2k */
+	{11289600, 88200, 128, 0xf, 0x0, 0x0},
+	{16934400, 88200, 192, 0xf, 0x1, 0x0},
+	{12000000, 88200, 136, 0xf, 0x1, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+		if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+			return i;
+	}
+	return i;
+}
+
+static int ssm2602_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	u16 srate;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec);
+	struct i2c_client *i2c = codec->control_data;
+	u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3;
+	int i = get_coeff(ssm2602->sysclk, params_rate(params));
+
+	if (substream == ssm2602->slave_substream) {
+		dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n");
+		return 0;
+	}
+
+	/*no match is found*/
+	if (i == ARRAY_SIZE(coeff_div))
+		return -EINVAL;
+
+	srate = (coeff_div[i].sr << 2) |
+		(coeff_div[i].bosr << 1) | coeff_div[i].usb;
+
+	ssm2602_write(codec, SSM2602_ACTIVE, 0);
+	ssm2602_write(codec, SSM2602_SRATE, srate);
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x0004;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x0008;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface |= 0x000c;
+		break;
+	}
+	ssm2602_write(codec, SSM2602_IFACE, iface);
+	ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC);
+	return 0;
+}
+
+static int ssm2602_startup(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec);
+	struct i2c_client *i2c = codec->control_data;
+	struct snd_pcm_runtime *master_runtime;
+
+	/* The DAI has shared clocks so if we already have a playback or
+	 * capture going then constrain this substream to match it.
+	 * TODO: the ssm2602 allows pairs of non-matching PB/REC rates
+	 */
+	if (ssm2602->master_substream) {
+		master_runtime = ssm2602->master_substream->runtime;
+		dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n",
+			master_runtime->sample_bits,
+			master_runtime->rate);
+
+		if (master_runtime->rate != 0)
+			snd_pcm_hw_constraint_minmax(substream->runtime,
+						     SNDRV_PCM_HW_PARAM_RATE,
+						     master_runtime->rate,
+						     master_runtime->rate);
+
+		if (master_runtime->sample_bits != 0)
+			snd_pcm_hw_constraint_minmax(substream->runtime,
+						     SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+						     master_runtime->sample_bits,
+						     master_runtime->sample_bits);
+
+		ssm2602->slave_substream = substream;
+	} else
+		ssm2602->master_substream = substream;
+
+	return 0;
+}
+
+static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	/* set active */
+	ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC);
+
+	return 0;
+}
+
+static void ssm2602_shutdown(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec);
+
+	/* deactivate */
+	if (!codec->active)
+		ssm2602_write(codec, SSM2602_ACTIVE, 0);
+
+	if (ssm2602->master_substream == substream)
+		ssm2602->master_substream = ssm2602->slave_substream;
+
+	ssm2602->slave_substream = NULL;
+}
+
+static int ssm2602_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = ssm2602_read_reg_cache(codec, SSM2602_APDIGI) & ~APDIGI_ENABLE_DAC_MUTE;
+	if (mute)
+		ssm2602_write(codec, SSM2602_APDIGI,
+				mute_reg | APDIGI_ENABLE_DAC_MUTE);
+	else
+		ssm2602_write(codec, SSM2602_APDIGI, mute_reg);
+	return 0;
+}
+
+static int ssm2602_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec);
+	switch (freq) {
+	case 11289600:
+	case 12000000:
+	case 12288000:
+	case 16934400:
+	case 18432000:
+		ssm2602->sysclk = freq;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int ssm2602_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = 0;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		iface |= 0x0040;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x0013;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface |= 0x0003;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x0090;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x0080;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x0010;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* set iface */
+	ssm2602_write(codec, SSM2602_IFACE, iface);
+	return 0;
+}
+
+static int ssm2602_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 reg = ssm2602_read_reg_cache(codec, SSM2602_PWR) & 0xff7f;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		/* vref/mid, osc on, dac unmute */
+		ssm2602_write(codec, SSM2602_PWR, reg);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		/* everything off except vref/vmid, */
+		ssm2602_write(codec, SSM2602_PWR, reg | PWR_CLK_OUT_PDN);
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* everything off, dac mute, inactive */
+		ssm2602_write(codec, SSM2602_ACTIVE, 0);
+		ssm2602_write(codec, SSM2602_PWR, 0xffff);
+		break;
+
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_32000 |\
+		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
+		SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+		SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops ssm2602_dai_ops = {
+	.startup	= ssm2602_startup,
+	.prepare	= ssm2602_pcm_prepare,
+	.hw_params	= ssm2602_hw_params,
+	.shutdown	= ssm2602_shutdown,
+	.digital_mute	= ssm2602_mute,
+	.set_sysclk	= ssm2602_set_dai_sysclk,
+	.set_fmt	= ssm2602_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver ssm2602_dai = {
+	.name = "ssm2602-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SSM2602_RATES,
+		.formats = SSM2602_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SSM2602_RATES,
+		.formats = SSM2602_FORMATS,},
+	.ops = &ssm2602_dai_ops,
+};
+
+static int ssm2602_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int ssm2602_resume(struct snd_soc_codec *codec)
+{
+	int i;
+	u8 data[2];
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 0; i < ARRAY_SIZE(ssm2602_reg); i++) {
+		data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+		data[1] = cache[i] & 0x00ff;
+		codec->hw_write(codec->control_data, data, 2);
+	}
+	ssm2602_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	return 0;
+}
+
+static int ssm2602_probe(struct snd_soc_codec *codec)
+{
+	struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0, reg;
+
+	pr_info("ssm2602 Audio Codec %s", SSM2602_VERSION);
+
+	codec->control_data = ssm2602->control_data;
+
+	ssm2602_reset(codec);
+
+	/*power on device*/
+	ssm2602_write(codec, SSM2602_ACTIVE, 0);
+	/* set the update bits */
+	reg = ssm2602_read_reg_cache(codec, SSM2602_LINVOL);
+	ssm2602_write(codec, SSM2602_LINVOL, reg | LINVOL_LRIN_BOTH);
+	reg = ssm2602_read_reg_cache(codec, SSM2602_RINVOL);
+	ssm2602_write(codec, SSM2602_RINVOL, reg | RINVOL_RLIN_BOTH);
+	reg = ssm2602_read_reg_cache(codec, SSM2602_LOUT1V);
+	ssm2602_write(codec, SSM2602_LOUT1V, reg | LOUT1V_LRHP_BOTH);
+	reg = ssm2602_read_reg_cache(codec, SSM2602_ROUT1V);
+	ssm2602_write(codec, SSM2602_ROUT1V, reg | ROUT1V_RLHP_BOTH);
+	/*select Line in as default input*/
+	ssm2602_write(codec, SSM2602_APANA, APANA_SELECT_DAC |
+			APANA_ENABLE_MIC_BOOST);
+	ssm2602_write(codec, SSM2602_PWR, 0);
+
+	snd_soc_add_controls(codec, ssm2602_snd_controls,
+				ARRAY_SIZE(ssm2602_snd_controls));
+	ssm2602_add_widgets(codec);
+
+	return ret;
+}
+
+/* remove everything here */
+static int ssm2602_remove(struct snd_soc_codec *codec)
+{
+	ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_ssm2602 = {
+	.probe =	ssm2602_probe,
+	.remove =	ssm2602_remove,
+	.suspend =	ssm2602_suspend,
+	.resume =	ssm2602_resume,
+	.read = ssm2602_read_reg_cache,
+	.write = ssm2602_write,
+	.set_bias_level = ssm2602_set_bias_level,
+	.reg_cache_size = sizeof(ssm2602_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = ssm2602_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * ssm2602 2 wire address is determined by GPIO5
+ * state during powerup.
+ *    low  = 0x1a
+ *    high = 0x1b
+ */
+static int ssm2602_i2c_probe(struct i2c_client *i2c,
+			     const struct i2c_device_id *id)
+{
+	struct ssm2602_priv *ssm2602;
+	int ret;
+
+	ssm2602 = kzalloc(sizeof(struct ssm2602_priv), GFP_KERNEL);
+	if (ssm2602 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, ssm2602);
+	ssm2602->control_data = i2c;
+	ssm2602->control_type = SND_SOC_I2C;
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_ssm2602, &ssm2602_dai, 1);
+	if (ret < 0)
+		kfree(ssm2602);
+	return ret;
+}
+
+static int ssm2602_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id ssm2602_i2c_id[] = {
+	{ "ssm2602", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ssm2602_i2c_id);
+
+/* corgi i2c codec control layer */
+static struct i2c_driver ssm2602_i2c_driver = {
+	.driver = {
+		.name = "ssm2602-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe = ssm2602_i2c_probe,
+	.remove = ssm2602_i2c_remove,
+	.id_table = ssm2602_i2c_id,
+};
+#endif
+
+
+static int __init ssm2602_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&ssm2602_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register SSM2602 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(ssm2602_modinit);
+
+static void __exit ssm2602_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&ssm2602_i2c_driver);
+#endif
+}
+module_exit(ssm2602_exit);
+
+MODULE_DESCRIPTION("ASoC ssm2602 driver");
+MODULE_AUTHOR("Cliff Cai");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/ssm2602.h b/linux/sound/soc/codecs/ssm2602.h
new file mode 100644
index 0000000..42a47d0
--- /dev/null
+++ b/linux/sound/soc/codecs/ssm2602.h
@@ -0,0 +1,127 @@
+/*
+ * File:         sound/soc/codecs/ssm2602.h
+ * Author:       Cliff Cai <Cliff.Cai@analog.com>
+ *
+ * Created:      Tue June 06 2008
+ *
+ * Modified:
+ *               Copyright 2008 Analog Devices Inc.
+ *
+ * Bugs:         Enter bugs at http://blackfin.uclinux.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, see the file COPYING, or write
+ * to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _SSM2602_H
+#define _SSM2602_H
+
+/* SSM2602 Codec Register definitions */
+
+#define SSM2602_LINVOL   0x00
+#define SSM2602_RINVOL   0x01
+#define SSM2602_LOUT1V   0x02
+#define SSM2602_ROUT1V   0x03
+#define SSM2602_APANA    0x04
+#define SSM2602_APDIGI   0x05
+#define SSM2602_PWR      0x06
+#define SSM2602_IFACE    0x07
+#define SSM2602_SRATE    0x08
+#define SSM2602_ACTIVE   0x09
+#define SSM2602_RESET	 0x0f
+
+/*SSM2602 Codec Register Field definitions
+ *(Mask value to extract the corresponding Register field)
+ */
+
+/*Left ADC Volume Control (SSM2602_REG_LEFT_ADC_VOL)*/
+#define     LINVOL_LIN_VOL                0x01F   /* Left Channel PGA Volume control                      */
+#define     LINVOL_LIN_ENABLE_MUTE        0x080   /* Left Channel Input Mute                              */
+#define     LINVOL_LRIN_BOTH              0x100   /* Left Channel Line Input Volume update                */
+
+/*Right ADC Volume Control (SSM2602_REG_RIGHT_ADC_VOL)*/
+#define     RINVOL_RIN_VOL                0x01F   /* Right Channel PGA Volume control                     */
+#define     RINVOL_RIN_ENABLE_MUTE        0x080   /* Right Channel Input Mute                             */
+#define     RINVOL_RLIN_BOTH              0x100   /* Right Channel Line Input Volume update               */
+
+/*Left DAC Volume Control (SSM2602_REG_LEFT_DAC_VOL)*/
+#define     LOUT1V_LHP_VOL                0x07F   /* Left Channel Headphone volume control                */
+#define     LOUT1V_ENABLE_LZC             0x080   /* Left Channel Zero cross detect enable                */
+#define     LOUT1V_LRHP_BOTH              0x100   /* Left Channel Headphone volume update                 */
+
+/*Right DAC Volume Control (SSM2602_REG_RIGHT_DAC_VOL)*/
+#define     ROUT1V_RHP_VOL                0x07F   /* Right Channel Headphone volume control               */
+#define     ROUT1V_ENABLE_RZC             0x080   /* Right Channel Zero cross detect enable               */
+#define     ROUT1V_RLHP_BOTH              0x100   /* Right Channel Headphone volume update                */
+
+/*Analogue Audio Path Control (SSM2602_REG_ANALOGUE_PATH)*/
+#define     APANA_ENABLE_MIC_BOOST       0x001   /* Primary Microphone Amplifier gain booster control    */
+#define     APANA_ENABLE_MIC_MUTE        0x002   /* Microphone Mute Control                              */
+#define     APANA_ADC_IN_SELECT          0x004   /* Microphone/Line IN select to ADC (1=MIC, 0=Line In)  */
+#define     APANA_ENABLE_BYPASS          0x008   /* Line input bypass to line output                     */
+#define     APANA_SELECT_DAC             0x010   /* Select DAC (1=Select DAC, 0=Don't Select DAC)        */
+#define     APANA_ENABLE_SIDETONE        0x020   /* Enable/Disable Side Tone                             */
+#define     APANA_SIDETONE_ATTN          0x0C0   /* Side Tone Attenuation                                */
+#define     APANA_ENABLE_MIC_BOOST2      0x100   /* Secondary Microphone Amplifier gain booster control  */
+
+/*Digital Audio Path Control (SSM2602_REG_DIGITAL_PATH)*/
+#define     APDIGI_ENABLE_ADC_HPF         0x001   /* Enable/Disable ADC Highpass Filter                   */
+#define     APDIGI_DE_EMPHASIS            0x006   /* De-Emphasis Control                                  */
+#define     APDIGI_ENABLE_DAC_MUTE        0x008   /* DAC Mute Control                                     */
+#define     APDIGI_STORE_OFFSET           0x010   /* Store/Clear DC offset when HPF is disabled           */
+
+/*Power Down Control (SSM2602_REG_POWER)
+ *(1=Enable PowerDown, 0=Disable PowerDown)
+ */
+#define     PWR_LINE_IN_PDN            0x001   /* Line Input Power Down                                */
+#define     PWR_MIC_PDN                0x002   /* Microphone Input & Bias Power Down                   */
+#define     PWR_ADC_PDN                0x004   /* ADC Power Down                                       */
+#define     PWR_DAC_PDN                0x008   /* DAC Power Down                                       */
+#define     PWR_OUT_PDN                0x010   /* Outputs Power Down                                   */
+#define     PWR_OSC_PDN                0x020   /* Oscillator Power Down                                */
+#define     PWR_CLK_OUT_PDN            0x040   /* CLKOUT Power Down                                    */
+#define     PWR_POWER_OFF              0x080   /* POWEROFF Mode                                        */
+
+/*Digital Audio Interface Format (SSM2602_REG_DIGITAL_IFACE)*/
+#define     IFACE_IFACE_FORMAT           0x003   /* Digital Audio input format control                   */
+#define     IFACE_AUDIO_DATA_LEN         0x00C   /* Audio Data word length control                       */
+#define     IFACE_DAC_LR_POLARITY        0x010   /* Polarity Control for clocks in RJ,LJ and I2S modes   */
+#define     IFACE_DAC_LR_SWAP            0x020   /* Swap DAC data control                                */
+#define     IFACE_ENABLE_MASTER          0x040   /* Enable/Disable Master Mode                           */
+#define     IFACE_BCLK_INVERT            0x080   /* Bit Clock Inversion control                          */
+
+/*Sampling Control (SSM2602_REG_SAMPLING_CTRL)*/
+#define     SRATE_ENABLE_USB_MODE        0x001   /* Enable/Disable USB Mode                              */
+#define     SRATE_BOS_RATE               0x002   /* Base Over-Sampling rate                              */
+#define     SRATE_SAMPLE_RATE            0x03C   /* Clock setting condition (Sampling rate control)      */
+#define     SRATE_CORECLK_DIV2           0x040   /* Core Clock divider select                            */
+#define     SRATE_CLKOUT_DIV2            0x080   /* Clock Out divider select                             */
+
+/*Active Control (SSM2602_REG_ACTIVE_CTRL)*/
+#define     ACTIVE_ACTIVATE_CODEC         0x001   /* Activate Codec Digital Audio Interface               */
+
+/*********************************************************************/
+
+#define SSM2602_CACHEREGNUM 	10
+
+#define SSM2602_SYSCLK	0
+#define SSM2602_DAI		0
+
+struct ssm2602_setup_data {
+	int i2c_bus;
+	unsigned short i2c_address;
+};
+
+#endif
diff --git a/linux/sound/soc/codecs/stac9766.c b/linux/sound/soc/codecs/stac9766.c
new file mode 100644
index 0000000..061f9e5
--- /dev/null
+++ b/linux/sound/soc/codecs/stac9766.c
@@ -0,0 +1,425 @@
+/*
+ * stac9766.c  --  ALSA SoC STAC9766 codec support
+ *
+ * Copyright 2009 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.com>
+ *
+ *  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.
+ *
+ *  Features:-
+ *
+ *   o Support for AC97 Codec, S/PDIF
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "stac9766.h"
+
+#define STAC9766_VERSION "0.10"
+
+/*
+ * STAC9766 register cache
+ */
+static const u16 stac9766_reg[] = {
+	0x6A90, 0x8000, 0x8000, 0x8000, /* 6 */
+	0x0000, 0x0000, 0x8008, 0x8008, /* e */
+	0x8808, 0x8808, 0x8808, 0x8808, /* 16 */
+	0x8808, 0x0000, 0x8000, 0x0000, /* 1e */
+	0x0000, 0x0000, 0x0000, 0x000f, /* 26 */
+	0x0a05, 0x0400, 0xbb80, 0x0000, /* 2e */
+	0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */
+	0x0000, 0x2000, 0x0000, 0x0100, /* 3e */
+	0x0000, 0x0000, 0x0080, 0x0000, /* 46 */
+	0x0000, 0x0000, 0x0003, 0xffff, /* 4e */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 56 */
+	0x4000, 0x0000, 0x0000, 0x0000, /* 5e */
+	0x1201, 0xFFFF, 0xFFFF, 0x0000, /* 66 */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 6e */
+	0x0000, 0x0000, 0x0000, 0x0006, /* 76 */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 7e */
+};
+
+static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX",
+			"Line", "Stereo Mix", "Mono Mix", "Phone"};
+static const char *stac9766_mono_mux[] = {"Mix", "Mic"};
+static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"};
+static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"};
+static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"};
+static const char *stac9766_record_all_mux[] = {"All analog",
+	"Analog plus DAC"};
+static const char *stac9766_boost1[] = {"0dB", "10dB"};
+static const char *stac9766_boost2[] = {"0dB", "20dB"};
+static const char *stac9766_stereo_mic[] = {"Off", "On"};
+
+static const struct soc_enum stac9766_record_enum =
+	SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, stac9766_record_mux);
+static const struct soc_enum stac9766_mono_enum =
+	SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, stac9766_mono_mux);
+static const struct soc_enum stac9766_mic_enum =
+	SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, stac9766_mic_mux);
+static const struct soc_enum stac9766_SPDIF_enum =
+	SOC_ENUM_SINGLE(AC97_STAC_DA_CONTROL, 1, 2, stac9766_SPDIF_mux);
+static const struct soc_enum stac9766_popbypass_enum =
+	SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, stac9766_popbypass_mux);
+static const struct soc_enum stac9766_record_all_enum =
+	SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2,
+			stac9766_record_all_mux);
+static const struct soc_enum stac9766_boost1_enum =
+	SOC_ENUM_SINGLE(AC97_MIC, 6, 2, stac9766_boost1); /* 0/10dB */
+static const struct soc_enum stac9766_boost2_enum =
+	SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 2, 2, stac9766_boost2); /* 0/20dB */
+static const struct soc_enum stac9766_stereo_mic_enum =
+	SOC_ENUM_SINGLE(AC97_STAC_STEREO_MIC, 2, 1, stac9766_stereo_mic);
+
+static const DECLARE_TLV_DB_LINEAR(master_tlv, -4600, 0);
+static const DECLARE_TLV_DB_LINEAR(record_tlv, 0, 2250);
+static const DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0);
+static const DECLARE_TLV_DB_LINEAR(mix_tlv, -3450, 1200);
+
+static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = {
+	SOC_DOUBLE_TLV("Speaker Volume", AC97_MASTER, 8, 0, 31, 1, master_tlv),
+	SOC_SINGLE("Speaker Switch", AC97_MASTER, 15, 1, 1),
+	SOC_DOUBLE_TLV("Headphone Volume", AC97_HEADPHONE, 8, 0, 31, 1,
+		       master_tlv),
+	SOC_SINGLE("Headphone Switch", AC97_HEADPHONE, 15, 1, 1),
+	SOC_SINGLE_TLV("Mono Out Volume", AC97_MASTER_MONO, 0, 31, 1,
+		       master_tlv),
+	SOC_SINGLE("Mono Out Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+	SOC_DOUBLE_TLV("Record Volume", AC97_REC_GAIN, 8, 0, 15, 0, record_tlv),
+	SOC_SINGLE("Record Switch", AC97_REC_GAIN, 15, 1, 1),
+
+
+	SOC_SINGLE_TLV("Beep Volume", AC97_PC_BEEP, 1, 15, 1, beep_tlv),
+	SOC_SINGLE("Beep Switch", AC97_PC_BEEP, 15, 1, 1),
+	SOC_SINGLE("Beep Frequency", AC97_PC_BEEP, 5, 127, 1),
+	SOC_SINGLE_TLV("Phone Volume", AC97_PHONE, 0, 31, 1, mix_tlv),
+	SOC_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1),
+
+	SOC_ENUM("Mic Boost1", stac9766_boost1_enum),
+	SOC_ENUM("Mic Boost2", stac9766_boost2_enum),
+	SOC_SINGLE_TLV("Mic Volume", AC97_MIC, 0, 31, 1, mix_tlv),
+	SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1),
+	SOC_ENUM("Stereo Mic", stac9766_stereo_mic_enum),
+
+	SOC_DOUBLE_TLV("Line Volume", AC97_LINE, 8, 0, 31, 1, mix_tlv),
+	SOC_SINGLE("Line Switch", AC97_LINE, 15, 1, 1),
+	SOC_DOUBLE_TLV("CD Volume", AC97_CD, 8, 0, 31, 1, mix_tlv),
+	SOC_SINGLE("CD Switch", AC97_CD, 15, 1, 1),
+	SOC_DOUBLE_TLV("AUX Volume", AC97_AUX, 8, 0, 31, 1, mix_tlv),
+	SOC_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1),
+	SOC_DOUBLE_TLV("Video Volume", AC97_VIDEO, 8, 0, 31, 1, mix_tlv),
+	SOC_SINGLE("Video Switch", AC97_VIDEO, 15, 1, 1),
+
+	SOC_DOUBLE_TLV("DAC Volume", AC97_PCM, 8, 0, 31, 1, mix_tlv),
+	SOC_SINGLE("DAC Switch", AC97_PCM, 15, 1, 1),
+	SOC_SINGLE("Loopback Test Switch", AC97_GENERAL_PURPOSE, 7, 1, 0),
+	SOC_SINGLE("3D Volume", AC97_3D_CONTROL, 3, 2, 1),
+	SOC_SINGLE("3D Switch", AC97_GENERAL_PURPOSE, 13, 1, 0),
+
+	SOC_ENUM("SPDIF Mux", stac9766_SPDIF_enum),
+	SOC_ENUM("Mic1/2 Mux", stac9766_mic_enum),
+	SOC_ENUM("Record All Mux", stac9766_record_all_enum),
+	SOC_ENUM("Record Mux", stac9766_record_enum),
+	SOC_ENUM("Mono Mux", stac9766_mono_enum),
+	SOC_ENUM("Pop Bypass Mux", stac9766_popbypass_enum),
+};
+
+static int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+			       unsigned int val)
+{
+	u16 *cache = codec->reg_cache;
+
+	if (reg > AC97_STAC_PAGE0) {
+		stac9766_ac97_write(codec, AC97_INT_PAGING, 0);
+		soc_ac97_ops.write(codec->ac97, reg, val);
+		stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
+		return 0;
+	}
+	if (reg / 2 >= ARRAY_SIZE(stac9766_reg))
+		return -EIO;
+
+	soc_ac97_ops.write(codec->ac97, reg, val);
+	cache[reg / 2] = val;
+	return 0;
+}
+
+static unsigned int stac9766_ac97_read(struct snd_soc_codec *codec,
+				       unsigned int reg)
+{
+	u16 val = 0, *cache = codec->reg_cache;
+
+	if (reg > AC97_STAC_PAGE0) {
+		stac9766_ac97_write(codec, AC97_INT_PAGING, 0);
+		val = soc_ac97_ops.read(codec->ac97, reg - AC97_STAC_PAGE0);
+		stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
+		return val;
+	}
+	if (reg / 2 >= ARRAY_SIZE(stac9766_reg))
+		return -EIO;
+
+	if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+		reg == AC97_INT_PAGING || reg == AC97_VENDOR_ID1 ||
+		reg == AC97_VENDOR_ID2) {
+
+		val = soc_ac97_ops.read(codec->ac97, reg);
+		return val;
+	}
+	return cache[reg / 2];
+}
+
+static int ac97_analog_prepare(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned short reg, vra;
+
+	vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
+
+	vra |= 0x1; /* enable variable rate audio */
+	vra &= ~0x4; /* disable SPDIF output */
+
+	stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		reg = AC97_PCM_FRONT_DAC_RATE;
+	else
+		reg = AC97_PCM_LR_ADC_RATE;
+
+	return stac9766_ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_digital_prepare(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned short reg, vra;
+
+	stac9766_ac97_write(codec, AC97_SPDIF, 0x2002);
+
+	vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS);
+	vra |= 0x5; /* Enable VRA and SPDIF out */
+
+	stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra);
+
+	reg = AC97_PCM_FRONT_DAC_RATE;
+
+	return stac9766_ac97_write(codec, reg, runtime->rate);
+}
+
+static int stac9766_set_bias_level(struct snd_soc_codec *codec,
+				   enum snd_soc_bias_level level)
+{
+	switch (level) {
+	case SND_SOC_BIAS_ON: /* full On */
+	case SND_SOC_BIAS_PREPARE: /* partial On */
+	case SND_SOC_BIAS_STANDBY: /* Off, with power */
+		stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000);
+		break;
+	case SND_SOC_BIAS_OFF: /* Off, without power */
+		/* disable everything including AC link */
+		stac9766_ac97_write(codec, AC97_POWERDOWN, 0xffff);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+static int stac9766_reset(struct snd_soc_codec *codec, int try_warm)
+{
+	if (try_warm && soc_ac97_ops.warm_reset) {
+		soc_ac97_ops.warm_reset(codec->ac97);
+		if (stac9766_ac97_read(codec, 0) == stac9766_reg[0])
+			return 1;
+	}
+
+	soc_ac97_ops.reset(codec->ac97);
+	if (soc_ac97_ops.warm_reset)
+		soc_ac97_ops.warm_reset(codec->ac97);
+	if (stac9766_ac97_read(codec, 0) != stac9766_reg[0])
+		return -EIO;
+	return 0;
+}
+
+static int stac9766_codec_suspend(struct snd_soc_codec *codec,
+				  pm_message_t state)
+{
+	stac9766_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int stac9766_codec_resume(struct snd_soc_codec *codec)
+{
+	u16 id, reset;
+
+	reset = 0;
+	/* give the codec an AC97 warm reset to start the link */
+reset:
+	if (reset > 5) {
+		printk(KERN_ERR "stac9766 failed to resume");
+		return -EIO;
+	}
+	codec->ac97->bus->ops->warm_reset(codec->ac97);
+	id = soc_ac97_ops.read(codec->ac97, AC97_VENDOR_ID2);
+	if (id != 0x4c13) {
+		stac9766_reset(codec, 0);
+		reset++;
+		goto reset;
+	}
+	stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops stac9766_dai_ops_analog = {
+	.prepare = ac97_analog_prepare,
+};
+
+static struct snd_soc_dai_ops stac9766_dai_ops_digital = {
+	.prepare = ac97_digital_prepare,
+};
+
+static struct snd_soc_dai_driver stac9766_dai[] = {
+{
+	.name = "stac9766-hifi-analog",
+	.ac97_control = 1,
+
+	/* stream cababilities */
+	.playback = {
+		.stream_name = "stac9766 analog",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = SND_SOC_STD_AC97_FMTS,
+	},
+	.capture = {
+		.stream_name = "stac9766 analog",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = SND_SOC_STD_AC97_FMTS,
+	},
+	/* alsa ops */
+	.ops = &stac9766_dai_ops_analog,
+},
+{
+	.name = "stac9766-hifi-IEC958",
+	.ac97_control = 1,
+
+	/* stream cababilities */
+	.playback = {
+		.stream_name = "stac9766 IEC958",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_32000 | \
+			SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE,
+	},
+	/* alsa ops */
+	.ops = &stac9766_dai_ops_digital,
+}
+};
+
+static int stac9766_codec_probe(struct snd_soc_codec *codec)
+{
+	int ret = 0;
+
+	printk(KERN_INFO "STAC9766 SoC Audio Codec %s\n", STAC9766_VERSION);
+
+	ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+	if (ret < 0)
+		goto codec_err;
+
+	/* do a cold reset for the controller and then try
+	 * a warm reset followed by an optional cold reset for codec */
+	stac9766_reset(codec, 0);
+	ret = stac9766_reset(codec, 1);
+	if (ret < 0) {
+		printk(KERN_ERR "Failed to reset STAC9766: AC97 link error\n");
+		goto codec_err;
+	}
+
+	stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	snd_soc_add_controls(codec, stac9766_snd_ac97_controls,
+			     ARRAY_SIZE(stac9766_snd_ac97_controls));
+
+	return 0;
+
+codec_err:
+	snd_soc_free_ac97_codec(codec);
+	return ret;
+}
+
+static int stac9766_codec_remove(struct snd_soc_codec *codec)
+{
+	snd_soc_free_ac97_codec(codec);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_stac9766 = {
+	.write = stac9766_ac97_write,
+	.read = stac9766_ac97_read,
+	.set_bias_level = stac9766_set_bias_level,
+	.probe = stac9766_codec_probe,
+	.remove = stac9766_codec_remove,
+	.suspend = stac9766_codec_suspend,
+	.resume = stac9766_codec_resume,
+	.reg_cache_size = sizeof(stac9766_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_step = 2,
+	.reg_cache_default = stac9766_reg,
+};
+
+static __devinit int stac9766_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_stac9766, stac9766_dai, ARRAY_SIZE(stac9766_dai));
+}
+
+static int __devexit stac9766_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver stac9766_codec_driver = {
+	.driver = {
+			.name = "stac9766-codec",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = stac9766_probe,
+	.remove = __devexit_p(stac9766_remove),
+};
+
+static int __init stac9766_init(void)
+{
+	return platform_driver_register(&stac9766_codec_driver);
+}
+module_init(stac9766_init);
+
+static void __exit stac9766_exit(void)
+{
+	platform_driver_unregister(&stac9766_codec_driver);
+}
+module_exit(stac9766_exit);
+
+MODULE_DESCRIPTION("ASoC stac9766 driver");
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/stac9766.h b/linux/sound/soc/codecs/stac9766.h
new file mode 100644
index 0000000..c726f90
--- /dev/null
+++ b/linux/sound/soc/codecs/stac9766.h
@@ -0,0 +1,17 @@
+/*
+ * stac9766.h  --  STAC9766 Soc Audio driver
+ */
+
+#ifndef _STAC9766_H
+#define _STAC9766_H
+
+#define AC97_STAC_PAGE0 0x1000
+#define AC97_STAC_DA_CONTROL (AC97_STAC_PAGE0 | 0x6A)
+#define AC97_STAC_ANALOG_SPECIAL (AC97_STAC_PAGE0 | 0x6E)
+#define AC97_STAC_STEREO_MIC 0x78
+
+/* STAC9766 DAI ID's */
+#define STAC9766_DAI_AC97_ANALOG		0
+#define STAC9766_DAI_AC97_DIGITAL		1
+
+#endif
diff --git a/linux/sound/soc/codecs/tlv320aic23.c b/linux/sound/soc/codecs/tlv320aic23.c
new file mode 100644
index 0000000..e8652b1
--- /dev/null
+++ b/linux/sound/soc/codecs/tlv320aic23.c
@@ -0,0 +1,780 @@
+/*
+ * ALSA SoC TLV320AIC23 codec driver
+ *
+ * Author:      Arun KS, <arunks@mistralsolutions.com>
+ * Copyright:   (C) 2008 Mistral Solutions Pvt Ltd.,
+ *
+ * Based on sound/soc/codecs/wm8731.c by Richard Purdie
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Notes:
+ *  The AIC23 is a driver for a low power stereo audio
+ *  codec tlv320aic23
+ *
+ *  The machine layer should disable unsupported inputs/outputs by
+ *  snd_soc_dapm_disable_pin(codec, "LHPOUT"), etc.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+
+#include "tlv320aic23.h"
+
+#define AIC23_VERSION "0.1"
+
+/*
+ * AIC23 register cache
+ */
+static const u16 tlv320aic23_reg[] = {
+	0x0097, 0x0097, 0x00F9, 0x00F9,	/* 0 */
+	0x001A, 0x0004, 0x0007, 0x0001,	/* 4 */
+	0x0020, 0x0000, 0x0000, 0x0000,	/* 8 */
+	0x0000, 0x0000, 0x0000, 0x0000,	/* 12 */
+};
+
+/*
+ * read tlv320aic23 register cache
+ */
+static inline unsigned int tlv320aic23_read_reg_cache(struct snd_soc_codec
+						      *codec, unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+	if (reg >= ARRAY_SIZE(tlv320aic23_reg))
+		return -1;
+	return cache[reg];
+}
+
+/*
+ * write tlv320aic23 register cache
+ */
+static inline void tlv320aic23_write_reg_cache(struct snd_soc_codec *codec,
+					       u8 reg, u16 value)
+{
+	u16 *cache = codec->reg_cache;
+	if (reg >= ARRAY_SIZE(tlv320aic23_reg))
+		return;
+	cache[reg] = value;
+}
+
+/*
+ * write to the tlv320aic23 register space
+ */
+static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg,
+			     unsigned int value)
+{
+
+	u8 data[2];
+
+	/* TLV320AIC23 has 7 bit address and 9 bits of data
+	 * so we need to switch one data bit into reg and rest
+	 * of data into val
+	 */
+
+	if (reg > 9 && reg != 15) {
+		printk(KERN_WARNING "%s Invalid register R%u\n", __func__, reg);
+		return -1;
+	}
+
+	data[0] = (reg << 1) | (value >> 8 & 0x01);
+	data[1] = value & 0xff;
+
+	tlv320aic23_write_reg_cache(codec, reg, value);
+
+	if (codec->hw_write(codec->control_data, data, 2) == 2)
+		return 0;
+
+	printk(KERN_ERR "%s cannot write %03x to register R%u\n", __func__,
+	       value, reg);
+
+	return -EIO;
+}
+
+static const char *rec_src_text[] = { "Line", "Mic" };
+static const char *deemph_text[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum rec_src_enum =
+	SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text);
+
+static const struct snd_kcontrol_new tlv320aic23_rec_src_mux_controls =
+SOC_DAPM_ENUM("Input Select", rec_src_enum);
+
+static const struct soc_enum tlv320aic23_rec_src =
+	SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text);
+static const struct soc_enum tlv320aic23_deemph =
+	SOC_ENUM_SINGLE(TLV320AIC23_DIGT, 1, 4, deemph_text);
+
+static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -12100, 100, 0);
+static const DECLARE_TLV_DB_SCALE(input_gain_tlv, -1725, 75, 0);
+static const DECLARE_TLV_DB_SCALE(sidetone_vol_tlv, -1800, 300, 0);
+
+static int snd_soc_tlv320aic23_put_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	u16 val, reg;
+
+	val = (ucontrol->value.integer.value[0] & 0x07);
+
+	/* linear conversion to userspace
+	* 000	=	-6db
+	* 001	=	-9db
+	* 010	=	-12db
+	* 011	=	-18db (Min)
+	* 100	=	0db (Max)
+	*/
+	val = (val >= 4) ? 4  : (3 - val);
+
+	reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (~0x1C0);
+	tlv320aic23_write(codec, TLV320AIC23_ANLG, reg | (val << 6));
+
+	return 0;
+}
+
+static int snd_soc_tlv320aic23_get_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	u16 val;
+
+	val = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (0x1C0);
+	val = val >> 6;
+	val = (val >= 4) ? 4  : (3 -  val);
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+
+}
+
+#define SOC_TLV320AIC23_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+		 SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+	.tlv.p = (tlv_array), \
+	.info = snd_soc_info_volsw, .get = snd_soc_tlv320aic23_get_volsw,\
+	.put = snd_soc_tlv320aic23_put_volsw, \
+	.private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+static const struct snd_kcontrol_new tlv320aic23_snd_controls[] = {
+	SOC_DOUBLE_R_TLV("Digital Playback Volume", TLV320AIC23_LCHNVOL,
+			 TLV320AIC23_RCHNVOL, 0, 127, 0, out_gain_tlv),
+	SOC_SINGLE("Digital Playback Switch", TLV320AIC23_DIGT, 3, 1, 1),
+	SOC_DOUBLE_R("Line Input Switch", TLV320AIC23_LINVOL,
+		     TLV320AIC23_RINVOL, 7, 1, 0),
+	SOC_DOUBLE_R_TLV("Line Input Volume", TLV320AIC23_LINVOL,
+			 TLV320AIC23_RINVOL, 0, 31, 0, input_gain_tlv),
+	SOC_SINGLE("Mic Input Switch", TLV320AIC23_ANLG, 1, 1, 1),
+	SOC_SINGLE("Mic Booster Switch", TLV320AIC23_ANLG, 0, 1, 0),
+	SOC_TLV320AIC23_SINGLE_TLV("Sidetone Volume", TLV320AIC23_ANLG,
+				  6, 4, 0, sidetone_vol_tlv),
+	SOC_ENUM("Playback De-emphasis", tlv320aic23_deemph),
+};
+
+/* PGA Mixer controls for Line and Mic switch */
+static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0),
+	SOC_DAPM_SINGLE("Mic Sidetone Switch", TLV320AIC23_ANLG, 5, 1, 0),
+	SOC_DAPM_SINGLE("Playback Switch", TLV320AIC23_ANLG, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+	SND_SOC_DAPM_DAC("DAC", "Playback", TLV320AIC23_PWR, 3, 1),
+	SND_SOC_DAPM_ADC("ADC", "Capture", TLV320AIC23_PWR, 2, 1),
+	SND_SOC_DAPM_MUX("Capture Source", SND_SOC_NOPM, 0, 0,
+			 &tlv320aic23_rec_src_mux_controls),
+	SND_SOC_DAPM_MIXER("Output Mixer", TLV320AIC23_PWR, 4, 1,
+			   &tlv320aic23_output_mixer_controls[0],
+			   ARRAY_SIZE(tlv320aic23_output_mixer_controls)),
+	SND_SOC_DAPM_PGA("Line Input", TLV320AIC23_PWR, 0, 1, NULL, 0),
+	SND_SOC_DAPM_PGA("Mic Input", TLV320AIC23_PWR, 1, 1, NULL, 0),
+
+	SND_SOC_DAPM_OUTPUT("LHPOUT"),
+	SND_SOC_DAPM_OUTPUT("RHPOUT"),
+	SND_SOC_DAPM_OUTPUT("LOUT"),
+	SND_SOC_DAPM_OUTPUT("ROUT"),
+
+	SND_SOC_DAPM_INPUT("LLINEIN"),
+	SND_SOC_DAPM_INPUT("RLINEIN"),
+
+	SND_SOC_DAPM_INPUT("MICIN"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+	/* Output Mixer */
+	{"Output Mixer", "Line Bypass Switch", "Line Input"},
+	{"Output Mixer", "Playback Switch", "DAC"},
+	{"Output Mixer", "Mic Sidetone Switch", "Mic Input"},
+
+	/* Outputs */
+	{"RHPOUT", NULL, "Output Mixer"},
+	{"LHPOUT", NULL, "Output Mixer"},
+	{"LOUT", NULL, "Output Mixer"},
+	{"ROUT", NULL, "Output Mixer"},
+
+	/* Inputs */
+	{"Line Input", "NULL", "LLINEIN"},
+	{"Line Input", "NULL", "RLINEIN"},
+
+	{"Mic Input", "NULL", "MICIN"},
+
+	/* input mux */
+	{"Capture Source", "Line", "Line Input"},
+	{"Capture Source", "Mic", "Mic Input"},
+	{"ADC", NULL, "Capture Source"},
+
+};
+
+/* AIC23 driver data */
+struct aic23 {
+	enum snd_soc_control_type control_type;
+	void *control_data;
+	int mclk;
+	int requested_adc;
+	int requested_dac;
+};
+
+/*
+ * Common Crystals used
+ * 11.2896 Mhz /128 = *88.2k  /192 = 58.8k
+ * 12.0000 Mhz /125 = *96k    /136 = 88.235K
+ * 12.2880 Mhz /128 = *96k    /192 = 64k
+ * 16.9344 Mhz /128 = 132.3k /192 = *88.2k
+ * 18.4320 Mhz /128 = 144k   /192 = *96k
+ */
+
+/*
+ * Normal BOSR 0-256/2 = 128, 1-384/2 = 192
+ * USB BOSR 0-250/2 = 125, 1-272/2 = 136
+ */
+static const int bosr_usb_divisor_table[] = {
+	128, 125, 192, 136
+};
+#define LOWER_GROUP ((1<<0) | (1<<1) | (1<<2) | (1<<3) | (1<<6) | (1<<7))
+#define UPPER_GROUP ((1<<8) | (1<<9) | (1<<10) | (1<<11)        | (1<<15))
+static const unsigned short sr_valid_mask[] = {
+	LOWER_GROUP|UPPER_GROUP,	/* Normal, bosr - 0*/
+	LOWER_GROUP,			/* Usb, bosr - 0*/
+	LOWER_GROUP|UPPER_GROUP,	/* Normal, bosr - 1*/
+	UPPER_GROUP,			/* Usb, bosr - 1*/
+};
+/*
+ * Every divisor is a factor of 11*12
+ */
+#define SR_MULT (11*12)
+#define A(x) (SR_MULT/x)
+static const unsigned char sr_adc_mult_table[] = {
+	A(2), A(2), A(12), A(12),  0, 0, A(3), A(1),
+	A(2), A(2), A(11), A(11),  0, 0, 0, A(1)
+};
+static const unsigned char sr_dac_mult_table[] = {
+	A(2), A(12), A(2), A(12),  0, 0, A(3), A(1),
+	A(2), A(11), A(2), A(11),  0, 0, 0, A(1)
+};
+
+static unsigned get_score(int adc, int adc_l, int adc_h, int need_adc,
+		int dac, int dac_l, int dac_h, int need_dac)
+{
+	if ((adc >= adc_l) && (adc <= adc_h) &&
+			(dac >= dac_l) && (dac <= dac_h)) {
+		int diff_adc = need_adc - adc;
+		int diff_dac = need_dac - dac;
+		return abs(diff_adc) + abs(diff_dac);
+	}
+	return UINT_MAX;
+}
+
+static int find_rate(int mclk, u32 need_adc, u32 need_dac)
+{
+	int i, j;
+	int best_i = -1;
+	int best_j = -1;
+	int best_div = 0;
+	unsigned best_score = UINT_MAX;
+	int adc_l, adc_h, dac_l, dac_h;
+
+	need_adc *= SR_MULT;
+	need_dac *= SR_MULT;
+	/*
+	 * rates given are +/- 1/32
+	 */
+	adc_l = need_adc - (need_adc >> 5);
+	adc_h = need_adc + (need_adc >> 5);
+	dac_l = need_dac - (need_dac >> 5);
+	dac_h = need_dac + (need_dac >> 5);
+	for (i = 0; i < ARRAY_SIZE(bosr_usb_divisor_table); i++) {
+		int base = mclk / bosr_usb_divisor_table[i];
+		int mask = sr_valid_mask[i];
+		for (j = 0; j < ARRAY_SIZE(sr_adc_mult_table);
+				j++, mask >>= 1) {
+			int adc;
+			int dac;
+			int score;
+			if ((mask & 1) == 0)
+				continue;
+			adc = base * sr_adc_mult_table[j];
+			dac = base * sr_dac_mult_table[j];
+			score = get_score(adc, adc_l, adc_h, need_adc,
+					dac, dac_l, dac_h, need_dac);
+			if (best_score > score) {
+				best_score = score;
+				best_i = i;
+				best_j = j;
+				best_div = 0;
+			}
+			score = get_score((adc >> 1), adc_l, adc_h, need_adc,
+					(dac >> 1), dac_l, dac_h, need_dac);
+			/* prefer to have a /2 */
+			if ((score != UINT_MAX) && (best_score >= score)) {
+				best_score = score;
+				best_i = i;
+				best_j = j;
+				best_div = 1;
+			}
+		}
+	}
+	return (best_j << 2) | best_i | (best_div << TLV320AIC23_CLKIN_SHIFT);
+}
+
+#ifdef DEBUG
+static void get_current_sample_rates(struct snd_soc_codec *codec, int mclk,
+		u32 *sample_rate_adc, u32 *sample_rate_dac)
+{
+	int src = tlv320aic23_read_reg_cache(codec, TLV320AIC23_SRATE);
+	int sr = (src >> 2) & 0x0f;
+	int val = (mclk / bosr_usb_divisor_table[src & 3]);
+	int adc = (val * sr_adc_mult_table[sr]) / SR_MULT;
+	int dac = (val * sr_dac_mult_table[sr]) / SR_MULT;
+	if (src & TLV320AIC23_CLKIN_HALF) {
+		adc >>= 1;
+		dac >>= 1;
+	}
+	*sample_rate_adc = adc;
+	*sample_rate_dac = dac;
+}
+#endif
+
+static int set_sample_rate_control(struct snd_soc_codec *codec, int mclk,
+		u32 sample_rate_adc, u32 sample_rate_dac)
+{
+	/* Search for the right sample rate */
+	int data = find_rate(mclk, sample_rate_adc, sample_rate_dac);
+	if (data < 0) {
+		printk(KERN_ERR "%s:Invalid rate %u,%u requested\n",
+				__func__, sample_rate_adc, sample_rate_dac);
+		return -EINVAL;
+	}
+	tlv320aic23_write(codec, TLV320AIC23_SRATE, data);
+#ifdef DEBUG
+	{
+		u32 adc, dac;
+		get_current_sample_rates(codec, mclk, &adc, &dac);
+		printk(KERN_DEBUG "actual samplerate = %u,%u reg=%x\n",
+			adc, dac, data);
+	}
+#endif
+	return 0;
+}
+
+static int tlv320aic23_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets,
+				  ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+	/* set up audio path interconnects */
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	return 0;
+}
+
+static int tlv320aic23_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	u16 iface_reg;
+	int ret;
+	struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec);
+	u32 sample_rate_adc = aic23->requested_adc;
+	u32 sample_rate_dac = aic23->requested_dac;
+	u32 sample_rate = params_rate(params);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		aic23->requested_dac = sample_rate_dac = sample_rate;
+		if (!sample_rate_adc)
+			sample_rate_adc = sample_rate;
+	} else {
+		aic23->requested_adc = sample_rate_adc = sample_rate;
+		if (!sample_rate_dac)
+			sample_rate_dac = sample_rate;
+	}
+	ret = set_sample_rate_control(codec, aic23->mclk, sample_rate_adc,
+			sample_rate_dac);
+	if (ret < 0)
+		return ret;
+
+	iface_reg =
+	    tlv320aic23_read_reg_cache(codec,
+				       TLV320AIC23_DIGT_FMT) & ~(0x03 << 2);
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface_reg |= (0x01 << 2);
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface_reg |= (0x02 << 2);
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface_reg |= (0x03 << 2);
+		break;
+	}
+	tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg);
+
+	return 0;
+}
+
+static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream,
+				   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	/* set active */
+	tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0001);
+
+	return 0;
+}
+
+static void tlv320aic23_shutdown(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec);
+
+	/* deactivate */
+	if (!codec->active) {
+		udelay(50);
+		tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
+	}
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		aic23->requested_dac = 0;
+	else
+		aic23->requested_adc = 0;
+}
+
+static int tlv320aic23_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 reg;
+
+	reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT);
+	if (mute)
+		reg |= TLV320AIC23_DACM_MUTE;
+
+	else
+		reg &= ~TLV320AIC23_DACM_MUTE;
+
+	tlv320aic23_write(codec, TLV320AIC23_DIGT, reg);
+
+	return 0;
+}
+
+static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai,
+				   unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface_reg;
+
+	iface_reg =
+	    tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT_FMT) & (~0x03);
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		iface_reg |= TLV320AIC23_MS_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface_reg |= TLV320AIC23_FOR_I2S;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface_reg |= TLV320AIC23_LRP_ON;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface_reg |= TLV320AIC23_FOR_DSP;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface_reg |= TLV320AIC23_FOR_LJUST;
+		break;
+	default:
+		return -EINVAL;
+
+	}
+
+	tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg);
+
+	return 0;
+}
+
+static int tlv320aic23_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+				      int clk_id, unsigned int freq, int dir)
+{
+	struct aic23 *aic23 = snd_soc_dai_get_drvdata(codec_dai);
+	aic23->mclk = freq;
+	return 0;
+}
+
+static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec,
+				      enum snd_soc_bias_level level)
+{
+	u16 reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_PWR) & 0xff7f;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		/* vref/mid, osc on, dac unmute */
+		reg &= ~(TLV320AIC23_DEVICE_PWR_OFF | TLV320AIC23_OSC_OFF | \
+			TLV320AIC23_DAC_OFF);
+		tlv320aic23_write(codec, TLV320AIC23_PWR, reg);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		/* everything off except vref/vmid, */
+		tlv320aic23_write(codec, TLV320AIC23_PWR, reg | \
+			TLV320AIC23_CLK_OFF);
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* everything off, dac mute, inactive */
+		tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
+		tlv320aic23_write(codec, TLV320AIC23_PWR, 0xffff);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define AIC23_RATES	SNDRV_PCM_RATE_8000_96000
+#define AIC23_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+			 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops tlv320aic23_dai_ops = {
+	.prepare	= tlv320aic23_pcm_prepare,
+	.hw_params	= tlv320aic23_hw_params,
+	.shutdown	= tlv320aic23_shutdown,
+	.digital_mute	= tlv320aic23_mute,
+	.set_fmt	= tlv320aic23_set_dai_fmt,
+	.set_sysclk	= tlv320aic23_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver tlv320aic23_dai = {
+	.name = "tlv320aic23-hifi",
+	.playback = {
+		     .stream_name = "Playback",
+		     .channels_min = 2,
+		     .channels_max = 2,
+		     .rates = AIC23_RATES,
+		     .formats = AIC23_FORMATS,},
+	.capture = {
+		    .stream_name = "Capture",
+		    .channels_min = 2,
+		    .channels_max = 2,
+		    .rates = AIC23_RATES,
+		    .formats = AIC23_FORMATS,},
+	.ops = &tlv320aic23_dai_ops,
+};
+
+static int tlv320aic23_suspend(struct snd_soc_codec *codec,
+			       pm_message_t state)
+{
+	tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int tlv320aic23_resume(struct snd_soc_codec *codec)
+{
+	u16 reg;
+
+	/* Sync reg_cache with the hardware */
+	for (reg = 0; reg <= TLV320AIC23_ACTIVE; reg++) {
+		u16 val = tlv320aic23_read_reg_cache(codec, reg);
+		tlv320aic23_write(codec, reg, val);
+	}
+	tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static int tlv320aic23_probe(struct snd_soc_codec *codec)
+{
+	struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec);
+	int reg;
+
+	printk(KERN_INFO "AIC23 Audio Codec %s\n", AIC23_VERSION);
+	codec->control_data = aic23->control_data;
+	codec->hw_write = (hw_write_t)i2c_master_send;
+	codec->hw_read = NULL;
+
+	/* Reset codec */
+	tlv320aic23_write(codec, TLV320AIC23_RESET, 0);
+
+	/* power on device */
+	tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	tlv320aic23_write(codec, TLV320AIC23_DIGT, TLV320AIC23_DEEMP_44K);
+
+	/* Unmute input */
+	reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_LINVOL);
+	tlv320aic23_write(codec, TLV320AIC23_LINVOL,
+			  (reg & (~TLV320AIC23_LIM_MUTED)) |
+			  (TLV320AIC23_LRS_ENABLED));
+
+	reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_RINVOL);
+	tlv320aic23_write(codec, TLV320AIC23_RINVOL,
+			  (reg & (~TLV320AIC23_LIM_MUTED)) |
+			  TLV320AIC23_LRS_ENABLED);
+
+	reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG);
+	tlv320aic23_write(codec, TLV320AIC23_ANLG,
+			 (reg) & (~TLV320AIC23_BYPASS_ON) &
+			 (~TLV320AIC23_MICM_MUTED));
+
+	/* Default output volume */
+	tlv320aic23_write(codec, TLV320AIC23_LCHNVOL,
+			  TLV320AIC23_DEFAULT_OUT_VOL &
+			  TLV320AIC23_OUT_VOL_MASK);
+	tlv320aic23_write(codec, TLV320AIC23_RCHNVOL,
+			  TLV320AIC23_DEFAULT_OUT_VOL &
+			  TLV320AIC23_OUT_VOL_MASK);
+
+	tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x1);
+
+	snd_soc_add_controls(codec, tlv320aic23_snd_controls,
+				ARRAY_SIZE(tlv320aic23_snd_controls));
+	tlv320aic23_add_widgets(codec);
+
+	return 0;
+}
+
+static int tlv320aic23_remove(struct snd_soc_codec *codec)
+{
+	tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_tlv320aic23 = {
+	.reg_cache_size = ARRAY_SIZE(tlv320aic23_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = tlv320aic23_reg,
+	.probe = tlv320aic23_probe,
+	.remove = tlv320aic23_remove,
+	.suspend = tlv320aic23_suspend,
+	.resume = tlv320aic23_resume,
+	.read = tlv320aic23_read_reg_cache,
+	.write = tlv320aic23_write,
+	.set_bias_level = tlv320aic23_set_bias_level,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * If the i2c layer weren't so broken, we could pass this kind of data
+ * around
+ */
+static int tlv320aic23_codec_probe(struct i2c_client *i2c,
+				   const struct i2c_device_id *i2c_id)
+{
+	struct aic23 *aic23;
+	int ret;
+
+	if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EINVAL;
+
+	aic23 = kzalloc(sizeof(struct aic23), GFP_KERNEL);
+	if (aic23 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, aic23);
+	aic23->control_data = i2c;
+	aic23->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_tlv320aic23, &tlv320aic23_dai, 1);
+	if (ret < 0)
+		kfree(aic23);
+	return ret;
+}
+static int __exit tlv320aic23_i2c_remove(struct i2c_client *i2c)
+{
+	snd_soc_unregister_codec(&i2c->dev);
+	kfree(i2c_get_clientdata(i2c));
+	return 0;
+}
+
+static const struct i2c_device_id tlv320aic23_id[] = {
+	{"tlv320aic23", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, tlv320aic23_id);
+
+static struct i2c_driver tlv320aic23_i2c_driver = {
+	.driver = {
+		   .name = "tlv320aic23-codec",
+		   },
+	.probe = tlv320aic23_codec_probe,
+	.remove = __exit_p(tlv320aic23_i2c_remove),
+	.id_table = tlv320aic23_id,
+};
+
+#endif
+
+static int __init tlv320aic23_modinit(void)
+{
+	int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&tlv320aic23_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register TLV320AIC23 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(tlv320aic23_modinit);
+
+static void __exit tlv320aic23_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&tlv320aic23_i2c_driver);
+#endif
+}
+module_exit(tlv320aic23_exit);
+
+MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver");
+MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/tlv320aic23.h b/linux/sound/soc/codecs/tlv320aic23.h
new file mode 100644
index 0000000..e804120
--- /dev/null
+++ b/linux/sound/soc/codecs/tlv320aic23.h
@@ -0,0 +1,119 @@
+/*
+ * ALSA SoC TLV320AIC23 codec driver
+ *
+ * Author:      Arun KS, <arunks@mistralsolutions.com>
+ * Copyright:   (C) 2008 Mistral Solutions Pvt Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _TLV320AIC23_H
+#define _TLV320AIC23_H
+
+/* Codec TLV320AIC23 */
+#define TLV320AIC23_LINVOL		0x00
+#define TLV320AIC23_RINVOL		0x01
+#define TLV320AIC23_LCHNVOL		0x02
+#define TLV320AIC23_RCHNVOL		0x03
+#define TLV320AIC23_ANLG		0x04
+#define TLV320AIC23_DIGT		0x05
+#define TLV320AIC23_PWR			0x06
+#define TLV320AIC23_DIGT_FMT		0x07
+#define TLV320AIC23_SRATE		0x08
+#define TLV320AIC23_ACTIVE		0x09
+#define TLV320AIC23_RESET		0x0F
+
+/* Left (right) line input volume control register */
+#define TLV320AIC23_LRS_ENABLED		0x0100
+#define TLV320AIC23_LIM_MUTED		0x0080
+#define TLV320AIC23_LIV_DEFAULT		0x0017
+#define TLV320AIC23_LIV_MAX		0x001f
+#define TLV320AIC23_LIV_MIN		0x0000
+
+/* Left (right) channel headphone volume control register */
+#define TLV320AIC23_LZC_ON		0x0080
+#define TLV320AIC23_LHV_DEFAULT		0x0079
+#define TLV320AIC23_LHV_MAX		0x007f
+#define TLV320AIC23_LHV_MIN		0x0000
+
+/* Analog audio path control register */
+#define TLV320AIC23_STA_REG(x)		((x)<<6)
+#define TLV320AIC23_STE_ENABLED		0x0020
+#define TLV320AIC23_DAC_SELECTED	0x0010
+#define TLV320AIC23_BYPASS_ON		0x0008
+#define TLV320AIC23_INSEL_MIC		0x0004
+#define TLV320AIC23_MICM_MUTED		0x0002
+#define TLV320AIC23_MICB_20DB		0x0001
+
+/* Digital audio path control register */
+#define TLV320AIC23_DACM_MUTE		0x0008
+#define TLV320AIC23_DEEMP_32K		0x0002
+#define TLV320AIC23_DEEMP_44K		0x0004
+#define TLV320AIC23_DEEMP_48K		0x0006
+#define TLV320AIC23_ADCHP_ON		0x0001
+
+/* Power control down register */
+#define TLV320AIC23_DEVICE_PWR_OFF  	0x0080
+#define TLV320AIC23_CLK_OFF		0x0040
+#define TLV320AIC23_OSC_OFF		0x0020
+#define TLV320AIC23_OUT_OFF		0x0010
+#define TLV320AIC23_DAC_OFF		0x0008
+#define TLV320AIC23_ADC_OFF		0x0004
+#define TLV320AIC23_MIC_OFF		0x0002
+#define TLV320AIC23_LINE_OFF		0x0001
+
+/* Digital audio interface register */
+#define TLV320AIC23_MS_MASTER		0x0040
+#define TLV320AIC23_LRSWAP_ON		0x0020
+#define TLV320AIC23_LRP_ON		0x0010
+#define TLV320AIC23_IWL_16		0x0000
+#define TLV320AIC23_IWL_20		0x0004
+#define TLV320AIC23_IWL_24		0x0008
+#define TLV320AIC23_IWL_32		0x000C
+#define TLV320AIC23_FOR_I2S		0x0002
+#define TLV320AIC23_FOR_DSP		0x0003
+#define TLV320AIC23_FOR_LJUST		0x0001
+
+/* Sample rate control register */
+#define TLV320AIC23_CLKOUT_HALF		0x0080
+#define TLV320AIC23_CLKIN_HALF		0x0040
+#define TLV320AIC23_BOSR_384fs		0x0002	/* BOSR_272fs in USB mode */
+#define TLV320AIC23_USB_CLK_ON		0x0001
+#define TLV320AIC23_SR_MASK             0xf
+#define TLV320AIC23_CLKOUT_SHIFT        7
+#define TLV320AIC23_CLKIN_SHIFT         6
+#define TLV320AIC23_SR_SHIFT            2
+#define TLV320AIC23_BOSR_SHIFT          1
+
+/* Digital interface register */
+#define TLV320AIC23_ACT_ON		0x0001
+
+/*
+ * AUDIO related MACROS
+ */
+
+#define TLV320AIC23_DEFAULT_OUT_VOL	0x70
+#define TLV320AIC23_DEFAULT_IN_VOLUME	0x10
+
+#define TLV320AIC23_OUT_VOL_MIN		TLV320AIC23_LHV_MIN
+#define TLV320AIC23_OUT_VOL_MAX		TLV320AIC23_LHV_MAX
+#define TLV320AIC23_OUT_VO_RANGE	(TLV320AIC23_OUT_VOL_MAX - \
+					TLV320AIC23_OUT_VOL_MIN)
+#define TLV320AIC23_OUT_VOL_MASK	TLV320AIC23_OUT_VOL_MAX
+
+#define TLV320AIC23_IN_VOL_MIN		TLV320AIC23_LIV_MIN
+#define TLV320AIC23_IN_VOL_MAX		TLV320AIC23_LIV_MAX
+#define TLV320AIC23_IN_VOL_RANGE	(TLV320AIC23_IN_VOL_MAX - \
+					TLV320AIC23_IN_VOL_MIN)
+#define TLV320AIC23_IN_VOL_MASK		TLV320AIC23_IN_VOL_MAX
+
+#define TLV320AIC23_SIDETONE_MASK	0x1c0
+#define TLV320AIC23_SIDETONE_0		0x100
+#define TLV320AIC23_SIDETONE_6		0x000
+#define TLV320AIC23_SIDETONE_9		0x040
+#define TLV320AIC23_SIDETONE_12		0x080
+#define TLV320AIC23_SIDETONE_18		0x0c0
+
+#endif /* _TLV320AIC23_H */
diff --git a/linux/sound/soc/codecs/tlv320aic26.c b/linux/sound/soc/codecs/tlv320aic26.c
new file mode 100644
index 0000000..6b7d71e
--- /dev/null
+++ b/linux/sound/soc/codecs/tlv320aic26.c
@@ -0,0 +1,459 @@
+/*
+ * Texas Instruments TLV320AIC26 low power audio CODEC
+ * ALSA SoC CODEC driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "tlv320aic26.h"
+
+MODULE_DESCRIPTION("ASoC TLV320AIC26 codec driver");
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_LICENSE("GPL");
+
+/* AIC26 driver private data */
+struct aic26 {
+	struct spi_device *spi;
+	struct snd_soc_codec codec;
+	u16 reg_cache[AIC26_NUM_REGS];	/* shadow registers */
+	int master;
+	int datfm;
+	int mclk;
+
+	/* Keyclick parameters */
+	int keyclick_amplitude;
+	int keyclick_freq;
+	int keyclick_len;
+};
+
+/* ---------------------------------------------------------------------
+ * Register access routines
+ */
+static unsigned int aic26_reg_read(struct snd_soc_codec *codec,
+				   unsigned int reg)
+{
+	struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+	u16 *cache = codec->reg_cache;
+	u16 cmd, value;
+	u8 buffer[2];
+	int rc;
+
+	if (reg >= AIC26_NUM_REGS) {
+		WARN_ON_ONCE(1);
+		return 0;
+	}
+
+	/* Do SPI transfer; first 16bits are command; remaining is
+	 * register contents */
+	cmd = AIC26_READ_COMMAND_WORD(reg);
+	buffer[0] = (cmd >> 8) & 0xff;
+	buffer[1] = cmd & 0xff;
+	rc = spi_write_then_read(aic26->spi, buffer, 2, buffer, 2);
+	if (rc) {
+		dev_err(&aic26->spi->dev, "AIC26 reg read error\n");
+		return -EIO;
+	}
+	value = (buffer[0] << 8) | buffer[1];
+
+	/* Update the cache before returning with the value */
+	cache[reg] = value;
+	return value;
+}
+
+static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec,
+					 unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+
+	if (reg >= AIC26_NUM_REGS) {
+		WARN_ON_ONCE(1);
+		return 0;
+	}
+
+	return cache[reg];
+}
+
+static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg,
+			   unsigned int value)
+{
+	struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+	u16 *cache = codec->reg_cache;
+	u16 cmd;
+	u8 buffer[4];
+	int rc;
+
+	if (reg >= AIC26_NUM_REGS) {
+		WARN_ON_ONCE(1);
+		return -EINVAL;
+	}
+
+	/* Do SPI transfer; first 16bits are command; remaining is data
+	 * to write into register */
+	cmd = AIC26_WRITE_COMMAND_WORD(reg);
+	buffer[0] = (cmd >> 8) & 0xff;
+	buffer[1] = cmd & 0xff;
+	buffer[2] = value >> 8;
+	buffer[3] = value;
+	rc = spi_write(aic26->spi, buffer, 4);
+	if (rc) {
+		dev_err(&aic26->spi->dev, "AIC26 reg read error\n");
+		return -EIO;
+	}
+
+	/* update cache before returning */
+	cache[reg] = value;
+	return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * Digital Audio Interface Operations
+ */
+static int aic26_hw_params(struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *params,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+	int fsref, divisor, wlen, pval, jval, dval, qval;
+	u16 reg;
+
+	dev_dbg(&aic26->spi->dev, "aic26_hw_params(substream=%p, params=%p)\n",
+		substream, params);
+	dev_dbg(&aic26->spi->dev, "rate=%i format=%i\n", params_rate(params),
+		params_format(params));
+
+	switch (params_rate(params)) {
+	case 8000:  fsref = 48000; divisor = AIC26_DIV_6; break;
+	case 11025: fsref = 44100; divisor = AIC26_DIV_4; break;
+	case 12000: fsref = 48000; divisor = AIC26_DIV_4; break;
+	case 16000: fsref = 48000; divisor = AIC26_DIV_3; break;
+	case 22050: fsref = 44100; divisor = AIC26_DIV_2; break;
+	case 24000: fsref = 48000; divisor = AIC26_DIV_2; break;
+	case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break;
+	case 44100: fsref = 44100; divisor = AIC26_DIV_1; break;
+	case 48000: fsref = 48000; divisor = AIC26_DIV_1; break;
+	default:
+		dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL;
+	}
+
+	/* select data word length */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:     wlen = AIC26_WLEN_16; break;
+	case SNDRV_PCM_FORMAT_S16_BE: wlen = AIC26_WLEN_16; break;
+	case SNDRV_PCM_FORMAT_S24_BE: wlen = AIC26_WLEN_24; break;
+	case SNDRV_PCM_FORMAT_S32_BE: wlen = AIC26_WLEN_32; break;
+	default:
+		dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
+	}
+
+	/* Configure PLL */
+	pval = 1;
+	jval = (fsref == 44100) ? 7 : 8;
+	dval = (fsref == 44100) ? 5264 : 1920;
+	qval = 0;
+	reg = 0x8000 | qval << 11 | pval << 8 | jval << 2;
+	aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg);
+	reg = dval << 2;
+	aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg);
+
+	/* Audio Control 3 (master mode, fsref rate) */
+	reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL3);
+	reg &= ~0xf800;
+	if (aic26->master)
+		reg |= 0x0800;
+	if (fsref == 48000)
+		reg |= 0x2000;
+	aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg);
+
+	/* Audio Control 1 (FSref divisor) */
+	reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL1);
+	reg &= ~0x0fff;
+	reg |= wlen | aic26->datfm | (divisor << 3) | divisor;
+	aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL1, reg);
+
+	return 0;
+}
+
+/**
+ * aic26_mute - Mute control to reduce noise when changing audio format
+ */
+static int aic26_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+	u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN);
+
+	dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n",
+		dai, mute);
+
+	if (mute)
+		reg |= 0x8080;
+	else
+		reg &= ~0x8080;
+	aic26_reg_write(codec, AIC26_REG_DAC_GAIN, reg);
+
+	return 0;
+}
+
+static int aic26_set_sysclk(struct snd_soc_dai *codec_dai,
+			    int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+
+	dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i,"
+		" freq=%i, dir=%i)\n",
+		codec_dai, clk_id, freq, dir);
+
+	/* MCLK needs to fall between 2MHz and 50 MHz */
+	if ((freq < 2000000) || (freq > 50000000))
+		return -EINVAL;
+
+	aic26->mclk = freq;
+	return 0;
+}
+
+static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+
+	dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n",
+		codec_dai, fmt);
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM: aic26->master = 1; break;
+	case SND_SOC_DAIFMT_CBS_CFS: aic26->master = 0; break;
+	default:
+		dev_dbg(&aic26->spi->dev, "bad master\n"); return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:     aic26->datfm = AIC26_DATFM_I2S; break;
+	case SND_SOC_DAIFMT_DSP_A:   aic26->datfm = AIC26_DATFM_DSP; break;
+	case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break;
+	case SND_SOC_DAIFMT_LEFT_J:  aic26->datfm = AIC26_DATFM_LEFTJ; break;
+	default:
+		dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * Digital Audio Interface Definition
+ */
+#define AIC26_RATES	(SNDRV_PCM_RATE_8000  | SNDRV_PCM_RATE_11025 |\
+			 SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+			 SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+			 SNDRV_PCM_RATE_48000)
+#define AIC26_FORMATS	(SNDRV_PCM_FMTBIT_S8     | SNDRV_PCM_FMTBIT_S16_BE |\
+			 SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
+
+static struct snd_soc_dai_ops aic26_dai_ops = {
+	.hw_params	= aic26_hw_params,
+	.digital_mute	= aic26_mute,
+	.set_sysclk	= aic26_set_sysclk,
+	.set_fmt	= aic26_set_fmt,
+};
+
+static struct snd_soc_dai_driver aic26_dai = {
+	.name = "tlv320aic26-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = AIC26_RATES,
+		.formats = AIC26_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = AIC26_RATES,
+		.formats = AIC26_FORMATS,
+	},
+	.ops = &aic26_dai_ops,
+};
+
+/* ---------------------------------------------------------------------
+ * ALSA controls
+ */
+static const char *aic26_capture_src_text[] = {"Mic", "Aux"};
+static const struct soc_enum aic26_capture_src_enum =
+	SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12, 2, aic26_capture_src_text);
+
+static const struct snd_kcontrol_new aic26_snd_controls[] = {
+	/* Output */
+	SOC_DOUBLE("PCM Playback Volume", AIC26_REG_DAC_GAIN, 8, 0, 0x7f, 1),
+	SOC_DOUBLE("PCM Playback Switch", AIC26_REG_DAC_GAIN, 15, 7, 1, 1),
+	SOC_SINGLE("PCM Capture Volume", AIC26_REG_ADC_GAIN, 8, 0x7f, 0),
+	SOC_SINGLE("PCM Capture Mute", AIC26_REG_ADC_GAIN, 15, 1, 1),
+	SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0),
+	SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0),
+	SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0),
+	SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0),
+	SOC_ENUM("Capture Source", aic26_capture_src_enum),
+};
+
+/* ---------------------------------------------------------------------
+ * SPI device portion of driver: sysfs files for debugging
+ */
+
+static ssize_t aic26_keyclick_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct aic26 *aic26 = dev_get_drvdata(dev);
+	int val, amp, freq, len;
+
+	val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2);
+	amp = (val >> 12) & 0x7;
+	freq = (125 << ((val >> 8) & 0x7)) >> 1;
+	len = 2 * (1 + ((val >> 4) & 0xf));
+
+	return sprintf(buf, "amp=%x freq=%iHz len=%iclks\n", amp, freq, len);
+}
+
+/* Any write to the keyclick attribute will trigger the keyclick event */
+static ssize_t aic26_keyclick_set(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct aic26 *aic26 = dev_get_drvdata(dev);
+	int val;
+
+	val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2);
+	val |= 0x8000;
+	aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL2, val);
+
+	return count;
+}
+
+static DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set);
+
+/* ---------------------------------------------------------------------
+ * SoC CODEC portion of driver: probe and release routines
+ */
+static int aic26_probe(struct snd_soc_codec *codec)
+{
+	struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec);
+	int ret, err, i, reg;
+
+	dev_info(codec->dev, "Probing AIC26 SoC CODEC driver\n");
+
+	/* Reset the codec to power on defaults */
+	aic26_reg_write(codec, AIC26_REG_RESET, 0xBB00);
+
+	/* Power up CODEC */
+	aic26_reg_write(codec, AIC26_REG_POWER_CTRL, 0);
+
+	/* Audio Control 3 (master mode, fsref rate) */
+	reg = aic26_reg_read(codec, AIC26_REG_AUDIO_CTRL3);
+	reg &= ~0xf800;
+	reg |= 0x0800; /* set master mode */
+	aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg);
+
+	/* Fill register cache */
+	for (i = 0; i < ARRAY_SIZE(aic26->reg_cache); i++)
+		aic26_reg_read(codec, i);
+
+	/* Register the sysfs files for debugging */
+	/* Create SysFS files */
+	ret = device_create_file(codec->dev, &dev_attr_keyclick);
+	if (ret)
+		dev_info(codec->dev, "error creating sysfs files\n");
+
+	/* register controls */
+	dev_dbg(codec->dev, "Registering controls\n");
+	err = snd_soc_add_controls(codec, aic26_snd_controls,
+			ARRAY_SIZE(aic26_snd_controls));
+	WARN_ON(err < 0);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver aic26_soc_codec_dev = {
+	.probe = aic26_probe,
+	.read = aic26_reg_read,
+	.write = aic26_reg_write,
+	.reg_cache_size = AIC26_NUM_REGS,
+	.reg_word_size = sizeof(u16),
+};
+
+/* ---------------------------------------------------------------------
+ * SPI device portion of driver: probe and release routines and SPI
+ * 				 driver registration.
+ */
+static int aic26_spi_probe(struct spi_device *spi)
+{
+	struct aic26 *aic26;
+	int ret;
+
+	dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n");
+
+	/* Allocate driver data */
+	aic26 = kzalloc(sizeof *aic26, GFP_KERNEL);
+	if (!aic26)
+		return -ENOMEM;
+
+	/* Initialize the driver data */
+	aic26->spi = spi;
+	dev_set_drvdata(&spi->dev, aic26);
+	aic26->master = 1;
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&aic26_soc_codec_dev, &aic26_dai, 1);
+	if (ret < 0)
+		kfree(aic26);
+	return ret;
+
+	dev_dbg(&spi->dev, "SPI device initialized\n");
+	return 0;
+}
+
+static int aic26_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver aic26_spi = {
+	.driver = {
+		.name = "tlv320aic26-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe = aic26_spi_probe,
+	.remove = aic26_spi_remove,
+};
+
+static int __init aic26_init(void)
+{
+	return spi_register_driver(&aic26_spi);
+}
+module_init(aic26_init);
+
+static void __exit aic26_exit(void)
+{
+	spi_unregister_driver(&aic26_spi);
+}
+module_exit(aic26_exit);
diff --git a/linux/sound/soc/codecs/tlv320aic26.h b/linux/sound/soc/codecs/tlv320aic26.h
new file mode 100644
index 0000000..62b1f22
--- /dev/null
+++ b/linux/sound/soc/codecs/tlv320aic26.h
@@ -0,0 +1,93 @@
+/*
+ * Texas Instruments TLV320AIC26 low power audio CODEC
+ * register definitions
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ */
+
+#ifndef _TLV320AIC16_H_
+#define _TLV320AIC16_H_
+
+/* AIC26 Registers */
+#define AIC26_READ_COMMAND_WORD(addr)	((1 << 15) | (addr << 5))
+#define AIC26_WRITE_COMMAND_WORD(addr)	((0 << 15) | (addr << 5))
+#define AIC26_PAGE_ADDR(page, offset)	((page << 6) | offset)
+#define AIC26_NUM_REGS			AIC26_PAGE_ADDR(3, 0)
+
+/* Page 0: Auxillary data registers */
+#define AIC26_REG_BAT1			AIC26_PAGE_ADDR(0, 0x05)
+#define AIC26_REG_BAT2			AIC26_PAGE_ADDR(0, 0x06)
+#define AIC26_REG_AUX			AIC26_PAGE_ADDR(0, 0x07)
+#define AIC26_REG_TEMP1			AIC26_PAGE_ADDR(0, 0x09)
+#define AIC26_REG_TEMP2			AIC26_PAGE_ADDR(0, 0x0A)
+
+/* Page 1: Auxillary control registers */
+#define AIC26_REG_AUX_ADC		AIC26_PAGE_ADDR(1, 0x00)
+#define AIC26_REG_STATUS		AIC26_PAGE_ADDR(1, 0x01)
+#define AIC26_REG_REFERENCE		AIC26_PAGE_ADDR(1, 0x03)
+#define AIC26_REG_RESET			AIC26_PAGE_ADDR(1, 0x04)
+
+/* Page 2: Audio control registers */
+#define AIC26_REG_AUDIO_CTRL1		AIC26_PAGE_ADDR(2, 0x00)
+#define AIC26_REG_ADC_GAIN		AIC26_PAGE_ADDR(2, 0x01)
+#define AIC26_REG_DAC_GAIN		AIC26_PAGE_ADDR(2, 0x02)
+#define AIC26_REG_SIDETONE		AIC26_PAGE_ADDR(2, 0x03)
+#define AIC26_REG_AUDIO_CTRL2		AIC26_PAGE_ADDR(2, 0x04)
+#define AIC26_REG_POWER_CTRL		AIC26_PAGE_ADDR(2, 0x05)
+#define AIC26_REG_AUDIO_CTRL3		AIC26_PAGE_ADDR(2, 0x06)
+
+#define AIC26_REG_FILTER_COEFF_L_N0	AIC26_PAGE_ADDR(2, 0x07)
+#define AIC26_REG_FILTER_COEFF_L_N1	AIC26_PAGE_ADDR(2, 0x08)
+#define AIC26_REG_FILTER_COEFF_L_N2	AIC26_PAGE_ADDR(2, 0x09)
+#define AIC26_REG_FILTER_COEFF_L_N3	AIC26_PAGE_ADDR(2, 0x0A)
+#define AIC26_REG_FILTER_COEFF_L_N4	AIC26_PAGE_ADDR(2, 0x0B)
+#define AIC26_REG_FILTER_COEFF_L_N5	AIC26_PAGE_ADDR(2, 0x0C)
+#define AIC26_REG_FILTER_COEFF_L_D1	AIC26_PAGE_ADDR(2, 0x0D)
+#define AIC26_REG_FILTER_COEFF_L_D2	AIC26_PAGE_ADDR(2, 0x0E)
+#define AIC26_REG_FILTER_COEFF_L_D4	AIC26_PAGE_ADDR(2, 0x0F)
+#define AIC26_REG_FILTER_COEFF_L_D5	AIC26_PAGE_ADDR(2, 0x10)
+#define AIC26_REG_FILTER_COEFF_R_N0	AIC26_PAGE_ADDR(2, 0x11)
+#define AIC26_REG_FILTER_COEFF_R_N1	AIC26_PAGE_ADDR(2, 0x12)
+#define AIC26_REG_FILTER_COEFF_R_N2	AIC26_PAGE_ADDR(2, 0x13)
+#define AIC26_REG_FILTER_COEFF_R_N3	AIC26_PAGE_ADDR(2, 0x14)
+#define AIC26_REG_FILTER_COEFF_R_N4	AIC26_PAGE_ADDR(2, 0x15)
+#define AIC26_REG_FILTER_COEFF_R_N5	AIC26_PAGE_ADDR(2, 0x16)
+#define AIC26_REG_FILTER_COEFF_R_D1	AIC26_PAGE_ADDR(2, 0x17)
+#define AIC26_REG_FILTER_COEFF_R_D2	AIC26_PAGE_ADDR(2, 0x18)
+#define AIC26_REG_FILTER_COEFF_R_D4	AIC26_PAGE_ADDR(2, 0x19)
+#define AIC26_REG_FILTER_COEFF_R_D5	AIC26_PAGE_ADDR(2, 0x1A)
+
+#define AIC26_REG_PLL_PROG1		AIC26_PAGE_ADDR(2, 0x1B)
+#define AIC26_REG_PLL_PROG2		AIC26_PAGE_ADDR(2, 0x1C)
+#define AIC26_REG_AUDIO_CTRL4		AIC26_PAGE_ADDR(2, 0x1D)
+#define AIC26_REG_AUDIO_CTRL5		AIC26_PAGE_ADDR(2, 0x1E)
+
+/* fsref dividers; used in register 'Audio Control 1' */
+enum aic26_divisors {
+	AIC26_DIV_1	= 0,
+	AIC26_DIV_1_5	= 1,
+	AIC26_DIV_2	= 2,
+	AIC26_DIV_3	= 3,
+	AIC26_DIV_4	= 4,
+	AIC26_DIV_5	= 5,
+	AIC26_DIV_5_5	= 6,
+	AIC26_DIV_6	= 7,
+};
+
+/* Digital data format */
+enum aic26_datfm {
+	AIC26_DATFM_I2S		= 0 << 8,
+	AIC26_DATFM_DSP		= 1 << 8,
+	AIC26_DATFM_RIGHTJ	= 2 << 8, /* right justified */
+	AIC26_DATFM_LEFTJ	= 3 << 8, /* left justified */
+};
+
+/* Sample word length in bits; used in register 'Audio Control 1' */
+enum aic26_wlen {
+	AIC26_WLEN_16	= 0 << 10,
+	AIC26_WLEN_20	= 1 << 10,
+	AIC26_WLEN_24	= 2 << 10,
+	AIC26_WLEN_32	= 3 << 10,
+};
+
+#endif /* _TLV320AIC16_H_ */
diff --git a/linux/sound/soc/codecs/tlv320aic3x.c b/linux/sound/soc/codecs/tlv320aic3x.c
new file mode 100644
index 0000000..77b8f9a
--- /dev/null
+++ b/linux/sound/soc/codecs/tlv320aic3x.c
@@ -0,0 +1,1567 @@
+/*
+ * ALSA SoC TLV320AIC3X codec driver
+ *
+ * Author:      Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright:   (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * Based on sound/soc/codecs/wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Notes:
+ *  The AIC3X is a driver for a low power stereo audio
+ *  codecs aic31, aic32, aic33, aic3007.
+ *
+ *  It supports full aic33 codec functionality.
+ *  The compatibility with aic32, aic31 and aic3007 is as follows:
+ *    aic32/aic3007    |        aic31
+ *  ---------------------------------------
+ *   MONO_LOUT -> N/A  |  MONO_LOUT -> N/A
+ *                     |  IN1L -> LINE1L
+ *                     |  IN1R -> LINE1R
+ *                     |  IN2L -> LINE2L
+ *                     |  IN2R -> LINE2R
+ *                     |  MIC3L/R -> N/A
+ *   truncated internal functionality in
+ *   accordance with documentation
+ *  ---------------------------------------
+ *
+ *  Hence the machine layer should disable unsupported inputs/outputs by
+ *  snd_soc_dapm_disable_pin(codec, "MONO_LOUT"), etc.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/tlv320aic3x.h>
+
+#include "tlv320aic3x.h"
+
+#define AIC3X_NUM_SUPPLIES	4
+static const char *aic3x_supply_names[AIC3X_NUM_SUPPLIES] = {
+	"IOVDD",	/* I/O Voltage */
+	"DVDD",		/* Digital Core Voltage */
+	"AVDD",		/* Analog DAC Voltage */
+	"DRVDD",	/* ADC Analog and Output Driver Voltage */
+};
+
+struct aic3x_priv;
+
+struct aic3x_disable_nb {
+	struct notifier_block nb;
+	struct aic3x_priv *aic3x;
+};
+
+/* codec private data */
+struct aic3x_priv {
+	struct snd_soc_codec *codec;
+	struct regulator_bulk_data supplies[AIC3X_NUM_SUPPLIES];
+	struct aic3x_disable_nb disable_nb[AIC3X_NUM_SUPPLIES];
+	enum snd_soc_control_type control_type;
+	struct aic3x_setup_data *setup;
+	void *control_data;
+	unsigned int sysclk;
+	int master;
+	int gpio_reset;
+	int power;
+#define AIC3X_MODEL_3X 0
+#define AIC3X_MODEL_33 1
+#define AIC3X_MODEL_3007 2
+	u16 model;
+};
+
+/*
+ * AIC3X register cache
+ * We can't read the AIC3X register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u8 aic3x_reg[AIC3X_CACHEREGNUM] = {
+	0x00, 0x00, 0x00, 0x10,	/* 0 */
+	0x04, 0x00, 0x00, 0x00,	/* 4 */
+	0x00, 0x00, 0x00, 0x01,	/* 8 */
+	0x00, 0x00, 0x00, 0x80,	/* 12 */
+	0x80, 0xff, 0xff, 0x78,	/* 16 */
+	0x78, 0x78, 0x78, 0x78,	/* 20 */
+	0x78, 0x00, 0x00, 0xfe,	/* 24 */
+	0x00, 0x00, 0xfe, 0x00,	/* 28 */
+	0x18, 0x18, 0x00, 0x00,	/* 32 */
+	0x00, 0x00, 0x00, 0x00,	/* 36 */
+	0x00, 0x00, 0x00, 0x80,	/* 40 */
+	0x80, 0x00, 0x00, 0x00,	/* 44 */
+	0x00, 0x00, 0x00, 0x04,	/* 48 */
+	0x00, 0x00, 0x00, 0x00,	/* 52 */
+	0x00, 0x00, 0x04, 0x00,	/* 56 */
+	0x00, 0x00, 0x00, 0x00,	/* 60 */
+	0x00, 0x04, 0x00, 0x00,	/* 64 */
+	0x00, 0x00, 0x00, 0x00,	/* 68 */
+	0x04, 0x00, 0x00, 0x00,	/* 72 */
+	0x00, 0x00, 0x00, 0x00,	/* 76 */
+	0x00, 0x00, 0x00, 0x00,	/* 80 */
+	0x00, 0x00, 0x00, 0x00,	/* 84 */
+	0x00, 0x00, 0x00, 0x00,	/* 88 */
+	0x00, 0x00, 0x00, 0x00,	/* 92 */
+	0x00, 0x00, 0x00, 0x00,	/* 96 */
+	0x00, 0x00, 0x02,	/* 100 */
+};
+
+/*
+ * read from the aic3x register space. Only use for this function is if
+ * wanting to read volatile bits from those registers that has both read-only
+ * and read/write bits. All other cases should use snd_soc_read.
+ */
+static int aic3x_read(struct snd_soc_codec *codec, unsigned int reg,
+		      u8 *value)
+{
+	u8 *cache = codec->reg_cache;
+
+	if (codec->cache_only)
+		return -EINVAL;
+	if (reg >= AIC3X_CACHEREGNUM)
+		return -1;
+
+	*value = codec->hw_read(codec, reg);
+	cache[reg] = *value;
+
+	return 0;
+}
+
+#define SOC_DAPM_SINGLE_AIC3X(xname, reg, shift, mask, invert) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = snd_soc_info_volsw, \
+	.get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw_aic3x, \
+	.private_value =  SOC_SINGLE_VALUE(reg, shift, mask, invert) }
+
+/*
+ * All input lines are connected when !0xf and disconnected with 0xf bit field,
+ * so we have to use specific dapm_put call for input mixer
+ */
+static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int shift = mc->shift;
+	int max = mc->max;
+	unsigned int mask = (1 << fls(max)) - 1;
+	unsigned int invert = mc->invert;
+	unsigned short val, val_mask;
+	int ret;
+	struct snd_soc_dapm_path *path;
+	int found = 0;
+
+	val = (ucontrol->value.integer.value[0] & mask);
+
+	mask = 0xf;
+	if (val)
+		val = mask;
+
+	if (invert)
+		val = mask - val;
+	val_mask = mask << shift;
+	val = val << shift;
+
+	mutex_lock(&widget->codec->mutex);
+
+	if (snd_soc_test_bits(widget->codec, reg, val_mask, val)) {
+		/* find dapm widget path assoc with kcontrol */
+		list_for_each_entry(path, &widget->codec->dapm_paths, list) {
+			if (path->kcontrol != kcontrol)
+				continue;
+
+			/* found, now check type */
+			found = 1;
+			if (val)
+				/* new connection */
+				path->connect = invert ? 0 : 1;
+			else
+				/* old connection must be powered down */
+				path->connect = invert ? 1 : 0;
+			break;
+		}
+
+		if (found)
+			snd_soc_dapm_sync(widget->codec);
+	}
+
+	ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
+
+	mutex_unlock(&widget->codec->mutex);
+	return ret;
+}
+
+static const char *aic3x_left_dac_mux[] = { "DAC_L1", "DAC_L3", "DAC_L2" };
+static const char *aic3x_right_dac_mux[] = { "DAC_R1", "DAC_R3", "DAC_R2" };
+static const char *aic3x_left_hpcom_mux[] =
+    { "differential of HPLOUT", "constant VCM", "single-ended" };
+static const char *aic3x_right_hpcom_mux[] =
+    { "differential of HPROUT", "constant VCM", "single-ended",
+      "differential of HPLCOM", "external feedback" };
+static const char *aic3x_linein_mode_mux[] = { "single-ended", "differential" };
+static const char *aic3x_adc_hpf[] =
+    { "Disabled", "0.0045xFs", "0.0125xFs", "0.025xFs" };
+
+#define LDAC_ENUM	0
+#define RDAC_ENUM	1
+#define LHPCOM_ENUM	2
+#define RHPCOM_ENUM	3
+#define LINE1L_ENUM	4
+#define LINE1R_ENUM	5
+#define LINE2L_ENUM	6
+#define LINE2R_ENUM	7
+#define ADC_HPF_ENUM	8
+
+static const struct soc_enum aic3x_enum[] = {
+	SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux),
+	SOC_ENUM_SINGLE(DAC_LINE_MUX, 4, 3, aic3x_right_dac_mux),
+	SOC_ENUM_SINGLE(HPLCOM_CFG, 4, 3, aic3x_left_hpcom_mux),
+	SOC_ENUM_SINGLE(HPRCOM_CFG, 3, 5, aic3x_right_hpcom_mux),
+	SOC_ENUM_SINGLE(LINE1L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+	SOC_ENUM_SINGLE(LINE1R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+	SOC_ENUM_SINGLE(LINE2L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+	SOC_ENUM_SINGLE(LINE2R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux),
+	SOC_ENUM_DOUBLE(AIC3X_CODEC_DFILT_CTRL, 6, 4, 4, aic3x_adc_hpf),
+};
+
+/*
+ * DAC digital volumes. From -63.5 to 0 dB in 0.5 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(dac_tlv, -6350, 50, 0);
+/* ADC PGA gain volumes. From 0 to 59.5 dB in 0.5 dB steps */
+static DECLARE_TLV_DB_SCALE(adc_tlv, 0, 50, 0);
+/*
+ * Output stage volumes. From -78.3 to 0 dB. Muted below -78.3 dB.
+ * Step size is approximately 0.5 dB over most of the scale but increasing
+ * near the very low levels.
+ * Define dB scale so that it is mostly correct for range about -55 to 0 dB
+ * but having increasing dB difference below that (and where it doesn't count
+ * so much). This setting shows -50 dB (actual is -50.3 dB) for register
+ * value 100 and -58.5 dB (actual is -78.3 dB) for register value 117.
+ */
+static DECLARE_TLV_DB_SCALE(output_stage_tlv, -5900, 50, 1);
+
+static const struct snd_kcontrol_new aic3x_snd_controls[] = {
+	/* Output */
+	SOC_DOUBLE_R_TLV("PCM Playback Volume",
+			 LDAC_VOL, RDAC_VOL, 0, 0x7f, 1, dac_tlv),
+
+	/*
+	 * Output controls that map to output mixer switches. Note these are
+	 * only for swapped L-to-R and R-to-L routes. See below stereo controls
+	 * for direct L-to-L and R-to-R routes.
+	 */
+	SOC_SINGLE_TLV("Left Line Mixer Line2R Bypass Volume",
+		       LINE2R_2_LLOPM_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Left Line Mixer PGAR Bypass Volume",
+		       PGAR_2_LLOPM_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Left Line Mixer DACR1 Playback Volume",
+		       DACR1_2_LLOPM_VOL, 0, 118, 1, output_stage_tlv),
+
+	SOC_SINGLE_TLV("Right Line Mixer Line2L Bypass Volume",
+		       LINE2L_2_RLOPM_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Right Line Mixer PGAL Bypass Volume",
+		       PGAL_2_RLOPM_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Right Line Mixer DACL1 Playback Volume",
+		       DACL1_2_RLOPM_VOL, 0, 118, 1, output_stage_tlv),
+
+	SOC_SINGLE_TLV("Left HP Mixer Line2R Bypass Volume",
+		       LINE2R_2_HPLOUT_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Left HP Mixer PGAR Bypass Volume",
+		       PGAR_2_HPLOUT_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Left HP Mixer DACR1 Playback Volume",
+		       DACR1_2_HPLOUT_VOL, 0, 118, 1, output_stage_tlv),
+
+	SOC_SINGLE_TLV("Right HP Mixer Line2L Bypass Volume",
+		       LINE2L_2_HPROUT_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Right HP Mixer PGAL Bypass Volume",
+		       PGAL_2_HPROUT_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Right HP Mixer DACL1 Playback Volume",
+		       DACL1_2_HPROUT_VOL, 0, 118, 1, output_stage_tlv),
+
+	SOC_SINGLE_TLV("Left HPCOM Mixer Line2R Bypass Volume",
+		       LINE2R_2_HPLCOM_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Left HPCOM Mixer PGAR Bypass Volume",
+		       PGAR_2_HPLCOM_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Left HPCOM Mixer DACR1 Playback Volume",
+		       DACR1_2_HPLCOM_VOL, 0, 118, 1, output_stage_tlv),
+
+	SOC_SINGLE_TLV("Right HPCOM Mixer Line2L Bypass Volume",
+		       LINE2L_2_HPRCOM_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Right HPCOM Mixer PGAL Bypass Volume",
+		       PGAL_2_HPRCOM_VOL, 0, 118, 1, output_stage_tlv),
+	SOC_SINGLE_TLV("Right HPCOM Mixer DACL1 Playback Volume",
+		       DACL1_2_HPRCOM_VOL, 0, 118, 1, output_stage_tlv),
+
+	/* Stereo output controls for direct L-to-L and R-to-R routes */
+	SOC_DOUBLE_R_TLV("Line Line2 Bypass Volume",
+			 LINE2L_2_LLOPM_VOL, LINE2R_2_RLOPM_VOL,
+			 0, 118, 1, output_stage_tlv),
+	SOC_DOUBLE_R_TLV("Line PGA Bypass Volume",
+			 PGAL_2_LLOPM_VOL, PGAR_2_RLOPM_VOL,
+			 0, 118, 1, output_stage_tlv),
+	SOC_DOUBLE_R_TLV("Line DAC Playback Volume",
+			 DACL1_2_LLOPM_VOL, DACR1_2_RLOPM_VOL,
+			 0, 118, 1, output_stage_tlv),
+
+	SOC_DOUBLE_R_TLV("Mono Line2 Bypass Volume",
+			 LINE2L_2_MONOLOPM_VOL, LINE2R_2_MONOLOPM_VOL,
+			 0, 118, 1, output_stage_tlv),
+	SOC_DOUBLE_R_TLV("Mono PGA Bypass Volume",
+			 PGAL_2_MONOLOPM_VOL, PGAR_2_MONOLOPM_VOL,
+			 0, 118, 1, output_stage_tlv),
+	SOC_DOUBLE_R_TLV("Mono DAC Playback Volume",
+			 DACL1_2_MONOLOPM_VOL, DACR1_2_MONOLOPM_VOL,
+			 0, 118, 1, output_stage_tlv),
+
+	SOC_DOUBLE_R_TLV("HP Line2 Bypass Volume",
+			 LINE2L_2_HPLOUT_VOL, LINE2R_2_HPROUT_VOL,
+			 0, 118, 1, output_stage_tlv),
+	SOC_DOUBLE_R_TLV("HP PGA Bypass Volume",
+			 PGAL_2_HPLOUT_VOL, PGAR_2_HPROUT_VOL,
+			 0, 118, 1, output_stage_tlv),
+	SOC_DOUBLE_R_TLV("HP DAC Playback Volume",
+			 DACL1_2_HPLOUT_VOL, DACR1_2_HPROUT_VOL,
+			 0, 118, 1, output_stage_tlv),
+
+	SOC_DOUBLE_R_TLV("HPCOM Line2 Bypass Volume",
+			 LINE2L_2_HPLCOM_VOL, LINE2R_2_HPRCOM_VOL,
+			 0, 118, 1, output_stage_tlv),
+	SOC_DOUBLE_R_TLV("HPCOM PGA Bypass Volume",
+			 PGAL_2_HPLCOM_VOL, PGAR_2_HPRCOM_VOL,
+			 0, 118, 1, output_stage_tlv),
+	SOC_DOUBLE_R_TLV("HPCOM DAC Playback Volume",
+			 DACL1_2_HPLCOM_VOL, DACR1_2_HPRCOM_VOL,
+			 0, 118, 1, output_stage_tlv),
+
+	/* Output pin mute controls */
+	SOC_DOUBLE_R("Line Playback Switch", LLOPM_CTRL, RLOPM_CTRL, 3,
+		     0x01, 0),
+	SOC_SINGLE("Mono Playback Switch", MONOLOPM_CTRL, 3, 0x01, 0),
+	SOC_DOUBLE_R("HP Playback Switch", HPLOUT_CTRL, HPROUT_CTRL, 3,
+		     0x01, 0),
+	SOC_DOUBLE_R("HPCOM Playback Switch", HPLCOM_CTRL, HPRCOM_CTRL, 3,
+		     0x01, 0),
+
+	/*
+	 * Note: enable Automatic input Gain Controller with care. It can
+	 * adjust PGA to max value when ADC is on and will never go back.
+	*/
+	SOC_DOUBLE_R("AGC Switch", LAGC_CTRL_A, RAGC_CTRL_A, 7, 0x01, 0),
+
+	/* Input */
+	SOC_DOUBLE_R_TLV("PGA Capture Volume", LADC_VOL, RADC_VOL,
+			 0, 119, 0, adc_tlv),
+	SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1),
+
+	SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]),
+};
+
+/*
+ * Class-D amplifier gain. From 0 to 18 dB in 6 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(classd_amp_tlv, 0, 600, 0);
+
+static const struct snd_kcontrol_new aic3x_classd_amp_gain_ctrl =
+	SOC_DOUBLE_TLV("Class-D Amplifier Gain", CLASSD_CTRL, 6, 4, 3, 0, classd_amp_tlv);
+
+/* Left DAC Mux */
+static const struct snd_kcontrol_new aic3x_left_dac_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LDAC_ENUM]);
+
+/* Right DAC Mux */
+static const struct snd_kcontrol_new aic3x_right_dac_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[RDAC_ENUM]);
+
+/* Left HPCOM Mux */
+static const struct snd_kcontrol_new aic3x_left_hpcom_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LHPCOM_ENUM]);
+
+/* Right HPCOM Mux */
+static const struct snd_kcontrol_new aic3x_right_hpcom_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[RHPCOM_ENUM]);
+
+/* Left Line Mixer */
+static const struct snd_kcontrol_new aic3x_left_line_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_LLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_LLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_LLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_LLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_LLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_LLOPM_VOL, 7, 1, 0),
+};
+
+/* Right Line Mixer */
+static const struct snd_kcontrol_new aic3x_right_line_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_RLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_RLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_RLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_RLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_RLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_RLOPM_VOL, 7, 1, 0),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new aic3x_mono_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_MONOLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_MONOLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_MONOLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_MONOLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_MONOLOPM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_MONOLOPM_VOL, 7, 1, 0),
+};
+
+/* Left HP Mixer */
+static const struct snd_kcontrol_new aic3x_left_hp_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPLOUT_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPLOUT_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPLOUT_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPLOUT_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPLOUT_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPLOUT_VOL, 7, 1, 0),
+};
+
+/* Right HP Mixer */
+static const struct snd_kcontrol_new aic3x_right_hp_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPROUT_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPROUT_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPROUT_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPROUT_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPROUT_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPROUT_VOL, 7, 1, 0),
+};
+
+/* Left HPCOM Mixer */
+static const struct snd_kcontrol_new aic3x_left_hpcom_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPLCOM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPLCOM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPLCOM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPLCOM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPLCOM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPLCOM_VOL, 7, 1, 0),
+};
+
+/* Right HPCOM Mixer */
+static const struct snd_kcontrol_new aic3x_right_hpcom_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPRCOM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPRCOM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPRCOM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPRCOM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPRCOM_VOL, 7, 1, 0),
+	SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPRCOM_VOL, 7, 1, 0),
+};
+
+/* Left PGA Mixer */
+static const struct snd_kcontrol_new aic3x_left_pga_mixer_controls[] = {
+	SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_LADC_CTRL, 3, 1, 1),
+	SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_LADC_CTRL, 3, 1, 1),
+	SOC_DAPM_SINGLE_AIC3X("Line2L Switch", LINE2L_2_LADC_CTRL, 3, 1, 1),
+	SOC_DAPM_SINGLE_AIC3X("Mic3L Switch", MIC3LR_2_LADC_CTRL, 4, 1, 1),
+	SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_LADC_CTRL, 0, 1, 1),
+};
+
+/* Right PGA Mixer */
+static const struct snd_kcontrol_new aic3x_right_pga_mixer_controls[] = {
+	SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_RADC_CTRL, 3, 1, 1),
+	SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_RADC_CTRL, 3, 1, 1),
+	SOC_DAPM_SINGLE_AIC3X("Line2R Switch", LINE2R_2_RADC_CTRL, 3, 1, 1),
+	SOC_DAPM_SINGLE_AIC3X("Mic3L Switch", MIC3LR_2_RADC_CTRL, 4, 1, 1),
+	SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_RADC_CTRL, 0, 1, 1),
+};
+
+/* Left Line1 Mux */
+static const struct snd_kcontrol_new aic3x_left_line1_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_ENUM]);
+
+/* Right Line1 Mux */
+static const struct snd_kcontrol_new aic3x_right_line1_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_ENUM]);
+
+/* Left Line2 Mux */
+static const struct snd_kcontrol_new aic3x_left_line2_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE2L_ENUM]);
+
+/* Right Line2 Mux */
+static const struct snd_kcontrol_new aic3x_right_line2_mux_controls =
+SOC_DAPM_ENUM("Route", aic3x_enum[LINE2R_ENUM]);
+
+static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
+	/* Left DAC to Left Outputs */
+	SND_SOC_DAPM_DAC("Left DAC", "Left Playback", DAC_PWR, 7, 0),
+	SND_SOC_DAPM_MUX("Left DAC Mux", SND_SOC_NOPM, 0, 0,
+			 &aic3x_left_dac_mux_controls),
+	SND_SOC_DAPM_MUX("Left HPCOM Mux", SND_SOC_NOPM, 0, 0,
+			 &aic3x_left_hpcom_mux_controls),
+	SND_SOC_DAPM_PGA("Left Line Out", LLOPM_CTRL, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Left HP Out", HPLOUT_CTRL, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Left HP Com", HPLCOM_CTRL, 0, 0, NULL, 0),
+
+	/* Right DAC to Right Outputs */
+	SND_SOC_DAPM_DAC("Right DAC", "Right Playback", DAC_PWR, 6, 0),
+	SND_SOC_DAPM_MUX("Right DAC Mux", SND_SOC_NOPM, 0, 0,
+			 &aic3x_right_dac_mux_controls),
+	SND_SOC_DAPM_MUX("Right HPCOM Mux", SND_SOC_NOPM, 0, 0,
+			 &aic3x_right_hpcom_mux_controls),
+	SND_SOC_DAPM_PGA("Right Line Out", RLOPM_CTRL, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right HP Out", HPROUT_CTRL, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right HP Com", HPRCOM_CTRL, 0, 0, NULL, 0),
+
+	/* Mono Output */
+	SND_SOC_DAPM_PGA("Mono Out", MONOLOPM_CTRL, 0, 0, NULL, 0),
+
+	/* Inputs to Left ADC */
+	SND_SOC_DAPM_ADC("Left ADC", "Left Capture", LINE1L_2_LADC_CTRL, 2, 0),
+	SND_SOC_DAPM_MIXER("Left PGA Mixer", SND_SOC_NOPM, 0, 0,
+			   &aic3x_left_pga_mixer_controls[0],
+			   ARRAY_SIZE(aic3x_left_pga_mixer_controls)),
+	SND_SOC_DAPM_MUX("Left Line1L Mux", SND_SOC_NOPM, 0, 0,
+			 &aic3x_left_line1_mux_controls),
+	SND_SOC_DAPM_MUX("Left Line1R Mux", SND_SOC_NOPM, 0, 0,
+			 &aic3x_left_line1_mux_controls),
+	SND_SOC_DAPM_MUX("Left Line2L Mux", SND_SOC_NOPM, 0, 0,
+			 &aic3x_left_line2_mux_controls),
+
+	/* Inputs to Right ADC */
+	SND_SOC_DAPM_ADC("Right ADC", "Right Capture",
+			 LINE1R_2_RADC_CTRL, 2, 0),
+	SND_SOC_DAPM_MIXER("Right PGA Mixer", SND_SOC_NOPM, 0, 0,
+			   &aic3x_right_pga_mixer_controls[0],
+			   ARRAY_SIZE(aic3x_right_pga_mixer_controls)),
+	SND_SOC_DAPM_MUX("Right Line1L Mux", SND_SOC_NOPM, 0, 0,
+			 &aic3x_right_line1_mux_controls),
+	SND_SOC_DAPM_MUX("Right Line1R Mux", SND_SOC_NOPM, 0, 0,
+			 &aic3x_right_line1_mux_controls),
+	SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0,
+			 &aic3x_right_line2_mux_controls),
+
+	/*
+	 * Not a real mic bias widget but similar function. This is for dynamic
+	 * control of GPIO1 digital mic modulator clock output function when
+	 * using digital mic.
+	 */
+	SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "GPIO1 dmic modclk",
+			 AIC3X_GPIO1_REG, 4, 0xf,
+			 AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK,
+			 AIC3X_GPIO1_FUNC_DISABLED),
+
+	/*
+	 * Also similar function like mic bias. Selects digital mic with
+	 * configurable oversampling rate instead of ADC converter.
+	 */
+	SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 128",
+			 AIC3X_ASD_INTF_CTRLA, 0, 3, 1, 0),
+	SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 64",
+			 AIC3X_ASD_INTF_CTRLA, 0, 3, 2, 0),
+	SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 32",
+			 AIC3X_ASD_INTF_CTRLA, 0, 3, 3, 0),
+
+	/* Mic Bias */
+	SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2V",
+			 MICBIAS_CTRL, 6, 3, 1, 0),
+	SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2.5V",
+			 MICBIAS_CTRL, 6, 3, 2, 0),
+	SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias AVDD",
+			 MICBIAS_CTRL, 6, 3, 3, 0),
+
+	/* Output mixers */
+	SND_SOC_DAPM_MIXER("Left Line Mixer", SND_SOC_NOPM, 0, 0,
+			   &aic3x_left_line_mixer_controls[0],
+			   ARRAY_SIZE(aic3x_left_line_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Right Line Mixer", SND_SOC_NOPM, 0, 0,
+			   &aic3x_right_line_mixer_controls[0],
+			   ARRAY_SIZE(aic3x_right_line_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0,
+			   &aic3x_mono_mixer_controls[0],
+			   ARRAY_SIZE(aic3x_mono_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+			   &aic3x_left_hp_mixer_controls[0],
+			   ARRAY_SIZE(aic3x_left_hp_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+			   &aic3x_right_hp_mixer_controls[0],
+			   ARRAY_SIZE(aic3x_right_hp_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Left HPCOM Mixer", SND_SOC_NOPM, 0, 0,
+			   &aic3x_left_hpcom_mixer_controls[0],
+			   ARRAY_SIZE(aic3x_left_hpcom_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Right HPCOM Mixer", SND_SOC_NOPM, 0, 0,
+			   &aic3x_right_hpcom_mixer_controls[0],
+			   ARRAY_SIZE(aic3x_right_hpcom_mixer_controls)),
+
+	SND_SOC_DAPM_OUTPUT("LLOUT"),
+	SND_SOC_DAPM_OUTPUT("RLOUT"),
+	SND_SOC_DAPM_OUTPUT("MONO_LOUT"),
+	SND_SOC_DAPM_OUTPUT("HPLOUT"),
+	SND_SOC_DAPM_OUTPUT("HPROUT"),
+	SND_SOC_DAPM_OUTPUT("HPLCOM"),
+	SND_SOC_DAPM_OUTPUT("HPRCOM"),
+
+	SND_SOC_DAPM_INPUT("MIC3L"),
+	SND_SOC_DAPM_INPUT("MIC3R"),
+	SND_SOC_DAPM_INPUT("LINE1L"),
+	SND_SOC_DAPM_INPUT("LINE1R"),
+	SND_SOC_DAPM_INPUT("LINE2L"),
+	SND_SOC_DAPM_INPUT("LINE2R"),
+
+	/*
+	 * Virtual output pin to detection block inside codec. This can be
+	 * used to keep codec bias on if gpio or detection features are needed.
+	 * Force pin on or construct a path with an input jack and mic bias
+	 * widgets.
+	 */
+	SND_SOC_DAPM_OUTPUT("Detection"),
+};
+
+static const struct snd_soc_dapm_widget aic3007_dapm_widgets[] = {
+	/* Class-D outputs */
+	SND_SOC_DAPM_PGA("Left Class-D Out", CLASSD_CTRL, 3, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right Class-D Out", CLASSD_CTRL, 2, 0, NULL, 0),
+
+	SND_SOC_DAPM_OUTPUT("SPOP"),
+	SND_SOC_DAPM_OUTPUT("SPOM"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+	/* Left Input */
+	{"Left Line1L Mux", "single-ended", "LINE1L"},
+	{"Left Line1L Mux", "differential", "LINE1L"},
+
+	{"Left Line2L Mux", "single-ended", "LINE2L"},
+	{"Left Line2L Mux", "differential", "LINE2L"},
+
+	{"Left PGA Mixer", "Line1L Switch", "Left Line1L Mux"},
+	{"Left PGA Mixer", "Line1R Switch", "Left Line1R Mux"},
+	{"Left PGA Mixer", "Line2L Switch", "Left Line2L Mux"},
+	{"Left PGA Mixer", "Mic3L Switch", "MIC3L"},
+	{"Left PGA Mixer", "Mic3R Switch", "MIC3R"},
+
+	{"Left ADC", NULL, "Left PGA Mixer"},
+	{"Left ADC", NULL, "GPIO1 dmic modclk"},
+
+	/* Right Input */
+	{"Right Line1R Mux", "single-ended", "LINE1R"},
+	{"Right Line1R Mux", "differential", "LINE1R"},
+
+	{"Right Line2R Mux", "single-ended", "LINE2R"},
+	{"Right Line2R Mux", "differential", "LINE2R"},
+
+	{"Right PGA Mixer", "Line1L Switch", "Right Line1L Mux"},
+	{"Right PGA Mixer", "Line1R Switch", "Right Line1R Mux"},
+	{"Right PGA Mixer", "Line2R Switch", "Right Line2R Mux"},
+	{"Right PGA Mixer", "Mic3L Switch", "MIC3L"},
+	{"Right PGA Mixer", "Mic3R Switch", "MIC3R"},
+
+	{"Right ADC", NULL, "Right PGA Mixer"},
+	{"Right ADC", NULL, "GPIO1 dmic modclk"},
+
+	/*
+	 * Logical path between digital mic enable and GPIO1 modulator clock
+	 * output function
+	 */
+	{"GPIO1 dmic modclk", NULL, "DMic Rate 128"},
+	{"GPIO1 dmic modclk", NULL, "DMic Rate 64"},
+	{"GPIO1 dmic modclk", NULL, "DMic Rate 32"},
+
+	/* Left DAC Output */
+	{"Left DAC Mux", "DAC_L1", "Left DAC"},
+	{"Left DAC Mux", "DAC_L2", "Left DAC"},
+	{"Left DAC Mux", "DAC_L3", "Left DAC"},
+
+	/* Right DAC Output */
+	{"Right DAC Mux", "DAC_R1", "Right DAC"},
+	{"Right DAC Mux", "DAC_R2", "Right DAC"},
+	{"Right DAC Mux", "DAC_R3", "Right DAC"},
+
+	/* Left Line Output */
+	{"Left Line Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+	{"Left Line Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+	{"Left Line Mixer", "DACL1 Switch", "Left DAC Mux"},
+	{"Left Line Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+	{"Left Line Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+	{"Left Line Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+	{"Left Line Out", NULL, "Left Line Mixer"},
+	{"Left Line Out", NULL, "Left DAC Mux"},
+	{"LLOUT", NULL, "Left Line Out"},
+
+	/* Right Line Output */
+	{"Right Line Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+	{"Right Line Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+	{"Right Line Mixer", "DACL1 Switch", "Left DAC Mux"},
+	{"Right Line Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+	{"Right Line Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+	{"Right Line Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+	{"Right Line Out", NULL, "Right Line Mixer"},
+	{"Right Line Out", NULL, "Right DAC Mux"},
+	{"RLOUT", NULL, "Right Line Out"},
+
+	/* Mono Output */
+	{"Mono Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+	{"Mono Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+	{"Mono Mixer", "DACL1 Switch", "Left DAC Mux"},
+	{"Mono Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+	{"Mono Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+	{"Mono Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+	{"Mono Out", NULL, "Mono Mixer"},
+	{"MONO_LOUT", NULL, "Mono Out"},
+
+	/* Left HP Output */
+	{"Left HP Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+	{"Left HP Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+	{"Left HP Mixer", "DACL1 Switch", "Left DAC Mux"},
+	{"Left HP Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+	{"Left HP Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+	{"Left HP Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+	{"Left HP Out", NULL, "Left HP Mixer"},
+	{"Left HP Out", NULL, "Left DAC Mux"},
+	{"HPLOUT", NULL, "Left HP Out"},
+
+	/* Right HP Output */
+	{"Right HP Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+	{"Right HP Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+	{"Right HP Mixer", "DACL1 Switch", "Left DAC Mux"},
+	{"Right HP Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+	{"Right HP Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+	{"Right HP Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+	{"Right HP Out", NULL, "Right HP Mixer"},
+	{"Right HP Out", NULL, "Right DAC Mux"},
+	{"HPROUT", NULL, "Right HP Out"},
+
+	/* Left HPCOM Output */
+	{"Left HPCOM Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+	{"Left HPCOM Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+	{"Left HPCOM Mixer", "DACL1 Switch", "Left DAC Mux"},
+	{"Left HPCOM Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+	{"Left HPCOM Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+	{"Left HPCOM Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+	{"Left HPCOM Mux", "differential of HPLOUT", "Left HP Mixer"},
+	{"Left HPCOM Mux", "constant VCM", "Left HPCOM Mixer"},
+	{"Left HPCOM Mux", "single-ended", "Left HPCOM Mixer"},
+	{"Left HP Com", NULL, "Left HPCOM Mux"},
+	{"HPLCOM", NULL, "Left HP Com"},
+
+	/* Right HPCOM Output */
+	{"Right HPCOM Mixer", "Line2L Bypass Switch", "Left Line2L Mux"},
+	{"Right HPCOM Mixer", "PGAL Bypass Switch", "Left PGA Mixer"},
+	{"Right HPCOM Mixer", "DACL1 Switch", "Left DAC Mux"},
+	{"Right HPCOM Mixer", "Line2R Bypass Switch", "Right Line2R Mux"},
+	{"Right HPCOM Mixer", "PGAR Bypass Switch", "Right PGA Mixer"},
+	{"Right HPCOM Mixer", "DACR1 Switch", "Right DAC Mux"},
+
+	{"Right HPCOM Mux", "differential of HPROUT", "Right HP Mixer"},
+	{"Right HPCOM Mux", "constant VCM", "Right HPCOM Mixer"},
+	{"Right HPCOM Mux", "single-ended", "Right HPCOM Mixer"},
+	{"Right HPCOM Mux", "differential of HPLCOM", "Left HPCOM Mixer"},
+	{"Right HPCOM Mux", "external feedback", "Right HPCOM Mixer"},
+	{"Right HP Com", NULL, "Right HPCOM Mux"},
+	{"HPRCOM", NULL, "Right HP Com"},
+};
+
+static const struct snd_soc_dapm_route intercon_3007[] = {
+	/* Class-D outputs */
+	{"Left Class-D Out", NULL, "Left Line Out"},
+	{"Right Class-D Out", NULL, "Left Line Out"},
+	{"SPOP", NULL, "Left Class-D Out"},
+	{"SPOM", NULL, "Right Class-D Out"},
+};
+
+static int aic3x_add_widgets(struct snd_soc_codec *codec)
+{
+	struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+
+	snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets,
+				  ARRAY_SIZE(aic3x_dapm_widgets));
+
+	/* set up audio path interconnects */
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	if (aic3x->model == AIC3X_MODEL_3007) {
+		snd_soc_dapm_new_controls(codec, aic3007_dapm_widgets,
+			ARRAY_SIZE(aic3007_dapm_widgets));
+		snd_soc_dapm_add_routes(codec, intercon_3007, ARRAY_SIZE(intercon_3007));
+	}
+
+	return 0;
+}
+
+static int aic3x_hw_params(struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *params,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec =rtd->codec;
+	struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+	int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0;
+	u8 data, j, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1;
+	u16 d, pll_d = 1;
+	u8 reg;
+	int clk;
+
+	/* select data word length */
+	data = snd_soc_read(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4));
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		data |= (0x01 << 4);
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		data |= (0x02 << 4);
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		data |= (0x03 << 4);
+		break;
+	}
+	snd_soc_write(codec, AIC3X_ASD_INTF_CTRLB, data);
+
+	/* Fsref can be 44100 or 48000 */
+	fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000;
+
+	/* Try to find a value for Q which allows us to bypass the PLL and
+	 * generate CODEC_CLK directly. */
+	for (pll_q = 2; pll_q < 18; pll_q++)
+		if (aic3x->sysclk / (128 * pll_q) == fsref) {
+			bypass_pll = 1;
+			break;
+		}
+
+	if (bypass_pll) {
+		pll_q &= 0xf;
+		snd_soc_write(codec, AIC3X_PLL_PROGA_REG, pll_q << PLLQ_SHIFT);
+		snd_soc_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_CLKDIV);
+		/* disable PLL if it is bypassed */
+		reg = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
+		snd_soc_write(codec, AIC3X_PLL_PROGA_REG, reg & ~PLL_ENABLE);
+
+	} else {
+		snd_soc_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_PLLDIV);
+		/* enable PLL when it is used */
+		reg = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
+		snd_soc_write(codec, AIC3X_PLL_PROGA_REG, reg | PLL_ENABLE);
+	}
+
+	/* Route Left DAC to left channel input and
+	 * right DAC to right channel input */
+	data = (LDAC2LCH | RDAC2RCH);
+	data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000;
+	if (params_rate(params) >= 64000)
+		data |= DUAL_RATE_MODE;
+	snd_soc_write(codec, AIC3X_CODEC_DATAPATH_REG, data);
+
+	/* codec sample rate select */
+	data = (fsref * 20) / params_rate(params);
+	if (params_rate(params) < 64000)
+		data /= 2;
+	data /= 5;
+	data -= 2;
+	data |= (data << 4);
+	snd_soc_write(codec, AIC3X_SAMPLE_RATE_SEL_REG, data);
+
+	if (bypass_pll)
+		return 0;
+
+	/* Use PLL, compute apropriate setup for j, d, r and p, the closest
+	 * one wins the game. Try with d==0 first, next with d!=0.
+	 * Constraints for j are according to the datasheet.
+	 * The sysclk is divided by 1000 to prevent integer overflows.
+	 */
+
+	codec_clk = (2048 * fsref) / (aic3x->sysclk / 1000);
+
+	for (r = 1; r <= 16; r++)
+		for (p = 1; p <= 8; p++) {
+			for (j = 4; j <= 55; j++) {
+				/* This is actually 1000*((j+(d/10000))*r)/p
+				 * The term had to be converted to get
+				 * rid of the division by 10000; d = 0 here
+				 */
+				int tmp_clk = (1000 * j * r) / p;
+
+				/* Check whether this values get closer than
+				 * the best ones we had before
+				 */
+				if (abs(codec_clk - tmp_clk) <
+					abs(codec_clk - last_clk)) {
+					pll_j = j; pll_d = 0;
+					pll_r = r; pll_p = p;
+					last_clk = tmp_clk;
+				}
+
+				/* Early exit for exact matches */
+				if (tmp_clk == codec_clk)
+					goto found;
+			}
+		}
+
+	/* try with d != 0 */
+	for (p = 1; p <= 8; p++) {
+		j = codec_clk * p / 1000;
+
+		if (j < 4 || j > 11)
+			continue;
+
+		/* do not use codec_clk here since we'd loose precision */
+		d = ((2048 * p * fsref) - j * aic3x->sysclk)
+			* 100 / (aic3x->sysclk/100);
+
+		clk = (10000 * j + d) / (10 * p);
+
+		/* check whether this values get closer than the best
+		 * ones we had before */
+		if (abs(codec_clk - clk) < abs(codec_clk - last_clk)) {
+			pll_j = j; pll_d = d; pll_r = 1; pll_p = p;
+			last_clk = clk;
+		}
+
+		/* Early exit for exact matches */
+		if (clk == codec_clk)
+			goto found;
+	}
+
+	if (last_clk == 0) {
+		printk(KERN_ERR "%s(): unable to setup PLL\n", __func__);
+		return -EINVAL;
+	}
+
+found:
+	data = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
+	snd_soc_write(codec, AIC3X_PLL_PROGA_REG,
+		      data | (pll_p << PLLP_SHIFT));
+	snd_soc_write(codec, AIC3X_OVRF_STATUS_AND_PLLR_REG,
+		      pll_r << PLLR_SHIFT);
+	snd_soc_write(codec, AIC3X_PLL_PROGB_REG, pll_j << PLLJ_SHIFT);
+	snd_soc_write(codec, AIC3X_PLL_PROGC_REG,
+		      (pll_d >> 6) << PLLD_MSB_SHIFT);
+	snd_soc_write(codec, AIC3X_PLL_PROGD_REG,
+		      (pll_d & 0x3F) << PLLD_LSB_SHIFT);
+
+	return 0;
+}
+
+static int aic3x_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u8 ldac_reg = snd_soc_read(codec, LDAC_VOL) & ~MUTE_ON;
+	u8 rdac_reg = snd_soc_read(codec, RDAC_VOL) & ~MUTE_ON;
+
+	if (mute) {
+		snd_soc_write(codec, LDAC_VOL, ldac_reg | MUTE_ON);
+		snd_soc_write(codec, RDAC_VOL, rdac_reg | MUTE_ON);
+	} else {
+		snd_soc_write(codec, LDAC_VOL, ldac_reg);
+		snd_soc_write(codec, RDAC_VOL, rdac_reg);
+	}
+
+	return 0;
+}
+
+static int aic3x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+				int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+
+	aic3x->sysclk = freq;
+	return 0;
+}
+
+static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			     unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+	u8 iface_areg, iface_breg;
+	int delay = 0;
+
+	iface_areg = snd_soc_read(codec, AIC3X_ASD_INTF_CTRLA) & 0x3f;
+	iface_breg = snd_soc_read(codec, AIC3X_ASD_INTF_CTRLB) & 0x3f;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		aic3x->master = 1;
+		iface_areg |= BIT_CLK_MASTER | WORD_CLK_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		aic3x->master = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/*
+	 * match both interface format and signal polarities since they
+	 * are fixed
+	 */
+	switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
+		       SND_SOC_DAIFMT_INV_MASK)) {
+	case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF):
+		break;
+	case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF):
+		delay = 1;
+	case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF):
+		iface_breg |= (0x01 << 6);
+		break;
+	case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF):
+		iface_breg |= (0x02 << 6);
+		break;
+	case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF):
+		iface_breg |= (0x03 << 6);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* set iface */
+	snd_soc_write(codec, AIC3X_ASD_INTF_CTRLA, iface_areg);
+	snd_soc_write(codec, AIC3X_ASD_INTF_CTRLB, iface_breg);
+	snd_soc_write(codec, AIC3X_ASD_INTF_CTRLC, delay);
+
+	return 0;
+}
+
+static int aic3x_init_3007(struct snd_soc_codec *codec)
+{
+	u8 tmp1, tmp2, *cache = codec->reg_cache;
+
+	/*
+	 * There is no need to cache writes to undocumented page 0xD but
+	 * respective page 0 register cache entries must be preserved
+	 */
+	tmp1 = cache[0xD];
+	tmp2 = cache[0x8];
+	/* Class-D speaker driver init; datasheet p. 46 */
+	snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x0D);
+	snd_soc_write(codec, 0xD, 0x0D);
+	snd_soc_write(codec, 0x8, 0x5C);
+	snd_soc_write(codec, 0x8, 0x5D);
+	snd_soc_write(codec, 0x8, 0x5C);
+	snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x00);
+	cache[0xD] = tmp1;
+	cache[0x8] = tmp2;
+
+	return 0;
+}
+
+static int aic3x_regulator_event(struct notifier_block *nb,
+				 unsigned long event, void *data)
+{
+	struct aic3x_disable_nb *disable_nb =
+		container_of(nb, struct aic3x_disable_nb, nb);
+	struct aic3x_priv *aic3x = disable_nb->aic3x;
+
+	if (event & REGULATOR_EVENT_DISABLE) {
+		/*
+		 * Put codec to reset and require cache sync as at least one
+		 * of the supplies was disabled
+		 */
+		if (aic3x->gpio_reset >= 0)
+			gpio_set_value(aic3x->gpio_reset, 0);
+		aic3x->codec->cache_sync = 1;
+	}
+
+	return 0;
+}
+
+static int aic3x_set_power(struct snd_soc_codec *codec, int power)
+{
+	struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+	int i, ret;
+	u8 *cache = codec->reg_cache;
+
+	if (power) {
+		ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies),
+					    aic3x->supplies);
+		if (ret)
+			goto out;
+		aic3x->power = 1;
+		/*
+		 * Reset release and cache sync is necessary only if some
+		 * supply was off or if there were cached writes
+		 */
+		if (!codec->cache_sync)
+			goto out;
+
+		if (aic3x->gpio_reset >= 0) {
+			udelay(1);
+			gpio_set_value(aic3x->gpio_reset, 1);
+		}
+
+		/* Sync reg_cache with the hardware */
+		codec->cache_only = 0;
+		for (i = 0; i < ARRAY_SIZE(aic3x_reg); i++)
+			snd_soc_write(codec, i, cache[i]);
+		if (aic3x->model == AIC3X_MODEL_3007)
+			aic3x_init_3007(codec);
+		codec->cache_sync = 0;
+	} else {
+		aic3x->power = 0;
+		/* HW writes are needless when bias is off */
+		codec->cache_only = 1;
+		ret = regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies),
+					     aic3x->supplies);
+	}
+out:
+	return ret;
+}
+
+static int aic3x_set_bias_level(struct snd_soc_codec *codec,
+				enum snd_soc_bias_level level)
+{
+	struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+	u8 reg;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		if (codec->bias_level == SND_SOC_BIAS_STANDBY &&
+		    aic3x->master) {
+			/* enable pll */
+			reg = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
+			snd_soc_write(codec, AIC3X_PLL_PROGA_REG,
+				      reg | PLL_ENABLE);
+		}
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (!aic3x->power)
+			aic3x_set_power(codec, 1);
+		if (codec->bias_level == SND_SOC_BIAS_PREPARE &&
+		    aic3x->master) {
+			/* disable pll */
+			reg = snd_soc_read(codec, AIC3X_PLL_PROGA_REG);
+			snd_soc_write(codec, AIC3X_PLL_PROGA_REG,
+				      reg & ~PLL_ENABLE);
+		}
+		break;
+	case SND_SOC_BIAS_OFF:
+		if (aic3x->power)
+			aic3x_set_power(codec, 0);
+		break;
+	}
+	codec->bias_level = level;
+
+	return 0;
+}
+
+void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state)
+{
+	u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG;
+	u8 bit = gpio ? 3: 0;
+	u8 val = snd_soc_read(codec, reg) & ~(1 << bit);
+	snd_soc_write(codec, reg, val | (!!state << bit));
+}
+EXPORT_SYMBOL_GPL(aic3x_set_gpio);
+
+int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio)
+{
+	u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG;
+	u8 val = 0, bit = gpio ? 2 : 1;
+
+	aic3x_read(codec, reg, &val);
+	return (val >> bit) & 1;
+}
+EXPORT_SYMBOL_GPL(aic3x_get_gpio);
+
+void aic3x_set_headset_detection(struct snd_soc_codec *codec, int detect,
+				 int headset_debounce, int button_debounce)
+{
+	u8 val;
+
+	val = ((detect & AIC3X_HEADSET_DETECT_MASK)
+		<< AIC3X_HEADSET_DETECT_SHIFT) |
+	      ((headset_debounce & AIC3X_HEADSET_DEBOUNCE_MASK)
+		<< AIC3X_HEADSET_DEBOUNCE_SHIFT) |
+	      ((button_debounce & AIC3X_BUTTON_DEBOUNCE_MASK)
+		<< AIC3X_BUTTON_DEBOUNCE_SHIFT);
+
+	if (detect & AIC3X_HEADSET_DETECT_MASK)
+		val |= AIC3X_HEADSET_DETECT_ENABLED;
+
+	snd_soc_write(codec, AIC3X_HEADSET_DETECT_CTRL_A, val);
+}
+EXPORT_SYMBOL_GPL(aic3x_set_headset_detection);
+
+int aic3x_headset_detected(struct snd_soc_codec *codec)
+{
+	u8 val = 0;
+	aic3x_read(codec, AIC3X_HEADSET_DETECT_CTRL_B, &val);
+	return (val >> 4) & 1;
+}
+EXPORT_SYMBOL_GPL(aic3x_headset_detected);
+
+int aic3x_button_pressed(struct snd_soc_codec *codec)
+{
+	u8 val = 0;
+	aic3x_read(codec, AIC3X_HEADSET_DETECT_CTRL_B, &val);
+	return (val >> 5) & 1;
+}
+EXPORT_SYMBOL_GPL(aic3x_button_pressed);
+
+#define AIC3X_RATES	SNDRV_PCM_RATE_8000_96000
+#define AIC3X_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+			 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops aic3x_dai_ops = {
+	.hw_params	= aic3x_hw_params,
+	.digital_mute	= aic3x_mute,
+	.set_sysclk	= aic3x_set_dai_sysclk,
+	.set_fmt	= aic3x_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver aic3x_dai = {
+	.name = "tlv320aic3x-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = AIC3X_RATES,
+		.formats = AIC3X_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = AIC3X_RATES,
+		.formats = AIC3X_FORMATS,},
+	.ops = &aic3x_dai_ops,
+	.symmetric_rates = 1,
+};
+
+static int aic3x_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int aic3x_resume(struct snd_soc_codec *codec)
+{
+	aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+/*
+ * initialise the AIC3X driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int aic3x_init(struct snd_soc_codec *codec)
+{
+	struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+	int reg;
+
+	snd_soc_write(codec, AIC3X_PAGE_SELECT, PAGE0_SELECT);
+	snd_soc_write(codec, AIC3X_RESET, SOFT_RESET);
+
+	/* DAC default volume and mute */
+	snd_soc_write(codec, LDAC_VOL, DEFAULT_VOL | MUTE_ON);
+	snd_soc_write(codec, RDAC_VOL, DEFAULT_VOL | MUTE_ON);
+
+	/* DAC to HP default volume and route to Output mixer */
+	snd_soc_write(codec, DACL1_2_HPLOUT_VOL, DEFAULT_VOL | ROUTE_ON);
+	snd_soc_write(codec, DACR1_2_HPROUT_VOL, DEFAULT_VOL | ROUTE_ON);
+	snd_soc_write(codec, DACL1_2_HPLCOM_VOL, DEFAULT_VOL | ROUTE_ON);
+	snd_soc_write(codec, DACR1_2_HPRCOM_VOL, DEFAULT_VOL | ROUTE_ON);
+	/* DAC to Line Out default volume and route to Output mixer */
+	snd_soc_write(codec, DACL1_2_LLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+	snd_soc_write(codec, DACR1_2_RLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+	/* DAC to Mono Line Out default volume and route to Output mixer */
+	snd_soc_write(codec, DACL1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+	snd_soc_write(codec, DACR1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON);
+
+	/* unmute all outputs */
+	reg = snd_soc_read(codec, LLOPM_CTRL);
+	snd_soc_write(codec, LLOPM_CTRL, reg | UNMUTE);
+	reg = snd_soc_read(codec, RLOPM_CTRL);
+	snd_soc_write(codec, RLOPM_CTRL, reg | UNMUTE);
+	reg = snd_soc_read(codec, MONOLOPM_CTRL);
+	snd_soc_write(codec, MONOLOPM_CTRL, reg | UNMUTE);
+	reg = snd_soc_read(codec, HPLOUT_CTRL);
+	snd_soc_write(codec, HPLOUT_CTRL, reg | UNMUTE);
+	reg = snd_soc_read(codec, HPROUT_CTRL);
+	snd_soc_write(codec, HPROUT_CTRL, reg | UNMUTE);
+	reg = snd_soc_read(codec, HPLCOM_CTRL);
+	snd_soc_write(codec, HPLCOM_CTRL, reg | UNMUTE);
+	reg = snd_soc_read(codec, HPRCOM_CTRL);
+	snd_soc_write(codec, HPRCOM_CTRL, reg | UNMUTE);
+
+	/* ADC default volume and unmute */
+	snd_soc_write(codec, LADC_VOL, DEFAULT_GAIN);
+	snd_soc_write(codec, RADC_VOL, DEFAULT_GAIN);
+	/* By default route Line1 to ADC PGA mixer */
+	snd_soc_write(codec, LINE1L_2_LADC_CTRL, 0x0);
+	snd_soc_write(codec, LINE1R_2_RADC_CTRL, 0x0);
+
+	/* PGA to HP Bypass default volume, disconnect from Output Mixer */
+	snd_soc_write(codec, PGAL_2_HPLOUT_VOL, DEFAULT_VOL);
+	snd_soc_write(codec, PGAR_2_HPROUT_VOL, DEFAULT_VOL);
+	snd_soc_write(codec, PGAL_2_HPLCOM_VOL, DEFAULT_VOL);
+	snd_soc_write(codec, PGAR_2_HPRCOM_VOL, DEFAULT_VOL);
+	/* PGA to Line Out default volume, disconnect from Output Mixer */
+	snd_soc_write(codec, PGAL_2_LLOPM_VOL, DEFAULT_VOL);
+	snd_soc_write(codec, PGAR_2_RLOPM_VOL, DEFAULT_VOL);
+	/* PGA to Mono Line Out default volume, disconnect from Output Mixer */
+	snd_soc_write(codec, PGAL_2_MONOLOPM_VOL, DEFAULT_VOL);
+	snd_soc_write(codec, PGAR_2_MONOLOPM_VOL, DEFAULT_VOL);
+
+	/* Line2 to HP Bypass default volume, disconnect from Output Mixer */
+	snd_soc_write(codec, LINE2L_2_HPLOUT_VOL, DEFAULT_VOL);
+	snd_soc_write(codec, LINE2R_2_HPROUT_VOL, DEFAULT_VOL);
+	snd_soc_write(codec, LINE2L_2_HPLCOM_VOL, DEFAULT_VOL);
+	snd_soc_write(codec, LINE2R_2_HPRCOM_VOL, DEFAULT_VOL);
+	/* Line2 Line Out default volume, disconnect from Output Mixer */
+	snd_soc_write(codec, LINE2L_2_LLOPM_VOL, DEFAULT_VOL);
+	snd_soc_write(codec, LINE2R_2_RLOPM_VOL, DEFAULT_VOL);
+	/* Line2 to Mono Out default volume, disconnect from Output Mixer */
+	snd_soc_write(codec, LINE2L_2_MONOLOPM_VOL, DEFAULT_VOL);
+	snd_soc_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL);
+
+	if (aic3x->model == AIC3X_MODEL_3007) {
+		aic3x_init_3007(codec);
+		snd_soc_write(codec, CLASSD_CTRL, 0);
+	}
+
+	return 0;
+}
+
+static int aic3x_probe(struct snd_soc_codec *codec)
+{
+	struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+	int ret, i;
+
+	codec->control_data = aic3x->control_data;
+	aic3x->codec = codec;
+	codec->idle_bias_off = 1;
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 8, aic3x->control_type);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	if (aic3x->gpio_reset >= 0) {
+		ret = gpio_request(aic3x->gpio_reset, "tlv320aic3x reset");
+		if (ret != 0)
+			goto err_gpio;
+		gpio_direction_output(aic3x->gpio_reset, 0);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++)
+		aic3x->supplies[i].supply = aic3x_supply_names[i];
+
+	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(aic3x->supplies),
+				 aic3x->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+		goto err_get;
+	}
+	for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) {
+		aic3x->disable_nb[i].nb.notifier_call = aic3x_regulator_event;
+		aic3x->disable_nb[i].aic3x = aic3x;
+		ret = regulator_register_notifier(aic3x->supplies[i].consumer,
+						  &aic3x->disable_nb[i].nb);
+		if (ret) {
+			dev_err(codec->dev,
+				"Failed to request regulator notifier: %d\n",
+				 ret);
+			goto err_notif;
+		}
+	}
+
+	codec->cache_only = 1;
+	aic3x_init(codec);
+
+	if (aic3x->setup) {
+		/* setup GPIO functions */
+		snd_soc_write(codec, AIC3X_GPIO1_REG,
+			      (aic3x->setup->gpio_func[0] & 0xf) << 4);
+		snd_soc_write(codec, AIC3X_GPIO2_REG,
+			      (aic3x->setup->gpio_func[1] & 0xf) << 4);
+	}
+
+	snd_soc_add_controls(codec, aic3x_snd_controls,
+			     ARRAY_SIZE(aic3x_snd_controls));
+	if (aic3x->model == AIC3X_MODEL_3007)
+		snd_soc_add_controls(codec, &aic3x_classd_amp_gain_ctrl, 1);
+
+	aic3x_add_widgets(codec);
+
+	return 0;
+
+err_notif:
+	while (i--)
+		regulator_unregister_notifier(aic3x->supplies[i].consumer,
+					      &aic3x->disable_nb[i].nb);
+	regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
+err_get:
+	if (aic3x->gpio_reset >= 0)
+		gpio_free(aic3x->gpio_reset);
+err_gpio:
+	kfree(aic3x);
+	return ret;
+}
+
+static int aic3x_remove(struct snd_soc_codec *codec)
+{
+	struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+	int i;
+
+	aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	if (aic3x->gpio_reset >= 0) {
+		gpio_set_value(aic3x->gpio_reset, 0);
+		gpio_free(aic3x->gpio_reset);
+	}
+	for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++)
+		regulator_unregister_notifier(aic3x->supplies[i].consumer,
+					      &aic3x->disable_nb[i].nb);
+	regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_aic3x = {
+	.set_bias_level = aic3x_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(aic3x_reg),
+	.reg_word_size = sizeof(u8),
+	.reg_cache_default = aic3x_reg,
+	.probe = aic3x_probe,
+	.remove = aic3x_remove,
+	.suspend = aic3x_suspend,
+	.resume = aic3x_resume,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * AIC3X 2 wire address can be up to 4 devices with device addresses
+ * 0x18, 0x19, 0x1A, 0x1B
+ */
+
+static const struct i2c_device_id aic3x_i2c_id[] = {
+	[AIC3X_MODEL_3X] = { "tlv320aic3x", 0 },
+	[AIC3X_MODEL_33] = { "tlv320aic33", 0 },
+	[AIC3X_MODEL_3007] = { "tlv320aic3007", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id);
+
+/*
+ * If the i2c layer weren't so broken, we could pass this kind of data
+ * around
+ */
+static int aic3x_i2c_probe(struct i2c_client *i2c,
+			   const struct i2c_device_id *id)
+{
+	struct aic3x_pdata *pdata = i2c->dev.platform_data;
+	struct aic3x_priv *aic3x;
+	int ret;
+	const struct i2c_device_id *tbl;
+
+	aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL);
+	if (aic3x == NULL) {
+		dev_err(&i2c->dev, "failed to create private data\n");
+		return -ENOMEM;
+	}
+
+	aic3x->control_data = i2c;
+	aic3x->control_type = SND_SOC_I2C;
+
+	i2c_set_clientdata(i2c, aic3x);
+	if (pdata) {
+		aic3x->gpio_reset = pdata->gpio_reset;
+		aic3x->setup = pdata->setup;
+	} else {
+		aic3x->gpio_reset = -1;
+	}
+
+	for (tbl = aic3x_i2c_id; tbl->name[0]; tbl++) {
+		if (!strcmp(tbl->name, id->name))
+			break;
+	}
+	aic3x->model = tbl - aic3x_i2c_id;
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_aic3x, &aic3x_dai, 1);
+	if (ret < 0)
+		kfree(aic3x);
+	return ret;
+}
+
+static int aic3x_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+/* machine i2c codec control layer */
+static struct i2c_driver aic3x_i2c_driver = {
+	.driver = {
+		.name = "tlv320aic3x-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe	= aic3x_i2c_probe,
+	.remove = aic3x_i2c_remove,
+	.id_table = aic3x_i2c_id,
+};
+
+static inline void aic3x_i2c_init(void)
+{
+	int ret;
+
+	ret = i2c_add_driver(&aic3x_i2c_driver);
+	if (ret)
+		printk(KERN_ERR "%s: error regsitering i2c driver, %d\n",
+		       __func__, ret);
+}
+
+static inline void aic3x_i2c_exit(void)
+{
+	i2c_del_driver(&aic3x_i2c_driver);
+}
+#endif
+
+static int __init aic3x_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&aic3x_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register TLV320AIC3x I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(aic3x_modinit);
+
+static void __exit aic3x_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&aic3x_i2c_driver);
+#endif
+}
+module_exit(aic3x_exit);
+
+MODULE_DESCRIPTION("ASoC TLV320AIC3X codec driver");
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/tlv320aic3x.h b/linux/sound/soc/codecs/tlv320aic3x.h
new file mode 100644
index 0000000..06a1978
--- /dev/null
+++ b/linux/sound/soc/codecs/tlv320aic3x.h
@@ -0,0 +1,261 @@
+/*
+ * ALSA SoC TLV320AIC3X codec driver
+ *
+ * Author:      Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright:   (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _AIC3X_H
+#define _AIC3X_H
+
+/* AIC3X register space */
+#define AIC3X_CACHEREGNUM		103
+
+/* Page select register */
+#define AIC3X_PAGE_SELECT		0
+/* Software reset register */
+#define AIC3X_RESET			1
+/* Codec Sample rate select register */
+#define AIC3X_SAMPLE_RATE_SEL_REG	2
+/* PLL progrramming register A */
+#define AIC3X_PLL_PROGA_REG		3
+/* PLL progrramming register B */
+#define AIC3X_PLL_PROGB_REG		4
+/* PLL progrramming register C */
+#define AIC3X_PLL_PROGC_REG		5
+/* PLL progrramming register D */
+#define AIC3X_PLL_PROGD_REG		6
+/* Codec datapath setup register */
+#define AIC3X_CODEC_DATAPATH_REG	7
+/* Audio serial data interface control register A */
+#define AIC3X_ASD_INTF_CTRLA		8
+/* Audio serial data interface control register B */
+#define AIC3X_ASD_INTF_CTRLB		9
+/* Audio serial data interface control register C */
+#define AIC3X_ASD_INTF_CTRLC		10
+/* Audio overflow status and PLL R value programming register */
+#define AIC3X_OVRF_STATUS_AND_PLLR_REG	11
+/* Audio codec digital filter control register */
+#define AIC3X_CODEC_DFILT_CTRL		12
+/* Headset/button press detection register */
+#define AIC3X_HEADSET_DETECT_CTRL_A	13
+#define AIC3X_HEADSET_DETECT_CTRL_B	14
+/* ADC PGA Gain control registers */
+#define LADC_VOL			15
+#define RADC_VOL			16
+/* MIC3 control registers */
+#define MIC3LR_2_LADC_CTRL		17
+#define MIC3LR_2_RADC_CTRL		18
+/* Line1 Input control registers */
+#define LINE1L_2_LADC_CTRL		19
+#define LINE1R_2_LADC_CTRL		21
+#define LINE1R_2_RADC_CTRL		22
+#define LINE1L_2_RADC_CTRL		24
+/* Line2 Input control registers */
+#define LINE2L_2_LADC_CTRL		20
+#define LINE2R_2_RADC_CTRL		23
+/* MICBIAS Control Register */
+#define MICBIAS_CTRL			25
+
+/* AGC Control Registers A, B, C */
+#define LAGC_CTRL_A			26
+#define LAGC_CTRL_B			27
+#define LAGC_CTRL_C			28
+#define RAGC_CTRL_A			29
+#define RAGC_CTRL_B			30
+#define RAGC_CTRL_C			31
+
+/* DAC Power and Left High Power Output control registers */
+#define DAC_PWR				37
+#define HPLCOM_CFG			37
+/* Right High Power Output control registers */
+#define HPRCOM_CFG			38
+/* DAC Output Switching control registers */
+#define DAC_LINE_MUX			41
+/* High Power Output Driver Pop Reduction registers */
+#define HPOUT_POP_REDUCTION		42
+/* DAC Digital control registers */
+#define LDAC_VOL			43
+#define RDAC_VOL			44
+/* Left High Power Output control registers */
+#define LINE2L_2_HPLOUT_VOL		45
+#define PGAL_2_HPLOUT_VOL		46
+#define DACL1_2_HPLOUT_VOL		47
+#define LINE2R_2_HPLOUT_VOL		48
+#define PGAR_2_HPLOUT_VOL		49
+#define DACR1_2_HPLOUT_VOL		50
+#define HPLOUT_CTRL			51
+/* Left High Power COM control registers */
+#define LINE2L_2_HPLCOM_VOL		52
+#define PGAL_2_HPLCOM_VOL		53
+#define DACL1_2_HPLCOM_VOL		54
+#define LINE2R_2_HPLCOM_VOL		55
+#define PGAR_2_HPLCOM_VOL		56
+#define DACR1_2_HPLCOM_VOL		57
+#define HPLCOM_CTRL			58
+/* Right High Power Output control registers */
+#define LINE2L_2_HPROUT_VOL		59
+#define PGAL_2_HPROUT_VOL		60
+#define DACL1_2_HPROUT_VOL		61
+#define LINE2R_2_HPROUT_VOL		62
+#define PGAR_2_HPROUT_VOL		63
+#define DACR1_2_HPROUT_VOL		64
+#define HPROUT_CTRL			65
+/* Right High Power COM control registers */
+#define LINE2L_2_HPRCOM_VOL		66
+#define PGAL_2_HPRCOM_VOL		67
+#define DACL1_2_HPRCOM_VOL		68
+#define LINE2R_2_HPRCOM_VOL		69
+#define PGAR_2_HPRCOM_VOL		70
+#define DACR1_2_HPRCOM_VOL		71
+#define HPRCOM_CTRL			72
+/* Mono Line Output Plus/Minus control registers */
+#define LINE2L_2_MONOLOPM_VOL		73
+#define PGAL_2_MONOLOPM_VOL		74
+#define DACL1_2_MONOLOPM_VOL		75
+#define LINE2R_2_MONOLOPM_VOL		76
+#define PGAR_2_MONOLOPM_VOL		77
+#define DACR1_2_MONOLOPM_VOL		78
+#define MONOLOPM_CTRL			79
+/* Class-D speaker driver on tlv320aic3007 */
+#define CLASSD_CTRL			73
+/* Left Line Output Plus/Minus control registers */
+#define LINE2L_2_LLOPM_VOL		80
+#define PGAL_2_LLOPM_VOL		81
+#define DACL1_2_LLOPM_VOL		82
+#define LINE2R_2_LLOPM_VOL		83
+#define PGAR_2_LLOPM_VOL		84
+#define DACR1_2_LLOPM_VOL		85
+#define LLOPM_CTRL			86
+/* Right Line Output Plus/Minus control registers */
+#define LINE2L_2_RLOPM_VOL		87
+#define PGAL_2_RLOPM_VOL		88
+#define DACL1_2_RLOPM_VOL		89
+#define LINE2R_2_RLOPM_VOL		90
+#define PGAR_2_RLOPM_VOL		91
+#define DACR1_2_RLOPM_VOL		92
+#define RLOPM_CTRL			93
+/* GPIO/IRQ registers */
+#define AIC3X_STICKY_IRQ_FLAGS_REG	96
+#define AIC3X_RT_IRQ_FLAGS_REG		97
+#define AIC3X_GPIO1_REG			98
+#define AIC3X_GPIO2_REG			99
+#define AIC3X_GPIOA_REG			100
+#define AIC3X_GPIOB_REG			101
+/* Clock generation control register */
+#define AIC3X_CLKGEN_CTRL_REG		102
+
+/* Page select register bits */
+#define PAGE0_SELECT		0
+#define PAGE1_SELECT		1
+
+/* Audio serial data interface control register A bits */
+#define BIT_CLK_MASTER          0x80
+#define WORD_CLK_MASTER         0x40
+
+/* Codec Datapath setup register 7 */
+#define FSREF_44100		(1 << 7)
+#define FSREF_48000		(0 << 7)
+#define DUAL_RATE_MODE		((1 << 5) | (1 << 6))
+#define LDAC2LCH		(0x1 << 3)
+#define RDAC2RCH		(0x1 << 1)
+
+/* PLL registers bitfields */
+#define PLLP_SHIFT		0
+#define PLLQ_SHIFT		3
+#define PLLR_SHIFT		0
+#define PLLJ_SHIFT		2
+#define PLLD_MSB_SHIFT		0
+#define PLLD_LSB_SHIFT		2
+
+/* Clock generation register bits */
+#define CODEC_CLKIN_PLLDIV	0
+#define CODEC_CLKIN_CLKDIV	1
+#define PLL_CLKIN_SHIFT		4
+#define MCLK_SOURCE		0x0
+#define PLL_CLKDIV_SHIFT	0
+
+/* Software reset register bits */
+#define SOFT_RESET		0x80
+
+/* PLL progrramming register A bits */
+#define PLL_ENABLE		0x80
+
+/* Route bits */
+#define ROUTE_ON		0x80
+
+/* Mute bits */
+#define UNMUTE			0x08
+#define MUTE_ON			0x80
+
+/* Power bits */
+#define LADC_PWR_ON		0x04
+#define RADC_PWR_ON		0x04
+#define LDAC_PWR_ON		0x80
+#define RDAC_PWR_ON		0x40
+#define HPLOUT_PWR_ON		0x01
+#define HPROUT_PWR_ON		0x01
+#define HPLCOM_PWR_ON		0x01
+#define HPRCOM_PWR_ON		0x01
+#define MONOLOPM_PWR_ON		0x01
+#define LLOPM_PWR_ON		0x01
+#define RLOPM_PWR_ON	0x01
+
+#define INVERT_VOL(val)   (0x7f - val)
+
+/* Default output volume (inverted) */
+#define DEFAULT_VOL     INVERT_VOL(0x50)
+/* Default input volume */
+#define DEFAULT_GAIN    0x20
+
+void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state);
+int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio);
+
+/* headset detection / button API */
+
+/* The AIC3x supports detection of stereo headsets (GND + left + right signal)
+ * and cellular headsets (GND + speaker output + microphone input).
+ * It is recommended to enable MIC bias for this function to work properly.
+ * For more information, please refer to the datasheet. */
+enum {
+	AIC3X_HEADSET_DETECT_OFF	= 0,
+	AIC3X_HEADSET_DETECT_STEREO	= 1,
+	AIC3X_HEADSET_DETECT_CELLULAR   = 2,
+	AIC3X_HEADSET_DETECT_BOTH	= 3
+};
+
+enum {
+	AIC3X_HEADSET_DEBOUNCE_16MS	= 0,
+	AIC3X_HEADSET_DEBOUNCE_32MS	= 1,
+	AIC3X_HEADSET_DEBOUNCE_64MS	= 2,
+	AIC3X_HEADSET_DEBOUNCE_128MS	= 3,
+	AIC3X_HEADSET_DEBOUNCE_256MS	= 4,
+	AIC3X_HEADSET_DEBOUNCE_512MS	= 5
+};
+
+enum {
+	AIC3X_BUTTON_DEBOUNCE_0MS	= 0,
+	AIC3X_BUTTON_DEBOUNCE_8MS	= 1,
+	AIC3X_BUTTON_DEBOUNCE_16MS	= 2,
+	AIC3X_BUTTON_DEBOUNCE_32MS	= 3
+};
+
+#define AIC3X_HEADSET_DETECT_ENABLED	0x80
+#define AIC3X_HEADSET_DETECT_SHIFT	5
+#define AIC3X_HEADSET_DETECT_MASK	3
+#define AIC3X_HEADSET_DEBOUNCE_SHIFT	2
+#define AIC3X_HEADSET_DEBOUNCE_MASK	7
+#define AIC3X_BUTTON_DEBOUNCE_SHIFT 	0
+#define AIC3X_BUTTON_DEBOUNCE_MASK	3
+
+/* see the enums above for valid parameters to this function */
+void aic3x_set_headset_detection(struct snd_soc_codec *codec, int detect,
+				 int headset_debounce, int button_debounce);
+int aic3x_headset_detected(struct snd_soc_codec *codec);
+int aic3x_button_pressed(struct snd_soc_codec *codec);
+
+#endif /* _AIC3X_H */
diff --git a/linux/sound/soc/codecs/tlv320dac33.c b/linux/sound/soc/codecs/tlv320dac33.c
new file mode 100644
index 0000000..c5ab8c8
--- /dev/null
+++ b/linux/sound/soc/codecs/tlv320dac33.c
@@ -0,0 +1,1676 @@
+/*
+ * ALSA SoC Texas Instruments TLV320DAC33 codec driver
+ *
+ * Author:	Peter Ujfalusi <peter.ujfalusi@nokia.com>
+ *
+ * Copyright:   (C) 2009 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <sound/tlv320dac33-plat.h>
+#include "tlv320dac33.h"
+
+#define DAC33_BUFFER_SIZE_BYTES		24576	/* bytes, 12288 16 bit words,
+						 * 6144 stereo */
+#define DAC33_BUFFER_SIZE_SAMPLES	6144
+
+#define NSAMPLE_MAX		5700
+
+#define MODE7_LTHR		10
+#define MODE7_UTHR		(DAC33_BUFFER_SIZE_SAMPLES - 10)
+
+#define BURST_BASEFREQ_HZ	49152000
+
+#define SAMPLES_TO_US(rate, samples) \
+	(1000000000 / ((rate * 1000) / samples))
+
+#define US_TO_SAMPLES(rate, us) \
+	(rate / (1000000 / (us < 1000000 ? us : 1000000)))
+
+#define UTHR_FROM_PERIOD_SIZE(samples, playrate, burstrate) \
+	((samples * 5000) / ((burstrate * 5000) / (burstrate - playrate)))
+
+static void dac33_calculate_times(struct snd_pcm_substream *substream);
+static int dac33_prepare_chip(struct snd_pcm_substream *substream);
+
+enum dac33_state {
+	DAC33_IDLE = 0,
+	DAC33_PREFILL,
+	DAC33_PLAYBACK,
+	DAC33_FLUSH,
+};
+
+enum dac33_fifo_modes {
+	DAC33_FIFO_BYPASS = 0,
+	DAC33_FIFO_MODE1,
+	DAC33_FIFO_MODE7,
+	DAC33_FIFO_LAST_MODE,
+};
+
+#define DAC33_NUM_SUPPLIES 3
+static const char *dac33_supply_names[DAC33_NUM_SUPPLIES] = {
+	"AVDD",
+	"DVDD",
+	"IOVDD",
+};
+
+struct tlv320dac33_priv {
+	struct mutex mutex;
+	struct workqueue_struct *dac33_wq;
+	struct work_struct work;
+	struct snd_soc_codec *codec;
+	struct regulator_bulk_data supplies[DAC33_NUM_SUPPLIES];
+	struct snd_pcm_substream *substream;
+	int power_gpio;
+	int chip_power;
+	int irq;
+	unsigned int refclk;
+
+	unsigned int alarm_threshold;	/* set to be half of LATENCY_TIME_MS */
+	unsigned int nsample_min;	/* nsample should not be lower than
+					 * this */
+	unsigned int nsample_max;	/* nsample should not be higher than
+					 * this */
+	enum dac33_fifo_modes fifo_mode;/* FIFO mode selection */
+	unsigned int nsample;		/* burst read amount from host */
+	int mode1_latency;		/* latency caused by the i2c writes in
+					 * us */
+	int auto_fifo_config; 		/* Configure the FIFO based on the
+					 * period size */
+	u8 burst_bclkdiv;		/* BCLK divider value in burst mode */
+	unsigned int burst_rate;	/* Interface speed in Burst modes */
+
+	int keep_bclk;			/* Keep the BCLK continuously running
+					 * in FIFO modes */
+	spinlock_t lock;
+	unsigned long long t_stamp1;	/* Time stamp for FIFO modes to */
+	unsigned long long t_stamp2;	/* calculate the FIFO caused delay */
+
+	unsigned int mode1_us_burst;	/* Time to burst read n number of
+					 * samples */
+	unsigned int mode7_us_to_lthr;	/* Time to reach lthr from uthr */
+
+	unsigned int uthr;
+
+	enum dac33_state state;
+	enum snd_soc_control_type control_type;
+	void *control_data;
+};
+
+static const u8 dac33_reg[DAC33_CACHEREGNUM] = {
+0x00, 0x00, 0x00, 0x00, /* 0x00 - 0x03 */
+0x00, 0x00, 0x00, 0x00, /* 0x04 - 0x07 */
+0x00, 0x00, 0x00, 0x00, /* 0x08 - 0x0b */
+0x00, 0x00, 0x00, 0x00, /* 0x0c - 0x0f */
+0x00, 0x00, 0x00, 0x00, /* 0x10 - 0x13 */
+0x00, 0x00, 0x00, 0x00, /* 0x14 - 0x17 */
+0x00, 0x00, 0x00, 0x00, /* 0x18 - 0x1b */
+0x00, 0x00, 0x00, 0x00, /* 0x1c - 0x1f */
+0x00, 0x00, 0x00, 0x00, /* 0x20 - 0x23 */
+0x00, 0x00, 0x00, 0x00, /* 0x24 - 0x27 */
+0x00, 0x00, 0x00, 0x00, /* 0x28 - 0x2b */
+0x00, 0x00, 0x00, 0x80, /* 0x2c - 0x2f */
+0x80, 0x00, 0x00, 0x00, /* 0x30 - 0x33 */
+0x00, 0x00, 0x00, 0x00, /* 0x34 - 0x37 */
+0x00, 0x00,             /* 0x38 - 0x39 */
+/* Registers 0x3a - 0x3f are reserved  */
+            0x00, 0x00, /* 0x3a - 0x3b */
+0x00, 0x00, 0x00, 0x00, /* 0x3c - 0x3f */
+
+0x00, 0x00, 0x00, 0x00, /* 0x40 - 0x43 */
+0x00, 0x80,             /* 0x44 - 0x45 */
+/* Registers 0x46 - 0x47 are reserved  */
+            0x80, 0x80, /* 0x46 - 0x47 */
+
+0x80, 0x00, 0x00,       /* 0x48 - 0x4a */
+/* Registers 0x4b - 0x7c are reserved  */
+                  0x00, /* 0x4b        */
+0x00, 0x00, 0x00, 0x00, /* 0x4c - 0x4f */
+0x00, 0x00, 0x00, 0x00, /* 0x50 - 0x53 */
+0x00, 0x00, 0x00, 0x00, /* 0x54 - 0x57 */
+0x00, 0x00, 0x00, 0x00, /* 0x58 - 0x5b */
+0x00, 0x00, 0x00, 0x00, /* 0x5c - 0x5f */
+0x00, 0x00, 0x00, 0x00, /* 0x60 - 0x63 */
+0x00, 0x00, 0x00, 0x00, /* 0x64 - 0x67 */
+0x00, 0x00, 0x00, 0x00, /* 0x68 - 0x6b */
+0x00, 0x00, 0x00, 0x00, /* 0x6c - 0x6f */
+0x00, 0x00, 0x00, 0x00, /* 0x70 - 0x73 */
+0x00, 0x00, 0x00, 0x00, /* 0x74 - 0x77 */
+0x00, 0x00, 0x00, 0x00, /* 0x78 - 0x7b */
+0x00,                   /* 0x7c        */
+
+      0xda, 0x33, 0x03, /* 0x7d - 0x7f */
+};
+
+/* Register read and write */
+static inline unsigned int dac33_read_reg_cache(struct snd_soc_codec *codec,
+						unsigned reg)
+{
+	u8 *cache = codec->reg_cache;
+	if (reg >= DAC33_CACHEREGNUM)
+		return 0;
+
+	return cache[reg];
+}
+
+static inline void dac33_write_reg_cache(struct snd_soc_codec *codec,
+					 u8 reg, u8 value)
+{
+	u8 *cache = codec->reg_cache;
+	if (reg >= DAC33_CACHEREGNUM)
+		return;
+
+	cache[reg] = value;
+}
+
+static int dac33_read(struct snd_soc_codec *codec, unsigned int reg,
+		      u8 *value)
+{
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	int val, ret = 0;
+
+	*value = reg & 0xff;
+
+	/* If powered off, return the cached value */
+	if (dac33->chip_power) {
+		val = i2c_smbus_read_byte_data(codec->control_data, value[0]);
+		if (val < 0) {
+			dev_err(codec->dev, "Read failed (%d)\n", val);
+			value[0] = dac33_read_reg_cache(codec, reg);
+			ret = val;
+		} else {
+			value[0] = val;
+			dac33_write_reg_cache(codec, reg, val);
+		}
+	} else {
+		value[0] = dac33_read_reg_cache(codec, reg);
+	}
+
+	return ret;
+}
+
+static int dac33_write(struct snd_soc_codec *codec, unsigned int reg,
+		       unsigned int value)
+{
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	u8 data[2];
+	int ret = 0;
+
+	/*
+	 * data is
+	 *   D15..D8 dac33 register offset
+	 *   D7...D0 register data
+	 */
+	data[0] = reg & 0xff;
+	data[1] = value & 0xff;
+
+	dac33_write_reg_cache(codec, data[0], data[1]);
+	if (dac33->chip_power) {
+		ret = codec->hw_write(codec->control_data, data, 2);
+		if (ret != 2)
+			dev_err(codec->dev, "Write failed (%d)\n", ret);
+		else
+			ret = 0;
+	}
+
+	return ret;
+}
+
+static int dac33_write_locked(struct snd_soc_codec *codec, unsigned int reg,
+		       unsigned int value)
+{
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	mutex_lock(&dac33->mutex);
+	ret = dac33_write(codec, reg, value);
+	mutex_unlock(&dac33->mutex);
+
+	return ret;
+}
+
+#define DAC33_I2C_ADDR_AUTOINC	0x80
+static int dac33_write16(struct snd_soc_codec *codec, unsigned int reg,
+		       unsigned int value)
+{
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	u8 data[3];
+	int ret = 0;
+
+	/*
+	 * data is
+	 *   D23..D16 dac33 register offset
+	 *   D15..D8  register data MSB
+	 *   D7...D0  register data LSB
+	 */
+	data[0] = reg & 0xff;
+	data[1] = (value >> 8) & 0xff;
+	data[2] = value & 0xff;
+
+	dac33_write_reg_cache(codec, data[0], data[1]);
+	dac33_write_reg_cache(codec, data[0] + 1, data[2]);
+
+	if (dac33->chip_power) {
+		/* We need to set autoincrement mode for 16 bit writes */
+		data[0] |= DAC33_I2C_ADDR_AUTOINC;
+		ret = codec->hw_write(codec->control_data, data, 3);
+		if (ret != 3)
+			dev_err(codec->dev, "Write failed (%d)\n", ret);
+		else
+			ret = 0;
+	}
+
+	return ret;
+}
+
+static void dac33_init_chip(struct snd_soc_codec *codec)
+{
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+	if (unlikely(!dac33->chip_power))
+		return;
+
+	/* 44-46: DAC Control Registers */
+	/* A : DAC sample rate Fsref/1.5 */
+	dac33_write(codec, DAC33_DAC_CTRL_A, DAC33_DACRATE(0));
+	/* B : DAC src=normal, not muted */
+	dac33_write(codec, DAC33_DAC_CTRL_B, DAC33_DACSRCR_RIGHT |
+					     DAC33_DACSRCL_LEFT);
+	/* C : (defaults) */
+	dac33_write(codec, DAC33_DAC_CTRL_C, 0x00);
+
+	/* 73 : volume soft stepping control,
+	 clock source = internal osc (?) */
+	dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN);
+
+	dac33_write(codec, DAC33_PWR_CTRL, DAC33_PDNALLB);
+
+	/* Restore only selected registers (gains mostly) */
+	dac33_write(codec, DAC33_LDAC_DIG_VOL_CTRL,
+		    dac33_read_reg_cache(codec, DAC33_LDAC_DIG_VOL_CTRL));
+	dac33_write(codec, DAC33_RDAC_DIG_VOL_CTRL,
+		    dac33_read_reg_cache(codec, DAC33_RDAC_DIG_VOL_CTRL));
+
+	dac33_write(codec, DAC33_LINEL_TO_LLO_VOL,
+		    dac33_read_reg_cache(codec, DAC33_LINEL_TO_LLO_VOL));
+	dac33_write(codec, DAC33_LINER_TO_RLO_VOL,
+		    dac33_read_reg_cache(codec, DAC33_LINER_TO_RLO_VOL));
+}
+
+static inline int dac33_read_id(struct snd_soc_codec *codec)
+{
+	int i, ret = 0;
+	u8 reg;
+
+	for (i = 0; i < 3; i++) {
+		ret = dac33_read(codec, DAC33_DEVICE_ID_MSB + i, &reg);
+		if (ret < 0)
+			break;
+	}
+
+	return ret;
+}
+
+static inline void dac33_soft_power(struct snd_soc_codec *codec, int power)
+{
+	u8 reg;
+
+	reg = dac33_read_reg_cache(codec, DAC33_PWR_CTRL);
+	if (power)
+		reg |= DAC33_PDNALLB;
+	else
+		reg &= ~(DAC33_PDNALLB | DAC33_OSCPDNB |
+			 DAC33_DACRPDNB | DAC33_DACLPDNB);
+	dac33_write(codec, DAC33_PWR_CTRL, reg);
+}
+
+static int dac33_hard_power(struct snd_soc_codec *codec, int power)
+{
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	mutex_lock(&dac33->mutex);
+
+	/* Safety check */
+	if (unlikely(power == dac33->chip_power)) {
+		dev_dbg(codec->dev, "Trying to set the same power state: %s\n",
+			power ? "ON" : "OFF");
+		goto exit;
+	}
+
+	if (power) {
+		ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies),
+					  dac33->supplies);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to enable supplies: %d\n", ret);
+				goto exit;
+		}
+
+		if (dac33->power_gpio >= 0)
+			gpio_set_value(dac33->power_gpio, 1);
+
+		dac33->chip_power = 1;
+	} else {
+		dac33_soft_power(codec, 0);
+		if (dac33->power_gpio >= 0)
+			gpio_set_value(dac33->power_gpio, 0);
+
+		ret = regulator_bulk_disable(ARRAY_SIZE(dac33->supplies),
+					     dac33->supplies);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to disable supplies: %d\n", ret);
+			goto exit;
+		}
+
+		dac33->chip_power = 0;
+	}
+
+exit:
+	mutex_unlock(&dac33->mutex);
+	return ret;
+}
+
+static int playback_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(w->codec);
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		if (likely(dac33->substream)) {
+			dac33_calculate_times(dac33->substream);
+			dac33_prepare_chip(dac33->substream);
+		}
+		break;
+	}
+	return 0;
+}
+
+static int dac33_get_nsample(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+	ucontrol->value.integer.value[0] = dac33->nsample;
+
+	return 0;
+}
+
+static int dac33_set_nsample(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	if (dac33->nsample == ucontrol->value.integer.value[0])
+		return 0;
+
+	if (ucontrol->value.integer.value[0] < dac33->nsample_min ||
+	    ucontrol->value.integer.value[0] > dac33->nsample_max) {
+		ret = -EINVAL;
+	} else {
+		dac33->nsample = ucontrol->value.integer.value[0];
+		/* Re calculate the burst time */
+		dac33->mode1_us_burst = SAMPLES_TO_US(dac33->burst_rate,
+						      dac33->nsample);
+	}
+
+	return ret;
+}
+
+static int dac33_get_uthr(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+	ucontrol->value.integer.value[0] = dac33->uthr;
+
+	return 0;
+}
+
+static int dac33_set_uthr(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	if (dac33->substream)
+		return -EBUSY;
+
+	if (dac33->uthr == ucontrol->value.integer.value[0])
+		return 0;
+
+	if (ucontrol->value.integer.value[0] < (MODE7_LTHR + 10) ||
+	    ucontrol->value.integer.value[0] > MODE7_UTHR)
+		ret = -EINVAL;
+	else
+		dac33->uthr = ucontrol->value.integer.value[0];
+
+	return ret;
+}
+
+static int dac33_get_fifo_mode(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+	ucontrol->value.integer.value[0] = dac33->fifo_mode;
+
+	return 0;
+}
+
+static int dac33_set_fifo_mode(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	if (dac33->fifo_mode == ucontrol->value.integer.value[0])
+		return 0;
+	/* Do not allow changes while stream is running*/
+	if (codec->active)
+		return -EPERM;
+
+	if (ucontrol->value.integer.value[0] < 0 ||
+	    ucontrol->value.integer.value[0] >= DAC33_FIFO_LAST_MODE)
+		ret = -EINVAL;
+	else
+		dac33->fifo_mode = ucontrol->value.integer.value[0];
+
+	return ret;
+}
+
+/* Codec operation modes */
+static const char *dac33_fifo_mode_texts[] = {
+	"Bypass", "Mode 1", "Mode 7"
+};
+
+static const struct soc_enum dac33_fifo_mode_enum =
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(dac33_fifo_mode_texts),
+			    dac33_fifo_mode_texts);
+
+/* L/R Line Output Gain */
+static const char *lr_lineout_gain_texts[] = {
+	"Line -12dB DAC 0dB", "Line -6dB DAC 6dB",
+	"Line 0dB DAC 12dB", "Line 6dB DAC 18dB",
+};
+
+static const struct soc_enum l_lineout_gain_enum =
+	SOC_ENUM_SINGLE(DAC33_LDAC_PWR_CTRL, 0,
+			ARRAY_SIZE(lr_lineout_gain_texts),
+			lr_lineout_gain_texts);
+
+static const struct soc_enum r_lineout_gain_enum =
+	SOC_ENUM_SINGLE(DAC33_RDAC_PWR_CTRL, 0,
+			ARRAY_SIZE(lr_lineout_gain_texts),
+			lr_lineout_gain_texts);
+
+/*
+ * DACL/R digital volume control:
+ * from 0 dB to -63.5 in 0.5 dB steps
+ * Need to be inverted later on:
+ * 0x00 == 0 dB
+ * 0x7f == -63.5 dB
+ */
+static DECLARE_TLV_DB_SCALE(dac_digivol_tlv, -6350, 50, 0);
+
+static const struct snd_kcontrol_new dac33_snd_controls[] = {
+	SOC_DOUBLE_R_TLV("DAC Digital Playback Volume",
+		DAC33_LDAC_DIG_VOL_CTRL, DAC33_RDAC_DIG_VOL_CTRL,
+		0, 0x7f, 1, dac_digivol_tlv),
+	SOC_DOUBLE_R("DAC Digital Playback Switch",
+		 DAC33_LDAC_DIG_VOL_CTRL, DAC33_RDAC_DIG_VOL_CTRL, 7, 1, 1),
+	SOC_DOUBLE_R("Line to Line Out Volume",
+		 DAC33_LINEL_TO_LLO_VOL, DAC33_LINER_TO_RLO_VOL, 0, 127, 1),
+	SOC_ENUM("Left Line Output Gain", l_lineout_gain_enum),
+	SOC_ENUM("Right Line Output Gain", r_lineout_gain_enum),
+};
+
+static const struct snd_kcontrol_new dac33_mode_snd_controls[] = {
+	SOC_ENUM_EXT("FIFO Mode", dac33_fifo_mode_enum,
+		 dac33_get_fifo_mode, dac33_set_fifo_mode),
+};
+
+static const struct snd_kcontrol_new dac33_fifo_snd_controls[] = {
+	SOC_SINGLE_EXT("nSample", 0, 0, 5900, 0,
+		dac33_get_nsample, dac33_set_nsample),
+	SOC_SINGLE_EXT("UTHR", 0, 0, MODE7_UTHR, 0,
+		 dac33_get_uthr, dac33_set_uthr),
+};
+
+/* Analog bypass */
+static const struct snd_kcontrol_new dac33_dapm_abypassl_control =
+	SOC_DAPM_SINGLE("Switch", DAC33_LINEL_TO_LLO_VOL, 7, 1, 1);
+
+static const struct snd_kcontrol_new dac33_dapm_abypassr_control =
+	SOC_DAPM_SINGLE("Switch", DAC33_LINER_TO_RLO_VOL, 7, 1, 1);
+
+static const struct snd_soc_dapm_widget dac33_dapm_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("LEFT_LO"),
+	SND_SOC_DAPM_OUTPUT("RIGHT_LO"),
+
+	SND_SOC_DAPM_INPUT("LINEL"),
+	SND_SOC_DAPM_INPUT("LINER"),
+
+	SND_SOC_DAPM_DAC("DACL", "Left Playback", DAC33_LDAC_PWR_CTRL, 2, 0),
+	SND_SOC_DAPM_DAC("DACR", "Right Playback", DAC33_RDAC_PWR_CTRL, 2, 0),
+
+	/* Analog bypass */
+	SND_SOC_DAPM_SWITCH("Analog Left Bypass", SND_SOC_NOPM, 0, 0,
+				&dac33_dapm_abypassl_control),
+	SND_SOC_DAPM_SWITCH("Analog Right Bypass", SND_SOC_NOPM, 0, 0,
+				&dac33_dapm_abypassr_control),
+
+	SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Left Amp Power",
+			 DAC33_OUT_AMP_PWR_CTRL, 6, 3, 3, 0),
+	SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Right Amp Power",
+			 DAC33_OUT_AMP_PWR_CTRL, 4, 3, 3, 0),
+
+	SND_SOC_DAPM_PRE("Prepare Playback", playback_event),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Analog bypass */
+	{"Analog Left Bypass", "Switch", "LINEL"},
+	{"Analog Right Bypass", "Switch", "LINER"},
+
+	{"Output Left Amp Power", NULL, "DACL"},
+	{"Output Right Amp Power", NULL, "DACR"},
+
+	{"Output Left Amp Power", NULL, "Analog Left Bypass"},
+	{"Output Right Amp Power", NULL, "Analog Right Bypass"},
+
+	/* output */
+	{"LEFT_LO", NULL, "Output Left Amp Power"},
+	{"RIGHT_LO", NULL, "Output Right Amp Power"},
+};
+
+static int dac33_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, dac33_dapm_widgets,
+				  ARRAY_SIZE(dac33_dapm_widgets));
+
+	/* set up audio path interconnects */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+static int dac33_set_bias_level(struct snd_soc_codec *codec,
+				enum snd_soc_bias_level level)
+{
+	int ret;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		dac33_soft_power(codec, 1);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Coming from OFF, switch on the codec */
+			ret = dac33_hard_power(codec, 1);
+			if (ret != 0)
+				return ret;
+
+			dac33_init_chip(codec);
+		}
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* Do not power off, when the codec is already off */
+		if (codec->bias_level == SND_SOC_BIAS_OFF)
+			return 0;
+		ret = dac33_hard_power(codec, 0);
+		if (ret != 0)
+			return ret;
+		break;
+	}
+	codec->bias_level = level;
+
+	return 0;
+}
+
+static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33)
+{
+	struct snd_soc_codec *codec = dac33->codec;
+	unsigned int delay;
+
+	switch (dac33->fifo_mode) {
+	case DAC33_FIFO_MODE1:
+		dac33_write16(codec, DAC33_NSAMPLE_MSB,
+			DAC33_THRREG(dac33->nsample));
+
+		/* Take the timestamps */
+		spin_lock_irq(&dac33->lock);
+		dac33->t_stamp2 = ktime_to_us(ktime_get());
+		dac33->t_stamp1 = dac33->t_stamp2;
+		spin_unlock_irq(&dac33->lock);
+
+		dac33_write16(codec, DAC33_PREFILL_MSB,
+				DAC33_THRREG(dac33->alarm_threshold));
+		/* Enable Alarm Threshold IRQ with a delay */
+		delay = SAMPLES_TO_US(dac33->burst_rate,
+				     dac33->alarm_threshold) + 1000;
+		usleep_range(delay, delay + 500);
+		dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MAT);
+		break;
+	case DAC33_FIFO_MODE7:
+		/* Take the timestamp */
+		spin_lock_irq(&dac33->lock);
+		dac33->t_stamp1 = ktime_to_us(ktime_get());
+		/* Move back the timestamp with drain time */
+		dac33->t_stamp1 -= dac33->mode7_us_to_lthr;
+		spin_unlock_irq(&dac33->lock);
+
+		dac33_write16(codec, DAC33_PREFILL_MSB,
+				DAC33_THRREG(MODE7_LTHR));
+
+		/* Enable Upper Threshold IRQ */
+		dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MUT);
+		break;
+	default:
+		dev_warn(codec->dev, "Unhandled FIFO mode: %d\n",
+							dac33->fifo_mode);
+		break;
+	}
+}
+
+static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33)
+{
+	struct snd_soc_codec *codec = dac33->codec;
+
+	switch (dac33->fifo_mode) {
+	case DAC33_FIFO_MODE1:
+		/* Take the timestamp */
+		spin_lock_irq(&dac33->lock);
+		dac33->t_stamp2 = ktime_to_us(ktime_get());
+		spin_unlock_irq(&dac33->lock);
+
+		dac33_write16(codec, DAC33_NSAMPLE_MSB,
+				DAC33_THRREG(dac33->nsample));
+		break;
+	case DAC33_FIFO_MODE7:
+		/* At the moment we are not using interrupts in mode7 */
+		break;
+	default:
+		dev_warn(codec->dev, "Unhandled FIFO mode: %d\n",
+							dac33->fifo_mode);
+		break;
+	}
+}
+
+static void dac33_work(struct work_struct *work)
+{
+	struct snd_soc_codec *codec;
+	struct tlv320dac33_priv *dac33;
+	u8 reg;
+
+	dac33 = container_of(work, struct tlv320dac33_priv, work);
+	codec = dac33->codec;
+
+	mutex_lock(&dac33->mutex);
+	switch (dac33->state) {
+	case DAC33_PREFILL:
+		dac33->state = DAC33_PLAYBACK;
+		dac33_prefill_handler(dac33);
+		break;
+	case DAC33_PLAYBACK:
+		dac33_playback_handler(dac33);
+		break;
+	case DAC33_IDLE:
+		break;
+	case DAC33_FLUSH:
+		dac33->state = DAC33_IDLE;
+		/* Mask all interrupts from dac33 */
+		dac33_write(codec, DAC33_FIFO_IRQ_MASK, 0);
+
+		/* flush fifo */
+		reg = dac33_read_reg_cache(codec, DAC33_FIFO_CTRL_A);
+		reg |= DAC33_FIFOFLUSH;
+		dac33_write(codec, DAC33_FIFO_CTRL_A, reg);
+		break;
+	}
+	mutex_unlock(&dac33->mutex);
+}
+
+static irqreturn_t dac33_interrupt_handler(int irq, void *dev)
+{
+	struct snd_soc_codec *codec = dev;
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+	spin_lock(&dac33->lock);
+	dac33->t_stamp1 = ktime_to_us(ktime_get());
+	spin_unlock(&dac33->lock);
+
+	/* Do not schedule the workqueue in Mode7 */
+	if (dac33->fifo_mode != DAC33_FIFO_MODE7)
+		queue_work(dac33->dac33_wq, &dac33->work);
+
+	return IRQ_HANDLED;
+}
+
+static void dac33_oscwait(struct snd_soc_codec *codec)
+{
+	int timeout = 60;
+	u8 reg;
+
+	do {
+		usleep_range(1000, 2000);
+		dac33_read(codec, DAC33_INT_OSC_STATUS, &reg);
+	} while (((reg & 0x03) != DAC33_OSCSTATUS_NORMAL) && timeout--);
+	if ((reg & 0x03) != DAC33_OSCSTATUS_NORMAL)
+		dev_err(codec->dev,
+			"internal oscillator calibration failed\n");
+}
+
+static int dac33_startup(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+	/* Stream started, save the substream pointer */
+	dac33->substream = substream;
+
+	return 0;
+}
+
+static void dac33_shutdown(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+	dac33->substream = NULL;
+
+	/* Reset the nSample restrictions */
+	dac33->nsample_min = 0;
+	dac33->nsample_max = NSAMPLE_MAX;
+}
+
+static int dac33_hw_params(struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *params,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	/* Check parameters for validity */
+	switch (params_rate(params)) {
+	case 44100:
+	case 48000:
+		break;
+	default:
+		dev_err(codec->dev, "unsupported rate %d\n",
+			params_rate(params));
+		return -EINVAL;
+	}
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	default:
+		dev_err(codec->dev, "unsupported format %d\n",
+			params_format(params));
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+#define CALC_OSCSET(rate, refclk) ( \
+	((((rate * 10000) / refclk) * 4096) + 7000) / 10000)
+#define CALC_RATIOSET(rate, refclk) ( \
+	((((refclk  * 100000) / rate) * 16384) + 50000) / 100000)
+
+/*
+ * tlv320dac33 is strict on the sequence of the register writes, if the register
+ * writes happens in different order, than dac33 might end up in unknown state.
+ * Use the known, working sequence of register writes to initialize the dac33.
+ */
+static int dac33_prepare_chip(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	unsigned int oscset, ratioset, pwr_ctrl, reg_tmp;
+	u8 aictrl_a, aictrl_b, fifoctrl_a;
+
+	switch (substream->runtime->rate) {
+	case 44100:
+	case 48000:
+		oscset = CALC_OSCSET(substream->runtime->rate, dac33->refclk);
+		ratioset = CALC_RATIOSET(substream->runtime->rate,
+					 dac33->refclk);
+		break;
+	default:
+		dev_err(codec->dev, "unsupported rate %d\n",
+			substream->runtime->rate);
+		return -EINVAL;
+	}
+
+
+	aictrl_a = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_A);
+	aictrl_a &= ~(DAC33_NCYCL_MASK | DAC33_WLEN_MASK);
+	/* Read FIFO control A, and clear FIFO flush bit */
+	fifoctrl_a = dac33_read_reg_cache(codec, DAC33_FIFO_CTRL_A);
+	fifoctrl_a &= ~DAC33_FIFOFLUSH;
+
+	fifoctrl_a &= ~DAC33_WIDTH;
+	switch (substream->runtime->format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		aictrl_a |= (DAC33_NCYCL_16 | DAC33_WLEN_16);
+		fifoctrl_a |= DAC33_WIDTH;
+		break;
+	default:
+		dev_err(codec->dev, "unsupported format %d\n",
+			substream->runtime->format);
+		return -EINVAL;
+	}
+
+	mutex_lock(&dac33->mutex);
+
+	if (!dac33->chip_power) {
+		/*
+		 * Chip is not powered yet.
+		 * Do the init in the dac33_set_bias_level later.
+		 */
+		mutex_unlock(&dac33->mutex);
+		return 0;
+	}
+
+	dac33_soft_power(codec, 0);
+	dac33_soft_power(codec, 1);
+
+	reg_tmp = dac33_read_reg_cache(codec, DAC33_INT_OSC_CTRL);
+	dac33_write(codec, DAC33_INT_OSC_CTRL, reg_tmp);
+
+	/* Write registers 0x08 and 0x09 (MSB, LSB) */
+	dac33_write16(codec, DAC33_INT_OSC_FREQ_RAT_A, oscset);
+
+	/* calib time: 128 is a nice number ;) */
+	dac33_write(codec, DAC33_CALIB_TIME, 128);
+
+	/* adjustment treshold & step */
+	dac33_write(codec, DAC33_INT_OSC_CTRL_B, DAC33_ADJTHRSHLD(2) |
+						 DAC33_ADJSTEP(1));
+
+	/* div=4 / gain=1 / div */
+	dac33_write(codec, DAC33_INT_OSC_CTRL_C, DAC33_REFDIV(4));
+
+	pwr_ctrl = dac33_read_reg_cache(codec, DAC33_PWR_CTRL);
+	pwr_ctrl |= DAC33_OSCPDNB | DAC33_DACRPDNB | DAC33_DACLPDNB;
+	dac33_write(codec, DAC33_PWR_CTRL, pwr_ctrl);
+
+	dac33_oscwait(codec);
+
+	if (dac33->fifo_mode) {
+		/* Generic for all FIFO modes */
+		/* 50-51 : ASRC Control registers */
+		dac33_write(codec, DAC33_ASRC_CTRL_A, DAC33_SRCLKDIV(1));
+		dac33_write(codec, DAC33_ASRC_CTRL_B, 1); /* ??? */
+
+		/* Write registers 0x34 and 0x35 (MSB, LSB) */
+		dac33_write16(codec, DAC33_SRC_REF_CLK_RATIO_A, ratioset);
+
+		/* Set interrupts to high active */
+		dac33_write(codec, DAC33_INTP_CTRL_A, DAC33_INTPM_AHIGH);
+	} else {
+		/* FIFO bypass mode */
+		/* 50-51 : ASRC Control registers */
+		dac33_write(codec, DAC33_ASRC_CTRL_A, DAC33_SRCBYP);
+		dac33_write(codec, DAC33_ASRC_CTRL_B, 0); /* ??? */
+	}
+
+	/* Interrupt behaviour configuration */
+	switch (dac33->fifo_mode) {
+	case DAC33_FIFO_MODE1:
+		dac33_write(codec, DAC33_FIFO_IRQ_MODE_B,
+			    DAC33_ATM(DAC33_FIFO_IRQ_MODE_LEVEL));
+		break;
+	case DAC33_FIFO_MODE7:
+		dac33_write(codec, DAC33_FIFO_IRQ_MODE_A,
+			DAC33_UTM(DAC33_FIFO_IRQ_MODE_LEVEL));
+		break;
+	default:
+		/* in FIFO bypass mode, the interrupts are not used */
+		break;
+	}
+
+	aictrl_b = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B);
+
+	switch (dac33->fifo_mode) {
+	case DAC33_FIFO_MODE1:
+		/*
+		 * For mode1:
+		 * Disable the FIFO bypass (Enable the use of FIFO)
+		 * Select nSample mode
+		 * BCLK is only running when data is needed by DAC33
+		 */
+		fifoctrl_a &= ~DAC33_FBYPAS;
+		fifoctrl_a &= ~DAC33_FAUTO;
+		if (dac33->keep_bclk)
+			aictrl_b |= DAC33_BCLKON;
+		else
+			aictrl_b &= ~DAC33_BCLKON;
+		break;
+	case DAC33_FIFO_MODE7:
+		/*
+		 * For mode1:
+		 * Disable the FIFO bypass (Enable the use of FIFO)
+		 * Select Threshold mode
+		 * BCLK is only running when data is needed by DAC33
+		 */
+		fifoctrl_a &= ~DAC33_FBYPAS;
+		fifoctrl_a |= DAC33_FAUTO;
+		if (dac33->keep_bclk)
+			aictrl_b |= DAC33_BCLKON;
+		else
+			aictrl_b &= ~DAC33_BCLKON;
+		break;
+	default:
+		/*
+		 * For FIFO bypass mode:
+		 * Enable the FIFO bypass (Disable the FIFO use)
+		 * Set the BCLK as continous
+		 */
+		fifoctrl_a |= DAC33_FBYPAS;
+		aictrl_b |= DAC33_BCLKON;
+		break;
+	}
+
+	dac33_write(codec, DAC33_FIFO_CTRL_A, fifoctrl_a);
+	dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_A, aictrl_a);
+	dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_B, aictrl_b);
+
+	/*
+	 * BCLK divide ratio
+	 * 0: 1.5
+	 * 1: 1
+	 * 2: 2
+	 * ...
+	 * 254: 254
+	 * 255: 255
+	 */
+	if (dac33->fifo_mode)
+		dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C,
+							dac33->burst_bclkdiv);
+	else
+		dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_C, 32);
+
+	switch (dac33->fifo_mode) {
+	case DAC33_FIFO_MODE1:
+		dac33_write16(codec, DAC33_ATHR_MSB,
+			      DAC33_THRREG(dac33->alarm_threshold));
+		break;
+	case DAC33_FIFO_MODE7:
+		/*
+		 * Configure the threshold levels, and leave 10 sample space
+		 * at the bottom, and also at the top of the FIFO
+		 */
+		dac33_write16(codec, DAC33_UTHR_MSB, DAC33_THRREG(dac33->uthr));
+		dac33_write16(codec, DAC33_LTHR_MSB, DAC33_THRREG(MODE7_LTHR));
+		break;
+	default:
+		break;
+	}
+
+	mutex_unlock(&dac33->mutex);
+
+	return 0;
+}
+
+static void dac33_calculate_times(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	unsigned int period_size = substream->runtime->period_size;
+	unsigned int rate = substream->runtime->rate;
+	unsigned int nsample_limit;
+
+	/* In bypass mode we don't need to calculate */
+	if (!dac33->fifo_mode)
+		return;
+
+	switch (dac33->fifo_mode) {
+	case DAC33_FIFO_MODE1:
+		/* Number of samples under i2c latency */
+		dac33->alarm_threshold = US_TO_SAMPLES(rate,
+						dac33->mode1_latency);
+		nsample_limit = DAC33_BUFFER_SIZE_SAMPLES -
+				dac33->alarm_threshold;
+
+		if (dac33->auto_fifo_config) {
+			if (period_size <= dac33->alarm_threshold)
+				/*
+				 * Configure nSamaple to number of periods,
+				 * which covers the latency requironment.
+				 */
+				dac33->nsample = period_size *
+				       ((dac33->alarm_threshold / period_size) +
+				       (dac33->alarm_threshold % period_size ?
+				       1 : 0));
+			else if (period_size > nsample_limit)
+				dac33->nsample = nsample_limit;
+			else
+				dac33->nsample = period_size;
+		} else {
+			/* nSample time shall not be shorter than i2c latency */
+			dac33->nsample_min = dac33->alarm_threshold;
+			/*
+			 * nSample should not be bigger than alsa buffer minus
+			 * size of one period to avoid overruns
+			 */
+			dac33->nsample_max = substream->runtime->buffer_size -
+						period_size;
+
+			if (dac33->nsample_max > nsample_limit)
+				dac33->nsample_max = nsample_limit;
+
+			/* Correct the nSample if it is outside of the ranges */
+			if (dac33->nsample < dac33->nsample_min)
+				dac33->nsample = dac33->nsample_min;
+			if (dac33->nsample > dac33->nsample_max)
+				dac33->nsample = dac33->nsample_max;
+		}
+
+		dac33->mode1_us_burst = SAMPLES_TO_US(dac33->burst_rate,
+						      dac33->nsample);
+		dac33->t_stamp1 = 0;
+		dac33->t_stamp2 = 0;
+		break;
+	case DAC33_FIFO_MODE7:
+		if (dac33->auto_fifo_config) {
+			dac33->uthr = UTHR_FROM_PERIOD_SIZE(
+					period_size,
+					rate,
+					dac33->burst_rate) + 9;
+			if (dac33->uthr > MODE7_UTHR)
+				dac33->uthr = MODE7_UTHR;
+			if (dac33->uthr < (MODE7_LTHR + 10))
+				dac33->uthr = (MODE7_LTHR + 10);
+		}
+		dac33->mode7_us_to_lthr =
+				SAMPLES_TO_US(substream->runtime->rate,
+					dac33->uthr - MODE7_LTHR + 1);
+		dac33->t_stamp1 = 0;
+		break;
+	default:
+		break;
+	}
+
+}
+
+static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (dac33->fifo_mode) {
+			dac33->state = DAC33_PREFILL;
+			queue_work(dac33->dac33_wq, &dac33->work);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (dac33->fifo_mode) {
+			dac33->state = DAC33_FLUSH;
+			queue_work(dac33->dac33_wq, &dac33->work);
+		}
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static snd_pcm_sframes_t dac33_dai_delay(
+			struct snd_pcm_substream *substream,
+			struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	unsigned long long t0, t1, t_now;
+	unsigned int time_delta, uthr;
+	int samples_out, samples_in, samples;
+	snd_pcm_sframes_t delay = 0;
+
+	switch (dac33->fifo_mode) {
+	case DAC33_FIFO_BYPASS:
+		break;
+	case DAC33_FIFO_MODE1:
+		spin_lock(&dac33->lock);
+		t0 = dac33->t_stamp1;
+		t1 = dac33->t_stamp2;
+		spin_unlock(&dac33->lock);
+		t_now = ktime_to_us(ktime_get());
+
+		/* We have not started to fill the FIFO yet, delay is 0 */
+		if (!t1)
+			goto out;
+
+		if (t0 > t1) {
+			/*
+			 * Phase 1:
+			 * After Alarm threshold, and before nSample write
+			 */
+			time_delta = t_now - t0;
+			samples_out = time_delta ? US_TO_SAMPLES(
+						substream->runtime->rate,
+						time_delta) : 0;
+
+			if (likely(dac33->alarm_threshold > samples_out))
+				delay = dac33->alarm_threshold - samples_out;
+			else
+				delay = 0;
+		} else if ((t_now - t1) <= dac33->mode1_us_burst) {
+			/*
+			 * Phase 2:
+			 * After nSample write (during burst operation)
+			 */
+			time_delta = t_now - t0;
+			samples_out = time_delta ? US_TO_SAMPLES(
+						substream->runtime->rate,
+						time_delta) : 0;
+
+			time_delta = t_now - t1;
+			samples_in = time_delta ? US_TO_SAMPLES(
+						dac33->burst_rate,
+						time_delta) : 0;
+
+			samples = dac33->alarm_threshold;
+			samples += (samples_in - samples_out);
+
+			if (likely(samples > 0))
+				delay = samples;
+			else
+				delay = 0;
+		} else {
+			/*
+			 * Phase 3:
+			 * After burst operation, before next alarm threshold
+			 */
+			time_delta = t_now - t0;
+			samples_out = time_delta ? US_TO_SAMPLES(
+						substream->runtime->rate,
+						time_delta) : 0;
+
+			samples_in = dac33->nsample;
+			samples = dac33->alarm_threshold;
+			samples += (samples_in - samples_out);
+
+			if (likely(samples > 0))
+				delay = samples > DAC33_BUFFER_SIZE_SAMPLES ?
+					DAC33_BUFFER_SIZE_SAMPLES : samples;
+			else
+				delay = 0;
+		}
+		break;
+	case DAC33_FIFO_MODE7:
+		spin_lock(&dac33->lock);
+		t0 = dac33->t_stamp1;
+		uthr = dac33->uthr;
+		spin_unlock(&dac33->lock);
+		t_now = ktime_to_us(ktime_get());
+
+		/* We have not started to fill the FIFO yet, delay is 0 */
+		if (!t0)
+			goto out;
+
+		if (t_now <= t0) {
+			/*
+			 * Either the timestamps are messed or equal. Report
+			 * maximum delay
+			 */
+			delay = uthr;
+			goto out;
+		}
+
+		time_delta = t_now - t0;
+		if (time_delta <= dac33->mode7_us_to_lthr) {
+			/*
+			* Phase 1:
+			* After burst (draining phase)
+			*/
+			samples_out = US_TO_SAMPLES(
+					substream->runtime->rate,
+					time_delta);
+
+			if (likely(uthr > samples_out))
+				delay = uthr - samples_out;
+			else
+				delay = 0;
+		} else {
+			/*
+			* Phase 2:
+			* During burst operation
+			*/
+			time_delta = time_delta - dac33->mode7_us_to_lthr;
+
+			samples_out = US_TO_SAMPLES(
+					substream->runtime->rate,
+					time_delta);
+			samples_in = US_TO_SAMPLES(
+					dac33->burst_rate,
+					time_delta);
+			delay = MODE7_LTHR + samples_in - samples_out;
+
+			if (unlikely(delay > uthr))
+				delay = uthr;
+		}
+		break;
+	default:
+		dev_warn(codec->dev, "Unhandled FIFO mode: %d\n",
+							dac33->fifo_mode);
+		break;
+	}
+out:
+	return delay;
+}
+
+static int dac33_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	u8 ioc_reg, asrcb_reg;
+
+	ioc_reg = dac33_read_reg_cache(codec, DAC33_INT_OSC_CTRL);
+	asrcb_reg = dac33_read_reg_cache(codec, DAC33_ASRC_CTRL_B);
+	switch (clk_id) {
+	case TLV320DAC33_MCLK:
+		ioc_reg |= DAC33_REFSEL;
+		asrcb_reg |= DAC33_SRCREFSEL;
+		break;
+	case TLV320DAC33_SLEEPCLK:
+		ioc_reg &= ~DAC33_REFSEL;
+		asrcb_reg &= ~DAC33_SRCREFSEL;
+		break;
+	default:
+		dev_err(codec->dev, "Invalid clock ID (%d)\n", clk_id);
+		break;
+	}
+	dac33->refclk = freq;
+
+	dac33_write_reg_cache(codec, DAC33_INT_OSC_CTRL, ioc_reg);
+	dac33_write_reg_cache(codec, DAC33_ASRC_CTRL_B, asrcb_reg);
+
+	return 0;
+}
+
+static int dac33_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			     unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	u8 aictrl_a, aictrl_b;
+
+	aictrl_a = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_A);
+	aictrl_b = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B);
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		/* Codec Master */
+		aictrl_a |= (DAC33_MSBCLK | DAC33_MSWCLK);
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		/* Codec Slave */
+		if (dac33->fifo_mode) {
+			dev_err(codec->dev, "FIFO mode requires master mode\n");
+			return -EINVAL;
+		} else
+			aictrl_a &= ~(DAC33_MSBCLK | DAC33_MSWCLK);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	aictrl_a &= ~DAC33_AFMT_MASK;
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		aictrl_a |= DAC33_AFMT_I2S;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		aictrl_a |= DAC33_AFMT_DSP;
+		aictrl_b &= ~DAC33_DATA_DELAY_MASK;
+		aictrl_b |= DAC33_DATA_DELAY(0);
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		aictrl_a |= DAC33_AFMT_RIGHT_J;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		aictrl_a |= DAC33_AFMT_LEFT_J;
+		break;
+	default:
+		dev_err(codec->dev, "Unsupported format (%u)\n",
+			fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+		return -EINVAL;
+	}
+
+	dac33_write_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_A, aictrl_a);
+	dac33_write_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B, aictrl_b);
+
+	return 0;
+}
+
+static int dac33_soc_probe(struct snd_soc_codec *codec)
+{
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	codec->control_data = dac33->control_data;
+	codec->hw_write = (hw_write_t) i2c_master_send;
+	codec->idle_bias_off = 1;
+	dac33->codec = codec;
+
+	/* Read the tlv320dac33 ID registers */
+	ret = dac33_hard_power(codec, 1);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to power up codec: %d\n", ret);
+		goto err_power;
+	}
+	ret = dac33_read_id(codec);
+	dac33_hard_power(codec, 0);
+
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to read chip ID: %d\n", ret);
+		ret = -ENODEV;
+		goto err_power;
+	}
+
+	/* Check if the IRQ number is valid and request it */
+	if (dac33->irq >= 0) {
+		ret = request_irq(dac33->irq, dac33_interrupt_handler,
+				  IRQF_TRIGGER_RISING | IRQF_DISABLED,
+				  codec->name, codec);
+		if (ret < 0) {
+			dev_err(codec->dev, "Could not request IRQ%d (%d)\n",
+						dac33->irq, ret);
+			dac33->irq = -1;
+		}
+		if (dac33->irq != -1) {
+			/* Setup work queue */
+			dac33->dac33_wq =
+				create_singlethread_workqueue("tlv320dac33");
+			if (dac33->dac33_wq == NULL) {
+				free_irq(dac33->irq, codec);
+				return -ENOMEM;
+			}
+
+			INIT_WORK(&dac33->work, dac33_work);
+		}
+	}
+
+	snd_soc_add_controls(codec, dac33_snd_controls,
+			     ARRAY_SIZE(dac33_snd_controls));
+	/* Only add the FIFO controls, if we have valid IRQ number */
+	if (dac33->irq >= 0) {
+		snd_soc_add_controls(codec, dac33_mode_snd_controls,
+				     ARRAY_SIZE(dac33_mode_snd_controls));
+		/* FIFO usage controls only, if autoio config is not selected */
+		if (!dac33->auto_fifo_config)
+			snd_soc_add_controls(codec, dac33_fifo_snd_controls,
+					ARRAY_SIZE(dac33_fifo_snd_controls));
+	}
+	dac33_add_widgets(codec);
+
+err_power:
+	return ret;
+}
+
+static int dac33_soc_remove(struct snd_soc_codec *codec)
+{
+	struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+	dac33_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	if (dac33->irq >= 0) {
+		free_irq(dac33->irq, dac33->codec);
+		destroy_workqueue(dac33->dac33_wq);
+	}
+	return 0;
+}
+
+static int dac33_soc_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	dac33_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int dac33_soc_resume(struct snd_soc_codec *codec)
+{
+	dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_tlv320dac33 = {
+	.read = dac33_read_reg_cache,
+	.write = dac33_write_locked,
+	.set_bias_level = dac33_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(dac33_reg),
+	.reg_word_size = sizeof(u8),
+	.reg_cache_default = dac33_reg,
+	.probe = dac33_soc_probe,
+	.remove = dac33_soc_remove,
+	.suspend = dac33_soc_suspend,
+	.resume = dac33_soc_resume,
+};
+
+#define DAC33_RATES	(SNDRV_PCM_RATE_44100 | \
+			 SNDRV_PCM_RATE_48000)
+#define DAC33_FORMATS	SNDRV_PCM_FMTBIT_S16_LE
+
+static struct snd_soc_dai_ops dac33_dai_ops = {
+	.startup	= dac33_startup,
+	.shutdown	= dac33_shutdown,
+	.hw_params	= dac33_hw_params,
+	.trigger	= dac33_pcm_trigger,
+	.delay		= dac33_dai_delay,
+	.set_sysclk	= dac33_set_dai_sysclk,
+	.set_fmt	= dac33_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver dac33_dai = {
+	.name = "tlv320dac33-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = DAC33_RATES,
+		.formats = DAC33_FORMATS,},
+	.ops = &dac33_dai_ops,
+};
+
+static int __devinit dac33_i2c_probe(struct i2c_client *client,
+				     const struct i2c_device_id *id)
+{
+	struct tlv320dac33_platform_data *pdata;
+	struct tlv320dac33_priv *dac33;
+	int ret, i;
+
+	if (client->dev.platform_data == NULL) {
+		dev_err(&client->dev, "Platform data not set\n");
+		return -ENODEV;
+	}
+	pdata = client->dev.platform_data;
+
+	dac33 = kzalloc(sizeof(struct tlv320dac33_priv), GFP_KERNEL);
+	if (dac33 == NULL)
+		return -ENOMEM;
+
+	dac33->control_data = client;
+	mutex_init(&dac33->mutex);
+	spin_lock_init(&dac33->lock);
+
+	i2c_set_clientdata(client, dac33);
+
+	dac33->power_gpio = pdata->power_gpio;
+	dac33->burst_bclkdiv = pdata->burst_bclkdiv;
+	/* Pre calculate the burst rate */
+	dac33->burst_rate = BURST_BASEFREQ_HZ / dac33->burst_bclkdiv / 32;
+	dac33->keep_bclk = pdata->keep_bclk;
+	dac33->auto_fifo_config = pdata->auto_fifo_config;
+	dac33->mode1_latency = pdata->mode1_latency;
+	if (!dac33->mode1_latency)
+		dac33->mode1_latency = 10000; /* 10ms */
+	dac33->irq = client->irq;
+	dac33->nsample = NSAMPLE_MAX;
+	dac33->nsample_max = NSAMPLE_MAX;
+	dac33->uthr = MODE7_UTHR;
+	/* Disable FIFO use by default */
+	dac33->fifo_mode = DAC33_FIFO_BYPASS;
+
+	/* Check if the reset GPIO number is valid and request it */
+	if (dac33->power_gpio >= 0) {
+		ret = gpio_request(dac33->power_gpio, "tlv320dac33 reset");
+		if (ret < 0) {
+			dev_err(&client->dev,
+				"Failed to request reset GPIO (%d)\n",
+				dac33->power_gpio);
+			goto err_gpio;
+		}
+		gpio_direction_output(dac33->power_gpio, 0);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(dac33->supplies); i++)
+		dac33->supplies[i].supply = dac33_supply_names[i];
+
+	ret = regulator_bulk_get(&client->dev, ARRAY_SIZE(dac33->supplies),
+				 dac33->supplies);
+
+	if (ret != 0) {
+		dev_err(&client->dev, "Failed to request supplies: %d\n", ret);
+		goto err_get;
+	}
+
+	ret = snd_soc_register_codec(&client->dev,
+			&soc_codec_dev_tlv320dac33, &dac33_dai, 1);
+	if (ret < 0)
+		goto err_register;
+
+	return ret;
+err_register:
+	regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies);
+err_get:
+	if (dac33->power_gpio >= 0)
+		gpio_free(dac33->power_gpio);
+err_gpio:
+	kfree(dac33);
+	return ret;
+}
+
+static int __devexit dac33_i2c_remove(struct i2c_client *client)
+{
+	struct tlv320dac33_priv *dac33 = i2c_get_clientdata(client);
+
+	if (unlikely(dac33->chip_power))
+		dac33_hard_power(dac33->codec, 0);
+
+	if (dac33->power_gpio >= 0)
+		gpio_free(dac33->power_gpio);
+
+	regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies);
+
+	snd_soc_unregister_codec(&client->dev);
+	kfree(dac33);
+
+	return 0;
+}
+
+static const struct i2c_device_id tlv320dac33_i2c_id[] = {
+	{
+		.name = "tlv320dac33",
+		.driver_data = 0,
+	},
+	{ },
+};
+
+static struct i2c_driver tlv320dac33_i2c_driver = {
+	.driver = {
+		.name = "tlv320dac33-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe		= dac33_i2c_probe,
+	.remove		= __devexit_p(dac33_i2c_remove),
+	.id_table	= tlv320dac33_i2c_id,
+};
+
+static int __init dac33_module_init(void)
+{
+	int r;
+	r = i2c_add_driver(&tlv320dac33_i2c_driver);
+	if (r < 0) {
+		printk(KERN_ERR "DAC33: driver registration failed\n");
+		return r;
+	}
+	return 0;
+}
+module_init(dac33_module_init);
+
+static void __exit dac33_module_exit(void)
+{
+	i2c_del_driver(&tlv320dac33_i2c_driver);
+}
+module_exit(dac33_module_exit);
+
+
+MODULE_DESCRIPTION("ASoC TLV320DAC33 codec driver");
+MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@nokia.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/tlv320dac33.h b/linux/sound/soc/codecs/tlv320dac33.h
new file mode 100644
index 0000000..7c318b5
--- /dev/null
+++ b/linux/sound/soc/codecs/tlv320dac33.h
@@ -0,0 +1,264 @@
+/*
+ * ALSA SoC Texas Instruments TLV320DAC33 codec driver
+ *
+ * Author:	Peter Ujfalusi <peter.ujfalusi@nokia.com>
+ *
+ * Copyright:   (C) 2009 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TLV320DAC33_H
+#define __TLV320DAC33_H
+
+#define DAC33_PAGE_SELECT		0x00
+#define DAC33_PWR_CTRL			0x01
+#define DAC33_PLL_CTRL_A		0x02
+#define DAC33_PLL_CTRL_B		0x03
+#define DAC33_PLL_CTRL_C		0x04
+#define DAC33_PLL_CTRL_D		0x05
+#define DAC33_PLL_CTRL_E		0x06
+#define DAC33_INT_OSC_CTRL		0x07
+#define DAC33_INT_OSC_FREQ_RAT_A	0x08
+#define DAC33_INT_OSC_FREQ_RAT_B	0x09
+#define DAC33_INT_OSC_DAC_RATIO_SET	0x0A
+#define DAC33_CALIB_TIME		0x0B
+#define DAC33_INT_OSC_CTRL_B		0x0C
+#define DAC33_INT_OSC_CTRL_C		0x0D
+#define DAC33_INT_OSC_STATUS		0x0E
+#define DAC33_INT_OSC_DAC_RATIO_READ	0x0F
+#define DAC33_INT_OSC_FREQ_RAT_READ_A	0x10
+#define DAC33_INT_OSC_FREQ_RAT_READ_B	0x11
+#define DAC33_SER_AUDIOIF_CTRL_A	0x12
+#define DAC33_SER_AUDIOIF_CTRL_B	0x13
+#define DAC33_SER_AUDIOIF_CTRL_C	0x14
+#define DAC33_FIFO_CTRL_A		0x15
+#define DAC33_UTHR_MSB			0x16
+#define DAC33_UTHR_LSB			0x17
+#define DAC33_ATHR_MSB			0x18
+#define DAC33_ATHR_LSB			0x19
+#define DAC33_LTHR_MSB			0x1A
+#define DAC33_LTHR_LSB			0x1B
+#define DAC33_PREFILL_MSB		0x1C
+#define DAC33_PREFILL_LSB		0x1D
+#define DAC33_NSAMPLE_MSB		0x1E
+#define DAC33_NSAMPLE_LSB		0x1F
+#define DAC33_FIFO_WPTR_MSB		0x20
+#define DAC33_FIFO_WPTR_LSB		0x21
+#define DAC33_FIFO_RPTR_MSB		0x22
+#define DAC33_FIFO_RPTR_LSB		0x23
+#define DAC33_FIFO_DEPTH_MSB		0x24
+#define DAC33_FIFO_DEPTH_LSB		0x25
+#define DAC33_SAMPLES_REMAINING_MSB	0x26
+#define DAC33_SAMPLES_REMAINING_LSB	0x27
+#define DAC33_FIFO_IRQ_FLAG		0x28
+#define DAC33_FIFO_IRQ_MASK		0x29
+#define DAC33_FIFO_IRQ_MODE_A		0x2A
+#define DAC33_FIFO_IRQ_MODE_B		0x2B
+#define DAC33_DAC_CTRL_A		0x2C
+#define DAC33_DAC_CTRL_B		0x2D
+#define DAC33_DAC_CTRL_C		0x2E
+#define DAC33_LDAC_DIG_VOL_CTRL		0x2F
+#define DAC33_RDAC_DIG_VOL_CTRL		0x30
+#define DAC33_DAC_STATUS_FLAGS		0x31
+#define DAC33_ASRC_CTRL_A		0x32
+#define DAC33_ASRC_CTRL_B		0x33
+#define DAC33_SRC_REF_CLK_RATIO_A	0x34
+#define DAC33_SRC_REF_CLK_RATIO_B	0x35
+#define DAC33_SRC_EST_REF_CLK_RATIO_A	0x36
+#define DAC33_SRC_EST_REF_CLK_RATIO_B	0x37
+#define DAC33_INTP_CTRL_A		0x38
+#define DAC33_INTP_CTRL_B		0x39
+/* Registers 0x3A - 0x3F Reserved */
+#define DAC33_LDAC_PWR_CTRL		0x40
+#define DAC33_RDAC_PWR_CTRL		0x41
+#define DAC33_OUT_AMP_CM_CTRL		0x42
+#define DAC33_OUT_AMP_PWR_CTRL		0x43
+#define DAC33_OUT_AMP_CTRL		0x44
+#define DAC33_LINEL_TO_LLO_VOL		0x45
+/* Registers 0x45 - 0x47 Reserved */
+#define DAC33_LINER_TO_RLO_VOL		0x48
+#define DAC33_ANA_VOL_SOFT_STEP_CTRL	0x49
+#define DAC33_OSC_TRIM			0x4A
+/* Registers 0x4B - 0x7C Reserved */
+#define DAC33_DEVICE_ID_MSB		0x7D
+#define DAC33_DEVICE_ID_LSB		0x7E
+#define DAC33_DEVICE_REV_ID		0x7F
+
+#define DAC33_CACHEREGNUM               128
+
+/* Bit definitions */
+
+/* DAC33_PWR_CTRL (0x01) */
+#define DAC33_DACRPDNB			(0x01 << 0)
+#define DAC33_DACLPDNB			(0x01 << 1)
+#define DAC33_OSCPDNB			(0x01 << 2)
+#define DAC33_PLLPDNB			(0x01 << 3)
+#define DAC33_PDNALLB			(0x01 << 4)
+#define DAC33_SOFT_RESET		(0x01 << 7)
+
+/* DAC33_INT_OSC_CTRL (0x07) */
+#define DAC33_REFSEL			(0x01 << 1)
+
+/* DAC33_INT_OSC_CTRL_B (0x0C) */
+#define DAC33_ADJSTEP(x)		(x << 0)
+#define DAC33_ADJTHRSHLD(x)		(x << 4)
+
+/* DAC33_INT_OSC_CTRL_C (0x0D) */
+#define DAC33_REFDIV(x)			(x << 4)
+
+/* DAC33_INT_OSC_STATUS (0x0E) */
+#define DAC33_OSCSTATUS_IDLE_CALIB	(0x00)
+#define DAC33_OSCSTATUS_NORMAL		(0x01)
+#define DAC33_OSCSTATUS_ADJUSTMENT	(0x03)
+#define DAC33_OSCSTATUS_NOT_USED	(0x02)
+
+/* DAC33_SER_AUDIOIF_CTRL_A (0x12) */
+#define DAC33_MSWCLK			(0x01 << 0)
+#define DAC33_MSBCLK			(0x01 << 1)
+#define DAC33_AFMT_MASK			(0x03 << 2)
+#define DAC33_AFMT_I2S			(0x00 << 2)
+#define DAC33_AFMT_DSP			(0x01 << 2)
+#define DAC33_AFMT_RIGHT_J		(0x02 << 2)
+#define DAC33_AFMT_LEFT_J		(0x03 << 2)
+#define DAC33_WLEN_MASK			(0x03 << 4)
+#define DAC33_WLEN_16			(0x00 << 4)
+#define DAC33_WLEN_20			(0x01 << 4)
+#define DAC33_WLEN_24			(0x02 << 4)
+#define DAC33_WLEN_32			(0x03 << 4)
+#define DAC33_NCYCL_MASK		(0x03 << 6)
+#define DAC33_NCYCL_16			(0x00 << 6)
+#define DAC33_NCYCL_20			(0x01 << 6)
+#define DAC33_NCYCL_24			(0x02 << 6)
+#define DAC33_NCYCL_32			(0x03 << 6)
+
+/* DAC33_SER_AUDIOIF_CTRL_B (0x13) */
+#define DAC33_DATA_DELAY_MASK		(0x03 << 2)
+#define DAC33_DATA_DELAY(x)		(x << 2)
+#define DAC33_BCLKON			(0x01 << 5)
+
+/* DAC33_FIFO_CTRL_A (0x15) */
+#define DAC33_WIDTH				(0x01 << 0)
+#define DAC33_FBYPAS				(0x01 << 1)
+#define DAC33_FAUTO				(0x01 << 2)
+#define DAC33_FIFOFLUSH			(0x01 << 3)
+
+/*
+ * UTHR, ATHR, LTHR, PREFILL, NSAMPLE (0x16 - 0x1F)
+ * 13-bit values
+*/
+#define DAC33_THRREG(x)			(((x) & 0x1FFF) << 3)
+
+/* DAC33_FIFO_IRQ_MASK (0x29) */
+#define DAC33_MNS			(0x01 << 0)
+#define DAC33_MPS			(0x01 << 1)
+#define DAC33_MAT			(0x01 << 2)
+#define DAC33_MLT			(0x01 << 3)
+#define DAC33_MUT			(0x01 << 4)
+#define DAC33_MUF			(0x01 << 5)
+#define DAC33_MOF			(0x01 << 6)
+
+#define DAC33_FIFO_IRQ_MODE_MASK	(0x03)
+#define DAC33_FIFO_IRQ_MODE_RISING	(0x00)
+#define DAC33_FIFO_IRQ_MODE_FALLING	(0x01)
+#define DAC33_FIFO_IRQ_MODE_LEVEL	(0x02)
+#define DAC33_FIFO_IRQ_MODE_EDGE	(0x03)
+
+/* DAC33_FIFO_IRQ_MODE_A (0x2A) */
+#define DAC33_UTM(x)			(x << 0)
+#define DAC33_UFM(x)			(x << 2)
+#define DAC33_OFM(x)			(x << 4)
+
+/* DAC33_FIFO_IRQ_MODE_B (0x2B) */
+#define DAC33_NSM(x)			(x << 0)
+#define DAC33_PSM(x)			(x << 2)
+#define DAC33_ATM(x)			(x << 4)
+#define DAC33_LTM(x)			(x << 6)
+
+/* DAC33_DAC_CTRL_A (0x2C) */
+#define DAC33_DACRATE(x)		(x << 0)
+#define DAC33_DACDUAL			(0x01 << 4)
+#define DAC33_DACLKSEL_MASK		(0x03 << 5)
+#define DAC33_DACLKSEL_INTSOC		(0x00 << 5)
+#define DAC33_DACLKSEL_PLL		(0x01 << 5)
+#define DAC33_DACLKSEL_MCLK		(0x02 << 5)
+#define DAC33_DACLKSEL_BCLK		(0x03 << 5)
+
+/* DAC33_DAC_CTRL_B (0x2D) */
+#define DAC33_DACSRCR_MASK		(0x03 << 0)
+#define DAC33_DACSRCR_MUTE		(0x00 << 0)
+#define DAC33_DACSRCR_RIGHT		(0x01 << 0)
+#define DAC33_DACSRCR_LEFT		(0x02 << 0)
+#define DAC33_DACSRCR_MONOMIX		(0x03 << 0)
+#define DAC33_DACSRCL_MASK		(0x03 << 2)
+#define DAC33_DACSRCL_MUTE		(0x00 << 2)
+#define DAC33_DACSRCL_LEFT		(0x01 << 2)
+#define DAC33_DACSRCL_RIGHT		(0x02 << 2)
+#define DAC33_DACSRCL_MONOMIX		(0x03 << 2)
+#define DAC33_DVOLSTEP_MASK		(0x03 << 4)
+#define DAC33_DVOLSTEP_SS_PERFS		(0x00 << 4)
+#define DAC33_DVOLSTEP_SS_PER2FS	(0x01 << 4)
+#define DAC33_DVOLSTEP_SS_DISABLED	(0x02 << 4)
+#define DAC33_DVOLCTRL_MASK		(0x03 << 6)
+#define DAC33_DVOLCTRL_LR_INDEPENDENT1	(0x00 << 6)
+#define DAC33_DVOLCTRL_LR_RIGHT_CONTROL	(0x01 << 6)
+#define DAC33_DVOLCTRL_LR_LEFT_CONTROL	(0x02 << 6)
+#define DAC33_DVOLCTRL_LR_INDEPENDENT2	(0x03 << 6)
+
+/* DAC33_DAC_CTRL_C (0x2E) */
+#define DAC33_DEEMENR			(0x01 << 0)
+#define DAC33_EFFENR			(0x01 << 1)
+#define DAC33_DEEMENL			(0x01 << 2)
+#define DAC33_EFFENL			(0x01 << 3)
+#define DAC33_EN3D			(0x01 << 4)
+#define DAC33_RESYNMUTE			(0x01 << 5)
+#define DAC33_RESYNEN			(0x01 << 6)
+
+/* DAC33_ASRC_CTRL_A (0x32) */
+#define DAC33_SRCBYP			(0x01 << 0)
+#define DAC33_SRCLKSEL_MASK		(0x03 << 1)
+#define DAC33_SRCLKSEL_INTSOC		(0x00 << 1)
+#define DAC33_SRCLKSEL_PLL		(0x01 << 1)
+#define DAC33_SRCLKSEL_MCLK		(0x02 << 1)
+#define DAC33_SRCLKSEL_BCLK		(0x03 << 1)
+#define DAC33_SRCLKDIV(x)		(x << 3)
+
+/* DAC33_ASRC_CTRL_B (0x33) */
+#define DAC33_SRCSETUP(x)		(x << 0)
+#define DAC33_SRCREFSEL			(0x01 << 4)
+#define DAC33_SRCREFDIV(x)		(x << 5)
+
+/* DAC33_INTP_CTRL_A (0x38) */
+#define DAC33_INTPSEL			(0x01 << 0)
+#define DAC33_INTPM_MASK		(0x03 << 1)
+#define DAC33_INTPM_ALOW_OPENDRAIN	(0x00 << 1)
+#define DAC33_INTPM_ALOW		(0x01 << 1)
+#define DAC33_INTPM_AHIGH		(0x02 << 1)
+
+/* DAC33_LDAC_PWR_CTRL (0x40) */
+/* DAC33_RDAC_PWR_CTRL (0x41) */
+#define DAC33_DACLRNUM			(0x01 << 2)
+#define DAC33_LROUT_GAIN(x)		(x << 0)
+
+/* DAC33_ANA_VOL_SOFT_STEP_CTRL (0x49) */
+#define DAC33_VOLCLKSEL			(0x01 << 0)
+#define DAC33_VOLCLKEN			(0x01 << 1)
+#define DAC33_VOLBYPASS			(0x01 << 2)
+
+#define TLV320DAC33_MCLK		0
+#define TLV320DAC33_SLEEPCLK		1
+
+#endif /* __TLV320DAC33_H */
diff --git a/linux/sound/soc/codecs/tpa6130a2.c b/linux/sound/soc/codecs/tpa6130a2.c
new file mode 100644
index 0000000..d2c2430
--- /dev/null
+++ b/linux/sound/soc/codecs/tpa6130a2.c
@@ -0,0 +1,560 @@
+/*
+ * ALSA SoC Texas Instruments TPA6130A2 headset stereo amplifier driver
+ *
+ * Copyright (C) Nokia Corporation
+ *
+ * Author: Peter Ujfalusi <peter.ujfalusi@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/tpa6130a2-plat.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+
+#include "tpa6130a2.h"
+
+static struct i2c_client *tpa6130a2_client;
+
+/* This struct is used to save the context */
+struct tpa6130a2_data {
+	struct mutex mutex;
+	unsigned char regs[TPA6130A2_CACHEREGNUM];
+	struct regulator *supply;
+	int power_gpio;
+	unsigned char power_state;
+	enum tpa_model id;
+};
+
+static int tpa6130a2_i2c_read(int reg)
+{
+	struct tpa6130a2_data *data;
+	int val;
+
+	BUG_ON(tpa6130a2_client == NULL);
+	data = i2c_get_clientdata(tpa6130a2_client);
+
+	/* If powered off, return the cached value */
+	if (data->power_state) {
+		val = i2c_smbus_read_byte_data(tpa6130a2_client, reg);
+		if (val < 0)
+			dev_err(&tpa6130a2_client->dev, "Read failed\n");
+		else
+			data->regs[reg] = val;
+	} else {
+		val = data->regs[reg];
+	}
+
+	return val;
+}
+
+static int tpa6130a2_i2c_write(int reg, u8 value)
+{
+	struct tpa6130a2_data *data;
+	int val = 0;
+
+	BUG_ON(tpa6130a2_client == NULL);
+	data = i2c_get_clientdata(tpa6130a2_client);
+
+	if (data->power_state) {
+		val = i2c_smbus_write_byte_data(tpa6130a2_client, reg, value);
+		if (val < 0) {
+			dev_err(&tpa6130a2_client->dev, "Write failed\n");
+			return val;
+		}
+	}
+
+	/* Either powered on or off, we save the context */
+	data->regs[reg] = value;
+
+	return val;
+}
+
+static u8 tpa6130a2_read(int reg)
+{
+	struct tpa6130a2_data *data;
+
+	BUG_ON(tpa6130a2_client == NULL);
+	data = i2c_get_clientdata(tpa6130a2_client);
+
+	return data->regs[reg];
+}
+
+static int tpa6130a2_initialize(void)
+{
+	struct tpa6130a2_data *data;
+	int i, ret = 0;
+
+	BUG_ON(tpa6130a2_client == NULL);
+	data = i2c_get_clientdata(tpa6130a2_client);
+
+	for (i = 1; i < TPA6130A2_REG_VERSION; i++) {
+		ret = tpa6130a2_i2c_write(i, data->regs[i]);
+		if (ret < 0)
+			break;
+	}
+
+	return ret;
+}
+
+static int tpa6130a2_power(int power)
+{
+	struct	tpa6130a2_data *data;
+	u8	val;
+	int	ret = 0;
+
+	BUG_ON(tpa6130a2_client == NULL);
+	data = i2c_get_clientdata(tpa6130a2_client);
+
+	mutex_lock(&data->mutex);
+	if (power && !data->power_state) {
+		/* Power on */
+		if (data->power_gpio >= 0)
+			gpio_set_value(data->power_gpio, 1);
+
+		ret = regulator_enable(data->supply);
+		if (ret != 0) {
+			dev_err(&tpa6130a2_client->dev,
+				"Failed to enable supply: %d\n", ret);
+			goto exit;
+		}
+
+		data->power_state = 1;
+		ret = tpa6130a2_initialize();
+		if (ret < 0) {
+			dev_err(&tpa6130a2_client->dev,
+				"Failed to initialize chip\n");
+			if (data->power_gpio >= 0)
+				gpio_set_value(data->power_gpio, 0);
+			regulator_disable(data->supply);
+			data->power_state = 0;
+			goto exit;
+		}
+
+		/* Clear SWS */
+		val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
+		val &= ~TPA6130A2_SWS;
+		tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
+	} else if (!power && data->power_state) {
+		/* set SWS */
+		val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
+		val |= TPA6130A2_SWS;
+		tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
+
+		/* Power off */
+		if (data->power_gpio >= 0)
+			gpio_set_value(data->power_gpio, 0);
+
+		ret = regulator_disable(data->supply);
+		if (ret != 0) {
+			dev_err(&tpa6130a2_client->dev,
+				"Failed to disable supply: %d\n", ret);
+			goto exit;
+		}
+
+		data->power_state = 0;
+	}
+
+exit:
+	mutex_unlock(&data->mutex);
+	return ret;
+}
+
+static int tpa6130a2_get_volsw(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct tpa6130a2_data *data;
+	unsigned int reg = mc->reg;
+	unsigned int shift = mc->shift;
+	int max = mc->max;
+	unsigned int mask = (1 << fls(max)) - 1;
+	unsigned int invert = mc->invert;
+
+	BUG_ON(tpa6130a2_client == NULL);
+	data = i2c_get_clientdata(tpa6130a2_client);
+
+	mutex_lock(&data->mutex);
+
+	ucontrol->value.integer.value[0] =
+		(tpa6130a2_read(reg) >> shift) & mask;
+
+	if (invert)
+		ucontrol->value.integer.value[0] =
+			max - ucontrol->value.integer.value[0];
+
+	mutex_unlock(&data->mutex);
+	return 0;
+}
+
+static int tpa6130a2_put_volsw(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct tpa6130a2_data *data;
+	unsigned int reg = mc->reg;
+	unsigned int shift = mc->shift;
+	int max = mc->max;
+	unsigned int mask = (1 << fls(max)) - 1;
+	unsigned int invert = mc->invert;
+	unsigned int val = (ucontrol->value.integer.value[0] & mask);
+	unsigned int val_reg;
+
+	BUG_ON(tpa6130a2_client == NULL);
+	data = i2c_get_clientdata(tpa6130a2_client);
+
+	if (invert)
+		val = max - val;
+
+	mutex_lock(&data->mutex);
+
+	val_reg = tpa6130a2_read(reg);
+	if (((val_reg >> shift) & mask) == val) {
+		mutex_unlock(&data->mutex);
+		return 0;
+	}
+
+	val_reg &= ~(mask << shift);
+	val_reg |= val << shift;
+	tpa6130a2_i2c_write(reg, val_reg);
+
+	mutex_unlock(&data->mutex);
+
+	return 1;
+}
+
+/*
+ * TPA6130 volume. From -59.5 to 4 dB with increasing step size when going
+ * down in gain.
+ */
+static const unsigned int tpa6130_tlv[] = {
+	TLV_DB_RANGE_HEAD(10),
+	0, 1, TLV_DB_SCALE_ITEM(-5950, 600, 0),
+	2, 3, TLV_DB_SCALE_ITEM(-5000, 250, 0),
+	4, 5, TLV_DB_SCALE_ITEM(-4550, 160, 0),
+	6, 7, TLV_DB_SCALE_ITEM(-4140, 190, 0),
+	8, 9, TLV_DB_SCALE_ITEM(-3650, 120, 0),
+	10, 11, TLV_DB_SCALE_ITEM(-3330, 160, 0),
+	12, 13, TLV_DB_SCALE_ITEM(-3040, 180, 0),
+	14, 20, TLV_DB_SCALE_ITEM(-2710, 110, 0),
+	21, 37, TLV_DB_SCALE_ITEM(-1960, 74, 0),
+	38, 63, TLV_DB_SCALE_ITEM(-720, 45, 0),
+};
+
+static const struct snd_kcontrol_new tpa6130a2_controls[] = {
+	SOC_SINGLE_EXT_TLV("TPA6130A2 Headphone Playback Volume",
+		       TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0,
+		       tpa6130a2_get_volsw, tpa6130a2_put_volsw,
+		       tpa6130_tlv),
+};
+
+static const unsigned int tpa6140_tlv[] = {
+	TLV_DB_RANGE_HEAD(3),
+	0, 8, TLV_DB_SCALE_ITEM(-5900, 400, 0),
+	9, 16, TLV_DB_SCALE_ITEM(-2500, 200, 0),
+	17, 31, TLV_DB_SCALE_ITEM(-1000, 100, 0),
+};
+
+static const struct snd_kcontrol_new tpa6140a2_controls[] = {
+	SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume",
+		       TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0,
+		       tpa6130a2_get_volsw, tpa6130a2_put_volsw,
+		       tpa6140_tlv),
+};
+
+/*
+ * Enable or disable channel (left or right)
+ * The bit number for mute and amplifier are the same per channel:
+ * bit 6: Right channel
+ * bit 7: Left channel
+ * in both registers.
+ */
+static void tpa6130a2_channel_enable(u8 channel, int enable)
+{
+	u8	val;
+
+	if (enable) {
+		/* Enable channel */
+		/* Enable amplifier */
+		val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
+		val |= channel;
+		tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
+
+		/* Unmute channel */
+		val = tpa6130a2_read(TPA6130A2_REG_VOL_MUTE);
+		val &= ~channel;
+		tpa6130a2_i2c_write(TPA6130A2_REG_VOL_MUTE, val);
+	} else {
+		/* Disable channel */
+		/* Mute channel */
+		val = tpa6130a2_read(TPA6130A2_REG_VOL_MUTE);
+		val |= channel;
+		tpa6130a2_i2c_write(TPA6130A2_REG_VOL_MUTE, val);
+
+		/* Disable amplifier */
+		val = tpa6130a2_read(TPA6130A2_REG_CONTROL);
+		val &= ~channel;
+		tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val);
+	}
+}
+
+static int tpa6130a2_left_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		tpa6130a2_channel_enable(TPA6130A2_HP_EN_L, 1);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		tpa6130a2_channel_enable(TPA6130A2_HP_EN_L, 0);
+		break;
+	}
+	return 0;
+}
+
+static int tpa6130a2_right_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		tpa6130a2_channel_enable(TPA6130A2_HP_EN_R, 1);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		tpa6130a2_channel_enable(TPA6130A2_HP_EN_R, 0);
+		break;
+	}
+	return 0;
+}
+
+static int tpa6130a2_supply_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	int ret = 0;
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		ret = tpa6130a2_power(1);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		ret = tpa6130a2_power(0);
+		break;
+	}
+	return ret;
+}
+
+static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = {
+	SND_SOC_DAPM_PGA_E("TPA6130A2 Left", SND_SOC_NOPM,
+			0, 0, NULL, 0, tpa6130a2_left_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_PGA_E("TPA6130A2 Right", SND_SOC_NOPM,
+			0, 0, NULL, 0, tpa6130a2_right_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_SUPPLY("TPA6130A2 Enable", SND_SOC_NOPM,
+			0, 0, tpa6130a2_supply_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+	/* Outputs */
+	SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Left"),
+	SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Right"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"TPA6130A2 Headphone Left", NULL, "TPA6130A2 Left"},
+	{"TPA6130A2 Headphone Right", NULL, "TPA6130A2 Right"},
+
+	{"TPA6130A2 Headphone Left", NULL, "TPA6130A2 Enable"},
+	{"TPA6130A2 Headphone Right", NULL, "TPA6130A2 Enable"},
+};
+
+int tpa6130a2_add_controls(struct snd_soc_codec *codec)
+{
+	struct	tpa6130a2_data *data;
+
+	if (tpa6130a2_client == NULL)
+		return -ENODEV;
+
+	data = i2c_get_clientdata(tpa6130a2_client);
+
+	snd_soc_dapm_new_controls(codec, tpa6130a2_dapm_widgets,
+				ARRAY_SIZE(tpa6130a2_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	if (data->id == TPA6140A2)
+		return snd_soc_add_controls(codec, tpa6140a2_controls,
+						ARRAY_SIZE(tpa6140a2_controls));
+	else
+		return snd_soc_add_controls(codec, tpa6130a2_controls,
+						ARRAY_SIZE(tpa6130a2_controls));
+
+}
+EXPORT_SYMBOL_GPL(tpa6130a2_add_controls);
+
+static int __devinit tpa6130a2_probe(struct i2c_client *client,
+				     const struct i2c_device_id *id)
+{
+	struct device *dev;
+	struct tpa6130a2_data *data;
+	struct tpa6130a2_platform_data *pdata;
+	const char *regulator;
+	int ret;
+
+	dev = &client->dev;
+
+	if (client->dev.platform_data == NULL) {
+		dev_err(dev, "Platform data not set\n");
+		dump_stack();
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (data == NULL) {
+		dev_err(dev, "Can not allocate memory\n");
+		return -ENOMEM;
+	}
+
+	tpa6130a2_client = client;
+
+	i2c_set_clientdata(tpa6130a2_client, data);
+
+	pdata = client->dev.platform_data;
+	data->power_gpio = pdata->power_gpio;
+	data->id = pdata->id;
+
+	mutex_init(&data->mutex);
+
+	/* Set default register values */
+	data->regs[TPA6130A2_REG_CONTROL] =	TPA6130A2_SWS;
+	data->regs[TPA6130A2_REG_VOL_MUTE] =	TPA6130A2_MUTE_R |
+						TPA6130A2_MUTE_L;
+
+	if (data->power_gpio >= 0) {
+		ret = gpio_request(data->power_gpio, "tpa6130a2 enable");
+		if (ret < 0) {
+			dev_err(dev, "Failed to request power GPIO (%d)\n",
+				data->power_gpio);
+			goto err_gpio;
+		}
+		gpio_direction_output(data->power_gpio, 0);
+	}
+
+	switch (data->id) {
+	default:
+		dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n",
+			 pdata->id);
+	case TPA6130A2:
+		regulator = "Vdd";
+		break;
+	case TPA6140A2:
+		regulator = "AVdd";
+		break;
+	}
+
+	data->supply = regulator_get(dev, regulator);
+	if (IS_ERR(data->supply)) {
+		ret = PTR_ERR(data->supply);
+		dev_err(dev, "Failed to request supply: %d\n", ret);
+		goto err_regulator;
+	}
+
+	ret = tpa6130a2_power(1);
+	if (ret != 0)
+		goto err_power;
+
+
+	/* Read version */
+	ret = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) &
+				 TPA6130A2_VERSION_MASK;
+	if ((ret != 1) && (ret != 2))
+		dev_warn(dev, "UNTESTED version detected (%d)\n", ret);
+
+	/* Disable the chip */
+	ret = tpa6130a2_power(0);
+	if (ret != 0)
+		goto err_power;
+
+	return 0;
+
+err_power:
+	regulator_put(data->supply);
+err_regulator:
+	if (data->power_gpio >= 0)
+		gpio_free(data->power_gpio);
+err_gpio:
+	kfree(data);
+	i2c_set_clientdata(tpa6130a2_client, NULL);
+	tpa6130a2_client = NULL;
+
+	return ret;
+}
+
+static int __devexit tpa6130a2_remove(struct i2c_client *client)
+{
+	struct tpa6130a2_data *data = i2c_get_clientdata(client);
+
+	tpa6130a2_power(0);
+
+	if (data->power_gpio >= 0)
+		gpio_free(data->power_gpio);
+
+	regulator_put(data->supply);
+
+	kfree(data);
+	tpa6130a2_client = NULL;
+
+	return 0;
+}
+
+static const struct i2c_device_id tpa6130a2_id[] = {
+	{ "tpa6130a2", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tpa6130a2_id);
+
+static struct i2c_driver tpa6130a2_i2c_driver = {
+	.driver = {
+		.name = "tpa6130a2",
+		.owner = THIS_MODULE,
+	},
+	.probe = tpa6130a2_probe,
+	.remove = __devexit_p(tpa6130a2_remove),
+	.id_table = tpa6130a2_id,
+};
+
+static int __init tpa6130a2_init(void)
+{
+	return i2c_add_driver(&tpa6130a2_i2c_driver);
+}
+
+static void __exit tpa6130a2_exit(void)
+{
+	i2c_del_driver(&tpa6130a2_i2c_driver);
+}
+
+MODULE_AUTHOR("Peter Ujfalusi");
+MODULE_DESCRIPTION("TPA6130A2 Headphone amplifier driver");
+MODULE_LICENSE("GPL");
+
+module_init(tpa6130a2_init);
+module_exit(tpa6130a2_exit);
diff --git a/linux/sound/soc/codecs/tpa6130a2.h b/linux/sound/soc/codecs/tpa6130a2.h
new file mode 100644
index 0000000..57e867f
--- /dev/null
+++ b/linux/sound/soc/codecs/tpa6130a2.h
@@ -0,0 +1,61 @@
+/*
+ * ALSA SoC TPA6130A2 amplifier driver
+ *
+ * Copyright (C) Nokia Corporation
+ *
+ * Author: Peter Ujfalusi <peter.ujfalusi@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TPA6130A2_H__
+#define __TPA6130A2_H__
+
+/* Register addresses */
+#define TPA6130A2_REG_CONTROL		0x01
+#define TPA6130A2_REG_VOL_MUTE		0x02
+#define TPA6130A2_REG_OUT_IMPEDANCE	0x03
+#define TPA6130A2_REG_VERSION		0x04
+
+#define TPA6130A2_CACHEREGNUM	(TPA6130A2_REG_VERSION + 1)
+
+/* Register bits */
+/* TPA6130A2_REG_CONTROL (0x01) */
+#define TPA6130A2_SWS			(0x01 << 0)
+#define TPA6130A2_TERMAL		(0x01 << 1)
+#define TPA6130A2_MODE(x)		(x << 4)
+#define TPA6130A2_MODE_STEREO		(0x00)
+#define TPA6130A2_MODE_DUAL_MONO	(0x01)
+#define TPA6130A2_MODE_BRIDGE		(0x02)
+#define TPA6130A2_MODE_MASK		(0x03)
+#define TPA6130A2_HP_EN_R		(0x01 << 6)
+#define TPA6130A2_HP_EN_L		(0x01 << 7)
+
+/* TPA6130A2_REG_VOL_MUTE (0x02) */
+#define TPA6130A2_VOLUME(x)		((x & 0x3f) << 0)
+#define TPA6130A2_MUTE_R		(0x01 << 6)
+#define TPA6130A2_MUTE_L		(0x01 << 7)
+
+/* TPA6130A2_REG_OUT_IMPEDANCE (0x03) */
+#define TPA6130A2_HIZ_R			(0x01 << 0)
+#define TPA6130A2_HIZ_L			(0x01 << 1)
+
+/* TPA6130A2_REG_VERSION (0x04) */
+#define TPA6130A2_VERSION_MASK		(0x0f)
+
+extern int tpa6130a2_add_controls(struct snd_soc_codec *codec);
+
+#endif /* __TPA6130A2_H__ */
diff --git a/linux/sound/soc/codecs/twl4030.c b/linux/sound/soc/codecs/twl4030.c
new file mode 100644
index 0000000..cbebec6
--- /dev/null
+++ b/linux/sound/soc/codecs/twl4030.c
@@ -0,0 +1,2326 @@
+/*
+ * ALSA SoC TWL4030 codec driver
+ *
+ * Author:      Steve Sakoman, <steve@sakoman.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+/* Register descriptions are here */
+#include <linux/mfd/twl4030-codec.h>
+
+/* Shadow register used by the audio driver */
+#define TWL4030_REG_SW_SHADOW		0x4A
+#define TWL4030_CACHEREGNUM	(TWL4030_REG_SW_SHADOW + 1)
+
+/* TWL4030_REG_SW_SHADOW (0x4A) Fields */
+#define TWL4030_HFL_EN			0x01
+#define TWL4030_HFR_EN			0x02
+
+/*
+ * twl4030 register cache & default register settings
+ */
+static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
+	0x00, /* this register not used		*/
+	0x00, /* REG_CODEC_MODE		(0x1)	*/
+	0x00, /* REG_OPTION		(0x2)	*/
+	0x00, /* REG_UNKNOWN		(0x3)	*/
+	0x00, /* REG_MICBIAS_CTL	(0x4)	*/
+	0x00, /* REG_ANAMICL		(0x5)	*/
+	0x00, /* REG_ANAMICR		(0x6)	*/
+	0x00, /* REG_AVADC_CTL		(0x7)	*/
+	0x00, /* REG_ADCMICSEL		(0x8)	*/
+	0x00, /* REG_DIGMIXING		(0x9)	*/
+	0x0f, /* REG_ATXL1PGA		(0xA)	*/
+	0x0f, /* REG_ATXR1PGA		(0xB)	*/
+	0x0f, /* REG_AVTXL2PGA		(0xC)	*/
+	0x0f, /* REG_AVTXR2PGA		(0xD)	*/
+	0x00, /* REG_AUDIO_IF		(0xE)	*/
+	0x00, /* REG_VOICE_IF		(0xF)	*/
+	0x3f, /* REG_ARXR1PGA		(0x10)	*/
+	0x3f, /* REG_ARXL1PGA		(0x11)	*/
+	0x3f, /* REG_ARXR2PGA		(0x12)	*/
+	0x3f, /* REG_ARXL2PGA		(0x13)	*/
+	0x25, /* REG_VRXPGA		(0x14)	*/
+	0x00, /* REG_VSTPGA		(0x15)	*/
+	0x00, /* REG_VRX2ARXPGA		(0x16)	*/
+	0x00, /* REG_AVDAC_CTL		(0x17)	*/
+	0x00, /* REG_ARX2VTXPGA		(0x18)	*/
+	0x32, /* REG_ARXL1_APGA_CTL	(0x19)	*/
+	0x32, /* REG_ARXR1_APGA_CTL	(0x1A)	*/
+	0x32, /* REG_ARXL2_APGA_CTL	(0x1B)	*/
+	0x32, /* REG_ARXR2_APGA_CTL	(0x1C)	*/
+	0x00, /* REG_ATX2ARXPGA		(0x1D)	*/
+	0x00, /* REG_BT_IF		(0x1E)	*/
+	0x55, /* REG_BTPGA		(0x1F)	*/
+	0x00, /* REG_BTSTPGA		(0x20)	*/
+	0x00, /* REG_EAR_CTL		(0x21)	*/
+	0x00, /* REG_HS_SEL		(0x22)	*/
+	0x00, /* REG_HS_GAIN_SET	(0x23)	*/
+	0x00, /* REG_HS_POPN_SET	(0x24)	*/
+	0x00, /* REG_PREDL_CTL		(0x25)	*/
+	0x00, /* REG_PREDR_CTL		(0x26)	*/
+	0x00, /* REG_PRECKL_CTL		(0x27)	*/
+	0x00, /* REG_PRECKR_CTL		(0x28)	*/
+	0x00, /* REG_HFL_CTL		(0x29)	*/
+	0x00, /* REG_HFR_CTL		(0x2A)	*/
+	0x05, /* REG_ALC_CTL		(0x2B)	*/
+	0x00, /* REG_ALC_SET1		(0x2C)	*/
+	0x00, /* REG_ALC_SET2		(0x2D)	*/
+	0x00, /* REG_BOOST_CTL		(0x2E)	*/
+	0x00, /* REG_SOFTVOL_CTL	(0x2F)	*/
+	0x13, /* REG_DTMF_FREQSEL	(0x30)	*/
+	0x00, /* REG_DTMF_TONEXT1H	(0x31)	*/
+	0x00, /* REG_DTMF_TONEXT1L	(0x32)	*/
+	0x00, /* REG_DTMF_TONEXT2H	(0x33)	*/
+	0x00, /* REG_DTMF_TONEXT2L	(0x34)	*/
+	0x79, /* REG_DTMF_TONOFF	(0x35)	*/
+	0x11, /* REG_DTMF_WANONOFF	(0x36)	*/
+	0x00, /* REG_I2S_RX_SCRAMBLE_H	(0x37)	*/
+	0x00, /* REG_I2S_RX_SCRAMBLE_M	(0x38)	*/
+	0x00, /* REG_I2S_RX_SCRAMBLE_L	(0x39)	*/
+	0x06, /* REG_APLL_CTL		(0x3A)	*/
+	0x00, /* REG_DTMF_CTL		(0x3B)	*/
+	0x44, /* REG_DTMF_PGA_CTL2	(0x3C)	*/
+	0x69, /* REG_DTMF_PGA_CTL1	(0x3D)	*/
+	0x00, /* REG_MISC_SET_1		(0x3E)	*/
+	0x00, /* REG_PCMBTMUX		(0x3F)	*/
+	0x00, /* not used		(0x40)	*/
+	0x00, /* not used		(0x41)	*/
+	0x00, /* not used		(0x42)	*/
+	0x00, /* REG_RX_PATH_SEL	(0x43)	*/
+	0x32, /* REG_VDL_APGA_CTL	(0x44)	*/
+	0x00, /* REG_VIBRA_CTL		(0x45)	*/
+	0x00, /* REG_VIBRA_SET		(0x46)	*/
+	0x00, /* REG_VIBRA_PWM_SET	(0x47)	*/
+	0x00, /* REG_ANAMIC_GAIN	(0x48)	*/
+	0x00, /* REG_MISC_SET_2		(0x49)	*/
+	0x00, /* REG_SW_SHADOW		(0x4A)	- Shadow, non HW register */
+};
+
+/* codec private data */
+struct twl4030_priv {
+	struct snd_soc_codec codec;
+
+	unsigned int codec_powered;
+
+	/* reference counts of AIF/APLL users */
+	unsigned int apll_enabled;
+
+	struct snd_pcm_substream *master_substream;
+	struct snd_pcm_substream *slave_substream;
+
+	unsigned int configured;
+	unsigned int rate;
+	unsigned int sample_bits;
+	unsigned int channels;
+
+	unsigned int sysclk;
+
+	/* Output (with associated amp) states */
+	u8 hsl_enabled, hsr_enabled;
+	u8 earpiece_enabled;
+	u8 predrivel_enabled, predriver_enabled;
+	u8 carkitl_enabled, carkitr_enabled;
+
+	/* Delay needed after enabling the digimic interface */
+	unsigned int digimic_delay;
+};
+
+/*
+ * read twl4030 register cache
+ */
+static inline unsigned int twl4030_read_reg_cache(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	u8 *cache = codec->reg_cache;
+
+	if (reg >= TWL4030_CACHEREGNUM)
+		return -EIO;
+
+	return cache[reg];
+}
+
+/*
+ * write twl4030 register cache
+ */
+static inline void twl4030_write_reg_cache(struct snd_soc_codec *codec,
+						u8 reg, u8 value)
+{
+	u8 *cache = codec->reg_cache;
+
+	if (reg >= TWL4030_CACHEREGNUM)
+		return;
+	cache[reg] = value;
+}
+
+/*
+ * write to the twl4030 register space
+ */
+static int twl4030_write(struct snd_soc_codec *codec,
+			unsigned int reg, unsigned int value)
+{
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+	int write_to_reg = 0;
+
+	twl4030_write_reg_cache(codec, reg, value);
+	if (likely(reg < TWL4030_REG_SW_SHADOW)) {
+		/* Decide if the given register can be written */
+		switch (reg) {
+		case TWL4030_REG_EAR_CTL:
+			if (twl4030->earpiece_enabled)
+				write_to_reg = 1;
+			break;
+		case TWL4030_REG_PREDL_CTL:
+			if (twl4030->predrivel_enabled)
+				write_to_reg = 1;
+			break;
+		case TWL4030_REG_PREDR_CTL:
+			if (twl4030->predriver_enabled)
+				write_to_reg = 1;
+			break;
+		case TWL4030_REG_PRECKL_CTL:
+			if (twl4030->carkitl_enabled)
+				write_to_reg = 1;
+			break;
+		case TWL4030_REG_PRECKR_CTL:
+			if (twl4030->carkitr_enabled)
+				write_to_reg = 1;
+			break;
+		case TWL4030_REG_HS_GAIN_SET:
+			if (twl4030->hsl_enabled || twl4030->hsr_enabled)
+				write_to_reg = 1;
+			break;
+		default:
+			/* All other register can be written */
+			write_to_reg = 1;
+			break;
+		}
+		if (write_to_reg)
+			return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+						    value, reg);
+	}
+	return 0;
+}
+
+static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable)
+{
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+	int mode;
+
+	if (enable == twl4030->codec_powered)
+		return;
+
+	if (enable)
+		mode = twl4030_codec_enable_resource(TWL4030_CODEC_RES_POWER);
+	else
+		mode = twl4030_codec_disable_resource(TWL4030_CODEC_RES_POWER);
+
+	if (mode >= 0) {
+		twl4030_write_reg_cache(codec, TWL4030_REG_CODEC_MODE, mode);
+		twl4030->codec_powered = enable;
+	}
+
+	/* REVISIT: this delay is present in TI sample drivers */
+	/* but there seems to be no TRM requirement for it     */
+	udelay(10);
+}
+
+static inline void twl4030_check_defaults(struct snd_soc_codec *codec)
+{
+	int i, difference = 0;
+	u8 val;
+
+	dev_dbg(codec->dev, "Checking TWL audio default configuration\n");
+	for (i = 1; i <= TWL4030_REG_MISC_SET_2; i++) {
+		twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, i);
+		if (val != twl4030_reg[i]) {
+			difference++;
+			dev_dbg(codec->dev,
+				 "Reg 0x%02x: chip: 0x%02x driver: 0x%02x\n",
+				 i, val, twl4030_reg[i]);
+		}
+	}
+	dev_dbg(codec->dev, "Found %d non maching registers. %s\n",
+		 difference, difference ? "Not OK" : "OK");
+}
+
+static inline void twl4030_reset_registers(struct snd_soc_codec *codec)
+{
+	int i;
+
+	/* set all audio section registers to reasonable defaults */
+	for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++)
+		if (i != TWL4030_REG_APLL_CTL)
+			twl4030_write(codec, i, twl4030_reg[i]);
+
+}
+
+static void twl4030_init_chip(struct snd_soc_codec *codec)
+{
+	struct twl4030_codec_audio_data *pdata = dev_get_platdata(codec->dev);
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+	u8 reg, byte;
+	int i = 0;
+
+	/* Check defaults, if instructed before anything else */
+	if (pdata && pdata->check_defaults)
+		twl4030_check_defaults(codec);
+
+	/* Reset registers, if no setup data or if instructed to do so */
+	if (!pdata || (pdata && pdata->reset_registers))
+		twl4030_reset_registers(codec);
+
+	/* Refresh APLL_CTL register from HW */
+	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte,
+			    TWL4030_REG_APLL_CTL);
+	twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, byte);
+
+	/* anti-pop when changing analog gain */
+	reg = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1);
+	twl4030_write(codec, TWL4030_REG_MISC_SET_1,
+		reg | TWL4030_SMOOTH_ANAVOL_EN);
+
+	twl4030_write(codec, TWL4030_REG_OPTION,
+		TWL4030_ATXL1_EN | TWL4030_ATXR1_EN |
+		TWL4030_ARXL2_EN | TWL4030_ARXR2_EN);
+
+	/* REG_ARXR2_APGA_CTL reset according to the TRM: 0dB, DA_EN */
+	twl4030_write(codec, TWL4030_REG_ARXR2_APGA_CTL, 0x32);
+
+	/* Machine dependent setup */
+	if (!pdata)
+		return;
+
+	twl4030->digimic_delay = pdata->digimic_delay;
+
+	reg = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
+	reg &= ~TWL4030_RAMP_DELAY;
+	reg |= (pdata->ramp_delay_value << 2);
+	twl4030_write_reg_cache(codec, TWL4030_REG_HS_POPN_SET, reg);
+
+	/* initiate offset cancellation */
+	twl4030_codec_enable(codec, 1);
+
+	reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL);
+	reg &= ~TWL4030_OFFSET_CNCL_SEL;
+	reg |= pdata->offset_cncl_path;
+	twl4030_write(codec, TWL4030_REG_ANAMICL,
+		reg | TWL4030_CNCL_OFFSET_START);
+
+	/* wait for offset cancellation to complete */
+	do {
+		/* this takes a little while, so don't slam i2c */
+		udelay(2000);
+		twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte,
+				    TWL4030_REG_ANAMICL);
+	} while ((i++ < 100) &&
+		 ((byte & TWL4030_CNCL_OFFSET_START) ==
+		  TWL4030_CNCL_OFFSET_START));
+
+	/* Make sure that the reg_cache has the same value as the HW */
+	twl4030_write_reg_cache(codec, TWL4030_REG_ANAMICL, byte);
+
+	twl4030_codec_enable(codec, 0);
+}
+
+static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable)
+{
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+	int status = -1;
+
+	if (enable) {
+		twl4030->apll_enabled++;
+		if (twl4030->apll_enabled == 1)
+			status = twl4030_codec_enable_resource(
+							TWL4030_CODEC_RES_APLL);
+	} else {
+		twl4030->apll_enabled--;
+		if (!twl4030->apll_enabled)
+			status = twl4030_codec_disable_resource(
+							TWL4030_CODEC_RES_APLL);
+	}
+
+	if (status >= 0)
+		twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, status);
+}
+
+/* Earpiece */
+static const struct snd_kcontrol_new twl4030_dapm_earpiece_controls[] = {
+	SOC_DAPM_SINGLE("Voice", TWL4030_REG_EAR_CTL, 0, 1, 0),
+	SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_EAR_CTL, 1, 1, 0),
+	SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_EAR_CTL, 2, 1, 0),
+	SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_EAR_CTL, 3, 1, 0),
+};
+
+/* PreDrive Left */
+static const struct snd_kcontrol_new twl4030_dapm_predrivel_controls[] = {
+	SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDL_CTL, 0, 1, 0),
+	SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PREDL_CTL, 1, 1, 0),
+	SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDL_CTL, 2, 1, 0),
+	SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDL_CTL, 3, 1, 0),
+};
+
+/* PreDrive Right */
+static const struct snd_kcontrol_new twl4030_dapm_predriver_controls[] = {
+	SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDR_CTL, 0, 1, 0),
+	SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PREDR_CTL, 1, 1, 0),
+	SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDR_CTL, 2, 1, 0),
+	SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDR_CTL, 3, 1, 0),
+};
+
+/* Headset Left */
+static const struct snd_kcontrol_new twl4030_dapm_hsol_controls[] = {
+	SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 0, 1, 0),
+	SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_HS_SEL, 1, 1, 0),
+	SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_HS_SEL, 2, 1, 0),
+};
+
+/* Headset Right */
+static const struct snd_kcontrol_new twl4030_dapm_hsor_controls[] = {
+	SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 3, 1, 0),
+	SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_HS_SEL, 4, 1, 0),
+	SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_HS_SEL, 5, 1, 0),
+};
+
+/* Carkit Left */
+static const struct snd_kcontrol_new twl4030_dapm_carkitl_controls[] = {
+	SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKL_CTL, 0, 1, 0),
+	SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PRECKL_CTL, 1, 1, 0),
+	SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PRECKL_CTL, 2, 1, 0),
+};
+
+/* Carkit Right */
+static const struct snd_kcontrol_new twl4030_dapm_carkitr_controls[] = {
+	SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKR_CTL, 0, 1, 0),
+	SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PRECKR_CTL, 1, 1, 0),
+	SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PRECKR_CTL, 2, 1, 0),
+};
+
+/* Handsfree Left */
+static const char *twl4030_handsfreel_texts[] =
+		{"Voice", "AudioL1", "AudioL2", "AudioR2"};
+
+static const struct soc_enum twl4030_handsfreel_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_HFL_CTL, 0,
+			ARRAY_SIZE(twl4030_handsfreel_texts),
+			twl4030_handsfreel_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_handsfreel_control =
+SOC_DAPM_ENUM("Route", twl4030_handsfreel_enum);
+
+/* Handsfree Left virtual mute */
+static const struct snd_kcontrol_new twl4030_dapm_handsfreelmute_control =
+	SOC_DAPM_SINGLE("Switch", TWL4030_REG_SW_SHADOW, 0, 1, 0);
+
+/* Handsfree Right */
+static const char *twl4030_handsfreer_texts[] =
+		{"Voice", "AudioR1", "AudioR2", "AudioL2"};
+
+static const struct soc_enum twl4030_handsfreer_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_HFR_CTL, 0,
+			ARRAY_SIZE(twl4030_handsfreer_texts),
+			twl4030_handsfreer_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_handsfreer_control =
+SOC_DAPM_ENUM("Route", twl4030_handsfreer_enum);
+
+/* Handsfree Right virtual mute */
+static const struct snd_kcontrol_new twl4030_dapm_handsfreermute_control =
+	SOC_DAPM_SINGLE("Switch", TWL4030_REG_SW_SHADOW, 1, 1, 0);
+
+/* Vibra */
+/* Vibra audio path selection */
+static const char *twl4030_vibra_texts[] =
+		{"AudioL1", "AudioR1", "AudioL2", "AudioR2"};
+
+static const struct soc_enum twl4030_vibra_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 2,
+			ARRAY_SIZE(twl4030_vibra_texts),
+			twl4030_vibra_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_vibra_control =
+SOC_DAPM_ENUM("Route", twl4030_vibra_enum);
+
+/* Vibra path selection: local vibrator (PWM) or audio driven */
+static const char *twl4030_vibrapath_texts[] =
+		{"Local vibrator", "Audio"};
+
+static const struct soc_enum twl4030_vibrapath_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 4,
+			ARRAY_SIZE(twl4030_vibrapath_texts),
+			twl4030_vibrapath_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_vibrapath_control =
+SOC_DAPM_ENUM("Route", twl4030_vibrapath_enum);
+
+/* Left analog microphone selection */
+static const struct snd_kcontrol_new twl4030_dapm_analoglmic_controls[] = {
+	SOC_DAPM_SINGLE("Main Mic Capture Switch",
+			TWL4030_REG_ANAMICL, 0, 1, 0),
+	SOC_DAPM_SINGLE("Headset Mic Capture Switch",
+			TWL4030_REG_ANAMICL, 1, 1, 0),
+	SOC_DAPM_SINGLE("AUXL Capture Switch",
+			TWL4030_REG_ANAMICL, 2, 1, 0),
+	SOC_DAPM_SINGLE("Carkit Mic Capture Switch",
+			TWL4030_REG_ANAMICL, 3, 1, 0),
+};
+
+/* Right analog microphone selection */
+static const struct snd_kcontrol_new twl4030_dapm_analogrmic_controls[] = {
+	SOC_DAPM_SINGLE("Sub Mic Capture Switch", TWL4030_REG_ANAMICR, 0, 1, 0),
+	SOC_DAPM_SINGLE("AUXR Capture Switch", TWL4030_REG_ANAMICR, 2, 1, 0),
+};
+
+/* TX1 L/R Analog/Digital microphone selection */
+static const char *twl4030_micpathtx1_texts[] =
+		{"Analog", "Digimic0"};
+
+static const struct soc_enum twl4030_micpathtx1_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_ADCMICSEL, 0,
+			ARRAY_SIZE(twl4030_micpathtx1_texts),
+			twl4030_micpathtx1_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_micpathtx1_control =
+SOC_DAPM_ENUM("Route", twl4030_micpathtx1_enum);
+
+/* TX2 L/R Analog/Digital microphone selection */
+static const char *twl4030_micpathtx2_texts[] =
+		{"Analog", "Digimic1"};
+
+static const struct soc_enum twl4030_micpathtx2_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_ADCMICSEL, 2,
+			ARRAY_SIZE(twl4030_micpathtx2_texts),
+			twl4030_micpathtx2_texts);
+
+static const struct snd_kcontrol_new twl4030_dapm_micpathtx2_control =
+SOC_DAPM_ENUM("Route", twl4030_micpathtx2_enum);
+
+/* Analog bypass for AudioR1 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassr1_control =
+	SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR1_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioL1 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassl1_control =
+	SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL1_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioR2 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassr2_control =
+	SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR2_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioL2 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassl2_control =
+	SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL2_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for Voice */
+static const struct snd_kcontrol_new twl4030_dapm_abypassv_control =
+	SOC_DAPM_SINGLE("Switch", TWL4030_REG_VDL_APGA_CTL, 2, 1, 0);
+
+/* Digital bypass gain, mute instead of -30dB */
+static const unsigned int twl4030_dapm_dbypass_tlv[] = {
+	TLV_DB_RANGE_HEAD(3),
+	0, 1, TLV_DB_SCALE_ITEM(-3000, 600, 1),
+	2, 3, TLV_DB_SCALE_ITEM(-2400, 0, 0),
+	4, 7, TLV_DB_SCALE_ITEM(-1800, 600, 0),
+};
+
+/* Digital bypass left (TX1L -> RX2L) */
+static const struct snd_kcontrol_new twl4030_dapm_dbypassl_control =
+	SOC_DAPM_SINGLE_TLV("Volume",
+			TWL4030_REG_ATX2ARXPGA, 3, 7, 0,
+			twl4030_dapm_dbypass_tlv);
+
+/* Digital bypass right (TX1R -> RX2R) */
+static const struct snd_kcontrol_new twl4030_dapm_dbypassr_control =
+	SOC_DAPM_SINGLE_TLV("Volume",
+			TWL4030_REG_ATX2ARXPGA, 0, 7, 0,
+			twl4030_dapm_dbypass_tlv);
+
+/*
+ * Voice Sidetone GAIN volume control:
+ * from -51 to -10 dB in 1 dB steps (mute instead of -51 dB)
+ */
+static DECLARE_TLV_DB_SCALE(twl4030_dapm_dbypassv_tlv, -5100, 100, 1);
+
+/* Digital bypass voice: sidetone (VUL -> VDL)*/
+static const struct snd_kcontrol_new twl4030_dapm_dbypassv_control =
+	SOC_DAPM_SINGLE_TLV("Volume",
+			TWL4030_REG_VSTPGA, 0, 0x29, 0,
+			twl4030_dapm_dbypassv_tlv);
+
+/*
+ * Output PGA builder:
+ * Handle the muting and unmuting of the given output (turning off the
+ * amplifier associated with the output pin)
+ * On mute bypass the reg_cache and write 0 to the register
+ * On unmute: restore the register content from the reg_cache
+ * Outputs handled in this way:  Earpiece, PreDrivL/R, CarkitL/R
+ */
+#define TWL4030_OUTPUT_PGA(pin_name, reg, mask)				\
+static int pin_name##pga_event(struct snd_soc_dapm_widget *w,		\
+		struct snd_kcontrol *kcontrol, int event)		\
+{									\
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec); \
+									\
+	switch (event) {						\
+	case SND_SOC_DAPM_POST_PMU:					\
+		twl4030->pin_name##_enabled = 1;			\
+		twl4030_write(w->codec, reg,				\
+			twl4030_read_reg_cache(w->codec, reg));		\
+		break;							\
+	case SND_SOC_DAPM_POST_PMD:					\
+		twl4030->pin_name##_enabled = 0;			\
+		twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,		\
+					0, reg);			\
+		break;							\
+	}								\
+	return 0;							\
+}
+
+TWL4030_OUTPUT_PGA(earpiece, TWL4030_REG_EAR_CTL, TWL4030_EAR_GAIN);
+TWL4030_OUTPUT_PGA(predrivel, TWL4030_REG_PREDL_CTL, TWL4030_PREDL_GAIN);
+TWL4030_OUTPUT_PGA(predriver, TWL4030_REG_PREDR_CTL, TWL4030_PREDR_GAIN);
+TWL4030_OUTPUT_PGA(carkitl, TWL4030_REG_PRECKL_CTL, TWL4030_PRECKL_GAIN);
+TWL4030_OUTPUT_PGA(carkitr, TWL4030_REG_PRECKR_CTL, TWL4030_PRECKR_GAIN);
+
+static void handsfree_ramp(struct snd_soc_codec *codec, int reg, int ramp)
+{
+	unsigned char hs_ctl;
+
+	hs_ctl = twl4030_read_reg_cache(codec, reg);
+
+	if (ramp) {
+		/* HF ramp-up */
+		hs_ctl |= TWL4030_HF_CTL_REF_EN;
+		twl4030_write(codec, reg, hs_ctl);
+		udelay(10);
+		hs_ctl |= TWL4030_HF_CTL_RAMP_EN;
+		twl4030_write(codec, reg, hs_ctl);
+		udelay(40);
+		hs_ctl |= TWL4030_HF_CTL_LOOP_EN;
+		hs_ctl |= TWL4030_HF_CTL_HB_EN;
+		twl4030_write(codec, reg, hs_ctl);
+	} else {
+		/* HF ramp-down */
+		hs_ctl &= ~TWL4030_HF_CTL_LOOP_EN;
+		hs_ctl &= ~TWL4030_HF_CTL_HB_EN;
+		twl4030_write(codec, reg, hs_ctl);
+		hs_ctl &= ~TWL4030_HF_CTL_RAMP_EN;
+		twl4030_write(codec, reg, hs_ctl);
+		udelay(40);
+		hs_ctl &= ~TWL4030_HF_CTL_REF_EN;
+		twl4030_write(codec, reg, hs_ctl);
+	}
+}
+
+static int handsfreelpga_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		handsfree_ramp(w->codec, TWL4030_REG_HFL_CTL, 1);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		handsfree_ramp(w->codec, TWL4030_REG_HFL_CTL, 0);
+		break;
+	}
+	return 0;
+}
+
+static int handsfreerpga_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		handsfree_ramp(w->codec, TWL4030_REG_HFR_CTL, 1);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		handsfree_ramp(w->codec, TWL4030_REG_HFR_CTL, 0);
+		break;
+	}
+	return 0;
+}
+
+static int vibramux_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	twl4030_write(w->codec, TWL4030_REG_VIBRA_SET, 0xff);
+	return 0;
+}
+
+static int apll_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		twl4030_apll_enable(w->codec, 1);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		twl4030_apll_enable(w->codec, 0);
+		break;
+	}
+	return 0;
+}
+
+static int aif_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	u8 audio_if;
+
+	audio_if = twl4030_read_reg_cache(w->codec, TWL4030_REG_AUDIO_IF);
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		/* Enable AIF */
+		/* enable the PLL before we use it to clock the DAI */
+		twl4030_apll_enable(w->codec, 1);
+
+		twl4030_write(w->codec, TWL4030_REG_AUDIO_IF,
+						audio_if | TWL4030_AIF_EN);
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		/* disable the DAI before we stop it's source PLL */
+		twl4030_write(w->codec, TWL4030_REG_AUDIO_IF,
+						audio_if &  ~TWL4030_AIF_EN);
+		twl4030_apll_enable(w->codec, 0);
+		break;
+	}
+	return 0;
+}
+
+static void headset_ramp(struct snd_soc_codec *codec, int ramp)
+{
+	struct twl4030_codec_audio_data *pdata = codec->dev->platform_data;
+	unsigned char hs_gain, hs_pop;
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+	/* Base values for ramp delay calculation: 2^19 - 2^26 */
+	unsigned int ramp_base[] = {524288, 1048576, 2097152, 4194304,
+				    8388608, 16777216, 33554432, 67108864};
+
+	hs_gain = twl4030_read_reg_cache(codec, TWL4030_REG_HS_GAIN_SET);
+	hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
+
+	/* Enable external mute control, this dramatically reduces
+	 * the pop-noise */
+	if (pdata && pdata->hs_extmute) {
+		if (pdata->set_hs_extmute) {
+			pdata->set_hs_extmute(1);
+		} else {
+			hs_pop |= TWL4030_EXTMUTE;
+			twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+		}
+	}
+
+	if (ramp) {
+		/* Headset ramp-up according to the TRM */
+		hs_pop |= TWL4030_VMID_EN;
+		twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+		/* Actually write to the register */
+		twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+					hs_gain,
+					TWL4030_REG_HS_GAIN_SET);
+		hs_pop |= TWL4030_RAMP_EN;
+		twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+		/* Wait ramp delay time + 1, so the VMID can settle */
+		mdelay((ramp_base[(hs_pop & TWL4030_RAMP_DELAY) >> 2] /
+			twl4030->sysclk) + 1);
+	} else {
+		/* Headset ramp-down _not_ according to
+		 * the TRM, but in a way that it is working */
+		hs_pop &= ~TWL4030_RAMP_EN;
+		twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+		/* Wait ramp delay time + 1, so the VMID can settle */
+		mdelay((ramp_base[(hs_pop & TWL4030_RAMP_DELAY) >> 2] /
+			twl4030->sysclk) + 1);
+		/* Bypass the reg_cache to mute the headset */
+		twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+					hs_gain & (~0x0f),
+					TWL4030_REG_HS_GAIN_SET);
+
+		hs_pop &= ~TWL4030_VMID_EN;
+		twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+	}
+
+	/* Disable external mute */
+	if (pdata && pdata->hs_extmute) {
+		if (pdata->set_hs_extmute) {
+			pdata->set_hs_extmute(0);
+		} else {
+			hs_pop &= ~TWL4030_EXTMUTE;
+			twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+		}
+	}
+}
+
+static int headsetlpga_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec);
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		/* Do the ramp-up only once */
+		if (!twl4030->hsr_enabled)
+			headset_ramp(w->codec, 1);
+
+		twl4030->hsl_enabled = 1;
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		/* Do the ramp-down only if both headsetL/R is disabled */
+		if (!twl4030->hsr_enabled)
+			headset_ramp(w->codec, 0);
+
+		twl4030->hsl_enabled = 0;
+		break;
+	}
+	return 0;
+}
+
+static int headsetrpga_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec);
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		/* Do the ramp-up only once */
+		if (!twl4030->hsl_enabled)
+			headset_ramp(w->codec, 1);
+
+		twl4030->hsr_enabled = 1;
+		break;
+	case SND_SOC_DAPM_POST_PMD:
+		/* Do the ramp-down only if both headsetL/R is disabled */
+		if (!twl4030->hsl_enabled)
+			headset_ramp(w->codec, 0);
+
+		twl4030->hsr_enabled = 0;
+		break;
+	}
+	return 0;
+}
+
+static int digimic_event(struct snd_soc_dapm_widget *w,
+		struct snd_kcontrol *kcontrol, int event)
+{
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec);
+
+	if (twl4030->digimic_delay)
+		mdelay(twl4030->digimic_delay);
+	return 0;
+}
+
+/*
+ * Some of the gain controls in TWL (mostly those which are associated with
+ * the outputs) are implemented in an interesting way:
+ * 0x0 : Power down (mute)
+ * 0x1 : 6dB
+ * 0x2 : 0 dB
+ * 0x3 : -6 dB
+ * Inverting not going to help with these.
+ * Custom volsw and volsw_2r get/put functions to handle these gain bits.
+ */
+#define SOC_DOUBLE_TLV_TWL4030(xname, xreg, shift_left, shift_right, xmax,\
+			       xinvert, tlv_array) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
+	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+		 SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+	.tlv.p = (tlv_array), \
+	.info = snd_soc_info_volsw, \
+	.get = snd_soc_get_volsw_twl4030, \
+	.put = snd_soc_put_volsw_twl4030, \
+	.private_value = (unsigned long)&(struct soc_mixer_control) \
+		{.reg = xreg, .shift = shift_left, .rshift = shift_right,\
+		 .max = xmax, .invert = xinvert} }
+#define SOC_DOUBLE_R_TLV_TWL4030(xname, reg_left, reg_right, xshift, xmax,\
+				 xinvert, tlv_array) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
+	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+		 SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+	.tlv.p = (tlv_array), \
+	.info = snd_soc_info_volsw_2r, \
+	.get = snd_soc_get_volsw_r2_twl4030,\
+	.put = snd_soc_put_volsw_r2_twl4030, \
+	.private_value = (unsigned long)&(struct soc_mixer_control) \
+		{.reg = reg_left, .rreg = reg_right, .shift = xshift, \
+		 .rshift = xshift, .max = xmax, .invert = xinvert} }
+#define SOC_SINGLE_TLV_TWL4030(xname, xreg, xshift, xmax, xinvert, tlv_array) \
+	SOC_DOUBLE_TLV_TWL4030(xname, xreg, xshift, xshift, xmax, \
+			       xinvert, tlv_array)
+
+static int snd_soc_get_volsw_twl4030(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int shift = mc->shift;
+	unsigned int rshift = mc->rshift;
+	int max = mc->max;
+	int mask = (1 << fls(max)) - 1;
+
+	ucontrol->value.integer.value[0] =
+		(snd_soc_read(codec, reg) >> shift) & mask;
+	if (ucontrol->value.integer.value[0])
+		ucontrol->value.integer.value[0] =
+			max + 1 - ucontrol->value.integer.value[0];
+
+	if (shift != rshift) {
+		ucontrol->value.integer.value[1] =
+			(snd_soc_read(codec, reg) >> rshift) & mask;
+		if (ucontrol->value.integer.value[1])
+			ucontrol->value.integer.value[1] =
+				max + 1 - ucontrol->value.integer.value[1];
+	}
+
+	return 0;
+}
+
+static int snd_soc_put_volsw_twl4030(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int shift = mc->shift;
+	unsigned int rshift = mc->rshift;
+	int max = mc->max;
+	int mask = (1 << fls(max)) - 1;
+	unsigned short val, val2, val_mask;
+
+	val = (ucontrol->value.integer.value[0] & mask);
+
+	val_mask = mask << shift;
+	if (val)
+		val = max + 1 - val;
+	val = val << shift;
+	if (shift != rshift) {
+		val2 = (ucontrol->value.integer.value[1] & mask);
+		val_mask |= mask << rshift;
+		if (val2)
+			val2 = max + 1 - val2;
+		val |= val2 << rshift;
+	}
+	return snd_soc_update_bits(codec, reg, val_mask, val);
+}
+
+static int snd_soc_get_volsw_r2_twl4030(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int reg2 = mc->rreg;
+	unsigned int shift = mc->shift;
+	int max = mc->max;
+	int mask = (1<<fls(max))-1;
+
+	ucontrol->value.integer.value[0] =
+		(snd_soc_read(codec, reg) >> shift) & mask;
+	ucontrol->value.integer.value[1] =
+		(snd_soc_read(codec, reg2) >> shift) & mask;
+
+	if (ucontrol->value.integer.value[0])
+		ucontrol->value.integer.value[0] =
+			max + 1 - ucontrol->value.integer.value[0];
+	if (ucontrol->value.integer.value[1])
+		ucontrol->value.integer.value[1] =
+			max + 1 - ucontrol->value.integer.value[1];
+
+	return 0;
+}
+
+static int snd_soc_put_volsw_r2_twl4030(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int reg2 = mc->rreg;
+	unsigned int shift = mc->shift;
+	int max = mc->max;
+	int mask = (1 << fls(max)) - 1;
+	int err;
+	unsigned short val, val2, val_mask;
+
+	val_mask = mask << shift;
+	val = (ucontrol->value.integer.value[0] & mask);
+	val2 = (ucontrol->value.integer.value[1] & mask);
+
+	if (val)
+		val = max + 1 - val;
+	if (val2)
+		val2 = max + 1 - val2;
+
+	val = val << shift;
+	val2 = val2 << shift;
+
+	err = snd_soc_update_bits(codec, reg, val_mask, val);
+	if (err < 0)
+		return err;
+
+	err = snd_soc_update_bits(codec, reg2, val_mask, val2);
+	return err;
+}
+
+/* Codec operation modes */
+static const char *twl4030_op_modes_texts[] = {
+	"Option 2 (voice/audio)", "Option 1 (audio)"
+};
+
+static const struct soc_enum twl4030_op_modes_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_CODEC_MODE, 0,
+			ARRAY_SIZE(twl4030_op_modes_texts),
+			twl4030_op_modes_texts);
+
+static int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned short val;
+	unsigned short mask, bitmask;
+
+	if (twl4030->configured) {
+		printk(KERN_ERR "twl4030 operation mode cannot be "
+			"changed on-the-fly\n");
+		return -EBUSY;
+	}
+
+	for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+		;
+	if (ucontrol->value.enumerated.item[0] > e->max - 1)
+		return -EINVAL;
+
+	val = ucontrol->value.enumerated.item[0] << e->shift_l;
+	mask = (bitmask - 1) << e->shift_l;
+	if (e->shift_l != e->shift_r) {
+		if (ucontrol->value.enumerated.item[1] > e->max - 1)
+			return -EINVAL;
+		val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+		mask |= (bitmask - 1) << e->shift_r;
+	}
+
+	return snd_soc_update_bits(codec, e->reg, mask, val);
+}
+
+/*
+ * FGAIN volume control:
+ * from -62 to 0 dB in 1 dB steps (mute instead of -63 dB)
+ */
+static DECLARE_TLV_DB_SCALE(digital_fine_tlv, -6300, 100, 1);
+
+/*
+ * CGAIN volume control:
+ * 0 dB to 12 dB in 6 dB steps
+ * value 2 and 3 means 12 dB
+ */
+static DECLARE_TLV_DB_SCALE(digital_coarse_tlv, 0, 600, 0);
+
+/*
+ * Voice Downlink GAIN volume control:
+ * from -37 to 12 dB in 1 dB steps (mute instead of -37 dB)
+ */
+static DECLARE_TLV_DB_SCALE(digital_voice_downlink_tlv, -3700, 100, 1);
+
+/*
+ * Analog playback gain
+ * -24 dB to 12 dB in 2 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(analog_tlv, -2400, 200, 0);
+
+/*
+ * Gain controls tied to outputs
+ * -6 dB to 6 dB in 6 dB steps (mute instead of -12)
+ */
+static DECLARE_TLV_DB_SCALE(output_tvl, -1200, 600, 1);
+
+/*
+ * Gain control for earpiece amplifier
+ * 0 dB to 12 dB in 6 dB steps (mute instead of -6)
+ */
+static DECLARE_TLV_DB_SCALE(output_ear_tvl, -600, 600, 1);
+
+/*
+ * Capture gain after the ADCs
+ * from 0 dB to 31 dB in 1 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0);
+
+/*
+ * Gain control for input amplifiers
+ * 0 dB to 30 dB in 6 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0);
+
+/* AVADC clock priority */
+static const char *twl4030_avadc_clk_priority_texts[] = {
+	"Voice high priority", "HiFi high priority"
+};
+
+static const struct soc_enum twl4030_avadc_clk_priority_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_AVADC_CTL, 2,
+			ARRAY_SIZE(twl4030_avadc_clk_priority_texts),
+			twl4030_avadc_clk_priority_texts);
+
+static const char *twl4030_rampdelay_texts[] = {
+	"27/20/14 ms", "55/40/27 ms", "109/81/55 ms", "218/161/109 ms",
+	"437/323/218 ms", "874/645/437 ms", "1748/1291/874 ms",
+	"3495/2581/1748 ms"
+};
+
+static const struct soc_enum twl4030_rampdelay_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_HS_POPN_SET, 2,
+			ARRAY_SIZE(twl4030_rampdelay_texts),
+			twl4030_rampdelay_texts);
+
+/* Vibra H-bridge direction mode */
+static const char *twl4030_vibradirmode_texts[] = {
+	"Vibra H-bridge direction", "Audio data MSB",
+};
+
+static const struct soc_enum twl4030_vibradirmode_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 5,
+			ARRAY_SIZE(twl4030_vibradirmode_texts),
+			twl4030_vibradirmode_texts);
+
+/* Vibra H-bridge direction */
+static const char *twl4030_vibradir_texts[] = {
+	"Positive polarity", "Negative polarity",
+};
+
+static const struct soc_enum twl4030_vibradir_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 1,
+			ARRAY_SIZE(twl4030_vibradir_texts),
+			twl4030_vibradir_texts);
+
+/* Digimic Left and right swapping */
+static const char *twl4030_digimicswap_texts[] = {
+	"Not swapped", "Swapped",
+};
+
+static const struct soc_enum twl4030_digimicswap_enum =
+	SOC_ENUM_SINGLE(TWL4030_REG_MISC_SET_1, 0,
+			ARRAY_SIZE(twl4030_digimicswap_texts),
+			twl4030_digimicswap_texts);
+
+static const struct snd_kcontrol_new twl4030_snd_controls[] = {
+	/* Codec operation mode control */
+	SOC_ENUM_EXT("Codec Operation Mode", twl4030_op_modes_enum,
+		snd_soc_get_enum_double,
+		snd_soc_put_twl4030_opmode_enum_double),
+
+	/* Common playback gain controls */
+	SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume",
+		TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA,
+		0, 0x3f, 0, digital_fine_tlv),
+	SOC_DOUBLE_R_TLV("DAC2 Digital Fine Playback Volume",
+		TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA,
+		0, 0x3f, 0, digital_fine_tlv),
+
+	SOC_DOUBLE_R_TLV("DAC1 Digital Coarse Playback Volume",
+		TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA,
+		6, 0x2, 0, digital_coarse_tlv),
+	SOC_DOUBLE_R_TLV("DAC2 Digital Coarse Playback Volume",
+		TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA,
+		6, 0x2, 0, digital_coarse_tlv),
+
+	SOC_DOUBLE_R_TLV("DAC1 Analog Playback Volume",
+		TWL4030_REG_ARXL1_APGA_CTL, TWL4030_REG_ARXR1_APGA_CTL,
+		3, 0x12, 1, analog_tlv),
+	SOC_DOUBLE_R_TLV("DAC2 Analog Playback Volume",
+		TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL,
+		3, 0x12, 1, analog_tlv),
+	SOC_DOUBLE_R("DAC1 Analog Playback Switch",
+		TWL4030_REG_ARXL1_APGA_CTL, TWL4030_REG_ARXR1_APGA_CTL,
+		1, 1, 0),
+	SOC_DOUBLE_R("DAC2 Analog Playback Switch",
+		TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL,
+		1, 1, 0),
+
+	/* Common voice downlink gain controls */
+	SOC_SINGLE_TLV("DAC Voice Digital Downlink Volume",
+		TWL4030_REG_VRXPGA, 0, 0x31, 0, digital_voice_downlink_tlv),
+
+	SOC_SINGLE_TLV("DAC Voice Analog Downlink Volume",
+		TWL4030_REG_VDL_APGA_CTL, 3, 0x12, 1, analog_tlv),
+
+	SOC_SINGLE("DAC Voice Analog Downlink Switch",
+		TWL4030_REG_VDL_APGA_CTL, 1, 1, 0),
+
+	/* Separate output gain controls */
+	SOC_DOUBLE_R_TLV_TWL4030("PreDriv Playback Volume",
+		TWL4030_REG_PREDL_CTL, TWL4030_REG_PREDR_CTL,
+		4, 3, 0, output_tvl),
+
+	SOC_DOUBLE_TLV_TWL4030("Headset Playback Volume",
+		TWL4030_REG_HS_GAIN_SET, 0, 2, 3, 0, output_tvl),
+
+	SOC_DOUBLE_R_TLV_TWL4030("Carkit Playback Volume",
+		TWL4030_REG_PRECKL_CTL, TWL4030_REG_PRECKR_CTL,
+		4, 3, 0, output_tvl),
+
+	SOC_SINGLE_TLV_TWL4030("Earpiece Playback Volume",
+		TWL4030_REG_EAR_CTL, 4, 3, 0, output_ear_tvl),
+
+	/* Common capture gain controls */
+	SOC_DOUBLE_R_TLV("TX1 Digital Capture Volume",
+		TWL4030_REG_ATXL1PGA, TWL4030_REG_ATXR1PGA,
+		0, 0x1f, 0, digital_capture_tlv),
+	SOC_DOUBLE_R_TLV("TX2 Digital Capture Volume",
+		TWL4030_REG_AVTXL2PGA, TWL4030_REG_AVTXR2PGA,
+		0, 0x1f, 0, digital_capture_tlv),
+
+	SOC_DOUBLE_TLV("Analog Capture Volume", TWL4030_REG_ANAMIC_GAIN,
+		0, 3, 5, 0, input_gain_tlv),
+
+	SOC_ENUM("AVADC Clock Priority", twl4030_avadc_clk_priority_enum),
+
+	SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum),
+
+	SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum),
+	SOC_ENUM("Vibra H-bridge direction", twl4030_vibradir_enum),
+
+	SOC_ENUM("Digimic LR Swap", twl4030_digimicswap_enum),
+};
+
+static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
+	/* Left channel inputs */
+	SND_SOC_DAPM_INPUT("MAINMIC"),
+	SND_SOC_DAPM_INPUT("HSMIC"),
+	SND_SOC_DAPM_INPUT("AUXL"),
+	SND_SOC_DAPM_INPUT("CARKITMIC"),
+	/* Right channel inputs */
+	SND_SOC_DAPM_INPUT("SUBMIC"),
+	SND_SOC_DAPM_INPUT("AUXR"),
+	/* Digital microphones (Stereo) */
+	SND_SOC_DAPM_INPUT("DIGIMIC0"),
+	SND_SOC_DAPM_INPUT("DIGIMIC1"),
+
+	/* Outputs */
+	SND_SOC_DAPM_OUTPUT("EARPIECE"),
+	SND_SOC_DAPM_OUTPUT("PREDRIVEL"),
+	SND_SOC_DAPM_OUTPUT("PREDRIVER"),
+	SND_SOC_DAPM_OUTPUT("HSOL"),
+	SND_SOC_DAPM_OUTPUT("HSOR"),
+	SND_SOC_DAPM_OUTPUT("CARKITL"),
+	SND_SOC_DAPM_OUTPUT("CARKITR"),
+	SND_SOC_DAPM_OUTPUT("HFL"),
+	SND_SOC_DAPM_OUTPUT("HFR"),
+	SND_SOC_DAPM_OUTPUT("VIBRA"),
+
+	/* AIF and APLL clocks for running DAIs (including loopback) */
+	SND_SOC_DAPM_OUTPUT("Virtual HiFi OUT"),
+	SND_SOC_DAPM_INPUT("Virtual HiFi IN"),
+	SND_SOC_DAPM_OUTPUT("Virtual Voice OUT"),
+
+	/* DACs */
+	SND_SOC_DAPM_DAC("DAC Right1", "Right Front HiFi Playback",
+			SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("DAC Left1", "Left Front HiFi Playback",
+			SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("DAC Right2", "Right Rear HiFi Playback",
+			SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("DAC Left2", "Left Rear HiFi Playback",
+			SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("DAC Voice", "Voice Playback",
+			SND_SOC_NOPM, 0, 0),
+
+	/* Analog bypasses */
+	SND_SOC_DAPM_SWITCH("Right1 Analog Loopback", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_abypassr1_control),
+	SND_SOC_DAPM_SWITCH("Left1 Analog Loopback", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_abypassl1_control),
+	SND_SOC_DAPM_SWITCH("Right2 Analog Loopback", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_abypassr2_control),
+	SND_SOC_DAPM_SWITCH("Left2 Analog Loopback", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_abypassl2_control),
+	SND_SOC_DAPM_SWITCH("Voice Analog Loopback", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_abypassv_control),
+
+	/* Master analog loopback switch */
+	SND_SOC_DAPM_SUPPLY("FM Loop Enable", TWL4030_REG_MISC_SET_1, 5, 0,
+			    NULL, 0),
+
+	/* Digital bypasses */
+	SND_SOC_DAPM_SWITCH("Left Digital Loopback", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_dbypassl_control),
+	SND_SOC_DAPM_SWITCH("Right Digital Loopback", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_dbypassr_control),
+	SND_SOC_DAPM_SWITCH("Voice Digital Loopback", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_dbypassv_control),
+
+	/* Digital mixers, power control for the physical DACs */
+	SND_SOC_DAPM_MIXER("Digital R1 Playback Mixer",
+			TWL4030_REG_AVDAC_CTL, 0, 0, NULL, 0),
+	SND_SOC_DAPM_MIXER("Digital L1 Playback Mixer",
+			TWL4030_REG_AVDAC_CTL, 1, 0, NULL, 0),
+	SND_SOC_DAPM_MIXER("Digital R2 Playback Mixer",
+			TWL4030_REG_AVDAC_CTL, 2, 0, NULL, 0),
+	SND_SOC_DAPM_MIXER("Digital L2 Playback Mixer",
+			TWL4030_REG_AVDAC_CTL, 3, 0, NULL, 0),
+	SND_SOC_DAPM_MIXER("Digital Voice Playback Mixer",
+			TWL4030_REG_AVDAC_CTL, 4, 0, NULL, 0),
+
+	/* Analog mixers, power control for the physical PGAs */
+	SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer",
+			TWL4030_REG_ARXR1_APGA_CTL, 0, 0, NULL, 0),
+	SND_SOC_DAPM_MIXER("Analog L1 Playback Mixer",
+			TWL4030_REG_ARXL1_APGA_CTL, 0, 0, NULL, 0),
+	SND_SOC_DAPM_MIXER("Analog R2 Playback Mixer",
+			TWL4030_REG_ARXR2_APGA_CTL, 0, 0, NULL, 0),
+	SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer",
+			TWL4030_REG_ARXL2_APGA_CTL, 0, 0, NULL, 0),
+	SND_SOC_DAPM_MIXER("Analog Voice Playback Mixer",
+			TWL4030_REG_VDL_APGA_CTL, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_SUPPLY("APLL Enable", SND_SOC_NOPM, 0, 0, apll_event,
+			    SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD),
+
+	SND_SOC_DAPM_SUPPLY("AIF Enable", SND_SOC_NOPM, 0, 0, aif_event,
+			    SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD),
+
+	/* Output MIXER controls */
+	/* Earpiece */
+	SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_earpiece_controls[0],
+			ARRAY_SIZE(twl4030_dapm_earpiece_controls)),
+	SND_SOC_DAPM_PGA_E("Earpiece PGA", SND_SOC_NOPM,
+			0, 0, NULL, 0, earpiecepga_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+	/* PreDrivL/R */
+	SND_SOC_DAPM_MIXER("PredriveL Mixer", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_predrivel_controls[0],
+			ARRAY_SIZE(twl4030_dapm_predrivel_controls)),
+	SND_SOC_DAPM_PGA_E("PredriveL PGA", SND_SOC_NOPM,
+			0, 0, NULL, 0, predrivelpga_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_MIXER("PredriveR Mixer", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_predriver_controls[0],
+			ARRAY_SIZE(twl4030_dapm_predriver_controls)),
+	SND_SOC_DAPM_PGA_E("PredriveR PGA", SND_SOC_NOPM,
+			0, 0, NULL, 0, predriverpga_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+	/* HeadsetL/R */
+	SND_SOC_DAPM_MIXER("HeadsetL Mixer", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_hsol_controls[0],
+			ARRAY_SIZE(twl4030_dapm_hsol_controls)),
+	SND_SOC_DAPM_PGA_E("HeadsetL PGA", SND_SOC_NOPM,
+			0, 0, NULL, 0, headsetlpga_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_MIXER("HeadsetR Mixer", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_hsor_controls[0],
+			ARRAY_SIZE(twl4030_dapm_hsor_controls)),
+	SND_SOC_DAPM_PGA_E("HeadsetR PGA", SND_SOC_NOPM,
+			0, 0, NULL, 0, headsetrpga_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+	/* CarkitL/R */
+	SND_SOC_DAPM_MIXER("CarkitL Mixer", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_carkitl_controls[0],
+			ARRAY_SIZE(twl4030_dapm_carkitl_controls)),
+	SND_SOC_DAPM_PGA_E("CarkitL PGA", SND_SOC_NOPM,
+			0, 0, NULL, 0, carkitlpga_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_MIXER("CarkitR Mixer", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_carkitr_controls[0],
+			ARRAY_SIZE(twl4030_dapm_carkitr_controls)),
+	SND_SOC_DAPM_PGA_E("CarkitR PGA", SND_SOC_NOPM,
+			0, 0, NULL, 0, carkitrpga_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+
+	/* Output MUX controls */
+	/* HandsfreeL/R */
+	SND_SOC_DAPM_MUX("HandsfreeL Mux", SND_SOC_NOPM, 0, 0,
+		&twl4030_dapm_handsfreel_control),
+	SND_SOC_DAPM_SWITCH("HandsfreeL", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_handsfreelmute_control),
+	SND_SOC_DAPM_PGA_E("HandsfreeL PGA", SND_SOC_NOPM,
+			0, 0, NULL, 0, handsfreelpga_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_MUX("HandsfreeR Mux", SND_SOC_NOPM, 5, 0,
+		&twl4030_dapm_handsfreer_control),
+	SND_SOC_DAPM_SWITCH("HandsfreeR", SND_SOC_NOPM, 0, 0,
+			&twl4030_dapm_handsfreermute_control),
+	SND_SOC_DAPM_PGA_E("HandsfreeR PGA", SND_SOC_NOPM,
+			0, 0, NULL, 0, handsfreerpga_event,
+			SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+	/* Vibra */
+	SND_SOC_DAPM_MUX_E("Vibra Mux", TWL4030_REG_VIBRA_CTL, 0, 0,
+			   &twl4030_dapm_vibra_control, vibramux_event,
+			   SND_SOC_DAPM_PRE_PMU),
+	SND_SOC_DAPM_MUX("Vibra Route", SND_SOC_NOPM, 0, 0,
+		&twl4030_dapm_vibrapath_control),
+
+	/* Introducing four virtual ADC, since TWL4030 have four channel for
+	   capture */
+	SND_SOC_DAPM_ADC("ADC Virtual Left1", "Left Front Capture",
+		SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_ADC("ADC Virtual Right1", "Right Front Capture",
+		SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_ADC("ADC Virtual Left2", "Left Rear Capture",
+		SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_ADC("ADC Virtual Right2", "Right Rear Capture",
+		SND_SOC_NOPM, 0, 0),
+
+	/* Analog/Digital mic path selection.
+	   TX1 Left/Right: either analog Left/Right or Digimic0
+	   TX2 Left/Right: either analog Left/Right or Digimic1 */
+	SND_SOC_DAPM_MUX("TX1 Capture Route", SND_SOC_NOPM, 0, 0,
+		&twl4030_dapm_micpathtx1_control),
+	SND_SOC_DAPM_MUX("TX2 Capture Route", SND_SOC_NOPM, 0, 0,
+		&twl4030_dapm_micpathtx2_control),
+
+	/* Analog input mixers for the capture amplifiers */
+	SND_SOC_DAPM_MIXER("Analog Left",
+		TWL4030_REG_ANAMICL, 4, 0,
+		&twl4030_dapm_analoglmic_controls[0],
+		ARRAY_SIZE(twl4030_dapm_analoglmic_controls)),
+	SND_SOC_DAPM_MIXER("Analog Right",
+		TWL4030_REG_ANAMICR, 4, 0,
+		&twl4030_dapm_analogrmic_controls[0],
+		ARRAY_SIZE(twl4030_dapm_analogrmic_controls)),
+
+	SND_SOC_DAPM_PGA("ADC Physical Left",
+		TWL4030_REG_AVADC_CTL, 3, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("ADC Physical Right",
+		TWL4030_REG_AVADC_CTL, 1, 0, NULL, 0),
+
+	SND_SOC_DAPM_PGA_E("Digimic0 Enable",
+		TWL4030_REG_ADCMICSEL, 1, 0, NULL, 0,
+		digimic_event, SND_SOC_DAPM_POST_PMU),
+	SND_SOC_DAPM_PGA_E("Digimic1 Enable",
+		TWL4030_REG_ADCMICSEL, 3, 0, NULL, 0,
+		digimic_event, SND_SOC_DAPM_POST_PMU),
+
+	SND_SOC_DAPM_SUPPLY("micbias1 select", TWL4030_REG_MICBIAS_CTL, 5, 0,
+			    NULL, 0),
+	SND_SOC_DAPM_SUPPLY("micbias2 select", TWL4030_REG_MICBIAS_CTL, 6, 0,
+			    NULL, 0),
+
+	SND_SOC_DAPM_MICBIAS("Mic Bias 1", TWL4030_REG_MICBIAS_CTL, 0, 0),
+	SND_SOC_DAPM_MICBIAS("Mic Bias 2", TWL4030_REG_MICBIAS_CTL, 1, 0),
+	SND_SOC_DAPM_MICBIAS("Headset Mic Bias", TWL4030_REG_MICBIAS_CTL, 2, 0),
+
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+	{"Digital L1 Playback Mixer", NULL, "DAC Left1"},
+	{"Digital R1 Playback Mixer", NULL, "DAC Right1"},
+	{"Digital L2 Playback Mixer", NULL, "DAC Left2"},
+	{"Digital R2 Playback Mixer", NULL, "DAC Right2"},
+	{"Digital Voice Playback Mixer", NULL, "DAC Voice"},
+
+	/* Supply for the digital part (APLL) */
+	{"Digital Voice Playback Mixer", NULL, "APLL Enable"},
+
+	{"DAC Left1", NULL, "AIF Enable"},
+	{"DAC Right1", NULL, "AIF Enable"},
+	{"DAC Left2", NULL, "AIF Enable"},
+	{"DAC Right1", NULL, "AIF Enable"},
+
+	{"Digital R2 Playback Mixer", NULL, "AIF Enable"},
+	{"Digital L2 Playback Mixer", NULL, "AIF Enable"},
+
+	{"Analog L1 Playback Mixer", NULL, "Digital L1 Playback Mixer"},
+	{"Analog R1 Playback Mixer", NULL, "Digital R1 Playback Mixer"},
+	{"Analog L2 Playback Mixer", NULL, "Digital L2 Playback Mixer"},
+	{"Analog R2 Playback Mixer", NULL, "Digital R2 Playback Mixer"},
+	{"Analog Voice Playback Mixer", NULL, "Digital Voice Playback Mixer"},
+
+	/* Internal playback routings */
+	/* Earpiece */
+	{"Earpiece Mixer", "Voice", "Analog Voice Playback Mixer"},
+	{"Earpiece Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+	{"Earpiece Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+	{"Earpiece Mixer", "AudioR1", "Analog R1 Playback Mixer"},
+	{"Earpiece PGA", NULL, "Earpiece Mixer"},
+	/* PreDrivL */
+	{"PredriveL Mixer", "Voice", "Analog Voice Playback Mixer"},
+	{"PredriveL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+	{"PredriveL Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+	{"PredriveL Mixer", "AudioR2", "Analog R2 Playback Mixer"},
+	{"PredriveL PGA", NULL, "PredriveL Mixer"},
+	/* PreDrivR */
+	{"PredriveR Mixer", "Voice", "Analog Voice Playback Mixer"},
+	{"PredriveR Mixer", "AudioR1", "Analog R1 Playback Mixer"},
+	{"PredriveR Mixer", "AudioR2", "Analog R2 Playback Mixer"},
+	{"PredriveR Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+	{"PredriveR PGA", NULL, "PredriveR Mixer"},
+	/* HeadsetL */
+	{"HeadsetL Mixer", "Voice", "Analog Voice Playback Mixer"},
+	{"HeadsetL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+	{"HeadsetL Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+	{"HeadsetL PGA", NULL, "HeadsetL Mixer"},
+	/* HeadsetR */
+	{"HeadsetR Mixer", "Voice", "Analog Voice Playback Mixer"},
+	{"HeadsetR Mixer", "AudioR1", "Analog R1 Playback Mixer"},
+	{"HeadsetR Mixer", "AudioR2", "Analog R2 Playback Mixer"},
+	{"HeadsetR PGA", NULL, "HeadsetR Mixer"},
+	/* CarkitL */
+	{"CarkitL Mixer", "Voice", "Analog Voice Playback Mixer"},
+	{"CarkitL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
+	{"CarkitL Mixer", "AudioL2", "Analog L2 Playback Mixer"},
+	{"CarkitL PGA", NULL, "CarkitL Mixer"},
+	/* CarkitR */
+	{"CarkitR Mixer", "Voice", "Analog Voice Playback Mixer"},
+	{"CarkitR Mixer", "AudioR1", "Analog R1 Playback Mixer"},
+	{"CarkitR Mixer", "AudioR2", "Analog R2 Playback Mixer"},
+	{"CarkitR PGA", NULL, "CarkitR Mixer"},
+	/* HandsfreeL */
+	{"HandsfreeL Mux", "Voice", "Analog Voice Playback Mixer"},
+	{"HandsfreeL Mux", "AudioL1", "Analog L1 Playback Mixer"},
+	{"HandsfreeL Mux", "AudioL2", "Analog L2 Playback Mixer"},
+	{"HandsfreeL Mux", "AudioR2", "Analog R2 Playback Mixer"},
+	{"HandsfreeL", "Switch", "HandsfreeL Mux"},
+	{"HandsfreeL PGA", NULL, "HandsfreeL"},
+	/* HandsfreeR */
+	{"HandsfreeR Mux", "Voice", "Analog Voice Playback Mixer"},
+	{"HandsfreeR Mux", "AudioR1", "Analog R1 Playback Mixer"},
+	{"HandsfreeR Mux", "AudioR2", "Analog R2 Playback Mixer"},
+	{"HandsfreeR Mux", "AudioL2", "Analog L2 Playback Mixer"},
+	{"HandsfreeR", "Switch", "HandsfreeR Mux"},
+	{"HandsfreeR PGA", NULL, "HandsfreeR"},
+	/* Vibra */
+	{"Vibra Mux", "AudioL1", "DAC Left1"},
+	{"Vibra Mux", "AudioR1", "DAC Right1"},
+	{"Vibra Mux", "AudioL2", "DAC Left2"},
+	{"Vibra Mux", "AudioR2", "DAC Right2"},
+
+	/* outputs */
+	/* Must be always connected (for AIF and APLL) */
+	{"Virtual HiFi OUT", NULL, "DAC Left1"},
+	{"Virtual HiFi OUT", NULL, "DAC Right1"},
+	{"Virtual HiFi OUT", NULL, "DAC Left2"},
+	{"Virtual HiFi OUT", NULL, "DAC Right2"},
+	/* Must be always connected (for APLL) */
+	{"Virtual Voice OUT", NULL, "Digital Voice Playback Mixer"},
+	/* Physical outputs */
+	{"EARPIECE", NULL, "Earpiece PGA"},
+	{"PREDRIVEL", NULL, "PredriveL PGA"},
+	{"PREDRIVER", NULL, "PredriveR PGA"},
+	{"HSOL", NULL, "HeadsetL PGA"},
+	{"HSOR", NULL, "HeadsetR PGA"},
+	{"CARKITL", NULL, "CarkitL PGA"},
+	{"CARKITR", NULL, "CarkitR PGA"},
+	{"HFL", NULL, "HandsfreeL PGA"},
+	{"HFR", NULL, "HandsfreeR PGA"},
+	{"Vibra Route", "Audio", "Vibra Mux"},
+	{"VIBRA", NULL, "Vibra Route"},
+
+	/* Capture path */
+	/* Must be always connected (for AIF and APLL) */
+	{"ADC Virtual Left1", NULL, "Virtual HiFi IN"},
+	{"ADC Virtual Right1", NULL, "Virtual HiFi IN"},
+	{"ADC Virtual Left2", NULL, "Virtual HiFi IN"},
+	{"ADC Virtual Right2", NULL, "Virtual HiFi IN"},
+	/* Physical inputs */
+	{"Analog Left", "Main Mic Capture Switch", "MAINMIC"},
+	{"Analog Left", "Headset Mic Capture Switch", "HSMIC"},
+	{"Analog Left", "AUXL Capture Switch", "AUXL"},
+	{"Analog Left", "Carkit Mic Capture Switch", "CARKITMIC"},
+
+	{"Analog Right", "Sub Mic Capture Switch", "SUBMIC"},
+	{"Analog Right", "AUXR Capture Switch", "AUXR"},
+
+	{"ADC Physical Left", NULL, "Analog Left"},
+	{"ADC Physical Right", NULL, "Analog Right"},
+
+	{"Digimic0 Enable", NULL, "DIGIMIC0"},
+	{"Digimic1 Enable", NULL, "DIGIMIC1"},
+
+	{"DIGIMIC0", NULL, "micbias1 select"},
+	{"DIGIMIC1", NULL, "micbias2 select"},
+
+	/* TX1 Left capture path */
+	{"TX1 Capture Route", "Analog", "ADC Physical Left"},
+	{"TX1 Capture Route", "Digimic0", "Digimic0 Enable"},
+	/* TX1 Right capture path */
+	{"TX1 Capture Route", "Analog", "ADC Physical Right"},
+	{"TX1 Capture Route", "Digimic0", "Digimic0 Enable"},
+	/* TX2 Left capture path */
+	{"TX2 Capture Route", "Analog", "ADC Physical Left"},
+	{"TX2 Capture Route", "Digimic1", "Digimic1 Enable"},
+	/* TX2 Right capture path */
+	{"TX2 Capture Route", "Analog", "ADC Physical Right"},
+	{"TX2 Capture Route", "Digimic1", "Digimic1 Enable"},
+
+	{"ADC Virtual Left1", NULL, "TX1 Capture Route"},
+	{"ADC Virtual Right1", NULL, "TX1 Capture Route"},
+	{"ADC Virtual Left2", NULL, "TX2 Capture Route"},
+	{"ADC Virtual Right2", NULL, "TX2 Capture Route"},
+
+	{"ADC Virtual Left1", NULL, "AIF Enable"},
+	{"ADC Virtual Right1", NULL, "AIF Enable"},
+	{"ADC Virtual Left2", NULL, "AIF Enable"},
+	{"ADC Virtual Right2", NULL, "AIF Enable"},
+
+	/* Analog bypass routes */
+	{"Right1 Analog Loopback", "Switch", "Analog Right"},
+	{"Left1 Analog Loopback", "Switch", "Analog Left"},
+	{"Right2 Analog Loopback", "Switch", "Analog Right"},
+	{"Left2 Analog Loopback", "Switch", "Analog Left"},
+	{"Voice Analog Loopback", "Switch", "Analog Left"},
+
+	/* Supply for the Analog loopbacks */
+	{"Right1 Analog Loopback", NULL, "FM Loop Enable"},
+	{"Left1 Analog Loopback", NULL, "FM Loop Enable"},
+	{"Right2 Analog Loopback", NULL, "FM Loop Enable"},
+	{"Left2 Analog Loopback", NULL, "FM Loop Enable"},
+	{"Voice Analog Loopback", NULL, "FM Loop Enable"},
+
+	{"Analog R1 Playback Mixer", NULL, "Right1 Analog Loopback"},
+	{"Analog L1 Playback Mixer", NULL, "Left1 Analog Loopback"},
+	{"Analog R2 Playback Mixer", NULL, "Right2 Analog Loopback"},
+	{"Analog L2 Playback Mixer", NULL, "Left2 Analog Loopback"},
+	{"Analog Voice Playback Mixer", NULL, "Voice Analog Loopback"},
+
+	/* Digital bypass routes */
+	{"Right Digital Loopback", "Volume", "TX1 Capture Route"},
+	{"Left Digital Loopback", "Volume", "TX1 Capture Route"},
+	{"Voice Digital Loopback", "Volume", "TX2 Capture Route"},
+
+	{"Digital R2 Playback Mixer", NULL, "Right Digital Loopback"},
+	{"Digital L2 Playback Mixer", NULL, "Left Digital Loopback"},
+	{"Digital Voice Playback Mixer", NULL, "Voice Digital Loopback"},
+
+};
+
+static int twl4030_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, twl4030_dapm_widgets,
+				 ARRAY_SIZE(twl4030_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	return 0;
+}
+
+static int twl4030_set_bias_level(struct snd_soc_codec *codec,
+				  enum snd_soc_bias_level level)
+{
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF)
+			twl4030_codec_enable(codec, 1);
+		break;
+	case SND_SOC_BIAS_OFF:
+		twl4030_codec_enable(codec, 0);
+		break;
+	}
+	codec->bias_level = level;
+
+	return 0;
+}
+
+static void twl4030_constraints(struct twl4030_priv *twl4030,
+				struct snd_pcm_substream *mst_substream)
+{
+	struct snd_pcm_substream *slv_substream;
+
+	/* Pick the stream, which need to be constrained */
+	if (mst_substream == twl4030->master_substream)
+		slv_substream = twl4030->slave_substream;
+	else if (mst_substream == twl4030->slave_substream)
+		slv_substream = twl4030->master_substream;
+	else /* This should not happen.. */
+		return;
+
+	/* Set the constraints according to the already configured stream */
+	snd_pcm_hw_constraint_minmax(slv_substream->runtime,
+				SNDRV_PCM_HW_PARAM_RATE,
+				twl4030->rate,
+				twl4030->rate);
+
+	snd_pcm_hw_constraint_minmax(slv_substream->runtime,
+				SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+				twl4030->sample_bits,
+				twl4030->sample_bits);
+
+	snd_pcm_hw_constraint_minmax(slv_substream->runtime,
+				SNDRV_PCM_HW_PARAM_CHANNELS,
+				twl4030->channels,
+				twl4030->channels);
+}
+
+/* In case of 4 channel mode, the RX1 L/R for playback and the TX2 L/R for
+ * capture has to be enabled/disabled. */
+static void twl4030_tdm_enable(struct snd_soc_codec *codec, int direction,
+				int enable)
+{
+	u8 reg, mask;
+
+	reg = twl4030_read_reg_cache(codec, TWL4030_REG_OPTION);
+
+	if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+		mask = TWL4030_ARXL1_VRX_EN | TWL4030_ARXR1_EN;
+	else
+		mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN;
+
+	if (enable)
+		reg |= mask;
+	else
+		reg &= ~mask;
+
+	twl4030_write(codec, TWL4030_REG_OPTION, reg);
+}
+
+static int twl4030_startup(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+
+	if (twl4030->master_substream) {
+		twl4030->slave_substream = substream;
+		/* The DAI has one configuration for playback and capture, so
+		 * if the DAI has been already configured then constrain this
+		 * substream to match it. */
+		if (twl4030->configured)
+			twl4030_constraints(twl4030, twl4030->master_substream);
+	} else {
+		if (!(twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) &
+			TWL4030_OPTION_1)) {
+			/* In option2 4 channel is not supported, set the
+			 * constraint for the first stream for channels, the
+			 * second stream will 'inherit' this cosntraint */
+			snd_pcm_hw_constraint_minmax(substream->runtime,
+						SNDRV_PCM_HW_PARAM_CHANNELS,
+						2, 2);
+		}
+		twl4030->master_substream = substream;
+	}
+
+	return 0;
+}
+
+static void twl4030_shutdown(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+
+	if (twl4030->master_substream == substream)
+		twl4030->master_substream = twl4030->slave_substream;
+
+	twl4030->slave_substream = NULL;
+
+	/* If all streams are closed, or the remaining stream has not yet
+	 * been configured than set the DAI as not configured. */
+	if (!twl4030->master_substream)
+		twl4030->configured = 0;
+	 else if (!twl4030->master_substream->runtime->channels)
+		twl4030->configured = 0;
+
+	 /* If the closing substream had 4 channel, do the necessary cleanup */
+	if (substream->runtime->channels == 4)
+		twl4030_tdm_enable(codec, substream->stream, 0);
+}
+
+static int twl4030_hw_params(struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *params,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+	u8 mode, old_mode, format, old_format;
+
+	 /* If the substream has 4 channel, do the necessary setup */
+	if (params_channels(params) == 4) {
+		format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+		mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE);
+
+		/* Safety check: are we in the correct operating mode and
+		 * the interface is in TDM mode? */
+		if ((mode & TWL4030_OPTION_1) &&
+		    ((format & TWL4030_AIF_FORMAT) == TWL4030_AIF_FORMAT_TDM))
+			twl4030_tdm_enable(codec, substream->stream, 1);
+		else
+			return -EINVAL;
+	}
+
+	if (twl4030->configured)
+		/* Ignoring hw_params for already configured DAI */
+		return 0;
+
+	/* bit rate */
+	old_mode = twl4030_read_reg_cache(codec,
+			TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ;
+	mode = old_mode & ~TWL4030_APLL_RATE;
+
+	switch (params_rate(params)) {
+	case 8000:
+		mode |= TWL4030_APLL_RATE_8000;
+		break;
+	case 11025:
+		mode |= TWL4030_APLL_RATE_11025;
+		break;
+	case 12000:
+		mode |= TWL4030_APLL_RATE_12000;
+		break;
+	case 16000:
+		mode |= TWL4030_APLL_RATE_16000;
+		break;
+	case 22050:
+		mode |= TWL4030_APLL_RATE_22050;
+		break;
+	case 24000:
+		mode |= TWL4030_APLL_RATE_24000;
+		break;
+	case 32000:
+		mode |= TWL4030_APLL_RATE_32000;
+		break;
+	case 44100:
+		mode |= TWL4030_APLL_RATE_44100;
+		break;
+	case 48000:
+		mode |= TWL4030_APLL_RATE_48000;
+		break;
+	case 96000:
+		mode |= TWL4030_APLL_RATE_96000;
+		break;
+	default:
+		printk(KERN_ERR "TWL4030 hw params: unknown rate %d\n",
+			params_rate(params));
+		return -EINVAL;
+	}
+
+	/* sample size */
+	old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+	format = old_format;
+	format &= ~TWL4030_DATA_WIDTH;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		format |= TWL4030_DATA_WIDTH_16S_16W;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		format |= TWL4030_DATA_WIDTH_32S_24W;
+		break;
+	default:
+		printk(KERN_ERR "TWL4030 hw params: unknown format %d\n",
+			params_format(params));
+		return -EINVAL;
+	}
+
+	if (format != old_format || mode != old_mode) {
+		if (twl4030->codec_powered) {
+			/*
+			 * If the codec is powered, than we need to toggle the
+			 * codec power.
+			 */
+			twl4030_codec_enable(codec, 0);
+			twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+			twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+			twl4030_codec_enable(codec, 1);
+		} else {
+			twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+			twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+		}
+	}
+
+	/* Store the important parameters for the DAI configuration and set
+	 * the DAI as configured */
+	twl4030->configured = 1;
+	twl4030->rate = params_rate(params);
+	twl4030->sample_bits = hw_param_interval(params,
+					SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
+	twl4030->channels = params_channels(params);
+
+	/* If both playback and capture streams are open, and one of them
+	 * is setting the hw parameters right now (since we are here), set
+	 * constraints to the other stream to match the current one. */
+	if (twl4030->slave_substream)
+		twl4030_constraints(twl4030, substream);
+
+	return 0;
+}
+
+static int twl4030_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+
+	switch (freq) {
+	case 19200000:
+	case 26000000:
+	case 38400000:
+		break;
+	default:
+		dev_err(codec->dev, "Unsupported APLL mclk: %u\n", freq);
+		return -EINVAL;
+	}
+
+	if ((freq / 1000) != twl4030->sysclk) {
+		dev_err(codec->dev,
+			"Mismatch in APLL mclk: %u (configured: %u)\n",
+			freq, twl4030->sysclk * 1000);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			     unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+	u8 old_format, format;
+
+	/* get format */
+	old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+	format = old_format;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		format &= ~(TWL4030_AIF_SLAVE_EN);
+		format &= ~(TWL4030_CLK256FS_EN);
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		format |= TWL4030_AIF_SLAVE_EN;
+		format |= TWL4030_CLK256FS_EN;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	format &= ~TWL4030_AIF_FORMAT;
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		format |= TWL4030_AIF_FORMAT_CODEC;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		format |= TWL4030_AIF_FORMAT_TDM;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (format != old_format) {
+		if (twl4030->codec_powered) {
+			/*
+			 * If the codec is powered, than we need to toggle the
+			 * codec power.
+			 */
+			twl4030_codec_enable(codec, 0);
+			twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+			twl4030_codec_enable(codec, 1);
+		} else {
+			twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+		}
+	}
+
+	return 0;
+}
+
+static int twl4030_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
+
+	if (tristate)
+		reg |= TWL4030_AIF_TRI_EN;
+	else
+		reg &= ~TWL4030_AIF_TRI_EN;
+
+	return twl4030_write(codec, TWL4030_REG_AUDIO_IF, reg);
+}
+
+/* In case of voice mode, the RX1 L(VRX) for downlink and the TX2 L/R
+ * (VTXL, VTXR) for uplink has to be enabled/disabled. */
+static void twl4030_voice_enable(struct snd_soc_codec *codec, int direction,
+				int enable)
+{
+	u8 reg, mask;
+
+	reg = twl4030_read_reg_cache(codec, TWL4030_REG_OPTION);
+
+	if (direction == SNDRV_PCM_STREAM_PLAYBACK)
+		mask = TWL4030_ARXL1_VRX_EN;
+	else
+		mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN;
+
+	if (enable)
+		reg |= mask;
+	else
+		reg &= ~mask;
+
+	twl4030_write(codec, TWL4030_REG_OPTION, reg);
+}
+
+static int twl4030_voice_startup(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+	u8 mode;
+
+	/* If the system master clock is not 26MHz, the voice PCM interface is
+	 * not avilable.
+	 */
+	if (twl4030->sysclk != 26000) {
+		dev_err(codec->dev, "The board is configured for %u Hz, while"
+			"the Voice interface needs 26MHz APLL mclk\n",
+			twl4030->sysclk * 1000);
+		return -EINVAL;
+	}
+
+	/* If the codec mode is not option2, the voice PCM interface is not
+	 * avilable.
+	 */
+	mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE)
+		& TWL4030_OPT_MODE;
+
+	if (mode != TWL4030_OPTION_2) {
+		printk(KERN_ERR "TWL4030 voice startup: "
+			"the codec mode is not option2\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void twl4030_voice_shutdown(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	/* Enable voice digital filters */
+	twl4030_voice_enable(codec, substream->stream, 0);
+}
+
+static int twl4030_voice_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+	u8 old_mode, mode;
+
+	/* Enable voice digital filters */
+	twl4030_voice_enable(codec, substream->stream, 1);
+
+	/* bit rate */
+	old_mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE)
+		& ~(TWL4030_CODECPDZ);
+	mode = old_mode;
+
+	switch (params_rate(params)) {
+	case 8000:
+		mode &= ~(TWL4030_SEL_16K);
+		break;
+	case 16000:
+		mode |= TWL4030_SEL_16K;
+		break;
+	default:
+		printk(KERN_ERR "TWL4030 voice hw params: unknown rate %d\n",
+			params_rate(params));
+		return -EINVAL;
+	}
+
+	if (mode != old_mode) {
+		if (twl4030->codec_powered) {
+			/*
+			 * If the codec is powered, than we need to toggle the
+			 * codec power.
+			 */
+			twl4030_codec_enable(codec, 0);
+			twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+			twl4030_codec_enable(codec, 1);
+		} else {
+			twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+		}
+	}
+
+	return 0;
+}
+
+static int twl4030_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+
+	if (freq != 26000000) {
+		dev_err(codec->dev, "Unsupported APLL mclk: %u, the Voice"
+			"interface needs 26MHz APLL mclk\n", freq);
+		return -EINVAL;
+	}
+	if ((freq / 1000) != twl4030->sysclk) {
+		dev_err(codec->dev,
+			"Mismatch in APLL mclk: %u (configured: %u)\n",
+			freq, twl4030->sysclk * 1000);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+	u8 old_format, format;
+
+	/* get format */
+	old_format = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF);
+	format = old_format;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		format &= ~(TWL4030_VIF_SLAVE_EN);
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		format |= TWL4030_VIF_SLAVE_EN;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_IB_NF:
+		format &= ~(TWL4030_VIF_FORMAT);
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		format |= TWL4030_VIF_FORMAT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (format != old_format) {
+		if (twl4030->codec_powered) {
+			/*
+			 * If the codec is powered, than we need to toggle the
+			 * codec power.
+			 */
+			twl4030_codec_enable(codec, 0);
+			twl4030_write(codec, TWL4030_REG_VOICE_IF, format);
+			twl4030_codec_enable(codec, 1);
+		} else {
+			twl4030_write(codec, TWL4030_REG_VOICE_IF, format);
+		}
+	}
+
+	return 0;
+}
+
+static int twl4030_voice_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF);
+
+	if (tristate)
+		reg |= TWL4030_VIF_TRI_EN;
+	else
+		reg &= ~TWL4030_VIF_TRI_EN;
+
+	return twl4030_write(codec, TWL4030_REG_VOICE_IF, reg);
+}
+
+#define TWL4030_RATES	 (SNDRV_PCM_RATE_8000_48000)
+#define TWL4030_FORMATS	 (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE)
+
+static struct snd_soc_dai_ops twl4030_dai_hifi_ops = {
+	.startup	= twl4030_startup,
+	.shutdown	= twl4030_shutdown,
+	.hw_params	= twl4030_hw_params,
+	.set_sysclk	= twl4030_set_dai_sysclk,
+	.set_fmt	= twl4030_set_dai_fmt,
+	.set_tristate	= twl4030_set_tristate,
+};
+
+static struct snd_soc_dai_ops twl4030_dai_voice_ops = {
+	.startup	= twl4030_voice_startup,
+	.shutdown	= twl4030_voice_shutdown,
+	.hw_params	= twl4030_voice_hw_params,
+	.set_sysclk	= twl4030_voice_set_dai_sysclk,
+	.set_fmt	= twl4030_voice_set_dai_fmt,
+	.set_tristate	= twl4030_voice_set_tristate,
+};
+
+static struct snd_soc_dai_driver twl4030_dai[] = {
+{
+	.name = "twl4030-hifi",
+	.playback = {
+		.stream_name = "HiFi Playback",
+		.channels_min = 2,
+		.channels_max = 4,
+		.rates = TWL4030_RATES | SNDRV_PCM_RATE_96000,
+		.formats = TWL4030_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 4,
+		.rates = TWL4030_RATES,
+		.formats = TWL4030_FORMATS,},
+	.ops = &twl4030_dai_hifi_ops,
+},
+{
+	.name = "twl4030-voice",
+	.playback = {
+		.stream_name = "Voice Playback",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.ops = &twl4030_dai_voice_ops,
+},
+};
+
+static int twl4030_soc_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int twl4030_soc_resume(struct snd_soc_codec *codec)
+{
+	twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	return 0;
+}
+
+static int twl4030_soc_probe(struct snd_soc_codec *codec)
+{
+	struct twl4030_priv *twl4030;
+
+	twl4030 = kzalloc(sizeof(struct twl4030_priv), GFP_KERNEL);
+	if (twl4030 == NULL) {
+		printk("Can not allocate memroy\n");
+		return -ENOMEM;
+	}
+	snd_soc_codec_set_drvdata(codec, twl4030);
+	/* Set the defaults, and power up the codec */
+	twl4030->sysclk = twl4030_codec_get_mclk() / 1000;
+	codec->idle_bias_off = 1;
+
+	twl4030_init_chip(codec);
+
+	snd_soc_add_controls(codec, twl4030_snd_controls,
+				ARRAY_SIZE(twl4030_snd_controls));
+	twl4030_add_widgets(codec);
+	return 0;
+}
+
+static int twl4030_soc_remove(struct snd_soc_codec *codec)
+{
+	/* Reset registers to their chip default before leaving */
+	twl4030_reset_registers(codec);
+	twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_twl4030 = {
+	.probe = twl4030_soc_probe,
+	.remove = twl4030_soc_remove,
+	.suspend = twl4030_soc_suspend,
+	.resume = twl4030_soc_resume,
+	.read = twl4030_read_reg_cache,
+	.write = twl4030_write,
+	.set_bias_level = twl4030_set_bias_level,
+	.reg_cache_size = sizeof(twl4030_reg),
+	.reg_word_size = sizeof(u8),
+	.reg_cache_default = twl4030_reg,
+};
+
+static int __devinit twl4030_codec_probe(struct platform_device *pdev)
+{
+	struct twl4030_codec_audio_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "platform_data is missing\n");
+		return -EINVAL;
+	}
+
+	return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_twl4030,
+			twl4030_dai, ARRAY_SIZE(twl4030_dai));
+}
+
+static int __devexit twl4030_codec_remove(struct platform_device *pdev)
+{
+	struct twl4030_priv *twl4030 = dev_get_drvdata(&pdev->dev);
+
+	snd_soc_unregister_codec(&pdev->dev);
+	kfree(twl4030);
+	return 0;
+}
+
+MODULE_ALIAS("platform:twl4030-codec");
+
+static struct platform_driver twl4030_codec_driver = {
+	.probe		= twl4030_codec_probe,
+	.remove		= __devexit_p(twl4030_codec_remove),
+	.driver		= {
+		.name	= "twl4030-codec",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init twl4030_modinit(void)
+{
+	return platform_driver_register(&twl4030_codec_driver);
+}
+module_init(twl4030_modinit);
+
+static void __exit twl4030_exit(void)
+{
+	platform_driver_unregister(&twl4030_codec_driver);
+}
+module_exit(twl4030_exit);
+
+MODULE_DESCRIPTION("ASoC TWL4030 codec driver");
+MODULE_AUTHOR("Steve Sakoman");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/twl6040.c b/linux/sound/soc/codecs/twl6040.c
new file mode 100644
index 0000000..10f6e52
--- /dev/null
+++ b/linux/sound/soc/codecs/twl6040.c
@@ -0,0 +1,1164 @@
+/*
+ * ALSA SoC TWL6040 codec driver
+ *
+ * Author:	 Misael Lopez Cruz <x0052729@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/i2c/twl.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "twl6040.h"
+
+#define TWL6040_RATES	 (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+#define TWL6040_FORMATS	 (SNDRV_PCM_FMTBIT_S32_LE)
+
+/* codec private data */
+struct twl6040_data {
+	int audpwron;
+	int naudint;
+	int codec_powered;
+	int pll;
+	int non_lp;
+	unsigned int sysclk;
+	struct snd_pcm_hw_constraint_list *sysclk_constraints;
+	struct completion ready;
+};
+
+/*
+ * twl6040 register cache & default register settings
+ */
+static const u8 twl6040_reg[TWL6040_CACHEREGNUM] = {
+	0x00, /* not used		0x00	*/
+	0x4B, /* TWL6040_ASICID (ro)	0x01	*/
+	0x00, /* TWL6040_ASICREV (ro)	0x02	*/
+	0x00, /* TWL6040_INTID		0x03	*/
+	0x00, /* TWL6040_INTMR		0x04	*/
+	0x00, /* TWL6040_NCPCTRL	0x05	*/
+	0x00, /* TWL6040_LDOCTL		0x06	*/
+	0x60, /* TWL6040_HPPLLCTL	0x07	*/
+	0x00, /* TWL6040_LPPLLCTL	0x08	*/
+	0x4A, /* TWL6040_LPPLLDIV	0x09	*/
+	0x00, /* TWL6040_AMICBCTL	0x0A	*/
+	0x00, /* TWL6040_DMICBCTL	0x0B	*/
+	0x18, /* TWL6040_MICLCTL	0x0C	- No input selected on Left Mic */
+	0x18, /* TWL6040_MICRCTL	0x0D	- No input selected on Right Mic */
+	0x00, /* TWL6040_MICGAIN	0x0E	*/
+	0x1B, /* TWL6040_LINEGAIN	0x0F	*/
+	0x00, /* TWL6040_HSLCTL		0x10	*/
+	0x00, /* TWL6040_HSRCTL		0x11	*/
+	0x00, /* TWL6040_HSGAIN		0x12	*/
+	0x00, /* TWL6040_EARCTL		0x13	*/
+	0x00, /* TWL6040_HFLCTL		0x14	*/
+	0x00, /* TWL6040_HFLGAIN	0x15	*/
+	0x00, /* TWL6040_HFRCTL		0x16	*/
+	0x00, /* TWL6040_HFRGAIN	0x17	*/
+	0x00, /* TWL6040_VIBCTLL	0x18	*/
+	0x00, /* TWL6040_VIBDATL	0x19	*/
+	0x00, /* TWL6040_VIBCTLR	0x1A	*/
+	0x00, /* TWL6040_VIBDATR	0x1B	*/
+	0x00, /* TWL6040_HKCTL1		0x1C	*/
+	0x00, /* TWL6040_HKCTL2		0x1D	*/
+	0x00, /* TWL6040_GPOCTL		0x1E	*/
+	0x00, /* TWL6040_ALB		0x1F	*/
+	0x00, /* TWL6040_DLB		0x20	*/
+	0x00, /* not used		0x21	*/
+	0x00, /* not used		0x22	*/
+	0x00, /* not used		0x23	*/
+	0x00, /* not used		0x24	*/
+	0x00, /* not used		0x25	*/
+	0x00, /* not used		0x26	*/
+	0x00, /* not used		0x27	*/
+	0x00, /* TWL6040_TRIM1		0x28	*/
+	0x00, /* TWL6040_TRIM2		0x29	*/
+	0x00, /* TWL6040_TRIM3		0x2A	*/
+	0x00, /* TWL6040_HSOTRIM	0x2B	*/
+	0x00, /* TWL6040_HFOTRIM	0x2C	*/
+	0x09, /* TWL6040_ACCCTL		0x2D	*/
+	0x00, /* TWL6040_STATUS (ro)	0x2E	*/
+};
+
+/*
+ * twl6040 vio/gnd registers:
+ * registers under vio/gnd supply can be accessed
+ * before the power-up sequence, after NRESPWRON goes high
+ */
+static const int twl6040_vio_reg[TWL6040_VIOREGNUM] = {
+	TWL6040_REG_ASICID,
+	TWL6040_REG_ASICREV,
+	TWL6040_REG_INTID,
+	TWL6040_REG_INTMR,
+	TWL6040_REG_NCPCTL,
+	TWL6040_REG_LDOCTL,
+	TWL6040_REG_AMICBCTL,
+	TWL6040_REG_DMICBCTL,
+	TWL6040_REG_HKCTL1,
+	TWL6040_REG_HKCTL2,
+	TWL6040_REG_GPOCTL,
+	TWL6040_REG_TRIM1,
+	TWL6040_REG_TRIM2,
+	TWL6040_REG_TRIM3,
+	TWL6040_REG_HSOTRIM,
+	TWL6040_REG_HFOTRIM,
+	TWL6040_REG_ACCCTL,
+	TWL6040_REG_STATUS,
+};
+
+/*
+ * twl6040 vdd/vss registers:
+ * registers under vdd/vss supplies can only be accessed
+ * after the power-up sequence
+ */
+static const int twl6040_vdd_reg[TWL6040_VDDREGNUM] = {
+	TWL6040_REG_HPPLLCTL,
+	TWL6040_REG_LPPLLCTL,
+	TWL6040_REG_LPPLLDIV,
+	TWL6040_REG_MICLCTL,
+	TWL6040_REG_MICRCTL,
+	TWL6040_REG_MICGAIN,
+	TWL6040_REG_LINEGAIN,
+	TWL6040_REG_HSLCTL,
+	TWL6040_REG_HSRCTL,
+	TWL6040_REG_HSGAIN,
+	TWL6040_REG_EARCTL,
+	TWL6040_REG_HFLCTL,
+	TWL6040_REG_HFLGAIN,
+	TWL6040_REG_HFRCTL,
+	TWL6040_REG_HFRGAIN,
+	TWL6040_REG_VIBCTLL,
+	TWL6040_REG_VIBDATL,
+	TWL6040_REG_VIBCTLR,
+	TWL6040_REG_VIBDATR,
+	TWL6040_REG_ALB,
+	TWL6040_REG_DLB,
+};
+
+/*
+ * read twl6040 register cache
+ */
+static inline unsigned int twl6040_read_reg_cache(struct snd_soc_codec *codec,
+						unsigned int reg)
+{
+	u8 *cache = codec->reg_cache;
+
+	if (reg >= TWL6040_CACHEREGNUM)
+		return -EIO;
+
+	return cache[reg];
+}
+
+/*
+ * write twl6040 register cache
+ */
+static inline void twl6040_write_reg_cache(struct snd_soc_codec *codec,
+						u8 reg, u8 value)
+{
+	u8 *cache = codec->reg_cache;
+
+	if (reg >= TWL6040_CACHEREGNUM)
+		return;
+	cache[reg] = value;
+}
+
+/*
+ * read from twl6040 hardware register
+ */
+static int twl6040_read_reg_volatile(struct snd_soc_codec *codec,
+			unsigned int reg)
+{
+	u8 value;
+
+	if (reg >= TWL6040_CACHEREGNUM)
+		return -EIO;
+
+	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &value, reg);
+	twl6040_write_reg_cache(codec, reg, value);
+
+	return value;
+}
+
+/*
+ * write to the twl6040 register space
+ */
+static int twl6040_write(struct snd_soc_codec *codec,
+			unsigned int reg, unsigned int value)
+{
+	if (reg >= TWL6040_CACHEREGNUM)
+		return -EIO;
+
+	twl6040_write_reg_cache(codec, reg, value);
+	return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg);
+}
+
+static void twl6040_init_vio_regs(struct snd_soc_codec *codec)
+{
+	u8 *cache = codec->reg_cache;
+	int reg, i;
+
+	/* allow registers to be accessed by i2c */
+	twl6040_write(codec, TWL6040_REG_ACCCTL, cache[TWL6040_REG_ACCCTL]);
+
+	for (i = 0; i < TWL6040_VIOREGNUM; i++) {
+		reg = twl6040_vio_reg[i];
+		/* skip read-only registers (ASICID, ASICREV, STATUS) */
+		switch (reg) {
+		case TWL6040_REG_ASICID:
+		case TWL6040_REG_ASICREV:
+		case TWL6040_REG_STATUS:
+			continue;
+		default:
+			break;
+		}
+		twl6040_write(codec, reg, cache[reg]);
+	}
+}
+
+static void twl6040_init_vdd_regs(struct snd_soc_codec *codec)
+{
+	u8 *cache = codec->reg_cache;
+	int reg, i;
+
+	for (i = 0; i < TWL6040_VDDREGNUM; i++) {
+		reg = twl6040_vdd_reg[i];
+		twl6040_write(codec, reg, cache[reg]);
+	}
+}
+
+/* twl6040 codec manual power-up sequence */
+static void twl6040_power_up(struct snd_soc_codec *codec)
+{
+	u8 ncpctl, ldoctl, lppllctl, accctl;
+
+	ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL);
+	ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL);
+	lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
+	accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL);
+
+	/* enable reference system */
+	ldoctl |= TWL6040_REFENA;
+	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+	msleep(10);
+	/* enable internal oscillator */
+	ldoctl |= TWL6040_OSCENA;
+	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+	udelay(10);
+	/* enable high-side ldo */
+	ldoctl |= TWL6040_HSLDOENA;
+	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+	udelay(244);
+	/* enable negative charge pump */
+	ncpctl |= TWL6040_NCPENA | TWL6040_NCPOPEN;
+	twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl);
+	udelay(488);
+	/* enable low-side ldo */
+	ldoctl |= TWL6040_LSLDOENA;
+	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+	udelay(244);
+	/* enable low-power pll */
+	lppllctl |= TWL6040_LPLLENA;
+	twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+	/* reset state machine */
+	accctl |= TWL6040_RESETSPLIT;
+	twl6040_write(codec, TWL6040_REG_ACCCTL, accctl);
+	mdelay(5);
+	accctl &= ~TWL6040_RESETSPLIT;
+	twl6040_write(codec, TWL6040_REG_ACCCTL, accctl);
+	/* disable internal oscillator */
+	ldoctl &= ~TWL6040_OSCENA;
+	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+}
+
+/* twl6040 codec manual power-down sequence */
+static void twl6040_power_down(struct snd_soc_codec *codec)
+{
+	u8 ncpctl, ldoctl, lppllctl, accctl;
+
+	ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL);
+	ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL);
+	lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
+	accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL);
+
+	/* enable internal oscillator */
+	ldoctl |= TWL6040_OSCENA;
+	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+	udelay(10);
+	/* disable low-power pll */
+	lppllctl &= ~TWL6040_LPLLENA;
+	twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+	/* disable low-side ldo */
+	ldoctl &= ~TWL6040_LSLDOENA;
+	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+	udelay(244);
+	/* disable negative charge pump */
+	ncpctl &= ~(TWL6040_NCPENA | TWL6040_NCPOPEN);
+	twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl);
+	udelay(488);
+	/* disable high-side ldo */
+	ldoctl &= ~TWL6040_HSLDOENA;
+	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+	udelay(244);
+	/* disable internal oscillator */
+	ldoctl &= ~TWL6040_OSCENA;
+	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+	/* disable reference system */
+	ldoctl &= ~TWL6040_REFENA;
+	twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl);
+	msleep(10);
+}
+
+/* set headset dac and driver power mode */
+static int headset_power_mode(struct snd_soc_codec *codec, int high_perf)
+{
+	int hslctl, hsrctl;
+	int mask = TWL6040_HSDRVMODEL | TWL6040_HSDACMODEL;
+
+	hslctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSLCTL);
+	hsrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSRCTL);
+
+	if (high_perf) {
+		hslctl &= ~mask;
+		hsrctl &= ~mask;
+	} else {
+		hslctl |= mask;
+		hsrctl |= mask;
+	}
+
+	twl6040_write(codec, TWL6040_REG_HSLCTL, hslctl);
+	twl6040_write(codec, TWL6040_REG_HSRCTL, hsrctl);
+
+	return 0;
+}
+
+static int twl6040_hs_dac_event(struct snd_soc_dapm_widget *w,
+			struct snd_kcontrol *kcontrol, int event)
+{
+	msleep(1);
+	return 0;
+}
+
+static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w,
+			struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		priv->non_lp++;
+	else
+		priv->non_lp--;
+
+	msleep(1);
+
+	return 0;
+}
+
+/* audio interrupt handler */
+static irqreturn_t twl6040_naudint_handler(int irq, void *data)
+{
+	struct snd_soc_codec *codec = data;
+	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+	u8 intid;
+
+	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID);
+
+	switch (intid) {
+	case TWL6040_THINT:
+		dev_alert(codec->dev, "die temp over-limit detection\n");
+		break;
+	case TWL6040_PLUGINT:
+	case TWL6040_UNPLUGINT:
+	case TWL6040_HOOKINT:
+		break;
+	case TWL6040_HFINT:
+		dev_alert(codec->dev, "hf drivers over current detection\n");
+		break;
+	case TWL6040_VIBINT:
+		dev_alert(codec->dev, "vib drivers over current detection\n");
+		break;
+	case TWL6040_READYINT:
+		complete(&priv->ready);
+		break;
+	default:
+		dev_err(codec->dev, "unknown audio interrupt %d\n", intid);
+		break;
+	}
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * MICATT volume control:
+ * from -6 to 0 dB in 6 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0);
+
+/*
+ * MICGAIN volume control:
+ * from 6 to 30 dB in 6 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(mic_amp_tlv, 600, 600, 0);
+
+/*
+ * HSGAIN volume control:
+ * from -30 to 0 dB in 2 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(hs_tlv, -3000, 200, 0);
+
+/*
+ * HFGAIN volume control:
+ * from -52 to 6 dB in 2 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(hf_tlv, -5200, 200, 0);
+
+/*
+ * EPGAIN volume control:
+ * from -24 to 6 dB in 2 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(ep_tlv, -2400, 200, 0);
+
+/* Left analog microphone selection */
+static const char *twl6040_amicl_texts[] =
+	{"Headset Mic", "Main Mic", "Aux/FM Left", "Off"};
+
+/* Right analog microphone selection */
+static const char *twl6040_amicr_texts[] =
+	{"Headset Mic", "Sub Mic", "Aux/FM Right", "Off"};
+
+static const struct soc_enum twl6040_enum[] = {
+	SOC_ENUM_SINGLE(TWL6040_REG_MICLCTL, 3, 3, twl6040_amicl_texts),
+	SOC_ENUM_SINGLE(TWL6040_REG_MICRCTL, 3, 3, twl6040_amicr_texts),
+};
+
+static const struct snd_kcontrol_new amicl_control =
+	SOC_DAPM_ENUM("Route", twl6040_enum[0]);
+
+static const struct snd_kcontrol_new amicr_control =
+	SOC_DAPM_ENUM("Route", twl6040_enum[1]);
+
+/* Headset DAC playback switches */
+static const struct snd_kcontrol_new hsdacl_switch_controls =
+	SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSLCTL, 5, 1, 0);
+
+static const struct snd_kcontrol_new hsdacr_switch_controls =
+	SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSRCTL, 5, 1, 0);
+
+/* Handsfree DAC playback switches */
+static const struct snd_kcontrol_new hfdacl_switch_controls =
+	SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFLCTL, 2, 1, 0);
+
+static const struct snd_kcontrol_new hfdacr_switch_controls =
+	SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 2, 1, 0);
+
+static const struct snd_kcontrol_new ep_driver_switch_controls =
+	SOC_DAPM_SINGLE("Switch", TWL6040_REG_EARCTL, 0, 1, 0);
+
+static const struct snd_kcontrol_new twl6040_snd_controls[] = {
+	/* Capture gains */
+	SOC_DOUBLE_TLV("Capture Preamplifier Volume",
+		TWL6040_REG_MICGAIN, 6, 7, 1, 1, mic_preamp_tlv),
+	SOC_DOUBLE_TLV("Capture Volume",
+		TWL6040_REG_MICGAIN, 0, 3, 4, 0, mic_amp_tlv),
+
+	/* Playback gains */
+	SOC_DOUBLE_TLV("Headset Playback Volume",
+		TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv),
+	SOC_DOUBLE_R_TLV("Handsfree Playback Volume",
+		TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv),
+	SOC_SINGLE_TLV("Earphone Playback Volume",
+		TWL6040_REG_EARCTL, 1, 0xF, 1, ep_tlv),
+};
+
+static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
+	/* Inputs */
+	SND_SOC_DAPM_INPUT("MAINMIC"),
+	SND_SOC_DAPM_INPUT("HSMIC"),
+	SND_SOC_DAPM_INPUT("SUBMIC"),
+	SND_SOC_DAPM_INPUT("AFML"),
+	SND_SOC_DAPM_INPUT("AFMR"),
+
+	/* Outputs */
+	SND_SOC_DAPM_OUTPUT("HSOL"),
+	SND_SOC_DAPM_OUTPUT("HSOR"),
+	SND_SOC_DAPM_OUTPUT("HFL"),
+	SND_SOC_DAPM_OUTPUT("HFR"),
+	SND_SOC_DAPM_OUTPUT("EP"),
+
+	/* Analog input muxes for the capture amplifiers */
+	SND_SOC_DAPM_MUX("Analog Left Capture Route",
+			SND_SOC_NOPM, 0, 0, &amicl_control),
+	SND_SOC_DAPM_MUX("Analog Right Capture Route",
+			SND_SOC_NOPM, 0, 0, &amicr_control),
+
+	/* Analog capture PGAs */
+	SND_SOC_DAPM_PGA("MicAmpL",
+			TWL6040_REG_MICLCTL, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("MicAmpR",
+			TWL6040_REG_MICRCTL, 0, 0, NULL, 0),
+
+	/* ADCs */
+	SND_SOC_DAPM_ADC("ADC Left", "Left Front Capture",
+			TWL6040_REG_MICLCTL, 2, 0),
+	SND_SOC_DAPM_ADC("ADC Right", "Right Front Capture",
+			TWL6040_REG_MICRCTL, 2, 0),
+
+	/* Microphone bias */
+	SND_SOC_DAPM_MICBIAS("Headset Mic Bias",
+			TWL6040_REG_AMICBCTL, 0, 0),
+	SND_SOC_DAPM_MICBIAS("Main Mic Bias",
+			TWL6040_REG_AMICBCTL, 4, 0),
+	SND_SOC_DAPM_MICBIAS("Digital Mic1 Bias",
+			TWL6040_REG_DMICBCTL, 0, 0),
+	SND_SOC_DAPM_MICBIAS("Digital Mic2 Bias",
+			TWL6040_REG_DMICBCTL, 4, 0),
+
+	/* DACs */
+	SND_SOC_DAPM_DAC_E("HSDAC Left", "Headset Playback",
+			TWL6040_REG_HSLCTL, 0, 0,
+			twl6040_hs_dac_event,
+			SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_DAC_E("HSDAC Right", "Headset Playback",
+			TWL6040_REG_HSRCTL, 0, 0,
+			twl6040_hs_dac_event,
+			SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_DAC_E("HFDAC Left", "Handsfree Playback",
+			TWL6040_REG_HFLCTL, 0, 0,
+			twl6040_power_mode_event,
+			SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_DAC_E("HFDAC Right", "Handsfree Playback",
+			TWL6040_REG_HFRCTL, 0, 0,
+			twl6040_power_mode_event,
+			SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+	/* Analog playback switches */
+	SND_SOC_DAPM_SWITCH("HSDAC Left Playback",
+			SND_SOC_NOPM, 0, 0, &hsdacl_switch_controls),
+	SND_SOC_DAPM_SWITCH("HSDAC Right Playback",
+			SND_SOC_NOPM, 0, 0, &hsdacr_switch_controls),
+	SND_SOC_DAPM_SWITCH("HFDAC Left Playback",
+			SND_SOC_NOPM, 0, 0, &hfdacl_switch_controls),
+	SND_SOC_DAPM_SWITCH("HFDAC Right Playback",
+			SND_SOC_NOPM, 0, 0, &hfdacr_switch_controls),
+
+	/* Analog playback drivers */
+	SND_SOC_DAPM_PGA_E("Handsfree Left Driver",
+			TWL6040_REG_HFLCTL, 4, 0, NULL, 0,
+			twl6040_power_mode_event,
+			SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_PGA_E("Handsfree Right Driver",
+			TWL6040_REG_HFRCTL, 4, 0, NULL, 0,
+			twl6040_power_mode_event,
+			SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_PGA("Headset Left Driver",
+			TWL6040_REG_HSLCTL, 2, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Headset Right Driver",
+			TWL6040_REG_HSRCTL, 2, 0, NULL, 0),
+	SND_SOC_DAPM_SWITCH_E("Earphone Driver",
+			SND_SOC_NOPM, 0, 0, &ep_driver_switch_controls,
+			twl6040_power_mode_event,
+			SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+	/* Analog playback PGAs */
+	SND_SOC_DAPM_PGA("HFDAC Left PGA",
+			TWL6040_REG_HFLCTL, 1, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("HFDAC Right PGA",
+			TWL6040_REG_HFRCTL, 1, 0, NULL, 0),
+
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+	/* Capture path */
+	{"Analog Left Capture Route", "Headset Mic", "HSMIC"},
+	{"Analog Left Capture Route", "Main Mic", "MAINMIC"},
+	{"Analog Left Capture Route", "Aux/FM Left", "AFML"},
+
+	{"Analog Right Capture Route", "Headset Mic", "HSMIC"},
+	{"Analog Right Capture Route", "Sub Mic", "SUBMIC"},
+	{"Analog Right Capture Route", "Aux/FM Right", "AFMR"},
+
+	{"MicAmpL", NULL, "Analog Left Capture Route"},
+	{"MicAmpR", NULL, "Analog Right Capture Route"},
+
+	{"ADC Left", NULL, "MicAmpL"},
+	{"ADC Right", NULL, "MicAmpR"},
+
+	/* Headset playback path */
+	{"HSDAC Left Playback", "Switch", "HSDAC Left"},
+	{"HSDAC Right Playback", "Switch", "HSDAC Right"},
+
+	{"Headset Left Driver", NULL, "HSDAC Left Playback"},
+	{"Headset Right Driver", NULL, "HSDAC Right Playback"},
+
+	{"HSOL", NULL, "Headset Left Driver"},
+	{"HSOR", NULL, "Headset Right Driver"},
+
+	/* Earphone playback path */
+	{"Earphone Driver", "Switch", "HSDAC Left"},
+	{"EP", NULL, "Earphone Driver"},
+
+	/* Handsfree playback path */
+	{"HFDAC Left Playback", "Switch", "HFDAC Left"},
+	{"HFDAC Right Playback", "Switch", "HFDAC Right"},
+
+	{"HFDAC Left PGA", NULL, "HFDAC Left Playback"},
+	{"HFDAC Right PGA", NULL, "HFDAC Right Playback"},
+
+	{"Handsfree Left Driver", "Switch", "HFDAC Left PGA"},
+	{"Handsfree Right Driver", "Switch", "HFDAC Right PGA"},
+
+	{"HFL", NULL, "Handsfree Left Driver"},
+	{"HFR", NULL, "Handsfree Right Driver"},
+};
+
+static int twl6040_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, twl6040_dapm_widgets,
+				 ARRAY_SIZE(twl6040_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	snd_soc_dapm_new_widgets(codec);
+
+	return 0;
+}
+
+static int twl6040_power_up_completion(struct snd_soc_codec *codec,
+					int naudint)
+{
+	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+	int time_left;
+	u8 intid;
+
+	time_left = wait_for_completion_timeout(&priv->ready,
+				msecs_to_jiffies(48));
+
+	if (!time_left) {
+		twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &intid,
+							TWL6040_REG_INTID);
+		if (!(intid & TWL6040_READYINT)) {
+			dev_err(codec->dev, "timeout waiting for READYINT\n");
+			return -ETIMEDOUT;
+		}
+	}
+
+	priv->codec_powered = 1;
+
+	return 0;
+}
+
+static int twl6040_set_bias_level(struct snd_soc_codec *codec,
+				enum snd_soc_bias_level level)
+{
+	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+	int audpwron = priv->audpwron;
+	int naudint = priv->naudint;
+	int ret;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (priv->codec_powered)
+			break;
+
+		if (gpio_is_valid(audpwron)) {
+			/* use AUDPWRON line */
+			gpio_set_value(audpwron, 1);
+
+			/* wait for power-up completion */
+			ret = twl6040_power_up_completion(codec, naudint);
+			if (ret)
+				return ret;
+
+			/* sync registers updated during power-up sequence */
+			twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL);
+			twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL);
+			twl6040_read_reg_volatile(codec, TWL6040_REG_LPPLLCTL);
+		} else {
+			/* use manual power-up sequence */
+			twl6040_power_up(codec);
+			priv->codec_powered = 1;
+		}
+
+		/* initialize vdd/vss registers with reg_cache */
+		twl6040_init_vdd_regs(codec);
+		break;
+	case SND_SOC_BIAS_OFF:
+		if (!priv->codec_powered)
+			break;
+
+		if (gpio_is_valid(audpwron)) {
+			/* use AUDPWRON line */
+			gpio_set_value(audpwron, 0);
+
+			/* power-down sequence latency */
+			udelay(500);
+
+			/* sync registers updated during power-down sequence */
+			twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL);
+			twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL);
+			twl6040_write_reg_cache(codec, TWL6040_REG_LPPLLCTL,
+						0x00);
+		} else {
+			/* use manual power-down sequence */
+			twl6040_power_down(codec);
+		}
+
+		priv->codec_powered = 0;
+		break;
+	}
+
+	codec->bias_level = level;
+
+	return 0;
+}
+
+/* set of rates for each pll: low-power and high-performance */
+
+static unsigned int lp_rates[] = {
+	88200,
+	96000,
+};
+
+static struct snd_pcm_hw_constraint_list lp_constraints = {
+	.count	= ARRAY_SIZE(lp_rates),
+	.list	= lp_rates,
+};
+
+static unsigned int hp_rates[] = {
+	96000,
+};
+
+static struct snd_pcm_hw_constraint_list hp_constraints = {
+	.count	= ARRAY_SIZE(hp_rates),
+	.list	= hp_rates,
+};
+
+static int twl6040_startup(struct snd_pcm_substream *substream,
+			struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+
+	if (!priv->sysclk) {
+		dev_err(codec->dev,
+			"no mclk configured, call set_sysclk() on init\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * capture is not supported at 17.64 MHz,
+	 * it's reserved for headset low-power playback scenario
+	 */
+	if ((priv->sysclk == 17640000) && substream->stream) {
+		dev_err(codec->dev,
+			"capture mode is not supported at %dHz\n",
+			priv->sysclk);
+		return -EINVAL;
+	}
+
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				priv->sysclk_constraints);
+
+	return 0;
+}
+
+static int twl6040_hw_params(struct snd_pcm_substream *substream,
+			struct snd_pcm_hw_params *params,
+			struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+	u8 lppllctl;
+	int rate;
+
+	/* nothing to do for high-perf pll, it supports only 48 kHz */
+	if (priv->pll == TWL6040_HPPLL_ID)
+		return 0;
+
+	lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
+
+	rate = params_rate(params);
+	switch (rate) {
+	case 88200:
+		lppllctl |= TWL6040_LPLLFIN;
+		priv->sysclk = 17640000;
+		break;
+	case 96000:
+		lppllctl &= ~TWL6040_LPLLFIN;
+		priv->sysclk = 19200000;
+		break;
+	default:
+		dev_err(codec->dev, "unsupported rate %d\n", rate);
+		return -EINVAL;
+	}
+
+	twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+
+	return 0;
+}
+
+static int twl6040_trigger(struct snd_pcm_substream *substream,
+			int cmd, struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		/*
+		 * low-power playback mode is restricted
+		 * for headset path only
+		 */
+		if ((priv->sysclk == 17640000) && priv->non_lp) {
+			dev_err(codec->dev,
+				"some enabled paths aren't supported at %dHz\n",
+				priv->sysclk);
+			return -EPERM;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+	u8 hppllctl, lppllctl;
+
+	hppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_HPPLLCTL);
+	lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL);
+
+	switch (clk_id) {
+	case TWL6040_SYSCLK_SEL_LPPLL:
+		switch (freq) {
+		case 32768:
+			/* headset dac and driver must be in low-power mode */
+			headset_power_mode(codec, 0);
+
+			/* clk32k input requires low-power pll */
+			lppllctl |= TWL6040_LPLLENA;
+			twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+			mdelay(5);
+			lppllctl &= ~TWL6040_HPLLSEL;
+			twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+			hppllctl &= ~TWL6040_HPLLENA;
+			twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl);
+			break;
+		default:
+			dev_err(codec->dev, "unknown mclk freq %d\n", freq);
+			return -EINVAL;
+		}
+
+		/* lppll divider */
+		switch (priv->sysclk) {
+		case 17640000:
+			lppllctl |= TWL6040_LPLLFIN;
+			break;
+		case 19200000:
+			lppllctl &= ~TWL6040_LPLLFIN;
+			break;
+		default:
+			/* sysclk not yet configured */
+			lppllctl &= ~TWL6040_LPLLFIN;
+			priv->sysclk = 19200000;
+			break;
+		}
+
+		twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+
+		priv->pll = TWL6040_LPPLL_ID;
+		priv->sysclk_constraints = &lp_constraints;
+		break;
+	case TWL6040_SYSCLK_SEL_HPPLL:
+		hppllctl &= ~TWL6040_MCLK_MSK;
+
+		switch (freq) {
+		case 12000000:
+			/* mclk input, pll enabled */
+			hppllctl |= TWL6040_MCLK_12000KHZ |
+				    TWL6040_HPLLSQRBP |
+				    TWL6040_HPLLENA;
+			break;
+		case 19200000:
+			/* mclk input, pll disabled */
+			hppllctl |= TWL6040_MCLK_19200KHZ |
+				    TWL6040_HPLLSQRENA |
+				    TWL6040_HPLLBP;
+			break;
+		case 26000000:
+			/* mclk input, pll enabled */
+			hppllctl |= TWL6040_MCLK_26000KHZ |
+				    TWL6040_HPLLSQRBP |
+				    TWL6040_HPLLENA;
+			break;
+		case 38400000:
+			/* clk slicer, pll disabled */
+			hppllctl |= TWL6040_MCLK_38400KHZ |
+				    TWL6040_HPLLSQRENA |
+				    TWL6040_HPLLBP;
+			break;
+		default:
+			dev_err(codec->dev, "unknown mclk freq %d\n", freq);
+			return -EINVAL;
+		}
+
+		/* headset dac and driver must be in high-performance mode */
+		headset_power_mode(codec, 1);
+
+		twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl);
+		udelay(500);
+		lppllctl |= TWL6040_HPLLSEL;
+		twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+		lppllctl &= ~TWL6040_LPLLENA;
+		twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl);
+
+		/* high-performance pll can provide only 19.2 MHz */
+		priv->pll = TWL6040_HPPLL_ID;
+		priv->sysclk = 19200000;
+		priv->sysclk_constraints = &hp_constraints;
+		break;
+	default:
+		dev_err(codec->dev, "unknown clk_id %d\n", clk_id);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops twl6040_dai_ops = {
+	.startup	= twl6040_startup,
+	.hw_params	= twl6040_hw_params,
+	.trigger	= twl6040_trigger,
+	.set_sysclk	= twl6040_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver twl6040_dai = {
+	.name = "twl6040-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 4,
+		.rates = TWL6040_RATES,
+		.formats = TWL6040_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = TWL6040_RATES,
+		.formats = TWL6040_FORMATS,
+	},
+	.ops = &twl6040_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int twl6040_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int twl6040_resume(struct snd_soc_codec *codec)
+{
+	twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+#else
+#define twl6040_suspend NULL
+#define twl6040_resume NULL
+#endif
+
+static int twl6040_probe(struct snd_soc_codec *codec)
+{
+	struct twl4030_codec_data *twl_codec = codec->dev->platform_data;
+	struct twl6040_data *priv;
+	int audpwron, naudint;
+	int ret = 0;
+
+	priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL);
+	if (priv == NULL)
+		return -ENOMEM;
+	snd_soc_codec_set_drvdata(codec, priv);
+
+	if (twl_codec) {
+		audpwron = twl_codec->audpwron_gpio;
+		naudint = twl_codec->naudint_irq;
+	} else {
+		audpwron = -EINVAL;
+		naudint = 0;
+	}
+
+	priv->audpwron = audpwron;
+	priv->naudint = naudint;
+
+	init_completion(&priv->ready);
+
+	if (gpio_is_valid(audpwron)) {
+		ret = gpio_request(audpwron, "audpwron");
+		if (ret)
+			goto gpio1_err;
+
+		ret = gpio_direction_output(audpwron, 0);
+		if (ret)
+			goto gpio2_err;
+
+		priv->codec_powered = 0;
+	}
+
+	if (naudint) {
+		/* audio interrupt */
+		ret = request_threaded_irq(naudint, NULL,
+				twl6040_naudint_handler,
+				IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+				"twl6040_codec", codec);
+		if (ret)
+			goto gpio2_err;
+	} else {
+		if (gpio_is_valid(audpwron)) {
+			/* enable only codec ready interrupt */
+			twl6040_write_reg_cache(codec, TWL6040_REG_INTMR,
+					~TWL6040_READYMSK & TWL6040_ALLINT_MSK);
+		} else {
+			/* no interrupts at all */
+			twl6040_write_reg_cache(codec, TWL6040_REG_INTMR,
+						TWL6040_ALLINT_MSK);
+		}
+	}
+
+	/* init vio registers */
+	twl6040_init_vio_regs(codec);
+
+	/* power on device */
+	ret = twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	if (ret)
+		goto irq_err;
+
+	snd_soc_add_controls(codec, twl6040_snd_controls,
+				ARRAY_SIZE(twl6040_snd_controls));
+	twl6040_add_widgets(codec);
+
+	return 0;
+
+irq_err:
+	if (naudint)
+		free_irq(naudint, codec);
+gpio2_err:
+	if (gpio_is_valid(audpwron))
+		gpio_free(audpwron);
+gpio1_err:
+	kfree(priv);
+	return ret;
+}
+
+static int twl6040_remove(struct snd_soc_codec *codec)
+{
+	struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
+	int audpwron = priv->audpwron;
+	int naudint = priv->naudint;
+
+	twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	if (gpio_is_valid(audpwron))
+		gpio_free(audpwron);
+
+	if (naudint)
+		free_irq(naudint, codec);
+
+	kfree(priv);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_twl6040 = {
+	.probe = twl6040_probe,
+	.remove = twl6040_remove,
+	.suspend = twl6040_suspend,
+	.resume = twl6040_resume,
+	.read = twl6040_read_reg_cache,
+	.write = twl6040_write,
+	.set_bias_level = twl6040_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(twl6040_reg),
+	.reg_word_size = sizeof(u8),
+	.reg_cache_default = twl6040_reg,
+};
+
+static int __devinit twl6040_codec_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_twl6040, &twl6040_dai, 1);
+}
+
+static int __devexit twl6040_codec_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver twl6040_codec_driver = {
+	.driver = {
+		.name = "twl6040-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe = twl6040_codec_probe,
+	.remove = __devexit_p(twl6040_codec_remove),
+};
+
+static int __init twl6040_codec_init(void)
+{
+	return platform_driver_register(&twl6040_codec_driver);
+}
+module_init(twl6040_codec_init);
+
+static void __exit twl6040_codec_exit(void)
+{
+	platform_driver_unregister(&twl6040_codec_driver);
+}
+module_exit(twl6040_codec_exit);
+
+MODULE_DESCRIPTION("ASoC TWL6040 codec driver");
+MODULE_AUTHOR("Misael Lopez Cruz");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/twl6040.h b/linux/sound/soc/codecs/twl6040.h
new file mode 100644
index 0000000..f7c77fa
--- /dev/null
+++ b/linux/sound/soc/codecs/twl6040.h
@@ -0,0 +1,138 @@
+/*
+ * ALSA SoC TWL6040 codec driver
+ *
+ * Author:	Misael Lopez Cruz <x0052729@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TWL6040_H__
+#define __TWL6040_H__
+
+#define TWL6040_REG_ASICID		0x01
+#define TWL6040_REG_ASICREV		0x02
+#define TWL6040_REG_INTID		0x03
+#define TWL6040_REG_INTMR		0x04
+#define TWL6040_REG_NCPCTL		0x05
+#define TWL6040_REG_LDOCTL		0x06
+#define TWL6040_REG_HPPLLCTL		0x07
+#define TWL6040_REG_LPPLLCTL		0x08
+#define TWL6040_REG_LPPLLDIV		0x09
+#define TWL6040_REG_AMICBCTL		0x0A
+#define TWL6040_REG_DMICBCTL		0x0B
+#define TWL6040_REG_MICLCTL		0x0C
+#define TWL6040_REG_MICRCTL		0x0D
+#define TWL6040_REG_MICGAIN		0x0E
+#define TWL6040_REG_LINEGAIN		0x0F
+#define TWL6040_REG_HSLCTL		0x10
+#define TWL6040_REG_HSRCTL		0x11
+#define TWL6040_REG_HSGAIN		0x12
+#define TWL6040_REG_EARCTL		0x13
+#define TWL6040_REG_HFLCTL		0x14
+#define TWL6040_REG_HFLGAIN		0x15
+#define TWL6040_REG_HFRCTL		0x16
+#define TWL6040_REG_HFRGAIN		0x17
+#define TWL6040_REG_VIBCTLL		0x18
+#define TWL6040_REG_VIBDATL		0x19
+#define TWL6040_REG_VIBCTLR		0x1A
+#define TWL6040_REG_VIBDATR		0x1B
+#define TWL6040_REG_HKCTL1		0x1C
+#define TWL6040_REG_HKCTL2		0x1D
+#define TWL6040_REG_GPOCTL		0x1E
+#define TWL6040_REG_ALB			0x1F
+#define TWL6040_REG_DLB			0x20
+#define TWL6040_REG_TRIM1		0x28
+#define TWL6040_REG_TRIM2		0x29
+#define TWL6040_REG_TRIM3		0x2A
+#define TWL6040_REG_HSOTRIM		0x2B
+#define TWL6040_REG_HFOTRIM		0x2C
+#define TWL6040_REG_ACCCTL		0x2D
+#define TWL6040_REG_STATUS		0x2E
+
+#define TWL6040_CACHEREGNUM		(TWL6040_REG_STATUS + 1)
+
+#define TWL6040_VIOREGNUM		18
+#define TWL6040_VDDREGNUM		21
+
+/* INTID (0x03) fields */
+
+#define TWL6040_THINT			0x01
+#define TWL6040_PLUGINT			0x02
+#define TWL6040_UNPLUGINT		0x04
+#define TWL6040_HOOKINT			0x08
+#define TWL6040_HFINT			0x10
+#define TWL6040_VIBINT			0x20
+#define TWL6040_READYINT		0x40
+
+/* INTMR (0x04) fields */
+
+#define TWL6040_READYMSK		0x40
+#define TWL6040_ALLINT_MSK		0x7B
+
+/* NCPCTL (0x05) fields */
+
+#define TWL6040_NCPENA			0x01
+#define TWL6040_NCPOPEN			0x40
+
+/* LDOCTL (0x06) fields */
+
+#define TWL6040_LSLDOENA		0x01
+#define TWL6040_HSLDOENA		0x04
+#define TWL6040_REFENA			0x40
+#define TWL6040_OSCENA			0x80
+
+/* HPPLLCTL (0x07) fields */
+
+#define TWL6040_HPLLENA			0x01
+#define TWL6040_HPLLRST			0x02
+#define TWL6040_HPLLBP			0x04
+#define TWL6040_HPLLSQRENA		0x08
+#define TWL6040_HPLLSQRBP		0x10
+#define TWL6040_MCLK_12000KHZ		(0 << 5)
+#define TWL6040_MCLK_19200KHZ		(1 << 5)
+#define TWL6040_MCLK_26000KHZ		(2 << 5)
+#define TWL6040_MCLK_38400KHZ		(3 << 5)
+#define TWL6040_MCLK_MSK		0x60
+
+/* LPPLLCTL (0x08) fields */
+
+#define TWL6040_LPLLENA			0x01
+#define TWL6040_LPLLRST			0x02
+#define TWL6040_LPLLSEL			0x04
+#define TWL6040_LPLLFIN			0x08
+#define TWL6040_HPLLSEL			0x10
+
+/* HSLCTL (0x10) fields */
+
+#define TWL6040_HSDACMODEL		0x02
+#define TWL6040_HSDRVMODEL		0x08
+
+/* HSRCTL (0x11) fields */
+
+#define TWL6040_HSDACMODER		0x02
+#define TWL6040_HSDRVMODER		0x08
+
+/* ACCCTL (0x2D) fields */
+
+#define TWL6040_RESETSPLIT		0x04
+
+#define TWL6040_SYSCLK_SEL_LPPLL	1
+#define TWL6040_SYSCLK_SEL_HPPLL	2
+
+#define TWL6040_HPPLL_ID		1
+#define TWL6040_LPPLL_ID		2
+
+#endif /* End of __TWL6040_H__ */
diff --git a/linux/sound/soc/codecs/uda134x.c b/linux/sound/soc/codecs/uda134x.c
new file mode 100644
index 0000000..464f0cf
--- /dev/null
+++ b/linux/sound/soc/codecs/uda134x.c
@@ -0,0 +1,644 @@
+/*
+ * uda134x.c  --  UDA134X ALSA SoC Codec driver
+ *
+ * Modifications by Christian Pellegrin <chripell@evolware.org>
+ *
+ * Copyright 2007 Dension Audio Systems Ltd.
+ * Author: Zoltan Devai
+ *
+ * Based on the WM87xx drivers by Liam Girdwood and Richard Purdie
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include <sound/uda134x.h>
+#include <sound/l3.h>
+
+#include "uda134x.h"
+
+
+#define UDA134X_RATES SNDRV_PCM_RATE_8000_48000
+#define UDA134X_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
+		SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE)
+
+struct uda134x_priv {
+	int sysclk;
+	int dai_fmt;
+
+	struct snd_pcm_substream *master_substream;
+	struct snd_pcm_substream *slave_substream;
+};
+
+/* In-data addresses are hard-coded into the reg-cache values */
+static const char uda134x_reg[UDA134X_REGS_NUM] = {
+	/* Extended address registers */
+	0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+	/* Status, data regs */
+	0x00, 0x83, 0x00, 0x40, 0x80, 0xC0, 0x00,
+};
+
+/*
+ * The codec has no support for reading its registers except for peak level...
+ */
+static inline unsigned int uda134x_read_reg_cache(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	u8 *cache = codec->reg_cache;
+
+	if (reg >= UDA134X_REGS_NUM)
+		return -1;
+	return cache[reg];
+}
+
+/*
+ * Write the register cache
+ */
+static inline void uda134x_write_reg_cache(struct snd_soc_codec *codec,
+	u8 reg, unsigned int value)
+{
+	u8 *cache = codec->reg_cache;
+
+	if (reg >= UDA134X_REGS_NUM)
+		return;
+	cache[reg] = value;
+}
+
+/*
+ * Write to the uda134x registers
+ *
+ */
+static int uda134x_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int value)
+{
+	int ret;
+	u8 addr;
+	u8 data = value;
+	struct uda134x_platform_data *pd = codec->control_data;
+
+	pr_debug("%s reg: %02X, value:%02X\n", __func__, reg, value);
+
+	if (reg >= UDA134X_REGS_NUM) {
+		printk(KERN_ERR "%s unknown register: reg: %u",
+		       __func__, reg);
+		return -EINVAL;
+	}
+
+	uda134x_write_reg_cache(codec, reg, value);
+
+	switch (reg) {
+	case UDA134X_STATUS0:
+	case UDA134X_STATUS1:
+		addr = UDA134X_STATUS_ADDR;
+		break;
+	case UDA134X_DATA000:
+	case UDA134X_DATA001:
+	case UDA134X_DATA010:
+	case UDA134X_DATA011:
+		addr = UDA134X_DATA0_ADDR;
+		break;
+	case UDA134X_DATA1:
+		addr = UDA134X_DATA1_ADDR;
+		break;
+	default:
+		/* It's an extended address register */
+		addr =  (reg | UDA134X_EXTADDR_PREFIX);
+
+		ret = l3_write(&pd->l3,
+			       UDA134X_DATA0_ADDR, &addr, 1);
+		if (ret != 1)
+			return -EIO;
+
+		addr = UDA134X_DATA0_ADDR;
+		data = (value | UDA134X_EXTDATA_PREFIX);
+		break;
+	}
+
+	ret = l3_write(&pd->l3,
+		       addr, &data, 1);
+	if (ret != 1)
+		return -EIO;
+
+	return 0;
+}
+
+static inline void uda134x_reset(struct snd_soc_codec *codec)
+{
+	u8 reset_reg = uda134x_read_reg_cache(codec, UDA134X_STATUS0);
+	uda134x_write(codec, UDA134X_STATUS0, reset_reg | (1<<6));
+	msleep(1);
+	uda134x_write(codec, UDA134X_STATUS0, reset_reg & ~(1<<6));
+}
+
+static int uda134x_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u8 mute_reg = uda134x_read_reg_cache(codec, UDA134X_DATA010);
+
+	pr_debug("%s mute: %d\n", __func__, mute);
+
+	if (mute)
+		mute_reg |= (1<<2);
+	else
+		mute_reg &= ~(1<<2);
+
+	uda134x_write(codec, UDA134X_DATA010, mute_reg);
+
+	return 0;
+}
+
+static int uda134x_startup(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec =rtd->codec;
+	struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+	struct snd_pcm_runtime *master_runtime;
+
+	if (uda134x->master_substream) {
+		master_runtime = uda134x->master_substream->runtime;
+
+		pr_debug("%s constraining to %d bits at %d\n", __func__,
+			 master_runtime->sample_bits,
+			 master_runtime->rate);
+
+		snd_pcm_hw_constraint_minmax(substream->runtime,
+					     SNDRV_PCM_HW_PARAM_RATE,
+					     master_runtime->rate,
+					     master_runtime->rate);
+
+		snd_pcm_hw_constraint_minmax(substream->runtime,
+					     SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+					     master_runtime->sample_bits,
+					     master_runtime->sample_bits);
+
+		uda134x->slave_substream = substream;
+	} else
+		uda134x->master_substream = substream;
+
+	return 0;
+}
+
+static void uda134x_shutdown(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+
+	if (uda134x->master_substream == substream)
+		uda134x->master_substream = uda134x->slave_substream;
+
+	uda134x->slave_substream = NULL;
+}
+
+static int uda134x_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+	u8 hw_params;
+
+	if (substream == uda134x->slave_substream) {
+		pr_debug("%s ignoring hw_params for slave substream\n",
+			 __func__);
+		return 0;
+	}
+
+	hw_params = uda134x_read_reg_cache(codec, UDA134X_STATUS0);
+	hw_params &= STATUS0_SYSCLK_MASK;
+	hw_params &= STATUS0_DAIFMT_MASK;
+
+	pr_debug("%s sysclk: %d, rate:%d\n", __func__,
+		 uda134x->sysclk, params_rate(params));
+
+	/* set SYSCLK / fs ratio */
+	switch (uda134x->sysclk / params_rate(params)) {
+	case 512:
+		break;
+	case 384:
+		hw_params |= (1<<4);
+		break;
+	case 256:
+		hw_params |= (1<<5);
+		break;
+	default:
+		printk(KERN_ERR "%s unsupported fs\n", __func__);
+		return -EINVAL;
+	}
+
+	pr_debug("%s dai_fmt: %d, params_format:%d\n", __func__,
+		 uda134x->dai_fmt, params_format(params));
+
+	/* set DAI format and word length */
+	switch (uda134x->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		switch (params_format(params)) {
+		case SNDRV_PCM_FORMAT_S16_LE:
+			hw_params |= (1<<1);
+			break;
+		case SNDRV_PCM_FORMAT_S18_3LE:
+			hw_params |= (1<<2);
+			break;
+		case SNDRV_PCM_FORMAT_S20_3LE:
+			hw_params |= ((1<<2) | (1<<1));
+			break;
+		default:
+			printk(KERN_ERR "%s unsupported format (right)\n",
+			       __func__);
+			return -EINVAL;
+		}
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		hw_params |= (1<<3);
+		break;
+	default:
+		printk(KERN_ERR "%s unsupported format\n", __func__);
+		return -EINVAL;
+	}
+
+	uda134x_write(codec, UDA134X_STATUS0, hw_params);
+
+	return 0;
+}
+
+static int uda134x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+				  int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+
+	pr_debug("%s clk_id: %d, freq: %u, dir: %d\n", __func__,
+		 clk_id, freq, dir);
+
+	/* Anything between 256fs*8Khz and 512fs*48Khz should be acceptable
+	   because the codec is slave. Of course limitations of the clock
+	   master (the IIS controller) apply.
+	   We'll error out on set_hw_params if it's not OK */
+	if ((freq >= (256 * 8000)) && (freq <= (512 * 48000))) {
+		uda134x->sysclk = freq;
+		return 0;
+	}
+
+	printk(KERN_ERR "%s unsupported sysclk\n", __func__);
+	return -EINVAL;
+}
+
+static int uda134x_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			       unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+
+	pr_debug("%s fmt: %08X\n", __func__, fmt);
+
+	/* codec supports only full slave mode */
+	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) {
+		printk(KERN_ERR "%s unsupported slave mode\n", __func__);
+		return -EINVAL;
+	}
+
+	/* no support for clock inversion */
+	if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) {
+		printk(KERN_ERR "%s unsupported clock inversion\n", __func__);
+		return -EINVAL;
+	}
+
+	/* We can't setup DAI format here as it depends on the word bit num */
+	/* so let's just store the value for later */
+	uda134x->dai_fmt = fmt;
+
+	return 0;
+}
+
+static int uda134x_set_bias_level(struct snd_soc_codec *codec,
+				  enum snd_soc_bias_level level)
+{
+	u8 reg;
+	struct uda134x_platform_data *pd = codec->control_data;
+	int i;
+	u8 *cache = codec->reg_cache;
+
+	pr_debug("%s bias level %d\n", __func__, level);
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		/* ADC, DAC on */
+		switch (pd->model) {
+		case UDA134X_UDA1340:
+		case UDA134X_UDA1344:
+		case UDA134X_UDA1345:
+			reg = uda134x_read_reg_cache(codec, UDA134X_DATA011);
+			uda134x_write(codec, UDA134X_DATA011, reg | 0x03);
+			break;
+		case UDA134X_UDA1341:
+			reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1);
+			uda134x_write(codec, UDA134X_STATUS1, reg | 0x03);
+			break;
+		default:
+			printk(KERN_ERR "UDA134X SoC codec: "
+			       "unsupported model %d\n", pd->model);
+			return -EINVAL;
+		}
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		/* power on */
+		if (pd->power) {
+			pd->power(1);
+			/* Sync reg_cache with the hardware */
+			for (i = 0; i < ARRAY_SIZE(uda134x_reg); i++)
+				codec->driver->write(codec, i, *cache++);
+		}
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		/* ADC, DAC power off */
+		switch (pd->model) {
+		case UDA134X_UDA1340:
+		case UDA134X_UDA1344:
+		case UDA134X_UDA1345:
+			reg = uda134x_read_reg_cache(codec, UDA134X_DATA011);
+			uda134x_write(codec, UDA134X_DATA011, reg & ~(0x03));
+			break;
+		case UDA134X_UDA1341:
+			reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1);
+			uda134x_write(codec, UDA134X_STATUS1, reg & ~(0x03));
+			break;
+		default:
+			printk(KERN_ERR "UDA134X SoC codec: "
+			       "unsupported model %d\n", pd->model);
+			return -EINVAL;
+		}
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* power off */
+		if (pd->power)
+			pd->power(0);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+static const char *uda134x_dsp_setting[] = {"Flat", "Minimum1",
+					    "Minimum2", "Maximum"};
+static const char *uda134x_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const char *uda134x_mixmode[] = {"Differential", "Analog1",
+					"Analog2", "Both"};
+
+static const struct soc_enum uda134x_mixer_enum[] = {
+SOC_ENUM_SINGLE(UDA134X_DATA010, 0, 0x04, uda134x_dsp_setting),
+SOC_ENUM_SINGLE(UDA134X_DATA010, 3, 0x04, uda134x_deemph),
+SOC_ENUM_SINGLE(UDA134X_EA010, 0, 0x04, uda134x_mixmode),
+};
+
+static const struct snd_kcontrol_new uda1341_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+SOC_SINGLE("Capture Volume", UDA134X_EA010, 2, 0x07, 0),
+SOC_SINGLE("Analog1 Volume", UDA134X_EA000, 0, 0x1F, 1),
+SOC_SINGLE("Analog2 Volume", UDA134X_EA001, 0, 0x1F, 1),
+
+SOC_SINGLE("Mic Sensitivity", UDA134X_EA010, 2, 7, 0),
+SOC_SINGLE("Mic Volume", UDA134X_EA101, 0, 0x1F, 0),
+
+SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),
+SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0),
+
+SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]),
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+SOC_ENUM("Input Mux", uda134x_mixer_enum[2]),
+
+SOC_SINGLE("AGC Switch", UDA134X_EA100, 4, 1, 0),
+SOC_SINGLE("AGC Target Volume", UDA134X_EA110, 0, 0x03, 1),
+SOC_SINGLE("AGC Timing", UDA134X_EA110, 2, 0x07, 0),
+
+SOC_SINGLE("DAC +6dB Switch", UDA134X_STATUS1, 6, 1, 0),
+SOC_SINGLE("ADC +6dB Switch", UDA134X_STATUS1, 5, 1, 0),
+SOC_SINGLE("ADC Polarity Switch", UDA134X_STATUS1, 4, 1, 0),
+SOC_SINGLE("DAC Polarity Switch", UDA134X_STATUS1, 3, 1, 0),
+SOC_SINGLE("Double Speed Playback Switch", UDA134X_STATUS1, 2, 1, 0),
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new uda1340_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+
+SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),
+SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0),
+
+SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]),
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new uda1345_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+static struct snd_soc_dai_ops uda134x_dai_ops = {
+	.startup	= uda134x_startup,
+	.shutdown	= uda134x_shutdown,
+	.hw_params	= uda134x_hw_params,
+	.digital_mute	= uda134x_mute,
+	.set_sysclk	= uda134x_set_dai_sysclk,
+	.set_fmt	= uda134x_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver uda134x_dai = {
+	.name = "uda134x-hifi",
+	/* playback capabilities */
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = UDA134X_RATES,
+		.formats = UDA134X_FORMATS,
+	},
+	/* capture capabilities */
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = UDA134X_RATES,
+		.formats = UDA134X_FORMATS,
+	},
+	/* pcm operations */
+	.ops = &uda134x_dai_ops,
+};
+
+static int uda134x_soc_probe(struct snd_soc_codec *codec)
+{
+	struct uda134x_priv *uda134x;
+	struct uda134x_platform_data *pd = dev_get_drvdata(codec->card->dev);
+	int ret;
+
+	printk(KERN_INFO "UDA134X SoC Audio Codec\n");
+
+	if (!pd) {
+		printk(KERN_ERR "UDA134X SoC codec: "
+		       "missing L3 bitbang function\n");
+		return -ENODEV;
+	}
+
+	switch (pd->model) {
+	case UDA134X_UDA1340:
+	case UDA134X_UDA1341:
+	case UDA134X_UDA1344:
+	case UDA134X_UDA1345:
+		break;
+	default:
+		printk(KERN_ERR "UDA134X SoC codec: "
+		       "unsupported model %d\n",
+			pd->model);
+		return -EINVAL;
+	}
+
+	uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL);
+	if (uda134x == NULL)
+		return -ENOMEM;
+	snd_soc_codec_set_drvdata(codec, uda134x);
+
+	codec->control_data = pd;
+
+	if (pd->power)
+		pd->power(1);
+
+	uda134x_reset(codec);
+
+	if (pd->is_powered_on_standby)
+		uda134x_set_bias_level(codec, SND_SOC_BIAS_ON);
+	else
+		uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	switch (pd->model) {
+	case UDA134X_UDA1340:
+	case UDA134X_UDA1344:
+		ret = snd_soc_add_controls(codec, uda1340_snd_controls,
+					ARRAY_SIZE(uda1340_snd_controls));
+	break;
+	case UDA134X_UDA1341:
+		ret = snd_soc_add_controls(codec, uda1341_snd_controls,
+					ARRAY_SIZE(uda1341_snd_controls));
+	break;
+	case UDA134X_UDA1345:
+		ret = snd_soc_add_controls(codec, uda1345_snd_controls,
+					ARRAY_SIZE(uda1345_snd_controls));
+	break;
+	default:
+		printk(KERN_ERR "%s unknown codec type: %d",
+			__func__, pd->model);
+		kfree(uda134x);
+		return -EINVAL;
+	}
+
+	if (ret < 0) {
+		printk(KERN_ERR "UDA134X: failed to register controls\n");
+		kfree(uda134x);
+		return ret;
+	}
+
+	return 0;
+}
+
+/* power down chip */
+static int uda134x_soc_remove(struct snd_soc_codec *codec)
+{
+	struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec);
+
+	uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	kfree(uda134x);
+	return 0;
+}
+
+#if defined(CONFIG_PM)
+static int uda134x_soc_suspend(struct snd_soc_codec *codec,
+						pm_message_t state)
+{
+	uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int uda134x_soc_resume(struct snd_soc_codec *codec)
+{
+	uda134x_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+	uda134x_set_bias_level(codec, SND_SOC_BIAS_ON);
+	return 0;
+}
+#else
+#define uda134x_soc_suspend NULL
+#define uda134x_soc_resume NULL
+#endif /* CONFIG_PM */
+
+static struct snd_soc_codec_driver soc_codec_dev_uda134x = {
+	.probe =        uda134x_soc_probe,
+	.remove =       uda134x_soc_remove,
+	.suspend =      uda134x_soc_suspend,
+	.resume =       uda134x_soc_resume,
+	.reg_cache_size = sizeof(uda134x_reg),
+	.reg_word_size = sizeof(u8),
+	.reg_cache_default = uda134x_reg,
+	.reg_cache_step = 1,
+	.read = uda134x_read_reg_cache,
+	.write = uda134x_write,
+#ifdef POWER_OFF_ON_STANDBY
+	.set_bias_level = uda134x_set_bias_level,
+#endif
+};
+
+static int __devinit uda134x_codec_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_uda134x, &uda134x_dai, 1);
+}
+
+static int __devexit uda134x_codec_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver uda134x_codec_driver = {
+	.driver = {
+		.name = "uda134x-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe = uda134x_codec_probe,
+	.remove = __devexit_p(uda134x_codec_remove),
+};
+
+static int __init uda134x_codec_init(void)
+{
+	return platform_driver_register(&uda134x_codec_driver);
+}
+module_init(uda134x_codec_init);
+
+static void __exit uda134x_codec_exit(void)
+{
+	platform_driver_unregister(&uda134x_codec_driver);
+}
+module_exit(uda134x_codec_exit);
+
+MODULE_DESCRIPTION("UDA134X ALSA soc codec driver");
+MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/uda134x.h b/linux/sound/soc/codecs/uda134x.h
new file mode 100644
index 0000000..9faae06
--- /dev/null
+++ b/linux/sound/soc/codecs/uda134x.h
@@ -0,0 +1,34 @@
+#ifndef _UDA134X_CODEC_H
+#define _UDA134X_CODEC_H
+
+#define UDA134X_L3ADDR	5
+#define UDA134X_DATA0_ADDR	((UDA134X_L3ADDR << 2) | 0)
+#define UDA134X_DATA1_ADDR	((UDA134X_L3ADDR << 2) | 1)
+#define UDA134X_STATUS_ADDR	((UDA134X_L3ADDR << 2) | 2)
+
+#define UDA134X_EXTADDR_PREFIX	0xC0
+#define UDA134X_EXTDATA_PREFIX	0xE0
+
+/* UDA134X registers */
+#define UDA134X_EA000	0
+#define UDA134X_EA001	1
+#define UDA134X_EA010	2
+#define UDA134X_EA011	3
+#define UDA134X_EA100	4
+#define UDA134X_EA101	5
+#define UDA134X_EA110	6
+#define UDA134X_EA111	7
+#define UDA134X_STATUS0 8
+#define UDA134X_STATUS1 9
+#define UDA134X_DATA000 10
+#define UDA134X_DATA001 11
+#define UDA134X_DATA010 12
+#define UDA134X_DATA011 13
+#define UDA134X_DATA1   14
+
+#define UDA134X_REGS_NUM 15
+
+#define STATUS0_DAIFMT_MASK (~(7<<1))
+#define STATUS0_SYSCLK_MASK (~(3<<4))
+
+#endif
diff --git a/linux/sound/soc/codecs/uda1380.c b/linux/sound/soc/codecs/uda1380.c
new file mode 100644
index 0000000..0c6c725
--- /dev/null
+++ b/linux/sound/soc/codecs/uda1380.c
@@ -0,0 +1,887 @@
+/*
+ * uda1380.c - Philips UDA1380 ALSA SoC audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (c) 2007-2009 Philipp Zabel <philipp.zabel@gmail.com>
+ *
+ * Modified by Richard Purdie <richard@openedhand.com> to fit into SoC
+ * codec model.
+ *
+ * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org>
+ * Copyright 2005 Openedhand Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/uda1380.h>
+
+#include "uda1380.h"
+
+/* codec private data */
+struct uda1380_priv {
+	struct snd_soc_codec *codec;
+	u16 reg_cache[UDA1380_CACHEREGNUM];
+	unsigned int dac_clk;
+	struct work_struct work;
+	void *control_data;
+};
+
+/*
+ * uda1380 register cache
+ */
+static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = {
+	0x0502, 0x0000, 0x0000, 0x3f3f,
+	0x0202, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0xff00, 0x0000, 0x4800,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x8000, 0x0002, 0x0000,
+};
+
+static unsigned long uda1380_cache_dirty;
+
+/*
+ * read uda1380 register cache
+ */
+static inline unsigned int uda1380_read_reg_cache(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+	if (reg == UDA1380_RESET)
+		return 0;
+	if (reg >= UDA1380_CACHEREGNUM)
+		return -1;
+	return cache[reg];
+}
+
+/*
+ * write uda1380 register cache
+ */
+static inline void uda1380_write_reg_cache(struct snd_soc_codec *codec,
+	u16 reg, unsigned int value)
+{
+	u16 *cache = codec->reg_cache;
+
+	if (reg >= UDA1380_CACHEREGNUM)
+		return;
+	if ((reg >= 0x10) && (cache[reg] != value))
+		set_bit(reg - 0x10, &uda1380_cache_dirty);
+	cache[reg] = value;
+}
+
+/*
+ * write to the UDA1380 register space
+ */
+static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int value)
+{
+	u8 data[3];
+
+	/* data is
+	 *   data[0] is register offset
+	 *   data[1] is MS byte
+	 *   data[2] is LS byte
+	 */
+	data[0] = reg;
+	data[1] = (value & 0xff00) >> 8;
+	data[2] = value & 0x00ff;
+
+	uda1380_write_reg_cache(codec, reg, value);
+
+	/* the interpolator & decimator regs must only be written when the
+	 * codec DAI is active.
+	 */
+	if (!codec->active && (reg >= UDA1380_MVOL))
+		return 0;
+	pr_debug("uda1380: hw write %x val %x\n", reg, value);
+	if (codec->hw_write(codec->control_data, data, 3) == 3) {
+		unsigned int val;
+		i2c_master_send(codec->control_data, data, 1);
+		i2c_master_recv(codec->control_data, data, 2);
+		val = (data[0]<<8) | data[1];
+		if (val != value) {
+			pr_debug("uda1380: READ BACK VAL %x\n",
+					(data[0]<<8) | data[1]);
+			return -EIO;
+		}
+		if (reg >= 0x10)
+			clear_bit(reg - 0x10, &uda1380_cache_dirty);
+		return 0;
+	} else
+		return -EIO;
+}
+
+static void uda1380_sync_cache(struct snd_soc_codec *codec)
+{
+	int reg;
+	u8 data[3];
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware */
+	for (reg = 0; reg < UDA1380_MVOL; reg++) {
+		data[0] = reg;
+		data[1] = (cache[reg] & 0xff00) >> 8;
+		data[2] = cache[reg] & 0x00ff;
+		if (codec->hw_write(codec->control_data, data, 3) != 3)
+			dev_err(codec->dev, "%s: write to reg 0x%x failed\n",
+				__func__, reg);
+	}
+}
+
+static int uda1380_reset(struct snd_soc_codec *codec)
+{
+	struct uda1380_platform_data *pdata = codec->dev->platform_data;
+
+	if (gpio_is_valid(pdata->gpio_reset)) {
+		gpio_set_value(pdata->gpio_reset, 1);
+		mdelay(1);
+		gpio_set_value(pdata->gpio_reset, 0);
+	} else {
+		u8 data[3];
+
+		data[0] = UDA1380_RESET;
+		data[1] = 0;
+		data[2] = 0;
+
+		if (codec->hw_write(codec->control_data, data, 3) != 3) {
+			dev_err(codec->dev, "%s: failed\n", __func__);
+			return -EIO;
+		}
+	}
+
+	return 0;
+}
+
+static void uda1380_flush_work(struct work_struct *work)
+{
+	struct uda1380_priv *uda1380 = container_of(work, struct uda1380_priv, work);
+	struct snd_soc_codec *uda1380_codec = uda1380->codec;
+	int bit, reg;
+
+	for_each_set_bit(bit, &uda1380_cache_dirty, UDA1380_CACHEREGNUM - 0x10) {
+		reg = 0x10 + bit;
+		pr_debug("uda1380: flush reg %x val %x:\n", reg,
+				uda1380_read_reg_cache(uda1380_codec, reg));
+		uda1380_write(uda1380_codec, reg,
+				uda1380_read_reg_cache(uda1380_codec, reg));
+		clear_bit(bit, &uda1380_cache_dirty);
+	}
+
+}
+
+/* declarations of ALSA reg_elem_REAL controls */
+static const char *uda1380_deemp[] = {
+	"None",
+	"32kHz",
+	"44.1kHz",
+	"48kHz",
+	"96kHz",
+};
+static const char *uda1380_input_sel[] = {
+	"Line",
+	"Mic + Line R",
+	"Line L",
+	"Mic",
+};
+static const char *uda1380_output_sel[] = {
+	"DAC",
+	"Analog Mixer",
+};
+static const char *uda1380_spf_mode[] = {
+	"Flat",
+	"Minimum1",
+	"Minimum2",
+	"Maximum"
+};
+static const char *uda1380_capture_sel[] = {
+	"ADC",
+	"Digital Mixer"
+};
+static const char *uda1380_sel_ns[] = {
+	"3rd-order",
+	"5th-order"
+};
+static const char *uda1380_mix_control[] = {
+	"off",
+	"PCM only",
+	"before sound processing",
+	"after sound processing"
+};
+static const char *uda1380_sdet_setting[] = {
+	"3200",
+	"4800",
+	"9600",
+	"19200"
+};
+static const char *uda1380_os_setting[] = {
+	"single-speed",
+	"double-speed (no mixing)",
+	"quad-speed (no mixing)"
+};
+
+static const struct soc_enum uda1380_deemp_enum[] = {
+	SOC_ENUM_SINGLE(UDA1380_DEEMP, 8, 5, uda1380_deemp),
+	SOC_ENUM_SINGLE(UDA1380_DEEMP, 0, 5, uda1380_deemp),
+};
+static const struct soc_enum uda1380_input_sel_enum =
+	SOC_ENUM_SINGLE(UDA1380_ADC, 2, 4, uda1380_input_sel);		/* SEL_MIC, SEL_LNA */
+static const struct soc_enum uda1380_output_sel_enum =
+	SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel);		/* R02_EN_AVC */
+static const struct soc_enum uda1380_spf_enum =
+	SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode);		/* M */
+static const struct soc_enum uda1380_capture_sel_enum =
+	SOC_ENUM_SINGLE(UDA1380_IFACE, 6, 2, uda1380_capture_sel);	/* SEL_SOURCE */
+static const struct soc_enum uda1380_sel_ns_enum =
+	SOC_ENUM_SINGLE(UDA1380_MIXER, 14, 2, uda1380_sel_ns);		/* SEL_NS */
+static const struct soc_enum uda1380_mix_enum =
+	SOC_ENUM_SINGLE(UDA1380_MIXER, 12, 4, uda1380_mix_control);	/* MIX, MIX_POS */
+static const struct soc_enum uda1380_sdet_enum =
+	SOC_ENUM_SINGLE(UDA1380_MIXER, 4, 4, uda1380_sdet_setting);	/* SD_VALUE */
+static const struct soc_enum uda1380_os_enum =
+	SOC_ENUM_SINGLE(UDA1380_MIXER, 0, 3, uda1380_os_setting);	/* OS */
+
+/*
+ * from -48 dB in 1.5 dB steps (mute instead of -49.5 dB)
+ */
+static DECLARE_TLV_DB_SCALE(amix_tlv, -4950, 150, 1);
+
+/*
+ * from -78 dB in 1 dB steps (3 dB steps, really. LSB are ignored),
+ * from -66 dB in 0.5 dB steps (2 dB steps, really) and
+ * from -52 dB in 0.25 dB steps
+ */
+static const unsigned int mvol_tlv[] = {
+	TLV_DB_RANGE_HEAD(3),
+	0, 15, TLV_DB_SCALE_ITEM(-8200, 100, 1),
+	16, 43, TLV_DB_SCALE_ITEM(-6600, 50, 0),
+	44, 252, TLV_DB_SCALE_ITEM(-5200, 25, 0),
+};
+
+/*
+ * from -72 dB in 1.5 dB steps (6 dB steps really),
+ * from -66 dB in 0.75 dB steps (3 dB steps really),
+ * from -60 dB in 0.5 dB steps (2 dB steps really) and
+ * from -46 dB in 0.25 dB steps
+ */
+static const unsigned int vc_tlv[] = {
+	TLV_DB_RANGE_HEAD(4),
+	0, 7, TLV_DB_SCALE_ITEM(-7800, 150, 1),
+	8, 15, TLV_DB_SCALE_ITEM(-6600, 75, 0),
+	16, 43, TLV_DB_SCALE_ITEM(-6000, 50, 0),
+	44, 228, TLV_DB_SCALE_ITEM(-4600, 25, 0),
+};
+
+/* from 0 to 6 dB in 2 dB steps if SPF mode != flat */
+static DECLARE_TLV_DB_SCALE(tr_tlv, 0, 200, 0);
+
+/* from 0 to 24 dB in 2 dB steps, if SPF mode == maximum, otherwise cuts
+ * off at 18 dB max) */
+static DECLARE_TLV_DB_SCALE(bb_tlv, 0, 200, 0);
+
+/* from -63 to 24 dB in 0.5 dB steps (-128...48) */
+static DECLARE_TLV_DB_SCALE(dec_tlv, -6400, 50, 1);
+
+/* from 0 to 24 dB in 3 dB steps */
+static DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0);
+
+/* from 0 to 30 dB in 2 dB steps */
+static DECLARE_TLV_DB_SCALE(vga_tlv, 0, 200, 0);
+
+static const struct snd_kcontrol_new uda1380_snd_controls[] = {
+	SOC_DOUBLE_TLV("Analog Mixer Volume", UDA1380_AMIX, 0, 8, 44, 1, amix_tlv),	/* AVCR, AVCL */
+	SOC_DOUBLE_TLV("Master Playback Volume", UDA1380_MVOL, 0, 8, 252, 1, mvol_tlv),	/* MVCL, MVCR */
+	SOC_SINGLE_TLV("ADC Playback Volume", UDA1380_MIXVOL, 8, 228, 1, vc_tlv),	/* VC2 */
+	SOC_SINGLE_TLV("PCM Playback Volume", UDA1380_MIXVOL, 0, 228, 1, vc_tlv),	/* VC1 */
+	SOC_ENUM("Sound Processing Filter", uda1380_spf_enum),				/* M */
+	SOC_DOUBLE_TLV("Tone Control - Treble", UDA1380_MODE, 4, 12, 3, 0, tr_tlv), 	/* TRL, TRR */
+	SOC_DOUBLE_TLV("Tone Control - Bass", UDA1380_MODE, 0, 8, 15, 0, bb_tlv),	/* BBL, BBR */
+/**/	SOC_SINGLE("Master Playback Switch", UDA1380_DEEMP, 14, 1, 1),		/* MTM */
+	SOC_SINGLE("ADC Playback Switch", UDA1380_DEEMP, 11, 1, 1),		/* MT2 from decimation filter */
+	SOC_ENUM("ADC Playback De-emphasis", uda1380_deemp_enum[0]),		/* DE2 */
+	SOC_SINGLE("PCM Playback Switch", UDA1380_DEEMP, 3, 1, 1),		/* MT1, from digital data input */
+	SOC_ENUM("PCM Playback De-emphasis", uda1380_deemp_enum[1]),		/* DE1 */
+	SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0),	/* DA_POL_INV */
+	SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum),				/* SEL_NS */
+	SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum),		/* MIX_POS, MIX */
+	SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0),		/* SDET_ON */
+	SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum),		/* SD_VALUE */
+	SOC_ENUM("Oversampling Input", uda1380_os_enum),			/* OS */
+	SOC_DOUBLE_S8_TLV("ADC Capture Volume", UDA1380_DEC, -128, 48, dec_tlv),	/* ML_DEC, MR_DEC */
+/**/	SOC_SINGLE("ADC Capture Switch", UDA1380_PGA, 15, 1, 1),		/* MT_ADC */
+	SOC_DOUBLE_TLV("Line Capture Volume", UDA1380_PGA, 0, 8, 8, 0, pga_tlv), /* PGA_GAINCTRLL, PGA_GAINCTRLR */
+	SOC_SINGLE("ADC Polarity inverting Switch", UDA1380_ADC, 12, 1, 0),	/* ADCPOL_INV */
+	SOC_SINGLE_TLV("Mic Capture Volume", UDA1380_ADC, 8, 15, 0, vga_tlv),	/* VGA_CTRL */
+	SOC_SINGLE("DC Filter Bypass Switch", UDA1380_ADC, 1, 1, 0),		/* SKIP_DCFIL (before decimator) */
+	SOC_SINGLE("DC Filter Enable Switch", UDA1380_ADC, 0, 1, 0),		/* EN_DCFIL (at output of decimator) */
+	SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0),			/* TODO: enum, see table 62 */
+	SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1),			/* AGC_LEVEL */
+	/* -5.5, -8, -11.5, -14 dBFS */
+	SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new uda1380_input_mux_control =
+	SOC_DAPM_ENUM("Route", uda1380_input_sel_enum);
+
+/* Output mux */
+static const struct snd_kcontrol_new uda1380_output_mux_control =
+	SOC_DAPM_ENUM("Route", uda1380_output_sel_enum);
+
+/* Capture mux */
+static const struct snd_kcontrol_new uda1380_capture_mux_control =
+	SOC_DAPM_ENUM("Route", uda1380_capture_sel_enum);
+
+
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+	SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0,
+		&uda1380_input_mux_control),
+	SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, 0, 0,
+		&uda1380_output_mux_control),
+	SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0,
+		&uda1380_capture_mux_control),
+	SND_SOC_DAPM_PGA("Left PGA", UDA1380_PM, 3, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right PGA", UDA1380_PM, 1, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Mic LNA", UDA1380_PM, 4, 0, NULL, 0),
+	SND_SOC_DAPM_ADC("Left ADC", "Left Capture", UDA1380_PM, 2, 0),
+	SND_SOC_DAPM_ADC("Right ADC", "Right Capture", UDA1380_PM, 0, 0),
+	SND_SOC_DAPM_INPUT("VINM"),
+	SND_SOC_DAPM_INPUT("VINL"),
+	SND_SOC_DAPM_INPUT("VINR"),
+	SND_SOC_DAPM_MIXER("Analog Mixer", UDA1380_PM, 6, 0, NULL, 0),
+	SND_SOC_DAPM_OUTPUT("VOUTLHP"),
+	SND_SOC_DAPM_OUTPUT("VOUTRHP"),
+	SND_SOC_DAPM_OUTPUT("VOUTL"),
+	SND_SOC_DAPM_OUTPUT("VOUTR"),
+	SND_SOC_DAPM_DAC("DAC", "Playback", UDA1380_PM, 10, 0),
+	SND_SOC_DAPM_PGA("HeadPhone Driver", UDA1380_PM, 13, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+
+	/* output mux */
+	{"HeadPhone Driver", NULL, "Output Mux"},
+	{"VOUTR", NULL, "Output Mux"},
+	{"VOUTL", NULL, "Output Mux"},
+
+	{"Analog Mixer", NULL, "VINR"},
+	{"Analog Mixer", NULL, "VINL"},
+	{"Analog Mixer", NULL, "DAC"},
+
+	{"Output Mux", "DAC", "DAC"},
+	{"Output Mux", "Analog Mixer", "Analog Mixer"},
+
+	/* {"DAC", "Digital Mixer", "I2S" } */
+
+	/* headphone driver */
+	{"VOUTLHP", NULL, "HeadPhone Driver"},
+	{"VOUTRHP", NULL, "HeadPhone Driver"},
+
+	/* input mux */
+	{"Left ADC", NULL, "Input Mux"},
+	{"Input Mux", "Mic", "Mic LNA"},
+	{"Input Mux", "Mic + Line R", "Mic LNA"},
+	{"Input Mux", "Line L", "Left PGA"},
+	{"Input Mux", "Line", "Left PGA"},
+
+	/* right input */
+	{"Right ADC", "Mic + Line R", "Right PGA"},
+	{"Right ADC", "Line", "Right PGA"},
+
+	/* inputs */
+	{"Mic LNA", NULL, "VINM"},
+	{"Left PGA", NULL, "VINL"},
+	{"Right PGA", NULL, "VINR"},
+};
+
+static int uda1380_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, uda1380_dapm_widgets,
+				  ARRAY_SIZE(uda1380_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+static int uda1380_set_dai_fmt_both(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	int iface;
+
+	/* set up DAI based upon fmt */
+	iface = uda1380_read_reg_cache(codec, UDA1380_IFACE);
+	iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK);
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= R01_SFORI_I2S | R01_SFORO_I2S;
+		break;
+	case SND_SOC_DAIFMT_LSB:
+		iface |= R01_SFORI_LSB16 | R01_SFORO_LSB16;
+		break;
+	case SND_SOC_DAIFMT_MSB:
+		iface |= R01_SFORI_MSB | R01_SFORO_MSB;
+	}
+
+	/* DATAI is slave only, so in single-link mode, this has to be slave */
+	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+		return -EINVAL;
+
+	uda1380_write(codec, UDA1380_IFACE, iface);
+
+	return 0;
+}
+
+static int uda1380_set_dai_fmt_playback(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	int iface;
+
+	/* set up DAI based upon fmt */
+	iface = uda1380_read_reg_cache(codec, UDA1380_IFACE);
+	iface &= ~R01_SFORI_MASK;
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= R01_SFORI_I2S;
+		break;
+	case SND_SOC_DAIFMT_LSB:
+		iface |= R01_SFORI_LSB16;
+		break;
+	case SND_SOC_DAIFMT_MSB:
+		iface |= R01_SFORI_MSB;
+	}
+
+	/* DATAI is slave only, so this has to be slave */
+	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+		return -EINVAL;
+
+	uda1380_write(codec, UDA1380_IFACE, iface);
+
+	return 0;
+}
+
+static int uda1380_set_dai_fmt_capture(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	int iface;
+
+	/* set up DAI based upon fmt */
+	iface = uda1380_read_reg_cache(codec, UDA1380_IFACE);
+	iface &= ~(R01_SIM | R01_SFORO_MASK);
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= R01_SFORO_I2S;
+		break;
+	case SND_SOC_DAIFMT_LSB:
+		iface |= R01_SFORO_LSB16;
+		break;
+	case SND_SOC_DAIFMT_MSB:
+		iface |= R01_SFORO_MSB;
+	}
+
+	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM)
+		iface |= R01_SIM;
+
+	uda1380_write(codec, UDA1380_IFACE, iface);
+
+	return 0;
+}
+
+static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd,
+		struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct uda1380_priv *uda1380 = snd_soc_codec_get_drvdata(codec);
+	int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		uda1380_write_reg_cache(codec, UDA1380_MIXER,
+					mixer & ~R14_SILENCE);
+		schedule_work(&uda1380->work);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		uda1380_write_reg_cache(codec, UDA1380_MIXER,
+					mixer | R14_SILENCE);
+		schedule_work(&uda1380->work);
+		break;
+	}
+	return 0;
+}
+
+static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+
+	/* set WSPLL power and divider if running from this clock */
+	if (clk & R00_DAC_CLK) {
+		int rate = params_rate(params);
+		u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+		clk &= ~0x3; /* clear SEL_LOOP_DIV */
+		switch (rate) {
+		case 6250 ... 12500:
+			clk |= 0x0;
+			break;
+		case 12501 ... 25000:
+			clk |= 0x1;
+			break;
+		case 25001 ... 50000:
+			clk |= 0x2;
+			break;
+		case 50001 ... 100000:
+			clk |= 0x3;
+			break;
+		}
+		uda1380_write(codec, UDA1380_PM, R02_PON_PLL | pm);
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		clk |= R00_EN_DAC | R00_EN_INT;
+	else
+		clk |= R00_EN_ADC | R00_EN_DEC;
+
+	uda1380_write(codec, UDA1380_CLK, clk);
+	return 0;
+}
+
+static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
+
+	/* shut down WSPLL power if running from this clock */
+	if (clk & R00_DAC_CLK) {
+		u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+		uda1380_write(codec, UDA1380_PM, ~R02_PON_PLL & pm);
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		clk &= ~(R00_EN_DAC | R00_EN_INT);
+	else
+		clk &= ~(R00_EN_ADC | R00_EN_DEC);
+
+	uda1380_write(codec, UDA1380_CLK, clk);
+}
+
+static int uda1380_set_bias_level(struct snd_soc_codec *codec,
+	enum snd_soc_bias_level level)
+{
+	int pm = uda1380_read_reg_cache(codec, UDA1380_PM);
+	int reg;
+	struct uda1380_platform_data *pdata = codec->dev->platform_data;
+
+	if (codec->bias_level == level)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+		/* ADC, DAC on */
+		uda1380_write(codec, UDA1380_PM, R02_PON_BIAS | pm);
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			if (gpio_is_valid(pdata->gpio_power)) {
+				gpio_set_value(pdata->gpio_power, 1);
+				mdelay(1);
+				uda1380_reset(codec);
+			}
+
+			uda1380_sync_cache(codec);
+		}
+		uda1380_write(codec, UDA1380_PM, 0x0);
+		break;
+	case SND_SOC_BIAS_OFF:
+		if (!gpio_is_valid(pdata->gpio_power))
+			break;
+
+		gpio_set_value(pdata->gpio_power, 0);
+
+		/* Mark mixer regs cache dirty to sync them with
+		 * codec regs on power on.
+		 */
+		for (reg = UDA1380_MVOL; reg < UDA1380_CACHEREGNUM; reg++)
+			set_bit(reg - 0x10, &uda1380_cache_dirty);
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define UDA1380_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+		       SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+		       SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops uda1380_dai_ops = {
+	.hw_params	= uda1380_pcm_hw_params,
+	.shutdown	= uda1380_pcm_shutdown,
+	.trigger	= uda1380_trigger,
+	.set_fmt	= uda1380_set_dai_fmt_both,
+};
+
+static struct snd_soc_dai_ops uda1380_dai_ops_playback = {
+	.hw_params	= uda1380_pcm_hw_params,
+	.shutdown	= uda1380_pcm_shutdown,
+	.trigger	= uda1380_trigger,
+	.set_fmt	= uda1380_set_dai_fmt_playback,
+};
+
+static struct snd_soc_dai_ops uda1380_dai_ops_capture = {
+	.hw_params	= uda1380_pcm_hw_params,
+	.shutdown	= uda1380_pcm_shutdown,
+	.trigger	= uda1380_trigger,
+	.set_fmt	= uda1380_set_dai_fmt_capture,
+};
+
+static struct snd_soc_dai_driver uda1380_dai[] = {
+{
+	.name = "uda1380-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = UDA1380_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = UDA1380_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.ops = &uda1380_dai_ops,
+},
+{ /* playback only - dual interface */
+	.name = "uda1380-hifi-playback",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = UDA1380_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.ops = &uda1380_dai_ops_playback,
+},
+{ /* capture only - dual interface*/
+	.name = "uda1380-hifi-capture",
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = UDA1380_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.ops = &uda1380_dai_ops_capture,
+},
+};
+
+static int uda1380_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int uda1380_resume(struct snd_soc_codec *codec)
+{
+	uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	return 0;
+}
+
+static int uda1380_probe(struct snd_soc_codec *codec)
+{
+	struct uda1380_platform_data *pdata =codec->dev->platform_data;
+	struct uda1380_priv *uda1380 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	uda1380->codec = codec;
+
+	codec->hw_write = (hw_write_t)i2c_master_send;
+	codec->control_data = uda1380->control_data;
+
+	if (!pdata)
+		return -EINVAL;
+
+	if (gpio_is_valid(pdata->gpio_reset)) {
+		ret = gpio_request(pdata->gpio_reset, "uda1380 reset");
+		if (ret)
+			goto err_out;
+		ret = gpio_direction_output(pdata->gpio_reset, 0);
+		if (ret)
+			goto err_gpio_reset_conf;
+	}
+
+	if (gpio_is_valid(pdata->gpio_power)) {
+		ret = gpio_request(pdata->gpio_power, "uda1380 power");
+		if (ret)
+			goto err_gpio;
+		ret = gpio_direction_output(pdata->gpio_power, 0);
+		if (ret)
+			goto err_gpio_power_conf;
+	} else {
+		ret = uda1380_reset(codec);
+		if (ret) {
+			dev_err(codec->dev, "Failed to issue reset\n");
+			goto err_reset;
+		}
+	}
+
+	INIT_WORK(&uda1380->work, uda1380_flush_work);
+
+	/* power on device */
+	uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	/* set clock input */
+	switch (pdata->dac_clk) {
+	case UDA1380_DAC_CLK_SYSCLK:
+		uda1380_write_reg_cache(codec, UDA1380_CLK, 0);
+		break;
+	case UDA1380_DAC_CLK_WSPLL:
+		uda1380_write_reg_cache(codec, UDA1380_CLK,
+			R00_DAC_CLK);
+		break;
+	}
+
+	snd_soc_add_controls(codec, uda1380_snd_controls,
+				ARRAY_SIZE(uda1380_snd_controls));
+	uda1380_add_widgets(codec);
+
+	return 0;
+
+err_reset:
+err_gpio_power_conf:
+	if (gpio_is_valid(pdata->gpio_power))
+		gpio_free(pdata->gpio_power);
+
+err_gpio_reset_conf:
+err_gpio:
+	if (gpio_is_valid(pdata->gpio_reset))
+		gpio_free(pdata->gpio_reset);
+err_out:
+	return ret;
+}
+
+/* power down chip */
+static int uda1380_remove(struct snd_soc_codec *codec)
+{
+	struct uda1380_platform_data *pdata =codec->dev->platform_data;
+
+	uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	gpio_free(pdata->gpio_reset);
+	gpio_free(pdata->gpio_power);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_uda1380 = {
+	.probe =	uda1380_probe,
+	.remove =	uda1380_remove,
+	.suspend =	uda1380_suspend,
+	.resume =	uda1380_resume,
+	.read =		uda1380_read_reg_cache,
+	.write =	uda1380_write,
+	.set_bias_level = uda1380_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(uda1380_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = uda1380_reg,
+	.reg_cache_step = 1,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int uda1380_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct uda1380_priv *uda1380;
+	int ret;
+
+	uda1380 = kzalloc(sizeof(struct uda1380_priv), GFP_KERNEL);
+	if (uda1380 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, uda1380);
+	uda1380->control_data = i2c;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_uda1380, uda1380_dai, ARRAY_SIZE(uda1380_dai));
+	if (ret < 0)
+		kfree(uda1380);
+	return ret;
+}
+
+static int __devexit uda1380_i2c_remove(struct i2c_client *i2c)
+{
+	snd_soc_unregister_codec(&i2c->dev);
+	kfree(i2c_get_clientdata(i2c));
+	return 0;
+}
+
+static const struct i2c_device_id uda1380_i2c_id[] = {
+	{ "uda1380", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id);
+
+static struct i2c_driver uda1380_i2c_driver = {
+	.driver = {
+		.name =  "uda1380-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    uda1380_i2c_probe,
+	.remove =   __devexit_p(uda1380_i2c_remove),
+	.id_table = uda1380_i2c_id,
+};
+#endif
+
+static int __init uda1380_modinit(void)
+{
+	int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&uda1380_i2c_driver);
+	if (ret != 0)
+		pr_err("Failed to register UDA1380 I2C driver: %d\n", ret);
+#endif
+	return 0;
+}
+module_init(uda1380_modinit);
+
+static void __exit uda1380_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&uda1380_i2c_driver);
+#endif
+}
+module_exit(uda1380_exit);
+
+MODULE_AUTHOR("Giorgio Padrin");
+MODULE_DESCRIPTION("Audio support for codec Philips UDA1380");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/uda1380.h b/linux/sound/soc/codecs/uda1380.h
new file mode 100644
index 0000000..942e392
--- /dev/null
+++ b/linux/sound/soc/codecs/uda1380.h
@@ -0,0 +1,79 @@
+/*
+ * Audio support for Philips UDA1380
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org>
+ */
+
+#ifndef _UDA1380_H
+#define _UDA1380_H
+
+#define UDA1380_CLK	0x00
+#define UDA1380_IFACE	0x01
+#define UDA1380_PM	0x02
+#define UDA1380_AMIX	0x03
+#define UDA1380_HP	0x04
+#define UDA1380_MVOL	0x10
+#define UDA1380_MIXVOL	0x11
+#define UDA1380_MODE	0x12
+#define UDA1380_DEEMP	0x13
+#define UDA1380_MIXER	0x14
+#define UDA1380_INTSTAT	0x18
+#define UDA1380_DEC	0x20
+#define UDA1380_PGA	0x21
+#define UDA1380_ADC	0x22
+#define UDA1380_AGC	0x23
+#define UDA1380_DECSTAT	0x28
+#define UDA1380_RESET	0x7f
+
+#define UDA1380_CACHEREGNUM 0x24
+
+/* Register flags */
+#define R00_EN_ADC	0x0800
+#define R00_EN_DEC	0x0400
+#define R00_EN_DAC	0x0200
+#define R00_EN_INT	0x0100
+#define R00_DAC_CLK	0x0010
+#define R01_SFORI_I2S   0x0000
+#define R01_SFORI_LSB16 0x0100
+#define R01_SFORI_LSB18 0x0200
+#define R01_SFORI_LSB20 0x0300
+#define R01_SFORI_MSB   0x0500
+#define R01_SFORI_MASK  0x0700
+#define R01_SFORO_I2S   0x0000
+#define R01_SFORO_LSB16 0x0001
+#define R01_SFORO_LSB18 0x0002
+#define R01_SFORO_LSB20 0x0003
+#define R01_SFORO_LSB24 0x0004
+#define R01_SFORO_MSB   0x0005
+#define R01_SFORO_MASK  0x0007
+#define R01_SEL_SOURCE  0x0040
+#define R01_SIM		0x0010
+#define R02_PON_PLL	0x8000
+#define R02_PON_HP	0x2000
+#define R02_PON_DAC	0x0400
+#define R02_PON_BIAS	0x0100
+#define R02_EN_AVC	0x0080
+#define R02_PON_AVC	0x0040
+#define R02_PON_LNA	0x0010
+#define R02_PON_PGAL	0x0008
+#define R02_PON_ADCL	0x0004
+#define R02_PON_PGAR	0x0002
+#define R02_PON_ADCR	0x0001
+#define R13_MTM		0x4000
+#define R14_SILENCE	0x0080
+#define R14_SDET_ON	0x0040
+#define R21_MT_ADC	0x8000
+#define R22_SEL_LNA	0x0008
+#define R22_SEL_MIC	0x0004
+#define R22_SKIP_DCFIL	0x0002
+#define R23_AGC_EN	0x0001
+
+#define UDA1380_DAI_DUPLEX	0 /* playback and capture on single DAI */
+#define UDA1380_DAI_PLAYBACK	1 /* playback DAI */
+#define UDA1380_DAI_CAPTURE	2 /* capture DAI */
+
+#endif /* _UDA1380_H */
diff --git a/linux/sound/soc/codecs/wl1271bt.c b/linux/sound/soc/codecs/wl1271bt.c
new file mode 100644
index 0000000..cc9f610
--- /dev/null
+++ b/linux/sound/soc/codecs/wl1271bt.c
@@ -0,0 +1,61 @@
+/*
+ * wl1271bt.c  --  ALSA SoC WL1271 Bluetooth codec driver for omap3evm board
+ *
+ * Author: Sinoj M. Issac, <sinoj at mistralsolutions.com>
+ *
+ * Based on sound/soc/codecs/twl4030.c by Steve Sakoman
+ *
+ * This file provides stub codec that can be used on OMAP3530 evm to
+ * send/receive voice samples to/from WL1271 Bluetooth chip over PCM interface.
+ * The Bluetoothchip codec interface is configured by HCI commands. ALSA is
+ * configured and aligned to the codec interface.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+
+/*
+ * Since WL1271 PCM interface is intended for Voice,
+ * Support sampling rate 8K only
+ */
+#define WL1271BT_RATES		SNDRV_PCM_RATE_8000
+#define WL1271BT_FORMATS	SNDRV_PCM_FMTBIT_S16_LE
+
+struct snd_soc_dai wl1271bt_dai = {
+	.name = "wl1271bt",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WL1271BT_RATES,
+		.formats = WL1271BT_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WL1271BT_RATES,
+		.formats = WL1271BT_FORMATS,},
+};
+
+static int __init wl1271bt_modinit(void)
+{
+	/* Register number of DAIs (wl1271bt_dai) with the ASoC core */
+	return snd_soc_register_dais(&wl1271bt_dai, 1);
+}
+
+static void __exit wl1271bt_modexit(void)
+{
+	/* Unregister number of DAIs (wl1271bt_dai) from the ASoC core */
+	snd_soc_unregister_dais(&wl1271bt_dai, 1);
+}
+
+module_init(wl1271bt_modinit);
+module_exit(wl1271bt_modexit);
+
+
diff --git a/linux/sound/soc/codecs/wl1271bt.h b/linux/sound/soc/codecs/wl1271bt.h
new file mode 100644
index 0000000..769f9a9
--- /dev/null
+++ b/linux/sound/soc/codecs/wl1271bt.h
@@ -0,0 +1,16 @@
+/*
+ * wl1271bt.h  --  ALSA SoC WL1271 Bluetooth codec driver header
+ *
+ * Author: Sinoj M. Issac, <sinoj at mistralsolutions.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef WL1271BT_H
+#define WL1271BT_H
+
+extern struct snd_soc_dai wl1271bt_dai;
+
+#endif /* WL1271BT_H */
diff --git a/linux/sound/soc/codecs/wl1273.c b/linux/sound/soc/codecs/wl1273.c
new file mode 100644
index 0000000..0c47c78
--- /dev/null
+++ b/linux/sound/soc/codecs/wl1273.c
@@ -0,0 +1,528 @@
+/*
+ * ALSA SoC WL1273 codec driver
+ *
+ * Author:      Matti Aaltonen, <matti.j.aaltonen@nokia.com>
+ *
+ * Copyright:   (C) 2010 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/mfd/wl1273-core.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dai.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "wl1273.h"
+
+enum wl1273_mode { WL1273_MODE_BT, WL1273_MODE_FM_RX, WL1273_MODE_FM_TX };
+
+/* codec private data */
+struct wl1273_priv {
+	enum wl1273_mode mode;
+	struct wl1273_core *core;
+	unsigned int channels;
+};
+
+static int snd_wl1273_fm_set_i2s_mode(struct wl1273_core *core,
+				      int rate, int width)
+{
+	struct device *dev = &core->i2c_dev->dev;
+	int r = 0;
+	u16 mode;
+
+	dev_dbg(dev, "rate: %d\n", rate);
+	dev_dbg(dev, "width: %d\n", width);
+
+	mutex_lock(&core->lock);
+
+	mode = core->i2s_mode & ~WL1273_IS2_WIDTH & ~WL1273_IS2_RATE;
+
+	switch (rate) {
+	case 48000:
+		mode |= WL1273_IS2_RATE_48K;
+		break;
+	case 44100:
+		mode |= WL1273_IS2_RATE_44_1K;
+		break;
+	case 32000:
+		mode |= WL1273_IS2_RATE_32K;
+		break;
+	case 22050:
+		mode |= WL1273_IS2_RATE_22_05K;
+		break;
+	case 16000:
+		mode |= WL1273_IS2_RATE_16K;
+		break;
+	case 12000:
+		mode |= WL1273_IS2_RATE_12K;
+		break;
+	case 11025:
+		mode |= WL1273_IS2_RATE_11_025;
+		break;
+	case 8000:
+		mode |= WL1273_IS2_RATE_8K;
+		break;
+	default:
+		dev_err(dev, "Sampling rate: %d not supported\n", rate);
+		r = -EINVAL;
+		goto out;
+	}
+
+	switch (width) {
+	case 16:
+		mode |= WL1273_IS2_WIDTH_32;
+		break;
+	case 20:
+		mode |= WL1273_IS2_WIDTH_40;
+		break;
+	case 24:
+		mode |= WL1273_IS2_WIDTH_48;
+		break;
+	case 25:
+		mode |= WL1273_IS2_WIDTH_50;
+		break;
+	case 30:
+		mode |= WL1273_IS2_WIDTH_60;
+		break;
+	case 32:
+		mode |= WL1273_IS2_WIDTH_64;
+		break;
+	case 40:
+		mode |= WL1273_IS2_WIDTH_80;
+		break;
+	case 48:
+		mode |= WL1273_IS2_WIDTH_96;
+		break;
+	case 64:
+		mode |= WL1273_IS2_WIDTH_128;
+		break;
+	default:
+		dev_err(dev, "Data width: %d not supported\n", width);
+		r = -EINVAL;
+		goto out;
+	}
+
+	dev_dbg(dev, "WL1273_I2S_DEF_MODE: 0x%04x\n",  WL1273_I2S_DEF_MODE);
+	dev_dbg(dev, "core->i2s_mode: 0x%04x\n", core->i2s_mode);
+	dev_dbg(dev, "mode: 0x%04x\n", mode);
+
+	if (core->i2s_mode != mode) {
+		r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET, mode);
+		if (r)
+			goto out;
+
+		core->i2s_mode = mode;
+		r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE,
+					WL1273_AUDIO_ENABLE_I2S);
+		if (r)
+			goto out;
+	}
+out:
+	mutex_unlock(&core->lock);
+
+	return r;
+}
+
+static int snd_wl1273_fm_set_channel_number(struct wl1273_core *core,
+					    int channel_number)
+{
+	struct i2c_client *client = core->i2c_dev;
+	struct device *dev = &client->dev;
+	int r = 0;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	mutex_lock(&core->lock);
+
+	if (core->channel_number == channel_number)
+		goto out;
+
+	if (channel_number == 1 && core->mode == WL1273_MODE_RX)
+		r = wl1273_fm_write_cmd(core, WL1273_MOST_MODE_SET,
+					WL1273_RX_MONO);
+	else if (channel_number == 1 && core->mode == WL1273_MODE_TX)
+		r = wl1273_fm_write_cmd(core, WL1273_MONO_SET,
+					WL1273_TX_MONO);
+	else if (channel_number == 2 && core->mode == WL1273_MODE_RX)
+		r = wl1273_fm_write_cmd(core, WL1273_MOST_MODE_SET,
+					WL1273_RX_STEREO);
+	else if (channel_number == 2 && core->mode == WL1273_MODE_TX)
+		r = wl1273_fm_write_cmd(core, WL1273_MONO_SET,
+					WL1273_TX_STEREO);
+	else
+		r = -EINVAL;
+out:
+	mutex_unlock(&core->lock);
+
+	return r;
+}
+
+static int snd_wl1273_get_audio_route(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+	ucontrol->value.integer.value[0] = wl1273->mode;
+
+	return 0;
+}
+
+static const char *wl1273_audio_route[] = { "Bt", "FmRx", "FmTx" };
+
+static int snd_wl1273_set_audio_route(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+	if (wl1273->mode == ucontrol->value.integer.value[0])
+		return 0;
+
+	/* Do not allow changes while stream is running */
+	if (codec->active)
+		return -EPERM;
+
+	if (ucontrol->value.integer.value[0] < 0 ||
+	    ucontrol->value.integer.value[0] >=  ARRAY_SIZE(wl1273_audio_route))
+		return -EINVAL;
+
+	wl1273->mode = ucontrol->value.integer.value[0];
+
+	return 1;
+}
+
+static const struct soc_enum wl1273_enum =
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(wl1273_audio_route), wl1273_audio_route);
+
+static int snd_wl1273_fm_audio_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+	dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+	ucontrol->value.integer.value[0] = wl1273->core->audio_mode;
+
+	return 0;
+}
+
+static int snd_wl1273_fm_audio_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+	int val, r = 0;
+
+	dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+	val = ucontrol->value.integer.value[0];
+	if (wl1273->core->audio_mode == val)
+		return 0;
+
+	r = wl1273_fm_set_audio(wl1273->core, val);
+	if (r < 0)
+		return r;
+
+	return 1;
+}
+
+static const char *wl1273_audio_strings[] = { "Digital", "Analog" };
+
+static const struct soc_enum wl1273_audio_enum =
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(wl1273_audio_strings),
+			    wl1273_audio_strings);
+
+static int snd_wl1273_fm_volume_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+	dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+	ucontrol->value.integer.value[0] = wl1273->core->volume;
+
+	return 0;
+}
+
+static int snd_wl1273_fm_volume_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+	int r;
+
+	dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+	r = wl1273_fm_set_volume(wl1273->core,
+				 ucontrol->value.integer.value[0]);
+	if (r)
+		return r;
+
+	return 1;
+}
+
+static const struct snd_kcontrol_new wl1273_controls[] = {
+	SOC_ENUM_EXT("Codec Mode", wl1273_enum,
+		     snd_wl1273_get_audio_route, snd_wl1273_set_audio_route),
+	SOC_ENUM_EXT("Audio Switch", wl1273_audio_enum,
+		     snd_wl1273_fm_audio_get,  snd_wl1273_fm_audio_put),
+	SOC_SINGLE_EXT("Volume", 0, 0, WL1273_MAX_VOLUME, 0,
+		       snd_wl1273_fm_volume_get, snd_wl1273_fm_volume_put),
+};
+
+static int wl1273_startup(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+	switch (wl1273->mode) {
+	case WL1273_MODE_BT:
+		snd_pcm_hw_constraint_minmax(substream->runtime,
+					     SNDRV_PCM_HW_PARAM_RATE,
+					     8000, 8000);
+		snd_pcm_hw_constraint_minmax(substream->runtime,
+					     SNDRV_PCM_HW_PARAM_CHANNELS, 1, 1);
+		break;
+	case WL1273_MODE_FM_RX:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			pr_err("Cannot play in RX mode.\n");
+			return -EINVAL;
+		}
+		break;
+	case WL1273_MODE_FM_TX:
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+			pr_err("Cannot capture in TX mode.\n");
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+		break;
+	}
+
+	return 0;
+}
+
+static int wl1273_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(rtd->codec);
+	struct wl1273_core *core = wl1273->core;
+	unsigned int rate, width, r;
+
+	if (params_format(params) != SNDRV_PCM_FORMAT_S16_LE) {
+		pr_err("Only SNDRV_PCM_FORMAT_S16_LE supported.\n");
+		return -EINVAL;
+	}
+
+	rate = params_rate(params);
+	width =  hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
+
+	if (wl1273->mode == WL1273_MODE_BT) {
+		if (rate != 8000) {
+			pr_err("Rate %d not supported.\n", params_rate(params));
+			return -EINVAL;
+		}
+
+		if (params_channels(params) != 1) {
+			pr_err("Only mono supported.\n");
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	if (wl1273->mode == WL1273_MODE_FM_TX &&
+	    substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		pr_err("Only playback supported with TX.\n");
+		return -EINVAL;
+	}
+
+	if (wl1273->mode == WL1273_MODE_FM_RX  &&
+	    substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		pr_err("Only capture supported with RX.\n");
+		return -EINVAL;
+	}
+
+	if (wl1273->mode != WL1273_MODE_FM_RX  &&
+	    wl1273->mode != WL1273_MODE_FM_TX) {
+		pr_err("Unexpected mode: %d.\n", wl1273->mode);
+		return -EINVAL;
+	}
+
+	r = snd_wl1273_fm_set_i2s_mode(core, rate, width);
+	if (r)
+		return r;
+
+	wl1273->channels = params_channels(params);
+	r = snd_wl1273_fm_set_channel_number(core, wl1273->channels);
+	if (r)
+		return r;
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops wl1273_dai_ops = {
+	.startup	= wl1273_startup,
+	.hw_params	= wl1273_hw_params,
+};
+
+static struct snd_soc_dai_driver wl1273_dai = {
+	.name = "wl1273-fm",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE},
+	.ops = &wl1273_dai_ops,
+};
+
+/* Audio interface format for the soc_card driver */
+int wl1273_get_format(struct snd_soc_codec *codec, unsigned int *fmt)
+{
+	struct wl1273_priv *wl1273;
+
+	if (codec == NULL || fmt == NULL)
+		return -EINVAL;
+
+	wl1273 = snd_soc_codec_get_drvdata(codec);
+
+	switch (wl1273->mode) {
+	case WL1273_MODE_FM_RX:
+	case WL1273_MODE_FM_TX:
+		*fmt =	SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_CBM_CFM;
+
+		break;
+	case WL1273_MODE_BT:
+		*fmt =	SND_SOC_DAIFMT_DSP_A |
+			SND_SOC_DAIFMT_IB_NF |
+			SND_SOC_DAIFMT_CBM_CFM;
+
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wl1273_get_format);
+
+static int wl1273_probe(struct snd_soc_codec *codec)
+{
+	struct wl1273_core **core = codec->dev->platform_data;
+	struct wl1273_priv *wl1273;
+	int r;
+
+	dev_dbg(codec->dev, "%s.\n", __func__);
+
+	if (!core) {
+		dev_err(codec->dev, "Platform data is missing.\n");
+		return -EINVAL;
+	}
+
+	wl1273 = kzalloc(sizeof(struct wl1273_priv), GFP_KERNEL);
+	if (wl1273 == NULL) {
+		dev_err(codec->dev, "Cannot allocate memory.\n");
+		return -ENOMEM;
+	}
+
+	wl1273->mode = WL1273_MODE_BT;
+	wl1273->core = *core;
+
+	snd_soc_codec_set_drvdata(codec, wl1273);
+	mutex_init(&codec->mutex);
+
+	r = snd_soc_add_controls(codec, wl1273_controls,
+				 ARRAY_SIZE(wl1273_controls));
+	if (r)
+		kfree(wl1273);
+
+	return r;
+}
+
+static int wl1273_remove(struct snd_soc_codec *codec)
+{
+	struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
+
+	dev_dbg(codec->dev, "%s\n", __func__);
+	kfree(wl1273);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wl1273 = {
+	.probe = wl1273_probe,
+	.remove = wl1273_remove,
+};
+
+static int __devinit wl1273_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wl1273,
+				      &wl1273_dai, 1);
+}
+
+static int __devexit wl1273_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+MODULE_ALIAS("platform:wl1273-codec");
+
+static struct platform_driver wl1273_platform_driver = {
+	.driver		= {
+		.name	= "wl1273-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= wl1273_platform_probe,
+	.remove		= __devexit_p(wl1273_platform_remove),
+};
+
+static int __init wl1273_init(void)
+{
+	return platform_driver_register(&wl1273_platform_driver);
+}
+module_init(wl1273_init);
+
+static void __exit wl1273_exit(void)
+{
+	platform_driver_unregister(&wl1273_platform_driver);
+}
+module_exit(wl1273_exit);
+
+MODULE_AUTHOR("Matti Aaltonen <matti.j.aaltonen@nokia.com>");
+MODULE_DESCRIPTION("ASoC WL1273 codec driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wl1273.h b/linux/sound/soc/codecs/wl1273.h
new file mode 100644
index 0000000..14ed027
--- /dev/null
+++ b/linux/sound/soc/codecs/wl1273.h
@@ -0,0 +1,101 @@
+/*
+ * sound/soc/codec/wl1273.h
+ *
+ * ALSA SoC WL1273 codec driver
+ *
+ * Copyright (C) Nokia Corporation
+ * Author: Matti Aaltonen <matti.j.aaltonen@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __WL1273_CODEC_H__
+#define __WL1273_CODEC_H__
+
+/* I2S protocol, left channel first, data width 16 bits */
+#define WL1273_PCM_DEF_MODE		0x00
+
+/* Rx */
+#define WL1273_AUDIO_ENABLE_I2S		(1 << 0)
+#define WL1273_AUDIO_ENABLE_ANALOG	(1 << 1)
+
+/* Tx */
+#define WL1273_AUDIO_IO_SET_ANALOG	0
+#define WL1273_AUDIO_IO_SET_I2S		1
+
+#define WL1273_POWER_SET_OFF		0
+#define WL1273_POWER_SET_FM		(1 << 0)
+#define WL1273_POWER_SET_RDS		(1 << 1)
+#define WL1273_POWER_SET_RETENTION	(1 << 4)
+
+#define WL1273_PUPD_SET_OFF		0x00
+#define WL1273_PUPD_SET_ON		0x01
+#define WL1273_PUPD_SET_RETENTION	0x10
+
+/* I2S mode */
+#define WL1273_IS2_WIDTH_32	0x0
+#define WL1273_IS2_WIDTH_40	0x1
+#define WL1273_IS2_WIDTH_22_23	0x2
+#define WL1273_IS2_WIDTH_23_22	0x3
+#define WL1273_IS2_WIDTH_48	0x4
+#define WL1273_IS2_WIDTH_50	0x5
+#define WL1273_IS2_WIDTH_60	0x6
+#define WL1273_IS2_WIDTH_64	0x7
+#define WL1273_IS2_WIDTH_80	0x8
+#define WL1273_IS2_WIDTH_96	0x9
+#define WL1273_IS2_WIDTH_128	0xa
+#define WL1273_IS2_WIDTH	0xf
+
+#define WL1273_IS2_FORMAT_STD	(0x0 << 4)
+#define WL1273_IS2_FORMAT_LEFT	(0x1 << 4)
+#define WL1273_IS2_FORMAT_RIGHT	(0x2 << 4)
+#define WL1273_IS2_FORMAT_USER	(0x3 << 4)
+
+#define WL1273_IS2_MASTER	(0x0 << 6)
+#define WL1273_IS2_SLAVEW	(0x1 << 6)
+
+#define WL1273_IS2_TRI_AFTER_SENDING	(0x0 << 7)
+#define WL1273_IS2_TRI_ALWAYS_ACTIVE	(0x1 << 7)
+
+#define WL1273_IS2_SDOWS_RR	(0x0 << 8)
+#define WL1273_IS2_SDOWS_RF	(0x1 << 8)
+#define WL1273_IS2_SDOWS_FR	(0x2 << 8)
+#define WL1273_IS2_SDOWS_FF	(0x3 << 8)
+
+#define WL1273_IS2_TRI_OPT	(0x0 << 10)
+#define WL1273_IS2_TRI_ALWAYS	(0x1 << 10)
+
+#define WL1273_IS2_RATE_48K	(0x0 << 12)
+#define WL1273_IS2_RATE_44_1K	(0x1 << 12)
+#define WL1273_IS2_RATE_32K	(0x2 << 12)
+#define WL1273_IS2_RATE_22_05K	(0x4 << 12)
+#define WL1273_IS2_RATE_16K	(0x5 << 12)
+#define WL1273_IS2_RATE_12K	(0x8 << 12)
+#define WL1273_IS2_RATE_11_025	(0x9 << 12)
+#define WL1273_IS2_RATE_8K	(0xa << 12)
+#define WL1273_IS2_RATE		(0xf << 12)
+
+#define WL1273_I2S_DEF_MODE	(WL1273_IS2_WIDTH_32 | \
+				 WL1273_IS2_FORMAT_STD | \
+				 WL1273_IS2_MASTER | \
+				 WL1273_IS2_TRI_AFTER_SENDING | \
+				 WL1273_IS2_SDOWS_RR | \
+				 WL1273_IS2_TRI_OPT | \
+				 WL1273_IS2_RATE_48K)
+
+int wl1273_get_format(struct snd_soc_codec *codec, unsigned int *fmt);
+
+#endif	/* End of __WL1273_CODEC_H__ */
diff --git a/linux/sound/soc/codecs/wm2000.c b/linux/sound/soc/codecs/wm2000.c
new file mode 100644
index 0000000..4bcd168
--- /dev/null
+++ b/linux/sound/soc/codecs/wm2000.c
@@ -0,0 +1,890 @@
+/*
+ * wm2000.c  --  WM2000 ALSA Soc Audio driver
+ *
+ * Copyright 2008-2010 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * The download image for the WM2000 will be requested as
+ * 'wm2000_anc.bin' by default (overridable via platform data) at
+ * runtime and is expected to be in flat binary format.  This is
+ * generated by Wolfson configuration tools and includes
+ * system-specific callibration information.  If supplied as a
+ * sequence of ASCII-encoded hexidecimal bytes this can be converted
+ * into a flat binary with a command such as this on the command line:
+ *
+ * perl -e 'while (<>) { s/[\r\n]+// ; printf("%c", hex($_)); }'
+ *                 < file  > wm2000_anc.bin
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <sound/wm2000.h>
+
+#include "wm2000.h"
+
+enum wm2000_anc_mode {
+	ANC_ACTIVE = 0,
+	ANC_BYPASS = 1,
+	ANC_STANDBY = 2,
+	ANC_OFF = 3,
+};
+
+struct wm2000_priv {
+	struct i2c_client *i2c;
+
+	enum wm2000_anc_mode anc_mode;
+
+	unsigned int anc_active:1;
+	unsigned int anc_eng_ena:1;
+	unsigned int spk_ena:1;
+
+	unsigned int mclk_div:1;
+	unsigned int speech_clarity:1;
+
+	int anc_download_size;
+	char *anc_download;
+};
+
+static struct i2c_client *wm2000_i2c;
+
+static int wm2000_write(struct i2c_client *i2c, unsigned int reg,
+			unsigned int value)
+{
+	u8 data[3];
+	int ret;
+
+	data[0] = (reg >> 8) & 0xff;
+	data[1] = reg & 0xff;
+	data[2] = value & 0xff;
+
+	dev_vdbg(&i2c->dev, "write %x = %x\n", reg, value);
+
+	ret = i2c_master_send(i2c, data, 3);
+	if (ret == 3)
+		return 0;
+	if (ret < 0)
+		return ret;
+	else
+		return -EIO;
+}
+
+static unsigned int wm2000_read(struct i2c_client *i2c, unsigned int r)
+{
+	struct i2c_msg xfer[2];
+	u8 reg[2];
+	u8 data;
+	int ret;
+
+	/* Write register */
+	reg[0] = (r >> 8) & 0xff;
+	reg[1] = r & 0xff;
+	xfer[0].addr = i2c->addr;
+	xfer[0].flags = 0;
+	xfer[0].len = sizeof(reg);
+	xfer[0].buf = &reg[0];
+
+	/* Read data */
+	xfer[1].addr = i2c->addr;
+	xfer[1].flags = I2C_M_RD;
+	xfer[1].len = 1;
+	xfer[1].buf = &data;
+
+	ret = i2c_transfer(i2c->adapter, xfer, 2);
+	if (ret != 2) {
+		dev_err(&i2c->dev, "i2c_transfer() returned %d\n", ret);
+		return 0;
+	}
+
+	dev_vdbg(&i2c->dev, "read %x from %x\n", data, r);
+
+	return data;
+}
+
+static void wm2000_reset(struct wm2000_priv *wm2000)
+{
+	struct i2c_client *i2c = wm2000->i2c;
+
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR);
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR);
+	wm2000_write(i2c, WM2000_REG_ID1, 0);
+
+	wm2000->anc_mode = ANC_OFF;
+}
+
+static int wm2000_poll_bit(struct i2c_client *i2c,
+			   unsigned int reg, u8 mask, int timeout)
+{
+	int val;
+
+	val = wm2000_read(i2c, reg);
+
+	while (!(val & mask) && --timeout) {
+		msleep(1);
+		val = wm2000_read(i2c, reg);
+	}
+
+	if (timeout == 0)
+		return 0;
+	else
+		return 1;
+}
+
+static int wm2000_power_up(struct i2c_client *i2c, int analogue)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+	int ret, timeout;
+
+	BUG_ON(wm2000->anc_mode != ANC_OFF);
+
+	dev_dbg(&i2c->dev, "Beginning power up\n");
+
+	if (!wm2000->mclk_div) {
+		dev_dbg(&i2c->dev, "Disabling MCLK divider\n");
+		wm2000_write(i2c, WM2000_REG_SYS_CTL2,
+			     WM2000_MCLK_DIV2_ENA_CLR);
+	} else {
+		dev_dbg(&i2c->dev, "Enabling MCLK divider\n");
+		wm2000_write(i2c, WM2000_REG_SYS_CTL2,
+			     WM2000_MCLK_DIV2_ENA_SET);
+	}
+
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR);
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_SET);
+
+	/* Wait for ANC engine to become ready */
+	if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT,
+			     WM2000_ANC_ENG_IDLE, 1)) {
+		dev_err(&i2c->dev, "ANC engine failed to reset\n");
+		return -ETIMEDOUT;
+	}
+
+	if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+			     WM2000_STATUS_BOOT_COMPLETE, 1)) {
+		dev_err(&i2c->dev, "ANC engine failed to initialise\n");
+		return -ETIMEDOUT;
+	}
+
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET);
+
+	/* Open code download of the data since it is the only bulk
+	 * write we do. */
+	dev_dbg(&i2c->dev, "Downloading %d bytes\n",
+		wm2000->anc_download_size - 2);
+
+	ret = i2c_master_send(i2c, wm2000->anc_download,
+			      wm2000->anc_download_size);
+	if (ret < 0) {
+		dev_err(&i2c->dev, "i2c_transfer() failed: %d\n", ret);
+		return ret;
+	}
+	if (ret != wm2000->anc_download_size) {
+		dev_err(&i2c->dev, "i2c_transfer() failed, %d != %d\n",
+			ret, wm2000->anc_download_size);
+		return -EIO;
+	}
+
+	dev_dbg(&i2c->dev, "Download complete\n");
+
+	if (analogue) {
+		timeout = 248;
+		wm2000_write(i2c, WM2000_REG_ANA_VMID_PU_TIME, timeout / 4);
+
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_ANA_SEQ_INCLUDE |
+			     WM2000_MODE_MOUSE_ENABLE |
+			     WM2000_MODE_THERMAL_ENABLE);
+	} else {
+		timeout = 10;
+
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_MOUSE_ENABLE |
+			     WM2000_MODE_THERMAL_ENABLE);
+	}
+
+	ret = wm2000_read(i2c, WM2000_REG_SPEECH_CLARITY);
+	if (wm2000->speech_clarity)
+		ret &= ~WM2000_SPEECH_CLARITY;
+	else
+		ret |= WM2000_SPEECH_CLARITY;
+	wm2000_write(i2c, WM2000_REG_SPEECH_CLARITY, ret);
+
+	wm2000_write(i2c, WM2000_REG_SYS_START0, 0x33);
+	wm2000_write(i2c, WM2000_REG_SYS_START1, 0x02);
+
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR);
+
+	if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+			     WM2000_STATUS_MOUSE_ACTIVE, timeout)) {
+		dev_err(&i2c->dev, "Timed out waiting for device after %dms\n",
+			timeout * 10);
+		return -ETIMEDOUT;
+	}
+
+	dev_dbg(&i2c->dev, "ANC active\n");
+	if (analogue)
+		dev_dbg(&i2c->dev, "Analogue active\n");
+	wm2000->anc_mode = ANC_ACTIVE;
+
+	return 0;
+}
+
+static int wm2000_power_down(struct i2c_client *i2c, int analogue)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+	int timeout;
+
+	if (analogue) {
+		timeout = 248;
+		wm2000_write(i2c, WM2000_REG_ANA_VMID_PD_TIME, timeout / 4);
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_ANA_SEQ_INCLUDE |
+			     WM2000_MODE_POWER_DOWN);
+	} else {
+		timeout = 10;
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_POWER_DOWN);
+	}
+
+	if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+			     WM2000_STATUS_POWER_DOWN_COMPLETE, timeout)) {
+		dev_err(&i2c->dev, "Timeout waiting for ANC power down\n");
+		return -ETIMEDOUT;
+	}
+
+	if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT,
+			     WM2000_ANC_ENG_IDLE, 1)) {
+		dev_err(&i2c->dev, "Timeout waiting for ANC engine idle\n");
+		return -ETIMEDOUT;
+	}
+
+	dev_dbg(&i2c->dev, "powered off\n");
+	wm2000->anc_mode = ANC_OFF;
+
+	return 0;
+}
+
+static int wm2000_enter_bypass(struct i2c_client *i2c, int analogue)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+	BUG_ON(wm2000->anc_mode != ANC_ACTIVE);
+
+	if (analogue) {
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_ANA_SEQ_INCLUDE |
+			     WM2000_MODE_THERMAL_ENABLE |
+			     WM2000_MODE_BYPASS_ENTRY);
+	} else {
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_THERMAL_ENABLE |
+			     WM2000_MODE_BYPASS_ENTRY);
+	}
+
+	if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+			     WM2000_STATUS_ANC_DISABLED, 10)) {
+		dev_err(&i2c->dev, "Timeout waiting for ANC disable\n");
+		return -ETIMEDOUT;
+	}
+
+	if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT,
+			     WM2000_ANC_ENG_IDLE, 1)) {
+		dev_err(&i2c->dev, "Timeout waiting for ANC engine idle\n");
+		return -ETIMEDOUT;
+	}
+
+	wm2000_write(i2c, WM2000_REG_SYS_CTL1, WM2000_SYS_STBY);
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR);
+
+	wm2000->anc_mode = ANC_BYPASS;
+	dev_dbg(&i2c->dev, "bypass enabled\n");
+
+	return 0;
+}
+
+static int wm2000_exit_bypass(struct i2c_client *i2c, int analogue)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+	BUG_ON(wm2000->anc_mode != ANC_BYPASS);
+	
+	wm2000_write(i2c, WM2000_REG_SYS_CTL1, 0);
+
+	if (analogue) {
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_ANA_SEQ_INCLUDE |
+			     WM2000_MODE_MOUSE_ENABLE |
+			     WM2000_MODE_THERMAL_ENABLE);
+	} else {
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_MOUSE_ENABLE |
+			     WM2000_MODE_THERMAL_ENABLE);
+	}
+
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET);
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR);
+
+	if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+			     WM2000_STATUS_MOUSE_ACTIVE, 10)) {
+		dev_err(&i2c->dev, "Timed out waiting for MOUSE\n");
+		return -ETIMEDOUT;
+	}
+
+	wm2000->anc_mode = ANC_ACTIVE;
+	dev_dbg(&i2c->dev, "MOUSE active\n");
+
+	return 0;
+}
+
+static int wm2000_enter_standby(struct i2c_client *i2c, int analogue)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+	int timeout;
+
+	BUG_ON(wm2000->anc_mode != ANC_ACTIVE);
+
+	if (analogue) {
+		timeout = 248;
+		wm2000_write(i2c, WM2000_REG_ANA_VMID_PD_TIME, timeout / 4);
+
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_ANA_SEQ_INCLUDE |
+			     WM2000_MODE_THERMAL_ENABLE |
+			     WM2000_MODE_STANDBY_ENTRY);
+	} else {
+		timeout = 10;
+
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_THERMAL_ENABLE |
+			     WM2000_MODE_STANDBY_ENTRY);
+	}
+
+	if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+			     WM2000_STATUS_ANC_DISABLED, timeout)) {
+		dev_err(&i2c->dev,
+			"Timed out waiting for ANC disable after 1ms\n");
+		return -ETIMEDOUT;
+	}
+
+	if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, WM2000_ANC_ENG_IDLE,
+			     1)) {
+		dev_err(&i2c->dev,
+			"Timed out waiting for standby after %dms\n",
+			timeout * 10);
+		return -ETIMEDOUT;
+	}
+
+	wm2000_write(i2c, WM2000_REG_SYS_CTL1, WM2000_SYS_STBY);
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR);
+
+	wm2000->anc_mode = ANC_STANDBY;
+	dev_dbg(&i2c->dev, "standby\n");
+	if (analogue)
+		dev_dbg(&i2c->dev, "Analogue disabled\n");
+
+	return 0;
+}
+
+static int wm2000_exit_standby(struct i2c_client *i2c, int analogue)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+	int timeout;
+
+	BUG_ON(wm2000->anc_mode != ANC_STANDBY);
+
+	wm2000_write(i2c, WM2000_REG_SYS_CTL1, 0);
+
+	if (analogue) {
+		timeout = 248;
+		wm2000_write(i2c, WM2000_REG_ANA_VMID_PU_TIME, timeout / 4);
+
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_ANA_SEQ_INCLUDE |
+			     WM2000_MODE_THERMAL_ENABLE |
+			     WM2000_MODE_MOUSE_ENABLE);
+	} else {
+		timeout = 10;
+
+		wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL,
+			     WM2000_MODE_THERMAL_ENABLE |
+			     WM2000_MODE_MOUSE_ENABLE);
+	}
+
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET);
+	wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR);
+
+	if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS,
+			     WM2000_STATUS_MOUSE_ACTIVE, timeout)) {
+		dev_err(&i2c->dev, "Timed out waiting for MOUSE after %dms\n",
+			timeout * 10);
+		return -ETIMEDOUT;
+	}
+
+	wm2000->anc_mode = ANC_ACTIVE;
+	dev_dbg(&i2c->dev, "MOUSE active\n");
+	if (analogue)
+		dev_dbg(&i2c->dev, "Analogue enabled\n");
+
+	return 0;
+}
+
+typedef int (*wm2000_mode_fn)(struct i2c_client *i2c, int analogue);
+
+static struct {
+	enum wm2000_anc_mode source;
+	enum wm2000_anc_mode dest;
+	int analogue;
+	wm2000_mode_fn step[2];
+} anc_transitions[] = {
+	{
+		.source = ANC_OFF,
+		.dest = ANC_ACTIVE,
+		.analogue = 1,
+		.step = {
+			wm2000_power_up,
+		},
+	},
+	{
+		.source = ANC_OFF,
+		.dest = ANC_STANDBY,
+		.step = {
+			wm2000_power_up,
+			wm2000_enter_standby,
+		},
+	},
+	{
+		.source = ANC_OFF,
+		.dest = ANC_BYPASS,
+		.analogue = 1,
+		.step = {
+			wm2000_power_up,
+			wm2000_enter_bypass,
+		},
+	},
+	{
+		.source = ANC_ACTIVE,
+		.dest = ANC_BYPASS,
+		.analogue = 1,
+		.step = {
+			wm2000_enter_bypass,
+		},
+	},
+	{
+		.source = ANC_ACTIVE,
+		.dest = ANC_STANDBY,
+		.analogue = 1,
+		.step = {
+			wm2000_enter_standby,
+		},
+	},
+	{
+		.source = ANC_ACTIVE,
+		.dest = ANC_OFF,
+		.analogue = 1,
+		.step = {
+			wm2000_power_down,
+		},
+	},
+	{
+		.source = ANC_BYPASS,
+		.dest = ANC_ACTIVE,
+		.analogue = 1,
+		.step = {
+			wm2000_exit_bypass,
+		},
+	},
+	{
+		.source = ANC_BYPASS,
+		.dest = ANC_STANDBY,
+		.analogue = 1,
+		.step = {
+			wm2000_exit_bypass,
+			wm2000_enter_standby,
+		},
+	},
+	{
+		.source = ANC_BYPASS,
+		.dest = ANC_OFF,
+		.step = {
+			wm2000_exit_bypass,
+			wm2000_power_down,
+		},
+	},
+	{
+		.source = ANC_STANDBY,
+		.dest = ANC_ACTIVE,
+		.analogue = 1,
+		.step = {
+			wm2000_exit_standby,
+		},
+	},
+	{
+		.source = ANC_STANDBY,
+		.dest = ANC_BYPASS,
+		.analogue = 1,
+		.step = {
+			wm2000_exit_standby,
+			wm2000_enter_bypass,
+		},
+	},
+	{
+		.source = ANC_STANDBY,
+		.dest = ANC_OFF,
+		.step = {
+			wm2000_exit_standby,
+			wm2000_power_down,
+		},
+	},
+};
+
+static int wm2000_anc_transition(struct wm2000_priv *wm2000,
+				 enum wm2000_anc_mode mode)
+{
+	struct i2c_client *i2c = wm2000->i2c;
+	int i, j;
+	int ret;
+
+	if (wm2000->anc_mode == mode)
+		return 0;
+
+	for (i = 0; i < ARRAY_SIZE(anc_transitions); i++)
+		if (anc_transitions[i].source == wm2000->anc_mode &&
+		    anc_transitions[i].dest == mode)
+			break;
+	if (i == ARRAY_SIZE(anc_transitions)) {
+		dev_err(&i2c->dev, "No transition for %d->%d\n",
+			wm2000->anc_mode, mode);
+		return -EINVAL;
+	}
+
+	for (j = 0; j < ARRAY_SIZE(anc_transitions[j].step); j++) {
+		if (!anc_transitions[i].step[j])
+			break;
+		ret = anc_transitions[i].step[j](i2c,
+						 anc_transitions[i].analogue);
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int wm2000_anc_set_mode(struct wm2000_priv *wm2000)
+{
+	struct i2c_client *i2c = wm2000->i2c;
+	enum wm2000_anc_mode mode;
+
+	if (wm2000->anc_eng_ena && wm2000->spk_ena)
+		if (wm2000->anc_active)
+			mode = ANC_ACTIVE;
+		else
+			mode = ANC_BYPASS;
+	else
+		mode = ANC_STANDBY;
+
+	dev_dbg(&i2c->dev, "Set mode %d (enabled %d, mute %d, active %d)\n",
+		mode, wm2000->anc_eng_ena, !wm2000->spk_ena,
+		wm2000->anc_active);
+
+	return wm2000_anc_transition(wm2000, mode);
+}
+
+static int wm2000_anc_mode_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
+
+	ucontrol->value.enumerated.item[0] = wm2000->anc_active;
+
+	return 0;
+}
+
+static int wm2000_anc_mode_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
+	int anc_active = ucontrol->value.enumerated.item[0];
+
+	if (anc_active > 1)
+		return -EINVAL;
+
+	wm2000->anc_active = anc_active;
+
+	return wm2000_anc_set_mode(wm2000);
+}
+
+static int wm2000_speaker_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
+
+	ucontrol->value.enumerated.item[0] = wm2000->spk_ena;
+
+	return 0;
+}
+
+static int wm2000_speaker_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
+	int val = ucontrol->value.enumerated.item[0];
+
+	if (val > 1)
+		return -EINVAL;
+
+	wm2000->spk_ena = val;
+
+	return wm2000_anc_set_mode(wm2000);
+}
+
+static const struct snd_kcontrol_new wm2000_controls[] = {
+	SOC_SINGLE_BOOL_EXT("WM2000 ANC Switch", 0,
+			    wm2000_anc_mode_get,
+			    wm2000_anc_mode_put),
+	SOC_SINGLE_BOOL_EXT("WM2000 Switch", 0,
+			    wm2000_speaker_get,
+			    wm2000_speaker_put),
+};
+
+static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w,
+				  struct snd_kcontrol *kcontrol, int event)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev);
+
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		wm2000->anc_eng_ena = 1;
+
+	if (SND_SOC_DAPM_EVENT_OFF(event))
+		wm2000->anc_eng_ena = 0;
+
+	return wm2000_anc_set_mode(wm2000);
+}
+
+static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = {
+/* Externally visible pins */
+SND_SOC_DAPM_OUTPUT("WM2000 SPKN"),
+SND_SOC_DAPM_OUTPUT("WM2000 SPKP"),
+
+SND_SOC_DAPM_INPUT("WM2000 LINN"),
+SND_SOC_DAPM_INPUT("WM2000 LINP"),
+
+SND_SOC_DAPM_PGA_E("ANC Engine", SND_SOC_NOPM, 0, 0, NULL, 0,
+		   wm2000_anc_power_event,
+		   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+};
+
+/* Target, Path, Source */
+static const struct snd_soc_dapm_route audio_map[] = {
+	{ "WM2000 SPKN", NULL, "ANC Engine" },
+	{ "WM2000 SPKP", NULL, "ANC Engine" },
+	{ "ANC Engine", NULL, "WM2000 LINN" },
+	{ "ANC Engine", NULL, "WM2000 LINP" },
+};
+
+/* Called from the machine driver */
+int wm2000_add_controls(struct snd_soc_codec *codec)
+{
+	int ret;
+
+	if (!wm2000_i2c) {
+		pr_err("WM2000 not yet probed\n");
+		return -ENODEV;
+	}
+
+	ret = snd_soc_dapm_new_controls(codec, wm2000_dapm_widgets,
+					ARRAY_SIZE(wm2000_dapm_widgets));
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+	if (ret < 0)
+		return ret;
+
+	return snd_soc_add_controls(codec, wm2000_controls,
+			ARRAY_SIZE(wm2000_controls));
+}
+EXPORT_SYMBOL_GPL(wm2000_add_controls);
+
+static int __devinit wm2000_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *i2c_id)
+{
+	struct wm2000_priv *wm2000;
+	struct wm2000_platform_data *pdata;
+	const char *filename;
+	const struct firmware *fw;
+	int reg, ret;
+	u16 id;
+
+	if (wm2000_i2c) {
+		dev_err(&i2c->dev, "Another WM2000 is already registered\n");
+		return -EINVAL;
+	}
+
+	wm2000 = kzalloc(sizeof(struct wm2000_priv), GFP_KERNEL);
+	if (wm2000 == NULL) {
+		dev_err(&i2c->dev, "Unable to allocate private data\n");
+		return -ENOMEM;
+	}
+
+	/* Verify that this is a WM2000 */
+	reg = wm2000_read(i2c, WM2000_REG_ID1);
+	id = reg << 8;
+	reg = wm2000_read(i2c, WM2000_REG_ID2);
+	id |= reg & 0xff;
+
+	if (id != 0x2000) {
+		dev_err(&i2c->dev, "Device is not a WM2000 - ID %x\n", id);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	reg = wm2000_read(i2c, WM2000_REG_REVISON);
+	dev_info(&i2c->dev, "revision %c\n", reg + 'A');
+
+	filename = "wm2000_anc.bin";
+	pdata = dev_get_platdata(&i2c->dev);
+	if (pdata) {
+		wm2000->mclk_div = pdata->mclkdiv2;
+		wm2000->speech_clarity = !pdata->speech_enh_disable;
+
+		if (pdata->download_file)
+			filename = pdata->download_file;
+	}
+
+	ret = request_firmware(&fw, filename, &i2c->dev);
+	if (ret != 0) {
+		dev_err(&i2c->dev, "Failed to acquire ANC data: %d\n", ret);
+		goto err;
+	}
+
+	/* Pre-cook the concatenation of the register address onto the image */
+	wm2000->anc_download_size = fw->size + 2;
+	wm2000->anc_download = kmalloc(wm2000->anc_download_size, GFP_KERNEL);
+	if (wm2000->anc_download == NULL) {
+		dev_err(&i2c->dev, "Out of memory\n");
+		ret = -ENOMEM;
+		goto err_fw;
+	}
+
+	wm2000->anc_download[0] = 0x80;
+	wm2000->anc_download[1] = 0x00;
+	memcpy(wm2000->anc_download + 2, fw->data, fw->size);
+
+	release_firmware(fw);
+
+	dev_set_drvdata(&i2c->dev, wm2000);
+	wm2000->anc_eng_ena = 1;
+	wm2000->anc_active = 1;
+	wm2000->spk_ena = 1;
+	wm2000->i2c = i2c;
+
+	wm2000_reset(wm2000);
+
+	/* This will trigger a transition to standby mode by default */
+	wm2000_anc_set_mode(wm2000);	
+
+	wm2000_i2c = i2c;
+
+	return 0;
+
+err_fw:
+	release_firmware(fw);
+err:
+	kfree(wm2000);
+	return ret;
+}
+
+static __devexit int wm2000_i2c_remove(struct i2c_client *i2c)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+	wm2000_anc_transition(wm2000, ANC_OFF);
+
+	wm2000_i2c = NULL;
+	kfree(wm2000->anc_download);
+	kfree(wm2000);
+
+	return 0;
+}
+
+static void wm2000_i2c_shutdown(struct i2c_client *i2c)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+	wm2000_anc_transition(wm2000, ANC_OFF);
+}
+
+#ifdef CONFIG_PM
+static int wm2000_i2c_suspend(struct i2c_client *i2c, pm_message_t mesg)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+	return wm2000_anc_transition(wm2000, ANC_OFF);
+}
+
+static int wm2000_i2c_resume(struct i2c_client *i2c)
+{
+	struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
+
+	return wm2000_anc_set_mode(wm2000);
+}
+#else
+#define wm2000_i2c_suspend NULL
+#define wm2000_i2c_resume NULL
+#endif
+
+static const struct i2c_device_id wm2000_i2c_id[] = {
+	{ "wm2000", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm2000_i2c_id);
+
+static struct i2c_driver wm2000_i2c_driver = {
+	.driver = {
+		.name = "wm2000",
+		.owner = THIS_MODULE,
+	},
+	.probe = wm2000_i2c_probe,
+	.remove = __devexit_p(wm2000_i2c_remove),
+	.suspend = wm2000_i2c_suspend,
+	.resume = wm2000_i2c_resume,
+	.shutdown = wm2000_i2c_shutdown,
+	.id_table = wm2000_i2c_id,
+};
+
+static int __init wm2000_init(void)
+{
+	return i2c_add_driver(&wm2000_i2c_driver);
+}
+module_init(wm2000_init);
+
+static void __exit wm2000_exit(void)
+{
+	i2c_del_driver(&wm2000_i2c_driver);
+}
+module_exit(wm2000_exit);
+
+MODULE_DESCRIPTION("ASoC WM2000 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm2000.h b/linux/sound/soc/codecs/wm2000.h
new file mode 100644
index 0000000..0b6f056
--- /dev/null
+++ b/linux/sound/soc/codecs/wm2000.h
@@ -0,0 +1,76 @@
+/*
+ * wm2000.h  --  WM2000 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM2000_H
+#define _WM2000_H
+
+struct wm2000_setup_data {
+	unsigned short i2c_address;
+	int mclk_div;   /* Set to a non-zero value if MCLK_DIV_2 required */
+};
+
+extern int wm2000_add_controls(struct snd_soc_codec *codec);
+
+#define WM2000_REG_SYS_START	    0x8000
+#define WM2000_REG_SPEECH_CLARITY   0x8fef
+#define WM2000_REG_SYS_WATCHDOG     0x8ff6
+#define WM2000_REG_ANA_VMID_PD_TIME 0x8ff7
+#define WM2000_REG_ANA_VMID_PU_TIME 0x8ff8
+#define WM2000_REG_CAT_FLTR_INDX    0x8ff9
+#define WM2000_REG_CAT_GAIN_0       0x8ffa
+#define WM2000_REG_SYS_STATUS       0x8ffc
+#define WM2000_REG_SYS_MODE_CNTRL   0x8ffd
+#define WM2000_REG_SYS_START0       0x8ffe
+#define WM2000_REG_SYS_START1       0x8fff
+#define WM2000_REG_ID1              0xf000
+#define WM2000_REG_ID2              0xf001
+#define WM2000_REG_REVISON          0xf002
+#define WM2000_REG_SYS_CTL1         0xf003
+#define WM2000_REG_SYS_CTL2         0xf004
+#define WM2000_REG_ANC_STAT         0xf005
+#define WM2000_REG_IF_CTL           0xf006
+
+/* SPEECH_CLARITY */
+#define WM2000_SPEECH_CLARITY   0x01
+
+/* SYS_STATUS */
+#define WM2000_STATUS_MOUSE_ACTIVE              0x40
+#define WM2000_STATUS_CAT_FREQ_COMPLETE         0x20
+#define WM2000_STATUS_CAT_GAIN_COMPLETE         0x10
+#define WM2000_STATUS_THERMAL_SHUTDOWN_COMPLETE 0x08
+#define WM2000_STATUS_ANC_DISABLED              0x04
+#define WM2000_STATUS_POWER_DOWN_COMPLETE       0x02
+#define WM2000_STATUS_BOOT_COMPLETE             0x01
+
+/* SYS_MODE_CNTRL */
+#define WM2000_MODE_ANA_SEQ_INCLUDE 0x80
+#define WM2000_MODE_MOUSE_ENABLE    0x40
+#define WM2000_MODE_CAT_FREQ_ENABLE 0x20
+#define WM2000_MODE_CAT_GAIN_ENABLE 0x10
+#define WM2000_MODE_BYPASS_ENTRY    0x08
+#define WM2000_MODE_STANDBY_ENTRY   0x04
+#define WM2000_MODE_THERMAL_ENABLE  0x02
+#define WM2000_MODE_POWER_DOWN      0x01
+
+/* SYS_CTL1 */
+#define WM2000_SYS_STBY          0x01
+
+/* SYS_CTL2 */
+#define WM2000_MCLK_DIV2_ENA_CLR 0x80
+#define WM2000_MCLK_DIV2_ENA_SET 0x40
+#define WM2000_ANC_ENG_CLR       0x20
+#define WM2000_ANC_ENG_SET       0x10
+#define WM2000_ANC_INT_N_CLR     0x08
+#define WM2000_ANC_INT_N_SET     0x04
+#define WM2000_RAM_CLR           0x02
+#define WM2000_RAM_SET           0x01
+
+/* ANC_STAT */
+#define WM2000_ANC_ENG_IDLE      0x01
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8350.c b/linux/sound/soc/codecs/wm8350.c
new file mode 100644
index 0000000..7611add
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8350.c
@@ -0,0 +1,1709 @@
+/*
+ * wm8350.c -- WM8350 ALSA SoC audio driver
+ *
+ * Copyright (C) 2007, 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/wm8350/audio.h>
+#include <linux/mfd/wm8350/core.h>
+#include <linux/regulator/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8350.h"
+
+#define WM8350_OUTn_0dB 0x39
+
+#define WM8350_RAMP_NONE	0
+#define WM8350_RAMP_UP		1
+#define WM8350_RAMP_DOWN	2
+
+/* We only include the analogue supplies here; the digital supplies
+ * need to be available well before this driver can be probed.
+ */
+static const char *supply_names[] = {
+	"AVDD",
+	"HPVDD",
+};
+
+struct wm8350_output {
+	u16 active;
+	u16 left_vol;
+	u16 right_vol;
+	u16 ramp;
+	u16 mute;
+};
+
+struct wm8350_jack_data {
+	struct snd_soc_jack *jack;
+	int report;
+	int short_report;
+};
+
+struct wm8350_data {
+	struct snd_soc_codec codec;
+	struct wm8350_output out1;
+	struct wm8350_output out2;
+	struct wm8350_jack_data hpl;
+	struct wm8350_jack_data hpr;
+	struct wm8350_jack_data mic;
+	struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
+	int fll_freq_out;
+	int fll_freq_in;
+};
+
+static unsigned int wm8350_codec_cache_read(struct snd_soc_codec *codec,
+					    unsigned int reg)
+{
+	struct wm8350 *wm8350 = codec->control_data;
+	return wm8350->reg_cache[reg];
+}
+
+static unsigned int wm8350_codec_read(struct snd_soc_codec *codec,
+				      unsigned int reg)
+{
+	struct wm8350 *wm8350 = codec->control_data;
+	return wm8350_reg_read(wm8350, reg);
+}
+
+static int wm8350_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+			      unsigned int value)
+{
+	struct wm8350 *wm8350 = codec->control_data;
+	return wm8350_reg_write(wm8350, reg, value);
+}
+
+/*
+ * Ramp OUT1 PGA volume to minimise pops at stream startup and shutdown.
+ */
+static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec)
+{
+	struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec);
+	struct wm8350_output *out1 = &wm8350_data->out1;
+	struct wm8350 *wm8350 = codec->control_data;
+	int left_complete = 0, right_complete = 0;
+	u16 reg, val;
+
+	/* left channel */
+	reg = wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME);
+	val = (reg & WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+
+	if (out1->ramp == WM8350_RAMP_UP) {
+		/* ramp step up */
+		if (val < out1->left_vol) {
+			val++;
+			reg &= ~WM8350_OUT1L_VOL_MASK;
+			wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME,
+					 reg | (val << WM8350_OUT1L_VOL_SHIFT));
+		} else
+			left_complete = 1;
+	} else if (out1->ramp == WM8350_RAMP_DOWN) {
+		/* ramp step down */
+		if (val > 0) {
+			val--;
+			reg &= ~WM8350_OUT1L_VOL_MASK;
+			wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME,
+					 reg | (val << WM8350_OUT1L_VOL_SHIFT));
+		} else
+			left_complete = 1;
+	} else
+		return 1;
+
+	/* right channel */
+	reg = wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME);
+	val = (reg & WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+	if (out1->ramp == WM8350_RAMP_UP) {
+		/* ramp step up */
+		if (val < out1->right_vol) {
+			val++;
+			reg &= ~WM8350_OUT1R_VOL_MASK;
+			wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME,
+					 reg | (val << WM8350_OUT1R_VOL_SHIFT));
+		} else
+			right_complete = 1;
+	} else if (out1->ramp == WM8350_RAMP_DOWN) {
+		/* ramp step down */
+		if (val > 0) {
+			val--;
+			reg &= ~WM8350_OUT1R_VOL_MASK;
+			wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME,
+					 reg | (val << WM8350_OUT1R_VOL_SHIFT));
+		} else
+			right_complete = 1;
+	}
+
+	/* only hit the update bit if either volume has changed this step */
+	if (!left_complete || !right_complete)
+		wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, WM8350_OUT1_VU);
+
+	return left_complete & right_complete;
+}
+
+/*
+ * Ramp OUT2 PGA volume to minimise pops at stream startup and shutdown.
+ */
+static inline int wm8350_out2_ramp_step(struct snd_soc_codec *codec)
+{
+	struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec);
+	struct wm8350_output *out2 = &wm8350_data->out2;
+	struct wm8350 *wm8350 = codec->control_data;
+	int left_complete = 0, right_complete = 0;
+	u16 reg, val;
+
+	/* left channel */
+	reg = wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME);
+	val = (reg & WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+	if (out2->ramp == WM8350_RAMP_UP) {
+		/* ramp step up */
+		if (val < out2->left_vol) {
+			val++;
+			reg &= ~WM8350_OUT2L_VOL_MASK;
+			wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME,
+					 reg | (val << WM8350_OUT1L_VOL_SHIFT));
+		} else
+			left_complete = 1;
+	} else if (out2->ramp == WM8350_RAMP_DOWN) {
+		/* ramp step down */
+		if (val > 0) {
+			val--;
+			reg &= ~WM8350_OUT2L_VOL_MASK;
+			wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME,
+					 reg | (val << WM8350_OUT1L_VOL_SHIFT));
+		} else
+			left_complete = 1;
+	} else
+		return 1;
+
+	/* right channel */
+	reg = wm8350_reg_read(wm8350, WM8350_ROUT2_VOLUME);
+	val = (reg & WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+	if (out2->ramp == WM8350_RAMP_UP) {
+		/* ramp step up */
+		if (val < out2->right_vol) {
+			val++;
+			reg &= ~WM8350_OUT2R_VOL_MASK;
+			wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME,
+					 reg | (val << WM8350_OUT1R_VOL_SHIFT));
+		} else
+			right_complete = 1;
+	} else if (out2->ramp == WM8350_RAMP_DOWN) {
+		/* ramp step down */
+		if (val > 0) {
+			val--;
+			reg &= ~WM8350_OUT2R_VOL_MASK;
+			wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME,
+					 reg | (val << WM8350_OUT1R_VOL_SHIFT));
+		} else
+			right_complete = 1;
+	}
+
+	/* only hit the update bit if either volume has changed this step */
+	if (!left_complete || !right_complete)
+		wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME, WM8350_OUT2_VU);
+
+	return left_complete & right_complete;
+}
+
+/*
+ * This work ramps both output PGAs at stream start/stop time to
+ * minimise pop associated with DAPM power switching.
+ * It's best to enable Zero Cross when ramping occurs to minimise any
+ * zipper noises.
+ */
+static void wm8350_pga_work(struct work_struct *work)
+{
+	struct snd_soc_codec *codec =
+	    container_of(work, struct snd_soc_codec, delayed_work.work);
+	struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec);
+	struct wm8350_output *out1 = &wm8350_data->out1,
+	    *out2 = &wm8350_data->out2;
+	int i, out1_complete, out2_complete;
+
+	/* do we need to ramp at all ? */
+	if (out1->ramp == WM8350_RAMP_NONE && out2->ramp == WM8350_RAMP_NONE)
+		return;
+
+	/* PGA volumes have 6 bits of resolution to ramp */
+	for (i = 0; i <= 63; i++) {
+		out1_complete = 1, out2_complete = 1;
+		if (out1->ramp != WM8350_RAMP_NONE)
+			out1_complete = wm8350_out1_ramp_step(codec);
+		if (out2->ramp != WM8350_RAMP_NONE)
+			out2_complete = wm8350_out2_ramp_step(codec);
+
+		/* ramp finished ? */
+		if (out1_complete && out2_complete)
+			break;
+
+		/* we need to delay longer on the up ramp */
+		if (out1->ramp == WM8350_RAMP_UP ||
+		    out2->ramp == WM8350_RAMP_UP) {
+			/* delay is longer over 0dB as increases are larger */
+			if (i >= WM8350_OUTn_0dB)
+				schedule_timeout_interruptible(msecs_to_jiffies
+							       (2));
+			else
+				schedule_timeout_interruptible(msecs_to_jiffies
+							       (1));
+		} else
+			udelay(50);	/* doesn't matter if we delay longer */
+	}
+
+	out1->ramp = WM8350_RAMP_NONE;
+	out2->ramp = WM8350_RAMP_NONE;
+}
+
+/*
+ * WM8350 Controls
+ */
+
+static int pga_event(struct snd_soc_dapm_widget *w,
+		     struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec);
+	struct wm8350_output *out;
+
+	switch (w->shift) {
+	case 0:
+	case 1:
+		out = &wm8350_data->out1;
+		break;
+	case 2:
+	case 3:
+		out = &wm8350_data->out2;
+		break;
+
+	default:
+		BUG();
+		return -1;
+	}
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		out->ramp = WM8350_RAMP_UP;
+		out->active = 1;
+
+		if (!delayed_work_pending(&codec->delayed_work))
+			schedule_delayed_work(&codec->delayed_work,
+					      msecs_to_jiffies(1));
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		out->ramp = WM8350_RAMP_DOWN;
+		out->active = 0;
+
+		if (!delayed_work_pending(&codec->delayed_work))
+			schedule_delayed_work(&codec->delayed_work,
+					      msecs_to_jiffies(1));
+		break;
+	}
+
+	return 0;
+}
+
+static int wm8350_put_volsw_2r_vu(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8350_data *wm8350_priv = snd_soc_codec_get_drvdata(codec);
+	struct wm8350_output *out = NULL;
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	int ret;
+	unsigned int reg = mc->reg;
+	u16 val;
+
+	/* For OUT1 and OUT2 we shadow the values and only actually write
+	 * them out when active in order to ensure the amplifier comes on
+	 * as quietly as possible. */
+	switch (reg) {
+	case WM8350_LOUT1_VOLUME:
+		out = &wm8350_priv->out1;
+		break;
+	case WM8350_LOUT2_VOLUME:
+		out = &wm8350_priv->out2;
+		break;
+	default:
+		break;
+	}
+
+	if (out) {
+		out->left_vol = ucontrol->value.integer.value[0];
+		out->right_vol = ucontrol->value.integer.value[1];
+		if (!out->active)
+			return 1;
+	}
+
+	ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+	if (ret < 0)
+		return ret;
+
+	/* now hit the volume update bits (always bit 8) */
+	val = wm8350_codec_read(codec, reg);
+	wm8350_codec_write(codec, reg, val | WM8350_OUT1_VU);
+	return 1;
+}
+
+static int wm8350_get_volsw_2r(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8350_data *wm8350_priv = snd_soc_codec_get_drvdata(codec);
+	struct wm8350_output *out1 = &wm8350_priv->out1;
+	struct wm8350_output *out2 = &wm8350_priv->out2;
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+
+	/* If these are cached registers use the cache */
+	switch (reg) {
+	case WM8350_LOUT1_VOLUME:
+		ucontrol->value.integer.value[0] = out1->left_vol;
+		ucontrol->value.integer.value[1] = out1->right_vol;
+		return 0;
+
+	case WM8350_LOUT2_VOLUME:
+		ucontrol->value.integer.value[0] = out2->left_vol;
+		ucontrol->value.integer.value[1] = out2->right_vol;
+		return 0;
+
+	default:
+		break;
+	}
+
+	return snd_soc_get_volsw_2r(kcontrol, ucontrol);
+}
+
+/* double control with volume update */
+#define SOC_WM8350_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, \
+				xinvert, tlv_array) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+		SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+		SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+	.tlv.p = (tlv_array), \
+	.info = snd_soc_info_volsw_2r, \
+	.get = wm8350_get_volsw_2r, .put = wm8350_put_volsw_2r_vu, \
+	.private_value = (unsigned long)&(struct soc_mixer_control) \
+		{.reg = reg_left, .rreg = reg_right, .shift = xshift, \
+		 .rshift = xshift, .max = xmax, .invert = xinvert}, }
+
+static const char *wm8350_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8350_pol[] = { "Normal", "Inv R", "Inv L", "Inv L & R" };
+static const char *wm8350_dacmutem[] = { "Normal", "Soft" };
+static const char *wm8350_dacmutes[] = { "Fast", "Slow" };
+static const char *wm8350_adcfilter[] = { "None", "High Pass" };
+static const char *wm8350_adchp[] = { "44.1kHz", "8kHz", "16kHz", "32kHz" };
+static const char *wm8350_lr[] = { "Left", "Right" };
+
+static const struct soc_enum wm8350_enum[] = {
+	SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 4, 4, wm8350_deemp),
+	SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol),
+	SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem),
+	SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes),
+	SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter),
+	SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp),
+	SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol),
+	SOC_ENUM_SINGLE(WM8350_INPUT_MIXER_VOLUME, 15, 2, wm8350_lr),
+};
+
+static DECLARE_TLV_DB_SCALE(pre_amp_tlv, -1200, 3525, 0);
+static DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 600, 0);
+static DECLARE_TLV_DB_SCALE(dac_pcm_tlv, -7163, 36, 1);
+static DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -12700, 50, 1);
+static DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 1);
+
+static const unsigned int capture_sd_tlv[] = {
+	TLV_DB_RANGE_HEAD(2),
+	0, 12, TLV_DB_SCALE_ITEM(-3600, 300, 1),
+	13, 15, TLV_DB_SCALE_ITEM(0, 0, 0),
+};
+
+static const struct snd_kcontrol_new wm8350_snd_controls[] = {
+	SOC_ENUM("Playback Deemphasis", wm8350_enum[0]),
+	SOC_ENUM("Playback DAC Inversion", wm8350_enum[1]),
+	SOC_WM8350_DOUBLE_R_TLV("Playback PCM Volume",
+				WM8350_DAC_DIGITAL_VOLUME_L,
+				WM8350_DAC_DIGITAL_VOLUME_R,
+				0, 255, 0, dac_pcm_tlv),
+	SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]),
+	SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]),
+	SOC_ENUM("Capture PCM Filter", wm8350_enum[4]),
+	SOC_ENUM("Capture PCM HP Filter", wm8350_enum[5]),
+	SOC_ENUM("Capture ADC Inversion", wm8350_enum[6]),
+	SOC_WM8350_DOUBLE_R_TLV("Capture PCM Volume",
+				WM8350_ADC_DIGITAL_VOLUME_L,
+				WM8350_ADC_DIGITAL_VOLUME_R,
+				0, 255, 0, adc_pcm_tlv),
+	SOC_DOUBLE_TLV("Capture Sidetone Volume",
+		       WM8350_ADC_DIVIDER,
+		       8, 4, 15, 1, capture_sd_tlv),
+	SOC_WM8350_DOUBLE_R_TLV("Capture Volume",
+				WM8350_LEFT_INPUT_VOLUME,
+				WM8350_RIGHT_INPUT_VOLUME,
+				2, 63, 0, pre_amp_tlv),
+	SOC_DOUBLE_R("Capture ZC Switch",
+		     WM8350_LEFT_INPUT_VOLUME,
+		     WM8350_RIGHT_INPUT_VOLUME, 13, 1, 0),
+	SOC_SINGLE_TLV("Left Input Left Sidetone Volume",
+		       WM8350_OUTPUT_LEFT_MIXER_VOLUME, 1, 7, 0, out_mix_tlv),
+	SOC_SINGLE_TLV("Left Input Right Sidetone Volume",
+		       WM8350_OUTPUT_LEFT_MIXER_VOLUME,
+		       5, 7, 0, out_mix_tlv),
+	SOC_SINGLE_TLV("Left Input Bypass Volume",
+		       WM8350_OUTPUT_LEFT_MIXER_VOLUME,
+		       9, 7, 0, out_mix_tlv),
+	SOC_SINGLE_TLV("Right Input Left Sidetone Volume",
+		       WM8350_OUTPUT_RIGHT_MIXER_VOLUME,
+		       1, 7, 0, out_mix_tlv),
+	SOC_SINGLE_TLV("Right Input Right Sidetone Volume",
+		       WM8350_OUTPUT_RIGHT_MIXER_VOLUME,
+		       5, 7, 0, out_mix_tlv),
+	SOC_SINGLE_TLV("Right Input Bypass Volume",
+		       WM8350_OUTPUT_RIGHT_MIXER_VOLUME,
+		       13, 7, 0, out_mix_tlv),
+	SOC_SINGLE("Left Input Mixer +20dB Switch",
+		   WM8350_INPUT_MIXER_VOLUME_L, 0, 1, 0),
+	SOC_SINGLE("Right Input Mixer +20dB Switch",
+		   WM8350_INPUT_MIXER_VOLUME_R, 0, 1, 0),
+	SOC_SINGLE_TLV("Out4 Capture Volume",
+		       WM8350_INPUT_MIXER_VOLUME,
+		       1, 7, 0, out_mix_tlv),
+	SOC_WM8350_DOUBLE_R_TLV("Out1 Playback Volume",
+				WM8350_LOUT1_VOLUME,
+				WM8350_ROUT1_VOLUME,
+				2, 63, 0, out_pga_tlv),
+	SOC_DOUBLE_R("Out1 Playback ZC Switch",
+		     WM8350_LOUT1_VOLUME,
+		     WM8350_ROUT1_VOLUME, 13, 1, 0),
+	SOC_WM8350_DOUBLE_R_TLV("Out2 Playback Volume",
+				WM8350_LOUT2_VOLUME,
+				WM8350_ROUT2_VOLUME,
+				2, 63, 0, out_pga_tlv),
+	SOC_DOUBLE_R("Out2 Playback ZC Switch", WM8350_LOUT2_VOLUME,
+		     WM8350_ROUT2_VOLUME, 13, 1, 0),
+	SOC_SINGLE("Out2 Right Invert Switch", WM8350_ROUT2_VOLUME, 10, 1, 0),
+	SOC_SINGLE_TLV("Out2 Beep Volume", WM8350_BEEP_VOLUME,
+		       5, 7, 0, out_mix_tlv),
+
+	SOC_DOUBLE_R("Out1 Playback Switch",
+		     WM8350_LOUT1_VOLUME,
+		     WM8350_ROUT1_VOLUME,
+		     14, 1, 1),
+	SOC_DOUBLE_R("Out2 Playback Switch",
+		     WM8350_LOUT2_VOLUME,
+		     WM8350_ROUT2_VOLUME,
+		     14, 1, 1),
+};
+
+/*
+ * DAPM Controls
+ */
+
+/* Left Playback Mixer */
+static const struct snd_kcontrol_new wm8350_left_play_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Playback Switch",
+			WM8350_LEFT_MIXER_CONTROL, 11, 1, 0),
+	SOC_DAPM_SINGLE("Left Bypass Switch",
+			WM8350_LEFT_MIXER_CONTROL, 2, 1, 0),
+	SOC_DAPM_SINGLE("Right Playback Switch",
+			WM8350_LEFT_MIXER_CONTROL, 12, 1, 0),
+	SOC_DAPM_SINGLE("Left Sidetone Switch",
+			WM8350_LEFT_MIXER_CONTROL, 0, 1, 0),
+	SOC_DAPM_SINGLE("Right Sidetone Switch",
+			WM8350_LEFT_MIXER_CONTROL, 1, 1, 0),
+};
+
+/* Right Playback Mixer */
+static const struct snd_kcontrol_new wm8350_right_play_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Playback Switch",
+			WM8350_RIGHT_MIXER_CONTROL, 12, 1, 0),
+	SOC_DAPM_SINGLE("Right Bypass Switch",
+			WM8350_RIGHT_MIXER_CONTROL, 3, 1, 0),
+	SOC_DAPM_SINGLE("Left Playback Switch",
+			WM8350_RIGHT_MIXER_CONTROL, 11, 1, 0),
+	SOC_DAPM_SINGLE("Left Sidetone Switch",
+			WM8350_RIGHT_MIXER_CONTROL, 0, 1, 0),
+	SOC_DAPM_SINGLE("Right Sidetone Switch",
+			WM8350_RIGHT_MIXER_CONTROL, 1, 1, 0),
+};
+
+/* Out4 Mixer */
+static const struct snd_kcontrol_new wm8350_out4_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Right Playback Switch",
+			WM8350_OUT4_MIXER_CONTROL, 12, 1, 0),
+	SOC_DAPM_SINGLE("Left Playback Switch",
+			WM8350_OUT4_MIXER_CONTROL, 11, 1, 0),
+	SOC_DAPM_SINGLE("Right Capture Switch",
+			WM8350_OUT4_MIXER_CONTROL, 9, 1, 0),
+	SOC_DAPM_SINGLE("Out3 Playback Switch",
+			WM8350_OUT4_MIXER_CONTROL, 2, 1, 0),
+	SOC_DAPM_SINGLE("Right Mixer Switch",
+			WM8350_OUT4_MIXER_CONTROL, 1, 1, 0),
+	SOC_DAPM_SINGLE("Left Mixer Switch",
+			WM8350_OUT4_MIXER_CONTROL, 0, 1, 0),
+};
+
+/* Out3 Mixer */
+static const struct snd_kcontrol_new wm8350_out3_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Left Playback Switch",
+			WM8350_OUT3_MIXER_CONTROL, 11, 1, 0),
+	SOC_DAPM_SINGLE("Left Capture Switch",
+			WM8350_OUT3_MIXER_CONTROL, 8, 1, 0),
+	SOC_DAPM_SINGLE("Out4 Playback Switch",
+			WM8350_OUT3_MIXER_CONTROL, 3, 1, 0),
+	SOC_DAPM_SINGLE("Left Mixer Switch",
+			WM8350_OUT3_MIXER_CONTROL, 0, 1, 0),
+};
+
+/* Left Input Mixer */
+static const struct snd_kcontrol_new wm8350_left_capt_mixer_controls[] = {
+	SOC_DAPM_SINGLE_TLV("L2 Capture Volume",
+			    WM8350_INPUT_MIXER_VOLUME_L, 1, 7, 0, out_mix_tlv),
+	SOC_DAPM_SINGLE_TLV("L3 Capture Volume",
+			    WM8350_INPUT_MIXER_VOLUME_L, 9, 7, 0, out_mix_tlv),
+	SOC_DAPM_SINGLE("PGA Capture Switch",
+			WM8350_LEFT_INPUT_VOLUME, 14, 1, 1),
+};
+
+/* Right Input Mixer */
+static const struct snd_kcontrol_new wm8350_right_capt_mixer_controls[] = {
+	SOC_DAPM_SINGLE_TLV("L2 Capture Volume",
+			    WM8350_INPUT_MIXER_VOLUME_R, 5, 7, 0, out_mix_tlv),
+	SOC_DAPM_SINGLE_TLV("L3 Capture Volume",
+			    WM8350_INPUT_MIXER_VOLUME_R, 13, 7, 0, out_mix_tlv),
+	SOC_DAPM_SINGLE("PGA Capture Switch",
+			WM8350_RIGHT_INPUT_VOLUME, 14, 1, 1),
+};
+
+/* Left Mic Mixer */
+static const struct snd_kcontrol_new wm8350_left_mic_mixer_controls[] = {
+	SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 1, 1, 0),
+	SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 0, 1, 0),
+	SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 2, 1, 0),
+};
+
+/* Right Mic Mixer */
+static const struct snd_kcontrol_new wm8350_right_mic_mixer_controls[] = {
+	SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 9, 1, 0),
+	SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 8, 1, 0),
+	SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 10, 1, 0),
+};
+
+/* Beep Switch */
+static const struct snd_kcontrol_new wm8350_beep_switch_controls =
+SOC_DAPM_SINGLE("Switch", WM8350_BEEP_VOLUME, 15, 1, 1);
+
+/* Out4 Capture Mux */
+static const struct snd_kcontrol_new wm8350_out4_capture_controls =
+SOC_DAPM_ENUM("Route", wm8350_enum[7]);
+
+static const struct snd_soc_dapm_widget wm8350_dapm_widgets[] = {
+
+	SND_SOC_DAPM_PGA("IN3R PGA", WM8350_POWER_MGMT_2, 11, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("IN3L PGA", WM8350_POWER_MGMT_2, 10, 0, NULL, 0),
+	SND_SOC_DAPM_PGA_E("Right Out2 PGA", WM8350_POWER_MGMT_3, 3, 0, NULL,
+			   0, pga_event,
+			   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_PGA_E("Left Out2 PGA", WM8350_POWER_MGMT_3, 2, 0, NULL, 0,
+			   pga_event,
+			   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_PGA_E("Right Out1 PGA", WM8350_POWER_MGMT_3, 1, 0, NULL,
+			   0, pga_event,
+			   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_PGA_E("Left Out1 PGA", WM8350_POWER_MGMT_3, 0, 0, NULL, 0,
+			   pga_event,
+			   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+	SND_SOC_DAPM_MIXER("Right Capture Mixer", WM8350_POWER_MGMT_2,
+			   7, 0, &wm8350_right_capt_mixer_controls[0],
+			   ARRAY_SIZE(wm8350_right_capt_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("Left Capture Mixer", WM8350_POWER_MGMT_2,
+			   6, 0, &wm8350_left_capt_mixer_controls[0],
+			   ARRAY_SIZE(wm8350_left_capt_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("Out4 Mixer", WM8350_POWER_MGMT_2, 5, 0,
+			   &wm8350_out4_mixer_controls[0],
+			   ARRAY_SIZE(wm8350_out4_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("Out3 Mixer", WM8350_POWER_MGMT_2, 4, 0,
+			   &wm8350_out3_mixer_controls[0],
+			   ARRAY_SIZE(wm8350_out3_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("Right Playback Mixer", WM8350_POWER_MGMT_2, 1, 0,
+			   &wm8350_right_play_mixer_controls[0],
+			   ARRAY_SIZE(wm8350_right_play_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("Left Playback Mixer", WM8350_POWER_MGMT_2, 0, 0,
+			   &wm8350_left_play_mixer_controls[0],
+			   ARRAY_SIZE(wm8350_left_play_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("Left Mic Mixer", WM8350_POWER_MGMT_2, 8, 0,
+			   &wm8350_left_mic_mixer_controls[0],
+			   ARRAY_SIZE(wm8350_left_mic_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("Right Mic Mixer", WM8350_POWER_MGMT_2, 9, 0,
+			   &wm8350_right_mic_mixer_controls[0],
+			   ARRAY_SIZE(wm8350_right_mic_mixer_controls)),
+
+	/* virtual mixer for Beep and Out2R */
+	SND_SOC_DAPM_MIXER("Out2 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_SWITCH("Beep", WM8350_POWER_MGMT_3, 7, 0,
+			    &wm8350_beep_switch_controls),
+
+	SND_SOC_DAPM_ADC("Right ADC", "Right Capture",
+			 WM8350_POWER_MGMT_4, 3, 0),
+	SND_SOC_DAPM_ADC("Left ADC", "Left Capture",
+			 WM8350_POWER_MGMT_4, 2, 0),
+	SND_SOC_DAPM_DAC("Right DAC", "Right Playback",
+			 WM8350_POWER_MGMT_4, 5, 0),
+	SND_SOC_DAPM_DAC("Left DAC", "Left Playback",
+			 WM8350_POWER_MGMT_4, 4, 0),
+
+	SND_SOC_DAPM_MICBIAS("Mic Bias", WM8350_POWER_MGMT_1, 4, 0),
+
+	SND_SOC_DAPM_MUX("Out4 Capture Channel", SND_SOC_NOPM, 0, 0,
+			 &wm8350_out4_capture_controls),
+
+	SND_SOC_DAPM_OUTPUT("OUT1R"),
+	SND_SOC_DAPM_OUTPUT("OUT1L"),
+	SND_SOC_DAPM_OUTPUT("OUT2R"),
+	SND_SOC_DAPM_OUTPUT("OUT2L"),
+	SND_SOC_DAPM_OUTPUT("OUT3"),
+	SND_SOC_DAPM_OUTPUT("OUT4"),
+
+	SND_SOC_DAPM_INPUT("IN1RN"),
+	SND_SOC_DAPM_INPUT("IN1RP"),
+	SND_SOC_DAPM_INPUT("IN2R"),
+	SND_SOC_DAPM_INPUT("IN1LP"),
+	SND_SOC_DAPM_INPUT("IN1LN"),
+	SND_SOC_DAPM_INPUT("IN2L"),
+	SND_SOC_DAPM_INPUT("IN3R"),
+	SND_SOC_DAPM_INPUT("IN3L"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+
+	/* left playback mixer */
+	{"Left Playback Mixer", "Playback Switch", "Left DAC"},
+	{"Left Playback Mixer", "Left Bypass Switch", "IN3L PGA"},
+	{"Left Playback Mixer", "Right Playback Switch", "Right DAC"},
+	{"Left Playback Mixer", "Left Sidetone Switch", "Left Mic Mixer"},
+	{"Left Playback Mixer", "Right Sidetone Switch", "Right Mic Mixer"},
+
+	/* right playback mixer */
+	{"Right Playback Mixer", "Playback Switch", "Right DAC"},
+	{"Right Playback Mixer", "Right Bypass Switch", "IN3R PGA"},
+	{"Right Playback Mixer", "Left Playback Switch", "Left DAC"},
+	{"Right Playback Mixer", "Left Sidetone Switch", "Left Mic Mixer"},
+	{"Right Playback Mixer", "Right Sidetone Switch", "Right Mic Mixer"},
+
+	/* out4 playback mixer */
+	{"Out4 Mixer", "Right Playback Switch", "Right DAC"},
+	{"Out4 Mixer", "Left Playback Switch", "Left DAC"},
+	{"Out4 Mixer", "Right Capture Switch", "Right Capture Mixer"},
+	{"Out4 Mixer", "Out3 Playback Switch", "Out3 Mixer"},
+	{"Out4 Mixer", "Right Mixer Switch", "Right Playback Mixer"},
+	{"Out4 Mixer", "Left Mixer Switch", "Left Playback Mixer"},
+	{"OUT4", NULL, "Out4 Mixer"},
+
+	/* out3 playback mixer */
+	{"Out3 Mixer", "Left Playback Switch", "Left DAC"},
+	{"Out3 Mixer", "Left Capture Switch", "Left Capture Mixer"},
+	{"Out3 Mixer", "Left Mixer Switch", "Left Playback Mixer"},
+	{"Out3 Mixer", "Out4 Playback Switch", "Out4 Mixer"},
+	{"OUT3", NULL, "Out3 Mixer"},
+
+	/* out2 */
+	{"Right Out2 PGA", NULL, "Right Playback Mixer"},
+	{"Left Out2 PGA", NULL, "Left Playback Mixer"},
+	{"OUT2L", NULL, "Left Out2 PGA"},
+	{"OUT2R", NULL, "Right Out2 PGA"},
+
+	/* out1 */
+	{"Right Out1 PGA", NULL, "Right Playback Mixer"},
+	{"Left Out1 PGA", NULL, "Left Playback Mixer"},
+	{"OUT1L", NULL, "Left Out1 PGA"},
+	{"OUT1R", NULL, "Right Out1 PGA"},
+
+	/* ADCs */
+	{"Left ADC", NULL, "Left Capture Mixer"},
+	{"Right ADC", NULL, "Right Capture Mixer"},
+
+	/* Left capture mixer */
+	{"Left Capture Mixer", "L2 Capture Volume", "IN2L"},
+	{"Left Capture Mixer", "L3 Capture Volume", "IN3L PGA"},
+	{"Left Capture Mixer", "PGA Capture Switch", "Left Mic Mixer"},
+	{"Left Capture Mixer", NULL, "Out4 Capture Channel"},
+
+	/* Right capture mixer */
+	{"Right Capture Mixer", "L2 Capture Volume", "IN2R"},
+	{"Right Capture Mixer", "L3 Capture Volume", "IN3R PGA"},
+	{"Right Capture Mixer", "PGA Capture Switch", "Right Mic Mixer"},
+	{"Right Capture Mixer", NULL, "Out4 Capture Channel"},
+
+	/* L3 Inputs */
+	{"IN3L PGA", NULL, "IN3L"},
+	{"IN3R PGA", NULL, "IN3R"},
+
+	/* Left Mic mixer */
+	{"Left Mic Mixer", "INN Capture Switch", "IN1LN"},
+	{"Left Mic Mixer", "INP Capture Switch", "IN1LP"},
+	{"Left Mic Mixer", "IN2 Capture Switch", "IN2L"},
+
+	/* Right Mic mixer */
+	{"Right Mic Mixer", "INN Capture Switch", "IN1RN"},
+	{"Right Mic Mixer", "INP Capture Switch", "IN1RP"},
+	{"Right Mic Mixer", "IN2 Capture Switch", "IN2R"},
+
+	/* out 4 capture */
+	{"Out4 Capture Channel", NULL, "Out4 Mixer"},
+
+	/* Beep */
+	{"Beep", NULL, "IN3R PGA"},
+};
+
+static int wm8350_add_widgets(struct snd_soc_codec *codec)
+{
+	int ret;
+
+	ret = snd_soc_dapm_new_controls(codec,
+					wm8350_dapm_widgets,
+					ARRAY_SIZE(wm8350_dapm_widgets));
+	if (ret != 0) {
+		dev_err(codec->dev, "dapm control register failed\n");
+		return ret;
+	}
+
+	/* set up audio paths */
+	ret = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+	if (ret != 0) {
+		dev_err(codec->dev, "DAPM route register failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int wm8350_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+				 int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8350 *wm8350 = codec->control_data;
+	u16 fll_4;
+
+	switch (clk_id) {
+	case WM8350_MCLK_SEL_MCLK:
+		wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_1,
+				  WM8350_MCLK_SEL);
+		break;
+	case WM8350_MCLK_SEL_PLL_MCLK:
+	case WM8350_MCLK_SEL_PLL_DAC:
+	case WM8350_MCLK_SEL_PLL_ADC:
+	case WM8350_MCLK_SEL_PLL_32K:
+		wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_1,
+				WM8350_MCLK_SEL);
+		fll_4 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_4) &
+		    ~WM8350_FLL_CLK_SRC_MASK;
+		wm8350_codec_write(codec, WM8350_FLL_CONTROL_4, fll_4 | clk_id);
+		break;
+	}
+
+	/* MCLK direction */
+	if (dir == SND_SOC_CLOCK_OUT)
+		wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+				WM8350_MCLK_DIR);
+	else
+		wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+				  WM8350_MCLK_DIR);
+
+	return 0;
+}
+
+static int wm8350_set_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 val;
+
+	switch (div_id) {
+	case WM8350_ADC_CLKDIV:
+		val = wm8350_codec_read(codec, WM8350_ADC_DIVIDER) &
+		    ~WM8350_ADC_CLKDIV_MASK;
+		wm8350_codec_write(codec, WM8350_ADC_DIVIDER, val | div);
+		break;
+	case WM8350_DAC_CLKDIV:
+		val = wm8350_codec_read(codec, WM8350_DAC_CLOCK_CONTROL) &
+		    ~WM8350_DAC_CLKDIV_MASK;
+		wm8350_codec_write(codec, WM8350_DAC_CLOCK_CONTROL, val | div);
+		break;
+	case WM8350_BCLK_CLKDIV:
+		val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) &
+		    ~WM8350_BCLK_DIV_MASK;
+		wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div);
+		break;
+	case WM8350_OPCLK_CLKDIV:
+		val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) &
+		    ~WM8350_OPCLK_DIV_MASK;
+		wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div);
+		break;
+	case WM8350_SYS_CLKDIV:
+		val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) &
+		    ~WM8350_MCLK_DIV_MASK;
+		wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div);
+		break;
+	case WM8350_DACLR_CLKDIV:
+		val = wm8350_codec_read(codec, WM8350_DAC_LR_RATE) &
+		    ~WM8350_DACLRC_RATE_MASK;
+		wm8350_codec_write(codec, WM8350_DAC_LR_RATE, val | div);
+		break;
+	case WM8350_ADCLR_CLKDIV:
+		val = wm8350_codec_read(codec, WM8350_ADC_LR_RATE) &
+		    ~WM8350_ADCLRC_RATE_MASK;
+		wm8350_codec_write(codec, WM8350_ADC_LR_RATE, val | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wm8350_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) &
+	    ~(WM8350_AIF_BCLK_INV | WM8350_AIF_LRCLK_INV | WM8350_AIF_FMT_MASK);
+	u16 master = wm8350_codec_read(codec, WM8350_AI_DAC_CONTROL) &
+	    ~WM8350_BCLK_MSTR;
+	u16 dac_lrc = wm8350_codec_read(codec, WM8350_DAC_LR_RATE) &
+	    ~WM8350_DACLRC_ENA;
+	u16 adc_lrc = wm8350_codec_read(codec, WM8350_ADC_LR_RATE) &
+	    ~WM8350_ADCLRC_ENA;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		master |= WM8350_BCLK_MSTR;
+		dac_lrc |= WM8350_DACLRC_ENA;
+		adc_lrc |= WM8350_ADCLRC_ENA;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x2 << 8;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x1 << 8;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x3 << 8;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface |= 0x3 << 8 | WM8350_AIF_LRCLK_INV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= WM8350_AIF_LRCLK_INV | WM8350_AIF_BCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= WM8350_AIF_BCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= WM8350_AIF_LRCLK_INV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	wm8350_codec_write(codec, WM8350_AI_FORMATING, iface);
+	wm8350_codec_write(codec, WM8350_AI_DAC_CONTROL, master);
+	wm8350_codec_write(codec, WM8350_DAC_LR_RATE, dac_lrc);
+	wm8350_codec_write(codec, WM8350_ADC_LR_RATE, adc_lrc);
+	return 0;
+}
+
+static int wm8350_pcm_trigger(struct snd_pcm_substream *substream,
+			      int cmd, struct snd_soc_dai *codec_dai)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	int master = wm8350_codec_cache_read(codec, WM8350_AI_DAC_CONTROL) &
+	    WM8350_BCLK_MSTR;
+	int enabled = 0;
+
+	/* Check that the DACs or ADCs are enabled since they are
+	 * required for LRC in master mode. The DACs or ADCs need a
+	 * valid audio path i.e. pin -> ADC or DAC -> pin before
+	 * the LRC will be enabled in master mode. */
+	if (!master || cmd != SNDRV_PCM_TRIGGER_START)
+		return 0;
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		enabled = wm8350_codec_cache_read(codec, WM8350_POWER_MGMT_4) &
+		    (WM8350_ADCR_ENA | WM8350_ADCL_ENA);
+	} else {
+		enabled = wm8350_codec_cache_read(codec, WM8350_POWER_MGMT_4) &
+		    (WM8350_DACR_ENA | WM8350_DACL_ENA);
+	}
+
+	if (!enabled) {
+		dev_err(codec->dev,
+		       "%s: invalid audio path - no clocks available\n",
+		       __func__);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *codec_dai)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8350 *wm8350 = codec->control_data;
+	u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) &
+	    ~WM8350_AIF_WL_MASK;
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x1 << 10;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x2 << 10;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface |= 0x3 << 10;
+		break;
+	}
+
+	wm8350_codec_write(codec, WM8350_AI_FORMATING, iface);
+
+	/* The sloping stopband filter is recommended for use with
+	 * lower sample rates to improve performance.
+	 */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (params_rate(params) < 24000)
+			wm8350_set_bits(wm8350, WM8350_DAC_MUTE_VOLUME,
+					WM8350_DAC_SB_FILT);
+		else
+			wm8350_clear_bits(wm8350, WM8350_DAC_MUTE_VOLUME,
+					  WM8350_DAC_SB_FILT);
+	}
+
+	return 0;
+}
+
+static int wm8350_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8350 *wm8350 = codec->control_data;
+
+	if (mute)
+		wm8350_set_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA);
+	else
+		wm8350_clear_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA);
+	return 0;
+}
+
+/* FLL divisors */
+struct _fll_div {
+	int div;		/* FLL_OUTDIV */
+	int n;
+	int k;
+	int ratio;		/* FLL_FRATIO */
+};
+
+/* The size in bits of the fll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static inline int fll_factors(struct _fll_div *fll_div, unsigned int input,
+			      unsigned int output)
+{
+	u64 Kpart;
+	unsigned int t1, t2, K, Nmod;
+
+	if (output >= 2815250 && output <= 3125000)
+		fll_div->div = 0x4;
+	else if (output >= 5625000 && output <= 6250000)
+		fll_div->div = 0x3;
+	else if (output >= 11250000 && output <= 12500000)
+		fll_div->div = 0x2;
+	else if (output >= 22500000 && output <= 25000000)
+		fll_div->div = 0x1;
+	else {
+		printk(KERN_ERR "wm8350: fll freq %d out of range\n", output);
+		return -EINVAL;
+	}
+
+	if (input > 48000)
+		fll_div->ratio = 1;
+	else
+		fll_div->ratio = 8;
+
+	t1 = output * (1 << (fll_div->div + 1));
+	t2 = input * fll_div->ratio;
+
+	fll_div->n = t1 / t2;
+	Nmod = t1 % t2;
+
+	if (Nmod) {
+		Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+		do_div(Kpart, t2);
+		K = Kpart & 0xFFFFFFFF;
+
+		/* Check if we need to round */
+		if ((K % 10) >= 5)
+			K += 5;
+
+		/* Move down to proper range now rounding is done */
+		K /= 10;
+		fll_div->k = K;
+	} else
+		fll_div->k = 0;
+
+	return 0;
+}
+
+static int wm8350_set_fll(struct snd_soc_dai *codec_dai,
+			  int pll_id, int source, unsigned int freq_in,
+			  unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8350 *wm8350 = codec->control_data;
+	struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec);
+	struct _fll_div fll_div;
+	int ret = 0;
+	u16 fll_1, fll_4;
+
+	if (freq_in == priv->fll_freq_in && freq_out == priv->fll_freq_out)
+		return 0;
+
+	/* power down FLL - we need to do this for reconfiguration */
+	wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4,
+			  WM8350_FLL_ENA | WM8350_FLL_OSC_ENA);
+
+	if (freq_out == 0 || freq_in == 0)
+		return ret;
+
+	ret = fll_factors(&fll_div, freq_in, freq_out);
+	if (ret < 0)
+		return ret;
+	dev_dbg(wm8350->dev,
+		"FLL in %u FLL out %u N 0x%x K 0x%x div %d ratio %d",
+		freq_in, freq_out, fll_div.n, fll_div.k, fll_div.div,
+		fll_div.ratio);
+
+	/* set up N.K & dividers */
+	fll_1 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_1) &
+	    ~(WM8350_FLL_OUTDIV_MASK | WM8350_FLL_RSP_RATE_MASK | 0xc000);
+	wm8350_codec_write(codec, WM8350_FLL_CONTROL_1,
+			   fll_1 | (fll_div.div << 8) | 0x50);
+	wm8350_codec_write(codec, WM8350_FLL_CONTROL_2,
+			   (fll_div.ratio << 11) | (fll_div.
+						    n & WM8350_FLL_N_MASK));
+	wm8350_codec_write(codec, WM8350_FLL_CONTROL_3, fll_div.k);
+	fll_4 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_4) &
+	    ~(WM8350_FLL_FRAC | WM8350_FLL_SLOW_LOCK_REF);
+	wm8350_codec_write(codec, WM8350_FLL_CONTROL_4,
+			   fll_4 | (fll_div.k ? WM8350_FLL_FRAC : 0) |
+			   (fll_div.ratio == 8 ? WM8350_FLL_SLOW_LOCK_REF : 0));
+
+	/* power FLL on */
+	wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_OSC_ENA);
+	wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_ENA);
+
+	priv->fll_freq_out = freq_out;
+	priv->fll_freq_in = freq_in;
+
+	return 0;
+}
+
+static int wm8350_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	struct wm8350 *wm8350 = codec->control_data;
+	struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec);
+	struct wm8350_audio_platform_data *platform =
+		wm8350->codec.platform_data;
+	u16 pm1;
+	int ret;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+		    ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK);
+		wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+				 pm1 | WM8350_VMID_50K |
+				 platform->codec_current_on << 14);
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1);
+		pm1 &= ~WM8350_VMID_MASK;
+		wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+				 pm1 | WM8350_VMID_50K);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies),
+						    priv->supplies);
+			if (ret != 0)
+				return ret;
+
+			/* Enable the system clock */
+			wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4,
+					WM8350_SYSCLK_ENA);
+
+			/* mute DAC & outputs */
+			wm8350_set_bits(wm8350, WM8350_DAC_MUTE,
+					WM8350_DAC_MUTE_ENA);
+
+			/* discharge cap memory */
+			wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+					 platform->dis_out1 |
+					 (platform->dis_out2 << 2) |
+					 (platform->dis_out3 << 4) |
+					 (platform->dis_out4 << 6));
+
+			/* wait for discharge */
+			schedule_timeout_interruptible(msecs_to_jiffies
+						       (platform->
+							cap_discharge_msecs));
+
+			/* enable antipop */
+			wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+					 (platform->vmid_s_curve << 8));
+
+			/* ramp up vmid */
+			wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+					 (platform->
+					  codec_current_charge << 14) |
+					 WM8350_VMID_5K | WM8350_VMIDEN |
+					 WM8350_VBUFEN);
+
+			/* wait for vmid */
+			schedule_timeout_interruptible(msecs_to_jiffies
+						       (platform->
+							vmid_charge_msecs));
+
+			/* turn on vmid 300k  */
+			pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+			    ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK);
+			pm1 |= WM8350_VMID_300K |
+				(platform->codec_current_standby << 14);
+			wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+					 pm1);
+
+
+			/* enable analogue bias */
+			pm1 |= WM8350_BIASEN;
+			wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1);
+
+			/* disable antipop */
+			wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, 0);
+
+		} else {
+			/* turn on vmid 300k and reduce current */
+			pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+			    ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK);
+			wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+					 pm1 | WM8350_VMID_300K |
+					 (platform->
+					  codec_current_standby << 14));
+
+		}
+		break;
+
+	case SND_SOC_BIAS_OFF:
+
+		/* mute DAC & enable outputs */
+		wm8350_set_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA);
+
+		wm8350_set_bits(wm8350, WM8350_POWER_MGMT_3,
+				WM8350_OUT1L_ENA | WM8350_OUT1R_ENA |
+				WM8350_OUT2L_ENA | WM8350_OUT2R_ENA);
+
+		/* enable anti pop S curve */
+		wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+				 (platform->vmid_s_curve << 8));
+
+		/* turn off vmid  */
+		pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+		    ~WM8350_VMIDEN;
+		wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1);
+
+		/* wait */
+		schedule_timeout_interruptible(msecs_to_jiffies
+					       (platform->
+						vmid_discharge_msecs));
+
+		wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+				 (platform->vmid_s_curve << 8) |
+				 platform->dis_out1 |
+				 (platform->dis_out2 << 2) |
+				 (platform->dis_out3 << 4) |
+				 (platform->dis_out4 << 6));
+
+		/* turn off VBuf and drain */
+		pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+		    ~(WM8350_VBUFEN | WM8350_VMID_MASK);
+		wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+				 pm1 | WM8350_OUTPUT_DRAIN_EN);
+
+		/* wait */
+		schedule_timeout_interruptible(msecs_to_jiffies
+					       (platform->drain_msecs));
+
+		pm1 &= ~WM8350_BIASEN;
+		wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1);
+
+		/* disable anti-pop */
+		wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, 0);
+
+		wm8350_clear_bits(wm8350, WM8350_LOUT1_VOLUME,
+				  WM8350_OUT1L_ENA);
+		wm8350_clear_bits(wm8350, WM8350_ROUT1_VOLUME,
+				  WM8350_OUT1R_ENA);
+		wm8350_clear_bits(wm8350, WM8350_LOUT2_VOLUME,
+				  WM8350_OUT2L_ENA);
+		wm8350_clear_bits(wm8350, WM8350_ROUT2_VOLUME,
+				  WM8350_OUT2R_ENA);
+
+		/* disable clock gen */
+		wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4,
+				  WM8350_SYSCLK_ENA);
+
+		regulator_bulk_disable(ARRAY_SIZE(priv->supplies),
+				       priv->supplies);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+static int wm8350_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8350_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8350_resume(struct snd_soc_codec *codec)
+{
+	wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static irqreturn_t wm8350_hp_jack_handler(int irq, void *data)
+{
+	struct wm8350_data *priv = data;
+	struct wm8350 *wm8350 = priv->codec.control_data;
+	u16 reg;
+	int report;
+	int mask;
+	struct wm8350_jack_data *jack = NULL;
+
+	switch (irq - wm8350->irq_base) {
+	case WM8350_IRQ_CODEC_JCK_DET_L:
+		jack = &priv->hpl;
+		mask = WM8350_JACK_L_LVL;
+		break;
+
+	case WM8350_IRQ_CODEC_JCK_DET_R:
+		jack = &priv->hpr;
+		mask = WM8350_JACK_R_LVL;
+		break;
+
+	default:
+		BUG();
+	}
+
+	if (!jack->jack) {
+		dev_warn(wm8350->dev, "Jack interrupt called with no jack\n");
+		return IRQ_NONE;
+	}
+
+	/* Debounce */
+	msleep(200);
+
+	reg = wm8350_reg_read(wm8350, WM8350_JACK_PIN_STATUS);
+	if (reg & mask)
+		report = jack->report;
+	else
+		report = 0;
+
+	snd_soc_jack_report(jack->jack, report, jack->report);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * wm8350_hp_jack_detect - Enable headphone jack detection.
+ *
+ * @codec:  WM8350 codec
+ * @which:  left or right jack detect signal
+ * @jack:   jack to report detection events on
+ * @report: value to report
+ *
+ * Enables the headphone jack detection of the WM8350.  If no report
+ * is specified then detection is disabled.
+ */
+int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which,
+			  struct snd_soc_jack *jack, int report)
+{
+	struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec);
+	struct wm8350 *wm8350 = codec->control_data;
+	int irq;
+	int ena;
+
+	switch (which) {
+	case WM8350_JDL:
+		priv->hpl.jack = jack;
+		priv->hpl.report = report;
+		irq = WM8350_IRQ_CODEC_JCK_DET_L;
+		ena = WM8350_JDL_ENA;
+		break;
+
+	case WM8350_JDR:
+		priv->hpr.jack = jack;
+		priv->hpr.report = report;
+		irq = WM8350_IRQ_CODEC_JCK_DET_R;
+		ena = WM8350_JDR_ENA;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (report) {
+		wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA);
+		wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena);
+	} else {
+		wm8350_clear_bits(wm8350, WM8350_JACK_DETECT, ena);
+	}
+
+	/* Sync status */
+	wm8350_hp_jack_handler(irq + wm8350->irq_base, priv);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_hp_jack_detect);
+
+static irqreturn_t wm8350_mic_handler(int irq, void *data)
+{
+	struct wm8350_data *priv = data;
+	struct wm8350 *wm8350 = priv->codec.control_data;
+	u16 reg;
+	int report = 0;
+
+	reg = wm8350_reg_read(wm8350, WM8350_JACK_PIN_STATUS);
+	if (reg & WM8350_JACK_MICSCD_LVL)
+		report |= priv->mic.short_report;
+	if (reg & WM8350_JACK_MICSD_LVL)
+		report |= priv->mic.report;
+
+	snd_soc_jack_report(priv->mic.jack, report,
+			    priv->mic.report | priv->mic.short_report);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * wm8350_mic_jack_detect - Enable microphone jack detection.
+ *
+ * @codec:         WM8350 codec
+ * @jack:          jack to report detection events on
+ * @detect_report: value to report when presence detected
+ * @short_report:  value to report when microphone short detected
+ *
+ * Enables the microphone jack detection of the WM8350.  If both reports
+ * are specified as zero then detection is disabled.
+ */
+int wm8350_mic_jack_detect(struct snd_soc_codec *codec,
+			   struct snd_soc_jack *jack,
+			   int detect_report, int short_report)
+{
+	struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec);
+	struct wm8350 *wm8350 = codec->control_data;
+
+	priv->mic.jack = jack;
+	priv->mic.report = detect_report;
+	priv->mic.short_report = short_report;
+
+	if (detect_report || short_report) {
+		wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA);
+		wm8350_set_bits(wm8350, WM8350_POWER_MGMT_1,
+				WM8350_MIC_DET_ENA);
+	} else {
+		wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_1,
+				  WM8350_MIC_DET_ENA);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_mic_jack_detect);
+
+#define WM8350_RATES (SNDRV_PCM_RATE_8000_96000)
+
+#define WM8350_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+			SNDRV_PCM_FMTBIT_S20_3LE |\
+			SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8350_dai_ops = {
+	 .hw_params	= wm8350_pcm_hw_params,
+	 .digital_mute	= wm8350_mute,
+	 .trigger	= wm8350_pcm_trigger,
+	 .set_fmt	= wm8350_set_dai_fmt,
+	 .set_sysclk	= wm8350_set_dai_sysclk,
+	 .set_pll	= wm8350_set_fll,
+	 .set_clkdiv	= wm8350_set_clkdiv,
+};
+
+static struct snd_soc_dai_driver wm8350_dai = {
+	.name = "wm8350-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8350_RATES,
+		.formats = WM8350_FORMATS,
+	},
+	.capture = {
+		 .stream_name = "Capture",
+		 .channels_min = 1,
+		 .channels_max = 2,
+		 .rates = WM8350_RATES,
+		 .formats = WM8350_FORMATS,
+	 },
+	.ops = &wm8350_dai_ops,
+};
+
+static  int wm8350_codec_probe(struct snd_soc_codec *codec)
+{
+	struct wm8350 *wm8350 = dev_get_platdata(codec->dev);
+	struct wm8350_data *priv;
+	struct wm8350_output *out1;
+	struct wm8350_output *out2;
+	int ret, i;
+
+	if (wm8350->codec.platform_data == NULL) {
+		dev_err(codec->dev, "No audio platform data supplied\n");
+		return -EINVAL;
+	}
+
+	priv = kzalloc(sizeof(struct wm8350_data), GFP_KERNEL);
+	if (priv == NULL)
+		return -ENOMEM;
+	snd_soc_codec_set_drvdata(codec, priv);
+
+	for (i = 0; i < ARRAY_SIZE(supply_names); i++)
+		priv->supplies[i].supply = supply_names[i];
+
+	ret = regulator_bulk_get(wm8350->dev, ARRAY_SIZE(priv->supplies),
+				 priv->supplies);
+	if (ret != 0)
+		goto err_priv;
+
+	wm8350->codec.codec = codec;
+	codec->control_data = wm8350;
+
+	/* Put the codec into reset if it wasn't already */
+	wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
+
+	INIT_DELAYED_WORK(&codec->delayed_work, wm8350_pga_work);
+
+	/* Enable the codec */
+	wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
+
+	/* Enable robust clocking mode in ADC */
+	wm8350_codec_write(codec, WM8350_SECURITY, 0xa7);
+	wm8350_codec_write(codec, 0xde, 0x13);
+	wm8350_codec_write(codec, WM8350_SECURITY, 0);
+
+	/* read OUT1 & OUT2 volumes */
+	out1 = &priv->out1;
+	out2 = &priv->out2;
+	out1->left_vol = (wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME) &
+			  WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+	out1->right_vol = (wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME) &
+			   WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+	out2->left_vol = (wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME) &
+			  WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+	out2->right_vol = (wm8350_reg_read(wm8350, WM8350_ROUT2_VOLUME) &
+			   WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+	wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, 0);
+	wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, 0);
+	wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, 0);
+	wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, 0);
+
+	/* Latch VU bits & mute */
+	wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME,
+			WM8350_OUT1_VU | WM8350_OUT1L_MUTE);
+	wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME,
+			WM8350_OUT2_VU | WM8350_OUT2L_MUTE);
+	wm8350_set_bits(wm8350, WM8350_ROUT1_VOLUME,
+			WM8350_OUT1_VU | WM8350_OUT1R_MUTE);
+	wm8350_set_bits(wm8350, WM8350_ROUT2_VOLUME,
+			WM8350_OUT2_VU | WM8350_OUT2R_MUTE);
+
+	/* Make sure AIF tristating is disabled by default */
+	wm8350_clear_bits(wm8350, WM8350_AI_FORMATING, WM8350_AIF_TRI);
+
+	/* Make sure we've got a sane companding setup too */
+	wm8350_clear_bits(wm8350, WM8350_ADC_DAC_COMP,
+			  WM8350_DAC_COMP | WM8350_LOOPBACK);
+
+	/* Make sure jack detect is disabled to start off with */
+	wm8350_clear_bits(wm8350, WM8350_JACK_DETECT,
+			  WM8350_JDL_ENA | WM8350_JDR_ENA);
+
+	wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L,
+			    wm8350_hp_jack_handler, 0, "Left jack detect",
+			    priv);
+	wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R,
+			    wm8350_hp_jack_handler, 0, "Right jack detect",
+			    priv);
+	wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICSCD,
+			    wm8350_mic_handler, 0, "Microphone short", priv);
+	wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICD,
+			    wm8350_mic_handler, 0, "Microphone detect", priv);
+
+
+	snd_soc_add_controls(codec, wm8350_snd_controls,
+				ARRAY_SIZE(wm8350_snd_controls));
+	wm8350_add_widgets(codec);
+
+	wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+
+err_priv:
+	kfree(priv);
+	return ret;
+}
+
+static int  wm8350_codec_remove(struct snd_soc_codec *codec)
+{
+	struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec);
+	struct wm8350 *wm8350 = dev_get_platdata(codec->dev);
+	int ret;
+
+	wm8350_clear_bits(wm8350, WM8350_JACK_DETECT,
+			  WM8350_JDL_ENA | WM8350_JDR_ENA);
+	wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA);
+
+	wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_MICD, priv);
+	wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_MICSCD, priv);
+	wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L, priv);
+	wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, priv);
+
+	priv->hpl.jack = NULL;
+	priv->hpr.jack = NULL;
+	priv->mic.jack = NULL;
+
+	/* cancel any work waiting to be queued. */
+	ret = cancel_delayed_work(&codec->delayed_work);
+
+	/* if there was any work waiting then we run it now and
+	 * wait for its completion */
+	if (ret) {
+		schedule_delayed_work(&codec->delayed_work, 0);
+		flush_scheduled_work();
+	}
+
+	wm8350_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
+
+	regulator_bulk_free(ARRAY_SIZE(priv->supplies), priv->supplies);
+	kfree(priv);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8350 = {
+	.probe =	wm8350_codec_probe,
+	.remove =	wm8350_codec_remove,
+	.suspend = 	wm8350_suspend,
+	.resume =	wm8350_resume,
+	.read = wm8350_codec_read,
+	.write = wm8350_codec_write,
+	.set_bias_level = wm8350_set_bias_level,
+};
+
+static int __devinit wm8350_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8350,
+			&wm8350_dai, 1);
+}
+
+static int __devexit wm8350_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver wm8350_codec_driver = {
+	.driver = {
+		   .name = "wm8350-codec",
+		   .owner = THIS_MODULE,
+		   },
+	.probe = wm8350_probe,
+	.remove = __devexit_p(wm8350_remove),
+};
+
+static __init int wm8350_init(void)
+{
+	return platform_driver_register(&wm8350_codec_driver);
+}
+module_init(wm8350_init);
+
+static __exit void wm8350_exit(void)
+{
+	platform_driver_unregister(&wm8350_codec_driver);
+}
+module_exit(wm8350_exit);
+
+MODULE_DESCRIPTION("ASoC WM8350 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm8350-codec");
diff --git a/linux/sound/soc/codecs/wm8350.h b/linux/sound/soc/codecs/wm8350.h
new file mode 100644
index 0000000..74108eb
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8350.h
@@ -0,0 +1,29 @@
+/*
+ * wm8350.h - WM8903 audio codec interface
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ *
+ *  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.
+ */
+
+#ifndef _WM8350_H
+#define _WM8350_H
+
+#include <sound/soc.h>
+#include <linux/mfd/wm8350/audio.h>
+
+enum wm8350_jack {
+	WM8350_JDL = 1,
+	WM8350_JDR = 2,
+};
+
+int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which,
+			  struct snd_soc_jack *jack, int report);
+int wm8350_mic_jack_detect(struct snd_soc_codec *codec,
+			   struct snd_soc_jack *jack,
+			   int detect_report, int short_report);
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8400.c b/linux/sound/soc/codecs/wm8400.c
new file mode 100644
index 0000000..8502997
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8400.c
@@ -0,0 +1,1494 @@
+/*
+ * wm8400.c  --  WM8400 ALSA Soc Audio driver
+ *
+ * Copyright 2008, 2009 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mfd/wm8400-audio.h>
+#include <linux/mfd/wm8400-private.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8400.h"
+
+/* Fake register for internal state */
+#define WM8400_INTDRIVBITS      (WM8400_REGISTER_COUNT + 1)
+#define WM8400_INMIXL_PWR			0
+#define WM8400_AINLMUX_PWR			1
+#define WM8400_INMIXR_PWR			2
+#define WM8400_AINRMUX_PWR			3
+
+static struct regulator_bulk_data power[] = {
+	{
+		.supply = "I2S1VDD",
+	},
+	{
+		.supply = "I2S2VDD",
+	},
+	{
+		.supply = "DCVDD",
+	},
+	{
+		.supply = "AVDD",
+	},
+	{
+		.supply = "FLLVDD",
+	},
+	{
+		.supply = "HPVDD",
+	},
+	{
+		.supply = "SPKVDD",
+	},
+};
+
+/* codec private data */
+struct wm8400_priv {
+	struct snd_soc_codec *codec;
+	struct wm8400 *wm8400;
+	u16 fake_register;
+	unsigned int sysclk;
+	unsigned int pcmclk;
+	struct work_struct work;
+	int fll_in, fll_out;
+};
+
+static inline unsigned int wm8400_read(struct snd_soc_codec *codec,
+				       unsigned int reg)
+{
+	struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+
+	if (reg == WM8400_INTDRIVBITS)
+		return wm8400->fake_register;
+	else
+		return wm8400_reg_read(wm8400->wm8400, reg);
+}
+
+/*
+ * write to the wm8400 register space
+ */
+static int wm8400_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int value)
+{
+	struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+
+	if (reg == WM8400_INTDRIVBITS) {
+		wm8400->fake_register = value;
+		return 0;
+	} else
+		return wm8400_set_bits(wm8400->wm8400, reg, 0xffff, value);
+}
+
+static void wm8400_codec_reset(struct snd_soc_codec *codec)
+{
+	struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+
+	wm8400_reset_codec_reg_cache(wm8400->wm8400);
+}
+
+static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1650, 3000, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_mix_tlv, -2100, 0, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -7300, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_omix_tlv, -600, 0, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_dac_tlv, -7163, 0, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_adc_tlv, -7163, 1763, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_sidetone_tlv, -3600, 0, 0);
+
+static int wm8400_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol,
+        struct snd_ctl_elem_value *ucontrol)
+{
+        struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	int reg = mc->reg;
+        int ret;
+        u16 val;
+
+        ret = snd_soc_put_volsw(kcontrol, ucontrol);
+        if (ret < 0)
+                return ret;
+
+        /* now hit the volume update bits (always bit 8) */
+        val = wm8400_read(codec, reg);
+        return wm8400_write(codec, reg, val | 0x0100);
+}
+
+#define WM8400_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert, tlv_array) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+		SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+	.tlv.p = (tlv_array), \
+	.info = snd_soc_info_volsw, \
+	.get = snd_soc_get_volsw, .put = wm8400_outpga_put_volsw_vu, \
+	.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+
+static const char *wm8400_digital_sidetone[] =
+	{"None", "Left ADC", "Right ADC", "Reserved"};
+
+static const struct soc_enum wm8400_left_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8400_DIGITAL_SIDE_TONE,
+		WM8400_ADC_TO_DACL_SHIFT, 2, wm8400_digital_sidetone);
+
+static const struct soc_enum wm8400_right_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8400_DIGITAL_SIDE_TONE,
+		WM8400_ADC_TO_DACR_SHIFT, 2, wm8400_digital_sidetone);
+
+static const char *wm8400_adcmode[] =
+	{"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"};
+
+static const struct soc_enum wm8400_right_adcmode_enum =
+SOC_ENUM_SINGLE(WM8400_ADC_CTRL, WM8400_ADC_HPF_CUT_SHIFT, 3, wm8400_adcmode);
+
+static const struct snd_kcontrol_new wm8400_snd_controls[] = {
+/* INMIXL */
+SOC_SINGLE("LIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L12MNBST_SHIFT,
+	   1, 0),
+SOC_SINGLE("LIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L34MNBST_SHIFT,
+	   1, 0),
+/* INMIXR */
+SOC_SINGLE("RIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R12MNBST_SHIFT,
+	   1, 0),
+SOC_SINGLE("RIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R34MNBST_SHIFT,
+	   1, 0),
+
+/* LOMIX */
+SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER3,
+	WM8400_LLI3LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3,
+	WM8400_LR12LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3,
+	WM8400_LL12LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER5,
+	WM8400_LRI3LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER5,
+	WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER5,
+	WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv),
+
+/* ROMIX */
+SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER4,
+	WM8400_RRI3ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4,
+	WM8400_RL12ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4,
+	WM8400_RR12ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER6,
+	WM8400_RLI3ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER6,
+	WM8400_RLBROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER6,
+	WM8400_RRBROVOL_SHIFT, 7, 0, out_mix_tlv),
+
+/* LOUT */
+WM8400_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8400_LEFT_OUTPUT_VOLUME,
+	WM8400_LOUTVOL_SHIFT, WM8400_LOUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOUT ZC", WM8400_LEFT_OUTPUT_VOLUME, WM8400_LOZC_SHIFT, 1, 0),
+
+/* ROUT */
+WM8400_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8400_RIGHT_OUTPUT_VOLUME,
+	WM8400_ROUTVOL_SHIFT, WM8400_ROUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROUT ZC", WM8400_RIGHT_OUTPUT_VOLUME, WM8400_ROZC_SHIFT, 1, 0),
+
+/* LOPGA */
+WM8400_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8400_LEFT_OPGA_VOLUME,
+	WM8400_LOPGAVOL_SHIFT, WM8400_LOPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOPGA ZC Switch", WM8400_LEFT_OPGA_VOLUME,
+	WM8400_LOPGAZC_SHIFT, 1, 0),
+
+/* ROPGA */
+WM8400_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8400_RIGHT_OPGA_VOLUME,
+	WM8400_ROPGAVOL_SHIFT, WM8400_ROPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROPGA ZC Switch", WM8400_RIGHT_OPGA_VOLUME,
+	WM8400_ROPGAZC_SHIFT, 1, 0),
+
+SOC_SINGLE("LON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+	WM8400_LONMUTE_SHIFT, 1, 0),
+SOC_SINGLE("LOP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+	WM8400_LOPMUTE_SHIFT, 1, 0),
+SOC_SINGLE("LOP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME,
+	WM8400_LOATTN_SHIFT, 1, 0),
+SOC_SINGLE("RON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+	WM8400_RONMUTE_SHIFT, 1, 0),
+SOC_SINGLE("ROP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+	WM8400_ROPMUTE_SHIFT, 1, 0),
+SOC_SINGLE("ROP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME,
+	WM8400_ROATTN_SHIFT, 1, 0),
+
+SOC_SINGLE("OUT3 Mute Switch", WM8400_OUT3_4_VOLUME,
+	WM8400_OUT3MUTE_SHIFT, 1, 0),
+SOC_SINGLE("OUT3 Attenuation Switch", WM8400_OUT3_4_VOLUME,
+	WM8400_OUT3ATTN_SHIFT, 1, 0),
+
+SOC_SINGLE("OUT4 Mute Switch", WM8400_OUT3_4_VOLUME,
+	WM8400_OUT4MUTE_SHIFT, 1, 0),
+SOC_SINGLE("OUT4 Attenuation Switch", WM8400_OUT3_4_VOLUME,
+	WM8400_OUT4ATTN_SHIFT, 1, 0),
+
+SOC_SINGLE("Speaker Mode Switch", WM8400_CLASSD1,
+	WM8400_CDMODE_SHIFT, 1, 0),
+
+SOC_SINGLE("Speaker Output Attenuation Volume", WM8400_SPEAKER_VOLUME,
+	WM8400_SPKATTN_SHIFT, WM8400_SPKATTN_MASK, 0),
+SOC_SINGLE("Speaker DC Boost Volume", WM8400_CLASSD3,
+	WM8400_DCGAIN_SHIFT, 6, 0),
+SOC_SINGLE("Speaker AC Boost Volume", WM8400_CLASSD3,
+	WM8400_ACGAIN_SHIFT, 6, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume",
+	WM8400_LEFT_DAC_DIGITAL_VOLUME, WM8400_DACL_VOL_SHIFT,
+	127, 0, out_dac_tlv),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume",
+	WM8400_RIGHT_DAC_DIGITAL_VOLUME, WM8400_DACR_VOL_SHIFT,
+	127, 0, out_dac_tlv),
+
+SOC_ENUM("Left Digital Sidetone", wm8400_left_digital_sidetone_enum),
+SOC_ENUM("Right Digital Sidetone", wm8400_right_digital_sidetone_enum),
+
+SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE,
+	WM8400_ADCL_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv),
+SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE,
+	WM8400_ADCR_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv),
+
+SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8400_ADC_CTRL,
+	WM8400_ADC_HPF_ENA_SHIFT, 1, 0),
+
+SOC_ENUM("ADC HPF Mode", wm8400_right_adcmode_enum),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume",
+	WM8400_LEFT_ADC_DIGITAL_VOLUME,
+	WM8400_ADCL_VOL_SHIFT,
+	WM8400_ADCL_VOL_MASK,
+	0,
+	in_adc_tlv),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume",
+	WM8400_RIGHT_ADC_DIGITAL_VOLUME,
+	WM8400_ADCR_VOL_SHIFT,
+	WM8400_ADCR_VOL_MASK,
+	0,
+	in_adc_tlv),
+
+WM8400_OUTPGA_SINGLE_R_TLV("LIN12 Volume",
+	WM8400_LEFT_LINE_INPUT_1_2_VOLUME,
+	WM8400_LIN12VOL_SHIFT,
+	WM8400_LIN12VOL_MASK,
+	0,
+	in_pga_tlv),
+
+SOC_SINGLE("LIN12 ZC Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME,
+	WM8400_LI12ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("LIN12 Mute Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME,
+	WM8400_LI12MUTE_SHIFT, 1, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("LIN34 Volume",
+	WM8400_LEFT_LINE_INPUT_3_4_VOLUME,
+	WM8400_LIN34VOL_SHIFT,
+	WM8400_LIN34VOL_MASK,
+	0,
+	in_pga_tlv),
+
+SOC_SINGLE("LIN34 ZC Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME,
+	WM8400_LI34ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("LIN34 Mute Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME,
+	WM8400_LI34MUTE_SHIFT, 1, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("RIN12 Volume",
+	WM8400_RIGHT_LINE_INPUT_1_2_VOLUME,
+	WM8400_RIN12VOL_SHIFT,
+	WM8400_RIN12VOL_MASK,
+	0,
+	in_pga_tlv),
+
+SOC_SINGLE("RIN12 ZC Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME,
+	WM8400_RI12ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("RIN12 Mute Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME,
+	WM8400_RI12MUTE_SHIFT, 1, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("RIN34 Volume",
+	WM8400_RIGHT_LINE_INPUT_3_4_VOLUME,
+	WM8400_RIN34VOL_SHIFT,
+	WM8400_RIN34VOL_MASK,
+	0,
+	in_pga_tlv),
+
+SOC_SINGLE("RIN34 ZC Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME,
+	WM8400_RI34ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("RIN34 Mute Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME,
+	WM8400_RI34MUTE_SHIFT, 1, 0),
+
+};
+
+/* add non dapm controls */
+static int wm8400_add_controls(struct snd_soc_codec *codec)
+{
+	return snd_soc_add_controls(codec, wm8400_snd_controls,
+				ARRAY_SIZE(wm8400_snd_controls));
+}
+
+/*
+ * _DAPM_ Controls
+ */
+
+static int inmixer_event (struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event)
+{
+	u16 reg, fakepower;
+
+	reg = wm8400_read(w->codec, WM8400_POWER_MANAGEMENT_2);
+	fakepower = wm8400_read(w->codec, WM8400_INTDRIVBITS);
+
+	if (fakepower & ((1 << WM8400_INMIXL_PWR) |
+		(1 << WM8400_AINLMUX_PWR))) {
+		reg |= WM8400_AINL_ENA;
+	} else {
+		reg &= ~WM8400_AINL_ENA;
+	}
+
+	if (fakepower & ((1 << WM8400_INMIXR_PWR) |
+		(1 << WM8400_AINRMUX_PWR))) {
+		reg |= WM8400_AINR_ENA;
+	} else {
+		reg &= ~WM8400_AINL_ENA;
+	}
+	wm8400_write(w->codec, WM8400_POWER_MANAGEMENT_2, reg);
+
+	return 0;
+}
+
+static int outmixer_event (struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol * kcontrol, int event)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	u32 reg_shift = mc->shift;
+	int ret = 0;
+	u16 reg;
+
+	switch (reg_shift) {
+	case WM8400_SPEAKER_MIXER | (WM8400_LDSPK << 8) :
+		reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER1);
+		if (reg & WM8400_LDLO) {
+			printk(KERN_WARNING
+			"Cannot set as Output Mixer 1 LDLO Set\n");
+			ret = -1;
+		}
+		break;
+	case WM8400_SPEAKER_MIXER | (WM8400_RDSPK << 8):
+		reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER2);
+		if (reg & WM8400_RDRO) {
+			printk(KERN_WARNING
+			"Cannot set as Output Mixer 2 RDRO Set\n");
+			ret = -1;
+		}
+		break;
+	case WM8400_OUTPUT_MIXER1 | (WM8400_LDLO << 8):
+		reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER);
+		if (reg & WM8400_LDSPK) {
+			printk(KERN_WARNING
+			"Cannot set as Speaker Mixer LDSPK Set\n");
+			ret = -1;
+		}
+		break;
+	case WM8400_OUTPUT_MIXER2 | (WM8400_RDRO << 8):
+		reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER);
+		if (reg & WM8400_RDSPK) {
+			printk(KERN_WARNING
+			"Cannot set as Speaker Mixer RDSPK Set\n");
+			ret = -1;
+		}
+		break;
+	}
+
+	return ret;
+}
+
+/* INMIX dB values */
+static const unsigned int in_mix_tlv[] = {
+	TLV_DB_RANGE_HEAD(1),
+	0,7, TLV_DB_SCALE_ITEM(-1200, 600, 0),
+};
+
+/* Left In PGA Connections */
+static const struct snd_kcontrol_new wm8400_dapm_lin12_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN1 Switch", WM8400_INPUT_MIXER2, WM8400_LMN1_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LIN2 Switch", WM8400_INPUT_MIXER2, WM8400_LMP2_SHIFT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8400_dapm_lin34_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN3 Switch", WM8400_INPUT_MIXER2, WM8400_LMN3_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LIN4 Switch", WM8400_INPUT_MIXER2, WM8400_LMP4_SHIFT, 1, 0),
+};
+
+/* Right In PGA Connections */
+static const struct snd_kcontrol_new wm8400_dapm_rin12_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN1 Switch", WM8400_INPUT_MIXER2, WM8400_RMN1_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RIN2 Switch", WM8400_INPUT_MIXER2, WM8400_RMP2_SHIFT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8400_dapm_rin34_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN3 Switch", WM8400_INPUT_MIXER2, WM8400_RMN3_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RIN4 Switch", WM8400_INPUT_MIXER2, WM8400_RMP4_SHIFT, 1, 0),
+};
+
+/* INMIXL */
+static const struct snd_kcontrol_new wm8400_dapm_inmixl_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8400_INPUT_MIXER3,
+	WM8400_LDBVOL_SHIFT, WM8400_LDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8400_INPUT_MIXER5, WM8400_LI2BVOL_SHIFT,
+	7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("LINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT,
+		1, 0),
+SOC_DAPM_SINGLE("LINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT,
+		1, 0),
+};
+
+/* INMIXR */
+static const struct snd_kcontrol_new wm8400_dapm_inmixr_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8400_INPUT_MIXER4,
+	WM8400_RDBVOL_SHIFT, WM8400_RDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8400_INPUT_MIXER6, WM8400_RI2BVOL_SHIFT,
+	7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("RINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT,
+	1, 0),
+SOC_DAPM_SINGLE("RINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT,
+	1, 0),
+};
+
+/* AINLMUX */
+static const char *wm8400_ainlmux[] =
+	{"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"};
+
+static const struct soc_enum wm8400_ainlmux_enum =
+SOC_ENUM_SINGLE( WM8400_INPUT_MIXER1, WM8400_AINLMODE_SHIFT,
+	ARRAY_SIZE(wm8400_ainlmux), wm8400_ainlmux);
+
+static const struct snd_kcontrol_new wm8400_dapm_ainlmux_controls =
+SOC_DAPM_ENUM("Route", wm8400_ainlmux_enum);
+
+/* DIFFINL */
+
+/* AINRMUX */
+static const char *wm8400_ainrmux[] =
+	{"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"};
+
+static const struct soc_enum wm8400_ainrmux_enum =
+SOC_ENUM_SINGLE( WM8400_INPUT_MIXER1, WM8400_AINRMODE_SHIFT,
+	ARRAY_SIZE(wm8400_ainrmux), wm8400_ainrmux);
+
+static const struct snd_kcontrol_new wm8400_dapm_ainrmux_controls =
+SOC_DAPM_ENUM("Route", wm8400_ainrmux_enum);
+
+/* RXVOICE */
+static const struct snd_kcontrol_new wm8400_dapm_rxvoice_controls[] = {
+SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8400_INPUT_MIXER5, WM8400_LR4BVOL_SHIFT,
+			WM8400_LR4BVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8400_INPUT_MIXER6, WM8400_RL4BVOL_SHIFT,
+			WM8400_RL4BVOL_MASK, 0, in_mix_tlv),
+};
+
+/* LOMIX */
+static const struct snd_kcontrol_new wm8400_dapm_lomix_controls[] = {
+SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER1,
+	WM8400_LRBLO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER1,
+	WM8400_LLBLO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER1,
+	WM8400_LRI3LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER1,
+	WM8400_LLI3LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1,
+	WM8400_LR12LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1,
+	WM8400_LL12LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8400_OUTPUT_MIXER1,
+	WM8400_LDLO_SHIFT, 1, 0),
+};
+
+/* ROMIX */
+static const struct snd_kcontrol_new wm8400_dapm_romix_controls[] = {
+SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER2,
+	WM8400_RLBRO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER2,
+	WM8400_RRBRO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER2,
+	WM8400_RLI3RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER2,
+	WM8400_RRI3RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2,
+	WM8400_RL12RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2,
+	WM8400_RR12RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8400_OUTPUT_MIXER2,
+	WM8400_RDRO_SHIFT, 1, 0),
+};
+
+/* LONMIX */
+static const struct snd_kcontrol_new wm8400_dapm_lonmix_controls[] = {
+SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1,
+	WM8400_LLOPGALON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER1,
+	WM8400_LROPGALON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8400_LINE_MIXER1,
+	WM8400_LOPLON_SHIFT, 1, 0),
+};
+
+/* LOPMIX */
+static const struct snd_kcontrol_new wm8400_dapm_lopmix_controls[] = {
+SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER1,
+	WM8400_LR12LOP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER1,
+	WM8400_LL12LOP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1,
+	WM8400_LLOPGALOP_SHIFT, 1, 0),
+};
+
+/* RONMIX */
+static const struct snd_kcontrol_new wm8400_dapm_ronmix_controls[] = {
+SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2,
+	WM8400_RROPGARON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER2,
+	WM8400_RLOPGARON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8400_LINE_MIXER2,
+	WM8400_ROPRON_SHIFT, 1, 0),
+};
+
+/* ROPMIX */
+static const struct snd_kcontrol_new wm8400_dapm_ropmix_controls[] = {
+SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER2,
+	WM8400_RL12ROP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER2,
+	WM8400_RR12ROP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2,
+	WM8400_RROPGAROP_SHIFT, 1, 0),
+};
+
+/* OUT3MIX */
+static const struct snd_kcontrol_new wm8400_dapm_out3mix_controls[] = {
+SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER,
+	WM8400_LI4O3_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8400_OUT3_4_MIXER,
+	WM8400_LPGAO3_SHIFT, 1, 0),
+};
+
+/* OUT4MIX */
+static const struct snd_kcontrol_new wm8400_dapm_out4mix_controls[] = {
+SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8400_OUT3_4_MIXER,
+	WM8400_RPGAO4_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER,
+	WM8400_RI4O4_SHIFT, 1, 0),
+};
+
+/* SPKMIX */
+static const struct snd_kcontrol_new wm8400_dapm_spkmix_controls[] = {
+SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8400_SPEAKER_MIXER,
+	WM8400_LI2SPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8400_SPEAKER_MIXER,
+	WM8400_LB2SPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8400_SPEAKER_MIXER,
+	WM8400_LOPGASPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8400_SPEAKER_MIXER,
+	WM8400_LDSPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8400_SPEAKER_MIXER,
+	WM8400_RDSPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8400_SPEAKER_MIXER,
+	WM8400_ROPGASPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8400_SPEAKER_MIXER,
+	WM8400_RL12ROP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8400_SPEAKER_MIXER,
+	WM8400_RI2SPK_SHIFT, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8400_dapm_widgets[] = {
+/* Input Side */
+/* Input Lines */
+SND_SOC_DAPM_INPUT("LIN1"),
+SND_SOC_DAPM_INPUT("LIN2"),
+SND_SOC_DAPM_INPUT("LIN3"),
+SND_SOC_DAPM_INPUT("LIN4/RXN"),
+SND_SOC_DAPM_INPUT("RIN3"),
+SND_SOC_DAPM_INPUT("RIN4/RXP"),
+SND_SOC_DAPM_INPUT("RIN1"),
+SND_SOC_DAPM_INPUT("RIN2"),
+SND_SOC_DAPM_INPUT("Internal ADC Source"),
+
+/* DACs */
+SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8400_POWER_MANAGEMENT_2,
+	WM8400_ADCL_ENA_SHIFT, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8400_POWER_MANAGEMENT_2,
+	WM8400_ADCR_ENA_SHIFT, 0),
+
+/* Input PGAs */
+SND_SOC_DAPM_MIXER("LIN12 PGA", WM8400_POWER_MANAGEMENT_2,
+		   WM8400_LIN12_ENA_SHIFT,
+		   0, &wm8400_dapm_lin12_pga_controls[0],
+		   ARRAY_SIZE(wm8400_dapm_lin12_pga_controls)),
+SND_SOC_DAPM_MIXER("LIN34 PGA", WM8400_POWER_MANAGEMENT_2,
+		   WM8400_LIN34_ENA_SHIFT,
+		   0, &wm8400_dapm_lin34_pga_controls[0],
+		   ARRAY_SIZE(wm8400_dapm_lin34_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN12 PGA", WM8400_POWER_MANAGEMENT_2,
+		   WM8400_RIN12_ENA_SHIFT,
+		   0, &wm8400_dapm_rin12_pga_controls[0],
+		   ARRAY_SIZE(wm8400_dapm_rin12_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN34 PGA", WM8400_POWER_MANAGEMENT_2,
+		   WM8400_RIN34_ENA_SHIFT,
+		   0, &wm8400_dapm_rin34_pga_controls[0],
+		   ARRAY_SIZE(wm8400_dapm_rin34_pga_controls)),
+
+/* INMIXL */
+SND_SOC_DAPM_MIXER_E("INMIXL", WM8400_INTDRIVBITS, WM8400_INMIXL_PWR, 0,
+	&wm8400_dapm_inmixl_controls[0],
+	ARRAY_SIZE(wm8400_dapm_inmixl_controls),
+	inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINLMUX */
+SND_SOC_DAPM_MUX_E("AILNMUX", WM8400_INTDRIVBITS, WM8400_AINLMUX_PWR, 0,
+	&wm8400_dapm_ainlmux_controls, inmixer_event,
+	SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* INMIXR */
+SND_SOC_DAPM_MIXER_E("INMIXR", WM8400_INTDRIVBITS, WM8400_INMIXR_PWR, 0,
+	&wm8400_dapm_inmixr_controls[0],
+	ARRAY_SIZE(wm8400_dapm_inmixr_controls),
+	inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINRMUX */
+SND_SOC_DAPM_MUX_E("AIRNMUX", WM8400_INTDRIVBITS, WM8400_AINRMUX_PWR, 0,
+	&wm8400_dapm_ainrmux_controls, inmixer_event,
+	SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* Output Side */
+/* DACs */
+SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8400_POWER_MANAGEMENT_3,
+	WM8400_DACL_ENA_SHIFT, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8400_POWER_MANAGEMENT_3,
+	WM8400_DACR_ENA_SHIFT, 0),
+
+/* LOMIX */
+SND_SOC_DAPM_MIXER_E("LOMIX", WM8400_POWER_MANAGEMENT_3,
+		     WM8400_LOMIX_ENA_SHIFT,
+		     0, &wm8400_dapm_lomix_controls[0],
+		     ARRAY_SIZE(wm8400_dapm_lomix_controls),
+		     outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LONMIX */
+SND_SOC_DAPM_MIXER("LONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LON_ENA_SHIFT,
+		   0, &wm8400_dapm_lonmix_controls[0],
+		   ARRAY_SIZE(wm8400_dapm_lonmix_controls)),
+
+/* LOPMIX */
+SND_SOC_DAPM_MIXER("LOPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LOP_ENA_SHIFT,
+		   0, &wm8400_dapm_lopmix_controls[0],
+		   ARRAY_SIZE(wm8400_dapm_lopmix_controls)),
+
+/* OUT3MIX */
+SND_SOC_DAPM_MIXER("OUT3MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT3_ENA_SHIFT,
+		   0, &wm8400_dapm_out3mix_controls[0],
+		   ARRAY_SIZE(wm8400_dapm_out3mix_controls)),
+
+/* SPKMIX */
+SND_SOC_DAPM_MIXER_E("SPKMIX", WM8400_POWER_MANAGEMENT_1, WM8400_SPK_ENA_SHIFT,
+		     0, &wm8400_dapm_spkmix_controls[0],
+		     ARRAY_SIZE(wm8400_dapm_spkmix_controls), outmixer_event,
+		     SND_SOC_DAPM_PRE_REG),
+
+/* OUT4MIX */
+SND_SOC_DAPM_MIXER("OUT4MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT4_ENA_SHIFT,
+	0, &wm8400_dapm_out4mix_controls[0],
+	ARRAY_SIZE(wm8400_dapm_out4mix_controls)),
+
+/* ROPMIX */
+SND_SOC_DAPM_MIXER("ROPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_ROP_ENA_SHIFT,
+		   0, &wm8400_dapm_ropmix_controls[0],
+		   ARRAY_SIZE(wm8400_dapm_ropmix_controls)),
+
+/* RONMIX */
+SND_SOC_DAPM_MIXER("RONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_RON_ENA_SHIFT,
+		   0, &wm8400_dapm_ronmix_controls[0],
+		   ARRAY_SIZE(wm8400_dapm_ronmix_controls)),
+
+/* ROMIX */
+SND_SOC_DAPM_MIXER_E("ROMIX", WM8400_POWER_MANAGEMENT_3,
+		     WM8400_ROMIX_ENA_SHIFT,
+		     0, &wm8400_dapm_romix_controls[0],
+		     ARRAY_SIZE(wm8400_dapm_romix_controls),
+		     outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LOUT PGA */
+SND_SOC_DAPM_PGA("LOUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_LOUT_ENA_SHIFT,
+		 0, NULL, 0),
+
+/* ROUT PGA */
+SND_SOC_DAPM_PGA("ROUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_ROUT_ENA_SHIFT,
+		 0, NULL, 0),
+
+/* LOPGA */
+SND_SOC_DAPM_PGA("LOPGA", WM8400_POWER_MANAGEMENT_3, WM8400_LOPGA_ENA_SHIFT, 0,
+	NULL, 0),
+
+/* ROPGA */
+SND_SOC_DAPM_PGA("ROPGA", WM8400_POWER_MANAGEMENT_3, WM8400_ROPGA_ENA_SHIFT, 0,
+	NULL, 0),
+
+/* MICBIAS */
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8400_POWER_MANAGEMENT_1,
+	WM8400_MIC1BIAS_ENA_SHIFT, 0),
+
+SND_SOC_DAPM_OUTPUT("LON"),
+SND_SOC_DAPM_OUTPUT("LOP"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("SPKN"),
+SND_SOC_DAPM_OUTPUT("SPKP"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_OUTPUT("ROP"),
+SND_SOC_DAPM_OUTPUT("RON"),
+
+SND_SOC_DAPM_OUTPUT("Internal DAC Sink"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Make DACs turn on when playing even if not mixed into any outputs */
+	{"Internal DAC Sink", NULL, "Left DAC"},
+	{"Internal DAC Sink", NULL, "Right DAC"},
+
+	/* Make ADCs turn on when recording
+	 * even if not mixed from any inputs */
+	{"Left ADC", NULL, "Internal ADC Source"},
+	{"Right ADC", NULL, "Internal ADC Source"},
+
+	/* Input Side */
+	/* LIN12 PGA */
+	{"LIN12 PGA", "LIN1 Switch", "LIN1"},
+	{"LIN12 PGA", "LIN2 Switch", "LIN2"},
+	/* LIN34 PGA */
+	{"LIN34 PGA", "LIN3 Switch", "LIN3"},
+	{"LIN34 PGA", "LIN4 Switch", "LIN4/RXN"},
+	/* INMIXL */
+	{"INMIXL", "Record Left Volume", "LOMIX"},
+	{"INMIXL", "LIN2 Volume", "LIN2"},
+	{"INMIXL", "LINPGA12 Switch", "LIN12 PGA"},
+	{"INMIXL", "LINPGA34 Switch", "LIN34 PGA"},
+	/* AILNMUX */
+	{"AILNMUX", "INMIXL Mix", "INMIXL"},
+	{"AILNMUX", "DIFFINL Mix", "LIN12 PGA"},
+	{"AILNMUX", "DIFFINL Mix", "LIN34 PGA"},
+	{"AILNMUX", "RXVOICE Mix", "LIN4/RXN"},
+	{"AILNMUX", "RXVOICE Mix", "RIN4/RXP"},
+	/* ADC */
+	{"Left ADC", NULL, "AILNMUX"},
+
+	/* RIN12 PGA */
+	{"RIN12 PGA", "RIN1 Switch", "RIN1"},
+	{"RIN12 PGA", "RIN2 Switch", "RIN2"},
+	/* RIN34 PGA */
+	{"RIN34 PGA", "RIN3 Switch", "RIN3"},
+	{"RIN34 PGA", "RIN4 Switch", "RIN4/RXP"},
+	/* INMIXL */
+	{"INMIXR", "Record Right Volume", "ROMIX"},
+	{"INMIXR", "RIN2 Volume", "RIN2"},
+	{"INMIXR", "RINPGA12 Switch", "RIN12 PGA"},
+	{"INMIXR", "RINPGA34 Switch", "RIN34 PGA"},
+	/* AIRNMUX */
+	{"AIRNMUX", "INMIXR Mix", "INMIXR"},
+	{"AIRNMUX", "DIFFINR Mix", "RIN12 PGA"},
+	{"AIRNMUX", "DIFFINR Mix", "RIN34 PGA"},
+	{"AIRNMUX", "RXVOICE Mix", "LIN4/RXN"},
+	{"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"},
+	/* ADC */
+	{"Right ADC", NULL, "AIRNMUX"},
+
+	/* LOMIX */
+	{"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"},
+	{"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"},
+	{"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+	{"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+	{"LOMIX", "LOMIX Right ADC Bypass Switch", "AIRNMUX"},
+	{"LOMIX", "LOMIX Left ADC Bypass Switch", "AILNMUX"},
+	{"LOMIX", "LOMIX Left DAC Switch", "Left DAC"},
+
+	/* ROMIX */
+	{"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"},
+	{"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"},
+	{"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+	{"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+	{"ROMIX", "ROMIX Right ADC Bypass Switch", "AIRNMUX"},
+	{"ROMIX", "ROMIX Left ADC Bypass Switch", "AILNMUX"},
+	{"ROMIX", "ROMIX Right DAC Switch", "Right DAC"},
+
+	/* SPKMIX */
+	{"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"},
+	{"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"},
+	{"SPKMIX", "SPKMIX LADC Bypass Switch", "AILNMUX"},
+	{"SPKMIX", "SPKMIX RADC Bypass Switch", "AIRNMUX"},
+	{"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"},
+	{"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"},
+	{"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"},
+	{"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"},
+
+	/* LONMIX */
+	{"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"},
+	{"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"},
+	{"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"},
+
+	/* LOPMIX */
+	{"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+	{"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+	{"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"},
+
+	/* OUT3MIX */
+	{"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXN"},
+	{"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"},
+
+	/* OUT4MIX */
+	{"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"},
+	{"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"},
+
+	/* RONMIX */
+	{"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"},
+	{"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"},
+	{"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"},
+
+	/* ROPMIX */
+	{"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+	{"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+	{"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"},
+
+	/* Out Mixer PGAs */
+	{"LOPGA", NULL, "LOMIX"},
+	{"ROPGA", NULL, "ROMIX"},
+
+	{"LOUT PGA", NULL, "LOMIX"},
+	{"ROUT PGA", NULL, "ROMIX"},
+
+	/* Output Pins */
+	{"LON", NULL, "LONMIX"},
+	{"LOP", NULL, "LOPMIX"},
+	{"OUT3", NULL, "OUT3MIX"},
+	{"LOUT", NULL, "LOUT PGA"},
+	{"SPKN", NULL, "SPKMIX"},
+	{"ROUT", NULL, "ROUT PGA"},
+	{"OUT4", NULL, "OUT4MIX"},
+	{"ROP", NULL, "ROPMIX"},
+	{"RON", NULL, "RONMIX"},
+};
+
+static int wm8400_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8400_dapm_widgets,
+				  ARRAY_SIZE(wm8400_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+/*
+ * Clock after FLL and dividers
+ */
+static int wm8400_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+
+	wm8400->sysclk = freq;
+	return 0;
+}
+
+struct fll_factors {
+	u16 n;
+	u16 k;
+	u16 outdiv;
+	u16 fratio;
+	u16 freq_ref;
+};
+
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors,
+		       unsigned int Fref, unsigned int Fout)
+{
+	u64 Kpart;
+	unsigned int K, Nmod, target;
+
+	factors->outdiv = 2;
+	while (Fout * factors->outdiv <  90000000 ||
+	       Fout * factors->outdiv > 100000000) {
+		factors->outdiv *= 2;
+		if (factors->outdiv > 32) {
+			dev_err(wm8400->wm8400->dev,
+				"Unsupported FLL output frequency %uHz\n",
+				Fout);
+			return -EINVAL;
+		}
+	}
+	target = Fout * factors->outdiv;
+	factors->outdiv = factors->outdiv >> 2;
+
+	if (Fref < 48000)
+		factors->freq_ref = 1;
+	else
+		factors->freq_ref = 0;
+
+	if (Fref < 1000000)
+		factors->fratio = 9;
+	else
+		factors->fratio = 0;
+
+	/* Ensure we have a fractional part */
+	do {
+		if (Fref < 1000000)
+			factors->fratio--;
+		else
+			factors->fratio++;
+
+		if (factors->fratio < 1 || factors->fratio > 8) {
+			dev_err(wm8400->wm8400->dev,
+				"Unable to calculate FRATIO\n");
+			return -EINVAL;
+		}
+
+		factors->n = target / (Fref * factors->fratio);
+		Nmod = target % (Fref * factors->fratio);
+	} while (Nmod == 0);
+
+	/* Calculate fractional part - scale up so we can round. */
+	Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, (Fref * factors->fratio));
+
+	K = Kpart & 0xFFFFFFFF;
+
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	factors->k = K / 10;
+
+	dev_dbg(wm8400->wm8400->dev,
+		"FLL: Fref=%u Fout=%u N=%x K=%x, FRATIO=%x OUTDIV=%x\n",
+		Fref, Fout,
+		factors->n, factors->k, factors->fratio, factors->outdiv);
+
+	return 0;
+}
+
+static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+			      int source, unsigned int freq_in,
+			      unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+	struct fll_factors factors;
+	int ret;
+	u16 reg;
+
+	if (freq_in == wm8400->fll_in && freq_out == wm8400->fll_out)
+		return 0;
+
+	if (freq_out) {
+		ret = fll_factors(wm8400, &factors, freq_in, freq_out);
+		if (ret != 0)
+			return ret;
+	} else {
+		/* Bodge GCC 4.4.0 uninitialised variable warning - it
+		 * doesn't seem capable of working out that we exit if
+		 * freq_out is 0 before any of the uses. */
+		memset(&factors, 0, sizeof(factors));
+	}
+
+	wm8400->fll_out = freq_out;
+	wm8400->fll_in = freq_in;
+
+	/* We *must* disable the FLL before any changes */
+	reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_2);
+	reg &= ~WM8400_FLL_ENA;
+	wm8400_write(codec, WM8400_POWER_MANAGEMENT_2, reg);
+
+	reg = wm8400_read(codec, WM8400_FLL_CONTROL_1);
+	reg &= ~WM8400_FLL_OSC_ENA;
+	wm8400_write(codec, WM8400_FLL_CONTROL_1, reg);
+
+	if (!freq_out)
+		return 0;
+
+	reg &= ~(WM8400_FLL_REF_FREQ | WM8400_FLL_FRATIO_MASK);
+	reg |= WM8400_FLL_FRAC | factors.fratio;
+	reg |= factors.freq_ref << WM8400_FLL_REF_FREQ_SHIFT;
+	wm8400_write(codec, WM8400_FLL_CONTROL_1, reg);
+
+	wm8400_write(codec, WM8400_FLL_CONTROL_2, factors.k);
+	wm8400_write(codec, WM8400_FLL_CONTROL_3, factors.n);
+
+	reg = wm8400_read(codec, WM8400_FLL_CONTROL_4);
+	reg &= WM8400_FLL_OUTDIV_MASK;
+	reg |= factors.outdiv;
+	wm8400_write(codec, WM8400_FLL_CONTROL_4, reg);
+
+	return 0;
+}
+
+/*
+ * Sets ADC and Voice DAC format.
+ */
+static int wm8400_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 audio1, audio3;
+
+	audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1);
+	audio3 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_3);
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		audio3 &= ~WM8400_AIF_MSTR1;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		audio3 |= WM8400_AIF_MSTR1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	audio1 &= ~WM8400_AIF_FMT_MASK;
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		audio1 |= WM8400_AIF_FMT_I2S;
+		audio1 &= ~WM8400_AIF_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		audio1 |= WM8400_AIF_FMT_RIGHTJ;
+		audio1 &= ~WM8400_AIF_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		audio1 |= WM8400_AIF_FMT_LEFTJ;
+		audio1 &= ~WM8400_AIF_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		audio1 |= WM8400_AIF_FMT_DSP;
+		audio1 &= ~WM8400_AIF_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		audio1 |= WM8400_AIF_FMT_DSP | WM8400_AIF_LRCLK_INV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1);
+	wm8400_write(codec, WM8400_AUDIO_INTERFACE_3, audio3);
+	return 0;
+}
+
+static int wm8400_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+		int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+
+	switch (div_id) {
+	case WM8400_MCLK_DIV:
+		reg = wm8400_read(codec, WM8400_CLOCKING_2) &
+			~WM8400_MCLK_DIV_MASK;
+		wm8400_write(codec, WM8400_CLOCKING_2, reg | div);
+		break;
+	case WM8400_DACCLK_DIV:
+		reg = wm8400_read(codec, WM8400_CLOCKING_2) &
+			~WM8400_DAC_CLKDIV_MASK;
+		wm8400_write(codec, WM8400_CLOCKING_2, reg | div);
+		break;
+	case WM8400_ADCCLK_DIV:
+		reg = wm8400_read(codec, WM8400_CLOCKING_2) &
+			~WM8400_ADC_CLKDIV_MASK;
+		wm8400_write(codec, WM8400_CLOCKING_2, reg | div);
+		break;
+	case WM8400_BCLK_DIV:
+		reg = wm8400_read(codec, WM8400_CLOCKING_1) &
+			~WM8400_BCLK_DIV_MASK;
+		wm8400_write(codec, WM8400_CLOCKING_1, reg | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8400_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	u16 audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1);
+
+	audio1 &= ~WM8400_AIF_WL_MASK;
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		audio1 |= WM8400_AIF_WL_20BITS;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		audio1 |= WM8400_AIF_WL_24BITS;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		audio1 |= WM8400_AIF_WL_32BITS;
+		break;
+	}
+
+	wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1);
+	return 0;
+}
+
+static int wm8400_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 val = wm8400_read(codec, WM8400_DAC_CTRL) & ~WM8400_DAC_MUTE;
+
+	if (mute)
+		wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE);
+	else
+		wm8400_write(codec, WM8400_DAC_CTRL, val);
+
+	return 0;
+}
+
+/* TODO: set bias for best performance at standby */
+static int wm8400_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec);
+	u16 val;
+	int ret;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		/* VMID=2*50k */
+		val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) &
+			~WM8400_VMID_MODE_MASK;
+		wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x2);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			ret = regulator_bulk_enable(ARRAY_SIZE(power),
+						    &power[0]);
+			if (ret != 0) {
+				dev_err(wm8400->wm8400->dev,
+					"Failed to enable regulators: %d\n",
+					ret);
+				return ret;
+			}
+
+			wm8400_write(codec, WM8400_POWER_MANAGEMENT_1,
+				     WM8400_CODEC_ENA | WM8400_SYSCLK_ENA);
+
+			/* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */
+			wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+				     WM8400_BUFDCOPEN | WM8400_POBCTRL);
+
+			msleep(50);
+
+			/* Enable VREF & VMID at 2x50k */
+			val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1);
+			val |= 0x2 | WM8400_VREF_ENA;
+			wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val);
+
+			/* Enable BUFIOEN */
+			wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+				     WM8400_BUFDCOPEN | WM8400_POBCTRL |
+				     WM8400_BUFIOEN);
+
+			/* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+			wm8400_write(codec, WM8400_ANTIPOP2, WM8400_BUFIOEN);
+		}
+
+		/* VMID=2*300k */
+		val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) &
+			~WM8400_VMID_MODE_MASK;
+		wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x4);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		/* Enable POBCTRL and SOFT_ST */
+		wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+			WM8400_POBCTRL | WM8400_BUFIOEN);
+
+		/* Enable POBCTRL, SOFT_ST and BUFDCOPEN */
+		wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+			WM8400_BUFDCOPEN | WM8400_POBCTRL |
+			WM8400_BUFIOEN);
+
+		/* mute DAC */
+		val = wm8400_read(codec, WM8400_DAC_CTRL);
+		wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE);
+
+		/* Enable any disabled outputs */
+		val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1);
+		val |= WM8400_SPK_ENA | WM8400_OUT3_ENA |
+			WM8400_OUT4_ENA | WM8400_LOUT_ENA |
+			WM8400_ROUT_ENA;
+		wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val);
+
+		/* Disable VMID */
+		val &= ~WM8400_VMID_MODE_MASK;
+		wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val);
+
+		msleep(300);
+
+		/* Enable all output discharge bits */
+		wm8400_write(codec, WM8400_ANTIPOP1, WM8400_DIS_LLINE |
+			WM8400_DIS_RLINE | WM8400_DIS_OUT3 |
+			WM8400_DIS_OUT4 | WM8400_DIS_LOUT |
+			WM8400_DIS_ROUT);
+
+		/* Disable VREF */
+		val &= ~WM8400_VREF_ENA;
+		wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val);
+
+		/* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+		wm8400_write(codec, WM8400_ANTIPOP2, 0x0);
+
+		ret = regulator_bulk_disable(ARRAY_SIZE(power),
+					     &power[0]);
+		if (ret != 0)
+			return ret;
+
+		break;
+	}
+
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8400_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8400_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8400_dai_ops = {
+	.hw_params = wm8400_hw_params,
+	.digital_mute = wm8400_mute,
+	.set_fmt = wm8400_set_dai_fmt,
+	.set_clkdiv = wm8400_set_dai_clkdiv,
+	.set_sysclk = wm8400_set_dai_sysclk,
+	.set_pll = wm8400_set_dai_pll,
+};
+
+/*
+ * The WM8400 supports 2 different and mutually exclusive DAI
+ * configurations.
+ *
+ * 1. ADC/DAC on Primary Interface
+ * 2. ADC on Primary Interface/DAC on secondary
+ */
+static struct snd_soc_dai_driver wm8400_dai = {
+/* ADC/DAC on primary */
+	.name = "wm8400-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8400_RATES,
+		.formats = WM8400_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8400_RATES,
+		.formats = WM8400_FORMATS,
+	},
+	.ops = &wm8400_dai_ops,
+};
+
+static int wm8400_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8400_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm8400_resume(struct snd_soc_codec *codec)
+{
+	wm8400_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static void wm8400_probe_deferred(struct work_struct *work)
+{
+	struct wm8400_priv *priv = container_of(work, struct wm8400_priv,
+						work);
+	struct snd_soc_codec *codec = priv->codec;
+
+	/* charge output caps */
+	wm8400_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+}
+
+static int wm8400_codec_probe(struct snd_soc_codec *codec)
+{
+	struct wm8400 *wm8400 = dev_get_platdata(codec->dev);
+	struct wm8400_priv *priv;
+	int ret;
+	u16 reg;
+
+	priv = kzalloc(sizeof(struct wm8400_priv), GFP_KERNEL);
+	if (priv == NULL)
+		return -ENOMEM;
+
+	snd_soc_codec_set_drvdata(codec, priv);
+	codec->control_data = priv->wm8400 = wm8400;
+	priv->codec = codec;
+
+	ret = regulator_bulk_get(wm8400->dev,
+				 ARRAY_SIZE(power), &power[0]);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to get regulators: %d\n", ret);
+	        goto err;
+	}
+
+	INIT_WORK(&priv->work, wm8400_probe_deferred);
+
+	wm8400_codec_reset(codec);
+
+	reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1);
+	wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, reg | WM8400_CODEC_ENA);
+
+	/* Latch volume update bits */
+	reg = wm8400_read(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME);
+	wm8400_write(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME,
+		     reg & WM8400_IPVU);
+	reg = wm8400_read(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME);
+	wm8400_write(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME,
+		     reg & WM8400_IPVU);
+
+	wm8400_write(codec, WM8400_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8));
+	wm8400_write(codec, WM8400_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8));
+
+	if (!schedule_work(&priv->work)) {
+		ret = -EINVAL;
+		goto err_regulator;
+	}
+	wm8400_add_controls(codec);
+	wm8400_add_widgets(codec);
+	return 0;
+
+err_regulator:
+	regulator_bulk_free(ARRAY_SIZE(power), power);
+err:
+	kfree(priv);
+	return ret;
+}
+
+static int  wm8400_codec_remove(struct snd_soc_codec *codec)
+{
+	struct wm8400_priv *priv = snd_soc_codec_get_drvdata(codec);
+	u16 reg;
+
+	reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1);
+	wm8400_write(codec, WM8400_POWER_MANAGEMENT_1,
+		     reg & (~WM8400_CODEC_ENA));
+
+	regulator_bulk_free(ARRAY_SIZE(power), power);
+	kfree(priv);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8400 = {
+	.probe =	wm8400_codec_probe,
+	.remove =	wm8400_codec_remove,
+	.suspend =	wm8400_suspend,
+	.resume =	wm8400_resume,
+	.read = wm8400_read,
+	.write = wm8400_write,
+	.set_bias_level = wm8400_set_bias_level,
+};
+
+static int __devinit wm8400_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8400,
+			&wm8400_dai, 1);
+}
+
+static int __devexit wm8400_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver wm8400_codec_driver = {
+	.driver = {
+		   .name = "wm8400-codec",
+		   .owner = THIS_MODULE,
+		   },
+	.probe = wm8400_probe,
+	.remove = __devexit_p(wm8400_remove),
+};
+
+static __init int wm8400_init(void)
+{
+	return platform_driver_register(&wm8400_codec_driver);
+}
+module_init(wm8400_init);
+
+static __exit void wm8400_exit(void)
+{
+	platform_driver_unregister(&wm8400_codec_driver);
+}
+module_exit(wm8400_exit);
+
+MODULE_DESCRIPTION("ASoC WM8400 driver");
+MODULE_AUTHOR("Mark Brown");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm8400-codec");
diff --git a/linux/sound/soc/codecs/wm8400.h b/linux/sound/soc/codecs/wm8400.h
new file mode 100644
index 0000000..521adb1
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8400.h
@@ -0,0 +1,59 @@
+/*
+ * wm8400.h  --  audio driver for WM8400
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  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.
+ *
+ */
+
+#ifndef _WM8400_CODEC_H
+#define _WM8400_CODEC_H
+
+#define WM8400_MCLK_DIV 0
+#define WM8400_DACCLK_DIV 1
+#define WM8400_ADCCLK_DIV 2
+#define WM8400_BCLK_DIV 3
+
+#define WM8400_MCLK_DIV_1 0x400
+#define WM8400_MCLK_DIV_2 0x800
+
+#define WM8400_DAC_CLKDIV_1    0x00
+#define WM8400_DAC_CLKDIV_1_5  0x04
+#define WM8400_DAC_CLKDIV_2    0x08
+#define WM8400_DAC_CLKDIV_3    0x0c
+#define WM8400_DAC_CLKDIV_4    0x10
+#define WM8400_DAC_CLKDIV_5_5  0x14
+#define WM8400_DAC_CLKDIV_6    0x18
+
+#define WM8400_ADC_CLKDIV_1    0x00
+#define WM8400_ADC_CLKDIV_1_5  0x20
+#define WM8400_ADC_CLKDIV_2    0x40
+#define WM8400_ADC_CLKDIV_3    0x60
+#define WM8400_ADC_CLKDIV_4    0x80
+#define WM8400_ADC_CLKDIV_5_5  0xa0
+#define WM8400_ADC_CLKDIV_6    0xc0
+
+
+#define WM8400_BCLK_DIV_1                       (0x0 << 1)
+#define WM8400_BCLK_DIV_1_5                     (0x1 << 1)
+#define WM8400_BCLK_DIV_2                       (0x2 << 1)
+#define WM8400_BCLK_DIV_3                       (0x3 << 1)
+#define WM8400_BCLK_DIV_4                       (0x4 << 1)
+#define WM8400_BCLK_DIV_5_5                     (0x5 << 1)
+#define WM8400_BCLK_DIV_6                       (0x6 << 1)
+#define WM8400_BCLK_DIV_8                       (0x7 << 1)
+#define WM8400_BCLK_DIV_11                      (0x8 << 1)
+#define WM8400_BCLK_DIV_12                      (0x9 << 1)
+#define WM8400_BCLK_DIV_16                      (0xA << 1)
+#define WM8400_BCLK_DIV_22                      (0xB << 1)
+#define WM8400_BCLK_DIV_24                      (0xC << 1)
+#define WM8400_BCLK_DIV_32                      (0xD << 1)
+#define WM8400_BCLK_DIV_44                      (0xE << 1)
+#define WM8400_BCLK_DIV_48                      (0xF << 1)
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8510.c b/linux/sound/soc/codecs/wm8510.c
new file mode 100644
index 0000000..8f10709
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8510.c
@@ -0,0 +1,715 @@
+/*
+ * wm8510.c  --  WM8510 ALSA Soc Audio driver
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "wm8510.h"
+
+/*
+ * wm8510 register cache
+ * We can't read the WM8510 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8510_reg[WM8510_CACHEREGNUM] = {
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0050, 0x0000, 0x0140, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x00ff,
+	0x0000, 0x0000, 0x0100, 0x00ff,
+	0x0000, 0x0000, 0x012c, 0x002c,
+	0x002c, 0x002c, 0x002c, 0x0000,
+	0x0032, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0038, 0x000b, 0x0032, 0x0000,
+	0x0008, 0x000c, 0x0093, 0x00e9,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0003, 0x0010, 0x0000, 0x0000,
+	0x0000, 0x0002, 0x0001, 0x0000,
+	0x0000, 0x0000, 0x0039, 0x0000,
+	0x0001,
+};
+
+#define WM8510_POWER1_BIASEN  0x08
+#define WM8510_POWER1_BUFIOEN 0x10
+
+#define wm8510_reset(c)	snd_soc_write(c, WM8510_RESET, 0)
+
+/* codec private data */
+struct wm8510_priv {
+	enum snd_soc_control_type control_type;
+};
+
+static const char *wm8510_companding[] = { "Off", "NC", "u-law", "A-law" };
+static const char *wm8510_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8510_alc[] = { "ALC", "Limiter" };
+
+static const struct soc_enum wm8510_enum[] = {
+	SOC_ENUM_SINGLE(WM8510_COMP, 1, 4, wm8510_companding), /* adc */
+	SOC_ENUM_SINGLE(WM8510_COMP, 3, 4, wm8510_companding), /* dac */
+	SOC_ENUM_SINGLE(WM8510_DAC,  4, 4, wm8510_deemp),
+	SOC_ENUM_SINGLE(WM8510_ALC3,  8, 2, wm8510_alc),
+};
+
+static const struct snd_kcontrol_new wm8510_snd_controls[] = {
+
+SOC_SINGLE("Digital Loopback Switch", WM8510_COMP, 0, 1, 0),
+
+SOC_ENUM("DAC Companding", wm8510_enum[1]),
+SOC_ENUM("ADC Companding", wm8510_enum[0]),
+
+SOC_ENUM("Playback De-emphasis", wm8510_enum[2]),
+SOC_SINGLE("DAC Inversion Switch", WM8510_DAC, 0, 1, 0),
+
+SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0),
+
+SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0),
+SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0),
+SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0),
+
+SOC_SINGLE("Capture Volume", WM8510_ADCVOL,  0, 127, 0),
+
+SOC_SINGLE("DAC Playback Limiter Switch", WM8510_DACLIM1,  8, 1, 0),
+SOC_SINGLE("DAC Playback Limiter Decay", WM8510_DACLIM1,  4, 15, 0),
+SOC_SINGLE("DAC Playback Limiter Attack", WM8510_DACLIM1,  0, 15, 0),
+
+SOC_SINGLE("DAC Playback Limiter Threshold", WM8510_DACLIM2,  4, 7, 0),
+SOC_SINGLE("DAC Playback Limiter Boost", WM8510_DACLIM2,  0, 15, 0),
+
+SOC_SINGLE("ALC Enable Switch", WM8510_ALC1,  8, 1, 0),
+SOC_SINGLE("ALC Capture Max Gain", WM8510_ALC1,  3, 7, 0),
+SOC_SINGLE("ALC Capture Min Gain", WM8510_ALC1,  0, 7, 0),
+
+SOC_SINGLE("ALC Capture ZC Switch", WM8510_ALC2,  8, 1, 0),
+SOC_SINGLE("ALC Capture Hold", WM8510_ALC2,  4, 7, 0),
+SOC_SINGLE("ALC Capture Target", WM8510_ALC2,  0, 15, 0),
+
+SOC_ENUM("ALC Capture Mode", wm8510_enum[3]),
+SOC_SINGLE("ALC Capture Decay", WM8510_ALC3,  4, 15, 0),
+SOC_SINGLE("ALC Capture Attack", WM8510_ALC3,  0, 15, 0),
+
+SOC_SINGLE("ALC Capture Noise Gate Switch", WM8510_NGATE,  3, 1, 0),
+SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8510_NGATE,  0, 7, 0),
+
+SOC_SINGLE("Capture PGA ZC Switch", WM8510_INPPGA,  7, 1, 0),
+SOC_SINGLE("Capture PGA Volume", WM8510_INPPGA,  0, 63, 0),
+
+SOC_SINGLE("Speaker Playback ZC Switch", WM8510_SPKVOL,  7, 1, 0),
+SOC_SINGLE("Speaker Playback Switch", WM8510_SPKVOL,  6, 1, 1),
+SOC_SINGLE("Speaker Playback Volume", WM8510_SPKVOL,  0, 63, 0),
+SOC_SINGLE("Speaker Boost", WM8510_OUTPUT, 2, 1, 0),
+
+SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST,  8, 1, 0),
+SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 1),
+};
+
+/* Speaker Output Mixer */
+static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_SPKMIX, 5, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_SPKMIX, 0, 1, 0),
+};
+
+/* Mono Output Mixer */
+static const struct snd_kcontrol_new wm8510_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_MONOMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_MONOMIX, 2, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8510_boost_controls[] = {
+SOC_DAPM_SINGLE("Mic PGA Switch", WM8510_INPPGA,  6, 1, 1),
+SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0),
+SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0),
+};
+
+static const struct snd_kcontrol_new wm8510_micpga_controls[] = {
+SOC_DAPM_SINGLE("MICP Switch", WM8510_INPUT, 0, 1, 0),
+SOC_DAPM_SINGLE("MICN Switch", WM8510_INPUT, 1, 1, 0),
+SOC_DAPM_SINGLE("AUX Switch", WM8510_INPUT, 2, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8510_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Speaker Mixer", WM8510_POWER3, 2, 0,
+	&wm8510_speaker_mixer_controls[0],
+	ARRAY_SIZE(wm8510_speaker_mixer_controls)),
+SND_SOC_DAPM_MIXER("Mono Mixer", WM8510_POWER3, 3, 0,
+	&wm8510_mono_mixer_controls[0],
+	ARRAY_SIZE(wm8510_mono_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8510_POWER3, 0, 0),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8510_POWER2, 0, 0),
+SND_SOC_DAPM_PGA("Aux Input", WM8510_POWER1, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Mic PGA", WM8510_POWER2, 2, 0,
+		   &wm8510_micpga_controls[0],
+		   ARRAY_SIZE(wm8510_micpga_controls)),
+SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0,
+	&wm8510_boost_controls[0],
+	ARRAY_SIZE(wm8510_boost_controls)),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8510_POWER1, 4, 0),
+
+SND_SOC_DAPM_INPUT("MICN"),
+SND_SOC_DAPM_INPUT("MICP"),
+SND_SOC_DAPM_INPUT("AUX"),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("SPKOUTP"),
+SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Mono output mixer */
+	{"Mono Mixer", "PCM Playback Switch", "DAC"},
+	{"Mono Mixer", "Aux Playback Switch", "Aux Input"},
+	{"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+	/* Speaker output mixer */
+	{"Speaker Mixer", "PCM Playback Switch", "DAC"},
+	{"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
+	{"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+	/* Outputs */
+	{"Mono Out", NULL, "Mono Mixer"},
+	{"MONOOUT", NULL, "Mono Out"},
+	{"SpkN Out", NULL, "Speaker Mixer"},
+	{"SpkP Out", NULL, "Speaker Mixer"},
+	{"SPKOUTN", NULL, "SpkN Out"},
+	{"SPKOUTP", NULL, "SpkP Out"},
+
+	/* Microphone PGA */
+	{"Mic PGA", "MICN Switch", "MICN"},
+	{"Mic PGA", "MICP Switch", "MICP"},
+	{ "Mic PGA", "AUX Switch", "Aux Input" },
+
+	/* Boost Mixer */
+	{"Boost Mixer", "Mic PGA Switch", "Mic PGA"},
+	{"Boost Mixer", "Mic Volume", "MICP"},
+	{"Boost Mixer", "Aux Volume", "Aux Input"},
+
+	{"ADC", NULL, "Boost Mixer"},
+};
+
+static int wm8510_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8510_dapm_widgets,
+				  ARRAY_SIZE(wm8510_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+struct pll_ {
+	unsigned int pre_div:4; /* prescale - 1 */
+	unsigned int n:4;
+	unsigned int k;
+};
+
+static struct pll_ pll_div;
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+
+static void pll_factors(unsigned int target, unsigned int source)
+{
+	unsigned long long Kpart;
+	unsigned int K, Ndiv, Nmod;
+
+	Ndiv = target / source;
+	if (Ndiv < 6) {
+		source >>= 1;
+		pll_div.pre_div = 1;
+		Ndiv = target / source;
+	} else
+		pll_div.pre_div = 0;
+
+	if ((Ndiv < 6) || (Ndiv > 12))
+		printk(KERN_WARNING
+			"WM8510 N value %u outwith recommended range!d\n",
+			Ndiv);
+
+	pll_div.n = Ndiv;
+	Nmod = target % source;
+	Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	/* Check if we need to round */
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	K /= 10;
+
+	pll_div.k = K;
+}
+
+static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+
+	if (freq_in == 0 || freq_out == 0) {
+		/* Clock CODEC directly from MCLK */
+		reg = snd_soc_read(codec, WM8510_CLOCK);
+		snd_soc_write(codec, WM8510_CLOCK, reg & 0x0ff);
+
+		/* Turn off PLL */
+		reg = snd_soc_read(codec, WM8510_POWER1);
+		snd_soc_write(codec, WM8510_POWER1, reg & 0x1df);
+		return 0;
+	}
+
+	pll_factors(freq_out*4, freq_in);
+
+	snd_soc_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n);
+	snd_soc_write(codec, WM8510_PLLK1, pll_div.k >> 18);
+	snd_soc_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff);
+	snd_soc_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff);
+	reg = snd_soc_read(codec, WM8510_POWER1);
+	snd_soc_write(codec, WM8510_POWER1, reg | 0x020);
+
+	/* Run CODEC from PLL instead of MCLK */
+	reg = snd_soc_read(codec, WM8510_CLOCK);
+	snd_soc_write(codec, WM8510_CLOCK, reg | 0x100);
+
+	return 0;
+}
+
+/*
+ * Configure WM8510 clock dividers.
+ */
+static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+		int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+
+	switch (div_id) {
+	case WM8510_OPCLKDIV:
+		reg = snd_soc_read(codec, WM8510_GPIO) & 0x1cf;
+		snd_soc_write(codec, WM8510_GPIO, reg | div);
+		break;
+	case WM8510_MCLKDIV:
+		reg = snd_soc_read(codec, WM8510_CLOCK) & 0x11f;
+		snd_soc_write(codec, WM8510_CLOCK, reg | div);
+		break;
+	case WM8510_ADCCLK:
+		reg = snd_soc_read(codec, WM8510_ADC) & 0x1f7;
+		snd_soc_write(codec, WM8510_ADC, reg | div);
+		break;
+	case WM8510_DACCLK:
+		reg = snd_soc_read(codec, WM8510_DAC) & 0x1f7;
+		snd_soc_write(codec, WM8510_DAC, reg | div);
+		break;
+	case WM8510_BCLKDIV:
+		reg = snd_soc_read(codec, WM8510_CLOCK) & 0x1e3;
+		snd_soc_write(codec, WM8510_CLOCK, reg | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = 0;
+	u16 clk = snd_soc_read(codec, WM8510_CLOCK) & 0x1fe;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		clk |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x0010;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x0008;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x00018;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x0180;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x0100;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x0080;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8510_IFACE, iface);
+	snd_soc_write(codec, WM8510_CLOCK, clk);
+	return 0;
+}
+
+static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	u16 iface = snd_soc_read(codec, WM8510_IFACE) & 0x19f;
+	u16 adn = snd_soc_read(codec, WM8510_ADD) & 0x1f1;
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x0020;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x0040;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface |= 0x0060;
+		break;
+	}
+
+	/* filter coefficient */
+	switch (params_rate(params)) {
+	case 8000:
+		adn |= 0x5 << 1;
+		break;
+	case 11025:
+		adn |= 0x4 << 1;
+		break;
+	case 16000:
+		adn |= 0x3 << 1;
+		break;
+	case 22050:
+		adn |= 0x2 << 1;
+		break;
+	case 32000:
+		adn |= 0x1 << 1;
+		break;
+	case 44100:
+	case 48000:
+		break;
+	}
+
+	snd_soc_write(codec, WM8510_IFACE, iface);
+	snd_soc_write(codec, WM8510_ADD, adn);
+	return 0;
+}
+
+static int wm8510_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = snd_soc_read(codec, WM8510_DAC) & 0xffbf;
+
+	if (mute)
+		snd_soc_write(codec, WM8510_DAC, mute_reg | 0x40);
+	else
+		snd_soc_write(codec, WM8510_DAC, mute_reg);
+	return 0;
+}
+
+/* liam need to make this lower power with dapm */
+static int wm8510_set_bias_level(struct snd_soc_codec *codec,
+	enum snd_soc_bias_level level)
+{
+	u16 power1 = snd_soc_read(codec, WM8510_POWER1) & ~0x3;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+		power1 |= 0x1;  /* VMID 50k */
+		snd_soc_write(codec, WM8510_POWER1, power1);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		power1 |= WM8510_POWER1_BIASEN | WM8510_POWER1_BUFIOEN;
+
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Initial cap charge at VMID 5k */
+			snd_soc_write(codec, WM8510_POWER1, power1 | 0x3);
+			mdelay(100);
+		}
+
+		power1 |= 0x2;  /* VMID 500k */
+		snd_soc_write(codec, WM8510_POWER1, power1);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		snd_soc_write(codec, WM8510_POWER1, 0);
+		snd_soc_write(codec, WM8510_POWER2, 0);
+		snd_soc_write(codec, WM8510_POWER3, 0);
+		break;
+	}
+
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8510_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+		SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8510_dai_ops = {
+	.hw_params	= wm8510_pcm_hw_params,
+	.digital_mute	= wm8510_mute,
+	.set_fmt	= wm8510_set_dai_fmt,
+	.set_clkdiv	= wm8510_set_dai_clkdiv,
+	.set_pll	= wm8510_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver wm8510_dai = {
+	.name = "wm8510-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = WM8510_RATES,
+		.formats = WM8510_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = WM8510_RATES,
+		.formats = WM8510_FORMATS,},
+	.ops = &wm8510_dai_ops,
+	.symmetric_rates = 1,
+};
+
+static int wm8510_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8510_resume(struct snd_soc_codec *codec)
+{
+	int i;
+	u8 data[2];
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 0; i < ARRAY_SIZE(wm8510_reg); i++) {
+		data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+		data[1] = cache[i] & 0x00ff;
+		codec->hw_write(codec->control_data, data, 2);
+	}
+	wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static int wm8510_probe(struct snd_soc_codec *codec)
+{
+	struct wm8510_priv *wm8510 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9,  wm8510->control_type);
+	if (ret < 0) {
+		printk(KERN_ERR "wm8510: failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	wm8510_reset(codec);
+
+	/* power on device */
+	wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	snd_soc_add_controls(codec, wm8510_snd_controls,
+				ARRAY_SIZE(wm8510_snd_controls));
+	wm8510_add_widgets(codec);
+
+	return ret;
+}
+
+/* power down chip */
+static int wm8510_remove(struct snd_soc_codec *codec)
+{
+	struct wm8510_priv *wm8510 = snd_soc_codec_get_drvdata(codec);
+
+	wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	kfree(wm8510);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8510 = {
+	.probe =	wm8510_probe,
+	.remove =	wm8510_remove,
+	.suspend =	wm8510_suspend,
+	.resume =	wm8510_resume,
+	.set_bias_level = wm8510_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8510_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default =wm8510_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8510_spi_probe(struct spi_device *spi)
+{
+	struct wm8510_priv *wm8510;
+	int ret;
+
+	wm8510 = kzalloc(sizeof(struct wm8510_priv), GFP_KERNEL);
+	if (wm8510 == NULL)
+		return -ENOMEM;
+
+	wm8510->control_type = SND_SOC_SPI;
+	spi_set_drvdata(spi, wm8510);
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_dev_wm8510, &wm8510_dai, 1);
+	if (ret < 0)
+		kfree(wm8510);
+	return ret;
+}
+
+static int __devexit wm8510_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	return 0;
+}
+
+static struct spi_driver wm8510_spi_driver = {
+	.driver = {
+		.name	= "wm8510",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= wm8510_spi_probe,
+	.remove		= __devexit_p(wm8510_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8510_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8510_priv *wm8510;
+	int ret;
+
+	wm8510 = kzalloc(sizeof(struct wm8510_priv), GFP_KERNEL);
+	if (wm8510 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8510);
+	wm8510->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8510, &wm8510_dai, 1);
+	if (ret < 0)
+		kfree(wm8510);
+	return ret;
+}
+
+static __devexit int wm8510_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	return 0;
+}
+
+static const struct i2c_device_id wm8510_i2c_id[] = {
+	{ "wm8510", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8510_i2c_id);
+
+static struct i2c_driver wm8510_i2c_driver = {
+	.driver = {
+		.name = "wm8510-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8510_i2c_probe,
+	.remove =   __devexit_p(wm8510_i2c_remove),
+	.id_table = wm8510_i2c_id,
+};
+#endif
+
+static int __init wm8510_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8510_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8510 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&wm8510_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8510 SPI driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8510_modinit);
+
+static void __exit wm8510_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8510_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&wm8510_spi_driver);
+#endif
+}
+module_exit(wm8510_exit);
+
+MODULE_DESCRIPTION("ASoC WM8510 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8510.h b/linux/sound/soc/codecs/wm8510.h
new file mode 100644
index 0000000..b3e26ed
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8510.h
@@ -0,0 +1,102 @@
+/*
+ * wm8510.h  --  WM8510 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8510_H
+#define _WM8510_H
+
+/* WM8510 register space */
+
+#define WM8510_RESET		0x0
+#define WM8510_POWER1		0x1
+#define WM8510_POWER2		0x2
+#define WM8510_POWER3		0x3
+#define WM8510_IFACE		0x4
+#define WM8510_COMP			0x5
+#define WM8510_CLOCK		0x6
+#define WM8510_ADD			0x7
+#define WM8510_GPIO			0x8
+#define WM8510_DAC			0xa
+#define WM8510_DACVOL		0xb
+#define WM8510_ADC			0xe
+#define WM8510_ADCVOL		0xf
+#define WM8510_EQ1			0x12
+#define WM8510_EQ2			0x13
+#define WM8510_EQ3			0x14
+#define WM8510_EQ4			0x15
+#define WM8510_EQ5			0x16
+#define WM8510_DACLIM1		0x18
+#define WM8510_DACLIM2		0x19
+#define WM8510_NOTCH1		0x1b
+#define WM8510_NOTCH2		0x1c
+#define WM8510_NOTCH3		0x1d
+#define WM8510_NOTCH4		0x1e
+#define WM8510_ALC1			0x20
+#define WM8510_ALC2			0x21
+#define WM8510_ALC3			0x22
+#define WM8510_NGATE		0x23
+#define WM8510_PLLN			0x24
+#define WM8510_PLLK1		0x25
+#define WM8510_PLLK2		0x26
+#define WM8510_PLLK3		0x27
+#define WM8510_ATTEN		0x28
+#define WM8510_INPUT		0x2c
+#define WM8510_INPPGA		0x2d
+#define WM8510_ADCBOOST		0x2f
+#define WM8510_OUTPUT		0x31
+#define WM8510_SPKMIX		0x32
+#define WM8510_SPKVOL		0x36
+#define WM8510_MONOMIX		0x38
+
+#define WM8510_CACHEREGNUM 	57
+
+/* Clock divider Id's */
+#define WM8510_OPCLKDIV		0
+#define WM8510_MCLKDIV		1
+#define WM8510_ADCCLK		2
+#define WM8510_DACCLK		3
+#define WM8510_BCLKDIV		4
+
+/* DAC clock dividers */
+#define WM8510_DACCLK_F2	(1 << 3)
+#define WM8510_DACCLK_F4	(0 << 3)
+
+/* ADC clock dividers */
+#define WM8510_ADCCLK_F2	(1 << 3)
+#define WM8510_ADCCLK_F4	(0 << 3)
+
+/* PLL Out dividers */
+#define WM8510_OPCLKDIV_1	(0 << 4)
+#define WM8510_OPCLKDIV_2	(1 << 4)
+#define WM8510_OPCLKDIV_3	(2 << 4)
+#define WM8510_OPCLKDIV_4	(3 << 4)
+
+/* BCLK clock dividers */
+#define WM8510_BCLKDIV_1	(0 << 2)
+#define WM8510_BCLKDIV_2	(1 << 2)
+#define WM8510_BCLKDIV_4	(2 << 2)
+#define WM8510_BCLKDIV_8	(3 << 2)
+#define WM8510_BCLKDIV_16	(4 << 2)
+#define WM8510_BCLKDIV_32	(5 << 2)
+
+/* MCLK clock dividers */
+#define WM8510_MCLKDIV_1	(0 << 5)
+#define WM8510_MCLKDIV_1_5	(1 << 5)
+#define WM8510_MCLKDIV_2	(2 << 5)
+#define WM8510_MCLKDIV_3	(3 << 5)
+#define WM8510_MCLKDIV_4	(4 << 5)
+#define WM8510_MCLKDIV_6	(5 << 5)
+#define WM8510_MCLKDIV_8	(6 << 5)
+#define WM8510_MCLKDIV_12	(7 << 5)
+
+struct wm8510_setup_data {
+	int spi;
+	int i2c_bus;
+	unsigned short i2c_address;
+};
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8523.c b/linux/sound/soc/codecs/wm8523.c
new file mode 100644
index 0000000..deca79e
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8523.c
@@ -0,0 +1,587 @@
+/*
+ * wm8523.c  --  WM8523 ALSA SoC Audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8523.h"
+
+#define WM8523_NUM_SUPPLIES 2
+static const char *wm8523_supply_names[WM8523_NUM_SUPPLIES] = {
+	"AVDD",
+	"LINEVDD",
+};
+
+#define WM8523_NUM_RATES 7
+
+/* codec private data */
+struct wm8523_priv {
+	enum snd_soc_control_type control_type;
+	struct regulator_bulk_data supplies[WM8523_NUM_SUPPLIES];
+	unsigned int sysclk;
+	unsigned int rate_constraint_list[WM8523_NUM_RATES];
+	struct snd_pcm_hw_constraint_list rate_constraint;
+};
+
+static const u16 wm8523_reg[WM8523_REGISTER_COUNT] = {
+	0x8523,     /* R0 - DEVICE_ID */
+	0x0001,     /* R1 - REVISION */
+	0x0000,     /* R2 - PSCTRL1 */
+	0x1812,     /* R3 - AIF_CTRL1 */
+	0x0000,     /* R4 - AIF_CTRL2 */
+	0x0001,     /* R5 - DAC_CTRL3 */
+	0x0190,     /* R6 - DAC_GAINL */
+	0x0190,     /* R7 - DAC_GAINR */
+	0x0000,     /* R8 - ZERO_DETECT */
+};
+
+static int wm8523_volatile_register(unsigned int reg)
+{
+	switch (reg) {
+	case WM8523_DEVICE_ID:
+	case WM8523_REVISION:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static int wm8523_reset(struct snd_soc_codec *codec)
+{
+	return snd_soc_write(codec, WM8523_DEVICE_ID, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -10000, 25, 0);
+
+static const char *wm8523_zd_count_text[] = {
+	"1024",
+	"2048",
+};
+
+static const struct soc_enum wm8523_zc_count =
+	SOC_ENUM_SINGLE(WM8523_ZERO_DETECT, 0, 2, wm8523_zd_count_text);
+
+static const struct snd_kcontrol_new wm8523_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Playback Volume", WM8523_DAC_GAINL, WM8523_DAC_GAINR,
+		 0, 448, 0, dac_tlv),
+SOC_SINGLE("ZC Switch", WM8523_DAC_CTRL3, 4, 1, 0),
+SOC_SINGLE("Playback Deemphasis Switch", WM8523_AIF_CTRL1, 8, 1, 0),
+SOC_DOUBLE("Playback Switch", WM8523_DAC_CTRL3, 2, 3, 1, 1),
+SOC_SINGLE("Volume Ramp Up Switch", WM8523_DAC_CTRL3, 1, 1, 0),
+SOC_SINGLE("Volume Ramp Down Switch", WM8523_DAC_CTRL3, 0, 1, 0),
+SOC_ENUM("Zero Detect Count", wm8523_zc_count),
+};
+
+static const struct snd_soc_dapm_widget wm8523_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_OUTPUT("LINEVOUTL"),
+SND_SOC_DAPM_OUTPUT("LINEVOUTR"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+	{ "LINEVOUTL", NULL, "DAC" },
+	{ "LINEVOUTR", NULL, "DAC" },
+};
+
+static int wm8523_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8523_dapm_widgets,
+				  ARRAY_SIZE(wm8523_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	return 0;
+}
+
+static struct {
+	int value;
+	int ratio;
+} lrclk_ratios[WM8523_NUM_RATES] = {
+	{ 1, 128 },
+	{ 2, 192 },
+	{ 3, 256 },
+	{ 4, 384 },
+	{ 5, 512 },
+	{ 6, 768 },
+	{ 7, 1152 },
+};
+
+static int wm8523_startup(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+
+	/* The set of sample rates that can be supported depends on the
+	 * MCLK supplied to the CODEC - enforce this.
+	 */
+	if (!wm8523->sysclk) {
+		dev_err(codec->dev,
+			"No MCLK configured, call set_sysclk() on init\n");
+		return -EINVAL;
+	}
+
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				   SNDRV_PCM_HW_PARAM_RATE,
+				   &wm8523->rate_constraint);
+
+	return 0;
+}
+
+static int wm8523_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+	int i;
+	u16 aifctrl1 = snd_soc_read(codec, WM8523_AIF_CTRL1);
+	u16 aifctrl2 = snd_soc_read(codec, WM8523_AIF_CTRL2);
+
+	/* Find a supported LRCLK ratio */
+	for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
+		if (wm8523->sysclk / params_rate(params) ==
+		    lrclk_ratios[i].ratio)
+			break;
+	}
+
+	/* Should never happen, should be handled by constraints */
+	if (i == ARRAY_SIZE(lrclk_ratios)) {
+		dev_err(codec->dev, "MCLK/fs ratio %d unsupported\n",
+			wm8523->sysclk / params_rate(params));
+		return -EINVAL;
+	}
+
+	aifctrl2 &= ~WM8523_SR_MASK;
+	aifctrl2 |= lrclk_ratios[i].value;
+
+	aifctrl1 &= ~WM8523_WL_MASK;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		aifctrl1 |= 0x8;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		aifctrl1 |= 0x10;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		aifctrl1 |= 0x18;
+		break;
+	}
+
+	snd_soc_write(codec, WM8523_AIF_CTRL1, aifctrl1);
+	snd_soc_write(codec, WM8523_AIF_CTRL2, aifctrl2);
+
+	return 0;
+}
+
+static int wm8523_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+	unsigned int val;
+	int i;
+
+	wm8523->sysclk = freq;
+
+	wm8523->rate_constraint.count = 0;
+	for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
+		val = freq / lrclk_ratios[i].ratio;
+		/* Check that it's a standard rate since core can't
+		 * cope with others and having the odd rates confuses
+		 * constraint matching.
+		 */
+		switch (val) {
+		case 8000:
+		case 11025:
+		case 16000:
+		case 22050:
+		case 32000:
+		case 44100:
+		case 48000:
+		case 64000:
+		case 88200:
+		case 96000:
+		case 176400:
+		case 192000:
+			dev_dbg(codec->dev, "Supported sample rate: %dHz\n",
+				val);
+			wm8523->rate_constraint_list[i] = val;
+			wm8523->rate_constraint.count++;
+			break;
+		default:
+			dev_dbg(codec->dev, "Skipping sample rate: %dHz\n",
+				val);
+		}
+	}
+
+	/* Need at least one supported rate... */
+	if (wm8523->rate_constraint.count == 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+
+static int wm8523_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 aifctrl1 = snd_soc_read(codec, WM8523_AIF_CTRL1);
+
+	aifctrl1 &= ~(WM8523_BCLK_INV_MASK | WM8523_LRCLK_INV_MASK |
+		      WM8523_FMT_MASK | WM8523_AIF_MSTR_MASK);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		aifctrl1 |= WM8523_AIF_MSTR;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		aifctrl1 |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		aifctrl1 |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		aifctrl1 |= 0x0003;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		aifctrl1 |= 0x0023;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		aifctrl1 |= WM8523_BCLK_INV | WM8523_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		aifctrl1 |= WM8523_BCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		aifctrl1 |= WM8523_LRCLK_INV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8523_AIF_CTRL1, aifctrl1);
+
+	return 0;
+}
+
+static int wm8523_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+	u16 *reg_cache = codec->reg_cache;
+	int ret, i;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		/* Full power on */
+		snd_soc_update_bits(codec, WM8523_PSCTRL1,
+				    WM8523_SYS_ENA_MASK, 3);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			ret = regulator_bulk_enable(ARRAY_SIZE(wm8523->supplies),
+						    wm8523->supplies);
+			if (ret != 0) {
+				dev_err(codec->dev,
+					"Failed to enable supplies: %d\n",
+					ret);
+				return ret;
+			}
+
+			/* Initial power up */
+			snd_soc_update_bits(codec, WM8523_PSCTRL1,
+					    WM8523_SYS_ENA_MASK, 1);
+
+			/* Sync back default/cached values */
+			for (i = WM8523_AIF_CTRL1;
+			     i < WM8523_MAX_REGISTER; i++)
+				snd_soc_write(codec, i, reg_cache[i]);
+
+
+			msleep(100);
+		}
+
+		/* Power up to mute */
+		snd_soc_update_bits(codec, WM8523_PSCTRL1,
+				    WM8523_SYS_ENA_MASK, 2);
+
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		/* The chip runs through the power down sequence for us. */
+		snd_soc_update_bits(codec, WM8523_PSCTRL1,
+				    WM8523_SYS_ENA_MASK, 0);
+		msleep(100);
+
+		regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies),
+				       wm8523->supplies);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8523_RATES SNDRV_PCM_RATE_8000_192000
+
+#define WM8523_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8523_dai_ops = {
+	.startup	= wm8523_startup,
+	.hw_params	= wm8523_hw_params,
+	.set_sysclk	= wm8523_set_dai_sysclk,
+	.set_fmt	= wm8523_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver wm8523_dai = {
+	.name = "wm8523-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,  /* Mono modes not yet supported */
+		.channels_max = 2,
+		.rates = WM8523_RATES,
+		.formats = WM8523_FORMATS,
+	},
+	.ops = &wm8523_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int wm8523_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8523_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8523_resume(struct snd_soc_codec *codec)
+{
+	wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	return 0;
+}
+#else
+#define wm8523_suspend NULL
+#define wm8523_resume NULL
+#endif
+
+static int wm8523_probe(struct snd_soc_codec *codec)
+{
+	struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+	u16 *reg_cache = codec->reg_cache;
+	int ret, i;
+
+	codec->hw_write = (hw_write_t)i2c_master_send;
+	wm8523->rate_constraint.list = &wm8523->rate_constraint_list[0];
+	wm8523->rate_constraint.count =
+		ARRAY_SIZE(wm8523->rate_constraint_list);
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm8523->control_type);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm8523->supplies); i++)
+		wm8523->supplies[i].supply = wm8523_supply_names[i];
+
+	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8523->supplies),
+				 wm8523->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+		return ret;
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(wm8523->supplies),
+				    wm8523->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+		goto err_get;
+	}
+
+	ret = snd_soc_read(codec, WM8523_DEVICE_ID);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to read ID register\n");
+		goto err_enable;
+	}
+	if (ret != wm8523_reg[WM8523_DEVICE_ID]) {
+		dev_err(codec->dev, "Device is not a WM8523, ID is %x\n", ret);
+		ret = -EINVAL;
+		goto err_enable;
+	}
+
+	ret = snd_soc_read(codec, WM8523_REVISION);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to read revision register\n");
+		goto err_enable;
+	}
+	dev_info(codec->dev, "revision %c\n",
+		 (ret & WM8523_CHIP_REV_MASK) + 'A');
+
+	ret = wm8523_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		goto err_enable;
+	}
+
+	/* Change some default settings - latch VU and enable ZC */
+	reg_cache[WM8523_DAC_GAINR] |= WM8523_DACR_VU;
+	reg_cache[WM8523_DAC_CTRL3] |= WM8523_ZC;
+
+	wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Bias level configuration will have done an extra enable */
+	regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
+
+	snd_soc_add_controls(codec, wm8523_snd_controls,
+			     ARRAY_SIZE(wm8523_snd_controls));
+	wm8523_add_widgets(codec);
+
+	return 0;
+
+err_enable:
+	regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
+err_get:
+	regulator_bulk_free(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
+
+	return ret;
+}
+
+static int wm8523_remove(struct snd_soc_codec *codec)
+{
+	struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
+
+	wm8523_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	regulator_bulk_free(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8523 = {
+	.probe =	wm8523_probe,
+	.remove =	wm8523_remove,
+	.suspend =	wm8523_suspend,
+	.resume =	wm8523_resume,
+	.set_bias_level = wm8523_set_bias_level,
+	.reg_cache_size = WM8523_REGISTER_COUNT,
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8523_reg,
+	.volatile_register = wm8523_volatile_register,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8523_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8523_priv *wm8523;
+	int ret;
+
+	wm8523 = kzalloc(sizeof(struct wm8523_priv), GFP_KERNEL);
+	if (wm8523 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8523);
+	wm8523->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8523, &wm8523_dai, 1);
+	if (ret < 0)
+		kfree(wm8523);
+	return ret;
+
+}
+
+static __devexit int wm8523_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8523_i2c_id[] = {
+	{ "wm8523", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8523_i2c_id);
+
+static struct i2c_driver wm8523_i2c_driver = {
+	.driver = {
+		.name = "wm8523-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8523_i2c_probe,
+	.remove =   __devexit_p(wm8523_i2c_remove),
+	.id_table = wm8523_i2c_id,
+};
+#endif
+
+static int __init wm8523_modinit(void)
+{
+	int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8523_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8523 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return 0;
+}
+module_init(wm8523_modinit);
+
+static void __exit wm8523_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8523_i2c_driver);
+#endif
+}
+module_exit(wm8523_exit);
+
+MODULE_DESCRIPTION("ASoC WM8523 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8523.h b/linux/sound/soc/codecs/wm8523.h
new file mode 100644
index 0000000..4d5b1eb
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8523.h
@@ -0,0 +1,157 @@
+/*
+ * wm8523.h  --  WM8423 ASoC driver
+ *
+ * Copyright 2009 Wolfson Microelectronics, plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8523_H
+#define _WM8523_H
+
+/*
+ * Register values.
+ */
+#define WM8523_DEVICE_ID                        0x00
+#define WM8523_REVISION                         0x01
+#define WM8523_PSCTRL1                          0x02
+#define WM8523_AIF_CTRL1                        0x03
+#define WM8523_AIF_CTRL2                        0x04
+#define WM8523_DAC_CTRL3                        0x05
+#define WM8523_DAC_GAINL                        0x06
+#define WM8523_DAC_GAINR                        0x07
+#define WM8523_ZERO_DETECT                      0x08
+
+#define WM8523_REGISTER_COUNT                   9
+#define WM8523_MAX_REGISTER                     0x08
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - DEVICE_ID
+ */
+#define WM8523_CHIP_ID_MASK                     0xFFFF  /* CHIP_ID - [15:0] */
+#define WM8523_CHIP_ID_SHIFT                         0  /* CHIP_ID - [15:0] */
+#define WM8523_CHIP_ID_WIDTH                        16  /* CHIP_ID - [15:0] */
+
+/*
+ * R1 (0x01) - REVISION
+ */
+#define WM8523_CHIP_REV_MASK                    0x0007  /* CHIP_REV - [2:0] */
+#define WM8523_CHIP_REV_SHIFT                        0  /* CHIP_REV - [2:0] */
+#define WM8523_CHIP_REV_WIDTH                        3  /* CHIP_REV - [2:0] */
+
+/*
+ * R2 (0x02) - PSCTRL1
+ */
+#define WM8523_SYS_ENA_MASK                     0x0003  /* SYS_ENA - [1:0] */
+#define WM8523_SYS_ENA_SHIFT                         0  /* SYS_ENA - [1:0] */
+#define WM8523_SYS_ENA_WIDTH                         2  /* SYS_ENA - [1:0] */
+
+/*
+ * R3 (0x03) - AIF_CTRL1
+ */
+#define WM8523_TDM_MODE_MASK                    0x1800  /* TDM_MODE - [12:11] */
+#define WM8523_TDM_MODE_SHIFT                       11  /* TDM_MODE - [12:11] */
+#define WM8523_TDM_MODE_WIDTH                        2  /* TDM_MODE - [12:11] */
+#define WM8523_TDM_SLOT_MASK                    0x0600  /* TDM_SLOT - [10:9] */
+#define WM8523_TDM_SLOT_SHIFT                        9  /* TDM_SLOT - [10:9] */
+#define WM8523_TDM_SLOT_WIDTH                        2  /* TDM_SLOT - [10:9] */
+#define WM8523_DEEMPH                           0x0100  /* DEEMPH  */
+#define WM8523_DEEMPH_MASK                      0x0100  /* DEEMPH  */
+#define WM8523_DEEMPH_SHIFT                          8  /* DEEMPH  */
+#define WM8523_DEEMPH_WIDTH                          1  /* DEEMPH  */
+#define WM8523_AIF_MSTR                         0x0080  /* AIF_MSTR  */
+#define WM8523_AIF_MSTR_MASK                    0x0080  /* AIF_MSTR  */
+#define WM8523_AIF_MSTR_SHIFT                        7  /* AIF_MSTR  */
+#define WM8523_AIF_MSTR_WIDTH                        1  /* AIF_MSTR  */
+#define WM8523_LRCLK_INV                        0x0040  /* LRCLK_INV  */
+#define WM8523_LRCLK_INV_MASK                   0x0040  /* LRCLK_INV  */
+#define WM8523_LRCLK_INV_SHIFT                       6  /* LRCLK_INV  */
+#define WM8523_LRCLK_INV_WIDTH                       1  /* LRCLK_INV  */
+#define WM8523_BCLK_INV                         0x0020  /* BCLK_INV  */
+#define WM8523_BCLK_INV_MASK                    0x0020  /* BCLK_INV  */
+#define WM8523_BCLK_INV_SHIFT                        5  /* BCLK_INV  */
+#define WM8523_BCLK_INV_WIDTH                        1  /* BCLK_INV  */
+#define WM8523_WL_MASK                          0x0018  /* WL - [4:3] */
+#define WM8523_WL_SHIFT                              3  /* WL - [4:3] */
+#define WM8523_WL_WIDTH                              2  /* WL - [4:3] */
+#define WM8523_FMT_MASK                         0x0007  /* FMT - [2:0] */
+#define WM8523_FMT_SHIFT                             0  /* FMT - [2:0] */
+#define WM8523_FMT_WIDTH                             3  /* FMT - [2:0] */
+
+/*
+ * R4 (0x04) - AIF_CTRL2
+ */
+#define WM8523_DAC_OP_MUX_MASK                  0x00C0  /* DAC_OP_MUX - [7:6] */
+#define WM8523_DAC_OP_MUX_SHIFT                      6  /* DAC_OP_MUX - [7:6] */
+#define WM8523_DAC_OP_MUX_WIDTH                      2  /* DAC_OP_MUX - [7:6] */
+#define WM8523_BCLKDIV_MASK                     0x0038  /* BCLKDIV - [5:3] */
+#define WM8523_BCLKDIV_SHIFT                         3  /* BCLKDIV - [5:3] */
+#define WM8523_BCLKDIV_WIDTH                         3  /* BCLKDIV - [5:3] */
+#define WM8523_SR_MASK                          0x0007  /* SR - [2:0] */
+#define WM8523_SR_SHIFT                              0  /* SR - [2:0] */
+#define WM8523_SR_WIDTH                              3  /* SR - [2:0] */
+
+/*
+ * R5 (0x05) - DAC_CTRL3
+ */
+#define WM8523_ZC                               0x0010  /* ZC  */
+#define WM8523_ZC_MASK                          0x0010  /* ZC  */
+#define WM8523_ZC_SHIFT                              4  /* ZC  */
+#define WM8523_ZC_WIDTH                              1  /* ZC  */
+#define WM8523_DACR                             0x0008  /* DACR  */
+#define WM8523_DACR_MASK                        0x0008  /* DACR  */
+#define WM8523_DACR_SHIFT                            3  /* DACR  */
+#define WM8523_DACR_WIDTH                            1  /* DACR  */
+#define WM8523_DACL                             0x0004  /* DACL  */
+#define WM8523_DACL_MASK                        0x0004  /* DACL  */
+#define WM8523_DACL_SHIFT                            2  /* DACL  */
+#define WM8523_DACL_WIDTH                            1  /* DACL  */
+#define WM8523_VOL_UP_RAMP                      0x0002  /* VOL_UP_RAMP  */
+#define WM8523_VOL_UP_RAMP_MASK                 0x0002  /* VOL_UP_RAMP  */
+#define WM8523_VOL_UP_RAMP_SHIFT                     1  /* VOL_UP_RAMP  */
+#define WM8523_VOL_UP_RAMP_WIDTH                     1  /* VOL_UP_RAMP  */
+#define WM8523_VOL_DOWN_RAMP                    0x0001  /* VOL_DOWN_RAMP  */
+#define WM8523_VOL_DOWN_RAMP_MASK               0x0001  /* VOL_DOWN_RAMP  */
+#define WM8523_VOL_DOWN_RAMP_SHIFT                   0  /* VOL_DOWN_RAMP  */
+#define WM8523_VOL_DOWN_RAMP_WIDTH                   1  /* VOL_DOWN_RAMP  */
+
+/*
+ * R6 (0x06) - DAC_GAINL
+ */
+#define WM8523_DACL_VU                          0x0200  /* DACL_VU  */
+#define WM8523_DACL_VU_MASK                     0x0200  /* DACL_VU  */
+#define WM8523_DACL_VU_SHIFT                         9  /* DACL_VU  */
+#define WM8523_DACL_VU_WIDTH                         1  /* DACL_VU  */
+#define WM8523_DACL_VOL_MASK                    0x01FF  /* DACL_VOL - [8:0] */
+#define WM8523_DACL_VOL_SHIFT                        0  /* DACL_VOL - [8:0] */
+#define WM8523_DACL_VOL_WIDTH                        9  /* DACL_VOL - [8:0] */
+
+/*
+ * R7 (0x07) - DAC_GAINR
+ */
+#define WM8523_DACR_VU                          0x0200  /* DACR_VU  */
+#define WM8523_DACR_VU_MASK                     0x0200  /* DACR_VU  */
+#define WM8523_DACR_VU_SHIFT                         9  /* DACR_VU  */
+#define WM8523_DACR_VU_WIDTH                         1  /* DACR_VU  */
+#define WM8523_DACR_VOL_MASK                    0x01FF  /* DACR_VOL - [8:0] */
+#define WM8523_DACR_VOL_SHIFT                        0  /* DACR_VOL - [8:0] */
+#define WM8523_DACR_VOL_WIDTH                        9  /* DACR_VOL - [8:0] */
+
+/*
+ * R8 (0x08) - ZERO_DETECT
+ */
+#define WM8523_ZD_COUNT_MASK                    0x0003  /* ZD_COUNT - [1:0] */
+#define WM8523_ZD_COUNT_SHIFT                        0  /* ZD_COUNT - [1:0] */
+#define WM8523_ZD_COUNT_WIDTH                        2  /* ZD_COUNT - [1:0] */
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8580.c b/linux/sound/soc/codecs/wm8580.c
new file mode 100644
index 0000000..8725d4e
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8580.c
@@ -0,0 +1,981 @@
+/*
+ * wm8580.c  --  WM8580 ALSA Soc Audio driver
+ *
+ * Copyright 2008, 2009 Wolfson Microelectronics PLC.
+ *
+ *  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.
+ *
+ * Notes:
+ *  The WM8580 is a multichannel codec with S/PDIF support, featuring six
+ *  DAC channels and two ADC channels.
+ *
+ *  Currently only the primary audio interface is supported - S/PDIF and
+ *  the secondary audio interfaces are not.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <asm/div64.h>
+
+#include "wm8580.h"
+
+/* WM8580 register space */
+#define WM8580_PLLA1                         0x00
+#define WM8580_PLLA2                         0x01
+#define WM8580_PLLA3                         0x02
+#define WM8580_PLLA4                         0x03
+#define WM8580_PLLB1                         0x04
+#define WM8580_PLLB2                         0x05
+#define WM8580_PLLB3                         0x06
+#define WM8580_PLLB4                         0x07
+#define WM8580_CLKSEL                        0x08
+#define WM8580_PAIF1                         0x09
+#define WM8580_PAIF2                         0x0A
+#define WM8580_SAIF1                         0x0B
+#define WM8580_PAIF3                         0x0C
+#define WM8580_PAIF4                         0x0D
+#define WM8580_SAIF2                         0x0E
+#define WM8580_DAC_CONTROL1                  0x0F
+#define WM8580_DAC_CONTROL2                  0x10
+#define WM8580_DAC_CONTROL3                  0x11
+#define WM8580_DAC_CONTROL4                  0x12
+#define WM8580_DAC_CONTROL5                  0x13
+#define WM8580_DIGITAL_ATTENUATION_DACL1     0x14
+#define WM8580_DIGITAL_ATTENUATION_DACR1     0x15
+#define WM8580_DIGITAL_ATTENUATION_DACL2     0x16
+#define WM8580_DIGITAL_ATTENUATION_DACR2     0x17
+#define WM8580_DIGITAL_ATTENUATION_DACL3     0x18
+#define WM8580_DIGITAL_ATTENUATION_DACR3     0x19
+#define WM8580_MASTER_DIGITAL_ATTENUATION    0x1C
+#define WM8580_ADC_CONTROL1                  0x1D
+#define WM8580_SPDTXCHAN0                    0x1E
+#define WM8580_SPDTXCHAN1                    0x1F
+#define WM8580_SPDTXCHAN2                    0x20
+#define WM8580_SPDTXCHAN3                    0x21
+#define WM8580_SPDTXCHAN4                    0x22
+#define WM8580_SPDTXCHAN5                    0x23
+#define WM8580_SPDMODE                       0x24
+#define WM8580_INTMASK                       0x25
+#define WM8580_GPO1                          0x26
+#define WM8580_GPO2                          0x27
+#define WM8580_GPO3                          0x28
+#define WM8580_GPO4                          0x29
+#define WM8580_GPO5                          0x2A
+#define WM8580_INTSTAT                       0x2B
+#define WM8580_SPDRXCHAN1                    0x2C
+#define WM8580_SPDRXCHAN2                    0x2D
+#define WM8580_SPDRXCHAN3                    0x2E
+#define WM8580_SPDRXCHAN4                    0x2F
+#define WM8580_SPDRXCHAN5                    0x30
+#define WM8580_SPDSTAT                       0x31
+#define WM8580_PWRDN1                        0x32
+#define WM8580_PWRDN2                        0x33
+#define WM8580_READBACK                      0x34
+#define WM8580_RESET                         0x35
+
+#define WM8580_MAX_REGISTER                  0x35
+
+#define WM8580_DACOSR 0x40
+
+/* PLLB4 (register 7h) */
+#define WM8580_PLLB4_MCLKOUTSRC_MASK   0x60
+#define WM8580_PLLB4_MCLKOUTSRC_PLLA   0x20
+#define WM8580_PLLB4_MCLKOUTSRC_PLLB   0x40
+#define WM8580_PLLB4_MCLKOUTSRC_OSC    0x60
+
+#define WM8580_PLLB4_CLKOUTSRC_MASK    0x180
+#define WM8580_PLLB4_CLKOUTSRC_PLLACLK 0x080
+#define WM8580_PLLB4_CLKOUTSRC_PLLBCLK 0x100
+#define WM8580_PLLB4_CLKOUTSRC_OSCCLK  0x180
+
+/* CLKSEL (register 8h) */
+#define WM8580_CLKSEL_DAC_CLKSEL_MASK 0x03
+#define WM8580_CLKSEL_DAC_CLKSEL_PLLA 0x01
+#define WM8580_CLKSEL_DAC_CLKSEL_PLLB 0x02
+
+/* AIF control 1 (registers 9h-bh) */
+#define WM8580_AIF_RATE_MASK       0x7
+#define WM8580_AIF_BCLKSEL_MASK   0x18
+
+#define WM8580_AIF_MS             0x20
+
+#define WM8580_AIF_CLKSRC_MASK    0xc0
+#define WM8580_AIF_CLKSRC_PLLA    0x40
+#define WM8580_AIF_CLKSRC_PLLB    0x40
+#define WM8580_AIF_CLKSRC_MCLK    0xc0
+
+/* AIF control 2 (registers ch-eh) */
+#define WM8580_AIF_FMT_MASK    0x03
+#define WM8580_AIF_FMT_RIGHTJ  0x00
+#define WM8580_AIF_FMT_LEFTJ   0x01
+#define WM8580_AIF_FMT_I2S     0x02
+#define WM8580_AIF_FMT_DSP     0x03
+
+#define WM8580_AIF_LENGTH_MASK   0x0c
+#define WM8580_AIF_LENGTH_16     0x00
+#define WM8580_AIF_LENGTH_20     0x04
+#define WM8580_AIF_LENGTH_24     0x08
+#define WM8580_AIF_LENGTH_32     0x0c
+
+#define WM8580_AIF_LRP         0x10
+#define WM8580_AIF_BCP         0x20
+
+/* Powerdown Register 1 (register 32h) */
+#define WM8580_PWRDN1_PWDN     0x001
+#define WM8580_PWRDN1_ALLDACPD 0x040
+
+/* Powerdown Register 2 (register 33h) */
+#define WM8580_PWRDN2_OSSCPD   0x001
+#define WM8580_PWRDN2_PLLAPD   0x002
+#define WM8580_PWRDN2_PLLBPD   0x004
+#define WM8580_PWRDN2_SPDIFPD  0x008
+#define WM8580_PWRDN2_SPDIFTXD 0x010
+#define WM8580_PWRDN2_SPDIFRXD 0x020
+
+#define WM8580_DAC_CONTROL5_MUTEALL 0x10
+
+/*
+ * wm8580 register cache
+ * We can't read the WM8580 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8580_reg[] = {
+	0x0121, 0x017e, 0x007d, 0x0014, /*R3*/
+	0x0121, 0x017e, 0x007d, 0x0194, /*R7*/
+	0x0010, 0x0002, 0x0002, 0x00c2, /*R11*/
+	0x0182, 0x0082, 0x000a, 0x0024, /*R15*/
+	0x0009, 0x0000, 0x00ff, 0x0000, /*R19*/
+	0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R23*/
+	0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R27*/
+	0x01f0, 0x0040, 0x0000, 0x0000, /*R31(0x1F)*/
+	0x0000, 0x0000, 0x0031, 0x000b, /*R35*/
+	0x0039, 0x0000, 0x0010, 0x0032, /*R39*/
+	0x0054, 0x0076, 0x0098, 0x0000, /*R43(0x2B)*/
+	0x0000, 0x0000, 0x0000, 0x0000, /*R47*/
+	0x0000, 0x0000, 0x005e, 0x003e, /*R51(0x33)*/
+	0x0000, 0x0000 /*R53*/
+};
+
+struct pll_state {
+	unsigned int in;
+	unsigned int out;
+};
+
+#define WM8580_NUM_SUPPLIES 3
+static const char *wm8580_supply_names[WM8580_NUM_SUPPLIES] = {
+	"AVDD",
+	"DVDD",
+	"PVDD",
+};
+
+/* codec private data */
+struct wm8580_priv {
+	enum snd_soc_control_type control_type;
+	struct regulator_bulk_data supplies[WM8580_NUM_SUPPLIES];
+	u16 reg_cache[WM8580_MAX_REGISTER + 1];
+	struct pll_state a;
+	struct pll_state b;
+	int sysclk[2];
+};
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+
+static int wm8580_out_vu(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	u16 *reg_cache = codec->reg_cache;
+	unsigned int reg = mc->reg;
+	unsigned int reg2 = mc->rreg;
+	int ret;
+
+	/* Clear the register cache so we write without VU set */
+	reg_cache[reg] = 0;
+	reg_cache[reg2] = 0;
+
+	ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+	if (ret < 0)
+		return ret;
+
+	/* Now write again with the volume update bit set */
+	snd_soc_update_bits(codec, reg, 0x100, 0x100);
+	snd_soc_update_bits(codec, reg2, 0x100, 0x100);
+
+	return 0;
+}
+
+#define SOC_WM8580_OUT_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, \
+				    xinvert, tlv_array)			\
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+		SNDRV_CTL_ELEM_ACCESS_READWRITE,  \
+	.tlv.p = (tlv_array), \
+	.info = snd_soc_info_volsw_2r, \
+	.get = snd_soc_get_volsw_2r, .put = wm8580_out_vu, \
+	.private_value = (unsigned long)&(struct soc_mixer_control) \
+		{.reg = reg_left, .rreg = reg_right, .shift = xshift, \
+		.max = xmax, .invert = xinvert} }
+
+static const struct snd_kcontrol_new wm8580_snd_controls[] = {
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC1 Playback Volume",
+			    WM8580_DIGITAL_ATTENUATION_DACL1,
+			    WM8580_DIGITAL_ATTENUATION_DACR1,
+			    0, 0xff, 0, dac_tlv),
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC2 Playback Volume",
+			    WM8580_DIGITAL_ATTENUATION_DACL2,
+			    WM8580_DIGITAL_ATTENUATION_DACR2,
+			    0, 0xff, 0, dac_tlv),
+SOC_WM8580_OUT_DOUBLE_R_TLV("DAC3 Playback Volume",
+			    WM8580_DIGITAL_ATTENUATION_DACL3,
+			    WM8580_DIGITAL_ATTENUATION_DACR3,
+			    0, 0xff, 0, dac_tlv),
+
+SOC_SINGLE("DAC1 Deemphasis Switch", WM8580_DAC_CONTROL3, 0, 1, 0),
+SOC_SINGLE("DAC2 Deemphasis Switch", WM8580_DAC_CONTROL3, 1, 1, 0),
+SOC_SINGLE("DAC3 Deemphasis Switch", WM8580_DAC_CONTROL3, 2, 1, 0),
+
+SOC_DOUBLE("DAC1 Invert Switch", WM8580_DAC_CONTROL4,  0, 1, 1, 0),
+SOC_DOUBLE("DAC2 Invert Switch", WM8580_DAC_CONTROL4,  2, 3, 1, 0),
+SOC_DOUBLE("DAC3 Invert Switch", WM8580_DAC_CONTROL4,  4, 5, 1, 0),
+
+SOC_SINGLE("DAC ZC Switch", WM8580_DAC_CONTROL5, 5, 1, 0),
+SOC_SINGLE("DAC1 Switch", WM8580_DAC_CONTROL5, 0, 1, 1),
+SOC_SINGLE("DAC2 Switch", WM8580_DAC_CONTROL5, 1, 1, 1),
+SOC_SINGLE("DAC3 Switch", WM8580_DAC_CONTROL5, 2, 1, 1),
+
+SOC_DOUBLE("Capture Switch", WM8580_ADC_CONTROL1, 0, 1, 1, 1),
+SOC_SINGLE("Capture High-Pass Filter Switch", WM8580_ADC_CONTROL1, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8580_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC1", "Playback", WM8580_PWRDN1, 2, 1),
+SND_SOC_DAPM_DAC("DAC2", "Playback", WM8580_PWRDN1, 3, 1),
+SND_SOC_DAPM_DAC("DAC3", "Playback", WM8580_PWRDN1, 4, 1),
+
+SND_SOC_DAPM_OUTPUT("VOUT1L"),
+SND_SOC_DAPM_OUTPUT("VOUT1R"),
+SND_SOC_DAPM_OUTPUT("VOUT2L"),
+SND_SOC_DAPM_OUTPUT("VOUT2R"),
+SND_SOC_DAPM_OUTPUT("VOUT3L"),
+SND_SOC_DAPM_OUTPUT("VOUT3R"),
+
+SND_SOC_DAPM_ADC("ADC", "Capture", WM8580_PWRDN1, 1, 1),
+
+SND_SOC_DAPM_INPUT("AINL"),
+SND_SOC_DAPM_INPUT("AINR"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{ "VOUT1L", NULL, "DAC1" },
+	{ "VOUT1R", NULL, "DAC1" },
+
+	{ "VOUT2L", NULL, "DAC2" },
+	{ "VOUT2R", NULL, "DAC2" },
+
+	{ "VOUT3L", NULL, "DAC3" },
+	{ "VOUT3R", NULL, "DAC3" },
+
+	{ "ADC", NULL, "AINL" },
+	{ "ADC", NULL, "AINR" },
+};
+
+static int wm8580_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8580_dapm_widgets,
+				  ARRAY_SIZE(wm8580_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+	u32 prescale:1;
+	u32 postscale:1;
+	u32 freqmode:2;
+	u32 n:4;
+	u32 k:24;
+};
+
+/* The size in bits of the pll divide */
+#define FIXED_PLL_SIZE (1 << 22)
+
+/* PLL rate to output rate divisions */
+static struct {
+	unsigned int div;
+	unsigned int freqmode;
+	unsigned int postscale;
+} post_table[] = {
+	{  2,  0, 0 },
+	{  4,  0, 1 },
+	{  4,  1, 0 },
+	{  8,  1, 1 },
+	{  8,  2, 0 },
+	{ 16,  2, 1 },
+	{ 12,  3, 0 },
+	{ 24,  3, 1 }
+};
+
+static int pll_factors(struct _pll_div *pll_div, unsigned int target,
+		       unsigned int source)
+{
+	u64 Kpart;
+	unsigned int K, Ndiv, Nmod;
+	int i;
+
+	pr_debug("wm8580: PLL %uHz->%uHz\n", source, target);
+
+	/* Scale the output frequency up; the PLL should run in the
+	 * region of 90-100MHz.
+	 */
+	for (i = 0; i < ARRAY_SIZE(post_table); i++) {
+		if (target * post_table[i].div >=  90000000 &&
+		    target * post_table[i].div <= 100000000) {
+			pll_div->freqmode = post_table[i].freqmode;
+			pll_div->postscale = post_table[i].postscale;
+			target *= post_table[i].div;
+			break;
+		}
+	}
+
+	if (i == ARRAY_SIZE(post_table)) {
+		printk(KERN_ERR "wm8580: Unable to scale output frequency "
+		       "%u\n", target);
+		return -EINVAL;
+	}
+
+	Ndiv = target / source;
+
+	if (Ndiv < 5) {
+		source /= 2;
+		pll_div->prescale = 1;
+		Ndiv = target / source;
+	} else
+		pll_div->prescale = 0;
+
+	if ((Ndiv < 5) || (Ndiv > 13)) {
+		printk(KERN_ERR
+			"WM8580 N=%u outside supported range\n", Ndiv);
+		return -EINVAL;
+	}
+
+	pll_div->n = Ndiv;
+	Nmod = target % source;
+	Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	pll_div->k = K;
+
+	pr_debug("PLL %x.%x prescale %d freqmode %d postscale %d\n",
+		 pll_div->n, pll_div->k, pll_div->prescale, pll_div->freqmode,
+		 pll_div->postscale);
+
+	return 0;
+}
+
+static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	int offset;
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec);
+	struct pll_state *state;
+	struct _pll_div pll_div;
+	unsigned int reg;
+	unsigned int pwr_mask;
+	int ret;
+
+	/* GCC isn't able to work out the ifs below for initialising/using
+	 * pll_div so suppress warnings.
+	 */
+	memset(&pll_div, 0, sizeof(pll_div));
+
+	switch (pll_id) {
+	case WM8580_PLLA:
+		state = &wm8580->a;
+		offset = 0;
+		pwr_mask = WM8580_PWRDN2_PLLAPD;
+		break;
+	case WM8580_PLLB:
+		state = &wm8580->b;
+		offset = 4;
+		pwr_mask = WM8580_PWRDN2_PLLBPD;
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	if (freq_in && freq_out) {
+		ret = pll_factors(&pll_div, freq_out, freq_in);
+		if (ret != 0)
+			return ret;
+	}
+
+	state->in = freq_in;
+	state->out = freq_out;
+
+	/* Always disable the PLL - it is not safe to leave it running
+	 * while reprogramming it.
+	 */
+	reg = snd_soc_read(codec, WM8580_PWRDN2);
+	snd_soc_write(codec, WM8580_PWRDN2, reg | pwr_mask);
+
+	if (!freq_in || !freq_out)
+		return 0;
+
+	snd_soc_write(codec, WM8580_PLLA1 + offset, pll_div.k & 0x1ff);
+	snd_soc_write(codec, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0x1ff);
+	snd_soc_write(codec, WM8580_PLLA3 + offset,
+		     (pll_div.k >> 18 & 0xf) | (pll_div.n << 4));
+
+	reg = snd_soc_read(codec, WM8580_PLLA4 + offset);
+	reg &= ~0x1b;
+	reg |= pll_div.prescale | pll_div.postscale << 1 |
+		pll_div.freqmode << 3;
+
+	snd_soc_write(codec, WM8580_PLLA4 + offset, reg);
+
+	/* All done, turn it on */
+	reg = snd_soc_read(codec, WM8580_PWRDN2);
+	snd_soc_write(codec, WM8580_PWRDN2, reg & ~pwr_mask);
+
+	return 0;
+}
+
+static const int wm8580_sysclk_ratios[] = {
+	128, 192, 256, 384, 512, 768, 1152,
+};
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8580_paif_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec);
+	u16 paifa = 0;
+	u16 paifb = 0;
+	int i, ratio, osr;
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		paifa |= 0x8;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		paifa |= 0x0;
+		paifb |= WM8580_AIF_LENGTH_20;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		paifa |= 0x0;
+		paifb |= WM8580_AIF_LENGTH_24;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		paifa |= 0x0;
+		paifb |= WM8580_AIF_LENGTH_32;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Look up the SYSCLK ratio; accept only exact matches */
+	ratio = wm8580->sysclk[dai->id] / params_rate(params);
+	for (i = 0; i < ARRAY_SIZE(wm8580_sysclk_ratios); i++)
+		if (ratio == wm8580_sysclk_ratios[i])
+			break;
+	if (i == ARRAY_SIZE(wm8580_sysclk_ratios)) {
+		dev_err(codec->dev, "Invalid clock ratio %d/%d\n",
+			wm8580->sysclk[dai->id], params_rate(params));
+		return -EINVAL;
+	}
+	paifa |= i;
+	dev_dbg(codec->dev, "Running at %dfs with %dHz clock\n",
+		wm8580_sysclk_ratios[i], wm8580->sysclk[dai->driver->id]);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		switch (ratio) {
+		case 128:
+		case 192:
+			osr = WM8580_DACOSR;
+			dev_dbg(codec->dev, "Selecting 64x OSR\n");
+			break;
+		default:
+			osr = 0;
+			dev_dbg(codec->dev, "Selecting 128x OSR\n");
+			break;
+		}
+
+		snd_soc_update_bits(codec, WM8580_PAIF3, WM8580_DACOSR, osr);
+	}
+
+	snd_soc_update_bits(codec, WM8580_PAIF1 + dai->driver->id,
+			    WM8580_AIF_RATE_MASK | WM8580_AIF_BCLKSEL_MASK,
+			    paifa);
+	snd_soc_update_bits(codec, WM8580_PAIF3 + dai->driver->id,
+			    WM8580_AIF_LENGTH_MASK, paifb);
+	return 0;
+}
+
+static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai,
+				      unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	unsigned int aifa;
+	unsigned int aifb;
+	int can_invert_lrclk;
+
+	aifa = snd_soc_read(codec, WM8580_PAIF1 + codec_dai->driver->id);
+	aifb = snd_soc_read(codec, WM8580_PAIF3 + codec_dai->driver->id);
+
+	aifb &= ~(WM8580_AIF_FMT_MASK | WM8580_AIF_LRP | WM8580_AIF_BCP);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		aifa &= ~WM8580_AIF_MS;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		aifa |= WM8580_AIF_MS;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		can_invert_lrclk = 1;
+		aifb |= WM8580_AIF_FMT_I2S;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		can_invert_lrclk = 1;
+		aifb |= WM8580_AIF_FMT_RIGHTJ;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		can_invert_lrclk = 1;
+		aifb |= WM8580_AIF_FMT_LEFTJ;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		can_invert_lrclk = 0;
+		aifb |= WM8580_AIF_FMT_DSP;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		can_invert_lrclk = 0;
+		aifb |= WM8580_AIF_FMT_DSP;
+		aifb |= WM8580_AIF_LRP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+
+	case SND_SOC_DAIFMT_IB_IF:
+		if (!can_invert_lrclk)
+			return -EINVAL;
+		aifb |= WM8580_AIF_BCP;
+		aifb |= WM8580_AIF_LRP;
+		break;
+
+	case SND_SOC_DAIFMT_IB_NF:
+		aifb |= WM8580_AIF_BCP;
+		break;
+
+	case SND_SOC_DAIFMT_NB_IF:
+		if (!can_invert_lrclk)
+			return -EINVAL;
+		aifb |= WM8580_AIF_LRP;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8580_PAIF1 + codec_dai->driver->id, aifa);
+	snd_soc_write(codec, WM8580_PAIF3 + codec_dai->driver->id, aifb);
+
+	return 0;
+}
+
+static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+				 int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	unsigned int reg;
+
+	switch (div_id) {
+	case WM8580_MCLK:
+		reg = snd_soc_read(codec, WM8580_PLLB4);
+		reg &= ~WM8580_PLLB4_MCLKOUTSRC_MASK;
+
+		switch (div) {
+		case WM8580_CLKSRC_MCLK:
+			/* Input */
+			break;
+
+		case WM8580_CLKSRC_PLLA:
+			reg |= WM8580_PLLB4_MCLKOUTSRC_PLLA;
+			break;
+		case WM8580_CLKSRC_PLLB:
+			reg |= WM8580_PLLB4_MCLKOUTSRC_PLLB;
+			break;
+
+		case WM8580_CLKSRC_OSC:
+			reg |= WM8580_PLLB4_MCLKOUTSRC_OSC;
+			break;
+
+		default:
+			return -EINVAL;
+		}
+		snd_soc_write(codec, WM8580_PLLB4, reg);
+		break;
+
+	case WM8580_CLKOUTSRC:
+		reg = snd_soc_read(codec, WM8580_PLLB4);
+		reg &= ~WM8580_PLLB4_CLKOUTSRC_MASK;
+
+		switch (div) {
+		case WM8580_CLKSRC_NONE:
+			break;
+
+		case WM8580_CLKSRC_PLLA:
+			reg |= WM8580_PLLB4_CLKOUTSRC_PLLACLK;
+			break;
+
+		case WM8580_CLKSRC_PLLB:
+			reg |= WM8580_PLLB4_CLKOUTSRC_PLLBCLK;
+			break;
+
+		case WM8580_CLKSRC_OSC:
+			reg |= WM8580_PLLB4_CLKOUTSRC_OSCCLK;
+			break;
+
+		default:
+			return -EINVAL;
+		}
+		snd_soc_write(codec, WM8580_PLLB4, reg);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wm8580_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+			     unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec);
+	int sel, sel_mask, sel_shift;
+
+	switch (dai->driver->id) {
+	case WM8580_DAI_PAIFRX:
+		sel_mask = 0x3;
+		sel_shift = 0;
+		break;
+
+	case WM8580_DAI_PAIFTX:
+		sel_mask = 0xc;
+		sel_shift = 2;
+		break;
+
+	default:
+		BUG_ON("Unknown DAI driver ID\n");
+		return -EINVAL;
+	}
+
+	switch (clk_id) {
+	case WM8580_CLKSRC_ADCMCLK:
+		if (dai->id != WM8580_DAI_PAIFTX)
+			return -EINVAL;
+		sel = 0 << sel_shift;
+		break;
+	case WM8580_CLKSRC_PLLA:
+		sel = 1 << sel_shift;
+		break;
+	case WM8580_CLKSRC_PLLB:
+		sel = 2 << sel_shift;
+		break;
+	case WM8580_CLKSRC_MCLK:
+		sel = 3 << sel_shift;
+		break;
+	default:
+		dev_err(codec->dev, "Unknown clock %d\n", clk_id);
+		return -EINVAL;
+	}
+
+	/* We really should validate PLL settings but not yet */
+	wm8580->sysclk[dai->id] = freq;
+
+	return snd_soc_update_bits(codec, WM8580_CLKSEL, sel_mask, sel);
+}
+
+static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	unsigned int reg;
+
+	reg = snd_soc_read(codec, WM8580_DAC_CONTROL5);
+
+	if (mute)
+		reg |= WM8580_DAC_CONTROL5_MUTEALL;
+	else
+		reg &= ~WM8580_DAC_CONTROL5_MUTEALL;
+
+	snd_soc_write(codec, WM8580_DAC_CONTROL5, reg);
+
+	return 0;
+}
+
+static int wm8580_set_bias_level(struct snd_soc_codec *codec,
+	enum snd_soc_bias_level level)
+{
+	u16 reg;
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Power up and get individual control of the DACs */
+			reg = snd_soc_read(codec, WM8580_PWRDN1);
+			reg &= ~(WM8580_PWRDN1_PWDN | WM8580_PWRDN1_ALLDACPD);
+			snd_soc_write(codec, WM8580_PWRDN1, reg);
+
+			/* Make VMID high impedence */
+			reg = snd_soc_read(codec,  WM8580_ADC_CONTROL1);
+			reg &= ~0x100;
+			snd_soc_write(codec, WM8580_ADC_CONTROL1, reg);
+		}
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		reg = snd_soc_read(codec, WM8580_PWRDN1);
+		snd_soc_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8580_dai_ops_playback = {
+	.set_sysclk	= wm8580_set_sysclk,
+	.hw_params	= wm8580_paif_hw_params,
+	.set_fmt	= wm8580_set_paif_dai_fmt,
+	.set_clkdiv	= wm8580_set_dai_clkdiv,
+	.set_pll	= wm8580_set_dai_pll,
+	.digital_mute	= wm8580_digital_mute,
+};
+
+static struct snd_soc_dai_ops wm8580_dai_ops_capture = {
+	.set_sysclk	= wm8580_set_sysclk,
+	.hw_params	= wm8580_paif_hw_params,
+	.set_fmt	= wm8580_set_paif_dai_fmt,
+	.set_clkdiv	= wm8580_set_dai_clkdiv,
+	.set_pll	= wm8580_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver wm8580_dai[] = {
+	{
+		.name = "wm8580-hifi-playback",
+		.id	= WM8580_DAI_PAIFRX,
+		.playback = {
+			.stream_name = "Playback",
+			.channels_min = 1,
+			.channels_max = 6,
+			.rates = SNDRV_PCM_RATE_8000_192000,
+			.formats = WM8580_FORMATS,
+		},
+		.ops = &wm8580_dai_ops_playback,
+	},
+	{
+		.name = "wm8580-hifi-capture",
+		.id	=	WM8580_DAI_PAIFTX,
+		.capture = {
+			.stream_name = "Capture",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_192000,
+			.formats = WM8580_FORMATS,
+		},
+		.ops = &wm8580_dai_ops_capture,
+	},
+};
+
+static int wm8580_probe(struct snd_soc_codec *codec)
+{
+	struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0,i;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8580->control_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm8580->supplies); i++)
+		wm8580->supplies[i].supply = wm8580_supply_names[i];
+
+	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8580->supplies),
+				 wm8580->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+		return ret;
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(wm8580->supplies),
+				    wm8580->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+		goto err_regulator_get;
+	}
+
+	/* Get the codec into a known state */
+	ret = snd_soc_write(codec, WM8580_RESET, 0);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to reset codec: %d\n", ret);
+		goto err_regulator_enable;
+	}
+
+	wm8580_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	snd_soc_add_controls(codec, wm8580_snd_controls,
+			     ARRAY_SIZE(wm8580_snd_controls));
+	wm8580_add_widgets(codec);
+
+	return 0;
+
+err_regulator_enable:
+	regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
+err_regulator_get:
+	regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
+	return ret;
+}
+
+/* power down chip */
+static int wm8580_remove(struct snd_soc_codec *codec)
+{
+	struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec);
+
+	wm8580_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
+	regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8580 = {
+	.probe =	wm8580_probe,
+	.remove =	wm8580_remove,
+	.set_bias_level = wm8580_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8580_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = &wm8580_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int wm8580_i2c_probe(struct i2c_client *i2c,
+			    const struct i2c_device_id *id)
+{
+	struct wm8580_priv *wm8580;
+	int ret;
+
+	wm8580 = kzalloc(sizeof(struct wm8580_priv), GFP_KERNEL);
+	if (wm8580 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8580);
+	wm8580->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8580, wm8580_dai, ARRAY_SIZE(wm8580_dai));
+	if (ret < 0)
+		kfree(wm8580);
+	return ret;
+}
+
+static int wm8580_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8580_i2c_id[] = {
+	{ "wm8580", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8580_i2c_id);
+
+static struct i2c_driver wm8580_i2c_driver = {
+	.driver = {
+		.name = "wm8580-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8580_i2c_probe,
+	.remove =   wm8580_i2c_remove,
+	.id_table = wm8580_i2c_id,
+};
+#endif
+
+static int __init wm8580_modinit(void)
+{
+	int ret = 0;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8580_i2c_driver);
+	if (ret != 0) {
+		pr_err("Failed to register WM8580 I2C driver: %d\n", ret);
+	}
+#endif
+
+	return ret;
+}
+module_init(wm8580_modinit);
+
+static void __exit wm8580_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8580_i2c_driver);
+#endif
+}
+module_exit(wm8580_exit);
+
+MODULE_DESCRIPTION("ASoC WM8580 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8580.h b/linux/sound/soc/codecs/wm8580.h
new file mode 100644
index 0000000..1d34656
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8580.h
@@ -0,0 +1,35 @@
+/*
+ * wm8580.h  --  audio driver for WM8580
+ *
+ * Copyright 2008 Samsung Electronics.
+ * Author: Ryu Euiyoul
+ *         ryu.real@gmail.com
+ *
+ *  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.
+ *
+ */
+
+#ifndef _WM8580_H
+#define _WM8580_H
+
+#define WM8580_PLLA  1
+#define WM8580_PLLB  2
+
+#define WM8580_MCLK       1
+#define WM8580_CLKOUTSRC  2
+
+#define WM8580_CLKSRC_MCLK    1
+#define WM8580_CLKSRC_PLLA    2
+#define WM8580_CLKSRC_PLLB    3
+#define WM8580_CLKSRC_OSC     4
+#define WM8580_CLKSRC_NONE    5
+#define WM8580_CLKSRC_ADCMCLK 6
+
+#define WM8580_DAI_PAIFRX 0
+#define WM8580_DAI_PAIFTX 1
+
+#endif
+
diff --git a/linux/sound/soc/codecs/wm8711.c b/linux/sound/soc/codecs/wm8711.c
new file mode 100644
index 0000000..54fbd76
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8711.c
@@ -0,0 +1,542 @@
+/*
+ * wm8711.c  --  WM8711 ALSA SoC Audio driver
+ *
+ * Copyright 2006 Wolfson Microelectronics
+ *
+ * Author: Mike Arthur <linux@wolfsonmicro.com>
+ *
+ * Based on wm8731.c by Richard Purdie
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+
+#include "wm8711.h"
+
+/* codec private data */
+struct wm8711_priv {
+	enum snd_soc_control_type bus_type;
+	u16 reg_cache[WM8711_CACHEREGNUM];
+	unsigned int sysclk;
+};
+
+/*
+ * wm8711 register cache
+ * We can't read the WM8711 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u16 wm8711_reg[WM8711_CACHEREGNUM] = {
+	0x0079, 0x0079, 0x000a, 0x0008,
+	0x009f, 0x000a, 0x0000, 0x0000
+};
+
+#define wm8711_reset(c)	snd_soc_write(c, WM8711_RESET, 0)
+
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+
+static const struct snd_kcontrol_new wm8711_snd_controls[] = {
+
+SOC_DOUBLE_R_TLV("Master Playback Volume", WM8711_LOUT1V, WM8711_ROUT1V,
+		 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Master Playback ZC Switch", WM8711_LOUT1V, WM8711_ROUT1V,
+	7, 1, 0),
+
+};
+
+/* Output Mixer */
+static const struct snd_kcontrol_new wm8711_output_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8711_APANA, 3, 1, 0),
+SOC_DAPM_SINGLE("HiFi Playback Switch", WM8711_APANA, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8711_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Output Mixer", WM8711_PWR, 4, 1,
+	&wm8711_output_mixer_controls[0],
+	ARRAY_SIZE(wm8711_output_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8711_PWR, 3, 1),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("LHPOUT"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("RHPOUT"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+	/* output mixer */
+	{"Output Mixer", "Line Bypass Switch", "Line Input"},
+	{"Output Mixer", "HiFi Playback Switch", "DAC"},
+
+	/* outputs */
+	{"RHPOUT", NULL, "Output Mixer"},
+	{"ROUT", NULL, "Output Mixer"},
+	{"LHPOUT", NULL, "Output Mixer"},
+	{"LOUT", NULL, "Output Mixer"},
+};
+
+static int wm8711_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8711_dapm_widgets,
+				  ARRAY_SIZE(wm8711_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	return 0;
+}
+
+struct _coeff_div {
+	u32 mclk;
+	u32 rate;
+	u16 fs;
+	u8 sr:4;
+	u8 bosr:1;
+	u8 usb:1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+	/* 48k */
+	{12288000, 48000, 256, 0x0, 0x0, 0x0},
+	{18432000, 48000, 384, 0x0, 0x1, 0x0},
+	{12000000, 48000, 250, 0x0, 0x0, 0x1},
+
+	/* 32k */
+	{12288000, 32000, 384, 0x6, 0x0, 0x0},
+	{18432000, 32000, 576, 0x6, 0x1, 0x0},
+	{12000000, 32000, 375, 0x6, 0x0, 0x1},
+
+	/* 8k */
+	{12288000, 8000, 1536, 0x3, 0x0, 0x0},
+	{18432000, 8000, 2304, 0x3, 0x1, 0x0},
+	{11289600, 8000, 1408, 0xb, 0x0, 0x0},
+	{16934400, 8000, 2112, 0xb, 0x1, 0x0},
+	{12000000, 8000, 1500, 0x3, 0x0, 0x1},
+
+	/* 96k */
+	{12288000, 96000, 128, 0x7, 0x0, 0x0},
+	{18432000, 96000, 192, 0x7, 0x1, 0x0},
+	{12000000, 96000, 125, 0x7, 0x0, 0x1},
+
+	/* 44.1k */
+	{11289600, 44100, 256, 0x8, 0x0, 0x0},
+	{16934400, 44100, 384, 0x8, 0x1, 0x0},
+	{12000000, 44100, 272, 0x8, 0x1, 0x1},
+
+	/* 88.2k */
+	{11289600, 88200, 128, 0xf, 0x0, 0x0},
+	{16934400, 88200, 192, 0xf, 0x1, 0x0},
+	{12000000, 88200, 136, 0xf, 0x1, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+		if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+			return i;
+	}
+	return 0;
+}
+
+static int wm8711_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8711_priv *wm8711 =  snd_soc_codec_get_drvdata(codec);
+	u16 iface = snd_soc_read(codec, WM8711_IFACE) & 0xfffc;
+	int i = get_coeff(wm8711->sysclk, params_rate(params));
+	u16 srate = (coeff_div[i].sr << 2) |
+		(coeff_div[i].bosr << 1) | coeff_div[i].usb;
+
+	snd_soc_write(codec, WM8711_SRATE, srate);
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x0004;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x0008;
+		break;
+	}
+
+	snd_soc_write(codec, WM8711_IFACE, iface);
+	return 0;
+}
+
+static int wm8711_pcm_prepare(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+
+	/* set active */
+	snd_soc_write(codec, WM8711_ACTIVE, 0x0001);
+
+	return 0;
+}
+
+static void wm8711_shutdown(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+
+	/* deactivate */
+	if (!codec->active) {
+		udelay(50);
+		snd_soc_write(codec, WM8711_ACTIVE, 0x0);
+	}
+}
+
+static int wm8711_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = snd_soc_read(codec, WM8711_APDIGI) & 0xfff7;
+
+	if (mute)
+		snd_soc_write(codec, WM8711_APDIGI, mute_reg | 0x8);
+	else
+		snd_soc_write(codec, WM8711_APDIGI, mute_reg);
+
+	return 0;
+}
+
+static int wm8711_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8711_priv *wm8711 =  snd_soc_codec_get_drvdata(codec);
+
+	switch (freq) {
+	case 11289600:
+	case 12000000:
+	case 12288000:
+	case 16934400:
+	case 18432000:
+		wm8711->sysclk = freq;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int wm8711_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = 0;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		iface |= 0x0040;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x0003;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface |= 0x0013;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x0090;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x0080;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x0010;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* set iface */
+	snd_soc_write(codec, WM8711_IFACE, iface);
+	return 0;
+}
+
+
+static int wm8711_set_bias_level(struct snd_soc_codec *codec,
+	enum snd_soc_bias_level level)
+{
+	u16 reg = snd_soc_read(codec, WM8711_PWR) & 0xff7f;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		snd_soc_write(codec, WM8711_PWR, reg);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		snd_soc_write(codec, WM8711_PWR, reg | 0x0040);
+		break;
+	case SND_SOC_BIAS_OFF:
+		snd_soc_write(codec, WM8711_ACTIVE, 0x0);
+		snd_soc_write(codec, WM8711_PWR, 0xffff);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8711_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8711_ops = {
+	.prepare = wm8711_pcm_prepare,
+	.hw_params = wm8711_hw_params,
+	.shutdown = wm8711_shutdown,
+	.digital_mute = wm8711_mute,
+	.set_sysclk = wm8711_set_dai_sysclk,
+	.set_fmt = wm8711_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver wm8711_dai = {
+	.name = "wm8711-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8711_RATES,
+		.formats = WM8711_FORMATS,
+	},
+	.ops = &wm8711_ops,
+};
+
+static int wm8711_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	snd_soc_write(codec, WM8711_ACTIVE, 0x0);
+	wm8711_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8711_resume(struct snd_soc_codec *codec)
+{
+	int i;
+	u8 data[2];
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 0; i < ARRAY_SIZE(wm8711_reg); i++) {
+		data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+		data[1] = cache[i] & 0x00ff;
+		codec->hw_write(codec->control_data, data, 2);
+	}
+	wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static int wm8711_probe(struct snd_soc_codec *codec)
+{
+	struct wm8711_priv *wm8711 = snd_soc_codec_get_drvdata(codec);
+	int ret, reg;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8711->bus_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm8711_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		return ret;
+	}
+
+	wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Latch the update bits */
+	reg = snd_soc_read(codec, WM8711_LOUT1V);
+	snd_soc_write(codec, WM8711_LOUT1V, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8711_ROUT1V);
+	snd_soc_write(codec, WM8711_ROUT1V, reg | 0x0100);
+
+	snd_soc_add_controls(codec, wm8711_snd_controls,
+			     ARRAY_SIZE(wm8711_snd_controls));
+	wm8711_add_widgets(codec);
+
+	return ret;
+
+}
+
+/* power down chip */
+static int wm8711_remove(struct snd_soc_codec *codec)
+{
+	wm8711_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8711 = {
+	.probe =	wm8711_probe,
+	.remove =	wm8711_remove,
+	.suspend =	wm8711_suspend,
+	.resume =	wm8711_resume,
+	.set_bias_level = wm8711_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8711_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8711_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8711_spi_probe(struct spi_device *spi)
+{
+	struct wm8711_priv *wm8711;
+	int ret;
+
+	wm8711 = kzalloc(sizeof(struct wm8711_priv), GFP_KERNEL);
+	if (wm8711 == NULL)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, wm8711);
+	wm8711->bus_type = SND_SOC_SPI;
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_dev_wm8711, &wm8711_dai, 1);
+	if (ret < 0)
+		kfree(wm8711);
+	return ret;
+}
+
+static int __devexit wm8711_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver wm8711_spi_driver = {
+	.driver = {
+		.name	= "wm8711-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= wm8711_spi_probe,
+	.remove		= __devexit_p(wm8711_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8711_i2c_probe(struct i2c_client *client,
+				      const struct i2c_device_id *id)
+{
+	struct wm8711_priv *wm8711;
+	int ret;
+
+	wm8711 = kzalloc(sizeof(struct wm8711_priv), GFP_KERNEL);
+	if (wm8711 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, wm8711);
+	wm8711->bus_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&client->dev,
+			&soc_codec_dev_wm8711, &wm8711_dai, 1);
+	if (ret < 0)
+		kfree(wm8711);
+	return ret;
+}
+
+static __devexit int wm8711_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8711_i2c_id[] = {
+	{ "wm8711", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8711_i2c_id);
+
+static struct i2c_driver wm8711_i2c_driver = {
+	.driver = {
+		.name = "wm8711-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8711_i2c_probe,
+	.remove =   __devexit_p(wm8711_i2c_remove),
+	.id_table = wm8711_i2c_id,
+};
+#endif
+
+static int __init wm8711_modinit(void)
+{
+	int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8711_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8711 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&wm8711_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8711 SPI driver: %d\n",
+		       ret);
+	}
+#endif
+	return 0;
+}
+module_init(wm8711_modinit);
+
+static void __exit wm8711_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8711_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&wm8711_spi_driver);
+#endif
+}
+module_exit(wm8711_exit);
+
+MODULE_DESCRIPTION("ASoC WM8711 driver");
+MODULE_AUTHOR("Mike Arthur");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8711.h b/linux/sound/soc/codecs/wm8711.h
new file mode 100644
index 0000000..a61db98
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8711.h
@@ -0,0 +1,39 @@
+/*
+ * wm8711.h  --  WM8711 Soc Audio driver
+ *
+ * Copyright 2006 Wolfson Microelectronics
+ *
+ * Author: Mike Arthur <linux@wolfsonmicro.com>
+ *
+ * Based on wm8731.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8711_H
+#define _WM8711_H
+
+/* WM8711 register space */
+
+#define WM8711_LOUT1V   0x02
+#define WM8711_ROUT1V   0x03
+#define WM8711_APANA    0x04
+#define WM8711_APDIGI   0x05
+#define WM8711_PWR      0x06
+#define WM8711_IFACE    0x07
+#define WM8711_SRATE    0x08
+#define WM8711_ACTIVE   0x09
+#define WM8711_RESET	0x0f
+
+#define WM8711_CACHEREGNUM 	8
+
+#define WM8711_SYSCLK	0
+#define WM8711_DAI		0
+
+struct wm8711_setup_data {
+	unsigned short i2c_address;
+};
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8727.c b/linux/sound/soc/codecs/wm8727.c
new file mode 100644
index 0000000..7488082
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8727.c
@@ -0,0 +1,84 @@
+/*
+ * wm8727.c
+ *
+ *  Created on: 15-Oct-2009
+ *      Author: neil.jones@imgtec.com
+ *
+ * Copyright (C) 2009 Imagination Technologies Ltd.
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+/*
+ * Note this is a simple chip with no configuration interface, sample rate is
+ * determined automatically by examining the Master clock and Bit clock ratios
+ */
+#define WM8727_RATES  (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+			SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |\
+			SNDRV_PCM_RATE_192000)
+
+
+static struct snd_soc_dai_driver wm8727_dai = {
+	.name = "wm8727-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = WM8727_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+		},
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8727;
+
+static __devinit int wm8727_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_wm8727, &wm8727_dai, 1);
+}
+
+static int __devexit wm8727_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver wm8727_codec_driver = {
+	.driver = {
+			.name = "wm8727-codec",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = wm8727_probe,
+	.remove = __devexit_p(wm8727_remove),
+};
+
+static int __init wm8727_init(void)
+{
+	return platform_driver_register(&wm8727_codec_driver);
+}
+module_init(wm8727_init);
+
+static void __exit wm8727_exit(void)
+{
+	platform_driver_unregister(&wm8727_codec_driver);
+}
+module_exit(wm8727_exit);
+
+MODULE_DESCRIPTION("ASoC wm8727 driver");
+MODULE_AUTHOR("Neil Jones");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8728.c b/linux/sound/soc/codecs/wm8728.c
new file mode 100644
index 0000000..075f35e
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8728.c
@@ -0,0 +1,396 @@
+/*
+ * wm8728.c  --  WM8728 ALSA SoC Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8728.h"
+
+/*
+ * We can't read the WM8728 register space so we cache them instead.
+ * Note that the defaults here aren't the physical defaults, we latch
+ * the volume update bits, mute the output and enable infinite zero
+ * detect.
+ */
+static const u16 wm8728_reg_defaults[] = {
+	0x1ff,
+	0x1ff,
+	0x001,
+	0x100,
+};
+
+/* codec private data */
+struct wm8728_priv {
+	enum snd_soc_control_type control_type;
+};
+
+static const DECLARE_TLV_DB_SCALE(wm8728_tlv, -12750, 50, 1);
+
+static const struct snd_kcontrol_new wm8728_snd_controls[] = {
+
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8728_DACLVOL, WM8728_DACRVOL,
+		 0, 255, 0, wm8728_tlv),
+
+SOC_SINGLE("Deemphasis", WM8728_DACCTL, 1, 1, 0),
+};
+
+/*
+ * DAPM controls.
+ */
+static const struct snd_soc_dapm_widget wm8728_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_OUTPUT("VOUTL"),
+SND_SOC_DAPM_OUTPUT("VOUTR"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+	{"VOUTL", NULL, "DAC"},
+	{"VOUTR", NULL, "DAC"},
+};
+
+static int wm8728_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8728_dapm_widgets,
+				  ARRAY_SIZE(wm8728_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	return 0;
+}
+
+static int wm8728_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = snd_soc_read(codec, WM8728_DACCTL);
+
+	if (mute)
+		snd_soc_write(codec, WM8728_DACCTL, mute_reg | 1);
+	else
+		snd_soc_write(codec, WM8728_DACCTL, mute_reg & ~1);
+
+	return 0;
+}
+
+static int wm8728_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	u16 dac = snd_soc_read(codec, WM8728_DACCTL);
+
+	dac &= ~0x18;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		dac |= 0x10;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		dac |= 0x08;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8728_DACCTL, dac);
+
+	return 0;
+}
+
+static int wm8728_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = snd_soc_read(codec, WM8728_IFCTL);
+
+	/* Currently only I2S is supported by the driver, though the
+	 * hardware is more flexible.
+	 */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* The hardware only support full slave mode */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		iface &= ~0x22;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |=  0x20;
+		iface &= ~0x02;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x02;
+		iface &= ~0x20;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x22;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8728_IFCTL, iface);
+	return 0;
+}
+
+static int wm8728_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 reg;
+	int i;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Power everything up... */
+			reg = snd_soc_read(codec, WM8728_DACCTL);
+			snd_soc_write(codec, WM8728_DACCTL, reg & ~0x4);
+
+			/* ..then sync in the register cache. */
+			for (i = 0; i < ARRAY_SIZE(wm8728_reg_defaults); i++)
+				snd_soc_write(codec, i,
+					     snd_soc_read(codec, i));
+		}
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		reg = snd_soc_read(codec, WM8728_DACCTL);
+		snd_soc_write(codec, WM8728_DACCTL, reg | 0x4);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8728_RATES (SNDRV_PCM_RATE_8000_192000)
+
+#define WM8728_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8728_dai_ops = {
+	.hw_params	= wm8728_hw_params,
+	.digital_mute	= wm8728_mute,
+	.set_fmt	= wm8728_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver wm8728_dai = {
+	.name = "wm8728-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = WM8728_RATES,
+		.formats = WM8728_FORMATS,
+	},
+	.ops = &wm8728_dai_ops,
+};
+
+static int wm8728_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm8728_resume(struct snd_soc_codec *codec)
+{
+	wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static int wm8728_probe(struct snd_soc_codec *codec)
+{
+	struct wm8728_priv *wm8728 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8728->control_type);
+	if (ret < 0) {
+		printk(KERN_ERR "wm8728: failed to configure cache I/O: %d\n",
+		       ret);
+		return ret;
+	}
+
+	/* power on device */
+	wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	snd_soc_add_controls(codec, wm8728_snd_controls,
+				ARRAY_SIZE(wm8728_snd_controls));
+	wm8728_add_widgets(codec);
+
+	return ret;
+}
+
+static int wm8728_remove(struct snd_soc_codec *codec)
+{
+	wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8728 = {
+	.probe =	wm8728_probe,
+	.remove =	wm8728_remove,
+	.suspend =	wm8728_suspend,
+	.resume =	wm8728_resume,
+	.set_bias_level = wm8728_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8728_reg_defaults),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8728_reg_defaults,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8728_spi_probe(struct spi_device *spi)
+{
+	struct wm8728_priv *wm8728;
+	int ret;
+
+	wm8728 = kzalloc(sizeof(struct wm8728_priv), GFP_KERNEL);
+	if (wm8728 == NULL)
+		return -ENOMEM;
+
+	wm8728->control_type = SND_SOC_SPI;
+	spi_set_drvdata(spi, wm8728);
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_dev_wm8728, &wm8728_dai, 1);
+	if (ret < 0)
+		kfree(wm8728);
+	return ret;
+}
+
+static int __devexit wm8728_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver wm8728_spi_driver = {
+	.driver = {
+		.name	= "wm8728-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= wm8728_spi_probe,
+	.remove		= __devexit_p(wm8728_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8728_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8728_priv *wm8728;
+	int ret;
+
+	wm8728 = kzalloc(sizeof(struct wm8728_priv), GFP_KERNEL);
+	if (wm8728 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8728);
+	wm8728->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8728, &wm8728_dai, 1);
+	if (ret < 0)
+		kfree(wm8728);
+	return ret;
+}
+
+static __devexit int wm8728_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8728_i2c_id[] = {
+	{ "wm8728", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8728_i2c_id);
+
+static struct i2c_driver wm8728_i2c_driver = {
+	.driver = {
+		.name = "wm8728-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8728_i2c_probe,
+	.remove =   __devexit_p(wm8728_i2c_remove),
+	.id_table = wm8728_i2c_id,
+};
+#endif
+
+static int __init wm8728_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8728_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8728 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&wm8728_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8728 SPI driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8728_modinit);
+
+static void __exit wm8728_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8728_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&wm8728_spi_driver);
+#endif
+}
+module_exit(wm8728_exit);
+
+MODULE_DESCRIPTION("ASoC WM8728 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8728.h b/linux/sound/soc/codecs/wm8728.h
new file mode 100644
index 0000000..8aea362
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8728.h
@@ -0,0 +1,21 @@
+/*
+ * wm8728.h  --  WM8728 ASoC codec driver
+ *
+ * Copyright 2008 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8728_H
+#define _WM8728_H
+
+#define WM8728_DACLVOL   0x00
+#define WM8728_DACRVOL   0x01
+#define WM8728_DACCTL    0x02
+#define WM8728_IFCTL     0x03
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8731.c b/linux/sound/soc/codecs/wm8731.c
new file mode 100644
index 0000000..e725c09
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8731.c
@@ -0,0 +1,689 @@
+/*
+ * wm8731.c  --  WM8731 ALSA SoC Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8731.h"
+
+#define WM8731_NUM_SUPPLIES 4
+static const char *wm8731_supply_names[WM8731_NUM_SUPPLIES] = {
+	"AVDD",
+	"HPVDD",
+	"DCVDD",
+	"DBVDD",
+};
+
+/* codec private data */
+struct wm8731_priv {
+	enum snd_soc_control_type control_type;
+	struct regulator_bulk_data supplies[WM8731_NUM_SUPPLIES];
+	u16 reg_cache[WM8731_CACHEREGNUM];
+	unsigned int sysclk;
+	int sysclk_type;
+};
+
+
+/*
+ * wm8731 register cache
+ * We can't read the WM8731 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ * There is no point in caching the reset register
+ */
+static const u16 wm8731_reg[WM8731_CACHEREGNUM] = {
+	0x0097, 0x0097, 0x0079, 0x0079,
+	0x000a, 0x0008, 0x009f, 0x000a,
+	0x0000, 0x0000
+};
+
+#define wm8731_reset(c)	snd_soc_write(c, WM8731_RESET, 0)
+
+static const char *wm8731_input_select[] = {"Line In", "Mic"};
+static const char *wm8731_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+
+static const struct soc_enum wm8731_enum[] = {
+	SOC_ENUM_SINGLE(WM8731_APANA, 2, 2, wm8731_input_select),
+	SOC_ENUM_SINGLE(WM8731_APDIGI, 1, 4, wm8731_deemph),
+};
+
+static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+
+static const struct snd_kcontrol_new wm8731_snd_controls[] = {
+
+SOC_DOUBLE_R_TLV("Master Playback Volume", WM8731_LOUT1V, WM8731_ROUT1V,
+		 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Master Playback ZC Switch", WM8731_LOUT1V, WM8731_ROUT1V,
+	7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0,
+		 in_tlv),
+SOC_DOUBLE_R("Line Capture Switch", WM8731_LINVOL, WM8731_RINVOL, 7, 1, 1),
+
+SOC_SINGLE("Mic Boost (+20dB)", WM8731_APANA, 0, 1, 0),
+SOC_SINGLE("Mic Capture Switch", WM8731_APANA, 1, 1, 1),
+
+SOC_SINGLE_TLV("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1,
+	       sidetone_tlv),
+
+SOC_SINGLE("ADC High Pass Filter Switch", WM8731_APDIGI, 0, 1, 1),
+SOC_SINGLE("Store DC Offset Switch", WM8731_APDIGI, 4, 1, 0),
+
+SOC_ENUM("Playback De-emphasis", wm8731_enum[1]),
+};
+
+/* Output Mixer */
+static const struct snd_kcontrol_new wm8731_output_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8731_APANA, 3, 1, 0),
+SOC_DAPM_SINGLE("Mic Sidetone Switch", WM8731_APANA, 5, 1, 0),
+SOC_DAPM_SINGLE("HiFi Playback Switch", WM8731_APANA, 4, 1, 0),
+};
+
+/* Input mux */
+static const struct snd_kcontrol_new wm8731_input_mux_controls =
+SOC_DAPM_ENUM("Input Select", wm8731_enum[0]);
+
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("OSC", WM8731_PWR, 5, 1, NULL, 0),
+SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1,
+	&wm8731_output_mixer_controls[0],
+	ARRAY_SIZE(wm8731_output_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8731_PWR, 3, 1),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("LHPOUT"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("RHPOUT"),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8731_PWR, 2, 1),
+SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &wm8731_input_mux_controls),
+SND_SOC_DAPM_PGA("Line Input", WM8731_PWR, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8731_PWR, 1, 1),
+SND_SOC_DAPM_INPUT("MICIN"),
+SND_SOC_DAPM_INPUT("RLINEIN"),
+SND_SOC_DAPM_INPUT("LLINEIN"),
+};
+
+static int wm8731_check_osc(struct snd_soc_dapm_widget *source,
+			    struct snd_soc_dapm_widget *sink)
+{
+	struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(source->codec);
+
+	return wm8731->sysclk_type == WM8731_SYSCLK_MCLK;
+}
+
+static const struct snd_soc_dapm_route intercon[] = {
+	{"DAC", NULL, "OSC", wm8731_check_osc},
+	{"ADC", NULL, "OSC", wm8731_check_osc},
+
+	/* output mixer */
+	{"Output Mixer", "Line Bypass Switch", "Line Input"},
+	{"Output Mixer", "HiFi Playback Switch", "DAC"},
+	{"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
+
+	/* outputs */
+	{"RHPOUT", NULL, "Output Mixer"},
+	{"ROUT", NULL, "Output Mixer"},
+	{"LHPOUT", NULL, "Output Mixer"},
+	{"LOUT", NULL, "Output Mixer"},
+
+	/* input mux */
+	{"Input Mux", "Line In", "Line Input"},
+	{"Input Mux", "Mic", "Mic Bias"},
+	{"ADC", NULL, "Input Mux"},
+
+	/* inputs */
+	{"Line Input", NULL, "LLINEIN"},
+	{"Line Input", NULL, "RLINEIN"},
+	{"Mic Bias", NULL, "MICIN"},
+};
+
+static int wm8731_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets,
+				  ARRAY_SIZE(wm8731_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	return 0;
+}
+
+struct _coeff_div {
+	u32 mclk;
+	u32 rate;
+	u16 fs;
+	u8 sr:4;
+	u8 bosr:1;
+	u8 usb:1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+	/* 48k */
+	{12288000, 48000, 256, 0x0, 0x0, 0x0},
+	{18432000, 48000, 384, 0x0, 0x1, 0x0},
+	{12000000, 48000, 250, 0x0, 0x0, 0x1},
+
+	/* 32k */
+	{12288000, 32000, 384, 0x6, 0x0, 0x0},
+	{18432000, 32000, 576, 0x6, 0x1, 0x0},
+	{12000000, 32000, 375, 0x6, 0x0, 0x1},
+
+	/* 8k */
+	{12288000, 8000, 1536, 0x3, 0x0, 0x0},
+	{18432000, 8000, 2304, 0x3, 0x1, 0x0},
+	{11289600, 8000, 1408, 0xb, 0x0, 0x0},
+	{16934400, 8000, 2112, 0xb, 0x1, 0x0},
+	{12000000, 8000, 1500, 0x3, 0x0, 0x1},
+
+	/* 96k */
+	{12288000, 96000, 128, 0x7, 0x0, 0x0},
+	{18432000, 96000, 192, 0x7, 0x1, 0x0},
+	{12000000, 96000, 125, 0x7, 0x0, 0x1},
+
+	/* 44.1k */
+	{11289600, 44100, 256, 0x8, 0x0, 0x0},
+	{16934400, 44100, 384, 0x8, 0x1, 0x0},
+	{12000000, 44100, 272, 0x8, 0x1, 0x1},
+
+	/* 88.2k */
+	{11289600, 88200, 128, 0xf, 0x0, 0x0},
+	{16934400, 88200, 192, 0xf, 0x1, 0x0},
+	{12000000, 88200, 136, 0xf, 0x1, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+		if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+			return i;
+	}
+	return 0;
+}
+
+static int wm8731_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+	u16 iface = snd_soc_read(codec, WM8731_IFACE) & 0xfff3;
+	int i = get_coeff(wm8731->sysclk, params_rate(params));
+	u16 srate = (coeff_div[i].sr << 2) |
+		(coeff_div[i].bosr << 1) | coeff_div[i].usb;
+
+	snd_soc_write(codec, WM8731_SRATE, srate);
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x0004;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x0008;
+		break;
+	}
+
+	snd_soc_write(codec, WM8731_IFACE, iface);
+	return 0;
+}
+
+static int wm8731_pcm_prepare(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+
+	/* set active */
+	snd_soc_write(codec, WM8731_ACTIVE, 0x0001);
+
+	return 0;
+}
+
+static void wm8731_shutdown(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+
+	/* deactivate */
+	if (!codec->active) {
+		udelay(50);
+		snd_soc_write(codec, WM8731_ACTIVE, 0x0);
+	}
+}
+
+static int wm8731_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = snd_soc_read(codec, WM8731_APDIGI) & 0xfff7;
+
+	if (mute)
+		snd_soc_write(codec, WM8731_APDIGI, mute_reg | 0x8);
+	else
+		snd_soc_write(codec, WM8731_APDIGI, mute_reg);
+	return 0;
+}
+
+static int wm8731_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+
+	switch (clk_id) {
+	case WM8731_SYSCLK_XTAL:
+	case WM8731_SYSCLK_MCLK:
+		wm8731->sysclk_type = clk_id;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (freq) {
+	case 11289600:
+	case 12000000:
+	case 12288000:
+	case 16934400:
+	case 18432000:
+		wm8731->sysclk = freq;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+
+static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = 0;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		iface |= 0x0040;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x0003;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface |= 0x0013;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x0090;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x0080;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x0010;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* set iface */
+	snd_soc_write(codec, WM8731_IFACE, iface);
+	return 0;
+}
+
+static int wm8731_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+	int i, ret;
+	u8 data[2];
+	u16 *cache = codec->reg_cache;
+	u16 reg;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies),
+						    wm8731->supplies);
+			if (ret != 0)
+				return ret;
+
+			/* Sync reg_cache with the hardware */
+			for (i = 0; i < ARRAY_SIZE(wm8731_reg); i++) {
+				if (cache[i] == wm8731_reg[i])
+					continue;
+
+				data[0] = (i << 1) | ((cache[i] >> 8)
+						      & 0x0001);
+				data[1] = cache[i] & 0x00ff;
+				codec->hw_write(codec->control_data, data, 2);
+			}
+		}
+
+		/* Clear PWROFF, gate CLKOUT, everything else as-is */
+		reg = snd_soc_read(codec, WM8731_PWR) & 0xff7f;
+		snd_soc_write(codec, WM8731_PWR, reg | 0x0040);
+		break;
+	case SND_SOC_BIAS_OFF:
+		snd_soc_write(codec, WM8731_ACTIVE, 0x0);
+		snd_soc_write(codec, WM8731_PWR, 0xffff);
+		regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies),
+				       wm8731->supplies);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8731_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8731_dai_ops = {
+	.prepare	= wm8731_pcm_prepare,
+	.hw_params	= wm8731_hw_params,
+	.shutdown	= wm8731_shutdown,
+	.digital_mute	= wm8731_mute,
+	.set_sysclk	= wm8731_set_dai_sysclk,
+	.set_fmt	= wm8731_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver wm8731_dai = {
+	.name = "wm8731-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8731_RATES,
+		.formats = WM8731_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8731_RATES,
+		.formats = WM8731_FORMATS,},
+	.ops = &wm8731_dai_ops,
+	.symmetric_rates = 1,
+};
+
+#ifdef CONFIG_PM
+static int wm8731_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm8731_resume(struct snd_soc_codec *codec)
+{
+	wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+#else
+#define wm8731_suspend NULL
+#define wm8731_resume NULL
+#endif
+
+static int wm8731_probe(struct snd_soc_codec *codec)
+{
+	struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0, i;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8731->control_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm8731->supplies); i++)
+		wm8731->supplies[i].supply = wm8731_supply_names[i];
+
+	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8731->supplies),
+				 wm8731->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+		return ret;
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies),
+				    wm8731->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+		goto err_regulator_get;
+	}
+
+	ret = wm8731_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+		goto err_regulator_enable;
+	}
+
+	wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Latch the update bits */
+	snd_soc_update_bits(codec, WM8731_LOUT1V, 0x100, 0);
+	snd_soc_update_bits(codec, WM8731_ROUT1V, 0x100, 0);
+	snd_soc_update_bits(codec, WM8731_LINVOL, 0x100, 0);
+	snd_soc_update_bits(codec, WM8731_RINVOL, 0x100, 0);
+
+	/* Disable bypass path by default */
+	snd_soc_update_bits(codec, WM8731_APANA, 0x8, 0);
+
+	snd_soc_add_controls(codec, wm8731_snd_controls,
+			     ARRAY_SIZE(wm8731_snd_controls));
+	wm8731_add_widgets(codec);
+
+	/* Regulators will have been enabled by bias management */
+	regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies);
+
+	return 0;
+
+err_regulator_enable:
+	regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies);
+err_regulator_get:
+	regulator_bulk_free(ARRAY_SIZE(wm8731->supplies), wm8731->supplies);
+
+	kfree(wm8731);
+	return ret;
+}
+
+/* power down chip */
+static int wm8731_remove(struct snd_soc_codec *codec)
+{
+	struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec);
+
+	wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies);
+	regulator_bulk_free(ARRAY_SIZE(wm8731->supplies), wm8731->supplies);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8731 = {
+	.probe =	wm8731_probe,
+	.remove =	wm8731_remove,
+	.suspend =	wm8731_suspend,
+	.resume =	wm8731_resume,
+	.set_bias_level = wm8731_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8731_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8731_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8731_spi_probe(struct spi_device *spi)
+{
+	struct wm8731_priv *wm8731;
+	int ret;
+
+	wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL);
+	if (wm8731 == NULL)
+		return -ENOMEM;
+
+	wm8731->control_type = SND_SOC_SPI;
+	spi_set_drvdata(spi, wm8731);
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_dev_wm8731, &wm8731_dai, 1);
+	if (ret < 0)
+		kfree(wm8731);
+	return ret;
+}
+
+static int __devexit wm8731_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver wm8731_spi_driver = {
+	.driver = {
+		.name	= "wm8731-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= wm8731_spi_probe,
+	.remove		= __devexit_p(wm8731_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8731_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8731_priv *wm8731;
+	int ret;
+
+	wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL);
+	if (wm8731 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8731);
+	wm8731->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8731, &wm8731_dai, 1);
+	if (ret < 0)
+		kfree(wm8731);
+	return ret;
+}
+
+static __devexit int wm8731_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8731_i2c_id[] = {
+	{ "wm8731", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id);
+
+static struct i2c_driver wm8731_i2c_driver = {
+	.driver = {
+		.name = "wm8731-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8731_i2c_probe,
+	.remove =   __devexit_p(wm8731_i2c_remove),
+	.id_table = wm8731_i2c_id,
+};
+#endif
+
+static int __init wm8731_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8731_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8731 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&wm8731_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8731 SPI driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8731_modinit);
+
+static void __exit wm8731_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8731_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&wm8731_spi_driver);
+#endif
+}
+module_exit(wm8731_exit);
+
+MODULE_DESCRIPTION("ASoC WM8731 driver");
+MODULE_AUTHOR("Richard Purdie");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8731.h b/linux/sound/soc/codecs/wm8731.h
new file mode 100644
index 0000000..e9c0c76
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8731.h
@@ -0,0 +1,39 @@
+/*
+ * wm8731.h  --  WM8731 Soc Audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8731_H
+#define _WM8731_H
+
+/* WM8731 register space */
+
+#define WM8731_LINVOL   0x00
+#define WM8731_RINVOL   0x01
+#define WM8731_LOUT1V   0x02
+#define WM8731_ROUT1V   0x03
+#define WM8731_APANA    0x04
+#define WM8731_APDIGI   0x05
+#define WM8731_PWR      0x06
+#define WM8731_IFACE    0x07
+#define WM8731_SRATE    0x08
+#define WM8731_ACTIVE   0x09
+#define WM8731_RESET	0x0f
+
+#define WM8731_CACHEREGNUM 	10
+
+#define WM8731_SYSCLK_XTAL 1
+#define WM8731_SYSCLK_MCLK 2
+
+#define WM8731_DAI		0
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8741.c b/linux/sound/soc/codecs/wm8741.c
new file mode 100644
index 0000000..aea60ef
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8741.c
@@ -0,0 +1,559 @@
+/*
+ * wm8741.c  --  WM8741 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Ian Lartey <ian@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8741.h"
+
+#define WM8741_NUM_SUPPLIES 2
+static const char *wm8741_supply_names[WM8741_NUM_SUPPLIES] = {
+	"AVDD",
+	"DVDD",
+};
+
+#define WM8741_NUM_RATES 6
+
+/* codec private data */
+struct wm8741_priv {
+	enum snd_soc_control_type control_type;
+	struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES];
+	unsigned int sysclk;
+	struct snd_pcm_hw_constraint_list *sysclk_constraints;
+};
+
+static const u16 wm8741_reg_defaults[WM8741_REGISTER_COUNT] = {
+	0x0000,     /* R0  - DACLLSB Attenuation */
+	0x0000,     /* R1  - DACLMSB Attenuation */
+	0x0000,     /* R2  - DACRLSB Attenuation */
+	0x0000,     /* R3  - DACRMSB Attenuation */
+	0x0000,     /* R4  - Volume Control */
+	0x000A,     /* R5  - Format Control */
+	0x0000,     /* R6  - Filter Control */
+	0x0000,     /* R7  - Mode Control 1 */
+	0x0002,     /* R8  - Mode Control 2 */
+	0x0000,	    /* R9  - Reset */
+	0x0002,     /* R32 - ADDITONAL_CONTROL_1 */
+};
+
+
+static int wm8741_reset(struct snd_soc_codec *codec)
+{
+	return snd_soc_write(codec, WM8741_RESET, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv_fine, -12700, 13, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 400, 0);
+
+static const struct snd_kcontrol_new wm8741_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Fine Playback Volume", WM8741_DACLLSB_ATTENUATION,
+		 WM8741_DACRLSB_ATTENUATION, 1, 255, 1, dac_tlv_fine),
+SOC_DOUBLE_R_TLV("Playback Volume", WM8741_DACLMSB_ATTENUATION,
+		 WM8741_DACRMSB_ATTENUATION, 0, 511, 1, dac_tlv),
+};
+
+static const struct snd_soc_dapm_widget wm8741_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DACL", "Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_DAC("DACR", "Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_OUTPUT("VOUTLP"),
+SND_SOC_DAPM_OUTPUT("VOUTLN"),
+SND_SOC_DAPM_OUTPUT("VOUTRP"),
+SND_SOC_DAPM_OUTPUT("VOUTRN"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+	{ "VOUTLP", NULL, "DACL" },
+	{ "VOUTLN", NULL, "DACL" },
+	{ "VOUTRP", NULL, "DACR" },
+	{ "VOUTRN", NULL, "DACR" },
+};
+
+static int wm8741_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8741_dapm_widgets,
+				  ARRAY_SIZE(wm8741_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	return 0;
+}
+
+static struct {
+	int value;
+	int ratio;
+} lrclk_ratios[WM8741_NUM_RATES] = {
+	{ 1, 128 },
+	{ 2, 192 },
+	{ 3, 256 },
+	{ 4, 384 },
+	{ 5, 512 },
+	{ 6, 768 },
+};
+
+static unsigned int rates_11289[] = {
+	44100, 88235,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_11289 = {
+	.count	= ARRAY_SIZE(rates_11289),
+	.list	= rates_11289,
+};
+
+static unsigned int rates_12288[] = {
+	32000, 48000, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_12288 = {
+	.count	= ARRAY_SIZE(rates_12288),
+	.list	= rates_12288,
+};
+
+static unsigned int rates_16384[] = {
+	32000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_16384 = {
+	.count	= ARRAY_SIZE(rates_16384),
+	.list	= rates_16384,
+};
+
+static unsigned int rates_16934[] = {
+	44100, 88235,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_16934 = {
+	.count	= ARRAY_SIZE(rates_16934),
+	.list	= rates_16934,
+};
+
+static unsigned int rates_18432[] = {
+	48000, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_18432 = {
+	.count	= ARRAY_SIZE(rates_18432),
+	.list	= rates_18432,
+};
+
+static unsigned int rates_22579[] = {
+	44100, 88235, 1764000
+};
+
+static struct snd_pcm_hw_constraint_list constraints_22579 = {
+	.count	= ARRAY_SIZE(rates_22579),
+	.list	= rates_22579,
+};
+
+static unsigned int rates_24576[] = {
+	32000, 48000, 96000, 192000
+};
+
+static struct snd_pcm_hw_constraint_list constraints_24576 = {
+	.count	= ARRAY_SIZE(rates_24576),
+	.list	= rates_24576,
+};
+
+static unsigned int rates_36864[] = {
+	48000, 96000, 19200
+};
+
+static struct snd_pcm_hw_constraint_list constraints_36864 = {
+	.count	= ARRAY_SIZE(rates_36864),
+	.list	= rates_36864,
+};
+
+
+static int wm8741_startup(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+
+	/* The set of sample rates that can be supported depends on the
+	 * MCLK supplied to the CODEC - enforce this.
+	 */
+	if (!wm8741->sysclk) {
+		dev_err(codec->dev,
+			"No MCLK configured, call set_sysclk() on init\n");
+		return -EINVAL;
+	}
+
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				   SNDRV_PCM_HW_PARAM_RATE,
+				   wm8741->sysclk_constraints);
+
+	return 0;
+}
+
+static int wm8741_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+	u16 iface = snd_soc_read(codec, WM8741_FORMAT_CONTROL) & 0x1FC;
+	int i;
+
+	/* Find a supported LRCLK ratio */
+	for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
+		if (wm8741->sysclk / params_rate(params) ==
+		    lrclk_ratios[i].ratio)
+			break;
+	}
+
+	/* Should never happen, should be handled by constraints */
+	if (i == ARRAY_SIZE(lrclk_ratios)) {
+		dev_err(codec->dev, "MCLK/fs ratio %d unsupported\n",
+			wm8741->sysclk / params_rate(params));
+		return -EINVAL;
+	}
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x0001;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x0002;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface |= 0x0003;
+		break;
+	default:
+		dev_dbg(codec->dev, "wm8741_hw_params:    Unsupported bit size param = %d",
+			params_format(params));
+		return -EINVAL;
+	}
+
+	dev_dbg(codec->dev, "wm8741_hw_params:    bit size param = %d",
+		params_format(params));
+
+	snd_soc_write(codec, WM8741_FORMAT_CONTROL, iface);
+	return 0;
+}
+
+static int wm8741_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+
+	dev_dbg(codec->dev, "wm8741_set_dai_sysclk info: freq=%dHz\n", freq);
+
+	switch (freq) {
+	case 11289600:
+		wm8741->sysclk_constraints = &constraints_11289;
+		wm8741->sysclk = freq;
+		return 0;
+
+	case 12288000:
+		wm8741->sysclk_constraints = &constraints_12288;
+		wm8741->sysclk = freq;
+		return 0;
+
+	case 16384000:
+		wm8741->sysclk_constraints = &constraints_16384;
+		wm8741->sysclk = freq;
+		return 0;
+
+	case 16934400:
+		wm8741->sysclk_constraints = &constraints_16934;
+		wm8741->sysclk = freq;
+		return 0;
+
+	case 18432000:
+		wm8741->sysclk_constraints = &constraints_18432;
+		wm8741->sysclk = freq;
+		return 0;
+
+	case 22579200:
+	case 33868800:
+		wm8741->sysclk_constraints = &constraints_22579;
+		wm8741->sysclk = freq;
+		return 0;
+
+	case 24576000:
+		wm8741->sysclk_constraints = &constraints_24576;
+		wm8741->sysclk = freq;
+		return 0;
+
+	case 36864000:
+		wm8741->sysclk_constraints = &constraints_36864;
+		wm8741->sysclk = freq;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int wm8741_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = snd_soc_read(codec, WM8741_FORMAT_CONTROL) & 0x1C3;
+
+	/* check master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x0008;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x0004;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x0003;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface |= 0x0013;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x0010;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x0020;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x0030;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+
+	dev_dbg(codec->dev, "wm8741_set_dai_fmt:    Format=%x, Clock Inv=%x\n",
+				fmt & SND_SOC_DAIFMT_FORMAT_MASK,
+				((fmt & SND_SOC_DAIFMT_INV_MASK)));
+
+	snd_soc_write(codec, WM8741_FORMAT_CONTROL, iface);
+	return 0;
+}
+
+#define WM8741_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+			SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
+			SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \
+			SNDRV_PCM_RATE_192000)
+
+#define WM8741_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8741_dai_ops = {
+	.startup	= wm8741_startup,
+	.hw_params	= wm8741_hw_params,
+	.set_sysclk	= wm8741_set_dai_sysclk,
+	.set_fmt	= wm8741_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver wm8741_dai = {
+	.name = "wm8741",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,  /* Mono modes not yet supported */
+		.channels_max = 2,
+		.rates = WM8741_RATES,
+		.formats = WM8741_FORMATS,
+	},
+	.ops = &wm8741_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int wm8741_resume(struct snd_soc_codec *codec)
+{
+	u16 *cache = codec->reg_cache;
+	int i;
+
+	/* RESTORE REG Cache */
+	for (i = 0; i < WM8741_REGISTER_COUNT; i++) {
+		if (cache[i] == wm8741_reg_defaults[i] || WM8741_RESET == i)
+			continue;
+		snd_soc_write(codec, i, cache[i]);
+	}
+	return 0;
+}
+#else
+#define wm8741_suspend NULL
+#define wm8741_resume NULL
+#endif
+
+static int wm8741_probe(struct snd_soc_codec *codec)
+{
+	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+	u16 *reg_cache = codec->reg_cache;
+	int ret = 0;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8741->control_type);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm8741_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		return ret;
+	}
+
+	/* Change some default settings - latch VU */
+	reg_cache[WM8741_DACLLSB_ATTENUATION] |= WM8741_UPDATELL;
+	reg_cache[WM8741_DACLMSB_ATTENUATION] |= WM8741_UPDATELM;
+	reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERL;
+	reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERM;
+
+	snd_soc_add_controls(codec, wm8741_snd_controls,
+			     ARRAY_SIZE(wm8741_snd_controls));
+	wm8741_add_widgets(codec);
+
+	dev_dbg(codec->dev, "Successful registration\n");
+	return ret;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8741 = {
+	.probe =	wm8741_probe,
+	.resume =	wm8741_resume,
+	.reg_cache_size = ARRAY_SIZE(wm8741_reg_defaults),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = &wm8741_reg_defaults,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int wm8741_i2c_probe(struct i2c_client *i2c,
+			    const struct i2c_device_id *id)
+{
+	struct wm8741_priv *wm8741;
+	int ret, i;
+
+	wm8741 = kzalloc(sizeof(struct wm8741_priv), GFP_KERNEL);
+	if (wm8741 == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(wm8741->supplies); i++)
+		wm8741->supplies[i].supply = wm8741_supply_names[i];
+
+	ret = regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8741->supplies),
+				 wm8741->supplies);
+	if (ret != 0) {
+		dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret);
+		goto err;
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(wm8741->supplies),
+				    wm8741->supplies);
+	if (ret != 0) {
+		dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret);
+		goto err_get;
+	}
+
+	i2c_set_clientdata(i2c, wm8741);
+	wm8741->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8741, &wm8741_dai, 1);
+	if (ret < 0)
+		goto err_enable;
+	return ret;
+
+err_enable:
+	regulator_bulk_disable(ARRAY_SIZE(wm8741->supplies), wm8741->supplies);
+
+err_get:
+	regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies);
+err:
+	kfree(wm8741);
+	return ret;
+}
+
+static int wm8741_i2c_remove(struct i2c_client *client)
+{
+	struct wm8741_priv *wm8741 = i2c_get_clientdata(client);
+
+	snd_soc_unregister_codec(&client->dev);
+	regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8741_i2c_id[] = {
+	{ "wm8741", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8741_i2c_id);
+
+static struct i2c_driver wm8741_i2c_driver = {
+	.driver = {
+		.name = "wm8741-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8741_i2c_probe,
+	.remove =   wm8741_i2c_remove,
+	.id_table = wm8741_i2c_id,
+};
+#endif
+
+static int __init wm8741_modinit(void)
+{
+	int ret = 0;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8741_i2c_driver);
+	if (ret != 0)
+		pr_err("Failed to register WM8741 I2C driver: %d\n", ret);
+#endif
+
+	return ret;
+}
+module_init(wm8741_modinit);
+
+static void __exit wm8741_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8741_i2c_driver);
+#endif
+}
+module_exit(wm8741_exit);
+
+MODULE_DESCRIPTION("ASoC WM8741 driver");
+MODULE_AUTHOR("Ian Lartey <ian@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8741.h b/linux/sound/soc/codecs/wm8741.h
new file mode 100644
index 0000000..56c1b1d
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8741.h
@@ -0,0 +1,211 @@
+/*
+ * wm8741.h  --  WM8423 ASoC driver
+ *
+ * Copyright 2010 Wolfson Microelectronics, plc
+ *
+ * Author: Ian Lartey <ian@opensource.wolfsonmicro.com>
+ *
+ * Based on wm8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8741_H
+#define _WM8741_H
+
+/*
+ * Register values.
+ */
+#define WM8741_DACLLSB_ATTENUATION              0x00
+#define WM8741_DACLMSB_ATTENUATION              0x01
+#define WM8741_DACRLSB_ATTENUATION              0x02
+#define WM8741_DACRMSB_ATTENUATION              0x03
+#define WM8741_VOLUME_CONTROL                   0x04
+#define WM8741_FORMAT_CONTROL                   0x05
+#define WM8741_FILTER_CONTROL                   0x06
+#define WM8741_MODE_CONTROL_1                   0x07
+#define WM8741_MODE_CONTROL_2                   0x08
+#define WM8741_RESET                            0x09
+#define WM8741_ADDITIONAL_CONTROL_1             0x20
+
+#define WM8741_REGISTER_COUNT                   11
+#define WM8741_MAX_REGISTER                     0x20
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - DACLLSB_ATTENUATION
+ */
+#define WM8741_UPDATELL                         0x0020  /* UPDATELL */
+#define WM8741_UPDATELL_MASK                    0x0020  /* UPDATELL */
+#define WM8741_UPDATELL_SHIFT                        5  /* UPDATELL */
+#define WM8741_UPDATELL_WIDTH                        1  /* UPDATELL */
+#define WM8741_LAT_4_0_MASK                     0x001F  /* LAT[4:0] - [4:0] */
+#define WM8741_LAT_4_0_SHIFT                         0  /* LAT[4:0] - [4:0] */
+#define WM8741_LAT_4_0_WIDTH                         5  /* LAT[4:0] - [4:0] */
+
+/*
+ * R1 (0x01) - DACLMSB_ATTENUATION
+ */
+#define WM8741_UPDATELM                         0x0020  /* UPDATELM */
+#define WM8741_UPDATELM_MASK                    0x0020  /* UPDATELM */
+#define WM8741_UPDATELM_SHIFT                        5  /* UPDATELM */
+#define WM8741_UPDATELM_WIDTH                        1  /* UPDATELM */
+#define WM8741_LAT_9_5_0_MASK                   0x001F  /* LAT[9:5] - [4:0] */
+#define WM8741_LAT_9_5_0_SHIFT                       0  /* LAT[9:5] - [4:0] */
+#define WM8741_LAT_9_5_0_WIDTH                       5  /* LAT[9:5] - [4:0] */
+
+/*
+ * R2 (0x02) - DACRLSB_ATTENUATION
+ */
+#define WM8741_UPDATERL                         0x0020  /* UPDATERL */
+#define WM8741_UPDATERL_MASK                    0x0020  /* UPDATERL */
+#define WM8741_UPDATERL_SHIFT                        5  /* UPDATERL */
+#define WM8741_UPDATERL_WIDTH                        1  /* UPDATERL */
+#define WM8741_RAT_4_0_MASK                     0x001F  /* RAT[4:0] - [4:0] */
+#define WM8741_RAT_4_0_SHIFT                         0  /* RAT[4:0] - [4:0] */
+#define WM8741_RAT_4_0_WIDTH                         5  /* RAT[4:0] - [4:0] */
+
+/*
+ * R3 (0x03) - DACRMSB_ATTENUATION
+ */
+#define WM8741_UPDATERM                         0x0020  /* UPDATERM */
+#define WM8741_UPDATERM_MASK                    0x0020  /* UPDATERM */
+#define WM8741_UPDATERM_SHIFT                        5  /* UPDATERM */
+#define WM8741_UPDATERM_WIDTH                        1  /* UPDATERM */
+#define WM8741_RAT_9_5_0_MASK                   0x001F  /* RAT[9:5] - [4:0] */
+#define WM8741_RAT_9_5_0_SHIFT                       0  /* RAT[9:5] - [4:0] */
+#define WM8741_RAT_9_5_0_WIDTH                       5  /* RAT[9:5] - [4:0] */
+
+/*
+ * R4 (0x04) - VOLUME_CONTROL
+ */
+#define WM8741_AMUTE                            0x0080  /* AMUTE */
+#define WM8741_AMUTE_MASK                       0x0080  /* AMUTE */
+#define WM8741_AMUTE_SHIFT                           7  /* AMUTE */
+#define WM8741_AMUTE_WIDTH                           1  /* AMUTE */
+#define WM8741_ZFLAG_MASK                       0x0060  /* ZFLAG - [6:5] */
+#define WM8741_ZFLAG_SHIFT                           5  /* ZFLAG - [6:5] */
+#define WM8741_ZFLAG_WIDTH                           2  /* ZFLAG - [6:5] */
+#define WM8741_IZD                              0x0010  /* IZD */
+#define WM8741_IZD_MASK                         0x0010  /* IZD */
+#define WM8741_IZD_SHIFT                             4  /* IZD */
+#define WM8741_IZD_WIDTH                             1  /* IZD */
+#define WM8741_SOFT                             0x0008  /* SOFT MUTE */
+#define WM8741_SOFT_MASK                        0x0008  /* SOFT MUTE */
+#define WM8741_SOFT_SHIFT                            3  /* SOFT MUTE */
+#define WM8741_SOFT_WIDTH                            1  /* SOFT MUTE */
+#define WM8741_ATC                              0x0004  /* ATC */
+#define WM8741_ATC_MASK                         0x0004  /* ATC */
+#define WM8741_ATC_SHIFT                             2  /* ATC */
+#define WM8741_ATC_WIDTH                             1  /* ATC */
+#define WM8741_ATT2DB                           0x0002  /* ATT2DB */
+#define WM8741_ATT2DB_MASK                      0x0002  /* ATT2DB */
+#define WM8741_ATT2DB_SHIFT                          1  /* ATT2DB */
+#define WM8741_ATT2DB_WIDTH                          1  /* ATT2DB */
+#define WM8741_VOL_RAMP                         0x0001  /* VOL_RAMP */
+#define WM8741_VOL_RAMP_MASK                    0x0001  /* VOL_RAMP */
+#define WM8741_VOL_RAMP_SHIFT                        0  /* VOL_RAMP */
+#define WM8741_VOL_RAMP_WIDTH                        1  /* VOL_RAMP */
+
+/*
+ * R5 (0x05) - FORMAT_CONTROL
+ */
+#define WM8741_PWDN                             0x0080  /* PWDN */
+#define WM8741_PWDN_MASK                        0x0080  /* PWDN */
+#define WM8741_PWDN_SHIFT                            7  /* PWDN */
+#define WM8741_PWDN_WIDTH                            1  /* PWDN */
+#define WM8741_REV                              0x0040  /* REV */
+#define WM8741_REV_MASK                         0x0040  /* REV */
+#define WM8741_REV_SHIFT                             6  /* REV */
+#define WM8741_REV_WIDTH                             1  /* REV */
+#define WM8741_BCP                              0x0020  /* BCP */
+#define WM8741_BCP_MASK                         0x0020  /* BCP */
+#define WM8741_BCP_SHIFT                             5  /* BCP */
+#define WM8741_BCP_WIDTH                             1  /* BCP */
+#define WM8741_LRP                              0x0010  /* LRP */
+#define WM8741_LRP_MASK                         0x0010  /* LRP */
+#define WM8741_LRP_SHIFT                             4  /* LRP */
+#define WM8741_LRP_WIDTH                             1  /* LRP */
+#define WM8741_FMT_MASK                         0x000C  /* FMT - [3:2] */
+#define WM8741_FMT_SHIFT                             2  /* FMT - [3:2] */
+#define WM8741_FMT_WIDTH                             2  /* FMT - [3:2] */
+#define WM8741_IWL_MASK                         0x0003  /* IWL - [1:0] */
+#define WM8741_IWL_SHIFT                             0  /* IWL - [1:0] */
+#define WM8741_IWL_WIDTH                             2  /* IWL - [1:0] */
+
+/*
+ * R6 (0x06) - FILTER_CONTROL
+ */
+#define WM8741_ZFLAG_HI                         0x0080  /* ZFLAG_HI */
+#define WM8741_ZFLAG_HI_MASK                    0x0080  /* ZFLAG_HI */
+#define WM8741_ZFLAG_HI_SHIFT                        7  /* ZFLAG_HI */
+#define WM8741_ZFLAG_HI_WIDTH                        1  /* ZFLAG_HI */
+#define WM8741_DEEMPH_MASK                      0x0060  /* DEEMPH - [6:5] */
+#define WM8741_DEEMPH_SHIFT                          5  /* DEEMPH - [6:5] */
+#define WM8741_DEEMPH_WIDTH                          2  /* DEEMPH - [6:5] */
+#define WM8741_DSDFILT_MASK                     0x0018  /* DSDFILT - [4:3] */
+#define WM8741_DSDFILT_SHIFT                         3  /* DSDFILT - [4:3] */
+#define WM8741_DSDFILT_WIDTH                         2  /* DSDFILT - [4:3] */
+#define WM8741_FIRSEL_MASK                      0x0007  /* FIRSEL - [2:0] */
+#define WM8741_FIRSEL_SHIFT                          0  /* FIRSEL - [2:0] */
+#define WM8741_FIRSEL_WIDTH                          3  /* FIRSEL - [2:0] */
+
+/*
+ * R7 (0x07) - MODE_CONTROL_1
+ */
+#define WM8741_MODE8X                           0x0080  /* MODE8X */
+#define WM8741_MODE8X_MASK                      0x0080  /* MODE8X */
+#define WM8741_MODE8X_SHIFT                          7  /* MODE8X */
+#define WM8741_MODE8X_WIDTH                          1  /* MODE8X */
+#define WM8741_OSR_MASK                         0x0060  /* OSR - [6:5] */
+#define WM8741_OSR_SHIFT                             5  /* OSR - [6:5] */
+#define WM8741_OSR_WIDTH                             2  /* OSR - [6:5] */
+#define WM8741_SR_MASK                          0x001C  /* SR - [4:2] */
+#define WM8741_SR_SHIFT                              2  /* SR - [4:2] */
+#define WM8741_SR_WIDTH                              3  /* SR - [4:2] */
+#define WM8741_MODESEL_MASK                     0x0003  /* MODESEL - [1:0] */
+#define WM8741_MODESEL_SHIFT                         0  /* MODESEL - [1:0] */
+#define WM8741_MODESEL_WIDTH                         2  /* MODESEL - [1:0] */
+
+/*
+ * R8 (0x08) - MODE_CONTROL_2
+ */
+#define WM8741_DSD_GAIN                         0x0040  /* DSD_GAIN */
+#define WM8741_DSD_GAIN_MASK                    0x0040  /* DSD_GAIN */
+#define WM8741_DSD_GAIN_SHIFT                        6  /* DSD_GAIN */
+#define WM8741_DSD_GAIN_WIDTH                        1  /* DSD_GAIN */
+#define WM8741_SDOUT                            0x0020  /* SDOUT */
+#define WM8741_SDOUT_MASK                       0x0020  /* SDOUT */
+#define WM8741_SDOUT_SHIFT                           5  /* SDOUT */
+#define WM8741_SDOUT_WIDTH                           1  /* SDOUT */
+#define WM8741_DOUT                             0x0010  /* DOUT */
+#define WM8741_DOUT_MASK                        0x0010  /* DOUT */
+#define WM8741_DOUT_SHIFT                            4  /* DOUT */
+#define WM8741_DOUT_WIDTH                            1  /* DOUT */
+#define WM8741_DIFF_MASK                        0x000C  /* DIFF - [3:2] */
+#define WM8741_DIFF_SHIFT                            2  /* DIFF - [3:2] */
+#define WM8741_DIFF_WIDTH                            2  /* DIFF - [3:2] */
+#define WM8741_DITHER_MASK                      0x0003  /* DITHER - [1:0] */
+#define WM8741_DITHER_SHIFT                          0  /* DITHER - [1:0] */
+#define WM8741_DITHER_WIDTH                          2  /* DITHER - [1:0] */
+
+/*
+ * R32 (0x20) - ADDITONAL_CONTROL_1
+ */
+#define WM8741_DSD_LEVEL                        0x0002  /* DSD_LEVEL */
+#define WM8741_DSD_LEVEL_MASK                   0x0002  /* DSD_LEVEL */
+#define WM8741_DSD_LEVEL_SHIFT                       1  /* DSD_LEVEL */
+#define WM8741_DSD_LEVEL_WIDTH                       1  /* DSD_LEVEL */
+#define WM8741_DSD_NO_NOTCH                     0x0001  /* DSD_NO_NOTCH */
+#define WM8741_DSD_NO_NOTCH_MASK                0x0001  /* DSD_NO_NOTCH */
+#define WM8741_DSD_NO_NOTCH_SHIFT                    0  /* DSD_NO_NOTCH */
+#define WM8741_DSD_NO_NOTCH_WIDTH                    1  /* DSD_NO_NOTCH */
+
+#define  WM8741_SYSCLK 0
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8750.c b/linux/sound/soc/codecs/wm8750.c
new file mode 100644
index 0000000..6c924cd
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8750.c
@@ -0,0 +1,872 @@
+/*
+ * wm8750.c -- WM8750 ALSA SoC audio driver
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on WM8753.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "wm8750.h"
+
+/*
+ * wm8750 register cache
+ * We can't read the WM8750 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8750_reg[] = {
+	0x0097, 0x0097, 0x0079, 0x0079,  /*  0 */
+	0x0000, 0x0008, 0x0000, 0x000a,  /*  4 */
+	0x0000, 0x0000, 0x00ff, 0x00ff,  /*  8 */
+	0x000f, 0x000f, 0x0000, 0x0000,  /* 12 */
+	0x0000, 0x007b, 0x0000, 0x0032,  /* 16 */
+	0x0000, 0x00c3, 0x00c3, 0x00c0,  /* 20 */
+	0x0000, 0x0000, 0x0000, 0x0000,  /* 24 */
+	0x0000, 0x0000, 0x0000, 0x0000,  /* 28 */
+	0x0000, 0x0000, 0x0050, 0x0050,  /* 32 */
+	0x0050, 0x0050, 0x0050, 0x0050,  /* 36 */
+	0x0079, 0x0079, 0x0079,          /* 40 */
+};
+
+/* codec private data */
+struct wm8750_priv {
+	unsigned int sysclk;
+	enum snd_soc_control_type control_type;
+	u16 reg_cache[ARRAY_SIZE(wm8750_reg)];
+};
+
+#define wm8750_reset(c)	snd_soc_write(c, WM8750_RESET, 0)
+
+/*
+ * WM8750 Controls
+ */
+static const char *wm8750_bass[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm8750_bass_filter[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" };
+static const char *wm8750_treble[] = {"8kHz", "4kHz"};
+static const char *wm8750_3d_lc[] = {"200Hz", "500Hz"};
+static const char *wm8750_3d_uc[] = {"2.2kHz", "1.5kHz"};
+static const char *wm8750_3d_func[] = {"Capture", "Playback"};
+static const char *wm8750_alc_func[] = {"Off", "Right", "Left", "Stereo"};
+static const char *wm8750_ng_type[] = {"Constant PGA Gain",
+	"Mute ADC Output"};
+static const char *wm8750_line_mux[] = {"Line 1", "Line 2", "Line 3", "PGA",
+	"Differential"};
+static const char *wm8750_pga_sel[] = {"Line 1", "Line 2", "Line 3",
+	"Differential"};
+static const char *wm8750_out3[] = {"VREF", "ROUT1 + Vol", "MonoOut",
+	"ROUT1"};
+static const char *wm8750_diff_sel[] = {"Line 1", "Line 2"};
+static const char *wm8750_adcpol[] = {"Normal", "L Invert", "R Invert",
+	"L + R Invert"};
+static const char *wm8750_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const char *wm8750_mono_mux[] = {"Stereo", "Mono (Left)",
+	"Mono (Right)", "Digital Mono"};
+
+static const struct soc_enum wm8750_enum[] = {
+SOC_ENUM_SINGLE(WM8750_BASS, 7, 2, wm8750_bass),
+SOC_ENUM_SINGLE(WM8750_BASS, 6, 2, wm8750_bass_filter),
+SOC_ENUM_SINGLE(WM8750_TREBLE, 6, 2, wm8750_treble),
+SOC_ENUM_SINGLE(WM8750_3D, 5, 2, wm8750_3d_lc),
+SOC_ENUM_SINGLE(WM8750_3D, 6, 2, wm8750_3d_uc),
+SOC_ENUM_SINGLE(WM8750_3D, 7, 2, wm8750_3d_func),
+SOC_ENUM_SINGLE(WM8750_ALC1, 7, 4, wm8750_alc_func),
+SOC_ENUM_SINGLE(WM8750_NGATE, 1, 2, wm8750_ng_type),
+SOC_ENUM_SINGLE(WM8750_LOUTM1, 0, 5, wm8750_line_mux),
+SOC_ENUM_SINGLE(WM8750_ROUTM1, 0, 5, wm8750_line_mux),
+SOC_ENUM_SINGLE(WM8750_LADCIN, 6, 4, wm8750_pga_sel), /* 10 */
+SOC_ENUM_SINGLE(WM8750_RADCIN, 6, 4, wm8750_pga_sel),
+SOC_ENUM_SINGLE(WM8750_ADCTL2, 7, 4, wm8750_out3),
+SOC_ENUM_SINGLE(WM8750_ADCIN, 8, 2, wm8750_diff_sel),
+SOC_ENUM_SINGLE(WM8750_ADCDAC, 5, 4, wm8750_adcpol),
+SOC_ENUM_SINGLE(WM8750_ADCDAC, 1, 4, wm8750_deemph),
+SOC_ENUM_SINGLE(WM8750_ADCIN, 6, 4, wm8750_mono_mux), /* 16 */
+
+};
+
+static const struct snd_kcontrol_new wm8750_snd_controls[] = {
+
+SOC_DOUBLE_R("Capture Volume", WM8750_LINVOL, WM8750_RINVOL, 0, 63, 0),
+SOC_DOUBLE_R("Capture ZC Switch", WM8750_LINVOL, WM8750_RINVOL, 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8750_LINVOL, WM8750_RINVOL, 7, 1, 1),
+
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8750_LOUT1V,
+	WM8750_ROUT1V, 7, 1, 0),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8750_LOUT2V,
+	WM8750_ROUT2V, 7, 1, 0),
+
+SOC_ENUM("Playback De-emphasis", wm8750_enum[15]),
+
+SOC_ENUM("Capture Polarity", wm8750_enum[14]),
+SOC_SINGLE("Playback 6dB Attenuate", WM8750_ADCDAC, 7, 1, 0),
+SOC_SINGLE("Capture 6dB Attenuate", WM8750_ADCDAC, 8, 1, 0),
+
+SOC_DOUBLE_R("PCM Volume", WM8750_LDAC, WM8750_RDAC, 0, 255, 0),
+
+SOC_ENUM("Bass Boost", wm8750_enum[0]),
+SOC_ENUM("Bass Filter", wm8750_enum[1]),
+SOC_SINGLE("Bass Volume", WM8750_BASS, 0, 15, 1),
+
+SOC_SINGLE("Treble Volume", WM8750_TREBLE, 0, 15, 1),
+SOC_ENUM("Treble Cut-off", wm8750_enum[2]),
+
+SOC_SINGLE("3D Switch", WM8750_3D, 0, 1, 0),
+SOC_SINGLE("3D Volume", WM8750_3D, 1, 15, 0),
+SOC_ENUM("3D Lower Cut-off", wm8750_enum[3]),
+SOC_ENUM("3D Upper Cut-off", wm8750_enum[4]),
+SOC_ENUM("3D Mode", wm8750_enum[5]),
+
+SOC_SINGLE("ALC Capture Target Volume", WM8750_ALC1, 0, 7, 0),
+SOC_SINGLE("ALC Capture Max Volume", WM8750_ALC1, 4, 7, 0),
+SOC_ENUM("ALC Capture Function", wm8750_enum[6]),
+SOC_SINGLE("ALC Capture ZC Switch", WM8750_ALC2, 7, 1, 0),
+SOC_SINGLE("ALC Capture Hold Time", WM8750_ALC2, 0, 15, 0),
+SOC_SINGLE("ALC Capture Decay Time", WM8750_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Capture Attack Time", WM8750_ALC3, 0, 15, 0),
+SOC_SINGLE("ALC Capture NG Threshold", WM8750_NGATE, 3, 31, 0),
+SOC_ENUM("ALC Capture NG Type", wm8750_enum[4]),
+SOC_SINGLE("ALC Capture NG Switch", WM8750_NGATE, 0, 1, 0),
+
+SOC_SINGLE("Left ADC Capture Volume", WM8750_LADC, 0, 255, 0),
+SOC_SINGLE("Right ADC Capture Volume", WM8750_RADC, 0, 255, 0),
+
+SOC_SINGLE("ZC Timeout Switch", WM8750_ADCTL1, 0, 1, 0),
+SOC_SINGLE("Playback Invert Switch", WM8750_ADCTL1, 1, 1, 0),
+
+SOC_SINGLE("Right Speaker Playback Invert Switch", WM8750_ADCTL2, 4, 1, 0),
+
+/* Unimplemented */
+/* ADCDAC Bit 0 - ADCHPD */
+/* ADCDAC Bit 4 - HPOR */
+/* ADCTL1 Bit 2,3 - DATSEL */
+/* ADCTL1 Bit 4,5 - DMONOMIX */
+/* ADCTL1 Bit 6,7 - VSEL */
+/* ADCTL2 Bit 2 - LRCM */
+/* ADCTL2 Bit 3 - TRI */
+/* ADCTL3 Bit 5 - HPFLREN */
+/* ADCTL3 Bit 6 - VROI */
+/* ADCTL3 Bit 7,8 - ADCLRM */
+/* ADCIN Bit 4 - LDCM */
+/* ADCIN Bit 5 - RDCM */
+
+SOC_DOUBLE_R("Mic Boost", WM8750_LADCIN, WM8750_RADCIN, 4, 3, 0),
+
+SOC_DOUBLE_R("Bypass Left Playback Volume", WM8750_LOUTM1,
+	WM8750_LOUTM2, 4, 7, 1),
+SOC_DOUBLE_R("Bypass Right Playback Volume", WM8750_ROUTM1,
+	WM8750_ROUTM2, 4, 7, 1),
+SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8750_MOUTM1,
+	WM8750_MOUTM2, 4, 7, 1),
+
+SOC_SINGLE("Mono Playback ZC Switch", WM8750_MOUTV, 7, 1, 0),
+
+SOC_DOUBLE_R("Headphone Playback Volume", WM8750_LOUT1V, WM8750_ROUT1V,
+	0, 127, 0),
+SOC_DOUBLE_R("Speaker Playback Volume", WM8750_LOUT2V, WM8750_ROUT2V,
+	0, 127, 0),
+
+SOC_SINGLE("Mono Playback Volume", WM8750_MOUTV, 0, 127, 0),
+
+};
+
+/*
+ * DAPM Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8750_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8750_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_LOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8750_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_LOUTM2, 7, 1, 0),
+};
+
+/* Right Mixer */
+static const struct snd_kcontrol_new wm8750_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8750_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_ROUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM8750_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_ROUTM2, 7, 1, 0),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm8750_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8750_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_MOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8750_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_MOUTM2, 7, 1, 0),
+};
+
+/* Left Line Mux */
+static const struct snd_kcontrol_new wm8750_left_line_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[8]);
+
+/* Right Line Mux */
+static const struct snd_kcontrol_new wm8750_right_line_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[9]);
+
+/* Left PGA Mux */
+static const struct snd_kcontrol_new wm8750_left_pga_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[10]);
+
+/* Right PGA Mux */
+static const struct snd_kcontrol_new wm8750_right_pga_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[11]);
+
+/* Out 3 Mux */
+static const struct snd_kcontrol_new wm8750_out3_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[12]);
+
+/* Differential Mux */
+static const struct snd_kcontrol_new wm8750_diffmux_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[13]);
+
+/* Mono ADC Mux */
+static const struct snd_kcontrol_new wm8750_monomux_controls =
+SOC_DAPM_ENUM("Route", wm8750_enum[16]);
+
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+	SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
+		&wm8750_left_mixer_controls[0],
+		ARRAY_SIZE(wm8750_left_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
+		&wm8750_right_mixer_controls[0],
+		ARRAY_SIZE(wm8750_right_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Mono Mixer", WM8750_PWR2, 2, 0,
+		&wm8750_mono_mixer_controls[0],
+		ARRAY_SIZE(wm8750_mono_mixer_controls)),
+
+	SND_SOC_DAPM_PGA("Right Out 2", WM8750_PWR2, 3, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Left Out 2", WM8750_PWR2, 4, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right Out 1", WM8750_PWR2, 5, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Left Out 1", WM8750_PWR2, 6, 0, NULL, 0),
+	SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8750_PWR2, 7, 0),
+	SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8750_PWR2, 8, 0),
+
+	SND_SOC_DAPM_MICBIAS("Mic Bias", WM8750_PWR1, 1, 0),
+	SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8750_PWR1, 2, 0),
+	SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8750_PWR1, 3, 0),
+
+	SND_SOC_DAPM_MUX("Left PGA Mux", WM8750_PWR1, 5, 0,
+		&wm8750_left_pga_controls),
+	SND_SOC_DAPM_MUX("Right PGA Mux", WM8750_PWR1, 4, 0,
+		&wm8750_right_pga_controls),
+	SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
+		&wm8750_left_line_controls),
+	SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
+		&wm8750_right_line_controls),
+
+	SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8750_out3_controls),
+	SND_SOC_DAPM_PGA("Out 3", WM8750_PWR2, 1, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Mono Out 1", WM8750_PWR2, 2, 0, NULL, 0),
+
+	SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0,
+		&wm8750_diffmux_controls),
+	SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+		&wm8750_monomux_controls),
+	SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+		&wm8750_monomux_controls),
+
+	SND_SOC_DAPM_OUTPUT("LOUT1"),
+	SND_SOC_DAPM_OUTPUT("ROUT1"),
+	SND_SOC_DAPM_OUTPUT("LOUT2"),
+	SND_SOC_DAPM_OUTPUT("ROUT2"),
+	SND_SOC_DAPM_OUTPUT("MONO1"),
+	SND_SOC_DAPM_OUTPUT("OUT3"),
+	SND_SOC_DAPM_OUTPUT("VREF"),
+
+	SND_SOC_DAPM_INPUT("LINPUT1"),
+	SND_SOC_DAPM_INPUT("LINPUT2"),
+	SND_SOC_DAPM_INPUT("LINPUT3"),
+	SND_SOC_DAPM_INPUT("RINPUT1"),
+	SND_SOC_DAPM_INPUT("RINPUT2"),
+	SND_SOC_DAPM_INPUT("RINPUT3"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* left mixer */
+	{"Left Mixer", "Playback Switch", "Left DAC"},
+	{"Left Mixer", "Left Bypass Switch", "Left Line Mux"},
+	{"Left Mixer", "Right Playback Switch", "Right DAC"},
+	{"Left Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+	/* right mixer */
+	{"Right Mixer", "Left Playback Switch", "Left DAC"},
+	{"Right Mixer", "Left Bypass Switch", "Left Line Mux"},
+	{"Right Mixer", "Playback Switch", "Right DAC"},
+	{"Right Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+	/* left out 1 */
+	{"Left Out 1", NULL, "Left Mixer"},
+	{"LOUT1", NULL, "Left Out 1"},
+
+	/* left out 2 */
+	{"Left Out 2", NULL, "Left Mixer"},
+	{"LOUT2", NULL, "Left Out 2"},
+
+	/* right out 1 */
+	{"Right Out 1", NULL, "Right Mixer"},
+	{"ROUT1", NULL, "Right Out 1"},
+
+	/* right out 2 */
+	{"Right Out 2", NULL, "Right Mixer"},
+	{"ROUT2", NULL, "Right Out 2"},
+
+	/* mono mixer */
+	{"Mono Mixer", "Left Playback Switch", "Left DAC"},
+	{"Mono Mixer", "Left Bypass Switch", "Left Line Mux"},
+	{"Mono Mixer", "Right Playback Switch", "Right DAC"},
+	{"Mono Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+	/* mono out */
+	{"Mono Out 1", NULL, "Mono Mixer"},
+	{"MONO1", NULL, "Mono Out 1"},
+
+	/* out 3 */
+	{"Out3 Mux", "VREF", "VREF"},
+	{"Out3 Mux", "ROUT1 + Vol", "ROUT1"},
+	{"Out3 Mux", "ROUT1", "Right Mixer"},
+	{"Out3 Mux", "MonoOut", "MONO1"},
+	{"Out 3", NULL, "Out3 Mux"},
+	{"OUT3", NULL, "Out 3"},
+
+	/* Left Line Mux */
+	{"Left Line Mux", "Line 1", "LINPUT1"},
+	{"Left Line Mux", "Line 2", "LINPUT2"},
+	{"Left Line Mux", "Line 3", "LINPUT3"},
+	{"Left Line Mux", "PGA", "Left PGA Mux"},
+	{"Left Line Mux", "Differential", "Differential Mux"},
+
+	/* Right Line Mux */
+	{"Right Line Mux", "Line 1", "RINPUT1"},
+	{"Right Line Mux", "Line 2", "RINPUT2"},
+	{"Right Line Mux", "Line 3", "RINPUT3"},
+	{"Right Line Mux", "PGA", "Right PGA Mux"},
+	{"Right Line Mux", "Differential", "Differential Mux"},
+
+	/* Left PGA Mux */
+	{"Left PGA Mux", "Line 1", "LINPUT1"},
+	{"Left PGA Mux", "Line 2", "LINPUT2"},
+	{"Left PGA Mux", "Line 3", "LINPUT3"},
+	{"Left PGA Mux", "Differential", "Differential Mux"},
+
+	/* Right PGA Mux */
+	{"Right PGA Mux", "Line 1", "RINPUT1"},
+	{"Right PGA Mux", "Line 2", "RINPUT2"},
+	{"Right PGA Mux", "Line 3", "RINPUT3"},
+	{"Right PGA Mux", "Differential", "Differential Mux"},
+
+	/* Differential Mux */
+	{"Differential Mux", "Line 1", "LINPUT1"},
+	{"Differential Mux", "Line 1", "RINPUT1"},
+	{"Differential Mux", "Line 2", "LINPUT2"},
+	{"Differential Mux", "Line 2", "RINPUT2"},
+
+	/* Left ADC Mux */
+	{"Left ADC Mux", "Stereo", "Left PGA Mux"},
+	{"Left ADC Mux", "Mono (Left)", "Left PGA Mux"},
+	{"Left ADC Mux", "Digital Mono", "Left PGA Mux"},
+
+	/* Right ADC Mux */
+	{"Right ADC Mux", "Stereo", "Right PGA Mux"},
+	{"Right ADC Mux", "Mono (Right)", "Right PGA Mux"},
+	{"Right ADC Mux", "Digital Mono", "Right PGA Mux"},
+
+	/* ADC */
+	{"Left ADC", NULL, "Left ADC Mux"},
+	{"Right ADC", NULL, "Right ADC Mux"},
+};
+
+static int wm8750_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets,
+				  ARRAY_SIZE(wm8750_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+struct _coeff_div {
+	u32 mclk;
+	u32 rate;
+	u16 fs;
+	u8 sr:5;
+	u8 usb:1;
+};
+
+/* codec hifi mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+	/* 8k */
+	{12288000, 8000, 1536, 0x6, 0x0},
+	{11289600, 8000, 1408, 0x16, 0x0},
+	{18432000, 8000, 2304, 0x7, 0x0},
+	{16934400, 8000, 2112, 0x17, 0x0},
+	{12000000, 8000, 1500, 0x6, 0x1},
+
+	/* 11.025k */
+	{11289600, 11025, 1024, 0x18, 0x0},
+	{16934400, 11025, 1536, 0x19, 0x0},
+	{12000000, 11025, 1088, 0x19, 0x1},
+
+	/* 16k */
+	{12288000, 16000, 768, 0xa, 0x0},
+	{18432000, 16000, 1152, 0xb, 0x0},
+	{12000000, 16000, 750, 0xa, 0x1},
+
+	/* 22.05k */
+	{11289600, 22050, 512, 0x1a, 0x0},
+	{16934400, 22050, 768, 0x1b, 0x0},
+	{12000000, 22050, 544, 0x1b, 0x1},
+
+	/* 32k */
+	{12288000, 32000, 384, 0xc, 0x0},
+	{18432000, 32000, 576, 0xd, 0x0},
+	{12000000, 32000, 375, 0xa, 0x1},
+
+	/* 44.1k */
+	{11289600, 44100, 256, 0x10, 0x0},
+	{16934400, 44100, 384, 0x11, 0x0},
+	{12000000, 44100, 272, 0x11, 0x1},
+
+	/* 48k */
+	{12288000, 48000, 256, 0x0, 0x0},
+	{18432000, 48000, 384, 0x1, 0x0},
+	{12000000, 48000, 250, 0x0, 0x1},
+
+	/* 88.2k */
+	{11289600, 88200, 128, 0x1e, 0x0},
+	{16934400, 88200, 192, 0x1f, 0x0},
+	{12000000, 88200, 136, 0x1f, 0x1},
+
+	/* 96k */
+	{12288000, 96000, 128, 0xe, 0x0},
+	{18432000, 96000, 192, 0xf, 0x0},
+	{12000000, 96000, 125, 0xe, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+		if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+			return i;
+	}
+
+	printk(KERN_ERR "wm8750: could not get coeff for mclk %d @ rate %d\n",
+		mclk, rate);
+	return -EINVAL;
+}
+
+static int wm8750_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8750_priv *wm8750 = snd_soc_codec_get_drvdata(codec);
+
+	switch (freq) {
+	case 11289600:
+	case 12000000:
+	case 12288000:
+	case 16934400:
+	case 18432000:
+		wm8750->sysclk = freq;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int wm8750_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = 0;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		iface = 0x0040;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x0003;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface |= 0x0013;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x0090;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x0080;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x0010;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8750_IFACE, iface);
+	return 0;
+}
+
+static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8750_priv *wm8750 = snd_soc_codec_get_drvdata(codec);
+	u16 iface = snd_soc_read(codec, WM8750_IFACE) & 0x1f3;
+	u16 srate = snd_soc_read(codec, WM8750_SRATE) & 0x1c0;
+	int coeff = get_coeff(wm8750->sysclk, params_rate(params));
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x0004;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x0008;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface |= 0x000c;
+		break;
+	}
+
+	/* set iface & srate */
+	snd_soc_write(codec, WM8750_IFACE, iface);
+	if (coeff >= 0)
+		snd_soc_write(codec, WM8750_SRATE, srate |
+			(coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
+
+	return 0;
+}
+
+static int wm8750_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = snd_soc_read(codec, WM8750_ADCDAC) & 0xfff7;
+
+	if (mute)
+		snd_soc_write(codec, WM8750_ADCDAC, mute_reg | 0x8);
+	else
+		snd_soc_write(codec, WM8750_ADCDAC, mute_reg);
+	return 0;
+}
+
+static int wm8750_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 pwr_reg = snd_soc_read(codec, WM8750_PWR1) & 0xfe3e;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		/* set vmid to 50k and unmute dac */
+		snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x00c0);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Set VMID to 5k */
+			snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x01c1);
+
+			/* ...and ramp */
+			msleep(1000);
+		}
+
+		/* mute dac and set vmid to 500k, enable VREF */
+		snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x0141);
+		break;
+	case SND_SOC_BIAS_OFF:
+		snd_soc_write(codec, WM8750_PWR1, 0x0001);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8750_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+	SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+	SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8750_dai_ops = {
+	.hw_params	= wm8750_pcm_hw_params,
+	.digital_mute	= wm8750_mute,
+	.set_fmt	= wm8750_set_dai_fmt,
+	.set_sysclk	= wm8750_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8750_dai = {
+	.name = "wm8750-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8750_RATES,
+		.formats = WM8750_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8750_RATES,
+		.formats = WM8750_FORMATS,},
+	.ops = &wm8750_dai_ops,
+};
+
+static int wm8750_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8750_resume(struct snd_soc_codec *codec)
+{
+	int i;
+	u8 data[2];
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 0; i < ARRAY_SIZE(wm8750_reg); i++) {
+		if (i == WM8750_RESET)
+			continue;
+		data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+		data[1] = cache[i] & 0x00ff;
+		codec->hw_write(codec->control_data, data, 2);
+	}
+
+	wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static int wm8750_probe(struct snd_soc_codec *codec)
+{
+	struct wm8750_priv *wm8750 = snd_soc_codec_get_drvdata(codec);
+	int reg, ret;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8750->control_type);
+	if (ret < 0) {
+		printk(KERN_ERR "wm8750: failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm8750_reset(codec);
+	if (ret < 0) {
+		printk(KERN_ERR "wm8750: failed to reset: %d\n", ret);
+		return ret;
+	}
+
+	/* charge output caps */
+	wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* set the update bits */
+	reg = snd_soc_read(codec, WM8750_LDAC);
+	snd_soc_write(codec, WM8750_LDAC, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8750_RDAC);
+	snd_soc_write(codec, WM8750_RDAC, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8750_LOUT1V);
+	snd_soc_write(codec, WM8750_LOUT1V, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8750_ROUT1V);
+	snd_soc_write(codec, WM8750_ROUT1V, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8750_LOUT2V);
+	snd_soc_write(codec, WM8750_LOUT2V, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8750_ROUT2V);
+	snd_soc_write(codec, WM8750_ROUT2V, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8750_LINVOL);
+	snd_soc_write(codec, WM8750_LINVOL, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8750_RINVOL);
+	snd_soc_write(codec, WM8750_RINVOL, reg | 0x0100);
+
+	snd_soc_add_controls(codec, wm8750_snd_controls,
+				ARRAY_SIZE(wm8750_snd_controls));
+	wm8750_add_widgets(codec);
+	return ret;
+}
+
+static int wm8750_remove(struct snd_soc_codec *codec)
+{
+	wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8750 = {
+	.probe =	wm8750_probe,
+	.remove =	wm8750_remove,
+	.suspend =	wm8750_suspend,
+	.resume =	wm8750_resume,
+	.set_bias_level = wm8750_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8750_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8750_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8750_spi_probe(struct spi_device *spi)
+{
+	struct wm8750_priv *wm8750;
+	int ret;
+
+	wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL);
+	if (wm8750 == NULL)
+		return -ENOMEM;
+
+	wm8750->control_type = SND_SOC_SPI;
+	spi_set_drvdata(spi, wm8750);
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_dev_wm8750, &wm8750_dai, 1);
+	if (ret < 0)
+		kfree(wm8750);
+	return ret;
+}
+
+static int __devexit wm8750_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver wm8750_spi_driver = {
+	.driver = {
+		.name	= "wm8750-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= wm8750_spi_probe,
+	.remove		= __devexit_p(wm8750_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8750_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8750_priv *wm8750;
+	int ret;
+
+	wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL);
+	if (wm8750 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8750);
+	wm8750->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8750, &wm8750_dai, 1);
+	if (ret < 0)
+		kfree(wm8750);
+	return ret;
+}
+
+static __devexit int wm8750_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8750_i2c_id[] = {
+	{ "wm8750", 0 },
+	{ "wm8987", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8750_i2c_id);
+
+static struct i2c_driver wm8750_i2c_driver = {
+	.driver = {
+		.name = "wm8750-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8750_i2c_probe,
+	.remove =   __devexit_p(wm8750_i2c_remove),
+	.id_table = wm8750_i2c_id,
+};
+#endif
+
+static int __init wm8750_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8750_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8750 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&wm8750_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8750 SPI driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8750_modinit);
+
+static void __exit wm8750_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8750_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&wm8750_spi_driver);
+#endif
+}
+module_exit(wm8750_exit);
+
+MODULE_DESCRIPTION("ASoC WM8750 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8750.h b/linux/sound/soc/codecs/wm8750.h
new file mode 100644
index 0000000..121427c
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8750.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on WM8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _WM8750_H
+#define _WM8750_H
+
+/* WM8750 register space */
+
+#define WM8750_LINVOL    0x00
+#define WM8750_RINVOL    0x01
+#define WM8750_LOUT1V    0x02
+#define WM8750_ROUT1V    0x03
+#define WM8750_ADCDAC    0x05
+#define WM8750_IFACE     0x07
+#define WM8750_SRATE     0x08
+#define WM8750_LDAC      0x0a
+#define WM8750_RDAC      0x0b
+#define WM8750_BASS      0x0c
+#define WM8750_TREBLE    0x0d
+#define WM8750_RESET     0x0f
+#define WM8750_3D        0x10
+#define WM8750_ALC1      0x11
+#define WM8750_ALC2      0x12
+#define WM8750_ALC3      0x13
+#define WM8750_NGATE     0x14
+#define WM8750_LADC      0x15
+#define WM8750_RADC      0x16
+#define WM8750_ADCTL1    0x17
+#define WM8750_ADCTL2    0x18
+#define WM8750_PWR1      0x19
+#define WM8750_PWR2      0x1a
+#define WM8750_ADCTL3    0x1b
+#define WM8750_ADCIN     0x1f
+#define WM8750_LADCIN    0x20
+#define WM8750_RADCIN    0x21
+#define WM8750_LOUTM1    0x22
+#define WM8750_LOUTM2    0x23
+#define WM8750_ROUTM1    0x24
+#define WM8750_ROUTM2    0x25
+#define WM8750_MOUTM1    0x26
+#define WM8750_MOUTM2    0x27
+#define WM8750_LOUT2V    0x28
+#define WM8750_ROUT2V    0x29
+#define WM8750_MOUTV     0x2a
+
+#define WM8750_CACHE_REGNUM 0x2a
+
+#define WM8750_SYSCLK	0
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8753.c b/linux/sound/soc/codecs/wm8753.c
new file mode 100644
index 0000000..87caae5
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8753.c
@@ -0,0 +1,1680 @@
+/*
+ * wm8753.c  --  WM8753 ALSA Soc Audio driver
+ *
+ * Copyright 2003 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ *  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.
+ *
+ * Notes:
+ *  The WM8753 is a low power, high quality stereo codec with integrated PCM
+ *  codec designed for portable digital telephony applications.
+ *
+ * Dual DAI:-
+ *
+ * This driver support 2 DAI PCM's. This makes the default PCM available for
+ * HiFi audio (e.g. MP3, ogg) playback/capture and the other PCM available for
+ * voice.
+ *
+ * Please note that the voice PCM can be connected directly to a Bluetooth
+ * codec or GSM modem and thus cannot be read or written to, although it is
+ * available to be configured with snd_hw_params(), etc and kcontrols in the
+ * normal alsa manner.
+ *
+ * Fast DAI switching:-
+ *
+ * The driver can now fast switch between the DAI configurations via a
+ * an alsa kcontrol. This allows the PCM to remain open.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <asm/div64.h>
+
+#include "wm8753.h"
+
+static int caps_charge = 2000;
+module_param(caps_charge, int, 0);
+MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)");
+
+static void wm8753_set_dai_mode(struct snd_soc_codec *codec,
+		struct snd_soc_dai *dai, unsigned int hifi);
+
+/*
+ * wm8753 register cache
+ * We can't read the WM8753 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8753_reg[] = {
+	0x0000, 0x0008, 0x0000, 0x000a,
+	0x000a, 0x0033, 0x0000, 0x0007,
+	0x00ff, 0x00ff, 0x000f, 0x000f,
+	0x007b, 0x0000, 0x0032, 0x0000,
+	0x00c3, 0x00c3, 0x00c0, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0055, 0x0005, 0x0050, 0x0055,
+	0x0050, 0x0055, 0x0050, 0x0055,
+	0x0079, 0x0079, 0x0079, 0x0079,
+	0x0079, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0097, 0x0097, 0x0000,
+	0x0004, 0x0000, 0x0083, 0x0024,
+	0x01ba, 0x0000, 0x0083, 0x0024,
+	0x01ba, 0x0000, 0x0000, 0x0000
+};
+
+/* codec private data */
+struct wm8753_priv {
+	enum snd_soc_control_type control_type;
+	unsigned int sysclk;
+	unsigned int pcmclk;
+	int dai_func;
+};
+
+#define wm8753_reset(c) snd_soc_write(c, WM8753_RESET, 0)
+
+/*
+ * WM8753 Controls
+ */
+static const char *wm8753_base[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm8753_base_filter[] =
+	{"130Hz @ 48kHz", "200Hz @ 48kHz", "100Hz @ 16kHz", "400Hz @ 48kHz",
+	"100Hz @ 8kHz", "200Hz @ 8kHz"};
+static const char *wm8753_treble[] = {"8kHz", "4kHz"};
+static const char *wm8753_alc_func[] = {"Off", "Right", "Left", "Stereo"};
+static const char *wm8753_ng_type[] = {"Constant PGA Gain", "Mute ADC Output"};
+static const char *wm8753_3d_func[] = {"Capture", "Playback"};
+static const char *wm8753_3d_uc[] = {"2.2kHz", "1.5kHz"};
+static const char *wm8753_3d_lc[] = {"200Hz", "500Hz"};
+static const char *wm8753_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz"};
+static const char *wm8753_mono_mix[] = {"Stereo", "Left", "Right", "Mono"};
+static const char *wm8753_dac_phase[] = {"Non Inverted", "Inverted"};
+static const char *wm8753_line_mix[] = {"Line 1 + 2", "Line 1 - 2",
+	"Line 1", "Line 2"};
+static const char *wm8753_mono_mux[] = {"Line Mix", "Rx Mix"};
+static const char *wm8753_right_mux[] = {"Line 2", "Rx Mix"};
+static const char *wm8753_left_mux[] = {"Line 1", "Rx Mix"};
+static const char *wm8753_rxmsel[] = {"RXP - RXN", "RXP + RXN", "RXP", "RXN"};
+static const char *wm8753_sidetone_mux[] = {"Left PGA", "Mic 1", "Mic 2",
+	"Right PGA"};
+static const char *wm8753_mono2_src[] = {"Inverted Mono 1", "Left", "Right",
+	"Left + Right"};
+static const char *wm8753_out3[] = {"VREF", "ROUT2", "Left + Right"};
+static const char *wm8753_out4[] = {"VREF", "Capture ST", "LOUT2"};
+static const char *wm8753_radcsel[] = {"PGA", "Line or RXP-RXN", "Sidetone"};
+static const char *wm8753_ladcsel[] = {"PGA", "Line or RXP-RXN", "Line"};
+static const char *wm8753_mono_adc[] = {"Stereo", "Analogue Mix Left",
+	"Analogue Mix Right", "Digital Mono Mix"};
+static const char *wm8753_adc_hp[] = {"3.4Hz @ 48kHz", "82Hz @ 16k",
+	"82Hz @ 8kHz", "170Hz @ 8kHz"};
+static const char *wm8753_adc_filter[] = {"HiFi", "Voice"};
+static const char *wm8753_mic_sel[] = {"Mic 1", "Mic 2", "Mic 3"};
+static const char *wm8753_dai_mode[] = {"DAI 0", "DAI 1", "DAI 2", "DAI 3"};
+static const char *wm8753_dat_sel[] = {"Stereo", "Left ADC", "Right ADC",
+	"Channel Swap"};
+static const char *wm8753_rout2_phase[] = {"Non Inverted", "Inverted"};
+
+static const struct soc_enum wm8753_enum[] = {
+SOC_ENUM_SINGLE(WM8753_BASS, 7, 2, wm8753_base),
+SOC_ENUM_SINGLE(WM8753_BASS, 4, 6, wm8753_base_filter),
+SOC_ENUM_SINGLE(WM8753_TREBLE, 6, 2, wm8753_treble),
+SOC_ENUM_SINGLE(WM8753_ALC1, 7, 4, wm8753_alc_func),
+SOC_ENUM_SINGLE(WM8753_NGATE, 1, 2, wm8753_ng_type),
+SOC_ENUM_SINGLE(WM8753_3D, 7, 2, wm8753_3d_func),
+SOC_ENUM_SINGLE(WM8753_3D, 6, 2, wm8753_3d_uc),
+SOC_ENUM_SINGLE(WM8753_3D, 5, 2, wm8753_3d_lc),
+SOC_ENUM_SINGLE(WM8753_DAC, 1, 4, wm8753_deemp),
+SOC_ENUM_SINGLE(WM8753_DAC, 4, 4, wm8753_mono_mix),
+SOC_ENUM_SINGLE(WM8753_DAC, 6, 2, wm8753_dac_phase),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 3, 4, wm8753_line_mix),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 2, 2, wm8753_mono_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 1, 2, wm8753_right_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL1, 0, 2, wm8753_left_mux),
+SOC_ENUM_SINGLE(WM8753_INCTL2, 6, 4, wm8753_rxmsel),
+SOC_ENUM_SINGLE(WM8753_INCTL2, 4, 4, wm8753_sidetone_mux),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 7, 4, wm8753_mono2_src),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 0, 3, wm8753_out3),
+SOC_ENUM_SINGLE(WM8753_ADCTL2, 7, 3, wm8753_out4),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 2, 3, wm8753_radcsel),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 0, 3, wm8753_ladcsel),
+SOC_ENUM_SINGLE(WM8753_ADCIN, 4, 4, wm8753_mono_adc),
+SOC_ENUM_SINGLE(WM8753_ADC, 2, 4, wm8753_adc_hp),
+SOC_ENUM_SINGLE(WM8753_ADC, 4, 2, wm8753_adc_filter),
+SOC_ENUM_SINGLE(WM8753_MICBIAS, 6, 3, wm8753_mic_sel),
+SOC_ENUM_SINGLE(WM8753_IOCTL, 2, 4, wm8753_dai_mode),
+SOC_ENUM_SINGLE(WM8753_ADC, 7, 4, wm8753_dat_sel),
+SOC_ENUM_SINGLE(WM8753_OUTCTL, 2, 2, wm8753_rout2_phase),
+};
+
+
+static int wm8753_get_dai(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+	int mode = snd_soc_read(codec, WM8753_IOCTL);
+
+	ucontrol->value.integer.value[0] = (mode & 0xc) >> 2;
+	return 0;
+}
+
+static int wm8753_set_dai(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+	int mode = snd_soc_read(codec, WM8753_IOCTL);
+	struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+
+	if (((mode & 0xc) >> 2) == ucontrol->value.integer.value[0])
+		return 0;
+
+	mode &= 0xfff3;
+	mode |= (ucontrol->value.integer.value[0] << 2);
+
+	wm8753->dai_func =  ucontrol->value.integer.value[0];
+	return 1;
+}
+
+static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(mic_preamp_tlv, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+static const unsigned int out_tlv[] = {
+	TLV_DB_RANGE_HEAD(2),
+	/* 0000000 - 0101111 = "Analogue mute" */
+	0, 48, TLV_DB_SCALE_ITEM(-25500, 0, 0),
+	48, 127, TLV_DB_SCALE_ITEM(-7300, 100, 0),
+};
+static const DECLARE_TLV_DB_SCALE(mix_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(voice_mix_tlv, -1200, 300, 0);
+static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0);
+
+static const struct snd_kcontrol_new wm8753_snd_controls[] = {
+SOC_DOUBLE_R_TLV("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0, dac_tlv),
+
+SOC_DOUBLE_R_TLV("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0,
+		 adc_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V,
+		 0, 127, 0, out_tlv),
+SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0,
+		 127, 0, out_tlv),
+
+SOC_SINGLE_TLV("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0, out_tlv),
+
+SOC_DOUBLE_R_TLV("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7,
+		 1, mix_tlv),
+SOC_DOUBLE_R_TLV("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4,
+		 7, 1, mix_tlv),
+SOC_DOUBLE_R_TLV("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7,
+		 1, voice_mix_tlv),
+
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7,
+	     1, 0),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7,
+	     1, 0),
+
+SOC_SINGLE_TLV("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1, mix_tlv),
+SOC_SINGLE_TLV("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1,
+	       mix_tlv),
+SOC_SINGLE_TLV("Mono Voice Playback Volume", WM8753_MOUTM2, 0, 7, 1,
+	       voice_mix_tlv),
+SOC_SINGLE("Mono Playback ZC Switch", WM8753_MOUTV, 7, 1, 0),
+
+SOC_ENUM("Bass Boost", wm8753_enum[0]),
+SOC_ENUM("Bass Filter", wm8753_enum[1]),
+SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1),
+
+SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1),
+SOC_ENUM("Treble Cut-off", wm8753_enum[2]),
+
+SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1,
+	       rec_mix_tlv),
+SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1,
+	       rec_mix_tlv),
+
+SOC_DOUBLE_R_TLV("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0,
+		 pga_tlv),
+SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8753_LINVOL, WM8753_RINVOL, 7, 1, 1),
+
+SOC_ENUM("Capture Filter Select", wm8753_enum[23]),
+SOC_ENUM("Capture Filter Cut-off", wm8753_enum[24]),
+SOC_SINGLE("Capture Filter Switch", WM8753_ADC, 0, 1, 1),
+
+SOC_SINGLE("ALC Capture Target Volume", WM8753_ALC1, 0, 7, 0),
+SOC_SINGLE("ALC Capture Max Volume", WM8753_ALC1, 4, 7, 0),
+SOC_ENUM("ALC Capture Function", wm8753_enum[3]),
+SOC_SINGLE("ALC Capture ZC Switch", WM8753_ALC2, 8, 1, 0),
+SOC_SINGLE("ALC Capture Hold Time", WM8753_ALC2, 0, 15, 1),
+SOC_SINGLE("ALC Capture Decay Time", WM8753_ALC3, 4, 15, 1),
+SOC_SINGLE("ALC Capture Attack Time", WM8753_ALC3, 0, 15, 0),
+SOC_SINGLE("ALC Capture NG Threshold", WM8753_NGATE, 3, 31, 0),
+SOC_ENUM("ALC Capture NG Type", wm8753_enum[4]),
+SOC_SINGLE("ALC Capture NG Switch", WM8753_NGATE, 0, 1, 0),
+
+SOC_ENUM("3D Function", wm8753_enum[5]),
+SOC_ENUM("3D Upper Cut-off", wm8753_enum[6]),
+SOC_ENUM("3D Lower Cut-off", wm8753_enum[7]),
+SOC_SINGLE("3D Volume", WM8753_3D, 1, 15, 0),
+SOC_SINGLE("3D Switch", WM8753_3D, 0, 1, 0),
+
+SOC_SINGLE("Capture 6dB Attenuate", WM8753_ADCTL1, 2, 1, 0),
+SOC_SINGLE("Playback 6dB Attenuate", WM8753_ADCTL1, 1, 1, 0),
+
+SOC_ENUM("De-emphasis", wm8753_enum[8]),
+SOC_ENUM("Playback Mono Mix", wm8753_enum[9]),
+SOC_ENUM("Playback Phase", wm8753_enum[10]),
+
+SOC_SINGLE_TLV("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0, mic_preamp_tlv),
+SOC_SINGLE_TLV("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0, mic_preamp_tlv),
+
+SOC_ENUM_EXT("DAI Mode", wm8753_enum[26], wm8753_get_dai, wm8753_set_dai),
+
+SOC_ENUM("ADC Data Select", wm8753_enum[27]),
+SOC_ENUM("ROUT2 Phase", wm8753_enum[28]),
+};
+
+/*
+ * _DAPM_ Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8753_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_LOUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Left Playback Switch", WM8753_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_LOUTM1, 7, 1, 0),
+};
+
+/* Right mixer */
+static const struct snd_kcontrol_new wm8753_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_ROUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8753_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_ROUTM1, 7, 1, 0),
+};
+
+/* Mono mixer */
+static const struct snd_kcontrol_new wm8753_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8753_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8753_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_MOUTM2, 3, 1, 0),
+SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_MOUTM2, 7, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_MOUTM1, 7, 1, 0),
+};
+
+/* Mono 2 Mux */
+static const struct snd_kcontrol_new wm8753_mono2_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[17]);
+
+/* Out 3 Mux */
+static const struct snd_kcontrol_new wm8753_out3_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[18]);
+
+/* Out 4 Mux */
+static const struct snd_kcontrol_new wm8753_out4_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[19]);
+
+/* ADC Mono Mix */
+static const struct snd_kcontrol_new wm8753_adc_mono_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[22]);
+
+/* Record mixer */
+static const struct snd_kcontrol_new wm8753_record_mixer_controls[] = {
+SOC_DAPM_SINGLE("Voice Capture Switch", WM8753_RECMIX2, 3, 1, 0),
+SOC_DAPM_SINGLE("Left Capture Switch", WM8753_RECMIX1, 3, 1, 0),
+SOC_DAPM_SINGLE("Right Capture Switch", WM8753_RECMIX1, 7, 1, 0),
+};
+
+/* Left ADC mux */
+static const struct snd_kcontrol_new wm8753_adc_left_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[21]);
+
+/* Right ADC mux */
+static const struct snd_kcontrol_new wm8753_adc_right_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[20]);
+
+/* MIC mux */
+static const struct snd_kcontrol_new wm8753_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[16]);
+
+/* ALC mixer */
+static const struct snd_kcontrol_new wm8753_alc_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Capture Switch", WM8753_INCTL2, 3, 1, 0),
+SOC_DAPM_SINGLE("Mic2 Capture Switch", WM8753_INCTL2, 2, 1, 0),
+SOC_DAPM_SINGLE("Mic1 Capture Switch", WM8753_INCTL2, 1, 1, 0),
+SOC_DAPM_SINGLE("Rx Capture Switch", WM8753_INCTL2, 0, 1, 0),
+};
+
+/* Left Line mux */
+static const struct snd_kcontrol_new wm8753_line_left_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[14]);
+
+/* Right Line mux */
+static const struct snd_kcontrol_new wm8753_line_right_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[13]);
+
+/* Mono Line mux */
+static const struct snd_kcontrol_new wm8753_line_mono_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[12]);
+
+/* Line mux and mixer */
+static const struct snd_kcontrol_new wm8753_line_mux_mix_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[11]);
+
+/* Rx mux and mixer */
+static const struct snd_kcontrol_new wm8753_rx_mux_mix_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[15]);
+
+/* Mic Selector Mux */
+static const struct snd_kcontrol_new wm8753_mic_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm8753_enum[25]);
+
+static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = {
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8753_PWR1, 5, 0),
+SND_SOC_DAPM_MIXER("Left Mixer", WM8753_PWR4, 0, 0,
+	&wm8753_left_mixer_controls[0], ARRAY_SIZE(wm8753_left_mixer_controls)),
+SND_SOC_DAPM_PGA("Left Out 1", WM8753_PWR3, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left Out 2", WM8753_PWR3, 6, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", WM8753_PWR1, 3, 0),
+SND_SOC_DAPM_OUTPUT("LOUT1"),
+SND_SOC_DAPM_OUTPUT("LOUT2"),
+SND_SOC_DAPM_MIXER("Right Mixer", WM8753_PWR4, 1, 0,
+	&wm8753_right_mixer_controls[0], ARRAY_SIZE(wm8753_right_mixer_controls)),
+SND_SOC_DAPM_PGA("Right Out 1", WM8753_PWR3, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Out 2", WM8753_PWR3, 5, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", WM8753_PWR1, 2, 0),
+SND_SOC_DAPM_OUTPUT("ROUT1"),
+SND_SOC_DAPM_OUTPUT("ROUT2"),
+SND_SOC_DAPM_MIXER("Mono Mixer", WM8753_PWR4, 2, 0,
+	&wm8753_mono_mixer_controls[0], ARRAY_SIZE(wm8753_mono_mixer_controls)),
+SND_SOC_DAPM_PGA("Mono Out 1", WM8753_PWR3, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out 2", WM8753_PWR3, 1, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", WM8753_PWR1, 4, 0),
+SND_SOC_DAPM_OUTPUT("MONO1"),
+SND_SOC_DAPM_MUX("Mono 2 Mux", SND_SOC_NOPM, 0, 0, &wm8753_mono2_controls),
+SND_SOC_DAPM_OUTPUT("MONO2"),
+SND_SOC_DAPM_MIXER("Out3 Left + Right", -1, 0, 0, NULL, 0),
+SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out3_controls),
+SND_SOC_DAPM_PGA("Out 3", WM8753_PWR3, 4, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_MUX("Out4 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out4_controls),
+SND_SOC_DAPM_PGA("Out 4", WM8753_PWR3, 3, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_MIXER("Playback Mixer", WM8753_PWR4, 3, 0,
+	&wm8753_record_mixer_controls[0],
+	ARRAY_SIZE(wm8753_record_mixer_controls)),
+SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8753_PWR2, 3, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8753_PWR2, 2, 0),
+SND_SOC_DAPM_MUX("Capture Left Mixer", SND_SOC_NOPM, 0, 0,
+	&wm8753_adc_mono_controls),
+SND_SOC_DAPM_MUX("Capture Right Mixer", SND_SOC_NOPM, 0, 0,
+	&wm8753_adc_mono_controls),
+SND_SOC_DAPM_MUX("Capture Left Mux", SND_SOC_NOPM, 0, 0,
+	&wm8753_adc_left_controls),
+SND_SOC_DAPM_MUX("Capture Right Mux", SND_SOC_NOPM, 0, 0,
+	&wm8753_adc_right_controls),
+SND_SOC_DAPM_MUX("Mic Sidetone Mux", SND_SOC_NOPM, 0, 0,
+	&wm8753_mic_mux_controls),
+SND_SOC_DAPM_PGA("Left Capture Volume", WM8753_PWR2, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Capture Volume", WM8753_PWR2, 4, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("ALC Mixer", WM8753_PWR2, 6, 0,
+	&wm8753_alc_mixer_controls[0], ARRAY_SIZE(wm8753_alc_mixer_controls)),
+SND_SOC_DAPM_MUX("Line Left Mux", SND_SOC_NOPM, 0, 0,
+	&wm8753_line_left_controls),
+SND_SOC_DAPM_MUX("Line Right Mux", SND_SOC_NOPM, 0, 0,
+	&wm8753_line_right_controls),
+SND_SOC_DAPM_MUX("Line Mono Mux", SND_SOC_NOPM, 0, 0,
+	&wm8753_line_mono_controls),
+SND_SOC_DAPM_MUX("Line Mixer", WM8753_PWR2, 0, 0,
+	&wm8753_line_mux_mix_controls),
+SND_SOC_DAPM_MUX("Rx Mixer", WM8753_PWR2, 1, 0,
+	&wm8753_rx_mux_mix_controls),
+SND_SOC_DAPM_PGA("Mic 1 Volume", WM8753_PWR2, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mic 2 Volume", WM8753_PWR2, 7, 0, NULL, 0),
+SND_SOC_DAPM_MUX("Mic Selection Mux", SND_SOC_NOPM, 0, 0,
+	&wm8753_mic_sel_mux_controls),
+SND_SOC_DAPM_INPUT("LINE1"),
+SND_SOC_DAPM_INPUT("LINE2"),
+SND_SOC_DAPM_INPUT("RXP"),
+SND_SOC_DAPM_INPUT("RXN"),
+SND_SOC_DAPM_INPUT("ACIN"),
+SND_SOC_DAPM_OUTPUT("ACOP"),
+SND_SOC_DAPM_INPUT("MIC1N"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2N"),
+SND_SOC_DAPM_INPUT("MIC2"),
+SND_SOC_DAPM_VMID("VREF"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* left mixer */
+	{"Left Mixer", "Left Playback Switch", "Left DAC"},
+	{"Left Mixer", "Voice Playback Switch", "Voice DAC"},
+	{"Left Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+	{"Left Mixer", "Bypass Playback Switch", "Line Left Mux"},
+
+	/* right mixer */
+	{"Right Mixer", "Right Playback Switch", "Right DAC"},
+	{"Right Mixer", "Voice Playback Switch", "Voice DAC"},
+	{"Right Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+	{"Right Mixer", "Bypass Playback Switch", "Line Right Mux"},
+
+	/* mono mixer */
+	{"Mono Mixer", "Voice Playback Switch", "Voice DAC"},
+	{"Mono Mixer", "Left Playback Switch", "Left DAC"},
+	{"Mono Mixer", "Right Playback Switch", "Right DAC"},
+	{"Mono Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"},
+	{"Mono Mixer", "Bypass Playback Switch", "Line Mono Mux"},
+
+	/* left out */
+	{"Left Out 1", NULL, "Left Mixer"},
+	{"Left Out 2", NULL, "Left Mixer"},
+	{"LOUT1", NULL, "Left Out 1"},
+	{"LOUT2", NULL, "Left Out 2"},
+
+	/* right out */
+	{"Right Out 1", NULL, "Right Mixer"},
+	{"Right Out 2", NULL, "Right Mixer"},
+	{"ROUT1", NULL, "Right Out 1"},
+	{"ROUT2", NULL, "Right Out 2"},
+
+	/* mono 1 out */
+	{"Mono Out 1", NULL, "Mono Mixer"},
+	{"MONO1", NULL, "Mono Out 1"},
+
+	/* mono 2 out */
+	{"Mono 2 Mux", "Left + Right", "Out3 Left + Right"},
+	{"Mono 2 Mux", "Inverted Mono 1", "MONO1"},
+	{"Mono 2 Mux", "Left", "Left Mixer"},
+	{"Mono 2 Mux", "Right", "Right Mixer"},
+	{"Mono Out 2", NULL, "Mono 2 Mux"},
+	{"MONO2", NULL, "Mono Out 2"},
+
+	/* out 3 */
+	{"Out3 Left + Right", NULL, "Left Mixer"},
+	{"Out3 Left + Right", NULL, "Right Mixer"},
+	{"Out3 Mux", "VREF", "VREF"},
+	{"Out3 Mux", "Left + Right", "Out3 Left + Right"},
+	{"Out3 Mux", "ROUT2", "ROUT2"},
+	{"Out 3", NULL, "Out3 Mux"},
+	{"OUT3", NULL, "Out 3"},
+
+	/* out 4 */
+	{"Out4 Mux", "VREF", "VREF"},
+	{"Out4 Mux", "Capture ST", "Playback Mixer"},
+	{"Out4 Mux", "LOUT2", "LOUT2"},
+	{"Out 4", NULL, "Out4 Mux"},
+	{"OUT4", NULL, "Out 4"},
+
+	/* record mixer  */
+	{"Playback Mixer", "Left Capture Switch", "Left Mixer"},
+	{"Playback Mixer", "Voice Capture Switch", "Mono Mixer"},
+	{"Playback Mixer", "Right Capture Switch", "Right Mixer"},
+
+	/* Mic/SideTone Mux */
+	{"Mic Sidetone Mux", "Left PGA", "Left Capture Volume"},
+	{"Mic Sidetone Mux", "Right PGA", "Right Capture Volume"},
+	{"Mic Sidetone Mux", "Mic 1", "Mic 1 Volume"},
+	{"Mic Sidetone Mux", "Mic 2", "Mic 2 Volume"},
+
+	/* Capture Left Mux */
+	{"Capture Left Mux", "PGA", "Left Capture Volume"},
+	{"Capture Left Mux", "Line or RXP-RXN", "Line Left Mux"},
+	{"Capture Left Mux", "Line", "LINE1"},
+
+	/* Capture Right Mux */
+	{"Capture Right Mux", "PGA", "Right Capture Volume"},
+	{"Capture Right Mux", "Line or RXP-RXN", "Line Right Mux"},
+	{"Capture Right Mux", "Sidetone", "Playback Mixer"},
+
+	/* Mono Capture mixer-mux */
+	{"Capture Right Mixer", "Stereo", "Capture Right Mux"},
+	{"Capture Left Mixer", "Stereo", "Capture Left Mux"},
+	{"Capture Left Mixer", "Analogue Mix Left", "Capture Left Mux"},
+	{"Capture Left Mixer", "Analogue Mix Left", "Capture Right Mux"},
+	{"Capture Right Mixer", "Analogue Mix Right", "Capture Left Mux"},
+	{"Capture Right Mixer", "Analogue Mix Right", "Capture Right Mux"},
+	{"Capture Left Mixer", "Digital Mono Mix", "Capture Left Mux"},
+	{"Capture Left Mixer", "Digital Mono Mix", "Capture Right Mux"},
+	{"Capture Right Mixer", "Digital Mono Mix", "Capture Left Mux"},
+	{"Capture Right Mixer", "Digital Mono Mix", "Capture Right Mux"},
+
+	/* ADC */
+	{"Left ADC", NULL, "Capture Left Mixer"},
+	{"Right ADC", NULL, "Capture Right Mixer"},
+
+	/* Left Capture Volume */
+	{"Left Capture Volume", NULL, "ACIN"},
+
+	/* Right Capture Volume */
+	{"Right Capture Volume", NULL, "Mic 2 Volume"},
+
+	/* ALC Mixer */
+	{"ALC Mixer", "Line Capture Switch", "Line Mixer"},
+	{"ALC Mixer", "Mic2 Capture Switch", "Mic 2 Volume"},
+	{"ALC Mixer", "Mic1 Capture Switch", "Mic 1 Volume"},
+	{"ALC Mixer", "Rx Capture Switch", "Rx Mixer"},
+
+	/* Line Left Mux */
+	{"Line Left Mux", "Line 1", "LINE1"},
+	{"Line Left Mux", "Rx Mix", "Rx Mixer"},
+
+	/* Line Right Mux */
+	{"Line Right Mux", "Line 2", "LINE2"},
+	{"Line Right Mux", "Rx Mix", "Rx Mixer"},
+
+	/* Line Mono Mux */
+	{"Line Mono Mux", "Line Mix", "Line Mixer"},
+	{"Line Mono Mux", "Rx Mix", "Rx Mixer"},
+
+	/* Line Mixer/Mux */
+	{"Line Mixer", "Line 1 + 2", "LINE1"},
+	{"Line Mixer", "Line 1 - 2", "LINE1"},
+	{"Line Mixer", "Line 1 + 2", "LINE2"},
+	{"Line Mixer", "Line 1 - 2", "LINE2"},
+	{"Line Mixer", "Line 1", "LINE1"},
+	{"Line Mixer", "Line 2", "LINE2"},
+
+	/* Rx Mixer/Mux */
+	{"Rx Mixer", "RXP - RXN", "RXP"},
+	{"Rx Mixer", "RXP + RXN", "RXP"},
+	{"Rx Mixer", "RXP - RXN", "RXN"},
+	{"Rx Mixer", "RXP + RXN", "RXN"},
+	{"Rx Mixer", "RXP", "RXP"},
+	{"Rx Mixer", "RXN", "RXN"},
+
+	/* Mic 1 Volume */
+	{"Mic 1 Volume", NULL, "MIC1N"},
+	{"Mic 1 Volume", NULL, "Mic Selection Mux"},
+
+	/* Mic 2 Volume */
+	{"Mic 2 Volume", NULL, "MIC2N"},
+	{"Mic 2 Volume", NULL, "MIC2"},
+
+	/* Mic Selector Mux */
+	{"Mic Selection Mux", "Mic 1", "MIC1"},
+	{"Mic Selection Mux", "Mic 2", "MIC2N"},
+	{"Mic Selection Mux", "Mic 3", "MIC2"},
+
+	/* ACOP */
+	{"ACOP", NULL, "ALC Mixer"},
+};
+
+static int wm8753_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets,
+				  ARRAY_SIZE(wm8753_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+	u32 div2:1;
+	u32 n:4;
+	u32 k:24;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 22) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int target,
+	unsigned int source)
+{
+	u64 Kpart;
+	unsigned int K, Ndiv, Nmod;
+
+	Ndiv = target / source;
+	if (Ndiv < 6) {
+		source >>= 1;
+		pll_div->div2 = 1;
+		Ndiv = target / source;
+	} else
+		pll_div->div2 = 0;
+
+	if ((Ndiv < 6) || (Ndiv > 12))
+		printk(KERN_WARNING
+			"wm8753: unsupported N = %u\n", Ndiv);
+
+	pll_div->n = Ndiv;
+	Nmod = target % source;
+	Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	/* Check if we need to round */
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	K /= 10;
+
+	pll_div->k = K;
+}
+
+static int wm8753_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	u16 reg, enable;
+	int offset;
+	struct snd_soc_codec *codec = codec_dai->codec;
+
+	if (pll_id < WM8753_PLL1 || pll_id > WM8753_PLL2)
+		return -ENODEV;
+
+	if (pll_id == WM8753_PLL1) {
+		offset = 0;
+		enable = 0x10;
+		reg = snd_soc_read(codec, WM8753_CLOCK) & 0xffef;
+	} else {
+		offset = 4;
+		enable = 0x8;
+		reg = snd_soc_read(codec, WM8753_CLOCK) & 0xfff7;
+	}
+
+	if (!freq_in || !freq_out) {
+		/* disable PLL  */
+		snd_soc_write(codec, WM8753_PLL1CTL1 + offset, 0x0026);
+		snd_soc_write(codec, WM8753_CLOCK, reg);
+		return 0;
+	} else {
+		u16 value = 0;
+		struct _pll_div pll_div;
+
+		pll_factors(&pll_div, freq_out * 8, freq_in);
+
+		/* set up N and K PLL divisor ratios */
+		/* bits 8:5 = PLL_N, bits 3:0 = PLL_K[21:18] */
+		value = (pll_div.n << 5) + ((pll_div.k & 0x3c0000) >> 18);
+		snd_soc_write(codec, WM8753_PLL1CTL2 + offset, value);
+
+		/* bits 8:0 = PLL_K[17:9] */
+		value = (pll_div.k & 0x03fe00) >> 9;
+		snd_soc_write(codec, WM8753_PLL1CTL3 + offset, value);
+
+		/* bits 8:0 = PLL_K[8:0] */
+		value = pll_div.k & 0x0001ff;
+		snd_soc_write(codec, WM8753_PLL1CTL4 + offset, value);
+
+		/* set PLL as input and enable */
+		snd_soc_write(codec, WM8753_PLL1CTL1 + offset, 0x0027 |
+			(pll_div.div2 << 3));
+		snd_soc_write(codec, WM8753_CLOCK, reg | enable);
+	}
+	return 0;
+}
+
+struct _coeff_div {
+	u32 mclk;
+	u32 rate;
+	u8 sr:5;
+	u8 usb:1;
+};
+
+/* codec hifi mclk (after PLL) clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+	/* 8k */
+	{12288000, 8000, 0x6, 0x0},
+	{11289600, 8000, 0x16, 0x0},
+	{18432000, 8000, 0x7, 0x0},
+	{16934400, 8000, 0x17, 0x0},
+	{12000000, 8000, 0x6, 0x1},
+
+	/* 11.025k */
+	{11289600, 11025, 0x18, 0x0},
+	{16934400, 11025, 0x19, 0x0},
+	{12000000, 11025, 0x19, 0x1},
+
+	/* 16k */
+	{12288000, 16000, 0xa, 0x0},
+	{18432000, 16000, 0xb, 0x0},
+	{12000000, 16000, 0xa, 0x1},
+
+	/* 22.05k */
+	{11289600, 22050, 0x1a, 0x0},
+	{16934400, 22050, 0x1b, 0x0},
+	{12000000, 22050, 0x1b, 0x1},
+
+	/* 32k */
+	{12288000, 32000, 0xc, 0x0},
+	{18432000, 32000, 0xd, 0x0},
+	{12000000, 32000, 0xa, 0x1},
+
+	/* 44.1k */
+	{11289600, 44100, 0x10, 0x0},
+	{16934400, 44100, 0x11, 0x0},
+	{12000000, 44100, 0x11, 0x1},
+
+	/* 48k */
+	{12288000, 48000, 0x0, 0x0},
+	{18432000, 48000, 0x1, 0x0},
+	{12000000, 48000, 0x0, 0x1},
+
+	/* 88.2k */
+	{11289600, 88200, 0x1e, 0x0},
+	{16934400, 88200, 0x1f, 0x0},
+	{12000000, 88200, 0x1f, 0x1},
+
+	/* 96k */
+	{12288000, 96000, 0xe, 0x0},
+	{18432000, 96000, 0xf, 0x0},
+	{12000000, 96000, 0xe, 0x1},
+};
+
+static int get_coeff(int mclk, int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+		if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+			return i;
+	}
+	return -EINVAL;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int wm8753_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+
+	switch (freq) {
+	case 11289600:
+	case 12000000:
+	case 12288000:
+	case 16934400:
+	case 18432000:
+		if (clk_id == WM8753_MCLK) {
+			wm8753->sysclk = freq;
+			return 0;
+		} else if (clk_id == WM8753_PCMCLK) {
+			wm8753->pcmclk = freq;
+			return 0;
+		}
+		break;
+	}
+	return -EINVAL;
+}
+
+/*
+ * Set's ADC and Voice DAC format.
+ */
+static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 voice = snd_soc_read(codec, WM8753_PCM) & 0x01ec;
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		voice |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		voice |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		voice |= 0x0003;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		voice |= 0x0013;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8753_PCM, voice);
+	return 0;
+}
+
+static int wm8753_pcm_startup(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	wm8753_set_dai_mode(dai->codec, dai, 0);
+	return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+	u16 voice = snd_soc_read(codec, WM8753_PCM) & 0x01f3;
+	u16 srate = snd_soc_read(codec, WM8753_SRATE1) & 0x017f;
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		voice |= 0x0004;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		voice |= 0x0008;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		voice |= 0x000c;
+		break;
+	}
+
+	/* sample rate */
+	if (params_rate(params) * 384 == wm8753->pcmclk)
+		srate |= 0x80;
+	snd_soc_write(codec, WM8753_SRATE1, srate);
+
+	snd_soc_write(codec, WM8753_PCM, voice);
+	return 0;
+}
+
+/*
+ * Set's PCM dai fmt and BCLK.
+ */
+static int wm8753_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 voice, ioctl;
+
+	voice = snd_soc_read(codec, WM8753_PCM) & 0x011f;
+	ioctl = snd_soc_read(codec, WM8753_IOCTL) & 0x015d;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		ioctl |= 0x2;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		voice |= 0x0040;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		/* frame inversion not valid for DSP modes */
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			voice |= 0x0080;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_LEFT_J:
+		voice &= ~0x0010;
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			voice |= 0x0090;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			voice |= 0x0080;
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			voice |= 0x0010;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8753_PCM, voice);
+	snd_soc_write(codec, WM8753_IOCTL, ioctl);
+	return 0;
+}
+
+static int wm8753_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+		int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+
+	switch (div_id) {
+	case WM8753_PCMDIV:
+		reg = snd_soc_read(codec, WM8753_CLOCK) & 0x003f;
+		snd_soc_write(codec, WM8753_CLOCK, reg | div);
+		break;
+	case WM8753_BCLKDIV:
+		reg = snd_soc_read(codec, WM8753_SRATE2) & 0x01c7;
+		snd_soc_write(codec, WM8753_SRATE2, reg | div);
+		break;
+	case WM8753_VXCLKDIV:
+		reg = snd_soc_read(codec, WM8753_SRATE2) & 0x003f;
+		snd_soc_write(codec, WM8753_SRATE2, reg | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * Set's HiFi DAC format.
+ */
+static int wm8753_hdac_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 hifi = snd_soc_read(codec, WM8753_HIFI) & 0x01e0;
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		hifi |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		hifi |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		hifi |= 0x0003;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		hifi |= 0x0013;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8753_HIFI, hifi);
+	return 0;
+}
+
+/*
+ * Set's I2S DAI format.
+ */
+static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 ioctl, hifi;
+
+	hifi = snd_soc_read(codec, WM8753_HIFI) & 0x011f;
+	ioctl = snd_soc_read(codec, WM8753_IOCTL) & 0x00ae;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		ioctl |= 0x1;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		hifi |= 0x0040;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		/* frame inversion not valid for DSP modes */
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			hifi |= 0x0080;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_LEFT_J:
+		hifi &= ~0x0010;
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			hifi |= 0x0090;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			hifi |= 0x0080;
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			hifi |= 0x0010;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8753_HIFI, hifi);
+	snd_soc_write(codec, WM8753_IOCTL, ioctl);
+	return 0;
+}
+
+static int wm8753_i2s_startup(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	wm8753_set_dai_mode(dai->codec, dai, 1);
+	return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+	u16 srate = snd_soc_read(codec, WM8753_SRATE1) & 0x01c0;
+	u16 hifi = snd_soc_read(codec, WM8753_HIFI) & 0x01f3;
+	int coeff;
+
+	/* is digital filter coefficient valid ? */
+	coeff = get_coeff(wm8753->sysclk, params_rate(params));
+	if (coeff < 0) {
+		printk(KERN_ERR "wm8753 invalid MCLK or rate\n");
+		return coeff;
+	}
+	snd_soc_write(codec, WM8753_SRATE1, srate | (coeff_div[coeff].sr << 1) |
+		coeff_div[coeff].usb);
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		hifi |= 0x0004;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		hifi |= 0x0008;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		hifi |= 0x000c;
+		break;
+	}
+
+	snd_soc_write(codec, WM8753_HIFI, hifi);
+	return 0;
+}
+
+static int wm8753_mode1v_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 clock;
+
+	/* set clk source as pcmclk */
+	clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb;
+	snd_soc_write(codec, WM8753_CLOCK, clock);
+
+	if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
+		return -EINVAL;
+	return wm8753_pcm_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mode1h_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0)
+		return -EINVAL;
+	return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mode2_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 clock;
+
+	/* set clk source as pcmclk */
+	clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb;
+	snd_soc_write(codec, WM8753_CLOCK, clock);
+
+	if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
+		return -EINVAL;
+	return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 clock;
+
+	/* set clk source as mclk */
+	clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb;
+	snd_soc_write(codec, WM8753_CLOCK, clock | 0x4);
+
+	if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0)
+		return -EINVAL;
+	if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
+		return -EINVAL;
+	return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+}
+
+static int wm8753_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = snd_soc_read(codec, WM8753_DAC) & 0xfff7;
+	struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+
+	/* the digital mute covers the HiFi and Voice DAC's on the WM8753.
+	 * make sure we check if they are not both active when we mute */
+	if (mute && wm8753->dai_func == 1) {
+		if (!codec->active)
+			snd_soc_write(codec, WM8753_DAC, mute_reg | 0x8);
+	} else {
+		if (mute)
+			snd_soc_write(codec, WM8753_DAC, mute_reg | 0x8);
+		else
+			snd_soc_write(codec, WM8753_DAC, mute_reg);
+	}
+
+	return 0;
+}
+
+static int wm8753_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 pwr_reg = snd_soc_read(codec, WM8753_PWR1) & 0xfe3e;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		/* set vmid to 50k and unmute dac */
+		snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x00c0);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		/* set vmid to 5k for quick power up */
+		snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x01c1);
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		/* mute dac and set vmid to 500k, enable VREF */
+		snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x0141);
+		break;
+	case SND_SOC_BIAS_OFF:
+		snd_soc_write(codec, WM8753_PWR1, 0x0001);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8753_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+		SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
+		SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define WM8753_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+/*
+ * The WM8753 supports upto 4 different and mutually exclusive DAI
+ * configurations. This gives 2 PCM's available for use, hifi and voice.
+ * NOTE: The Voice PCM cannot play or capture audio to the CPU as it's DAI
+ * is connected between the wm8753 and a BT codec or GSM modem.
+ *
+ * 1. Voice over PCM DAI - HIFI DAC over HIFI DAI
+ * 2. Voice over HIFI DAI - HIFI disabled
+ * 3. Voice disabled - HIFI over HIFI
+ * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture
+ */
+static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode1 = {
+	.startup = wm8753_i2s_startup,
+	.hw_params	= wm8753_i2s_hw_params,
+	.digital_mute	= wm8753_mute,
+	.set_fmt	= wm8753_mode1h_set_dai_fmt,
+	.set_clkdiv	= wm8753_set_dai_clkdiv,
+	.set_pll	= wm8753_set_dai_pll,
+	.set_sysclk	= wm8753_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode1 = {
+	.startup = wm8753_pcm_startup,
+	.hw_params	= wm8753_pcm_hw_params,
+	.digital_mute	= wm8753_mute,
+	.set_fmt	= wm8753_mode1v_set_dai_fmt,
+	.set_clkdiv	= wm8753_set_dai_clkdiv,
+	.set_pll	= wm8753_set_dai_pll,
+	.set_sysclk	= wm8753_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode2 = {
+	.startup = wm8753_pcm_startup,
+	.hw_params	= wm8753_pcm_hw_params,
+	.digital_mute	= wm8753_mute,
+	.set_fmt	= wm8753_mode2_set_dai_fmt,
+	.set_clkdiv	= wm8753_set_dai_clkdiv,
+	.set_pll	= wm8753_set_dai_pll,
+	.set_sysclk	= wm8753_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode3	= {
+	.startup = wm8753_i2s_startup,
+	.hw_params	= wm8753_i2s_hw_params,
+	.digital_mute	= wm8753_mute,
+	.set_fmt	= wm8753_mode3_4_set_dai_fmt,
+	.set_clkdiv	= wm8753_set_dai_clkdiv,
+	.set_pll	= wm8753_set_dai_pll,
+	.set_sysclk	= wm8753_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode4	= {
+	.startup = wm8753_i2s_startup,
+	.hw_params	= wm8753_i2s_hw_params,
+	.digital_mute	= wm8753_mute,
+	.set_fmt	= wm8753_mode3_4_set_dai_fmt,
+	.set_clkdiv	= wm8753_set_dai_clkdiv,
+	.set_pll	= wm8753_set_dai_pll,
+	.set_sysclk	= wm8753_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8753_all_dai[] = {
+/* DAI HiFi mode 1 */
+{	.name = "wm8753-hifi",
+	.playback = {
+		.stream_name = "HiFi Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8753_RATES,
+		.formats = WM8753_FORMATS},
+	.capture = { /* dummy for fast DAI switching */
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8753_RATES,
+		.formats = WM8753_FORMATS},
+	.ops = &wm8753_dai_ops_hifi_mode1,
+},
+/* DAI Voice mode 1 */
+{	.name = "wm8753-voice",
+	.playback = {
+		.stream_name = "Voice Playback",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = WM8753_RATES,
+		.formats = WM8753_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8753_RATES,
+		.formats = WM8753_FORMATS,},
+	.ops = &wm8753_dai_ops_voice_mode1,
+},
+/* DAI HiFi mode 2 - dummy */
+{	.name = "wm8753-hifi",
+},
+/* DAI Voice mode 2 */
+{	.name = "wm8753-voice",
+	.playback = {
+		.stream_name = "Voice Playback",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = WM8753_RATES,
+		.formats = WM8753_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8753_RATES,
+		.formats = WM8753_FORMATS,},
+	.ops = &wm8753_dai_ops_voice_mode2,
+},
+/* DAI HiFi mode 3 */
+{	.name = "wm8753-hifi",
+	.playback = {
+		.stream_name = "HiFi Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8753_RATES,
+		.formats = WM8753_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8753_RATES,
+		.formats = WM8753_FORMATS,},
+	.ops = &wm8753_dai_ops_hifi_mode3,
+},
+/* DAI Voice mode 3 - dummy */
+{	.name = "wm8753-voice",
+},
+/* DAI HiFi mode 4 */
+{	.name = "wm8753-hifi",
+	.playback = {
+		.stream_name = "HiFi Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8753_RATES,
+		.formats = WM8753_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8753_RATES,
+		.formats = WM8753_FORMATS,},
+	.ops = &wm8753_dai_ops_hifi_mode4,
+},
+/* DAI Voice mode 4 - dummy */
+{	.name = "wm8753-voice",
+},
+};
+
+static struct snd_soc_dai_driver wm8753_dai[] = {
+	{
+		.name = "wm8753-aif0",
+	},
+	{
+		.name = "wm8753-aif1",
+	},
+};
+
+static void wm8753_set_dai_mode(struct snd_soc_codec *codec,
+		struct snd_soc_dai *dai, unsigned int hifi)
+{
+	struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+
+	if (wm8753->dai_func < 4) {
+		if (hifi)
+			dai->driver = &wm8753_all_dai[wm8753->dai_func << 1];
+		else
+			dai->driver = &wm8753_all_dai[(wm8753->dai_func << 1) + 1];
+	}
+	snd_soc_write(codec, WM8753_IOCTL, wm8753->dai_func);
+}
+
+static void wm8753_work(struct work_struct *work)
+{
+	struct snd_soc_codec *codec =
+		container_of(work, struct snd_soc_codec, delayed_work.work);
+	wm8753_set_bias_level(codec, codec->bias_level);
+}
+
+static int wm8753_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8753_resume(struct snd_soc_codec *codec)
+{
+	u16 *reg_cache = codec->reg_cache;
+	int i;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 1; i < ARRAY_SIZE(wm8753_reg); i++) {
+		if (i == WM8753_RESET)
+			continue;
+
+		/* No point in writing hardware default values back */
+		if (reg_cache[i] == wm8753_reg[i])
+			continue;
+
+		snd_soc_write(codec, i, reg_cache[i]);
+	}
+
+	wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* charge wm8753 caps */
+	if (codec->suspend_bias_level == SND_SOC_BIAS_ON) {
+		wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+		codec->bias_level = SND_SOC_BIAS_ON;
+		schedule_delayed_work(&codec->delayed_work,
+			msecs_to_jiffies(caps_charge));
+	}
+
+	return 0;
+}
+
+/*
+ * This function forces any delayed work to be queued and run.
+ */
+static int run_delayed_work(struct delayed_work *dwork)
+{
+	int ret;
+
+	/* cancel any work waiting to be queued. */
+	ret = cancel_delayed_work(dwork);
+
+	/* if there was any work waiting then we run it now and
+	 * wait for it's completion */
+	if (ret) {
+		schedule_delayed_work(dwork, 0);
+		flush_scheduled_work();
+	}
+	return ret;
+}
+
+static int wm8753_probe(struct snd_soc_codec *codec)
+{
+	struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	INIT_DELAYED_WORK(&codec->delayed_work, wm8753_work);
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8753->control_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm8753_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+		return ret;
+	}
+
+	wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	wm8753->dai_func = 0;
+
+	/* charge output caps */
+	wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+	schedule_delayed_work(&codec->delayed_work,
+			      msecs_to_jiffies(caps_charge));
+
+	/* set the update bits */
+	snd_soc_update_bits(codec, WM8753_LDAC, 0x0100, 0x0100);
+	snd_soc_update_bits(codec, WM8753_RDAC, 0x0100, 0x0100);
+	snd_soc_update_bits(codec, WM8753_LDAC, 0x0100, 0x0100);
+	snd_soc_update_bits(codec, WM8753_RDAC, 0x0100, 0x0100);
+	snd_soc_update_bits(codec, WM8753_LOUT1V, 0x0100, 0x0100);
+	snd_soc_update_bits(codec, WM8753_ROUT1V, 0x0100, 0x0100);
+	snd_soc_update_bits(codec, WM8753_LOUT2V, 0x0100, 0x0100);
+	snd_soc_update_bits(codec, WM8753_ROUT2V, 0x0100, 0x0100);
+	snd_soc_update_bits(codec, WM8753_LINVOL, 0x0100, 0x0100);
+	snd_soc_update_bits(codec, WM8753_RINVOL, 0x0100, 0x0100);
+
+	snd_soc_add_controls(codec, wm8753_snd_controls,
+			     ARRAY_SIZE(wm8753_snd_controls));
+	wm8753_add_widgets(codec);
+
+	return 0;
+}
+
+/* power down chip */
+static int wm8753_remove(struct snd_soc_codec *codec)
+{
+	run_delayed_work(&codec->delayed_work);
+	wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8753 = {
+	.probe =	wm8753_probe,
+	.remove =	wm8753_remove,
+	.suspend =	wm8753_suspend,
+	.resume =	wm8753_resume,
+	.set_bias_level = wm8753_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8753_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8753_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8753_spi_probe(struct spi_device *spi)
+{
+	struct wm8753_priv *wm8753;
+	int ret;
+
+	wm8753 = kzalloc(sizeof(struct wm8753_priv), GFP_KERNEL);
+	if (wm8753 == NULL)
+		return -ENOMEM;
+
+	wm8753->control_type = SND_SOC_SPI;
+	spi_set_drvdata(spi, wm8753);
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_dev_wm8753, wm8753_dai, ARRAY_SIZE(wm8753_dai));
+	if (ret < 0)
+		kfree(wm8753);
+	return ret;
+}
+
+static int __devexit wm8753_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver wm8753_spi_driver = {
+	.driver = {
+		.name	= "wm8753-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= wm8753_spi_probe,
+	.remove		= __devexit_p(wm8753_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8753_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8753_priv *wm8753;
+	int ret;
+
+	wm8753 = kzalloc(sizeof(struct wm8753_priv), GFP_KERNEL);
+	if (wm8753 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8753);
+	wm8753->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8753, wm8753_dai, ARRAY_SIZE(wm8753_dai));
+	if (ret < 0)
+		kfree(wm8753);
+	return ret;
+}
+
+static __devexit int wm8753_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8753_i2c_id[] = {
+	{ "wm8753", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8753_i2c_id);
+
+static struct i2c_driver wm8753_i2c_driver = {
+	.driver = {
+		.name = "wm8753-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8753_i2c_probe,
+	.remove =   __devexit_p(wm8753_i2c_remove),
+	.id_table = wm8753_i2c_id,
+};
+#endif
+
+static int __init wm8753_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8753_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8753 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&wm8753_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8753 SPI driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8753_modinit);
+
+static void __exit wm8753_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8753_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&wm8753_spi_driver);
+#endif
+}
+module_exit(wm8753_exit);
+
+MODULE_DESCRIPTION("ASoC WM8753 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8753.h b/linux/sound/soc/codecs/wm8753.h
new file mode 100644
index 0000000..94edac1
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8753.h
@@ -0,0 +1,118 @@
+/*
+ * wm8753.h  --  audio driver for WM8753
+ *
+ * Copyright 2003 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ *  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.
+ *
+ */
+
+#ifndef _WM8753_H
+#define _WM8753_H
+
+/* WM8753 register space */
+
+#define WM8753_DAC		0x01
+#define WM8753_ADC		0x02
+#define WM8753_PCM		0x03
+#define WM8753_HIFI		0x04
+#define WM8753_IOCTL		0x05
+#define WM8753_SRATE1		0x06
+#define WM8753_SRATE2		0x07
+#define WM8753_LDAC		0x08
+#define WM8753_RDAC		0x09
+#define WM8753_BASS		0x0a
+#define WM8753_TREBLE		0x0b
+#define WM8753_ALC1		0x0c
+#define WM8753_ALC2		0x0d
+#define WM8753_ALC3		0x0e
+#define WM8753_NGATE		0x0f
+#define WM8753_LADC		0x10
+#define WM8753_RADC		0x11
+#define WM8753_ADCTL1		0x12
+#define WM8753_3D		0x13
+#define WM8753_PWR1		0x14
+#define WM8753_PWR2		0x15
+#define WM8753_PWR3		0x16
+#define WM8753_PWR4		0x17
+#define WM8753_ID		0x18
+#define WM8753_INTPOL		0x19
+#define WM8753_INTEN		0x1a
+#define WM8753_GPIO1		0x1b
+#define WM8753_GPIO2		0x1c
+#define WM8753_RESET		0x1f
+#define WM8753_RECMIX1		0x20
+#define WM8753_RECMIX2		0x21
+#define WM8753_LOUTM1		0x22
+#define WM8753_LOUTM2		0x23
+#define WM8753_ROUTM1		0x24
+#define WM8753_ROUTM2		0x25
+#define WM8753_MOUTM1		0x26
+#define WM8753_MOUTM2		0x27
+#define WM8753_LOUT1V		0x28
+#define WM8753_ROUT1V		0x29
+#define WM8753_LOUT2V		0x2a
+#define WM8753_ROUT2V		0x2b
+#define WM8753_MOUTV		0x2c
+#define WM8753_OUTCTL		0x2d
+#define WM8753_ADCIN		0x2e
+#define WM8753_INCTL1		0x2f
+#define WM8753_INCTL2		0x30
+#define WM8753_LINVOL		0x31
+#define WM8753_RINVOL		0x32
+#define WM8753_MICBIAS		0x33
+#define WM8753_CLOCK		0x34
+#define WM8753_PLL1CTL1		0x35
+#define WM8753_PLL1CTL2		0x36
+#define WM8753_PLL1CTL3		0x37
+#define WM8753_PLL1CTL4		0x38
+#define WM8753_PLL2CTL1		0x39
+#define WM8753_PLL2CTL2		0x3a
+#define WM8753_PLL2CTL3		0x3b
+#define WM8753_PLL2CTL4		0x3c
+#define WM8753_BIASCTL		0x3d
+#define WM8753_ADCTL2		0x3f
+
+#define WM8753_PLL1			0
+#define WM8753_PLL2			1
+
+/* clock inputs */
+#define WM8753_MCLK		0
+#define WM8753_PCMCLK		1
+
+/* clock divider id's */
+#define WM8753_PCMDIV		0
+#define WM8753_BCLKDIV		1
+#define WM8753_VXCLKDIV		2
+
+/* PCM clock dividers */
+#define WM8753_PCM_DIV_1	(0 << 6)
+#define WM8753_PCM_DIV_3	(2 << 6)
+#define WM8753_PCM_DIV_5_5	(3 << 6)
+#define WM8753_PCM_DIV_2	(4 << 6)
+#define WM8753_PCM_DIV_4	(5 << 6)
+#define WM8753_PCM_DIV_6	(6 << 6)
+#define WM8753_PCM_DIV_8	(7 << 6)
+
+/* BCLK clock dividers */
+#define WM8753_BCLK_DIV_1	(0 << 3)
+#define WM8753_BCLK_DIV_2	(1 << 3)
+#define WM8753_BCLK_DIV_4	(2 << 3)
+#define WM8753_BCLK_DIV_8	(3 << 3)
+#define WM8753_BCLK_DIV_16	(4 << 3)
+
+/* VXCLK clock dividers */
+#define WM8753_VXCLK_DIV_1	(0 << 6)
+#define WM8753_VXCLK_DIV_2	(1 << 6)
+#define WM8753_VXCLK_DIV_4	(2 << 6)
+#define WM8753_VXCLK_DIV_8	(3 << 6)
+#define WM8753_VXCLK_DIV_16	(4 << 6)
+
+#define WM8753_DAI_HIFI		0
+#define WM8753_DAI_VOICE		1
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8776.c b/linux/sound/soc/codecs/wm8776.c
new file mode 100644
index 0000000..0132a27
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8776.c
@@ -0,0 +1,571 @@
+/*
+ * wm8776.c  --  WM8776 ALSA SoC Audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO: Input ALC/limiter support
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8776.h"
+
+/* codec private data */
+struct wm8776_priv {
+	enum snd_soc_control_type control_type;
+	int sysclk[2];
+};
+
+static const u16 wm8776_reg[WM8776_CACHEREGNUM] = {
+	0x79, 0x79, 0x79, 0xff, 0xff,  /* 4 */
+	0xff, 0x00, 0x90, 0x00, 0x00,  /* 9 */
+	0x22, 0x22, 0x22, 0x08, 0xcf,  /* 14 */
+	0xcf, 0x7b, 0x00, 0x32, 0x00,  /* 19 */
+	0xa6, 0x01, 0x01
+};
+
+static int wm8776_reset(struct snd_soc_codec *codec)
+{
+	return snd_soc_write(codec, WM8776_RESET, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(hp_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -10350, 50, 1);
+
+static const struct snd_kcontrol_new wm8776_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8776_HPLVOL, WM8776_HPRVOL,
+		 0, 127, 0, hp_tlv),
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8776_DACLVOL, WM8776_DACRVOL,
+		 0, 255, 0, dac_tlv),
+SOC_SINGLE("Digital Playback ZC Switch", WM8776_DACCTRL1, 0, 1, 0),
+
+SOC_SINGLE("Deemphasis Switch", WM8776_DACCTRL2, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("Capture Volume", WM8776_ADCLVOL, WM8776_ADCRVOL,
+		 0, 255, 0, adc_tlv),
+SOC_DOUBLE("Capture Switch", WM8776_ADCMUX, 7, 6, 1, 1),
+SOC_DOUBLE_R("Capture ZC Switch", WM8776_ADCLVOL, WM8776_ADCRVOL, 8, 1, 0),
+SOC_SINGLE("Capture HPF Switch", WM8776_ADCIFCTRL, 8, 1, 1),
+};
+
+static const struct snd_kcontrol_new inmix_controls[] = {
+SOC_DAPM_SINGLE("AIN1 Switch", WM8776_ADCMUX, 0, 1, 0),
+SOC_DAPM_SINGLE("AIN2 Switch", WM8776_ADCMUX, 1, 1, 0),
+SOC_DAPM_SINGLE("AIN3 Switch", WM8776_ADCMUX, 2, 1, 0),
+SOC_DAPM_SINGLE("AIN4 Switch", WM8776_ADCMUX, 3, 1, 0),
+SOC_DAPM_SINGLE("AIN5 Switch", WM8776_ADCMUX, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new outmix_controls[] = {
+SOC_DAPM_SINGLE("DAC Switch", WM8776_OUTMUX, 0, 1, 0),
+SOC_DAPM_SINGLE("AUX Switch", WM8776_OUTMUX, 1, 1, 0),
+SOC_DAPM_SINGLE("Bypass Switch", WM8776_OUTMUX, 2, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8776_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("AUX"),
+
+SND_SOC_DAPM_INPUT("AIN1"),
+SND_SOC_DAPM_INPUT("AIN2"),
+SND_SOC_DAPM_INPUT("AIN3"),
+SND_SOC_DAPM_INPUT("AIN4"),
+SND_SOC_DAPM_INPUT("AIN5"),
+
+SND_SOC_DAPM_MIXER("Input Mixer", WM8776_PWRDOWN, 6, 1,
+		   inmix_controls, ARRAY_SIZE(inmix_controls)),
+
+SND_SOC_DAPM_ADC("ADC", "Capture", WM8776_PWRDOWN, 1, 1),
+SND_SOC_DAPM_DAC("DAC", "Playback", WM8776_PWRDOWN, 2, 1),
+
+SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0,
+		   outmix_controls, ARRAY_SIZE(outmix_controls)),
+
+SND_SOC_DAPM_PGA("Headphone PGA", WM8776_PWRDOWN, 3, 1, NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("VOUT"),
+
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+};
+
+static const struct snd_soc_dapm_route routes[] = {
+	{ "Input Mixer", "AIN1 Switch", "AIN1" },
+	{ "Input Mixer", "AIN2 Switch", "AIN2" },
+	{ "Input Mixer", "AIN3 Switch", "AIN3" },
+	{ "Input Mixer", "AIN4 Switch", "AIN4" },
+	{ "Input Mixer", "AIN5 Switch", "AIN5" },
+
+	{ "ADC", NULL, "Input Mixer" },
+
+	{ "Output Mixer", "DAC Switch", "DAC" },
+	{ "Output Mixer", "AUX Switch", "AUX" },
+	{ "Output Mixer", "Bypass Switch", "Input Mixer" },
+
+	{ "VOUT", NULL, "Output Mixer" },
+
+	{ "Headphone PGA", NULL, "Output Mixer" },
+
+	{ "HPOUTL", NULL, "Headphone PGA" },
+	{ "HPOUTR", NULL, "Headphone PGA" },
+};
+
+static int wm8776_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	int reg, iface, master;
+
+	switch (dai->driver->id) {
+	case WM8776_DAI_DAC:
+		reg = WM8776_DACIFCTRL;
+		master = 0x80;
+		break;
+	case WM8776_DAI_ADC:
+		reg = WM8776_ADCIFCTRL;
+		master = 0x100;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	iface = 0;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		master = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x0001;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x00c;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x008;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x004;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Finally, write out the values */
+	snd_soc_update_bits(codec, reg, 0xf, iface);
+	snd_soc_update_bits(codec, WM8776_MSTRCTRL, 0x180, master);
+
+	return 0;
+}
+
+static int mclk_ratios[] = {
+	128,
+	192,
+	256,
+	384,
+	512,
+	768,
+};
+
+static int wm8776_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec);
+	int iface_reg, iface;
+	int ratio_shift, master;
+	int i;
+
+	iface = 0;
+
+	switch (dai->driver->id) {
+	case WM8776_DAI_DAC:
+		iface_reg = WM8776_DACIFCTRL;
+		master = 0x80;
+		ratio_shift = 4;
+		break;
+	case WM8776_DAI_ADC:
+		iface_reg = WM8776_ADCIFCTRL;
+		master = 0x100;
+		ratio_shift = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+
+	/* Set word length */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x10;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x20;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface |= 0x30;
+		break;
+	}
+
+	/* Only need to set MCLK/LRCLK ratio if we're master */
+	if (snd_soc_read(codec, WM8776_MSTRCTRL) & master) {
+		for (i = 0; i < ARRAY_SIZE(mclk_ratios); i++) {
+			if (wm8776->sysclk[dai->driver->id] / params_rate(params)
+			    == mclk_ratios[i])
+				break;
+		}
+
+		if (i == ARRAY_SIZE(mclk_ratios)) {
+			dev_err(codec->dev,
+				"Unable to configure MCLK ratio %d/%d\n",
+				wm8776->sysclk[dai->driver->id], params_rate(params));
+			return -EINVAL;
+		}
+
+		dev_dbg(codec->dev, "MCLK is %dfs\n", mclk_ratios[i]);
+
+		snd_soc_update_bits(codec, WM8776_MSTRCTRL,
+				    0x7 << ratio_shift, i << ratio_shift);
+	} else {
+		dev_dbg(codec->dev, "DAI in slave mode\n");
+	}
+
+	snd_soc_update_bits(codec, iface_reg, 0x30, iface);
+
+	return 0;
+}
+
+static int wm8776_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+
+	return snd_soc_write(codec, WM8776_DACMUTE, !!mute);
+}
+
+static int wm8776_set_sysclk(struct snd_soc_dai *dai,
+			     int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec);
+
+	BUG_ON(dai->driver->id >= ARRAY_SIZE(wm8776->sysclk));
+
+	wm8776->sysclk[dai->driver->id] = freq;
+
+	return 0;
+}
+
+static int wm8776_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Disable the global powerdown; DAPM does the rest */
+			snd_soc_update_bits(codec, WM8776_PWRDOWN, 1, 0);
+		}
+
+		break;
+	case SND_SOC_BIAS_OFF:
+		snd_soc_update_bits(codec, WM8776_PWRDOWN, 1, 1);
+		break;
+	}
+
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8776_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+		      SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
+		      SNDRV_PCM_RATE_96000)
+
+
+#define WM8776_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8776_dac_ops = {
+	.digital_mute	= wm8776_mute,
+	.hw_params      = wm8776_hw_params,
+	.set_fmt        = wm8776_set_fmt,
+	.set_sysclk     = wm8776_set_sysclk,
+};
+
+static struct snd_soc_dai_ops wm8776_adc_ops = {
+	.hw_params      = wm8776_hw_params,
+	.set_fmt        = wm8776_set_fmt,
+	.set_sysclk     = wm8776_set_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8776_dai[] = {
+	{
+		.name = "wm8776-hifi-playback",
+		.id	= WM8776_DAI_DAC,
+		.playback = {
+			.stream_name = "Playback",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = WM8776_RATES,
+			.formats = WM8776_FORMATS,
+		},
+		.ops = &wm8776_dac_ops,
+	},
+	{
+		.name = "wm8776-hifi-capture",
+		.id	= WM8776_DAI_ADC,
+		.capture = {
+			.stream_name = "Capture",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = WM8776_RATES,
+			.formats = WM8776_FORMATS,
+		},
+		.ops = &wm8776_adc_ops,
+	},
+};
+
+#ifdef CONFIG_PM
+static int wm8776_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8776_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm8776_resume(struct snd_soc_codec *codec)
+{
+	int i;
+	u8 data[2];
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 0; i < ARRAY_SIZE(wm8776_reg); i++) {
+		if (cache[i] == wm8776_reg[i])
+			continue;
+		data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+		data[1] = cache[i] & 0x00ff;
+		codec->hw_write(codec->control_data, data, 2);
+	}
+
+	wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+#else
+#define wm8776_suspend NULL
+#define wm8776_resume NULL
+#endif
+
+static int wm8776_probe(struct snd_soc_codec *codec)
+{
+	struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8776->control_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm8776_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+		return ret;
+	}
+
+	wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Latch the update bits; right channel only since we always
+	 * update both. */
+	snd_soc_update_bits(codec, WM8776_HPRVOL, 0x100, 0x100);
+	snd_soc_update_bits(codec, WM8776_DACRVOL, 0x100, 0x100);
+
+	snd_soc_add_controls(codec, wm8776_snd_controls,
+			     ARRAY_SIZE(wm8776_snd_controls));
+	snd_soc_dapm_new_controls(codec, wm8776_dapm_widgets,
+				  ARRAY_SIZE(wm8776_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, routes, ARRAY_SIZE(routes));
+
+	return ret;
+}
+
+/* power down chip */
+static int wm8776_remove(struct snd_soc_codec *codec)
+{
+	wm8776_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8776 = {
+	.probe = 	wm8776_probe,
+	.remove = 	wm8776_remove,
+	.suspend = 	wm8776_suspend,
+	.resume =	wm8776_resume,
+	.set_bias_level = wm8776_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8776_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8776_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8776_spi_probe(struct spi_device *spi)
+{
+	struct wm8776_priv *wm8776;
+	int ret;
+
+	wm8776 = kzalloc(sizeof(struct wm8776_priv), GFP_KERNEL);
+	if (wm8776 == NULL)
+		return -ENOMEM;
+
+	wm8776->control_type = SND_SOC_SPI;
+	spi_set_drvdata(spi, wm8776);
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_dev_wm8776, wm8776_dai, ARRAY_SIZE(wm8776_dai));
+	if (ret < 0)
+		kfree(wm8776);
+	return ret;
+}
+
+static int __devexit wm8776_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver wm8776_spi_driver = {
+	.driver = {
+		.name	= "wm8776-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= wm8776_spi_probe,
+	.remove		= __devexit_p(wm8776_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8776_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8776_priv *wm8776;
+	int ret;
+
+	wm8776 = kzalloc(sizeof(struct wm8776_priv), GFP_KERNEL);
+	if (wm8776 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8776);
+	wm8776->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8776, wm8776_dai, ARRAY_SIZE(wm8776_dai));
+	if (ret < 0)
+		kfree(wm8776);
+	return ret;
+}
+
+static __devexit int wm8776_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8776_i2c_id[] = {
+	{ "wm8776", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8776_i2c_id);
+
+static struct i2c_driver wm8776_i2c_driver = {
+	.driver = {
+		.name = "wm8776-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8776_i2c_probe,
+	.remove =   __devexit_p(wm8776_i2c_remove),
+	.id_table = wm8776_i2c_id,
+};
+#endif
+
+static int __init wm8776_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8776_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8776 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&wm8776_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8776 SPI driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8776_modinit);
+
+static void __exit wm8776_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8776_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&wm8776_spi_driver);
+#endif
+}
+module_exit(wm8776_exit);
+
+MODULE_DESCRIPTION("ASoC WM8776 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8776.h b/linux/sound/soc/codecs/wm8776.h
new file mode 100644
index 0000000..4cf1c8e
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8776.h
@@ -0,0 +1,48 @@
+/*
+ * wm8776.h  --  WM8776 ASoC driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8776_H
+#define _WM8776_H
+
+/* Registers */
+
+#define WM8776_HPLVOL    0x00
+#define WM8776_HPRVOL    0x01
+#define WM8776_HPMASTER  0x02
+#define WM8776_DACLVOL   0x03
+#define WM8776_DACRVOL   0x04
+#define WM8776_DACMASTER 0x05
+#define WM8776_PHASESWAP 0x06
+#define WM8776_DACCTRL1  0x07
+#define WM8776_DACMUTE   0x08
+#define WM8776_DACCTRL2  0x09
+#define WM8776_DACIFCTRL 0x0a
+#define WM8776_ADCIFCTRL 0x0b
+#define WM8776_MSTRCTRL  0x0c
+#define WM8776_PWRDOWN   0x0d
+#define WM8776_ADCLVOL   0x0e
+#define WM8776_ADCRVOL   0x0f
+#define WM8776_ALCCTRL1  0x10
+#define WM8776_ALCCTRL2  0x11
+#define WM8776_ALCCTRL3  0x12
+#define WM8776_NOISEGATE 0x13
+#define WM8776_LIMITER   0x14
+#define WM8776_ADCMUX    0x15
+#define WM8776_OUTMUX    0x16
+#define WM8776_RESET     0x17
+
+#define WM8776_CACHEREGNUM 0x17
+
+#define WM8776_DAI_DAC 0
+#define WM8776_DAI_ADC 1
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8804.c b/linux/sound/soc/codecs/wm8804.c
new file mode 100644
index 0000000..4599e8e
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8804.c
@@ -0,0 +1,833 @@
+/*
+ * wm8804.c  --  WM8804 S/PDIF transceiver driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8804.h"
+
+#define WM8804_NUM_SUPPLIES 2
+static const char *wm8804_supply_names[WM8804_NUM_SUPPLIES] = {
+	"PVDD",
+	"DVDD"
+};
+
+static const u8 wm8804_reg_defs[] = {
+	0x05,     /* R0  - RST/DEVID1 */
+	0x88,     /* R1  - DEVID2 */
+	0x04,     /* R2  - DEVREV */
+	0x21,     /* R3  - PLL1 */
+	0xFD,     /* R4  - PLL2 */
+	0x36,     /* R5  - PLL3 */
+	0x07,     /* R6  - PLL4 */
+	0x16,     /* R7  - PLL5 */
+	0x18,     /* R8  - PLL6 */
+	0xFF,     /* R9  - SPDMODE */
+	0x00,     /* R10 - INTMASK */
+	0x00,     /* R11 - INTSTAT */
+	0x00,     /* R12 - SPDSTAT */
+	0x00,     /* R13 - RXCHAN1 */
+	0x00,     /* R14 - RXCHAN2 */
+	0x00,     /* R15 - RXCHAN3 */
+	0x00,     /* R16 - RXCHAN4 */
+	0x00,     /* R17 - RXCHAN5 */
+	0x00,     /* R18 - SPDTX1 */
+	0x00,     /* R19 - SPDTX2 */
+	0x00,     /* R20 - SPDTX3 */
+	0x71,     /* R21 - SPDTX4 */
+	0x0B,     /* R22 - SPDTX5 */
+	0x70,     /* R23 - GPO0 */
+	0x57,     /* R24 - GPO1 */
+	0x00,     /* R25 */
+	0x42,     /* R26 - GPO2 */
+	0x06,     /* R27 - AIFTX */
+	0x06,     /* R28 - AIFRX */
+	0x80,     /* R29 - SPDRX1 */
+	0x07,     /* R30 - PWRDN */
+};
+
+struct wm8804_priv {
+	enum snd_soc_control_type control_type;
+	struct regulator_bulk_data supplies[WM8804_NUM_SUPPLIES];
+	struct notifier_block disable_nb[WM8804_NUM_SUPPLIES];
+	struct snd_soc_codec *codec;
+};
+
+static int txsrc_get(struct snd_kcontrol *kcontrol,
+		     struct snd_ctl_elem_value *ucontrol);
+
+static int txsrc_put(struct snd_kcontrol *kcontrol,
+		     struct snd_ctl_elem_value *ucontrol);
+
+/*
+ * We can't use the same notifier block for more than one supply and
+ * there's no way I can see to get from a callback to the caller
+ * except container_of().
+ */
+#define WM8804_REGULATOR_EVENT(n) \
+static int wm8804_regulator_event_##n(struct notifier_block *nb, \
+				      unsigned long event, void *data)    \
+{ \
+	struct wm8804_priv *wm8804 = container_of(nb, struct wm8804_priv, \
+						  disable_nb[n]); \
+	if (event & REGULATOR_EVENT_DISABLE) { \
+		wm8804->codec->cache_sync = 1; \
+	} \
+	return 0; \
+}
+
+WM8804_REGULATOR_EVENT(0)
+WM8804_REGULATOR_EVENT(1)
+
+static const char *txsrc_text[] = { "S/PDIF RX", "AIF" };
+static const SOC_ENUM_SINGLE_EXT_DECL(txsrc, txsrc_text);
+
+static const struct snd_kcontrol_new wm8804_snd_controls[] = {
+	SOC_ENUM_EXT("Input Source", txsrc, txsrc_get, txsrc_put),
+	SOC_SINGLE("TX Playback Switch", WM8804_PWRDN, 2, 1, 1),
+	SOC_SINGLE("AIF Playback Switch", WM8804_PWRDN, 4, 1, 1)
+};
+
+static int txsrc_get(struct snd_kcontrol *kcontrol,
+		     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec;
+	unsigned int src;
+
+	codec = snd_kcontrol_chip(kcontrol);
+	src = snd_soc_read(codec, WM8804_SPDTX4);
+	if (src & 0x40)
+		ucontrol->value.integer.value[0] = 1;
+	else
+		ucontrol->value.integer.value[0] = 0;
+
+	return 0;
+}
+
+static int txsrc_put(struct snd_kcontrol *kcontrol,
+		     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec;
+	unsigned int src, txpwr;
+
+	codec = snd_kcontrol_chip(kcontrol);
+
+	if (ucontrol->value.integer.value[0] != 0
+			&& ucontrol->value.integer.value[0] != 1)
+		return -EINVAL;
+
+	src = snd_soc_read(codec, WM8804_SPDTX4);
+	switch ((src & 0x40) >> 6) {
+	case 0:
+		if (!ucontrol->value.integer.value[0])
+			return 0;
+		break;
+	case 1:
+		if (ucontrol->value.integer.value[1])
+			return 0;
+		break;
+	}
+
+	/* save the current power state of the transmitter */
+	txpwr = snd_soc_read(codec, WM8804_PWRDN) & 0x4;
+	/* power down the transmitter */
+	snd_soc_update_bits(codec, WM8804_PWRDN, 0x4, 0x4);
+	/* set the tx source */
+	snd_soc_update_bits(codec, WM8804_SPDTX4, 0x40,
+			    ucontrol->value.integer.value[0] << 6);
+
+	if (ucontrol->value.integer.value[0]) {
+		/* power down the receiver */
+		snd_soc_update_bits(codec, WM8804_PWRDN, 0x2, 0x2);
+		/* power up the AIF */
+		snd_soc_update_bits(codec, WM8804_PWRDN, 0x10, 0);
+	} else {
+		/* don't power down the AIF -- may be used as an output */
+		/* power up the receiver */
+		snd_soc_update_bits(codec, WM8804_PWRDN, 0x2, 0);
+	}
+
+	/* restore the transmitter's configuration */
+	snd_soc_update_bits(codec, WM8804_PWRDN, 0x4, txpwr);
+
+	return 0;
+}
+
+static int wm8804_volatile(unsigned int reg)
+{
+	switch (reg) {
+	case WM8804_RST_DEVID1:
+	case WM8804_DEVID2:
+	case WM8804_DEVREV:
+	case WM8804_INTSTAT:
+	case WM8804_SPDSTAT:
+	case WM8804_RXCHAN1:
+	case WM8804_RXCHAN2:
+	case WM8804_RXCHAN3:
+	case WM8804_RXCHAN4:
+	case WM8804_RXCHAN5:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int wm8804_reset(struct snd_soc_codec *codec)
+{
+	return snd_soc_write(codec, WM8804_RST_DEVID1, 0x0);
+}
+
+static int wm8804_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec;
+	u16 format, master, bcp, lrp;
+
+	codec = dai->codec;
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		format = 0x2;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		format = 0x0;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		format = 0x1;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		format = 0x3;
+		break;
+	default:
+		dev_err(dai->dev, "Unknown dai format\n");
+		return -EINVAL;
+	}
+
+	/* set data format */
+	snd_soc_update_bits(codec, WM8804_AIFTX, 0x3, format);
+	snd_soc_update_bits(codec, WM8804_AIFRX, 0x3, format);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		master = 1;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		master = 0;
+		break;
+	default:
+		dev_err(dai->dev, "Unknown master/slave configuration\n");
+		return -EINVAL;
+	}
+
+	/* set master/slave mode */
+	snd_soc_update_bits(codec, WM8804_AIFRX, 0x40, master << 6);
+
+	bcp = lrp = 0;
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		bcp = lrp = 1;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		bcp = 1;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		lrp = 1;
+		break;
+	default:
+		dev_err(dai->dev, "Unknown polarity configuration\n");
+		return -EINVAL;
+	}
+
+	/* set frame inversion */
+	snd_soc_update_bits(codec, WM8804_AIFTX, 0x10 | 0x20,
+			    (bcp << 4) | (lrp << 5));
+	snd_soc_update_bits(codec, WM8804_AIFRX, 0x10 | 0x20,
+			    (bcp << 4) | (lrp << 5));
+	return 0;
+}
+
+static int wm8804_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec;
+	u16 blen;
+
+	codec = dai->codec;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		blen = 0x0;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		blen = 0x1;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		blen = 0x2;
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported word length: %u\n",
+			params_format(params));
+		return -EINVAL;
+	}
+
+	/* set word length */
+	snd_soc_update_bits(codec, WM8804_AIFTX, 0xc, blen << 2);
+	snd_soc_update_bits(codec, WM8804_AIFRX, 0xc, blen << 2);
+
+	return 0;
+}
+
+struct pll_div {
+	u32 prescale:1;
+	u32 mclkdiv:1;
+	u32 freqmode:2;
+	u32 n:4;
+	u32 k:22;
+};
+
+/* PLL rate to output rate divisions */
+static struct {
+	unsigned int div;
+	unsigned int freqmode;
+	unsigned int mclkdiv;
+} post_table[] = {
+	{  2,  0, 0 },
+	{  4,  0, 1 },
+	{  4,  1, 0 },
+	{  8,  1, 1 },
+	{  8,  2, 0 },
+	{ 16,  2, 1 },
+	{ 12,  3, 0 },
+	{ 24,  3, 1 }
+};
+
+#define FIXED_PLL_SIZE ((1ULL << 22) * 10)
+static int pll_factors(struct pll_div *pll_div, unsigned int target,
+		       unsigned int source)
+{
+	u64 Kpart;
+	unsigned long int K, Ndiv, Nmod, tmp;
+	int i;
+
+	/*
+	 * Scale the output frequency up; the PLL should run in the
+	 * region of 90-100MHz.
+	 */
+	for (i = 0; i < ARRAY_SIZE(post_table); i++) {
+		tmp = target * post_table[i].div;
+		if (tmp >= 90000000 && tmp <= 100000000) {
+			pll_div->freqmode = post_table[i].freqmode;
+			pll_div->mclkdiv = post_table[i].mclkdiv;
+			target *= post_table[i].div;
+			break;
+		}
+	}
+
+	if (i == ARRAY_SIZE(post_table)) {
+		pr_err("%s: Unable to scale output frequency: %uHz\n",
+		       __func__, target);
+		return -EINVAL;
+	}
+
+	pll_div->prescale = 0;
+	Ndiv = target / source;
+	if (Ndiv < 5) {
+		source >>= 1;
+		pll_div->prescale = 1;
+		Ndiv = target / source;
+	}
+
+	if (Ndiv < 5 || Ndiv > 13) {
+		pr_err("%s: WM8804 N value is not within the recommended range: %lu\n",
+		       __func__, Ndiv);
+		return -EINVAL;
+	}
+	pll_div->n = Ndiv;
+
+	Nmod = target % source;
+	Kpart = FIXED_PLL_SIZE * (u64)Nmod;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xffffffff;
+	if ((K % 10) >= 5)
+		K += 5;
+	K /= 10;
+	pll_div->k = K;
+
+	return 0;
+}
+
+static int wm8804_set_pll(struct snd_soc_dai *dai, int pll_id,
+			  int source, unsigned int freq_in,
+			  unsigned int freq_out)
+{
+	struct snd_soc_codec *codec;
+
+	codec = dai->codec;
+	if (!freq_in || !freq_out) {
+		/* disable the PLL */
+		snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0x1);
+		return 0;
+	} else {
+		int ret;
+		struct pll_div pll_div;
+
+		ret = pll_factors(&pll_div, freq_out, freq_in);
+		if (ret)
+			return ret;
+
+		/* power down the PLL before reprogramming it */
+		snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0x1);
+
+		if (!freq_in || !freq_out)
+			return 0;
+
+		/* set PLLN and PRESCALE */
+		snd_soc_update_bits(codec, WM8804_PLL4, 0xf | 0x10,
+				    pll_div.n | (pll_div.prescale << 4));
+		/* set mclkdiv and freqmode */
+		snd_soc_update_bits(codec, WM8804_PLL5, 0x3 | 0x8,
+				    pll_div.freqmode | (pll_div.mclkdiv << 3));
+		/* set PLLK */
+		snd_soc_write(codec, WM8804_PLL1, pll_div.k & 0xff);
+		snd_soc_write(codec, WM8804_PLL2, (pll_div.k >> 8) & 0xff);
+		snd_soc_write(codec, WM8804_PLL3, pll_div.k >> 16);
+
+		/* power up the PLL */
+		snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0);
+	}
+
+	return 0;
+}
+
+static int wm8804_set_sysclk(struct snd_soc_dai *dai,
+			     int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec;
+
+	codec = dai->codec;
+
+	switch (clk_id) {
+	case WM8804_TX_CLKSRC_MCLK:
+		if ((freq >= 10000000 && freq <= 14400000)
+				|| (freq >= 16280000 && freq <= 27000000))
+			snd_soc_update_bits(codec, WM8804_PLL6, 0x80, 0x80);
+		else {
+			dev_err(dai->dev, "OSCCLOCK is not within the "
+				"recommended range: %uHz\n", freq);
+			return -EINVAL;
+		}
+		break;
+	case WM8804_TX_CLKSRC_PLL:
+		snd_soc_update_bits(codec, WM8804_PLL6, 0x80, 0);
+		break;
+	case WM8804_CLKOUT_SRC_CLK1:
+		snd_soc_update_bits(codec, WM8804_PLL6, 0x8, 0);
+		break;
+	case WM8804_CLKOUT_SRC_OSCCLK:
+		snd_soc_update_bits(codec, WM8804_PLL6, 0x8, 0x8);
+		break;
+	default:
+		dev_err(dai->dev, "Unknown clock source: %d\n", clk_id);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wm8804_set_clkdiv(struct snd_soc_dai *dai,
+			     int div_id, int div)
+{
+	struct snd_soc_codec *codec;
+
+	codec = dai->codec;
+	switch (div_id) {
+	case WM8804_CLKOUT_DIV:
+		snd_soc_update_bits(codec, WM8804_PLL5, 0x30,
+				    (div & 0x3) << 4);
+		break;
+	default:
+		dev_err(dai->dev, "Unknown clock divider: %d\n", div_id);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void wm8804_sync_cache(struct snd_soc_codec *codec)
+{
+	short i;
+	u8 *cache;
+
+	if (!codec->cache_sync)
+		return;
+
+	codec->cache_only = 0;
+	cache = codec->reg_cache;
+	for (i = 0; i < codec->driver->reg_cache_size; i++) {
+		if (i == WM8804_RST_DEVID1 || cache[i] == wm8804_reg_defs[i])
+			continue;
+		snd_soc_write(codec, i, cache[i]);
+	}
+	codec->cache_sync = 0;
+}
+
+static int wm8804_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	int ret;
+	struct wm8804_priv *wm8804;
+
+	wm8804 = snd_soc_codec_get_drvdata(codec);
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		/* power up the OSC and the PLL */
+		snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0);
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies),
+						    wm8804->supplies);
+			if (ret) {
+				dev_err(codec->dev,
+					"Failed to enable supplies: %d\n",
+					ret);
+				return ret;
+			}
+			wm8804_sync_cache(codec);
+		}
+		/* power down the OSC and the PLL */
+		snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0x9);
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* power down the OSC and the PLL */
+		snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0x9);
+		regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies),
+				       wm8804->supplies);
+		break;
+	}
+
+	codec->bias_level = level;
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm8804_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8804_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8804_resume(struct snd_soc_codec *codec)
+{
+	wm8804_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	return 0;
+}
+#else
+#define wm8804_suspend NULL
+#define wm8804_resume NULL
+#endif
+
+static int wm8804_remove(struct snd_soc_codec *codec)
+{
+	struct wm8804_priv *wm8804;
+	int i;
+
+	wm8804 = snd_soc_codec_get_drvdata(codec);
+	wm8804_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	for (i = 0; i < ARRAY_SIZE(wm8804->supplies); ++i)
+		regulator_unregister_notifier(wm8804->supplies[i].consumer,
+					      &wm8804->disable_nb[i]);
+	regulator_bulk_free(ARRAY_SIZE(wm8804->supplies), wm8804->supplies);
+	return 0;
+}
+
+static int wm8804_probe(struct snd_soc_codec *codec)
+{
+	struct wm8804_priv *wm8804;
+	int i, id1, id2, ret;
+
+	wm8804 = snd_soc_codec_get_drvdata(codec);
+	wm8804->codec = codec;
+
+	codec->idle_bias_off = 1;
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 8, wm8804->control_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache i/o: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++)
+		wm8804->supplies[i].supply = wm8804_supply_names[i];
+
+	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8804->supplies),
+				 wm8804->supplies);
+	if (ret) {
+		dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+		return ret;
+	}
+
+	wm8804->disable_nb[0].notifier_call = wm8804_regulator_event_0;
+	wm8804->disable_nb[1].notifier_call = wm8804_regulator_event_1;
+
+	/* This should really be moved into the regulator core */
+	for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++) {
+		ret = regulator_register_notifier(wm8804->supplies[i].consumer,
+						  &wm8804->disable_nb[i]);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to register regulator notifier: %d\n",
+				ret);
+		}
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies),
+				    wm8804->supplies);
+	if (ret) {
+		dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+		goto err_reg_get;
+	}
+
+	id1 = snd_soc_read(codec, WM8804_RST_DEVID1);
+	if (id1 < 0) {
+		dev_err(codec->dev, "Failed to read device ID: %d\n", id1);
+		ret = id1;
+		goto err_reg_enable;
+	}
+
+	id2 = snd_soc_read(codec, WM8804_DEVID2);
+	if (id2 < 0) {
+		dev_err(codec->dev, "Failed to read device ID: %d\n", id2);
+		ret = id2;
+		goto err_reg_enable;
+	}
+
+	id2 = (id2 << 8) | id1;
+
+	if (id2 != ((wm8804_reg_defs[WM8804_DEVID2] << 8)
+			| wm8804_reg_defs[WM8804_RST_DEVID1])) {
+		dev_err(codec->dev, "Invalid device ID: %#x\n", id2);
+		ret = -EINVAL;
+		goto err_reg_enable;
+	}
+
+	ret = snd_soc_read(codec, WM8804_DEVREV);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to read device revision: %d\n",
+			ret);
+		goto err_reg_enable;
+	}
+	dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+	ret = wm8804_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+		goto err_reg_enable;
+	}
+
+	wm8804_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	snd_soc_add_controls(codec, wm8804_snd_controls,
+			     ARRAY_SIZE(wm8804_snd_controls));
+	return 0;
+
+err_reg_enable:
+	regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), wm8804->supplies);
+err_reg_get:
+	regulator_bulk_free(ARRAY_SIZE(wm8804->supplies), wm8804->supplies);
+	return ret;
+}
+
+static struct snd_soc_dai_ops wm8804_dai_ops = {
+	.hw_params = wm8804_hw_params,
+	.set_fmt = wm8804_set_fmt,
+	.set_sysclk = wm8804_set_sysclk,
+	.set_clkdiv = wm8804_set_clkdiv,
+	.set_pll = wm8804_set_pll
+};
+
+#define WM8804_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+			SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_driver wm8804_dai = {
+	.name = "wm8804-spdif",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_192000,
+		.formats = WM8804_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_192000,
+		.formats = WM8804_FORMATS,
+	},
+	.ops = &wm8804_dai_ops,
+	.symmetric_rates = 1
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8804 = {
+	.probe = wm8804_probe,
+	.remove = wm8804_remove,
+	.suspend = wm8804_suspend,
+	.resume = wm8804_resume,
+	.set_bias_level = wm8804_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8804_reg_defs),
+	.reg_word_size = sizeof(u8),
+	.reg_cache_default = wm8804_reg_defs,
+	.volatile_register = wm8804_volatile
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8804_spi_probe(struct spi_device *spi)
+{
+	struct wm8804_priv *wm8804;
+	int ret;
+
+	wm8804 = kzalloc(sizeof *wm8804, GFP_KERNEL);
+	if (!wm8804)
+		return -ENOMEM;
+
+	wm8804->control_type = SND_SOC_SPI;
+	spi_set_drvdata(spi, wm8804);
+
+	ret = snd_soc_register_codec(&spi->dev,
+				     &soc_codec_dev_wm8804, &wm8804_dai, 1);
+	if (ret < 0)
+		kfree(wm8804);
+	return ret;
+}
+
+static int __devexit wm8804_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver wm8804_spi_driver = {
+	.driver = {
+		.name = "wm8804",
+		.owner = THIS_MODULE,
+	},
+	.probe = wm8804_spi_probe,
+	.remove = __devexit_p(wm8804_spi_remove)
+};
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8804_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8804_priv *wm8804;
+	int ret;
+
+	wm8804 = kzalloc(sizeof *wm8804, GFP_KERNEL);
+	if (!wm8804)
+		return -ENOMEM;
+
+	wm8804->control_type = SND_SOC_I2C;
+	i2c_set_clientdata(i2c, wm8804);
+
+	ret = snd_soc_register_codec(&i2c->dev,
+				     &soc_codec_dev_wm8804, &wm8804_dai, 1);
+	if (ret < 0)
+		kfree(wm8804);
+	return ret;
+}
+
+static __devexit int wm8804_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8804_i2c_id[] = {
+	{ "wm8804", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8804_i2c_id);
+
+static struct i2c_driver wm8804_i2c_driver = {
+	.driver = {
+		.name = "wm8804",
+		.owner = THIS_MODULE,
+	},
+	.probe = wm8804_i2c_probe,
+	.remove = __devexit_p(wm8804_i2c_remove),
+	.id_table = wm8804_i2c_id
+};
+#endif
+
+static int __init wm8804_modinit(void)
+{
+	int ret = 0;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8804_i2c_driver);
+	if (ret) {
+		printk(KERN_ERR "Failed to register wm8804 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&wm8804_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8804 SPI driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8804_modinit);
+
+static void __exit wm8804_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8804_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&wm8804_spi_driver);
+#endif
+}
+module_exit(wm8804_exit);
+
+MODULE_DESCRIPTION("ASoC WM8804 driver");
+MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8804.h b/linux/sound/soc/codecs/wm8804.h
new file mode 100644
index 0000000..8ec14f5
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8804.h
@@ -0,0 +1,61 @@
+/*
+ * wm8804.h  --  WM8804 S/PDIF transceiver driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8804_H
+#define _WM8804_H
+
+/*
+ * Register values.
+ */
+#define WM8804_RST_DEVID1			0x00
+#define WM8804_DEVID2				0x01
+#define WM8804_DEVREV				0x02
+#define WM8804_PLL1				0x03
+#define WM8804_PLL2				0x04
+#define WM8804_PLL3				0x05
+#define WM8804_PLL4				0x06
+#define WM8804_PLL5				0x07
+#define WM8804_PLL6				0x08
+#define WM8804_SPDMODE				0x09
+#define WM8804_INTMASK				0x0A
+#define WM8804_INTSTAT				0x0B
+#define WM8804_SPDSTAT				0x0C
+#define WM8804_RXCHAN1				0x0D
+#define WM8804_RXCHAN2				0x0E
+#define WM8804_RXCHAN3				0x0F
+#define WM8804_RXCHAN4				0x10
+#define WM8804_RXCHAN5				0x11
+#define WM8804_SPDTX1				0x12
+#define WM8804_SPDTX2				0x13
+#define WM8804_SPDTX3				0x14
+#define WM8804_SPDTX4				0x15
+#define WM8804_SPDTX5				0x16
+#define WM8804_GPO0				0x17
+#define WM8804_GPO1				0x18
+#define WM8804_GPO2				0x1A
+#define WM8804_AIFTX				0x1B
+#define WM8804_AIFRX				0x1C
+#define WM8804_SPDRX1				0x1D
+#define WM8804_PWRDN				0x1E
+
+#define WM8804_REGISTER_COUNT			30
+#define WM8804_MAX_REGISTER			0x1E
+
+#define WM8804_TX_CLKSRC_MCLK			1
+#define WM8804_TX_CLKSRC_PLL			2
+
+#define WM8804_CLKOUT_SRC_CLK1			3
+#define WM8804_CLKOUT_SRC_OSCCLK		4
+
+#define WM8804_CLKOUT_DIV			1
+
+#endif  /* _WM8804_H */
diff --git a/linux/sound/soc/codecs/wm8900.c b/linux/sound/soc/codecs/wm8900.c
new file mode 100644
index 0000000..aca4b1e
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8900.c
@@ -0,0 +1,1374 @@
+/*
+ * wm8900.c  --  WM8900 ALSA Soc Audio driver
+ *
+ * Copyright 2007, 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO:
+ *  - Tristating.
+ *  - TDM.
+ *  - Jack detect.
+ *  - FLL source configuration, currently only MCLK is supported.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8900.h"
+
+/* WM8900 register space */
+#define WM8900_REG_RESET	0x0
+#define WM8900_REG_ID		0x0
+#define WM8900_REG_POWER1	0x1
+#define WM8900_REG_POWER2	0x2
+#define WM8900_REG_POWER3	0x3
+#define WM8900_REG_AUDIO1	0x4
+#define WM8900_REG_AUDIO2	0x5
+#define WM8900_REG_CLOCKING1    0x6
+#define WM8900_REG_CLOCKING2    0x7
+#define WM8900_REG_AUDIO3       0x8
+#define WM8900_REG_AUDIO4       0x9
+#define WM8900_REG_DACCTRL      0xa
+#define WM8900_REG_LDAC_DV      0xb
+#define WM8900_REG_RDAC_DV      0xc
+#define WM8900_REG_SIDETONE     0xd
+#define WM8900_REG_ADCCTRL      0xe
+#define WM8900_REG_LADC_DV	0xf
+#define WM8900_REG_RADC_DV      0x10
+#define WM8900_REG_GPIO         0x12
+#define WM8900_REG_INCTL	0x15
+#define WM8900_REG_LINVOL	0x16
+#define WM8900_REG_RINVOL	0x17
+#define WM8900_REG_INBOOSTMIX1  0x18
+#define WM8900_REG_INBOOSTMIX2  0x19
+#define WM8900_REG_ADCPATH	0x1a
+#define WM8900_REG_AUXBOOST	0x1b
+#define WM8900_REG_ADDCTL       0x1e
+#define WM8900_REG_FLLCTL1      0x24
+#define WM8900_REG_FLLCTL2      0x25
+#define WM8900_REG_FLLCTL3      0x26
+#define WM8900_REG_FLLCTL4      0x27
+#define WM8900_REG_FLLCTL5      0x28
+#define WM8900_REG_FLLCTL6      0x29
+#define WM8900_REG_LOUTMIXCTL1  0x2c
+#define WM8900_REG_ROUTMIXCTL1  0x2d
+#define WM8900_REG_BYPASS1	0x2e
+#define WM8900_REG_BYPASS2	0x2f
+#define WM8900_REG_AUXOUT_CTL   0x30
+#define WM8900_REG_LOUT1CTL     0x33
+#define WM8900_REG_ROUT1CTL     0x34
+#define WM8900_REG_LOUT2CTL	0x35
+#define WM8900_REG_ROUT2CTL	0x36
+#define WM8900_REG_HPCTL1	0x3a
+#define WM8900_REG_OUTBIASCTL   0x73
+
+#define WM8900_MAXREG		0x80
+
+#define WM8900_REG_ADDCTL_OUT1_DIS    0x80
+#define WM8900_REG_ADDCTL_OUT2_DIS    0x40
+#define WM8900_REG_ADDCTL_VMID_DIS    0x20
+#define WM8900_REG_ADDCTL_BIAS_SRC    0x10
+#define WM8900_REG_ADDCTL_VMID_SOFTST 0x04
+#define WM8900_REG_ADDCTL_TEMP_SD     0x02
+
+#define WM8900_REG_GPIO_TEMP_ENA   0x2
+
+#define WM8900_REG_POWER1_STARTUP_BIAS_ENA 0x0100
+#define WM8900_REG_POWER1_BIAS_ENA         0x0008
+#define WM8900_REG_POWER1_VMID_BUF_ENA     0x0004
+#define WM8900_REG_POWER1_FLL_ENA          0x0040
+
+#define WM8900_REG_POWER2_SYSCLK_ENA  0x8000
+#define WM8900_REG_POWER2_ADCL_ENA    0x0002
+#define WM8900_REG_POWER2_ADCR_ENA    0x0001
+
+#define WM8900_REG_POWER3_DACL_ENA    0x0002
+#define WM8900_REG_POWER3_DACR_ENA    0x0001
+
+#define WM8900_REG_AUDIO1_AIF_FMT_MASK 0x0018
+#define WM8900_REG_AUDIO1_LRCLK_INV    0x0080
+#define WM8900_REG_AUDIO1_BCLK_INV     0x0100
+
+#define WM8900_REG_CLOCKING1_BCLK_DIR   0x1
+#define WM8900_REG_CLOCKING1_MCLK_SRC   0x100
+#define WM8900_REG_CLOCKING1_BCLK_MASK  (~0x01e)
+#define WM8900_REG_CLOCKING1_OPCLK_MASK (~0x7000)
+
+#define WM8900_REG_CLOCKING2_ADC_CLKDIV 0xe0
+#define WM8900_REG_CLOCKING2_DAC_CLKDIV 0x1c
+
+#define WM8900_REG_DACCTRL_MUTE          0x004
+#define WM8900_REG_DACCTRL_DAC_SB_FILT   0x100
+#define WM8900_REG_DACCTRL_AIF_LRCLKRATE 0x400
+
+#define WM8900_REG_AUDIO3_ADCLRC_DIR    0x0800
+
+#define WM8900_REG_AUDIO4_DACLRC_DIR    0x0800
+
+#define WM8900_REG_FLLCTL1_OSC_ENA    0x100
+
+#define WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF 0x100
+
+#define WM8900_REG_HPCTL1_HP_IPSTAGE_ENA 0x80
+#define WM8900_REG_HPCTL1_HP_OPSTAGE_ENA 0x40
+#define WM8900_REG_HPCTL1_HP_CLAMP_IP    0x20
+#define WM8900_REG_HPCTL1_HP_CLAMP_OP    0x10
+#define WM8900_REG_HPCTL1_HP_SHORT       0x08
+#define WM8900_REG_HPCTL1_HP_SHORT2      0x04
+
+#define WM8900_LRC_MASK 0xfc00
+
+struct wm8900_priv {
+	enum snd_soc_control_type control_type;
+	u16 reg_cache[WM8900_MAXREG];
+
+	u32 fll_in; /* FLL input frequency */
+	u32 fll_out; /* FLL output frequency */
+};
+
+/*
+ * wm8900 register cache.  We can't read the entire register space and we
+ * have slow control buses so we cache the registers.
+ */
+static const u16 wm8900_reg_defaults[WM8900_MAXREG] = {
+	0x8900, 0x0000,
+	0xc000, 0x0000,
+	0x4050, 0x4000,
+	0x0008, 0x0000,
+	0x0040, 0x0040,
+	0x1004, 0x00c0,
+	0x00c0, 0x0000,
+	0x0100, 0x00c0,
+	0x00c0, 0x0000,
+	0xb001, 0x0000,
+	0x0000, 0x0044,
+	0x004c, 0x004c,
+	0x0044, 0x0044,
+	0x0000, 0x0044,
+	0x0000, 0x0000,
+	0x0002, 0x0000,
+	0x0000, 0x0000,
+	0x0000, 0x0000,
+	0x0008, 0x0000,
+	0x0000, 0x0008,
+	0x0097, 0x0100,
+	0x0000, 0x0000,
+	0x0050, 0x0050,
+	0x0055, 0x0055,
+	0x0055, 0x0000,
+	0x0000, 0x0079,
+	0x0079, 0x0079,
+	0x0079, 0x0000,
+	/* Remaining registers all zero */
+};
+
+static int wm8900_volatile_register(unsigned int reg)
+{
+	switch (reg) {
+	case WM8900_REG_ID:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static void wm8900_reset(struct snd_soc_codec *codec)
+{
+	snd_soc_write(codec, WM8900_REG_RESET, 0);
+
+	memcpy(codec->reg_cache, wm8900_reg_defaults,
+	       sizeof(wm8900_reg_defaults));
+}
+
+static int wm8900_hp_event(struct snd_soc_dapm_widget *w,
+			   struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	u16 hpctl1 = snd_soc_read(codec, WM8900_REG_HPCTL1);
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		/* Clamp headphone outputs */
+		hpctl1 = WM8900_REG_HPCTL1_HP_CLAMP_IP |
+			WM8900_REG_HPCTL1_HP_CLAMP_OP;
+		snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+		break;
+
+	case SND_SOC_DAPM_POST_PMU:
+		/* Enable the input stage */
+		hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_IP;
+		hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT |
+			WM8900_REG_HPCTL1_HP_SHORT2 |
+			WM8900_REG_HPCTL1_HP_IPSTAGE_ENA;
+		snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+		msleep(400);
+
+		/* Enable the output stage */
+		hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_OP;
+		hpctl1 |= WM8900_REG_HPCTL1_HP_OPSTAGE_ENA;
+		snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+		/* Remove the shorts */
+		hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT2;
+		snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+		hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT;
+		snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		/* Short the output */
+		hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT;
+		snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+		/* Disable the output stage */
+		hpctl1 &= ~WM8900_REG_HPCTL1_HP_OPSTAGE_ENA;
+		snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+
+		/* Clamp the outputs and power down input */
+		hpctl1 |= WM8900_REG_HPCTL1_HP_CLAMP_IP |
+			WM8900_REG_HPCTL1_HP_CLAMP_OP;
+		hpctl1 &= ~WM8900_REG_HPCTL1_HP_IPSTAGE_ENA;
+		snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
+		break;
+
+	case SND_SOC_DAPM_POST_PMD:
+		/* Disable everything */
+		snd_soc_write(codec, WM8900_REG_HPCTL1, 0);
+		break;
+
+	default:
+		BUG();
+	}
+
+	return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_boost_tlv, -1200, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1200, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1);
+
+static const DECLARE_TLV_DB_SCALE(adc_svol_tlv, -3600, 300, 0);
+
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1);
+
+static const char *mic_bias_level_txt[] = { "0.9*AVDD", "0.65*AVDD" };
+
+static const struct soc_enum mic_bias_level =
+SOC_ENUM_SINGLE(WM8900_REG_INCTL, 8, 2, mic_bias_level_txt);
+
+static const char *dac_mute_rate_txt[] = { "Fast", "Slow" };
+
+static const struct soc_enum dac_mute_rate =
+SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 7, 2, dac_mute_rate_txt);
+
+static const char *dac_deemphasis_txt[] = {
+	"Disabled", "32kHz", "44.1kHz", "48kHz"
+};
+
+static const struct soc_enum dac_deemphasis =
+SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 4, 4, dac_deemphasis_txt);
+
+static const char *adc_hpf_cut_txt[] = {
+	"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"
+};
+
+static const struct soc_enum adc_hpf_cut =
+SOC_ENUM_SINGLE(WM8900_REG_ADCCTRL, 5, 4, adc_hpf_cut_txt);
+
+static const char *lr_txt[] = {
+	"Left", "Right"
+};
+
+static const struct soc_enum aifl_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 15, 2, lr_txt);
+
+static const struct soc_enum aifr_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 14, 2, lr_txt);
+
+static const struct soc_enum dacl_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 15, 2, lr_txt);
+
+static const struct soc_enum dacr_src =
+SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 14, 2, lr_txt);
+
+static const char *sidetone_txt[] = {
+	"Disabled", "Left ADC", "Right ADC"
+};
+
+static const struct soc_enum dacl_sidetone =
+SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 2, 3, sidetone_txt);
+
+static const struct soc_enum dacr_sidetone =
+SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 0, 3, sidetone_txt);
+
+static const struct snd_kcontrol_new wm8900_snd_controls[] = {
+SOC_ENUM("Mic Bias Level", mic_bias_level),
+
+SOC_SINGLE_TLV("Left Input PGA Volume", WM8900_REG_LINVOL, 0, 31, 0,
+	       in_pga_tlv),
+SOC_SINGLE("Left Input PGA Switch", WM8900_REG_LINVOL, 6, 1, 1),
+SOC_SINGLE("Left Input PGA ZC Switch", WM8900_REG_LINVOL, 7, 1, 0),
+
+SOC_SINGLE_TLV("Right Input PGA Volume", WM8900_REG_RINVOL, 0, 31, 0,
+	       in_pga_tlv),
+SOC_SINGLE("Right Input PGA Switch", WM8900_REG_RINVOL, 6, 1, 1),
+SOC_SINGLE("Right Input PGA ZC Switch", WM8900_REG_RINVOL, 7, 1, 0),
+
+SOC_SINGLE("DAC Soft Mute Switch", WM8900_REG_DACCTRL, 6, 1, 1),
+SOC_ENUM("DAC Mute Rate", dac_mute_rate),
+SOC_SINGLE("DAC Mono Switch", WM8900_REG_DACCTRL, 9, 1, 0),
+SOC_ENUM("DAC Deemphasis", dac_deemphasis),
+SOC_SINGLE("DAC Sigma-Delta Modulator Clock Switch", WM8900_REG_DACCTRL,
+	   12, 1, 0),
+
+SOC_SINGLE("ADC HPF Switch", WM8900_REG_ADCCTRL, 8, 1, 0),
+SOC_ENUM("ADC HPF Cut-Off", adc_hpf_cut),
+SOC_DOUBLE("ADC Invert Switch", WM8900_REG_ADCCTRL, 1, 0, 1, 0),
+SOC_SINGLE_TLV("Left ADC Sidetone Volume", WM8900_REG_SIDETONE, 9, 12, 0,
+	       adc_svol_tlv),
+SOC_SINGLE_TLV("Right ADC Sidetone Volume", WM8900_REG_SIDETONE, 5, 12, 0,
+	       adc_svol_tlv),
+SOC_ENUM("Left Digital Audio Source", aifl_src),
+SOC_ENUM("Right Digital Audio Source", aifr_src),
+
+SOC_SINGLE_TLV("DAC Input Boost Volume", WM8900_REG_AUDIO2, 10, 4, 0,
+	       dac_boost_tlv),
+SOC_ENUM("Left DAC Source", dacl_src),
+SOC_ENUM("Right DAC Source", dacr_src),
+SOC_ENUM("Left DAC Sidetone", dacl_sidetone),
+SOC_ENUM("Right DAC Sidetone", dacr_sidetone),
+SOC_DOUBLE("DAC Invert Switch", WM8900_REG_DACCTRL, 1, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("Digital Playback Volume",
+		 WM8900_REG_LDAC_DV, WM8900_REG_RDAC_DV,
+		 1, 96, 0, dac_tlv),
+SOC_DOUBLE_R_TLV("Digital Capture Volume",
+		 WM8900_REG_LADC_DV, WM8900_REG_RADC_DV, 1, 119, 0, adc_tlv),
+
+SOC_SINGLE_TLV("LINPUT3 Bypass Volume", WM8900_REG_LOUTMIXCTL1, 4, 7, 0,
+	       out_mix_tlv),
+SOC_SINGLE_TLV("RINPUT3 Bypass Volume", WM8900_REG_ROUTMIXCTL1, 4, 7, 0,
+	       out_mix_tlv),
+SOC_SINGLE_TLV("Left AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 4, 7, 0,
+	       out_mix_tlv),
+SOC_SINGLE_TLV("Right AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 0, 7, 0,
+	       out_mix_tlv),
+
+SOC_SINGLE_TLV("LeftIn to RightOut Mixer Volume", WM8900_REG_BYPASS1, 0, 7, 0,
+	       out_mix_tlv),
+SOC_SINGLE_TLV("LeftIn to LeftOut Mixer Volume", WM8900_REG_BYPASS1, 4, 7, 0,
+	       out_mix_tlv),
+SOC_SINGLE_TLV("RightIn to LeftOut Mixer Volume", WM8900_REG_BYPASS2, 0, 7, 0,
+	       out_mix_tlv),
+SOC_SINGLE_TLV("RightIn to RightOut Mixer Volume", WM8900_REG_BYPASS2, 4, 7, 0,
+	       out_mix_tlv),
+
+SOC_SINGLE_TLV("IN2L Boost Volume", WM8900_REG_INBOOSTMIX1, 0, 3, 0,
+	       in_boost_tlv),
+SOC_SINGLE_TLV("IN3L Boost Volume", WM8900_REG_INBOOSTMIX1, 4, 3, 0,
+	       in_boost_tlv),
+SOC_SINGLE_TLV("IN2R Boost Volume", WM8900_REG_INBOOSTMIX2, 0, 3, 0,
+	       in_boost_tlv),
+SOC_SINGLE_TLV("IN3R Boost Volume", WM8900_REG_INBOOSTMIX2, 4, 3, 0,
+	       in_boost_tlv),
+SOC_SINGLE_TLV("Left AUX Boost Volume", WM8900_REG_AUXBOOST, 4, 3, 0,
+	       in_boost_tlv),
+SOC_SINGLE_TLV("Right AUX Boost Volume", WM8900_REG_AUXBOOST, 0, 3, 0,
+	       in_boost_tlv),
+
+SOC_DOUBLE_R_TLV("LINEOUT1 Volume", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+	       0, 63, 0, out_pga_tlv),
+SOC_DOUBLE_R("LINEOUT1 Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+	     6, 1, 1),
+SOC_DOUBLE_R("LINEOUT1 ZC Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL,
+	     7, 1, 0),
+
+SOC_DOUBLE_R_TLV("LINEOUT2 Volume",
+		 WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL,
+		 0, 63, 0, out_pga_tlv),
+SOC_DOUBLE_R("LINEOUT2 Switch",
+	     WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 6, 1, 1),
+SOC_DOUBLE_R("LINEOUT2 ZC Switch",
+	     WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 7, 1, 0),
+SOC_SINGLE("LINEOUT2 LP -12dB", WM8900_REG_LOUTMIXCTL1,
+	   0, 1, 1),
+
+};
+
+static const struct snd_kcontrol_new wm8900_dapm_loutput2_control =
+SOC_DAPM_SINGLE("LINEOUT2L Switch", WM8900_REG_POWER3, 6, 1, 0);
+
+static const struct snd_kcontrol_new wm8900_dapm_routput2_control =
+SOC_DAPM_SINGLE("LINEOUT2R Switch", WM8900_REG_POWER3, 5, 1, 0);
+
+static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {
+SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),
+SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),
+SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),
+SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_routmix_controls[] = {
+SOC_DAPM_SINGLE("RINPUT3 Bypass Switch", WM8900_REG_ROUTMIXCTL1, 7, 1, 0),
+SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 3, 1, 0),
+SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 3, 1, 0),
+SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 7, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8900_REG_ROUTMIXCTL1, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_linmix_controls[] = {
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INBOOSTMIX1, 2, 1, 1),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INBOOSTMIX1, 6, 1, 1),
+SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 6, 1, 1),
+SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 6, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_rinmix_controls[] = {
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INBOOSTMIX2, 2, 1, 1),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INBOOSTMIX2, 6, 1, 1),
+SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 2, 1, 1),
+SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_linpga_controls[] = {
+SOC_DAPM_SINGLE("LINPUT1 Switch", WM8900_REG_INCTL, 6, 1, 0),
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INCTL, 5, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INCTL, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8900_rinpga_controls[] = {
+SOC_DAPM_SINGLE("RINPUT1 Switch", WM8900_REG_INCTL, 2, 1, 0),
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INCTL, 1, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INCTL, 0, 1, 0),
+};
+
+static const char *wm9700_lp_mux[] = { "Disabled", "Enabled" };
+
+static const struct soc_enum wm8900_lineout2_lp_mux =
+SOC_ENUM_SINGLE(WM8900_REG_LOUTMIXCTL1, 1, 2, wm9700_lp_mux);
+
+static const struct snd_kcontrol_new wm8900_lineout2_lp =
+SOC_DAPM_ENUM("Route", wm8900_lineout2_lp_mux);
+
+static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = {
+
+/* Externally visible pins */
+SND_SOC_DAPM_OUTPUT("LINEOUT1L"),
+SND_SOC_DAPM_OUTPUT("LINEOUT1R"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2L"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2R"),
+SND_SOC_DAPM_OUTPUT("HP_L"),
+SND_SOC_DAPM_OUTPUT("HP_R"),
+
+SND_SOC_DAPM_INPUT("RINPUT1"),
+SND_SOC_DAPM_INPUT("LINPUT1"),
+SND_SOC_DAPM_INPUT("RINPUT2"),
+SND_SOC_DAPM_INPUT("LINPUT2"),
+SND_SOC_DAPM_INPUT("RINPUT3"),
+SND_SOC_DAPM_INPUT("LINPUT3"),
+SND_SOC_DAPM_INPUT("AUX"),
+
+SND_SOC_DAPM_VMID("VMID"),
+
+/* Input */
+SND_SOC_DAPM_MIXER("Left Input PGA", WM8900_REG_POWER2, 3, 0,
+		   wm8900_linpga_controls,
+		   ARRAY_SIZE(wm8900_linpga_controls)),
+SND_SOC_DAPM_MIXER("Right Input PGA", WM8900_REG_POWER2, 2, 0,
+		   wm8900_rinpga_controls,
+		   ARRAY_SIZE(wm8900_rinpga_controls)),
+
+SND_SOC_DAPM_MIXER("Left Input Mixer", WM8900_REG_POWER2, 5, 0,
+		   wm8900_linmix_controls,
+		   ARRAY_SIZE(wm8900_linmix_controls)),
+SND_SOC_DAPM_MIXER("Right Input Mixer", WM8900_REG_POWER2, 4, 0,
+		   wm8900_rinmix_controls,
+		   ARRAY_SIZE(wm8900_rinmix_controls)),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8900_REG_POWER1, 4, 0),
+
+SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8900_REG_POWER2, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8900_REG_POWER2, 0, 0),
+
+/* Output */
+SND_SOC_DAPM_DAC("DACL", "Left HiFi Playback", WM8900_REG_POWER3, 1, 0),
+SND_SOC_DAPM_DAC("DACR", "Right HiFi Playback", WM8900_REG_POWER3, 0, 0),
+
+SND_SOC_DAPM_PGA_E("Headphone Amplifier", WM8900_REG_POWER3, 7, 0, NULL, 0,
+		   wm8900_hp_event,
+		   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+		   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_PGA("LINEOUT1L PGA", WM8900_REG_POWER2, 8, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT1R PGA", WM8900_REG_POWER2, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_MUX("LINEOUT2 LP", SND_SOC_NOPM, 0, 0, &wm8900_lineout2_lp),
+SND_SOC_DAPM_PGA("LINEOUT2L PGA", WM8900_REG_POWER3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT2R PGA", WM8900_REG_POWER3, 5, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0,
+		   wm8900_loutmix_controls,
+		   ARRAY_SIZE(wm8900_loutmix_controls)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8900_REG_POWER3, 2, 0,
+		   wm8900_routmix_controls,
+		   ARRAY_SIZE(wm8900_routmix_controls)),
+};
+
+/* Target, Path, Source */
+static const struct snd_soc_dapm_route audio_map[] = {
+/* Inputs */
+{"Left Input PGA", "LINPUT1 Switch", "LINPUT1"},
+{"Left Input PGA", "LINPUT2 Switch", "LINPUT2"},
+{"Left Input PGA", "LINPUT3 Switch", "LINPUT3"},
+
+{"Right Input PGA", "RINPUT1 Switch", "RINPUT1"},
+{"Right Input PGA", "RINPUT2 Switch", "RINPUT2"},
+{"Right Input PGA", "RINPUT3 Switch", "RINPUT3"},
+
+{"Left Input Mixer", "LINPUT2 Switch", "LINPUT2"},
+{"Left Input Mixer", "LINPUT3 Switch", "LINPUT3"},
+{"Left Input Mixer", "AUX Switch", "AUX"},
+{"Left Input Mixer", "Input PGA Switch", "Left Input PGA"},
+
+{"Right Input Mixer", "RINPUT2 Switch", "RINPUT2"},
+{"Right Input Mixer", "RINPUT3 Switch", "RINPUT3"},
+{"Right Input Mixer", "AUX Switch", "AUX"},
+{"Right Input Mixer", "Input PGA Switch", "Right Input PGA"},
+
+{"ADCL", NULL, "Left Input Mixer"},
+{"ADCR", NULL, "Right Input Mixer"},
+
+/* Outputs */
+{"LINEOUT1L", NULL, "LINEOUT1L PGA"},
+{"LINEOUT1L PGA", NULL, "Left Output Mixer"},
+{"LINEOUT1R", NULL, "LINEOUT1R PGA"},
+{"LINEOUT1R PGA", NULL, "Right Output Mixer"},
+
+{"LINEOUT2L PGA", NULL, "Left Output Mixer"},
+{"LINEOUT2 LP", "Disabled", "LINEOUT2L PGA"},
+{"LINEOUT2 LP", "Enabled", "Left Output Mixer"},
+{"LINEOUT2L", NULL, "LINEOUT2 LP"},
+
+{"LINEOUT2R PGA", NULL, "Right Output Mixer"},
+{"LINEOUT2 LP", "Disabled", "LINEOUT2R PGA"},
+{"LINEOUT2 LP", "Enabled", "Right Output Mixer"},
+{"LINEOUT2R", NULL, "LINEOUT2 LP"},
+
+{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},
+{"Left Output Mixer", "AUX Bypass Switch", "AUX"},
+{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
+{"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},
+{"Left Output Mixer", "DACL Switch", "DACL"},
+
+{"Right Output Mixer", "RINPUT3 Bypass Switch", "RINPUT3"},
+{"Right Output Mixer", "AUX Bypass Switch", "AUX"},
+{"Right Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
+{"Right Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},
+{"Right Output Mixer", "DACR Switch", "DACR"},
+
+/* Note that the headphone output stage needs to be connected
+ * externally to LINEOUT2 via DC blocking capacitors.  Other
+ * configurations are not supported.
+ *
+ * Note also that left and right headphone paths are treated as a
+ * mono path.
+ */
+{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
+{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
+{"HP_L", NULL, "Headphone Amplifier"},
+{"HP_R", NULL, "Headphone Amplifier"},
+};
+
+static int wm8900_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8900_dapm_widgets,
+				  ARRAY_SIZE(wm8900_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+static int wm8900_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	u16 reg;
+
+	reg = snd_soc_read(codec, WM8900_REG_AUDIO1) & ~0x60;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		reg |= 0x20;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		reg |= 0x40;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		reg |= 0x60;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8900_REG_AUDIO1, reg);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		reg = snd_soc_read(codec, WM8900_REG_DACCTRL);
+
+		if (params_rate(params) <= 24000)
+			reg |= WM8900_REG_DACCTRL_DAC_SB_FILT;
+		else
+			reg &= ~WM8900_REG_DACCTRL_DAC_SB_FILT;
+
+		snd_soc_write(codec, WM8900_REG_DACCTRL, reg);
+	}
+
+	return 0;
+}
+
+/* FLL divisors */
+struct _fll_div {
+	u16 fll_ratio;
+	u16 fllclk_div;
+	u16 fll_slow_lock_ref;
+	u16 n;
+	u16 k;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+		       unsigned int Fout)
+{
+	u64 Kpart;
+	unsigned int K, Ndiv, Nmod, target;
+	unsigned int div;
+
+	BUG_ON(!Fout);
+
+	/* The FLL must run at 90-100MHz which is then scaled down to
+	 * the output value by FLLCLK_DIV. */
+	target = Fout;
+	div = 1;
+	while (target < 90000000) {
+		div *= 2;
+		target *= 2;
+	}
+
+	if (target > 100000000)
+		printk(KERN_WARNING "wm8900: FLL rate %u out of range, Fref=%u"
+		       " Fout=%u\n", target, Fref, Fout);
+	if (div > 32) {
+		printk(KERN_ERR "wm8900: Invalid FLL division rate %u, "
+		       "Fref=%u, Fout=%u, target=%u\n",
+		       div, Fref, Fout, target);
+		return -EINVAL;
+	}
+
+	fll_div->fllclk_div = div >> 2;
+
+	if (Fref < 48000)
+		fll_div->fll_slow_lock_ref = 1;
+	else
+		fll_div->fll_slow_lock_ref = 0;
+
+	Ndiv = target / Fref;
+
+	if (Fref < 1000000)
+		fll_div->fll_ratio = 8;
+	else
+		fll_div->fll_ratio = 1;
+
+	fll_div->n = Ndiv / fll_div->fll_ratio;
+	Nmod = (target / fll_div->fll_ratio) % Fref;
+
+	/* Calculate fractional part - scale up so we can round. */
+	Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, Fref);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	fll_div->k = K / 10;
+
+	BUG_ON(target != Fout * (fll_div->fllclk_div << 2));
+	BUG_ON(!K && target != Fref * fll_div->fll_ratio * fll_div->n);
+
+	return 0;
+}
+
+static int wm8900_set_fll(struct snd_soc_codec *codec,
+	int fll_id, unsigned int freq_in, unsigned int freq_out)
+{
+	struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec);
+	struct _fll_div fll_div;
+	unsigned int reg;
+
+	if (wm8900->fll_in == freq_in && wm8900->fll_out == freq_out)
+		return 0;
+
+	/* The digital side should be disabled during any change. */
+	reg = snd_soc_read(codec, WM8900_REG_POWER1);
+	snd_soc_write(codec, WM8900_REG_POWER1,
+		     reg & (~WM8900_REG_POWER1_FLL_ENA));
+
+	/* Disable the FLL? */
+	if (!freq_in || !freq_out) {
+		reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
+		snd_soc_write(codec, WM8900_REG_CLOCKING1,
+			     reg & (~WM8900_REG_CLOCKING1_MCLK_SRC));
+
+		reg = snd_soc_read(codec, WM8900_REG_FLLCTL1);
+		snd_soc_write(codec, WM8900_REG_FLLCTL1,
+			     reg & (~WM8900_REG_FLLCTL1_OSC_ENA));
+
+		wm8900->fll_in = freq_in;
+		wm8900->fll_out = freq_out;
+
+		return 0;
+	}
+
+	if (fll_factors(&fll_div, freq_in, freq_out) != 0)
+		goto reenable;
+
+	wm8900->fll_in = freq_in;
+	wm8900->fll_out = freq_out;
+
+	/* The osclilator *MUST* be enabled before we enable the
+	 * digital circuit. */
+	snd_soc_write(codec, WM8900_REG_FLLCTL1,
+		     fll_div.fll_ratio | WM8900_REG_FLLCTL1_OSC_ENA);
+
+	snd_soc_write(codec, WM8900_REG_FLLCTL4, fll_div.n >> 5);
+	snd_soc_write(codec, WM8900_REG_FLLCTL5,
+		     (fll_div.fllclk_div << 6) | (fll_div.n & 0x1f));
+
+	if (fll_div.k) {
+		snd_soc_write(codec, WM8900_REG_FLLCTL2,
+			     (fll_div.k >> 8) | 0x100);
+		snd_soc_write(codec, WM8900_REG_FLLCTL3, fll_div.k & 0xff);
+	} else
+		snd_soc_write(codec, WM8900_REG_FLLCTL2, 0);
+
+	if (fll_div.fll_slow_lock_ref)
+		snd_soc_write(codec, WM8900_REG_FLLCTL6,
+			     WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF);
+	else
+		snd_soc_write(codec, WM8900_REG_FLLCTL6, 0);
+
+	reg = snd_soc_read(codec, WM8900_REG_POWER1);
+	snd_soc_write(codec, WM8900_REG_POWER1,
+		     reg | WM8900_REG_POWER1_FLL_ENA);
+
+reenable:
+	reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
+	snd_soc_write(codec, WM8900_REG_CLOCKING1,
+		     reg | WM8900_REG_CLOCKING1_MCLK_SRC);
+
+	return 0;
+}
+
+static int wm8900_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	return wm8900_set_fll(codec_dai->codec, pll_id, freq_in, freq_out);
+}
+
+static int wm8900_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+				 int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	unsigned int reg;
+
+	switch (div_id) {
+	case WM8900_BCLK_DIV:
+		reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
+		snd_soc_write(codec, WM8900_REG_CLOCKING1,
+			     div | (reg & WM8900_REG_CLOCKING1_BCLK_MASK));
+		break;
+	case WM8900_OPCLK_DIV:
+		reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
+		snd_soc_write(codec, WM8900_REG_CLOCKING1,
+			     div | (reg & WM8900_REG_CLOCKING1_OPCLK_MASK));
+		break;
+	case WM8900_DAC_LRCLK:
+		reg = snd_soc_read(codec, WM8900_REG_AUDIO4);
+		snd_soc_write(codec, WM8900_REG_AUDIO4,
+			     div | (reg & WM8900_LRC_MASK));
+		break;
+	case WM8900_ADC_LRCLK:
+		reg = snd_soc_read(codec, WM8900_REG_AUDIO3);
+		snd_soc_write(codec, WM8900_REG_AUDIO3,
+			     div | (reg & WM8900_LRC_MASK));
+		break;
+	case WM8900_DAC_CLKDIV:
+		reg = snd_soc_read(codec, WM8900_REG_CLOCKING2);
+		snd_soc_write(codec, WM8900_REG_CLOCKING2,
+			     div | (reg & WM8900_REG_CLOCKING2_DAC_CLKDIV));
+		break;
+	case WM8900_ADC_CLKDIV:
+		reg = snd_soc_read(codec, WM8900_REG_CLOCKING2);
+		snd_soc_write(codec, WM8900_REG_CLOCKING2,
+			     div | (reg & WM8900_REG_CLOCKING2_ADC_CLKDIV));
+		break;
+	case WM8900_LRCLK_MODE:
+		reg = snd_soc_read(codec, WM8900_REG_DACCTRL);
+		snd_soc_write(codec, WM8900_REG_DACCTRL,
+			     div | (reg & WM8900_REG_DACCTRL_AIF_LRCLKRATE));
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+
+static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			      unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	unsigned int clocking1, aif1, aif3, aif4;
+
+	clocking1 = snd_soc_read(codec, WM8900_REG_CLOCKING1);
+	aif1 = snd_soc_read(codec, WM8900_REG_AUDIO1);
+	aif3 = snd_soc_read(codec, WM8900_REG_AUDIO3);
+	aif4 = snd_soc_read(codec, WM8900_REG_AUDIO4);
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR;
+		aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR;
+		aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR;
+		aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR;
+		aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR;
+		aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR;
+		aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR;
+		aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR;
+		aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+		aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK;
+		aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK;
+		aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+		aif1 |= 0x10;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK;
+		aif1 |= 0x8;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		/* frame inversion not valid for DSP modes */
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_LEFT_J:
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+			aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+			aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif1 |= WM8900_REG_AUDIO1_BCLK_INV;
+			aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV;
+			aif1 |= WM8900_REG_AUDIO1_LRCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8900_REG_CLOCKING1, clocking1);
+	snd_soc_write(codec, WM8900_REG_AUDIO1, aif1);
+	snd_soc_write(codec, WM8900_REG_AUDIO3, aif3);
+	snd_soc_write(codec, WM8900_REG_AUDIO4, aif4);
+
+	return 0;
+}
+
+static int wm8900_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+
+	reg = snd_soc_read(codec, WM8900_REG_DACCTRL);
+
+	if (mute)
+		reg |= WM8900_REG_DACCTRL_MUTE;
+	else
+		reg &= ~WM8900_REG_DACCTRL_MUTE;
+
+	snd_soc_write(codec, WM8900_REG_DACCTRL, reg);
+
+	return 0;
+}
+
+#define WM8900_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+		      SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+		      SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define WM8900_PCM_FORMATS \
+	(SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
+	 SNDRV_PCM_FORMAT_S24_LE)
+
+static struct snd_soc_dai_ops wm8900_dai_ops = {
+	.hw_params	= wm8900_hw_params,
+	.set_clkdiv	= wm8900_set_dai_clkdiv,
+	.set_pll	= wm8900_set_dai_pll,
+	.set_fmt	= wm8900_set_dai_fmt,
+	.digital_mute	= wm8900_digital_mute,
+};
+
+static struct snd_soc_dai_driver wm8900_dai = {
+	.name = "wm8900-hifi",
+	.playback = {
+		.stream_name = "HiFi Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8900_RATES,
+		.formats = WM8900_PCM_FORMATS,
+	},
+	.capture = {
+		.stream_name = "HiFi Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8900_RATES,
+		.formats = WM8900_PCM_FORMATS,
+	 },
+	.ops = &wm8900_dai_ops,
+};
+
+static int wm8900_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 reg;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		/* Enable thermal shutdown */
+		reg = snd_soc_read(codec, WM8900_REG_GPIO);
+		snd_soc_write(codec, WM8900_REG_GPIO,
+			     reg | WM8900_REG_GPIO_TEMP_ENA);
+		reg = snd_soc_read(codec, WM8900_REG_ADDCTL);
+		snd_soc_write(codec, WM8900_REG_ADDCTL,
+			     reg | WM8900_REG_ADDCTL_TEMP_SD);
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		/* Charge capacitors if initial power up */
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* STARTUP_BIAS_ENA on */
+			snd_soc_write(codec, WM8900_REG_POWER1,
+				     WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+
+			/* Startup bias mode */
+			snd_soc_write(codec, WM8900_REG_ADDCTL,
+				     WM8900_REG_ADDCTL_BIAS_SRC |
+				     WM8900_REG_ADDCTL_VMID_SOFTST);
+
+			/* VMID 2x50k */
+			snd_soc_write(codec, WM8900_REG_POWER1,
+				     WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1);
+
+			/* Allow capacitors to charge */
+			schedule_timeout_interruptible(msecs_to_jiffies(400));
+
+			/* Enable bias */
+			snd_soc_write(codec, WM8900_REG_POWER1,
+				     WM8900_REG_POWER1_STARTUP_BIAS_ENA |
+				     WM8900_REG_POWER1_BIAS_ENA | 0x1);
+
+			snd_soc_write(codec, WM8900_REG_ADDCTL, 0);
+
+			snd_soc_write(codec, WM8900_REG_POWER1,
+				     WM8900_REG_POWER1_BIAS_ENA | 0x1);
+		}
+
+		reg = snd_soc_read(codec, WM8900_REG_POWER1);
+		snd_soc_write(codec, WM8900_REG_POWER1,
+			     (reg & WM8900_REG_POWER1_FLL_ENA) |
+			     WM8900_REG_POWER1_BIAS_ENA | 0x1);
+		snd_soc_write(codec, WM8900_REG_POWER2,
+			     WM8900_REG_POWER2_SYSCLK_ENA);
+		snd_soc_write(codec, WM8900_REG_POWER3, 0);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		/* Startup bias enable */
+		reg = snd_soc_read(codec, WM8900_REG_POWER1);
+		snd_soc_write(codec, WM8900_REG_POWER1,
+			     reg & WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+		snd_soc_write(codec, WM8900_REG_ADDCTL,
+			     WM8900_REG_ADDCTL_BIAS_SRC |
+			     WM8900_REG_ADDCTL_VMID_SOFTST);
+
+		/* Discharge caps */
+		snd_soc_write(codec, WM8900_REG_POWER1,
+			     WM8900_REG_POWER1_STARTUP_BIAS_ENA);
+		schedule_timeout_interruptible(msecs_to_jiffies(500));
+
+		/* Remove clamp */
+		snd_soc_write(codec, WM8900_REG_HPCTL1, 0);
+
+		/* Power down */
+		snd_soc_write(codec, WM8900_REG_ADDCTL, 0);
+		snd_soc_write(codec, WM8900_REG_POWER1, 0);
+		snd_soc_write(codec, WM8900_REG_POWER2, 0);
+		snd_soc_write(codec, WM8900_REG_POWER3, 0);
+
+		/* Need to let things settle before stopping the clock
+		 * to ensure that restart works, see "Stopping the
+		 * master clock" in the datasheet. */
+		schedule_timeout_interruptible(msecs_to_jiffies(1));
+		snd_soc_write(codec, WM8900_REG_POWER2,
+			     WM8900_REG_POWER2_SYSCLK_ENA);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+static int wm8900_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec);
+	int fll_out = wm8900->fll_out;
+	int fll_in  = wm8900->fll_in;
+	int ret;
+
+	/* Stop the FLL in an orderly fashion */
+	ret = wm8900_set_fll(codec, 0, 0, 0);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to stop FLL\n");
+		return ret;
+	}
+
+	wm8900->fll_out = fll_out;
+	wm8900->fll_in = fll_in;
+
+	wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm8900_resume(struct snd_soc_codec *codec)
+{
+	struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec);
+	u16 *cache;
+	int i, ret;
+
+	cache = kmemdup(codec->reg_cache, sizeof(wm8900_reg_defaults),
+			GFP_KERNEL);
+
+	wm8900_reset(codec);
+	wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Restart the FLL? */
+	if (wm8900->fll_out) {
+		int fll_out = wm8900->fll_out;
+		int fll_in  = wm8900->fll_in;
+
+		wm8900->fll_in = 0;
+		wm8900->fll_out = 0;
+
+		ret = wm8900_set_fll(codec, 0, fll_in, fll_out);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to restart FLL\n");
+			return ret;
+		}
+	}
+
+	if (cache) {
+		for (i = 0; i < WM8900_MAXREG; i++)
+			snd_soc_write(codec, i, cache[i]);
+		kfree(cache);
+	} else
+		dev_err(codec->dev, "Unable to allocate register cache\n");
+
+	return 0;
+}
+
+static int wm8900_probe(struct snd_soc_codec *codec)
+{
+	struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0, reg;
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm8900->control_type);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	reg = snd_soc_read(codec, WM8900_REG_ID);
+	if (reg != 0x8900) {
+		dev_err(codec->dev, "Device is not a WM8900 - ID %x\n", reg);
+		return -ENODEV;
+	}
+
+	wm8900_reset(codec);
+
+	/* Turn the chip on */
+	wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Latch the volume update bits */
+	snd_soc_write(codec, WM8900_REG_LINVOL,
+		      snd_soc_read(codec, WM8900_REG_LINVOL) | 0x100);
+	snd_soc_write(codec, WM8900_REG_RINVOL,
+		      snd_soc_read(codec, WM8900_REG_RINVOL) | 0x100);
+	snd_soc_write(codec, WM8900_REG_LOUT1CTL,
+		      snd_soc_read(codec, WM8900_REG_LOUT1CTL) | 0x100);
+	snd_soc_write(codec, WM8900_REG_ROUT1CTL,
+		      snd_soc_read(codec, WM8900_REG_ROUT1CTL) | 0x100);
+	snd_soc_write(codec, WM8900_REG_LOUT2CTL,
+		      snd_soc_read(codec, WM8900_REG_LOUT2CTL) | 0x100);
+	snd_soc_write(codec, WM8900_REG_ROUT2CTL,
+		      snd_soc_read(codec, WM8900_REG_ROUT2CTL) | 0x100);
+	snd_soc_write(codec, WM8900_REG_LDAC_DV,
+		      snd_soc_read(codec, WM8900_REG_LDAC_DV) | 0x100);
+	snd_soc_write(codec, WM8900_REG_RDAC_DV,
+		      snd_soc_read(codec, WM8900_REG_RDAC_DV) | 0x100);
+	snd_soc_write(codec, WM8900_REG_LADC_DV,
+		      snd_soc_read(codec, WM8900_REG_LADC_DV) | 0x100);
+	snd_soc_write(codec, WM8900_REG_RADC_DV,
+		      snd_soc_read(codec, WM8900_REG_RADC_DV) | 0x100);
+
+	/* Set the DAC and mixer output bias */
+	snd_soc_write(codec, WM8900_REG_OUTBIASCTL, 0x81);
+
+	snd_soc_add_controls(codec, wm8900_snd_controls,
+				ARRAY_SIZE(wm8900_snd_controls));
+	wm8900_add_widgets(codec);
+
+	return 0;
+}
+
+/* power down chip */
+static int wm8900_remove(struct snd_soc_codec *codec)
+{
+	wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8900 = {
+	.probe =	wm8900_probe,
+	.remove =	wm8900_remove,
+	.suspend =	wm8900_suspend,
+	.resume =	wm8900_resume,
+	.set_bias_level = wm8900_set_bias_level,
+	.volatile_register = wm8900_volatile_register,
+	.reg_cache_size = ARRAY_SIZE(wm8900_reg_defaults),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8900_reg_defaults,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8900_spi_probe(struct spi_device *spi)
+{
+	struct wm8900_priv *wm8900;
+	int ret;
+
+	wm8900 = kzalloc(sizeof(struct wm8900_priv), GFP_KERNEL);
+	if (wm8900 == NULL)
+		return -ENOMEM;
+
+	wm8900->control_type = SND_SOC_SPI;
+	spi_set_drvdata(spi, wm8900);
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_dev_wm8900, &wm8900_dai, 1);
+	if (ret < 0)
+		kfree(wm8900);
+	return ret;
+}
+
+static int __devexit wm8900_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver wm8900_spi_driver = {
+	.driver = {
+		.name	= "wm8900-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= wm8900_spi_probe,
+	.remove		= __devexit_p(wm8900_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8900_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8900_priv *wm8900;
+	int ret;
+
+	wm8900 = kzalloc(sizeof(struct wm8900_priv), GFP_KERNEL);
+	if (wm8900 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8900);
+	wm8900->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8900, &wm8900_dai, 1);
+	if (ret < 0)
+		kfree(wm8900);
+	return ret;
+}
+
+static __devexit int wm8900_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8900_i2c_id[] = {
+	{ "wm8900", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8900_i2c_id);
+
+static struct i2c_driver wm8900_i2c_driver = {
+	.driver = {
+		.name = "wm8900-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8900_i2c_probe,
+	.remove =   __devexit_p(wm8900_i2c_remove),
+	.id_table = wm8900_i2c_id,
+};
+#endif
+
+static int __init wm8900_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8900_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8900 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&wm8900_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8900 SPI driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8900_modinit);
+
+static void __exit wm8900_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8900_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&wm8900_spi_driver);
+#endif
+}
+module_exit(wm8900_exit);
+
+MODULE_DESCRIPTION("ASoC WM8900 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8900.h b/linux/sound/soc/codecs/wm8900.h
new file mode 100644
index 0000000..583f257
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8900.h
@@ -0,0 +1,55 @@
+/*
+ * wm8900.h  --  WM890 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8900_H
+#define _WM8900_H
+
+#define WM8900_FLL 1
+
+#define WM8900_BCLK_DIV   1
+#define WM8900_ADC_CLKDIV 2
+#define WM8900_DAC_CLKDIV 3
+#define WM8900_ADC_LRCLK  4
+#define WM8900_DAC_LRCLK  5
+#define WM8900_OPCLK_DIV  6
+#define WM8900_LRCLK_MODE 7
+
+#define WM8900_BCLK_DIV_1   0x00
+#define WM8900_BCLK_DIV_1_5 0x02
+#define WM8900_BCLK_DIV_2   0x04
+#define WM8900_BCLK_DIV_3   0x06
+#define WM8900_BCLK_DIV_4   0x08
+#define WM8900_BCLK_DIV_5_5 0x0a
+#define WM8900_BCLK_DIV_6   0x0c
+#define WM8900_BCLK_DIV_8   0x0e
+#define WM8900_BCLK_DIV_11  0x10
+#define WM8900_BCLK_DIV_12  0x12
+#define WM8900_BCLK_DIV_16  0x14
+#define WM8900_BCLK_DIV_22  0x16
+#define WM8900_BCLK_DIV_24  0x18
+#define WM8900_BCLK_DIV_32  0x1a
+#define WM8900_BCLK_DIV_44  0x1c
+#define WM8900_BCLK_DIV_48  0x1e
+
+#define WM8900_ADC_CLKDIV_1   0x00
+#define WM8900_ADC_CLKDIV_1_5 0x20
+#define WM8900_ADC_CLKDIV_2   0x40
+#define WM8900_ADC_CLKDIV_3   0x60
+#define WM8900_ADC_CLKDIV_4   0x80
+#define WM8900_ADC_CLKDIV_5_5 0xa0
+#define WM8900_ADC_CLKDIV_6   0xc0
+
+#define WM8900_DAC_CLKDIV_1   0x00
+#define WM8900_DAC_CLKDIV_1_5 0x04
+#define WM8900_DAC_CLKDIV_2   0x08
+#define WM8900_DAC_CLKDIV_3   0x0c
+#define WM8900_DAC_CLKDIV_4   0x10
+#define WM8900_DAC_CLKDIV_5_5 0x14
+#define WM8900_DAC_CLKDIV_6   0x18
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8903.c b/linux/sound/soc/codecs/wm8903.c
new file mode 100644
index 0000000..622b602
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8903.c
@@ -0,0 +1,1837 @@
+/*
+ * wm8903.c  --  WM8903 ALSA SoC Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO:
+ *  - TDM mode configuration.
+ *  - Digital microphone support.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/wm8903.h>
+
+#include "wm8903.h"
+
+/* Register defaults at reset */
+static u16 wm8903_reg_defaults[] = {
+	0x8903,     /* R0   - SW Reset and ID */
+	0x0000,     /* R1   - Revision Number */
+	0x0000,     /* R2 */
+	0x0000,     /* R3 */
+	0x0018,     /* R4   - Bias Control 0 */
+	0x0000,     /* R5   - VMID Control 0 */
+	0x0000,     /* R6   - Mic Bias Control 0 */
+	0x0000,     /* R7 */
+	0x0001,     /* R8   - Analogue DAC 0 */
+	0x0000,     /* R9 */
+	0x0001,     /* R10  - Analogue ADC 0 */
+	0x0000,     /* R11 */
+	0x0000,     /* R12  - Power Management 0 */
+	0x0000,     /* R13  - Power Management 1 */
+	0x0000,     /* R14  - Power Management 2 */
+	0x0000,     /* R15  - Power Management 3 */
+	0x0000,     /* R16  - Power Management 4 */
+	0x0000,     /* R17  - Power Management 5 */
+	0x0000,     /* R18  - Power Management 6 */
+	0x0000,     /* R19 */
+	0x0400,     /* R20  - Clock Rates 0 */
+	0x0D07,     /* R21  - Clock Rates 1 */
+	0x0000,     /* R22  - Clock Rates 2 */
+	0x0000,     /* R23 */
+	0x0050,     /* R24  - Audio Interface 0 */
+	0x0242,     /* R25  - Audio Interface 1 */
+	0x0008,     /* R26  - Audio Interface 2 */
+	0x0022,     /* R27  - Audio Interface 3 */
+	0x0000,     /* R28 */
+	0x0000,     /* R29 */
+	0x00C0,     /* R30  - DAC Digital Volume Left */
+	0x00C0,     /* R31  - DAC Digital Volume Right */
+	0x0000,     /* R32  - DAC Digital 0 */
+	0x0000,     /* R33  - DAC Digital 1 */
+	0x0000,     /* R34 */
+	0x0000,     /* R35 */
+	0x00C0,     /* R36  - ADC Digital Volume Left */
+	0x00C0,     /* R37  - ADC Digital Volume Right */
+	0x0000,     /* R38  - ADC Digital 0 */
+	0x0073,     /* R39  - Digital Microphone 0 */
+	0x09BF,     /* R40  - DRC 0 */
+	0x3241,     /* R41  - DRC 1 */
+	0x0020,     /* R42  - DRC 2 */
+	0x0000,     /* R43  - DRC 3 */
+	0x0085,     /* R44  - Analogue Left Input 0 */
+	0x0085,     /* R45  - Analogue Right Input 0 */
+	0x0044,     /* R46  - Analogue Left Input 1 */
+	0x0044,     /* R47  - Analogue Right Input 1 */
+	0x0000,     /* R48 */
+	0x0000,     /* R49 */
+	0x0008,     /* R50  - Analogue Left Mix 0 */
+	0x0004,     /* R51  - Analogue Right Mix 0 */
+	0x0000,     /* R52  - Analogue Spk Mix Left 0 */
+	0x0000,     /* R53  - Analogue Spk Mix Left 1 */
+	0x0000,     /* R54  - Analogue Spk Mix Right 0 */
+	0x0000,     /* R55  - Analogue Spk Mix Right 1 */
+	0x0000,     /* R56 */
+	0x002D,     /* R57  - Analogue OUT1 Left */
+	0x002D,     /* R58  - Analogue OUT1 Right */
+	0x0039,     /* R59  - Analogue OUT2 Left */
+	0x0039,     /* R60  - Analogue OUT2 Right */
+	0x0100,     /* R61 */
+	0x0139,     /* R62  - Analogue OUT3 Left */
+	0x0139,     /* R63  - Analogue OUT3 Right */
+	0x0000,     /* R64 */
+	0x0000,     /* R65  - Analogue SPK Output Control 0 */
+	0x0000,     /* R66 */
+	0x0010,     /* R67  - DC Servo 0 */
+	0x0100,     /* R68 */
+	0x00A4,     /* R69  - DC Servo 2 */
+	0x0807,     /* R70 */
+	0x0000,     /* R71 */
+	0x0000,     /* R72 */
+	0x0000,     /* R73 */
+	0x0000,     /* R74 */
+	0x0000,     /* R75 */
+	0x0000,     /* R76 */
+	0x0000,     /* R77 */
+	0x0000,     /* R78 */
+	0x000E,     /* R79 */
+	0x0000,     /* R80 */
+	0x0000,     /* R81 */
+	0x0000,     /* R82 */
+	0x0000,     /* R83 */
+	0x0000,     /* R84 */
+	0x0000,     /* R85 */
+	0x0000,     /* R86 */
+	0x0006,     /* R87 */
+	0x0000,     /* R88 */
+	0x0000,     /* R89 */
+	0x0000,     /* R90  - Analogue HP 0 */
+	0x0060,     /* R91 */
+	0x0000,     /* R92 */
+	0x0000,     /* R93 */
+	0x0000,     /* R94  - Analogue Lineout 0 */
+	0x0060,     /* R95 */
+	0x0000,     /* R96 */
+	0x0000,     /* R97 */
+	0x0000,     /* R98  - Charge Pump 0 */
+	0x1F25,     /* R99 */
+	0x2B19,     /* R100 */
+	0x01C0,     /* R101 */
+	0x01EF,     /* R102 */
+	0x2B00,     /* R103 */
+	0x0000,     /* R104 - Class W 0 */
+	0x01C0,     /* R105 */
+	0x1C10,     /* R106 */
+	0x0000,     /* R107 */
+	0x0000,     /* R108 - Write Sequencer 0 */
+	0x0000,     /* R109 - Write Sequencer 1 */
+	0x0000,     /* R110 - Write Sequencer 2 */
+	0x0000,     /* R111 - Write Sequencer 3 */
+	0x0000,     /* R112 - Write Sequencer 4 */
+	0x0000,     /* R113 */
+	0x0000,     /* R114 - Control Interface */
+	0x0000,     /* R115 */
+	0x00A8,     /* R116 - GPIO Control 1 */
+	0x00A8,     /* R117 - GPIO Control 2 */
+	0x00A8,     /* R118 - GPIO Control 3 */
+	0x0220,     /* R119 - GPIO Control 4 */
+	0x01A0,     /* R120 - GPIO Control 5 */
+	0x0000,     /* R121 - Interrupt Status 1 */
+	0xFFFF,     /* R122 - Interrupt Status 1 Mask */
+	0x0000,     /* R123 - Interrupt Polarity 1 */
+	0x0000,     /* R124 */
+	0x0003,     /* R125 */
+	0x0000,     /* R126 - Interrupt Control */
+	0x0000,     /* R127 */
+	0x0005,     /* R128 */
+	0x0000,     /* R129 - Control Interface Test 1 */
+	0x0000,     /* R130 */
+	0x0000,     /* R131 */
+	0x0000,     /* R132 */
+	0x0000,     /* R133 */
+	0x0000,     /* R134 */
+	0x03FF,     /* R135 */
+	0x0007,     /* R136 */
+	0x0040,     /* R137 */
+	0x0000,     /* R138 */
+	0x0000,     /* R139 */
+	0x0000,     /* R140 */
+	0x0000,     /* R141 */
+	0x0000,     /* R142 */
+	0x0000,     /* R143 */
+	0x0000,     /* R144 */
+	0x0000,     /* R145 */
+	0x0000,     /* R146 */
+	0x0000,     /* R147 */
+	0x4000,     /* R148 */
+	0x6810,     /* R149 - Charge Pump Test 1 */
+	0x0004,     /* R150 */
+	0x0000,     /* R151 */
+	0x0000,     /* R152 */
+	0x0000,     /* R153 */
+	0x0000,     /* R154 */
+	0x0000,     /* R155 */
+	0x0000,     /* R156 */
+	0x0000,     /* R157 */
+	0x0000,     /* R158 */
+	0x0000,     /* R159 */
+	0x0000,     /* R160 */
+	0x0000,     /* R161 */
+	0x0000,     /* R162 */
+	0x0000,     /* R163 */
+	0x0028,     /* R164 - Clock Rate Test 4 */
+	0x0004,     /* R165 */
+	0x0000,     /* R166 */
+	0x0060,     /* R167 */
+	0x0000,     /* R168 */
+	0x0000,     /* R169 */
+	0x0000,     /* R170 */
+	0x0000,     /* R171 */
+	0x0000,     /* R172 - Analogue Output Bias 0 */
+};
+
+struct wm8903_priv {
+
+	u16 reg_cache[ARRAY_SIZE(wm8903_reg_defaults)];
+
+	int sysclk;
+	int irq;
+
+	/* Reference counts */
+	int class_w_users;
+	int playback_active;
+	int capture_active;
+
+	struct completion wseq;
+
+	struct snd_soc_jack *mic_jack;
+	int mic_det;
+	int mic_short;
+	int mic_last_report;
+	int mic_delay;
+
+	struct snd_pcm_substream *master_substream;
+	struct snd_pcm_substream *slave_substream;
+};
+
+static int wm8903_volatile_register(unsigned int reg)
+{
+	switch (reg) {
+	case WM8903_SW_RESET_AND_ID:
+	case WM8903_REVISION_NUMBER:
+	case WM8903_INTERRUPT_STATUS_1:
+	case WM8903_WRITE_SEQUENCER_4:
+		return 1;
+
+	default:
+		return 0;
+	}
+}
+
+static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start)
+{
+	u16 reg[5];
+	struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+
+	BUG_ON(start > 48);
+
+	/* Enable the sequencer if it's not already on */
+	reg[0] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_0);
+	snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0,
+		      reg[0] | WM8903_WSEQ_ENA);
+
+	dev_dbg(codec->dev, "Starting sequence at %d\n", start);
+
+	snd_soc_write(codec, WM8903_WRITE_SEQUENCER_3,
+		     start | WM8903_WSEQ_START);
+
+	/* Wait for it to complete.  If we have the interrupt wired up then
+	 * that will break us out of the poll early.
+	 */
+	do {
+		wait_for_completion_timeout(&wm8903->wseq,
+					    msecs_to_jiffies(10));
+
+		reg[4] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_4);
+	} while (reg[4] & WM8903_WSEQ_BUSY);
+
+	dev_dbg(codec->dev, "Sequence complete\n");
+
+	/* Disable the sequencer again if we enabled it */
+	snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]);
+
+	return 0;
+}
+
+static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache)
+{
+	int i;
+
+	/* There really ought to be something better we can do here :/ */
+	for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++)
+		cache[i] = codec->hw_read(codec, i);
+}
+
+static void wm8903_reset(struct snd_soc_codec *codec)
+{
+	snd_soc_write(codec, WM8903_SW_RESET_AND_ID, 0);
+	memcpy(codec->reg_cache, wm8903_reg_defaults,
+	       sizeof(wm8903_reg_defaults));
+}
+
+#define WM8903_OUTPUT_SHORT 0x8
+#define WM8903_OUTPUT_OUT   0x4
+#define WM8903_OUTPUT_INT   0x2
+#define WM8903_OUTPUT_IN    0x1
+
+static int wm8903_cp_event(struct snd_soc_dapm_widget *w,
+			   struct snd_kcontrol *kcontrol, int event)
+{
+	WARN_ON(event != SND_SOC_DAPM_POST_PMU);
+	mdelay(4);
+
+	return 0;
+}
+
+/*
+ * Event for headphone and line out amplifier power changes.  Special
+ * power up/down sequences are required in order to maximise pop/click
+ * performance.
+ */
+static int wm8903_output_event(struct snd_soc_dapm_widget *w,
+			       struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	u16 val;
+	u16 reg;
+	u16 dcs_reg;
+	u16 dcs_bit;
+	int shift;
+
+	switch (w->reg) {
+	case WM8903_POWER_MANAGEMENT_2:
+		reg = WM8903_ANALOGUE_HP_0;
+		dcs_bit = 0 + w->shift;
+		break;
+	case WM8903_POWER_MANAGEMENT_3:
+		reg = WM8903_ANALOGUE_LINEOUT_0;
+		dcs_bit = 2 + w->shift;
+		break;
+	default:
+		BUG();
+		return -EINVAL;  /* Spurious warning from some compilers */
+	}
+
+	switch (w->shift) {
+	case 0:
+		shift = 0;
+		break;
+	case 1:
+		shift = 4;
+		break;
+	default:
+		BUG();
+		return -EINVAL;  /* Spurious warning from some compilers */
+	}
+
+	if (event & SND_SOC_DAPM_PRE_PMU) {
+		val = snd_soc_read(codec, reg);
+
+		/* Short the output */
+		val &= ~(WM8903_OUTPUT_SHORT << shift);
+		snd_soc_write(codec, reg, val);
+	}
+
+	if (event & SND_SOC_DAPM_POST_PMU) {
+		val = snd_soc_read(codec, reg);
+
+		val |= (WM8903_OUTPUT_IN << shift);
+		snd_soc_write(codec, reg, val);
+
+		val |= (WM8903_OUTPUT_INT << shift);
+		snd_soc_write(codec, reg, val);
+
+		/* Turn on the output ENA_OUTP */
+		val |= (WM8903_OUTPUT_OUT << shift);
+		snd_soc_write(codec, reg, val);
+
+		/* Enable the DC servo */
+		dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0);
+		dcs_reg |= dcs_bit;
+		snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg);
+
+		/* Remove the short */
+		val |= (WM8903_OUTPUT_SHORT << shift);
+		snd_soc_write(codec, reg, val);
+	}
+
+	if (event & SND_SOC_DAPM_PRE_PMD) {
+		val = snd_soc_read(codec, reg);
+
+		/* Short the output */
+		val &= ~(WM8903_OUTPUT_SHORT << shift);
+		snd_soc_write(codec, reg, val);
+
+		/* Disable the DC servo */
+		dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0);
+		dcs_reg &= ~dcs_bit;
+		snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg);
+
+		/* Then disable the intermediate and output stages */
+		val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT |
+			  WM8903_OUTPUT_IN) << shift);
+		snd_soc_write(codec, reg, val);
+	}
+
+	return 0;
+}
+
+/*
+ * When used with DAC outputs only the WM8903 charge pump supports
+ * operation in class W mode, providing very low power consumption
+ * when used with digital sources.  Enable and disable this mode
+ * automatically depending on the mixer configuration.
+ *
+ * All the relevant controls are simple switches.
+ */
+static int wm8903_class_w_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_codec *codec = widget->codec;
+	struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+	u16 reg;
+	int ret;
+
+	reg = snd_soc_read(codec, WM8903_CLASS_W_0);
+
+	/* Turn it off if we're about to enable bypass */
+	if (ucontrol->value.integer.value[0]) {
+		if (wm8903->class_w_users == 0) {
+			dev_dbg(codec->dev, "Disabling Class W\n");
+			snd_soc_write(codec, WM8903_CLASS_W_0, reg &
+				     ~(WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V));
+		}
+		wm8903->class_w_users++;
+	}
+
+	/* Implement the change */
+	ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+
+	/* If we've just disabled the last bypass path turn Class W on */
+	if (!ucontrol->value.integer.value[0]) {
+		if (wm8903->class_w_users == 1) {
+			dev_dbg(codec->dev, "Enabling Class W\n");
+			snd_soc_write(codec, WM8903_CLASS_W_0, reg |
+				     WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V);
+		}
+		wm8903->class_w_users--;
+	}
+
+	dev_dbg(codec->dev, "Bypass use count now %d\n",
+		wm8903->class_w_users);
+
+	return ret;
+}
+
+#define SOC_DAPM_SINGLE_W(xname, reg, shift, max, invert) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = snd_soc_info_volsw, \
+	.get = snd_soc_dapm_get_volsw, .put = wm8903_class_w_put, \
+	.private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+
+/* ALSA can only do steps of .01dB */
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+
+static const DECLARE_TLV_DB_SCALE(digital_sidetone_tlv, -3600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_amp, -2250, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_min, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_max, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_tlv_startup, -300, 50, 0);
+
+static const char *drc_slope_text[] = {
+	"1", "1/2", "1/4", "1/8", "1/16", "0"
+};
+
+static const struct soc_enum drc_slope_r0 =
+	SOC_ENUM_SINGLE(WM8903_DRC_2, 3, 6, drc_slope_text);
+
+static const struct soc_enum drc_slope_r1 =
+	SOC_ENUM_SINGLE(WM8903_DRC_2, 0, 6, drc_slope_text);
+
+static const char *drc_attack_text[] = {
+	"instantaneous",
+	"363us", "762us", "1.45ms", "2.9ms", "5.8ms", "11.6ms", "23.2ms",
+	"46.4ms", "92.8ms", "185.6ms"
+};
+
+static const struct soc_enum drc_attack =
+	SOC_ENUM_SINGLE(WM8903_DRC_1, 12, 11, drc_attack_text);
+
+static const char *drc_decay_text[] = {
+	"186ms", "372ms", "743ms", "1.49s", "2.97s", "5.94s", "11.89s",
+	"23.87s", "47.56s"
+};
+
+static const struct soc_enum drc_decay =
+	SOC_ENUM_SINGLE(WM8903_DRC_1, 8, 9, drc_decay_text);
+
+static const char *drc_ff_delay_text[] = {
+	"5 samples", "9 samples"
+};
+
+static const struct soc_enum drc_ff_delay =
+	SOC_ENUM_SINGLE(WM8903_DRC_0, 5, 2, drc_ff_delay_text);
+
+static const char *drc_qr_decay_text[] = {
+	"0.725ms", "1.45ms", "5.8ms"
+};
+
+static const struct soc_enum drc_qr_decay =
+	SOC_ENUM_SINGLE(WM8903_DRC_1, 4, 3, drc_qr_decay_text);
+
+static const char *drc_smoothing_text[] = {
+	"Low", "Medium", "High"
+};
+
+static const struct soc_enum drc_smoothing =
+	SOC_ENUM_SINGLE(WM8903_DRC_0, 11, 3, drc_smoothing_text);
+
+static const char *soft_mute_text[] = {
+	"Fast (fs/2)", "Slow (fs/32)"
+};
+
+static const struct soc_enum soft_mute =
+	SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 10, 2, soft_mute_text);
+
+static const char *mute_mode_text[] = {
+	"Hard", "Soft"
+};
+
+static const struct soc_enum mute_mode =
+	SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 9, 2, mute_mode_text);
+
+static const char *dac_deemphasis_text[] = {
+	"Disabled", "32kHz", "44.1kHz", "48kHz"
+};
+
+static const struct soc_enum dac_deemphasis =
+	SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 1, 4, dac_deemphasis_text);
+
+static const char *companding_text[] = {
+	"ulaw", "alaw"
+};
+
+static const struct soc_enum dac_companding =
+	SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 0, 2, companding_text);
+
+static const struct soc_enum adc_companding =
+	SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 2, 2, companding_text);
+
+static const char *input_mode_text[] = {
+	"Single-Ended", "Differential Line", "Differential Mic"
+};
+
+static const struct soc_enum linput_mode_enum =
+	SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 0, 3, input_mode_text);
+
+static const struct soc_enum rinput_mode_enum =
+	SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 0, 3, input_mode_text);
+
+static const char *linput_mux_text[] = {
+	"IN1L", "IN2L", "IN3L"
+};
+
+static const struct soc_enum linput_enum =
+	SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 2, 3, linput_mux_text);
+
+static const struct soc_enum linput_inv_enum =
+	SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 4, 3, linput_mux_text);
+
+static const char *rinput_mux_text[] = {
+	"IN1R", "IN2R", "IN3R"
+};
+
+static const struct soc_enum rinput_enum =
+	SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 2, 3, rinput_mux_text);
+
+static const struct soc_enum rinput_inv_enum =
+	SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 4, 3, rinput_mux_text);
+
+
+static const char *sidetone_text[] = {
+	"None", "Left", "Right"
+};
+
+static const struct soc_enum lsidetone_enum =
+	SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 2, 3, sidetone_text);
+
+static const struct soc_enum rsidetone_enum =
+	SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 0, 3, sidetone_text);
+
+static const struct snd_kcontrol_new wm8903_snd_controls[] = {
+
+/* Input PGAs - No TLV since the scale depends on PGA mode */
+SOC_SINGLE("Left Input PGA Switch", WM8903_ANALOGUE_LEFT_INPUT_0,
+	   7, 1, 1),
+SOC_SINGLE("Left Input PGA Volume", WM8903_ANALOGUE_LEFT_INPUT_0,
+	   0, 31, 0),
+SOC_SINGLE("Left Input PGA Common Mode Switch", WM8903_ANALOGUE_LEFT_INPUT_1,
+	   6, 1, 0),
+
+SOC_SINGLE("Right Input PGA Switch", WM8903_ANALOGUE_RIGHT_INPUT_0,
+	   7, 1, 1),
+SOC_SINGLE("Right Input PGA Volume", WM8903_ANALOGUE_RIGHT_INPUT_0,
+	   0, 31, 0),
+SOC_SINGLE("Right Input PGA Common Mode Switch", WM8903_ANALOGUE_RIGHT_INPUT_1,
+	   6, 1, 0),
+
+/* ADCs */
+SOC_SINGLE("DRC Switch", WM8903_DRC_0, 15, 1, 0),
+SOC_ENUM("DRC Compressor Slope R0", drc_slope_r0),
+SOC_ENUM("DRC Compressor Slope R1", drc_slope_r1),
+SOC_SINGLE_TLV("DRC Compressor Threshold Volume", WM8903_DRC_3, 5, 124, 1,
+	       drc_tlv_thresh),
+SOC_SINGLE_TLV("DRC Volume", WM8903_DRC_3, 0, 30, 1, drc_tlv_amp),
+SOC_SINGLE_TLV("DRC Minimum Gain Volume", WM8903_DRC_1, 2, 3, 1, drc_tlv_min),
+SOC_SINGLE_TLV("DRC Maximum Gain Volume", WM8903_DRC_1, 0, 3, 0, drc_tlv_max),
+SOC_ENUM("DRC Attack Rate", drc_attack),
+SOC_ENUM("DRC Decay Rate", drc_decay),
+SOC_ENUM("DRC FF Delay", drc_ff_delay),
+SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0),
+SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0),
+SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max),
+SOC_ENUM("DRC QR Decay Rate", drc_qr_decay),
+SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0),
+SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0),
+SOC_ENUM("DRC Smoothing Threshold", drc_smoothing),
+SOC_SINGLE_TLV("DRC Startup Volume", WM8903_DRC_0, 6, 18, 0, drc_tlv_startup),
+
+SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT,
+		 WM8903_ADC_DIGITAL_VOLUME_RIGHT, 1, 96, 0, digital_tlv),
+SOC_ENUM("ADC Companding Mode", adc_companding),
+SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0),
+
+SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8903_DAC_DIGITAL_0, 4, 8,
+	       12, 0, digital_sidetone_tlv),
+
+/* DAC */
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT,
+		 WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv),
+SOC_ENUM("DAC Soft Mute Rate", soft_mute),
+SOC_ENUM("DAC Mute Mode", mute_mode),
+SOC_SINGLE("DAC Mono Switch", WM8903_DAC_DIGITAL_1, 12, 1, 0),
+SOC_ENUM("DAC De-emphasis", dac_deemphasis),
+SOC_ENUM("DAC Companding Mode", dac_companding),
+SOC_SINGLE("DAC Companding Switch", WM8903_AUDIO_INTERFACE_0, 1, 1, 0),
+
+/* Headphones */
+SOC_DOUBLE_R("Headphone Switch",
+	     WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+	     8, 1, 1),
+SOC_DOUBLE_R("Headphone ZC Switch",
+	     WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+	     6, 1, 0),
+SOC_DOUBLE_R_TLV("Headphone Volume",
+		 WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT,
+		 0, 63, 0, out_tlv),
+
+/* Line out */
+SOC_DOUBLE_R("Line Out Switch",
+	     WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+	     8, 1, 1),
+SOC_DOUBLE_R("Line Out ZC Switch",
+	     WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+	     6, 1, 0),
+SOC_DOUBLE_R_TLV("Line Out Volume",
+		 WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT,
+		 0, 63, 0, out_tlv),
+
+/* Speaker */
+SOC_DOUBLE_R("Speaker Switch",
+	     WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 8, 1, 1),
+SOC_DOUBLE_R("Speaker ZC Switch",
+	     WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 6, 1, 0),
+SOC_DOUBLE_R_TLV("Speaker Volume",
+		 WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT,
+		 0, 63, 0, out_tlv),
+};
+
+static const struct snd_kcontrol_new linput_mode_mux =
+	SOC_DAPM_ENUM("Left Input Mode Mux", linput_mode_enum);
+
+static const struct snd_kcontrol_new rinput_mode_mux =
+	SOC_DAPM_ENUM("Right Input Mode Mux", rinput_mode_enum);
+
+static const struct snd_kcontrol_new linput_mux =
+	SOC_DAPM_ENUM("Left Input Mux", linput_enum);
+
+static const struct snd_kcontrol_new linput_inv_mux =
+	SOC_DAPM_ENUM("Left Inverting Input Mux", linput_inv_enum);
+
+static const struct snd_kcontrol_new rinput_mux =
+	SOC_DAPM_ENUM("Right Input Mux", rinput_enum);
+
+static const struct snd_kcontrol_new rinput_inv_mux =
+	SOC_DAPM_ENUM("Right Inverting Input Mux", rinput_inv_enum);
+
+static const struct snd_kcontrol_new lsidetone_mux =
+	SOC_DAPM_ENUM("DACL Sidetone Mux", lsidetone_enum);
+
+static const struct snd_kcontrol_new rsidetone_mux =
+	SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_enum);
+
+static const struct snd_kcontrol_new left_output_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0),
+SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0),
+SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_output_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 2, 1, 0),
+SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0),
+SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 2, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 1, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0,
+		0, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 3, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 2, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0,
+		1, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0,
+		0, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1L"),
+SND_SOC_DAPM_INPUT("IN1R"),
+SND_SOC_DAPM_INPUT("IN2L"),
+SND_SOC_DAPM_INPUT("IN2R"),
+SND_SOC_DAPM_INPUT("IN3L"),
+SND_SOC_DAPM_INPUT("IN3R"),
+
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+SND_SOC_DAPM_OUTPUT("LINEOUTL"),
+SND_SOC_DAPM_OUTPUT("LINEOUTR"),
+SND_SOC_DAPM_OUTPUT("LOP"),
+SND_SOC_DAPM_OUTPUT("LON"),
+SND_SOC_DAPM_OUTPUT("ROP"),
+SND_SOC_DAPM_OUTPUT("RON"),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8903_MIC_BIAS_CONTROL_0, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Input Mux", SND_SOC_NOPM, 0, 0, &linput_mux),
+SND_SOC_DAPM_MUX("Left Input Inverting Mux", SND_SOC_NOPM, 0, 0,
+		 &linput_inv_mux),
+SND_SOC_DAPM_MUX("Left Input Mode Mux", SND_SOC_NOPM, 0, 0, &linput_mode_mux),
+
+SND_SOC_DAPM_MUX("Right Input Mux", SND_SOC_NOPM, 0, 0, &rinput_mux),
+SND_SOC_DAPM_MUX("Right Input Inverting Mux", SND_SOC_NOPM, 0, 0,
+		 &rinput_inv_mux),
+SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux),
+
+SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0),
+
+SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux),
+SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux),
+
+SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0),
+SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0,
+		   left_output_mixer, ARRAY_SIZE(left_output_mixer)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8903_POWER_MANAGEMENT_1, 0, 0,
+		   right_output_mixer, ARRAY_SIZE(right_output_mixer)),
+
+SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0,
+		   left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
+SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0,
+		   right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
+
+SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
+		   1, 0, NULL, 0, wm8903_output_event,
+		   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+		   SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
+		   0, 0, NULL, 0, wm8903_output_event,
+		   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+		   SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0,
+		   NULL, 0, wm8903_output_event,
+		   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+		   SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0,
+		   NULL, 0, wm8903_output_event,
+		   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+		   SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0,
+		 NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0,
+		 NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0,
+		    wm8903_cp_event, SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+
+	{ "Left Input Mux", "IN1L", "IN1L" },
+	{ "Left Input Mux", "IN2L", "IN2L" },
+	{ "Left Input Mux", "IN3L", "IN3L" },
+
+	{ "Left Input Inverting Mux", "IN1L", "IN1L" },
+	{ "Left Input Inverting Mux", "IN2L", "IN2L" },
+	{ "Left Input Inverting Mux", "IN3L", "IN3L" },
+
+	{ "Right Input Mux", "IN1R", "IN1R" },
+	{ "Right Input Mux", "IN2R", "IN2R" },
+	{ "Right Input Mux", "IN3R", "IN3R" },
+
+	{ "Right Input Inverting Mux", "IN1R", "IN1R" },
+	{ "Right Input Inverting Mux", "IN2R", "IN2R" },
+	{ "Right Input Inverting Mux", "IN3R", "IN3R" },
+
+	{ "Left Input Mode Mux", "Single-Ended", "Left Input Inverting Mux" },
+	{ "Left Input Mode Mux", "Differential Line",
+	  "Left Input Mux" },
+	{ "Left Input Mode Mux", "Differential Line",
+	  "Left Input Inverting Mux" },
+	{ "Left Input Mode Mux", "Differential Mic",
+	  "Left Input Mux" },
+	{ "Left Input Mode Mux", "Differential Mic",
+	  "Left Input Inverting Mux" },
+
+	{ "Right Input Mode Mux", "Single-Ended",
+	  "Right Input Inverting Mux" },
+	{ "Right Input Mode Mux", "Differential Line",
+	  "Right Input Mux" },
+	{ "Right Input Mode Mux", "Differential Line",
+	  "Right Input Inverting Mux" },
+	{ "Right Input Mode Mux", "Differential Mic",
+	  "Right Input Mux" },
+	{ "Right Input Mode Mux", "Differential Mic",
+	  "Right Input Inverting Mux" },
+
+	{ "Left Input PGA", NULL, "Left Input Mode Mux" },
+	{ "Right Input PGA", NULL, "Right Input Mode Mux" },
+
+	{ "ADCL", NULL, "Left Input PGA" },
+	{ "ADCL", NULL, "CLK_DSP" },
+	{ "ADCR", NULL, "Right Input PGA" },
+	{ "ADCR", NULL, "CLK_DSP" },
+
+	{ "DACL Sidetone", "Left", "ADCL" },
+	{ "DACL Sidetone", "Right", "ADCR" },
+	{ "DACR Sidetone", "Left", "ADCL" },
+	{ "DACR Sidetone", "Right", "ADCR" },
+
+	{ "DACL", NULL, "DACL Sidetone" },
+	{ "DACL", NULL, "CLK_DSP" },
+	{ "DACR", NULL, "DACR Sidetone" },
+	{ "DACR", NULL, "CLK_DSP" },
+
+	{ "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" },
+	{ "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" },
+	{ "Left Output Mixer", "DACL Switch", "DACL" },
+	{ "Left Output Mixer", "DACR Switch", "DACR" },
+
+	{ "Right Output Mixer", "Left Bypass Switch", "Left Input PGA" },
+	{ "Right Output Mixer", "Right Bypass Switch", "Right Input PGA" },
+	{ "Right Output Mixer", "DACL Switch", "DACL" },
+	{ "Right Output Mixer", "DACR Switch", "DACR" },
+
+	{ "Left Speaker Mixer", "Left Bypass Switch", "Left Input PGA" },
+	{ "Left Speaker Mixer", "Right Bypass Switch", "Right Input PGA" },
+	{ "Left Speaker Mixer", "DACL Switch", "DACL" },
+	{ "Left Speaker Mixer", "DACR Switch", "DACR" },
+
+	{ "Right Speaker Mixer", "Left Bypass Switch", "Left Input PGA" },
+	{ "Right Speaker Mixer", "Right Bypass Switch", "Right Input PGA" },
+	{ "Right Speaker Mixer", "DACL Switch", "DACL" },
+	{ "Right Speaker Mixer", "DACR Switch", "DACR" },
+
+	{ "Left Line Output PGA", NULL, "Left Output Mixer" },
+	{ "Right Line Output PGA", NULL, "Right Output Mixer" },
+
+	{ "Left Headphone Output PGA", NULL, "Left Output Mixer" },
+	{ "Right Headphone Output PGA", NULL, "Right Output Mixer" },
+
+	{ "Left Speaker PGA", NULL, "Left Speaker Mixer" },
+	{ "Right Speaker PGA", NULL, "Right Speaker Mixer" },
+
+	{ "HPOUTL", NULL, "Left Headphone Output PGA" },
+	{ "HPOUTR", NULL, "Right Headphone Output PGA" },
+
+	{ "LINEOUTL", NULL, "Left Line Output PGA" },
+	{ "LINEOUTR", NULL, "Right Line Output PGA" },
+
+	{ "LOP", NULL, "Left Speaker PGA" },
+	{ "LON", NULL, "Left Speaker PGA" },
+
+	{ "ROP", NULL, "Right Speaker PGA" },
+	{ "RON", NULL, "Right Speaker PGA" },
+
+	{ "Left Headphone Output PGA", NULL, "Charge Pump" },
+	{ "Right Headphone Output PGA", NULL, "Charge Pump" },
+	{ "Left Line Output PGA", NULL, "Charge Pump" },
+	{ "Right Line Output PGA", NULL, "Charge Pump" },
+};
+
+static int wm8903_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8903_dapm_widgets,
+				  ARRAY_SIZE(wm8903_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	return 0;
+}
+
+static int wm8903_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 reg, reg2;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+		reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0);
+		reg &= ~(WM8903_VMID_RES_MASK);
+		reg |= WM8903_VMID_RES_50K;
+		snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			snd_soc_write(codec, WM8903_CLOCK_RATES_2,
+				     WM8903_CLK_SYS_ENA);
+
+			/* Change DC servo dither level in startup sequence */
+			snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11);
+			snd_soc_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257);
+			snd_soc_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2);
+
+			wm8903_run_sequence(codec, 0);
+			wm8903_sync_reg_cache(codec, codec->reg_cache);
+
+			/* Enable low impedence charge pump output */
+			reg = snd_soc_read(codec,
+					  WM8903_CONTROL_INTERFACE_TEST_1);
+			snd_soc_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
+				     reg | WM8903_TEST_KEY);
+			reg2 = snd_soc_read(codec, WM8903_CHARGE_PUMP_TEST_1);
+			snd_soc_write(codec, WM8903_CHARGE_PUMP_TEST_1,
+				     reg2 | WM8903_CP_SW_KELVIN_MODE_MASK);
+			snd_soc_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
+				     reg);
+
+			/* By default no bypass paths are enabled so
+			 * enable Class W support.
+			 */
+			dev_dbg(codec->dev, "Enabling Class W\n");
+			snd_soc_write(codec, WM8903_CLASS_W_0, reg |
+				     WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V);
+		}
+
+		reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0);
+		reg &= ~(WM8903_VMID_RES_MASK);
+		reg |= WM8903_VMID_RES_250K;
+		snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		wm8903_run_sequence(codec, 32);
+		reg = snd_soc_read(codec, WM8903_CLOCK_RATES_2);
+		reg &= ~WM8903_CLK_SYS_ENA;
+		snd_soc_write(codec, WM8903_CLOCK_RATES_2, reg);
+		break;
+	}
+
+	codec->bias_level = level;
+
+	return 0;
+}
+
+static int wm8903_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+				 int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+
+	wm8903->sysclk = freq;
+
+	return 0;
+}
+
+static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			      unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 aif1 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_1);
+
+	aif1 &= ~(WM8903_LRCLK_DIR | WM8903_BCLK_DIR | WM8903_AIF_FMT_MASK |
+		  WM8903_AIF_LRCLK_INV | WM8903_AIF_BCLK_INV);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		aif1 |= WM8903_LRCLK_DIR;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		aif1 |= WM8903_LRCLK_DIR | WM8903_BCLK_DIR;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		aif1 |= WM8903_BCLK_DIR;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+		aif1 |= 0x3;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		aif1 |= 0x3 | WM8903_AIF_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		aif1 |= 0x2;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		aif1 |= 0x1;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		/* frame inversion not valid for DSP modes */
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif1 |= WM8903_AIF_BCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_LEFT_J:
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			aif1 |= WM8903_AIF_BCLK_INV | WM8903_AIF_LRCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif1 |= WM8903_AIF_BCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			aif1 |= WM8903_AIF_LRCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
+
+	return 0;
+}
+
+static int wm8903_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+
+	reg = snd_soc_read(codec, WM8903_DAC_DIGITAL_1);
+
+	if (mute)
+		reg |= WM8903_DAC_MUTE;
+	else
+		reg &= ~WM8903_DAC_MUTE;
+
+	snd_soc_write(codec, WM8903_DAC_DIGITAL_1, reg);
+
+	return 0;
+}
+
+/* Lookup table for CLK_SYS/fs ratio.  256fs or more is recommended
+ * for optimal performance so we list the lower rates first and match
+ * on the last match we find. */
+static struct {
+	int div;
+	int rate;
+	int mode;
+	int mclk_div;
+} clk_sys_ratios[] = {
+	{   64, 0x0, 0x0, 1 },
+	{   68, 0x0, 0x1, 1 },
+	{  125, 0x0, 0x2, 1 },
+	{  128, 0x1, 0x0, 1 },
+	{  136, 0x1, 0x1, 1 },
+	{  192, 0x2, 0x0, 1 },
+	{  204, 0x2, 0x1, 1 },
+
+	{   64, 0x0, 0x0, 2 },
+	{   68, 0x0, 0x1, 2 },
+	{  125, 0x0, 0x2, 2 },
+	{  128, 0x1, 0x0, 2 },
+	{  136, 0x1, 0x1, 2 },
+	{  192, 0x2, 0x0, 2 },
+	{  204, 0x2, 0x1, 2 },
+
+	{  250, 0x2, 0x2, 1 },
+	{  256, 0x3, 0x0, 1 },
+	{  272, 0x3, 0x1, 1 },
+	{  384, 0x4, 0x0, 1 },
+	{  408, 0x4, 0x1, 1 },
+	{  375, 0x4, 0x2, 1 },
+	{  512, 0x5, 0x0, 1 },
+	{  544, 0x5, 0x1, 1 },
+	{  500, 0x5, 0x2, 1 },
+	{  768, 0x6, 0x0, 1 },
+	{  816, 0x6, 0x1, 1 },
+	{  750, 0x6, 0x2, 1 },
+	{ 1024, 0x7, 0x0, 1 },
+	{ 1088, 0x7, 0x1, 1 },
+	{ 1000, 0x7, 0x2, 1 },
+	{ 1408, 0x8, 0x0, 1 },
+	{ 1496, 0x8, 0x1, 1 },
+	{ 1536, 0x9, 0x0, 1 },
+	{ 1632, 0x9, 0x1, 1 },
+	{ 1500, 0x9, 0x2, 1 },
+
+	{  250, 0x2, 0x2, 2 },
+	{  256, 0x3, 0x0, 2 },
+	{  272, 0x3, 0x1, 2 },
+	{  384, 0x4, 0x0, 2 },
+	{  408, 0x4, 0x1, 2 },
+	{  375, 0x4, 0x2, 2 },
+	{  512, 0x5, 0x0, 2 },
+	{  544, 0x5, 0x1, 2 },
+	{  500, 0x5, 0x2, 2 },
+	{  768, 0x6, 0x0, 2 },
+	{  816, 0x6, 0x1, 2 },
+	{  750, 0x6, 0x2, 2 },
+	{ 1024, 0x7, 0x0, 2 },
+	{ 1088, 0x7, 0x1, 2 },
+	{ 1000, 0x7, 0x2, 2 },
+	{ 1408, 0x8, 0x0, 2 },
+	{ 1496, 0x8, 0x1, 2 },
+	{ 1536, 0x9, 0x0, 2 },
+	{ 1632, 0x9, 0x1, 2 },
+	{ 1500, 0x9, 0x2, 2 },
+};
+
+/* CLK_SYS/BCLK ratios - multiplied by 10 due to .5s */
+static struct {
+	int ratio;
+	int div;
+} bclk_divs[] = {
+	{  10,  0 },
+	{  20,  2 },
+	{  30,  3 },
+	{  40,  4 },
+	{  50,  5 },
+	{  60,  7 },
+	{  80,  8 },
+	{ 100,  9 },
+	{ 120, 11 },
+	{ 160, 12 },
+	{ 200, 13 },
+	{ 220, 14 },
+	{ 240, 15 },
+	{ 300, 17 },
+	{ 320, 18 },
+	{ 440, 19 },
+	{ 480, 20 },
+};
+
+/* Sample rates for DSP */
+static struct {
+	int rate;
+	int value;
+} sample_rates[] = {
+	{  8000,  0 },
+	{ 11025,  1 },
+	{ 12000,  2 },
+	{ 16000,  3 },
+	{ 22050,  4 },
+	{ 24000,  5 },
+	{ 32000,  6 },
+	{ 44100,  7 },
+	{ 48000,  8 },
+	{ 88200,  9 },
+	{ 96000, 10 },
+	{ 0,      0 },
+};
+
+static int wm8903_startup(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+	struct snd_pcm_runtime *master_runtime;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		wm8903->playback_active++;
+	else
+		wm8903->capture_active++;
+
+	/* The DAI has shared clocks so if we already have a playback or
+	 * capture going then constrain this substream to match it.
+	 */
+	if (wm8903->master_substream) {
+		master_runtime = wm8903->master_substream->runtime;
+
+		dev_dbg(codec->dev, "Constraining to %d bits\n",
+			master_runtime->sample_bits);
+
+		snd_pcm_hw_constraint_minmax(substream->runtime,
+					     SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+					     master_runtime->sample_bits,
+					     master_runtime->sample_bits);
+
+		wm8903->slave_substream = substream;
+	} else
+		wm8903->master_substream = substream;
+
+	return 0;
+}
+
+static void wm8903_shutdown(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		wm8903->playback_active--;
+	else
+		wm8903->capture_active--;
+
+	if (wm8903->master_substream == substream)
+		wm8903->master_substream = wm8903->slave_substream;
+
+	wm8903->slave_substream = NULL;
+}
+
+static int wm8903_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec =rtd->codec;
+	struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+	int fs = params_rate(params);
+	int bclk;
+	int bclk_div;
+	int i;
+	int dsp_config;
+	int clk_config;
+	int best_val;
+	int cur_val;
+	int clk_sys;
+
+	u16 aif1 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_1);
+	u16 aif2 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_2);
+	u16 aif3 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_3);
+	u16 clock0 = snd_soc_read(codec, WM8903_CLOCK_RATES_0);
+	u16 clock1 = snd_soc_read(codec, WM8903_CLOCK_RATES_1);
+	u16 dac_digital1 = snd_soc_read(codec, WM8903_DAC_DIGITAL_1);
+
+	if (substream == wm8903->slave_substream) {
+		dev_dbg(codec->dev, "Ignoring hw_params for slave substream\n");
+		return 0;
+	}
+
+	/* Enable sloping stopband filter for low sample rates */
+	if (fs <= 24000)
+		dac_digital1 |= WM8903_DAC_SB_FILT;
+	else
+		dac_digital1 &= ~WM8903_DAC_SB_FILT;
+
+	/* Configure sample rate logic for DSP - choose nearest rate */
+	dsp_config = 0;
+	best_val = abs(sample_rates[dsp_config].rate - fs);
+	for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
+		cur_val = abs(sample_rates[i].rate - fs);
+		if (cur_val <= best_val) {
+			dsp_config = i;
+			best_val = cur_val;
+		}
+	}
+
+	/* Constraints should stop us hitting this but let's make sure */
+	if (wm8903->capture_active)
+		switch (sample_rates[dsp_config].rate) {
+		case 88200:
+		case 96000:
+			dev_err(codec->dev, "%dHz unsupported by ADC\n",
+				fs);
+			return -EINVAL;
+
+		default:
+			break;
+		}
+
+	dev_dbg(codec->dev, "DSP fs = %dHz\n", sample_rates[dsp_config].rate);
+	clock1 &= ~WM8903_SAMPLE_RATE_MASK;
+	clock1 |= sample_rates[dsp_config].value;
+
+	aif1 &= ~WM8903_AIF_WL_MASK;
+	bclk = 2 * fs;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		bclk *= 16;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		bclk *= 20;
+		aif1 |= 0x4;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		bclk *= 24;
+		aif1 |= 0x8;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		bclk *= 32;
+		aif1 |= 0xc;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dev_dbg(codec->dev, "MCLK = %dHz, target sample rate = %dHz\n",
+		wm8903->sysclk, fs);
+
+	/* We may not have an MCLK which allows us to generate exactly
+	 * the clock we want, particularly with USB derived inputs, so
+	 * approximate.
+	 */
+	clk_config = 0;
+	best_val = abs((wm8903->sysclk /
+			(clk_sys_ratios[0].mclk_div *
+			 clk_sys_ratios[0].div)) - fs);
+	for (i = 1; i < ARRAY_SIZE(clk_sys_ratios); i++) {
+		cur_val = abs((wm8903->sysclk /
+			       (clk_sys_ratios[i].mclk_div *
+				clk_sys_ratios[i].div)) - fs);
+
+		if (cur_val <= best_val) {
+			clk_config = i;
+			best_val = cur_val;
+		}
+	}
+
+	if (clk_sys_ratios[clk_config].mclk_div == 2) {
+		clock0 |= WM8903_MCLKDIV2;
+		clk_sys = wm8903->sysclk / 2;
+	} else {
+		clock0 &= ~WM8903_MCLKDIV2;
+		clk_sys = wm8903->sysclk;
+	}
+
+	clock1 &= ~(WM8903_CLK_SYS_RATE_MASK |
+		    WM8903_CLK_SYS_MODE_MASK);
+	clock1 |= clk_sys_ratios[clk_config].rate << WM8903_CLK_SYS_RATE_SHIFT;
+	clock1 |= clk_sys_ratios[clk_config].mode << WM8903_CLK_SYS_MODE_SHIFT;
+
+	dev_dbg(codec->dev, "CLK_SYS_RATE=%x, CLK_SYS_MODE=%x div=%d\n",
+		clk_sys_ratios[clk_config].rate,
+		clk_sys_ratios[clk_config].mode,
+		clk_sys_ratios[clk_config].div);
+
+	dev_dbg(codec->dev, "Actual CLK_SYS = %dHz\n", clk_sys);
+
+	/* We may not get quite the right frequency if using
+	 * approximate clocks so look for the closest match that is
+	 * higher than the target (we need to ensure that there enough
+	 * BCLKs to clock out the samples).
+	 */
+	bclk_div = 0;
+	best_val = ((clk_sys * 10) / bclk_divs[0].ratio) - bclk;
+	i = 1;
+	while (i < ARRAY_SIZE(bclk_divs)) {
+		cur_val = ((clk_sys * 10) / bclk_divs[i].ratio) - bclk;
+		if (cur_val < 0) /* BCLK table is sorted */
+			break;
+		bclk_div = i;
+		best_val = cur_val;
+		i++;
+	}
+
+	aif2 &= ~WM8903_BCLK_DIV_MASK;
+	aif3 &= ~WM8903_LRCLK_RATE_MASK;
+
+	dev_dbg(codec->dev, "BCLK ratio %d for %dHz - actual BCLK = %dHz\n",
+		bclk_divs[bclk_div].ratio / 10, bclk,
+		(clk_sys * 10) / bclk_divs[bclk_div].ratio);
+
+	aif2 |= bclk_divs[bclk_div].div;
+	aif3 |= bclk / fs;
+
+	snd_soc_write(codec, WM8903_CLOCK_RATES_0, clock0);
+	snd_soc_write(codec, WM8903_CLOCK_RATES_1, clock1);
+	snd_soc_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
+	snd_soc_write(codec, WM8903_AUDIO_INTERFACE_2, aif2);
+	snd_soc_write(codec, WM8903_AUDIO_INTERFACE_3, aif3);
+	snd_soc_write(codec, WM8903_DAC_DIGITAL_1, dac_digital1);
+
+	return 0;
+}
+
+/**
+ * wm8903_mic_detect - Enable microphone detection via the WM8903 IRQ
+ *
+ * @codec:  WM8903 codec
+ * @jack:   jack to report detection events on
+ * @det:    value to report for presence detection
+ * @shrt:   value to report for short detection
+ *
+ * Enable microphone detection via IRQ on the WM8903.  If GPIOs are
+ * being used to bring out signals to the processor then only platform
+ * data configuration is needed for WM8903 and processor GPIOs should
+ * be configured using snd_soc_jack_add_gpios() instead.
+ *
+ * The current threasholds for detection should be configured using
+ * micdet_cfg in the platform data.  Using this function will force on
+ * the microphone bias for the device.
+ */
+int wm8903_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+		      int det, int shrt)
+{
+	struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+	int irq_mask = WM8903_MICDET_EINT | WM8903_MICSHRT_EINT;
+
+	dev_dbg(codec->dev, "Enabling microphone detection: %x %x\n",
+		det, shrt);
+
+	/* Store the configuration */
+	wm8903->mic_jack = jack;
+	wm8903->mic_det = det;
+	wm8903->mic_short = shrt;
+
+	/* Enable interrupts we've got a report configured for */
+	if (det)
+		irq_mask &= ~WM8903_MICDET_EINT;
+	if (shrt)
+		irq_mask &= ~WM8903_MICSHRT_EINT;
+
+	snd_soc_update_bits(codec, WM8903_INTERRUPT_STATUS_1_MASK,
+			    WM8903_MICDET_EINT | WM8903_MICSHRT_EINT,
+			    irq_mask);
+
+	if (det && shrt) {
+		/* Enable mic detection, this may not have been set through
+		 * platform data (eg, if the defaults are OK). */
+		snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0,
+				    WM8903_WSEQ_ENA, WM8903_WSEQ_ENA);
+		snd_soc_update_bits(codec, WM8903_MIC_BIAS_CONTROL_0,
+				    WM8903_MICDET_ENA, WM8903_MICDET_ENA);
+	} else {
+		snd_soc_update_bits(codec, WM8903_MIC_BIAS_CONTROL_0,
+				    WM8903_MICDET_ENA, 0);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wm8903_mic_detect);
+
+static irqreturn_t wm8903_irq(int irq, void *data)
+{
+	struct snd_soc_codec *codec = data;
+	struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+	int mic_report;
+	int int_pol;
+	int int_val = 0;
+	int mask = ~snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1_MASK);
+
+	int_val = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1) & mask;
+
+	if (int_val & WM8903_WSEQ_BUSY_EINT) {
+		dev_dbg(codec->dev, "Write sequencer done\n");
+		complete(&wm8903->wseq);
+	}
+
+	/*
+	 * The rest is microphone jack detection.  We need to manually
+	 * invert the polarity of the interrupt after each event - to
+	 * simplify the code keep track of the last state we reported
+	 * and just invert the relevant bits in both the report and
+	 * the polarity register.
+	 */
+	mic_report = wm8903->mic_last_report;
+	int_pol = snd_soc_read(codec, WM8903_INTERRUPT_POLARITY_1);
+
+	if (int_val & WM8903_MICSHRT_EINT) {
+		dev_dbg(codec->dev, "Microphone short (pol=%x)\n", int_pol);
+
+		mic_report ^= wm8903->mic_short;
+		int_pol ^= WM8903_MICSHRT_INV;
+	}
+
+	if (int_val & WM8903_MICDET_EINT) {
+		dev_dbg(codec->dev, "Microphone detect (pol=%x)\n", int_pol);
+
+		mic_report ^= wm8903->mic_det;
+		int_pol ^= WM8903_MICDET_INV;
+
+		msleep(wm8903->mic_delay);
+	}
+
+	snd_soc_update_bits(codec, WM8903_INTERRUPT_POLARITY_1,
+			    WM8903_MICSHRT_INV | WM8903_MICDET_INV, int_pol);
+
+	snd_soc_jack_report(wm8903->mic_jack, mic_report,
+			    wm8903->mic_short | wm8903->mic_det);
+
+	wm8903->mic_last_report = mic_report;
+
+	return IRQ_HANDLED;
+}
+
+#define WM8903_PLAYBACK_RATES (SNDRV_PCM_RATE_8000 |\
+			       SNDRV_PCM_RATE_11025 |	\
+			       SNDRV_PCM_RATE_16000 |	\
+			       SNDRV_PCM_RATE_22050 |	\
+			       SNDRV_PCM_RATE_32000 |	\
+			       SNDRV_PCM_RATE_44100 |	\
+			       SNDRV_PCM_RATE_48000 |	\
+			       SNDRV_PCM_RATE_88200 |	\
+			       SNDRV_PCM_RATE_96000)
+
+#define WM8903_CAPTURE_RATES (SNDRV_PCM_RATE_8000 |\
+			      SNDRV_PCM_RATE_11025 |	\
+			      SNDRV_PCM_RATE_16000 |	\
+			      SNDRV_PCM_RATE_22050 |	\
+			      SNDRV_PCM_RATE_32000 |	\
+			      SNDRV_PCM_RATE_44100 |	\
+			      SNDRV_PCM_RATE_48000)
+
+#define WM8903_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+			SNDRV_PCM_FMTBIT_S20_3LE |\
+			SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8903_dai_ops = {
+	.startup	= wm8903_startup,
+	.shutdown	= wm8903_shutdown,
+	.hw_params	= wm8903_hw_params,
+	.digital_mute	= wm8903_digital_mute,
+	.set_fmt	= wm8903_set_dai_fmt,
+	.set_sysclk	= wm8903_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8903_dai = {
+	.name = "wm8903-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = WM8903_PLAYBACK_RATES,
+		.formats = WM8903_FORMATS,
+	},
+	.capture = {
+		 .stream_name = "Capture",
+		 .channels_min = 2,
+		 .channels_max = 2,
+		 .rates = WM8903_CAPTURE_RATES,
+		 .formats = WM8903_FORMATS,
+	 },
+	.ops = &wm8903_dai_ops,
+	.symmetric_rates = 1,
+};
+
+static int wm8903_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm8903_resume(struct snd_soc_codec *codec)
+{
+	int i;
+	u16 *reg_cache = codec->reg_cache;
+	u16 *tmp_cache = kmemdup(reg_cache, sizeof(wm8903_reg_defaults),
+				 GFP_KERNEL);
+
+	/* Bring the codec back up to standby first to minimise pop/clicks */
+	wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Sync back everything else */
+	if (tmp_cache) {
+		for (i = 2; i < ARRAY_SIZE(wm8903_reg_defaults); i++)
+			if (tmp_cache[i] != reg_cache[i])
+				snd_soc_write(codec, i, tmp_cache[i]);
+		kfree(tmp_cache);
+	} else {
+		dev_err(codec->dev, "Failed to allocate temporary cache\n");
+	}
+
+	return 0;
+}
+
+static int wm8903_probe(struct snd_soc_codec *codec)
+{
+	struct wm8903_platform_data *pdata = dev_get_platdata(codec->dev);
+	struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+	int ret, i;
+	int trigger, irq_pol;
+	u16 val;
+
+	init_completion(&wm8903->wseq);
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	val = snd_soc_read(codec, WM8903_SW_RESET_AND_ID);
+	if (val != wm8903_reg_defaults[WM8903_SW_RESET_AND_ID]) {
+		dev_err(codec->dev,
+			"Device with ID register %x is not a WM8903\n", val);
+		return -ENODEV;
+	}
+
+	val = snd_soc_read(codec, WM8903_REVISION_NUMBER);
+	dev_info(codec->dev, "WM8903 revision %d\n",
+		 val & WM8903_CHIP_REV_MASK);
+
+	wm8903_reset(codec);
+
+	/* Set up GPIOs and microphone detection */
+	if (pdata) {
+		for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) {
+			if (!pdata->gpio_cfg[i])
+				continue;
+
+			snd_soc_write(codec, WM8903_GPIO_CONTROL_1 + i,
+				      pdata->gpio_cfg[i] & 0xffff);
+		}
+
+		snd_soc_write(codec, WM8903_MIC_BIAS_CONTROL_0,
+			      pdata->micdet_cfg);
+
+		/* Microphone detection needs the WSEQ clock */
+		if (pdata->micdet_cfg)
+			snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0,
+					    WM8903_WSEQ_ENA, WM8903_WSEQ_ENA);
+
+		wm8903->mic_delay = pdata->micdet_delay;
+	}
+	
+	if (wm8903->irq) {
+		if (pdata && pdata->irq_active_low) {
+			trigger = IRQF_TRIGGER_LOW;
+			irq_pol = WM8903_IRQ_POL;
+		} else {
+			trigger = IRQF_TRIGGER_HIGH;
+			irq_pol = 0;
+		}
+
+		snd_soc_update_bits(codec, WM8903_INTERRUPT_CONTROL,
+				    WM8903_IRQ_POL, irq_pol);
+		
+		ret = request_threaded_irq(wm8903->irq, NULL, wm8903_irq,
+					   trigger | IRQF_ONESHOT,
+					   "wm8903", codec);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to request IRQ: %d\n",
+				ret);
+			return ret;
+		}
+
+		/* Enable write sequencer interrupts */
+		snd_soc_update_bits(codec, WM8903_INTERRUPT_STATUS_1_MASK,
+				    WM8903_IM_WSEQ_BUSY_EINT, 0);
+	}
+
+	/* power on device */
+	wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Latch volume update bits */
+	val = snd_soc_read(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT);
+	val |= WM8903_ADCVU;
+	snd_soc_write(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT, val);
+	snd_soc_write(codec, WM8903_ADC_DIGITAL_VOLUME_RIGHT, val);
+
+	val = snd_soc_read(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT);
+	val |= WM8903_DACVU;
+	snd_soc_write(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT, val);
+	snd_soc_write(codec, WM8903_DAC_DIGITAL_VOLUME_RIGHT, val);
+
+	val = snd_soc_read(codec, WM8903_ANALOGUE_OUT1_LEFT);
+	val |= WM8903_HPOUTVU;
+	snd_soc_write(codec, WM8903_ANALOGUE_OUT1_LEFT, val);
+	snd_soc_write(codec, WM8903_ANALOGUE_OUT1_RIGHT, val);
+
+	val = snd_soc_read(codec, WM8903_ANALOGUE_OUT2_LEFT);
+	val |= WM8903_LINEOUTVU;
+	snd_soc_write(codec, WM8903_ANALOGUE_OUT2_LEFT, val);
+	snd_soc_write(codec, WM8903_ANALOGUE_OUT2_RIGHT, val);
+
+	val = snd_soc_read(codec, WM8903_ANALOGUE_OUT3_LEFT);
+	val |= WM8903_SPKVU;
+	snd_soc_write(codec, WM8903_ANALOGUE_OUT3_LEFT, val);
+	snd_soc_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val);
+
+	/* Enable DAC soft mute by default */
+	val = snd_soc_read(codec, WM8903_DAC_DIGITAL_1);
+	val |= WM8903_DAC_MUTEMODE;
+	snd_soc_write(codec, WM8903_DAC_DIGITAL_1, val);
+
+	snd_soc_add_controls(codec, wm8903_snd_controls,
+				ARRAY_SIZE(wm8903_snd_controls));
+	wm8903_add_widgets(codec);
+
+	return ret;
+}
+
+/* power down chip */
+static int wm8903_remove(struct snd_soc_codec *codec)
+{
+	wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8903 = {
+	.probe =	wm8903_probe,
+	.remove =	wm8903_remove,
+	.suspend =	wm8903_suspend,
+	.resume =	wm8903_resume,
+	.set_bias_level = wm8903_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8903_reg_defaults),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8903_reg_defaults,
+	.volatile_register = wm8903_volatile_register,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8903_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8903_priv *wm8903;
+	int ret;
+
+	wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL);
+	if (wm8903 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8903);
+	wm8903->irq = i2c->irq;
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8903, &wm8903_dai, 1);
+	if (ret < 0)
+		kfree(wm8903);
+	return ret;
+}
+
+static __devexit int wm8903_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8903_i2c_id[] = {
+	{ "wm8903", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id);
+
+static struct i2c_driver wm8903_i2c_driver = {
+	.driver = {
+		.name = "wm8903-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8903_i2c_probe,
+	.remove =   __devexit_p(wm8903_i2c_remove),
+	.id_table = wm8903_i2c_id,
+};
+#endif
+
+static int __init wm8903_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8903_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8903 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8903_modinit);
+
+static void __exit wm8903_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8903_i2c_driver);
+#endif
+}
+module_exit(wm8903_exit);
+
+MODULE_DESCRIPTION("ASoC WM8903 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.cm>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8903.h b/linux/sound/soc/codecs/wm8903.h
new file mode 100644
index 0000000..996435e
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8903.h
@@ -0,0 +1,1242 @@
+/*
+ * wm8903.h - WM8903 audio codec interface
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  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.
+ */
+
+#ifndef _WM8903_H
+#define _WM8903_H
+
+#include <linux/i2c.h>
+
+extern int wm8903_mic_detect(struct snd_soc_codec *codec,
+			     struct snd_soc_jack *jack,
+			     int det, int shrt);
+
+#define WM8903_MCLK_DIV_2 1
+#define WM8903_CLK_SYS    2
+#define WM8903_BCLK       3
+#define WM8903_LRCLK      4
+
+/*
+ * Register values.
+ */
+#define WM8903_SW_RESET_AND_ID                  0x00
+#define WM8903_REVISION_NUMBER                  0x01
+#define WM8903_BIAS_CONTROL_0                   0x04
+#define WM8903_VMID_CONTROL_0                   0x05
+#define WM8903_MIC_BIAS_CONTROL_0               0x06
+#define WM8903_ANALOGUE_DAC_0                   0x08
+#define WM8903_ANALOGUE_ADC_0                   0x0A
+#define WM8903_POWER_MANAGEMENT_0               0x0C
+#define WM8903_POWER_MANAGEMENT_1               0x0D
+#define WM8903_POWER_MANAGEMENT_2               0x0E
+#define WM8903_POWER_MANAGEMENT_3               0x0F
+#define WM8903_POWER_MANAGEMENT_4               0x10
+#define WM8903_POWER_MANAGEMENT_5               0x11
+#define WM8903_POWER_MANAGEMENT_6               0x12
+#define WM8903_CLOCK_RATES_0                    0x14
+#define WM8903_CLOCK_RATES_1                    0x15
+#define WM8903_CLOCK_RATES_2                    0x16
+#define WM8903_AUDIO_INTERFACE_0                0x18
+#define WM8903_AUDIO_INTERFACE_1                0x19
+#define WM8903_AUDIO_INTERFACE_2                0x1A
+#define WM8903_AUDIO_INTERFACE_3                0x1B
+#define WM8903_DAC_DIGITAL_VOLUME_LEFT          0x1E
+#define WM8903_DAC_DIGITAL_VOLUME_RIGHT         0x1F
+#define WM8903_DAC_DIGITAL_0                    0x20
+#define WM8903_DAC_DIGITAL_1                    0x21
+#define WM8903_ADC_DIGITAL_VOLUME_LEFT          0x24
+#define WM8903_ADC_DIGITAL_VOLUME_RIGHT         0x25
+#define WM8903_ADC_DIGITAL_0                    0x26
+#define WM8903_DIGITAL_MICROPHONE_0             0x27
+#define WM8903_DRC_0                            0x28
+#define WM8903_DRC_1                            0x29
+#define WM8903_DRC_2                            0x2A
+#define WM8903_DRC_3                            0x2B
+#define WM8903_ANALOGUE_LEFT_INPUT_0            0x2C
+#define WM8903_ANALOGUE_RIGHT_INPUT_0           0x2D
+#define WM8903_ANALOGUE_LEFT_INPUT_1            0x2E
+#define WM8903_ANALOGUE_RIGHT_INPUT_1           0x2F
+#define WM8903_ANALOGUE_LEFT_MIX_0              0x32
+#define WM8903_ANALOGUE_RIGHT_MIX_0             0x33
+#define WM8903_ANALOGUE_SPK_MIX_LEFT_0          0x34
+#define WM8903_ANALOGUE_SPK_MIX_LEFT_1          0x35
+#define WM8903_ANALOGUE_SPK_MIX_RIGHT_0         0x36
+#define WM8903_ANALOGUE_SPK_MIX_RIGHT_1         0x37
+#define WM8903_ANALOGUE_OUT1_LEFT               0x39
+#define WM8903_ANALOGUE_OUT1_RIGHT              0x3A
+#define WM8903_ANALOGUE_OUT2_LEFT               0x3B
+#define WM8903_ANALOGUE_OUT2_RIGHT              0x3C
+#define WM8903_ANALOGUE_OUT3_LEFT               0x3E
+#define WM8903_ANALOGUE_OUT3_RIGHT              0x3F
+#define WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0    0x41
+#define WM8903_DC_SERVO_0                       0x43
+#define WM8903_DC_SERVO_2                       0x45
+#define WM8903_ANALOGUE_HP_0                    0x5A
+#define WM8903_ANALOGUE_LINEOUT_0               0x5E
+#define WM8903_CHARGE_PUMP_0                    0x62
+#define WM8903_CLASS_W_0                        0x68
+#define WM8903_WRITE_SEQUENCER_0                0x6C
+#define WM8903_WRITE_SEQUENCER_1                0x6D
+#define WM8903_WRITE_SEQUENCER_2                0x6E
+#define WM8903_WRITE_SEQUENCER_3                0x6F
+#define WM8903_WRITE_SEQUENCER_4                0x70
+#define WM8903_CONTROL_INTERFACE                0x72
+#define WM8903_GPIO_CONTROL_1                   0x74
+#define WM8903_GPIO_CONTROL_2                   0x75
+#define WM8903_GPIO_CONTROL_3                   0x76
+#define WM8903_GPIO_CONTROL_4                   0x77
+#define WM8903_GPIO_CONTROL_5                   0x78
+#define WM8903_INTERRUPT_STATUS_1               0x79
+#define WM8903_INTERRUPT_STATUS_1_MASK          0x7A
+#define WM8903_INTERRUPT_POLARITY_1             0x7B
+#define WM8903_INTERRUPT_CONTROL                0x7E
+#define WM8903_CONTROL_INTERFACE_TEST_1         0x81
+#define WM8903_CHARGE_PUMP_TEST_1               0x95
+#define WM8903_CLOCK_RATE_TEST_4                0xA4
+#define WM8903_ANALOGUE_OUTPUT_BIAS_0           0xAC
+
+#define WM8903_REGISTER_COUNT                   75
+#define WM8903_MAX_REGISTER                     0xAC
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - SW Reset and ID
+ */
+#define WM8903_SW_RESET_DEV_ID1_MASK            0xFFFF  /* SW_RESET_DEV_ID1 - [15:0] */
+#define WM8903_SW_RESET_DEV_ID1_SHIFT                0  /* SW_RESET_DEV_ID1 - [15:0] */
+#define WM8903_SW_RESET_DEV_ID1_WIDTH               16  /* SW_RESET_DEV_ID1 - [15:0] */
+
+/*
+ * R1 (0x01) - Revision Number
+ */
+#define WM8903_CHIP_REV_MASK                    0x000F  /* CHIP_REV - [3:0] */
+#define WM8903_CHIP_REV_SHIFT                        0  /* CHIP_REV - [3:0] */
+#define WM8903_CHIP_REV_WIDTH                        4  /* CHIP_REV - [3:0] */
+
+/*
+ * R4 (0x04) - Bias Control 0
+ */
+#define WM8903_POBCTRL                          0x0010  /* POBCTRL */
+#define WM8903_POBCTRL_MASK                     0x0010  /* POBCTRL */
+#define WM8903_POBCTRL_SHIFT                         4  /* POBCTRL */
+#define WM8903_POBCTRL_WIDTH                         1  /* POBCTRL */
+#define WM8903_ISEL_MASK                        0x000C  /* ISEL - [3:2] */
+#define WM8903_ISEL_SHIFT                            2  /* ISEL - [3:2] */
+#define WM8903_ISEL_WIDTH                            2  /* ISEL - [3:2] */
+#define WM8903_STARTUP_BIAS_ENA                 0x0002  /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_MASK            0x0002  /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_SHIFT                1  /* STARTUP_BIAS_ENA */
+#define WM8903_STARTUP_BIAS_ENA_WIDTH                1  /* STARTUP_BIAS_ENA */
+#define WM8903_BIAS_ENA                         0x0001  /* BIAS_ENA */
+#define WM8903_BIAS_ENA_MASK                    0x0001  /* BIAS_ENA */
+#define WM8903_BIAS_ENA_SHIFT                        0  /* BIAS_ENA */
+#define WM8903_BIAS_ENA_WIDTH                        1  /* BIAS_ENA */
+
+/*
+ * R5 (0x05) - VMID Control 0
+ */
+#define WM8903_VMID_TIE_ENA                     0x0080  /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_MASK                0x0080  /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_SHIFT                    7  /* VMID_TIE_ENA */
+#define WM8903_VMID_TIE_ENA_WIDTH                    1  /* VMID_TIE_ENA */
+#define WM8903_BUFIO_ENA                        0x0040  /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_MASK                   0x0040  /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_SHIFT                       6  /* BUFIO_ENA */
+#define WM8903_BUFIO_ENA_WIDTH                       1  /* BUFIO_ENA */
+#define WM8903_VMID_IO_ENA                      0x0020  /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_MASK                 0x0020  /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_SHIFT                     5  /* VMID_IO_ENA */
+#define WM8903_VMID_IO_ENA_WIDTH                     1  /* VMID_IO_ENA */
+#define WM8903_VMID_SOFT_MASK                   0x0018  /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_SOFT_SHIFT                       3  /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_SOFT_WIDTH                       2  /* VMID_SOFT - [4:3] */
+#define WM8903_VMID_RES_MASK                    0x0006  /* VMID_RES - [2:1] */
+#define WM8903_VMID_RES_SHIFT                        1  /* VMID_RES - [2:1] */
+#define WM8903_VMID_RES_WIDTH                        2  /* VMID_RES - [2:1] */
+#define WM8903_VMID_BUF_ENA                     0x0001  /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_MASK                0x0001  /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_SHIFT                    0  /* VMID_BUF_ENA */
+#define WM8903_VMID_BUF_ENA_WIDTH                    1  /* VMID_BUF_ENA */
+
+#define WM8903_VMID_RES_50K                          2
+#define WM8903_VMID_RES_250K                         3
+#define WM8903_VMID_RES_5K                           4
+
+/*
+ * R8 (0x08) - Analogue DAC 0
+ */
+#define WM8903_DACBIAS_SEL_MASK                 0x0018  /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACBIAS_SEL_SHIFT                     3  /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACBIAS_SEL_WIDTH                     2  /* DACBIAS_SEL - [4:3] */
+#define WM8903_DACVMID_BIAS_SEL_MASK            0x0006  /* DACVMID_BIAS_SEL - [2:1] */
+#define WM8903_DACVMID_BIAS_SEL_SHIFT                1  /* DACVMID_BIAS_SEL - [2:1] */
+#define WM8903_DACVMID_BIAS_SEL_WIDTH                2  /* DACVMID_BIAS_SEL - [2:1] */
+
+/*
+ * R10 (0x0A) - Analogue ADC 0
+ */
+#define WM8903_ADC_OSR128                       0x0001  /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_MASK                  0x0001  /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_SHIFT                      0  /* ADC_OSR128 */
+#define WM8903_ADC_OSR128_WIDTH                      1  /* ADC_OSR128 */
+
+/*
+ * R12 (0x0C) - Power Management 0
+ */
+#define WM8903_INL_ENA                          0x0002  /* INL_ENA */
+#define WM8903_INL_ENA_MASK                     0x0002  /* INL_ENA */
+#define WM8903_INL_ENA_SHIFT                         1  /* INL_ENA */
+#define WM8903_INL_ENA_WIDTH                         1  /* INL_ENA */
+#define WM8903_INR_ENA                          0x0001  /* INR_ENA */
+#define WM8903_INR_ENA_MASK                     0x0001  /* INR_ENA */
+#define WM8903_INR_ENA_SHIFT                         0  /* INR_ENA */
+#define WM8903_INR_ENA_WIDTH                         1  /* INR_ENA */
+
+/*
+ * R13 (0x0D) - Power Management 1
+ */
+#define WM8903_MIXOUTL_ENA                      0x0002  /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_MASK                 0x0002  /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_SHIFT                     1  /* MIXOUTL_ENA */
+#define WM8903_MIXOUTL_ENA_WIDTH                     1  /* MIXOUTL_ENA */
+#define WM8903_MIXOUTR_ENA                      0x0001  /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_MASK                 0x0001  /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_SHIFT                     0  /* MIXOUTR_ENA */
+#define WM8903_MIXOUTR_ENA_WIDTH                     1  /* MIXOUTR_ENA */
+
+/*
+ * R14 (0x0E) - Power Management 2
+ */
+#define WM8903_HPL_PGA_ENA                      0x0002  /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_MASK                 0x0002  /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_SHIFT                     1  /* HPL_PGA_ENA */
+#define WM8903_HPL_PGA_ENA_WIDTH                     1  /* HPL_PGA_ENA */
+#define WM8903_HPR_PGA_ENA                      0x0001  /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_MASK                 0x0001  /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_SHIFT                     0  /* HPR_PGA_ENA */
+#define WM8903_HPR_PGA_ENA_WIDTH                     1  /* HPR_PGA_ENA */
+
+/*
+ * R15 (0x0F) - Power Management 3
+ */
+#define WM8903_LINEOUTL_PGA_ENA                 0x0002  /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_MASK            0x0002  /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_SHIFT                1  /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTL_PGA_ENA_WIDTH                1  /* LINEOUTL_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA                 0x0001  /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_MASK            0x0001  /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_SHIFT                0  /* LINEOUTR_PGA_ENA */
+#define WM8903_LINEOUTR_PGA_ENA_WIDTH                1  /* LINEOUTR_PGA_ENA */
+
+/*
+ * R16 (0x10) - Power Management 4
+ */
+#define WM8903_MIXSPKL_ENA                      0x0002  /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_MASK                 0x0002  /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_SHIFT                     1  /* MIXSPKL_ENA */
+#define WM8903_MIXSPKL_ENA_WIDTH                     1  /* MIXSPKL_ENA */
+#define WM8903_MIXSPKR_ENA                      0x0001  /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_MASK                 0x0001  /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_SHIFT                     0  /* MIXSPKR_ENA */
+#define WM8903_MIXSPKR_ENA_WIDTH                     1  /* MIXSPKR_ENA */
+
+/*
+ * R17 (0x11) - Power Management 5
+ */
+#define WM8903_SPKL_ENA                         0x0002  /* SPKL_ENA */
+#define WM8903_SPKL_ENA_MASK                    0x0002  /* SPKL_ENA */
+#define WM8903_SPKL_ENA_SHIFT                        1  /* SPKL_ENA */
+#define WM8903_SPKL_ENA_WIDTH                        1  /* SPKL_ENA */
+#define WM8903_SPKR_ENA                         0x0001  /* SPKR_ENA */
+#define WM8903_SPKR_ENA_MASK                    0x0001  /* SPKR_ENA */
+#define WM8903_SPKR_ENA_SHIFT                        0  /* SPKR_ENA */
+#define WM8903_SPKR_ENA_WIDTH                        1  /* SPKR_ENA */
+
+/*
+ * R18 (0x12) - Power Management 6
+ */
+#define WM8903_DACL_ENA                         0x0008  /* DACL_ENA */
+#define WM8903_DACL_ENA_MASK                    0x0008  /* DACL_ENA */
+#define WM8903_DACL_ENA_SHIFT                        3  /* DACL_ENA */
+#define WM8903_DACL_ENA_WIDTH                        1  /* DACL_ENA */
+#define WM8903_DACR_ENA                         0x0004  /* DACR_ENA */
+#define WM8903_DACR_ENA_MASK                    0x0004  /* DACR_ENA */
+#define WM8903_DACR_ENA_SHIFT                        2  /* DACR_ENA */
+#define WM8903_DACR_ENA_WIDTH                        1  /* DACR_ENA */
+#define WM8903_ADCL_ENA                         0x0002  /* ADCL_ENA */
+#define WM8903_ADCL_ENA_MASK                    0x0002  /* ADCL_ENA */
+#define WM8903_ADCL_ENA_SHIFT                        1  /* ADCL_ENA */
+#define WM8903_ADCL_ENA_WIDTH                        1  /* ADCL_ENA */
+#define WM8903_ADCR_ENA                         0x0001  /* ADCR_ENA */
+#define WM8903_ADCR_ENA_MASK                    0x0001  /* ADCR_ENA */
+#define WM8903_ADCR_ENA_SHIFT                        0  /* ADCR_ENA */
+#define WM8903_ADCR_ENA_WIDTH                        1  /* ADCR_ENA */
+
+/*
+ * R20 (0x14) - Clock Rates 0
+ */
+#define WM8903_MCLKDIV2                         0x0001  /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_MASK                    0x0001  /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_SHIFT                        0  /* MCLKDIV2 */
+#define WM8903_MCLKDIV2_WIDTH                        1  /* MCLKDIV2 */
+
+/*
+ * R21 (0x15) - Clock Rates 1
+ */
+#define WM8903_CLK_SYS_RATE_MASK                0x3C00  /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_RATE_SHIFT                   10  /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_RATE_WIDTH                    4  /* CLK_SYS_RATE - [13:10] */
+#define WM8903_CLK_SYS_MODE_MASK                0x0300  /* CLK_SYS_MODE - [9:8] */
+#define WM8903_CLK_SYS_MODE_SHIFT                    8  /* CLK_SYS_MODE - [9:8] */
+#define WM8903_CLK_SYS_MODE_WIDTH                    2  /* CLK_SYS_MODE - [9:8] */
+#define WM8903_SAMPLE_RATE_MASK                 0x000F  /* SAMPLE_RATE - [3:0] */
+#define WM8903_SAMPLE_RATE_SHIFT                     0  /* SAMPLE_RATE - [3:0] */
+#define WM8903_SAMPLE_RATE_WIDTH                     4  /* SAMPLE_RATE - [3:0] */
+
+/*
+ * R22 (0x16) - Clock Rates 2
+ */
+#define WM8903_CLK_SYS_ENA                      0x0004  /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_MASK                 0x0004  /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_SHIFT                     2  /* CLK_SYS_ENA */
+#define WM8903_CLK_SYS_ENA_WIDTH                     1  /* CLK_SYS_ENA */
+#define WM8903_CLK_DSP_ENA                      0x0002  /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_MASK                 0x0002  /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_SHIFT                     1  /* CLK_DSP_ENA */
+#define WM8903_CLK_DSP_ENA_WIDTH                     1  /* CLK_DSP_ENA */
+#define WM8903_TO_ENA                           0x0001  /* TO_ENA */
+#define WM8903_TO_ENA_MASK                      0x0001  /* TO_ENA */
+#define WM8903_TO_ENA_SHIFT                          0  /* TO_ENA */
+#define WM8903_TO_ENA_WIDTH                          1  /* TO_ENA */
+
+/*
+ * R24 (0x18) - Audio Interface 0
+ */
+#define WM8903_DACL_DATINV                      0x1000  /* DACL_DATINV */
+#define WM8903_DACL_DATINV_MASK                 0x1000  /* DACL_DATINV */
+#define WM8903_DACL_DATINV_SHIFT                    12  /* DACL_DATINV */
+#define WM8903_DACL_DATINV_WIDTH                     1  /* DACL_DATINV */
+#define WM8903_DACR_DATINV                      0x0800  /* DACR_DATINV */
+#define WM8903_DACR_DATINV_MASK                 0x0800  /* DACR_DATINV */
+#define WM8903_DACR_DATINV_SHIFT                    11  /* DACR_DATINV */
+#define WM8903_DACR_DATINV_WIDTH                     1  /* DACR_DATINV */
+#define WM8903_DAC_BOOST_MASK                   0x0600  /* DAC_BOOST - [10:9] */
+#define WM8903_DAC_BOOST_SHIFT                       9  /* DAC_BOOST - [10:9] */
+#define WM8903_DAC_BOOST_WIDTH                       2  /* DAC_BOOST - [10:9] */
+#define WM8903_LOOPBACK                         0x0100  /* LOOPBACK */
+#define WM8903_LOOPBACK_MASK                    0x0100  /* LOOPBACK */
+#define WM8903_LOOPBACK_SHIFT                        8  /* LOOPBACK */
+#define WM8903_LOOPBACK_WIDTH                        1  /* LOOPBACK */
+#define WM8903_AIFADCL_SRC                      0x0080  /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_MASK                 0x0080  /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_SHIFT                     7  /* AIFADCL_SRC */
+#define WM8903_AIFADCL_SRC_WIDTH                     1  /* AIFADCL_SRC */
+#define WM8903_AIFADCR_SRC                      0x0040  /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_MASK                 0x0040  /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_SHIFT                     6  /* AIFADCR_SRC */
+#define WM8903_AIFADCR_SRC_WIDTH                     1  /* AIFADCR_SRC */
+#define WM8903_AIFDACL_SRC                      0x0020  /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_MASK                 0x0020  /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_SHIFT                     5  /* AIFDACL_SRC */
+#define WM8903_AIFDACL_SRC_WIDTH                     1  /* AIFDACL_SRC */
+#define WM8903_AIFDACR_SRC                      0x0010  /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_MASK                 0x0010  /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_SHIFT                     4  /* AIFDACR_SRC */
+#define WM8903_AIFDACR_SRC_WIDTH                     1  /* AIFDACR_SRC */
+#define WM8903_ADC_COMP                         0x0008  /* ADC_COMP */
+#define WM8903_ADC_COMP_MASK                    0x0008  /* ADC_COMP */
+#define WM8903_ADC_COMP_SHIFT                        3  /* ADC_COMP */
+#define WM8903_ADC_COMP_WIDTH                        1  /* ADC_COMP */
+#define WM8903_ADC_COMPMODE                     0x0004  /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_MASK                0x0004  /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_SHIFT                    2  /* ADC_COMPMODE */
+#define WM8903_ADC_COMPMODE_WIDTH                    1  /* ADC_COMPMODE */
+#define WM8903_DAC_COMP                         0x0002  /* DAC_COMP */
+#define WM8903_DAC_COMP_MASK                    0x0002  /* DAC_COMP */
+#define WM8903_DAC_COMP_SHIFT                        1  /* DAC_COMP */
+#define WM8903_DAC_COMP_WIDTH                        1  /* DAC_COMP */
+#define WM8903_DAC_COMPMODE                     0x0001  /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_MASK                0x0001  /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_SHIFT                    0  /* DAC_COMPMODE */
+#define WM8903_DAC_COMPMODE_WIDTH                    1  /* DAC_COMPMODE */
+
+/*
+ * R25 (0x19) - Audio Interface 1
+ */
+#define WM8903_AIFDAC_TDM                       0x2000  /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_MASK                  0x2000  /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_SHIFT                     13  /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_WIDTH                      1  /* AIFDAC_TDM */
+#define WM8903_AIFDAC_TDM_CHAN                  0x1000  /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_MASK             0x1000  /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_SHIFT                12  /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFDAC_TDM_CHAN_WIDTH                 1  /* AIFDAC_TDM_CHAN */
+#define WM8903_AIFADC_TDM                       0x0800  /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_MASK                  0x0800  /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_SHIFT                     11  /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_WIDTH                      1  /* AIFADC_TDM */
+#define WM8903_AIFADC_TDM_CHAN                  0x0400  /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_MASK             0x0400  /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_SHIFT                10  /* AIFADC_TDM_CHAN */
+#define WM8903_AIFADC_TDM_CHAN_WIDTH                 1  /* AIFADC_TDM_CHAN */
+#define WM8903_LRCLK_DIR                        0x0200  /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_MASK                   0x0200  /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_SHIFT                       9  /* LRCLK_DIR */
+#define WM8903_LRCLK_DIR_WIDTH                       1  /* LRCLK_DIR */
+#define WM8903_AIF_BCLK_INV                     0x0080  /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_MASK                0x0080  /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_SHIFT                    7  /* AIF_BCLK_INV */
+#define WM8903_AIF_BCLK_INV_WIDTH                    1  /* AIF_BCLK_INV */
+#define WM8903_BCLK_DIR                         0x0040  /* BCLK_DIR */
+#define WM8903_BCLK_DIR_MASK                    0x0040  /* BCLK_DIR */
+#define WM8903_BCLK_DIR_SHIFT                        6  /* BCLK_DIR */
+#define WM8903_BCLK_DIR_WIDTH                        1  /* BCLK_DIR */
+#define WM8903_AIF_LRCLK_INV                    0x0010  /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_MASK               0x0010  /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_SHIFT                   4  /* AIF_LRCLK_INV */
+#define WM8903_AIF_LRCLK_INV_WIDTH                   1  /* AIF_LRCLK_INV */
+#define WM8903_AIF_WL_MASK                      0x000C  /* AIF_WL - [3:2] */
+#define WM8903_AIF_WL_SHIFT                          2  /* AIF_WL - [3:2] */
+#define WM8903_AIF_WL_WIDTH                          2  /* AIF_WL - [3:2] */
+#define WM8903_AIF_FMT_MASK                     0x0003  /* AIF_FMT - [1:0] */
+#define WM8903_AIF_FMT_SHIFT                         0  /* AIF_FMT - [1:0] */
+#define WM8903_AIF_FMT_WIDTH                         2  /* AIF_FMT - [1:0] */
+
+/*
+ * R26 (0x1A) - Audio Interface 2
+ */
+#define WM8903_BCLK_DIV_MASK                    0x001F  /* BCLK_DIV - [4:0] */
+#define WM8903_BCLK_DIV_SHIFT                        0  /* BCLK_DIV - [4:0] */
+#define WM8903_BCLK_DIV_WIDTH                        5  /* BCLK_DIV - [4:0] */
+
+/*
+ * R27 (0x1B) - Audio Interface 3
+ */
+#define WM8903_LRCLK_RATE_MASK                  0x07FF  /* LRCLK_RATE - [10:0] */
+#define WM8903_LRCLK_RATE_SHIFT                      0  /* LRCLK_RATE - [10:0] */
+#define WM8903_LRCLK_RATE_WIDTH                     11  /* LRCLK_RATE - [10:0] */
+
+/*
+ * R30 (0x1E) - DAC Digital Volume Left
+ */
+#define WM8903_DACVU                            0x0100  /* DACVU */
+#define WM8903_DACVU_MASK                       0x0100  /* DACVU */
+#define WM8903_DACVU_SHIFT                           8  /* DACVU */
+#define WM8903_DACVU_WIDTH                           1  /* DACVU */
+#define WM8903_DACL_VOL_MASK                    0x00FF  /* DACL_VOL - [7:0] */
+#define WM8903_DACL_VOL_SHIFT                        0  /* DACL_VOL - [7:0] */
+#define WM8903_DACL_VOL_WIDTH                        8  /* DACL_VOL - [7:0] */
+
+/*
+ * R31 (0x1F) - DAC Digital Volume Right
+ */
+#define WM8903_DACVU                            0x0100  /* DACVU */
+#define WM8903_DACVU_MASK                       0x0100  /* DACVU */
+#define WM8903_DACVU_SHIFT                           8  /* DACVU */
+#define WM8903_DACVU_WIDTH                           1  /* DACVU */
+#define WM8903_DACR_VOL_MASK                    0x00FF  /* DACR_VOL - [7:0] */
+#define WM8903_DACR_VOL_SHIFT                        0  /* DACR_VOL - [7:0] */
+#define WM8903_DACR_VOL_WIDTH                        8  /* DACR_VOL - [7:0] */
+
+/*
+ * R32 (0x20) - DAC Digital 0
+ */
+#define WM8903_ADCL_DAC_SVOL_MASK               0x0F00  /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCL_DAC_SVOL_SHIFT                   8  /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCL_DAC_SVOL_WIDTH                   4  /* ADCL_DAC_SVOL - [11:8] */
+#define WM8903_ADCR_DAC_SVOL_MASK               0x00F0  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADCR_DAC_SVOL_SHIFT                   4  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADCR_DAC_SVOL_WIDTH                   4  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8903_ADC_TO_DACL_MASK                 0x000C  /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACL_SHIFT                     2  /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACL_WIDTH                     2  /* ADC_TO_DACL - [3:2] */
+#define WM8903_ADC_TO_DACR_MASK                 0x0003  /* ADC_TO_DACR - [1:0] */
+#define WM8903_ADC_TO_DACR_SHIFT                     0  /* ADC_TO_DACR - [1:0] */
+#define WM8903_ADC_TO_DACR_WIDTH                     2  /* ADC_TO_DACR - [1:0] */
+
+/*
+ * R33 (0x21) - DAC Digital 1
+ */
+#define WM8903_DAC_MONO                         0x1000  /* DAC_MONO */
+#define WM8903_DAC_MONO_MASK                    0x1000  /* DAC_MONO */
+#define WM8903_DAC_MONO_SHIFT                       12  /* DAC_MONO */
+#define WM8903_DAC_MONO_WIDTH                        1  /* DAC_MONO */
+#define WM8903_DAC_SB_FILT                      0x0800  /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_MASK                 0x0800  /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_SHIFT                    11  /* DAC_SB_FILT */
+#define WM8903_DAC_SB_FILT_WIDTH                     1  /* DAC_SB_FILT */
+#define WM8903_DAC_MUTERATE                     0x0400  /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_MASK                0x0400  /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_SHIFT                   10  /* DAC_MUTERATE */
+#define WM8903_DAC_MUTERATE_WIDTH                    1  /* DAC_MUTERATE */
+#define WM8903_DAC_MUTEMODE                     0x0200  /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_MASK                0x0200  /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_SHIFT                    9  /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTEMODE_WIDTH                    1  /* DAC_MUTEMODE */
+#define WM8903_DAC_MUTE                         0x0008  /* DAC_MUTE */
+#define WM8903_DAC_MUTE_MASK                    0x0008  /* DAC_MUTE */
+#define WM8903_DAC_MUTE_SHIFT                        3  /* DAC_MUTE */
+#define WM8903_DAC_MUTE_WIDTH                        1  /* DAC_MUTE */
+#define WM8903_DEEMPH_MASK                      0x0006  /* DEEMPH - [2:1] */
+#define WM8903_DEEMPH_SHIFT                          1  /* DEEMPH - [2:1] */
+#define WM8903_DEEMPH_WIDTH                          2  /* DEEMPH - [2:1] */
+
+/*
+ * R36 (0x24) - ADC Digital Volume Left
+ */
+#define WM8903_ADCVU                            0x0100  /* ADCVU */
+#define WM8903_ADCVU_MASK                       0x0100  /* ADCVU */
+#define WM8903_ADCVU_SHIFT                           8  /* ADCVU */
+#define WM8903_ADCVU_WIDTH                           1  /* ADCVU */
+#define WM8903_ADCL_VOL_MASK                    0x00FF  /* ADCL_VOL - [7:0] */
+#define WM8903_ADCL_VOL_SHIFT                        0  /* ADCL_VOL - [7:0] */
+#define WM8903_ADCL_VOL_WIDTH                        8  /* ADCL_VOL - [7:0] */
+
+/*
+ * R37 (0x25) - ADC Digital Volume Right
+ */
+#define WM8903_ADCVU                            0x0100  /* ADCVU */
+#define WM8903_ADCVU_MASK                       0x0100  /* ADCVU */
+#define WM8903_ADCVU_SHIFT                           8  /* ADCVU */
+#define WM8903_ADCVU_WIDTH                           1  /* ADCVU */
+#define WM8903_ADCR_VOL_MASK                    0x00FF  /* ADCR_VOL - [7:0] */
+#define WM8903_ADCR_VOL_SHIFT                        0  /* ADCR_VOL - [7:0] */
+#define WM8903_ADCR_VOL_WIDTH                        8  /* ADCR_VOL - [7:0] */
+
+/*
+ * R38 (0x26) - ADC Digital 0
+ */
+#define WM8903_ADC_HPF_CUT_MASK                 0x0060  /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_CUT_SHIFT                     5  /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_CUT_WIDTH                     2  /* ADC_HPF_CUT - [6:5] */
+#define WM8903_ADC_HPF_ENA                      0x0010  /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_MASK                 0x0010  /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_SHIFT                     4  /* ADC_HPF_ENA */
+#define WM8903_ADC_HPF_ENA_WIDTH                     1  /* ADC_HPF_ENA */
+#define WM8903_ADCL_DATINV                      0x0002  /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_MASK                 0x0002  /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_SHIFT                     1  /* ADCL_DATINV */
+#define WM8903_ADCL_DATINV_WIDTH                     1  /* ADCL_DATINV */
+#define WM8903_ADCR_DATINV                      0x0001  /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_MASK                 0x0001  /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_SHIFT                     0  /* ADCR_DATINV */
+#define WM8903_ADCR_DATINV_WIDTH                     1  /* ADCR_DATINV */
+
+/*
+ * R39 (0x27) - Digital Microphone 0
+ */
+#define WM8903_DIGMIC_MODE_SEL                  0x0100  /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_MASK             0x0100  /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_SHIFT                 8  /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_MODE_SEL_WIDTH                 1  /* DIGMIC_MODE_SEL */
+#define WM8903_DIGMIC_CLK_SEL_L_MASK            0x00C0  /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_L_SHIFT                6  /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_L_WIDTH                2  /* DIGMIC_CLK_SEL_L - [7:6] */
+#define WM8903_DIGMIC_CLK_SEL_R_MASK            0x0030  /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_R_SHIFT                4  /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_R_WIDTH                2  /* DIGMIC_CLK_SEL_R - [5:4] */
+#define WM8903_DIGMIC_CLK_SEL_RT_MASK           0x000C  /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_RT_SHIFT               2  /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_RT_WIDTH               2  /* DIGMIC_CLK_SEL_RT - [3:2] */
+#define WM8903_DIGMIC_CLK_SEL_MASK              0x0003  /* DIGMIC_CLK_SEL - [1:0] */
+#define WM8903_DIGMIC_CLK_SEL_SHIFT                  0  /* DIGMIC_CLK_SEL - [1:0] */
+#define WM8903_DIGMIC_CLK_SEL_WIDTH                  2  /* DIGMIC_CLK_SEL - [1:0] */
+
+/*
+ * R40 (0x28) - DRC 0
+ */
+#define WM8903_DRC_ENA                          0x8000  /* DRC_ENA */
+#define WM8903_DRC_ENA_MASK                     0x8000  /* DRC_ENA */
+#define WM8903_DRC_ENA_SHIFT                        15  /* DRC_ENA */
+#define WM8903_DRC_ENA_WIDTH                         1  /* DRC_ENA */
+#define WM8903_DRC_THRESH_HYST_MASK             0x1800  /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_THRESH_HYST_SHIFT                11  /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_THRESH_HYST_WIDTH                 2  /* DRC_THRESH_HYST - [12:11] */
+#define WM8903_DRC_STARTUP_GAIN_MASK            0x07C0  /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_STARTUP_GAIN_SHIFT                6  /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_STARTUP_GAIN_WIDTH                5  /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8903_DRC_FF_DELAY                     0x0020  /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_MASK                0x0020  /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_SHIFT                    5  /* DRC_FF_DELAY */
+#define WM8903_DRC_FF_DELAY_WIDTH                    1  /* DRC_FF_DELAY */
+#define WM8903_DRC_SMOOTH_ENA                   0x0008  /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_MASK              0x0008  /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_SHIFT                  3  /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_SMOOTH_ENA_WIDTH                  1  /* DRC_SMOOTH_ENA */
+#define WM8903_DRC_QR_ENA                       0x0004  /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_MASK                  0x0004  /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_SHIFT                      2  /* DRC_QR_ENA */
+#define WM8903_DRC_QR_ENA_WIDTH                      1  /* DRC_QR_ENA */
+#define WM8903_DRC_ANTICLIP_ENA                 0x0002  /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_MASK            0x0002  /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_SHIFT                1  /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_ANTICLIP_ENA_WIDTH                1  /* DRC_ANTICLIP_ENA */
+#define WM8903_DRC_HYST_ENA                     0x0001  /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_MASK                0x0001  /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_SHIFT                    0  /* DRC_HYST_ENA */
+#define WM8903_DRC_HYST_ENA_WIDTH                    1  /* DRC_HYST_ENA */
+
+/*
+ * R41 (0x29) - DRC 1
+ */
+#define WM8903_DRC_ATTACK_RATE_MASK             0xF000  /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_ATTACK_RATE_SHIFT                12  /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_ATTACK_RATE_WIDTH                 4  /* DRC_ATTACK_RATE - [15:12] */
+#define WM8903_DRC_DECAY_RATE_MASK              0x0F00  /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_DECAY_RATE_SHIFT                  8  /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_DECAY_RATE_WIDTH                  4  /* DRC_DECAY_RATE - [11:8] */
+#define WM8903_DRC_THRESH_QR_MASK               0x00C0  /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_THRESH_QR_SHIFT                   6  /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_THRESH_QR_WIDTH                   2  /* DRC_THRESH_QR - [7:6] */
+#define WM8903_DRC_RATE_QR_MASK                 0x0030  /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_RATE_QR_SHIFT                     4  /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_RATE_QR_WIDTH                     2  /* DRC_RATE_QR - [5:4] */
+#define WM8903_DRC_MINGAIN_MASK                 0x000C  /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MINGAIN_SHIFT                     2  /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MINGAIN_WIDTH                     2  /* DRC_MINGAIN - [3:2] */
+#define WM8903_DRC_MAXGAIN_MASK                 0x0003  /* DRC_MAXGAIN - [1:0] */
+#define WM8903_DRC_MAXGAIN_SHIFT                     0  /* DRC_MAXGAIN - [1:0] */
+#define WM8903_DRC_MAXGAIN_WIDTH                     2  /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R42 (0x2A) - DRC 2
+ */
+#define WM8903_DRC_R0_SLOPE_COMP_MASK           0x0038  /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R0_SLOPE_COMP_SHIFT               3  /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R0_SLOPE_COMP_WIDTH               3  /* DRC_R0_SLOPE_COMP - [5:3] */
+#define WM8903_DRC_R1_SLOPE_COMP_MASK           0x0007  /* DRC_R1_SLOPE_COMP - [2:0] */
+#define WM8903_DRC_R1_SLOPE_COMP_SHIFT               0  /* DRC_R1_SLOPE_COMP - [2:0] */
+#define WM8903_DRC_R1_SLOPE_COMP_WIDTH               3  /* DRC_R1_SLOPE_COMP - [2:0] */
+
+/*
+ * R43 (0x2B) - DRC 3
+ */
+#define WM8903_DRC_THRESH_COMP_MASK             0x07E0  /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_THRESH_COMP_SHIFT                 5  /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_THRESH_COMP_WIDTH                 6  /* DRC_THRESH_COMP - [10:5] */
+#define WM8903_DRC_AMP_COMP_MASK                0x001F  /* DRC_AMP_COMP - [4:0] */
+#define WM8903_DRC_AMP_COMP_SHIFT                    0  /* DRC_AMP_COMP - [4:0] */
+#define WM8903_DRC_AMP_COMP_WIDTH                    5  /* DRC_AMP_COMP - [4:0] */
+
+/*
+ * R44 (0x2C) - Analogue Left Input 0
+ */
+#define WM8903_LINMUTE                          0x0080  /* LINMUTE */
+#define WM8903_LINMUTE_MASK                     0x0080  /* LINMUTE */
+#define WM8903_LINMUTE_SHIFT                         7  /* LINMUTE */
+#define WM8903_LINMUTE_WIDTH                         1  /* LINMUTE */
+#define WM8903_LIN_VOL_MASK                     0x001F  /* LIN_VOL - [4:0] */
+#define WM8903_LIN_VOL_SHIFT                         0  /* LIN_VOL - [4:0] */
+#define WM8903_LIN_VOL_WIDTH                         5  /* LIN_VOL - [4:0] */
+
+/*
+ * R45 (0x2D) - Analogue Right Input 0
+ */
+#define WM8903_RINMUTE                          0x0080  /* RINMUTE */
+#define WM8903_RINMUTE_MASK                     0x0080  /* RINMUTE */
+#define WM8903_RINMUTE_SHIFT                         7  /* RINMUTE */
+#define WM8903_RINMUTE_WIDTH                         1  /* RINMUTE */
+#define WM8903_RIN_VOL_MASK                     0x001F  /* RIN_VOL - [4:0] */
+#define WM8903_RIN_VOL_SHIFT                         0  /* RIN_VOL - [4:0] */
+#define WM8903_RIN_VOL_WIDTH                         5  /* RIN_VOL - [4:0] */
+
+/*
+ * R46 (0x2E) - Analogue Left Input 1
+ */
+#define WM8903_INL_CM_ENA                       0x0040  /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_MASK                  0x0040  /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_SHIFT                      6  /* INL_CM_ENA */
+#define WM8903_INL_CM_ENA_WIDTH                      1  /* INL_CM_ENA */
+#define WM8903_L_IP_SEL_N_MASK                  0x0030  /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_N_SHIFT                      4  /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_N_WIDTH                      2  /* L_IP_SEL_N - [5:4] */
+#define WM8903_L_IP_SEL_P_MASK                  0x000C  /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_IP_SEL_P_SHIFT                      2  /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_IP_SEL_P_WIDTH                      2  /* L_IP_SEL_P - [3:2] */
+#define WM8903_L_MODE_MASK                      0x0003  /* L_MODE - [1:0] */
+#define WM8903_L_MODE_SHIFT                          0  /* L_MODE - [1:0] */
+#define WM8903_L_MODE_WIDTH                          2  /* L_MODE - [1:0] */
+
+/*
+ * R47 (0x2F) - Analogue Right Input 1
+ */
+#define WM8903_INR_CM_ENA                       0x0040  /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_MASK                  0x0040  /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_SHIFT                      6  /* INR_CM_ENA */
+#define WM8903_INR_CM_ENA_WIDTH                      1  /* INR_CM_ENA */
+#define WM8903_R_IP_SEL_N_MASK                  0x0030  /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_N_SHIFT                      4  /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_N_WIDTH                      2  /* R_IP_SEL_N - [5:4] */
+#define WM8903_R_IP_SEL_P_MASK                  0x000C  /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_IP_SEL_P_SHIFT                      2  /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_IP_SEL_P_WIDTH                      2  /* R_IP_SEL_P - [3:2] */
+#define WM8903_R_MODE_MASK                      0x0003  /* R_MODE - [1:0] */
+#define WM8903_R_MODE_SHIFT                          0  /* R_MODE - [1:0] */
+#define WM8903_R_MODE_WIDTH                          2  /* R_MODE - [1:0] */
+
+/*
+ * R50 (0x32) - Analogue Left Mix 0
+ */
+#define WM8903_DACL_TO_MIXOUTL                  0x0008  /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_MASK             0x0008  /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_SHIFT                 3  /* DACL_TO_MIXOUTL */
+#define WM8903_DACL_TO_MIXOUTL_WIDTH                 1  /* DACL_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL                  0x0004  /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_MASK             0x0004  /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_SHIFT                 2  /* DACR_TO_MIXOUTL */
+#define WM8903_DACR_TO_MIXOUTL_WIDTH                 1  /* DACR_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL               0x0002  /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_MASK          0x0002  /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_SHIFT              1  /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSL_TO_MIXOUTL_WIDTH              1  /* BYPASSL_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL               0x0001  /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_MASK          0x0001  /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_SHIFT              0  /* BYPASSR_TO_MIXOUTL */
+#define WM8903_BYPASSR_TO_MIXOUTL_WIDTH              1  /* BYPASSR_TO_MIXOUTL */
+
+/*
+ * R51 (0x33) - Analogue Right Mix 0
+ */
+#define WM8903_DACL_TO_MIXOUTR                  0x0008  /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_MASK             0x0008  /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_SHIFT                 3  /* DACL_TO_MIXOUTR */
+#define WM8903_DACL_TO_MIXOUTR_WIDTH                 1  /* DACL_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR                  0x0004  /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_MASK             0x0004  /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_SHIFT                 2  /* DACR_TO_MIXOUTR */
+#define WM8903_DACR_TO_MIXOUTR_WIDTH                 1  /* DACR_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR               0x0002  /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_MASK          0x0002  /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_SHIFT              1  /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSL_TO_MIXOUTR_WIDTH              1  /* BYPASSL_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR               0x0001  /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_MASK          0x0001  /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_SHIFT              0  /* BYPASSR_TO_MIXOUTR */
+#define WM8903_BYPASSR_TO_MIXOUTR_WIDTH              1  /* BYPASSR_TO_MIXOUTR */
+
+/*
+ * R52 (0x34) - Analogue Spk Mix Left 0
+ */
+#define WM8903_DACL_TO_MIXSPKL                  0x0008  /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_MASK             0x0008  /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_SHIFT                 3  /* DACL_TO_MIXSPKL */
+#define WM8903_DACL_TO_MIXSPKL_WIDTH                 1  /* DACL_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL                  0x0004  /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_MASK             0x0004  /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_SHIFT                 2  /* DACR_TO_MIXSPKL */
+#define WM8903_DACR_TO_MIXSPKL_WIDTH                 1  /* DACR_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL               0x0002  /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_MASK          0x0002  /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_SHIFT              1  /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSL_TO_MIXSPKL_WIDTH              1  /* BYPASSL_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL               0x0001  /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_MASK          0x0001  /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_SHIFT              0  /* BYPASSR_TO_MIXSPKL */
+#define WM8903_BYPASSR_TO_MIXSPKL_WIDTH              1  /* BYPASSR_TO_MIXSPKL */
+
+/*
+ * R53 (0x35) - Analogue Spk Mix Left 1
+ */
+#define WM8903_DACL_MIXSPKL_VOL                 0x0008  /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_MASK            0x0008  /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_SHIFT                3  /* DACL_MIXSPKL_VOL */
+#define WM8903_DACL_MIXSPKL_VOL_WIDTH                1  /* DACL_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL                 0x0004  /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_MASK            0x0004  /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_SHIFT                2  /* DACR_MIXSPKL_VOL */
+#define WM8903_DACR_MIXSPKL_VOL_WIDTH                1  /* DACR_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL              0x0002  /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_MASK         0x0002  /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_SHIFT             1  /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSL_MIXSPKL_VOL_WIDTH             1  /* BYPASSL_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL              0x0001  /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_MASK         0x0001  /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_SHIFT             0  /* BYPASSR_MIXSPKL_VOL */
+#define WM8903_BYPASSR_MIXSPKL_VOL_WIDTH             1  /* BYPASSR_MIXSPKL_VOL */
+
+/*
+ * R54 (0x36) - Analogue Spk Mix Right 0
+ */
+#define WM8903_DACL_TO_MIXSPKR                  0x0008  /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_MASK             0x0008  /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_SHIFT                 3  /* DACL_TO_MIXSPKR */
+#define WM8903_DACL_TO_MIXSPKR_WIDTH                 1  /* DACL_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR                  0x0004  /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_MASK             0x0004  /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_SHIFT                 2  /* DACR_TO_MIXSPKR */
+#define WM8903_DACR_TO_MIXSPKR_WIDTH                 1  /* DACR_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR               0x0002  /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_MASK          0x0002  /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_SHIFT              1  /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSL_TO_MIXSPKR_WIDTH              1  /* BYPASSL_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR               0x0001  /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_MASK          0x0001  /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_SHIFT              0  /* BYPASSR_TO_MIXSPKR */
+#define WM8903_BYPASSR_TO_MIXSPKR_WIDTH              1  /* BYPASSR_TO_MIXSPKR */
+
+/*
+ * R55 (0x37) - Analogue Spk Mix Right 1
+ */
+#define WM8903_DACL_MIXSPKR_VOL                 0x0008  /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_MASK            0x0008  /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_SHIFT                3  /* DACL_MIXSPKR_VOL */
+#define WM8903_DACL_MIXSPKR_VOL_WIDTH                1  /* DACL_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL                 0x0004  /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_MASK            0x0004  /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_SHIFT                2  /* DACR_MIXSPKR_VOL */
+#define WM8903_DACR_MIXSPKR_VOL_WIDTH                1  /* DACR_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL              0x0002  /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_MASK         0x0002  /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_SHIFT             1  /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSL_MIXSPKR_VOL_WIDTH             1  /* BYPASSL_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL              0x0001  /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_MASK         0x0001  /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_SHIFT             0  /* BYPASSR_MIXSPKR_VOL */
+#define WM8903_BYPASSR_MIXSPKR_VOL_WIDTH             1  /* BYPASSR_MIXSPKR_VOL */
+
+/*
+ * R57 (0x39) - Analogue OUT1 Left
+ */
+#define WM8903_HPL_MUTE                         0x0100  /* HPL_MUTE */
+#define WM8903_HPL_MUTE_MASK                    0x0100  /* HPL_MUTE */
+#define WM8903_HPL_MUTE_SHIFT                        8  /* HPL_MUTE */
+#define WM8903_HPL_MUTE_WIDTH                        1  /* HPL_MUTE */
+#define WM8903_HPOUTVU                          0x0080  /* HPOUTVU */
+#define WM8903_HPOUTVU_MASK                     0x0080  /* HPOUTVU */
+#define WM8903_HPOUTVU_SHIFT                         7  /* HPOUTVU */
+#define WM8903_HPOUTVU_WIDTH                         1  /* HPOUTVU */
+#define WM8903_HPOUTLZC                         0x0040  /* HPOUTLZC */
+#define WM8903_HPOUTLZC_MASK                    0x0040  /* HPOUTLZC */
+#define WM8903_HPOUTLZC_SHIFT                        6  /* HPOUTLZC */
+#define WM8903_HPOUTLZC_WIDTH                        1  /* HPOUTLZC */
+#define WM8903_HPOUTL_VOL_MASK                  0x003F  /* HPOUTL_VOL - [5:0] */
+#define WM8903_HPOUTL_VOL_SHIFT                      0  /* HPOUTL_VOL - [5:0] */
+#define WM8903_HPOUTL_VOL_WIDTH                      6  /* HPOUTL_VOL - [5:0] */
+
+/*
+ * R58 (0x3A) - Analogue OUT1 Right
+ */
+#define WM8903_HPR_MUTE                         0x0100  /* HPR_MUTE */
+#define WM8903_HPR_MUTE_MASK                    0x0100  /* HPR_MUTE */
+#define WM8903_HPR_MUTE_SHIFT                        8  /* HPR_MUTE */
+#define WM8903_HPR_MUTE_WIDTH                        1  /* HPR_MUTE */
+#define WM8903_HPOUTVU                          0x0080  /* HPOUTVU */
+#define WM8903_HPOUTVU_MASK                     0x0080  /* HPOUTVU */
+#define WM8903_HPOUTVU_SHIFT                         7  /* HPOUTVU */
+#define WM8903_HPOUTVU_WIDTH                         1  /* HPOUTVU */
+#define WM8903_HPOUTRZC                         0x0040  /* HPOUTRZC */
+#define WM8903_HPOUTRZC_MASK                    0x0040  /* HPOUTRZC */
+#define WM8903_HPOUTRZC_SHIFT                        6  /* HPOUTRZC */
+#define WM8903_HPOUTRZC_WIDTH                        1  /* HPOUTRZC */
+#define WM8903_HPOUTR_VOL_MASK                  0x003F  /* HPOUTR_VOL - [5:0] */
+#define WM8903_HPOUTR_VOL_SHIFT                      0  /* HPOUTR_VOL - [5:0] */
+#define WM8903_HPOUTR_VOL_WIDTH                      6  /* HPOUTR_VOL - [5:0] */
+
+/*
+ * R59 (0x3B) - Analogue OUT2 Left
+ */
+#define WM8903_LINEOUTL_MUTE                    0x0100  /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_MASK               0x0100  /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_SHIFT                   8  /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTL_MUTE_WIDTH                   1  /* LINEOUTL_MUTE */
+#define WM8903_LINEOUTVU                        0x0080  /* LINEOUTVU */
+#define WM8903_LINEOUTVU_MASK                   0x0080  /* LINEOUTVU */
+#define WM8903_LINEOUTVU_SHIFT                       7  /* LINEOUTVU */
+#define WM8903_LINEOUTVU_WIDTH                       1  /* LINEOUTVU */
+#define WM8903_LINEOUTLZC                       0x0040  /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_MASK                  0x0040  /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_SHIFT                      6  /* LINEOUTLZC */
+#define WM8903_LINEOUTLZC_WIDTH                      1  /* LINEOUTLZC */
+#define WM8903_LINEOUTL_VOL_MASK                0x003F  /* LINEOUTL_VOL - [5:0] */
+#define WM8903_LINEOUTL_VOL_SHIFT                    0  /* LINEOUTL_VOL - [5:0] */
+#define WM8903_LINEOUTL_VOL_WIDTH                    6  /* LINEOUTL_VOL - [5:0] */
+
+/*
+ * R60 (0x3C) - Analogue OUT2 Right
+ */
+#define WM8903_LINEOUTR_MUTE                    0x0100  /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_MASK               0x0100  /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_SHIFT                   8  /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTR_MUTE_WIDTH                   1  /* LINEOUTR_MUTE */
+#define WM8903_LINEOUTVU                        0x0080  /* LINEOUTVU */
+#define WM8903_LINEOUTVU_MASK                   0x0080  /* LINEOUTVU */
+#define WM8903_LINEOUTVU_SHIFT                       7  /* LINEOUTVU */
+#define WM8903_LINEOUTVU_WIDTH                       1  /* LINEOUTVU */
+#define WM8903_LINEOUTRZC                       0x0040  /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_MASK                  0x0040  /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_SHIFT                      6  /* LINEOUTRZC */
+#define WM8903_LINEOUTRZC_WIDTH                      1  /* LINEOUTRZC */
+#define WM8903_LINEOUTR_VOL_MASK                0x003F  /* LINEOUTR_VOL - [5:0] */
+#define WM8903_LINEOUTR_VOL_SHIFT                    0  /* LINEOUTR_VOL - [5:0] */
+#define WM8903_LINEOUTR_VOL_WIDTH                    6  /* LINEOUTR_VOL - [5:0] */
+
+/*
+ * R62 (0x3E) - Analogue OUT3 Left
+ */
+#define WM8903_SPKL_MUTE                        0x0100  /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_MASK                   0x0100  /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_SHIFT                       8  /* SPKL_MUTE */
+#define WM8903_SPKL_MUTE_WIDTH                       1  /* SPKL_MUTE */
+#define WM8903_SPKVU                            0x0080  /* SPKVU */
+#define WM8903_SPKVU_MASK                       0x0080  /* SPKVU */
+#define WM8903_SPKVU_SHIFT                           7  /* SPKVU */
+#define WM8903_SPKVU_WIDTH                           1  /* SPKVU */
+#define WM8903_SPKLZC                           0x0040  /* SPKLZC */
+#define WM8903_SPKLZC_MASK                      0x0040  /* SPKLZC */
+#define WM8903_SPKLZC_SHIFT                          6  /* SPKLZC */
+#define WM8903_SPKLZC_WIDTH                          1  /* SPKLZC */
+#define WM8903_SPKL_VOL_MASK                    0x003F  /* SPKL_VOL - [5:0] */
+#define WM8903_SPKL_VOL_SHIFT                        0  /* SPKL_VOL - [5:0] */
+#define WM8903_SPKL_VOL_WIDTH                        6  /* SPKL_VOL - [5:0] */
+
+/*
+ * R63 (0x3F) - Analogue OUT3 Right
+ */
+#define WM8903_SPKR_MUTE                        0x0100  /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_MASK                   0x0100  /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_SHIFT                       8  /* SPKR_MUTE */
+#define WM8903_SPKR_MUTE_WIDTH                       1  /* SPKR_MUTE */
+#define WM8903_SPKVU                            0x0080  /* SPKVU */
+#define WM8903_SPKVU_MASK                       0x0080  /* SPKVU */
+#define WM8903_SPKVU_SHIFT                           7  /* SPKVU */
+#define WM8903_SPKVU_WIDTH                           1  /* SPKVU */
+#define WM8903_SPKRZC                           0x0040  /* SPKRZC */
+#define WM8903_SPKRZC_MASK                      0x0040  /* SPKRZC */
+#define WM8903_SPKRZC_SHIFT                          6  /* SPKRZC */
+#define WM8903_SPKRZC_WIDTH                          1  /* SPKRZC */
+#define WM8903_SPKR_VOL_MASK                    0x003F  /* SPKR_VOL - [5:0] */
+#define WM8903_SPKR_VOL_SHIFT                        0  /* SPKR_VOL - [5:0] */
+#define WM8903_SPKR_VOL_WIDTH                        6  /* SPKR_VOL - [5:0] */
+
+/*
+ * R65 (0x41) - Analogue SPK Output Control 0
+ */
+#define WM8903_SPK_DISCHARGE                    0x0002  /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_MASK               0x0002  /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_SHIFT                   1  /* SPK_DISCHARGE */
+#define WM8903_SPK_DISCHARGE_WIDTH                   1  /* SPK_DISCHARGE */
+#define WM8903_VROI                             0x0001  /* VROI */
+#define WM8903_VROI_MASK                        0x0001  /* VROI */
+#define WM8903_VROI_SHIFT                            0  /* VROI */
+#define WM8903_VROI_WIDTH                            1  /* VROI */
+
+/*
+ * R67 (0x43) - DC Servo 0
+ */
+#define WM8903_DCS_MASTER_ENA                   0x0010  /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_MASK              0x0010  /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_SHIFT                  4  /* DCS_MASTER_ENA */
+#define WM8903_DCS_MASTER_ENA_WIDTH                  1  /* DCS_MASTER_ENA */
+#define WM8903_DCS_ENA_MASK                     0x000F  /* DCS_ENA - [3:0] */
+#define WM8903_DCS_ENA_SHIFT                         0  /* DCS_ENA - [3:0] */
+#define WM8903_DCS_ENA_WIDTH                         4  /* DCS_ENA - [3:0] */
+
+/*
+ * R69 (0x45) - DC Servo 2
+ */
+#define WM8903_DCS_MODE_MASK                    0x0003  /* DCS_MODE - [1:0] */
+#define WM8903_DCS_MODE_SHIFT                        0  /* DCS_MODE - [1:0] */
+#define WM8903_DCS_MODE_WIDTH                        2  /* DCS_MODE - [1:0] */
+
+/*
+ * R90 (0x5A) - Analogue HP 0
+ */
+#define WM8903_HPL_RMV_SHORT                    0x0080  /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_MASK               0x0080  /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_SHIFT                   7  /* HPL_RMV_SHORT */
+#define WM8903_HPL_RMV_SHORT_WIDTH                   1  /* HPL_RMV_SHORT */
+#define WM8903_HPL_ENA_OUTP                     0x0040  /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_MASK                0x0040  /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_SHIFT                    6  /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_OUTP_WIDTH                    1  /* HPL_ENA_OUTP */
+#define WM8903_HPL_ENA_DLY                      0x0020  /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_MASK                 0x0020  /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_SHIFT                     5  /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA_DLY_WIDTH                     1  /* HPL_ENA_DLY */
+#define WM8903_HPL_ENA                          0x0010  /* HPL_ENA */
+#define WM8903_HPL_ENA_MASK                     0x0010  /* HPL_ENA */
+#define WM8903_HPL_ENA_SHIFT                         4  /* HPL_ENA */
+#define WM8903_HPL_ENA_WIDTH                         1  /* HPL_ENA */
+#define WM8903_HPR_RMV_SHORT                    0x0008  /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_MASK               0x0008  /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_SHIFT                   3  /* HPR_RMV_SHORT */
+#define WM8903_HPR_RMV_SHORT_WIDTH                   1  /* HPR_RMV_SHORT */
+#define WM8903_HPR_ENA_OUTP                     0x0004  /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_MASK                0x0004  /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_SHIFT                    2  /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_OUTP_WIDTH                    1  /* HPR_ENA_OUTP */
+#define WM8903_HPR_ENA_DLY                      0x0002  /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_MASK                 0x0002  /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_SHIFT                     1  /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA_DLY_WIDTH                     1  /* HPR_ENA_DLY */
+#define WM8903_HPR_ENA                          0x0001  /* HPR_ENA */
+#define WM8903_HPR_ENA_MASK                     0x0001  /* HPR_ENA */
+#define WM8903_HPR_ENA_SHIFT                         0  /* HPR_ENA */
+#define WM8903_HPR_ENA_WIDTH                         1  /* HPR_ENA */
+
+/*
+ * R94 (0x5E) - Analogue Lineout 0
+ */
+#define WM8903_LINEOUTL_RMV_SHORT               0x0080  /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_MASK          0x0080  /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_SHIFT              7  /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_RMV_SHORT_WIDTH              1  /* LINEOUTL_RMV_SHORT */
+#define WM8903_LINEOUTL_ENA_OUTP                0x0040  /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_MASK           0x0040  /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_SHIFT               6  /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_OUTP_WIDTH               1  /* LINEOUTL_ENA_OUTP */
+#define WM8903_LINEOUTL_ENA_DLY                 0x0020  /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_MASK            0x0020  /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_SHIFT                5  /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA_DLY_WIDTH                1  /* LINEOUTL_ENA_DLY */
+#define WM8903_LINEOUTL_ENA                     0x0010  /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_MASK                0x0010  /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_SHIFT                    4  /* LINEOUTL_ENA */
+#define WM8903_LINEOUTL_ENA_WIDTH                    1  /* LINEOUTL_ENA */
+#define WM8903_LINEOUTR_RMV_SHORT               0x0008  /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_MASK          0x0008  /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_SHIFT              3  /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_RMV_SHORT_WIDTH              1  /* LINEOUTR_RMV_SHORT */
+#define WM8903_LINEOUTR_ENA_OUTP                0x0004  /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_MASK           0x0004  /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_SHIFT               2  /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_OUTP_WIDTH               1  /* LINEOUTR_ENA_OUTP */
+#define WM8903_LINEOUTR_ENA_DLY                 0x0002  /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_MASK            0x0002  /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_SHIFT                1  /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA_DLY_WIDTH                1  /* LINEOUTR_ENA_DLY */
+#define WM8903_LINEOUTR_ENA                     0x0001  /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_MASK                0x0001  /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_SHIFT                    0  /* LINEOUTR_ENA */
+#define WM8903_LINEOUTR_ENA_WIDTH                    1  /* LINEOUTR_ENA */
+
+/*
+ * R98 (0x62) - Charge Pump 0
+ */
+#define WM8903_CP_ENA                           0x0001  /* CP_ENA */
+#define WM8903_CP_ENA_MASK                      0x0001  /* CP_ENA */
+#define WM8903_CP_ENA_SHIFT                          0  /* CP_ENA */
+#define WM8903_CP_ENA_WIDTH                          1  /* CP_ENA */
+
+/*
+ * R104 (0x68) - Class W 0
+ */
+#define WM8903_CP_DYN_FREQ                      0x0002  /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_MASK                 0x0002  /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_SHIFT                     1  /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_FREQ_WIDTH                     1  /* CP_DYN_FREQ */
+#define WM8903_CP_DYN_V                         0x0001  /* CP_DYN_V */
+#define WM8903_CP_DYN_V_MASK                    0x0001  /* CP_DYN_V */
+#define WM8903_CP_DYN_V_SHIFT                        0  /* CP_DYN_V */
+#define WM8903_CP_DYN_V_WIDTH                        1  /* CP_DYN_V */
+
+/*
+ * R108 (0x6C) - Write Sequencer 0
+ */
+#define WM8903_WSEQ_ENA                         0x0100  /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_MASK                    0x0100  /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_SHIFT                        8  /* WSEQ_ENA */
+#define WM8903_WSEQ_ENA_WIDTH                        1  /* WSEQ_ENA */
+#define WM8903_WSEQ_WRITE_INDEX_MASK            0x001F  /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8903_WSEQ_WRITE_INDEX_SHIFT                0  /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8903_WSEQ_WRITE_INDEX_WIDTH                5  /* WSEQ_WRITE_INDEX - [4:0] */
+
+/*
+ * R109 (0x6D) - Write Sequencer 1
+ */
+#define WM8903_WSEQ_DATA_WIDTH_MASK             0x7000  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_WIDTH_SHIFT                12  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_WIDTH_WIDTH                 3  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8903_WSEQ_DATA_START_MASK             0x0F00  /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_DATA_START_SHIFT                 8  /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_DATA_START_WIDTH                 4  /* WSEQ_DATA_START - [11:8] */
+#define WM8903_WSEQ_ADDR_MASK                   0x00FF  /* WSEQ_ADDR - [7:0] */
+#define WM8903_WSEQ_ADDR_SHIFT                       0  /* WSEQ_ADDR - [7:0] */
+#define WM8903_WSEQ_ADDR_WIDTH                       8  /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R110 (0x6E) - Write Sequencer 2
+ */
+#define WM8903_WSEQ_EOS                         0x4000  /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_MASK                    0x4000  /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_SHIFT                       14  /* WSEQ_EOS */
+#define WM8903_WSEQ_EOS_WIDTH                        1  /* WSEQ_EOS */
+#define WM8903_WSEQ_DELAY_MASK                  0x0F00  /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DELAY_SHIFT                      8  /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DELAY_WIDTH                      4  /* WSEQ_DELAY - [11:8] */
+#define WM8903_WSEQ_DATA_MASK                   0x00FF  /* WSEQ_DATA - [7:0] */
+#define WM8903_WSEQ_DATA_SHIFT                       0  /* WSEQ_DATA - [7:0] */
+#define WM8903_WSEQ_DATA_WIDTH                       8  /* WSEQ_DATA - [7:0] */
+
+/*
+ * R111 (0x6F) - Write Sequencer 3
+ */
+#define WM8903_WSEQ_ABORT                       0x0200  /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_MASK                  0x0200  /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_SHIFT                      9  /* WSEQ_ABORT */
+#define WM8903_WSEQ_ABORT_WIDTH                      1  /* WSEQ_ABORT */
+#define WM8903_WSEQ_START                       0x0100  /* WSEQ_START */
+#define WM8903_WSEQ_START_MASK                  0x0100  /* WSEQ_START */
+#define WM8903_WSEQ_START_SHIFT                      8  /* WSEQ_START */
+#define WM8903_WSEQ_START_WIDTH                      1  /* WSEQ_START */
+#define WM8903_WSEQ_START_INDEX_MASK            0x003F  /* WSEQ_START_INDEX - [5:0] */
+#define WM8903_WSEQ_START_INDEX_SHIFT                0  /* WSEQ_START_INDEX - [5:0] */
+#define WM8903_WSEQ_START_INDEX_WIDTH                6  /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R112 (0x70) - Write Sequencer 4
+ */
+#define WM8903_WSEQ_CURRENT_INDEX_MASK          0x03F0  /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_CURRENT_INDEX_SHIFT              4  /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_CURRENT_INDEX_WIDTH              6  /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8903_WSEQ_BUSY                        0x0001  /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_MASK                   0x0001  /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_SHIFT                       0  /* WSEQ_BUSY */
+#define WM8903_WSEQ_BUSY_WIDTH                       1  /* WSEQ_BUSY */
+
+/*
+ * R114 (0x72) - Control Interface
+ */
+#define WM8903_MASK_WRITE_ENA                   0x0001  /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_MASK              0x0001  /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_SHIFT                  0  /* MASK_WRITE_ENA */
+#define WM8903_MASK_WRITE_ENA_WIDTH                  1  /* MASK_WRITE_ENA */
+
+/*
+ * R121 (0x79) - Interrupt Status 1
+ */
+#define WM8903_MICSHRT_EINT                     0x8000  /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_MASK                0x8000  /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_SHIFT                   15  /* MICSHRT_EINT */
+#define WM8903_MICSHRT_EINT_WIDTH                    1  /* MICSHRT_EINT */
+#define WM8903_MICDET_EINT                      0x4000  /* MICDET_EINT */
+#define WM8903_MICDET_EINT_MASK                 0x4000  /* MICDET_EINT */
+#define WM8903_MICDET_EINT_SHIFT                    14  /* MICDET_EINT */
+#define WM8903_MICDET_EINT_WIDTH                     1  /* MICDET_EINT */
+#define WM8903_WSEQ_BUSY_EINT                   0x2000  /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_MASK              0x2000  /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_SHIFT                 13  /* WSEQ_BUSY_EINT */
+#define WM8903_WSEQ_BUSY_EINT_WIDTH                  1  /* WSEQ_BUSY_EINT */
+#define WM8903_GP5_EINT                         0x0010  /* GP5_EINT */
+#define WM8903_GP5_EINT_MASK                    0x0010  /* GP5_EINT */
+#define WM8903_GP5_EINT_SHIFT                        4  /* GP5_EINT */
+#define WM8903_GP5_EINT_WIDTH                        1  /* GP5_EINT */
+#define WM8903_GP4_EINT                         0x0008  /* GP4_EINT */
+#define WM8903_GP4_EINT_MASK                    0x0008  /* GP4_EINT */
+#define WM8903_GP4_EINT_SHIFT                        3  /* GP4_EINT */
+#define WM8903_GP4_EINT_WIDTH                        1  /* GP4_EINT */
+#define WM8903_GP3_EINT                         0x0004  /* GP3_EINT */
+#define WM8903_GP3_EINT_MASK                    0x0004  /* GP3_EINT */
+#define WM8903_GP3_EINT_SHIFT                        2  /* GP3_EINT */
+#define WM8903_GP3_EINT_WIDTH                        1  /* GP3_EINT */
+#define WM8903_GP2_EINT                         0x0002  /* GP2_EINT */
+#define WM8903_GP2_EINT_MASK                    0x0002  /* GP2_EINT */
+#define WM8903_GP2_EINT_SHIFT                        1  /* GP2_EINT */
+#define WM8903_GP2_EINT_WIDTH                        1  /* GP2_EINT */
+#define WM8903_GP1_EINT                         0x0001  /* GP1_EINT */
+#define WM8903_GP1_EINT_MASK                    0x0001  /* GP1_EINT */
+#define WM8903_GP1_EINT_SHIFT                        0  /* GP1_EINT */
+#define WM8903_GP1_EINT_WIDTH                        1  /* GP1_EINT */
+
+/*
+ * R122 (0x7A) - Interrupt Status 1 Mask
+ */
+#define WM8903_IM_MICSHRT_EINT                  0x8000  /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_MASK             0x8000  /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_SHIFT                15  /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICSHRT_EINT_WIDTH                 1  /* IM_MICSHRT_EINT */
+#define WM8903_IM_MICDET_EINT                   0x4000  /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_MASK              0x4000  /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_SHIFT                 14  /* IM_MICDET_EINT */
+#define WM8903_IM_MICDET_EINT_WIDTH                  1  /* IM_MICDET_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT                0x2000  /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_MASK           0x2000  /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_SHIFT              13  /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_WSEQ_BUSY_EINT_WIDTH               1  /* IM_WSEQ_BUSY_EINT */
+#define WM8903_IM_GP5_EINT                      0x0010  /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_MASK                 0x0010  /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_SHIFT                     4  /* IM_GP5_EINT */
+#define WM8903_IM_GP5_EINT_WIDTH                     1  /* IM_GP5_EINT */
+#define WM8903_IM_GP4_EINT                      0x0008  /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_MASK                 0x0008  /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_SHIFT                     3  /* IM_GP4_EINT */
+#define WM8903_IM_GP4_EINT_WIDTH                     1  /* IM_GP4_EINT */
+#define WM8903_IM_GP3_EINT                      0x0004  /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_MASK                 0x0004  /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_SHIFT                     2  /* IM_GP3_EINT */
+#define WM8903_IM_GP3_EINT_WIDTH                     1  /* IM_GP3_EINT */
+#define WM8903_IM_GP2_EINT                      0x0002  /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_MASK                 0x0002  /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_SHIFT                     1  /* IM_GP2_EINT */
+#define WM8903_IM_GP2_EINT_WIDTH                     1  /* IM_GP2_EINT */
+#define WM8903_IM_GP1_EINT                      0x0001  /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_MASK                 0x0001  /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_SHIFT                     0  /* IM_GP1_EINT */
+#define WM8903_IM_GP1_EINT_WIDTH                     1  /* IM_GP1_EINT */
+
+/*
+ * R123 (0x7B) - Interrupt Polarity 1
+ */
+#define WM8903_MICSHRT_INV                      0x8000  /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_MASK                 0x8000  /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_SHIFT                    15  /* MICSHRT_INV */
+#define WM8903_MICSHRT_INV_WIDTH                     1  /* MICSHRT_INV */
+#define WM8903_MICDET_INV                       0x4000  /* MICDET_INV */
+#define WM8903_MICDET_INV_MASK                  0x4000  /* MICDET_INV */
+#define WM8903_MICDET_INV_SHIFT                     14  /* MICDET_INV */
+#define WM8903_MICDET_INV_WIDTH                      1  /* MICDET_INV */
+
+/*
+ * R126 (0x7E) - Interrupt Control
+ */
+#define WM8903_IRQ_POL                          0x0001  /* IRQ_POL */
+#define WM8903_IRQ_POL_MASK                     0x0001  /* IRQ_POL */
+#define WM8903_IRQ_POL_SHIFT                         0  /* IRQ_POL */
+#define WM8903_IRQ_POL_WIDTH                         1  /* IRQ_POL */
+
+/*
+ * R129 (0x81) - Control Interface Test 1
+ */
+#define WM8903_USER_KEY                         0x0002  /* USER_KEY */
+#define WM8903_USER_KEY_MASK                    0x0002  /* USER_KEY */
+#define WM8903_USER_KEY_SHIFT                        1  /* USER_KEY */
+#define WM8903_USER_KEY_WIDTH                        1  /* USER_KEY */
+#define WM8903_TEST_KEY                         0x0001  /* TEST_KEY */
+#define WM8903_TEST_KEY_MASK                    0x0001  /* TEST_KEY */
+#define WM8903_TEST_KEY_SHIFT                        0  /* TEST_KEY */
+#define WM8903_TEST_KEY_WIDTH                        1  /* TEST_KEY */
+
+/*
+ * R149 (0x95) - Charge Pump Test 1
+ */
+#define WM8903_CP_SW_KELVIN_MODE_MASK           0x0006  /* CP_SW_KELVIN_MODE - [2:1] */
+#define WM8903_CP_SW_KELVIN_MODE_SHIFT               1  /* CP_SW_KELVIN_MODE - [2:1] */
+#define WM8903_CP_SW_KELVIN_MODE_WIDTH               2  /* CP_SW_KELVIN_MODE - [2:1] */
+
+/*
+ * R164 (0xA4) - Clock Rate Test 4
+ */
+#define WM8903_ADC_DIG_MIC                      0x0200  /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_MASK                 0x0200  /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_SHIFT                     9  /* ADC_DIG_MIC */
+#define WM8903_ADC_DIG_MIC_WIDTH                     1  /* ADC_DIG_MIC */
+
+/*
+ * R172 (0xAC) - Analogue Output Bias 0
+ */
+#define WM8903_PGA_BIAS_MASK                    0x0070  /* PGA_BIAS - [6:4] */
+#define WM8903_PGA_BIAS_SHIFT                        4  /* PGA_BIAS - [6:4] */
+#define WM8903_PGA_BIAS_WIDTH                        3  /* PGA_BIAS - [6:4] */
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8904.c b/linux/sound/soc/codecs/wm8904.c
new file mode 100644
index 0000000..1ec12ef
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8904.c
@@ -0,0 +1,2591 @@
+/*
+ * wm8904.c  --  WM8904 ALSA SoC Audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/wm8904.h>
+
+#include "wm8904.h"
+
+enum wm8904_type {
+	WM8904,
+	WM8912,
+};
+
+#define WM8904_NUM_DCS_CHANNELS 4
+
+#define WM8904_NUM_SUPPLIES 5
+static const char *wm8904_supply_names[WM8904_NUM_SUPPLIES] = {
+	"DCVDD",
+	"DBVDD",
+	"AVDD",
+	"CPVDD",
+	"MICVDD",
+};
+
+/* codec private data */
+struct wm8904_priv {
+
+	enum wm8904_type devtype;
+	void *control_data;
+
+	struct regulator_bulk_data supplies[WM8904_NUM_SUPPLIES];
+
+	struct wm8904_pdata *pdata;
+
+	int deemph;
+
+	/* Platform provided DRC configuration */
+	const char **drc_texts;
+	int drc_cfg;
+	struct soc_enum drc_enum;
+
+	/* Platform provided ReTune mobile configuration */
+	int num_retune_mobile_texts;
+	const char **retune_mobile_texts;
+	int retune_mobile_cfg;
+	struct soc_enum retune_mobile_enum;
+
+	/* FLL setup */
+	int fll_src;
+	int fll_fref;
+	int fll_fout;
+
+	/* Clocking configuration */
+	unsigned int mclk_rate;
+	int sysclk_src;
+	unsigned int sysclk_rate;
+
+	int tdm_width;
+	int tdm_slots;
+	int bclk;
+	int fs;
+
+	/* DC servo configuration - cached offset values */
+	int dcs_state[WM8904_NUM_DCS_CHANNELS];
+};
+
+static const u16 wm8904_reg[WM8904_MAX_REGISTER + 1] = {
+	0x8904,     /* R0   - SW Reset and ID */
+	0x0000,     /* R1   - Revision */
+	0x0000,     /* R2 */
+	0x0000,     /* R3 */
+	0x0018,     /* R4   - Bias Control 0 */
+	0x0000,     /* R5   - VMID Control 0 */
+	0x0000,     /* R6   - Mic Bias Control 0 */
+	0x0000,     /* R7   - Mic Bias Control 1 */
+	0x0001,     /* R8   - Analogue DAC 0 */
+	0x9696,     /* R9   - mic Filter Control */
+	0x0001,     /* R10  - Analogue ADC 0 */
+	0x0000,     /* R11 */
+	0x0000,     /* R12  - Power Management 0 */
+	0x0000,     /* R13 */
+	0x0000,     /* R14  - Power Management 2 */
+	0x0000,     /* R15  - Power Management 3 */
+	0x0000,     /* R16 */
+	0x0000,     /* R17 */
+	0x0000,     /* R18  - Power Management 6 */
+	0x0000,     /* R19 */
+	0x945E,     /* R20  - Clock Rates 0 */
+	0x0C05,     /* R21  - Clock Rates 1 */
+	0x0006,     /* R22  - Clock Rates 2 */
+	0x0000,     /* R23 */
+	0x0050,     /* R24  - Audio Interface 0 */
+	0x000A,     /* R25  - Audio Interface 1 */
+	0x00E4,     /* R26  - Audio Interface 2 */
+	0x0040,     /* R27  - Audio Interface 3 */
+	0x0000,     /* R28 */
+	0x0000,     /* R29 */
+	0x00C0,     /* R30  - DAC Digital Volume Left */
+	0x00C0,     /* R31  - DAC Digital Volume Right */
+	0x0000,     /* R32  - DAC Digital 0 */
+	0x0008,     /* R33  - DAC Digital 1 */
+	0x0000,     /* R34 */
+	0x0000,     /* R35 */
+	0x00C0,     /* R36  - ADC Digital Volume Left */
+	0x00C0,     /* R37  - ADC Digital Volume Right */
+	0x0010,     /* R38  - ADC Digital 0 */
+	0x0000,     /* R39  - Digital Microphone 0 */
+	0x01AF,     /* R40  - DRC 0 */
+	0x3248,     /* R41  - DRC 1 */
+	0x0000,     /* R42  - DRC 2 */
+	0x0000,     /* R43  - DRC 3 */
+	0x0085,     /* R44  - Analogue Left Input 0 */
+	0x0085,     /* R45  - Analogue Right Input 0 */
+	0x0044,     /* R46  - Analogue Left Input 1 */
+	0x0044,     /* R47  - Analogue Right Input 1 */
+	0x0000,     /* R48 */
+	0x0000,     /* R49 */
+	0x0000,     /* R50 */
+	0x0000,     /* R51 */
+	0x0000,     /* R52 */
+	0x0000,     /* R53 */
+	0x0000,     /* R54 */
+	0x0000,     /* R55 */
+	0x0000,     /* R56 */
+	0x002D,     /* R57  - Analogue OUT1 Left */
+	0x002D,     /* R58  - Analogue OUT1 Right */
+	0x0039,     /* R59  - Analogue OUT2 Left */
+	0x0039,     /* R60  - Analogue OUT2 Right */
+	0x0000,     /* R61  - Analogue OUT12 ZC */
+	0x0000,     /* R62 */
+	0x0000,     /* R63 */
+	0x0000,     /* R64 */
+	0x0000,     /* R65 */
+	0x0000,     /* R66 */
+	0x0000,     /* R67  - DC Servo 0 */
+	0x0000,     /* R68  - DC Servo 1 */
+	0xAAAA,     /* R69  - DC Servo 2 */
+	0x0000,     /* R70 */
+	0xAAAA,     /* R71  - DC Servo 4 */
+	0xAAAA,     /* R72  - DC Servo 5 */
+	0x0000,     /* R73  - DC Servo 6 */
+	0x0000,     /* R74  - DC Servo 7 */
+	0x0000,     /* R75  - DC Servo 8 */
+	0x0000,     /* R76  - DC Servo 9 */
+	0x0000,     /* R77  - DC Servo Readback 0 */
+	0x0000,     /* R78 */
+	0x0000,     /* R79 */
+	0x0000,     /* R80 */
+	0x0000,     /* R81 */
+	0x0000,     /* R82 */
+	0x0000,     /* R83 */
+	0x0000,     /* R84 */
+	0x0000,     /* R85 */
+	0x0000,     /* R86 */
+	0x0000,     /* R87 */
+	0x0000,     /* R88 */
+	0x0000,     /* R89 */
+	0x0000,     /* R90  - Analogue HP 0 */
+	0x0000,     /* R91 */
+	0x0000,     /* R92 */
+	0x0000,     /* R93 */
+	0x0000,     /* R94  - Analogue Lineout 0 */
+	0x0000,     /* R95 */
+	0x0000,     /* R96 */
+	0x0000,     /* R97 */
+	0x0000,     /* R98  - Charge Pump 0 */
+	0x0000,     /* R99 */
+	0x0000,     /* R100 */
+	0x0000,     /* R101 */
+	0x0000,     /* R102 */
+	0x0000,     /* R103 */
+	0x0004,     /* R104 - Class W 0 */
+	0x0000,     /* R105 */
+	0x0000,     /* R106 */
+	0x0000,     /* R107 */
+	0x0000,     /* R108 - Write Sequencer 0 */
+	0x0000,     /* R109 - Write Sequencer 1 */
+	0x0000,     /* R110 - Write Sequencer 2 */
+	0x0000,     /* R111 - Write Sequencer 3 */
+	0x0000,     /* R112 - Write Sequencer 4 */
+	0x0000,     /* R113 */
+	0x0000,     /* R114 */
+	0x0000,     /* R115 */
+	0x0000,     /* R116 - FLL Control 1 */
+	0x0007,     /* R117 - FLL Control 2 */
+	0x0000,     /* R118 - FLL Control 3 */
+	0x2EE0,     /* R119 - FLL Control 4 */
+	0x0004,     /* R120 - FLL Control 5 */
+	0x0014,     /* R121 - GPIO Control 1 */
+	0x0010,     /* R122 - GPIO Control 2 */
+	0x0010,     /* R123 - GPIO Control 3 */
+	0x0000,     /* R124 - GPIO Control 4 */
+	0x0000,     /* R125 */
+	0x0000,     /* R126 - Digital Pulls */
+	0x0000,     /* R127 - Interrupt Status */
+	0xFFFF,     /* R128 - Interrupt Status Mask */
+	0x0000,     /* R129 - Interrupt Polarity */
+	0x0000,     /* R130 - Interrupt Debounce */
+	0x0000,     /* R131 */
+	0x0000,     /* R132 */
+	0x0000,     /* R133 */
+	0x0000,     /* R134 - EQ1 */
+	0x000C,     /* R135 - EQ2 */
+	0x000C,     /* R136 - EQ3 */
+	0x000C,     /* R137 - EQ4 */
+	0x000C,     /* R138 - EQ5 */
+	0x000C,     /* R139 - EQ6 */
+	0x0FCA,     /* R140 - EQ7 */
+	0x0400,     /* R141 - EQ8 */
+	0x00D8,     /* R142 - EQ9 */
+	0x1EB5,     /* R143 - EQ10 */
+	0xF145,     /* R144 - EQ11 */
+	0x0B75,     /* R145 - EQ12 */
+	0x01C5,     /* R146 - EQ13 */
+	0x1C58,     /* R147 - EQ14 */
+	0xF373,     /* R148 - EQ15 */
+	0x0A54,     /* R149 - EQ16 */
+	0x0558,     /* R150 - EQ17 */
+	0x168E,     /* R151 - EQ18 */
+	0xF829,     /* R152 - EQ19 */
+	0x07AD,     /* R153 - EQ20 */
+	0x1103,     /* R154 - EQ21 */
+	0x0564,     /* R155 - EQ22 */
+	0x0559,     /* R156 - EQ23 */
+	0x4000,     /* R157 - EQ24 */
+	0x0000,     /* R158 */
+	0x0000,     /* R159 */
+	0x0000,     /* R160 */
+	0x0000,     /* R161 - Control Interface Test 1 */
+	0x0000,     /* R162 */
+	0x0000,     /* R163 */
+	0x0000,     /* R164 */
+	0x0000,     /* R165 */
+	0x0000,     /* R166 */
+	0x0000,     /* R167 */
+	0x0000,     /* R168 */
+	0x0000,     /* R169 */
+	0x0000,     /* R170 */
+	0x0000,     /* R171 */
+	0x0000,     /* R172 */
+	0x0000,     /* R173 */
+	0x0000,     /* R174 */
+	0x0000,     /* R175 */
+	0x0000,     /* R176 */
+	0x0000,     /* R177 */
+	0x0000,     /* R178 */
+	0x0000,     /* R179 */
+	0x0000,     /* R180 */
+	0x0000,     /* R181 */
+	0x0000,     /* R182 */
+	0x0000,     /* R183 */
+	0x0000,     /* R184 */
+	0x0000,     /* R185 */
+	0x0000,     /* R186 */
+	0x0000,     /* R187 */
+	0x0000,     /* R188 */
+	0x0000,     /* R189 */
+	0x0000,     /* R190 */
+	0x0000,     /* R191 */
+	0x0000,     /* R192 */
+	0x0000,     /* R193 */
+	0x0000,     /* R194 */
+	0x0000,     /* R195 */
+	0x0000,     /* R196 */
+	0x0000,     /* R197 */
+	0x0000,     /* R198 */
+	0x0000,     /* R199 */
+	0x0000,     /* R200 */
+	0x0000,     /* R201 */
+	0x0000,     /* R202 */
+	0x0000,     /* R203 */
+	0x0000,     /* R204 - Analogue Output Bias 0 */
+	0x0000,     /* R205 */
+	0x0000,     /* R206 */
+	0x0000,     /* R207 */
+	0x0000,     /* R208 */
+	0x0000,     /* R209 */
+	0x0000,     /* R210 */
+	0x0000,     /* R211 */
+	0x0000,     /* R212 */
+	0x0000,     /* R213 */
+	0x0000,     /* R214 */
+	0x0000,     /* R215 */
+	0x0000,     /* R216 */
+	0x0000,     /* R217 */
+	0x0000,     /* R218 */
+	0x0000,     /* R219 */
+	0x0000,     /* R220 */
+	0x0000,     /* R221 */
+	0x0000,     /* R222 */
+	0x0000,     /* R223 */
+	0x0000,     /* R224 */
+	0x0000,     /* R225 */
+	0x0000,     /* R226 */
+	0x0000,     /* R227 */
+	0x0000,     /* R228 */
+	0x0000,     /* R229 */
+	0x0000,     /* R230 */
+	0x0000,     /* R231 */
+	0x0000,     /* R232 */
+	0x0000,     /* R233 */
+	0x0000,     /* R234 */
+	0x0000,     /* R235 */
+	0x0000,     /* R236 */
+	0x0000,     /* R237 */
+	0x0000,     /* R238 */
+	0x0000,     /* R239 */
+	0x0000,     /* R240 */
+	0x0000,     /* R241 */
+	0x0000,     /* R242 */
+	0x0000,     /* R243 */
+	0x0000,     /* R244 */
+	0x0000,     /* R245 */
+	0x0000,     /* R246 */
+	0x0000,     /* R247 - FLL NCO Test 0 */
+	0x0019,     /* R248 - FLL NCO Test 1 */
+};
+
+static struct {
+	int readable;
+	int writable;
+	int vol;
+} wm8904_access[] = {
+	{ 0xFFFF, 0xFFFF, 1 }, /* R0   - SW Reset and ID */
+	{ 0x0000, 0x0000, 0 }, /* R1   - Revision */
+	{ 0x0000, 0x0000, 0 }, /* R2 */
+	{ 0x0000, 0x0000, 0 }, /* R3 */
+	{ 0x001F, 0x001F, 0 }, /* R4   - Bias Control 0 */
+	{ 0x0047, 0x0047, 0 }, /* R5   - VMID Control 0 */
+	{ 0x007F, 0x007F, 0 }, /* R6   - Mic Bias Control 0 */
+	{ 0xC007, 0xC007, 0 }, /* R7   - Mic Bias Control 1 */
+	{ 0x001E, 0x001E, 0 }, /* R8   - Analogue DAC 0 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R9   - mic Filter Control */
+	{ 0x0001, 0x0001, 0 }, /* R10  - Analogue ADC 0 */
+	{ 0x0000, 0x0000, 0 }, /* R11 */
+	{ 0x0003, 0x0003, 0 }, /* R12  - Power Management 0 */
+	{ 0x0000, 0x0000, 0 }, /* R13 */
+	{ 0x0003, 0x0003, 0 }, /* R14  - Power Management 2 */
+	{ 0x0003, 0x0003, 0 }, /* R15  - Power Management 3 */
+	{ 0x0000, 0x0000, 0 }, /* R16 */
+	{ 0x0000, 0x0000, 0 }, /* R17 */
+	{ 0x000F, 0x000F, 0 }, /* R18  - Power Management 6 */
+	{ 0x0000, 0x0000, 0 }, /* R19 */
+	{ 0x7001, 0x7001, 0 }, /* R20  - Clock Rates 0 */
+	{ 0x3C07, 0x3C07, 0 }, /* R21  - Clock Rates 1 */
+	{ 0xD00F, 0xD00F, 0 }, /* R22  - Clock Rates 2 */
+	{ 0x0000, 0x0000, 0 }, /* R23 */
+	{ 0x1FFF, 0x1FFF, 0 }, /* R24  - Audio Interface 0 */
+	{ 0x3DDF, 0x3DDF, 0 }, /* R25  - Audio Interface 1 */
+	{ 0x0F1F, 0x0F1F, 0 }, /* R26  - Audio Interface 2 */
+	{ 0x0FFF, 0x0FFF, 0 }, /* R27  - Audio Interface 3 */
+	{ 0x0000, 0x0000, 0 }, /* R28 */
+	{ 0x0000, 0x0000, 0 }, /* R29 */
+	{ 0x00FF, 0x01FF, 0 }, /* R30  - DAC Digital Volume Left */
+	{ 0x00FF, 0x01FF, 0 }, /* R31  - DAC Digital Volume Right */
+	{ 0x0FFF, 0x0FFF, 0 }, /* R32  - DAC Digital 0 */
+	{ 0x1E4E, 0x1E4E, 0 }, /* R33  - DAC Digital 1 */
+	{ 0x0000, 0x0000, 0 }, /* R34 */
+	{ 0x0000, 0x0000, 0 }, /* R35 */
+	{ 0x00FF, 0x01FF, 0 }, /* R36  - ADC Digital Volume Left */
+	{ 0x00FF, 0x01FF, 0 }, /* R37  - ADC Digital Volume Right */
+	{ 0x0073, 0x0073, 0 }, /* R38  - ADC Digital 0 */
+	{ 0x1800, 0x1800, 0 }, /* R39  - Digital Microphone 0 */
+	{ 0xDFEF, 0xDFEF, 0 }, /* R40  - DRC 0 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R41  - DRC 1 */
+	{ 0x003F, 0x003F, 0 }, /* R42  - DRC 2 */
+	{ 0x07FF, 0x07FF, 0 }, /* R43  - DRC 3 */
+	{ 0x009F, 0x009F, 0 }, /* R44  - Analogue Left Input 0 */
+	{ 0x009F, 0x009F, 0 }, /* R45  - Analogue Right Input 0 */
+	{ 0x007F, 0x007F, 0 }, /* R46  - Analogue Left Input 1 */
+	{ 0x007F, 0x007F, 0 }, /* R47  - Analogue Right Input 1 */
+	{ 0x0000, 0x0000, 0 }, /* R48 */
+	{ 0x0000, 0x0000, 0 }, /* R49 */
+	{ 0x0000, 0x0000, 0 }, /* R50 */
+	{ 0x0000, 0x0000, 0 }, /* R51 */
+	{ 0x0000, 0x0000, 0 }, /* R52 */
+	{ 0x0000, 0x0000, 0 }, /* R53 */
+	{ 0x0000, 0x0000, 0 }, /* R54 */
+	{ 0x0000, 0x0000, 0 }, /* R55 */
+	{ 0x0000, 0x0000, 0 }, /* R56 */
+	{ 0x017F, 0x01FF, 0 }, /* R57  - Analogue OUT1 Left */
+	{ 0x017F, 0x01FF, 0 }, /* R58  - Analogue OUT1 Right */
+	{ 0x017F, 0x01FF, 0 }, /* R59  - Analogue OUT2 Left */
+	{ 0x017F, 0x01FF, 0 }, /* R60  - Analogue OUT2 Right */
+	{ 0x000F, 0x000F, 0 }, /* R61  - Analogue OUT12 ZC */
+	{ 0x0000, 0x0000, 0 }, /* R62 */
+	{ 0x0000, 0x0000, 0 }, /* R63 */
+	{ 0x0000, 0x0000, 0 }, /* R64 */
+	{ 0x0000, 0x0000, 0 }, /* R65 */
+	{ 0x0000, 0x0000, 0 }, /* R66 */
+	{ 0x000F, 0x000F, 0 }, /* R67  - DC Servo 0 */
+	{ 0xFFFF, 0xFFFF, 1 }, /* R68  - DC Servo 1 */
+	{ 0x0F0F, 0x0F0F, 0 }, /* R69  - DC Servo 2 */
+	{ 0x0000, 0x0000, 0 }, /* R70 */
+	{ 0x007F, 0x007F, 0 }, /* R71  - DC Servo 4 */
+	{ 0x007F, 0x007F, 0 }, /* R72  - DC Servo 5 */
+	{ 0x00FF, 0x00FF, 1 }, /* R73  - DC Servo 6 */
+	{ 0x00FF, 0x00FF, 1 }, /* R74  - DC Servo 7 */
+	{ 0x00FF, 0x00FF, 1 }, /* R75  - DC Servo 8 */
+	{ 0x00FF, 0x00FF, 1 }, /* R76  - DC Servo 9 */
+	{ 0x0FFF, 0x0000, 1 }, /* R77  - DC Servo Readback 0 */
+	{ 0x0000, 0x0000, 0 }, /* R78 */
+	{ 0x0000, 0x0000, 0 }, /* R79 */
+	{ 0x0000, 0x0000, 0 }, /* R80 */
+	{ 0x0000, 0x0000, 0 }, /* R81 */
+	{ 0x0000, 0x0000, 0 }, /* R82 */
+	{ 0x0000, 0x0000, 0 }, /* R83 */
+	{ 0x0000, 0x0000, 0 }, /* R84 */
+	{ 0x0000, 0x0000, 0 }, /* R85 */
+	{ 0x0000, 0x0000, 0 }, /* R86 */
+	{ 0x0000, 0x0000, 0 }, /* R87 */
+	{ 0x0000, 0x0000, 0 }, /* R88 */
+	{ 0x0000, 0x0000, 0 }, /* R89 */
+	{ 0x00FF, 0x00FF, 0 }, /* R90  - Analogue HP 0 */
+	{ 0x0000, 0x0000, 0 }, /* R91 */
+	{ 0x0000, 0x0000, 0 }, /* R92 */
+	{ 0x0000, 0x0000, 0 }, /* R93 */
+	{ 0x00FF, 0x00FF, 0 }, /* R94  - Analogue Lineout 0 */
+	{ 0x0000, 0x0000, 0 }, /* R95 */
+	{ 0x0000, 0x0000, 0 }, /* R96 */
+	{ 0x0000, 0x0000, 0 }, /* R97 */
+	{ 0x0001, 0x0001, 0 }, /* R98  - Charge Pump 0 */
+	{ 0x0000, 0x0000, 0 }, /* R99 */
+	{ 0x0000, 0x0000, 0 }, /* R100 */
+	{ 0x0000, 0x0000, 0 }, /* R101 */
+	{ 0x0000, 0x0000, 0 }, /* R102 */
+	{ 0x0000, 0x0000, 0 }, /* R103 */
+	{ 0x0001, 0x0001, 0 }, /* R104 - Class W 0 */
+	{ 0x0000, 0x0000, 0 }, /* R105 */
+	{ 0x0000, 0x0000, 0 }, /* R106 */
+	{ 0x0000, 0x0000, 0 }, /* R107 */
+	{ 0x011F, 0x011F, 0 }, /* R108 - Write Sequencer 0 */
+	{ 0x7FFF, 0x7FFF, 0 }, /* R109 - Write Sequencer 1 */
+	{ 0x4FFF, 0x4FFF, 0 }, /* R110 - Write Sequencer 2 */
+	{ 0x003F, 0x033F, 0 }, /* R111 - Write Sequencer 3 */
+	{ 0x03F1, 0x0000, 0 }, /* R112 - Write Sequencer 4 */
+	{ 0x0000, 0x0000, 0 }, /* R113 */
+	{ 0x0000, 0x0000, 0 }, /* R114 */
+	{ 0x0000, 0x0000, 0 }, /* R115 */
+	{ 0x0007, 0x0007, 0 }, /* R116 - FLL Control 1 */
+	{ 0x3F77, 0x3F77, 0 }, /* R117 - FLL Control 2 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R118 - FLL Control 3 */
+	{ 0x7FEF, 0x7FEF, 0 }, /* R119 - FLL Control 4 */
+	{ 0x001B, 0x001B, 0 }, /* R120 - FLL Control 5 */
+	{ 0x003F, 0x003F, 0 }, /* R121 - GPIO Control 1 */
+	{ 0x003F, 0x003F, 0 }, /* R122 - GPIO Control 2 */
+	{ 0x003F, 0x003F, 0 }, /* R123 - GPIO Control 3 */
+	{ 0x038F, 0x038F, 0 }, /* R124 - GPIO Control 4 */
+	{ 0x0000, 0x0000, 0 }, /* R125 */
+	{ 0x00FF, 0x00FF, 0 }, /* R126 - Digital Pulls */
+	{ 0x07FF, 0x03FF, 1 }, /* R127 - Interrupt Status */
+	{ 0x03FF, 0x03FF, 0 }, /* R128 - Interrupt Status Mask */
+	{ 0x03FF, 0x03FF, 0 }, /* R129 - Interrupt Polarity */
+	{ 0x03FF, 0x03FF, 0 }, /* R130 - Interrupt Debounce */
+	{ 0x0000, 0x0000, 0 }, /* R131 */
+	{ 0x0000, 0x0000, 0 }, /* R132 */
+	{ 0x0000, 0x0000, 0 }, /* R133 */
+	{ 0x0001, 0x0001, 0 }, /* R134 - EQ1 */
+	{ 0x001F, 0x001F, 0 }, /* R135 - EQ2 */
+	{ 0x001F, 0x001F, 0 }, /* R136 - EQ3 */
+	{ 0x001F, 0x001F, 0 }, /* R137 - EQ4 */
+	{ 0x001F, 0x001F, 0 }, /* R138 - EQ5 */
+	{ 0x001F, 0x001F, 0 }, /* R139 - EQ6 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R140 - EQ7 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R141 - EQ8 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R142 - EQ9 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R143 - EQ10 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R144 - EQ11 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R145 - EQ12 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R146 - EQ13 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R147 - EQ14 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R148 - EQ15 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R149 - EQ16 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R150 - EQ17 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R151wm8523_dai - EQ18 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R152 - EQ19 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R153 - EQ20 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R154 - EQ21 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R155 - EQ22 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R156 - EQ23 */
+	{ 0xFFFF, 0xFFFF, 0 }, /* R157 - EQ24 */
+	{ 0x0000, 0x0000, 0 }, /* R158 */
+	{ 0x0000, 0x0000, 0 }, /* R159 */
+	{ 0x0000, 0x0000, 0 }, /* R160 */
+	{ 0x0002, 0x0002, 0 }, /* R161 - Control Interface Test 1 */
+	{ 0x0000, 0x0000, 0 }, /* R162 */
+	{ 0x0000, 0x0000, 0 }, /* R163 */
+	{ 0x0000, 0x0000, 0 }, /* R164 */
+	{ 0x0000, 0x0000, 0 }, /* R165 */
+	{ 0x0000, 0x0000, 0 }, /* R166 */
+	{ 0x0000, 0x0000, 0 }, /* R167 */
+	{ 0x0000, 0x0000, 0 }, /* R168 */
+	{ 0x0000, 0x0000, 0 }, /* R169 */
+	{ 0x0000, 0x0000, 0 }, /* R170 */
+	{ 0x0000, 0x0000, 0 }, /* R171 */
+	{ 0x0000, 0x0000, 0 }, /* R172 */
+	{ 0x0000, 0x0000, 0 }, /* R173 */
+	{ 0x0000, 0x0000, 0 }, /* R174 */
+	{ 0x0000, 0x0000, 0 }, /* R175 */
+	{ 0x0000, 0x0000, 0 }, /* R176 */
+	{ 0x0000, 0x0000, 0 }, /* R177 */
+	{ 0x0000, 0x0000, 0 }, /* R178 */
+	{ 0x0000, 0x0000, 0 }, /* R179 */
+	{ 0x0000, 0x0000, 0 }, /* R180 */
+	{ 0x0000, 0x0000, 0 }, /* R181 */
+	{ 0x0000, 0x0000, 0 }, /* R182 */
+	{ 0x0000, 0x0000, 0 }, /* R183 */
+	{ 0x0000, 0x0000, 0 }, /* R184 */
+	{ 0x0000, 0x0000, 0 }, /* R185 */
+	{ 0x0000, 0x0000, 0 }, /* R186 */
+	{ 0x0000, 0x0000, 0 }, /* R187 */
+	{ 0x0000, 0x0000, 0 }, /* R188 */
+	{ 0x0000, 0x0000, 0 }, /* R189 */
+	{ 0x0000, 0x0000, 0 }, /* R190 */
+	{ 0x0000, 0x0000, 0 }, /* R191 */
+	{ 0x0000, 0x0000, 0 }, /* R192 */
+	{ 0x0000, 0x0000, 0 }, /* R193 */
+	{ 0x0000, 0x0000, 0 }, /* R194 */
+	{ 0x0000, 0x0000, 0 }, /* R195 */
+	{ 0x0000, 0x0000, 0 }, /* R196 */
+	{ 0x0000, 0x0000, 0 }, /* R197 */
+	{ 0x0000, 0x0000, 0 }, /* R198 */
+	{ 0x0000, 0x0000, 0 }, /* R199 */
+	{ 0x0000, 0x0000, 0 }, /* R200 */
+	{ 0x0000, 0x0000, 0 }, /* R201 */
+	{ 0x0000, 0x0000, 0 }, /* R202 */
+	{ 0x0000, 0x0000, 0 }, /* R203 */
+	{ 0x0070, 0x0070, 0 }, /* R204 - Analogue Output Bias 0 */
+	{ 0x0000, 0x0000, 0 }, /* R205 */
+	{ 0x0000, 0x0000, 0 }, /* R206 */
+	{ 0x0000, 0x0000, 0 }, /* R207 */
+	{ 0x0000, 0x0000, 0 }, /* R208 */
+	{ 0x0000, 0x0000, 0 }, /* R209 */
+	{ 0x0000, 0x0000, 0 }, /* R210 */
+	{ 0x0000, 0x0000, 0 }, /* R211 */
+	{ 0x0000, 0x0000, 0 }, /* R212 */
+	{ 0x0000, 0x0000, 0 }, /* R213 */
+	{ 0x0000, 0x0000, 0 }, /* R214 */
+	{ 0x0000, 0x0000, 0 }, /* R215 */
+	{ 0x0000, 0x0000, 0 }, /* R216 */
+	{ 0x0000, 0x0000, 0 }, /* R217 */
+	{ 0x0000, 0x0000, 0 }, /* R218 */
+	{ 0x0000, 0x0000, 0 }, /* R219 */
+	{ 0x0000, 0x0000, 0 }, /* R220 */
+	{ 0x0000, 0x0000, 0 }, /* R221 */
+	{ 0x0000, 0x0000, 0 }, /* R222 */
+	{ 0x0000, 0x0000, 0 }, /* R223 */
+	{ 0x0000, 0x0000, 0 }, /* R224 */
+	{ 0x0000, 0x0000, 0 }, /* R225 */
+	{ 0x0000, 0x0000, 0 }, /* R226 */
+	{ 0x0000, 0x0000, 0 }, /* R227 */
+	{ 0x0000, 0x0000, 0 }, /* R228 */
+	{ 0x0000, 0x0000, 0 }, /* R229 */
+	{ 0x0000, 0x0000, 0 }, /* R230 */
+	{ 0x0000, 0x0000, 0 }, /* R231 */
+	{ 0x0000, 0x0000, 0 }, /* R232 */
+	{ 0x0000, 0x0000, 0 }, /* R233 */
+	{ 0x0000, 0x0000, 0 }, /* R234 */
+	{ 0x0000, 0x0000, 0 }, /* R235 */
+	{ 0x0000, 0x0000, 0 }, /* R236 */
+	{ 0x0000, 0x0000, 0 }, /* R237 */
+	{ 0x0000, 0x0000, 0 }, /* R238 */
+	{ 0x0000, 0x0000, 0 }, /* R239 */
+	{ 0x0000, 0x0000, 0 }, /* R240 */
+	{ 0x0000, 0x0000, 0 }, /* R241 */
+	{ 0x0000, 0x0000, 0 }, /* R242 */
+	{ 0x0000, 0x0000, 0 }, /* R243 */
+	{ 0x0000, 0x0000, 0 }, /* R244 */
+	{ 0x0000, 0x0000, 0 }, /* R245 */
+	{ 0x0000, 0x0000, 0 }, /* R246 */
+	{ 0x0001, 0x0001, 0 }, /* R247 - FLL NCO Test 0 */
+	{ 0x003F, 0x003F, 0 }, /* R248 - FLL NCO Test 1 */
+};
+
+static int wm8904_volatile_register(unsigned int reg)
+{
+	return wm8904_access[reg].vol;
+}
+
+static int wm8904_reset(struct snd_soc_codec *codec)
+{
+	return snd_soc_write(codec, WM8904_SW_RESET_AND_ID, 0);
+}
+
+static int wm8904_configure_clocking(struct snd_soc_codec *codec)
+{
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	unsigned int clock0, clock2, rate;
+
+	/* Gate the clock while we're updating to avoid misclocking */
+	clock2 = snd_soc_read(codec, WM8904_CLOCK_RATES_2);
+	snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+			    WM8904_SYSCLK_SRC, 0);
+
+	/* This should be done on init() for bypass paths */
+	switch (wm8904->sysclk_src) {
+	case WM8904_CLK_MCLK:
+		dev_dbg(codec->dev, "Using %dHz MCLK\n", wm8904->mclk_rate);
+
+		clock2 &= ~WM8904_SYSCLK_SRC;
+		rate = wm8904->mclk_rate;
+
+		/* Ensure the FLL is stopped */
+		snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+				    WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+		break;
+
+	case WM8904_CLK_FLL:
+		dev_dbg(codec->dev, "Using %dHz FLL clock\n",
+			wm8904->fll_fout);
+
+		clock2 |= WM8904_SYSCLK_SRC;
+		rate = wm8904->fll_fout;
+		break;
+
+	default:
+		dev_err(codec->dev, "System clock not configured\n");
+		return -EINVAL;
+	}
+
+	/* SYSCLK shouldn't be over 13.5MHz */
+	if (rate > 13500000) {
+		clock0 = WM8904_MCLK_DIV;
+		wm8904->sysclk_rate = rate / 2;
+	} else {
+		clock0 = 0;
+		wm8904->sysclk_rate = rate;
+	}
+
+	snd_soc_update_bits(codec, WM8904_CLOCK_RATES_0, WM8904_MCLK_DIV,
+			    clock0);
+
+	snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+			    WM8904_CLK_SYS_ENA | WM8904_SYSCLK_SRC, clock2);
+
+	dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm8904->sysclk_rate);
+
+	return 0;
+}
+
+static void wm8904_set_drc(struct snd_soc_codec *codec)
+{
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	struct wm8904_pdata *pdata = wm8904->pdata;
+	int save, i;
+
+	/* Save any enables; the configuration should clear them. */
+	save = snd_soc_read(codec, WM8904_DRC_0);
+
+	for (i = 0; i < WM8904_DRC_REGS; i++)
+		snd_soc_update_bits(codec, WM8904_DRC_0 + i, 0xffff,
+				    pdata->drc_cfgs[wm8904->drc_cfg].regs[i]);
+
+	/* Reenable the DRC */
+	snd_soc_update_bits(codec, WM8904_DRC_0,
+			    WM8904_DRC_ENA | WM8904_DRC_DAC_PATH, save);
+}
+
+static int wm8904_put_drc_enum(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	struct wm8904_pdata *pdata = wm8904->pdata;
+	int value = ucontrol->value.integer.value[0];
+
+	if (value >= pdata->num_drc_cfgs)
+		return -EINVAL;
+
+	wm8904->drc_cfg = value;
+
+	wm8904_set_drc(codec);
+
+	return 0;
+}
+
+static int wm8904_get_drc_enum(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+
+	ucontrol->value.enumerated.item[0] = wm8904->drc_cfg;
+
+	return 0;
+}
+
+static void wm8904_set_retune_mobile(struct snd_soc_codec *codec)
+{
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	struct wm8904_pdata *pdata = wm8904->pdata;
+	int best, best_val, save, i, cfg;
+
+	if (!pdata || !wm8904->num_retune_mobile_texts)
+		return;
+
+	/* Find the version of the currently selected configuration
+	 * with the nearest sample rate. */
+	cfg = wm8904->retune_mobile_cfg;
+	best = 0;
+	best_val = INT_MAX;
+	for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+		if (strcmp(pdata->retune_mobile_cfgs[i].name,
+			   wm8904->retune_mobile_texts[cfg]) == 0 &&
+		    abs(pdata->retune_mobile_cfgs[i].rate
+			- wm8904->fs) < best_val) {
+			best = i;
+			best_val = abs(pdata->retune_mobile_cfgs[i].rate
+				       - wm8904->fs);
+		}
+	}
+
+	dev_dbg(codec->dev, "ReTune Mobile %s/%dHz for %dHz sample rate\n",
+		pdata->retune_mobile_cfgs[best].name,
+		pdata->retune_mobile_cfgs[best].rate,
+		wm8904->fs);
+
+	/* The EQ will be disabled while reconfiguring it, remember the
+	 * current configuration. 
+	 */
+	save = snd_soc_read(codec, WM8904_EQ1);
+
+	for (i = 0; i < WM8904_EQ_REGS; i++)
+		snd_soc_update_bits(codec, WM8904_EQ1 + i, 0xffff,
+				pdata->retune_mobile_cfgs[best].regs[i]);
+
+	snd_soc_update_bits(codec, WM8904_EQ1, WM8904_EQ_ENA, save);
+}
+
+static int wm8904_put_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	struct wm8904_pdata *pdata = wm8904->pdata;
+	int value = ucontrol->value.integer.value[0];
+
+	if (value >= pdata->num_retune_mobile_cfgs)
+		return -EINVAL;
+
+	wm8904->retune_mobile_cfg = value;
+
+	wm8904_set_retune_mobile(codec);
+
+	return 0;
+}
+
+static int wm8904_get_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+
+	ucontrol->value.enumerated.item[0] = wm8904->retune_mobile_cfg;
+
+	return 0;
+}
+
+static int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8904_set_deemph(struct snd_soc_codec *codec)
+{
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	int val, i, best;
+
+	/* If we're using deemphasis select the nearest available sample 
+	 * rate.
+	 */
+	if (wm8904->deemph) {
+		best = 1;
+		for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+			if (abs(deemph_settings[i] - wm8904->fs) <
+			    abs(deemph_settings[best] - wm8904->fs))
+				best = i;
+		}
+
+		val = best << WM8904_DEEMPH_SHIFT;
+	} else {
+		val = 0;
+	}
+
+	dev_dbg(codec->dev, "Set deemphasis %d\n", val);
+
+	return snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_1,
+				   WM8904_DEEMPH_MASK, val);
+}
+
+static int wm8904_get_deemph(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+
+	ucontrol->value.enumerated.item[0] = wm8904->deemph;
+	return 0;
+}
+
+static int wm8904_put_deemph(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	int deemph = ucontrol->value.enumerated.item[0];
+
+	if (deemph > 1)
+		return -EINVAL;
+
+	wm8904->deemph = deemph;
+
+	return wm8904_set_deemph(codec);
+}
+
+static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+
+static const char *input_mode_text[] = {
+	"Single-Ended", "Differential Line", "Differential Mic"
+};
+
+static const struct soc_enum lin_mode =
+	SOC_ENUM_SINGLE(WM8904_ANALOGUE_LEFT_INPUT_1, 0, 3, input_mode_text);
+
+static const struct soc_enum rin_mode =
+	SOC_ENUM_SINGLE(WM8904_ANALOGUE_RIGHT_INPUT_1, 0, 3, input_mode_text);
+
+static const char *hpf_mode_text[] = {
+	"Hi-fi", "Voice 1", "Voice 2", "Voice 3"
+};
+
+static const struct soc_enum hpf_mode =
+	SOC_ENUM_SINGLE(WM8904_ADC_DIGITAL_0, 5, 4, hpf_mode_text);
+
+static const struct snd_kcontrol_new wm8904_adc_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8904_ADC_DIGITAL_VOLUME_LEFT,
+		 WM8904_ADC_DIGITAL_VOLUME_RIGHT, 1, 119, 0, digital_tlv),
+
+SOC_ENUM("Left Caputure Mode", lin_mode),
+SOC_ENUM("Right Capture Mode", rin_mode),
+
+/* No TLV since it depends on mode */
+SOC_DOUBLE_R("Capture Volume", WM8904_ANALOGUE_LEFT_INPUT_0,
+	     WM8904_ANALOGUE_RIGHT_INPUT_0, 0, 31, 0),
+SOC_DOUBLE_R("Capture Switch", WM8904_ANALOGUE_LEFT_INPUT_0,
+	     WM8904_ANALOGUE_RIGHT_INPUT_0, 7, 1, 0),
+
+SOC_SINGLE("High Pass Filter Switch", WM8904_ADC_DIGITAL_0, 4, 1, 0),
+SOC_ENUM("High Pass Filter Mode", hpf_mode),
+
+SOC_SINGLE("ADC 128x OSR Switch", WM8904_ANALOGUE_ADC_0, 0, 1, 0),
+};
+
+static const char *drc_path_text[] = {
+	"ADC", "DAC"
+};
+
+static const struct soc_enum drc_path =
+	SOC_ENUM_SINGLE(WM8904_DRC_0, 14, 2, drc_path_text);
+
+static const struct snd_kcontrol_new wm8904_dac_snd_controls[] = {
+SOC_SINGLE_TLV("Digital Playback Boost Volume", 
+	       WM8904_AUDIO_INTERFACE_0, 9, 3, 0, dac_boost_tlv),
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8904_DAC_DIGITAL_VOLUME_LEFT,
+		 WM8904_DAC_DIGITAL_VOLUME_RIGHT, 1, 96, 0, digital_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Volume", WM8904_ANALOGUE_OUT1_LEFT,
+		 WM8904_ANALOGUE_OUT1_RIGHT, 0, 63, 0, out_tlv),
+SOC_DOUBLE_R("Headphone Switch", WM8904_ANALOGUE_OUT1_LEFT,
+	     WM8904_ANALOGUE_OUT1_RIGHT, 8, 1, 1),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8904_ANALOGUE_OUT1_LEFT,
+	     WM8904_ANALOGUE_OUT1_RIGHT, 6, 1, 0),
+
+SOC_DOUBLE_R_TLV("Line Output Volume", WM8904_ANALOGUE_OUT2_LEFT,
+		 WM8904_ANALOGUE_OUT2_RIGHT, 0, 63, 0, out_tlv),
+SOC_DOUBLE_R("Line Output Switch", WM8904_ANALOGUE_OUT2_LEFT,
+	     WM8904_ANALOGUE_OUT2_RIGHT, 8, 1, 1),
+SOC_DOUBLE_R("Line Output ZC Switch", WM8904_ANALOGUE_OUT2_LEFT,
+	     WM8904_ANALOGUE_OUT2_RIGHT, 6, 1, 0),
+
+SOC_SINGLE("EQ Switch", WM8904_EQ1, 0, 1, 0),
+SOC_SINGLE("DRC Switch", WM8904_DRC_0, 15, 1, 0),
+SOC_ENUM("DRC Path", drc_path),
+SOC_SINGLE("DAC OSRx2 Switch", WM8904_DAC_DIGITAL_1, 6, 1, 0),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+		    wm8904_get_deemph, wm8904_put_deemph),
+};
+
+static const struct snd_kcontrol_new wm8904_snd_controls[] = {
+SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8904_DAC_DIGITAL_0, 4, 8, 15, 0,
+	       sidetone_tlv),
+};
+
+static const struct snd_kcontrol_new wm8904_eq_controls[] = {
+SOC_SINGLE_TLV("EQ1 Volume", WM8904_EQ2, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ2 Volume", WM8904_EQ3, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ3 Volume", WM8904_EQ4, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ4 Volume", WM8904_EQ5, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ5 Volume", WM8904_EQ6, 0, 24, 0, eq_tlv),
+};
+
+static int cp_event(struct snd_soc_dapm_widget *w,
+		    struct snd_kcontrol *kcontrol, int event)
+{
+	BUG_ON(event != SND_SOC_DAPM_POST_PMU);
+
+	/* Maximum startup time */
+	udelay(500);
+
+	return 0;
+}
+
+static int sysclk_event(struct snd_soc_dapm_widget *w,
+			 struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		/* If we're using the FLL then we only start it when
+		 * required; we assume that the configuration has been
+		 * done previously and all we need to do is kick it
+		 * off.
+		 */
+		switch (wm8904->sysclk_src) {
+		case WM8904_CLK_FLL:
+			snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+					    WM8904_FLL_OSC_ENA,
+					    WM8904_FLL_OSC_ENA);
+
+			snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+					    WM8904_FLL_ENA,
+					    WM8904_FLL_ENA);
+			break;
+
+		default:
+			break;
+		}
+		break;
+
+	case SND_SOC_DAPM_POST_PMD:
+		snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+				    WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+		break;
+	}
+
+	return 0;
+}
+
+static int out_pga_event(struct snd_soc_dapm_widget *w,
+			 struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	int reg, val;
+	int dcs_mask;
+	int dcs_l, dcs_r;
+	int dcs_l_reg, dcs_r_reg;
+	int timeout;
+	int pwr_reg;
+
+	/* This code is shared between HP and LINEOUT; we do all our
+	 * power management in stereo pairs to avoid latency issues so
+	 * we reuse shift to identify which rather than strcmp() the
+	 * name. */
+	reg = w->shift;
+
+	switch (reg) {
+	case WM8904_ANALOGUE_HP_0:
+		pwr_reg = WM8904_POWER_MANAGEMENT_2;
+		dcs_mask = WM8904_DCS_ENA_CHAN_0 | WM8904_DCS_ENA_CHAN_1;
+		dcs_r_reg = WM8904_DC_SERVO_8;
+		dcs_l_reg = WM8904_DC_SERVO_9;
+		dcs_l = 0;
+		dcs_r = 1;
+		break;
+	case WM8904_ANALOGUE_LINEOUT_0:
+		pwr_reg = WM8904_POWER_MANAGEMENT_3;
+		dcs_mask = WM8904_DCS_ENA_CHAN_2 | WM8904_DCS_ENA_CHAN_3;
+		dcs_r_reg = WM8904_DC_SERVO_6;
+		dcs_l_reg = WM8904_DC_SERVO_7;
+		dcs_l = 2;
+		dcs_r = 3;
+		break;
+	default:
+		BUG();
+		return -EINVAL;
+	}
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		/* Power on the PGAs */
+		snd_soc_update_bits(codec, pwr_reg,
+				    WM8904_HPL_PGA_ENA | WM8904_HPR_PGA_ENA,
+				    WM8904_HPL_PGA_ENA | WM8904_HPR_PGA_ENA);
+
+		/* Power on the amplifier */
+		snd_soc_update_bits(codec, reg,
+				    WM8904_HPL_ENA | WM8904_HPR_ENA,
+				    WM8904_HPL_ENA | WM8904_HPR_ENA);
+
+
+		/* Enable the first stage */
+		snd_soc_update_bits(codec, reg,
+				    WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY,
+				    WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY);
+
+		/* Power up the DC servo */
+		snd_soc_update_bits(codec, WM8904_DC_SERVO_0,
+				    dcs_mask, dcs_mask);
+
+		/* Either calibrate the DC servo or restore cached state
+		 * if we have that.
+		 */
+		if (wm8904->dcs_state[dcs_l] || wm8904->dcs_state[dcs_r]) {
+			dev_dbg(codec->dev, "Restoring DC servo state\n");
+
+			snd_soc_write(codec, dcs_l_reg,
+				      wm8904->dcs_state[dcs_l]);
+			snd_soc_write(codec, dcs_r_reg,
+				      wm8904->dcs_state[dcs_r]);
+
+			snd_soc_write(codec, WM8904_DC_SERVO_1, dcs_mask);
+
+			timeout = 20;
+		} else {
+			dev_dbg(codec->dev, "Calibrating DC servo\n");
+
+			snd_soc_write(codec, WM8904_DC_SERVO_1,
+				dcs_mask << WM8904_DCS_TRIG_STARTUP_0_SHIFT);
+
+			timeout = 500;
+		}
+
+		/* Wait for DC servo to complete */
+		dcs_mask <<= WM8904_DCS_CAL_COMPLETE_SHIFT;
+		do {
+			val = snd_soc_read(codec, WM8904_DC_SERVO_READBACK_0);
+			if ((val & dcs_mask) == dcs_mask)
+				break;
+
+			msleep(1);
+		} while (--timeout);
+
+		if ((val & dcs_mask) != dcs_mask)
+			dev_warn(codec->dev, "DC servo timed out\n");
+		else
+			dev_dbg(codec->dev, "DC servo ready\n");
+
+		/* Enable the output stage */
+		snd_soc_update_bits(codec, reg,
+				    WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP,
+				    WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP);
+		break;
+
+	case SND_SOC_DAPM_POST_PMU:
+		/* Unshort the output itself */
+		snd_soc_update_bits(codec, reg,
+				    WM8904_HPL_RMV_SHORT |
+				    WM8904_HPR_RMV_SHORT,
+				    WM8904_HPL_RMV_SHORT |
+				    WM8904_HPR_RMV_SHORT);
+
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		/* Short the output */
+		snd_soc_update_bits(codec, reg,
+				    WM8904_HPL_RMV_SHORT |
+				    WM8904_HPR_RMV_SHORT, 0);
+		break;
+
+	case SND_SOC_DAPM_POST_PMD:
+		/* Cache the DC servo configuration; this will be
+		 * invalidated if we change the configuration. */
+		wm8904->dcs_state[dcs_l] = snd_soc_read(codec, dcs_l_reg);
+		wm8904->dcs_state[dcs_r] = snd_soc_read(codec, dcs_r_reg);
+
+		snd_soc_update_bits(codec, WM8904_DC_SERVO_0,
+				    dcs_mask, 0);
+
+		/* Disable the amplifier input and output stages */
+		snd_soc_update_bits(codec, reg,
+				    WM8904_HPL_ENA | WM8904_HPR_ENA |
+				    WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY |
+				    WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP,
+				    0);
+
+		/* PGAs too */
+		snd_soc_update_bits(codec, pwr_reg,
+				    WM8904_HPL_PGA_ENA | WM8904_HPR_PGA_ENA,
+				    0);
+		break;
+	}
+
+	return 0;
+}
+
+static const char *lin_text[] = {
+	"IN1L", "IN2L", "IN3L"
+};
+
+static const struct soc_enum lin_enum =
+	SOC_ENUM_SINGLE(WM8904_ANALOGUE_LEFT_INPUT_1, 2, 3, lin_text);
+
+static const struct snd_kcontrol_new lin_mux =
+	SOC_DAPM_ENUM("Left Capture Mux", lin_enum);
+
+static const struct soc_enum lin_inv_enum =
+	SOC_ENUM_SINGLE(WM8904_ANALOGUE_LEFT_INPUT_1, 4, 3, lin_text);
+
+static const struct snd_kcontrol_new lin_inv_mux =
+	SOC_DAPM_ENUM("Left Capture Inveting Mux", lin_inv_enum);
+
+static const char *rin_text[] = {
+	"IN1R", "IN2R", "IN3R"
+};
+
+static const struct soc_enum rin_enum =
+	SOC_ENUM_SINGLE(WM8904_ANALOGUE_RIGHT_INPUT_1, 2, 3, rin_text);
+
+static const struct snd_kcontrol_new rin_mux =
+	SOC_DAPM_ENUM("Right Capture Mux", rin_enum);
+
+static const struct soc_enum rin_inv_enum =
+	SOC_ENUM_SINGLE(WM8904_ANALOGUE_RIGHT_INPUT_1, 4, 3, rin_text);
+
+static const struct snd_kcontrol_new rin_inv_mux =
+	SOC_DAPM_ENUM("Right Capture Inveting Mux", rin_inv_enum);
+
+static const char *aif_text[] = {
+	"Left", "Right"
+};
+
+static const struct soc_enum aifoutl_enum =
+	SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 7, 2, aif_text);
+
+static const struct snd_kcontrol_new aifoutl_mux =
+	SOC_DAPM_ENUM("AIFOUTL Mux", aifoutl_enum);
+
+static const struct soc_enum aifoutr_enum =
+	SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 6, 2, aif_text);
+
+static const struct snd_kcontrol_new aifoutr_mux =
+	SOC_DAPM_ENUM("AIFOUTR Mux", aifoutr_enum);
+
+static const struct soc_enum aifinl_enum =
+	SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 5, 2, aif_text);
+
+static const struct snd_kcontrol_new aifinl_mux =
+	SOC_DAPM_ENUM("AIFINL Mux", aifinl_enum);
+
+static const struct soc_enum aifinr_enum =
+	SOC_ENUM_SINGLE(WM8904_AUDIO_INTERFACE_0, 4, 2, aif_text);
+
+static const struct snd_kcontrol_new aifinr_mux =
+	SOC_DAPM_ENUM("AIFINR Mux", aifinr_enum);
+
+static const struct snd_soc_dapm_widget wm8904_core_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("SYSCLK", WM8904_CLOCK_RATES_2, 2, 0, sysclk_event,
+		    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8904_CLOCK_RATES_2, 1, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("TOCLK", WM8904_CLOCK_RATES_2, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8904_adc_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1L"),
+SND_SOC_DAPM_INPUT("IN1R"),
+SND_SOC_DAPM_INPUT("IN2L"),
+SND_SOC_DAPM_INPUT("IN2R"),
+SND_SOC_DAPM_INPUT("IN3L"),
+SND_SOC_DAPM_INPUT("IN3R"),
+
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8904_MIC_BIAS_CONTROL_0, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lin_mux),
+SND_SOC_DAPM_MUX("Left Capture Inverting Mux", SND_SOC_NOPM, 0, 0,
+		 &lin_inv_mux),
+SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rin_mux),
+SND_SOC_DAPM_MUX("Right Capture Inverting Mux", SND_SOC_NOPM, 0, 0,
+		 &rin_inv_mux),
+
+SND_SOC_DAPM_PGA("Left Capture PGA", WM8904_POWER_MANAGEMENT_0, 1, 0,
+		 NULL, 0),
+SND_SOC_DAPM_PGA("Right Capture PGA", WM8904_POWER_MANAGEMENT_0, 0, 0,
+		 NULL, 0),
+
+SND_SOC_DAPM_ADC("ADCL", NULL, WM8904_POWER_MANAGEMENT_6, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, WM8904_POWER_MANAGEMENT_6, 0, 0),
+
+SND_SOC_DAPM_MUX("AIFOUTL Mux", SND_SOC_NOPM, 0, 0, &aifoutl_mux),
+SND_SOC_DAPM_MUX("AIFOUTR Mux", SND_SOC_NOPM, 0, 0, &aifoutr_mux),
+
+SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8904_dac_dapm_widgets[] = {
+SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &aifinl_mux),
+SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &aifinr_mux),
+
+SND_SOC_DAPM_DAC("DACL", NULL, WM8904_POWER_MANAGEMENT_6, 3, 0),
+SND_SOC_DAPM_DAC("DACR", NULL, WM8904_POWER_MANAGEMENT_6, 2, 0),
+
+SND_SOC_DAPM_SUPPLY("Charge pump", WM8904_CHARGE_PUMP_0, 0, 0, cp_event,
+		    SND_SOC_DAPM_POST_PMU),
+
+SND_SOC_DAPM_PGA("HPL PGA", SND_SOC_NOPM, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("HPR PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("LINEL PGA", SND_SOC_NOPM, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LINER PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA_E("Headphone Output", SND_SOC_NOPM, WM8904_ANALOGUE_HP_0,
+		   0, NULL, 0, out_pga_event,
+		   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+		   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_PGA_E("Line Output", SND_SOC_NOPM, WM8904_ANALOGUE_LINEOUT_0,
+		   0, NULL, 0, out_pga_event,
+		   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+		   SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+SND_SOC_DAPM_OUTPUT("LINEOUTL"),
+SND_SOC_DAPM_OUTPUT("LINEOUTR"),
+};
+
+static const char *out_mux_text[] = {
+	"DAC", "Bypass"
+};
+
+static const struct soc_enum hpl_enum =
+	SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 3, 2, out_mux_text);
+
+static const struct snd_kcontrol_new hpl_mux =
+	SOC_DAPM_ENUM("HPL Mux", hpl_enum);
+
+static const struct soc_enum hpr_enum =
+	SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 2, 2, out_mux_text);
+
+static const struct snd_kcontrol_new hpr_mux =
+	SOC_DAPM_ENUM("HPR Mux", hpr_enum);
+
+static const struct soc_enum linel_enum =
+	SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 1, 2, out_mux_text);
+
+static const struct snd_kcontrol_new linel_mux =
+	SOC_DAPM_ENUM("LINEL Mux", linel_enum);
+
+static const struct soc_enum liner_enum =
+	SOC_ENUM_SINGLE(WM8904_ANALOGUE_OUT12_ZC, 0, 2, out_mux_text);
+
+static const struct snd_kcontrol_new liner_mux =
+	SOC_DAPM_ENUM("LINEL Mux", liner_enum);
+
+static const char *sidetone_text[] = {
+	"None", "Left", "Right"
+};
+
+static const struct soc_enum dacl_sidetone_enum =
+	SOC_ENUM_SINGLE(WM8904_DAC_DIGITAL_0, 2, 3, sidetone_text);
+
+static const struct snd_kcontrol_new dacl_sidetone_mux =
+	SOC_DAPM_ENUM("Left Sidetone Mux", dacl_sidetone_enum);
+
+static const struct soc_enum dacr_sidetone_enum =
+	SOC_ENUM_SINGLE(WM8904_DAC_DIGITAL_0, 0, 3, sidetone_text);
+
+static const struct snd_kcontrol_new dacr_sidetone_mux =
+	SOC_DAPM_ENUM("Right Sidetone Mux", dacr_sidetone_enum);
+
+static const struct snd_soc_dapm_widget wm8904_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("Class G", WM8904_CLASS_W_0, 0, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Left Bypass", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Bypass", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &dacl_sidetone_mux),
+SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &dacr_sidetone_mux),
+
+SND_SOC_DAPM_MUX("HPL Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
+SND_SOC_DAPM_MUX("HPR Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
+SND_SOC_DAPM_MUX("LINEL Mux", SND_SOC_NOPM, 0, 0, &linel_mux),
+SND_SOC_DAPM_MUX("LINER Mux", SND_SOC_NOPM, 0, 0, &liner_mux),
+};
+
+static const struct snd_soc_dapm_route core_intercon[] = {
+	{ "CLK_DSP", NULL, "SYSCLK" },
+	{ "TOCLK", NULL, "SYSCLK" },
+};
+
+static const struct snd_soc_dapm_route adc_intercon[] = {
+	{ "Left Capture Mux", "IN1L", "IN1L" },
+	{ "Left Capture Mux", "IN2L", "IN2L" },
+	{ "Left Capture Mux", "IN3L", "IN3L" },
+
+	{ "Left Capture Inverting Mux", "IN1L", "IN1L" },
+	{ "Left Capture Inverting Mux", "IN2L", "IN2L" },
+	{ "Left Capture Inverting Mux", "IN3L", "IN3L" },
+
+	{ "Right Capture Mux", "IN1R", "IN1R" },
+	{ "Right Capture Mux", "IN2R", "IN2R" },
+	{ "Right Capture Mux", "IN3R", "IN3R" },
+
+	{ "Right Capture Inverting Mux", "IN1R", "IN1R" },
+	{ "Right Capture Inverting Mux", "IN2R", "IN2R" },
+	{ "Right Capture Inverting Mux", "IN3R", "IN3R" },
+
+	{ "Left Capture PGA", NULL, "Left Capture Mux" },
+	{ "Left Capture PGA", NULL, "Left Capture Inverting Mux" },
+
+	{ "Right Capture PGA", NULL, "Right Capture Mux" },
+	{ "Right Capture PGA", NULL, "Right Capture Inverting Mux" },
+
+	{ "AIFOUTL", "Left",  "ADCL" },
+	{ "AIFOUTL", "Right", "ADCR" },
+	{ "AIFOUTR", "Left",  "ADCL" },
+	{ "AIFOUTR", "Right", "ADCR" },
+
+	{ "ADCL", NULL, "CLK_DSP" },
+	{ "ADCL", NULL, "Left Capture PGA" },
+
+	{ "ADCR", NULL, "CLK_DSP" },
+	{ "ADCR", NULL, "Right Capture PGA" },
+};
+
+static const struct snd_soc_dapm_route dac_intercon[] = {
+	{ "DACL", "Right", "AIFINR" },
+	{ "DACL", "Left",  "AIFINL" },
+	{ "DACL", NULL, "CLK_DSP" },
+
+	{ "DACR", "Right", "AIFINR" },
+	{ "DACR", "Left",  "AIFINL" },
+	{ "DACR", NULL, "CLK_DSP" },
+
+	{ "Charge pump", NULL, "SYSCLK" },
+
+	{ "Headphone Output", NULL, "HPL PGA" },
+	{ "Headphone Output", NULL, "HPR PGA" },
+	{ "Headphone Output", NULL, "Charge pump" },
+	{ "Headphone Output", NULL, "TOCLK" },
+
+	{ "Line Output", NULL, "LINEL PGA" },
+	{ "Line Output", NULL, "LINER PGA" },
+	{ "Line Output", NULL, "Charge pump" },
+	{ "Line Output", NULL, "TOCLK" },
+
+	{ "HPOUTL", NULL, "Headphone Output" },
+	{ "HPOUTR", NULL, "Headphone Output" },
+
+	{ "LINEOUTL", NULL, "Line Output" },
+	{ "LINEOUTR", NULL, "Line Output" },
+};
+
+static const struct snd_soc_dapm_route wm8904_intercon[] = {
+	{ "Left Sidetone", "Left", "ADCL" },
+	{ "Left Sidetone", "Right", "ADCR" },
+	{ "DACL", NULL, "Left Sidetone" },
+	
+	{ "Right Sidetone", "Left", "ADCL" },
+	{ "Right Sidetone", "Right", "ADCR" },
+	{ "DACR", NULL, "Right Sidetone" },
+
+	{ "Left Bypass", NULL, "Class G" },
+	{ "Left Bypass", NULL, "Left Capture PGA" },
+
+	{ "Right Bypass", NULL, "Class G" },
+	{ "Right Bypass", NULL, "Right Capture PGA" },
+
+	{ "HPL Mux", "DAC", "DACL" },
+	{ "HPL Mux", "Bypass", "Left Bypass" },
+
+	{ "HPR Mux", "DAC", "DACR" },
+	{ "HPR Mux", "Bypass", "Right Bypass" },
+
+	{ "LINEL Mux", "DAC", "DACL" },
+	{ "LINEL Mux", "Bypass", "Left Bypass" },
+
+	{ "LINER Mux", "DAC", "DACR" },
+	{ "LINER Mux", "Bypass", "Right Bypass" },
+
+	{ "HPL PGA", NULL, "HPL Mux" },
+	{ "HPR PGA", NULL, "HPR Mux" },
+
+	{ "LINEL PGA", NULL, "LINEL Mux" },
+	{ "LINER PGA", NULL, "LINER Mux" },
+};
+
+static const struct snd_soc_dapm_route wm8912_intercon[] = {
+	{ "HPL PGA", NULL, "DACL" },
+	{ "HPR PGA", NULL, "DACR" },
+
+	{ "LINEL PGA", NULL, "DACL" },
+	{ "LINER PGA", NULL, "DACR" },
+};
+
+static int wm8904_add_widgets(struct snd_soc_codec *codec)
+{
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+
+	snd_soc_dapm_new_controls(codec, wm8904_core_dapm_widgets,
+				  ARRAY_SIZE(wm8904_core_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, core_intercon,
+				ARRAY_SIZE(core_intercon));
+
+	switch (wm8904->devtype) {
+	case WM8904:
+		snd_soc_add_controls(codec, wm8904_adc_snd_controls,
+				     ARRAY_SIZE(wm8904_adc_snd_controls));
+		snd_soc_add_controls(codec, wm8904_dac_snd_controls,
+				     ARRAY_SIZE(wm8904_dac_snd_controls));
+		snd_soc_add_controls(codec, wm8904_snd_controls,
+				     ARRAY_SIZE(wm8904_snd_controls));
+
+		snd_soc_dapm_new_controls(codec, wm8904_adc_dapm_widgets,
+					  ARRAY_SIZE(wm8904_adc_dapm_widgets));
+		snd_soc_dapm_new_controls(codec, wm8904_dac_dapm_widgets,
+					  ARRAY_SIZE(wm8904_dac_dapm_widgets));
+		snd_soc_dapm_new_controls(codec, wm8904_dapm_widgets,
+					  ARRAY_SIZE(wm8904_dapm_widgets));
+
+		snd_soc_dapm_add_routes(codec, core_intercon,
+					ARRAY_SIZE(core_intercon));
+		snd_soc_dapm_add_routes(codec, adc_intercon,
+					ARRAY_SIZE(adc_intercon));
+		snd_soc_dapm_add_routes(codec, dac_intercon,
+					ARRAY_SIZE(dac_intercon));
+		snd_soc_dapm_add_routes(codec, wm8904_intercon,
+					ARRAY_SIZE(wm8904_intercon));
+		break;
+
+	case WM8912:
+		snd_soc_add_controls(codec, wm8904_dac_snd_controls,
+				     ARRAY_SIZE(wm8904_dac_snd_controls));
+
+		snd_soc_dapm_new_controls(codec, wm8904_dac_dapm_widgets,
+					  ARRAY_SIZE(wm8904_dac_dapm_widgets));
+
+		snd_soc_dapm_add_routes(codec, dac_intercon,
+					ARRAY_SIZE(dac_intercon));
+		snd_soc_dapm_add_routes(codec, wm8912_intercon,
+					ARRAY_SIZE(wm8912_intercon));
+		break;
+	}
+
+	snd_soc_dapm_new_widgets(codec);
+	return 0;
+}
+
+static struct {
+	int ratio;
+	unsigned int clk_sys_rate;
+} clk_sys_rates[] = {
+	{   64,  0 },
+	{  128,  1 },
+	{  192,  2 },
+	{  256,  3 },
+	{  384,  4 },
+	{  512,  5 },
+	{  786,  6 },
+	{ 1024,  7 },
+	{ 1408,  8 },
+	{ 1536,  9 },
+};
+
+static struct {
+	int rate;
+	int sample_rate;
+} sample_rates[] = {
+	{ 8000,  0  },
+	{ 11025, 1  },
+	{ 12000, 1  },
+	{ 16000, 2  },
+	{ 22050, 3  },
+	{ 24000, 3  },
+	{ 32000, 4  },
+	{ 44100, 5  },
+	{ 48000, 5  },
+};
+
+static struct {
+	int div; /* *10 due to .5s */
+	int bclk_div;
+} bclk_divs[] = {
+	{ 10,  0  },
+	{ 15,  1  },
+	{ 20,  2  },
+	{ 30,  3  },
+	{ 40,  4  },
+	{ 50,  5  },
+	{ 55,  6  },
+	{ 60,  7  },
+	{ 80,  8  },
+	{ 100, 9  },
+	{ 110, 10 },
+	{ 120, 11 },
+	{ 160, 12 },
+	{ 200, 13 },
+	{ 220, 14 },
+	{ 240, 16 },
+	{ 200, 17 },
+	{ 320, 18 },
+	{ 440, 19 },
+	{ 480, 20 },
+};
+
+
+static int wm8904_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	int ret, i, best, best_val, cur_val;
+	unsigned int aif1 = 0;
+	unsigned int aif2 = 0;
+	unsigned int aif3 = 0;
+	unsigned int clock1 = 0;
+	unsigned int dac_digital1 = 0;
+
+	/* What BCLK do we need? */
+	wm8904->fs = params_rate(params);
+	if (wm8904->tdm_slots) {
+		dev_dbg(codec->dev, "Configuring for %d %d bit TDM slots\n",
+			wm8904->tdm_slots, wm8904->tdm_width);
+		wm8904->bclk = snd_soc_calc_bclk(wm8904->fs,
+						 wm8904->tdm_width, 2,
+						 wm8904->tdm_slots);
+	} else {
+		wm8904->bclk = snd_soc_params_to_bclk(params);
+	}
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		aif1 |= 0x40;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		aif1 |= 0x80;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		aif1 |= 0xc0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+
+	dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm8904->bclk);
+
+	ret = wm8904_configure_clocking(codec);
+	if (ret != 0)
+		return ret;
+
+	/* Select nearest CLK_SYS_RATE */
+	best = 0;
+	best_val = abs((wm8904->sysclk_rate / clk_sys_rates[0].ratio)
+		       - wm8904->fs);
+	for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
+		cur_val = abs((wm8904->sysclk_rate /
+			       clk_sys_rates[i].ratio) - wm8904->fs);;
+		if (cur_val < best_val) {
+			best = i;
+			best_val = cur_val;
+		}
+	}
+	dev_dbg(codec->dev, "Selected CLK_SYS_RATIO of %d\n",
+		clk_sys_rates[best].ratio);
+	clock1 |= (clk_sys_rates[best].clk_sys_rate
+		   << WM8904_CLK_SYS_RATE_SHIFT);
+
+	/* SAMPLE_RATE */
+	best = 0;
+	best_val = abs(wm8904->fs - sample_rates[0].rate);
+	for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
+		/* Closest match */
+		cur_val = abs(wm8904->fs - sample_rates[i].rate);
+		if (cur_val < best_val) {
+			best = i;
+			best_val = cur_val;
+		}
+	}
+	dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n",
+		sample_rates[best].rate);
+	clock1 |= (sample_rates[best].sample_rate
+		   << WM8904_SAMPLE_RATE_SHIFT);
+
+	/* Enable sloping stopband filter for low sample rates */
+	if (wm8904->fs <= 24000)
+		dac_digital1 |= WM8904_DAC_SB_FILT;
+
+	/* BCLK_DIV */
+	best = 0;
+	best_val = INT_MAX;
+	for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+		cur_val = ((wm8904->sysclk_rate * 10) / bclk_divs[i].div)
+			- wm8904->bclk;
+		if (cur_val < 0) /* Table is sorted */
+			break;
+		if (cur_val < best_val) {
+			best = i;
+			best_val = cur_val;
+		}
+	}
+	wm8904->bclk = (wm8904->sysclk_rate * 10) / bclk_divs[best].div;
+	dev_dbg(codec->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n",
+		bclk_divs[best].div, wm8904->bclk);
+	aif2 |= bclk_divs[best].bclk_div;
+
+	/* LRCLK is a simple fraction of BCLK */
+	dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm8904->bclk / wm8904->fs);
+	aif3 |= wm8904->bclk / wm8904->fs;
+
+	/* Apply the settings */
+	snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_1,
+			    WM8904_DAC_SB_FILT, dac_digital1);
+	snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_1,
+			    WM8904_AIF_WL_MASK, aif1);
+	snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_2,
+			    WM8904_BCLK_DIV_MASK, aif2);
+	snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_3,
+			    WM8904_LRCLK_RATE_MASK, aif3);
+	snd_soc_update_bits(codec, WM8904_CLOCK_RATES_1,
+			    WM8904_SAMPLE_RATE_MASK |
+			    WM8904_CLK_SYS_RATE_MASK, clock1);
+
+	/* Update filters for the new settings */
+	wm8904_set_retune_mobile(codec);
+	wm8904_set_deemph(codec);
+
+	return 0;
+}
+
+
+static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+			     unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8904_priv *priv = snd_soc_codec_get_drvdata(codec);
+
+	switch (clk_id) {
+	case WM8904_CLK_MCLK:
+		priv->sysclk_src = clk_id;
+		priv->mclk_rate = freq;
+		break;
+
+	case WM8904_CLK_FLL:
+		priv->sysclk_src = clk_id;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+	wm8904_configure_clocking(codec);
+
+	return 0;
+}
+
+static int wm8904_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	unsigned int aif1 = 0;
+	unsigned int aif3 = 0;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		aif3 |= WM8904_LRCLK_DIR;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		aif1 |= WM8904_BCLK_DIR;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		aif1 |= WM8904_BCLK_DIR;
+		aif3 |= WM8904_LRCLK_DIR;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_B:
+		aif1 |= WM8904_AIF_LRCLK_INV;
+	case SND_SOC_DAIFMT_DSP_A:
+		aif1 |= 0x3;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		aif1 |= 0x2;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		aif1 |= 0x1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		/* frame inversion not valid for DSP modes */
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif1 |= WM8904_AIF_BCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_LEFT_J:
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			aif1 |= WM8904_AIF_BCLK_INV | WM8904_AIF_LRCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif1 |= WM8904_AIF_BCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			aif1 |= WM8904_AIF_LRCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_1,
+			    WM8904_AIF_BCLK_INV | WM8904_AIF_LRCLK_INV |
+			    WM8904_AIF_FMT_MASK | WM8904_BCLK_DIR, aif1);
+	snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_3,
+			    WM8904_LRCLK_DIR, aif3);
+
+	return 0;
+}
+
+
+static int wm8904_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+			       unsigned int rx_mask, int slots, int slot_width)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	int aif1 = 0;
+
+	/* Don't need to validate anything if we're turning off TDM */
+	if (slots == 0)
+		goto out;
+
+	/* Note that we allow configurations we can't handle ourselves - 
+	 * for example, we can generate clocks for slots 2 and up even if
+	 * we can't use those slots ourselves.
+	 */
+	aif1 |= WM8904_AIFADC_TDM | WM8904_AIFDAC_TDM;
+
+	switch (rx_mask) {
+	case 3:
+		break;
+	case 0xc:
+		aif1 |= WM8904_AIFADC_TDM_CHAN;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+
+	switch (tx_mask) {
+	case 3:
+		break;
+	case 0xc:
+		aif1 |= WM8904_AIFDAC_TDM_CHAN;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+out:
+	wm8904->tdm_width = slot_width;
+	wm8904->tdm_slots = slots / 2;
+
+	snd_soc_update_bits(codec, WM8904_AUDIO_INTERFACE_1,
+			    WM8904_AIFADC_TDM | WM8904_AIFADC_TDM_CHAN |
+			    WM8904_AIFDAC_TDM | WM8904_AIFDAC_TDM_CHAN, aif1);
+
+	return 0;
+}
+
+struct _fll_div {
+	u16 fll_fratio;
+	u16 fll_outdiv;
+	u16 fll_clk_ref_div;
+	u16 n;
+	u16 k;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static struct {
+	unsigned int min;
+	unsigned int max;
+	u16 fll_fratio;
+	int ratio;
+} fll_fratios[] = {
+	{       0,    64000, 4, 16 },
+	{   64000,   128000, 3,  8 },
+	{  128000,   256000, 2,  4 },
+	{  256000,  1000000, 1,  2 },
+	{ 1000000, 13500000, 0,  1 },
+};
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+		       unsigned int Fout)
+{
+	u64 Kpart;
+	unsigned int K, Ndiv, Nmod, target;
+	unsigned int div;
+	int i;
+
+	/* Fref must be <=13.5MHz */
+	div = 1;
+	fll_div->fll_clk_ref_div = 0;
+	while ((Fref / div) > 13500000) {
+		div *= 2;
+		fll_div->fll_clk_ref_div++;
+
+		if (div > 8) {
+			pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
+			       Fref);
+			return -EINVAL;
+		}
+	}
+
+	pr_debug("Fref=%u Fout=%u\n", Fref, Fout);
+
+	/* Apply the division for our remaining calculations */
+	Fref /= div;
+
+	/* Fvco should be 90-100MHz; don't check the upper bound */
+	div = 4;
+	while (Fout * div < 90000000) {
+		div++;
+		if (div > 64) {
+			pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
+			       Fout);
+			return -EINVAL;
+		}
+	}
+	target = Fout * div;
+	fll_div->fll_outdiv = div - 1;
+
+	pr_debug("Fvco=%dHz\n", target);
+
+	/* Find an appropraite FLL_FRATIO and factor it out of the target */
+	for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
+		if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
+			fll_div->fll_fratio = fll_fratios[i].fll_fratio;
+			target /= fll_fratios[i].ratio;
+			break;
+		}
+	}
+	if (i == ARRAY_SIZE(fll_fratios)) {
+		pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
+		return -EINVAL;
+	}
+
+	/* Now, calculate N.K */
+	Ndiv = target / Fref;
+
+	fll_div->n = Ndiv;
+	Nmod = target % Fref;
+	pr_debug("Nmod=%d\n", Nmod);
+
+	/* Calculate fractional part - scale up so we can round. */
+	Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, Fref);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	fll_div->k = K / 10;
+
+	pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n",
+		 fll_div->n, fll_div->k,
+		 fll_div->fll_fratio, fll_div->fll_outdiv,
+		 fll_div->fll_clk_ref_div);
+
+	return 0;
+}
+
+static int wm8904_set_fll(struct snd_soc_dai *dai, int fll_id, int source,
+			  unsigned int Fref, unsigned int Fout)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	struct _fll_div fll_div;
+	int ret, val;
+	int clock2, fll1;
+
+	/* Any change? */
+	if (source == wm8904->fll_src && Fref == wm8904->fll_fref &&
+	    Fout == wm8904->fll_fout)
+		return 0;
+
+	clock2 = snd_soc_read(codec, WM8904_CLOCK_RATES_2);
+
+	if (Fout == 0) {
+		dev_dbg(codec->dev, "FLL disabled\n");
+
+		wm8904->fll_fref = 0;
+		wm8904->fll_fout = 0;
+
+		/* Gate SYSCLK to avoid glitches */
+		snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+				    WM8904_CLK_SYS_ENA, 0);
+
+		snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+				    WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+
+		goto out;
+	}
+
+	/* Validate the FLL ID */
+	switch (source) {
+	case WM8904_FLL_MCLK:
+	case WM8904_FLL_LRCLK:
+	case WM8904_FLL_BCLK:
+		ret = fll_factors(&fll_div, Fref, Fout);
+		if (ret != 0)
+			return ret;
+		break;
+
+	case WM8904_FLL_FREE_RUNNING:
+		dev_dbg(codec->dev, "Using free running FLL\n");
+		/* Force 12MHz and output/4 for now */
+		Fout = 12000000;
+		Fref = 12000000;
+
+		memset(&fll_div, 0, sizeof(fll_div));
+		fll_div.fll_outdiv = 3;
+		break;
+
+	default:
+		dev_err(codec->dev, "Unknown FLL ID %d\n", fll_id);
+		return -EINVAL;
+	}
+
+	/* Save current state then disable the FLL and SYSCLK to avoid
+	 * misclocking */
+	fll1 = snd_soc_read(codec, WM8904_FLL_CONTROL_1);
+	snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+			    WM8904_CLK_SYS_ENA, 0);
+	snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+			    WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+
+	/* Unlock forced oscilator control to switch it on/off */
+	snd_soc_update_bits(codec, WM8904_CONTROL_INTERFACE_TEST_1,
+			    WM8904_USER_KEY, WM8904_USER_KEY);
+
+	if (fll_id == WM8904_FLL_FREE_RUNNING) {
+		val = WM8904_FLL_FRC_NCO;
+	} else {
+		val = 0;
+	}
+
+	snd_soc_update_bits(codec, WM8904_FLL_NCO_TEST_1, WM8904_FLL_FRC_NCO,
+			    val);
+	snd_soc_update_bits(codec, WM8904_CONTROL_INTERFACE_TEST_1,
+			    WM8904_USER_KEY, 0);
+
+	switch (fll_id) {
+	case WM8904_FLL_MCLK:
+		snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+				    WM8904_FLL_CLK_REF_SRC_MASK, 0);
+		break;
+
+	case WM8904_FLL_LRCLK:
+		snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+				    WM8904_FLL_CLK_REF_SRC_MASK, 1);
+		break;
+
+	case WM8904_FLL_BCLK:
+		snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+				    WM8904_FLL_CLK_REF_SRC_MASK, 2);
+		break;
+	}
+
+	if (fll_div.k)
+		val = WM8904_FLL_FRACN_ENA;
+	else
+		val = 0;
+	snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+			    WM8904_FLL_FRACN_ENA, val);
+
+	snd_soc_update_bits(codec, WM8904_FLL_CONTROL_2,
+			    WM8904_FLL_OUTDIV_MASK | WM8904_FLL_FRATIO_MASK,
+			    (fll_div.fll_outdiv << WM8904_FLL_OUTDIV_SHIFT) |
+			    (fll_div.fll_fratio << WM8904_FLL_FRATIO_SHIFT));
+
+	snd_soc_write(codec, WM8904_FLL_CONTROL_3, fll_div.k);
+
+	snd_soc_update_bits(codec, WM8904_FLL_CONTROL_4, WM8904_FLL_N_MASK,
+			    fll_div.n << WM8904_FLL_N_SHIFT);
+
+	snd_soc_update_bits(codec, WM8904_FLL_CONTROL_5,
+			    WM8904_FLL_CLK_REF_DIV_MASK,
+			    fll_div.fll_clk_ref_div 
+			    << WM8904_FLL_CLK_REF_DIV_SHIFT);
+
+	dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout);
+
+	wm8904->fll_fref = Fref;
+	wm8904->fll_fout = Fout;
+	wm8904->fll_src = source;
+
+	/* Enable the FLL if it was previously active */
+	snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+			    WM8904_FLL_OSC_ENA, fll1);
+	snd_soc_update_bits(codec, WM8904_FLL_CONTROL_1,
+			    WM8904_FLL_ENA, fll1);
+
+out:
+	/* Reenable SYSCLK if it was previously active */
+	snd_soc_update_bits(codec, WM8904_CLOCK_RATES_2,
+			    WM8904_CLK_SYS_ENA, clock2);
+
+	return 0;
+}
+
+static int wm8904_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	int val;
+
+	if (mute)
+		val = WM8904_DAC_MUTE;
+	else
+		val = 0;
+
+	snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_1, WM8904_DAC_MUTE, val);
+
+	return 0;
+}
+
+static void wm8904_sync_cache(struct snd_soc_codec *codec)
+{
+	u16 *reg_cache = codec->reg_cache;
+	int i;
+
+	if (!codec->cache_sync)
+		return;
+
+	codec->cache_only = 0;
+
+	/* Sync back cached values if they're different from the
+	 * hardware default.
+	 */
+	for (i = 1; i < codec->driver->reg_cache_size; i++) {
+		if (!wm8904_access[i].writable)
+			continue;
+
+		if (reg_cache[i] == wm8904_reg[i])
+			continue;
+
+		snd_soc_write(codec, i, reg_cache[i]);
+	}
+
+	codec->cache_sync = 0;
+}
+
+static int wm8904_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		/* VMID resistance 2*50k */
+		snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+				    WM8904_VMID_RES_MASK,
+				    0x1 << WM8904_VMID_RES_SHIFT);
+
+		/* Normal bias current */
+		snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+				    WM8904_ISEL_MASK, 2 << WM8904_ISEL_SHIFT);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			ret = regulator_bulk_enable(ARRAY_SIZE(wm8904->supplies),
+						    wm8904->supplies);
+			if (ret != 0) {
+				dev_err(codec->dev,
+					"Failed to enable supplies: %d\n",
+					ret);
+				return ret;
+			}
+
+			wm8904_sync_cache(codec);
+
+			/* Enable bias */
+			snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+					    WM8904_BIAS_ENA, WM8904_BIAS_ENA);
+
+			/* Enable VMID, VMID buffering, 2*5k resistance */
+			snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+					    WM8904_VMID_ENA |
+					    WM8904_VMID_RES_MASK,
+					    WM8904_VMID_ENA |
+					    0x3 << WM8904_VMID_RES_SHIFT);
+
+			/* Let VMID ramp */
+			msleep(1);
+		}
+
+		/* Maintain VMID with 2*250k */
+		snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+				    WM8904_VMID_RES_MASK,
+				    0x2 << WM8904_VMID_RES_SHIFT);
+
+		/* Bias current *0.5 */
+		snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+				    WM8904_ISEL_MASK, 0);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		/* Turn off VMID */
+		snd_soc_update_bits(codec, WM8904_VMID_CONTROL_0,
+				    WM8904_VMID_RES_MASK | WM8904_VMID_ENA, 0);
+
+		/* Stop bias generation */
+		snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+				    WM8904_BIAS_ENA, 0);
+
+#ifdef CONFIG_REGULATOR
+		/* Post 2.6.34 we will be able to get a callback when
+		 * the regulators are disabled which we can use but
+		 * for now just assume that the power will be cut if
+		 * the regulator API is in use.
+		 */
+		codec->cache_sync = 1;
+#endif
+
+		regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies),
+				       wm8904->supplies);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8904_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8904_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8904_dai_ops = {
+	.set_sysclk = wm8904_set_sysclk,
+	.set_fmt = wm8904_set_fmt,
+	.set_tdm_slot = wm8904_set_tdm_slot,
+	.set_pll = wm8904_set_fll,
+	.hw_params = wm8904_hw_params,
+	.digital_mute = wm8904_digital_mute,
+};
+
+static struct snd_soc_dai_driver wm8904_dai = {
+	.name = "wm8904-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = WM8904_RATES,
+		.formats = WM8904_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = WM8904_RATES,
+		.formats = WM8904_FORMATS,
+	},
+	.ops = &wm8904_dai_ops,
+	.symmetric_rates = 1,
+};
+
+#ifdef CONFIG_PM
+static int wm8904_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8904_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm8904_resume(struct snd_soc_codec *codec)
+{
+	wm8904_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+#else
+#define wm8904_suspend NULL
+#define wm8904_resume NULL
+#endif
+
+static void wm8904_handle_retune_mobile_pdata(struct snd_soc_codec *codec)
+{
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	struct wm8904_pdata *pdata = wm8904->pdata;
+	struct snd_kcontrol_new control =
+		SOC_ENUM_EXT("EQ Mode",
+			     wm8904->retune_mobile_enum,
+			     wm8904_get_retune_mobile_enum,
+			     wm8904_put_retune_mobile_enum);
+	int ret, i, j;
+	const char **t;
+
+	/* We need an array of texts for the enum API but the number
+	 * of texts is likely to be less than the number of
+	 * configurations due to the sample rate dependency of the
+	 * configurations. */
+	wm8904->num_retune_mobile_texts = 0;
+	wm8904->retune_mobile_texts = NULL;
+	for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+		for (j = 0; j < wm8904->num_retune_mobile_texts; j++) {
+			if (strcmp(pdata->retune_mobile_cfgs[i].name,
+				   wm8904->retune_mobile_texts[j]) == 0)
+				break;
+		}
+
+		if (j != wm8904->num_retune_mobile_texts)
+			continue;
+
+		/* Expand the array... */
+		t = krealloc(wm8904->retune_mobile_texts,
+			     sizeof(char *) * 
+			     (wm8904->num_retune_mobile_texts + 1),
+			     GFP_KERNEL);
+		if (t == NULL)
+			continue;
+
+		/* ...store the new entry... */
+		t[wm8904->num_retune_mobile_texts] = 
+			pdata->retune_mobile_cfgs[i].name;
+
+		/* ...and remember the new version. */
+		wm8904->num_retune_mobile_texts++;
+		wm8904->retune_mobile_texts = t;
+	}
+
+	dev_dbg(codec->dev, "Allocated %d unique ReTune Mobile names\n",
+		wm8904->num_retune_mobile_texts);
+
+	wm8904->retune_mobile_enum.max = wm8904->num_retune_mobile_texts;
+	wm8904->retune_mobile_enum.texts = wm8904->retune_mobile_texts;
+
+	ret = snd_soc_add_controls(codec, &control, 1);
+	if (ret != 0)
+		dev_err(codec->dev,
+			"Failed to add ReTune Mobile control: %d\n", ret);
+}
+
+static void wm8904_handle_pdata(struct snd_soc_codec *codec)
+{
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	struct wm8904_pdata *pdata = wm8904->pdata;
+	int ret, i;
+
+	if (!pdata) {
+		snd_soc_add_controls(codec, wm8904_eq_controls,
+				     ARRAY_SIZE(wm8904_eq_controls));
+		return;
+	}
+
+	dev_dbg(codec->dev, "%d DRC configurations\n", pdata->num_drc_cfgs);
+
+	if (pdata->num_drc_cfgs) {
+		struct snd_kcontrol_new control =
+			SOC_ENUM_EXT("DRC Mode", wm8904->drc_enum,
+				     wm8904_get_drc_enum, wm8904_put_drc_enum);
+
+		/* We need an array of texts for the enum API */
+		wm8904->drc_texts = kmalloc(sizeof(char *)
+					    * pdata->num_drc_cfgs, GFP_KERNEL);
+		if (!wm8904->drc_texts) {
+			dev_err(codec->dev,
+				"Failed to allocate %d DRC config texts\n",
+				pdata->num_drc_cfgs);
+			return;
+		}
+
+		for (i = 0; i < pdata->num_drc_cfgs; i++)
+			wm8904->drc_texts[i] = pdata->drc_cfgs[i].name;
+
+		wm8904->drc_enum.max = pdata->num_drc_cfgs;
+		wm8904->drc_enum.texts = wm8904->drc_texts;
+
+		ret = snd_soc_add_controls(codec, &control, 1);
+		if (ret != 0)
+			dev_err(codec->dev,
+				"Failed to add DRC mode control: %d\n", ret);
+
+		wm8904_set_drc(codec);
+	}
+
+	dev_dbg(codec->dev, "%d ReTune Mobile configurations\n",
+		pdata->num_retune_mobile_cfgs);
+
+	if (pdata->num_retune_mobile_cfgs)
+		wm8904_handle_retune_mobile_pdata(codec);
+	else
+		snd_soc_add_controls(codec, wm8904_eq_controls,
+				     ARRAY_SIZE(wm8904_eq_controls));
+}
+
+
+static int wm8904_probe(struct snd_soc_codec *codec)
+{
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+	struct wm8904_pdata *pdata = wm8904->pdata;
+	u16 *reg_cache = codec->reg_cache;
+	int ret, i;
+
+	codec->cache_sync = 1;
+	codec->idle_bias_off = 1;
+
+	switch (wm8904->devtype) {
+	case WM8904:
+		break;
+	case WM8912:
+		memset(&wm8904_dai.capture, 0, sizeof(wm8904_dai.capture));
+		break;
+	default:
+		dev_err(codec->dev, "Unknown device type %d\n",
+			wm8904->devtype);
+		return -EINVAL;
+	}
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm8904->supplies); i++)
+		wm8904->supplies[i].supply = wm8904_supply_names[i];
+
+	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8904->supplies),
+				 wm8904->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+		return ret;
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(wm8904->supplies),
+				    wm8904->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+		goto err_get;
+	}
+
+	ret = snd_soc_read(codec, WM8904_SW_RESET_AND_ID);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to read ID register\n");
+		goto err_enable;
+	}
+	if (ret != wm8904_reg[WM8904_SW_RESET_AND_ID]) {
+		dev_err(codec->dev, "Device is not a WM8904, ID is %x\n", ret);
+		ret = -EINVAL;
+		goto err_enable;
+	}
+
+	ret = snd_soc_read(codec, WM8904_REVISION);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to read device revision: %d\n",
+			ret);
+		goto err_enable;
+	}
+	dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+	ret = wm8904_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		goto err_enable;
+	}
+
+	/* Change some default settings - latch VU and enable ZC */
+	reg_cache[WM8904_ADC_DIGITAL_VOLUME_LEFT] |= WM8904_ADC_VU;
+	reg_cache[WM8904_ADC_DIGITAL_VOLUME_RIGHT] |= WM8904_ADC_VU;
+	reg_cache[WM8904_DAC_DIGITAL_VOLUME_LEFT] |= WM8904_DAC_VU;
+	reg_cache[WM8904_DAC_DIGITAL_VOLUME_RIGHT] |= WM8904_DAC_VU;
+	reg_cache[WM8904_ANALOGUE_OUT1_LEFT] |= WM8904_HPOUT_VU |
+		WM8904_HPOUTLZC;
+	reg_cache[WM8904_ANALOGUE_OUT1_RIGHT] |= WM8904_HPOUT_VU |
+		WM8904_HPOUTRZC;
+	reg_cache[WM8904_ANALOGUE_OUT2_LEFT] |= WM8904_LINEOUT_VU |
+		WM8904_LINEOUTLZC;
+	reg_cache[WM8904_ANALOGUE_OUT2_RIGHT] |= WM8904_LINEOUT_VU |
+		WM8904_LINEOUTRZC;
+	reg_cache[WM8904_CLOCK_RATES_0] &= ~WM8904_SR_MODE;
+
+	/* Apply configuration from the platform data. */
+	if (wm8904->pdata) {
+		for (i = 0; i < WM8904_GPIO_REGS; i++) {
+			if (!pdata->gpio_cfg[i])
+				continue;
+
+			reg_cache[WM8904_GPIO_CONTROL_1 + i]
+				= pdata->gpio_cfg[i] & 0xffff;
+		}
+
+		/* Zero is the default value for these anyway */
+		for (i = 0; i < WM8904_MIC_REGS; i++)
+			reg_cache[WM8904_MIC_BIAS_CONTROL_0 + i]
+				= pdata->mic_cfg[i];
+	}
+
+	/* Set Class W by default - this will be managed by the Class
+	 * G widget at runtime where bypass paths are available.
+	 */
+	reg_cache[WM8904_CLASS_W_0] |= WM8904_CP_DYN_PWR;
+
+	/* Use normal bias source */
+	reg_cache[WM8904_BIAS_CONTROL_0] &= ~WM8904_POBCTRL;
+
+	wm8904_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Bias level configuration will have done an extra enable */
+	regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+
+	wm8904_handle_pdata(codec);
+
+	wm8904_add_widgets(codec);
+
+	return 0;
+
+err_enable:
+	regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+err_get:
+	regulator_bulk_free(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+	return ret;
+}
+
+static int wm8904_remove(struct snd_soc_codec *codec)
+{
+	struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec);
+
+	wm8904_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	regulator_bulk_free(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
+	kfree(wm8904->retune_mobile_texts);
+	kfree(wm8904->drc_texts);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8904 = {
+	.probe =	wm8904_probe,
+	.remove =	wm8904_remove,
+	.suspend =	wm8904_suspend,
+	.resume =	wm8904_resume,
+	.set_bias_level = wm8904_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8904_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8904_reg,
+	.volatile_register = wm8904_volatile_register,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8904_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8904_priv *wm8904;
+	int ret;
+
+	wm8904 = kzalloc(sizeof(struct wm8904_priv), GFP_KERNEL);
+	if (wm8904 == NULL)
+		return -ENOMEM;
+
+	wm8904->devtype = id->driver_data;
+	i2c_set_clientdata(i2c, wm8904);
+	wm8904->control_data = i2c;
+	wm8904->pdata = i2c->dev.platform_data;
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8904, &wm8904_dai, 1);
+	if (ret < 0)
+		kfree(wm8904);
+	return ret;
+}
+
+static __devexit int wm8904_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8904_i2c_id[] = {
+	{ "wm8904", WM8904 },
+	{ "wm8912", WM8912 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8904_i2c_id);
+
+static struct i2c_driver wm8904_i2c_driver = {
+	.driver = {
+		.name = "wm8904-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8904_i2c_probe,
+	.remove =   __devexit_p(wm8904_i2c_remove),
+	.id_table = wm8904_i2c_id,
+};
+#endif
+
+static int __init wm8904_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8904_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8904 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8904_modinit);
+
+static void __exit wm8904_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8904_i2c_driver);
+#endif
+}
+module_exit(wm8904_exit);
+
+MODULE_DESCRIPTION("ASoC WM8904 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8904.h b/linux/sound/soc/codecs/wm8904.h
new file mode 100644
index 0000000..9e8c841
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8904.h
@@ -0,0 +1,1581 @@
+/*
+ * wm8904.h  --  WM8904 ASoC driver
+ *
+ * Copyright 2009 Wolfson Microelectronics, plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8904_H
+#define _WM8904_H
+
+#define WM8904_CLK_MCLK 1
+#define WM8904_CLK_FLL  2
+
+#define WM8904_FLL_MCLK          1
+#define WM8904_FLL_BCLK          2
+#define WM8904_FLL_LRCLK         3
+#define WM8904_FLL_FREE_RUNNING  4
+
+/*
+ * Register values.
+ */
+#define WM8904_SW_RESET_AND_ID                  0x00
+#define WM8904_REVISION				0x01
+#define WM8904_BIAS_CONTROL_0                   0x04
+#define WM8904_VMID_CONTROL_0                   0x05
+#define WM8904_MIC_BIAS_CONTROL_0               0x06
+#define WM8904_MIC_BIAS_CONTROL_1               0x07
+#define WM8904_ANALOGUE_DAC_0                   0x08
+#define WM8904_MIC_FILTER_CONTROL               0x09
+#define WM8904_ANALOGUE_ADC_0                   0x0A
+#define WM8904_POWER_MANAGEMENT_0               0x0C
+#define WM8904_POWER_MANAGEMENT_2               0x0E
+#define WM8904_POWER_MANAGEMENT_3               0x0F
+#define WM8904_POWER_MANAGEMENT_6               0x12
+#define WM8904_CLOCK_RATES_0                    0x14
+#define WM8904_CLOCK_RATES_1                    0x15
+#define WM8904_CLOCK_RATES_2                    0x16
+#define WM8904_AUDIO_INTERFACE_0                0x18
+#define WM8904_AUDIO_INTERFACE_1                0x19
+#define WM8904_AUDIO_INTERFACE_2                0x1A
+#define WM8904_AUDIO_INTERFACE_3                0x1B
+#define WM8904_DAC_DIGITAL_VOLUME_LEFT          0x1E
+#define WM8904_DAC_DIGITAL_VOLUME_RIGHT         0x1F
+#define WM8904_DAC_DIGITAL_0                    0x20
+#define WM8904_DAC_DIGITAL_1                    0x21
+#define WM8904_ADC_DIGITAL_VOLUME_LEFT          0x24
+#define WM8904_ADC_DIGITAL_VOLUME_RIGHT         0x25
+#define WM8904_ADC_DIGITAL_0                    0x26
+#define WM8904_DIGITAL_MICROPHONE_0             0x27
+#define WM8904_DRC_0                            0x28
+#define WM8904_DRC_1                            0x29
+#define WM8904_DRC_2                            0x2A
+#define WM8904_DRC_3                            0x2B
+#define WM8904_ANALOGUE_LEFT_INPUT_0            0x2C
+#define WM8904_ANALOGUE_RIGHT_INPUT_0           0x2D
+#define WM8904_ANALOGUE_LEFT_INPUT_1            0x2E
+#define WM8904_ANALOGUE_RIGHT_INPUT_1           0x2F
+#define WM8904_ANALOGUE_OUT1_LEFT               0x39
+#define WM8904_ANALOGUE_OUT1_RIGHT              0x3A
+#define WM8904_ANALOGUE_OUT2_LEFT               0x3B
+#define WM8904_ANALOGUE_OUT2_RIGHT              0x3C
+#define WM8904_ANALOGUE_OUT12_ZC                0x3D
+#define WM8904_DC_SERVO_0                       0x43
+#define WM8904_DC_SERVO_1                       0x44
+#define WM8904_DC_SERVO_2                       0x45
+#define WM8904_DC_SERVO_4                       0x47
+#define WM8904_DC_SERVO_5                       0x48
+#define WM8904_DC_SERVO_6                       0x49
+#define WM8904_DC_SERVO_7                       0x4A
+#define WM8904_DC_SERVO_8                       0x4B
+#define WM8904_DC_SERVO_9                       0x4C
+#define WM8904_DC_SERVO_READBACK_0              0x4D
+#define WM8904_ANALOGUE_HP_0                    0x5A
+#define WM8904_ANALOGUE_LINEOUT_0               0x5E
+#define WM8904_CHARGE_PUMP_0                    0x62
+#define WM8904_CLASS_W_0                        0x68
+#define WM8904_WRITE_SEQUENCER_0                0x6C
+#define WM8904_WRITE_SEQUENCER_1                0x6D
+#define WM8904_WRITE_SEQUENCER_2                0x6E
+#define WM8904_WRITE_SEQUENCER_3                0x6F
+#define WM8904_WRITE_SEQUENCER_4                0x70
+#define WM8904_FLL_CONTROL_1                    0x74
+#define WM8904_FLL_CONTROL_2                    0x75
+#define WM8904_FLL_CONTROL_3                    0x76
+#define WM8904_FLL_CONTROL_4                    0x77
+#define WM8904_FLL_CONTROL_5                    0x78
+#define WM8904_GPIO_CONTROL_1                   0x79
+#define WM8904_GPIO_CONTROL_2                   0x7A
+#define WM8904_GPIO_CONTROL_3                   0x7B
+#define WM8904_GPIO_CONTROL_4                   0x7C
+#define WM8904_DIGITAL_PULLS                    0x7E
+#define WM8904_INTERRUPT_STATUS                 0x7F
+#define WM8904_INTERRUPT_STATUS_MASK            0x80
+#define WM8904_INTERRUPT_POLARITY               0x81
+#define WM8904_INTERRUPT_DEBOUNCE               0x82
+#define WM8904_EQ1                              0x86
+#define WM8904_EQ2                              0x87
+#define WM8904_EQ3                              0x88
+#define WM8904_EQ4                              0x89
+#define WM8904_EQ5                              0x8A
+#define WM8904_EQ6                              0x8B
+#define WM8904_EQ7                              0x8C
+#define WM8904_EQ8                              0x8D
+#define WM8904_EQ9                              0x8E
+#define WM8904_EQ10                             0x8F
+#define WM8904_EQ11                             0x90
+#define WM8904_EQ12                             0x91
+#define WM8904_EQ13                             0x92
+#define WM8904_EQ14                             0x93
+#define WM8904_EQ15                             0x94
+#define WM8904_EQ16                             0x95
+#define WM8904_EQ17                             0x96
+#define WM8904_EQ18                             0x97
+#define WM8904_EQ19                             0x98
+#define WM8904_EQ20                             0x99
+#define WM8904_EQ21                             0x9A
+#define WM8904_EQ22                             0x9B
+#define WM8904_EQ23                             0x9C
+#define WM8904_EQ24                             0x9D
+#define WM8904_CONTROL_INTERFACE_TEST_1         0xA1
+#define WM8904_ANALOGUE_OUTPUT_BIAS_0           0xCC
+#define WM8904_FLL_NCO_TEST_0                   0xF7
+#define WM8904_FLL_NCO_TEST_1                   0xF8
+
+#define WM8904_REGISTER_COUNT                   101
+#define WM8904_MAX_REGISTER                     0xF8
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - SW Reset and ID
+ */
+#define WM8904_SW_RST_DEV_ID1_MASK              0xFFFF  /* SW_RST_DEV_ID1 - [15:0] */
+#define WM8904_SW_RST_DEV_ID1_SHIFT                  0  /* SW_RST_DEV_ID1 - [15:0] */
+#define WM8904_SW_RST_DEV_ID1_WIDTH                 16  /* SW_RST_DEV_ID1 - [15:0] */
+
+/*
+ * R1 (0x01) - Revision
+ */
+#define WM8904_REVISION_MASK              	0x000F  /* REVISION - [3:0] */
+#define WM8904_REVISION_SHIFT             	     0  /* REVISION - [3:0] */
+#define WM8904_REVISION_WIDTH             	    16  /* REVISION - [3:0] */
+
+/*
+ * R4 (0x04) - Bias Control 0
+ */
+#define WM8904_POBCTRL                          0x0010  /* POBCTRL */
+#define WM8904_POBCTRL_MASK                     0x0010  /* POBCTRL */
+#define WM8904_POBCTRL_SHIFT                         4  /* POBCTRL */
+#define WM8904_POBCTRL_WIDTH                         1  /* POBCTRL */
+#define WM8904_ISEL_MASK                        0x000C  /* ISEL - [3:2] */
+#define WM8904_ISEL_SHIFT                            2  /* ISEL - [3:2] */
+#define WM8904_ISEL_WIDTH                            2  /* ISEL - [3:2] */
+#define WM8904_STARTUP_BIAS_ENA                 0x0002  /* STARTUP_BIAS_ENA */
+#define WM8904_STARTUP_BIAS_ENA_MASK            0x0002  /* STARTUP_BIAS_ENA */
+#define WM8904_STARTUP_BIAS_ENA_SHIFT                1  /* STARTUP_BIAS_ENA */
+#define WM8904_STARTUP_BIAS_ENA_WIDTH                1  /* STARTUP_BIAS_ENA */
+#define WM8904_BIAS_ENA                         0x0001  /* BIAS_ENA */
+#define WM8904_BIAS_ENA_MASK                    0x0001  /* BIAS_ENA */
+#define WM8904_BIAS_ENA_SHIFT                        0  /* BIAS_ENA */
+#define WM8904_BIAS_ENA_WIDTH                        1  /* BIAS_ENA */
+
+/*
+ * R5 (0x05) - VMID Control 0
+ */
+#define WM8904_VMID_BUF_ENA                     0x0040  /* VMID_BUF_ENA */
+#define WM8904_VMID_BUF_ENA_MASK                0x0040  /* VMID_BUF_ENA */
+#define WM8904_VMID_BUF_ENA_SHIFT                    6  /* VMID_BUF_ENA */
+#define WM8904_VMID_BUF_ENA_WIDTH                    1  /* VMID_BUF_ENA */
+#define WM8904_VMID_RES_MASK                    0x0006  /* VMID_RES - [2:1] */
+#define WM8904_VMID_RES_SHIFT                        1  /* VMID_RES - [2:1] */
+#define WM8904_VMID_RES_WIDTH                        2  /* VMID_RES - [2:1] */
+#define WM8904_VMID_ENA                         0x0001  /* VMID_ENA */
+#define WM8904_VMID_ENA_MASK                    0x0001  /* VMID_ENA */
+#define WM8904_VMID_ENA_SHIFT                        0  /* VMID_ENA */
+#define WM8904_VMID_ENA_WIDTH                        1  /* VMID_ENA */
+
+/*
+ * R8 (0x08) - Analogue DAC 0
+ */
+#define WM8904_DAC_BIAS_SEL_MASK                0x0018  /* DAC_BIAS_SEL - [4:3] */
+#define WM8904_DAC_BIAS_SEL_SHIFT                    3  /* DAC_BIAS_SEL - [4:3] */
+#define WM8904_DAC_BIAS_SEL_WIDTH                    2  /* DAC_BIAS_SEL - [4:3] */
+#define WM8904_DAC_VMID_BIAS_SEL_MASK           0x0006  /* DAC_VMID_BIAS_SEL - [2:1] */
+#define WM8904_DAC_VMID_BIAS_SEL_SHIFT               1  /* DAC_VMID_BIAS_SEL - [2:1] */
+#define WM8904_DAC_VMID_BIAS_SEL_WIDTH               2  /* DAC_VMID_BIAS_SEL - [2:1] */
+
+/*
+ * R9 (0x09) - mic Filter Control
+ */
+#define WM8904_MIC_DET_SET_THRESHOLD_MASK       0xF000  /* MIC_DET_SET_THRESHOLD - [15:12] */
+#define WM8904_MIC_DET_SET_THRESHOLD_SHIFT          12  /* MIC_DET_SET_THRESHOLD - [15:12] */
+#define WM8904_MIC_DET_SET_THRESHOLD_WIDTH           4  /* MIC_DET_SET_THRESHOLD - [15:12] */
+#define WM8904_MIC_DET_RESET_THRESHOLD_MASK     0x0F00  /* MIC_DET_RESET_THRESHOLD - [11:8] */
+#define WM8904_MIC_DET_RESET_THRESHOLD_SHIFT         8  /* MIC_DET_RESET_THRESHOLD - [11:8] */
+#define WM8904_MIC_DET_RESET_THRESHOLD_WIDTH         4  /* MIC_DET_RESET_THRESHOLD - [11:8] */
+#define WM8904_MIC_SHORT_SET_THRESHOLD_MASK     0x00F0  /* MIC_SHORT_SET_THRESHOLD - [7:4] */
+#define WM8904_MIC_SHORT_SET_THRESHOLD_SHIFT         4  /* MIC_SHORT_SET_THRESHOLD - [7:4] */
+#define WM8904_MIC_SHORT_SET_THRESHOLD_WIDTH         4  /* MIC_SHORT_SET_THRESHOLD - [7:4] */
+#define WM8904_MIC_SHORT_RESET_THRESHOLD_MASK   0x000F  /* MIC_SHORT_RESET_THRESHOLD - [3:0] */
+#define WM8904_MIC_SHORT_RESET_THRESHOLD_SHIFT       0  /* MIC_SHORT_RESET_THRESHOLD - [3:0] */
+#define WM8904_MIC_SHORT_RESET_THRESHOLD_WIDTH       4  /* MIC_SHORT_RESET_THRESHOLD - [3:0] */
+
+/*
+ * R10 (0x0A) - Analogue ADC 0
+ */
+#define WM8904_ADC_OSR128                       0x0001  /* ADC_OSR128 */
+#define WM8904_ADC_OSR128_MASK                  0x0001  /* ADC_OSR128 */
+#define WM8904_ADC_OSR128_SHIFT                      0  /* ADC_OSR128 */
+#define WM8904_ADC_OSR128_WIDTH                      1  /* ADC_OSR128 */
+
+/*
+ * R12 (0x0C) - Power Management 0
+ */
+#define WM8904_INL_ENA                          0x0002  /* INL_ENA */
+#define WM8904_INL_ENA_MASK                     0x0002  /* INL_ENA */
+#define WM8904_INL_ENA_SHIFT                         1  /* INL_ENA */
+#define WM8904_INL_ENA_WIDTH                         1  /* INL_ENA */
+#define WM8904_INR_ENA                          0x0001  /* INR_ENA */
+#define WM8904_INR_ENA_MASK                     0x0001  /* INR_ENA */
+#define WM8904_INR_ENA_SHIFT                         0  /* INR_ENA */
+#define WM8904_INR_ENA_WIDTH                         1  /* INR_ENA */
+
+/*
+ * R14 (0x0E) - Power Management 2
+ */
+#define WM8904_HPL_PGA_ENA                      0x0002  /* HPL_PGA_ENA */
+#define WM8904_HPL_PGA_ENA_MASK                 0x0002  /* HPL_PGA_ENA */
+#define WM8904_HPL_PGA_ENA_SHIFT                     1  /* HPL_PGA_ENA */
+#define WM8904_HPL_PGA_ENA_WIDTH                     1  /* HPL_PGA_ENA */
+#define WM8904_HPR_PGA_ENA                      0x0001  /* HPR_PGA_ENA */
+#define WM8904_HPR_PGA_ENA_MASK                 0x0001  /* HPR_PGA_ENA */
+#define WM8904_HPR_PGA_ENA_SHIFT                     0  /* HPR_PGA_ENA */
+#define WM8904_HPR_PGA_ENA_WIDTH                     1  /* HPR_PGA_ENA */
+
+/*
+ * R15 (0x0F) - Power Management 3
+ */
+#define WM8904_LINEOUTL_PGA_ENA                 0x0002  /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTL_PGA_ENA_MASK            0x0002  /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTL_PGA_ENA_SHIFT                1  /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTL_PGA_ENA_WIDTH                1  /* LINEOUTL_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA                 0x0001  /* LINEOUTR_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA_MASK            0x0001  /* LINEOUTR_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA_SHIFT                0  /* LINEOUTR_PGA_ENA */
+#define WM8904_LINEOUTR_PGA_ENA_WIDTH                1  /* LINEOUTR_PGA_ENA */
+
+/*
+ * R18 (0x12) - Power Management 6
+ */
+#define WM8904_DACL_ENA                         0x0008  /* DACL_ENA */
+#define WM8904_DACL_ENA_MASK                    0x0008  /* DACL_ENA */
+#define WM8904_DACL_ENA_SHIFT                        3  /* DACL_ENA */
+#define WM8904_DACL_ENA_WIDTH                        1  /* DACL_ENA */
+#define WM8904_DACR_ENA                         0x0004  /* DACR_ENA */
+#define WM8904_DACR_ENA_MASK                    0x0004  /* DACR_ENA */
+#define WM8904_DACR_ENA_SHIFT                        2  /* DACR_ENA */
+#define WM8904_DACR_ENA_WIDTH                        1  /* DACR_ENA */
+#define WM8904_ADCL_ENA                         0x0002  /* ADCL_ENA */
+#define WM8904_ADCL_ENA_MASK                    0x0002  /* ADCL_ENA */
+#define WM8904_ADCL_ENA_SHIFT                        1  /* ADCL_ENA */
+#define WM8904_ADCL_ENA_WIDTH                        1  /* ADCL_ENA */
+#define WM8904_ADCR_ENA                         0x0001  /* ADCR_ENA */
+#define WM8904_ADCR_ENA_MASK                    0x0001  /* ADCR_ENA */
+#define WM8904_ADCR_ENA_SHIFT                        0  /* ADCR_ENA */
+#define WM8904_ADCR_ENA_WIDTH                        1  /* ADCR_ENA */
+
+/*
+ * R20 (0x14) - Clock Rates 0
+ */
+#define WM8904_TOCLK_RATE_DIV16                 0x4000  /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_DIV16_MASK            0x4000  /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_DIV16_SHIFT               14  /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_DIV16_WIDTH                1  /* TOCLK_RATE_DIV16 */
+#define WM8904_TOCLK_RATE_X4                    0x2000  /* TOCLK_RATE_X4 */
+#define WM8904_TOCLK_RATE_X4_MASK               0x2000  /* TOCLK_RATE_X4 */
+#define WM8904_TOCLK_RATE_X4_SHIFT                  13  /* TOCLK_RATE_X4 */
+#define WM8904_TOCLK_RATE_X4_WIDTH                   1  /* TOCLK_RATE_X4 */
+#define WM8904_SR_MODE                          0x1000  /* SR_MODE */
+#define WM8904_SR_MODE_MASK                     0x1000  /* SR_MODE */
+#define WM8904_SR_MODE_SHIFT                        12  /* SR_MODE */
+#define WM8904_SR_MODE_WIDTH                         1  /* SR_MODE */
+#define WM8904_MCLK_DIV                         0x0001  /* MCLK_DIV */
+#define WM8904_MCLK_DIV_MASK                    0x0001  /* MCLK_DIV */
+#define WM8904_MCLK_DIV_SHIFT                        0  /* MCLK_DIV */
+#define WM8904_MCLK_DIV_WIDTH                        1  /* MCLK_DIV */
+
+/*
+ * R21 (0x15) - Clock Rates 1
+ */
+#define WM8904_CLK_SYS_RATE_MASK                0x3C00  /* CLK_SYS_RATE - [13:10] */
+#define WM8904_CLK_SYS_RATE_SHIFT                   10  /* CLK_SYS_RATE - [13:10] */
+#define WM8904_CLK_SYS_RATE_WIDTH                    4  /* CLK_SYS_RATE - [13:10] */
+#define WM8904_SAMPLE_RATE_MASK                 0x0007  /* SAMPLE_RATE - [2:0] */
+#define WM8904_SAMPLE_RATE_SHIFT                     0  /* SAMPLE_RATE - [2:0] */
+#define WM8904_SAMPLE_RATE_WIDTH                     3  /* SAMPLE_RATE - [2:0] */
+
+/*
+ * R22 (0x16) - Clock Rates 2
+ */
+#define WM8904_MCLK_INV                         0x8000  /* MCLK_INV */
+#define WM8904_MCLK_INV_MASK                    0x8000  /* MCLK_INV */
+#define WM8904_MCLK_INV_SHIFT                       15  /* MCLK_INV */
+#define WM8904_MCLK_INV_WIDTH                        1  /* MCLK_INV */
+#define WM8904_SYSCLK_SRC                       0x4000  /* SYSCLK_SRC */
+#define WM8904_SYSCLK_SRC_MASK                  0x4000  /* SYSCLK_SRC */
+#define WM8904_SYSCLK_SRC_SHIFT                     14  /* SYSCLK_SRC */
+#define WM8904_SYSCLK_SRC_WIDTH                      1  /* SYSCLK_SRC */
+#define WM8904_TOCLK_RATE                       0x1000  /* TOCLK_RATE */
+#define WM8904_TOCLK_RATE_MASK                  0x1000  /* TOCLK_RATE */
+#define WM8904_TOCLK_RATE_SHIFT                     12  /* TOCLK_RATE */
+#define WM8904_TOCLK_RATE_WIDTH                      1  /* TOCLK_RATE */
+#define WM8904_OPCLK_ENA                        0x0008  /* OPCLK_ENA */
+#define WM8904_OPCLK_ENA_MASK                   0x0008  /* OPCLK_ENA */
+#define WM8904_OPCLK_ENA_SHIFT                       3  /* OPCLK_ENA */
+#define WM8904_OPCLK_ENA_WIDTH                       1  /* OPCLK_ENA */
+#define WM8904_CLK_SYS_ENA                      0x0004  /* CLK_SYS_ENA */
+#define WM8904_CLK_SYS_ENA_MASK                 0x0004  /* CLK_SYS_ENA */
+#define WM8904_CLK_SYS_ENA_SHIFT                     2  /* CLK_SYS_ENA */
+#define WM8904_CLK_SYS_ENA_WIDTH                     1  /* CLK_SYS_ENA */
+#define WM8904_CLK_DSP_ENA                      0x0002  /* CLK_DSP_ENA */
+#define WM8904_CLK_DSP_ENA_MASK                 0x0002  /* CLK_DSP_ENA */
+#define WM8904_CLK_DSP_ENA_SHIFT                     1  /* CLK_DSP_ENA */
+#define WM8904_CLK_DSP_ENA_WIDTH                     1  /* CLK_DSP_ENA */
+#define WM8904_TOCLK_ENA                        0x0001  /* TOCLK_ENA */
+#define WM8904_TOCLK_ENA_MASK                   0x0001  /* TOCLK_ENA */
+#define WM8904_TOCLK_ENA_SHIFT                       0  /* TOCLK_ENA */
+#define WM8904_TOCLK_ENA_WIDTH                       1  /* TOCLK_ENA */
+
+/*
+ * R24 (0x18) - Audio Interface 0
+ */
+#define WM8904_DACL_DATINV                      0x1000  /* DACL_DATINV */
+#define WM8904_DACL_DATINV_MASK                 0x1000  /* DACL_DATINV */
+#define WM8904_DACL_DATINV_SHIFT                    12  /* DACL_DATINV */
+#define WM8904_DACL_DATINV_WIDTH                     1  /* DACL_DATINV */
+#define WM8904_DACR_DATINV                      0x0800  /* DACR_DATINV */
+#define WM8904_DACR_DATINV_MASK                 0x0800  /* DACR_DATINV */
+#define WM8904_DACR_DATINV_SHIFT                    11  /* DACR_DATINV */
+#define WM8904_DACR_DATINV_WIDTH                     1  /* DACR_DATINV */
+#define WM8904_DAC_BOOST_MASK                   0x0600  /* DAC_BOOST - [10:9] */
+#define WM8904_DAC_BOOST_SHIFT                       9  /* DAC_BOOST - [10:9] */
+#define WM8904_DAC_BOOST_WIDTH                       2  /* DAC_BOOST - [10:9] */
+#define WM8904_LOOPBACK                         0x0100  /* LOOPBACK */
+#define WM8904_LOOPBACK_MASK                    0x0100  /* LOOPBACK */
+#define WM8904_LOOPBACK_SHIFT                        8  /* LOOPBACK */
+#define WM8904_LOOPBACK_WIDTH                        1  /* LOOPBACK */
+#define WM8904_AIFADCL_SRC                      0x0080  /* AIFADCL_SRC */
+#define WM8904_AIFADCL_SRC_MASK                 0x0080  /* AIFADCL_SRC */
+#define WM8904_AIFADCL_SRC_SHIFT                     7  /* AIFADCL_SRC */
+#define WM8904_AIFADCL_SRC_WIDTH                     1  /* AIFADCL_SRC */
+#define WM8904_AIFADCR_SRC                      0x0040  /* AIFADCR_SRC */
+#define WM8904_AIFADCR_SRC_MASK                 0x0040  /* AIFADCR_SRC */
+#define WM8904_AIFADCR_SRC_SHIFT                     6  /* AIFADCR_SRC */
+#define WM8904_AIFADCR_SRC_WIDTH                     1  /* AIFADCR_SRC */
+#define WM8904_AIFDACL_SRC                      0x0020  /* AIFDACL_SRC */
+#define WM8904_AIFDACL_SRC_MASK                 0x0020  /* AIFDACL_SRC */
+#define WM8904_AIFDACL_SRC_SHIFT                     5  /* AIFDACL_SRC */
+#define WM8904_AIFDACL_SRC_WIDTH                     1  /* AIFDACL_SRC */
+#define WM8904_AIFDACR_SRC                      0x0010  /* AIFDACR_SRC */
+#define WM8904_AIFDACR_SRC_MASK                 0x0010  /* AIFDACR_SRC */
+#define WM8904_AIFDACR_SRC_SHIFT                     4  /* AIFDACR_SRC */
+#define WM8904_AIFDACR_SRC_WIDTH                     1  /* AIFDACR_SRC */
+#define WM8904_ADC_COMP                         0x0008  /* ADC_COMP */
+#define WM8904_ADC_COMP_MASK                    0x0008  /* ADC_COMP */
+#define WM8904_ADC_COMP_SHIFT                        3  /* ADC_COMP */
+#define WM8904_ADC_COMP_WIDTH                        1  /* ADC_COMP */
+#define WM8904_ADC_COMPMODE                     0x0004  /* ADC_COMPMODE */
+#define WM8904_ADC_COMPMODE_MASK                0x0004  /* ADC_COMPMODE */
+#define WM8904_ADC_COMPMODE_SHIFT                    2  /* ADC_COMPMODE */
+#define WM8904_ADC_COMPMODE_WIDTH                    1  /* ADC_COMPMODE */
+#define WM8904_DAC_COMP                         0x0002  /* DAC_COMP */
+#define WM8904_DAC_COMP_MASK                    0x0002  /* DAC_COMP */
+#define WM8904_DAC_COMP_SHIFT                        1  /* DAC_COMP */
+#define WM8904_DAC_COMP_WIDTH                        1  /* DAC_COMP */
+#define WM8904_DAC_COMPMODE                     0x0001  /* DAC_COMPMODE */
+#define WM8904_DAC_COMPMODE_MASK                0x0001  /* DAC_COMPMODE */
+#define WM8904_DAC_COMPMODE_SHIFT                    0  /* DAC_COMPMODE */
+#define WM8904_DAC_COMPMODE_WIDTH                    1  /* DAC_COMPMODE */
+
+/*
+ * R25 (0x19) - Audio Interface 1
+ */
+#define WM8904_AIFDAC_TDM                       0x2000  /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_MASK                  0x2000  /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_SHIFT                     13  /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_WIDTH                      1  /* AIFDAC_TDM */
+#define WM8904_AIFDAC_TDM_CHAN                  0x1000  /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFDAC_TDM_CHAN_MASK             0x1000  /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFDAC_TDM_CHAN_SHIFT                12  /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFDAC_TDM_CHAN_WIDTH                 1  /* AIFDAC_TDM_CHAN */
+#define WM8904_AIFADC_TDM                       0x0800  /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_MASK                  0x0800  /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_SHIFT                     11  /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_WIDTH                      1  /* AIFADC_TDM */
+#define WM8904_AIFADC_TDM_CHAN                  0x0400  /* AIFADC_TDM_CHAN */
+#define WM8904_AIFADC_TDM_CHAN_MASK             0x0400  /* AIFADC_TDM_CHAN */
+#define WM8904_AIFADC_TDM_CHAN_SHIFT                10  /* AIFADC_TDM_CHAN */
+#define WM8904_AIFADC_TDM_CHAN_WIDTH                 1  /* AIFADC_TDM_CHAN */
+#define WM8904_AIF_TRIS                         0x0100  /* AIF_TRIS */
+#define WM8904_AIF_TRIS_MASK                    0x0100  /* AIF_TRIS */
+#define WM8904_AIF_TRIS_SHIFT                        8  /* AIF_TRIS */
+#define WM8904_AIF_TRIS_WIDTH                        1  /* AIF_TRIS */
+#define WM8904_AIF_BCLK_INV                     0x0080  /* AIF_BCLK_INV */
+#define WM8904_AIF_BCLK_INV_MASK                0x0080  /* AIF_BCLK_INV */
+#define WM8904_AIF_BCLK_INV_SHIFT                    7  /* AIF_BCLK_INV */
+#define WM8904_AIF_BCLK_INV_WIDTH                    1  /* AIF_BCLK_INV */
+#define WM8904_BCLK_DIR                         0x0040  /* BCLK_DIR */
+#define WM8904_BCLK_DIR_MASK                    0x0040  /* BCLK_DIR */
+#define WM8904_BCLK_DIR_SHIFT                        6  /* BCLK_DIR */
+#define WM8904_BCLK_DIR_WIDTH                        1  /* BCLK_DIR */
+#define WM8904_AIF_LRCLK_INV                    0x0010  /* AIF_LRCLK_INV */
+#define WM8904_AIF_LRCLK_INV_MASK               0x0010  /* AIF_LRCLK_INV */
+#define WM8904_AIF_LRCLK_INV_SHIFT                   4  /* AIF_LRCLK_INV */
+#define WM8904_AIF_LRCLK_INV_WIDTH                   1  /* AIF_LRCLK_INV */
+#define WM8904_AIF_WL_MASK                      0x000C  /* AIF_WL - [3:2] */
+#define WM8904_AIF_WL_SHIFT                          2  /* AIF_WL - [3:2] */
+#define WM8904_AIF_WL_WIDTH                          2  /* AIF_WL - [3:2] */
+#define WM8904_AIF_FMT_MASK                     0x0003  /* AIF_FMT - [1:0] */
+#define WM8904_AIF_FMT_SHIFT                         0  /* AIF_FMT - [1:0] */
+#define WM8904_AIF_FMT_WIDTH                         2  /* AIF_FMT - [1:0] */
+
+/*
+ * R26 (0x1A) - Audio Interface 2
+ */
+#define WM8904_OPCLK_DIV_MASK                   0x0F00  /* OPCLK_DIV - [11:8] */
+#define WM8904_OPCLK_DIV_SHIFT                       8  /* OPCLK_DIV - [11:8] */
+#define WM8904_OPCLK_DIV_WIDTH                       4  /* OPCLK_DIV - [11:8] */
+#define WM8904_BCLK_DIV_MASK                    0x001F  /* BCLK_DIV - [4:0] */
+#define WM8904_BCLK_DIV_SHIFT                        0  /* BCLK_DIV - [4:0] */
+#define WM8904_BCLK_DIV_WIDTH                        5  /* BCLK_DIV - [4:0] */
+
+/*
+ * R27 (0x1B) - Audio Interface 3
+ */
+#define WM8904_LRCLK_DIR                        0x0800  /* LRCLK_DIR */
+#define WM8904_LRCLK_DIR_MASK                   0x0800  /* LRCLK_DIR */
+#define WM8904_LRCLK_DIR_SHIFT                      11  /* LRCLK_DIR */
+#define WM8904_LRCLK_DIR_WIDTH                       1  /* LRCLK_DIR */
+#define WM8904_LRCLK_RATE_MASK                  0x07FF  /* LRCLK_RATE - [10:0] */
+#define WM8904_LRCLK_RATE_SHIFT                      0  /* LRCLK_RATE - [10:0] */
+#define WM8904_LRCLK_RATE_WIDTH                     11  /* LRCLK_RATE - [10:0] */
+
+/*
+ * R30 (0x1E) - DAC Digital Volume Left
+ */
+#define WM8904_DAC_VU                           0x0100  /* DAC_VU */
+#define WM8904_DAC_VU_MASK                      0x0100  /* DAC_VU */
+#define WM8904_DAC_VU_SHIFT                          8  /* DAC_VU */
+#define WM8904_DAC_VU_WIDTH                          1  /* DAC_VU */
+#define WM8904_DACL_VOL_MASK                    0x00FF  /* DACL_VOL - [7:0] */
+#define WM8904_DACL_VOL_SHIFT                        0  /* DACL_VOL - [7:0] */
+#define WM8904_DACL_VOL_WIDTH                        8  /* DACL_VOL - [7:0] */
+
+/*
+ * R31 (0x1F) - DAC Digital Volume Right
+ */
+#define WM8904_DAC_VU                           0x0100  /* DAC_VU */
+#define WM8904_DAC_VU_MASK                      0x0100  /* DAC_VU */
+#define WM8904_DAC_VU_SHIFT                          8  /* DAC_VU */
+#define WM8904_DAC_VU_WIDTH                          1  /* DAC_VU */
+#define WM8904_DACR_VOL_MASK                    0x00FF  /* DACR_VOL - [7:0] */
+#define WM8904_DACR_VOL_SHIFT                        0  /* DACR_VOL - [7:0] */
+#define WM8904_DACR_VOL_WIDTH                        8  /* DACR_VOL - [7:0] */
+
+/*
+ * R32 (0x20) - DAC Digital 0
+ */
+#define WM8904_ADCL_DAC_SVOL_MASK               0x0F00  /* ADCL_DAC_SVOL - [11:8] */
+#define WM8904_ADCL_DAC_SVOL_SHIFT                   8  /* ADCL_DAC_SVOL - [11:8] */
+#define WM8904_ADCL_DAC_SVOL_WIDTH                   4  /* ADCL_DAC_SVOL - [11:8] */
+#define WM8904_ADCR_DAC_SVOL_MASK               0x00F0  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8904_ADCR_DAC_SVOL_SHIFT                   4  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8904_ADCR_DAC_SVOL_WIDTH                   4  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8904_ADC_TO_DACL_MASK                 0x000C  /* ADC_TO_DACL - [3:2] */
+#define WM8904_ADC_TO_DACL_SHIFT                     2  /* ADC_TO_DACL - [3:2] */
+#define WM8904_ADC_TO_DACL_WIDTH                     2  /* ADC_TO_DACL - [3:2] */
+#define WM8904_ADC_TO_DACR_MASK                 0x0003  /* ADC_TO_DACR - [1:0] */
+#define WM8904_ADC_TO_DACR_SHIFT                     0  /* ADC_TO_DACR - [1:0] */
+#define WM8904_ADC_TO_DACR_WIDTH                     2  /* ADC_TO_DACR - [1:0] */
+
+/*
+ * R33 (0x21) - DAC Digital 1
+ */
+#define WM8904_DAC_MONO                         0x1000  /* DAC_MONO */
+#define WM8904_DAC_MONO_MASK                    0x1000  /* DAC_MONO */
+#define WM8904_DAC_MONO_SHIFT                       12  /* DAC_MONO */
+#define WM8904_DAC_MONO_WIDTH                        1  /* DAC_MONO */
+#define WM8904_DAC_SB_FILT                      0x0800  /* DAC_SB_FILT */
+#define WM8904_DAC_SB_FILT_MASK                 0x0800  /* DAC_SB_FILT */
+#define WM8904_DAC_SB_FILT_SHIFT                    11  /* DAC_SB_FILT */
+#define WM8904_DAC_SB_FILT_WIDTH                     1  /* DAC_SB_FILT */
+#define WM8904_DAC_MUTERATE                     0x0400  /* DAC_MUTERATE */
+#define WM8904_DAC_MUTERATE_MASK                0x0400  /* DAC_MUTERATE */
+#define WM8904_DAC_MUTERATE_SHIFT                   10  /* DAC_MUTERATE */
+#define WM8904_DAC_MUTERATE_WIDTH                    1  /* DAC_MUTERATE */
+#define WM8904_DAC_UNMUTE_RAMP                  0x0200  /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_UNMUTE_RAMP_MASK             0x0200  /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_UNMUTE_RAMP_SHIFT                 9  /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_UNMUTE_RAMP_WIDTH                 1  /* DAC_UNMUTE_RAMP */
+#define WM8904_DAC_OSR128                       0x0040  /* DAC_OSR128 */
+#define WM8904_DAC_OSR128_MASK                  0x0040  /* DAC_OSR128 */
+#define WM8904_DAC_OSR128_SHIFT                      6  /* DAC_OSR128 */
+#define WM8904_DAC_OSR128_WIDTH                      1  /* DAC_OSR128 */
+#define WM8904_DAC_MUTE                         0x0008  /* DAC_MUTE */
+#define WM8904_DAC_MUTE_MASK                    0x0008  /* DAC_MUTE */
+#define WM8904_DAC_MUTE_SHIFT                        3  /* DAC_MUTE */
+#define WM8904_DAC_MUTE_WIDTH                        1  /* DAC_MUTE */
+#define WM8904_DEEMPH_MASK                      0x0006  /* DEEMPH - [2:1] */
+#define WM8904_DEEMPH_SHIFT                          1  /* DEEMPH - [2:1] */
+#define WM8904_DEEMPH_WIDTH                          2  /* DEEMPH - [2:1] */
+
+/*
+ * R36 (0x24) - ADC Digital Volume Left
+ */
+#define WM8904_ADC_VU                           0x0100  /* ADC_VU */
+#define WM8904_ADC_VU_MASK                      0x0100  /* ADC_VU */
+#define WM8904_ADC_VU_SHIFT                          8  /* ADC_VU */
+#define WM8904_ADC_VU_WIDTH                          1  /* ADC_VU */
+#define WM8904_ADCL_VOL_MASK                    0x00FF  /* ADCL_VOL - [7:0] */
+#define WM8904_ADCL_VOL_SHIFT                        0  /* ADCL_VOL - [7:0] */
+#define WM8904_ADCL_VOL_WIDTH                        8  /* ADCL_VOL - [7:0] */
+
+/*
+ * R37 (0x25) - ADC Digital Volume Right
+ */
+#define WM8904_ADC_VU                           0x0100  /* ADC_VU */
+#define WM8904_ADC_VU_MASK                      0x0100  /* ADC_VU */
+#define WM8904_ADC_VU_SHIFT                          8  /* ADC_VU */
+#define WM8904_ADC_VU_WIDTH                          1  /* ADC_VU */
+#define WM8904_ADCR_VOL_MASK                    0x00FF  /* ADCR_VOL - [7:0] */
+#define WM8904_ADCR_VOL_SHIFT                        0  /* ADCR_VOL - [7:0] */
+#define WM8904_ADCR_VOL_WIDTH                        8  /* ADCR_VOL - [7:0] */
+
+/*
+ * R38 (0x26) - ADC Digital 0
+ */
+#define WM8904_ADC_HPF_CUT_MASK                 0x0060  /* ADC_HPF_CUT - [6:5] */
+#define WM8904_ADC_HPF_CUT_SHIFT                     5  /* ADC_HPF_CUT - [6:5] */
+#define WM8904_ADC_HPF_CUT_WIDTH                     2  /* ADC_HPF_CUT - [6:5] */
+#define WM8904_ADC_HPF                          0x0010  /* ADC_HPF */
+#define WM8904_ADC_HPF_MASK                     0x0010  /* ADC_HPF */
+#define WM8904_ADC_HPF_SHIFT                         4  /* ADC_HPF */
+#define WM8904_ADC_HPF_WIDTH                         1  /* ADC_HPF */
+#define WM8904_ADCL_DATINV                      0x0002  /* ADCL_DATINV */
+#define WM8904_ADCL_DATINV_MASK                 0x0002  /* ADCL_DATINV */
+#define WM8904_ADCL_DATINV_SHIFT                     1  /* ADCL_DATINV */
+#define WM8904_ADCL_DATINV_WIDTH                     1  /* ADCL_DATINV */
+#define WM8904_ADCR_DATINV                      0x0001  /* ADCR_DATINV */
+#define WM8904_ADCR_DATINV_MASK                 0x0001  /* ADCR_DATINV */
+#define WM8904_ADCR_DATINV_SHIFT                     0  /* ADCR_DATINV */
+#define WM8904_ADCR_DATINV_WIDTH                     1  /* ADCR_DATINV */
+
+/*
+ * R39 (0x27) - Digital Microphone 0
+ */
+#define WM8904_DMIC_ENA                         0x1000  /* DMIC_ENA */
+#define WM8904_DMIC_ENA_MASK                    0x1000  /* DMIC_ENA */
+#define WM8904_DMIC_ENA_SHIFT                       12  /* DMIC_ENA */
+#define WM8904_DMIC_ENA_WIDTH                        1  /* DMIC_ENA */
+#define WM8904_DMIC_SRC                         0x0800  /* DMIC_SRC */
+#define WM8904_DMIC_SRC_MASK                    0x0800  /* DMIC_SRC */
+#define WM8904_DMIC_SRC_SHIFT                       11  /* DMIC_SRC */
+#define WM8904_DMIC_SRC_WIDTH                        1  /* DMIC_SRC */
+
+/*
+ * R40 (0x28) - DRC 0
+ */
+#define WM8904_DRC_ENA                          0x8000  /* DRC_ENA */
+#define WM8904_DRC_ENA_MASK                     0x8000  /* DRC_ENA */
+#define WM8904_DRC_ENA_SHIFT                        15  /* DRC_ENA */
+#define WM8904_DRC_ENA_WIDTH                         1  /* DRC_ENA */
+#define WM8904_DRC_DAC_PATH                     0x4000  /* DRC_DAC_PATH */
+#define WM8904_DRC_DAC_PATH_MASK                0x4000  /* DRC_DAC_PATH */
+#define WM8904_DRC_DAC_PATH_SHIFT                   14  /* DRC_DAC_PATH */
+#define WM8904_DRC_DAC_PATH_WIDTH                    1  /* DRC_DAC_PATH */
+#define WM8904_DRC_GS_HYST_LVL_MASK             0x1800  /* DRC_GS_HYST_LVL - [12:11] */
+#define WM8904_DRC_GS_HYST_LVL_SHIFT                11  /* DRC_GS_HYST_LVL - [12:11] */
+#define WM8904_DRC_GS_HYST_LVL_WIDTH                 2  /* DRC_GS_HYST_LVL - [12:11] */
+#define WM8904_DRC_STARTUP_GAIN_MASK            0x07C0  /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8904_DRC_STARTUP_GAIN_SHIFT                6  /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8904_DRC_STARTUP_GAIN_WIDTH                5  /* DRC_STARTUP_GAIN - [10:6] */
+#define WM8904_DRC_FF_DELAY                     0x0020  /* DRC_FF_DELAY */
+#define WM8904_DRC_FF_DELAY_MASK                0x0020  /* DRC_FF_DELAY */
+#define WM8904_DRC_FF_DELAY_SHIFT                    5  /* DRC_FF_DELAY */
+#define WM8904_DRC_FF_DELAY_WIDTH                    1  /* DRC_FF_DELAY */
+#define WM8904_DRC_GS_ENA                       0x0008  /* DRC_GS_ENA */
+#define WM8904_DRC_GS_ENA_MASK                  0x0008  /* DRC_GS_ENA */
+#define WM8904_DRC_GS_ENA_SHIFT                      3  /* DRC_GS_ENA */
+#define WM8904_DRC_GS_ENA_WIDTH                      1  /* DRC_GS_ENA */
+#define WM8904_DRC_QR                           0x0004  /* DRC_QR */
+#define WM8904_DRC_QR_MASK                      0x0004  /* DRC_QR */
+#define WM8904_DRC_QR_SHIFT                          2  /* DRC_QR */
+#define WM8904_DRC_QR_WIDTH                          1  /* DRC_QR */
+#define WM8904_DRC_ANTICLIP                     0x0002  /* DRC_ANTICLIP */
+#define WM8904_DRC_ANTICLIP_MASK                0x0002  /* DRC_ANTICLIP */
+#define WM8904_DRC_ANTICLIP_SHIFT                    1  /* DRC_ANTICLIP */
+#define WM8904_DRC_ANTICLIP_WIDTH                    1  /* DRC_ANTICLIP */
+#define WM8904_DRC_GS_HYST                      0x0001  /* DRC_GS_HYST */
+#define WM8904_DRC_GS_HYST_MASK                 0x0001  /* DRC_GS_HYST */
+#define WM8904_DRC_GS_HYST_SHIFT                     0  /* DRC_GS_HYST */
+#define WM8904_DRC_GS_HYST_WIDTH                     1  /* DRC_GS_HYST */
+
+/*
+ * R41 (0x29) - DRC 1
+ */
+#define WM8904_DRC_ATK_MASK                     0xF000  /* DRC_ATK - [15:12] */
+#define WM8904_DRC_ATK_SHIFT                        12  /* DRC_ATK - [15:12] */
+#define WM8904_DRC_ATK_WIDTH                         4  /* DRC_ATK - [15:12] */
+#define WM8904_DRC_DCY_MASK                     0x0F00  /* DRC_DCY - [11:8] */
+#define WM8904_DRC_DCY_SHIFT                         8  /* DRC_DCY - [11:8] */
+#define WM8904_DRC_DCY_WIDTH                         4  /* DRC_DCY - [11:8] */
+#define WM8904_DRC_QR_THR_MASK                  0x00C0  /* DRC_QR_THR - [7:6] */
+#define WM8904_DRC_QR_THR_SHIFT                      6  /* DRC_QR_THR - [7:6] */
+#define WM8904_DRC_QR_THR_WIDTH                      2  /* DRC_QR_THR - [7:6] */
+#define WM8904_DRC_QR_DCY_MASK                  0x0030  /* DRC_QR_DCY - [5:4] */
+#define WM8904_DRC_QR_DCY_SHIFT                      4  /* DRC_QR_DCY - [5:4] */
+#define WM8904_DRC_QR_DCY_WIDTH                      2  /* DRC_QR_DCY - [5:4] */
+#define WM8904_DRC_MINGAIN_MASK                 0x000C  /* DRC_MINGAIN - [3:2] */
+#define WM8904_DRC_MINGAIN_SHIFT                     2  /* DRC_MINGAIN - [3:2] */
+#define WM8904_DRC_MINGAIN_WIDTH                     2  /* DRC_MINGAIN - [3:2] */
+#define WM8904_DRC_MAXGAIN_MASK                 0x0003  /* DRC_MAXGAIN - [1:0] */
+#define WM8904_DRC_MAXGAIN_SHIFT                     0  /* DRC_MAXGAIN - [1:0] */
+#define WM8904_DRC_MAXGAIN_WIDTH                     2  /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R42 (0x2A) - DRC 2
+ */
+#define WM8904_DRC_HI_COMP_MASK                 0x0038  /* DRC_HI_COMP - [5:3] */
+#define WM8904_DRC_HI_COMP_SHIFT                     3  /* DRC_HI_COMP - [5:3] */
+#define WM8904_DRC_HI_COMP_WIDTH                     3  /* DRC_HI_COMP - [5:3] */
+#define WM8904_DRC_LO_COMP_MASK                 0x0007  /* DRC_LO_COMP - [2:0] */
+#define WM8904_DRC_LO_COMP_SHIFT                     0  /* DRC_LO_COMP - [2:0] */
+#define WM8904_DRC_LO_COMP_WIDTH                     3  /* DRC_LO_COMP - [2:0] */
+
+/*
+ * R43 (0x2B) - DRC 3
+ */
+#define WM8904_DRC_KNEE_IP_MASK                 0x07E0  /* DRC_KNEE_IP - [10:5] */
+#define WM8904_DRC_KNEE_IP_SHIFT                     5  /* DRC_KNEE_IP - [10:5] */
+#define WM8904_DRC_KNEE_IP_WIDTH                     6  /* DRC_KNEE_IP - [10:5] */
+#define WM8904_DRC_KNEE_OP_MASK                 0x001F  /* DRC_KNEE_OP - [4:0] */
+#define WM8904_DRC_KNEE_OP_SHIFT                     0  /* DRC_KNEE_OP - [4:0] */
+#define WM8904_DRC_KNEE_OP_WIDTH                     5  /* DRC_KNEE_OP - [4:0] */
+
+/*
+ * R44 (0x2C) - Analogue Left Input 0
+ */
+#define WM8904_LINMUTE                          0x0080  /* LINMUTE */
+#define WM8904_LINMUTE_MASK                     0x0080  /* LINMUTE */
+#define WM8904_LINMUTE_SHIFT                         7  /* LINMUTE */
+#define WM8904_LINMUTE_WIDTH                         1  /* LINMUTE */
+#define WM8904_LIN_VOL_MASK                     0x001F  /* LIN_VOL - [4:0] */
+#define WM8904_LIN_VOL_SHIFT                         0  /* LIN_VOL - [4:0] */
+#define WM8904_LIN_VOL_WIDTH                         5  /* LIN_VOL - [4:0] */
+
+/*
+ * R45 (0x2D) - Analogue Right Input 0
+ */
+#define WM8904_RINMUTE                          0x0080  /* RINMUTE */
+#define WM8904_RINMUTE_MASK                     0x0080  /* RINMUTE */
+#define WM8904_RINMUTE_SHIFT                         7  /* RINMUTE */
+#define WM8904_RINMUTE_WIDTH                         1  /* RINMUTE */
+#define WM8904_RIN_VOL_MASK                     0x001F  /* RIN_VOL - [4:0] */
+#define WM8904_RIN_VOL_SHIFT                         0  /* RIN_VOL - [4:0] */
+#define WM8904_RIN_VOL_WIDTH                         5  /* RIN_VOL - [4:0] */
+
+/*
+ * R46 (0x2E) - Analogue Left Input 1
+ */
+#define WM8904_INL_CM_ENA                       0x0040  /* INL_CM_ENA */
+#define WM8904_INL_CM_ENA_MASK                  0x0040  /* INL_CM_ENA */
+#define WM8904_INL_CM_ENA_SHIFT                      6  /* INL_CM_ENA */
+#define WM8904_INL_CM_ENA_WIDTH                      1  /* INL_CM_ENA */
+#define WM8904_L_IP_SEL_N_MASK                  0x0030  /* L_IP_SEL_N - [5:4] */
+#define WM8904_L_IP_SEL_N_SHIFT                      4  /* L_IP_SEL_N - [5:4] */
+#define WM8904_L_IP_SEL_N_WIDTH                      2  /* L_IP_SEL_N - [5:4] */
+#define WM8904_L_IP_SEL_P_MASK                  0x000C  /* L_IP_SEL_P - [3:2] */
+#define WM8904_L_IP_SEL_P_SHIFT                      2  /* L_IP_SEL_P - [3:2] */
+#define WM8904_L_IP_SEL_P_WIDTH                      2  /* L_IP_SEL_P - [3:2] */
+#define WM8904_L_MODE_MASK                      0x0003  /* L_MODE - [1:0] */
+#define WM8904_L_MODE_SHIFT                          0  /* L_MODE - [1:0] */
+#define WM8904_L_MODE_WIDTH                          2  /* L_MODE - [1:0] */
+
+/*
+ * R47 (0x2F) - Analogue Right Input 1
+ */
+#define WM8904_INR_CM_ENA                       0x0040  /* INR_CM_ENA */
+#define WM8904_INR_CM_ENA_MASK                  0x0040  /* INR_CM_ENA */
+#define WM8904_INR_CM_ENA_SHIFT                      6  /* INR_CM_ENA */
+#define WM8904_INR_CM_ENA_WIDTH                      1  /* INR_CM_ENA */
+#define WM8904_R_IP_SEL_N_MASK                  0x0030  /* R_IP_SEL_N - [5:4] */
+#define WM8904_R_IP_SEL_N_SHIFT                      4  /* R_IP_SEL_N - [5:4] */
+#define WM8904_R_IP_SEL_N_WIDTH                      2  /* R_IP_SEL_N - [5:4] */
+#define WM8904_R_IP_SEL_P_MASK                  0x000C  /* R_IP_SEL_P - [3:2] */
+#define WM8904_R_IP_SEL_P_SHIFT                      2  /* R_IP_SEL_P - [3:2] */
+#define WM8904_R_IP_SEL_P_WIDTH                      2  /* R_IP_SEL_P - [3:2] */
+#define WM8904_R_MODE_MASK                      0x0003  /* R_MODE - [1:0] */
+#define WM8904_R_MODE_SHIFT                          0  /* R_MODE - [1:0] */
+#define WM8904_R_MODE_WIDTH                          2  /* R_MODE - [1:0] */
+
+/*
+ * R57 (0x39) - Analogue OUT1 Left
+ */
+#define WM8904_HPOUTL_MUTE                      0x0100  /* HPOUTL_MUTE */
+#define WM8904_HPOUTL_MUTE_MASK                 0x0100  /* HPOUTL_MUTE */
+#define WM8904_HPOUTL_MUTE_SHIFT                     8  /* HPOUTL_MUTE */
+#define WM8904_HPOUTL_MUTE_WIDTH                     1  /* HPOUTL_MUTE */
+#define WM8904_HPOUT_VU                         0x0080  /* HPOUT_VU */
+#define WM8904_HPOUT_VU_MASK                    0x0080  /* HPOUT_VU */
+#define WM8904_HPOUT_VU_SHIFT                        7  /* HPOUT_VU */
+#define WM8904_HPOUT_VU_WIDTH                        1  /* HPOUT_VU */
+#define WM8904_HPOUTLZC                         0x0040  /* HPOUTLZC */
+#define WM8904_HPOUTLZC_MASK                    0x0040  /* HPOUTLZC */
+#define WM8904_HPOUTLZC_SHIFT                        6  /* HPOUTLZC */
+#define WM8904_HPOUTLZC_WIDTH                        1  /* HPOUTLZC */
+#define WM8904_HPOUTL_VOL_MASK                  0x003F  /* HPOUTL_VOL - [5:0] */
+#define WM8904_HPOUTL_VOL_SHIFT                      0  /* HPOUTL_VOL - [5:0] */
+#define WM8904_HPOUTL_VOL_WIDTH                      6  /* HPOUTL_VOL - [5:0] */
+
+/*
+ * R58 (0x3A) - Analogue OUT1 Right
+ */
+#define WM8904_HPOUTR_MUTE                      0x0100  /* HPOUTR_MUTE */
+#define WM8904_HPOUTR_MUTE_MASK                 0x0100  /* HPOUTR_MUTE */
+#define WM8904_HPOUTR_MUTE_SHIFT                     8  /* HPOUTR_MUTE */
+#define WM8904_HPOUTR_MUTE_WIDTH                     1  /* HPOUTR_MUTE */
+#define WM8904_HPOUT_VU                         0x0080  /* HPOUT_VU */
+#define WM8904_HPOUT_VU_MASK                    0x0080  /* HPOUT_VU */
+#define WM8904_HPOUT_VU_SHIFT                        7  /* HPOUT_VU */
+#define WM8904_HPOUT_VU_WIDTH                        1  /* HPOUT_VU */
+#define WM8904_HPOUTRZC                         0x0040  /* HPOUTRZC */
+#define WM8904_HPOUTRZC_MASK                    0x0040  /* HPOUTRZC */
+#define WM8904_HPOUTRZC_SHIFT                        6  /* HPOUTRZC */
+#define WM8904_HPOUTRZC_WIDTH                        1  /* HPOUTRZC */
+#define WM8904_HPOUTR_VOL_MASK                  0x003F  /* HPOUTR_VOL - [5:0] */
+#define WM8904_HPOUTR_VOL_SHIFT                      0  /* HPOUTR_VOL - [5:0] */
+#define WM8904_HPOUTR_VOL_WIDTH                      6  /* HPOUTR_VOL - [5:0] */
+
+/*
+ * R59 (0x3B) - Analogue OUT2 Left
+ */
+#define WM8904_LINEOUTL_MUTE                    0x0100  /* LINEOUTL_MUTE */
+#define WM8904_LINEOUTL_MUTE_MASK               0x0100  /* LINEOUTL_MUTE */
+#define WM8904_LINEOUTL_MUTE_SHIFT                   8  /* LINEOUTL_MUTE */
+#define WM8904_LINEOUTL_MUTE_WIDTH                   1  /* LINEOUTL_MUTE */
+#define WM8904_LINEOUT_VU                       0x0080  /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_MASK                  0x0080  /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_SHIFT                      7  /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_WIDTH                      1  /* LINEOUT_VU */
+#define WM8904_LINEOUTLZC                       0x0040  /* LINEOUTLZC */
+#define WM8904_LINEOUTLZC_MASK                  0x0040  /* LINEOUTLZC */
+#define WM8904_LINEOUTLZC_SHIFT                      6  /* LINEOUTLZC */
+#define WM8904_LINEOUTLZC_WIDTH                      1  /* LINEOUTLZC */
+#define WM8904_LINEOUTL_VOL_MASK                0x003F  /* LINEOUTL_VOL - [5:0] */
+#define WM8904_LINEOUTL_VOL_SHIFT                    0  /* LINEOUTL_VOL - [5:0] */
+#define WM8904_LINEOUTL_VOL_WIDTH                    6  /* LINEOUTL_VOL - [5:0] */
+
+/*
+ * R60 (0x3C) - Analogue OUT2 Right
+ */
+#define WM8904_LINEOUTR_MUTE                    0x0100  /* LINEOUTR_MUTE */
+#define WM8904_LINEOUTR_MUTE_MASK               0x0100  /* LINEOUTR_MUTE */
+#define WM8904_LINEOUTR_MUTE_SHIFT                   8  /* LINEOUTR_MUTE */
+#define WM8904_LINEOUTR_MUTE_WIDTH                   1  /* LINEOUTR_MUTE */
+#define WM8904_LINEOUT_VU                       0x0080  /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_MASK                  0x0080  /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_SHIFT                      7  /* LINEOUT_VU */
+#define WM8904_LINEOUT_VU_WIDTH                      1  /* LINEOUT_VU */
+#define WM8904_LINEOUTRZC                       0x0040  /* LINEOUTRZC */
+#define WM8904_LINEOUTRZC_MASK                  0x0040  /* LINEOUTRZC */
+#define WM8904_LINEOUTRZC_SHIFT                      6  /* LINEOUTRZC */
+#define WM8904_LINEOUTRZC_WIDTH                      1  /* LINEOUTRZC */
+#define WM8904_LINEOUTR_VOL_MASK                0x003F  /* LINEOUTR_VOL - [5:0] */
+#define WM8904_LINEOUTR_VOL_SHIFT                    0  /* LINEOUTR_VOL - [5:0] */
+#define WM8904_LINEOUTR_VOL_WIDTH                    6  /* LINEOUTR_VOL - [5:0] */
+
+/*
+ * R61 (0x3D) - Analogue OUT12 ZC
+ */
+#define WM8904_HPL_BYP_ENA                      0x0008  /* HPL_BYP_ENA */
+#define WM8904_HPL_BYP_ENA_MASK                 0x0008  /* HPL_BYP_ENA */
+#define WM8904_HPL_BYP_ENA_SHIFT                     3  /* HPL_BYP_ENA */
+#define WM8904_HPL_BYP_ENA_WIDTH                     1  /* HPL_BYP_ENA */
+#define WM8904_HPR_BYP_ENA                      0x0004  /* HPR_BYP_ENA */
+#define WM8904_HPR_BYP_ENA_MASK                 0x0004  /* HPR_BYP_ENA */
+#define WM8904_HPR_BYP_ENA_SHIFT                     2  /* HPR_BYP_ENA */
+#define WM8904_HPR_BYP_ENA_WIDTH                     1  /* HPR_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA                 0x0002  /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA_MASK            0x0002  /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA_SHIFT                1  /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTL_BYP_ENA_WIDTH                1  /* LINEOUTL_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA                 0x0001  /* LINEOUTR_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA_MASK            0x0001  /* LINEOUTR_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA_SHIFT                0  /* LINEOUTR_BYP_ENA */
+#define WM8904_LINEOUTR_BYP_ENA_WIDTH                1  /* LINEOUTR_BYP_ENA */
+
+/*
+ * R67 (0x43) - DC Servo 0
+ */
+#define WM8904_DCS_ENA_CHAN_3                   0x0008  /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_3_MASK              0x0008  /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_3_SHIFT                  3  /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_3_WIDTH                  1  /* DCS_ENA_CHAN_3 */
+#define WM8904_DCS_ENA_CHAN_2                   0x0004  /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_2_MASK              0x0004  /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_2_SHIFT                  2  /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_2_WIDTH                  1  /* DCS_ENA_CHAN_2 */
+#define WM8904_DCS_ENA_CHAN_1                   0x0002  /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_1_MASK              0x0002  /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_1_SHIFT                  1  /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_1_WIDTH                  1  /* DCS_ENA_CHAN_1 */
+#define WM8904_DCS_ENA_CHAN_0                   0x0001  /* DCS_ENA_CHAN_0 */
+#define WM8904_DCS_ENA_CHAN_0_MASK              0x0001  /* DCS_ENA_CHAN_0 */
+#define WM8904_DCS_ENA_CHAN_0_SHIFT                  0  /* DCS_ENA_CHAN_0 */
+#define WM8904_DCS_ENA_CHAN_0_WIDTH                  1  /* DCS_ENA_CHAN_0 */
+
+/*
+ * R68 (0x44) - DC Servo 1
+ */
+#define WM8904_DCS_TRIG_SINGLE_3                0x8000  /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_3_MASK           0x8000  /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_3_SHIFT              15  /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_3_WIDTH               1  /* DCS_TRIG_SINGLE_3 */
+#define WM8904_DCS_TRIG_SINGLE_2                0x4000  /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_2_MASK           0x4000  /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_2_SHIFT              14  /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_2_WIDTH               1  /* DCS_TRIG_SINGLE_2 */
+#define WM8904_DCS_TRIG_SINGLE_1                0x2000  /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_1_MASK           0x2000  /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_1_SHIFT              13  /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_1_WIDTH               1  /* DCS_TRIG_SINGLE_1 */
+#define WM8904_DCS_TRIG_SINGLE_0                0x1000  /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SINGLE_0_MASK           0x1000  /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SINGLE_0_SHIFT              12  /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SINGLE_0_WIDTH               1  /* DCS_TRIG_SINGLE_0 */
+#define WM8904_DCS_TRIG_SERIES_3                0x0800  /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_3_MASK           0x0800  /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_3_SHIFT              11  /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_3_WIDTH               1  /* DCS_TRIG_SERIES_3 */
+#define WM8904_DCS_TRIG_SERIES_2                0x0400  /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_2_MASK           0x0400  /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_2_SHIFT              10  /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_2_WIDTH               1  /* DCS_TRIG_SERIES_2 */
+#define WM8904_DCS_TRIG_SERIES_1                0x0200  /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_1_MASK           0x0200  /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_1_SHIFT               9  /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_1_WIDTH               1  /* DCS_TRIG_SERIES_1 */
+#define WM8904_DCS_TRIG_SERIES_0                0x0100  /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_SERIES_0_MASK           0x0100  /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_SERIES_0_SHIFT               8  /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_SERIES_0_WIDTH               1  /* DCS_TRIG_SERIES_0 */
+#define WM8904_DCS_TRIG_STARTUP_3               0x0080  /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_3_MASK          0x0080  /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_3_SHIFT              7  /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_3_WIDTH              1  /* DCS_TRIG_STARTUP_3 */
+#define WM8904_DCS_TRIG_STARTUP_2               0x0040  /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_2_MASK          0x0040  /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_2_SHIFT              6  /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_2_WIDTH              1  /* DCS_TRIG_STARTUP_2 */
+#define WM8904_DCS_TRIG_STARTUP_1               0x0020  /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_1_MASK          0x0020  /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_1_SHIFT              5  /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_1_WIDTH              1  /* DCS_TRIG_STARTUP_1 */
+#define WM8904_DCS_TRIG_STARTUP_0               0x0010  /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_STARTUP_0_MASK          0x0010  /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_STARTUP_0_SHIFT              4  /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_STARTUP_0_WIDTH              1  /* DCS_TRIG_STARTUP_0 */
+#define WM8904_DCS_TRIG_DAC_WR_3                0x0008  /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_3_MASK           0x0008  /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_3_SHIFT               3  /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_3_WIDTH               1  /* DCS_TRIG_DAC_WR_3 */
+#define WM8904_DCS_TRIG_DAC_WR_2                0x0004  /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_2_MASK           0x0004  /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_2_SHIFT               2  /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_2_WIDTH               1  /* DCS_TRIG_DAC_WR_2 */
+#define WM8904_DCS_TRIG_DAC_WR_1                0x0002  /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_1_MASK           0x0002  /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_1_SHIFT               1  /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_1_WIDTH               1  /* DCS_TRIG_DAC_WR_1 */
+#define WM8904_DCS_TRIG_DAC_WR_0                0x0001  /* DCS_TRIG_DAC_WR_0 */
+#define WM8904_DCS_TRIG_DAC_WR_0_MASK           0x0001  /* DCS_TRIG_DAC_WR_0 */
+#define WM8904_DCS_TRIG_DAC_WR_0_SHIFT               0  /* DCS_TRIG_DAC_WR_0 */
+#define WM8904_DCS_TRIG_DAC_WR_0_WIDTH               1  /* DCS_TRIG_DAC_WR_0 */
+
+/*
+ * R69 (0x45) - DC Servo 2
+ */
+#define WM8904_DCS_TIMER_PERIOD_23_MASK         0x0F00  /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8904_DCS_TIMER_PERIOD_23_SHIFT             8  /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8904_DCS_TIMER_PERIOD_23_WIDTH             4  /* DCS_TIMER_PERIOD_23 - [11:8] */
+#define WM8904_DCS_TIMER_PERIOD_01_MASK         0x000F  /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8904_DCS_TIMER_PERIOD_01_SHIFT             0  /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8904_DCS_TIMER_PERIOD_01_WIDTH             4  /* DCS_TIMER_PERIOD_01 - [3:0] */
+
+/*
+ * R71 (0x47) - DC Servo 4
+ */
+#define WM8904_DCS_SERIES_NO_23_MASK            0x007F  /* DCS_SERIES_NO_23 - [6:0] */
+#define WM8904_DCS_SERIES_NO_23_SHIFT                0  /* DCS_SERIES_NO_23 - [6:0] */
+#define WM8904_DCS_SERIES_NO_23_WIDTH                7  /* DCS_SERIES_NO_23 - [6:0] */
+
+/*
+ * R72 (0x48) - DC Servo 5
+ */
+#define WM8904_DCS_SERIES_NO_01_MASK            0x007F  /* DCS_SERIES_NO_01 - [6:0] */
+#define WM8904_DCS_SERIES_NO_01_SHIFT                0  /* DCS_SERIES_NO_01 - [6:0] */
+#define WM8904_DCS_SERIES_NO_01_WIDTH                7  /* DCS_SERIES_NO_01 - [6:0] */
+
+/*
+ * R73 (0x49) - DC Servo 6
+ */
+#define WM8904_DCS_DAC_WR_VAL_3_MASK            0x00FF  /* DCS_DAC_WR_VAL_3 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_3_SHIFT                0  /* DCS_DAC_WR_VAL_3 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_3_WIDTH                8  /* DCS_DAC_WR_VAL_3 - [7:0] */
+
+/*
+ * R74 (0x4A) - DC Servo 7
+ */
+#define WM8904_DCS_DAC_WR_VAL_2_MASK            0x00FF  /* DCS_DAC_WR_VAL_2 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_2_SHIFT                0  /* DCS_DAC_WR_VAL_2 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_2_WIDTH                8  /* DCS_DAC_WR_VAL_2 - [7:0] */
+
+/*
+ * R75 (0x4B) - DC Servo 8
+ */
+#define WM8904_DCS_DAC_WR_VAL_1_MASK            0x00FF  /* DCS_DAC_WR_VAL_1 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_1_SHIFT                0  /* DCS_DAC_WR_VAL_1 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_1_WIDTH                8  /* DCS_DAC_WR_VAL_1 - [7:0] */
+
+/*
+ * R76 (0x4C) - DC Servo 9
+ */
+#define WM8904_DCS_DAC_WR_VAL_0_MASK            0x00FF  /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_0_SHIFT                0  /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8904_DCS_DAC_WR_VAL_0_WIDTH                8  /* DCS_DAC_WR_VAL_0 - [7:0] */
+
+/*
+ * R77 (0x4D) - DC Servo Readback 0
+ */
+#define WM8904_DCS_CAL_COMPLETE_MASK            0x0F00  /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8904_DCS_CAL_COMPLETE_SHIFT                8  /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8904_DCS_CAL_COMPLETE_WIDTH                4  /* DCS_CAL_COMPLETE - [11:8] */
+#define WM8904_DCS_DAC_WR_COMPLETE_MASK         0x00F0  /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8904_DCS_DAC_WR_COMPLETE_SHIFT             4  /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8904_DCS_DAC_WR_COMPLETE_WIDTH             4  /* DCS_DAC_WR_COMPLETE - [7:4] */
+#define WM8904_DCS_STARTUP_COMPLETE_MASK        0x000F  /* DCS_STARTUP_COMPLETE - [3:0] */
+#define WM8904_DCS_STARTUP_COMPLETE_SHIFT            0  /* DCS_STARTUP_COMPLETE - [3:0] */
+#define WM8904_DCS_STARTUP_COMPLETE_WIDTH            4  /* DCS_STARTUP_COMPLETE - [3:0] */
+
+/*
+ * R90 (0x5A) - Analogue HP 0
+ */
+#define WM8904_HPL_RMV_SHORT                    0x0080  /* HPL_RMV_SHORT */
+#define WM8904_HPL_RMV_SHORT_MASK               0x0080  /* HPL_RMV_SHORT */
+#define WM8904_HPL_RMV_SHORT_SHIFT                   7  /* HPL_RMV_SHORT */
+#define WM8904_HPL_RMV_SHORT_WIDTH                   1  /* HPL_RMV_SHORT */
+#define WM8904_HPL_ENA_OUTP                     0x0040  /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_OUTP_MASK                0x0040  /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_OUTP_SHIFT                    6  /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_OUTP_WIDTH                    1  /* HPL_ENA_OUTP */
+#define WM8904_HPL_ENA_DLY                      0x0020  /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA_DLY_MASK                 0x0020  /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA_DLY_SHIFT                     5  /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA_DLY_WIDTH                     1  /* HPL_ENA_DLY */
+#define WM8904_HPL_ENA                          0x0010  /* HPL_ENA */
+#define WM8904_HPL_ENA_MASK                     0x0010  /* HPL_ENA */
+#define WM8904_HPL_ENA_SHIFT                         4  /* HPL_ENA */
+#define WM8904_HPL_ENA_WIDTH                         1  /* HPL_ENA */
+#define WM8904_HPR_RMV_SHORT                    0x0008  /* HPR_RMV_SHORT */
+#define WM8904_HPR_RMV_SHORT_MASK               0x0008  /* HPR_RMV_SHORT */
+#define WM8904_HPR_RMV_SHORT_SHIFT                   3  /* HPR_RMV_SHORT */
+#define WM8904_HPR_RMV_SHORT_WIDTH                   1  /* HPR_RMV_SHORT */
+#define WM8904_HPR_ENA_OUTP                     0x0004  /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_OUTP_MASK                0x0004  /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_OUTP_SHIFT                    2  /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_OUTP_WIDTH                    1  /* HPR_ENA_OUTP */
+#define WM8904_HPR_ENA_DLY                      0x0002  /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA_DLY_MASK                 0x0002  /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA_DLY_SHIFT                     1  /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA_DLY_WIDTH                     1  /* HPR_ENA_DLY */
+#define WM8904_HPR_ENA                          0x0001  /* HPR_ENA */
+#define WM8904_HPR_ENA_MASK                     0x0001  /* HPR_ENA */
+#define WM8904_HPR_ENA_SHIFT                         0  /* HPR_ENA */
+#define WM8904_HPR_ENA_WIDTH                         1  /* HPR_ENA */
+
+/*
+ * R94 (0x5E) - Analogue Lineout 0
+ */
+#define WM8904_LINEOUTL_RMV_SHORT               0x0080  /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_RMV_SHORT_MASK          0x0080  /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_RMV_SHORT_SHIFT              7  /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_RMV_SHORT_WIDTH              1  /* LINEOUTL_RMV_SHORT */
+#define WM8904_LINEOUTL_ENA_OUTP                0x0040  /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_OUTP_MASK           0x0040  /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_OUTP_SHIFT               6  /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_OUTP_WIDTH               1  /* LINEOUTL_ENA_OUTP */
+#define WM8904_LINEOUTL_ENA_DLY                 0x0020  /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA_DLY_MASK            0x0020  /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA_DLY_SHIFT                5  /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA_DLY_WIDTH                1  /* LINEOUTL_ENA_DLY */
+#define WM8904_LINEOUTL_ENA                     0x0010  /* LINEOUTL_ENA */
+#define WM8904_LINEOUTL_ENA_MASK                0x0010  /* LINEOUTL_ENA */
+#define WM8904_LINEOUTL_ENA_SHIFT                    4  /* LINEOUTL_ENA */
+#define WM8904_LINEOUTL_ENA_WIDTH                    1  /* LINEOUTL_ENA */
+#define WM8904_LINEOUTR_RMV_SHORT               0x0008  /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_RMV_SHORT_MASK          0x0008  /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_RMV_SHORT_SHIFT              3  /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_RMV_SHORT_WIDTH              1  /* LINEOUTR_RMV_SHORT */
+#define WM8904_LINEOUTR_ENA_OUTP                0x0004  /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_OUTP_MASK           0x0004  /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_OUTP_SHIFT               2  /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_OUTP_WIDTH               1  /* LINEOUTR_ENA_OUTP */
+#define WM8904_LINEOUTR_ENA_DLY                 0x0002  /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA_DLY_MASK            0x0002  /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA_DLY_SHIFT                1  /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA_DLY_WIDTH                1  /* LINEOUTR_ENA_DLY */
+#define WM8904_LINEOUTR_ENA                     0x0001  /* LINEOUTR_ENA */
+#define WM8904_LINEOUTR_ENA_MASK                0x0001  /* LINEOUTR_ENA */
+#define WM8904_LINEOUTR_ENA_SHIFT                    0  /* LINEOUTR_ENA */
+#define WM8904_LINEOUTR_ENA_WIDTH                    1  /* LINEOUTR_ENA */
+
+/*
+ * R98 (0x62) - Charge Pump 0
+ */
+#define WM8904_CP_ENA                           0x0001  /* CP_ENA */
+#define WM8904_CP_ENA_MASK                      0x0001  /* CP_ENA */
+#define WM8904_CP_ENA_SHIFT                          0  /* CP_ENA */
+#define WM8904_CP_ENA_WIDTH                          1  /* CP_ENA */
+
+/*
+ * R104 (0x68) - Class W 0
+ */
+#define WM8904_CP_DYN_PWR                       0x0001  /* CP_DYN_PWR */
+#define WM8904_CP_DYN_PWR_MASK                  0x0001  /* CP_DYN_PWR */
+#define WM8904_CP_DYN_PWR_SHIFT                      0  /* CP_DYN_PWR */
+#define WM8904_CP_DYN_PWR_WIDTH                      1  /* CP_DYN_PWR */
+
+/*
+ * R108 (0x6C) - Write Sequencer 0
+ */
+#define WM8904_WSEQ_ENA                         0x0100  /* WSEQ_ENA */
+#define WM8904_WSEQ_ENA_MASK                    0x0100  /* WSEQ_ENA */
+#define WM8904_WSEQ_ENA_SHIFT                        8  /* WSEQ_ENA */
+#define WM8904_WSEQ_ENA_WIDTH                        1  /* WSEQ_ENA */
+#define WM8904_WSEQ_WRITE_INDEX_MASK            0x001F  /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8904_WSEQ_WRITE_INDEX_SHIFT                0  /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8904_WSEQ_WRITE_INDEX_WIDTH                5  /* WSEQ_WRITE_INDEX - [4:0] */
+
+/*
+ * R109 (0x6D) - Write Sequencer 1
+ */
+#define WM8904_WSEQ_DATA_WIDTH_MASK             0x7000  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8904_WSEQ_DATA_WIDTH_SHIFT                12  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8904_WSEQ_DATA_WIDTH_WIDTH                 3  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8904_WSEQ_DATA_START_MASK             0x0F00  /* WSEQ_DATA_START - [11:8] */
+#define WM8904_WSEQ_DATA_START_SHIFT                 8  /* WSEQ_DATA_START - [11:8] */
+#define WM8904_WSEQ_DATA_START_WIDTH                 4  /* WSEQ_DATA_START - [11:8] */
+#define WM8904_WSEQ_ADDR_MASK                   0x00FF  /* WSEQ_ADDR - [7:0] */
+#define WM8904_WSEQ_ADDR_SHIFT                       0  /* WSEQ_ADDR - [7:0] */
+#define WM8904_WSEQ_ADDR_WIDTH                       8  /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R110 (0x6E) - Write Sequencer 2
+ */
+#define WM8904_WSEQ_EOS                         0x4000  /* WSEQ_EOS */
+#define WM8904_WSEQ_EOS_MASK                    0x4000  /* WSEQ_EOS */
+#define WM8904_WSEQ_EOS_SHIFT                       14  /* WSEQ_EOS */
+#define WM8904_WSEQ_EOS_WIDTH                        1  /* WSEQ_EOS */
+#define WM8904_WSEQ_DELAY_MASK                  0x0F00  /* WSEQ_DELAY - [11:8] */
+#define WM8904_WSEQ_DELAY_SHIFT                      8  /* WSEQ_DELAY - [11:8] */
+#define WM8904_WSEQ_DELAY_WIDTH                      4  /* WSEQ_DELAY - [11:8] */
+#define WM8904_WSEQ_DATA_MASK                   0x00FF  /* WSEQ_DATA - [7:0] */
+#define WM8904_WSEQ_DATA_SHIFT                       0  /* WSEQ_DATA - [7:0] */
+#define WM8904_WSEQ_DATA_WIDTH                       8  /* WSEQ_DATA - [7:0] */
+
+/*
+ * R111 (0x6F) - Write Sequencer 3
+ */
+#define WM8904_WSEQ_ABORT                       0x0200  /* WSEQ_ABORT */
+#define WM8904_WSEQ_ABORT_MASK                  0x0200  /* WSEQ_ABORT */
+#define WM8904_WSEQ_ABORT_SHIFT                      9  /* WSEQ_ABORT */
+#define WM8904_WSEQ_ABORT_WIDTH                      1  /* WSEQ_ABORT */
+#define WM8904_WSEQ_START                       0x0100  /* WSEQ_START */
+#define WM8904_WSEQ_START_MASK                  0x0100  /* WSEQ_START */
+#define WM8904_WSEQ_START_SHIFT                      8  /* WSEQ_START */
+#define WM8904_WSEQ_START_WIDTH                      1  /* WSEQ_START */
+#define WM8904_WSEQ_START_INDEX_MASK            0x003F  /* WSEQ_START_INDEX - [5:0] */
+#define WM8904_WSEQ_START_INDEX_SHIFT                0  /* WSEQ_START_INDEX - [5:0] */
+#define WM8904_WSEQ_START_INDEX_WIDTH                6  /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R112 (0x70) - Write Sequencer 4
+ */
+#define WM8904_WSEQ_CURRENT_INDEX_MASK          0x03F0  /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8904_WSEQ_CURRENT_INDEX_SHIFT              4  /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8904_WSEQ_CURRENT_INDEX_WIDTH              6  /* WSEQ_CURRENT_INDEX - [9:4] */
+#define WM8904_WSEQ_BUSY                        0x0001  /* WSEQ_BUSY */
+#define WM8904_WSEQ_BUSY_MASK                   0x0001  /* WSEQ_BUSY */
+#define WM8904_WSEQ_BUSY_SHIFT                       0  /* WSEQ_BUSY */
+#define WM8904_WSEQ_BUSY_WIDTH                       1  /* WSEQ_BUSY */
+
+/*
+ * R116 (0x74) - FLL Control 1
+ */
+#define WM8904_FLL_FRACN_ENA                    0x0004  /* FLL_FRACN_ENA */
+#define WM8904_FLL_FRACN_ENA_MASK               0x0004  /* FLL_FRACN_ENA */
+#define WM8904_FLL_FRACN_ENA_SHIFT                   2  /* FLL_FRACN_ENA */
+#define WM8904_FLL_FRACN_ENA_WIDTH                   1  /* FLL_FRACN_ENA */
+#define WM8904_FLL_OSC_ENA                      0x0002  /* FLL_OSC_ENA */
+#define WM8904_FLL_OSC_ENA_MASK                 0x0002  /* FLL_OSC_ENA */
+#define WM8904_FLL_OSC_ENA_SHIFT                     1  /* FLL_OSC_ENA */
+#define WM8904_FLL_OSC_ENA_WIDTH                     1  /* FLL_OSC_ENA */
+#define WM8904_FLL_ENA                          0x0001  /* FLL_ENA */
+#define WM8904_FLL_ENA_MASK                     0x0001  /* FLL_ENA */
+#define WM8904_FLL_ENA_SHIFT                         0  /* FLL_ENA */
+#define WM8904_FLL_ENA_WIDTH                         1  /* FLL_ENA */
+
+/*
+ * R117 (0x75) - FLL Control 2
+ */
+#define WM8904_FLL_OUTDIV_MASK                  0x3F00  /* FLL_OUTDIV - [13:8] */
+#define WM8904_FLL_OUTDIV_SHIFT                      8  /* FLL_OUTDIV - [13:8] */
+#define WM8904_FLL_OUTDIV_WIDTH                      6  /* FLL_OUTDIV - [13:8] */
+#define WM8904_FLL_CTRL_RATE_MASK               0x0070  /* FLL_CTRL_RATE - [6:4] */
+#define WM8904_FLL_CTRL_RATE_SHIFT                   4  /* FLL_CTRL_RATE - [6:4] */
+#define WM8904_FLL_CTRL_RATE_WIDTH                   3  /* FLL_CTRL_RATE - [6:4] */
+#define WM8904_FLL_FRATIO_MASK                  0x0007  /* FLL_FRATIO - [2:0] */
+#define WM8904_FLL_FRATIO_SHIFT                      0  /* FLL_FRATIO - [2:0] */
+#define WM8904_FLL_FRATIO_WIDTH                      3  /* FLL_FRATIO - [2:0] */
+
+/*
+ * R118 (0x76) - FLL Control 3
+ */
+#define WM8904_FLL_K_MASK                       0xFFFF  /* FLL_K - [15:0] */
+#define WM8904_FLL_K_SHIFT                           0  /* FLL_K - [15:0] */
+#define WM8904_FLL_K_WIDTH                          16  /* FLL_K - [15:0] */
+
+/*
+ * R119 (0x77) - FLL Control 4
+ */
+#define WM8904_FLL_N_MASK                       0x7FE0  /* FLL_N - [14:5] */
+#define WM8904_FLL_N_SHIFT                           5  /* FLL_N - [14:5] */
+#define WM8904_FLL_N_WIDTH                          10  /* FLL_N - [14:5] */
+#define WM8904_FLL_GAIN_MASK                    0x000F  /* FLL_GAIN - [3:0] */
+#define WM8904_FLL_GAIN_SHIFT                        0  /* FLL_GAIN - [3:0] */
+#define WM8904_FLL_GAIN_WIDTH                        4  /* FLL_GAIN - [3:0] */
+
+/*
+ * R120 (0x78) - FLL Control 5
+ */
+#define WM8904_FLL_CLK_REF_DIV_MASK             0x0018  /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8904_FLL_CLK_REF_DIV_SHIFT                 3  /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8904_FLL_CLK_REF_DIV_WIDTH                 2  /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8904_FLL_CLK_REF_SRC_MASK             0x0003  /* FLL_CLK_REF_SRC - [1:0] */
+#define WM8904_FLL_CLK_REF_SRC_SHIFT                 0  /* FLL_CLK_REF_SRC - [1:0] */
+#define WM8904_FLL_CLK_REF_SRC_WIDTH                 2  /* FLL_CLK_REF_SRC - [1:0] */
+
+/*
+ * R126 (0x7E) - Digital Pulls
+ */
+#define WM8904_MCLK_PU                          0x0080  /* MCLK_PU */
+#define WM8904_MCLK_PU_MASK                     0x0080  /* MCLK_PU */
+#define WM8904_MCLK_PU_SHIFT                         7  /* MCLK_PU */
+#define WM8904_MCLK_PU_WIDTH                         1  /* MCLK_PU */
+#define WM8904_MCLK_PD                          0x0040  /* MCLK_PD */
+#define WM8904_MCLK_PD_MASK                     0x0040  /* MCLK_PD */
+#define WM8904_MCLK_PD_SHIFT                         6  /* MCLK_PD */
+#define WM8904_MCLK_PD_WIDTH                         1  /* MCLK_PD */
+#define WM8904_DACDAT_PU                        0x0020  /* DACDAT_PU */
+#define WM8904_DACDAT_PU_MASK                   0x0020  /* DACDAT_PU */
+#define WM8904_DACDAT_PU_SHIFT                       5  /* DACDAT_PU */
+#define WM8904_DACDAT_PU_WIDTH                       1  /* DACDAT_PU */
+#define WM8904_DACDAT_PD                        0x0010  /* DACDAT_PD */
+#define WM8904_DACDAT_PD_MASK                   0x0010  /* DACDAT_PD */
+#define WM8904_DACDAT_PD_SHIFT                       4  /* DACDAT_PD */
+#define WM8904_DACDAT_PD_WIDTH                       1  /* DACDAT_PD */
+#define WM8904_LRCLK_PU                         0x0008  /* LRCLK_PU */
+#define WM8904_LRCLK_PU_MASK                    0x0008  /* LRCLK_PU */
+#define WM8904_LRCLK_PU_SHIFT                        3  /* LRCLK_PU */
+#define WM8904_LRCLK_PU_WIDTH                        1  /* LRCLK_PU */
+#define WM8904_LRCLK_PD                         0x0004  /* LRCLK_PD */
+#define WM8904_LRCLK_PD_MASK                    0x0004  /* LRCLK_PD */
+#define WM8904_LRCLK_PD_SHIFT                        2  /* LRCLK_PD */
+#define WM8904_LRCLK_PD_WIDTH                        1  /* LRCLK_PD */
+#define WM8904_BCLK_PU                          0x0002  /* BCLK_PU */
+#define WM8904_BCLK_PU_MASK                     0x0002  /* BCLK_PU */
+#define WM8904_BCLK_PU_SHIFT                         1  /* BCLK_PU */
+#define WM8904_BCLK_PU_WIDTH                         1  /* BCLK_PU */
+#define WM8904_BCLK_PD                          0x0001  /* BCLK_PD */
+#define WM8904_BCLK_PD_MASK                     0x0001  /* BCLK_PD */
+#define WM8904_BCLK_PD_SHIFT                         0  /* BCLK_PD */
+#define WM8904_BCLK_PD_WIDTH                         1  /* BCLK_PD */
+
+/*
+ * R127 (0x7F) - Interrupt Status
+ */
+#define WM8904_IRQ                              0x0400  /* IRQ */
+#define WM8904_IRQ_MASK                         0x0400  /* IRQ */
+#define WM8904_IRQ_SHIFT                            10  /* IRQ */
+#define WM8904_IRQ_WIDTH                             1  /* IRQ */
+#define WM8904_GPIO_BCLK_EINT                   0x0200  /* GPIO_BCLK_EINT */
+#define WM8904_GPIO_BCLK_EINT_MASK              0x0200  /* GPIO_BCLK_EINT */
+#define WM8904_GPIO_BCLK_EINT_SHIFT                  9  /* GPIO_BCLK_EINT */
+#define WM8904_GPIO_BCLK_EINT_WIDTH                  1  /* GPIO_BCLK_EINT */
+#define WM8904_WSEQ_EINT                        0x0100  /* WSEQ_EINT */
+#define WM8904_WSEQ_EINT_MASK                   0x0100  /* WSEQ_EINT */
+#define WM8904_WSEQ_EINT_SHIFT                       8  /* WSEQ_EINT */
+#define WM8904_WSEQ_EINT_WIDTH                       1  /* WSEQ_EINT */
+#define WM8904_GPIO3_EINT                       0x0080  /* GPIO3_EINT */
+#define WM8904_GPIO3_EINT_MASK                  0x0080  /* GPIO3_EINT */
+#define WM8904_GPIO3_EINT_SHIFT                      7  /* GPIO3_EINT */
+#define WM8904_GPIO3_EINT_WIDTH                      1  /* GPIO3_EINT */
+#define WM8904_GPIO2_EINT                       0x0040  /* GPIO2_EINT */
+#define WM8904_GPIO2_EINT_MASK                  0x0040  /* GPIO2_EINT */
+#define WM8904_GPIO2_EINT_SHIFT                      6  /* GPIO2_EINT */
+#define WM8904_GPIO2_EINT_WIDTH                      1  /* GPIO2_EINT */
+#define WM8904_GPIO1_EINT                       0x0020  /* GPIO1_EINT */
+#define WM8904_GPIO1_EINT_MASK                  0x0020  /* GPIO1_EINT */
+#define WM8904_GPIO1_EINT_SHIFT                      5  /* GPIO1_EINT */
+#define WM8904_GPIO1_EINT_WIDTH                      1  /* GPIO1_EINT */
+#define WM8904_GPI8_EINT                        0x0010  /* GPI8_EINT */
+#define WM8904_GPI8_EINT_MASK                   0x0010  /* GPI8_EINT */
+#define WM8904_GPI8_EINT_SHIFT                       4  /* GPI8_EINT */
+#define WM8904_GPI8_EINT_WIDTH                       1  /* GPI8_EINT */
+#define WM8904_GPI7_EINT                        0x0008  /* GPI7_EINT */
+#define WM8904_GPI7_EINT_MASK                   0x0008  /* GPI7_EINT */
+#define WM8904_GPI7_EINT_SHIFT                       3  /* GPI7_EINT */
+#define WM8904_GPI7_EINT_WIDTH                       1  /* GPI7_EINT */
+#define WM8904_FLL_LOCK_EINT                    0x0004  /* FLL_LOCK_EINT */
+#define WM8904_FLL_LOCK_EINT_MASK               0x0004  /* FLL_LOCK_EINT */
+#define WM8904_FLL_LOCK_EINT_SHIFT                   2  /* FLL_LOCK_EINT */
+#define WM8904_FLL_LOCK_EINT_WIDTH                   1  /* FLL_LOCK_EINT */
+#define WM8904_MIC_SHRT_EINT                    0x0002  /* MIC_SHRT_EINT */
+#define WM8904_MIC_SHRT_EINT_MASK               0x0002  /* MIC_SHRT_EINT */
+#define WM8904_MIC_SHRT_EINT_SHIFT                   1  /* MIC_SHRT_EINT */
+#define WM8904_MIC_SHRT_EINT_WIDTH                   1  /* MIC_SHRT_EINT */
+#define WM8904_MIC_DET_EINT                     0x0001  /* MIC_DET_EINT */
+#define WM8904_MIC_DET_EINT_MASK                0x0001  /* MIC_DET_EINT */
+#define WM8904_MIC_DET_EINT_SHIFT                    0  /* MIC_DET_EINT */
+#define WM8904_MIC_DET_EINT_WIDTH                    1  /* MIC_DET_EINT */
+
+/*
+ * R128 (0x80) - Interrupt Status Mask
+ */
+#define WM8904_IM_GPIO_BCLK_EINT                0x0200  /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_GPIO_BCLK_EINT_MASK           0x0200  /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_GPIO_BCLK_EINT_SHIFT               9  /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_GPIO_BCLK_EINT_WIDTH               1  /* IM_GPIO_BCLK_EINT */
+#define WM8904_IM_WSEQ_EINT                     0x0100  /* IM_WSEQ_EINT */
+#define WM8904_IM_WSEQ_EINT_MASK                0x0100  /* IM_WSEQ_EINT */
+#define WM8904_IM_WSEQ_EINT_SHIFT                    8  /* IM_WSEQ_EINT */
+#define WM8904_IM_WSEQ_EINT_WIDTH                    1  /* IM_WSEQ_EINT */
+#define WM8904_IM_GPIO3_EINT                    0x0080  /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO3_EINT_MASK               0x0080  /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO3_EINT_SHIFT                   7  /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO3_EINT_WIDTH                   1  /* IM_GPIO3_EINT */
+#define WM8904_IM_GPIO2_EINT                    0x0040  /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO2_EINT_MASK               0x0040  /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO2_EINT_SHIFT                   6  /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO2_EINT_WIDTH                   1  /* IM_GPIO2_EINT */
+#define WM8904_IM_GPIO1_EINT                    0x0020  /* IM_GPIO1_EINT */
+#define WM8904_IM_GPIO1_EINT_MASK               0x0020  /* IM_GPIO1_EINT */
+#define WM8904_IM_GPIO1_EINT_SHIFT                   5  /* IM_GPIO1_EINT */
+#define WM8904_IM_GPIO1_EINT_WIDTH                   1  /* IM_GPIO1_EINT */
+#define WM8904_IM_GPI8_EINT                     0x0010  /* IM_GPI8_EINT */
+#define WM8904_IM_GPI8_EINT_MASK                0x0010  /* IM_GPI8_EINT */
+#define WM8904_IM_GPI8_EINT_SHIFT                    4  /* IM_GPI8_EINT */
+#define WM8904_IM_GPI8_EINT_WIDTH                    1  /* IM_GPI8_EINT */
+#define WM8904_IM_GPI7_EINT                     0x0008  /* IM_GPI7_EINT */
+#define WM8904_IM_GPI7_EINT_MASK                0x0008  /* IM_GPI7_EINT */
+#define WM8904_IM_GPI7_EINT_SHIFT                    3  /* IM_GPI7_EINT */
+#define WM8904_IM_GPI7_EINT_WIDTH                    1  /* IM_GPI7_EINT */
+#define WM8904_IM_FLL_LOCK_EINT                 0x0004  /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_FLL_LOCK_EINT_MASK            0x0004  /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_FLL_LOCK_EINT_SHIFT                2  /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_FLL_LOCK_EINT_WIDTH                1  /* IM_FLL_LOCK_EINT */
+#define WM8904_IM_MIC_SHRT_EINT                 0x0002  /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_SHRT_EINT_MASK            0x0002  /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_SHRT_EINT_SHIFT                1  /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_SHRT_EINT_WIDTH                1  /* IM_MIC_SHRT_EINT */
+#define WM8904_IM_MIC_DET_EINT                  0x0001  /* IM_MIC_DET_EINT */
+#define WM8904_IM_MIC_DET_EINT_MASK             0x0001  /* IM_MIC_DET_EINT */
+#define WM8904_IM_MIC_DET_EINT_SHIFT                 0  /* IM_MIC_DET_EINT */
+#define WM8904_IM_MIC_DET_EINT_WIDTH                 1  /* IM_MIC_DET_EINT */
+
+/*
+ * R129 (0x81) - Interrupt Polarity
+ */
+#define WM8904_GPIO_BCLK_EINT_POL               0x0200  /* GPIO_BCLK_EINT_POL */
+#define WM8904_GPIO_BCLK_EINT_POL_MASK          0x0200  /* GPIO_BCLK_EINT_POL */
+#define WM8904_GPIO_BCLK_EINT_POL_SHIFT              9  /* GPIO_BCLK_EINT_POL */
+#define WM8904_GPIO_BCLK_EINT_POL_WIDTH              1  /* GPIO_BCLK_EINT_POL */
+#define WM8904_WSEQ_EINT_POL                    0x0100  /* WSEQ_EINT_POL */
+#define WM8904_WSEQ_EINT_POL_MASK               0x0100  /* WSEQ_EINT_POL */
+#define WM8904_WSEQ_EINT_POL_SHIFT                   8  /* WSEQ_EINT_POL */
+#define WM8904_WSEQ_EINT_POL_WIDTH                   1  /* WSEQ_EINT_POL */
+#define WM8904_GPIO3_EINT_POL                   0x0080  /* GPIO3_EINT_POL */
+#define WM8904_GPIO3_EINT_POL_MASK              0x0080  /* GPIO3_EINT_POL */
+#define WM8904_GPIO3_EINT_POL_SHIFT                  7  /* GPIO3_EINT_POL */
+#define WM8904_GPIO3_EINT_POL_WIDTH                  1  /* GPIO3_EINT_POL */
+#define WM8904_GPIO2_EINT_POL                   0x0040  /* GPIO2_EINT_POL */
+#define WM8904_GPIO2_EINT_POL_MASK              0x0040  /* GPIO2_EINT_POL */
+#define WM8904_GPIO2_EINT_POL_SHIFT                  6  /* GPIO2_EINT_POL */
+#define WM8904_GPIO2_EINT_POL_WIDTH                  1  /* GPIO2_EINT_POL */
+#define WM8904_GPIO1_EINT_POL                   0x0020  /* GPIO1_EINT_POL */
+#define WM8904_GPIO1_EINT_POL_MASK              0x0020  /* GPIO1_EINT_POL */
+#define WM8904_GPIO1_EINT_POL_SHIFT                  5  /* GPIO1_EINT_POL */
+#define WM8904_GPIO1_EINT_POL_WIDTH                  1  /* GPIO1_EINT_POL */
+#define WM8904_GPI8_EINT_POL                    0x0010  /* GPI8_EINT_POL */
+#define WM8904_GPI8_EINT_POL_MASK               0x0010  /* GPI8_EINT_POL */
+#define WM8904_GPI8_EINT_POL_SHIFT                   4  /* GPI8_EINT_POL */
+#define WM8904_GPI8_EINT_POL_WIDTH                   1  /* GPI8_EINT_POL */
+#define WM8904_GPI7_EINT_POL                    0x0008  /* GPI7_EINT_POL */
+#define WM8904_GPI7_EINT_POL_MASK               0x0008  /* GPI7_EINT_POL */
+#define WM8904_GPI7_EINT_POL_SHIFT                   3  /* GPI7_EINT_POL */
+#define WM8904_GPI7_EINT_POL_WIDTH                   1  /* GPI7_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL                0x0004  /* FLL_LOCK_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL_MASK           0x0004  /* FLL_LOCK_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL_SHIFT               2  /* FLL_LOCK_EINT_POL */
+#define WM8904_FLL_LOCK_EINT_POL_WIDTH               1  /* FLL_LOCK_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL                0x0002  /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL_MASK           0x0002  /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL_SHIFT               1  /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_SHRT_EINT_POL_WIDTH               1  /* MIC_SHRT_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL                 0x0001  /* MIC_DET_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL_MASK            0x0001  /* MIC_DET_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL_SHIFT                0  /* MIC_DET_EINT_POL */
+#define WM8904_MIC_DET_EINT_POL_WIDTH                1  /* MIC_DET_EINT_POL */
+
+/*
+ * R130 (0x82) - Interrupt Debounce
+ */
+#define WM8904_GPIO_BCLK_EINT_DB                0x0200  /* GPIO_BCLK_EINT_DB */
+#define WM8904_GPIO_BCLK_EINT_DB_MASK           0x0200  /* GPIO_BCLK_EINT_DB */
+#define WM8904_GPIO_BCLK_EINT_DB_SHIFT               9  /* GPIO_BCLK_EINT_DB */
+#define WM8904_GPIO_BCLK_EINT_DB_WIDTH               1  /* GPIO_BCLK_EINT_DB */
+#define WM8904_WSEQ_EINT_DB                     0x0100  /* WSEQ_EINT_DB */
+#define WM8904_WSEQ_EINT_DB_MASK                0x0100  /* WSEQ_EINT_DB */
+#define WM8904_WSEQ_EINT_DB_SHIFT                    8  /* WSEQ_EINT_DB */
+#define WM8904_WSEQ_EINT_DB_WIDTH                    1  /* WSEQ_EINT_DB */
+#define WM8904_GPIO3_EINT_DB                    0x0080  /* GPIO3_EINT_DB */
+#define WM8904_GPIO3_EINT_DB_MASK               0x0080  /* GPIO3_EINT_DB */
+#define WM8904_GPIO3_EINT_DB_SHIFT                   7  /* GPIO3_EINT_DB */
+#define WM8904_GPIO3_EINT_DB_WIDTH                   1  /* GPIO3_EINT_DB */
+#define WM8904_GPIO2_EINT_DB                    0x0040  /* GPIO2_EINT_DB */
+#define WM8904_GPIO2_EINT_DB_MASK               0x0040  /* GPIO2_EINT_DB */
+#define WM8904_GPIO2_EINT_DB_SHIFT                   6  /* GPIO2_EINT_DB */
+#define WM8904_GPIO2_EINT_DB_WIDTH                   1  /* GPIO2_EINT_DB */
+#define WM8904_GPIO1_EINT_DB                    0x0020  /* GPIO1_EINT_DB */
+#define WM8904_GPIO1_EINT_DB_MASK               0x0020  /* GPIO1_EINT_DB */
+#define WM8904_GPIO1_EINT_DB_SHIFT                   5  /* GPIO1_EINT_DB */
+#define WM8904_GPIO1_EINT_DB_WIDTH                   1  /* GPIO1_EINT_DB */
+#define WM8904_GPI8_EINT_DB                     0x0010  /* GPI8_EINT_DB */
+#define WM8904_GPI8_EINT_DB_MASK                0x0010  /* GPI8_EINT_DB */
+#define WM8904_GPI8_EINT_DB_SHIFT                    4  /* GPI8_EINT_DB */
+#define WM8904_GPI8_EINT_DB_WIDTH                    1  /* GPI8_EINT_DB */
+#define WM8904_GPI7_EINT_DB                     0x0008  /* GPI7_EINT_DB */
+#define WM8904_GPI7_EINT_DB_MASK                0x0008  /* GPI7_EINT_DB */
+#define WM8904_GPI7_EINT_DB_SHIFT                    3  /* GPI7_EINT_DB */
+#define WM8904_GPI7_EINT_DB_WIDTH                    1  /* GPI7_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB                 0x0004  /* FLL_LOCK_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB_MASK            0x0004  /* FLL_LOCK_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB_SHIFT                2  /* FLL_LOCK_EINT_DB */
+#define WM8904_FLL_LOCK_EINT_DB_WIDTH                1  /* FLL_LOCK_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB                 0x0002  /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB_MASK            0x0002  /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB_SHIFT                1  /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_SHRT_EINT_DB_WIDTH                1  /* MIC_SHRT_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB                  0x0001  /* MIC_DET_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB_MASK             0x0001  /* MIC_DET_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB_SHIFT                 0  /* MIC_DET_EINT_DB */
+#define WM8904_MIC_DET_EINT_DB_WIDTH                 1  /* MIC_DET_EINT_DB */
+
+/*
+ * R134 (0x86) - EQ1
+ */
+#define WM8904_EQ_ENA                           0x0001  /* EQ_ENA */
+#define WM8904_EQ_ENA_MASK                      0x0001  /* EQ_ENA */
+#define WM8904_EQ_ENA_SHIFT                          0  /* EQ_ENA */
+#define WM8904_EQ_ENA_WIDTH                          1  /* EQ_ENA */
+
+/*
+ * R135 (0x87) - EQ2
+ */
+#define WM8904_EQ_B1_GAIN_MASK                  0x001F  /* EQ_B1_GAIN - [4:0] */
+#define WM8904_EQ_B1_GAIN_SHIFT                      0  /* EQ_B1_GAIN - [4:0] */
+#define WM8904_EQ_B1_GAIN_WIDTH                      5  /* EQ_B1_GAIN - [4:0] */
+
+/*
+ * R136 (0x88) - EQ3
+ */
+#define WM8904_EQ_B2_GAIN_MASK                  0x001F  /* EQ_B2_GAIN - [4:0] */
+#define WM8904_EQ_B2_GAIN_SHIFT                      0  /* EQ_B2_GAIN - [4:0] */
+#define WM8904_EQ_B2_GAIN_WIDTH                      5  /* EQ_B2_GAIN - [4:0] */
+
+/*
+ * R137 (0x89) - EQ4
+ */
+#define WM8904_EQ_B3_GAIN_MASK                  0x001F  /* EQ_B3_GAIN - [4:0] */
+#define WM8904_EQ_B3_GAIN_SHIFT                      0  /* EQ_B3_GAIN - [4:0] */
+#define WM8904_EQ_B3_GAIN_WIDTH                      5  /* EQ_B3_GAIN - [4:0] */
+
+/*
+ * R138 (0x8A) - EQ5
+ */
+#define WM8904_EQ_B4_GAIN_MASK                  0x001F  /* EQ_B4_GAIN - [4:0] */
+#define WM8904_EQ_B4_GAIN_SHIFT                      0  /* EQ_B4_GAIN - [4:0] */
+#define WM8904_EQ_B4_GAIN_WIDTH                      5  /* EQ_B4_GAIN - [4:0] */
+
+/*
+ * R139 (0x8B) - EQ6
+ */
+#define WM8904_EQ_B5_GAIN_MASK                  0x001F  /* EQ_B5_GAIN - [4:0] */
+#define WM8904_EQ_B5_GAIN_SHIFT                      0  /* EQ_B5_GAIN - [4:0] */
+#define WM8904_EQ_B5_GAIN_WIDTH                      5  /* EQ_B5_GAIN - [4:0] */
+
+/*
+ * R140 (0x8C) - EQ7
+ */
+#define WM8904_EQ_B1_A_MASK                     0xFFFF  /* EQ_B1_A - [15:0] */
+#define WM8904_EQ_B1_A_SHIFT                         0  /* EQ_B1_A - [15:0] */
+#define WM8904_EQ_B1_A_WIDTH                        16  /* EQ_B1_A - [15:0] */
+
+/*
+ * R141 (0x8D) - EQ8
+ */
+#define WM8904_EQ_B1_B_MASK                     0xFFFF  /* EQ_B1_B - [15:0] */
+#define WM8904_EQ_B1_B_SHIFT                         0  /* EQ_B1_B - [15:0] */
+#define WM8904_EQ_B1_B_WIDTH                        16  /* EQ_B1_B - [15:0] */
+
+/*
+ * R142 (0x8E) - EQ9
+ */
+#define WM8904_EQ_B1_PG_MASK                    0xFFFF  /* EQ_B1_PG - [15:0] */
+#define WM8904_EQ_B1_PG_SHIFT                        0  /* EQ_B1_PG - [15:0] */
+#define WM8904_EQ_B1_PG_WIDTH                       16  /* EQ_B1_PG - [15:0] */
+
+/*
+ * R143 (0x8F) - EQ10
+ */
+#define WM8904_EQ_B2_A_MASK                     0xFFFF  /* EQ_B2_A - [15:0] */
+#define WM8904_EQ_B2_A_SHIFT                         0  /* EQ_B2_A - [15:0] */
+#define WM8904_EQ_B2_A_WIDTH                        16  /* EQ_B2_A - [15:0] */
+
+/*
+ * R144 (0x90) - EQ11
+ */
+#define WM8904_EQ_B2_B_MASK                     0xFFFF  /* EQ_B2_B - [15:0] */
+#define WM8904_EQ_B2_B_SHIFT                         0  /* EQ_B2_B - [15:0] */
+#define WM8904_EQ_B2_B_WIDTH                        16  /* EQ_B2_B - [15:0] */
+
+/*
+ * R145 (0x91) - EQ12
+ */
+#define WM8904_EQ_B2_C_MASK                     0xFFFF  /* EQ_B2_C - [15:0] */
+#define WM8904_EQ_B2_C_SHIFT                         0  /* EQ_B2_C - [15:0] */
+#define WM8904_EQ_B2_C_WIDTH                        16  /* EQ_B2_C - [15:0] */
+
+/*
+ * R146 (0x92) - EQ13
+ */
+#define WM8904_EQ_B2_PG_MASK                    0xFFFF  /* EQ_B2_PG - [15:0] */
+#define WM8904_EQ_B2_PG_SHIFT                        0  /* EQ_B2_PG - [15:0] */
+#define WM8904_EQ_B2_PG_WIDTH                       16  /* EQ_B2_PG - [15:0] */
+
+/*
+ * R147 (0x93) - EQ14
+ */
+#define WM8904_EQ_B3_A_MASK                     0xFFFF  /* EQ_B3_A - [15:0] */
+#define WM8904_EQ_B3_A_SHIFT                         0  /* EQ_B3_A - [15:0] */
+#define WM8904_EQ_B3_A_WIDTH                        16  /* EQ_B3_A - [15:0] */
+
+/*
+ * R148 (0x94) - EQ15
+ */
+#define WM8904_EQ_B3_B_MASK                     0xFFFF  /* EQ_B3_B - [15:0] */
+#define WM8904_EQ_B3_B_SHIFT                         0  /* EQ_B3_B - [15:0] */
+#define WM8904_EQ_B3_B_WIDTH                        16  /* EQ_B3_B - [15:0] */
+
+/*
+ * R149 (0x95) - EQ16
+ */
+#define WM8904_EQ_B3_C_MASK                     0xFFFF  /* EQ_B3_C - [15:0] */
+#define WM8904_EQ_B3_C_SHIFT                         0  /* EQ_B3_C - [15:0] */
+#define WM8904_EQ_B3_C_WIDTH                        16  /* EQ_B3_C - [15:0] */
+
+/*
+ * R150 (0x96) - EQ17
+ */
+#define WM8904_EQ_B3_PG_MASK                    0xFFFF  /* EQ_B3_PG - [15:0] */
+#define WM8904_EQ_B3_PG_SHIFT                        0  /* EQ_B3_PG - [15:0] */
+#define WM8904_EQ_B3_PG_WIDTH                       16  /* EQ_B3_PG - [15:0] */
+
+/*
+ * R151 (0x97) - EQ18
+ */
+#define WM8904_EQ_B4_A_MASK                     0xFFFF  /* EQ_B4_A - [15:0] */
+#define WM8904_EQ_B4_A_SHIFT                         0  /* EQ_B4_A - [15:0] */
+#define WM8904_EQ_B4_A_WIDTH                        16  /* EQ_B4_A - [15:0] */
+
+/*
+ * R152 (0x98) - EQ19
+ */
+#define WM8904_EQ_B4_B_MASK                     0xFFFF  /* EQ_B4_B - [15:0] */
+#define WM8904_EQ_B4_B_SHIFT                         0  /* EQ_B4_B - [15:0] */
+#define WM8904_EQ_B4_B_WIDTH                        16  /* EQ_B4_B - [15:0] */
+
+/*
+ * R153 (0x99) - EQ20
+ */
+#define WM8904_EQ_B4_C_MASK                     0xFFFF  /* EQ_B4_C - [15:0] */
+#define WM8904_EQ_B4_C_SHIFT                         0  /* EQ_B4_C - [15:0] */
+#define WM8904_EQ_B4_C_WIDTH                        16  /* EQ_B4_C - [15:0] */
+
+/*
+ * R154 (0x9A) - EQ21
+ */
+#define WM8904_EQ_B4_PG_MASK                    0xFFFF  /* EQ_B4_PG - [15:0] */
+#define WM8904_EQ_B4_PG_SHIFT                        0  /* EQ_B4_PG - [15:0] */
+#define WM8904_EQ_B4_PG_WIDTH                       16  /* EQ_B4_PG - [15:0] */
+
+/*
+ * R155 (0x9B) - EQ22
+ */
+#define WM8904_EQ_B5_A_MASK                     0xFFFF  /* EQ_B5_A - [15:0] */
+#define WM8904_EQ_B5_A_SHIFT                         0  /* EQ_B5_A - [15:0] */
+#define WM8904_EQ_B5_A_WIDTH                        16  /* EQ_B5_A - [15:0] */
+
+/*
+ * R156 (0x9C) - EQ23
+ */
+#define WM8904_EQ_B5_B_MASK                     0xFFFF  /* EQ_B5_B - [15:0] */
+#define WM8904_EQ_B5_B_SHIFT                         0  /* EQ_B5_B - [15:0] */
+#define WM8904_EQ_B5_B_WIDTH                        16  /* EQ_B5_B - [15:0] */
+
+/*
+ * R157 (0x9D) - EQ24
+ */
+#define WM8904_EQ_B5_PG_MASK                    0xFFFF  /* EQ_B5_PG - [15:0] */
+#define WM8904_EQ_B5_PG_SHIFT                        0  /* EQ_B5_PG - [15:0] */
+#define WM8904_EQ_B5_PG_WIDTH                       16  /* EQ_B5_PG - [15:0] */
+
+/*
+ * R161 (0xA1) - Control Interface Test 1
+ */
+#define WM8904_USER_KEY                         0x0002  /* USER_KEY */
+#define WM8904_USER_KEY_MASK                    0x0002  /* USER_KEY */
+#define WM8904_USER_KEY_SHIFT                        1  /* USER_KEY */
+#define WM8904_USER_KEY_WIDTH                        1  /* USER_KEY */
+
+/*
+ * R204 (0xCC) - Analogue Output Bias 0
+ */
+#define WM8904_PGA_BIAS_MASK                    0x0070  /* PGA_BIAS - [6:4] */
+#define WM8904_PGA_BIAS_SHIFT                        4  /* PGA_BIAS - [6:4] */
+#define WM8904_PGA_BIAS_WIDTH                        3  /* PGA_BIAS - [6:4] */
+
+/*
+ * R247 (0xF7) - FLL NCO Test 0
+ */
+#define WM8904_FLL_FRC_NCO                      0x0001  /* FLL_FRC_NCO */
+#define WM8904_FLL_FRC_NCO_MASK                 0x0001  /* FLL_FRC_NCO */
+#define WM8904_FLL_FRC_NCO_SHIFT                     0  /* FLL_FRC_NCO */
+#define WM8904_FLL_FRC_NCO_WIDTH                     1  /* FLL_FRC_NCO */
+
+/*
+ * R248 (0xF8) - FLL NCO Test 1
+ */
+#define WM8904_FLL_FRC_NCO_VAL_MASK             0x003F  /* FLL_FRC_NCO_VAL - [5:0] */
+#define WM8904_FLL_FRC_NCO_VAL_SHIFT                 0  /* FLL_FRC_NCO_VAL - [5:0] */
+#define WM8904_FLL_FRC_NCO_VAL_WIDTH                 6  /* FLL_FRC_NCO_VAL - [5:0] */
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8940.c b/linux/sound/soc/codecs/wm8940.c
new file mode 100644
index 0000000..23086e2
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8940.c
@@ -0,0 +1,828 @@
+/*
+ * wm8940.c  --  WM8940 ALSA Soc Audio driver
+ *
+ * Author: Jonathan Cameron <jic23@cam.ac.uk>
+ *
+ * Based on wm8510.c
+ *    Copyright  2006 Wolfson Microelectronics PLC.
+ *    Author:  Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Not currently handled:
+ * Notch filter control
+ * AUXMode (inverting vs mixer)
+ * No means to obtain current gain if alc enabled.
+ * No use made of gpio
+ * Fast VMID discharge for power down
+ * Soft Start
+ * DLR and ALR Swaps not enabled
+ * Digital Sidetone not supported
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8940.h"
+
+struct wm8940_priv {
+	unsigned int sysclk;
+	u16 reg_cache[WM8940_CACHEREGNUM];
+	enum snd_soc_control_type control_type;
+	void *control_data;
+};
+
+static u16 wm8940_reg_defaults[] = {
+	0x8940, /* Soft Reset */
+	0x0000, /* Power 1 */
+	0x0000, /* Power 2 */
+	0x0000, /* Power 3 */
+	0x0010, /* Interface Control */
+	0x0000, /* Companding Control */
+	0x0140, /* Clock Control */
+	0x0000, /* Additional Controls */
+	0x0000, /* GPIO Control */
+	0x0002, /* Auto Increment Control */
+	0x0000, /* DAC Control */
+	0x00FF, /* DAC Volume */
+	0,
+	0,
+	0x0100, /* ADC Control */
+	0x00FF, /* ADC Volume */
+	0x0000, /* Notch Filter 1 Control 1 */
+	0x0000, /* Notch Filter 1 Control 2 */
+	0x0000, /* Notch Filter 2 Control 1 */
+	0x0000, /* Notch Filter 2 Control 2 */
+	0x0000, /* Notch Filter 3 Control 1 */
+	0x0000, /* Notch Filter 3 Control 2 */
+	0x0000, /* Notch Filter 4 Control 1 */
+	0x0000, /* Notch Filter 4 Control 2 */
+	0x0032, /* DAC Limit Control 1 */
+	0x0000, /* DAC Limit Control 2 */
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0x0038, /* ALC Control 1 */
+	0x000B, /* ALC Control 2 */
+	0x0032, /* ALC Control 3 */
+	0x0000, /* Noise Gate */
+	0x0041, /* PLLN */
+	0x000C, /* PLLK1 */
+	0x0093, /* PLLK2 */
+	0x00E9, /* PLLK3 */
+	0,
+	0,
+	0x0030, /* ALC Control 4 */
+	0,
+	0x0002, /* Input Control */
+	0x0050, /* PGA Gain */
+	0,
+	0x0002, /* ADC Boost Control */
+	0,
+	0x0002, /* Output Control */
+	0x0000, /* Speaker Mixer Control */
+	0,
+	0,
+	0,
+	0x0079, /* Speaker Volume */
+	0,
+	0x0000, /* Mono Mixer Control */
+};
+
+static const char *wm8940_companding[] = { "Off", "NC", "u-law", "A-law" };
+static const struct soc_enum wm8940_adc_companding_enum
+= SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 1, 4, wm8940_companding);
+static const struct soc_enum wm8940_dac_companding_enum
+= SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 3, 4, wm8940_companding);
+
+static const char *wm8940_alc_mode_text[] = {"ALC", "Limiter"};
+static const struct soc_enum wm8940_alc_mode_enum
+= SOC_ENUM_SINGLE(WM8940_ALC3, 8, 2, wm8940_alc_mode_text);
+
+static const char *wm8940_mic_bias_level_text[] = {"0.9", "0.65"};
+static const struct soc_enum wm8940_mic_bias_level_enum
+= SOC_ENUM_SINGLE(WM8940_INPUTCTL, 8, 2, wm8940_mic_bias_level_text);
+
+static const char *wm8940_filter_mode_text[] = {"Audio", "Application"};
+static const struct soc_enum wm8940_filter_mode_enum
+= SOC_ENUM_SINGLE(WM8940_ADC, 7, 2, wm8940_filter_mode_text);
+
+static DECLARE_TLV_DB_SCALE(wm8940_spk_vol_tlv, -5700, 100, 1);
+static DECLARE_TLV_DB_SCALE(wm8940_att_tlv, -1000, 1000, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_pga_vol_tlv, -1200, 75, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_alc_min_tlv, -1200, 600, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_alc_max_tlv, 675, 600, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_alc_tar_tlv, -2250, 50, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_lim_boost_tlv, 0, 100, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_lim_thresh_tlv, -600, 100, 0);
+static DECLARE_TLV_DB_SCALE(wm8940_adc_tlv, -12750, 50, 1);
+static DECLARE_TLV_DB_SCALE(wm8940_capture_boost_vol_tlv, 0, 2000, 0);
+
+static const struct snd_kcontrol_new wm8940_snd_controls[] = {
+	SOC_SINGLE("Digital Loopback Switch", WM8940_COMPANDINGCTL,
+		   6, 1, 0),
+	SOC_ENUM("DAC Companding", wm8940_dac_companding_enum),
+	SOC_ENUM("ADC Companding", wm8940_adc_companding_enum),
+
+	SOC_ENUM("ALC Mode", wm8940_alc_mode_enum),
+	SOC_SINGLE("ALC Switch", WM8940_ALC1, 8, 1, 0),
+	SOC_SINGLE_TLV("ALC Capture Max Gain", WM8940_ALC1,
+		       3, 7, 1, wm8940_alc_max_tlv),
+	SOC_SINGLE_TLV("ALC Capture Min Gain", WM8940_ALC1,
+		       0, 7, 0, wm8940_alc_min_tlv),
+	SOC_SINGLE_TLV("ALC Capture Target", WM8940_ALC2,
+		       0, 14, 0, wm8940_alc_tar_tlv),
+	SOC_SINGLE("ALC Capture Hold", WM8940_ALC2, 4, 10, 0),
+	SOC_SINGLE("ALC Capture Decay", WM8940_ALC3, 4, 10, 0),
+	SOC_SINGLE("ALC Capture Attach", WM8940_ALC3, 0, 10, 0),
+	SOC_SINGLE("ALC ZC Switch", WM8940_ALC4, 1, 1, 0),
+	SOC_SINGLE("ALC Capture Noise Gate Switch", WM8940_NOISEGATE,
+		   3, 1, 0),
+	SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8940_NOISEGATE,
+		   0, 7, 0),
+
+	SOC_SINGLE("DAC Playback Limiter Switch", WM8940_DACLIM1, 8, 1, 0),
+	SOC_SINGLE("DAC Playback Limiter Attack", WM8940_DACLIM1, 0, 9, 0),
+	SOC_SINGLE("DAC Playback Limiter Decay", WM8940_DACLIM1, 4, 11, 0),
+	SOC_SINGLE_TLV("DAC Playback Limiter Threshold", WM8940_DACLIM2,
+		       4, 9, 1, wm8940_lim_thresh_tlv),
+	SOC_SINGLE_TLV("DAC Playback Limiter Boost", WM8940_DACLIM2,
+		       0, 12, 0, wm8940_lim_boost_tlv),
+
+	SOC_SINGLE("Capture PGA ZC Switch", WM8940_PGAGAIN, 7, 1, 0),
+	SOC_SINGLE_TLV("Capture PGA Volume", WM8940_PGAGAIN,
+		       0, 63, 0, wm8940_pga_vol_tlv),
+	SOC_SINGLE_TLV("Digital Playback Volume", WM8940_DACVOL,
+		       0, 255, 0, wm8940_adc_tlv),
+	SOC_SINGLE_TLV("Digital Capture Volume", WM8940_ADCVOL,
+		       0, 255, 0, wm8940_adc_tlv),
+	SOC_ENUM("Mic Bias Level", wm8940_mic_bias_level_enum),
+	SOC_SINGLE_TLV("Capture Boost Volue", WM8940_ADCBOOST,
+		       8, 1, 0, wm8940_capture_boost_vol_tlv),
+	SOC_SINGLE_TLV("Speaker Playback Volume", WM8940_SPKVOL,
+		       0, 63, 0, wm8940_spk_vol_tlv),
+	SOC_SINGLE("Speaker Playback Switch", WM8940_SPKVOL,  6, 1, 1),
+
+	SOC_SINGLE_TLV("Speaker Mixer Line Bypass Volume", WM8940_SPKVOL,
+		       8, 1, 1, wm8940_att_tlv),
+	SOC_SINGLE("Speaker Playback ZC Switch", WM8940_SPKVOL, 7, 1, 0),
+
+	SOC_SINGLE("Mono Out Switch", WM8940_MONOMIX, 6, 1, 1),
+	SOC_SINGLE_TLV("Mono Mixer Line Bypass Volume", WM8940_MONOMIX,
+		       7, 1, 1, wm8940_att_tlv),
+
+	SOC_SINGLE("High Pass Filter Switch", WM8940_ADC, 8, 1, 0),
+	SOC_ENUM("High Pass Filter Mode", wm8940_filter_mode_enum),
+	SOC_SINGLE("High Pass Filter Cut Off", WM8940_ADC, 4, 7, 0),
+	SOC_SINGLE("ADC Inversion Switch", WM8940_ADC, 0, 1, 0),
+	SOC_SINGLE("DAC Inversion Switch", WM8940_DAC, 0, 1, 0),
+	SOC_SINGLE("DAC Auto Mute Switch", WM8940_DAC, 2, 1, 0),
+	SOC_SINGLE("ZC Timeout Clock Switch", WM8940_ADDCNTRL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8940_speaker_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_SPKMIX, 1, 1, 0),
+	SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_SPKMIX, 5, 1, 0),
+	SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_SPKMIX, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8940_mono_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_MONOMIX, 1, 1, 0),
+	SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_MONOMIX, 2, 1, 0),
+	SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_MONOMIX, 0, 1, 0),
+};
+
+static DECLARE_TLV_DB_SCALE(wm8940_boost_vol_tlv, -1500, 300, 1);
+static const struct snd_kcontrol_new wm8940_input_boost_controls[] = {
+	SOC_DAPM_SINGLE("Mic PGA Switch", WM8940_PGAGAIN, 6, 1, 1),
+	SOC_DAPM_SINGLE_TLV("Aux Volume", WM8940_ADCBOOST,
+			    0, 7, 0, wm8940_boost_vol_tlv),
+	SOC_DAPM_SINGLE_TLV("Mic Volume", WM8940_ADCBOOST,
+			    4, 7, 0, wm8940_boost_vol_tlv),
+};
+
+static const struct snd_kcontrol_new wm8940_micpga_controls[] = {
+	SOC_DAPM_SINGLE("AUX Switch", WM8940_INPUTCTL, 2, 1, 0),
+	SOC_DAPM_SINGLE("MICP Switch", WM8940_INPUTCTL, 0, 1, 0),
+	SOC_DAPM_SINGLE("MICN Switch", WM8940_INPUTCTL, 1, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8940_dapm_widgets[] = {
+	SND_SOC_DAPM_MIXER("Speaker Mixer", WM8940_POWER3, 2, 0,
+			   &wm8940_speaker_mixer_controls[0],
+			   ARRAY_SIZE(wm8940_speaker_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Mono Mixer", WM8940_POWER3, 3, 0,
+			   &wm8940_mono_mixer_controls[0],
+			   ARRAY_SIZE(wm8940_mono_mixer_controls)),
+	SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8940_POWER3, 0, 0),
+
+	SND_SOC_DAPM_PGA("SpkN Out", WM8940_POWER3, 5, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("SpkP Out", WM8940_POWER3, 6, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Mono Out", WM8940_POWER3, 7, 0, NULL, 0),
+	SND_SOC_DAPM_OUTPUT("MONOOUT"),
+	SND_SOC_DAPM_OUTPUT("SPKOUTP"),
+	SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+
+	SND_SOC_DAPM_PGA("Aux Input", WM8940_POWER1, 6, 0, NULL, 0),
+	SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8940_POWER2, 0, 0),
+	SND_SOC_DAPM_MIXER("Mic PGA", WM8940_POWER2, 2, 0,
+			   &wm8940_micpga_controls[0],
+			   ARRAY_SIZE(wm8940_micpga_controls)),
+	SND_SOC_DAPM_MIXER("Boost Mixer", WM8940_POWER2, 4, 0,
+			   &wm8940_input_boost_controls[0],
+			   ARRAY_SIZE(wm8940_input_boost_controls)),
+	SND_SOC_DAPM_MICBIAS("Mic Bias", WM8940_POWER1, 4, 0),
+
+	SND_SOC_DAPM_INPUT("MICN"),
+	SND_SOC_DAPM_INPUT("MICP"),
+	SND_SOC_DAPM_INPUT("AUX"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Mono output mixer */
+	{"Mono Mixer", "PCM Playback Switch", "DAC"},
+	{"Mono Mixer", "Aux Playback Switch", "Aux Input"},
+	{"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+	/* Speaker output mixer */
+	{"Speaker Mixer", "PCM Playback Switch", "DAC"},
+	{"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
+	{"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+	/* Outputs */
+	{"Mono Out", NULL, "Mono Mixer"},
+	{"MONOOUT", NULL, "Mono Out"},
+	{"SpkN Out", NULL, "Speaker Mixer"},
+	{"SpkP Out", NULL, "Speaker Mixer"},
+	{"SPKOUTN", NULL, "SpkN Out"},
+	{"SPKOUTP", NULL, "SpkP Out"},
+
+	/*  Microphone PGA */
+	{"Mic PGA", "MICN Switch", "MICN"},
+	{"Mic PGA", "MICP Switch", "MICP"},
+	{"Mic PGA", "AUX Switch", "AUX"},
+
+	/* Boost Mixer */
+	{"Boost Mixer", "Mic PGA Switch", "Mic PGA"},
+	{"Boost Mixer", "Mic Volume",  "MICP"},
+	{"Boost Mixer", "Aux Volume", "Aux Input"},
+
+	{"ADC", NULL, "Boost Mixer"},
+};
+
+static int wm8940_add_widgets(struct snd_soc_codec *codec)
+{
+	int ret;
+
+	ret = snd_soc_dapm_new_controls(codec, wm8940_dapm_widgets,
+					ARRAY_SIZE(wm8940_dapm_widgets));
+	if (ret)
+		goto error_ret;
+	ret = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+	if (ret)
+		goto error_ret;
+
+error_ret:
+	return ret;
+}
+
+#define wm8940_reset(c) snd_soc_write(c, WM8940_SOFTRESET, 0);
+
+static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			      unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = snd_soc_read(codec, WM8940_IFACE) & 0xFE67;
+	u16 clk = snd_soc_read(codec, WM8940_CLOCK) & 0x1fe;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		clk |= 1;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_soc_write(codec, WM8940_CLOCK, clk);
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= (2 << 3);
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= (1 << 3);
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= (3 << 3);
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface |= (3 << 3) | (1 << 7);
+		break;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= (1 << 7);
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= (1 << 8);
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= (1 << 8) | (1 << 7);
+		break;
+	}
+
+	snd_soc_write(codec, WM8940_IFACE, iface);
+
+	return 0;
+}
+
+static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	u16 iface = snd_soc_read(codec, WM8940_IFACE) & 0xFD9F;
+	u16 addcntrl = snd_soc_read(codec, WM8940_ADDCNTRL) & 0xFFF1;
+	u16 companding =  snd_soc_read(codec,
+						WM8940_COMPANDINGCTL) & 0xFFDF;
+	int ret;
+
+	/* LoutR control */
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE
+	    && params_channels(params) == 2)
+		iface |= (1 << 9);
+
+	switch (params_rate(params)) {
+	case 8000:
+		addcntrl |= (0x5 << 1);
+		break;
+	case 11025:
+		addcntrl |= (0x4 << 1);
+		break;
+	case 16000:
+		addcntrl |= (0x3 << 1);
+		break;
+	case 22050:
+		addcntrl |= (0x2 << 1);
+		break;
+	case 32000:
+		addcntrl |= (0x1 << 1);
+		break;
+	case 44100:
+	case 48000:
+		break;
+	}
+	ret = snd_soc_write(codec, WM8940_ADDCNTRL, addcntrl);
+	if (ret)
+		goto error_ret;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		companding = companding | (1 << 5);
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= (1 << 5);
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= (2 << 5);
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface |= (3 << 5);
+		break;
+	}
+	ret = snd_soc_write(codec, WM8940_COMPANDINGCTL, companding);
+	if (ret)
+		goto error_ret;
+	ret = snd_soc_write(codec, WM8940_IFACE, iface);
+
+error_ret:
+	return ret;
+}
+
+static int wm8940_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = snd_soc_read(codec, WM8940_DAC) & 0xffbf;
+
+	if (mute)
+		mute_reg |= 0x40;
+
+	return snd_soc_write(codec, WM8940_DAC, mute_reg);
+}
+
+static int wm8940_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 val;
+	u16 pwr_reg = snd_soc_read(codec, WM8940_POWER1) & 0x1F0;
+	int ret = 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		/* ensure bufioen and biasen */
+		pwr_reg |= (1 << 2) | (1 << 3);
+		/* Enable thermal shutdown */
+		val = snd_soc_read(codec, WM8940_OUTPUTCTL);
+		ret = snd_soc_write(codec, WM8940_OUTPUTCTL, val | 0x2);
+		if (ret)
+			break;
+		/* set vmid to 75k */
+		ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x1);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		/* ensure bufioen and biasen */
+		pwr_reg |= (1 << 2) | (1 << 3);
+		ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x1);
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		/* ensure bufioen and biasen */
+		pwr_reg |= (1 << 2) | (1 << 3);
+		/* set vmid to 300k for standby */
+		ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x2);
+		break;
+	case SND_SOC_BIAS_OFF:
+		ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg);
+		break;
+	}
+
+	return ret;
+}
+
+struct pll_ {
+	unsigned int pre_scale:2;
+	unsigned int n:4;
+	unsigned int k;
+};
+
+static struct pll_ pll_div;
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+static void pll_factors(unsigned int target, unsigned int source)
+{
+	unsigned long long Kpart;
+	unsigned int K, Ndiv, Nmod;
+	/* The left shift ist to avoid accuracy loss when right shifting */
+	Ndiv = target / source;
+
+	if (Ndiv > 12) {
+		source <<= 1;
+		/* Multiply by 2 */
+		pll_div.pre_scale = 0;
+		Ndiv = target / source;
+	} else if (Ndiv < 3) {
+		source >>= 2;
+		/* Divide by 4 */
+		pll_div.pre_scale = 3;
+		Ndiv = target / source;
+	} else if (Ndiv < 6) {
+		source >>= 1;
+		/* divide by 2 */
+		pll_div.pre_scale = 2;
+		Ndiv = target / source;
+	} else
+		pll_div.pre_scale = 1;
+
+	if ((Ndiv < 6) || (Ndiv > 12))
+		printk(KERN_WARNING
+			"WM8940 N value %d outwith recommended range!d\n",
+			Ndiv);
+
+	pll_div.n = Ndiv;
+	Nmod = target % source;
+	Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	/* Check if we need to round */
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	K /= 10;
+
+	pll_div.k = K;
+}
+
+/* Untested at the moment */
+static int wm8940_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+
+	/* Turn off PLL */
+	reg = snd_soc_read(codec, WM8940_POWER1);
+	snd_soc_write(codec, WM8940_POWER1, reg & 0x1df);
+
+	if (freq_in == 0 || freq_out == 0) {
+		/* Clock CODEC directly from MCLK */
+		reg = snd_soc_read(codec, WM8940_CLOCK);
+		snd_soc_write(codec, WM8940_CLOCK, reg & 0x0ff);
+		/* Pll power down */
+		snd_soc_write(codec, WM8940_PLLN, (1 << 7));
+		return 0;
+	}
+
+	/* Pll is followed by a frequency divide by 4 */
+	pll_factors(freq_out*4, freq_in);
+	if (pll_div.k)
+		snd_soc_write(codec, WM8940_PLLN,
+			     (pll_div.pre_scale << 4) | pll_div.n | (1 << 6));
+	else /* No factional component */
+		snd_soc_write(codec, WM8940_PLLN,
+			     (pll_div.pre_scale << 4) | pll_div.n);
+	snd_soc_write(codec, WM8940_PLLK1, pll_div.k >> 18);
+	snd_soc_write(codec, WM8940_PLLK2, (pll_div.k >> 9) & 0x1ff);
+	snd_soc_write(codec, WM8940_PLLK3, pll_div.k & 0x1ff);
+	/* Enable the PLL */
+	reg = snd_soc_read(codec, WM8940_POWER1);
+	snd_soc_write(codec, WM8940_POWER1, reg | 0x020);
+
+	/* Run CODEC from PLL instead of MCLK */
+	reg = snd_soc_read(codec, WM8940_CLOCK);
+	snd_soc_write(codec, WM8940_CLOCK, reg | 0x100);
+
+	return 0;
+}
+
+static int wm8940_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+				 int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8940_priv *wm8940 = snd_soc_codec_get_drvdata(codec);
+
+	switch (freq) {
+	case 11289600:
+	case 12000000:
+	case 12288000:
+	case 16934400:
+	case 18432000:
+		wm8940->sysclk = freq;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int wm8940_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+				 int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+	int ret = 0;
+
+	switch (div_id) {
+	case WM8940_BCLKDIV:
+		reg = snd_soc_read(codec, WM8940_CLOCK) & 0xFFEF3;
+		ret = snd_soc_write(codec, WM8940_CLOCK, reg | (div << 2));
+		break;
+	case WM8940_MCLKDIV:
+		reg = snd_soc_read(codec, WM8940_CLOCK) & 0xFF1F;
+		ret = snd_soc_write(codec, WM8940_CLOCK, reg | (div << 5));
+		break;
+	case WM8940_OPCLKDIV:
+		reg = snd_soc_read(codec, WM8940_ADDCNTRL) & 0xFFCF;
+		ret = snd_soc_write(codec, WM8940_ADDCNTRL, reg | (div << 4));
+		break;
+	}
+	return ret;
+}
+
+#define WM8940_RATES SNDRV_PCM_RATE_8000_48000
+
+#define WM8940_FORMATS (SNDRV_PCM_FMTBIT_S8 |				\
+			SNDRV_PCM_FMTBIT_S16_LE |			\
+			SNDRV_PCM_FMTBIT_S20_3LE |			\
+			SNDRV_PCM_FMTBIT_S24_LE |			\
+			SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8940_dai_ops = {
+	.hw_params = wm8940_i2s_hw_params,
+	.set_sysclk = wm8940_set_dai_sysclk,
+	.digital_mute = wm8940_mute,
+	.set_fmt = wm8940_set_dai_fmt,
+	.set_clkdiv = wm8940_set_dai_clkdiv,
+	.set_pll = wm8940_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver wm8940_dai = {
+	.name = "wm8940-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8940_RATES,
+		.formats = WM8940_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8940_RATES,
+		.formats = WM8940_FORMATS,
+	},
+	.ops = &wm8940_dai_ops,
+	.symmetric_rates = 1,
+};
+
+static int wm8940_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	return wm8940_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static int wm8940_resume(struct snd_soc_codec *codec)
+{
+	int i;
+	int ret;
+	u8 data[3];
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware
+	 * Could use auto incremented writes to speed this up
+	 */
+	for (i = 0; i < ARRAY_SIZE(wm8940_reg_defaults); i++) {
+		data[0] = i;
+		data[1] = (cache[i] & 0xFF00) >> 8;
+		data[2] = cache[i] & 0x00FF;
+		ret = codec->hw_write(codec->control_data, data, 3);
+		if (ret < 0)
+			goto error_ret;
+		else if (ret != 3) {
+			ret = -EIO;
+			goto error_ret;
+		}
+	}
+	ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	if (ret)
+		goto error_ret;
+
+error_ret:
+	return ret;
+}
+
+static int wm8940_probe(struct snd_soc_codec *codec)
+{
+	struct wm8940_priv *wm8940 = snd_soc_codec_get_drvdata(codec);
+	struct wm8940_setup_data *pdata = codec->dev->platform_data;
+	int ret;
+	u16 reg;
+
+	codec->control_data = wm8940->control_data;
+	ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm8940->control_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm8940_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		return ret;
+	}
+
+	wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	ret = snd_soc_write(codec, WM8940_POWER1, 0x180);
+	if (ret < 0)
+		return ret;
+
+	if (!pdata)
+		dev_warn(codec->dev, "No platform data supplied\n");
+	else {
+		reg = snd_soc_read(codec, WM8940_OUTPUTCTL);
+		ret = snd_soc_write(codec, WM8940_OUTPUTCTL, reg | pdata->vroi);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = snd_soc_add_controls(codec, wm8940_snd_controls,
+			     ARRAY_SIZE(wm8940_snd_controls));
+	if (ret)
+		return ret;
+	ret = wm8940_add_widgets(codec);
+	if (ret)
+		return ret;
+
+	return ret;
+;
+}
+
+static int wm8940_remove(struct snd_soc_codec *codec)
+{
+	wm8940_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8940 = {
+	.probe =	wm8940_probe,
+	.remove =	wm8940_remove,
+	.suspend =	wm8940_suspend,
+	.resume =	wm8940_resume,
+	.set_bias_level = wm8940_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8940_reg_defaults),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8940_reg_defaults,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8940_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8940_priv *wm8940;
+	int ret;
+
+	wm8940 = kzalloc(sizeof(struct wm8940_priv), GFP_KERNEL);
+	if (wm8940 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8940);
+	wm8940->control_data = i2c;
+	wm8940->control_type = SND_SOC_I2C;
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8940, &wm8940_dai, 1);
+	if (ret < 0)
+		kfree(wm8940);
+	return ret;
+}
+
+static __devexit int wm8940_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8940_i2c_id[] = {
+	{ "wm8940", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8940_i2c_id);
+
+static struct i2c_driver wm8940_i2c_driver = {
+	.driver = {
+		.name = "wm8940-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8940_i2c_probe,
+	.remove =   __devexit_p(wm8940_i2c_remove),
+	.id_table = wm8940_i2c_id,
+};
+#endif
+
+static int __init wm8940_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8940_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8940 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8940_modinit);
+
+static void __exit wm8940_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8940_i2c_driver);
+#endif
+}
+module_exit(wm8940_exit);
+
+MODULE_DESCRIPTION("ASoC WM8940 driver");
+MODULE_AUTHOR("Jonathan Cameron");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8940.h b/linux/sound/soc/codecs/wm8940.h
new file mode 100644
index 0000000..907fe19
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8940.h
@@ -0,0 +1,102 @@
+/*
+ * wm8940.h -- WM8940 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8940_H
+#define _WM8940_H
+
+struct wm8940_setup_data {
+	/* Vref to analogue output resistance */
+#define WM8940_VROI_1K 0
+#define WM8940_VROI_30K 1
+	unsigned int vroi:1;
+};
+
+/* WM8940 register space */
+#define WM8940_SOFTRESET	0x00
+#define WM8940_POWER1		0x01
+#define WM8940_POWER2		0x02
+#define WM8940_POWER3		0x03
+#define WM8940_IFACE		0x04
+#define WM8940_COMPANDINGCTL	0x05
+#define WM8940_CLOCK		0x06
+#define WM8940_ADDCNTRL		0x07
+#define WM8940_GPIO		0x08
+#define WM8940_CTLINT		0x09
+#define WM8940_DAC		0x0A
+#define WM8940_DACVOL		0x0B
+
+#define WM8940_ADC		0x0E
+#define WM8940_ADCVOL		0x0F
+#define WM8940_NOTCH1		0x10
+#define WM8940_NOTCH2		0x11
+#define WM8940_NOTCH3		0x12
+#define WM8940_NOTCH4		0x13
+#define WM8940_NOTCH5		0x14
+#define WM8940_NOTCH6		0x15
+#define WM8940_NOTCH7		0x16
+#define WM8940_NOTCH8		0x17
+#define WM8940_DACLIM1		0x18
+#define WM8940_DACLIM2		0x19
+
+#define WM8940_ALC1		0x20
+#define WM8940_ALC2		0x21
+#define WM8940_ALC3		0x22
+#define WM8940_NOISEGATE	0x23
+#define WM8940_PLLN		0x24
+#define WM8940_PLLK1		0x25
+#define WM8940_PLLK2		0x26
+#define WM8940_PLLK3		0x27
+
+#define WM8940_ALC4		0x2A
+
+#define WM8940_INPUTCTL		0x2C
+#define WM8940_PGAGAIN		0x2D
+
+#define WM8940_ADCBOOST		0x2F
+
+#define WM8940_OUTPUTCTL	0x31
+#define WM8940_SPKMIX		0x32
+
+#define WM8940_SPKVOL		0x36
+
+#define WM8940_MONOMIX		0x38
+
+#define WM8940_CACHEREGNUM  0x57
+
+
+/* Clock divider Id's */
+#define WM8940_BCLKDIV 0
+#define WM8940_MCLKDIV 1
+#define WM8940_OPCLKDIV 2
+
+/* MCLK clock dividers */
+#define WM8940_MCLKDIV_1	0
+#define WM8940_MCLKDIV_1_5	1
+#define WM8940_MCLKDIV_2	2
+#define WM8940_MCLKDIV_3	3
+#define WM8940_MCLKDIV_4	4
+#define WM8940_MCLKDIV_6	5
+#define WM8940_MCLKDIV_8	6
+#define WM8940_MCLKDIV_12	7
+
+/* BCLK clock dividers */
+#define WM8940_BCLKDIV_1 0
+#define WM8940_BCLKDIV_2 1
+#define WM8940_BCLKDIV_4 2
+#define WM8940_BCLKDIV_8 3
+#define WM8940_BCLKDIV_16 4
+#define WM8940_BCLKDIV_32 5
+
+/* PLL Out Dividers */
+#define WM8940_OPCLKDIV_1 0
+#define WM8940_OPCLKDIV_2 1
+#define WM8940_OPCLKDIV_3 2
+#define WM8940_OPCLKDIV_4 3
+
+#endif /* _WM8940_H */
+
diff --git a/linux/sound/soc/codecs/wm8955.c b/linux/sound/soc/codecs/wm8955.c
new file mode 100644
index 0000000..2ac35b0
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8955.c
@@ -0,0 +1,1063 @@
+/*
+ * wm8955.c  --  WM8955 ALSA SoC Audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/wm8955.h>
+
+#include "wm8955.h"
+
+#define WM8955_NUM_SUPPLIES 4
+static const char *wm8955_supply_names[WM8955_NUM_SUPPLIES] = {
+	"DCVDD",
+	"DBVDD",
+	"HPVDD",
+	"AVDD",
+};
+
+/* codec private data */
+struct wm8955_priv {
+	enum snd_soc_control_type control_type;
+
+	unsigned int mclk_rate;
+
+	int deemph;
+	int fs;
+
+	struct regulator_bulk_data supplies[WM8955_NUM_SUPPLIES];
+};
+
+static const u16 wm8955_reg[WM8955_MAX_REGISTER + 1] = {
+	0x0000,     /* R0 */
+	0x0000,     /* R1 */
+	0x0079,     /* R2  - LOUT1 volume */
+	0x0079,     /* R3  - ROUT1 volume */
+	0x0000,     /* R4 */
+	0x0008,     /* R5  - DAC Control */
+	0x0000,     /* R6 */
+	0x000A,     /* R7  - Audio Interface */
+	0x0000,     /* R8  - Sample Rate */
+	0x0000,     /* R9 */
+	0x00FF,     /* R10 - Left DAC volume */
+	0x00FF,     /* R11 - Right DAC volume */
+	0x000F,     /* R12 - Bass control */
+	0x000F,     /* R13 - Treble control */
+	0x0000,     /* R14 */
+	0x0000,     /* R15 - Reset */
+	0x0000,     /* R16 */
+	0x0000,     /* R17 */
+	0x0000,     /* R18 */
+	0x0000,     /* R19 */
+	0x0000,     /* R20 */
+	0x0000,     /* R21 */
+	0x0000,     /* R22 */
+	0x00C1,     /* R23 - Additional control (1) */
+	0x0000,     /* R24 - Additional control (2) */
+	0x0000,     /* R25 - Power Management (1) */
+	0x0000,     /* R26 - Power Management (2) */
+	0x0000,     /* R27 - Additional Control (3) */
+	0x0000,     /* R28 */
+	0x0000,     /* R29 */
+	0x0000,     /* R30 */
+	0x0000,     /* R31 */
+	0x0000,     /* R32 */
+	0x0000,     /* R33 */
+	0x0050,     /* R34 - Left out Mix (1) */
+	0x0050,     /* R35 - Left out Mix (2) */
+	0x0050,     /* R36 - Right out Mix (1) */
+	0x0050,     /* R37 - Right Out Mix (2) */
+	0x0050,     /* R38 - Mono out Mix (1) */
+	0x0050,     /* R39 - Mono out Mix (2) */
+	0x0079,     /* R40 - LOUT2 volume */
+	0x0079,     /* R41 - ROUT2 volume */
+	0x0079,     /* R42 - MONOOUT volume */
+	0x0000,     /* R43 - Clocking / PLL */
+	0x0103,     /* R44 - PLL Control 1 */
+	0x0024,     /* R45 - PLL Control 2 */
+	0x01BA,     /* R46 - PLL Control 3 */
+	0x0000,     /* R47 */
+	0x0000,     /* R48 */
+	0x0000,     /* R49 */
+	0x0000,     /* R50 */
+	0x0000,     /* R51 */
+	0x0000,     /* R52 */
+	0x0000,     /* R53 */
+	0x0000,     /* R54 */
+	0x0000,     /* R55 */
+	0x0000,     /* R56 */
+	0x0000,     /* R57 */
+	0x0000,     /* R58 */
+	0x0000,     /* R59 - PLL Control 4 */
+};
+
+static int wm8955_reset(struct snd_soc_codec *codec)
+{
+	return snd_soc_write(codec, WM8955_RESET, 0);
+}
+
+struct pll_factors {
+	int n;
+	int k;
+	int outdiv;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 22) * 10)
+
+static int wm8995_pll_factors(struct device *dev,
+			      int Fref, int Fout, struct pll_factors *pll)
+{
+	u64 Kpart;
+	unsigned int K, Ndiv, Nmod, target;
+
+	dev_dbg(dev, "Fref=%u Fout=%u\n", Fref, Fout);
+
+	/* The oscilator should run at should be 90-100MHz, and
+	 * there's a divide by 4 plus an optional divide by 2 in the
+	 * output path to generate the system clock.  The clock table
+	 * is sortd so we should always generate a suitable target. */
+	target = Fout * 4;
+	if (target < 90000000) {
+		pll->outdiv = 1;
+		target *= 2;
+	} else {
+		pll->outdiv = 0;
+	}
+
+	WARN_ON(target < 90000000 || target > 100000000);
+
+	dev_dbg(dev, "Fvco=%dHz\n", target);
+
+	/* Now, calculate N.K */
+	Ndiv = target / Fref;
+
+	pll->n = Ndiv;
+	Nmod = target % Fref;
+	dev_dbg(dev, "Nmod=%d\n", Nmod);
+
+	/* Calculate fractional part - scale up so we can round. */
+	Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, Fref);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	pll->k = K / 10;
+
+	dev_dbg(dev, "N=%x K=%x OUTDIV=%x\n", pll->n, pll->k, pll->outdiv);
+
+	return 0;
+}
+
+/* Lookup table specifiying SRATE (table 25 in datasheet); some of the
+ * output frequencies have been rounded to the standard frequencies
+ * they are intended to match where the error is slight. */
+static struct {
+	int mclk;
+	int fs;
+	int usb;
+	int sr;
+} clock_cfgs[] = {
+	{ 18432000,  8000, 0,  3, },
+	{ 18432000, 12000, 0,  9, },
+	{ 18432000, 16000, 0, 11, },
+	{ 18432000, 24000, 0, 29, },
+	{ 18432000, 32000, 0, 13, },
+	{ 18432000, 48000, 0,  1, },
+	{ 18432000, 96000, 0, 15, },
+
+	{ 16934400,  8018, 0, 19, },
+	{ 16934400, 11025, 0, 25, },
+	{ 16934400, 22050, 0, 27, },
+	{ 16934400, 44100, 0, 17, },
+	{ 16934400, 88200, 0, 31, },
+
+	{ 12000000,  8000, 1,  2, },
+	{ 12000000, 11025, 1, 25, },
+	{ 12000000, 12000, 1,  8, },
+	{ 12000000, 16000, 1, 10, },
+	{ 12000000, 22050, 1, 27, },
+	{ 12000000, 24000, 1, 28, },
+	{ 12000000, 32000, 1, 12, },
+	{ 12000000, 44100, 1, 17, },
+	{ 12000000, 48000, 1,  0, },
+	{ 12000000, 88200, 1, 31, },
+	{ 12000000, 96000, 1, 14, },
+
+	{ 12288000,  8000, 0,  2, },
+	{ 12288000, 12000, 0,  8, },
+	{ 12288000, 16000, 0, 10, },
+	{ 12288000, 24000, 0, 28, },
+	{ 12288000, 32000, 0, 12, },
+	{ 12288000, 48000, 0,  0, },
+	{ 12288000, 96000, 0, 14, },
+
+	{ 12289600,  8018, 0, 18, },
+	{ 12289600, 11025, 0, 24, },
+	{ 12289600, 22050, 0, 26, },
+	{ 11289600, 44100, 0, 16, },
+	{ 11289600, 88200, 0, 31, },
+};
+
+static int wm8955_configure_clocking(struct snd_soc_codec *codec)
+{
+	struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+	int i, ret, val;
+	int clocking = 0;
+	int srate = 0;
+	int sr = -1;
+	struct pll_factors pll;
+
+	/* If we're not running a sample rate currently just pick one */
+	if (wm8955->fs == 0)
+		wm8955->fs = 8000;
+
+	/* Can we generate an exact output? */
+	for (i = 0; i < ARRAY_SIZE(clock_cfgs); i++) {
+		if (wm8955->fs != clock_cfgs[i].fs)
+			continue;
+		sr = i;
+
+		if (wm8955->mclk_rate == clock_cfgs[i].mclk)
+			break;
+	}
+
+	/* We should never get here with an unsupported sample rate */
+	if (sr == -1) {
+		dev_err(codec->dev, "Sample rate %dHz unsupported\n",
+			wm8955->fs);
+		WARN_ON(sr == -1);
+		return -EINVAL;
+	}
+
+	if (i == ARRAY_SIZE(clock_cfgs)) {
+		/* If we can't generate the right clock from MCLK then
+		 * we should configure the PLL to supply us with an
+		 * appropriate clock.
+		 */
+		clocking |= WM8955_MCLKSEL;
+
+		/* Use the last divider configuration we saw for the
+		 * sample rate. */
+		ret = wm8995_pll_factors(codec->dev, wm8955->mclk_rate,
+					 clock_cfgs[sr].mclk, &pll);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Unable to generate %dHz from %dHz MCLK\n",
+				wm8955->fs, wm8955->mclk_rate);
+			return -EINVAL;
+		}
+
+		snd_soc_update_bits(codec, WM8955_PLL_CONTROL_1,
+				    WM8955_N_MASK | WM8955_K_21_18_MASK,
+				    (pll.n << WM8955_N_SHIFT) |
+				    pll.k >> 18);
+		snd_soc_update_bits(codec, WM8955_PLL_CONTROL_2,
+				    WM8955_K_17_9_MASK,
+				    (pll.k >> 9) & WM8955_K_17_9_MASK);
+		snd_soc_update_bits(codec, WM8955_PLL_CONTROL_2,
+				    WM8955_K_8_0_MASK,
+				    pll.k & WM8955_K_8_0_MASK);
+		if (pll.k)
+			snd_soc_update_bits(codec, WM8955_PLL_CONTROL_4,
+					    WM8955_KEN, WM8955_KEN);
+		else
+			snd_soc_update_bits(codec, WM8955_PLL_CONTROL_4,
+					    WM8955_KEN, 0);
+
+		if (pll.outdiv)
+			val = WM8955_PLL_RB | WM8955_PLLOUTDIV2;
+		else
+			val = WM8955_PLL_RB;
+
+		/* Now start the PLL running */
+		snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+				    WM8955_PLL_RB | WM8955_PLLOUTDIV2, val);
+		snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+				    WM8955_PLLEN, WM8955_PLLEN);
+	}
+
+	srate = clock_cfgs[sr].usb | (clock_cfgs[sr].sr << WM8955_SR_SHIFT);
+
+	snd_soc_update_bits(codec, WM8955_SAMPLE_RATE,
+			    WM8955_USB | WM8955_SR_MASK, srate);
+	snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+			    WM8955_MCLKSEL, clocking);
+
+	return 0;
+}
+
+static int wm8955_sysclk(struct snd_soc_dapm_widget *w,
+			 struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	int ret = 0;
+
+	/* Always disable the clocks - if we're doing reconfiguration this
+	 * avoids misclocking.
+	 */
+	snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+			    WM8955_DIGENB, 0);
+	snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+			    WM8955_PLL_RB | WM8955_PLLEN, 0);
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMD:
+		break;
+	case SND_SOC_DAPM_PRE_PMU:
+		ret = wm8955_configure_clocking(codec);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8955_set_deemph(struct snd_soc_codec *codec)
+{
+	struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+	int val, i, best;
+
+	/* If we're using deemphasis select the nearest available sample
+	 * rate.
+	 */
+	if (wm8955->deemph) {
+		best = 1;
+		for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+			if (abs(deemph_settings[i] - wm8955->fs) <
+			    abs(deemph_settings[best] - wm8955->fs))
+				best = i;
+		}
+
+		val = best << WM8955_DEEMPH_SHIFT;
+	} else {
+		val = 0;
+	}
+
+	dev_dbg(codec->dev, "Set deemphasis %d\n", val);
+
+	return snd_soc_update_bits(codec, WM8955_DAC_CONTROL,
+				   WM8955_DEEMPH_MASK, val);
+}
+
+static int wm8955_get_deemph(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+
+	ucontrol->value.enumerated.item[0] = wm8955->deemph;
+	return 0;
+}
+
+static int wm8955_put_deemph(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+	int deemph = ucontrol->value.enumerated.item[0];
+
+	if (deemph > 1)
+		return -EINVAL;
+
+	wm8955->deemph = deemph;
+
+	return wm8955_set_deemph(codec);
+}
+
+static const char *bass_mode_text[] = {
+	"Linear", "Adaptive",
+};
+
+static const struct soc_enum bass_mode =
+	SOC_ENUM_SINGLE(WM8955_BASS_CONTROL, 7, 2, bass_mode_text);
+
+static const char *bass_cutoff_text[] = {
+	"Low", "High"
+};
+
+static const struct soc_enum bass_cutoff =
+	SOC_ENUM_SINGLE(WM8955_BASS_CONTROL, 6, 2, bass_cutoff_text);
+
+static const char *treble_cutoff_text[] = {
+	"High", "Low"
+};
+
+static const struct soc_enum treble_cutoff =
+	SOC_ENUM_SINGLE(WM8955_TREBLE_CONTROL, 6, 2, treble_cutoff_text);
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(atten_tlv, -600, 600, 0);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(mono_tlv, -2100, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(treble_tlv, -1200, 150, 1);
+
+static const struct snd_kcontrol_new wm8955_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8955_LEFT_DAC_VOLUME,
+		 WM8955_RIGHT_DAC_VOLUME, 0, 255, 0, digital_tlv),
+SOC_SINGLE_TLV("Playback Attenuation Volume", WM8955_DAC_CONTROL, 7, 1, 1,
+	       atten_tlv),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+		    wm8955_get_deemph, wm8955_put_deemph),
+
+SOC_ENUM("Bass Mode", bass_mode),
+SOC_ENUM("Bass Cutoff", bass_cutoff),
+SOC_SINGLE("Bass Volume", WM8955_BASS_CONTROL, 0, 15, 1),
+
+SOC_ENUM("Treble Cutoff", treble_cutoff),
+SOC_SINGLE_TLV("Treble Volume", WM8955_TREBLE_CONTROL, 0, 14, 1, treble_tlv),
+
+SOC_SINGLE_TLV("Left Bypass Volume", WM8955_LEFT_OUT_MIX_1, 4, 7, 1,
+	       bypass_tlv),
+SOC_SINGLE_TLV("Left Mono Volume", WM8955_LEFT_OUT_MIX_2, 4, 7, 1,
+	       bypass_tlv),
+
+SOC_SINGLE_TLV("Right Mono Volume", WM8955_RIGHT_OUT_MIX_1, 4, 7, 1,
+	       bypass_tlv),
+SOC_SINGLE_TLV("Right Bypass Volume", WM8955_RIGHT_OUT_MIX_2, 4, 7, 1,
+	       bypass_tlv),
+
+/* Not a stereo pair so they line up with the DAPM switches */
+SOC_SINGLE_TLV("Mono Left Bypass Volume", WM8955_MONO_OUT_MIX_1, 4, 7, 1,
+	       mono_tlv),
+SOC_SINGLE_TLV("Mono Right Bypass Volume", WM8955_MONO_OUT_MIX_2, 4, 7, 1,
+	       mono_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Volume", WM8955_LOUT1_VOLUME,
+		 WM8955_ROUT1_VOLUME, 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8955_LOUT1_VOLUME,
+	     WM8955_ROUT1_VOLUME, 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Speaker Volume", WM8955_LOUT2_VOLUME,
+		 WM8955_ROUT2_VOLUME, 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Speaker ZC Switch", WM8955_LOUT2_VOLUME,
+	     WM8955_ROUT2_VOLUME, 7, 1, 0),
+
+SOC_SINGLE_TLV("Mono Volume", WM8955_MONOOUT_VOLUME, 0, 127, 0, out_tlv),
+SOC_SINGLE("Mono ZC Switch", WM8955_MONOOUT_VOLUME, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new lmixer[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8955_LEFT_OUT_MIX_1, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Switch", WM8955_LEFT_OUT_MIX_1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8955_LEFT_OUT_MIX_2, 8, 1, 0),
+SOC_DAPM_SINGLE("Mono Switch", WM8955_LEFT_OUT_MIX_2, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new rmixer[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8955_RIGHT_OUT_MIX_1, 8, 1, 0),
+SOC_DAPM_SINGLE("Mono Switch", WM8955_RIGHT_OUT_MIX_1, 7, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM8955_RIGHT_OUT_MIX_2, 8, 1, 0),
+SOC_DAPM_SINGLE("Bypass Switch", WM8955_RIGHT_OUT_MIX_2, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new mmixer[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8955_MONO_OUT_MIX_1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8955_MONO_OUT_MIX_1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8955_MONO_OUT_MIX_2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8955_MONO_OUT_MIX_2, 7, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8955_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("MONOIN-"),
+SND_SOC_DAPM_INPUT("MONOIN+"),
+SND_SOC_DAPM_INPUT("LINEINR"),
+SND_SOC_DAPM_INPUT("LINEINL"),
+
+SND_SOC_DAPM_PGA("Mono Input", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("SYSCLK", WM8955_POWER_MANAGEMENT_1, 0, 1, wm8955_sysclk,
+		    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("TSDEN", WM8955_ADDITIONAL_CONTROL_1, 8, 0, NULL, 0),
+
+SND_SOC_DAPM_DAC("DACL", "Playback", WM8955_POWER_MANAGEMENT_2, 8, 0),
+SND_SOC_DAPM_DAC("DACR", "Playback", WM8955_POWER_MANAGEMENT_2, 7, 0),
+
+SND_SOC_DAPM_PGA("LOUT1 PGA", WM8955_POWER_MANAGEMENT_2, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("ROUT1 PGA", WM8955_POWER_MANAGEMENT_2, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("LOUT2 PGA", WM8955_POWER_MANAGEMENT_2, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("ROUT2 PGA", WM8955_POWER_MANAGEMENT_2, 3, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MOUT PGA", WM8955_POWER_MANAGEMENT_2, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("OUT3 PGA", WM8955_POWER_MANAGEMENT_2, 1, 0, NULL, 0),
+
+/* The names are chosen to make the control names nice */
+SND_SOC_DAPM_MIXER("Left", SND_SOC_NOPM, 0, 0,
+		   lmixer, ARRAY_SIZE(lmixer)),
+SND_SOC_DAPM_MIXER("Right", SND_SOC_NOPM, 0, 0,
+		   rmixer, ARRAY_SIZE(rmixer)),
+SND_SOC_DAPM_MIXER("Mono", SND_SOC_NOPM, 0, 0,
+		   mmixer, ARRAY_SIZE(mmixer)),
+
+SND_SOC_DAPM_OUTPUT("LOUT1"),
+SND_SOC_DAPM_OUTPUT("ROUT1"),
+SND_SOC_DAPM_OUTPUT("LOUT2"),
+SND_SOC_DAPM_OUTPUT("ROUT2"),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+};
+
+static const struct snd_soc_dapm_route wm8955_intercon[] = {
+	{ "DACL", NULL, "SYSCLK" },
+	{ "DACR", NULL, "SYSCLK" },
+
+	{ "Mono Input", NULL, "MONOIN-" },
+	{ "Mono Input", NULL, "MONOIN+" },
+
+	{ "Left", "Playback Switch", "DACL" },
+	{ "Left", "Right Playback Switch", "DACR" },
+	{ "Left", "Bypass Switch", "LINEINL" },
+	{ "Left", "Mono Switch", "Mono Input" },
+
+	{ "Right", "Playback Switch", "DACR" },
+	{ "Right", "Left Playback Switch", "DACL" },
+	{ "Right", "Bypass Switch", "LINEINR" },
+	{ "Right", "Mono Switch", "Mono Input" },
+
+	{ "Mono", "Left Playback Switch", "DACL" },
+	{ "Mono", "Right Playback Switch", "DACR" },
+	{ "Mono", "Left Bypass Switch", "LINEINL" },
+	{ "Mono", "Right Bypass Switch", "LINEINR" },
+
+	{ "LOUT1 PGA", NULL, "Left" },
+	{ "LOUT1", NULL, "TSDEN" },
+	{ "LOUT1", NULL, "LOUT1 PGA" },
+
+	{ "ROUT1 PGA", NULL, "Right" },
+	{ "ROUT1", NULL, "TSDEN" },
+	{ "ROUT1", NULL, "ROUT1 PGA" },
+
+	{ "LOUT2 PGA", NULL, "Left" },
+	{ "LOUT2", NULL, "TSDEN" },
+	{ "LOUT2", NULL, "LOUT2 PGA" },
+
+	{ "ROUT2 PGA", NULL, "Right" },
+	{ "ROUT2", NULL, "TSDEN" },
+	{ "ROUT2", NULL, "ROUT2 PGA" },
+
+	{ "MOUT PGA", NULL, "Mono" },
+	{ "MONOOUT", NULL, "MOUT PGA" },
+
+	/* OUT3 not currently implemented */
+	{ "OUT3", NULL, "OUT3 PGA" },
+};
+
+static int wm8955_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_add_controls(codec, wm8955_snd_controls,
+			     ARRAY_SIZE(wm8955_snd_controls));
+
+	snd_soc_dapm_new_controls(codec, wm8955_dapm_widgets,
+				  ARRAY_SIZE(wm8955_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, wm8955_intercon,
+				ARRAY_SIZE(wm8955_intercon));
+
+	return 0;
+}
+
+static int wm8955_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+	int wl;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		wl = 0;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		wl = 0x4;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		wl = 0x8;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		wl = 0xc;
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_soc_update_bits(codec, WM8955_AUDIO_INTERFACE,
+			    WM8955_WL_MASK, wl);
+
+	wm8955->fs = params_rate(params);
+	wm8955_set_deemph(codec);
+
+	/* If the chip is clocked then disable the clocks and force a
+	 * reconfiguration, otherwise DAPM will power up the
+	 * clocks for us later. */
+	ret = snd_soc_read(codec, WM8955_POWER_MANAGEMENT_1);
+	if (ret < 0)
+		return ret;
+	if (ret & WM8955_DIGENB) {
+		snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+				    WM8955_DIGENB, 0);
+		snd_soc_update_bits(codec, WM8955_CLOCKING_PLL,
+				    WM8955_PLL_RB | WM8955_PLLEN, 0);
+
+		wm8955_configure_clocking(codec);
+	}
+
+	return 0;
+}
+
+
+static int wm8955_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+			     unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8955_priv *priv = snd_soc_codec_get_drvdata(codec);
+	int div;
+
+	switch (clk_id) {
+	case WM8955_CLK_MCLK:
+		if (freq > 15000000) {
+			priv->mclk_rate = freq /= 2;
+			div = WM8955_MCLKDIV2;
+		} else {
+			priv->mclk_rate = freq;
+			div = 0;
+		}
+
+		snd_soc_update_bits(codec, WM8955_SAMPLE_RATE,
+				    WM8955_MCLKDIV2, div);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+	return 0;
+}
+
+static int wm8955_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 aif = 0;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		aif |= WM8955_MS;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_B:
+		aif |= WM8955_LRP;
+	case SND_SOC_DAIFMT_DSP_A:
+		aif |= 0x3;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		aif |= 0x2;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		aif |= 0x1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		/* frame inversion not valid for DSP modes */
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif |= WM8955_BCLKINV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_LEFT_J:
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			aif |= WM8955_BCLKINV | WM8955_LRP;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif |= WM8955_BCLKINV;
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			aif |= WM8955_LRP;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_update_bits(codec, WM8955_AUDIO_INTERFACE,
+			    WM8955_MS | WM8955_FORMAT_MASK | WM8955_BCLKINV |
+			    WM8955_LRP, aif);
+
+	return 0;
+}
+
+
+static int wm8955_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	int val;
+
+	if (mute)
+		val = WM8955_DACMU;
+	else
+		val = 0;
+
+	snd_soc_update_bits(codec, WM8955_DAC_CONTROL, WM8955_DACMU, val);
+
+	return 0;
+}
+
+static int wm8955_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+	u16 *reg_cache = codec->reg_cache;
+	int ret, i;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		/* VMID resistance 2*50k */
+		snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+				    WM8955_VMIDSEL_MASK,
+				    0x1 << WM8955_VMIDSEL_SHIFT);
+
+		/* Default bias current */
+		snd_soc_update_bits(codec, WM8955_ADDITIONAL_CONTROL_1,
+				    WM8955_VSEL_MASK,
+				    0x2 << WM8955_VSEL_SHIFT);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies),
+						    wm8955->supplies);
+			if (ret != 0) {
+				dev_err(codec->dev,
+					"Failed to enable supplies: %d\n",
+					ret);
+				return ret;
+			}
+
+			/* Sync back cached values if they're
+			 * different from the hardware default.
+			 */
+			for (i = 0; i < codec->driver->reg_cache_size; i++) {
+				if (i == WM8955_RESET)
+					continue;
+
+				if (reg_cache[i] == wm8955_reg[i])
+					continue;
+
+				snd_soc_write(codec, i, reg_cache[i]);
+			}
+
+			/* Enable VREF and VMID */
+			snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+					    WM8955_VREF |
+					    WM8955_VMIDSEL_MASK,
+					    WM8955_VREF |
+					    0x3 << WM8955_VREF_SHIFT);
+
+			/* Let VMID ramp */
+			msleep(500);
+
+			/* High resistance VROI to maintain outputs */
+			snd_soc_update_bits(codec,
+					    WM8955_ADDITIONAL_CONTROL_3,
+					    WM8955_VROI, WM8955_VROI);
+		}
+
+		/* Maintain VMID with 2*250k */
+		snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+				    WM8955_VMIDSEL_MASK,
+				    0x2 << WM8955_VMIDSEL_SHIFT);
+
+		/* Minimum bias current */
+		snd_soc_update_bits(codec, WM8955_ADDITIONAL_CONTROL_1,
+				    WM8955_VSEL_MASK, 0);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		/* Low resistance VROI to help discharge */
+		snd_soc_update_bits(codec,
+				    WM8955_ADDITIONAL_CONTROL_3,
+				    WM8955_VROI, 0);
+
+		/* Turn off VMID and VREF */
+		snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1,
+				    WM8955_VREF |
+				    WM8955_VMIDSEL_MASK, 0);
+
+		regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies),
+				       wm8955->supplies);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8955_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8955_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8955_dai_ops = {
+	.set_sysclk = wm8955_set_sysclk,
+	.set_fmt = wm8955_set_fmt,
+	.hw_params = wm8955_hw_params,
+	.digital_mute = wm8955_digital_mute,
+};
+
+static struct snd_soc_dai_driver wm8955_dai = {
+	.name = "wm8955-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = WM8955_RATES,
+		.formats = WM8955_FORMATS,
+	},
+	.ops = &wm8955_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int wm8955_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8955_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm8955_resume(struct snd_soc_codec *codec)
+{
+	wm8955_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+#else
+#define wm8955_suspend NULL
+#define wm8955_resume NULL
+#endif
+
+static int wm8955_probe(struct snd_soc_codec *codec)
+{
+	struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+	struct wm8955_pdata *pdata = dev_get_platdata(codec->dev);
+	u16 *reg_cache = codec->reg_cache;
+	int ret, i;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8955->control_type);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++)
+		wm8955->supplies[i].supply = wm8955_supply_names[i];
+
+	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8955->supplies),
+				 wm8955->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+		return ret;
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies),
+				    wm8955->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+		goto err_get;
+	}
+
+	ret = wm8955_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+		goto err_enable;
+	}
+
+	/* Change some default settings - latch VU and enable ZC */
+	reg_cache[WM8955_LEFT_DAC_VOLUME] |= WM8955_LDVU;
+	reg_cache[WM8955_RIGHT_DAC_VOLUME] |= WM8955_RDVU;
+	reg_cache[WM8955_LOUT1_VOLUME] |= WM8955_LO1VU | WM8955_LO1ZC;
+	reg_cache[WM8955_ROUT1_VOLUME] |= WM8955_RO1VU | WM8955_RO1ZC;
+	reg_cache[WM8955_LOUT2_VOLUME] |= WM8955_LO2VU | WM8955_LO2ZC;
+	reg_cache[WM8955_ROUT2_VOLUME] |= WM8955_RO2VU | WM8955_RO2ZC;
+	reg_cache[WM8955_MONOOUT_VOLUME] |= WM8955_MOZC;
+
+	/* Also enable adaptive bass boost by default */
+	reg_cache[WM8955_BASS_CONTROL] |= WM8955_BB;
+
+	/* Set platform data values */
+	if (pdata) {
+		if (pdata->out2_speaker)
+			reg_cache[WM8955_ADDITIONAL_CONTROL_2]
+				|= WM8955_ROUT2INV;
+
+		if (pdata->monoin_diff)
+			reg_cache[WM8955_MONO_OUT_MIX_1]
+				|= WM8955_DMEN;
+	}
+
+	wm8955_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Bias level configuration will have done an extra enable */
+	regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+
+	wm8955_add_widgets(codec);
+	return 0;
+
+err_enable:
+	regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+err_get:
+	regulator_bulk_free(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+	return ret;
+}
+
+static int wm8955_remove(struct snd_soc_codec *codec)
+{
+	struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec);
+
+	wm8955_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	regulator_bulk_free(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8955 = {
+	.probe =	wm8955_probe,
+	.remove =	wm8955_remove,
+	.suspend =	wm8955_suspend,
+	.resume =	wm8955_resume,
+	.set_bias_level = wm8955_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8955_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8955_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8955_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8955_priv *wm8955;
+	int ret;
+
+	wm8955 = kzalloc(sizeof(struct wm8955_priv), GFP_KERNEL);
+	if (wm8955 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8955);
+	wm8955->control_type = SND_SOC_I2C;
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8955, &wm8955_dai, 1);
+	if (ret < 0)
+		kfree(wm8955);
+	return ret;
+}
+
+static __devexit int wm8955_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8955_i2c_id[] = {
+	{ "wm8955", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8955_i2c_id);
+
+static struct i2c_driver wm8955_i2c_driver = {
+	.driver = {
+		.name = "wm8955-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8955_i2c_probe,
+	.remove =   __devexit_p(wm8955_i2c_remove),
+	.id_table = wm8955_i2c_id,
+};
+#endif
+
+static int __init wm8955_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8955_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8955 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8955_modinit);
+
+static void __exit wm8955_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8955_i2c_driver);
+#endif
+}
+module_exit(wm8955_exit);
+
+MODULE_DESCRIPTION("ASoC WM8955 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8955.h b/linux/sound/soc/codecs/wm8955.h
new file mode 100644
index 0000000..d13fd5c
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8955.h
@@ -0,0 +1,486 @@
+/*
+ * wm8955.h  --  WM8904 ASoC driver
+ *
+ * Copyright 2009 Wolfson Microelectronics, plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8955_H
+#define _WM8955_H
+
+#define WM8955_CLK_MCLK 1
+
+/*
+ * Register values.
+ */
+#define WM8955_LOUT1_VOLUME                     0x02
+#define WM8955_ROUT1_VOLUME                     0x03
+#define WM8955_DAC_CONTROL                      0x05
+#define WM8955_AUDIO_INTERFACE                  0x07
+#define WM8955_SAMPLE_RATE                      0x08
+#define WM8955_LEFT_DAC_VOLUME                  0x0A
+#define WM8955_RIGHT_DAC_VOLUME                 0x0B
+#define WM8955_BASS_CONTROL                     0x0C
+#define WM8955_TREBLE_CONTROL                   0x0D
+#define WM8955_RESET                            0x0F
+#define WM8955_ADDITIONAL_CONTROL_1             0x17
+#define WM8955_ADDITIONAL_CONTROL_2             0x18
+#define WM8955_POWER_MANAGEMENT_1               0x19
+#define WM8955_POWER_MANAGEMENT_2               0x1A
+#define WM8955_ADDITIONAL_CONTROL_3             0x1B
+#define WM8955_LEFT_OUT_MIX_1                   0x22
+#define WM8955_LEFT_OUT_MIX_2                   0x23
+#define WM8955_RIGHT_OUT_MIX_1                  0x24
+#define WM8955_RIGHT_OUT_MIX_2                  0x25
+#define WM8955_MONO_OUT_MIX_1                   0x26
+#define WM8955_MONO_OUT_MIX_2                   0x27
+#define WM8955_LOUT2_VOLUME                     0x28
+#define WM8955_ROUT2_VOLUME                     0x29
+#define WM8955_MONOOUT_VOLUME                   0x2A
+#define WM8955_CLOCKING_PLL                     0x2B
+#define WM8955_PLL_CONTROL_1                    0x2C
+#define WM8955_PLL_CONTROL_2                    0x2D
+#define WM8955_PLL_CONTROL_3                    0x2E
+#define WM8955_PLL_CONTROL_4                    0x3B
+
+#define WM8955_REGISTER_COUNT                   29
+#define WM8955_MAX_REGISTER                     0x3B
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R2 (0x02) - LOUT1 volume
+ */
+#define WM8955_LO1VU                            0x0100  /* LO1VU */
+#define WM8955_LO1VU_MASK                       0x0100  /* LO1VU */
+#define WM8955_LO1VU_SHIFT                           8  /* LO1VU */
+#define WM8955_LO1VU_WIDTH                           1  /* LO1VU */
+#define WM8955_LO1ZC                            0x0080  /* LO1ZC */
+#define WM8955_LO1ZC_MASK                       0x0080  /* LO1ZC */
+#define WM8955_LO1ZC_SHIFT                           7  /* LO1ZC */
+#define WM8955_LO1ZC_WIDTH                           1  /* LO1ZC */
+#define WM8955_LOUTVOL_MASK                     0x007F  /* LOUTVOL - [6:0] */
+#define WM8955_LOUTVOL_SHIFT                         0  /* LOUTVOL - [6:0] */
+#define WM8955_LOUTVOL_WIDTH                         7  /* LOUTVOL - [6:0] */
+
+/*
+ * R3 (0x03) - ROUT1 volume
+ */
+#define WM8955_RO1VU                            0x0100  /* RO1VU */
+#define WM8955_RO1VU_MASK                       0x0100  /* RO1VU */
+#define WM8955_RO1VU_SHIFT                           8  /* RO1VU */
+#define WM8955_RO1VU_WIDTH                           1  /* RO1VU */
+#define WM8955_RO1ZC                            0x0080  /* RO1ZC */
+#define WM8955_RO1ZC_MASK                       0x0080  /* RO1ZC */
+#define WM8955_RO1ZC_SHIFT                           7  /* RO1ZC */
+#define WM8955_RO1ZC_WIDTH                           1  /* RO1ZC */
+#define WM8955_ROUTVOL_MASK                     0x007F  /* ROUTVOL - [6:0] */
+#define WM8955_ROUTVOL_SHIFT                         0  /* ROUTVOL - [6:0] */
+#define WM8955_ROUTVOL_WIDTH                         7  /* ROUTVOL - [6:0] */
+
+/*
+ * R5 (0x05) - DAC Control
+ */
+#define WM8955_DAT                              0x0080  /* DAT */
+#define WM8955_DAT_MASK                         0x0080  /* DAT */
+#define WM8955_DAT_SHIFT                             7  /* DAT */
+#define WM8955_DAT_WIDTH                             1  /* DAT */
+#define WM8955_DACMU                            0x0008  /* DACMU */
+#define WM8955_DACMU_MASK                       0x0008  /* DACMU */
+#define WM8955_DACMU_SHIFT                           3  /* DACMU */
+#define WM8955_DACMU_WIDTH                           1  /* DACMU */
+#define WM8955_DEEMPH_MASK                      0x0006  /* DEEMPH - [2:1] */
+#define WM8955_DEEMPH_SHIFT                          1  /* DEEMPH - [2:1] */
+#define WM8955_DEEMPH_WIDTH                          2  /* DEEMPH - [2:1] */
+
+/*
+ * R7 (0x07) - Audio Interface
+ */
+#define WM8955_BCLKINV                          0x0080  /* BCLKINV */
+#define WM8955_BCLKINV_MASK                     0x0080  /* BCLKINV */
+#define WM8955_BCLKINV_SHIFT                         7  /* BCLKINV */
+#define WM8955_BCLKINV_WIDTH                         1  /* BCLKINV */
+#define WM8955_MS                               0x0040  /* MS */
+#define WM8955_MS_MASK                          0x0040  /* MS */
+#define WM8955_MS_SHIFT                              6  /* MS */
+#define WM8955_MS_WIDTH                              1  /* MS */
+#define WM8955_LRSWAP                           0x0020  /* LRSWAP */
+#define WM8955_LRSWAP_MASK                      0x0020  /* LRSWAP */
+#define WM8955_LRSWAP_SHIFT                          5  /* LRSWAP */
+#define WM8955_LRSWAP_WIDTH                          1  /* LRSWAP */
+#define WM8955_LRP                              0x0010  /* LRP */
+#define WM8955_LRP_MASK                         0x0010  /* LRP */
+#define WM8955_LRP_SHIFT                             4  /* LRP */
+#define WM8955_LRP_WIDTH                             1  /* LRP */
+#define WM8955_WL_MASK                          0x000C  /* WL - [3:2] */
+#define WM8955_WL_SHIFT                              2  /* WL - [3:2] */
+#define WM8955_WL_WIDTH                              2  /* WL - [3:2] */
+#define WM8955_FORMAT_MASK                      0x0003  /* FORMAT - [1:0] */
+#define WM8955_FORMAT_SHIFT                          0  /* FORMAT - [1:0] */
+#define WM8955_FORMAT_WIDTH                          2  /* FORMAT - [1:0] */
+
+/*
+ * R8 (0x08) - Sample Rate
+ */
+#define WM8955_BCLKDIV2                         0x0080  /* BCLKDIV2 */
+#define WM8955_BCLKDIV2_MASK                    0x0080  /* BCLKDIV2 */
+#define WM8955_BCLKDIV2_SHIFT                        7  /* BCLKDIV2 */
+#define WM8955_BCLKDIV2_WIDTH                        1  /* BCLKDIV2 */
+#define WM8955_MCLKDIV2                         0x0040  /* MCLKDIV2 */
+#define WM8955_MCLKDIV2_MASK                    0x0040  /* MCLKDIV2 */
+#define WM8955_MCLKDIV2_SHIFT                        6  /* MCLKDIV2 */
+#define WM8955_MCLKDIV2_WIDTH                        1  /* MCLKDIV2 */
+#define WM8955_SR_MASK                          0x003E  /* SR - [5:1] */
+#define WM8955_SR_SHIFT                              1  /* SR - [5:1] */
+#define WM8955_SR_WIDTH                              5  /* SR - [5:1] */
+#define WM8955_USB                              0x0001  /* USB */
+#define WM8955_USB_MASK                         0x0001  /* USB */
+#define WM8955_USB_SHIFT                             0  /* USB */
+#define WM8955_USB_WIDTH                             1  /* USB */
+
+/*
+ * R10 (0x0A) - Left DAC volume
+ */
+#define WM8955_LDVU                             0x0100  /* LDVU */
+#define WM8955_LDVU_MASK                        0x0100  /* LDVU */
+#define WM8955_LDVU_SHIFT                            8  /* LDVU */
+#define WM8955_LDVU_WIDTH                            1  /* LDVU */
+#define WM8955_LDACVOL_MASK                     0x00FF  /* LDACVOL - [7:0] */
+#define WM8955_LDACVOL_SHIFT                         0  /* LDACVOL - [7:0] */
+#define WM8955_LDACVOL_WIDTH                         8  /* LDACVOL - [7:0] */
+
+/*
+ * R11 (0x0B) - Right DAC volume
+ */
+#define WM8955_RDVU                             0x0100  /* RDVU */
+#define WM8955_RDVU_MASK                        0x0100  /* RDVU */
+#define WM8955_RDVU_SHIFT                            8  /* RDVU */
+#define WM8955_RDVU_WIDTH                            1  /* RDVU */
+#define WM8955_RDACVOL_MASK                     0x00FF  /* RDACVOL - [7:0] */
+#define WM8955_RDACVOL_SHIFT                         0  /* RDACVOL - [7:0] */
+#define WM8955_RDACVOL_WIDTH                         8  /* RDACVOL - [7:0] */
+
+/*
+ * R12 (0x0C) - Bass control
+ */
+#define WM8955_BB                               0x0080  /* BB */
+#define WM8955_BB_MASK                          0x0080  /* BB */
+#define WM8955_BB_SHIFT                              7  /* BB */
+#define WM8955_BB_WIDTH                              1  /* BB */
+#define WM8955_BC                               0x0040  /* BC */
+#define WM8955_BC_MASK                          0x0040  /* BC */
+#define WM8955_BC_SHIFT                              6  /* BC */
+#define WM8955_BC_WIDTH                              1  /* BC */
+#define WM8955_BASS_MASK                        0x000F  /* BASS - [3:0] */
+#define WM8955_BASS_SHIFT                            0  /* BASS - [3:0] */
+#define WM8955_BASS_WIDTH                            4  /* BASS - [3:0] */
+
+/*
+ * R13 (0x0D) - Treble control
+ */
+#define WM8955_TC                               0x0040  /* TC */
+#define WM8955_TC_MASK                          0x0040  /* TC */
+#define WM8955_TC_SHIFT                              6  /* TC */
+#define WM8955_TC_WIDTH                              1  /* TC */
+#define WM8955_TRBL_MASK                        0x000F  /* TRBL - [3:0] */
+#define WM8955_TRBL_SHIFT                            0  /* TRBL - [3:0] */
+#define WM8955_TRBL_WIDTH                            4  /* TRBL - [3:0] */
+
+/*
+ * R15 (0x0F) - Reset
+ */
+#define WM8955_RESET_MASK                       0x01FF  /* RESET - [8:0] */
+#define WM8955_RESET_SHIFT                           0  /* RESET - [8:0] */
+#define WM8955_RESET_WIDTH                           9  /* RESET - [8:0] */
+
+/*
+ * R23 (0x17) - Additional control (1)
+ */
+#define WM8955_TSDEN                            0x0100  /* TSDEN */
+#define WM8955_TSDEN_MASK                       0x0100  /* TSDEN */
+#define WM8955_TSDEN_SHIFT                           8  /* TSDEN */
+#define WM8955_TSDEN_WIDTH                           1  /* TSDEN */
+#define WM8955_VSEL_MASK                        0x00C0  /* VSEL - [7:6] */
+#define WM8955_VSEL_SHIFT                            6  /* VSEL - [7:6] */
+#define WM8955_VSEL_WIDTH                            2  /* VSEL - [7:6] */
+#define WM8955_DMONOMIX_MASK                    0x0030  /* DMONOMIX - [5:4] */
+#define WM8955_DMONOMIX_SHIFT                        4  /* DMONOMIX - [5:4] */
+#define WM8955_DMONOMIX_WIDTH                        2  /* DMONOMIX - [5:4] */
+#define WM8955_DACINV                           0x0002  /* DACINV */
+#define WM8955_DACINV_MASK                      0x0002  /* DACINV */
+#define WM8955_DACINV_SHIFT                          1  /* DACINV */
+#define WM8955_DACINV_WIDTH                          1  /* DACINV */
+#define WM8955_TOEN                             0x0001  /* TOEN */
+#define WM8955_TOEN_MASK                        0x0001  /* TOEN */
+#define WM8955_TOEN_SHIFT                            0  /* TOEN */
+#define WM8955_TOEN_WIDTH                            1  /* TOEN */
+
+/*
+ * R24 (0x18) - Additional control (2)
+ */
+#define WM8955_OUT3SW_MASK                      0x0180  /* OUT3SW - [8:7] */
+#define WM8955_OUT3SW_SHIFT                          7  /* OUT3SW - [8:7] */
+#define WM8955_OUT3SW_WIDTH                          2  /* OUT3SW - [8:7] */
+#define WM8955_ROUT2INV                         0x0010  /* ROUT2INV */
+#define WM8955_ROUT2INV_MASK                    0x0010  /* ROUT2INV */
+#define WM8955_ROUT2INV_SHIFT                        4  /* ROUT2INV */
+#define WM8955_ROUT2INV_WIDTH                        1  /* ROUT2INV */
+#define WM8955_DACOSR                           0x0001  /* DACOSR */
+#define WM8955_DACOSR_MASK                      0x0001  /* DACOSR */
+#define WM8955_DACOSR_SHIFT                          0  /* DACOSR */
+#define WM8955_DACOSR_WIDTH                          1  /* DACOSR */
+
+/*
+ * R25 (0x19) - Power Management (1)
+ */
+#define WM8955_VMIDSEL_MASK                     0x0180  /* VMIDSEL - [8:7] */
+#define WM8955_VMIDSEL_SHIFT                         7  /* VMIDSEL - [8:7] */
+#define WM8955_VMIDSEL_WIDTH                         2  /* VMIDSEL - [8:7] */
+#define WM8955_VREF                             0x0040  /* VREF */
+#define WM8955_VREF_MASK                        0x0040  /* VREF */
+#define WM8955_VREF_SHIFT                            6  /* VREF */
+#define WM8955_VREF_WIDTH                            1  /* VREF */
+#define WM8955_DIGENB                           0x0001  /* DIGENB */
+#define WM8955_DIGENB_MASK                      0x0001  /* DIGENB */
+#define WM8955_DIGENB_SHIFT                          0  /* DIGENB */
+#define WM8955_DIGENB_WIDTH                          1  /* DIGENB */
+
+/*
+ * R26 (0x1A) - Power Management (2)
+ */
+#define WM8955_DACL                             0x0100  /* DACL */
+#define WM8955_DACL_MASK                        0x0100  /* DACL */
+#define WM8955_DACL_SHIFT                            8  /* DACL */
+#define WM8955_DACL_WIDTH                            1  /* DACL */
+#define WM8955_DACR                             0x0080  /* DACR */
+#define WM8955_DACR_MASK                        0x0080  /* DACR */
+#define WM8955_DACR_SHIFT                            7  /* DACR */
+#define WM8955_DACR_WIDTH                            1  /* DACR */
+#define WM8955_LOUT1                            0x0040  /* LOUT1 */
+#define WM8955_LOUT1_MASK                       0x0040  /* LOUT1 */
+#define WM8955_LOUT1_SHIFT                           6  /* LOUT1 */
+#define WM8955_LOUT1_WIDTH                           1  /* LOUT1 */
+#define WM8955_ROUT1                            0x0020  /* ROUT1 */
+#define WM8955_ROUT1_MASK                       0x0020  /* ROUT1 */
+#define WM8955_ROUT1_SHIFT                           5  /* ROUT1 */
+#define WM8955_ROUT1_WIDTH                           1  /* ROUT1 */
+#define WM8955_LOUT2                            0x0010  /* LOUT2 */
+#define WM8955_LOUT2_MASK                       0x0010  /* LOUT2 */
+#define WM8955_LOUT2_SHIFT                           4  /* LOUT2 */
+#define WM8955_LOUT2_WIDTH                           1  /* LOUT2 */
+#define WM8955_ROUT2                            0x0008  /* ROUT2 */
+#define WM8955_ROUT2_MASK                       0x0008  /* ROUT2 */
+#define WM8955_ROUT2_SHIFT                           3  /* ROUT2 */
+#define WM8955_ROUT2_WIDTH                           1  /* ROUT2 */
+#define WM8955_MONO                             0x0004  /* MONO */
+#define WM8955_MONO_MASK                        0x0004  /* MONO */
+#define WM8955_MONO_SHIFT                            2  /* MONO */
+#define WM8955_MONO_WIDTH                            1  /* MONO */
+#define WM8955_OUT3                             0x0002  /* OUT3 */
+#define WM8955_OUT3_MASK                        0x0002  /* OUT3 */
+#define WM8955_OUT3_SHIFT                            1  /* OUT3 */
+#define WM8955_OUT3_WIDTH                            1  /* OUT3 */
+
+/*
+ * R27 (0x1B) - Additional Control (3)
+ */
+#define WM8955_VROI                             0x0040  /* VROI */
+#define WM8955_VROI_MASK                        0x0040  /* VROI */
+#define WM8955_VROI_SHIFT                            6  /* VROI */
+#define WM8955_VROI_WIDTH                            1  /* VROI */
+
+/*
+ * R34 (0x22) - Left out Mix (1)
+ */
+#define WM8955_LD2LO                            0x0100  /* LD2LO */
+#define WM8955_LD2LO_MASK                       0x0100  /* LD2LO */
+#define WM8955_LD2LO_SHIFT                           8  /* LD2LO */
+#define WM8955_LD2LO_WIDTH                           1  /* LD2LO */
+#define WM8955_LI2LO                            0x0080  /* LI2LO */
+#define WM8955_LI2LO_MASK                       0x0080  /* LI2LO */
+#define WM8955_LI2LO_SHIFT                           7  /* LI2LO */
+#define WM8955_LI2LO_WIDTH                           1  /* LI2LO */
+#define WM8955_LI2LOVOL_MASK                    0x0070  /* LI2LOVOL - [6:4] */
+#define WM8955_LI2LOVOL_SHIFT                        4  /* LI2LOVOL - [6:4] */
+#define WM8955_LI2LOVOL_WIDTH                        3  /* LI2LOVOL - [6:4] */
+
+/*
+ * R35 (0x23) - Left out Mix (2)
+ */
+#define WM8955_RD2LO                            0x0100  /* RD2LO */
+#define WM8955_RD2LO_MASK                       0x0100  /* RD2LO */
+#define WM8955_RD2LO_SHIFT                           8  /* RD2LO */
+#define WM8955_RD2LO_WIDTH                           1  /* RD2LO */
+#define WM8955_RI2LO                            0x0080  /* RI2LO */
+#define WM8955_RI2LO_MASK                       0x0080  /* RI2LO */
+#define WM8955_RI2LO_SHIFT                           7  /* RI2LO */
+#define WM8955_RI2LO_WIDTH                           1  /* RI2LO */
+#define WM8955_RI2LOVOL_MASK                    0x0070  /* RI2LOVOL - [6:4] */
+#define WM8955_RI2LOVOL_SHIFT                        4  /* RI2LOVOL - [6:4] */
+#define WM8955_RI2LOVOL_WIDTH                        3  /* RI2LOVOL - [6:4] */
+
+/*
+ * R36 (0x24) - Right out Mix (1)
+ */
+#define WM8955_LD2RO                            0x0100  /* LD2RO */
+#define WM8955_LD2RO_MASK                       0x0100  /* LD2RO */
+#define WM8955_LD2RO_SHIFT                           8  /* LD2RO */
+#define WM8955_LD2RO_WIDTH                           1  /* LD2RO */
+#define WM8955_LI2RO                            0x0080  /* LI2RO */
+#define WM8955_LI2RO_MASK                       0x0080  /* LI2RO */
+#define WM8955_LI2RO_SHIFT                           7  /* LI2RO */
+#define WM8955_LI2RO_WIDTH                           1  /* LI2RO */
+#define WM8955_LI2ROVOL_MASK                    0x0070  /* LI2ROVOL - [6:4] */
+#define WM8955_LI2ROVOL_SHIFT                        4  /* LI2ROVOL - [6:4] */
+#define WM8955_LI2ROVOL_WIDTH                        3  /* LI2ROVOL - [6:4] */
+
+/*
+ * R37 (0x25) - Right Out Mix (2)
+ */
+#define WM8955_RD2RO                            0x0100  /* RD2RO */
+#define WM8955_RD2RO_MASK                       0x0100  /* RD2RO */
+#define WM8955_RD2RO_SHIFT                           8  /* RD2RO */
+#define WM8955_RD2RO_WIDTH                           1  /* RD2RO */
+#define WM8955_RI2RO                            0x0080  /* RI2RO */
+#define WM8955_RI2RO_MASK                       0x0080  /* RI2RO */
+#define WM8955_RI2RO_SHIFT                           7  /* RI2RO */
+#define WM8955_RI2RO_WIDTH                           1  /* RI2RO */
+#define WM8955_RI2ROVOL_MASK                    0x0070  /* RI2ROVOL - [6:4] */
+#define WM8955_RI2ROVOL_SHIFT                        4  /* RI2ROVOL - [6:4] */
+#define WM8955_RI2ROVOL_WIDTH                        3  /* RI2ROVOL - [6:4] */
+
+/*
+ * R38 (0x26) - Mono out Mix (1)
+ */
+#define WM8955_LD2MO                            0x0100  /* LD2MO */
+#define WM8955_LD2MO_MASK                       0x0100  /* LD2MO */
+#define WM8955_LD2MO_SHIFT                           8  /* LD2MO */
+#define WM8955_LD2MO_WIDTH                           1  /* LD2MO */
+#define WM8955_LI2MO                            0x0080  /* LI2MO */
+#define WM8955_LI2MO_MASK                       0x0080  /* LI2MO */
+#define WM8955_LI2MO_SHIFT                           7  /* LI2MO */
+#define WM8955_LI2MO_WIDTH                           1  /* LI2MO */
+#define WM8955_LI2MOVOL_MASK                    0x0070  /* LI2MOVOL - [6:4] */
+#define WM8955_LI2MOVOL_SHIFT                        4  /* LI2MOVOL - [6:4] */
+#define WM8955_LI2MOVOL_WIDTH                        3  /* LI2MOVOL - [6:4] */
+#define WM8955_DMEN                             0x0001  /* DMEN */
+#define WM8955_DMEN_MASK                        0x0001  /* DMEN */
+#define WM8955_DMEN_SHIFT                            0  /* DMEN */
+#define WM8955_DMEN_WIDTH                            1  /* DMEN */
+
+/*
+ * R39 (0x27) - Mono out Mix (2)
+ */
+#define WM8955_RD2MO                            0x0100  /* RD2MO */
+#define WM8955_RD2MO_MASK                       0x0100  /* RD2MO */
+#define WM8955_RD2MO_SHIFT                           8  /* RD2MO */
+#define WM8955_RD2MO_WIDTH                           1  /* RD2MO */
+#define WM8955_RI2MO                            0x0080  /* RI2MO */
+#define WM8955_RI2MO_MASK                       0x0080  /* RI2MO */
+#define WM8955_RI2MO_SHIFT                           7  /* RI2MO */
+#define WM8955_RI2MO_WIDTH                           1  /* RI2MO */
+#define WM8955_RI2MOVOL_MASK                    0x0070  /* RI2MOVOL - [6:4] */
+#define WM8955_RI2MOVOL_SHIFT                        4  /* RI2MOVOL - [6:4] */
+#define WM8955_RI2MOVOL_WIDTH                        3  /* RI2MOVOL - [6:4] */
+
+/*
+ * R40 (0x28) - LOUT2 volume
+ */
+#define WM8955_LO2VU                            0x0100  /* LO2VU */
+#define WM8955_LO2VU_MASK                       0x0100  /* LO2VU */
+#define WM8955_LO2VU_SHIFT                           8  /* LO2VU */
+#define WM8955_LO2VU_WIDTH                           1  /* LO2VU */
+#define WM8955_LO2ZC                            0x0080  /* LO2ZC */
+#define WM8955_LO2ZC_MASK                       0x0080  /* LO2ZC */
+#define WM8955_LO2ZC_SHIFT                           7  /* LO2ZC */
+#define WM8955_LO2ZC_WIDTH                           1  /* LO2ZC */
+#define WM8955_LOUT2VOL_MASK                    0x007F  /* LOUT2VOL - [6:0] */
+#define WM8955_LOUT2VOL_SHIFT                        0  /* LOUT2VOL - [6:0] */
+#define WM8955_LOUT2VOL_WIDTH                        7  /* LOUT2VOL - [6:0] */
+
+/*
+ * R41 (0x29) - ROUT2 volume
+ */
+#define WM8955_RO2VU                            0x0100  /* RO2VU */
+#define WM8955_RO2VU_MASK                       0x0100  /* RO2VU */
+#define WM8955_RO2VU_SHIFT                           8  /* RO2VU */
+#define WM8955_RO2VU_WIDTH                           1  /* RO2VU */
+#define WM8955_RO2ZC                            0x0080  /* RO2ZC */
+#define WM8955_RO2ZC_MASK                       0x0080  /* RO2ZC */
+#define WM8955_RO2ZC_SHIFT                           7  /* RO2ZC */
+#define WM8955_RO2ZC_WIDTH                           1  /* RO2ZC */
+#define WM8955_ROUT2VOL_MASK                    0x007F  /* ROUT2VOL - [6:0] */
+#define WM8955_ROUT2VOL_SHIFT                        0  /* ROUT2VOL - [6:0] */
+#define WM8955_ROUT2VOL_WIDTH                        7  /* ROUT2VOL - [6:0] */
+
+/*
+ * R42 (0x2A) - MONOOUT volume
+ */
+#define WM8955_MOZC                             0x0080  /* MOZC */
+#define WM8955_MOZC_MASK                        0x0080  /* MOZC */
+#define WM8955_MOZC_SHIFT                            7  /* MOZC */
+#define WM8955_MOZC_WIDTH                            1  /* MOZC */
+#define WM8955_MOUTVOL_MASK                     0x007F  /* MOUTVOL - [6:0] */
+#define WM8955_MOUTVOL_SHIFT                         0  /* MOUTVOL - [6:0] */
+#define WM8955_MOUTVOL_WIDTH                         7  /* MOUTVOL - [6:0] */
+
+/*
+ * R43 (0x2B) - Clocking / PLL
+ */
+#define WM8955_MCLKSEL                          0x0100  /* MCLKSEL */
+#define WM8955_MCLKSEL_MASK                     0x0100  /* MCLKSEL */
+#define WM8955_MCLKSEL_SHIFT                         8  /* MCLKSEL */
+#define WM8955_MCLKSEL_WIDTH                         1  /* MCLKSEL */
+#define WM8955_PLLOUTDIV2                       0x0020  /* PLLOUTDIV2 */
+#define WM8955_PLLOUTDIV2_MASK                  0x0020  /* PLLOUTDIV2 */
+#define WM8955_PLLOUTDIV2_SHIFT                      5  /* PLLOUTDIV2 */
+#define WM8955_PLLOUTDIV2_WIDTH                      1  /* PLLOUTDIV2 */
+#define WM8955_PLL_RB                           0x0010  /* PLL_RB */
+#define WM8955_PLL_RB_MASK                      0x0010  /* PLL_RB */
+#define WM8955_PLL_RB_SHIFT                          4  /* PLL_RB */
+#define WM8955_PLL_RB_WIDTH                          1  /* PLL_RB */
+#define WM8955_PLLEN                            0x0008  /* PLLEN */
+#define WM8955_PLLEN_MASK                       0x0008  /* PLLEN */
+#define WM8955_PLLEN_SHIFT                           3  /* PLLEN */
+#define WM8955_PLLEN_WIDTH                           1  /* PLLEN */
+
+/*
+ * R44 (0x2C) - PLL Control 1
+ */
+#define WM8955_N_MASK                           0x01E0  /* N - [8:5] */
+#define WM8955_N_SHIFT                               5  /* N - [8:5] */
+#define WM8955_N_WIDTH                               4  /* N - [8:5] */
+#define WM8955_K_21_18_MASK                     0x000F  /* K(21:18) - [3:0] */
+#define WM8955_K_21_18_SHIFT                         0  /* K(21:18) - [3:0] */
+#define WM8955_K_21_18_WIDTH                         4  /* K(21:18) - [3:0] */
+
+/*
+ * R45 (0x2D) - PLL Control 2
+ */
+#define WM8955_K_17_9_MASK                      0x01FF  /* K(17:9) - [8:0] */
+#define WM8955_K_17_9_SHIFT                          0  /* K(17:9) - [8:0] */
+#define WM8955_K_17_9_WIDTH                          9  /* K(17:9) - [8:0] */
+
+/*
+ * R46 (0x2E) - PLL Control 3
+ */
+#define WM8955_K_8_0_MASK                       0x01FF  /* K(8:0) - [8:0] */
+#define WM8955_K_8_0_SHIFT                           0  /* K(8:0) - [8:0] */
+#define WM8955_K_8_0_WIDTH                           9  /* K(8:0) - [8:0] */
+
+/*
+ * R59 (0x3B) - PLL Control 4
+ */
+#define WM8955_KEN                              0x0080  /* KEN */
+#define WM8955_KEN_MASK                         0x0080  /* KEN */
+#define WM8955_KEN_SHIFT                             7  /* KEN */
+#define WM8955_KEN_WIDTH                             1  /* KEN */
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8960.c b/linux/sound/soc/codecs/wm8960.c
new file mode 100644
index 0000000..ff6ff2f
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8960.c
@@ -0,0 +1,1074 @@
+/*
+ * wm8960.c  --  WM8960 ALSA SoC Audio driver
+ *
+ * Author: Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/wm8960.h>
+
+#include "wm8960.h"
+
+#define AUDIO_NAME "wm8960"
+
+/* R25 - Power 1 */
+#define WM8960_VMID_MASK 0x180
+#define WM8960_VREF      0x40
+
+/* R26 - Power 2 */
+#define WM8960_PWR2_LOUT1	0x40
+#define WM8960_PWR2_ROUT1	0x20
+#define WM8960_PWR2_OUT3	0x02
+
+/* R28 - Anti-pop 1 */
+#define WM8960_POBCTRL   0x80
+#define WM8960_BUFDCOPEN 0x10
+#define WM8960_BUFIOEN   0x08
+#define WM8960_SOFT_ST   0x04
+#define WM8960_HPSTBY    0x01
+
+/* R29 - Anti-pop 2 */
+#define WM8960_DISOP     0x40
+#define WM8960_DRES_MASK 0x30
+
+/*
+ * wm8960 register cache
+ * We can't read the WM8960 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8960_reg[WM8960_CACHEREGNUM] = {
+	0x0097, 0x0097, 0x0000, 0x0000,
+	0x0000, 0x0008, 0x0000, 0x000a,
+	0x01c0, 0x0000, 0x00ff, 0x00ff,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x007b, 0x0100, 0x0032,
+	0x0000, 0x00c3, 0x00c3, 0x01c0,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0100, 0x0100, 0x0050, 0x0050,
+	0x0050, 0x0050, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0040, 0x0000,
+	0x0000, 0x0050, 0x0050, 0x0000,
+	0x0002, 0x0037, 0x004d, 0x0080,
+	0x0008, 0x0031, 0x0026, 0x00e9,
+};
+
+struct wm8960_priv {
+	u16 reg_cache[WM8960_CACHEREGNUM];
+	enum snd_soc_control_type control_type;
+	void *control_data;
+	int (*set_bias_level)(struct snd_soc_codec *,
+			      enum snd_soc_bias_level level);
+	struct snd_soc_dapm_widget *lout1;
+	struct snd_soc_dapm_widget *rout1;
+	struct snd_soc_dapm_widget *out3;
+	bool deemph;
+	int playback_fs;
+};
+
+#define wm8960_reset(c)	snd_soc_write(c, WM8960_RESET, 0)
+
+/* enumerated controls */
+static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted",
+	"Right Inverted", "Stereo Inversion"};
+static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"};
+static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"};
+static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"};
+static const char *wm8960_alcmode[] = {"ALC", "Limiter"};
+
+static const struct soc_enum wm8960_enum[] = {
+	SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity),
+	SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity),
+	SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff),
+	SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff),
+	SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc),
+	SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode),
+};
+
+static const int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8960_set_deemph(struct snd_soc_codec *codec)
+{
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+	int val, i, best;
+
+	/* If we're using deemphasis select the nearest available sample
+	 * rate.
+	 */
+	if (wm8960->deemph) {
+		best = 1;
+		for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+			if (abs(deemph_settings[i] - wm8960->playback_fs) <
+			    abs(deemph_settings[best] - wm8960->playback_fs))
+				best = i;
+		}
+
+		val = best << 1;
+	} else {
+		val = 0;
+	}
+
+	dev_dbg(codec->dev, "Set deemphasis %d\n", val);
+
+	return snd_soc_update_bits(codec, WM8960_DACCTL1,
+				   0x6, val);
+}
+
+static int wm8960_get_deemph(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+	ucontrol->value.enumerated.item[0] = wm8960->deemph;
+	return 0;
+}
+
+static int wm8960_put_deemph(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+	int deemph = ucontrol->value.enumerated.item[0];
+
+	if (deemph > 1)
+		return -EINVAL;
+
+	wm8960->deemph = deemph;
+
+	return wm8960_set_deemph(codec);
+}
+
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+
+static const struct snd_kcontrol_new wm8960_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL,
+		 0, 63, 0, adc_tlv),
+SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL,
+	6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL,
+	7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC,
+		 0, 255, 0, dac_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1,
+		 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1,
+	7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2,
+		 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2,
+	7, 1, 0),
+SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
+SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0),
+
+SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),
+SOC_ENUM("ADC Polarity", wm8960_enum[0]),
+SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),
+
+SOC_ENUM("DAC Polarity", wm8960_enum[2]),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+		    wm8960_get_deemph, wm8960_put_deemph),
+
+SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]),
+SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]),
+SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0),
+SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0),
+
+SOC_ENUM("ALC Function", wm8960_enum[4]),
+SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0),
+SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1),
+SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0),
+SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0),
+SOC_ENUM("ALC Mode", wm8960_enum[5]),
+SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0),
+
+SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0),
+SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0),
+
+SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH,
+	0, 127, 0),
+
+SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume",
+	       WM8960_BYPASS1, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume",
+	       WM8960_LOUTMIX, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume",
+	       WM8960_BYPASS2, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume",
+	       WM8960_ROUTMIX, 4, 7, 1, bypass_tlv),
+};
+
+static const struct snd_kcontrol_new wm8960_lin_boost[] = {
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),
+SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_lin[] = {
+SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_rin_boost[] = {
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0),
+SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_rin[] = {
+SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),
+SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_routput_mixer[] = {
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0),
+SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_mono_out[] = {
+SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("LINPUT1"),
+SND_SOC_DAPM_INPUT("RINPUT1"),
+SND_SOC_DAPM_INPUT("LINPUT2"),
+SND_SOC_DAPM_INPUT("RINPUT2"),
+SND_SOC_DAPM_INPUT("LINPUT3"),
+SND_SOC_DAPM_INPUT("RINPUT3"),
+
+SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0),
+
+SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0,
+		   wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),
+SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0,
+		   wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)),
+
+SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0,
+		   wm8960_lin, ARRAY_SIZE(wm8960_lin)),
+SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0,
+		   wm8960_rin, ARRAY_SIZE(wm8960_rin)),
+
+SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER2, 3, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER2, 2, 0),
+
+SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,
+	&wm8960_loutput_mixer[0],
+	ARRAY_SIZE(wm8960_loutput_mixer)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0,
+	&wm8960_routput_mixer[0],
+	ARRAY_SIZE(wm8960_routput_mixer)),
+
+SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("SPK_LP"),
+SND_SOC_DAPM_OUTPUT("SPK_LN"),
+SND_SOC_DAPM_OUTPUT("HP_L"),
+SND_SOC_DAPM_OUTPUT("HP_R"),
+SND_SOC_DAPM_OUTPUT("SPK_RP"),
+SND_SOC_DAPM_OUTPUT("SPK_RN"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+};
+
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = {
+SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0,
+	&wm8960_mono_out[0],
+	ARRAY_SIZE(wm8960_mono_out)),
+};
+
+/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = {
+SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+	{ "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
+	{ "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
+	{ "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" },
+
+	{ "Left Input Mixer", "Boost Switch", "Left Boost Mixer", },
+	{ "Left Input Mixer", NULL, "LINPUT1", },  /* Really Boost Switch */
+	{ "Left Input Mixer", NULL, "LINPUT2" },
+	{ "Left Input Mixer", NULL, "LINPUT3" },
+
+	{ "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" },
+	{ "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" },
+	{ "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" },
+
+	{ "Right Input Mixer", "Boost Switch", "Right Boost Mixer", },
+	{ "Right Input Mixer", NULL, "RINPUT1", },  /* Really Boost Switch */
+	{ "Right Input Mixer", NULL, "RINPUT2" },
+	{ "Right Input Mixer", NULL, "LINPUT3" },
+
+	{ "Left ADC", NULL, "Left Input Mixer" },
+	{ "Right ADC", NULL, "Right Input Mixer" },
+
+	{ "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" },
+	{ "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} ,
+	{ "Left Output Mixer", "PCM Playback Switch", "Left DAC" },
+
+	{ "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" },
+	{ "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } ,
+	{ "Right Output Mixer", "PCM Playback Switch", "Right DAC" },
+
+	{ "LOUT1 PGA", NULL, "Left Output Mixer" },
+	{ "ROUT1 PGA", NULL, "Right Output Mixer" },
+
+	{ "HP_L", NULL, "LOUT1 PGA" },
+	{ "HP_R", NULL, "ROUT1 PGA" },
+
+	{ "Left Speaker PGA", NULL, "Left Output Mixer" },
+	{ "Right Speaker PGA", NULL, "Right Output Mixer" },
+
+	{ "Left Speaker Output", NULL, "Left Speaker PGA" },
+	{ "Right Speaker Output", NULL, "Right Speaker PGA" },
+
+	{ "SPK_LN", NULL, "Left Speaker Output" },
+	{ "SPK_LP", NULL, "Left Speaker Output" },
+	{ "SPK_RN", NULL, "Right Speaker Output" },
+	{ "SPK_RP", NULL, "Right Speaker Output" },
+};
+
+static const struct snd_soc_dapm_route audio_paths_out3[] = {
+	{ "Mono Output Mixer", "Left Switch", "Left Output Mixer" },
+	{ "Mono Output Mixer", "Right Switch", "Right Output Mixer" },
+
+	{ "OUT3", NULL, "Mono Output Mixer", }
+};
+
+static const struct snd_soc_dapm_route audio_paths_capless[] = {
+	{ "HP_L", NULL, "OUT3 VMID" },
+	{ "HP_R", NULL, "OUT3 VMID" },
+
+	{ "OUT3 VMID", NULL, "Left Output Mixer" },
+	{ "OUT3 VMID", NULL, "Right Output Mixer" },
+};
+
+static int wm8960_add_widgets(struct snd_soc_codec *codec)
+{
+	struct wm8960_data *pdata = codec->dev->platform_data;
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+	struct snd_soc_dapm_widget *w;
+
+	snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets,
+				  ARRAY_SIZE(wm8960_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
+
+	/* In capless mode OUT3 is used to provide VMID for the
+	 * headphone outputs, otherwise it is used as a mono mixer.
+	 */
+	if (pdata && pdata->capless) {
+		snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_capless,
+					  ARRAY_SIZE(wm8960_dapm_widgets_capless));
+
+		snd_soc_dapm_add_routes(codec, audio_paths_capless,
+					ARRAY_SIZE(audio_paths_capless));
+	} else {
+		snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_out3,
+					  ARRAY_SIZE(wm8960_dapm_widgets_out3));
+
+		snd_soc_dapm_add_routes(codec, audio_paths_out3,
+					ARRAY_SIZE(audio_paths_out3));
+	}
+
+	/* We need to power up the headphone output stage out of
+	 * sequence for capless mode.  To save scanning the widget
+	 * list each time to find the desired power state do so now
+	 * and save the result.
+	 */
+	list_for_each_entry(w, &codec->dapm_widgets, list) {
+		if (strcmp(w->name, "LOUT1 PGA") == 0)
+			wm8960->lout1 = w;
+		if (strcmp(w->name, "ROUT1 PGA") == 0)
+			wm8960->rout1 = w;
+		if (strcmp(w->name, "OUT3 VMID") == 0)
+			wm8960->out3 = w;
+	}
+	
+	return 0;
+}
+
+static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = 0;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		iface |= 0x0040;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x0003;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface |= 0x0013;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x0090;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x0080;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x0010;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* set iface */
+	snd_soc_write(codec, WM8960_IFACE1, iface);
+	return 0;
+}
+
+static struct {
+	int rate;
+	unsigned int val;
+} alc_rates[] = {
+	{ 48000, 0 },
+	{ 44100, 0 },
+	{ 32000, 1 },
+	{ 22050, 2 },
+	{ 24000, 2 },
+	{ 16000, 3 },
+	{ 11250, 4 },
+	{ 12000, 4 },
+	{  8000, 5 },
+};
+
+static int wm8960_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+	u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3;
+	int i;
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x0004;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x0008;
+		break;
+	}
+
+	/* Update filters for the new rate */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		wm8960->playback_fs = params_rate(params);
+		wm8960_set_deemph(codec);
+	} else {
+		for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
+			if (alc_rates[i].rate == params_rate(params))
+				snd_soc_update_bits(codec,
+						    WM8960_ADDCTL3, 0x7,
+						    alc_rates[i].val);
+	}
+
+	/* set iface */
+	snd_soc_write(codec, WM8960_IFACE1, iface);
+	return 0;
+}
+
+static int wm8960_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = snd_soc_read(codec, WM8960_DACCTL1) & 0xfff7;
+
+	if (mute)
+		snd_soc_write(codec, WM8960_DACCTL1, mute_reg | 0x8);
+	else
+		snd_soc_write(codec, WM8960_DACCTL1, mute_reg);
+	return 0;
+}
+
+static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec,
+				      enum snd_soc_bias_level level)
+{
+	u16 reg;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		/* Set VMID to 2x50k */
+		reg = snd_soc_read(codec, WM8960_POWER1);
+		reg &= ~0x180;
+		reg |= 0x80;
+		snd_soc_write(codec, WM8960_POWER1, reg);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Enable anti-pop features */
+			snd_soc_write(codec, WM8960_APOP1,
+				      WM8960_POBCTRL | WM8960_SOFT_ST |
+				      WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+			/* Enable & ramp VMID at 2x50k */
+			reg = snd_soc_read(codec, WM8960_POWER1);
+			reg |= 0x80;
+			snd_soc_write(codec, WM8960_POWER1, reg);
+			msleep(100);
+
+			/* Enable VREF */
+			snd_soc_write(codec, WM8960_POWER1, reg | WM8960_VREF);
+
+			/* Disable anti-pop features */
+			snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN);
+		}
+
+		/* Set VMID to 2x250k */
+		reg = snd_soc_read(codec, WM8960_POWER1);
+		reg &= ~0x180;
+		reg |= 0x100;
+		snd_soc_write(codec, WM8960_POWER1, reg);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		/* Enable anti-pop features */
+		snd_soc_write(codec, WM8960_APOP1,
+			     WM8960_POBCTRL | WM8960_SOFT_ST |
+			     WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+		/* Disable VMID and VREF, let them discharge */
+		snd_soc_write(codec, WM8960_POWER1, 0);
+		msleep(600);
+		break;
+	}
+
+	codec->bias_level = level;
+
+	return 0;
+}
+
+static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec,
+					 enum snd_soc_bias_level level)
+{
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+	int reg;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		switch (codec->bias_level) {
+		case SND_SOC_BIAS_STANDBY:
+			/* Enable anti pop mode */
+			snd_soc_update_bits(codec, WM8960_APOP1,
+					    WM8960_POBCTRL | WM8960_SOFT_ST |
+					    WM8960_BUFDCOPEN,
+					    WM8960_POBCTRL | WM8960_SOFT_ST |
+					    WM8960_BUFDCOPEN);
+
+			/* Enable LOUT1, ROUT1 and OUT3 if they're enabled */
+			reg = 0;
+			if (wm8960->lout1 && wm8960->lout1->power)
+				reg |= WM8960_PWR2_LOUT1;
+			if (wm8960->rout1 && wm8960->rout1->power)
+				reg |= WM8960_PWR2_ROUT1;
+			if (wm8960->out3 && wm8960->out3->power)
+				reg |= WM8960_PWR2_OUT3;
+			snd_soc_update_bits(codec, WM8960_POWER2,
+					    WM8960_PWR2_LOUT1 |
+					    WM8960_PWR2_ROUT1 |
+					    WM8960_PWR2_OUT3, reg);
+
+			/* Enable VMID at 2*50k */
+			snd_soc_update_bits(codec, WM8960_POWER1,
+					    WM8960_VMID_MASK, 0x80);
+
+			/* Ramp */
+			msleep(100);
+
+			/* Enable VREF */
+			snd_soc_update_bits(codec, WM8960_POWER1,
+					    WM8960_VREF, WM8960_VREF);
+
+			msleep(100);
+			break;
+
+		case SND_SOC_BIAS_ON:
+			/* Enable anti-pop mode */
+			snd_soc_update_bits(codec, WM8960_APOP1,
+					    WM8960_POBCTRL | WM8960_SOFT_ST |
+					    WM8960_BUFDCOPEN,
+					    WM8960_POBCTRL | WM8960_SOFT_ST |
+					    WM8960_BUFDCOPEN);
+
+			/* Disable VMID and VREF */
+			snd_soc_update_bits(codec, WM8960_POWER1,
+					    WM8960_VREF | WM8960_VMID_MASK, 0);
+			break;
+
+		default:
+			break;
+		}
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		switch (codec->bias_level) {
+		case SND_SOC_BIAS_PREPARE:
+			/* Disable HP discharge */
+			snd_soc_update_bits(codec, WM8960_APOP2,
+					    WM8960_DISOP | WM8960_DRES_MASK,
+					    0);
+
+			/* Disable anti-pop features */
+			snd_soc_update_bits(codec, WM8960_APOP1,
+					    WM8960_POBCTRL | WM8960_SOFT_ST |
+					    WM8960_BUFDCOPEN,
+					    WM8960_POBCTRL | WM8960_SOFT_ST |
+					    WM8960_BUFDCOPEN);
+			break;
+
+		default:
+			break;
+		}
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		break;
+	}
+
+	codec->bias_level = level;
+
+	return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+	u32 pre_div:1;
+	u32 n:4;
+	u32 k:24;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+
+static int pll_factors(unsigned int source, unsigned int target,
+		       struct _pll_div *pll_div)
+{
+	unsigned long long Kpart;
+	unsigned int K, Ndiv, Nmod;
+
+	pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target);
+
+	/* Scale up target to PLL operating frequency */
+	target *= 4;
+
+	Ndiv = target / source;
+	if (Ndiv < 6) {
+		source >>= 1;
+		pll_div->pre_div = 1;
+		Ndiv = target / source;
+	} else
+		pll_div->pre_div = 0;
+
+	if ((Ndiv < 6) || (Ndiv > 12)) {
+		pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv);
+		return -EINVAL;
+	}
+
+	pll_div->n = Ndiv;
+	Nmod = target % source;
+	Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	/* Check if we need to round */
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	K /= 10;
+
+	pll_div->k = K;
+
+	pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n",
+		 pll_div->n, pll_div->k, pll_div->pre_div);
+
+	return 0;
+}
+
+static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+	static struct _pll_div pll_div;
+	int ret;
+
+	if (freq_in && freq_out) {
+		ret = pll_factors(freq_in, freq_out, &pll_div);
+		if (ret != 0)
+			return ret;
+	}
+
+	/* Disable the PLL: even if we are changing the frequency the
+	 * PLL needs to be disabled while we do so. */
+	snd_soc_write(codec, WM8960_CLOCK1,
+		     snd_soc_read(codec, WM8960_CLOCK1) & ~1);
+	snd_soc_write(codec, WM8960_POWER2,
+		     snd_soc_read(codec, WM8960_POWER2) & ~1);
+
+	if (!freq_in || !freq_out)
+		return 0;
+
+	reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f;
+	reg |= pll_div.pre_div << 4;
+	reg |= pll_div.n;
+
+	if (pll_div.k) {
+		reg |= 0x20;
+
+		snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f);
+		snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff);
+		snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff);
+	}
+	snd_soc_write(codec, WM8960_PLL1, reg);
+
+	/* Turn it on */
+	snd_soc_write(codec, WM8960_POWER2,
+		     snd_soc_read(codec, WM8960_POWER2) | 1);
+	msleep(250);
+	snd_soc_write(codec, WM8960_CLOCK1,
+		     snd_soc_read(codec, WM8960_CLOCK1) | 1);
+
+	return 0;
+}
+
+static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+		int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+
+	switch (div_id) {
+	case WM8960_SYSCLKDIV:
+		reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9;
+		snd_soc_write(codec, WM8960_CLOCK1, reg | div);
+		break;
+	case WM8960_DACDIV:
+		reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7;
+		snd_soc_write(codec, WM8960_CLOCK1, reg | div);
+		break;
+	case WM8960_OPCLKDIV:
+		reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f;
+		snd_soc_write(codec, WM8960_PLL1, reg | div);
+		break;
+	case WM8960_DCLKDIV:
+		reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f;
+		snd_soc_write(codec, WM8960_CLOCK2, reg | div);
+		break;
+	case WM8960_TOCLKSEL:
+		reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd;
+		snd_soc_write(codec, WM8960_ADDCTL1, reg | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wm8960_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+	return wm8960->set_bias_level(codec, level);
+}
+
+#define WM8960_RATES SNDRV_PCM_RATE_8000_48000
+
+#define WM8960_FORMATS \
+	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8960_dai_ops = {
+	.hw_params = wm8960_hw_params,
+	.digital_mute = wm8960_mute,
+	.set_fmt = wm8960_set_dai_fmt,
+	.set_clkdiv = wm8960_set_dai_clkdiv,
+	.set_pll = wm8960_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver wm8960_dai = {
+	.name = "wm8960-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8960_RATES,
+		.formats = WM8960_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8960_RATES,
+		.formats = WM8960_FORMATS,},
+	.ops = &wm8960_dai_ops,
+	.symmetric_rates = 1,
+};
+
+static int wm8960_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+	wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8960_resume(struct snd_soc_codec *codec)
+{
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+	int i;
+	u8 data[2];
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) {
+		data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+		data[1] = cache[i] & 0x00ff;
+		codec->hw_write(codec->control_data, data, 2);
+	}
+
+	wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	return 0;
+}
+
+static int wm8960_probe(struct snd_soc_codec *codec)
+{
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+	struct wm8960_data *pdata = dev_get_platdata(codec->dev);
+	int ret;
+	u16 reg;
+
+	wm8960->set_bias_level = wm8960_set_bias_level_out3;
+	codec->control_data = wm8960->control_data;
+
+	if (!pdata) {
+		dev_warn(codec->dev, "No platform data supplied\n");
+	} else {
+		if (pdata->dres > WM8960_DRES_MAX) {
+			dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres);
+			pdata->dres = 0;
+		}
+
+		if (pdata->capless)
+			wm8960->set_bias_level = wm8960_set_bias_level_capless;
+	}
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8960->control_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm8960_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		return ret;
+	}
+
+	wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Latch the update bits */
+	reg = snd_soc_read(codec, WM8960_LINVOL);
+	snd_soc_write(codec, WM8960_LINVOL, reg | 0x100);
+	reg = snd_soc_read(codec, WM8960_RINVOL);
+	snd_soc_write(codec, WM8960_RINVOL, reg | 0x100);
+	reg = snd_soc_read(codec, WM8960_LADC);
+	snd_soc_write(codec, WM8960_LADC, reg | 0x100);
+	reg = snd_soc_read(codec, WM8960_RADC);
+	snd_soc_write(codec, WM8960_RADC, reg | 0x100);
+	reg = snd_soc_read(codec, WM8960_LDAC);
+	snd_soc_write(codec, WM8960_LDAC, reg | 0x100);
+	reg = snd_soc_read(codec, WM8960_RDAC);
+	snd_soc_write(codec, WM8960_RDAC, reg | 0x100);
+	reg = snd_soc_read(codec, WM8960_LOUT1);
+	snd_soc_write(codec, WM8960_LOUT1, reg | 0x100);
+	reg = snd_soc_read(codec, WM8960_ROUT1);
+	snd_soc_write(codec, WM8960_ROUT1, reg | 0x100);
+	reg = snd_soc_read(codec, WM8960_LOUT2);
+	snd_soc_write(codec, WM8960_LOUT2, reg | 0x100);
+	reg = snd_soc_read(codec, WM8960_ROUT2);
+	snd_soc_write(codec, WM8960_ROUT2, reg | 0x100);
+
+	snd_soc_add_controls(codec, wm8960_snd_controls,
+				     ARRAY_SIZE(wm8960_snd_controls));
+	wm8960_add_widgets(codec);
+
+	return 0;
+}
+
+/* power down chip */
+static int wm8960_remove(struct snd_soc_codec *codec)
+{
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+	wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8960 = {
+	.probe =	wm8960_probe,
+	.remove =	wm8960_remove,
+	.suspend =	wm8960_suspend,
+	.resume =	wm8960_resume,
+	.set_bias_level = wm8960_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8960_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8960_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8960_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8960_priv *wm8960;
+	int ret;
+
+	wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL);
+	if (wm8960 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8960);
+	wm8960->control_type = SND_SOC_I2C;
+	wm8960->control_data = i2c;
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8960, &wm8960_dai, 1);
+	if (ret < 0)
+		kfree(wm8960);
+	return ret;
+}
+
+static __devexit int wm8960_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8960_i2c_id[] = {
+	{ "wm8960", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id);
+
+static struct i2c_driver wm8960_i2c_driver = {
+	.driver = {
+		.name = "wm8960-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8960_i2c_probe,
+	.remove =   __devexit_p(wm8960_i2c_remove),
+	.id_table = wm8960_i2c_id,
+};
+#endif
+
+static int __init wm8960_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8960_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8960_modinit);
+
+static void __exit wm8960_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8960_i2c_driver);
+#endif
+}
+module_exit(wm8960_exit);
+
+MODULE_DESCRIPTION("ASoC WM8960 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8960.h b/linux/sound/soc/codecs/wm8960.h
new file mode 100644
index 0000000..2d8163d
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8960.h
@@ -0,0 +1,113 @@
+/*
+ * wm8960.h  --  WM8960 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8960_H
+#define _WM8960_H
+
+/* WM8960 register space */
+
+
+#define WM8960_CACHEREGNUM 	56
+
+#define WM8960_LINVOL		0x0
+#define WM8960_RINVOL		0x1
+#define WM8960_LOUT1		0x2
+#define WM8960_ROUT1		0x3
+#define WM8960_CLOCK1		0x4
+#define WM8960_DACCTL1		0x5
+#define WM8960_DACCTL2		0x6
+#define WM8960_IFACE1		0x7
+#define WM8960_CLOCK2		0x8
+#define WM8960_IFACE2		0x9
+#define WM8960_LDAC		0xa
+#define WM8960_RDAC		0xb
+
+#define WM8960_RESET		0xf
+#define WM8960_3D		0x10
+#define WM8960_ALC1		0x11
+#define WM8960_ALC2		0x12
+#define WM8960_ALC3		0x13
+#define WM8960_NOISEG		0x14
+#define WM8960_LADC		0x15
+#define WM8960_RADC		0x16
+#define WM8960_ADDCTL1		0x17
+#define WM8960_ADDCTL2		0x18
+#define WM8960_POWER1		0x19
+#define WM8960_POWER2		0x1a
+#define WM8960_ADDCTL3		0x1b
+#define WM8960_APOP1		0x1c
+#define WM8960_APOP2		0x1d
+
+#define WM8960_LINPATH		0x20
+#define WM8960_RINPATH		0x21
+#define WM8960_LOUTMIX		0x22
+
+#define WM8960_ROUTMIX		0x25
+#define WM8960_MONOMIX1		0x26
+#define WM8960_MONOMIX2		0x27
+#define WM8960_LOUT2		0x28
+#define WM8960_ROUT2		0x29
+#define WM8960_MONO		0x2a
+#define WM8960_INBMIX1		0x2b
+#define WM8960_INBMIX2		0x2c
+#define WM8960_BYPASS1		0x2d
+#define WM8960_BYPASS2		0x2e
+#define WM8960_POWER3		0x2f
+#define WM8960_ADDCTL4		0x30
+#define WM8960_CLASSD1		0x31
+
+#define WM8960_CLASSD3		0x33
+#define WM8960_PLL1		0x34
+#define WM8960_PLL2		0x35
+#define WM8960_PLL3		0x36
+#define WM8960_PLL4		0x37
+
+
+/*
+ * WM8960 Clock dividers
+ */
+#define WM8960_SYSCLKDIV 		0
+#define WM8960_DACDIV			1
+#define WM8960_OPCLKDIV			2
+#define WM8960_DCLKDIV			3
+#define WM8960_TOCLKSEL			4
+
+#define WM8960_SYSCLK_DIV_1		(0 << 1)
+#define WM8960_SYSCLK_DIV_2		(2 << 1)
+
+#define WM8960_SYSCLK_MCLK		(0 << 0)
+#define WM8960_SYSCLK_PLL		(1 << 0)
+
+#define WM8960_DAC_DIV_1		(0 << 3)
+#define WM8960_DAC_DIV_1_5		(1 << 3)
+#define WM8960_DAC_DIV_2		(2 << 3)
+#define WM8960_DAC_DIV_3		(3 << 3)
+#define WM8960_DAC_DIV_4		(4 << 3)
+#define WM8960_DAC_DIV_5_5		(5 << 3)
+#define WM8960_DAC_DIV_6		(6 << 3)
+
+#define WM8960_DCLK_DIV_1_5		(0 << 6)
+#define WM8960_DCLK_DIV_2		(1 << 6)
+#define WM8960_DCLK_DIV_3		(2 << 6)
+#define WM8960_DCLK_DIV_4		(3 << 6)
+#define WM8960_DCLK_DIV_6		(4 << 6)
+#define WM8960_DCLK_DIV_8		(5 << 6)
+#define WM8960_DCLK_DIV_12		(6 << 6)
+#define WM8960_DCLK_DIV_16		(7 << 6)
+
+#define WM8960_TOCLK_F19		(0 << 1)
+#define WM8960_TOCLK_F21		(1 << 1)
+
+#define WM8960_OPCLK_DIV_1		(0 << 0)
+#define WM8960_OPCLK_DIV_2		(1 << 0)
+#define WM8960_OPCLK_DIV_3		(2 << 0)
+#define WM8960_OPCLK_DIV_4		(3 << 0)
+#define WM8960_OPCLK_DIV_5_5		(4 << 0)
+#define WM8960_OPCLK_DIV_6		(5 << 0)
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8961.c b/linux/sound/soc/codecs/wm8961.c
new file mode 100644
index 0000000..8340485
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8961.c
@@ -0,0 +1,1152 @@
+/*
+ * wm8961.c  --  WM8961 ALSA SoC Audio driver
+ *
+ * Author: Mark Brown
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Currently unimplemented features:
+ *  - ALC
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8961.h"
+
+#define WM8961_MAX_REGISTER                     0xFC
+
+static u16 wm8961_reg_defaults[] = {
+	0x009F,     /* R0   - Left Input volume */
+	0x009F,     /* R1   - Right Input volume */
+	0x0000,     /* R2   - LOUT1 volume */
+	0x0000,     /* R3   - ROUT1 volume */
+	0x0020,     /* R4   - Clocking1 */
+	0x0008,     /* R5   - ADC & DAC Control 1 */
+	0x0000,     /* R6   - ADC & DAC Control 2 */
+	0x000A,     /* R7   - Audio Interface 0 */
+	0x01F4,     /* R8   - Clocking2 */
+	0x0000,     /* R9   - Audio Interface 1 */
+	0x00FF,     /* R10  - Left DAC volume */
+	0x00FF,     /* R11  - Right DAC volume */
+	0x0000,     /* R12 */
+	0x0000,     /* R13 */
+	0x0040,     /* R14  - Audio Interface 2 */
+	0x0000,     /* R15  - Software Reset */
+	0x0000,     /* R16 */
+	0x007B,     /* R17  - ALC1 */
+	0x0000,     /* R18  - ALC2 */
+	0x0032,     /* R19  - ALC3 */
+	0x0000,     /* R20  - Noise Gate */
+	0x00C0,     /* R21  - Left ADC volume */
+	0x00C0,     /* R22  - Right ADC volume */
+	0x0120,     /* R23  - Additional control(1) */
+	0x0000,     /* R24  - Additional control(2) */
+	0x0000,     /* R25  - Pwr Mgmt (1) */
+	0x0000,     /* R26  - Pwr Mgmt (2) */
+	0x0000,     /* R27  - Additional Control (3) */
+	0x0000,     /* R28  - Anti-pop */
+	0x0000,     /* R29 */
+	0x005F,     /* R30  - Clocking 3 */
+	0x0000,     /* R31 */
+	0x0000,     /* R32  - ADCL signal path */
+	0x0000,     /* R33  - ADCR signal path */
+	0x0000,     /* R34 */
+	0x0000,     /* R35 */
+	0x0000,     /* R36 */
+	0x0000,     /* R37 */
+	0x0000,     /* R38 */
+	0x0000,     /* R39 */
+	0x0000,     /* R40  - LOUT2 volume */
+	0x0000,     /* R41  - ROUT2 volume */
+	0x0000,     /* R42 */
+	0x0000,     /* R43 */
+	0x0000,     /* R44 */
+	0x0000,     /* R45 */
+	0x0000,     /* R46 */
+	0x0000,     /* R47  - Pwr Mgmt (3) */
+	0x0023,     /* R48  - Additional Control (4) */
+	0x0000,     /* R49  - Class D Control 1 */
+	0x0000,     /* R50 */
+	0x0003,     /* R51  - Class D Control 2 */
+	0x0000,     /* R52 */
+	0x0000,     /* R53 */
+	0x0000,     /* R54 */
+	0x0000,     /* R55 */
+	0x0106,     /* R56  - Clocking 4 */
+	0x0000,     /* R57  - DSP Sidetone 0 */
+	0x0000,     /* R58  - DSP Sidetone 1 */
+	0x0000,     /* R59 */
+	0x0000,     /* R60  - DC Servo 0 */
+	0x0000,     /* R61  - DC Servo 1 */
+	0x0000,     /* R62 */
+	0x015E,     /* R63  - DC Servo 3 */
+	0x0010,     /* R64 */
+	0x0010,     /* R65  - DC Servo 5 */
+	0x0000,     /* R66 */
+	0x0001,     /* R67 */
+	0x0003,     /* R68  - Analogue PGA Bias */
+	0x0000,     /* R69  - Analogue HP 0 */
+	0x0060,     /* R70 */
+	0x01FB,     /* R71  - Analogue HP 2 */
+	0x0000,     /* R72  - Charge Pump 1 */
+	0x0065,     /* R73 */
+	0x005F,     /* R74 */
+	0x0059,     /* R75 */
+	0x006B,     /* R76 */
+	0x0038,     /* R77 */
+	0x000C,     /* R78 */
+	0x000A,     /* R79 */
+	0x006B,     /* R80 */
+	0x0000,     /* R81 */
+	0x0000,     /* R82  - Charge Pump B */
+	0x0087,     /* R83 */
+	0x0000,     /* R84 */
+	0x005C,     /* R85 */
+	0x0000,     /* R86 */
+	0x0000,     /* R87  - Write Sequencer 1 */
+	0x0000,     /* R88  - Write Sequencer 2 */
+	0x0000,     /* R89  - Write Sequencer 3 */
+	0x0000,     /* R90  - Write Sequencer 4 */
+	0x0000,     /* R91  - Write Sequencer 5 */
+	0x0000,     /* R92  - Write Sequencer 6 */
+	0x0000,     /* R93  - Write Sequencer 7 */
+	0x0000,     /* R94 */
+	0x0000,     /* R95 */
+	0x0000,     /* R96 */
+	0x0000,     /* R97 */
+	0x0000,     /* R98 */
+	0x0000,     /* R99 */
+	0x0000,     /* R100 */
+	0x0000,     /* R101 */
+	0x0000,     /* R102 */
+	0x0000,     /* R103 */
+	0x0000,     /* R104 */
+	0x0000,     /* R105 */
+	0x0000,     /* R106 */
+	0x0000,     /* R107 */
+	0x0000,     /* R108 */
+	0x0000,     /* R109 */
+	0x0000,     /* R110 */
+	0x0000,     /* R111 */
+	0x0000,     /* R112 */
+	0x0000,     /* R113 */
+	0x0000,     /* R114 */
+	0x0000,     /* R115 */
+	0x0000,     /* R116 */
+	0x0000,     /* R117 */
+	0x0000,     /* R118 */
+	0x0000,     /* R119 */
+	0x0000,     /* R120 */
+	0x0000,     /* R121 */
+	0x0000,     /* R122 */
+	0x0000,     /* R123 */
+	0x0000,     /* R124 */
+	0x0000,     /* R125 */
+	0x0000,     /* R126 */
+	0x0000,     /* R127 */
+	0x0000,     /* R128 */
+	0x0000,     /* R129 */
+	0x0000,     /* R130 */
+	0x0000,     /* R131 */
+	0x0000,     /* R132 */
+	0x0000,     /* R133 */
+	0x0000,     /* R134 */
+	0x0000,     /* R135 */
+	0x0000,     /* R136 */
+	0x0000,     /* R137 */
+	0x0000,     /* R138 */
+	0x0000,     /* R139 */
+	0x0000,     /* R140 */
+	0x0000,     /* R141 */
+	0x0000,     /* R142 */
+	0x0000,     /* R143 */
+	0x0000,     /* R144 */
+	0x0000,     /* R145 */
+	0x0000,     /* R146 */
+	0x0000,     /* R147 */
+	0x0000,     /* R148 */
+	0x0000,     /* R149 */
+	0x0000,     /* R150 */
+	0x0000,     /* R151 */
+	0x0000,     /* R152 */
+	0x0000,     /* R153 */
+	0x0000,     /* R154 */
+	0x0000,     /* R155 */
+	0x0000,     /* R156 */
+	0x0000,     /* R157 */
+	0x0000,     /* R158 */
+	0x0000,     /* R159 */
+	0x0000,     /* R160 */
+	0x0000,     /* R161 */
+	0x0000,     /* R162 */
+	0x0000,     /* R163 */
+	0x0000,     /* R164 */
+	0x0000,     /* R165 */
+	0x0000,     /* R166 */
+	0x0000,     /* R167 */
+	0x0000,     /* R168 */
+	0x0000,     /* R169 */
+	0x0000,     /* R170 */
+	0x0000,     /* R171 */
+	0x0000,     /* R172 */
+	0x0000,     /* R173 */
+	0x0000,     /* R174 */
+	0x0000,     /* R175 */
+	0x0000,     /* R176 */
+	0x0000,     /* R177 */
+	0x0000,     /* R178 */
+	0x0000,     /* R179 */
+	0x0000,     /* R180 */
+	0x0000,     /* R181 */
+	0x0000,     /* R182 */
+	0x0000,     /* R183 */
+	0x0000,     /* R184 */
+	0x0000,     /* R185 */
+	0x0000,     /* R186 */
+	0x0000,     /* R187 */
+	0x0000,     /* R188 */
+	0x0000,     /* R189 */
+	0x0000,     /* R190 */
+	0x0000,     /* R191 */
+	0x0000,     /* R192 */
+	0x0000,     /* R193 */
+	0x0000,     /* R194 */
+	0x0000,     /* R195 */
+	0x0030,     /* R196 */
+	0x0006,     /* R197 */
+	0x0000,     /* R198 */
+	0x0060,     /* R199 */
+	0x0000,     /* R200 */
+	0x003F,     /* R201 */
+	0x0000,     /* R202 */
+	0x0000,     /* R203 */
+	0x0000,     /* R204 */
+	0x0001,     /* R205 */
+	0x0000,     /* R206 */
+	0x0181,     /* R207 */
+	0x0005,     /* R208 */
+	0x0008,     /* R209 */
+	0x0008,     /* R210 */
+	0x0000,     /* R211 */
+	0x013B,     /* R212 */
+	0x0000,     /* R213 */
+	0x0000,     /* R214 */
+	0x0000,     /* R215 */
+	0x0000,     /* R216 */
+	0x0070,     /* R217 */
+	0x0000,     /* R218 */
+	0x0000,     /* R219 */
+	0x0000,     /* R220 */
+	0x0000,     /* R221 */
+	0x0000,     /* R222 */
+	0x0003,     /* R223 */
+	0x0000,     /* R224 */
+	0x0000,     /* R225 */
+	0x0001,     /* R226 */
+	0x0008,     /* R227 */
+	0x0000,     /* R228 */
+	0x0000,     /* R229 */
+	0x0000,     /* R230 */
+	0x0000,     /* R231 */
+	0x0004,     /* R232 */
+	0x0000,     /* R233 */
+	0x0000,     /* R234 */
+	0x0000,     /* R235 */
+	0x0000,     /* R236 */
+	0x0000,     /* R237 */
+	0x0080,     /* R238 */
+	0x0000,     /* R239 */
+	0x0000,     /* R240 */
+	0x0000,     /* R241 */
+	0x0000,     /* R242 */
+	0x0000,     /* R243 */
+	0x0000,     /* R244 */
+	0x0052,     /* R245 */
+	0x0110,     /* R246 */
+	0x0040,     /* R247 */
+	0x0000,     /* R248 */
+	0x0030,     /* R249 */
+	0x0000,     /* R250 */
+	0x0000,     /* R251 */
+	0x0001,     /* R252 - General test 1 */
+};
+
+struct wm8961_priv {
+	enum snd_soc_control_type control_type;
+	int sysclk;
+	u16 reg_cache[WM8961_MAX_REGISTER];
+};
+
+static int wm8961_volatile_register(unsigned int reg)
+{
+	switch (reg) {
+	case WM8961_SOFTWARE_RESET:
+	case WM8961_WRITE_SEQUENCER_7:
+	case WM8961_DC_SERVO_1:
+		return 1;
+
+	default:
+		return 0;
+	}
+}
+
+static int wm8961_reset(struct snd_soc_codec *codec)
+{
+	return snd_soc_write(codec, WM8961_SOFTWARE_RESET, 0);
+}
+
+/*
+ * The headphone output supports special anti-pop sequences giving
+ * silent power up and power down.
+ */
+static int wm8961_hp_event(struct snd_soc_dapm_widget *w,
+			   struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	u16 hp_reg = snd_soc_read(codec, WM8961_ANALOGUE_HP_0);
+	u16 cp_reg = snd_soc_read(codec, WM8961_CHARGE_PUMP_1);
+	u16 pwr_reg = snd_soc_read(codec, WM8961_PWR_MGMT_2);
+	u16 dcs_reg = snd_soc_read(codec, WM8961_DC_SERVO_1);
+	int timeout = 500;
+
+	if (event & SND_SOC_DAPM_POST_PMU) {
+		/* Make sure the output is shorted */
+		hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT);
+		snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+		/* Enable the charge pump */
+		cp_reg |= WM8961_CP_ENA;
+		snd_soc_write(codec, WM8961_CHARGE_PUMP_1, cp_reg);
+		mdelay(5);
+
+		/* Enable the PGA */
+		pwr_reg |= WM8961_LOUT1_PGA | WM8961_ROUT1_PGA;
+		snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg);
+
+		/* Enable the amplifier */
+		hp_reg |= WM8961_HPR_ENA | WM8961_HPL_ENA;
+		snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+		/* Second stage enable */
+		hp_reg |= WM8961_HPR_ENA_DLY | WM8961_HPL_ENA_DLY;
+		snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+		/* Enable the DC servo & trigger startup */
+		dcs_reg |=
+			WM8961_DCS_ENA_CHAN_HPR | WM8961_DCS_TRIG_STARTUP_HPR |
+			WM8961_DCS_ENA_CHAN_HPL | WM8961_DCS_TRIG_STARTUP_HPL;
+		dev_dbg(codec->dev, "Enabling DC servo\n");
+
+		snd_soc_write(codec, WM8961_DC_SERVO_1, dcs_reg);
+		do {
+			msleep(1);
+			dcs_reg = snd_soc_read(codec, WM8961_DC_SERVO_1);
+		} while (--timeout &&
+			 dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR |
+				WM8961_DCS_TRIG_STARTUP_HPL));
+		if (dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR |
+			       WM8961_DCS_TRIG_STARTUP_HPL))
+			dev_err(codec->dev, "DC servo timed out\n");
+		else
+			dev_dbg(codec->dev, "DC servo startup complete\n");
+
+		/* Enable the output stage */
+		hp_reg |= WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP;
+		snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+		/* Remove the short on the output stage */
+		hp_reg |= WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT;
+		snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+	}
+
+	if (event & SND_SOC_DAPM_PRE_PMD) {
+		/* Short the output */
+		hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT);
+		snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+		/* Disable the output stage */
+		hp_reg &= ~(WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP);
+		snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+		/* Disable DC offset cancellation */
+		dcs_reg &= ~(WM8961_DCS_ENA_CHAN_HPR |
+			     WM8961_DCS_ENA_CHAN_HPL);
+		snd_soc_write(codec, WM8961_DC_SERVO_1, dcs_reg);
+
+		/* Finish up */
+		hp_reg &= ~(WM8961_HPR_ENA_DLY | WM8961_HPR_ENA |
+			    WM8961_HPL_ENA_DLY | WM8961_HPL_ENA);
+		snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg);
+
+		/* Disable the PGA */
+		pwr_reg &= ~(WM8961_LOUT1_PGA | WM8961_ROUT1_PGA);
+		snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg);
+
+		/* Disable the charge pump */
+		dev_dbg(codec->dev, "Disabling charge pump\n");
+		snd_soc_write(codec, WM8961_CHARGE_PUMP_1,
+			     cp_reg & ~WM8961_CP_ENA);
+	}
+
+	return 0;
+}
+
+static int wm8961_spk_event(struct snd_soc_dapm_widget *w,
+			    struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	u16 pwr_reg = snd_soc_read(codec, WM8961_PWR_MGMT_2);
+	u16 spk_reg = snd_soc_read(codec, WM8961_CLASS_D_CONTROL_1);
+
+	if (event & SND_SOC_DAPM_POST_PMU) {
+		/* Enable the PGA */
+		pwr_reg |= WM8961_SPKL_PGA | WM8961_SPKR_PGA;
+		snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg);
+
+		/* Enable the amplifier */
+		spk_reg |= WM8961_SPKL_ENA | WM8961_SPKR_ENA;
+		snd_soc_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg);
+	}
+
+	if (event & SND_SOC_DAPM_PRE_PMD) {
+		/* Enable the amplifier */
+		spk_reg &= ~(WM8961_SPKL_ENA | WM8961_SPKR_ENA);
+		snd_soc_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg);
+
+		/* Enable the PGA */
+		pwr_reg &= ~(WM8961_SPKL_PGA | WM8961_SPKR_PGA);
+		snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg);
+	}
+
+	return 0;
+}
+
+static const char *adc_hpf_text[] = {
+	"Hi-fi", "Voice 1", "Voice 2", "Voice 3",
+};
+
+static const struct soc_enum adc_hpf =
+	SOC_ENUM_SINGLE(WM8961_ADC_DAC_CONTROL_2, 7, 4, adc_hpf_text);
+
+static const char *dac_deemph_text[] = {
+	"None", "32kHz", "44.1kHz", "48kHz",
+};
+
+static const struct soc_enum dac_deemph =
+	SOC_ENUM_SINGLE(WM8961_ADC_DAC_CONTROL_1, 1, 4, dac_deemph_text);
+
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(hp_sec_tlv, -700, 100, 0);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0);
+static unsigned int boost_tlv[] = {
+	TLV_DB_RANGE_HEAD(4),
+	0, 0, TLV_DB_SCALE_ITEM(0,  0, 0),
+	1, 1, TLV_DB_SCALE_ITEM(13, 0, 0),
+	2, 2, TLV_DB_SCALE_ITEM(20, 0, 0),
+	3, 3, TLV_DB_SCALE_ITEM(29, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(pga_tlv, -2325, 75, 0);
+
+static const struct snd_kcontrol_new wm8961_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Headphone Volume", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME,
+		 0, 127, 0, out_tlv),
+SOC_DOUBLE_TLV("Headphone Secondary Volume", WM8961_ANALOGUE_HP_2,
+	       6, 3, 7, 0, hp_sec_tlv),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME,
+	     7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Speaker Volume", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME,
+		 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Speaker ZC Switch", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME,
+	   7, 1, 0),
+SOC_SINGLE("Speaker AC Gain", WM8961_CLASS_D_CONTROL_2, 0, 7, 0),
+
+SOC_SINGLE("DAC x128 OSR Switch", WM8961_ADC_DAC_CONTROL_2, 0, 1, 0),
+SOC_ENUM("DAC Deemphasis", dac_deemph),
+SOC_SINGLE("DAC Soft Mute Switch", WM8961_ADC_DAC_CONTROL_2, 3, 1, 0),
+
+SOC_DOUBLE_R_TLV("Sidetone Volume", WM8961_DSP_SIDETONE_0,
+		 WM8961_DSP_SIDETONE_1, 4, 12, 0, sidetone_tlv),
+
+SOC_SINGLE("ADC High Pass Filter Switch", WM8961_ADC_DAC_CONTROL_1, 0, 1, 0),
+SOC_ENUM("ADC High Pass Filter Mode", adc_hpf),
+
+SOC_DOUBLE_R_TLV("Capture Volume",
+		 WM8961_LEFT_ADC_VOLUME, WM8961_RIGHT_ADC_VOLUME,
+		 1, 119, 0, adc_tlv),
+SOC_DOUBLE_R_TLV("Capture Boost Volume",
+		 WM8961_ADCL_SIGNAL_PATH, WM8961_ADCR_SIGNAL_PATH,
+		 4, 3, 0, boost_tlv),
+SOC_DOUBLE_R_TLV("Capture PGA Volume",
+		 WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME,
+		 0, 62, 0, pga_tlv),
+SOC_DOUBLE_R("Capture PGA ZC Switch",
+	     WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME,
+	     6, 1, 1),
+SOC_DOUBLE_R("Capture PGA Switch",
+	     WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME,
+	     7, 1, 1),
+};
+
+static const char *sidetone_text[] = {
+	"None", "Left", "Right"
+};
+
+static const struct soc_enum dacl_sidetone =
+	SOC_ENUM_SINGLE(WM8961_DSP_SIDETONE_0, 2, 3, sidetone_text);
+
+static const struct soc_enum dacr_sidetone =
+	SOC_ENUM_SINGLE(WM8961_DSP_SIDETONE_1, 2, 3, sidetone_text);
+
+static const struct snd_kcontrol_new dacl_mux =
+	SOC_DAPM_ENUM("DACL Sidetone", dacl_sidetone);
+
+static const struct snd_kcontrol_new dacr_mux =
+	SOC_DAPM_ENUM("DACR Sidetone", dacr_sidetone);
+
+static const struct snd_soc_dapm_widget wm8961_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("LINPUT"),
+SND_SOC_DAPM_INPUT("RINPUT"),
+
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8961_CLOCKING2, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Left Input", WM8961_PWR_MGMT_1, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Input", WM8961_PWR_MGMT_1, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", WM8961_PWR_MGMT_1, 3, 0),
+SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", WM8961_PWR_MGMT_1, 2, 0),
+
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8961_PWR_MGMT_1, 1, 0),
+
+SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &dacl_mux),
+SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &dacr_mux),
+
+SND_SOC_DAPM_DAC("DACL", "HiFi Playback", WM8961_PWR_MGMT_2, 8, 0),
+SND_SOC_DAPM_DAC("DACR", "HiFi Playback", WM8961_PWR_MGMT_2, 7, 0),
+
+/* Handle as a mono path for DCS */
+SND_SOC_DAPM_PGA_E("Headphone Output", SND_SOC_NOPM,
+		   4, 0, NULL, 0, wm8961_hp_event,
+		   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_E("Speaker Output", SND_SOC_NOPM,
+		   4, 0, NULL, 0, wm8961_spk_event,
+		   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_OUTPUT("HP_L"),
+SND_SOC_DAPM_OUTPUT("HP_R"),
+SND_SOC_DAPM_OUTPUT("SPK_LN"),
+SND_SOC_DAPM_OUTPUT("SPK_LP"),
+SND_SOC_DAPM_OUTPUT("SPK_RN"),
+SND_SOC_DAPM_OUTPUT("SPK_RP"),
+};
+
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+	{ "DACL", NULL, "CLK_DSP" },
+	{ "DACL", NULL, "DACL Sidetone" },
+	{ "DACR", NULL, "CLK_DSP" },
+	{ "DACR", NULL, "DACR Sidetone" },
+
+	{ "DACL Sidetone", "Left", "ADCL" },
+	{ "DACL Sidetone", "Right", "ADCR" },
+
+	{ "DACR Sidetone", "Left", "ADCL" },
+	{ "DACR Sidetone", "Right", "ADCR" },
+
+	{ "HP_L", NULL, "Headphone Output" },
+	{ "HP_R", NULL, "Headphone Output" },
+	{ "Headphone Output", NULL, "DACL" },
+	{ "Headphone Output", NULL, "DACR" },
+
+	{ "SPK_LN", NULL, "Speaker Output" },
+	{ "SPK_LP", NULL, "Speaker Output" },
+	{ "SPK_RN", NULL, "Speaker Output" },
+	{ "SPK_RP", NULL, "Speaker Output" },
+
+	{ "Speaker Output", NULL, "DACL" },
+	{ "Speaker Output", NULL, "DACR" },
+
+	{ "ADCL", NULL, "Left Input" },
+	{ "ADCL", NULL, "CLK_DSP" },
+	{ "ADCR", NULL, "Right Input" },
+	{ "ADCR", NULL, "CLK_DSP" },
+
+	{ "Left Input", NULL, "LINPUT" },
+	{ "Right Input", NULL, "RINPUT" },
+
+};
+
+/* Values for CLK_SYS_RATE */
+static struct {
+	int ratio;
+	u16 val;
+} wm8961_clk_sys_ratio[] = {
+	{  64,  0 },
+	{  128, 1 },
+	{  192, 2 },
+	{  256, 3 },
+	{  384, 4 },
+	{  512, 5 },
+	{  768, 6 },
+	{ 1024, 7 },
+	{ 1408, 8 },
+	{ 1536, 9 },
+};
+
+/* Values for SAMPLE_RATE */
+static struct {
+	int rate;
+	u16 val;
+} wm8961_srate[] = {
+	{ 48000, 0 },
+	{ 44100, 0 },
+	{ 32000, 1 },
+	{ 22050, 2 },
+	{ 24000, 2 },
+	{ 16000, 3 },
+	{ 11250, 4 },
+	{ 12000, 4 },
+	{  8000, 5 },
+};
+
+static int wm8961_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8961_priv *wm8961 = snd_soc_codec_get_drvdata(codec);
+	int i, best, target, fs;
+	u16 reg;
+
+	fs = params_rate(params);
+
+	if (!wm8961->sysclk) {
+		dev_err(codec->dev, "MCLK has not been specified\n");
+		return -EINVAL;
+	}
+
+	/* Find the closest sample rate for the filters */
+	best = 0;
+	for (i = 0; i < ARRAY_SIZE(wm8961_srate); i++) {
+		if (abs(wm8961_srate[i].rate - fs) <
+		    abs(wm8961_srate[best].rate - fs))
+			best = i;
+	}
+	reg = snd_soc_read(codec, WM8961_ADDITIONAL_CONTROL_3);
+	reg &= ~WM8961_SAMPLE_RATE_MASK;
+	reg |= wm8961_srate[best].val;
+	snd_soc_write(codec, WM8961_ADDITIONAL_CONTROL_3, reg);
+	dev_dbg(codec->dev, "Selected SRATE %dHz for %dHz\n",
+		wm8961_srate[best].rate, fs);
+
+	/* Select a CLK_SYS/fs ratio equal to or higher than required */
+	target = wm8961->sysclk / fs;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && target < 64) {
+		dev_err(codec->dev,
+			"SYSCLK must be at least 64*fs for DAC\n");
+		return -EINVAL;
+	}
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && target < 256) {
+		dev_err(codec->dev,
+			"SYSCLK must be at least 256*fs for ADC\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm8961_clk_sys_ratio); i++) {
+		if (wm8961_clk_sys_ratio[i].ratio >= target)
+			break;
+	}
+	if (i == ARRAY_SIZE(wm8961_clk_sys_ratio)) {
+		dev_err(codec->dev, "Unable to generate CLK_SYS_RATE\n");
+		return -EINVAL;
+	}
+	dev_dbg(codec->dev, "Selected CLK_SYS_RATE of %d for %d/%d=%d\n",
+		wm8961_clk_sys_ratio[i].ratio, wm8961->sysclk, fs,
+		wm8961->sysclk / fs);
+
+	reg = snd_soc_read(codec, WM8961_CLOCKING_4);
+	reg &= ~WM8961_CLK_SYS_RATE_MASK;
+	reg |= wm8961_clk_sys_ratio[i].val << WM8961_CLK_SYS_RATE_SHIFT;
+	snd_soc_write(codec, WM8961_CLOCKING_4, reg);
+
+	reg = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_0);
+	reg &= ~WM8961_WL_MASK;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		reg |= 1 << WM8961_WL_SHIFT;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		reg |= 2 << WM8961_WL_SHIFT;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		reg |= 3 << WM8961_WL_SHIFT;
+		break;
+	default:
+		return -EINVAL;
+	}
+	snd_soc_write(codec, WM8961_AUDIO_INTERFACE_0, reg);
+
+	/* Sloping stop-band filter is recommended for <= 24kHz */
+	reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_2);
+	if (fs <= 24000)
+		reg |= WM8961_DACSLOPE;
+	else
+		reg &= ~WM8961_DACSLOPE;
+	snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_2, reg);
+
+	return 0;
+}
+
+static int wm8961_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+			     unsigned int freq,
+			     int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8961_priv *wm8961 = snd_soc_codec_get_drvdata(codec);
+	u16 reg = snd_soc_read(codec, WM8961_CLOCKING1);
+
+	if (freq > 33000000) {
+		dev_err(codec->dev, "MCLK must be <33MHz\n");
+		return -EINVAL;
+	}
+
+	if (freq > 16500000) {
+		dev_dbg(codec->dev, "Using MCLK/2 for %dHz MCLK\n", freq);
+		reg |= WM8961_MCLKDIV;
+		freq /= 2;
+	} else {
+		dev_dbg(codec->dev, "Using MCLK/1 for %dHz MCLK\n", freq);
+		reg &= ~WM8961_MCLKDIV;
+	}
+
+	snd_soc_write(codec, WM8961_CLOCKING1, reg);
+
+	wm8961->sysclk = freq;
+
+	return 0;
+}
+
+static int wm8961_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 aif = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_0);
+
+	aif &= ~(WM8961_BCLKINV | WM8961_LRP |
+		 WM8961_MS | WM8961_FORMAT_MASK);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		aif |= WM8961_MS;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+
+	case SND_SOC_DAIFMT_LEFT_J:
+		aif |= 1;
+		break;
+
+	case SND_SOC_DAIFMT_I2S:
+		aif |= 2;
+		break;
+
+	case SND_SOC_DAIFMT_DSP_B:
+		aif |= WM8961_LRP;
+	case SND_SOC_DAIFMT_DSP_A:
+		aif |= 3;
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+		case SND_SOC_DAIFMT_IB_NF:
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		aif |= WM8961_LRP;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		aif |= WM8961_BCLKINV;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		aif |= WM8961_BCLKINV | WM8961_LRP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snd_soc_write(codec, WM8961_AUDIO_INTERFACE_0, aif);
+}
+
+static int wm8961_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 reg = snd_soc_read(codec, WM8961_ADDITIONAL_CONTROL_2);
+
+	if (tristate)
+		reg |= WM8961_TRIS;
+	else
+		reg &= ~WM8961_TRIS;
+
+	return snd_soc_write(codec, WM8961_ADDITIONAL_CONTROL_2, reg);
+}
+
+static int wm8961_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_1);
+
+	if (mute)
+		reg |= WM8961_DACMU;
+	else
+		reg &= ~WM8961_DACMU;
+
+	msleep(17);
+
+	return snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_1, reg);
+}
+
+static int wm8961_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 reg;
+
+	switch (div_id) {
+	case WM8961_BCLK:
+		reg = snd_soc_read(codec, WM8961_CLOCKING2);
+		reg &= ~WM8961_BCLKDIV_MASK;
+		reg |= div;
+		snd_soc_write(codec, WM8961_CLOCKING2, reg);
+		break;
+
+	case WM8961_LRCLK:
+		reg = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_2);
+		reg &= ~WM8961_LRCLK_RATE_MASK;
+		reg |= div;
+		snd_soc_write(codec, WM8961_AUDIO_INTERFACE_2, reg);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wm8961_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 reg;
+
+	/* This is all slightly unusual since we have no bypass paths
+	 * and the output amplifier structure means we can just slam
+	 * the biases straight up rather than having to ramp them
+	 * slowly.
+	 */
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		if (codec->bias_level == SND_SOC_BIAS_STANDBY) {
+			/* Enable bias generation */
+			reg = snd_soc_read(codec, WM8961_ANTI_POP);
+			reg |= WM8961_BUFIOEN | WM8961_BUFDCOPEN;
+			snd_soc_write(codec, WM8961_ANTI_POP, reg);
+
+			/* VMID=2*50k, VREF */
+			reg = snd_soc_read(codec, WM8961_PWR_MGMT_1);
+			reg &= ~WM8961_VMIDSEL_MASK;
+			reg |= (1 << WM8961_VMIDSEL_SHIFT) | WM8961_VREF;
+			snd_soc_write(codec, WM8961_PWR_MGMT_1, reg);
+		}
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_PREPARE) {
+			/* VREF off */
+			reg = snd_soc_read(codec, WM8961_PWR_MGMT_1);
+			reg &= ~WM8961_VREF;
+			snd_soc_write(codec, WM8961_PWR_MGMT_1, reg);
+
+			/* Bias generation off */
+			reg = snd_soc_read(codec, WM8961_ANTI_POP);
+			reg &= ~(WM8961_BUFIOEN | WM8961_BUFDCOPEN);
+			snd_soc_write(codec, WM8961_ANTI_POP, reg);
+
+			/* VMID off */
+			reg = snd_soc_read(codec, WM8961_PWR_MGMT_1);
+			reg &= ~WM8961_VMIDSEL_MASK;
+			snd_soc_write(codec, WM8961_PWR_MGMT_1, reg);
+		}
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		break;
+	}
+
+	codec->bias_level = level;
+
+	return 0;
+}
+
+
+#define WM8961_RATES SNDRV_PCM_RATE_8000_48000
+
+#define WM8961_FORMATS \
+	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8961_dai_ops = {
+	.hw_params = wm8961_hw_params,
+	.set_sysclk = wm8961_set_sysclk,
+	.set_fmt = wm8961_set_fmt,
+	.digital_mute = wm8961_digital_mute,
+	.set_tristate = wm8961_set_tristate,
+	.set_clkdiv = wm8961_set_clkdiv,
+};
+
+static struct snd_soc_dai_driver wm8961_dai = {
+	.name = "wm8961-hifi",
+	.playback = {
+		.stream_name = "HiFi Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8961_RATES,
+		.formats = WM8961_FORMATS,},
+	.capture = {
+		.stream_name = "HiFi Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8961_RATES,
+		.formats = WM8961_FORMATS,},
+	.ops = &wm8961_dai_ops,
+};
+
+static int wm8961_probe(struct snd_soc_codec *codec)
+{
+	int ret = 0;
+	u16 reg;
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	reg = snd_soc_read(codec, WM8961_SOFTWARE_RESET);
+	if (reg != 0x1801) {
+		dev_err(codec->dev, "Device is not a WM8961: ID=0x%x\n", reg);
+		return -EINVAL;
+	}
+
+	/* This isn't volatile - readback doesn't correspond to write */
+	reg = codec->hw_read(codec, WM8961_RIGHT_INPUT_VOLUME);
+	dev_info(codec->dev, "WM8961 family %d revision %c\n",
+		 (reg & WM8961_DEVICE_ID_MASK) >> WM8961_DEVICE_ID_SHIFT,
+		 ((reg & WM8961_CHIP_REV_MASK) >> WM8961_CHIP_REV_SHIFT)
+		 + 'A');
+
+	ret = wm8961_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		return ret;
+	}
+
+	/* Enable class W */
+	reg = snd_soc_read(codec, WM8961_CHARGE_PUMP_B);
+	reg |= WM8961_CP_DYN_PWR_MASK;
+	snd_soc_write(codec, WM8961_CHARGE_PUMP_B, reg);
+
+	/* Latch volume update bits (right channel only, we always
+	 * write both out) and default ZC on. */
+	reg = snd_soc_read(codec, WM8961_ROUT1_VOLUME);
+	snd_soc_write(codec, WM8961_ROUT1_VOLUME,
+		     reg | WM8961_LO1ZC | WM8961_OUT1VU);
+	snd_soc_write(codec, WM8961_LOUT1_VOLUME, reg | WM8961_LO1ZC);
+	reg = snd_soc_read(codec, WM8961_ROUT2_VOLUME);
+	snd_soc_write(codec, WM8961_ROUT2_VOLUME,
+		     reg | WM8961_SPKRZC | WM8961_SPKVU);
+	snd_soc_write(codec, WM8961_LOUT2_VOLUME, reg | WM8961_SPKLZC);
+
+	reg = snd_soc_read(codec, WM8961_RIGHT_ADC_VOLUME);
+	snd_soc_write(codec, WM8961_RIGHT_ADC_VOLUME, reg | WM8961_ADCVU);
+	reg = snd_soc_read(codec, WM8961_RIGHT_INPUT_VOLUME);
+	snd_soc_write(codec, WM8961_RIGHT_INPUT_VOLUME, reg | WM8961_IPVU);
+
+	/* Use soft mute by default */
+	reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_2);
+	reg |= WM8961_DACSMM;
+	snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_2, reg);
+
+	/* Use automatic clocking mode by default; for now this is all
+	 * we support.
+	 */
+	reg = snd_soc_read(codec, WM8961_CLOCKING_3);
+	reg &= ~WM8961_MANUAL_MODE;
+	snd_soc_write(codec, WM8961_CLOCKING_3, reg);
+
+	wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	snd_soc_add_controls(codec, wm8961_snd_controls,
+				ARRAY_SIZE(wm8961_snd_controls));
+	snd_soc_dapm_new_controls(codec, wm8961_dapm_widgets,
+				  ARRAY_SIZE(wm8961_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
+
+	return 0;
+}
+
+static int wm8961_remove(struct snd_soc_codec *codec)
+{
+	wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm8961_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm8961_resume(struct snd_soc_codec *codec)
+{
+	u16 *reg_cache = codec->reg_cache;
+	int i;
+
+	for (i = 0; i < codec->driver->reg_cache_size; i++) {
+		if (reg_cache[i] == wm8961_reg_defaults[i])
+			continue;
+
+		if (i == WM8961_SOFTWARE_RESET)
+			continue;
+
+		snd_soc_write(codec, i, reg_cache[i]);
+	}
+
+	wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+#else
+#define wm8961_suspend NULL
+#define wm8961_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8961 = {
+	.probe =	wm8961_probe,
+	.remove =	wm8961_remove,
+	.suspend =	wm8961_suspend,
+	.resume =	wm8961_resume,
+	.set_bias_level = wm8961_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8961_reg_defaults),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8961_reg_defaults,
+	.volatile_register = wm8961_volatile_register,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8961_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8961_priv *wm8961;
+	int ret;
+
+	wm8961 = kzalloc(sizeof(struct wm8961_priv), GFP_KERNEL);
+	if (wm8961 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8961);
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8961, &wm8961_dai, 1);
+	if (ret < 0)
+		kfree(wm8961);
+	return ret;
+}
+
+static __devexit int wm8961_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8961_i2c_id[] = {
+	{ "wm8961", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8961_i2c_id);
+
+static struct i2c_driver wm8961_i2c_driver = {
+	.driver = {
+		.name = "wm8961-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8961_i2c_probe,
+	.remove =   __devexit_p(wm8961_i2c_remove),
+	.id_table = wm8961_i2c_id,
+};
+#endif
+
+static int __init wm8961_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8961_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8961 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8961_modinit);
+
+static void __exit wm8961_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8961_i2c_driver);
+#endif
+}
+module_exit(wm8961_exit);
+
+MODULE_DESCRIPTION("ASoC WM8961 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8961.h b/linux/sound/soc/codecs/wm8961.h
new file mode 100644
index 0000000..1d736e5
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8961.h
@@ -0,0 +1,863 @@
+/*
+ * wm8961.h  --  WM8961 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8961_H
+#define _WM8961_H
+
+#include <sound/soc.h>
+
+#define WM8961_BCLK  1
+#define WM8961_LRCLK 2
+
+#define WM8961_BCLK_DIV_1    0
+#define WM8961_BCLK_DIV_1_5  1
+#define WM8961_BCLK_DIV_2    2
+#define WM8961_BCLK_DIV_3    3
+#define WM8961_BCLK_DIV_4    4
+#define WM8961_BCLK_DIV_5_5  5
+#define WM8961_BCLK_DIV_6    6
+#define WM8961_BCLK_DIV_8    7
+#define WM8961_BCLK_DIV_11   8
+#define WM8961_BCLK_DIV_12   9
+#define WM8961_BCLK_DIV_16  10
+#define WM8961_BCLK_DIV_24  11
+#define WM8961_BCLK_DIV_32  13
+
+
+/*
+ * Register values.
+ */
+#define WM8961_LEFT_INPUT_VOLUME                0x00
+#define WM8961_RIGHT_INPUT_VOLUME               0x01
+#define WM8961_LOUT1_VOLUME                     0x02
+#define WM8961_ROUT1_VOLUME                     0x03
+#define WM8961_CLOCKING1                        0x04
+#define WM8961_ADC_DAC_CONTROL_1                0x05
+#define WM8961_ADC_DAC_CONTROL_2                0x06
+#define WM8961_AUDIO_INTERFACE_0                0x07
+#define WM8961_CLOCKING2                        0x08
+#define WM8961_AUDIO_INTERFACE_1                0x09
+#define WM8961_LEFT_DAC_VOLUME                  0x0A
+#define WM8961_RIGHT_DAC_VOLUME                 0x0B
+#define WM8961_AUDIO_INTERFACE_2                0x0E
+#define WM8961_SOFTWARE_RESET                   0x0F
+#define WM8961_ALC1                             0x11
+#define WM8961_ALC2                             0x12
+#define WM8961_ALC3                             0x13
+#define WM8961_NOISE_GATE                       0x14
+#define WM8961_LEFT_ADC_VOLUME                  0x15
+#define WM8961_RIGHT_ADC_VOLUME                 0x16
+#define WM8961_ADDITIONAL_CONTROL_1             0x17
+#define WM8961_ADDITIONAL_CONTROL_2             0x18
+#define WM8961_PWR_MGMT_1                       0x19
+#define WM8961_PWR_MGMT_2                       0x1A
+#define WM8961_ADDITIONAL_CONTROL_3             0x1B
+#define WM8961_ANTI_POP                         0x1C
+#define WM8961_CLOCKING_3                       0x1E
+#define WM8961_ADCL_SIGNAL_PATH                 0x20
+#define WM8961_ADCR_SIGNAL_PATH                 0x21
+#define WM8961_LOUT2_VOLUME                     0x28
+#define WM8961_ROUT2_VOLUME                     0x29
+#define WM8961_PWR_MGMT_3                       0x2F
+#define WM8961_ADDITIONAL_CONTROL_4             0x30
+#define WM8961_CLASS_D_CONTROL_1                0x31
+#define WM8961_CLASS_D_CONTROL_2                0x33
+#define WM8961_CLOCKING_4                       0x38
+#define WM8961_DSP_SIDETONE_0                   0x39
+#define WM8961_DSP_SIDETONE_1                   0x3A
+#define WM8961_DC_SERVO_0                       0x3C
+#define WM8961_DC_SERVO_1                       0x3D
+#define WM8961_DC_SERVO_3                       0x3F
+#define WM8961_DC_SERVO_5                       0x41
+#define WM8961_ANALOGUE_PGA_BIAS                0x44
+#define WM8961_ANALOGUE_HP_0                    0x45
+#define WM8961_ANALOGUE_HP_2                    0x47
+#define WM8961_CHARGE_PUMP_1                    0x48
+#define WM8961_CHARGE_PUMP_B                    0x52
+#define WM8961_WRITE_SEQUENCER_1                0x57
+#define WM8961_WRITE_SEQUENCER_2                0x58
+#define WM8961_WRITE_SEQUENCER_3                0x59
+#define WM8961_WRITE_SEQUENCER_4                0x5A
+#define WM8961_WRITE_SEQUENCER_5                0x5B
+#define WM8961_WRITE_SEQUENCER_6                0x5C
+#define WM8961_WRITE_SEQUENCER_7                0x5D
+#define WM8961_GENERAL_TEST_1                   0xFC
+
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Left Input volume
+ */
+#define WM8961_IPVU                             0x0100  /* IPVU */
+#define WM8961_IPVU_MASK                        0x0100  /* IPVU */
+#define WM8961_IPVU_SHIFT                            8  /* IPVU */
+#define WM8961_IPVU_WIDTH                            1  /* IPVU */
+#define WM8961_LINMUTE                          0x0080  /* LINMUTE */
+#define WM8961_LINMUTE_MASK                     0x0080  /* LINMUTE */
+#define WM8961_LINMUTE_SHIFT                         7  /* LINMUTE */
+#define WM8961_LINMUTE_WIDTH                         1  /* LINMUTE */
+#define WM8961_LIZC                             0x0040  /* LIZC */
+#define WM8961_LIZC_MASK                        0x0040  /* LIZC */
+#define WM8961_LIZC_SHIFT                            6  /* LIZC */
+#define WM8961_LIZC_WIDTH                            1  /* LIZC */
+#define WM8961_LINVOL_MASK                      0x003F  /* LINVOL - [5:0] */
+#define WM8961_LINVOL_SHIFT                          0  /* LINVOL - [5:0] */
+#define WM8961_LINVOL_WIDTH                          6  /* LINVOL - [5:0] */
+
+/*
+ * R1 (0x01) - Right Input volume
+ */
+#define WM8961_DEVICE_ID_MASK                   0xF000  /* DEVICE_ID - [15:12] */
+#define WM8961_DEVICE_ID_SHIFT                      12  /* DEVICE_ID - [15:12] */
+#define WM8961_DEVICE_ID_WIDTH                       4  /* DEVICE_ID - [15:12] */
+#define WM8961_CHIP_REV_MASK                    0x0E00  /* CHIP_REV - [11:9] */
+#define WM8961_CHIP_REV_SHIFT                        9  /* CHIP_REV - [11:9] */
+#define WM8961_CHIP_REV_WIDTH                        3  /* CHIP_REV - [11:9] */
+#define WM8961_IPVU                             0x0100  /* IPVU */
+#define WM8961_IPVU_MASK                        0x0100  /* IPVU */
+#define WM8961_IPVU_SHIFT                            8  /* IPVU */
+#define WM8961_IPVU_WIDTH                            1  /* IPVU */
+#define WM8961_RINMUTE                          0x0080  /* RINMUTE */
+#define WM8961_RINMUTE_MASK                     0x0080  /* RINMUTE */
+#define WM8961_RINMUTE_SHIFT                         7  /* RINMUTE */
+#define WM8961_RINMUTE_WIDTH                         1  /* RINMUTE */
+#define WM8961_RIZC                             0x0040  /* RIZC */
+#define WM8961_RIZC_MASK                        0x0040  /* RIZC */
+#define WM8961_RIZC_SHIFT                            6  /* RIZC */
+#define WM8961_RIZC_WIDTH                            1  /* RIZC */
+#define WM8961_RINVOL_MASK                      0x003F  /* RINVOL - [5:0] */
+#define WM8961_RINVOL_SHIFT                          0  /* RINVOL - [5:0] */
+#define WM8961_RINVOL_WIDTH                          6  /* RINVOL - [5:0] */
+
+/*
+ * R2 (0x02) - LOUT1 volume
+ */
+#define WM8961_OUT1VU                           0x0100  /* OUT1VU */
+#define WM8961_OUT1VU_MASK                      0x0100  /* OUT1VU */
+#define WM8961_OUT1VU_SHIFT                          8  /* OUT1VU */
+#define WM8961_OUT1VU_WIDTH                          1  /* OUT1VU */
+#define WM8961_LO1ZC                            0x0080  /* LO1ZC */
+#define WM8961_LO1ZC_MASK                       0x0080  /* LO1ZC */
+#define WM8961_LO1ZC_SHIFT                           7  /* LO1ZC */
+#define WM8961_LO1ZC_WIDTH                           1  /* LO1ZC */
+#define WM8961_LOUT1VOL_MASK                    0x007F  /* LOUT1VOL - [6:0] */
+#define WM8961_LOUT1VOL_SHIFT                        0  /* LOUT1VOL - [6:0] */
+#define WM8961_LOUT1VOL_WIDTH                        7  /* LOUT1VOL - [6:0] */
+
+/*
+ * R3 (0x03) - ROUT1 volume
+ */
+#define WM8961_OUT1VU                           0x0100  /* OUT1VU */
+#define WM8961_OUT1VU_MASK                      0x0100  /* OUT1VU */
+#define WM8961_OUT1VU_SHIFT                          8  /* OUT1VU */
+#define WM8961_OUT1VU_WIDTH                          1  /* OUT1VU */
+#define WM8961_RO1ZC                            0x0080  /* RO1ZC */
+#define WM8961_RO1ZC_MASK                       0x0080  /* RO1ZC */
+#define WM8961_RO1ZC_SHIFT                           7  /* RO1ZC */
+#define WM8961_RO1ZC_WIDTH                           1  /* RO1ZC */
+#define WM8961_ROUT1VOL_MASK                    0x007F  /* ROUT1VOL - [6:0] */
+#define WM8961_ROUT1VOL_SHIFT                        0  /* ROUT1VOL - [6:0] */
+#define WM8961_ROUT1VOL_WIDTH                        7  /* ROUT1VOL - [6:0] */
+
+/*
+ * R4 (0x04) - Clocking1
+ */
+#define WM8961_ADCDIV_MASK                      0x01C0  /* ADCDIV - [8:6] */
+#define WM8961_ADCDIV_SHIFT                          6  /* ADCDIV - [8:6] */
+#define WM8961_ADCDIV_WIDTH                          3  /* ADCDIV - [8:6] */
+#define WM8961_DACDIV_MASK                      0x0038  /* DACDIV - [5:3] */
+#define WM8961_DACDIV_SHIFT                          3  /* DACDIV - [5:3] */
+#define WM8961_DACDIV_WIDTH                          3  /* DACDIV - [5:3] */
+#define WM8961_MCLKDIV                          0x0004  /* MCLKDIV */
+#define WM8961_MCLKDIV_MASK                     0x0004  /* MCLKDIV */
+#define WM8961_MCLKDIV_SHIFT                         2  /* MCLKDIV */
+#define WM8961_MCLKDIV_WIDTH                         1  /* MCLKDIV */
+
+/*
+ * R5 (0x05) - ADC & DAC Control 1
+ */
+#define WM8961_ADCPOL_MASK                      0x0060  /* ADCPOL - [6:5] */
+#define WM8961_ADCPOL_SHIFT                          5  /* ADCPOL - [6:5] */
+#define WM8961_ADCPOL_WIDTH                          2  /* ADCPOL - [6:5] */
+#define WM8961_DACMU                            0x0008  /* DACMU */
+#define WM8961_DACMU_MASK                       0x0008  /* DACMU */
+#define WM8961_DACMU_SHIFT                           3  /* DACMU */
+#define WM8961_DACMU_WIDTH                           1  /* DACMU */
+#define WM8961_DEEMPH_MASK                      0x0006  /* DEEMPH - [2:1] */
+#define WM8961_DEEMPH_SHIFT                          1  /* DEEMPH - [2:1] */
+#define WM8961_DEEMPH_WIDTH                          2  /* DEEMPH - [2:1] */
+#define WM8961_ADCHPD                           0x0001  /* ADCHPD */
+#define WM8961_ADCHPD_MASK                      0x0001  /* ADCHPD */
+#define WM8961_ADCHPD_SHIFT                          0  /* ADCHPD */
+#define WM8961_ADCHPD_WIDTH                          1  /* ADCHPD */
+
+/*
+ * R6 (0x06) - ADC & DAC Control 2
+ */
+#define WM8961_ADC_HPF_CUT_MASK                 0x0180  /* ADC_HPF_CUT - [8:7] */
+#define WM8961_ADC_HPF_CUT_SHIFT                     7  /* ADC_HPF_CUT - [8:7] */
+#define WM8961_ADC_HPF_CUT_WIDTH                     2  /* ADC_HPF_CUT - [8:7] */
+#define WM8961_DACPOL_MASK                      0x0060  /* DACPOL - [6:5] */
+#define WM8961_DACPOL_SHIFT                          5  /* DACPOL - [6:5] */
+#define WM8961_DACPOL_WIDTH                          2  /* DACPOL - [6:5] */
+#define WM8961_DACSMM                           0x0008  /* DACSMM */
+#define WM8961_DACSMM_MASK                      0x0008  /* DACSMM */
+#define WM8961_DACSMM_SHIFT                          3  /* DACSMM */
+#define WM8961_DACSMM_WIDTH                          1  /* DACSMM */
+#define WM8961_DACMR                            0x0004  /* DACMR */
+#define WM8961_DACMR_MASK                       0x0004  /* DACMR */
+#define WM8961_DACMR_SHIFT                           2  /* DACMR */
+#define WM8961_DACMR_WIDTH                           1  /* DACMR */
+#define WM8961_DACSLOPE                         0x0002  /* DACSLOPE */
+#define WM8961_DACSLOPE_MASK                    0x0002  /* DACSLOPE */
+#define WM8961_DACSLOPE_SHIFT                        1  /* DACSLOPE */
+#define WM8961_DACSLOPE_WIDTH                        1  /* DACSLOPE */
+#define WM8961_DAC_OSR128                       0x0001  /* DAC_OSR128 */
+#define WM8961_DAC_OSR128_MASK                  0x0001  /* DAC_OSR128 */
+#define WM8961_DAC_OSR128_SHIFT                      0  /* DAC_OSR128 */
+#define WM8961_DAC_OSR128_WIDTH                      1  /* DAC_OSR128 */
+
+/*
+ * R7 (0x07) - Audio Interface 0
+ */
+#define WM8961_ALRSWAP                          0x0100  /* ALRSWAP */
+#define WM8961_ALRSWAP_MASK                     0x0100  /* ALRSWAP */
+#define WM8961_ALRSWAP_SHIFT                         8  /* ALRSWAP */
+#define WM8961_ALRSWAP_WIDTH                         1  /* ALRSWAP */
+#define WM8961_BCLKINV                          0x0080  /* BCLKINV */
+#define WM8961_BCLKINV_MASK                     0x0080  /* BCLKINV */
+#define WM8961_BCLKINV_SHIFT                         7  /* BCLKINV */
+#define WM8961_BCLKINV_WIDTH                         1  /* BCLKINV */
+#define WM8961_MS                               0x0040  /* MS */
+#define WM8961_MS_MASK                          0x0040  /* MS */
+#define WM8961_MS_SHIFT                              6  /* MS */
+#define WM8961_MS_WIDTH                              1  /* MS */
+#define WM8961_DLRSWAP                          0x0020  /* DLRSWAP */
+#define WM8961_DLRSWAP_MASK                     0x0020  /* DLRSWAP */
+#define WM8961_DLRSWAP_SHIFT                         5  /* DLRSWAP */
+#define WM8961_DLRSWAP_WIDTH                         1  /* DLRSWAP */
+#define WM8961_LRP                              0x0010  /* LRP */
+#define WM8961_LRP_MASK                         0x0010  /* LRP */
+#define WM8961_LRP_SHIFT                             4  /* LRP */
+#define WM8961_LRP_WIDTH                             1  /* LRP */
+#define WM8961_WL_MASK                          0x000C  /* WL - [3:2] */
+#define WM8961_WL_SHIFT                              2  /* WL - [3:2] */
+#define WM8961_WL_WIDTH                              2  /* WL - [3:2] */
+#define WM8961_FORMAT_MASK                      0x0003  /* FORMAT - [1:0] */
+#define WM8961_FORMAT_SHIFT                          0  /* FORMAT - [1:0] */
+#define WM8961_FORMAT_WIDTH                          2  /* FORMAT - [1:0] */
+
+/*
+ * R8 (0x08) - Clocking2
+ */
+#define WM8961_DCLKDIV_MASK                     0x01C0  /* DCLKDIV - [8:6] */
+#define WM8961_DCLKDIV_SHIFT                         6  /* DCLKDIV - [8:6] */
+#define WM8961_DCLKDIV_WIDTH                         3  /* DCLKDIV - [8:6] */
+#define WM8961_CLK_SYS_ENA                      0x0020  /* CLK_SYS_ENA */
+#define WM8961_CLK_SYS_ENA_MASK                 0x0020  /* CLK_SYS_ENA */
+#define WM8961_CLK_SYS_ENA_SHIFT                     5  /* CLK_SYS_ENA */
+#define WM8961_CLK_SYS_ENA_WIDTH                     1  /* CLK_SYS_ENA */
+#define WM8961_CLK_DSP_ENA                      0x0010  /* CLK_DSP_ENA */
+#define WM8961_CLK_DSP_ENA_MASK                 0x0010  /* CLK_DSP_ENA */
+#define WM8961_CLK_DSP_ENA_SHIFT                     4  /* CLK_DSP_ENA */
+#define WM8961_CLK_DSP_ENA_WIDTH                     1  /* CLK_DSP_ENA */
+#define WM8961_BCLKDIV_MASK                     0x000F  /* BCLKDIV - [3:0] */
+#define WM8961_BCLKDIV_SHIFT                         0  /* BCLKDIV - [3:0] */
+#define WM8961_BCLKDIV_WIDTH                         4  /* BCLKDIV - [3:0] */
+
+/*
+ * R9 (0x09) - Audio Interface 1
+ */
+#define WM8961_DACCOMP_MASK                     0x0018  /* DACCOMP - [4:3] */
+#define WM8961_DACCOMP_SHIFT                         3  /* DACCOMP - [4:3] */
+#define WM8961_DACCOMP_WIDTH                         2  /* DACCOMP - [4:3] */
+#define WM8961_ADCCOMP_MASK                     0x0006  /* ADCCOMP - [2:1] */
+#define WM8961_ADCCOMP_SHIFT                         1  /* ADCCOMP - [2:1] */
+#define WM8961_ADCCOMP_WIDTH                         2  /* ADCCOMP - [2:1] */
+#define WM8961_LOOPBACK                         0x0001  /* LOOPBACK */
+#define WM8961_LOOPBACK_MASK                    0x0001  /* LOOPBACK */
+#define WM8961_LOOPBACK_SHIFT                        0  /* LOOPBACK */
+#define WM8961_LOOPBACK_WIDTH                        1  /* LOOPBACK */
+
+/*
+ * R10 (0x0A) - Left DAC volume
+ */
+#define WM8961_DACVU                            0x0100  /* DACVU */
+#define WM8961_DACVU_MASK                       0x0100  /* DACVU */
+#define WM8961_DACVU_SHIFT                           8  /* DACVU */
+#define WM8961_DACVU_WIDTH                           1  /* DACVU */
+#define WM8961_LDACVOL_MASK                     0x00FF  /* LDACVOL - [7:0] */
+#define WM8961_LDACVOL_SHIFT                         0  /* LDACVOL - [7:0] */
+#define WM8961_LDACVOL_WIDTH                         8  /* LDACVOL - [7:0] */
+
+/*
+ * R11 (0x0B) - Right DAC volume
+ */
+#define WM8961_DACVU                            0x0100  /* DACVU */
+#define WM8961_DACVU_MASK                       0x0100  /* DACVU */
+#define WM8961_DACVU_SHIFT                           8  /* DACVU */
+#define WM8961_DACVU_WIDTH                           1  /* DACVU */
+#define WM8961_RDACVOL_MASK                     0x00FF  /* RDACVOL - [7:0] */
+#define WM8961_RDACVOL_SHIFT                         0  /* RDACVOL - [7:0] */
+#define WM8961_RDACVOL_WIDTH                         8  /* RDACVOL - [7:0] */
+
+/*
+ * R14 (0x0E) - Audio Interface 2
+ */
+#define WM8961_LRCLK_RATE_MASK                  0x01FF  /* LRCLK_RATE - [8:0] */
+#define WM8961_LRCLK_RATE_SHIFT                      0  /* LRCLK_RATE - [8:0] */
+#define WM8961_LRCLK_RATE_WIDTH                      9  /* LRCLK_RATE - [8:0] */
+
+/*
+ * R15 (0x0F) - Software Reset
+ */
+#define WM8961_SW_RST_DEV_ID1_MASK              0xFFFF  /* SW_RST_DEV_ID1 - [15:0] */
+#define WM8961_SW_RST_DEV_ID1_SHIFT                  0  /* SW_RST_DEV_ID1 - [15:0] */
+#define WM8961_SW_RST_DEV_ID1_WIDTH                 16  /* SW_RST_DEV_ID1 - [15:0] */
+
+/*
+ * R17 (0x11) - ALC1
+ */
+#define WM8961_ALCSEL_MASK                      0x0180  /* ALCSEL - [8:7] */
+#define WM8961_ALCSEL_SHIFT                          7  /* ALCSEL - [8:7] */
+#define WM8961_ALCSEL_WIDTH                          2  /* ALCSEL - [8:7] */
+#define WM8961_MAXGAIN_MASK                     0x0070  /* MAXGAIN - [6:4] */
+#define WM8961_MAXGAIN_SHIFT                         4  /* MAXGAIN - [6:4] */
+#define WM8961_MAXGAIN_WIDTH                         3  /* MAXGAIN - [6:4] */
+#define WM8961_ALCL_MASK                        0x000F  /* ALCL - [3:0] */
+#define WM8961_ALCL_SHIFT                            0  /* ALCL - [3:0] */
+#define WM8961_ALCL_WIDTH                            4  /* ALCL - [3:0] */
+
+/*
+ * R18 (0x12) - ALC2
+ */
+#define WM8961_ALCZC                            0x0080  /* ALCZC */
+#define WM8961_ALCZC_MASK                       0x0080  /* ALCZC */
+#define WM8961_ALCZC_SHIFT                           7  /* ALCZC */
+#define WM8961_ALCZC_WIDTH                           1  /* ALCZC */
+#define WM8961_MINGAIN_MASK                     0x0070  /* MINGAIN - [6:4] */
+#define WM8961_MINGAIN_SHIFT                         4  /* MINGAIN - [6:4] */
+#define WM8961_MINGAIN_WIDTH                         3  /* MINGAIN - [6:4] */
+#define WM8961_HLD_MASK                         0x000F  /* HLD - [3:0] */
+#define WM8961_HLD_SHIFT                             0  /* HLD - [3:0] */
+#define WM8961_HLD_WIDTH                             4  /* HLD - [3:0] */
+
+/*
+ * R19 (0x13) - ALC3
+ */
+#define WM8961_ALCMODE                          0x0100  /* ALCMODE */
+#define WM8961_ALCMODE_MASK                     0x0100  /* ALCMODE */
+#define WM8961_ALCMODE_SHIFT                         8  /* ALCMODE */
+#define WM8961_ALCMODE_WIDTH                         1  /* ALCMODE */
+#define WM8961_DCY_MASK                         0x00F0  /* DCY - [7:4] */
+#define WM8961_DCY_SHIFT                             4  /* DCY - [7:4] */
+#define WM8961_DCY_WIDTH                             4  /* DCY - [7:4] */
+#define WM8961_ATK_MASK                         0x000F  /* ATK - [3:0] */
+#define WM8961_ATK_SHIFT                             0  /* ATK - [3:0] */
+#define WM8961_ATK_WIDTH                             4  /* ATK - [3:0] */
+
+/*
+ * R20 (0x14) - Noise Gate
+ */
+#define WM8961_NGTH_MASK                        0x00F8  /* NGTH - [7:3] */
+#define WM8961_NGTH_SHIFT                            3  /* NGTH - [7:3] */
+#define WM8961_NGTH_WIDTH                            5  /* NGTH - [7:3] */
+#define WM8961_NGG                              0x0002  /* NGG */
+#define WM8961_NGG_MASK                         0x0002  /* NGG */
+#define WM8961_NGG_SHIFT                             1  /* NGG */
+#define WM8961_NGG_WIDTH                             1  /* NGG */
+#define WM8961_NGAT                             0x0001  /* NGAT */
+#define WM8961_NGAT_MASK                        0x0001  /* NGAT */
+#define WM8961_NGAT_SHIFT                            0  /* NGAT */
+#define WM8961_NGAT_WIDTH                            1  /* NGAT */
+
+/*
+ * R21 (0x15) - Left ADC volume
+ */
+#define WM8961_ADCVU                            0x0100  /* ADCVU */
+#define WM8961_ADCVU_MASK                       0x0100  /* ADCVU */
+#define WM8961_ADCVU_SHIFT                           8  /* ADCVU */
+#define WM8961_ADCVU_WIDTH                           1  /* ADCVU */
+#define WM8961_LADCVOL_MASK                     0x00FF  /* LADCVOL - [7:0] */
+#define WM8961_LADCVOL_SHIFT                         0  /* LADCVOL - [7:0] */
+#define WM8961_LADCVOL_WIDTH                         8  /* LADCVOL - [7:0] */
+
+/*
+ * R22 (0x16) - Right ADC volume
+ */
+#define WM8961_ADCVU                            0x0100  /* ADCVU */
+#define WM8961_ADCVU_MASK                       0x0100  /* ADCVU */
+#define WM8961_ADCVU_SHIFT                           8  /* ADCVU */
+#define WM8961_ADCVU_WIDTH                           1  /* ADCVU */
+#define WM8961_RADCVOL_MASK                     0x00FF  /* RADCVOL - [7:0] */
+#define WM8961_RADCVOL_SHIFT                         0  /* RADCVOL - [7:0] */
+#define WM8961_RADCVOL_WIDTH                         8  /* RADCVOL - [7:0] */
+
+/*
+ * R23 (0x17) - Additional control(1)
+ */
+#define WM8961_TSDEN                            0x0100  /* TSDEN */
+#define WM8961_TSDEN_MASK                       0x0100  /* TSDEN */
+#define WM8961_TSDEN_SHIFT                           8  /* TSDEN */
+#define WM8961_TSDEN_WIDTH                           1  /* TSDEN */
+#define WM8961_DMONOMIX                         0x0010  /* DMONOMIX */
+#define WM8961_DMONOMIX_MASK                    0x0010  /* DMONOMIX */
+#define WM8961_DMONOMIX_SHIFT                        4  /* DMONOMIX */
+#define WM8961_DMONOMIX_WIDTH                        1  /* DMONOMIX */
+#define WM8961_TOEN                             0x0001  /* TOEN */
+#define WM8961_TOEN_MASK                        0x0001  /* TOEN */
+#define WM8961_TOEN_SHIFT                            0  /* TOEN */
+#define WM8961_TOEN_WIDTH                            1  /* TOEN */
+
+/*
+ * R24 (0x18) - Additional control(2)
+ */
+#define WM8961_TRIS                             0x0008  /* TRIS */
+#define WM8961_TRIS_MASK                        0x0008  /* TRIS */
+#define WM8961_TRIS_SHIFT                            3  /* TRIS */
+#define WM8961_TRIS_WIDTH                            1  /* TRIS */
+
+/*
+ * R25 (0x19) - Pwr Mgmt (1)
+ */
+#define WM8961_VMIDSEL_MASK                     0x0180  /* VMIDSEL - [8:7] */
+#define WM8961_VMIDSEL_SHIFT                         7  /* VMIDSEL - [8:7] */
+#define WM8961_VMIDSEL_WIDTH                         2  /* VMIDSEL - [8:7] */
+#define WM8961_VREF                             0x0040  /* VREF */
+#define WM8961_VREF_MASK                        0x0040  /* VREF */
+#define WM8961_VREF_SHIFT                            6  /* VREF */
+#define WM8961_VREF_WIDTH                            1  /* VREF */
+#define WM8961_AINL                             0x0020  /* AINL */
+#define WM8961_AINL_MASK                        0x0020  /* AINL */
+#define WM8961_AINL_SHIFT                            5  /* AINL */
+#define WM8961_AINL_WIDTH                            1  /* AINL */
+#define WM8961_AINR                             0x0010  /* AINR */
+#define WM8961_AINR_MASK                        0x0010  /* AINR */
+#define WM8961_AINR_SHIFT                            4  /* AINR */
+#define WM8961_AINR_WIDTH                            1  /* AINR */
+#define WM8961_ADCL                             0x0008  /* ADCL */
+#define WM8961_ADCL_MASK                        0x0008  /* ADCL */
+#define WM8961_ADCL_SHIFT                            3  /* ADCL */
+#define WM8961_ADCL_WIDTH                            1  /* ADCL */
+#define WM8961_ADCR                             0x0004  /* ADCR */
+#define WM8961_ADCR_MASK                        0x0004  /* ADCR */
+#define WM8961_ADCR_SHIFT                            2  /* ADCR */
+#define WM8961_ADCR_WIDTH                            1  /* ADCR */
+#define WM8961_MICB                             0x0002  /* MICB */
+#define WM8961_MICB_MASK                        0x0002  /* MICB */
+#define WM8961_MICB_SHIFT                            1  /* MICB */
+#define WM8961_MICB_WIDTH                            1  /* MICB */
+
+/*
+ * R26 (0x1A) - Pwr Mgmt (2)
+ */
+#define WM8961_DACL                             0x0100  /* DACL */
+#define WM8961_DACL_MASK                        0x0100  /* DACL */
+#define WM8961_DACL_SHIFT                            8  /* DACL */
+#define WM8961_DACL_WIDTH                            1  /* DACL */
+#define WM8961_DACR                             0x0080  /* DACR */
+#define WM8961_DACR_MASK                        0x0080  /* DACR */
+#define WM8961_DACR_SHIFT                            7  /* DACR */
+#define WM8961_DACR_WIDTH                            1  /* DACR */
+#define WM8961_LOUT1_PGA                        0x0040  /* LOUT1_PGA */
+#define WM8961_LOUT1_PGA_MASK                   0x0040  /* LOUT1_PGA */
+#define WM8961_LOUT1_PGA_SHIFT                       6  /* LOUT1_PGA */
+#define WM8961_LOUT1_PGA_WIDTH                       1  /* LOUT1_PGA */
+#define WM8961_ROUT1_PGA                        0x0020  /* ROUT1_PGA */
+#define WM8961_ROUT1_PGA_MASK                   0x0020  /* ROUT1_PGA */
+#define WM8961_ROUT1_PGA_SHIFT                       5  /* ROUT1_PGA */
+#define WM8961_ROUT1_PGA_WIDTH                       1  /* ROUT1_PGA */
+#define WM8961_SPKL_PGA                         0x0010  /* SPKL_PGA */
+#define WM8961_SPKL_PGA_MASK                    0x0010  /* SPKL_PGA */
+#define WM8961_SPKL_PGA_SHIFT                        4  /* SPKL_PGA */
+#define WM8961_SPKL_PGA_WIDTH                        1  /* SPKL_PGA */
+#define WM8961_SPKR_PGA                         0x0008  /* SPKR_PGA */
+#define WM8961_SPKR_PGA_MASK                    0x0008  /* SPKR_PGA */
+#define WM8961_SPKR_PGA_SHIFT                        3  /* SPKR_PGA */
+#define WM8961_SPKR_PGA_WIDTH                        1  /* SPKR_PGA */
+
+/*
+ * R27 (0x1B) - Additional Control (3)
+ */
+#define WM8961_SAMPLE_RATE_MASK                 0x0007  /* SAMPLE_RATE - [2:0] */
+#define WM8961_SAMPLE_RATE_SHIFT                     0  /* SAMPLE_RATE - [2:0] */
+#define WM8961_SAMPLE_RATE_WIDTH                     3  /* SAMPLE_RATE - [2:0] */
+
+/*
+ * R28 (0x1C) - Anti-pop
+ */
+#define WM8961_BUFDCOPEN                        0x0010  /* BUFDCOPEN */
+#define WM8961_BUFDCOPEN_MASK                   0x0010  /* BUFDCOPEN */
+#define WM8961_BUFDCOPEN_SHIFT                       4  /* BUFDCOPEN */
+#define WM8961_BUFDCOPEN_WIDTH                       1  /* BUFDCOPEN */
+#define WM8961_BUFIOEN                          0x0008  /* BUFIOEN */
+#define WM8961_BUFIOEN_MASK                     0x0008  /* BUFIOEN */
+#define WM8961_BUFIOEN_SHIFT                         3  /* BUFIOEN */
+#define WM8961_BUFIOEN_WIDTH                         1  /* BUFIOEN */
+#define WM8961_SOFT_ST                          0x0004  /* SOFT_ST */
+#define WM8961_SOFT_ST_MASK                     0x0004  /* SOFT_ST */
+#define WM8961_SOFT_ST_SHIFT                         2  /* SOFT_ST */
+#define WM8961_SOFT_ST_WIDTH                         1  /* SOFT_ST */
+
+/*
+ * R30 (0x1E) - Clocking 3
+ */
+#define WM8961_CLK_TO_DIV_MASK                  0x0180  /* CLK_TO_DIV - [8:7] */
+#define WM8961_CLK_TO_DIV_SHIFT                      7  /* CLK_TO_DIV - [8:7] */
+#define WM8961_CLK_TO_DIV_WIDTH                      2  /* CLK_TO_DIV - [8:7] */
+#define WM8961_CLK_256K_DIV_MASK                0x007E  /* CLK_256K_DIV - [6:1] */
+#define WM8961_CLK_256K_DIV_SHIFT                    1  /* CLK_256K_DIV - [6:1] */
+#define WM8961_CLK_256K_DIV_WIDTH                    6  /* CLK_256K_DIV - [6:1] */
+#define WM8961_MANUAL_MODE                      0x0001  /* MANUAL_MODE */
+#define WM8961_MANUAL_MODE_MASK                 0x0001  /* MANUAL_MODE */
+#define WM8961_MANUAL_MODE_SHIFT                     0  /* MANUAL_MODE */
+#define WM8961_MANUAL_MODE_WIDTH                     1  /* MANUAL_MODE */
+
+/*
+ * R32 (0x20) - ADCL signal path
+ */
+#define WM8961_LMICBOOST_MASK                   0x0030  /* LMICBOOST - [5:4] */
+#define WM8961_LMICBOOST_SHIFT                       4  /* LMICBOOST - [5:4] */
+#define WM8961_LMICBOOST_WIDTH                       2  /* LMICBOOST - [5:4] */
+
+/*
+ * R33 (0x21) - ADCR signal path
+ */
+#define WM8961_RMICBOOST_MASK                   0x0030  /* RMICBOOST - [5:4] */
+#define WM8961_RMICBOOST_SHIFT                       4  /* RMICBOOST - [5:4] */
+#define WM8961_RMICBOOST_WIDTH                       2  /* RMICBOOST - [5:4] */
+
+/*
+ * R40 (0x28) - LOUT2 volume
+ */
+#define WM8961_SPKVU                            0x0100  /* SPKVU */
+#define WM8961_SPKVU_MASK                       0x0100  /* SPKVU */
+#define WM8961_SPKVU_SHIFT                           8  /* SPKVU */
+#define WM8961_SPKVU_WIDTH                           1  /* SPKVU */
+#define WM8961_SPKLZC                           0x0080  /* SPKLZC */
+#define WM8961_SPKLZC_MASK                      0x0080  /* SPKLZC */
+#define WM8961_SPKLZC_SHIFT                          7  /* SPKLZC */
+#define WM8961_SPKLZC_WIDTH                          1  /* SPKLZC */
+#define WM8961_SPKLVOL_MASK                     0x007F  /* SPKLVOL - [6:0] */
+#define WM8961_SPKLVOL_SHIFT                         0  /* SPKLVOL - [6:0] */
+#define WM8961_SPKLVOL_WIDTH                         7  /* SPKLVOL - [6:0] */
+
+/*
+ * R41 (0x29) - ROUT2 volume
+ */
+#define WM8961_SPKVU                            0x0100  /* SPKVU */
+#define WM8961_SPKVU_MASK                       0x0100  /* SPKVU */
+#define WM8961_SPKVU_SHIFT                           8  /* SPKVU */
+#define WM8961_SPKVU_WIDTH                           1  /* SPKVU */
+#define WM8961_SPKRZC                           0x0080  /* SPKRZC */
+#define WM8961_SPKRZC_MASK                      0x0080  /* SPKRZC */
+#define WM8961_SPKRZC_SHIFT                          7  /* SPKRZC */
+#define WM8961_SPKRZC_WIDTH                          1  /* SPKRZC */
+#define WM8961_SPKRVOL_MASK                     0x007F  /* SPKRVOL - [6:0] */
+#define WM8961_SPKRVOL_SHIFT                         0  /* SPKRVOL - [6:0] */
+#define WM8961_SPKRVOL_WIDTH                         7  /* SPKRVOL - [6:0] */
+
+/*
+ * R47 (0x2F) - Pwr Mgmt (3)
+ */
+#define WM8961_TEMP_SHUT                        0x0002  /* TEMP_SHUT */
+#define WM8961_TEMP_SHUT_MASK                   0x0002  /* TEMP_SHUT */
+#define WM8961_TEMP_SHUT_SHIFT                       1  /* TEMP_SHUT */
+#define WM8961_TEMP_SHUT_WIDTH                       1  /* TEMP_SHUT */
+#define WM8961_TEMP_WARN                        0x0001  /* TEMP_WARN */
+#define WM8961_TEMP_WARN_MASK                   0x0001  /* TEMP_WARN */
+#define WM8961_TEMP_WARN_SHIFT                       0  /* TEMP_WARN */
+#define WM8961_TEMP_WARN_WIDTH                       1  /* TEMP_WARN */
+
+/*
+ * R48 (0x30) - Additional Control (4)
+ */
+#define WM8961_TSENSEN                          0x0002  /* TSENSEN */
+#define WM8961_TSENSEN_MASK                     0x0002  /* TSENSEN */
+#define WM8961_TSENSEN_SHIFT                         1  /* TSENSEN */
+#define WM8961_TSENSEN_WIDTH                         1  /* TSENSEN */
+#define WM8961_MBSEL                            0x0001  /* MBSEL */
+#define WM8961_MBSEL_MASK                       0x0001  /* MBSEL */
+#define WM8961_MBSEL_SHIFT                           0  /* MBSEL */
+#define WM8961_MBSEL_WIDTH                           1  /* MBSEL */
+
+/*
+ * R49 (0x31) - Class D Control 1
+ */
+#define WM8961_SPKR_ENA                         0x0080  /* SPKR_ENA */
+#define WM8961_SPKR_ENA_MASK                    0x0080  /* SPKR_ENA */
+#define WM8961_SPKR_ENA_SHIFT                        7  /* SPKR_ENA */
+#define WM8961_SPKR_ENA_WIDTH                        1  /* SPKR_ENA */
+#define WM8961_SPKL_ENA                         0x0040  /* SPKL_ENA */
+#define WM8961_SPKL_ENA_MASK                    0x0040  /* SPKL_ENA */
+#define WM8961_SPKL_ENA_SHIFT                        6  /* SPKL_ENA */
+#define WM8961_SPKL_ENA_WIDTH                        1  /* SPKL_ENA */
+
+/*
+ * R51 (0x33) - Class D Control 2
+ */
+#define WM8961_CLASSD_ACGAIN_MASK               0x0007  /* CLASSD_ACGAIN - [2:0] */
+#define WM8961_CLASSD_ACGAIN_SHIFT                   0  /* CLASSD_ACGAIN - [2:0] */
+#define WM8961_CLASSD_ACGAIN_WIDTH                   3  /* CLASSD_ACGAIN - [2:0] */
+
+/*
+ * R56 (0x38) - Clocking 4
+ */
+#define WM8961_CLK_DCS_DIV_MASK                 0x01E0  /* CLK_DCS_DIV - [8:5] */
+#define WM8961_CLK_DCS_DIV_SHIFT                     5  /* CLK_DCS_DIV - [8:5] */
+#define WM8961_CLK_DCS_DIV_WIDTH                     4  /* CLK_DCS_DIV - [8:5] */
+#define WM8961_CLK_SYS_RATE_MASK                0x001E  /* CLK_SYS_RATE - [4:1] */
+#define WM8961_CLK_SYS_RATE_SHIFT                    1  /* CLK_SYS_RATE - [4:1] */
+#define WM8961_CLK_SYS_RATE_WIDTH                    4  /* CLK_SYS_RATE - [4:1] */
+
+/*
+ * R57 (0x39) - DSP Sidetone 0
+ */
+#define WM8961_ADCR_DAC_SVOL_MASK               0x00F0  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8961_ADCR_DAC_SVOL_SHIFT                   4  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8961_ADCR_DAC_SVOL_WIDTH                   4  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8961_ADC_TO_DACR_MASK                 0x000C  /* ADC_TO_DACR - [3:2] */
+#define WM8961_ADC_TO_DACR_SHIFT                     2  /* ADC_TO_DACR - [3:2] */
+#define WM8961_ADC_TO_DACR_WIDTH                     2  /* ADC_TO_DACR - [3:2] */
+
+/*
+ * R58 (0x3A) - DSP Sidetone 1
+ */
+#define WM8961_ADCL_DAC_SVOL_MASK               0x00F0  /* ADCL_DAC_SVOL - [7:4] */
+#define WM8961_ADCL_DAC_SVOL_SHIFT                   4  /* ADCL_DAC_SVOL - [7:4] */
+#define WM8961_ADCL_DAC_SVOL_WIDTH                   4  /* ADCL_DAC_SVOL - [7:4] */
+#define WM8961_ADC_TO_DACL_MASK                 0x000C  /* ADC_TO_DACL - [3:2] */
+#define WM8961_ADC_TO_DACL_SHIFT                     2  /* ADC_TO_DACL - [3:2] */
+#define WM8961_ADC_TO_DACL_WIDTH                     2  /* ADC_TO_DACL - [3:2] */
+
+/*
+ * R60 (0x3C) - DC Servo 0
+ */
+#define WM8961_DCS_ENA_CHAN_INL                 0x0080  /* DCS_ENA_CHAN_INL */
+#define WM8961_DCS_ENA_CHAN_INL_MASK            0x0080  /* DCS_ENA_CHAN_INL */
+#define WM8961_DCS_ENA_CHAN_INL_SHIFT                7  /* DCS_ENA_CHAN_INL */
+#define WM8961_DCS_ENA_CHAN_INL_WIDTH                1  /* DCS_ENA_CHAN_INL */
+#define WM8961_DCS_TRIG_STARTUP_INL             0x0040  /* DCS_TRIG_STARTUP_INL */
+#define WM8961_DCS_TRIG_STARTUP_INL_MASK        0x0040  /* DCS_TRIG_STARTUP_INL */
+#define WM8961_DCS_TRIG_STARTUP_INL_SHIFT            6  /* DCS_TRIG_STARTUP_INL */
+#define WM8961_DCS_TRIG_STARTUP_INL_WIDTH            1  /* DCS_TRIG_STARTUP_INL */
+#define WM8961_DCS_TRIG_SERIES_INL              0x0010  /* DCS_TRIG_SERIES_INL */
+#define WM8961_DCS_TRIG_SERIES_INL_MASK         0x0010  /* DCS_TRIG_SERIES_INL */
+#define WM8961_DCS_TRIG_SERIES_INL_SHIFT             4  /* DCS_TRIG_SERIES_INL */
+#define WM8961_DCS_TRIG_SERIES_INL_WIDTH             1  /* DCS_TRIG_SERIES_INL */
+#define WM8961_DCS_ENA_CHAN_INR                 0x0008  /* DCS_ENA_CHAN_INR */
+#define WM8961_DCS_ENA_CHAN_INR_MASK            0x0008  /* DCS_ENA_CHAN_INR */
+#define WM8961_DCS_ENA_CHAN_INR_SHIFT                3  /* DCS_ENA_CHAN_INR */
+#define WM8961_DCS_ENA_CHAN_INR_WIDTH                1  /* DCS_ENA_CHAN_INR */
+#define WM8961_DCS_TRIG_STARTUP_INR             0x0004  /* DCS_TRIG_STARTUP_INR */
+#define WM8961_DCS_TRIG_STARTUP_INR_MASK        0x0004  /* DCS_TRIG_STARTUP_INR */
+#define WM8961_DCS_TRIG_STARTUP_INR_SHIFT            2  /* DCS_TRIG_STARTUP_INR */
+#define WM8961_DCS_TRIG_STARTUP_INR_WIDTH            1  /* DCS_TRIG_STARTUP_INR */
+#define WM8961_DCS_TRIG_SERIES_INR              0x0001  /* DCS_TRIG_SERIES_INR */
+#define WM8961_DCS_TRIG_SERIES_INR_MASK         0x0001  /* DCS_TRIG_SERIES_INR */
+#define WM8961_DCS_TRIG_SERIES_INR_SHIFT             0  /* DCS_TRIG_SERIES_INR */
+#define WM8961_DCS_TRIG_SERIES_INR_WIDTH             1  /* DCS_TRIG_SERIES_INR */
+
+/*
+ * R61 (0x3D) - DC Servo 1
+ */
+#define WM8961_DCS_ENA_CHAN_HPL                 0x0080  /* DCS_ENA_CHAN_HPL */
+#define WM8961_DCS_ENA_CHAN_HPL_MASK            0x0080  /* DCS_ENA_CHAN_HPL */
+#define WM8961_DCS_ENA_CHAN_HPL_SHIFT                7  /* DCS_ENA_CHAN_HPL */
+#define WM8961_DCS_ENA_CHAN_HPL_WIDTH                1  /* DCS_ENA_CHAN_HPL */
+#define WM8961_DCS_TRIG_STARTUP_HPL             0x0040  /* DCS_TRIG_STARTUP_HPL */
+#define WM8961_DCS_TRIG_STARTUP_HPL_MASK        0x0040  /* DCS_TRIG_STARTUP_HPL */
+#define WM8961_DCS_TRIG_STARTUP_HPL_SHIFT            6  /* DCS_TRIG_STARTUP_HPL */
+#define WM8961_DCS_TRIG_STARTUP_HPL_WIDTH            1  /* DCS_TRIG_STARTUP_HPL */
+#define WM8961_DCS_TRIG_SERIES_HPL              0x0010  /* DCS_TRIG_SERIES_HPL */
+#define WM8961_DCS_TRIG_SERIES_HPL_MASK         0x0010  /* DCS_TRIG_SERIES_HPL */
+#define WM8961_DCS_TRIG_SERIES_HPL_SHIFT             4  /* DCS_TRIG_SERIES_HPL */
+#define WM8961_DCS_TRIG_SERIES_HPL_WIDTH             1  /* DCS_TRIG_SERIES_HPL */
+#define WM8961_DCS_ENA_CHAN_HPR                 0x0008  /* DCS_ENA_CHAN_HPR */
+#define WM8961_DCS_ENA_CHAN_HPR_MASK            0x0008  /* DCS_ENA_CHAN_HPR */
+#define WM8961_DCS_ENA_CHAN_HPR_SHIFT                3  /* DCS_ENA_CHAN_HPR */
+#define WM8961_DCS_ENA_CHAN_HPR_WIDTH                1  /* DCS_ENA_CHAN_HPR */
+#define WM8961_DCS_TRIG_STARTUP_HPR             0x0004  /* DCS_TRIG_STARTUP_HPR */
+#define WM8961_DCS_TRIG_STARTUP_HPR_MASK        0x0004  /* DCS_TRIG_STARTUP_HPR */
+#define WM8961_DCS_TRIG_STARTUP_HPR_SHIFT            2  /* DCS_TRIG_STARTUP_HPR */
+#define WM8961_DCS_TRIG_STARTUP_HPR_WIDTH            1  /* DCS_TRIG_STARTUP_HPR */
+#define WM8961_DCS_TRIG_SERIES_HPR              0x0001  /* DCS_TRIG_SERIES_HPR */
+#define WM8961_DCS_TRIG_SERIES_HPR_MASK         0x0001  /* DCS_TRIG_SERIES_HPR */
+#define WM8961_DCS_TRIG_SERIES_HPR_SHIFT             0  /* DCS_TRIG_SERIES_HPR */
+#define WM8961_DCS_TRIG_SERIES_HPR_WIDTH             1  /* DCS_TRIG_SERIES_HPR */
+
+/*
+ * R63 (0x3F) - DC Servo 3
+ */
+#define WM8961_DCS_FILT_BW_SERIES_MASK          0x0030  /* DCS_FILT_BW_SERIES - [5:4] */
+#define WM8961_DCS_FILT_BW_SERIES_SHIFT              4  /* DCS_FILT_BW_SERIES - [5:4] */
+#define WM8961_DCS_FILT_BW_SERIES_WIDTH              2  /* DCS_FILT_BW_SERIES - [5:4] */
+
+/*
+ * R65 (0x41) - DC Servo 5
+ */
+#define WM8961_DCS_SERIES_NO_HP_MASK            0x007F  /* DCS_SERIES_NO_HP - [6:0] */
+#define WM8961_DCS_SERIES_NO_HP_SHIFT                0  /* DCS_SERIES_NO_HP - [6:0] */
+#define WM8961_DCS_SERIES_NO_HP_WIDTH                7  /* DCS_SERIES_NO_HP - [6:0] */
+
+/*
+ * R68 (0x44) - Analogue PGA Bias
+ */
+#define WM8961_HP_PGAS_BIAS_MASK                0x0007  /* HP_PGAS_BIAS - [2:0] */
+#define WM8961_HP_PGAS_BIAS_SHIFT                    0  /* HP_PGAS_BIAS - [2:0] */
+#define WM8961_HP_PGAS_BIAS_WIDTH                    3  /* HP_PGAS_BIAS - [2:0] */
+
+/*
+ * R69 (0x45) - Analogue HP 0
+ */
+#define WM8961_HPL_RMV_SHORT                    0x0080  /* HPL_RMV_SHORT */
+#define WM8961_HPL_RMV_SHORT_MASK               0x0080  /* HPL_RMV_SHORT */
+#define WM8961_HPL_RMV_SHORT_SHIFT                   7  /* HPL_RMV_SHORT */
+#define WM8961_HPL_RMV_SHORT_WIDTH                   1  /* HPL_RMV_SHORT */
+#define WM8961_HPL_ENA_OUTP                     0x0040  /* HPL_ENA_OUTP */
+#define WM8961_HPL_ENA_OUTP_MASK                0x0040  /* HPL_ENA_OUTP */
+#define WM8961_HPL_ENA_OUTP_SHIFT                    6  /* HPL_ENA_OUTP */
+#define WM8961_HPL_ENA_OUTP_WIDTH                    1  /* HPL_ENA_OUTP */
+#define WM8961_HPL_ENA_DLY                      0x0020  /* HPL_ENA_DLY */
+#define WM8961_HPL_ENA_DLY_MASK                 0x0020  /* HPL_ENA_DLY */
+#define WM8961_HPL_ENA_DLY_SHIFT                     5  /* HPL_ENA_DLY */
+#define WM8961_HPL_ENA_DLY_WIDTH                     1  /* HPL_ENA_DLY */
+#define WM8961_HPL_ENA                          0x0010  /* HPL_ENA */
+#define WM8961_HPL_ENA_MASK                     0x0010  /* HPL_ENA */
+#define WM8961_HPL_ENA_SHIFT                         4  /* HPL_ENA */
+#define WM8961_HPL_ENA_WIDTH                         1  /* HPL_ENA */
+#define WM8961_HPR_RMV_SHORT                    0x0008  /* HPR_RMV_SHORT */
+#define WM8961_HPR_RMV_SHORT_MASK               0x0008  /* HPR_RMV_SHORT */
+#define WM8961_HPR_RMV_SHORT_SHIFT                   3  /* HPR_RMV_SHORT */
+#define WM8961_HPR_RMV_SHORT_WIDTH                   1  /* HPR_RMV_SHORT */
+#define WM8961_HPR_ENA_OUTP                     0x0004  /* HPR_ENA_OUTP */
+#define WM8961_HPR_ENA_OUTP_MASK                0x0004  /* HPR_ENA_OUTP */
+#define WM8961_HPR_ENA_OUTP_SHIFT                    2  /* HPR_ENA_OUTP */
+#define WM8961_HPR_ENA_OUTP_WIDTH                    1  /* HPR_ENA_OUTP */
+#define WM8961_HPR_ENA_DLY                      0x0002  /* HPR_ENA_DLY */
+#define WM8961_HPR_ENA_DLY_MASK                 0x0002  /* HPR_ENA_DLY */
+#define WM8961_HPR_ENA_DLY_SHIFT                     1  /* HPR_ENA_DLY */
+#define WM8961_HPR_ENA_DLY_WIDTH                     1  /* HPR_ENA_DLY */
+#define WM8961_HPR_ENA                          0x0001  /* HPR_ENA */
+#define WM8961_HPR_ENA_MASK                     0x0001  /* HPR_ENA */
+#define WM8961_HPR_ENA_SHIFT                         0  /* HPR_ENA */
+#define WM8961_HPR_ENA_WIDTH                         1  /* HPR_ENA */
+
+/*
+ * R71 (0x47) - Analogue HP 2
+ */
+#define WM8961_HPL_VOL_MASK                     0x01C0  /* HPL_VOL - [8:6] */
+#define WM8961_HPL_VOL_SHIFT                         6  /* HPL_VOL - [8:6] */
+#define WM8961_HPL_VOL_WIDTH                         3  /* HPL_VOL - [8:6] */
+#define WM8961_HPR_VOL_MASK                     0x0038  /* HPR_VOL - [5:3] */
+#define WM8961_HPR_VOL_SHIFT                         3  /* HPR_VOL - [5:3] */
+#define WM8961_HPR_VOL_WIDTH                         3  /* HPR_VOL - [5:3] */
+#define WM8961_HP_BIAS_BOOST_MASK               0x0007  /* HP_BIAS_BOOST - [2:0] */
+#define WM8961_HP_BIAS_BOOST_SHIFT                   0  /* HP_BIAS_BOOST - [2:0] */
+#define WM8961_HP_BIAS_BOOST_WIDTH                   3  /* HP_BIAS_BOOST - [2:0] */
+
+/*
+ * R72 (0x48) - Charge Pump 1
+ */
+#define WM8961_CP_ENA                           0x0001  /* CP_ENA */
+#define WM8961_CP_ENA_MASK                      0x0001  /* CP_ENA */
+#define WM8961_CP_ENA_SHIFT                          0  /* CP_ENA */
+#define WM8961_CP_ENA_WIDTH                          1  /* CP_ENA */
+
+/*
+ * R82 (0x52) - Charge Pump B
+ */
+#define WM8961_CP_DYN_PWR_MASK                  0x0003  /* CP_DYN_PWR - [1:0] */
+#define WM8961_CP_DYN_PWR_SHIFT                      0  /* CP_DYN_PWR - [1:0] */
+#define WM8961_CP_DYN_PWR_WIDTH                      2  /* CP_DYN_PWR - [1:0] */
+
+/*
+ * R87 (0x57) - Write Sequencer 1
+ */
+#define WM8961_WSEQ_ENA                         0x0020  /* WSEQ_ENA */
+#define WM8961_WSEQ_ENA_MASK                    0x0020  /* WSEQ_ENA */
+#define WM8961_WSEQ_ENA_SHIFT                        5  /* WSEQ_ENA */
+#define WM8961_WSEQ_ENA_WIDTH                        1  /* WSEQ_ENA */
+#define WM8961_WSEQ_WRITE_INDEX_MASK            0x001F  /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8961_WSEQ_WRITE_INDEX_SHIFT                0  /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8961_WSEQ_WRITE_INDEX_WIDTH                5  /* WSEQ_WRITE_INDEX - [4:0] */
+
+/*
+ * R88 (0x58) - Write Sequencer 2
+ */
+#define WM8961_WSEQ_EOS                         0x0100  /* WSEQ_EOS */
+#define WM8961_WSEQ_EOS_MASK                    0x0100  /* WSEQ_EOS */
+#define WM8961_WSEQ_EOS_SHIFT                        8  /* WSEQ_EOS */
+#define WM8961_WSEQ_EOS_WIDTH                        1  /* WSEQ_EOS */
+#define WM8961_WSEQ_ADDR_MASK                   0x00FF  /* WSEQ_ADDR - [7:0] */
+#define WM8961_WSEQ_ADDR_SHIFT                       0  /* WSEQ_ADDR - [7:0] */
+#define WM8961_WSEQ_ADDR_WIDTH                       8  /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R89 (0x59) - Write Sequencer 3
+ */
+#define WM8961_WSEQ_DATA_MASK                   0x00FF  /* WSEQ_DATA - [7:0] */
+#define WM8961_WSEQ_DATA_SHIFT                       0  /* WSEQ_DATA - [7:0] */
+#define WM8961_WSEQ_DATA_WIDTH                       8  /* WSEQ_DATA - [7:0] */
+
+/*
+ * R90 (0x5A) - Write Sequencer 4
+ */
+#define WM8961_WSEQ_ABORT                       0x0100  /* WSEQ_ABORT */
+#define WM8961_WSEQ_ABORT_MASK                  0x0100  /* WSEQ_ABORT */
+#define WM8961_WSEQ_ABORT_SHIFT                      8  /* WSEQ_ABORT */
+#define WM8961_WSEQ_ABORT_WIDTH                      1  /* WSEQ_ABORT */
+#define WM8961_WSEQ_START                       0x0080  /* WSEQ_START */
+#define WM8961_WSEQ_START_MASK                  0x0080  /* WSEQ_START */
+#define WM8961_WSEQ_START_SHIFT                      7  /* WSEQ_START */
+#define WM8961_WSEQ_START_WIDTH                      1  /* WSEQ_START */
+#define WM8961_WSEQ_START_INDEX_MASK            0x003F  /* WSEQ_START_INDEX - [5:0] */
+#define WM8961_WSEQ_START_INDEX_SHIFT                0  /* WSEQ_START_INDEX - [5:0] */
+#define WM8961_WSEQ_START_INDEX_WIDTH                6  /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R91 (0x5B) - Write Sequencer 5
+ */
+#define WM8961_WSEQ_DATA_WIDTH_MASK             0x0070  /* WSEQ_DATA_WIDTH - [6:4] */
+#define WM8961_WSEQ_DATA_WIDTH_SHIFT                 4  /* WSEQ_DATA_WIDTH - [6:4] */
+#define WM8961_WSEQ_DATA_WIDTH_WIDTH                 3  /* WSEQ_DATA_WIDTH - [6:4] */
+#define WM8961_WSEQ_DATA_START_MASK             0x000F  /* WSEQ_DATA_START - [3:0] */
+#define WM8961_WSEQ_DATA_START_SHIFT                 0  /* WSEQ_DATA_START - [3:0] */
+#define WM8961_WSEQ_DATA_START_WIDTH                 4  /* WSEQ_DATA_START - [3:0] */
+
+/*
+ * R92 (0x5C) - Write Sequencer 6
+ */
+#define WM8961_WSEQ_DELAY_MASK                  0x000F  /* WSEQ_DELAY - [3:0] */
+#define WM8961_WSEQ_DELAY_SHIFT                      0  /* WSEQ_DELAY - [3:0] */
+#define WM8961_WSEQ_DELAY_WIDTH                      4  /* WSEQ_DELAY - [3:0] */
+
+/*
+ * R93 (0x5D) - Write Sequencer 7
+ */
+#define WM8961_WSEQ_BUSY                        0x0001  /* WSEQ_BUSY */
+#define WM8961_WSEQ_BUSY_MASK                   0x0001  /* WSEQ_BUSY */
+#define WM8961_WSEQ_BUSY_SHIFT                       0  /* WSEQ_BUSY */
+#define WM8961_WSEQ_BUSY_WIDTH                       1  /* WSEQ_BUSY */
+
+/*
+ * R252 (0xFC) - General test 1
+ */
+#define WM8961_ARA_ENA                          0x0002  /* ARA_ENA */
+#define WM8961_ARA_ENA_MASK                     0x0002  /* ARA_ENA */
+#define WM8961_ARA_ENA_SHIFT                         1  /* ARA_ENA */
+#define WM8961_ARA_ENA_WIDTH                         1  /* ARA_ENA */
+#define WM8961_AUTO_INC                         0x0001  /* AUTO_INC */
+#define WM8961_AUTO_INC_MASK                    0x0001  /* AUTO_INC */
+#define WM8961_AUTO_INC_SHIFT                        0  /* AUTO_INC */
+#define WM8961_AUTO_INC_WIDTH                        1  /* AUTO_INC */
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8962.c b/linux/sound/soc/codecs/wm8962.c
new file mode 100644
index 0000000..7c421cc
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8962.c
@@ -0,0 +1,3975 @@
+/*
+ * wm8962.c  --  WM8962 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/gcd.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/wm8962.h>
+
+#include "wm8962.h"
+
+#define WM8962_NUM_SUPPLIES 8
+static const char *wm8962_supply_names[WM8962_NUM_SUPPLIES] = {
+	"DCVDD",
+	"DBVDD",
+	"AVDD",
+	"CPVDD",
+	"MICVDD",
+	"PLLVDD",
+	"SPKVDD1",
+	"SPKVDD2",
+};
+
+/* codec private data */
+struct wm8962_priv {
+	struct snd_soc_codec *codec;
+
+	int sysclk;
+	int sysclk_rate;
+
+	int bclk;  /* Desired BCLK */
+	int lrclk;
+
+	int fll_src;
+	int fll_fref;
+	int fll_fout;
+
+	struct delayed_work mic_work;
+	struct snd_soc_jack *jack;
+
+	struct regulator_bulk_data supplies[WM8962_NUM_SUPPLIES];
+	struct notifier_block disable_nb[WM8962_NUM_SUPPLIES];
+
+#if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE)
+	struct input_dev *beep;
+	struct work_struct beep_work;
+	int beep_rate;
+#endif
+
+#ifdef CONFIG_GPIOLIB
+	struct gpio_chip gpio_chip;
+#endif
+};
+
+/* We can't use the same notifier block for more than one supply and
+ * there's no way I can see to get from a callback to the caller
+ * except container_of().
+ */
+#define WM8962_REGULATOR_EVENT(n) \
+static int wm8962_regulator_event_##n(struct notifier_block *nb, \
+				    unsigned long event, void *data)	\
+{ \
+	struct wm8962_priv *wm8962 = container_of(nb, struct wm8962_priv, \
+						  disable_nb[n]); \
+	if (event & REGULATOR_EVENT_DISABLE) { \
+		wm8962->codec->cache_sync = 1; \
+	} \
+	return 0; \
+}
+
+WM8962_REGULATOR_EVENT(0)
+WM8962_REGULATOR_EVENT(1)
+WM8962_REGULATOR_EVENT(2)
+WM8962_REGULATOR_EVENT(3)
+WM8962_REGULATOR_EVENT(4)
+WM8962_REGULATOR_EVENT(5)
+WM8962_REGULATOR_EVENT(6)
+WM8962_REGULATOR_EVENT(7)
+
+static const u16 wm8962_reg[WM8962_MAX_REGISTER + 1] = {
+	[0] = 0x009F,     /* R0     - Left Input volume */
+	[1] = 0x049F,     /* R1     - Right Input volume */
+	[2] = 0x0000,     /* R2     - HPOUTL volume */
+	[3] = 0x0000,     /* R3     - HPOUTR volume */
+	[4] = 0x0020,     /* R4     - Clocking1 */
+	[5] = 0x0018,     /* R5     - ADC & DAC Control 1 */
+	[6] = 0x2008,     /* R6     - ADC & DAC Control 2 */
+	[7] = 0x000A,     /* R7     - Audio Interface 0 */
+	[8] = 0x01E4,     /* R8     - Clocking2 */
+	[9] = 0x0300,     /* R9     - Audio Interface 1 */
+	[10] = 0x00C0,    /* R10    - Left DAC volume */
+	[11] = 0x00C0,    /* R11    - Right DAC volume */
+
+	[14] = 0x0040,     /* R14    - Audio Interface 2 */
+	[15] = 0x6243,     /* R15    - Software Reset */
+
+	[17] = 0x007B,     /* R17    - ALC1 */
+	[18] = 0x0000,     /* R18    - ALC2 */
+	[19] = 0x1C32,     /* R19    - ALC3 */
+	[20] = 0x3200,     /* R20    - Noise Gate */
+	[21] = 0x00C0,     /* R21    - Left ADC volume */
+	[22] = 0x00C0,     /* R22    - Right ADC volume */
+	[23] = 0x0160,     /* R23    - Additional control(1) */
+	[24] = 0x0000,     /* R24    - Additional control(2) */
+	[25] = 0x0000,     /* R25    - Pwr Mgmt (1) */
+	[26] = 0x0000,     /* R26    - Pwr Mgmt (2) */
+	[27] = 0x0010,     /* R27    - Additional Control (3) */
+	[28] = 0x0000,     /* R28    - Anti-pop */
+
+	[30] = 0x005E,     /* R30    - Clocking 3 */
+	[31] = 0x0000,     /* R31    - Input mixer control (1) */
+	[32] = 0x0145,     /* R32    - Left input mixer volume */
+	[33] = 0x0145,     /* R33    - Right input mixer volume */
+	[34] = 0x0009,     /* R34    - Input mixer control (2) */
+	[35] = 0x0003,     /* R35    - Input bias control */
+	[37] = 0x0008,     /* R37    - Left input PGA control */
+	[38] = 0x0008,     /* R38    - Right input PGA control */
+
+	[40] = 0x0000,     /* R40    - SPKOUTL volume */
+	[41] = 0x0000,     /* R41    - SPKOUTR volume */
+
+	[47] = 0x0000,     /* R47    - Thermal Shutdown Status */
+	[48] = 0x8027,     /* R48    - Additional Control (4) */
+	[49] = 0x0010,     /* R49    - Class D Control 1 */
+
+	[51] = 0x0003,     /* R51    - Class D Control 2 */
+
+	[56] = 0x0506,     /* R56    - Clocking 4 */
+	[57] = 0x0000,     /* R57    - DAC DSP Mixing (1) */
+	[58] = 0x0000,     /* R58    - DAC DSP Mixing (2) */
+
+	[60] = 0x0300,     /* R60    - DC Servo 0 */
+	[61] = 0x0300,     /* R61    - DC Servo 1 */
+
+	[64] = 0x0810,     /* R64    - DC Servo 4 */
+
+	[66] = 0x0000,     /* R66    - DC Servo 6 */
+
+	[68] = 0x001B,     /* R68    - Analogue PGA Bias */
+	[69] = 0x0000,     /* R69    - Analogue HP 0 */
+
+	[71] = 0x01FB,     /* R71    - Analogue HP 2 */
+	[72] = 0x0000,     /* R72    - Charge Pump 1 */
+
+	[82] = 0x0004,     /* R82    - Charge Pump B */
+
+	[87] = 0x0000,     /* R87    - Write Sequencer Control 1 */
+
+	[90] = 0x0000,     /* R90    - Write Sequencer Control 2 */
+
+	[93] = 0x0000,     /* R93    - Write Sequencer Control 3 */
+	[94] = 0x0000,     /* R94    - Control Interface */
+
+	[99] = 0x0000,     /* R99    - Mixer Enables */
+	[100] = 0x0000,     /* R100   - Headphone Mixer (1) */
+	[101] = 0x0000,     /* R101   - Headphone Mixer (2) */
+	[102] = 0x013F,     /* R102   - Headphone Mixer (3) */
+	[103] = 0x013F,     /* R103   - Headphone Mixer (4) */
+
+	[105] = 0x0000,     /* R105   - Speaker Mixer (1) */
+	[106] = 0x0000,     /* R106   - Speaker Mixer (2) */
+	[107] = 0x013F,     /* R107   - Speaker Mixer (3) */
+	[108] = 0x013F,     /* R108   - Speaker Mixer (4) */
+	[109] = 0x0003,     /* R109   - Speaker Mixer (5) */
+	[110] = 0x0002,     /* R110   - Beep Generator (1) */
+
+	[115] = 0x0006,     /* R115   - Oscillator Trim (3) */
+	[116] = 0x0026,     /* R116   - Oscillator Trim (4) */
+
+	[119] = 0x0000,     /* R119   - Oscillator Trim (7) */
+
+	[124] = 0x0011,     /* R124   - Analogue Clocking1 */
+	[125] = 0x004B,     /* R125   - Analogue Clocking2 */
+	[126] = 0x000D,     /* R126   - Analogue Clocking3 */
+	[127] = 0x0000,     /* R127   - PLL Software Reset */
+
+	[129] = 0x0000,     /* R129   - PLL2 */
+
+	[131] = 0x0000,     /* R131   - PLL 4 */
+
+	[136] = 0x0067,     /* R136   - PLL 9 */
+	[137] = 0x001C,     /* R137   - PLL 10 */
+	[138] = 0x0071,     /* R138   - PLL 11 */
+	[139] = 0x00C7,     /* R139   - PLL 12 */
+	[140] = 0x0067,     /* R140   - PLL 13 */
+	[141] = 0x0048,     /* R141   - PLL 14 */
+	[142] = 0x0022,     /* R142   - PLL 15 */
+	[143] = 0x0097,     /* R143   - PLL 16 */
+
+	[155] = 0x000C,     /* R155   - FLL Control (1) */
+	[156] = 0x0039,     /* R156   - FLL Control (2) */
+	[157] = 0x0180,     /* R157   - FLL Control (3) */
+
+	[159] = 0x0032,     /* R159   - FLL Control (5) */
+	[160] = 0x0018,     /* R160   - FLL Control (6) */
+	[161] = 0x007D,     /* R161   - FLL Control (7) */
+	[162] = 0x0008,     /* R162   - FLL Control (8) */
+
+	[252] = 0x0005,     /* R252   - General test 1 */
+
+	[256] = 0x0000,     /* R256   - DF1 */
+	[257] = 0x0000,     /* R257   - DF2 */
+	[258] = 0x0000,     /* R258   - DF3 */
+	[259] = 0x0000,     /* R259   - DF4 */
+	[260] = 0x0000,     /* R260   - DF5 */
+	[261] = 0x0000,     /* R261   - DF6 */
+	[262] = 0x0000,     /* R262   - DF7 */
+
+	[264] = 0x0000,     /* R264   - LHPF1 */
+	[265] = 0x0000,     /* R265   - LHPF2 */
+
+	[268] = 0x0000,     /* R268   - THREED1 */
+	[269] = 0x0000,     /* R269   - THREED2 */
+	[270] = 0x0000,     /* R270   - THREED3 */
+	[271] = 0x0000,     /* R271   - THREED4 */
+
+	[276] = 0x000C,     /* R276   - DRC 1 */
+	[277] = 0x0925,     /* R277   - DRC 2 */
+	[278] = 0x0000,     /* R278   - DRC 3 */
+	[279] = 0x0000,     /* R279   - DRC 4 */
+	[280] = 0x0000,     /* R280   - DRC 5 */
+
+	[285] = 0x0000,     /* R285   - Tloopback */
+
+	[335] = 0x0004,     /* R335   - EQ1 */
+	[336] = 0x6318,     /* R336   - EQ2 */
+	[337] = 0x6300,     /* R337   - EQ3 */
+	[338] = 0x0FCA,     /* R338   - EQ4 */
+	[339] = 0x0400,     /* R339   - EQ5 */
+	[340] = 0x00D8,     /* R340   - EQ6 */
+	[341] = 0x1EB5,     /* R341   - EQ7 */
+	[342] = 0xF145,     /* R342   - EQ8 */
+	[343] = 0x0B75,     /* R343   - EQ9 */
+	[344] = 0x01C5,     /* R344   - EQ10 */
+	[345] = 0x1C58,     /* R345   - EQ11 */
+	[346] = 0xF373,     /* R346   - EQ12 */
+	[347] = 0x0A54,     /* R347   - EQ13 */
+	[348] = 0x0558,     /* R348   - EQ14 */
+	[349] = 0x168E,     /* R349   - EQ15 */
+	[350] = 0xF829,     /* R350   - EQ16 */
+	[351] = 0x07AD,     /* R351   - EQ17 */
+	[352] = 0x1103,     /* R352   - EQ18 */
+	[353] = 0x0564,     /* R353   - EQ19 */
+	[354] = 0x0559,     /* R354   - EQ20 */
+	[355] = 0x4000,     /* R355   - EQ21 */
+	[356] = 0x6318,     /* R356   - EQ22 */
+	[357] = 0x6300,     /* R357   - EQ23 */
+	[358] = 0x0FCA,     /* R358   - EQ24 */
+	[359] = 0x0400,     /* R359   - EQ25 */
+	[360] = 0x00D8,     /* R360   - EQ26 */
+	[361] = 0x1EB5,     /* R361   - EQ27 */
+	[362] = 0xF145,     /* R362   - EQ28 */
+	[363] = 0x0B75,     /* R363   - EQ29 */
+	[364] = 0x01C5,     /* R364   - EQ30 */
+	[365] = 0x1C58,     /* R365   - EQ31 */
+	[366] = 0xF373,     /* R366   - EQ32 */
+	[367] = 0x0A54,     /* R367   - EQ33 */
+	[368] = 0x0558,     /* R368   - EQ34 */
+	[369] = 0x168E,     /* R369   - EQ35 */
+	[370] = 0xF829,     /* R370   - EQ36 */
+	[371] = 0x07AD,     /* R371   - EQ37 */
+	[372] = 0x1103,     /* R372   - EQ38 */
+	[373] = 0x0564,     /* R373   - EQ39 */
+	[374] = 0x0559,     /* R374   - EQ40 */
+	[375] = 0x4000,     /* R375   - EQ41 */
+
+	[513] = 0x0000,     /* R513   - GPIO 2 */
+	[514] = 0x0000,     /* R514   - GPIO 3 */
+
+	[516] = 0x8100,     /* R516   - GPIO 5 */
+	[517] = 0x8100,     /* R517   - GPIO 6 */
+
+	[560] = 0x0000,     /* R560   - Interrupt Status 1 */
+	[561] = 0x0000,     /* R561   - Interrupt Status 2 */
+
+	[568] = 0x0030,     /* R568   - Interrupt Status 1 Mask */
+	[569] = 0xFFED,     /* R569   - Interrupt Status 2 Mask */
+
+	[576] = 0x0000,     /* R576   - Interrupt Control */
+
+	[584] = 0x002D,     /* R584   - IRQ Debounce */
+
+	[586] = 0x0000,     /* R586   -  MICINT Source Pol */
+
+	[768] = 0x1C00,     /* R768   - DSP2 Power Management */
+
+	[1037] = 0x0000,     /* R1037  - DSP2_ExecControl */
+
+	[8192] = 0x0000,     /* R8192  - DSP2 Instruction RAM 0 */
+
+	[9216] = 0x0030,     /* R9216  - DSP2 Address RAM 2 */
+	[9217] = 0x0000,     /* R9217  - DSP2 Address RAM 1 */
+	[9218] = 0x0000,     /* R9218  - DSP2 Address RAM 0 */
+
+	[12288] = 0x0000,     /* R12288 - DSP2 Data1 RAM 1 */
+	[12289] = 0x0000,     /* R12289 - DSP2 Data1 RAM 0 */
+
+	[13312] = 0x0000,     /* R13312 - DSP2 Data2 RAM 1 */
+	[13313] = 0x0000,     /* R13313 - DSP2 Data2 RAM 0 */
+
+	[14336] = 0x0000,     /* R14336 - DSP2 Data3 RAM 1 */
+	[14337] = 0x0000,     /* R14337 - DSP2 Data3 RAM 0 */
+
+	[15360] = 0x000A,     /* R15360 - DSP2 Coeff RAM 0 */
+
+	[16384] = 0x0000,     /* R16384 - RETUNEADC_SHARED_COEFF_1 */
+	[16385] = 0x0000,     /* R16385 - RETUNEADC_SHARED_COEFF_0 */
+	[16386] = 0x0000,     /* R16386 - RETUNEDAC_SHARED_COEFF_1 */
+	[16387] = 0x0000,     /* R16387 - RETUNEDAC_SHARED_COEFF_0 */
+	[16388] = 0x0000,     /* R16388 - SOUNDSTAGE_ENABLES_1 */
+	[16389] = 0x0000,     /* R16389 - SOUNDSTAGE_ENABLES_0 */
+
+	[16896] = 0x0002,     /* R16896 - HDBASS_AI_1 */
+	[16897] = 0xBD12,     /* R16897 - HDBASS_AI_0 */
+	[16898] = 0x007C,     /* R16898 - HDBASS_AR_1 */
+	[16899] = 0x586C,     /* R16899 - HDBASS_AR_0 */
+	[16900] = 0x0053,     /* R16900 - HDBASS_B_1 */
+	[16901] = 0x8121,     /* R16901 - HDBASS_B_0 */
+	[16902] = 0x003F,     /* R16902 - HDBASS_K_1 */
+	[16903] = 0x8BD8,     /* R16903 - HDBASS_K_0 */
+	[16904] = 0x0032,     /* R16904 - HDBASS_N1_1 */
+	[16905] = 0xF52D,     /* R16905 - HDBASS_N1_0 */
+	[16906] = 0x0065,     /* R16906 - HDBASS_N2_1 */
+	[16907] = 0xAC8C,     /* R16907 - HDBASS_N2_0 */
+	[16908] = 0x006B,     /* R16908 - HDBASS_N3_1 */
+	[16909] = 0xE087,     /* R16909 - HDBASS_N3_0 */
+	[16910] = 0x0072,     /* R16910 - HDBASS_N4_1 */
+	[16911] = 0x1483,     /* R16911 - HDBASS_N4_0 */
+	[16912] = 0x0072,     /* R16912 - HDBASS_N5_1 */
+	[16913] = 0x1483,     /* R16913 - HDBASS_N5_0 */
+	[16914] = 0x0043,     /* R16914 - HDBASS_X1_1 */
+	[16915] = 0x3525,     /* R16915 - HDBASS_X1_0 */
+	[16916] = 0x0006,     /* R16916 - HDBASS_X2_1 */
+	[16917] = 0x6A4A,     /* R16917 - HDBASS_X2_0 */
+	[16918] = 0x0043,     /* R16918 - HDBASS_X3_1 */
+	[16919] = 0x6079,     /* R16919 - HDBASS_X3_0 */
+	[16920] = 0x0008,     /* R16920 - HDBASS_ATK_1 */
+	[16921] = 0x0000,     /* R16921 - HDBASS_ATK_0 */
+	[16922] = 0x0001,     /* R16922 - HDBASS_DCY_1 */
+	[16923] = 0x0000,     /* R16923 - HDBASS_DCY_0 */
+	[16924] = 0x0059,     /* R16924 - HDBASS_PG_1 */
+	[16925] = 0x999A,     /* R16925 - HDBASS_PG_0 */
+
+	[17048] = 0x0083,     /* R17408 - HPF_C_1 */
+	[17049] = 0x98AD,     /* R17409 - HPF_C_0 */
+
+	[17920] = 0x007F,     /* R17920 - ADCL_RETUNE_C1_1 */
+	[17921] = 0xFFFF,     /* R17921 - ADCL_RETUNE_C1_0 */
+	[17922] = 0x0000,     /* R17922 - ADCL_RETUNE_C2_1 */
+	[17923] = 0x0000,     /* R17923 - ADCL_RETUNE_C2_0 */
+	[17924] = 0x0000,     /* R17924 - ADCL_RETUNE_C3_1 */
+	[17925] = 0x0000,     /* R17925 - ADCL_RETUNE_C3_0 */
+	[17926] = 0x0000,     /* R17926 - ADCL_RETUNE_C4_1 */
+	[17927] = 0x0000,     /* R17927 - ADCL_RETUNE_C4_0 */
+	[17928] = 0x0000,     /* R17928 - ADCL_RETUNE_C5_1 */
+	[17929] = 0x0000,     /* R17929 - ADCL_RETUNE_C5_0 */
+	[17930] = 0x0000,     /* R17930 - ADCL_RETUNE_C6_1 */
+	[17931] = 0x0000,     /* R17931 - ADCL_RETUNE_C6_0 */
+	[17932] = 0x0000,     /* R17932 - ADCL_RETUNE_C7_1 */
+	[17933] = 0x0000,     /* R17933 - ADCL_RETUNE_C7_0 */
+	[17934] = 0x0000,     /* R17934 - ADCL_RETUNE_C8_1 */
+	[17935] = 0x0000,     /* R17935 - ADCL_RETUNE_C8_0 */
+	[17936] = 0x0000,     /* R17936 - ADCL_RETUNE_C9_1 */
+	[17937] = 0x0000,     /* R17937 - ADCL_RETUNE_C9_0 */
+	[17938] = 0x0000,     /* R17938 - ADCL_RETUNE_C10_1 */
+	[17939] = 0x0000,     /* R17939 - ADCL_RETUNE_C10_0 */
+	[17940] = 0x0000,     /* R17940 - ADCL_RETUNE_C11_1 */
+	[17941] = 0x0000,     /* R17941 - ADCL_RETUNE_C11_0 */
+	[17942] = 0x0000,     /* R17942 - ADCL_RETUNE_C12_1 */
+	[17943] = 0x0000,     /* R17943 - ADCL_RETUNE_C12_0 */
+	[17944] = 0x0000,     /* R17944 - ADCL_RETUNE_C13_1 */
+	[17945] = 0x0000,     /* R17945 - ADCL_RETUNE_C13_0 */
+	[17946] = 0x0000,     /* R17946 - ADCL_RETUNE_C14_1 */
+	[17947] = 0x0000,     /* R17947 - ADCL_RETUNE_C14_0 */
+	[17948] = 0x0000,     /* R17948 - ADCL_RETUNE_C15_1 */
+	[17949] = 0x0000,     /* R17949 - ADCL_RETUNE_C15_0 */
+	[17950] = 0x0000,     /* R17950 - ADCL_RETUNE_C16_1 */
+	[17951] = 0x0000,     /* R17951 - ADCL_RETUNE_C16_0 */
+	[17952] = 0x0000,     /* R17952 - ADCL_RETUNE_C17_1 */
+	[17953] = 0x0000,     /* R17953 - ADCL_RETUNE_C17_0 */
+	[17954] = 0x0000,     /* R17954 - ADCL_RETUNE_C18_1 */
+	[17955] = 0x0000,     /* R17955 - ADCL_RETUNE_C18_0 */
+	[17956] = 0x0000,     /* R17956 - ADCL_RETUNE_C19_1 */
+	[17957] = 0x0000,     /* R17957 - ADCL_RETUNE_C19_0 */
+	[17958] = 0x0000,     /* R17958 - ADCL_RETUNE_C20_1 */
+	[17959] = 0x0000,     /* R17959 - ADCL_RETUNE_C20_0 */
+	[17960] = 0x0000,     /* R17960 - ADCL_RETUNE_C21_1 */
+	[17961] = 0x0000,     /* R17961 - ADCL_RETUNE_C21_0 */
+	[17962] = 0x0000,     /* R17962 - ADCL_RETUNE_C22_1 */
+	[17963] = 0x0000,     /* R17963 - ADCL_RETUNE_C22_0 */
+	[17964] = 0x0000,     /* R17964 - ADCL_RETUNE_C23_1 */
+	[17965] = 0x0000,     /* R17965 - ADCL_RETUNE_C23_0 */
+	[17966] = 0x0000,     /* R17966 - ADCL_RETUNE_C24_1 */
+	[17967] = 0x0000,     /* R17967 - ADCL_RETUNE_C24_0 */
+	[17968] = 0x0000,     /* R17968 - ADCL_RETUNE_C25_1 */
+	[17969] = 0x0000,     /* R17969 - ADCL_RETUNE_C25_0 */
+	[17970] = 0x0000,     /* R17970 - ADCL_RETUNE_C26_1 */
+	[17971] = 0x0000,     /* R17971 - ADCL_RETUNE_C26_0 */
+	[17972] = 0x0000,     /* R17972 - ADCL_RETUNE_C27_1 */
+	[17973] = 0x0000,     /* R17973 - ADCL_RETUNE_C27_0 */
+	[17974] = 0x0000,     /* R17974 - ADCL_RETUNE_C28_1 */
+	[17975] = 0x0000,     /* R17975 - ADCL_RETUNE_C28_0 */
+	[17976] = 0x0000,     /* R17976 - ADCL_RETUNE_C29_1 */
+	[17977] = 0x0000,     /* R17977 - ADCL_RETUNE_C29_0 */
+	[17978] = 0x0000,     /* R17978 - ADCL_RETUNE_C30_1 */
+	[17979] = 0x0000,     /* R17979 - ADCL_RETUNE_C30_0 */
+	[17980] = 0x0000,     /* R17980 - ADCL_RETUNE_C31_1 */
+	[17981] = 0x0000,     /* R17981 - ADCL_RETUNE_C31_0 */
+	[17982] = 0x0000,     /* R17982 - ADCL_RETUNE_C32_1 */
+	[17983] = 0x0000,     /* R17983 - ADCL_RETUNE_C32_0 */
+
+	[18432] = 0x0020,     /* R18432 - RETUNEADC_PG2_1 */
+	[18433] = 0x0000,     /* R18433 - RETUNEADC_PG2_0 */
+	[18434] = 0x0040,     /* R18434 - RETUNEADC_PG_1 */
+	[18435] = 0x0000,     /* R18435 - RETUNEADC_PG_0 */
+
+	[18944] = 0x007F,     /* R18944 - ADCR_RETUNE_C1_1 */
+	[18945] = 0xFFFF,     /* R18945 - ADCR_RETUNE_C1_0 */
+	[18946] = 0x0000,     /* R18946 - ADCR_RETUNE_C2_1 */
+	[18947] = 0x0000,     /* R18947 - ADCR_RETUNE_C2_0 */
+	[18948] = 0x0000,     /* R18948 - ADCR_RETUNE_C3_1 */
+	[18949] = 0x0000,     /* R18949 - ADCR_RETUNE_C3_0 */
+	[18950] = 0x0000,     /* R18950 - ADCR_RETUNE_C4_1 */
+	[18951] = 0x0000,     /* R18951 - ADCR_RETUNE_C4_0 */
+	[18952] = 0x0000,     /* R18952 - ADCR_RETUNE_C5_1 */
+	[18953] = 0x0000,     /* R18953 - ADCR_RETUNE_C5_0 */
+	[18954] = 0x0000,     /* R18954 - ADCR_RETUNE_C6_1 */
+	[18955] = 0x0000,     /* R18955 - ADCR_RETUNE_C6_0 */
+	[18956] = 0x0000,     /* R18956 - ADCR_RETUNE_C7_1 */
+	[18957] = 0x0000,     /* R18957 - ADCR_RETUNE_C7_0 */
+	[18958] = 0x0000,     /* R18958 - ADCR_RETUNE_C8_1 */
+	[18959] = 0x0000,     /* R18959 - ADCR_RETUNE_C8_0 */
+	[18960] = 0x0000,     /* R18960 - ADCR_RETUNE_C9_1 */
+	[18961] = 0x0000,     /* R18961 - ADCR_RETUNE_C9_0 */
+	[18962] = 0x0000,     /* R18962 - ADCR_RETUNE_C10_1 */
+	[18963] = 0x0000,     /* R18963 - ADCR_RETUNE_C10_0 */
+	[18964] = 0x0000,     /* R18964 - ADCR_RETUNE_C11_1 */
+	[18965] = 0x0000,     /* R18965 - ADCR_RETUNE_C11_0 */
+	[18966] = 0x0000,     /* R18966 - ADCR_RETUNE_C12_1 */
+	[18967] = 0x0000,     /* R18967 - ADCR_RETUNE_C12_0 */
+	[18968] = 0x0000,     /* R18968 - ADCR_RETUNE_C13_1 */
+	[18969] = 0x0000,     /* R18969 - ADCR_RETUNE_C13_0 */
+	[18970] = 0x0000,     /* R18970 - ADCR_RETUNE_C14_1 */
+	[18971] = 0x0000,     /* R18971 - ADCR_RETUNE_C14_0 */
+	[18972] = 0x0000,     /* R18972 - ADCR_RETUNE_C15_1 */
+	[18973] = 0x0000,     /* R18973 - ADCR_RETUNE_C15_0 */
+	[18974] = 0x0000,     /* R18974 - ADCR_RETUNE_C16_1 */
+	[18975] = 0x0000,     /* R18975 - ADCR_RETUNE_C16_0 */
+	[18976] = 0x0000,     /* R18976 - ADCR_RETUNE_C17_1 */
+	[18977] = 0x0000,     /* R18977 - ADCR_RETUNE_C17_0 */
+	[18978] = 0x0000,     /* R18978 - ADCR_RETUNE_C18_1 */
+	[18979] = 0x0000,     /* R18979 - ADCR_RETUNE_C18_0 */
+	[18980] = 0x0000,     /* R18980 - ADCR_RETUNE_C19_1 */
+	[18981] = 0x0000,     /* R18981 - ADCR_RETUNE_C19_0 */
+	[18982] = 0x0000,     /* R18982 - ADCR_RETUNE_C20_1 */
+	[18983] = 0x0000,     /* R18983 - ADCR_RETUNE_C20_0 */
+	[18984] = 0x0000,     /* R18984 - ADCR_RETUNE_C21_1 */
+	[18985] = 0x0000,     /* R18985 - ADCR_RETUNE_C21_0 */
+	[18986] = 0x0000,     /* R18986 - ADCR_RETUNE_C22_1 */
+	[18987] = 0x0000,     /* R18987 - ADCR_RETUNE_C22_0 */
+	[18988] = 0x0000,     /* R18988 - ADCR_RETUNE_C23_1 */
+	[18989] = 0x0000,     /* R18989 - ADCR_RETUNE_C23_0 */
+	[18990] = 0x0000,     /* R18990 - ADCR_RETUNE_C24_1 */
+	[18991] = 0x0000,     /* R18991 - ADCR_RETUNE_C24_0 */
+	[18992] = 0x0000,     /* R18992 - ADCR_RETUNE_C25_1 */
+	[18993] = 0x0000,     /* R18993 - ADCR_RETUNE_C25_0 */
+	[18994] = 0x0000,     /* R18994 - ADCR_RETUNE_C26_1 */
+	[18995] = 0x0000,     /* R18995 - ADCR_RETUNE_C26_0 */
+	[18996] = 0x0000,     /* R18996 - ADCR_RETUNE_C27_1 */
+	[18997] = 0x0000,     /* R18997 - ADCR_RETUNE_C27_0 */
+	[18998] = 0x0000,     /* R18998 - ADCR_RETUNE_C28_1 */
+	[18999] = 0x0000,     /* R18999 - ADCR_RETUNE_C28_0 */
+	[19000] = 0x0000,     /* R19000 - ADCR_RETUNE_C29_1 */
+	[19001] = 0x0000,     /* R19001 - ADCR_RETUNE_C29_0 */
+	[19002] = 0x0000,     /* R19002 - ADCR_RETUNE_C30_1 */
+	[19003] = 0x0000,     /* R19003 - ADCR_RETUNE_C30_0 */
+	[19004] = 0x0000,     /* R19004 - ADCR_RETUNE_C31_1 */
+	[19005] = 0x0000,     /* R19005 - ADCR_RETUNE_C31_0 */
+	[19006] = 0x0000,     /* R19006 - ADCR_RETUNE_C32_1 */
+	[19007] = 0x0000,     /* R19007 - ADCR_RETUNE_C32_0 */
+
+	[19456] = 0x007F,     /* R19456 - DACL_RETUNE_C1_1 */
+	[19457] = 0xFFFF,     /* R19457 - DACL_RETUNE_C1_0 */
+	[19458] = 0x0000,     /* R19458 - DACL_RETUNE_C2_1 */
+	[19459] = 0x0000,     /* R19459 - DACL_RETUNE_C2_0 */
+	[19460] = 0x0000,     /* R19460 - DACL_RETUNE_C3_1 */
+	[19461] = 0x0000,     /* R19461 - DACL_RETUNE_C3_0 */
+	[19462] = 0x0000,     /* R19462 - DACL_RETUNE_C4_1 */
+	[19463] = 0x0000,     /* R19463 - DACL_RETUNE_C4_0 */
+	[19464] = 0x0000,     /* R19464 - DACL_RETUNE_C5_1 */
+	[19465] = 0x0000,     /* R19465 - DACL_RETUNE_C5_0 */
+	[19466] = 0x0000,     /* R19466 - DACL_RETUNE_C6_1 */
+	[19467] = 0x0000,     /* R19467 - DACL_RETUNE_C6_0 */
+	[19468] = 0x0000,     /* R19468 - DACL_RETUNE_C7_1 */
+	[19469] = 0x0000,     /* R19469 - DACL_RETUNE_C7_0 */
+	[19470] = 0x0000,     /* R19470 - DACL_RETUNE_C8_1 */
+	[19471] = 0x0000,     /* R19471 - DACL_RETUNE_C8_0 */
+	[19472] = 0x0000,     /* R19472 - DACL_RETUNE_C9_1 */
+	[19473] = 0x0000,     /* R19473 - DACL_RETUNE_C9_0 */
+	[19474] = 0x0000,     /* R19474 - DACL_RETUNE_C10_1 */
+	[19475] = 0x0000,     /* R19475 - DACL_RETUNE_C10_0 */
+	[19476] = 0x0000,     /* R19476 - DACL_RETUNE_C11_1 */
+	[19477] = 0x0000,     /* R19477 - DACL_RETUNE_C11_0 */
+	[19478] = 0x0000,     /* R19478 - DACL_RETUNE_C12_1 */
+	[19479] = 0x0000,     /* R19479 - DACL_RETUNE_C12_0 */
+	[19480] = 0x0000,     /* R19480 - DACL_RETUNE_C13_1 */
+	[19481] = 0x0000,     /* R19481 - DACL_RETUNE_C13_0 */
+	[19482] = 0x0000,     /* R19482 - DACL_RETUNE_C14_1 */
+	[19483] = 0x0000,     /* R19483 - DACL_RETUNE_C14_0 */
+	[19484] = 0x0000,     /* R19484 - DACL_RETUNE_C15_1 */
+	[19485] = 0x0000,     /* R19485 - DACL_RETUNE_C15_0 */
+	[19486] = 0x0000,     /* R19486 - DACL_RETUNE_C16_1 */
+	[19487] = 0x0000,     /* R19487 - DACL_RETUNE_C16_0 */
+	[19488] = 0x0000,     /* R19488 - DACL_RETUNE_C17_1 */
+	[19489] = 0x0000,     /* R19489 - DACL_RETUNE_C17_0 */
+	[19490] = 0x0000,     /* R19490 - DACL_RETUNE_C18_1 */
+	[19491] = 0x0000,     /* R19491 - DACL_RETUNE_C18_0 */
+	[19492] = 0x0000,     /* R19492 - DACL_RETUNE_C19_1 */
+	[19493] = 0x0000,     /* R19493 - DACL_RETUNE_C19_0 */
+	[19494] = 0x0000,     /* R19494 - DACL_RETUNE_C20_1 */
+	[19495] = 0x0000,     /* R19495 - DACL_RETUNE_C20_0 */
+	[19496] = 0x0000,     /* R19496 - DACL_RETUNE_C21_1 */
+	[19497] = 0x0000,     /* R19497 - DACL_RETUNE_C21_0 */
+	[19498] = 0x0000,     /* R19498 - DACL_RETUNE_C22_1 */
+	[19499] = 0x0000,     /* R19499 - DACL_RETUNE_C22_0 */
+	[19500] = 0x0000,     /* R19500 - DACL_RETUNE_C23_1 */
+	[19501] = 0x0000,     /* R19501 - DACL_RETUNE_C23_0 */
+	[19502] = 0x0000,     /* R19502 - DACL_RETUNE_C24_1 */
+	[19503] = 0x0000,     /* R19503 - DACL_RETUNE_C24_0 */
+	[19504] = 0x0000,     /* R19504 - DACL_RETUNE_C25_1 */
+	[19505] = 0x0000,     /* R19505 - DACL_RETUNE_C25_0 */
+	[19506] = 0x0000,     /* R19506 - DACL_RETUNE_C26_1 */
+	[19507] = 0x0000,     /* R19507 - DACL_RETUNE_C26_0 */
+	[19508] = 0x0000,     /* R19508 - DACL_RETUNE_C27_1 */
+	[19509] = 0x0000,     /* R19509 - DACL_RETUNE_C27_0 */
+	[19510] = 0x0000,     /* R19510 - DACL_RETUNE_C28_1 */
+	[19511] = 0x0000,     /* R19511 - DACL_RETUNE_C28_0 */
+	[19512] = 0x0000,     /* R19512 - DACL_RETUNE_C29_1 */
+	[19513] = 0x0000,     /* R19513 - DACL_RETUNE_C29_0 */
+	[19514] = 0x0000,     /* R19514 - DACL_RETUNE_C30_1 */
+	[19515] = 0x0000,     /* R19515 - DACL_RETUNE_C30_0 */
+	[19516] = 0x0000,     /* R19516 - DACL_RETUNE_C31_1 */
+	[19517] = 0x0000,     /* R19517 - DACL_RETUNE_C31_0 */
+	[19518] = 0x0000,     /* R19518 - DACL_RETUNE_C32_1 */
+	[19519] = 0x0000,     /* R19519 - DACL_RETUNE_C32_0 */
+
+	[19968] = 0x0020,     /* R19968 - RETUNEDAC_PG2_1 */
+	[19969] = 0x0000,     /* R19969 - RETUNEDAC_PG2_0 */
+	[19970] = 0x0040,     /* R19970 - RETUNEDAC_PG_1 */
+	[19971] = 0x0000,     /* R19971 - RETUNEDAC_PG_0 */
+
+	[20480] = 0x007F,     /* R20480 - DACR_RETUNE_C1_1 */
+	[20481] = 0xFFFF,     /* R20481 - DACR_RETUNE_C1_0 */
+	[20482] = 0x0000,     /* R20482 - DACR_RETUNE_C2_1 */
+	[20483] = 0x0000,     /* R20483 - DACR_RETUNE_C2_0 */
+	[20484] = 0x0000,     /* R20484 - DACR_RETUNE_C3_1 */
+	[20485] = 0x0000,     /* R20485 - DACR_RETUNE_C3_0 */
+	[20486] = 0x0000,     /* R20486 - DACR_RETUNE_C4_1 */
+	[20487] = 0x0000,     /* R20487 - DACR_RETUNE_C4_0 */
+	[20488] = 0x0000,     /* R20488 - DACR_RETUNE_C5_1 */
+	[20489] = 0x0000,     /* R20489 - DACR_RETUNE_C5_0 */
+	[20490] = 0x0000,     /* R20490 - DACR_RETUNE_C6_1 */
+	[20491] = 0x0000,     /* R20491 - DACR_RETUNE_C6_0 */
+	[20492] = 0x0000,     /* R20492 - DACR_RETUNE_C7_1 */
+	[20493] = 0x0000,     /* R20493 - DACR_RETUNE_C7_0 */
+	[20494] = 0x0000,     /* R20494 - DACR_RETUNE_C8_1 */
+	[20495] = 0x0000,     /* R20495 - DACR_RETUNE_C8_0 */
+	[20496] = 0x0000,     /* R20496 - DACR_RETUNE_C9_1 */
+	[20497] = 0x0000,     /* R20497 - DACR_RETUNE_C9_0 */
+	[20498] = 0x0000,     /* R20498 - DACR_RETUNE_C10_1 */
+	[20499] = 0x0000,     /* R20499 - DACR_RETUNE_C10_0 */
+	[20500] = 0x0000,     /* R20500 - DACR_RETUNE_C11_1 */
+	[20501] = 0x0000,     /* R20501 - DACR_RETUNE_C11_0 */
+	[20502] = 0x0000,     /* R20502 - DACR_RETUNE_C12_1 */
+	[20503] = 0x0000,     /* R20503 - DACR_RETUNE_C12_0 */
+	[20504] = 0x0000,     /* R20504 - DACR_RETUNE_C13_1 */
+	[20505] = 0x0000,     /* R20505 - DACR_RETUNE_C13_0 */
+	[20506] = 0x0000,     /* R20506 - DACR_RETUNE_C14_1 */
+	[20507] = 0x0000,     /* R20507 - DACR_RETUNE_C14_0 */
+	[20508] = 0x0000,     /* R20508 - DACR_RETUNE_C15_1 */
+	[20509] = 0x0000,     /* R20509 - DACR_RETUNE_C15_0 */
+	[20510] = 0x0000,     /* R20510 - DACR_RETUNE_C16_1 */
+	[20511] = 0x0000,     /* R20511 - DACR_RETUNE_C16_0 */
+	[20512] = 0x0000,     /* R20512 - DACR_RETUNE_C17_1 */
+	[20513] = 0x0000,     /* R20513 - DACR_RETUNE_C17_0 */
+	[20514] = 0x0000,     /* R20514 - DACR_RETUNE_C18_1 */
+	[20515] = 0x0000,     /* R20515 - DACR_RETUNE_C18_0 */
+	[20516] = 0x0000,     /* R20516 - DACR_RETUNE_C19_1 */
+	[20517] = 0x0000,     /* R20517 - DACR_RETUNE_C19_0 */
+	[20518] = 0x0000,     /* R20518 - DACR_RETUNE_C20_1 */
+	[20519] = 0x0000,     /* R20519 - DACR_RETUNE_C20_0 */
+	[20520] = 0x0000,     /* R20520 - DACR_RETUNE_C21_1 */
+	[20521] = 0x0000,     /* R20521 - DACR_RETUNE_C21_0 */
+	[20522] = 0x0000,     /* R20522 - DACR_RETUNE_C22_1 */
+	[20523] = 0x0000,     /* R20523 - DACR_RETUNE_C22_0 */
+	[20524] = 0x0000,     /* R20524 - DACR_RETUNE_C23_1 */
+	[20525] = 0x0000,     /* R20525 - DACR_RETUNE_C23_0 */
+	[20526] = 0x0000,     /* R20526 - DACR_RETUNE_C24_1 */
+	[20527] = 0x0000,     /* R20527 - DACR_RETUNE_C24_0 */
+	[20528] = 0x0000,     /* R20528 - DACR_RETUNE_C25_1 */
+	[20529] = 0x0000,     /* R20529 - DACR_RETUNE_C25_0 */
+	[20530] = 0x0000,     /* R20530 - DACR_RETUNE_C26_1 */
+	[20531] = 0x0000,     /* R20531 - DACR_RETUNE_C26_0 */
+	[20532] = 0x0000,     /* R20532 - DACR_RETUNE_C27_1 */
+	[20533] = 0x0000,     /* R20533 - DACR_RETUNE_C27_0 */
+	[20534] = 0x0000,     /* R20534 - DACR_RETUNE_C28_1 */
+	[20535] = 0x0000,     /* R20535 - DACR_RETUNE_C28_0 */
+	[20536] = 0x0000,     /* R20536 - DACR_RETUNE_C29_1 */
+	[20537] = 0x0000,     /* R20537 - DACR_RETUNE_C29_0 */
+	[20538] = 0x0000,     /* R20538 - DACR_RETUNE_C30_1 */
+	[20539] = 0x0000,     /* R20539 - DACR_RETUNE_C30_0 */
+	[20540] = 0x0000,     /* R20540 - DACR_RETUNE_C31_1 */
+	[20541] = 0x0000,     /* R20541 - DACR_RETUNE_C31_0 */
+	[20542] = 0x0000,     /* R20542 - DACR_RETUNE_C32_1 */
+	[20543] = 0x0000,     /* R20543 - DACR_RETUNE_C32_0 */
+
+	[20992] = 0x008C,     /* R20992 - VSS_XHD2_1 */
+	[20993] = 0x0200,     /* R20993 - VSS_XHD2_0 */
+	[20994] = 0x0035,     /* R20994 - VSS_XHD3_1 */
+	[20995] = 0x0700,     /* R20995 - VSS_XHD3_0 */
+	[20996] = 0x003A,     /* R20996 - VSS_XHN1_1 */
+	[20997] = 0x4100,     /* R20997 - VSS_XHN1_0 */
+	[20998] = 0x008B,     /* R20998 - VSS_XHN2_1 */
+	[20999] = 0x7D00,     /* R20999 - VSS_XHN2_0 */
+	[21000] = 0x003A,     /* R21000 - VSS_XHN3_1 */
+	[21001] = 0x4100,     /* R21001 - VSS_XHN3_0 */
+	[21002] = 0x008C,     /* R21002 - VSS_XLA_1 */
+	[21003] = 0xFEE8,     /* R21003 - VSS_XLA_0 */
+	[21004] = 0x0078,     /* R21004 - VSS_XLB_1 */
+	[21005] = 0x0000,     /* R21005 - VSS_XLB_0 */
+	[21006] = 0x003F,     /* R21006 - VSS_XLG_1 */
+	[21007] = 0xB260,     /* R21007 - VSS_XLG_0 */
+	[21008] = 0x002D,     /* R21008 - VSS_PG2_1 */
+	[21009] = 0x1818,     /* R21009 - VSS_PG2_0 */
+	[21010] = 0x0020,     /* R21010 - VSS_PG_1 */
+	[21011] = 0x0000,     /* R21011 - VSS_PG_0 */
+	[21012] = 0x00F1,     /* R21012 - VSS_XTD1_1 */
+	[21013] = 0x8340,     /* R21013 - VSS_XTD1_0 */
+	[21014] = 0x00FB,     /* R21014 - VSS_XTD2_1 */
+	[21015] = 0x8300,     /* R21015 - VSS_XTD2_0 */
+	[21016] = 0x00EE,     /* R21016 - VSS_XTD3_1 */
+	[21017] = 0xAEC0,     /* R21017 - VSS_XTD3_0 */
+	[21018] = 0x00FB,     /* R21018 - VSS_XTD4_1 */
+	[21019] = 0xAC40,     /* R21019 - VSS_XTD4_0 */
+	[21020] = 0x00F1,     /* R21020 - VSS_XTD5_1 */
+	[21021] = 0x7F80,     /* R21021 - VSS_XTD5_0 */
+	[21022] = 0x00F4,     /* R21022 - VSS_XTD6_1 */
+	[21023] = 0x3B40,     /* R21023 - VSS_XTD6_0 */
+	[21024] = 0x00F5,     /* R21024 - VSS_XTD7_1 */
+	[21025] = 0xFB00,     /* R21025 - VSS_XTD7_0 */
+	[21026] = 0x00EA,     /* R21026 - VSS_XTD8_1 */
+	[21027] = 0x10C0,     /* R21027 - VSS_XTD8_0 */
+	[21028] = 0x00FC,     /* R21028 - VSS_XTD9_1 */
+	[21029] = 0xC580,     /* R21029 - VSS_XTD9_0 */
+	[21030] = 0x00E2,     /* R21030 - VSS_XTD10_1 */
+	[21031] = 0x75C0,     /* R21031 - VSS_XTD10_0 */
+	[21032] = 0x0004,     /* R21032 - VSS_XTD11_1 */
+	[21033] = 0xB480,     /* R21033 - VSS_XTD11_0 */
+	[21034] = 0x00D4,     /* R21034 - VSS_XTD12_1 */
+	[21035] = 0xF980,     /* R21035 - VSS_XTD12_0 */
+	[21036] = 0x0004,     /* R21036 - VSS_XTD13_1 */
+	[21037] = 0x9140,     /* R21037 - VSS_XTD13_0 */
+	[21038] = 0x00D8,     /* R21038 - VSS_XTD14_1 */
+	[21039] = 0xA480,     /* R21039 - VSS_XTD14_0 */
+	[21040] = 0x0002,     /* R21040 - VSS_XTD15_1 */
+	[21041] = 0x3DC0,     /* R21041 - VSS_XTD15_0 */
+	[21042] = 0x00CF,     /* R21042 - VSS_XTD16_1 */
+	[21043] = 0x7A80,     /* R21043 - VSS_XTD16_0 */
+	[21044] = 0x00DC,     /* R21044 - VSS_XTD17_1 */
+	[21045] = 0x0600,     /* R21045 - VSS_XTD17_0 */
+	[21046] = 0x00F2,     /* R21046 - VSS_XTD18_1 */
+	[21047] = 0xDAC0,     /* R21047 - VSS_XTD18_0 */
+	[21048] = 0x00BA,     /* R21048 - VSS_XTD19_1 */
+	[21049] = 0xF340,     /* R21049 - VSS_XTD19_0 */
+	[21050] = 0x000A,     /* R21050 - VSS_XTD20_1 */
+	[21051] = 0x7940,     /* R21051 - VSS_XTD20_0 */
+	[21052] = 0x001C,     /* R21052 - VSS_XTD21_1 */
+	[21053] = 0x0680,     /* R21053 - VSS_XTD21_0 */
+	[21054] = 0x00FD,     /* R21054 - VSS_XTD22_1 */
+	[21055] = 0x2D00,     /* R21055 - VSS_XTD22_0 */
+	[21056] = 0x001C,     /* R21056 - VSS_XTD23_1 */
+	[21057] = 0xE840,     /* R21057 - VSS_XTD23_0 */
+	[21058] = 0x000D,     /* R21058 - VSS_XTD24_1 */
+	[21059] = 0xDC40,     /* R21059 - VSS_XTD24_0 */
+	[21060] = 0x00FC,     /* R21060 - VSS_XTD25_1 */
+	[21061] = 0x9D00,     /* R21061 - VSS_XTD25_0 */
+	[21062] = 0x0009,     /* R21062 - VSS_XTD26_1 */
+	[21063] = 0x5580,     /* R21063 - VSS_XTD26_0 */
+	[21064] = 0x00FE,     /* R21064 - VSS_XTD27_1 */
+	[21065] = 0x7E80,     /* R21065 - VSS_XTD27_0 */
+	[21066] = 0x000E,     /* R21066 - VSS_XTD28_1 */
+	[21067] = 0xAB40,     /* R21067 - VSS_XTD28_0 */
+	[21068] = 0x00F9,     /* R21068 - VSS_XTD29_1 */
+	[21069] = 0x9880,     /* R21069 - VSS_XTD29_0 */
+	[21070] = 0x0009,     /* R21070 - VSS_XTD30_1 */
+	[21071] = 0x87C0,     /* R21071 - VSS_XTD30_0 */
+	[21072] = 0x00FD,     /* R21072 - VSS_XTD31_1 */
+	[21073] = 0x2C40,     /* R21073 - VSS_XTD31_0 */
+	[21074] = 0x0009,     /* R21074 - VSS_XTD32_1 */
+	[21075] = 0x4800,     /* R21075 - VSS_XTD32_0 */
+	[21076] = 0x0003,     /* R21076 - VSS_XTS1_1 */
+	[21077] = 0x5F40,     /* R21077 - VSS_XTS1_0 */
+	[21078] = 0x0000,     /* R21078 - VSS_XTS2_1 */
+	[21079] = 0x8700,     /* R21079 - VSS_XTS2_0 */
+	[21080] = 0x00FA,     /* R21080 - VSS_XTS3_1 */
+	[21081] = 0xE4C0,     /* R21081 - VSS_XTS3_0 */
+	[21082] = 0x0000,     /* R21082 - VSS_XTS4_1 */
+	[21083] = 0x0B40,     /* R21083 - VSS_XTS4_0 */
+	[21084] = 0x0004,     /* R21084 - VSS_XTS5_1 */
+	[21085] = 0xE180,     /* R21085 - VSS_XTS5_0 */
+	[21086] = 0x0001,     /* R21086 - VSS_XTS6_1 */
+	[21087] = 0x1F40,     /* R21087 - VSS_XTS6_0 */
+	[21088] = 0x00F8,     /* R21088 - VSS_XTS7_1 */
+	[21089] = 0xB000,     /* R21089 - VSS_XTS7_0 */
+	[21090] = 0x00FB,     /* R21090 - VSS_XTS8_1 */
+	[21091] = 0xCBC0,     /* R21091 - VSS_XTS8_0 */
+	[21092] = 0x0004,     /* R21092 - VSS_XTS9_1 */
+	[21093] = 0xF380,     /* R21093 - VSS_XTS9_0 */
+	[21094] = 0x0007,     /* R21094 - VSS_XTS10_1 */
+	[21095] = 0xDF40,     /* R21095 - VSS_XTS10_0 */
+	[21096] = 0x00FF,     /* R21096 - VSS_XTS11_1 */
+	[21097] = 0x0700,     /* R21097 - VSS_XTS11_0 */
+	[21098] = 0x00EF,     /* R21098 - VSS_XTS12_1 */
+	[21099] = 0xD700,     /* R21099 - VSS_XTS12_0 */
+	[21100] = 0x00FB,     /* R21100 - VSS_XTS13_1 */
+	[21101] = 0xAF40,     /* R21101 - VSS_XTS13_0 */
+	[21102] = 0x0010,     /* R21102 - VSS_XTS14_1 */
+	[21103] = 0x8A80,     /* R21103 - VSS_XTS14_0 */
+	[21104] = 0x0011,     /* R21104 - VSS_XTS15_1 */
+	[21105] = 0x07C0,     /* R21105 - VSS_XTS15_0 */
+	[21106] = 0x00E0,     /* R21106 - VSS_XTS16_1 */
+	[21107] = 0x0800,     /* R21107 - VSS_XTS16_0 */
+	[21108] = 0x00D2,     /* R21108 - VSS_XTS17_1 */
+	[21109] = 0x7600,     /* R21109 - VSS_XTS17_0 */
+	[21110] = 0x0020,     /* R21110 - VSS_XTS18_1 */
+	[21111] = 0xCF40,     /* R21111 - VSS_XTS18_0 */
+	[21112] = 0x0030,     /* R21112 - VSS_XTS19_1 */
+	[21113] = 0x2340,     /* R21113 - VSS_XTS19_0 */
+	[21114] = 0x00FD,     /* R21114 - VSS_XTS20_1 */
+	[21115] = 0x69C0,     /* R21115 - VSS_XTS20_0 */
+	[21116] = 0x0028,     /* R21116 - VSS_XTS21_1 */
+	[21117] = 0x3500,     /* R21117 - VSS_XTS21_0 */
+	[21118] = 0x0006,     /* R21118 - VSS_XTS22_1 */
+	[21119] = 0x3300,     /* R21119 - VSS_XTS22_0 */
+	[21120] = 0x00D9,     /* R21120 - VSS_XTS23_1 */
+	[21121] = 0xF6C0,     /* R21121 - VSS_XTS23_0 */
+	[21122] = 0x00F3,     /* R21122 - VSS_XTS24_1 */
+	[21123] = 0x3340,     /* R21123 - VSS_XTS24_0 */
+	[21124] = 0x000F,     /* R21124 - VSS_XTS25_1 */
+	[21125] = 0x4200,     /* R21125 - VSS_XTS25_0 */
+	[21126] = 0x0004,     /* R21126 - VSS_XTS26_1 */
+	[21127] = 0x0C80,     /* R21127 - VSS_XTS26_0 */
+	[21128] = 0x00FB,     /* R21128 - VSS_XTS27_1 */
+	[21129] = 0x3F80,     /* R21129 - VSS_XTS27_0 */
+	[21130] = 0x00F7,     /* R21130 - VSS_XTS28_1 */
+	[21131] = 0x57C0,     /* R21131 - VSS_XTS28_0 */
+	[21132] = 0x0003,     /* R21132 - VSS_XTS29_1 */
+	[21133] = 0x5400,     /* R21133 - VSS_XTS29_0 */
+	[21134] = 0x0000,     /* R21134 - VSS_XTS30_1 */
+	[21135] = 0xC6C0,     /* R21135 - VSS_XTS30_0 */
+	[21136] = 0x0003,     /* R21136 - VSS_XTS31_1 */
+	[21137] = 0x12C0,     /* R21137 - VSS_XTS31_0 */
+	[21138] = 0x00FD,     /* R21138 - VSS_XTS32_1 */
+	[21139] = 0x8580,     /* R21139 - VSS_XTS32_0 */
+};
+
+static const struct wm8962_reg_access {
+	u16 read;
+	u16 write;
+	u16 vol;
+} wm8962_reg_access[WM8962_MAX_REGISTER + 1] = {
+	[0] = { 0x00FF, 0x01FF, 0x0000 }, /* R0     - Left Input volume */
+	[1] = { 0xFEFF, 0x01FF, 0xFFFF }, /* R1     - Right Input volume */
+	[2] = { 0x00FF, 0x01FF, 0x0000 }, /* R2     - HPOUTL volume */
+	[3] = { 0x00FF, 0x01FF, 0x0000 }, /* R3     - HPOUTR volume */
+	[4] = { 0x07FE, 0x07FE, 0xFFFF }, /* R4     - Clocking1 */
+	[5] = { 0x007F, 0x007F, 0x0000 }, /* R5     - ADC & DAC Control 1 */
+	[6] = { 0x37ED, 0x37ED, 0x0000 }, /* R6     - ADC & DAC Control 2 */
+	[7] = { 0x1FFF, 0x1FFF, 0x0000 }, /* R7     - Audio Interface 0 */
+	[8] = { 0x0FEF, 0x0FEF, 0xFFFF }, /* R8     - Clocking2 */
+	[9] = { 0x0B9F, 0x039F, 0x0000 }, /* R9     - Audio Interface 1 */
+	[10] = { 0x00FF, 0x01FF, 0x0000 }, /* R10    - Left DAC volume */
+	[11] = { 0x00FF, 0x01FF, 0x0000 }, /* R11    - Right DAC volume */
+	[14] = { 0x07FF, 0x07FF, 0x0000 }, /* R14    - Audio Interface 2 */
+	[15] = { 0xFFFF, 0xFFFF, 0xFFFF }, /* R15    - Software Reset */
+	[17] = { 0x07FF, 0x07FF, 0x0000 }, /* R17    - ALC1 */
+	[18] = { 0xF8FF, 0x00FF, 0xFFFF }, /* R18    - ALC2 */
+	[19] = { 0x1DFF, 0x1DFF, 0x0000 }, /* R19    - ALC3 */
+	[20] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20    - Noise Gate */
+	[21] = { 0x00FF, 0x01FF, 0x0000 }, /* R21    - Left ADC volume */
+	[22] = { 0x00FF, 0x01FF, 0x0000 }, /* R22    - Right ADC volume */
+	[23] = { 0x0161, 0x0161, 0x0000 }, /* R23    - Additional control(1) */
+	[24] = { 0x0008, 0x0008, 0x0000 }, /* R24    - Additional control(2) */
+	[25] = { 0x07FE, 0x07FE, 0x0000 }, /* R25    - Pwr Mgmt (1) */
+	[26] = { 0x01FB, 0x01FB, 0x0000 }, /* R26    - Pwr Mgmt (2) */
+	[27] = { 0x0017, 0x0017, 0x0000 }, /* R27    - Additional Control (3) */
+	[28] = { 0x001C, 0x001C, 0x0000 }, /* R28    - Anti-pop */
+
+	[30] = { 0xFFFE, 0xFFFE, 0x0000 }, /* R30    - Clocking 3 */
+	[31] = { 0x000F, 0x000F, 0x0000 }, /* R31    - Input mixer control (1) */
+	[32] = { 0x01FF, 0x01FF, 0x0000 }, /* R32    - Left input mixer volume */
+	[33] = { 0x01FF, 0x01FF, 0x0000 }, /* R33    - Right input mixer volume */
+	[34] = { 0x003F, 0x003F, 0x0000 }, /* R34    - Input mixer control (2) */
+	[35] = { 0x003F, 0x003F, 0x0000 }, /* R35    - Input bias control */
+	[37] = { 0x001F, 0x001F, 0x0000 }, /* R37    - Left input PGA control */
+	[38] = { 0x001F, 0x001F, 0x0000 }, /* R38    - Right input PGA control */
+	[40] = { 0x00FF, 0x01FF, 0x0000 }, /* R40    - SPKOUTL volume */
+	[41] = { 0x00FF, 0x01FF, 0x0000 }, /* R41    - SPKOUTR volume */
+
+	[47] = { 0x000F, 0x0000, 0x0000 }, /* R47    - Thermal Shutdown Status */
+	[48] = { 0x7EC7, 0x7E07, 0xFFFF }, /* R48    - Additional Control (4) */
+	[49] = { 0x00D3, 0x00D7, 0xFFFF }, /* R49    - Class D Control 1 */
+	[51] = { 0x0047, 0x0047, 0x0000 }, /* R51    - Class D Control 2 */
+	[56] = { 0x001E, 0x001E, 0x0000 }, /* R56    - Clocking 4 */
+	[57] = { 0x02FC, 0x02FC, 0x0000 }, /* R57    - DAC DSP Mixing (1) */
+	[58] = { 0x00FC, 0x00FC, 0x0000 }, /* R58    - DAC DSP Mixing (2) */
+	[60] = { 0x00CC, 0x00CC, 0x0000 }, /* R60    - DC Servo 0 */
+	[61] = { 0x00DD, 0x00DD, 0x0000 }, /* R61    - DC Servo 1 */
+	[64] = { 0x3F80, 0x3F80, 0x0000 }, /* R64    - DC Servo 4 */
+	[66] = { 0x0780, 0x0000, 0xFFFF }, /* R66    - DC Servo 6 */
+	[68] = { 0x0007, 0x0007, 0x0000 }, /* R68    - Analogue PGA Bias */
+	[69] = { 0x00FF, 0x00FF, 0x0000 }, /* R69    - Analogue HP 0 */
+	[71] = { 0x01FF, 0x01FF, 0x0000 }, /* R71    - Analogue HP 2 */
+	[72] = { 0x0001, 0x0001, 0x0000 }, /* R72    - Charge Pump 1 */
+	[82] = { 0x0001, 0x0001, 0x0000 }, /* R82    - Charge Pump B */
+	[87] = { 0x00A0, 0x00A0, 0x0000 }, /* R87    - Write Sequencer Control 1 */
+	[90] = { 0x007F, 0x01FF, 0x0000 }, /* R90    - Write Sequencer Control 2 */
+	[93] = { 0x03F9, 0x0000, 0x0000 }, /* R93    - Write Sequencer Control 3 */
+	[94] = { 0x0070, 0x0070, 0x0000 }, /* R94    - Control Interface */
+	[99] = { 0x000F, 0x000F, 0x0000 }, /* R99    - Mixer Enables */
+	[100] = { 0x00BF, 0x00BF, 0x0000 }, /* R100   - Headphone Mixer (1) */
+	[101] = { 0x00BF, 0x00BF, 0x0000 }, /* R101   - Headphone Mixer (2) */
+	[102] = { 0x01FF, 0x01FF, 0x0000 }, /* R102   - Headphone Mixer (3) */
+	[103] = { 0x01FF, 0x01FF, 0x0000 }, /* R103   - Headphone Mixer (4) */
+	[105] = { 0x00BF, 0x00BF, 0x0000 }, /* R105   - Speaker Mixer (1) */
+	[106] = { 0x00BF, 0x00BF, 0x0000 }, /* R106   - Speaker Mixer (2) */
+	[107] = { 0x01FF, 0x01FF, 0x0000 }, /* R107   - Speaker Mixer (3) */
+	[108] = { 0x01FF, 0x01FF, 0x0000 }, /* R108   - Speaker Mixer (4) */
+	[109] = { 0x00F0, 0x00F0, 0x0000 }, /* R109   - Speaker Mixer (5) */
+	[110] = { 0x00F7, 0x00F7, 0x0000 }, /* R110   - Beep Generator (1) */
+	[115] = { 0x001F, 0x001F, 0x0000 }, /* R115   - Oscillator Trim (3) */
+	[116] = { 0x001F, 0x001F, 0x0000 }, /* R116   - Oscillator Trim (4) */
+	[119] = { 0x00FF, 0x00FF, 0x0000 }, /* R119   - Oscillator Trim (7) */
+	[124] = { 0x0079, 0x0079, 0x0000 }, /* R124   - Analogue Clocking1 */
+	[125] = { 0x00DF, 0x00DF, 0x0000 }, /* R125   - Analogue Clocking2 */
+	[126] = { 0x000D, 0x000D, 0x0000 }, /* R126   - Analogue Clocking3 */
+	[127] = { 0x0000, 0xFFFF, 0x0000 }, /* R127   - PLL Software Reset */
+	[129] = { 0x00B0, 0x00B0, 0x0000 }, /* R129   - PLL2 */
+	[131] = { 0x0003, 0x0003, 0x0000 }, /* R131   - PLL 4 */
+	[136] = { 0x005F, 0x005F, 0x0000 }, /* R136   - PLL 9 */
+	[137] = { 0x00FF, 0x00FF, 0x0000 }, /* R137   - PLL 10 */
+	[138] = { 0x00FF, 0x00FF, 0x0000 }, /* R138   - PLL 11 */
+	[139] = { 0x00FF, 0x00FF, 0x0000 }, /* R139   - PLL 12 */
+	[140] = { 0x005F, 0x005F, 0x0000 }, /* R140   - PLL 13 */
+	[141] = { 0x00FF, 0x00FF, 0x0000 }, /* R141   - PLL 14 */
+	[142] = { 0x00FF, 0x00FF, 0x0000 }, /* R142   - PLL 15 */
+	[143] = { 0x00FF, 0x00FF, 0x0000 }, /* R143   - PLL 16 */
+	[155] = { 0x0067, 0x0067, 0x0000 }, /* R155   - FLL Control (1) */
+	[156] = { 0x01FB, 0x01FB, 0x0000 }, /* R156   - FLL Control (2) */
+	[157] = { 0x0007, 0x0007, 0x0000 }, /* R157   - FLL Control (3) */
+	[159] = { 0x007F, 0x007F, 0x0000 }, /* R159   - FLL Control (5) */
+	[160] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R160   - FLL Control (6) */
+	[161] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R161   - FLL Control (7) */
+	[162] = { 0x03FF, 0x03FF, 0x0000 }, /* R162   - FLL Control (8) */
+	[252] = { 0x0005, 0x0005, 0x0000 }, /* R252   - General test 1 */
+	[256] = { 0x000F, 0x000F, 0x0000 }, /* R256   - DF1 */
+	[257] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R257   - DF2 */
+	[258] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R258   - DF3 */
+	[259] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R259   - DF4 */
+	[260] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R260   - DF5 */
+	[261] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R261   - DF6 */
+	[262] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R262   - DF7 */
+	[264] = { 0x0003, 0x0003, 0x0000 }, /* R264   - LHPF1 */
+	[265] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R265   - LHPF2 */
+	[268] = { 0x0077, 0x0077, 0x0000 }, /* R268   - THREED1 */
+	[269] = { 0xFFFC, 0xFFFC, 0x0000 }, /* R269   - THREED2 */
+	[270] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R270   - THREED3 */
+	[271] = { 0xFFFC, 0xFFFC, 0x0000 }, /* R271   - THREED4 */
+	[276] = { 0x7FFF, 0x7FFF, 0x0000 }, /* R276   - DRC 1 */
+	[277] = { 0x1FFF, 0x1FFF, 0x0000 }, /* R277   - DRC 2 */
+	[278] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R278   - DRC 3 */
+	[279] = { 0x07FF, 0x07FF, 0x0000 }, /* R279   - DRC 4 */
+	[280] = { 0x03FF, 0x03FF, 0x0000 }, /* R280   - DRC 5 */
+	[285] = { 0x0003, 0x0003, 0x0000 }, /* R285   - Tloopback */
+	[335] = { 0x0007, 0x0007, 0x0000 }, /* R335   - EQ1 */
+	[336] = { 0xFFFE, 0xFFFE, 0x0000 }, /* R336   - EQ2 */
+	[337] = { 0xFFC0, 0xFFC0, 0x0000 }, /* R337   - EQ3 */
+	[338] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R338   - EQ4 */
+	[339] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R339   - EQ5 */
+	[340] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R340   - EQ6 */
+	[341] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R341   - EQ7 */
+	[342] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R342   - EQ8 */
+	[343] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R343   - EQ9 */
+	[344] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R344   - EQ10 */
+	[345] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R345   - EQ11 */
+	[346] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R346   - EQ12 */
+	[347] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R347   - EQ13 */
+	[348] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R348   - EQ14 */
+	[349] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R349   - EQ15 */
+	[350] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R350   - EQ16 */
+	[351] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R351   - EQ17 */
+	[352] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R352   - EQ18 */
+	[353] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R353   - EQ19 */
+	[354] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R354   - EQ20 */
+	[355] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R355   - EQ21 */
+	[356] = { 0xFFFE, 0xFFFE, 0x0000 }, /* R356   - EQ22 */
+	[357] = { 0xFFC0, 0xFFC0, 0x0000 }, /* R357   - EQ23 */
+	[358] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R358   - EQ24 */
+	[359] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R359   - EQ25 */
+	[360] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R360   - EQ26 */
+	[361] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R361   - EQ27 */
+	[362] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R362   - EQ28 */
+	[363] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R363   - EQ29 */
+	[364] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R364   - EQ30 */
+	[365] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R365   - EQ31 */
+	[366] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R366   - EQ32 */
+	[367] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R367   - EQ33 */
+	[368] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R368   - EQ34 */
+	[369] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R369   - EQ35 */
+	[370] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R370   - EQ36 */
+	[371] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R371   - EQ37 */
+	[372] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R372   - EQ38 */
+	[373] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R373   - EQ39 */
+	[374] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R374   - EQ40 */
+	[375] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R375   - EQ41 */
+	[513] = { 0x045F, 0x045F, 0x0000 }, /* R513   - GPIO 2 */
+	[514] = { 0x045F, 0x045F, 0x0000 }, /* R514   - GPIO 3 */
+	[516] = { 0xE75F, 0xE75F, 0x0000 }, /* R516   - GPIO 5 */
+	[517] = { 0xE75F, 0xE75F, 0x0000 }, /* R517   - GPIO 6 */
+	[560] = { 0x0030, 0x0030, 0xFFFF }, /* R560   - Interrupt Status 1 */
+	[561] = { 0xFFED, 0xFFED, 0xFFFF }, /* R561   - Interrupt Status 2 */
+	[568] = { 0x0030, 0x0030, 0x0000 }, /* R568   - Interrupt Status 1 Mask */
+	[569] = { 0xFFED, 0xFFED, 0x0000 }, /* R569   - Interrupt Status 2 Mask */
+	[576] = { 0x0001, 0x0001, 0x0000 }, /* R576   - Interrupt Control */
+	[584] = { 0x002D, 0x002D, 0x0000 }, /* R584   - IRQ Debounce */
+	[586] = { 0xC000, 0xC000, 0x0000 }, /* R586   -  MICINT Source Pol */
+	[768] = { 0x0001, 0x0001, 0x0000 }, /* R768   - DSP2 Power Management */
+	[1037] = { 0x0000, 0x003F, 0x0000 }, /* R1037  - DSP2_ExecControl */
+	[4096] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4096  - Write Sequencer 0 */
+	[4097] = { 0x00FF, 0x00FF, 0x0000 }, /* R4097  - Write Sequencer 1 */
+	[4098] = { 0x070F, 0x070F, 0x0000 }, /* R4098  - Write Sequencer 2 */
+	[4099] = { 0x010F, 0x010F, 0x0000 }, /* R4099  - Write Sequencer 3 */
+	[4100] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4100  - Write Sequencer 4 */
+	[4101] = { 0x00FF, 0x00FF, 0x0000 }, /* R4101  - Write Sequencer 5 */
+	[4102] = { 0x070F, 0x070F, 0x0000 }, /* R4102  - Write Sequencer 6 */
+	[4103] = { 0x010F, 0x010F, 0x0000 }, /* R4103  - Write Sequencer 7 */
+	[4104] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4104  - Write Sequencer 8 */
+	[4105] = { 0x00FF, 0x00FF, 0x0000 }, /* R4105  - Write Sequencer 9 */
+	[4106] = { 0x070F, 0x070F, 0x0000 }, /* R4106  - Write Sequencer 10 */
+	[4107] = { 0x010F, 0x010F, 0x0000 }, /* R4107  - Write Sequencer 11 */
+	[4108] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4108  - Write Sequencer 12 */
+	[4109] = { 0x00FF, 0x00FF, 0x0000 }, /* R4109  - Write Sequencer 13 */
+	[4110] = { 0x070F, 0x070F, 0x0000 }, /* R4110  - Write Sequencer 14 */
+	[4111] = { 0x010F, 0x010F, 0x0000 }, /* R4111  - Write Sequencer 15 */
+	[4112] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4112  - Write Sequencer 16 */
+	[4113] = { 0x00FF, 0x00FF, 0x0000 }, /* R4113  - Write Sequencer 17 */
+	[4114] = { 0x070F, 0x070F, 0x0000 }, /* R4114  - Write Sequencer 18 */
+	[4115] = { 0x010F, 0x010F, 0x0000 }, /* R4115  - Write Sequencer 19 */
+	[4116] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4116  - Write Sequencer 20 */
+	[4117] = { 0x00FF, 0x00FF, 0x0000 }, /* R4117  - Write Sequencer 21 */
+	[4118] = { 0x070F, 0x070F, 0x0000 }, /* R4118  - Write Sequencer 22 */
+	[4119] = { 0x010F, 0x010F, 0x0000 }, /* R4119  - Write Sequencer 23 */
+	[4120] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4120  - Write Sequencer 24 */
+	[4121] = { 0x00FF, 0x00FF, 0x0000 }, /* R4121  - Write Sequencer 25 */
+	[4122] = { 0x070F, 0x070F, 0x0000 }, /* R4122  - Write Sequencer 26 */
+	[4123] = { 0x010F, 0x010F, 0x0000 }, /* R4123  - Write Sequencer 27 */
+	[4124] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4124  - Write Sequencer 28 */
+	[4125] = { 0x00FF, 0x00FF, 0x0000 }, /* R4125  - Write Sequencer 29 */
+	[4126] = { 0x070F, 0x070F, 0x0000 }, /* R4126  - Write Sequencer 30 */
+	[4127] = { 0x010F, 0x010F, 0x0000 }, /* R4127  - Write Sequencer 31 */
+	[4128] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4128  - Write Sequencer 32 */
+	[4129] = { 0x00FF, 0x00FF, 0x0000 }, /* R4129  - Write Sequencer 33 */
+	[4130] = { 0x070F, 0x070F, 0x0000 }, /* R4130  - Write Sequencer 34 */
+	[4131] = { 0x010F, 0x010F, 0x0000 }, /* R4131  - Write Sequencer 35 */
+	[4132] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4132  - Write Sequencer 36 */
+	[4133] = { 0x00FF, 0x00FF, 0x0000 }, /* R4133  - Write Sequencer 37 */
+	[4134] = { 0x070F, 0x070F, 0x0000 }, /* R4134  - Write Sequencer 38 */
+	[4135] = { 0x010F, 0x010F, 0x0000 }, /* R4135  - Write Sequencer 39 */
+	[4136] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4136  - Write Sequencer 40 */
+	[4137] = { 0x00FF, 0x00FF, 0x0000 }, /* R4137  - Write Sequencer 41 */
+	[4138] = { 0x070F, 0x070F, 0x0000 }, /* R4138  - Write Sequencer 42 */
+	[4139] = { 0x010F, 0x010F, 0x0000 }, /* R4139  - Write Sequencer 43 */
+	[4140] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4140  - Write Sequencer 44 */
+	[4141] = { 0x00FF, 0x00FF, 0x0000 }, /* R4141  - Write Sequencer 45 */
+	[4142] = { 0x070F, 0x070F, 0x0000 }, /* R4142  - Write Sequencer 46 */
+	[4143] = { 0x010F, 0x010F, 0x0000 }, /* R4143  - Write Sequencer 47 */
+	[4144] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4144  - Write Sequencer 48 */
+	[4145] = { 0x00FF, 0x00FF, 0x0000 }, /* R4145  - Write Sequencer 49 */
+	[4146] = { 0x070F, 0x070F, 0x0000 }, /* R4146  - Write Sequencer 50 */
+	[4147] = { 0x010F, 0x010F, 0x0000 }, /* R4147  - Write Sequencer 51 */
+	[4148] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4148  - Write Sequencer 52 */
+	[4149] = { 0x00FF, 0x00FF, 0x0000 }, /* R4149  - Write Sequencer 53 */
+	[4150] = { 0x070F, 0x070F, 0x0000 }, /* R4150  - Write Sequencer 54 */
+	[4151] = { 0x010F, 0x010F, 0x0000 }, /* R4151  - Write Sequencer 55 */
+	[4152] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4152  - Write Sequencer 56 */
+	[4153] = { 0x00FF, 0x00FF, 0x0000 }, /* R4153  - Write Sequencer 57 */
+	[4154] = { 0x070F, 0x070F, 0x0000 }, /* R4154  - Write Sequencer 58 */
+	[4155] = { 0x010F, 0x010F, 0x0000 }, /* R4155  - Write Sequencer 59 */
+	[4156] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4156  - Write Sequencer 60 */
+	[4157] = { 0x00FF, 0x00FF, 0x0000 }, /* R4157  - Write Sequencer 61 */
+	[4158] = { 0x070F, 0x070F, 0x0000 }, /* R4158  - Write Sequencer 62 */
+	[4159] = { 0x010F, 0x010F, 0x0000 }, /* R4159  - Write Sequencer 63 */
+	[4160] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4160  - Write Sequencer 64 */
+	[4161] = { 0x00FF, 0x00FF, 0x0000 }, /* R4161  - Write Sequencer 65 */
+	[4162] = { 0x070F, 0x070F, 0x0000 }, /* R4162  - Write Sequencer 66 */
+	[4163] = { 0x010F, 0x010F, 0x0000 }, /* R4163  - Write Sequencer 67 */
+	[4164] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4164  - Write Sequencer 68 */
+	[4165] = { 0x00FF, 0x00FF, 0x0000 }, /* R4165  - Write Sequencer 69 */
+	[4166] = { 0x070F, 0x070F, 0x0000 }, /* R4166  - Write Sequencer 70 */
+	[4167] = { 0x010F, 0x010F, 0x0000 }, /* R4167  - Write Sequencer 71 */
+	[4168] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4168  - Write Sequencer 72 */
+	[4169] = { 0x00FF, 0x00FF, 0x0000 }, /* R4169  - Write Sequencer 73 */
+	[4170] = { 0x070F, 0x070F, 0x0000 }, /* R4170  - Write Sequencer 74 */
+	[4171] = { 0x010F, 0x010F, 0x0000 }, /* R4171  - Write Sequencer 75 */
+	[4172] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4172  - Write Sequencer 76 */
+	[4173] = { 0x00FF, 0x00FF, 0x0000 }, /* R4173  - Write Sequencer 77 */
+	[4174] = { 0x070F, 0x070F, 0x0000 }, /* R4174  - Write Sequencer 78 */
+	[4175] = { 0x010F, 0x010F, 0x0000 }, /* R4175  - Write Sequencer 79 */
+	[4176] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4176  - Write Sequencer 80 */
+	[4177] = { 0x00FF, 0x00FF, 0x0000 }, /* R4177  - Write Sequencer 81 */
+	[4178] = { 0x070F, 0x070F, 0x0000 }, /* R4178  - Write Sequencer 82 */
+	[4179] = { 0x010F, 0x010F, 0x0000 }, /* R4179  - Write Sequencer 83 */
+	[4180] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4180  - Write Sequencer 84 */
+	[4181] = { 0x00FF, 0x00FF, 0x0000 }, /* R4181  - Write Sequencer 85 */
+	[4182] = { 0x070F, 0x070F, 0x0000 }, /* R4182  - Write Sequencer 86 */
+	[4183] = { 0x010F, 0x010F, 0x0000 }, /* R4183  - Write Sequencer 87 */
+	[4184] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4184  - Write Sequencer 88 */
+	[4185] = { 0x00FF, 0x00FF, 0x0000 }, /* R4185  - Write Sequencer 89 */
+	[4186] = { 0x070F, 0x070F, 0x0000 }, /* R4186  - Write Sequencer 90 */
+	[4187] = { 0x010F, 0x010F, 0x0000 }, /* R4187  - Write Sequencer 91 */
+	[4188] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4188  - Write Sequencer 92 */
+	[4189] = { 0x00FF, 0x00FF, 0x0000 }, /* R4189  - Write Sequencer 93 */
+	[4190] = { 0x070F, 0x070F, 0x0000 }, /* R4190  - Write Sequencer 94 */
+	[4191] = { 0x010F, 0x010F, 0x0000 }, /* R4191  - Write Sequencer 95 */
+	[4192] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4192  - Write Sequencer 96 */
+	[4193] = { 0x00FF, 0x00FF, 0x0000 }, /* R4193  - Write Sequencer 97 */
+	[4194] = { 0x070F, 0x070F, 0x0000 }, /* R4194  - Write Sequencer 98 */
+	[4195] = { 0x010F, 0x010F, 0x0000 }, /* R4195  - Write Sequencer 99 */
+	[4196] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4196  - Write Sequencer 100 */
+	[4197] = { 0x00FF, 0x00FF, 0x0000 }, /* R4197  - Write Sequencer 101 */
+	[4198] = { 0x070F, 0x070F, 0x0000 }, /* R4198  - Write Sequencer 102 */
+	[4199] = { 0x010F, 0x010F, 0x0000 }, /* R4199  - Write Sequencer 103 */
+	[4200] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4200  - Write Sequencer 104 */
+	[4201] = { 0x00FF, 0x00FF, 0x0000 }, /* R4201  - Write Sequencer 105 */
+	[4202] = { 0x070F, 0x070F, 0x0000 }, /* R4202  - Write Sequencer 106 */
+	[4203] = { 0x010F, 0x010F, 0x0000 }, /* R4203  - Write Sequencer 107 */
+	[4204] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4204  - Write Sequencer 108 */
+	[4205] = { 0x00FF, 0x00FF, 0x0000 }, /* R4205  - Write Sequencer 109 */
+	[4206] = { 0x070F, 0x070F, 0x0000 }, /* R4206  - Write Sequencer 110 */
+	[4207] = { 0x010F, 0x010F, 0x0000 }, /* R4207  - Write Sequencer 111 */
+	[4208] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4208  - Write Sequencer 112 */
+	[4209] = { 0x00FF, 0x00FF, 0x0000 }, /* R4209  - Write Sequencer 113 */
+	[4210] = { 0x070F, 0x070F, 0x0000 }, /* R4210  - Write Sequencer 114 */
+	[4211] = { 0x010F, 0x010F, 0x0000 }, /* R4211  - Write Sequencer 115 */
+	[4212] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4212  - Write Sequencer 116 */
+	[4213] = { 0x00FF, 0x00FF, 0x0000 }, /* R4213  - Write Sequencer 117 */
+	[4214] = { 0x070F, 0x070F, 0x0000 }, /* R4214  - Write Sequencer 118 */
+	[4215] = { 0x010F, 0x010F, 0x0000 }, /* R4215  - Write Sequencer 119 */
+	[4216] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4216  - Write Sequencer 120 */
+	[4217] = { 0x00FF, 0x00FF, 0x0000 }, /* R4217  - Write Sequencer 121 */
+	[4218] = { 0x070F, 0x070F, 0x0000 }, /* R4218  - Write Sequencer 122 */
+	[4219] = { 0x010F, 0x010F, 0x0000 }, /* R4219  - Write Sequencer 123 */
+	[4220] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4220  - Write Sequencer 124 */
+	[4221] = { 0x00FF, 0x00FF, 0x0000 }, /* R4221  - Write Sequencer 125 */
+	[4222] = { 0x070F, 0x070F, 0x0000 }, /* R4222  - Write Sequencer 126 */
+	[4223] = { 0x010F, 0x010F, 0x0000 }, /* R4223  - Write Sequencer 127 */
+	[4224] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4224  - Write Sequencer 128 */
+	[4225] = { 0x00FF, 0x00FF, 0x0000 }, /* R4225  - Write Sequencer 129 */
+	[4226] = { 0x070F, 0x070F, 0x0000 }, /* R4226  - Write Sequencer 130 */
+	[4227] = { 0x010F, 0x010F, 0x0000 }, /* R4227  - Write Sequencer 131 */
+	[4228] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4228  - Write Sequencer 132 */
+	[4229] = { 0x00FF, 0x00FF, 0x0000 }, /* R4229  - Write Sequencer 133 */
+	[4230] = { 0x070F, 0x070F, 0x0000 }, /* R4230  - Write Sequencer 134 */
+	[4231] = { 0x010F, 0x010F, 0x0000 }, /* R4231  - Write Sequencer 135 */
+	[4232] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4232  - Write Sequencer 136 */
+	[4233] = { 0x00FF, 0x00FF, 0x0000 }, /* R4233  - Write Sequencer 137 */
+	[4234] = { 0x070F, 0x070F, 0x0000 }, /* R4234  - Write Sequencer 138 */
+	[4235] = { 0x010F, 0x010F, 0x0000 }, /* R4235  - Write Sequencer 139 */
+	[4236] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4236  - Write Sequencer 140 */
+	[4237] = { 0x00FF, 0x00FF, 0x0000 }, /* R4237  - Write Sequencer 141 */
+	[4238] = { 0x070F, 0x070F, 0x0000 }, /* R4238  - Write Sequencer 142 */
+	[4239] = { 0x010F, 0x010F, 0x0000 }, /* R4239  - Write Sequencer 143 */
+	[4240] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4240  - Write Sequencer 144 */
+	[4241] = { 0x00FF, 0x00FF, 0x0000 }, /* R4241  - Write Sequencer 145 */
+	[4242] = { 0x070F, 0x070F, 0x0000 }, /* R4242  - Write Sequencer 146 */
+	[4243] = { 0x010F, 0x010F, 0x0000 }, /* R4243  - Write Sequencer 147 */
+	[4244] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4244  - Write Sequencer 148 */
+	[4245] = { 0x00FF, 0x00FF, 0x0000 }, /* R4245  - Write Sequencer 149 */
+	[4246] = { 0x070F, 0x070F, 0x0000 }, /* R4246  - Write Sequencer 150 */
+	[4247] = { 0x010F, 0x010F, 0x0000 }, /* R4247  - Write Sequencer 151 */
+	[4248] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4248  - Write Sequencer 152 */
+	[4249] = { 0x00FF, 0x00FF, 0x0000 }, /* R4249  - Write Sequencer 153 */
+	[4250] = { 0x070F, 0x070F, 0x0000 }, /* R4250  - Write Sequencer 154 */
+	[4251] = { 0x010F, 0x010F, 0x0000 }, /* R4251  - Write Sequencer 155 */
+	[4252] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4252  - Write Sequencer 156 */
+	[4253] = { 0x00FF, 0x00FF, 0x0000 }, /* R4253  - Write Sequencer 157 */
+	[4254] = { 0x070F, 0x070F, 0x0000 }, /* R4254  - Write Sequencer 158 */
+	[4255] = { 0x010F, 0x010F, 0x0000 }, /* R4255  - Write Sequencer 159 */
+	[4256] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4256  - Write Sequencer 160 */
+	[4257] = { 0x00FF, 0x00FF, 0x0000 }, /* R4257  - Write Sequencer 161 */
+	[4258] = { 0x070F, 0x070F, 0x0000 }, /* R4258  - Write Sequencer 162 */
+	[4259] = { 0x010F, 0x010F, 0x0000 }, /* R4259  - Write Sequencer 163 */
+	[4260] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4260  - Write Sequencer 164 */
+	[4261] = { 0x00FF, 0x00FF, 0x0000 }, /* R4261  - Write Sequencer 165 */
+	[4262] = { 0x070F, 0x070F, 0x0000 }, /* R4262  - Write Sequencer 166 */
+	[4263] = { 0x010F, 0x010F, 0x0000 }, /* R4263  - Write Sequencer 167 */
+	[4264] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4264  - Write Sequencer 168 */
+	[4265] = { 0x00FF, 0x00FF, 0x0000 }, /* R4265  - Write Sequencer 169 */
+	[4266] = { 0x070F, 0x070F, 0x0000 }, /* R4266  - Write Sequencer 170 */
+	[4267] = { 0x010F, 0x010F, 0x0000 }, /* R4267  - Write Sequencer 171 */
+	[4268] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4268  - Write Sequencer 172 */
+	[4269] = { 0x00FF, 0x00FF, 0x0000 }, /* R4269  - Write Sequencer 173 */
+	[4270] = { 0x070F, 0x070F, 0x0000 }, /* R4270  - Write Sequencer 174 */
+	[4271] = { 0x010F, 0x010F, 0x0000 }, /* R4271  - Write Sequencer 175 */
+	[4272] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4272  - Write Sequencer 176 */
+	[4273] = { 0x00FF, 0x00FF, 0x0000 }, /* R4273  - Write Sequencer 177 */
+	[4274] = { 0x070F, 0x070F, 0x0000 }, /* R4274  - Write Sequencer 178 */
+	[4275] = { 0x010F, 0x010F, 0x0000 }, /* R4275  - Write Sequencer 179 */
+	[4276] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4276  - Write Sequencer 180 */
+	[4277] = { 0x00FF, 0x00FF, 0x0000 }, /* R4277  - Write Sequencer 181 */
+	[4278] = { 0x070F, 0x070F, 0x0000 }, /* R4278  - Write Sequencer 182 */
+	[4279] = { 0x010F, 0x010F, 0x0000 }, /* R4279  - Write Sequencer 183 */
+	[4280] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4280  - Write Sequencer 184 */
+	[4281] = { 0x00FF, 0x00FF, 0x0000 }, /* R4281  - Write Sequencer 185 */
+	[4282] = { 0x070F, 0x070F, 0x0000 }, /* R4282  - Write Sequencer 186 */
+	[4283] = { 0x010F, 0x010F, 0x0000 }, /* R4283  - Write Sequencer 187 */
+	[4284] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4284  - Write Sequencer 188 */
+	[4285] = { 0x00FF, 0x00FF, 0x0000 }, /* R4285  - Write Sequencer 189 */
+	[4286] = { 0x070F, 0x070F, 0x0000 }, /* R4286  - Write Sequencer 190 */
+	[4287] = { 0x010F, 0x010F, 0x0000 }, /* R4287  - Write Sequencer 191 */
+	[4288] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4288  - Write Sequencer 192 */
+	[4289] = { 0x00FF, 0x00FF, 0x0000 }, /* R4289  - Write Sequencer 193 */
+	[4290] = { 0x070F, 0x070F, 0x0000 }, /* R4290  - Write Sequencer 194 */
+	[4291] = { 0x010F, 0x010F, 0x0000 }, /* R4291  - Write Sequencer 195 */
+	[4292] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4292  - Write Sequencer 196 */
+	[4293] = { 0x00FF, 0x00FF, 0x0000 }, /* R4293  - Write Sequencer 197 */
+	[4294] = { 0x070F, 0x070F, 0x0000 }, /* R4294  - Write Sequencer 198 */
+	[4295] = { 0x010F, 0x010F, 0x0000 }, /* R4295  - Write Sequencer 199 */
+	[4296] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4296  - Write Sequencer 200 */
+	[4297] = { 0x00FF, 0x00FF, 0x0000 }, /* R4297  - Write Sequencer 201 */
+	[4298] = { 0x070F, 0x070F, 0x0000 }, /* R4298  - Write Sequencer 202 */
+	[4299] = { 0x010F, 0x010F, 0x0000 }, /* R4299  - Write Sequencer 203 */
+	[4300] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4300  - Write Sequencer 204 */
+	[4301] = { 0x00FF, 0x00FF, 0x0000 }, /* R4301  - Write Sequencer 205 */
+	[4302] = { 0x070F, 0x070F, 0x0000 }, /* R4302  - Write Sequencer 206 */
+	[4303] = { 0x010F, 0x010F, 0x0000 }, /* R4303  - Write Sequencer 207 */
+	[4304] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4304  - Write Sequencer 208 */
+	[4305] = { 0x00FF, 0x00FF, 0x0000 }, /* R4305  - Write Sequencer 209 */
+	[4306] = { 0x070F, 0x070F, 0x0000 }, /* R4306  - Write Sequencer 210 */
+	[4307] = { 0x010F, 0x010F, 0x0000 }, /* R4307  - Write Sequencer 211 */
+	[4308] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4308  - Write Sequencer 212 */
+	[4309] = { 0x00FF, 0x00FF, 0x0000 }, /* R4309  - Write Sequencer 213 */
+	[4310] = { 0x070F, 0x070F, 0x0000 }, /* R4310  - Write Sequencer 214 */
+	[4311] = { 0x010F, 0x010F, 0x0000 }, /* R4311  - Write Sequencer 215 */
+	[4312] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4312  - Write Sequencer 216 */
+	[4313] = { 0x00FF, 0x00FF, 0x0000 }, /* R4313  - Write Sequencer 217 */
+	[4314] = { 0x070F, 0x070F, 0x0000 }, /* R4314  - Write Sequencer 218 */
+	[4315] = { 0x010F, 0x010F, 0x0000 }, /* R4315  - Write Sequencer 219 */
+	[4316] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4316  - Write Sequencer 220 */
+	[4317] = { 0x00FF, 0x00FF, 0x0000 }, /* R4317  - Write Sequencer 221 */
+	[4318] = { 0x070F, 0x070F, 0x0000 }, /* R4318  - Write Sequencer 222 */
+	[4319] = { 0x010F, 0x010F, 0x0000 }, /* R4319  - Write Sequencer 223 */
+	[4320] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4320  - Write Sequencer 224 */
+	[4321] = { 0x00FF, 0x00FF, 0x0000 }, /* R4321  - Write Sequencer 225 */
+	[4322] = { 0x070F, 0x070F, 0x0000 }, /* R4322  - Write Sequencer 226 */
+	[4323] = { 0x010F, 0x010F, 0x0000 }, /* R4323  - Write Sequencer 227 */
+	[4324] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4324  - Write Sequencer 228 */
+	[4325] = { 0x00FF, 0x00FF, 0x0000 }, /* R4325  - Write Sequencer 229 */
+	[4326] = { 0x070F, 0x070F, 0x0000 }, /* R4326  - Write Sequencer 230 */
+	[4327] = { 0x010F, 0x010F, 0x0000 }, /* R4327  - Write Sequencer 231 */
+	[4328] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4328  - Write Sequencer 232 */
+	[4329] = { 0x00FF, 0x00FF, 0x0000 }, /* R4329  - Write Sequencer 233 */
+	[4330] = { 0x070F, 0x070F, 0x0000 }, /* R4330  - Write Sequencer 234 */
+	[4331] = { 0x010F, 0x010F, 0x0000 }, /* R4331  - Write Sequencer 235 */
+	[4332] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4332  - Write Sequencer 236 */
+	[4333] = { 0x00FF, 0x00FF, 0x0000 }, /* R4333  - Write Sequencer 237 */
+	[4334] = { 0x070F, 0x070F, 0x0000 }, /* R4334  - Write Sequencer 238 */
+	[4335] = { 0x010F, 0x010F, 0x0000 }, /* R4335  - Write Sequencer 239 */
+	[4336] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4336  - Write Sequencer 240 */
+	[4337] = { 0x00FF, 0x00FF, 0x0000 }, /* R4337  - Write Sequencer 241 */
+	[4338] = { 0x070F, 0x070F, 0x0000 }, /* R4338  - Write Sequencer 242 */
+	[4339] = { 0x010F, 0x010F, 0x0000 }, /* R4339  - Write Sequencer 243 */
+	[4340] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4340  - Write Sequencer 244 */
+	[4341] = { 0x00FF, 0x00FF, 0x0000 }, /* R4341  - Write Sequencer 245 */
+	[4342] = { 0x070F, 0x070F, 0x0000 }, /* R4342  - Write Sequencer 246 */
+	[4343] = { 0x010F, 0x010F, 0x0000 }, /* R4343  - Write Sequencer 247 */
+	[4344] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4344  - Write Sequencer 248 */
+	[4345] = { 0x00FF, 0x00FF, 0x0000 }, /* R4345  - Write Sequencer 249 */
+	[4346] = { 0x070F, 0x070F, 0x0000 }, /* R4346  - Write Sequencer 250 */
+	[4347] = { 0x010F, 0x010F, 0x0000 }, /* R4347  - Write Sequencer 251 */
+	[4348] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4348  - Write Sequencer 252 */
+	[4349] = { 0x00FF, 0x00FF, 0x0000 }, /* R4349  - Write Sequencer 253 */
+	[4350] = { 0x070F, 0x070F, 0x0000 }, /* R4350  - Write Sequencer 254 */
+	[4351] = { 0x010F, 0x010F, 0x0000 }, /* R4351  - Write Sequencer 255 */
+	[4352] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4352  - Write Sequencer 256 */
+	[4353] = { 0x00FF, 0x00FF, 0x0000 }, /* R4353  - Write Sequencer 257 */
+	[4354] = { 0x070F, 0x070F, 0x0000 }, /* R4354  - Write Sequencer 258 */
+	[4355] = { 0x010F, 0x010F, 0x0000 }, /* R4355  - Write Sequencer 259 */
+	[4356] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4356  - Write Sequencer 260 */
+	[4357] = { 0x00FF, 0x00FF, 0x0000 }, /* R4357  - Write Sequencer 261 */
+	[4358] = { 0x070F, 0x070F, 0x0000 }, /* R4358  - Write Sequencer 262 */
+	[4359] = { 0x010F, 0x010F, 0x0000 }, /* R4359  - Write Sequencer 263 */
+	[4360] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4360  - Write Sequencer 264 */
+	[4361] = { 0x00FF, 0x00FF, 0x0000 }, /* R4361  - Write Sequencer 265 */
+	[4362] = { 0x070F, 0x070F, 0x0000 }, /* R4362  - Write Sequencer 266 */
+	[4363] = { 0x010F, 0x010F, 0x0000 }, /* R4363  - Write Sequencer 267 */
+	[4364] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4364  - Write Sequencer 268 */
+	[4365] = { 0x00FF, 0x00FF, 0x0000 }, /* R4365  - Write Sequencer 269 */
+	[4366] = { 0x070F, 0x070F, 0x0000 }, /* R4366  - Write Sequencer 270 */
+	[4367] = { 0x010F, 0x010F, 0x0000 }, /* R4367  - Write Sequencer 271 */
+	[4368] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4368  - Write Sequencer 272 */
+	[4369] = { 0x00FF, 0x00FF, 0x0000 }, /* R4369  - Write Sequencer 273 */
+	[4370] = { 0x070F, 0x070F, 0x0000 }, /* R4370  - Write Sequencer 274 */
+	[4371] = { 0x010F, 0x010F, 0x0000 }, /* R4371  - Write Sequencer 275 */
+	[4372] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4372  - Write Sequencer 276 */
+	[4373] = { 0x00FF, 0x00FF, 0x0000 }, /* R4373  - Write Sequencer 277 */
+	[4374] = { 0x070F, 0x070F, 0x0000 }, /* R4374  - Write Sequencer 278 */
+	[4375] = { 0x010F, 0x010F, 0x0000 }, /* R4375  - Write Sequencer 279 */
+	[4376] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4376  - Write Sequencer 280 */
+	[4377] = { 0x00FF, 0x00FF, 0x0000 }, /* R4377  - Write Sequencer 281 */
+	[4378] = { 0x070F, 0x070F, 0x0000 }, /* R4378  - Write Sequencer 282 */
+	[4379] = { 0x010F, 0x010F, 0x0000 }, /* R4379  - Write Sequencer 283 */
+	[4380] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4380  - Write Sequencer 284 */
+	[4381] = { 0x00FF, 0x00FF, 0x0000 }, /* R4381  - Write Sequencer 285 */
+	[4382] = { 0x070F, 0x070F, 0x0000 }, /* R4382  - Write Sequencer 286 */
+	[4383] = { 0x010F, 0x010F, 0x0000 }, /* R4383  - Write Sequencer 287 */
+	[4384] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4384  - Write Sequencer 288 */
+	[4385] = { 0x00FF, 0x00FF, 0x0000 }, /* R4385  - Write Sequencer 289 */
+	[4386] = { 0x070F, 0x070F, 0x0000 }, /* R4386  - Write Sequencer 290 */
+	[4387] = { 0x010F, 0x010F, 0x0000 }, /* R4387  - Write Sequencer 291 */
+	[4388] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4388  - Write Sequencer 292 */
+	[4389] = { 0x00FF, 0x00FF, 0x0000 }, /* R4389  - Write Sequencer 293 */
+	[4390] = { 0x070F, 0x070F, 0x0000 }, /* R4390  - Write Sequencer 294 */
+	[4391] = { 0x010F, 0x010F, 0x0000 }, /* R4391  - Write Sequencer 295 */
+	[4392] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4392  - Write Sequencer 296 */
+	[4393] = { 0x00FF, 0x00FF, 0x0000 }, /* R4393  - Write Sequencer 297 */
+	[4394] = { 0x070F, 0x070F, 0x0000 }, /* R4394  - Write Sequencer 298 */
+	[4395] = { 0x010F, 0x010F, 0x0000 }, /* R4395  - Write Sequencer 299 */
+	[4396] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4396  - Write Sequencer 300 */
+	[4397] = { 0x00FF, 0x00FF, 0x0000 }, /* R4397  - Write Sequencer 301 */
+	[4398] = { 0x070F, 0x070F, 0x0000 }, /* R4398  - Write Sequencer 302 */
+	[4399] = { 0x010F, 0x010F, 0x0000 }, /* R4399  - Write Sequencer 303 */
+	[4400] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4400  - Write Sequencer 304 */
+	[4401] = { 0x00FF, 0x00FF, 0x0000 }, /* R4401  - Write Sequencer 305 */
+	[4402] = { 0x070F, 0x070F, 0x0000 }, /* R4402  - Write Sequencer 306 */
+	[4403] = { 0x010F, 0x010F, 0x0000 }, /* R4403  - Write Sequencer 307 */
+	[4404] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4404  - Write Sequencer 308 */
+	[4405] = { 0x00FF, 0x00FF, 0x0000 }, /* R4405  - Write Sequencer 309 */
+	[4406] = { 0x070F, 0x070F, 0x0000 }, /* R4406  - Write Sequencer 310 */
+	[4407] = { 0x010F, 0x010F, 0x0000 }, /* R4407  - Write Sequencer 311 */
+	[4408] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4408  - Write Sequencer 312 */
+	[4409] = { 0x00FF, 0x00FF, 0x0000 }, /* R4409  - Write Sequencer 313 */
+	[4410] = { 0x070F, 0x070F, 0x0000 }, /* R4410  - Write Sequencer 314 */
+	[4411] = { 0x010F, 0x010F, 0x0000 }, /* R4411  - Write Sequencer 315 */
+	[4412] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4412  - Write Sequencer 316 */
+	[4413] = { 0x00FF, 0x00FF, 0x0000 }, /* R4413  - Write Sequencer 317 */
+	[4414] = { 0x070F, 0x070F, 0x0000 }, /* R4414  - Write Sequencer 318 */
+	[4415] = { 0x010F, 0x010F, 0x0000 }, /* R4415  - Write Sequencer 319 */
+	[4416] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4416  - Write Sequencer 320 */
+	[4417] = { 0x00FF, 0x00FF, 0x0000 }, /* R4417  - Write Sequencer 321 */
+	[4418] = { 0x070F, 0x070F, 0x0000 }, /* R4418  - Write Sequencer 322 */
+	[4419] = { 0x010F, 0x010F, 0x0000 }, /* R4419  - Write Sequencer 323 */
+	[4420] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4420  - Write Sequencer 324 */
+	[4421] = { 0x00FF, 0x00FF, 0x0000 }, /* R4421  - Write Sequencer 325 */
+	[4422] = { 0x070F, 0x070F, 0x0000 }, /* R4422  - Write Sequencer 326 */
+	[4423] = { 0x010F, 0x010F, 0x0000 }, /* R4423  - Write Sequencer 327 */
+	[4424] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4424  - Write Sequencer 328 */
+	[4425] = { 0x00FF, 0x00FF, 0x0000 }, /* R4425  - Write Sequencer 329 */
+	[4426] = { 0x070F, 0x070F, 0x0000 }, /* R4426  - Write Sequencer 330 */
+	[4427] = { 0x010F, 0x010F, 0x0000 }, /* R4427  - Write Sequencer 331 */
+	[4428] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4428  - Write Sequencer 332 */
+	[4429] = { 0x00FF, 0x00FF, 0x0000 }, /* R4429  - Write Sequencer 333 */
+	[4430] = { 0x070F, 0x070F, 0x0000 }, /* R4430  - Write Sequencer 334 */
+	[4431] = { 0x010F, 0x010F, 0x0000 }, /* R4431  - Write Sequencer 335 */
+	[4432] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4432  - Write Sequencer 336 */
+	[4433] = { 0x00FF, 0x00FF, 0x0000 }, /* R4433  - Write Sequencer 337 */
+	[4434] = { 0x070F, 0x070F, 0x0000 }, /* R4434  - Write Sequencer 338 */
+	[4435] = { 0x010F, 0x010F, 0x0000 }, /* R4435  - Write Sequencer 339 */
+	[4436] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4436  - Write Sequencer 340 */
+	[4437] = { 0x00FF, 0x00FF, 0x0000 }, /* R4437  - Write Sequencer 341 */
+	[4438] = { 0x070F, 0x070F, 0x0000 }, /* R4438  - Write Sequencer 342 */
+	[4439] = { 0x010F, 0x010F, 0x0000 }, /* R4439  - Write Sequencer 343 */
+	[4440] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4440  - Write Sequencer 344 */
+	[4441] = { 0x00FF, 0x00FF, 0x0000 }, /* R4441  - Write Sequencer 345 */
+	[4442] = { 0x070F, 0x070F, 0x0000 }, /* R4442  - Write Sequencer 346 */
+	[4443] = { 0x010F, 0x010F, 0x0000 }, /* R4443  - Write Sequencer 347 */
+	[4444] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4444  - Write Sequencer 348 */
+	[4445] = { 0x00FF, 0x00FF, 0x0000 }, /* R4445  - Write Sequencer 349 */
+	[4446] = { 0x070F, 0x070F, 0x0000 }, /* R4446  - Write Sequencer 350 */
+	[4447] = { 0x010F, 0x010F, 0x0000 }, /* R4447  - Write Sequencer 351 */
+	[4448] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4448  - Write Sequencer 352 */
+	[4449] = { 0x00FF, 0x00FF, 0x0000 }, /* R4449  - Write Sequencer 353 */
+	[4450] = { 0x070F, 0x070F, 0x0000 }, /* R4450  - Write Sequencer 354 */
+	[4451] = { 0x010F, 0x010F, 0x0000 }, /* R4451  - Write Sequencer 355 */
+	[4452] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4452  - Write Sequencer 356 */
+	[4453] = { 0x00FF, 0x00FF, 0x0000 }, /* R4453  - Write Sequencer 357 */
+	[4454] = { 0x070F, 0x070F, 0x0000 }, /* R4454  - Write Sequencer 358 */
+	[4455] = { 0x010F, 0x010F, 0x0000 }, /* R4455  - Write Sequencer 359 */
+	[4456] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4456  - Write Sequencer 360 */
+	[4457] = { 0x00FF, 0x00FF, 0x0000 }, /* R4457  - Write Sequencer 361 */
+	[4458] = { 0x070F, 0x070F, 0x0000 }, /* R4458  - Write Sequencer 362 */
+	[4459] = { 0x010F, 0x010F, 0x0000 }, /* R4459  - Write Sequencer 363 */
+	[4460] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4460  - Write Sequencer 364 */
+	[4461] = { 0x00FF, 0x00FF, 0x0000 }, /* R4461  - Write Sequencer 365 */
+	[4462] = { 0x070F, 0x070F, 0x0000 }, /* R4462  - Write Sequencer 366 */
+	[4463] = { 0x010F, 0x010F, 0x0000 }, /* R4463  - Write Sequencer 367 */
+	[4464] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4464  - Write Sequencer 368 */
+	[4465] = { 0x00FF, 0x00FF, 0x0000 }, /* R4465  - Write Sequencer 369 */
+	[4466] = { 0x070F, 0x070F, 0x0000 }, /* R4466  - Write Sequencer 370 */
+	[4467] = { 0x010F, 0x010F, 0x0000 }, /* R4467  - Write Sequencer 371 */
+	[4468] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4468  - Write Sequencer 372 */
+	[4469] = { 0x00FF, 0x00FF, 0x0000 }, /* R4469  - Write Sequencer 373 */
+	[4470] = { 0x070F, 0x070F, 0x0000 }, /* R4470  - Write Sequencer 374 */
+	[4471] = { 0x010F, 0x010F, 0x0000 }, /* R4471  - Write Sequencer 375 */
+	[4472] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4472  - Write Sequencer 376 */
+	[4473] = { 0x00FF, 0x00FF, 0x0000 }, /* R4473  - Write Sequencer 377 */
+	[4474] = { 0x070F, 0x070F, 0x0000 }, /* R4474  - Write Sequencer 378 */
+	[4475] = { 0x010F, 0x010F, 0x0000 }, /* R4475  - Write Sequencer 379 */
+	[4476] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4476  - Write Sequencer 380 */
+	[4477] = { 0x00FF, 0x00FF, 0x0000 }, /* R4477  - Write Sequencer 381 */
+	[4478] = { 0x070F, 0x070F, 0x0000 }, /* R4478  - Write Sequencer 382 */
+	[4479] = { 0x010F, 0x010F, 0x0000 }, /* R4479  - Write Sequencer 383 */
+	[4480] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4480  - Write Sequencer 384 */
+	[4481] = { 0x00FF, 0x00FF, 0x0000 }, /* R4481  - Write Sequencer 385 */
+	[4482] = { 0x070F, 0x070F, 0x0000 }, /* R4482  - Write Sequencer 386 */
+	[4483] = { 0x010F, 0x010F, 0x0000 }, /* R4483  - Write Sequencer 387 */
+	[4484] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4484  - Write Sequencer 388 */
+	[4485] = { 0x00FF, 0x00FF, 0x0000 }, /* R4485  - Write Sequencer 389 */
+	[4486] = { 0x070F, 0x070F, 0x0000 }, /* R4486  - Write Sequencer 390 */
+	[4487] = { 0x010F, 0x010F, 0x0000 }, /* R4487  - Write Sequencer 391 */
+	[4488] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4488  - Write Sequencer 392 */
+	[4489] = { 0x00FF, 0x00FF, 0x0000 }, /* R4489  - Write Sequencer 393 */
+	[4490] = { 0x070F, 0x070F, 0x0000 }, /* R4490  - Write Sequencer 394 */
+	[4491] = { 0x010F, 0x010F, 0x0000 }, /* R4491  - Write Sequencer 395 */
+	[4492] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4492  - Write Sequencer 396 */
+	[4493] = { 0x00FF, 0x00FF, 0x0000 }, /* R4493  - Write Sequencer 397 */
+	[4494] = { 0x070F, 0x070F, 0x0000 }, /* R4494  - Write Sequencer 398 */
+	[4495] = { 0x010F, 0x010F, 0x0000 }, /* R4495  - Write Sequencer 399 */
+	[4496] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4496  - Write Sequencer 400 */
+	[4497] = { 0x00FF, 0x00FF, 0x0000 }, /* R4497  - Write Sequencer 401 */
+	[4498] = { 0x070F, 0x070F, 0x0000 }, /* R4498  - Write Sequencer 402 */
+	[4499] = { 0x010F, 0x010F, 0x0000 }, /* R4499  - Write Sequencer 403 */
+	[4500] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4500  - Write Sequencer 404 */
+	[4501] = { 0x00FF, 0x00FF, 0x0000 }, /* R4501  - Write Sequencer 405 */
+	[4502] = { 0x070F, 0x070F, 0x0000 }, /* R4502  - Write Sequencer 406 */
+	[4503] = { 0x010F, 0x010F, 0x0000 }, /* R4503  - Write Sequencer 407 */
+	[4504] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4504  - Write Sequencer 408 */
+	[4505] = { 0x00FF, 0x00FF, 0x0000 }, /* R4505  - Write Sequencer 409 */
+	[4506] = { 0x070F, 0x070F, 0x0000 }, /* R4506  - Write Sequencer 410 */
+	[4507] = { 0x010F, 0x010F, 0x0000 }, /* R4507  - Write Sequencer 411 */
+	[4508] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4508  - Write Sequencer 412 */
+	[4509] = { 0x00FF, 0x00FF, 0x0000 }, /* R4509  - Write Sequencer 413 */
+	[4510] = { 0x070F, 0x070F, 0x0000 }, /* R4510  - Write Sequencer 414 */
+	[4511] = { 0x010F, 0x010F, 0x0000 }, /* R4511  - Write Sequencer 415 */
+	[4512] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4512  - Write Sequencer 416 */
+	[4513] = { 0x00FF, 0x00FF, 0x0000 }, /* R4513  - Write Sequencer 417 */
+	[4514] = { 0x070F, 0x070F, 0x0000 }, /* R4514  - Write Sequencer 418 */
+	[4515] = { 0x010F, 0x010F, 0x0000 }, /* R4515  - Write Sequencer 419 */
+	[4516] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4516  - Write Sequencer 420 */
+	[4517] = { 0x00FF, 0x00FF, 0x0000 }, /* R4517  - Write Sequencer 421 */
+	[4518] = { 0x070F, 0x070F, 0x0000 }, /* R4518  - Write Sequencer 422 */
+	[4519] = { 0x010F, 0x010F, 0x0000 }, /* R4519  - Write Sequencer 423 */
+	[4520] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4520  - Write Sequencer 424 */
+	[4521] = { 0x00FF, 0x00FF, 0x0000 }, /* R4521  - Write Sequencer 425 */
+	[4522] = { 0x070F, 0x070F, 0x0000 }, /* R4522  - Write Sequencer 426 */
+	[4523] = { 0x010F, 0x010F, 0x0000 }, /* R4523  - Write Sequencer 427 */
+	[4524] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4524  - Write Sequencer 428 */
+	[4525] = { 0x00FF, 0x00FF, 0x0000 }, /* R4525  - Write Sequencer 429 */
+	[4526] = { 0x070F, 0x070F, 0x0000 }, /* R4526  - Write Sequencer 430 */
+	[4527] = { 0x010F, 0x010F, 0x0000 }, /* R4527  - Write Sequencer 431 */
+	[4528] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4528  - Write Sequencer 432 */
+	[4529] = { 0x00FF, 0x00FF, 0x0000 }, /* R4529  - Write Sequencer 433 */
+	[4530] = { 0x070F, 0x070F, 0x0000 }, /* R4530  - Write Sequencer 434 */
+	[4531] = { 0x010F, 0x010F, 0x0000 }, /* R4531  - Write Sequencer 435 */
+	[4532] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4532  - Write Sequencer 436 */
+	[4533] = { 0x00FF, 0x00FF, 0x0000 }, /* R4533  - Write Sequencer 437 */
+	[4534] = { 0x070F, 0x070F, 0x0000 }, /* R4534  - Write Sequencer 438 */
+	[4535] = { 0x010F, 0x010F, 0x0000 }, /* R4535  - Write Sequencer 439 */
+	[4536] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4536  - Write Sequencer 440 */
+	[4537] = { 0x00FF, 0x00FF, 0x0000 }, /* R4537  - Write Sequencer 441 */
+	[4538] = { 0x070F, 0x070F, 0x0000 }, /* R4538  - Write Sequencer 442 */
+	[4539] = { 0x010F, 0x010F, 0x0000 }, /* R4539  - Write Sequencer 443 */
+	[4540] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4540  - Write Sequencer 444 */
+	[4541] = { 0x00FF, 0x00FF, 0x0000 }, /* R4541  - Write Sequencer 445 */
+	[4542] = { 0x070F, 0x070F, 0x0000 }, /* R4542  - Write Sequencer 446 */
+	[4543] = { 0x010F, 0x010F, 0x0000 }, /* R4543  - Write Sequencer 447 */
+	[4544] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4544  - Write Sequencer 448 */
+	[4545] = { 0x00FF, 0x00FF, 0x0000 }, /* R4545  - Write Sequencer 449 */
+	[4546] = { 0x070F, 0x070F, 0x0000 }, /* R4546  - Write Sequencer 450 */
+	[4547] = { 0x010F, 0x010F, 0x0000 }, /* R4547  - Write Sequencer 451 */
+	[4548] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4548  - Write Sequencer 452 */
+	[4549] = { 0x00FF, 0x00FF, 0x0000 }, /* R4549  - Write Sequencer 453 */
+	[4550] = { 0x070F, 0x070F, 0x0000 }, /* R4550  - Write Sequencer 454 */
+	[4551] = { 0x010F, 0x010F, 0x0000 }, /* R4551  - Write Sequencer 455 */
+	[4552] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4552  - Write Sequencer 456 */
+	[4553] = { 0x00FF, 0x00FF, 0x0000 }, /* R4553  - Write Sequencer 457 */
+	[4554] = { 0x070F, 0x070F, 0x0000 }, /* R4554  - Write Sequencer 458 */
+	[4555] = { 0x010F, 0x010F, 0x0000 }, /* R4555  - Write Sequencer 459 */
+	[4556] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4556  - Write Sequencer 460 */
+	[4557] = { 0x00FF, 0x00FF, 0x0000 }, /* R4557  - Write Sequencer 461 */
+	[4558] = { 0x070F, 0x070F, 0x0000 }, /* R4558  - Write Sequencer 462 */
+	[4559] = { 0x010F, 0x010F, 0x0000 }, /* R4559  - Write Sequencer 463 */
+	[4560] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4560  - Write Sequencer 464 */
+	[4561] = { 0x00FF, 0x00FF, 0x0000 }, /* R4561  - Write Sequencer 465 */
+	[4562] = { 0x070F, 0x070F, 0x0000 }, /* R4562  - Write Sequencer 466 */
+	[4563] = { 0x010F, 0x010F, 0x0000 }, /* R4563  - Write Sequencer 467 */
+	[4564] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4564  - Write Sequencer 468 */
+	[4565] = { 0x00FF, 0x00FF, 0x0000 }, /* R4565  - Write Sequencer 469 */
+	[4566] = { 0x070F, 0x070F, 0x0000 }, /* R4566  - Write Sequencer 470 */
+	[4567] = { 0x010F, 0x010F, 0x0000 }, /* R4567  - Write Sequencer 471 */
+	[4568] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4568  - Write Sequencer 472 */
+	[4569] = { 0x00FF, 0x00FF, 0x0000 }, /* R4569  - Write Sequencer 473 */
+	[4570] = { 0x070F, 0x070F, 0x0000 }, /* R4570  - Write Sequencer 474 */
+	[4571] = { 0x010F, 0x010F, 0x0000 }, /* R4571  - Write Sequencer 475 */
+	[4572] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4572  - Write Sequencer 476 */
+	[4573] = { 0x00FF, 0x00FF, 0x0000 }, /* R4573  - Write Sequencer 477 */
+	[4574] = { 0x070F, 0x070F, 0x0000 }, /* R4574  - Write Sequencer 478 */
+	[4575] = { 0x010F, 0x010F, 0x0000 }, /* R4575  - Write Sequencer 479 */
+	[4576] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4576  - Write Sequencer 480 */
+	[4577] = { 0x00FF, 0x00FF, 0x0000 }, /* R4577  - Write Sequencer 481 */
+	[4578] = { 0x070F, 0x070F, 0x0000 }, /* R4578  - Write Sequencer 482 */
+	[4579] = { 0x010F, 0x010F, 0x0000 }, /* R4579  - Write Sequencer 483 */
+	[4580] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4580  - Write Sequencer 484 */
+	[4581] = { 0x00FF, 0x00FF, 0x0000 }, /* R4581  - Write Sequencer 485 */
+	[4582] = { 0x070F, 0x070F, 0x0000 }, /* R4582  - Write Sequencer 486 */
+	[4583] = { 0x010F, 0x010F, 0x0000 }, /* R4583  - Write Sequencer 487 */
+	[4584] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4584  - Write Sequencer 488 */
+	[4585] = { 0x00FF, 0x00FF, 0x0000 }, /* R4585  - Write Sequencer 489 */
+	[4586] = { 0x070F, 0x070F, 0x0000 }, /* R4586  - Write Sequencer 490 */
+	[4587] = { 0x010F, 0x010F, 0x0000 }, /* R4587  - Write Sequencer 491 */
+	[4588] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4588  - Write Sequencer 492 */
+	[4589] = { 0x00FF, 0x00FF, 0x0000 }, /* R4589  - Write Sequencer 493 */
+	[4590] = { 0x070F, 0x070F, 0x0000 }, /* R4590  - Write Sequencer 494 */
+	[4591] = { 0x010F, 0x010F, 0x0000 }, /* R4591  - Write Sequencer 495 */
+	[4592] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4592  - Write Sequencer 496 */
+	[4593] = { 0x00FF, 0x00FF, 0x0000 }, /* R4593  - Write Sequencer 497 */
+	[4594] = { 0x070F, 0x070F, 0x0000 }, /* R4594  - Write Sequencer 498 */
+	[4595] = { 0x010F, 0x010F, 0x0000 }, /* R4595  - Write Sequencer 499 */
+	[4596] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4596  - Write Sequencer 500 */
+	[4597] = { 0x00FF, 0x00FF, 0x0000 }, /* R4597  - Write Sequencer 501 */
+	[4598] = { 0x070F, 0x070F, 0x0000 }, /* R4598  - Write Sequencer 502 */
+	[4599] = { 0x010F, 0x010F, 0x0000 }, /* R4599  - Write Sequencer 503 */
+	[4600] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4600  - Write Sequencer 504 */
+	[4601] = { 0x00FF, 0x00FF, 0x0000 }, /* R4601  - Write Sequencer 505 */
+	[4602] = { 0x070F, 0x070F, 0x0000 }, /* R4602  - Write Sequencer 506 */
+	[4603] = { 0x010F, 0x010F, 0x0000 }, /* R4603  - Write Sequencer 507 */
+	[4604] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4604  - Write Sequencer 508 */
+	[4605] = { 0x00FF, 0x00FF, 0x0000 }, /* R4605  - Write Sequencer 509 */
+	[4606] = { 0x070F, 0x070F, 0x0000 }, /* R4606  - Write Sequencer 510 */
+	[4607] = { 0x010F, 0x010F, 0x0000 }, /* R4607  - Write Sequencer 511 */
+	[8192] = { 0x03FF, 0x03FF, 0x0000 }, /* R8192  - DSP2 Instruction RAM 0 */
+	[9216] = { 0x003F, 0x003F, 0x0000 }, /* R9216  - DSP2 Address RAM 2 */
+	[9217] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R9217  - DSP2 Address RAM 1 */
+	[9218] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R9218  - DSP2 Address RAM 0 */
+	[12288] = { 0x00FF, 0x00FF, 0x0000 }, /* R12288 - DSP2 Data1 RAM 1 */
+	[12289] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R12289 - DSP2 Data1 RAM 0 */
+	[13312] = { 0x00FF, 0x00FF, 0x0000 }, /* R13312 - DSP2 Data2 RAM 1 */
+	[13313] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R13313 - DSP2 Data2 RAM 0 */
+	[14336] = { 0x00FF, 0x00FF, 0x0000 }, /* R14336 - DSP2 Data3 RAM 1 */
+	[14337] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R14337 - DSP2 Data3 RAM 0 */
+	[15360] = { 0x07FF, 0x07FF, 0x0000 }, /* R15360 - DSP2 Coeff RAM 0 */
+	[16384] = { 0x00FF, 0x00FF, 0x0000 }, /* R16384 - RETUNEADC_SHARED_COEFF_1 */
+	[16385] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16385 - RETUNEADC_SHARED_COEFF_0 */
+	[16386] = { 0x00FF, 0x00FF, 0x0000 }, /* R16386 - RETUNEDAC_SHARED_COEFF_1 */
+	[16387] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16387 - RETUNEDAC_SHARED_COEFF_0 */
+	[16388] = { 0x00FF, 0x00FF, 0x0000 }, /* R16388 - SOUNDSTAGE_ENABLES_1 */
+	[16389] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16389 - SOUNDSTAGE_ENABLES_0 */
+	[16896] = { 0x00FF, 0x00FF, 0x0000 }, /* R16896 - HDBASS_AI_1 */
+	[16897] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16897 - HDBASS_AI_0 */
+	[16898] = { 0x00FF, 0x00FF, 0x0000 }, /* R16898 - HDBASS_AR_1 */
+	[16899] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16899 - HDBASS_AR_0 */
+	[16900] = { 0x00FF, 0x00FF, 0x0000 }, /* R16900 - HDBASS_B_1 */
+	[16901] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16901 - HDBASS_B_0 */
+	[16902] = { 0x00FF, 0x00FF, 0x0000 }, /* R16902 - HDBASS_K_1 */
+	[16903] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16903 - HDBASS_K_0 */
+	[16904] = { 0x00FF, 0x00FF, 0x0000 }, /* R16904 - HDBASS_N1_1 */
+	[16905] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16905 - HDBASS_N1_0 */
+	[16906] = { 0x00FF, 0x00FF, 0x0000 }, /* R16906 - HDBASS_N2_1 */
+	[16907] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16907 - HDBASS_N2_0 */
+	[16908] = { 0x00FF, 0x00FF, 0x0000 }, /* R16908 - HDBASS_N3_1 */
+	[16909] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16909 - HDBASS_N3_0 */
+	[16910] = { 0x00FF, 0x00FF, 0x0000 }, /* R16910 - HDBASS_N4_1 */
+	[16911] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16911 - HDBASS_N4_0 */
+	[16912] = { 0x00FF, 0x00FF, 0x0000 }, /* R16912 - HDBASS_N5_1 */
+	[16913] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16913 - HDBASS_N5_0 */
+	[16914] = { 0x00FF, 0x00FF, 0x0000 }, /* R16914 - HDBASS_X1_1 */
+	[16915] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16915 - HDBASS_X1_0 */
+	[16916] = { 0x00FF, 0x00FF, 0x0000 }, /* R16916 - HDBASS_X2_1 */
+	[16917] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16917 - HDBASS_X2_0 */
+	[16918] = { 0x00FF, 0x00FF, 0x0000 }, /* R16918 - HDBASS_X3_1 */
+	[16919] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16919 - HDBASS_X3_0 */
+	[16920] = { 0x00FF, 0x00FF, 0x0000 }, /* R16920 - HDBASS_ATK_1 */
+	[16921] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16921 - HDBASS_ATK_0 */
+	[16922] = { 0x00FF, 0x00FF, 0x0000 }, /* R16922 - HDBASS_DCY_1 */
+	[16923] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16923 - HDBASS_DCY_0 */
+	[16924] = { 0x00FF, 0x00FF, 0x0000 }, /* R16924 - HDBASS_PG_1 */
+	[16925] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R16925 - HDBASS_PG_0 */
+	[17408] = { 0x00FF, 0x00FF, 0x0000 }, /* R17408 - HPF_C_1 */
+	[17409] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17409 - HPF_C_0 */
+	[17920] = { 0x00FF, 0x00FF, 0x0000 }, /* R17920 - ADCL_RETUNE_C1_1 */
+	[17921] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17921 - ADCL_RETUNE_C1_0 */
+	[17922] = { 0x00FF, 0x00FF, 0x0000 }, /* R17922 - ADCL_RETUNE_C2_1 */
+	[17923] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17923 - ADCL_RETUNE_C2_0 */
+	[17924] = { 0x00FF, 0x00FF, 0x0000 }, /* R17924 - ADCL_RETUNE_C3_1 */
+	[17925] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17925 - ADCL_RETUNE_C3_0 */
+	[17926] = { 0x00FF, 0x00FF, 0x0000 }, /* R17926 - ADCL_RETUNE_C4_1 */
+	[17927] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17927 - ADCL_RETUNE_C4_0 */
+	[17928] = { 0x00FF, 0x00FF, 0x0000 }, /* R17928 - ADCL_RETUNE_C5_1 */
+	[17929] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17929 - ADCL_RETUNE_C5_0 */
+	[17930] = { 0x00FF, 0x00FF, 0x0000 }, /* R17930 - ADCL_RETUNE_C6_1 */
+	[17931] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17931 - ADCL_RETUNE_C6_0 */
+	[17932] = { 0x00FF, 0x00FF, 0x0000 }, /* R17932 - ADCL_RETUNE_C7_1 */
+	[17933] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17933 - ADCL_RETUNE_C7_0 */
+	[17934] = { 0x00FF, 0x00FF, 0x0000 }, /* R17934 - ADCL_RETUNE_C8_1 */
+	[17935] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17935 - ADCL_RETUNE_C8_0 */
+	[17936] = { 0x00FF, 0x00FF, 0x0000 }, /* R17936 - ADCL_RETUNE_C9_1 */
+	[17937] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17937 - ADCL_RETUNE_C9_0 */
+	[17938] = { 0x00FF, 0x00FF, 0x0000 }, /* R17938 - ADCL_RETUNE_C10_1 */
+	[17939] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17939 - ADCL_RETUNE_C10_0 */
+	[17940] = { 0x00FF, 0x00FF, 0x0000 }, /* R17940 - ADCL_RETUNE_C11_1 */
+	[17941] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17941 - ADCL_RETUNE_C11_0 */
+	[17942] = { 0x00FF, 0x00FF, 0x0000 }, /* R17942 - ADCL_RETUNE_C12_1 */
+	[17943] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17943 - ADCL_RETUNE_C12_0 */
+	[17944] = { 0x00FF, 0x00FF, 0x0000 }, /* R17944 - ADCL_RETUNE_C13_1 */
+	[17945] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17945 - ADCL_RETUNE_C13_0 */
+	[17946] = { 0x00FF, 0x00FF, 0x0000 }, /* R17946 - ADCL_RETUNE_C14_1 */
+	[17947] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17947 - ADCL_RETUNE_C14_0 */
+	[17948] = { 0x00FF, 0x00FF, 0x0000 }, /* R17948 - ADCL_RETUNE_C15_1 */
+	[17949] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17949 - ADCL_RETUNE_C15_0 */
+	[17950] = { 0x00FF, 0x00FF, 0x0000 }, /* R17950 - ADCL_RETUNE_C16_1 */
+	[17951] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17951 - ADCL_RETUNE_C16_0 */
+	[17952] = { 0x00FF, 0x00FF, 0x0000 }, /* R17952 - ADCL_RETUNE_C17_1 */
+	[17953] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17953 - ADCL_RETUNE_C17_0 */
+	[17954] = { 0x00FF, 0x00FF, 0x0000 }, /* R17954 - ADCL_RETUNE_C18_1 */
+	[17955] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17955 - ADCL_RETUNE_C18_0 */
+	[17956] = { 0x00FF, 0x00FF, 0x0000 }, /* R17956 - ADCL_RETUNE_C19_1 */
+	[17957] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17957 - ADCL_RETUNE_C19_0 */
+	[17958] = { 0x00FF, 0x00FF, 0x0000 }, /* R17958 - ADCL_RETUNE_C20_1 */
+	[17959] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17959 - ADCL_RETUNE_C20_0 */
+	[17960] = { 0x00FF, 0x00FF, 0x0000 }, /* R17960 - ADCL_RETUNE_C21_1 */
+	[17961] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17961 - ADCL_RETUNE_C21_0 */
+	[17962] = { 0x00FF, 0x00FF, 0x0000 }, /* R17962 - ADCL_RETUNE_C22_1 */
+	[17963] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17963 - ADCL_RETUNE_C22_0 */
+	[17964] = { 0x00FF, 0x00FF, 0x0000 }, /* R17964 - ADCL_RETUNE_C23_1 */
+	[17965] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17965 - ADCL_RETUNE_C23_0 */
+	[17966] = { 0x00FF, 0x00FF, 0x0000 }, /* R17966 - ADCL_RETUNE_C24_1 */
+	[17967] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17967 - ADCL_RETUNE_C24_0 */
+	[17968] = { 0x00FF, 0x00FF, 0x0000 }, /* R17968 - ADCL_RETUNE_C25_1 */
+	[17969] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17969 - ADCL_RETUNE_C25_0 */
+	[17970] = { 0x00FF, 0x00FF, 0x0000 }, /* R17970 - ADCL_RETUNE_C26_1 */
+	[17971] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17971 - ADCL_RETUNE_C26_0 */
+	[17972] = { 0x00FF, 0x00FF, 0x0000 }, /* R17972 - ADCL_RETUNE_C27_1 */
+	[17973] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17973 - ADCL_RETUNE_C27_0 */
+	[17974] = { 0x00FF, 0x00FF, 0x0000 }, /* R17974 - ADCL_RETUNE_C28_1 */
+	[17975] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17975 - ADCL_RETUNE_C28_0 */
+	[17976] = { 0x00FF, 0x00FF, 0x0000 }, /* R17976 - ADCL_RETUNE_C29_1 */
+	[17977] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17977 - ADCL_RETUNE_C29_0 */
+	[17978] = { 0x00FF, 0x00FF, 0x0000 }, /* R17978 - ADCL_RETUNE_C30_1 */
+	[17979] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17979 - ADCL_RETUNE_C30_0 */
+	[17980] = { 0x00FF, 0x00FF, 0x0000 }, /* R17980 - ADCL_RETUNE_C31_1 */
+	[17981] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17981 - ADCL_RETUNE_C31_0 */
+	[17982] = { 0x00FF, 0x00FF, 0x0000 }, /* R17982 - ADCL_RETUNE_C32_1 */
+	[17983] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R17983 - ADCL_RETUNE_C32_0 */
+	[18432] = { 0x00FF, 0x00FF, 0x0000 }, /* R18432 - RETUNEADC_PG2_1 */
+	[18433] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18433 - RETUNEADC_PG2_0 */
+	[18434] = { 0x00FF, 0x00FF, 0x0000 }, /* R18434 - RETUNEADC_PG_1 */
+	[18435] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18435 - RETUNEADC_PG_0 */
+	[18944] = { 0x00FF, 0x00FF, 0x0000 }, /* R18944 - ADCR_RETUNE_C1_1 */
+	[18945] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18945 - ADCR_RETUNE_C1_0 */
+	[18946] = { 0x00FF, 0x00FF, 0x0000 }, /* R18946 - ADCR_RETUNE_C2_1 */
+	[18947] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18947 - ADCR_RETUNE_C2_0 */
+	[18948] = { 0x00FF, 0x00FF, 0x0000 }, /* R18948 - ADCR_RETUNE_C3_1 */
+	[18949] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18949 - ADCR_RETUNE_C3_0 */
+	[18950] = { 0x00FF, 0x00FF, 0x0000 }, /* R18950 - ADCR_RETUNE_C4_1 */
+	[18951] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18951 - ADCR_RETUNE_C4_0 */
+	[18952] = { 0x00FF, 0x00FF, 0x0000 }, /* R18952 - ADCR_RETUNE_C5_1 */
+	[18953] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18953 - ADCR_RETUNE_C5_0 */
+	[18954] = { 0x00FF, 0x00FF, 0x0000 }, /* R18954 - ADCR_RETUNE_C6_1 */
+	[18955] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18955 - ADCR_RETUNE_C6_0 */
+	[18956] = { 0x00FF, 0x00FF, 0x0000 }, /* R18956 - ADCR_RETUNE_C7_1 */
+	[18957] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18957 - ADCR_RETUNE_C7_0 */
+	[18958] = { 0x00FF, 0x00FF, 0x0000 }, /* R18958 - ADCR_RETUNE_C8_1 */
+	[18959] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18959 - ADCR_RETUNE_C8_0 */
+	[18960] = { 0x00FF, 0x00FF, 0x0000 }, /* R18960 - ADCR_RETUNE_C9_1 */
+	[18961] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18961 - ADCR_RETUNE_C9_0 */
+	[18962] = { 0x00FF, 0x00FF, 0x0000 }, /* R18962 - ADCR_RETUNE_C10_1 */
+	[18963] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18963 - ADCR_RETUNE_C10_0 */
+	[18964] = { 0x00FF, 0x00FF, 0x0000 }, /* R18964 - ADCR_RETUNE_C11_1 */
+	[18965] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18965 - ADCR_RETUNE_C11_0 */
+	[18966] = { 0x00FF, 0x00FF, 0x0000 }, /* R18966 - ADCR_RETUNE_C12_1 */
+	[18967] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18967 - ADCR_RETUNE_C12_0 */
+	[18968] = { 0x00FF, 0x00FF, 0x0000 }, /* R18968 - ADCR_RETUNE_C13_1 */
+	[18969] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18969 - ADCR_RETUNE_C13_0 */
+	[18970] = { 0x00FF, 0x00FF, 0x0000 }, /* R18970 - ADCR_RETUNE_C14_1 */
+	[18971] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18971 - ADCR_RETUNE_C14_0 */
+	[18972] = { 0x00FF, 0x00FF, 0x0000 }, /* R18972 - ADCR_RETUNE_C15_1 */
+	[18973] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18973 - ADCR_RETUNE_C15_0 */
+	[18974] = { 0x00FF, 0x00FF, 0x0000 }, /* R18974 - ADCR_RETUNE_C16_1 */
+	[18975] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18975 - ADCR_RETUNE_C16_0 */
+	[18976] = { 0x00FF, 0x00FF, 0x0000 }, /* R18976 - ADCR_RETUNE_C17_1 */
+	[18977] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18977 - ADCR_RETUNE_C17_0 */
+	[18978] = { 0x00FF, 0x00FF, 0x0000 }, /* R18978 - ADCR_RETUNE_C18_1 */
+	[18979] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18979 - ADCR_RETUNE_C18_0 */
+	[18980] = { 0x00FF, 0x00FF, 0x0000 }, /* R18980 - ADCR_RETUNE_C19_1 */
+	[18981] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18981 - ADCR_RETUNE_C19_0 */
+	[18982] = { 0x00FF, 0x00FF, 0x0000 }, /* R18982 - ADCR_RETUNE_C20_1 */
+	[18983] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18983 - ADCR_RETUNE_C20_0 */
+	[18984] = { 0x00FF, 0x00FF, 0x0000 }, /* R18984 - ADCR_RETUNE_C21_1 */
+	[18985] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18985 - ADCR_RETUNE_C21_0 */
+	[18986] = { 0x00FF, 0x00FF, 0x0000 }, /* R18986 - ADCR_RETUNE_C22_1 */
+	[18987] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18987 - ADCR_RETUNE_C22_0 */
+	[18988] = { 0x00FF, 0x00FF, 0x0000 }, /* R18988 - ADCR_RETUNE_C23_1 */
+	[18989] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18989 - ADCR_RETUNE_C23_0 */
+	[18990] = { 0x00FF, 0x00FF, 0x0000 }, /* R18990 - ADCR_RETUNE_C24_1 */
+	[18991] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18991 - ADCR_RETUNE_C24_0 */
+	[18992] = { 0x00FF, 0x00FF, 0x0000 }, /* R18992 - ADCR_RETUNE_C25_1 */
+	[18993] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18993 - ADCR_RETUNE_C25_0 */
+	[18994] = { 0x00FF, 0x00FF, 0x0000 }, /* R18994 - ADCR_RETUNE_C26_1 */
+	[18995] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18995 - ADCR_RETUNE_C26_0 */
+	[18996] = { 0x00FF, 0x00FF, 0x0000 }, /* R18996 - ADCR_RETUNE_C27_1 */
+	[18997] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18997 - ADCR_RETUNE_C27_0 */
+	[18998] = { 0x00FF, 0x00FF, 0x0000 }, /* R18998 - ADCR_RETUNE_C28_1 */
+	[18999] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R18999 - ADCR_RETUNE_C28_0 */
+	[19000] = { 0x00FF, 0x00FF, 0x0000 }, /* R19000 - ADCR_RETUNE_C29_1 */
+	[19001] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19001 - ADCR_RETUNE_C29_0 */
+	[19002] = { 0x00FF, 0x00FF, 0x0000 }, /* R19002 - ADCR_RETUNE_C30_1 */
+	[19003] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19003 - ADCR_RETUNE_C30_0 */
+	[19004] = { 0x00FF, 0x00FF, 0x0000 }, /* R19004 - ADCR_RETUNE_C31_1 */
+	[19005] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19005 - ADCR_RETUNE_C31_0 */
+	[19006] = { 0x00FF, 0x00FF, 0x0000 }, /* R19006 - ADCR_RETUNE_C32_1 */
+	[19007] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19007 - ADCR_RETUNE_C32_0 */
+	[19456] = { 0x00FF, 0x00FF, 0x0000 }, /* R19456 - DACL_RETUNE_C1_1 */
+	[19457] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19457 - DACL_RETUNE_C1_0 */
+	[19458] = { 0x00FF, 0x00FF, 0x0000 }, /* R19458 - DACL_RETUNE_C2_1 */
+	[19459] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19459 - DACL_RETUNE_C2_0 */
+	[19460] = { 0x00FF, 0x00FF, 0x0000 }, /* R19460 - DACL_RETUNE_C3_1 */
+	[19461] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19461 - DACL_RETUNE_C3_0 */
+	[19462] = { 0x00FF, 0x00FF, 0x0000 }, /* R19462 - DACL_RETUNE_C4_1 */
+	[19463] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19463 - DACL_RETUNE_C4_0 */
+	[19464] = { 0x00FF, 0x00FF, 0x0000 }, /* R19464 - DACL_RETUNE_C5_1 */
+	[19465] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19465 - DACL_RETUNE_C5_0 */
+	[19466] = { 0x00FF, 0x00FF, 0x0000 }, /* R19466 - DACL_RETUNE_C6_1 */
+	[19467] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19467 - DACL_RETUNE_C6_0 */
+	[19468] = { 0x00FF, 0x00FF, 0x0000 }, /* R19468 - DACL_RETUNE_C7_1 */
+	[19469] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19469 - DACL_RETUNE_C7_0 */
+	[19470] = { 0x00FF, 0x00FF, 0x0000 }, /* R19470 - DACL_RETUNE_C8_1 */
+	[19471] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19471 - DACL_RETUNE_C8_0 */
+	[19472] = { 0x00FF, 0x00FF, 0x0000 }, /* R19472 - DACL_RETUNE_C9_1 */
+	[19473] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19473 - DACL_RETUNE_C9_0 */
+	[19474] = { 0x00FF, 0x00FF, 0x0000 }, /* R19474 - DACL_RETUNE_C10_1 */
+	[19475] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19475 - DACL_RETUNE_C10_0 */
+	[19476] = { 0x00FF, 0x00FF, 0x0000 }, /* R19476 - DACL_RETUNE_C11_1 */
+	[19477] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19477 - DACL_RETUNE_C11_0 */
+	[19478] = { 0x00FF, 0x00FF, 0x0000 }, /* R19478 - DACL_RETUNE_C12_1 */
+	[19479] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19479 - DACL_RETUNE_C12_0 */
+	[19480] = { 0x00FF, 0x00FF, 0x0000 }, /* R19480 - DACL_RETUNE_C13_1 */
+	[19481] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19481 - DACL_RETUNE_C13_0 */
+	[19482] = { 0x00FF, 0x00FF, 0x0000 }, /* R19482 - DACL_RETUNE_C14_1 */
+	[19483] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19483 - DACL_RETUNE_C14_0 */
+	[19484] = { 0x00FF, 0x00FF, 0x0000 }, /* R19484 - DACL_RETUNE_C15_1 */
+	[19485] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19485 - DACL_RETUNE_C15_0 */
+	[19486] = { 0x00FF, 0x00FF, 0x0000 }, /* R19486 - DACL_RETUNE_C16_1 */
+	[19487] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19487 - DACL_RETUNE_C16_0 */
+	[19488] = { 0x00FF, 0x00FF, 0x0000 }, /* R19488 - DACL_RETUNE_C17_1 */
+	[19489] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19489 - DACL_RETUNE_C17_0 */
+	[19490] = { 0x00FF, 0x00FF, 0x0000 }, /* R19490 - DACL_RETUNE_C18_1 */
+	[19491] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19491 - DACL_RETUNE_C18_0 */
+	[19492] = { 0x00FF, 0x00FF, 0x0000 }, /* R19492 - DACL_RETUNE_C19_1 */
+	[19493] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19493 - DACL_RETUNE_C19_0 */
+	[19494] = { 0x00FF, 0x00FF, 0x0000 }, /* R19494 - DACL_RETUNE_C20_1 */
+	[19495] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19495 - DACL_RETUNE_C20_0 */
+	[19496] = { 0x00FF, 0x00FF, 0x0000 }, /* R19496 - DACL_RETUNE_C21_1 */
+	[19497] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19497 - DACL_RETUNE_C21_0 */
+	[19498] = { 0x00FF, 0x00FF, 0x0000 }, /* R19498 - DACL_RETUNE_C22_1 */
+	[19499] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19499 - DACL_RETUNE_C22_0 */
+	[19500] = { 0x00FF, 0x00FF, 0x0000 }, /* R19500 - DACL_RETUNE_C23_1 */
+	[19501] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19501 - DACL_RETUNE_C23_0 */
+	[19502] = { 0x00FF, 0x00FF, 0x0000 }, /* R19502 - DACL_RETUNE_C24_1 */
+	[19503] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19503 - DACL_RETUNE_C24_0 */
+	[19504] = { 0x00FF, 0x00FF, 0x0000 }, /* R19504 - DACL_RETUNE_C25_1 */
+	[19505] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19505 - DACL_RETUNE_C25_0 */
+	[19506] = { 0x00FF, 0x00FF, 0x0000 }, /* R19506 - DACL_RETUNE_C26_1 */
+	[19507] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19507 - DACL_RETUNE_C26_0 */
+	[19508] = { 0x00FF, 0x00FF, 0x0000 }, /* R19508 - DACL_RETUNE_C27_1 */
+	[19509] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19509 - DACL_RETUNE_C27_0 */
+	[19510] = { 0x00FF, 0x00FF, 0x0000 }, /* R19510 - DACL_RETUNE_C28_1 */
+	[19511] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19511 - DACL_RETUNE_C28_0 */
+	[19512] = { 0x00FF, 0x00FF, 0x0000 }, /* R19512 - DACL_RETUNE_C29_1 */
+	[19513] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19513 - DACL_RETUNE_C29_0 */
+	[19514] = { 0x00FF, 0x00FF, 0x0000 }, /* R19514 - DACL_RETUNE_C30_1 */
+	[19515] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19515 - DACL_RETUNE_C30_0 */
+	[19516] = { 0x00FF, 0x00FF, 0x0000 }, /* R19516 - DACL_RETUNE_C31_1 */
+	[19517] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19517 - DACL_RETUNE_C31_0 */
+	[19518] = { 0x00FF, 0x00FF, 0x0000 }, /* R19518 - DACL_RETUNE_C32_1 */
+	[19519] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19519 - DACL_RETUNE_C32_0 */
+	[19968] = { 0x00FF, 0x00FF, 0x0000 }, /* R19968 - RETUNEDAC_PG2_1 */
+	[19969] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19969 - RETUNEDAC_PG2_0 */
+	[19970] = { 0x00FF, 0x00FF, 0x0000 }, /* R19970 - RETUNEDAC_PG_1 */
+	[19971] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R19971 - RETUNEDAC_PG_0 */
+	[20480] = { 0x00FF, 0x00FF, 0x0000 }, /* R20480 - DACR_RETUNE_C1_1 */
+	[20481] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20481 - DACR_RETUNE_C1_0 */
+	[20482] = { 0x00FF, 0x00FF, 0x0000 }, /* R20482 - DACR_RETUNE_C2_1 */
+	[20483] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20483 - DACR_RETUNE_C2_0 */
+	[20484] = { 0x00FF, 0x00FF, 0x0000 }, /* R20484 - DACR_RETUNE_C3_1 */
+	[20485] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20485 - DACR_RETUNE_C3_0 */
+	[20486] = { 0x00FF, 0x00FF, 0x0000 }, /* R20486 - DACR_RETUNE_C4_1 */
+	[20487] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20487 - DACR_RETUNE_C4_0 */
+	[20488] = { 0x00FF, 0x00FF, 0x0000 }, /* R20488 - DACR_RETUNE_C5_1 */
+	[20489] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20489 - DACR_RETUNE_C5_0 */
+	[20490] = { 0x00FF, 0x00FF, 0x0000 }, /* R20490 - DACR_RETUNE_C6_1 */
+	[20491] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20491 - DACR_RETUNE_C6_0 */
+	[20492] = { 0x00FF, 0x00FF, 0x0000 }, /* R20492 - DACR_RETUNE_C7_1 */
+	[20493] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20493 - DACR_RETUNE_C7_0 */
+	[20494] = { 0x00FF, 0x00FF, 0x0000 }, /* R20494 - DACR_RETUNE_C8_1 */
+	[20495] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20495 - DACR_RETUNE_C8_0 */
+	[20496] = { 0x00FF, 0x00FF, 0x0000 }, /* R20496 - DACR_RETUNE_C9_1 */
+	[20497] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20497 - DACR_RETUNE_C9_0 */
+	[20498] = { 0x00FF, 0x00FF, 0x0000 }, /* R20498 - DACR_RETUNE_C10_1 */
+	[20499] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20499 - DACR_RETUNE_C10_0 */
+	[20500] = { 0x00FF, 0x00FF, 0x0000 }, /* R20500 - DACR_RETUNE_C11_1 */
+	[20501] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20501 - DACR_RETUNE_C11_0 */
+	[20502] = { 0x00FF, 0x00FF, 0x0000 }, /* R20502 - DACR_RETUNE_C12_1 */
+	[20503] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20503 - DACR_RETUNE_C12_0 */
+	[20504] = { 0x00FF, 0x00FF, 0x0000 }, /* R20504 - DACR_RETUNE_C13_1 */
+	[20505] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20505 - DACR_RETUNE_C13_0 */
+	[20506] = { 0x00FF, 0x00FF, 0x0000 }, /* R20506 - DACR_RETUNE_C14_1 */
+	[20507] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20507 - DACR_RETUNE_C14_0 */
+	[20508] = { 0x00FF, 0x00FF, 0x0000 }, /* R20508 - DACR_RETUNE_C15_1 */
+	[20509] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20509 - DACR_RETUNE_C15_0 */
+	[20510] = { 0x00FF, 0x00FF, 0x0000 }, /* R20510 - DACR_RETUNE_C16_1 */
+	[20511] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20511 - DACR_RETUNE_C16_0 */
+	[20512] = { 0x00FF, 0x00FF, 0x0000 }, /* R20512 - DACR_RETUNE_C17_1 */
+	[20513] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20513 - DACR_RETUNE_C17_0 */
+	[20514] = { 0x00FF, 0x00FF, 0x0000 }, /* R20514 - DACR_RETUNE_C18_1 */
+	[20515] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20515 - DACR_RETUNE_C18_0 */
+	[20516] = { 0x00FF, 0x00FF, 0x0000 }, /* R20516 - DACR_RETUNE_C19_1 */
+	[20517] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20517 - DACR_RETUNE_C19_0 */
+	[20518] = { 0x00FF, 0x00FF, 0x0000 }, /* R20518 - DACR_RETUNE_C20_1 */
+	[20519] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20519 - DACR_RETUNE_C20_0 */
+	[20520] = { 0x00FF, 0x00FF, 0x0000 }, /* R20520 - DACR_RETUNE_C21_1 */
+	[20521] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20521 - DACR_RETUNE_C21_0 */
+	[20522] = { 0x00FF, 0x00FF, 0x0000 }, /* R20522 - DACR_RETUNE_C22_1 */
+	[20523] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20523 - DACR_RETUNE_C22_0 */
+	[20524] = { 0x00FF, 0x00FF, 0x0000 }, /* R20524 - DACR_RETUNE_C23_1 */
+	[20525] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20525 - DACR_RETUNE_C23_0 */
+	[20526] = { 0x00FF, 0x00FF, 0x0000 }, /* R20526 - DACR_RETUNE_C24_1 */
+	[20527] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20527 - DACR_RETUNE_C24_0 */
+	[20528] = { 0x00FF, 0x00FF, 0x0000 }, /* R20528 - DACR_RETUNE_C25_1 */
+	[20529] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20529 - DACR_RETUNE_C25_0 */
+	[20530] = { 0x00FF, 0x00FF, 0x0000 }, /* R20530 - DACR_RETUNE_C26_1 */
+	[20531] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20531 - DACR_RETUNE_C26_0 */
+	[20532] = { 0x00FF, 0x00FF, 0x0000 }, /* R20532 - DACR_RETUNE_C27_1 */
+	[20533] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20533 - DACR_RETUNE_C27_0 */
+	[20534] = { 0x00FF, 0x00FF, 0x0000 }, /* R20534 - DACR_RETUNE_C28_1 */
+	[20535] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20535 - DACR_RETUNE_C28_0 */
+	[20536] = { 0x00FF, 0x00FF, 0x0000 }, /* R20536 - DACR_RETUNE_C29_1 */
+	[20537] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20537 - DACR_RETUNE_C29_0 */
+	[20538] = { 0x00FF, 0x00FF, 0x0000 }, /* R20538 - DACR_RETUNE_C30_1 */
+	[20539] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20539 - DACR_RETUNE_C30_0 */
+	[20540] = { 0x00FF, 0x00FF, 0x0000 }, /* R20540 - DACR_RETUNE_C31_1 */
+	[20541] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20541 - DACR_RETUNE_C31_0 */
+	[20542] = { 0x00FF, 0x00FF, 0x0000 }, /* R20542 - DACR_RETUNE_C32_1 */
+	[20543] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20543 - DACR_RETUNE_C32_0 */
+	[20992] = { 0x00FF, 0x00FF, 0x0000 }, /* R20992 - VSS_XHD2_1 */
+	[20993] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20993 - VSS_XHD2_0 */
+	[20994] = { 0x00FF, 0x00FF, 0x0000 }, /* R20994 - VSS_XHD3_1 */
+	[20995] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20995 - VSS_XHD3_0 */
+	[20996] = { 0x00FF, 0x00FF, 0x0000 }, /* R20996 - VSS_XHN1_1 */
+	[20997] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20997 - VSS_XHN1_0 */
+	[20998] = { 0x00FF, 0x00FF, 0x0000 }, /* R20998 - VSS_XHN2_1 */
+	[20999] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R20999 - VSS_XHN2_0 */
+	[21000] = { 0x00FF, 0x00FF, 0x0000 }, /* R21000 - VSS_XHN3_1 */
+	[21001] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21001 - VSS_XHN3_0 */
+	[21002] = { 0x00FF, 0x00FF, 0x0000 }, /* R21002 - VSS_XLA_1 */
+	[21003] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21003 - VSS_XLA_0 */
+	[21004] = { 0x00FF, 0x00FF, 0x0000 }, /* R21004 - VSS_XLB_1 */
+	[21005] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21005 - VSS_XLB_0 */
+	[21006] = { 0x00FF, 0x00FF, 0x0000 }, /* R21006 - VSS_XLG_1 */
+	[21007] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21007 - VSS_XLG_0 */
+	[21008] = { 0x00FF, 0x00FF, 0x0000 }, /* R21008 - VSS_PG2_1 */
+	[21009] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21009 - VSS_PG2_0 */
+	[21010] = { 0x00FF, 0x00FF, 0x0000 }, /* R21010 - VSS_PG_1 */
+	[21011] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21011 - VSS_PG_0 */
+	[21012] = { 0x00FF, 0x00FF, 0x0000 }, /* R21012 - VSS_XTD1_1 */
+	[21013] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21013 - VSS_XTD1_0 */
+	[21014] = { 0x00FF, 0x00FF, 0x0000 }, /* R21014 - VSS_XTD2_1 */
+	[21015] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21015 - VSS_XTD2_0 */
+	[21016] = { 0x00FF, 0x00FF, 0x0000 }, /* R21016 - VSS_XTD3_1 */
+	[21017] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21017 - VSS_XTD3_0 */
+	[21018] = { 0x00FF, 0x00FF, 0x0000 }, /* R21018 - VSS_XTD4_1 */
+	[21019] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21019 - VSS_XTD4_0 */
+	[21020] = { 0x00FF, 0x00FF, 0x0000 }, /* R21020 - VSS_XTD5_1 */
+	[21021] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21021 - VSS_XTD5_0 */
+	[21022] = { 0x00FF, 0x00FF, 0x0000 }, /* R21022 - VSS_XTD6_1 */
+	[21023] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21023 - VSS_XTD6_0 */
+	[21024] = { 0x00FF, 0x00FF, 0x0000 }, /* R21024 - VSS_XTD7_1 */
+	[21025] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21025 - VSS_XTD7_0 */
+	[21026] = { 0x00FF, 0x00FF, 0x0000 }, /* R21026 - VSS_XTD8_1 */
+	[21027] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21027 - VSS_XTD8_0 */
+	[21028] = { 0x00FF, 0x00FF, 0x0000 }, /* R21028 - VSS_XTD9_1 */
+	[21029] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21029 - VSS_XTD9_0 */
+	[21030] = { 0x00FF, 0x00FF, 0x0000 }, /* R21030 - VSS_XTD10_1 */
+	[21031] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21031 - VSS_XTD10_0 */
+	[21032] = { 0x00FF, 0x00FF, 0x0000 }, /* R21032 - VSS_XTD11_1 */
+	[21033] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21033 - VSS_XTD11_0 */
+	[21034] = { 0x00FF, 0x00FF, 0x0000 }, /* R21034 - VSS_XTD12_1 */
+	[21035] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21035 - VSS_XTD12_0 */
+	[21036] = { 0x00FF, 0x00FF, 0x0000 }, /* R21036 - VSS_XTD13_1 */
+	[21037] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21037 - VSS_XTD13_0 */
+	[21038] = { 0x00FF, 0x00FF, 0x0000 }, /* R21038 - VSS_XTD14_1 */
+	[21039] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21039 - VSS_XTD14_0 */
+	[21040] = { 0x00FF, 0x00FF, 0x0000 }, /* R21040 - VSS_XTD15_1 */
+	[21041] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21041 - VSS_XTD15_0 */
+	[21042] = { 0x00FF, 0x00FF, 0x0000 }, /* R21042 - VSS_XTD16_1 */
+	[21043] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21043 - VSS_XTD16_0 */
+	[21044] = { 0x00FF, 0x00FF, 0x0000 }, /* R21044 - VSS_XTD17_1 */
+	[21045] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21045 - VSS_XTD17_0 */
+	[21046] = { 0x00FF, 0x00FF, 0x0000 }, /* R21046 - VSS_XTD18_1 */
+	[21047] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21047 - VSS_XTD18_0 */
+	[21048] = { 0x00FF, 0x00FF, 0x0000 }, /* R21048 - VSS_XTD19_1 */
+	[21049] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21049 - VSS_XTD19_0 */
+	[21050] = { 0x00FF, 0x00FF, 0x0000 }, /* R21050 - VSS_XTD20_1 */
+	[21051] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21051 - VSS_XTD20_0 */
+	[21052] = { 0x00FF, 0x00FF, 0x0000 }, /* R21052 - VSS_XTD21_1 */
+	[21053] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21053 - VSS_XTD21_0 */
+	[21054] = { 0x00FF, 0x00FF, 0x0000 }, /* R21054 - VSS_XTD22_1 */
+	[21055] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21055 - VSS_XTD22_0 */
+	[21056] = { 0x00FF, 0x00FF, 0x0000 }, /* R21056 - VSS_XTD23_1 */
+	[21057] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21057 - VSS_XTD23_0 */
+	[21058] = { 0x00FF, 0x00FF, 0x0000 }, /* R21058 - VSS_XTD24_1 */
+	[21059] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21059 - VSS_XTD24_0 */
+	[21060] = { 0x00FF, 0x00FF, 0x0000 }, /* R21060 - VSS_XTD25_1 */
+	[21061] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21061 - VSS_XTD25_0 */
+	[21062] = { 0x00FF, 0x00FF, 0x0000 }, /* R21062 - VSS_XTD26_1 */
+	[21063] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21063 - VSS_XTD26_0 */
+	[21064] = { 0x00FF, 0x00FF, 0x0000 }, /* R21064 - VSS_XTD27_1 */
+	[21065] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21065 - VSS_XTD27_0 */
+	[21066] = { 0x00FF, 0x00FF, 0x0000 }, /* R21066 - VSS_XTD28_1 */
+	[21067] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21067 - VSS_XTD28_0 */
+	[21068] = { 0x00FF, 0x00FF, 0x0000 }, /* R21068 - VSS_XTD29_1 */
+	[21069] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21069 - VSS_XTD29_0 */
+	[21070] = { 0x00FF, 0x00FF, 0x0000 }, /* R21070 - VSS_XTD30_1 */
+	[21071] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21071 - VSS_XTD30_0 */
+	[21072] = { 0x00FF, 0x00FF, 0x0000 }, /* R21072 - VSS_XTD31_1 */
+	[21073] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21073 - VSS_XTD31_0 */
+	[21074] = { 0x00FF, 0x00FF, 0x0000 }, /* R21074 - VSS_XTD32_1 */
+	[21075] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21075 - VSS_XTD32_0 */
+	[21076] = { 0x00FF, 0x00FF, 0x0000 }, /* R21076 - VSS_XTS1_1 */
+	[21077] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21077 - VSS_XTS1_0 */
+	[21078] = { 0x00FF, 0x00FF, 0x0000 }, /* R21078 - VSS_XTS2_1 */
+	[21079] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21079 - VSS_XTS2_0 */
+	[21080] = { 0x00FF, 0x00FF, 0x0000 }, /* R21080 - VSS_XTS3_1 */
+	[21081] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21081 - VSS_XTS3_0 */
+	[21082] = { 0x00FF, 0x00FF, 0x0000 }, /* R21082 - VSS_XTS4_1 */
+	[21083] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21083 - VSS_XTS4_0 */
+	[21084] = { 0x00FF, 0x00FF, 0x0000 }, /* R21084 - VSS_XTS5_1 */
+	[21085] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21085 - VSS_XTS5_0 */
+	[21086] = { 0x00FF, 0x00FF, 0x0000 }, /* R21086 - VSS_XTS6_1 */
+	[21087] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21087 - VSS_XTS6_0 */
+	[21088] = { 0x00FF, 0x00FF, 0x0000 }, /* R21088 - VSS_XTS7_1 */
+	[21089] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21089 - VSS_XTS7_0 */
+	[21090] = { 0x00FF, 0x00FF, 0x0000 }, /* R21090 - VSS_XTS8_1 */
+	[21091] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21091 - VSS_XTS8_0 */
+	[21092] = { 0x00FF, 0x00FF, 0x0000 }, /* R21092 - VSS_XTS9_1 */
+	[21093] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21093 - VSS_XTS9_0 */
+	[21094] = { 0x00FF, 0x00FF, 0x0000 }, /* R21094 - VSS_XTS10_1 */
+	[21095] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21095 - VSS_XTS10_0 */
+	[21096] = { 0x00FF, 0x00FF, 0x0000 }, /* R21096 - VSS_XTS11_1 */
+	[21097] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21097 - VSS_XTS11_0 */
+	[21098] = { 0x00FF, 0x00FF, 0x0000 }, /* R21098 - VSS_XTS12_1 */
+	[21099] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21099 - VSS_XTS12_0 */
+	[21100] = { 0x00FF, 0x00FF, 0x0000 }, /* R21100 - VSS_XTS13_1 */
+	[21101] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21101 - VSS_XTS13_0 */
+	[21102] = { 0x00FF, 0x00FF, 0x0000 }, /* R21102 - VSS_XTS14_1 */
+	[21103] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21103 - VSS_XTS14_0 */
+	[21104] = { 0x00FF, 0x00FF, 0x0000 }, /* R21104 - VSS_XTS15_1 */
+	[21105] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21105 - VSS_XTS15_0 */
+	[21106] = { 0x00FF, 0x00FF, 0x0000 }, /* R21106 - VSS_XTS16_1 */
+	[21107] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21107 - VSS_XTS16_0 */
+	[21108] = { 0x00FF, 0x00FF, 0x0000 }, /* R21108 - VSS_XTS17_1 */
+	[21109] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21109 - VSS_XTS17_0 */
+	[21110] = { 0x00FF, 0x00FF, 0x0000 }, /* R21110 - VSS_XTS18_1 */
+	[21111] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21111 - VSS_XTS18_0 */
+	[21112] = { 0x00FF, 0x00FF, 0x0000 }, /* R21112 - VSS_XTS19_1 */
+	[21113] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21113 - VSS_XTS19_0 */
+	[21114] = { 0x00FF, 0x00FF, 0x0000 }, /* R21114 - VSS_XTS20_1 */
+	[21115] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21115 - VSS_XTS20_0 */
+	[21116] = { 0x00FF, 0x00FF, 0x0000 }, /* R21116 - VSS_XTS21_1 */
+	[21117] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21117 - VSS_XTS21_0 */
+	[21118] = { 0x00FF, 0x00FF, 0x0000 }, /* R21118 - VSS_XTS22_1 */
+	[21119] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21119 - VSS_XTS22_0 */
+	[21120] = { 0x00FF, 0x00FF, 0x0000 }, /* R21120 - VSS_XTS23_1 */
+	[21121] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21121 - VSS_XTS23_0 */
+	[21122] = { 0x00FF, 0x00FF, 0x0000 }, /* R21122 - VSS_XTS24_1 */
+	[21123] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21123 - VSS_XTS24_0 */
+	[21124] = { 0x00FF, 0x00FF, 0x0000 }, /* R21124 - VSS_XTS25_1 */
+	[21125] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21125 - VSS_XTS25_0 */
+	[21126] = { 0x00FF, 0x00FF, 0x0000 }, /* R21126 - VSS_XTS26_1 */
+	[21127] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21127 - VSS_XTS26_0 */
+	[21128] = { 0x00FF, 0x00FF, 0x0000 }, /* R21128 - VSS_XTS27_1 */
+	[21129] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21129 - VSS_XTS27_0 */
+	[21130] = { 0x00FF, 0x00FF, 0x0000 }, /* R21130 - VSS_XTS28_1 */
+	[21131] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21131 - VSS_XTS28_0 */
+	[21132] = { 0x00FF, 0x00FF, 0x0000 }, /* R21132 - VSS_XTS29_1 */
+	[21133] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21133 - VSS_XTS29_0 */
+	[21134] = { 0x00FF, 0x00FF, 0x0000 }, /* R21134 - VSS_XTS30_1 */
+	[21135] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21135 - VSS_XTS30_0 */
+	[21136] = { 0x00FF, 0x00FF, 0x0000 }, /* R21136 - VSS_XTS31_1 */
+	[21137] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21137 - VSS_XTS31_0 */
+	[21138] = { 0x00FF, 0x00FF, 0x0000 }, /* R21138 - VSS_XTS32_1 */
+	[21139] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21139 - VSS_XTS32_0 */
+};
+
+static int wm8962_volatile_register(unsigned int reg)
+{
+	if (wm8962_reg_access[reg].vol)
+		return 1;
+	else
+		return 0;
+}
+
+static int wm8962_readable_register(unsigned int reg)
+{
+	if (wm8962_reg_access[reg].read)
+		return 1;
+	else
+		return 0;
+}
+
+static int wm8962_reset(struct snd_soc_codec *codec)
+{
+	return snd_soc_write(codec, WM8962_SOFTWARE_RESET, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, -2325, 75, 0);
+static const DECLARE_TLV_DB_SCALE(mixin_tlv, -1500, 300, 0);
+static const unsigned int mixinpga_tlv[] = {
+	TLV_DB_RANGE_HEAD(7),
+	0, 1, TLV_DB_SCALE_ITEM(0, 600, 0),
+	2, 2, TLV_DB_SCALE_ITEM(1300, 1300, 0),
+	3, 4, TLV_DB_SCALE_ITEM(1800, 200, 0),
+	5, 5, TLV_DB_SCALE_ITEM(2400, 0, 0),
+	6, 7, TLV_DB_SCALE_ITEM(2700, 300, 0),
+};
+static const DECLARE_TLV_DB_SCALE(beep_tlv, -9600, 600, 1);
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(st_tlv, -3600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(inmix_tlv, -600, 600, 0);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(hp_tlv, -700, 100, 0);
+static const unsigned int classd_tlv[] = {
+	TLV_DB_RANGE_HEAD(7),
+	0, 6, TLV_DB_SCALE_ITEM(0, 150, 0),
+	7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0),
+};
+
+/* The VU bits for the headphones are in a different register to the mute
+ * bits and only take effect on the PGA if it is actually powered.
+ */
+static int wm8962_put_hp_sw(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	u16 *reg_cache = codec->reg_cache;
+	int ret;
+
+	/* Apply the update (if any) */
+        ret = snd_soc_put_volsw(kcontrol, ucontrol);
+	if (ret == 0)
+		return 0;
+
+	/* If the left PGA is enabled hit that VU bit... */
+	if (reg_cache[WM8962_PWR_MGMT_2] & WM8962_HPOUTL_PGA_ENA)
+		return snd_soc_write(codec, WM8962_HPOUTL_VOLUME,
+				     reg_cache[WM8962_HPOUTL_VOLUME]);
+
+	/* ...otherwise the right.  The VU is stereo. */
+	if (reg_cache[WM8962_PWR_MGMT_2] & WM8962_HPOUTR_PGA_ENA)
+		return snd_soc_write(codec, WM8962_HPOUTR_VOLUME,
+				     reg_cache[WM8962_HPOUTR_VOLUME]);
+
+	return 0;
+}
+
+/* The VU bits for the speakers are in a different register to the mute
+ * bits and only take effect on the PGA if it is actually powered.
+ */
+static int wm8962_put_spk_sw(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	u16 *reg_cache = codec->reg_cache;
+	int ret;
+
+	/* Apply the update (if any) */
+        ret = snd_soc_put_volsw(kcontrol, ucontrol);
+	if (ret == 0)
+		return 0;
+
+	/* If the left PGA is enabled hit that VU bit... */
+	if (reg_cache[WM8962_PWR_MGMT_2] & WM8962_SPKOUTL_PGA_ENA)
+		return snd_soc_write(codec, WM8962_SPKOUTL_VOLUME,
+				     reg_cache[WM8962_SPKOUTL_VOLUME]);
+
+	/* ...otherwise the right.  The VU is stereo. */
+	if (reg_cache[WM8962_PWR_MGMT_2] & WM8962_SPKOUTR_PGA_ENA)
+		return snd_soc_write(codec, WM8962_SPKOUTR_VOLUME,
+				     reg_cache[WM8962_SPKOUTR_VOLUME]);
+
+	return 0;
+}
+
+static const struct snd_kcontrol_new wm8962_snd_controls[] = {
+SOC_DOUBLE("Input Mixer Switch", WM8962_INPUT_MIXER_CONTROL_1, 3, 2, 1, 1),
+
+SOC_SINGLE_TLV("MIXINL IN2L Volume", WM8962_LEFT_INPUT_MIXER_VOLUME, 6, 7, 0,
+	       mixin_tlv),
+SOC_SINGLE_TLV("MIXINL PGA Volume", WM8962_LEFT_INPUT_MIXER_VOLUME, 3, 7, 0,
+	       mixinpga_tlv),
+SOC_SINGLE_TLV("MIXINL IN3L Volume", WM8962_LEFT_INPUT_MIXER_VOLUME, 0, 7, 0,
+	       mixin_tlv),
+
+SOC_SINGLE_TLV("MIXINR IN2R Volume", WM8962_RIGHT_INPUT_MIXER_VOLUME, 6, 7, 0,
+	       mixin_tlv),
+SOC_SINGLE_TLV("MIXINR PGA Volume", WM8962_RIGHT_INPUT_MIXER_VOLUME, 3, 7, 0,
+	       mixinpga_tlv),
+SOC_SINGLE_TLV("MIXINR IN3R Volume", WM8962_RIGHT_INPUT_MIXER_VOLUME, 0, 7, 0,
+	       mixin_tlv),
+
+SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8962_LEFT_ADC_VOLUME,
+		 WM8962_RIGHT_ADC_VOLUME, 1, 127, 0, digital_tlv),
+SOC_DOUBLE_R_TLV("Capture Volume", WM8962_LEFT_INPUT_VOLUME,
+		 WM8962_RIGHT_INPUT_VOLUME, 0, 63, 0, inpga_tlv),
+SOC_DOUBLE_R("Capture Switch", WM8962_LEFT_INPUT_VOLUME,
+	     WM8962_RIGHT_INPUT_VOLUME, 7, 1, 1),
+SOC_DOUBLE_R("Capture ZC Switch", WM8962_LEFT_INPUT_VOLUME,
+	     WM8962_RIGHT_INPUT_VOLUME, 6, 1, 1),
+
+SOC_DOUBLE_R_TLV("Sidetone Volume", WM8962_DAC_DSP_MIXING_1,
+		 WM8962_DAC_DSP_MIXING_2, 4, 12, 0, st_tlv),
+
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8962_LEFT_DAC_VOLUME,
+		 WM8962_RIGHT_DAC_VOLUME, 1, 127, 0, digital_tlv),
+SOC_SINGLE("DAC High Performance Switch", WM8962_ADC_DAC_CONTROL_2, 0, 1, 0),
+
+SOC_SINGLE("ADC High Performance Switch", WM8962_ADDITIONAL_CONTROL_1,
+	   5, 1, 0),
+
+SOC_SINGLE_TLV("Beep Volume", WM8962_BEEP_GENERATOR_1, 4, 15, 0, beep_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Volume", WM8962_HPOUTL_VOLUME,
+		 WM8962_HPOUTR_VOLUME, 0, 127, 0, out_tlv),
+SOC_DOUBLE_EXT("Headphone Switch", WM8962_PWR_MGMT_2, 1, 0, 1, 1,
+	       snd_soc_get_volsw, wm8962_put_hp_sw),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8962_HPOUTL_VOLUME, WM8962_HPOUTR_VOLUME,
+	     7, 1, 0),
+SOC_DOUBLE_TLV("Headphone Aux Volume", WM8962_ANALOGUE_HP_2, 3, 6, 7, 0,
+	       hp_tlv),
+
+SOC_DOUBLE_R("Headphone Mixer Switch", WM8962_HEADPHONE_MIXER_3,
+	     WM8962_HEADPHONE_MIXER_4, 8, 1, 1),
+
+SOC_SINGLE_TLV("HPMIXL IN4L Volume", WM8962_HEADPHONE_MIXER_3,
+	       3, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("HPMIXL IN4R Volume", WM8962_HEADPHONE_MIXER_3,
+	       0, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("HPMIXL MIXINL Volume", WM8962_HEADPHONE_MIXER_3,
+	       7, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("HPMIXL MIXINR Volume", WM8962_HEADPHONE_MIXER_3,
+	       6, 1, 1, inmix_tlv),
+
+SOC_SINGLE_TLV("HPMIXR IN4L Volume", WM8962_HEADPHONE_MIXER_4,
+	       3, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("HPMIXR IN4R Volume", WM8962_HEADPHONE_MIXER_4,
+	       0, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("HPMIXR MIXINL Volume", WM8962_HEADPHONE_MIXER_4,
+	       7, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("HPMIXR MIXINR Volume", WM8962_HEADPHONE_MIXER_4,
+	       6, 1, 1, inmix_tlv),
+
+SOC_SINGLE_TLV("Speaker Boost Volume", WM8962_CLASS_D_CONTROL_2, 0, 7, 0,
+	       classd_tlv),
+};
+
+static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = {
+SOC_SINGLE_TLV("Speaker Volume", WM8962_SPKOUTL_VOLUME, 0, 127, 0, out_tlv),
+SOC_SINGLE_EXT("Speaker Switch", WM8962_CLASS_D_CONTROL_1, 1, 1, 1,
+	       snd_soc_get_volsw, wm8962_put_spk_sw),
+SOC_SINGLE("Speaker ZC Switch", WM8962_SPKOUTL_VOLUME, 7, 1, 0),
+
+SOC_SINGLE("Speaker Mixer Switch", WM8962_SPEAKER_MIXER_3, 8, 1, 1),
+SOC_SINGLE_TLV("Speaker Mixer IN4L Volume", WM8962_SPEAKER_MIXER_3,
+	       3, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("Speaker Mixer IN4R Volume", WM8962_SPEAKER_MIXER_3,
+	       0, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("Speaker Mixer MIXINL Volume", WM8962_SPEAKER_MIXER_3,
+	       7, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("Speaker Mixer MIXINR Volume", WM8962_SPEAKER_MIXER_3,
+	       6, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("Speaker Mixer DACL Volume", WM8962_SPEAKER_MIXER_5,
+	       7, 1, 0, inmix_tlv),
+SOC_SINGLE_TLV("Speaker Mixer DACR Volume", WM8962_SPEAKER_MIXER_5,
+	       6, 1, 0, inmix_tlv),
+};
+
+static const struct snd_kcontrol_new wm8962_spk_stereo_controls[] = {
+SOC_DOUBLE_R_TLV("Speaker Volume", WM8962_SPKOUTL_VOLUME,
+		 WM8962_SPKOUTR_VOLUME, 0, 127, 0, out_tlv),
+SOC_DOUBLE_EXT("Speaker Switch", WM8962_CLASS_D_CONTROL_1, 1, 0, 1, 1,
+	       snd_soc_get_volsw, wm8962_put_spk_sw),
+SOC_DOUBLE_R("Speaker ZC Switch", WM8962_SPKOUTL_VOLUME, WM8962_SPKOUTR_VOLUME,
+	     7, 1, 0),
+
+SOC_DOUBLE_R("Speaker Mixer Switch", WM8962_SPEAKER_MIXER_3,
+	     WM8962_SPEAKER_MIXER_4, 8, 1, 1),
+
+SOC_SINGLE_TLV("SPKOUTL Mixer IN4L Volume", WM8962_SPEAKER_MIXER_3,
+	       3, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("SPKOUTL Mixer IN4R Volume", WM8962_SPEAKER_MIXER_3,
+	       0, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("SPKOUTL Mixer MIXINL Volume", WM8962_SPEAKER_MIXER_3,
+	       7, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTL Mixer MIXINR Volume", WM8962_SPEAKER_MIXER_3,
+	       6, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTL Mixer DACL Volume", WM8962_SPEAKER_MIXER_5,
+	       7, 1, 0, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTL Mixer DACR Volume", WM8962_SPEAKER_MIXER_5,
+	       6, 1, 0, inmix_tlv),
+
+SOC_SINGLE_TLV("SPKOUTR Mixer IN4L Volume", WM8962_SPEAKER_MIXER_4,
+	       3, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("SPKOUTR Mixer IN4R Volume", WM8962_SPEAKER_MIXER_4,
+	       0, 7, 0, bypass_tlv),
+SOC_SINGLE_TLV("SPKOUTR Mixer MIXINL Volume", WM8962_SPEAKER_MIXER_4,
+	       7, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTR Mixer MIXINR Volume", WM8962_SPEAKER_MIXER_4,
+	       6, 1, 1, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTR Mixer DACL Volume", WM8962_SPEAKER_MIXER_5,
+	       5, 1, 0, inmix_tlv),
+SOC_SINGLE_TLV("SPKOUTR Mixer DACR Volume", WM8962_SPEAKER_MIXER_5,
+	       4, 1, 0, inmix_tlv),
+};
+
+static int sysclk_event(struct snd_soc_dapm_widget *w,
+			struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	int src;
+	int fll;
+
+	src = snd_soc_read(codec, WM8962_CLOCKING2) & WM8962_SYSCLK_SRC_MASK;
+
+	switch (src) {
+	case 0:      /* MCLK */
+		fll = 0;
+		break;
+	case 0x200:  /* FLL */
+		fll = 1;
+		break;
+	default:
+		dev_err(codec->dev, "Unknown SYSCLK source %x\n", src);
+		return -EINVAL;
+	}
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		if (fll)
+			snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
+					    WM8962_FLL_ENA, WM8962_FLL_ENA);
+		break;
+
+	case SND_SOC_DAPM_POST_PMD:
+		if (fll)
+			snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
+					    WM8962_FLL_ENA, 0);
+		break;
+
+	default:
+		BUG();
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cp_event(struct snd_soc_dapm_widget *w,
+		    struct snd_kcontrol *kcontrol, int event)
+{
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		msleep(5);
+		break;
+
+	default:
+		BUG();
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int hp_event(struct snd_soc_dapm_widget *w,
+		    struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	int timeout;
+	int reg;
+	int expected = (WM8962_DCS_STARTUP_DONE_HP1L |
+			WM8962_DCS_STARTUP_DONE_HP1R);
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+				    WM8962_HP1L_ENA | WM8962_HP1R_ENA,
+				    WM8962_HP1L_ENA | WM8962_HP1R_ENA);
+		udelay(20);
+
+		snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+				    WM8962_HP1L_ENA_DLY | WM8962_HP1R_ENA_DLY,
+				    WM8962_HP1L_ENA_DLY | WM8962_HP1R_ENA_DLY);
+
+		/* Start the DC servo */
+		snd_soc_update_bits(codec, WM8962_DC_SERVO_1,
+				    WM8962_HP1L_DCS_ENA | WM8962_HP1R_DCS_ENA |
+				    WM8962_HP1L_DCS_STARTUP |
+				    WM8962_HP1R_DCS_STARTUP,
+				    WM8962_HP1L_DCS_ENA | WM8962_HP1R_DCS_ENA |
+				    WM8962_HP1L_DCS_STARTUP |
+				    WM8962_HP1R_DCS_STARTUP);
+
+		/* Wait for it to complete, should be well under 100ms */
+		timeout = 0;
+		do {
+			msleep(1);
+			reg = snd_soc_read(codec, WM8962_DC_SERVO_6);
+			if (reg < 0) {
+				dev_err(codec->dev,
+					"Failed to read DCS status: %d\n",
+					reg);
+				continue;
+			}
+			dev_dbg(codec->dev, "DCS status: %x\n", reg);
+		} while (++timeout < 200 && (reg & expected) != expected);
+
+		if ((reg & expected) != expected)
+			dev_err(codec->dev, "DC servo timed out\n");
+		else
+			dev_dbg(codec->dev, "DC servo complete after %dms\n",
+				timeout);
+
+		snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+				    WM8962_HP1L_ENA_OUTP |
+				    WM8962_HP1R_ENA_OUTP,
+				    WM8962_HP1L_ENA_OUTP |
+				    WM8962_HP1R_ENA_OUTP);
+		udelay(20);
+
+		snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+				    WM8962_HP1L_RMV_SHORT |
+				    WM8962_HP1R_RMV_SHORT,
+				    WM8962_HP1L_RMV_SHORT |
+				    WM8962_HP1R_RMV_SHORT);
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+				    WM8962_HP1L_RMV_SHORT |
+				    WM8962_HP1R_RMV_SHORT, 0);
+
+		udelay(20);
+
+		snd_soc_update_bits(codec, WM8962_DC_SERVO_1,
+				    WM8962_HP1L_DCS_ENA | WM8962_HP1R_DCS_ENA |
+				    WM8962_HP1L_DCS_STARTUP |
+				    WM8962_HP1R_DCS_STARTUP,
+				    0);
+
+		snd_soc_update_bits(codec, WM8962_ANALOGUE_HP_0,
+				    WM8962_HP1L_ENA | WM8962_HP1R_ENA |
+				    WM8962_HP1L_ENA_DLY | WM8962_HP1R_ENA_DLY |
+				    WM8962_HP1L_ENA_OUTP |
+				    WM8962_HP1R_ENA_OUTP, 0);
+				    
+		break;
+
+	default:
+		BUG();
+		return -EINVAL;
+	
+	}
+
+	return 0;
+}
+
+/* VU bits for the output PGAs only take effect while the PGA is powered */
+static int out_pga_event(struct snd_soc_dapm_widget *w,
+			 struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	u16 *reg_cache = codec->reg_cache;
+	int reg;
+
+	switch (w->shift) {
+	case WM8962_HPOUTR_PGA_ENA_SHIFT:
+		reg = WM8962_HPOUTR_VOLUME;
+		break;
+	case WM8962_HPOUTL_PGA_ENA_SHIFT:
+		reg = WM8962_HPOUTL_VOLUME;
+		break;
+	case WM8962_SPKOUTR_PGA_ENA_SHIFT:
+		reg = WM8962_SPKOUTR_VOLUME;
+		break;
+	case WM8962_SPKOUTL_PGA_ENA_SHIFT:
+		reg = WM8962_SPKOUTL_VOLUME;
+		break;
+	default:
+		BUG();
+		return -EINVAL;
+	}
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		return snd_soc_write(codec, reg, reg_cache[reg]);
+	default:
+		BUG();
+		return -EINVAL;
+	}
+}
+
+static const char *st_text[] = { "None", "Right", "Left" };
+
+static const struct soc_enum str_enum =
+	SOC_ENUM_SINGLE(WM8962_DAC_DSP_MIXING_1, 2, 3, st_text);
+
+static const struct snd_kcontrol_new str_mux =
+	SOC_DAPM_ENUM("Right Sidetone", str_enum);
+
+static const struct soc_enum stl_enum =
+	SOC_ENUM_SINGLE(WM8962_DAC_DSP_MIXING_2, 2, 3, st_text);
+
+static const struct snd_kcontrol_new stl_mux =
+	SOC_DAPM_ENUM("Left Sidetone", stl_enum);
+
+static const char *outmux_text[] = { "DAC", "Mixer" };
+
+static const struct soc_enum spkoutr_enum =
+	SOC_ENUM_SINGLE(WM8962_SPEAKER_MIXER_2, 7, 2, outmux_text);
+
+static const struct snd_kcontrol_new spkoutr_mux =
+	SOC_DAPM_ENUM("SPKOUTR Mux", spkoutr_enum);
+
+static const struct soc_enum spkoutl_enum =
+	SOC_ENUM_SINGLE(WM8962_SPEAKER_MIXER_1, 7, 2, outmux_text);
+
+static const struct snd_kcontrol_new spkoutl_mux =
+	SOC_DAPM_ENUM("SPKOUTL Mux", spkoutl_enum);
+
+static const struct soc_enum hpoutr_enum =
+	SOC_ENUM_SINGLE(WM8962_HEADPHONE_MIXER_2, 7, 2, outmux_text);
+
+static const struct snd_kcontrol_new hpoutr_mux =
+	SOC_DAPM_ENUM("HPOUTR Mux", hpoutr_enum);
+
+static const struct soc_enum hpoutl_enum =
+	SOC_ENUM_SINGLE(WM8962_HEADPHONE_MIXER_1, 7, 2, outmux_text);
+
+static const struct snd_kcontrol_new hpoutl_mux =
+	SOC_DAPM_ENUM("HPOUTL Mux", hpoutl_enum);
+
+static const struct snd_kcontrol_new inpgal[] = {
+SOC_DAPM_SINGLE("IN1L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 3, 1, 0),
+SOC_DAPM_SINGLE("IN2L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 2, 1, 0),
+SOC_DAPM_SINGLE("IN3L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new inpgar[] = {
+SOC_DAPM_SINGLE("IN1R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 3, 1, 0),
+SOC_DAPM_SINGLE("IN2R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 2, 1, 0),
+SOC_DAPM_SINGLE("IN3R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixinl[] = {
+SOC_DAPM_SINGLE("IN2L Switch", WM8962_INPUT_MIXER_CONTROL_2, 5, 1, 0),
+SOC_DAPM_SINGLE("IN3L Switch", WM8962_INPUT_MIXER_CONTROL_2, 4, 1, 0),
+SOC_DAPM_SINGLE("PGA Switch", WM8962_INPUT_MIXER_CONTROL_2, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixinr[] = {
+SOC_DAPM_SINGLE("IN2R Switch", WM8962_INPUT_MIXER_CONTROL_2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN3R Switch", WM8962_INPUT_MIXER_CONTROL_2, 1, 1, 0),
+SOC_DAPM_SINGLE("PGA Switch", WM8962_INPUT_MIXER_CONTROL_2, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new hpmixl[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8962_HEADPHONE_MIXER_1, 5, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8962_HEADPHONE_MIXER_1, 4, 1, 0),
+SOC_DAPM_SINGLE("MIXINL Switch", WM8962_HEADPHONE_MIXER_1, 3, 1, 0),
+SOC_DAPM_SINGLE("MIXINR Switch", WM8962_HEADPHONE_MIXER_1, 2, 1, 0),
+SOC_DAPM_SINGLE("IN4L Switch", WM8962_HEADPHONE_MIXER_1, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4R Switch", WM8962_HEADPHONE_MIXER_1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new hpmixr[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8962_HEADPHONE_MIXER_2, 5, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8962_HEADPHONE_MIXER_2, 4, 1, 0),
+SOC_DAPM_SINGLE("MIXINL Switch", WM8962_HEADPHONE_MIXER_2, 3, 1, 0),
+SOC_DAPM_SINGLE("MIXINR Switch", WM8962_HEADPHONE_MIXER_2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN4L Switch", WM8962_HEADPHONE_MIXER_2, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4R Switch", WM8962_HEADPHONE_MIXER_2, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new spkmixl[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8962_SPEAKER_MIXER_1, 5, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8962_SPEAKER_MIXER_1, 4, 1, 0),
+SOC_DAPM_SINGLE("MIXINL Switch", WM8962_SPEAKER_MIXER_1, 3, 1, 0),
+SOC_DAPM_SINGLE("MIXINR Switch", WM8962_SPEAKER_MIXER_1, 2, 1, 0),
+SOC_DAPM_SINGLE("IN4L Switch", WM8962_SPEAKER_MIXER_1, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4R Switch", WM8962_SPEAKER_MIXER_1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new spkmixr[] = {
+SOC_DAPM_SINGLE("DACL Switch", WM8962_SPEAKER_MIXER_2, 5, 1, 0),
+SOC_DAPM_SINGLE("DACR Switch", WM8962_SPEAKER_MIXER_2, 4, 1, 0),
+SOC_DAPM_SINGLE("MIXINL Switch", WM8962_SPEAKER_MIXER_2, 3, 1, 0),
+SOC_DAPM_SINGLE("MIXINR Switch", WM8962_SPEAKER_MIXER_2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN4L Switch", WM8962_SPEAKER_MIXER_2, 1, 1, 0),
+SOC_DAPM_SINGLE("IN4R Switch", WM8962_SPEAKER_MIXER_2, 0, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8962_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1L"),
+SND_SOC_DAPM_INPUT("IN1R"),
+SND_SOC_DAPM_INPUT("IN2L"),
+SND_SOC_DAPM_INPUT("IN2R"),
+SND_SOC_DAPM_INPUT("IN3L"),
+SND_SOC_DAPM_INPUT("IN3R"),
+SND_SOC_DAPM_INPUT("IN4L"),
+SND_SOC_DAPM_INPUT("IN4R"),
+SND_SOC_DAPM_INPUT("Beep"),
+
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8962_PWR_MGMT_1, 1, 0),
+
+SND_SOC_DAPM_SUPPLY("Class G", WM8962_CHARGE_PUMP_B, 0, 1, NULL, 0),
+SND_SOC_DAPM_SUPPLY("SYSCLK", WM8962_CLOCKING2, 5, 0, sysclk_event,
+		    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("Charge Pump", WM8962_CHARGE_PUMP_1, 0, 0, cp_event,
+		    SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_SUPPLY("TOCLK", WM8962_ADDITIONAL_CONTROL_1, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("INPGAL", WM8962_LEFT_INPUT_PGA_CONTROL, 4, 0,
+		   inpgal, ARRAY_SIZE(inpgal)),
+SND_SOC_DAPM_MIXER("INPGAR", WM8962_RIGHT_INPUT_PGA_CONTROL, 4, 0,
+		   inpgar, ARRAY_SIZE(inpgar)),
+SND_SOC_DAPM_MIXER("MIXINL", WM8962_PWR_MGMT_1, 5, 0,
+		   mixinl, ARRAY_SIZE(mixinl)),
+SND_SOC_DAPM_MIXER("MIXINR", WM8962_PWR_MGMT_1, 4, 0,
+		   mixinr, ARRAY_SIZE(mixinr)),
+
+SND_SOC_DAPM_ADC("ADCL", "Capture", WM8962_PWR_MGMT_1, 3, 0),
+SND_SOC_DAPM_ADC("ADCR", "Capture", WM8962_PWR_MGMT_1, 2, 0),
+
+SND_SOC_DAPM_MUX("STL", SND_SOC_NOPM, 0, 0, &stl_mux),
+SND_SOC_DAPM_MUX("STR", SND_SOC_NOPM, 0, 0, &str_mux),
+
+SND_SOC_DAPM_DAC("DACL", "Playback", WM8962_PWR_MGMT_2, 8, 0),
+SND_SOC_DAPM_DAC("DACR", "Playback", WM8962_PWR_MGMT_2, 7, 0),
+
+SND_SOC_DAPM_PGA("Left Bypass", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Bypass", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("HPMIXL", WM8962_MIXER_ENABLES, 3, 0,
+		   hpmixl, ARRAY_SIZE(hpmixl)),
+SND_SOC_DAPM_MIXER("HPMIXR", WM8962_MIXER_ENABLES, 2, 0,
+		   hpmixr, ARRAY_SIZE(hpmixr)),
+
+SND_SOC_DAPM_MUX_E("HPOUTL PGA", WM8962_PWR_MGMT_2, 6, 0, &hpoutl_mux,
+		   out_pga_event, SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_MUX_E("HPOUTR PGA", WM8962_PWR_MGMT_2, 5, 0, &hpoutr_mux,
+		   out_pga_event, SND_SOC_DAPM_POST_PMU),
+
+SND_SOC_DAPM_PGA_E("HPOUT", SND_SOC_NOPM, 0, 0, NULL, 0, hp_event,
+		   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+};
+
+static const struct snd_soc_dapm_widget wm8962_dapm_spk_mono_widgets[] = {
+SND_SOC_DAPM_MIXER("Speaker Mixer", WM8962_MIXER_ENABLES, 1, 0,
+		   spkmixl, ARRAY_SIZE(spkmixl)),
+SND_SOC_DAPM_MUX_E("Speaker PGA", WM8962_PWR_MGMT_2, 4, 0, &spkoutl_mux,
+		   out_pga_event, SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_PGA("Speaker Output", WM8962_CLASS_D_CONTROL_1, 7, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("SPKOUT"),
+};
+
+static const struct snd_soc_dapm_widget wm8962_dapm_spk_stereo_widgets[] = {
+SND_SOC_DAPM_MIXER("SPKOUTL Mixer", WM8962_MIXER_ENABLES, 1, 0,
+		   spkmixl, ARRAY_SIZE(spkmixl)),
+SND_SOC_DAPM_MIXER("SPKOUTR Mixer", WM8962_MIXER_ENABLES, 0, 0,
+		   spkmixr, ARRAY_SIZE(spkmixr)),
+
+SND_SOC_DAPM_MUX_E("SPKOUTL PGA", WM8962_PWR_MGMT_2, 4, 0, &spkoutl_mux,
+		   out_pga_event, SND_SOC_DAPM_POST_PMU),
+SND_SOC_DAPM_MUX_E("SPKOUTR PGA", WM8962_PWR_MGMT_2, 3, 0, &spkoutr_mux,
+		   out_pga_event, SND_SOC_DAPM_POST_PMU),
+
+SND_SOC_DAPM_PGA("SPKOUTR Output", WM8962_CLASS_D_CONTROL_1, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SPKOUTL Output", WM8962_CLASS_D_CONTROL_1, 6, 0, NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("SPKOUTL"),
+SND_SOC_DAPM_OUTPUT("SPKOUTR"),
+};
+
+static const struct snd_soc_dapm_route wm8962_intercon[] = {
+	{ "INPGAL", "IN1L Switch", "IN1L" },
+	{ "INPGAL", "IN2L Switch", "IN2L" },
+	{ "INPGAL", "IN3L Switch", "IN3L" },
+	{ "INPGAL", "IN4L Switch", "IN4L" },
+
+	{ "INPGAR", "IN1R Switch", "IN1R" },
+	{ "INPGAR", "IN2R Switch", "IN2R" },
+	{ "INPGAR", "IN3R Switch", "IN3R" },
+	{ "INPGAR", "IN4R Switch", "IN4R" },
+
+	{ "MIXINL", "IN2L Switch", "IN2L" },
+	{ "MIXINL", "IN3L Switch", "IN3L" },
+	{ "MIXINL", "PGA Switch", "INPGAL" },
+
+	{ "MIXINR", "IN2R Switch", "IN2R" },
+	{ "MIXINR", "IN3R Switch", "IN3R" },
+	{ "MIXINR", "PGA Switch", "INPGAR" },
+
+	{ "MICBIAS", NULL, "SYSCLK" },
+
+	{ "ADCL", NULL, "SYSCLK" },
+	{ "ADCL", NULL, "TOCLK" },
+	{ "ADCL", NULL, "MIXINL" },
+
+	{ "ADCR", NULL, "SYSCLK" },
+	{ "ADCR", NULL, "TOCLK" },
+	{ "ADCR", NULL, "MIXINR" },
+
+	{ "STL", "Left", "ADCL" },
+	{ "STL", "Right", "ADCR" },
+
+	{ "STR", "Left", "ADCL" },
+	{ "STR", "Right", "ADCR" },
+
+	{ "DACL", NULL, "SYSCLK" },
+	{ "DACL", NULL, "TOCLK" },
+	{ "DACL", NULL, "Beep" },
+	{ "DACL", NULL, "STL" },
+
+	{ "DACR", NULL, "SYSCLK" },
+	{ "DACR", NULL, "TOCLK" },
+	{ "DACR", NULL, "Beep" },
+	{ "DACR", NULL, "STR" },
+
+	{ "HPMIXL", "IN4L Switch", "IN4L" },
+	{ "HPMIXL", "IN4R Switch", "IN4R" },
+	{ "HPMIXL", "DACL Switch", "DACL" },
+	{ "HPMIXL", "DACR Switch", "DACR" },
+	{ "HPMIXL", "MIXINL Switch", "MIXINL" },
+	{ "HPMIXL", "MIXINR Switch", "MIXINR" },
+
+	{ "HPMIXR", "IN4L Switch", "IN4L" },
+	{ "HPMIXR", "IN4R Switch", "IN4R" },
+	{ "HPMIXR", "DACL Switch", "DACL" },
+	{ "HPMIXR", "DACR Switch", "DACR" },
+	{ "HPMIXR", "MIXINL Switch", "MIXINL" },
+	{ "HPMIXR", "MIXINR Switch", "MIXINR" },
+
+	{ "Left Bypass", NULL, "HPMIXL" },
+	{ "Left Bypass", NULL, "Class G" },
+
+	{ "Right Bypass", NULL, "HPMIXR" },
+	{ "Right Bypass", NULL, "Class G" },
+
+	{ "HPOUTL PGA", "Mixer", "Left Bypass" },
+	{ "HPOUTL PGA", "DAC", "DACL" },
+
+	{ "HPOUTR PGA", "Mixer", "Right Bypass" },
+	{ "HPOUTR PGA", "DAC", "DACR" },
+
+	{ "HPOUT", NULL, "HPOUTL PGA" },
+	{ "HPOUT", NULL, "HPOUTR PGA" },
+	{ "HPOUT", NULL, "Charge Pump" },
+	{ "HPOUT", NULL, "SYSCLK" },
+	{ "HPOUT", NULL, "TOCLK" },
+
+	{ "HPOUTL", NULL, "HPOUT" },
+	{ "HPOUTR", NULL, "HPOUT" },
+};
+
+static const struct snd_soc_dapm_route wm8962_spk_mono_intercon[] = {
+	{ "Speaker Mixer", "IN4L Switch", "IN4L" },
+	{ "Speaker Mixer", "IN4R Switch", "IN4R" },
+	{ "Speaker Mixer", "DACL Switch", "DACL" },
+	{ "Speaker Mixer", "DACR Switch", "DACR" },
+	{ "Speaker Mixer", "MIXINL Switch", "MIXINL" },
+	{ "Speaker Mixer", "MIXINR Switch", "MIXINR" },
+
+	{ "Speaker PGA", "Mixer", "Speaker Mixer" },
+	{ "Speaker PGA", "DAC", "DACL" },
+
+	{ "Speaker Output", NULL, "Speaker PGA" },
+	{ "Speaker Output", NULL, "SYSCLK" },
+	{ "Speaker Output", NULL, "TOCLK" },
+
+	{ "SPKOUT", NULL, "Speaker Output" },
+};
+
+static const struct snd_soc_dapm_route wm8962_spk_stereo_intercon[] = {
+	{ "SPKOUTL Mixer", "IN4L Switch", "IN4L" },
+	{ "SPKOUTL Mixer", "IN4R Switch", "IN4R" },
+	{ "SPKOUTL Mixer", "DACL Switch", "DACL" },
+	{ "SPKOUTL Mixer", "DACR Switch", "DACR" },
+	{ "SPKOUTL Mixer", "MIXINL Switch", "MIXINL" },
+	{ "SPKOUTL Mixer", "MIXINR Switch", "MIXINR" },
+
+	{ "SPKOUTR Mixer", "IN4L Switch", "IN4L" },
+	{ "SPKOUTR Mixer", "IN4R Switch", "IN4R" },
+	{ "SPKOUTR Mixer", "DACL Switch", "DACL" },
+	{ "SPKOUTR Mixer", "DACR Switch", "DACR" },
+	{ "SPKOUTR Mixer", "MIXINL Switch", "MIXINL" },
+	{ "SPKOUTR Mixer", "MIXINR Switch", "MIXINR" },
+
+	{ "SPKOUTL PGA", "Mixer", "SPKOUTL Mixer" },
+	{ "SPKOUTL PGA", "DAC", "DACL" },
+
+	{ "SPKOUTR PGA", "Mixer", "SPKOUTR Mixer" },
+	{ "SPKOUTR PGA", "DAC", "DACR" },
+
+	{ "SPKOUTL Output", NULL, "SPKOUTL PGA" },
+	{ "SPKOUTL Output", NULL, "SYSCLK" },
+	{ "SPKOUTL Output", NULL, "TOCLK" },
+
+	{ "SPKOUTR Output", NULL, "SPKOUTR PGA" },
+	{ "SPKOUTR Output", NULL, "SYSCLK" },
+	{ "SPKOUTR Output", NULL, "TOCLK" },
+
+	{ "SPKOUTL", NULL, "SPKOUTL Output" },
+	{ "SPKOUTR", NULL, "SPKOUTR Output" },
+};
+
+static int wm8962_add_widgets(struct snd_soc_codec *codec)
+{
+	struct wm8962_pdata *pdata = dev_get_platdata(codec->dev);
+
+	snd_soc_add_controls(codec, wm8962_snd_controls,
+			     ARRAY_SIZE(wm8962_snd_controls));
+	if (pdata && pdata->spk_mono)
+		snd_soc_add_controls(codec, wm8962_spk_mono_controls,
+				     ARRAY_SIZE(wm8962_spk_mono_controls));
+	else
+		snd_soc_add_controls(codec, wm8962_spk_stereo_controls,
+				     ARRAY_SIZE(wm8962_spk_stereo_controls));
+
+
+	snd_soc_dapm_new_controls(codec, wm8962_dapm_widgets,
+				  ARRAY_SIZE(wm8962_dapm_widgets));
+	if (pdata && pdata->spk_mono)
+		snd_soc_dapm_new_controls(codec, wm8962_dapm_spk_mono_widgets,
+					  ARRAY_SIZE(wm8962_dapm_spk_mono_widgets));
+	else
+		snd_soc_dapm_new_controls(codec, wm8962_dapm_spk_stereo_widgets,
+					  ARRAY_SIZE(wm8962_dapm_spk_stereo_widgets));
+
+	snd_soc_dapm_add_routes(codec, wm8962_intercon,
+				ARRAY_SIZE(wm8962_intercon));
+	if (pdata && pdata->spk_mono)
+		snd_soc_dapm_add_routes(codec, wm8962_spk_mono_intercon,
+					ARRAY_SIZE(wm8962_spk_mono_intercon));
+	else
+		snd_soc_dapm_add_routes(codec, wm8962_spk_stereo_intercon,
+					ARRAY_SIZE(wm8962_spk_stereo_intercon));
+
+
+	snd_soc_dapm_disable_pin(codec, "Beep");
+
+	return 0;
+}
+
+static void wm8962_sync_cache(struct snd_soc_codec *codec)
+{
+	u16 *reg_cache = codec->reg_cache;
+	int i;
+
+	if (!codec->cache_sync)
+		return;
+
+	dev_dbg(codec->dev, "Syncing cache\n");
+
+	codec->cache_only = 0;
+
+	/* Sync back cached values if they're different from the
+	 * hardware default.
+	 */
+	for (i = 1; i < codec->driver->reg_cache_size; i++) {
+		if (i == WM8962_SOFTWARE_RESET)
+			continue;
+		if (reg_cache[i] == wm8962_reg[i])
+			continue;
+
+		snd_soc_write(codec, i, reg_cache[i]);
+	}
+
+	codec->cache_sync = 0;
+}
+
+/* -1 for reserved values */
+static const int bclk_divs[] = {
+	1, -1, 2, 3, 4, -1, 6, 8, -1, 12, 16, 24, -1, 32, 32, 32
+};
+
+static void wm8962_configure_bclk(struct snd_soc_codec *codec)
+{
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	int dspclk, i;
+	int clocking2 = 0;
+	int aif2 = 0;
+
+	if (!wm8962->bclk) {
+		dev_dbg(codec->dev, "No BCLK rate configured\n");
+		return;
+	}
+
+	dspclk = snd_soc_read(codec, WM8962_CLOCKING1);
+	if (dspclk < 0) {
+		dev_err(codec->dev, "Failed to read DSPCLK: %d\n", dspclk);
+		return;
+	}
+
+	dspclk = (dspclk & WM8962_DSPCLK_DIV_MASK) >> WM8962_DSPCLK_DIV_SHIFT;
+	switch (dspclk) {
+	case 0:
+		dspclk = wm8962->sysclk_rate;
+		break;
+	case 1:
+		dspclk = wm8962->sysclk_rate / 2;
+		break;
+	case 2:
+		dspclk = wm8962->sysclk_rate / 4;
+		break;
+	default:
+		dev_warn(codec->dev, "Unknown DSPCLK divisor read back\n");
+		dspclk = wm8962->sysclk;
+	}
+
+	dev_dbg(codec->dev, "DSPCLK is %dHz, BCLK %d\n", dspclk, wm8962->bclk);
+
+	/* We're expecting an exact match */
+	for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+		if (bclk_divs[i] < 0)
+			continue;
+
+		if (dspclk / bclk_divs[i] == wm8962->bclk) {
+			dev_dbg(codec->dev, "Selected BCLK_DIV %d for %dHz\n",
+				bclk_divs[i], wm8962->bclk);
+			clocking2 |= i;
+			break;
+		}
+	}
+	if (i == ARRAY_SIZE(bclk_divs)) {
+		dev_err(codec->dev, "Unsupported BCLK ratio %d\n",
+			dspclk / wm8962->bclk);
+		return;
+	}
+
+	aif2 |= wm8962->bclk / wm8962->lrclk;
+	dev_dbg(codec->dev, "Selected LRCLK divisor %d for %dHz\n",
+		wm8962->bclk / wm8962->lrclk, wm8962->lrclk);
+
+	snd_soc_update_bits(codec, WM8962_CLOCKING2,
+			    WM8962_BCLK_DIV_MASK, clocking2);
+	snd_soc_update_bits(codec, WM8962_AUDIO_INTERFACE_2,
+			    WM8962_AIF_RATE_MASK, aif2);
+}
+
+static int wm8962_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	if (level == codec->bias_level)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		/* VMID 2*50k */
+		snd_soc_update_bits(codec, WM8962_PWR_MGMT_1,
+				    WM8962_VMID_SEL_MASK, 0x80);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			ret = regulator_bulk_enable(ARRAY_SIZE(wm8962->supplies),
+						    wm8962->supplies);
+			if (ret != 0) {
+				dev_err(codec->dev,
+					"Failed to enable supplies: %d\n",
+					ret);
+				return ret;
+			}
+
+			wm8962_sync_cache(codec);
+
+			snd_soc_update_bits(codec, WM8962_ANTI_POP,
+					    WM8962_STARTUP_BIAS_ENA |
+					    WM8962_VMID_BUF_ENA,
+					    WM8962_STARTUP_BIAS_ENA |
+					    WM8962_VMID_BUF_ENA);
+
+			/* Bias enable at 2*50k for ramp */
+			snd_soc_update_bits(codec, WM8962_PWR_MGMT_1,
+					    WM8962_VMID_SEL_MASK |
+					    WM8962_BIAS_ENA,
+					    WM8962_BIAS_ENA | 0x180);
+
+			msleep(5);
+
+			snd_soc_update_bits(codec, WM8962_CLOCKING2,
+					    WM8962_CLKREG_OVD,
+					    WM8962_CLKREG_OVD);
+
+			wm8962_configure_bclk(codec);
+		}
+
+		/* VMID 2*250k */
+		snd_soc_update_bits(codec, WM8962_PWR_MGMT_1,
+				    WM8962_VMID_SEL_MASK, 0x100);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		snd_soc_update_bits(codec, WM8962_PWR_MGMT_1,
+				    WM8962_VMID_SEL_MASK | WM8962_BIAS_ENA, 0);
+
+		snd_soc_update_bits(codec, WM8962_ANTI_POP,
+				    WM8962_STARTUP_BIAS_ENA |
+				    WM8962_VMID_BUF_ENA, 0);
+
+		regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies),
+				       wm8962->supplies);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+static const struct {
+	int rate;
+	int reg;
+} sr_vals[] = {
+	{ 48000, 0 },
+	{ 44100, 0 },
+	{ 32000, 1 },
+	{ 22050, 2 },
+	{ 24000, 2 },
+	{ 16000, 3 },
+	{ 11025, 4 },
+	{ 12000, 4 },
+	{ 8000,  5 },
+	{ 88200, 6 },
+	{ 96000, 6 },
+};
+
+static const int sysclk_rates[] = {
+	64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536,
+};
+
+static int wm8962_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	int rate = params_rate(params);
+	int i;
+	int aif0 = 0;
+	int adctl3 = 0;
+	int clocking4 = 0;
+
+	wm8962->bclk = snd_soc_params_to_bclk(params);
+	wm8962->lrclk = params_rate(params);
+
+	for (i = 0; i < ARRAY_SIZE(sr_vals); i++) {
+		if (sr_vals[i].rate == rate) {
+			adctl3 |= sr_vals[i].reg;
+			break;
+		}
+	}
+	if (i == ARRAY_SIZE(sr_vals)) {
+		dev_err(codec->dev, "Unsupported rate %dHz\n", rate);
+		return -EINVAL;
+	}
+
+	if (rate % 8000 == 0)
+		adctl3 |= WM8962_SAMPLE_RATE_INT_MODE;
+
+	for (i = 0; i < ARRAY_SIZE(sysclk_rates); i++) {
+		if (sysclk_rates[i] == wm8962->sysclk_rate / rate) {
+			clocking4 |= i << WM8962_SYSCLK_RATE_SHIFT;
+			break;
+		}
+	}
+	if (i == ARRAY_SIZE(sysclk_rates)) {
+		dev_err(codec->dev, "Unsupported sysclk ratio %d\n",
+			wm8962->sysclk_rate / rate);
+		return -EINVAL;
+	}
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		aif0 |= 0x40;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		aif0 |= 0x80;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		aif0 |= 0xc0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_update_bits(codec, WM8962_AUDIO_INTERFACE_0,
+			    WM8962_WL_MASK, aif0);
+	snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_3,
+			    WM8962_SAMPLE_RATE_INT_MODE |
+			    WM8962_SAMPLE_RATE_MASK, adctl3);
+	snd_soc_update_bits(codec, WM8962_CLOCKING_4,
+			    WM8962_SYSCLK_RATE_MASK, clocking4);
+
+	wm8962_configure_bclk(codec);
+
+	return 0;
+}
+
+static int wm8962_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+				 unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	int src;
+
+	switch (clk_id) {
+	case WM8962_SYSCLK_MCLK:
+		wm8962->sysclk = WM8962_SYSCLK_MCLK;
+		src = 0;
+		break;
+	case WM8962_SYSCLK_FLL:
+		wm8962->sysclk = WM8962_SYSCLK_FLL;
+		src = 1 << WM8962_SYSCLK_SRC_SHIFT;
+		WARN_ON(freq != wm8962->fll_fout);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_update_bits(codec, WM8962_CLOCKING2, WM8962_SYSCLK_SRC_MASK,
+			    src);
+
+	wm8962->sysclk_rate = freq;
+
+	return 0;
+}
+
+static int wm8962_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	int aif0 = 0;
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+		aif0 |= WM8962_LRCLK_INV;
+	case SND_SOC_DAIFMT_DSP_B:
+		aif0 |= 3;
+
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+		case SND_SOC_DAIFMT_IB_NF:
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		aif0 |= 1;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		aif0 |= 2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		aif0 |= WM8962_BCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		aif0 |= WM8962_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		aif0 |= WM8962_BCLK_INV | WM8962_LRCLK_INV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		aif0 |= WM8962_MSTR;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_update_bits(codec, WM8962_AUDIO_INTERFACE_0,
+			    WM8962_FMT_MASK | WM8962_BCLK_INV | WM8962_MSTR |
+			    WM8962_LRCLK_INV, aif0);
+
+	return 0;
+}
+
+struct _fll_div {
+	u16 fll_fratio;
+	u16 fll_outdiv;
+	u16 fll_refclk_div;
+	u16 n;
+	u16 theta;
+	u16 lambda;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static struct {
+	unsigned int min;
+	unsigned int max;
+	u16 fll_fratio;
+	int ratio;
+} fll_fratios[] = {
+	{       0,    64000, 4, 16 },
+	{   64000,   128000, 3,  8 },
+	{  128000,   256000, 2,  4 },
+	{  256000,  1000000, 1,  2 },
+	{ 1000000, 13500000, 0,  1 },
+};
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+		       unsigned int Fout)
+{
+	unsigned int target;
+	unsigned int div;
+	unsigned int fratio, gcd_fll;
+	int i;
+
+	/* Fref must be <=13.5MHz */
+	div = 1;
+	fll_div->fll_refclk_div = 0;
+	while ((Fref / div) > 13500000) {
+		div *= 2;
+		fll_div->fll_refclk_div++;
+
+		if (div > 4) {
+			pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
+			       Fref);
+			return -EINVAL;
+		}
+	}
+
+	pr_debug("FLL Fref=%u Fout=%u\n", Fref, Fout);
+
+	/* Apply the division for our remaining calculations */
+	Fref /= div;
+
+	/* Fvco should be 90-100MHz; don't check the upper bound */
+	div = 2;
+	while (Fout * div < 90000000) {
+		div++;
+		if (div > 64) {
+			pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
+			       Fout);
+			return -EINVAL;
+		}
+	}
+	target = Fout * div;
+	fll_div->fll_outdiv = div - 1;
+
+	pr_debug("FLL Fvco=%dHz\n", target);
+
+	/* Find an appropraite FLL_FRATIO and factor it out of the target */
+	for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
+		if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
+			fll_div->fll_fratio = fll_fratios[i].fll_fratio;
+			fratio = fll_fratios[i].ratio;
+			break;
+		}
+	}
+	if (i == ARRAY_SIZE(fll_fratios)) {
+		pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
+		return -EINVAL;
+	}
+
+	fll_div->n = target / (fratio * Fref);
+
+	if (target % Fref == 0) {
+		fll_div->theta = 0;
+		fll_div->lambda = 0;
+	} else {
+		gcd_fll = gcd(target, fratio * Fref);
+
+		fll_div->theta = (target - (fll_div->n * fratio * Fref))
+			/ gcd_fll;
+		fll_div->lambda = (fratio * Fref) / gcd_fll;
+	}
+
+	pr_debug("FLL N=%x THETA=%x LAMBDA=%x\n",
+		 fll_div->n, fll_div->theta, fll_div->lambda);
+	pr_debug("FLL_FRATIO=%x FLL_OUTDIV=%x FLL_REFCLK_DIV=%x\n",
+		 fll_div->fll_fratio, fll_div->fll_outdiv,
+		 fll_div->fll_refclk_div);
+
+	return 0;
+}
+
+static int wm8962_set_fll(struct snd_soc_dai *dai, int fll_id, int source,
+			  unsigned int Fref, unsigned int Fout)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	struct _fll_div fll_div;
+	int ret;
+	int fll1 = snd_soc_read(codec, WM8962_FLL_CONTROL_1) & WM8962_FLL_ENA;
+
+	/* Any change? */
+	if (source == wm8962->fll_src && Fref == wm8962->fll_fref &&
+	    Fout == wm8962->fll_fout)
+		return 0;
+
+	if (Fout == 0) {
+		dev_dbg(codec->dev, "FLL disabled\n");
+
+		wm8962->fll_fref = 0;
+		wm8962->fll_fout = 0;
+
+		snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
+				    WM8962_FLL_ENA, 0);
+
+		return 0;
+	}
+
+	ret = fll_factors(&fll_div, Fref, Fout);
+	if (ret != 0)
+		return ret;
+
+	switch (fll_id) {
+	case WM8962_FLL_MCLK:
+	case WM8962_FLL_BCLK:
+	case WM8962_FLL_OSC:
+		fll1 |= (fll_id - 1) << WM8962_FLL_REFCLK_SRC_SHIFT;
+		break;
+	case WM8962_FLL_INT:
+		snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
+				    WM8962_FLL_OSC_ENA, WM8962_FLL_OSC_ENA);
+		snd_soc_update_bits(codec, WM8962_FLL_CONTROL_5,
+				    WM8962_FLL_FRC_NCO, WM8962_FLL_FRC_NCO);
+		break;
+	default:
+		dev_err(codec->dev, "Unknown FLL source %d\n", ret);
+		return -EINVAL;
+	}
+
+	if (fll_div.theta || fll_div.lambda)
+		fll1 |= WM8962_FLL_FRAC;
+
+	/* Stop the FLL while we reconfigure */
+	snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1, WM8962_FLL_ENA, 0);
+
+	snd_soc_update_bits(codec, WM8962_FLL_CONTROL_2,
+			    WM8962_FLL_OUTDIV_MASK |
+			    WM8962_FLL_REFCLK_DIV_MASK,
+			    (fll_div.fll_outdiv << WM8962_FLL_OUTDIV_SHIFT) |
+			    (fll_div.fll_refclk_div));
+
+	snd_soc_update_bits(codec, WM8962_FLL_CONTROL_3,
+			    WM8962_FLL_FRATIO_MASK, fll_div.fll_fratio);
+
+	snd_soc_write(codec, WM8962_FLL_CONTROL_6, fll_div.theta);
+	snd_soc_write(codec, WM8962_FLL_CONTROL_7, fll_div.lambda);
+	snd_soc_write(codec, WM8962_FLL_CONTROL_8, fll_div.n);
+
+	snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1,
+			    WM8962_FLL_FRAC | WM8962_FLL_REFCLK_SRC_MASK |
+			    WM8962_FLL_ENA, fll1);
+
+	dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout);
+
+	wm8962->fll_fref = Fref;
+	wm8962->fll_fout = Fout;
+	wm8962->fll_src = source;
+
+	return 0;
+}
+
+static int wm8962_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	int val;
+
+	if (mute)
+		val = WM8962_DAC_MUTE;
+	else
+		val = 0;
+
+	return snd_soc_update_bits(codec, WM8962_ADC_DAC_CONTROL_1,
+				   WM8962_DAC_MUTE, val);
+}
+
+#define WM8962_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8962_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8962_dai_ops = {
+	.hw_params = wm8962_hw_params,
+	.set_sysclk = wm8962_set_dai_sysclk,
+	.set_fmt = wm8962_set_dai_fmt,
+	.set_pll = wm8962_set_fll,
+	.digital_mute = wm8962_mute,
+};
+
+static struct snd_soc_dai_driver wm8962_dai = {
+	.name = "wm8962",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = WM8962_RATES,
+		.formats = WM8962_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = WM8962_RATES,
+		.formats = WM8962_FORMATS,
+	},
+	.ops = &wm8962_dai_ops,
+	.symmetric_rates = 1,
+};
+
+static void wm8962_mic_work(struct work_struct *work)
+{
+	struct wm8962_priv *wm8962 = container_of(work,
+						  struct wm8962_priv,
+						  mic_work.work);
+	struct snd_soc_codec *codec = wm8962->codec;
+	int status = 0;
+	int irq_pol = 0;
+	int reg;
+
+	reg = snd_soc_read(codec, WM8962_ADDITIONAL_CONTROL_4);
+
+	if (reg & WM8962_MICDET_STS) {
+		status |= SND_JACK_MICROPHONE;
+		irq_pol |= WM8962_MICD_IRQ_POL;
+	}
+
+	if (reg & WM8962_MICSHORT_STS) {
+		status |= SND_JACK_BTN_0;
+		irq_pol |= WM8962_MICSCD_IRQ_POL;
+	}
+
+	snd_soc_jack_report(wm8962->jack, status,
+			    SND_JACK_MICROPHONE | SND_JACK_BTN_0);
+
+	snd_soc_update_bits(codec, WM8962_MICINT_SOURCE_POL,
+			    WM8962_MICSCD_IRQ_POL |
+			    WM8962_MICD_IRQ_POL, irq_pol);
+}
+
+static irqreturn_t wm8962_irq(int irq, void *data)
+{
+	struct snd_soc_codec *codec = data;
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	int mask;
+	int active;
+
+	mask = snd_soc_read(codec, WM8962_INTERRUPT_STATUS_2_MASK);
+
+	active = snd_soc_read(codec, WM8962_INTERRUPT_STATUS_2);
+	active &= ~mask;
+
+	if (active & WM8962_FIFOS_ERR_EINT)
+		dev_err(codec->dev, "FIFO error\n");
+
+	if (active & WM8962_TEMP_SHUT_EINT)
+		dev_crit(codec->dev, "Thermal shutdown\n");
+
+	if (active & (WM8962_MICSCD_EINT | WM8962_MICD_EINT)) {
+		dev_dbg(codec->dev, "Microphone event detected\n");
+
+		schedule_delayed_work(&wm8962->mic_work,
+				      msecs_to_jiffies(250));
+	}
+
+	/* Acknowledge the interrupts */
+	snd_soc_write(codec, WM8962_INTERRUPT_STATUS_2, active);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * wm8962_mic_detect - Enable microphone detection via the WM8962 IRQ
+ *
+ * @codec:  WM8962 codec
+ * @jack:   jack to report detection events on
+ *
+ * Enable microphone detection via IRQ on the WM8962.  If GPIOs are
+ * being used to bring out signals to the processor then only platform
+ * data configuration is needed for WM8962 and processor GPIOs should
+ * be configured using snd_soc_jack_add_gpios() instead.
+ *
+ * If no jack is supplied detection will be disabled.
+ */
+int wm8962_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack)
+{
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	int irq_mask, enable;
+
+	wm8962->jack = jack;
+	if (jack) {
+		irq_mask = 0;
+		enable = WM8962_MICDET_ENA;
+	} else {
+		irq_mask = WM8962_MICD_EINT | WM8962_MICSCD_EINT;
+		enable = 0;
+	}
+
+	snd_soc_update_bits(codec, WM8962_INTERRUPT_STATUS_2_MASK,
+			    WM8962_MICD_EINT | WM8962_MICSCD_EINT, irq_mask);
+	snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_4,
+			    WM8962_MICDET_ENA, enable);
+
+	/* Send an initial empty report */
+	snd_soc_jack_report(wm8962->jack, 0,
+			    SND_JACK_MICROPHONE | SND_JACK_BTN_0);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wm8962_mic_detect);
+
+#ifdef CONFIG_PM
+static int wm8962_resume(struct snd_soc_codec *codec)
+{
+	u16 *reg_cache = codec->reg_cache;
+	int i;
+
+	/* Restore the registers */
+	for (i = 1; i < codec->driver->reg_cache_size; i++) {
+		switch (i) {
+		case WM8962_SOFTWARE_RESET:
+			continue;
+		default:
+			break;
+		}
+
+		if (reg_cache[i] != wm8962_reg[i])
+			snd_soc_write(codec, i, reg_cache[i]);
+	}
+
+	return 0;
+}
+#else
+#define wm8962_resume NULL
+#endif
+
+#if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE)
+static int beep_rates[] = {
+	500, 1000, 2000, 4000,
+};
+
+static void wm8962_beep_work(struct work_struct *work)
+{
+	struct wm8962_priv *wm8962 =
+		container_of(work, struct wm8962_priv, beep_work);
+	struct snd_soc_codec *codec = wm8962->codec;
+	int i;
+	int reg = 0;
+	int best = 0;
+
+	if (wm8962->beep_rate) {
+		for (i = 0; i < ARRAY_SIZE(beep_rates); i++) {
+			if (abs(wm8962->beep_rate - beep_rates[i]) <
+			    abs(wm8962->beep_rate - beep_rates[best]))
+				best = i;
+		}
+
+		dev_dbg(codec->dev, "Set beep rate %dHz for requested %dHz\n",
+			beep_rates[best], wm8962->beep_rate);
+
+		reg = WM8962_BEEP_ENA | (best << WM8962_BEEP_RATE_SHIFT);
+
+		snd_soc_dapm_enable_pin(codec, "Beep");
+	} else {
+		dev_dbg(codec->dev, "Disabling beep\n");
+		snd_soc_dapm_disable_pin(codec, "Beep");
+	}
+
+	snd_soc_update_bits(codec, WM8962_BEEP_GENERATOR_1,
+			    WM8962_BEEP_ENA | WM8962_BEEP_RATE_MASK, reg);
+
+	snd_soc_dapm_sync(codec);
+}
+
+/* For usability define a way of injecting beep events for the device -
+ * many systems will not have a keyboard.
+ */
+static int wm8962_beep_event(struct input_dev *dev, unsigned int type,
+			     unsigned int code, int hz)
+{
+	struct snd_soc_codec *codec = input_get_drvdata(dev);
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+
+	dev_dbg(codec->dev, "Beep event %x %x\n", code, hz);
+
+	switch (code) {
+	case SND_BELL:
+		if (hz)
+			hz = 1000;
+	case SND_TONE:
+		break;
+	default:
+		return -1;
+	}
+
+	/* Kick the beep from a workqueue */
+	wm8962->beep_rate = hz;
+	schedule_work(&wm8962->beep_work);
+	return 0;
+}
+
+static ssize_t wm8962_beep_set(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct wm8962_priv *wm8962 = dev_get_drvdata(dev);
+	long int time;
+	int ret;
+
+	ret = strict_strtol(buf, 10, &time);
+	if (ret != 0)
+		return ret;
+
+	input_event(wm8962->beep, EV_SND, SND_TONE, time);
+
+	return count;
+}
+
+static DEVICE_ATTR(beep, 0200, NULL, wm8962_beep_set);
+
+static void wm8962_init_beep(struct snd_soc_codec *codec)
+{
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	wm8962->beep = input_allocate_device();
+	if (!wm8962->beep) {
+		dev_err(codec->dev, "Failed to allocate beep device\n");
+		return;
+	}
+
+	INIT_WORK(&wm8962->beep_work, wm8962_beep_work);
+	wm8962->beep_rate = 0;
+
+	wm8962->beep->name = "WM8962 Beep Generator";
+	wm8962->beep->phys = dev_name(codec->dev);
+	wm8962->beep->id.bustype = BUS_I2C;
+
+	wm8962->beep->evbit[0] = BIT_MASK(EV_SND);
+	wm8962->beep->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+	wm8962->beep->event = wm8962_beep_event;
+	wm8962->beep->dev.parent = codec->dev;
+	input_set_drvdata(wm8962->beep, codec);
+
+	ret = input_register_device(wm8962->beep);
+	if (ret != 0) {
+		input_free_device(wm8962->beep);
+		wm8962->beep = NULL;
+		dev_err(codec->dev, "Failed to register beep device\n");
+	}
+
+	ret = device_create_file(codec->dev, &dev_attr_beep);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to create keyclick file: %d\n",
+			ret);
+	}
+}
+
+static void wm8962_free_beep(struct snd_soc_codec *codec)
+{
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+
+	device_remove_file(codec->dev, &dev_attr_beep);
+	input_unregister_device(wm8962->beep);
+	cancel_work_sync(&wm8962->beep_work);
+	wm8962->beep = NULL;
+
+	snd_soc_update_bits(codec, WM8962_BEEP_GENERATOR_1, WM8962_BEEP_ENA,0);
+}
+#else
+static void wm8962_init_beep(struct snd_soc_codec *codec)
+{
+}
+
+static void wm8962_free_beep(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+static void wm8962_set_gpio_mode(struct snd_soc_codec *codec, int gpio)
+{
+	int mask = 0;
+	int val = 0;
+
+	/* Some of the GPIOs are behind MFP configuration and need to
+	 * be put into GPIO mode. */
+	switch (gpio) {
+	case 2:
+		mask = WM8962_CLKOUT2_SEL_MASK;
+		val = 1 << WM8962_CLKOUT2_SEL_SHIFT;
+		break;
+	case 3:
+		mask = WM8962_CLKOUT3_SEL_MASK;
+		val = 1 << WM8962_CLKOUT3_SEL_SHIFT;
+		break;
+	default:
+		break;
+	}
+
+	if (mask)
+		snd_soc_update_bits(codec, WM8962_ANALOGUE_CLOCKING1,
+				    mask, val);
+}
+
+#ifdef CONFIG_GPIOLIB
+static inline struct wm8962_priv *gpio_to_wm8962(struct gpio_chip *chip)
+{
+	return container_of(chip, struct wm8962_priv, gpio_chip);
+}
+
+static int wm8962_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+	struct wm8962_priv *wm8962 = gpio_to_wm8962(chip);
+	struct snd_soc_codec *codec = wm8962->codec;
+
+	/* The WM8962 GPIOs aren't linearly numbered.  For simplicity
+	 * we export linear numbers and error out if the unsupported
+	 * ones are requsted.
+	 */
+	switch (offset + 1) {
+	case 2:
+	case 3:
+	case 5:
+	case 6:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	wm8962_set_gpio_mode(codec, offset + 1);
+
+	return 0;
+}
+
+static void wm8962_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+	struct wm8962_priv *wm8962 = gpio_to_wm8962(chip);
+	struct snd_soc_codec *codec = wm8962->codec;
+
+	snd_soc_update_bits(codec, WM8962_GPIO_BASE + offset,
+			    WM8962_GP2_LVL, value << WM8962_GP2_LVL_SHIFT);
+}
+
+static int wm8962_gpio_direction_out(struct gpio_chip *chip,
+				     unsigned offset, int value)
+{
+	struct wm8962_priv *wm8962 = gpio_to_wm8962(chip);
+	struct snd_soc_codec *codec = wm8962->codec;
+	int val;
+
+	/* Force function 1 (logic output) */
+	val = (1 << WM8962_GP2_FN_SHIFT) | (value << WM8962_GP2_LVL_SHIFT);
+
+	return snd_soc_update_bits(codec, WM8962_GPIO_BASE + offset,
+				   WM8962_GP2_FN_MASK | WM8962_GP2_LVL, val);
+}
+
+static struct gpio_chip wm8962_template_chip = {
+	.label			= "wm8962",
+	.owner			= THIS_MODULE,
+	.request		= wm8962_gpio_request,
+	.direction_output	= wm8962_gpio_direction_out,
+	.set			= wm8962_gpio_set,
+	.can_sleep		= 1,
+};
+
+static void wm8962_init_gpio(struct snd_soc_codec *codec)
+{
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	struct wm8962_pdata *pdata = dev_get_platdata(codec->dev);
+	int ret;
+
+	wm8962->gpio_chip = wm8962_template_chip;
+	wm8962->gpio_chip.ngpio = WM8962_MAX_GPIO;
+	wm8962->gpio_chip.dev = codec->dev;
+
+	if (pdata && pdata->gpio_base)
+		wm8962->gpio_chip.base = pdata->gpio_base;
+	else
+		wm8962->gpio_chip.base = -1;
+
+	ret = gpiochip_add(&wm8962->gpio_chip);
+	if (ret != 0)
+		dev_err(codec->dev, "Failed to add GPIOs: %d\n", ret);
+}
+
+static void wm8962_free_gpio(struct snd_soc_codec *codec)
+{
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	ret = gpiochip_remove(&wm8962->gpio_chip);
+	if (ret != 0)
+		dev_err(codec->dev, "Failed to remove GPIOs: %d\n", ret);
+}
+#else
+static void wm8962_init_gpio(struct snd_soc_codec *codec)
+{
+}
+
+static void wm8962_free_gpio(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+static int wm8962_probe(struct snd_soc_codec *codec)
+{
+	int ret;
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	struct wm8962_pdata *pdata = dev_get_platdata(codec->dev);
+	struct i2c_client *i2c = container_of(codec->dev, struct i2c_client,
+					      dev);
+	u16 *reg_cache = codec->reg_cache;
+	int i, trigger, irq_pol;
+
+	wm8962->codec = codec;
+	INIT_DELAYED_WORK(&wm8962->mic_work, wm8962_mic_work);
+
+	codec->cache_sync = 1;
+	codec->idle_bias_off = 1;
+
+	ret = snd_soc_codec_set_cache_io(codec, 16, 16, SND_SOC_I2C);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		goto err;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++)
+		wm8962->supplies[i].supply = wm8962_supply_names[i];
+
+	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8962->supplies),
+				 wm8962->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+		goto err;
+	}
+
+	wm8962->disable_nb[0].notifier_call = wm8962_regulator_event_0;
+	wm8962->disable_nb[1].notifier_call = wm8962_regulator_event_1;
+	wm8962->disable_nb[2].notifier_call = wm8962_regulator_event_2;
+	wm8962->disable_nb[3].notifier_call = wm8962_regulator_event_3;
+	wm8962->disable_nb[4].notifier_call = wm8962_regulator_event_4;
+	wm8962->disable_nb[5].notifier_call = wm8962_regulator_event_5;
+	wm8962->disable_nb[6].notifier_call = wm8962_regulator_event_6;
+	wm8962->disable_nb[7].notifier_call = wm8962_regulator_event_7;
+
+	/* This should really be moved into the regulator core */
+	for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++) {
+		ret = regulator_register_notifier(wm8962->supplies[i].consumer,
+						  &wm8962->disable_nb[i]);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to register regulator notifier: %d\n",
+				ret);
+		}
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(wm8962->supplies),
+				    wm8962->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+		goto err_get;
+	}
+
+	ret = snd_soc_read(codec, WM8962_SOFTWARE_RESET);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to read ID register\n");
+		goto err_enable;
+	}
+	if (ret != wm8962_reg[WM8962_SOFTWARE_RESET]) {
+		dev_err(codec->dev, "Device is not a WM8962, ID %x != %x\n",
+			ret, wm8962_reg[WM8962_SOFTWARE_RESET]);
+		ret = -EINVAL;
+		goto err_enable;
+	}
+
+	ret = snd_soc_read(codec, WM8962_RIGHT_INPUT_VOLUME);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to read device revision: %d\n",
+			ret);
+		goto err_enable;
+	}
+	
+	dev_info(codec->dev, "customer id %x revision %c\n",
+		 (ret & WM8962_CUST_ID_MASK) >> WM8962_CUST_ID_SHIFT,
+		 ((ret & WM8962_CHIP_REV_MASK) >> WM8962_CHIP_REV_SHIFT)
+		 + 'A');
+
+	ret = wm8962_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		goto err_enable;
+	}
+
+	/* SYSCLK defaults to on; make sure it is off so we can safely
+	 * write to registers if the device is declocked.
+	 */
+	snd_soc_update_bits(codec, WM8962_CLOCKING2, WM8962_SYSCLK_ENA, 0);
+
+	regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), wm8962->supplies);
+
+	if (pdata) {
+		/* Apply static configuration for GPIOs */
+		for (i = 0; i < ARRAY_SIZE(pdata->gpio_init); i++)
+			if (pdata->gpio_init[i]) {
+				wm8962_set_gpio_mode(codec, i + 1);
+				snd_soc_write(codec, 0x200 + i,
+					      pdata->gpio_init[i] & 0xffff);
+			}
+
+		/* Put the speakers into mono mode? */
+		if (pdata->spk_mono)
+			reg_cache[WM8962_CLASS_D_CONTROL_2]
+				|= WM8962_SPK_MONO;
+
+		/* Micbias setup, detection enable and detection
+		 * threasholds. */
+		if (pdata->mic_cfg)
+			snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_4,
+					    WM8962_MICDET_ENA |
+					    WM8962_MICDET_THR_MASK |
+					    WM8962_MICSHORT_THR_MASK |
+					    WM8962_MICBIAS_LVL,
+					    pdata->mic_cfg);
+	}
+
+	/* Latch volume update bits */
+	reg_cache[WM8962_LEFT_INPUT_VOLUME] |= WM8962_IN_VU;
+	reg_cache[WM8962_RIGHT_INPUT_VOLUME] |= WM8962_IN_VU;
+	reg_cache[WM8962_LEFT_ADC_VOLUME] |= WM8962_ADC_VU;
+	reg_cache[WM8962_RIGHT_ADC_VOLUME] |= WM8962_ADC_VU;
+	reg_cache[WM8962_LEFT_DAC_VOLUME] |= WM8962_DAC_VU;
+	reg_cache[WM8962_RIGHT_DAC_VOLUME] |= WM8962_DAC_VU;
+	reg_cache[WM8962_SPKOUTL_VOLUME] |= WM8962_SPKOUT_VU;
+	reg_cache[WM8962_SPKOUTR_VOLUME] |= WM8962_SPKOUT_VU;
+	reg_cache[WM8962_HPOUTL_VOLUME] |= WM8962_HPOUT_VU;
+	reg_cache[WM8962_HPOUTR_VOLUME] |= WM8962_HPOUT_VU;
+
+	wm8962_add_widgets(codec);
+
+	wm8962_init_beep(codec);
+	wm8962_init_gpio(codec);
+
+	if (i2c->irq) {
+		if (pdata && pdata->irq_active_low) {
+			trigger = IRQF_TRIGGER_LOW;
+			irq_pol = WM8962_IRQ_POL;
+		} else {
+			trigger = IRQF_TRIGGER_HIGH;
+			irq_pol = 0;
+		}
+
+		snd_soc_update_bits(codec, WM8962_INTERRUPT_CONTROL,
+				    WM8962_IRQ_POL, irq_pol);
+
+		ret = request_threaded_irq(i2c->irq, NULL, wm8962_irq,
+					   trigger | IRQF_ONESHOT,
+					   "wm8962", codec);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to request IRQ %d: %d\n",
+				i2c->irq, ret);
+			/* Non-fatal */
+		} else {
+			/* Enable error reporting IRQs by default */
+			snd_soc_update_bits(codec,
+					    WM8962_INTERRUPT_STATUS_2_MASK,
+					    WM8962_TEMP_SHUT_EINT |
+					    WM8962_FIFOS_ERR_EINT, 0);
+		}
+	}
+
+	return 0;
+
+err_enable:
+	regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), wm8962->supplies);
+err_get:
+	regulator_bulk_free(ARRAY_SIZE(wm8962->supplies), wm8962->supplies);
+err:
+	kfree(wm8962);
+	return ret;
+}
+
+static int wm8962_remove(struct snd_soc_codec *codec)
+{
+	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+	struct i2c_client *i2c = container_of(codec->dev, struct i2c_client,
+					      dev);
+	int i;
+
+	if (i2c->irq)
+		free_irq(i2c->irq, codec);
+
+	cancel_delayed_work_sync(&wm8962->mic_work);
+
+	wm8962_free_gpio(codec);
+	wm8962_free_beep(codec);
+	for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++)
+		regulator_unregister_notifier(wm8962->supplies[i].consumer,
+					      &wm8962->disable_nb[i]);
+	regulator_bulk_free(ARRAY_SIZE(wm8962->supplies), wm8962->supplies);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8962 = {
+	.probe =	wm8962_probe,
+	.remove =	wm8962_remove,
+	.resume =	wm8962_resume,
+	.set_bias_level = wm8962_set_bias_level,
+	.reg_cache_size = WM8962_MAX_REGISTER + 1,
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8962_reg,
+	.volatile_register = wm8962_volatile_register,
+	.readable_register = wm8962_readable_register,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8962_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8962_priv *wm8962;
+	int ret;
+
+	wm8962 = kzalloc(sizeof(struct wm8962_priv), GFP_KERNEL);
+	if (wm8962 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8962);
+
+	ret = snd_soc_register_codec(&i2c->dev,
+				     &soc_codec_dev_wm8962, &wm8962_dai, 1);
+	if (ret < 0)
+		kfree(wm8962);
+
+	return ret;
+}
+
+static __devexit int wm8962_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8962_i2c_id[] = {
+	{ "wm8962", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8962_i2c_id);
+
+static struct i2c_driver wm8962_i2c_driver = {
+	.driver = {
+		.name = "wm8962",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8962_i2c_probe,
+	.remove =   __devexit_p(wm8962_i2c_remove),
+	.id_table = wm8962_i2c_id,
+};
+#endif
+
+static int __init wm8962_modinit(void)
+{
+	int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8962_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8962 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return 0;
+}
+module_init(wm8962_modinit);
+
+static void __exit wm8962_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8962_i2c_driver);
+#endif
+}
+module_exit(wm8962_exit);
+
+MODULE_DESCRIPTION("ASoC WM8962 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8962.h b/linux/sound/soc/codecs/wm8962.h
new file mode 100644
index 0000000..a1a5d52
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8962.h
@@ -0,0 +1,3780 @@
+/*
+ * wm8962.h  --  WM8962 ASoC driver
+ *
+ * Copyright 2010 Wolfson Microelectronics, plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8962_H
+#define _WM8962_H
+
+#include <asm/types.h>
+#include <sound/soc.h>
+
+#define WM8962_SYSCLK_MCLK 1
+#define WM8962_SYSCLK_FLL  2
+#define WM8962_SYSCLK_PLL3 3
+
+#define WM8962_FLL  1
+
+#define WM8962_FLL_MCLK 1
+#define WM8962_FLL_BCLK 2
+#define WM8962_FLL_OSC  3
+#define WM8962_FLL_INT  4
+
+/*
+ * Register values.
+ */
+#define WM8962_LEFT_INPUT_VOLUME                0x00
+#define WM8962_RIGHT_INPUT_VOLUME               0x01
+#define WM8962_HPOUTL_VOLUME                    0x02
+#define WM8962_HPOUTR_VOLUME                    0x03
+#define WM8962_CLOCKING1                        0x04
+#define WM8962_ADC_DAC_CONTROL_1                0x05
+#define WM8962_ADC_DAC_CONTROL_2                0x06
+#define WM8962_AUDIO_INTERFACE_0                0x07
+#define WM8962_CLOCKING2                        0x08
+#define WM8962_AUDIO_INTERFACE_1                0x09
+#define WM8962_LEFT_DAC_VOLUME                  0x0A
+#define WM8962_RIGHT_DAC_VOLUME                 0x0B
+#define WM8962_AUDIO_INTERFACE_2                0x0E
+#define WM8962_SOFTWARE_RESET                   0x0F
+#define WM8962_ALC1                             0x11
+#define WM8962_ALC2                             0x12
+#define WM8962_ALC3                             0x13
+#define WM8962_NOISE_GATE                       0x14
+#define WM8962_LEFT_ADC_VOLUME                  0x15
+#define WM8962_RIGHT_ADC_VOLUME                 0x16
+#define WM8962_ADDITIONAL_CONTROL_1             0x17
+#define WM8962_ADDITIONAL_CONTROL_2             0x18
+#define WM8962_PWR_MGMT_1                       0x19
+#define WM8962_PWR_MGMT_2                       0x1A
+#define WM8962_ADDITIONAL_CONTROL_3             0x1B
+#define WM8962_ANTI_POP                         0x1C
+#define WM8962_CLOCKING_3                       0x1E
+#define WM8962_INPUT_MIXER_CONTROL_1            0x1F
+#define WM8962_LEFT_INPUT_MIXER_VOLUME          0x20
+#define WM8962_RIGHT_INPUT_MIXER_VOLUME         0x21
+#define WM8962_INPUT_MIXER_CONTROL_2            0x22
+#define WM8962_INPUT_BIAS_CONTROL               0x23
+#define WM8962_LEFT_INPUT_PGA_CONTROL           0x25
+#define WM8962_RIGHT_INPUT_PGA_CONTROL          0x26
+#define WM8962_SPKOUTL_VOLUME                   0x28
+#define WM8962_SPKOUTR_VOLUME                   0x29
+#define WM8962_THERMAL_SHUTDOWN_STATUS          0x2F
+#define WM8962_ADDITIONAL_CONTROL_4             0x30
+#define WM8962_CLASS_D_CONTROL_1                0x31
+#define WM8962_CLASS_D_CONTROL_2                0x33
+#define WM8962_CLOCKING_4                       0x38
+#define WM8962_DAC_DSP_MIXING_1                 0x39
+#define WM8962_DAC_DSP_MIXING_2                 0x3A
+#define WM8962_DC_SERVO_0                       0x3C
+#define WM8962_DC_SERVO_1                       0x3D
+#define WM8962_DC_SERVO_4                       0x40
+#define WM8962_DC_SERVO_6                       0x42
+#define WM8962_ANALOGUE_PGA_BIAS                0x44
+#define WM8962_ANALOGUE_HP_0                    0x45
+#define WM8962_ANALOGUE_HP_2                    0x47
+#define WM8962_CHARGE_PUMP_1                    0x48
+#define WM8962_CHARGE_PUMP_B                    0x52
+#define WM8962_WRITE_SEQUENCER_CONTROL_1        0x57
+#define WM8962_WRITE_SEQUENCER_CONTROL_2        0x5A
+#define WM8962_WRITE_SEQUENCER_CONTROL_3        0x5D
+#define WM8962_CONTROL_INTERFACE                0x5E
+#define WM8962_MIXER_ENABLES                    0x63
+#define WM8962_HEADPHONE_MIXER_1                0x64
+#define WM8962_HEADPHONE_MIXER_2                0x65
+#define WM8962_HEADPHONE_MIXER_3                0x66
+#define WM8962_HEADPHONE_MIXER_4                0x67
+#define WM8962_SPEAKER_MIXER_1                  0x69
+#define WM8962_SPEAKER_MIXER_2                  0x6A
+#define WM8962_SPEAKER_MIXER_3                  0x6B
+#define WM8962_SPEAKER_MIXER_4                  0x6C
+#define WM8962_SPEAKER_MIXER_5                  0x6D
+#define WM8962_BEEP_GENERATOR_1                 0x6E
+#define WM8962_OSCILLATOR_TRIM_3                0x73
+#define WM8962_OSCILLATOR_TRIM_4                0x74
+#define WM8962_OSCILLATOR_TRIM_7                0x77
+#define WM8962_ANALOGUE_CLOCKING1               0x7C
+#define WM8962_ANALOGUE_CLOCKING2               0x7D
+#define WM8962_ANALOGUE_CLOCKING3               0x7E
+#define WM8962_PLL_SOFTWARE_RESET               0x7F
+#define WM8962_PLL2                             0x81
+#define WM8962_PLL_4                            0x83
+#define WM8962_PLL_9                            0x88
+#define WM8962_PLL_10                           0x89
+#define WM8962_PLL_11                           0x8A
+#define WM8962_PLL_12                           0x8B
+#define WM8962_PLL_13                           0x8C
+#define WM8962_PLL_14                           0x8D
+#define WM8962_PLL_15                           0x8E
+#define WM8962_PLL_16                           0x8F
+#define WM8962_FLL_CONTROL_1                    0x9B
+#define WM8962_FLL_CONTROL_2                    0x9C
+#define WM8962_FLL_CONTROL_3                    0x9D
+#define WM8962_FLL_CONTROL_5                    0x9F
+#define WM8962_FLL_CONTROL_6                    0xA0
+#define WM8962_FLL_CONTROL_7                    0xA1
+#define WM8962_FLL_CONTROL_8                    0xA2
+#define WM8962_GENERAL_TEST_1                   0xFC
+#define WM8962_DF1                              0x100
+#define WM8962_DF2                              0x101
+#define WM8962_DF3                              0x102
+#define WM8962_DF4                              0x103
+#define WM8962_DF5                              0x104
+#define WM8962_DF6                              0x105
+#define WM8962_DF7                              0x106
+#define WM8962_LHPF1                            0x108
+#define WM8962_LHPF2                            0x109
+#define WM8962_THREED1                          0x10C
+#define WM8962_THREED2                          0x10D
+#define WM8962_THREED3                          0x10E
+#define WM8962_THREED4                          0x10F
+#define WM8962_DRC_1                            0x114
+#define WM8962_DRC_2                            0x115
+#define WM8962_DRC_3                            0x116
+#define WM8962_DRC_4                            0x117
+#define WM8962_DRC_5                            0x118
+#define WM8962_TLOOPBACK                        0x11D
+#define WM8962_EQ1                              0x14F
+#define WM8962_EQ2                              0x150
+#define WM8962_EQ3                              0x151
+#define WM8962_EQ4                              0x152
+#define WM8962_EQ5                              0x153
+#define WM8962_EQ6                              0x154
+#define WM8962_EQ7                              0x155
+#define WM8962_EQ8                              0x156
+#define WM8962_EQ9                              0x157
+#define WM8962_EQ10                             0x158
+#define WM8962_EQ11                             0x159
+#define WM8962_EQ12                             0x15A
+#define WM8962_EQ13                             0x15B
+#define WM8962_EQ14                             0x15C
+#define WM8962_EQ15                             0x15D
+#define WM8962_EQ16                             0x15E
+#define WM8962_EQ17                             0x15F
+#define WM8962_EQ18                             0x160
+#define WM8962_EQ19                             0x161
+#define WM8962_EQ20                             0x162
+#define WM8962_EQ21                             0x163
+#define WM8962_EQ22                             0x164
+#define WM8962_EQ23                             0x165
+#define WM8962_EQ24                             0x166
+#define WM8962_EQ25                             0x167
+#define WM8962_EQ26                             0x168
+#define WM8962_EQ27                             0x169
+#define WM8962_EQ28                             0x16A
+#define WM8962_EQ29                             0x16B
+#define WM8962_EQ30                             0x16C
+#define WM8962_EQ31                             0x16D
+#define WM8962_EQ32                             0x16E
+#define WM8962_EQ33                             0x16F
+#define WM8962_EQ34                             0x170
+#define WM8962_EQ35                             0x171
+#define WM8962_EQ36                             0x172
+#define WM8962_EQ37                             0x173
+#define WM8962_EQ38                             0x174
+#define WM8962_EQ39                             0x175
+#define WM8962_EQ40                             0x176
+#define WM8962_EQ41                             0x177
+#define WM8962_GPIO_BASE			0x200
+#define WM8962_GPIO_2                           0x201
+#define WM8962_GPIO_3                           0x202
+#define WM8962_GPIO_5                           0x204
+#define WM8962_GPIO_6                           0x205
+#define WM8962_INTERRUPT_STATUS_1               0x230
+#define WM8962_INTERRUPT_STATUS_2               0x231
+#define WM8962_INTERRUPT_STATUS_1_MASK          0x238
+#define WM8962_INTERRUPT_STATUS_2_MASK          0x239
+#define WM8962_INTERRUPT_CONTROL                0x240
+#define WM8962_IRQ_DEBOUNCE                     0x248
+#define WM8962_MICINT_SOURCE_POL                0x24A
+#define WM8962_DSP2_POWER_MANAGEMENT            0x300
+#define WM8962_DSP2_EXECCONTROL                 0x40D
+#define WM8962_WRITE_SEQUENCER_0                0x1000
+#define WM8962_WRITE_SEQUENCER_1                0x1001
+#define WM8962_WRITE_SEQUENCER_2                0x1002
+#define WM8962_WRITE_SEQUENCER_3                0x1003
+#define WM8962_WRITE_SEQUENCER_4                0x1004
+#define WM8962_WRITE_SEQUENCER_5                0x1005
+#define WM8962_WRITE_SEQUENCER_6                0x1006
+#define WM8962_WRITE_SEQUENCER_7                0x1007
+#define WM8962_WRITE_SEQUENCER_8                0x1008
+#define WM8962_WRITE_SEQUENCER_9                0x1009
+#define WM8962_WRITE_SEQUENCER_10               0x100A
+#define WM8962_WRITE_SEQUENCER_11               0x100B
+#define WM8962_WRITE_SEQUENCER_12               0x100C
+#define WM8962_WRITE_SEQUENCER_13               0x100D
+#define WM8962_WRITE_SEQUENCER_14               0x100E
+#define WM8962_WRITE_SEQUENCER_15               0x100F
+#define WM8962_WRITE_SEQUENCER_16               0x1010
+#define WM8962_WRITE_SEQUENCER_17               0x1011
+#define WM8962_WRITE_SEQUENCER_18               0x1012
+#define WM8962_WRITE_SEQUENCER_19               0x1013
+#define WM8962_WRITE_SEQUENCER_20               0x1014
+#define WM8962_WRITE_SEQUENCER_21               0x1015
+#define WM8962_WRITE_SEQUENCER_22               0x1016
+#define WM8962_WRITE_SEQUENCER_23               0x1017
+#define WM8962_WRITE_SEQUENCER_24               0x1018
+#define WM8962_WRITE_SEQUENCER_25               0x1019
+#define WM8962_WRITE_SEQUENCER_26               0x101A
+#define WM8962_WRITE_SEQUENCER_27               0x101B
+#define WM8962_WRITE_SEQUENCER_28               0x101C
+#define WM8962_WRITE_SEQUENCER_29               0x101D
+#define WM8962_WRITE_SEQUENCER_30               0x101E
+#define WM8962_WRITE_SEQUENCER_31               0x101F
+#define WM8962_WRITE_SEQUENCER_32               0x1020
+#define WM8962_WRITE_SEQUENCER_33               0x1021
+#define WM8962_WRITE_SEQUENCER_34               0x1022
+#define WM8962_WRITE_SEQUENCER_35               0x1023
+#define WM8962_WRITE_SEQUENCER_36               0x1024
+#define WM8962_WRITE_SEQUENCER_37               0x1025
+#define WM8962_WRITE_SEQUENCER_38               0x1026
+#define WM8962_WRITE_SEQUENCER_39               0x1027
+#define WM8962_WRITE_SEQUENCER_40               0x1028
+#define WM8962_WRITE_SEQUENCER_41               0x1029
+#define WM8962_WRITE_SEQUENCER_42               0x102A
+#define WM8962_WRITE_SEQUENCER_43               0x102B
+#define WM8962_WRITE_SEQUENCER_44               0x102C
+#define WM8962_WRITE_SEQUENCER_45               0x102D
+#define WM8962_WRITE_SEQUENCER_46               0x102E
+#define WM8962_WRITE_SEQUENCER_47               0x102F
+#define WM8962_WRITE_SEQUENCER_48               0x1030
+#define WM8962_WRITE_SEQUENCER_49               0x1031
+#define WM8962_WRITE_SEQUENCER_50               0x1032
+#define WM8962_WRITE_SEQUENCER_51               0x1033
+#define WM8962_WRITE_SEQUENCER_52               0x1034
+#define WM8962_WRITE_SEQUENCER_53               0x1035
+#define WM8962_WRITE_SEQUENCER_54               0x1036
+#define WM8962_WRITE_SEQUENCER_55               0x1037
+#define WM8962_WRITE_SEQUENCER_56               0x1038
+#define WM8962_WRITE_SEQUENCER_57               0x1039
+#define WM8962_WRITE_SEQUENCER_58               0x103A
+#define WM8962_WRITE_SEQUENCER_59               0x103B
+#define WM8962_WRITE_SEQUENCER_60               0x103C
+#define WM8962_WRITE_SEQUENCER_61               0x103D
+#define WM8962_WRITE_SEQUENCER_62               0x103E
+#define WM8962_WRITE_SEQUENCER_63               0x103F
+#define WM8962_WRITE_SEQUENCER_64               0x1040
+#define WM8962_WRITE_SEQUENCER_65               0x1041
+#define WM8962_WRITE_SEQUENCER_66               0x1042
+#define WM8962_WRITE_SEQUENCER_67               0x1043
+#define WM8962_WRITE_SEQUENCER_68               0x1044
+#define WM8962_WRITE_SEQUENCER_69               0x1045
+#define WM8962_WRITE_SEQUENCER_70               0x1046
+#define WM8962_WRITE_SEQUENCER_71               0x1047
+#define WM8962_WRITE_SEQUENCER_72               0x1048
+#define WM8962_WRITE_SEQUENCER_73               0x1049
+#define WM8962_WRITE_SEQUENCER_74               0x104A
+#define WM8962_WRITE_SEQUENCER_75               0x104B
+#define WM8962_WRITE_SEQUENCER_76               0x104C
+#define WM8962_WRITE_SEQUENCER_77               0x104D
+#define WM8962_WRITE_SEQUENCER_78               0x104E
+#define WM8962_WRITE_SEQUENCER_79               0x104F
+#define WM8962_WRITE_SEQUENCER_80               0x1050
+#define WM8962_WRITE_SEQUENCER_81               0x1051
+#define WM8962_WRITE_SEQUENCER_82               0x1052
+#define WM8962_WRITE_SEQUENCER_83               0x1053
+#define WM8962_WRITE_SEQUENCER_84               0x1054
+#define WM8962_WRITE_SEQUENCER_85               0x1055
+#define WM8962_WRITE_SEQUENCER_86               0x1056
+#define WM8962_WRITE_SEQUENCER_87               0x1057
+#define WM8962_WRITE_SEQUENCER_88               0x1058
+#define WM8962_WRITE_SEQUENCER_89               0x1059
+#define WM8962_WRITE_SEQUENCER_90               0x105A
+#define WM8962_WRITE_SEQUENCER_91               0x105B
+#define WM8962_WRITE_SEQUENCER_92               0x105C
+#define WM8962_WRITE_SEQUENCER_93               0x105D
+#define WM8962_WRITE_SEQUENCER_94               0x105E
+#define WM8962_WRITE_SEQUENCER_95               0x105F
+#define WM8962_WRITE_SEQUENCER_96               0x1060
+#define WM8962_WRITE_SEQUENCER_97               0x1061
+#define WM8962_WRITE_SEQUENCER_98               0x1062
+#define WM8962_WRITE_SEQUENCER_99               0x1063
+#define WM8962_WRITE_SEQUENCER_100              0x1064
+#define WM8962_WRITE_SEQUENCER_101              0x1065
+#define WM8962_WRITE_SEQUENCER_102              0x1066
+#define WM8962_WRITE_SEQUENCER_103              0x1067
+#define WM8962_WRITE_SEQUENCER_104              0x1068
+#define WM8962_WRITE_SEQUENCER_105              0x1069
+#define WM8962_WRITE_SEQUENCER_106              0x106A
+#define WM8962_WRITE_SEQUENCER_107              0x106B
+#define WM8962_WRITE_SEQUENCER_108              0x106C
+#define WM8962_WRITE_SEQUENCER_109              0x106D
+#define WM8962_WRITE_SEQUENCER_110              0x106E
+#define WM8962_WRITE_SEQUENCER_111              0x106F
+#define WM8962_WRITE_SEQUENCER_112              0x1070
+#define WM8962_WRITE_SEQUENCER_113              0x1071
+#define WM8962_WRITE_SEQUENCER_114              0x1072
+#define WM8962_WRITE_SEQUENCER_115              0x1073
+#define WM8962_WRITE_SEQUENCER_116              0x1074
+#define WM8962_WRITE_SEQUENCER_117              0x1075
+#define WM8962_WRITE_SEQUENCER_118              0x1076
+#define WM8962_WRITE_SEQUENCER_119              0x1077
+#define WM8962_WRITE_SEQUENCER_120              0x1078
+#define WM8962_WRITE_SEQUENCER_121              0x1079
+#define WM8962_WRITE_SEQUENCER_122              0x107A
+#define WM8962_WRITE_SEQUENCER_123              0x107B
+#define WM8962_WRITE_SEQUENCER_124              0x107C
+#define WM8962_WRITE_SEQUENCER_125              0x107D
+#define WM8962_WRITE_SEQUENCER_126              0x107E
+#define WM8962_WRITE_SEQUENCER_127              0x107F
+#define WM8962_WRITE_SEQUENCER_128              0x1080
+#define WM8962_WRITE_SEQUENCER_129              0x1081
+#define WM8962_WRITE_SEQUENCER_130              0x1082
+#define WM8962_WRITE_SEQUENCER_131              0x1083
+#define WM8962_WRITE_SEQUENCER_132              0x1084
+#define WM8962_WRITE_SEQUENCER_133              0x1085
+#define WM8962_WRITE_SEQUENCER_134              0x1086
+#define WM8962_WRITE_SEQUENCER_135              0x1087
+#define WM8962_WRITE_SEQUENCER_136              0x1088
+#define WM8962_WRITE_SEQUENCER_137              0x1089
+#define WM8962_WRITE_SEQUENCER_138              0x108A
+#define WM8962_WRITE_SEQUENCER_139              0x108B
+#define WM8962_WRITE_SEQUENCER_140              0x108C
+#define WM8962_WRITE_SEQUENCER_141              0x108D
+#define WM8962_WRITE_SEQUENCER_142              0x108E
+#define WM8962_WRITE_SEQUENCER_143              0x108F
+#define WM8962_WRITE_SEQUENCER_144              0x1090
+#define WM8962_WRITE_SEQUENCER_145              0x1091
+#define WM8962_WRITE_SEQUENCER_146              0x1092
+#define WM8962_WRITE_SEQUENCER_147              0x1093
+#define WM8962_WRITE_SEQUENCER_148              0x1094
+#define WM8962_WRITE_SEQUENCER_149              0x1095
+#define WM8962_WRITE_SEQUENCER_150              0x1096
+#define WM8962_WRITE_SEQUENCER_151              0x1097
+#define WM8962_WRITE_SEQUENCER_152              0x1098
+#define WM8962_WRITE_SEQUENCER_153              0x1099
+#define WM8962_WRITE_SEQUENCER_154              0x109A
+#define WM8962_WRITE_SEQUENCER_155              0x109B
+#define WM8962_WRITE_SEQUENCER_156              0x109C
+#define WM8962_WRITE_SEQUENCER_157              0x109D
+#define WM8962_WRITE_SEQUENCER_158              0x109E
+#define WM8962_WRITE_SEQUENCER_159              0x109F
+#define WM8962_WRITE_SEQUENCER_160              0x10A0
+#define WM8962_WRITE_SEQUENCER_161              0x10A1
+#define WM8962_WRITE_SEQUENCER_162              0x10A2
+#define WM8962_WRITE_SEQUENCER_163              0x10A3
+#define WM8962_WRITE_SEQUENCER_164              0x10A4
+#define WM8962_WRITE_SEQUENCER_165              0x10A5
+#define WM8962_WRITE_SEQUENCER_166              0x10A6
+#define WM8962_WRITE_SEQUENCER_167              0x10A7
+#define WM8962_WRITE_SEQUENCER_168              0x10A8
+#define WM8962_WRITE_SEQUENCER_169              0x10A9
+#define WM8962_WRITE_SEQUENCER_170              0x10AA
+#define WM8962_WRITE_SEQUENCER_171              0x10AB
+#define WM8962_WRITE_SEQUENCER_172              0x10AC
+#define WM8962_WRITE_SEQUENCER_173              0x10AD
+#define WM8962_WRITE_SEQUENCER_174              0x10AE
+#define WM8962_WRITE_SEQUENCER_175              0x10AF
+#define WM8962_WRITE_SEQUENCER_176              0x10B0
+#define WM8962_WRITE_SEQUENCER_177              0x10B1
+#define WM8962_WRITE_SEQUENCER_178              0x10B2
+#define WM8962_WRITE_SEQUENCER_179              0x10B3
+#define WM8962_WRITE_SEQUENCER_180              0x10B4
+#define WM8962_WRITE_SEQUENCER_181              0x10B5
+#define WM8962_WRITE_SEQUENCER_182              0x10B6
+#define WM8962_WRITE_SEQUENCER_183              0x10B7
+#define WM8962_WRITE_SEQUENCER_184              0x10B8
+#define WM8962_WRITE_SEQUENCER_185              0x10B9
+#define WM8962_WRITE_SEQUENCER_186              0x10BA
+#define WM8962_WRITE_SEQUENCER_187              0x10BB
+#define WM8962_WRITE_SEQUENCER_188              0x10BC
+#define WM8962_WRITE_SEQUENCER_189              0x10BD
+#define WM8962_WRITE_SEQUENCER_190              0x10BE
+#define WM8962_WRITE_SEQUENCER_191              0x10BF
+#define WM8962_WRITE_SEQUENCER_192              0x10C0
+#define WM8962_WRITE_SEQUENCER_193              0x10C1
+#define WM8962_WRITE_SEQUENCER_194              0x10C2
+#define WM8962_WRITE_SEQUENCER_195              0x10C3
+#define WM8962_WRITE_SEQUENCER_196              0x10C4
+#define WM8962_WRITE_SEQUENCER_197              0x10C5
+#define WM8962_WRITE_SEQUENCER_198              0x10C6
+#define WM8962_WRITE_SEQUENCER_199              0x10C7
+#define WM8962_WRITE_SEQUENCER_200              0x10C8
+#define WM8962_WRITE_SEQUENCER_201              0x10C9
+#define WM8962_WRITE_SEQUENCER_202              0x10CA
+#define WM8962_WRITE_SEQUENCER_203              0x10CB
+#define WM8962_WRITE_SEQUENCER_204              0x10CC
+#define WM8962_WRITE_SEQUENCER_205              0x10CD
+#define WM8962_WRITE_SEQUENCER_206              0x10CE
+#define WM8962_WRITE_SEQUENCER_207              0x10CF
+#define WM8962_WRITE_SEQUENCER_208              0x10D0
+#define WM8962_WRITE_SEQUENCER_209              0x10D1
+#define WM8962_WRITE_SEQUENCER_210              0x10D2
+#define WM8962_WRITE_SEQUENCER_211              0x10D3
+#define WM8962_WRITE_SEQUENCER_212              0x10D4
+#define WM8962_WRITE_SEQUENCER_213              0x10D5
+#define WM8962_WRITE_SEQUENCER_214              0x10D6
+#define WM8962_WRITE_SEQUENCER_215              0x10D7
+#define WM8962_WRITE_SEQUENCER_216              0x10D8
+#define WM8962_WRITE_SEQUENCER_217              0x10D9
+#define WM8962_WRITE_SEQUENCER_218              0x10DA
+#define WM8962_WRITE_SEQUENCER_219              0x10DB
+#define WM8962_WRITE_SEQUENCER_220              0x10DC
+#define WM8962_WRITE_SEQUENCER_221              0x10DD
+#define WM8962_WRITE_SEQUENCER_222              0x10DE
+#define WM8962_WRITE_SEQUENCER_223              0x10DF
+#define WM8962_WRITE_SEQUENCER_224              0x10E0
+#define WM8962_WRITE_SEQUENCER_225              0x10E1
+#define WM8962_WRITE_SEQUENCER_226              0x10E2
+#define WM8962_WRITE_SEQUENCER_227              0x10E3
+#define WM8962_WRITE_SEQUENCER_228              0x10E4
+#define WM8962_WRITE_SEQUENCER_229              0x10E5
+#define WM8962_WRITE_SEQUENCER_230              0x10E6
+#define WM8962_WRITE_SEQUENCER_231              0x10E7
+#define WM8962_WRITE_SEQUENCER_232              0x10E8
+#define WM8962_WRITE_SEQUENCER_233              0x10E9
+#define WM8962_WRITE_SEQUENCER_234              0x10EA
+#define WM8962_WRITE_SEQUENCER_235              0x10EB
+#define WM8962_WRITE_SEQUENCER_236              0x10EC
+#define WM8962_WRITE_SEQUENCER_237              0x10ED
+#define WM8962_WRITE_SEQUENCER_238              0x10EE
+#define WM8962_WRITE_SEQUENCER_239              0x10EF
+#define WM8962_WRITE_SEQUENCER_240              0x10F0
+#define WM8962_WRITE_SEQUENCER_241              0x10F1
+#define WM8962_WRITE_SEQUENCER_242              0x10F2
+#define WM8962_WRITE_SEQUENCER_243              0x10F3
+#define WM8962_WRITE_SEQUENCER_244              0x10F4
+#define WM8962_WRITE_SEQUENCER_245              0x10F5
+#define WM8962_WRITE_SEQUENCER_246              0x10F6
+#define WM8962_WRITE_SEQUENCER_247              0x10F7
+#define WM8962_WRITE_SEQUENCER_248              0x10F8
+#define WM8962_WRITE_SEQUENCER_249              0x10F9
+#define WM8962_WRITE_SEQUENCER_250              0x10FA
+#define WM8962_WRITE_SEQUENCER_251              0x10FB
+#define WM8962_WRITE_SEQUENCER_252              0x10FC
+#define WM8962_WRITE_SEQUENCER_253              0x10FD
+#define WM8962_WRITE_SEQUENCER_254              0x10FE
+#define WM8962_WRITE_SEQUENCER_255              0x10FF
+#define WM8962_WRITE_SEQUENCER_256              0x1100
+#define WM8962_WRITE_SEQUENCER_257              0x1101
+#define WM8962_WRITE_SEQUENCER_258              0x1102
+#define WM8962_WRITE_SEQUENCER_259              0x1103
+#define WM8962_WRITE_SEQUENCER_260              0x1104
+#define WM8962_WRITE_SEQUENCER_261              0x1105
+#define WM8962_WRITE_SEQUENCER_262              0x1106
+#define WM8962_WRITE_SEQUENCER_263              0x1107
+#define WM8962_WRITE_SEQUENCER_264              0x1108
+#define WM8962_WRITE_SEQUENCER_265              0x1109
+#define WM8962_WRITE_SEQUENCER_266              0x110A
+#define WM8962_WRITE_SEQUENCER_267              0x110B
+#define WM8962_WRITE_SEQUENCER_268              0x110C
+#define WM8962_WRITE_SEQUENCER_269              0x110D
+#define WM8962_WRITE_SEQUENCER_270              0x110E
+#define WM8962_WRITE_SEQUENCER_271              0x110F
+#define WM8962_WRITE_SEQUENCER_272              0x1110
+#define WM8962_WRITE_SEQUENCER_273              0x1111
+#define WM8962_WRITE_SEQUENCER_274              0x1112
+#define WM8962_WRITE_SEQUENCER_275              0x1113
+#define WM8962_WRITE_SEQUENCER_276              0x1114
+#define WM8962_WRITE_SEQUENCER_277              0x1115
+#define WM8962_WRITE_SEQUENCER_278              0x1116
+#define WM8962_WRITE_SEQUENCER_279              0x1117
+#define WM8962_WRITE_SEQUENCER_280              0x1118
+#define WM8962_WRITE_SEQUENCER_281              0x1119
+#define WM8962_WRITE_SEQUENCER_282              0x111A
+#define WM8962_WRITE_SEQUENCER_283              0x111B
+#define WM8962_WRITE_SEQUENCER_284              0x111C
+#define WM8962_WRITE_SEQUENCER_285              0x111D
+#define WM8962_WRITE_SEQUENCER_286              0x111E
+#define WM8962_WRITE_SEQUENCER_287              0x111F
+#define WM8962_WRITE_SEQUENCER_288              0x1120
+#define WM8962_WRITE_SEQUENCER_289              0x1121
+#define WM8962_WRITE_SEQUENCER_290              0x1122
+#define WM8962_WRITE_SEQUENCER_291              0x1123
+#define WM8962_WRITE_SEQUENCER_292              0x1124
+#define WM8962_WRITE_SEQUENCER_293              0x1125
+#define WM8962_WRITE_SEQUENCER_294              0x1126
+#define WM8962_WRITE_SEQUENCER_295              0x1127
+#define WM8962_WRITE_SEQUENCER_296              0x1128
+#define WM8962_WRITE_SEQUENCER_297              0x1129
+#define WM8962_WRITE_SEQUENCER_298              0x112A
+#define WM8962_WRITE_SEQUENCER_299              0x112B
+#define WM8962_WRITE_SEQUENCER_300              0x112C
+#define WM8962_WRITE_SEQUENCER_301              0x112D
+#define WM8962_WRITE_SEQUENCER_302              0x112E
+#define WM8962_WRITE_SEQUENCER_303              0x112F
+#define WM8962_WRITE_SEQUENCER_304              0x1130
+#define WM8962_WRITE_SEQUENCER_305              0x1131
+#define WM8962_WRITE_SEQUENCER_306              0x1132
+#define WM8962_WRITE_SEQUENCER_307              0x1133
+#define WM8962_WRITE_SEQUENCER_308              0x1134
+#define WM8962_WRITE_SEQUENCER_309              0x1135
+#define WM8962_WRITE_SEQUENCER_310              0x1136
+#define WM8962_WRITE_SEQUENCER_311              0x1137
+#define WM8962_WRITE_SEQUENCER_312              0x1138
+#define WM8962_WRITE_SEQUENCER_313              0x1139
+#define WM8962_WRITE_SEQUENCER_314              0x113A
+#define WM8962_WRITE_SEQUENCER_315              0x113B
+#define WM8962_WRITE_SEQUENCER_316              0x113C
+#define WM8962_WRITE_SEQUENCER_317              0x113D
+#define WM8962_WRITE_SEQUENCER_318              0x113E
+#define WM8962_WRITE_SEQUENCER_319              0x113F
+#define WM8962_WRITE_SEQUENCER_320              0x1140
+#define WM8962_WRITE_SEQUENCER_321              0x1141
+#define WM8962_WRITE_SEQUENCER_322              0x1142
+#define WM8962_WRITE_SEQUENCER_323              0x1143
+#define WM8962_WRITE_SEQUENCER_324              0x1144
+#define WM8962_WRITE_SEQUENCER_325              0x1145
+#define WM8962_WRITE_SEQUENCER_326              0x1146
+#define WM8962_WRITE_SEQUENCER_327              0x1147
+#define WM8962_WRITE_SEQUENCER_328              0x1148
+#define WM8962_WRITE_SEQUENCER_329              0x1149
+#define WM8962_WRITE_SEQUENCER_330              0x114A
+#define WM8962_WRITE_SEQUENCER_331              0x114B
+#define WM8962_WRITE_SEQUENCER_332              0x114C
+#define WM8962_WRITE_SEQUENCER_333              0x114D
+#define WM8962_WRITE_SEQUENCER_334              0x114E
+#define WM8962_WRITE_SEQUENCER_335              0x114F
+#define WM8962_WRITE_SEQUENCER_336              0x1150
+#define WM8962_WRITE_SEQUENCER_337              0x1151
+#define WM8962_WRITE_SEQUENCER_338              0x1152
+#define WM8962_WRITE_SEQUENCER_339              0x1153
+#define WM8962_WRITE_SEQUENCER_340              0x1154
+#define WM8962_WRITE_SEQUENCER_341              0x1155
+#define WM8962_WRITE_SEQUENCER_342              0x1156
+#define WM8962_WRITE_SEQUENCER_343              0x1157
+#define WM8962_WRITE_SEQUENCER_344              0x1158
+#define WM8962_WRITE_SEQUENCER_345              0x1159
+#define WM8962_WRITE_SEQUENCER_346              0x115A
+#define WM8962_WRITE_SEQUENCER_347              0x115B
+#define WM8962_WRITE_SEQUENCER_348              0x115C
+#define WM8962_WRITE_SEQUENCER_349              0x115D
+#define WM8962_WRITE_SEQUENCER_350              0x115E
+#define WM8962_WRITE_SEQUENCER_351              0x115F
+#define WM8962_WRITE_SEQUENCER_352              0x1160
+#define WM8962_WRITE_SEQUENCER_353              0x1161
+#define WM8962_WRITE_SEQUENCER_354              0x1162
+#define WM8962_WRITE_SEQUENCER_355              0x1163
+#define WM8962_WRITE_SEQUENCER_356              0x1164
+#define WM8962_WRITE_SEQUENCER_357              0x1165
+#define WM8962_WRITE_SEQUENCER_358              0x1166
+#define WM8962_WRITE_SEQUENCER_359              0x1167
+#define WM8962_WRITE_SEQUENCER_360              0x1168
+#define WM8962_WRITE_SEQUENCER_361              0x1169
+#define WM8962_WRITE_SEQUENCER_362              0x116A
+#define WM8962_WRITE_SEQUENCER_363              0x116B
+#define WM8962_WRITE_SEQUENCER_364              0x116C
+#define WM8962_WRITE_SEQUENCER_365              0x116D
+#define WM8962_WRITE_SEQUENCER_366              0x116E
+#define WM8962_WRITE_SEQUENCER_367              0x116F
+#define WM8962_WRITE_SEQUENCER_368              0x1170
+#define WM8962_WRITE_SEQUENCER_369              0x1171
+#define WM8962_WRITE_SEQUENCER_370              0x1172
+#define WM8962_WRITE_SEQUENCER_371              0x1173
+#define WM8962_WRITE_SEQUENCER_372              0x1174
+#define WM8962_WRITE_SEQUENCER_373              0x1175
+#define WM8962_WRITE_SEQUENCER_374              0x1176
+#define WM8962_WRITE_SEQUENCER_375              0x1177
+#define WM8962_WRITE_SEQUENCER_376              0x1178
+#define WM8962_WRITE_SEQUENCER_377              0x1179
+#define WM8962_WRITE_SEQUENCER_378              0x117A
+#define WM8962_WRITE_SEQUENCER_379              0x117B
+#define WM8962_WRITE_SEQUENCER_380              0x117C
+#define WM8962_WRITE_SEQUENCER_381              0x117D
+#define WM8962_WRITE_SEQUENCER_382              0x117E
+#define WM8962_WRITE_SEQUENCER_383              0x117F
+#define WM8962_WRITE_SEQUENCER_384              0x1180
+#define WM8962_WRITE_SEQUENCER_385              0x1181
+#define WM8962_WRITE_SEQUENCER_386              0x1182
+#define WM8962_WRITE_SEQUENCER_387              0x1183
+#define WM8962_WRITE_SEQUENCER_388              0x1184
+#define WM8962_WRITE_SEQUENCER_389              0x1185
+#define WM8962_WRITE_SEQUENCER_390              0x1186
+#define WM8962_WRITE_SEQUENCER_391              0x1187
+#define WM8962_WRITE_SEQUENCER_392              0x1188
+#define WM8962_WRITE_SEQUENCER_393              0x1189
+#define WM8962_WRITE_SEQUENCER_394              0x118A
+#define WM8962_WRITE_SEQUENCER_395              0x118B
+#define WM8962_WRITE_SEQUENCER_396              0x118C
+#define WM8962_WRITE_SEQUENCER_397              0x118D
+#define WM8962_WRITE_SEQUENCER_398              0x118E
+#define WM8962_WRITE_SEQUENCER_399              0x118F
+#define WM8962_WRITE_SEQUENCER_400              0x1190
+#define WM8962_WRITE_SEQUENCER_401              0x1191
+#define WM8962_WRITE_SEQUENCER_402              0x1192
+#define WM8962_WRITE_SEQUENCER_403              0x1193
+#define WM8962_WRITE_SEQUENCER_404              0x1194
+#define WM8962_WRITE_SEQUENCER_405              0x1195
+#define WM8962_WRITE_SEQUENCER_406              0x1196
+#define WM8962_WRITE_SEQUENCER_407              0x1197
+#define WM8962_WRITE_SEQUENCER_408              0x1198
+#define WM8962_WRITE_SEQUENCER_409              0x1199
+#define WM8962_WRITE_SEQUENCER_410              0x119A
+#define WM8962_WRITE_SEQUENCER_411              0x119B
+#define WM8962_WRITE_SEQUENCER_412              0x119C
+#define WM8962_WRITE_SEQUENCER_413              0x119D
+#define WM8962_WRITE_SEQUENCER_414              0x119E
+#define WM8962_WRITE_SEQUENCER_415              0x119F
+#define WM8962_WRITE_SEQUENCER_416              0x11A0
+#define WM8962_WRITE_SEQUENCER_417              0x11A1
+#define WM8962_WRITE_SEQUENCER_418              0x11A2
+#define WM8962_WRITE_SEQUENCER_419              0x11A3
+#define WM8962_WRITE_SEQUENCER_420              0x11A4
+#define WM8962_WRITE_SEQUENCER_421              0x11A5
+#define WM8962_WRITE_SEQUENCER_422              0x11A6
+#define WM8962_WRITE_SEQUENCER_423              0x11A7
+#define WM8962_WRITE_SEQUENCER_424              0x11A8
+#define WM8962_WRITE_SEQUENCER_425              0x11A9
+#define WM8962_WRITE_SEQUENCER_426              0x11AA
+#define WM8962_WRITE_SEQUENCER_427              0x11AB
+#define WM8962_WRITE_SEQUENCER_428              0x11AC
+#define WM8962_WRITE_SEQUENCER_429              0x11AD
+#define WM8962_WRITE_SEQUENCER_430              0x11AE
+#define WM8962_WRITE_SEQUENCER_431              0x11AF
+#define WM8962_WRITE_SEQUENCER_432              0x11B0
+#define WM8962_WRITE_SEQUENCER_433              0x11B1
+#define WM8962_WRITE_SEQUENCER_434              0x11B2
+#define WM8962_WRITE_SEQUENCER_435              0x11B3
+#define WM8962_WRITE_SEQUENCER_436              0x11B4
+#define WM8962_WRITE_SEQUENCER_437              0x11B5
+#define WM8962_WRITE_SEQUENCER_438              0x11B6
+#define WM8962_WRITE_SEQUENCER_439              0x11B7
+#define WM8962_WRITE_SEQUENCER_440              0x11B8
+#define WM8962_WRITE_SEQUENCER_441              0x11B9
+#define WM8962_WRITE_SEQUENCER_442              0x11BA
+#define WM8962_WRITE_SEQUENCER_443              0x11BB
+#define WM8962_WRITE_SEQUENCER_444              0x11BC
+#define WM8962_WRITE_SEQUENCER_445              0x11BD
+#define WM8962_WRITE_SEQUENCER_446              0x11BE
+#define WM8962_WRITE_SEQUENCER_447              0x11BF
+#define WM8962_WRITE_SEQUENCER_448              0x11C0
+#define WM8962_WRITE_SEQUENCER_449              0x11C1
+#define WM8962_WRITE_SEQUENCER_450              0x11C2
+#define WM8962_WRITE_SEQUENCER_451              0x11C3
+#define WM8962_WRITE_SEQUENCER_452              0x11C4
+#define WM8962_WRITE_SEQUENCER_453              0x11C5
+#define WM8962_WRITE_SEQUENCER_454              0x11C6
+#define WM8962_WRITE_SEQUENCER_455              0x11C7
+#define WM8962_WRITE_SEQUENCER_456              0x11C8
+#define WM8962_WRITE_SEQUENCER_457              0x11C9
+#define WM8962_WRITE_SEQUENCER_458              0x11CA
+#define WM8962_WRITE_SEQUENCER_459              0x11CB
+#define WM8962_WRITE_SEQUENCER_460              0x11CC
+#define WM8962_WRITE_SEQUENCER_461              0x11CD
+#define WM8962_WRITE_SEQUENCER_462              0x11CE
+#define WM8962_WRITE_SEQUENCER_463              0x11CF
+#define WM8962_WRITE_SEQUENCER_464              0x11D0
+#define WM8962_WRITE_SEQUENCER_465              0x11D1
+#define WM8962_WRITE_SEQUENCER_466              0x11D2
+#define WM8962_WRITE_SEQUENCER_467              0x11D3
+#define WM8962_WRITE_SEQUENCER_468              0x11D4
+#define WM8962_WRITE_SEQUENCER_469              0x11D5
+#define WM8962_WRITE_SEQUENCER_470              0x11D6
+#define WM8962_WRITE_SEQUENCER_471              0x11D7
+#define WM8962_WRITE_SEQUENCER_472              0x11D8
+#define WM8962_WRITE_SEQUENCER_473              0x11D9
+#define WM8962_WRITE_SEQUENCER_474              0x11DA
+#define WM8962_WRITE_SEQUENCER_475              0x11DB
+#define WM8962_WRITE_SEQUENCER_476              0x11DC
+#define WM8962_WRITE_SEQUENCER_477              0x11DD
+#define WM8962_WRITE_SEQUENCER_478              0x11DE
+#define WM8962_WRITE_SEQUENCER_479              0x11DF
+#define WM8962_WRITE_SEQUENCER_480              0x11E0
+#define WM8962_WRITE_SEQUENCER_481              0x11E1
+#define WM8962_WRITE_SEQUENCER_482              0x11E2
+#define WM8962_WRITE_SEQUENCER_483              0x11E3
+#define WM8962_WRITE_SEQUENCER_484              0x11E4
+#define WM8962_WRITE_SEQUENCER_485              0x11E5
+#define WM8962_WRITE_SEQUENCER_486              0x11E6
+#define WM8962_WRITE_SEQUENCER_487              0x11E7
+#define WM8962_WRITE_SEQUENCER_488              0x11E8
+#define WM8962_WRITE_SEQUENCER_489              0x11E9
+#define WM8962_WRITE_SEQUENCER_490              0x11EA
+#define WM8962_WRITE_SEQUENCER_491              0x11EB
+#define WM8962_WRITE_SEQUENCER_492              0x11EC
+#define WM8962_WRITE_SEQUENCER_493              0x11ED
+#define WM8962_WRITE_SEQUENCER_494              0x11EE
+#define WM8962_WRITE_SEQUENCER_495              0x11EF
+#define WM8962_WRITE_SEQUENCER_496              0x11F0
+#define WM8962_WRITE_SEQUENCER_497              0x11F1
+#define WM8962_WRITE_SEQUENCER_498              0x11F2
+#define WM8962_WRITE_SEQUENCER_499              0x11F3
+#define WM8962_WRITE_SEQUENCER_500              0x11F4
+#define WM8962_WRITE_SEQUENCER_501              0x11F5
+#define WM8962_WRITE_SEQUENCER_502              0x11F6
+#define WM8962_WRITE_SEQUENCER_503              0x11F7
+#define WM8962_WRITE_SEQUENCER_504              0x11F8
+#define WM8962_WRITE_SEQUENCER_505              0x11F9
+#define WM8962_WRITE_SEQUENCER_506              0x11FA
+#define WM8962_WRITE_SEQUENCER_507              0x11FB
+#define WM8962_WRITE_SEQUENCER_508              0x11FC
+#define WM8962_WRITE_SEQUENCER_509              0x11FD
+#define WM8962_WRITE_SEQUENCER_510              0x11FE
+#define WM8962_WRITE_SEQUENCER_511              0x11FF
+#define WM8962_DSP2_INSTRUCTION_RAM_0           0x2000
+#define WM8962_DSP2_ADDRESS_RAM_2               0x2400
+#define WM8962_DSP2_ADDRESS_RAM_1               0x2401
+#define WM8962_DSP2_ADDRESS_RAM_0               0x2402
+#define WM8962_DSP2_DATA1_RAM_1                 0x3000
+#define WM8962_DSP2_DATA1_RAM_0                 0x3001
+#define WM8962_DSP2_DATA2_RAM_1                 0x3400
+#define WM8962_DSP2_DATA2_RAM_0                 0x3401
+#define WM8962_DSP2_DATA3_RAM_1                 0x3800
+#define WM8962_DSP2_DATA3_RAM_0                 0x3801
+#define WM8962_DSP2_COEFF_RAM_0                 0x3C00
+#define WM8962_RETUNEADC_SHARED_COEFF_1         0x4000
+#define WM8962_RETUNEADC_SHARED_COEFF_0         0x4001
+#define WM8962_RETUNEDAC_SHARED_COEFF_1         0x4002
+#define WM8962_RETUNEDAC_SHARED_COEFF_0         0x4003
+#define WM8962_SOUNDSTAGE_ENABLES_1             0x4004
+#define WM8962_SOUNDSTAGE_ENABLES_0             0x4005
+#define WM8962_HDBASS_AI_1                      0x4200
+#define WM8962_HDBASS_AI_0                      0x4201
+#define WM8962_HDBASS_AR_1                      0x4202
+#define WM8962_HDBASS_AR_0                      0x4203
+#define WM8962_HDBASS_B_1                       0x4204
+#define WM8962_HDBASS_B_0                       0x4205
+#define WM8962_HDBASS_K_1                       0x4206
+#define WM8962_HDBASS_K_0                       0x4207
+#define WM8962_HDBASS_N1_1                      0x4208
+#define WM8962_HDBASS_N1_0                      0x4209
+#define WM8962_HDBASS_N2_1                      0x420A
+#define WM8962_HDBASS_N2_0                      0x420B
+#define WM8962_HDBASS_N3_1                      0x420C
+#define WM8962_HDBASS_N3_0                      0x420D
+#define WM8962_HDBASS_N4_1                      0x420E
+#define WM8962_HDBASS_N4_0                      0x420F
+#define WM8962_HDBASS_N5_1                      0x4210
+#define WM8962_HDBASS_N5_0                      0x4211
+#define WM8962_HDBASS_X1_1                      0x4212
+#define WM8962_HDBASS_X1_0                      0x4213
+#define WM8962_HDBASS_X2_1                      0x4214
+#define WM8962_HDBASS_X2_0                      0x4215
+#define WM8962_HDBASS_X3_1                      0x4216
+#define WM8962_HDBASS_X3_0                      0x4217
+#define WM8962_HDBASS_ATK_1                     0x4218
+#define WM8962_HDBASS_ATK_0                     0x4219
+#define WM8962_HDBASS_DCY_1                     0x421A
+#define WM8962_HDBASS_DCY_0                     0x421B
+#define WM8962_HDBASS_PG_1                      0x421C
+#define WM8962_HDBASS_PG_0                      0x421D
+#define WM8962_HPF_C_1                          0x4400
+#define WM8962_HPF_C_0                          0x4401
+#define WM8962_ADCL_RETUNE_C1_1                 0x4600
+#define WM8962_ADCL_RETUNE_C1_0                 0x4601
+#define WM8962_ADCL_RETUNE_C2_1                 0x4602
+#define WM8962_ADCL_RETUNE_C2_0                 0x4603
+#define WM8962_ADCL_RETUNE_C3_1                 0x4604
+#define WM8962_ADCL_RETUNE_C3_0                 0x4605
+#define WM8962_ADCL_RETUNE_C4_1                 0x4606
+#define WM8962_ADCL_RETUNE_C4_0                 0x4607
+#define WM8962_ADCL_RETUNE_C5_1                 0x4608
+#define WM8962_ADCL_RETUNE_C5_0                 0x4609
+#define WM8962_ADCL_RETUNE_C6_1                 0x460A
+#define WM8962_ADCL_RETUNE_C6_0                 0x460B
+#define WM8962_ADCL_RETUNE_C7_1                 0x460C
+#define WM8962_ADCL_RETUNE_C7_0                 0x460D
+#define WM8962_ADCL_RETUNE_C8_1                 0x460E
+#define WM8962_ADCL_RETUNE_C8_0                 0x460F
+#define WM8962_ADCL_RETUNE_C9_1                 0x4610
+#define WM8962_ADCL_RETUNE_C9_0                 0x4611
+#define WM8962_ADCL_RETUNE_C10_1                0x4612
+#define WM8962_ADCL_RETUNE_C10_0                0x4613
+#define WM8962_ADCL_RETUNE_C11_1                0x4614
+#define WM8962_ADCL_RETUNE_C11_0                0x4615
+#define WM8962_ADCL_RETUNE_C12_1                0x4616
+#define WM8962_ADCL_RETUNE_C12_0                0x4617
+#define WM8962_ADCL_RETUNE_C13_1                0x4618
+#define WM8962_ADCL_RETUNE_C13_0                0x4619
+#define WM8962_ADCL_RETUNE_C14_1                0x461A
+#define WM8962_ADCL_RETUNE_C14_0                0x461B
+#define WM8962_ADCL_RETUNE_C15_1                0x461C
+#define WM8962_ADCL_RETUNE_C15_0                0x461D
+#define WM8962_ADCL_RETUNE_C16_1                0x461E
+#define WM8962_ADCL_RETUNE_C16_0                0x461F
+#define WM8962_ADCL_RETUNE_C17_1                0x4620
+#define WM8962_ADCL_RETUNE_C17_0                0x4621
+#define WM8962_ADCL_RETUNE_C18_1                0x4622
+#define WM8962_ADCL_RETUNE_C18_0                0x4623
+#define WM8962_ADCL_RETUNE_C19_1                0x4624
+#define WM8962_ADCL_RETUNE_C19_0                0x4625
+#define WM8962_ADCL_RETUNE_C20_1                0x4626
+#define WM8962_ADCL_RETUNE_C20_0                0x4627
+#define WM8962_ADCL_RETUNE_C21_1                0x4628
+#define WM8962_ADCL_RETUNE_C21_0                0x4629
+#define WM8962_ADCL_RETUNE_C22_1                0x462A
+#define WM8962_ADCL_RETUNE_C22_0                0x462B
+#define WM8962_ADCL_RETUNE_C23_1                0x462C
+#define WM8962_ADCL_RETUNE_C23_0                0x462D
+#define WM8962_ADCL_RETUNE_C24_1                0x462E
+#define WM8962_ADCL_RETUNE_C24_0                0x462F
+#define WM8962_ADCL_RETUNE_C25_1                0x4630
+#define WM8962_ADCL_RETUNE_C25_0                0x4631
+#define WM8962_ADCL_RETUNE_C26_1                0x4632
+#define WM8962_ADCL_RETUNE_C26_0                0x4633
+#define WM8962_ADCL_RETUNE_C27_1                0x4634
+#define WM8962_ADCL_RETUNE_C27_0                0x4635
+#define WM8962_ADCL_RETUNE_C28_1                0x4636
+#define WM8962_ADCL_RETUNE_C28_0                0x4637
+#define WM8962_ADCL_RETUNE_C29_1                0x4638
+#define WM8962_ADCL_RETUNE_C29_0                0x4639
+#define WM8962_ADCL_RETUNE_C30_1                0x463A
+#define WM8962_ADCL_RETUNE_C30_0                0x463B
+#define WM8962_ADCL_RETUNE_C31_1                0x463C
+#define WM8962_ADCL_RETUNE_C31_0                0x463D
+#define WM8962_ADCL_RETUNE_C32_1                0x463E
+#define WM8962_ADCL_RETUNE_C32_0                0x463F
+#define WM8962_RETUNEADC_PG2_1                  0x4800
+#define WM8962_RETUNEADC_PG2_0                  0x4801
+#define WM8962_RETUNEADC_PG_1                   0x4802
+#define WM8962_RETUNEADC_PG_0                   0x4803
+#define WM8962_ADCR_RETUNE_C1_1                 0x4A00
+#define WM8962_ADCR_RETUNE_C1_0                 0x4A01
+#define WM8962_ADCR_RETUNE_C2_1                 0x4A02
+#define WM8962_ADCR_RETUNE_C2_0                 0x4A03
+#define WM8962_ADCR_RETUNE_C3_1                 0x4A04
+#define WM8962_ADCR_RETUNE_C3_0                 0x4A05
+#define WM8962_ADCR_RETUNE_C4_1                 0x4A06
+#define WM8962_ADCR_RETUNE_C4_0                 0x4A07
+#define WM8962_ADCR_RETUNE_C5_1                 0x4A08
+#define WM8962_ADCR_RETUNE_C5_0                 0x4A09
+#define WM8962_ADCR_RETUNE_C6_1                 0x4A0A
+#define WM8962_ADCR_RETUNE_C6_0                 0x4A0B
+#define WM8962_ADCR_RETUNE_C7_1                 0x4A0C
+#define WM8962_ADCR_RETUNE_C7_0                 0x4A0D
+#define WM8962_ADCR_RETUNE_C8_1                 0x4A0E
+#define WM8962_ADCR_RETUNE_C8_0                 0x4A0F
+#define WM8962_ADCR_RETUNE_C9_1                 0x4A10
+#define WM8962_ADCR_RETUNE_C9_0                 0x4A11
+#define WM8962_ADCR_RETUNE_C10_1                0x4A12
+#define WM8962_ADCR_RETUNE_C10_0                0x4A13
+#define WM8962_ADCR_RETUNE_C11_1                0x4A14
+#define WM8962_ADCR_RETUNE_C11_0                0x4A15
+#define WM8962_ADCR_RETUNE_C12_1                0x4A16
+#define WM8962_ADCR_RETUNE_C12_0                0x4A17
+#define WM8962_ADCR_RETUNE_C13_1                0x4A18
+#define WM8962_ADCR_RETUNE_C13_0                0x4A19
+#define WM8962_ADCR_RETUNE_C14_1                0x4A1A
+#define WM8962_ADCR_RETUNE_C14_0                0x4A1B
+#define WM8962_ADCR_RETUNE_C15_1                0x4A1C
+#define WM8962_ADCR_RETUNE_C15_0                0x4A1D
+#define WM8962_ADCR_RETUNE_C16_1                0x4A1E
+#define WM8962_ADCR_RETUNE_C16_0                0x4A1F
+#define WM8962_ADCR_RETUNE_C17_1                0x4A20
+#define WM8962_ADCR_RETUNE_C17_0                0x4A21
+#define WM8962_ADCR_RETUNE_C18_1                0x4A22
+#define WM8962_ADCR_RETUNE_C18_0                0x4A23
+#define WM8962_ADCR_RETUNE_C19_1                0x4A24
+#define WM8962_ADCR_RETUNE_C19_0                0x4A25
+#define WM8962_ADCR_RETUNE_C20_1                0x4A26
+#define WM8962_ADCR_RETUNE_C20_0                0x4A27
+#define WM8962_ADCR_RETUNE_C21_1                0x4A28
+#define WM8962_ADCR_RETUNE_C21_0                0x4A29
+#define WM8962_ADCR_RETUNE_C22_1                0x4A2A
+#define WM8962_ADCR_RETUNE_C22_0                0x4A2B
+#define WM8962_ADCR_RETUNE_C23_1                0x4A2C
+#define WM8962_ADCR_RETUNE_C23_0                0x4A2D
+#define WM8962_ADCR_RETUNE_C24_1                0x4A2E
+#define WM8962_ADCR_RETUNE_C24_0                0x4A2F
+#define WM8962_ADCR_RETUNE_C25_1                0x4A30
+#define WM8962_ADCR_RETUNE_C25_0                0x4A31
+#define WM8962_ADCR_RETUNE_C26_1                0x4A32
+#define WM8962_ADCR_RETUNE_C26_0                0x4A33
+#define WM8962_ADCR_RETUNE_C27_1                0x4A34
+#define WM8962_ADCR_RETUNE_C27_0                0x4A35
+#define WM8962_ADCR_RETUNE_C28_1                0x4A36
+#define WM8962_ADCR_RETUNE_C28_0                0x4A37
+#define WM8962_ADCR_RETUNE_C29_1                0x4A38
+#define WM8962_ADCR_RETUNE_C29_0                0x4A39
+#define WM8962_ADCR_RETUNE_C30_1                0x4A3A
+#define WM8962_ADCR_RETUNE_C30_0                0x4A3B
+#define WM8962_ADCR_RETUNE_C31_1                0x4A3C
+#define WM8962_ADCR_RETUNE_C31_0                0x4A3D
+#define WM8962_ADCR_RETUNE_C32_1                0x4A3E
+#define WM8962_ADCR_RETUNE_C32_0                0x4A3F
+#define WM8962_DACL_RETUNE_C1_1                 0x4C00
+#define WM8962_DACL_RETUNE_C1_0                 0x4C01
+#define WM8962_DACL_RETUNE_C2_1                 0x4C02
+#define WM8962_DACL_RETUNE_C2_0                 0x4C03
+#define WM8962_DACL_RETUNE_C3_1                 0x4C04
+#define WM8962_DACL_RETUNE_C3_0                 0x4C05
+#define WM8962_DACL_RETUNE_C4_1                 0x4C06
+#define WM8962_DACL_RETUNE_C4_0                 0x4C07
+#define WM8962_DACL_RETUNE_C5_1                 0x4C08
+#define WM8962_DACL_RETUNE_C5_0                 0x4C09
+#define WM8962_DACL_RETUNE_C6_1                 0x4C0A
+#define WM8962_DACL_RETUNE_C6_0                 0x4C0B
+#define WM8962_DACL_RETUNE_C7_1                 0x4C0C
+#define WM8962_DACL_RETUNE_C7_0                 0x4C0D
+#define WM8962_DACL_RETUNE_C8_1                 0x4C0E
+#define WM8962_DACL_RETUNE_C8_0                 0x4C0F
+#define WM8962_DACL_RETUNE_C9_1                 0x4C10
+#define WM8962_DACL_RETUNE_C9_0                 0x4C11
+#define WM8962_DACL_RETUNE_C10_1                0x4C12
+#define WM8962_DACL_RETUNE_C10_0                0x4C13
+#define WM8962_DACL_RETUNE_C11_1                0x4C14
+#define WM8962_DACL_RETUNE_C11_0                0x4C15
+#define WM8962_DACL_RETUNE_C12_1                0x4C16
+#define WM8962_DACL_RETUNE_C12_0                0x4C17
+#define WM8962_DACL_RETUNE_C13_1                0x4C18
+#define WM8962_DACL_RETUNE_C13_0                0x4C19
+#define WM8962_DACL_RETUNE_C14_1                0x4C1A
+#define WM8962_DACL_RETUNE_C14_0                0x4C1B
+#define WM8962_DACL_RETUNE_C15_1                0x4C1C
+#define WM8962_DACL_RETUNE_C15_0                0x4C1D
+#define WM8962_DACL_RETUNE_C16_1                0x4C1E
+#define WM8962_DACL_RETUNE_C16_0                0x4C1F
+#define WM8962_DACL_RETUNE_C17_1                0x4C20
+#define WM8962_DACL_RETUNE_C17_0                0x4C21
+#define WM8962_DACL_RETUNE_C18_1                0x4C22
+#define WM8962_DACL_RETUNE_C18_0                0x4C23
+#define WM8962_DACL_RETUNE_C19_1                0x4C24
+#define WM8962_DACL_RETUNE_C19_0                0x4C25
+#define WM8962_DACL_RETUNE_C20_1                0x4C26
+#define WM8962_DACL_RETUNE_C20_0                0x4C27
+#define WM8962_DACL_RETUNE_C21_1                0x4C28
+#define WM8962_DACL_RETUNE_C21_0                0x4C29
+#define WM8962_DACL_RETUNE_C22_1                0x4C2A
+#define WM8962_DACL_RETUNE_C22_0                0x4C2B
+#define WM8962_DACL_RETUNE_C23_1                0x4C2C
+#define WM8962_DACL_RETUNE_C23_0                0x4C2D
+#define WM8962_DACL_RETUNE_C24_1                0x4C2E
+#define WM8962_DACL_RETUNE_C24_0                0x4C2F
+#define WM8962_DACL_RETUNE_C25_1                0x4C30
+#define WM8962_DACL_RETUNE_C25_0                0x4C31
+#define WM8962_DACL_RETUNE_C26_1                0x4C32
+#define WM8962_DACL_RETUNE_C26_0                0x4C33
+#define WM8962_DACL_RETUNE_C27_1                0x4C34
+#define WM8962_DACL_RETUNE_C27_0                0x4C35
+#define WM8962_DACL_RETUNE_C28_1                0x4C36
+#define WM8962_DACL_RETUNE_C28_0                0x4C37
+#define WM8962_DACL_RETUNE_C29_1                0x4C38
+#define WM8962_DACL_RETUNE_C29_0                0x4C39
+#define WM8962_DACL_RETUNE_C30_1                0x4C3A
+#define WM8962_DACL_RETUNE_C30_0                0x4C3B
+#define WM8962_DACL_RETUNE_C31_1                0x4C3C
+#define WM8962_DACL_RETUNE_C31_0                0x4C3D
+#define WM8962_DACL_RETUNE_C32_1                0x4C3E
+#define WM8962_DACL_RETUNE_C32_0                0x4C3F
+#define WM8962_RETUNEDAC_PG2_1                  0x4E00
+#define WM8962_RETUNEDAC_PG2_0                  0x4E01
+#define WM8962_RETUNEDAC_PG_1                   0x4E02
+#define WM8962_RETUNEDAC_PG_0                   0x4E03
+#define WM8962_DACR_RETUNE_C1_1                 0x5000
+#define WM8962_DACR_RETUNE_C1_0                 0x5001
+#define WM8962_DACR_RETUNE_C2_1                 0x5002
+#define WM8962_DACR_RETUNE_C2_0                 0x5003
+#define WM8962_DACR_RETUNE_C3_1                 0x5004
+#define WM8962_DACR_RETUNE_C3_0                 0x5005
+#define WM8962_DACR_RETUNE_C4_1                 0x5006
+#define WM8962_DACR_RETUNE_C4_0                 0x5007
+#define WM8962_DACR_RETUNE_C5_1                 0x5008
+#define WM8962_DACR_RETUNE_C5_0                 0x5009
+#define WM8962_DACR_RETUNE_C6_1                 0x500A
+#define WM8962_DACR_RETUNE_C6_0                 0x500B
+#define WM8962_DACR_RETUNE_C7_1                 0x500C
+#define WM8962_DACR_RETUNE_C7_0                 0x500D
+#define WM8962_DACR_RETUNE_C8_1                 0x500E
+#define WM8962_DACR_RETUNE_C8_0                 0x500F
+#define WM8962_DACR_RETUNE_C9_1                 0x5010
+#define WM8962_DACR_RETUNE_C9_0                 0x5011
+#define WM8962_DACR_RETUNE_C10_1                0x5012
+#define WM8962_DACR_RETUNE_C10_0                0x5013
+#define WM8962_DACR_RETUNE_C11_1                0x5014
+#define WM8962_DACR_RETUNE_C11_0                0x5015
+#define WM8962_DACR_RETUNE_C12_1                0x5016
+#define WM8962_DACR_RETUNE_C12_0                0x5017
+#define WM8962_DACR_RETUNE_C13_1                0x5018
+#define WM8962_DACR_RETUNE_C13_0                0x5019
+#define WM8962_DACR_RETUNE_C14_1                0x501A
+#define WM8962_DACR_RETUNE_C14_0                0x501B
+#define WM8962_DACR_RETUNE_C15_1                0x501C
+#define WM8962_DACR_RETUNE_C15_0                0x501D
+#define WM8962_DACR_RETUNE_C16_1                0x501E
+#define WM8962_DACR_RETUNE_C16_0                0x501F
+#define WM8962_DACR_RETUNE_C17_1                0x5020
+#define WM8962_DACR_RETUNE_C17_0                0x5021
+#define WM8962_DACR_RETUNE_C18_1                0x5022
+#define WM8962_DACR_RETUNE_C18_0                0x5023
+#define WM8962_DACR_RETUNE_C19_1                0x5024
+#define WM8962_DACR_RETUNE_C19_0                0x5025
+#define WM8962_DACR_RETUNE_C20_1                0x5026
+#define WM8962_DACR_RETUNE_C20_0                0x5027
+#define WM8962_DACR_RETUNE_C21_1                0x5028
+#define WM8962_DACR_RETUNE_C21_0                0x5029
+#define WM8962_DACR_RETUNE_C22_1                0x502A
+#define WM8962_DACR_RETUNE_C22_0                0x502B
+#define WM8962_DACR_RETUNE_C23_1                0x502C
+#define WM8962_DACR_RETUNE_C23_0                0x502D
+#define WM8962_DACR_RETUNE_C24_1                0x502E
+#define WM8962_DACR_RETUNE_C24_0                0x502F
+#define WM8962_DACR_RETUNE_C25_1                0x5030
+#define WM8962_DACR_RETUNE_C25_0                0x5031
+#define WM8962_DACR_RETUNE_C26_1                0x5032
+#define WM8962_DACR_RETUNE_C26_0                0x5033
+#define WM8962_DACR_RETUNE_C27_1                0x5034
+#define WM8962_DACR_RETUNE_C27_0                0x5035
+#define WM8962_DACR_RETUNE_C28_1                0x5036
+#define WM8962_DACR_RETUNE_C28_0                0x5037
+#define WM8962_DACR_RETUNE_C29_1                0x5038
+#define WM8962_DACR_RETUNE_C29_0                0x5039
+#define WM8962_DACR_RETUNE_C30_1                0x503A
+#define WM8962_DACR_RETUNE_C30_0                0x503B
+#define WM8962_DACR_RETUNE_C31_1                0x503C
+#define WM8962_DACR_RETUNE_C31_0                0x503D
+#define WM8962_DACR_RETUNE_C32_1                0x503E
+#define WM8962_DACR_RETUNE_C32_0                0x503F
+#define WM8962_VSS_XHD2_1                       0x5200
+#define WM8962_VSS_XHD2_0                       0x5201
+#define WM8962_VSS_XHD3_1                       0x5202
+#define WM8962_VSS_XHD3_0                       0x5203
+#define WM8962_VSS_XHN1_1                       0x5204
+#define WM8962_VSS_XHN1_0                       0x5205
+#define WM8962_VSS_XHN2_1                       0x5206
+#define WM8962_VSS_XHN2_0                       0x5207
+#define WM8962_VSS_XHN3_1                       0x5208
+#define WM8962_VSS_XHN3_0                       0x5209
+#define WM8962_VSS_XLA_1                        0x520A
+#define WM8962_VSS_XLA_0                        0x520B
+#define WM8962_VSS_XLB_1                        0x520C
+#define WM8962_VSS_XLB_0                        0x520D
+#define WM8962_VSS_XLG_1                        0x520E
+#define WM8962_VSS_XLG_0                        0x520F
+#define WM8962_VSS_PG2_1                        0x5210
+#define WM8962_VSS_PG2_0                        0x5211
+#define WM8962_VSS_PG_1                         0x5212
+#define WM8962_VSS_PG_0                         0x5213
+#define WM8962_VSS_XTD1_1                       0x5214
+#define WM8962_VSS_XTD1_0                       0x5215
+#define WM8962_VSS_XTD2_1                       0x5216
+#define WM8962_VSS_XTD2_0                       0x5217
+#define WM8962_VSS_XTD3_1                       0x5218
+#define WM8962_VSS_XTD3_0                       0x5219
+#define WM8962_VSS_XTD4_1                       0x521A
+#define WM8962_VSS_XTD4_0                       0x521B
+#define WM8962_VSS_XTD5_1                       0x521C
+#define WM8962_VSS_XTD5_0                       0x521D
+#define WM8962_VSS_XTD6_1                       0x521E
+#define WM8962_VSS_XTD6_0                       0x521F
+#define WM8962_VSS_XTD7_1                       0x5220
+#define WM8962_VSS_XTD7_0                       0x5221
+#define WM8962_VSS_XTD8_1                       0x5222
+#define WM8962_VSS_XTD8_0                       0x5223
+#define WM8962_VSS_XTD9_1                       0x5224
+#define WM8962_VSS_XTD9_0                       0x5225
+#define WM8962_VSS_XTD10_1                      0x5226
+#define WM8962_VSS_XTD10_0                      0x5227
+#define WM8962_VSS_XTD11_1                      0x5228
+#define WM8962_VSS_XTD11_0                      0x5229
+#define WM8962_VSS_XTD12_1                      0x522A
+#define WM8962_VSS_XTD12_0                      0x522B
+#define WM8962_VSS_XTD13_1                      0x522C
+#define WM8962_VSS_XTD13_0                      0x522D
+#define WM8962_VSS_XTD14_1                      0x522E
+#define WM8962_VSS_XTD14_0                      0x522F
+#define WM8962_VSS_XTD15_1                      0x5230
+#define WM8962_VSS_XTD15_0                      0x5231
+#define WM8962_VSS_XTD16_1                      0x5232
+#define WM8962_VSS_XTD16_0                      0x5233
+#define WM8962_VSS_XTD17_1                      0x5234
+#define WM8962_VSS_XTD17_0                      0x5235
+#define WM8962_VSS_XTD18_1                      0x5236
+#define WM8962_VSS_XTD18_0                      0x5237
+#define WM8962_VSS_XTD19_1                      0x5238
+#define WM8962_VSS_XTD19_0                      0x5239
+#define WM8962_VSS_XTD20_1                      0x523A
+#define WM8962_VSS_XTD20_0                      0x523B
+#define WM8962_VSS_XTD21_1                      0x523C
+#define WM8962_VSS_XTD21_0                      0x523D
+#define WM8962_VSS_XTD22_1                      0x523E
+#define WM8962_VSS_XTD22_0                      0x523F
+#define WM8962_VSS_XTD23_1                      0x5240
+#define WM8962_VSS_XTD23_0                      0x5241
+#define WM8962_VSS_XTD24_1                      0x5242
+#define WM8962_VSS_XTD24_0                      0x5243
+#define WM8962_VSS_XTD25_1                      0x5244
+#define WM8962_VSS_XTD25_0                      0x5245
+#define WM8962_VSS_XTD26_1                      0x5246
+#define WM8962_VSS_XTD26_0                      0x5247
+#define WM8962_VSS_XTD27_1                      0x5248
+#define WM8962_VSS_XTD27_0                      0x5249
+#define WM8962_VSS_XTD28_1                      0x524A
+#define WM8962_VSS_XTD28_0                      0x524B
+#define WM8962_VSS_XTD29_1                      0x524C
+#define WM8962_VSS_XTD29_0                      0x524D
+#define WM8962_VSS_XTD30_1                      0x524E
+#define WM8962_VSS_XTD30_0                      0x524F
+#define WM8962_VSS_XTD31_1                      0x5250
+#define WM8962_VSS_XTD31_0                      0x5251
+#define WM8962_VSS_XTD32_1                      0x5252
+#define WM8962_VSS_XTD32_0                      0x5253
+#define WM8962_VSS_XTS1_1                       0x5254
+#define WM8962_VSS_XTS1_0                       0x5255
+#define WM8962_VSS_XTS2_1                       0x5256
+#define WM8962_VSS_XTS2_0                       0x5257
+#define WM8962_VSS_XTS3_1                       0x5258
+#define WM8962_VSS_XTS3_0                       0x5259
+#define WM8962_VSS_XTS4_1                       0x525A
+#define WM8962_VSS_XTS4_0                       0x525B
+#define WM8962_VSS_XTS5_1                       0x525C
+#define WM8962_VSS_XTS5_0                       0x525D
+#define WM8962_VSS_XTS6_1                       0x525E
+#define WM8962_VSS_XTS6_0                       0x525F
+#define WM8962_VSS_XTS7_1                       0x5260
+#define WM8962_VSS_XTS7_0                       0x5261
+#define WM8962_VSS_XTS8_1                       0x5262
+#define WM8962_VSS_XTS8_0                       0x5263
+#define WM8962_VSS_XTS9_1                       0x5264
+#define WM8962_VSS_XTS9_0                       0x5265
+#define WM8962_VSS_XTS10_1                      0x5266
+#define WM8962_VSS_XTS10_0                      0x5267
+#define WM8962_VSS_XTS11_1                      0x5268
+#define WM8962_VSS_XTS11_0                      0x5269
+#define WM8962_VSS_XTS12_1                      0x526A
+#define WM8962_VSS_XTS12_0                      0x526B
+#define WM8962_VSS_XTS13_1                      0x526C
+#define WM8962_VSS_XTS13_0                      0x526D
+#define WM8962_VSS_XTS14_1                      0x526E
+#define WM8962_VSS_XTS14_0                      0x526F
+#define WM8962_VSS_XTS15_1                      0x5270
+#define WM8962_VSS_XTS15_0                      0x5271
+#define WM8962_VSS_XTS16_1                      0x5272
+#define WM8962_VSS_XTS16_0                      0x5273
+#define WM8962_VSS_XTS17_1                      0x5274
+#define WM8962_VSS_XTS17_0                      0x5275
+#define WM8962_VSS_XTS18_1                      0x5276
+#define WM8962_VSS_XTS18_0                      0x5277
+#define WM8962_VSS_XTS19_1                      0x5278
+#define WM8962_VSS_XTS19_0                      0x5279
+#define WM8962_VSS_XTS20_1                      0x527A
+#define WM8962_VSS_XTS20_0                      0x527B
+#define WM8962_VSS_XTS21_1                      0x527C
+#define WM8962_VSS_XTS21_0                      0x527D
+#define WM8962_VSS_XTS22_1                      0x527E
+#define WM8962_VSS_XTS22_0                      0x527F
+#define WM8962_VSS_XTS23_1                      0x5280
+#define WM8962_VSS_XTS23_0                      0x5281
+#define WM8962_VSS_XTS24_1                      0x5282
+#define WM8962_VSS_XTS24_0                      0x5283
+#define WM8962_VSS_XTS25_1                      0x5284
+#define WM8962_VSS_XTS25_0                      0x5285
+#define WM8962_VSS_XTS26_1                      0x5286
+#define WM8962_VSS_XTS26_0                      0x5287
+#define WM8962_VSS_XTS27_1                      0x5288
+#define WM8962_VSS_XTS27_0                      0x5289
+#define WM8962_VSS_XTS28_1                      0x528A
+#define WM8962_VSS_XTS28_0                      0x528B
+#define WM8962_VSS_XTS29_1                      0x528C
+#define WM8962_VSS_XTS29_0                      0x528D
+#define WM8962_VSS_XTS30_1                      0x528E
+#define WM8962_VSS_XTS30_0                      0x528F
+#define WM8962_VSS_XTS31_1                      0x5290
+#define WM8962_VSS_XTS31_0                      0x5291
+#define WM8962_VSS_XTS32_1                      0x5292
+#define WM8962_VSS_XTS32_0                      0x5293
+
+#define WM8962_REGISTER_COUNT                   1138
+#define WM8962_MAX_REGISTER                     0x5293
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Left Input volume
+ */
+#define WM8962_IN_VU                            0x0100  /* IN_VU */
+#define WM8962_IN_VU_MASK                       0x0100  /* IN_VU */
+#define WM8962_IN_VU_SHIFT                           8  /* IN_VU */
+#define WM8962_IN_VU_WIDTH                           1  /* IN_VU */
+#define WM8962_INPGAL_MUTE                      0x0080  /* INPGAL_MUTE */
+#define WM8962_INPGAL_MUTE_MASK                 0x0080  /* INPGAL_MUTE */
+#define WM8962_INPGAL_MUTE_SHIFT                     7  /* INPGAL_MUTE */
+#define WM8962_INPGAL_MUTE_WIDTH                     1  /* INPGAL_MUTE */
+#define WM8962_INL_ZC                           0x0040  /* INL_ZC */
+#define WM8962_INL_ZC_MASK                      0x0040  /* INL_ZC */
+#define WM8962_INL_ZC_SHIFT                          6  /* INL_ZC */
+#define WM8962_INL_ZC_WIDTH                          1  /* INL_ZC */
+#define WM8962_INL_VOL_MASK                     0x003F  /* INL_VOL - [5:0] */
+#define WM8962_INL_VOL_SHIFT                         0  /* INL_VOL - [5:0] */
+#define WM8962_INL_VOL_WIDTH                         6  /* INL_VOL - [5:0] */
+
+/*
+ * R1 (0x01) - Right Input volume
+ */
+#define WM8962_CUST_ID_MASK                     0xF000  /* CUST_ID - [15:12] */
+#define WM8962_CUST_ID_SHIFT                        12  /* CUST_ID - [15:12] */
+#define WM8962_CUST_ID_WIDTH                         4  /* CUST_ID - [15:12] */
+#define WM8962_CHIP_REV_MASK                    0x0E00  /* CHIP_REV - [11:9] */
+#define WM8962_CHIP_REV_SHIFT                        9  /* CHIP_REV - [11:9] */
+#define WM8962_CHIP_REV_WIDTH                        3  /* CHIP_REV - [11:9] */
+#define WM8962_IN_VU                            0x0100  /* IN_VU */
+#define WM8962_IN_VU_MASK                       0x0100  /* IN_VU */
+#define WM8962_IN_VU_SHIFT                           8  /* IN_VU */
+#define WM8962_IN_VU_WIDTH                           1  /* IN_VU */
+#define WM8962_INPGAR_MUTE                      0x0080  /* INPGAR_MUTE */
+#define WM8962_INPGAR_MUTE_MASK                 0x0080  /* INPGAR_MUTE */
+#define WM8962_INPGAR_MUTE_SHIFT                     7  /* INPGAR_MUTE */
+#define WM8962_INPGAR_MUTE_WIDTH                     1  /* INPGAR_MUTE */
+#define WM8962_INR_ZC                           0x0040  /* INR_ZC */
+#define WM8962_INR_ZC_MASK                      0x0040  /* INR_ZC */
+#define WM8962_INR_ZC_SHIFT                          6  /* INR_ZC */
+#define WM8962_INR_ZC_WIDTH                          1  /* INR_ZC */
+#define WM8962_INR_VOL_MASK                     0x003F  /* INR_VOL - [5:0] */
+#define WM8962_INR_VOL_SHIFT                         0  /* INR_VOL - [5:0] */
+#define WM8962_INR_VOL_WIDTH                         6  /* INR_VOL - [5:0] */
+
+/*
+ * R2 (0x02) - HPOUTL volume
+ */
+#define WM8962_HPOUT_VU                         0x0100  /* HPOUT_VU */
+#define WM8962_HPOUT_VU_MASK                    0x0100  /* HPOUT_VU */
+#define WM8962_HPOUT_VU_SHIFT                        8  /* HPOUT_VU */
+#define WM8962_HPOUT_VU_WIDTH                        1  /* HPOUT_VU */
+#define WM8962_HPOUTL_ZC                        0x0080  /* HPOUTL_ZC */
+#define WM8962_HPOUTL_ZC_MASK                   0x0080  /* HPOUTL_ZC */
+#define WM8962_HPOUTL_ZC_SHIFT                       7  /* HPOUTL_ZC */
+#define WM8962_HPOUTL_ZC_WIDTH                       1  /* HPOUTL_ZC */
+#define WM8962_HPOUTL_VOL_MASK                  0x007F  /* HPOUTL_VOL - [6:0] */
+#define WM8962_HPOUTL_VOL_SHIFT                      0  /* HPOUTL_VOL - [6:0] */
+#define WM8962_HPOUTL_VOL_WIDTH                      7  /* HPOUTL_VOL - [6:0] */
+
+/*
+ * R3 (0x03) - HPOUTR volume
+ */
+#define WM8962_HPOUT_VU                         0x0100  /* HPOUT_VU */
+#define WM8962_HPOUT_VU_MASK                    0x0100  /* HPOUT_VU */
+#define WM8962_HPOUT_VU_SHIFT                        8  /* HPOUT_VU */
+#define WM8962_HPOUT_VU_WIDTH                        1  /* HPOUT_VU */
+#define WM8962_HPOUTR_ZC                        0x0080  /* HPOUTR_ZC */
+#define WM8962_HPOUTR_ZC_MASK                   0x0080  /* HPOUTR_ZC */
+#define WM8962_HPOUTR_ZC_SHIFT                       7  /* HPOUTR_ZC */
+#define WM8962_HPOUTR_ZC_WIDTH                       1  /* HPOUTR_ZC */
+#define WM8962_HPOUTR_VOL_MASK                  0x007F  /* HPOUTR_VOL - [6:0] */
+#define WM8962_HPOUTR_VOL_SHIFT                      0  /* HPOUTR_VOL - [6:0] */
+#define WM8962_HPOUTR_VOL_WIDTH                      7  /* HPOUTR_VOL - [6:0] */
+
+/*
+ * R4 (0x04) - Clocking1
+ */
+#define WM8962_DSPCLK_DIV_MASK                  0x0600  /* DSPCLK_DIV - [10:9] */
+#define WM8962_DSPCLK_DIV_SHIFT                      9  /* DSPCLK_DIV - [10:9] */
+#define WM8962_DSPCLK_DIV_WIDTH                      2  /* DSPCLK_DIV - [10:9] */
+#define WM8962_ADCSYS_CLK_DIV_MASK              0x01C0  /* ADCSYS_CLK_DIV - [8:6] */
+#define WM8962_ADCSYS_CLK_DIV_SHIFT                  6  /* ADCSYS_CLK_DIV - [8:6] */
+#define WM8962_ADCSYS_CLK_DIV_WIDTH                  3  /* ADCSYS_CLK_DIV - [8:6] */
+#define WM8962_DACSYS_CLK_DIV_MASK              0x0038  /* DACSYS_CLK_DIV - [5:3] */
+#define WM8962_DACSYS_CLK_DIV_SHIFT                  3  /* DACSYS_CLK_DIV - [5:3] */
+#define WM8962_DACSYS_CLK_DIV_WIDTH                  3  /* DACSYS_CLK_DIV - [5:3] */
+#define WM8962_MCLKDIV_MASK                     0x0006  /* MCLKDIV - [2:1] */
+#define WM8962_MCLKDIV_SHIFT                         1  /* MCLKDIV - [2:1] */
+#define WM8962_MCLKDIV_WIDTH                         2  /* MCLKDIV - [2:1] */
+
+/*
+ * R5 (0x05) - ADC & DAC Control 1
+ */
+#define WM8962_ADCR_DAT_INV                     0x0040  /* ADCR_DAT_INV */
+#define WM8962_ADCR_DAT_INV_MASK                0x0040  /* ADCR_DAT_INV */
+#define WM8962_ADCR_DAT_INV_SHIFT                    6  /* ADCR_DAT_INV */
+#define WM8962_ADCR_DAT_INV_WIDTH                    1  /* ADCR_DAT_INV */
+#define WM8962_ADCL_DAT_INV                     0x0020  /* ADCL_DAT_INV */
+#define WM8962_ADCL_DAT_INV_MASK                0x0020  /* ADCL_DAT_INV */
+#define WM8962_ADCL_DAT_INV_SHIFT                    5  /* ADCL_DAT_INV */
+#define WM8962_ADCL_DAT_INV_WIDTH                    1  /* ADCL_DAT_INV */
+#define WM8962_DAC_MUTE_RAMP                    0x0010  /* DAC_MUTE_RAMP */
+#define WM8962_DAC_MUTE_RAMP_MASK               0x0010  /* DAC_MUTE_RAMP */
+#define WM8962_DAC_MUTE_RAMP_SHIFT                   4  /* DAC_MUTE_RAMP */
+#define WM8962_DAC_MUTE_RAMP_WIDTH                   1  /* DAC_MUTE_RAMP */
+#define WM8962_DAC_MUTE                         0x0008  /* DAC_MUTE */
+#define WM8962_DAC_MUTE_MASK                    0x0008  /* DAC_MUTE */
+#define WM8962_DAC_MUTE_SHIFT                        3  /* DAC_MUTE */
+#define WM8962_DAC_MUTE_WIDTH                        1  /* DAC_MUTE */
+#define WM8962_DAC_DEEMP_MASK                   0x0006  /* DAC_DEEMP - [2:1] */
+#define WM8962_DAC_DEEMP_SHIFT                       1  /* DAC_DEEMP - [2:1] */
+#define WM8962_DAC_DEEMP_WIDTH                       2  /* DAC_DEEMP - [2:1] */
+#define WM8962_ADC_HPF_DIS                      0x0001  /* ADC_HPF_DIS */
+#define WM8962_ADC_HPF_DIS_MASK                 0x0001  /* ADC_HPF_DIS */
+#define WM8962_ADC_HPF_DIS_SHIFT                     0  /* ADC_HPF_DIS */
+#define WM8962_ADC_HPF_DIS_WIDTH                     1  /* ADC_HPF_DIS */
+
+/*
+ * R6 (0x06) - ADC & DAC Control 2
+ */
+#define WM8962_ADC_HPF_SR_MASK                  0x3000  /* ADC_HPF_SR - [13:12] */
+#define WM8962_ADC_HPF_SR_SHIFT                     12  /* ADC_HPF_SR - [13:12] */
+#define WM8962_ADC_HPF_SR_WIDTH                      2  /* ADC_HPF_SR - [13:12] */
+#define WM8962_ADC_HPF_MODE                     0x0400  /* ADC_HPF_MODE */
+#define WM8962_ADC_HPF_MODE_MASK                0x0400  /* ADC_HPF_MODE */
+#define WM8962_ADC_HPF_MODE_SHIFT                   10  /* ADC_HPF_MODE */
+#define WM8962_ADC_HPF_MODE_WIDTH                    1  /* ADC_HPF_MODE */
+#define WM8962_ADC_HPF_CUT_MASK                 0x0380  /* ADC_HPF_CUT - [9:7] */
+#define WM8962_ADC_HPF_CUT_SHIFT                     7  /* ADC_HPF_CUT - [9:7] */
+#define WM8962_ADC_HPF_CUT_WIDTH                     3  /* ADC_HPF_CUT - [9:7] */
+#define WM8962_DACR_DAT_INV                     0x0040  /* DACR_DAT_INV */
+#define WM8962_DACR_DAT_INV_MASK                0x0040  /* DACR_DAT_INV */
+#define WM8962_DACR_DAT_INV_SHIFT                    6  /* DACR_DAT_INV */
+#define WM8962_DACR_DAT_INV_WIDTH                    1  /* DACR_DAT_INV */
+#define WM8962_DACL_DAT_INV                     0x0020  /* DACL_DAT_INV */
+#define WM8962_DACL_DAT_INV_MASK                0x0020  /* DACL_DAT_INV */
+#define WM8962_DACL_DAT_INV_SHIFT                    5  /* DACL_DAT_INV */
+#define WM8962_DACL_DAT_INV_WIDTH                    1  /* DACL_DAT_INV */
+#define WM8962_DAC_UNMUTE_RAMP                  0x0008  /* DAC_UNMUTE_RAMP */
+#define WM8962_DAC_UNMUTE_RAMP_MASK             0x0008  /* DAC_UNMUTE_RAMP */
+#define WM8962_DAC_UNMUTE_RAMP_SHIFT                 3  /* DAC_UNMUTE_RAMP */
+#define WM8962_DAC_UNMUTE_RAMP_WIDTH                 1  /* DAC_UNMUTE_RAMP */
+#define WM8962_DAC_MUTERATE                     0x0004  /* DAC_MUTERATE */
+#define WM8962_DAC_MUTERATE_MASK                0x0004  /* DAC_MUTERATE */
+#define WM8962_DAC_MUTERATE_SHIFT                    2  /* DAC_MUTERATE */
+#define WM8962_DAC_MUTERATE_WIDTH                    1  /* DAC_MUTERATE */
+#define WM8962_DAC_HP                           0x0001  /* DAC_HP */
+#define WM8962_DAC_HP_MASK                      0x0001  /* DAC_HP */
+#define WM8962_DAC_HP_SHIFT                          0  /* DAC_HP */
+#define WM8962_DAC_HP_WIDTH                          1  /* DAC_HP */
+
+/*
+ * R7 (0x07) - Audio Interface 0
+ */
+#define WM8962_AIFDAC_TDM_MODE                  0x1000  /* AIFDAC_TDM_MODE */
+#define WM8962_AIFDAC_TDM_MODE_MASK             0x1000  /* AIFDAC_TDM_MODE */
+#define WM8962_AIFDAC_TDM_MODE_SHIFT                12  /* AIFDAC_TDM_MODE */
+#define WM8962_AIFDAC_TDM_MODE_WIDTH                 1  /* AIFDAC_TDM_MODE */
+#define WM8962_AIFDAC_TDM_SLOT                  0x0800  /* AIFDAC_TDM_SLOT */
+#define WM8962_AIFDAC_TDM_SLOT_MASK             0x0800  /* AIFDAC_TDM_SLOT */
+#define WM8962_AIFDAC_TDM_SLOT_SHIFT                11  /* AIFDAC_TDM_SLOT */
+#define WM8962_AIFDAC_TDM_SLOT_WIDTH                 1  /* AIFDAC_TDM_SLOT */
+#define WM8962_AIFADC_TDM_MODE                  0x0400  /* AIFADC_TDM_MODE */
+#define WM8962_AIFADC_TDM_MODE_MASK             0x0400  /* AIFADC_TDM_MODE */
+#define WM8962_AIFADC_TDM_MODE_SHIFT                10  /* AIFADC_TDM_MODE */
+#define WM8962_AIFADC_TDM_MODE_WIDTH                 1  /* AIFADC_TDM_MODE */
+#define WM8962_AIFADC_TDM_SLOT                  0x0200  /* AIFADC_TDM_SLOT */
+#define WM8962_AIFADC_TDM_SLOT_MASK             0x0200  /* AIFADC_TDM_SLOT */
+#define WM8962_AIFADC_TDM_SLOT_SHIFT                 9  /* AIFADC_TDM_SLOT */
+#define WM8962_AIFADC_TDM_SLOT_WIDTH                 1  /* AIFADC_TDM_SLOT */
+#define WM8962_ADC_LRSWAP                       0x0100  /* ADC_LRSWAP */
+#define WM8962_ADC_LRSWAP_MASK                  0x0100  /* ADC_LRSWAP */
+#define WM8962_ADC_LRSWAP_SHIFT                      8  /* ADC_LRSWAP */
+#define WM8962_ADC_LRSWAP_WIDTH                      1  /* ADC_LRSWAP */
+#define WM8962_BCLK_INV                         0x0080  /* BCLK_INV */
+#define WM8962_BCLK_INV_MASK                    0x0080  /* BCLK_INV */
+#define WM8962_BCLK_INV_SHIFT                        7  /* BCLK_INV */
+#define WM8962_BCLK_INV_WIDTH                        1  /* BCLK_INV */
+#define WM8962_MSTR                             0x0040  /* MSTR */
+#define WM8962_MSTR_MASK                        0x0040  /* MSTR */
+#define WM8962_MSTR_SHIFT                            6  /* MSTR */
+#define WM8962_MSTR_WIDTH                            1  /* MSTR */
+#define WM8962_DAC_LRSWAP                       0x0020  /* DAC_LRSWAP */
+#define WM8962_DAC_LRSWAP_MASK                  0x0020  /* DAC_LRSWAP */
+#define WM8962_DAC_LRSWAP_SHIFT                      5  /* DAC_LRSWAP */
+#define WM8962_DAC_LRSWAP_WIDTH                      1  /* DAC_LRSWAP */
+#define WM8962_LRCLK_INV                        0x0010  /* LRCLK_INV */
+#define WM8962_LRCLK_INV_MASK                   0x0010  /* LRCLK_INV */
+#define WM8962_LRCLK_INV_SHIFT                       4  /* LRCLK_INV */
+#define WM8962_LRCLK_INV_WIDTH                       1  /* LRCLK_INV */
+#define WM8962_WL_MASK                          0x000C  /* WL - [3:2] */
+#define WM8962_WL_SHIFT                              2  /* WL - [3:2] */
+#define WM8962_WL_WIDTH                              2  /* WL - [3:2] */
+#define WM8962_FMT_MASK                         0x0003  /* FMT - [1:0] */
+#define WM8962_FMT_SHIFT                             0  /* FMT - [1:0] */
+#define WM8962_FMT_WIDTH                             2  /* FMT - [1:0] */
+
+/*
+ * R8 (0x08) - Clocking2
+ */
+#define WM8962_CLKREG_OVD                       0x0800  /* CLKREG_OVD */
+#define WM8962_CLKREG_OVD_MASK                  0x0800  /* CLKREG_OVD */
+#define WM8962_CLKREG_OVD_SHIFT                     11  /* CLKREG_OVD */
+#define WM8962_CLKREG_OVD_WIDTH                      1  /* CLKREG_OVD */
+#define WM8962_SYSCLK_SRC_MASK                  0x0600  /* SYSCLK_SRC - [10:9] */
+#define WM8962_SYSCLK_SRC_SHIFT                      9  /* SYSCLK_SRC - [10:9] */
+#define WM8962_SYSCLK_SRC_WIDTH                      2  /* SYSCLK_SRC - [10:9] */
+#define WM8962_CLASSD_CLK_DIV_MASK              0x01C0  /* CLASSD_CLK_DIV - [8:6] */
+#define WM8962_CLASSD_CLK_DIV_SHIFT                  6  /* CLASSD_CLK_DIV - [8:6] */
+#define WM8962_CLASSD_CLK_DIV_WIDTH                  3  /* CLASSD_CLK_DIV - [8:6] */
+#define WM8962_SYSCLK_ENA                       0x0020  /* SYSCLK_ENA */
+#define WM8962_SYSCLK_ENA_MASK                  0x0020  /* SYSCLK_ENA */
+#define WM8962_SYSCLK_ENA_SHIFT                      5  /* SYSCLK_ENA */
+#define WM8962_SYSCLK_ENA_WIDTH                      1  /* SYSCLK_ENA */
+#define WM8962_BCLK_DIV_MASK                    0x000F  /* BCLK_DIV - [3:0] */
+#define WM8962_BCLK_DIV_SHIFT                        0  /* BCLK_DIV - [3:0] */
+#define WM8962_BCLK_DIV_WIDTH                        4  /* BCLK_DIV - [3:0] */
+
+/*
+ * R9 (0x09) - Audio Interface 1
+ */
+#define WM8962_AUTOMUTE_STS                     0x0800  /* AUTOMUTE_STS */
+#define WM8962_AUTOMUTE_STS_MASK                0x0800  /* AUTOMUTE_STS */
+#define WM8962_AUTOMUTE_STS_SHIFT                   11  /* AUTOMUTE_STS */
+#define WM8962_AUTOMUTE_STS_WIDTH                    1  /* AUTOMUTE_STS */
+#define WM8962_DAC_AUTOMUTE_SAMPLES_MASK        0x0300  /* DAC_AUTOMUTE_SAMPLES - [9:8] */
+#define WM8962_DAC_AUTOMUTE_SAMPLES_SHIFT            8  /* DAC_AUTOMUTE_SAMPLES - [9:8] */
+#define WM8962_DAC_AUTOMUTE_SAMPLES_WIDTH            2  /* DAC_AUTOMUTE_SAMPLES - [9:8] */
+#define WM8962_DAC_AUTOMUTE                     0x0080  /* DAC_AUTOMUTE */
+#define WM8962_DAC_AUTOMUTE_MASK                0x0080  /* DAC_AUTOMUTE */
+#define WM8962_DAC_AUTOMUTE_SHIFT                    7  /* DAC_AUTOMUTE */
+#define WM8962_DAC_AUTOMUTE_WIDTH                    1  /* DAC_AUTOMUTE */
+#define WM8962_DAC_COMP                         0x0010  /* DAC_COMP */
+#define WM8962_DAC_COMP_MASK                    0x0010  /* DAC_COMP */
+#define WM8962_DAC_COMP_SHIFT                        4  /* DAC_COMP */
+#define WM8962_DAC_COMP_WIDTH                        1  /* DAC_COMP */
+#define WM8962_DAC_COMPMODE                     0x0008  /* DAC_COMPMODE */
+#define WM8962_DAC_COMPMODE_MASK                0x0008  /* DAC_COMPMODE */
+#define WM8962_DAC_COMPMODE_SHIFT                    3  /* DAC_COMPMODE */
+#define WM8962_DAC_COMPMODE_WIDTH                    1  /* DAC_COMPMODE */
+#define WM8962_ADC_COMP                         0x0004  /* ADC_COMP */
+#define WM8962_ADC_COMP_MASK                    0x0004  /* ADC_COMP */
+#define WM8962_ADC_COMP_SHIFT                        2  /* ADC_COMP */
+#define WM8962_ADC_COMP_WIDTH                        1  /* ADC_COMP */
+#define WM8962_ADC_COMPMODE                     0x0002  /* ADC_COMPMODE */
+#define WM8962_ADC_COMPMODE_MASK                0x0002  /* ADC_COMPMODE */
+#define WM8962_ADC_COMPMODE_SHIFT                    1  /* ADC_COMPMODE */
+#define WM8962_ADC_COMPMODE_WIDTH                    1  /* ADC_COMPMODE */
+#define WM8962_LOOPBACK                         0x0001  /* LOOPBACK */
+#define WM8962_LOOPBACK_MASK                    0x0001  /* LOOPBACK */
+#define WM8962_LOOPBACK_SHIFT                        0  /* LOOPBACK */
+#define WM8962_LOOPBACK_WIDTH                        1  /* LOOPBACK */
+
+/*
+ * R10 (0x0A) - Left DAC volume
+ */
+#define WM8962_DAC_VU                           0x0100  /* DAC_VU */
+#define WM8962_DAC_VU_MASK                      0x0100  /* DAC_VU */
+#define WM8962_DAC_VU_SHIFT                          8  /* DAC_VU */
+#define WM8962_DAC_VU_WIDTH                          1  /* DAC_VU */
+#define WM8962_DACL_VOL_MASK                    0x00FF  /* DACL_VOL - [7:0] */
+#define WM8962_DACL_VOL_SHIFT                        0  /* DACL_VOL - [7:0] */
+#define WM8962_DACL_VOL_WIDTH                        8  /* DACL_VOL - [7:0] */
+
+/*
+ * R11 (0x0B) - Right DAC volume
+ */
+#define WM8962_DAC_VU                           0x0100  /* DAC_VU */
+#define WM8962_DAC_VU_MASK                      0x0100  /* DAC_VU */
+#define WM8962_DAC_VU_SHIFT                          8  /* DAC_VU */
+#define WM8962_DAC_VU_WIDTH                          1  /* DAC_VU */
+#define WM8962_DACR_VOL_MASK                    0x00FF  /* DACR_VOL - [7:0] */
+#define WM8962_DACR_VOL_SHIFT                        0  /* DACR_VOL - [7:0] */
+#define WM8962_DACR_VOL_WIDTH                        8  /* DACR_VOL - [7:0] */
+
+/*
+ * R14 (0x0E) - Audio Interface 2
+ */
+#define WM8962_AIF_RATE_MASK                    0x07FF  /* AIF_RATE - [10:0] */
+#define WM8962_AIF_RATE_SHIFT                        0  /* AIF_RATE - [10:0] */
+#define WM8962_AIF_RATE_WIDTH                       11  /* AIF_RATE - [10:0] */
+
+/*
+ * R15 (0x0F) - Software Reset
+ */
+#define WM8962_SW_RESET_MASK                    0xFFFF  /* SW_RESET - [15:0] */
+#define WM8962_SW_RESET_SHIFT                        0  /* SW_RESET - [15:0] */
+#define WM8962_SW_RESET_WIDTH                       16  /* SW_RESET - [15:0] */
+
+/*
+ * R17 (0x11) - ALC1
+ */
+#define WM8962_ALC_INACTIVE_ENA                 0x0400  /* ALC_INACTIVE_ENA */
+#define WM8962_ALC_INACTIVE_ENA_MASK            0x0400  /* ALC_INACTIVE_ENA */
+#define WM8962_ALC_INACTIVE_ENA_SHIFT               10  /* ALC_INACTIVE_ENA */
+#define WM8962_ALC_INACTIVE_ENA_WIDTH                1  /* ALC_INACTIVE_ENA */
+#define WM8962_ALC_LVL_MODE                     0x0200  /* ALC_LVL_MODE */
+#define WM8962_ALC_LVL_MODE_MASK                0x0200  /* ALC_LVL_MODE */
+#define WM8962_ALC_LVL_MODE_SHIFT                    9  /* ALC_LVL_MODE */
+#define WM8962_ALC_LVL_MODE_WIDTH                    1  /* ALC_LVL_MODE */
+#define WM8962_ALCL_ENA                         0x0100  /* ALCL_ENA */
+#define WM8962_ALCL_ENA_MASK                    0x0100  /* ALCL_ENA */
+#define WM8962_ALCL_ENA_SHIFT                        8  /* ALCL_ENA */
+#define WM8962_ALCL_ENA_WIDTH                        1  /* ALCL_ENA */
+#define WM8962_ALCR_ENA                         0x0080  /* ALCR_ENA */
+#define WM8962_ALCR_ENA_MASK                    0x0080  /* ALCR_ENA */
+#define WM8962_ALCR_ENA_SHIFT                        7  /* ALCR_ENA */
+#define WM8962_ALCR_ENA_WIDTH                        1  /* ALCR_ENA */
+#define WM8962_ALC_MAXGAIN_MASK                 0x0070  /* ALC_MAXGAIN - [6:4] */
+#define WM8962_ALC_MAXGAIN_SHIFT                     4  /* ALC_MAXGAIN - [6:4] */
+#define WM8962_ALC_MAXGAIN_WIDTH                     3  /* ALC_MAXGAIN - [6:4] */
+#define WM8962_ALC_LVL_MASK                     0x000F  /* ALC_LVL - [3:0] */
+#define WM8962_ALC_LVL_SHIFT                         0  /* ALC_LVL - [3:0] */
+#define WM8962_ALC_LVL_WIDTH                         4  /* ALC_LVL - [3:0] */
+
+/*
+ * R18 (0x12) - ALC2
+ */
+#define WM8962_ALC_LOCK_STS                     0x8000  /* ALC_LOCK_STS */
+#define WM8962_ALC_LOCK_STS_MASK                0x8000  /* ALC_LOCK_STS */
+#define WM8962_ALC_LOCK_STS_SHIFT                   15  /* ALC_LOCK_STS */
+#define WM8962_ALC_LOCK_STS_WIDTH                    1  /* ALC_LOCK_STS */
+#define WM8962_ALC_THRESH_STS                   0x4000  /* ALC_THRESH_STS */
+#define WM8962_ALC_THRESH_STS_MASK              0x4000  /* ALC_THRESH_STS */
+#define WM8962_ALC_THRESH_STS_SHIFT                 14  /* ALC_THRESH_STS */
+#define WM8962_ALC_THRESH_STS_WIDTH                  1  /* ALC_THRESH_STS */
+#define WM8962_ALC_SAT_STS                      0x2000  /* ALC_SAT_STS */
+#define WM8962_ALC_SAT_STS_MASK                 0x2000  /* ALC_SAT_STS */
+#define WM8962_ALC_SAT_STS_SHIFT                    13  /* ALC_SAT_STS */
+#define WM8962_ALC_SAT_STS_WIDTH                     1  /* ALC_SAT_STS */
+#define WM8962_ALC_PKOVR_STS                    0x1000  /* ALC_PKOVR_STS */
+#define WM8962_ALC_PKOVR_STS_MASK               0x1000  /* ALC_PKOVR_STS */
+#define WM8962_ALC_PKOVR_STS_SHIFT                  12  /* ALC_PKOVR_STS */
+#define WM8962_ALC_PKOVR_STS_WIDTH                   1  /* ALC_PKOVR_STS */
+#define WM8962_ALC_NGATE_STS                    0x0800  /* ALC_NGATE_STS */
+#define WM8962_ALC_NGATE_STS_MASK               0x0800  /* ALC_NGATE_STS */
+#define WM8962_ALC_NGATE_STS_SHIFT                  11  /* ALC_NGATE_STS */
+#define WM8962_ALC_NGATE_STS_WIDTH                   1  /* ALC_NGATE_STS */
+#define WM8962_ALC_ZC                           0x0080  /* ALC_ZC */
+#define WM8962_ALC_ZC_MASK                      0x0080  /* ALC_ZC */
+#define WM8962_ALC_ZC_SHIFT                          7  /* ALC_ZC */
+#define WM8962_ALC_ZC_WIDTH                          1  /* ALC_ZC */
+#define WM8962_ALC_MINGAIN_MASK                 0x0070  /* ALC_MINGAIN - [6:4] */
+#define WM8962_ALC_MINGAIN_SHIFT                     4  /* ALC_MINGAIN - [6:4] */
+#define WM8962_ALC_MINGAIN_WIDTH                     3  /* ALC_MINGAIN - [6:4] */
+#define WM8962_ALC_HLD_MASK                     0x000F  /* ALC_HLD - [3:0] */
+#define WM8962_ALC_HLD_SHIFT                         0  /* ALC_HLD - [3:0] */
+#define WM8962_ALC_HLD_WIDTH                         4  /* ALC_HLD - [3:0] */
+
+/*
+ * R19 (0x13) - ALC3
+ */
+#define WM8962_ALC_NGATE_GAIN_MASK              0x1C00  /* ALC_NGATE_GAIN - [12:10] */
+#define WM8962_ALC_NGATE_GAIN_SHIFT                 10  /* ALC_NGATE_GAIN - [12:10] */
+#define WM8962_ALC_NGATE_GAIN_WIDTH                  3  /* ALC_NGATE_GAIN - [12:10] */
+#define WM8962_ALC_MODE                         0x0100  /* ALC_MODE */
+#define WM8962_ALC_MODE_MASK                    0x0100  /* ALC_MODE */
+#define WM8962_ALC_MODE_SHIFT                        8  /* ALC_MODE */
+#define WM8962_ALC_MODE_WIDTH                        1  /* ALC_MODE */
+#define WM8962_ALC_DCY_MASK                     0x00F0  /* ALC_DCY - [7:4] */
+#define WM8962_ALC_DCY_SHIFT                         4  /* ALC_DCY - [7:4] */
+#define WM8962_ALC_DCY_WIDTH                         4  /* ALC_DCY - [7:4] */
+#define WM8962_ALC_ATK_MASK                     0x000F  /* ALC_ATK - [3:0] */
+#define WM8962_ALC_ATK_SHIFT                         0  /* ALC_ATK - [3:0] */
+#define WM8962_ALC_ATK_WIDTH                         4  /* ALC_ATK - [3:0] */
+
+/*
+ * R20 (0x14) - Noise Gate
+ */
+#define WM8962_ALC_NGATE_DCY_MASK               0xF000  /* ALC_NGATE_DCY - [15:12] */
+#define WM8962_ALC_NGATE_DCY_SHIFT                  12  /* ALC_NGATE_DCY - [15:12] */
+#define WM8962_ALC_NGATE_DCY_WIDTH                   4  /* ALC_NGATE_DCY - [15:12] */
+#define WM8962_ALC_NGATE_ATK_MASK               0x0F00  /* ALC_NGATE_ATK - [11:8] */
+#define WM8962_ALC_NGATE_ATK_SHIFT                   8  /* ALC_NGATE_ATK - [11:8] */
+#define WM8962_ALC_NGATE_ATK_WIDTH                   4  /* ALC_NGATE_ATK - [11:8] */
+#define WM8962_ALC_NGATE_THR_MASK               0x00F8  /* ALC_NGATE_THR - [7:3] */
+#define WM8962_ALC_NGATE_THR_SHIFT                   3  /* ALC_NGATE_THR - [7:3] */
+#define WM8962_ALC_NGATE_THR_WIDTH                   5  /* ALC_NGATE_THR - [7:3] */
+#define WM8962_ALC_NGATE_MODE_MASK              0x0006  /* ALC_NGATE_MODE - [2:1] */
+#define WM8962_ALC_NGATE_MODE_SHIFT                  1  /* ALC_NGATE_MODE - [2:1] */
+#define WM8962_ALC_NGATE_MODE_WIDTH                  2  /* ALC_NGATE_MODE - [2:1] */
+#define WM8962_ALC_NGATE_ENA                    0x0001  /* ALC_NGATE_ENA */
+#define WM8962_ALC_NGATE_ENA_MASK               0x0001  /* ALC_NGATE_ENA */
+#define WM8962_ALC_NGATE_ENA_SHIFT                   0  /* ALC_NGATE_ENA */
+#define WM8962_ALC_NGATE_ENA_WIDTH                   1  /* ALC_NGATE_ENA */
+
+/*
+ * R21 (0x15) - Left ADC volume
+ */
+#define WM8962_ADC_VU                           0x0100  /* ADC_VU */
+#define WM8962_ADC_VU_MASK                      0x0100  /* ADC_VU */
+#define WM8962_ADC_VU_SHIFT                          8  /* ADC_VU */
+#define WM8962_ADC_VU_WIDTH                          1  /* ADC_VU */
+#define WM8962_ADCL_VOL_MASK                    0x00FF  /* ADCL_VOL - [7:0] */
+#define WM8962_ADCL_VOL_SHIFT                        0  /* ADCL_VOL - [7:0] */
+#define WM8962_ADCL_VOL_WIDTH                        8  /* ADCL_VOL - [7:0] */
+
+/*
+ * R22 (0x16) - Right ADC volume
+ */
+#define WM8962_ADC_VU                           0x0100  /* ADC_VU */
+#define WM8962_ADC_VU_MASK                      0x0100  /* ADC_VU */
+#define WM8962_ADC_VU_SHIFT                          8  /* ADC_VU */
+#define WM8962_ADC_VU_WIDTH                          1  /* ADC_VU */
+#define WM8962_ADCR_VOL_MASK                    0x00FF  /* ADCR_VOL - [7:0] */
+#define WM8962_ADCR_VOL_SHIFT                        0  /* ADCR_VOL - [7:0] */
+#define WM8962_ADCR_VOL_WIDTH                        8  /* ADCR_VOL - [7:0] */
+
+/*
+ * R23 (0x17) - Additional control(1)
+ */
+#define WM8962_THERR_ACT                        0x0100  /* THERR_ACT */
+#define WM8962_THERR_ACT_MASK                   0x0100  /* THERR_ACT */
+#define WM8962_THERR_ACT_SHIFT                       8  /* THERR_ACT */
+#define WM8962_THERR_ACT_WIDTH                       1  /* THERR_ACT */
+#define WM8962_ADC_BIAS                         0x0040  /* ADC_BIAS */
+#define WM8962_ADC_BIAS_MASK                    0x0040  /* ADC_BIAS */
+#define WM8962_ADC_BIAS_SHIFT                        6  /* ADC_BIAS */
+#define WM8962_ADC_BIAS_WIDTH                        1  /* ADC_BIAS */
+#define WM8962_ADC_HP                           0x0020  /* ADC_HP */
+#define WM8962_ADC_HP_MASK                      0x0020  /* ADC_HP */
+#define WM8962_ADC_HP_SHIFT                          5  /* ADC_HP */
+#define WM8962_ADC_HP_WIDTH                          1  /* ADC_HP */
+#define WM8962_TOCLK_ENA                        0x0001  /* TOCLK_ENA */
+#define WM8962_TOCLK_ENA_MASK                   0x0001  /* TOCLK_ENA */
+#define WM8962_TOCLK_ENA_SHIFT                       0  /* TOCLK_ENA */
+#define WM8962_TOCLK_ENA_WIDTH                       1  /* TOCLK_ENA */
+
+/*
+ * R24 (0x18) - Additional control(2)
+ */
+#define WM8962_AIF_TRI                          0x0008  /* AIF_TRI */
+#define WM8962_AIF_TRI_MASK                     0x0008  /* AIF_TRI */
+#define WM8962_AIF_TRI_SHIFT                         3  /* AIF_TRI */
+#define WM8962_AIF_TRI_WIDTH                         1  /* AIF_TRI */
+
+/*
+ * R25 (0x19) - Pwr Mgmt (1)
+ */
+#define WM8962_DMIC_ENA                         0x0400  /* DMIC_ENA */
+#define WM8962_DMIC_ENA_MASK                    0x0400  /* DMIC_ENA */
+#define WM8962_DMIC_ENA_SHIFT                       10  /* DMIC_ENA */
+#define WM8962_DMIC_ENA_WIDTH                        1  /* DMIC_ENA */
+#define WM8962_OPCLK_ENA                        0x0200  /* OPCLK_ENA */
+#define WM8962_OPCLK_ENA_MASK                   0x0200  /* OPCLK_ENA */
+#define WM8962_OPCLK_ENA_SHIFT                       9  /* OPCLK_ENA */
+#define WM8962_OPCLK_ENA_WIDTH                       1  /* OPCLK_ENA */
+#define WM8962_VMID_SEL_MASK                    0x0180  /* VMID_SEL - [8:7] */
+#define WM8962_VMID_SEL_SHIFT                        7  /* VMID_SEL - [8:7] */
+#define WM8962_VMID_SEL_WIDTH                        2  /* VMID_SEL - [8:7] */
+#define WM8962_BIAS_ENA                         0x0040  /* BIAS_ENA */
+#define WM8962_BIAS_ENA_MASK                    0x0040  /* BIAS_ENA */
+#define WM8962_BIAS_ENA_SHIFT                        6  /* BIAS_ENA */
+#define WM8962_BIAS_ENA_WIDTH                        1  /* BIAS_ENA */
+#define WM8962_INL_ENA                          0x0020  /* INL_ENA */
+#define WM8962_INL_ENA_MASK                     0x0020  /* INL_ENA */
+#define WM8962_INL_ENA_SHIFT                         5  /* INL_ENA */
+#define WM8962_INL_ENA_WIDTH                         1  /* INL_ENA */
+#define WM8962_INR_ENA                          0x0010  /* INR_ENA */
+#define WM8962_INR_ENA_MASK                     0x0010  /* INR_ENA */
+#define WM8962_INR_ENA_SHIFT                         4  /* INR_ENA */
+#define WM8962_INR_ENA_WIDTH                         1  /* INR_ENA */
+#define WM8962_ADCL_ENA                         0x0008  /* ADCL_ENA */
+#define WM8962_ADCL_ENA_MASK                    0x0008  /* ADCL_ENA */
+#define WM8962_ADCL_ENA_SHIFT                        3  /* ADCL_ENA */
+#define WM8962_ADCL_ENA_WIDTH                        1  /* ADCL_ENA */
+#define WM8962_ADCR_ENA                         0x0004  /* ADCR_ENA */
+#define WM8962_ADCR_ENA_MASK                    0x0004  /* ADCR_ENA */
+#define WM8962_ADCR_ENA_SHIFT                        2  /* ADCR_ENA */
+#define WM8962_ADCR_ENA_WIDTH                        1  /* ADCR_ENA */
+#define WM8962_MICBIAS_ENA                      0x0002  /* MICBIAS_ENA */
+#define WM8962_MICBIAS_ENA_MASK                 0x0002  /* MICBIAS_ENA */
+#define WM8962_MICBIAS_ENA_SHIFT                     1  /* MICBIAS_ENA */
+#define WM8962_MICBIAS_ENA_WIDTH                     1  /* MICBIAS_ENA */
+
+/*
+ * R26 (0x1A) - Pwr Mgmt (2)
+ */
+#define WM8962_DACL_ENA                         0x0100  /* DACL_ENA */
+#define WM8962_DACL_ENA_MASK                    0x0100  /* DACL_ENA */
+#define WM8962_DACL_ENA_SHIFT                        8  /* DACL_ENA */
+#define WM8962_DACL_ENA_WIDTH                        1  /* DACL_ENA */
+#define WM8962_DACR_ENA                         0x0080  /* DACR_ENA */
+#define WM8962_DACR_ENA_MASK                    0x0080  /* DACR_ENA */
+#define WM8962_DACR_ENA_SHIFT                        7  /* DACR_ENA */
+#define WM8962_DACR_ENA_WIDTH                        1  /* DACR_ENA */
+#define WM8962_HPOUTL_PGA_ENA                   0x0040  /* HPOUTL_PGA_ENA */
+#define WM8962_HPOUTL_PGA_ENA_MASK              0x0040  /* HPOUTL_PGA_ENA */
+#define WM8962_HPOUTL_PGA_ENA_SHIFT                  6  /* HPOUTL_PGA_ENA */
+#define WM8962_HPOUTL_PGA_ENA_WIDTH                  1  /* HPOUTL_PGA_ENA */
+#define WM8962_HPOUTR_PGA_ENA                   0x0020  /* HPOUTR_PGA_ENA */
+#define WM8962_HPOUTR_PGA_ENA_MASK              0x0020  /* HPOUTR_PGA_ENA */
+#define WM8962_HPOUTR_PGA_ENA_SHIFT                  5  /* HPOUTR_PGA_ENA */
+#define WM8962_HPOUTR_PGA_ENA_WIDTH                  1  /* HPOUTR_PGA_ENA */
+#define WM8962_SPKOUTL_PGA_ENA                  0x0010  /* SPKOUTL_PGA_ENA */
+#define WM8962_SPKOUTL_PGA_ENA_MASK             0x0010  /* SPKOUTL_PGA_ENA */
+#define WM8962_SPKOUTL_PGA_ENA_SHIFT                 4  /* SPKOUTL_PGA_ENA */
+#define WM8962_SPKOUTL_PGA_ENA_WIDTH                 1  /* SPKOUTL_PGA_ENA */
+#define WM8962_SPKOUTR_PGA_ENA                  0x0008  /* SPKOUTR_PGA_ENA */
+#define WM8962_SPKOUTR_PGA_ENA_MASK             0x0008  /* SPKOUTR_PGA_ENA */
+#define WM8962_SPKOUTR_PGA_ENA_SHIFT                 3  /* SPKOUTR_PGA_ENA */
+#define WM8962_SPKOUTR_PGA_ENA_WIDTH                 1  /* SPKOUTR_PGA_ENA */
+#define WM8962_HPOUTL_PGA_MUTE                  0x0002  /* HPOUTL_PGA_MUTE */
+#define WM8962_HPOUTL_PGA_MUTE_MASK             0x0002  /* HPOUTL_PGA_MUTE */
+#define WM8962_HPOUTL_PGA_MUTE_SHIFT                 1  /* HPOUTL_PGA_MUTE */
+#define WM8962_HPOUTL_PGA_MUTE_WIDTH                 1  /* HPOUTL_PGA_MUTE */
+#define WM8962_HPOUTR_PGA_MUTE                  0x0001  /* HPOUTR_PGA_MUTE */
+#define WM8962_HPOUTR_PGA_MUTE_MASK             0x0001  /* HPOUTR_PGA_MUTE */
+#define WM8962_HPOUTR_PGA_MUTE_SHIFT                 0  /* HPOUTR_PGA_MUTE */
+#define WM8962_HPOUTR_PGA_MUTE_WIDTH                 1  /* HPOUTR_PGA_MUTE */
+
+/*
+ * R27 (0x1B) - Additional Control (3)
+ */
+#define WM8962_SAMPLE_RATE_INT_MODE             0x0010  /* SAMPLE_RATE_INT_MODE */
+#define WM8962_SAMPLE_RATE_INT_MODE_MASK        0x0010  /* SAMPLE_RATE_INT_MODE */
+#define WM8962_SAMPLE_RATE_INT_MODE_SHIFT            4  /* SAMPLE_RATE_INT_MODE */
+#define WM8962_SAMPLE_RATE_INT_MODE_WIDTH            1  /* SAMPLE_RATE_INT_MODE */
+#define WM8962_SAMPLE_RATE_MASK                 0x0007  /* SAMPLE_RATE - [2:0] */
+#define WM8962_SAMPLE_RATE_SHIFT                     0  /* SAMPLE_RATE - [2:0] */
+#define WM8962_SAMPLE_RATE_WIDTH                     3  /* SAMPLE_RATE - [2:0] */
+
+/*
+ * R28 (0x1C) - Anti-pop
+ */
+#define WM8962_STARTUP_BIAS_ENA                 0x0010  /* STARTUP_BIAS_ENA */
+#define WM8962_STARTUP_BIAS_ENA_MASK            0x0010  /* STARTUP_BIAS_ENA */
+#define WM8962_STARTUP_BIAS_ENA_SHIFT                4  /* STARTUP_BIAS_ENA */
+#define WM8962_STARTUP_BIAS_ENA_WIDTH                1  /* STARTUP_BIAS_ENA */
+#define WM8962_VMID_BUF_ENA                     0x0008  /* VMID_BUF_ENA */
+#define WM8962_VMID_BUF_ENA_MASK                0x0008  /* VMID_BUF_ENA */
+#define WM8962_VMID_BUF_ENA_SHIFT                    3  /* VMID_BUF_ENA */
+#define WM8962_VMID_BUF_ENA_WIDTH                    1  /* VMID_BUF_ENA */
+#define WM8962_VMID_RAMP                        0x0004  /* VMID_RAMP */
+#define WM8962_VMID_RAMP_MASK                   0x0004  /* VMID_RAMP */
+#define WM8962_VMID_RAMP_SHIFT                       2  /* VMID_RAMP */
+#define WM8962_VMID_RAMP_WIDTH                       1  /* VMID_RAMP */
+
+/*
+ * R30 (0x1E) - Clocking 3
+ */
+#define WM8962_DBCLK_DIV_MASK                   0xE000  /* DBCLK_DIV - [15:13] */
+#define WM8962_DBCLK_DIV_SHIFT                      13  /* DBCLK_DIV - [15:13] */
+#define WM8962_DBCLK_DIV_WIDTH                       3  /* DBCLK_DIV - [15:13] */
+#define WM8962_OPCLK_DIV_MASK                   0x1C00  /* OPCLK_DIV - [12:10] */
+#define WM8962_OPCLK_DIV_SHIFT                      10  /* OPCLK_DIV - [12:10] */
+#define WM8962_OPCLK_DIV_WIDTH                       3  /* OPCLK_DIV - [12:10] */
+#define WM8962_TOCLK_DIV_MASK                   0x0380  /* TOCLK_DIV - [9:7] */
+#define WM8962_TOCLK_DIV_SHIFT                       7  /* TOCLK_DIV - [9:7] */
+#define WM8962_TOCLK_DIV_WIDTH                       3  /* TOCLK_DIV - [9:7] */
+#define WM8962_F256KCLK_DIV_MASK                0x007E  /* F256KCLK_DIV - [6:1] */
+#define WM8962_F256KCLK_DIV_SHIFT                    1  /* F256KCLK_DIV - [6:1] */
+#define WM8962_F256KCLK_DIV_WIDTH                    6  /* F256KCLK_DIV - [6:1] */
+
+/*
+ * R31 (0x1F) - Input mixer control (1)
+ */
+#define WM8962_MIXINL_MUTE                      0x0008  /* MIXINL_MUTE */
+#define WM8962_MIXINL_MUTE_MASK                 0x0008  /* MIXINL_MUTE */
+#define WM8962_MIXINL_MUTE_SHIFT                     3  /* MIXINL_MUTE */
+#define WM8962_MIXINL_MUTE_WIDTH                     1  /* MIXINL_MUTE */
+#define WM8962_MIXINR_MUTE                      0x0004  /* MIXINR_MUTE */
+#define WM8962_MIXINR_MUTE_MASK                 0x0004  /* MIXINR_MUTE */
+#define WM8962_MIXINR_MUTE_SHIFT                     2  /* MIXINR_MUTE */
+#define WM8962_MIXINR_MUTE_WIDTH                     1  /* MIXINR_MUTE */
+#define WM8962_MIXINL_ENA                       0x0002  /* MIXINL_ENA */
+#define WM8962_MIXINL_ENA_MASK                  0x0002  /* MIXINL_ENA */
+#define WM8962_MIXINL_ENA_SHIFT                      1  /* MIXINL_ENA */
+#define WM8962_MIXINL_ENA_WIDTH                      1  /* MIXINL_ENA */
+#define WM8962_MIXINR_ENA                       0x0001  /* MIXINR_ENA */
+#define WM8962_MIXINR_ENA_MASK                  0x0001  /* MIXINR_ENA */
+#define WM8962_MIXINR_ENA_SHIFT                      0  /* MIXINR_ENA */
+#define WM8962_MIXINR_ENA_WIDTH                      1  /* MIXINR_ENA */
+
+/*
+ * R32 (0x20) - Left input mixer volume
+ */
+#define WM8962_IN2L_MIXINL_VOL_MASK             0x01C0  /* IN2L_MIXINL_VOL - [8:6] */
+#define WM8962_IN2L_MIXINL_VOL_SHIFT                 6  /* IN2L_MIXINL_VOL - [8:6] */
+#define WM8962_IN2L_MIXINL_VOL_WIDTH                 3  /* IN2L_MIXINL_VOL - [8:6] */
+#define WM8962_INPGAL_MIXINL_VOL_MASK           0x0038  /* INPGAL_MIXINL_VOL - [5:3] */
+#define WM8962_INPGAL_MIXINL_VOL_SHIFT               3  /* INPGAL_MIXINL_VOL - [5:3] */
+#define WM8962_INPGAL_MIXINL_VOL_WIDTH               3  /* INPGAL_MIXINL_VOL - [5:3] */
+#define WM8962_IN3L_MIXINL_VOL_MASK             0x0007  /* IN3L_MIXINL_VOL - [2:0] */
+#define WM8962_IN3L_MIXINL_VOL_SHIFT                 0  /* IN3L_MIXINL_VOL - [2:0] */
+#define WM8962_IN3L_MIXINL_VOL_WIDTH                 3  /* IN3L_MIXINL_VOL - [2:0] */
+
+/*
+ * R33 (0x21) - Right input mixer volume
+ */
+#define WM8962_IN2R_MIXINR_VOL_MASK             0x01C0  /* IN2R_MIXINR_VOL - [8:6] */
+#define WM8962_IN2R_MIXINR_VOL_SHIFT                 6  /* IN2R_MIXINR_VOL - [8:6] */
+#define WM8962_IN2R_MIXINR_VOL_WIDTH                 3  /* IN2R_MIXINR_VOL - [8:6] */
+#define WM8962_INPGAR_MIXINR_VOL_MASK           0x0038  /* INPGAR_MIXINR_VOL - [5:3] */
+#define WM8962_INPGAR_MIXINR_VOL_SHIFT               3  /* INPGAR_MIXINR_VOL - [5:3] */
+#define WM8962_INPGAR_MIXINR_VOL_WIDTH               3  /* INPGAR_MIXINR_VOL - [5:3] */
+#define WM8962_IN3R_MIXINR_VOL_MASK             0x0007  /* IN3R_MIXINR_VOL - [2:0] */
+#define WM8962_IN3R_MIXINR_VOL_SHIFT                 0  /* IN3R_MIXINR_VOL - [2:0] */
+#define WM8962_IN3R_MIXINR_VOL_WIDTH                 3  /* IN3R_MIXINR_VOL - [2:0] */
+
+/*
+ * R34 (0x22) - Input mixer control (2)
+ */
+#define WM8962_IN2L_TO_MIXINL                   0x0020  /* IN2L_TO_MIXINL */
+#define WM8962_IN2L_TO_MIXINL_MASK              0x0020  /* IN2L_TO_MIXINL */
+#define WM8962_IN2L_TO_MIXINL_SHIFT                  5  /* IN2L_TO_MIXINL */
+#define WM8962_IN2L_TO_MIXINL_WIDTH                  1  /* IN2L_TO_MIXINL */
+#define WM8962_IN3L_TO_MIXINL                   0x0010  /* IN3L_TO_MIXINL */
+#define WM8962_IN3L_TO_MIXINL_MASK              0x0010  /* IN3L_TO_MIXINL */
+#define WM8962_IN3L_TO_MIXINL_SHIFT                  4  /* IN3L_TO_MIXINL */
+#define WM8962_IN3L_TO_MIXINL_WIDTH                  1  /* IN3L_TO_MIXINL */
+#define WM8962_INPGAL_TO_MIXINL                 0x0008  /* INPGAL_TO_MIXINL */
+#define WM8962_INPGAL_TO_MIXINL_MASK            0x0008  /* INPGAL_TO_MIXINL */
+#define WM8962_INPGAL_TO_MIXINL_SHIFT                3  /* INPGAL_TO_MIXINL */
+#define WM8962_INPGAL_TO_MIXINL_WIDTH                1  /* INPGAL_TO_MIXINL */
+#define WM8962_IN2R_TO_MIXINR                   0x0004  /* IN2R_TO_MIXINR */
+#define WM8962_IN2R_TO_MIXINR_MASK              0x0004  /* IN2R_TO_MIXINR */
+#define WM8962_IN2R_TO_MIXINR_SHIFT                  2  /* IN2R_TO_MIXINR */
+#define WM8962_IN2R_TO_MIXINR_WIDTH                  1  /* IN2R_TO_MIXINR */
+#define WM8962_IN3R_TO_MIXINR                   0x0002  /* IN3R_TO_MIXINR */
+#define WM8962_IN3R_TO_MIXINR_MASK              0x0002  /* IN3R_TO_MIXINR */
+#define WM8962_IN3R_TO_MIXINR_SHIFT                  1  /* IN3R_TO_MIXINR */
+#define WM8962_IN3R_TO_MIXINR_WIDTH                  1  /* IN3R_TO_MIXINR */
+#define WM8962_INPGAR_TO_MIXINR                 0x0001  /* INPGAR_TO_MIXINR */
+#define WM8962_INPGAR_TO_MIXINR_MASK            0x0001  /* INPGAR_TO_MIXINR */
+#define WM8962_INPGAR_TO_MIXINR_SHIFT                0  /* INPGAR_TO_MIXINR */
+#define WM8962_INPGAR_TO_MIXINR_WIDTH                1  /* INPGAR_TO_MIXINR */
+
+/*
+ * R35 (0x23) - Input bias control
+ */
+#define WM8962_MIXIN_BIAS_MASK                  0x0038  /* MIXIN_BIAS - [5:3] */
+#define WM8962_MIXIN_BIAS_SHIFT                      3  /* MIXIN_BIAS - [5:3] */
+#define WM8962_MIXIN_BIAS_WIDTH                      3  /* MIXIN_BIAS - [5:3] */
+#define WM8962_INPGA_BIAS_MASK                  0x0007  /* INPGA_BIAS - [2:0] */
+#define WM8962_INPGA_BIAS_SHIFT                      0  /* INPGA_BIAS - [2:0] */
+#define WM8962_INPGA_BIAS_WIDTH                      3  /* INPGA_BIAS - [2:0] */
+
+/*
+ * R37 (0x25) - Left input PGA control
+ */
+#define WM8962_INPGAL_ENA                       0x0010  /* INPGAL_ENA */
+#define WM8962_INPGAL_ENA_MASK                  0x0010  /* INPGAL_ENA */
+#define WM8962_INPGAL_ENA_SHIFT                      4  /* INPGAL_ENA */
+#define WM8962_INPGAL_ENA_WIDTH                      1  /* INPGAL_ENA */
+#define WM8962_IN1L_TO_INPGAL                   0x0008  /* IN1L_TO_INPGAL */
+#define WM8962_IN1L_TO_INPGAL_MASK              0x0008  /* IN1L_TO_INPGAL */
+#define WM8962_IN1L_TO_INPGAL_SHIFT                  3  /* IN1L_TO_INPGAL */
+#define WM8962_IN1L_TO_INPGAL_WIDTH                  1  /* IN1L_TO_INPGAL */
+#define WM8962_IN2L_TO_INPGAL                   0x0004  /* IN2L_TO_INPGAL */
+#define WM8962_IN2L_TO_INPGAL_MASK              0x0004  /* IN2L_TO_INPGAL */
+#define WM8962_IN2L_TO_INPGAL_SHIFT                  2  /* IN2L_TO_INPGAL */
+#define WM8962_IN2L_TO_INPGAL_WIDTH                  1  /* IN2L_TO_INPGAL */
+#define WM8962_IN3L_TO_INPGAL                   0x0002  /* IN3L_TO_INPGAL */
+#define WM8962_IN3L_TO_INPGAL_MASK              0x0002  /* IN3L_TO_INPGAL */
+#define WM8962_IN3L_TO_INPGAL_SHIFT                  1  /* IN3L_TO_INPGAL */
+#define WM8962_IN3L_TO_INPGAL_WIDTH                  1  /* IN3L_TO_INPGAL */
+#define WM8962_IN4L_TO_INPGAL                   0x0001  /* IN4L_TO_INPGAL */
+#define WM8962_IN4L_TO_INPGAL_MASK              0x0001  /* IN4L_TO_INPGAL */
+#define WM8962_IN4L_TO_INPGAL_SHIFT                  0  /* IN4L_TO_INPGAL */
+#define WM8962_IN4L_TO_INPGAL_WIDTH                  1  /* IN4L_TO_INPGAL */
+
+/*
+ * R38 (0x26) - Right input PGA control
+ */
+#define WM8962_INPGAR_ENA                       0x0010  /* INPGAR_ENA */
+#define WM8962_INPGAR_ENA_MASK                  0x0010  /* INPGAR_ENA */
+#define WM8962_INPGAR_ENA_SHIFT                      4  /* INPGAR_ENA */
+#define WM8962_INPGAR_ENA_WIDTH                      1  /* INPGAR_ENA */
+#define WM8962_IN1R_TO_INPGAR                   0x0008  /* IN1R_TO_INPGAR */
+#define WM8962_IN1R_TO_INPGAR_MASK              0x0008  /* IN1R_TO_INPGAR */
+#define WM8962_IN1R_TO_INPGAR_SHIFT                  3  /* IN1R_TO_INPGAR */
+#define WM8962_IN1R_TO_INPGAR_WIDTH                  1  /* IN1R_TO_INPGAR */
+#define WM8962_IN2R_TO_INPGAR                   0x0004  /* IN2R_TO_INPGAR */
+#define WM8962_IN2R_TO_INPGAR_MASK              0x0004  /* IN2R_TO_INPGAR */
+#define WM8962_IN2R_TO_INPGAR_SHIFT                  2  /* IN2R_TO_INPGAR */
+#define WM8962_IN2R_TO_INPGAR_WIDTH                  1  /* IN2R_TO_INPGAR */
+#define WM8962_IN3R_TO_INPGAR                   0x0002  /* IN3R_TO_INPGAR */
+#define WM8962_IN3R_TO_INPGAR_MASK              0x0002  /* IN3R_TO_INPGAR */
+#define WM8962_IN3R_TO_INPGAR_SHIFT                  1  /* IN3R_TO_INPGAR */
+#define WM8962_IN3R_TO_INPGAR_WIDTH                  1  /* IN3R_TO_INPGAR */
+#define WM8962_IN4R_TO_INPGAR                   0x0001  /* IN4R_TO_INPGAR */
+#define WM8962_IN4R_TO_INPGAR_MASK              0x0001  /* IN4R_TO_INPGAR */
+#define WM8962_IN4R_TO_INPGAR_SHIFT                  0  /* IN4R_TO_INPGAR */
+#define WM8962_IN4R_TO_INPGAR_WIDTH                  1  /* IN4R_TO_INPGAR */
+
+/*
+ * R40 (0x28) - SPKOUTL volume
+ */
+#define WM8962_SPKOUT_VU                        0x0100  /* SPKOUT_VU */
+#define WM8962_SPKOUT_VU_MASK                   0x0100  /* SPKOUT_VU */
+#define WM8962_SPKOUT_VU_SHIFT                       8  /* SPKOUT_VU */
+#define WM8962_SPKOUT_VU_WIDTH                       1  /* SPKOUT_VU */
+#define WM8962_SPKOUTL_ZC                       0x0080  /* SPKOUTL_ZC */
+#define WM8962_SPKOUTL_ZC_MASK                  0x0080  /* SPKOUTL_ZC */
+#define WM8962_SPKOUTL_ZC_SHIFT                      7  /* SPKOUTL_ZC */
+#define WM8962_SPKOUTL_ZC_WIDTH                      1  /* SPKOUTL_ZC */
+#define WM8962_SPKOUTL_VOL_MASK                 0x007F  /* SPKOUTL_VOL - [6:0] */
+#define WM8962_SPKOUTL_VOL_SHIFT                     0  /* SPKOUTL_VOL - [6:0] */
+#define WM8962_SPKOUTL_VOL_WIDTH                     7  /* SPKOUTL_VOL - [6:0] */
+
+/*
+ * R41 (0x29) - SPKOUTR volume
+ */
+#define WM8962_SPKOUTR_ZC                       0x0080  /* SPKOUTR_ZC */
+#define WM8962_SPKOUTR_ZC_MASK                  0x0080  /* SPKOUTR_ZC */
+#define WM8962_SPKOUTR_ZC_SHIFT                      7  /* SPKOUTR_ZC */
+#define WM8962_SPKOUTR_ZC_WIDTH                      1  /* SPKOUTR_ZC */
+#define WM8962_SPKOUTR_VOL_MASK                 0x007F  /* SPKOUTR_VOL - [6:0] */
+#define WM8962_SPKOUTR_VOL_SHIFT                     0  /* SPKOUTR_VOL - [6:0] */
+#define WM8962_SPKOUTR_VOL_WIDTH                     7  /* SPKOUTR_VOL - [6:0] */
+
+/*
+ * R47 (0x2F) - Thermal Shutdown Status
+ */
+#define WM8962_TEMP_ERR_HP                      0x0008  /* TEMP_ERR_HP */
+#define WM8962_TEMP_ERR_HP_MASK                 0x0008  /* TEMP_ERR_HP */
+#define WM8962_TEMP_ERR_HP_SHIFT                     3  /* TEMP_ERR_HP */
+#define WM8962_TEMP_ERR_HP_WIDTH                     1  /* TEMP_ERR_HP */
+#define WM8962_TEMP_WARN_HP                     0x0004  /* TEMP_WARN_HP */
+#define WM8962_TEMP_WARN_HP_MASK                0x0004  /* TEMP_WARN_HP */
+#define WM8962_TEMP_WARN_HP_SHIFT                    2  /* TEMP_WARN_HP */
+#define WM8962_TEMP_WARN_HP_WIDTH                    1  /* TEMP_WARN_HP */
+#define WM8962_TEMP_ERR_SPK                     0x0002  /* TEMP_ERR_SPK */
+#define WM8962_TEMP_ERR_SPK_MASK                0x0002  /* TEMP_ERR_SPK */
+#define WM8962_TEMP_ERR_SPK_SHIFT                    1  /* TEMP_ERR_SPK */
+#define WM8962_TEMP_ERR_SPK_WIDTH                    1  /* TEMP_ERR_SPK */
+#define WM8962_TEMP_WARN_SPK                    0x0001  /* TEMP_WARN_SPK */
+#define WM8962_TEMP_WARN_SPK_MASK               0x0001  /* TEMP_WARN_SPK */
+#define WM8962_TEMP_WARN_SPK_SHIFT                   0  /* TEMP_WARN_SPK */
+#define WM8962_TEMP_WARN_SPK_WIDTH                   1  /* TEMP_WARN_SPK */
+
+/*
+ * R48 (0x30) - Additional Control (4)
+ */
+#define WM8962_MICDET_THR_MASK                  0x7000  /* MICDET_THR - [14:12] */
+#define WM8962_MICDET_THR_SHIFT                     12  /* MICDET_THR - [14:12] */
+#define WM8962_MICDET_THR_WIDTH                      3  /* MICDET_THR - [14:12] */
+#define WM8962_MICSHORT_THR_MASK                0x0C00  /* MICSHORT_THR - [11:10] */
+#define WM8962_MICSHORT_THR_SHIFT                   10  /* MICSHORT_THR - [11:10] */
+#define WM8962_MICSHORT_THR_WIDTH                    2  /* MICSHORT_THR - [11:10] */
+#define WM8962_MICDET_ENA                       0x0200  /* MICDET_ENA */
+#define WM8962_MICDET_ENA_MASK                  0x0200  /* MICDET_ENA */
+#define WM8962_MICDET_ENA_SHIFT                      9  /* MICDET_ENA */
+#define WM8962_MICDET_ENA_WIDTH                      1  /* MICDET_ENA */
+#define WM8962_MICDET_STS                       0x0080  /* MICDET_STS */
+#define WM8962_MICDET_STS_MASK                  0x0080  /* MICDET_STS */
+#define WM8962_MICDET_STS_SHIFT                      7  /* MICDET_STS */
+#define WM8962_MICDET_STS_WIDTH                      1  /* MICDET_STS */
+#define WM8962_MICSHORT_STS                     0x0040  /* MICSHORT_STS */
+#define WM8962_MICSHORT_STS_MASK                0x0040  /* MICSHORT_STS */
+#define WM8962_MICSHORT_STS_SHIFT                    6  /* MICSHORT_STS */
+#define WM8962_MICSHORT_STS_WIDTH                    1  /* MICSHORT_STS */
+#define WM8962_TEMP_ENA_HP                      0x0004  /* TEMP_ENA_HP */
+#define WM8962_TEMP_ENA_HP_MASK                 0x0004  /* TEMP_ENA_HP */
+#define WM8962_TEMP_ENA_HP_SHIFT                     2  /* TEMP_ENA_HP */
+#define WM8962_TEMP_ENA_HP_WIDTH                     1  /* TEMP_ENA_HP */
+#define WM8962_TEMP_ENA_SPK                     0x0002  /* TEMP_ENA_SPK */
+#define WM8962_TEMP_ENA_SPK_MASK                0x0002  /* TEMP_ENA_SPK */
+#define WM8962_TEMP_ENA_SPK_SHIFT                    1  /* TEMP_ENA_SPK */
+#define WM8962_TEMP_ENA_SPK_WIDTH                    1  /* TEMP_ENA_SPK */
+#define WM8962_MICBIAS_LVL                      0x0001  /* MICBIAS_LVL */
+#define WM8962_MICBIAS_LVL_MASK                 0x0001  /* MICBIAS_LVL */
+#define WM8962_MICBIAS_LVL_SHIFT                     0  /* MICBIAS_LVL */
+#define WM8962_MICBIAS_LVL_WIDTH                     1  /* MICBIAS_LVL */
+
+/*
+ * R49 (0x31) - Class D Control 1
+ */
+#define WM8962_SPKOUTR_ENA                      0x0080  /* SPKOUTR_ENA */
+#define WM8962_SPKOUTR_ENA_MASK                 0x0080  /* SPKOUTR_ENA */
+#define WM8962_SPKOUTR_ENA_SHIFT                     7  /* SPKOUTR_ENA */
+#define WM8962_SPKOUTR_ENA_WIDTH                     1  /* SPKOUTR_ENA */
+#define WM8962_SPKOUTL_ENA                      0x0040  /* SPKOUTL_ENA */
+#define WM8962_SPKOUTL_ENA_MASK                 0x0040  /* SPKOUTL_ENA */
+#define WM8962_SPKOUTL_ENA_SHIFT                     6  /* SPKOUTL_ENA */
+#define WM8962_SPKOUTL_ENA_WIDTH                     1  /* SPKOUTL_ENA */
+#define WM8962_SPKOUTL_PGA_MUTE                 0x0002  /* SPKOUTL_PGA_MUTE */
+#define WM8962_SPKOUTL_PGA_MUTE_MASK            0x0002  /* SPKOUTL_PGA_MUTE */
+#define WM8962_SPKOUTL_PGA_MUTE_SHIFT                1  /* SPKOUTL_PGA_MUTE */
+#define WM8962_SPKOUTL_PGA_MUTE_WIDTH                1  /* SPKOUTL_PGA_MUTE */
+#define WM8962_SPKOUTR_PGA_MUTE                 0x0001  /* SPKOUTR_PGA_MUTE */
+#define WM8962_SPKOUTR_PGA_MUTE_MASK            0x0001  /* SPKOUTR_PGA_MUTE */
+#define WM8962_SPKOUTR_PGA_MUTE_SHIFT                0  /* SPKOUTR_PGA_MUTE */
+#define WM8962_SPKOUTR_PGA_MUTE_WIDTH                1  /* SPKOUTR_PGA_MUTE */
+
+/*
+ * R51 (0x33) - Class D Control 2
+ */
+#define WM8962_SPK_MONO                         0x0040  /* SPK_MONO */
+#define WM8962_SPK_MONO_MASK                    0x0040  /* SPK_MONO */
+#define WM8962_SPK_MONO_SHIFT                        6  /* SPK_MONO */
+#define WM8962_SPK_MONO_WIDTH                        1  /* SPK_MONO */
+#define WM8962_CLASSD_VOL_MASK                  0x0007  /* CLASSD_VOL - [2:0] */
+#define WM8962_CLASSD_VOL_SHIFT                      0  /* CLASSD_VOL - [2:0] */
+#define WM8962_CLASSD_VOL_WIDTH                      3  /* CLASSD_VOL - [2:0] */
+
+/*
+ * R56 (0x38) - Clocking 4
+ */
+#define WM8962_SYSCLK_RATE_MASK                 0x001E  /* SYSCLK_RATE - [4:1] */
+#define WM8962_SYSCLK_RATE_SHIFT                     1  /* SYSCLK_RATE - [4:1] */
+#define WM8962_SYSCLK_RATE_WIDTH                     4  /* SYSCLK_RATE - [4:1] */
+
+/*
+ * R57 (0x39) - DAC DSP Mixing (1)
+ */
+#define WM8962_DAC_MONOMIX                      0x0200  /* DAC_MONOMIX */
+#define WM8962_DAC_MONOMIX_MASK                 0x0200  /* DAC_MONOMIX */
+#define WM8962_DAC_MONOMIX_SHIFT                     9  /* DAC_MONOMIX */
+#define WM8962_DAC_MONOMIX_WIDTH                     1  /* DAC_MONOMIX */
+#define WM8962_ADCR_DAC_SVOL_MASK               0x00F0  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8962_ADCR_DAC_SVOL_SHIFT                   4  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8962_ADCR_DAC_SVOL_WIDTH                   4  /* ADCR_DAC_SVOL - [7:4] */
+#define WM8962_ADC_TO_DACR_MASK                 0x000C  /* ADC_TO_DACR - [3:2] */
+#define WM8962_ADC_TO_DACR_SHIFT                     2  /* ADC_TO_DACR - [3:2] */
+#define WM8962_ADC_TO_DACR_WIDTH                     2  /* ADC_TO_DACR - [3:2] */
+
+/*
+ * R58 (0x3A) - DAC DSP Mixing (2)
+ */
+#define WM8962_ADCL_DAC_SVOL_MASK               0x00F0  /* ADCL_DAC_SVOL - [7:4] */
+#define WM8962_ADCL_DAC_SVOL_SHIFT                   4  /* ADCL_DAC_SVOL - [7:4] */
+#define WM8962_ADCL_DAC_SVOL_WIDTH                   4  /* ADCL_DAC_SVOL - [7:4] */
+#define WM8962_ADC_TO_DACL_MASK                 0x000C  /* ADC_TO_DACL - [3:2] */
+#define WM8962_ADC_TO_DACL_SHIFT                     2  /* ADC_TO_DACL - [3:2] */
+#define WM8962_ADC_TO_DACL_WIDTH                     2  /* ADC_TO_DACL - [3:2] */
+
+/*
+ * R60 (0x3C) - DC Servo 0
+ */
+#define WM8962_INL_DCS_ENA                      0x0080  /* INL_DCS_ENA */
+#define WM8962_INL_DCS_ENA_MASK                 0x0080  /* INL_DCS_ENA */
+#define WM8962_INL_DCS_ENA_SHIFT                     7  /* INL_DCS_ENA */
+#define WM8962_INL_DCS_ENA_WIDTH                     1  /* INL_DCS_ENA */
+#define WM8962_INL_DCS_STARTUP                  0x0040  /* INL_DCS_STARTUP */
+#define WM8962_INL_DCS_STARTUP_MASK             0x0040  /* INL_DCS_STARTUP */
+#define WM8962_INL_DCS_STARTUP_SHIFT                 6  /* INL_DCS_STARTUP */
+#define WM8962_INL_DCS_STARTUP_WIDTH                 1  /* INL_DCS_STARTUP */
+#define WM8962_INR_DCS_ENA                      0x0008  /* INR_DCS_ENA */
+#define WM8962_INR_DCS_ENA_MASK                 0x0008  /* INR_DCS_ENA */
+#define WM8962_INR_DCS_ENA_SHIFT                     3  /* INR_DCS_ENA */
+#define WM8962_INR_DCS_ENA_WIDTH                     1  /* INR_DCS_ENA */
+#define WM8962_INR_DCS_STARTUP                  0x0004  /* INR_DCS_STARTUP */
+#define WM8962_INR_DCS_STARTUP_MASK             0x0004  /* INR_DCS_STARTUP */
+#define WM8962_INR_DCS_STARTUP_SHIFT                 2  /* INR_DCS_STARTUP */
+#define WM8962_INR_DCS_STARTUP_WIDTH                 1  /* INR_DCS_STARTUP */
+
+/*
+ * R61 (0x3D) - DC Servo 1
+ */
+#define WM8962_HP1L_DCS_ENA                     0x0080  /* HP1L_DCS_ENA */
+#define WM8962_HP1L_DCS_ENA_MASK                0x0080  /* HP1L_DCS_ENA */
+#define WM8962_HP1L_DCS_ENA_SHIFT                    7  /* HP1L_DCS_ENA */
+#define WM8962_HP1L_DCS_ENA_WIDTH                    1  /* HP1L_DCS_ENA */
+#define WM8962_HP1L_DCS_STARTUP                 0x0040  /* HP1L_DCS_STARTUP */
+#define WM8962_HP1L_DCS_STARTUP_MASK            0x0040  /* HP1L_DCS_STARTUP */
+#define WM8962_HP1L_DCS_STARTUP_SHIFT                6  /* HP1L_DCS_STARTUP */
+#define WM8962_HP1L_DCS_STARTUP_WIDTH                1  /* HP1L_DCS_STARTUP */
+#define WM8962_HP1L_DCS_SYNC                    0x0010  /* HP1L_DCS_SYNC */
+#define WM8962_HP1L_DCS_SYNC_MASK               0x0010  /* HP1L_DCS_SYNC */
+#define WM8962_HP1L_DCS_SYNC_SHIFT                   4  /* HP1L_DCS_SYNC */
+#define WM8962_HP1L_DCS_SYNC_WIDTH                   1  /* HP1L_DCS_SYNC */
+#define WM8962_HP1R_DCS_ENA                     0x0008  /* HP1R_DCS_ENA */
+#define WM8962_HP1R_DCS_ENA_MASK                0x0008  /* HP1R_DCS_ENA */
+#define WM8962_HP1R_DCS_ENA_SHIFT                    3  /* HP1R_DCS_ENA */
+#define WM8962_HP1R_DCS_ENA_WIDTH                    1  /* HP1R_DCS_ENA */
+#define WM8962_HP1R_DCS_STARTUP                 0x0004  /* HP1R_DCS_STARTUP */
+#define WM8962_HP1R_DCS_STARTUP_MASK            0x0004  /* HP1R_DCS_STARTUP */
+#define WM8962_HP1R_DCS_STARTUP_SHIFT                2  /* HP1R_DCS_STARTUP */
+#define WM8962_HP1R_DCS_STARTUP_WIDTH                1  /* HP1R_DCS_STARTUP */
+#define WM8962_HP1R_DCS_SYNC                    0x0001  /* HP1R_DCS_SYNC */
+#define WM8962_HP1R_DCS_SYNC_MASK               0x0001  /* HP1R_DCS_SYNC */
+#define WM8962_HP1R_DCS_SYNC_SHIFT                   0  /* HP1R_DCS_SYNC */
+#define WM8962_HP1R_DCS_SYNC_WIDTH                   1  /* HP1R_DCS_SYNC */
+
+/*
+ * R64 (0x40) - DC Servo 4
+ */
+#define WM8962_HP1_DCS_SYNC_STEPS_MASK          0x3F80  /* HP1_DCS_SYNC_STEPS - [13:7] */
+#define WM8962_HP1_DCS_SYNC_STEPS_SHIFT              7  /* HP1_DCS_SYNC_STEPS - [13:7] */
+#define WM8962_HP1_DCS_SYNC_STEPS_WIDTH              7  /* HP1_DCS_SYNC_STEPS - [13:7] */
+
+/*
+ * R66 (0x42) - DC Servo 6
+ */
+#define WM8962_DCS_STARTUP_DONE_INL             0x0400  /* DCS_STARTUP_DONE_INL */
+#define WM8962_DCS_STARTUP_DONE_INL_MASK        0x0400  /* DCS_STARTUP_DONE_INL */
+#define WM8962_DCS_STARTUP_DONE_INL_SHIFT           10  /* DCS_STARTUP_DONE_INL */
+#define WM8962_DCS_STARTUP_DONE_INL_WIDTH            1  /* DCS_STARTUP_DONE_INL */
+#define WM8962_DCS_STARTUP_DONE_INR             0x0200  /* DCS_STARTUP_DONE_INR */
+#define WM8962_DCS_STARTUP_DONE_INR_MASK        0x0200  /* DCS_STARTUP_DONE_INR */
+#define WM8962_DCS_STARTUP_DONE_INR_SHIFT            9  /* DCS_STARTUP_DONE_INR */
+#define WM8962_DCS_STARTUP_DONE_INR_WIDTH            1  /* DCS_STARTUP_DONE_INR */
+#define WM8962_DCS_STARTUP_DONE_HP1L            0x0100  /* DCS_STARTUP_DONE_HP1L */
+#define WM8962_DCS_STARTUP_DONE_HP1L_MASK       0x0100  /* DCS_STARTUP_DONE_HP1L */
+#define WM8962_DCS_STARTUP_DONE_HP1L_SHIFT           8  /* DCS_STARTUP_DONE_HP1L */
+#define WM8962_DCS_STARTUP_DONE_HP1L_WIDTH           1  /* DCS_STARTUP_DONE_HP1L */
+#define WM8962_DCS_STARTUP_DONE_HP1R            0x0080  /* DCS_STARTUP_DONE_HP1R */
+#define WM8962_DCS_STARTUP_DONE_HP1R_MASK       0x0080  /* DCS_STARTUP_DONE_HP1R */
+#define WM8962_DCS_STARTUP_DONE_HP1R_SHIFT           7  /* DCS_STARTUP_DONE_HP1R */
+#define WM8962_DCS_STARTUP_DONE_HP1R_WIDTH           1  /* DCS_STARTUP_DONE_HP1R */
+
+/*
+ * R68 (0x44) - Analogue PGA Bias
+ */
+#define WM8962_HP_PGAS_BIAS_MASK                0x0007  /* HP_PGAS_BIAS - [2:0] */
+#define WM8962_HP_PGAS_BIAS_SHIFT                    0  /* HP_PGAS_BIAS - [2:0] */
+#define WM8962_HP_PGAS_BIAS_WIDTH                    3  /* HP_PGAS_BIAS - [2:0] */
+
+/*
+ * R69 (0x45) - Analogue HP 0
+ */
+#define WM8962_HP1L_RMV_SHORT                   0x0080  /* HP1L_RMV_SHORT */
+#define WM8962_HP1L_RMV_SHORT_MASK              0x0080  /* HP1L_RMV_SHORT */
+#define WM8962_HP1L_RMV_SHORT_SHIFT                  7  /* HP1L_RMV_SHORT */
+#define WM8962_HP1L_RMV_SHORT_WIDTH                  1  /* HP1L_RMV_SHORT */
+#define WM8962_HP1L_ENA_OUTP                    0x0040  /* HP1L_ENA_OUTP */
+#define WM8962_HP1L_ENA_OUTP_MASK               0x0040  /* HP1L_ENA_OUTP */
+#define WM8962_HP1L_ENA_OUTP_SHIFT                   6  /* HP1L_ENA_OUTP */
+#define WM8962_HP1L_ENA_OUTP_WIDTH                   1  /* HP1L_ENA_OUTP */
+#define WM8962_HP1L_ENA_DLY                     0x0020  /* HP1L_ENA_DLY */
+#define WM8962_HP1L_ENA_DLY_MASK                0x0020  /* HP1L_ENA_DLY */
+#define WM8962_HP1L_ENA_DLY_SHIFT                    5  /* HP1L_ENA_DLY */
+#define WM8962_HP1L_ENA_DLY_WIDTH                    1  /* HP1L_ENA_DLY */
+#define WM8962_HP1L_ENA                         0x0010  /* HP1L_ENA */
+#define WM8962_HP1L_ENA_MASK                    0x0010  /* HP1L_ENA */
+#define WM8962_HP1L_ENA_SHIFT                        4  /* HP1L_ENA */
+#define WM8962_HP1L_ENA_WIDTH                        1  /* HP1L_ENA */
+#define WM8962_HP1R_RMV_SHORT                   0x0008  /* HP1R_RMV_SHORT */
+#define WM8962_HP1R_RMV_SHORT_MASK              0x0008  /* HP1R_RMV_SHORT */
+#define WM8962_HP1R_RMV_SHORT_SHIFT                  3  /* HP1R_RMV_SHORT */
+#define WM8962_HP1R_RMV_SHORT_WIDTH                  1  /* HP1R_RMV_SHORT */
+#define WM8962_HP1R_ENA_OUTP                    0x0004  /* HP1R_ENA_OUTP */
+#define WM8962_HP1R_ENA_OUTP_MASK               0x0004  /* HP1R_ENA_OUTP */
+#define WM8962_HP1R_ENA_OUTP_SHIFT                   2  /* HP1R_ENA_OUTP */
+#define WM8962_HP1R_ENA_OUTP_WIDTH                   1  /* HP1R_ENA_OUTP */
+#define WM8962_HP1R_ENA_DLY                     0x0002  /* HP1R_ENA_DLY */
+#define WM8962_HP1R_ENA_DLY_MASK                0x0002  /* HP1R_ENA_DLY */
+#define WM8962_HP1R_ENA_DLY_SHIFT                    1  /* HP1R_ENA_DLY */
+#define WM8962_HP1R_ENA_DLY_WIDTH                    1  /* HP1R_ENA_DLY */
+#define WM8962_HP1R_ENA                         0x0001  /* HP1R_ENA */
+#define WM8962_HP1R_ENA_MASK                    0x0001  /* HP1R_ENA */
+#define WM8962_HP1R_ENA_SHIFT                        0  /* HP1R_ENA */
+#define WM8962_HP1R_ENA_WIDTH                        1  /* HP1R_ENA */
+
+/*
+ * R71 (0x47) - Analogue HP 2
+ */
+#define WM8962_HP1L_VOL_MASK                    0x01C0  /* HP1L_VOL - [8:6] */
+#define WM8962_HP1L_VOL_SHIFT                        6  /* HP1L_VOL - [8:6] */
+#define WM8962_HP1L_VOL_WIDTH                        3  /* HP1L_VOL - [8:6] */
+#define WM8962_HP1R_VOL_MASK                    0x0038  /* HP1R_VOL - [5:3] */
+#define WM8962_HP1R_VOL_SHIFT                        3  /* HP1R_VOL - [5:3] */
+#define WM8962_HP1R_VOL_WIDTH                        3  /* HP1R_VOL - [5:3] */
+#define WM8962_HP_BIAS_BOOST_MASK               0x0007  /* HP_BIAS_BOOST - [2:0] */
+#define WM8962_HP_BIAS_BOOST_SHIFT                   0  /* HP_BIAS_BOOST - [2:0] */
+#define WM8962_HP_BIAS_BOOST_WIDTH                   3  /* HP_BIAS_BOOST - [2:0] */
+
+/*
+ * R72 (0x48) - Charge Pump 1
+ */
+#define WM8962_CP_ENA                           0x0001  /* CP_ENA */
+#define WM8962_CP_ENA_MASK                      0x0001  /* CP_ENA */
+#define WM8962_CP_ENA_SHIFT                          0  /* CP_ENA */
+#define WM8962_CP_ENA_WIDTH                          1  /* CP_ENA */
+
+/*
+ * R82 (0x52) - Charge Pump B
+ */
+#define WM8962_CP_DYN_PWR                       0x0001  /* CP_DYN_PWR */
+#define WM8962_CP_DYN_PWR_MASK                  0x0001  /* CP_DYN_PWR */
+#define WM8962_CP_DYN_PWR_SHIFT                      0  /* CP_DYN_PWR */
+#define WM8962_CP_DYN_PWR_WIDTH                      1  /* CP_DYN_PWR */
+
+/*
+ * R87 (0x57) - Write Sequencer Control 1
+ */
+#define WM8962_WSEQ_AUTOSEQ_ENA                 0x0080  /* WSEQ_AUTOSEQ_ENA */
+#define WM8962_WSEQ_AUTOSEQ_ENA_MASK            0x0080  /* WSEQ_AUTOSEQ_ENA */
+#define WM8962_WSEQ_AUTOSEQ_ENA_SHIFT                7  /* WSEQ_AUTOSEQ_ENA */
+#define WM8962_WSEQ_AUTOSEQ_ENA_WIDTH                1  /* WSEQ_AUTOSEQ_ENA */
+#define WM8962_WSEQ_ENA                         0x0020  /* WSEQ_ENA */
+#define WM8962_WSEQ_ENA_MASK                    0x0020  /* WSEQ_ENA */
+#define WM8962_WSEQ_ENA_SHIFT                        5  /* WSEQ_ENA */
+#define WM8962_WSEQ_ENA_WIDTH                        1  /* WSEQ_ENA */
+
+/*
+ * R90 (0x5A) - Write Sequencer Control 2
+ */
+#define WM8962_WSEQ_ABORT                       0x0100  /* WSEQ_ABORT */
+#define WM8962_WSEQ_ABORT_MASK                  0x0100  /* WSEQ_ABORT */
+#define WM8962_WSEQ_ABORT_SHIFT                      8  /* WSEQ_ABORT */
+#define WM8962_WSEQ_ABORT_WIDTH                      1  /* WSEQ_ABORT */
+#define WM8962_WSEQ_START                       0x0080  /* WSEQ_START */
+#define WM8962_WSEQ_START_MASK                  0x0080  /* WSEQ_START */
+#define WM8962_WSEQ_START_SHIFT                      7  /* WSEQ_START */
+#define WM8962_WSEQ_START_WIDTH                      1  /* WSEQ_START */
+#define WM8962_WSEQ_START_INDEX_MASK            0x007F  /* WSEQ_START_INDEX - [6:0] */
+#define WM8962_WSEQ_START_INDEX_SHIFT                0  /* WSEQ_START_INDEX - [6:0] */
+#define WM8962_WSEQ_START_INDEX_WIDTH                7  /* WSEQ_START_INDEX - [6:0] */
+
+/*
+ * R93 (0x5D) - Write Sequencer Control 3
+ */
+#define WM8962_WSEQ_CURRENT_INDEX_MASK          0x03F8  /* WSEQ_CURRENT_INDEX - [9:3] */
+#define WM8962_WSEQ_CURRENT_INDEX_SHIFT              3  /* WSEQ_CURRENT_INDEX - [9:3] */
+#define WM8962_WSEQ_CURRENT_INDEX_WIDTH              7  /* WSEQ_CURRENT_INDEX - [9:3] */
+#define WM8962_WSEQ_BUSY                        0x0001  /* WSEQ_BUSY */
+#define WM8962_WSEQ_BUSY_MASK                   0x0001  /* WSEQ_BUSY */
+#define WM8962_WSEQ_BUSY_SHIFT                       0  /* WSEQ_BUSY */
+#define WM8962_WSEQ_BUSY_WIDTH                       1  /* WSEQ_BUSY */
+
+/*
+ * R94 (0x5E) - Control Interface
+ */
+#define WM8962_SPI_CONTRD                       0x0040  /* SPI_CONTRD */
+#define WM8962_SPI_CONTRD_MASK                  0x0040  /* SPI_CONTRD */
+#define WM8962_SPI_CONTRD_SHIFT                      6  /* SPI_CONTRD */
+#define WM8962_SPI_CONTRD_WIDTH                      1  /* SPI_CONTRD */
+#define WM8962_SPI_4WIRE                        0x0020  /* SPI_4WIRE */
+#define WM8962_SPI_4WIRE_MASK                   0x0020  /* SPI_4WIRE */
+#define WM8962_SPI_4WIRE_SHIFT                       5  /* SPI_4WIRE */
+#define WM8962_SPI_4WIRE_WIDTH                       1  /* SPI_4WIRE */
+#define WM8962_SPI_CFG                          0x0010  /* SPI_CFG */
+#define WM8962_SPI_CFG_MASK                     0x0010  /* SPI_CFG */
+#define WM8962_SPI_CFG_SHIFT                         4  /* SPI_CFG */
+#define WM8962_SPI_CFG_WIDTH                         1  /* SPI_CFG */
+
+/*
+ * R99 (0x63) - Mixer Enables
+ */
+#define WM8962_HPMIXL_ENA                       0x0008  /* HPMIXL_ENA */
+#define WM8962_HPMIXL_ENA_MASK                  0x0008  /* HPMIXL_ENA */
+#define WM8962_HPMIXL_ENA_SHIFT                      3  /* HPMIXL_ENA */
+#define WM8962_HPMIXL_ENA_WIDTH                      1  /* HPMIXL_ENA */
+#define WM8962_HPMIXR_ENA                       0x0004  /* HPMIXR_ENA */
+#define WM8962_HPMIXR_ENA_MASK                  0x0004  /* HPMIXR_ENA */
+#define WM8962_HPMIXR_ENA_SHIFT                      2  /* HPMIXR_ENA */
+#define WM8962_HPMIXR_ENA_WIDTH                      1  /* HPMIXR_ENA */
+#define WM8962_SPKMIXL_ENA                      0x0002  /* SPKMIXL_ENA */
+#define WM8962_SPKMIXL_ENA_MASK                 0x0002  /* SPKMIXL_ENA */
+#define WM8962_SPKMIXL_ENA_SHIFT                     1  /* SPKMIXL_ENA */
+#define WM8962_SPKMIXL_ENA_WIDTH                     1  /* SPKMIXL_ENA */
+#define WM8962_SPKMIXR_ENA                      0x0001  /* SPKMIXR_ENA */
+#define WM8962_SPKMIXR_ENA_MASK                 0x0001  /* SPKMIXR_ENA */
+#define WM8962_SPKMIXR_ENA_SHIFT                     0  /* SPKMIXR_ENA */
+#define WM8962_SPKMIXR_ENA_WIDTH                     1  /* SPKMIXR_ENA */
+
+/*
+ * R100 (0x64) - Headphone Mixer (1)
+ */
+#define WM8962_HPMIXL_TO_HPOUTL_PGA             0x0080  /* HPMIXL_TO_HPOUTL_PGA */
+#define WM8962_HPMIXL_TO_HPOUTL_PGA_MASK        0x0080  /* HPMIXL_TO_HPOUTL_PGA */
+#define WM8962_HPMIXL_TO_HPOUTL_PGA_SHIFT            7  /* HPMIXL_TO_HPOUTL_PGA */
+#define WM8962_HPMIXL_TO_HPOUTL_PGA_WIDTH            1  /* HPMIXL_TO_HPOUTL_PGA */
+#define WM8962_DACL_TO_HPMIXL                   0x0020  /* DACL_TO_HPMIXL */
+#define WM8962_DACL_TO_HPMIXL_MASK              0x0020  /* DACL_TO_HPMIXL */
+#define WM8962_DACL_TO_HPMIXL_SHIFT                  5  /* DACL_TO_HPMIXL */
+#define WM8962_DACL_TO_HPMIXL_WIDTH                  1  /* DACL_TO_HPMIXL */
+#define WM8962_DACR_TO_HPMIXL                   0x0010  /* DACR_TO_HPMIXL */
+#define WM8962_DACR_TO_HPMIXL_MASK              0x0010  /* DACR_TO_HPMIXL */
+#define WM8962_DACR_TO_HPMIXL_SHIFT                  4  /* DACR_TO_HPMIXL */
+#define WM8962_DACR_TO_HPMIXL_WIDTH                  1  /* DACR_TO_HPMIXL */
+#define WM8962_MIXINL_TO_HPMIXL                 0x0008  /* MIXINL_TO_HPMIXL */
+#define WM8962_MIXINL_TO_HPMIXL_MASK            0x0008  /* MIXINL_TO_HPMIXL */
+#define WM8962_MIXINL_TO_HPMIXL_SHIFT                3  /* MIXINL_TO_HPMIXL */
+#define WM8962_MIXINL_TO_HPMIXL_WIDTH                1  /* MIXINL_TO_HPMIXL */
+#define WM8962_MIXINR_TO_HPMIXL                 0x0004  /* MIXINR_TO_HPMIXL */
+#define WM8962_MIXINR_TO_HPMIXL_MASK            0x0004  /* MIXINR_TO_HPMIXL */
+#define WM8962_MIXINR_TO_HPMIXL_SHIFT                2  /* MIXINR_TO_HPMIXL */
+#define WM8962_MIXINR_TO_HPMIXL_WIDTH                1  /* MIXINR_TO_HPMIXL */
+#define WM8962_IN4L_TO_HPMIXL                   0x0002  /* IN4L_TO_HPMIXL */
+#define WM8962_IN4L_TO_HPMIXL_MASK              0x0002  /* IN4L_TO_HPMIXL */
+#define WM8962_IN4L_TO_HPMIXL_SHIFT                  1  /* IN4L_TO_HPMIXL */
+#define WM8962_IN4L_TO_HPMIXL_WIDTH                  1  /* IN4L_TO_HPMIXL */
+#define WM8962_IN4R_TO_HPMIXL                   0x0001  /* IN4R_TO_HPMIXL */
+#define WM8962_IN4R_TO_HPMIXL_MASK              0x0001  /* IN4R_TO_HPMIXL */
+#define WM8962_IN4R_TO_HPMIXL_SHIFT                  0  /* IN4R_TO_HPMIXL */
+#define WM8962_IN4R_TO_HPMIXL_WIDTH                  1  /* IN4R_TO_HPMIXL */
+
+/*
+ * R101 (0x65) - Headphone Mixer (2)
+ */
+#define WM8962_HPMIXR_TO_HPOUTR_PGA             0x0080  /* HPMIXR_TO_HPOUTR_PGA */
+#define WM8962_HPMIXR_TO_HPOUTR_PGA_MASK        0x0080  /* HPMIXR_TO_HPOUTR_PGA */
+#define WM8962_HPMIXR_TO_HPOUTR_PGA_SHIFT            7  /* HPMIXR_TO_HPOUTR_PGA */
+#define WM8962_HPMIXR_TO_HPOUTR_PGA_WIDTH            1  /* HPMIXR_TO_HPOUTR_PGA */
+#define WM8962_DACL_TO_HPMIXR                   0x0020  /* DACL_TO_HPMIXR */
+#define WM8962_DACL_TO_HPMIXR_MASK              0x0020  /* DACL_TO_HPMIXR */
+#define WM8962_DACL_TO_HPMIXR_SHIFT                  5  /* DACL_TO_HPMIXR */
+#define WM8962_DACL_TO_HPMIXR_WIDTH                  1  /* DACL_TO_HPMIXR */
+#define WM8962_DACR_TO_HPMIXR                   0x0010  /* DACR_TO_HPMIXR */
+#define WM8962_DACR_TO_HPMIXR_MASK              0x0010  /* DACR_TO_HPMIXR */
+#define WM8962_DACR_TO_HPMIXR_SHIFT                  4  /* DACR_TO_HPMIXR */
+#define WM8962_DACR_TO_HPMIXR_WIDTH                  1  /* DACR_TO_HPMIXR */
+#define WM8962_MIXINL_TO_HPMIXR                 0x0008  /* MIXINL_TO_HPMIXR */
+#define WM8962_MIXINL_TO_HPMIXR_MASK            0x0008  /* MIXINL_TO_HPMIXR */
+#define WM8962_MIXINL_TO_HPMIXR_SHIFT                3  /* MIXINL_TO_HPMIXR */
+#define WM8962_MIXINL_TO_HPMIXR_WIDTH                1  /* MIXINL_TO_HPMIXR */
+#define WM8962_MIXINR_TO_HPMIXR                 0x0004  /* MIXINR_TO_HPMIXR */
+#define WM8962_MIXINR_TO_HPMIXR_MASK            0x0004  /* MIXINR_TO_HPMIXR */
+#define WM8962_MIXINR_TO_HPMIXR_SHIFT                2  /* MIXINR_TO_HPMIXR */
+#define WM8962_MIXINR_TO_HPMIXR_WIDTH                1  /* MIXINR_TO_HPMIXR */
+#define WM8962_IN4L_TO_HPMIXR                   0x0002  /* IN4L_TO_HPMIXR */
+#define WM8962_IN4L_TO_HPMIXR_MASK              0x0002  /* IN4L_TO_HPMIXR */
+#define WM8962_IN4L_TO_HPMIXR_SHIFT                  1  /* IN4L_TO_HPMIXR */
+#define WM8962_IN4L_TO_HPMIXR_WIDTH                  1  /* IN4L_TO_HPMIXR */
+#define WM8962_IN4R_TO_HPMIXR                   0x0001  /* IN4R_TO_HPMIXR */
+#define WM8962_IN4R_TO_HPMIXR_MASK              0x0001  /* IN4R_TO_HPMIXR */
+#define WM8962_IN4R_TO_HPMIXR_SHIFT                  0  /* IN4R_TO_HPMIXR */
+#define WM8962_IN4R_TO_HPMIXR_WIDTH                  1  /* IN4R_TO_HPMIXR */
+
+/*
+ * R102 (0x66) - Headphone Mixer (3)
+ */
+#define WM8962_HPMIXL_MUTE                      0x0100  /* HPMIXL_MUTE */
+#define WM8962_HPMIXL_MUTE_MASK                 0x0100  /* HPMIXL_MUTE */
+#define WM8962_HPMIXL_MUTE_SHIFT                     8  /* HPMIXL_MUTE */
+#define WM8962_HPMIXL_MUTE_WIDTH                     1  /* HPMIXL_MUTE */
+#define WM8962_MIXINL_HPMIXL_VOL                0x0080  /* MIXINL_HPMIXL_VOL */
+#define WM8962_MIXINL_HPMIXL_VOL_MASK           0x0080  /* MIXINL_HPMIXL_VOL */
+#define WM8962_MIXINL_HPMIXL_VOL_SHIFT               7  /* MIXINL_HPMIXL_VOL */
+#define WM8962_MIXINL_HPMIXL_VOL_WIDTH               1  /* MIXINL_HPMIXL_VOL */
+#define WM8962_MIXINR_HPMIXL_VOL                0x0040  /* MIXINR_HPMIXL_VOL */
+#define WM8962_MIXINR_HPMIXL_VOL_MASK           0x0040  /* MIXINR_HPMIXL_VOL */
+#define WM8962_MIXINR_HPMIXL_VOL_SHIFT               6  /* MIXINR_HPMIXL_VOL */
+#define WM8962_MIXINR_HPMIXL_VOL_WIDTH               1  /* MIXINR_HPMIXL_VOL */
+#define WM8962_IN4L_HPMIXL_VOL_MASK             0x0038  /* IN4L_HPMIXL_VOL - [5:3] */
+#define WM8962_IN4L_HPMIXL_VOL_SHIFT                 3  /* IN4L_HPMIXL_VOL - [5:3] */
+#define WM8962_IN4L_HPMIXL_VOL_WIDTH                 3  /* IN4L_HPMIXL_VOL - [5:3] */
+#define WM8962_IN4R_HPMIXL_VOL_MASK             0x0007  /* IN4R_HPMIXL_VOL - [2:0] */
+#define WM8962_IN4R_HPMIXL_VOL_SHIFT                 0  /* IN4R_HPMIXL_VOL - [2:0] */
+#define WM8962_IN4R_HPMIXL_VOL_WIDTH                 3  /* IN4R_HPMIXL_VOL - [2:0] */
+
+/*
+ * R103 (0x67) - Headphone Mixer (4)
+ */
+#define WM8962_HPMIXR_MUTE                      0x0100  /* HPMIXR_MUTE */
+#define WM8962_HPMIXR_MUTE_MASK                 0x0100  /* HPMIXR_MUTE */
+#define WM8962_HPMIXR_MUTE_SHIFT                     8  /* HPMIXR_MUTE */
+#define WM8962_HPMIXR_MUTE_WIDTH                     1  /* HPMIXR_MUTE */
+#define WM8962_MIXINL_HPMIXR_VOL                0x0080  /* MIXINL_HPMIXR_VOL */
+#define WM8962_MIXINL_HPMIXR_VOL_MASK           0x0080  /* MIXINL_HPMIXR_VOL */
+#define WM8962_MIXINL_HPMIXR_VOL_SHIFT               7  /* MIXINL_HPMIXR_VOL */
+#define WM8962_MIXINL_HPMIXR_VOL_WIDTH               1  /* MIXINL_HPMIXR_VOL */
+#define WM8962_MIXINR_HPMIXR_VOL                0x0040  /* MIXINR_HPMIXR_VOL */
+#define WM8962_MIXINR_HPMIXR_VOL_MASK           0x0040  /* MIXINR_HPMIXR_VOL */
+#define WM8962_MIXINR_HPMIXR_VOL_SHIFT               6  /* MIXINR_HPMIXR_VOL */
+#define WM8962_MIXINR_HPMIXR_VOL_WIDTH               1  /* MIXINR_HPMIXR_VOL */
+#define WM8962_IN4L_HPMIXR_VOL_MASK             0x0038  /* IN4L_HPMIXR_VOL - [5:3] */
+#define WM8962_IN4L_HPMIXR_VOL_SHIFT                 3  /* IN4L_HPMIXR_VOL - [5:3] */
+#define WM8962_IN4L_HPMIXR_VOL_WIDTH                 3  /* IN4L_HPMIXR_VOL - [5:3] */
+#define WM8962_IN4R_HPMIXR_VOL_MASK             0x0007  /* IN4R_HPMIXR_VOL - [2:0] */
+#define WM8962_IN4R_HPMIXR_VOL_SHIFT                 0  /* IN4R_HPMIXR_VOL - [2:0] */
+#define WM8962_IN4R_HPMIXR_VOL_WIDTH                 3  /* IN4R_HPMIXR_VOL - [2:0] */
+
+/*
+ * R105 (0x69) - Speaker Mixer (1)
+ */
+#define WM8962_SPKMIXL_TO_SPKOUTL_PGA           0x0080  /* SPKMIXL_TO_SPKOUTL_PGA */
+#define WM8962_SPKMIXL_TO_SPKOUTL_PGA_MASK      0x0080  /* SPKMIXL_TO_SPKOUTL_PGA */
+#define WM8962_SPKMIXL_TO_SPKOUTL_PGA_SHIFT          7  /* SPKMIXL_TO_SPKOUTL_PGA */
+#define WM8962_SPKMIXL_TO_SPKOUTL_PGA_WIDTH          1  /* SPKMIXL_TO_SPKOUTL_PGA */
+#define WM8962_DACL_TO_SPKMIXL                  0x0020  /* DACL_TO_SPKMIXL */
+#define WM8962_DACL_TO_SPKMIXL_MASK             0x0020  /* DACL_TO_SPKMIXL */
+#define WM8962_DACL_TO_SPKMIXL_SHIFT                 5  /* DACL_TO_SPKMIXL */
+#define WM8962_DACL_TO_SPKMIXL_WIDTH                 1  /* DACL_TO_SPKMIXL */
+#define WM8962_DACR_TO_SPKMIXL                  0x0010  /* DACR_TO_SPKMIXL */
+#define WM8962_DACR_TO_SPKMIXL_MASK             0x0010  /* DACR_TO_SPKMIXL */
+#define WM8962_DACR_TO_SPKMIXL_SHIFT                 4  /* DACR_TO_SPKMIXL */
+#define WM8962_DACR_TO_SPKMIXL_WIDTH                 1  /* DACR_TO_SPKMIXL */
+#define WM8962_MIXINL_TO_SPKMIXL                0x0008  /* MIXINL_TO_SPKMIXL */
+#define WM8962_MIXINL_TO_SPKMIXL_MASK           0x0008  /* MIXINL_TO_SPKMIXL */
+#define WM8962_MIXINL_TO_SPKMIXL_SHIFT               3  /* MIXINL_TO_SPKMIXL */
+#define WM8962_MIXINL_TO_SPKMIXL_WIDTH               1  /* MIXINL_TO_SPKMIXL */
+#define WM8962_MIXINR_TO_SPKMIXL                0x0004  /* MIXINR_TO_SPKMIXL */
+#define WM8962_MIXINR_TO_SPKMIXL_MASK           0x0004  /* MIXINR_TO_SPKMIXL */
+#define WM8962_MIXINR_TO_SPKMIXL_SHIFT               2  /* MIXINR_TO_SPKMIXL */
+#define WM8962_MIXINR_TO_SPKMIXL_WIDTH               1  /* MIXINR_TO_SPKMIXL */
+#define WM8962_IN4L_TO_SPKMIXL                  0x0002  /* IN4L_TO_SPKMIXL */
+#define WM8962_IN4L_TO_SPKMIXL_MASK             0x0002  /* IN4L_TO_SPKMIXL */
+#define WM8962_IN4L_TO_SPKMIXL_SHIFT                 1  /* IN4L_TO_SPKMIXL */
+#define WM8962_IN4L_TO_SPKMIXL_WIDTH                 1  /* IN4L_TO_SPKMIXL */
+#define WM8962_IN4R_TO_SPKMIXL                  0x0001  /* IN4R_TO_SPKMIXL */
+#define WM8962_IN4R_TO_SPKMIXL_MASK             0x0001  /* IN4R_TO_SPKMIXL */
+#define WM8962_IN4R_TO_SPKMIXL_SHIFT                 0  /* IN4R_TO_SPKMIXL */
+#define WM8962_IN4R_TO_SPKMIXL_WIDTH                 1  /* IN4R_TO_SPKMIXL */
+
+/*
+ * R106 (0x6A) - Speaker Mixer (2)
+ */
+#define WM8962_SPKMIXR_TO_SPKOUTR_PGA           0x0080  /* SPKMIXR_TO_SPKOUTR_PGA */
+#define WM8962_SPKMIXR_TO_SPKOUTR_PGA_MASK      0x0080  /* SPKMIXR_TO_SPKOUTR_PGA */
+#define WM8962_SPKMIXR_TO_SPKOUTR_PGA_SHIFT          7  /* SPKMIXR_TO_SPKOUTR_PGA */
+#define WM8962_SPKMIXR_TO_SPKOUTR_PGA_WIDTH          1  /* SPKMIXR_TO_SPKOUTR_PGA */
+#define WM8962_DACL_TO_SPKMIXR                  0x0020  /* DACL_TO_SPKMIXR */
+#define WM8962_DACL_TO_SPKMIXR_MASK             0x0020  /* DACL_TO_SPKMIXR */
+#define WM8962_DACL_TO_SPKMIXR_SHIFT                 5  /* DACL_TO_SPKMIXR */
+#define WM8962_DACL_TO_SPKMIXR_WIDTH                 1  /* DACL_TO_SPKMIXR */
+#define WM8962_DACR_TO_SPKMIXR                  0x0010  /* DACR_TO_SPKMIXR */
+#define WM8962_DACR_TO_SPKMIXR_MASK             0x0010  /* DACR_TO_SPKMIXR */
+#define WM8962_DACR_TO_SPKMIXR_SHIFT                 4  /* DACR_TO_SPKMIXR */
+#define WM8962_DACR_TO_SPKMIXR_WIDTH                 1  /* DACR_TO_SPKMIXR */
+#define WM8962_MIXINL_TO_SPKMIXR                0x0008  /* MIXINL_TO_SPKMIXR */
+#define WM8962_MIXINL_TO_SPKMIXR_MASK           0x0008  /* MIXINL_TO_SPKMIXR */
+#define WM8962_MIXINL_TO_SPKMIXR_SHIFT               3  /* MIXINL_TO_SPKMIXR */
+#define WM8962_MIXINL_TO_SPKMIXR_WIDTH               1  /* MIXINL_TO_SPKMIXR */
+#define WM8962_MIXINR_TO_SPKMIXR                0x0004  /* MIXINR_TO_SPKMIXR */
+#define WM8962_MIXINR_TO_SPKMIXR_MASK           0x0004  /* MIXINR_TO_SPKMIXR */
+#define WM8962_MIXINR_TO_SPKMIXR_SHIFT               2  /* MIXINR_TO_SPKMIXR */
+#define WM8962_MIXINR_TO_SPKMIXR_WIDTH               1  /* MIXINR_TO_SPKMIXR */
+#define WM8962_IN4L_TO_SPKMIXR                  0x0002  /* IN4L_TO_SPKMIXR */
+#define WM8962_IN4L_TO_SPKMIXR_MASK             0x0002  /* IN4L_TO_SPKMIXR */
+#define WM8962_IN4L_TO_SPKMIXR_SHIFT                 1  /* IN4L_TO_SPKMIXR */
+#define WM8962_IN4L_TO_SPKMIXR_WIDTH                 1  /* IN4L_TO_SPKMIXR */
+#define WM8962_IN4R_TO_SPKMIXR                  0x0001  /* IN4R_TO_SPKMIXR */
+#define WM8962_IN4R_TO_SPKMIXR_MASK             0x0001  /* IN4R_TO_SPKMIXR */
+#define WM8962_IN4R_TO_SPKMIXR_SHIFT                 0  /* IN4R_TO_SPKMIXR */
+#define WM8962_IN4R_TO_SPKMIXR_WIDTH                 1  /* IN4R_TO_SPKMIXR */
+
+/*
+ * R107 (0x6B) - Speaker Mixer (3)
+ */
+#define WM8962_SPKMIXL_MUTE                     0x0100  /* SPKMIXL_MUTE */
+#define WM8962_SPKMIXL_MUTE_MASK                0x0100  /* SPKMIXL_MUTE */
+#define WM8962_SPKMIXL_MUTE_SHIFT                    8  /* SPKMIXL_MUTE */
+#define WM8962_SPKMIXL_MUTE_WIDTH                    1  /* SPKMIXL_MUTE */
+#define WM8962_MIXINL_SPKMIXL_VOL               0x0080  /* MIXINL_SPKMIXL_VOL */
+#define WM8962_MIXINL_SPKMIXL_VOL_MASK          0x0080  /* MIXINL_SPKMIXL_VOL */
+#define WM8962_MIXINL_SPKMIXL_VOL_SHIFT              7  /* MIXINL_SPKMIXL_VOL */
+#define WM8962_MIXINL_SPKMIXL_VOL_WIDTH              1  /* MIXINL_SPKMIXL_VOL */
+#define WM8962_MIXINR_SPKMIXL_VOL               0x0040  /* MIXINR_SPKMIXL_VOL */
+#define WM8962_MIXINR_SPKMIXL_VOL_MASK          0x0040  /* MIXINR_SPKMIXL_VOL */
+#define WM8962_MIXINR_SPKMIXL_VOL_SHIFT              6  /* MIXINR_SPKMIXL_VOL */
+#define WM8962_MIXINR_SPKMIXL_VOL_WIDTH              1  /* MIXINR_SPKMIXL_VOL */
+#define WM8962_IN4L_SPKMIXL_VOL_MASK            0x0038  /* IN4L_SPKMIXL_VOL - [5:3] */
+#define WM8962_IN4L_SPKMIXL_VOL_SHIFT                3  /* IN4L_SPKMIXL_VOL - [5:3] */
+#define WM8962_IN4L_SPKMIXL_VOL_WIDTH                3  /* IN4L_SPKMIXL_VOL - [5:3] */
+#define WM8962_IN4R_SPKMIXL_VOL_MASK            0x0007  /* IN4R_SPKMIXL_VOL - [2:0] */
+#define WM8962_IN4R_SPKMIXL_VOL_SHIFT                0  /* IN4R_SPKMIXL_VOL - [2:0] */
+#define WM8962_IN4R_SPKMIXL_VOL_WIDTH                3  /* IN4R_SPKMIXL_VOL - [2:0] */
+
+/*
+ * R108 (0x6C) - Speaker Mixer (4)
+ */
+#define WM8962_SPKMIXR_MUTE                     0x0100  /* SPKMIXR_MUTE */
+#define WM8962_SPKMIXR_MUTE_MASK                0x0100  /* SPKMIXR_MUTE */
+#define WM8962_SPKMIXR_MUTE_SHIFT                    8  /* SPKMIXR_MUTE */
+#define WM8962_SPKMIXR_MUTE_WIDTH                    1  /* SPKMIXR_MUTE */
+#define WM8962_MIXINL_SPKMIXR_VOL               0x0080  /* MIXINL_SPKMIXR_VOL */
+#define WM8962_MIXINL_SPKMIXR_VOL_MASK          0x0080  /* MIXINL_SPKMIXR_VOL */
+#define WM8962_MIXINL_SPKMIXR_VOL_SHIFT              7  /* MIXINL_SPKMIXR_VOL */
+#define WM8962_MIXINL_SPKMIXR_VOL_WIDTH              1  /* MIXINL_SPKMIXR_VOL */
+#define WM8962_MIXINR_SPKMIXR_VOL               0x0040  /* MIXINR_SPKMIXR_VOL */
+#define WM8962_MIXINR_SPKMIXR_VOL_MASK          0x0040  /* MIXINR_SPKMIXR_VOL */
+#define WM8962_MIXINR_SPKMIXR_VOL_SHIFT              6  /* MIXINR_SPKMIXR_VOL */
+#define WM8962_MIXINR_SPKMIXR_VOL_WIDTH              1  /* MIXINR_SPKMIXR_VOL */
+#define WM8962_IN4L_SPKMIXR_VOL_MASK            0x0038  /* IN4L_SPKMIXR_VOL - [5:3] */
+#define WM8962_IN4L_SPKMIXR_VOL_SHIFT                3  /* IN4L_SPKMIXR_VOL - [5:3] */
+#define WM8962_IN4L_SPKMIXR_VOL_WIDTH                3  /* IN4L_SPKMIXR_VOL - [5:3] */
+#define WM8962_IN4R_SPKMIXR_VOL_MASK            0x0007  /* IN4R_SPKMIXR_VOL - [2:0] */
+#define WM8962_IN4R_SPKMIXR_VOL_SHIFT                0  /* IN4R_SPKMIXR_VOL - [2:0] */
+#define WM8962_IN4R_SPKMIXR_VOL_WIDTH                3  /* IN4R_SPKMIXR_VOL - [2:0] */
+
+/*
+ * R109 (0x6D) - Speaker Mixer (5)
+ */
+#define WM8962_DACL_SPKMIXL_VOL                 0x0080  /* DACL_SPKMIXL_VOL */
+#define WM8962_DACL_SPKMIXL_VOL_MASK            0x0080  /* DACL_SPKMIXL_VOL */
+#define WM8962_DACL_SPKMIXL_VOL_SHIFT                7  /* DACL_SPKMIXL_VOL */
+#define WM8962_DACL_SPKMIXL_VOL_WIDTH                1  /* DACL_SPKMIXL_VOL */
+#define WM8962_DACR_SPKMIXL_VOL                 0x0040  /* DACR_SPKMIXL_VOL */
+#define WM8962_DACR_SPKMIXL_VOL_MASK            0x0040  /* DACR_SPKMIXL_VOL */
+#define WM8962_DACR_SPKMIXL_VOL_SHIFT                6  /* DACR_SPKMIXL_VOL */
+#define WM8962_DACR_SPKMIXL_VOL_WIDTH                1  /* DACR_SPKMIXL_VOL */
+#define WM8962_DACL_SPKMIXR_VOL                 0x0020  /* DACL_SPKMIXR_VOL */
+#define WM8962_DACL_SPKMIXR_VOL_MASK            0x0020  /* DACL_SPKMIXR_VOL */
+#define WM8962_DACL_SPKMIXR_VOL_SHIFT                5  /* DACL_SPKMIXR_VOL */
+#define WM8962_DACL_SPKMIXR_VOL_WIDTH                1  /* DACL_SPKMIXR_VOL */
+#define WM8962_DACR_SPKMIXR_VOL                 0x0010  /* DACR_SPKMIXR_VOL */
+#define WM8962_DACR_SPKMIXR_VOL_MASK            0x0010  /* DACR_SPKMIXR_VOL */
+#define WM8962_DACR_SPKMIXR_VOL_SHIFT                4  /* DACR_SPKMIXR_VOL */
+#define WM8962_DACR_SPKMIXR_VOL_WIDTH                1  /* DACR_SPKMIXR_VOL */
+
+/*
+ * R110 (0x6E) - Beep Generator (1)
+ */
+#define WM8962_BEEP_GAIN_MASK                   0x00F0  /* BEEP_GAIN - [7:4] */
+#define WM8962_BEEP_GAIN_SHIFT                       4  /* BEEP_GAIN - [7:4] */
+#define WM8962_BEEP_GAIN_WIDTH                       4  /* BEEP_GAIN - [7:4] */
+#define WM8962_BEEP_RATE_MASK                   0x0006  /* BEEP_RATE - [2:1] */
+#define WM8962_BEEP_RATE_SHIFT                       1  /* BEEP_RATE - [2:1] */
+#define WM8962_BEEP_RATE_WIDTH                       2  /* BEEP_RATE - [2:1] */
+#define WM8962_BEEP_ENA                         0x0001  /* BEEP_ENA */
+#define WM8962_BEEP_ENA_MASK                    0x0001  /* BEEP_ENA */
+#define WM8962_BEEP_ENA_SHIFT                        0  /* BEEP_ENA */
+#define WM8962_BEEP_ENA_WIDTH                        1  /* BEEP_ENA */
+
+/*
+ * R115 (0x73) - Oscillator Trim (3)
+ */
+#define WM8962_OSC_TRIM_XTI_MASK                0x001F  /* OSC_TRIM_XTI - [4:0] */
+#define WM8962_OSC_TRIM_XTI_SHIFT                    0  /* OSC_TRIM_XTI - [4:0] */
+#define WM8962_OSC_TRIM_XTI_WIDTH                    5  /* OSC_TRIM_XTI - [4:0] */
+
+/*
+ * R116 (0x74) - Oscillator Trim (4)
+ */
+#define WM8962_OSC_TRIM_XTO_MASK                0x001F  /* OSC_TRIM_XTO - [4:0] */
+#define WM8962_OSC_TRIM_XTO_SHIFT                    0  /* OSC_TRIM_XTO - [4:0] */
+#define WM8962_OSC_TRIM_XTO_WIDTH                    5  /* OSC_TRIM_XTO - [4:0] */
+
+/*
+ * R119 (0x77) - Oscillator Trim (7)
+ */
+#define WM8962_XTO_CAP_SEL_MASK                 0x00F0  /* XTO_CAP_SEL - [7:4] */
+#define WM8962_XTO_CAP_SEL_SHIFT                     4  /* XTO_CAP_SEL - [7:4] */
+#define WM8962_XTO_CAP_SEL_WIDTH                     4  /* XTO_CAP_SEL - [7:4] */
+#define WM8962_XTI_CAP_SEL_MASK                 0x000F  /* XTI_CAP_SEL - [3:0] */
+#define WM8962_XTI_CAP_SEL_SHIFT                     0  /* XTI_CAP_SEL - [3:0] */
+#define WM8962_XTI_CAP_SEL_WIDTH                     4  /* XTI_CAP_SEL - [3:0] */
+
+/*
+ * R124 (0x7C) - Analogue Clocking1
+ */
+#define WM8962_CLKOUT2_SEL_MASK                 0x0060  /* CLKOUT2_SEL - [6:5] */
+#define WM8962_CLKOUT2_SEL_SHIFT                     5  /* CLKOUT2_SEL - [6:5] */
+#define WM8962_CLKOUT2_SEL_WIDTH                     2  /* CLKOUT2_SEL - [6:5] */
+#define WM8962_CLKOUT3_SEL_MASK                 0x0018  /* CLKOUT3_SEL - [4:3] */
+#define WM8962_CLKOUT3_SEL_SHIFT                     3  /* CLKOUT3_SEL - [4:3] */
+#define WM8962_CLKOUT3_SEL_WIDTH                     2  /* CLKOUT3_SEL - [4:3] */
+#define WM8962_CLKOUT5_SEL                      0x0001  /* CLKOUT5_SEL */
+#define WM8962_CLKOUT5_SEL_MASK                 0x0001  /* CLKOUT5_SEL */
+#define WM8962_CLKOUT5_SEL_SHIFT                     0  /* CLKOUT5_SEL */
+#define WM8962_CLKOUT5_SEL_WIDTH                     1  /* CLKOUT5_SEL */
+
+/*
+ * R125 (0x7D) - Analogue Clocking2
+ */
+#define WM8962_PLL2_OUTDIV                      0x0080  /* PLL2_OUTDIV */
+#define WM8962_PLL2_OUTDIV_MASK                 0x0080  /* PLL2_OUTDIV */
+#define WM8962_PLL2_OUTDIV_SHIFT                     7  /* PLL2_OUTDIV */
+#define WM8962_PLL2_OUTDIV_WIDTH                     1  /* PLL2_OUTDIV */
+#define WM8962_PLL3_OUTDIV                      0x0040  /* PLL3_OUTDIV */
+#define WM8962_PLL3_OUTDIV_MASK                 0x0040  /* PLL3_OUTDIV */
+#define WM8962_PLL3_OUTDIV_SHIFT                     6  /* PLL3_OUTDIV */
+#define WM8962_PLL3_OUTDIV_WIDTH                     1  /* PLL3_OUTDIV */
+#define WM8962_PLL_SYSCLK_DIV_MASK              0x0018  /* PLL_SYSCLK_DIV - [4:3] */
+#define WM8962_PLL_SYSCLK_DIV_SHIFT                  3  /* PLL_SYSCLK_DIV - [4:3] */
+#define WM8962_PLL_SYSCLK_DIV_WIDTH                  2  /* PLL_SYSCLK_DIV - [4:3] */
+#define WM8962_CLKOUT3_DIV                      0x0004  /* CLKOUT3_DIV */
+#define WM8962_CLKOUT3_DIV_MASK                 0x0004  /* CLKOUT3_DIV */
+#define WM8962_CLKOUT3_DIV_SHIFT                     2  /* CLKOUT3_DIV */
+#define WM8962_CLKOUT3_DIV_WIDTH                     1  /* CLKOUT3_DIV */
+#define WM8962_CLKOUT2_DIV                      0x0002  /* CLKOUT2_DIV */
+#define WM8962_CLKOUT2_DIV_MASK                 0x0002  /* CLKOUT2_DIV */
+#define WM8962_CLKOUT2_DIV_SHIFT                     1  /* CLKOUT2_DIV */
+#define WM8962_CLKOUT2_DIV_WIDTH                     1  /* CLKOUT2_DIV */
+#define WM8962_CLKOUT5_DIV                      0x0001  /* CLKOUT5_DIV */
+#define WM8962_CLKOUT5_DIV_MASK                 0x0001  /* CLKOUT5_DIV */
+#define WM8962_CLKOUT5_DIV_SHIFT                     0  /* CLKOUT5_DIV */
+#define WM8962_CLKOUT5_DIV_WIDTH                     1  /* CLKOUT5_DIV */
+
+/*
+ * R126 (0x7E) - Analogue Clocking3
+ */
+#define WM8962_CLKOUT2_OE                       0x0008  /* CLKOUT2_OE */
+#define WM8962_CLKOUT2_OE_MASK                  0x0008  /* CLKOUT2_OE */
+#define WM8962_CLKOUT2_OE_SHIFT                      3  /* CLKOUT2_OE */
+#define WM8962_CLKOUT2_OE_WIDTH                      1  /* CLKOUT2_OE */
+#define WM8962_CLKOUT3_OE                       0x0004  /* CLKOUT3_OE */
+#define WM8962_CLKOUT3_OE_MASK                  0x0004  /* CLKOUT3_OE */
+#define WM8962_CLKOUT3_OE_SHIFT                      2  /* CLKOUT3_OE */
+#define WM8962_CLKOUT3_OE_WIDTH                      1  /* CLKOUT3_OE */
+#define WM8962_CLKOUT5_OE                       0x0001  /* CLKOUT5_OE */
+#define WM8962_CLKOUT5_OE_MASK                  0x0001  /* CLKOUT5_OE */
+#define WM8962_CLKOUT5_OE_SHIFT                      0  /* CLKOUT5_OE */
+#define WM8962_CLKOUT5_OE_WIDTH                      1  /* CLKOUT5_OE */
+
+/*
+ * R127 (0x7F) - PLL Software Reset
+ */
+#define WM8962_SW_RESET_PLL_MASK                0xFFFF  /* SW_RESET_PLL - [15:0] */
+#define WM8962_SW_RESET_PLL_SHIFT                    0  /* SW_RESET_PLL - [15:0] */
+#define WM8962_SW_RESET_PLL_WIDTH                   16  /* SW_RESET_PLL - [15:0] */
+
+/*
+ * R129 (0x81) - PLL2
+ */
+#define WM8962_OSC_ENA                          0x0080  /* OSC_ENA */
+#define WM8962_OSC_ENA_MASK                     0x0080  /* OSC_ENA */
+#define WM8962_OSC_ENA_SHIFT                         7  /* OSC_ENA */
+#define WM8962_OSC_ENA_WIDTH                         1  /* OSC_ENA */
+#define WM8962_PLL2_ENA                         0x0020  /* PLL2_ENA */
+#define WM8962_PLL2_ENA_MASK                    0x0020  /* PLL2_ENA */
+#define WM8962_PLL2_ENA_SHIFT                        5  /* PLL2_ENA */
+#define WM8962_PLL2_ENA_WIDTH                        1  /* PLL2_ENA */
+#define WM8962_PLL3_ENA                         0x0010  /* PLL3_ENA */
+#define WM8962_PLL3_ENA_MASK                    0x0010  /* PLL3_ENA */
+#define WM8962_PLL3_ENA_SHIFT                        4  /* PLL3_ENA */
+#define WM8962_PLL3_ENA_WIDTH                        1  /* PLL3_ENA */
+
+/*
+ * R131 (0x83) - PLL 4
+ */
+#define WM8962_PLL_CLK_SRC                      0x0002  /* PLL_CLK_SRC */
+#define WM8962_PLL_CLK_SRC_MASK                 0x0002  /* PLL_CLK_SRC */
+#define WM8962_PLL_CLK_SRC_SHIFT                     1  /* PLL_CLK_SRC */
+#define WM8962_PLL_CLK_SRC_WIDTH                     1  /* PLL_CLK_SRC */
+#define WM8962_FLL_TO_PLL3                      0x0001  /* FLL_TO_PLL3 */
+#define WM8962_FLL_TO_PLL3_MASK                 0x0001  /* FLL_TO_PLL3 */
+#define WM8962_FLL_TO_PLL3_SHIFT                     0  /* FLL_TO_PLL3 */
+#define WM8962_FLL_TO_PLL3_WIDTH                     1  /* FLL_TO_PLL3 */
+
+/*
+ * R136 (0x88) - PLL 9
+ */
+#define WM8962_PLL2_FRAC                        0x0040  /* PLL2_FRAC */
+#define WM8962_PLL2_FRAC_MASK                   0x0040  /* PLL2_FRAC */
+#define WM8962_PLL2_FRAC_SHIFT                       6  /* PLL2_FRAC */
+#define WM8962_PLL2_FRAC_WIDTH                       1  /* PLL2_FRAC */
+#define WM8962_PLL2_N_MASK                      0x001F  /* PLL2_N - [4:0] */
+#define WM8962_PLL2_N_SHIFT                          0  /* PLL2_N - [4:0] */
+#define WM8962_PLL2_N_WIDTH                          5  /* PLL2_N - [4:0] */
+
+/*
+ * R137 (0x89) - PLL 10
+ */
+#define WM8962_PLL2_K_MASK                      0x00FF  /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_SHIFT                          0  /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_WIDTH                          8  /* PLL2_K - [7:0] */
+
+/*
+ * R138 (0x8A) - PLL 11
+ */
+#define WM8962_PLL2_K_MASK                      0x00FF  /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_SHIFT                          0  /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_WIDTH                          8  /* PLL2_K - [7:0] */
+
+/*
+ * R139 (0x8B) - PLL 12
+ */
+#define WM8962_PLL2_K_MASK                      0x00FF  /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_SHIFT                          0  /* PLL2_K - [7:0] */
+#define WM8962_PLL2_K_WIDTH                          8  /* PLL2_K - [7:0] */
+
+/*
+ * R140 (0x8C) - PLL 13
+ */
+#define WM8962_PLL3_FRAC                        0x0040  /* PLL3_FRAC */
+#define WM8962_PLL3_FRAC_MASK                   0x0040  /* PLL3_FRAC */
+#define WM8962_PLL3_FRAC_SHIFT                       6  /* PLL3_FRAC */
+#define WM8962_PLL3_FRAC_WIDTH                       1  /* PLL3_FRAC */
+#define WM8962_PLL3_N_MASK                      0x001F  /* PLL3_N - [4:0] */
+#define WM8962_PLL3_N_SHIFT                          0  /* PLL3_N - [4:0] */
+#define WM8962_PLL3_N_WIDTH                          5  /* PLL3_N - [4:0] */
+
+/*
+ * R141 (0x8D) - PLL 14
+ */
+#define WM8962_PLL3_K_MASK                      0x00FF  /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_SHIFT                          0  /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_WIDTH                          8  /* PLL3_K - [7:0] */
+
+/*
+ * R142 (0x8E) - PLL 15
+ */
+#define WM8962_PLL3_K_MASK                      0x00FF  /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_SHIFT                          0  /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_WIDTH                          8  /* PLL3_K - [7:0] */
+
+/*
+ * R143 (0x8F) - PLL 16
+ */
+#define WM8962_PLL3_K_MASK                      0x00FF  /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_SHIFT                          0  /* PLL3_K - [7:0] */
+#define WM8962_PLL3_K_WIDTH                          8  /* PLL3_K - [7:0] */
+
+/*
+ * R155 (0x9B) - FLL Control (1)
+ */
+#define WM8962_FLL_REFCLK_SRC_MASK              0x0060  /* FLL_REFCLK_SRC - [6:5] */
+#define WM8962_FLL_REFCLK_SRC_SHIFT                  5  /* FLL_REFCLK_SRC - [6:5] */
+#define WM8962_FLL_REFCLK_SRC_WIDTH                  2  /* FLL_REFCLK_SRC - [6:5] */
+#define WM8962_FLL_FRAC                         0x0004  /* FLL_FRAC */
+#define WM8962_FLL_FRAC_MASK                    0x0004  /* FLL_FRAC */
+#define WM8962_FLL_FRAC_SHIFT                        2  /* FLL_FRAC */
+#define WM8962_FLL_FRAC_WIDTH                        1  /* FLL_FRAC */
+#define WM8962_FLL_OSC_ENA                      0x0002  /* FLL_OSC_ENA */
+#define WM8962_FLL_OSC_ENA_MASK                 0x0002  /* FLL_OSC_ENA */
+#define WM8962_FLL_OSC_ENA_SHIFT                     1  /* FLL_OSC_ENA */
+#define WM8962_FLL_OSC_ENA_WIDTH                     1  /* FLL_OSC_ENA */
+#define WM8962_FLL_ENA                          0x0001  /* FLL_ENA */
+#define WM8962_FLL_ENA_MASK                     0x0001  /* FLL_ENA */
+#define WM8962_FLL_ENA_SHIFT                         0  /* FLL_ENA */
+#define WM8962_FLL_ENA_WIDTH                         1  /* FLL_ENA */
+
+/*
+ * R156 (0x9C) - FLL Control (2)
+ */
+#define WM8962_FLL_OUTDIV_MASK                  0x01F8  /* FLL_OUTDIV - [8:3] */
+#define WM8962_FLL_OUTDIV_SHIFT                      3  /* FLL_OUTDIV - [8:3] */
+#define WM8962_FLL_OUTDIV_WIDTH                      6  /* FLL_OUTDIV - [8:3] */
+#define WM8962_FLL_REFCLK_DIV_MASK              0x0003  /* FLL_REFCLK_DIV - [1:0] */
+#define WM8962_FLL_REFCLK_DIV_SHIFT                  0  /* FLL_REFCLK_DIV - [1:0] */
+#define WM8962_FLL_REFCLK_DIV_WIDTH                  2  /* FLL_REFCLK_DIV - [1:0] */
+
+/*
+ * R157 (0x9D) - FLL Control (3)
+ */
+#define WM8962_FLL_FRATIO_MASK                  0x0007  /* FLL_FRATIO - [2:0] */
+#define WM8962_FLL_FRATIO_SHIFT                      0  /* FLL_FRATIO - [2:0] */
+#define WM8962_FLL_FRATIO_WIDTH                      3  /* FLL_FRATIO - [2:0] */
+
+/*
+ * R159 (0x9F) - FLL Control (5)
+ */
+#define WM8962_FLL_FRC_NCO_VAL_MASK             0x007E  /* FLL_FRC_NCO_VAL - [6:1] */
+#define WM8962_FLL_FRC_NCO_VAL_SHIFT                 1  /* FLL_FRC_NCO_VAL - [6:1] */
+#define WM8962_FLL_FRC_NCO_VAL_WIDTH                 6  /* FLL_FRC_NCO_VAL - [6:1] */
+#define WM8962_FLL_FRC_NCO                      0x0001  /* FLL_FRC_NCO */
+#define WM8962_FLL_FRC_NCO_MASK                 0x0001  /* FLL_FRC_NCO */
+#define WM8962_FLL_FRC_NCO_SHIFT                     0  /* FLL_FRC_NCO */
+#define WM8962_FLL_FRC_NCO_WIDTH                     1  /* FLL_FRC_NCO */
+
+/*
+ * R160 (0xA0) - FLL Control (6)
+ */
+#define WM8962_FLL_THETA_MASK                   0xFFFF  /* FLL_THETA - [15:0] */
+#define WM8962_FLL_THETA_SHIFT                       0  /* FLL_THETA - [15:0] */
+#define WM8962_FLL_THETA_WIDTH                      16  /* FLL_THETA - [15:0] */
+
+/*
+ * R161 (0xA1) - FLL Control (7)
+ */
+#define WM8962_FLL_LAMBDA_MASK                  0xFFFF  /* FLL_LAMBDA - [15:0] */
+#define WM8962_FLL_LAMBDA_SHIFT                      0  /* FLL_LAMBDA - [15:0] */
+#define WM8962_FLL_LAMBDA_WIDTH                     16  /* FLL_LAMBDA - [15:0] */
+
+/*
+ * R162 (0xA2) - FLL Control (8)
+ */
+#define WM8962_FLL_N_MASK                       0x03FF  /* FLL_N - [9:0] */
+#define WM8962_FLL_N_SHIFT                           0  /* FLL_N - [9:0] */
+#define WM8962_FLL_N_WIDTH                          10  /* FLL_N - [9:0] */
+
+/*
+ * R252 (0xFC) - General test 1
+ */
+#define WM8962_REG_SYNC                         0x0004  /* REG_SYNC */
+#define WM8962_REG_SYNC_MASK                    0x0004  /* REG_SYNC */
+#define WM8962_REG_SYNC_SHIFT                        2  /* REG_SYNC */
+#define WM8962_REG_SYNC_WIDTH                        1  /* REG_SYNC */
+#define WM8962_AUTO_INC                         0x0001  /* AUTO_INC */
+#define WM8962_AUTO_INC_MASK                    0x0001  /* AUTO_INC */
+#define WM8962_AUTO_INC_SHIFT                        0  /* AUTO_INC */
+#define WM8962_AUTO_INC_WIDTH                        1  /* AUTO_INC */
+
+/*
+ * R256 (0x100) - DF1
+ */
+#define WM8962_DRC_DF1_ENA                      0x0008  /* DRC_DF1_ENA */
+#define WM8962_DRC_DF1_ENA_MASK                 0x0008  /* DRC_DF1_ENA */
+#define WM8962_DRC_DF1_ENA_SHIFT                     3  /* DRC_DF1_ENA */
+#define WM8962_DRC_DF1_ENA_WIDTH                     1  /* DRC_DF1_ENA */
+#define WM8962_DF1_SHARED_COEFF                 0x0004  /* DF1_SHARED_COEFF */
+#define WM8962_DF1_SHARED_COEFF_MASK            0x0004  /* DF1_SHARED_COEFF */
+#define WM8962_DF1_SHARED_COEFF_SHIFT                2  /* DF1_SHARED_COEFF */
+#define WM8962_DF1_SHARED_COEFF_WIDTH                1  /* DF1_SHARED_COEFF */
+#define WM8962_DF1_SHARED_COEFF_SEL             0x0002  /* DF1_SHARED_COEFF_SEL */
+#define WM8962_DF1_SHARED_COEFF_SEL_MASK        0x0002  /* DF1_SHARED_COEFF_SEL */
+#define WM8962_DF1_SHARED_COEFF_SEL_SHIFT            1  /* DF1_SHARED_COEFF_SEL */
+#define WM8962_DF1_SHARED_COEFF_SEL_WIDTH            1  /* DF1_SHARED_COEFF_SEL */
+#define WM8962_DF1_ENA                          0x0001  /* DF1_ENA */
+#define WM8962_DF1_ENA_MASK                     0x0001  /* DF1_ENA */
+#define WM8962_DF1_ENA_SHIFT                         0  /* DF1_ENA */
+#define WM8962_DF1_ENA_WIDTH                         1  /* DF1_ENA */
+
+/*
+ * R257 (0x101) - DF2
+ */
+#define WM8962_DF1_COEFF_L0_MASK                0xFFFF  /* DF1_COEFF_L0 - [15:0] */
+#define WM8962_DF1_COEFF_L0_SHIFT                    0  /* DF1_COEFF_L0 - [15:0] */
+#define WM8962_DF1_COEFF_L0_WIDTH                   16  /* DF1_COEFF_L0 - [15:0] */
+
+/*
+ * R258 (0x102) - DF3
+ */
+#define WM8962_DF1_COEFF_L1_MASK                0xFFFF  /* DF1_COEFF_L1 - [15:0] */
+#define WM8962_DF1_COEFF_L1_SHIFT                    0  /* DF1_COEFF_L1 - [15:0] */
+#define WM8962_DF1_COEFF_L1_WIDTH                   16  /* DF1_COEFF_L1 - [15:0] */
+
+/*
+ * R259 (0x103) - DF4
+ */
+#define WM8962_DF1_COEFF_L2_MASK                0xFFFF  /* DF1_COEFF_L2 - [15:0] */
+#define WM8962_DF1_COEFF_L2_SHIFT                    0  /* DF1_COEFF_L2 - [15:0] */
+#define WM8962_DF1_COEFF_L2_WIDTH                   16  /* DF1_COEFF_L2 - [15:0] */
+
+/*
+ * R260 (0x104) - DF5
+ */
+#define WM8962_DF1_COEFF_R0_MASK                0xFFFF  /* DF1_COEFF_R0 - [15:0] */
+#define WM8962_DF1_COEFF_R0_SHIFT                    0  /* DF1_COEFF_R0 - [15:0] */
+#define WM8962_DF1_COEFF_R0_WIDTH                   16  /* DF1_COEFF_R0 - [15:0] */
+
+/*
+ * R261 (0x105) - DF6
+ */
+#define WM8962_DF1_COEFF_R1_MASK                0xFFFF  /* DF1_COEFF_R1 - [15:0] */
+#define WM8962_DF1_COEFF_R1_SHIFT                    0  /* DF1_COEFF_R1 - [15:0] */
+#define WM8962_DF1_COEFF_R1_WIDTH                   16  /* DF1_COEFF_R1 - [15:0] */
+
+/*
+ * R262 (0x106) - DF7
+ */
+#define WM8962_DF1_COEFF_R2_MASK                0xFFFF  /* DF1_COEFF_R2 - [15:0] */
+#define WM8962_DF1_COEFF_R2_SHIFT                    0  /* DF1_COEFF_R2 - [15:0] */
+#define WM8962_DF1_COEFF_R2_WIDTH                   16  /* DF1_COEFF_R2 - [15:0] */
+
+/*
+ * R264 (0x108) - LHPF1
+ */
+#define WM8962_LHPF_MODE                        0x0002  /* LHPF_MODE */
+#define WM8962_LHPF_MODE_MASK                   0x0002  /* LHPF_MODE */
+#define WM8962_LHPF_MODE_SHIFT                       1  /* LHPF_MODE */
+#define WM8962_LHPF_MODE_WIDTH                       1  /* LHPF_MODE */
+#define WM8962_LHPF_ENA                         0x0001  /* LHPF_ENA */
+#define WM8962_LHPF_ENA_MASK                    0x0001  /* LHPF_ENA */
+#define WM8962_LHPF_ENA_SHIFT                        0  /* LHPF_ENA */
+#define WM8962_LHPF_ENA_WIDTH                        1  /* LHPF_ENA */
+
+/*
+ * R265 (0x109) - LHPF2
+ */
+#define WM8962_LHPF_COEFF_MASK                  0xFFFF  /* LHPF_COEFF - [15:0] */
+#define WM8962_LHPF_COEFF_SHIFT                      0  /* LHPF_COEFF - [15:0] */
+#define WM8962_LHPF_COEFF_WIDTH                     16  /* LHPF_COEFF - [15:0] */
+
+/*
+ * R268 (0x10C) - THREED1
+ */
+#define WM8962_ADC_MONOMIX                      0x0040  /* ADC_MONOMIX */
+#define WM8962_ADC_MONOMIX_MASK                 0x0040  /* ADC_MONOMIX */
+#define WM8962_ADC_MONOMIX_SHIFT                     6  /* ADC_MONOMIX */
+#define WM8962_ADC_MONOMIX_WIDTH                     1  /* ADC_MONOMIX */
+#define WM8962_THREED_SIGN_L                    0x0020  /* THREED_SIGN_L */
+#define WM8962_THREED_SIGN_L_MASK               0x0020  /* THREED_SIGN_L */
+#define WM8962_THREED_SIGN_L_SHIFT                   5  /* THREED_SIGN_L */
+#define WM8962_THREED_SIGN_L_WIDTH                   1  /* THREED_SIGN_L */
+#define WM8962_THREED_SIGN_R                    0x0010  /* THREED_SIGN_R */
+#define WM8962_THREED_SIGN_R_MASK               0x0010  /* THREED_SIGN_R */
+#define WM8962_THREED_SIGN_R_SHIFT                   4  /* THREED_SIGN_R */
+#define WM8962_THREED_SIGN_R_WIDTH                   1  /* THREED_SIGN_R */
+#define WM8962_THREED_LHPF_MODE                 0x0004  /* THREED_LHPF_MODE */
+#define WM8962_THREED_LHPF_MODE_MASK            0x0004  /* THREED_LHPF_MODE */
+#define WM8962_THREED_LHPF_MODE_SHIFT                2  /* THREED_LHPF_MODE */
+#define WM8962_THREED_LHPF_MODE_WIDTH                1  /* THREED_LHPF_MODE */
+#define WM8962_THREED_LHPF_ENA                  0x0002  /* THREED_LHPF_ENA */
+#define WM8962_THREED_LHPF_ENA_MASK             0x0002  /* THREED_LHPF_ENA */
+#define WM8962_THREED_LHPF_ENA_SHIFT                 1  /* THREED_LHPF_ENA */
+#define WM8962_THREED_LHPF_ENA_WIDTH                 1  /* THREED_LHPF_ENA */
+#define WM8962_THREED_ENA                       0x0001  /* THREED_ENA */
+#define WM8962_THREED_ENA_MASK                  0x0001  /* THREED_ENA */
+#define WM8962_THREED_ENA_SHIFT                      0  /* THREED_ENA */
+#define WM8962_THREED_ENA_WIDTH                      1  /* THREED_ENA */
+
+/*
+ * R269 (0x10D) - THREED2
+ */
+#define WM8962_THREED_FGAINL_MASK               0xF800  /* THREED_FGAINL - [15:11] */
+#define WM8962_THREED_FGAINL_SHIFT                  11  /* THREED_FGAINL - [15:11] */
+#define WM8962_THREED_FGAINL_WIDTH                   5  /* THREED_FGAINL - [15:11] */
+#define WM8962_THREED_CGAINL_MASK               0x07C0  /* THREED_CGAINL - [10:6] */
+#define WM8962_THREED_CGAINL_SHIFT                   6  /* THREED_CGAINL - [10:6] */
+#define WM8962_THREED_CGAINL_WIDTH                   5  /* THREED_CGAINL - [10:6] */
+#define WM8962_THREED_DELAYL_MASK               0x003C  /* THREED_DELAYL - [5:2] */
+#define WM8962_THREED_DELAYL_SHIFT                   2  /* THREED_DELAYL - [5:2] */
+#define WM8962_THREED_DELAYL_WIDTH                   4  /* THREED_DELAYL - [5:2] */
+
+/*
+ * R270 (0x10E) - THREED3
+ */
+#define WM8962_THREED_LHPF_COEFF_MASK           0xFFFF  /* THREED_LHPF_COEFF - [15:0] */
+#define WM8962_THREED_LHPF_COEFF_SHIFT               0  /* THREED_LHPF_COEFF - [15:0] */
+#define WM8962_THREED_LHPF_COEFF_WIDTH              16  /* THREED_LHPF_COEFF - [15:0] */
+
+/*
+ * R271 (0x10F) - THREED4
+ */
+#define WM8962_THREED_FGAINR_MASK               0xF800  /* THREED_FGAINR - [15:11] */
+#define WM8962_THREED_FGAINR_SHIFT                  11  /* THREED_FGAINR - [15:11] */
+#define WM8962_THREED_FGAINR_WIDTH                   5  /* THREED_FGAINR - [15:11] */
+#define WM8962_THREED_CGAINR_MASK               0x07C0  /* THREED_CGAINR - [10:6] */
+#define WM8962_THREED_CGAINR_SHIFT                   6  /* THREED_CGAINR - [10:6] */
+#define WM8962_THREED_CGAINR_WIDTH                   5  /* THREED_CGAINR - [10:6] */
+#define WM8962_THREED_DELAYR_MASK               0x003C  /* THREED_DELAYR - [5:2] */
+#define WM8962_THREED_DELAYR_SHIFT                   2  /* THREED_DELAYR - [5:2] */
+#define WM8962_THREED_DELAYR_WIDTH                   4  /* THREED_DELAYR - [5:2] */
+
+/*
+ * R276 (0x114) - DRC 1
+ */
+#define WM8962_DRC_SIG_DET_RMS_MASK             0x7C00  /* DRC_SIG_DET_RMS - [14:10] */
+#define WM8962_DRC_SIG_DET_RMS_SHIFT                10  /* DRC_SIG_DET_RMS - [14:10] */
+#define WM8962_DRC_SIG_DET_RMS_WIDTH                 5  /* DRC_SIG_DET_RMS - [14:10] */
+#define WM8962_DRC_SIG_DET_PK_MASK              0x0300  /* DRC_SIG_DET_PK - [9:8] */
+#define WM8962_DRC_SIG_DET_PK_SHIFT                  8  /* DRC_SIG_DET_PK - [9:8] */
+#define WM8962_DRC_SIG_DET_PK_WIDTH                  2  /* DRC_SIG_DET_PK - [9:8] */
+#define WM8962_DRC_NG_ENA                       0x0080  /* DRC_NG_ENA */
+#define WM8962_DRC_NG_ENA_MASK                  0x0080  /* DRC_NG_ENA */
+#define WM8962_DRC_NG_ENA_SHIFT                      7  /* DRC_NG_ENA */
+#define WM8962_DRC_NG_ENA_WIDTH                      1  /* DRC_NG_ENA */
+#define WM8962_DRC_SIG_DET_MODE                 0x0040  /* DRC_SIG_DET_MODE */
+#define WM8962_DRC_SIG_DET_MODE_MASK            0x0040  /* DRC_SIG_DET_MODE */
+#define WM8962_DRC_SIG_DET_MODE_SHIFT                6  /* DRC_SIG_DET_MODE */
+#define WM8962_DRC_SIG_DET_MODE_WIDTH                1  /* DRC_SIG_DET_MODE */
+#define WM8962_DRC_SIG_DET                      0x0020  /* DRC_SIG_DET */
+#define WM8962_DRC_SIG_DET_MASK                 0x0020  /* DRC_SIG_DET */
+#define WM8962_DRC_SIG_DET_SHIFT                     5  /* DRC_SIG_DET */
+#define WM8962_DRC_SIG_DET_WIDTH                     1  /* DRC_SIG_DET */
+#define WM8962_DRC_KNEE2_OP_ENA                 0x0010  /* DRC_KNEE2_OP_ENA */
+#define WM8962_DRC_KNEE2_OP_ENA_MASK            0x0010  /* DRC_KNEE2_OP_ENA */
+#define WM8962_DRC_KNEE2_OP_ENA_SHIFT                4  /* DRC_KNEE2_OP_ENA */
+#define WM8962_DRC_KNEE2_OP_ENA_WIDTH                1  /* DRC_KNEE2_OP_ENA */
+#define WM8962_DRC_QR                           0x0008  /* DRC_QR */
+#define WM8962_DRC_QR_MASK                      0x0008  /* DRC_QR */
+#define WM8962_DRC_QR_SHIFT                          3  /* DRC_QR */
+#define WM8962_DRC_QR_WIDTH                          1  /* DRC_QR */
+#define WM8962_DRC_ANTICLIP                     0x0004  /* DRC_ANTICLIP */
+#define WM8962_DRC_ANTICLIP_MASK                0x0004  /* DRC_ANTICLIP */
+#define WM8962_DRC_ANTICLIP_SHIFT                    2  /* DRC_ANTICLIP */
+#define WM8962_DRC_ANTICLIP_WIDTH                    1  /* DRC_ANTICLIP */
+#define WM8962_DRC_MODE                         0x0002  /* DRC_MODE */
+#define WM8962_DRC_MODE_MASK                    0x0002  /* DRC_MODE */
+#define WM8962_DRC_MODE_SHIFT                        1  /* DRC_MODE */
+#define WM8962_DRC_MODE_WIDTH                        1  /* DRC_MODE */
+#define WM8962_DRC_ENA                          0x0001  /* DRC_ENA */
+#define WM8962_DRC_ENA_MASK                     0x0001  /* DRC_ENA */
+#define WM8962_DRC_ENA_SHIFT                         0  /* DRC_ENA */
+#define WM8962_DRC_ENA_WIDTH                         1  /* DRC_ENA */
+
+/*
+ * R277 (0x115) - DRC 2
+ */
+#define WM8962_DRC_ATK_MASK                     0x1E00  /* DRC_ATK - [12:9] */
+#define WM8962_DRC_ATK_SHIFT                         9  /* DRC_ATK - [12:9] */
+#define WM8962_DRC_ATK_WIDTH                         4  /* DRC_ATK - [12:9] */
+#define WM8962_DRC_DCY_MASK                     0x01E0  /* DRC_DCY - [8:5] */
+#define WM8962_DRC_DCY_SHIFT                         5  /* DRC_DCY - [8:5] */
+#define WM8962_DRC_DCY_WIDTH                         4  /* DRC_DCY - [8:5] */
+#define WM8962_DRC_MINGAIN_MASK                 0x001C  /* DRC_MINGAIN - [4:2] */
+#define WM8962_DRC_MINGAIN_SHIFT                     2  /* DRC_MINGAIN - [4:2] */
+#define WM8962_DRC_MINGAIN_WIDTH                     3  /* DRC_MINGAIN - [4:2] */
+#define WM8962_DRC_MAXGAIN_MASK                 0x0003  /* DRC_MAXGAIN - [1:0] */
+#define WM8962_DRC_MAXGAIN_SHIFT                     0  /* DRC_MAXGAIN - [1:0] */
+#define WM8962_DRC_MAXGAIN_WIDTH                     2  /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R278 (0x116) - DRC 3
+ */
+#define WM8962_DRC_NG_MINGAIN_MASK              0xF000  /* DRC_NG_MINGAIN - [15:12] */
+#define WM8962_DRC_NG_MINGAIN_SHIFT                 12  /* DRC_NG_MINGAIN - [15:12] */
+#define WM8962_DRC_NG_MINGAIN_WIDTH                  4  /* DRC_NG_MINGAIN - [15:12] */
+#define WM8962_DRC_QR_THR_MASK                  0x0C00  /* DRC_QR_THR - [11:10] */
+#define WM8962_DRC_QR_THR_SHIFT                     10  /* DRC_QR_THR - [11:10] */
+#define WM8962_DRC_QR_THR_WIDTH                      2  /* DRC_QR_THR - [11:10] */
+#define WM8962_DRC_QR_DCY_MASK                  0x0300  /* DRC_QR_DCY - [9:8] */
+#define WM8962_DRC_QR_DCY_SHIFT                      8  /* DRC_QR_DCY - [9:8] */
+#define WM8962_DRC_QR_DCY_WIDTH                      2  /* DRC_QR_DCY - [9:8] */
+#define WM8962_DRC_NG_EXP_MASK                  0x00C0  /* DRC_NG_EXP - [7:6] */
+#define WM8962_DRC_NG_EXP_SHIFT                      6  /* DRC_NG_EXP - [7:6] */
+#define WM8962_DRC_NG_EXP_WIDTH                      2  /* DRC_NG_EXP - [7:6] */
+#define WM8962_DRC_HI_COMP_MASK                 0x0038  /* DRC_HI_COMP - [5:3] */
+#define WM8962_DRC_HI_COMP_SHIFT                     3  /* DRC_HI_COMP - [5:3] */
+#define WM8962_DRC_HI_COMP_WIDTH                     3  /* DRC_HI_COMP - [5:3] */
+#define WM8962_DRC_LO_COMP_MASK                 0x0007  /* DRC_LO_COMP - [2:0] */
+#define WM8962_DRC_LO_COMP_SHIFT                     0  /* DRC_LO_COMP - [2:0] */
+#define WM8962_DRC_LO_COMP_WIDTH                     3  /* DRC_LO_COMP - [2:0] */
+
+/*
+ * R279 (0x117) - DRC 4
+ */
+#define WM8962_DRC_KNEE_IP_MASK                 0x07E0  /* DRC_KNEE_IP - [10:5] */
+#define WM8962_DRC_KNEE_IP_SHIFT                     5  /* DRC_KNEE_IP - [10:5] */
+#define WM8962_DRC_KNEE_IP_WIDTH                     6  /* DRC_KNEE_IP - [10:5] */
+#define WM8962_DRC_KNEE_OP_MASK                 0x001F  /* DRC_KNEE_OP - [4:0] */
+#define WM8962_DRC_KNEE_OP_SHIFT                     0  /* DRC_KNEE_OP - [4:0] */
+#define WM8962_DRC_KNEE_OP_WIDTH                     5  /* DRC_KNEE_OP - [4:0] */
+
+/*
+ * R280 (0x118) - DRC 5
+ */
+#define WM8962_DRC_KNEE2_IP_MASK                0x03E0  /* DRC_KNEE2_IP - [9:5] */
+#define WM8962_DRC_KNEE2_IP_SHIFT                    5  /* DRC_KNEE2_IP - [9:5] */
+#define WM8962_DRC_KNEE2_IP_WIDTH                    5  /* DRC_KNEE2_IP - [9:5] */
+#define WM8962_DRC_KNEE2_OP_MASK                0x001F  /* DRC_KNEE2_OP - [4:0] */
+#define WM8962_DRC_KNEE2_OP_SHIFT                    0  /* DRC_KNEE2_OP - [4:0] */
+#define WM8962_DRC_KNEE2_OP_WIDTH                    5  /* DRC_KNEE2_OP - [4:0] */
+
+/*
+ * R285 (0x11D) - Tloopback
+ */
+#define WM8962_TLB_ENA                          0x0002  /* TLB_ENA */
+#define WM8962_TLB_ENA_MASK                     0x0002  /* TLB_ENA */
+#define WM8962_TLB_ENA_SHIFT                         1  /* TLB_ENA */
+#define WM8962_TLB_ENA_WIDTH                         1  /* TLB_ENA */
+#define WM8962_TLB_MODE                         0x0001  /* TLB_MODE */
+#define WM8962_TLB_MODE_MASK                    0x0001  /* TLB_MODE */
+#define WM8962_TLB_MODE_SHIFT                        0  /* TLB_MODE */
+#define WM8962_TLB_MODE_WIDTH                        1  /* TLB_MODE */
+
+/*
+ * R335 (0x14F) - EQ1
+ */
+#define WM8962_EQ_SHARED_COEFF                  0x0004  /* EQ_SHARED_COEFF */
+#define WM8962_EQ_SHARED_COEFF_MASK             0x0004  /* EQ_SHARED_COEFF */
+#define WM8962_EQ_SHARED_COEFF_SHIFT                 2  /* EQ_SHARED_COEFF */
+#define WM8962_EQ_SHARED_COEFF_WIDTH                 1  /* EQ_SHARED_COEFF */
+#define WM8962_EQ_SHARED_COEFF_SEL              0x0002  /* EQ_SHARED_COEFF_SEL */
+#define WM8962_EQ_SHARED_COEFF_SEL_MASK         0x0002  /* EQ_SHARED_COEFF_SEL */
+#define WM8962_EQ_SHARED_COEFF_SEL_SHIFT             1  /* EQ_SHARED_COEFF_SEL */
+#define WM8962_EQ_SHARED_COEFF_SEL_WIDTH             1  /* EQ_SHARED_COEFF_SEL */
+#define WM8962_EQ_ENA                           0x0001  /* EQ_ENA */
+#define WM8962_EQ_ENA_MASK                      0x0001  /* EQ_ENA */
+#define WM8962_EQ_ENA_SHIFT                          0  /* EQ_ENA */
+#define WM8962_EQ_ENA_WIDTH                          1  /* EQ_ENA */
+
+/*
+ * R336 (0x150) - EQ2
+ */
+#define WM8962_EQL_B1_GAIN_MASK                 0xF800  /* EQL_B1_GAIN - [15:11] */
+#define WM8962_EQL_B1_GAIN_SHIFT                    11  /* EQL_B1_GAIN - [15:11] */
+#define WM8962_EQL_B1_GAIN_WIDTH                     5  /* EQL_B1_GAIN - [15:11] */
+#define WM8962_EQL_B2_GAIN_MASK                 0x07C0  /* EQL_B2_GAIN - [10:6] */
+#define WM8962_EQL_B2_GAIN_SHIFT                     6  /* EQL_B2_GAIN - [10:6] */
+#define WM8962_EQL_B2_GAIN_WIDTH                     5  /* EQL_B2_GAIN - [10:6] */
+#define WM8962_EQL_B3_GAIN_MASK                 0x003E  /* EQL_B3_GAIN - [5:1] */
+#define WM8962_EQL_B3_GAIN_SHIFT                     1  /* EQL_B3_GAIN - [5:1] */
+#define WM8962_EQL_B3_GAIN_WIDTH                     5  /* EQL_B3_GAIN - [5:1] */
+
+/*
+ * R337 (0x151) - EQ3
+ */
+#define WM8962_EQL_B4_GAIN_MASK                 0xF800  /* EQL_B4_GAIN - [15:11] */
+#define WM8962_EQL_B4_GAIN_SHIFT                    11  /* EQL_B4_GAIN - [15:11] */
+#define WM8962_EQL_B4_GAIN_WIDTH                     5  /* EQL_B4_GAIN - [15:11] */
+#define WM8962_EQL_B5_GAIN_MASK                 0x07C0  /* EQL_B5_GAIN - [10:6] */
+#define WM8962_EQL_B5_GAIN_SHIFT                     6  /* EQL_B5_GAIN - [10:6] */
+#define WM8962_EQL_B5_GAIN_WIDTH                     5  /* EQL_B5_GAIN - [10:6] */
+
+/*
+ * R338 (0x152) - EQ4
+ */
+#define WM8962_EQL_B1_A_MASK                    0xFFFF  /* EQL_B1_A - [15:0] */
+#define WM8962_EQL_B1_A_SHIFT                        0  /* EQL_B1_A - [15:0] */
+#define WM8962_EQL_B1_A_WIDTH                       16  /* EQL_B1_A - [15:0] */
+
+/*
+ * R339 (0x153) - EQ5
+ */
+#define WM8962_EQL_B1_B_MASK                    0xFFFF  /* EQL_B1_B - [15:0] */
+#define WM8962_EQL_B1_B_SHIFT                        0  /* EQL_B1_B - [15:0] */
+#define WM8962_EQL_B1_B_WIDTH                       16  /* EQL_B1_B - [15:0] */
+
+/*
+ * R340 (0x154) - EQ6
+ */
+#define WM8962_EQL_B1_PG_MASK                   0xFFFF  /* EQL_B1_PG - [15:0] */
+#define WM8962_EQL_B1_PG_SHIFT                       0  /* EQL_B1_PG - [15:0] */
+#define WM8962_EQL_B1_PG_WIDTH                      16  /* EQL_B1_PG - [15:0] */
+
+/*
+ * R341 (0x155) - EQ7
+ */
+#define WM8962_EQL_B2_A_MASK                    0xFFFF  /* EQL_B2_A - [15:0] */
+#define WM8962_EQL_B2_A_SHIFT                        0  /* EQL_B2_A - [15:0] */
+#define WM8962_EQL_B2_A_WIDTH                       16  /* EQL_B2_A - [15:0] */
+
+/*
+ * R342 (0x156) - EQ8
+ */
+#define WM8962_EQL_B2_B_MASK                    0xFFFF  /* EQL_B2_B - [15:0] */
+#define WM8962_EQL_B2_B_SHIFT                        0  /* EQL_B2_B - [15:0] */
+#define WM8962_EQL_B2_B_WIDTH                       16  /* EQL_B2_B - [15:0] */
+
+/*
+ * R343 (0x157) - EQ9
+ */
+#define WM8962_EQL_B2_C_MASK                    0xFFFF  /* EQL_B2_C - [15:0] */
+#define WM8962_EQL_B2_C_SHIFT                        0  /* EQL_B2_C - [15:0] */
+#define WM8962_EQL_B2_C_WIDTH                       16  /* EQL_B2_C - [15:0] */
+
+/*
+ * R344 (0x158) - EQ10
+ */
+#define WM8962_EQL_B2_PG_MASK                   0xFFFF  /* EQL_B2_PG - [15:0] */
+#define WM8962_EQL_B2_PG_SHIFT                       0  /* EQL_B2_PG - [15:0] */
+#define WM8962_EQL_B2_PG_WIDTH                      16  /* EQL_B2_PG - [15:0] */
+
+/*
+ * R345 (0x159) - EQ11
+ */
+#define WM8962_EQL_B3_A_MASK                    0xFFFF  /* EQL_B3_A - [15:0] */
+#define WM8962_EQL_B3_A_SHIFT                        0  /* EQL_B3_A - [15:0] */
+#define WM8962_EQL_B3_A_WIDTH                       16  /* EQL_B3_A - [15:0] */
+
+/*
+ * R346 (0x15A) - EQ12
+ */
+#define WM8962_EQL_B3_B_MASK                    0xFFFF  /* EQL_B3_B - [15:0] */
+#define WM8962_EQL_B3_B_SHIFT                        0  /* EQL_B3_B - [15:0] */
+#define WM8962_EQL_B3_B_WIDTH                       16  /* EQL_B3_B - [15:0] */
+
+/*
+ * R347 (0x15B) - EQ13
+ */
+#define WM8962_EQL_B3_C_MASK                    0xFFFF  /* EQL_B3_C - [15:0] */
+#define WM8962_EQL_B3_C_SHIFT                        0  /* EQL_B3_C - [15:0] */
+#define WM8962_EQL_B3_C_WIDTH                       16  /* EQL_B3_C - [15:0] */
+
+/*
+ * R348 (0x15C) - EQ14
+ */
+#define WM8962_EQL_B3_PG_MASK                   0xFFFF  /* EQL_B3_PG - [15:0] */
+#define WM8962_EQL_B3_PG_SHIFT                       0  /* EQL_B3_PG - [15:0] */
+#define WM8962_EQL_B3_PG_WIDTH                      16  /* EQL_B3_PG - [15:0] */
+
+/*
+ * R349 (0x15D) - EQ15
+ */
+#define WM8962_EQL_B4_A_MASK                    0xFFFF  /* EQL_B4_A - [15:0] */
+#define WM8962_EQL_B4_A_SHIFT                        0  /* EQL_B4_A - [15:0] */
+#define WM8962_EQL_B4_A_WIDTH                       16  /* EQL_B4_A - [15:0] */
+
+/*
+ * R350 (0x15E) - EQ16
+ */
+#define WM8962_EQL_B4_B_MASK                    0xFFFF  /* EQL_B4_B - [15:0] */
+#define WM8962_EQL_B4_B_SHIFT                        0  /* EQL_B4_B - [15:0] */
+#define WM8962_EQL_B4_B_WIDTH                       16  /* EQL_B4_B - [15:0] */
+
+/*
+ * R351 (0x15F) - EQ17
+ */
+#define WM8962_EQL_B4_C_MASK                    0xFFFF  /* EQL_B4_C - [15:0] */
+#define WM8962_EQL_B4_C_SHIFT                        0  /* EQL_B4_C - [15:0] */
+#define WM8962_EQL_B4_C_WIDTH                       16  /* EQL_B4_C - [15:0] */
+
+/*
+ * R352 (0x160) - EQ18
+ */
+#define WM8962_EQL_B4_PG_MASK                   0xFFFF  /* EQL_B4_PG - [15:0] */
+#define WM8962_EQL_B4_PG_SHIFT                       0  /* EQL_B4_PG - [15:0] */
+#define WM8962_EQL_B4_PG_WIDTH                      16  /* EQL_B4_PG - [15:0] */
+
+/*
+ * R353 (0x161) - EQ19
+ */
+#define WM8962_EQL_B5_A_MASK                    0xFFFF  /* EQL_B5_A - [15:0] */
+#define WM8962_EQL_B5_A_SHIFT                        0  /* EQL_B5_A - [15:0] */
+#define WM8962_EQL_B5_A_WIDTH                       16  /* EQL_B5_A - [15:0] */
+
+/*
+ * R354 (0x162) - EQ20
+ */
+#define WM8962_EQL_B5_B_MASK                    0xFFFF  /* EQL_B5_B - [15:0] */
+#define WM8962_EQL_B5_B_SHIFT                        0  /* EQL_B5_B - [15:0] */
+#define WM8962_EQL_B5_B_WIDTH                       16  /* EQL_B5_B - [15:0] */
+
+/*
+ * R355 (0x163) - EQ21
+ */
+#define WM8962_EQL_B5_PG_MASK                   0xFFFF  /* EQL_B5_PG - [15:0] */
+#define WM8962_EQL_B5_PG_SHIFT                       0  /* EQL_B5_PG - [15:0] */
+#define WM8962_EQL_B5_PG_WIDTH                      16  /* EQL_B5_PG - [15:0] */
+
+/*
+ * R356 (0x164) - EQ22
+ */
+#define WM8962_EQR_B1_GAIN_MASK                 0xF800  /* EQR_B1_GAIN - [15:11] */
+#define WM8962_EQR_B1_GAIN_SHIFT                    11  /* EQR_B1_GAIN - [15:11] */
+#define WM8962_EQR_B1_GAIN_WIDTH                     5  /* EQR_B1_GAIN - [15:11] */
+#define WM8962_EQR_B2_GAIN_MASK                 0x07C0  /* EQR_B2_GAIN - [10:6] */
+#define WM8962_EQR_B2_GAIN_SHIFT                     6  /* EQR_B2_GAIN - [10:6] */
+#define WM8962_EQR_B2_GAIN_WIDTH                     5  /* EQR_B2_GAIN - [10:6] */
+#define WM8962_EQR_B3_GAIN_MASK                 0x003E  /* EQR_B3_GAIN - [5:1] */
+#define WM8962_EQR_B3_GAIN_SHIFT                     1  /* EQR_B3_GAIN - [5:1] */
+#define WM8962_EQR_B3_GAIN_WIDTH                     5  /* EQR_B3_GAIN - [5:1] */
+
+/*
+ * R357 (0x165) - EQ23
+ */
+#define WM8962_EQR_B4_GAIN_MASK                 0xF800  /* EQR_B4_GAIN - [15:11] */
+#define WM8962_EQR_B4_GAIN_SHIFT                    11  /* EQR_B4_GAIN - [15:11] */
+#define WM8962_EQR_B4_GAIN_WIDTH                     5  /* EQR_B4_GAIN - [15:11] */
+#define WM8962_EQR_B5_GAIN_MASK                 0x07C0  /* EQR_B5_GAIN - [10:6] */
+#define WM8962_EQR_B5_GAIN_SHIFT                     6  /* EQR_B5_GAIN - [10:6] */
+#define WM8962_EQR_B5_GAIN_WIDTH                     5  /* EQR_B5_GAIN - [10:6] */
+
+/*
+ * R358 (0x166) - EQ24
+ */
+#define WM8962_EQR_B1_A_MASK                    0xFFFF  /* EQR_B1_A - [15:0] */
+#define WM8962_EQR_B1_A_SHIFT                        0  /* EQR_B1_A - [15:0] */
+#define WM8962_EQR_B1_A_WIDTH                       16  /* EQR_B1_A - [15:0] */
+
+/*
+ * R359 (0x167) - EQ25
+ */
+#define WM8962_EQR_B1_B_MASK                    0xFFFF  /* EQR_B1_B - [15:0] */
+#define WM8962_EQR_B1_B_SHIFT                        0  /* EQR_B1_B - [15:0] */
+#define WM8962_EQR_B1_B_WIDTH                       16  /* EQR_B1_B - [15:0] */
+
+/*
+ * R360 (0x168) - EQ26
+ */
+#define WM8962_EQR_B1_PG_MASK                   0xFFFF  /* EQR_B1_PG - [15:0] */
+#define WM8962_EQR_B1_PG_SHIFT                       0  /* EQR_B1_PG - [15:0] */
+#define WM8962_EQR_B1_PG_WIDTH                      16  /* EQR_B1_PG - [15:0] */
+
+/*
+ * R361 (0x169) - EQ27
+ */
+#define WM8962_EQR_B2_A_MASK                    0xFFFF  /* EQR_B2_A - [15:0] */
+#define WM8962_EQR_B2_A_SHIFT                        0  /* EQR_B2_A - [15:0] */
+#define WM8962_EQR_B2_A_WIDTH                       16  /* EQR_B2_A - [15:0] */
+
+/*
+ * R362 (0x16A) - EQ28
+ */
+#define WM8962_EQR_B2_B_MASK                    0xFFFF  /* EQR_B2_B - [15:0] */
+#define WM8962_EQR_B2_B_SHIFT                        0  /* EQR_B2_B - [15:0] */
+#define WM8962_EQR_B2_B_WIDTH                       16  /* EQR_B2_B - [15:0] */
+
+/*
+ * R363 (0x16B) - EQ29
+ */
+#define WM8962_EQR_B2_C_MASK                    0xFFFF  /* EQR_B2_C - [15:0] */
+#define WM8962_EQR_B2_C_SHIFT                        0  /* EQR_B2_C - [15:0] */
+#define WM8962_EQR_B2_C_WIDTH                       16  /* EQR_B2_C - [15:0] */
+
+/*
+ * R364 (0x16C) - EQ30
+ */
+#define WM8962_EQR_B2_PG_MASK                   0xFFFF  /* EQR_B2_PG - [15:0] */
+#define WM8962_EQR_B2_PG_SHIFT                       0  /* EQR_B2_PG - [15:0] */
+#define WM8962_EQR_B2_PG_WIDTH                      16  /* EQR_B2_PG - [15:0] */
+
+/*
+ * R365 (0x16D) - EQ31
+ */
+#define WM8962_EQR_B3_A_MASK                    0xFFFF  /* EQR_B3_A - [15:0] */
+#define WM8962_EQR_B3_A_SHIFT                        0  /* EQR_B3_A - [15:0] */
+#define WM8962_EQR_B3_A_WIDTH                       16  /* EQR_B3_A - [15:0] */
+
+/*
+ * R366 (0x16E) - EQ32
+ */
+#define WM8962_EQR_B3_B_MASK                    0xFFFF  /* EQR_B3_B - [15:0] */
+#define WM8962_EQR_B3_B_SHIFT                        0  /* EQR_B3_B - [15:0] */
+#define WM8962_EQR_B3_B_WIDTH                       16  /* EQR_B3_B - [15:0] */
+
+/*
+ * R367 (0x16F) - EQ33
+ */
+#define WM8962_EQR_B3_C_MASK                    0xFFFF  /* EQR_B3_C - [15:0] */
+#define WM8962_EQR_B3_C_SHIFT                        0  /* EQR_B3_C - [15:0] */
+#define WM8962_EQR_B3_C_WIDTH                       16  /* EQR_B3_C - [15:0] */
+
+/*
+ * R368 (0x170) - EQ34
+ */
+#define WM8962_EQR_B3_PG_MASK                   0xFFFF  /* EQR_B3_PG - [15:0] */
+#define WM8962_EQR_B3_PG_SHIFT                       0  /* EQR_B3_PG - [15:0] */
+#define WM8962_EQR_B3_PG_WIDTH                      16  /* EQR_B3_PG - [15:0] */
+
+/*
+ * R369 (0x171) - EQ35
+ */
+#define WM8962_EQR_B4_A_MASK                    0xFFFF  /* EQR_B4_A - [15:0] */
+#define WM8962_EQR_B4_A_SHIFT                        0  /* EQR_B4_A - [15:0] */
+#define WM8962_EQR_B4_A_WIDTH                       16  /* EQR_B4_A - [15:0] */
+
+/*
+ * R370 (0x172) - EQ36
+ */
+#define WM8962_EQR_B4_B_MASK                    0xFFFF  /* EQR_B4_B - [15:0] */
+#define WM8962_EQR_B4_B_SHIFT                        0  /* EQR_B4_B - [15:0] */
+#define WM8962_EQR_B4_B_WIDTH                       16  /* EQR_B4_B - [15:0] */
+
+/*
+ * R371 (0x173) - EQ37
+ */
+#define WM8962_EQR_B4_C_MASK                    0xFFFF  /* EQR_B4_C - [15:0] */
+#define WM8962_EQR_B4_C_SHIFT                        0  /* EQR_B4_C - [15:0] */
+#define WM8962_EQR_B4_C_WIDTH                       16  /* EQR_B4_C - [15:0] */
+
+/*
+ * R372 (0x174) - EQ38
+ */
+#define WM8962_EQR_B4_PG_MASK                   0xFFFF  /* EQR_B4_PG - [15:0] */
+#define WM8962_EQR_B4_PG_SHIFT                       0  /* EQR_B4_PG - [15:0] */
+#define WM8962_EQR_B4_PG_WIDTH                      16  /* EQR_B4_PG - [15:0] */
+
+/*
+ * R373 (0x175) - EQ39
+ */
+#define WM8962_EQR_B5_A_MASK                    0xFFFF  /* EQR_B5_A - [15:0] */
+#define WM8962_EQR_B5_A_SHIFT                        0  /* EQR_B5_A - [15:0] */
+#define WM8962_EQR_B5_A_WIDTH                       16  /* EQR_B5_A - [15:0] */
+
+/*
+ * R374 (0x176) - EQ40
+ */
+#define WM8962_EQR_B5_B_MASK                    0xFFFF  /* EQR_B5_B - [15:0] */
+#define WM8962_EQR_B5_B_SHIFT                        0  /* EQR_B5_B - [15:0] */
+#define WM8962_EQR_B5_B_WIDTH                       16  /* EQR_B5_B - [15:0] */
+
+/*
+ * R375 (0x177) - EQ41
+ */
+#define WM8962_EQR_B5_PG_MASK                   0xFFFF  /* EQR_B5_PG - [15:0] */
+#define WM8962_EQR_B5_PG_SHIFT                       0  /* EQR_B5_PG - [15:0] */
+#define WM8962_EQR_B5_PG_WIDTH                      16  /* EQR_B5_PG - [15:0] */
+
+/*
+ * R513 (0x201) - GPIO 2
+ */
+#define WM8962_GP2_POL                          0x0400  /* GP2_POL */
+#define WM8962_GP2_POL_MASK                     0x0400  /* GP2_POL */
+#define WM8962_GP2_POL_SHIFT                        10  /* GP2_POL */
+#define WM8962_GP2_POL_WIDTH                         1  /* GP2_POL */
+#define WM8962_GP2_LVL                          0x0040  /* GP2_LVL */
+#define WM8962_GP2_LVL_MASK                     0x0040  /* GP2_LVL */
+#define WM8962_GP2_LVL_SHIFT                         6  /* GP2_LVL */
+#define WM8962_GP2_LVL_WIDTH                         1  /* GP2_LVL */
+#define WM8962_GP2_FN_MASK                      0x001F  /* GP2_FN - [4:0] */
+#define WM8962_GP2_FN_SHIFT                          0  /* GP2_FN - [4:0] */
+#define WM8962_GP2_FN_WIDTH                          5  /* GP2_FN - [4:0] */
+
+/*
+ * R514 (0x202) - GPIO 3
+ */
+#define WM8962_GP3_POL                          0x0400  /* GP3_POL */
+#define WM8962_GP3_POL_MASK                     0x0400  /* GP3_POL */
+#define WM8962_GP3_POL_SHIFT                        10  /* GP3_POL */
+#define WM8962_GP3_POL_WIDTH                         1  /* GP3_POL */
+#define WM8962_GP3_LVL                          0x0040  /* GP3_LVL */
+#define WM8962_GP3_LVL_MASK                     0x0040  /* GP3_LVL */
+#define WM8962_GP3_LVL_SHIFT                         6  /* GP3_LVL */
+#define WM8962_GP3_LVL_WIDTH                         1  /* GP3_LVL */
+#define WM8962_GP3_FN_MASK                      0x001F  /* GP3_FN - [4:0] */
+#define WM8962_GP3_FN_SHIFT                          0  /* GP3_FN - [4:0] */
+#define WM8962_GP3_FN_WIDTH                          5  /* GP3_FN - [4:0] */
+
+/*
+ * R516 (0x204) - GPIO 5
+ */
+#define WM8962_GP5_DIR                          0x8000  /* GP5_DIR */
+#define WM8962_GP5_DIR_MASK                     0x8000  /* GP5_DIR */
+#define WM8962_GP5_DIR_SHIFT                        15  /* GP5_DIR */
+#define WM8962_GP5_DIR_WIDTH                         1  /* GP5_DIR */
+#define WM8962_GP5_PU                           0x4000  /* GP5_PU */
+#define WM8962_GP5_PU_MASK                      0x4000  /* GP5_PU */
+#define WM8962_GP5_PU_SHIFT                         14  /* GP5_PU */
+#define WM8962_GP5_PU_WIDTH                          1  /* GP5_PU */
+#define WM8962_GP5_PD                           0x2000  /* GP5_PD */
+#define WM8962_GP5_PD_MASK                      0x2000  /* GP5_PD */
+#define WM8962_GP5_PD_SHIFT                         13  /* GP5_PD */
+#define WM8962_GP5_PD_WIDTH                          1  /* GP5_PD */
+#define WM8962_GP5_POL                          0x0400  /* GP5_POL */
+#define WM8962_GP5_POL_MASK                     0x0400  /* GP5_POL */
+#define WM8962_GP5_POL_SHIFT                        10  /* GP5_POL */
+#define WM8962_GP5_POL_WIDTH                         1  /* GP5_POL */
+#define WM8962_GP5_OP_CFG                       0x0200  /* GP5_OP_CFG */
+#define WM8962_GP5_OP_CFG_MASK                  0x0200  /* GP5_OP_CFG */
+#define WM8962_GP5_OP_CFG_SHIFT                      9  /* GP5_OP_CFG */
+#define WM8962_GP5_OP_CFG_WIDTH                      1  /* GP5_OP_CFG */
+#define WM8962_GP5_DB                           0x0100  /* GP5_DB */
+#define WM8962_GP5_DB_MASK                      0x0100  /* GP5_DB */
+#define WM8962_GP5_DB_SHIFT                          8  /* GP5_DB */
+#define WM8962_GP5_DB_WIDTH                          1  /* GP5_DB */
+#define WM8962_GP5_LVL                          0x0040  /* GP5_LVL */
+#define WM8962_GP5_LVL_MASK                     0x0040  /* GP5_LVL */
+#define WM8962_GP5_LVL_SHIFT                         6  /* GP5_LVL */
+#define WM8962_GP5_LVL_WIDTH                         1  /* GP5_LVL */
+#define WM8962_GP5_FN_MASK                      0x001F  /* GP5_FN - [4:0] */
+#define WM8962_GP5_FN_SHIFT                          0  /* GP5_FN - [4:0] */
+#define WM8962_GP5_FN_WIDTH                          5  /* GP5_FN - [4:0] */
+
+/*
+ * R517 (0x205) - GPIO 6
+ */
+#define WM8962_GP6_DIR                          0x8000  /* GP6_DIR */
+#define WM8962_GP6_DIR_MASK                     0x8000  /* GP6_DIR */
+#define WM8962_GP6_DIR_SHIFT                        15  /* GP6_DIR */
+#define WM8962_GP6_DIR_WIDTH                         1  /* GP6_DIR */
+#define WM8962_GP6_PU                           0x4000  /* GP6_PU */
+#define WM8962_GP6_PU_MASK                      0x4000  /* GP6_PU */
+#define WM8962_GP6_PU_SHIFT                         14  /* GP6_PU */
+#define WM8962_GP6_PU_WIDTH                          1  /* GP6_PU */
+#define WM8962_GP6_PD                           0x2000  /* GP6_PD */
+#define WM8962_GP6_PD_MASK                      0x2000  /* GP6_PD */
+#define WM8962_GP6_PD_SHIFT                         13  /* GP6_PD */
+#define WM8962_GP6_PD_WIDTH                          1  /* GP6_PD */
+#define WM8962_GP6_POL                          0x0400  /* GP6_POL */
+#define WM8962_GP6_POL_MASK                     0x0400  /* GP6_POL */
+#define WM8962_GP6_POL_SHIFT                        10  /* GP6_POL */
+#define WM8962_GP6_POL_WIDTH                         1  /* GP6_POL */
+#define WM8962_GP6_OP_CFG                       0x0200  /* GP6_OP_CFG */
+#define WM8962_GP6_OP_CFG_MASK                  0x0200  /* GP6_OP_CFG */
+#define WM8962_GP6_OP_CFG_SHIFT                      9  /* GP6_OP_CFG */
+#define WM8962_GP6_OP_CFG_WIDTH                      1  /* GP6_OP_CFG */
+#define WM8962_GP6_DB                           0x0100  /* GP6_DB */
+#define WM8962_GP6_DB_MASK                      0x0100  /* GP6_DB */
+#define WM8962_GP6_DB_SHIFT                          8  /* GP6_DB */
+#define WM8962_GP6_DB_WIDTH                          1  /* GP6_DB */
+#define WM8962_GP6_LVL                          0x0040  /* GP6_LVL */
+#define WM8962_GP6_LVL_MASK                     0x0040  /* GP6_LVL */
+#define WM8962_GP6_LVL_SHIFT                         6  /* GP6_LVL */
+#define WM8962_GP6_LVL_WIDTH                         1  /* GP6_LVL */
+#define WM8962_GP6_FN_MASK                      0x001F  /* GP6_FN - [4:0] */
+#define WM8962_GP6_FN_SHIFT                          0  /* GP6_FN - [4:0] */
+#define WM8962_GP6_FN_WIDTH                          5  /* GP6_FN - [4:0] */
+
+/*
+ * R560 (0x230) - Interrupt Status 1
+ */
+#define WM8962_GP6_EINT                         0x0020  /* GP6_EINT */
+#define WM8962_GP6_EINT_MASK                    0x0020  /* GP6_EINT */
+#define WM8962_GP6_EINT_SHIFT                        5  /* GP6_EINT */
+#define WM8962_GP6_EINT_WIDTH                        1  /* GP6_EINT */
+#define WM8962_GP5_EINT                         0x0010  /* GP5_EINT */
+#define WM8962_GP5_EINT_MASK                    0x0010  /* GP5_EINT */
+#define WM8962_GP5_EINT_SHIFT                        4  /* GP5_EINT */
+#define WM8962_GP5_EINT_WIDTH                        1  /* GP5_EINT */
+
+/*
+ * R561 (0x231) - Interrupt Status 2
+ */
+#define WM8962_MICSCD_EINT                      0x8000  /* MICSCD_EINT */
+#define WM8962_MICSCD_EINT_MASK                 0x8000  /* MICSCD_EINT */
+#define WM8962_MICSCD_EINT_SHIFT                    15  /* MICSCD_EINT */
+#define WM8962_MICSCD_EINT_WIDTH                     1  /* MICSCD_EINT */
+#define WM8962_MICD_EINT                        0x4000  /* MICD_EINT */
+#define WM8962_MICD_EINT_MASK                   0x4000  /* MICD_EINT */
+#define WM8962_MICD_EINT_SHIFT                      14  /* MICD_EINT */
+#define WM8962_MICD_EINT_WIDTH                       1  /* MICD_EINT */
+#define WM8962_FIFOS_ERR_EINT                   0x2000  /* FIFOS_ERR_EINT */
+#define WM8962_FIFOS_ERR_EINT_MASK              0x2000  /* FIFOS_ERR_EINT */
+#define WM8962_FIFOS_ERR_EINT_SHIFT                 13  /* FIFOS_ERR_EINT */
+#define WM8962_FIFOS_ERR_EINT_WIDTH                  1  /* FIFOS_ERR_EINT */
+#define WM8962_ALC_LOCK_EINT                    0x1000  /* ALC_LOCK_EINT */
+#define WM8962_ALC_LOCK_EINT_MASK               0x1000  /* ALC_LOCK_EINT */
+#define WM8962_ALC_LOCK_EINT_SHIFT                  12  /* ALC_LOCK_EINT */
+#define WM8962_ALC_LOCK_EINT_WIDTH                   1  /* ALC_LOCK_EINT */
+#define WM8962_ALC_THRESH_EINT                  0x0800  /* ALC_THRESH_EINT */
+#define WM8962_ALC_THRESH_EINT_MASK             0x0800  /* ALC_THRESH_EINT */
+#define WM8962_ALC_THRESH_EINT_SHIFT                11  /* ALC_THRESH_EINT */
+#define WM8962_ALC_THRESH_EINT_WIDTH                 1  /* ALC_THRESH_EINT */
+#define WM8962_ALC_SAT_EINT                     0x0400  /* ALC_SAT_EINT */
+#define WM8962_ALC_SAT_EINT_MASK                0x0400  /* ALC_SAT_EINT */
+#define WM8962_ALC_SAT_EINT_SHIFT                   10  /* ALC_SAT_EINT */
+#define WM8962_ALC_SAT_EINT_WIDTH                    1  /* ALC_SAT_EINT */
+#define WM8962_ALC_PKOVR_EINT                   0x0200  /* ALC_PKOVR_EINT */
+#define WM8962_ALC_PKOVR_EINT_MASK              0x0200  /* ALC_PKOVR_EINT */
+#define WM8962_ALC_PKOVR_EINT_SHIFT                  9  /* ALC_PKOVR_EINT */
+#define WM8962_ALC_PKOVR_EINT_WIDTH                  1  /* ALC_PKOVR_EINT */
+#define WM8962_ALC_NGATE_EINT                   0x0100  /* ALC_NGATE_EINT */
+#define WM8962_ALC_NGATE_EINT_MASK              0x0100  /* ALC_NGATE_EINT */
+#define WM8962_ALC_NGATE_EINT_SHIFT                  8  /* ALC_NGATE_EINT */
+#define WM8962_ALC_NGATE_EINT_WIDTH                  1  /* ALC_NGATE_EINT */
+#define WM8962_WSEQ_DONE_EINT                   0x0080  /* WSEQ_DONE_EINT */
+#define WM8962_WSEQ_DONE_EINT_MASK              0x0080  /* WSEQ_DONE_EINT */
+#define WM8962_WSEQ_DONE_EINT_SHIFT                  7  /* WSEQ_DONE_EINT */
+#define WM8962_WSEQ_DONE_EINT_WIDTH                  1  /* WSEQ_DONE_EINT */
+#define WM8962_DRC_ACTDET_EINT                  0x0040  /* DRC_ACTDET_EINT */
+#define WM8962_DRC_ACTDET_EINT_MASK             0x0040  /* DRC_ACTDET_EINT */
+#define WM8962_DRC_ACTDET_EINT_SHIFT                 6  /* DRC_ACTDET_EINT */
+#define WM8962_DRC_ACTDET_EINT_WIDTH                 1  /* DRC_ACTDET_EINT */
+#define WM8962_FLL_LOCK_EINT                    0x0020  /* FLL_LOCK_EINT */
+#define WM8962_FLL_LOCK_EINT_MASK               0x0020  /* FLL_LOCK_EINT */
+#define WM8962_FLL_LOCK_EINT_SHIFT                   5  /* FLL_LOCK_EINT */
+#define WM8962_FLL_LOCK_EINT_WIDTH                   1  /* FLL_LOCK_EINT */
+#define WM8962_PLL3_LOCK_EINT                   0x0008  /* PLL3_LOCK_EINT */
+#define WM8962_PLL3_LOCK_EINT_MASK              0x0008  /* PLL3_LOCK_EINT */
+#define WM8962_PLL3_LOCK_EINT_SHIFT                  3  /* PLL3_LOCK_EINT */
+#define WM8962_PLL3_LOCK_EINT_WIDTH                  1  /* PLL3_LOCK_EINT */
+#define WM8962_PLL2_LOCK_EINT                   0x0004  /* PLL2_LOCK_EINT */
+#define WM8962_PLL2_LOCK_EINT_MASK              0x0004  /* PLL2_LOCK_EINT */
+#define WM8962_PLL2_LOCK_EINT_SHIFT                  2  /* PLL2_LOCK_EINT */
+#define WM8962_PLL2_LOCK_EINT_WIDTH                  1  /* PLL2_LOCK_EINT */
+#define WM8962_TEMP_SHUT_EINT                   0x0001  /* TEMP_SHUT_EINT */
+#define WM8962_TEMP_SHUT_EINT_MASK              0x0001  /* TEMP_SHUT_EINT */
+#define WM8962_TEMP_SHUT_EINT_SHIFT                  0  /* TEMP_SHUT_EINT */
+#define WM8962_TEMP_SHUT_EINT_WIDTH                  1  /* TEMP_SHUT_EINT */
+
+/*
+ * R568 (0x238) - Interrupt Status 1 Mask
+ */
+#define WM8962_IM_GP6_EINT                      0x0020  /* IM_GP6_EINT */
+#define WM8962_IM_GP6_EINT_MASK                 0x0020  /* IM_GP6_EINT */
+#define WM8962_IM_GP6_EINT_SHIFT                     5  /* IM_GP6_EINT */
+#define WM8962_IM_GP6_EINT_WIDTH                     1  /* IM_GP6_EINT */
+#define WM8962_IM_GP5_EINT                      0x0010  /* IM_GP5_EINT */
+#define WM8962_IM_GP5_EINT_MASK                 0x0010  /* IM_GP5_EINT */
+#define WM8962_IM_GP5_EINT_SHIFT                     4  /* IM_GP5_EINT */
+#define WM8962_IM_GP5_EINT_WIDTH                     1  /* IM_GP5_EINT */
+
+/*
+ * R569 (0x239) - Interrupt Status 2 Mask
+ */
+#define WM8962_IM_MICSCD_EINT                   0x8000  /* IM_MICSCD_EINT */
+#define WM8962_IM_MICSCD_EINT_MASK              0x8000  /* IM_MICSCD_EINT */
+#define WM8962_IM_MICSCD_EINT_SHIFT                 15  /* IM_MICSCD_EINT */
+#define WM8962_IM_MICSCD_EINT_WIDTH                  1  /* IM_MICSCD_EINT */
+#define WM8962_IM_MICD_EINT                     0x4000  /* IM_MICD_EINT */
+#define WM8962_IM_MICD_EINT_MASK                0x4000  /* IM_MICD_EINT */
+#define WM8962_IM_MICD_EINT_SHIFT                   14  /* IM_MICD_EINT */
+#define WM8962_IM_MICD_EINT_WIDTH                    1  /* IM_MICD_EINT */
+#define WM8962_IM_FIFOS_ERR_EINT                0x2000  /* IM_FIFOS_ERR_EINT */
+#define WM8962_IM_FIFOS_ERR_EINT_MASK           0x2000  /* IM_FIFOS_ERR_EINT */
+#define WM8962_IM_FIFOS_ERR_EINT_SHIFT              13  /* IM_FIFOS_ERR_EINT */
+#define WM8962_IM_FIFOS_ERR_EINT_WIDTH               1  /* IM_FIFOS_ERR_EINT */
+#define WM8962_IM_ALC_LOCK_EINT                 0x1000  /* IM_ALC_LOCK_EINT */
+#define WM8962_IM_ALC_LOCK_EINT_MASK            0x1000  /* IM_ALC_LOCK_EINT */
+#define WM8962_IM_ALC_LOCK_EINT_SHIFT               12  /* IM_ALC_LOCK_EINT */
+#define WM8962_IM_ALC_LOCK_EINT_WIDTH                1  /* IM_ALC_LOCK_EINT */
+#define WM8962_IM_ALC_THRESH_EINT               0x0800  /* IM_ALC_THRESH_EINT */
+#define WM8962_IM_ALC_THRESH_EINT_MASK          0x0800  /* IM_ALC_THRESH_EINT */
+#define WM8962_IM_ALC_THRESH_EINT_SHIFT             11  /* IM_ALC_THRESH_EINT */
+#define WM8962_IM_ALC_THRESH_EINT_WIDTH              1  /* IM_ALC_THRESH_EINT */
+#define WM8962_IM_ALC_SAT_EINT                  0x0400  /* IM_ALC_SAT_EINT */
+#define WM8962_IM_ALC_SAT_EINT_MASK             0x0400  /* IM_ALC_SAT_EINT */
+#define WM8962_IM_ALC_SAT_EINT_SHIFT                10  /* IM_ALC_SAT_EINT */
+#define WM8962_IM_ALC_SAT_EINT_WIDTH                 1  /* IM_ALC_SAT_EINT */
+#define WM8962_IM_ALC_PKOVR_EINT                0x0200  /* IM_ALC_PKOVR_EINT */
+#define WM8962_IM_ALC_PKOVR_EINT_MASK           0x0200  /* IM_ALC_PKOVR_EINT */
+#define WM8962_IM_ALC_PKOVR_EINT_SHIFT               9  /* IM_ALC_PKOVR_EINT */
+#define WM8962_IM_ALC_PKOVR_EINT_WIDTH               1  /* IM_ALC_PKOVR_EINT */
+#define WM8962_IM_ALC_NGATE_EINT                0x0100  /* IM_ALC_NGATE_EINT */
+#define WM8962_IM_ALC_NGATE_EINT_MASK           0x0100  /* IM_ALC_NGATE_EINT */
+#define WM8962_IM_ALC_NGATE_EINT_SHIFT               8  /* IM_ALC_NGATE_EINT */
+#define WM8962_IM_ALC_NGATE_EINT_WIDTH               1  /* IM_ALC_NGATE_EINT */
+#define WM8962_IM_WSEQ_DONE_EINT                0x0080  /* IM_WSEQ_DONE_EINT */
+#define WM8962_IM_WSEQ_DONE_EINT_MASK           0x0080  /* IM_WSEQ_DONE_EINT */
+#define WM8962_IM_WSEQ_DONE_EINT_SHIFT               7  /* IM_WSEQ_DONE_EINT */
+#define WM8962_IM_WSEQ_DONE_EINT_WIDTH               1  /* IM_WSEQ_DONE_EINT */
+#define WM8962_IM_DRC_ACTDET_EINT               0x0040  /* IM_DRC_ACTDET_EINT */
+#define WM8962_IM_DRC_ACTDET_EINT_MASK          0x0040  /* IM_DRC_ACTDET_EINT */
+#define WM8962_IM_DRC_ACTDET_EINT_SHIFT              6  /* IM_DRC_ACTDET_EINT */
+#define WM8962_IM_DRC_ACTDET_EINT_WIDTH              1  /* IM_DRC_ACTDET_EINT */
+#define WM8962_IM_FLL_LOCK_EINT                 0x0020  /* IM_FLL_LOCK_EINT */
+#define WM8962_IM_FLL_LOCK_EINT_MASK            0x0020  /* IM_FLL_LOCK_EINT */
+#define WM8962_IM_FLL_LOCK_EINT_SHIFT                5  /* IM_FLL_LOCK_EINT */
+#define WM8962_IM_FLL_LOCK_EINT_WIDTH                1  /* IM_FLL_LOCK_EINT */
+#define WM8962_IM_PLL3_LOCK_EINT                0x0008  /* IM_PLL3_LOCK_EINT */
+#define WM8962_IM_PLL3_LOCK_EINT_MASK           0x0008  /* IM_PLL3_LOCK_EINT */
+#define WM8962_IM_PLL3_LOCK_EINT_SHIFT               3  /* IM_PLL3_LOCK_EINT */
+#define WM8962_IM_PLL3_LOCK_EINT_WIDTH               1  /* IM_PLL3_LOCK_EINT */
+#define WM8962_IM_PLL2_LOCK_EINT                0x0004  /* IM_PLL2_LOCK_EINT */
+#define WM8962_IM_PLL2_LOCK_EINT_MASK           0x0004  /* IM_PLL2_LOCK_EINT */
+#define WM8962_IM_PLL2_LOCK_EINT_SHIFT               2  /* IM_PLL2_LOCK_EINT */
+#define WM8962_IM_PLL2_LOCK_EINT_WIDTH               1  /* IM_PLL2_LOCK_EINT */
+#define WM8962_IM_TEMP_SHUT_EINT                0x0001  /* IM_TEMP_SHUT_EINT */
+#define WM8962_IM_TEMP_SHUT_EINT_MASK           0x0001  /* IM_TEMP_SHUT_EINT */
+#define WM8962_IM_TEMP_SHUT_EINT_SHIFT               0  /* IM_TEMP_SHUT_EINT */
+#define WM8962_IM_TEMP_SHUT_EINT_WIDTH               1  /* IM_TEMP_SHUT_EINT */
+
+/*
+ * R576 (0x240) - Interrupt Control
+ */
+#define WM8962_IRQ_POL                          0x0001  /* IRQ_POL */
+#define WM8962_IRQ_POL_MASK                     0x0001  /* IRQ_POL */
+#define WM8962_IRQ_POL_SHIFT                         0  /* IRQ_POL */
+#define WM8962_IRQ_POL_WIDTH                         1  /* IRQ_POL */
+
+/*
+ * R584 (0x248) - IRQ Debounce
+ */
+#define WM8962_FLL_LOCK_DB                      0x0020  /* FLL_LOCK_DB */
+#define WM8962_FLL_LOCK_DB_MASK                 0x0020  /* FLL_LOCK_DB */
+#define WM8962_FLL_LOCK_DB_SHIFT                     5  /* FLL_LOCK_DB */
+#define WM8962_FLL_LOCK_DB_WIDTH                     1  /* FLL_LOCK_DB */
+#define WM8962_PLL3_LOCK_DB                     0x0008  /* PLL3_LOCK_DB */
+#define WM8962_PLL3_LOCK_DB_MASK                0x0008  /* PLL3_LOCK_DB */
+#define WM8962_PLL3_LOCK_DB_SHIFT                    3  /* PLL3_LOCK_DB */
+#define WM8962_PLL3_LOCK_DB_WIDTH                    1  /* PLL3_LOCK_DB */
+#define WM8962_PLL2_LOCK_DB                     0x0004  /* PLL2_LOCK_DB */
+#define WM8962_PLL2_LOCK_DB_MASK                0x0004  /* PLL2_LOCK_DB */
+#define WM8962_PLL2_LOCK_DB_SHIFT                    2  /* PLL2_LOCK_DB */
+#define WM8962_PLL2_LOCK_DB_WIDTH                    1  /* PLL2_LOCK_DB */
+#define WM8962_TEMP_SHUT_DB                     0x0001  /* TEMP_SHUT_DB */
+#define WM8962_TEMP_SHUT_DB_MASK                0x0001  /* TEMP_SHUT_DB */
+#define WM8962_TEMP_SHUT_DB_SHIFT                    0  /* TEMP_SHUT_DB */
+#define WM8962_TEMP_SHUT_DB_WIDTH                    1  /* TEMP_SHUT_DB */
+
+/*
+ * R586 (0x24A) -  MICINT Source Pol
+ */
+#define WM8962_MICSCD_IRQ_POL                   0x8000  /* MICSCD_IRQ_POL */
+#define WM8962_MICSCD_IRQ_POL_MASK              0x8000  /* MICSCD_IRQ_POL */
+#define WM8962_MICSCD_IRQ_POL_SHIFT                 15  /* MICSCD_IRQ_POL */
+#define WM8962_MICSCD_IRQ_POL_WIDTH                  1  /* MICSCD_IRQ_POL */
+#define WM8962_MICD_IRQ_POL                     0x4000  /* MICD_IRQ_POL */
+#define WM8962_MICD_IRQ_POL_MASK                0x4000  /* MICD_IRQ_POL */
+#define WM8962_MICD_IRQ_POL_SHIFT                   14  /* MICD_IRQ_POL */
+#define WM8962_MICD_IRQ_POL_WIDTH                    1  /* MICD_IRQ_POL */
+
+/*
+ * R768 (0x300) - DSP2 Power Management
+ */
+#define WM8962_DSP2_ENA                         0x0001  /* DSP2_ENA */
+#define WM8962_DSP2_ENA_MASK                    0x0001  /* DSP2_ENA */
+#define WM8962_DSP2_ENA_SHIFT                        0  /* DSP2_ENA */
+#define WM8962_DSP2_ENA_WIDTH                        1  /* DSP2_ENA */
+
+/*
+ * R1037 (0x40D) - DSP2_ExecControl
+ */
+#define WM8962_DSP2_STOPC                       0x0020  /* DSP2_STOPC */
+#define WM8962_DSP2_STOPC_MASK                  0x0020  /* DSP2_STOPC */
+#define WM8962_DSP2_STOPC_SHIFT                      5  /* DSP2_STOPC */
+#define WM8962_DSP2_STOPC_WIDTH                      1  /* DSP2_STOPC */
+#define WM8962_DSP2_STOPS                       0x0010  /* DSP2_STOPS */
+#define WM8962_DSP2_STOPS_MASK                  0x0010  /* DSP2_STOPS */
+#define WM8962_DSP2_STOPS_SHIFT                      4  /* DSP2_STOPS */
+#define WM8962_DSP2_STOPS_WIDTH                      1  /* DSP2_STOPS */
+#define WM8962_DSP2_STOPI                       0x0008  /* DSP2_STOPI */
+#define WM8962_DSP2_STOPI_MASK                  0x0008  /* DSP2_STOPI */
+#define WM8962_DSP2_STOPI_SHIFT                      3  /* DSP2_STOPI */
+#define WM8962_DSP2_STOPI_WIDTH                      1  /* DSP2_STOPI */
+#define WM8962_DSP2_STOP                        0x0004  /* DSP2_STOP */
+#define WM8962_DSP2_STOP_MASK                   0x0004  /* DSP2_STOP */
+#define WM8962_DSP2_STOP_SHIFT                       2  /* DSP2_STOP */
+#define WM8962_DSP2_STOP_WIDTH                       1  /* DSP2_STOP */
+#define WM8962_DSP2_RUNR                        0x0002  /* DSP2_RUNR */
+#define WM8962_DSP2_RUNR_MASK                   0x0002  /* DSP2_RUNR */
+#define WM8962_DSP2_RUNR_SHIFT                       1  /* DSP2_RUNR */
+#define WM8962_DSP2_RUNR_WIDTH                       1  /* DSP2_RUNR */
+#define WM8962_DSP2_RUN                         0x0001  /* DSP2_RUN */
+#define WM8962_DSP2_RUN_MASK                    0x0001  /* DSP2_RUN */
+#define WM8962_DSP2_RUN_SHIFT                        0  /* DSP2_RUN */
+#define WM8962_DSP2_RUN_WIDTH                        1  /* DSP2_RUN */
+
+/*
+ * R8192 (0x2000) - DSP2 Instruction RAM 0
+ */
+#define WM8962_DSP2_INSTR_RAM_1024_10_9_0_MASK  0x03FF  /* DSP2_INSTR_RAM_1024_10_9_0 - [9:0] */
+#define WM8962_DSP2_INSTR_RAM_1024_10_9_0_SHIFT      0  /* DSP2_INSTR_RAM_1024_10_9_0 - [9:0] */
+#define WM8962_DSP2_INSTR_RAM_1024_10_9_0_WIDTH     10  /* DSP2_INSTR_RAM_1024_10_9_0 - [9:0] */
+
+/*
+ * R9216 (0x2400) - DSP2 Address RAM 2
+ */
+#define WM8962_DSP2_ADDR_RAM_1024_38_37_32_MASK 0x003F  /* DSP2_ADDR_RAM_1024_38_37_32 - [5:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_37_32_SHIFT      0  /* DSP2_ADDR_RAM_1024_38_37_32 - [5:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_37_32_WIDTH      6  /* DSP2_ADDR_RAM_1024_38_37_32 - [5:0] */
+
+/*
+ * R9217 (0x2401) - DSP2 Address RAM 1
+ */
+#define WM8962_DSP2_ADDR_RAM_1024_38_31_16_MASK 0xFFFF  /* DSP2_ADDR_RAM_1024_38_31_16 - [15:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_31_16_SHIFT      0  /* DSP2_ADDR_RAM_1024_38_31_16 - [15:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_31_16_WIDTH     16  /* DSP2_ADDR_RAM_1024_38_31_16 - [15:0] */
+
+/*
+ * R9218 (0x2402) - DSP2 Address RAM 0
+ */
+#define WM8962_DSP2_ADDR_RAM_1024_38_15_0_MASK  0xFFFF  /* DSP2_ADDR_RAM_1024_38_15_0 - [15:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_15_0_SHIFT      0  /* DSP2_ADDR_RAM_1024_38_15_0 - [15:0] */
+#define WM8962_DSP2_ADDR_RAM_1024_38_15_0_WIDTH     16  /* DSP2_ADDR_RAM_1024_38_15_0 - [15:0] */
+
+/*
+ * R12288 (0x3000) - DSP2 Data1 RAM 1
+ */
+#define WM8962_DSP2_DATA1_RAM_384_24_23_16_MASK 0x00FF  /* DSP2_DATA1_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA1_RAM_384_24_23_16_SHIFT      0  /* DSP2_DATA1_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA1_RAM_384_24_23_16_WIDTH      8  /* DSP2_DATA1_RAM_384_24_23_16 - [7:0] */
+
+/*
+ * R12289 (0x3001) - DSP2 Data1 RAM 0
+ */
+#define WM8962_DSP2_DATA1_RAM_384_24_15_0_MASK  0xFFFF  /* DSP2_DATA1_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA1_RAM_384_24_15_0_SHIFT      0  /* DSP2_DATA1_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA1_RAM_384_24_15_0_WIDTH     16  /* DSP2_DATA1_RAM_384_24_15_0 - [15:0] */
+
+/*
+ * R13312 (0x3400) - DSP2 Data2 RAM 1
+ */
+#define WM8962_DSP2_DATA2_RAM_384_24_23_16_MASK 0x00FF  /* DSP2_DATA2_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA2_RAM_384_24_23_16_SHIFT      0  /* DSP2_DATA2_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA2_RAM_384_24_23_16_WIDTH      8  /* DSP2_DATA2_RAM_384_24_23_16 - [7:0] */
+
+/*
+ * R13313 (0x3401) - DSP2 Data2 RAM 0
+ */
+#define WM8962_DSP2_DATA2_RAM_384_24_15_0_MASK  0xFFFF  /* DSP2_DATA2_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA2_RAM_384_24_15_0_SHIFT      0  /* DSP2_DATA2_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA2_RAM_384_24_15_0_WIDTH     16  /* DSP2_DATA2_RAM_384_24_15_0 - [15:0] */
+
+/*
+ * R14336 (0x3800) - DSP2 Data3 RAM 1
+ */
+#define WM8962_DSP2_DATA3_RAM_384_24_23_16_MASK 0x00FF  /* DSP2_DATA3_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA3_RAM_384_24_23_16_SHIFT      0  /* DSP2_DATA3_RAM_384_24_23_16 - [7:0] */
+#define WM8962_DSP2_DATA3_RAM_384_24_23_16_WIDTH      8  /* DSP2_DATA3_RAM_384_24_23_16 - [7:0] */
+
+/*
+ * R14337 (0x3801) - DSP2 Data3 RAM 0
+ */
+#define WM8962_DSP2_DATA3_RAM_384_24_15_0_MASK  0xFFFF  /* DSP2_DATA3_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA3_RAM_384_24_15_0_SHIFT      0  /* DSP2_DATA3_RAM_384_24_15_0 - [15:0] */
+#define WM8962_DSP2_DATA3_RAM_384_24_15_0_WIDTH     16  /* DSP2_DATA3_RAM_384_24_15_0 - [15:0] */
+
+/*
+ * R15360 (0x3C00) - DSP2 Coeff RAM 0
+ */
+#define WM8962_DSP2_CMAP_RAM_384_11_10_0_MASK   0x07FF  /* DSP2_CMAP_RAM_384_11_10_0 - [10:0] */
+#define WM8962_DSP2_CMAP_RAM_384_11_10_0_SHIFT       0  /* DSP2_CMAP_RAM_384_11_10_0 - [10:0] */
+#define WM8962_DSP2_CMAP_RAM_384_11_10_0_WIDTH      11  /* DSP2_CMAP_RAM_384_11_10_0 - [10:0] */
+
+/*
+ * R16384 (0x4000) - RETUNEADC_SHARED_COEFF_1
+ */
+#define WM8962_ADC_RETUNE_SCV                   0x0080  /* ADC_RETUNE_SCV */
+#define WM8962_ADC_RETUNE_SCV_MASK              0x0080  /* ADC_RETUNE_SCV */
+#define WM8962_ADC_RETUNE_SCV_SHIFT                  7  /* ADC_RETUNE_SCV */
+#define WM8962_ADC_RETUNE_SCV_WIDTH                  1  /* ADC_RETUNE_SCV */
+#define WM8962_RETUNEADC_SHARED_COEFF_22_16_MASK 0x007F  /* RETUNEADC_SHARED_COEFF_22_16 - [6:0] */
+#define WM8962_RETUNEADC_SHARED_COEFF_22_16_SHIFT      0  /* RETUNEADC_SHARED_COEFF_22_16 - [6:0] */
+#define WM8962_RETUNEADC_SHARED_COEFF_22_16_WIDTH      7  /* RETUNEADC_SHARED_COEFF_22_16 - [6:0] */
+
+/*
+ * R16385 (0x4001) - RETUNEADC_SHARED_COEFF_0
+ */
+#define WM8962_RETUNEADC_SHARED_COEFF_15_00_MASK 0xFFFF  /* RETUNEADC_SHARED_COEFF_15_00 - [15:0] */
+#define WM8962_RETUNEADC_SHARED_COEFF_15_00_SHIFT      0  /* RETUNEADC_SHARED_COEFF_15_00 - [15:0] */
+#define WM8962_RETUNEADC_SHARED_COEFF_15_00_WIDTH     16  /* RETUNEADC_SHARED_COEFF_15_00 - [15:0] */
+
+/*
+ * R16386 (0x4002) - RETUNEDAC_SHARED_COEFF_1
+ */
+#define WM8962_DAC_RETUNE_SCV                   0x0080  /* DAC_RETUNE_SCV */
+#define WM8962_DAC_RETUNE_SCV_MASK              0x0080  /* DAC_RETUNE_SCV */
+#define WM8962_DAC_RETUNE_SCV_SHIFT                  7  /* DAC_RETUNE_SCV */
+#define WM8962_DAC_RETUNE_SCV_WIDTH                  1  /* DAC_RETUNE_SCV */
+#define WM8962_RETUNEDAC_SHARED_COEFF_23_16_MASK 0x007F  /* RETUNEDAC_SHARED_COEFF_23_16 - [6:0] */
+#define WM8962_RETUNEDAC_SHARED_COEFF_23_16_SHIFT      0  /* RETUNEDAC_SHARED_COEFF_23_16 - [6:0] */
+#define WM8962_RETUNEDAC_SHARED_COEFF_23_16_WIDTH      7  /* RETUNEDAC_SHARED_COEFF_23_16 - [6:0] */
+
+/*
+ * R16387 (0x4003) - RETUNEDAC_SHARED_COEFF_0
+ */
+#define WM8962_RETUNEDAC_SHARED_COEFF_15_00_MASK 0xFFFF  /* RETUNEDAC_SHARED_COEFF_15_00 - [15:0] */
+#define WM8962_RETUNEDAC_SHARED_COEFF_15_00_SHIFT      0  /* RETUNEDAC_SHARED_COEFF_15_00 - [15:0] */
+#define WM8962_RETUNEDAC_SHARED_COEFF_15_00_WIDTH     16  /* RETUNEDAC_SHARED_COEFF_15_00 - [15:0] */
+
+/*
+ * R16388 (0x4004) - SOUNDSTAGE_ENABLES_1
+ */
+#define WM8962_SOUNDSTAGE_ENABLES_23_16_MASK    0x00FF  /* SOUNDSTAGE_ENABLES_23_16 - [7:0] */
+#define WM8962_SOUNDSTAGE_ENABLES_23_16_SHIFT        0  /* SOUNDSTAGE_ENABLES_23_16 - [7:0] */
+#define WM8962_SOUNDSTAGE_ENABLES_23_16_WIDTH        8  /* SOUNDSTAGE_ENABLES_23_16 - [7:0] */
+
+/*
+ * R16389 (0x4005) - SOUNDSTAGE_ENABLES_0
+ */
+#define WM8962_SOUNDSTAGE_ENABLES_15_06_MASK    0xFFC0  /* SOUNDSTAGE_ENABLES_15_06 - [15:6] */
+#define WM8962_SOUNDSTAGE_ENABLES_15_06_SHIFT        6  /* SOUNDSTAGE_ENABLES_15_06 - [15:6] */
+#define WM8962_SOUNDSTAGE_ENABLES_15_06_WIDTH       10  /* SOUNDSTAGE_ENABLES_15_06 - [15:6] */
+#define WM8962_RTN_ADC_ENA                      0x0020  /* RTN_ADC_ENA */
+#define WM8962_RTN_ADC_ENA_MASK                 0x0020  /* RTN_ADC_ENA */
+#define WM8962_RTN_ADC_ENA_SHIFT                     5  /* RTN_ADC_ENA */
+#define WM8962_RTN_ADC_ENA_WIDTH                     1  /* RTN_ADC_ENA */
+#define WM8962_RTN_DAC_ENA                      0x0010  /* RTN_DAC_ENA */
+#define WM8962_RTN_DAC_ENA_MASK                 0x0010  /* RTN_DAC_ENA */
+#define WM8962_RTN_DAC_ENA_SHIFT                     4  /* RTN_DAC_ENA */
+#define WM8962_RTN_DAC_ENA_WIDTH                     1  /* RTN_DAC_ENA */
+#define WM8962_HDBASS_ENA                       0x0008  /* HDBASS_ENA */
+#define WM8962_HDBASS_ENA_MASK                  0x0008  /* HDBASS_ENA */
+#define WM8962_HDBASS_ENA_SHIFT                      3  /* HDBASS_ENA */
+#define WM8962_HDBASS_ENA_WIDTH                      1  /* HDBASS_ENA */
+#define WM8962_HPF2_ENA                         0x0004  /* HPF2_ENA */
+#define WM8962_HPF2_ENA_MASK                    0x0004  /* HPF2_ENA */
+#define WM8962_HPF2_ENA_SHIFT                        2  /* HPF2_ENA */
+#define WM8962_HPF2_ENA_WIDTH                        1  /* HPF2_ENA */
+#define WM8962_HPF1_ENA                         0x0002  /* HPF1_ENA */
+#define WM8962_HPF1_ENA_MASK                    0x0002  /* HPF1_ENA */
+#define WM8962_HPF1_ENA_SHIFT                        1  /* HPF1_ENA */
+#define WM8962_HPF1_ENA_WIDTH                        1  /* HPF1_ENA */
+#define WM8962_VSS_ENA                          0x0001  /* VSS_ENA */
+#define WM8962_VSS_ENA_MASK                     0x0001  /* VSS_ENA */
+#define WM8962_VSS_ENA_SHIFT                         0  /* VSS_ENA */
+#define WM8962_VSS_ENA_WIDTH                         1  /* VSS_ENA */
+
+int wm8962_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack);
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8971.c b/linux/sound/soc/codecs/wm8971.c
new file mode 100644
index 0000000..9f18db6
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8971.c
@@ -0,0 +1,779 @@
+/*
+ * wm8971.c  --  WM8971 ALSA SoC Audio driver
+ *
+ * Copyright 2005 Lab126, Inc.
+ *
+ * Author: Kenneth Kiraly <kiraly@lab126.com>
+ *
+ * Based on wm8753.c by Liam Girdwood
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "wm8971.h"
+
+#define	WM8971_REG_COUNT		43
+
+static struct workqueue_struct *wm8971_workq = NULL;
+
+/* codec private data */
+struct wm8971_priv {
+	enum snd_soc_control_type control_type;
+	unsigned int sysclk;
+};
+
+/*
+ * wm8971 register cache
+ * We can't read the WM8971 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8971_reg[] = {
+	0x0097, 0x0097, 0x0079, 0x0079,  /*  0 */
+	0x0000, 0x0008, 0x0000, 0x000a,  /*  4 */
+	0x0000, 0x0000, 0x00ff, 0x00ff,  /*  8 */
+	0x000f, 0x000f, 0x0000, 0x0000,  /* 12 */
+	0x0000, 0x007b, 0x0000, 0x0032,  /* 16 */
+	0x0000, 0x00c3, 0x00c3, 0x00c0,  /* 20 */
+	0x0000, 0x0000, 0x0000, 0x0000,  /* 24 */
+	0x0000, 0x0000, 0x0000, 0x0000,  /* 28 */
+	0x0000, 0x0000, 0x0050, 0x0050,  /* 32 */
+	0x0050, 0x0050, 0x0050, 0x0050,  /* 36 */
+	0x0079, 0x0079, 0x0079,          /* 40 */
+};
+
+#define wm8971_reset(c)	snd_soc_write(c, WM8971_RESET, 0)
+
+/* WM8971 Controls */
+static const char *wm8971_bass[] = { "Linear Control", "Adaptive Boost" };
+static const char *wm8971_bass_filter[] = { "130Hz @ 48kHz",
+	"200Hz @ 48kHz" };
+static const char *wm8971_treble[] = { "8kHz", "4kHz" };
+static const char *wm8971_alc_func[] = { "Off", "Right", "Left", "Stereo" };
+static const char *wm8971_ng_type[] = { "Constant PGA Gain",
+	"Mute ADC Output" };
+static const char *wm8971_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8971_mono_mux[] = {"Stereo", "Mono (Left)",
+	"Mono (Right)", "Digital Mono"};
+static const char *wm8971_dac_phase[] = { "Non Inverted", "Inverted" };
+static const char *wm8971_lline_mux[] = {"Line", "NC", "NC", "PGA",
+	"Differential"};
+static const char *wm8971_rline_mux[] = {"Line", "Mic", "NC", "PGA",
+	"Differential"};
+static const char *wm8971_lpga_sel[] = {"Line", "NC", "NC", "Differential"};
+static const char *wm8971_rpga_sel[] = {"Line", "Mic", "NC", "Differential"};
+static const char *wm8971_adcpol[] = {"Normal", "L Invert", "R Invert",
+	"L + R Invert"};
+
+static const struct soc_enum wm8971_enum[] = {
+	SOC_ENUM_SINGLE(WM8971_BASS, 7, 2, wm8971_bass),	/* 0 */
+	SOC_ENUM_SINGLE(WM8971_BASS, 6, 2, wm8971_bass_filter),
+	SOC_ENUM_SINGLE(WM8971_TREBLE, 6, 2, wm8971_treble),
+	SOC_ENUM_SINGLE(WM8971_ALC1, 7, 4, wm8971_alc_func),
+	SOC_ENUM_SINGLE(WM8971_NGATE, 1, 2, wm8971_ng_type),    /* 4 */
+	SOC_ENUM_SINGLE(WM8971_ADCDAC, 1, 4, wm8971_deemp),
+	SOC_ENUM_SINGLE(WM8971_ADCTL1, 4, 4, wm8971_mono_mux),
+	SOC_ENUM_SINGLE(WM8971_ADCTL1, 1, 2, wm8971_dac_phase),
+	SOC_ENUM_SINGLE(WM8971_LOUTM1, 0, 5, wm8971_lline_mux), /* 8 */
+	SOC_ENUM_SINGLE(WM8971_ROUTM1, 0, 5, wm8971_rline_mux),
+	SOC_ENUM_SINGLE(WM8971_LADCIN, 6, 4, wm8971_lpga_sel),
+	SOC_ENUM_SINGLE(WM8971_RADCIN, 6, 4, wm8971_rpga_sel),
+	SOC_ENUM_SINGLE(WM8971_ADCDAC, 5, 4, wm8971_adcpol),    /* 12 */
+	SOC_ENUM_SINGLE(WM8971_ADCIN, 6, 4, wm8971_mono_mux),
+};
+
+static const struct snd_kcontrol_new wm8971_snd_controls[] = {
+	SOC_DOUBLE_R("Capture Volume", WM8971_LINVOL, WM8971_RINVOL, 0, 63, 0),
+	SOC_DOUBLE_R("Capture ZC Switch", WM8971_LINVOL, WM8971_RINVOL,
+		     6, 1, 0),
+	SOC_DOUBLE_R("Capture Switch", WM8971_LINVOL, WM8971_RINVOL, 7, 1, 1),
+
+	SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8971_LOUT1V,
+		WM8971_ROUT1V, 7, 1, 0),
+	SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8971_LOUT2V,
+		WM8971_ROUT2V, 7, 1, 0),
+	SOC_SINGLE("Mono Playback ZC Switch", WM8971_MOUTV, 7, 1, 0),
+
+	SOC_DOUBLE_R("PCM Volume", WM8971_LDAC, WM8971_RDAC, 0, 255, 0),
+
+	SOC_DOUBLE_R("Bypass Left Playback Volume", WM8971_LOUTM1,
+		WM8971_LOUTM2, 4, 7, 1),
+	SOC_DOUBLE_R("Bypass Right Playback Volume", WM8971_ROUTM1,
+		WM8971_ROUTM2, 4, 7, 1),
+	SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8971_MOUTM1,
+		WM8971_MOUTM2, 4, 7, 1),
+
+	SOC_DOUBLE_R("Headphone Playback Volume", WM8971_LOUT1V,
+		WM8971_ROUT1V, 0, 127, 0),
+	SOC_DOUBLE_R("Speaker Playback Volume", WM8971_LOUT2V,
+		WM8971_ROUT2V, 0, 127, 0),
+
+	SOC_ENUM("Bass Boost", wm8971_enum[0]),
+	SOC_ENUM("Bass Filter", wm8971_enum[1]),
+	SOC_SINGLE("Bass Volume", WM8971_BASS, 0, 7, 1),
+
+	SOC_SINGLE("Treble Volume", WM8971_TREBLE, 0, 7, 0),
+	SOC_ENUM("Treble Cut-off", wm8971_enum[2]),
+
+	SOC_SINGLE("Capture Filter Switch", WM8971_ADCDAC, 0, 1, 1),
+
+	SOC_SINGLE("ALC Target Volume", WM8971_ALC1, 0, 7, 0),
+	SOC_SINGLE("ALC Max Volume", WM8971_ALC1, 4, 7, 0),
+
+	SOC_SINGLE("ALC Capture Target Volume", WM8971_ALC1, 0, 7, 0),
+	SOC_SINGLE("ALC Capture Max Volume", WM8971_ALC1, 4, 7, 0),
+	SOC_ENUM("ALC Capture Function", wm8971_enum[3]),
+	SOC_SINGLE("ALC Capture ZC Switch", WM8971_ALC2, 7, 1, 0),
+	SOC_SINGLE("ALC Capture Hold Time", WM8971_ALC2, 0, 15, 0),
+	SOC_SINGLE("ALC Capture Decay Time", WM8971_ALC3, 4, 15, 0),
+	SOC_SINGLE("ALC Capture Attack Time", WM8971_ALC3, 0, 15, 0),
+	SOC_SINGLE("ALC Capture NG Threshold", WM8971_NGATE, 3, 31, 0),
+	SOC_ENUM("ALC Capture NG Type", wm8971_enum[4]),
+	SOC_SINGLE("ALC Capture NG Switch", WM8971_NGATE, 0, 1, 0),
+
+	SOC_SINGLE("Capture 6dB Attenuate", WM8971_ADCDAC, 8, 1, 0),
+	SOC_SINGLE("Playback 6dB Attenuate", WM8971_ADCDAC, 7, 1, 0),
+
+	SOC_ENUM("Playback De-emphasis", wm8971_enum[5]),
+	SOC_ENUM("Playback Function", wm8971_enum[6]),
+	SOC_ENUM("Playback Phase", wm8971_enum[7]),
+
+	SOC_DOUBLE_R("Mic Boost", WM8971_LADCIN, WM8971_RADCIN, 4, 3, 0),
+};
+
+/*
+ * DAPM Controls
+ */
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8971_left_mixer_controls[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8971_LOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_LOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8971_LOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_LOUTM2, 7, 1, 0),
+};
+
+/* Right Mixer */
+static const struct snd_kcontrol_new wm8971_right_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8971_ROUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_ROUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM8971_ROUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_ROUTM2, 7, 1, 0),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm8971_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8971_MOUTM1, 8, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_MOUTM1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8971_MOUTM2, 8, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_MOUTM2, 7, 1, 0),
+};
+
+/* Left Line Mux */
+static const struct snd_kcontrol_new wm8971_left_line_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[8]);
+
+/* Right Line Mux */
+static const struct snd_kcontrol_new wm8971_right_line_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[9]);
+
+/* Left PGA Mux */
+static const struct snd_kcontrol_new wm8971_left_pga_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[10]);
+
+/* Right PGA Mux */
+static const struct snd_kcontrol_new wm8971_right_pga_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[11]);
+
+/* Mono ADC Mux */
+static const struct snd_kcontrol_new wm8971_monomux_controls =
+SOC_DAPM_ENUM("Route", wm8971_enum[13]);
+
+static const struct snd_soc_dapm_widget wm8971_dapm_widgets[] = {
+	SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
+		&wm8971_left_mixer_controls[0],
+		ARRAY_SIZE(wm8971_left_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
+		&wm8971_right_mixer_controls[0],
+		ARRAY_SIZE(wm8971_right_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Mono Mixer", WM8971_PWR2, 2, 0,
+		&wm8971_mono_mixer_controls[0],
+		ARRAY_SIZE(wm8971_mono_mixer_controls)),
+
+	SND_SOC_DAPM_PGA("Right Out 2", WM8971_PWR2, 3, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Left Out 2", WM8971_PWR2, 4, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right Out 1", WM8971_PWR2, 5, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Left Out 1", WM8971_PWR2, 6, 0, NULL, 0),
+	SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8971_PWR2, 7, 0),
+	SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8971_PWR2, 8, 0),
+	SND_SOC_DAPM_PGA("Mono Out 1", WM8971_PWR2, 2, 0, NULL, 0),
+
+	SND_SOC_DAPM_MICBIAS("Mic Bias", WM8971_PWR1, 1, 0),
+	SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8971_PWR1, 2, 0),
+	SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8971_PWR1, 3, 0),
+
+	SND_SOC_DAPM_MUX("Left PGA Mux", WM8971_PWR1, 5, 0,
+		&wm8971_left_pga_controls),
+	SND_SOC_DAPM_MUX("Right PGA Mux", WM8971_PWR1, 4, 0,
+		&wm8971_right_pga_controls),
+	SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
+		&wm8971_left_line_controls),
+	SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
+		&wm8971_right_line_controls),
+
+	SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+		&wm8971_monomux_controls),
+	SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+		&wm8971_monomux_controls),
+
+	SND_SOC_DAPM_OUTPUT("LOUT1"),
+	SND_SOC_DAPM_OUTPUT("ROUT1"),
+	SND_SOC_DAPM_OUTPUT("LOUT2"),
+	SND_SOC_DAPM_OUTPUT("ROUT2"),
+	SND_SOC_DAPM_OUTPUT("MONO"),
+
+	SND_SOC_DAPM_INPUT("LINPUT1"),
+	SND_SOC_DAPM_INPUT("RINPUT1"),
+	SND_SOC_DAPM_INPUT("MIC"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* left mixer */
+	{"Left Mixer", "Playback Switch", "Left DAC"},
+	{"Left Mixer", "Left Bypass Switch", "Left Line Mux"},
+	{"Left Mixer", "Right Playback Switch", "Right DAC"},
+	{"Left Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+	/* right mixer */
+	{"Right Mixer", "Left Playback Switch", "Left DAC"},
+	{"Right Mixer", "Left Bypass Switch", "Left Line Mux"},
+	{"Right Mixer", "Playback Switch", "Right DAC"},
+	{"Right Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+	/* left out 1 */
+	{"Left Out 1", NULL, "Left Mixer"},
+	{"LOUT1", NULL, "Left Out 1"},
+
+	/* left out 2 */
+	{"Left Out 2", NULL, "Left Mixer"},
+	{"LOUT2", NULL, "Left Out 2"},
+
+	/* right out 1 */
+	{"Right Out 1", NULL, "Right Mixer"},
+	{"ROUT1", NULL, "Right Out 1"},
+
+	/* right out 2 */
+	{"Right Out 2", NULL, "Right Mixer"},
+	{"ROUT2", NULL, "Right Out 2"},
+
+	/* mono mixer */
+	{"Mono Mixer", "Left Playback Switch", "Left DAC"},
+	{"Mono Mixer", "Left Bypass Switch", "Left Line Mux"},
+	{"Mono Mixer", "Right Playback Switch", "Right DAC"},
+	{"Mono Mixer", "Right Bypass Switch", "Right Line Mux"},
+
+	/* mono out */
+	{"Mono Out", NULL, "Mono Mixer"},
+	{"MONO1", NULL, "Mono Out"},
+
+	/* Left Line Mux */
+	{"Left Line Mux", "Line", "LINPUT1"},
+	{"Left Line Mux", "PGA", "Left PGA Mux"},
+	{"Left Line Mux", "Differential", "Differential Mux"},
+
+	/* Right Line Mux */
+	{"Right Line Mux", "Line", "RINPUT1"},
+	{"Right Line Mux", "Mic", "MIC"},
+	{"Right Line Mux", "PGA", "Right PGA Mux"},
+	{"Right Line Mux", "Differential", "Differential Mux"},
+
+	/* Left PGA Mux */
+	{"Left PGA Mux", "Line", "LINPUT1"},
+	{"Left PGA Mux", "Differential", "Differential Mux"},
+
+	/* Right PGA Mux */
+	{"Right PGA Mux", "Line", "RINPUT1"},
+	{"Right PGA Mux", "Differential", "Differential Mux"},
+
+	/* Differential Mux */
+	{"Differential Mux", "Line", "LINPUT1"},
+	{"Differential Mux", "Line", "RINPUT1"},
+
+	/* Left ADC Mux */
+	{"Left ADC Mux", "Stereo", "Left PGA Mux"},
+	{"Left ADC Mux", "Mono (Left)", "Left PGA Mux"},
+	{"Left ADC Mux", "Digital Mono", "Left PGA Mux"},
+
+	/* Right ADC Mux */
+	{"Right ADC Mux", "Stereo", "Right PGA Mux"},
+	{"Right ADC Mux", "Mono (Right)", "Right PGA Mux"},
+	{"Right ADC Mux", "Digital Mono", "Right PGA Mux"},
+
+	/* ADC */
+	{"Left ADC", NULL, "Left ADC Mux"},
+	{"Right ADC", NULL, "Right ADC Mux"},
+};
+
+static int wm8971_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8971_dapm_widgets,
+				  ARRAY_SIZE(wm8971_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+struct _coeff_div {
+	u32 mclk;
+	u32 rate;
+	u16 fs;
+	u8 sr:5;
+	u8 usb:1;
+};
+
+/* codec hifi mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+	/* 8k */
+	{12288000, 8000, 1536, 0x6, 0x0},
+	{11289600, 8000, 1408, 0x16, 0x0},
+	{18432000, 8000, 2304, 0x7, 0x0},
+	{16934400, 8000, 2112, 0x17, 0x0},
+	{12000000, 8000, 1500, 0x6, 0x1},
+
+	/* 11.025k */
+	{11289600, 11025, 1024, 0x18, 0x0},
+	{16934400, 11025, 1536, 0x19, 0x0},
+	{12000000, 11025, 1088, 0x19, 0x1},
+
+	/* 16k */
+	{12288000, 16000, 768, 0xa, 0x0},
+	{18432000, 16000, 1152, 0xb, 0x0},
+	{12000000, 16000, 750, 0xa, 0x1},
+
+	/* 22.05k */
+	{11289600, 22050, 512, 0x1a, 0x0},
+	{16934400, 22050, 768, 0x1b, 0x0},
+	{12000000, 22050, 544, 0x1b, 0x1},
+
+	/* 32k */
+	{12288000, 32000, 384, 0xc, 0x0},
+	{18432000, 32000, 576, 0xd, 0x0},
+	{12000000, 32000, 375, 0xa, 0x1},
+
+	/* 44.1k */
+	{11289600, 44100, 256, 0x10, 0x0},
+	{16934400, 44100, 384, 0x11, 0x0},
+	{12000000, 44100, 272, 0x11, 0x1},
+
+	/* 48k */
+	{12288000, 48000, 256, 0x0, 0x0},
+	{18432000, 48000, 384, 0x1, 0x0},
+	{12000000, 48000, 250, 0x0, 0x1},
+
+	/* 88.2k */
+	{11289600, 88200, 128, 0x1e, 0x0},
+	{16934400, 88200, 192, 0x1f, 0x0},
+	{12000000, 88200, 136, 0x1f, 0x1},
+
+	/* 96k */
+	{12288000, 96000, 128, 0xe, 0x0},
+	{18432000, 96000, 192, 0xf, 0x0},
+	{12000000, 96000, 125, 0xe, 0x1},
+};
+
+static int get_coeff(int mclk, int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+		if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+			return i;
+	}
+	return -EINVAL;
+}
+
+static int wm8971_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec);
+
+	switch (freq) {
+	case 11289600:
+	case 12000000:
+	case 12288000:
+	case 16934400:
+	case 18432000:
+		wm8971->sysclk = freq;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int wm8971_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = 0;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		iface = 0x0040;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x0003;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface |= 0x0013;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x0090;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x0080;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x0010;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8971_IFACE, iface);
+	return 0;
+}
+
+static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec);
+	u16 iface = snd_soc_read(codec, WM8971_IFACE) & 0x1f3;
+	u16 srate = snd_soc_read(codec, WM8971_SRATE) & 0x1c0;
+	int coeff = get_coeff(wm8971->sysclk, params_rate(params));
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x0004;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x0008;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface |= 0x000c;
+		break;
+	}
+
+	/* set iface & srate */
+	snd_soc_write(codec, WM8971_IFACE, iface);
+	if (coeff >= 0)
+		snd_soc_write(codec, WM8971_SRATE, srate |
+			(coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
+
+	return 0;
+}
+
+static int wm8971_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = snd_soc_read(codec, WM8971_ADCDAC) & 0xfff7;
+
+	if (mute)
+		snd_soc_write(codec, WM8971_ADCDAC, mute_reg | 0x8);
+	else
+		snd_soc_write(codec, WM8971_ADCDAC, mute_reg);
+	return 0;
+}
+
+static int wm8971_set_bias_level(struct snd_soc_codec *codec,
+	enum snd_soc_bias_level level)
+{
+	u16 pwr_reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		/* set vmid to 50k and unmute dac */
+		snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x00c1);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		/* mute dac and set vmid to 500k, enable VREF */
+		snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x0140);
+		break;
+	case SND_SOC_BIAS_OFF:
+		snd_soc_write(codec, WM8971_PWR1, 0x0001);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8971_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+		SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+		SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8971_dai_ops = {
+	.hw_params	= wm8971_pcm_hw_params,
+	.digital_mute	= wm8971_mute,
+	.set_fmt	= wm8971_set_dai_fmt,
+	.set_sysclk	= wm8971_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8971_dai = {
+	.name = "wm8971-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8971_RATES,
+		.formats = WM8971_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8971_RATES,
+		.formats = WM8971_FORMATS,},
+	.ops = &wm8971_dai_ops,
+};
+
+static void wm8971_work(struct work_struct *work)
+{
+	struct snd_soc_codec *codec =
+		container_of(work, struct snd_soc_codec, delayed_work.work);
+	wm8971_set_bias_level(codec, codec->bias_level);
+}
+
+static int wm8971_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8971_resume(struct snd_soc_codec *codec)
+{
+	int i;
+	u8 data[2];
+	u16 *cache = codec->reg_cache;
+	u16 reg;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 0; i < ARRAY_SIZE(wm8971_reg); i++) {
+		if (i + 1 == WM8971_RESET)
+			continue;
+		data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+		data[1] = cache[i] & 0x00ff;
+		codec->hw_write(codec->control_data, data, 2);
+	}
+
+	wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* charge wm8971 caps */
+	if (codec->suspend_bias_level == SND_SOC_BIAS_ON) {
+		reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
+		snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0);
+		codec->bias_level = SND_SOC_BIAS_ON;
+		queue_delayed_work(wm8971_workq, &codec->delayed_work,
+			msecs_to_jiffies(1000));
+	}
+
+	return 0;
+}
+
+static int wm8971_probe(struct snd_soc_codec *codec)
+{
+	struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+	u16 reg;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8971->control_type);
+	if (ret < 0) {
+		printk(KERN_ERR "wm8971: failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	INIT_DELAYED_WORK(&codec->delayed_work, wm8971_work);
+	wm8971_workq = create_workqueue("wm8971");
+	if (wm8971_workq == NULL)
+		return -ENOMEM;
+
+	wm8971_reset(codec);
+
+	/* charge output caps - set vmid to 5k for quick power up */
+	reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
+	snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0);
+	codec->bias_level = SND_SOC_BIAS_STANDBY;
+	queue_delayed_work(wm8971_workq, &codec->delayed_work,
+		msecs_to_jiffies(1000));
+
+	/* set the update bits */
+	reg = snd_soc_read(codec, WM8971_LDAC);
+	snd_soc_write(codec, WM8971_LDAC, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8971_RDAC);
+	snd_soc_write(codec, WM8971_RDAC, reg | 0x0100);
+
+	reg = snd_soc_read(codec, WM8971_LOUT1V);
+	snd_soc_write(codec, WM8971_LOUT1V, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8971_ROUT1V);
+	snd_soc_write(codec, WM8971_ROUT1V, reg | 0x0100);
+
+	reg = snd_soc_read(codec, WM8971_LOUT2V);
+	snd_soc_write(codec, WM8971_LOUT2V, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8971_ROUT2V);
+	snd_soc_write(codec, WM8971_ROUT2V, reg | 0x0100);
+
+	reg = snd_soc_read(codec, WM8971_LINVOL);
+	snd_soc_write(codec, WM8971_LINVOL, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8971_RINVOL);
+	snd_soc_write(codec, WM8971_RINVOL, reg | 0x0100);
+
+	snd_soc_add_controls(codec, wm8971_snd_controls,
+				ARRAY_SIZE(wm8971_snd_controls));
+	wm8971_add_widgets(codec);
+
+	return ret;
+}
+
+
+/* power down chip */
+static int wm8971_remove(struct snd_soc_codec *codec)
+{
+	wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	if (wm8971_workq)
+		destroy_workqueue(wm8971_workq);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8971 = {
+	.probe =	wm8971_probe,
+	.remove =	wm8971_remove,
+	.suspend =	wm8971_suspend,
+	.resume =	wm8971_resume,
+	.set_bias_level = wm8971_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8971_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8971_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8971_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8971_priv *wm8971;
+	int ret;
+
+	wm8971 = kzalloc(sizeof(struct wm8971_priv), GFP_KERNEL);
+	if (wm8971 == NULL)
+		return -ENOMEM;
+
+	wm8971->control_type = SND_SOC_I2C;
+	i2c_set_clientdata(i2c, wm8971);
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8971, &wm8971_dai, 1);
+	if (ret < 0)
+		kfree(wm8971);
+	return ret;
+}
+
+static __devexit int wm8971_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8971_i2c_id[] = {
+	{ "wm8971", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8971_i2c_id);
+
+static struct i2c_driver wm8971_i2c_driver = {
+	.driver = {
+		.name = "wm8971-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8971_i2c_probe,
+	.remove =   __devexit_p(wm8971_i2c_remove),
+	.id_table = wm8971_i2c_id,
+};
+#endif
+
+static int __init wm8971_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8971_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8971 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8971_modinit);
+
+static void __exit wm8971_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8971_i2c_driver);
+#endif
+}
+module_exit(wm8971_exit);
+
+MODULE_DESCRIPTION("ASoC WM8971 driver");
+MODULE_AUTHOR("Lab126");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8971.h b/linux/sound/soc/codecs/wm8971.h
new file mode 100644
index 0000000..f31c38f
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8971.h
@@ -0,0 +1,56 @@
+/*
+ * wm8971.h  --  audio driver for WM8971
+ *
+ * Copyright 2005 Lab126, Inc.
+ *
+ * Author: Kenneth Kiraly <kiraly@lab126.com>
+ *
+ * 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.
+ *
+ */
+
+#ifndef _WM8971_H
+#define _WM8971_H
+
+#define WM8971_LINVOL	0x00
+#define WM8971_RINVOL	0x01
+#define WM8971_LOUT1V	0x02
+#define WM8971_ROUT1V	0x03
+#define WM8971_ADCDAC	0x05
+#define WM8971_IFACE	0x07
+#define WM8971_SRATE	0x08
+#define WM8971_LDAC		0x0a
+#define WM8971_RDAC		0x0b
+#define WM8971_BASS		0x0c
+#define WM8971_TREBLE	0x0d
+#define WM8971_RESET	0x0f
+#define WM8971_ALC1		0x11
+#define	WM8971_ALC2		0x12
+#define	WM8971_ALC3		0x13
+#define WM8971_NGATE	0x14
+#define WM8971_LADC		0x15
+#define WM8971_RADC		0x16
+#define	WM8971_ADCTL1	0x17
+#define	WM8971_ADCTL2	0x18
+#define WM8971_PWR1		0x19
+#define WM8971_PWR2		0x1a
+#define	WM8971_ADCTL3	0x1b
+#define WM8971_ADCIN	0x1f
+#define	WM8971_LADCIN	0x20
+#define	WM8971_RADCIN	0x21
+#define WM8971_LOUTM1	0x22
+#define WM8971_LOUTM2	0x23
+#define WM8971_ROUTM1	0x24
+#define WM8971_ROUTM2	0x25
+#define WM8971_MOUTM1	0x26
+#define WM8971_MOUTM2	0x27
+#define WM8971_LOUT2V	0x28
+#define WM8971_ROUT2V	0x29
+#define WM8971_MOUTV	0x2A
+
+#define WM8971_SYSCLK	0
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8974.c b/linux/sound/soc/codecs/wm8974.c
new file mode 100644
index 0000000..b4363f6
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8974.c
@@ -0,0 +1,718 @@
+/*
+ * wm8974.c  --  WM8974 ALSA Soc Audio driver
+ *
+ * Copyright 2006-2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood <linux@wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8974.h"
+
+static const u16 wm8974_reg[WM8974_CACHEREGNUM] = {
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0050, 0x0000, 0x0140, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x00ff,
+	0x0000, 0x0000, 0x0100, 0x00ff,
+	0x0000, 0x0000, 0x012c, 0x002c,
+	0x002c, 0x002c, 0x002c, 0x0000,
+	0x0032, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0038, 0x000b, 0x0032, 0x0000,
+	0x0008, 0x000c, 0x0093, 0x00e9,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0003, 0x0010, 0x0000, 0x0000,
+	0x0000, 0x0002, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0039, 0x0000,
+	0x0000,
+};
+
+#define WM8974_POWER1_BIASEN  0x08
+#define WM8974_POWER1_BUFIOEN 0x04
+
+struct wm8974_priv {
+	enum snd_soc_control_type control_type;
+	u16 reg_cache[WM8974_CACHEREGNUM];
+};
+
+#define wm8974_reset(c)	snd_soc_write(c, WM8974_RESET, 0)
+
+static const char *wm8974_companding[] = {"Off", "NC", "u-law", "A-law" };
+static const char *wm8974_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" };
+static const char *wm8974_eqmode[] = {"Capture", "Playback" };
+static const char *wm8974_bw[] = {"Narrow", "Wide" };
+static const char *wm8974_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" };
+static const char *wm8974_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" };
+static const char *wm8974_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" };
+static const char *wm8974_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" };
+static const char *wm8974_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" };
+static const char *wm8974_alc[] = {"ALC", "Limiter" };
+
+static const struct soc_enum wm8974_enum[] = {
+	SOC_ENUM_SINGLE(WM8974_COMP, 1, 4, wm8974_companding), /* adc */
+	SOC_ENUM_SINGLE(WM8974_COMP, 3, 4, wm8974_companding), /* dac */
+	SOC_ENUM_SINGLE(WM8974_DAC,  4, 4, wm8974_deemp),
+	SOC_ENUM_SINGLE(WM8974_EQ1,  8, 2, wm8974_eqmode),
+
+	SOC_ENUM_SINGLE(WM8974_EQ1,  5, 4, wm8974_eq1),
+	SOC_ENUM_SINGLE(WM8974_EQ2,  8, 2, wm8974_bw),
+	SOC_ENUM_SINGLE(WM8974_EQ2,  5, 4, wm8974_eq2),
+	SOC_ENUM_SINGLE(WM8974_EQ3,  8, 2, wm8974_bw),
+
+	SOC_ENUM_SINGLE(WM8974_EQ3,  5, 4, wm8974_eq3),
+	SOC_ENUM_SINGLE(WM8974_EQ4,  8, 2, wm8974_bw),
+	SOC_ENUM_SINGLE(WM8974_EQ4,  5, 4, wm8974_eq4),
+	SOC_ENUM_SINGLE(WM8974_EQ5,  8, 2, wm8974_bw),
+
+	SOC_ENUM_SINGLE(WM8974_EQ5,  5, 4, wm8974_eq5),
+	SOC_ENUM_SINGLE(WM8974_ALC3,  8, 2, wm8974_alc),
+};
+
+static const char *wm8974_auxmode_text[] = { "Buffer", "Mixer" };
+
+static const struct soc_enum wm8974_auxmode =
+	SOC_ENUM_SINGLE(WM8974_INPUT,  3, 2, wm8974_auxmode_text);
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
+static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);
+
+static const struct snd_kcontrol_new wm8974_snd_controls[] = {
+
+SOC_SINGLE("Digital Loopback Switch", WM8974_COMP, 0, 1, 0),
+
+SOC_ENUM("DAC Companding", wm8974_enum[1]),
+SOC_ENUM("ADC Companding", wm8974_enum[0]),
+
+SOC_ENUM("Playback De-emphasis", wm8974_enum[2]),
+SOC_SINGLE("DAC Inversion Switch", WM8974_DAC, 0, 1, 0),
+
+SOC_SINGLE_TLV("PCM Volume", WM8974_DACVOL, 0, 255, 0, digital_tlv),
+
+SOC_SINGLE("High Pass Filter Switch", WM8974_ADC, 8, 1, 0),
+SOC_SINGLE("High Pass Cut Off", WM8974_ADC, 4, 7, 0),
+SOC_SINGLE("ADC Inversion Switch", WM8974_ADC, 0, 1, 0),
+
+SOC_SINGLE_TLV("Capture Volume", WM8974_ADCVOL,  0, 255, 0, digital_tlv),
+
+SOC_ENUM("Equaliser Function", wm8974_enum[3]),
+SOC_ENUM("EQ1 Cut Off", wm8974_enum[4]),
+SOC_SINGLE_TLV("EQ1 Volume", WM8974_EQ1,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ2 Bandwith", wm8974_enum[5]),
+SOC_ENUM("EQ2 Cut Off", wm8974_enum[6]),
+SOC_SINGLE_TLV("EQ2 Volume", WM8974_EQ2,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ3 Bandwith", wm8974_enum[7]),
+SOC_ENUM("EQ3 Cut Off", wm8974_enum[8]),
+SOC_SINGLE_TLV("EQ3 Volume", WM8974_EQ3,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ4 Bandwith", wm8974_enum[9]),
+SOC_ENUM("EQ4 Cut Off", wm8974_enum[10]),
+SOC_SINGLE_TLV("EQ4 Volume", WM8974_EQ4,  0, 24, 1, eq_tlv),
+
+SOC_ENUM("Equaliser EQ5 Bandwith", wm8974_enum[11]),
+SOC_ENUM("EQ5 Cut Off", wm8974_enum[12]),
+SOC_SINGLE_TLV("EQ5 Volume", WM8974_EQ5,  0, 24, 1, eq_tlv),
+
+SOC_SINGLE("DAC Playback Limiter Switch", WM8974_DACLIM1,  8, 1, 0),
+SOC_SINGLE("DAC Playback Limiter Decay", WM8974_DACLIM1,  4, 15, 0),
+SOC_SINGLE("DAC Playback Limiter Attack", WM8974_DACLIM1,  0, 15, 0),
+
+SOC_SINGLE("DAC Playback Limiter Threshold", WM8974_DACLIM2,  4, 7, 0),
+SOC_SINGLE("DAC Playback Limiter Boost", WM8974_DACLIM2,  0, 15, 0),
+
+SOC_SINGLE("ALC Enable Switch", WM8974_ALC1,  8, 1, 0),
+SOC_SINGLE("ALC Capture Max Gain", WM8974_ALC1,  3, 7, 0),
+SOC_SINGLE("ALC Capture Min Gain", WM8974_ALC1,  0, 7, 0),
+
+SOC_SINGLE("ALC Capture ZC Switch", WM8974_ALC2,  8, 1, 0),
+SOC_SINGLE("ALC Capture Hold", WM8974_ALC2,  4, 7, 0),
+SOC_SINGLE("ALC Capture Target", WM8974_ALC2,  0, 15, 0),
+
+SOC_ENUM("ALC Capture Mode", wm8974_enum[13]),
+SOC_SINGLE("ALC Capture Decay", WM8974_ALC3,  4, 15, 0),
+SOC_SINGLE("ALC Capture Attack", WM8974_ALC3,  0, 15, 0),
+
+SOC_SINGLE("ALC Capture Noise Gate Switch", WM8974_NGATE,  3, 1, 0),
+SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8974_NGATE,  0, 7, 0),
+
+SOC_SINGLE("Capture PGA ZC Switch", WM8974_INPPGA,  7, 1, 0),
+SOC_SINGLE_TLV("Capture PGA Volume", WM8974_INPPGA,  0, 63, 0, inpga_tlv),
+
+SOC_SINGLE("Speaker Playback ZC Switch", WM8974_SPKVOL,  7, 1, 0),
+SOC_SINGLE("Speaker Playback Switch", WM8974_SPKVOL,  6, 1, 1),
+SOC_SINGLE_TLV("Speaker Playback Volume", WM8974_SPKVOL,  0, 63, 0, spk_tlv),
+
+SOC_ENUM("Aux Mode", wm8974_auxmode),
+
+SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST,  8, 1, 0),
+SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 1),
+
+/* DAC / ADC oversampling */
+SOC_SINGLE("DAC 128x Oversampling Switch", WM8974_DAC, 8, 1, 0),
+SOC_SINGLE("ADC 128x Oversampling Switch", WM8974_ADC, 8, 1, 0),
+};
+
+/* Speaker Output Mixer */
+static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 0),
+};
+
+/* Mono Output Mixer */
+static const struct snd_kcontrol_new wm8974_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_MONOMIX, 1, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_MONOMIX, 2, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_MONOMIX, 0, 1, 0),
+};
+
+/* Boost mixer */
+static const struct snd_kcontrol_new wm8974_boost_mixer[] = {
+SOC_DAPM_SINGLE("Aux Switch", WM8974_INPPGA, 6, 1, 0),
+};
+
+/* Input PGA */
+static const struct snd_kcontrol_new wm8974_inpga[] = {
+SOC_DAPM_SINGLE("Aux Switch", WM8974_INPUT, 2, 1, 0),
+SOC_DAPM_SINGLE("MicN Switch", WM8974_INPUT, 1, 1, 0),
+SOC_DAPM_SINGLE("MicP Switch", WM8974_INPUT, 0, 1, 0),
+};
+
+/* AUX Input boost vol */
+static const struct snd_kcontrol_new wm8974_aux_boost_controls =
+SOC_DAPM_SINGLE("Aux Volume", WM8974_ADCBOOST, 0, 7, 0);
+
+/* Mic Input boost vol */
+static const struct snd_kcontrol_new wm8974_mic_boost_controls =
+SOC_DAPM_SINGLE("Mic Volume", WM8974_ADCBOOST, 4, 7, 0);
+
+static const struct snd_soc_dapm_widget wm8974_dapm_widgets[] = {
+SND_SOC_DAPM_MIXER("Speaker Mixer", WM8974_POWER3, 2, 0,
+	&wm8974_speaker_mixer_controls[0],
+	ARRAY_SIZE(wm8974_speaker_mixer_controls)),
+SND_SOC_DAPM_MIXER("Mono Mixer", WM8974_POWER3, 3, 0,
+	&wm8974_mono_mixer_controls[0],
+	ARRAY_SIZE(wm8974_mono_mixer_controls)),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8974_POWER3, 0, 0),
+SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8974_POWER2, 0, 0),
+SND_SOC_DAPM_PGA("Aux Input", WM8974_POWER1, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkN Out", WM8974_POWER3, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("SpkP Out", WM8974_POWER3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out", WM8974_POWER3, 7, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Input PGA", WM8974_POWER2, 2, 0, wm8974_inpga,
+		   ARRAY_SIZE(wm8974_inpga)),
+SND_SOC_DAPM_MIXER("Boost Mixer", WM8974_POWER2, 4, 0,
+		   wm8974_boost_mixer, ARRAY_SIZE(wm8974_boost_mixer)),
+
+SND_SOC_DAPM_MICBIAS("Mic Bias", WM8974_POWER1, 4, 0),
+
+SND_SOC_DAPM_INPUT("MICN"),
+SND_SOC_DAPM_INPUT("MICP"),
+SND_SOC_DAPM_INPUT("AUX"),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("SPKOUTP"),
+SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Mono output mixer */
+	{"Mono Mixer", "PCM Playback Switch", "DAC"},
+	{"Mono Mixer", "Aux Playback Switch", "Aux Input"},
+	{"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+	/* Speaker output mixer */
+	{"Speaker Mixer", "PCM Playback Switch", "DAC"},
+	{"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
+	{"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
+
+	/* Outputs */
+	{"Mono Out", NULL, "Mono Mixer"},
+	{"MONOOUT", NULL, "Mono Out"},
+	{"SpkN Out", NULL, "Speaker Mixer"},
+	{"SpkP Out", NULL, "Speaker Mixer"},
+	{"SPKOUTN", NULL, "SpkN Out"},
+	{"SPKOUTP", NULL, "SpkP Out"},
+
+	/* Boost Mixer */
+	{"ADC", NULL, "Boost Mixer"},
+	{"Boost Mixer", "Aux Switch", "Aux Input"},
+	{"Boost Mixer", NULL, "Input PGA"},
+	{"Boost Mixer", NULL, "MICP"},
+
+	/* Input PGA */
+	{"Input PGA", "Aux Switch", "Aux Input"},
+	{"Input PGA", "MicN Switch", "MICN"},
+	{"Input PGA", "MicP Switch", "MICP"},
+
+	/* Inputs */
+	{"Aux Input", NULL, "AUX"},
+};
+
+static int wm8974_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8974_dapm_widgets,
+				  ARRAY_SIZE(wm8974_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+struct pll_ {
+	unsigned int pre_div:1;
+	unsigned int n:4;
+	unsigned int k;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+
+static void pll_factors(struct pll_ *pll_div,
+			unsigned int target, unsigned int source)
+{
+	unsigned long long Kpart;
+	unsigned int K, Ndiv, Nmod;
+
+	/* There is a fixed divide by 4 in the output path */
+	target *= 4;
+
+	Ndiv = target / source;
+	if (Ndiv < 6) {
+		source /= 2;
+		pll_div->pre_div = 1;
+		Ndiv = target / source;
+	} else
+		pll_div->pre_div = 0;
+
+	if ((Ndiv < 6) || (Ndiv > 12))
+		printk(KERN_WARNING
+			"WM8974 N value %u outwith recommended range!\n",
+			Ndiv);
+
+	pll_div->n = Ndiv;
+	Nmod = target % source;
+	Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	/* Check if we need to round */
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	K /= 10;
+
+	pll_div->k = K;
+}
+
+static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct pll_ pll_div;
+	u16 reg;
+
+	if (freq_in == 0 || freq_out == 0) {
+		/* Clock CODEC directly from MCLK */
+		reg = snd_soc_read(codec, WM8974_CLOCK);
+		snd_soc_write(codec, WM8974_CLOCK, reg & 0x0ff);
+
+		/* Turn off PLL */
+		reg = snd_soc_read(codec, WM8974_POWER1);
+		snd_soc_write(codec, WM8974_POWER1, reg & 0x1df);
+		return 0;
+	}
+
+	pll_factors(&pll_div, freq_out, freq_in);
+
+	snd_soc_write(codec, WM8974_PLLN, (pll_div.pre_div << 4) | pll_div.n);
+	snd_soc_write(codec, WM8974_PLLK1, pll_div.k >> 18);
+	snd_soc_write(codec, WM8974_PLLK2, (pll_div.k >> 9) & 0x1ff);
+	snd_soc_write(codec, WM8974_PLLK3, pll_div.k & 0x1ff);
+	reg = snd_soc_read(codec, WM8974_POWER1);
+	snd_soc_write(codec, WM8974_POWER1, reg | 0x020);
+
+	/* Run CODEC from PLL instead of MCLK */
+	reg = snd_soc_read(codec, WM8974_CLOCK);
+	snd_soc_write(codec, WM8974_CLOCK, reg | 0x100);
+
+	return 0;
+}
+
+/*
+ * Configure WM8974 clock dividers.
+ */
+static int wm8974_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+		int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+
+	switch (div_id) {
+	case WM8974_OPCLKDIV:
+		reg = snd_soc_read(codec, WM8974_GPIO) & 0x1cf;
+		snd_soc_write(codec, WM8974_GPIO, reg | div);
+		break;
+	case WM8974_MCLKDIV:
+		reg = snd_soc_read(codec, WM8974_CLOCK) & 0x11f;
+		snd_soc_write(codec, WM8974_CLOCK, reg | div);
+		break;
+	case WM8974_BCLKDIV:
+		reg = snd_soc_read(codec, WM8974_CLOCK) & 0x1e3;
+		snd_soc_write(codec, WM8974_CLOCK, reg | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = 0;
+	u16 clk = snd_soc_read(codec, WM8974_CLOCK) & 0x1fe;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		clk |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x0010;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x0008;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x00018;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x0180;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x0100;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x0080;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8974_IFACE, iface);
+	snd_soc_write(codec, WM8974_CLOCK, clk);
+	return 0;
+}
+
+static int wm8974_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 iface = snd_soc_read(codec, WM8974_IFACE) & 0x19f;
+	u16 adn = snd_soc_read(codec, WM8974_ADD) & 0x1f1;
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x0020;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x0040;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface |= 0x0060;
+		break;
+	}
+
+	/* filter coefficient */
+	switch (params_rate(params)) {
+	case 8000:
+		adn |= 0x5 << 1;
+		break;
+	case 11025:
+		adn |= 0x4 << 1;
+		break;
+	case 16000:
+		adn |= 0x3 << 1;
+		break;
+	case 22050:
+		adn |= 0x2 << 1;
+		break;
+	case 32000:
+		adn |= 0x1 << 1;
+		break;
+	case 44100:
+	case 48000:
+		break;
+	}
+
+	snd_soc_write(codec, WM8974_IFACE, iface);
+	snd_soc_write(codec, WM8974_ADD, adn);
+	return 0;
+}
+
+static int wm8974_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = snd_soc_read(codec, WM8974_DAC) & 0xffbf;
+
+	if (mute)
+		snd_soc_write(codec, WM8974_DAC, mute_reg | 0x40);
+	else
+		snd_soc_write(codec, WM8974_DAC, mute_reg);
+	return 0;
+}
+
+/* liam need to make this lower power with dapm */
+static int wm8974_set_bias_level(struct snd_soc_codec *codec,
+	enum snd_soc_bias_level level)
+{
+	u16 power1 = snd_soc_read(codec, WM8974_POWER1) & ~0x3;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+		power1 |= 0x1;  /* VMID 50k */
+		snd_soc_write(codec, WM8974_POWER1, power1);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		power1 |= WM8974_POWER1_BIASEN | WM8974_POWER1_BUFIOEN;
+
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Initial cap charge at VMID 5k */
+			snd_soc_write(codec, WM8974_POWER1, power1 | 0x3);
+			mdelay(100);
+		}
+
+		power1 |= 0x2;  /* VMID 500k */
+		snd_soc_write(codec, WM8974_POWER1, power1);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		snd_soc_write(codec, WM8974_POWER1, 0);
+		snd_soc_write(codec, WM8974_POWER2, 0);
+		snd_soc_write(codec, WM8974_POWER3, 0);
+		break;
+	}
+
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8974_RATES (SNDRV_PCM_RATE_8000_48000)
+
+#define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8974_ops = {
+	.hw_params = wm8974_pcm_hw_params,
+	.digital_mute = wm8974_mute,
+	.set_fmt = wm8974_set_dai_fmt,
+	.set_clkdiv = wm8974_set_dai_clkdiv,
+	.set_pll = wm8974_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver wm8974_dai = {
+	.name = "wm8974-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,   /* Only 1 channel of data */
+		.rates = WM8974_RATES,
+		.formats = WM8974_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,   /* Only 1 channel of data */
+		.rates = WM8974_RATES,
+		.formats = WM8974_FORMATS,},
+	.ops = &wm8974_ops,
+	.symmetric_rates = 1,
+};
+
+static int wm8974_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8974_resume(struct snd_soc_codec *codec)
+{
+	int i;
+	u8 data[2];
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 0; i < ARRAY_SIZE(wm8974_reg); i++) {
+		data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+		data[1] = cache[i] & 0x00ff;
+		codec->hw_write(codec->control_data, data, 2);
+	}
+	wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static int wm8974_probe(struct snd_soc_codec *codec)
+{
+	int ret = 0;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_I2C);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm8974_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		return ret;
+	}
+
+	wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	snd_soc_add_controls(codec, wm8974_snd_controls,
+			     ARRAY_SIZE(wm8974_snd_controls));
+	wm8974_add_widgets(codec);
+
+	return ret;
+}
+
+/* power down chip */
+static int wm8974_remove(struct snd_soc_codec *codec)
+{
+	wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8974 = {
+	.probe = 	wm8974_probe,
+	.remove = 	wm8974_remove,
+	.suspend = 	wm8974_suspend,
+	.resume =	wm8974_resume,
+	.set_bias_level = wm8974_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8974_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8974_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8974_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8974_priv *wm8974;
+	int ret;
+
+	wm8974 = kzalloc(sizeof(struct wm8974_priv), GFP_KERNEL);
+	if (wm8974 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8974);
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8974, &wm8974_dai, 1);
+	if (ret < 0)
+		kfree(wm8974);
+	return ret;
+}
+
+static __devexit int wm8974_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8974_i2c_id[] = {
+	{ "wm8974", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8974_i2c_id);
+
+static struct i2c_driver wm8974_i2c_driver = {
+	.driver = {
+		.name = "wm8974-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8974_i2c_probe,
+	.remove =   __devexit_p(wm8974_i2c_remove),
+	.id_table = wm8974_i2c_id,
+};
+#endif
+
+static int __init wm8974_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8974_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8974 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8974_modinit);
+
+static void __exit wm8974_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8974_i2c_driver);
+#endif
+}
+module_exit(wm8974_exit);
+
+MODULE_DESCRIPTION("ASoC WM8974 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8974.h b/linux/sound/soc/codecs/wm8974.h
new file mode 100644
index 0000000..3c94e7b
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8974.h
@@ -0,0 +1,86 @@
+/*
+ * wm8974.h  --  WM8974 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8974_H
+#define _WM8974_H
+
+/* WM8974 register space */
+
+#define WM8974_RESET		0x0
+#define WM8974_POWER1		0x1
+#define WM8974_POWER2		0x2
+#define WM8974_POWER3		0x3
+#define WM8974_IFACE		0x4
+#define WM8974_COMP		0x5
+#define WM8974_CLOCK		0x6
+#define WM8974_ADD		0x7
+#define WM8974_GPIO		0x8
+#define WM8974_DAC		0xa
+#define WM8974_DACVOL		0xb
+#define WM8974_ADC		0xe
+#define WM8974_ADCVOL		0xf
+#define WM8974_EQ1		0x12
+#define WM8974_EQ2		0x13
+#define WM8974_EQ3		0x14
+#define WM8974_EQ4		0x15
+#define WM8974_EQ5		0x16
+#define WM8974_DACLIM1		0x18
+#define WM8974_DACLIM2		0x19
+#define WM8974_NOTCH1		0x1b
+#define WM8974_NOTCH2		0x1c
+#define WM8974_NOTCH3		0x1d
+#define WM8974_NOTCH4		0x1e
+#define WM8974_ALC1		0x20
+#define WM8974_ALC2		0x21
+#define WM8974_ALC3		0x22
+#define WM8974_NGATE		0x23
+#define WM8974_PLLN		0x24
+#define WM8974_PLLK1		0x25
+#define WM8974_PLLK2		0x26
+#define WM8974_PLLK3		0x27
+#define WM8974_ATTEN		0x28
+#define WM8974_INPUT		0x2c
+#define WM8974_INPPGA		0x2d
+#define WM8974_ADCBOOST		0x2f
+#define WM8974_OUTPUT		0x31
+#define WM8974_SPKMIX		0x32
+#define WM8974_SPKVOL		0x36
+#define WM8974_MONOMIX		0x38
+
+#define WM8974_CACHEREGNUM 	57
+
+/* Clock divider Id's */
+#define WM8974_OPCLKDIV		0
+#define WM8974_MCLKDIV		1
+#define WM8974_BCLKDIV		2
+
+/* PLL Out dividers */
+#define WM8974_OPCLKDIV_1	(0 << 4)
+#define WM8974_OPCLKDIV_2	(1 << 4)
+#define WM8974_OPCLKDIV_3	(2 << 4)
+#define WM8974_OPCLKDIV_4	(3 << 4)
+
+/* BCLK clock dividers */
+#define WM8974_BCLKDIV_1	(0 << 2)
+#define WM8974_BCLKDIV_2	(1 << 2)
+#define WM8974_BCLKDIV_4	(2 << 2)
+#define WM8974_BCLKDIV_8	(3 << 2)
+#define WM8974_BCLKDIV_16	(4 << 2)
+#define WM8974_BCLKDIV_32	(5 << 2)
+
+/* MCLK clock dividers */
+#define WM8974_MCLKDIV_1	(0 << 5)
+#define WM8974_MCLKDIV_1_5	(1 << 5)
+#define WM8974_MCLKDIV_2	(2 << 5)
+#define WM8974_MCLKDIV_3	(3 << 5)
+#define WM8974_MCLKDIV_4	(4 << 5)
+#define WM8974_MCLKDIV_6	(5 << 5)
+#define WM8974_MCLKDIV_8	(6 << 5)
+#define WM8974_MCLKDIV_12	(7 << 5)
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8978.c b/linux/sound/soc/codecs/wm8978.c
new file mode 100644
index 0000000..13b979a
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8978.c
@@ -0,0 +1,1074 @@
+/*
+ * wm8978.c  --  WM8978 ALSA SoC Audio Codec driver
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2007 Carlos Munoz <carlos@kenati.com>
+ * Copyright 2006-2009 Wolfson Microelectronics PLC.
+ * Based on wm8974 and wm8990 by Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <asm/div64.h>
+
+#include "wm8978.h"
+
+/* wm8978 register cache. Note that register 0 is not included in the cache. */
+static const u16 wm8978_reg[WM8978_CACHEREGNUM] = {
+	0x0000, 0x0000, 0x0000, 0x0000,	/* 0x00...0x03 */
+	0x0050, 0x0000, 0x0140, 0x0000,	/* 0x04...0x07 */
+	0x0000, 0x0000, 0x0000, 0x00ff,	/* 0x08...0x0b */
+	0x00ff, 0x0000, 0x0100, 0x00ff,	/* 0x0c...0x0f */
+	0x00ff, 0x0000, 0x012c, 0x002c,	/* 0x10...0x13 */
+	0x002c, 0x002c, 0x002c, 0x0000,	/* 0x14...0x17 */
+	0x0032, 0x0000, 0x0000, 0x0000,	/* 0x18...0x1b */
+	0x0000, 0x0000, 0x0000, 0x0000,	/* 0x1c...0x1f */
+	0x0038, 0x000b, 0x0032, 0x0000,	/* 0x20...0x23 */
+	0x0008, 0x000c, 0x0093, 0x00e9,	/* 0x24...0x27 */
+	0x0000, 0x0000, 0x0000, 0x0000,	/* 0x28...0x2b */
+	0x0033, 0x0010, 0x0010, 0x0100,	/* 0x2c...0x2f */
+	0x0100, 0x0002, 0x0001, 0x0001,	/* 0x30...0x33 */
+	0x0039, 0x0039, 0x0039, 0x0039,	/* 0x34...0x37 */
+	0x0001,	0x0001,			/* 0x38...0x3b */
+};
+
+/* codec private data */
+struct wm8978_priv {
+	enum snd_soc_control_type control_type;
+	void *control_data;
+	unsigned int f_pllout;
+	unsigned int f_mclk;
+	unsigned int f_256fs;
+	unsigned int f_opclk;
+	int mclk_idx;
+	enum wm8978_sysclk_src sysclk;
+	u16 reg_cache[WM8978_CACHEREGNUM];
+};
+
+static const char *wm8978_companding[] = {"Off", "NC", "u-law", "A-law"};
+static const char *wm8978_eqmode[] = {"Capture", "Playback"};
+static const char *wm8978_bw[] = {"Narrow", "Wide"};
+static const char *wm8978_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz"};
+static const char *wm8978_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz"};
+static const char *wm8978_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz"};
+static const char *wm8978_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz"};
+static const char *wm8978_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz"};
+static const char *wm8978_alc3[] = {"ALC", "Limiter"};
+static const char *wm8978_alc1[] = {"Off", "Right", "Left", "Both"};
+
+static const SOC_ENUM_SINGLE_DECL(adc_compand, WM8978_COMPANDING_CONTROL, 1,
+				  wm8978_companding);
+static const SOC_ENUM_SINGLE_DECL(dac_compand, WM8978_COMPANDING_CONTROL, 3,
+				  wm8978_companding);
+static const SOC_ENUM_SINGLE_DECL(eqmode, WM8978_EQ1, 8, wm8978_eqmode);
+static const SOC_ENUM_SINGLE_DECL(eq1, WM8978_EQ1, 5, wm8978_eq1);
+static const SOC_ENUM_SINGLE_DECL(eq2bw, WM8978_EQ2, 8, wm8978_bw);
+static const SOC_ENUM_SINGLE_DECL(eq2, WM8978_EQ2, 5, wm8978_eq2);
+static const SOC_ENUM_SINGLE_DECL(eq3bw, WM8978_EQ3, 8, wm8978_bw);
+static const SOC_ENUM_SINGLE_DECL(eq3, WM8978_EQ3, 5, wm8978_eq3);
+static const SOC_ENUM_SINGLE_DECL(eq4bw, WM8978_EQ4, 8, wm8978_bw);
+static const SOC_ENUM_SINGLE_DECL(eq4, WM8978_EQ4, 5, wm8978_eq4);
+static const SOC_ENUM_SINGLE_DECL(eq5, WM8978_EQ5, 5, wm8978_eq5);
+static const SOC_ENUM_SINGLE_DECL(alc3, WM8978_ALC_CONTROL_3, 8, wm8978_alc3);
+static const SOC_ENUM_SINGLE_DECL(alc1, WM8978_ALC_CONTROL_1, 7, wm8978_alc1);
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
+static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);
+static const DECLARE_TLV_DB_SCALE(boost_tlv, -1500, 300, 1);
+
+static const struct snd_kcontrol_new wm8978_snd_controls[] = {
+
+	SOC_SINGLE("Digital Loopback Switch",
+		WM8978_COMPANDING_CONTROL, 0, 1, 0),
+
+	SOC_ENUM("ADC Companding", adc_compand),
+	SOC_ENUM("DAC Companding", dac_compand),
+
+	SOC_DOUBLE("DAC Inversion Switch", WM8978_DAC_CONTROL, 0, 1, 1, 0),
+
+	SOC_DOUBLE_R_TLV("PCM Volume",
+		WM8978_LEFT_DAC_DIGITAL_VOLUME, WM8978_RIGHT_DAC_DIGITAL_VOLUME,
+		0, 255, 0, digital_tlv),
+
+	SOC_SINGLE("High Pass Filter Switch", WM8978_ADC_CONTROL, 8, 1, 0),
+	SOC_SINGLE("High Pass Cut Off", WM8978_ADC_CONTROL, 4, 7, 0),
+	SOC_DOUBLE("ADC Inversion Switch", WM8978_ADC_CONTROL, 0, 1, 1, 0),
+
+	SOC_DOUBLE_R_TLV("ADC Volume",
+		WM8978_LEFT_ADC_DIGITAL_VOLUME, WM8978_RIGHT_ADC_DIGITAL_VOLUME,
+		0, 255, 0, digital_tlv),
+
+	SOC_ENUM("Equaliser Function", eqmode),
+	SOC_ENUM("EQ1 Cut Off", eq1),
+	SOC_SINGLE_TLV("EQ1 Volume", WM8978_EQ1,  0, 24, 1, eq_tlv),
+
+	SOC_ENUM("Equaliser EQ2 Bandwith", eq2bw),
+	SOC_ENUM("EQ2 Cut Off", eq2),
+	SOC_SINGLE_TLV("EQ2 Volume", WM8978_EQ2,  0, 24, 1, eq_tlv),
+
+	SOC_ENUM("Equaliser EQ3 Bandwith", eq3bw),
+	SOC_ENUM("EQ3 Cut Off", eq3),
+	SOC_SINGLE_TLV("EQ3 Volume", WM8978_EQ3,  0, 24, 1, eq_tlv),
+
+	SOC_ENUM("Equaliser EQ4 Bandwith", eq4bw),
+	SOC_ENUM("EQ4 Cut Off", eq4),
+	SOC_SINGLE_TLV("EQ4 Volume", WM8978_EQ4,  0, 24, 1, eq_tlv),
+
+	SOC_ENUM("EQ5 Cut Off", eq5),
+	SOC_SINGLE_TLV("EQ5 Volume", WM8978_EQ5, 0, 24, 1, eq_tlv),
+
+	SOC_SINGLE("DAC Playback Limiter Switch",
+		WM8978_DAC_LIMITER_1, 8, 1, 0),
+	SOC_SINGLE("DAC Playback Limiter Decay",
+		WM8978_DAC_LIMITER_1, 4, 15, 0),
+	SOC_SINGLE("DAC Playback Limiter Attack",
+		WM8978_DAC_LIMITER_1, 0, 15, 0),
+
+	SOC_SINGLE("DAC Playback Limiter Threshold",
+		WM8978_DAC_LIMITER_2, 4, 7, 0),
+	SOC_SINGLE("DAC Playback Limiter Boost",
+		WM8978_DAC_LIMITER_2, 0, 15, 0),
+
+	SOC_ENUM("ALC Enable Switch", alc1),
+	SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 7, 0),
+	SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0),
+
+	SOC_SINGLE("ALC Capture Hold", WM8978_ALC_CONTROL_2, 4, 7, 0),
+	SOC_SINGLE("ALC Capture Target", WM8978_ALC_CONTROL_2, 0, 15, 0),
+
+	SOC_ENUM("ALC Capture Mode", alc3),
+	SOC_SINGLE("ALC Capture Decay", WM8978_ALC_CONTROL_3, 4, 15, 0),
+	SOC_SINGLE("ALC Capture Attack", WM8978_ALC_CONTROL_3, 0, 15, 0),
+
+	SOC_SINGLE("ALC Capture Noise Gate Switch", WM8978_NOISE_GATE, 3, 1, 0),
+	SOC_SINGLE("ALC Capture Noise Gate Threshold",
+		WM8978_NOISE_GATE, 0, 7, 0),
+
+	SOC_DOUBLE_R("Capture PGA ZC Switch",
+		WM8978_LEFT_INP_PGA_CONTROL, WM8978_RIGHT_INP_PGA_CONTROL,
+		7, 1, 0),
+
+	/* OUT1 - Headphones */
+	SOC_DOUBLE_R("Headphone Playback ZC Switch",
+		WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, 7, 1, 0),
+
+	SOC_DOUBLE_R_TLV("Headphone Playback Volume",
+		WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL,
+		0, 63, 0, spk_tlv),
+
+	/* OUT2 - Speakers */
+	SOC_DOUBLE_R("Speaker Playback ZC Switch",
+		WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 7, 1, 0),
+
+	SOC_DOUBLE_R_TLV("Speaker Playback Volume",
+		WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL,
+		0, 63, 0, spk_tlv),
+
+	/* OUT3/4 - Line Output */
+	SOC_DOUBLE_R("Line Playback Switch",
+		WM8978_OUT3_MIXER_CONTROL, WM8978_OUT4_MIXER_CONTROL, 6, 1, 1),
+
+	/* Mixer #3: Boost (Input) mixer */
+	SOC_DOUBLE_R("PGA Boost (+20dB)",
+		WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL,
+		8, 1, 0),
+	SOC_DOUBLE_R_TLV("L2/R2 Boost Volume",
+		WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL,
+		4, 7, 0, boost_tlv),
+	SOC_DOUBLE_R_TLV("Aux Boost Volume",
+		WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL,
+		0, 7, 0, boost_tlv),
+
+	/* Input PGA volume */
+	SOC_DOUBLE_R_TLV("Input PGA Volume",
+		WM8978_LEFT_INP_PGA_CONTROL, WM8978_RIGHT_INP_PGA_CONTROL,
+		0, 63, 0, inpga_tlv),
+
+	/* Headphone */
+	SOC_DOUBLE_R("Headphone Switch",
+		WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, 6, 1, 1),
+
+	/* Speaker */
+	SOC_DOUBLE_R("Speaker Switch",
+		WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 6, 1, 1),
+
+	/* DAC / ADC oversampling */
+	SOC_SINGLE("DAC 128x Oversampling Switch", WM8978_DAC_CONTROL, 8, 1, 0),
+	SOC_SINGLE("ADC 128x Oversampling Switch", WM8978_ADC_CONTROL, 8, 1, 0),
+};
+
+/* Mixer #1: Output (OUT1, OUT2) Mixer: mix AUX, Input mixer output and DAC */
+static const struct snd_kcontrol_new wm8978_left_out_mixer[] = {
+	SOC_DAPM_SINGLE("Line Bypass Switch", WM8978_LEFT_MIXER_CONTROL, 1, 1, 0),
+	SOC_DAPM_SINGLE("Aux Playback Switch", WM8978_LEFT_MIXER_CONTROL, 5, 1, 0),
+	SOC_DAPM_SINGLE("PCM Playback Switch", WM8978_LEFT_MIXER_CONTROL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8978_right_out_mixer[] = {
+	SOC_DAPM_SINGLE("Line Bypass Switch", WM8978_RIGHT_MIXER_CONTROL, 1, 1, 0),
+	SOC_DAPM_SINGLE("Aux Playback Switch", WM8978_RIGHT_MIXER_CONTROL, 5, 1, 0),
+	SOC_DAPM_SINGLE("PCM Playback Switch", WM8978_RIGHT_MIXER_CONTROL, 0, 1, 0),
+};
+
+/* OUT3/OUT4 Mixer not implemented */
+
+/* Mixer #2: Input PGA Mute */
+static const struct snd_kcontrol_new wm8978_left_input_mixer[] = {
+	SOC_DAPM_SINGLE("L2 Switch", WM8978_INPUT_CONTROL, 2, 1, 0),
+	SOC_DAPM_SINGLE("MicN Switch", WM8978_INPUT_CONTROL, 1, 1, 0),
+	SOC_DAPM_SINGLE("MicP Switch", WM8978_INPUT_CONTROL, 0, 1, 0),
+};
+static const struct snd_kcontrol_new wm8978_right_input_mixer[] = {
+	SOC_DAPM_SINGLE("R2 Switch", WM8978_INPUT_CONTROL, 6, 1, 0),
+	SOC_DAPM_SINGLE("MicN Switch", WM8978_INPUT_CONTROL, 5, 1, 0),
+	SOC_DAPM_SINGLE("MicP Switch", WM8978_INPUT_CONTROL, 4, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8978_dapm_widgets[] = {
+	SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback",
+			 WM8978_POWER_MANAGEMENT_3, 0, 0),
+	SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback",
+			 WM8978_POWER_MANAGEMENT_3, 1, 0),
+	SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture",
+			 WM8978_POWER_MANAGEMENT_2, 0, 0),
+	SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture",
+			 WM8978_POWER_MANAGEMENT_2, 1, 0),
+
+	/* Mixer #1: OUT1,2 */
+	SOC_MIXER_ARRAY("Left Output Mixer", WM8978_POWER_MANAGEMENT_3,
+			2, 0, wm8978_left_out_mixer),
+	SOC_MIXER_ARRAY("Right Output Mixer", WM8978_POWER_MANAGEMENT_3,
+			3, 0, wm8978_right_out_mixer),
+
+	SOC_MIXER_ARRAY("Left Input Mixer", WM8978_POWER_MANAGEMENT_2,
+			2, 0, wm8978_left_input_mixer),
+	SOC_MIXER_ARRAY("Right Input Mixer", WM8978_POWER_MANAGEMENT_2,
+			3, 0, wm8978_right_input_mixer),
+
+	SND_SOC_DAPM_PGA("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2,
+			 4, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2,
+			 5, 0, NULL, 0),
+
+	SND_SOC_DAPM_PGA("Left Capture PGA", WM8978_LEFT_INP_PGA_CONTROL,
+			 6, 1, NULL, 0),
+	SND_SOC_DAPM_PGA("Right Capture PGA", WM8978_RIGHT_INP_PGA_CONTROL,
+			 6, 1, NULL, 0),
+
+	SND_SOC_DAPM_PGA("Left Headphone Out", WM8978_POWER_MANAGEMENT_2,
+			 7, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right Headphone Out", WM8978_POWER_MANAGEMENT_2,
+			 8, 0, NULL, 0),
+
+	SND_SOC_DAPM_PGA("Left Speaker Out", WM8978_POWER_MANAGEMENT_3,
+			 6, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right Speaker Out", WM8978_POWER_MANAGEMENT_3,
+			 5, 0, NULL, 0),
+
+	SND_SOC_DAPM_MIXER("OUT4 VMID", WM8978_POWER_MANAGEMENT_3,
+			   8, 0, NULL, 0),
+
+	SND_SOC_DAPM_MICBIAS("Mic Bias", WM8978_POWER_MANAGEMENT_1, 4, 0),
+
+	SND_SOC_DAPM_INPUT("LMICN"),
+	SND_SOC_DAPM_INPUT("LMICP"),
+	SND_SOC_DAPM_INPUT("RMICN"),
+	SND_SOC_DAPM_INPUT("RMICP"),
+	SND_SOC_DAPM_INPUT("LAUX"),
+	SND_SOC_DAPM_INPUT("RAUX"),
+	SND_SOC_DAPM_INPUT("L2"),
+	SND_SOC_DAPM_INPUT("R2"),
+	SND_SOC_DAPM_OUTPUT("LHP"),
+	SND_SOC_DAPM_OUTPUT("RHP"),
+	SND_SOC_DAPM_OUTPUT("LSPK"),
+	SND_SOC_DAPM_OUTPUT("RSPK"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Output mixer */
+	{"Right Output Mixer", "PCM Playback Switch", "Right DAC"},
+	{"Right Output Mixer", "Aux Playback Switch", "RAUX"},
+	{"Right Output Mixer", "Line Bypass Switch", "Right Boost Mixer"},
+
+	{"Left Output Mixer", "PCM Playback Switch", "Left DAC"},
+	{"Left Output Mixer", "Aux Playback Switch", "LAUX"},
+	{"Left Output Mixer", "Line Bypass Switch", "Left Boost Mixer"},
+
+	/* Outputs */
+	{"Right Headphone Out", NULL, "Right Output Mixer"},
+	{"RHP", NULL, "Right Headphone Out"},
+
+	{"Left Headphone Out", NULL, "Left Output Mixer"},
+	{"LHP", NULL, "Left Headphone Out"},
+
+	{"Right Speaker Out", NULL, "Right Output Mixer"},
+	{"RSPK", NULL, "Right Speaker Out"},
+
+	{"Left Speaker Out", NULL, "Left Output Mixer"},
+	{"LSPK", NULL, "Left Speaker Out"},
+
+	/* Boost Mixer */
+	{"Right ADC", NULL, "Right Boost Mixer"},
+
+	{"Right Boost Mixer", NULL, "RAUX"},
+	{"Right Boost Mixer", NULL, "Right Capture PGA"},
+	{"Right Boost Mixer", NULL, "R2"},
+
+	{"Left ADC", NULL, "Left Boost Mixer"},
+
+	{"Left Boost Mixer", NULL, "LAUX"},
+	{"Left Boost Mixer", NULL, "Left Capture PGA"},
+	{"Left Boost Mixer", NULL, "L2"},
+
+	/* Input PGA */
+	{"Right Capture PGA", NULL, "Right Input Mixer"},
+	{"Left Capture PGA", NULL, "Left Input Mixer"},
+
+	{"Right Input Mixer", "R2 Switch", "R2"},
+	{"Right Input Mixer", "MicN Switch", "RMICN"},
+	{"Right Input Mixer", "MicP Switch", "RMICP"},
+
+	{"Left Input Mixer", "L2 Switch", "L2"},
+	{"Left Input Mixer", "MicN Switch", "LMICN"},
+	{"Left Input Mixer", "MicP Switch", "LMICP"},
+};
+
+static int wm8978_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8978_dapm_widgets,
+				  ARRAY_SIZE(wm8978_dapm_widgets));
+
+	/* set up the WM8978 audio map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+/* PLL divisors */
+struct wm8978_pll_div {
+	u32 k;
+	u8 n;
+	u8 div2;
+};
+
+#define FIXED_PLL_SIZE (1 << 24)
+
+static void pll_factors(struct snd_soc_codec *codec,
+		struct wm8978_pll_div *pll_div, unsigned int target, unsigned int source)
+{
+	u64 k_part;
+	unsigned int k, n_div, n_mod;
+
+	n_div = target / source;
+	if (n_div < 6) {
+		source >>= 1;
+		pll_div->div2 = 1;
+		n_div = target / source;
+	} else {
+		pll_div->div2 = 0;
+	}
+
+	if (n_div < 6 || n_div > 12)
+		dev_warn(codec->dev,
+			 "WM8978 N value exceeds recommended range! N = %u\n",
+			 n_div);
+
+	pll_div->n = n_div;
+	n_mod = target - source * n_div;
+	k_part = FIXED_PLL_SIZE * (long long)n_mod + source / 2;
+
+	do_div(k_part, source);
+
+	k = k_part & 0xFFFFFFFF;
+
+	pll_div->k = k;
+}
+
+/* MCLK dividers */
+static const int mclk_numerator[]	= {1, 3, 2, 3, 4, 6, 8, 12};
+static const int mclk_denominator[]	= {1, 2, 1, 1, 1, 1, 1, 1};
+
+/*
+ * find index >= idx, such that, for a given f_out,
+ * 3 * f_mclk / 4 <= f_PLLOUT < 13 * f_mclk / 4
+ * f_out can be f_256fs or f_opclk, currently only used for f_256fs. Can be
+ * generalised for f_opclk with suitable coefficient arrays, but currently
+ * the OPCLK divisor is calculated directly, not iteratively.
+ */
+static int wm8978_enum_mclk(unsigned int f_out, unsigned int f_mclk,
+			    unsigned int *f_pllout)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++) {
+		unsigned int f_pllout_x4 = 4 * f_out * mclk_numerator[i] /
+			mclk_denominator[i];
+		if (3 * f_mclk <= f_pllout_x4 && f_pllout_x4 < 13 * f_mclk) {
+			*f_pllout = f_pllout_x4 / 4;
+			return i;
+		}
+	}
+
+	return -EINVAL;
+}
+
+/*
+ * Calculate internal frequencies and dividers, according to Figure 40
+ * "PLL and Clock Select Circuit" in WM8978 datasheet Rev. 2.6
+ */
+static int wm8978_configure_pll(struct snd_soc_codec *codec)
+{
+	struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+	struct wm8978_pll_div pll_div;
+	unsigned int f_opclk = wm8978->f_opclk, f_mclk = wm8978->f_mclk,
+		f_256fs = wm8978->f_256fs;
+	unsigned int f2;
+
+	if (!f_mclk)
+		return -EINVAL;
+
+	if (f_opclk) {
+		unsigned int opclk_div;
+		/* Cannot set up MCLK divider now, do later */
+		wm8978->mclk_idx = -1;
+
+		/*
+		 * The user needs OPCLK. Choose OPCLKDIV to put
+		 * 6 <= R = f2 / f1 < 13, 1 <= OPCLKDIV <= 4.
+		 * f_opclk = f_mclk * prescale * R / 4 / OPCLKDIV, where
+		 * prescale = 1, or prescale = 2. Prescale is calculated inside
+		 * pll_factors(). We have to select f_PLLOUT, such that
+		 * f_mclk * 3 / 4 <= f_PLLOUT < f_mclk * 13 / 4. Must be
+		 * f_mclk * 3 / 16 <= f_opclk < f_mclk * 13 / 4.
+		 */
+		if (16 * f_opclk < 3 * f_mclk || 4 * f_opclk >= 13 * f_mclk)
+			return -EINVAL;
+
+		if (4 * f_opclk < 3 * f_mclk)
+			/* Have to use OPCLKDIV */
+			opclk_div = (3 * f_mclk / 4 + f_opclk - 1) / f_opclk;
+		else
+			opclk_div = 1;
+
+		dev_dbg(codec->dev, "%s: OPCLKDIV=%d\n", __func__, opclk_div);
+
+		snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 0x30,
+				    (opclk_div - 1) << 4);
+
+		wm8978->f_pllout = f_opclk * opclk_div;
+	} else if (f_256fs) {
+		/*
+		 * Not using OPCLK, but PLL is used for the codec, choose R:
+		 * 6 <= R = f2 / f1 < 13, to put 1 <= MCLKDIV <= 12.
+		 * f_256fs = f_mclk * prescale * R / 4 / MCLKDIV, where
+		 * prescale = 1, or prescale = 2. Prescale is calculated inside
+		 * pll_factors(). We have to select f_PLLOUT, such that
+		 * f_mclk * 3 / 4 <= f_PLLOUT < f_mclk * 13 / 4. Must be
+		 * f_mclk * 3 / 48 <= f_256fs < f_mclk * 13 / 4. This means MCLK
+		 * must be 3.781MHz <= f_MCLK <= 32.768MHz
+		 */
+		int idx = wm8978_enum_mclk(f_256fs, f_mclk, &wm8978->f_pllout);
+		if (idx < 0)
+			return idx;
+
+		wm8978->mclk_idx = idx;
+
+		/* GPIO1 into default mode as input - before configuring PLL */
+		snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 0);
+	} else {
+		return -EINVAL;
+	}
+
+	f2 = wm8978->f_pllout * 4;
+
+	dev_dbg(codec->dev, "%s: f_MCLK=%uHz, f_PLLOUT=%uHz\n", __func__,
+		wm8978->f_mclk, wm8978->f_pllout);
+
+	pll_factors(codec, &pll_div, f2, wm8978->f_mclk);
+
+	dev_dbg(codec->dev, "%s: calculated PLL N=0x%x, K=0x%x, div2=%d\n",
+		__func__, pll_div.n, pll_div.k, pll_div.div2);
+
+	/* Turn PLL off for configuration... */
+	snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0);
+
+	snd_soc_write(codec, WM8978_PLL_N, (pll_div.div2 << 4) | pll_div.n);
+	snd_soc_write(codec, WM8978_PLL_K1, pll_div.k >> 18);
+	snd_soc_write(codec, WM8978_PLL_K2, (pll_div.k >> 9) & 0x1ff);
+	snd_soc_write(codec, WM8978_PLL_K3, pll_div.k & 0x1ff);
+
+	/* ...and on again */
+	snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0x20);
+
+	if (f_opclk)
+		/* Output PLL (OPCLK) to GPIO1 */
+		snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 4);
+
+	return 0;
+}
+
+/*
+ * Configure WM8978 clock dividers.
+ */
+static int wm8978_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+				 int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	switch (div_id) {
+	case WM8978_OPCLKRATE:
+		wm8978->f_opclk = div;
+
+		if (wm8978->f_mclk)
+			/*
+			 * We know the MCLK frequency, the user has requested
+			 * OPCLK, configure the PLL based on that and start it
+			 * and OPCLK immediately. We will configure PLL to match
+			 * user-requested OPCLK frquency as good as possible.
+			 * In fact, it is likely, that matching the sampling
+			 * rate, when it becomes known, is more important, and
+			 * we will not be reconfiguring PLL then, because we
+			 * must not interrupt OPCLK. But it should be fine,
+			 * because typically the user will request OPCLK to run
+			 * at 256fs or 512fs, and for these cases we will also
+			 * find an exact MCLK divider configuration - it will
+			 * be equal to or double the OPCLK divisor.
+			 */
+			ret = wm8978_configure_pll(codec);
+		break;
+	case WM8978_BCLKDIV:
+		if (div & ~0x1c)
+			return -EINVAL;
+		snd_soc_update_bits(codec, WM8978_CLOCKING, 0x1c, div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dev_dbg(codec->dev, "%s: ID %d, value %u\n", __func__, div_id, div);
+
+	return ret;
+}
+
+/*
+ * @freq:	when .set_pll() us not used, freq is codec MCLK input frequency
+ */
+static int wm8978_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id,
+				 unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+
+	dev_dbg(codec->dev, "%s: ID %d, freq %u\n", __func__, clk_id, freq);
+
+	if (freq) {
+		wm8978->f_mclk = freq;
+
+		/* Even if MCLK is used for system clock, might have to drive OPCLK */
+		if (wm8978->f_opclk)
+			ret = wm8978_configure_pll(codec);
+
+		/* Our sysclk is fixed to 256 * fs, will configure in .hw_params()  */
+
+		if (!ret)
+			wm8978->sysclk = clk_id;
+	}
+
+	if (wm8978->sysclk == WM8978_PLL && (!freq || clk_id == WM8978_MCLK)) {
+		/* Clock CODEC directly from MCLK */
+		snd_soc_update_bits(codec, WM8978_CLOCKING, 0x100, 0);
+
+		/* GPIO1 into default mode as input - before configuring PLL */
+		snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 0);
+
+		/* Turn off PLL */
+		snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0);
+		wm8978->sysclk = WM8978_MCLK;
+		wm8978->f_pllout = 0;
+		wm8978->f_opclk = 0;
+	}
+
+	return ret;
+}
+
+/*
+ * Set ADC and Voice DAC format.
+ */
+static int wm8978_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	/*
+	 * BCLK polarity mask = 0x100, LRC clock polarity mask = 0x80,
+	 * Data Format mask = 0x18: all will be calculated anew
+	 */
+	u16 iface = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x198;
+	u16 clk = snd_soc_read(codec, WM8978_CLOCKING);
+
+	dev_dbg(codec->dev, "%s\n", __func__);
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		clk |= 1;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		clk &= ~1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x10;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x8;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x18;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x180;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x100;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x80;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface);
+	snd_soc_write(codec, WM8978_CLOCKING, clk);
+
+	return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8978_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+	/* Word length mask = 0x60 */
+	u16 iface_ctl = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x60;
+	/* Sampling rate mask = 0xe (for filters) */
+	u16 add_ctl = snd_soc_read(codec, WM8978_ADDITIONAL_CONTROL) & ~0xe;
+	u16 clking = snd_soc_read(codec, WM8978_CLOCKING);
+	enum wm8978_sysclk_src current_clk_id = clking & 0x100 ?
+		WM8978_PLL : WM8978_MCLK;
+	unsigned int f_sel, diff, diff_best = INT_MAX;
+	int i, best = 0;
+
+	if (!wm8978->f_mclk)
+		return -EINVAL;
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface_ctl |= 0x20;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface_ctl |= 0x40;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface_ctl |= 0x60;
+		break;
+	}
+
+	/* filter coefficient */
+	switch (params_rate(params)) {
+	case 8000:
+		add_ctl |= 0x5 << 1;
+		break;
+	case 11025:
+		add_ctl |= 0x4 << 1;
+		break;
+	case 16000:
+		add_ctl |= 0x3 << 1;
+		break;
+	case 22050:
+		add_ctl |= 0x2 << 1;
+		break;
+	case 32000:
+		add_ctl |= 0x1 << 1;
+		break;
+	case 44100:
+	case 48000:
+		break;
+	}
+
+	/* Sampling rate is known now, can configure the MCLK divider */
+	wm8978->f_256fs = params_rate(params) * 256;
+
+	if (wm8978->sysclk == WM8978_MCLK) {
+		wm8978->mclk_idx = -1;
+		f_sel = wm8978->f_mclk;
+	} else {
+		if (!wm8978->f_pllout) {
+			/* We only enter here, if OPCLK is not used */
+			int ret = wm8978_configure_pll(codec);
+			if (ret < 0)
+				return ret;
+		}
+		f_sel = wm8978->f_pllout;
+	}
+
+	if (wm8978->mclk_idx < 0) {
+		/* Either MCLK is used directly, or OPCLK is used */
+		if (f_sel < wm8978->f_256fs || f_sel > 12 * wm8978->f_256fs)
+			return -EINVAL;
+
+		for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++) {
+			diff = abs(wm8978->f_256fs * 3 -
+				   f_sel * 3 * mclk_denominator[i] / mclk_numerator[i]);
+
+			if (diff < diff_best) {
+				diff_best = diff;
+				best = i;
+			}
+
+			if (!diff)
+				break;
+		}
+	} else {
+		/* OPCLK not used, codec driven by PLL */
+		best = wm8978->mclk_idx;
+		diff = 0;
+	}
+
+	if (diff)
+		dev_warn(codec->dev, "Imprecise sampling rate: %uHz%s\n",
+			f_sel * mclk_denominator[best] / mclk_numerator[best] / 256,
+			wm8978->sysclk == WM8978_MCLK ?
+			", consider using PLL" : "");
+
+	dev_dbg(codec->dev, "%s: fmt %d, rate %u, MCLK divisor #%d\n", __func__,
+		params_format(params), params_rate(params), best);
+
+	/* MCLK divisor mask = 0xe0 */
+	snd_soc_update_bits(codec, WM8978_CLOCKING, 0xe0, best << 5);
+
+	snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl);
+	snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl);
+
+	if (wm8978->sysclk != current_clk_id) {
+		if (wm8978->sysclk == WM8978_PLL)
+			/* Run CODEC from PLL instead of MCLK */
+			snd_soc_update_bits(codec, WM8978_CLOCKING,
+					    0x100, 0x100);
+		else
+			/* Clock CODEC directly from MCLK */
+			snd_soc_update_bits(codec, WM8978_CLOCKING, 0x100, 0);
+	}
+
+	return 0;
+}
+
+static int wm8978_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+
+	dev_dbg(codec->dev, "%s: %d\n", __func__, mute);
+
+	if (mute)
+		snd_soc_update_bits(codec, WM8978_DAC_CONTROL, 0x40, 0x40);
+	else
+		snd_soc_update_bits(codec, WM8978_DAC_CONTROL, 0x40, 0);
+
+	return 0;
+}
+
+static int wm8978_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 power1 = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1) & ~3;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+		power1 |= 1;  /* VMID 75k */
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, power1);
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		/* bit 3: enable bias, bit 2: enable I/O tie off buffer */
+		power1 |= 0xc;
+
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Initial cap charge at VMID 5k */
+			snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1,
+				      power1 | 0x3);
+			mdelay(100);
+		}
+
+		power1 |= 0x2;  /* VMID 500k */
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, power1);
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* Preserve PLL - OPCLK may be used by someone */
+		snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, ~0x20, 0);
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0);
+		snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0);
+		break;
+	}
+
+	dev_dbg(codec->dev, "%s: %d, %x\n", __func__, level, power1);
+
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8978_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8978_dai_ops = {
+	.hw_params	= wm8978_hw_params,
+	.digital_mute	= wm8978_mute,
+	.set_fmt	= wm8978_set_dai_fmt,
+	.set_clkdiv	= wm8978_set_dai_clkdiv,
+	.set_sysclk	= wm8978_set_dai_sysclk,
+};
+
+/* Also supports 12kHz */
+static struct snd_soc_dai_driver wm8978_dai = {
+	.name = "wm8978-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = WM8978_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = WM8978_FORMATS,
+	},
+	.ops = &wm8978_dai_ops,
+};
+
+static int wm8978_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8978_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	/* Also switch PLL off */
+	snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 0);
+
+	return 0;
+}
+
+static int wm8978_resume(struct snd_soc_codec *codec)
+{
+	struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+	int i;
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 0; i < ARRAY_SIZE(wm8978_reg); i++) {
+		if (i == WM8978_RESET)
+			continue;
+		if (cache[i] != wm8978_reg[i])
+			snd_soc_write(codec, i, cache[i]);
+	}
+
+	wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	if (wm8978->f_pllout)
+		/* Switch PLL on */
+		snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0x20);
+
+	return 0;
+}
+
+/*
+ * These registers contain an "update" bit - bit 8. This means, for example,
+ * that one can write new DAC digital volume for both channels, but only when
+ * the update bit is set, will also the volume be updated - simultaneously for
+ * both channels.
+ */
+static const int update_reg[] = {
+	WM8978_LEFT_DAC_DIGITAL_VOLUME,
+	WM8978_RIGHT_DAC_DIGITAL_VOLUME,
+	WM8978_LEFT_ADC_DIGITAL_VOLUME,
+	WM8978_RIGHT_ADC_DIGITAL_VOLUME,
+	WM8978_LEFT_INP_PGA_CONTROL,
+	WM8978_RIGHT_INP_PGA_CONTROL,
+	WM8978_LOUT1_HP_CONTROL,
+	WM8978_ROUT1_HP_CONTROL,
+	WM8978_LOUT2_SPK_CONTROL,
+	WM8978_ROUT2_SPK_CONTROL,
+};
+
+static int wm8978_probe(struct snd_soc_codec *codec)
+{
+	struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0, i;
+
+	/*
+	 * Set default system clock to PLL, it is more precise, this is also the
+	 * default hardware setting
+	 */
+	wm8978->sysclk = WM8978_PLL;
+	codec->control_data = wm8978->control_data;
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_I2C);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * Set the update bit in all registers, that have one. This way all
+	 * writes to those registers will also cause the update bit to be
+	 * written.
+	 */
+	for (i = 0; i < ARRAY_SIZE(update_reg); i++)
+		((u16 *)codec->reg_cache)[update_reg[i]] |= 0x100;
+
+	/* Reset the codec */
+	ret = snd_soc_write(codec, WM8978_RESET, 0);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		return ret;
+	}
+
+	wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	snd_soc_add_controls(codec, wm8978_snd_controls,
+			     ARRAY_SIZE(wm8978_snd_controls));
+	wm8978_add_widgets(codec);
+
+	return 0;
+}
+
+/* power down chip */
+static int wm8978_remove(struct snd_soc_codec *codec)
+{
+	wm8978_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8978 = {
+	.probe =	wm8978_probe,
+	.remove =	wm8978_remove,
+	.suspend =	wm8978_suspend,
+	.resume =	wm8978_resume,
+	.set_bias_level = wm8978_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8978_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8978_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8978_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8978_priv *wm8978;
+	int ret;
+
+	wm8978 = kzalloc(sizeof(struct wm8978_priv), GFP_KERNEL);
+	if (wm8978 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8978);
+	wm8978->control_data = i2c;
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8978, &wm8978_dai, 1);
+	if (ret < 0)
+		kfree(wm8978);
+	return ret;
+}
+
+static __devexit int wm8978_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8978_i2c_id[] = {
+	{ "wm8978", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8978_i2c_id);
+
+static struct i2c_driver wm8978_i2c_driver = {
+	.driver = {
+		.name = "wm8978",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8978_i2c_probe,
+	.remove =   __devexit_p(wm8978_i2c_remove),
+	.id_table = wm8978_i2c_id,
+};
+#endif
+
+static int __init wm8978_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8978_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8978 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8978_modinit);
+
+static void __exit wm8978_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8978_i2c_driver);
+#endif
+}
+module_exit(wm8978_exit);
+
+MODULE_DESCRIPTION("ASoC WM8978 codec driver");
+MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8978.h b/linux/sound/soc/codecs/wm8978.h
new file mode 100644
index 0000000..c75525b
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8978.h
@@ -0,0 +1,83 @@
+/*
+ * wm8978.h		--  codec driver for WM8978
+ *
+ * Copyright 2009 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __WM8978_H__
+#define __WM8978_H__
+
+/*
+ * Register values.
+ */
+#define WM8978_RESET				0x00
+#define WM8978_POWER_MANAGEMENT_1		0x01
+#define WM8978_POWER_MANAGEMENT_2		0x02
+#define WM8978_POWER_MANAGEMENT_3		0x03
+#define WM8978_AUDIO_INTERFACE			0x04
+#define WM8978_COMPANDING_CONTROL		0x05
+#define WM8978_CLOCKING				0x06
+#define WM8978_ADDITIONAL_CONTROL		0x07
+#define WM8978_GPIO_CONTROL			0x08
+#define WM8978_JACK_DETECT_CONTROL_1		0x09
+#define WM8978_DAC_CONTROL			0x0A
+#define WM8978_LEFT_DAC_DIGITAL_VOLUME		0x0B
+#define WM8978_RIGHT_DAC_DIGITAL_VOLUME		0x0C
+#define WM8978_JACK_DETECT_CONTROL_2		0x0D
+#define WM8978_ADC_CONTROL			0x0E
+#define WM8978_LEFT_ADC_DIGITAL_VOLUME		0x0F
+#define WM8978_RIGHT_ADC_DIGITAL_VOLUME		0x10
+#define WM8978_EQ1				0x12
+#define WM8978_EQ2				0x13
+#define WM8978_EQ3				0x14
+#define WM8978_EQ4				0x15
+#define WM8978_EQ5				0x16
+#define WM8978_DAC_LIMITER_1			0x18
+#define WM8978_DAC_LIMITER_2			0x19
+#define WM8978_NOTCH_FILTER_1			0x1b
+#define WM8978_NOTCH_FILTER_2			0x1c
+#define WM8978_NOTCH_FILTER_3			0x1d
+#define WM8978_NOTCH_FILTER_4			0x1e
+#define WM8978_ALC_CONTROL_1			0x20
+#define WM8978_ALC_CONTROL_2			0x21
+#define WM8978_ALC_CONTROL_3			0x22
+#define WM8978_NOISE_GATE			0x23
+#define WM8978_PLL_N				0x24
+#define WM8978_PLL_K1				0x25
+#define WM8978_PLL_K2				0x26
+#define WM8978_PLL_K3				0x27
+#define WM8978_3D_CONTROL			0x29
+#define WM8978_BEEP_CONTROL			0x2b
+#define WM8978_INPUT_CONTROL			0x2c
+#define WM8978_LEFT_INP_PGA_CONTROL		0x2d
+#define WM8978_RIGHT_INP_PGA_CONTROL		0x2e
+#define WM8978_LEFT_ADC_BOOST_CONTROL		0x2f
+#define WM8978_RIGHT_ADC_BOOST_CONTROL		0x30
+#define WM8978_OUTPUT_CONTROL			0x31
+#define WM8978_LEFT_MIXER_CONTROL		0x32
+#define WM8978_RIGHT_MIXER_CONTROL		0x33
+#define WM8978_LOUT1_HP_CONTROL			0x34
+#define WM8978_ROUT1_HP_CONTROL			0x35
+#define WM8978_LOUT2_SPK_CONTROL		0x36
+#define WM8978_ROUT2_SPK_CONTROL		0x37
+#define WM8978_OUT3_MIXER_CONTROL		0x38
+#define WM8978_OUT4_MIXER_CONTROL		0x39
+
+#define WM8978_CACHEREGNUM			58
+
+/* Clock divider Id's */
+enum wm8978_clk_id {
+	WM8978_OPCLKRATE,
+	WM8978_BCLKDIV,
+};
+
+enum wm8978_sysclk_src {
+	WM8978_PLL,
+	WM8978_MCLK
+};
+
+#endif	/* __WM8978_H__ */
diff --git a/linux/sound/soc/codecs/wm8985.c b/linux/sound/soc/codecs/wm8985.c
new file mode 100644
index 0000000..fd2e7cc
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8985.c
@@ -0,0 +1,1192 @@
+/*
+ * wm8985.c  --  WM8985 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * TODO:
+ *  o Add OUT3/OUT4 mixer controls.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8985.h"
+
+#define WM8985_NUM_SUPPLIES 4
+static const char *wm8985_supply_names[WM8985_NUM_SUPPLIES] = {
+	"DCVDD",
+	"DBVDD",
+	"AVDD1",
+	"AVDD2"
+};
+
+static const u16 wm8985_reg_defs[] = {
+	0x0000,     /* R0  - Software Reset */
+	0x0000,     /* R1  - Power management 1 */
+	0x0000,     /* R2  - Power management 2 */
+	0x0000,     /* R3  - Power management 3 */
+	0x0050,     /* R4  - Audio Interface */
+	0x0000,     /* R5  - Companding control */
+	0x0140,     /* R6  - Clock Gen control */
+	0x0000,     /* R7  - Additional control */
+	0x0000,     /* R8  - GPIO Control */
+	0x0000,     /* R9  - Jack Detect Control 1 */
+	0x0000,     /* R10 - DAC Control */
+	0x00FF,     /* R11 - Left DAC digital Vol */
+	0x00FF,     /* R12 - Right DAC digital vol */
+	0x0000,     /* R13 - Jack Detect Control 2 */
+	0x0100,     /* R14 - ADC Control */
+	0x00FF,     /* R15 - Left ADC Digital Vol */
+	0x00FF,     /* R16 - Right ADC Digital Vol */
+	0x0000,     /* R17 */
+	0x012C,     /* R18 - EQ1 - low shelf */
+	0x002C,     /* R19 - EQ2 - peak 1 */
+	0x002C,     /* R20 - EQ3 - peak 2 */
+	0x002C,     /* R21 - EQ4 - peak 3 */
+	0x002C,     /* R22 - EQ5 - high shelf */
+	0x0000,     /* R23 */
+	0x0032,     /* R24 - DAC Limiter 1 */
+	0x0000,     /* R25 - DAC Limiter 2 */
+	0x0000,     /* R26 */
+	0x0000,     /* R27 - Notch Filter 1 */
+	0x0000,     /* R28 - Notch Filter 2 */
+	0x0000,     /* R29 - Notch Filter 3 */
+	0x0000,     /* R30 - Notch Filter 4 */
+	0x0000,     /* R31 */
+	0x0038,     /* R32 - ALC control 1 */
+	0x000B,     /* R33 - ALC control 2 */
+	0x0032,     /* R34 - ALC control 3 */
+	0x0000,     /* R35 - Noise Gate */
+	0x0008,     /* R36 - PLL N */
+	0x000C,     /* R37 - PLL K 1 */
+	0x0093,     /* R38 - PLL K 2 */
+	0x00E9,     /* R39 - PLL K 3 */
+	0x0000,     /* R40 */
+	0x0000,     /* R41 - 3D control */
+	0x0000,     /* R42 - OUT4 to ADC */
+	0x0000,     /* R43 - Beep control */
+	0x0033,     /* R44 - Input ctrl */
+	0x0010,     /* R45 - Left INP PGA gain ctrl */
+	0x0010,     /* R46 - Right INP PGA gain ctrl */
+	0x0100,     /* R47 - Left ADC BOOST ctrl */
+	0x0100,     /* R48 - Right ADC BOOST ctrl */
+	0x0002,     /* R49 - Output ctrl */
+	0x0001,     /* R50 - Left mixer ctrl */
+	0x0001,     /* R51 - Right mixer ctrl */
+	0x0039,     /* R52 - LOUT1 (HP) volume ctrl */
+	0x0039,     /* R53 - ROUT1 (HP) volume ctrl */
+	0x0039,     /* R54 - LOUT2 (SPK) volume ctrl */
+	0x0039,     /* R55 - ROUT2 (SPK) volume ctrl */
+	0x0001,     /* R56 - OUT3 mixer ctrl */
+	0x0001,     /* R57 - OUT4 (MONO) mix ctrl */
+	0x0001,     /* R58 */
+	0x0000,     /* R59 */
+	0x0004,     /* R60 - OUTPUT ctrl */
+	0x0000,     /* R61 - BIAS CTRL */
+	0x0180,     /* R62 */
+	0x0000      /* R63 */
+};
+
+/*
+ * latch bit 8 of these registers to ensure instant
+ * volume updates
+ */
+static const int volume_update_regs[] = {
+	WM8985_LEFT_DAC_DIGITAL_VOL,
+	WM8985_RIGHT_DAC_DIGITAL_VOL,
+	WM8985_LEFT_ADC_DIGITAL_VOL,
+	WM8985_RIGHT_ADC_DIGITAL_VOL,
+	WM8985_LOUT2_SPK_VOLUME_CTRL,
+	WM8985_ROUT2_SPK_VOLUME_CTRL,
+	WM8985_LOUT1_HP_VOLUME_CTRL,
+	WM8985_ROUT1_HP_VOLUME_CTRL,
+	WM8985_LEFT_INP_PGA_GAIN_CTRL,
+	WM8985_RIGHT_INP_PGA_GAIN_CTRL
+};
+
+struct wm8985_priv {
+	enum snd_soc_control_type control_type;
+	struct regulator_bulk_data supplies[WM8985_NUM_SUPPLIES];
+	unsigned int sysclk;
+	unsigned int bclk;
+};
+
+static const struct {
+	int div;
+	int ratio;
+} fs_ratios[] = {
+	{ 10, 128 },
+	{ 15, 192 },
+	{ 20, 256 },
+	{ 30, 384 },
+	{ 40, 512 },
+	{ 60, 768 },
+	{ 80, 1024 },
+	{ 120, 1536 }
+};
+
+static const int srates[] = { 48000, 32000, 24000, 16000, 12000, 8000 };
+
+static const int bclk_divs[] = {
+	1, 2, 4, 8, 16, 32
+};
+
+static int eqmode_get(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_value *ucontrol);
+static int eqmode_put(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_value *ucontrol);
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -12700, 50, 1);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+static const DECLARE_TLV_DB_SCALE(lim_thresh_tlv, -600, 100, 0);
+static const DECLARE_TLV_DB_SCALE(lim_boost_tlv, 0, 100, 0);
+static const DECLARE_TLV_DB_SCALE(alc_min_tlv, -1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(alc_max_tlv, -675, 600, 0);
+static const DECLARE_TLV_DB_SCALE(alc_tar_tlv, -2250, 150, 0);
+static const DECLARE_TLV_DB_SCALE(pga_vol_tlv, -1200, 75, 0);
+static const DECLARE_TLV_DB_SCALE(boost_tlv, -1200, 300, 1);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(aux_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0);
+static const DECLARE_TLV_DB_SCALE(pga_boost_tlv, 0, 2000, 0);
+
+static const char *alc_sel_text[] = { "Off", "Right", "Left", "Stereo" };
+static const SOC_ENUM_SINGLE_DECL(alc_sel, WM8985_ALC_CONTROL_1, 7,
+				  alc_sel_text);
+
+static const char *alc_mode_text[] = { "ALC", "Limiter" };
+static const SOC_ENUM_SINGLE_DECL(alc_mode, WM8985_ALC_CONTROL_3, 8,
+				  alc_mode_text);
+
+static const char *filter_mode_text[] = { "Audio", "Application" };
+static const SOC_ENUM_SINGLE_DECL(filter_mode, WM8985_ADC_CONTROL, 7,
+				  filter_mode_text);
+
+static const char *eq_bw_text[] = { "Narrow", "Wide" };
+static const char *eqmode_text[] = { "Capture", "Playback" };
+static const SOC_ENUM_SINGLE_EXT_DECL(eqmode, eqmode_text);
+
+static const char *eq1_cutoff_text[] = {
+	"80Hz", "105Hz", "135Hz", "175Hz"
+};
+static const SOC_ENUM_SINGLE_DECL(eq1_cutoff, WM8985_EQ1_LOW_SHELF, 5,
+				  eq1_cutoff_text);
+static const char *eq2_cutoff_text[] = {
+	"230Hz", "300Hz", "385Hz", "500Hz"
+};
+static const SOC_ENUM_SINGLE_DECL(eq2_bw, WM8985_EQ2_PEAK_1, 8, eq_bw_text);
+static const SOC_ENUM_SINGLE_DECL(eq2_cutoff, WM8985_EQ2_PEAK_1, 5,
+				  eq2_cutoff_text);
+static const char *eq3_cutoff_text[] = {
+	"650Hz", "850Hz", "1.1kHz", "1.4kHz"
+};
+static const SOC_ENUM_SINGLE_DECL(eq3_bw, WM8985_EQ3_PEAK_2, 8, eq_bw_text);
+static const SOC_ENUM_SINGLE_DECL(eq3_cutoff, WM8985_EQ3_PEAK_2, 5,
+				  eq3_cutoff_text);
+static const char *eq4_cutoff_text[] = {
+	"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz"
+};
+static const SOC_ENUM_SINGLE_DECL(eq4_bw, WM8985_EQ4_PEAK_3, 8, eq_bw_text);
+static const SOC_ENUM_SINGLE_DECL(eq4_cutoff, WM8985_EQ4_PEAK_3, 5,
+				  eq4_cutoff_text);
+static const char *eq5_cutoff_text[] = {
+	"5.3kHz", "6.9kHz", "9kHz", "11.7kHz"
+};
+static const SOC_ENUM_SINGLE_DECL(eq5_cutoff, WM8985_EQ5_HIGH_SHELF, 5,
+				  eq5_cutoff_text);
+
+static const char *speaker_mode_text[] = { "Class A/B", "Class D" };
+static const SOC_ENUM_SINGLE_DECL(speaker_mode, 0x17, 8, speaker_mode_text);
+
+static const char *depth_3d_text[] = {
+	"Off",
+	"6.67%",
+	"13.3%",
+	"20%",
+	"26.7%",
+	"33.3%",
+	"40%",
+	"46.6%",
+	"53.3%",
+	"60%",
+	"66.7%",
+	"73.3%",
+	"80%",
+	"86.7%",
+	"93.3%",
+	"100%"
+};
+static const SOC_ENUM_SINGLE_DECL(depth_3d, WM8985_3D_CONTROL, 0,
+				  depth_3d_text);
+
+static const struct snd_kcontrol_new wm8985_snd_controls[] = {
+	SOC_SINGLE("Digital Loopback Switch", WM8985_COMPANDING_CONTROL,
+		0, 1, 0),
+
+	SOC_ENUM("ALC Capture Function", alc_sel),
+	SOC_SINGLE_TLV("ALC Capture Max Volume", WM8985_ALC_CONTROL_1,
+		3, 7, 0, alc_max_tlv),
+	SOC_SINGLE_TLV("ALC Capture Min Volume", WM8985_ALC_CONTROL_1,
+		0, 7, 0, alc_min_tlv),
+	SOC_SINGLE_TLV("ALC Capture Target Volume", WM8985_ALC_CONTROL_2,
+		0, 15, 0, alc_tar_tlv),
+	SOC_SINGLE("ALC Capture Attack", WM8985_ALC_CONTROL_3, 0, 10, 0),
+	SOC_SINGLE("ALC Capture Hold", WM8985_ALC_CONTROL_2, 4, 10, 0),
+	SOC_SINGLE("ALC Capture Decay", WM8985_ALC_CONTROL_3, 4, 10, 0),
+	SOC_ENUM("ALC Mode", alc_mode),
+	SOC_SINGLE("ALC Capture NG Switch", WM8985_NOISE_GATE,
+		3, 1, 0),
+	SOC_SINGLE("ALC Capture NG Threshold", WM8985_NOISE_GATE,
+		0, 7, 1),
+
+	SOC_DOUBLE_R_TLV("Capture Volume", WM8985_LEFT_ADC_DIGITAL_VOL,
+		WM8985_RIGHT_ADC_DIGITAL_VOL, 0, 255, 0, adc_tlv),
+	SOC_DOUBLE_R("Capture PGA ZC Switch", WM8985_LEFT_INP_PGA_GAIN_CTRL,
+		WM8985_RIGHT_INP_PGA_GAIN_CTRL, 7, 1, 0),
+	SOC_DOUBLE_R_TLV("Capture PGA Volume", WM8985_LEFT_INP_PGA_GAIN_CTRL,
+		WM8985_RIGHT_INP_PGA_GAIN_CTRL, 0, 63, 0, pga_vol_tlv),
+
+	SOC_DOUBLE_R_TLV("Capture PGA Boost Volume",
+		WM8985_LEFT_ADC_BOOST_CTRL, WM8985_RIGHT_ADC_BOOST_CTRL,
+		8, 1, 0, pga_boost_tlv),
+
+	SOC_DOUBLE("ADC Inversion Switch", WM8985_ADC_CONTROL, 0, 1, 1, 0),
+	SOC_SINGLE("ADC 128x Oversampling Switch", WM8985_ADC_CONTROL, 8, 1, 0),
+
+	SOC_DOUBLE_R_TLV("Playback Volume", WM8985_LEFT_DAC_DIGITAL_VOL,
+		WM8985_RIGHT_DAC_DIGITAL_VOL, 0, 255, 0, dac_tlv),
+
+	SOC_SINGLE("DAC Playback Limiter Switch", WM8985_DAC_LIMITER_1, 8, 1, 0),
+	SOC_SINGLE("DAC Playback Limiter Decay", WM8985_DAC_LIMITER_1, 4, 10, 0),
+	SOC_SINGLE("DAC Playback Limiter Attack", WM8985_DAC_LIMITER_1, 0, 11, 0),
+	SOC_SINGLE_TLV("DAC Playback Limiter Threshold", WM8985_DAC_LIMITER_2,
+		4, 7, 1, lim_thresh_tlv),
+	SOC_SINGLE_TLV("DAC Playback Limiter Boost Volume", WM8985_DAC_LIMITER_2,
+		0, 12, 0, lim_boost_tlv),
+	SOC_DOUBLE("DAC Inversion Switch", WM8985_DAC_CONTROL, 0, 1, 1, 0),
+	SOC_SINGLE("DAC Auto Mute Switch", WM8985_DAC_CONTROL, 2, 1, 0),
+	SOC_SINGLE("DAC 128x Oversampling Switch", WM8985_DAC_CONTROL, 3, 1, 0),
+
+	SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8985_LOUT1_HP_VOLUME_CTRL,
+		WM8985_ROUT1_HP_VOLUME_CTRL, 0, 63, 0, out_tlv),
+	SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8985_LOUT1_HP_VOLUME_CTRL,
+		WM8985_ROUT1_HP_VOLUME_CTRL, 7, 1, 0),
+	SOC_DOUBLE_R("Headphone Switch", WM8985_LOUT1_HP_VOLUME_CTRL,
+		WM8985_ROUT1_HP_VOLUME_CTRL, 6, 1, 1),
+
+	SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8985_LOUT2_SPK_VOLUME_CTRL,
+		WM8985_ROUT2_SPK_VOLUME_CTRL, 0, 63, 0, out_tlv),
+	SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8985_LOUT2_SPK_VOLUME_CTRL,
+		WM8985_ROUT2_SPK_VOLUME_CTRL, 7, 1, 0),
+	SOC_DOUBLE_R("Speaker Switch", WM8985_LOUT2_SPK_VOLUME_CTRL,
+		WM8985_ROUT2_SPK_VOLUME_CTRL, 6, 1, 1),
+
+	SOC_SINGLE("High Pass Filter Switch", WM8985_ADC_CONTROL, 8, 1, 0),
+	SOC_ENUM("High Pass Filter Mode", filter_mode),
+	SOC_SINGLE("High Pass Filter Cutoff", WM8985_ADC_CONTROL, 4, 7, 0),
+
+	SOC_DOUBLE_R_TLV("Aux Bypass Volume",
+		WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 6, 7, 0,
+		aux_tlv),
+
+	SOC_DOUBLE_R_TLV("Input PGA Bypass Volume",
+		WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 2, 7, 0,
+		bypass_tlv),
+
+	SOC_ENUM_EXT("Equalizer Function", eqmode, eqmode_get, eqmode_put),
+	SOC_ENUM("EQ1 Cutoff", eq1_cutoff),
+	SOC_SINGLE_TLV("EQ1 Volume", WM8985_EQ1_LOW_SHELF,  0, 24, 1, eq_tlv),
+	SOC_ENUM("EQ2 Bandwith", eq2_bw),
+	SOC_ENUM("EQ2 Cutoff", eq2_cutoff),
+	SOC_SINGLE_TLV("EQ2 Volume", WM8985_EQ2_PEAK_1, 0, 24, 1, eq_tlv),
+	SOC_ENUM("EQ3 Bandwith", eq3_bw),
+	SOC_ENUM("EQ3 Cutoff", eq3_cutoff),
+	SOC_SINGLE_TLV("EQ3 Volume", WM8985_EQ3_PEAK_2, 0, 24, 1, eq_tlv),
+	SOC_ENUM("EQ4 Bandwith", eq4_bw),
+	SOC_ENUM("EQ4 Cutoff", eq4_cutoff),
+	SOC_SINGLE_TLV("EQ4 Volume", WM8985_EQ4_PEAK_3, 0, 24, 1, eq_tlv),
+	SOC_ENUM("EQ5 Cutoff", eq5_cutoff),
+	SOC_SINGLE_TLV("EQ5 Volume", WM8985_EQ5_HIGH_SHELF, 0, 24, 1, eq_tlv),
+
+	SOC_ENUM("3D Depth", depth_3d),
+
+	SOC_ENUM("Speaker Mode", speaker_mode)
+};
+
+static const struct snd_kcontrol_new left_out_mixer[] = {
+	SOC_DAPM_SINGLE("Line Switch", WM8985_LEFT_MIXER_CTRL, 1, 1, 0),
+	SOC_DAPM_SINGLE("Aux Switch", WM8985_LEFT_MIXER_CTRL, 5, 1, 0),
+	SOC_DAPM_SINGLE("PCM Switch", WM8985_LEFT_MIXER_CTRL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_out_mixer[] = {
+	SOC_DAPM_SINGLE("Line Switch", WM8985_RIGHT_MIXER_CTRL, 1, 1, 0),
+	SOC_DAPM_SINGLE("Aux Switch", WM8985_RIGHT_MIXER_CTRL, 5, 1, 0),
+	SOC_DAPM_SINGLE("PCM Switch", WM8985_RIGHT_MIXER_CTRL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_input_mixer[] = {
+	SOC_DAPM_SINGLE("L2 Switch", WM8985_INPUT_CTRL, 2, 1, 0),
+	SOC_DAPM_SINGLE("MicN Switch", WM8985_INPUT_CTRL, 1, 1, 0),
+	SOC_DAPM_SINGLE("MicP Switch", WM8985_INPUT_CTRL, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_input_mixer[] = {
+	SOC_DAPM_SINGLE("R2 Switch", WM8985_INPUT_CTRL, 6, 1, 0),
+	SOC_DAPM_SINGLE("MicN Switch", WM8985_INPUT_CTRL, 5, 1, 0),
+	SOC_DAPM_SINGLE("MicP Switch", WM8985_INPUT_CTRL, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_boost_mixer[] = {
+	SOC_DAPM_SINGLE_TLV("L2 Volume", WM8985_LEFT_ADC_BOOST_CTRL,
+		4, 7, 0, boost_tlv),
+	SOC_DAPM_SINGLE_TLV("AUXL Volume", WM8985_LEFT_ADC_BOOST_CTRL,
+		0, 7, 0, boost_tlv)
+};
+
+static const struct snd_kcontrol_new right_boost_mixer[] = {
+	SOC_DAPM_SINGLE_TLV("R2 Volume", WM8985_RIGHT_ADC_BOOST_CTRL,
+		4, 7, 0, boost_tlv),
+	SOC_DAPM_SINGLE_TLV("AUXR Volume", WM8985_RIGHT_ADC_BOOST_CTRL,
+		0, 7, 0, boost_tlv)
+};
+
+static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = {
+	SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8985_POWER_MANAGEMENT_3,
+		0, 0),
+	SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8985_POWER_MANAGEMENT_3,
+		1, 0),
+	SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8985_POWER_MANAGEMENT_2,
+		0, 0),
+	SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8985_POWER_MANAGEMENT_2,
+		1, 0),
+
+	SND_SOC_DAPM_MIXER("Left Output Mixer", WM8985_POWER_MANAGEMENT_3,
+		2, 0, left_out_mixer, ARRAY_SIZE(left_out_mixer)),
+	SND_SOC_DAPM_MIXER("Right Output Mixer", WM8985_POWER_MANAGEMENT_3,
+		3, 0, right_out_mixer, ARRAY_SIZE(right_out_mixer)),
+
+	SND_SOC_DAPM_MIXER("Left Input Mixer", WM8985_POWER_MANAGEMENT_2,
+		2, 0, left_input_mixer, ARRAY_SIZE(left_input_mixer)),
+	SND_SOC_DAPM_MIXER("Right Input Mixer", WM8985_POWER_MANAGEMENT_2,
+		3, 0, right_input_mixer, ARRAY_SIZE(right_input_mixer)),
+
+	SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8985_POWER_MANAGEMENT_2,
+		4, 0, left_boost_mixer, ARRAY_SIZE(left_boost_mixer)),
+	SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8985_POWER_MANAGEMENT_2,
+		5, 0, right_boost_mixer, ARRAY_SIZE(right_boost_mixer)),
+
+	SND_SOC_DAPM_PGA("Left Capture PGA", WM8985_LEFT_INP_PGA_GAIN_CTRL,
+		6, 1, NULL, 0),
+	SND_SOC_DAPM_PGA("Right Capture PGA", WM8985_RIGHT_INP_PGA_GAIN_CTRL,
+		6, 1, NULL, 0),
+
+	SND_SOC_DAPM_PGA("Left Headphone Out", WM8985_POWER_MANAGEMENT_2,
+		7, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right Headphone Out", WM8985_POWER_MANAGEMENT_2,
+		8, 0, NULL, 0),
+
+	SND_SOC_DAPM_PGA("Left Speaker Out", WM8985_POWER_MANAGEMENT_3,
+		5, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right Speaker Out", WM8985_POWER_MANAGEMENT_3,
+		6, 0, NULL, 0),
+
+	SND_SOC_DAPM_MICBIAS("Mic Bias", WM8985_POWER_MANAGEMENT_1, 4, 0),
+
+	SND_SOC_DAPM_INPUT("LIN"),
+	SND_SOC_DAPM_INPUT("LIP"),
+	SND_SOC_DAPM_INPUT("RIN"),
+	SND_SOC_DAPM_INPUT("RIP"),
+	SND_SOC_DAPM_INPUT("AUXL"),
+	SND_SOC_DAPM_INPUT("AUXR"),
+	SND_SOC_DAPM_INPUT("L2"),
+	SND_SOC_DAPM_INPUT("R2"),
+	SND_SOC_DAPM_OUTPUT("HPL"),
+	SND_SOC_DAPM_OUTPUT("HPR"),
+	SND_SOC_DAPM_OUTPUT("SPKL"),
+	SND_SOC_DAPM_OUTPUT("SPKR")
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{ "Right Output Mixer", "PCM Switch", "Right DAC" },
+	{ "Right Output Mixer", "Aux Switch", "AUXR" },
+	{ "Right Output Mixer", "Line Switch", "Right Boost Mixer" },
+
+	{ "Left Output Mixer", "PCM Switch", "Left DAC" },
+	{ "Left Output Mixer", "Aux Switch", "AUXL" },
+	{ "Left Output Mixer", "Line Switch", "Left Boost Mixer" },
+
+	{ "Right Headphone Out", NULL, "Right Output Mixer" },
+	{ "HPR", NULL, "Right Headphone Out" },
+
+	{ "Left Headphone Out", NULL, "Left Output Mixer" },
+	{ "HPL", NULL, "Left Headphone Out" },
+
+	{ "Right Speaker Out", NULL, "Right Output Mixer" },
+	{ "SPKR", NULL, "Right Speaker Out" },
+
+	{ "Left Speaker Out", NULL, "Left Output Mixer" },
+	{ "SPKL", NULL, "Left Speaker Out" },
+
+	{ "Right ADC", NULL, "Right Boost Mixer" },
+
+	{ "Right Boost Mixer", "AUXR Volume", "AUXR" },
+	{ "Right Boost Mixer", NULL, "Right Capture PGA" },
+	{ "Right Boost Mixer", "R2 Volume", "R2" },
+
+	{ "Left ADC", NULL, "Left Boost Mixer" },
+
+	{ "Left Boost Mixer", "AUXL Volume", "AUXL" },
+	{ "Left Boost Mixer", NULL, "Left Capture PGA" },
+	{ "Left Boost Mixer", "L2 Volume", "L2" },
+
+	{ "Right Capture PGA", NULL, "Right Input Mixer" },
+	{ "Left Capture PGA", NULL, "Left Input Mixer" },
+
+	{ "Right Input Mixer", "R2 Switch", "R2" },
+	{ "Right Input Mixer", "MicN Switch", "RIN" },
+	{ "Right Input Mixer", "MicP Switch", "RIP" },
+
+	{ "Left Input Mixer", "L2 Switch", "L2" },
+	{ "Left Input Mixer", "MicN Switch", "LIN" },
+	{ "Left Input Mixer", "MicP Switch", "LIP" },
+};
+
+static int eqmode_get(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg;
+
+	reg = snd_soc_read(codec, WM8985_EQ1_LOW_SHELF);
+	if (reg & WM8985_EQ3DMODE)
+		ucontrol->value.integer.value[0] = 1;
+	else
+		ucontrol->value.integer.value[0] = 0;
+
+	return 0;
+}
+
+static int eqmode_put(struct snd_kcontrol *kcontrol,
+		      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int regpwr2, regpwr3;
+	unsigned int reg_eq;
+
+	if (ucontrol->value.integer.value[0] != 0
+			&& ucontrol->value.integer.value[0] != 1)
+		return -EINVAL;
+
+	reg_eq = snd_soc_read(codec, WM8985_EQ1_LOW_SHELF);
+	switch ((reg_eq & WM8985_EQ3DMODE) >> WM8985_EQ3DMODE_SHIFT) {
+	case 0:
+		if (!ucontrol->value.integer.value[0])
+			return 0;
+		break;
+	case 1:
+		if (ucontrol->value.integer.value[0])
+			return 0;
+		break;
+	}
+
+	regpwr2 = snd_soc_read(codec, WM8985_POWER_MANAGEMENT_2);
+	regpwr3 = snd_soc_read(codec, WM8985_POWER_MANAGEMENT_3);
+	/* disable the DACs and ADCs */
+	snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_2,
+			    WM8985_ADCENR_MASK | WM8985_ADCENL_MASK, 0);
+	snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_3,
+			    WM8985_DACENR_MASK | WM8985_DACENL_MASK, 0);
+	snd_soc_update_bits(codec, WM8985_ADDITIONAL_CONTROL,
+			    WM8985_M128ENB_MASK, WM8985_M128ENB);
+	/* set the desired eqmode */
+	snd_soc_update_bits(codec, WM8985_EQ1_LOW_SHELF,
+			    WM8985_EQ3DMODE_MASK,
+			    ucontrol->value.integer.value[0]
+			    << WM8985_EQ3DMODE_SHIFT);
+	/* restore DAC/ADC configuration */
+	snd_soc_write(codec, WM8985_POWER_MANAGEMENT_2, regpwr2);
+	snd_soc_write(codec, WM8985_POWER_MANAGEMENT_3, regpwr3);
+	return 0;
+}
+
+static int wm8985_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8985_dapm_widgets,
+				  ARRAY_SIZE(wm8985_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map,
+				ARRAY_SIZE(audio_map));
+	return 0;
+}
+
+static int wm8985_reset(struct snd_soc_codec *codec)
+{
+	return snd_soc_write(codec, WM8985_SOFTWARE_RESET, 0x0);
+}
+
+static int wm8985_dac_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+
+	return snd_soc_update_bits(codec, WM8985_DAC_CONTROL,
+				   WM8985_SOFTMUTE_MASK,
+				   !!mute << WM8985_SOFTMUTE_SHIFT);
+}
+
+static int wm8985_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec;
+	u16 format, master, bcp, lrp;
+
+	codec = dai->codec;
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		format = 0x2;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		format = 0x0;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		format = 0x1;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		format = 0x3;
+		break;
+	default:
+		dev_err(dai->dev, "Unknown dai format\n");
+		return -EINVAL;
+	}
+
+	snd_soc_update_bits(codec, WM8985_AUDIO_INTERFACE,
+			    WM8985_FMT_MASK, format << WM8985_FMT_SHIFT);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		master = 1;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		master = 0;
+		break;
+	default:
+		dev_err(dai->dev, "Unknown master/slave configuration\n");
+		return -EINVAL;
+	}
+
+	snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+			    WM8985_MS_MASK, master << WM8985_MS_SHIFT);
+
+	/* frame inversion is not valid for dsp modes */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_IB_IF:
+		case SND_SOC_DAIFMT_NB_IF:
+			return -EINVAL;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	bcp = lrp = 0;
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		bcp = lrp = 1;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		bcp = 1;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		lrp = 1;
+		break;
+	default:
+		dev_err(dai->dev, "Unknown polarity configuration\n");
+		return -EINVAL;
+	}
+
+	snd_soc_update_bits(codec, WM8985_AUDIO_INTERFACE,
+			    WM8985_LRP_MASK, lrp << WM8985_LRP_SHIFT);
+	snd_soc_update_bits(codec, WM8985_AUDIO_INTERFACE,
+			    WM8985_BCP_MASK, bcp << WM8985_BCP_SHIFT);
+	return 0;
+}
+
+static int wm8985_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	int i;
+	struct snd_soc_codec *codec;
+	struct wm8985_priv *wm8985;
+	u16 blen, srate_idx;
+	unsigned int tmp;
+	int srate_best;
+
+	codec = dai->codec;
+	wm8985 = snd_soc_codec_get_drvdata(codec);
+
+	wm8985->bclk = snd_soc_params_to_bclk(params);
+	if ((int)wm8985->bclk < 0)
+		return wm8985->bclk;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		blen = 0x0;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		blen = 0x1;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		blen = 0x2;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		blen = 0x3;
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported word length %u\n",
+			params_format(params));
+		return -EINVAL;
+	}
+
+	snd_soc_update_bits(codec, WM8985_AUDIO_INTERFACE,
+			    WM8985_WL_MASK, blen << WM8985_WL_SHIFT);
+
+	/*
+	 * match to the nearest possible sample rate and rely
+	 * on the array index to configure the SR register
+	 */
+	srate_idx = 0;
+	srate_best = abs(srates[0] - params_rate(params));
+	for (i = 1; i < ARRAY_SIZE(srates); ++i) {
+		if (abs(srates[i] - params_rate(params)) >= srate_best)
+			continue;
+		srate_idx = i;
+		srate_best = abs(srates[i] - params_rate(params));
+	}
+
+	dev_dbg(dai->dev, "Selected SRATE = %d\n", srates[srate_idx]);
+	snd_soc_update_bits(codec, WM8985_ADDITIONAL_CONTROL,
+			    WM8985_SR_MASK, srate_idx << WM8985_SR_SHIFT);
+
+	dev_dbg(dai->dev, "Target BCLK = %uHz\n", wm8985->bclk);
+	dev_dbg(dai->dev, "SYSCLK = %uHz\n", wm8985->sysclk);
+
+	for (i = 0; i < ARRAY_SIZE(fs_ratios); ++i) {
+		if (wm8985->sysclk / params_rate(params)
+				== fs_ratios[i].ratio)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(fs_ratios)) {
+		dev_err(dai->dev, "Unable to configure MCLK ratio %u/%u\n",
+			wm8985->sysclk, params_rate(params));
+		return -EINVAL;
+	}
+
+	dev_dbg(dai->dev, "MCLK ratio = %dfs\n", fs_ratios[i].ratio);
+	snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+			    WM8985_MCLKDIV_MASK, i << WM8985_MCLKDIV_SHIFT);
+
+	/* select the appropriate bclk divider */
+	tmp = (wm8985->sysclk / fs_ratios[i].div) * 10;
+	for (i = 0; i < ARRAY_SIZE(bclk_divs); ++i) {
+		if (wm8985->bclk == tmp / bclk_divs[i])
+			break;
+	}
+
+	if (i == ARRAY_SIZE(bclk_divs)) {
+		dev_err(dai->dev, "No matching BCLK divider found\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(dai->dev, "BCLK div = %d\n", i);
+	snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+			    WM8985_BCLKDIV_MASK, i << WM8985_BCLKDIV_SHIFT);
+	return 0;
+}
+
+struct pll_div {
+	u32 div2:1;
+	u32 n:4;
+	u32 k:24;
+};
+
+#define FIXED_PLL_SIZE ((1ULL << 24) * 10)
+static int pll_factors(struct pll_div *pll_div, unsigned int target,
+		       unsigned int source)
+{
+	u64 Kpart;
+	unsigned long int K, Ndiv, Nmod;
+
+	pll_div->div2 = 0;
+	Ndiv = target / source;
+	if (Ndiv < 6) {
+		source >>= 1;
+		pll_div->div2 = 1;
+		Ndiv = target / source;
+	}
+
+	if (Ndiv < 6 || Ndiv > 12) {
+		printk(KERN_ERR "%s: WM8985 N value is not within"
+		       " the recommended range: %lu\n", __func__, Ndiv);
+		return -EINVAL;
+	}
+	pll_div->n = Ndiv;
+
+	Nmod = target % source;
+	Kpart = FIXED_PLL_SIZE * (u64)Nmod;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xffffffff;
+	if ((K % 10) >= 5)
+		K += 5;
+	K /= 10;
+	pll_div->k = K;
+
+	return 0;
+}
+
+static int wm8985_set_pll(struct snd_soc_dai *dai, int pll_id,
+			  int source, unsigned int freq_in,
+			  unsigned int freq_out)
+{
+	int ret;
+	struct snd_soc_codec *codec;
+	struct pll_div pll_div;
+
+	codec = dai->codec;
+	if (freq_in && freq_out) {
+		ret = pll_factors(&pll_div, freq_out * 4 * 2, freq_in);
+		if (ret)
+			return ret;
+	}
+
+	/* disable the PLL before reprogramming it */
+	snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+			    WM8985_PLLEN_MASK, 0);
+	
+	if (!freq_in || !freq_out)
+		return 0;
+
+	/* set PLLN and PRESCALE */
+	snd_soc_write(codec, WM8985_PLL_N,
+		      (pll_div.div2 << WM8985_PLL_PRESCALE_SHIFT)
+		      | pll_div.n);
+	/* set PLLK */
+	snd_soc_write(codec, WM8985_PLL_K_3, pll_div.k & 0x1ff);
+	snd_soc_write(codec, WM8985_PLL_K_2, (pll_div.k >> 9) & 0x1ff);
+	snd_soc_write(codec, WM8985_PLL_K_1, (pll_div.k >> 18));
+	/* set the source of the clock to be the PLL */
+	snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+			    WM8985_CLKSEL_MASK, WM8985_CLKSEL);
+	/* enable the PLL */
+	snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+			    WM8985_PLLEN_MASK, WM8985_PLLEN);
+	return 0;
+}
+
+static int wm8985_set_sysclk(struct snd_soc_dai *dai,
+			     int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec;
+	struct wm8985_priv *wm8985;
+
+	codec = dai->codec;
+	wm8985 = snd_soc_codec_get_drvdata(codec);
+
+	switch (clk_id) {
+	case WM8985_CLKSRC_MCLK:
+		snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+				    WM8985_CLKSEL_MASK, 0);
+		snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+				    WM8985_PLLEN_MASK, 0);
+		break;
+	case WM8985_CLKSRC_PLL:
+		snd_soc_update_bits(codec, WM8985_CLOCK_GEN_CONTROL,
+				    WM8985_CLKSEL_MASK, WM8985_CLKSEL);
+		break;
+	default:
+		dev_err(dai->dev, "Unknown clock source %d\n", clk_id);
+		return -EINVAL;
+	}
+
+	wm8985->sysclk = freq;
+	return 0;
+}
+
+static void wm8985_sync_cache(struct snd_soc_codec *codec)
+{
+	short i;
+	u16 *cache;
+
+	if (!codec->cache_sync)
+		return;
+	codec->cache_only = 0;
+	/* restore cache */
+	cache = codec->reg_cache;
+	for (i = 0; i < codec->driver->reg_cache_size; i++) {
+		if (i == WM8985_SOFTWARE_RESET
+				|| cache[i] == wm8985_reg_defs[i])
+			continue;
+		snd_soc_write(codec, i, cache[i]);
+	}
+	codec->cache_sync = 0;
+}
+
+static int wm8985_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	int ret;
+	struct wm8985_priv *wm8985;
+
+	wm8985 = snd_soc_codec_get_drvdata(codec);
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+		/* VMID at 75k */
+		snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+				    WM8985_VMIDSEL_MASK,
+				    1 << WM8985_VMIDSEL_SHIFT);
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			ret = regulator_bulk_enable(ARRAY_SIZE(wm8985->supplies),
+						    wm8985->supplies);
+			if (ret) {
+				dev_err(codec->dev,
+					"Failed to enable supplies: %d\n",
+					ret);
+				return ret;
+			}
+
+			wm8985_sync_cache(codec);
+
+			/* enable anti-pop features */
+			snd_soc_update_bits(codec, WM8985_OUT4_TO_ADC,
+					    WM8985_POBCTRL_MASK,
+					    WM8985_POBCTRL);
+			/* enable thermal shutdown */
+			snd_soc_update_bits(codec, WM8985_OUTPUT_CTRL0,
+					    WM8985_TSDEN_MASK, WM8985_TSDEN);
+			snd_soc_update_bits(codec, WM8985_OUTPUT_CTRL0,
+					    WM8985_TSOPCTRL_MASK,
+					    WM8985_TSOPCTRL);
+			/* enable BIASEN */
+			snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+					    WM8985_BIASEN_MASK, WM8985_BIASEN);
+			/* VMID at 75k */
+			snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+					    WM8985_VMIDSEL_MASK,
+					    1 << WM8985_VMIDSEL_SHIFT);
+			msleep(500);
+			/* disable anti-pop features */
+			snd_soc_update_bits(codec, WM8985_OUT4_TO_ADC,
+					    WM8985_POBCTRL_MASK, 0);
+		}
+		/* VMID at 300k */
+		snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+				    WM8985_VMIDSEL_MASK,
+				    2 << WM8985_VMIDSEL_SHIFT);
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* disable thermal shutdown */
+		snd_soc_update_bits(codec, WM8985_OUTPUT_CTRL0,
+				    WM8985_TSOPCTRL_MASK, 0);
+		snd_soc_update_bits(codec, WM8985_OUTPUT_CTRL0,
+				    WM8985_TSDEN_MASK, 0);
+		/* disable VMIDSEL and BIASEN */
+		snd_soc_update_bits(codec, WM8985_POWER_MANAGEMENT_1,
+				    WM8985_VMIDSEL_MASK | WM8985_BIASEN_MASK,
+				    0);
+		snd_soc_write(codec, WM8985_POWER_MANAGEMENT_1, 0);
+		snd_soc_write(codec, WM8985_POWER_MANAGEMENT_2, 0);
+		snd_soc_write(codec, WM8985_POWER_MANAGEMENT_3, 0);
+
+		codec->cache_sync = 1;
+
+		regulator_bulk_disable(ARRAY_SIZE(wm8985->supplies),
+				       wm8985->supplies);
+		break;
+	}
+
+	codec->bias_level = level;
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm8985_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8985_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8985_resume(struct snd_soc_codec *codec)
+{
+	wm8985_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	return 0;
+}
+#else
+#define wm8985_suspend NULL
+#define wm8985_resume NULL
+#endif
+
+static int wm8985_remove(struct snd_soc_codec *codec)
+{
+	struct wm8985_priv *wm8985;
+
+	wm8985 = snd_soc_codec_get_drvdata(codec);
+	wm8985_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	regulator_bulk_free(ARRAY_SIZE(wm8985->supplies), wm8985->supplies);
+	return 0;
+}
+
+static int wm8985_probe(struct snd_soc_codec *codec)
+{
+	size_t i;
+	struct wm8985_priv *wm8985;
+	int ret;
+	u16 *cache;
+
+	wm8985 = snd_soc_codec_get_drvdata(codec);
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8985->control_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache i/o: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm8985->supplies); i++)
+		wm8985->supplies[i].supply = wm8985_supply_names[i];
+
+	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8985->supplies),
+				 wm8985->supplies);
+	if (ret) {
+		dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+		return ret;
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(wm8985->supplies),
+				    wm8985->supplies);
+	if (ret) {
+		dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+		goto err_reg_get;
+	}
+
+	ret = wm8985_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
+		goto err_reg_enable;
+	}
+
+	cache = codec->reg_cache;
+	/* latch volume update bits */
+	for (i = 0; i < ARRAY_SIZE(volume_update_regs); ++i)
+		cache[volume_update_regs[i]] |= 0x100;
+	/* enable BIASCUT */
+	cache[WM8985_BIAS_CTRL] |= WM8985_BIASCUT;
+	codec->cache_sync = 1;
+
+	snd_soc_add_controls(codec, wm8985_snd_controls,
+			     ARRAY_SIZE(wm8985_snd_controls));
+	wm8985_add_widgets(codec);
+
+	wm8985_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	return 0;
+
+err_reg_enable:
+	regulator_bulk_disable(ARRAY_SIZE(wm8985->supplies), wm8985->supplies);
+err_reg_get:
+	regulator_bulk_free(ARRAY_SIZE(wm8985->supplies), wm8985->supplies);
+	return ret;
+}
+
+static struct snd_soc_dai_ops wm8985_dai_ops = {
+	.digital_mute = wm8985_dac_mute,
+	.hw_params = wm8985_hw_params,
+	.set_fmt = wm8985_set_fmt,
+	.set_sysclk = wm8985_set_sysclk,
+	.set_pll = wm8985_set_pll
+};
+
+#define WM8985_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver wm8985_dai = {
+	.name = "wm8985-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = WM8985_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = WM8985_FORMATS,
+	},
+	.ops = &wm8985_dai_ops,
+	.symmetric_rates = 1
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8985 = {
+	.probe = wm8985_probe,
+	.remove = wm8985_remove,
+	.suspend = wm8985_suspend,
+	.resume = wm8985_resume,
+	.set_bias_level = wm8985_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8985_reg_defs),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8985_reg_defs
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8985_spi_probe(struct spi_device *spi)
+{
+	struct wm8985_priv *wm8985;
+	int ret;
+
+	wm8985 = kzalloc(sizeof *wm8985, GFP_KERNEL);
+	if (!wm8985)
+		return -ENOMEM;
+
+	wm8985->control_type = SND_SOC_SPI;
+	spi_set_drvdata(spi, wm8985);
+
+	ret = snd_soc_register_codec(&spi->dev,
+				     &soc_codec_dev_wm8985, &wm8985_dai, 1);
+	if (ret < 0)
+		kfree(wm8985);
+	return ret;
+}
+
+static int __devexit wm8985_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver wm8985_spi_driver = {
+	.driver = {
+		.name = "wm8985",
+		.owner = THIS_MODULE,
+	},
+	.probe = wm8985_spi_probe,
+	.remove = __devexit_p(wm8985_spi_remove)
+};
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8985_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8985_priv *wm8985;
+	int ret;
+
+	wm8985 = kzalloc(sizeof *wm8985, GFP_KERNEL);
+	if (!wm8985)
+		return -ENOMEM;
+
+	wm8985->control_type = SND_SOC_I2C;
+	i2c_set_clientdata(i2c, wm8985);
+
+	ret = snd_soc_register_codec(&i2c->dev,
+				     &soc_codec_dev_wm8985, &wm8985_dai, 1);
+	if (ret < 0)
+		kfree(wm8985);
+	return ret;
+}
+
+static __devexit int wm8985_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8985_i2c_id[] = {
+	{ "wm8985", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8985_i2c_id);
+
+static struct i2c_driver wm8985_i2c_driver = {
+	.driver = {
+		.name = "wm8985",
+		.owner = THIS_MODULE,
+	},
+	.probe = wm8985_i2c_probe,
+	.remove = __devexit_p(wm8985_i2c_remove),
+	.id_table = wm8985_i2c_id
+};
+#endif
+
+static int __init wm8985_modinit(void)
+{
+	int ret = 0;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8985_i2c_driver);
+	if (ret) {
+		printk(KERN_ERR "Failed to register wm8985 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&wm8985_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8985 SPI driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8985_modinit);
+
+static void __exit wm8985_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8985_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&wm8985_spi_driver);
+#endif
+}
+module_exit(wm8985_exit);
+
+MODULE_DESCRIPTION("ASoC WM8985 driver");
+MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8985.h b/linux/sound/soc/codecs/wm8985.h
new file mode 100644
index 0000000..2e71ff5
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8985.h
@@ -0,0 +1,1045 @@
+/*
+ * wm8985.h  --  WM8985 ASoC driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8985_H
+#define _WM8985_H
+
+#define WM8985_SOFTWARE_RESET                   0x00
+#define WM8985_POWER_MANAGEMENT_1               0x01
+#define WM8985_POWER_MANAGEMENT_2               0x02
+#define WM8985_POWER_MANAGEMENT_3               0x03
+#define WM8985_AUDIO_INTERFACE                  0x04
+#define WM8985_COMPANDING_CONTROL               0x05
+#define WM8985_CLOCK_GEN_CONTROL                0x06
+#define WM8985_ADDITIONAL_CONTROL               0x07
+#define WM8985_GPIO_CONTROL                     0x08
+#define WM8985_JACK_DETECT_CONTROL_1            0x09
+#define WM8985_DAC_CONTROL                      0x0A
+#define WM8985_LEFT_DAC_DIGITAL_VOL             0x0B
+#define WM8985_RIGHT_DAC_DIGITAL_VOL            0x0C
+#define WM8985_JACK_DETECT_CONTROL_2            0x0D
+#define WM8985_ADC_CONTROL                      0x0E
+#define WM8985_LEFT_ADC_DIGITAL_VOL             0x0F
+#define WM8985_RIGHT_ADC_DIGITAL_VOL            0x10
+#define WM8985_EQ1_LOW_SHELF                    0x12
+#define WM8985_EQ2_PEAK_1                       0x13
+#define WM8985_EQ3_PEAK_2                       0x14
+#define WM8985_EQ4_PEAK_3                       0x15
+#define WM8985_EQ5_HIGH_SHELF                   0x16
+#define WM8985_DAC_LIMITER_1                    0x18
+#define WM8985_DAC_LIMITER_2                    0x19
+#define WM8985_NOTCH_FILTER_1                   0x1B
+#define WM8985_NOTCH_FILTER_2                   0x1C
+#define WM8985_NOTCH_FILTER_3                   0x1D
+#define WM8985_NOTCH_FILTER_4                   0x1E
+#define WM8985_ALC_CONTROL_1                    0x20
+#define WM8985_ALC_CONTROL_2                    0x21
+#define WM8985_ALC_CONTROL_3                    0x22
+#define WM8985_NOISE_GATE                       0x23
+#define WM8985_PLL_N                            0x24
+#define WM8985_PLL_K_1                          0x25
+#define WM8985_PLL_K_2                          0x26
+#define WM8985_PLL_K_3                          0x27
+#define WM8985_3D_CONTROL                       0x29
+#define WM8985_OUT4_TO_ADC                      0x2A
+#define WM8985_BEEP_CONTROL                     0x2B
+#define WM8985_INPUT_CTRL                       0x2C
+#define WM8985_LEFT_INP_PGA_GAIN_CTRL           0x2D
+#define WM8985_RIGHT_INP_PGA_GAIN_CTRL          0x2E
+#define WM8985_LEFT_ADC_BOOST_CTRL              0x2F
+#define WM8985_RIGHT_ADC_BOOST_CTRL             0x30
+#define WM8985_OUTPUT_CTRL0                     0x31
+#define WM8985_LEFT_MIXER_CTRL                  0x32
+#define WM8985_RIGHT_MIXER_CTRL                 0x33
+#define WM8985_LOUT1_HP_VOLUME_CTRL             0x34
+#define WM8985_ROUT1_HP_VOLUME_CTRL             0x35
+#define WM8985_LOUT2_SPK_VOLUME_CTRL            0x36
+#define WM8985_ROUT2_SPK_VOLUME_CTRL            0x37
+#define WM8985_OUT3_MIXER_CTRL                  0x38
+#define WM8985_OUT4_MONO_MIX_CTRL               0x39
+#define WM8985_OUTPUT_CTRL1                     0x3C
+#define WM8985_BIAS_CTRL                        0x3D
+
+#define WM8985_REGISTER_COUNT                   59
+#define WM8985_MAX_REGISTER                     0x3F
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Software Reset
+ */
+#define WM8985_SOFTWARE_RESET_MASK              0x01FF  /* SOFTWARE_RESET - [8:0] */
+#define WM8985_SOFTWARE_RESET_SHIFT                  0  /* SOFTWARE_RESET - [8:0] */
+#define WM8985_SOFTWARE_RESET_WIDTH                  9  /* SOFTWARE_RESET - [8:0] */
+
+/*
+ * R1 (0x01) - Power management 1
+ */
+#define WM8985_OUT4MIXEN                        0x0080  /* OUT4MIXEN */
+#define WM8985_OUT4MIXEN_MASK                   0x0080  /* OUT4MIXEN */
+#define WM8985_OUT4MIXEN_SHIFT                       7  /* OUT4MIXEN */
+#define WM8985_OUT4MIXEN_WIDTH                       1  /* OUT4MIXEN */
+#define WM8985_OUT3MIXEN                        0x0040  /* OUT3MIXEN */
+#define WM8985_OUT3MIXEN_MASK                   0x0040  /* OUT3MIXEN */
+#define WM8985_OUT3MIXEN_SHIFT                       6  /* OUT3MIXEN */
+#define WM8985_OUT3MIXEN_WIDTH                       1  /* OUT3MIXEN */
+#define WM8985_PLLEN                            0x0020  /* PLLEN */
+#define WM8985_PLLEN_MASK                       0x0020  /* PLLEN */
+#define WM8985_PLLEN_SHIFT                           5  /* PLLEN */
+#define WM8985_PLLEN_WIDTH                           1  /* PLLEN */
+#define WM8985_MICBEN                           0x0010  /* MICBEN */
+#define WM8985_MICBEN_MASK                      0x0010  /* MICBEN */
+#define WM8985_MICBEN_SHIFT                          4  /* MICBEN */
+#define WM8985_MICBEN_WIDTH                          1  /* MICBEN */
+#define WM8985_BIASEN                           0x0008  /* BIASEN */
+#define WM8985_BIASEN_MASK                      0x0008  /* BIASEN */
+#define WM8985_BIASEN_SHIFT                          3  /* BIASEN */
+#define WM8985_BIASEN_WIDTH                          1  /* BIASEN */
+#define WM8985_BUFIOEN                          0x0004  /* BUFIOEN */
+#define WM8985_BUFIOEN_MASK                     0x0004  /* BUFIOEN */
+#define WM8985_BUFIOEN_SHIFT                         2  /* BUFIOEN */
+#define WM8985_BUFIOEN_WIDTH                         1  /* BUFIOEN */
+#define WM8985_VMIDSEL                          0x0003  /* VMIDSEL */
+#define WM8985_VMIDSEL_MASK                     0x0003  /* VMIDSEL - [1:0] */
+#define WM8985_VMIDSEL_SHIFT                         0  /* VMIDSEL - [1:0] */
+#define WM8985_VMIDSEL_WIDTH                         2  /* VMIDSEL - [1:0] */
+
+/*
+ * R2 (0x02) - Power management 2
+ */
+#define WM8985_ROUT1EN                          0x0100  /* ROUT1EN */
+#define WM8985_ROUT1EN_MASK                     0x0100  /* ROUT1EN */
+#define WM8985_ROUT1EN_SHIFT                         8  /* ROUT1EN */
+#define WM8985_ROUT1EN_WIDTH                         1  /* ROUT1EN */
+#define WM8985_LOUT1EN                          0x0080  /* LOUT1EN */
+#define WM8985_LOUT1EN_MASK                     0x0080  /* LOUT1EN */
+#define WM8985_LOUT1EN_SHIFT                         7  /* LOUT1EN */
+#define WM8985_LOUT1EN_WIDTH                         1  /* LOUT1EN */
+#define WM8985_SLEEP                            0x0040  /* SLEEP */
+#define WM8985_SLEEP_MASK                       0x0040  /* SLEEP */
+#define WM8985_SLEEP_SHIFT                           6  /* SLEEP */
+#define WM8985_SLEEP_WIDTH                           1  /* SLEEP */
+#define WM8985_BOOSTENR                         0x0020  /* BOOSTENR */
+#define WM8985_BOOSTENR_MASK                    0x0020  /* BOOSTENR */
+#define WM8985_BOOSTENR_SHIFT                        5  /* BOOSTENR */
+#define WM8985_BOOSTENR_WIDTH                        1  /* BOOSTENR */
+#define WM8985_BOOSTENL                         0x0010  /* BOOSTENL */
+#define WM8985_BOOSTENL_MASK                    0x0010  /* BOOSTENL */
+#define WM8985_BOOSTENL_SHIFT                        4  /* BOOSTENL */
+#define WM8985_BOOSTENL_WIDTH                        1  /* BOOSTENL */
+#define WM8985_INPGAENR                         0x0008  /* INPGAENR */
+#define WM8985_INPGAENR_MASK                    0x0008  /* INPGAENR */
+#define WM8985_INPGAENR_SHIFT                        3  /* INPGAENR */
+#define WM8985_INPGAENR_WIDTH                        1  /* INPGAENR */
+#define WM8985_INPPGAENL                        0x0004  /* INPPGAENL */
+#define WM8985_INPPGAENL_MASK                   0x0004  /* INPPGAENL */
+#define WM8985_INPPGAENL_SHIFT                       2  /* INPPGAENL */
+#define WM8985_INPPGAENL_WIDTH                       1  /* INPPGAENL */
+#define WM8985_ADCENR                           0x0002  /* ADCENR */
+#define WM8985_ADCENR_MASK                      0x0002  /* ADCENR */
+#define WM8985_ADCENR_SHIFT                          1  /* ADCENR */
+#define WM8985_ADCENR_WIDTH                          1  /* ADCENR */
+#define WM8985_ADCENL                           0x0001  /* ADCENL */
+#define WM8985_ADCENL_MASK                      0x0001  /* ADCENL */
+#define WM8985_ADCENL_SHIFT                          0  /* ADCENL */
+#define WM8985_ADCENL_WIDTH                          1  /* ADCENL */
+
+/*
+ * R3 (0x03) - Power management 3
+ */
+#define WM8985_OUT4EN                           0x0100  /* OUT4EN */
+#define WM8985_OUT4EN_MASK                      0x0100  /* OUT4EN */
+#define WM8985_OUT4EN_SHIFT                          8  /* OUT4EN */
+#define WM8985_OUT4EN_WIDTH                          1  /* OUT4EN */
+#define WM8985_OUT3EN                           0x0080  /* OUT3EN */
+#define WM8985_OUT3EN_MASK                      0x0080  /* OUT3EN */
+#define WM8985_OUT3EN_SHIFT                          7  /* OUT3EN */
+#define WM8985_OUT3EN_WIDTH                          1  /* OUT3EN */
+#define WM8985_ROUT2EN                          0x0040  /* ROUT2EN */
+#define WM8985_ROUT2EN_MASK                     0x0040  /* ROUT2EN */
+#define WM8985_ROUT2EN_SHIFT                         6  /* ROUT2EN */
+#define WM8985_ROUT2EN_WIDTH                         1  /* ROUT2EN */
+#define WM8985_LOUT2EN                          0x0020  /* LOUT2EN */
+#define WM8985_LOUT2EN_MASK                     0x0020  /* LOUT2EN */
+#define WM8985_LOUT2EN_SHIFT                         5  /* LOUT2EN */
+#define WM8985_LOUT2EN_WIDTH                         1  /* LOUT2EN */
+#define WM8985_RMIXEN                           0x0008  /* RMIXEN */
+#define WM8985_RMIXEN_MASK                      0x0008  /* RMIXEN */
+#define WM8985_RMIXEN_SHIFT                          3  /* RMIXEN */
+#define WM8985_RMIXEN_WIDTH                          1  /* RMIXEN */
+#define WM8985_LMIXEN                           0x0004  /* LMIXEN */
+#define WM8985_LMIXEN_MASK                      0x0004  /* LMIXEN */
+#define WM8985_LMIXEN_SHIFT                          2  /* LMIXEN */
+#define WM8985_LMIXEN_WIDTH                          1  /* LMIXEN */
+#define WM8985_DACENR                           0x0002  /* DACENR */
+#define WM8985_DACENR_MASK                      0x0002  /* DACENR */
+#define WM8985_DACENR_SHIFT                          1  /* DACENR */
+#define WM8985_DACENR_WIDTH                          1  /* DACENR */
+#define WM8985_DACENL                           0x0001  /* DACENL */
+#define WM8985_DACENL_MASK                      0x0001  /* DACENL */
+#define WM8985_DACENL_SHIFT                          0  /* DACENL */
+#define WM8985_DACENL_WIDTH                          1  /* DACENL */
+
+/*
+ * R4 (0x04) - Audio Interface
+ */
+#define WM8985_BCP                              0x0100  /* BCP */
+#define WM8985_BCP_MASK                         0x0100  /* BCP */
+#define WM8985_BCP_SHIFT                             8  /* BCP */
+#define WM8985_BCP_WIDTH                             1  /* BCP */
+#define WM8985_LRP                              0x0080  /* LRP */
+#define WM8985_LRP_MASK                         0x0080  /* LRP */
+#define WM8985_LRP_SHIFT                             7  /* LRP */
+#define WM8985_LRP_WIDTH                             1  /* LRP */
+#define WM8985_WL_MASK                          0x0060  /* WL - [6:5] */
+#define WM8985_WL_SHIFT                              5  /* WL - [6:5] */
+#define WM8985_WL_WIDTH                              2  /* WL - [6:5] */
+#define WM8985_FMT_MASK                         0x0018  /* FMT - [4:3] */
+#define WM8985_FMT_SHIFT                             3  /* FMT - [4:3] */
+#define WM8985_FMT_WIDTH                             2  /* FMT - [4:3] */
+#define WM8985_DLRSWAP                          0x0004  /* DLRSWAP */
+#define WM8985_DLRSWAP_MASK                     0x0004  /* DLRSWAP */
+#define WM8985_DLRSWAP_SHIFT                         2  /* DLRSWAP */
+#define WM8985_DLRSWAP_WIDTH                         1  /* DLRSWAP */
+#define WM8985_ALRSWAP                          0x0002  /* ALRSWAP */
+#define WM8985_ALRSWAP_MASK                     0x0002  /* ALRSWAP */
+#define WM8985_ALRSWAP_SHIFT                         1  /* ALRSWAP */
+#define WM8985_ALRSWAP_WIDTH                         1  /* ALRSWAP */
+#define WM8985_MONO                             0x0001  /* MONO */
+#define WM8985_MONO_MASK                        0x0001  /* MONO */
+#define WM8985_MONO_SHIFT                            0  /* MONO */
+#define WM8985_MONO_WIDTH                            1  /* MONO */
+
+/*
+ * R5 (0x05) - Companding control
+ */
+#define WM8985_WL8                              0x0020  /* WL8 */
+#define WM8985_WL8_MASK                         0x0020  /* WL8 */
+#define WM8985_WL8_SHIFT                             5  /* WL8 */
+#define WM8985_WL8_WIDTH                             1  /* WL8 */
+#define WM8985_DAC_COMP_MASK                    0x0018  /* DAC_COMP - [4:3] */
+#define WM8985_DAC_COMP_SHIFT                        3  /* DAC_COMP - [4:3] */
+#define WM8985_DAC_COMP_WIDTH                        2  /* DAC_COMP - [4:3] */
+#define WM8985_ADC_COMP_MASK                    0x0006  /* ADC_COMP - [2:1] */
+#define WM8985_ADC_COMP_SHIFT                        1  /* ADC_COMP - [2:1] */
+#define WM8985_ADC_COMP_WIDTH                        2  /* ADC_COMP - [2:1] */
+#define WM8985_LOOPBACK                         0x0001  /* LOOPBACK */
+#define WM8985_LOOPBACK_MASK                    0x0001  /* LOOPBACK */
+#define WM8985_LOOPBACK_SHIFT                        0  /* LOOPBACK */
+#define WM8985_LOOPBACK_WIDTH                        1  /* LOOPBACK */
+
+/*
+ * R6 (0x06) - Clock Gen control
+ */
+#define WM8985_CLKSEL                           0x0100  /* CLKSEL */
+#define WM8985_CLKSEL_MASK                      0x0100  /* CLKSEL */
+#define WM8985_CLKSEL_SHIFT                          8  /* CLKSEL */
+#define WM8985_CLKSEL_WIDTH                          1  /* CLKSEL */
+#define WM8985_MCLKDIV_MASK                     0x00E0  /* MCLKDIV - [7:5] */
+#define WM8985_MCLKDIV_SHIFT                         5  /* MCLKDIV - [7:5] */
+#define WM8985_MCLKDIV_WIDTH                         3  /* MCLKDIV - [7:5] */
+#define WM8985_BCLKDIV_MASK                     0x001C  /* BCLKDIV - [4:2] */
+#define WM8985_BCLKDIV_SHIFT                         2  /* BCLKDIV - [4:2] */
+#define WM8985_BCLKDIV_WIDTH                         3  /* BCLKDIV - [4:2] */
+#define WM8985_MS                               0x0001  /* MS */
+#define WM8985_MS_MASK                          0x0001  /* MS */
+#define WM8985_MS_SHIFT                              0  /* MS */
+#define WM8985_MS_WIDTH                              1  /* MS */
+
+/*
+ * R7 (0x07) - Additional control
+ */
+#define WM8985_M128ENB                          0x0100  /* M128ENB */
+#define WM8985_M128ENB_MASK                     0x0100  /* M128ENB */
+#define WM8985_M128ENB_SHIFT                         8  /* M128ENB */
+#define WM8985_M128ENB_WIDTH                         1  /* M128ENB */
+#define WM8985_DCLKDIV_MASK                     0x00F0  /* DCLKDIV - [7:4] */
+#define WM8985_DCLKDIV_SHIFT                         4  /* DCLKDIV - [7:4] */
+#define WM8985_DCLKDIV_WIDTH                         4  /* DCLKDIV - [7:4] */
+#define WM8985_SR_MASK                          0x000E  /* SR - [3:1] */
+#define WM8985_SR_SHIFT                              1  /* SR - [3:1] */
+#define WM8985_SR_WIDTH                              3  /* SR - [3:1] */
+#define WM8985_SLOWCLKEN                        0x0001  /* SLOWCLKEN */
+#define WM8985_SLOWCLKEN_MASK                   0x0001  /* SLOWCLKEN */
+#define WM8985_SLOWCLKEN_SHIFT                       0  /* SLOWCLKEN */
+#define WM8985_SLOWCLKEN_WIDTH                       1  /* SLOWCLKEN */
+
+/*
+ * R8 (0x08) - GPIO Control
+ */
+#define WM8985_GPIO1GP                          0x0100  /* GPIO1GP */
+#define WM8985_GPIO1GP_MASK                     0x0100  /* GPIO1GP */
+#define WM8985_GPIO1GP_SHIFT                         8  /* GPIO1GP */
+#define WM8985_GPIO1GP_WIDTH                         1  /* GPIO1GP */
+#define WM8985_GPIO1GPU                         0x0080  /* GPIO1GPU */
+#define WM8985_GPIO1GPU_MASK                    0x0080  /* GPIO1GPU */
+#define WM8985_GPIO1GPU_SHIFT                        7  /* GPIO1GPU */
+#define WM8985_GPIO1GPU_WIDTH                        1  /* GPIO1GPU */
+#define WM8985_GPIO1GPD                         0x0040  /* GPIO1GPD */
+#define WM8985_GPIO1GPD_MASK                    0x0040  /* GPIO1GPD */
+#define WM8985_GPIO1GPD_SHIFT                        6  /* GPIO1GPD */
+#define WM8985_GPIO1GPD_WIDTH                        1  /* GPIO1GPD */
+#define WM8985_GPIO1POL                         0x0008  /* GPIO1POL */
+#define WM8985_GPIO1POL_MASK                    0x0008  /* GPIO1POL */
+#define WM8985_GPIO1POL_SHIFT                        3  /* GPIO1POL */
+#define WM8985_GPIO1POL_WIDTH                        1  /* GPIO1POL */
+#define WM8985_GPIO1SEL_MASK                    0x0007  /* GPIO1SEL - [2:0] */
+#define WM8985_GPIO1SEL_SHIFT                        0  /* GPIO1SEL - [2:0] */
+#define WM8985_GPIO1SEL_WIDTH                        3  /* GPIO1SEL - [2:0] */
+
+/*
+ * R9 (0x09) - Jack Detect Control 1
+ */
+#define WM8985_JD_EN                            0x0040  /* JD_EN */
+#define WM8985_JD_EN_MASK                       0x0040  /* JD_EN */
+#define WM8985_JD_EN_SHIFT                           6  /* JD_EN */
+#define WM8985_JD_EN_WIDTH                           1  /* JD_EN */
+#define WM8985_JD_SEL_MASK                      0x0030  /* JD_SEL - [5:4] */
+#define WM8985_JD_SEL_SHIFT                          4  /* JD_SEL - [5:4] */
+#define WM8985_JD_SEL_WIDTH                          2  /* JD_SEL - [5:4] */
+
+/*
+ * R10 (0x0A) - DAC Control
+ */
+#define WM8985_SOFTMUTE                         0x0040  /* SOFTMUTE */
+#define WM8985_SOFTMUTE_MASK                    0x0040  /* SOFTMUTE */
+#define WM8985_SOFTMUTE_SHIFT                        6  /* SOFTMUTE */
+#define WM8985_SOFTMUTE_WIDTH                        1  /* SOFTMUTE */
+#define WM8985_DACOSR128                        0x0008  /* DACOSR128 */
+#define WM8985_DACOSR128_MASK                   0x0008  /* DACOSR128 */
+#define WM8985_DACOSR128_SHIFT                       3  /* DACOSR128 */
+#define WM8985_DACOSR128_WIDTH                       1  /* DACOSR128 */
+#define WM8985_AMUTE                            0x0004  /* AMUTE */
+#define WM8985_AMUTE_MASK                       0x0004  /* AMUTE */
+#define WM8985_AMUTE_SHIFT                           2  /* AMUTE */
+#define WM8985_AMUTE_WIDTH                           1  /* AMUTE */
+#define WM8985_DACPOLR                          0x0002  /* DACPOLR */
+#define WM8985_DACPOLR_MASK                     0x0002  /* DACPOLR */
+#define WM8985_DACPOLR_SHIFT                         1  /* DACPOLR */
+#define WM8985_DACPOLR_WIDTH                         1  /* DACPOLR */
+#define WM8985_DACPOLL                          0x0001  /* DACPOLL */
+#define WM8985_DACPOLL_MASK                     0x0001  /* DACPOLL */
+#define WM8985_DACPOLL_SHIFT                         0  /* DACPOLL */
+#define WM8985_DACPOLL_WIDTH                         1  /* DACPOLL */
+
+/*
+ * R11 (0x0B) - Left DAC digital Vol
+ */
+#define WM8985_DACVU                            0x0100  /* DACVU */
+#define WM8985_DACVU_MASK                       0x0100  /* DACVU */
+#define WM8985_DACVU_SHIFT                           8  /* DACVU */
+#define WM8985_DACVU_WIDTH                           1  /* DACVU */
+#define WM8985_DACVOLL_MASK                     0x00FF  /* DACVOLL - [7:0] */
+#define WM8985_DACVOLL_SHIFT                         0  /* DACVOLL - [7:0] */
+#define WM8985_DACVOLL_WIDTH                         8  /* DACVOLL - [7:0] */
+
+/*
+ * R12 (0x0C) - Right DAC digital vol
+ */
+#define WM8985_DACVU                            0x0100  /* DACVU */
+#define WM8985_DACVU_MASK                       0x0100  /* DACVU */
+#define WM8985_DACVU_SHIFT                           8  /* DACVU */
+#define WM8985_DACVU_WIDTH                           1  /* DACVU */
+#define WM8985_DACVOLR_MASK                     0x00FF  /* DACVOLR - [7:0] */
+#define WM8985_DACVOLR_SHIFT                         0  /* DACVOLR - [7:0] */
+#define WM8985_DACVOLR_WIDTH                         8  /* DACVOLR - [7:0] */
+
+/*
+ * R13 (0x0D) - Jack Detect Control 2
+ */
+#define WM8985_JD_EN1_MASK                      0x00F0  /* JD_EN1 - [7:4] */
+#define WM8985_JD_EN1_SHIFT                          4  /* JD_EN1 - [7:4] */
+#define WM8985_JD_EN1_WIDTH                          4  /* JD_EN1 - [7:4] */
+#define WM8985_JD_EN0_MASK                      0x000F  /* JD_EN0 - [3:0] */
+#define WM8985_JD_EN0_SHIFT                          0  /* JD_EN0 - [3:0] */
+#define WM8985_JD_EN0_WIDTH                          4  /* JD_EN0 - [3:0] */
+
+/*
+ * R14 (0x0E) - ADC Control
+ */
+#define WM8985_HPFEN                            0x0100  /* HPFEN */
+#define WM8985_HPFEN_MASK                       0x0100  /* HPFEN */
+#define WM8985_HPFEN_SHIFT                           8  /* HPFEN */
+#define WM8985_HPFEN_WIDTH                           1  /* HPFEN */
+#define WM8985_HPFAPP                           0x0080  /* HPFAPP */
+#define WM8985_HPFAPP_MASK                      0x0080  /* HPFAPP */
+#define WM8985_HPFAPP_SHIFT                          7  /* HPFAPP */
+#define WM8985_HPFAPP_WIDTH                          1  /* HPFAPP */
+#define WM8985_HPFCUT_MASK                      0x0070  /* HPFCUT - [6:4] */
+#define WM8985_HPFCUT_SHIFT                          4  /* HPFCUT - [6:4] */
+#define WM8985_HPFCUT_WIDTH                          3  /* HPFCUT - [6:4] */
+#define WM8985_ADCOSR128                        0x0008  /* ADCOSR128 */
+#define WM8985_ADCOSR128_MASK                   0x0008  /* ADCOSR128 */
+#define WM8985_ADCOSR128_SHIFT                       3  /* ADCOSR128 */
+#define WM8985_ADCOSR128_WIDTH                       1  /* ADCOSR128 */
+#define WM8985_ADCRPOL                          0x0002  /* ADCRPOL */
+#define WM8985_ADCRPOL_MASK                     0x0002  /* ADCRPOL */
+#define WM8985_ADCRPOL_SHIFT                         1  /* ADCRPOL */
+#define WM8985_ADCRPOL_WIDTH                         1  /* ADCRPOL */
+#define WM8985_ADCLPOL                          0x0001  /* ADCLPOL */
+#define WM8985_ADCLPOL_MASK                     0x0001  /* ADCLPOL */
+#define WM8985_ADCLPOL_SHIFT                         0  /* ADCLPOL */
+#define WM8985_ADCLPOL_WIDTH                         1  /* ADCLPOL */
+
+/*
+ * R15 (0x0F) - Left ADC Digital Vol
+ */
+#define WM8985_ADCVU                            0x0100  /* ADCVU */
+#define WM8985_ADCVU_MASK                       0x0100  /* ADCVU */
+#define WM8985_ADCVU_SHIFT                           8  /* ADCVU */
+#define WM8985_ADCVU_WIDTH                           1  /* ADCVU */
+#define WM8985_ADCVOLL_MASK                     0x00FF  /* ADCVOLL - [7:0] */
+#define WM8985_ADCVOLL_SHIFT                         0  /* ADCVOLL - [7:0] */
+#define WM8985_ADCVOLL_WIDTH                         8  /* ADCVOLL - [7:0] */
+
+/*
+ * R16 (0x10) - Right ADC Digital Vol
+ */
+#define WM8985_ADCVU                            0x0100  /* ADCVU */
+#define WM8985_ADCVU_MASK                       0x0100  /* ADCVU */
+#define WM8985_ADCVU_SHIFT                           8  /* ADCVU */
+#define WM8985_ADCVU_WIDTH                           1  /* ADCVU */
+#define WM8985_ADCVOLR_MASK                     0x00FF  /* ADCVOLR - [7:0] */
+#define WM8985_ADCVOLR_SHIFT                         0  /* ADCVOLR - [7:0] */
+#define WM8985_ADCVOLR_WIDTH                         8  /* ADCVOLR - [7:0] */
+
+/*
+ * R18 (0x12) - EQ1 - low shelf
+ */
+#define WM8985_EQ3DMODE                         0x0100  /* EQ3DMODE */
+#define WM8985_EQ3DMODE_MASK                    0x0100  /* EQ3DMODE */
+#define WM8985_EQ3DMODE_SHIFT                        8  /* EQ3DMODE */
+#define WM8985_EQ3DMODE_WIDTH                        1  /* EQ3DMODE */
+#define WM8985_EQ1C_MASK                        0x0060  /* EQ1C - [6:5] */
+#define WM8985_EQ1C_SHIFT                            5  /* EQ1C - [6:5] */
+#define WM8985_EQ1C_WIDTH                            2  /* EQ1C - [6:5] */
+#define WM8985_EQ1G_MASK                        0x001F  /* EQ1G - [4:0] */
+#define WM8985_EQ1G_SHIFT                            0  /* EQ1G - [4:0] */
+#define WM8985_EQ1G_WIDTH                            5  /* EQ1G - [4:0] */
+
+/*
+ * R19 (0x13) - EQ2 - peak 1
+ */
+#define WM8985_EQ2BW                            0x0100  /* EQ2BW */
+#define WM8985_EQ2BW_MASK                       0x0100  /* EQ2BW */
+#define WM8985_EQ2BW_SHIFT                           8  /* EQ2BW */
+#define WM8985_EQ2BW_WIDTH                           1  /* EQ2BW */
+#define WM8985_EQ2C_MASK                        0x0060  /* EQ2C - [6:5] */
+#define WM8985_EQ2C_SHIFT                            5  /* EQ2C - [6:5] */
+#define WM8985_EQ2C_WIDTH                            2  /* EQ2C - [6:5] */
+#define WM8985_EQ2G_MASK                        0x001F  /* EQ2G - [4:0] */
+#define WM8985_EQ2G_SHIFT                            0  /* EQ2G - [4:0] */
+#define WM8985_EQ2G_WIDTH                            5  /* EQ2G - [4:0] */
+
+/*
+ * R20 (0x14) - EQ3 - peak 2
+ */
+#define WM8985_EQ3BW                            0x0100  /* EQ3BW */
+#define WM8985_EQ3BW_MASK                       0x0100  /* EQ3BW */
+#define WM8985_EQ3BW_SHIFT                           8  /* EQ3BW */
+#define WM8985_EQ3BW_WIDTH                           1  /* EQ3BW */
+#define WM8985_EQ3C_MASK                        0x0060  /* EQ3C - [6:5] */
+#define WM8985_EQ3C_SHIFT                            5  /* EQ3C - [6:5] */
+#define WM8985_EQ3C_WIDTH                            2  /* EQ3C - [6:5] */
+#define WM8985_EQ3G_MASK                        0x001F  /* EQ3G - [4:0] */
+#define WM8985_EQ3G_SHIFT                            0  /* EQ3G - [4:0] */
+#define WM8985_EQ3G_WIDTH                            5  /* EQ3G - [4:0] */
+
+/*
+ * R21 (0x15) - EQ4 - peak 3
+ */
+#define WM8985_EQ4BW                            0x0100  /* EQ4BW */
+#define WM8985_EQ4BW_MASK                       0x0100  /* EQ4BW */
+#define WM8985_EQ4BW_SHIFT                           8  /* EQ4BW */
+#define WM8985_EQ4BW_WIDTH                           1  /* EQ4BW */
+#define WM8985_EQ4C_MASK                        0x0060  /* EQ4C - [6:5] */
+#define WM8985_EQ4C_SHIFT                            5  /* EQ4C - [6:5] */
+#define WM8985_EQ4C_WIDTH                            2  /* EQ4C - [6:5] */
+#define WM8985_EQ4G_MASK                        0x001F  /* EQ4G - [4:0] */
+#define WM8985_EQ4G_SHIFT                            0  /* EQ4G - [4:0] */
+#define WM8985_EQ4G_WIDTH                            5  /* EQ4G - [4:0] */
+
+/*
+ * R22 (0x16) - EQ5 - high shelf
+ */
+#define WM8985_EQ5C_MASK                        0x0060  /* EQ5C - [6:5] */
+#define WM8985_EQ5C_SHIFT                            5  /* EQ5C - [6:5] */
+#define WM8985_EQ5C_WIDTH                            2  /* EQ5C - [6:5] */
+#define WM8985_EQ5G_MASK                        0x001F  /* EQ5G - [4:0] */
+#define WM8985_EQ5G_SHIFT                            0  /* EQ5G - [4:0] */
+#define WM8985_EQ5G_WIDTH                            5  /* EQ5G - [4:0] */
+
+/*
+ * R24 (0x18) - DAC Limiter 1
+ */
+#define WM8985_LIMEN                            0x0100  /* LIMEN */
+#define WM8985_LIMEN_MASK                       0x0100  /* LIMEN */
+#define WM8985_LIMEN_SHIFT                           8  /* LIMEN */
+#define WM8985_LIMEN_WIDTH                           1  /* LIMEN */
+#define WM8985_LIMDCY_MASK                      0x00F0  /* LIMDCY - [7:4] */
+#define WM8985_LIMDCY_SHIFT                          4  /* LIMDCY - [7:4] */
+#define WM8985_LIMDCY_WIDTH                          4  /* LIMDCY - [7:4] */
+#define WM8985_LIMATK_MASK                      0x000F  /* LIMATK - [3:0] */
+#define WM8985_LIMATK_SHIFT                          0  /* LIMATK - [3:0] */
+#define WM8985_LIMATK_WIDTH                          4  /* LIMATK - [3:0] */
+
+/*
+ * R25 (0x19) - DAC Limiter 2
+ */
+#define WM8985_LIMLVL_MASK                      0x0070  /* LIMLVL - [6:4] */
+#define WM8985_LIMLVL_SHIFT                          4  /* LIMLVL - [6:4] */
+#define WM8985_LIMLVL_WIDTH                          3  /* LIMLVL - [6:4] */
+#define WM8985_LIMBOOST_MASK                    0x000F  /* LIMBOOST - [3:0] */
+#define WM8985_LIMBOOST_SHIFT                        0  /* LIMBOOST - [3:0] */
+#define WM8985_LIMBOOST_WIDTH                        4  /* LIMBOOST - [3:0] */
+
+/*
+ * R27 (0x1B) - Notch Filter 1
+ */
+#define WM8985_NFU                              0x0100  /* NFU */
+#define WM8985_NFU_MASK                         0x0100  /* NFU */
+#define WM8985_NFU_SHIFT                             8  /* NFU */
+#define WM8985_NFU_WIDTH                             1  /* NFU */
+#define WM8985_NFEN                             0x0080  /* NFEN */
+#define WM8985_NFEN_MASK                        0x0080  /* NFEN */
+#define WM8985_NFEN_SHIFT                            7  /* NFEN */
+#define WM8985_NFEN_WIDTH                            1  /* NFEN */
+#define WM8985_NFA0_13_7_MASK                   0x007F  /* NFA0(13:7) - [6:0] */
+#define WM8985_NFA0_13_7_SHIFT                       0  /* NFA0(13:7) - [6:0] */
+#define WM8985_NFA0_13_7_WIDTH                       7  /* NFA0(13:7) - [6:0] */
+
+/*
+ * R28 (0x1C) - Notch Filter 2
+ */
+#define WM8985_NFU                              0x0100  /* NFU */
+#define WM8985_NFU_MASK                         0x0100  /* NFU */
+#define WM8985_NFU_SHIFT                             8  /* NFU */
+#define WM8985_NFU_WIDTH                             1  /* NFU */
+#define WM8985_NFA0_6_0_MASK                    0x007F  /* NFA0(6:0) - [6:0] */
+#define WM8985_NFA0_6_0_SHIFT                        0  /* NFA0(6:0) - [6:0] */
+#define WM8985_NFA0_6_0_WIDTH                        7  /* NFA0(6:0) - [6:0] */
+
+/*
+ * R29 (0x1D) - Notch Filter 3
+ */
+#define WM8985_NFU                              0x0100  /* NFU */
+#define WM8985_NFU_MASK                         0x0100  /* NFU */
+#define WM8985_NFU_SHIFT                             8  /* NFU */
+#define WM8985_NFU_WIDTH                             1  /* NFU */
+#define WM8985_NFA1_13_7_MASK                   0x007F  /* NFA1(13:7) - [6:0] */
+#define WM8985_NFA1_13_7_SHIFT                       0  /* NFA1(13:7) - [6:0] */
+#define WM8985_NFA1_13_7_WIDTH                       7  /* NFA1(13:7) - [6:0] */
+
+/*
+ * R30 (0x1E) - Notch Filter 4
+ */
+#define WM8985_NFU                              0x0100  /* NFU */
+#define WM8985_NFU_MASK                         0x0100  /* NFU */
+#define WM8985_NFU_SHIFT                             8  /* NFU */
+#define WM8985_NFU_WIDTH                             1  /* NFU */
+#define WM8985_NFA1_6_0_MASK                    0x007F  /* NFA1(6:0) - [6:0] */
+#define WM8985_NFA1_6_0_SHIFT                        0  /* NFA1(6:0) - [6:0] */
+#define WM8985_NFA1_6_0_WIDTH                        7  /* NFA1(6:0) - [6:0] */
+
+/*
+ * R32 (0x20) - ALC control 1
+ */
+#define WM8985_ALCSEL_MASK                      0x0180  /* ALCSEL - [8:7] */
+#define WM8985_ALCSEL_SHIFT                          7  /* ALCSEL - [8:7] */
+#define WM8985_ALCSEL_WIDTH                          2  /* ALCSEL - [8:7] */
+#define WM8985_ALCMAX_MASK                      0x0038  /* ALCMAX - [5:3] */
+#define WM8985_ALCMAX_SHIFT                          3  /* ALCMAX - [5:3] */
+#define WM8985_ALCMAX_WIDTH                          3  /* ALCMAX - [5:3] */
+#define WM8985_ALCMIN_MASK                      0x0007  /* ALCMIN - [2:0] */
+#define WM8985_ALCMIN_SHIFT                          0  /* ALCMIN - [2:0] */
+#define WM8985_ALCMIN_WIDTH                          3  /* ALCMIN - [2:0] */
+
+/*
+ * R33 (0x21) - ALC control 2
+ */
+#define WM8985_ALCHLD_MASK                      0x00F0  /* ALCHLD - [7:4] */
+#define WM8985_ALCHLD_SHIFT                          4  /* ALCHLD - [7:4] */
+#define WM8985_ALCHLD_WIDTH                          4  /* ALCHLD - [7:4] */
+#define WM8985_ALCLVL_MASK                      0x000F  /* ALCLVL - [3:0] */
+#define WM8985_ALCLVL_SHIFT                          0  /* ALCLVL - [3:0] */
+#define WM8985_ALCLVL_WIDTH                          4  /* ALCLVL - [3:0] */
+
+/*
+ * R34 (0x22) - ALC control 3
+ */
+#define WM8985_ALCMODE                          0x0100  /* ALCMODE */
+#define WM8985_ALCMODE_MASK                     0x0100  /* ALCMODE */
+#define WM8985_ALCMODE_SHIFT                         8  /* ALCMODE */
+#define WM8985_ALCMODE_WIDTH                         1  /* ALCMODE */
+#define WM8985_ALCDCY_MASK                      0x00F0  /* ALCDCY - [7:4] */
+#define WM8985_ALCDCY_SHIFT                          4  /* ALCDCY - [7:4] */
+#define WM8985_ALCDCY_WIDTH                          4  /* ALCDCY - [7:4] */
+#define WM8985_ALCATK_MASK                      0x000F  /* ALCATK - [3:0] */
+#define WM8985_ALCATK_SHIFT                          0  /* ALCATK - [3:0] */
+#define WM8985_ALCATK_WIDTH                          4  /* ALCATK - [3:0] */
+
+/*
+ * R35 (0x23) - Noise Gate
+ */
+#define WM8985_NGEN                             0x0008  /* NGEN */
+#define WM8985_NGEN_MASK                        0x0008  /* NGEN */
+#define WM8985_NGEN_SHIFT                            3  /* NGEN */
+#define WM8985_NGEN_WIDTH                            1  /* NGEN */
+#define WM8985_NGTH_MASK                        0x0007  /* NGTH - [2:0] */
+#define WM8985_NGTH_SHIFT                            0  /* NGTH - [2:0] */
+#define WM8985_NGTH_WIDTH                            3  /* NGTH - [2:0] */
+
+/*
+ * R36 (0x24) - PLL N
+ */
+#define WM8985_PLL_PRESCALE                     0x0010  /* PLL_PRESCALE */
+#define WM8985_PLL_PRESCALE_MASK                0x0010  /* PLL_PRESCALE */
+#define WM8985_PLL_PRESCALE_SHIFT                    4  /* PLL_PRESCALE */
+#define WM8985_PLL_PRESCALE_WIDTH                    1  /* PLL_PRESCALE */
+#define WM8985_PLLN_MASK                        0x000F  /* PLLN - [3:0] */
+#define WM8985_PLLN_SHIFT                            0  /* PLLN - [3:0] */
+#define WM8985_PLLN_WIDTH                            4  /* PLLN - [3:0] */
+
+/*
+ * R37 (0x25) - PLL K 1
+ */
+#define WM8985_PLLK_23_18_MASK                  0x003F  /* PLLK(23:18) - [5:0] */
+#define WM8985_PLLK_23_18_SHIFT                      0  /* PLLK(23:18) - [5:0] */
+#define WM8985_PLLK_23_18_WIDTH                      6  /* PLLK(23:18) - [5:0] */
+
+/*
+ * R38 (0x26) - PLL K 2
+ */
+#define WM8985_PLLK_17_9_MASK                   0x01FF  /* PLLK(17:9) - [8:0] */
+#define WM8985_PLLK_17_9_SHIFT                       0  /* PLLK(17:9) - [8:0] */
+#define WM8985_PLLK_17_9_WIDTH                       9  /* PLLK(17:9) - [8:0] */
+
+/*
+ * R39 (0x27) - PLL K 3
+ */
+#define WM8985_PLLK_8_0_MASK                    0x01FF  /* PLLK(8:0) - [8:0] */
+#define WM8985_PLLK_8_0_SHIFT                        0  /* PLLK(8:0) - [8:0] */
+#define WM8985_PLLK_8_0_WIDTH                        9  /* PLLK(8:0) - [8:0] */
+
+/*
+ * R41 (0x29) - 3D control
+ */
+#define WM8985_DEPTH3D_MASK                     0x000F  /* DEPTH3D - [3:0] */
+#define WM8985_DEPTH3D_SHIFT                         0  /* DEPTH3D - [3:0] */
+#define WM8985_DEPTH3D_WIDTH                         4  /* DEPTH3D - [3:0] */
+
+/*
+ * R42 (0x2A) - OUT4 to ADC
+ */
+#define WM8985_OUT4_2ADCVOL_MASK                0x01C0  /* OUT4_2ADCVOL - [8:6] */
+#define WM8985_OUT4_2ADCVOL_SHIFT                    6  /* OUT4_2ADCVOL - [8:6] */
+#define WM8985_OUT4_2ADCVOL_WIDTH                    3  /* OUT4_2ADCVOL - [8:6] */
+#define WM8985_OUT4_2LNR                        0x0020  /* OUT4_2LNR */
+#define WM8985_OUT4_2LNR_MASK                   0x0020  /* OUT4_2LNR */
+#define WM8985_OUT4_2LNR_SHIFT                       5  /* OUT4_2LNR */
+#define WM8985_OUT4_2LNR_WIDTH                       1  /* OUT4_2LNR */
+#define WM8985_POBCTRL                          0x0004  /* POBCTRL */
+#define WM8985_POBCTRL_MASK                     0x0004  /* POBCTRL */
+#define WM8985_POBCTRL_SHIFT                         2  /* POBCTRL */
+#define WM8985_POBCTRL_WIDTH                         1  /* POBCTRL */
+#define WM8985_DELEN                            0x0002  /* DELEN */
+#define WM8985_DELEN_MASK                       0x0002  /* DELEN */
+#define WM8985_DELEN_SHIFT                           1  /* DELEN */
+#define WM8985_DELEN_WIDTH                           1  /* DELEN */
+#define WM8985_OUT1DEL                          0x0001  /* OUT1DEL */
+#define WM8985_OUT1DEL_MASK                     0x0001  /* OUT1DEL */
+#define WM8985_OUT1DEL_SHIFT                         0  /* OUT1DEL */
+#define WM8985_OUT1DEL_WIDTH                         1  /* OUT1DEL */
+
+/*
+ * R43 (0x2B) - Beep control
+ */
+#define WM8985_BYPL2RMIX                        0x0100  /* BYPL2RMIX */
+#define WM8985_BYPL2RMIX_MASK                   0x0100  /* BYPL2RMIX */
+#define WM8985_BYPL2RMIX_SHIFT                       8  /* BYPL2RMIX */
+#define WM8985_BYPL2RMIX_WIDTH                       1  /* BYPL2RMIX */
+#define WM8985_BYPR2LMIX                        0x0080  /* BYPR2LMIX */
+#define WM8985_BYPR2LMIX_MASK                   0x0080  /* BYPR2LMIX */
+#define WM8985_BYPR2LMIX_SHIFT                       7  /* BYPR2LMIX */
+#define WM8985_BYPR2LMIX_WIDTH                       1  /* BYPR2LMIX */
+#define WM8985_MUTERPGA2INV                     0x0020  /* MUTERPGA2INV */
+#define WM8985_MUTERPGA2INV_MASK                0x0020  /* MUTERPGA2INV */
+#define WM8985_MUTERPGA2INV_SHIFT                    5  /* MUTERPGA2INV */
+#define WM8985_MUTERPGA2INV_WIDTH                    1  /* MUTERPGA2INV */
+#define WM8985_INVROUT2                         0x0010  /* INVROUT2 */
+#define WM8985_INVROUT2_MASK                    0x0010  /* INVROUT2 */
+#define WM8985_INVROUT2_SHIFT                        4  /* INVROUT2 */
+#define WM8985_INVROUT2_WIDTH                        1  /* INVROUT2 */
+#define WM8985_BEEPVOL_MASK                     0x000E  /* BEEPVOL - [3:1] */
+#define WM8985_BEEPVOL_SHIFT                         1  /* BEEPVOL - [3:1] */
+#define WM8985_BEEPVOL_WIDTH                         3  /* BEEPVOL - [3:1] */
+#define WM8985_BEEPEN                           0x0001  /* BEEPEN */
+#define WM8985_BEEPEN_MASK                      0x0001  /* BEEPEN */
+#define WM8985_BEEPEN_SHIFT                          0  /* BEEPEN */
+#define WM8985_BEEPEN_WIDTH                          1  /* BEEPEN */
+
+/*
+ * R44 (0x2C) - Input ctrl
+ */
+#define WM8985_MBVSEL                           0x0100  /* MBVSEL */
+#define WM8985_MBVSEL_MASK                      0x0100  /* MBVSEL */
+#define WM8985_MBVSEL_SHIFT                          8  /* MBVSEL */
+#define WM8985_MBVSEL_WIDTH                          1  /* MBVSEL */
+#define WM8985_R2_2INPPGA                       0x0040  /* R2_2INPPGA */
+#define WM8985_R2_2INPPGA_MASK                  0x0040  /* R2_2INPPGA */
+#define WM8985_R2_2INPPGA_SHIFT                      6  /* R2_2INPPGA */
+#define WM8985_R2_2INPPGA_WIDTH                      1  /* R2_2INPPGA */
+#define WM8985_RIN2INPPGA                       0x0020  /* RIN2INPPGA */
+#define WM8985_RIN2INPPGA_MASK                  0x0020  /* RIN2INPPGA */
+#define WM8985_RIN2INPPGA_SHIFT                      5  /* RIN2INPPGA */
+#define WM8985_RIN2INPPGA_WIDTH                      1  /* RIN2INPPGA */
+#define WM8985_RIP2INPPGA                       0x0010  /* RIP2INPPGA */
+#define WM8985_RIP2INPPGA_MASK                  0x0010  /* RIP2INPPGA */
+#define WM8985_RIP2INPPGA_SHIFT                      4  /* RIP2INPPGA */
+#define WM8985_RIP2INPPGA_WIDTH                      1  /* RIP2INPPGA */
+#define WM8985_L2_2INPPGA                       0x0004  /* L2_2INPPGA */
+#define WM8985_L2_2INPPGA_MASK                  0x0004  /* L2_2INPPGA */
+#define WM8985_L2_2INPPGA_SHIFT                      2  /* L2_2INPPGA */
+#define WM8985_L2_2INPPGA_WIDTH                      1  /* L2_2INPPGA */
+#define WM8985_LIN2INPPGA                       0x0002  /* LIN2INPPGA */
+#define WM8985_LIN2INPPGA_MASK                  0x0002  /* LIN2INPPGA */
+#define WM8985_LIN2INPPGA_SHIFT                      1  /* LIN2INPPGA */
+#define WM8985_LIN2INPPGA_WIDTH                      1  /* LIN2INPPGA */
+#define WM8985_LIP2INPPGA                       0x0001  /* LIP2INPPGA */
+#define WM8985_LIP2INPPGA_MASK                  0x0001  /* LIP2INPPGA */
+#define WM8985_LIP2INPPGA_SHIFT                      0  /* LIP2INPPGA */
+#define WM8985_LIP2INPPGA_WIDTH                      1  /* LIP2INPPGA */
+
+/*
+ * R45 (0x2D) - Left INP PGA gain ctrl
+ */
+#define WM8985_INPGAVU                          0x0100  /* INPGAVU */
+#define WM8985_INPGAVU_MASK                     0x0100  /* INPGAVU */
+#define WM8985_INPGAVU_SHIFT                         8  /* INPGAVU */
+#define WM8985_INPGAVU_WIDTH                         1  /* INPGAVU */
+#define WM8985_INPPGAZCL                        0x0080  /* INPPGAZCL */
+#define WM8985_INPPGAZCL_MASK                   0x0080  /* INPPGAZCL */
+#define WM8985_INPPGAZCL_SHIFT                       7  /* INPPGAZCL */
+#define WM8985_INPPGAZCL_WIDTH                       1  /* INPPGAZCL */
+#define WM8985_INPPGAMUTEL                      0x0040  /* INPPGAMUTEL */
+#define WM8985_INPPGAMUTEL_MASK                 0x0040  /* INPPGAMUTEL */
+#define WM8985_INPPGAMUTEL_SHIFT                     6  /* INPPGAMUTEL */
+#define WM8985_INPPGAMUTEL_WIDTH                     1  /* INPPGAMUTEL */
+#define WM8985_INPPGAVOLL_MASK                  0x003F  /* INPPGAVOLL - [5:0] */
+#define WM8985_INPPGAVOLL_SHIFT                      0  /* INPPGAVOLL - [5:0] */
+#define WM8985_INPPGAVOLL_WIDTH                      6  /* INPPGAVOLL - [5:0] */
+
+/*
+ * R46 (0x2E) - Right INP PGA gain ctrl
+ */
+#define WM8985_INPGAVU                          0x0100  /* INPGAVU */
+#define WM8985_INPGAVU_MASK                     0x0100  /* INPGAVU */
+#define WM8985_INPGAVU_SHIFT                         8  /* INPGAVU */
+#define WM8985_INPGAVU_WIDTH                         1  /* INPGAVU */
+#define WM8985_INPPGAZCR                        0x0080  /* INPPGAZCR */
+#define WM8985_INPPGAZCR_MASK                   0x0080  /* INPPGAZCR */
+#define WM8985_INPPGAZCR_SHIFT                       7  /* INPPGAZCR */
+#define WM8985_INPPGAZCR_WIDTH                       1  /* INPPGAZCR */
+#define WM8985_INPPGAMUTER                      0x0040  /* INPPGAMUTER */
+#define WM8985_INPPGAMUTER_MASK                 0x0040  /* INPPGAMUTER */
+#define WM8985_INPPGAMUTER_SHIFT                     6  /* INPPGAMUTER */
+#define WM8985_INPPGAMUTER_WIDTH                     1  /* INPPGAMUTER */
+#define WM8985_INPPGAVOLR_MASK                  0x003F  /* INPPGAVOLR - [5:0] */
+#define WM8985_INPPGAVOLR_SHIFT                      0  /* INPPGAVOLR - [5:0] */
+#define WM8985_INPPGAVOLR_WIDTH                      6  /* INPPGAVOLR - [5:0] */
+
+/*
+ * R47 (0x2F) - Left ADC BOOST ctrl
+ */
+#define WM8985_PGABOOSTL                        0x0100  /* PGABOOSTL */
+#define WM8985_PGABOOSTL_MASK                   0x0100  /* PGABOOSTL */
+#define WM8985_PGABOOSTL_SHIFT                       8  /* PGABOOSTL */
+#define WM8985_PGABOOSTL_WIDTH                       1  /* PGABOOSTL */
+#define WM8985_L2_2BOOSTVOL_MASK                0x0070  /* L2_2BOOSTVOL - [6:4] */
+#define WM8985_L2_2BOOSTVOL_SHIFT                    4  /* L2_2BOOSTVOL - [6:4] */
+#define WM8985_L2_2BOOSTVOL_WIDTH                    3  /* L2_2BOOSTVOL - [6:4] */
+#define WM8985_AUXL2BOOSTVOL_MASK               0x0007  /* AUXL2BOOSTVOL - [2:0] */
+#define WM8985_AUXL2BOOSTVOL_SHIFT                   0  /* AUXL2BOOSTVOL - [2:0] */
+#define WM8985_AUXL2BOOSTVOL_WIDTH                   3  /* AUXL2BOOSTVOL - [2:0] */
+
+/*
+ * R48 (0x30) - Right ADC BOOST ctrl
+ */
+#define WM8985_PGABOOSTR                        0x0100  /* PGABOOSTR */
+#define WM8985_PGABOOSTR_MASK                   0x0100  /* PGABOOSTR */
+#define WM8985_PGABOOSTR_SHIFT                       8  /* PGABOOSTR */
+#define WM8985_PGABOOSTR_WIDTH                       1  /* PGABOOSTR */
+#define WM8985_R2_2BOOSTVOL_MASK                0x0070  /* R2_2BOOSTVOL - [6:4] */
+#define WM8985_R2_2BOOSTVOL_SHIFT                    4  /* R2_2BOOSTVOL - [6:4] */
+#define WM8985_R2_2BOOSTVOL_WIDTH                    3  /* R2_2BOOSTVOL - [6:4] */
+#define WM8985_AUXR2BOOSTVOL_MASK               0x0007  /* AUXR2BOOSTVOL - [2:0] */
+#define WM8985_AUXR2BOOSTVOL_SHIFT                   0  /* AUXR2BOOSTVOL - [2:0] */
+#define WM8985_AUXR2BOOSTVOL_WIDTH                   3  /* AUXR2BOOSTVOL - [2:0] */
+
+/*
+ * R49 (0x31) - Output ctrl
+ */
+#define WM8985_DACL2RMIX                        0x0040  /* DACL2RMIX */
+#define WM8985_DACL2RMIX_MASK                   0x0040  /* DACL2RMIX */
+#define WM8985_DACL2RMIX_SHIFT                       6  /* DACL2RMIX */
+#define WM8985_DACL2RMIX_WIDTH                       1  /* DACL2RMIX */
+#define WM8985_DACR2LMIX                        0x0020  /* DACR2LMIX */
+#define WM8985_DACR2LMIX_MASK                   0x0020  /* DACR2LMIX */
+#define WM8985_DACR2LMIX_SHIFT                       5  /* DACR2LMIX */
+#define WM8985_DACR2LMIX_WIDTH                       1  /* DACR2LMIX */
+#define WM8985_OUT4BOOST                        0x0010  /* OUT4BOOST */
+#define WM8985_OUT4BOOST_MASK                   0x0010  /* OUT4BOOST */
+#define WM8985_OUT4BOOST_SHIFT                       4  /* OUT4BOOST */
+#define WM8985_OUT4BOOST_WIDTH                       1  /* OUT4BOOST */
+#define WM8985_OUT3BOOST                        0x0008  /* OUT3BOOST */
+#define WM8985_OUT3BOOST_MASK                   0x0008  /* OUT3BOOST */
+#define WM8985_OUT3BOOST_SHIFT                       3  /* OUT3BOOST */
+#define WM8985_OUT3BOOST_WIDTH                       1  /* OUT3BOOST */
+#define WM8985_TSOPCTRL                         0x0004  /* TSOPCTRL */
+#define WM8985_TSOPCTRL_MASK                    0x0004  /* TSOPCTRL */
+#define WM8985_TSOPCTRL_SHIFT                        2  /* TSOPCTRL */
+#define WM8985_TSOPCTRL_WIDTH                        1  /* TSOPCTRL */
+#define WM8985_TSDEN                            0x0002  /* TSDEN */
+#define WM8985_TSDEN_MASK                       0x0002  /* TSDEN */
+#define WM8985_TSDEN_SHIFT                           1  /* TSDEN */
+#define WM8985_TSDEN_WIDTH                           1  /* TSDEN */
+#define WM8985_VROI                             0x0001  /* VROI */
+#define WM8985_VROI_MASK                        0x0001  /* VROI */
+#define WM8985_VROI_SHIFT                            0  /* VROI */
+#define WM8985_VROI_WIDTH                            1  /* VROI */
+
+/*
+ * R50 (0x32) - Left mixer ctrl
+ */
+#define WM8985_AUXLMIXVOL_MASK                  0x01C0  /* AUXLMIXVOL - [8:6] */
+#define WM8985_AUXLMIXVOL_SHIFT                      6  /* AUXLMIXVOL - [8:6] */
+#define WM8985_AUXLMIXVOL_WIDTH                      3  /* AUXLMIXVOL - [8:6] */
+#define WM8985_AUXL2LMIX                        0x0020  /* AUXL2LMIX */
+#define WM8985_AUXL2LMIX_MASK                   0x0020  /* AUXL2LMIX */
+#define WM8985_AUXL2LMIX_SHIFT                       5  /* AUXL2LMIX */
+#define WM8985_AUXL2LMIX_WIDTH                       1  /* AUXL2LMIX */
+#define WM8985_BYPLMIXVOL_MASK                  0x001C  /* BYPLMIXVOL - [4:2] */
+#define WM8985_BYPLMIXVOL_SHIFT                      2  /* BYPLMIXVOL - [4:2] */
+#define WM8985_BYPLMIXVOL_WIDTH                      3  /* BYPLMIXVOL - [4:2] */
+#define WM8985_BYPL2LMIX                        0x0002  /* BYPL2LMIX */
+#define WM8985_BYPL2LMIX_MASK                   0x0002  /* BYPL2LMIX */
+#define WM8985_BYPL2LMIX_SHIFT                       1  /* BYPL2LMIX */
+#define WM8985_BYPL2LMIX_WIDTH                       1  /* BYPL2LMIX */
+#define WM8985_DACL2LMIX                        0x0001  /* DACL2LMIX */
+#define WM8985_DACL2LMIX_MASK                   0x0001  /* DACL2LMIX */
+#define WM8985_DACL2LMIX_SHIFT                       0  /* DACL2LMIX */
+#define WM8985_DACL2LMIX_WIDTH                       1  /* DACL2LMIX */
+
+/*
+ * R51 (0x33) - Right mixer ctrl
+ */
+#define WM8985_AUXRMIXVOL_MASK                  0x01C0  /* AUXRMIXVOL - [8:6] */
+#define WM8985_AUXRMIXVOL_SHIFT                      6  /* AUXRMIXVOL - [8:6] */
+#define WM8985_AUXRMIXVOL_WIDTH                      3  /* AUXRMIXVOL - [8:6] */
+#define WM8985_AUXR2RMIX                        0x0020  /* AUXR2RMIX */
+#define WM8985_AUXR2RMIX_MASK                   0x0020  /* AUXR2RMIX */
+#define WM8985_AUXR2RMIX_SHIFT                       5  /* AUXR2RMIX */
+#define WM8985_AUXR2RMIX_WIDTH                       1  /* AUXR2RMIX */
+#define WM8985_BYPRMIXVOL_MASK                  0x001C  /* BYPRMIXVOL - [4:2] */
+#define WM8985_BYPRMIXVOL_SHIFT                      2  /* BYPRMIXVOL - [4:2] */
+#define WM8985_BYPRMIXVOL_WIDTH                      3  /* BYPRMIXVOL - [4:2] */
+#define WM8985_BYPR2RMIX                        0x0002  /* BYPR2RMIX */
+#define WM8985_BYPR2RMIX_MASK                   0x0002  /* BYPR2RMIX */
+#define WM8985_BYPR2RMIX_SHIFT                       1  /* BYPR2RMIX */
+#define WM8985_BYPR2RMIX_WIDTH                       1  /* BYPR2RMIX */
+#define WM8985_DACR2RMIX                        0x0001  /* DACR2RMIX */
+#define WM8985_DACR2RMIX_MASK                   0x0001  /* DACR2RMIX */
+#define WM8985_DACR2RMIX_SHIFT                       0  /* DACR2RMIX */
+#define WM8985_DACR2RMIX_WIDTH                       1  /* DACR2RMIX */
+
+/*
+ * R52 (0x34) - LOUT1 (HP) volume ctrl
+ */
+#define WM8985_OUT1VU                           0x0100  /* OUT1VU */
+#define WM8985_OUT1VU_MASK                      0x0100  /* OUT1VU */
+#define WM8985_OUT1VU_SHIFT                          8  /* OUT1VU */
+#define WM8985_OUT1VU_WIDTH                          1  /* OUT1VU */
+#define WM8985_LOUT1ZC                          0x0080  /* LOUT1ZC */
+#define WM8985_LOUT1ZC_MASK                     0x0080  /* LOUT1ZC */
+#define WM8985_LOUT1ZC_SHIFT                         7  /* LOUT1ZC */
+#define WM8985_LOUT1ZC_WIDTH                         1  /* LOUT1ZC */
+#define WM8985_LOUT1MUTE                        0x0040  /* LOUT1MUTE */
+#define WM8985_LOUT1MUTE_MASK                   0x0040  /* LOUT1MUTE */
+#define WM8985_LOUT1MUTE_SHIFT                       6  /* LOUT1MUTE */
+#define WM8985_LOUT1MUTE_WIDTH                       1  /* LOUT1MUTE */
+#define WM8985_LOUT1VOL_MASK                    0x003F  /* LOUT1VOL - [5:0] */
+#define WM8985_LOUT1VOL_SHIFT                        0  /* LOUT1VOL - [5:0] */
+#define WM8985_LOUT1VOL_WIDTH                        6  /* LOUT1VOL - [5:0] */
+
+/*
+ * R53 (0x35) - ROUT1 (HP) volume ctrl
+ */
+#define WM8985_OUT1VU                           0x0100  /* OUT1VU */
+#define WM8985_OUT1VU_MASK                      0x0100  /* OUT1VU */
+#define WM8985_OUT1VU_SHIFT                          8  /* OUT1VU */
+#define WM8985_OUT1VU_WIDTH                          1  /* OUT1VU */
+#define WM8985_ROUT1ZC                          0x0080  /* ROUT1ZC */
+#define WM8985_ROUT1ZC_MASK                     0x0080  /* ROUT1ZC */
+#define WM8985_ROUT1ZC_SHIFT                         7  /* ROUT1ZC */
+#define WM8985_ROUT1ZC_WIDTH                         1  /* ROUT1ZC */
+#define WM8985_ROUT1MUTE                        0x0040  /* ROUT1MUTE */
+#define WM8985_ROUT1MUTE_MASK                   0x0040  /* ROUT1MUTE */
+#define WM8985_ROUT1MUTE_SHIFT                       6  /* ROUT1MUTE */
+#define WM8985_ROUT1MUTE_WIDTH                       1  /* ROUT1MUTE */
+#define WM8985_ROUT1VOL_MASK                    0x003F  /* ROUT1VOL - [5:0] */
+#define WM8985_ROUT1VOL_SHIFT                        0  /* ROUT1VOL - [5:0] */
+#define WM8985_ROUT1VOL_WIDTH                        6  /* ROUT1VOL - [5:0] */
+
+/*
+ * R54 (0x36) - LOUT2 (SPK) volume ctrl
+ */
+#define WM8985_OUT2VU                           0x0100  /* OUT2VU */
+#define WM8985_OUT2VU_MASK                      0x0100  /* OUT2VU */
+#define WM8985_OUT2VU_SHIFT                          8  /* OUT2VU */
+#define WM8985_OUT2VU_WIDTH                          1  /* OUT2VU */
+#define WM8985_LOUT2ZC                          0x0080  /* LOUT2ZC */
+#define WM8985_LOUT2ZC_MASK                     0x0080  /* LOUT2ZC */
+#define WM8985_LOUT2ZC_SHIFT                         7  /* LOUT2ZC */
+#define WM8985_LOUT2ZC_WIDTH                         1  /* LOUT2ZC */
+#define WM8985_LOUT2MUTE                        0x0040  /* LOUT2MUTE */
+#define WM8985_LOUT2MUTE_MASK                   0x0040  /* LOUT2MUTE */
+#define WM8985_LOUT2MUTE_SHIFT                       6  /* LOUT2MUTE */
+#define WM8985_LOUT2MUTE_WIDTH                       1  /* LOUT2MUTE */
+#define WM8985_LOUT2VOL_MASK                    0x003F  /* LOUT2VOL - [5:0] */
+#define WM8985_LOUT2VOL_SHIFT                        0  /* LOUT2VOL - [5:0] */
+#define WM8985_LOUT2VOL_WIDTH                        6  /* LOUT2VOL - [5:0] */
+
+/*
+ * R55 (0x37) - ROUT2 (SPK) volume ctrl
+ */
+#define WM8985_OUT2VU                           0x0100  /* OUT2VU */
+#define WM8985_OUT2VU_MASK                      0x0100  /* OUT2VU */
+#define WM8985_OUT2VU_SHIFT                          8  /* OUT2VU */
+#define WM8985_OUT2VU_WIDTH                          1  /* OUT2VU */
+#define WM8985_ROUT2ZC                          0x0080  /* ROUT2ZC */
+#define WM8985_ROUT2ZC_MASK                     0x0080  /* ROUT2ZC */
+#define WM8985_ROUT2ZC_SHIFT                         7  /* ROUT2ZC */
+#define WM8985_ROUT2ZC_WIDTH                         1  /* ROUT2ZC */
+#define WM8985_ROUT2MUTE                        0x0040  /* ROUT2MUTE */
+#define WM8985_ROUT2MUTE_MASK                   0x0040  /* ROUT2MUTE */
+#define WM8985_ROUT2MUTE_SHIFT                       6  /* ROUT2MUTE */
+#define WM8985_ROUT2MUTE_WIDTH                       1  /* ROUT2MUTE */
+#define WM8985_ROUT2VOL_MASK                    0x003F  /* ROUT2VOL - [5:0] */
+#define WM8985_ROUT2VOL_SHIFT                        0  /* ROUT2VOL - [5:0] */
+#define WM8985_ROUT2VOL_WIDTH                        6  /* ROUT2VOL - [5:0] */
+
+/*
+ * R56 (0x38) - OUT3 mixer ctrl
+ */
+#define WM8985_OUT3MUTE                         0x0040  /* OUT3MUTE */
+#define WM8985_OUT3MUTE_MASK                    0x0040  /* OUT3MUTE */
+#define WM8985_OUT3MUTE_SHIFT                        6  /* OUT3MUTE */
+#define WM8985_OUT3MUTE_WIDTH                        1  /* OUT3MUTE */
+#define WM8985_OUT4_2OUT3                       0x0008  /* OUT4_2OUT3 */
+#define WM8985_OUT4_2OUT3_MASK                  0x0008  /* OUT4_2OUT3 */
+#define WM8985_OUT4_2OUT3_SHIFT                      3  /* OUT4_2OUT3 */
+#define WM8985_OUT4_2OUT3_WIDTH                      1  /* OUT4_2OUT3 */
+#define WM8985_BYPL2OUT3                        0x0004  /* BYPL2OUT3 */
+#define WM8985_BYPL2OUT3_MASK                   0x0004  /* BYPL2OUT3 */
+#define WM8985_BYPL2OUT3_SHIFT                       2  /* BYPL2OUT3 */
+#define WM8985_BYPL2OUT3_WIDTH                       1  /* BYPL2OUT3 */
+#define WM8985_LMIX2OUT3                        0x0002  /* LMIX2OUT3 */
+#define WM8985_LMIX2OUT3_MASK                   0x0002  /* LMIX2OUT3 */
+#define WM8985_LMIX2OUT3_SHIFT                       1  /* LMIX2OUT3 */
+#define WM8985_LMIX2OUT3_WIDTH                       1  /* LMIX2OUT3 */
+#define WM8985_LDAC2OUT3                        0x0001  /* LDAC2OUT3 */
+#define WM8985_LDAC2OUT3_MASK                   0x0001  /* LDAC2OUT3 */
+#define WM8985_LDAC2OUT3_SHIFT                       0  /* LDAC2OUT3 */
+#define WM8985_LDAC2OUT3_WIDTH                       1  /* LDAC2OUT3 */
+
+/*
+ * R57 (0x39) - OUT4 (MONO) mix ctrl
+ */
+#define WM8985_OUT3_2OUT4                       0x0080  /* OUT3_2OUT4 */
+#define WM8985_OUT3_2OUT4_MASK                  0x0080  /* OUT3_2OUT4 */
+#define WM8985_OUT3_2OUT4_SHIFT                      7  /* OUT3_2OUT4 */
+#define WM8985_OUT3_2OUT4_WIDTH                      1  /* OUT3_2OUT4 */
+#define WM8985_OUT4MUTE                         0x0040  /* OUT4MUTE */
+#define WM8985_OUT4MUTE_MASK                    0x0040  /* OUT4MUTE */
+#define WM8985_OUT4MUTE_SHIFT                        6  /* OUT4MUTE */
+#define WM8985_OUT4MUTE_WIDTH                        1  /* OUT4MUTE */
+#define WM8985_OUT4ATTN                         0x0020  /* OUT4ATTN */
+#define WM8985_OUT4ATTN_MASK                    0x0020  /* OUT4ATTN */
+#define WM8985_OUT4ATTN_SHIFT                        5  /* OUT4ATTN */
+#define WM8985_OUT4ATTN_WIDTH                        1  /* OUT4ATTN */
+#define WM8985_LMIX2OUT4                        0x0010  /* LMIX2OUT4 */
+#define WM8985_LMIX2OUT4_MASK                   0x0010  /* LMIX2OUT4 */
+#define WM8985_LMIX2OUT4_SHIFT                       4  /* LMIX2OUT4 */
+#define WM8985_LMIX2OUT4_WIDTH                       1  /* LMIX2OUT4 */
+#define WM8985_LDAC2OUT4                        0x0008  /* LDAC2OUT4 */
+#define WM8985_LDAC2OUT4_MASK                   0x0008  /* LDAC2OUT4 */
+#define WM8985_LDAC2OUT4_SHIFT                       3  /* LDAC2OUT4 */
+#define WM8985_LDAC2OUT4_WIDTH                       1  /* LDAC2OUT4 */
+#define WM8985_BYPR2OUT4                        0x0004  /* BYPR2OUT4 */
+#define WM8985_BYPR2OUT4_MASK                   0x0004  /* BYPR2OUT4 */
+#define WM8985_BYPR2OUT4_SHIFT                       2  /* BYPR2OUT4 */
+#define WM8985_BYPR2OUT4_WIDTH                       1  /* BYPR2OUT4 */
+#define WM8985_RMIX2OUT4                        0x0002  /* RMIX2OUT4 */
+#define WM8985_RMIX2OUT4_MASK                   0x0002  /* RMIX2OUT4 */
+#define WM8985_RMIX2OUT4_SHIFT                       1  /* RMIX2OUT4 */
+#define WM8985_RMIX2OUT4_WIDTH                       1  /* RMIX2OUT4 */
+#define WM8985_RDAC2OUT4                        0x0001  /* RDAC2OUT4 */
+#define WM8985_RDAC2OUT4_MASK                   0x0001  /* RDAC2OUT4 */
+#define WM8985_RDAC2OUT4_SHIFT                       0  /* RDAC2OUT4 */
+#define WM8985_RDAC2OUT4_WIDTH                       1  /* RDAC2OUT4 */
+
+/*
+ * R60 (0x3C) - OUTPUT ctrl
+ */
+#define WM8985_VIDBUFFTST_MASK                  0x01E0  /* VIDBUFFTST - [8:5] */
+#define WM8985_VIDBUFFTST_SHIFT                      5  /* VIDBUFFTST - [8:5] */
+#define WM8985_VIDBUFFTST_WIDTH                      4  /* VIDBUFFTST - [8:5] */
+#define WM8985_HPTOG                            0x0008  /* HPTOG */
+#define WM8985_HPTOG_MASK                       0x0008  /* HPTOG */
+#define WM8985_HPTOG_SHIFT                           3  /* HPTOG */
+#define WM8985_HPTOG_WIDTH                           1  /* HPTOG */
+
+/*
+ * R61 (0x3D) - BIAS CTRL
+ */
+#define WM8985_BIASCUT                          0x0100  /* BIASCUT */
+#define WM8985_BIASCUT_MASK                     0x0100  /* BIASCUT */
+#define WM8985_BIASCUT_SHIFT                         8  /* BIASCUT */
+#define WM8985_BIASCUT_WIDTH                         1  /* BIASCUT */
+#define WM8985_HALFIPBIAS                       0x0080  /* HALFIPBIAS */
+#define WM8985_HALFIPBIAS_MASK                  0x0080  /* HALFIPBIAS */
+#define WM8985_HALFIPBIAS_SHIFT                      7  /* HALFIPBIAS */
+#define WM8985_HALFIPBIAS_WIDTH                      1  /* HALFIPBIAS */
+#define WM8985_VBBIASTST_MASK                   0x0060  /* VBBIASTST - [6:5] */
+#define WM8985_VBBIASTST_SHIFT                       5  /* VBBIASTST - [6:5] */
+#define WM8985_VBBIASTST_WIDTH                       2  /* VBBIASTST - [6:5] */
+#define WM8985_BUFBIAS_MASK                     0x0018  /* BUFBIAS - [4:3] */
+#define WM8985_BUFBIAS_SHIFT                         3  /* BUFBIAS - [4:3] */
+#define WM8985_BUFBIAS_WIDTH                         2  /* BUFBIAS - [4:3] */
+#define WM8985_ADCBIAS_MASK                     0x0006  /* ADCBIAS - [2:1] */
+#define WM8985_ADCBIAS_SHIFT                         1  /* ADCBIAS - [2:1] */
+#define WM8985_ADCBIAS_WIDTH                         2  /* ADCBIAS - [2:1] */
+#define WM8985_HALFOPBIAS                       0x0001  /* HALFOPBIAS */
+#define WM8985_HALFOPBIAS_MASK                  0x0001  /* HALFOPBIAS */
+#define WM8985_HALFOPBIAS_SHIFT                      0  /* HALFOPBIAS */
+#define WM8985_HALFOPBIAS_WIDTH                      1  /* HALFOPBIAS */
+
+enum clk_src {
+	WM8985_CLKSRC_MCLK,
+	WM8985_CLKSRC_PLL
+};
+
+#define WM8985_PLL 0
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8988.c b/linux/sound/soc/codecs/wm8988.c
new file mode 100644
index 0000000..d7f2597
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8988.c
@@ -0,0 +1,934 @@
+/*
+ * wm8988.c -- WM8988 ALSA SoC audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "wm8988.h"
+
+/*
+ * wm8988 register cache
+ * We can't read the WM8988 register space when we
+ * are using 2 wire for device control, so we cache them instead.
+ */
+static const u16 wm8988_reg[] = {
+	0x0097, 0x0097, 0x0079, 0x0079,  /*  0 */
+	0x0000, 0x0008, 0x0000, 0x000a,  /*  4 */
+	0x0000, 0x0000, 0x00ff, 0x00ff,  /*  8 */
+	0x000f, 0x000f, 0x0000, 0x0000,  /* 12 */
+	0x0000, 0x007b, 0x0000, 0x0032,  /* 16 */
+	0x0000, 0x00c3, 0x00c3, 0x00c0,  /* 20 */
+	0x0000, 0x0000, 0x0000, 0x0000,  /* 24 */
+	0x0000, 0x0000, 0x0000, 0x0000,  /* 28 */
+	0x0000, 0x0000, 0x0050, 0x0050,  /* 32 */
+	0x0050, 0x0050, 0x0050, 0x0050,  /* 36 */
+	0x0079, 0x0079, 0x0079,          /* 40 */
+};
+
+/* codec private data */
+struct wm8988_priv {
+	unsigned int sysclk;
+	enum snd_soc_control_type control_type;
+	struct snd_pcm_hw_constraint_list *sysclk_constraints;
+	u16 reg_cache[WM8988_NUM_REG];
+};
+
+
+#define wm8988_reset(c)	snd_soc_write(c, WM8988_RESET, 0)
+
+/*
+ * WM8988 Controls
+ */
+
+static const char *bass_boost_txt[] = {"Linear Control", "Adaptive Boost"};
+static const struct soc_enum bass_boost =
+	SOC_ENUM_SINGLE(WM8988_BASS, 7, 2, bass_boost_txt);
+
+static const char *bass_filter_txt[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" };
+static const struct soc_enum bass_filter =
+	SOC_ENUM_SINGLE(WM8988_BASS, 6, 2, bass_filter_txt);
+
+static const char *treble_txt[] = {"8kHz", "4kHz"};
+static const struct soc_enum treble =
+	SOC_ENUM_SINGLE(WM8988_TREBLE, 6, 2, treble_txt);
+
+static const char *stereo_3d_lc_txt[] = {"200Hz", "500Hz"};
+static const struct soc_enum stereo_3d_lc =
+	SOC_ENUM_SINGLE(WM8988_3D, 5, 2, stereo_3d_lc_txt);
+
+static const char *stereo_3d_uc_txt[] = {"2.2kHz", "1.5kHz"};
+static const struct soc_enum stereo_3d_uc =
+	SOC_ENUM_SINGLE(WM8988_3D, 6, 2, stereo_3d_uc_txt);
+
+static const char *stereo_3d_func_txt[] = {"Capture", "Playback"};
+static const struct soc_enum stereo_3d_func =
+	SOC_ENUM_SINGLE(WM8988_3D, 7, 2, stereo_3d_func_txt);
+
+static const char *alc_func_txt[] = {"Off", "Right", "Left", "Stereo"};
+static const struct soc_enum alc_func =
+	SOC_ENUM_SINGLE(WM8988_ALC1, 7, 4, alc_func_txt);
+
+static const char *ng_type_txt[] = {"Constant PGA Gain",
+				    "Mute ADC Output"};
+static const struct soc_enum ng_type =
+	SOC_ENUM_SINGLE(WM8988_NGATE, 1, 2, ng_type_txt);
+
+static const char *deemph_txt[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const struct soc_enum deemph =
+	SOC_ENUM_SINGLE(WM8988_ADCDAC, 1, 4, deemph_txt);
+
+static const char *adcpol_txt[] = {"Normal", "L Invert", "R Invert",
+				   "L + R Invert"};
+static const struct soc_enum adcpol =
+	SOC_ENUM_SINGLE(WM8988_ADCDAC, 5, 4, adcpol_txt);
+
+static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0);
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0);
+
+static const struct snd_kcontrol_new wm8988_snd_controls[] = {
+
+SOC_ENUM("Bass Boost", bass_boost),
+SOC_ENUM("Bass Filter", bass_filter),
+SOC_SINGLE("Bass Volume", WM8988_BASS, 0, 15, 1),
+
+SOC_SINGLE("Treble Volume", WM8988_TREBLE, 0, 15, 0),
+SOC_ENUM("Treble Cut-off", treble),
+
+SOC_SINGLE("3D Switch", WM8988_3D, 0, 1, 0),
+SOC_SINGLE("3D Volume", WM8988_3D, 1, 15, 0),
+SOC_ENUM("3D Lower Cut-off", stereo_3d_lc),
+SOC_ENUM("3D Upper Cut-off", stereo_3d_uc),
+SOC_ENUM("3D Mode", stereo_3d_func),
+
+SOC_SINGLE("ALC Capture Target Volume", WM8988_ALC1, 0, 7, 0),
+SOC_SINGLE("ALC Capture Max Volume", WM8988_ALC1, 4, 7, 0),
+SOC_ENUM("ALC Capture Function", alc_func),
+SOC_SINGLE("ALC Capture ZC Switch", WM8988_ALC2, 7, 1, 0),
+SOC_SINGLE("ALC Capture Hold Time", WM8988_ALC2, 0, 15, 0),
+SOC_SINGLE("ALC Capture Decay Time", WM8988_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Capture Attack Time", WM8988_ALC3, 0, 15, 0),
+SOC_SINGLE("ALC Capture NG Threshold", WM8988_NGATE, 3, 31, 0),
+SOC_ENUM("ALC Capture NG Type", ng_type),
+SOC_SINGLE("ALC Capture NG Switch", WM8988_NGATE, 0, 1, 0),
+
+SOC_SINGLE("ZC Timeout Switch", WM8988_ADCTL1, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("Capture Digital Volume", WM8988_LADC, WM8988_RADC,
+		 0, 255, 0, adc_tlv),
+SOC_DOUBLE_R_TLV("Capture Volume", WM8988_LINVOL, WM8988_RINVOL,
+		 0, 63, 0, pga_tlv),
+SOC_DOUBLE_R("Capture ZC Switch", WM8988_LINVOL, WM8988_RINVOL, 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8988_LINVOL, WM8988_RINVOL, 7, 1, 1),
+
+SOC_ENUM("Playback De-emphasis", deemph),
+
+SOC_ENUM("Capture Polarity", adcpol),
+SOC_SINGLE("Playback 6dB Attenuate", WM8988_ADCDAC, 7, 1, 0),
+SOC_SINGLE("Capture 6dB Attenuate", WM8988_ADCDAC, 8, 1, 0),
+
+SOC_DOUBLE_R_TLV("PCM Volume", WM8988_LDAC, WM8988_RDAC, 0, 255, 0, dac_tlv),
+
+SOC_SINGLE_TLV("Left Mixer Left Bypass Volume", WM8988_LOUTM1, 4, 7, 1,
+	       bypass_tlv),
+SOC_SINGLE_TLV("Left Mixer Right Bypass Volume", WM8988_LOUTM2, 4, 7, 1,
+	       bypass_tlv),
+SOC_SINGLE_TLV("Right Mixer Left Bypass Volume", WM8988_ROUTM1, 4, 7, 1,
+	       bypass_tlv),
+SOC_SINGLE_TLV("Right Mixer Right Bypass Volume", WM8988_ROUTM2, 4, 7, 1,
+	       bypass_tlv),
+
+SOC_DOUBLE_R("Output 1 Playback ZC Switch", WM8988_LOUT1V,
+	     WM8988_ROUT1V, 7, 1, 0),
+SOC_DOUBLE_R_TLV("Output 1 Playback Volume", WM8988_LOUT1V, WM8988_ROUT1V,
+		 0, 127, 0, out_tlv),
+
+SOC_DOUBLE_R("Output 2 Playback ZC Switch", WM8988_LOUT2V,
+	     WM8988_ROUT2V, 7, 1, 0),
+SOC_DOUBLE_R_TLV("Output 2 Playback Volume", WM8988_LOUT2V, WM8988_ROUT2V,
+		 0, 127, 0, out_tlv),
+
+};
+
+/*
+ * DAPM Controls
+ */
+
+static int wm8988_lrc_control(struct snd_soc_dapm_widget *w,
+			      struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	u16 adctl2 = snd_soc_read(codec, WM8988_ADCTL2);
+
+	/* Use the DAC to gate LRC if active, otherwise use ADC */
+	if (snd_soc_read(codec, WM8988_PWR2) & 0x180)
+		adctl2 &= ~0x4;
+	else
+		adctl2 |= 0x4;
+
+	return snd_soc_write(codec, WM8988_ADCTL2, adctl2);
+}
+
+static const char *wm8988_line_texts[] = {
+	"Line 1", "Line 2", "PGA", "Differential"};
+
+static const unsigned int wm8988_line_values[] = {
+	0, 1, 3, 4};
+
+static const struct soc_enum wm8988_lline_enum =
+	SOC_VALUE_ENUM_SINGLE(WM8988_LOUTM1, 0, 7,
+			      ARRAY_SIZE(wm8988_line_texts),
+			      wm8988_line_texts,
+			      wm8988_line_values);
+static const struct snd_kcontrol_new wm8988_left_line_controls =
+	SOC_DAPM_VALUE_ENUM("Route", wm8988_lline_enum);
+
+static const struct soc_enum wm8988_rline_enum =
+	SOC_VALUE_ENUM_SINGLE(WM8988_ROUTM1, 0, 7,
+			      ARRAY_SIZE(wm8988_line_texts),
+			      wm8988_line_texts,
+			      wm8988_line_values);
+static const struct snd_kcontrol_new wm8988_right_line_controls =
+	SOC_DAPM_VALUE_ENUM("Route", wm8988_lline_enum);
+
+/* Left Mixer */
+static const struct snd_kcontrol_new wm8988_left_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Playback Switch", WM8988_LOUTM1, 8, 1, 0),
+	SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_LOUTM1, 7, 1, 0),
+	SOC_DAPM_SINGLE("Right Playback Switch", WM8988_LOUTM2, 8, 1, 0),
+	SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_LOUTM2, 7, 1, 0),
+};
+
+/* Right Mixer */
+static const struct snd_kcontrol_new wm8988_right_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Left Playback Switch", WM8988_ROUTM1, 8, 1, 0),
+	SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_ROUTM1, 7, 1, 0),
+	SOC_DAPM_SINGLE("Playback Switch", WM8988_ROUTM2, 8, 1, 0),
+	SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_ROUTM2, 7, 1, 0),
+};
+
+static const char *wm8988_pga_sel[] = {"Line 1", "Line 2", "Differential"};
+static const unsigned int wm8988_pga_val[] = { 0, 1, 3 };
+
+/* Left PGA Mux */
+static const struct soc_enum wm8988_lpga_enum =
+	SOC_VALUE_ENUM_SINGLE(WM8988_LADCIN, 6, 3,
+			      ARRAY_SIZE(wm8988_pga_sel),
+			      wm8988_pga_sel,
+			      wm8988_pga_val);
+static const struct snd_kcontrol_new wm8988_left_pga_controls =
+	SOC_DAPM_VALUE_ENUM("Route", wm8988_lpga_enum);
+
+/* Right PGA Mux */
+static const struct soc_enum wm8988_rpga_enum =
+	SOC_VALUE_ENUM_SINGLE(WM8988_RADCIN, 6, 3,
+			      ARRAY_SIZE(wm8988_pga_sel),
+			      wm8988_pga_sel,
+			      wm8988_pga_val);
+static const struct snd_kcontrol_new wm8988_right_pga_controls =
+	SOC_DAPM_VALUE_ENUM("Route", wm8988_rpga_enum);
+
+/* Differential Mux */
+static const char *wm8988_diff_sel[] = {"Line 1", "Line 2"};
+static const struct soc_enum diffmux =
+	SOC_ENUM_SINGLE(WM8988_ADCIN, 8, 2, wm8988_diff_sel);
+static const struct snd_kcontrol_new wm8988_diffmux_controls =
+	SOC_DAPM_ENUM("Route", diffmux);
+
+/* Mono ADC Mux */
+static const char *wm8988_mono_mux[] = {"Stereo", "Mono (Left)",
+	"Mono (Right)", "Digital Mono"};
+static const struct soc_enum monomux =
+	SOC_ENUM_SINGLE(WM8988_ADCIN, 6, 4, wm8988_mono_mux);
+static const struct snd_kcontrol_new wm8988_monomux_controls =
+	SOC_DAPM_ENUM("Route", monomux);
+
+static const struct snd_soc_dapm_widget wm8988_dapm_widgets[] = {
+	SND_SOC_DAPM_MICBIAS("Mic Bias", WM8988_PWR1, 1, 0),
+
+	SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0,
+		&wm8988_diffmux_controls),
+	SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
+		&wm8988_monomux_controls),
+	SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
+		&wm8988_monomux_controls),
+
+	SND_SOC_DAPM_MUX("Left PGA Mux", WM8988_PWR1, 5, 0,
+		&wm8988_left_pga_controls),
+	SND_SOC_DAPM_MUX("Right PGA Mux", WM8988_PWR1, 4, 0,
+		&wm8988_right_pga_controls),
+
+	SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
+		&wm8988_left_line_controls),
+	SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
+		&wm8988_right_line_controls),
+
+	SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8988_PWR1, 2, 0),
+	SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8988_PWR1, 3, 0),
+
+	SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8988_PWR2, 7, 0),
+	SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8988_PWR2, 8, 0),
+
+	SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
+		&wm8988_left_mixer_controls[0],
+		ARRAY_SIZE(wm8988_left_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
+		&wm8988_right_mixer_controls[0],
+		ARRAY_SIZE(wm8988_right_mixer_controls)),
+
+	SND_SOC_DAPM_PGA("Right Out 2", WM8988_PWR2, 3, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Left Out 2", WM8988_PWR2, 4, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Right Out 1", WM8988_PWR2, 5, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Left Out 1", WM8988_PWR2, 6, 0, NULL, 0),
+
+	SND_SOC_DAPM_POST("LRC control", wm8988_lrc_control),
+
+	SND_SOC_DAPM_OUTPUT("LOUT1"),
+	SND_SOC_DAPM_OUTPUT("ROUT1"),
+	SND_SOC_DAPM_OUTPUT("LOUT2"),
+	SND_SOC_DAPM_OUTPUT("ROUT2"),
+	SND_SOC_DAPM_OUTPUT("VREF"),
+
+	SND_SOC_DAPM_INPUT("LINPUT1"),
+	SND_SOC_DAPM_INPUT("LINPUT2"),
+	SND_SOC_DAPM_INPUT("RINPUT1"),
+	SND_SOC_DAPM_INPUT("RINPUT2"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+
+	{ "Left Line Mux", "Line 1", "LINPUT1" },
+	{ "Left Line Mux", "Line 2", "LINPUT2" },
+	{ "Left Line Mux", "PGA", "Left PGA Mux" },
+	{ "Left Line Mux", "Differential", "Differential Mux" },
+
+	{ "Right Line Mux", "Line 1", "RINPUT1" },
+	{ "Right Line Mux", "Line 2", "RINPUT2" },
+	{ "Right Line Mux", "PGA", "Right PGA Mux" },
+	{ "Right Line Mux", "Differential", "Differential Mux" },
+
+	{ "Left PGA Mux", "Line 1", "LINPUT1" },
+	{ "Left PGA Mux", "Line 2", "LINPUT2" },
+	{ "Left PGA Mux", "Differential", "Differential Mux" },
+
+	{ "Right PGA Mux", "Line 1", "RINPUT1" },
+	{ "Right PGA Mux", "Line 2", "RINPUT2" },
+	{ "Right PGA Mux", "Differential", "Differential Mux" },
+
+	{ "Differential Mux", "Line 1", "LINPUT1" },
+	{ "Differential Mux", "Line 1", "RINPUT1" },
+	{ "Differential Mux", "Line 2", "LINPUT2" },
+	{ "Differential Mux", "Line 2", "RINPUT2" },
+
+	{ "Left ADC Mux", "Stereo", "Left PGA Mux" },
+	{ "Left ADC Mux", "Mono (Left)", "Left PGA Mux" },
+	{ "Left ADC Mux", "Digital Mono", "Left PGA Mux" },
+
+	{ "Right ADC Mux", "Stereo", "Right PGA Mux" },
+	{ "Right ADC Mux", "Mono (Right)", "Right PGA Mux" },
+	{ "Right ADC Mux", "Digital Mono", "Right PGA Mux" },
+
+	{ "Left ADC", NULL, "Left ADC Mux" },
+	{ "Right ADC", NULL, "Right ADC Mux" },
+
+	{ "Left Line Mux", "Line 1", "LINPUT1" },
+	{ "Left Line Mux", "Line 2", "LINPUT2" },
+	{ "Left Line Mux", "PGA", "Left PGA Mux" },
+	{ "Left Line Mux", "Differential", "Differential Mux" },
+
+	{ "Right Line Mux", "Line 1", "RINPUT1" },
+	{ "Right Line Mux", "Line 2", "RINPUT2" },
+	{ "Right Line Mux", "PGA", "Right PGA Mux" },
+	{ "Right Line Mux", "Differential", "Differential Mux" },
+
+	{ "Left Mixer", "Playback Switch", "Left DAC" },
+	{ "Left Mixer", "Left Bypass Switch", "Left Line Mux" },
+	{ "Left Mixer", "Right Playback Switch", "Right DAC" },
+	{ "Left Mixer", "Right Bypass Switch", "Right Line Mux" },
+
+	{ "Right Mixer", "Left Playback Switch", "Left DAC" },
+	{ "Right Mixer", "Left Bypass Switch", "Left Line Mux" },
+	{ "Right Mixer", "Playback Switch", "Right DAC" },
+	{ "Right Mixer", "Right Bypass Switch", "Right Line Mux" },
+
+	{ "Left Out 1", NULL, "Left Mixer" },
+	{ "LOUT1", NULL, "Left Out 1" },
+	{ "Right Out 1", NULL, "Right Mixer" },
+	{ "ROUT1", NULL, "Right Out 1" },
+
+	{ "Left Out 2", NULL, "Left Mixer" },
+	{ "LOUT2", NULL, "Left Out 2" },
+	{ "Right Out 2", NULL, "Right Mixer" },
+	{ "ROUT2", NULL, "Right Out 2" },
+};
+
+struct _coeff_div {
+	u32 mclk;
+	u32 rate;
+	u16 fs;
+	u8 sr:5;
+	u8 usb:1;
+};
+
+/* codec hifi mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+	/* 8k */
+	{12288000, 8000, 1536, 0x6, 0x0},
+	{11289600, 8000, 1408, 0x16, 0x0},
+	{18432000, 8000, 2304, 0x7, 0x0},
+	{16934400, 8000, 2112, 0x17, 0x0},
+	{12000000, 8000, 1500, 0x6, 0x1},
+
+	/* 11.025k */
+	{11289600, 11025, 1024, 0x18, 0x0},
+	{16934400, 11025, 1536, 0x19, 0x0},
+	{12000000, 11025, 1088, 0x19, 0x1},
+
+	/* 16k */
+	{12288000, 16000, 768, 0xa, 0x0},
+	{18432000, 16000, 1152, 0xb, 0x0},
+	{12000000, 16000, 750, 0xa, 0x1},
+
+	/* 22.05k */
+	{11289600, 22050, 512, 0x1a, 0x0},
+	{16934400, 22050, 768, 0x1b, 0x0},
+	{12000000, 22050, 544, 0x1b, 0x1},
+
+	/* 32k */
+	{12288000, 32000, 384, 0xc, 0x0},
+	{18432000, 32000, 576, 0xd, 0x0},
+	{12000000, 32000, 375, 0xa, 0x1},
+
+	/* 44.1k */
+	{11289600, 44100, 256, 0x10, 0x0},
+	{16934400, 44100, 384, 0x11, 0x0},
+	{12000000, 44100, 272, 0x11, 0x1},
+
+	/* 48k */
+	{12288000, 48000, 256, 0x0, 0x0},
+	{18432000, 48000, 384, 0x1, 0x0},
+	{12000000, 48000, 250, 0x0, 0x1},
+
+	/* 88.2k */
+	{11289600, 88200, 128, 0x1e, 0x0},
+	{16934400, 88200, 192, 0x1f, 0x0},
+	{12000000, 88200, 136, 0x1f, 0x1},
+
+	/* 96k */
+	{12288000, 96000, 128, 0xe, 0x0},
+	{18432000, 96000, 192, 0xf, 0x0},
+	{12000000, 96000, 125, 0xe, 0x1},
+};
+
+static inline int get_coeff(int mclk, int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+		if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk)
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+/* The set of rates we can generate from the above for each SYSCLK */
+
+static unsigned int rates_12288[] = {
+	8000, 12000, 16000, 24000, 24000, 32000, 48000, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_12288 = {
+	.count	= ARRAY_SIZE(rates_12288),
+	.list	= rates_12288,
+};
+
+static unsigned int rates_112896[] = {
+	8000, 11025, 22050, 44100,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_112896 = {
+	.count	= ARRAY_SIZE(rates_112896),
+	.list	= rates_112896,
+};
+
+static unsigned int rates_12[] = {
+	8000, 11025, 12000, 16000, 22050, 2400, 32000, 41100, 48000,
+	48000, 88235, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_12 = {
+	.count	= ARRAY_SIZE(rates_12),
+	.list	= rates_12,
+};
+
+/*
+ * Note that this should be called from init rather than from hw_params.
+ */
+static int wm8988_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec);
+
+	switch (freq) {
+	case 11289600:
+	case 18432000:
+	case 22579200:
+	case 36864000:
+		wm8988->sysclk_constraints = &constraints_112896;
+		wm8988->sysclk = freq;
+		return 0;
+
+	case 12288000:
+	case 16934400:
+	case 24576000:
+	case 33868800:
+		wm8988->sysclk_constraints = &constraints_12288;
+		wm8988->sysclk = freq;
+		return 0;
+
+	case 12000000:
+	case 24000000:
+		wm8988->sysclk_constraints = &constraints_12;
+		wm8988->sysclk = freq;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int wm8988_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 iface = 0;
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		iface = 0x0040;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		iface |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iface |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		iface |= 0x0003;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		iface |= 0x0013;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		iface |= 0x0090;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		iface |= 0x0080;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		iface |= 0x0010;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8988_IFACE, iface);
+	return 0;
+}
+
+static int wm8988_pcm_startup(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec);
+
+	/* The set of sample rates that can be supported depends on the
+	 * MCLK supplied to the CODEC - enforce this.
+	 */
+	if (!wm8988->sysclk) {
+		dev_err(codec->dev,
+			"No MCLK configured, call set_sysclk() on init\n");
+		return -EINVAL;
+	}
+
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				   SNDRV_PCM_HW_PARAM_RATE,
+				   wm8988->sysclk_constraints);
+
+	return 0;
+}
+
+static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec);
+	u16 iface = snd_soc_read(codec, WM8988_IFACE) & 0x1f3;
+	u16 srate = snd_soc_read(codec, WM8988_SRATE) & 0x180;
+	int coeff;
+
+	coeff = get_coeff(wm8988->sysclk, params_rate(params));
+	if (coeff < 0) {
+		coeff = get_coeff(wm8988->sysclk / 2, params_rate(params));
+		srate |= 0x40;
+	}
+	if (coeff < 0) {
+		dev_err(codec->dev,
+			"Unable to configure sample rate %dHz with %dHz MCLK\n",
+			params_rate(params), wm8988->sysclk);
+		return coeff;
+	}
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		iface |= 0x0004;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iface |= 0x0008;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		iface |= 0x000c;
+		break;
+	}
+
+	/* set iface & srate */
+	snd_soc_write(codec, WM8988_IFACE, iface);
+	if (coeff >= 0)
+		snd_soc_write(codec, WM8988_SRATE, srate |
+			(coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
+
+	return 0;
+}
+
+static int wm8988_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 mute_reg = snd_soc_read(codec, WM8988_ADCDAC) & 0xfff7;
+
+	if (mute)
+		snd_soc_write(codec, WM8988_ADCDAC, mute_reg | 0x8);
+	else
+		snd_soc_write(codec, WM8988_ADCDAC, mute_reg);
+	return 0;
+}
+
+static int wm8988_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 pwr_reg = snd_soc_read(codec, WM8988_PWR1) & ~0x1c1;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		/* VREF, VMID=2x50k, digital enabled */
+		snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x00c0);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* VREF, VMID=2x5k */
+			snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x1c1);
+
+			/* Charge caps */
+			msleep(100);
+		}
+
+		/* VREF, VMID=2*500k, digital stopped */
+		snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x0141);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		snd_soc_write(codec, WM8988_PWR1, 0x0000);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8988_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8988_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8988_ops = {
+	.startup = wm8988_pcm_startup,
+	.hw_params = wm8988_pcm_hw_params,
+	.set_fmt = wm8988_set_dai_fmt,
+	.set_sysclk = wm8988_set_dai_sysclk,
+	.digital_mute = wm8988_mute,
+};
+
+static struct snd_soc_dai_driver wm8988_dai = {
+	.name = "wm8988-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8988_RATES,
+		.formats = WM8988_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8988_RATES,
+		.formats = WM8988_FORMATS,
+	 },
+	.ops = &wm8988_ops,
+	.symmetric_rates = 1,
+};
+
+static int wm8988_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8988_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8988_resume(struct snd_soc_codec *codec)
+{
+	int i;
+	u8 data[2];
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 0; i < WM8988_NUM_REG; i++) {
+		if (i == WM8988_RESET)
+			continue;
+		data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
+		data[1] = cache[i] & 0x00ff;
+		codec->hw_write(codec->control_data, data, 2);
+	}
+
+	wm8988_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+
+static int wm8988_probe(struct snd_soc_codec *codec)
+{
+	struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec);
+	int ret = 0;
+	u16 reg;
+
+	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8988->control_type);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm8988_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		return ret;
+	}
+
+	/* set the update bits (we always update left then right) */
+	reg = snd_soc_read(codec, WM8988_RADC);
+	snd_soc_write(codec, WM8988_RADC, reg | 0x100);
+	reg = snd_soc_read(codec, WM8988_RDAC);
+	snd_soc_write(codec, WM8988_RDAC, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8988_ROUT1V);
+	snd_soc_write(codec, WM8988_ROUT1V, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8988_ROUT2V);
+	snd_soc_write(codec, WM8988_ROUT2V, reg | 0x0100);
+	reg = snd_soc_read(codec, WM8988_RINVOL);
+	snd_soc_write(codec, WM8988_RINVOL, reg | 0x0100);
+
+	wm8988_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	snd_soc_add_controls(codec, wm8988_snd_controls,
+				ARRAY_SIZE(wm8988_snd_controls));
+	snd_soc_dapm_new_controls(codec, wm8988_dapm_widgets,
+				  ARRAY_SIZE(wm8988_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+static int wm8988_remove(struct snd_soc_codec *codec)
+{
+	wm8988_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8988 = {
+	.probe =	wm8988_probe,
+	.remove =	wm8988_remove,
+	.suspend =	wm8988_suspend,
+	.resume =	wm8988_resume,
+	.set_bias_level = wm8988_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8988_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8988_reg,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit wm8988_spi_probe(struct spi_device *spi)
+{
+	struct wm8988_priv *wm8988;
+	int ret;
+
+	wm8988 = kzalloc(sizeof(struct wm8988_priv), GFP_KERNEL);
+	if (wm8988 == NULL)
+		return -ENOMEM;
+
+	wm8988->control_type = SND_SOC_SPI;
+	spi_set_drvdata(spi, wm8988);
+
+	ret = snd_soc_register_codec(&spi->dev,
+			&soc_codec_dev_wm8988, &wm8988_dai, 1);
+	if (ret < 0)
+		kfree(wm8988);
+	return ret;
+}
+
+static int __devexit wm8988_spi_remove(struct spi_device *spi)
+{
+	snd_soc_unregister_codec(&spi->dev);
+	kfree(spi_get_drvdata(spi));
+	return 0;
+}
+
+static struct spi_driver wm8988_spi_driver = {
+	.driver = {
+		.name	= "wm8988-codec",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= wm8988_spi_probe,
+	.remove		= __devexit_p(wm8988_spi_remove),
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8988_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8988_priv *wm8988;
+	int ret;
+
+	wm8988 = kzalloc(sizeof(struct wm8988_priv), GFP_KERNEL);
+	if (wm8988 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8988);
+	wm8988->control_type = SND_SOC_I2C;
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8988, &wm8988_dai, 1);
+	if (ret < 0)
+		kfree(wm8988);
+	return ret;
+}
+
+static __devexit int wm8988_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8988_i2c_id[] = {
+	{ "wm8988", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8988_i2c_id);
+
+static struct i2c_driver wm8988_i2c_driver = {
+	.driver = {
+		.name = "wm8988-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8988_i2c_probe,
+	.remove =   __devexit_p(wm8988_i2c_remove),
+	.id_table = wm8988_i2c_id,
+};
+#endif
+
+static int __init wm8988_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8988_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8988 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	ret = spi_register_driver(&wm8988_spi_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM8988 SPI driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8988_modinit);
+
+static void __exit wm8988_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8988_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&wm8988_spi_driver);
+#endif
+}
+module_exit(wm8988_exit);
+
+
+MODULE_DESCRIPTION("ASoC WM8988 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8988.h b/linux/sound/soc/codecs/wm8988.h
new file mode 100644
index 0000000..5c04024
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8988.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.com>
+ *
+ * Based on WM8753.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _WM8988_H
+#define _WM8988_H
+
+/* WM8988 register space */
+
+#define WM8988_LINVOL    0x00
+#define WM8988_RINVOL    0x01
+#define WM8988_LOUT1V    0x02
+#define WM8988_ROUT1V    0x03
+#define WM8988_ADCDAC    0x05
+#define WM8988_IFACE     0x07
+#define WM8988_SRATE     0x08
+#define WM8988_LDAC      0x0a
+#define WM8988_RDAC      0x0b
+#define WM8988_BASS      0x0c
+#define WM8988_TREBLE    0x0d
+#define WM8988_RESET     0x0f
+#define WM8988_3D        0x10
+#define WM8988_ALC1      0x11
+#define WM8988_ALC2      0x12
+#define WM8988_ALC3      0x13
+#define WM8988_NGATE     0x14
+#define WM8988_LADC      0x15
+#define WM8988_RADC      0x16
+#define WM8988_ADCTL1    0x17
+#define WM8988_ADCTL2    0x18
+#define WM8988_PWR1      0x19
+#define WM8988_PWR2      0x1a
+#define WM8988_ADCTL3    0x1b
+#define WM8988_ADCIN     0x1f
+#define WM8988_LADCIN    0x20
+#define WM8988_RADCIN    0x21
+#define WM8988_LOUTM1    0x22
+#define WM8988_LOUTM2    0x23
+#define WM8988_ROUTM1    0x24
+#define WM8988_ROUTM2    0x25
+#define WM8988_LOUT2V    0x28
+#define WM8988_ROUT2V    0x29
+#define WM8988_LPPB      0x43
+#define WM8988_NUM_REG   0x44
+
+#define WM8988_SYSCLK	0
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8990.c b/linux/sound/soc/codecs/wm8990.c
new file mode 100644
index 0000000..264828e
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8990.c
@@ -0,0 +1,1465 @@
+/*
+ * wm8990.c  --  WM8990 ALSA Soc Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <asm/div64.h>
+
+#include "wm8990.h"
+
+/* codec private data */
+struct wm8990_priv {
+	enum snd_soc_control_type control_type;
+	unsigned int sysclk;
+	unsigned int pcmclk;
+};
+
+/*
+ * wm8990 register cache.  Note that register 0 is not included in the
+ * cache.
+ */
+static const u16 wm8990_reg[] = {
+	0x8990,     /* R0  - Reset */
+	0x0000,     /* R1  - Power Management (1) */
+	0x6000,     /* R2  - Power Management (2) */
+	0x0000,     /* R3  - Power Management (3) */
+	0x4050,     /* R4  - Audio Interface (1) */
+	0x4000,     /* R5  - Audio Interface (2) */
+	0x01C8,     /* R6  - Clocking (1) */
+	0x0000,     /* R7  - Clocking (2) */
+	0x0040,     /* R8  - Audio Interface (3) */
+	0x0040,     /* R9  - Audio Interface (4) */
+	0x0004,     /* R10 - DAC CTRL */
+	0x00C0,     /* R11 - Left DAC Digital Volume */
+	0x00C0,     /* R12 - Right DAC Digital Volume */
+	0x0000,     /* R13 - Digital Side Tone */
+	0x0100,     /* R14 - ADC CTRL */
+	0x00C0,     /* R15 - Left ADC Digital Volume */
+	0x00C0,     /* R16 - Right ADC Digital Volume */
+	0x0000,     /* R17 */
+	0x0000,     /* R18 - GPIO CTRL 1 */
+	0x1000,     /* R19 - GPIO1 & GPIO2 */
+	0x1010,     /* R20 - GPIO3 & GPIO4 */
+	0x1010,     /* R21 - GPIO5 & GPIO6 */
+	0x8000,     /* R22 - GPIOCTRL 2 */
+	0x0800,     /* R23 - GPIO_POL */
+	0x008B,     /* R24 - Left Line Input 1&2 Volume */
+	0x008B,     /* R25 - Left Line Input 3&4 Volume */
+	0x008B,     /* R26 - Right Line Input 1&2 Volume */
+	0x008B,     /* R27 - Right Line Input 3&4 Volume */
+	0x0000,     /* R28 - Left Output Volume */
+	0x0000,     /* R29 - Right Output Volume */
+	0x0066,     /* R30 - Line Outputs Volume */
+	0x0022,     /* R31 - Out3/4 Volume */
+	0x0079,     /* R32 - Left OPGA Volume */
+	0x0079,     /* R33 - Right OPGA Volume */
+	0x0003,     /* R34 - Speaker Volume */
+	0x0003,     /* R35 - ClassD1 */
+	0x0000,     /* R36 */
+	0x0100,     /* R37 - ClassD3 */
+	0x0079,     /* R38 - ClassD4 */
+	0x0000,     /* R39 - Input Mixer1 */
+	0x0000,     /* R40 - Input Mixer2 */
+	0x0000,     /* R41 - Input Mixer3 */
+	0x0000,     /* R42 - Input Mixer4 */
+	0x0000,     /* R43 - Input Mixer5 */
+	0x0000,     /* R44 - Input Mixer6 */
+	0x0000,     /* R45 - Output Mixer1 */
+	0x0000,     /* R46 - Output Mixer2 */
+	0x0000,     /* R47 - Output Mixer3 */
+	0x0000,     /* R48 - Output Mixer4 */
+	0x0000,     /* R49 - Output Mixer5 */
+	0x0000,     /* R50 - Output Mixer6 */
+	0x0180,     /* R51 - Out3/4 Mixer */
+	0x0000,     /* R52 - Line Mixer1 */
+	0x0000,     /* R53 - Line Mixer2 */
+	0x0000,     /* R54 - Speaker Mixer */
+	0x0000,     /* R55 - Additional Control */
+	0x0000,     /* R56 - AntiPOP1 */
+	0x0000,     /* R57 - AntiPOP2 */
+	0x0000,     /* R58 - MICBIAS */
+	0x0000,     /* R59 */
+	0x0008,     /* R60 - PLL1 */
+	0x0031,     /* R61 - PLL2 */
+	0x0026,     /* R62 - PLL3 */
+	0x0000,	    /* R63 - Driver internal */
+};
+
+#define wm8990_reset(c) snd_soc_write(c, WM8990_RESET, 0)
+
+static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1650, 3000, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_mix_tlv, 0, -2100, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -7300, 600, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_omix_tlv, -600, 0, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_dac_tlv, -7163, 0, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_adc_tlv, -7163, 1763, 0);
+
+static const DECLARE_TLV_DB_SCALE(out_sidetone_tlv, -3600, 0, 0);
+
+static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	int reg = mc->reg;
+	int ret;
+	u16 val;
+
+	ret = snd_soc_put_volsw(kcontrol, ucontrol);
+	if (ret < 0)
+		return ret;
+
+	/* now hit the volume update bits (always bit 8) */
+	val = snd_soc_read(codec, reg);
+	return snd_soc_write(codec, reg, val | 0x0100);
+}
+
+#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\
+	 tlv_array) {\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+		  SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+	.tlv.p = (tlv_array), \
+	.info = snd_soc_info_volsw, \
+	.get = snd_soc_get_volsw, .put = wm899x_outpga_put_volsw_vu, \
+	.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+
+static const char *wm8990_digital_sidetone[] =
+	{"None", "Left ADC", "Right ADC", "Reserved"};
+
+static const struct soc_enum wm8990_left_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE,
+	WM8990_ADC_TO_DACL_SHIFT,
+	WM8990_ADC_TO_DACL_MASK,
+	wm8990_digital_sidetone);
+
+static const struct soc_enum wm8990_right_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE,
+	WM8990_ADC_TO_DACR_SHIFT,
+	WM8990_ADC_TO_DACR_MASK,
+	wm8990_digital_sidetone);
+
+static const char *wm8990_adcmode[] =
+	{"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"};
+
+static const struct soc_enum wm8990_right_adcmode_enum =
+SOC_ENUM_SINGLE(WM8990_ADC_CTRL,
+	WM8990_ADC_HPF_CUT_SHIFT,
+	WM8990_ADC_HPF_CUT_MASK,
+	wm8990_adcmode);
+
+static const struct snd_kcontrol_new wm8990_snd_controls[] = {
+/* INMIXL */
+SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0),
+SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0),
+/* INMIXR */
+SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0),
+SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0),
+
+/* LOMIX */
+SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3,
+	WM8990_LLI3LOVOL_SHIFT, WM8990_LLI3LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3,
+	WM8990_LR12LOVOL_SHIFT, WM8990_LR12LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3,
+	WM8990_LL12LOVOL_SHIFT, WM8990_LL12LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5,
+	WM8990_LRI3LOVOL_SHIFT, WM8990_LRI3LOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5,
+	WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5,
+	WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv),
+
+/* ROMIX */
+SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4,
+	WM8990_RRI3ROVOL_SHIFT, WM8990_RRI3ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4,
+	WM8990_RL12ROVOL_SHIFT, WM8990_RL12ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4,
+	WM8990_RR12ROVOL_SHIFT, WM8990_RR12ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER6,
+	WM8990_RLI3ROVOL_SHIFT, WM8990_RLI3ROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER6,
+	WM8990_RLBROVOL_SHIFT, WM8990_RLBROVOL_MASK, 1, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER6,
+	WM8990_RRBROVOL_SHIFT, WM8990_RRBROVOL_MASK, 1, out_mix_tlv),
+
+/* LOUT */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8990_LEFT_OUTPUT_VOLUME,
+	WM8990_LOUTVOL_SHIFT, WM8990_LOUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOUT ZC", WM8990_LEFT_OUTPUT_VOLUME, WM8990_LOZC_BIT, 1, 0),
+
+/* ROUT */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8990_RIGHT_OUTPUT_VOLUME,
+	WM8990_ROUTVOL_SHIFT, WM8990_ROUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROUT ZC", WM8990_RIGHT_OUTPUT_VOLUME, WM8990_ROZC_BIT, 1, 0),
+
+/* LOPGA */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8990_LEFT_OPGA_VOLUME,
+	WM8990_LOPGAVOL_SHIFT, WM8990_LOPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOPGA ZC Switch", WM8990_LEFT_OPGA_VOLUME,
+	WM8990_LOPGAZC_BIT, 1, 0),
+
+/* ROPGA */
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8990_RIGHT_OPGA_VOLUME,
+	WM8990_ROPGAVOL_SHIFT, WM8990_ROPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROPGA ZC Switch", WM8990_RIGHT_OPGA_VOLUME,
+	WM8990_ROPGAZC_BIT, 1, 0),
+
+SOC_SINGLE("LON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+	WM8990_LONMUTE_BIT, 1, 0),
+SOC_SINGLE("LOP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+	WM8990_LOPMUTE_BIT, 1, 0),
+SOC_SINGLE("LOP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME,
+	WM8990_LOATTN_BIT, 1, 0),
+SOC_SINGLE("RON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+	WM8990_RONMUTE_BIT, 1, 0),
+SOC_SINGLE("ROP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME,
+	WM8990_ROPMUTE_BIT, 1, 0),
+SOC_SINGLE("ROP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME,
+	WM8990_ROATTN_BIT, 1, 0),
+
+SOC_SINGLE("OUT3 Mute Switch", WM8990_OUT3_4_VOLUME,
+	WM8990_OUT3MUTE_BIT, 1, 0),
+SOC_SINGLE("OUT3 Attenuation Switch", WM8990_OUT3_4_VOLUME,
+	WM8990_OUT3ATTN_BIT, 1, 0),
+
+SOC_SINGLE("OUT4 Mute Switch", WM8990_OUT3_4_VOLUME,
+	WM8990_OUT4MUTE_BIT, 1, 0),
+SOC_SINGLE("OUT4 Attenuation Switch", WM8990_OUT3_4_VOLUME,
+	WM8990_OUT4ATTN_BIT, 1, 0),
+
+SOC_SINGLE("Speaker Mode Switch", WM8990_CLASSD1,
+	WM8990_CDMODE_BIT, 1, 0),
+
+SOC_SINGLE("Speaker Output Attenuation Volume", WM8990_SPEAKER_VOLUME,
+	WM8990_SPKATTN_SHIFT, WM8990_SPKATTN_MASK, 0),
+SOC_SINGLE("Speaker DC Boost Volume", WM8990_CLASSD3,
+	WM8990_DCGAIN_SHIFT, WM8990_DCGAIN_MASK, 0),
+SOC_SINGLE("Speaker AC Boost Volume", WM8990_CLASSD3,
+	WM8990_ACGAIN_SHIFT, WM8990_ACGAIN_MASK, 0),
+SOC_SINGLE_TLV("Speaker Volume", WM8990_CLASSD4,
+	WM8990_SPKVOL_SHIFT, WM8990_SPKVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("Speaker ZC Switch", WM8990_CLASSD4,
+	WM8990_SPKZC_SHIFT, WM8990_SPKZC_MASK, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume",
+	WM8990_LEFT_DAC_DIGITAL_VOLUME,
+	WM8990_DACL_VOL_SHIFT,
+	WM8990_DACL_VOL_MASK,
+	0,
+	out_dac_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume",
+	WM8990_RIGHT_DAC_DIGITAL_VOLUME,
+	WM8990_DACR_VOL_SHIFT,
+	WM8990_DACR_VOL_MASK,
+	0,
+	out_dac_tlv),
+
+SOC_ENUM("Left Digital Sidetone", wm8990_left_digital_sidetone_enum),
+SOC_ENUM("Right Digital Sidetone", wm8990_right_digital_sidetone_enum),
+
+SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE,
+	WM8990_ADCL_DAC_SVOL_SHIFT, WM8990_ADCL_DAC_SVOL_MASK, 0,
+	out_sidetone_tlv),
+SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE,
+	WM8990_ADCR_DAC_SVOL_SHIFT, WM8990_ADCR_DAC_SVOL_MASK, 0,
+	out_sidetone_tlv),
+
+SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8990_ADC_CTRL,
+	WM8990_ADC_HPF_ENA_BIT, 1, 0),
+
+SOC_ENUM("ADC HPF Mode", wm8990_right_adcmode_enum),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume",
+	WM8990_LEFT_ADC_DIGITAL_VOLUME,
+	WM8990_ADCL_VOL_SHIFT,
+	WM8990_ADCL_VOL_MASK,
+	0,
+	in_adc_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume",
+	WM8990_RIGHT_ADC_DIGITAL_VOLUME,
+	WM8990_ADCR_VOL_SHIFT,
+	WM8990_ADCR_VOL_MASK,
+	0,
+	in_adc_tlv),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume",
+	WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+	WM8990_LIN12VOL_SHIFT,
+	WM8990_LIN12VOL_MASK,
+	0,
+	in_pga_tlv),
+
+SOC_SINGLE("LIN12 ZC Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+	WM8990_LI12ZC_BIT, 1, 0),
+
+SOC_SINGLE("LIN12 Mute Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME,
+	WM8990_LI12MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume",
+	WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+	WM8990_LIN34VOL_SHIFT,
+	WM8990_LIN34VOL_MASK,
+	0,
+	in_pga_tlv),
+
+SOC_SINGLE("LIN34 ZC Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+	WM8990_LI34ZC_BIT, 1, 0),
+
+SOC_SINGLE("LIN34 Mute Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME,
+	WM8990_LI34MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume",
+	WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+	WM8990_RIN12VOL_SHIFT,
+	WM8990_RIN12VOL_MASK,
+	0,
+	in_pga_tlv),
+
+SOC_SINGLE("RIN12 ZC Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+	WM8990_RI12ZC_BIT, 1, 0),
+
+SOC_SINGLE("RIN12 Mute Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME,
+	WM8990_RI12MUTE_BIT, 1, 0),
+
+SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume",
+	WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+	WM8990_RIN34VOL_SHIFT,
+	WM8990_RIN34VOL_MASK,
+	0,
+	in_pga_tlv),
+
+SOC_SINGLE("RIN34 ZC Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+	WM8990_RI34ZC_BIT, 1, 0),
+
+SOC_SINGLE("RIN34 Mute Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME,
+	WM8990_RI34MUTE_BIT, 1, 0),
+
+};
+
+/*
+ * _DAPM_ Controls
+ */
+
+static int inmixer_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event)
+{
+	u16 reg, fakepower;
+
+	reg = snd_soc_read(w->codec, WM8990_POWER_MANAGEMENT_2);
+	fakepower = snd_soc_read(w->codec, WM8990_INTDRIVBITS);
+
+	if (fakepower & ((1 << WM8990_INMIXL_PWR_BIT) |
+		(1 << WM8990_AINLMUX_PWR_BIT))) {
+		reg |= WM8990_AINL_ENA;
+	} else {
+		reg &= ~WM8990_AINL_ENA;
+	}
+
+	if (fakepower & ((1 << WM8990_INMIXR_PWR_BIT) |
+		(1 << WM8990_AINRMUX_PWR_BIT))) {
+		reg |= WM8990_AINR_ENA;
+	} else {
+		reg &= ~WM8990_AINL_ENA;
+	}
+	snd_soc_write(w->codec, WM8990_POWER_MANAGEMENT_2, reg);
+
+	return 0;
+}
+
+static int outmixer_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event)
+{
+	u32 reg_shift = kcontrol->private_value & 0xfff;
+	int ret = 0;
+	u16 reg;
+
+	switch (reg_shift) {
+	case WM8990_SPEAKER_MIXER | (WM8990_LDSPK_BIT << 8) :
+		reg = snd_soc_read(w->codec, WM8990_OUTPUT_MIXER1);
+		if (reg & WM8990_LDLO) {
+			printk(KERN_WARNING
+			"Cannot set as Output Mixer 1 LDLO Set\n");
+			ret = -1;
+		}
+		break;
+	case WM8990_SPEAKER_MIXER | (WM8990_RDSPK_BIT << 8):
+		reg = snd_soc_read(w->codec, WM8990_OUTPUT_MIXER2);
+		if (reg & WM8990_RDRO) {
+			printk(KERN_WARNING
+			"Cannot set as Output Mixer 2 RDRO Set\n");
+			ret = -1;
+		}
+		break;
+	case WM8990_OUTPUT_MIXER1 | (WM8990_LDLO_BIT << 8):
+		reg = snd_soc_read(w->codec, WM8990_SPEAKER_MIXER);
+		if (reg & WM8990_LDSPK) {
+			printk(KERN_WARNING
+			"Cannot set as Speaker Mixer LDSPK Set\n");
+			ret = -1;
+		}
+		break;
+	case WM8990_OUTPUT_MIXER2 | (WM8990_RDRO_BIT << 8):
+		reg = snd_soc_read(w->codec, WM8990_SPEAKER_MIXER);
+		if (reg & WM8990_RDSPK) {
+			printk(KERN_WARNING
+			"Cannot set as Speaker Mixer RDSPK Set\n");
+			ret = -1;
+		}
+		break;
+	}
+
+	return ret;
+}
+
+/* INMIX dB values */
+static const unsigned int in_mix_tlv[] = {
+	TLV_DB_RANGE_HEAD(1),
+	0, 7, TLV_DB_SCALE_ITEM(-1200, 600, 0),
+};
+
+/* Left In PGA Connections */
+static const struct snd_kcontrol_new wm8990_dapm_lin12_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN1 Switch", WM8990_INPUT_MIXER2, WM8990_LMN1_BIT, 1, 0),
+SOC_DAPM_SINGLE("LIN2 Switch", WM8990_INPUT_MIXER2, WM8990_LMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8990_dapm_lin34_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN3 Switch", WM8990_INPUT_MIXER2, WM8990_LMN3_BIT, 1, 0),
+SOC_DAPM_SINGLE("LIN4 Switch", WM8990_INPUT_MIXER2, WM8990_LMP4_BIT, 1, 0),
+};
+
+/* Right In PGA Connections */
+static const struct snd_kcontrol_new wm8990_dapm_rin12_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN1 Switch", WM8990_INPUT_MIXER2, WM8990_RMN1_BIT, 1, 0),
+SOC_DAPM_SINGLE("RIN2 Switch", WM8990_INPUT_MIXER2, WM8990_RMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8990_dapm_rin34_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN3 Switch", WM8990_INPUT_MIXER2, WM8990_RMN3_BIT, 1, 0),
+SOC_DAPM_SINGLE("RIN4 Switch", WM8990_INPUT_MIXER2, WM8990_RMP4_BIT, 1, 0),
+};
+
+/* INMIXL */
+static const struct snd_kcontrol_new wm8990_dapm_inmixl_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8990_INPUT_MIXER3,
+	WM8990_LDBVOL_SHIFT, WM8990_LDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8990_INPUT_MIXER5, WM8990_LI2BVOL_SHIFT,
+	7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("LINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT,
+	1, 0),
+SOC_DAPM_SINGLE("LINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT,
+	1, 0),
+};
+
+/* INMIXR */
+static const struct snd_kcontrol_new wm8990_dapm_inmixr_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8990_INPUT_MIXER4,
+	WM8990_RDBVOL_SHIFT, WM8990_RDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8990_INPUT_MIXER6, WM8990_RI2BVOL_SHIFT,
+	7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT,
+	1, 0),
+SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT,
+	1, 0),
+};
+
+/* AINLMUX */
+static const char *wm8990_ainlmux[] =
+	{"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"};
+
+static const struct soc_enum wm8990_ainlmux_enum =
+SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINLMODE_SHIFT,
+	ARRAY_SIZE(wm8990_ainlmux), wm8990_ainlmux);
+
+static const struct snd_kcontrol_new wm8990_dapm_ainlmux_controls =
+SOC_DAPM_ENUM("Route", wm8990_ainlmux_enum);
+
+/* DIFFINL */
+
+/* AINRMUX */
+static const char *wm8990_ainrmux[] =
+	{"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"};
+
+static const struct soc_enum wm8990_ainrmux_enum =
+SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINRMODE_SHIFT,
+	ARRAY_SIZE(wm8990_ainrmux), wm8990_ainrmux);
+
+static const struct snd_kcontrol_new wm8990_dapm_ainrmux_controls =
+SOC_DAPM_ENUM("Route", wm8990_ainrmux_enum);
+
+/* RXVOICE */
+static const struct snd_kcontrol_new wm8990_dapm_rxvoice_controls[] = {
+SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8990_INPUT_MIXER5, WM8990_LR4BVOL_SHIFT,
+			WM8990_LR4BVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8990_INPUT_MIXER6, WM8990_RL4BVOL_SHIFT,
+			WM8990_RL4BVOL_MASK, 0, in_mix_tlv),
+};
+
+/* LOMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lomix_controls[] = {
+SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER1,
+	WM8990_LRBLO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER1,
+	WM8990_LLBLO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER1,
+	WM8990_LRI3LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER1,
+	WM8990_LLI3LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1,
+	WM8990_LR12LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1,
+	WM8990_LL12LO_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8990_OUTPUT_MIXER1,
+	WM8990_LDLO_BIT, 1, 0),
+};
+
+/* ROMIX */
+static const struct snd_kcontrol_new wm8990_dapm_romix_controls[] = {
+SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER2,
+	WM8990_RLBRO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER2,
+	WM8990_RRBRO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER2,
+	WM8990_RLI3RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER2,
+	WM8990_RRI3RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2,
+	WM8990_RL12RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2,
+	WM8990_RR12RO_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8990_OUTPUT_MIXER2,
+	WM8990_RDRO_BIT, 1, 0),
+};
+
+/* LONMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lonmix_controls[] = {
+SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1,
+	WM8990_LLOPGALON_BIT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER1,
+	WM8990_LROPGALON_BIT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8990_LINE_MIXER1,
+	WM8990_LOPLON_BIT, 1, 0),
+};
+
+/* LOPMIX */
+static const struct snd_kcontrol_new wm8990_dapm_lopmix_controls[] = {
+SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER1,
+	WM8990_LR12LOP_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER1,
+	WM8990_LL12LOP_BIT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1,
+	WM8990_LLOPGALOP_BIT, 1, 0),
+};
+
+/* RONMIX */
+static const struct snd_kcontrol_new wm8990_dapm_ronmix_controls[] = {
+SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2,
+	WM8990_RROPGARON_BIT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER2,
+	WM8990_RLOPGARON_BIT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8990_LINE_MIXER2,
+	WM8990_ROPRON_BIT, 1, 0),
+};
+
+/* ROPMIX */
+static const struct snd_kcontrol_new wm8990_dapm_ropmix_controls[] = {
+SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER2,
+	WM8990_RL12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER2,
+	WM8990_RR12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2,
+	WM8990_RROPGAROP_BIT, 1, 0),
+};
+
+/* OUT3MIX */
+static const struct snd_kcontrol_new wm8990_dapm_out3mix_controls[] = {
+SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER,
+	WM8990_LI4O3_BIT, 1, 0),
+SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8990_OUT3_4_MIXER,
+	WM8990_LPGAO3_BIT, 1, 0),
+};
+
+/* OUT4MIX */
+static const struct snd_kcontrol_new wm8990_dapm_out4mix_controls[] = {
+SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8990_OUT3_4_MIXER,
+	WM8990_RPGAO4_BIT, 1, 0),
+SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER,
+	WM8990_RI4O4_BIT, 1, 0),
+};
+
+/* SPKMIX */
+static const struct snd_kcontrol_new wm8990_dapm_spkmix_controls[] = {
+SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8990_SPEAKER_MIXER,
+	WM8990_LI2SPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8990_SPEAKER_MIXER,
+	WM8990_LB2SPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8990_SPEAKER_MIXER,
+	WM8990_LOPGASPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8990_SPEAKER_MIXER,
+	WM8990_LDSPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8990_SPEAKER_MIXER,
+	WM8990_RDSPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8990_SPEAKER_MIXER,
+	WM8990_ROPGASPK_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8990_SPEAKER_MIXER,
+	WM8990_RL12ROP_BIT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8990_SPEAKER_MIXER,
+	WM8990_RI2SPK_BIT, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8990_dapm_widgets[] = {
+/* Input Side */
+/* Input Lines */
+SND_SOC_DAPM_INPUT("LIN1"),
+SND_SOC_DAPM_INPUT("LIN2"),
+SND_SOC_DAPM_INPUT("LIN3"),
+SND_SOC_DAPM_INPUT("LIN4/RXN"),
+SND_SOC_DAPM_INPUT("RIN3"),
+SND_SOC_DAPM_INPUT("RIN4/RXP"),
+SND_SOC_DAPM_INPUT("RIN1"),
+SND_SOC_DAPM_INPUT("RIN2"),
+SND_SOC_DAPM_INPUT("Internal ADC Source"),
+
+/* DACs */
+SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8990_POWER_MANAGEMENT_2,
+	WM8990_ADCL_ENA_BIT, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8990_POWER_MANAGEMENT_2,
+	WM8990_ADCR_ENA_BIT, 0),
+
+/* Input PGAs */
+SND_SOC_DAPM_MIXER("LIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN12_ENA_BIT,
+	0, &wm8990_dapm_lin12_pga_controls[0],
+	ARRAY_SIZE(wm8990_dapm_lin12_pga_controls)),
+SND_SOC_DAPM_MIXER("LIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN34_ENA_BIT,
+	0, &wm8990_dapm_lin34_pga_controls[0],
+	ARRAY_SIZE(wm8990_dapm_lin34_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN12_ENA_BIT,
+	0, &wm8990_dapm_rin12_pga_controls[0],
+	ARRAY_SIZE(wm8990_dapm_rin12_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN34_ENA_BIT,
+	0, &wm8990_dapm_rin34_pga_controls[0],
+	ARRAY_SIZE(wm8990_dapm_rin34_pga_controls)),
+
+/* INMIXL */
+SND_SOC_DAPM_MIXER_E("INMIXL", WM8990_INTDRIVBITS, WM8990_INMIXL_PWR_BIT, 0,
+	&wm8990_dapm_inmixl_controls[0],
+	ARRAY_SIZE(wm8990_dapm_inmixl_controls),
+	inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINLMUX */
+SND_SOC_DAPM_MUX_E("AINLMUX", WM8990_INTDRIVBITS, WM8990_AINLMUX_PWR_BIT, 0,
+	&wm8990_dapm_ainlmux_controls, inmixer_event,
+	SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* INMIXR */
+SND_SOC_DAPM_MIXER_E("INMIXR", WM8990_INTDRIVBITS, WM8990_INMIXR_PWR_BIT, 0,
+	&wm8990_dapm_inmixr_controls[0],
+	ARRAY_SIZE(wm8990_dapm_inmixr_controls),
+	inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINRMUX */
+SND_SOC_DAPM_MUX_E("AINRMUX", WM8990_INTDRIVBITS, WM8990_AINRMUX_PWR_BIT, 0,
+	&wm8990_dapm_ainrmux_controls, inmixer_event,
+	SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* Output Side */
+/* DACs */
+SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8990_POWER_MANAGEMENT_3,
+	WM8990_DACL_ENA_BIT, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8990_POWER_MANAGEMENT_3,
+	WM8990_DACR_ENA_BIT, 0),
+
+/* LOMIX */
+SND_SOC_DAPM_MIXER_E("LOMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOMIX_ENA_BIT,
+	0, &wm8990_dapm_lomix_controls[0],
+	ARRAY_SIZE(wm8990_dapm_lomix_controls),
+	outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LONMIX */
+SND_SOC_DAPM_MIXER("LONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LON_ENA_BIT, 0,
+	&wm8990_dapm_lonmix_controls[0],
+	ARRAY_SIZE(wm8990_dapm_lonmix_controls)),
+
+/* LOPMIX */
+SND_SOC_DAPM_MIXER("LOPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOP_ENA_BIT, 0,
+	&wm8990_dapm_lopmix_controls[0],
+	ARRAY_SIZE(wm8990_dapm_lopmix_controls)),
+
+/* OUT3MIX */
+SND_SOC_DAPM_MIXER("OUT3MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT3_ENA_BIT, 0,
+	&wm8990_dapm_out3mix_controls[0],
+	ARRAY_SIZE(wm8990_dapm_out3mix_controls)),
+
+/* SPKMIX */
+SND_SOC_DAPM_MIXER_E("SPKMIX", WM8990_POWER_MANAGEMENT_1, WM8990_SPK_ENA_BIT, 0,
+	&wm8990_dapm_spkmix_controls[0],
+	ARRAY_SIZE(wm8990_dapm_spkmix_controls), outmixer_event,
+	SND_SOC_DAPM_PRE_REG),
+
+/* OUT4MIX */
+SND_SOC_DAPM_MIXER("OUT4MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT4_ENA_BIT, 0,
+	&wm8990_dapm_out4mix_controls[0],
+	ARRAY_SIZE(wm8990_dapm_out4mix_controls)),
+
+/* ROPMIX */
+SND_SOC_DAPM_MIXER("ROPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROP_ENA_BIT, 0,
+	&wm8990_dapm_ropmix_controls[0],
+	ARRAY_SIZE(wm8990_dapm_ropmix_controls)),
+
+/* RONMIX */
+SND_SOC_DAPM_MIXER("RONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_RON_ENA_BIT, 0,
+	&wm8990_dapm_ronmix_controls[0],
+	ARRAY_SIZE(wm8990_dapm_ronmix_controls)),
+
+/* ROMIX */
+SND_SOC_DAPM_MIXER_E("ROMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROMIX_ENA_BIT,
+	0, &wm8990_dapm_romix_controls[0],
+	ARRAY_SIZE(wm8990_dapm_romix_controls),
+	outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LOUT PGA */
+SND_SOC_DAPM_PGA("LOUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_LOUT_ENA_BIT, 0,
+	NULL, 0),
+
+/* ROUT PGA */
+SND_SOC_DAPM_PGA("ROUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_ROUT_ENA_BIT, 0,
+	NULL, 0),
+
+/* LOPGA */
+SND_SOC_DAPM_PGA("LOPGA", WM8990_POWER_MANAGEMENT_3, WM8990_LOPGA_ENA_BIT, 0,
+	NULL, 0),
+
+/* ROPGA */
+SND_SOC_DAPM_PGA("ROPGA", WM8990_POWER_MANAGEMENT_3, WM8990_ROPGA_ENA_BIT, 0,
+	NULL, 0),
+
+/* MICBIAS */
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8990_POWER_MANAGEMENT_1,
+	WM8990_MICBIAS_ENA_BIT, 0),
+
+SND_SOC_DAPM_OUTPUT("LON"),
+SND_SOC_DAPM_OUTPUT("LOP"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("SPKN"),
+SND_SOC_DAPM_OUTPUT("SPKP"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_OUTPUT("ROP"),
+SND_SOC_DAPM_OUTPUT("RON"),
+
+SND_SOC_DAPM_OUTPUT("Internal DAC Sink"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Make DACs turn on when playing even if not mixed into any outputs */
+	{"Internal DAC Sink", NULL, "Left DAC"},
+	{"Internal DAC Sink", NULL, "Right DAC"},
+
+	/* Make ADCs turn on when recording even if not mixed from any inputs */
+	{"Left ADC", NULL, "Internal ADC Source"},
+	{"Right ADC", NULL, "Internal ADC Source"},
+
+	/* Input Side */
+	/* LIN12 PGA */
+	{"LIN12 PGA", "LIN1 Switch", "LIN1"},
+	{"LIN12 PGA", "LIN2 Switch", "LIN2"},
+	/* LIN34 PGA */
+	{"LIN34 PGA", "LIN3 Switch", "LIN3"},
+	{"LIN34 PGA", "LIN4 Switch", "LIN4/RXN"},
+	/* INMIXL */
+	{"INMIXL", "Record Left Volume", "LOMIX"},
+	{"INMIXL", "LIN2 Volume", "LIN2"},
+	{"INMIXL", "LINPGA12 Switch", "LIN12 PGA"},
+	{"INMIXL", "LINPGA34 Switch", "LIN34 PGA"},
+	/* AINLMUX */
+	{"AINLMUX", "INMIXL Mix", "INMIXL"},
+	{"AINLMUX", "DIFFINL Mix", "LIN12 PGA"},
+	{"AINLMUX", "DIFFINL Mix", "LIN34 PGA"},
+	{"AINLMUX", "RXVOICE Mix", "LIN4/RXN"},
+	{"AINLMUX", "RXVOICE Mix", "RIN4/RXP"},
+	/* ADC */
+	{"Left ADC", NULL, "AINLMUX"},
+
+	/* RIN12 PGA */
+	{"RIN12 PGA", "RIN1 Switch", "RIN1"},
+	{"RIN12 PGA", "RIN2 Switch", "RIN2"},
+	/* RIN34 PGA */
+	{"RIN34 PGA", "RIN3 Switch", "RIN3"},
+	{"RIN34 PGA", "RIN4 Switch", "RIN4/RXP"},
+	/* INMIXL */
+	{"INMIXR", "Record Right Volume", "ROMIX"},
+	{"INMIXR", "RIN2 Volume", "RIN2"},
+	{"INMIXR", "RINPGA12 Switch", "RIN12 PGA"},
+	{"INMIXR", "RINPGA34 Switch", "RIN34 PGA"},
+	/* AINRMUX */
+	{"AINRMUX", "INMIXR Mix", "INMIXR"},
+	{"AINRMUX", "DIFFINR Mix", "RIN12 PGA"},
+	{"AINRMUX", "DIFFINR Mix", "RIN34 PGA"},
+	{"AINRMUX", "RXVOICE Mix", "LIN4/RXN"},
+	{"AINRMUX", "RXVOICE Mix", "RIN4/RXP"},
+	/* ADC */
+	{"Right ADC", NULL, "AINRMUX"},
+
+	/* LOMIX */
+	{"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"},
+	{"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"},
+	{"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+	{"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+	{"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"},
+	{"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"},
+	{"LOMIX", "LOMIX Left DAC Switch", "Left DAC"},
+
+	/* ROMIX */
+	{"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"},
+	{"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"},
+	{"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+	{"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+	{"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"},
+	{"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"},
+	{"ROMIX", "ROMIX Right DAC Switch", "Right DAC"},
+
+	/* SPKMIX */
+	{"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"},
+	{"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"},
+	{"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"},
+	{"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"},
+	{"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"},
+	{"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"},
+	{"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"},
+	{"SPKMIX", "SPKMIX Left DAC Switch", "Left DAC"},
+
+	/* LONMIX */
+	{"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"},
+	{"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"},
+	{"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"},
+
+	/* LOPMIX */
+	{"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+	{"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+	{"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"},
+
+	/* OUT3MIX */
+	{"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXN"},
+	{"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"},
+
+	/* OUT4MIX */
+	{"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"},
+	{"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"},
+
+	/* RONMIX */
+	{"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"},
+	{"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"},
+	{"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"},
+
+	/* ROPMIX */
+	{"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+	{"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+	{"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"},
+
+	/* Out Mixer PGAs */
+	{"LOPGA", NULL, "LOMIX"},
+	{"ROPGA", NULL, "ROMIX"},
+
+	{"LOUT PGA", NULL, "LOMIX"},
+	{"ROUT PGA", NULL, "ROMIX"},
+
+	/* Output Pins */
+	{"LON", NULL, "LONMIX"},
+	{"LOP", NULL, "LOPMIX"},
+	{"OUT3", NULL, "OUT3MIX"},
+	{"LOUT", NULL, "LOUT PGA"},
+	{"SPKN", NULL, "SPKMIX"},
+	{"ROUT", NULL, "ROUT PGA"},
+	{"OUT4", NULL, "OUT4MIX"},
+	{"ROP", NULL, "ROPMIX"},
+	{"RON", NULL, "RONMIX"},
+};
+
+static int wm8990_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm8990_dapm_widgets,
+				  ARRAY_SIZE(wm8990_dapm_widgets));
+
+	/* set up the WM8990 audio map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+	u32 div2;
+	u32 n;
+	u32 k;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 16) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int target,
+	unsigned int source)
+{
+	u64 Kpart;
+	unsigned int K, Ndiv, Nmod;
+
+
+	Ndiv = target / source;
+	if (Ndiv < 6) {
+		source >>= 1;
+		pll_div->div2 = 1;
+		Ndiv = target / source;
+	} else
+		pll_div->div2 = 0;
+
+	if ((Ndiv < 6) || (Ndiv > 12))
+		printk(KERN_WARNING
+		"WM8990 N value outwith recommended range! N = %u\n", Ndiv);
+
+	pll_div->n = Ndiv;
+	Nmod = target % source;
+	Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	/* Check if we need to round */
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	K /= 10;
+
+	pll_div->k = K;
+}
+
+static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	u16 reg;
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct _pll_div pll_div;
+
+	if (freq_in && freq_out) {
+		pll_factors(&pll_div, freq_out * 4, freq_in);
+
+		/* Turn on PLL */
+		reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2);
+		reg |= WM8990_PLL_ENA;
+		snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
+
+		/* sysclk comes from PLL */
+		reg = snd_soc_read(codec, WM8990_CLOCKING_2);
+		snd_soc_write(codec, WM8990_CLOCKING_2, reg | WM8990_SYSCLK_SRC);
+
+		/* set up N , fractional mode and pre-divisor if necessary */
+		snd_soc_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM |
+			(pll_div.div2?WM8990_PRESCALE:0));
+		snd_soc_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8));
+		snd_soc_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF));
+	} else {
+		/* Turn on PLL */
+		reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2);
+		reg &= ~WM8990_PLL_ENA;
+		snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
+	}
+	return 0;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int wm8990_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8990_priv *wm8990 = snd_soc_codec_get_drvdata(codec);
+
+	wm8990->sysclk = freq;
+	return 0;
+}
+
+/*
+ * Set's ADC and Voice DAC format.
+ */
+static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 audio1, audio3;
+
+	audio1 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_1);
+	audio3 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_3);
+
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		audio3 &= ~WM8990_AIF_MSTR1;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		audio3 |= WM8990_AIF_MSTR1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	audio1 &= ~WM8990_AIF_FMT_MASK;
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		audio1 |= WM8990_AIF_TMF_I2S;
+		audio1 &= ~WM8990_AIF_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		audio1 |= WM8990_AIF_TMF_RIGHTJ;
+		audio1 &= ~WM8990_AIF_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		audio1 |= WM8990_AIF_TMF_LEFTJ;
+		audio1 &= ~WM8990_AIF_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		audio1 |= WM8990_AIF_TMF_DSP;
+		audio1 &= ~WM8990_AIF_LRCLK_INV;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		audio1 |= WM8990_AIF_TMF_DSP | WM8990_AIF_LRCLK_INV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
+	snd_soc_write(codec, WM8990_AUDIO_INTERFACE_3, audio3);
+	return 0;
+}
+
+static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+		int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+
+	switch (div_id) {
+	case WM8990_MCLK_DIV:
+		reg = snd_soc_read(codec, WM8990_CLOCKING_2) &
+			~WM8990_MCLK_DIV_MASK;
+		snd_soc_write(codec, WM8990_CLOCKING_2, reg | div);
+		break;
+	case WM8990_DACCLK_DIV:
+		reg = snd_soc_read(codec, WM8990_CLOCKING_2) &
+			~WM8990_DAC_CLKDIV_MASK;
+		snd_soc_write(codec, WM8990_CLOCKING_2, reg | div);
+		break;
+	case WM8990_ADCCLK_DIV:
+		reg = snd_soc_read(codec, WM8990_CLOCKING_2) &
+			~WM8990_ADC_CLKDIV_MASK;
+		snd_soc_write(codec, WM8990_CLOCKING_2, reg | div);
+		break;
+	case WM8990_BCLK_DIV:
+		reg = snd_soc_read(codec, WM8990_CLOCKING_1) &
+			~WM8990_BCLK_DIV_MASK;
+		snd_soc_write(codec, WM8990_CLOCKING_1, reg | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8990_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	u16 audio1 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_1);
+
+	audio1 &= ~WM8990_AIF_WL_MASK;
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		audio1 |= WM8990_AIF_WL_20BITS;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		audio1 |= WM8990_AIF_WL_24BITS;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		audio1 |= WM8990_AIF_WL_32BITS;
+		break;
+	}
+
+	snd_soc_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
+	return 0;
+}
+
+static int wm8990_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 val;
+
+	val  = snd_soc_read(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE;
+
+	if (mute)
+		snd_soc_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
+	else
+		snd_soc_write(codec, WM8990_DAC_CTRL, val);
+
+	return 0;
+}
+
+static int wm8990_set_bias_level(struct snd_soc_codec *codec,
+	enum snd_soc_bias_level level)
+{
+	u16 val;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		/* VMID=2*50k */
+		val = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_1) &
+			~WM8990_VMID_MODE_MASK;
+		snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x2);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Enable all output discharge bits */
+			snd_soc_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
+				WM8990_DIS_RLINE | WM8990_DIS_OUT3 |
+				WM8990_DIS_OUT4 | WM8990_DIS_LOUT |
+				WM8990_DIS_ROUT);
+
+			/* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */
+			snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+				     WM8990_BUFDCOPEN | WM8990_POBCTRL |
+				     WM8990_VMIDTOG);
+
+			/* Delay to allow output caps to discharge */
+			msleep(msecs_to_jiffies(300));
+
+			/* Disable VMIDTOG */
+			snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+				     WM8990_BUFDCOPEN | WM8990_POBCTRL);
+
+			/* disable all output discharge bits */
+			snd_soc_write(codec, WM8990_ANTIPOP1, 0);
+
+			/* Enable outputs */
+			snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00);
+
+			msleep(msecs_to_jiffies(50));
+
+			/* Enable VMID at 2x50k */
+			snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02);
+
+			msleep(msecs_to_jiffies(100));
+
+			/* Enable VREF */
+			snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
+
+			msleep(msecs_to_jiffies(600));
+
+			/* Enable BUFIOEN */
+			snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+				     WM8990_BUFDCOPEN | WM8990_POBCTRL |
+				     WM8990_BUFIOEN);
+
+			/* Disable outputs */
+			snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3);
+
+			/* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+			snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN);
+
+			/* Enable workaround for ADC clocking issue. */
+			snd_soc_write(codec, WM8990_EXT_ACCESS_ENA, 0x2);
+			snd_soc_write(codec, WM8990_EXT_CTL1, 0xa003);
+			snd_soc_write(codec, WM8990_EXT_ACCESS_ENA, 0);
+		}
+
+		/* VMID=2*250k */
+		val = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_1) &
+			~WM8990_VMID_MODE_MASK;
+		snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x4);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		/* Enable POBCTRL and SOFT_ST */
+		snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+			WM8990_POBCTRL | WM8990_BUFIOEN);
+
+		/* Enable POBCTRL, SOFT_ST and BUFDCOPEN */
+		snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
+			WM8990_BUFDCOPEN | WM8990_POBCTRL |
+			WM8990_BUFIOEN);
+
+		/* mute DAC */
+		val = snd_soc_read(codec, WM8990_DAC_CTRL);
+		snd_soc_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
+
+		/* Enable any disabled outputs */
+		snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
+
+		/* Disable VMID */
+		snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01);
+
+		msleep(msecs_to_jiffies(300));
+
+		/* Enable all output discharge bits */
+		snd_soc_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
+			WM8990_DIS_RLINE | WM8990_DIS_OUT3 |
+			WM8990_DIS_OUT4 | WM8990_DIS_LOUT |
+			WM8990_DIS_ROUT);
+
+		/* Disable VREF */
+		snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0);
+
+		/* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+		snd_soc_write(codec, WM8990_ANTIPOP2, 0x0);
+		break;
+	}
+
+	codec->bias_level = level;
+	return 0;
+}
+
+#define WM8990_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+	SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+	SNDRV_PCM_RATE_48000)
+
+#define WM8990_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+/*
+ * The WM8990 supports 2 different and mutually exclusive DAI
+ * configurations.
+ *
+ * 1. ADC/DAC on Primary Interface
+ * 2. ADC on Primary Interface/DAC on secondary
+ */
+static struct snd_soc_dai_ops wm8990_dai_ops = {
+	.hw_params	= wm8990_hw_params,
+	.digital_mute	= wm8990_mute,
+	.set_fmt	= wm8990_set_dai_fmt,
+	.set_clkdiv	= wm8990_set_dai_clkdiv,
+	.set_pll	= wm8990_set_dai_pll,
+	.set_sysclk	= wm8990_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver wm8990_dai = {
+/* ADC/DAC on primary */
+	.name = "wm8990-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8990_RATES,
+		.formats = WM8990_FORMATS,},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8990_RATES,
+		.formats = WM8990_FORMATS,},
+	.ops = &wm8990_dai_ops,
+};
+
+static int wm8990_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm8990_resume(struct snd_soc_codec *codec)
+{
+	int i;
+	u8 data[2];
+	u16 *cache = codec->reg_cache;
+
+	/* Sync reg_cache with the hardware */
+	for (i = 0; i < ARRAY_SIZE(wm8990_reg); i++) {
+		if (i + 1 == WM8990_RESET)
+			continue;
+		data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001);
+		data[1] = cache[i] & 0x00ff;
+		codec->hw_write(codec->control_data, data, 2);
+	}
+
+	wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	return 0;
+}
+
+/*
+ * initialise the WM8990 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wm8990_probe(struct snd_soc_codec *codec)
+{
+	int ret;
+	u16 reg;
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+	if (ret < 0) {
+		printk(KERN_ERR "wm8990: failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	wm8990_reset(codec);
+
+	/* charge output caps */
+	wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	reg = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_4);
+	snd_soc_write(codec, WM8990_AUDIO_INTERFACE_4, reg | WM8990_ALRCGPIO1);
+
+	reg = snd_soc_read(codec, WM8990_GPIO1_GPIO2) &
+		~WM8990_GPIO1_SEL_MASK;
+	snd_soc_write(codec, WM8990_GPIO1_GPIO2, reg | 1);
+
+	reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2);
+	snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg | WM8990_OPCLK_ENA);
+
+	snd_soc_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8));
+	snd_soc_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8));
+
+	snd_soc_add_controls(codec, wm8990_snd_controls,
+				ARRAY_SIZE(wm8990_snd_controls));
+	wm8990_add_widgets(codec);
+
+	return 0;
+}
+
+/* power down chip */
+static int wm8990_remove(struct snd_soc_codec *codec)
+{
+	wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8990 = {
+	.probe =	wm8990_probe,
+	.remove =	wm8990_remove,
+	.suspend =	wm8990_suspend,
+	.resume =	wm8990_resume,
+	.set_bias_level = wm8990_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8990_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8990_reg,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8990_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8990_priv *wm8990;
+	int ret;
+
+	wm8990 = kzalloc(sizeof(struct wm8990_priv), GFP_KERNEL);
+	if (wm8990 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8990);
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8990, &wm8990_dai, 1);
+	if (ret < 0)
+		kfree(wm8990);
+	return ret;
+}
+
+static __devexit int wm8990_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8990_i2c_id[] = {
+	{ "wm8990", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8990_i2c_id);
+
+static struct i2c_driver wm8990_i2c_driver = {
+	.driver = {
+		.name = "wm8990-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8990_i2c_probe,
+	.remove =   __devexit_p(wm8990_i2c_remove),
+	.id_table = wm8990_i2c_id,
+};
+#endif
+
+static int __init wm8990_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8990_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register wm8990 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8990_modinit);
+
+static void __exit wm8990_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8990_i2c_driver);
+#endif
+}
+module_exit(wm8990_exit);
+
+MODULE_DESCRIPTION("ASoC WM8990 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8990.h b/linux/sound/soc/codecs/wm8990.h
new file mode 100644
index 0000000..77c98a4
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8990.h
@@ -0,0 +1,835 @@
+/*
+ * wm8990.h  --  audio driver for WM8990
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ *         graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ *  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.
+ *
+ */
+
+#ifndef __WM8990REGISTERDEFS_H__
+#define __WM8990REGISTERDEFS_H__
+
+/*
+ * Register values.
+ */
+#define WM8990_RESET                            0x00
+#define WM8990_POWER_MANAGEMENT_1               0x01
+#define WM8990_POWER_MANAGEMENT_2               0x02
+#define WM8990_POWER_MANAGEMENT_3               0x03
+#define WM8990_AUDIO_INTERFACE_1                0x04
+#define WM8990_AUDIO_INTERFACE_2                0x05
+#define WM8990_CLOCKING_1                       0x06
+#define WM8990_CLOCKING_2                       0x07
+#define WM8990_AUDIO_INTERFACE_3                0x08
+#define WM8990_AUDIO_INTERFACE_4                0x09
+#define WM8990_DAC_CTRL                         0x0A
+#define WM8990_LEFT_DAC_DIGITAL_VOLUME          0x0B
+#define WM8990_RIGHT_DAC_DIGITAL_VOLUME         0x0C
+#define WM8990_DIGITAL_SIDE_TONE                0x0D
+#define WM8990_ADC_CTRL                         0x0E
+#define WM8990_LEFT_ADC_DIGITAL_VOLUME          0x0F
+#define WM8990_RIGHT_ADC_DIGITAL_VOLUME         0x10
+#define WM8990_GPIO_CTRL_1                      0x12
+#define WM8990_GPIO1_GPIO2                      0x13
+#define WM8990_GPIO3_GPIO4                      0x14
+#define WM8990_GPIO5_GPIO6                      0x15
+#define WM8990_GPIOCTRL_2                       0x16
+#define WM8990_GPIO_POL                         0x17
+#define WM8990_LEFT_LINE_INPUT_1_2_VOLUME       0x18
+#define WM8990_LEFT_LINE_INPUT_3_4_VOLUME       0x19
+#define WM8990_RIGHT_LINE_INPUT_1_2_VOLUME      0x1A
+#define WM8990_RIGHT_LINE_INPUT_3_4_VOLUME      0x1B
+#define WM8990_LEFT_OUTPUT_VOLUME               0x1C
+#define WM8990_RIGHT_OUTPUT_VOLUME              0x1D
+#define WM8990_LINE_OUTPUTS_VOLUME              0x1E
+#define WM8990_OUT3_4_VOLUME                    0x1F
+#define WM8990_LEFT_OPGA_VOLUME                 0x20
+#define WM8990_RIGHT_OPGA_VOLUME                0x21
+#define WM8990_SPEAKER_VOLUME                   0x22
+#define WM8990_CLASSD1                          0x23
+#define WM8990_CLASSD3                          0x25
+#define WM8990_CLASSD4                          0x26
+#define WM8990_INPUT_MIXER1                     0x27
+#define WM8990_INPUT_MIXER2                     0x28
+#define WM8990_INPUT_MIXER3                     0x29
+#define WM8990_INPUT_MIXER4                     0x2A
+#define WM8990_INPUT_MIXER5                     0x2B
+#define WM8990_INPUT_MIXER6                     0x2C
+#define WM8990_OUTPUT_MIXER1                    0x2D
+#define WM8990_OUTPUT_MIXER2                    0x2E
+#define WM8990_OUTPUT_MIXER3                    0x2F
+#define WM8990_OUTPUT_MIXER4                    0x30
+#define WM8990_OUTPUT_MIXER5                    0x31
+#define WM8990_OUTPUT_MIXER6                    0x32
+#define WM8990_OUT3_4_MIXER                     0x33
+#define WM8990_LINE_MIXER1                      0x34
+#define WM8990_LINE_MIXER2                      0x35
+#define WM8990_SPEAKER_MIXER                    0x36
+#define WM8990_ADDITIONAL_CONTROL               0x37
+#define WM8990_ANTIPOP1                         0x38
+#define WM8990_ANTIPOP2                         0x39
+#define WM8990_MICBIAS                          0x3A
+#define WM8990_PLL1                             0x3C
+#define WM8990_PLL2                             0x3D
+#define WM8990_PLL3                             0x3E
+#define WM8990_INTDRIVBITS			0x3F
+
+#define WM8990_EXT_ACCESS_ENA			0x75
+#define WM8990_EXT_CTL1				0x7a
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Reset
+ */
+#define WM8990_SW_RESET_CHIP_ID_MASK            0xFFFF  /* SW_RESET_CHIP_ID */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM8990_SPK_ENA                          0x1000  /* SPK_ENA */
+#define WM8990_SPK_ENA_BIT			12
+#define WM8990_OUT3_ENA                         0x0800  /* OUT3_ENA */
+#define WM8990_OUT3_ENA_BIT			11
+#define WM8990_OUT4_ENA                         0x0400  /* OUT4_ENA */
+#define WM8990_OUT4_ENA_BIT			10
+#define WM8990_LOUT_ENA                         0x0200  /* LOUT_ENA */
+#define WM8990_LOUT_ENA_BIT			9
+#define WM8990_ROUT_ENA                         0x0100  /* ROUT_ENA */
+#define WM8990_ROUT_ENA_BIT			8
+#define WM8990_MICBIAS_ENA                      0x0010  /* MICBIAS_ENA */
+#define WM8990_MICBIAS_ENA_BIT			4
+#define WM8990_VMID_MODE_MASK                   0x0006  /* VMID_MODE - [2:1] */
+#define WM8990_VREF_ENA                         0x0001  /* VREF_ENA */
+#define WM8990_VREF_ENA_BIT			0
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM8990_PLL_ENA                          0x8000  /* PLL_ENA */
+#define WM8990_PLL_ENA_BIT			15
+#define WM8990_TSHUT_ENA                        0x4000  /* TSHUT_ENA */
+#define WM8990_TSHUT_ENA_BIT			14
+#define WM8990_TSHUT_OPDIS                      0x2000  /* TSHUT_OPDIS */
+#define WM8990_TSHUT_OPDIS_BIT			13
+#define WM8990_OPCLK_ENA                        0x0800  /* OPCLK_ENA */
+#define WM8990_OPCLK_ENA_BIT			11
+#define WM8990_AINL_ENA                         0x0200  /* AINL_ENA */
+#define WM8990_AINL_ENA_BIT			9
+#define WM8990_AINR_ENA                         0x0100  /* AINR_ENA */
+#define WM8990_AINR_ENA_BIT			8
+#define WM8990_LIN34_ENA                        0x0080  /* LIN34_ENA */
+#define WM8990_LIN34_ENA_BIT			7
+#define WM8990_LIN12_ENA                        0x0040  /* LIN12_ENA */
+#define WM8990_LIN12_ENA_BIT			6
+#define WM8990_RIN34_ENA                        0x0020  /* RIN34_ENA */
+#define WM8990_RIN34_ENA_BIT			5
+#define WM8990_RIN12_ENA                        0x0010  /* RIN12_ENA */
+#define WM8990_RIN12_ENA_BIT			4
+#define WM8990_ADCL_ENA                         0x0002  /* ADCL_ENA */
+#define WM8990_ADCL_ENA_BIT			1
+#define WM8990_ADCR_ENA                         0x0001  /* ADCR_ENA */
+#define WM8990_ADCR_ENA_BIT			0
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM8990_LON_ENA                          0x2000  /* LON_ENA */
+#define WM8990_LON_ENA_BIT			13
+#define WM8990_LOP_ENA                          0x1000  /* LOP_ENA */
+#define WM8990_LOP_ENA_BIT			12
+#define WM8990_RON_ENA                          0x0800  /* RON_ENA */
+#define WM8990_RON_ENA_BIT			11
+#define WM8990_ROP_ENA                          0x0400  /* ROP_ENA */
+#define WM8990_ROP_ENA_BIT			10
+#define WM8990_LOPGA_ENA                        0x0080  /* LOPGA_ENA */
+#define WM8990_LOPGA_ENA_BIT			7
+#define WM8990_ROPGA_ENA                        0x0040  /* ROPGA_ENA */
+#define WM8990_ROPGA_ENA_BIT			6
+#define WM8990_LOMIX_ENA                        0x0020  /* LOMIX_ENA */
+#define WM8990_LOMIX_ENA_BIT			5
+#define WM8990_ROMIX_ENA                        0x0010  /* ROMIX_ENA */
+#define WM8990_ROMIX_ENA_BIT			4
+#define WM8990_DACL_ENA                         0x0002  /* DACL_ENA */
+#define WM8990_DACL_ENA_BIT			1
+#define WM8990_DACR_ENA                         0x0001  /* DACR_ENA */
+#define WM8990_DACR_ENA_BIT			0
+
+/*
+ * R4 (0x04) - Audio Interface (1)
+ */
+#define WM8990_AIFADCL_SRC                      0x8000  /* AIFADCL_SRC */
+#define WM8990_AIFADCR_SRC                      0x4000  /* AIFADCR_SRC */
+#define WM8990_AIFADC_TDM                       0x2000  /* AIFADC_TDM */
+#define WM8990_AIFADC_TDM_CHAN                  0x1000  /* AIFADC_TDM_CHAN */
+#define WM8990_AIF_BCLK_INV                     0x0100  /* AIF_BCLK_INV */
+#define WM8990_AIF_LRCLK_INV                    0x0080  /* AIF_LRCLK_INV */
+#define WM8990_AIF_WL_MASK                      0x0060  /* AIF_WL - [6:5] */
+#define WM8990_AIF_WL_16BITS			(0 << 5)
+#define WM8990_AIF_WL_20BITS			(1 << 5)
+#define WM8990_AIF_WL_24BITS			(2 << 5)
+#define WM8990_AIF_WL_32BITS			(3 << 5)
+#define WM8990_AIF_FMT_MASK                     0x0018  /* AIF_FMT - [4:3] */
+#define WM8990_AIF_TMF_RIGHTJ			(0 << 3)
+#define WM8990_AIF_TMF_LEFTJ			(1 << 3)
+#define WM8990_AIF_TMF_I2S			(2 << 3)
+#define WM8990_AIF_TMF_DSP			(3 << 3)
+
+/*
+ * R5 (0x05) - Audio Interface (2)
+ */
+#define WM8990_DACL_SRC                         0x8000  /* DACL_SRC */
+#define WM8990_DACR_SRC                         0x4000  /* DACR_SRC */
+#define WM8990_AIFDAC_TDM                       0x2000  /* AIFDAC_TDM */
+#define WM8990_AIFDAC_TDM_CHAN                  0x1000  /* AIFDAC_TDM_CHAN */
+#define WM8990_DAC_BOOST_MASK                   0x0C00  /* DAC_BOOST */
+#define WM8990_DAC_COMP                         0x0010  /* DAC_COMP */
+#define WM8990_DAC_COMPMODE                     0x0008  /* DAC_COMPMODE */
+#define WM8990_ADC_COMP                         0x0004  /* ADC_COMP */
+#define WM8990_ADC_COMPMODE                     0x0002  /* ADC_COMPMODE */
+#define WM8990_LOOPBACK                         0x0001  /* LOOPBACK */
+
+/*
+ * R6 (0x06) - Clocking (1)
+ */
+#define WM8990_TOCLK_RATE                       0x8000  /* TOCLK_RATE */
+#define WM8990_TOCLK_ENA                        0x4000  /* TOCLK_ENA */
+#define WM8990_OPCLKDIV_MASK                    0x1E00  /* OPCLKDIV - [12:9] */
+#define WM8990_DCLKDIV_MASK                     0x01C0  /* DCLKDIV - [8:6] */
+#define WM8990_BCLK_DIV_MASK                    0x001E  /* BCLK_DIV - [4:1] */
+#define WM8990_BCLK_DIV_1			(0x0 << 1)
+#define WM8990_BCLK_DIV_1_5			(0x1 << 1)
+#define WM8990_BCLK_DIV_2			(0x2 << 1)
+#define WM8990_BCLK_DIV_3			(0x3 << 1)
+#define WM8990_BCLK_DIV_4			(0x4 << 1)
+#define WM8990_BCLK_DIV_5_5			(0x5 << 1)
+#define WM8990_BCLK_DIV_6			(0x6 << 1)
+#define WM8990_BCLK_DIV_8			(0x7 << 1)
+#define WM8990_BCLK_DIV_11			(0x8 << 1)
+#define WM8990_BCLK_DIV_12			(0x9 << 1)
+#define WM8990_BCLK_DIV_16			(0xA << 1)
+#define WM8990_BCLK_DIV_22			(0xB << 1)
+#define WM8990_BCLK_DIV_24			(0xC << 1)
+#define WM8990_BCLK_DIV_32			(0xD << 1)
+#define WM8990_BCLK_DIV_44			(0xE << 1)
+#define WM8990_BCLK_DIV_48			(0xF << 1)
+
+/*
+ * R7 (0x07) - Clocking (2)
+ */
+#define WM8990_MCLK_SRC                         0x8000  /* MCLK_SRC */
+#define WM8990_SYSCLK_SRC                       0x4000  /* SYSCLK_SRC */
+#define WM8990_CLK_FORCE                        0x2000  /* CLK_FORCE */
+#define WM8990_MCLK_DIV_MASK                    0x1800  /* MCLK_DIV - [12:11] */
+#define WM8990_MCLK_DIV_1			(0 << 11)
+#define WM8990_MCLK_DIV_2			(2 << 11)
+#define WM8990_MCLK_INV                         0x0400  /* MCLK_INV */
+#define WM8990_ADC_CLKDIV_MASK                  0x00E0  /* ADC_CLKDIV */
+#define WM8990_ADC_CLKDIV_1			(0 << 5)
+#define WM8990_ADC_CLKDIV_1_5			(1 << 5)
+#define WM8990_ADC_CLKDIV_2			(2 << 5)
+#define WM8990_ADC_CLKDIV_3			(3 << 5)
+#define WM8990_ADC_CLKDIV_4			(4 << 5)
+#define WM8990_ADC_CLKDIV_5_5			(5 << 5)
+#define WM8990_ADC_CLKDIV_6			(6 << 5)
+#define WM8990_DAC_CLKDIV_MASK                  0x001C  /* DAC_CLKDIV - [4:2] */
+#define WM8990_DAC_CLKDIV_1			(0 << 2)
+#define WM8990_DAC_CLKDIV_1_5			(1 << 2)
+#define WM8990_DAC_CLKDIV_2			(2 << 2)
+#define WM8990_DAC_CLKDIV_3			(3 << 2)
+#define WM8990_DAC_CLKDIV_4			(4 << 2)
+#define WM8990_DAC_CLKDIV_5_5			(5 << 2)
+#define WM8990_DAC_CLKDIV_6			(6 << 2)
+
+/*
+ * R8 (0x08) - Audio Interface (3)
+ */
+#define WM8990_AIF_MSTR1                        0x8000  /* AIF_MSTR1 */
+#define WM8990_AIF_MSTR2                        0x4000  /* AIF_MSTR2 */
+#define WM8990_AIF_SEL                          0x2000  /* AIF_SEL */
+#define WM8990_ADCLRC_DIR                       0x0800  /* ADCLRC_DIR */
+#define WM8990_ADCLRC_RATE_MASK                 0x07FF  /* ADCLRC_RATE */
+
+/*
+ * R9 (0x09) - Audio Interface (4)
+ */
+#define WM8990_ALRCGPIO1                        0x8000  /* ALRCGPIO1 */
+#define WM8990_ALRCBGPIO6                       0x4000  /* ALRCBGPIO6 */
+#define WM8990_AIF_TRIS                         0x2000  /* AIF_TRIS */
+#define WM8990_DACLRC_DIR                       0x0800  /* DACLRC_DIR */
+#define WM8990_DACLRC_RATE_MASK                 0x07FF  /* DACLRC_RATE */
+
+/*
+ * R10 (0x0A) - DAC CTRL
+ */
+#define WM8990_AIF_LRCLKRATE                    0x0400  /* AIF_LRCLKRATE */
+#define WM8990_DAC_MONO                         0x0200  /* DAC_MONO */
+#define WM8990_DAC_SB_FILT                      0x0100  /* DAC_SB_FILT */
+#define WM8990_DAC_MUTERATE                     0x0080  /* DAC_MUTERATE */
+#define WM8990_DAC_MUTEMODE                     0x0040  /* DAC_MUTEMODE */
+#define WM8990_DEEMP_MASK                       0x0030  /* DEEMP - [5:4] */
+#define WM8990_DAC_MUTE                         0x0004  /* DAC_MUTE */
+#define WM8990_DACL_DATINV                      0x0002  /* DACL_DATINV */
+#define WM8990_DACR_DATINV                      0x0001  /* DACR_DATINV */
+
+/*
+ * R11 (0x0B) - Left DAC Digital Volume
+ */
+#define WM8990_DAC_VU                           0x0100  /* DAC_VU */
+#define WM8990_DACL_VOL_MASK                    0x00FF  /* DACL_VOL - [7:0] */
+#define WM8990_DACL_VOL_SHIFT			0
+/*
+ * R12 (0x0C) - Right DAC Digital Volume
+ */
+#define WM8990_DAC_VU                           0x0100  /* DAC_VU */
+#define WM8990_DACR_VOL_MASK                    0x00FF  /* DACR_VOL - [7:0] */
+#define WM8990_DACR_VOL_SHIFT			0
+/*
+ * R13 (0x0D) - Digital Side Tone
+ */
+#define WM8990_ADCL_DAC_SVOL_MASK               0x0F  /* ADCL_DAC_SVOL */
+#define WM8990_ADCL_DAC_SVOL_SHIFT		9
+#define WM8990_ADCR_DAC_SVOL_MASK               0x0F  /* ADCR_DAC_SVOL */
+#define WM8990_ADCR_DAC_SVOL_SHIFT		5
+#define WM8990_ADC_TO_DACL_MASK                 0x03  /* ADC_TO_DACL - [3:2] */
+#define WM8990_ADC_TO_DACL_SHIFT		2
+#define WM8990_ADC_TO_DACR_MASK                 0x03  /* ADC_TO_DACR - [1:0] */
+#define WM8990_ADC_TO_DACR_SHIFT		0
+
+/*
+ * R14 (0x0E) - ADC CTRL
+ */
+#define WM8990_ADC_HPF_ENA                      0x0100  /* ADC_HPF_ENA */
+#define WM8990_ADC_HPF_ENA_BIT			8
+#define WM8990_ADC_HPF_CUT_MASK                 0x03  /* ADC_HPF_CUT - [6:5] */
+#define WM8990_ADC_HPF_CUT_SHIFT		5
+#define WM8990_ADCL_DATINV                      0x0002  /* ADCL_DATINV */
+#define WM8990_ADCL_DATINV_BIT			1
+#define WM8990_ADCR_DATINV                      0x0001  /* ADCR_DATINV */
+#define WM8990_ADCR_DATINV_BIT			0
+
+/*
+ * R15 (0x0F) - Left ADC Digital Volume
+ */
+#define WM8990_ADC_VU                           0x0100  /* ADC_VU */
+#define WM8990_ADCL_VOL_MASK                    0x00FF  /* ADCL_VOL - [7:0] */
+#define WM8990_ADCL_VOL_SHIFT			0
+
+/*
+ * R16 (0x10) - Right ADC Digital Volume
+ */
+#define WM8990_ADC_VU                           0x0100  /* ADC_VU */
+#define WM8990_ADCR_VOL_MASK                    0x00FF  /* ADCR_VOL - [7:0] */
+#define WM8990_ADCR_VOL_SHIFT			0
+
+/*
+ * R18 (0x12) - GPIO CTRL 1
+ */
+#define WM8990_IRQ                              0x1000  /* IRQ */
+#define WM8990_TEMPOK                           0x0800  /* TEMPOK */
+#define WM8990_MICSHRT                          0x0400  /* MICSHRT */
+#define WM8990_MICDET                           0x0200  /* MICDET */
+#define WM8990_PLL_LCK                          0x0100  /* PLL_LCK */
+#define WM8990_GPI8_STATUS                      0x0080  /* GPI8_STATUS */
+#define WM8990_GPI7_STATUS                      0x0040  /* GPI7_STATUS */
+#define WM8990_GPIO6_STATUS                     0x0020  /* GPIO6_STATUS */
+#define WM8990_GPIO5_STATUS                     0x0010  /* GPIO5_STATUS */
+#define WM8990_GPIO4_STATUS                     0x0008  /* GPIO4_STATUS */
+#define WM8990_GPIO3_STATUS                     0x0004  /* GPIO3_STATUS */
+#define WM8990_GPIO2_STATUS                     0x0002  /* GPIO2_STATUS */
+#define WM8990_GPIO1_STATUS                     0x0001  /* GPIO1_STATUS */
+
+/*
+ * R19 (0x13) - GPIO1 & GPIO2
+ */
+#define WM8990_GPIO2_DEB_ENA                    0x8000  /* GPIO2_DEB_ENA */
+#define WM8990_GPIO2_IRQ_ENA                    0x4000  /* GPIO2_IRQ_ENA */
+#define WM8990_GPIO2_PU                         0x2000  /* GPIO2_PU */
+#define WM8990_GPIO2_PD                         0x1000  /* GPIO2_PD */
+#define WM8990_GPIO2_SEL_MASK                   0x0F00  /* GPIO2_SEL - [11:8] */
+#define WM8990_GPIO1_DEB_ENA                    0x0080  /* GPIO1_DEB_ENA */
+#define WM8990_GPIO1_IRQ_ENA                    0x0040  /* GPIO1_IRQ_ENA */
+#define WM8990_GPIO1_PU                         0x0020  /* GPIO1_PU */
+#define WM8990_GPIO1_PD                         0x0010  /* GPIO1_PD */
+#define WM8990_GPIO1_SEL_MASK                   0x000F  /* GPIO1_SEL - [3:0] */
+
+/*
+ * R20 (0x14) - GPIO3 & GPIO4
+ */
+#define WM8990_GPIO4_DEB_ENA                    0x8000  /* GPIO4_DEB_ENA */
+#define WM8990_GPIO4_IRQ_ENA                    0x4000  /* GPIO4_IRQ_ENA */
+#define WM8990_GPIO4_PU                         0x2000  /* GPIO4_PU */
+#define WM8990_GPIO4_PD                         0x1000  /* GPIO4_PD */
+#define WM8990_GPIO4_SEL_MASK                   0x0F00  /* GPIO4_SEL - [11:8] */
+#define WM8990_GPIO3_DEB_ENA                    0x0080  /* GPIO3_DEB_ENA */
+#define WM8990_GPIO3_IRQ_ENA                    0x0040  /* GPIO3_IRQ_ENA */
+#define WM8990_GPIO3_PU                         0x0020  /* GPIO3_PU */
+#define WM8990_GPIO3_PD                         0x0010  /* GPIO3_PD */
+#define WM8990_GPIO3_SEL_MASK                   0x000F  /* GPIO3_SEL - [3:0] */
+
+/*
+ * R21 (0x15) - GPIO5 & GPIO6
+ */
+#define WM8990_GPIO6_DEB_ENA                    0x8000  /* GPIO6_DEB_ENA */
+#define WM8990_GPIO6_IRQ_ENA                    0x4000  /* GPIO6_IRQ_ENA */
+#define WM8990_GPIO6_PU                         0x2000  /* GPIO6_PU */
+#define WM8990_GPIO6_PD                         0x1000  /* GPIO6_PD */
+#define WM8990_GPIO6_SEL_MASK                   0x0F00  /* GPIO6_SEL - [11:8] */
+#define WM8990_GPIO5_DEB_ENA                    0x0080  /* GPIO5_DEB_ENA */
+#define WM8990_GPIO5_IRQ_ENA                    0x0040  /* GPIO5_IRQ_ENA */
+#define WM8990_GPIO5_PU                         0x0020  /* GPIO5_PU */
+#define WM8990_GPIO5_PD                         0x0010  /* GPIO5_PD */
+#define WM8990_GPIO5_SEL_MASK                   0x000F  /* GPIO5_SEL - [3:0] */
+
+/*
+ * R22 (0x16) - GPIOCTRL 2
+ */
+#define WM8990_RD_3W_ENA                        0x8000  /* RD_3W_ENA */
+#define WM8990_MODE_3W4W                        0x4000  /* MODE_3W4W */
+#define WM8990_TEMPOK_IRQ_ENA                   0x0800  /* TEMPOK_IRQ_ENA */
+#define WM8990_MICSHRT_IRQ_ENA                  0x0400  /* MICSHRT_IRQ_ENA */
+#define WM8990_MICDET_IRQ_ENA                   0x0200  /* MICDET_IRQ_ENA */
+#define WM8990_PLL_LCK_IRQ_ENA                  0x0100  /* PLL_LCK_IRQ_ENA */
+#define WM8990_GPI8_DEB_ENA                     0x0080  /* GPI8_DEB_ENA */
+#define WM8990_GPI8_IRQ_ENA                     0x0040  /* GPI8_IRQ_ENA */
+#define WM8990_GPI8_ENA                         0x0010  /* GPI8_ENA */
+#define WM8990_GPI7_DEB_ENA                     0x0008  /* GPI7_DEB_ENA */
+#define WM8990_GPI7_IRQ_ENA                     0x0004  /* GPI7_IRQ_ENA */
+#define WM8990_GPI7_ENA                         0x0001  /* GPI7_ENA */
+
+/*
+ * R23 (0x17) - GPIO_POL
+ */
+#define WM8990_IRQ_INV                          0x1000  /* IRQ_INV */
+#define WM8990_TEMPOK_POL                       0x0800  /* TEMPOK_POL */
+#define WM8990_MICSHRT_POL                      0x0400  /* MICSHRT_POL */
+#define WM8990_MICDET_POL                       0x0200  /* MICDET_POL */
+#define WM8990_PLL_LCK_POL                      0x0100  /* PLL_LCK_POL */
+#define WM8990_GPI8_POL                         0x0080  /* GPI8_POL */
+#define WM8990_GPI7_POL                         0x0040  /* GPI7_POL */
+#define WM8990_GPIO6_POL                        0x0020  /* GPIO6_POL */
+#define WM8990_GPIO5_POL                        0x0010  /* GPIO5_POL */
+#define WM8990_GPIO4_POL                        0x0008  /* GPIO4_POL */
+#define WM8990_GPIO3_POL                        0x0004  /* GPIO3_POL */
+#define WM8990_GPIO2_POL                        0x0002  /* GPIO2_POL */
+#define WM8990_GPIO1_POL                        0x0001  /* GPIO1_POL */
+
+/*
+ * R24 (0x18) - Left Line Input 1&2 Volume
+ */
+#define WM8990_IPVU                             0x0100  /* IPVU */
+#define WM8990_LI12MUTE                         0x0080  /* LI12MUTE */
+#define WM8990_LI12MUTE_BIT			7
+#define WM8990_LI12ZC                           0x0040  /* LI12ZC */
+#define WM8990_LI12ZC_BIT			6
+#define WM8990_LIN12VOL_MASK                    0x001F  /* LIN12VOL - [4:0] */
+#define WM8990_LIN12VOL_SHIFT			0
+/*
+ * R25 (0x19) - Left Line Input 3&4 Volume
+ */
+#define WM8990_IPVU                             0x0100  /* IPVU */
+#define WM8990_LI34MUTE                         0x0080  /* LI34MUTE */
+#define WM8990_LI34MUTE_BIT			7
+#define WM8990_LI34ZC                           0x0040  /* LI34ZC */
+#define WM8990_LI34ZC_BIT			6
+#define WM8990_LIN34VOL_MASK                    0x001F  /* LIN34VOL - [4:0] */
+#define WM8990_LIN34VOL_SHIFT			0
+
+/*
+ * R26 (0x1A) - Right Line Input 1&2 Volume
+ */
+#define WM8990_IPVU                             0x0100  /* IPVU */
+#define WM8990_RI12MUTE                         0x0080  /* RI12MUTE */
+#define WM8990_RI12MUTE_BIT			7
+#define WM8990_RI12ZC                           0x0040  /* RI12ZC */
+#define WM8990_RI12ZC_BIT			6
+#define WM8990_RIN12VOL_MASK                    0x001F  /* RIN12VOL - [4:0] */
+#define WM8990_RIN12VOL_SHIFT			0
+
+/*
+ * R27 (0x1B) - Right Line Input 3&4 Volume
+ */
+#define WM8990_IPVU                             0x0100  /* IPVU */
+#define WM8990_RI34MUTE                         0x0080  /* RI34MUTE */
+#define WM8990_RI34MUTE_BIT			7
+#define WM8990_RI34ZC                           0x0040  /* RI34ZC */
+#define WM8990_RI34ZC_BIT			6
+#define WM8990_RIN34VOL_MASK                    0x001F  /* RIN34VOL - [4:0] */
+#define WM8990_RIN34VOL_SHIFT			0
+
+/*
+ * R28 (0x1C) - Left Output Volume
+ */
+#define WM8990_OPVU                             0x0100  /* OPVU */
+#define WM8990_LOZC                             0x0080  /* LOZC */
+#define WM8990_LOZC_BIT				7
+#define WM8990_LOUTVOL_MASK                     0x007F  /* LOUTVOL - [6:0] */
+#define WM8990_LOUTVOL_SHIFT			0
+/*
+ * R29 (0x1D) - Right Output Volume
+ */
+#define WM8990_OPVU                             0x0100  /* OPVU */
+#define WM8990_ROZC                             0x0080  /* ROZC */
+#define WM8990_ROZC_BIT				7
+#define WM8990_ROUTVOL_MASK                     0x007F  /* ROUTVOL - [6:0] */
+#define WM8990_ROUTVOL_SHIFT			0
+/*
+ * R30 (0x1E) - Line Outputs Volume
+ */
+#define WM8990_LONMUTE                          0x0040  /* LONMUTE */
+#define WM8990_LONMUTE_BIT			6
+#define WM8990_LOPMUTE                          0x0020  /* LOPMUTE */
+#define WM8990_LOPMUTE_BIT			5
+#define WM8990_LOATTN                           0x0010  /* LOATTN */
+#define WM8990_LOATTN_BIT			4
+#define WM8990_RONMUTE                          0x0004  /* RONMUTE */
+#define WM8990_RONMUTE_BIT			2
+#define WM8990_ROPMUTE                          0x0002  /* ROPMUTE */
+#define WM8990_ROPMUTE_BIT			1
+#define WM8990_ROATTN                           0x0001  /* ROATTN */
+#define WM8990_ROATTN_BIT			0
+
+/*
+ * R31 (0x1F) - Out3/4 Volume
+ */
+#define WM8990_OUT3MUTE                         0x0020  /* OUT3MUTE */
+#define WM8990_OUT3MUTE_BIT			5
+#define WM8990_OUT3ATTN                         0x0010  /* OUT3ATTN */
+#define WM8990_OUT3ATTN_BIT			4
+#define WM8990_OUT4MUTE                         0x0002  /* OUT4MUTE */
+#define WM8990_OUT4MUTE_BIT			1
+#define WM8990_OUT4ATTN                         0x0001  /* OUT4ATTN */
+#define WM8990_OUT4ATTN_BIT			0
+
+/*
+ * R32 (0x20) - Left OPGA Volume
+ */
+#define WM8990_OPVU                             0x0100  /* OPVU */
+#define WM8990_LOPGAZC                          0x0080  /* LOPGAZC */
+#define WM8990_LOPGAZC_BIT			7
+#define WM8990_LOPGAVOL_MASK                    0x007F  /* LOPGAVOL - [6:0] */
+#define WM8990_LOPGAVOL_SHIFT			0
+
+/*
+ * R33 (0x21) - Right OPGA Volume
+ */
+#define WM8990_OPVU                             0x0100  /* OPVU */
+#define WM8990_ROPGAZC                          0x0080  /* ROPGAZC */
+#define WM8990_ROPGAZC_BIT			7
+#define WM8990_ROPGAVOL_MASK                    0x007F  /* ROPGAVOL - [6:0] */
+#define WM8990_ROPGAVOL_SHIFT			0
+/*
+ * R34 (0x22) - Speaker Volume
+ */
+#define WM8990_SPKATTN_MASK                      0x0003  /* SPKATTN - [1:0] */
+#define WM8990_SPKATTN_SHIFT			 0
+
+/*
+ * R35 (0x23) - ClassD1
+ */
+#define WM8990_CDMODE                           0x0100  /* CDMODE */
+#define WM8990_CDMODE_BIT			8
+
+/*
+ * R37 (0x25) - ClassD3
+ */
+#define WM8990_DCGAIN_MASK                      0x0007  /* DCGAIN - [5:3] */
+#define WM8990_DCGAIN_SHIFT			3
+#define WM8990_ACGAIN_MASK                      0x0007  /* ACGAIN - [2:0] */
+#define WM8990_ACGAIN_SHIFT			0
+
+/*
+ * R38 (0x26) - ClassD4
+ */
+#define WM8990_SPKZC_MASK                       0x0001  /* SPKZC */
+#define WM8990_SPKZC_SHIFT                           7  /* SPKZC */
+#define WM8990_SPKVOL_MASK                      0x007F  /* SPKVOL - [6:0] */
+#define WM8990_SPKVOL_SHIFT                          0  /* SPKVOL - [6:0] */
+
+/*
+ * R39 (0x27) - Input Mixer1
+ */
+#define WM8990_AINLMODE_MASK                    0x000C  /* AINLMODE - [3:2] */
+#define WM8990_AINLMODE_SHIFT			2
+#define WM8990_AINRMODE_MASK                    0x0003  /* AINRMODE - [1:0] */
+#define WM8990_AINRMODE_SHIFT			0
+
+/*
+ * R40 (0x28) - Input Mixer2
+ */
+#define WM8990_LMP4				0x0080	/* LMP4 */
+#define WM8990_LMP4_BIT                         7	/* LMP4 */
+#define WM8990_LMN3                             0x0040  /* LMN3 */
+#define WM8990_LMN3_BIT                         6       /* LMN3 */
+#define WM8990_LMP2                             0x0020  /* LMP2 */
+#define WM8990_LMP2_BIT                         5       /* LMP2 */
+#define WM8990_LMN1                             0x0010  /* LMN1 */
+#define WM8990_LMN1_BIT                         4       /* LMN1 */
+#define WM8990_RMP4                             0x0008  /* RMP4 */
+#define WM8990_RMP4_BIT                         3       /* RMP4 */
+#define WM8990_RMN3                             0x0004  /* RMN3 */
+#define WM8990_RMN3_BIT                         2       /* RMN3 */
+#define WM8990_RMP2                             0x0002  /* RMP2 */
+#define WM8990_RMP2_BIT                         1       /* RMP2 */
+#define WM8990_RMN1                             0x0001  /* RMN1 */
+#define WM8990_RMN1_BIT                         0       /* RMN1 */
+
+/*
+ * R41 (0x29) - Input Mixer3
+ */
+#define WM8990_L34MNB                           0x0100  /* L34MNB */
+#define WM8990_L34MNB_BIT			8
+#define WM8990_L34MNBST                         0x0080  /* L34MNBST */
+#define WM8990_L34MNBST_BIT			7
+#define WM8990_L12MNB                           0x0020  /* L12MNB */
+#define WM8990_L12MNB_BIT			5
+#define WM8990_L12MNBST                         0x0010  /* L12MNBST */
+#define WM8990_L12MNBST_BIT			4
+#define WM8990_LDBVOL_MASK                      0x0007  /* LDBVOL - [2:0] */
+#define WM8990_LDBVOL_SHIFT			0
+
+/*
+ * R42 (0x2A) - Input Mixer4
+ */
+#define WM8990_R34MNB                           0x0100  /* R34MNB */
+#define WM8990_R34MNB_BIT			8
+#define WM8990_R34MNBST                         0x0080  /* R34MNBST */
+#define WM8990_R34MNBST_BIT			7
+#define WM8990_R12MNB                           0x0020  /* R12MNB */
+#define WM8990_R12MNB_BIT			5
+#define WM8990_R12MNBST                         0x0010  /* R12MNBST */
+#define WM8990_R12MNBST_BIT			4
+#define WM8990_RDBVOL_MASK                      0x0007  /* RDBVOL - [2:0] */
+#define WM8990_RDBVOL_SHIFT			0
+
+/*
+ * R43 (0x2B) - Input Mixer5
+ */
+#define WM8990_LI2BVOL_MASK                     0x07  /* LI2BVOL - [8:6] */
+#define WM8990_LI2BVOL_SHIFT			6
+#define WM8990_LR4BVOL_MASK                     0x07  /* LR4BVOL - [5:3] */
+#define WM8990_LR4BVOL_SHIFT			3
+#define WM8990_LL4BVOL_MASK                     0x07  /* LL4BVOL - [2:0] */
+#define WM8990_LL4BVOL_SHIFT			0
+
+/*
+ * R44 (0x2C) - Input Mixer6
+ */
+#define WM8990_RI2BVOL_MASK                     0x07  /* RI2BVOL - [8:6] */
+#define WM8990_RI2BVOL_SHIFT			6
+#define WM8990_RL4BVOL_MASK                     0x07  /* RL4BVOL - [5:3] */
+#define WM8990_RL4BVOL_SHIFT			3
+#define WM8990_RR4BVOL_MASK                     0x07  /* RR4BVOL - [2:0] */
+#define WM8990_RR4BVOL_SHIFT			0
+
+/*
+ * R45 (0x2D) - Output Mixer1
+ */
+#define WM8990_LRBLO                            0x0080  /* LRBLO */
+#define WM8990_LRBLO_BIT			7
+#define WM8990_LLBLO                            0x0040  /* LLBLO */
+#define WM8990_LLBLO_BIT			6
+#define WM8990_LRI3LO                           0x0020  /* LRI3LO */
+#define WM8990_LRI3LO_BIT			5
+#define WM8990_LLI3LO                           0x0010  /* LLI3LO */
+#define WM8990_LLI3LO_BIT			4
+#define WM8990_LR12LO                           0x0008  /* LR12LO */
+#define WM8990_LR12LO_BIT			3
+#define WM8990_LL12LO                           0x0004  /* LL12LO */
+#define WM8990_LL12LO_BIT			2
+#define WM8990_LDLO                             0x0001  /* LDLO */
+#define WM8990_LDLO_BIT				0
+
+/*
+ * R46 (0x2E) - Output Mixer2
+ */
+#define WM8990_RLBRO                            0x0080  /* RLBRO */
+#define WM8990_RLBRO_BIT			7
+#define WM8990_RRBRO                            0x0040  /* RRBRO */
+#define WM8990_RRBRO_BIT			6
+#define WM8990_RLI3RO                           0x0020  /* RLI3RO */
+#define WM8990_RLI3RO_BIT			5
+#define WM8990_RRI3RO                           0x0010  /* RRI3RO */
+#define WM8990_RRI3RO_BIT			4
+#define WM8990_RL12RO                           0x0008  /* RL12RO */
+#define WM8990_RL12RO_BIT			3
+#define WM8990_RR12RO                           0x0004  /* RR12RO */
+#define WM8990_RR12RO_BIT			2
+#define WM8990_RDRO                             0x0001  /* RDRO */
+#define WM8990_RDRO_BIT				0
+
+/*
+ * R47 (0x2F) - Output Mixer3
+ */
+#define WM8990_LLI3LOVOL_MASK                   0x07  /* LLI3LOVOL - [8:6] */
+#define WM8990_LLI3LOVOL_SHIFT			6
+#define WM8990_LR12LOVOL_MASK                   0x07  /* LR12LOVOL - [5:3] */
+#define WM8990_LR12LOVOL_SHIFT			3
+#define WM8990_LL12LOVOL_MASK                   0x07  /* LL12LOVOL - [2:0] */
+#define WM8990_LL12LOVOL_SHIFT			0
+
+/*
+ * R48 (0x30) - Output Mixer4
+ */
+#define WM8990_RRI3ROVOL_MASK                   0x07  /* RRI3ROVOL - [8:6] */
+#define WM8990_RRI3ROVOL_SHIFT			6
+#define WM8990_RL12ROVOL_MASK                   0x07  /* RL12ROVOL - [5:3] */
+#define WM8990_RL12ROVOL_SHIFT			3
+#define WM8990_RR12ROVOL_MASK                   0x07  /* RR12ROVOL - [2:0] */
+#define WM8990_RR12ROVOL_SHIFT			0
+
+/*
+ * R49 (0x31) - Output Mixer5
+ */
+#define WM8990_LRI3LOVOL_MASK                   0x07  /* LRI3LOVOL - [8:6] */
+#define WM8990_LRI3LOVOL_SHIFT			6
+#define WM8990_LRBLOVOL_MASK                    0x07  /* LRBLOVOL - [5:3] */
+#define WM8990_LRBLOVOL_SHIFT			3
+#define WM8990_LLBLOVOL_MASK                    0x07  /* LLBLOVOL - [2:0] */
+#define WM8990_LLBLOVOL_SHIFT			0
+
+/*
+ * R50 (0x32) - Output Mixer6
+ */
+#define WM8990_RLI3ROVOL_MASK                   0x07  /* RLI3ROVOL - [8:6] */
+#define WM8990_RLI3ROVOL_SHIFT			6
+#define WM8990_RLBROVOL_MASK                    0x07  /* RLBROVOL - [5:3] */
+#define WM8990_RLBROVOL_SHIFT			3
+#define WM8990_RRBROVOL_MASK                    0x07  /* RRBROVOL - [2:0] */
+#define WM8990_RRBROVOL_SHIFT			0
+
+/*
+ * R51 (0x33) - Out3/4 Mixer
+ */
+#define WM8990_VSEL_MASK                        0x0180  /* VSEL - [8:7] */
+#define WM8990_LI4O3                            0x0020  /* LI4O3 */
+#define WM8990_LI4O3_BIT			5
+#define WM8990_LPGAO3                           0x0010  /* LPGAO3 */
+#define WM8990_LPGAO3_BIT			4
+#define WM8990_RI4O4                            0x0002  /* RI4O4 */
+#define WM8990_RI4O4_BIT			1
+#define WM8990_RPGAO4                           0x0001  /* RPGAO4 */
+#define WM8990_RPGAO4_BIT			0
+/*
+ * R52 (0x34) - Line Mixer1
+ */
+#define WM8990_LLOPGALON                        0x0040  /* LLOPGALON */
+#define WM8990_LLOPGALON_BIT			6
+#define WM8990_LROPGALON                        0x0020  /* LROPGALON */
+#define WM8990_LROPGALON_BIT			5
+#define WM8990_LOPLON                           0x0010  /* LOPLON */
+#define WM8990_LOPLON_BIT			4
+#define WM8990_LR12LOP                          0x0004  /* LR12LOP */
+#define WM8990_LR12LOP_BIT			2
+#define WM8990_LL12LOP                          0x0002  /* LL12LOP */
+#define WM8990_LL12LOP_BIT			1
+#define WM8990_LLOPGALOP                        0x0001  /* LLOPGALOP */
+#define WM8990_LLOPGALOP_BIT			0
+/*
+ * R53 (0x35) - Line Mixer2
+ */
+#define WM8990_RROPGARON                        0x0040  /* RROPGARON */
+#define WM8990_RROPGARON_BIT			6
+#define WM8990_RLOPGARON                        0x0020  /* RLOPGARON */
+#define WM8990_RLOPGARON_BIT			5
+#define WM8990_ROPRON                           0x0010  /* ROPRON */
+#define WM8990_ROPRON_BIT			4
+#define WM8990_RL12ROP                          0x0004  /* RL12ROP */
+#define WM8990_RL12ROP_BIT			2
+#define WM8990_RR12ROP                          0x0002  /* RR12ROP */
+#define WM8990_RR12ROP_BIT			1
+#define WM8990_RROPGAROP                        0x0001  /* RROPGAROP */
+#define WM8990_RROPGAROP_BIT			0
+
+/*
+ * R54 (0x36) - Speaker Mixer
+ */
+#define WM8990_LB2SPK                           0x0080  /* LB2SPK */
+#define WM8990_LB2SPK_BIT			7
+#define WM8990_RB2SPK                           0x0040  /* RB2SPK */
+#define WM8990_RB2SPK_BIT			6
+#define WM8990_LI2SPK                           0x0020  /* LI2SPK */
+#define WM8990_LI2SPK_BIT			5
+#define WM8990_RI2SPK                           0x0010  /* RI2SPK */
+#define WM8990_RI2SPK_BIT			4
+#define WM8990_LOPGASPK                         0x0008  /* LOPGASPK */
+#define WM8990_LOPGASPK_BIT			3
+#define WM8990_ROPGASPK                         0x0004  /* ROPGASPK */
+#define WM8990_ROPGASPK_BIT			2
+#define WM8990_LDSPK                            0x0002  /* LDSPK */
+#define WM8990_LDSPK_BIT			1
+#define WM8990_RDSPK                            0x0001  /* RDSPK */
+#define WM8990_RDSPK_BIT			0
+
+/*
+ * R55 (0x37) - Additional Control
+ */
+#define WM8990_VROI                             0x0001  /* VROI */
+
+/*
+ * R56 (0x38) - AntiPOP1
+ */
+#define WM8990_DIS_LLINE                        0x0020  /* DIS_LLINE */
+#define WM8990_DIS_RLINE                        0x0010  /* DIS_RLINE */
+#define WM8990_DIS_OUT3                         0x0008  /* DIS_OUT3 */
+#define WM8990_DIS_OUT4                         0x0004  /* DIS_OUT4 */
+#define WM8990_DIS_LOUT                         0x0002  /* DIS_LOUT */
+#define WM8990_DIS_ROUT                         0x0001  /* DIS_ROUT */
+
+/*
+ * R57 (0x39) - AntiPOP2
+ */
+#define WM8990_SOFTST                           0x0040  /* SOFTST */
+#define WM8990_BUFIOEN                          0x0008  /* BUFIOEN */
+#define WM8990_BUFDCOPEN                        0x0004  /* BUFDCOPEN */
+#define WM8990_POBCTRL                          0x0002  /* POBCTRL */
+#define WM8990_VMIDTOG                          0x0001  /* VMIDTOG */
+
+/*
+ * R58 (0x3A) - MICBIAS
+ */
+#define WM8990_MCDSCTH_MASK                     0x00C0  /* MCDSCTH - [7:6] */
+#define WM8990_MCDTHR_MASK                      0x0038  /* MCDTHR - [5:3] */
+#define WM8990_MCD                              0x0004  /* MCD */
+#define WM8990_MBSEL                            0x0001  /* MBSEL */
+
+/*
+ * R60 (0x3C) - PLL1
+ */
+#define WM8990_SDM                              0x0080  /* SDM */
+#define WM8990_PRESCALE                         0x0040  /* PRESCALE */
+#define WM8990_PLLN_MASK                        0x000F  /* PLLN - [3:0] */
+
+/*
+ * R61 (0x3D) - PLL2
+ */
+#define WM8990_PLLK1_MASK                       0x00FF  /* PLLK1 - [7:0] */
+
+/*
+ * R62 (0x3E) - PLL3
+ */
+#define WM8990_PLLK2_MASK                       0x00FF  /* PLLK2 - [7:0] */
+
+/*
+ * R63 (0x3F) - Internal Driver Bits
+ */
+#define WM8990_INMIXL_PWR_BIT			0
+#define WM8990_AINLMUX_PWR_BIT			1
+#define WM8990_INMIXR_PWR_BIT			2
+#define WM8990_AINRMUX_PWR_BIT			3
+
+#define WM8990_MCLK_DIV 0
+#define WM8990_DACCLK_DIV 1
+#define WM8990_ADCCLK_DIV 2
+#define WM8990_BCLK_DIV 3
+
+#endif	/* __WM8990REGISTERDEFS_H__ */
+/*------------------------------ END OF FILE ---------------------------------*/
diff --git a/linux/sound/soc/codecs/wm8993.c b/linux/sound/soc/codecs/wm8993.c
new file mode 100644
index 0000000..589e3fa
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8993.c
@@ -0,0 +1,1664 @@
+/*
+ * wm8993.c -- WM8993 ALSA SoC audio driver
+ *
+ * Copyright 2009, 2010 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/wm8993.h>
+
+#include "wm8993.h"
+#include "wm_hubs.h"
+
+#define WM8993_NUM_SUPPLIES 6
+static const char *wm8993_supply_names[WM8993_NUM_SUPPLIES] = {
+	"DCVDD",
+	"DBVDD",
+	"AVDD1",
+	"AVDD2",
+	"CPVDD",
+	"SPKVDD",
+};
+
+static u16 wm8993_reg_defaults[WM8993_REGISTER_COUNT] = {
+	0x8993,     /* R0   - Software Reset */
+	0x0000,     /* R1   - Power Management (1) */
+	0x6000,     /* R2   - Power Management (2) */
+	0x0000,     /* R3   - Power Management (3) */
+	0x4050,     /* R4   - Audio Interface (1) */
+	0x4000,     /* R5   - Audio Interface (2) */
+	0x01C8,     /* R6   - Clocking 1 */
+	0x0000,     /* R7   - Clocking 2 */
+	0x0000,     /* R8   - Audio Interface (3) */
+	0x0040,     /* R9   - Audio Interface (4) */
+	0x0004,     /* R10  - DAC CTRL */
+	0x00C0,     /* R11  - Left DAC Digital Volume */
+	0x00C0,     /* R12  - Right DAC Digital Volume */
+	0x0000,     /* R13  - Digital Side Tone */
+	0x0300,     /* R14  - ADC CTRL */
+	0x00C0,     /* R15  - Left ADC Digital Volume */
+	0x00C0,     /* R16  - Right ADC Digital Volume */
+	0x0000,     /* R17 */
+	0x0000,     /* R18  - GPIO CTRL 1 */
+	0x0010,     /* R19  - GPIO1 */
+	0x0000,     /* R20  - IRQ_DEBOUNCE */
+	0x0000,     /* R21 */
+	0x8000,     /* R22  - GPIOCTRL 2 */
+	0x0800,     /* R23  - GPIO_POL */
+	0x008B,     /* R24  - Left Line Input 1&2 Volume */
+	0x008B,     /* R25  - Left Line Input 3&4 Volume */
+	0x008B,     /* R26  - Right Line Input 1&2 Volume */
+	0x008B,     /* R27  - Right Line Input 3&4 Volume */
+	0x006D,     /* R28  - Left Output Volume */
+	0x006D,     /* R29  - Right Output Volume */
+	0x0066,     /* R30  - Line Outputs Volume */
+	0x0020,     /* R31  - HPOUT2 Volume */
+	0x0079,     /* R32  - Left OPGA Volume */
+	0x0079,     /* R33  - Right OPGA Volume */
+	0x0003,     /* R34  - SPKMIXL Attenuation */
+	0x0003,     /* R35  - SPKMIXR Attenuation */
+	0x0011,     /* R36  - SPKOUT Mixers */
+	0x0100,     /* R37  - SPKOUT Boost */
+	0x0079,     /* R38  - Speaker Volume Left */
+	0x0079,     /* R39  - Speaker Volume Right */
+	0x0000,     /* R40  - Input Mixer2 */
+	0x0000,     /* R41  - Input Mixer3 */
+	0x0000,     /* R42  - Input Mixer4 */
+	0x0000,     /* R43  - Input Mixer5 */
+	0x0000,     /* R44  - Input Mixer6 */
+	0x0000,     /* R45  - Output Mixer1 */
+	0x0000,     /* R46  - Output Mixer2 */
+	0x0000,     /* R47  - Output Mixer3 */
+	0x0000,     /* R48  - Output Mixer4 */
+	0x0000,     /* R49  - Output Mixer5 */
+	0x0000,     /* R50  - Output Mixer6 */
+	0x0000,     /* R51  - HPOUT2 Mixer */
+	0x0000,     /* R52  - Line Mixer1 */
+	0x0000,     /* R53  - Line Mixer2 */
+	0x0000,     /* R54  - Speaker Mixer */
+	0x0000,     /* R55  - Additional Control */
+	0x0000,     /* R56  - AntiPOP1 */
+	0x0000,     /* R57  - AntiPOP2 */
+	0x0000,     /* R58  - MICBIAS */
+	0x0000,     /* R59 */
+	0x0000,     /* R60  - FLL Control 1 */
+	0x0000,     /* R61  - FLL Control 2 */
+	0x0000,     /* R62  - FLL Control 3 */
+	0x2EE0,     /* R63  - FLL Control 4 */
+	0x0002,     /* R64  - FLL Control 5 */
+	0x2287,     /* R65  - Clocking 3 */
+	0x025F,     /* R66  - Clocking 4 */
+	0x0000,     /* R67  - MW Slave Control */
+	0x0000,     /* R68 */
+	0x0002,     /* R69  - Bus Control 1 */
+	0x0000,     /* R70  - Write Sequencer 0 */
+	0x0000,     /* R71  - Write Sequencer 1 */
+	0x0000,     /* R72  - Write Sequencer 2 */
+	0x0000,     /* R73  - Write Sequencer 3 */
+	0x0000,     /* R74  - Write Sequencer 4 */
+	0x0000,     /* R75  - Write Sequencer 5 */
+	0x1F25,     /* R76  - Charge Pump 1 */
+	0x0000,     /* R77 */
+	0x0000,     /* R78 */
+	0x0000,     /* R79 */
+	0x0000,     /* R80 */
+	0x0000,     /* R81  - Class W 0 */
+	0x0000,     /* R82 */
+	0x0000,     /* R83 */
+	0x0000,     /* R84  - DC Servo 0 */
+	0x054A,     /* R85  - DC Servo 1 */
+	0x0000,     /* R86 */
+	0x0000,     /* R87  - DC Servo 3 */
+	0x0000,     /* R88  - DC Servo Readback 0 */
+	0x0000,     /* R89  - DC Servo Readback 1 */
+	0x0000,     /* R90  - DC Servo Readback 2 */
+	0x0000,     /* R91 */
+	0x0000,     /* R92 */
+	0x0000,     /* R93 */
+	0x0000,     /* R94 */
+	0x0000,     /* R95 */
+	0x0100,     /* R96  - Analogue HP 0 */
+	0x0000,     /* R97 */
+	0x0000,     /* R98  - EQ1 */
+	0x000C,     /* R99  - EQ2 */
+	0x000C,     /* R100 - EQ3 */
+	0x000C,     /* R101 - EQ4 */
+	0x000C,     /* R102 - EQ5 */
+	0x000C,     /* R103 - EQ6 */
+	0x0FCA,     /* R104 - EQ7 */
+	0x0400,     /* R105 - EQ8 */
+	0x00D8,     /* R106 - EQ9 */
+	0x1EB5,     /* R107 - EQ10 */
+	0xF145,     /* R108 - EQ11 */
+	0x0B75,     /* R109 - EQ12 */
+	0x01C5,     /* R110 - EQ13 */
+	0x1C58,     /* R111 - EQ14 */
+	0xF373,     /* R112 - EQ15 */
+	0x0A54,     /* R113 - EQ16 */
+	0x0558,     /* R114 - EQ17 */
+	0x168E,     /* R115 - EQ18 */
+	0xF829,     /* R116 - EQ19 */
+	0x07AD,     /* R117 - EQ20 */
+	0x1103,     /* R118 - EQ21 */
+	0x0564,     /* R119 - EQ22 */
+	0x0559,     /* R120 - EQ23 */
+	0x4000,     /* R121 - EQ24 */
+	0x0000,     /* R122 - Digital Pulls */
+	0x0F08,     /* R123 - DRC Control 1 */
+	0x0000,     /* R124 - DRC Control 2 */
+	0x0080,     /* R125 - DRC Control 3 */
+	0x0000,     /* R126 - DRC Control 4 */
+};
+
+static struct {
+	int ratio;
+	int clk_sys_rate;
+} clk_sys_rates[] = {
+	{ 64,   0 },
+	{ 128,  1 },
+	{ 192,  2 },
+	{ 256,  3 },
+	{ 384,  4 },
+	{ 512,  5 },
+	{ 768,  6 },
+	{ 1024, 7 },
+	{ 1408, 8 },
+	{ 1536, 9 },
+};
+
+static struct {
+	int rate;
+	int sample_rate;
+} sample_rates[] = {
+	{ 8000,  0  },
+	{ 11025, 1  },
+	{ 12000, 1  },
+	{ 16000, 2  },
+	{ 22050, 3  },
+	{ 24000, 3  },
+	{ 32000, 4  },
+	{ 44100, 5  },
+	{ 48000, 5  },
+};
+
+static struct {
+	int div; /* *10 due to .5s */
+	int bclk_div;
+} bclk_divs[] = {
+	{ 10,  0  },
+	{ 15,  1  },
+	{ 20,  2  },
+	{ 30,  3  },
+	{ 40,  4  },
+	{ 55,  5  },
+	{ 60,  6  },
+	{ 80,  7  },
+	{ 110, 8  },
+	{ 120, 9  },
+	{ 160, 10 },
+	{ 220, 11 },
+	{ 240, 12 },
+	{ 320, 13 },
+	{ 440, 14 },
+	{ 480, 15 },
+};
+
+struct wm8993_priv {
+	struct wm_hubs_data hubs_data;
+	u16 reg_cache[WM8993_REGISTER_COUNT];
+	struct regulator_bulk_data supplies[WM8993_NUM_SUPPLIES];
+	struct wm8993_platform_data pdata;
+	enum snd_soc_control_type control_type;
+	int master;
+	int sysclk_source;
+	int tdm_slots;
+	int tdm_width;
+	unsigned int mclk_rate;
+	unsigned int sysclk_rate;
+	unsigned int fs;
+	unsigned int bclk;
+	int class_w_users;
+	unsigned int fll_fref;
+	unsigned int fll_fout;
+	int fll_src;
+};
+
+static int wm8993_volatile(unsigned int reg)
+{
+	switch (reg) {
+	case WM8993_SOFTWARE_RESET:
+	case WM8993_DC_SERVO_0:
+	case WM8993_DC_SERVO_READBACK_0:
+	case WM8993_DC_SERVO_READBACK_1:
+	case WM8993_DC_SERVO_READBACK_2:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+struct _fll_div {
+	u16 fll_fratio;
+	u16 fll_outdiv;
+	u16 fll_clk_ref_div;
+	u16 n;
+	u16 k;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static struct {
+	unsigned int min;
+	unsigned int max;
+	u16 fll_fratio;
+	int ratio;
+} fll_fratios[] = {
+	{       0,    64000, 4, 16 },
+	{   64000,   128000, 3,  8 },
+	{  128000,   256000, 2,  4 },
+	{  256000,  1000000, 1,  2 },
+	{ 1000000, 13500000, 0,  1 },
+};
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+		       unsigned int Fout)
+{
+	u64 Kpart;
+	unsigned int K, Ndiv, Nmod, target;
+	unsigned int div;
+	int i;
+
+	/* Fref must be <=13.5MHz */
+	div = 1;
+	fll_div->fll_clk_ref_div = 0;
+	while ((Fref / div) > 13500000) {
+		div *= 2;
+		fll_div->fll_clk_ref_div++;
+
+		if (div > 8) {
+			pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
+			       Fref);
+			return -EINVAL;
+		}
+	}
+
+	pr_debug("Fref=%u Fout=%u\n", Fref, Fout);
+
+	/* Apply the division for our remaining calculations */
+	Fref /= div;
+
+	/* Fvco should be 90-100MHz; don't check the upper bound */
+	div = 0;
+	target = Fout * 2;
+	while (target < 90000000) {
+		div++;
+		target *= 2;
+		if (div > 7) {
+			pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
+			       Fout);
+			return -EINVAL;
+		}
+	}
+	fll_div->fll_outdiv = div;
+
+	pr_debug("Fvco=%dHz\n", target);
+
+	/* Find an appropraite FLL_FRATIO and factor it out of the target */
+	for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
+		if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
+			fll_div->fll_fratio = fll_fratios[i].fll_fratio;
+			target /= fll_fratios[i].ratio;
+			break;
+		}
+	}
+	if (i == ARRAY_SIZE(fll_fratios)) {
+		pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
+		return -EINVAL;
+	}
+
+	/* Now, calculate N.K */
+	Ndiv = target / Fref;
+
+	fll_div->n = Ndiv;
+	Nmod = target % Fref;
+	pr_debug("Nmod=%d\n", Nmod);
+
+	/* Calculate fractional part - scale up so we can round. */
+	Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, Fref);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	fll_div->k = K / 10;
+
+	pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n",
+		 fll_div->n, fll_div->k,
+		 fll_div->fll_fratio, fll_div->fll_outdiv,
+		 fll_div->fll_clk_ref_div);
+
+	return 0;
+}
+
+static int _wm8993_set_fll(struct snd_soc_codec *codec, int fll_id, int source,
+			  unsigned int Fref, unsigned int Fout)
+{
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+	u16 reg1, reg4, reg5;
+	struct _fll_div fll_div;
+	int ret;
+
+	/* Any change? */
+	if (Fref == wm8993->fll_fref && Fout == wm8993->fll_fout)
+		return 0;
+
+	/* Disable the FLL */
+	if (Fout == 0) {
+		dev_dbg(codec->dev, "FLL disabled\n");
+		wm8993->fll_fref = 0;
+		wm8993->fll_fout = 0;
+
+		reg1 = snd_soc_read(codec, WM8993_FLL_CONTROL_1);
+		reg1 &= ~WM8993_FLL_ENA;
+		snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1);
+
+		return 0;
+	}
+
+	ret = fll_factors(&fll_div, Fref, Fout);
+	if (ret != 0)
+		return ret;
+
+	reg5 = snd_soc_read(codec, WM8993_FLL_CONTROL_5);
+	reg5 &= ~WM8993_FLL_CLK_SRC_MASK;
+
+	switch (fll_id) {
+	case WM8993_FLL_MCLK:
+		break;
+
+	case WM8993_FLL_LRCLK:
+		reg5 |= 1;
+		break;
+
+	case WM8993_FLL_BCLK:
+		reg5 |= 2;
+		break;
+
+	default:
+		dev_err(codec->dev, "Unknown FLL ID %d\n", fll_id);
+		return -EINVAL;
+	}
+
+	/* Any FLL configuration change requires that the FLL be
+	 * disabled first. */
+	reg1 = snd_soc_read(codec, WM8993_FLL_CONTROL_1);
+	reg1 &= ~WM8993_FLL_ENA;
+	snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1);
+
+	/* Apply the configuration */
+	if (fll_div.k)
+		reg1 |= WM8993_FLL_FRAC_MASK;
+	else
+		reg1 &= ~WM8993_FLL_FRAC_MASK;
+	snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1);
+
+	snd_soc_write(codec, WM8993_FLL_CONTROL_2,
+		      (fll_div.fll_outdiv << WM8993_FLL_OUTDIV_SHIFT) |
+		      (fll_div.fll_fratio << WM8993_FLL_FRATIO_SHIFT));
+	snd_soc_write(codec, WM8993_FLL_CONTROL_3, fll_div.k);
+
+	reg4 = snd_soc_read(codec, WM8993_FLL_CONTROL_4);
+	reg4 &= ~WM8993_FLL_N_MASK;
+	reg4 |= fll_div.n << WM8993_FLL_N_SHIFT;
+	snd_soc_write(codec, WM8993_FLL_CONTROL_4, reg4);
+
+	reg5 &= ~WM8993_FLL_CLK_REF_DIV_MASK;
+	reg5 |= fll_div.fll_clk_ref_div << WM8993_FLL_CLK_REF_DIV_SHIFT;
+	snd_soc_write(codec, WM8993_FLL_CONTROL_5, reg5);
+
+	/* Enable the FLL */
+	snd_soc_write(codec, WM8993_FLL_CONTROL_1, reg1 | WM8993_FLL_ENA);
+
+	dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout);
+
+	wm8993->fll_fref = Fref;
+	wm8993->fll_fout = Fout;
+	wm8993->fll_src = source;
+
+	return 0;
+}
+
+static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source,
+			  unsigned int Fref, unsigned int Fout)
+{
+	return _wm8993_set_fll(dai->codec, fll_id, source, Fref, Fout);
+}
+
+static int configure_clock(struct snd_soc_codec *codec)
+{
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+	unsigned int reg;
+
+	/* This should be done on init() for bypass paths */
+	switch (wm8993->sysclk_source) {
+	case WM8993_SYSCLK_MCLK:
+		dev_dbg(codec->dev, "Using %dHz MCLK\n", wm8993->mclk_rate);
+
+		reg = snd_soc_read(codec, WM8993_CLOCKING_2);
+		reg &= ~(WM8993_MCLK_DIV | WM8993_SYSCLK_SRC);
+		if (wm8993->mclk_rate > 13500000) {
+			reg |= WM8993_MCLK_DIV;
+			wm8993->sysclk_rate = wm8993->mclk_rate / 2;
+		} else {
+			reg &= ~WM8993_MCLK_DIV;
+			wm8993->sysclk_rate = wm8993->mclk_rate;
+		}
+		snd_soc_write(codec, WM8993_CLOCKING_2, reg);
+		break;
+
+	case WM8993_SYSCLK_FLL:
+		dev_dbg(codec->dev, "Using %dHz FLL clock\n",
+			wm8993->fll_fout);
+
+		reg = snd_soc_read(codec, WM8993_CLOCKING_2);
+		reg |= WM8993_SYSCLK_SRC;
+		if (wm8993->fll_fout > 13500000) {
+			reg |= WM8993_MCLK_DIV;
+			wm8993->sysclk_rate = wm8993->fll_fout / 2;
+		} else {
+			reg &= ~WM8993_MCLK_DIV;
+			wm8993->sysclk_rate = wm8993->fll_fout;
+		}
+		snd_soc_write(codec, WM8993_CLOCKING_2, reg);
+		break;
+
+	default:
+		dev_err(codec->dev, "System clock not configured\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm8993->sysclk_rate);
+
+	return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(drc_comp_threash, -4500, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_comp_amp, -2250, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_min_tlv, -1800, 600, 0);
+static const unsigned int drc_max_tlv[] = {
+	TLV_DB_RANGE_HEAD(4),
+	0, 2, TLV_DB_SCALE_ITEM(1200, 600, 0),
+	3, 3, TLV_DB_SCALE_ITEM(3600, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(drc_qr_tlv, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_startup_tlv, -1800, 300, 0);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0);
+
+static const char *dac_deemph_text[] = {
+	"None",
+	"32kHz",
+	"44.1kHz",
+	"48kHz",
+};
+
+static const struct soc_enum dac_deemph =
+	SOC_ENUM_SINGLE(WM8993_DAC_CTRL, 4, 4, dac_deemph_text);
+
+static const char *adc_hpf_text[] = {
+	"Hi-Fi",
+	"Voice 1",
+	"Voice 2",
+	"Voice 3",
+};
+
+static const struct soc_enum adc_hpf =
+	SOC_ENUM_SINGLE(WM8993_ADC_CTRL, 5, 4, adc_hpf_text);
+
+static const char *drc_path_text[] = {
+	"ADC",
+	"DAC"
+};
+
+static const struct soc_enum drc_path =
+	SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 14, 2, drc_path_text);
+
+static const char *drc_r0_text[] = {
+	"1",
+	"1/2",
+	"1/4",
+	"1/8",
+	"1/16",
+	"0",
+};
+
+static const struct soc_enum drc_r0 =
+	SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_3, 8, 6, drc_r0_text);
+
+static const char *drc_r1_text[] = {
+	"1",
+	"1/2",
+	"1/4",
+	"1/8",
+	"0",
+};
+
+static const struct soc_enum drc_r1 =
+	SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_4, 13, 5, drc_r1_text);
+
+static const char *drc_attack_text[] = {
+	"Reserved",
+	"181us",
+	"363us",
+	"726us",
+	"1.45ms",
+	"2.9ms",
+	"5.8ms",
+	"11.6ms",
+	"23.2ms",
+	"46.4ms",
+	"92.8ms",
+	"185.6ms",
+};
+
+static const struct soc_enum drc_attack =
+	SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_2, 12, 12, drc_attack_text);
+
+static const char *drc_decay_text[] = {
+	"186ms",
+	"372ms",
+	"743ms",
+	"1.49s",
+	"2.97ms",
+	"5.94ms",
+	"11.89ms",
+	"23.78ms",
+	"47.56ms",
+};
+
+static const struct soc_enum drc_decay =
+	SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_2, 8, 9, drc_decay_text);
+
+static const char *drc_ff_text[] = {
+	"5 samples",
+	"9 samples",
+};
+
+static const struct soc_enum drc_ff =
+	SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_3, 7, 2, drc_ff_text);
+
+static const char *drc_qr_rate_text[] = {
+	"0.725ms",
+	"1.45ms",
+	"5.8ms",
+};
+
+static const struct soc_enum drc_qr_rate =
+	SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_3, 0, 3, drc_qr_rate_text);
+
+static const char *drc_smooth_text[] = {
+	"Low",
+	"Medium",
+	"High",
+};
+
+static const struct soc_enum drc_smooth =
+	SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 4, 3, drc_smooth_text);
+
+static const struct snd_kcontrol_new wm8993_snd_controls[] = {
+SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8993_DIGITAL_SIDE_TONE,
+	       5, 9, 12, 0, sidetone_tlv),
+
+SOC_SINGLE("DRC Switch", WM8993_DRC_CONTROL_1, 15, 1, 0),
+SOC_ENUM("DRC Path", drc_path),
+SOC_SINGLE_TLV("DRC Compressor Threshold Volume", WM8993_DRC_CONTROL_2,
+	       2, 60, 1, drc_comp_threash),
+SOC_SINGLE_TLV("DRC Compressor Amplitude Volume", WM8993_DRC_CONTROL_3,
+	       11, 30, 1, drc_comp_amp),
+SOC_ENUM("DRC R0", drc_r0),
+SOC_ENUM("DRC R1", drc_r1),
+SOC_SINGLE_TLV("DRC Minimum Volume", WM8993_DRC_CONTROL_1, 2, 3, 1,
+	       drc_min_tlv),
+SOC_SINGLE_TLV("DRC Maximum Volume", WM8993_DRC_CONTROL_1, 0, 3, 0,
+	       drc_max_tlv),
+SOC_ENUM("DRC Attack Rate", drc_attack),
+SOC_ENUM("DRC Decay Rate", drc_decay),
+SOC_ENUM("DRC FF Delay", drc_ff),
+SOC_SINGLE("DRC Anti-clip Switch", WM8993_DRC_CONTROL_1, 9, 1, 0),
+SOC_SINGLE("DRC Quick Release Switch", WM8993_DRC_CONTROL_1, 10, 1, 0),
+SOC_SINGLE_TLV("DRC Quick Release Volume", WM8993_DRC_CONTROL_3, 2, 3, 0,
+	       drc_qr_tlv),
+SOC_ENUM("DRC Quick Release Rate", drc_qr_rate),
+SOC_SINGLE("DRC Smoothing Switch", WM8993_DRC_CONTROL_1, 11, 1, 0),
+SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8993_DRC_CONTROL_1, 8, 1, 0),
+SOC_ENUM("DRC Smoothing Hysteresis Threshold", drc_smooth),
+SOC_SINGLE_TLV("DRC Startup Volume", WM8993_DRC_CONTROL_4, 8, 18, 0,
+	       drc_startup_tlv),
+
+SOC_SINGLE("EQ Switch", WM8993_EQ1, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("Capture Volume", WM8993_LEFT_ADC_DIGITAL_VOLUME,
+		 WM8993_RIGHT_ADC_DIGITAL_VOLUME, 1, 96, 0, digital_tlv),
+SOC_SINGLE("ADC High Pass Filter Switch", WM8993_ADC_CTRL, 8, 1, 0),
+SOC_ENUM("ADC High Pass Filter Mode", adc_hpf),
+
+SOC_DOUBLE_R_TLV("Playback Volume", WM8993_LEFT_DAC_DIGITAL_VOLUME,
+		 WM8993_RIGHT_DAC_DIGITAL_VOLUME, 1, 96, 0, digital_tlv),
+SOC_SINGLE_TLV("Playback Boost Volume", WM8993_AUDIO_INTERFACE_2, 10, 3, 0,
+	       dac_boost_tlv),
+SOC_ENUM("DAC Deemphasis", dac_deemph),
+
+SOC_SINGLE_TLV("SPKL DAC Volume", WM8993_SPKMIXL_ATTENUATION,
+	       2, 1, 1, wm_hubs_spkmix_tlv),
+
+SOC_SINGLE_TLV("SPKR DAC Volume", WM8993_SPKMIXR_ATTENUATION,
+	       2, 1, 1, wm_hubs_spkmix_tlv),
+};
+
+static const struct snd_kcontrol_new wm8993_eq_controls[] = {
+SOC_SINGLE_TLV("EQ1 Volume", WM8993_EQ2, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ2 Volume", WM8993_EQ3, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ3 Volume", WM8993_EQ4, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ4 Volume", WM8993_EQ5, 0, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ5 Volume", WM8993_EQ6, 0, 24, 0, eq_tlv),
+};
+
+static int clk_sys_event(struct snd_soc_dapm_widget *w,
+			 struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		return configure_clock(codec);
+
+	case SND_SOC_DAPM_POST_PMD:
+		break;
+	}
+
+	return 0;
+}
+
+/*
+ * When used with DAC outputs only the WM8993 charge pump supports
+ * operation in class W mode, providing very low power consumption
+ * when used with digital sources.  Enable and disable this mode
+ * automatically depending on the mixer configuration.
+ *
+ * Currently the only supported paths are the direct DAC->headphone
+ * paths (which provide minimum power consumption anyway).
+ */
+static int class_w_put(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_codec *codec = widget->codec;
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	/* Turn it off if we're using the main output mixer */
+	if (ucontrol->value.integer.value[0] == 0) {
+		if (wm8993->class_w_users == 0) {
+			dev_dbg(codec->dev, "Disabling Class W\n");
+			snd_soc_update_bits(codec, WM8993_CLASS_W_0,
+					    WM8993_CP_DYN_FREQ |
+					    WM8993_CP_DYN_V,
+					    0);
+		}
+		wm8993->class_w_users++;
+	}
+
+	/* Implement the change */
+	ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
+
+	/* Enable it if we're using the direct DAC path */
+	if (ucontrol->value.integer.value[0] == 1) {
+		if (wm8993->class_w_users == 1) {
+			dev_dbg(codec->dev, "Enabling Class W\n");
+			snd_soc_update_bits(codec, WM8993_CLASS_W_0,
+					    WM8993_CP_DYN_FREQ |
+					    WM8993_CP_DYN_V,
+					    WM8993_CP_DYN_FREQ |
+					    WM8993_CP_DYN_V);
+		}
+		wm8993->class_w_users--;
+	}
+
+	dev_dbg(codec->dev, "Indirect DAC use count now %d\n",
+		wm8993->class_w_users);
+
+	return ret;
+}
+
+#define SOC_DAPM_ENUM_W(xname, xenum) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = snd_soc_info_enum_double, \
+	.get = snd_soc_dapm_get_enum_double, \
+	.put = class_w_put, \
+	.private_value = (unsigned long)&xenum }
+
+static const char *hp_mux_text[] = {
+	"Mixer",
+	"DAC",
+};
+
+static const struct soc_enum hpl_enum =
+	SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER1, 8, 2, hp_mux_text);
+
+static const struct snd_kcontrol_new hpl_mux =
+	SOC_DAPM_ENUM_W("Left Headphone Mux", hpl_enum);
+
+static const struct soc_enum hpr_enum =
+	SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER2, 8, 2, hp_mux_text);
+
+static const struct snd_kcontrol_new hpr_mux =
+	SOC_DAPM_ENUM_W("Right Headphone Mux", hpr_enum);
+
+static const struct snd_kcontrol_new left_speaker_mixer[] = {
+SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0),
+SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_speaker_mixer[] = {
+SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
+SOC_DAPM_SINGLE("IN1RP Switch", WM8993_SPEAKER_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 0, 1, 0),
+};
+
+static const char *aif_text[] = {
+	"Left", "Right"
+};
+
+static const struct soc_enum aifoutl_enum =
+	SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_1, 15, 2, aif_text);
+
+static const struct snd_kcontrol_new aifoutl_mux =
+	SOC_DAPM_ENUM("AIFOUTL Mux", aifoutl_enum);
+
+static const struct soc_enum aifoutr_enum =
+	SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_1, 14, 2, aif_text);
+
+static const struct snd_kcontrol_new aifoutr_mux =
+	SOC_DAPM_ENUM("AIFOUTR Mux", aifoutr_enum);
+
+static const struct soc_enum aifinl_enum =
+	SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_2, 15, 2, aif_text);
+
+static const struct snd_kcontrol_new aifinl_mux =
+	SOC_DAPM_ENUM("AIFINL Mux", aifinl_enum);
+
+static const struct soc_enum aifinr_enum =
+	SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_2, 14, 2, aif_text);
+
+static const struct snd_kcontrol_new aifinr_mux =
+	SOC_DAPM_ENUM("AIFINR Mux", aifinr_enum);
+
+static const char *sidetone_text[] = {
+	"None", "Left", "Right"
+};
+
+static const struct soc_enum sidetonel_enum =
+	SOC_ENUM_SINGLE(WM8993_DIGITAL_SIDE_TONE, 2, 3, sidetone_text);
+
+static const struct snd_kcontrol_new sidetonel_mux =
+	SOC_DAPM_ENUM("Left Sidetone", sidetonel_enum);
+
+static const struct soc_enum sidetoner_enum =
+	SOC_ENUM_SINGLE(WM8993_DIGITAL_SIDE_TONE, 0, 3, sidetone_text);
+
+static const struct snd_kcontrol_new sidetoner_mux =
+	SOC_DAPM_ENUM("Right Sidetone", sidetoner_enum);
+
+static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8993_BUS_CONTROL_1, 1, 0, clk_sys_event,
+		    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("TOCLK", WM8993_CLOCKING_1, 14, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8993_CLOCKING_3, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_ADC("ADCL", NULL, WM8993_POWER_MANAGEMENT_2, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, WM8993_POWER_MANAGEMENT_2, 0, 0),
+
+SND_SOC_DAPM_MUX("AIFOUTL Mux", SND_SOC_NOPM, 0, 0, &aifoutl_mux),
+SND_SOC_DAPM_MUX("AIFOUTR Mux", SND_SOC_NOPM, 0, 0, &aifoutr_mux),
+
+SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &aifinl_mux),
+SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &aifinr_mux),
+
+SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &sidetonel_mux),
+SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &sidetoner_mux),
+
+SND_SOC_DAPM_DAC("DACL", NULL, WM8993_POWER_MANAGEMENT_3, 1, 0),
+SND_SOC_DAPM_DAC("DACR", NULL, WM8993_POWER_MANAGEMENT_3, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
+SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
+
+SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0,
+		   left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
+SND_SOC_DAPM_MIXER("SPKR", WM8993_POWER_MANAGEMENT_3, 9, 0,
+		   right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
+
+};
+
+static const struct snd_soc_dapm_route routes[] = {
+	{ "ADCL", NULL, "CLK_SYS" },
+	{ "ADCL", NULL, "CLK_DSP" },
+	{ "ADCR", NULL, "CLK_SYS" },
+	{ "ADCR", NULL, "CLK_DSP" },
+
+	{ "AIFOUTL Mux", "Left", "ADCL" },
+	{ "AIFOUTL Mux", "Right", "ADCR" },
+	{ "AIFOUTR Mux", "Left", "ADCL" },
+	{ "AIFOUTR Mux", "Right", "ADCR" },
+
+	{ "AIFOUTL", NULL, "AIFOUTL Mux" },
+	{ "AIFOUTR", NULL, "AIFOUTR Mux" },
+
+	{ "DACL Mux", "Left", "AIFINL" },
+	{ "DACL Mux", "Right", "AIFINR" },
+	{ "DACR Mux", "Left", "AIFINL" },
+	{ "DACR Mux", "Right", "AIFINR" },
+
+	{ "DACL Sidetone", "Left", "ADCL" },
+	{ "DACL Sidetone", "Right", "ADCR" },
+	{ "DACR Sidetone", "Left", "ADCL" },
+	{ "DACR Sidetone", "Right", "ADCR" },
+
+	{ "DACL", NULL, "CLK_SYS" },
+	{ "DACL", NULL, "CLK_DSP" },
+	{ "DACL", NULL, "DACL Mux" },
+	{ "DACL", NULL, "DACL Sidetone" },
+	{ "DACR", NULL, "CLK_SYS" },
+	{ "DACR", NULL, "CLK_DSP" },
+	{ "DACR", NULL, "DACR Mux" },
+	{ "DACR", NULL, "DACR Sidetone" },
+
+	{ "Left Output Mixer", "DAC Switch", "DACL" },
+
+	{ "Right Output Mixer", "DAC Switch", "DACR" },
+
+	{ "Left Output PGA", NULL, "CLK_SYS" },
+
+	{ "Right Output PGA", NULL, "CLK_SYS" },
+
+	{ "SPKL", "DAC Switch", "DACL" },
+	{ "SPKL", NULL, "CLK_SYS" },
+
+	{ "SPKR", "DAC Switch", "DACR" },
+	{ "SPKR", NULL, "CLK_SYS" },
+
+	{ "Left Headphone Mux", "DAC", "DACL" },
+	{ "Right Headphone Mux", "DAC", "DACR" },
+};
+
+static void wm8993_cache_restore(struct snd_soc_codec *codec)
+{
+	u16 *cache = codec->reg_cache;
+	int i;
+
+	if (!codec->cache_sync)
+		return;
+
+	/* Reenable hardware writes */
+	codec->cache_only = 0;
+
+	/* Restore the register settings */
+	for (i = 1; i < WM8993_MAX_REGISTER; i++) {
+		if (cache[i] == wm8993_reg_defaults[i])
+			continue;
+		snd_soc_write(codec, i, cache[i]);
+	}
+
+	/* We're in sync again */
+	codec->cache_sync = 0;
+}
+
+static int wm8993_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+		/* VMID=2*40k */
+		snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+				    WM8993_VMID_SEL_MASK, 0x2);
+		snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_2,
+				    WM8993_TSHUT_ENA, WM8993_TSHUT_ENA);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			ret = regulator_bulk_enable(ARRAY_SIZE(wm8993->supplies),
+						    wm8993->supplies);
+			if (ret != 0)
+				return ret;
+
+			wm8993_cache_restore(codec);
+
+			/* Tune DC servo configuration */
+			snd_soc_write(codec, 0x44, 3);
+			snd_soc_write(codec, 0x56, 3);
+			snd_soc_write(codec, 0x44, 0);
+
+			/* Bring up VMID with fast soft start */
+			snd_soc_update_bits(codec, WM8993_ANTIPOP2,
+					    WM8993_STARTUP_BIAS_ENA |
+					    WM8993_VMID_BUF_ENA |
+					    WM8993_VMID_RAMP_MASK |
+					    WM8993_BIAS_SRC,
+					    WM8993_STARTUP_BIAS_ENA |
+					    WM8993_VMID_BUF_ENA |
+					    WM8993_VMID_RAMP_MASK |
+					    WM8993_BIAS_SRC);
+
+			/* If either line output is single ended we
+			 * need the VMID buffer */
+			if (!wm8993->pdata.lineout1_diff ||
+			    !wm8993->pdata.lineout2_diff)
+				snd_soc_update_bits(codec, WM8993_ANTIPOP1,
+						 WM8993_LINEOUT_VMID_BUF_ENA,
+						 WM8993_LINEOUT_VMID_BUF_ENA);
+
+			/* VMID=2*40k */
+			snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+					    WM8993_VMID_SEL_MASK |
+					    WM8993_BIAS_ENA,
+					    WM8993_BIAS_ENA | 0x2);
+			msleep(32);
+
+			/* Switch to normal bias */
+			snd_soc_update_bits(codec, WM8993_ANTIPOP2,
+					    WM8993_BIAS_SRC |
+					    WM8993_STARTUP_BIAS_ENA, 0);
+		}
+
+		/* VMID=2*240k */
+		snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+				    WM8993_VMID_SEL_MASK, 0x4);
+
+		snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_2,
+				    WM8993_TSHUT_ENA, 0);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		snd_soc_update_bits(codec, WM8993_ANTIPOP1,
+				    WM8993_LINEOUT_VMID_BUF_ENA, 0);
+
+		snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+				    WM8993_VMID_SEL_MASK | WM8993_BIAS_ENA,
+				    0);
+
+#ifdef CONFIG_REGULATOR
+               /* Post 2.6.34 we will be able to get a callback when
+                * the regulators are disabled which we can use but
+		* for now just assume that the power will be cut if
+		* the regulator API is in use.
+		*/
+		codec->cache_sync = 1;
+#endif
+
+		regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies),
+				       wm8993->supplies);
+		break;
+	}
+
+	codec->bias_level = level;
+
+	return 0;
+}
+
+static int wm8993_set_sysclk(struct snd_soc_dai *codec_dai,
+			     int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+
+	switch (clk_id) {
+	case WM8993_SYSCLK_MCLK:
+		wm8993->mclk_rate = freq;
+	case WM8993_SYSCLK_FLL:
+		wm8993->sysclk_source = clk_id;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wm8993_set_dai_fmt(struct snd_soc_dai *dai,
+			      unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+	unsigned int aif1 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_1);
+	unsigned int aif4 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_4);
+
+	aif1 &= ~(WM8993_BCLK_DIR | WM8993_AIF_BCLK_INV |
+		  WM8993_AIF_LRCLK_INV | WM8993_AIF_FMT_MASK);
+	aif4 &= ~WM8993_LRCLK_DIR;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		wm8993->master = 0;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		aif4 |= WM8993_LRCLK_DIR;
+		wm8993->master = 1;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		aif1 |= WM8993_BCLK_DIR;
+		wm8993->master = 1;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		aif1 |= WM8993_BCLK_DIR;
+		aif4 |= WM8993_LRCLK_DIR;
+		wm8993->master = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_B:
+		aif1 |= WM8993_AIF_LRCLK_INV;
+	case SND_SOC_DAIFMT_DSP_A:
+		aif1 |= 0x18;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		aif1 |= 0x10;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		aif1 |= 0x8;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		/* frame inversion not valid for DSP modes */
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif1 |= WM8993_AIF_BCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_LEFT_J:
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			aif1 |= WM8993_AIF_BCLK_INV | WM8993_AIF_LRCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif1 |= WM8993_AIF_BCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			aif1 |= WM8993_AIF_LRCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM8993_AUDIO_INTERFACE_1, aif1);
+	snd_soc_write(codec, WM8993_AUDIO_INTERFACE_4, aif4);
+
+	return 0;
+}
+
+static int wm8993_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+	int ret, i, best, best_val, cur_val;
+	unsigned int clocking1, clocking3, aif1, aif4;
+
+	clocking1 = snd_soc_read(codec, WM8993_CLOCKING_1);
+	clocking1 &= ~WM8993_BCLK_DIV_MASK;
+
+	clocking3 = snd_soc_read(codec, WM8993_CLOCKING_3);
+	clocking3 &= ~(WM8993_CLK_SYS_RATE_MASK | WM8993_SAMPLE_RATE_MASK);
+
+	aif1 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_1);
+	aif1 &= ~WM8993_AIF_WL_MASK;
+
+	aif4 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_4);
+	aif4 &= ~WM8993_LRCLK_RATE_MASK;
+
+	/* What BCLK do we need? */
+	wm8993->fs = params_rate(params);
+	wm8993->bclk = 2 * wm8993->fs;
+	if (wm8993->tdm_slots) {
+		dev_dbg(codec->dev, "Configuring for %d %d bit TDM slots\n",
+			wm8993->tdm_slots, wm8993->tdm_width);
+		wm8993->bclk *= wm8993->tdm_width * wm8993->tdm_slots;
+	} else {
+		switch (params_format(params)) {
+		case SNDRV_PCM_FORMAT_S16_LE:
+			wm8993->bclk *= 16;
+			break;
+		case SNDRV_PCM_FORMAT_S20_3LE:
+			wm8993->bclk *= 20;
+			aif1 |= 0x8;
+			break;
+		case SNDRV_PCM_FORMAT_S24_LE:
+			wm8993->bclk *= 24;
+			aif1 |= 0x10;
+			break;
+		case SNDRV_PCM_FORMAT_S32_LE:
+			wm8993->bclk *= 32;
+			aif1 |= 0x18;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm8993->bclk);
+
+	ret = configure_clock(codec);
+	if (ret != 0)
+		return ret;
+
+	/* Select nearest CLK_SYS_RATE */
+	best = 0;
+	best_val = abs((wm8993->sysclk_rate / clk_sys_rates[0].ratio)
+		       - wm8993->fs);
+	for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
+		cur_val = abs((wm8993->sysclk_rate /
+			       clk_sys_rates[i].ratio) - wm8993->fs);;
+		if (cur_val < best_val) {
+			best = i;
+			best_val = cur_val;
+		}
+	}
+	dev_dbg(codec->dev, "Selected CLK_SYS_RATIO of %d\n",
+		clk_sys_rates[best].ratio);
+	clocking3 |= (clk_sys_rates[best].clk_sys_rate
+		      << WM8993_CLK_SYS_RATE_SHIFT);
+
+	/* SAMPLE_RATE */
+	best = 0;
+	best_val = abs(wm8993->fs - sample_rates[0].rate);
+	for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
+		/* Closest match */
+		cur_val = abs(wm8993->fs - sample_rates[i].rate);
+		if (cur_val < best_val) {
+			best = i;
+			best_val = cur_val;
+		}
+	}
+	dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n",
+		sample_rates[best].rate);
+	clocking3 |= (sample_rates[best].sample_rate
+		      << WM8993_SAMPLE_RATE_SHIFT);
+
+	/* BCLK_DIV */
+	best = 0;
+	best_val = INT_MAX;
+	for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+		cur_val = ((wm8993->sysclk_rate * 10) / bclk_divs[i].div)
+			- wm8993->bclk;
+		if (cur_val < 0) /* Table is sorted */
+			break;
+		if (cur_val < best_val) {
+			best = i;
+			best_val = cur_val;
+		}
+	}
+	wm8993->bclk = (wm8993->sysclk_rate * 10) / bclk_divs[best].div;
+	dev_dbg(codec->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n",
+		bclk_divs[best].div, wm8993->bclk);
+	clocking1 |= bclk_divs[best].bclk_div << WM8993_BCLK_DIV_SHIFT;
+
+	/* LRCLK is a simple fraction of BCLK */
+	dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm8993->bclk / wm8993->fs);
+	aif4 |= wm8993->bclk / wm8993->fs;
+
+	snd_soc_write(codec, WM8993_CLOCKING_1, clocking1);
+	snd_soc_write(codec, WM8993_CLOCKING_3, clocking3);
+	snd_soc_write(codec, WM8993_AUDIO_INTERFACE_1, aif1);
+	snd_soc_write(codec, WM8993_AUDIO_INTERFACE_4, aif4);
+
+	/* ReTune Mobile? */
+	if (wm8993->pdata.num_retune_configs) {
+		u16 eq1 = snd_soc_read(codec, WM8993_EQ1);
+		struct wm8993_retune_mobile_setting *s;
+
+		best = 0;
+		best_val = abs(wm8993->pdata.retune_configs[0].rate
+			       - wm8993->fs);
+		for (i = 0; i < wm8993->pdata.num_retune_configs; i++) {
+			cur_val = abs(wm8993->pdata.retune_configs[i].rate
+				      - wm8993->fs);
+			if (cur_val < best_val) {
+				best_val = cur_val;
+				best = i;
+			}
+		}
+		s = &wm8993->pdata.retune_configs[best];
+
+		dev_dbg(codec->dev, "ReTune Mobile %s tuned for %dHz\n",
+			s->name, s->rate);
+
+		/* Disable EQ while we reconfigure */
+		snd_soc_update_bits(codec, WM8993_EQ1, WM8993_EQ_ENA, 0);
+
+		for (i = 1; i < ARRAY_SIZE(s->config); i++)
+			snd_soc_write(codec, WM8993_EQ1 + i, s->config[i]);
+
+		snd_soc_update_bits(codec, WM8993_EQ1, WM8993_EQ_ENA, eq1);
+	}
+
+	return 0;
+}
+
+static int wm8993_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	unsigned int reg;
+
+	reg = snd_soc_read(codec, WM8993_DAC_CTRL);
+
+	if (mute)
+		reg |= WM8993_DAC_MUTE;
+	else
+		reg &= ~WM8993_DAC_MUTE;
+
+	snd_soc_write(codec, WM8993_DAC_CTRL, reg);
+
+	return 0;
+}
+
+static int wm8993_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+			       unsigned int rx_mask, int slots, int slot_width)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+	int aif1 = 0;
+	int aif2 = 0;
+
+	/* Don't need to validate anything if we're turning off TDM */
+	if (slots == 0) {
+		wm8993->tdm_slots = 0;
+		goto out;
+	}
+
+	/* Note that we allow configurations we can't handle ourselves - 
+	 * for example, we can generate clocks for slots 2 and up even if
+	 * we can't use those slots ourselves.
+	 */
+	aif1 |= WM8993_AIFADC_TDM;
+	aif2 |= WM8993_AIFDAC_TDM;
+
+	switch (rx_mask) {
+	case 3:
+		break;
+	case 0xc:
+		aif1 |= WM8993_AIFADC_TDM_CHAN;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+
+	switch (tx_mask) {
+	case 3:
+		break;
+	case 0xc:
+		aif2 |= WM8993_AIFDAC_TDM_CHAN;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+out:
+	wm8993->tdm_width = slot_width;
+	wm8993->tdm_slots = slots / 2;
+
+	snd_soc_update_bits(codec, WM8993_AUDIO_INTERFACE_1,
+			    WM8993_AIFADC_TDM | WM8993_AIFADC_TDM_CHAN, aif1);
+	snd_soc_update_bits(codec, WM8993_AUDIO_INTERFACE_2,
+			    WM8993_AIFDAC_TDM | WM8993_AIFDAC_TDM_CHAN, aif2);
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops wm8993_ops = {
+	.set_sysclk = wm8993_set_sysclk,
+	.set_fmt = wm8993_set_dai_fmt,
+	.hw_params = wm8993_hw_params,
+	.digital_mute = wm8993_digital_mute,
+	.set_pll = wm8993_set_fll,
+	.set_tdm_slot = wm8993_set_tdm_slot,
+};
+
+#define WM8993_RATES SNDRV_PCM_RATE_8000_48000
+
+#define WM8993_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+			SNDRV_PCM_FMTBIT_S20_3LE |\
+			SNDRV_PCM_FMTBIT_S24_LE |\
+			SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver wm8993_dai = {
+	.name = "wm8993-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM8993_RATES,
+		.formats = WM8993_FORMATS,
+	},
+	.capture = {
+		 .stream_name = "Capture",
+		 .channels_min = 1,
+		 .channels_max = 2,
+		 .rates = WM8993_RATES,
+		 .formats = WM8993_FORMATS,
+	 },
+	.ops = &wm8993_ops,
+	.symmetric_rates = 1,
+};
+
+static int wm8993_probe(struct snd_soc_codec *codec)
+{
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+	int ret, i, val;
+
+	wm8993->hubs_data.hp_startup_mode = 1;
+	wm8993->hubs_data.dcs_codes = -2;
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm8993->supplies); i++)
+		wm8993->supplies[i].supply = wm8993_supply_names[i];
+
+	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8993->supplies),
+				 wm8993->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+		return ret;
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(wm8993->supplies),
+				    wm8993->supplies);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+		goto err_get;
+	}
+
+	val = snd_soc_read(codec, WM8993_SOFTWARE_RESET);
+	if (val != wm8993_reg_defaults[WM8993_SOFTWARE_RESET]) {
+		dev_err(codec->dev, "Invalid ID register value %x\n", val);
+		ret = -EINVAL;
+		goto err_enable;
+	}
+
+	ret = snd_soc_write(codec, WM8993_SOFTWARE_RESET, 0xffff);
+	if (ret != 0)
+		goto err_enable;
+
+	codec->cache_only = 1;
+
+	/* By default we're using the output mixers */
+	wm8993->class_w_users = 2;
+
+	/* Latch volume update bits and default ZC on */
+	snd_soc_update_bits(codec, WM8993_RIGHT_DAC_DIGITAL_VOLUME,
+			    WM8993_DAC_VU, WM8993_DAC_VU);
+	snd_soc_update_bits(codec, WM8993_RIGHT_ADC_DIGITAL_VOLUME,
+			    WM8993_ADC_VU, WM8993_ADC_VU);
+
+	/* Manualy manage the HPOUT sequencing for independent stereo
+	 * control. */
+	snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
+			    WM8993_HPOUT1_AUTO_PU, 0);
+
+	/* Use automatic clock configuration */
+	snd_soc_update_bits(codec, WM8993_CLOCKING_4, WM8993_SR_MODE, 0);
+
+	wm_hubs_handle_analogue_pdata(codec, wm8993->pdata.lineout1_diff,
+				      wm8993->pdata.lineout2_diff,
+				      wm8993->pdata.lineout1fb,
+				      wm8993->pdata.lineout2fb,
+				      wm8993->pdata.jd_scthr,
+				      wm8993->pdata.jd_thr,
+				      wm8993->pdata.micbias1_lvl,
+				      wm8993->pdata.micbias2_lvl);
+
+	ret = wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	if (ret != 0)
+		goto err_enable;
+
+	snd_soc_add_controls(codec, wm8993_snd_controls,
+			     ARRAY_SIZE(wm8993_snd_controls));
+	if (wm8993->pdata.num_retune_configs != 0) {
+		dev_dbg(codec->dev, "Using ReTune Mobile\n");
+	} else {
+		dev_dbg(codec->dev, "No ReTune Mobile, using normal EQ\n");
+		snd_soc_add_controls(codec, wm8993_eq_controls,
+				     ARRAY_SIZE(wm8993_eq_controls));
+	}
+
+	snd_soc_dapm_new_controls(codec, wm8993_dapm_widgets,
+				  ARRAY_SIZE(wm8993_dapm_widgets));
+	wm_hubs_add_analogue_controls(codec);
+
+	snd_soc_dapm_add_routes(codec, routes, ARRAY_SIZE(routes));
+	wm_hubs_add_analogue_routes(codec, wm8993->pdata.lineout1_diff,
+				    wm8993->pdata.lineout2_diff);
+
+	return 0;
+
+err_enable:
+	regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
+err_get:
+	regulator_bulk_free(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
+	return ret;
+}
+
+static int wm8993_remove(struct snd_soc_codec *codec)
+{
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+
+	wm8993_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	regulator_bulk_free(ARRAY_SIZE(wm8993->supplies), wm8993->supplies);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm8993_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+	int fll_fout = wm8993->fll_fout;
+	int fll_fref  = wm8993->fll_fref;
+	int ret;
+
+	/* Stop the FLL in an orderly fashion */
+	ret = _wm8993_set_fll(codec, 0, 0, 0, 0);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to stop FLL\n");
+		return ret;
+	}
+
+	wm8993->fll_fout = fll_fout;
+	wm8993->fll_fref = fll_fref;
+
+	wm8993_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm8993_resume(struct snd_soc_codec *codec)
+{
+	struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Restart the FLL? */
+	if (wm8993->fll_fout) {
+		int fll_fout = wm8993->fll_fout;
+		int fll_fref  = wm8993->fll_fref;
+
+		wm8993->fll_fref = 0;
+		wm8993->fll_fout = 0;
+
+		ret = _wm8993_set_fll(codec, 0, wm8993->fll_src,
+				     fll_fref, fll_fout);
+		if (ret != 0)
+			dev_err(codec->dev, "Failed to restart FLL\n");
+	}
+
+	return 0;
+}
+#else
+#define wm8993_suspend NULL
+#define wm8993_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8993 = {
+	.probe = 	wm8993_probe,
+	.remove = 	wm8993_remove,
+	.suspend =	wm8993_suspend,
+	.resume =	wm8993_resume,
+	.set_bias_level = wm8993_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm8993_reg_defaults),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm8993_reg_defaults,
+	.volatile_register = wm8993_volatile,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8993_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm8993_priv *wm8993;
+	int ret;
+
+	wm8993 = kzalloc(sizeof(struct wm8993_priv), GFP_KERNEL);
+	if (wm8993 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm8993);
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm8993, &wm8993_dai, 1);
+	if (ret < 0)
+		kfree(wm8993);
+	return ret;
+}
+
+static __devexit int wm8993_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm8993_i2c_id[] = {
+	{ "wm8993", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm8993_i2c_id);
+
+static struct i2c_driver wm8993_i2c_driver = {
+	.driver = {
+		.name = "wm8993-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm8993_i2c_probe,
+	.remove =   __devexit_p(wm8993_i2c_remove),
+	.id_table = wm8993_i2c_id,
+};
+#endif
+
+static int __init wm8993_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm8993_i2c_driver);
+	if (ret != 0) {
+		pr_err("WM8993: Unable to register I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm8993_modinit);
+
+static void __exit wm8993_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm8993_i2c_driver);
+#endif
+}
+module_exit(wm8993_exit);
+
+
+MODULE_DESCRIPTION("ASoC WM8993 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm8993.h b/linux/sound/soc/codecs/wm8993.h
new file mode 100644
index 0000000..2184617
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8993.h
@@ -0,0 +1,2129 @@
+#ifndef WM8993_H
+#define WM8993_H
+
+#define WM8993_SYSCLK_MCLK     1
+#define WM8993_SYSCLK_FLL      2
+
+#define WM8993_FLL_MCLK  1
+#define WM8993_FLL_BCLK  2
+#define WM8993_FLL_LRCLK 3
+
+/*
+ * Register values.
+ */
+#define WM8993_SOFTWARE_RESET                   0x00
+#define WM8993_POWER_MANAGEMENT_1               0x01
+#define WM8993_POWER_MANAGEMENT_2               0x02
+#define WM8993_POWER_MANAGEMENT_3               0x03
+#define WM8993_AUDIO_INTERFACE_1                0x04
+#define WM8993_AUDIO_INTERFACE_2                0x05
+#define WM8993_CLOCKING_1                       0x06
+#define WM8993_CLOCKING_2                       0x07
+#define WM8993_AUDIO_INTERFACE_3                0x08
+#define WM8993_AUDIO_INTERFACE_4                0x09
+#define WM8993_DAC_CTRL                         0x0A
+#define WM8993_LEFT_DAC_DIGITAL_VOLUME          0x0B
+#define WM8993_RIGHT_DAC_DIGITAL_VOLUME         0x0C
+#define WM8993_DIGITAL_SIDE_TONE                0x0D
+#define WM8993_ADC_CTRL                         0x0E
+#define WM8993_LEFT_ADC_DIGITAL_VOLUME          0x0F
+#define WM8993_RIGHT_ADC_DIGITAL_VOLUME         0x10
+#define WM8993_GPIO_CTRL_1                      0x12
+#define WM8993_GPIO1                            0x13
+#define WM8993_IRQ_DEBOUNCE                     0x14
+#define WM8993_GPIOCTRL_2                       0x16
+#define WM8993_GPIO_POL                         0x17
+#define WM8993_LEFT_LINE_INPUT_1_2_VOLUME       0x18
+#define WM8993_LEFT_LINE_INPUT_3_4_VOLUME       0x19
+#define WM8993_RIGHT_LINE_INPUT_1_2_VOLUME      0x1A
+#define WM8993_RIGHT_LINE_INPUT_3_4_VOLUME      0x1B
+#define WM8993_LEFT_OUTPUT_VOLUME               0x1C
+#define WM8993_RIGHT_OUTPUT_VOLUME              0x1D
+#define WM8993_LINE_OUTPUTS_VOLUME              0x1E
+#define WM8993_HPOUT2_VOLUME                    0x1F
+#define WM8993_LEFT_OPGA_VOLUME                 0x20
+#define WM8993_RIGHT_OPGA_VOLUME                0x21
+#define WM8993_SPKMIXL_ATTENUATION              0x22
+#define WM8993_SPKMIXR_ATTENUATION              0x23
+#define WM8993_SPKOUT_MIXERS                    0x24
+#define WM8993_SPKOUT_BOOST                     0x25
+#define WM8993_SPEAKER_VOLUME_LEFT              0x26
+#define WM8993_SPEAKER_VOLUME_RIGHT             0x27
+#define WM8993_INPUT_MIXER2                     0x28
+#define WM8993_INPUT_MIXER3                     0x29
+#define WM8993_INPUT_MIXER4                     0x2A
+#define WM8993_INPUT_MIXER5                     0x2B
+#define WM8993_INPUT_MIXER6                     0x2C
+#define WM8993_OUTPUT_MIXER1                    0x2D
+#define WM8993_OUTPUT_MIXER2                    0x2E
+#define WM8993_OUTPUT_MIXER3                    0x2F
+#define WM8993_OUTPUT_MIXER4                    0x30
+#define WM8993_OUTPUT_MIXER5                    0x31
+#define WM8993_OUTPUT_MIXER6                    0x32
+#define WM8993_HPOUT2_MIXER                     0x33
+#define WM8993_LINE_MIXER1                      0x34
+#define WM8993_LINE_MIXER2                      0x35
+#define WM8993_SPEAKER_MIXER                    0x36
+#define WM8993_ADDITIONAL_CONTROL               0x37
+#define WM8993_ANTIPOP1                         0x38
+#define WM8993_ANTIPOP2                         0x39
+#define WM8993_MICBIAS                          0x3A
+#define WM8993_FLL_CONTROL_1                    0x3C
+#define WM8993_FLL_CONTROL_2                    0x3D
+#define WM8993_FLL_CONTROL_3                    0x3E
+#define WM8993_FLL_CONTROL_4                    0x3F
+#define WM8993_FLL_CONTROL_5                    0x40
+#define WM8993_CLOCKING_3                       0x41
+#define WM8993_CLOCKING_4                       0x42
+#define WM8993_MW_SLAVE_CONTROL                 0x43
+#define WM8993_BUS_CONTROL_1                    0x45
+#define WM8993_WRITE_SEQUENCER_0                0x46
+#define WM8993_WRITE_SEQUENCER_1                0x47
+#define WM8993_WRITE_SEQUENCER_2                0x48
+#define WM8993_WRITE_SEQUENCER_3                0x49
+#define WM8993_WRITE_SEQUENCER_4                0x4A
+#define WM8993_WRITE_SEQUENCER_5                0x4B
+#define WM8993_CHARGE_PUMP_1                    0x4C
+#define WM8993_CLASS_W_0                        0x51
+#define WM8993_DC_SERVO_0                       0x54
+#define WM8993_DC_SERVO_1                       0x55
+#define WM8993_DC_SERVO_3                       0x57
+#define WM8993_DC_SERVO_READBACK_0              0x58
+#define WM8993_DC_SERVO_READBACK_1              0x59
+#define WM8993_DC_SERVO_READBACK_2              0x5A
+#define WM8993_ANALOGUE_HP_0                    0x60
+#define WM8993_EQ1                              0x62
+#define WM8993_EQ2                              0x63
+#define WM8993_EQ3                              0x64
+#define WM8993_EQ4                              0x65
+#define WM8993_EQ5                              0x66
+#define WM8993_EQ6                              0x67
+#define WM8993_EQ7                              0x68
+#define WM8993_EQ8                              0x69
+#define WM8993_EQ9                              0x6A
+#define WM8993_EQ10                             0x6B
+#define WM8993_EQ11                             0x6C
+#define WM8993_EQ12                             0x6D
+#define WM8993_EQ13                             0x6E
+#define WM8993_EQ14                             0x6F
+#define WM8993_EQ15                             0x70
+#define WM8993_EQ16                             0x71
+#define WM8993_EQ17                             0x72
+#define WM8993_EQ18                             0x73
+#define WM8993_EQ19                             0x74
+#define WM8993_EQ20                             0x75
+#define WM8993_EQ21                             0x76
+#define WM8993_EQ22                             0x77
+#define WM8993_EQ23                             0x78
+#define WM8993_EQ24                             0x79
+#define WM8993_DIGITAL_PULLS                    0x7A
+#define WM8993_DRC_CONTROL_1                    0x7B
+#define WM8993_DRC_CONTROL_2                    0x7C
+#define WM8993_DRC_CONTROL_3                    0x7D
+#define WM8993_DRC_CONTROL_4                    0x7E
+
+#define WM8993_REGISTER_COUNT                   0x7F
+#define WM8993_MAX_REGISTER                     0x7E
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Software Reset
+ */
+#define WM8993_SW_RESET_MASK                    0xFFFF  /* SW_RESET - [15:0] */
+#define WM8993_SW_RESET_SHIFT                        0  /* SW_RESET - [15:0] */
+#define WM8993_SW_RESET_WIDTH                       16  /* SW_RESET - [15:0] */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM8993_SPKOUTR_ENA                      0x2000  /* SPKOUTR_ENA */
+#define WM8993_SPKOUTR_ENA_MASK                 0x2000  /* SPKOUTR_ENA */
+#define WM8993_SPKOUTR_ENA_SHIFT                    13  /* SPKOUTR_ENA */
+#define WM8993_SPKOUTR_ENA_WIDTH                     1  /* SPKOUTR_ENA */
+#define WM8993_SPKOUTL_ENA                      0x1000  /* SPKOUTL_ENA */
+#define WM8993_SPKOUTL_ENA_MASK                 0x1000  /* SPKOUTL_ENA */
+#define WM8993_SPKOUTL_ENA_SHIFT                    12  /* SPKOUTL_ENA */
+#define WM8993_SPKOUTL_ENA_WIDTH                     1  /* SPKOUTL_ENA */
+#define WM8993_HPOUT2_ENA                       0x0800  /* HPOUT2_ENA */
+#define WM8993_HPOUT2_ENA_MASK                  0x0800  /* HPOUT2_ENA */
+#define WM8993_HPOUT2_ENA_SHIFT                     11  /* HPOUT2_ENA */
+#define WM8993_HPOUT2_ENA_WIDTH                      1  /* HPOUT2_ENA */
+#define WM8993_HPOUT1L_ENA                      0x0200  /* HPOUT1L_ENA */
+#define WM8993_HPOUT1L_ENA_MASK                 0x0200  /* HPOUT1L_ENA */
+#define WM8993_HPOUT1L_ENA_SHIFT                     9  /* HPOUT1L_ENA */
+#define WM8993_HPOUT1L_ENA_WIDTH                     1  /* HPOUT1L_ENA */
+#define WM8993_HPOUT1R_ENA                      0x0100  /* HPOUT1R_ENA */
+#define WM8993_HPOUT1R_ENA_MASK                 0x0100  /* HPOUT1R_ENA */
+#define WM8993_HPOUT1R_ENA_SHIFT                     8  /* HPOUT1R_ENA */
+#define WM8993_HPOUT1R_ENA_WIDTH                     1  /* HPOUT1R_ENA */
+#define WM8993_MICB2_ENA                        0x0020  /* MICB2_ENA */
+#define WM8993_MICB2_ENA_MASK                   0x0020  /* MICB2_ENA */
+#define WM8993_MICB2_ENA_SHIFT                       5  /* MICB2_ENA */
+#define WM8993_MICB2_ENA_WIDTH                       1  /* MICB2_ENA */
+#define WM8993_MICB1_ENA                        0x0010  /* MICB1_ENA */
+#define WM8993_MICB1_ENA_MASK                   0x0010  /* MICB1_ENA */
+#define WM8993_MICB1_ENA_SHIFT                       4  /* MICB1_ENA */
+#define WM8993_MICB1_ENA_WIDTH                       1  /* MICB1_ENA */
+#define WM8993_VMID_SEL_MASK                    0x0006  /* VMID_SEL - [2:1] */
+#define WM8993_VMID_SEL_SHIFT                        1  /* VMID_SEL - [2:1] */
+#define WM8993_VMID_SEL_WIDTH                        2  /* VMID_SEL - [2:1] */
+#define WM8993_BIAS_ENA                         0x0001  /* BIAS_ENA */
+#define WM8993_BIAS_ENA_MASK                    0x0001  /* BIAS_ENA */
+#define WM8993_BIAS_ENA_SHIFT                        0  /* BIAS_ENA */
+#define WM8993_BIAS_ENA_WIDTH                        1  /* BIAS_ENA */
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM8993_TSHUT_ENA                        0x4000  /* TSHUT_ENA */
+#define WM8993_TSHUT_ENA_MASK                   0x4000  /* TSHUT_ENA */
+#define WM8993_TSHUT_ENA_SHIFT                      14  /* TSHUT_ENA */
+#define WM8993_TSHUT_ENA_WIDTH                       1  /* TSHUT_ENA */
+#define WM8993_TSHUT_OPDIS                      0x2000  /* TSHUT_OPDIS */
+#define WM8993_TSHUT_OPDIS_MASK                 0x2000  /* TSHUT_OPDIS */
+#define WM8993_TSHUT_OPDIS_SHIFT                    13  /* TSHUT_OPDIS */
+#define WM8993_TSHUT_OPDIS_WIDTH                     1  /* TSHUT_OPDIS */
+#define WM8993_OPCLK_ENA                        0x0800  /* OPCLK_ENA */
+#define WM8993_OPCLK_ENA_MASK                   0x0800  /* OPCLK_ENA */
+#define WM8993_OPCLK_ENA_SHIFT                      11  /* OPCLK_ENA */
+#define WM8993_OPCLK_ENA_WIDTH                       1  /* OPCLK_ENA */
+#define WM8993_MIXINL_ENA                       0x0200  /* MIXINL_ENA */
+#define WM8993_MIXINL_ENA_MASK                  0x0200  /* MIXINL_ENA */
+#define WM8993_MIXINL_ENA_SHIFT                      9  /* MIXINL_ENA */
+#define WM8993_MIXINL_ENA_WIDTH                      1  /* MIXINL_ENA */
+#define WM8993_MIXINR_ENA                       0x0100  /* MIXINR_ENA */
+#define WM8993_MIXINR_ENA_MASK                  0x0100  /* MIXINR_ENA */
+#define WM8993_MIXINR_ENA_SHIFT                      8  /* MIXINR_ENA */
+#define WM8993_MIXINR_ENA_WIDTH                      1  /* MIXINR_ENA */
+#define WM8993_IN2L_ENA                         0x0080  /* IN2L_ENA */
+#define WM8993_IN2L_ENA_MASK                    0x0080  /* IN2L_ENA */
+#define WM8993_IN2L_ENA_SHIFT                        7  /* IN2L_ENA */
+#define WM8993_IN2L_ENA_WIDTH                        1  /* IN2L_ENA */
+#define WM8993_IN1L_ENA                         0x0040  /* IN1L_ENA */
+#define WM8993_IN1L_ENA_MASK                    0x0040  /* IN1L_ENA */
+#define WM8993_IN1L_ENA_SHIFT                        6  /* IN1L_ENA */
+#define WM8993_IN1L_ENA_WIDTH                        1  /* IN1L_ENA */
+#define WM8993_IN2R_ENA                         0x0020  /* IN2R_ENA */
+#define WM8993_IN2R_ENA_MASK                    0x0020  /* IN2R_ENA */
+#define WM8993_IN2R_ENA_SHIFT                        5  /* IN2R_ENA */
+#define WM8993_IN2R_ENA_WIDTH                        1  /* IN2R_ENA */
+#define WM8993_IN1R_ENA                         0x0010  /* IN1R_ENA */
+#define WM8993_IN1R_ENA_MASK                    0x0010  /* IN1R_ENA */
+#define WM8993_IN1R_ENA_SHIFT                        4  /* IN1R_ENA */
+#define WM8993_IN1R_ENA_WIDTH                        1  /* IN1R_ENA */
+#define WM8993_ADCL_ENA                         0x0002  /* ADCL_ENA */
+#define WM8993_ADCL_ENA_MASK                    0x0002  /* ADCL_ENA */
+#define WM8993_ADCL_ENA_SHIFT                        1  /* ADCL_ENA */
+#define WM8993_ADCL_ENA_WIDTH                        1  /* ADCL_ENA */
+#define WM8993_ADCR_ENA                         0x0001  /* ADCR_ENA */
+#define WM8993_ADCR_ENA_MASK                    0x0001  /* ADCR_ENA */
+#define WM8993_ADCR_ENA_SHIFT                        0  /* ADCR_ENA */
+#define WM8993_ADCR_ENA_WIDTH                        1  /* ADCR_ENA */
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM8993_LINEOUT1N_ENA                    0x2000  /* LINEOUT1N_ENA */
+#define WM8993_LINEOUT1N_ENA_MASK               0x2000  /* LINEOUT1N_ENA */
+#define WM8993_LINEOUT1N_ENA_SHIFT                  13  /* LINEOUT1N_ENA */
+#define WM8993_LINEOUT1N_ENA_WIDTH                   1  /* LINEOUT1N_ENA */
+#define WM8993_LINEOUT1P_ENA                    0x1000  /* LINEOUT1P_ENA */
+#define WM8993_LINEOUT1P_ENA_MASK               0x1000  /* LINEOUT1P_ENA */
+#define WM8993_LINEOUT1P_ENA_SHIFT                  12  /* LINEOUT1P_ENA */
+#define WM8993_LINEOUT1P_ENA_WIDTH                   1  /* LINEOUT1P_ENA */
+#define WM8993_LINEOUT2N_ENA                    0x0800  /* LINEOUT2N_ENA */
+#define WM8993_LINEOUT2N_ENA_MASK               0x0800  /* LINEOUT2N_ENA */
+#define WM8993_LINEOUT2N_ENA_SHIFT                  11  /* LINEOUT2N_ENA */
+#define WM8993_LINEOUT2N_ENA_WIDTH                   1  /* LINEOUT2N_ENA */
+#define WM8993_LINEOUT2P_ENA                    0x0400  /* LINEOUT2P_ENA */
+#define WM8993_LINEOUT2P_ENA_MASK               0x0400  /* LINEOUT2P_ENA */
+#define WM8993_LINEOUT2P_ENA_SHIFT                  10  /* LINEOUT2P_ENA */
+#define WM8993_LINEOUT2P_ENA_WIDTH                   1  /* LINEOUT2P_ENA */
+#define WM8993_SPKRVOL_ENA                      0x0200  /* SPKRVOL_ENA */
+#define WM8993_SPKRVOL_ENA_MASK                 0x0200  /* SPKRVOL_ENA */
+#define WM8993_SPKRVOL_ENA_SHIFT                     9  /* SPKRVOL_ENA */
+#define WM8993_SPKRVOL_ENA_WIDTH                     1  /* SPKRVOL_ENA */
+#define WM8993_SPKLVOL_ENA                      0x0100  /* SPKLVOL_ENA */
+#define WM8993_SPKLVOL_ENA_MASK                 0x0100  /* SPKLVOL_ENA */
+#define WM8993_SPKLVOL_ENA_SHIFT                     8  /* SPKLVOL_ENA */
+#define WM8993_SPKLVOL_ENA_WIDTH                     1  /* SPKLVOL_ENA */
+#define WM8993_MIXOUTLVOL_ENA                   0x0080  /* MIXOUTLVOL_ENA */
+#define WM8993_MIXOUTLVOL_ENA_MASK              0x0080  /* MIXOUTLVOL_ENA */
+#define WM8993_MIXOUTLVOL_ENA_SHIFT                  7  /* MIXOUTLVOL_ENA */
+#define WM8993_MIXOUTLVOL_ENA_WIDTH                  1  /* MIXOUTLVOL_ENA */
+#define WM8993_MIXOUTRVOL_ENA                   0x0040  /* MIXOUTRVOL_ENA */
+#define WM8993_MIXOUTRVOL_ENA_MASK              0x0040  /* MIXOUTRVOL_ENA */
+#define WM8993_MIXOUTRVOL_ENA_SHIFT                  6  /* MIXOUTRVOL_ENA */
+#define WM8993_MIXOUTRVOL_ENA_WIDTH                  1  /* MIXOUTRVOL_ENA */
+#define WM8993_MIXOUTL_ENA                      0x0020  /* MIXOUTL_ENA */
+#define WM8993_MIXOUTL_ENA_MASK                 0x0020  /* MIXOUTL_ENA */
+#define WM8993_MIXOUTL_ENA_SHIFT                     5  /* MIXOUTL_ENA */
+#define WM8993_MIXOUTL_ENA_WIDTH                     1  /* MIXOUTL_ENA */
+#define WM8993_MIXOUTR_ENA                      0x0010  /* MIXOUTR_ENA */
+#define WM8993_MIXOUTR_ENA_MASK                 0x0010  /* MIXOUTR_ENA */
+#define WM8993_MIXOUTR_ENA_SHIFT                     4  /* MIXOUTR_ENA */
+#define WM8993_MIXOUTR_ENA_WIDTH                     1  /* MIXOUTR_ENA */
+#define WM8993_DACL_ENA                         0x0002  /* DACL_ENA */
+#define WM8993_DACL_ENA_MASK                    0x0002  /* DACL_ENA */
+#define WM8993_DACL_ENA_SHIFT                        1  /* DACL_ENA */
+#define WM8993_DACL_ENA_WIDTH                        1  /* DACL_ENA */
+#define WM8993_DACR_ENA                         0x0001  /* DACR_ENA */
+#define WM8993_DACR_ENA_MASK                    0x0001  /* DACR_ENA */
+#define WM8993_DACR_ENA_SHIFT                        0  /* DACR_ENA */
+#define WM8993_DACR_ENA_WIDTH                        1  /* DACR_ENA */
+
+/*
+ * R4 (0x04) - Audio Interface (1)
+ */
+#define WM8993_AIFADCL_SRC                      0x8000  /* AIFADCL_SRC */
+#define WM8993_AIFADCL_SRC_MASK                 0x8000  /* AIFADCL_SRC */
+#define WM8993_AIFADCL_SRC_SHIFT                    15  /* AIFADCL_SRC */
+#define WM8993_AIFADCL_SRC_WIDTH                     1  /* AIFADCL_SRC */
+#define WM8993_AIFADCR_SRC                      0x4000  /* AIFADCR_SRC */
+#define WM8993_AIFADCR_SRC_MASK                 0x4000  /* AIFADCR_SRC */
+#define WM8993_AIFADCR_SRC_SHIFT                    14  /* AIFADCR_SRC */
+#define WM8993_AIFADCR_SRC_WIDTH                     1  /* AIFADCR_SRC */
+#define WM8993_AIFADC_TDM                       0x2000  /* AIFADC_TDM */
+#define WM8993_AIFADC_TDM_MASK                  0x2000  /* AIFADC_TDM */
+#define WM8993_AIFADC_TDM_SHIFT                     13  /* AIFADC_TDM */
+#define WM8993_AIFADC_TDM_WIDTH                      1  /* AIFADC_TDM */
+#define WM8993_AIFADC_TDM_CHAN                  0x1000  /* AIFADC_TDM_CHAN */
+#define WM8993_AIFADC_TDM_CHAN_MASK             0x1000  /* AIFADC_TDM_CHAN */
+#define WM8993_AIFADC_TDM_CHAN_SHIFT                12  /* AIFADC_TDM_CHAN */
+#define WM8993_AIFADC_TDM_CHAN_WIDTH                 1  /* AIFADC_TDM_CHAN */
+#define WM8993_BCLK_DIR                         0x0200  /* BCLK_DIR */
+#define WM8993_BCLK_DIR_MASK                    0x0200  /* BCLK_DIR */
+#define WM8993_BCLK_DIR_SHIFT                        9  /* BCLK_DIR */
+#define WM8993_BCLK_DIR_WIDTH                        1  /* BCLK_DIR */
+#define WM8993_AIF_BCLK_INV                     0x0100  /* AIF_BCLK_INV */
+#define WM8993_AIF_BCLK_INV_MASK                0x0100  /* AIF_BCLK_INV */
+#define WM8993_AIF_BCLK_INV_SHIFT                    8  /* AIF_BCLK_INV */
+#define WM8993_AIF_BCLK_INV_WIDTH                    1  /* AIF_BCLK_INV */
+#define WM8993_AIF_LRCLK_INV                    0x0080  /* AIF_LRCLK_INV */
+#define WM8993_AIF_LRCLK_INV_MASK               0x0080  /* AIF_LRCLK_INV */
+#define WM8993_AIF_LRCLK_INV_SHIFT                   7  /* AIF_LRCLK_INV */
+#define WM8993_AIF_LRCLK_INV_WIDTH                   1  /* AIF_LRCLK_INV */
+#define WM8993_AIF_WL_MASK                      0x0060  /* AIF_WL - [6:5] */
+#define WM8993_AIF_WL_SHIFT                          5  /* AIF_WL - [6:5] */
+#define WM8993_AIF_WL_WIDTH                          2  /* AIF_WL - [6:5] */
+#define WM8993_AIF_FMT_MASK                     0x0018  /* AIF_FMT - [4:3] */
+#define WM8993_AIF_FMT_SHIFT                         3  /* AIF_FMT - [4:3] */
+#define WM8993_AIF_FMT_WIDTH                         2  /* AIF_FMT - [4:3] */
+
+/*
+ * R5 (0x05) - Audio Interface (2)
+ */
+#define WM8993_AIFDACL_SRC                      0x8000  /* AIFDACL_SRC */
+#define WM8993_AIFDACL_SRC_MASK                 0x8000  /* AIFDACL_SRC */
+#define WM8993_AIFDACL_SRC_SHIFT                    15  /* AIFDACL_SRC */
+#define WM8993_AIFDACL_SRC_WIDTH                     1  /* AIFDACL_SRC */
+#define WM8993_AIFDACR_SRC                      0x4000  /* AIFDACR_SRC */
+#define WM8993_AIFDACR_SRC_MASK                 0x4000  /* AIFDACR_SRC */
+#define WM8993_AIFDACR_SRC_SHIFT                    14  /* AIFDACR_SRC */
+#define WM8993_AIFDACR_SRC_WIDTH                     1  /* AIFDACR_SRC */
+#define WM8993_AIFDAC_TDM                       0x2000  /* AIFDAC_TDM */
+#define WM8993_AIFDAC_TDM_MASK                  0x2000  /* AIFDAC_TDM */
+#define WM8993_AIFDAC_TDM_SHIFT                     13  /* AIFDAC_TDM */
+#define WM8993_AIFDAC_TDM_WIDTH                      1  /* AIFDAC_TDM */
+#define WM8993_AIFDAC_TDM_CHAN                  0x1000  /* AIFDAC_TDM_CHAN */
+#define WM8993_AIFDAC_TDM_CHAN_MASK             0x1000  /* AIFDAC_TDM_CHAN */
+#define WM8993_AIFDAC_TDM_CHAN_SHIFT                12  /* AIFDAC_TDM_CHAN */
+#define WM8993_AIFDAC_TDM_CHAN_WIDTH                 1  /* AIFDAC_TDM_CHAN */
+#define WM8993_DAC_BOOST_MASK                   0x0C00  /* DAC_BOOST - [11:10] */
+#define WM8993_DAC_BOOST_SHIFT                      10  /* DAC_BOOST - [11:10] */
+#define WM8993_DAC_BOOST_WIDTH                       2  /* DAC_BOOST - [11:10] */
+#define WM8993_DAC_COMP                         0x0010  /* DAC_COMP */
+#define WM8993_DAC_COMP_MASK                    0x0010  /* DAC_COMP */
+#define WM8993_DAC_COMP_SHIFT                        4  /* DAC_COMP */
+#define WM8993_DAC_COMP_WIDTH                        1  /* DAC_COMP */
+#define WM8993_DAC_COMPMODE                     0x0008  /* DAC_COMPMODE */
+#define WM8993_DAC_COMPMODE_MASK                0x0008  /* DAC_COMPMODE */
+#define WM8993_DAC_COMPMODE_SHIFT                    3  /* DAC_COMPMODE */
+#define WM8993_DAC_COMPMODE_WIDTH                    1  /* DAC_COMPMODE */
+#define WM8993_ADC_COMP                         0x0004  /* ADC_COMP */
+#define WM8993_ADC_COMP_MASK                    0x0004  /* ADC_COMP */
+#define WM8993_ADC_COMP_SHIFT                        2  /* ADC_COMP */
+#define WM8993_ADC_COMP_WIDTH                        1  /* ADC_COMP */
+#define WM8993_ADC_COMPMODE                     0x0002  /* ADC_COMPMODE */
+#define WM8993_ADC_COMPMODE_MASK                0x0002  /* ADC_COMPMODE */
+#define WM8993_ADC_COMPMODE_SHIFT                    1  /* ADC_COMPMODE */
+#define WM8993_ADC_COMPMODE_WIDTH                    1  /* ADC_COMPMODE */
+#define WM8993_LOOPBACK                         0x0001  /* LOOPBACK */
+#define WM8993_LOOPBACK_MASK                    0x0001  /* LOOPBACK */
+#define WM8993_LOOPBACK_SHIFT                        0  /* LOOPBACK */
+#define WM8993_LOOPBACK_WIDTH                        1  /* LOOPBACK */
+
+/*
+ * R6 (0x06) - Clocking 1
+ */
+#define WM8993_TOCLK_RATE                       0x8000  /* TOCLK_RATE */
+#define WM8993_TOCLK_RATE_MASK                  0x8000  /* TOCLK_RATE */
+#define WM8993_TOCLK_RATE_SHIFT                     15  /* TOCLK_RATE */
+#define WM8993_TOCLK_RATE_WIDTH                      1  /* TOCLK_RATE */
+#define WM8993_TOCLK_ENA                        0x4000  /* TOCLK_ENA */
+#define WM8993_TOCLK_ENA_MASK                   0x4000  /* TOCLK_ENA */
+#define WM8993_TOCLK_ENA_SHIFT                      14  /* TOCLK_ENA */
+#define WM8993_TOCLK_ENA_WIDTH                       1  /* TOCLK_ENA */
+#define WM8993_OPCLK_DIV_MASK                   0x1E00  /* OPCLK_DIV - [12:9] */
+#define WM8993_OPCLK_DIV_SHIFT                       9  /* OPCLK_DIV - [12:9] */
+#define WM8993_OPCLK_DIV_WIDTH                       4  /* OPCLK_DIV - [12:9] */
+#define WM8993_DCLK_DIV_MASK                    0x01C0  /* DCLK_DIV - [8:6] */
+#define WM8993_DCLK_DIV_SHIFT                        6  /* DCLK_DIV - [8:6] */
+#define WM8993_DCLK_DIV_WIDTH                        3  /* DCLK_DIV - [8:6] */
+#define WM8993_BCLK_DIV_MASK                    0x001E  /* BCLK_DIV - [4:1] */
+#define WM8993_BCLK_DIV_SHIFT                        1  /* BCLK_DIV - [4:1] */
+#define WM8993_BCLK_DIV_WIDTH                        4  /* BCLK_DIV - [4:1] */
+
+/*
+ * R7 (0x07) - Clocking 2
+ */
+#define WM8993_MCLK_SRC                         0x8000  /* MCLK_SRC */
+#define WM8993_MCLK_SRC_MASK                    0x8000  /* MCLK_SRC */
+#define WM8993_MCLK_SRC_SHIFT                       15  /* MCLK_SRC */
+#define WM8993_MCLK_SRC_WIDTH                        1  /* MCLK_SRC */
+#define WM8993_SYSCLK_SRC                       0x4000  /* SYSCLK_SRC */
+#define WM8993_SYSCLK_SRC_MASK                  0x4000  /* SYSCLK_SRC */
+#define WM8993_SYSCLK_SRC_SHIFT                     14  /* SYSCLK_SRC */
+#define WM8993_SYSCLK_SRC_WIDTH                      1  /* SYSCLK_SRC */
+#define WM8993_MCLK_DIV                         0x1000  /* MCLK_DIV */
+#define WM8993_MCLK_DIV_MASK                    0x1000  /* MCLK_DIV */
+#define WM8993_MCLK_DIV_SHIFT                       12  /* MCLK_DIV */
+#define WM8993_MCLK_DIV_WIDTH                        1  /* MCLK_DIV */
+#define WM8993_MCLK_INV                         0x0400  /* MCLK_INV */
+#define WM8993_MCLK_INV_MASK                    0x0400  /* MCLK_INV */
+#define WM8993_MCLK_INV_SHIFT                       10  /* MCLK_INV */
+#define WM8993_MCLK_INV_WIDTH                        1  /* MCLK_INV */
+#define WM8993_ADC_DIV_MASK                     0x00E0  /* ADC_DIV - [7:5] */
+#define WM8993_ADC_DIV_SHIFT                         5  /* ADC_DIV - [7:5] */
+#define WM8993_ADC_DIV_WIDTH                         3  /* ADC_DIV - [7:5] */
+#define WM8993_DAC_DIV_MASK                     0x001C  /* DAC_DIV - [4:2] */
+#define WM8993_DAC_DIV_SHIFT                         2  /* DAC_DIV - [4:2] */
+#define WM8993_DAC_DIV_WIDTH                         3  /* DAC_DIV - [4:2] */
+
+/*
+ * R8 (0x08) - Audio Interface (3)
+ */
+#define WM8993_AIF_MSTR1                        0x8000  /* AIF_MSTR1 */
+#define WM8993_AIF_MSTR1_MASK                   0x8000  /* AIF_MSTR1 */
+#define WM8993_AIF_MSTR1_SHIFT                      15  /* AIF_MSTR1 */
+#define WM8993_AIF_MSTR1_WIDTH                       1  /* AIF_MSTR1 */
+
+/*
+ * R9 (0x09) - Audio Interface (4)
+ */
+#define WM8993_AIF_TRIS                         0x2000  /* AIF_TRIS */
+#define WM8993_AIF_TRIS_MASK                    0x2000  /* AIF_TRIS */
+#define WM8993_AIF_TRIS_SHIFT                       13  /* AIF_TRIS */
+#define WM8993_AIF_TRIS_WIDTH                        1  /* AIF_TRIS */
+#define WM8993_LRCLK_DIR                        0x0800  /* LRCLK_DIR */
+#define WM8993_LRCLK_DIR_MASK                   0x0800  /* LRCLK_DIR */
+#define WM8993_LRCLK_DIR_SHIFT                      11  /* LRCLK_DIR */
+#define WM8993_LRCLK_DIR_WIDTH                       1  /* LRCLK_DIR */
+#define WM8993_LRCLK_RATE_MASK                  0x07FF  /* LRCLK_RATE - [10:0] */
+#define WM8993_LRCLK_RATE_SHIFT                      0  /* LRCLK_RATE - [10:0] */
+#define WM8993_LRCLK_RATE_WIDTH                     11  /* LRCLK_RATE - [10:0] */
+
+/*
+ * R10 (0x0A) - DAC CTRL
+ */
+#define WM8993_DAC_OSR128                       0x2000  /* DAC_OSR128 */
+#define WM8993_DAC_OSR128_MASK                  0x2000  /* DAC_OSR128 */
+#define WM8993_DAC_OSR128_SHIFT                     13  /* DAC_OSR128 */
+#define WM8993_DAC_OSR128_WIDTH                      1  /* DAC_OSR128 */
+#define WM8993_DAC_MONO                         0x0200  /* DAC_MONO */
+#define WM8993_DAC_MONO_MASK                    0x0200  /* DAC_MONO */
+#define WM8993_DAC_MONO_SHIFT                        9  /* DAC_MONO */
+#define WM8993_DAC_MONO_WIDTH                        1  /* DAC_MONO */
+#define WM8993_DAC_SB_FILT                      0x0100  /* DAC_SB_FILT */
+#define WM8993_DAC_SB_FILT_MASK                 0x0100  /* DAC_SB_FILT */
+#define WM8993_DAC_SB_FILT_SHIFT                     8  /* DAC_SB_FILT */
+#define WM8993_DAC_SB_FILT_WIDTH                     1  /* DAC_SB_FILT */
+#define WM8993_DAC_MUTERATE                     0x0080  /* DAC_MUTERATE */
+#define WM8993_DAC_MUTERATE_MASK                0x0080  /* DAC_MUTERATE */
+#define WM8993_DAC_MUTERATE_SHIFT                    7  /* DAC_MUTERATE */
+#define WM8993_DAC_MUTERATE_WIDTH                    1  /* DAC_MUTERATE */
+#define WM8993_DAC_UNMUTE_RAMP                  0x0040  /* DAC_UNMUTE_RAMP */
+#define WM8993_DAC_UNMUTE_RAMP_MASK             0x0040  /* DAC_UNMUTE_RAMP */
+#define WM8993_DAC_UNMUTE_RAMP_SHIFT                 6  /* DAC_UNMUTE_RAMP */
+#define WM8993_DAC_UNMUTE_RAMP_WIDTH                 1  /* DAC_UNMUTE_RAMP */
+#define WM8993_DEEMPH_MASK                      0x0030  /* DEEMPH - [5:4] */
+#define WM8993_DEEMPH_SHIFT                          4  /* DEEMPH - [5:4] */
+#define WM8993_DEEMPH_WIDTH                          2  /* DEEMPH - [5:4] */
+#define WM8993_DAC_MUTE                         0x0004  /* DAC_MUTE */
+#define WM8993_DAC_MUTE_MASK                    0x0004  /* DAC_MUTE */
+#define WM8993_DAC_MUTE_SHIFT                        2  /* DAC_MUTE */
+#define WM8993_DAC_MUTE_WIDTH                        1  /* DAC_MUTE */
+#define WM8993_DACL_DATINV                      0x0002  /* DACL_DATINV */
+#define WM8993_DACL_DATINV_MASK                 0x0002  /* DACL_DATINV */
+#define WM8993_DACL_DATINV_SHIFT                     1  /* DACL_DATINV */
+#define WM8993_DACL_DATINV_WIDTH                     1  /* DACL_DATINV */
+#define WM8993_DACR_DATINV                      0x0001  /* DACR_DATINV */
+#define WM8993_DACR_DATINV_MASK                 0x0001  /* DACR_DATINV */
+#define WM8993_DACR_DATINV_SHIFT                     0  /* DACR_DATINV */
+#define WM8993_DACR_DATINV_WIDTH                     1  /* DACR_DATINV */
+
+/*
+ * R11 (0x0B) - Left DAC Digital Volume
+ */
+#define WM8993_DAC_VU                           0x0100  /* DAC_VU */
+#define WM8993_DAC_VU_MASK                      0x0100  /* DAC_VU */
+#define WM8993_DAC_VU_SHIFT                          8  /* DAC_VU */
+#define WM8993_DAC_VU_WIDTH                          1  /* DAC_VU */
+#define WM8993_DACL_VOL_MASK                    0x00FF  /* DACL_VOL - [7:0] */
+#define WM8993_DACL_VOL_SHIFT                        0  /* DACL_VOL - [7:0] */
+#define WM8993_DACL_VOL_WIDTH                        8  /* DACL_VOL - [7:0] */
+
+/*
+ * R12 (0x0C) - Right DAC Digital Volume
+ */
+#define WM8993_DAC_VU                           0x0100  /* DAC_VU */
+#define WM8993_DAC_VU_MASK                      0x0100  /* DAC_VU */
+#define WM8993_DAC_VU_SHIFT                          8  /* DAC_VU */
+#define WM8993_DAC_VU_WIDTH                          1  /* DAC_VU */
+#define WM8993_DACR_VOL_MASK                    0x00FF  /* DACR_VOL - [7:0] */
+#define WM8993_DACR_VOL_SHIFT                        0  /* DACR_VOL - [7:0] */
+#define WM8993_DACR_VOL_WIDTH                        8  /* DACR_VOL - [7:0] */
+
+/*
+ * R13 (0x0D) - Digital Side Tone
+ */
+#define WM8993_ADCL_DAC_SVOL_MASK               0x1E00  /* ADCL_DAC_SVOL - [12:9] */
+#define WM8993_ADCL_DAC_SVOL_SHIFT                   9  /* ADCL_DAC_SVOL - [12:9] */
+#define WM8993_ADCL_DAC_SVOL_WIDTH                   4  /* ADCL_DAC_SVOL - [12:9] */
+#define WM8993_ADCR_DAC_SVOL_MASK               0x01E0  /* ADCR_DAC_SVOL - [8:5] */
+#define WM8993_ADCR_DAC_SVOL_SHIFT                   5  /* ADCR_DAC_SVOL - [8:5] */
+#define WM8993_ADCR_DAC_SVOL_WIDTH                   4  /* ADCR_DAC_SVOL - [8:5] */
+#define WM8993_ADC_TO_DACL_MASK                 0x000C  /* ADC_TO_DACL - [3:2] */
+#define WM8993_ADC_TO_DACL_SHIFT                     2  /* ADC_TO_DACL - [3:2] */
+#define WM8993_ADC_TO_DACL_WIDTH                     2  /* ADC_TO_DACL - [3:2] */
+#define WM8993_ADC_TO_DACR_MASK                 0x0003  /* ADC_TO_DACR - [1:0] */
+#define WM8993_ADC_TO_DACR_SHIFT                     0  /* ADC_TO_DACR - [1:0] */
+#define WM8993_ADC_TO_DACR_WIDTH                     2  /* ADC_TO_DACR - [1:0] */
+
+/*
+ * R14 (0x0E) - ADC CTRL
+ */
+#define WM8993_ADC_OSR128                       0x0200  /* ADC_OSR128 */
+#define WM8993_ADC_OSR128_MASK                  0x0200  /* ADC_OSR128 */
+#define WM8993_ADC_OSR128_SHIFT                      9  /* ADC_OSR128 */
+#define WM8993_ADC_OSR128_WIDTH                      1  /* ADC_OSR128 */
+#define WM8993_ADC_HPF                          0x0100  /* ADC_HPF */
+#define WM8993_ADC_HPF_MASK                     0x0100  /* ADC_HPF */
+#define WM8993_ADC_HPF_SHIFT                         8  /* ADC_HPF */
+#define WM8993_ADC_HPF_WIDTH                         1  /* ADC_HPF */
+#define WM8993_ADC_HPF_CUT_MASK                 0x0060  /* ADC_HPF_CUT - [6:5] */
+#define WM8993_ADC_HPF_CUT_SHIFT                     5  /* ADC_HPF_CUT - [6:5] */
+#define WM8993_ADC_HPF_CUT_WIDTH                     2  /* ADC_HPF_CUT - [6:5] */
+#define WM8993_ADCL_DATINV                      0x0002  /* ADCL_DATINV */
+#define WM8993_ADCL_DATINV_MASK                 0x0002  /* ADCL_DATINV */
+#define WM8993_ADCL_DATINV_SHIFT                     1  /* ADCL_DATINV */
+#define WM8993_ADCL_DATINV_WIDTH                     1  /* ADCL_DATINV */
+#define WM8993_ADCR_DATINV                      0x0001  /* ADCR_DATINV */
+#define WM8993_ADCR_DATINV_MASK                 0x0001  /* ADCR_DATINV */
+#define WM8993_ADCR_DATINV_SHIFT                     0  /* ADCR_DATINV */
+#define WM8993_ADCR_DATINV_WIDTH                     1  /* ADCR_DATINV */
+
+/*
+ * R15 (0x0F) - Left ADC Digital Volume
+ */
+#define WM8993_ADC_VU                           0x0100  /* ADC_VU */
+#define WM8993_ADC_VU_MASK                      0x0100  /* ADC_VU */
+#define WM8993_ADC_VU_SHIFT                          8  /* ADC_VU */
+#define WM8993_ADC_VU_WIDTH                          1  /* ADC_VU */
+#define WM8993_ADCL_VOL_MASK                    0x00FF  /* ADCL_VOL - [7:0] */
+#define WM8993_ADCL_VOL_SHIFT                        0  /* ADCL_VOL - [7:0] */
+#define WM8993_ADCL_VOL_WIDTH                        8  /* ADCL_VOL - [7:0] */
+
+/*
+ * R16 (0x10) - Right ADC Digital Volume
+ */
+#define WM8993_ADC_VU                           0x0100  /* ADC_VU */
+#define WM8993_ADC_VU_MASK                      0x0100  /* ADC_VU */
+#define WM8993_ADC_VU_SHIFT                          8  /* ADC_VU */
+#define WM8993_ADC_VU_WIDTH                          1  /* ADC_VU */
+#define WM8993_ADCR_VOL_MASK                    0x00FF  /* ADCR_VOL - [7:0] */
+#define WM8993_ADCR_VOL_SHIFT                        0  /* ADCR_VOL - [7:0] */
+#define WM8993_ADCR_VOL_WIDTH                        8  /* ADCR_VOL - [7:0] */
+
+/*
+ * R18 (0x12) - GPIO CTRL 1
+ */
+#define WM8993_JD2_SC_EINT                      0x8000  /* JD2_SC_EINT */
+#define WM8993_JD2_SC_EINT_MASK                 0x8000  /* JD2_SC_EINT */
+#define WM8993_JD2_SC_EINT_SHIFT                    15  /* JD2_SC_EINT */
+#define WM8993_JD2_SC_EINT_WIDTH                     1  /* JD2_SC_EINT */
+#define WM8993_JD2_EINT                         0x4000  /* JD2_EINT */
+#define WM8993_JD2_EINT_MASK                    0x4000  /* JD2_EINT */
+#define WM8993_JD2_EINT_SHIFT                       14  /* JD2_EINT */
+#define WM8993_JD2_EINT_WIDTH                        1  /* JD2_EINT */
+#define WM8993_WSEQ_EINT                        0x2000  /* WSEQ_EINT */
+#define WM8993_WSEQ_EINT_MASK                   0x2000  /* WSEQ_EINT */
+#define WM8993_WSEQ_EINT_SHIFT                      13  /* WSEQ_EINT */
+#define WM8993_WSEQ_EINT_WIDTH                       1  /* WSEQ_EINT */
+#define WM8993_IRQ                              0x1000  /* IRQ */
+#define WM8993_IRQ_MASK                         0x1000  /* IRQ */
+#define WM8993_IRQ_SHIFT                            12  /* IRQ */
+#define WM8993_IRQ_WIDTH                             1  /* IRQ */
+#define WM8993_TEMPOK_EINT                      0x0800  /* TEMPOK_EINT */
+#define WM8993_TEMPOK_EINT_MASK                 0x0800  /* TEMPOK_EINT */
+#define WM8993_TEMPOK_EINT_SHIFT                    11  /* TEMPOK_EINT */
+#define WM8993_TEMPOK_EINT_WIDTH                     1  /* TEMPOK_EINT */
+#define WM8993_JD1_SC_EINT                      0x0400  /* JD1_SC_EINT */
+#define WM8993_JD1_SC_EINT_MASK                 0x0400  /* JD1_SC_EINT */
+#define WM8993_JD1_SC_EINT_SHIFT                    10  /* JD1_SC_EINT */
+#define WM8993_JD1_SC_EINT_WIDTH                     1  /* JD1_SC_EINT */
+#define WM8993_JD1_EINT                         0x0200  /* JD1_EINT */
+#define WM8993_JD1_EINT_MASK                    0x0200  /* JD1_EINT */
+#define WM8993_JD1_EINT_SHIFT                        9  /* JD1_EINT */
+#define WM8993_JD1_EINT_WIDTH                        1  /* JD1_EINT */
+#define WM8993_FLL_LOCK_EINT                    0x0100  /* FLL_LOCK_EINT */
+#define WM8993_FLL_LOCK_EINT_MASK               0x0100  /* FLL_LOCK_EINT */
+#define WM8993_FLL_LOCK_EINT_SHIFT                   8  /* FLL_LOCK_EINT */
+#define WM8993_FLL_LOCK_EINT_WIDTH                   1  /* FLL_LOCK_EINT */
+#define WM8993_GPI8_EINT                        0x0080  /* GPI8_EINT */
+#define WM8993_GPI8_EINT_MASK                   0x0080  /* GPI8_EINT */
+#define WM8993_GPI8_EINT_SHIFT                       7  /* GPI8_EINT */
+#define WM8993_GPI8_EINT_WIDTH                       1  /* GPI8_EINT */
+#define WM8993_GPI7_EINT                        0x0040  /* GPI7_EINT */
+#define WM8993_GPI7_EINT_MASK                   0x0040  /* GPI7_EINT */
+#define WM8993_GPI7_EINT_SHIFT                       6  /* GPI7_EINT */
+#define WM8993_GPI7_EINT_WIDTH                       1  /* GPI7_EINT */
+#define WM8993_GPIO1_EINT                       0x0001  /* GPIO1_EINT */
+#define WM8993_GPIO1_EINT_MASK                  0x0001  /* GPIO1_EINT */
+#define WM8993_GPIO1_EINT_SHIFT                      0  /* GPIO1_EINT */
+#define WM8993_GPIO1_EINT_WIDTH                      1  /* GPIO1_EINT */
+
+/*
+ * R19 (0x13) - GPIO1
+ */
+#define WM8993_GPIO1_PU                         0x0020  /* GPIO1_PU */
+#define WM8993_GPIO1_PU_MASK                    0x0020  /* GPIO1_PU */
+#define WM8993_GPIO1_PU_SHIFT                        5  /* GPIO1_PU */
+#define WM8993_GPIO1_PU_WIDTH                        1  /* GPIO1_PU */
+#define WM8993_GPIO1_PD                         0x0010  /* GPIO1_PD */
+#define WM8993_GPIO1_PD_MASK                    0x0010  /* GPIO1_PD */
+#define WM8993_GPIO1_PD_SHIFT                        4  /* GPIO1_PD */
+#define WM8993_GPIO1_PD_WIDTH                        1  /* GPIO1_PD */
+#define WM8993_GPIO1_SEL_MASK                   0x000F  /* GPIO1_SEL - [3:0] */
+#define WM8993_GPIO1_SEL_SHIFT                       0  /* GPIO1_SEL - [3:0] */
+#define WM8993_GPIO1_SEL_WIDTH                       4  /* GPIO1_SEL - [3:0] */
+
+/*
+ * R20 (0x14) - IRQ_DEBOUNCE
+ */
+#define WM8993_JD2_SC_DB                        0x8000  /* JD2_SC_DB */
+#define WM8993_JD2_SC_DB_MASK                   0x8000  /* JD2_SC_DB */
+#define WM8993_JD2_SC_DB_SHIFT                      15  /* JD2_SC_DB */
+#define WM8993_JD2_SC_DB_WIDTH                       1  /* JD2_SC_DB */
+#define WM8993_JD2_DB                           0x4000  /* JD2_DB */
+#define WM8993_JD2_DB_MASK                      0x4000  /* JD2_DB */
+#define WM8993_JD2_DB_SHIFT                         14  /* JD2_DB */
+#define WM8993_JD2_DB_WIDTH                          1  /* JD2_DB */
+#define WM8993_WSEQ_DB                          0x2000  /* WSEQ_DB */
+#define WM8993_WSEQ_DB_MASK                     0x2000  /* WSEQ_DB */
+#define WM8993_WSEQ_DB_SHIFT                        13  /* WSEQ_DB */
+#define WM8993_WSEQ_DB_WIDTH                         1  /* WSEQ_DB */
+#define WM8993_TEMPOK_DB                        0x0800  /* TEMPOK_DB */
+#define WM8993_TEMPOK_DB_MASK                   0x0800  /* TEMPOK_DB */
+#define WM8993_TEMPOK_DB_SHIFT                      11  /* TEMPOK_DB */
+#define WM8993_TEMPOK_DB_WIDTH                       1  /* TEMPOK_DB */
+#define WM8993_JD1_SC_DB                        0x0400  /* JD1_SC_DB */
+#define WM8993_JD1_SC_DB_MASK                   0x0400  /* JD1_SC_DB */
+#define WM8993_JD1_SC_DB_SHIFT                      10  /* JD1_SC_DB */
+#define WM8993_JD1_SC_DB_WIDTH                       1  /* JD1_SC_DB */
+#define WM8993_JD1_DB                           0x0200  /* JD1_DB */
+#define WM8993_JD1_DB_MASK                      0x0200  /* JD1_DB */
+#define WM8993_JD1_DB_SHIFT                          9  /* JD1_DB */
+#define WM8993_JD1_DB_WIDTH                          1  /* JD1_DB */
+#define WM8993_FLL_LOCK_DB                      0x0100  /* FLL_LOCK_DB */
+#define WM8993_FLL_LOCK_DB_MASK                 0x0100  /* FLL_LOCK_DB */
+#define WM8993_FLL_LOCK_DB_SHIFT                     8  /* FLL_LOCK_DB */
+#define WM8993_FLL_LOCK_DB_WIDTH                     1  /* FLL_LOCK_DB */
+#define WM8993_GPI8_DB                          0x0080  /* GPI8_DB */
+#define WM8993_GPI8_DB_MASK                     0x0080  /* GPI8_DB */
+#define WM8993_GPI8_DB_SHIFT                         7  /* GPI8_DB */
+#define WM8993_GPI8_DB_WIDTH                         1  /* GPI8_DB */
+#define WM8993_GPI7_DB                          0x0008  /* GPI7_DB */
+#define WM8993_GPI7_DB_MASK                     0x0008  /* GPI7_DB */
+#define WM8993_GPI7_DB_SHIFT                         3  /* GPI7_DB */
+#define WM8993_GPI7_DB_WIDTH                         1  /* GPI7_DB */
+#define WM8993_GPIO1_DB                         0x0001  /* GPIO1_DB */
+#define WM8993_GPIO1_DB_MASK                    0x0001  /* GPIO1_DB */
+#define WM8993_GPIO1_DB_SHIFT                        0  /* GPIO1_DB */
+#define WM8993_GPIO1_DB_WIDTH                        1  /* GPIO1_DB */
+
+/*
+ * R22 (0x16) - GPIOCTRL 2
+ */
+#define WM8993_IM_JD2_EINT                      0x2000  /* IM_JD2_EINT */
+#define WM8993_IM_JD2_EINT_MASK                 0x2000  /* IM_JD2_EINT */
+#define WM8993_IM_JD2_EINT_SHIFT                    13  /* IM_JD2_EINT */
+#define WM8993_IM_JD2_EINT_WIDTH                     1  /* IM_JD2_EINT */
+#define WM8993_IM_JD2_SC_EINT                   0x1000  /* IM_JD2_SC_EINT */
+#define WM8993_IM_JD2_SC_EINT_MASK              0x1000  /* IM_JD2_SC_EINT */
+#define WM8993_IM_JD2_SC_EINT_SHIFT                 12  /* IM_JD2_SC_EINT */
+#define WM8993_IM_JD2_SC_EINT_WIDTH                  1  /* IM_JD2_SC_EINT */
+#define WM8993_IM_TEMPOK_EINT                   0x0800  /* IM_TEMPOK_EINT */
+#define WM8993_IM_TEMPOK_EINT_MASK              0x0800  /* IM_TEMPOK_EINT */
+#define WM8993_IM_TEMPOK_EINT_SHIFT                 11  /* IM_TEMPOK_EINT */
+#define WM8993_IM_TEMPOK_EINT_WIDTH                  1  /* IM_TEMPOK_EINT */
+#define WM8993_IM_JD1_SC_EINT                   0x0400  /* IM_JD1_SC_EINT */
+#define WM8993_IM_JD1_SC_EINT_MASK              0x0400  /* IM_JD1_SC_EINT */
+#define WM8993_IM_JD1_SC_EINT_SHIFT                 10  /* IM_JD1_SC_EINT */
+#define WM8993_IM_JD1_SC_EINT_WIDTH                  1  /* IM_JD1_SC_EINT */
+#define WM8993_IM_JD1_EINT                      0x0200  /* IM_JD1_EINT */
+#define WM8993_IM_JD1_EINT_MASK                 0x0200  /* IM_JD1_EINT */
+#define WM8993_IM_JD1_EINT_SHIFT                     9  /* IM_JD1_EINT */
+#define WM8993_IM_JD1_EINT_WIDTH                     1  /* IM_JD1_EINT */
+#define WM8993_IM_FLL_LOCK_EINT                 0x0100  /* IM_FLL_LOCK_EINT */
+#define WM8993_IM_FLL_LOCK_EINT_MASK            0x0100  /* IM_FLL_LOCK_EINT */
+#define WM8993_IM_FLL_LOCK_EINT_SHIFT                8  /* IM_FLL_LOCK_EINT */
+#define WM8993_IM_FLL_LOCK_EINT_WIDTH                1  /* IM_FLL_LOCK_EINT */
+#define WM8993_IM_GPI8_EINT                     0x0040  /* IM_GPI8_EINT */
+#define WM8993_IM_GPI8_EINT_MASK                0x0040  /* IM_GPI8_EINT */
+#define WM8993_IM_GPI8_EINT_SHIFT                    6  /* IM_GPI8_EINT */
+#define WM8993_IM_GPI8_EINT_WIDTH                    1  /* IM_GPI8_EINT */
+#define WM8993_IM_GPIO1_EINT                    0x0020  /* IM_GPIO1_EINT */
+#define WM8993_IM_GPIO1_EINT_MASK               0x0020  /* IM_GPIO1_EINT */
+#define WM8993_IM_GPIO1_EINT_SHIFT                   5  /* IM_GPIO1_EINT */
+#define WM8993_IM_GPIO1_EINT_WIDTH                   1  /* IM_GPIO1_EINT */
+#define WM8993_GPI8_ENA                         0x0010  /* GPI8_ENA */
+#define WM8993_GPI8_ENA_MASK                    0x0010  /* GPI8_ENA */
+#define WM8993_GPI8_ENA_SHIFT                        4  /* GPI8_ENA */
+#define WM8993_GPI8_ENA_WIDTH                        1  /* GPI8_ENA */
+#define WM8993_IM_GPI7_EINT                     0x0004  /* IM_GPI7_EINT */
+#define WM8993_IM_GPI7_EINT_MASK                0x0004  /* IM_GPI7_EINT */
+#define WM8993_IM_GPI7_EINT_SHIFT                    2  /* IM_GPI7_EINT */
+#define WM8993_IM_GPI7_EINT_WIDTH                    1  /* IM_GPI7_EINT */
+#define WM8993_IM_WSEQ_EINT                     0x0002  /* IM_WSEQ_EINT */
+#define WM8993_IM_WSEQ_EINT_MASK                0x0002  /* IM_WSEQ_EINT */
+#define WM8993_IM_WSEQ_EINT_SHIFT                    1  /* IM_WSEQ_EINT */
+#define WM8993_IM_WSEQ_EINT_WIDTH                    1  /* IM_WSEQ_EINT */
+#define WM8993_GPI7_ENA                         0x0001  /* GPI7_ENA */
+#define WM8993_GPI7_ENA_MASK                    0x0001  /* GPI7_ENA */
+#define WM8993_GPI7_ENA_SHIFT                        0  /* GPI7_ENA */
+#define WM8993_GPI7_ENA_WIDTH                        1  /* GPI7_ENA */
+
+/*
+ * R23 (0x17) - GPIO_POL
+ */
+#define WM8993_JD2_SC_POL                       0x8000  /* JD2_SC_POL */
+#define WM8993_JD2_SC_POL_MASK                  0x8000  /* JD2_SC_POL */
+#define WM8993_JD2_SC_POL_SHIFT                     15  /* JD2_SC_POL */
+#define WM8993_JD2_SC_POL_WIDTH                      1  /* JD2_SC_POL */
+#define WM8993_JD2_POL                          0x4000  /* JD2_POL */
+#define WM8993_JD2_POL_MASK                     0x4000  /* JD2_POL */
+#define WM8993_JD2_POL_SHIFT                        14  /* JD2_POL */
+#define WM8993_JD2_POL_WIDTH                         1  /* JD2_POL */
+#define WM8993_WSEQ_POL                         0x2000  /* WSEQ_POL */
+#define WM8993_WSEQ_POL_MASK                    0x2000  /* WSEQ_POL */
+#define WM8993_WSEQ_POL_SHIFT                       13  /* WSEQ_POL */
+#define WM8993_WSEQ_POL_WIDTH                        1  /* WSEQ_POL */
+#define WM8993_IRQ_POL                          0x1000  /* IRQ_POL */
+#define WM8993_IRQ_POL_MASK                     0x1000  /* IRQ_POL */
+#define WM8993_IRQ_POL_SHIFT                        12  /* IRQ_POL */
+#define WM8993_IRQ_POL_WIDTH                         1  /* IRQ_POL */
+#define WM8993_TEMPOK_POL                       0x0800  /* TEMPOK_POL */
+#define WM8993_TEMPOK_POL_MASK                  0x0800  /* TEMPOK_POL */
+#define WM8993_TEMPOK_POL_SHIFT                     11  /* TEMPOK_POL */
+#define WM8993_TEMPOK_POL_WIDTH                      1  /* TEMPOK_POL */
+#define WM8993_JD1_SC_POL                       0x0400  /* JD1_SC_POL */
+#define WM8993_JD1_SC_POL_MASK                  0x0400  /* JD1_SC_POL */
+#define WM8993_JD1_SC_POL_SHIFT                     10  /* JD1_SC_POL */
+#define WM8993_JD1_SC_POL_WIDTH                      1  /* JD1_SC_POL */
+#define WM8993_JD1_POL                          0x0200  /* JD1_POL */
+#define WM8993_JD1_POL_MASK                     0x0200  /* JD1_POL */
+#define WM8993_JD1_POL_SHIFT                         9  /* JD1_POL */
+#define WM8993_JD1_POL_WIDTH                         1  /* JD1_POL */
+#define WM8993_FLL_LOCK_POL                     0x0100  /* FLL_LOCK_POL */
+#define WM8993_FLL_LOCK_POL_MASK                0x0100  /* FLL_LOCK_POL */
+#define WM8993_FLL_LOCK_POL_SHIFT                    8  /* FLL_LOCK_POL */
+#define WM8993_FLL_LOCK_POL_WIDTH                    1  /* FLL_LOCK_POL */
+#define WM8993_GPI8_POL                         0x0080  /* GPI8_POL */
+#define WM8993_GPI8_POL_MASK                    0x0080  /* GPI8_POL */
+#define WM8993_GPI8_POL_SHIFT                        7  /* GPI8_POL */
+#define WM8993_GPI8_POL_WIDTH                        1  /* GPI8_POL */
+#define WM8993_GPI7_POL                         0x0040  /* GPI7_POL */
+#define WM8993_GPI7_POL_MASK                    0x0040  /* GPI7_POL */
+#define WM8993_GPI7_POL_SHIFT                        6  /* GPI7_POL */
+#define WM8993_GPI7_POL_WIDTH                        1  /* GPI7_POL */
+#define WM8993_GPIO1_POL                        0x0001  /* GPIO1_POL */
+#define WM8993_GPIO1_POL_MASK                   0x0001  /* GPIO1_POL */
+#define WM8993_GPIO1_POL_SHIFT                       0  /* GPIO1_POL */
+#define WM8993_GPIO1_POL_WIDTH                       1  /* GPIO1_POL */
+
+/*
+ * R24 (0x18) - Left Line Input 1&2 Volume
+ */
+#define WM8993_IN1_VU                           0x0100  /* IN1_VU */
+#define WM8993_IN1_VU_MASK                      0x0100  /* IN1_VU */
+#define WM8993_IN1_VU_SHIFT                          8  /* IN1_VU */
+#define WM8993_IN1_VU_WIDTH                          1  /* IN1_VU */
+#define WM8993_IN1L_MUTE                        0x0080  /* IN1L_MUTE */
+#define WM8993_IN1L_MUTE_MASK                   0x0080  /* IN1L_MUTE */
+#define WM8993_IN1L_MUTE_SHIFT                       7  /* IN1L_MUTE */
+#define WM8993_IN1L_MUTE_WIDTH                       1  /* IN1L_MUTE */
+#define WM8993_IN1L_ZC                          0x0040  /* IN1L_ZC */
+#define WM8993_IN1L_ZC_MASK                     0x0040  /* IN1L_ZC */
+#define WM8993_IN1L_ZC_SHIFT                         6  /* IN1L_ZC */
+#define WM8993_IN1L_ZC_WIDTH                         1  /* IN1L_ZC */
+#define WM8993_IN1L_VOL_MASK                    0x001F  /* IN1L_VOL - [4:0] */
+#define WM8993_IN1L_VOL_SHIFT                        0  /* IN1L_VOL - [4:0] */
+#define WM8993_IN1L_VOL_WIDTH                        5  /* IN1L_VOL - [4:0] */
+
+/*
+ * R25 (0x19) - Left Line Input 3&4 Volume
+ */
+#define WM8993_IN2_VU                           0x0100  /* IN2_VU */
+#define WM8993_IN2_VU_MASK                      0x0100  /* IN2_VU */
+#define WM8993_IN2_VU_SHIFT                          8  /* IN2_VU */
+#define WM8993_IN2_VU_WIDTH                          1  /* IN2_VU */
+#define WM8993_IN2L_MUTE                        0x0080  /* IN2L_MUTE */
+#define WM8993_IN2L_MUTE_MASK                   0x0080  /* IN2L_MUTE */
+#define WM8993_IN2L_MUTE_SHIFT                       7  /* IN2L_MUTE */
+#define WM8993_IN2L_MUTE_WIDTH                       1  /* IN2L_MUTE */
+#define WM8993_IN2L_ZC                          0x0040  /* IN2L_ZC */
+#define WM8993_IN2L_ZC_MASK                     0x0040  /* IN2L_ZC */
+#define WM8993_IN2L_ZC_SHIFT                         6  /* IN2L_ZC */
+#define WM8993_IN2L_ZC_WIDTH                         1  /* IN2L_ZC */
+#define WM8993_IN2L_VOL_MASK                    0x001F  /* IN2L_VOL - [4:0] */
+#define WM8993_IN2L_VOL_SHIFT                        0  /* IN2L_VOL - [4:0] */
+#define WM8993_IN2L_VOL_WIDTH                        5  /* IN2L_VOL - [4:0] */
+
+/*
+ * R26 (0x1A) - Right Line Input 1&2 Volume
+ */
+#define WM8993_IN1_VU                           0x0100  /* IN1_VU */
+#define WM8993_IN1_VU_MASK                      0x0100  /* IN1_VU */
+#define WM8993_IN1_VU_SHIFT                          8  /* IN1_VU */
+#define WM8993_IN1_VU_WIDTH                          1  /* IN1_VU */
+#define WM8993_IN1R_MUTE                        0x0080  /* IN1R_MUTE */
+#define WM8993_IN1R_MUTE_MASK                   0x0080  /* IN1R_MUTE */
+#define WM8993_IN1R_MUTE_SHIFT                       7  /* IN1R_MUTE */
+#define WM8993_IN1R_MUTE_WIDTH                       1  /* IN1R_MUTE */
+#define WM8993_IN1R_ZC                          0x0040  /* IN1R_ZC */
+#define WM8993_IN1R_ZC_MASK                     0x0040  /* IN1R_ZC */
+#define WM8993_IN1R_ZC_SHIFT                         6  /* IN1R_ZC */
+#define WM8993_IN1R_ZC_WIDTH                         1  /* IN1R_ZC */
+#define WM8993_IN1R_VOL_MASK                    0x001F  /* IN1R_VOL - [4:0] */
+#define WM8993_IN1R_VOL_SHIFT                        0  /* IN1R_VOL - [4:0] */
+#define WM8993_IN1R_VOL_WIDTH                        5  /* IN1R_VOL - [4:0] */
+
+/*
+ * R27 (0x1B) - Right Line Input 3&4 Volume
+ */
+#define WM8993_IN2_VU                           0x0100  /* IN2_VU */
+#define WM8993_IN2_VU_MASK                      0x0100  /* IN2_VU */
+#define WM8993_IN2_VU_SHIFT                          8  /* IN2_VU */
+#define WM8993_IN2_VU_WIDTH                          1  /* IN2_VU */
+#define WM8993_IN2R_MUTE                        0x0080  /* IN2R_MUTE */
+#define WM8993_IN2R_MUTE_MASK                   0x0080  /* IN2R_MUTE */
+#define WM8993_IN2R_MUTE_SHIFT                       7  /* IN2R_MUTE */
+#define WM8993_IN2R_MUTE_WIDTH                       1  /* IN2R_MUTE */
+#define WM8993_IN2R_ZC                          0x0040  /* IN2R_ZC */
+#define WM8993_IN2R_ZC_MASK                     0x0040  /* IN2R_ZC */
+#define WM8993_IN2R_ZC_SHIFT                         6  /* IN2R_ZC */
+#define WM8993_IN2R_ZC_WIDTH                         1  /* IN2R_ZC */
+#define WM8993_IN2R_VOL_MASK                    0x001F  /* IN2R_VOL - [4:0] */
+#define WM8993_IN2R_VOL_SHIFT                        0  /* IN2R_VOL - [4:0] */
+#define WM8993_IN2R_VOL_WIDTH                        5  /* IN2R_VOL - [4:0] */
+
+/*
+ * R28 (0x1C) - Left Output Volume
+ */
+#define WM8993_HPOUT1_VU                        0x0100  /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_MASK                   0x0100  /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_SHIFT                       8  /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_WIDTH                       1  /* HPOUT1_VU */
+#define WM8993_HPOUT1L_ZC                       0x0080  /* HPOUT1L_ZC */
+#define WM8993_HPOUT1L_ZC_MASK                  0x0080  /* HPOUT1L_ZC */
+#define WM8993_HPOUT1L_ZC_SHIFT                      7  /* HPOUT1L_ZC */
+#define WM8993_HPOUT1L_ZC_WIDTH                      1  /* HPOUT1L_ZC */
+#define WM8993_HPOUT1L_MUTE_N                   0x0040  /* HPOUT1L_MUTE_N */
+#define WM8993_HPOUT1L_MUTE_N_MASK              0x0040  /* HPOUT1L_MUTE_N */
+#define WM8993_HPOUT1L_MUTE_N_SHIFT                  6  /* HPOUT1L_MUTE_N */
+#define WM8993_HPOUT1L_MUTE_N_WIDTH                  1  /* HPOUT1L_MUTE_N */
+#define WM8993_HPOUT1L_VOL_MASK                 0x003F  /* HPOUT1L_VOL - [5:0] */
+#define WM8993_HPOUT1L_VOL_SHIFT                     0  /* HPOUT1L_VOL - [5:0] */
+#define WM8993_HPOUT1L_VOL_WIDTH                     6  /* HPOUT1L_VOL - [5:0] */
+
+/*
+ * R29 (0x1D) - Right Output Volume
+ */
+#define WM8993_HPOUT1_VU                        0x0100  /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_MASK                   0x0100  /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_SHIFT                       8  /* HPOUT1_VU */
+#define WM8993_HPOUT1_VU_WIDTH                       1  /* HPOUT1_VU */
+#define WM8993_HPOUT1R_ZC                       0x0080  /* HPOUT1R_ZC */
+#define WM8993_HPOUT1R_ZC_MASK                  0x0080  /* HPOUT1R_ZC */
+#define WM8993_HPOUT1R_ZC_SHIFT                      7  /* HPOUT1R_ZC */
+#define WM8993_HPOUT1R_ZC_WIDTH                      1  /* HPOUT1R_ZC */
+#define WM8993_HPOUT1R_MUTE_N                   0x0040  /* HPOUT1R_MUTE_N */
+#define WM8993_HPOUT1R_MUTE_N_MASK              0x0040  /* HPOUT1R_MUTE_N */
+#define WM8993_HPOUT1R_MUTE_N_SHIFT                  6  /* HPOUT1R_MUTE_N */
+#define WM8993_HPOUT1R_MUTE_N_WIDTH                  1  /* HPOUT1R_MUTE_N */
+#define WM8993_HPOUT1R_VOL_MASK                 0x003F  /* HPOUT1R_VOL - [5:0] */
+#define WM8993_HPOUT1R_VOL_SHIFT                     0  /* HPOUT1R_VOL - [5:0] */
+#define WM8993_HPOUT1R_VOL_WIDTH                     6  /* HPOUT1R_VOL - [5:0] */
+
+/*
+ * R30 (0x1E) - Line Outputs Volume
+ */
+#define WM8993_LINEOUT1N_MUTE                   0x0040  /* LINEOUT1N_MUTE */
+#define WM8993_LINEOUT1N_MUTE_MASK              0x0040  /* LINEOUT1N_MUTE */
+#define WM8993_LINEOUT1N_MUTE_SHIFT                  6  /* LINEOUT1N_MUTE */
+#define WM8993_LINEOUT1N_MUTE_WIDTH                  1  /* LINEOUT1N_MUTE */
+#define WM8993_LINEOUT1P_MUTE                   0x0020  /* LINEOUT1P_MUTE */
+#define WM8993_LINEOUT1P_MUTE_MASK              0x0020  /* LINEOUT1P_MUTE */
+#define WM8993_LINEOUT1P_MUTE_SHIFT                  5  /* LINEOUT1P_MUTE */
+#define WM8993_LINEOUT1P_MUTE_WIDTH                  1  /* LINEOUT1P_MUTE */
+#define WM8993_LINEOUT1_VOL                     0x0010  /* LINEOUT1_VOL */
+#define WM8993_LINEOUT1_VOL_MASK                0x0010  /* LINEOUT1_VOL */
+#define WM8993_LINEOUT1_VOL_SHIFT                    4  /* LINEOUT1_VOL */
+#define WM8993_LINEOUT1_VOL_WIDTH                    1  /* LINEOUT1_VOL */
+#define WM8993_LINEOUT2N_MUTE                   0x0004  /* LINEOUT2N_MUTE */
+#define WM8993_LINEOUT2N_MUTE_MASK              0x0004  /* LINEOUT2N_MUTE */
+#define WM8993_LINEOUT2N_MUTE_SHIFT                  2  /* LINEOUT2N_MUTE */
+#define WM8993_LINEOUT2N_MUTE_WIDTH                  1  /* LINEOUT2N_MUTE */
+#define WM8993_LINEOUT2P_MUTE                   0x0002  /* LINEOUT2P_MUTE */
+#define WM8993_LINEOUT2P_MUTE_MASK              0x0002  /* LINEOUT2P_MUTE */
+#define WM8993_LINEOUT2P_MUTE_SHIFT                  1  /* LINEOUT2P_MUTE */
+#define WM8993_LINEOUT2P_MUTE_WIDTH                  1  /* LINEOUT2P_MUTE */
+#define WM8993_LINEOUT2_VOL                     0x0001  /* LINEOUT2_VOL */
+#define WM8993_LINEOUT2_VOL_MASK                0x0001  /* LINEOUT2_VOL */
+#define WM8993_LINEOUT2_VOL_SHIFT                    0  /* LINEOUT2_VOL */
+#define WM8993_LINEOUT2_VOL_WIDTH                    1  /* LINEOUT2_VOL */
+
+/*
+ * R31 (0x1F) - HPOUT2 Volume
+ */
+#define WM8993_HPOUT2_MUTE                      0x0020  /* HPOUT2_MUTE */
+#define WM8993_HPOUT2_MUTE_MASK                 0x0020  /* HPOUT2_MUTE */
+#define WM8993_HPOUT2_MUTE_SHIFT                     5  /* HPOUT2_MUTE */
+#define WM8993_HPOUT2_MUTE_WIDTH                     1  /* HPOUT2_MUTE */
+#define WM8993_HPOUT2_VOL                       0x0010  /* HPOUT2_VOL */
+#define WM8993_HPOUT2_VOL_MASK                  0x0010  /* HPOUT2_VOL */
+#define WM8993_HPOUT2_VOL_SHIFT                      4  /* HPOUT2_VOL */
+#define WM8993_HPOUT2_VOL_WIDTH                      1  /* HPOUT2_VOL */
+
+/*
+ * R32 (0x20) - Left OPGA Volume
+ */
+#define WM8993_MIXOUT_VU                        0x0100  /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_MASK                   0x0100  /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_SHIFT                       8  /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_WIDTH                       1  /* MIXOUT_VU */
+#define WM8993_MIXOUTL_ZC                       0x0080  /* MIXOUTL_ZC */
+#define WM8993_MIXOUTL_ZC_MASK                  0x0080  /* MIXOUTL_ZC */
+#define WM8993_MIXOUTL_ZC_SHIFT                      7  /* MIXOUTL_ZC */
+#define WM8993_MIXOUTL_ZC_WIDTH                      1  /* MIXOUTL_ZC */
+#define WM8993_MIXOUTL_MUTE_N                   0x0040  /* MIXOUTL_MUTE_N */
+#define WM8993_MIXOUTL_MUTE_N_MASK              0x0040  /* MIXOUTL_MUTE_N */
+#define WM8993_MIXOUTL_MUTE_N_SHIFT                  6  /* MIXOUTL_MUTE_N */
+#define WM8993_MIXOUTL_MUTE_N_WIDTH                  1  /* MIXOUTL_MUTE_N */
+#define WM8993_MIXOUTL_VOL_MASK                 0x003F  /* MIXOUTL_VOL - [5:0] */
+#define WM8993_MIXOUTL_VOL_SHIFT                     0  /* MIXOUTL_VOL - [5:0] */
+#define WM8993_MIXOUTL_VOL_WIDTH                     6  /* MIXOUTL_VOL - [5:0] */
+
+/*
+ * R33 (0x21) - Right OPGA Volume
+ */
+#define WM8993_MIXOUT_VU                        0x0100  /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_MASK                   0x0100  /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_SHIFT                       8  /* MIXOUT_VU */
+#define WM8993_MIXOUT_VU_WIDTH                       1  /* MIXOUT_VU */
+#define WM8993_MIXOUTR_ZC                       0x0080  /* MIXOUTR_ZC */
+#define WM8993_MIXOUTR_ZC_MASK                  0x0080  /* MIXOUTR_ZC */
+#define WM8993_MIXOUTR_ZC_SHIFT                      7  /* MIXOUTR_ZC */
+#define WM8993_MIXOUTR_ZC_WIDTH                      1  /* MIXOUTR_ZC */
+#define WM8993_MIXOUTR_MUTE_N                   0x0040  /* MIXOUTR_MUTE_N */
+#define WM8993_MIXOUTR_MUTE_N_MASK              0x0040  /* MIXOUTR_MUTE_N */
+#define WM8993_MIXOUTR_MUTE_N_SHIFT                  6  /* MIXOUTR_MUTE_N */
+#define WM8993_MIXOUTR_MUTE_N_WIDTH                  1  /* MIXOUTR_MUTE_N */
+#define WM8993_MIXOUTR_VOL_MASK                 0x003F  /* MIXOUTR_VOL - [5:0] */
+#define WM8993_MIXOUTR_VOL_SHIFT                     0  /* MIXOUTR_VOL - [5:0] */
+#define WM8993_MIXOUTR_VOL_WIDTH                     6  /* MIXOUTR_VOL - [5:0] */
+
+/*
+ * R34 (0x22) - SPKMIXL Attenuation
+ */
+#define WM8993_MIXINL_SPKMIXL_VOL               0x0020  /* MIXINL_SPKMIXL_VOL */
+#define WM8993_MIXINL_SPKMIXL_VOL_MASK          0x0020  /* MIXINL_SPKMIXL_VOL */
+#define WM8993_MIXINL_SPKMIXL_VOL_SHIFT              5  /* MIXINL_SPKMIXL_VOL */
+#define WM8993_MIXINL_SPKMIXL_VOL_WIDTH              1  /* MIXINL_SPKMIXL_VOL */
+#define WM8993_IN1LP_SPKMIXL_VOL                0x0010  /* IN1LP_SPKMIXL_VOL */
+#define WM8993_IN1LP_SPKMIXL_VOL_MASK           0x0010  /* IN1LP_SPKMIXL_VOL */
+#define WM8993_IN1LP_SPKMIXL_VOL_SHIFT               4  /* IN1LP_SPKMIXL_VOL */
+#define WM8993_IN1LP_SPKMIXL_VOL_WIDTH               1  /* IN1LP_SPKMIXL_VOL */
+#define WM8993_MIXOUTL_SPKMIXL_VOL              0x0008  /* MIXOUTL_SPKMIXL_VOL */
+#define WM8993_MIXOUTL_SPKMIXL_VOL_MASK         0x0008  /* MIXOUTL_SPKMIXL_VOL */
+#define WM8993_MIXOUTL_SPKMIXL_VOL_SHIFT             3  /* MIXOUTL_SPKMIXL_VOL */
+#define WM8993_MIXOUTL_SPKMIXL_VOL_WIDTH             1  /* MIXOUTL_SPKMIXL_VOL */
+#define WM8993_DACL_SPKMIXL_VOL                 0x0004  /* DACL_SPKMIXL_VOL */
+#define WM8993_DACL_SPKMIXL_VOL_MASK            0x0004  /* DACL_SPKMIXL_VOL */
+#define WM8993_DACL_SPKMIXL_VOL_SHIFT                2  /* DACL_SPKMIXL_VOL */
+#define WM8993_DACL_SPKMIXL_VOL_WIDTH                1  /* DACL_SPKMIXL_VOL */
+#define WM8993_SPKMIXL_VOL_MASK                 0x0003  /* SPKMIXL_VOL - [1:0] */
+#define WM8993_SPKMIXL_VOL_SHIFT                     0  /* SPKMIXL_VOL - [1:0] */
+#define WM8993_SPKMIXL_VOL_WIDTH                     2  /* SPKMIXL_VOL - [1:0] */
+
+/*
+ * R35 (0x23) - SPKMIXR Attenuation
+ */
+#define WM8993_SPKOUT_CLASSAB_MODE              0x0100  /* SPKOUT_CLASSAB_MODE */
+#define WM8993_SPKOUT_CLASSAB_MODE_MASK         0x0100  /* SPKOUT_CLASSAB_MODE */
+#define WM8993_SPKOUT_CLASSAB_MODE_SHIFT             8  /* SPKOUT_CLASSAB_MODE */
+#define WM8993_SPKOUT_CLASSAB_MODE_WIDTH             1  /* SPKOUT_CLASSAB_MODE */
+#define WM8993_MIXINR_SPKMIXR_VOL               0x0020  /* MIXINR_SPKMIXR_VOL */
+#define WM8993_MIXINR_SPKMIXR_VOL_MASK          0x0020  /* MIXINR_SPKMIXR_VOL */
+#define WM8993_MIXINR_SPKMIXR_VOL_SHIFT              5  /* MIXINR_SPKMIXR_VOL */
+#define WM8993_MIXINR_SPKMIXR_VOL_WIDTH              1  /* MIXINR_SPKMIXR_VOL */
+#define WM8993_IN1RP_SPKMIXR_VOL                0x0010  /* IN1RP_SPKMIXR_VOL */
+#define WM8993_IN1RP_SPKMIXR_VOL_MASK           0x0010  /* IN1RP_SPKMIXR_VOL */
+#define WM8993_IN1RP_SPKMIXR_VOL_SHIFT               4  /* IN1RP_SPKMIXR_VOL */
+#define WM8993_IN1RP_SPKMIXR_VOL_WIDTH               1  /* IN1RP_SPKMIXR_VOL */
+#define WM8993_MIXOUTR_SPKMIXR_VOL              0x0008  /* MIXOUTR_SPKMIXR_VOL */
+#define WM8993_MIXOUTR_SPKMIXR_VOL_MASK         0x0008  /* MIXOUTR_SPKMIXR_VOL */
+#define WM8993_MIXOUTR_SPKMIXR_VOL_SHIFT             3  /* MIXOUTR_SPKMIXR_VOL */
+#define WM8993_MIXOUTR_SPKMIXR_VOL_WIDTH             1  /* MIXOUTR_SPKMIXR_VOL */
+#define WM8993_DACR_SPKMIXR_VOL                 0x0004  /* DACR_SPKMIXR_VOL */
+#define WM8993_DACR_SPKMIXR_VOL_MASK            0x0004  /* DACR_SPKMIXR_VOL */
+#define WM8993_DACR_SPKMIXR_VOL_SHIFT                2  /* DACR_SPKMIXR_VOL */
+#define WM8993_DACR_SPKMIXR_VOL_WIDTH                1  /* DACR_SPKMIXR_VOL */
+#define WM8993_SPKMIXR_VOL_MASK                 0x0003  /* SPKMIXR_VOL - [1:0] */
+#define WM8993_SPKMIXR_VOL_SHIFT                     0  /* SPKMIXR_VOL - [1:0] */
+#define WM8993_SPKMIXR_VOL_WIDTH                     2  /* SPKMIXR_VOL - [1:0] */
+
+/*
+ * R36 (0x24) - SPKOUT Mixers
+ */
+#define WM8993_VRX_TO_SPKOUTL                   0x0020  /* VRX_TO_SPKOUTL */
+#define WM8993_VRX_TO_SPKOUTL_MASK              0x0020  /* VRX_TO_SPKOUTL */
+#define WM8993_VRX_TO_SPKOUTL_SHIFT                  5  /* VRX_TO_SPKOUTL */
+#define WM8993_VRX_TO_SPKOUTL_WIDTH                  1  /* VRX_TO_SPKOUTL */
+#define WM8993_SPKMIXL_TO_SPKOUTL               0x0010  /* SPKMIXL_TO_SPKOUTL */
+#define WM8993_SPKMIXL_TO_SPKOUTL_MASK          0x0010  /* SPKMIXL_TO_SPKOUTL */
+#define WM8993_SPKMIXL_TO_SPKOUTL_SHIFT              4  /* SPKMIXL_TO_SPKOUTL */
+#define WM8993_SPKMIXL_TO_SPKOUTL_WIDTH              1  /* SPKMIXL_TO_SPKOUTL */
+#define WM8993_SPKMIXR_TO_SPKOUTL               0x0008  /* SPKMIXR_TO_SPKOUTL */
+#define WM8993_SPKMIXR_TO_SPKOUTL_MASK          0x0008  /* SPKMIXR_TO_SPKOUTL */
+#define WM8993_SPKMIXR_TO_SPKOUTL_SHIFT              3  /* SPKMIXR_TO_SPKOUTL */
+#define WM8993_SPKMIXR_TO_SPKOUTL_WIDTH              1  /* SPKMIXR_TO_SPKOUTL */
+#define WM8993_VRX_TO_SPKOUTR                   0x0004  /* VRX_TO_SPKOUTR */
+#define WM8993_VRX_TO_SPKOUTR_MASK              0x0004  /* VRX_TO_SPKOUTR */
+#define WM8993_VRX_TO_SPKOUTR_SHIFT                  2  /* VRX_TO_SPKOUTR */
+#define WM8993_VRX_TO_SPKOUTR_WIDTH                  1  /* VRX_TO_SPKOUTR */
+#define WM8993_SPKMIXL_TO_SPKOUTR               0x0002  /* SPKMIXL_TO_SPKOUTR */
+#define WM8993_SPKMIXL_TO_SPKOUTR_MASK          0x0002  /* SPKMIXL_TO_SPKOUTR */
+#define WM8993_SPKMIXL_TO_SPKOUTR_SHIFT              1  /* SPKMIXL_TO_SPKOUTR */
+#define WM8993_SPKMIXL_TO_SPKOUTR_WIDTH              1  /* SPKMIXL_TO_SPKOUTR */
+#define WM8993_SPKMIXR_TO_SPKOUTR               0x0001  /* SPKMIXR_TO_SPKOUTR */
+#define WM8993_SPKMIXR_TO_SPKOUTR_MASK          0x0001  /* SPKMIXR_TO_SPKOUTR */
+#define WM8993_SPKMIXR_TO_SPKOUTR_SHIFT              0  /* SPKMIXR_TO_SPKOUTR */
+#define WM8993_SPKMIXR_TO_SPKOUTR_WIDTH              1  /* SPKMIXR_TO_SPKOUTR */
+
+/*
+ * R37 (0x25) - SPKOUT Boost
+ */
+#define WM8993_SPKOUTL_BOOST_MASK               0x0038  /* SPKOUTL_BOOST - [5:3] */
+#define WM8993_SPKOUTL_BOOST_SHIFT                   3  /* SPKOUTL_BOOST - [5:3] */
+#define WM8993_SPKOUTL_BOOST_WIDTH                   3  /* SPKOUTL_BOOST - [5:3] */
+#define WM8993_SPKOUTR_BOOST_MASK               0x0007  /* SPKOUTR_BOOST - [2:0] */
+#define WM8993_SPKOUTR_BOOST_SHIFT                   0  /* SPKOUTR_BOOST - [2:0] */
+#define WM8993_SPKOUTR_BOOST_WIDTH                   3  /* SPKOUTR_BOOST - [2:0] */
+
+/*
+ * R38 (0x26) - Speaker Volume Left
+ */
+#define WM8993_SPKOUT_VU                        0x0100  /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_MASK                   0x0100  /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_SHIFT                       8  /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_WIDTH                       1  /* SPKOUT_VU */
+#define WM8993_SPKOUTL_ZC                       0x0080  /* SPKOUTL_ZC */
+#define WM8993_SPKOUTL_ZC_MASK                  0x0080  /* SPKOUTL_ZC */
+#define WM8993_SPKOUTL_ZC_SHIFT                      7  /* SPKOUTL_ZC */
+#define WM8993_SPKOUTL_ZC_WIDTH                      1  /* SPKOUTL_ZC */
+#define WM8993_SPKOUTL_MUTE_N                   0x0040  /* SPKOUTL_MUTE_N */
+#define WM8993_SPKOUTL_MUTE_N_MASK              0x0040  /* SPKOUTL_MUTE_N */
+#define WM8993_SPKOUTL_MUTE_N_SHIFT                  6  /* SPKOUTL_MUTE_N */
+#define WM8993_SPKOUTL_MUTE_N_WIDTH                  1  /* SPKOUTL_MUTE_N */
+#define WM8993_SPKOUTL_VOL_MASK                 0x003F  /* SPKOUTL_VOL - [5:0] */
+#define WM8993_SPKOUTL_VOL_SHIFT                     0  /* SPKOUTL_VOL - [5:0] */
+#define WM8993_SPKOUTL_VOL_WIDTH                     6  /* SPKOUTL_VOL - [5:0] */
+
+/*
+ * R39 (0x27) - Speaker Volume Right
+ */
+#define WM8993_SPKOUT_VU                        0x0100  /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_MASK                   0x0100  /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_SHIFT                       8  /* SPKOUT_VU */
+#define WM8993_SPKOUT_VU_WIDTH                       1  /* SPKOUT_VU */
+#define WM8993_SPKOUTR_ZC                       0x0080  /* SPKOUTR_ZC */
+#define WM8993_SPKOUTR_ZC_MASK                  0x0080  /* SPKOUTR_ZC */
+#define WM8993_SPKOUTR_ZC_SHIFT                      7  /* SPKOUTR_ZC */
+#define WM8993_SPKOUTR_ZC_WIDTH                      1  /* SPKOUTR_ZC */
+#define WM8993_SPKOUTR_MUTE_N                   0x0040  /* SPKOUTR_MUTE_N */
+#define WM8993_SPKOUTR_MUTE_N_MASK              0x0040  /* SPKOUTR_MUTE_N */
+#define WM8993_SPKOUTR_MUTE_N_SHIFT                  6  /* SPKOUTR_MUTE_N */
+#define WM8993_SPKOUTR_MUTE_N_WIDTH                  1  /* SPKOUTR_MUTE_N */
+#define WM8993_SPKOUTR_VOL_MASK                 0x003F  /* SPKOUTR_VOL - [5:0] */
+#define WM8993_SPKOUTR_VOL_SHIFT                     0  /* SPKOUTR_VOL - [5:0] */
+#define WM8993_SPKOUTR_VOL_WIDTH                     6  /* SPKOUTR_VOL - [5:0] */
+
+/*
+ * R40 (0x28) - Input Mixer2
+ */
+#define WM8993_IN2LP_TO_IN2L                    0x0080  /* IN2LP_TO_IN2L */
+#define WM8993_IN2LP_TO_IN2L_MASK               0x0080  /* IN2LP_TO_IN2L */
+#define WM8993_IN2LP_TO_IN2L_SHIFT                   7  /* IN2LP_TO_IN2L */
+#define WM8993_IN2LP_TO_IN2L_WIDTH                   1  /* IN2LP_TO_IN2L */
+#define WM8993_IN2LN_TO_IN2L                    0x0040  /* IN2LN_TO_IN2L */
+#define WM8993_IN2LN_TO_IN2L_MASK               0x0040  /* IN2LN_TO_IN2L */
+#define WM8993_IN2LN_TO_IN2L_SHIFT                   6  /* IN2LN_TO_IN2L */
+#define WM8993_IN2LN_TO_IN2L_WIDTH                   1  /* IN2LN_TO_IN2L */
+#define WM8993_IN1LP_TO_IN1L                    0x0020  /* IN1LP_TO_IN1L */
+#define WM8993_IN1LP_TO_IN1L_MASK               0x0020  /* IN1LP_TO_IN1L */
+#define WM8993_IN1LP_TO_IN1L_SHIFT                   5  /* IN1LP_TO_IN1L */
+#define WM8993_IN1LP_TO_IN1L_WIDTH                   1  /* IN1LP_TO_IN1L */
+#define WM8993_IN1LN_TO_IN1L                    0x0010  /* IN1LN_TO_IN1L */
+#define WM8993_IN1LN_TO_IN1L_MASK               0x0010  /* IN1LN_TO_IN1L */
+#define WM8993_IN1LN_TO_IN1L_SHIFT                   4  /* IN1LN_TO_IN1L */
+#define WM8993_IN1LN_TO_IN1L_WIDTH                   1  /* IN1LN_TO_IN1L */
+#define WM8993_IN2RP_TO_IN2R                    0x0008  /* IN2RP_TO_IN2R */
+#define WM8993_IN2RP_TO_IN2R_MASK               0x0008  /* IN2RP_TO_IN2R */
+#define WM8993_IN2RP_TO_IN2R_SHIFT                   3  /* IN2RP_TO_IN2R */
+#define WM8993_IN2RP_TO_IN2R_WIDTH                   1  /* IN2RP_TO_IN2R */
+#define WM8993_IN2RN_TO_IN2R                    0x0004  /* IN2RN_TO_IN2R */
+#define WM8993_IN2RN_TO_IN2R_MASK               0x0004  /* IN2RN_TO_IN2R */
+#define WM8993_IN2RN_TO_IN2R_SHIFT                   2  /* IN2RN_TO_IN2R */
+#define WM8993_IN2RN_TO_IN2R_WIDTH                   1  /* IN2RN_TO_IN2R */
+#define WM8993_IN1RP_TO_IN1R                    0x0002  /* IN1RP_TO_IN1R */
+#define WM8993_IN1RP_TO_IN1R_MASK               0x0002  /* IN1RP_TO_IN1R */
+#define WM8993_IN1RP_TO_IN1R_SHIFT                   1  /* IN1RP_TO_IN1R */
+#define WM8993_IN1RP_TO_IN1R_WIDTH                   1  /* IN1RP_TO_IN1R */
+#define WM8993_IN1RN_TO_IN1R                    0x0001  /* IN1RN_TO_IN1R */
+#define WM8993_IN1RN_TO_IN1R_MASK               0x0001  /* IN1RN_TO_IN1R */
+#define WM8993_IN1RN_TO_IN1R_SHIFT                   0  /* IN1RN_TO_IN1R */
+#define WM8993_IN1RN_TO_IN1R_WIDTH                   1  /* IN1RN_TO_IN1R */
+
+/*
+ * R41 (0x29) - Input Mixer3
+ */
+#define WM8993_IN2L_TO_MIXINL                   0x0100  /* IN2L_TO_MIXINL */
+#define WM8993_IN2L_TO_MIXINL_MASK              0x0100  /* IN2L_TO_MIXINL */
+#define WM8993_IN2L_TO_MIXINL_SHIFT                  8  /* IN2L_TO_MIXINL */
+#define WM8993_IN2L_TO_MIXINL_WIDTH                  1  /* IN2L_TO_MIXINL */
+#define WM8993_IN2L_MIXINL_VOL                  0x0080  /* IN2L_MIXINL_VOL */
+#define WM8993_IN2L_MIXINL_VOL_MASK             0x0080  /* IN2L_MIXINL_VOL */
+#define WM8993_IN2L_MIXINL_VOL_SHIFT                 7  /* IN2L_MIXINL_VOL */
+#define WM8993_IN2L_MIXINL_VOL_WIDTH                 1  /* IN2L_MIXINL_VOL */
+#define WM8993_IN1L_TO_MIXINL                   0x0020  /* IN1L_TO_MIXINL */
+#define WM8993_IN1L_TO_MIXINL_MASK              0x0020  /* IN1L_TO_MIXINL */
+#define WM8993_IN1L_TO_MIXINL_SHIFT                  5  /* IN1L_TO_MIXINL */
+#define WM8993_IN1L_TO_MIXINL_WIDTH                  1  /* IN1L_TO_MIXINL */
+#define WM8993_IN1L_MIXINL_VOL                  0x0010  /* IN1L_MIXINL_VOL */
+#define WM8993_IN1L_MIXINL_VOL_MASK             0x0010  /* IN1L_MIXINL_VOL */
+#define WM8993_IN1L_MIXINL_VOL_SHIFT                 4  /* IN1L_MIXINL_VOL */
+#define WM8993_IN1L_MIXINL_VOL_WIDTH                 1  /* IN1L_MIXINL_VOL */
+#define WM8993_MIXOUTL_MIXINL_VOL_MASK          0x0007  /* MIXOUTL_MIXINL_VOL - [2:0] */
+#define WM8993_MIXOUTL_MIXINL_VOL_SHIFT              0  /* MIXOUTL_MIXINL_VOL - [2:0] */
+#define WM8993_MIXOUTL_MIXINL_VOL_WIDTH              3  /* MIXOUTL_MIXINL_VOL - [2:0] */
+
+/*
+ * R42 (0x2A) - Input Mixer4
+ */
+#define WM8993_IN2R_TO_MIXINR                   0x0100  /* IN2R_TO_MIXINR */
+#define WM8993_IN2R_TO_MIXINR_MASK              0x0100  /* IN2R_TO_MIXINR */
+#define WM8993_IN2R_TO_MIXINR_SHIFT                  8  /* IN2R_TO_MIXINR */
+#define WM8993_IN2R_TO_MIXINR_WIDTH                  1  /* IN2R_TO_MIXINR */
+#define WM8993_IN2R_MIXINR_VOL                  0x0080  /* IN2R_MIXINR_VOL */
+#define WM8993_IN2R_MIXINR_VOL_MASK             0x0080  /* IN2R_MIXINR_VOL */
+#define WM8993_IN2R_MIXINR_VOL_SHIFT                 7  /* IN2R_MIXINR_VOL */
+#define WM8993_IN2R_MIXINR_VOL_WIDTH                 1  /* IN2R_MIXINR_VOL */
+#define WM8993_IN1R_TO_MIXINR                   0x0020  /* IN1R_TO_MIXINR */
+#define WM8993_IN1R_TO_MIXINR_MASK              0x0020  /* IN1R_TO_MIXINR */
+#define WM8993_IN1R_TO_MIXINR_SHIFT                  5  /* IN1R_TO_MIXINR */
+#define WM8993_IN1R_TO_MIXINR_WIDTH                  1  /* IN1R_TO_MIXINR */
+#define WM8993_IN1R_MIXINR_VOL                  0x0010  /* IN1R_MIXINR_VOL */
+#define WM8993_IN1R_MIXINR_VOL_MASK             0x0010  /* IN1R_MIXINR_VOL */
+#define WM8993_IN1R_MIXINR_VOL_SHIFT                 4  /* IN1R_MIXINR_VOL */
+#define WM8993_IN1R_MIXINR_VOL_WIDTH                 1  /* IN1R_MIXINR_VOL */
+#define WM8993_MIXOUTR_MIXINR_VOL_MASK          0x0007  /* MIXOUTR_MIXINR_VOL - [2:0] */
+#define WM8993_MIXOUTR_MIXINR_VOL_SHIFT              0  /* MIXOUTR_MIXINR_VOL - [2:0] */
+#define WM8993_MIXOUTR_MIXINR_VOL_WIDTH              3  /* MIXOUTR_MIXINR_VOL - [2:0] */
+
+/*
+ * R43 (0x2B) - Input Mixer5
+ */
+#define WM8993_IN1LP_MIXINL_VOL_MASK            0x01C0  /* IN1LP_MIXINL_VOL - [8:6] */
+#define WM8993_IN1LP_MIXINL_VOL_SHIFT                6  /* IN1LP_MIXINL_VOL - [8:6] */
+#define WM8993_IN1LP_MIXINL_VOL_WIDTH                3  /* IN1LP_MIXINL_VOL - [8:6] */
+#define WM8993_VRX_MIXINL_VOL_MASK              0x0007  /* VRX_MIXINL_VOL - [2:0] */
+#define WM8993_VRX_MIXINL_VOL_SHIFT                  0  /* VRX_MIXINL_VOL - [2:0] */
+#define WM8993_VRX_MIXINL_VOL_WIDTH                  3  /* VRX_MIXINL_VOL - [2:0] */
+
+/*
+ * R44 (0x2C) - Input Mixer6
+ */
+#define WM8993_IN1RP_MIXINR_VOL_MASK            0x01C0  /* IN1RP_MIXINR_VOL - [8:6] */
+#define WM8993_IN1RP_MIXINR_VOL_SHIFT                6  /* IN1RP_MIXINR_VOL - [8:6] */
+#define WM8993_IN1RP_MIXINR_VOL_WIDTH                3  /* IN1RP_MIXINR_VOL - [8:6] */
+#define WM8993_VRX_MIXINR_VOL_MASK              0x0007  /* VRX_MIXINR_VOL - [2:0] */
+#define WM8993_VRX_MIXINR_VOL_SHIFT                  0  /* VRX_MIXINR_VOL - [2:0] */
+#define WM8993_VRX_MIXINR_VOL_WIDTH                  3  /* VRX_MIXINR_VOL - [2:0] */
+
+/*
+ * R45 (0x2D) - Output Mixer1
+ */
+#define WM8993_DACL_TO_HPOUT1L                  0x0100  /* DACL_TO_HPOUT1L */
+#define WM8993_DACL_TO_HPOUT1L_MASK             0x0100  /* DACL_TO_HPOUT1L */
+#define WM8993_DACL_TO_HPOUT1L_SHIFT                 8  /* DACL_TO_HPOUT1L */
+#define WM8993_DACL_TO_HPOUT1L_WIDTH                 1  /* DACL_TO_HPOUT1L */
+#define WM8993_MIXINR_TO_MIXOUTL                0x0080  /* MIXINR_TO_MIXOUTL */
+#define WM8993_MIXINR_TO_MIXOUTL_MASK           0x0080  /* MIXINR_TO_MIXOUTL */
+#define WM8993_MIXINR_TO_MIXOUTL_SHIFT               7  /* MIXINR_TO_MIXOUTL */
+#define WM8993_MIXINR_TO_MIXOUTL_WIDTH               1  /* MIXINR_TO_MIXOUTL */
+#define WM8993_MIXINL_TO_MIXOUTL                0x0040  /* MIXINL_TO_MIXOUTL */
+#define WM8993_MIXINL_TO_MIXOUTL_MASK           0x0040  /* MIXINL_TO_MIXOUTL */
+#define WM8993_MIXINL_TO_MIXOUTL_SHIFT               6  /* MIXINL_TO_MIXOUTL */
+#define WM8993_MIXINL_TO_MIXOUTL_WIDTH               1  /* MIXINL_TO_MIXOUTL */
+#define WM8993_IN2RN_TO_MIXOUTL                 0x0020  /* IN2RN_TO_MIXOUTL */
+#define WM8993_IN2RN_TO_MIXOUTL_MASK            0x0020  /* IN2RN_TO_MIXOUTL */
+#define WM8993_IN2RN_TO_MIXOUTL_SHIFT                5  /* IN2RN_TO_MIXOUTL */
+#define WM8993_IN2RN_TO_MIXOUTL_WIDTH                1  /* IN2RN_TO_MIXOUTL */
+#define WM8993_IN2LN_TO_MIXOUTL                 0x0010  /* IN2LN_TO_MIXOUTL */
+#define WM8993_IN2LN_TO_MIXOUTL_MASK            0x0010  /* IN2LN_TO_MIXOUTL */
+#define WM8993_IN2LN_TO_MIXOUTL_SHIFT                4  /* IN2LN_TO_MIXOUTL */
+#define WM8993_IN2LN_TO_MIXOUTL_WIDTH                1  /* IN2LN_TO_MIXOUTL */
+#define WM8993_IN1R_TO_MIXOUTL                  0x0008  /* IN1R_TO_MIXOUTL */
+#define WM8993_IN1R_TO_MIXOUTL_MASK             0x0008  /* IN1R_TO_MIXOUTL */
+#define WM8993_IN1R_TO_MIXOUTL_SHIFT                 3  /* IN1R_TO_MIXOUTL */
+#define WM8993_IN1R_TO_MIXOUTL_WIDTH                 1  /* IN1R_TO_MIXOUTL */
+#define WM8993_IN1L_TO_MIXOUTL                  0x0004  /* IN1L_TO_MIXOUTL */
+#define WM8993_IN1L_TO_MIXOUTL_MASK             0x0004  /* IN1L_TO_MIXOUTL */
+#define WM8993_IN1L_TO_MIXOUTL_SHIFT                 2  /* IN1L_TO_MIXOUTL */
+#define WM8993_IN1L_TO_MIXOUTL_WIDTH                 1  /* IN1L_TO_MIXOUTL */
+#define WM8993_IN2LP_TO_MIXOUTL                 0x0002  /* IN2LP_TO_MIXOUTL */
+#define WM8993_IN2LP_TO_MIXOUTL_MASK            0x0002  /* IN2LP_TO_MIXOUTL */
+#define WM8993_IN2LP_TO_MIXOUTL_SHIFT                1  /* IN2LP_TO_MIXOUTL */
+#define WM8993_IN2LP_TO_MIXOUTL_WIDTH                1  /* IN2LP_TO_MIXOUTL */
+#define WM8993_DACL_TO_MIXOUTL                  0x0001  /* DACL_TO_MIXOUTL */
+#define WM8993_DACL_TO_MIXOUTL_MASK             0x0001  /* DACL_TO_MIXOUTL */
+#define WM8993_DACL_TO_MIXOUTL_SHIFT                 0  /* DACL_TO_MIXOUTL */
+#define WM8993_DACL_TO_MIXOUTL_WIDTH                 1  /* DACL_TO_MIXOUTL */
+
+/*
+ * R46 (0x2E) - Output Mixer2
+ */
+#define WM8993_DACR_TO_HPOUT1R                  0x0100  /* DACR_TO_HPOUT1R */
+#define WM8993_DACR_TO_HPOUT1R_MASK             0x0100  /* DACR_TO_HPOUT1R */
+#define WM8993_DACR_TO_HPOUT1R_SHIFT                 8  /* DACR_TO_HPOUT1R */
+#define WM8993_DACR_TO_HPOUT1R_WIDTH                 1  /* DACR_TO_HPOUT1R */
+#define WM8993_MIXINL_TO_MIXOUTR                0x0080  /* MIXINL_TO_MIXOUTR */
+#define WM8993_MIXINL_TO_MIXOUTR_MASK           0x0080  /* MIXINL_TO_MIXOUTR */
+#define WM8993_MIXINL_TO_MIXOUTR_SHIFT               7  /* MIXINL_TO_MIXOUTR */
+#define WM8993_MIXINL_TO_MIXOUTR_WIDTH               1  /* MIXINL_TO_MIXOUTR */
+#define WM8993_MIXINR_TO_MIXOUTR                0x0040  /* MIXINR_TO_MIXOUTR */
+#define WM8993_MIXINR_TO_MIXOUTR_MASK           0x0040  /* MIXINR_TO_MIXOUTR */
+#define WM8993_MIXINR_TO_MIXOUTR_SHIFT               6  /* MIXINR_TO_MIXOUTR */
+#define WM8993_MIXINR_TO_MIXOUTR_WIDTH               1  /* MIXINR_TO_MIXOUTR */
+#define WM8993_IN2LN_TO_MIXOUTR                 0x0020  /* IN2LN_TO_MIXOUTR */
+#define WM8993_IN2LN_TO_MIXOUTR_MASK            0x0020  /* IN2LN_TO_MIXOUTR */
+#define WM8993_IN2LN_TO_MIXOUTR_SHIFT                5  /* IN2LN_TO_MIXOUTR */
+#define WM8993_IN2LN_TO_MIXOUTR_WIDTH                1  /* IN2LN_TO_MIXOUTR */
+#define WM8993_IN2RN_TO_MIXOUTR                 0x0010  /* IN2RN_TO_MIXOUTR */
+#define WM8993_IN2RN_TO_MIXOUTR_MASK            0x0010  /* IN2RN_TO_MIXOUTR */
+#define WM8993_IN2RN_TO_MIXOUTR_SHIFT                4  /* IN2RN_TO_MIXOUTR */
+#define WM8993_IN2RN_TO_MIXOUTR_WIDTH                1  /* IN2RN_TO_MIXOUTR */
+#define WM8993_IN1L_TO_MIXOUTR                  0x0008  /* IN1L_TO_MIXOUTR */
+#define WM8993_IN1L_TO_MIXOUTR_MASK             0x0008  /* IN1L_TO_MIXOUTR */
+#define WM8993_IN1L_TO_MIXOUTR_SHIFT                 3  /* IN1L_TO_MIXOUTR */
+#define WM8993_IN1L_TO_MIXOUTR_WIDTH                 1  /* IN1L_TO_MIXOUTR */
+#define WM8993_IN1R_TO_MIXOUTR                  0x0004  /* IN1R_TO_MIXOUTR */
+#define WM8993_IN1R_TO_MIXOUTR_MASK             0x0004  /* IN1R_TO_MIXOUTR */
+#define WM8993_IN1R_TO_MIXOUTR_SHIFT                 2  /* IN1R_TO_MIXOUTR */
+#define WM8993_IN1R_TO_MIXOUTR_WIDTH                 1  /* IN1R_TO_MIXOUTR */
+#define WM8993_IN2RP_TO_MIXOUTR                 0x0002  /* IN2RP_TO_MIXOUTR */
+#define WM8993_IN2RP_TO_MIXOUTR_MASK            0x0002  /* IN2RP_TO_MIXOUTR */
+#define WM8993_IN2RP_TO_MIXOUTR_SHIFT                1  /* IN2RP_TO_MIXOUTR */
+#define WM8993_IN2RP_TO_MIXOUTR_WIDTH                1  /* IN2RP_TO_MIXOUTR */
+#define WM8993_DACR_TO_MIXOUTR                  0x0001  /* DACR_TO_MIXOUTR */
+#define WM8993_DACR_TO_MIXOUTR_MASK             0x0001  /* DACR_TO_MIXOUTR */
+#define WM8993_DACR_TO_MIXOUTR_SHIFT                 0  /* DACR_TO_MIXOUTR */
+#define WM8993_DACR_TO_MIXOUTR_WIDTH                 1  /* DACR_TO_MIXOUTR */
+
+/*
+ * R47 (0x2F) - Output Mixer3
+ */
+#define WM8993_IN2LP_MIXOUTL_VOL_MASK           0x0E00  /* IN2LP_MIXOUTL_VOL - [11:9] */
+#define WM8993_IN2LP_MIXOUTL_VOL_SHIFT               9  /* IN2LP_MIXOUTL_VOL - [11:9] */
+#define WM8993_IN2LP_MIXOUTL_VOL_WIDTH               3  /* IN2LP_MIXOUTL_VOL - [11:9] */
+#define WM8993_IN2LN_MIXOUTL_VOL_MASK           0x01C0  /* IN2LN_MIXOUTL_VOL - [8:6] */
+#define WM8993_IN2LN_MIXOUTL_VOL_SHIFT               6  /* IN2LN_MIXOUTL_VOL - [8:6] */
+#define WM8993_IN2LN_MIXOUTL_VOL_WIDTH               3  /* IN2LN_MIXOUTL_VOL - [8:6] */
+#define WM8993_IN1R_MIXOUTL_VOL_MASK            0x0038  /* IN1R_MIXOUTL_VOL - [5:3] */
+#define WM8993_IN1R_MIXOUTL_VOL_SHIFT                3  /* IN1R_MIXOUTL_VOL - [5:3] */
+#define WM8993_IN1R_MIXOUTL_VOL_WIDTH                3  /* IN1R_MIXOUTL_VOL - [5:3] */
+#define WM8993_IN1L_MIXOUTL_VOL_MASK            0x0007  /* IN1L_MIXOUTL_VOL - [2:0] */
+#define WM8993_IN1L_MIXOUTL_VOL_SHIFT                0  /* IN1L_MIXOUTL_VOL - [2:0] */
+#define WM8993_IN1L_MIXOUTL_VOL_WIDTH                3  /* IN1L_MIXOUTL_VOL - [2:0] */
+
+/*
+ * R48 (0x30) - Output Mixer4
+ */
+#define WM8993_IN2RP_MIXOUTR_VOL_MASK           0x0E00  /* IN2RP_MIXOUTR_VOL - [11:9] */
+#define WM8993_IN2RP_MIXOUTR_VOL_SHIFT               9  /* IN2RP_MIXOUTR_VOL - [11:9] */
+#define WM8993_IN2RP_MIXOUTR_VOL_WIDTH               3  /* IN2RP_MIXOUTR_VOL - [11:9] */
+#define WM8993_IN2RN_MIXOUTR_VOL_MASK           0x01C0  /* IN2RN_MIXOUTR_VOL - [8:6] */
+#define WM8993_IN2RN_MIXOUTR_VOL_SHIFT               6  /* IN2RN_MIXOUTR_VOL - [8:6] */
+#define WM8993_IN2RN_MIXOUTR_VOL_WIDTH               3  /* IN2RN_MIXOUTR_VOL - [8:6] */
+#define WM8993_IN1L_MIXOUTR_VOL_MASK            0x0038  /* IN1L_MIXOUTR_VOL - [5:3] */
+#define WM8993_IN1L_MIXOUTR_VOL_SHIFT                3  /* IN1L_MIXOUTR_VOL - [5:3] */
+#define WM8993_IN1L_MIXOUTR_VOL_WIDTH                3  /* IN1L_MIXOUTR_VOL - [5:3] */
+#define WM8993_IN1R_MIXOUTR_VOL_MASK            0x0007  /* IN1R_MIXOUTR_VOL - [2:0] */
+#define WM8993_IN1R_MIXOUTR_VOL_SHIFT                0  /* IN1R_MIXOUTR_VOL - [2:0] */
+#define WM8993_IN1R_MIXOUTR_VOL_WIDTH                3  /* IN1R_MIXOUTR_VOL - [2:0] */
+
+/*
+ * R49 (0x31) - Output Mixer5
+ */
+#define WM8993_DACL_MIXOUTL_VOL_MASK            0x0E00  /* DACL_MIXOUTL_VOL - [11:9] */
+#define WM8993_DACL_MIXOUTL_VOL_SHIFT                9  /* DACL_MIXOUTL_VOL - [11:9] */
+#define WM8993_DACL_MIXOUTL_VOL_WIDTH                3  /* DACL_MIXOUTL_VOL - [11:9] */
+#define WM8993_IN2RN_MIXOUTL_VOL_MASK           0x01C0  /* IN2RN_MIXOUTL_VOL - [8:6] */
+#define WM8993_IN2RN_MIXOUTL_VOL_SHIFT               6  /* IN2RN_MIXOUTL_VOL - [8:6] */
+#define WM8993_IN2RN_MIXOUTL_VOL_WIDTH               3  /* IN2RN_MIXOUTL_VOL - [8:6] */
+#define WM8993_MIXINR_MIXOUTL_VOL_MASK          0x0038  /* MIXINR_MIXOUTL_VOL - [5:3] */
+#define WM8993_MIXINR_MIXOUTL_VOL_SHIFT              3  /* MIXINR_MIXOUTL_VOL - [5:3] */
+#define WM8993_MIXINR_MIXOUTL_VOL_WIDTH              3  /* MIXINR_MIXOUTL_VOL - [5:3] */
+#define WM8993_MIXINL_MIXOUTL_VOL_MASK          0x0007  /* MIXINL_MIXOUTL_VOL - [2:0] */
+#define WM8993_MIXINL_MIXOUTL_VOL_SHIFT              0  /* MIXINL_MIXOUTL_VOL - [2:0] */
+#define WM8993_MIXINL_MIXOUTL_VOL_WIDTH              3  /* MIXINL_MIXOUTL_VOL - [2:0] */
+
+/*
+ * R50 (0x32) - Output Mixer6
+ */
+#define WM8993_DACR_MIXOUTR_VOL_MASK            0x0E00  /* DACR_MIXOUTR_VOL - [11:9] */
+#define WM8993_DACR_MIXOUTR_VOL_SHIFT                9  /* DACR_MIXOUTR_VOL - [11:9] */
+#define WM8993_DACR_MIXOUTR_VOL_WIDTH                3  /* DACR_MIXOUTR_VOL - [11:9] */
+#define WM8993_IN2LN_MIXOUTR_VOL_MASK           0x01C0  /* IN2LN_MIXOUTR_VOL - [8:6] */
+#define WM8993_IN2LN_MIXOUTR_VOL_SHIFT               6  /* IN2LN_MIXOUTR_VOL - [8:6] */
+#define WM8993_IN2LN_MIXOUTR_VOL_WIDTH               3  /* IN2LN_MIXOUTR_VOL - [8:6] */
+#define WM8993_MIXINL_MIXOUTR_VOL_MASK          0x0038  /* MIXINL_MIXOUTR_VOL - [5:3] */
+#define WM8993_MIXINL_MIXOUTR_VOL_SHIFT              3  /* MIXINL_MIXOUTR_VOL - [5:3] */
+#define WM8993_MIXINL_MIXOUTR_VOL_WIDTH              3  /* MIXINL_MIXOUTR_VOL - [5:3] */
+#define WM8993_MIXINR_MIXOUTR_VOL_MASK          0x0007  /* MIXINR_MIXOUTR_VOL - [2:0] */
+#define WM8993_MIXINR_MIXOUTR_VOL_SHIFT              0  /* MIXINR_MIXOUTR_VOL - [2:0] */
+#define WM8993_MIXINR_MIXOUTR_VOL_WIDTH              3  /* MIXINR_MIXOUTR_VOL - [2:0] */
+
+/*
+ * R51 (0x33) - HPOUT2 Mixer
+ */
+#define WM8993_VRX_TO_HPOUT2                    0x0020  /* VRX_TO_HPOUT2 */
+#define WM8993_VRX_TO_HPOUT2_MASK               0x0020  /* VRX_TO_HPOUT2 */
+#define WM8993_VRX_TO_HPOUT2_SHIFT                   5  /* VRX_TO_HPOUT2 */
+#define WM8993_VRX_TO_HPOUT2_WIDTH                   1  /* VRX_TO_HPOUT2 */
+#define WM8993_MIXOUTLVOL_TO_HPOUT2             0x0010  /* MIXOUTLVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTLVOL_TO_HPOUT2_MASK        0x0010  /* MIXOUTLVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTLVOL_TO_HPOUT2_SHIFT            4  /* MIXOUTLVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTLVOL_TO_HPOUT2_WIDTH            1  /* MIXOUTLVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTRVOL_TO_HPOUT2             0x0008  /* MIXOUTRVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTRVOL_TO_HPOUT2_MASK        0x0008  /* MIXOUTRVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTRVOL_TO_HPOUT2_SHIFT            3  /* MIXOUTRVOL_TO_HPOUT2 */
+#define WM8993_MIXOUTRVOL_TO_HPOUT2_WIDTH            1  /* MIXOUTRVOL_TO_HPOUT2 */
+
+/*
+ * R52 (0x34) - Line Mixer1
+ */
+#define WM8993_MIXOUTL_TO_LINEOUT1N             0x0040  /* MIXOUTL_TO_LINEOUT1N */
+#define WM8993_MIXOUTL_TO_LINEOUT1N_MASK        0x0040  /* MIXOUTL_TO_LINEOUT1N */
+#define WM8993_MIXOUTL_TO_LINEOUT1N_SHIFT            6  /* MIXOUTL_TO_LINEOUT1N */
+#define WM8993_MIXOUTL_TO_LINEOUT1N_WIDTH            1  /* MIXOUTL_TO_LINEOUT1N */
+#define WM8993_MIXOUTR_TO_LINEOUT1N             0x0020  /* MIXOUTR_TO_LINEOUT1N */
+#define WM8993_MIXOUTR_TO_LINEOUT1N_MASK        0x0020  /* MIXOUTR_TO_LINEOUT1N */
+#define WM8993_MIXOUTR_TO_LINEOUT1N_SHIFT            5  /* MIXOUTR_TO_LINEOUT1N */
+#define WM8993_MIXOUTR_TO_LINEOUT1N_WIDTH            1  /* MIXOUTR_TO_LINEOUT1N */
+#define WM8993_LINEOUT1_MODE                    0x0010  /* LINEOUT1_MODE */
+#define WM8993_LINEOUT1_MODE_MASK               0x0010  /* LINEOUT1_MODE */
+#define WM8993_LINEOUT1_MODE_SHIFT                   4  /* LINEOUT1_MODE */
+#define WM8993_LINEOUT1_MODE_WIDTH                   1  /* LINEOUT1_MODE */
+#define WM8993_IN1R_TO_LINEOUT1P                0x0004  /* IN1R_TO_LINEOUT1P */
+#define WM8993_IN1R_TO_LINEOUT1P_MASK           0x0004  /* IN1R_TO_LINEOUT1P */
+#define WM8993_IN1R_TO_LINEOUT1P_SHIFT               2  /* IN1R_TO_LINEOUT1P */
+#define WM8993_IN1R_TO_LINEOUT1P_WIDTH               1  /* IN1R_TO_LINEOUT1P */
+#define WM8993_IN1L_TO_LINEOUT1P                0x0002  /* IN1L_TO_LINEOUT1P */
+#define WM8993_IN1L_TO_LINEOUT1P_MASK           0x0002  /* IN1L_TO_LINEOUT1P */
+#define WM8993_IN1L_TO_LINEOUT1P_SHIFT               1  /* IN1L_TO_LINEOUT1P */
+#define WM8993_IN1L_TO_LINEOUT1P_WIDTH               1  /* IN1L_TO_LINEOUT1P */
+#define WM8993_MIXOUTL_TO_LINEOUT1P             0x0001  /* MIXOUTL_TO_LINEOUT1P */
+#define WM8993_MIXOUTL_TO_LINEOUT1P_MASK        0x0001  /* MIXOUTL_TO_LINEOUT1P */
+#define WM8993_MIXOUTL_TO_LINEOUT1P_SHIFT            0  /* MIXOUTL_TO_LINEOUT1P */
+#define WM8993_MIXOUTL_TO_LINEOUT1P_WIDTH            1  /* MIXOUTL_TO_LINEOUT1P */
+
+/*
+ * R53 (0x35) - Line Mixer2
+ */
+#define WM8993_MIXOUTR_TO_LINEOUT2N             0x0040  /* MIXOUTR_TO_LINEOUT2N */
+#define WM8993_MIXOUTR_TO_LINEOUT2N_MASK        0x0040  /* MIXOUTR_TO_LINEOUT2N */
+#define WM8993_MIXOUTR_TO_LINEOUT2N_SHIFT            6  /* MIXOUTR_TO_LINEOUT2N */
+#define WM8993_MIXOUTR_TO_LINEOUT2N_WIDTH            1  /* MIXOUTR_TO_LINEOUT2N */
+#define WM8993_MIXOUTL_TO_LINEOUT2N             0x0020  /* MIXOUTL_TO_LINEOUT2N */
+#define WM8993_MIXOUTL_TO_LINEOUT2N_MASK        0x0020  /* MIXOUTL_TO_LINEOUT2N */
+#define WM8993_MIXOUTL_TO_LINEOUT2N_SHIFT            5  /* MIXOUTL_TO_LINEOUT2N */
+#define WM8993_MIXOUTL_TO_LINEOUT2N_WIDTH            1  /* MIXOUTL_TO_LINEOUT2N */
+#define WM8993_LINEOUT2_MODE                    0x0010  /* LINEOUT2_MODE */
+#define WM8993_LINEOUT2_MODE_MASK               0x0010  /* LINEOUT2_MODE */
+#define WM8993_LINEOUT2_MODE_SHIFT                   4  /* LINEOUT2_MODE */
+#define WM8993_LINEOUT2_MODE_WIDTH                   1  /* LINEOUT2_MODE */
+#define WM8993_IN1L_TO_LINEOUT2P                0x0004  /* IN1L_TO_LINEOUT2P */
+#define WM8993_IN1L_TO_LINEOUT2P_MASK           0x0004  /* IN1L_TO_LINEOUT2P */
+#define WM8993_IN1L_TO_LINEOUT2P_SHIFT               2  /* IN1L_TO_LINEOUT2P */
+#define WM8993_IN1L_TO_LINEOUT2P_WIDTH               1  /* IN1L_TO_LINEOUT2P */
+#define WM8993_IN1R_TO_LINEOUT2P                0x0002  /* IN1R_TO_LINEOUT2P */
+#define WM8993_IN1R_TO_LINEOUT2P_MASK           0x0002  /* IN1R_TO_LINEOUT2P */
+#define WM8993_IN1R_TO_LINEOUT2P_SHIFT               1  /* IN1R_TO_LINEOUT2P */
+#define WM8993_IN1R_TO_LINEOUT2P_WIDTH               1  /* IN1R_TO_LINEOUT2P */
+#define WM8993_MIXOUTR_TO_LINEOUT2P             0x0001  /* MIXOUTR_TO_LINEOUT2P */
+#define WM8993_MIXOUTR_TO_LINEOUT2P_MASK        0x0001  /* MIXOUTR_TO_LINEOUT2P */
+#define WM8993_MIXOUTR_TO_LINEOUT2P_SHIFT            0  /* MIXOUTR_TO_LINEOUT2P */
+#define WM8993_MIXOUTR_TO_LINEOUT2P_WIDTH            1  /* MIXOUTR_TO_LINEOUT2P */
+
+/*
+ * R54 (0x36) - Speaker Mixer
+ */
+#define WM8993_SPKAB_REF_SEL                    0x0100  /* SPKAB_REF_SEL */
+#define WM8993_SPKAB_REF_SEL_MASK               0x0100  /* SPKAB_REF_SEL */
+#define WM8993_SPKAB_REF_SEL_SHIFT                   8  /* SPKAB_REF_SEL */
+#define WM8993_SPKAB_REF_SEL_WIDTH                   1  /* SPKAB_REF_SEL */
+#define WM8993_MIXINL_TO_SPKMIXL                0x0080  /* MIXINL_TO_SPKMIXL */
+#define WM8993_MIXINL_TO_SPKMIXL_MASK           0x0080  /* MIXINL_TO_SPKMIXL */
+#define WM8993_MIXINL_TO_SPKMIXL_SHIFT               7  /* MIXINL_TO_SPKMIXL */
+#define WM8993_MIXINL_TO_SPKMIXL_WIDTH               1  /* MIXINL_TO_SPKMIXL */
+#define WM8993_MIXINR_TO_SPKMIXR                0x0040  /* MIXINR_TO_SPKMIXR */
+#define WM8993_MIXINR_TO_SPKMIXR_MASK           0x0040  /* MIXINR_TO_SPKMIXR */
+#define WM8993_MIXINR_TO_SPKMIXR_SHIFT               6  /* MIXINR_TO_SPKMIXR */
+#define WM8993_MIXINR_TO_SPKMIXR_WIDTH               1  /* MIXINR_TO_SPKMIXR */
+#define WM8993_IN1LP_TO_SPKMIXL                 0x0020  /* IN1LP_TO_SPKMIXL */
+#define WM8993_IN1LP_TO_SPKMIXL_MASK            0x0020  /* IN1LP_TO_SPKMIXL */
+#define WM8993_IN1LP_TO_SPKMIXL_SHIFT                5  /* IN1LP_TO_SPKMIXL */
+#define WM8993_IN1LP_TO_SPKMIXL_WIDTH                1  /* IN1LP_TO_SPKMIXL */
+#define WM8993_IN1RP_TO_SPKMIXR                 0x0010  /* IN1RP_TO_SPKMIXR */
+#define WM8993_IN1RP_TO_SPKMIXR_MASK            0x0010  /* IN1RP_TO_SPKMIXR */
+#define WM8993_IN1RP_TO_SPKMIXR_SHIFT                4  /* IN1RP_TO_SPKMIXR */
+#define WM8993_IN1RP_TO_SPKMIXR_WIDTH                1  /* IN1RP_TO_SPKMIXR */
+#define WM8993_MIXOUTL_TO_SPKMIXL               0x0008  /* MIXOUTL_TO_SPKMIXL */
+#define WM8993_MIXOUTL_TO_SPKMIXL_MASK          0x0008  /* MIXOUTL_TO_SPKMIXL */
+#define WM8993_MIXOUTL_TO_SPKMIXL_SHIFT              3  /* MIXOUTL_TO_SPKMIXL */
+#define WM8993_MIXOUTL_TO_SPKMIXL_WIDTH              1  /* MIXOUTL_TO_SPKMIXL */
+#define WM8993_MIXOUTR_TO_SPKMIXR               0x0004  /* MIXOUTR_TO_SPKMIXR */
+#define WM8993_MIXOUTR_TO_SPKMIXR_MASK          0x0004  /* MIXOUTR_TO_SPKMIXR */
+#define WM8993_MIXOUTR_TO_SPKMIXR_SHIFT              2  /* MIXOUTR_TO_SPKMIXR */
+#define WM8993_MIXOUTR_TO_SPKMIXR_WIDTH              1  /* MIXOUTR_TO_SPKMIXR */
+#define WM8993_DACL_TO_SPKMIXL                  0x0002  /* DACL_TO_SPKMIXL */
+#define WM8993_DACL_TO_SPKMIXL_MASK             0x0002  /* DACL_TO_SPKMIXL */
+#define WM8993_DACL_TO_SPKMIXL_SHIFT                 1  /* DACL_TO_SPKMIXL */
+#define WM8993_DACL_TO_SPKMIXL_WIDTH                 1  /* DACL_TO_SPKMIXL */
+#define WM8993_DACR_TO_SPKMIXR                  0x0001  /* DACR_TO_SPKMIXR */
+#define WM8993_DACR_TO_SPKMIXR_MASK             0x0001  /* DACR_TO_SPKMIXR */
+#define WM8993_DACR_TO_SPKMIXR_SHIFT                 0  /* DACR_TO_SPKMIXR */
+#define WM8993_DACR_TO_SPKMIXR_WIDTH                 1  /* DACR_TO_SPKMIXR */
+
+/*
+ * R55 (0x37) - Additional Control
+ */
+#define WM8993_LINEOUT1_FB                      0x0080  /* LINEOUT1_FB */
+#define WM8993_LINEOUT1_FB_MASK                 0x0080  /* LINEOUT1_FB */
+#define WM8993_LINEOUT1_FB_SHIFT                     7  /* LINEOUT1_FB */
+#define WM8993_LINEOUT1_FB_WIDTH                     1  /* LINEOUT1_FB */
+#define WM8993_LINEOUT2_FB                      0x0040  /* LINEOUT2_FB */
+#define WM8993_LINEOUT2_FB_MASK                 0x0040  /* LINEOUT2_FB */
+#define WM8993_LINEOUT2_FB_SHIFT                     6  /* LINEOUT2_FB */
+#define WM8993_LINEOUT2_FB_WIDTH                     1  /* LINEOUT2_FB */
+#define WM8993_VROI                             0x0001  /* VROI */
+#define WM8993_VROI_MASK                        0x0001  /* VROI */
+#define WM8993_VROI_SHIFT                            0  /* VROI */
+#define WM8993_VROI_WIDTH                            1  /* VROI */
+
+/*
+ * R56 (0x38) - AntiPOP1
+ */
+#define WM8993_LINEOUT_VMID_BUF_ENA             0x0080  /* LINEOUT_VMID_BUF_ENA */
+#define WM8993_LINEOUT_VMID_BUF_ENA_MASK        0x0080  /* LINEOUT_VMID_BUF_ENA */
+#define WM8993_LINEOUT_VMID_BUF_ENA_SHIFT            7  /* LINEOUT_VMID_BUF_ENA */
+#define WM8993_LINEOUT_VMID_BUF_ENA_WIDTH            1  /* LINEOUT_VMID_BUF_ENA */
+#define WM8993_HPOUT2_IN_ENA                    0x0040  /* HPOUT2_IN_ENA */
+#define WM8993_HPOUT2_IN_ENA_MASK               0x0040  /* HPOUT2_IN_ENA */
+#define WM8993_HPOUT2_IN_ENA_SHIFT                   6  /* HPOUT2_IN_ENA */
+#define WM8993_HPOUT2_IN_ENA_WIDTH                   1  /* HPOUT2_IN_ENA */
+#define WM8993_LINEOUT1_DISCH                   0x0020  /* LINEOUT1_DISCH */
+#define WM8993_LINEOUT1_DISCH_MASK              0x0020  /* LINEOUT1_DISCH */
+#define WM8993_LINEOUT1_DISCH_SHIFT                  5  /* LINEOUT1_DISCH */
+#define WM8993_LINEOUT1_DISCH_WIDTH                  1  /* LINEOUT1_DISCH */
+#define WM8993_LINEOUT2_DISCH                   0x0010  /* LINEOUT2_DISCH */
+#define WM8993_LINEOUT2_DISCH_MASK              0x0010  /* LINEOUT2_DISCH */
+#define WM8993_LINEOUT2_DISCH_SHIFT                  4  /* LINEOUT2_DISCH */
+#define WM8993_LINEOUT2_DISCH_WIDTH                  1  /* LINEOUT2_DISCH */
+
+/*
+ * R57 (0x39) - AntiPOP2
+ */
+#define WM8993_VMID_RAMP_MASK                   0x0060  /* VMID_RAMP - [6:5] */
+#define WM8993_VMID_RAMP_SHIFT                       5  /* VMID_RAMP - [6:5] */
+#define WM8993_VMID_RAMP_WIDTH                       2  /* VMID_RAMP - [6:5] */
+#define WM8993_VMID_BUF_ENA                     0x0008  /* VMID_BUF_ENA */
+#define WM8993_VMID_BUF_ENA_MASK                0x0008  /* VMID_BUF_ENA */
+#define WM8993_VMID_BUF_ENA_SHIFT                    3  /* VMID_BUF_ENA */
+#define WM8993_VMID_BUF_ENA_WIDTH                    1  /* VMID_BUF_ENA */
+#define WM8993_STARTUP_BIAS_ENA                 0x0004  /* STARTUP_BIAS_ENA */
+#define WM8993_STARTUP_BIAS_ENA_MASK            0x0004  /* STARTUP_BIAS_ENA */
+#define WM8993_STARTUP_BIAS_ENA_SHIFT                2  /* STARTUP_BIAS_ENA */
+#define WM8993_STARTUP_BIAS_ENA_WIDTH                1  /* STARTUP_BIAS_ENA */
+#define WM8993_BIAS_SRC                         0x0002  /* BIAS_SRC */
+#define WM8993_BIAS_SRC_MASK                    0x0002  /* BIAS_SRC */
+#define WM8993_BIAS_SRC_SHIFT                        1  /* BIAS_SRC */
+#define WM8993_BIAS_SRC_WIDTH                        1  /* BIAS_SRC */
+#define WM8993_VMID_DISCH                       0x0001  /* VMID_DISCH */
+#define WM8993_VMID_DISCH_MASK                  0x0001  /* VMID_DISCH */
+#define WM8993_VMID_DISCH_SHIFT                      0  /* VMID_DISCH */
+#define WM8993_VMID_DISCH_WIDTH                      1  /* VMID_DISCH */
+
+/*
+ * R58 (0x3A) - MICBIAS
+ */
+#define WM8993_JD_SCTHR_MASK                    0x00C0  /* JD_SCTHR - [7:6] */
+#define WM8993_JD_SCTHR_SHIFT                        6  /* JD_SCTHR - [7:6] */
+#define WM8993_JD_SCTHR_WIDTH                        2  /* JD_SCTHR - [7:6] */
+#define WM8993_JD_THR_MASK                      0x0030  /* JD_THR - [5:4] */
+#define WM8993_JD_THR_SHIFT                          4  /* JD_THR - [5:4] */
+#define WM8993_JD_THR_WIDTH                          2  /* JD_THR - [5:4] */
+#define WM8993_JD_ENA                           0x0004  /* JD_ENA */
+#define WM8993_JD_ENA_MASK                      0x0004  /* JD_ENA */
+#define WM8993_JD_ENA_SHIFT                          2  /* JD_ENA */
+#define WM8993_JD_ENA_WIDTH                          1  /* JD_ENA */
+#define WM8993_MICB2_LVL                        0x0002  /* MICB2_LVL */
+#define WM8993_MICB2_LVL_MASK                   0x0002  /* MICB2_LVL */
+#define WM8993_MICB2_LVL_SHIFT                       1  /* MICB2_LVL */
+#define WM8993_MICB2_LVL_WIDTH                       1  /* MICB2_LVL */
+#define WM8993_MICB1_LVL                        0x0001  /* MICB1_LVL */
+#define WM8993_MICB1_LVL_MASK                   0x0001  /* MICB1_LVL */
+#define WM8993_MICB1_LVL_SHIFT                       0  /* MICB1_LVL */
+#define WM8993_MICB1_LVL_WIDTH                       1  /* MICB1_LVL */
+
+/*
+ * R60 (0x3C) - FLL Control 1
+ */
+#define WM8993_FLL_FRAC                         0x0004  /* FLL_FRAC */
+#define WM8993_FLL_FRAC_MASK                    0x0004  /* FLL_FRAC */
+#define WM8993_FLL_FRAC_SHIFT                        2  /* FLL_FRAC */
+#define WM8993_FLL_FRAC_WIDTH                        1  /* FLL_FRAC */
+#define WM8993_FLL_OSC_ENA                      0x0002  /* FLL_OSC_ENA */
+#define WM8993_FLL_OSC_ENA_MASK                 0x0002  /* FLL_OSC_ENA */
+#define WM8993_FLL_OSC_ENA_SHIFT                     1  /* FLL_OSC_ENA */
+#define WM8993_FLL_OSC_ENA_WIDTH                     1  /* FLL_OSC_ENA */
+#define WM8993_FLL_ENA                          0x0001  /* FLL_ENA */
+#define WM8993_FLL_ENA_MASK                     0x0001  /* FLL_ENA */
+#define WM8993_FLL_ENA_SHIFT                         0  /* FLL_ENA */
+#define WM8993_FLL_ENA_WIDTH                         1  /* FLL_ENA */
+
+/*
+ * R61 (0x3D) - FLL Control 2
+ */
+#define WM8993_FLL_OUTDIV_MASK                  0x0700  /* FLL_OUTDIV - [10:8] */
+#define WM8993_FLL_OUTDIV_SHIFT                      8  /* FLL_OUTDIV - [10:8] */
+#define WM8993_FLL_OUTDIV_WIDTH                      3  /* FLL_OUTDIV - [10:8] */
+#define WM8993_FLL_CTRL_RATE_MASK               0x0070  /* FLL_CTRL_RATE - [6:4] */
+#define WM8993_FLL_CTRL_RATE_SHIFT                   4  /* FLL_CTRL_RATE - [6:4] */
+#define WM8993_FLL_CTRL_RATE_WIDTH                   3  /* FLL_CTRL_RATE - [6:4] */
+#define WM8993_FLL_FRATIO_MASK                  0x0007  /* FLL_FRATIO - [2:0] */
+#define WM8993_FLL_FRATIO_SHIFT                      0  /* FLL_FRATIO - [2:0] */
+#define WM8993_FLL_FRATIO_WIDTH                      3  /* FLL_FRATIO - [2:0] */
+
+/*
+ * R62 (0x3E) - FLL Control 3
+ */
+#define WM8993_FLL_K_MASK                       0xFFFF  /* FLL_K - [15:0] */
+#define WM8993_FLL_K_SHIFT                           0  /* FLL_K - [15:0] */
+#define WM8993_FLL_K_WIDTH                          16  /* FLL_K - [15:0] */
+
+/*
+ * R63 (0x3F) - FLL Control 4
+ */
+#define WM8993_FLL_N_MASK                       0x7FE0  /* FLL_N - [14:5] */
+#define WM8993_FLL_N_SHIFT                           5  /* FLL_N - [14:5] */
+#define WM8993_FLL_N_WIDTH                          10  /* FLL_N - [14:5] */
+#define WM8993_FLL_GAIN_MASK                    0x000F  /* FLL_GAIN - [3:0] */
+#define WM8993_FLL_GAIN_SHIFT                        0  /* FLL_GAIN - [3:0] */
+#define WM8993_FLL_GAIN_WIDTH                        4  /* FLL_GAIN - [3:0] */
+
+/*
+ * R64 (0x40) - FLL Control 5
+ */
+#define WM8993_FLL_FRC_NCO_VAL_MASK             0x1F80  /* FLL_FRC_NCO_VAL - [12:7] */
+#define WM8993_FLL_FRC_NCO_VAL_SHIFT                 7  /* FLL_FRC_NCO_VAL - [12:7] */
+#define WM8993_FLL_FRC_NCO_VAL_WIDTH                 6  /* FLL_FRC_NCO_VAL - [12:7] */
+#define WM8993_FLL_FRC_NCO                      0x0040  /* FLL_FRC_NCO */
+#define WM8993_FLL_FRC_NCO_MASK                 0x0040  /* FLL_FRC_NCO */
+#define WM8993_FLL_FRC_NCO_SHIFT                     6  /* FLL_FRC_NCO */
+#define WM8993_FLL_FRC_NCO_WIDTH                     1  /* FLL_FRC_NCO */
+#define WM8993_FLL_CLK_REF_DIV_MASK             0x0018  /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8993_FLL_CLK_REF_DIV_SHIFT                 3  /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8993_FLL_CLK_REF_DIV_WIDTH                 2  /* FLL_CLK_REF_DIV - [4:3] */
+#define WM8993_FLL_CLK_SRC_MASK                 0x0003  /* FLL_CLK_SRC - [1:0] */
+#define WM8993_FLL_CLK_SRC_SHIFT                     0  /* FLL_CLK_SRC - [1:0] */
+#define WM8993_FLL_CLK_SRC_WIDTH                     2  /* FLL_CLK_SRC - [1:0] */
+
+/*
+ * R65 (0x41) - Clocking 3
+ */
+#define WM8993_CLK_DCS_DIV_MASK                 0x3C00  /* CLK_DCS_DIV - [13:10] */
+#define WM8993_CLK_DCS_DIV_SHIFT                    10  /* CLK_DCS_DIV - [13:10] */
+#define WM8993_CLK_DCS_DIV_WIDTH                     4  /* CLK_DCS_DIV - [13:10] */
+#define WM8993_SAMPLE_RATE_MASK                 0x0380  /* SAMPLE_RATE - [9:7] */
+#define WM8993_SAMPLE_RATE_SHIFT                     7  /* SAMPLE_RATE - [9:7] */
+#define WM8993_SAMPLE_RATE_WIDTH                     3  /* SAMPLE_RATE - [9:7] */
+#define WM8993_CLK_SYS_RATE_MASK                0x001E  /* CLK_SYS_RATE - [4:1] */
+#define WM8993_CLK_SYS_RATE_SHIFT                    1  /* CLK_SYS_RATE - [4:1] */
+#define WM8993_CLK_SYS_RATE_WIDTH                    4  /* CLK_SYS_RATE - [4:1] */
+#define WM8993_CLK_DSP_ENA                      0x0001  /* CLK_DSP_ENA */
+#define WM8993_CLK_DSP_ENA_MASK                 0x0001  /* CLK_DSP_ENA */
+#define WM8993_CLK_DSP_ENA_SHIFT                     0  /* CLK_DSP_ENA */
+#define WM8993_CLK_DSP_ENA_WIDTH                     1  /* CLK_DSP_ENA */
+
+/*
+ * R66 (0x42) - Clocking 4
+ */
+#define WM8993_DAC_DIV4                         0x0200  /* DAC_DIV4 */
+#define WM8993_DAC_DIV4_MASK                    0x0200  /* DAC_DIV4 */
+#define WM8993_DAC_DIV4_SHIFT                        9  /* DAC_DIV4 */
+#define WM8993_DAC_DIV4_WIDTH                        1  /* DAC_DIV4 */
+#define WM8993_CLK_256K_DIV_MASK                0x007E  /* CLK_256K_DIV - [6:1] */
+#define WM8993_CLK_256K_DIV_SHIFT                    1  /* CLK_256K_DIV - [6:1] */
+#define WM8993_CLK_256K_DIV_WIDTH                    6  /* CLK_256K_DIV - [6:1] */
+#define WM8993_SR_MODE                          0x0001  /* SR_MODE */
+#define WM8993_SR_MODE_MASK                     0x0001  /* SR_MODE */
+#define WM8993_SR_MODE_SHIFT                         0  /* SR_MODE */
+#define WM8993_SR_MODE_WIDTH                         1  /* SR_MODE */
+
+/*
+ * R67 (0x43) - MW Slave Control
+ */
+#define WM8993_MASK_WRITE_ENA                   0x0001  /* MASK_WRITE_ENA */
+#define WM8993_MASK_WRITE_ENA_MASK              0x0001  /* MASK_WRITE_ENA */
+#define WM8993_MASK_WRITE_ENA_SHIFT                  0  /* MASK_WRITE_ENA */
+#define WM8993_MASK_WRITE_ENA_WIDTH                  1  /* MASK_WRITE_ENA */
+
+/*
+ * R69 (0x45) - Bus Control 1
+ */
+#define WM8993_CLK_SYS_ENA                      0x0002  /* CLK_SYS_ENA */
+#define WM8993_CLK_SYS_ENA_MASK                 0x0002  /* CLK_SYS_ENA */
+#define WM8993_CLK_SYS_ENA_SHIFT                     1  /* CLK_SYS_ENA */
+#define WM8993_CLK_SYS_ENA_WIDTH                     1  /* CLK_SYS_ENA */
+
+/*
+ * R70 (0x46) - Write Sequencer 0
+ */
+#define WM8993_WSEQ_ENA                         0x0100  /* WSEQ_ENA */
+#define WM8993_WSEQ_ENA_MASK                    0x0100  /* WSEQ_ENA */
+#define WM8993_WSEQ_ENA_SHIFT                        8  /* WSEQ_ENA */
+#define WM8993_WSEQ_ENA_WIDTH                        1  /* WSEQ_ENA */
+#define WM8993_WSEQ_WRITE_INDEX_MASK            0x001F  /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8993_WSEQ_WRITE_INDEX_SHIFT                0  /* WSEQ_WRITE_INDEX - [4:0] */
+#define WM8993_WSEQ_WRITE_INDEX_WIDTH                5  /* WSEQ_WRITE_INDEX - [4:0] */
+
+/*
+ * R71 (0x47) - Write Sequencer 1
+ */
+#define WM8993_WSEQ_DATA_WIDTH_MASK             0x7000  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8993_WSEQ_DATA_WIDTH_SHIFT                12  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8993_WSEQ_DATA_WIDTH_WIDTH                 3  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM8993_WSEQ_DATA_START_MASK             0x0F00  /* WSEQ_DATA_START - [11:8] */
+#define WM8993_WSEQ_DATA_START_SHIFT                 8  /* WSEQ_DATA_START - [11:8] */
+#define WM8993_WSEQ_DATA_START_WIDTH                 4  /* WSEQ_DATA_START - [11:8] */
+#define WM8993_WSEQ_ADDR_MASK                   0x00FF  /* WSEQ_ADDR - [7:0] */
+#define WM8993_WSEQ_ADDR_SHIFT                       0  /* WSEQ_ADDR - [7:0] */
+#define WM8993_WSEQ_ADDR_WIDTH                       8  /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R72 (0x48) - Write Sequencer 2
+ */
+#define WM8993_WSEQ_EOS                         0x4000  /* WSEQ_EOS */
+#define WM8993_WSEQ_EOS_MASK                    0x4000  /* WSEQ_EOS */
+#define WM8993_WSEQ_EOS_SHIFT                       14  /* WSEQ_EOS */
+#define WM8993_WSEQ_EOS_WIDTH                        1  /* WSEQ_EOS */
+#define WM8993_WSEQ_DELAY_MASK                  0x0F00  /* WSEQ_DELAY - [11:8] */
+#define WM8993_WSEQ_DELAY_SHIFT                      8  /* WSEQ_DELAY - [11:8] */
+#define WM8993_WSEQ_DELAY_WIDTH                      4  /* WSEQ_DELAY - [11:8] */
+#define WM8993_WSEQ_DATA_MASK                   0x00FF  /* WSEQ_DATA - [7:0] */
+#define WM8993_WSEQ_DATA_SHIFT                       0  /* WSEQ_DATA - [7:0] */
+#define WM8993_WSEQ_DATA_WIDTH                       8  /* WSEQ_DATA - [7:0] */
+
+/*
+ * R73 (0x49) - Write Sequencer 3
+ */
+#define WM8993_WSEQ_ABORT                       0x0200  /* WSEQ_ABORT */
+#define WM8993_WSEQ_ABORT_MASK                  0x0200  /* WSEQ_ABORT */
+#define WM8993_WSEQ_ABORT_SHIFT                      9  /* WSEQ_ABORT */
+#define WM8993_WSEQ_ABORT_WIDTH                      1  /* WSEQ_ABORT */
+#define WM8993_WSEQ_START                       0x0100  /* WSEQ_START */
+#define WM8993_WSEQ_START_MASK                  0x0100  /* WSEQ_START */
+#define WM8993_WSEQ_START_SHIFT                      8  /* WSEQ_START */
+#define WM8993_WSEQ_START_WIDTH                      1  /* WSEQ_START */
+#define WM8993_WSEQ_START_INDEX_MASK            0x003F  /* WSEQ_START_INDEX - [5:0] */
+#define WM8993_WSEQ_START_INDEX_SHIFT                0  /* WSEQ_START_INDEX - [5:0] */
+#define WM8993_WSEQ_START_INDEX_WIDTH                6  /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R74 (0x4A) - Write Sequencer 4
+ */
+#define WM8993_WSEQ_BUSY                        0x0001  /* WSEQ_BUSY */
+#define WM8993_WSEQ_BUSY_MASK                   0x0001  /* WSEQ_BUSY */
+#define WM8993_WSEQ_BUSY_SHIFT                       0  /* WSEQ_BUSY */
+#define WM8993_WSEQ_BUSY_WIDTH                       1  /* WSEQ_BUSY */
+
+/*
+ * R75 (0x4B) - Write Sequencer 5
+ */
+#define WM8993_WSEQ_CURRENT_INDEX_MASK          0x003F  /* WSEQ_CURRENT_INDEX - [5:0] */
+#define WM8993_WSEQ_CURRENT_INDEX_SHIFT              0  /* WSEQ_CURRENT_INDEX - [5:0] */
+#define WM8993_WSEQ_CURRENT_INDEX_WIDTH              6  /* WSEQ_CURRENT_INDEX - [5:0] */
+
+/*
+ * R76 (0x4C) - Charge Pump 1
+ */
+#define WM8993_CP_ENA                           0x8000  /* CP_ENA */
+#define WM8993_CP_ENA_MASK                      0x8000  /* CP_ENA */
+#define WM8993_CP_ENA_SHIFT                         15  /* CP_ENA */
+#define WM8993_CP_ENA_WIDTH                          1  /* CP_ENA */
+
+/*
+ * R81 (0x51) - Class W 0
+ */
+#define WM8993_CP_DYN_FREQ                      0x0002  /* CP_DYN_FREQ */
+#define WM8993_CP_DYN_FREQ_MASK                 0x0002  /* CP_DYN_FREQ */
+#define WM8993_CP_DYN_FREQ_SHIFT                     1  /* CP_DYN_FREQ */
+#define WM8993_CP_DYN_FREQ_WIDTH                     1  /* CP_DYN_FREQ */
+#define WM8993_CP_DYN_V                         0x0001  /* CP_DYN_V */
+#define WM8993_CP_DYN_V_MASK                    0x0001  /* CP_DYN_V */
+#define WM8993_CP_DYN_V_SHIFT                        0  /* CP_DYN_V */
+#define WM8993_CP_DYN_V_WIDTH                        1  /* CP_DYN_V */
+
+/*
+ * R84 (0x54) - DC Servo 0
+ */
+#define WM8993_DCS_TRIG_SINGLE_1                0x2000  /* DCS_TRIG_SINGLE_1 */
+#define WM8993_DCS_TRIG_SINGLE_1_MASK           0x2000  /* DCS_TRIG_SINGLE_1 */
+#define WM8993_DCS_TRIG_SINGLE_1_SHIFT              13  /* DCS_TRIG_SINGLE_1 */
+#define WM8993_DCS_TRIG_SINGLE_1_WIDTH               1  /* DCS_TRIG_SINGLE_1 */
+#define WM8993_DCS_TRIG_SINGLE_0                0x1000  /* DCS_TRIG_SINGLE_0 */
+#define WM8993_DCS_TRIG_SINGLE_0_MASK           0x1000  /* DCS_TRIG_SINGLE_0 */
+#define WM8993_DCS_TRIG_SINGLE_0_SHIFT              12  /* DCS_TRIG_SINGLE_0 */
+#define WM8993_DCS_TRIG_SINGLE_0_WIDTH               1  /* DCS_TRIG_SINGLE_0 */
+#define WM8993_DCS_TRIG_SERIES_1                0x0200  /* DCS_TRIG_SERIES_1 */
+#define WM8993_DCS_TRIG_SERIES_1_MASK           0x0200  /* DCS_TRIG_SERIES_1 */
+#define WM8993_DCS_TRIG_SERIES_1_SHIFT               9  /* DCS_TRIG_SERIES_1 */
+#define WM8993_DCS_TRIG_SERIES_1_WIDTH               1  /* DCS_TRIG_SERIES_1 */
+#define WM8993_DCS_TRIG_SERIES_0                0x0100  /* DCS_TRIG_SERIES_0 */
+#define WM8993_DCS_TRIG_SERIES_0_MASK           0x0100  /* DCS_TRIG_SERIES_0 */
+#define WM8993_DCS_TRIG_SERIES_0_SHIFT               8  /* DCS_TRIG_SERIES_0 */
+#define WM8993_DCS_TRIG_SERIES_0_WIDTH               1  /* DCS_TRIG_SERIES_0 */
+#define WM8993_DCS_TRIG_STARTUP_1               0x0020  /* DCS_TRIG_STARTUP_1 */
+#define WM8993_DCS_TRIG_STARTUP_1_MASK          0x0020  /* DCS_TRIG_STARTUP_1 */
+#define WM8993_DCS_TRIG_STARTUP_1_SHIFT              5  /* DCS_TRIG_STARTUP_1 */
+#define WM8993_DCS_TRIG_STARTUP_1_WIDTH              1  /* DCS_TRIG_STARTUP_1 */
+#define WM8993_DCS_TRIG_STARTUP_0               0x0010  /* DCS_TRIG_STARTUP_0 */
+#define WM8993_DCS_TRIG_STARTUP_0_MASK          0x0010  /* DCS_TRIG_STARTUP_0 */
+#define WM8993_DCS_TRIG_STARTUP_0_SHIFT              4  /* DCS_TRIG_STARTUP_0 */
+#define WM8993_DCS_TRIG_STARTUP_0_WIDTH              1  /* DCS_TRIG_STARTUP_0 */
+#define WM8993_DCS_TRIG_DAC_WR_1                0x0008  /* DCS_TRIG_DAC_WR_1 */
+#define WM8993_DCS_TRIG_DAC_WR_1_MASK           0x0008  /* DCS_TRIG_DAC_WR_1 */
+#define WM8993_DCS_TRIG_DAC_WR_1_SHIFT               3  /* DCS_TRIG_DAC_WR_1 */
+#define WM8993_DCS_TRIG_DAC_WR_1_WIDTH               1  /* DCS_TRIG_DAC_WR_1 */
+#define WM8993_DCS_TRIG_DAC_WR_0                0x0004  /* DCS_TRIG_DAC_WR_0 */
+#define WM8993_DCS_TRIG_DAC_WR_0_MASK           0x0004  /* DCS_TRIG_DAC_WR_0 */
+#define WM8993_DCS_TRIG_DAC_WR_0_SHIFT               2  /* DCS_TRIG_DAC_WR_0 */
+#define WM8993_DCS_TRIG_DAC_WR_0_WIDTH               1  /* DCS_TRIG_DAC_WR_0 */
+#define WM8993_DCS_ENA_CHAN_1                   0x0002  /* DCS_ENA_CHAN_1 */
+#define WM8993_DCS_ENA_CHAN_1_MASK              0x0002  /* DCS_ENA_CHAN_1 */
+#define WM8993_DCS_ENA_CHAN_1_SHIFT                  1  /* DCS_ENA_CHAN_1 */
+#define WM8993_DCS_ENA_CHAN_1_WIDTH                  1  /* DCS_ENA_CHAN_1 */
+#define WM8993_DCS_ENA_CHAN_0                   0x0001  /* DCS_ENA_CHAN_0 */
+#define WM8993_DCS_ENA_CHAN_0_MASK              0x0001  /* DCS_ENA_CHAN_0 */
+#define WM8993_DCS_ENA_CHAN_0_SHIFT                  0  /* DCS_ENA_CHAN_0 */
+#define WM8993_DCS_ENA_CHAN_0_WIDTH                  1  /* DCS_ENA_CHAN_0 */
+
+/*
+ * R85 (0x55) - DC Servo 1
+ */
+#define WM8993_DCS_SERIES_NO_01_MASK            0x0FE0  /* DCS_SERIES_NO_01 - [11:5] */
+#define WM8993_DCS_SERIES_NO_01_SHIFT                5  /* DCS_SERIES_NO_01 - [11:5] */
+#define WM8993_DCS_SERIES_NO_01_WIDTH                7  /* DCS_SERIES_NO_01 - [11:5] */
+#define WM8993_DCS_TIMER_PERIOD_01_MASK         0x000F  /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8993_DCS_TIMER_PERIOD_01_SHIFT             0  /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM8993_DCS_TIMER_PERIOD_01_WIDTH             4  /* DCS_TIMER_PERIOD_01 - [3:0] */
+
+/*
+ * R87 (0x57) - DC Servo 3
+ */
+#define WM8993_DCS_DAC_WR_VAL_1_MASK            0xFF00  /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8993_DCS_DAC_WR_VAL_1_SHIFT                8  /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8993_DCS_DAC_WR_VAL_1_WIDTH                8  /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM8993_DCS_DAC_WR_VAL_0_MASK            0x00FF  /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8993_DCS_DAC_WR_VAL_0_SHIFT                0  /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM8993_DCS_DAC_WR_VAL_0_WIDTH                8  /* DCS_DAC_WR_VAL_0 - [7:0] */
+
+/*
+ * R88 (0x58) - DC Servo Readback 0
+ */
+#define WM8993_DCS_DATAPATH_BUSY                0x4000  /* DCS_DATAPATH_BUSY */
+#define WM8993_DCS_DATAPATH_BUSY_MASK           0x4000  /* DCS_DATAPATH_BUSY */
+#define WM8993_DCS_DATAPATH_BUSY_SHIFT              14  /* DCS_DATAPATH_BUSY */
+#define WM8993_DCS_DATAPATH_BUSY_WIDTH               1  /* DCS_DATAPATH_BUSY */
+#define WM8993_DCS_CHANNEL_MASK                 0x3000  /* DCS_CHANNEL - [13:12] */
+#define WM8993_DCS_CHANNEL_SHIFT                    12  /* DCS_CHANNEL - [13:12] */
+#define WM8993_DCS_CHANNEL_WIDTH                     2  /* DCS_CHANNEL - [13:12] */
+#define WM8993_DCS_CAL_COMPLETE_MASK            0x0300  /* DCS_CAL_COMPLETE - [9:8] */
+#define WM8993_DCS_CAL_COMPLETE_SHIFT                8  /* DCS_CAL_COMPLETE - [9:8] */
+#define WM8993_DCS_CAL_COMPLETE_WIDTH                2  /* DCS_CAL_COMPLETE - [9:8] */
+#define WM8993_DCS_DAC_WR_COMPLETE_MASK         0x0030  /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM8993_DCS_DAC_WR_COMPLETE_SHIFT             4  /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM8993_DCS_DAC_WR_COMPLETE_WIDTH             2  /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM8993_DCS_STARTUP_COMPLETE_MASK        0x0003  /* DCS_STARTUP_COMPLETE - [1:0] */
+#define WM8993_DCS_STARTUP_COMPLETE_SHIFT            0  /* DCS_STARTUP_COMPLETE - [1:0] */
+#define WM8993_DCS_STARTUP_COMPLETE_WIDTH            2  /* DCS_STARTUP_COMPLETE - [1:0] */
+
+/*
+ * R89 (0x59) - DC Servo Readback 1
+ */
+#define WM8993_DCS_INTEG_CHAN_1_MASK            0x00FF  /* DCS_INTEG_CHAN_1 - [7:0] */
+#define WM8993_DCS_INTEG_CHAN_1_SHIFT                0  /* DCS_INTEG_CHAN_1 - [7:0] */
+#define WM8993_DCS_INTEG_CHAN_1_WIDTH                8  /* DCS_INTEG_CHAN_1 - [7:0] */
+
+/*
+ * R90 (0x5A) - DC Servo Readback 2
+ */
+#define WM8993_DCS_INTEG_CHAN_0_MASK            0x00FF  /* DCS_INTEG_CHAN_0 - [7:0] */
+#define WM8993_DCS_INTEG_CHAN_0_SHIFT                0  /* DCS_INTEG_CHAN_0 - [7:0] */
+#define WM8993_DCS_INTEG_CHAN_0_WIDTH                8  /* DCS_INTEG_CHAN_0 - [7:0] */
+
+/*
+ * R96 (0x60) - Analogue HP 0
+ */
+#define WM8993_HPOUT1_AUTO_PU                   0x0100  /* HPOUT1_AUTO_PU */
+#define WM8993_HPOUT1_AUTO_PU_MASK              0x0100  /* HPOUT1_AUTO_PU */
+#define WM8993_HPOUT1_AUTO_PU_SHIFT                  8  /* HPOUT1_AUTO_PU */
+#define WM8993_HPOUT1_AUTO_PU_WIDTH                  1  /* HPOUT1_AUTO_PU */
+#define WM8993_HPOUT1L_RMV_SHORT                0x0080  /* HPOUT1L_RMV_SHORT */
+#define WM8993_HPOUT1L_RMV_SHORT_MASK           0x0080  /* HPOUT1L_RMV_SHORT */
+#define WM8993_HPOUT1L_RMV_SHORT_SHIFT               7  /* HPOUT1L_RMV_SHORT */
+#define WM8993_HPOUT1L_RMV_SHORT_WIDTH               1  /* HPOUT1L_RMV_SHORT */
+#define WM8993_HPOUT1L_OUTP                     0x0040  /* HPOUT1L_OUTP */
+#define WM8993_HPOUT1L_OUTP_MASK                0x0040  /* HPOUT1L_OUTP */
+#define WM8993_HPOUT1L_OUTP_SHIFT                    6  /* HPOUT1L_OUTP */
+#define WM8993_HPOUT1L_OUTP_WIDTH                    1  /* HPOUT1L_OUTP */
+#define WM8993_HPOUT1L_DLY                      0x0020  /* HPOUT1L_DLY */
+#define WM8993_HPOUT1L_DLY_MASK                 0x0020  /* HPOUT1L_DLY */
+#define WM8993_HPOUT1L_DLY_SHIFT                     5  /* HPOUT1L_DLY */
+#define WM8993_HPOUT1L_DLY_WIDTH                     1  /* HPOUT1L_DLY */
+#define WM8993_HPOUT1R_RMV_SHORT                0x0008  /* HPOUT1R_RMV_SHORT */
+#define WM8993_HPOUT1R_RMV_SHORT_MASK           0x0008  /* HPOUT1R_RMV_SHORT */
+#define WM8993_HPOUT1R_RMV_SHORT_SHIFT               3  /* HPOUT1R_RMV_SHORT */
+#define WM8993_HPOUT1R_RMV_SHORT_WIDTH               1  /* HPOUT1R_RMV_SHORT */
+#define WM8993_HPOUT1R_OUTP                     0x0004  /* HPOUT1R_OUTP */
+#define WM8993_HPOUT1R_OUTP_MASK                0x0004  /* HPOUT1R_OUTP */
+#define WM8993_HPOUT1R_OUTP_SHIFT                    2  /* HPOUT1R_OUTP */
+#define WM8993_HPOUT1R_OUTP_WIDTH                    1  /* HPOUT1R_OUTP */
+#define WM8993_HPOUT1R_DLY                      0x0002  /* HPOUT1R_DLY */
+#define WM8993_HPOUT1R_DLY_MASK                 0x0002  /* HPOUT1R_DLY */
+#define WM8993_HPOUT1R_DLY_SHIFT                     1  /* HPOUT1R_DLY */
+#define WM8993_HPOUT1R_DLY_WIDTH                     1  /* HPOUT1R_DLY */
+
+/*
+ * R98 (0x62) - EQ1
+ */
+#define WM8993_EQ_ENA                           0x0001  /* EQ_ENA */
+#define WM8993_EQ_ENA_MASK                      0x0001  /* EQ_ENA */
+#define WM8993_EQ_ENA_SHIFT                          0  /* EQ_ENA */
+#define WM8993_EQ_ENA_WIDTH                          1  /* EQ_ENA */
+
+/*
+ * R99 (0x63) - EQ2
+ */
+#define WM8993_EQ_B1_GAIN_MASK                  0x001F  /* EQ_B1_GAIN - [4:0] */
+#define WM8993_EQ_B1_GAIN_SHIFT                      0  /* EQ_B1_GAIN - [4:0] */
+#define WM8993_EQ_B1_GAIN_WIDTH                      5  /* EQ_B1_GAIN - [4:0] */
+
+/*
+ * R100 (0x64) - EQ3
+ */
+#define WM8993_EQ_B2_GAIN_MASK                  0x001F  /* EQ_B2_GAIN - [4:0] */
+#define WM8993_EQ_B2_GAIN_SHIFT                      0  /* EQ_B2_GAIN - [4:0] */
+#define WM8993_EQ_B2_GAIN_WIDTH                      5  /* EQ_B2_GAIN - [4:0] */
+
+/*
+ * R101 (0x65) - EQ4
+ */
+#define WM8993_EQ_B3_GAIN_MASK                  0x001F  /* EQ_B3_GAIN - [4:0] */
+#define WM8993_EQ_B3_GAIN_SHIFT                      0  /* EQ_B3_GAIN - [4:0] */
+#define WM8993_EQ_B3_GAIN_WIDTH                      5  /* EQ_B3_GAIN - [4:0] */
+
+/*
+ * R102 (0x66) - EQ5
+ */
+#define WM8993_EQ_B4_GAIN_MASK                  0x001F  /* EQ_B4_GAIN - [4:0] */
+#define WM8993_EQ_B4_GAIN_SHIFT                      0  /* EQ_B4_GAIN - [4:0] */
+#define WM8993_EQ_B4_GAIN_WIDTH                      5  /* EQ_B4_GAIN - [4:0] */
+
+/*
+ * R103 (0x67) - EQ6
+ */
+#define WM8993_EQ_B5_GAIN_MASK                  0x001F  /* EQ_B5_GAIN - [4:0] */
+#define WM8993_EQ_B5_GAIN_SHIFT                      0  /* EQ_B5_GAIN - [4:0] */
+#define WM8993_EQ_B5_GAIN_WIDTH                      5  /* EQ_B5_GAIN - [4:0] */
+
+/*
+ * R104 (0x68) - EQ7
+ */
+#define WM8993_EQ_B1_A_MASK                     0xFFFF  /* EQ_B1_A - [15:0] */
+#define WM8993_EQ_B1_A_SHIFT                         0  /* EQ_B1_A - [15:0] */
+#define WM8993_EQ_B1_A_WIDTH                        16  /* EQ_B1_A - [15:0] */
+
+/*
+ * R105 (0x69) - EQ8
+ */
+#define WM8993_EQ_B1_B_MASK                     0xFFFF  /* EQ_B1_B - [15:0] */
+#define WM8993_EQ_B1_B_SHIFT                         0  /* EQ_B1_B - [15:0] */
+#define WM8993_EQ_B1_B_WIDTH                        16  /* EQ_B1_B - [15:0] */
+
+/*
+ * R106 (0x6A) - EQ9
+ */
+#define WM8993_EQ_B1_PG_MASK                    0xFFFF  /* EQ_B1_PG - [15:0] */
+#define WM8993_EQ_B1_PG_SHIFT                        0  /* EQ_B1_PG - [15:0] */
+#define WM8993_EQ_B1_PG_WIDTH                       16  /* EQ_B1_PG - [15:0] */
+
+/*
+ * R107 (0x6B) - EQ10
+ */
+#define WM8993_EQ_B2_A_MASK                     0xFFFF  /* EQ_B2_A - [15:0] */
+#define WM8993_EQ_B2_A_SHIFT                         0  /* EQ_B2_A - [15:0] */
+#define WM8993_EQ_B2_A_WIDTH                        16  /* EQ_B2_A - [15:0] */
+
+/*
+ * R108 (0x6C) - EQ11
+ */
+#define WM8993_EQ_B2_B_MASK                     0xFFFF  /* EQ_B2_B - [15:0] */
+#define WM8993_EQ_B2_B_SHIFT                         0  /* EQ_B2_B - [15:0] */
+#define WM8993_EQ_B2_B_WIDTH                        16  /* EQ_B2_B - [15:0] */
+
+/*
+ * R109 (0x6D) - EQ12
+ */
+#define WM8993_EQ_B2_C_MASK                     0xFFFF  /* EQ_B2_C - [15:0] */
+#define WM8993_EQ_B2_C_SHIFT                         0  /* EQ_B2_C - [15:0] */
+#define WM8993_EQ_B2_C_WIDTH                        16  /* EQ_B2_C - [15:0] */
+
+/*
+ * R110 (0x6E) - EQ13
+ */
+#define WM8993_EQ_B2_PG_MASK                    0xFFFF  /* EQ_B2_PG - [15:0] */
+#define WM8993_EQ_B2_PG_SHIFT                        0  /* EQ_B2_PG - [15:0] */
+#define WM8993_EQ_B2_PG_WIDTH                       16  /* EQ_B2_PG - [15:0] */
+
+/*
+ * R111 (0x6F) - EQ14
+ */
+#define WM8993_EQ_B3_A_MASK                     0xFFFF  /* EQ_B3_A - [15:0] */
+#define WM8993_EQ_B3_A_SHIFT                         0  /* EQ_B3_A - [15:0] */
+#define WM8993_EQ_B3_A_WIDTH                        16  /* EQ_B3_A - [15:0] */
+
+/*
+ * R112 (0x70) - EQ15
+ */
+#define WM8993_EQ_B3_B_MASK                     0xFFFF  /* EQ_B3_B - [15:0] */
+#define WM8993_EQ_B3_B_SHIFT                         0  /* EQ_B3_B - [15:0] */
+#define WM8993_EQ_B3_B_WIDTH                        16  /* EQ_B3_B - [15:0] */
+
+/*
+ * R113 (0x71) - EQ16
+ */
+#define WM8993_EQ_B3_C_MASK                     0xFFFF  /* EQ_B3_C - [15:0] */
+#define WM8993_EQ_B3_C_SHIFT                         0  /* EQ_B3_C - [15:0] */
+#define WM8993_EQ_B3_C_WIDTH                        16  /* EQ_B3_C - [15:0] */
+
+/*
+ * R114 (0x72) - EQ17
+ */
+#define WM8993_EQ_B3_PG_MASK                    0xFFFF  /* EQ_B3_PG - [15:0] */
+#define WM8993_EQ_B3_PG_SHIFT                        0  /* EQ_B3_PG - [15:0] */
+#define WM8993_EQ_B3_PG_WIDTH                       16  /* EQ_B3_PG - [15:0] */
+
+/*
+ * R115 (0x73) - EQ18
+ */
+#define WM8993_EQ_B4_A_MASK                     0xFFFF  /* EQ_B4_A - [15:0] */
+#define WM8993_EQ_B4_A_SHIFT                         0  /* EQ_B4_A - [15:0] */
+#define WM8993_EQ_B4_A_WIDTH                        16  /* EQ_B4_A - [15:0] */
+
+/*
+ * R116 (0x74) - EQ19
+ */
+#define WM8993_EQ_B4_B_MASK                     0xFFFF  /* EQ_B4_B - [15:0] */
+#define WM8993_EQ_B4_B_SHIFT                         0  /* EQ_B4_B - [15:0] */
+#define WM8993_EQ_B4_B_WIDTH                        16  /* EQ_B4_B - [15:0] */
+
+/*
+ * R117 (0x75) - EQ20
+ */
+#define WM8993_EQ_B4_C_MASK                     0xFFFF  /* EQ_B4_C - [15:0] */
+#define WM8993_EQ_B4_C_SHIFT                         0  /* EQ_B4_C - [15:0] */
+#define WM8993_EQ_B4_C_WIDTH                        16  /* EQ_B4_C - [15:0] */
+
+/*
+ * R118 (0x76) - EQ21
+ */
+#define WM8993_EQ_B4_PG_MASK                    0xFFFF  /* EQ_B4_PG - [15:0] */
+#define WM8993_EQ_B4_PG_SHIFT                        0  /* EQ_B4_PG - [15:0] */
+#define WM8993_EQ_B4_PG_WIDTH                       16  /* EQ_B4_PG - [15:0] */
+
+/*
+ * R119 (0x77) - EQ22
+ */
+#define WM8993_EQ_B5_A_MASK                     0xFFFF  /* EQ_B5_A - [15:0] */
+#define WM8993_EQ_B5_A_SHIFT                         0  /* EQ_B5_A - [15:0] */
+#define WM8993_EQ_B5_A_WIDTH                        16  /* EQ_B5_A - [15:0] */
+
+/*
+ * R120 (0x78) - EQ23
+ */
+#define WM8993_EQ_B5_B_MASK                     0xFFFF  /* EQ_B5_B - [15:0] */
+#define WM8993_EQ_B5_B_SHIFT                         0  /* EQ_B5_B - [15:0] */
+#define WM8993_EQ_B5_B_WIDTH                        16  /* EQ_B5_B - [15:0] */
+
+/*
+ * R121 (0x79) - EQ24
+ */
+#define WM8993_EQ_B5_PG_MASK                    0xFFFF  /* EQ_B5_PG - [15:0] */
+#define WM8993_EQ_B5_PG_SHIFT                        0  /* EQ_B5_PG - [15:0] */
+#define WM8993_EQ_B5_PG_WIDTH                       16  /* EQ_B5_PG - [15:0] */
+
+/*
+ * R122 (0x7A) - Digital Pulls
+ */
+#define WM8993_MCLK_PU                          0x0080  /* MCLK_PU */
+#define WM8993_MCLK_PU_MASK                     0x0080  /* MCLK_PU */
+#define WM8993_MCLK_PU_SHIFT                         7  /* MCLK_PU */
+#define WM8993_MCLK_PU_WIDTH                         1  /* MCLK_PU */
+#define WM8993_MCLK_PD                          0x0040  /* MCLK_PD */
+#define WM8993_MCLK_PD_MASK                     0x0040  /* MCLK_PD */
+#define WM8993_MCLK_PD_SHIFT                         6  /* MCLK_PD */
+#define WM8993_MCLK_PD_WIDTH                         1  /* MCLK_PD */
+#define WM8993_DACDAT_PU                        0x0020  /* DACDAT_PU */
+#define WM8993_DACDAT_PU_MASK                   0x0020  /* DACDAT_PU */
+#define WM8993_DACDAT_PU_SHIFT                       5  /* DACDAT_PU */
+#define WM8993_DACDAT_PU_WIDTH                       1  /* DACDAT_PU */
+#define WM8993_DACDAT_PD                        0x0010  /* DACDAT_PD */
+#define WM8993_DACDAT_PD_MASK                   0x0010  /* DACDAT_PD */
+#define WM8993_DACDAT_PD_SHIFT                       4  /* DACDAT_PD */
+#define WM8993_DACDAT_PD_WIDTH                       1  /* DACDAT_PD */
+#define WM8993_LRCLK_PU                         0x0008  /* LRCLK_PU */
+#define WM8993_LRCLK_PU_MASK                    0x0008  /* LRCLK_PU */
+#define WM8993_LRCLK_PU_SHIFT                        3  /* LRCLK_PU */
+#define WM8993_LRCLK_PU_WIDTH                        1  /* LRCLK_PU */
+#define WM8993_LRCLK_PD                         0x0004  /* LRCLK_PD */
+#define WM8993_LRCLK_PD_MASK                    0x0004  /* LRCLK_PD */
+#define WM8993_LRCLK_PD_SHIFT                        2  /* LRCLK_PD */
+#define WM8993_LRCLK_PD_WIDTH                        1  /* LRCLK_PD */
+#define WM8993_BCLK_PU                          0x0002  /* BCLK_PU */
+#define WM8993_BCLK_PU_MASK                     0x0002  /* BCLK_PU */
+#define WM8993_BCLK_PU_SHIFT                         1  /* BCLK_PU */
+#define WM8993_BCLK_PU_WIDTH                         1  /* BCLK_PU */
+#define WM8993_BCLK_PD                          0x0001  /* BCLK_PD */
+#define WM8993_BCLK_PD_MASK                     0x0001  /* BCLK_PD */
+#define WM8993_BCLK_PD_SHIFT                         0  /* BCLK_PD */
+#define WM8993_BCLK_PD_WIDTH                         1  /* BCLK_PD */
+
+/*
+ * R123 (0x7B) - DRC Control 1
+ */
+#define WM8993_DRC_ENA                          0x8000  /* DRC_ENA */
+#define WM8993_DRC_ENA_MASK                     0x8000  /* DRC_ENA */
+#define WM8993_DRC_ENA_SHIFT                        15  /* DRC_ENA */
+#define WM8993_DRC_ENA_WIDTH                         1  /* DRC_ENA */
+#define WM8993_DRC_DAC_PATH                     0x4000  /* DRC_DAC_PATH */
+#define WM8993_DRC_DAC_PATH_MASK                0x4000  /* DRC_DAC_PATH */
+#define WM8993_DRC_DAC_PATH_SHIFT                   14  /* DRC_DAC_PATH */
+#define WM8993_DRC_DAC_PATH_WIDTH                    1  /* DRC_DAC_PATH */
+#define WM8993_DRC_SMOOTH_ENA                   0x0800  /* DRC_SMOOTH_ENA */
+#define WM8993_DRC_SMOOTH_ENA_MASK              0x0800  /* DRC_SMOOTH_ENA */
+#define WM8993_DRC_SMOOTH_ENA_SHIFT                 11  /* DRC_SMOOTH_ENA */
+#define WM8993_DRC_SMOOTH_ENA_WIDTH                  1  /* DRC_SMOOTH_ENA */
+#define WM8993_DRC_QR_ENA                       0x0400  /* DRC_QR_ENA */
+#define WM8993_DRC_QR_ENA_MASK                  0x0400  /* DRC_QR_ENA */
+#define WM8993_DRC_QR_ENA_SHIFT                     10  /* DRC_QR_ENA */
+#define WM8993_DRC_QR_ENA_WIDTH                      1  /* DRC_QR_ENA */
+#define WM8993_DRC_ANTICLIP_ENA                 0x0200  /* DRC_ANTICLIP_ENA */
+#define WM8993_DRC_ANTICLIP_ENA_MASK            0x0200  /* DRC_ANTICLIP_ENA */
+#define WM8993_DRC_ANTICLIP_ENA_SHIFT                9  /* DRC_ANTICLIP_ENA */
+#define WM8993_DRC_ANTICLIP_ENA_WIDTH                1  /* DRC_ANTICLIP_ENA */
+#define WM8993_DRC_HYST_ENA                     0x0100  /* DRC_HYST_ENA */
+#define WM8993_DRC_HYST_ENA_MASK                0x0100  /* DRC_HYST_ENA */
+#define WM8993_DRC_HYST_ENA_SHIFT                    8  /* DRC_HYST_ENA */
+#define WM8993_DRC_HYST_ENA_WIDTH                    1  /* DRC_HYST_ENA */
+#define WM8993_DRC_THRESH_HYST_MASK             0x0030  /* DRC_THRESH_HYST - [5:4] */
+#define WM8993_DRC_THRESH_HYST_SHIFT                 4  /* DRC_THRESH_HYST - [5:4] */
+#define WM8993_DRC_THRESH_HYST_WIDTH                 2  /* DRC_THRESH_HYST - [5:4] */
+#define WM8993_DRC_MINGAIN_MASK                 0x000C  /* DRC_MINGAIN - [3:2] */
+#define WM8993_DRC_MINGAIN_SHIFT                     2  /* DRC_MINGAIN - [3:2] */
+#define WM8993_DRC_MINGAIN_WIDTH                     2  /* DRC_MINGAIN - [3:2] */
+#define WM8993_DRC_MAXGAIN_MASK                 0x0003  /* DRC_MAXGAIN - [1:0] */
+#define WM8993_DRC_MAXGAIN_SHIFT                     0  /* DRC_MAXGAIN - [1:0] */
+#define WM8993_DRC_MAXGAIN_WIDTH                     2  /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R124 (0x7C) - DRC Control 2
+ */
+#define WM8993_DRC_ATTACK_RATE_MASK             0xF000  /* DRC_ATTACK_RATE - [15:12] */
+#define WM8993_DRC_ATTACK_RATE_SHIFT                12  /* DRC_ATTACK_RATE - [15:12] */
+#define WM8993_DRC_ATTACK_RATE_WIDTH                 4  /* DRC_ATTACK_RATE - [15:12] */
+#define WM8993_DRC_DECAY_RATE_MASK              0x0F00  /* DRC_DECAY_RATE - [11:8] */
+#define WM8993_DRC_DECAY_RATE_SHIFT                  8  /* DRC_DECAY_RATE - [11:8] */
+#define WM8993_DRC_DECAY_RATE_WIDTH                  4  /* DRC_DECAY_RATE - [11:8] */
+#define WM8993_DRC_THRESH_COMP_MASK             0x00FC  /* DRC_THRESH_COMP - [7:2] */
+#define WM8993_DRC_THRESH_COMP_SHIFT                 2  /* DRC_THRESH_COMP - [7:2] */
+#define WM8993_DRC_THRESH_COMP_WIDTH                 6  /* DRC_THRESH_COMP - [7:2] */
+
+/*
+ * R125 (0x7D) - DRC Control 3
+ */
+#define WM8993_DRC_AMP_COMP_MASK                0xF800  /* DRC_AMP_COMP - [15:11] */
+#define WM8993_DRC_AMP_COMP_SHIFT                   11  /* DRC_AMP_COMP - [15:11] */
+#define WM8993_DRC_AMP_COMP_WIDTH                    5  /* DRC_AMP_COMP - [15:11] */
+#define WM8993_DRC_R0_SLOPE_COMP_MASK           0x0700  /* DRC_R0_SLOPE_COMP - [10:8] */
+#define WM8993_DRC_R0_SLOPE_COMP_SHIFT               8  /* DRC_R0_SLOPE_COMP - [10:8] */
+#define WM8993_DRC_R0_SLOPE_COMP_WIDTH               3  /* DRC_R0_SLOPE_COMP - [10:8] */
+#define WM8993_DRC_FF_DELAY                     0x0080  /* DRC_FF_DELAY */
+#define WM8993_DRC_FF_DELAY_MASK                0x0080  /* DRC_FF_DELAY */
+#define WM8993_DRC_FF_DELAY_SHIFT                    7  /* DRC_FF_DELAY */
+#define WM8993_DRC_FF_DELAY_WIDTH                    1  /* DRC_FF_DELAY */
+#define WM8993_DRC_THRESH_QR_MASK               0x000C  /* DRC_THRESH_QR - [3:2] */
+#define WM8993_DRC_THRESH_QR_SHIFT                   2  /* DRC_THRESH_QR - [3:2] */
+#define WM8993_DRC_THRESH_QR_WIDTH                   2  /* DRC_THRESH_QR - [3:2] */
+#define WM8993_DRC_RATE_QR_MASK                 0x0003  /* DRC_RATE_QR - [1:0] */
+#define WM8993_DRC_RATE_QR_SHIFT                     0  /* DRC_RATE_QR - [1:0] */
+#define WM8993_DRC_RATE_QR_WIDTH                     2  /* DRC_RATE_QR - [1:0] */
+
+/*
+ * R126 (0x7E) - DRC Control 4
+ */
+#define WM8993_DRC_R1_SLOPE_COMP_MASK           0xE000  /* DRC_R1_SLOPE_COMP - [15:13] */
+#define WM8993_DRC_R1_SLOPE_COMP_SHIFT              13  /* DRC_R1_SLOPE_COMP - [15:13] */
+#define WM8993_DRC_R1_SLOPE_COMP_WIDTH               3  /* DRC_R1_SLOPE_COMP - [15:13] */
+#define WM8993_DRC_STARTUP_GAIN_MASK            0x1F00  /* DRC_STARTUP_GAIN - [12:8] */
+#define WM8993_DRC_STARTUP_GAIN_SHIFT                8  /* DRC_STARTUP_GAIN - [12:8] */
+#define WM8993_DRC_STARTUP_GAIN_WIDTH                5  /* DRC_STARTUP_GAIN - [12:8] */
+
+#endif
diff --git a/linux/sound/soc/codecs/wm8994.c b/linux/sound/soc/codecs/wm8994.c
new file mode 100644
index 0000000..4d3e6f1
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8994.c
@@ -0,0 +1,4120 @@
+/*
+ * wm8994.c  --  WM8994 ALSA SoC Audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <linux/mfd/wm8994/core.h>
+#include <linux/mfd/wm8994/registers.h>
+#include <linux/mfd/wm8994/pdata.h>
+#include <linux/mfd/wm8994/gpio.h>
+
+#include "wm8994.h"
+#include "wm_hubs.h"
+
+struct fll_config {
+	int src;
+	int in;
+	int out;
+};
+
+#define WM8994_NUM_DRC 3
+#define WM8994_NUM_EQ  3
+
+static int wm8994_drc_base[] = {
+	WM8994_AIF1_DRC1_1,
+	WM8994_AIF1_DRC2_1,
+	WM8994_AIF2_DRC_1,
+};
+
+static int wm8994_retune_mobile_base[] = {
+	WM8994_AIF1_DAC1_EQ_GAINS_1,
+	WM8994_AIF1_DAC2_EQ_GAINS_1,
+	WM8994_AIF2_EQ_GAINS_1,
+};
+
+#define WM8994_REG_CACHE_SIZE  0x621
+
+struct wm8994_micdet {
+	struct snd_soc_jack *jack;
+	int det;
+	int shrt;
+};
+
+/* codec private data */
+struct wm8994_priv {
+	struct wm_hubs_data hubs;
+	enum snd_soc_control_type control_type;
+	void *control_data;
+	struct snd_soc_codec *codec;
+	u16 reg_cache[WM8994_REG_CACHE_SIZE + 1];
+	int sysclk[2];
+	int sysclk_rate[2];
+	int mclk[2];
+	int aifclk[2];
+	struct fll_config fll[2], fll_suspend[2];
+
+	int dac_rates[2];
+	int lrclk_shared[2];
+
+	/* Platform dependant DRC configuration */
+	const char **drc_texts;
+	int drc_cfg[WM8994_NUM_DRC];
+	struct soc_enum drc_enum;
+
+	/* Platform dependant ReTune mobile configuration */
+	int num_retune_mobile_texts;
+	const char **retune_mobile_texts;
+	int retune_mobile_cfg[WM8994_NUM_EQ];
+	struct soc_enum retune_mobile_enum;
+
+	struct wm8994_micdet micdet[2];
+
+	int revision;
+	struct wm8994_pdata *pdata;
+};
+
+static const struct {
+	unsigned short readable;   /* Mask of readable bits */
+	unsigned short writable;   /* Mask of writable bits */
+} access_masks[] = {
+	{ 0xFFFF, 0xFFFF }, /* R0     - Software Reset */
+	{ 0x3B37, 0x3B37 }, /* R1     - Power Management (1) */
+	{ 0x6BF0, 0x6BF0 }, /* R2     - Power Management (2) */
+	{ 0x3FF0, 0x3FF0 }, /* R3     - Power Management (3) */
+	{ 0x3F3F, 0x3F3F }, /* R4     - Power Management (4) */
+	{ 0x3F0F, 0x3F0F }, /* R5     - Power Management (5) */
+	{ 0x003F, 0x003F }, /* R6     - Power Management (6) */
+	{ 0x0000, 0x0000 }, /* R7 */
+	{ 0x0000, 0x0000 }, /* R8 */
+	{ 0x0000, 0x0000 }, /* R9 */
+	{ 0x0000, 0x0000 }, /* R10 */
+	{ 0x0000, 0x0000 }, /* R11 */
+	{ 0x0000, 0x0000 }, /* R12 */
+	{ 0x0000, 0x0000 }, /* R13 */
+	{ 0x0000, 0x0000 }, /* R14 */
+	{ 0x0000, 0x0000 }, /* R15 */
+	{ 0x0000, 0x0000 }, /* R16 */
+	{ 0x0000, 0x0000 }, /* R17 */
+	{ 0x0000, 0x0000 }, /* R18 */
+	{ 0x0000, 0x0000 }, /* R19 */
+	{ 0x0000, 0x0000 }, /* R20 */
+	{ 0x01C0, 0x01C0 }, /* R21    - Input Mixer (1) */
+	{ 0x0000, 0x0000 }, /* R22 */
+	{ 0x0000, 0x0000 }, /* R23 */
+	{ 0x00DF, 0x01DF }, /* R24    - Left Line Input 1&2 Volume */
+	{ 0x00DF, 0x01DF }, /* R25    - Left Line Input 3&4 Volume */
+	{ 0x00DF, 0x01DF }, /* R26    - Right Line Input 1&2 Volume */
+	{ 0x00DF, 0x01DF }, /* R27    - Right Line Input 3&4 Volume */
+	{ 0x00FF, 0x01FF }, /* R28    - Left Output Volume */
+	{ 0x00FF, 0x01FF }, /* R29    - Right Output Volume */
+	{ 0x0077, 0x0077 }, /* R30    - Line Outputs Volume */
+	{ 0x0030, 0x0030 }, /* R31    - HPOUT2 Volume */
+	{ 0x00FF, 0x01FF }, /* R32    - Left OPGA Volume */
+	{ 0x00FF, 0x01FF }, /* R33    - Right OPGA Volume */
+	{ 0x007F, 0x007F }, /* R34    - SPKMIXL Attenuation */
+	{ 0x017F, 0x017F }, /* R35    - SPKMIXR Attenuation */
+	{ 0x003F, 0x003F }, /* R36    - SPKOUT Mixers */
+	{ 0x003F, 0x003F }, /* R37    - ClassD */
+	{ 0x00FF, 0x01FF }, /* R38    - Speaker Volume Left */
+	{ 0x00FF, 0x01FF }, /* R39    - Speaker Volume Right */
+	{ 0x00FF, 0x00FF }, /* R40    - Input Mixer (2) */
+	{ 0x01B7, 0x01B7 }, /* R41    - Input Mixer (3) */
+	{ 0x01B7, 0x01B7 }, /* R42    - Input Mixer (4) */
+	{ 0x01C7, 0x01C7 }, /* R43    - Input Mixer (5) */
+	{ 0x01C7, 0x01C7 }, /* R44    - Input Mixer (6) */
+	{ 0x01FF, 0x01FF }, /* R45    - Output Mixer (1) */
+	{ 0x01FF, 0x01FF }, /* R46    - Output Mixer (2) */
+	{ 0x0FFF, 0x0FFF }, /* R47    - Output Mixer (3) */
+	{ 0x0FFF, 0x0FFF }, /* R48    - Output Mixer (4) */
+	{ 0x0FFF, 0x0FFF }, /* R49    - Output Mixer (5) */
+	{ 0x0FFF, 0x0FFF }, /* R50    - Output Mixer (6) */
+	{ 0x0038, 0x0038 }, /* R51    - HPOUT2 Mixer */
+	{ 0x0077, 0x0077 }, /* R52    - Line Mixer (1) */
+	{ 0x0077, 0x0077 }, /* R53    - Line Mixer (2) */
+	{ 0x03FF, 0x03FF }, /* R54    - Speaker Mixer */
+	{ 0x00C1, 0x00C1 }, /* R55    - Additional Control */
+	{ 0x00F0, 0x00F0 }, /* R56    - AntiPOP (1) */
+	{ 0x01EF, 0x01EF }, /* R57    - AntiPOP (2) */
+	{ 0x00FF, 0x00FF }, /* R58    - MICBIAS */
+	{ 0x000F, 0x000F }, /* R59    - LDO 1 */
+	{ 0x0007, 0x0007 }, /* R60    - LDO 2 */
+	{ 0x0000, 0x0000 }, /* R61 */
+	{ 0x0000, 0x0000 }, /* R62 */
+	{ 0x0000, 0x0000 }, /* R63 */
+	{ 0x0000, 0x0000 }, /* R64 */
+	{ 0x0000, 0x0000 }, /* R65 */
+	{ 0x0000, 0x0000 }, /* R66 */
+	{ 0x0000, 0x0000 }, /* R67 */
+	{ 0x0000, 0x0000 }, /* R68 */
+	{ 0x0000, 0x0000 }, /* R69 */
+	{ 0x0000, 0x0000 }, /* R70 */
+	{ 0x0000, 0x0000 }, /* R71 */
+	{ 0x0000, 0x0000 }, /* R72 */
+	{ 0x0000, 0x0000 }, /* R73 */
+	{ 0x0000, 0x0000 }, /* R74 */
+	{ 0x0000, 0x0000 }, /* R75 */
+	{ 0x8000, 0x8000 }, /* R76    - Charge Pump (1) */
+	{ 0x0000, 0x0000 }, /* R77 */
+	{ 0x0000, 0x0000 }, /* R78 */
+	{ 0x0000, 0x0000 }, /* R79 */
+	{ 0x0000, 0x0000 }, /* R80 */
+	{ 0x0301, 0x0301 }, /* R81    - Class W (1) */
+	{ 0x0000, 0x0000 }, /* R82 */
+	{ 0x0000, 0x0000 }, /* R83 */
+	{ 0x333F, 0x333F }, /* R84    - DC Servo (1) */
+	{ 0x0FEF, 0x0FEF }, /* R85    - DC Servo (2) */
+	{ 0x0000, 0x0000 }, /* R86 */
+	{ 0xFFFF, 0xFFFF }, /* R87    - DC Servo (4) */
+	{ 0x0333, 0x0000 }, /* R88    - DC Servo Readback */
+	{ 0x0000, 0x0000 }, /* R89 */
+	{ 0x0000, 0x0000 }, /* R90 */
+	{ 0x0000, 0x0000 }, /* R91 */
+	{ 0x0000, 0x0000 }, /* R92 */
+	{ 0x0000, 0x0000 }, /* R93 */
+	{ 0x0000, 0x0000 }, /* R94 */
+	{ 0x0000, 0x0000 }, /* R95 */
+	{ 0x00EE, 0x00EE }, /* R96    - Analogue HP (1) */
+	{ 0x0000, 0x0000 }, /* R97 */
+	{ 0x0000, 0x0000 }, /* R98 */
+	{ 0x0000, 0x0000 }, /* R99 */
+	{ 0x0000, 0x0000 }, /* R100 */
+	{ 0x0000, 0x0000 }, /* R101 */
+	{ 0x0000, 0x0000 }, /* R102 */
+	{ 0x0000, 0x0000 }, /* R103 */
+	{ 0x0000, 0x0000 }, /* R104 */
+	{ 0x0000, 0x0000 }, /* R105 */
+	{ 0x0000, 0x0000 }, /* R106 */
+	{ 0x0000, 0x0000 }, /* R107 */
+	{ 0x0000, 0x0000 }, /* R108 */
+	{ 0x0000, 0x0000 }, /* R109 */
+	{ 0x0000, 0x0000 }, /* R110 */
+	{ 0x0000, 0x0000 }, /* R111 */
+	{ 0x0000, 0x0000 }, /* R112 */
+	{ 0x0000, 0x0000 }, /* R113 */
+	{ 0x0000, 0x0000 }, /* R114 */
+	{ 0x0000, 0x0000 }, /* R115 */
+	{ 0x0000, 0x0000 }, /* R116 */
+	{ 0x0000, 0x0000 }, /* R117 */
+	{ 0x0000, 0x0000 }, /* R118 */
+	{ 0x0000, 0x0000 }, /* R119 */
+	{ 0x0000, 0x0000 }, /* R120 */
+	{ 0x0000, 0x0000 }, /* R121 */
+	{ 0x0000, 0x0000 }, /* R122 */
+	{ 0x0000, 0x0000 }, /* R123 */
+	{ 0x0000, 0x0000 }, /* R124 */
+	{ 0x0000, 0x0000 }, /* R125 */
+	{ 0x0000, 0x0000 }, /* R126 */
+	{ 0x0000, 0x0000 }, /* R127 */
+	{ 0x0000, 0x0000 }, /* R128 */
+	{ 0x0000, 0x0000 }, /* R129 */
+	{ 0x0000, 0x0000 }, /* R130 */
+	{ 0x0000, 0x0000 }, /* R131 */
+	{ 0x0000, 0x0000 }, /* R132 */
+	{ 0x0000, 0x0000 }, /* R133 */
+	{ 0x0000, 0x0000 }, /* R134 */
+	{ 0x0000, 0x0000 }, /* R135 */
+	{ 0x0000, 0x0000 }, /* R136 */
+	{ 0x0000, 0x0000 }, /* R137 */
+	{ 0x0000, 0x0000 }, /* R138 */
+	{ 0x0000, 0x0000 }, /* R139 */
+	{ 0x0000, 0x0000 }, /* R140 */
+	{ 0x0000, 0x0000 }, /* R141 */
+	{ 0x0000, 0x0000 }, /* R142 */
+	{ 0x0000, 0x0000 }, /* R143 */
+	{ 0x0000, 0x0000 }, /* R144 */
+	{ 0x0000, 0x0000 }, /* R145 */
+	{ 0x0000, 0x0000 }, /* R146 */
+	{ 0x0000, 0x0000 }, /* R147 */
+	{ 0x0000, 0x0000 }, /* R148 */
+	{ 0x0000, 0x0000 }, /* R149 */
+	{ 0x0000, 0x0000 }, /* R150 */
+	{ 0x0000, 0x0000 }, /* R151 */
+	{ 0x0000, 0x0000 }, /* R152 */
+	{ 0x0000, 0x0000 }, /* R153 */
+	{ 0x0000, 0x0000 }, /* R154 */
+	{ 0x0000, 0x0000 }, /* R155 */
+	{ 0x0000, 0x0000 }, /* R156 */
+	{ 0x0000, 0x0000 }, /* R157 */
+	{ 0x0000, 0x0000 }, /* R158 */
+	{ 0x0000, 0x0000 }, /* R159 */
+	{ 0x0000, 0x0000 }, /* R160 */
+	{ 0x0000, 0x0000 }, /* R161 */
+	{ 0x0000, 0x0000 }, /* R162 */
+	{ 0x0000, 0x0000 }, /* R163 */
+	{ 0x0000, 0x0000 }, /* R164 */
+	{ 0x0000, 0x0000 }, /* R165 */
+	{ 0x0000, 0x0000 }, /* R166 */
+	{ 0x0000, 0x0000 }, /* R167 */
+	{ 0x0000, 0x0000 }, /* R168 */
+	{ 0x0000, 0x0000 }, /* R169 */
+	{ 0x0000, 0x0000 }, /* R170 */
+	{ 0x0000, 0x0000 }, /* R171 */
+	{ 0x0000, 0x0000 }, /* R172 */
+	{ 0x0000, 0x0000 }, /* R173 */
+	{ 0x0000, 0x0000 }, /* R174 */
+	{ 0x0000, 0x0000 }, /* R175 */
+	{ 0x0000, 0x0000 }, /* R176 */
+	{ 0x0000, 0x0000 }, /* R177 */
+	{ 0x0000, 0x0000 }, /* R178 */
+	{ 0x0000, 0x0000 }, /* R179 */
+	{ 0x0000, 0x0000 }, /* R180 */
+	{ 0x0000, 0x0000 }, /* R181 */
+	{ 0x0000, 0x0000 }, /* R182 */
+	{ 0x0000, 0x0000 }, /* R183 */
+	{ 0x0000, 0x0000 }, /* R184 */
+	{ 0x0000, 0x0000 }, /* R185 */
+	{ 0x0000, 0x0000 }, /* R186 */
+	{ 0x0000, 0x0000 }, /* R187 */
+	{ 0x0000, 0x0000 }, /* R188 */
+	{ 0x0000, 0x0000 }, /* R189 */
+	{ 0x0000, 0x0000 }, /* R190 */
+	{ 0x0000, 0x0000 }, /* R191 */
+	{ 0x0000, 0x0000 }, /* R192 */
+	{ 0x0000, 0x0000 }, /* R193 */
+	{ 0x0000, 0x0000 }, /* R194 */
+	{ 0x0000, 0x0000 }, /* R195 */
+	{ 0x0000, 0x0000 }, /* R196 */
+	{ 0x0000, 0x0000 }, /* R197 */
+	{ 0x0000, 0x0000 }, /* R198 */
+	{ 0x0000, 0x0000 }, /* R199 */
+	{ 0x0000, 0x0000 }, /* R200 */
+	{ 0x0000, 0x0000 }, /* R201 */
+	{ 0x0000, 0x0000 }, /* R202 */
+	{ 0x0000, 0x0000 }, /* R203 */
+	{ 0x0000, 0x0000 }, /* R204 */
+	{ 0x0000, 0x0000 }, /* R205 */
+	{ 0x0000, 0x0000 }, /* R206 */
+	{ 0x0000, 0x0000 }, /* R207 */
+	{ 0x0000, 0x0000 }, /* R208 */
+	{ 0x0000, 0x0000 }, /* R209 */
+	{ 0x0000, 0x0000 }, /* R210 */
+	{ 0x0000, 0x0000 }, /* R211 */
+	{ 0x0000, 0x0000 }, /* R212 */
+	{ 0x0000, 0x0000 }, /* R213 */
+	{ 0x0000, 0x0000 }, /* R214 */
+	{ 0x0000, 0x0000 }, /* R215 */
+	{ 0x0000, 0x0000 }, /* R216 */
+	{ 0x0000, 0x0000 }, /* R217 */
+	{ 0x0000, 0x0000 }, /* R218 */
+	{ 0x0000, 0x0000 }, /* R219 */
+	{ 0x0000, 0x0000 }, /* R220 */
+	{ 0x0000, 0x0000 }, /* R221 */
+	{ 0x0000, 0x0000 }, /* R222 */
+	{ 0x0000, 0x0000 }, /* R223 */
+	{ 0x0000, 0x0000 }, /* R224 */
+	{ 0x0000, 0x0000 }, /* R225 */
+	{ 0x0000, 0x0000 }, /* R226 */
+	{ 0x0000, 0x0000 }, /* R227 */
+	{ 0x0000, 0x0000 }, /* R228 */
+	{ 0x0000, 0x0000 }, /* R229 */
+	{ 0x0000, 0x0000 }, /* R230 */
+	{ 0x0000, 0x0000 }, /* R231 */
+	{ 0x0000, 0x0000 }, /* R232 */
+	{ 0x0000, 0x0000 }, /* R233 */
+	{ 0x0000, 0x0000 }, /* R234 */
+	{ 0x0000, 0x0000 }, /* R235 */
+	{ 0x0000, 0x0000 }, /* R236 */
+	{ 0x0000, 0x0000 }, /* R237 */
+	{ 0x0000, 0x0000 }, /* R238 */
+	{ 0x0000, 0x0000 }, /* R239 */
+	{ 0x0000, 0x0000 }, /* R240 */
+	{ 0x0000, 0x0000 }, /* R241 */
+	{ 0x0000, 0x0000 }, /* R242 */
+	{ 0x0000, 0x0000 }, /* R243 */
+	{ 0x0000, 0x0000 }, /* R244 */
+	{ 0x0000, 0x0000 }, /* R245 */
+	{ 0x0000, 0x0000 }, /* R246 */
+	{ 0x0000, 0x0000 }, /* R247 */
+	{ 0x0000, 0x0000 }, /* R248 */
+	{ 0x0000, 0x0000 }, /* R249 */
+	{ 0x0000, 0x0000 }, /* R250 */
+	{ 0x0000, 0x0000 }, /* R251 */
+	{ 0x0000, 0x0000 }, /* R252 */
+	{ 0x0000, 0x0000 }, /* R253 */
+	{ 0x0000, 0x0000 }, /* R254 */
+	{ 0x0000, 0x0000 }, /* R255 */
+	{ 0x000F, 0x0000 }, /* R256   - Chip Revision */
+	{ 0x0074, 0x0074 }, /* R257   - Control Interface */
+	{ 0x0000, 0x0000 }, /* R258 */
+	{ 0x0000, 0x0000 }, /* R259 */
+	{ 0x0000, 0x0000 }, /* R260 */
+	{ 0x0000, 0x0000 }, /* R261 */
+	{ 0x0000, 0x0000 }, /* R262 */
+	{ 0x0000, 0x0000 }, /* R263 */
+	{ 0x0000, 0x0000 }, /* R264 */
+	{ 0x0000, 0x0000 }, /* R265 */
+	{ 0x0000, 0x0000 }, /* R266 */
+	{ 0x0000, 0x0000 }, /* R267 */
+	{ 0x0000, 0x0000 }, /* R268 */
+	{ 0x0000, 0x0000 }, /* R269 */
+	{ 0x0000, 0x0000 }, /* R270 */
+	{ 0x0000, 0x0000 }, /* R271 */
+	{ 0x807F, 0x837F }, /* R272   - Write Sequencer Ctrl (1) */
+	{ 0x017F, 0x0000 }, /* R273   - Write Sequencer Ctrl (2) */
+	{ 0x0000, 0x0000 }, /* R274 */
+	{ 0x0000, 0x0000 }, /* R275 */
+	{ 0x0000, 0x0000 }, /* R276 */
+	{ 0x0000, 0x0000 }, /* R277 */
+	{ 0x0000, 0x0000 }, /* R278 */
+	{ 0x0000, 0x0000 }, /* R279 */
+	{ 0x0000, 0x0000 }, /* R280 */
+	{ 0x0000, 0x0000 }, /* R281 */
+	{ 0x0000, 0x0000 }, /* R282 */
+	{ 0x0000, 0x0000 }, /* R283 */
+	{ 0x0000, 0x0000 }, /* R284 */
+	{ 0x0000, 0x0000 }, /* R285 */
+	{ 0x0000, 0x0000 }, /* R286 */
+	{ 0x0000, 0x0000 }, /* R287 */
+	{ 0x0000, 0x0000 }, /* R288 */
+	{ 0x0000, 0x0000 }, /* R289 */
+	{ 0x0000, 0x0000 }, /* R290 */
+	{ 0x0000, 0x0000 }, /* R291 */
+	{ 0x0000, 0x0000 }, /* R292 */
+	{ 0x0000, 0x0000 }, /* R293 */
+	{ 0x0000, 0x0000 }, /* R294 */
+	{ 0x0000, 0x0000 }, /* R295 */
+	{ 0x0000, 0x0000 }, /* R296 */
+	{ 0x0000, 0x0000 }, /* R297 */
+	{ 0x0000, 0x0000 }, /* R298 */
+	{ 0x0000, 0x0000 }, /* R299 */
+	{ 0x0000, 0x0000 }, /* R300 */
+	{ 0x0000, 0x0000 }, /* R301 */
+	{ 0x0000, 0x0000 }, /* R302 */
+	{ 0x0000, 0x0000 }, /* R303 */
+	{ 0x0000, 0x0000 }, /* R304 */
+	{ 0x0000, 0x0000 }, /* R305 */
+	{ 0x0000, 0x0000 }, /* R306 */
+	{ 0x0000, 0x0000 }, /* R307 */
+	{ 0x0000, 0x0000 }, /* R308 */
+	{ 0x0000, 0x0000 }, /* R309 */
+	{ 0x0000, 0x0000 }, /* R310 */
+	{ 0x0000, 0x0000 }, /* R311 */
+	{ 0x0000, 0x0000 }, /* R312 */
+	{ 0x0000, 0x0000 }, /* R313 */
+	{ 0x0000, 0x0000 }, /* R314 */
+	{ 0x0000, 0x0000 }, /* R315 */
+	{ 0x0000, 0x0000 }, /* R316 */
+	{ 0x0000, 0x0000 }, /* R317 */
+	{ 0x0000, 0x0000 }, /* R318 */
+	{ 0x0000, 0x0000 }, /* R319 */
+	{ 0x0000, 0x0000 }, /* R320 */
+	{ 0x0000, 0x0000 }, /* R321 */
+	{ 0x0000, 0x0000 }, /* R322 */
+	{ 0x0000, 0x0000 }, /* R323 */
+	{ 0x0000, 0x0000 }, /* R324 */
+	{ 0x0000, 0x0000 }, /* R325 */
+	{ 0x0000, 0x0000 }, /* R326 */
+	{ 0x0000, 0x0000 }, /* R327 */
+	{ 0x0000, 0x0000 }, /* R328 */
+	{ 0x0000, 0x0000 }, /* R329 */
+	{ 0x0000, 0x0000 }, /* R330 */
+	{ 0x0000, 0x0000 }, /* R331 */
+	{ 0x0000, 0x0000 }, /* R332 */
+	{ 0x0000, 0x0000 }, /* R333 */
+	{ 0x0000, 0x0000 }, /* R334 */
+	{ 0x0000, 0x0000 }, /* R335 */
+	{ 0x0000, 0x0000 }, /* R336 */
+	{ 0x0000, 0x0000 }, /* R337 */
+	{ 0x0000, 0x0000 }, /* R338 */
+	{ 0x0000, 0x0000 }, /* R339 */
+	{ 0x0000, 0x0000 }, /* R340 */
+	{ 0x0000, 0x0000 }, /* R341 */
+	{ 0x0000, 0x0000 }, /* R342 */
+	{ 0x0000, 0x0000 }, /* R343 */
+	{ 0x0000, 0x0000 }, /* R344 */
+	{ 0x0000, 0x0000 }, /* R345 */
+	{ 0x0000, 0x0000 }, /* R346 */
+	{ 0x0000, 0x0000 }, /* R347 */
+	{ 0x0000, 0x0000 }, /* R348 */
+	{ 0x0000, 0x0000 }, /* R349 */
+	{ 0x0000, 0x0000 }, /* R350 */
+	{ 0x0000, 0x0000 }, /* R351 */
+	{ 0x0000, 0x0000 }, /* R352 */
+	{ 0x0000, 0x0000 }, /* R353 */
+	{ 0x0000, 0x0000 }, /* R354 */
+	{ 0x0000, 0x0000 }, /* R355 */
+	{ 0x0000, 0x0000 }, /* R356 */
+	{ 0x0000, 0x0000 }, /* R357 */
+	{ 0x0000, 0x0000 }, /* R358 */
+	{ 0x0000, 0x0000 }, /* R359 */
+	{ 0x0000, 0x0000 }, /* R360 */
+	{ 0x0000, 0x0000 }, /* R361 */
+	{ 0x0000, 0x0000 }, /* R362 */
+	{ 0x0000, 0x0000 }, /* R363 */
+	{ 0x0000, 0x0000 }, /* R364 */
+	{ 0x0000, 0x0000 }, /* R365 */
+	{ 0x0000, 0x0000 }, /* R366 */
+	{ 0x0000, 0x0000 }, /* R367 */
+	{ 0x0000, 0x0000 }, /* R368 */
+	{ 0x0000, 0x0000 }, /* R369 */
+	{ 0x0000, 0x0000 }, /* R370 */
+	{ 0x0000, 0x0000 }, /* R371 */
+	{ 0x0000, 0x0000 }, /* R372 */
+	{ 0x0000, 0x0000 }, /* R373 */
+	{ 0x0000, 0x0000 }, /* R374 */
+	{ 0x0000, 0x0000 }, /* R375 */
+	{ 0x0000, 0x0000 }, /* R376 */
+	{ 0x0000, 0x0000 }, /* R377 */
+	{ 0x0000, 0x0000 }, /* R378 */
+	{ 0x0000, 0x0000 }, /* R379 */
+	{ 0x0000, 0x0000 }, /* R380 */
+	{ 0x0000, 0x0000 }, /* R381 */
+	{ 0x0000, 0x0000 }, /* R382 */
+	{ 0x0000, 0x0000 }, /* R383 */
+	{ 0x0000, 0x0000 }, /* R384 */
+	{ 0x0000, 0x0000 }, /* R385 */
+	{ 0x0000, 0x0000 }, /* R386 */
+	{ 0x0000, 0x0000 }, /* R387 */
+	{ 0x0000, 0x0000 }, /* R388 */
+	{ 0x0000, 0x0000 }, /* R389 */
+	{ 0x0000, 0x0000 }, /* R390 */
+	{ 0x0000, 0x0000 }, /* R391 */
+	{ 0x0000, 0x0000 }, /* R392 */
+	{ 0x0000, 0x0000 }, /* R393 */
+	{ 0x0000, 0x0000 }, /* R394 */
+	{ 0x0000, 0x0000 }, /* R395 */
+	{ 0x0000, 0x0000 }, /* R396 */
+	{ 0x0000, 0x0000 }, /* R397 */
+	{ 0x0000, 0x0000 }, /* R398 */
+	{ 0x0000, 0x0000 }, /* R399 */
+	{ 0x0000, 0x0000 }, /* R400 */
+	{ 0x0000, 0x0000 }, /* R401 */
+	{ 0x0000, 0x0000 }, /* R402 */
+	{ 0x0000, 0x0000 }, /* R403 */
+	{ 0x0000, 0x0000 }, /* R404 */
+	{ 0x0000, 0x0000 }, /* R405 */
+	{ 0x0000, 0x0000 }, /* R406 */
+	{ 0x0000, 0x0000 }, /* R407 */
+	{ 0x0000, 0x0000 }, /* R408 */
+	{ 0x0000, 0x0000 }, /* R409 */
+	{ 0x0000, 0x0000 }, /* R410 */
+	{ 0x0000, 0x0000 }, /* R411 */
+	{ 0x0000, 0x0000 }, /* R412 */
+	{ 0x0000, 0x0000 }, /* R413 */
+	{ 0x0000, 0x0000 }, /* R414 */
+	{ 0x0000, 0x0000 }, /* R415 */
+	{ 0x0000, 0x0000 }, /* R416 */
+	{ 0x0000, 0x0000 }, /* R417 */
+	{ 0x0000, 0x0000 }, /* R418 */
+	{ 0x0000, 0x0000 }, /* R419 */
+	{ 0x0000, 0x0000 }, /* R420 */
+	{ 0x0000, 0x0000 }, /* R421 */
+	{ 0x0000, 0x0000 }, /* R422 */
+	{ 0x0000, 0x0000 }, /* R423 */
+	{ 0x0000, 0x0000 }, /* R424 */
+	{ 0x0000, 0x0000 }, /* R425 */
+	{ 0x0000, 0x0000 }, /* R426 */
+	{ 0x0000, 0x0000 }, /* R427 */
+	{ 0x0000, 0x0000 }, /* R428 */
+	{ 0x0000, 0x0000 }, /* R429 */
+	{ 0x0000, 0x0000 }, /* R430 */
+	{ 0x0000, 0x0000 }, /* R431 */
+	{ 0x0000, 0x0000 }, /* R432 */
+	{ 0x0000, 0x0000 }, /* R433 */
+	{ 0x0000, 0x0000 }, /* R434 */
+	{ 0x0000, 0x0000 }, /* R435 */
+	{ 0x0000, 0x0000 }, /* R436 */
+	{ 0x0000, 0x0000 }, /* R437 */
+	{ 0x0000, 0x0000 }, /* R438 */
+	{ 0x0000, 0x0000 }, /* R439 */
+	{ 0x0000, 0x0000 }, /* R440 */
+	{ 0x0000, 0x0000 }, /* R441 */
+	{ 0x0000, 0x0000 }, /* R442 */
+	{ 0x0000, 0x0000 }, /* R443 */
+	{ 0x0000, 0x0000 }, /* R444 */
+	{ 0x0000, 0x0000 }, /* R445 */
+	{ 0x0000, 0x0000 }, /* R446 */
+	{ 0x0000, 0x0000 }, /* R447 */
+	{ 0x0000, 0x0000 }, /* R448 */
+	{ 0x0000, 0x0000 }, /* R449 */
+	{ 0x0000, 0x0000 }, /* R450 */
+	{ 0x0000, 0x0000 }, /* R451 */
+	{ 0x0000, 0x0000 }, /* R452 */
+	{ 0x0000, 0x0000 }, /* R453 */
+	{ 0x0000, 0x0000 }, /* R454 */
+	{ 0x0000, 0x0000 }, /* R455 */
+	{ 0x0000, 0x0000 }, /* R456 */
+	{ 0x0000, 0x0000 }, /* R457 */
+	{ 0x0000, 0x0000 }, /* R458 */
+	{ 0x0000, 0x0000 }, /* R459 */
+	{ 0x0000, 0x0000 }, /* R460 */
+	{ 0x0000, 0x0000 }, /* R461 */
+	{ 0x0000, 0x0000 }, /* R462 */
+	{ 0x0000, 0x0000 }, /* R463 */
+	{ 0x0000, 0x0000 }, /* R464 */
+	{ 0x0000, 0x0000 }, /* R465 */
+	{ 0x0000, 0x0000 }, /* R466 */
+	{ 0x0000, 0x0000 }, /* R467 */
+	{ 0x0000, 0x0000 }, /* R468 */
+	{ 0x0000, 0x0000 }, /* R469 */
+	{ 0x0000, 0x0000 }, /* R470 */
+	{ 0x0000, 0x0000 }, /* R471 */
+	{ 0x0000, 0x0000 }, /* R472 */
+	{ 0x0000, 0x0000 }, /* R473 */
+	{ 0x0000, 0x0000 }, /* R474 */
+	{ 0x0000, 0x0000 }, /* R475 */
+	{ 0x0000, 0x0000 }, /* R476 */
+	{ 0x0000, 0x0000 }, /* R477 */
+	{ 0x0000, 0x0000 }, /* R478 */
+	{ 0x0000, 0x0000 }, /* R479 */
+	{ 0x0000, 0x0000 }, /* R480 */
+	{ 0x0000, 0x0000 }, /* R481 */
+	{ 0x0000, 0x0000 }, /* R482 */
+	{ 0x0000, 0x0000 }, /* R483 */
+	{ 0x0000, 0x0000 }, /* R484 */
+	{ 0x0000, 0x0000 }, /* R485 */
+	{ 0x0000, 0x0000 }, /* R486 */
+	{ 0x0000, 0x0000 }, /* R487 */
+	{ 0x0000, 0x0000 }, /* R488 */
+	{ 0x0000, 0x0000 }, /* R489 */
+	{ 0x0000, 0x0000 }, /* R490 */
+	{ 0x0000, 0x0000 }, /* R491 */
+	{ 0x0000, 0x0000 }, /* R492 */
+	{ 0x0000, 0x0000 }, /* R493 */
+	{ 0x0000, 0x0000 }, /* R494 */
+	{ 0x0000, 0x0000 }, /* R495 */
+	{ 0x0000, 0x0000 }, /* R496 */
+	{ 0x0000, 0x0000 }, /* R497 */
+	{ 0x0000, 0x0000 }, /* R498 */
+	{ 0x0000, 0x0000 }, /* R499 */
+	{ 0x0000, 0x0000 }, /* R500 */
+	{ 0x0000, 0x0000 }, /* R501 */
+	{ 0x0000, 0x0000 }, /* R502 */
+	{ 0x0000, 0x0000 }, /* R503 */
+	{ 0x0000, 0x0000 }, /* R504 */
+	{ 0x0000, 0x0000 }, /* R505 */
+	{ 0x0000, 0x0000 }, /* R506 */
+	{ 0x0000, 0x0000 }, /* R507 */
+	{ 0x0000, 0x0000 }, /* R508 */
+	{ 0x0000, 0x0000 }, /* R509 */
+	{ 0x0000, 0x0000 }, /* R510 */
+	{ 0x0000, 0x0000 }, /* R511 */
+	{ 0x001F, 0x001F }, /* R512   - AIF1 Clocking (1) */
+	{ 0x003F, 0x003F }, /* R513   - AIF1 Clocking (2) */
+	{ 0x0000, 0x0000 }, /* R514 */
+	{ 0x0000, 0x0000 }, /* R515 */
+	{ 0x001F, 0x001F }, /* R516   - AIF2 Clocking (1) */
+	{ 0x003F, 0x003F }, /* R517   - AIF2 Clocking (2) */
+	{ 0x0000, 0x0000 }, /* R518 */
+	{ 0x0000, 0x0000 }, /* R519 */
+	{ 0x001F, 0x001F }, /* R520   - Clocking (1) */
+	{ 0x0777, 0x0777 }, /* R521   - Clocking (2) */
+	{ 0x0000, 0x0000 }, /* R522 */
+	{ 0x0000, 0x0000 }, /* R523 */
+	{ 0x0000, 0x0000 }, /* R524 */
+	{ 0x0000, 0x0000 }, /* R525 */
+	{ 0x0000, 0x0000 }, /* R526 */
+	{ 0x0000, 0x0000 }, /* R527 */
+	{ 0x00FF, 0x00FF }, /* R528   - AIF1 Rate */
+	{ 0x00FF, 0x00FF }, /* R529   - AIF2 Rate */
+	{ 0x000F, 0x0000 }, /* R530   - Rate Status */
+	{ 0x0000, 0x0000 }, /* R531 */
+	{ 0x0000, 0x0000 }, /* R532 */
+	{ 0x0000, 0x0000 }, /* R533 */
+	{ 0x0000, 0x0000 }, /* R534 */
+	{ 0x0000, 0x0000 }, /* R535 */
+	{ 0x0000, 0x0000 }, /* R536 */
+	{ 0x0000, 0x0000 }, /* R537 */
+	{ 0x0000, 0x0000 }, /* R538 */
+	{ 0x0000, 0x0000 }, /* R539 */
+	{ 0x0000, 0x0000 }, /* R540 */
+	{ 0x0000, 0x0000 }, /* R541 */
+	{ 0x0000, 0x0000 }, /* R542 */
+	{ 0x0000, 0x0000 }, /* R543 */
+	{ 0x0007, 0x0007 }, /* R544   - FLL1 Control (1) */
+	{ 0x3F77, 0x3F77 }, /* R545   - FLL1 Control (2) */
+	{ 0xFFFF, 0xFFFF }, /* R546   - FLL1 Control (3) */
+	{ 0x7FEF, 0x7FEF }, /* R547   - FLL1 Control (4) */
+	{ 0x1FDB, 0x1FDB }, /* R548   - FLL1 Control (5) */
+	{ 0x0000, 0x0000 }, /* R549 */
+	{ 0x0000, 0x0000 }, /* R550 */
+	{ 0x0000, 0x0000 }, /* R551 */
+	{ 0x0000, 0x0000 }, /* R552 */
+	{ 0x0000, 0x0000 }, /* R553 */
+	{ 0x0000, 0x0000 }, /* R554 */
+	{ 0x0000, 0x0000 }, /* R555 */
+	{ 0x0000, 0x0000 }, /* R556 */
+	{ 0x0000, 0x0000 }, /* R557 */
+	{ 0x0000, 0x0000 }, /* R558 */
+	{ 0x0000, 0x0000 }, /* R559 */
+	{ 0x0000, 0x0000 }, /* R560 */
+	{ 0x0000, 0x0000 }, /* R561 */
+	{ 0x0000, 0x0000 }, /* R562 */
+	{ 0x0000, 0x0000 }, /* R563 */
+	{ 0x0000, 0x0000 }, /* R564 */
+	{ 0x0000, 0x0000 }, /* R565 */
+	{ 0x0000, 0x0000 }, /* R566 */
+	{ 0x0000, 0x0000 }, /* R567 */
+	{ 0x0000, 0x0000 }, /* R568 */
+	{ 0x0000, 0x0000 }, /* R569 */
+	{ 0x0000, 0x0000 }, /* R570 */
+	{ 0x0000, 0x0000 }, /* R571 */
+	{ 0x0000, 0x0000 }, /* R572 */
+	{ 0x0000, 0x0000 }, /* R573 */
+	{ 0x0000, 0x0000 }, /* R574 */
+	{ 0x0000, 0x0000 }, /* R575 */
+	{ 0x0007, 0x0007 }, /* R576   - FLL2 Control (1) */
+	{ 0x3F77, 0x3F77 }, /* R577   - FLL2 Control (2) */
+	{ 0xFFFF, 0xFFFF }, /* R578   - FLL2 Control (3) */
+	{ 0x7FEF, 0x7FEF }, /* R579   - FLL2 Control (4) */
+	{ 0x1FDB, 0x1FDB }, /* R580   - FLL2 Control (5) */
+	{ 0x0000, 0x0000 }, /* R581 */
+	{ 0x0000, 0x0000 }, /* R582 */
+	{ 0x0000, 0x0000 }, /* R583 */
+	{ 0x0000, 0x0000 }, /* R584 */
+	{ 0x0000, 0x0000 }, /* R585 */
+	{ 0x0000, 0x0000 }, /* R586 */
+	{ 0x0000, 0x0000 }, /* R587 */
+	{ 0x0000, 0x0000 }, /* R588 */
+	{ 0x0000, 0x0000 }, /* R589 */
+	{ 0x0000, 0x0000 }, /* R590 */
+	{ 0x0000, 0x0000 }, /* R591 */
+	{ 0x0000, 0x0000 }, /* R592 */
+	{ 0x0000, 0x0000 }, /* R593 */
+	{ 0x0000, 0x0000 }, /* R594 */
+	{ 0x0000, 0x0000 }, /* R595 */
+	{ 0x0000, 0x0000 }, /* R596 */
+	{ 0x0000, 0x0000 }, /* R597 */
+	{ 0x0000, 0x0000 }, /* R598 */
+	{ 0x0000, 0x0000 }, /* R599 */
+	{ 0x0000, 0x0000 }, /* R600 */
+	{ 0x0000, 0x0000 }, /* R601 */
+	{ 0x0000, 0x0000 }, /* R602 */
+	{ 0x0000, 0x0000 }, /* R603 */
+	{ 0x0000, 0x0000 }, /* R604 */
+	{ 0x0000, 0x0000 }, /* R605 */
+	{ 0x0000, 0x0000 }, /* R606 */
+	{ 0x0000, 0x0000 }, /* R607 */
+	{ 0x0000, 0x0000 }, /* R608 */
+	{ 0x0000, 0x0000 }, /* R609 */
+	{ 0x0000, 0x0000 }, /* R610 */
+	{ 0x0000, 0x0000 }, /* R611 */
+	{ 0x0000, 0x0000 }, /* R612 */
+	{ 0x0000, 0x0000 }, /* R613 */
+	{ 0x0000, 0x0000 }, /* R614 */
+	{ 0x0000, 0x0000 }, /* R615 */
+	{ 0x0000, 0x0000 }, /* R616 */
+	{ 0x0000, 0x0000 }, /* R617 */
+	{ 0x0000, 0x0000 }, /* R618 */
+	{ 0x0000, 0x0000 }, /* R619 */
+	{ 0x0000, 0x0000 }, /* R620 */
+	{ 0x0000, 0x0000 }, /* R621 */
+	{ 0x0000, 0x0000 }, /* R622 */
+	{ 0x0000, 0x0000 }, /* R623 */
+	{ 0x0000, 0x0000 }, /* R624 */
+	{ 0x0000, 0x0000 }, /* R625 */
+	{ 0x0000, 0x0000 }, /* R626 */
+	{ 0x0000, 0x0000 }, /* R627 */
+	{ 0x0000, 0x0000 }, /* R628 */
+	{ 0x0000, 0x0000 }, /* R629 */
+	{ 0x0000, 0x0000 }, /* R630 */
+	{ 0x0000, 0x0000 }, /* R631 */
+	{ 0x0000, 0x0000 }, /* R632 */
+	{ 0x0000, 0x0000 }, /* R633 */
+	{ 0x0000, 0x0000 }, /* R634 */
+	{ 0x0000, 0x0000 }, /* R635 */
+	{ 0x0000, 0x0000 }, /* R636 */
+	{ 0x0000, 0x0000 }, /* R637 */
+	{ 0x0000, 0x0000 }, /* R638 */
+	{ 0x0000, 0x0000 }, /* R639 */
+	{ 0x0000, 0x0000 }, /* R640 */
+	{ 0x0000, 0x0000 }, /* R641 */
+	{ 0x0000, 0x0000 }, /* R642 */
+	{ 0x0000, 0x0000 }, /* R643 */
+	{ 0x0000, 0x0000 }, /* R644 */
+	{ 0x0000, 0x0000 }, /* R645 */
+	{ 0x0000, 0x0000 }, /* R646 */
+	{ 0x0000, 0x0000 }, /* R647 */
+	{ 0x0000, 0x0000 }, /* R648 */
+	{ 0x0000, 0x0000 }, /* R649 */
+	{ 0x0000, 0x0000 }, /* R650 */
+	{ 0x0000, 0x0000 }, /* R651 */
+	{ 0x0000, 0x0000 }, /* R652 */
+	{ 0x0000, 0x0000 }, /* R653 */
+	{ 0x0000, 0x0000 }, /* R654 */
+	{ 0x0000, 0x0000 }, /* R655 */
+	{ 0x0000, 0x0000 }, /* R656 */
+	{ 0x0000, 0x0000 }, /* R657 */
+	{ 0x0000, 0x0000 }, /* R658 */
+	{ 0x0000, 0x0000 }, /* R659 */
+	{ 0x0000, 0x0000 }, /* R660 */
+	{ 0x0000, 0x0000 }, /* R661 */
+	{ 0x0000, 0x0000 }, /* R662 */
+	{ 0x0000, 0x0000 }, /* R663 */
+	{ 0x0000, 0x0000 }, /* R664 */
+	{ 0x0000, 0x0000 }, /* R665 */
+	{ 0x0000, 0x0000 }, /* R666 */
+	{ 0x0000, 0x0000 }, /* R667 */
+	{ 0x0000, 0x0000 }, /* R668 */
+	{ 0x0000, 0x0000 }, /* R669 */
+	{ 0x0000, 0x0000 }, /* R670 */
+	{ 0x0000, 0x0000 }, /* R671 */
+	{ 0x0000, 0x0000 }, /* R672 */
+	{ 0x0000, 0x0000 }, /* R673 */
+	{ 0x0000, 0x0000 }, /* R674 */
+	{ 0x0000, 0x0000 }, /* R675 */
+	{ 0x0000, 0x0000 }, /* R676 */
+	{ 0x0000, 0x0000 }, /* R677 */
+	{ 0x0000, 0x0000 }, /* R678 */
+	{ 0x0000, 0x0000 }, /* R679 */
+	{ 0x0000, 0x0000 }, /* R680 */
+	{ 0x0000, 0x0000 }, /* R681 */
+	{ 0x0000, 0x0000 }, /* R682 */
+	{ 0x0000, 0x0000 }, /* R683 */
+	{ 0x0000, 0x0000 }, /* R684 */
+	{ 0x0000, 0x0000 }, /* R685 */
+	{ 0x0000, 0x0000 }, /* R686 */
+	{ 0x0000, 0x0000 }, /* R687 */
+	{ 0x0000, 0x0000 }, /* R688 */
+	{ 0x0000, 0x0000 }, /* R689 */
+	{ 0x0000, 0x0000 }, /* R690 */
+	{ 0x0000, 0x0000 }, /* R691 */
+	{ 0x0000, 0x0000 }, /* R692 */
+	{ 0x0000, 0x0000 }, /* R693 */
+	{ 0x0000, 0x0000 }, /* R694 */
+	{ 0x0000, 0x0000 }, /* R695 */
+	{ 0x0000, 0x0000 }, /* R696 */
+	{ 0x0000, 0x0000 }, /* R697 */
+	{ 0x0000, 0x0000 }, /* R698 */
+	{ 0x0000, 0x0000 }, /* R699 */
+	{ 0x0000, 0x0000 }, /* R700 */
+	{ 0x0000, 0x0000 }, /* R701 */
+	{ 0x0000, 0x0000 }, /* R702 */
+	{ 0x0000, 0x0000 }, /* R703 */
+	{ 0x0000, 0x0000 }, /* R704 */
+	{ 0x0000, 0x0000 }, /* R705 */
+	{ 0x0000, 0x0000 }, /* R706 */
+	{ 0x0000, 0x0000 }, /* R707 */
+	{ 0x0000, 0x0000 }, /* R708 */
+	{ 0x0000, 0x0000 }, /* R709 */
+	{ 0x0000, 0x0000 }, /* R710 */
+	{ 0x0000, 0x0000 }, /* R711 */
+	{ 0x0000, 0x0000 }, /* R712 */
+	{ 0x0000, 0x0000 }, /* R713 */
+	{ 0x0000, 0x0000 }, /* R714 */
+	{ 0x0000, 0x0000 }, /* R715 */
+	{ 0x0000, 0x0000 }, /* R716 */
+	{ 0x0000, 0x0000 }, /* R717 */
+	{ 0x0000, 0x0000 }, /* R718 */
+	{ 0x0000, 0x0000 }, /* R719 */
+	{ 0x0000, 0x0000 }, /* R720 */
+	{ 0x0000, 0x0000 }, /* R721 */
+	{ 0x0000, 0x0000 }, /* R722 */
+	{ 0x0000, 0x0000 }, /* R723 */
+	{ 0x0000, 0x0000 }, /* R724 */
+	{ 0x0000, 0x0000 }, /* R725 */
+	{ 0x0000, 0x0000 }, /* R726 */
+	{ 0x0000, 0x0000 }, /* R727 */
+	{ 0x0000, 0x0000 }, /* R728 */
+	{ 0x0000, 0x0000 }, /* R729 */
+	{ 0x0000, 0x0000 }, /* R730 */
+	{ 0x0000, 0x0000 }, /* R731 */
+	{ 0x0000, 0x0000 }, /* R732 */
+	{ 0x0000, 0x0000 }, /* R733 */
+	{ 0x0000, 0x0000 }, /* R734 */
+	{ 0x0000, 0x0000 }, /* R735 */
+	{ 0x0000, 0x0000 }, /* R736 */
+	{ 0x0000, 0x0000 }, /* R737 */
+	{ 0x0000, 0x0000 }, /* R738 */
+	{ 0x0000, 0x0000 }, /* R739 */
+	{ 0x0000, 0x0000 }, /* R740 */
+	{ 0x0000, 0x0000 }, /* R741 */
+	{ 0x0000, 0x0000 }, /* R742 */
+	{ 0x0000, 0x0000 }, /* R743 */
+	{ 0x0000, 0x0000 }, /* R744 */
+	{ 0x0000, 0x0000 }, /* R745 */
+	{ 0x0000, 0x0000 }, /* R746 */
+	{ 0x0000, 0x0000 }, /* R747 */
+	{ 0x0000, 0x0000 }, /* R748 */
+	{ 0x0000, 0x0000 }, /* R749 */
+	{ 0x0000, 0x0000 }, /* R750 */
+	{ 0x0000, 0x0000 }, /* R751 */
+	{ 0x0000, 0x0000 }, /* R752 */
+	{ 0x0000, 0x0000 }, /* R753 */
+	{ 0x0000, 0x0000 }, /* R754 */
+	{ 0x0000, 0x0000 }, /* R755 */
+	{ 0x0000, 0x0000 }, /* R756 */
+	{ 0x0000, 0x0000 }, /* R757 */
+	{ 0x0000, 0x0000 }, /* R758 */
+	{ 0x0000, 0x0000 }, /* R759 */
+	{ 0x0000, 0x0000 }, /* R760 */
+	{ 0x0000, 0x0000 }, /* R761 */
+	{ 0x0000, 0x0000 }, /* R762 */
+	{ 0x0000, 0x0000 }, /* R763 */
+	{ 0x0000, 0x0000 }, /* R764 */
+	{ 0x0000, 0x0000 }, /* R765 */
+	{ 0x0000, 0x0000 }, /* R766 */
+	{ 0x0000, 0x0000 }, /* R767 */
+	{ 0xE1F8, 0xE1F8 }, /* R768   - AIF1 Control (1) */
+	{ 0xCD1F, 0xCD1F }, /* R769   - AIF1 Control (2) */
+	{ 0xF000, 0xF000 }, /* R770   - AIF1 Master/Slave */
+	{ 0x01F0, 0x01F0 }, /* R771   - AIF1 BCLK */
+	{ 0x0FFF, 0x0FFF }, /* R772   - AIF1ADC LRCLK */
+	{ 0x0FFF, 0x0FFF }, /* R773   - AIF1DAC LRCLK */
+	{ 0x0003, 0x0003 }, /* R774   - AIF1DAC Data */
+	{ 0x0003, 0x0003 }, /* R775   - AIF1ADC Data */
+	{ 0x0000, 0x0000 }, /* R776 */
+	{ 0x0000, 0x0000 }, /* R777 */
+	{ 0x0000, 0x0000 }, /* R778 */
+	{ 0x0000, 0x0000 }, /* R779 */
+	{ 0x0000, 0x0000 }, /* R780 */
+	{ 0x0000, 0x0000 }, /* R781 */
+	{ 0x0000, 0x0000 }, /* R782 */
+	{ 0x0000, 0x0000 }, /* R783 */
+	{ 0xF1F8, 0xF1F8 }, /* R784   - AIF2 Control (1) */
+	{ 0xFD1F, 0xFD1F }, /* R785   - AIF2 Control (2) */
+	{ 0xF000, 0xF000 }, /* R786   - AIF2 Master/Slave */
+	{ 0x01F0, 0x01F0 }, /* R787   - AIF2 BCLK */
+	{ 0x0FFF, 0x0FFF }, /* R788   - AIF2ADC LRCLK */
+	{ 0x0FFF, 0x0FFF }, /* R789   - AIF2DAC LRCLK */
+	{ 0x0003, 0x0003 }, /* R790   - AIF2DAC Data */
+	{ 0x0003, 0x0003 }, /* R791   - AIF2ADC Data */
+	{ 0x0000, 0x0000 }, /* R792 */
+	{ 0x0000, 0x0000 }, /* R793 */
+	{ 0x0000, 0x0000 }, /* R794 */
+	{ 0x0000, 0x0000 }, /* R795 */
+	{ 0x0000, 0x0000 }, /* R796 */
+	{ 0x0000, 0x0000 }, /* R797 */
+	{ 0x0000, 0x0000 }, /* R798 */
+	{ 0x0000, 0x0000 }, /* R799 */
+	{ 0x0000, 0x0000 }, /* R800 */
+	{ 0x0000, 0x0000 }, /* R801 */
+	{ 0x0000, 0x0000 }, /* R802 */
+	{ 0x0000, 0x0000 }, /* R803 */
+	{ 0x0000, 0x0000 }, /* R804 */
+	{ 0x0000, 0x0000 }, /* R805 */
+	{ 0x0000, 0x0000 }, /* R806 */
+	{ 0x0000, 0x0000 }, /* R807 */
+	{ 0x0000, 0x0000 }, /* R808 */
+	{ 0x0000, 0x0000 }, /* R809 */
+	{ 0x0000, 0x0000 }, /* R810 */
+	{ 0x0000, 0x0000 }, /* R811 */
+	{ 0x0000, 0x0000 }, /* R812 */
+	{ 0x0000, 0x0000 }, /* R813 */
+	{ 0x0000, 0x0000 }, /* R814 */
+	{ 0x0000, 0x0000 }, /* R815 */
+	{ 0x0000, 0x0000 }, /* R816 */
+	{ 0x0000, 0x0000 }, /* R817 */
+	{ 0x0000, 0x0000 }, /* R818 */
+	{ 0x0000, 0x0000 }, /* R819 */
+	{ 0x0000, 0x0000 }, /* R820 */
+	{ 0x0000, 0x0000 }, /* R821 */
+	{ 0x0000, 0x0000 }, /* R822 */
+	{ 0x0000, 0x0000 }, /* R823 */
+	{ 0x0000, 0x0000 }, /* R824 */
+	{ 0x0000, 0x0000 }, /* R825 */
+	{ 0x0000, 0x0000 }, /* R826 */
+	{ 0x0000, 0x0000 }, /* R827 */
+	{ 0x0000, 0x0000 }, /* R828 */
+	{ 0x0000, 0x0000 }, /* R829 */
+	{ 0x0000, 0x0000 }, /* R830 */
+	{ 0x0000, 0x0000 }, /* R831 */
+	{ 0x0000, 0x0000 }, /* R832 */
+	{ 0x0000, 0x0000 }, /* R833 */
+	{ 0x0000, 0x0000 }, /* R834 */
+	{ 0x0000, 0x0000 }, /* R835 */
+	{ 0x0000, 0x0000 }, /* R836 */
+	{ 0x0000, 0x0000 }, /* R837 */
+	{ 0x0000, 0x0000 }, /* R838 */
+	{ 0x0000, 0x0000 }, /* R839 */
+	{ 0x0000, 0x0000 }, /* R840 */
+	{ 0x0000, 0x0000 }, /* R841 */
+	{ 0x0000, 0x0000 }, /* R842 */
+	{ 0x0000, 0x0000 }, /* R843 */
+	{ 0x0000, 0x0000 }, /* R844 */
+	{ 0x0000, 0x0000 }, /* R845 */
+	{ 0x0000, 0x0000 }, /* R846 */
+	{ 0x0000, 0x0000 }, /* R847 */
+	{ 0x0000, 0x0000 }, /* R848 */
+	{ 0x0000, 0x0000 }, /* R849 */
+	{ 0x0000, 0x0000 }, /* R850 */
+	{ 0x0000, 0x0000 }, /* R851 */
+	{ 0x0000, 0x0000 }, /* R852 */
+	{ 0x0000, 0x0000 }, /* R853 */
+	{ 0x0000, 0x0000 }, /* R854 */
+	{ 0x0000, 0x0000 }, /* R855 */
+	{ 0x0000, 0x0000 }, /* R856 */
+	{ 0x0000, 0x0000 }, /* R857 */
+	{ 0x0000, 0x0000 }, /* R858 */
+	{ 0x0000, 0x0000 }, /* R859 */
+	{ 0x0000, 0x0000 }, /* R860 */
+	{ 0x0000, 0x0000 }, /* R861 */
+	{ 0x0000, 0x0000 }, /* R862 */
+	{ 0x0000, 0x0000 }, /* R863 */
+	{ 0x0000, 0x0000 }, /* R864 */
+	{ 0x0000, 0x0000 }, /* R865 */
+	{ 0x0000, 0x0000 }, /* R866 */
+	{ 0x0000, 0x0000 }, /* R867 */
+	{ 0x0000, 0x0000 }, /* R868 */
+	{ 0x0000, 0x0000 }, /* R869 */
+	{ 0x0000, 0x0000 }, /* R870 */
+	{ 0x0000, 0x0000 }, /* R871 */
+	{ 0x0000, 0x0000 }, /* R872 */
+	{ 0x0000, 0x0000 }, /* R873 */
+	{ 0x0000, 0x0000 }, /* R874 */
+	{ 0x0000, 0x0000 }, /* R875 */
+	{ 0x0000, 0x0000 }, /* R876 */
+	{ 0x0000, 0x0000 }, /* R877 */
+	{ 0x0000, 0x0000 }, /* R878 */
+	{ 0x0000, 0x0000 }, /* R879 */
+	{ 0x0000, 0x0000 }, /* R880 */
+	{ 0x0000, 0x0000 }, /* R881 */
+	{ 0x0000, 0x0000 }, /* R882 */
+	{ 0x0000, 0x0000 }, /* R883 */
+	{ 0x0000, 0x0000 }, /* R884 */
+	{ 0x0000, 0x0000 }, /* R885 */
+	{ 0x0000, 0x0000 }, /* R886 */
+	{ 0x0000, 0x0000 }, /* R887 */
+	{ 0x0000, 0x0000 }, /* R888 */
+	{ 0x0000, 0x0000 }, /* R889 */
+	{ 0x0000, 0x0000 }, /* R890 */
+	{ 0x0000, 0x0000 }, /* R891 */
+	{ 0x0000, 0x0000 }, /* R892 */
+	{ 0x0000, 0x0000 }, /* R893 */
+	{ 0x0000, 0x0000 }, /* R894 */
+	{ 0x0000, 0x0000 }, /* R895 */
+	{ 0x0000, 0x0000 }, /* R896 */
+	{ 0x0000, 0x0000 }, /* R897 */
+	{ 0x0000, 0x0000 }, /* R898 */
+	{ 0x0000, 0x0000 }, /* R899 */
+	{ 0x0000, 0x0000 }, /* R900 */
+	{ 0x0000, 0x0000 }, /* R901 */
+	{ 0x0000, 0x0000 }, /* R902 */
+	{ 0x0000, 0x0000 }, /* R903 */
+	{ 0x0000, 0x0000 }, /* R904 */
+	{ 0x0000, 0x0000 }, /* R905 */
+	{ 0x0000, 0x0000 }, /* R906 */
+	{ 0x0000, 0x0000 }, /* R907 */
+	{ 0x0000, 0x0000 }, /* R908 */
+	{ 0x0000, 0x0000 }, /* R909 */
+	{ 0x0000, 0x0000 }, /* R910 */
+	{ 0x0000, 0x0000 }, /* R911 */
+	{ 0x0000, 0x0000 }, /* R912 */
+	{ 0x0000, 0x0000 }, /* R913 */
+	{ 0x0000, 0x0000 }, /* R914 */
+	{ 0x0000, 0x0000 }, /* R915 */
+	{ 0x0000, 0x0000 }, /* R916 */
+	{ 0x0000, 0x0000 }, /* R917 */
+	{ 0x0000, 0x0000 }, /* R918 */
+	{ 0x0000, 0x0000 }, /* R919 */
+	{ 0x0000, 0x0000 }, /* R920 */
+	{ 0x0000, 0x0000 }, /* R921 */
+	{ 0x0000, 0x0000 }, /* R922 */
+	{ 0x0000, 0x0000 }, /* R923 */
+	{ 0x0000, 0x0000 }, /* R924 */
+	{ 0x0000, 0x0000 }, /* R925 */
+	{ 0x0000, 0x0000 }, /* R926 */
+	{ 0x0000, 0x0000 }, /* R927 */
+	{ 0x0000, 0x0000 }, /* R928 */
+	{ 0x0000, 0x0000 }, /* R929 */
+	{ 0x0000, 0x0000 }, /* R930 */
+	{ 0x0000, 0x0000 }, /* R931 */
+	{ 0x0000, 0x0000 }, /* R932 */
+	{ 0x0000, 0x0000 }, /* R933 */
+	{ 0x0000, 0x0000 }, /* R934 */
+	{ 0x0000, 0x0000 }, /* R935 */
+	{ 0x0000, 0x0000 }, /* R936 */
+	{ 0x0000, 0x0000 }, /* R937 */
+	{ 0x0000, 0x0000 }, /* R938 */
+	{ 0x0000, 0x0000 }, /* R939 */
+	{ 0x0000, 0x0000 }, /* R940 */
+	{ 0x0000, 0x0000 }, /* R941 */
+	{ 0x0000, 0x0000 }, /* R942 */
+	{ 0x0000, 0x0000 }, /* R943 */
+	{ 0x0000, 0x0000 }, /* R944 */
+	{ 0x0000, 0x0000 }, /* R945 */
+	{ 0x0000, 0x0000 }, /* R946 */
+	{ 0x0000, 0x0000 }, /* R947 */
+	{ 0x0000, 0x0000 }, /* R948 */
+	{ 0x0000, 0x0000 }, /* R949 */
+	{ 0x0000, 0x0000 }, /* R950 */
+	{ 0x0000, 0x0000 }, /* R951 */
+	{ 0x0000, 0x0000 }, /* R952 */
+	{ 0x0000, 0x0000 }, /* R953 */
+	{ 0x0000, 0x0000 }, /* R954 */
+	{ 0x0000, 0x0000 }, /* R955 */
+	{ 0x0000, 0x0000 }, /* R956 */
+	{ 0x0000, 0x0000 }, /* R957 */
+	{ 0x0000, 0x0000 }, /* R958 */
+	{ 0x0000, 0x0000 }, /* R959 */
+	{ 0x0000, 0x0000 }, /* R960 */
+	{ 0x0000, 0x0000 }, /* R961 */
+	{ 0x0000, 0x0000 }, /* R962 */
+	{ 0x0000, 0x0000 }, /* R963 */
+	{ 0x0000, 0x0000 }, /* R964 */
+	{ 0x0000, 0x0000 }, /* R965 */
+	{ 0x0000, 0x0000 }, /* R966 */
+	{ 0x0000, 0x0000 }, /* R967 */
+	{ 0x0000, 0x0000 }, /* R968 */
+	{ 0x0000, 0x0000 }, /* R969 */
+	{ 0x0000, 0x0000 }, /* R970 */
+	{ 0x0000, 0x0000 }, /* R971 */
+	{ 0x0000, 0x0000 }, /* R972 */
+	{ 0x0000, 0x0000 }, /* R973 */
+	{ 0x0000, 0x0000 }, /* R974 */
+	{ 0x0000, 0x0000 }, /* R975 */
+	{ 0x0000, 0x0000 }, /* R976 */
+	{ 0x0000, 0x0000 }, /* R977 */
+	{ 0x0000, 0x0000 }, /* R978 */
+	{ 0x0000, 0x0000 }, /* R979 */
+	{ 0x0000, 0x0000 }, /* R980 */
+	{ 0x0000, 0x0000 }, /* R981 */
+	{ 0x0000, 0x0000 }, /* R982 */
+	{ 0x0000, 0x0000 }, /* R983 */
+	{ 0x0000, 0x0000 }, /* R984 */
+	{ 0x0000, 0x0000 }, /* R985 */
+	{ 0x0000, 0x0000 }, /* R986 */
+	{ 0x0000, 0x0000 }, /* R987 */
+	{ 0x0000, 0x0000 }, /* R988 */
+	{ 0x0000, 0x0000 }, /* R989 */
+	{ 0x0000, 0x0000 }, /* R990 */
+	{ 0x0000, 0x0000 }, /* R991 */
+	{ 0x0000, 0x0000 }, /* R992 */
+	{ 0x0000, 0x0000 }, /* R993 */
+	{ 0x0000, 0x0000 }, /* R994 */
+	{ 0x0000, 0x0000 }, /* R995 */
+	{ 0x0000, 0x0000 }, /* R996 */
+	{ 0x0000, 0x0000 }, /* R997 */
+	{ 0x0000, 0x0000 }, /* R998 */
+	{ 0x0000, 0x0000 }, /* R999 */
+	{ 0x0000, 0x0000 }, /* R1000 */
+	{ 0x0000, 0x0000 }, /* R1001 */
+	{ 0x0000, 0x0000 }, /* R1002 */
+	{ 0x0000, 0x0000 }, /* R1003 */
+	{ 0x0000, 0x0000 }, /* R1004 */
+	{ 0x0000, 0x0000 }, /* R1005 */
+	{ 0x0000, 0x0000 }, /* R1006 */
+	{ 0x0000, 0x0000 }, /* R1007 */
+	{ 0x0000, 0x0000 }, /* R1008 */
+	{ 0x0000, 0x0000 }, /* R1009 */
+	{ 0x0000, 0x0000 }, /* R1010 */
+	{ 0x0000, 0x0000 }, /* R1011 */
+	{ 0x0000, 0x0000 }, /* R1012 */
+	{ 0x0000, 0x0000 }, /* R1013 */
+	{ 0x0000, 0x0000 }, /* R1014 */
+	{ 0x0000, 0x0000 }, /* R1015 */
+	{ 0x0000, 0x0000 }, /* R1016 */
+	{ 0x0000, 0x0000 }, /* R1017 */
+	{ 0x0000, 0x0000 }, /* R1018 */
+	{ 0x0000, 0x0000 }, /* R1019 */
+	{ 0x0000, 0x0000 }, /* R1020 */
+	{ 0x0000, 0x0000 }, /* R1021 */
+	{ 0x0000, 0x0000 }, /* R1022 */
+	{ 0x0000, 0x0000 }, /* R1023 */
+	{ 0x00FF, 0x01FF }, /* R1024  - AIF1 ADC1 Left Volume */
+	{ 0x00FF, 0x01FF }, /* R1025  - AIF1 ADC1 Right Volume */
+	{ 0x00FF, 0x01FF }, /* R1026  - AIF1 DAC1 Left Volume */
+	{ 0x00FF, 0x01FF }, /* R1027  - AIF1 DAC1 Right Volume */
+	{ 0x00FF, 0x01FF }, /* R1028  - AIF1 ADC2 Left Volume */
+	{ 0x00FF, 0x01FF }, /* R1029  - AIF1 ADC2 Right Volume */
+	{ 0x00FF, 0x01FF }, /* R1030  - AIF1 DAC2 Left Volume */
+	{ 0x00FF, 0x01FF }, /* R1031  - AIF1 DAC2 Right Volume */
+	{ 0x0000, 0x0000 }, /* R1032 */
+	{ 0x0000, 0x0000 }, /* R1033 */
+	{ 0x0000, 0x0000 }, /* R1034 */
+	{ 0x0000, 0x0000 }, /* R1035 */
+	{ 0x0000, 0x0000 }, /* R1036 */
+	{ 0x0000, 0x0000 }, /* R1037 */
+	{ 0x0000, 0x0000 }, /* R1038 */
+	{ 0x0000, 0x0000 }, /* R1039 */
+	{ 0xF800, 0xF800 }, /* R1040  - AIF1 ADC1 Filters */
+	{ 0x7800, 0x7800 }, /* R1041  - AIF1 ADC2 Filters */
+	{ 0x0000, 0x0000 }, /* R1042 */
+	{ 0x0000, 0x0000 }, /* R1043 */
+	{ 0x0000, 0x0000 }, /* R1044 */
+	{ 0x0000, 0x0000 }, /* R1045 */
+	{ 0x0000, 0x0000 }, /* R1046 */
+	{ 0x0000, 0x0000 }, /* R1047 */
+	{ 0x0000, 0x0000 }, /* R1048 */
+	{ 0x0000, 0x0000 }, /* R1049 */
+	{ 0x0000, 0x0000 }, /* R1050 */
+	{ 0x0000, 0x0000 }, /* R1051 */
+	{ 0x0000, 0x0000 }, /* R1052 */
+	{ 0x0000, 0x0000 }, /* R1053 */
+	{ 0x0000, 0x0000 }, /* R1054 */
+	{ 0x0000, 0x0000 }, /* R1055 */
+	{ 0x02B6, 0x02B6 }, /* R1056  - AIF1 DAC1 Filters (1) */
+	{ 0x3F00, 0x3F00 }, /* R1057  - AIF1 DAC1 Filters (2) */
+	{ 0x02B6, 0x02B6 }, /* R1058  - AIF1 DAC2 Filters (1) */
+	{ 0x3F00, 0x3F00 }, /* R1059  - AIF1 DAC2 Filters (2) */
+	{ 0x0000, 0x0000 }, /* R1060 */
+	{ 0x0000, 0x0000 }, /* R1061 */
+	{ 0x0000, 0x0000 }, /* R1062 */
+	{ 0x0000, 0x0000 }, /* R1063 */
+	{ 0x0000, 0x0000 }, /* R1064 */
+	{ 0x0000, 0x0000 }, /* R1065 */
+	{ 0x0000, 0x0000 }, /* R1066 */
+	{ 0x0000, 0x0000 }, /* R1067 */
+	{ 0x0000, 0x0000 }, /* R1068 */
+	{ 0x0000, 0x0000 }, /* R1069 */
+	{ 0x0000, 0x0000 }, /* R1070 */
+	{ 0x0000, 0x0000 }, /* R1071 */
+	{ 0x0000, 0x0000 }, /* R1072 */
+	{ 0x0000, 0x0000 }, /* R1073 */
+	{ 0x0000, 0x0000 }, /* R1074 */
+	{ 0x0000, 0x0000 }, /* R1075 */
+	{ 0x0000, 0x0000 }, /* R1076 */
+	{ 0x0000, 0x0000 }, /* R1077 */
+	{ 0x0000, 0x0000 }, /* R1078 */
+	{ 0x0000, 0x0000 }, /* R1079 */
+	{ 0x0000, 0x0000 }, /* R1080 */
+	{ 0x0000, 0x0000 }, /* R1081 */
+	{ 0x0000, 0x0000 }, /* R1082 */
+	{ 0x0000, 0x0000 }, /* R1083 */
+	{ 0x0000, 0x0000 }, /* R1084 */
+	{ 0x0000, 0x0000 }, /* R1085 */
+	{ 0x0000, 0x0000 }, /* R1086 */
+	{ 0x0000, 0x0000 }, /* R1087 */
+	{ 0xFFFF, 0xFFFF }, /* R1088  - AIF1 DRC1 (1) */
+	{ 0x1FFF, 0x1FFF }, /* R1089  - AIF1 DRC1 (2) */
+	{ 0xFFFF, 0xFFFF }, /* R1090  - AIF1 DRC1 (3) */
+	{ 0x07FF, 0x07FF }, /* R1091  - AIF1 DRC1 (4) */
+	{ 0x03FF, 0x03FF }, /* R1092  - AIF1 DRC1 (5) */
+	{ 0x0000, 0x0000 }, /* R1093 */
+	{ 0x0000, 0x0000 }, /* R1094 */
+	{ 0x0000, 0x0000 }, /* R1095 */
+	{ 0x0000, 0x0000 }, /* R1096 */
+	{ 0x0000, 0x0000 }, /* R1097 */
+	{ 0x0000, 0x0000 }, /* R1098 */
+	{ 0x0000, 0x0000 }, /* R1099 */
+	{ 0x0000, 0x0000 }, /* R1100 */
+	{ 0x0000, 0x0000 }, /* R1101 */
+	{ 0x0000, 0x0000 }, /* R1102 */
+	{ 0x0000, 0x0000 }, /* R1103 */
+	{ 0xFFFF, 0xFFFF }, /* R1104  - AIF1 DRC2 (1) */
+	{ 0x1FFF, 0x1FFF }, /* R1105  - AIF1 DRC2 (2) */
+	{ 0xFFFF, 0xFFFF }, /* R1106  - AIF1 DRC2 (3) */
+	{ 0x07FF, 0x07FF }, /* R1107  - AIF1 DRC2 (4) */
+	{ 0x03FF, 0x03FF }, /* R1108  - AIF1 DRC2 (5) */
+	{ 0x0000, 0x0000 }, /* R1109 */
+	{ 0x0000, 0x0000 }, /* R1110 */
+	{ 0x0000, 0x0000 }, /* R1111 */
+	{ 0x0000, 0x0000 }, /* R1112 */
+	{ 0x0000, 0x0000 }, /* R1113 */
+	{ 0x0000, 0x0000 }, /* R1114 */
+	{ 0x0000, 0x0000 }, /* R1115 */
+	{ 0x0000, 0x0000 }, /* R1116 */
+	{ 0x0000, 0x0000 }, /* R1117 */
+	{ 0x0000, 0x0000 }, /* R1118 */
+	{ 0x0000, 0x0000 }, /* R1119 */
+	{ 0x0000, 0x0000 }, /* R1120 */
+	{ 0x0000, 0x0000 }, /* R1121 */
+	{ 0x0000, 0x0000 }, /* R1122 */
+	{ 0x0000, 0x0000 }, /* R1123 */
+	{ 0x0000, 0x0000 }, /* R1124 */
+	{ 0x0000, 0x0000 }, /* R1125 */
+	{ 0x0000, 0x0000 }, /* R1126 */
+	{ 0x0000, 0x0000 }, /* R1127 */
+	{ 0x0000, 0x0000 }, /* R1128 */
+	{ 0x0000, 0x0000 }, /* R1129 */
+	{ 0x0000, 0x0000 }, /* R1130 */
+	{ 0x0000, 0x0000 }, /* R1131 */
+	{ 0x0000, 0x0000 }, /* R1132 */
+	{ 0x0000, 0x0000 }, /* R1133 */
+	{ 0x0000, 0x0000 }, /* R1134 */
+	{ 0x0000, 0x0000 }, /* R1135 */
+	{ 0x0000, 0x0000 }, /* R1136 */
+	{ 0x0000, 0x0000 }, /* R1137 */
+	{ 0x0000, 0x0000 }, /* R1138 */
+	{ 0x0000, 0x0000 }, /* R1139 */
+	{ 0x0000, 0x0000 }, /* R1140 */
+	{ 0x0000, 0x0000 }, /* R1141 */
+	{ 0x0000, 0x0000 }, /* R1142 */
+	{ 0x0000, 0x0000 }, /* R1143 */
+	{ 0x0000, 0x0000 }, /* R1144 */
+	{ 0x0000, 0x0000 }, /* R1145 */
+	{ 0x0000, 0x0000 }, /* R1146 */
+	{ 0x0000, 0x0000 }, /* R1147 */
+	{ 0x0000, 0x0000 }, /* R1148 */
+	{ 0x0000, 0x0000 }, /* R1149 */
+	{ 0x0000, 0x0000 }, /* R1150 */
+	{ 0x0000, 0x0000 }, /* R1151 */
+	{ 0xFFFF, 0xFFFF }, /* R1152  - AIF1 DAC1 EQ Gains (1) */
+	{ 0xFFC0, 0xFFC0 }, /* R1153  - AIF1 DAC1 EQ Gains (2) */
+	{ 0xFFFF, 0xFFFF }, /* R1154  - AIF1 DAC1 EQ Band 1 A */
+	{ 0xFFFF, 0xFFFF }, /* R1155  - AIF1 DAC1 EQ Band 1 B */
+	{ 0xFFFF, 0xFFFF }, /* R1156  - AIF1 DAC1 EQ Band 1 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1157  - AIF1 DAC1 EQ Band 2 A */
+	{ 0xFFFF, 0xFFFF }, /* R1158  - AIF1 DAC1 EQ Band 2 B */
+	{ 0xFFFF, 0xFFFF }, /* R1159  - AIF1 DAC1 EQ Band 2 C */
+	{ 0xFFFF, 0xFFFF }, /* R1160  - AIF1 DAC1 EQ Band 2 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1161  - AIF1 DAC1 EQ Band 3 A */
+	{ 0xFFFF, 0xFFFF }, /* R1162  - AIF1 DAC1 EQ Band 3 B */
+	{ 0xFFFF, 0xFFFF }, /* R1163  - AIF1 DAC1 EQ Band 3 C */
+	{ 0xFFFF, 0xFFFF }, /* R1164  - AIF1 DAC1 EQ Band 3 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1165  - AIF1 DAC1 EQ Band 4 A */
+	{ 0xFFFF, 0xFFFF }, /* R1166  - AIF1 DAC1 EQ Band 4 B */
+	{ 0xFFFF, 0xFFFF }, /* R1167  - AIF1 DAC1 EQ Band 4 C */
+	{ 0xFFFF, 0xFFFF }, /* R1168  - AIF1 DAC1 EQ Band 4 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1169  - AIF1 DAC1 EQ Band 5 A */
+	{ 0xFFFF, 0xFFFF }, /* R1170  - AIF1 DAC1 EQ Band 5 B */
+	{ 0xFFFF, 0xFFFF }, /* R1171  - AIF1 DAC1 EQ Band 5 PG */
+	{ 0x0000, 0x0000 }, /* R1172 */
+	{ 0x0000, 0x0000 }, /* R1173 */
+	{ 0x0000, 0x0000 }, /* R1174 */
+	{ 0x0000, 0x0000 }, /* R1175 */
+	{ 0x0000, 0x0000 }, /* R1176 */
+	{ 0x0000, 0x0000 }, /* R1177 */
+	{ 0x0000, 0x0000 }, /* R1178 */
+	{ 0x0000, 0x0000 }, /* R1179 */
+	{ 0x0000, 0x0000 }, /* R1180 */
+	{ 0x0000, 0x0000 }, /* R1181 */
+	{ 0x0000, 0x0000 }, /* R1182 */
+	{ 0x0000, 0x0000 }, /* R1183 */
+	{ 0xFFFF, 0xFFFF }, /* R1184  - AIF1 DAC2 EQ Gains (1) */
+	{ 0xFFC0, 0xFFC0 }, /* R1185  - AIF1 DAC2 EQ Gains (2) */
+	{ 0xFFFF, 0xFFFF }, /* R1186  - AIF1 DAC2 EQ Band 1 A */
+	{ 0xFFFF, 0xFFFF }, /* R1187  - AIF1 DAC2 EQ Band 1 B */
+	{ 0xFFFF, 0xFFFF }, /* R1188  - AIF1 DAC2 EQ Band 1 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1189  - AIF1 DAC2 EQ Band 2 A */
+	{ 0xFFFF, 0xFFFF }, /* R1190  - AIF1 DAC2 EQ Band 2 B */
+	{ 0xFFFF, 0xFFFF }, /* R1191  - AIF1 DAC2 EQ Band 2 C */
+	{ 0xFFFF, 0xFFFF }, /* R1192  - AIF1 DAC2 EQ Band 2 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1193  - AIF1 DAC2 EQ Band 3 A */
+	{ 0xFFFF, 0xFFFF }, /* R1194  - AIF1 DAC2 EQ Band 3 B */
+	{ 0xFFFF, 0xFFFF }, /* R1195  - AIF1 DAC2 EQ Band 3 C */
+	{ 0xFFFF, 0xFFFF }, /* R1196  - AIF1 DAC2 EQ Band 3 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1197  - AIF1 DAC2 EQ Band 4 A */
+	{ 0xFFFF, 0xFFFF }, /* R1198  - AIF1 DAC2 EQ Band 4 B */
+	{ 0xFFFF, 0xFFFF }, /* R1199  - AIF1 DAC2 EQ Band 4 C */
+	{ 0xFFFF, 0xFFFF }, /* R1200  - AIF1 DAC2 EQ Band 4 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1201  - AIF1 DAC2 EQ Band 5 A */
+	{ 0xFFFF, 0xFFFF }, /* R1202  - AIF1 DAC2 EQ Band 5 B */
+	{ 0xFFFF, 0xFFFF }, /* R1203  - AIF1 DAC2 EQ Band 5 PG */
+	{ 0x0000, 0x0000 }, /* R1204 */
+	{ 0x0000, 0x0000 }, /* R1205 */
+	{ 0x0000, 0x0000 }, /* R1206 */
+	{ 0x0000, 0x0000 }, /* R1207 */
+	{ 0x0000, 0x0000 }, /* R1208 */
+	{ 0x0000, 0x0000 }, /* R1209 */
+	{ 0x0000, 0x0000 }, /* R1210 */
+	{ 0x0000, 0x0000 }, /* R1211 */
+	{ 0x0000, 0x0000 }, /* R1212 */
+	{ 0x0000, 0x0000 }, /* R1213 */
+	{ 0x0000, 0x0000 }, /* R1214 */
+	{ 0x0000, 0x0000 }, /* R1215 */
+	{ 0x0000, 0x0000 }, /* R1216 */
+	{ 0x0000, 0x0000 }, /* R1217 */
+	{ 0x0000, 0x0000 }, /* R1218 */
+	{ 0x0000, 0x0000 }, /* R1219 */
+	{ 0x0000, 0x0000 }, /* R1220 */
+	{ 0x0000, 0x0000 }, /* R1221 */
+	{ 0x0000, 0x0000 }, /* R1222 */
+	{ 0x0000, 0x0000 }, /* R1223 */
+	{ 0x0000, 0x0000 }, /* R1224 */
+	{ 0x0000, 0x0000 }, /* R1225 */
+	{ 0x0000, 0x0000 }, /* R1226 */
+	{ 0x0000, 0x0000 }, /* R1227 */
+	{ 0x0000, 0x0000 }, /* R1228 */
+	{ 0x0000, 0x0000 }, /* R1229 */
+	{ 0x0000, 0x0000 }, /* R1230 */
+	{ 0x0000, 0x0000 }, /* R1231 */
+	{ 0x0000, 0x0000 }, /* R1232 */
+	{ 0x0000, 0x0000 }, /* R1233 */
+	{ 0x0000, 0x0000 }, /* R1234 */
+	{ 0x0000, 0x0000 }, /* R1235 */
+	{ 0x0000, 0x0000 }, /* R1236 */
+	{ 0x0000, 0x0000 }, /* R1237 */
+	{ 0x0000, 0x0000 }, /* R1238 */
+	{ 0x0000, 0x0000 }, /* R1239 */
+	{ 0x0000, 0x0000 }, /* R1240 */
+	{ 0x0000, 0x0000 }, /* R1241 */
+	{ 0x0000, 0x0000 }, /* R1242 */
+	{ 0x0000, 0x0000 }, /* R1243 */
+	{ 0x0000, 0x0000 }, /* R1244 */
+	{ 0x0000, 0x0000 }, /* R1245 */
+	{ 0x0000, 0x0000 }, /* R1246 */
+	{ 0x0000, 0x0000 }, /* R1247 */
+	{ 0x0000, 0x0000 }, /* R1248 */
+	{ 0x0000, 0x0000 }, /* R1249 */
+	{ 0x0000, 0x0000 }, /* R1250 */
+	{ 0x0000, 0x0000 }, /* R1251 */
+	{ 0x0000, 0x0000 }, /* R1252 */
+	{ 0x0000, 0x0000 }, /* R1253 */
+	{ 0x0000, 0x0000 }, /* R1254 */
+	{ 0x0000, 0x0000 }, /* R1255 */
+	{ 0x0000, 0x0000 }, /* R1256 */
+	{ 0x0000, 0x0000 }, /* R1257 */
+	{ 0x0000, 0x0000 }, /* R1258 */
+	{ 0x0000, 0x0000 }, /* R1259 */
+	{ 0x0000, 0x0000 }, /* R1260 */
+	{ 0x0000, 0x0000 }, /* R1261 */
+	{ 0x0000, 0x0000 }, /* R1262 */
+	{ 0x0000, 0x0000 }, /* R1263 */
+	{ 0x0000, 0x0000 }, /* R1264 */
+	{ 0x0000, 0x0000 }, /* R1265 */
+	{ 0x0000, 0x0000 }, /* R1266 */
+	{ 0x0000, 0x0000 }, /* R1267 */
+	{ 0x0000, 0x0000 }, /* R1268 */
+	{ 0x0000, 0x0000 }, /* R1269 */
+	{ 0x0000, 0x0000 }, /* R1270 */
+	{ 0x0000, 0x0000 }, /* R1271 */
+	{ 0x0000, 0x0000 }, /* R1272 */
+	{ 0x0000, 0x0000 }, /* R1273 */
+	{ 0x0000, 0x0000 }, /* R1274 */
+	{ 0x0000, 0x0000 }, /* R1275 */
+	{ 0x0000, 0x0000 }, /* R1276 */
+	{ 0x0000, 0x0000 }, /* R1277 */
+	{ 0x0000, 0x0000 }, /* R1278 */
+	{ 0x0000, 0x0000 }, /* R1279 */
+	{ 0x00FF, 0x01FF }, /* R1280  - AIF2 ADC Left Volume */
+	{ 0x00FF, 0x01FF }, /* R1281  - AIF2 ADC Right Volume */
+	{ 0x00FF, 0x01FF }, /* R1282  - AIF2 DAC Left Volume */
+	{ 0x00FF, 0x01FF }, /* R1283  - AIF2 DAC Right Volume */
+	{ 0x0000, 0x0000 }, /* R1284 */
+	{ 0x0000, 0x0000 }, /* R1285 */
+	{ 0x0000, 0x0000 }, /* R1286 */
+	{ 0x0000, 0x0000 }, /* R1287 */
+	{ 0x0000, 0x0000 }, /* R1288 */
+	{ 0x0000, 0x0000 }, /* R1289 */
+	{ 0x0000, 0x0000 }, /* R1290 */
+	{ 0x0000, 0x0000 }, /* R1291 */
+	{ 0x0000, 0x0000 }, /* R1292 */
+	{ 0x0000, 0x0000 }, /* R1293 */
+	{ 0x0000, 0x0000 }, /* R1294 */
+	{ 0x0000, 0x0000 }, /* R1295 */
+	{ 0xF800, 0xF800 }, /* R1296  - AIF2 ADC Filters */
+	{ 0x0000, 0x0000 }, /* R1297 */
+	{ 0x0000, 0x0000 }, /* R1298 */
+	{ 0x0000, 0x0000 }, /* R1299 */
+	{ 0x0000, 0x0000 }, /* R1300 */
+	{ 0x0000, 0x0000 }, /* R1301 */
+	{ 0x0000, 0x0000 }, /* R1302 */
+	{ 0x0000, 0x0000 }, /* R1303 */
+	{ 0x0000, 0x0000 }, /* R1304 */
+	{ 0x0000, 0x0000 }, /* R1305 */
+	{ 0x0000, 0x0000 }, /* R1306 */
+	{ 0x0000, 0x0000 }, /* R1307 */
+	{ 0x0000, 0x0000 }, /* R1308 */
+	{ 0x0000, 0x0000 }, /* R1309 */
+	{ 0x0000, 0x0000 }, /* R1310 */
+	{ 0x0000, 0x0000 }, /* R1311 */
+	{ 0x02B6, 0x02B6 }, /* R1312  - AIF2 DAC Filters (1) */
+	{ 0x3F00, 0x3F00 }, /* R1313  - AIF2 DAC Filters (2) */
+	{ 0x0000, 0x0000 }, /* R1314 */
+	{ 0x0000, 0x0000 }, /* R1315 */
+	{ 0x0000, 0x0000 }, /* R1316 */
+	{ 0x0000, 0x0000 }, /* R1317 */
+	{ 0x0000, 0x0000 }, /* R1318 */
+	{ 0x0000, 0x0000 }, /* R1319 */
+	{ 0x0000, 0x0000 }, /* R1320 */
+	{ 0x0000, 0x0000 }, /* R1321 */
+	{ 0x0000, 0x0000 }, /* R1322 */
+	{ 0x0000, 0x0000 }, /* R1323 */
+	{ 0x0000, 0x0000 }, /* R1324 */
+	{ 0x0000, 0x0000 }, /* R1325 */
+	{ 0x0000, 0x0000 }, /* R1326 */
+	{ 0x0000, 0x0000 }, /* R1327 */
+	{ 0x0000, 0x0000 }, /* R1328 */
+	{ 0x0000, 0x0000 }, /* R1329 */
+	{ 0x0000, 0x0000 }, /* R1330 */
+	{ 0x0000, 0x0000 }, /* R1331 */
+	{ 0x0000, 0x0000 }, /* R1332 */
+	{ 0x0000, 0x0000 }, /* R1333 */
+	{ 0x0000, 0x0000 }, /* R1334 */
+	{ 0x0000, 0x0000 }, /* R1335 */
+	{ 0x0000, 0x0000 }, /* R1336 */
+	{ 0x0000, 0x0000 }, /* R1337 */
+	{ 0x0000, 0x0000 }, /* R1338 */
+	{ 0x0000, 0x0000 }, /* R1339 */
+	{ 0x0000, 0x0000 }, /* R1340 */
+	{ 0x0000, 0x0000 }, /* R1341 */
+	{ 0x0000, 0x0000 }, /* R1342 */
+	{ 0x0000, 0x0000 }, /* R1343 */
+	{ 0xFFFF, 0xFFFF }, /* R1344  - AIF2 DRC (1) */
+	{ 0x1FFF, 0x1FFF }, /* R1345  - AIF2 DRC (2) */
+	{ 0xFFFF, 0xFFFF }, /* R1346  - AIF2 DRC (3) */
+	{ 0x07FF, 0x07FF }, /* R1347  - AIF2 DRC (4) */
+	{ 0x03FF, 0x03FF }, /* R1348  - AIF2 DRC (5) */
+	{ 0x0000, 0x0000 }, /* R1349 */
+	{ 0x0000, 0x0000 }, /* R1350 */
+	{ 0x0000, 0x0000 }, /* R1351 */
+	{ 0x0000, 0x0000 }, /* R1352 */
+	{ 0x0000, 0x0000 }, /* R1353 */
+	{ 0x0000, 0x0000 }, /* R1354 */
+	{ 0x0000, 0x0000 }, /* R1355 */
+	{ 0x0000, 0x0000 }, /* R1356 */
+	{ 0x0000, 0x0000 }, /* R1357 */
+	{ 0x0000, 0x0000 }, /* R1358 */
+	{ 0x0000, 0x0000 }, /* R1359 */
+	{ 0x0000, 0x0000 }, /* R1360 */
+	{ 0x0000, 0x0000 }, /* R1361 */
+	{ 0x0000, 0x0000 }, /* R1362 */
+	{ 0x0000, 0x0000 }, /* R1363 */
+	{ 0x0000, 0x0000 }, /* R1364 */
+	{ 0x0000, 0x0000 }, /* R1365 */
+	{ 0x0000, 0x0000 }, /* R1366 */
+	{ 0x0000, 0x0000 }, /* R1367 */
+	{ 0x0000, 0x0000 }, /* R1368 */
+	{ 0x0000, 0x0000 }, /* R1369 */
+	{ 0x0000, 0x0000 }, /* R1370 */
+	{ 0x0000, 0x0000 }, /* R1371 */
+	{ 0x0000, 0x0000 }, /* R1372 */
+	{ 0x0000, 0x0000 }, /* R1373 */
+	{ 0x0000, 0x0000 }, /* R1374 */
+	{ 0x0000, 0x0000 }, /* R1375 */
+	{ 0x0000, 0x0000 }, /* R1376 */
+	{ 0x0000, 0x0000 }, /* R1377 */
+	{ 0x0000, 0x0000 }, /* R1378 */
+	{ 0x0000, 0x0000 }, /* R1379 */
+	{ 0x0000, 0x0000 }, /* R1380 */
+	{ 0x0000, 0x0000 }, /* R1381 */
+	{ 0x0000, 0x0000 }, /* R1382 */
+	{ 0x0000, 0x0000 }, /* R1383 */
+	{ 0x0000, 0x0000 }, /* R1384 */
+	{ 0x0000, 0x0000 }, /* R1385 */
+	{ 0x0000, 0x0000 }, /* R1386 */
+	{ 0x0000, 0x0000 }, /* R1387 */
+	{ 0x0000, 0x0000 }, /* R1388 */
+	{ 0x0000, 0x0000 }, /* R1389 */
+	{ 0x0000, 0x0000 }, /* R1390 */
+	{ 0x0000, 0x0000 }, /* R1391 */
+	{ 0x0000, 0x0000 }, /* R1392 */
+	{ 0x0000, 0x0000 }, /* R1393 */
+	{ 0x0000, 0x0000 }, /* R1394 */
+	{ 0x0000, 0x0000 }, /* R1395 */
+	{ 0x0000, 0x0000 }, /* R1396 */
+	{ 0x0000, 0x0000 }, /* R1397 */
+	{ 0x0000, 0x0000 }, /* R1398 */
+	{ 0x0000, 0x0000 }, /* R1399 */
+	{ 0x0000, 0x0000 }, /* R1400 */
+	{ 0x0000, 0x0000 }, /* R1401 */
+	{ 0x0000, 0x0000 }, /* R1402 */
+	{ 0x0000, 0x0000 }, /* R1403 */
+	{ 0x0000, 0x0000 }, /* R1404 */
+	{ 0x0000, 0x0000 }, /* R1405 */
+	{ 0x0000, 0x0000 }, /* R1406 */
+	{ 0x0000, 0x0000 }, /* R1407 */
+	{ 0xFFFF, 0xFFFF }, /* R1408  - AIF2 EQ Gains (1) */
+	{ 0xFFC0, 0xFFC0 }, /* R1409  - AIF2 EQ Gains (2) */
+	{ 0xFFFF, 0xFFFF }, /* R1410  - AIF2 EQ Band 1 A */
+	{ 0xFFFF, 0xFFFF }, /* R1411  - AIF2 EQ Band 1 B */
+	{ 0xFFFF, 0xFFFF }, /* R1412  - AIF2 EQ Band 1 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1413  - AIF2 EQ Band 2 A */
+	{ 0xFFFF, 0xFFFF }, /* R1414  - AIF2 EQ Band 2 B */
+	{ 0xFFFF, 0xFFFF }, /* R1415  - AIF2 EQ Band 2 C */
+	{ 0xFFFF, 0xFFFF }, /* R1416  - AIF2 EQ Band 2 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1417  - AIF2 EQ Band 3 A */
+	{ 0xFFFF, 0xFFFF }, /* R1418  - AIF2 EQ Band 3 B */
+	{ 0xFFFF, 0xFFFF }, /* R1419  - AIF2 EQ Band 3 C */
+	{ 0xFFFF, 0xFFFF }, /* R1420  - AIF2 EQ Band 3 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1421  - AIF2 EQ Band 4 A */
+	{ 0xFFFF, 0xFFFF }, /* R1422  - AIF2 EQ Band 4 B */
+	{ 0xFFFF, 0xFFFF }, /* R1423  - AIF2 EQ Band 4 C */
+	{ 0xFFFF, 0xFFFF }, /* R1424  - AIF2 EQ Band 4 PG */
+	{ 0xFFFF, 0xFFFF }, /* R1425  - AIF2 EQ Band 5 A */
+	{ 0xFFFF, 0xFFFF }, /* R1426  - AIF2 EQ Band 5 B */
+	{ 0xFFFF, 0xFFFF }, /* R1427  - AIF2 EQ Band 5 PG */
+	{ 0x0000, 0x0000 }, /* R1428 */
+	{ 0x0000, 0x0000 }, /* R1429 */
+	{ 0x0000, 0x0000 }, /* R1430 */
+	{ 0x0000, 0x0000 }, /* R1431 */
+	{ 0x0000, 0x0000 }, /* R1432 */
+	{ 0x0000, 0x0000 }, /* R1433 */
+	{ 0x0000, 0x0000 }, /* R1434 */
+	{ 0x0000, 0x0000 }, /* R1435 */
+	{ 0x0000, 0x0000 }, /* R1436 */
+	{ 0x0000, 0x0000 }, /* R1437 */
+	{ 0x0000, 0x0000 }, /* R1438 */
+	{ 0x0000, 0x0000 }, /* R1439 */
+	{ 0x0000, 0x0000 }, /* R1440 */
+	{ 0x0000, 0x0000 }, /* R1441 */
+	{ 0x0000, 0x0000 }, /* R1442 */
+	{ 0x0000, 0x0000 }, /* R1443 */
+	{ 0x0000, 0x0000 }, /* R1444 */
+	{ 0x0000, 0x0000 }, /* R1445 */
+	{ 0x0000, 0x0000 }, /* R1446 */
+	{ 0x0000, 0x0000 }, /* R1447 */
+	{ 0x0000, 0x0000 }, /* R1448 */
+	{ 0x0000, 0x0000 }, /* R1449 */
+	{ 0x0000, 0x0000 }, /* R1450 */
+	{ 0x0000, 0x0000 }, /* R1451 */
+	{ 0x0000, 0x0000 }, /* R1452 */
+	{ 0x0000, 0x0000 }, /* R1453 */
+	{ 0x0000, 0x0000 }, /* R1454 */
+	{ 0x0000, 0x0000 }, /* R1455 */
+	{ 0x0000, 0x0000 }, /* R1456 */
+	{ 0x0000, 0x0000 }, /* R1457 */
+	{ 0x0000, 0x0000 }, /* R1458 */
+	{ 0x0000, 0x0000 }, /* R1459 */
+	{ 0x0000, 0x0000 }, /* R1460 */
+	{ 0x0000, 0x0000 }, /* R1461 */
+	{ 0x0000, 0x0000 }, /* R1462 */
+	{ 0x0000, 0x0000 }, /* R1463 */
+	{ 0x0000, 0x0000 }, /* R1464 */
+	{ 0x0000, 0x0000 }, /* R1465 */
+	{ 0x0000, 0x0000 }, /* R1466 */
+	{ 0x0000, 0x0000 }, /* R1467 */
+	{ 0x0000, 0x0000 }, /* R1468 */
+	{ 0x0000, 0x0000 }, /* R1469 */
+	{ 0x0000, 0x0000 }, /* R1470 */
+	{ 0x0000, 0x0000 }, /* R1471 */
+	{ 0x0000, 0x0000 }, /* R1472 */
+	{ 0x0000, 0x0000 }, /* R1473 */
+	{ 0x0000, 0x0000 }, /* R1474 */
+	{ 0x0000, 0x0000 }, /* R1475 */
+	{ 0x0000, 0x0000 }, /* R1476 */
+	{ 0x0000, 0x0000 }, /* R1477 */
+	{ 0x0000, 0x0000 }, /* R1478 */
+	{ 0x0000, 0x0000 }, /* R1479 */
+	{ 0x0000, 0x0000 }, /* R1480 */
+	{ 0x0000, 0x0000 }, /* R1481 */
+	{ 0x0000, 0x0000 }, /* R1482 */
+	{ 0x0000, 0x0000 }, /* R1483 */
+	{ 0x0000, 0x0000 }, /* R1484 */
+	{ 0x0000, 0x0000 }, /* R1485 */
+	{ 0x0000, 0x0000 }, /* R1486 */
+	{ 0x0000, 0x0000 }, /* R1487 */
+	{ 0x0000, 0x0000 }, /* R1488 */
+	{ 0x0000, 0x0000 }, /* R1489 */
+	{ 0x0000, 0x0000 }, /* R1490 */
+	{ 0x0000, 0x0000 }, /* R1491 */
+	{ 0x0000, 0x0000 }, /* R1492 */
+	{ 0x0000, 0x0000 }, /* R1493 */
+	{ 0x0000, 0x0000 }, /* R1494 */
+	{ 0x0000, 0x0000 }, /* R1495 */
+	{ 0x0000, 0x0000 }, /* R1496 */
+	{ 0x0000, 0x0000 }, /* R1497 */
+	{ 0x0000, 0x0000 }, /* R1498 */
+	{ 0x0000, 0x0000 }, /* R1499 */
+	{ 0x0000, 0x0000 }, /* R1500 */
+	{ 0x0000, 0x0000 }, /* R1501 */
+	{ 0x0000, 0x0000 }, /* R1502 */
+	{ 0x0000, 0x0000 }, /* R1503 */
+	{ 0x0000, 0x0000 }, /* R1504 */
+	{ 0x0000, 0x0000 }, /* R1505 */
+	{ 0x0000, 0x0000 }, /* R1506 */
+	{ 0x0000, 0x0000 }, /* R1507 */
+	{ 0x0000, 0x0000 }, /* R1508 */
+	{ 0x0000, 0x0000 }, /* R1509 */
+	{ 0x0000, 0x0000 }, /* R1510 */
+	{ 0x0000, 0x0000 }, /* R1511 */
+	{ 0x0000, 0x0000 }, /* R1512 */
+	{ 0x0000, 0x0000 }, /* R1513 */
+	{ 0x0000, 0x0000 }, /* R1514 */
+	{ 0x0000, 0x0000 }, /* R1515 */
+	{ 0x0000, 0x0000 }, /* R1516 */
+	{ 0x0000, 0x0000 }, /* R1517 */
+	{ 0x0000, 0x0000 }, /* R1518 */
+	{ 0x0000, 0x0000 }, /* R1519 */
+	{ 0x0000, 0x0000 }, /* R1520 */
+	{ 0x0000, 0x0000 }, /* R1521 */
+	{ 0x0000, 0x0000 }, /* R1522 */
+	{ 0x0000, 0x0000 }, /* R1523 */
+	{ 0x0000, 0x0000 }, /* R1524 */
+	{ 0x0000, 0x0000 }, /* R1525 */
+	{ 0x0000, 0x0000 }, /* R1526 */
+	{ 0x0000, 0x0000 }, /* R1527 */
+	{ 0x0000, 0x0000 }, /* R1528 */
+	{ 0x0000, 0x0000 }, /* R1529 */
+	{ 0x0000, 0x0000 }, /* R1530 */
+	{ 0x0000, 0x0000 }, /* R1531 */
+	{ 0x0000, 0x0000 }, /* R1532 */
+	{ 0x0000, 0x0000 }, /* R1533 */
+	{ 0x0000, 0x0000 }, /* R1534 */
+	{ 0x0000, 0x0000 }, /* R1535 */
+	{ 0x01EF, 0x01EF }, /* R1536  - DAC1 Mixer Volumes */
+	{ 0x0037, 0x0037 }, /* R1537  - DAC1 Left Mixer Routing */
+	{ 0x0037, 0x0037 }, /* R1538  - DAC1 Right Mixer Routing */
+	{ 0x01EF, 0x01EF }, /* R1539  - DAC2 Mixer Volumes */
+	{ 0x0037, 0x0037 }, /* R1540  - DAC2 Left Mixer Routing */
+	{ 0x0037, 0x0037 }, /* R1541  - DAC2 Right Mixer Routing */
+	{ 0x0003, 0x0003 }, /* R1542  - AIF1 ADC1 Left Mixer Routing */
+	{ 0x0003, 0x0003 }, /* R1543  - AIF1 ADC1 Right Mixer Routing */
+	{ 0x0003, 0x0003 }, /* R1544  - AIF1 ADC2 Left Mixer Routing */
+	{ 0x0003, 0x0003 }, /* R1545  - AIF1 ADC2 Right mixer Routing */
+	{ 0x0000, 0x0000 }, /* R1546 */
+	{ 0x0000, 0x0000 }, /* R1547 */
+	{ 0x0000, 0x0000 }, /* R1548 */
+	{ 0x0000, 0x0000 }, /* R1549 */
+	{ 0x0000, 0x0000 }, /* R1550 */
+	{ 0x0000, 0x0000 }, /* R1551 */
+	{ 0x02FF, 0x03FF }, /* R1552  - DAC1 Left Volume */
+	{ 0x02FF, 0x03FF }, /* R1553  - DAC1 Right Volume */
+	{ 0x02FF, 0x03FF }, /* R1554  - DAC2 Left Volume */
+	{ 0x02FF, 0x03FF }, /* R1555  - DAC2 Right Volume */
+	{ 0x0003, 0x0003 }, /* R1556  - DAC Softmute */
+	{ 0x0000, 0x0000 }, /* R1557 */
+	{ 0x0000, 0x0000 }, /* R1558 */
+	{ 0x0000, 0x0000 }, /* R1559 */
+	{ 0x0000, 0x0000 }, /* R1560 */
+	{ 0x0000, 0x0000 }, /* R1561 */
+	{ 0x0000, 0x0000 }, /* R1562 */
+	{ 0x0000, 0x0000 }, /* R1563 */
+	{ 0x0000, 0x0000 }, /* R1564 */
+	{ 0x0000, 0x0000 }, /* R1565 */
+	{ 0x0000, 0x0000 }, /* R1566 */
+	{ 0x0000, 0x0000 }, /* R1567 */
+	{ 0x0003, 0x0003 }, /* R1568  - Oversampling */
+	{ 0x03C3, 0x03C3 }, /* R1569  - Sidetone */
+};
+
+static int wm8994_readable(unsigned int reg)
+{
+	switch (reg) {
+	case WM8994_GPIO_1:
+	case WM8994_GPIO_2:
+	case WM8994_GPIO_3:
+	case WM8994_GPIO_4:
+	case WM8994_GPIO_5:
+	case WM8994_GPIO_6:
+	case WM8994_GPIO_7:
+	case WM8994_GPIO_8:
+	case WM8994_GPIO_9:
+	case WM8994_GPIO_10:
+	case WM8994_GPIO_11:
+	case WM8994_INTERRUPT_STATUS_1:
+	case WM8994_INTERRUPT_STATUS_2:
+	case WM8994_INTERRUPT_RAW_STATUS_2:
+		return 1;
+	default:
+		break;
+	}
+
+	if (reg >= ARRAY_SIZE(access_masks))
+		return 0;
+	return access_masks[reg].readable != 0;
+}
+
+static int wm8994_volatile(unsigned int reg)
+{
+	if (reg >= WM8994_REG_CACHE_SIZE)
+		return 1;
+
+	switch (reg) {
+	case WM8994_SOFTWARE_RESET:
+	case WM8994_CHIP_REVISION:
+	case WM8994_DC_SERVO_1:
+	case WM8994_DC_SERVO_READBACK:
+	case WM8994_RATE_STATUS:
+	case WM8994_LDO_1:
+	case WM8994_LDO_2:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static int wm8994_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int value)
+{
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+	BUG_ON(reg > WM8994_MAX_REGISTER);
+
+	if (!wm8994_volatile(reg))
+		wm8994->reg_cache[reg] = value;
+
+	dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value);
+
+	return wm8994_reg_write(codec->control_data, reg, value);
+}
+
+static unsigned int wm8994_read(struct snd_soc_codec *codec,
+				unsigned int reg)
+{
+	u16 *reg_cache = codec->reg_cache;
+
+	BUG_ON(reg > WM8994_MAX_REGISTER);
+
+	if (wm8994_volatile(reg))
+		return wm8994_reg_read(codec->control_data, reg);
+	else
+		return reg_cache[reg];
+}
+
+static int configure_aif_clock(struct snd_soc_codec *codec, int aif)
+{
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	int rate;
+	int reg1 = 0;
+	int offset;
+
+	if (aif)
+		offset = 4;
+	else
+		offset = 0;
+
+	switch (wm8994->sysclk[aif]) {
+	case WM8994_SYSCLK_MCLK1:
+		rate = wm8994->mclk[0];
+		break;
+
+	case WM8994_SYSCLK_MCLK2:
+		reg1 |= 0x8;
+		rate = wm8994->mclk[1];
+		break;
+
+	case WM8994_SYSCLK_FLL1:
+		reg1 |= 0x10;
+		rate = wm8994->fll[0].out;
+		break;
+
+	case WM8994_SYSCLK_FLL2:
+		reg1 |= 0x18;
+		rate = wm8994->fll[1].out;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (rate >= 13500000) {
+		rate /= 2;
+		reg1 |= WM8994_AIF1CLK_DIV;
+
+		dev_dbg(codec->dev, "Dividing AIF%d clock to %dHz\n",
+			aif + 1, rate);
+	}
+
+	if (rate && rate < 3000000)
+		dev_warn(codec->dev, "AIF%dCLK is %dHz, should be >=3MHz for optimal performance\n",
+			 aif + 1, rate);
+
+	wm8994->aifclk[aif] = rate;
+
+	snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1 + offset,
+			    WM8994_AIF1CLK_SRC_MASK | WM8994_AIF1CLK_DIV,
+			    reg1);
+
+	return 0;
+}
+
+static int configure_clock(struct snd_soc_codec *codec)
+{
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	int old, new;
+
+	/* Bring up the AIF clocks first */
+	configure_aif_clock(codec, 0);
+	configure_aif_clock(codec, 1);
+
+	/* Then switch CLK_SYS over to the higher of them; a change
+	 * can only happen as a result of a clocking change which can
+	 * only be made outside of DAPM so we can safely redo the
+	 * clocking.
+	 */
+
+	/* If they're equal it doesn't matter which is used */
+	if (wm8994->aifclk[0] == wm8994->aifclk[1])
+		return 0;
+
+	if (wm8994->aifclk[0] < wm8994->aifclk[1])
+		new = WM8994_SYSCLK_SRC;
+	else
+		new = 0;
+
+	old = snd_soc_read(codec, WM8994_CLOCKING_1) & WM8994_SYSCLK_SRC;
+
+	/* If there's no change then we're done. */
+	if (old == new)
+		return 0;
+
+	snd_soc_update_bits(codec, WM8994_CLOCKING_1, WM8994_SYSCLK_SRC, new);
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static int check_clk_sys(struct snd_soc_dapm_widget *source,
+			 struct snd_soc_dapm_widget *sink)
+{
+	int reg = snd_soc_read(source->codec, WM8994_CLOCKING_1);
+	const char *clk;
+
+	/* Check what we're currently using for CLK_SYS */
+	if (reg & WM8994_SYSCLK_SRC)
+		clk = "AIF2CLK";
+	else
+		clk = "AIF1CLK";
+
+	return strcmp(source->name, clk) == 0;
+}
+
+static const char *sidetone_hpf_text[] = {
+	"2.7kHz", "1.35kHz", "675Hz", "370Hz", "180Hz", "90Hz", "45Hz"
+};
+
+static const struct soc_enum sidetone_hpf =
+	SOC_ENUM_SINGLE(WM8994_SIDETONE, 7, 7, sidetone_hpf_text);
+
+static const DECLARE_TLV_DB_SCALE(aif_tlv, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(st_tlv, -3600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(wm8994_3d_tlv, -1600, 183, 0);
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+
+#define WM8994_DRC_SWITCH(xname, reg, shift) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
+	.put = wm8994_put_drc_sw, \
+	.private_value =  SOC_SINGLE_VALUE(reg, shift, 1, 0) }
+
+static int wm8994_put_drc_sw(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	int mask, ret;
+
+	/* Can't enable both ADC and DAC paths simultaneously */
+	if (mc->shift == WM8994_AIF1DAC1_DRC_ENA_SHIFT)
+		mask = WM8994_AIF1ADC1L_DRC_ENA_MASK |
+			WM8994_AIF1ADC1R_DRC_ENA_MASK;
+	else
+		mask = WM8994_AIF1DAC1_DRC_ENA_MASK;
+
+	ret = snd_soc_read(codec, mc->reg);
+	if (ret < 0)
+		return ret;
+	if (ret & mask)
+		return -EINVAL;
+
+	return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
+static void wm8994_set_drc(struct snd_soc_codec *codec, int drc)
+{
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	struct wm8994_pdata *pdata = wm8994->pdata;
+	int base = wm8994_drc_base[drc];
+	int cfg = wm8994->drc_cfg[drc];
+	int save, i;
+
+	/* Save any enables; the configuration should clear them. */
+	save = snd_soc_read(codec, base);
+	save &= WM8994_AIF1DAC1_DRC_ENA | WM8994_AIF1ADC1L_DRC_ENA |
+		WM8994_AIF1ADC1R_DRC_ENA;
+
+	for (i = 0; i < WM8994_DRC_REGS; i++)
+		snd_soc_update_bits(codec, base + i, 0xffff,
+				    pdata->drc_cfgs[cfg].regs[i]);
+
+	snd_soc_update_bits(codec, base, WM8994_AIF1DAC1_DRC_ENA |
+			     WM8994_AIF1ADC1L_DRC_ENA |
+			     WM8994_AIF1ADC1R_DRC_ENA, save);
+}
+
+/* Icky as hell but saves code duplication */
+static int wm8994_get_drc(const char *name)
+{
+	if (strcmp(name, "AIF1DRC1 Mode") == 0)
+		return 0;
+	if (strcmp(name, "AIF1DRC2 Mode") == 0)
+		return 1;
+	if (strcmp(name, "AIF2DRC Mode") == 0)
+		return 2;
+	return -EINVAL;
+}
+
+static int wm8994_put_drc_enum(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	struct wm8994_pdata *pdata = wm8994->pdata;
+	int drc = wm8994_get_drc(kcontrol->id.name);
+	int value = ucontrol->value.integer.value[0];
+
+	if (drc < 0)
+		return drc;
+
+	if (value >= pdata->num_drc_cfgs)
+		return -EINVAL;
+
+	wm8994->drc_cfg[drc] = value;
+
+	wm8994_set_drc(codec, drc);
+
+	return 0;
+}
+
+static int wm8994_get_drc_enum(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	int drc = wm8994_get_drc(kcontrol->id.name);
+
+	ucontrol->value.enumerated.item[0] = wm8994->drc_cfg[drc];
+
+	return 0;
+}
+
+static void wm8994_set_retune_mobile(struct snd_soc_codec *codec, int block)
+{
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	struct wm8994_pdata *pdata = wm8994->pdata;
+	int base = wm8994_retune_mobile_base[block];
+	int iface, best, best_val, save, i, cfg;
+
+	if (!pdata || !wm8994->num_retune_mobile_texts)
+		return;
+
+	switch (block) {
+	case 0:
+	case 1:
+		iface = 0;
+		break;
+	case 2:
+		iface = 1;
+		break;
+	default:
+		return;
+	}
+
+	/* Find the version of the currently selected configuration
+	 * with the nearest sample rate. */
+	cfg = wm8994->retune_mobile_cfg[block];
+	best = 0;
+	best_val = INT_MAX;
+	for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+		if (strcmp(pdata->retune_mobile_cfgs[i].name,
+			   wm8994->retune_mobile_texts[cfg]) == 0 &&
+		    abs(pdata->retune_mobile_cfgs[i].rate
+			- wm8994->dac_rates[iface]) < best_val) {
+			best = i;
+			best_val = abs(pdata->retune_mobile_cfgs[i].rate
+				       - wm8994->dac_rates[iface]);
+		}
+	}
+
+	dev_dbg(codec->dev, "ReTune Mobile %d %s/%dHz for %dHz sample rate\n",
+		block,
+		pdata->retune_mobile_cfgs[best].name,
+		pdata->retune_mobile_cfgs[best].rate,
+		wm8994->dac_rates[iface]);
+
+	/* The EQ will be disabled while reconfiguring it, remember the
+	 * current configuration. 
+	 */
+	save = snd_soc_read(codec, base);
+	save &= WM8994_AIF1DAC1_EQ_ENA;
+
+	for (i = 0; i < WM8994_EQ_REGS; i++)
+		snd_soc_update_bits(codec, base + i, 0xffff,
+				pdata->retune_mobile_cfgs[best].regs[i]);
+
+	snd_soc_update_bits(codec, base, WM8994_AIF1DAC1_EQ_ENA, save);
+}
+
+/* Icky as hell but saves code duplication */
+static int wm8994_get_retune_mobile_block(const char *name)
+{
+	if (strcmp(name, "AIF1.1 EQ Mode") == 0)
+		return 0;
+	if (strcmp(name, "AIF1.2 EQ Mode") == 0)
+		return 1;
+	if (strcmp(name, "AIF2 EQ Mode") == 0)
+		return 2;
+	return -EINVAL;
+}
+
+static int wm8994_put_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	struct wm8994_pdata *pdata = wm8994->pdata;
+	int block = wm8994_get_retune_mobile_block(kcontrol->id.name);
+	int value = ucontrol->value.integer.value[0];
+
+	if (block < 0)
+		return block;
+
+	if (value >= pdata->num_retune_mobile_cfgs)
+		return -EINVAL;
+
+	wm8994->retune_mobile_cfg[block] = value;
+
+	wm8994_set_retune_mobile(codec, block);
+
+	return 0;
+}
+
+static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol,
+					 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm8994_priv *wm8994 =snd_soc_codec_get_drvdata(codec);
+	int block = wm8994_get_retune_mobile_block(kcontrol->id.name);
+
+	ucontrol->value.enumerated.item[0] = wm8994->retune_mobile_cfg[block];
+
+	return 0;
+}
+
+static const char *aifdac_src_text[] = {
+	"Left", "Right"
+};
+
+static const struct soc_enum aif1dacl_src =
+	SOC_ENUM_SINGLE(WM8994_AIF1_CONTROL_2, 15, 2, aifdac_src_text);
+
+static const struct soc_enum aif1dacr_src =
+	SOC_ENUM_SINGLE(WM8994_AIF1_CONTROL_2, 14, 2, aifdac_src_text);
+
+static const struct soc_enum aif2dacl_src =
+	SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_2, 15, 2, aifdac_src_text);
+
+static const struct soc_enum aif2dacr_src =
+	SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_2, 14, 2, aifdac_src_text);
+
+static const struct snd_kcontrol_new wm8994_snd_controls[] = {
+SOC_DOUBLE_R_TLV("AIF1ADC1 Volume", WM8994_AIF1_ADC1_LEFT_VOLUME,
+		 WM8994_AIF1_ADC1_RIGHT_VOLUME,
+		 1, 119, 0, digital_tlv),
+SOC_DOUBLE_R_TLV("AIF1ADC2 Volume", WM8994_AIF1_ADC2_LEFT_VOLUME,
+		 WM8994_AIF1_ADC2_RIGHT_VOLUME,
+		 1, 119, 0, digital_tlv),
+SOC_DOUBLE_R_TLV("AIF2ADC Volume", WM8994_AIF2_ADC_LEFT_VOLUME,
+		 WM8994_AIF2_ADC_RIGHT_VOLUME,
+		 1, 119, 0, digital_tlv),
+
+SOC_ENUM("AIF1DACL Source", aif1dacl_src),
+SOC_ENUM("AIF1DACR Source", aif1dacr_src),
+SOC_ENUM("AIF2DACL Source", aif1dacl_src),
+SOC_ENUM("AIF2DACR Source", aif1dacr_src),
+
+SOC_DOUBLE_R_TLV("AIF1DAC1 Volume", WM8994_AIF1_DAC1_LEFT_VOLUME,
+		 WM8994_AIF1_DAC1_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+SOC_DOUBLE_R_TLV("AIF1DAC2 Volume", WM8994_AIF1_DAC2_LEFT_VOLUME,
+		 WM8994_AIF1_DAC2_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+SOC_DOUBLE_R_TLV("AIF2DAC Volume", WM8994_AIF2_DAC_LEFT_VOLUME,
+		 WM8994_AIF2_DAC_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+
+SOC_SINGLE_TLV("AIF1 Boost Volume", WM8994_AIF1_CONTROL_2, 10, 3, 0, aif_tlv),
+SOC_SINGLE_TLV("AIF2 Boost Volume", WM8994_AIF2_CONTROL_2, 10, 3, 0, aif_tlv),
+
+SOC_SINGLE("AIF1DAC1 EQ Switch", WM8994_AIF1_DAC1_EQ_GAINS_1, 0, 1, 0),
+SOC_SINGLE("AIF1DAC2 EQ Switch", WM8994_AIF1_DAC2_EQ_GAINS_1, 0, 1, 0),
+SOC_SINGLE("AIF2 EQ Switch", WM8994_AIF2_EQ_GAINS_1, 0, 1, 0),
+
+WM8994_DRC_SWITCH("AIF1DAC1 DRC Switch", WM8994_AIF1_DRC1_1, 2),
+WM8994_DRC_SWITCH("AIF1ADC1L DRC Switch", WM8994_AIF1_DRC1_1, 1),
+WM8994_DRC_SWITCH("AIF1ADC1R DRC Switch", WM8994_AIF1_DRC1_1, 0),
+
+WM8994_DRC_SWITCH("AIF1DAC2 DRC Switch", WM8994_AIF1_DRC2_1, 2),
+WM8994_DRC_SWITCH("AIF1ADC2L DRC Switch", WM8994_AIF1_DRC2_1, 1),
+WM8994_DRC_SWITCH("AIF1ADC2R DRC Switch", WM8994_AIF1_DRC2_1, 0),
+
+WM8994_DRC_SWITCH("AIF2DAC DRC Switch", WM8994_AIF2_DRC_1, 2),
+WM8994_DRC_SWITCH("AIF2ADCL DRC Switch", WM8994_AIF2_DRC_1, 1),
+WM8994_DRC_SWITCH("AIF2ADCR DRC Switch", WM8994_AIF2_DRC_1, 0),
+
+SOC_SINGLE_TLV("DAC1 Right Sidetone Volume", WM8994_DAC1_MIXER_VOLUMES,
+	       5, 12, 0, st_tlv),
+SOC_SINGLE_TLV("DAC1 Left Sidetone Volume", WM8994_DAC1_MIXER_VOLUMES,
+	       0, 12, 0, st_tlv),
+SOC_SINGLE_TLV("DAC2 Right Sidetone Volume", WM8994_DAC2_MIXER_VOLUMES,
+	       5, 12, 0, st_tlv),
+SOC_SINGLE_TLV("DAC2 Left Sidetone Volume", WM8994_DAC2_MIXER_VOLUMES,
+	       0, 12, 0, st_tlv),
+SOC_ENUM("Sidetone HPF Mux", sidetone_hpf),
+SOC_SINGLE("Sidetone HPF Switch", WM8994_SIDETONE, 6, 1, 0),
+
+SOC_DOUBLE_R_TLV("DAC1 Volume", WM8994_DAC1_LEFT_VOLUME,
+		 WM8994_DAC1_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+SOC_DOUBLE_R("DAC1 Switch", WM8994_DAC1_LEFT_VOLUME,
+	     WM8994_DAC1_RIGHT_VOLUME, 9, 1, 1),
+
+SOC_DOUBLE_R_TLV("DAC2 Volume", WM8994_DAC2_LEFT_VOLUME,
+		 WM8994_DAC2_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
+SOC_DOUBLE_R("DAC2 Switch", WM8994_DAC2_LEFT_VOLUME,
+	     WM8994_DAC2_RIGHT_VOLUME, 9, 1, 1),
+
+SOC_SINGLE_TLV("SPKL DAC2 Volume", WM8994_SPKMIXL_ATTENUATION,
+	       6, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKL DAC1 Volume", WM8994_SPKMIXL_ATTENUATION,
+	       2, 1, 1, wm_hubs_spkmix_tlv),
+
+SOC_SINGLE_TLV("SPKR DAC2 Volume", WM8994_SPKMIXR_ATTENUATION,
+	       6, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKR DAC1 Volume", WM8994_SPKMIXR_ATTENUATION,
+	       2, 1, 1, wm_hubs_spkmix_tlv),
+
+SOC_SINGLE_TLV("AIF1DAC1 3D Stereo Volume", WM8994_AIF1_DAC1_FILTERS_2,
+	       10, 15, 0, wm8994_3d_tlv),
+SOC_SINGLE("AIF1DAC1 3D Stereo Switch", WM8994_AIF1_DAC2_FILTERS_2,
+	   8, 1, 0),
+SOC_SINGLE_TLV("AIF1DAC2 3D Stereo Volume", WM8994_AIF1_DAC2_FILTERS_2,
+	       10, 15, 0, wm8994_3d_tlv),
+SOC_SINGLE("AIF1DAC2 3D Stereo Switch", WM8994_AIF1_DAC2_FILTERS_2,
+	   8, 1, 0),
+SOC_SINGLE_TLV("AIF2DAC 3D Stereo Volume", WM8994_AIF1_DAC1_FILTERS_2,
+	       10, 15, 0, wm8994_3d_tlv),
+SOC_SINGLE("AIF2DAC 3D Stereo Switch", WM8994_AIF1_DAC2_FILTERS_2,
+	   8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8994_eq_controls[] = {
+SOC_SINGLE_TLV("AIF1DAC1 EQ1 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 11, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC1 EQ2 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 6, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC1 EQ3 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 1, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC1 EQ4 Volume", WM8994_AIF1_DAC1_EQ_GAINS_2, 11, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC1 EQ5 Volume", WM8994_AIF1_DAC1_EQ_GAINS_2, 6, 31, 0,
+	       eq_tlv),
+
+SOC_SINGLE_TLV("AIF1DAC2 EQ1 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 11, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC2 EQ2 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 6, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC2 EQ3 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 1, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC2 EQ4 Volume", WM8994_AIF1_DAC2_EQ_GAINS_2, 11, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF1DAC2 EQ5 Volume", WM8994_AIF1_DAC2_EQ_GAINS_2, 6, 31, 0,
+	       eq_tlv),
+
+SOC_SINGLE_TLV("AIF2 EQ1 Volume", WM8994_AIF2_EQ_GAINS_1, 11, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF2 EQ2 Volume", WM8994_AIF2_EQ_GAINS_1, 6, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF2 EQ3 Volume", WM8994_AIF2_EQ_GAINS_1, 1, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF2 EQ4 Volume", WM8994_AIF2_EQ_GAINS_2, 11, 31, 0,
+	       eq_tlv),
+SOC_SINGLE_TLV("AIF2 EQ5 Volume", WM8994_AIF2_EQ_GAINS_2, 6, 31, 0,
+	       eq_tlv),
+};
+
+static int clk_sys_event(struct snd_soc_dapm_widget *w,
+			 struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		return configure_clock(codec);
+
+	case SND_SOC_DAPM_POST_PMD:
+		configure_clock(codec);
+		break;
+	}
+
+	return 0;
+}
+
+static void wm8994_update_class_w(struct snd_soc_codec *codec)
+{
+	int enable = 1;
+	int source = 0;  /* GCC flow analysis can't track enable */
+	int reg, reg_r;
+
+	/* Only support direct DAC->headphone paths */
+	reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_1);
+	if (!(reg & WM8994_DAC1L_TO_HPOUT1L)) {
+		dev_vdbg(codec->dev, "HPL connected to output mixer\n");
+		enable = 0;
+	}
+
+	reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_2);
+	if (!(reg & WM8994_DAC1R_TO_HPOUT1R)) {
+		dev_vdbg(codec->dev, "HPR connected to output mixer\n");
+		enable = 0;
+	}
+
+	/* We also need the same setting for L/R and only one path */
+	reg = snd_soc_read(codec, WM8994_DAC1_LEFT_MIXER_ROUTING);
+	switch (reg) {
+	case WM8994_AIF2DACL_TO_DAC1L:
+		dev_vdbg(codec->dev, "Class W source AIF2DAC\n");
+		source = 2 << WM8994_CP_DYN_SRC_SEL_SHIFT;
+		break;
+	case WM8994_AIF1DAC2L_TO_DAC1L:
+		dev_vdbg(codec->dev, "Class W source AIF1DAC2\n");
+		source = 1 << WM8994_CP_DYN_SRC_SEL_SHIFT;
+		break;
+	case WM8994_AIF1DAC1L_TO_DAC1L:
+		dev_vdbg(codec->dev, "Class W source AIF1DAC1\n");
+		source = 0 << WM8994_CP_DYN_SRC_SEL_SHIFT;
+		break;
+	default:
+		dev_vdbg(codec->dev, "DAC mixer setting: %x\n", reg);
+		enable = 0;
+		break;
+	}
+
+	reg_r = snd_soc_read(codec, WM8994_DAC1_RIGHT_MIXER_ROUTING);
+	if (reg_r != reg) {
+		dev_vdbg(codec->dev, "Left and right DAC mixers different\n");
+		enable = 0;
+	}
+
+	if (enable) {
+		dev_dbg(codec->dev, "Class W enabled\n");
+		snd_soc_update_bits(codec, WM8994_CLASS_W_1,
+				    WM8994_CP_DYN_PWR |
+				    WM8994_CP_DYN_SRC_SEL_MASK,
+				    source | WM8994_CP_DYN_PWR);
+		
+	} else {
+		dev_dbg(codec->dev, "Class W disabled\n");
+		snd_soc_update_bits(codec, WM8994_CLASS_W_1,
+				    WM8994_CP_DYN_PWR, 0);
+	}
+}
+
+static const char *hp_mux_text[] = {
+	"Mixer",
+	"DAC",
+};
+
+#define WM8994_HP_ENUM(xname, xenum) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = snd_soc_info_enum_double, \
+ 	.get = snd_soc_dapm_get_enum_double, \
+ 	.put = wm8994_put_hp_enum, \
+  	.private_value = (unsigned long)&xenum }
+
+static int wm8994_put_hp_enum(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *w = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_codec *codec = w->codec;
+	int ret;
+
+	ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
+
+	wm8994_update_class_w(codec);
+
+	return ret;
+}
+
+static const struct soc_enum hpl_enum =
+	SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_1, 8, 2, hp_mux_text);
+
+static const struct snd_kcontrol_new hpl_mux =
+	WM8994_HP_ENUM("Left Headphone Mux", hpl_enum);
+
+static const struct soc_enum hpr_enum =
+	SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_2, 8, 2, hp_mux_text);
+
+static const struct snd_kcontrol_new hpr_mux =
+	WM8994_HP_ENUM("Right Headphone Mux", hpr_enum);
+
+static const char *adc_mux_text[] = {
+	"ADC",
+	"DMIC",
+};
+
+static const struct soc_enum adc_enum =
+	SOC_ENUM_SINGLE(0, 0, 2, adc_mux_text);
+
+static const struct snd_kcontrol_new adcl_mux =
+	SOC_DAPM_ENUM_VIRT("ADCL Mux", adc_enum);
+
+static const struct snd_kcontrol_new adcr_mux =
+	SOC_DAPM_ENUM_VIRT("ADCR Mux", adc_enum);
+
+static const struct snd_kcontrol_new left_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 9, 1, 0),
+SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 7, 1, 0),
+SOC_DAPM_SINGLE("IN1LP Switch", WM8994_SPEAKER_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 1, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_speaker_mixer[] = {
+SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 8, 1, 0),
+SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 6, 1, 0),
+SOC_DAPM_SINGLE("IN1RP Switch", WM8994_SPEAKER_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 0, 1, 0),
+};
+
+/* Debugging; dump chip status after DAPM transitions */
+static int post_ev(struct snd_soc_dapm_widget *w,
+	    struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	dev_dbg(codec->dev, "SRC status: %x\n",
+		snd_soc_read(codec,
+			     WM8994_RATE_STATUS));
+	return 0;
+}
+
+static const struct snd_kcontrol_new aif1adc1l_mix[] = {
+SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING,
+		1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING,
+		0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif1adc1r_mix[] = {
+SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING,
+		1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING,
+		0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif1adc2l_mix[] = {
+SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING,
+		1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING,
+		0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif1adc2r_mix[] = {
+SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING,
+		1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING,
+		0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif2dac2l_mix[] = {
+SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
+		5, 1, 0),
+SOC_DAPM_SINGLE("Left Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
+		4, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
+		2, 1, 0),
+SOC_DAPM_SINGLE("AIF1.2 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
+		1, 1, 0),
+SOC_DAPM_SINGLE("AIF1.1 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
+		0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif2dac2r_mix[] = {
+SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
+		5, 1, 0),
+SOC_DAPM_SINGLE("Left Sidetone Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
+		4, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
+		2, 1, 0),
+SOC_DAPM_SINGLE("AIF1.2 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
+		1, 1, 0),
+SOC_DAPM_SINGLE("AIF1.1 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
+		0, 1, 0),
+};
+
+#define WM8994_CLASS_W_SWITCH(xname, reg, shift, max, invert) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = snd_soc_info_volsw, \
+	.get = snd_soc_dapm_get_volsw, .put = wm8994_put_class_w, \
+	.private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+static int wm8994_put_class_w(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *w = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_codec *codec = w->codec;
+	int ret;
+
+	ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+
+	wm8994_update_class_w(codec);
+
+	return ret;
+}
+
+static const struct snd_kcontrol_new dac1l_mix[] = {
+WM8994_CLASS_W_SWITCH("Right Sidetone Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
+		      5, 1, 0),
+WM8994_CLASS_W_SWITCH("Left Sidetone Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
+		      4, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF2 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
+		      2, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF1.2 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
+		      1, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF1.1 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
+		      0, 1, 0),
+};
+
+static const struct snd_kcontrol_new dac1r_mix[] = {
+WM8994_CLASS_W_SWITCH("Right Sidetone Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
+		      5, 1, 0),
+WM8994_CLASS_W_SWITCH("Left Sidetone Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
+		      4, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF2 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
+		      2, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF1.2 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
+		      1, 1, 0),
+WM8994_CLASS_W_SWITCH("AIF1.1 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
+		      0, 1, 0),
+};
+
+static const char *sidetone_text[] = {
+	"ADC/DMIC1", "DMIC2",
+};
+
+static const struct soc_enum sidetone1_enum =
+	SOC_ENUM_SINGLE(WM8994_SIDETONE, 0, 2, sidetone_text);
+
+static const struct snd_kcontrol_new sidetone1_mux =
+	SOC_DAPM_ENUM("Left Sidetone Mux", sidetone1_enum);
+
+static const struct soc_enum sidetone2_enum =
+	SOC_ENUM_SINGLE(WM8994_SIDETONE, 1, 2, sidetone_text);
+
+static const struct snd_kcontrol_new sidetone2_mux =
+	SOC_DAPM_ENUM("Right Sidetone Mux", sidetone2_enum);
+
+static const char *aif1dac_text[] = {
+	"AIF1DACDAT", "AIF3DACDAT",
+};
+
+static const struct soc_enum aif1dac_enum =
+	SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 0, 2, aif1dac_text);
+
+static const struct snd_kcontrol_new aif1dac_mux =
+	SOC_DAPM_ENUM("AIF1DAC Mux", aif1dac_enum);
+
+static const char *aif2dac_text[] = {
+	"AIF2DACDAT", "AIF3DACDAT",
+};
+
+static const struct soc_enum aif2dac_enum =
+	SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 1, 2, aif2dac_text);
+
+static const struct snd_kcontrol_new aif2dac_mux =
+	SOC_DAPM_ENUM("AIF2DAC Mux", aif2dac_enum);
+
+static const char *aif2adc_text[] = {
+	"AIF2ADCDAT", "AIF3DACDAT",
+};
+
+static const struct soc_enum aif2adc_enum =
+	SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 2, 2, aif2adc_text);
+
+static const struct snd_kcontrol_new aif2adc_mux =
+	SOC_DAPM_ENUM("AIF2ADC Mux", aif2adc_enum);
+
+static const char *aif3adc_text[] = {
+	"AIF1ADCDAT", "AIF2ADCDAT", "AIF2DACDAT",
+};
+
+static const struct soc_enum aif3adc_enum =
+	SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 3, 3, aif3adc_text);
+
+static const struct snd_kcontrol_new aif3adc_mux =
+	SOC_DAPM_ENUM("AIF3ADC Mux", aif3adc_enum);
+
+static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("DMIC1DAT"),
+SND_SOC_DAPM_INPUT("DMIC2DAT"),
+SND_SOC_DAPM_INPUT("Clock"),
+
+SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event,
+		    SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_SUPPLY("DSP1CLK", WM8994_CLOCKING_1, 3, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("DSP2CLK", WM8994_CLOCKING_1, 2, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("DSPINTCLK", WM8994_CLOCKING_1, 1, 0, NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8994_AIF1_CLOCKING_1, 0, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_AIF_OUT("AIF1ADC1L", "AIF1 Capture",
+		     0, WM8994_POWER_MANAGEMENT_4, 9, 0),
+SND_SOC_DAPM_AIF_OUT("AIF1ADC1R", "AIF1 Capture",
+		     0, WM8994_POWER_MANAGEMENT_4, 8, 0),
+SND_SOC_DAPM_AIF_IN("AIF1DAC1L", NULL, 0,
+		    WM8994_POWER_MANAGEMENT_5, 9, 0),
+SND_SOC_DAPM_AIF_IN("AIF1DAC1R", NULL, 0,
+		    WM8994_POWER_MANAGEMENT_5, 8, 0),
+
+SND_SOC_DAPM_AIF_OUT("AIF1ADC2L", "AIF1 Capture",
+		     0, WM8994_POWER_MANAGEMENT_4, 11, 0),
+SND_SOC_DAPM_AIF_OUT("AIF1ADC2R", "AIF1 Capture",
+		     0, WM8994_POWER_MANAGEMENT_4, 10, 0),
+SND_SOC_DAPM_AIF_IN("AIF1DAC2L", NULL, 0,
+		    WM8994_POWER_MANAGEMENT_5, 11, 0),
+SND_SOC_DAPM_AIF_IN("AIF1DAC2R", NULL, 0,
+		    WM8994_POWER_MANAGEMENT_5, 10, 0),
+
+SND_SOC_DAPM_MIXER("AIF1ADC1L Mixer", SND_SOC_NOPM, 0, 0,
+		   aif1adc1l_mix, ARRAY_SIZE(aif1adc1l_mix)),
+SND_SOC_DAPM_MIXER("AIF1ADC1R Mixer", SND_SOC_NOPM, 0, 0,
+		   aif1adc1r_mix, ARRAY_SIZE(aif1adc1r_mix)),
+
+SND_SOC_DAPM_MIXER("AIF1ADC2L Mixer", SND_SOC_NOPM, 0, 0,
+		   aif1adc2l_mix, ARRAY_SIZE(aif1adc2l_mix)),
+SND_SOC_DAPM_MIXER("AIF1ADC2R Mixer", SND_SOC_NOPM, 0, 0,
+		   aif1adc2r_mix, ARRAY_SIZE(aif1adc2r_mix)),
+
+SND_SOC_DAPM_MIXER("AIF2DAC2L Mixer", SND_SOC_NOPM, 0, 0,
+		   aif2dac2l_mix, ARRAY_SIZE(aif2dac2l_mix)),
+SND_SOC_DAPM_MIXER("AIF2DAC2R Mixer", SND_SOC_NOPM, 0, 0,
+		   aif2dac2r_mix, ARRAY_SIZE(aif2dac2r_mix)),
+
+SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &sidetone1_mux),
+SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &sidetone2_mux),
+
+SND_SOC_DAPM_MIXER("DAC1L Mixer", SND_SOC_NOPM, 0, 0,
+		   dac1l_mix, ARRAY_SIZE(dac1l_mix)),
+SND_SOC_DAPM_MIXER("DAC1R Mixer", SND_SOC_NOPM, 0, 0,
+		   dac1r_mix, ARRAY_SIZE(dac1r_mix)),
+
+SND_SOC_DAPM_AIF_OUT("AIF2ADCL", NULL, 0,
+		     WM8994_POWER_MANAGEMENT_4, 13, 0),
+SND_SOC_DAPM_AIF_OUT("AIF2ADCR", NULL, 0,
+		     WM8994_POWER_MANAGEMENT_4, 12, 0),
+SND_SOC_DAPM_AIF_IN("AIF2DACL", NULL, 0,
+		    WM8994_POWER_MANAGEMENT_5, 13, 0),
+SND_SOC_DAPM_AIF_IN("AIF2DACR", NULL, 0,
+		    WM8994_POWER_MANAGEMENT_5, 12, 0),
+
+SND_SOC_DAPM_AIF_IN("AIF1DACDAT", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIF2DACDAT", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIF2ADCDAT", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("AIF1DAC Mux", SND_SOC_NOPM, 0, 0, &aif1dac_mux),
+SND_SOC_DAPM_MUX("AIF2DAC Mux", SND_SOC_NOPM, 0, 0, &aif2dac_mux),
+SND_SOC_DAPM_MUX("AIF2ADC Mux", SND_SOC_NOPM, 0, 0, &aif2adc_mux),
+SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &aif3adc_mux),
+
+SND_SOC_DAPM_AIF_IN("AIF3DACDAT", "AIF3 Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIF3ADCDAT", "AIF3 Capture", 0, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_SUPPLY("TOCLK", WM8994_CLOCKING_1, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_ADC("DMIC2L", NULL, WM8994_POWER_MANAGEMENT_4, 5, 0),
+SND_SOC_DAPM_ADC("DMIC2R", NULL, WM8994_POWER_MANAGEMENT_4, 4, 0),
+SND_SOC_DAPM_ADC("DMIC1L", NULL, WM8994_POWER_MANAGEMENT_4, 3, 0),
+SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8994_POWER_MANAGEMENT_4, 2, 0),
+
+/* Power is done with the muxes since the ADC power also controls the
+ * downsampling chain, the chip will automatically manage the analogue
+ * specific portions.
+ */
+SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux),
+SND_SOC_DAPM_MUX("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux),
+
+SND_SOC_DAPM_DAC("DAC2L", NULL, WM8994_POWER_MANAGEMENT_5, 3, 0),
+SND_SOC_DAPM_DAC("DAC2R", NULL, WM8994_POWER_MANAGEMENT_5, 2, 0),
+SND_SOC_DAPM_DAC("DAC1L", NULL, WM8994_POWER_MANAGEMENT_5, 1, 0),
+SND_SOC_DAPM_DAC("DAC1R", NULL, WM8994_POWER_MANAGEMENT_5, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
+SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
+
+SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
+		   left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
+SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
+		   right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
+
+SND_SOC_DAPM_POST("Debug log", post_ev),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+
+	{ "CLK_SYS", NULL, "AIF1CLK", check_clk_sys },
+	{ "CLK_SYS", NULL, "AIF2CLK", check_clk_sys },
+
+	{ "DSP1CLK", NULL, "CLK_SYS" },
+	{ "DSP2CLK", NULL, "CLK_SYS" },
+	{ "DSPINTCLK", NULL, "CLK_SYS" },
+
+	{ "AIF1ADC1L", NULL, "AIF1CLK" },
+	{ "AIF1ADC1L", NULL, "DSP1CLK" },
+	{ "AIF1ADC1R", NULL, "AIF1CLK" },
+	{ "AIF1ADC1R", NULL, "DSP1CLK" },
+	{ "AIF1ADC1R", NULL, "DSPINTCLK" },
+
+	{ "AIF1DAC1L", NULL, "AIF1CLK" },
+	{ "AIF1DAC1L", NULL, "DSP1CLK" },
+	{ "AIF1DAC1R", NULL, "AIF1CLK" },
+	{ "AIF1DAC1R", NULL, "DSP1CLK" },
+	{ "AIF1DAC1R", NULL, "DSPINTCLK" },
+
+	{ "AIF1ADC2L", NULL, "AIF1CLK" },
+	{ "AIF1ADC2L", NULL, "DSP1CLK" },
+	{ "AIF1ADC2R", NULL, "AIF1CLK" },
+	{ "AIF1ADC2R", NULL, "DSP1CLK" },
+	{ "AIF1ADC2R", NULL, "DSPINTCLK" },
+
+	{ "AIF1DAC2L", NULL, "AIF1CLK" },
+	{ "AIF1DAC2L", NULL, "DSP1CLK" },
+	{ "AIF1DAC2R", NULL, "AIF1CLK" },
+	{ "AIF1DAC2R", NULL, "DSP1CLK" },
+	{ "AIF1DAC2R", NULL, "DSPINTCLK" },
+
+	{ "AIF2ADCL", NULL, "AIF2CLK" },
+	{ "AIF2ADCL", NULL, "DSP2CLK" },
+	{ "AIF2ADCR", NULL, "AIF2CLK" },
+	{ "AIF2ADCR", NULL, "DSP2CLK" },
+	{ "AIF2ADCR", NULL, "DSPINTCLK" },
+
+	{ "AIF2DACL", NULL, "AIF2CLK" },
+	{ "AIF2DACL", NULL, "DSP2CLK" },
+	{ "AIF2DACR", NULL, "AIF2CLK" },
+	{ "AIF2DACR", NULL, "DSP2CLK" },
+	{ "AIF2DACR", NULL, "DSPINTCLK" },
+
+	{ "DMIC1L", NULL, "DMIC1DAT" },
+	{ "DMIC1L", NULL, "CLK_SYS" },
+	{ "DMIC1R", NULL, "DMIC1DAT" },
+	{ "DMIC1R", NULL, "CLK_SYS" },
+	{ "DMIC2L", NULL, "DMIC2DAT" },
+	{ "DMIC2L", NULL, "CLK_SYS" },
+	{ "DMIC2R", NULL, "DMIC2DAT" },
+	{ "DMIC2R", NULL, "CLK_SYS" },
+
+	{ "ADCL", NULL, "AIF1CLK" },
+	{ "ADCL", NULL, "DSP1CLK" },
+	{ "ADCL", NULL, "DSPINTCLK" },
+
+	{ "ADCR", NULL, "AIF1CLK" },
+	{ "ADCR", NULL, "DSP1CLK" },
+	{ "ADCR", NULL, "DSPINTCLK" },
+
+	{ "ADCL Mux", "ADC", "ADCL" },
+	{ "ADCL Mux", "DMIC", "DMIC1L" },
+	{ "ADCR Mux", "ADC", "ADCR" },
+	{ "ADCR Mux", "DMIC", "DMIC1R" },
+
+	{ "DAC1L", NULL, "AIF1CLK" },
+	{ "DAC1L", NULL, "DSP1CLK" },
+	{ "DAC1L", NULL, "DSPINTCLK" },
+
+	{ "DAC1R", NULL, "AIF1CLK" },
+	{ "DAC1R", NULL, "DSP1CLK" },
+	{ "DAC1R", NULL, "DSPINTCLK" },
+
+	{ "DAC2L", NULL, "AIF2CLK" },
+	{ "DAC2L", NULL, "DSP2CLK" },
+	{ "DAC2L", NULL, "DSPINTCLK" },
+
+	{ "DAC2R", NULL, "AIF2DACR" },
+	{ "DAC2R", NULL, "AIF2CLK" },
+	{ "DAC2R", NULL, "DSP2CLK" },
+	{ "DAC2R", NULL, "DSPINTCLK" },
+
+	{ "TOCLK", NULL, "CLK_SYS" },
+
+	/* AIF1 outputs */
+	{ "AIF1ADC1L", NULL, "AIF1ADC1L Mixer" },
+	{ "AIF1ADC1L Mixer", "ADC/DMIC Switch", "ADCL Mux" },
+	{ "AIF1ADC1L Mixer", "AIF2 Switch", "AIF2DACL" },
+
+	{ "AIF1ADC1R", NULL, "AIF1ADC1R Mixer" },
+	{ "AIF1ADC1R Mixer", "ADC/DMIC Switch", "ADCR Mux" },
+	{ "AIF1ADC1R Mixer", "AIF2 Switch", "AIF2DACR" },
+
+	{ "AIF1ADC2L", NULL, "AIF1ADC2L Mixer" },
+	{ "AIF1ADC2L Mixer", "DMIC Switch", "DMIC2L" },
+	{ "AIF1ADC2L Mixer", "AIF2 Switch", "AIF2DACL" },
+
+	{ "AIF1ADC2R", NULL, "AIF1ADC2R Mixer" },
+	{ "AIF1ADC2R Mixer", "DMIC Switch", "DMIC2R" },
+	{ "AIF1ADC2R Mixer", "AIF2 Switch", "AIF2DACR" },
+
+	/* Pin level routing for AIF3 */
+	{ "AIF1DAC1L", NULL, "AIF1DAC Mux" },
+	{ "AIF1DAC1R", NULL, "AIF1DAC Mux" },
+	{ "AIF1DAC2L", NULL, "AIF1DAC Mux" },
+	{ "AIF1DAC2R", NULL, "AIF1DAC Mux" },
+
+	{ "AIF2DACL", NULL, "AIF2DAC Mux" },
+	{ "AIF2DACR", NULL, "AIF2DAC Mux" },
+
+	{ "AIF1DAC Mux", "AIF1DACDAT", "AIF1DACDAT" },
+	{ "AIF1DAC Mux", "AIF3DACDAT", "AIF3DACDAT" },
+	{ "AIF2DAC Mux", "AIF2DACDAT", "AIF2DACDAT" },
+	{ "AIF2DAC Mux", "AIF3DACDAT", "AIF3DACDAT" },
+	{ "AIF2ADC Mux", "AIF2ADCDAT", "AIF2ADCL" },
+	{ "AIF2ADC Mux", "AIF2ADCDAT", "AIF2ADCR" },
+	{ "AIF2ADC Mux", "AIF3DACDAT", "AIF3ADCDAT" },
+
+	/* DAC1 inputs */
+	{ "DAC1L", NULL, "DAC1L Mixer" },
+	{ "DAC1L Mixer", "AIF2 Switch", "AIF2DACL" },
+	{ "DAC1L Mixer", "AIF1.2 Switch", "AIF1DAC2L" },
+	{ "DAC1L Mixer", "AIF1.1 Switch", "AIF1DAC1L" },
+	{ "DAC1L Mixer", "Left Sidetone Switch", "Left Sidetone" },
+	{ "DAC1L Mixer", "Right Sidetone Switch", "Right Sidetone" },
+
+	{ "DAC1R", NULL, "DAC1R Mixer" },
+	{ "DAC1R Mixer", "AIF2 Switch", "AIF2DACR" },
+	{ "DAC1R Mixer", "AIF1.2 Switch", "AIF1DAC2R" },
+	{ "DAC1R Mixer", "AIF1.1 Switch", "AIF1DAC1R" },
+	{ "DAC1R Mixer", "Left Sidetone Switch", "Left Sidetone" },
+	{ "DAC1R Mixer", "Right Sidetone Switch", "Right Sidetone" },
+
+	/* DAC2/AIF2 outputs  */
+	{ "AIF2ADCL", NULL, "AIF2DAC2L Mixer" },
+	{ "DAC2L", NULL, "AIF2DAC2L Mixer" },
+	{ "AIF2DAC2L Mixer", "AIF2 Switch", "AIF2DACL" },
+	{ "AIF2DAC2L Mixer", "AIF1.2 Switch", "AIF1DAC2L" },
+	{ "AIF2DAC2L Mixer", "AIF1.1 Switch", "AIF1DAC1L" },
+	{ "AIF2DAC2L Mixer", "Left Sidetone Switch", "Left Sidetone" },
+	{ "AIF2DAC2L Mixer", "Right Sidetone Switch", "Right Sidetone" },
+
+	{ "AIF2ADCR", NULL, "AIF2DAC2R Mixer" },
+	{ "DAC2R", NULL, "AIF2DAC2R Mixer" },
+	{ "AIF2DAC2R Mixer", "AIF2 Switch", "AIF2DACR" },
+	{ "AIF2DAC2R Mixer", "AIF1.2 Switch", "AIF1DAC2R" },
+	{ "AIF2DAC2R Mixer", "AIF1.1 Switch", "AIF1DAC1R" },
+	{ "AIF2DAC2R Mixer", "Left Sidetone Switch", "Left Sidetone" },
+	{ "AIF2DAC2R Mixer", "Right Sidetone Switch", "Right Sidetone" },
+
+	{ "AIF2ADCDAT", NULL, "AIF2ADC Mux" },
+
+	/* AIF3 output */
+	{ "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC1L" },
+	{ "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC1R" },
+	{ "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC2L" },
+	{ "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC2R" },
+	{ "AIF3ADCDAT", "AIF2ADCDAT", "AIF2ADCL" },
+	{ "AIF3ADCDAT", "AIF2ADCDAT", "AIF2ADCR" },
+	{ "AIF3ADCDAT", "AIF2DACDAT", "AIF2DACL" },
+	{ "AIF3ADCDAT", "AIF2DACDAT", "AIF2DACR" },
+
+	/* Sidetone */
+	{ "Left Sidetone", "ADC/DMIC1", "ADCL Mux" },
+	{ "Left Sidetone", "DMIC2", "DMIC2L" },
+	{ "Right Sidetone", "ADC/DMIC1", "ADCR Mux" },
+	{ "Right Sidetone", "DMIC2", "DMIC2R" },
+
+	/* Output stages */
+	{ "Left Output Mixer", "DAC Switch", "DAC1L" },
+	{ "Right Output Mixer", "DAC Switch", "DAC1R" },
+
+	{ "SPKL", "DAC1 Switch", "DAC1L" },
+	{ "SPKL", "DAC2 Switch", "DAC2L" },
+
+	{ "SPKR", "DAC1 Switch", "DAC1R" },
+	{ "SPKR", "DAC2 Switch", "DAC2R" },
+
+	{ "Left Headphone Mux", "DAC", "DAC1L" },
+	{ "Right Headphone Mux", "DAC", "DAC1R" },
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+struct fll_div {
+	u16 outdiv;
+	u16 n;
+	u16 k;
+	u16 clk_ref_div;
+	u16 fll_fratio;
+};
+
+static int wm8994_get_fll_config(struct fll_div *fll,
+				 int freq_in, int freq_out)
+{
+	u64 Kpart;
+	unsigned int K, Ndiv, Nmod;
+
+	pr_debug("FLL input=%dHz, output=%dHz\n", freq_in, freq_out);
+
+	/* Scale the input frequency down to <= 13.5MHz */
+	fll->clk_ref_div = 0;
+	while (freq_in > 13500000) {
+		fll->clk_ref_div++;
+		freq_in /= 2;
+
+		if (fll->clk_ref_div > 3)
+			return -EINVAL;
+	}
+	pr_debug("CLK_REF_DIV=%d, Fref=%dHz\n", fll->clk_ref_div, freq_in);
+
+	/* Scale the output to give 90MHz<=Fvco<=100MHz */
+	fll->outdiv = 3;
+	while (freq_out * (fll->outdiv + 1) < 90000000) {
+		fll->outdiv++;
+		if (fll->outdiv > 63)
+			return -EINVAL;
+	}
+	freq_out *= fll->outdiv + 1;
+	pr_debug("OUTDIV=%d, Fvco=%dHz\n", fll->outdiv, freq_out);
+
+	if (freq_in > 1000000) {
+		fll->fll_fratio = 0;
+	} else if (freq_in > 256000) {
+		fll->fll_fratio = 1;
+		freq_in *= 2;
+	} else if (freq_in > 128000) {
+		fll->fll_fratio = 2;
+		freq_in *= 4;
+	} else if (freq_in > 64000) {
+		fll->fll_fratio = 3;
+		freq_in *= 8;
+	} else {
+		fll->fll_fratio = 4;
+		freq_in *= 16;
+	}
+	pr_debug("FLL_FRATIO=%d, Fref=%dHz\n", fll->fll_fratio, freq_in);
+
+	/* Now, calculate N.K */
+	Ndiv = freq_out / freq_in;
+
+	fll->n = Ndiv;
+	Nmod = freq_out % freq_in;
+	pr_debug("Nmod=%d\n", Nmod);
+
+	/* Calculate fractional part - scale up so we can round. */
+	Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, freq_in);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	fll->k = K / 10;
+
+	pr_debug("N=%x K=%x\n", fll->n, fll->k);
+
+	return 0;
+}
+
+static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,
+			  unsigned int freq_in, unsigned int freq_out)
+{
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	int reg_offset, ret;
+	struct fll_div fll;
+	u16 reg, aif1, aif2;
+
+	aif1 = snd_soc_read(codec, WM8994_AIF1_CLOCKING_1)
+		& WM8994_AIF1CLK_ENA;
+
+	aif2 = snd_soc_read(codec, WM8994_AIF2_CLOCKING_1)
+		& WM8994_AIF2CLK_ENA;
+
+	switch (id) {
+	case WM8994_FLL1:
+		reg_offset = 0;
+		id = 0;
+		break;
+	case WM8994_FLL2:
+		reg_offset = 0x20;
+		id = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (src) {
+	case 0:
+		/* Allow no source specification when stopping */
+		if (freq_out)
+			return -EINVAL;
+		break;
+	case WM8994_FLL_SRC_MCLK1:
+	case WM8994_FLL_SRC_MCLK2:
+	case WM8994_FLL_SRC_LRCLK:
+	case WM8994_FLL_SRC_BCLK:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Are we changing anything? */
+	if (wm8994->fll[id].src == src &&
+	    wm8994->fll[id].in == freq_in && wm8994->fll[id].out == freq_out)
+		return 0;
+
+	/* If we're stopping the FLL redo the old config - no
+	 * registers will actually be written but we avoid GCC flow
+	 * analysis bugs spewing warnings.
+	 */
+	if (freq_out)
+		ret = wm8994_get_fll_config(&fll, freq_in, freq_out);
+	else
+		ret = wm8994_get_fll_config(&fll, wm8994->fll[id].in,
+					    wm8994->fll[id].out);
+	if (ret < 0)
+		return ret;
+
+	/* Gate the AIF clocks while we reclock */
+	snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
+			    WM8994_AIF1CLK_ENA, 0);
+	snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
+			    WM8994_AIF2CLK_ENA, 0);
+
+	/* We always need to disable the FLL while reconfiguring */
+	snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset,
+			    WM8994_FLL1_ENA, 0);
+
+	reg = (fll.outdiv << WM8994_FLL1_OUTDIV_SHIFT) |
+		(fll.fll_fratio << WM8994_FLL1_FRATIO_SHIFT);
+	snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_2 + reg_offset,
+			    WM8994_FLL1_OUTDIV_MASK |
+			    WM8994_FLL1_FRATIO_MASK, reg);
+
+	snd_soc_write(codec, WM8994_FLL1_CONTROL_3 + reg_offset, fll.k);
+
+	snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_4 + reg_offset,
+			    WM8994_FLL1_N_MASK,
+				    fll.n << WM8994_FLL1_N_SHIFT);
+
+	snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_5 + reg_offset,
+			    WM8994_FLL1_REFCLK_DIV_MASK |
+			    WM8994_FLL1_REFCLK_SRC_MASK,
+			    (fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT) |
+			    (src - 1));
+
+	/* Enable (with fractional mode if required) */
+	if (freq_out) {
+		if (fll.k)
+			reg = WM8994_FLL1_ENA | WM8994_FLL1_FRAC;
+		else
+			reg = WM8994_FLL1_ENA;
+		snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset,
+				    WM8994_FLL1_ENA | WM8994_FLL1_FRAC,
+				    reg);
+	}
+
+	wm8994->fll[id].in = freq_in;
+	wm8994->fll[id].out = freq_out;
+	wm8994->fll[id].src = src;
+
+	/* Enable any gated AIF clocks */
+	snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
+			    WM8994_AIF1CLK_ENA, aif1);
+	snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
+			    WM8994_AIF2CLK_ENA, aif2);
+
+	configure_clock(codec);
+
+	return 0;
+}
+
+
+static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 };
+
+static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src,
+			  unsigned int freq_in, unsigned int freq_out)
+{
+	return _wm8994_set_fll(dai->codec, id, src, freq_in, freq_out);
+}
+
+static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	int i;
+
+	switch (dai->id) {
+	case 1:
+	case 2:
+		break;
+
+	default:
+		/* AIF3 shares clocking with AIF1/2 */
+		return -EINVAL;
+	}
+
+	switch (clk_id) {
+	case WM8994_SYSCLK_MCLK1:
+		wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK1;
+		wm8994->mclk[0] = freq;
+		dev_dbg(dai->dev, "AIF%d using MCLK1 at %uHz\n",
+			dai->id, freq);
+		break;
+
+	case WM8994_SYSCLK_MCLK2:
+		/* TODO: Set GPIO AF */
+		wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK2;
+		wm8994->mclk[1] = freq;
+		dev_dbg(dai->dev, "AIF%d using MCLK2 at %uHz\n",
+			dai->id, freq);
+		break;
+
+	case WM8994_SYSCLK_FLL1:
+		wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_FLL1;
+		dev_dbg(dai->dev, "AIF%d using FLL1\n", dai->id);
+		break;
+
+	case WM8994_SYSCLK_FLL2:
+		wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_FLL2;
+		dev_dbg(dai->dev, "AIF%d using FLL2\n", dai->id);
+		break;
+
+	case WM8994_SYSCLK_OPCLK:
+		/* Special case - a division (times 10) is given and
+		 * no effect on main clocking. 
+		 */
+		if (freq) {
+			for (i = 0; i < ARRAY_SIZE(opclk_divs); i++)
+				if (opclk_divs[i] == freq)
+					break;
+			if (i == ARRAY_SIZE(opclk_divs))
+				return -EINVAL;
+			snd_soc_update_bits(codec, WM8994_CLOCKING_2,
+					    WM8994_OPCLK_DIV_MASK, i);
+			snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2,
+					    WM8994_OPCLK_ENA, WM8994_OPCLK_ENA);
+		} else {
+			snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2,
+					    WM8994_OPCLK_ENA, 0);
+		}
+
+	default:
+		return -EINVAL;
+	}
+
+	configure_clock(codec);
+
+	return 0;
+}
+
+static int wm8994_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		/* VMID=2x40k */
+		snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+				    WM8994_VMID_SEL_MASK, 0x2);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Tweak DC servo and DSP configuration for
+			 * improved performance. */
+			if (wm8994->revision < 4) {
+				/* Tweak DC servo and DSP configuration for
+				 * improved performance. */
+				snd_soc_write(codec, 0x102, 0x3);
+				snd_soc_write(codec, 0x56, 0x3);
+				snd_soc_write(codec, 0x817, 0);
+				snd_soc_write(codec, 0x102, 0);
+			}
+
+			/* Discharge LINEOUT1 & 2 */
+			snd_soc_update_bits(codec, WM8994_ANTIPOP_1,
+					    WM8994_LINEOUT1_DISCH |
+					    WM8994_LINEOUT2_DISCH,
+					    WM8994_LINEOUT1_DISCH |
+					    WM8994_LINEOUT2_DISCH);
+
+			/* Startup bias, VMID ramp & buffer */
+			snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+					    WM8994_STARTUP_BIAS_ENA |
+					    WM8994_VMID_BUF_ENA |
+					    WM8994_VMID_RAMP_MASK,
+					    WM8994_STARTUP_BIAS_ENA |
+					    WM8994_VMID_BUF_ENA |
+					    (0x11 << WM8994_VMID_RAMP_SHIFT));
+
+			/* Main bias enable, VMID=2x40k */
+			snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+					    WM8994_BIAS_ENA |
+					    WM8994_VMID_SEL_MASK,
+					    WM8994_BIAS_ENA | 0x2);
+
+			msleep(20);
+		}
+
+		/* VMID=2x500k */
+		snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+				    WM8994_VMID_SEL_MASK, 0x4);
+
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		if (codec->bias_level == SND_SOC_BIAS_STANDBY) {
+			/* Switch over to startup biases */
+			snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+					    WM8994_BIAS_SRC |
+					    WM8994_STARTUP_BIAS_ENA |
+					    WM8994_VMID_BUF_ENA |
+					    WM8994_VMID_RAMP_MASK,
+					    WM8994_BIAS_SRC |
+					    WM8994_STARTUP_BIAS_ENA |
+					    WM8994_VMID_BUF_ENA |
+					    (1 << WM8994_VMID_RAMP_SHIFT));
+
+			/* Disable main biases */
+			snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+					    WM8994_BIAS_ENA |
+					    WM8994_VMID_SEL_MASK, 0);
+
+			/* Discharge line */
+			snd_soc_update_bits(codec, WM8994_ANTIPOP_1,
+					    WM8994_LINEOUT1_DISCH |
+					    WM8994_LINEOUT2_DISCH,
+					    WM8994_LINEOUT1_DISCH |
+					    WM8994_LINEOUT2_DISCH);
+
+			msleep(5);
+
+			/* Switch off startup biases */
+			snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+					    WM8994_BIAS_SRC |
+					    WM8994_STARTUP_BIAS_ENA |
+					    WM8994_VMID_BUF_ENA |
+					    WM8994_VMID_RAMP_MASK, 0);
+		}
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+static int wm8994_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	int ms_reg;
+	int aif1_reg;
+	int ms = 0;
+	int aif1 = 0;
+
+	switch (dai->id) {
+	case 1:
+		ms_reg = WM8994_AIF1_MASTER_SLAVE;
+		aif1_reg = WM8994_AIF1_CONTROL_1;
+		break;
+	case 2:
+		ms_reg = WM8994_AIF2_MASTER_SLAVE;
+		aif1_reg = WM8994_AIF2_CONTROL_1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		ms = WM8994_AIF1_MSTR;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_B:
+		aif1 |= WM8994_AIF1_LRCLK_INV;
+	case SND_SOC_DAIFMT_DSP_A:
+		aif1 |= 0x18;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		aif1 |= 0x10;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		aif1 |= 0x8;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		/* frame inversion not valid for DSP modes */
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif1 |= WM8994_AIF1_BCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_LEFT_J:
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			aif1 |= WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif1 |= WM8994_AIF1_BCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			aif1 |= WM8994_AIF1_LRCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_update_bits(codec, aif1_reg,
+			    WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV |
+			    WM8994_AIF1_FMT_MASK,
+			    aif1);
+	snd_soc_update_bits(codec, ms_reg, WM8994_AIF1_MSTR,
+			    ms);
+
+	return 0;
+}
+
+static struct {
+	int val, rate;
+} srs[] = {
+	{ 0,   8000 },
+	{ 1,  11025 },
+	{ 2,  12000 },
+	{ 3,  16000 },
+	{ 4,  22050 },
+	{ 5,  24000 },
+	{ 6,  32000 },
+	{ 7,  44100 },
+	{ 8,  48000 },
+	{ 9,  88200 },
+	{ 10, 96000 },
+};
+
+static int fs_ratios[] = {
+	64, 128, 192, 256, 348, 512, 768, 1024, 1408, 1536
+};
+
+static int bclk_divs[] = {
+	10, 15, 20, 30, 40, 50, 60, 80, 110, 120, 160, 220, 240, 320, 440, 480,
+	640, 880, 960, 1280, 1760, 1920
+};
+
+static int wm8994_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	int aif1_reg;
+	int bclk_reg;
+	int lrclk_reg;
+	int rate_reg;
+	int aif1 = 0;
+	int bclk = 0;
+	int lrclk = 0;
+	int rate_val = 0;
+	int id = dai->id - 1;
+
+	int i, cur_val, best_val, bclk_rate, best;
+
+	switch (dai->id) {
+	case 1:
+		aif1_reg = WM8994_AIF1_CONTROL_1;
+		bclk_reg = WM8994_AIF1_BCLK;
+		rate_reg = WM8994_AIF1_RATE;
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
+		    wm8994->lrclk_shared[0]) {
+			lrclk_reg = WM8994_AIF1DAC_LRCLK;
+		} else {
+			lrclk_reg = WM8994_AIF1ADC_LRCLK;
+			dev_dbg(codec->dev, "AIF1 using split LRCLK\n");
+		}
+		break;
+	case 2:
+		aif1_reg = WM8994_AIF2_CONTROL_1;
+		bclk_reg = WM8994_AIF2_BCLK;
+		rate_reg = WM8994_AIF2_RATE;
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
+		    wm8994->lrclk_shared[1]) {
+			lrclk_reg = WM8994_AIF2DAC_LRCLK;
+		} else {
+			lrclk_reg = WM8994_AIF2ADC_LRCLK;
+			dev_dbg(codec->dev, "AIF2 using split LRCLK\n");
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	bclk_rate = params_rate(params) * 2;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		bclk_rate *= 16;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		bclk_rate *= 20;
+		aif1 |= 0x20;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		bclk_rate *= 24;
+		aif1 |= 0x40;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		bclk_rate *= 32;
+		aif1 |= 0x60;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Try to find an appropriate sample rate; look for an exact match. */
+	for (i = 0; i < ARRAY_SIZE(srs); i++)
+		if (srs[i].rate == params_rate(params))
+			break;
+	if (i == ARRAY_SIZE(srs))
+		return -EINVAL;
+	rate_val |= srs[i].val << WM8994_AIF1_SR_SHIFT;
+
+	dev_dbg(dai->dev, "Sample rate is %dHz\n", srs[i].rate);
+	dev_dbg(dai->dev, "AIF%dCLK is %dHz, target BCLK %dHz\n",
+		dai->id, wm8994->aifclk[id], bclk_rate);
+
+	if (wm8994->aifclk[id] == 0) {
+		dev_err(dai->dev, "AIF%dCLK not configured\n", dai->id);
+		return -EINVAL;
+	}
+
+	/* AIFCLK/fs ratio; look for a close match in either direction */
+	best = 0;
+	best_val = abs((fs_ratios[0] * params_rate(params))
+		       - wm8994->aifclk[id]);
+	for (i = 1; i < ARRAY_SIZE(fs_ratios); i++) {
+		cur_val = abs((fs_ratios[i] * params_rate(params))
+			      - wm8994->aifclk[id]);
+		if (cur_val >= best_val)
+			continue;
+		best = i;
+		best_val = cur_val;
+	}
+	dev_dbg(dai->dev, "Selected AIF%dCLK/fs = %d\n",
+		dai->id, fs_ratios[best]);
+	rate_val |= best;
+
+	/* We may not get quite the right frequency if using
+	 * approximate clocks so look for the closest match that is
+	 * higher than the target (we need to ensure that there enough
+	 * BCLKs to clock out the samples).
+	 */
+	best = 0;
+	for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+		cur_val = (wm8994->aifclk[id] * 10 / bclk_divs[i]) - bclk_rate;
+		if (cur_val < 0) /* BCLK table is sorted */
+			break;
+		best = i;
+	}
+	bclk_rate = wm8994->aifclk[id] * 10 / bclk_divs[best];
+	dev_dbg(dai->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n",
+		bclk_divs[best], bclk_rate);
+	bclk |= best << WM8994_AIF1_BCLK_DIV_SHIFT;
+
+	lrclk = bclk_rate / params_rate(params);
+	dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n",
+		lrclk, bclk_rate / lrclk);
+
+	snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_WL_MASK, aif1);
+	snd_soc_update_bits(codec, bclk_reg, WM8994_AIF1_BCLK_DIV_MASK, bclk);
+	snd_soc_update_bits(codec, lrclk_reg, WM8994_AIF1DAC_RATE_MASK,
+			    lrclk);
+	snd_soc_update_bits(codec, rate_reg, WM8994_AIF1_SR_MASK |
+			    WM8994_AIF1CLK_RATE_MASK, rate_val);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		switch (dai->id) {
+		case 1:
+			wm8994->dac_rates[0] = params_rate(params);
+			wm8994_set_retune_mobile(codec, 0);
+			wm8994_set_retune_mobile(codec, 1);
+			break;
+		case 2:
+			wm8994->dac_rates[1] = params_rate(params);
+			wm8994_set_retune_mobile(codec, 2);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	int mute_reg;
+	int reg;
+
+	switch (codec_dai->id) {
+	case 1:
+		mute_reg = WM8994_AIF1_DAC1_FILTERS_1;
+		break;
+	case 2:
+		mute_reg = WM8994_AIF2_DAC_FILTERS_1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (mute)
+		reg = WM8994_AIF1DAC1_MUTE;
+	else
+		reg = 0;
+
+	snd_soc_update_bits(codec, mute_reg, WM8994_AIF1DAC1_MUTE, reg);
+
+	return 0;
+}
+
+static int wm8994_set_tristate(struct snd_soc_dai *codec_dai, int tristate)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	int reg, val, mask;
+
+	switch (codec_dai->id) {
+	case 1:
+		reg = WM8994_AIF1_MASTER_SLAVE;
+		mask = WM8994_AIF1_TRI;
+		break;
+	case 2:
+		reg = WM8994_AIF2_MASTER_SLAVE;
+		mask = WM8994_AIF2_TRI;
+		break;
+	case 3:
+		reg = WM8994_POWER_MANAGEMENT_6;
+		mask = WM8994_AIF3_TRI;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (tristate)
+		val = mask;
+	else
+		val = 0;
+
+	return snd_soc_update_bits(codec, reg, mask, reg);
+}
+
+#define WM8994_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM8994_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+			SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm8994_aif1_dai_ops = {
+	.set_sysclk	= wm8994_set_dai_sysclk,
+	.set_fmt	= wm8994_set_dai_fmt,
+	.hw_params	= wm8994_hw_params,
+	.digital_mute	= wm8994_aif_mute,
+	.set_pll	= wm8994_set_fll,
+	.set_tristate	= wm8994_set_tristate,
+};
+
+static struct snd_soc_dai_ops wm8994_aif2_dai_ops = {
+	.set_sysclk	= wm8994_set_dai_sysclk,
+	.set_fmt	= wm8994_set_dai_fmt,
+	.hw_params	= wm8994_hw_params,
+	.digital_mute   = wm8994_aif_mute,
+	.set_pll	= wm8994_set_fll,
+	.set_tristate	= wm8994_set_tristate,
+};
+
+static struct snd_soc_dai_ops wm8994_aif3_dai_ops = {
+	.set_tristate	= wm8994_set_tristate,
+};
+
+static struct snd_soc_dai_driver wm8994_dai[] = {
+	{
+		.name = "wm8994-aif1",
+		.id = 1,
+		.playback = {
+			.stream_name = "AIF1 Playback",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = WM8994_RATES,
+			.formats = WM8994_FORMATS,
+		},
+		.capture = {
+			.stream_name = "AIF1 Capture",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = WM8994_RATES,
+			.formats = WM8994_FORMATS,
+		 },
+		.ops = &wm8994_aif1_dai_ops,
+	},
+	{
+		.name = "wm8994-aif2",
+		.id = 2,
+		.playback = {
+			.stream_name = "AIF2 Playback",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = WM8994_RATES,
+			.formats = WM8994_FORMATS,
+		},
+		.capture = {
+			.stream_name = "AIF2 Capture",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = WM8994_RATES,
+			.formats = WM8994_FORMATS,
+		},
+		.ops = &wm8994_aif2_dai_ops,
+	},
+	{
+		.name = "wm8994-aif3",
+		.id = 3,
+		.playback = {
+			.stream_name = "AIF3 Playback",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = WM8994_RATES,
+			.formats = WM8994_FORMATS,
+		},
+		.capture = {
+			.stream_name = "AIF3 Capture",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = WM8994_RATES,
+			.formats = WM8994_FORMATS,
+		},
+		.ops = &wm8994_aif3_dai_ops,
+	}
+};
+
+#ifdef CONFIG_PM
+static int wm8994_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) {
+		memcpy(&wm8994->fll_suspend[i], &wm8994->fll[i],
+		       sizeof(struct fll_config));
+		ret = _wm8994_set_fll(codec, i + 1, 0, 0, 0);
+		if (ret < 0)
+			dev_warn(codec->dev, "Failed to stop FLL%d: %d\n",
+				 i + 1, ret);
+	}
+
+	wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm8994_resume(struct snd_soc_codec *codec)
+{
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	u16 *reg_cache = codec->reg_cache;
+	int i, ret;
+
+	/* Restore the registers */
+	for (i = 1; i < ARRAY_SIZE(wm8994->reg_cache); i++) {
+		switch (i) {
+		case WM8994_LDO_1:
+		case WM8994_LDO_2:
+		case WM8994_SOFTWARE_RESET:
+			/* Handled by other MFD drivers */
+			continue;
+		default:
+			break;
+		}
+
+		if (!access_masks[i].writable)
+			continue;
+
+		wm8994_reg_write(codec->control_data, i, reg_cache[i]);
+	}
+
+	wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) {
+		if (!wm8994->fll_suspend[i].out)
+			continue;
+
+		ret = _wm8994_set_fll(codec, i + 1,
+				     wm8994->fll_suspend[i].src,
+				     wm8994->fll_suspend[i].in,
+				     wm8994->fll_suspend[i].out);
+		if (ret < 0)
+			dev_warn(codec->dev, "Failed to restore FLL%d: %d\n",
+				 i + 1, ret);
+	}
+
+	return 0;
+}
+#else
+#define wm8994_suspend NULL
+#define wm8994_resume NULL
+#endif
+
+static void wm8994_handle_retune_mobile_pdata(struct wm8994_priv *wm8994)
+{
+	struct snd_soc_codec *codec = wm8994->codec;
+	struct wm8994_pdata *pdata = wm8994->pdata;
+	struct snd_kcontrol_new controls[] = {
+		SOC_ENUM_EXT("AIF1.1 EQ Mode",
+			     wm8994->retune_mobile_enum,
+			     wm8994_get_retune_mobile_enum,
+			     wm8994_put_retune_mobile_enum),
+		SOC_ENUM_EXT("AIF1.2 EQ Mode",
+			     wm8994->retune_mobile_enum,
+			     wm8994_get_retune_mobile_enum,
+			     wm8994_put_retune_mobile_enum),
+		SOC_ENUM_EXT("AIF2 EQ Mode",
+			     wm8994->retune_mobile_enum,
+			     wm8994_get_retune_mobile_enum,
+			     wm8994_put_retune_mobile_enum),
+	};
+	int ret, i, j;
+	const char **t;
+
+	/* We need an array of texts for the enum API but the number
+	 * of texts is likely to be less than the number of
+	 * configurations due to the sample rate dependency of the
+	 * configurations. */
+	wm8994->num_retune_mobile_texts = 0;
+	wm8994->retune_mobile_texts = NULL;
+	for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
+		for (j = 0; j < wm8994->num_retune_mobile_texts; j++) {
+			if (strcmp(pdata->retune_mobile_cfgs[i].name,
+				   wm8994->retune_mobile_texts[j]) == 0)
+				break;
+		}
+
+		if (j != wm8994->num_retune_mobile_texts)
+			continue;
+
+		/* Expand the array... */
+		t = krealloc(wm8994->retune_mobile_texts,
+			     sizeof(char *) * 
+			     (wm8994->num_retune_mobile_texts + 1),
+			     GFP_KERNEL);
+		if (t == NULL)
+			continue;
+
+		/* ...store the new entry... */
+		t[wm8994->num_retune_mobile_texts] = 
+			pdata->retune_mobile_cfgs[i].name;
+
+		/* ...and remember the new version. */
+		wm8994->num_retune_mobile_texts++;
+		wm8994->retune_mobile_texts = t;
+	}
+
+	dev_dbg(codec->dev, "Allocated %d unique ReTune Mobile names\n",
+		wm8994->num_retune_mobile_texts);
+
+	wm8994->retune_mobile_enum.max = wm8994->num_retune_mobile_texts;
+	wm8994->retune_mobile_enum.texts = wm8994->retune_mobile_texts;
+
+	ret = snd_soc_add_controls(wm8994->codec, controls,
+				   ARRAY_SIZE(controls));
+	if (ret != 0)
+		dev_err(wm8994->codec->dev,
+			"Failed to add ReTune Mobile controls: %d\n", ret);
+}
+
+static void wm8994_handle_pdata(struct wm8994_priv *wm8994)
+{
+	struct snd_soc_codec *codec = wm8994->codec;
+	struct wm8994_pdata *pdata = wm8994->pdata;
+	int ret, i;
+
+	if (!pdata)
+		return;
+
+	wm_hubs_handle_analogue_pdata(codec, pdata->lineout1_diff,
+				      pdata->lineout2_diff,
+				      pdata->lineout1fb,
+				      pdata->lineout2fb,
+				      pdata->jd_scthr,
+				      pdata->jd_thr,
+				      pdata->micbias1_lvl,
+				      pdata->micbias2_lvl);
+
+	dev_dbg(codec->dev, "%d DRC configurations\n", pdata->num_drc_cfgs);
+
+	if (pdata->num_drc_cfgs) {
+		struct snd_kcontrol_new controls[] = {
+			SOC_ENUM_EXT("AIF1DRC1 Mode", wm8994->drc_enum,
+				     wm8994_get_drc_enum, wm8994_put_drc_enum),
+			SOC_ENUM_EXT("AIF1DRC2 Mode", wm8994->drc_enum,
+				     wm8994_get_drc_enum, wm8994_put_drc_enum),
+			SOC_ENUM_EXT("AIF2DRC Mode", wm8994->drc_enum,
+				     wm8994_get_drc_enum, wm8994_put_drc_enum),
+		};
+
+		/* We need an array of texts for the enum API */
+		wm8994->drc_texts = kmalloc(sizeof(char *)
+					    * pdata->num_drc_cfgs, GFP_KERNEL);
+		if (!wm8994->drc_texts) {
+			dev_err(wm8994->codec->dev,
+				"Failed to allocate %d DRC config texts\n",
+				pdata->num_drc_cfgs);
+			return;
+		}
+
+		for (i = 0; i < pdata->num_drc_cfgs; i++)
+			wm8994->drc_texts[i] = pdata->drc_cfgs[i].name;
+
+		wm8994->drc_enum.max = pdata->num_drc_cfgs;
+		wm8994->drc_enum.texts = wm8994->drc_texts;
+
+		ret = snd_soc_add_controls(wm8994->codec, controls,
+					   ARRAY_SIZE(controls));
+		if (ret != 0)
+			dev_err(wm8994->codec->dev,
+				"Failed to add DRC mode controls: %d\n", ret);
+
+		for (i = 0; i < WM8994_NUM_DRC; i++)
+			wm8994_set_drc(codec, i);
+	}
+
+	dev_dbg(codec->dev, "%d ReTune Mobile configurations\n",
+		pdata->num_retune_mobile_cfgs);
+
+	if (pdata->num_retune_mobile_cfgs)
+		wm8994_handle_retune_mobile_pdata(wm8994);
+	else
+		snd_soc_add_controls(wm8994->codec, wm8994_eq_controls,
+				     ARRAY_SIZE(wm8994_eq_controls));
+}
+
+/**
+ * wm8994_mic_detect - Enable microphone detection via the WM8994 IRQ
+ *
+ * @codec:   WM8994 codec
+ * @jack:    jack to report detection events on
+ * @micbias: microphone bias to detect on
+ * @det:     value to report for presence detection
+ * @shrt:    value to report for short detection
+ *
+ * Enable microphone detection via IRQ on the WM8994.  If GPIOs are
+ * being used to bring out signals to the processor then only platform
+ * data configuration is needed for WM8994 and processor GPIOs should
+ * be configured using snd_soc_jack_add_gpios() instead.
+ *
+ * Configuration of detection levels is available via the micbias1_lvl
+ * and micbias2_lvl platform data members.
+ */
+int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+		      int micbias, int det, int shrt)
+{
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+	struct wm8994_micdet *micdet;
+	int reg;
+
+	switch (micbias) {
+	case 1:
+		micdet = &wm8994->micdet[0];
+		break;
+	case 2:
+		micdet = &wm8994->micdet[1];
+		break;
+	default:
+		return -EINVAL;
+	}	
+
+	dev_dbg(codec->dev, "Configuring microphone detection on %d: %x %x\n",
+		micbias, det, shrt);
+
+	/* Store the configuration */
+	micdet->jack = jack;
+	micdet->det = det;
+	micdet->shrt = shrt;
+
+	/* If either of the jacks is set up then enable detection */
+	if (wm8994->micdet[0].jack || wm8994->micdet[1].jack)
+		reg = WM8994_MICD_ENA;
+	else 
+		reg = 0;
+
+	snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, reg);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wm8994_mic_detect);
+
+static irqreturn_t wm8994_mic_irq(int irq, void *data)
+{
+	struct wm8994_priv *priv = data;
+	struct snd_soc_codec *codec = priv->codec;
+	int reg;
+	int report;
+
+	reg = snd_soc_read(codec, WM8994_INTERRUPT_RAW_STATUS_2);
+	if (reg < 0) {
+		dev_err(codec->dev, "Failed to read microphone status: %d\n",
+			reg);
+		return IRQ_HANDLED;
+	}
+
+	dev_dbg(codec->dev, "Microphone status: %x\n", reg);
+
+	report = 0;
+	if (reg & WM8994_MIC1_DET_STS)
+		report |= priv->micdet[0].det;
+	if (reg & WM8994_MIC1_SHRT_STS)
+		report |= priv->micdet[0].shrt;
+	snd_soc_jack_report(priv->micdet[0].jack, report,
+			    priv->micdet[0].det | priv->micdet[0].shrt);
+
+	report = 0;
+	if (reg & WM8994_MIC2_DET_STS)
+		report |= priv->micdet[1].det;
+	if (reg & WM8994_MIC2_SHRT_STS)
+		report |= priv->micdet[1].shrt;
+	snd_soc_jack_report(priv->micdet[1].jack, report,
+			    priv->micdet[1].det | priv->micdet[1].shrt);
+
+	return IRQ_HANDLED;
+}
+
+static int wm8994_codec_probe(struct snd_soc_codec *codec)
+{
+	struct wm8994_priv *wm8994;
+	int ret, i;
+
+	codec->control_data = dev_get_drvdata(codec->dev->parent);
+
+	wm8994 = kzalloc(sizeof(struct wm8994_priv), GFP_KERNEL);
+	if (wm8994 == NULL)
+		return -ENOMEM;
+	snd_soc_codec_set_drvdata(codec, wm8994);
+
+	codec->reg_cache = &wm8994->reg_cache;
+
+	wm8994->pdata = dev_get_platdata(codec->dev->parent);
+	wm8994->codec = codec;
+
+	/* Fill the cache with physical values we inherited; don't reset */
+	ret = wm8994_bulk_read(codec->control_data, 0,
+			       ARRAY_SIZE(wm8994->reg_cache) - 1,
+			       codec->reg_cache);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to fill register cache: %d\n",
+			ret);
+		goto err;
+	}
+
+	/* Clear the cached values for unreadable/volatile registers to
+	 * avoid potential confusion.
+	 */
+	for (i = 0; i < ARRAY_SIZE(wm8994->reg_cache); i++)
+		if (wm8994_volatile(i) || !wm8994_readable(i))
+			wm8994->reg_cache[i] = 0;
+
+	/* Set revision-specific configuration */
+	wm8994->revision = snd_soc_read(codec, WM8994_CHIP_REVISION);
+	switch (wm8994->revision) {
+	case 2:
+	case 3:
+		wm8994->hubs.dcs_codes = -5;
+		wm8994->hubs.hp_startup_mode = 1;
+		wm8994->hubs.dcs_readback_mode = 1;
+		break;
+	default:
+		wm8994->hubs.dcs_readback_mode = 1;
+		break;
+	}
+
+	ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_DET,
+				 wm8994_mic_irq, "Mic 1 detect", wm8994);
+	if (ret != 0)
+		dev_warn(codec->dev,
+			 "Failed to request Mic1 detect IRQ: %d\n", ret);
+
+	ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT,
+				 wm8994_mic_irq, "Mic 1 short", wm8994);
+	if (ret != 0)
+		dev_warn(codec->dev,
+			 "Failed to request Mic1 short IRQ: %d\n", ret);
+
+	ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC2_DET,
+				 wm8994_mic_irq, "Mic 2 detect", wm8994);
+	if (ret != 0)
+		dev_warn(codec->dev,
+			 "Failed to request Mic2 detect IRQ: %d\n", ret);
+
+	ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT,
+				 wm8994_mic_irq, "Mic 2 short", wm8994);
+	if (ret != 0)
+		dev_warn(codec->dev,
+			 "Failed to request Mic2 short IRQ: %d\n", ret);
+
+	/* Remember if AIFnLRCLK is configured as a GPIO.  This should be
+	 * configured on init - if a system wants to do this dynamically
+	 * at runtime we can deal with that then.
+	 */
+	ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_1);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to read GPIO1 state: %d\n", ret);
+		goto err_irq;
+	}
+	if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) {
+		wm8994->lrclk_shared[0] = 1;
+		wm8994_dai[0].symmetric_rates = 1;
+	} else {
+		wm8994->lrclk_shared[0] = 0;
+	}
+
+	ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_6);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to read GPIO6 state: %d\n", ret);
+		goto err_irq;
+	}
+	if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) {
+		wm8994->lrclk_shared[1] = 1;
+		wm8994_dai[1].symmetric_rates = 1;
+	} else {
+		wm8994->lrclk_shared[1] = 0;
+	}
+
+	wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Latch volume updates (right only; we always do left then right). */
+	snd_soc_update_bits(codec, WM8994_AIF1_DAC1_RIGHT_VOLUME,
+			    WM8994_AIF1DAC1_VU, WM8994_AIF1DAC1_VU);
+	snd_soc_update_bits(codec, WM8994_AIF1_DAC2_RIGHT_VOLUME,
+			    WM8994_AIF1DAC2_VU, WM8994_AIF1DAC2_VU);
+	snd_soc_update_bits(codec, WM8994_AIF2_DAC_RIGHT_VOLUME,
+			    WM8994_AIF2DAC_VU, WM8994_AIF2DAC_VU);
+	snd_soc_update_bits(codec, WM8994_AIF1_ADC1_RIGHT_VOLUME,
+			    WM8994_AIF1ADC1_VU, WM8994_AIF1ADC1_VU);
+	snd_soc_update_bits(codec, WM8994_AIF1_ADC2_RIGHT_VOLUME,
+			    WM8994_AIF1ADC2_VU, WM8994_AIF1ADC2_VU);
+	snd_soc_update_bits(codec, WM8994_AIF2_ADC_RIGHT_VOLUME,
+			    WM8994_AIF2ADC_VU, WM8994_AIF1ADC2_VU);
+	snd_soc_update_bits(codec, WM8994_DAC1_RIGHT_VOLUME,
+			    WM8994_DAC1_VU, WM8994_DAC1_VU);
+	snd_soc_update_bits(codec, WM8994_DAC2_RIGHT_VOLUME,
+			    WM8994_DAC2_VU, WM8994_DAC2_VU);
+
+	/* Set the low bit of the 3D stereo depth so TLV matches */
+	snd_soc_update_bits(codec, WM8994_AIF1_DAC1_FILTERS_2,
+			    1 << WM8994_AIF1DAC1_3D_GAIN_SHIFT,
+			    1 << WM8994_AIF1DAC1_3D_GAIN_SHIFT);
+	snd_soc_update_bits(codec, WM8994_AIF1_DAC2_FILTERS_2,
+			    1 << WM8994_AIF1DAC2_3D_GAIN_SHIFT,
+			    1 << WM8994_AIF1DAC2_3D_GAIN_SHIFT);
+	snd_soc_update_bits(codec, WM8994_AIF2_DAC_FILTERS_2,
+			    1 << WM8994_AIF2DAC_3D_GAIN_SHIFT,
+			    1 << WM8994_AIF2DAC_3D_GAIN_SHIFT);
+
+	/* Unconditionally enable AIF1 ADC TDM mode; it only affects
+	 * behaviour on idle TDM clock cycles. */
+	snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1,
+			    WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM);
+
+	wm8994_update_class_w(codec);
+
+	wm8994_handle_pdata(wm8994);
+
+	wm_hubs_add_analogue_controls(codec);
+	snd_soc_add_controls(codec, wm8994_snd_controls,
+			     ARRAY_SIZE(wm8994_snd_controls));
+	snd_soc_dapm_new_controls(codec, wm8994_dapm_widgets,
+				  ARRAY_SIZE(wm8994_dapm_widgets));
+	wm_hubs_add_analogue_routes(codec, 0, 0);
+	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+	return 0;
+
+err_irq:
+	wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994);
+	wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994);
+	wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994);
+	wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994);
+err:
+	kfree(wm8994);
+	return ret;
+}
+
+static int  wm8994_codec_remove(struct snd_soc_codec *codec)
+{
+	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+	wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994);
+	wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994);
+	wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994);
+	wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994);
+	kfree(wm8994->retune_mobile_texts);
+	kfree(wm8994->drc_texts);
+	kfree(wm8994);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {
+	.probe =	wm8994_codec_probe,
+	.remove =	wm8994_codec_remove,
+	.suspend =	wm8994_suspend,
+	.resume =	wm8994_resume,
+	.read = wm8994_read,
+	.write = wm8994_write,
+	.readable_register = wm8994_readable,
+	.volatile_register = wm8994_volatile,
+	.set_bias_level = wm8994_set_bias_level,
+};
+
+static int __devinit wm8994_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,
+			wm8994_dai, ARRAY_SIZE(wm8994_dai));
+}
+
+static int __devexit wm8994_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver wm8994_codec_driver = {
+	.driver = {
+		   .name = "wm8994-codec",
+		   .owner = THIS_MODULE,
+		   },
+	.probe = wm8994_probe,
+	.remove = __devexit_p(wm8994_remove),
+};
+
+static __init int wm8994_init(void)
+{
+	return platform_driver_register(&wm8994_codec_driver);
+}
+module_init(wm8994_init);
+
+static __exit void wm8994_exit(void)
+{
+	platform_driver_unregister(&wm8994_codec_driver);
+}
+module_exit(wm8994_exit);
+
+
+MODULE_DESCRIPTION("ASoC WM8994 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm8994-codec");
diff --git a/linux/sound/soc/codecs/wm8994.h b/linux/sound/soc/codecs/wm8994.h
new file mode 100644
index 0000000..d8dce26
--- /dev/null
+++ b/linux/sound/soc/codecs/wm8994.h
@@ -0,0 +1,34 @@
+/*
+ * wm8994.h  --  WM8994 Soc Audio driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM8994_H
+#define _WM8994_H
+
+#include <sound/soc.h>
+
+/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */
+#define WM8994_SYSCLK_MCLK1 1
+#define WM8994_SYSCLK_MCLK2 2
+#define WM8994_SYSCLK_FLL1  3
+#define WM8994_SYSCLK_FLL2  4
+
+/* OPCLK is also configured with set_dai_sysclk, specify division*10 as rate. */
+#define WM8994_SYSCLK_OPCLK 5
+
+#define WM8994_FLL1 1
+#define WM8994_FLL2 2
+
+#define WM8994_FLL_SRC_MCLK1  1
+#define WM8994_FLL_SRC_MCLK2  2
+#define WM8994_FLL_SRC_LRCLK  3
+#define WM8994_FLL_SRC_BCLK   4
+
+int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
+		      int micbias, int det, int shrt);
+
+#endif
diff --git a/linux/sound/soc/codecs/wm9081.c b/linux/sound/soc/codecs/wm9081.c
new file mode 100644
index 0000000..a486670
--- /dev/null
+++ b/linux/sound/soc/codecs/wm9081.c
@@ -0,0 +1,1397 @@
+/*
+ * wm9081.c  --  WM9081 ALSA SoC Audio driver
+ *
+ * Author: Mark Brown
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <sound/wm9081.h>
+#include "wm9081.h"
+
+static u16 wm9081_reg_defaults[] = {
+	0x0000,     /* R0  - Software Reset */
+	0x0000,     /* R1 */
+	0x00B9,     /* R2  - Analogue Lineout */
+	0x00B9,     /* R3  - Analogue Speaker PGA */
+	0x0001,     /* R4  - VMID Control */
+	0x0068,     /* R5  - Bias Control 1 */
+	0x0000,     /* R6 */
+	0x0000,     /* R7  - Analogue Mixer */
+	0x0000,     /* R8  - Anti Pop Control */
+	0x01DB,     /* R9  - Analogue Speaker 1 */
+	0x0018,     /* R10 - Analogue Speaker 2 */
+	0x0180,     /* R11 - Power Management */
+	0x0000,     /* R12 - Clock Control 1 */
+	0x0038,     /* R13 - Clock Control 2 */
+	0x4000,     /* R14 - Clock Control 3 */
+	0x0000,     /* R15 */
+	0x0000,     /* R16 - FLL Control 1 */
+	0x0200,     /* R17 - FLL Control 2 */
+	0x0000,     /* R18 - FLL Control 3 */
+	0x0204,     /* R19 - FLL Control 4 */
+	0x0000,     /* R20 - FLL Control 5 */
+	0x0000,     /* R21 */
+	0x0000,     /* R22 - Audio Interface 1 */
+	0x0002,     /* R23 - Audio Interface 2 */
+	0x0008,     /* R24 - Audio Interface 3 */
+	0x0022,     /* R25 - Audio Interface 4 */
+	0x0000,     /* R26 - Interrupt Status */
+	0x0006,     /* R27 - Interrupt Status Mask */
+	0x0000,     /* R28 - Interrupt Polarity */
+	0x0000,     /* R29 - Interrupt Control */
+	0x00C0,     /* R30 - DAC Digital 1 */
+	0x0008,     /* R31 - DAC Digital 2 */
+	0x09AF,     /* R32 - DRC 1 */
+	0x4201,     /* R33 - DRC 2 */
+	0x0000,     /* R34 - DRC 3 */
+	0x0000,     /* R35 - DRC 4 */
+	0x0000,     /* R36 */
+	0x0000,     /* R37 */
+	0x0000,     /* R38 - Write Sequencer 1 */
+	0x0000,     /* R39 - Write Sequencer 2 */
+	0x0002,     /* R40 - MW Slave 1 */
+	0x0000,     /* R41 */
+	0x0000,     /* R42 - EQ 1 */
+	0x0000,     /* R43 - EQ 2 */
+	0x0FCA,     /* R44 - EQ 3 */
+	0x0400,     /* R45 - EQ 4 */
+	0x00B8,     /* R46 - EQ 5 */
+	0x1EB5,     /* R47 - EQ 6 */
+	0xF145,     /* R48 - EQ 7 */
+	0x0B75,     /* R49 - EQ 8 */
+	0x01C5,     /* R50 - EQ 9 */
+	0x169E,     /* R51 - EQ 10 */
+	0xF829,     /* R52 - EQ 11 */
+	0x07AD,     /* R53 - EQ 12 */
+	0x1103,     /* R54 - EQ 13 */
+	0x1C58,     /* R55 - EQ 14 */
+	0xF373,     /* R56 - EQ 15 */
+	0x0A54,     /* R57 - EQ 16 */
+	0x0558,     /* R58 - EQ 17 */
+	0x0564,     /* R59 - EQ 18 */
+	0x0559,     /* R60 - EQ 19 */
+	0x4000,     /* R61 - EQ 20 */
+};
+
+static struct {
+	int ratio;
+	int clk_sys_rate;
+} clk_sys_rates[] = {
+	{ 64,   0 },
+	{ 128,  1 },
+	{ 192,  2 },
+	{ 256,  3 },
+	{ 384,  4 },
+	{ 512,  5 },
+	{ 768,  6 },
+	{ 1024, 7 },
+	{ 1408, 8 },
+	{ 1536, 9 },
+};
+
+static struct {
+	int rate;
+	int sample_rate;
+} sample_rates[] = {
+	{ 8000,  0  },
+	{ 11025, 1  },
+	{ 12000, 2  },
+	{ 16000, 3  },
+	{ 22050, 4  },
+	{ 24000, 5  },
+	{ 32000, 6  },
+	{ 44100, 7  },
+	{ 48000, 8  },
+	{ 88200, 9  },
+	{ 96000, 10 },
+};
+
+static struct {
+	int div; /* *10 due to .5s */
+	int bclk_div;
+} bclk_divs[] = {
+	{ 10,  0  },
+	{ 15,  1  },
+	{ 20,  2  },
+	{ 30,  3  },
+	{ 40,  4  },
+	{ 50,  5  },
+	{ 55,  6  },
+	{ 60,  7  },
+	{ 80,  8  },
+	{ 100, 9  },
+	{ 110, 10 },
+	{ 120, 11 },
+	{ 160, 12 },
+	{ 200, 13 },
+	{ 220, 14 },
+	{ 240, 15 },
+	{ 250, 16 },
+	{ 300, 17 },
+	{ 320, 18 },
+	{ 440, 19 },
+	{ 480, 20 },
+};
+
+struct wm9081_priv {
+	enum snd_soc_control_type control_type;
+	void *control_data;
+	u16 reg_cache[WM9081_MAX_REGISTER + 1];
+	int sysclk_source;
+	int mclk_rate;
+	int sysclk_rate;
+	int fs;
+	int bclk;
+	int master;
+	int fll_fref;
+	int fll_fout;
+	int tdm_width;
+	struct wm9081_retune_mobile_config *retune;
+};
+
+static int wm9081_volatile_register(unsigned int reg)
+{
+	switch (reg) {
+	case WM9081_SOFTWARE_RESET:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static int wm9081_reset(struct snd_soc_codec *codec)
+{
+	return snd_soc_write(codec, WM9081_SOFTWARE_RESET, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(drc_in_tlv, -4500, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_out_tlv, -2250, 75, 0);
+static const DECLARE_TLV_DB_SCALE(drc_min_tlv, -1800, 600, 0);
+static unsigned int drc_max_tlv[] = {
+	TLV_DB_RANGE_HEAD(4),
+	0, 0, TLV_DB_SCALE_ITEM(1200, 0, 0),
+	1, 1, TLV_DB_SCALE_ITEM(1800, 0, 0),
+	2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0),
+	3, 3, TLV_DB_SCALE_ITEM(3600, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(drc_qr_tlv, 1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(drc_startup_tlv, -300, 50, 0);
+
+static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
+
+static const DECLARE_TLV_DB_SCALE(in_tlv, -600, 600, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+
+static const char *drc_high_text[] = {
+	"1",
+	"1/2",
+	"1/4",
+	"1/8",
+	"1/16",
+	"0",
+};
+
+static const struct soc_enum drc_high =
+	SOC_ENUM_SINGLE(WM9081_DRC_3, 3, 6, drc_high_text);
+
+static const char *drc_low_text[] = {
+	"1",
+	"1/2",
+	"1/4",
+	"1/8",
+	"0",
+};
+
+static const struct soc_enum drc_low =
+	SOC_ENUM_SINGLE(WM9081_DRC_3, 0, 5, drc_low_text);
+
+static const char *drc_atk_text[] = {
+	"181us",
+	"181us",
+	"363us",
+	"726us",
+	"1.45ms",
+	"2.9ms",
+	"5.8ms",
+	"11.6ms",
+	"23.2ms",
+	"46.4ms",
+	"92.8ms",
+	"185.6ms",
+};
+
+static const struct soc_enum drc_atk =
+	SOC_ENUM_SINGLE(WM9081_DRC_2, 12, 12, drc_atk_text);
+
+static const char *drc_dcy_text[] = {
+	"186ms",
+	"372ms",
+	"743ms",
+	"1.49s",
+	"2.97s",
+	"5.94s",
+	"11.89s",
+	"23.78s",
+	"47.56s",
+};
+
+static const struct soc_enum drc_dcy =
+	SOC_ENUM_SINGLE(WM9081_DRC_2, 8, 9, drc_dcy_text);
+
+static const char *drc_qr_dcy_text[] = {
+	"0.725ms",
+	"1.45ms",
+	"5.8ms",
+};
+
+static const struct soc_enum drc_qr_dcy =
+	SOC_ENUM_SINGLE(WM9081_DRC_2, 4, 3, drc_qr_dcy_text);
+
+static const char *dac_deemph_text[] = {
+	"None",
+	"32kHz",
+	"44.1kHz",
+	"48kHz",
+};
+
+static const struct soc_enum dac_deemph =
+	SOC_ENUM_SINGLE(WM9081_DAC_DIGITAL_2, 1, 4, dac_deemph_text);
+
+static const char *speaker_mode_text[] = {
+	"Class D",
+	"Class AB",
+};
+
+static const struct soc_enum speaker_mode =
+	SOC_ENUM_SINGLE(WM9081_ANALOGUE_SPEAKER_2, 6, 2, speaker_mode_text);
+
+static int speaker_mode_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg;
+
+	reg = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_2);
+	if (reg & WM9081_SPK_MODE)
+		ucontrol->value.integer.value[0] = 1;
+	else
+		ucontrol->value.integer.value[0] = 0;
+
+	return 0;
+}
+
+/*
+ * Stop any attempts to change speaker mode while the speaker is enabled.
+ *
+ * We also have some special anti-pop controls dependant on speaker
+ * mode which must be changed along with the mode.
+ */
+static int speaker_mode_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg_pwr = snd_soc_read(codec, WM9081_POWER_MANAGEMENT);
+	unsigned int reg2 = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_2);
+
+	/* Are we changing anything? */
+	if (ucontrol->value.integer.value[0] ==
+	    ((reg2 & WM9081_SPK_MODE) != 0))
+		return 0;
+
+	/* Don't try to change modes while enabled */
+	if (reg_pwr & WM9081_SPK_ENA)
+		return -EINVAL;
+
+	if (ucontrol->value.integer.value[0]) {
+		/* Class AB */
+		reg2 &= ~(WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL);
+		reg2 |= WM9081_SPK_MODE;
+	} else {
+		/* Class D */
+		reg2 |= WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL;
+		reg2 &= ~WM9081_SPK_MODE;
+	}
+
+	snd_soc_write(codec, WM9081_ANALOGUE_SPEAKER_2, reg2);
+
+	return 0;
+}
+
+static const struct snd_kcontrol_new wm9081_snd_controls[] = {
+SOC_SINGLE_TLV("IN1 Volume", WM9081_ANALOGUE_MIXER, 1, 1, 1, in_tlv),
+SOC_SINGLE_TLV("IN2 Volume", WM9081_ANALOGUE_MIXER, 3, 1, 1, in_tlv),
+
+SOC_SINGLE_TLV("Playback Volume", WM9081_DAC_DIGITAL_1, 1, 96, 0, dac_tlv),
+
+SOC_SINGLE("LINEOUT Switch", WM9081_ANALOGUE_LINEOUT, 7, 1, 1),
+SOC_SINGLE("LINEOUT ZC Switch", WM9081_ANALOGUE_LINEOUT, 6, 1, 0),
+SOC_SINGLE_TLV("LINEOUT Volume", WM9081_ANALOGUE_LINEOUT, 0, 63, 0, out_tlv),
+
+SOC_SINGLE("DRC Switch", WM9081_DRC_1, 15, 1, 0),
+SOC_ENUM("DRC High Slope", drc_high),
+SOC_ENUM("DRC Low Slope", drc_low),
+SOC_SINGLE_TLV("DRC Input Volume", WM9081_DRC_4, 5, 60, 1, drc_in_tlv),
+SOC_SINGLE_TLV("DRC Output Volume", WM9081_DRC_4, 0, 30, 1, drc_out_tlv),
+SOC_SINGLE_TLV("DRC Minimum Volume", WM9081_DRC_2, 2, 3, 1, drc_min_tlv),
+SOC_SINGLE_TLV("DRC Maximum Volume", WM9081_DRC_2, 0, 3, 0, drc_max_tlv),
+SOC_ENUM("DRC Attack", drc_atk),
+SOC_ENUM("DRC Decay", drc_dcy),
+SOC_SINGLE("DRC Quick Release Switch", WM9081_DRC_1, 2, 1, 0),
+SOC_SINGLE_TLV("DRC Quick Release Volume", WM9081_DRC_2, 6, 3, 0, drc_qr_tlv),
+SOC_ENUM("DRC Quick Release Decay", drc_qr_dcy),
+SOC_SINGLE_TLV("DRC Startup Volume", WM9081_DRC_1, 6, 18, 0, drc_startup_tlv),
+
+SOC_SINGLE("EQ Switch", WM9081_EQ_1, 0, 1, 0),
+
+SOC_SINGLE("Speaker DC Volume", WM9081_ANALOGUE_SPEAKER_1, 3, 5, 0),
+SOC_SINGLE("Speaker AC Volume", WM9081_ANALOGUE_SPEAKER_1, 0, 5, 0),
+SOC_SINGLE("Speaker Switch", WM9081_ANALOGUE_SPEAKER_PGA, 7, 1, 1),
+SOC_SINGLE("Speaker ZC Switch", WM9081_ANALOGUE_SPEAKER_PGA, 6, 1, 0),
+SOC_SINGLE_TLV("Speaker Volume", WM9081_ANALOGUE_SPEAKER_PGA, 0, 63, 0,
+	       out_tlv),
+SOC_ENUM("DAC Deemphasis", dac_deemph),
+SOC_ENUM_EXT("Speaker Mode", speaker_mode, speaker_mode_get, speaker_mode_put),
+};
+
+static const struct snd_kcontrol_new wm9081_eq_controls[] = {
+SOC_SINGLE_TLV("EQ1 Volume", WM9081_EQ_1, 11, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ2 Volume", WM9081_EQ_1, 6, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ3 Volume", WM9081_EQ_1, 1, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ4 Volume", WM9081_EQ_2, 11, 24, 0, eq_tlv),
+SOC_SINGLE_TLV("EQ5 Volume", WM9081_EQ_2, 6, 24, 0, eq_tlv),
+};
+
+static const struct snd_kcontrol_new mixer[] = {
+SOC_DAPM_SINGLE("IN1 Switch", WM9081_ANALOGUE_MIXER, 0, 1, 0),
+SOC_DAPM_SINGLE("IN2 Switch", WM9081_ANALOGUE_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("Playback Switch", WM9081_ANALOGUE_MIXER, 4, 1, 0),
+};
+
+static int speaker_event(struct snd_soc_dapm_widget *w,
+			 struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	unsigned int reg = snd_soc_read(codec, WM9081_POWER_MANAGEMENT);
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		reg |= WM9081_SPK_ENA;
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		reg &= ~WM9081_SPK_ENA;
+		break;
+	}
+
+	snd_soc_write(codec, WM9081_POWER_MANAGEMENT, reg);
+
+	return 0;
+}
+
+struct _fll_div {
+	u16 fll_fratio;
+	u16 fll_outdiv;
+	u16 fll_clk_ref_div;
+	u16 n;
+	u16 k;
+};
+
+/* The size in bits of the FLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static struct {
+	unsigned int min;
+	unsigned int max;
+	u16 fll_fratio;
+	int ratio;
+} fll_fratios[] = {
+	{       0,    64000, 4, 16 },
+	{   64000,   128000, 3,  8 },
+	{  128000,   256000, 2,  4 },
+	{  256000,  1000000, 1,  2 },
+	{ 1000000, 13500000, 0,  1 },
+};
+
+static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
+		       unsigned int Fout)
+{
+	u64 Kpart;
+	unsigned int K, Ndiv, Nmod, target;
+	unsigned int div;
+	int i;
+
+	/* Fref must be <=13.5MHz */
+	div = 1;
+	while ((Fref / div) > 13500000) {
+		div *= 2;
+
+		if (div > 8) {
+			pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
+			       Fref);
+			return -EINVAL;
+		}
+	}
+	fll_div->fll_clk_ref_div = div / 2;
+
+	pr_debug("Fref=%u Fout=%u\n", Fref, Fout);
+
+	/* Apply the division for our remaining calculations */
+	Fref /= div;
+
+	/* Fvco should be 90-100MHz; don't check the upper bound */
+	div = 0;
+	target = Fout * 2;
+	while (target < 90000000) {
+		div++;
+		target *= 2;
+		if (div > 7) {
+			pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
+			       Fout);
+			return -EINVAL;
+		}
+	}
+	fll_div->fll_outdiv = div;
+
+	pr_debug("Fvco=%dHz\n", target);
+
+	/* Find an appropraite FLL_FRATIO and factor it out of the target */
+	for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
+		if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
+			fll_div->fll_fratio = fll_fratios[i].fll_fratio;
+			target /= fll_fratios[i].ratio;
+			break;
+		}
+	}
+	if (i == ARRAY_SIZE(fll_fratios)) {
+		pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
+		return -EINVAL;
+	}
+
+	/* Now, calculate N.K */
+	Ndiv = target / Fref;
+
+	fll_div->n = Ndiv;
+	Nmod = target % Fref;
+	pr_debug("Nmod=%d\n", Nmod);
+
+	/* Calculate fractional part - scale up so we can round. */
+	Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, Fref);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	fll_div->k = K / 10;
+
+	pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n",
+		 fll_div->n, fll_div->k,
+		 fll_div->fll_fratio, fll_div->fll_outdiv,
+		 fll_div->fll_clk_ref_div);
+
+	return 0;
+}
+
+static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id,
+			  unsigned int Fref, unsigned int Fout)
+{
+	struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+	u16 reg1, reg4, reg5;
+	struct _fll_div fll_div;
+	int ret;
+	int clk_sys_reg;
+
+	/* Any change? */
+	if (Fref == wm9081->fll_fref && Fout == wm9081->fll_fout)
+		return 0;
+
+	/* Disable the FLL */
+	if (Fout == 0) {
+		dev_dbg(codec->dev, "FLL disabled\n");
+		wm9081->fll_fref = 0;
+		wm9081->fll_fout = 0;
+
+		return 0;
+	}
+
+	ret = fll_factors(&fll_div, Fref, Fout);
+	if (ret != 0)
+		return ret;
+
+	reg5 = snd_soc_read(codec, WM9081_FLL_CONTROL_5);
+	reg5 &= ~WM9081_FLL_CLK_SRC_MASK;
+
+	switch (fll_id) {
+	case WM9081_SYSCLK_FLL_MCLK:
+		reg5 |= 0x1;
+		break;
+
+	default:
+		dev_err(codec->dev, "Unknown FLL ID %d\n", fll_id);
+		return -EINVAL;
+	}
+
+	/* Disable CLK_SYS while we reconfigure */
+	clk_sys_reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_3);
+	if (clk_sys_reg & WM9081_CLK_SYS_ENA)
+		snd_soc_write(codec, WM9081_CLOCK_CONTROL_3,
+			     clk_sys_reg & ~WM9081_CLK_SYS_ENA);
+
+	/* Any FLL configuration change requires that the FLL be
+	 * disabled first. */
+	reg1 = snd_soc_read(codec, WM9081_FLL_CONTROL_1);
+	reg1 &= ~WM9081_FLL_ENA;
+	snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1);
+
+	/* Apply the configuration */
+	if (fll_div.k)
+		reg1 |= WM9081_FLL_FRAC_MASK;
+	else
+		reg1 &= ~WM9081_FLL_FRAC_MASK;
+	snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1);
+
+	snd_soc_write(codec, WM9081_FLL_CONTROL_2,
+		     (fll_div.fll_outdiv << WM9081_FLL_OUTDIV_SHIFT) |
+		     (fll_div.fll_fratio << WM9081_FLL_FRATIO_SHIFT));
+	snd_soc_write(codec, WM9081_FLL_CONTROL_3, fll_div.k);
+
+	reg4 = snd_soc_read(codec, WM9081_FLL_CONTROL_4);
+	reg4 &= ~WM9081_FLL_N_MASK;
+	reg4 |= fll_div.n << WM9081_FLL_N_SHIFT;
+	snd_soc_write(codec, WM9081_FLL_CONTROL_4, reg4);
+
+	reg5 &= ~WM9081_FLL_CLK_REF_DIV_MASK;
+	reg5 |= fll_div.fll_clk_ref_div << WM9081_FLL_CLK_REF_DIV_SHIFT;
+	snd_soc_write(codec, WM9081_FLL_CONTROL_5, reg5);
+
+	/* Enable the FLL */
+	snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1 | WM9081_FLL_ENA);
+
+	/* Then bring CLK_SYS up again if it was disabled */
+	if (clk_sys_reg & WM9081_CLK_SYS_ENA)
+		snd_soc_write(codec, WM9081_CLOCK_CONTROL_3, clk_sys_reg);
+
+	dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout);
+
+	wm9081->fll_fref = Fref;
+	wm9081->fll_fout = Fout;
+
+	return 0;
+}
+
+static int configure_clock(struct snd_soc_codec *codec)
+{
+	struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+	int new_sysclk, i, target;
+	unsigned int reg;
+	int ret = 0;
+	int mclkdiv = 0;
+	int fll = 0;
+
+	switch (wm9081->sysclk_source) {
+	case WM9081_SYSCLK_MCLK:
+		if (wm9081->mclk_rate > 12225000) {
+			mclkdiv = 1;
+			wm9081->sysclk_rate = wm9081->mclk_rate / 2;
+		} else {
+			wm9081->sysclk_rate = wm9081->mclk_rate;
+		}
+		wm9081_set_fll(codec, WM9081_SYSCLK_FLL_MCLK, 0, 0);
+		break;
+
+	case WM9081_SYSCLK_FLL_MCLK:
+		/* If we have a sample rate calculate a CLK_SYS that
+		 * gives us a suitable DAC configuration, plus BCLK.
+		 * Ideally we would check to see if we can clock
+		 * directly from MCLK and only use the FLL if this is
+		 * not the case, though care must be taken with free
+		 * running mode.
+		 */
+		if (wm9081->master && wm9081->bclk) {
+			/* Make sure we can generate CLK_SYS and BCLK
+			 * and that we've got 3MHz for optimal
+			 * performance. */
+			for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) {
+				target = wm9081->fs * clk_sys_rates[i].ratio;
+				new_sysclk = target;
+				if (target >= wm9081->bclk &&
+				    target > 3000000)
+					break;
+			}
+
+			if (i == ARRAY_SIZE(clk_sys_rates))
+				return -EINVAL;
+
+		} else if (wm9081->fs) {
+			for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) {
+				new_sysclk = clk_sys_rates[i].ratio
+					* wm9081->fs;
+				if (new_sysclk > 3000000)
+					break;
+			}
+
+			if (i == ARRAY_SIZE(clk_sys_rates))
+				return -EINVAL;
+
+		} else {
+			new_sysclk = 12288000;
+		}
+
+		ret = wm9081_set_fll(codec, WM9081_SYSCLK_FLL_MCLK,
+				     wm9081->mclk_rate, new_sysclk);
+		if (ret == 0) {
+			wm9081->sysclk_rate = new_sysclk;
+
+			/* Switch SYSCLK over to FLL */
+			fll = 1;
+		} else {
+			wm9081->sysclk_rate = wm9081->mclk_rate;
+		}
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_1);
+	if (mclkdiv)
+		reg |= WM9081_MCLKDIV2;
+	else
+		reg &= ~WM9081_MCLKDIV2;
+	snd_soc_write(codec, WM9081_CLOCK_CONTROL_1, reg);
+
+	reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_3);
+	if (fll)
+		reg |= WM9081_CLK_SRC_SEL;
+	else
+		reg &= ~WM9081_CLK_SRC_SEL;
+	snd_soc_write(codec, WM9081_CLOCK_CONTROL_3, reg);
+
+	dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm9081->sysclk_rate);
+
+	return ret;
+}
+
+static int clk_sys_event(struct snd_soc_dapm_widget *w,
+			 struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+
+	/* This should be done on init() for bypass paths */
+	switch (wm9081->sysclk_source) {
+	case WM9081_SYSCLK_MCLK:
+		dev_dbg(codec->dev, "Using %dHz MCLK\n", wm9081->mclk_rate);
+		break;
+	case WM9081_SYSCLK_FLL_MCLK:
+		dev_dbg(codec->dev, "Using %dHz MCLK with FLL\n",
+			wm9081->mclk_rate);
+		break;
+	default:
+		dev_err(codec->dev, "System clock not configured\n");
+		return -EINVAL;
+	}
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		configure_clock(codec);
+		break;
+
+	case SND_SOC_DAPM_POST_PMD:
+		/* Disable the FLL if it's running */
+		wm9081_set_fll(codec, 0, 0, 0);
+		break;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget wm9081_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1"),
+SND_SOC_DAPM_INPUT("IN2"),
+
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM9081_POWER_MANAGEMENT, 0, 0),
+
+SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0,
+			     mixer, ARRAY_SIZE(mixer)),
+
+SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA_E("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0,
+		   speaker_event,
+		   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_OUTPUT("LINEOUT"),
+SND_SOC_DAPM_OUTPUT("SPKN"),
+SND_SOC_DAPM_OUTPUT("SPKP"),
+
+SND_SOC_DAPM_SUPPLY("CLK_SYS", WM9081_CLOCK_CONTROL_3, 0, 0, clk_sys_event,
+		    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_SUPPLY("CLK_DSP", WM9081_CLOCK_CONTROL_3, 1, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("TOCLK", WM9081_CLOCK_CONTROL_3, 2, 0, NULL, 0),
+};
+
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+	{ "DAC", NULL, "CLK_SYS" },
+	{ "DAC", NULL, "CLK_DSP" },
+
+	{ "Mixer", "IN1 Switch", "IN1" },
+	{ "Mixer", "IN2 Switch", "IN2" },
+	{ "Mixer", "Playback Switch", "DAC" },
+
+	{ "LINEOUT PGA", NULL, "Mixer" },
+	{ "LINEOUT PGA", NULL, "TOCLK" },
+	{ "LINEOUT PGA", NULL, "CLK_SYS" },
+
+	{ "LINEOUT", NULL, "LINEOUT PGA" },
+
+	{ "Speaker PGA", NULL, "Mixer" },
+	{ "Speaker PGA", NULL, "TOCLK" },
+	{ "Speaker PGA", NULL, "CLK_SYS" },
+
+	{ "SPKN", NULL, "Speaker PGA" },
+	{ "SPKP", NULL, "Speaker PGA" },
+};
+
+static int wm9081_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 reg;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		/* VMID=2*40k */
+		reg = snd_soc_read(codec, WM9081_VMID_CONTROL);
+		reg &= ~WM9081_VMID_SEL_MASK;
+		reg |= 0x2;
+		snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
+
+		/* Normal bias current */
+		reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+		reg &= ~WM9081_STBY_BIAS_ENA;
+		snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		/* Initial cold start */
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Disable LINEOUT discharge */
+			reg = snd_soc_read(codec, WM9081_ANTI_POP_CONTROL);
+			reg &= ~WM9081_LINEOUT_DISCH;
+			snd_soc_write(codec, WM9081_ANTI_POP_CONTROL, reg);
+
+			/* Select startup bias source */
+			reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+			reg |= WM9081_BIAS_SRC | WM9081_BIAS_ENA;
+			snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
+
+			/* VMID 2*4k; Soft VMID ramp enable */
+			reg = snd_soc_read(codec, WM9081_VMID_CONTROL);
+			reg |= WM9081_VMID_RAMP | 0x6;
+			snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
+
+			mdelay(100);
+
+			/* Normal bias enable & soft start off */
+			reg |= WM9081_BIAS_ENA;
+			reg &= ~WM9081_VMID_RAMP;
+			snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
+
+			/* Standard bias source */
+			reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+			reg &= ~WM9081_BIAS_SRC;
+			snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
+		}
+
+		/* VMID 2*240k */
+		reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+		reg &= ~WM9081_VMID_SEL_MASK;
+		reg |= 0x40;
+		snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
+
+		/* Standby bias current on */
+		reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+		reg |= WM9081_STBY_BIAS_ENA;
+		snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		/* Startup bias source */
+		reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
+		reg |= WM9081_BIAS_SRC;
+		snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
+
+		/* Disable VMID and biases with soft ramping */
+		reg = snd_soc_read(codec, WM9081_VMID_CONTROL);
+		reg &= ~(WM9081_VMID_SEL_MASK | WM9081_BIAS_ENA);
+		reg |= WM9081_VMID_RAMP;
+		snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
+
+		/* Actively discharge LINEOUT */
+		reg = snd_soc_read(codec, WM9081_ANTI_POP_CONTROL);
+		reg |= WM9081_LINEOUT_DISCH;
+		snd_soc_write(codec, WM9081_ANTI_POP_CONTROL, reg);
+		break;
+	}
+
+	codec->bias_level = level;
+
+	return 0;
+}
+
+static int wm9081_set_dai_fmt(struct snd_soc_dai *dai,
+			      unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+	unsigned int aif2 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_2);
+
+	aif2 &= ~(WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV |
+		  WM9081_BCLK_DIR | WM9081_LRCLK_DIR | WM9081_AIF_FMT_MASK);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		wm9081->master = 0;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		aif2 |= WM9081_LRCLK_DIR;
+		wm9081->master = 1;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		aif2 |= WM9081_BCLK_DIR;
+		wm9081->master = 1;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		aif2 |= WM9081_LRCLK_DIR | WM9081_BCLK_DIR;
+		wm9081->master = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_B:
+		aif2 |= WM9081_AIF_LRCLK_INV;
+	case SND_SOC_DAIFMT_DSP_A:
+		aif2 |= 0x3;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		aif2 |= 0x2;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		aif2 |= 0x1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		/* frame inversion not valid for DSP modes */
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif2 |= WM9081_AIF_BCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_LEFT_J:
+		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+		case SND_SOC_DAIFMT_NB_NF:
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			aif2 |= WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			aif2 |= WM9081_AIF_BCLK_INV;
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			aif2 |= WM9081_AIF_LRCLK_INV;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM9081_AUDIO_INTERFACE_2, aif2);
+
+	return 0;
+}
+
+static int wm9081_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+	int ret, i, best, best_val, cur_val;
+	unsigned int clk_ctrl2, aif1, aif2, aif3, aif4;
+
+	clk_ctrl2 = snd_soc_read(codec, WM9081_CLOCK_CONTROL_2);
+	clk_ctrl2 &= ~(WM9081_CLK_SYS_RATE_MASK | WM9081_SAMPLE_RATE_MASK);
+
+	aif1 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_1);
+
+	aif2 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_2);
+	aif2 &= ~WM9081_AIF_WL_MASK;
+
+	aif3 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_3);
+	aif3 &= ~WM9081_BCLK_DIV_MASK;
+
+	aif4 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_4);
+	aif4 &= ~WM9081_LRCLK_RATE_MASK;
+
+	wm9081->fs = params_rate(params);
+
+	if (wm9081->tdm_width) {
+		/* If TDM is set up then that fixes our BCLK. */
+		int slots = ((aif1 & WM9081_AIFDAC_TDM_MODE_MASK) >>
+			     WM9081_AIFDAC_TDM_MODE_SHIFT) + 1;
+
+		wm9081->bclk = wm9081->fs * wm9081->tdm_width * slots;
+	} else {
+		/* Otherwise work out a BCLK from the sample size */
+		wm9081->bclk = 2 * wm9081->fs;
+
+		switch (params_format(params)) {
+		case SNDRV_PCM_FORMAT_S16_LE:
+			wm9081->bclk *= 16;
+			break;
+		case SNDRV_PCM_FORMAT_S20_3LE:
+			wm9081->bclk *= 20;
+			aif2 |= 0x4;
+			break;
+		case SNDRV_PCM_FORMAT_S24_LE:
+			wm9081->bclk *= 24;
+			aif2 |= 0x8;
+			break;
+		case SNDRV_PCM_FORMAT_S32_LE:
+			wm9081->bclk *= 32;
+			aif2 |= 0xc;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm9081->bclk);
+
+	ret = configure_clock(codec);
+	if (ret != 0)
+		return ret;
+
+	/* Select nearest CLK_SYS_RATE */
+	best = 0;
+	best_val = abs((wm9081->sysclk_rate / clk_sys_rates[0].ratio)
+		       - wm9081->fs);
+	for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
+		cur_val = abs((wm9081->sysclk_rate /
+			       clk_sys_rates[i].ratio) - wm9081->fs);
+		if (cur_val < best_val) {
+			best = i;
+			best_val = cur_val;
+		}
+	}
+	dev_dbg(codec->dev, "Selected CLK_SYS_RATIO of %d\n",
+		clk_sys_rates[best].ratio);
+	clk_ctrl2 |= (clk_sys_rates[best].clk_sys_rate
+		      << WM9081_CLK_SYS_RATE_SHIFT);
+
+	/* SAMPLE_RATE */
+	best = 0;
+	best_val = abs(wm9081->fs - sample_rates[0].rate);
+	for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
+		/* Closest match */
+		cur_val = abs(wm9081->fs - sample_rates[i].rate);
+		if (cur_val < best_val) {
+			best = i;
+			best_val = cur_val;
+		}
+	}
+	dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n",
+		sample_rates[best].rate);
+	clk_ctrl2 |= (sample_rates[best].sample_rate
+			<< WM9081_SAMPLE_RATE_SHIFT);
+
+	/* BCLK_DIV */
+	best = 0;
+	best_val = INT_MAX;
+	for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
+		cur_val = ((wm9081->sysclk_rate * 10) / bclk_divs[i].div)
+			- wm9081->bclk;
+		if (cur_val < 0) /* Table is sorted */
+			break;
+		if (cur_val < best_val) {
+			best = i;
+			best_val = cur_val;
+		}
+	}
+	wm9081->bclk = (wm9081->sysclk_rate * 10) / bclk_divs[best].div;
+	dev_dbg(codec->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n",
+		bclk_divs[best].div, wm9081->bclk);
+	aif3 |= bclk_divs[best].bclk_div;
+
+	/* LRCLK is a simple fraction of BCLK */
+	dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm9081->bclk / wm9081->fs);
+	aif4 |= wm9081->bclk / wm9081->fs;
+
+	/* Apply a ReTune Mobile configuration if it's in use */
+	if (wm9081->retune) {
+		struct wm9081_retune_mobile_config *retune = wm9081->retune;
+		struct wm9081_retune_mobile_setting *s;
+		int eq1;
+
+		best = 0;
+		best_val = abs(retune->configs[0].rate - wm9081->fs);
+		for (i = 0; i < retune->num_configs; i++) {
+			cur_val = abs(retune->configs[i].rate - wm9081->fs);
+			if (cur_val < best_val) {
+				best_val = cur_val;
+				best = i;
+			}
+		}
+		s = &retune->configs[best];
+
+		dev_dbg(codec->dev, "ReTune Mobile %s tuned for %dHz\n",
+			s->name, s->rate);
+
+		/* If the EQ is enabled then disable it while we write out */
+		eq1 = snd_soc_read(codec, WM9081_EQ_1) & WM9081_EQ_ENA;
+		if (eq1 & WM9081_EQ_ENA)
+			snd_soc_write(codec, WM9081_EQ_1, 0);
+
+		/* Write out the other values */
+		for (i = 1; i < ARRAY_SIZE(s->config); i++)
+			snd_soc_write(codec, WM9081_EQ_1 + i, s->config[i]);
+
+		eq1 |= (s->config[0] & ~WM9081_EQ_ENA);
+		snd_soc_write(codec, WM9081_EQ_1, eq1);
+	}
+
+	snd_soc_write(codec, WM9081_CLOCK_CONTROL_2, clk_ctrl2);
+	snd_soc_write(codec, WM9081_AUDIO_INTERFACE_2, aif2);
+	snd_soc_write(codec, WM9081_AUDIO_INTERFACE_3, aif3);
+	snd_soc_write(codec, WM9081_AUDIO_INTERFACE_4, aif4);
+
+	return 0;
+}
+
+static int wm9081_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	unsigned int reg;
+
+	reg = snd_soc_read(codec, WM9081_DAC_DIGITAL_2);
+
+	if (mute)
+		reg |= WM9081_DAC_MUTE;
+	else
+		reg &= ~WM9081_DAC_MUTE;
+
+	snd_soc_write(codec, WM9081_DAC_DIGITAL_2, reg);
+
+	return 0;
+}
+
+static int wm9081_set_sysclk(struct snd_soc_dai *codec_dai,
+			     int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+
+	switch (clk_id) {
+	case WM9081_SYSCLK_MCLK:
+	case WM9081_SYSCLK_FLL_MCLK:
+		wm9081->sysclk_source = clk_id;
+		wm9081->mclk_rate = freq;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wm9081_set_tdm_slot(struct snd_soc_dai *dai,
+	unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+	unsigned int aif1 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_1);
+
+	aif1 &= ~(WM9081_AIFDAC_TDM_SLOT_MASK | WM9081_AIFDAC_TDM_MODE_MASK);
+
+	if (slots < 0 || slots > 4)
+		return -EINVAL;
+
+	wm9081->tdm_width = slot_width;
+
+	if (slots == 0)
+		slots = 1;
+
+	aif1 |= (slots - 1) << WM9081_AIFDAC_TDM_MODE_SHIFT;
+
+	switch (rx_mask) {
+	case 1:
+		break;
+	case 2:
+		aif1 |= 0x10;
+		break;
+	case 4:
+		aif1 |= 0x20;
+		break;
+	case 8:
+		aif1 |= 0x30;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_write(codec, WM9081_AUDIO_INTERFACE_1, aif1);
+
+	return 0;
+}
+
+#define WM9081_RATES SNDRV_PCM_RATE_8000_96000
+
+#define WM9081_FORMATS \
+	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+	 SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops wm9081_dai_ops = {
+	.hw_params = wm9081_hw_params,
+	.set_sysclk = wm9081_set_sysclk,
+	.set_fmt = wm9081_set_dai_fmt,
+	.digital_mute = wm9081_digital_mute,
+	.set_tdm_slot = wm9081_set_tdm_slot,
+};
+
+/* We report two channels because the CODEC processes a stereo signal, even
+ * though it is only capable of handling a mono output.
+ */
+static struct snd_soc_dai_driver wm9081_dai = {
+	.name = "wm9081-hifi",
+	.playback = {
+		.stream_name = "HiFi Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM9081_RATES,
+		.formats = WM9081_FORMATS,
+	},
+	.ops = &wm9081_dai_ops,
+};
+
+static int wm9081_probe(struct snd_soc_codec *codec)
+{
+	struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+	u16 reg;
+
+	codec->control_data = wm9081->control_data;
+	ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm9081->control_type);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	reg = snd_soc_read(codec, WM9081_SOFTWARE_RESET);
+	if (reg != 0x9081) {
+		dev_err(codec->dev, "Device is not a WM9081: ID=0x%x\n", reg);
+		ret = -EINVAL;
+		return ret;
+	}
+
+	ret = wm9081_reset(codec);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to issue reset\n");
+		return ret;
+	}
+
+	wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* Enable zero cross by default */
+	reg = snd_soc_read(codec, WM9081_ANALOGUE_LINEOUT);
+	snd_soc_write(codec, WM9081_ANALOGUE_LINEOUT, reg | WM9081_LINEOUTZC);
+	reg = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_PGA);
+	snd_soc_write(codec, WM9081_ANALOGUE_SPEAKER_PGA,
+		     reg | WM9081_SPKPGAZC);
+
+	snd_soc_add_controls(codec, wm9081_snd_controls,
+			     ARRAY_SIZE(wm9081_snd_controls));
+	if (!wm9081->retune) {
+		dev_dbg(codec->dev,
+			"No ReTune Mobile data, using normal EQ\n");
+		snd_soc_add_controls(codec, wm9081_eq_controls,
+				     ARRAY_SIZE(wm9081_eq_controls));
+	}
+
+	snd_soc_dapm_new_controls(codec, wm9081_dapm_widgets,
+				  ARRAY_SIZE(wm9081_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
+
+	return ret;
+}
+
+static int wm9081_remove(struct snd_soc_codec *codec)
+{
+	wm9081_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm9081_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm9081_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm9081_resume(struct snd_soc_codec *codec)
+{
+	u16 *reg_cache = codec->reg_cache;
+	int i;
+
+	for (i = 0; i < codec->driver->reg_cache_size; i++) {
+		if (i == WM9081_SOFTWARE_RESET)
+			continue;
+
+		snd_soc_write(codec, i, reg_cache[i]);
+	}
+
+	wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+#else
+#define wm9081_suspend NULL
+#define wm9081_resume NULL
+#endif
+
+static struct snd_soc_codec_driver soc_codec_dev_wm9081 = {
+	.probe = 	wm9081_probe,
+	.remove = 	wm9081_remove,
+	.suspend =	wm9081_suspend,
+	.resume =	wm9081_resume,
+	.set_bias_level = wm9081_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm9081_reg_defaults),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm9081_reg_defaults,
+	.volatile_register = wm9081_volatile_register,
+};
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm9081_i2c_probe(struct i2c_client *i2c,
+				      const struct i2c_device_id *id)
+{
+	struct wm9081_priv *wm9081;
+	int ret;
+
+	wm9081 = kzalloc(sizeof(struct wm9081_priv), GFP_KERNEL);
+	if (wm9081 == NULL)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, wm9081);
+	wm9081->control_type = SND_SOC_I2C;
+	wm9081->control_data = i2c;
+
+	ret = snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm9081, &wm9081_dai, 1);
+	if (ret < 0)
+		kfree(wm9081);
+	return ret;
+}
+
+static __devexit int wm9081_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+static const struct i2c_device_id wm9081_i2c_id[] = {
+	{ "wm9081", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm9081_i2c_id);
+
+static struct i2c_driver wm9081_i2c_driver = {
+	.driver = {
+		.name = "wm9081-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe =    wm9081_i2c_probe,
+	.remove =   __devexit_p(wm9081_i2c_remove),
+	.id_table = wm9081_i2c_id,
+};
+#endif
+
+static int __init wm9081_modinit(void)
+{
+	int ret = 0;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&wm9081_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register WM9081 I2C driver: %d\n",
+		       ret);
+	}
+#endif
+	return ret;
+}
+module_init(wm9081_modinit);
+
+static void __exit wm9081_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&wm9081_i2c_driver);
+#endif
+}
+module_exit(wm9081_exit);
+
+
+MODULE_DESCRIPTION("ASoC WM9081 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm9081.h b/linux/sound/soc/codecs/wm9081.h
new file mode 100644
index 0000000..871cccb
--- /dev/null
+++ b/linux/sound/soc/codecs/wm9081.h
@@ -0,0 +1,784 @@
+#ifndef WM9081_H
+#define WM9081_H
+
+/*
+ * wm9081.c  --  WM9081 ALSA SoC Audio driver
+ *
+ * Author: Mark Brown
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <sound/soc.h>
+
+/*
+ * SYSCLK sources
+ */
+#define WM9081_SYSCLK_MCLK      1   /* Use MCLK without FLL */
+#define WM9081_SYSCLK_FLL_MCLK  2   /* Use MCLK, enabling FLL if required */
+
+/*
+ * Register values.
+ */
+#define WM9081_SOFTWARE_RESET                   0x00
+#define WM9081_ANALOGUE_LINEOUT                 0x02
+#define WM9081_ANALOGUE_SPEAKER_PGA             0x03
+#define WM9081_VMID_CONTROL                     0x04
+#define WM9081_BIAS_CONTROL_1                   0x05
+#define WM9081_ANALOGUE_MIXER                   0x07
+#define WM9081_ANTI_POP_CONTROL                 0x08
+#define WM9081_ANALOGUE_SPEAKER_1               0x09
+#define WM9081_ANALOGUE_SPEAKER_2               0x0A
+#define WM9081_POWER_MANAGEMENT                 0x0B
+#define WM9081_CLOCK_CONTROL_1                  0x0C
+#define WM9081_CLOCK_CONTROL_2                  0x0D
+#define WM9081_CLOCK_CONTROL_3                  0x0E
+#define WM9081_FLL_CONTROL_1                    0x10
+#define WM9081_FLL_CONTROL_2                    0x11
+#define WM9081_FLL_CONTROL_3                    0x12
+#define WM9081_FLL_CONTROL_4                    0x13
+#define WM9081_FLL_CONTROL_5                    0x14
+#define WM9081_AUDIO_INTERFACE_1                0x16
+#define WM9081_AUDIO_INTERFACE_2                0x17
+#define WM9081_AUDIO_INTERFACE_3                0x18
+#define WM9081_AUDIO_INTERFACE_4                0x19
+#define WM9081_INTERRUPT_STATUS                 0x1A
+#define WM9081_INTERRUPT_STATUS_MASK            0x1B
+#define WM9081_INTERRUPT_POLARITY               0x1C
+#define WM9081_INTERRUPT_CONTROL                0x1D
+#define WM9081_DAC_DIGITAL_1                    0x1E
+#define WM9081_DAC_DIGITAL_2                    0x1F
+#define WM9081_DRC_1                            0x20
+#define WM9081_DRC_2                            0x21
+#define WM9081_DRC_3                            0x22
+#define WM9081_DRC_4                            0x23
+#define WM9081_WRITE_SEQUENCER_1                0x26
+#define WM9081_WRITE_SEQUENCER_2                0x27
+#define WM9081_MW_SLAVE_1                       0x28
+#define WM9081_EQ_1                             0x2A
+#define WM9081_EQ_2                             0x2B
+#define WM9081_EQ_3                             0x2C
+#define WM9081_EQ_4                             0x2D
+#define WM9081_EQ_5                             0x2E
+#define WM9081_EQ_6                             0x2F
+#define WM9081_EQ_7                             0x30
+#define WM9081_EQ_8                             0x31
+#define WM9081_EQ_9                             0x32
+#define WM9081_EQ_10                            0x33
+#define WM9081_EQ_11                            0x34
+#define WM9081_EQ_12                            0x35
+#define WM9081_EQ_13                            0x36
+#define WM9081_EQ_14                            0x37
+#define WM9081_EQ_15                            0x38
+#define WM9081_EQ_16                            0x39
+#define WM9081_EQ_17                            0x3A
+#define WM9081_EQ_18                            0x3B
+#define WM9081_EQ_19                            0x3C
+#define WM9081_EQ_20                            0x3D
+
+#define WM9081_REGISTER_COUNT                   55
+#define WM9081_MAX_REGISTER                     0x3D
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Software Reset
+ */
+#define WM9081_SW_RST_DEV_ID1_MASK              0xFFFF  /* SW_RST_DEV_ID1 - [15:0] */
+#define WM9081_SW_RST_DEV_ID1_SHIFT                  0  /* SW_RST_DEV_ID1 - [15:0] */
+#define WM9081_SW_RST_DEV_ID1_WIDTH                 16  /* SW_RST_DEV_ID1 - [15:0] */
+
+/*
+ * R2 (0x02) - Analogue Lineout
+ */
+#define WM9081_LINEOUT_MUTE                     0x0080  /* LINEOUT_MUTE */
+#define WM9081_LINEOUT_MUTE_MASK                0x0080  /* LINEOUT_MUTE */
+#define WM9081_LINEOUT_MUTE_SHIFT                    7  /* LINEOUT_MUTE */
+#define WM9081_LINEOUT_MUTE_WIDTH                    1  /* LINEOUT_MUTE */
+#define WM9081_LINEOUTZC                        0x0040  /* LINEOUTZC */
+#define WM9081_LINEOUTZC_MASK                   0x0040  /* LINEOUTZC */
+#define WM9081_LINEOUTZC_SHIFT                       6  /* LINEOUTZC */
+#define WM9081_LINEOUTZC_WIDTH                       1  /* LINEOUTZC */
+#define WM9081_LINEOUT_VOL_MASK                 0x003F  /* LINEOUT_VOL - [5:0] */
+#define WM9081_LINEOUT_VOL_SHIFT                     0  /* LINEOUT_VOL - [5:0] */
+#define WM9081_LINEOUT_VOL_WIDTH                     6  /* LINEOUT_VOL - [5:0] */
+
+/*
+ * R3 (0x03) - Analogue Speaker PGA
+ */
+#define WM9081_SPKPGA_MUTE                      0x0080  /* SPKPGA_MUTE */
+#define WM9081_SPKPGA_MUTE_MASK                 0x0080  /* SPKPGA_MUTE */
+#define WM9081_SPKPGA_MUTE_SHIFT                     7  /* SPKPGA_MUTE */
+#define WM9081_SPKPGA_MUTE_WIDTH                     1  /* SPKPGA_MUTE */
+#define WM9081_SPKPGAZC                         0x0040  /* SPKPGAZC */
+#define WM9081_SPKPGAZC_MASK                    0x0040  /* SPKPGAZC */
+#define WM9081_SPKPGAZC_SHIFT                        6  /* SPKPGAZC */
+#define WM9081_SPKPGAZC_WIDTH                        1  /* SPKPGAZC */
+#define WM9081_SPKPGA_VOL_MASK                  0x003F  /* SPKPGA_VOL - [5:0] */
+#define WM9081_SPKPGA_VOL_SHIFT                      0  /* SPKPGA_VOL - [5:0] */
+#define WM9081_SPKPGA_VOL_WIDTH                      6  /* SPKPGA_VOL - [5:0] */
+
+/*
+ * R4 (0x04) - VMID Control
+ */
+#define WM9081_VMID_BUF_ENA                     0x0020  /* VMID_BUF_ENA */
+#define WM9081_VMID_BUF_ENA_MASK                0x0020  /* VMID_BUF_ENA */
+#define WM9081_VMID_BUF_ENA_SHIFT                    5  /* VMID_BUF_ENA */
+#define WM9081_VMID_BUF_ENA_WIDTH                    1  /* VMID_BUF_ENA */
+#define WM9081_VMID_RAMP                        0x0008  /* VMID_RAMP */
+#define WM9081_VMID_RAMP_MASK                   0x0008  /* VMID_RAMP */
+#define WM9081_VMID_RAMP_SHIFT                       3  /* VMID_RAMP */
+#define WM9081_VMID_RAMP_WIDTH                       1  /* VMID_RAMP */
+#define WM9081_VMID_SEL_MASK                    0x0006  /* VMID_SEL - [2:1] */
+#define WM9081_VMID_SEL_SHIFT                        1  /* VMID_SEL - [2:1] */
+#define WM9081_VMID_SEL_WIDTH                        2  /* VMID_SEL - [2:1] */
+#define WM9081_VMID_FAST_ST                     0x0001  /* VMID_FAST_ST */
+#define WM9081_VMID_FAST_ST_MASK                0x0001  /* VMID_FAST_ST */
+#define WM9081_VMID_FAST_ST_SHIFT                    0  /* VMID_FAST_ST */
+#define WM9081_VMID_FAST_ST_WIDTH                    1  /* VMID_FAST_ST */
+
+/*
+ * R5 (0x05) - Bias Control 1
+ */
+#define WM9081_BIAS_SRC                         0x0040  /* BIAS_SRC */
+#define WM9081_BIAS_SRC_MASK                    0x0040  /* BIAS_SRC */
+#define WM9081_BIAS_SRC_SHIFT                        6  /* BIAS_SRC */
+#define WM9081_BIAS_SRC_WIDTH                        1  /* BIAS_SRC */
+#define WM9081_STBY_BIAS_LVL                    0x0020  /* STBY_BIAS_LVL */
+#define WM9081_STBY_BIAS_LVL_MASK               0x0020  /* STBY_BIAS_LVL */
+#define WM9081_STBY_BIAS_LVL_SHIFT                   5  /* STBY_BIAS_LVL */
+#define WM9081_STBY_BIAS_LVL_WIDTH                   1  /* STBY_BIAS_LVL */
+#define WM9081_STBY_BIAS_ENA                    0x0010  /* STBY_BIAS_ENA */
+#define WM9081_STBY_BIAS_ENA_MASK               0x0010  /* STBY_BIAS_ENA */
+#define WM9081_STBY_BIAS_ENA_SHIFT                   4  /* STBY_BIAS_ENA */
+#define WM9081_STBY_BIAS_ENA_WIDTH                   1  /* STBY_BIAS_ENA */
+#define WM9081_BIAS_LVL_MASK                    0x000C  /* BIAS_LVL - [3:2] */
+#define WM9081_BIAS_LVL_SHIFT                        2  /* BIAS_LVL - [3:2] */
+#define WM9081_BIAS_LVL_WIDTH                        2  /* BIAS_LVL - [3:2] */
+#define WM9081_BIAS_ENA                         0x0002  /* BIAS_ENA */
+#define WM9081_BIAS_ENA_MASK                    0x0002  /* BIAS_ENA */
+#define WM9081_BIAS_ENA_SHIFT                        1  /* BIAS_ENA */
+#define WM9081_BIAS_ENA_WIDTH                        1  /* BIAS_ENA */
+#define WM9081_STARTUP_BIAS_ENA                 0x0001  /* STARTUP_BIAS_ENA */
+#define WM9081_STARTUP_BIAS_ENA_MASK            0x0001  /* STARTUP_BIAS_ENA */
+#define WM9081_STARTUP_BIAS_ENA_SHIFT                0  /* STARTUP_BIAS_ENA */
+#define WM9081_STARTUP_BIAS_ENA_WIDTH                1  /* STARTUP_BIAS_ENA */
+
+/*
+ * R7 (0x07) - Analogue Mixer
+ */
+#define WM9081_DAC_SEL                          0x0010  /* DAC_SEL */
+#define WM9081_DAC_SEL_MASK                     0x0010  /* DAC_SEL */
+#define WM9081_DAC_SEL_SHIFT                         4  /* DAC_SEL */
+#define WM9081_DAC_SEL_WIDTH                         1  /* DAC_SEL */
+#define WM9081_IN2_VOL                          0x0008  /* IN2_VOL */
+#define WM9081_IN2_VOL_MASK                     0x0008  /* IN2_VOL */
+#define WM9081_IN2_VOL_SHIFT                         3  /* IN2_VOL */
+#define WM9081_IN2_VOL_WIDTH                         1  /* IN2_VOL */
+#define WM9081_IN2_ENA                          0x0004  /* IN2_ENA */
+#define WM9081_IN2_ENA_MASK                     0x0004  /* IN2_ENA */
+#define WM9081_IN2_ENA_SHIFT                         2  /* IN2_ENA */
+#define WM9081_IN2_ENA_WIDTH                         1  /* IN2_ENA */
+#define WM9081_IN1_VOL                          0x0002  /* IN1_VOL */
+#define WM9081_IN1_VOL_MASK                     0x0002  /* IN1_VOL */
+#define WM9081_IN1_VOL_SHIFT                         1  /* IN1_VOL */
+#define WM9081_IN1_VOL_WIDTH                         1  /* IN1_VOL */
+#define WM9081_IN1_ENA                          0x0001  /* IN1_ENA */
+#define WM9081_IN1_ENA_MASK                     0x0001  /* IN1_ENA */
+#define WM9081_IN1_ENA_SHIFT                         0  /* IN1_ENA */
+#define WM9081_IN1_ENA_WIDTH                         1  /* IN1_ENA */
+
+/*
+ * R8 (0x08) - Anti Pop Control
+ */
+#define WM9081_LINEOUT_DISCH                    0x0004  /* LINEOUT_DISCH */
+#define WM9081_LINEOUT_DISCH_MASK               0x0004  /* LINEOUT_DISCH */
+#define WM9081_LINEOUT_DISCH_SHIFT                   2  /* LINEOUT_DISCH */
+#define WM9081_LINEOUT_DISCH_WIDTH                   1  /* LINEOUT_DISCH */
+#define WM9081_LINEOUT_VROI                     0x0002  /* LINEOUT_VROI */
+#define WM9081_LINEOUT_VROI_MASK                0x0002  /* LINEOUT_VROI */
+#define WM9081_LINEOUT_VROI_SHIFT                    1  /* LINEOUT_VROI */
+#define WM9081_LINEOUT_VROI_WIDTH                    1  /* LINEOUT_VROI */
+#define WM9081_LINEOUT_CLAMP                    0x0001  /* LINEOUT_CLAMP */
+#define WM9081_LINEOUT_CLAMP_MASK               0x0001  /* LINEOUT_CLAMP */
+#define WM9081_LINEOUT_CLAMP_SHIFT                   0  /* LINEOUT_CLAMP */
+#define WM9081_LINEOUT_CLAMP_WIDTH                   1  /* LINEOUT_CLAMP */
+
+/*
+ * R9 (0x09) - Analogue Speaker 1
+ */
+#define WM9081_SPK_DCGAIN_MASK                  0x0038  /* SPK_DCGAIN - [5:3] */
+#define WM9081_SPK_DCGAIN_SHIFT                      3  /* SPK_DCGAIN - [5:3] */
+#define WM9081_SPK_DCGAIN_WIDTH                      3  /* SPK_DCGAIN - [5:3] */
+#define WM9081_SPK_ACGAIN_MASK                  0x0007  /* SPK_ACGAIN - [2:0] */
+#define WM9081_SPK_ACGAIN_SHIFT                      0  /* SPK_ACGAIN - [2:0] */
+#define WM9081_SPK_ACGAIN_WIDTH                      3  /* SPK_ACGAIN - [2:0] */
+
+/*
+ * R10 (0x0A) - Analogue Speaker 2
+ */
+#define WM9081_SPK_MODE                         0x0040  /* SPK_MODE */
+#define WM9081_SPK_MODE_MASK                    0x0040  /* SPK_MODE */
+#define WM9081_SPK_MODE_SHIFT                        6  /* SPK_MODE */
+#define WM9081_SPK_MODE_WIDTH                        1  /* SPK_MODE */
+#define WM9081_SPK_INV_MUTE                     0x0010  /* SPK_INV_MUTE */
+#define WM9081_SPK_INV_MUTE_MASK                0x0010  /* SPK_INV_MUTE */
+#define WM9081_SPK_INV_MUTE_SHIFT                    4  /* SPK_INV_MUTE */
+#define WM9081_SPK_INV_MUTE_WIDTH                    1  /* SPK_INV_MUTE */
+#define WM9081_OUT_SPK_CTRL                     0x0008  /* OUT_SPK_CTRL */
+#define WM9081_OUT_SPK_CTRL_MASK                0x0008  /* OUT_SPK_CTRL */
+#define WM9081_OUT_SPK_CTRL_SHIFT                    3  /* OUT_SPK_CTRL */
+#define WM9081_OUT_SPK_CTRL_WIDTH                    1  /* OUT_SPK_CTRL */
+
+/*
+ * R11 (0x0B) - Power Management
+ */
+#define WM9081_TSHUT_ENA                        0x0100  /* TSHUT_ENA */
+#define WM9081_TSHUT_ENA_MASK                   0x0100  /* TSHUT_ENA */
+#define WM9081_TSHUT_ENA_SHIFT                       8  /* TSHUT_ENA */
+#define WM9081_TSHUT_ENA_WIDTH                       1  /* TSHUT_ENA */
+#define WM9081_TSENSE_ENA                       0x0080  /* TSENSE_ENA */
+#define WM9081_TSENSE_ENA_MASK                  0x0080  /* TSENSE_ENA */
+#define WM9081_TSENSE_ENA_SHIFT                      7  /* TSENSE_ENA */
+#define WM9081_TSENSE_ENA_WIDTH                      1  /* TSENSE_ENA */
+#define WM9081_TEMP_SHUT                        0x0040  /* TEMP_SHUT */
+#define WM9081_TEMP_SHUT_MASK                   0x0040  /* TEMP_SHUT */
+#define WM9081_TEMP_SHUT_SHIFT                       6  /* TEMP_SHUT */
+#define WM9081_TEMP_SHUT_WIDTH                       1  /* TEMP_SHUT */
+#define WM9081_LINEOUT_ENA                      0x0010  /* LINEOUT_ENA */
+#define WM9081_LINEOUT_ENA_MASK                 0x0010  /* LINEOUT_ENA */
+#define WM9081_LINEOUT_ENA_SHIFT                     4  /* LINEOUT_ENA */
+#define WM9081_LINEOUT_ENA_WIDTH                     1  /* LINEOUT_ENA */
+#define WM9081_SPKPGA_ENA                       0x0004  /* SPKPGA_ENA */
+#define WM9081_SPKPGA_ENA_MASK                  0x0004  /* SPKPGA_ENA */
+#define WM9081_SPKPGA_ENA_SHIFT                      2  /* SPKPGA_ENA */
+#define WM9081_SPKPGA_ENA_WIDTH                      1  /* SPKPGA_ENA */
+#define WM9081_SPK_ENA                          0x0002  /* SPK_ENA */
+#define WM9081_SPK_ENA_MASK                     0x0002  /* SPK_ENA */
+#define WM9081_SPK_ENA_SHIFT                         1  /* SPK_ENA */
+#define WM9081_SPK_ENA_WIDTH                         1  /* SPK_ENA */
+#define WM9081_DAC_ENA                          0x0001  /* DAC_ENA */
+#define WM9081_DAC_ENA_MASK                     0x0001  /* DAC_ENA */
+#define WM9081_DAC_ENA_SHIFT                         0  /* DAC_ENA */
+#define WM9081_DAC_ENA_WIDTH                         1  /* DAC_ENA */
+
+/*
+ * R12 (0x0C) - Clock Control 1
+ */
+#define WM9081_CLK_OP_DIV_MASK                  0x1C00  /* CLK_OP_DIV - [12:10] */
+#define WM9081_CLK_OP_DIV_SHIFT                     10  /* CLK_OP_DIV - [12:10] */
+#define WM9081_CLK_OP_DIV_WIDTH                      3  /* CLK_OP_DIV - [12:10] */
+#define WM9081_CLK_TO_DIV_MASK                  0x0300  /* CLK_TO_DIV - [9:8] */
+#define WM9081_CLK_TO_DIV_SHIFT                      8  /* CLK_TO_DIV - [9:8] */
+#define WM9081_CLK_TO_DIV_WIDTH                      2  /* CLK_TO_DIV - [9:8] */
+#define WM9081_MCLKDIV2                         0x0080  /* MCLKDIV2 */
+#define WM9081_MCLKDIV2_MASK                    0x0080  /* MCLKDIV2 */
+#define WM9081_MCLKDIV2_SHIFT                        7  /* MCLKDIV2 */
+#define WM9081_MCLKDIV2_WIDTH                        1  /* MCLKDIV2 */
+
+/*
+ * R13 (0x0D) - Clock Control 2
+ */
+#define WM9081_CLK_SYS_RATE_MASK                0x00F0  /* CLK_SYS_RATE - [7:4] */
+#define WM9081_CLK_SYS_RATE_SHIFT                    4  /* CLK_SYS_RATE - [7:4] */
+#define WM9081_CLK_SYS_RATE_WIDTH                    4  /* CLK_SYS_RATE - [7:4] */
+#define WM9081_SAMPLE_RATE_MASK                 0x000F  /* SAMPLE_RATE - [3:0] */
+#define WM9081_SAMPLE_RATE_SHIFT                     0  /* SAMPLE_RATE - [3:0] */
+#define WM9081_SAMPLE_RATE_WIDTH                     4  /* SAMPLE_RATE - [3:0] */
+
+/*
+ * R14 (0x0E) - Clock Control 3
+ */
+#define WM9081_CLK_SRC_SEL                      0x2000  /* CLK_SRC_SEL */
+#define WM9081_CLK_SRC_SEL_MASK                 0x2000  /* CLK_SRC_SEL */
+#define WM9081_CLK_SRC_SEL_SHIFT                    13  /* CLK_SRC_SEL */
+#define WM9081_CLK_SRC_SEL_WIDTH                     1  /* CLK_SRC_SEL */
+#define WM9081_CLK_OP_ENA                       0x0020  /* CLK_OP_ENA */
+#define WM9081_CLK_OP_ENA_MASK                  0x0020  /* CLK_OP_ENA */
+#define WM9081_CLK_OP_ENA_SHIFT                      5  /* CLK_OP_ENA */
+#define WM9081_CLK_OP_ENA_WIDTH                      1  /* CLK_OP_ENA */
+#define WM9081_CLK_TO_ENA                       0x0004  /* CLK_TO_ENA */
+#define WM9081_CLK_TO_ENA_MASK                  0x0004  /* CLK_TO_ENA */
+#define WM9081_CLK_TO_ENA_SHIFT                      2  /* CLK_TO_ENA */
+#define WM9081_CLK_TO_ENA_WIDTH                      1  /* CLK_TO_ENA */
+#define WM9081_CLK_DSP_ENA                      0x0002  /* CLK_DSP_ENA */
+#define WM9081_CLK_DSP_ENA_MASK                 0x0002  /* CLK_DSP_ENA */
+#define WM9081_CLK_DSP_ENA_SHIFT                     1  /* CLK_DSP_ENA */
+#define WM9081_CLK_DSP_ENA_WIDTH                     1  /* CLK_DSP_ENA */
+#define WM9081_CLK_SYS_ENA                      0x0001  /* CLK_SYS_ENA */
+#define WM9081_CLK_SYS_ENA_MASK                 0x0001  /* CLK_SYS_ENA */
+#define WM9081_CLK_SYS_ENA_SHIFT                     0  /* CLK_SYS_ENA */
+#define WM9081_CLK_SYS_ENA_WIDTH                     1  /* CLK_SYS_ENA */
+
+/*
+ * R16 (0x10) - FLL Control 1
+ */
+#define WM9081_FLL_HOLD                         0x0008  /* FLL_HOLD */
+#define WM9081_FLL_HOLD_MASK                    0x0008  /* FLL_HOLD */
+#define WM9081_FLL_HOLD_SHIFT                        3  /* FLL_HOLD */
+#define WM9081_FLL_HOLD_WIDTH                        1  /* FLL_HOLD */
+#define WM9081_FLL_FRAC                         0x0004  /* FLL_FRAC */
+#define WM9081_FLL_FRAC_MASK                    0x0004  /* FLL_FRAC */
+#define WM9081_FLL_FRAC_SHIFT                        2  /* FLL_FRAC */
+#define WM9081_FLL_FRAC_WIDTH                        1  /* FLL_FRAC */
+#define WM9081_FLL_ENA                          0x0001  /* FLL_ENA */
+#define WM9081_FLL_ENA_MASK                     0x0001  /* FLL_ENA */
+#define WM9081_FLL_ENA_SHIFT                         0  /* FLL_ENA */
+#define WM9081_FLL_ENA_WIDTH                         1  /* FLL_ENA */
+
+/*
+ * R17 (0x11) - FLL Control 2
+ */
+#define WM9081_FLL_OUTDIV_MASK                  0x0700  /* FLL_OUTDIV - [10:8] */
+#define WM9081_FLL_OUTDIV_SHIFT                      8  /* FLL_OUTDIV - [10:8] */
+#define WM9081_FLL_OUTDIV_WIDTH                      3  /* FLL_OUTDIV - [10:8] */
+#define WM9081_FLL_CTRL_RATE_MASK               0x0070  /* FLL_CTRL_RATE - [6:4] */
+#define WM9081_FLL_CTRL_RATE_SHIFT                   4  /* FLL_CTRL_RATE - [6:4] */
+#define WM9081_FLL_CTRL_RATE_WIDTH                   3  /* FLL_CTRL_RATE - [6:4] */
+#define WM9081_FLL_FRATIO_MASK                  0x0007  /* FLL_FRATIO - [2:0] */
+#define WM9081_FLL_FRATIO_SHIFT                      0  /* FLL_FRATIO - [2:0] */
+#define WM9081_FLL_FRATIO_WIDTH                      3  /* FLL_FRATIO - [2:0] */
+
+/*
+ * R18 (0x12) - FLL Control 3
+ */
+#define WM9081_FLL_K_MASK                       0xFFFF  /* FLL_K - [15:0] */
+#define WM9081_FLL_K_SHIFT                           0  /* FLL_K - [15:0] */
+#define WM9081_FLL_K_WIDTH                          16  /* FLL_K - [15:0] */
+
+/*
+ * R19 (0x13) - FLL Control 4
+ */
+#define WM9081_FLL_N_MASK                       0x7FE0  /* FLL_N - [14:5] */
+#define WM9081_FLL_N_SHIFT                           5  /* FLL_N - [14:5] */
+#define WM9081_FLL_N_WIDTH                          10  /* FLL_N - [14:5] */
+#define WM9081_FLL_GAIN_MASK                    0x000F  /* FLL_GAIN - [3:0] */
+#define WM9081_FLL_GAIN_SHIFT                        0  /* FLL_GAIN - [3:0] */
+#define WM9081_FLL_GAIN_WIDTH                        4  /* FLL_GAIN - [3:0] */
+
+/*
+ * R20 (0x14) - FLL Control 5
+ */
+#define WM9081_FLL_CLK_REF_DIV_MASK             0x0018  /* FLL_CLK_REF_DIV - [4:3] */
+#define WM9081_FLL_CLK_REF_DIV_SHIFT                 3  /* FLL_CLK_REF_DIV - [4:3] */
+#define WM9081_FLL_CLK_REF_DIV_WIDTH                 2  /* FLL_CLK_REF_DIV - [4:3] */
+#define WM9081_FLL_CLK_SRC_MASK                 0x0003  /* FLL_CLK_SRC - [1:0] */
+#define WM9081_FLL_CLK_SRC_SHIFT                     0  /* FLL_CLK_SRC - [1:0] */
+#define WM9081_FLL_CLK_SRC_WIDTH                     2  /* FLL_CLK_SRC - [1:0] */
+
+/*
+ * R22 (0x16) - Audio Interface 1
+ */
+#define WM9081_AIFDAC_CHAN                      0x0040  /* AIFDAC_CHAN */
+#define WM9081_AIFDAC_CHAN_MASK                 0x0040  /* AIFDAC_CHAN */
+#define WM9081_AIFDAC_CHAN_SHIFT                     6  /* AIFDAC_CHAN */
+#define WM9081_AIFDAC_CHAN_WIDTH                     1  /* AIFDAC_CHAN */
+#define WM9081_AIFDAC_TDM_SLOT_MASK             0x0030  /* AIFDAC_TDM_SLOT - [5:4] */
+#define WM9081_AIFDAC_TDM_SLOT_SHIFT                 4  /* AIFDAC_TDM_SLOT - [5:4] */
+#define WM9081_AIFDAC_TDM_SLOT_WIDTH                 2  /* AIFDAC_TDM_SLOT - [5:4] */
+#define WM9081_AIFDAC_TDM_MODE_MASK             0x000C  /* AIFDAC_TDM_MODE - [3:2] */
+#define WM9081_AIFDAC_TDM_MODE_SHIFT                 2  /* AIFDAC_TDM_MODE - [3:2] */
+#define WM9081_AIFDAC_TDM_MODE_WIDTH                 2  /* AIFDAC_TDM_MODE - [3:2] */
+#define WM9081_DAC_COMP                         0x0002  /* DAC_COMP */
+#define WM9081_DAC_COMP_MASK                    0x0002  /* DAC_COMP */
+#define WM9081_DAC_COMP_SHIFT                        1  /* DAC_COMP */
+#define WM9081_DAC_COMP_WIDTH                        1  /* DAC_COMP */
+#define WM9081_DAC_COMPMODE                     0x0001  /* DAC_COMPMODE */
+#define WM9081_DAC_COMPMODE_MASK                0x0001  /* DAC_COMPMODE */
+#define WM9081_DAC_COMPMODE_SHIFT                    0  /* DAC_COMPMODE */
+#define WM9081_DAC_COMPMODE_WIDTH                    1  /* DAC_COMPMODE */
+
+/*
+ * R23 (0x17) - Audio Interface 2
+ */
+#define WM9081_AIF_TRIS                         0x0200  /* AIF_TRIS */
+#define WM9081_AIF_TRIS_MASK                    0x0200  /* AIF_TRIS */
+#define WM9081_AIF_TRIS_SHIFT                        9  /* AIF_TRIS */
+#define WM9081_AIF_TRIS_WIDTH                        1  /* AIF_TRIS */
+#define WM9081_DAC_DAT_INV                      0x0100  /* DAC_DAT_INV */
+#define WM9081_DAC_DAT_INV_MASK                 0x0100  /* DAC_DAT_INV */
+#define WM9081_DAC_DAT_INV_SHIFT                     8  /* DAC_DAT_INV */
+#define WM9081_DAC_DAT_INV_WIDTH                     1  /* DAC_DAT_INV */
+#define WM9081_AIF_BCLK_INV                     0x0080  /* AIF_BCLK_INV */
+#define WM9081_AIF_BCLK_INV_MASK                0x0080  /* AIF_BCLK_INV */
+#define WM9081_AIF_BCLK_INV_SHIFT                    7  /* AIF_BCLK_INV */
+#define WM9081_AIF_BCLK_INV_WIDTH                    1  /* AIF_BCLK_INV */
+#define WM9081_BCLK_DIR                         0x0040  /* BCLK_DIR */
+#define WM9081_BCLK_DIR_MASK                    0x0040  /* BCLK_DIR */
+#define WM9081_BCLK_DIR_SHIFT                        6  /* BCLK_DIR */
+#define WM9081_BCLK_DIR_WIDTH                        1  /* BCLK_DIR */
+#define WM9081_LRCLK_DIR                        0x0020  /* LRCLK_DIR */
+#define WM9081_LRCLK_DIR_MASK                   0x0020  /* LRCLK_DIR */
+#define WM9081_LRCLK_DIR_SHIFT                       5  /* LRCLK_DIR */
+#define WM9081_LRCLK_DIR_WIDTH                       1  /* LRCLK_DIR */
+#define WM9081_AIF_LRCLK_INV                    0x0010  /* AIF_LRCLK_INV */
+#define WM9081_AIF_LRCLK_INV_MASK               0x0010  /* AIF_LRCLK_INV */
+#define WM9081_AIF_LRCLK_INV_SHIFT                   4  /* AIF_LRCLK_INV */
+#define WM9081_AIF_LRCLK_INV_WIDTH                   1  /* AIF_LRCLK_INV */
+#define WM9081_AIF_WL_MASK                      0x000C  /* AIF_WL - [3:2] */
+#define WM9081_AIF_WL_SHIFT                          2  /* AIF_WL - [3:2] */
+#define WM9081_AIF_WL_WIDTH                          2  /* AIF_WL - [3:2] */
+#define WM9081_AIF_FMT_MASK                     0x0003  /* AIF_FMT - [1:0] */
+#define WM9081_AIF_FMT_SHIFT                         0  /* AIF_FMT - [1:0] */
+#define WM9081_AIF_FMT_WIDTH                         2  /* AIF_FMT - [1:0] */
+
+/*
+ * R24 (0x18) - Audio Interface 3
+ */
+#define WM9081_BCLK_DIV_MASK                    0x001F  /* BCLK_DIV - [4:0] */
+#define WM9081_BCLK_DIV_SHIFT                        0  /* BCLK_DIV - [4:0] */
+#define WM9081_BCLK_DIV_WIDTH                        5  /* BCLK_DIV - [4:0] */
+
+/*
+ * R25 (0x19) - Audio Interface 4
+ */
+#define WM9081_LRCLK_RATE_MASK                  0x07FF  /* LRCLK_RATE - [10:0] */
+#define WM9081_LRCLK_RATE_SHIFT                      0  /* LRCLK_RATE - [10:0] */
+#define WM9081_LRCLK_RATE_WIDTH                     11  /* LRCLK_RATE - [10:0] */
+
+/*
+ * R26 (0x1A) - Interrupt Status
+ */
+#define WM9081_WSEQ_BUSY_EINT                   0x0004  /* WSEQ_BUSY_EINT */
+#define WM9081_WSEQ_BUSY_EINT_MASK              0x0004  /* WSEQ_BUSY_EINT */
+#define WM9081_WSEQ_BUSY_EINT_SHIFT                  2  /* WSEQ_BUSY_EINT */
+#define WM9081_WSEQ_BUSY_EINT_WIDTH                  1  /* WSEQ_BUSY_EINT */
+#define WM9081_TSHUT_EINT                       0x0001  /* TSHUT_EINT */
+#define WM9081_TSHUT_EINT_MASK                  0x0001  /* TSHUT_EINT */
+#define WM9081_TSHUT_EINT_SHIFT                      0  /* TSHUT_EINT */
+#define WM9081_TSHUT_EINT_WIDTH                      1  /* TSHUT_EINT */
+
+/*
+ * R27 (0x1B) - Interrupt Status Mask
+ */
+#define WM9081_IM_WSEQ_BUSY_EINT                0x0004  /* IM_WSEQ_BUSY_EINT */
+#define WM9081_IM_WSEQ_BUSY_EINT_MASK           0x0004  /* IM_WSEQ_BUSY_EINT */
+#define WM9081_IM_WSEQ_BUSY_EINT_SHIFT               2  /* IM_WSEQ_BUSY_EINT */
+#define WM9081_IM_WSEQ_BUSY_EINT_WIDTH               1  /* IM_WSEQ_BUSY_EINT */
+#define WM9081_IM_TSHUT_EINT                    0x0001  /* IM_TSHUT_EINT */
+#define WM9081_IM_TSHUT_EINT_MASK               0x0001  /* IM_TSHUT_EINT */
+#define WM9081_IM_TSHUT_EINT_SHIFT                   0  /* IM_TSHUT_EINT */
+#define WM9081_IM_TSHUT_EINT_WIDTH                   1  /* IM_TSHUT_EINT */
+
+/*
+ * R28 (0x1C) - Interrupt Polarity
+ */
+#define WM9081_TSHUT_INV                        0x0001  /* TSHUT_INV */
+#define WM9081_TSHUT_INV_MASK                   0x0001  /* TSHUT_INV */
+#define WM9081_TSHUT_INV_SHIFT                       0  /* TSHUT_INV */
+#define WM9081_TSHUT_INV_WIDTH                       1  /* TSHUT_INV */
+
+/*
+ * R29 (0x1D) - Interrupt Control
+ */
+#define WM9081_IRQ_POL                          0x8000  /* IRQ_POL */
+#define WM9081_IRQ_POL_MASK                     0x8000  /* IRQ_POL */
+#define WM9081_IRQ_POL_SHIFT                        15  /* IRQ_POL */
+#define WM9081_IRQ_POL_WIDTH                         1  /* IRQ_POL */
+#define WM9081_IRQ_OP_CTRL                      0x0001  /* IRQ_OP_CTRL */
+#define WM9081_IRQ_OP_CTRL_MASK                 0x0001  /* IRQ_OP_CTRL */
+#define WM9081_IRQ_OP_CTRL_SHIFT                     0  /* IRQ_OP_CTRL */
+#define WM9081_IRQ_OP_CTRL_WIDTH                     1  /* IRQ_OP_CTRL */
+
+/*
+ * R30 (0x1E) - DAC Digital 1
+ */
+#define WM9081_DAC_VOL_MASK                     0x00FF  /* DAC_VOL - [7:0] */
+#define WM9081_DAC_VOL_SHIFT                         0  /* DAC_VOL - [7:0] */
+#define WM9081_DAC_VOL_WIDTH                         8  /* DAC_VOL - [7:0] */
+
+/*
+ * R31 (0x1F) - DAC Digital 2
+ */
+#define WM9081_DAC_MUTERATE                     0x0400  /* DAC_MUTERATE */
+#define WM9081_DAC_MUTERATE_MASK                0x0400  /* DAC_MUTERATE */
+#define WM9081_DAC_MUTERATE_SHIFT                   10  /* DAC_MUTERATE */
+#define WM9081_DAC_MUTERATE_WIDTH                    1  /* DAC_MUTERATE */
+#define WM9081_DAC_MUTEMODE                     0x0200  /* DAC_MUTEMODE */
+#define WM9081_DAC_MUTEMODE_MASK                0x0200  /* DAC_MUTEMODE */
+#define WM9081_DAC_MUTEMODE_SHIFT                    9  /* DAC_MUTEMODE */
+#define WM9081_DAC_MUTEMODE_WIDTH                    1  /* DAC_MUTEMODE */
+#define WM9081_DAC_MUTE                         0x0008  /* DAC_MUTE */
+#define WM9081_DAC_MUTE_MASK                    0x0008  /* DAC_MUTE */
+#define WM9081_DAC_MUTE_SHIFT                        3  /* DAC_MUTE */
+#define WM9081_DAC_MUTE_WIDTH                        1  /* DAC_MUTE */
+#define WM9081_DEEMPH_MASK                      0x0006  /* DEEMPH - [2:1] */
+#define WM9081_DEEMPH_SHIFT                          1  /* DEEMPH - [2:1] */
+#define WM9081_DEEMPH_WIDTH                          2  /* DEEMPH - [2:1] */
+
+/*
+ * R32 (0x20) - DRC 1
+ */
+#define WM9081_DRC_ENA                          0x8000  /* DRC_ENA */
+#define WM9081_DRC_ENA_MASK                     0x8000  /* DRC_ENA */
+#define WM9081_DRC_ENA_SHIFT                        15  /* DRC_ENA */
+#define WM9081_DRC_ENA_WIDTH                         1  /* DRC_ENA */
+#define WM9081_DRC_STARTUP_GAIN_MASK            0x07C0  /* DRC_STARTUP_GAIN - [10:6] */
+#define WM9081_DRC_STARTUP_GAIN_SHIFT                6  /* DRC_STARTUP_GAIN - [10:6] */
+#define WM9081_DRC_STARTUP_GAIN_WIDTH                5  /* DRC_STARTUP_GAIN - [10:6] */
+#define WM9081_DRC_FF_DLY                       0x0020  /* DRC_FF_DLY */
+#define WM9081_DRC_FF_DLY_MASK                  0x0020  /* DRC_FF_DLY */
+#define WM9081_DRC_FF_DLY_SHIFT                      5  /* DRC_FF_DLY */
+#define WM9081_DRC_FF_DLY_WIDTH                      1  /* DRC_FF_DLY */
+#define WM9081_DRC_QR                           0x0004  /* DRC_QR */
+#define WM9081_DRC_QR_MASK                      0x0004  /* DRC_QR */
+#define WM9081_DRC_QR_SHIFT                          2  /* DRC_QR */
+#define WM9081_DRC_QR_WIDTH                          1  /* DRC_QR */
+#define WM9081_DRC_ANTICLIP                     0x0002  /* DRC_ANTICLIP */
+#define WM9081_DRC_ANTICLIP_MASK                0x0002  /* DRC_ANTICLIP */
+#define WM9081_DRC_ANTICLIP_SHIFT                    1  /* DRC_ANTICLIP */
+#define WM9081_DRC_ANTICLIP_WIDTH                    1  /* DRC_ANTICLIP */
+
+/*
+ * R33 (0x21) - DRC 2
+ */
+#define WM9081_DRC_ATK_MASK                     0xF000  /* DRC_ATK - [15:12] */
+#define WM9081_DRC_ATK_SHIFT                        12  /* DRC_ATK - [15:12] */
+#define WM9081_DRC_ATK_WIDTH                         4  /* DRC_ATK - [15:12] */
+#define WM9081_DRC_DCY_MASK                     0x0F00  /* DRC_DCY - [11:8] */
+#define WM9081_DRC_DCY_SHIFT                         8  /* DRC_DCY - [11:8] */
+#define WM9081_DRC_DCY_WIDTH                         4  /* DRC_DCY - [11:8] */
+#define WM9081_DRC_QR_THR_MASK                  0x00C0  /* DRC_QR_THR - [7:6] */
+#define WM9081_DRC_QR_THR_SHIFT                      6  /* DRC_QR_THR - [7:6] */
+#define WM9081_DRC_QR_THR_WIDTH                      2  /* DRC_QR_THR - [7:6] */
+#define WM9081_DRC_QR_DCY_MASK                  0x0030  /* DRC_QR_DCY - [5:4] */
+#define WM9081_DRC_QR_DCY_SHIFT                      4  /* DRC_QR_DCY - [5:4] */
+#define WM9081_DRC_QR_DCY_WIDTH                      2  /* DRC_QR_DCY - [5:4] */
+#define WM9081_DRC_MINGAIN_MASK                 0x000C  /* DRC_MINGAIN - [3:2] */
+#define WM9081_DRC_MINGAIN_SHIFT                     2  /* DRC_MINGAIN - [3:2] */
+#define WM9081_DRC_MINGAIN_WIDTH                     2  /* DRC_MINGAIN - [3:2] */
+#define WM9081_DRC_MAXGAIN_MASK                 0x0003  /* DRC_MAXGAIN - [1:0] */
+#define WM9081_DRC_MAXGAIN_SHIFT                     0  /* DRC_MAXGAIN - [1:0] */
+#define WM9081_DRC_MAXGAIN_WIDTH                     2  /* DRC_MAXGAIN - [1:0] */
+
+/*
+ * R34 (0x22) - DRC 3
+ */
+#define WM9081_DRC_HI_COMP_MASK                 0x0038  /* DRC_HI_COMP - [5:3] */
+#define WM9081_DRC_HI_COMP_SHIFT                     3  /* DRC_HI_COMP - [5:3] */
+#define WM9081_DRC_HI_COMP_WIDTH                     3  /* DRC_HI_COMP - [5:3] */
+#define WM9081_DRC_LO_COMP_MASK                 0x0007  /* DRC_LO_COMP - [2:0] */
+#define WM9081_DRC_LO_COMP_SHIFT                     0  /* DRC_LO_COMP - [2:0] */
+#define WM9081_DRC_LO_COMP_WIDTH                     3  /* DRC_LO_COMP - [2:0] */
+
+/*
+ * R35 (0x23) - DRC 4
+ */
+#define WM9081_DRC_KNEE_IP_MASK                 0x07E0  /* DRC_KNEE_IP - [10:5] */
+#define WM9081_DRC_KNEE_IP_SHIFT                     5  /* DRC_KNEE_IP - [10:5] */
+#define WM9081_DRC_KNEE_IP_WIDTH                     6  /* DRC_KNEE_IP - [10:5] */
+#define WM9081_DRC_KNEE_OP_MASK                 0x001F  /* DRC_KNEE_OP - [4:0] */
+#define WM9081_DRC_KNEE_OP_SHIFT                     0  /* DRC_KNEE_OP - [4:0] */
+#define WM9081_DRC_KNEE_OP_WIDTH                     5  /* DRC_KNEE_OP - [4:0] */
+
+/*
+ * R38 (0x26) - Write Sequencer 1
+ */
+#define WM9081_WSEQ_ENA                         0x8000  /* WSEQ_ENA */
+#define WM9081_WSEQ_ENA_MASK                    0x8000  /* WSEQ_ENA */
+#define WM9081_WSEQ_ENA_SHIFT                       15  /* WSEQ_ENA */
+#define WM9081_WSEQ_ENA_WIDTH                        1  /* WSEQ_ENA */
+#define WM9081_WSEQ_ABORT                       0x0200  /* WSEQ_ABORT */
+#define WM9081_WSEQ_ABORT_MASK                  0x0200  /* WSEQ_ABORT */
+#define WM9081_WSEQ_ABORT_SHIFT                      9  /* WSEQ_ABORT */
+#define WM9081_WSEQ_ABORT_WIDTH                      1  /* WSEQ_ABORT */
+#define WM9081_WSEQ_START                       0x0100  /* WSEQ_START */
+#define WM9081_WSEQ_START_MASK                  0x0100  /* WSEQ_START */
+#define WM9081_WSEQ_START_SHIFT                      8  /* WSEQ_START */
+#define WM9081_WSEQ_START_WIDTH                      1  /* WSEQ_START */
+#define WM9081_WSEQ_START_INDEX_MASK            0x007F  /* WSEQ_START_INDEX - [6:0] */
+#define WM9081_WSEQ_START_INDEX_SHIFT                0  /* WSEQ_START_INDEX - [6:0] */
+#define WM9081_WSEQ_START_INDEX_WIDTH                7  /* WSEQ_START_INDEX - [6:0] */
+
+/*
+ * R39 (0x27) - Write Sequencer 2
+ */
+#define WM9081_WSEQ_CURRENT_INDEX_MASK          0x07F0  /* WSEQ_CURRENT_INDEX - [10:4] */
+#define WM9081_WSEQ_CURRENT_INDEX_SHIFT              4  /* WSEQ_CURRENT_INDEX - [10:4] */
+#define WM9081_WSEQ_CURRENT_INDEX_WIDTH              7  /* WSEQ_CURRENT_INDEX - [10:4] */
+#define WM9081_WSEQ_BUSY                        0x0001  /* WSEQ_BUSY */
+#define WM9081_WSEQ_BUSY_MASK                   0x0001  /* WSEQ_BUSY */
+#define WM9081_WSEQ_BUSY_SHIFT                       0  /* WSEQ_BUSY */
+#define WM9081_WSEQ_BUSY_WIDTH                       1  /* WSEQ_BUSY */
+
+/*
+ * R40 (0x28) - MW Slave 1
+ */
+#define WM9081_SPI_CFG                          0x0020  /* SPI_CFG */
+#define WM9081_SPI_CFG_MASK                     0x0020  /* SPI_CFG */
+#define WM9081_SPI_CFG_SHIFT                         5  /* SPI_CFG */
+#define WM9081_SPI_CFG_WIDTH                         1  /* SPI_CFG */
+#define WM9081_SPI_4WIRE                        0x0010  /* SPI_4WIRE */
+#define WM9081_SPI_4WIRE_MASK                   0x0010  /* SPI_4WIRE */
+#define WM9081_SPI_4WIRE_SHIFT                       4  /* SPI_4WIRE */
+#define WM9081_SPI_4WIRE_WIDTH                       1  /* SPI_4WIRE */
+#define WM9081_ARA_ENA                          0x0008  /* ARA_ENA */
+#define WM9081_ARA_ENA_MASK                     0x0008  /* ARA_ENA */
+#define WM9081_ARA_ENA_SHIFT                         3  /* ARA_ENA */
+#define WM9081_ARA_ENA_WIDTH                         1  /* ARA_ENA */
+#define WM9081_AUTO_INC                         0x0002  /* AUTO_INC */
+#define WM9081_AUTO_INC_MASK                    0x0002  /* AUTO_INC */
+#define WM9081_AUTO_INC_SHIFT                        1  /* AUTO_INC */
+#define WM9081_AUTO_INC_WIDTH                        1  /* AUTO_INC */
+
+/*
+ * R42 (0x2A) - EQ 1
+ */
+#define WM9081_EQ_B1_GAIN_MASK                  0xF800  /* EQ_B1_GAIN - [15:11] */
+#define WM9081_EQ_B1_GAIN_SHIFT                     11  /* EQ_B1_GAIN - [15:11] */
+#define WM9081_EQ_B1_GAIN_WIDTH                      5  /* EQ_B1_GAIN - [15:11] */
+#define WM9081_EQ_B2_GAIN_MASK                  0x07C0  /* EQ_B2_GAIN - [10:6] */
+#define WM9081_EQ_B2_GAIN_SHIFT                      6  /* EQ_B2_GAIN - [10:6] */
+#define WM9081_EQ_B2_GAIN_WIDTH                      5  /* EQ_B2_GAIN - [10:6] */
+#define WM9081_EQ_B4_GAIN_MASK                  0x003E  /* EQ_B4_GAIN - [5:1] */
+#define WM9081_EQ_B4_GAIN_SHIFT                      1  /* EQ_B4_GAIN - [5:1] */
+#define WM9081_EQ_B4_GAIN_WIDTH                      5  /* EQ_B4_GAIN - [5:1] */
+#define WM9081_EQ_ENA                           0x0001  /* EQ_ENA */
+#define WM9081_EQ_ENA_MASK                      0x0001  /* EQ_ENA */
+#define WM9081_EQ_ENA_SHIFT                          0  /* EQ_ENA */
+#define WM9081_EQ_ENA_WIDTH                          1  /* EQ_ENA */
+
+/*
+ * R43 (0x2B) - EQ 2
+ */
+#define WM9081_EQ_B3_GAIN_MASK                  0xF800  /* EQ_B3_GAIN - [15:11] */
+#define WM9081_EQ_B3_GAIN_SHIFT                     11  /* EQ_B3_GAIN - [15:11] */
+#define WM9081_EQ_B3_GAIN_WIDTH                      5  /* EQ_B3_GAIN - [15:11] */
+#define WM9081_EQ_B5_GAIN_MASK                  0x07C0  /* EQ_B5_GAIN - [10:6] */
+#define WM9081_EQ_B5_GAIN_SHIFT                      6  /* EQ_B5_GAIN - [10:6] */
+#define WM9081_EQ_B5_GAIN_WIDTH                      5  /* EQ_B5_GAIN - [10:6] */
+
+/*
+ * R44 (0x2C) - EQ 3
+ */
+#define WM9081_EQ_B1_A_MASK                     0xFFFF  /* EQ_B1_A - [15:0] */
+#define WM9081_EQ_B1_A_SHIFT                         0  /* EQ_B1_A - [15:0] */
+#define WM9081_EQ_B1_A_WIDTH                        16  /* EQ_B1_A - [15:0] */
+
+/*
+ * R45 (0x2D) - EQ 4
+ */
+#define WM9081_EQ_B1_B_MASK                     0xFFFF  /* EQ_B1_B - [15:0] */
+#define WM9081_EQ_B1_B_SHIFT                         0  /* EQ_B1_B - [15:0] */
+#define WM9081_EQ_B1_B_WIDTH                        16  /* EQ_B1_B - [15:0] */
+
+/*
+ * R46 (0x2E) - EQ 5
+ */
+#define WM9081_EQ_B1_PG_MASK                    0xFFFF  /* EQ_B1_PG - [15:0] */
+#define WM9081_EQ_B1_PG_SHIFT                        0  /* EQ_B1_PG - [15:0] */
+#define WM9081_EQ_B1_PG_WIDTH                       16  /* EQ_B1_PG - [15:0] */
+
+/*
+ * R47 (0x2F) - EQ 6
+ */
+#define WM9081_EQ_B2_A_MASK                     0xFFFF  /* EQ_B2_A - [15:0] */
+#define WM9081_EQ_B2_A_SHIFT                         0  /* EQ_B2_A - [15:0] */
+#define WM9081_EQ_B2_A_WIDTH                        16  /* EQ_B2_A - [15:0] */
+
+/*
+ * R48 (0x30) - EQ 7
+ */
+#define WM9081_EQ_B2_B_MASK                     0xFFFF  /* EQ_B2_B - [15:0] */
+#define WM9081_EQ_B2_B_SHIFT                         0  /* EQ_B2_B - [15:0] */
+#define WM9081_EQ_B2_B_WIDTH                        16  /* EQ_B2_B - [15:0] */
+
+/*
+ * R49 (0x31) - EQ 8
+ */
+#define WM9081_EQ_B2_C_MASK                     0xFFFF  /* EQ_B2_C - [15:0] */
+#define WM9081_EQ_B2_C_SHIFT                         0  /* EQ_B2_C - [15:0] */
+#define WM9081_EQ_B2_C_WIDTH                        16  /* EQ_B2_C - [15:0] */
+
+/*
+ * R50 (0x32) - EQ 9
+ */
+#define WM9081_EQ_B2_PG_MASK                    0xFFFF  /* EQ_B2_PG - [15:0] */
+#define WM9081_EQ_B2_PG_SHIFT                        0  /* EQ_B2_PG - [15:0] */
+#define WM9081_EQ_B2_PG_WIDTH                       16  /* EQ_B2_PG - [15:0] */
+
+/*
+ * R51 (0x33) - EQ 10
+ */
+#define WM9081_EQ_B4_A_MASK                     0xFFFF  /* EQ_B4_A - [15:0] */
+#define WM9081_EQ_B4_A_SHIFT                         0  /* EQ_B4_A - [15:0] */
+#define WM9081_EQ_B4_A_WIDTH                        16  /* EQ_B4_A - [15:0] */
+
+/*
+ * R52 (0x34) - EQ 11
+ */
+#define WM9081_EQ_B4_B_MASK                     0xFFFF  /* EQ_B4_B - [15:0] */
+#define WM9081_EQ_B4_B_SHIFT                         0  /* EQ_B4_B - [15:0] */
+#define WM9081_EQ_B4_B_WIDTH                        16  /* EQ_B4_B - [15:0] */
+
+/*
+ * R53 (0x35) - EQ 12
+ */
+#define WM9081_EQ_B4_C_MASK                     0xFFFF  /* EQ_B4_C - [15:0] */
+#define WM9081_EQ_B4_C_SHIFT                         0  /* EQ_B4_C - [15:0] */
+#define WM9081_EQ_B4_C_WIDTH                        16  /* EQ_B4_C - [15:0] */
+
+/*
+ * R54 (0x36) - EQ 13
+ */
+#define WM9081_EQ_B4_PG_MASK                    0xFFFF  /* EQ_B4_PG - [15:0] */
+#define WM9081_EQ_B4_PG_SHIFT                        0  /* EQ_B4_PG - [15:0] */
+#define WM9081_EQ_B4_PG_WIDTH                       16  /* EQ_B4_PG - [15:0] */
+
+/*
+ * R55 (0x37) - EQ 14
+ */
+#define WM9081_EQ_B3_A_MASK                     0xFFFF  /* EQ_B3_A - [15:0] */
+#define WM9081_EQ_B3_A_SHIFT                         0  /* EQ_B3_A - [15:0] */
+#define WM9081_EQ_B3_A_WIDTH                        16  /* EQ_B3_A - [15:0] */
+
+/*
+ * R56 (0x38) - EQ 15
+ */
+#define WM9081_EQ_B3_B_MASK                     0xFFFF  /* EQ_B3_B - [15:0] */
+#define WM9081_EQ_B3_B_SHIFT                         0  /* EQ_B3_B - [15:0] */
+#define WM9081_EQ_B3_B_WIDTH                        16  /* EQ_B3_B - [15:0] */
+
+/*
+ * R57 (0x39) - EQ 16
+ */
+#define WM9081_EQ_B3_C_MASK                     0xFFFF  /* EQ_B3_C - [15:0] */
+#define WM9081_EQ_B3_C_SHIFT                         0  /* EQ_B3_C - [15:0] */
+#define WM9081_EQ_B3_C_WIDTH                        16  /* EQ_B3_C - [15:0] */
+
+/*
+ * R58 (0x3A) - EQ 17
+ */
+#define WM9081_EQ_B3_PG_MASK                    0xFFFF  /* EQ_B3_PG - [15:0] */
+#define WM9081_EQ_B3_PG_SHIFT                        0  /* EQ_B3_PG - [15:0] */
+#define WM9081_EQ_B3_PG_WIDTH                       16  /* EQ_B3_PG - [15:0] */
+
+/*
+ * R59 (0x3B) - EQ 18
+ */
+#define WM9081_EQ_B5_A_MASK                     0xFFFF  /* EQ_B5_A - [15:0] */
+#define WM9081_EQ_B5_A_SHIFT                         0  /* EQ_B5_A - [15:0] */
+#define WM9081_EQ_B5_A_WIDTH                        16  /* EQ_B5_A - [15:0] */
+
+/*
+ * R60 (0x3C) - EQ 19
+ */
+#define WM9081_EQ_B5_B_MASK                     0xFFFF  /* EQ_B5_B - [15:0] */
+#define WM9081_EQ_B5_B_SHIFT                         0  /* EQ_B5_B - [15:0] */
+#define WM9081_EQ_B5_B_WIDTH                        16  /* EQ_B5_B - [15:0] */
+
+/*
+ * R61 (0x3D) - EQ 20
+ */
+#define WM9081_EQ_B5_PG_MASK                    0xFFFF  /* EQ_B5_PG - [15:0] */
+#define WM9081_EQ_B5_PG_SHIFT                        0  /* EQ_B5_PG - [15:0] */
+#define WM9081_EQ_B5_PG_WIDTH                       16  /* EQ_B5_PG - [15:0] */
+
+
+#endif
diff --git a/linux/sound/soc/codecs/wm9090.c b/linux/sound/soc/codecs/wm9090.c
new file mode 100644
index 0000000..6e5f64f
--- /dev/null
+++ b/linux/sound/soc/codecs/wm9090.c
@@ -0,0 +1,708 @@
+/*
+ * ALSA SoC WM9090 driver
+ *
+ * Copyright 2009, 2010 Wolfson Microelectronics
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/wm9090.h>
+
+#include "wm9090.h"
+
+static const u16 wm9090_reg_defaults[] = {
+	0x9093,     /* R0   - Software Reset */
+	0x0006,     /* R1   - Power Management (1) */
+	0x6000,     /* R2   - Power Management (2) */
+	0x0000,     /* R3   - Power Management (3) */
+	0x0000,     /* R4 */
+	0x0000,     /* R5 */
+	0x01C0,     /* R6   - Clocking 1 */
+	0x0000,     /* R7 */
+	0x0000,     /* R8 */
+	0x0000,     /* R9 */
+	0x0000,     /* R10 */
+	0x0000,     /* R11 */
+	0x0000,     /* R12 */
+	0x0000,     /* R13 */
+	0x0000,     /* R14 */
+	0x0000,     /* R15 */
+	0x0000,     /* R16 */
+	0x0000,     /* R17 */
+	0x0000,     /* R18 */
+	0x0000,     /* R19 */
+	0x0000,     /* R20 */
+	0x0000,     /* R21 */
+	0x0003,     /* R22  - IN1 Line Control */
+	0x0003,     /* R23  - IN2 Line Control */
+	0x0083,     /* R24  - IN1 Line Input A Volume */
+	0x0083,     /* R25  - IN1  Line Input B Volume */
+	0x0083,     /* R26  - IN2 Line Input A Volume */
+	0x0083,     /* R27  - IN2 Line Input B Volume */
+	0x002D,     /* R28  - Left Output Volume */
+	0x002D,     /* R29  - Right Output Volume */
+	0x0000,     /* R30 */
+	0x0000,     /* R31 */
+	0x0000,     /* R32 */
+	0x0000,     /* R33 */
+	0x0100,     /* R34  - SPKMIXL Attenuation */
+	0x0000,     /* R35 */
+	0x0010,     /* R36  - SPKOUT Mixers */
+	0x0140,     /* R37  - ClassD3 */
+	0x0039,     /* R38  - Speaker Volume Left */
+	0x0000,     /* R39 */
+	0x0000,     /* R40 */
+	0x0000,     /* R41 */
+	0x0000,     /* R42 */
+	0x0000,     /* R43 */
+	0x0000,     /* R44 */
+	0x0000,     /* R45  - Output Mixer1 */
+	0x0000,     /* R46  - Output Mixer2 */
+	0x0100,     /* R47  - Output Mixer3 */
+	0x0100,     /* R48  - Output Mixer4 */
+	0x0000,     /* R49 */
+	0x0000,     /* R50 */
+	0x0000,     /* R51 */
+	0x0000,     /* R52 */
+	0x0000,     /* R53 */
+	0x0000,     /* R54  - Speaker Mixer */
+	0x0000,     /* R55 */
+	0x0000,     /* R56 */
+	0x000D,     /* R57  - AntiPOP2 */
+	0x0000,     /* R58 */
+	0x0000,     /* R59 */
+	0x0000,     /* R60 */
+	0x0000,     /* R61 */
+	0x0000,     /* R62 */
+	0x0000,     /* R63 */
+	0x0000,     /* R64 */
+	0x0000,     /* R65 */
+	0x0000,     /* R66 */
+	0x0000,     /* R67 */
+	0x0000,     /* R68 */
+	0x0000,     /* R69 */
+	0x0000,     /* R70  - Write Sequencer 0 */
+	0x0000,     /* R71  - Write Sequencer 1 */
+	0x0000,     /* R72  - Write Sequencer 2 */
+	0x0000,     /* R73  - Write Sequencer 3 */
+	0x0000,     /* R74  - Write Sequencer 4 */
+	0x0000,     /* R75  - Write Sequencer 5 */
+	0x1F25,     /* R76  - Charge Pump 1 */
+	0x0000,     /* R77 */
+	0x0000,     /* R78 */
+	0x0000,     /* R79 */
+	0x0000,     /* R80 */
+	0x0000,     /* R81 */
+	0x0000,     /* R82 */
+	0x0000,     /* R83 */
+	0x0000,     /* R84  - DC Servo 0 */
+	0x054A,     /* R85  - DC Servo 1 */
+	0x0000,     /* R86 */
+	0x0000,     /* R87  - DC Servo 3 */
+	0x0000,     /* R88  - DC Servo Readback 0 */
+	0x0000,     /* R89  - DC Servo Readback 1 */
+	0x0000,     /* R90  - DC Servo Readback 2 */
+	0x0000,     /* R91 */
+	0x0000,     /* R92 */
+	0x0000,     /* R93 */
+	0x0000,     /* R94 */
+	0x0000,     /* R95 */
+	0x0100,     /* R96  - Analogue HP 0 */
+	0x0000,     /* R97 */
+	0x8640,     /* R98  - AGC Control 0 */
+	0xC000,     /* R99  - AGC Control 1 */
+	0x0200,     /* R100 - AGC Control 2 */
+};
+
+/* This struct is used to save the context */
+struct wm9090_priv {
+	struct mutex mutex;
+	struct wm9090_platform_data pdata;
+	void *control_data;
+};
+
+static int wm9090_volatile(unsigned int reg)
+{
+	switch (reg) {
+	case WM9090_SOFTWARE_RESET:
+	case WM9090_DC_SERVO_0:
+	case WM9090_DC_SERVO_READBACK_0:
+	case WM9090_DC_SERVO_READBACK_1:
+	case WM9090_DC_SERVO_READBACK_2:
+		return 1;
+
+	default:
+		return 0;
+	}
+}
+
+static void wait_for_dc_servo(struct snd_soc_codec *codec)
+{
+	unsigned int reg;
+	int count = 0;
+
+	dev_dbg(codec->dev, "Waiting for DC servo...\n");
+	do {
+		count++;
+		msleep(1);
+		reg = snd_soc_read(codec, WM9090_DC_SERVO_READBACK_0);
+		dev_dbg(codec->dev, "DC servo status: %x\n", reg);
+	} while ((reg & WM9090_DCS_CAL_COMPLETE_MASK)
+		 != WM9090_DCS_CAL_COMPLETE_MASK && count < 1000);
+
+	if ((reg & WM9090_DCS_CAL_COMPLETE_MASK)
+	    != WM9090_DCS_CAL_COMPLETE_MASK)
+		dev_err(codec->dev, "Timed out waiting for DC Servo\n");
+}
+
+static const unsigned int in_tlv[] = {
+	TLV_DB_RANGE_HEAD(6),
+	0, 0, TLV_DB_SCALE_ITEM(-600, 0, 0),
+	1, 3, TLV_DB_SCALE_ITEM(-350, 350, 0),
+	4, 6, TLV_DB_SCALE_ITEM(600, 600, 0),
+};
+static const unsigned int mix_tlv[] = {
+	TLV_DB_RANGE_HEAD(4),
+	0, 2, TLV_DB_SCALE_ITEM(-1200, 300, 0),
+	3, 3, TLV_DB_SCALE_ITEM(0, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0);
+static const unsigned int spkboost_tlv[] = {
+	TLV_DB_RANGE_HEAD(7),
+	0, 6, TLV_DB_SCALE_ITEM(0, 150, 0),
+	7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0),
+};
+
+static const struct snd_kcontrol_new wm9090_controls[] = {
+SOC_SINGLE_TLV("IN1A Volume", WM9090_IN1_LINE_INPUT_A_VOLUME, 0, 6, 0,
+	       in_tlv),
+SOC_SINGLE("IN1A Switch", WM9090_IN1_LINE_INPUT_A_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN1A ZC Switch", WM9090_IN1_LINE_INPUT_A_VOLUME, 6, 1, 0),
+
+SOC_SINGLE_TLV("IN2A Volume", WM9090_IN2_LINE_INPUT_A_VOLUME, 0, 6, 0,
+	       in_tlv),
+SOC_SINGLE("IN2A Switch", WM9090_IN2_LINE_INPUT_A_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN2A ZC Switch", WM9090_IN2_LINE_INPUT_A_VOLUME, 6, 1, 0),
+
+SOC_SINGLE("MIXOUTL Switch", WM9090_OUTPUT_MIXER3, 8, 1, 1),
+SOC_SINGLE_TLV("MIXOUTL IN1A Volume", WM9090_OUTPUT_MIXER3, 6, 3, 1,
+	       mix_tlv),
+SOC_SINGLE_TLV("MIXOUTL IN2A Volume", WM9090_OUTPUT_MIXER3, 2, 3, 1,
+	       mix_tlv),
+
+SOC_SINGLE("MIXOUTR Switch", WM9090_OUTPUT_MIXER4, 8, 1, 1),
+SOC_SINGLE_TLV("MIXOUTR IN1A Volume", WM9090_OUTPUT_MIXER4, 6, 3, 1,
+	       mix_tlv),
+SOC_SINGLE_TLV("MIXOUTR IN2A Volume", WM9090_OUTPUT_MIXER4, 2, 3, 1,
+	       mix_tlv),
+
+SOC_SINGLE("SPKMIX Switch", WM9090_SPKMIXL_ATTENUATION, 8, 1, 1),
+SOC_SINGLE_TLV("SPKMIX IN1A Volume", WM9090_SPKMIXL_ATTENUATION, 6, 3, 1,
+	       mix_tlv),
+SOC_SINGLE_TLV("SPKMIX IN2A Volume", WM9090_SPKMIXL_ATTENUATION, 2, 3, 1,
+	       mix_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Volume", WM9090_LEFT_OUTPUT_VOLUME,
+		 WM9090_RIGHT_OUTPUT_VOLUME, 0, 63, 0, out_tlv),
+SOC_DOUBLE_R("Headphone Switch", WM9090_LEFT_OUTPUT_VOLUME,
+	     WM9090_RIGHT_OUTPUT_VOLUME, 6, 1, 1),
+SOC_DOUBLE_R("Headphone ZC Switch", WM9090_LEFT_OUTPUT_VOLUME,
+	     WM9090_RIGHT_OUTPUT_VOLUME, 7, 1, 0),
+
+SOC_SINGLE_TLV("Speaker Volume", WM9090_SPEAKER_VOLUME_LEFT, 0, 63, 0,
+	       out_tlv),
+SOC_SINGLE("Speaker Switch", WM9090_SPEAKER_VOLUME_LEFT, 6, 1, 1),
+SOC_SINGLE("Speaker ZC Switch", WM9090_SPEAKER_VOLUME_LEFT, 7, 1, 0),
+SOC_SINGLE_TLV("Speaker Boost Volume", WM9090_CLASSD3, 3, 7, 0, spkboost_tlv),
+};
+
+static const struct snd_kcontrol_new wm9090_in1_se_controls[] = {
+SOC_SINGLE_TLV("IN1B Volume", WM9090_IN1_LINE_INPUT_B_VOLUME, 0, 6, 0,
+	       in_tlv),
+SOC_SINGLE("IN1B Switch", WM9090_IN1_LINE_INPUT_B_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN1B ZC Switch", WM9090_IN1_LINE_INPUT_B_VOLUME, 6, 1, 0),
+
+SOC_SINGLE_TLV("SPKMIX IN1B Volume", WM9090_SPKMIXL_ATTENUATION, 4, 3, 1,
+	       mix_tlv),
+SOC_SINGLE_TLV("MIXOUTL IN1B Volume", WM9090_OUTPUT_MIXER3, 4, 3, 1,
+	       mix_tlv),
+SOC_SINGLE_TLV("MIXOUTR IN1B Volume", WM9090_OUTPUT_MIXER4, 4, 3, 1,
+	       mix_tlv),
+};
+
+static const struct snd_kcontrol_new wm9090_in2_se_controls[] = {
+SOC_SINGLE_TLV("IN2B Volume", WM9090_IN2_LINE_INPUT_B_VOLUME, 0, 6, 0,
+	       in_tlv),
+SOC_SINGLE("IN2B Switch", WM9090_IN2_LINE_INPUT_B_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN2B ZC Switch", WM9090_IN2_LINE_INPUT_B_VOLUME, 6, 1, 0),
+
+SOC_SINGLE_TLV("SPKMIX IN2B Volume", WM9090_SPKMIXL_ATTENUATION, 0, 3, 1,
+	       mix_tlv),
+SOC_SINGLE_TLV("MIXOUTL IN2B Volume", WM9090_OUTPUT_MIXER3, 0, 3, 1,
+	       mix_tlv),
+SOC_SINGLE_TLV("MIXOUTR IN2B Volume", WM9090_OUTPUT_MIXER4, 0, 3, 1,
+	       mix_tlv),
+};
+
+static int hp_ev(struct snd_soc_dapm_widget *w,
+		 struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	unsigned int reg = snd_soc_read(codec, WM9090_ANALOGUE_HP_0);
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		snd_soc_update_bits(codec, WM9090_CHARGE_PUMP_1,
+				    WM9090_CP_ENA, WM9090_CP_ENA);
+
+		msleep(5);
+
+		snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1,
+				    WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA,
+				    WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA);
+
+		reg |= WM9090_HPOUT1L_DLY | WM9090_HPOUT1R_DLY;
+		snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg);
+
+		/* Start the DC servo.  We don't currently use the
+		 * ability to save the state since we don't have full
+		 * control of the analogue paths and they can change
+		 * DC offsets; see the WM8904 driver for an example of
+		 * doing so.
+		 */
+		snd_soc_write(codec, WM9090_DC_SERVO_0,
+			      WM9090_DCS_ENA_CHAN_0 |
+			      WM9090_DCS_ENA_CHAN_1 |
+			      WM9090_DCS_TRIG_STARTUP_1 |
+			      WM9090_DCS_TRIG_STARTUP_0);
+		wait_for_dc_servo(codec);
+
+		reg |= WM9090_HPOUT1R_OUTP | WM9090_HPOUT1R_RMV_SHORT |
+			WM9090_HPOUT1L_OUTP | WM9090_HPOUT1L_RMV_SHORT;
+		snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg);
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		reg &= ~(WM9090_HPOUT1L_RMV_SHORT |
+			 WM9090_HPOUT1L_DLY |
+			 WM9090_HPOUT1L_OUTP |
+			 WM9090_HPOUT1R_RMV_SHORT |
+			 WM9090_HPOUT1R_DLY |
+			 WM9090_HPOUT1R_OUTP);
+
+		snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg);
+
+		snd_soc_write(codec, WM9090_DC_SERVO_0, 0);
+
+		snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1,
+				    WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA,
+				    0);
+
+		snd_soc_update_bits(codec, WM9090_CHARGE_PUMP_1,
+				    WM9090_CP_ENA, 0);
+		break;
+	}
+
+	return 0;
+}
+
+static const struct snd_kcontrol_new spkmix[] = {
+SOC_DAPM_SINGLE("IN1A Switch", WM9090_SPEAKER_MIXER, 6, 1, 0),
+SOC_DAPM_SINGLE("IN1B Switch", WM9090_SPEAKER_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("IN2A Switch", WM9090_SPEAKER_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("IN2B Switch", WM9090_SPEAKER_MIXER, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new spkout[] = {
+SOC_DAPM_SINGLE("Mixer Switch", WM9090_SPKOUT_MIXERS, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixoutl[] = {
+SOC_DAPM_SINGLE("IN1A Switch", WM9090_OUTPUT_MIXER1, 6, 1, 0),
+SOC_DAPM_SINGLE("IN1B Switch", WM9090_OUTPUT_MIXER1, 4, 1, 0),
+SOC_DAPM_SINGLE("IN2A Switch", WM9090_OUTPUT_MIXER1, 2, 1, 0),
+SOC_DAPM_SINGLE("IN2B Switch", WM9090_OUTPUT_MIXER1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixoutr[] = {
+SOC_DAPM_SINGLE("IN1A Switch", WM9090_OUTPUT_MIXER2, 6, 1, 0),
+SOC_DAPM_SINGLE("IN1B Switch", WM9090_OUTPUT_MIXER2, 4, 1, 0),
+SOC_DAPM_SINGLE("IN2A Switch", WM9090_OUTPUT_MIXER2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN2B Switch", WM9090_OUTPUT_MIXER2, 0, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm9090_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1+"),
+SND_SOC_DAPM_INPUT("IN1-"),
+SND_SOC_DAPM_INPUT("IN2+"),
+SND_SOC_DAPM_INPUT("IN2-"),
+
+SND_SOC_DAPM_SUPPLY("OSC", WM9090_POWER_MANAGEMENT_1, 3, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("IN1A PGA", WM9090_POWER_MANAGEMENT_2, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("IN1B PGA", WM9090_POWER_MANAGEMENT_2, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("IN2A PGA", WM9090_POWER_MANAGEMENT_2, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("IN2B PGA", WM9090_POWER_MANAGEMENT_2, 4, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("SPKMIX", WM9090_POWER_MANAGEMENT_3, 3, 0,
+		   spkmix, ARRAY_SIZE(spkmix)),
+SND_SOC_DAPM_MIXER("MIXOUTL", WM9090_POWER_MANAGEMENT_3, 5, 0,
+		   mixoutl, ARRAY_SIZE(mixoutl)),
+SND_SOC_DAPM_MIXER("MIXOUTR", WM9090_POWER_MANAGEMENT_3, 4, 0,
+		   mixoutr, ARRAY_SIZE(mixoutr)),
+
+SND_SOC_DAPM_PGA_E("HP PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
+		   hp_ev, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_PGA("SPKPGA", WM9090_POWER_MANAGEMENT_3, 8, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("SPKOUT", WM9090_POWER_MANAGEMENT_1, 12, 0,
+		   spkout, ARRAY_SIZE(spkout)),
+
+SND_SOC_DAPM_OUTPUT("HPR"),
+SND_SOC_DAPM_OUTPUT("HPL"),
+SND_SOC_DAPM_OUTPUT("Speaker"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{ "IN1A PGA", NULL, "IN1+" },
+	{ "IN2A PGA", NULL, "IN2+" },
+
+	{ "SPKMIX", "IN1A Switch", "IN1A PGA" },
+	{ "SPKMIX", "IN2A Switch", "IN2A PGA" },
+
+	{ "MIXOUTL", "IN1A Switch", "IN1A PGA" },
+	{ "MIXOUTL", "IN2A Switch", "IN2A PGA" },
+
+	{ "MIXOUTR", "IN1A Switch", "IN1A PGA" },
+	{ "MIXOUTR", "IN2A Switch", "IN2A PGA" },
+
+	{ "HP PGA", NULL, "OSC" },
+	{ "HP PGA", NULL, "MIXOUTL" },
+	{ "HP PGA", NULL, "MIXOUTR" },
+
+	{ "HPL", NULL, "HP PGA" },
+	{ "HPR", NULL, "HP PGA" },
+
+	{ "SPKPGA", NULL, "OSC" },
+	{ "SPKPGA", NULL, "SPKMIX" },
+
+	{ "SPKOUT", "Mixer Switch", "SPKPGA" },
+
+	{ "Speaker", NULL, "SPKOUT" },
+};
+
+static const struct snd_soc_dapm_route audio_map_in1_se[] = {
+	{ "IN1B PGA", NULL, "IN1-" },	
+
+	{ "SPKMIX", "IN1B Switch", "IN1B PGA" },
+	{ "MIXOUTL", "IN1B Switch", "IN1B PGA" },
+	{ "MIXOUTR", "IN1B Switch", "IN1B PGA" },
+};
+
+static const struct snd_soc_dapm_route audio_map_in1_diff[] = {
+	{ "IN1A PGA", NULL, "IN1-" },	
+};
+
+static const struct snd_soc_dapm_route audio_map_in2_se[] = {
+	{ "IN2B PGA", NULL, "IN2-" },	
+
+	{ "SPKMIX", "IN2B Switch", "IN2B PGA" },
+	{ "MIXOUTL", "IN2B Switch", "IN2B PGA" },
+	{ "MIXOUTR", "IN2B Switch", "IN2B PGA" },
+};
+
+static const struct snd_soc_dapm_route audio_map_in2_diff[] = {
+	{ "IN2A PGA", NULL, "IN2-" },	
+};
+
+static int wm9090_add_controls(struct snd_soc_codec *codec)
+{
+	struct wm9090_priv *wm9090 = snd_soc_codec_get_drvdata(codec);
+	int i;
+
+	snd_soc_dapm_new_controls(codec, wm9090_dapm_widgets,
+				  ARRAY_SIZE(wm9090_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_add_controls(codec, wm9090_controls,
+			     ARRAY_SIZE(wm9090_controls));
+
+	if (wm9090->pdata.lin1_diff) {
+		snd_soc_dapm_add_routes(codec, audio_map_in1_diff,
+					ARRAY_SIZE(audio_map_in1_diff));
+	} else {
+		snd_soc_dapm_add_routes(codec, audio_map_in1_se,
+					ARRAY_SIZE(audio_map_in1_se));
+		snd_soc_add_controls(codec, wm9090_in1_se_controls,
+				     ARRAY_SIZE(wm9090_in1_se_controls));
+	}
+
+	if (wm9090->pdata.lin2_diff) {
+		snd_soc_dapm_add_routes(codec, audio_map_in2_diff,
+					ARRAY_SIZE(audio_map_in2_diff));
+	} else {
+		snd_soc_dapm_add_routes(codec, audio_map_in2_se,
+					ARRAY_SIZE(audio_map_in2_se));
+		snd_soc_add_controls(codec, wm9090_in2_se_controls,
+				     ARRAY_SIZE(wm9090_in2_se_controls));
+	}
+
+	if (wm9090->pdata.agc_ena) {
+		for (i = 0; i < ARRAY_SIZE(wm9090->pdata.agc); i++)
+			snd_soc_write(codec, WM9090_AGC_CONTROL_0 + i,
+				      wm9090->pdata.agc[i]);
+		snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_3,
+				    WM9090_AGC_ENA, WM9090_AGC_ENA);
+	} else {
+		snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_3,
+				    WM9090_AGC_ENA, 0);
+	}
+
+	return 0;
+
+}
+
+/*
+ * The machine driver should call this from their set_bias_level; if there
+ * isn't one then this can just be set as the set_bias_level function.
+ */
+static int wm9090_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 *reg_cache = codec->reg_cache;
+	int i, ret;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		snd_soc_update_bits(codec, WM9090_ANTIPOP2, WM9090_VMID_ENA,
+				    WM9090_VMID_ENA);
+		snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1,
+				    WM9090_BIAS_ENA |
+				    WM9090_VMID_RES_MASK,
+				    WM9090_BIAS_ENA |
+				    1 << WM9090_VMID_RES_SHIFT);
+		msleep(1);  /* Probably an overestimate */
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF) {
+			/* Restore the register cache */
+			for (i = 1; i < codec->driver->reg_cache_size; i++) {
+				if (reg_cache[i] == wm9090_reg_defaults[i])
+					continue;
+				if (wm9090_volatile(i))
+					continue;
+
+				ret = snd_soc_write(codec, i, reg_cache[i]);
+				if (ret != 0)
+					dev_warn(codec->dev,
+						 "Failed to restore register %d: %d\n",
+						 i, ret);
+			}
+		}
+
+		/* We keep VMID off during standby since the combination of
+		 * ground referenced outputs and class D speaker mean that
+		 * latency is not an issue.
+		 */
+		snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1,
+				    WM9090_BIAS_ENA | WM9090_VMID_RES_MASK, 0);
+		snd_soc_update_bits(codec, WM9090_ANTIPOP2,
+				    WM9090_VMID_ENA, 0);
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		break;
+	}
+
+	codec->bias_level = level;
+
+	return 0;
+}
+
+static int wm9090_probe(struct snd_soc_codec *codec)
+{
+	struct wm9090_priv *wm9090 = snd_soc_codec_get_drvdata(codec);
+	u16 *reg_cache = codec->reg_cache;
+	int ret;
+
+	codec->control_data = wm9090->control_data;
+	ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	ret = snd_soc_read(codec, WM9090_SOFTWARE_RESET);
+	if (ret < 0)
+		return ret;
+	if (ret != wm9090_reg_defaults[WM9090_SOFTWARE_RESET]) {
+		dev_err(codec->dev, "Device is not a WM9090, ID=%x\n", ret);
+		return -EINVAL;
+	}
+
+	ret = snd_soc_write(codec, WM9090_SOFTWARE_RESET, 0);
+	if (ret < 0)
+		return ret;
+
+	/* Configure some defaults; they will be written out when we
+	 * bring the bias up.
+	 */
+	reg_cache[WM9090_IN1_LINE_INPUT_A_VOLUME] |= WM9090_IN1_VU
+		| WM9090_IN1A_ZC;
+	reg_cache[WM9090_IN1_LINE_INPUT_B_VOLUME] |= WM9090_IN1_VU
+		| WM9090_IN1B_ZC;
+	reg_cache[WM9090_IN2_LINE_INPUT_A_VOLUME] |= WM9090_IN2_VU
+		| WM9090_IN2A_ZC;
+	reg_cache[WM9090_IN2_LINE_INPUT_B_VOLUME] |= WM9090_IN2_VU
+		| WM9090_IN2B_ZC;
+	reg_cache[WM9090_SPEAKER_VOLUME_LEFT] |=
+		WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC;
+	reg_cache[WM9090_LEFT_OUTPUT_VOLUME] |=
+		WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC;
+	reg_cache[WM9090_RIGHT_OUTPUT_VOLUME] |=
+		WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC;
+
+	reg_cache[WM9090_CLOCKING_1] |= WM9090_TOCLK_ENA;
+
+	wm9090_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	wm9090_add_controls(codec);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm9090_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	wm9090_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static int wm9090_resume(struct snd_soc_codec *codec)
+{
+	wm9090_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	return 0;
+}
+#else
+#define wm9090_suspend NULL
+#define wm9090_resume NULL
+#endif
+
+static int wm9090_remove(struct snd_soc_codec *codec)
+{
+	wm9090_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm9090 = {
+	.probe = 	wm9090_probe,
+	.remove = 	wm9090_remove,
+	.suspend = 	wm9090_suspend,
+	.resume =	wm9090_resume,
+	.set_bias_level = wm9090_set_bias_level,
+	.reg_cache_size = (WM9090_MAX_REGISTER + 1),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_default = wm9090_reg_defaults,
+	.volatile_register = wm9090_volatile,
+};
+
+static int wm9090_i2c_probe(struct i2c_client *i2c,
+			    const struct i2c_device_id *id)
+{
+	struct wm9090_priv *wm9090;
+	int ret;
+
+	wm9090 = kzalloc(sizeof(*wm9090), GFP_KERNEL);
+	if (wm9090 == NULL) {
+		dev_err(&i2c->dev, "Can not allocate memory\n");
+		return -ENOMEM;
+	}
+
+	if (i2c->dev.platform_data)
+		memcpy(&wm9090->pdata, i2c->dev.platform_data,
+		       sizeof(wm9090->pdata));
+
+	i2c_set_clientdata(i2c, wm9090);
+	wm9090->control_data = i2c;
+	mutex_init(&wm9090->mutex);
+
+	ret =  snd_soc_register_codec(&i2c->dev,
+			&soc_codec_dev_wm9090,  NULL, 0);
+	if (ret < 0)
+		kfree(wm9090);
+	return ret;
+}
+
+static int __devexit wm9090_i2c_remove(struct i2c_client *i2c)
+{
+	struct wm9090_priv *wm9090 = i2c_get_clientdata(i2c);
+
+	snd_soc_unregister_codec(&i2c->dev);
+	kfree(wm9090);
+
+	return 0;
+}
+
+static const struct i2c_device_id wm9090_id[] = {
+	{ "wm9090", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wm9090_id);
+
+static struct i2c_driver wm9090_i2c_driver = {
+	.driver = {
+		.name = "wm9090-codec",
+		.owner = THIS_MODULE,
+	},
+	.probe = wm9090_i2c_probe,
+	.remove = __devexit_p(wm9090_i2c_remove),
+	.id_table = wm9090_id,
+};
+
+static int __init wm9090_init(void)
+{
+	return i2c_add_driver(&wm9090_i2c_driver);
+}
+module_init(wm9090_init);
+
+static void __exit wm9090_exit(void)
+{
+	i2c_del_driver(&wm9090_i2c_driver);
+}
+module_exit(wm9090_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM9090 ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm9090.h b/linux/sound/soc/codecs/wm9090.h
new file mode 100644
index 0000000..29b9d9f
--- /dev/null
+++ b/linux/sound/soc/codecs/wm9090.h
@@ -0,0 +1,713 @@
+/*
+ * ALSA SoC WM9090 driver
+ *
+ * Copyright 2009 Wolfson Microelectronics
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef __WM9090_H
+#define __WM9090_H
+
+/*
+ * Register values.
+ */
+#define WM9090_SOFTWARE_RESET                   0x00
+#define WM9090_POWER_MANAGEMENT_1               0x01
+#define WM9090_POWER_MANAGEMENT_2               0x02
+#define WM9090_POWER_MANAGEMENT_3               0x03
+#define WM9090_CLOCKING_1                       0x06
+#define WM9090_IN1_LINE_CONTROL                 0x16
+#define WM9090_IN2_LINE_CONTROL                 0x17
+#define WM9090_IN1_LINE_INPUT_A_VOLUME          0x18
+#define WM9090_IN1_LINE_INPUT_B_VOLUME          0x19
+#define WM9090_IN2_LINE_INPUT_A_VOLUME          0x1A
+#define WM9090_IN2_LINE_INPUT_B_VOLUME          0x1B
+#define WM9090_LEFT_OUTPUT_VOLUME               0x1C
+#define WM9090_RIGHT_OUTPUT_VOLUME              0x1D
+#define WM9090_SPKMIXL_ATTENUATION              0x22
+#define WM9090_SPKOUT_MIXERS                    0x24
+#define WM9090_CLASSD3                          0x25
+#define WM9090_SPEAKER_VOLUME_LEFT              0x26
+#define WM9090_OUTPUT_MIXER1                    0x2D
+#define WM9090_OUTPUT_MIXER2                    0x2E
+#define WM9090_OUTPUT_MIXER3                    0x2F
+#define WM9090_OUTPUT_MIXER4                    0x30
+#define WM9090_SPEAKER_MIXER                    0x36
+#define WM9090_ANTIPOP2                         0x39
+#define WM9090_WRITE_SEQUENCER_0                0x46
+#define WM9090_WRITE_SEQUENCER_1                0x47
+#define WM9090_WRITE_SEQUENCER_2                0x48
+#define WM9090_WRITE_SEQUENCER_3                0x49
+#define WM9090_WRITE_SEQUENCER_4                0x4A
+#define WM9090_WRITE_SEQUENCER_5                0x4B
+#define WM9090_CHARGE_PUMP_1                    0x4C
+#define WM9090_DC_SERVO_0                       0x54
+#define WM9090_DC_SERVO_1                       0x55
+#define WM9090_DC_SERVO_3                       0x57
+#define WM9090_DC_SERVO_READBACK_0              0x58
+#define WM9090_DC_SERVO_READBACK_1              0x59
+#define WM9090_DC_SERVO_READBACK_2              0x5A
+#define WM9090_ANALOGUE_HP_0                    0x60
+#define WM9090_AGC_CONTROL_0                    0x62
+#define WM9090_AGC_CONTROL_1                    0x63
+#define WM9090_AGC_CONTROL_2                    0x64
+
+#define WM9090_REGISTER_COUNT                   40
+#define WM9090_MAX_REGISTER                     0x64
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Software Reset
+ */
+#define WM9090_SW_RESET_MASK                    0xFFFF  /* SW_RESET - [15:0] */
+#define WM9090_SW_RESET_SHIFT                        0  /* SW_RESET - [15:0] */
+#define WM9090_SW_RESET_WIDTH                       16  /* SW_RESET - [15:0] */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM9090_SPKOUTL_ENA                      0x1000  /* SPKOUTL_ENA */
+#define WM9090_SPKOUTL_ENA_MASK                 0x1000  /* SPKOUTL_ENA */
+#define WM9090_SPKOUTL_ENA_SHIFT                    12  /* SPKOUTL_ENA */
+#define WM9090_SPKOUTL_ENA_WIDTH                     1  /* SPKOUTL_ENA */
+#define WM9090_HPOUT1L_ENA                      0x0200  /* HPOUT1L_ENA */
+#define WM9090_HPOUT1L_ENA_MASK                 0x0200  /* HPOUT1L_ENA */
+#define WM9090_HPOUT1L_ENA_SHIFT                     9  /* HPOUT1L_ENA */
+#define WM9090_HPOUT1L_ENA_WIDTH                     1  /* HPOUT1L_ENA */
+#define WM9090_HPOUT1R_ENA                      0x0100  /* HPOUT1R_ENA */
+#define WM9090_HPOUT1R_ENA_MASK                 0x0100  /* HPOUT1R_ENA */
+#define WM9090_HPOUT1R_ENA_SHIFT                     8  /* HPOUT1R_ENA */
+#define WM9090_HPOUT1R_ENA_WIDTH                     1  /* HPOUT1R_ENA */
+#define WM9090_OSC_ENA                          0x0008  /* OSC_ENA */
+#define WM9090_OSC_ENA_MASK                     0x0008  /* OSC_ENA */
+#define WM9090_OSC_ENA_SHIFT                         3  /* OSC_ENA */
+#define WM9090_OSC_ENA_WIDTH                         1  /* OSC_ENA */
+#define WM9090_VMID_RES_MASK                    0x0006  /* VMID_RES - [2:1] */
+#define WM9090_VMID_RES_SHIFT                        1  /* VMID_RES - [2:1] */
+#define WM9090_VMID_RES_WIDTH                        2  /* VMID_RES - [2:1] */
+#define WM9090_BIAS_ENA                         0x0001  /* BIAS_ENA */
+#define WM9090_BIAS_ENA_MASK                    0x0001  /* BIAS_ENA */
+#define WM9090_BIAS_ENA_SHIFT                        0  /* BIAS_ENA */
+#define WM9090_BIAS_ENA_WIDTH                        1  /* BIAS_ENA */
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM9090_TSHUT                            0x8000  /* TSHUT */
+#define WM9090_TSHUT_MASK                       0x8000  /* TSHUT */
+#define WM9090_TSHUT_SHIFT                          15  /* TSHUT */
+#define WM9090_TSHUT_WIDTH                           1  /* TSHUT */
+#define WM9090_TSHUT_ENA                        0x4000  /* TSHUT_ENA */
+#define WM9090_TSHUT_ENA_MASK                   0x4000  /* TSHUT_ENA */
+#define WM9090_TSHUT_ENA_SHIFT                      14  /* TSHUT_ENA */
+#define WM9090_TSHUT_ENA_WIDTH                       1  /* TSHUT_ENA */
+#define WM9090_TSHUT_OPDIS                      0x2000  /* TSHUT_OPDIS */
+#define WM9090_TSHUT_OPDIS_MASK                 0x2000  /* TSHUT_OPDIS */
+#define WM9090_TSHUT_OPDIS_SHIFT                    13  /* TSHUT_OPDIS */
+#define WM9090_TSHUT_OPDIS_WIDTH                     1  /* TSHUT_OPDIS */
+#define WM9090_IN1A_ENA                         0x0080  /* IN1A_ENA */
+#define WM9090_IN1A_ENA_MASK                    0x0080  /* IN1A_ENA */
+#define WM9090_IN1A_ENA_SHIFT                        7  /* IN1A_ENA */
+#define WM9090_IN1A_ENA_WIDTH                        1  /* IN1A_ENA */
+#define WM9090_IN1B_ENA                         0x0040  /* IN1B_ENA */
+#define WM9090_IN1B_ENA_MASK                    0x0040  /* IN1B_ENA */
+#define WM9090_IN1B_ENA_SHIFT                        6  /* IN1B_ENA */
+#define WM9090_IN1B_ENA_WIDTH                        1  /* IN1B_ENA */
+#define WM9090_IN2A_ENA                         0x0020  /* IN2A_ENA */
+#define WM9090_IN2A_ENA_MASK                    0x0020  /* IN2A_ENA */
+#define WM9090_IN2A_ENA_SHIFT                        5  /* IN2A_ENA */
+#define WM9090_IN2A_ENA_WIDTH                        1  /* IN2A_ENA */
+#define WM9090_IN2B_ENA                         0x0010  /* IN2B_ENA */
+#define WM9090_IN2B_ENA_MASK                    0x0010  /* IN2B_ENA */
+#define WM9090_IN2B_ENA_SHIFT                        4  /* IN2B_ENA */
+#define WM9090_IN2B_ENA_WIDTH                        1  /* IN2B_ENA */
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM9090_AGC_ENA                          0x4000  /* AGC_ENA */
+#define WM9090_AGC_ENA_MASK                     0x4000  /* AGC_ENA */
+#define WM9090_AGC_ENA_SHIFT                        14  /* AGC_ENA */
+#define WM9090_AGC_ENA_WIDTH                         1  /* AGC_ENA */
+#define WM9090_SPKLVOL_ENA                      0x0100  /* SPKLVOL_ENA */
+#define WM9090_SPKLVOL_ENA_MASK                 0x0100  /* SPKLVOL_ENA */
+#define WM9090_SPKLVOL_ENA_SHIFT                     8  /* SPKLVOL_ENA */
+#define WM9090_SPKLVOL_ENA_WIDTH                     1  /* SPKLVOL_ENA */
+#define WM9090_MIXOUTL_ENA                      0x0020  /* MIXOUTL_ENA */
+#define WM9090_MIXOUTL_ENA_MASK                 0x0020  /* MIXOUTL_ENA */
+#define WM9090_MIXOUTL_ENA_SHIFT                     5  /* MIXOUTL_ENA */
+#define WM9090_MIXOUTL_ENA_WIDTH                     1  /* MIXOUTL_ENA */
+#define WM9090_MIXOUTR_ENA                      0x0010  /* MIXOUTR_ENA */
+#define WM9090_MIXOUTR_ENA_MASK                 0x0010  /* MIXOUTR_ENA */
+#define WM9090_MIXOUTR_ENA_SHIFT                     4  /* MIXOUTR_ENA */
+#define WM9090_MIXOUTR_ENA_WIDTH                     1  /* MIXOUTR_ENA */
+#define WM9090_SPKMIX_ENA                       0x0008  /* SPKMIX_ENA */
+#define WM9090_SPKMIX_ENA_MASK                  0x0008  /* SPKMIX_ENA */
+#define WM9090_SPKMIX_ENA_SHIFT                      3  /* SPKMIX_ENA */
+#define WM9090_SPKMIX_ENA_WIDTH                      1  /* SPKMIX_ENA */
+
+/*
+ * R6 (0x06) - Clocking 1
+ */
+#define WM9090_TOCLK_RATE                       0x8000  /* TOCLK_RATE */
+#define WM9090_TOCLK_RATE_MASK                  0x8000  /* TOCLK_RATE */
+#define WM9090_TOCLK_RATE_SHIFT                     15  /* TOCLK_RATE */
+#define WM9090_TOCLK_RATE_WIDTH                      1  /* TOCLK_RATE */
+#define WM9090_TOCLK_ENA                        0x4000  /* TOCLK_ENA */
+#define WM9090_TOCLK_ENA_MASK                   0x4000  /* TOCLK_ENA */
+#define WM9090_TOCLK_ENA_SHIFT                      14  /* TOCLK_ENA */
+#define WM9090_TOCLK_ENA_WIDTH                       1  /* TOCLK_ENA */
+
+/*
+ * R22 (0x16) - IN1 Line Control
+ */
+#define WM9090_IN1_DIFF                         0x0002  /* IN1_DIFF */
+#define WM9090_IN1_DIFF_MASK                    0x0002  /* IN1_DIFF */
+#define WM9090_IN1_DIFF_SHIFT                        1  /* IN1_DIFF */
+#define WM9090_IN1_DIFF_WIDTH                        1  /* IN1_DIFF */
+#define WM9090_IN1_CLAMP                        0x0001  /* IN1_CLAMP */
+#define WM9090_IN1_CLAMP_MASK                   0x0001  /* IN1_CLAMP */
+#define WM9090_IN1_CLAMP_SHIFT                       0  /* IN1_CLAMP */
+#define WM9090_IN1_CLAMP_WIDTH                       1  /* IN1_CLAMP */
+
+/*
+ * R23 (0x17) - IN2 Line Control
+ */
+#define WM9090_IN2_DIFF                         0x0002  /* IN2_DIFF */
+#define WM9090_IN2_DIFF_MASK                    0x0002  /* IN2_DIFF */
+#define WM9090_IN2_DIFF_SHIFT                        1  /* IN2_DIFF */
+#define WM9090_IN2_DIFF_WIDTH                        1  /* IN2_DIFF */
+#define WM9090_IN2_CLAMP                        0x0001  /* IN2_CLAMP */
+#define WM9090_IN2_CLAMP_MASK                   0x0001  /* IN2_CLAMP */
+#define WM9090_IN2_CLAMP_SHIFT                       0  /* IN2_CLAMP */
+#define WM9090_IN2_CLAMP_WIDTH                       1  /* IN2_CLAMP */
+
+/*
+ * R24 (0x18) - IN1 Line Input A Volume
+ */
+#define WM9090_IN1_VU                           0x0100  /* IN1_VU */
+#define WM9090_IN1_VU_MASK                      0x0100  /* IN1_VU */
+#define WM9090_IN1_VU_SHIFT                          8  /* IN1_VU */
+#define WM9090_IN1_VU_WIDTH                          1  /* IN1_VU */
+#define WM9090_IN1A_MUTE                        0x0080  /* IN1A_MUTE */
+#define WM9090_IN1A_MUTE_MASK                   0x0080  /* IN1A_MUTE */
+#define WM9090_IN1A_MUTE_SHIFT                       7  /* IN1A_MUTE */
+#define WM9090_IN1A_MUTE_WIDTH                       1  /* IN1A_MUTE */
+#define WM9090_IN1A_ZC                          0x0040  /* IN1A_ZC */
+#define WM9090_IN1A_ZC_MASK                     0x0040  /* IN1A_ZC */
+#define WM9090_IN1A_ZC_SHIFT                         6  /* IN1A_ZC */
+#define WM9090_IN1A_ZC_WIDTH                         1  /* IN1A_ZC */
+#define WM9090_IN1A_VOL_MASK                    0x0007  /* IN1A_VOL - [2:0] */
+#define WM9090_IN1A_VOL_SHIFT                        0  /* IN1A_VOL - [2:0] */
+#define WM9090_IN1A_VOL_WIDTH                        3  /* IN1A_VOL - [2:0] */
+
+/*
+ * R25 (0x19) - IN1  Line Input B Volume
+ */
+#define WM9090_IN1_VU                           0x0100  /* IN1_VU */
+#define WM9090_IN1_VU_MASK                      0x0100  /* IN1_VU */
+#define WM9090_IN1_VU_SHIFT                          8  /* IN1_VU */
+#define WM9090_IN1_VU_WIDTH                          1  /* IN1_VU */
+#define WM9090_IN1B_MUTE                        0x0080  /* IN1B_MUTE */
+#define WM9090_IN1B_MUTE_MASK                   0x0080  /* IN1B_MUTE */
+#define WM9090_IN1B_MUTE_SHIFT                       7  /* IN1B_MUTE */
+#define WM9090_IN1B_MUTE_WIDTH                       1  /* IN1B_MUTE */
+#define WM9090_IN1B_ZC                          0x0040  /* IN1B_ZC */
+#define WM9090_IN1B_ZC_MASK                     0x0040  /* IN1B_ZC */
+#define WM9090_IN1B_ZC_SHIFT                         6  /* IN1B_ZC */
+#define WM9090_IN1B_ZC_WIDTH                         1  /* IN1B_ZC */
+#define WM9090_IN1B_VOL_MASK                    0x0007  /* IN1B_VOL - [2:0] */
+#define WM9090_IN1B_VOL_SHIFT                        0  /* IN1B_VOL - [2:0] */
+#define WM9090_IN1B_VOL_WIDTH                        3  /* IN1B_VOL - [2:0] */
+
+/*
+ * R26 (0x1A) - IN2 Line Input A Volume
+ */
+#define WM9090_IN2_VU                           0x0100  /* IN2_VU */
+#define WM9090_IN2_VU_MASK                      0x0100  /* IN2_VU */
+#define WM9090_IN2_VU_SHIFT                          8  /* IN2_VU */
+#define WM9090_IN2_VU_WIDTH                          1  /* IN2_VU */
+#define WM9090_IN2A_MUTE                        0x0080  /* IN2A_MUTE */
+#define WM9090_IN2A_MUTE_MASK                   0x0080  /* IN2A_MUTE */
+#define WM9090_IN2A_MUTE_SHIFT                       7  /* IN2A_MUTE */
+#define WM9090_IN2A_MUTE_WIDTH                       1  /* IN2A_MUTE */
+#define WM9090_IN2A_ZC                          0x0040  /* IN2A_ZC */
+#define WM9090_IN2A_ZC_MASK                     0x0040  /* IN2A_ZC */
+#define WM9090_IN2A_ZC_SHIFT                         6  /* IN2A_ZC */
+#define WM9090_IN2A_ZC_WIDTH                         1  /* IN2A_ZC */
+#define WM9090_IN2A_VOL_MASK                    0x0007  /* IN2A_VOL - [2:0] */
+#define WM9090_IN2A_VOL_SHIFT                        0  /* IN2A_VOL - [2:0] */
+#define WM9090_IN2A_VOL_WIDTH                        3  /* IN2A_VOL - [2:0] */
+
+/*
+ * R27 (0x1B) - IN2 Line Input B Volume
+ */
+#define WM9090_IN2_VU                           0x0100  /* IN2_VU */
+#define WM9090_IN2_VU_MASK                      0x0100  /* IN2_VU */
+#define WM9090_IN2_VU_SHIFT                          8  /* IN2_VU */
+#define WM9090_IN2_VU_WIDTH                          1  /* IN2_VU */
+#define WM9090_IN2B_MUTE                        0x0080  /* IN2B_MUTE */
+#define WM9090_IN2B_MUTE_MASK                   0x0080  /* IN2B_MUTE */
+#define WM9090_IN2B_MUTE_SHIFT                       7  /* IN2B_MUTE */
+#define WM9090_IN2B_MUTE_WIDTH                       1  /* IN2B_MUTE */
+#define WM9090_IN2B_ZC                          0x0040  /* IN2B_ZC */
+#define WM9090_IN2B_ZC_MASK                     0x0040  /* IN2B_ZC */
+#define WM9090_IN2B_ZC_SHIFT                         6  /* IN2B_ZC */
+#define WM9090_IN2B_ZC_WIDTH                         1  /* IN2B_ZC */
+#define WM9090_IN2B_VOL_MASK                    0x0007  /* IN2B_VOL - [2:0] */
+#define WM9090_IN2B_VOL_SHIFT                        0  /* IN2B_VOL - [2:0] */
+#define WM9090_IN2B_VOL_WIDTH                        3  /* IN2B_VOL - [2:0] */
+
+/*
+ * R28 (0x1C) - Left Output Volume
+ */
+#define WM9090_HPOUT1_VU                        0x0100  /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_MASK                   0x0100  /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_SHIFT                       8  /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_WIDTH                       1  /* HPOUT1_VU */
+#define WM9090_HPOUT1L_ZC                       0x0080  /* HPOUT1L_ZC */
+#define WM9090_HPOUT1L_ZC_MASK                  0x0080  /* HPOUT1L_ZC */
+#define WM9090_HPOUT1L_ZC_SHIFT                      7  /* HPOUT1L_ZC */
+#define WM9090_HPOUT1L_ZC_WIDTH                      1  /* HPOUT1L_ZC */
+#define WM9090_HPOUT1L_MUTE                     0x0040  /* HPOUT1L_MUTE */
+#define WM9090_HPOUT1L_MUTE_MASK                0x0040  /* HPOUT1L_MUTE */
+#define WM9090_HPOUT1L_MUTE_SHIFT                    6  /* HPOUT1L_MUTE */
+#define WM9090_HPOUT1L_MUTE_WIDTH                    1  /* HPOUT1L_MUTE */
+#define WM9090_HPOUT1L_VOL_MASK                 0x003F  /* HPOUT1L_VOL - [5:0] */
+#define WM9090_HPOUT1L_VOL_SHIFT                     0  /* HPOUT1L_VOL - [5:0] */
+#define WM9090_HPOUT1L_VOL_WIDTH                     6  /* HPOUT1L_VOL - [5:0] */
+
+/*
+ * R29 (0x1D) - Right Output Volume
+ */
+#define WM9090_HPOUT1_VU                        0x0100  /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_MASK                   0x0100  /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_SHIFT                       8  /* HPOUT1_VU */
+#define WM9090_HPOUT1_VU_WIDTH                       1  /* HPOUT1_VU */
+#define WM9090_HPOUT1R_ZC                       0x0080  /* HPOUT1R_ZC */
+#define WM9090_HPOUT1R_ZC_MASK                  0x0080  /* HPOUT1R_ZC */
+#define WM9090_HPOUT1R_ZC_SHIFT                      7  /* HPOUT1R_ZC */
+#define WM9090_HPOUT1R_ZC_WIDTH                      1  /* HPOUT1R_ZC */
+#define WM9090_HPOUT1R_MUTE                     0x0040  /* HPOUT1R_MUTE */
+#define WM9090_HPOUT1R_MUTE_MASK                0x0040  /* HPOUT1R_MUTE */
+#define WM9090_HPOUT1R_MUTE_SHIFT                    6  /* HPOUT1R_MUTE */
+#define WM9090_HPOUT1R_MUTE_WIDTH                    1  /* HPOUT1R_MUTE */
+#define WM9090_HPOUT1R_VOL_MASK                 0x003F  /* HPOUT1R_VOL - [5:0] */
+#define WM9090_HPOUT1R_VOL_SHIFT                     0  /* HPOUT1R_VOL - [5:0] */
+#define WM9090_HPOUT1R_VOL_WIDTH                     6  /* HPOUT1R_VOL - [5:0] */
+
+/*
+ * R34 (0x22) - SPKMIXL Attenuation
+ */
+#define WM9090_SPKMIX_MUTE                      0x0100  /* SPKMIX_MUTE */
+#define WM9090_SPKMIX_MUTE_MASK                 0x0100  /* SPKMIX_MUTE */
+#define WM9090_SPKMIX_MUTE_SHIFT                     8  /* SPKMIX_MUTE */
+#define WM9090_SPKMIX_MUTE_WIDTH                     1  /* SPKMIX_MUTE */
+#define WM9090_IN1A_SPKMIX_VOL_MASK             0x00C0  /* IN1A_SPKMIX_VOL - [7:6] */
+#define WM9090_IN1A_SPKMIX_VOL_SHIFT                 6  /* IN1A_SPKMIX_VOL - [7:6] */
+#define WM9090_IN1A_SPKMIX_VOL_WIDTH                 2  /* IN1A_SPKMIX_VOL - [7:6] */
+#define WM9090_IN1B_SPKMIX_VOL_MASK             0x0030  /* IN1B_SPKMIX_VOL - [5:4] */
+#define WM9090_IN1B_SPKMIX_VOL_SHIFT                 4  /* IN1B_SPKMIX_VOL - [5:4] */
+#define WM9090_IN1B_SPKMIX_VOL_WIDTH                 2  /* IN1B_SPKMIX_VOL - [5:4] */
+#define WM9090_IN2A_SPKMIX_VOL_MASK             0x000C  /* IN2A_SPKMIX_VOL - [3:2] */
+#define WM9090_IN2A_SPKMIX_VOL_SHIFT                 2  /* IN2A_SPKMIX_VOL - [3:2] */
+#define WM9090_IN2A_SPKMIX_VOL_WIDTH                 2  /* IN2A_SPKMIX_VOL - [3:2] */
+#define WM9090_IN2B_SPKMIX_VOL_MASK             0x0003  /* IN2B_SPKMIX_VOL - [1:0] */
+#define WM9090_IN2B_SPKMIX_VOL_SHIFT                 0  /* IN2B_SPKMIX_VOL - [1:0] */
+#define WM9090_IN2B_SPKMIX_VOL_WIDTH                 2  /* IN2B_SPKMIX_VOL - [1:0] */
+
+/*
+ * R36 (0x24) - SPKOUT Mixers
+ */
+#define WM9090_SPKMIXL_TO_SPKOUTL               0x0010  /* SPKMIXL_TO_SPKOUTL */
+#define WM9090_SPKMIXL_TO_SPKOUTL_MASK          0x0010  /* SPKMIXL_TO_SPKOUTL */
+#define WM9090_SPKMIXL_TO_SPKOUTL_SHIFT              4  /* SPKMIXL_TO_SPKOUTL */
+#define WM9090_SPKMIXL_TO_SPKOUTL_WIDTH              1  /* SPKMIXL_TO_SPKOUTL */
+
+/*
+ * R37 (0x25) - ClassD3
+ */
+#define WM9090_SPKOUTL_BOOST_MASK               0x0038  /* SPKOUTL_BOOST - [5:3] */
+#define WM9090_SPKOUTL_BOOST_SHIFT                   3  /* SPKOUTL_BOOST - [5:3] */
+#define WM9090_SPKOUTL_BOOST_WIDTH                   3  /* SPKOUTL_BOOST - [5:3] */
+
+/*
+ * R38 (0x26) - Speaker Volume Left
+ */
+#define WM9090_SPKOUT_VU                        0x0100  /* SPKOUT_VU */
+#define WM9090_SPKOUT_VU_MASK                   0x0100  /* SPKOUT_VU */
+#define WM9090_SPKOUT_VU_SHIFT                       8  /* SPKOUT_VU */
+#define WM9090_SPKOUT_VU_WIDTH                       1  /* SPKOUT_VU */
+#define WM9090_SPKOUTL_ZC                       0x0080  /* SPKOUTL_ZC */
+#define WM9090_SPKOUTL_ZC_MASK                  0x0080  /* SPKOUTL_ZC */
+#define WM9090_SPKOUTL_ZC_SHIFT                      7  /* SPKOUTL_ZC */
+#define WM9090_SPKOUTL_ZC_WIDTH                      1  /* SPKOUTL_ZC */
+#define WM9090_SPKOUTL_MUTE                     0x0040  /* SPKOUTL_MUTE */
+#define WM9090_SPKOUTL_MUTE_MASK                0x0040  /* SPKOUTL_MUTE */
+#define WM9090_SPKOUTL_MUTE_SHIFT                    6  /* SPKOUTL_MUTE */
+#define WM9090_SPKOUTL_MUTE_WIDTH                    1  /* SPKOUTL_MUTE */
+#define WM9090_SPKOUTL_VOL_MASK                 0x003F  /* SPKOUTL_VOL - [5:0] */
+#define WM9090_SPKOUTL_VOL_SHIFT                     0  /* SPKOUTL_VOL - [5:0] */
+#define WM9090_SPKOUTL_VOL_WIDTH                     6  /* SPKOUTL_VOL - [5:0] */
+
+/*
+ * R45 (0x2D) - Output Mixer1
+ */
+#define WM9090_IN1A_TO_MIXOUTL                  0x0040  /* IN1A_TO_MIXOUTL */
+#define WM9090_IN1A_TO_MIXOUTL_MASK             0x0040  /* IN1A_TO_MIXOUTL */
+#define WM9090_IN1A_TO_MIXOUTL_SHIFT                 6  /* IN1A_TO_MIXOUTL */
+#define WM9090_IN1A_TO_MIXOUTL_WIDTH                 1  /* IN1A_TO_MIXOUTL */
+#define WM9090_IN2A_TO_MIXOUTL                  0x0004  /* IN2A_TO_MIXOUTL */
+#define WM9090_IN2A_TO_MIXOUTL_MASK             0x0004  /* IN2A_TO_MIXOUTL */
+#define WM9090_IN2A_TO_MIXOUTL_SHIFT                 2  /* IN2A_TO_MIXOUTL */
+#define WM9090_IN2A_TO_MIXOUTL_WIDTH                 1  /* IN2A_TO_MIXOUTL */
+
+/*
+ * R46 (0x2E) - Output Mixer2
+ */
+#define WM9090_IN1A_TO_MIXOUTR                  0x0040  /* IN1A_TO_MIXOUTR */
+#define WM9090_IN1A_TO_MIXOUTR_MASK             0x0040  /* IN1A_TO_MIXOUTR */
+#define WM9090_IN1A_TO_MIXOUTR_SHIFT                 6  /* IN1A_TO_MIXOUTR */
+#define WM9090_IN1A_TO_MIXOUTR_WIDTH                 1  /* IN1A_TO_MIXOUTR */
+#define WM9090_IN1B_TO_MIXOUTR                  0x0010  /* IN1B_TO_MIXOUTR */
+#define WM9090_IN1B_TO_MIXOUTR_MASK             0x0010  /* IN1B_TO_MIXOUTR */
+#define WM9090_IN1B_TO_MIXOUTR_SHIFT                 4  /* IN1B_TO_MIXOUTR */
+#define WM9090_IN1B_TO_MIXOUTR_WIDTH                 1  /* IN1B_TO_MIXOUTR */
+#define WM9090_IN2A_TO_MIXOUTR                  0x0004  /* IN2A_TO_MIXOUTR */
+#define WM9090_IN2A_TO_MIXOUTR_MASK             0x0004  /* IN2A_TO_MIXOUTR */
+#define WM9090_IN2A_TO_MIXOUTR_SHIFT                 2  /* IN2A_TO_MIXOUTR */
+#define WM9090_IN2A_TO_MIXOUTR_WIDTH                 1  /* IN2A_TO_MIXOUTR */
+#define WM9090_IN2B_TO_MIXOUTR                  0x0001  /* IN2B_TO_MIXOUTR */
+#define WM9090_IN2B_TO_MIXOUTR_MASK             0x0001  /* IN2B_TO_MIXOUTR */
+#define WM9090_IN2B_TO_MIXOUTR_SHIFT                 0  /* IN2B_TO_MIXOUTR */
+#define WM9090_IN2B_TO_MIXOUTR_WIDTH                 1  /* IN2B_TO_MIXOUTR */
+
+/*
+ * R47 (0x2F) - Output Mixer3
+ */
+#define WM9090_MIXOUTL_MUTE                     0x0100  /* MIXOUTL_MUTE */
+#define WM9090_MIXOUTL_MUTE_MASK                0x0100  /* MIXOUTL_MUTE */
+#define WM9090_MIXOUTL_MUTE_SHIFT                    8  /* MIXOUTL_MUTE */
+#define WM9090_MIXOUTL_MUTE_WIDTH                    1  /* MIXOUTL_MUTE */
+#define WM9090_IN1A_MIXOUTL_VOL_MASK            0x00C0  /* IN1A_MIXOUTL_VOL - [7:6] */
+#define WM9090_IN1A_MIXOUTL_VOL_SHIFT                6  /* IN1A_MIXOUTL_VOL - [7:6] */
+#define WM9090_IN1A_MIXOUTL_VOL_WIDTH                2  /* IN1A_MIXOUTL_VOL - [7:6] */
+#define WM9090_IN2A_MIXOUTL_VOL_MASK            0x000C  /* IN2A_MIXOUTL_VOL - [3:2] */
+#define WM9090_IN2A_MIXOUTL_VOL_SHIFT                2  /* IN2A_MIXOUTL_VOL - [3:2] */
+#define WM9090_IN2A_MIXOUTL_VOL_WIDTH                2  /* IN2A_MIXOUTL_VOL - [3:2] */
+
+/*
+ * R48 (0x30) - Output Mixer4
+ */
+#define WM9090_MIXOUTR_MUTE                     0x0100  /* MIXOUTR_MUTE */
+#define WM9090_MIXOUTR_MUTE_MASK                0x0100  /* MIXOUTR_MUTE */
+#define WM9090_MIXOUTR_MUTE_SHIFT                    8  /* MIXOUTR_MUTE */
+#define WM9090_MIXOUTR_MUTE_WIDTH                    1  /* MIXOUTR_MUTE */
+#define WM9090_IN1A_MIXOUTR_VOL_MASK            0x00C0  /* IN1A_MIXOUTR_VOL - [7:6] */
+#define WM9090_IN1A_MIXOUTR_VOL_SHIFT                6  /* IN1A_MIXOUTR_VOL - [7:6] */
+#define WM9090_IN1A_MIXOUTR_VOL_WIDTH                2  /* IN1A_MIXOUTR_VOL - [7:6] */
+#define WM9090_IN1B_MIXOUTR_VOL_MASK            0x0030  /* IN1B_MIXOUTR_VOL - [5:4] */
+#define WM9090_IN1B_MIXOUTR_VOL_SHIFT                4  /* IN1B_MIXOUTR_VOL - [5:4] */
+#define WM9090_IN1B_MIXOUTR_VOL_WIDTH                2  /* IN1B_MIXOUTR_VOL - [5:4] */
+#define WM9090_IN2A_MIXOUTR_VOL_MASK            0x000C  /* IN2A_MIXOUTR_VOL - [3:2] */
+#define WM9090_IN2A_MIXOUTR_VOL_SHIFT                2  /* IN2A_MIXOUTR_VOL - [3:2] */
+#define WM9090_IN2A_MIXOUTR_VOL_WIDTH                2  /* IN2A_MIXOUTR_VOL - [3:2] */
+#define WM9090_IN2B_MIXOUTR_VOL_MASK            0x0003  /* IN2B_MIXOUTR_VOL - [1:0] */
+#define WM9090_IN2B_MIXOUTR_VOL_SHIFT                0  /* IN2B_MIXOUTR_VOL - [1:0] */
+#define WM9090_IN2B_MIXOUTR_VOL_WIDTH                2  /* IN2B_MIXOUTR_VOL - [1:0] */
+
+/*
+ * R54 (0x36) - Speaker Mixer
+ */
+#define WM9090_IN1A_TO_SPKMIX                   0x0040  /* IN1A_TO_SPKMIX */
+#define WM9090_IN1A_TO_SPKMIX_MASK              0x0040  /* IN1A_TO_SPKMIX */
+#define WM9090_IN1A_TO_SPKMIX_SHIFT                  6  /* IN1A_TO_SPKMIX */
+#define WM9090_IN1A_TO_SPKMIX_WIDTH                  1  /* IN1A_TO_SPKMIX */
+#define WM9090_IN1B_TO_SPKMIX                   0x0010  /* IN1B_TO_SPKMIX */
+#define WM9090_IN1B_TO_SPKMIX_MASK              0x0010  /* IN1B_TO_SPKMIX */
+#define WM9090_IN1B_TO_SPKMIX_SHIFT                  4  /* IN1B_TO_SPKMIX */
+#define WM9090_IN1B_TO_SPKMIX_WIDTH                  1  /* IN1B_TO_SPKMIX */
+#define WM9090_IN2A_TO_SPKMIX                   0x0004  /* IN2A_TO_SPKMIX */
+#define WM9090_IN2A_TO_SPKMIX_MASK              0x0004  /* IN2A_TO_SPKMIX */
+#define WM9090_IN2A_TO_SPKMIX_SHIFT                  2  /* IN2A_TO_SPKMIX */
+#define WM9090_IN2A_TO_SPKMIX_WIDTH                  1  /* IN2A_TO_SPKMIX */
+#define WM9090_IN2B_TO_SPKMIX                   0x0001  /* IN2B_TO_SPKMIX */
+#define WM9090_IN2B_TO_SPKMIX_MASK              0x0001  /* IN2B_TO_SPKMIX */
+#define WM9090_IN2B_TO_SPKMIX_SHIFT                  0  /* IN2B_TO_SPKMIX */
+#define WM9090_IN2B_TO_SPKMIX_WIDTH                  1  /* IN2B_TO_SPKMIX */
+
+/*
+ * R57 (0x39) - AntiPOP2
+ */
+#define WM9090_VMID_BUF_ENA                     0x0008  /* VMID_BUF_ENA */
+#define WM9090_VMID_BUF_ENA_MASK                0x0008  /* VMID_BUF_ENA */
+#define WM9090_VMID_BUF_ENA_SHIFT                    3  /* VMID_BUF_ENA */
+#define WM9090_VMID_BUF_ENA_WIDTH                    1  /* VMID_BUF_ENA */
+#define WM9090_VMID_ENA                         0x0001  /* VMID_ENA */
+#define WM9090_VMID_ENA_MASK                    0x0001  /* VMID_ENA */
+#define WM9090_VMID_ENA_SHIFT                        0  /* VMID_ENA */
+#define WM9090_VMID_ENA_WIDTH                        1  /* VMID_ENA */
+
+/*
+ * R70 (0x46) - Write Sequencer 0
+ */
+#define WM9090_WSEQ_ENA                         0x0100  /* WSEQ_ENA */
+#define WM9090_WSEQ_ENA_MASK                    0x0100  /* WSEQ_ENA */
+#define WM9090_WSEQ_ENA_SHIFT                        8  /* WSEQ_ENA */
+#define WM9090_WSEQ_ENA_WIDTH                        1  /* WSEQ_ENA */
+#define WM9090_WSEQ_WRITE_INDEX_MASK            0x000F  /* WSEQ_WRITE_INDEX - [3:0] */
+#define WM9090_WSEQ_WRITE_INDEX_SHIFT                0  /* WSEQ_WRITE_INDEX - [3:0] */
+#define WM9090_WSEQ_WRITE_INDEX_WIDTH                4  /* WSEQ_WRITE_INDEX - [3:0] */
+
+/*
+ * R71 (0x47) - Write Sequencer 1
+ */
+#define WM9090_WSEQ_DATA_WIDTH_MASK             0x7000  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM9090_WSEQ_DATA_WIDTH_SHIFT                12  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM9090_WSEQ_DATA_WIDTH_WIDTH                 3  /* WSEQ_DATA_WIDTH - [14:12] */
+#define WM9090_WSEQ_DATA_START_MASK             0x0F00  /* WSEQ_DATA_START - [11:8] */
+#define WM9090_WSEQ_DATA_START_SHIFT                 8  /* WSEQ_DATA_START - [11:8] */
+#define WM9090_WSEQ_DATA_START_WIDTH                 4  /* WSEQ_DATA_START - [11:8] */
+#define WM9090_WSEQ_ADDR_MASK                   0x00FF  /* WSEQ_ADDR - [7:0] */
+#define WM9090_WSEQ_ADDR_SHIFT                       0  /* WSEQ_ADDR - [7:0] */
+#define WM9090_WSEQ_ADDR_WIDTH                       8  /* WSEQ_ADDR - [7:0] */
+
+/*
+ * R72 (0x48) - Write Sequencer 2
+ */
+#define WM9090_WSEQ_EOS                         0x4000  /* WSEQ_EOS */
+#define WM9090_WSEQ_EOS_MASK                    0x4000  /* WSEQ_EOS */
+#define WM9090_WSEQ_EOS_SHIFT                       14  /* WSEQ_EOS */
+#define WM9090_WSEQ_EOS_WIDTH                        1  /* WSEQ_EOS */
+#define WM9090_WSEQ_DELAY_MASK                  0x0F00  /* WSEQ_DELAY - [11:8] */
+#define WM9090_WSEQ_DELAY_SHIFT                      8  /* WSEQ_DELAY - [11:8] */
+#define WM9090_WSEQ_DELAY_WIDTH                      4  /* WSEQ_DELAY - [11:8] */
+#define WM9090_WSEQ_DATA_MASK                   0x00FF  /* WSEQ_DATA - [7:0] */
+#define WM9090_WSEQ_DATA_SHIFT                       0  /* WSEQ_DATA - [7:0] */
+#define WM9090_WSEQ_DATA_WIDTH                       8  /* WSEQ_DATA - [7:0] */
+
+/*
+ * R73 (0x49) - Write Sequencer 3
+ */
+#define WM9090_WSEQ_ABORT                       0x0200  /* WSEQ_ABORT */
+#define WM9090_WSEQ_ABORT_MASK                  0x0200  /* WSEQ_ABORT */
+#define WM9090_WSEQ_ABORT_SHIFT                      9  /* WSEQ_ABORT */
+#define WM9090_WSEQ_ABORT_WIDTH                      1  /* WSEQ_ABORT */
+#define WM9090_WSEQ_START                       0x0100  /* WSEQ_START */
+#define WM9090_WSEQ_START_MASK                  0x0100  /* WSEQ_START */
+#define WM9090_WSEQ_START_SHIFT                      8  /* WSEQ_START */
+#define WM9090_WSEQ_START_WIDTH                      1  /* WSEQ_START */
+#define WM9090_WSEQ_START_INDEX_MASK            0x003F  /* WSEQ_START_INDEX - [5:0] */
+#define WM9090_WSEQ_START_INDEX_SHIFT                0  /* WSEQ_START_INDEX - [5:0] */
+#define WM9090_WSEQ_START_INDEX_WIDTH                6  /* WSEQ_START_INDEX - [5:0] */
+
+/*
+ * R74 (0x4A) - Write Sequencer 4
+ */
+#define WM9090_WSEQ_BUSY                        0x0001  /* WSEQ_BUSY */
+#define WM9090_WSEQ_BUSY_MASK                   0x0001  /* WSEQ_BUSY */
+#define WM9090_WSEQ_BUSY_SHIFT                       0  /* WSEQ_BUSY */
+#define WM9090_WSEQ_BUSY_WIDTH                       1  /* WSEQ_BUSY */
+
+/*
+ * R75 (0x4B) - Write Sequencer 5
+ */
+#define WM9090_WSEQ_CURRENT_INDEX_MASK          0x003F  /* WSEQ_CURRENT_INDEX - [5:0] */
+#define WM9090_WSEQ_CURRENT_INDEX_SHIFT              0  /* WSEQ_CURRENT_INDEX - [5:0] */
+#define WM9090_WSEQ_CURRENT_INDEX_WIDTH              6  /* WSEQ_CURRENT_INDEX - [5:0] */
+
+/*
+ * R76 (0x4C) - Charge Pump 1
+ */
+#define WM9090_CP_ENA                           0x8000  /* CP_ENA */
+#define WM9090_CP_ENA_MASK                      0x8000  /* CP_ENA */
+#define WM9090_CP_ENA_SHIFT                         15  /* CP_ENA */
+#define WM9090_CP_ENA_WIDTH                          1  /* CP_ENA */
+
+/*
+ * R84 (0x54) - DC Servo 0
+ */
+#define WM9090_DCS_TRIG_SINGLE_1                0x2000  /* DCS_TRIG_SINGLE_1 */
+#define WM9090_DCS_TRIG_SINGLE_1_MASK           0x2000  /* DCS_TRIG_SINGLE_1 */
+#define WM9090_DCS_TRIG_SINGLE_1_SHIFT              13  /* DCS_TRIG_SINGLE_1 */
+#define WM9090_DCS_TRIG_SINGLE_1_WIDTH               1  /* DCS_TRIG_SINGLE_1 */
+#define WM9090_DCS_TRIG_SINGLE_0                0x1000  /* DCS_TRIG_SINGLE_0 */
+#define WM9090_DCS_TRIG_SINGLE_0_MASK           0x1000  /* DCS_TRIG_SINGLE_0 */
+#define WM9090_DCS_TRIG_SINGLE_0_SHIFT              12  /* DCS_TRIG_SINGLE_0 */
+#define WM9090_DCS_TRIG_SINGLE_0_WIDTH               1  /* DCS_TRIG_SINGLE_0 */
+#define WM9090_DCS_TRIG_SERIES_1                0x0200  /* DCS_TRIG_SERIES_1 */
+#define WM9090_DCS_TRIG_SERIES_1_MASK           0x0200  /* DCS_TRIG_SERIES_1 */
+#define WM9090_DCS_TRIG_SERIES_1_SHIFT               9  /* DCS_TRIG_SERIES_1 */
+#define WM9090_DCS_TRIG_SERIES_1_WIDTH               1  /* DCS_TRIG_SERIES_1 */
+#define WM9090_DCS_TRIG_SERIES_0                0x0100  /* DCS_TRIG_SERIES_0 */
+#define WM9090_DCS_TRIG_SERIES_0_MASK           0x0100  /* DCS_TRIG_SERIES_0 */
+#define WM9090_DCS_TRIG_SERIES_0_SHIFT               8  /* DCS_TRIG_SERIES_0 */
+#define WM9090_DCS_TRIG_SERIES_0_WIDTH               1  /* DCS_TRIG_SERIES_0 */
+#define WM9090_DCS_TRIG_STARTUP_1               0x0020  /* DCS_TRIG_STARTUP_1 */
+#define WM9090_DCS_TRIG_STARTUP_1_MASK          0x0020  /* DCS_TRIG_STARTUP_1 */
+#define WM9090_DCS_TRIG_STARTUP_1_SHIFT              5  /* DCS_TRIG_STARTUP_1 */
+#define WM9090_DCS_TRIG_STARTUP_1_WIDTH              1  /* DCS_TRIG_STARTUP_1 */
+#define WM9090_DCS_TRIG_STARTUP_0               0x0010  /* DCS_TRIG_STARTUP_0 */
+#define WM9090_DCS_TRIG_STARTUP_0_MASK          0x0010  /* DCS_TRIG_STARTUP_0 */
+#define WM9090_DCS_TRIG_STARTUP_0_SHIFT              4  /* DCS_TRIG_STARTUP_0 */
+#define WM9090_DCS_TRIG_STARTUP_0_WIDTH              1  /* DCS_TRIG_STARTUP_0 */
+#define WM9090_DCS_TRIG_DAC_WR_1                0x0008  /* DCS_TRIG_DAC_WR_1 */
+#define WM9090_DCS_TRIG_DAC_WR_1_MASK           0x0008  /* DCS_TRIG_DAC_WR_1 */
+#define WM9090_DCS_TRIG_DAC_WR_1_SHIFT               3  /* DCS_TRIG_DAC_WR_1 */
+#define WM9090_DCS_TRIG_DAC_WR_1_WIDTH               1  /* DCS_TRIG_DAC_WR_1 */
+#define WM9090_DCS_TRIG_DAC_WR_0                0x0004  /* DCS_TRIG_DAC_WR_0 */
+#define WM9090_DCS_TRIG_DAC_WR_0_MASK           0x0004  /* DCS_TRIG_DAC_WR_0 */
+#define WM9090_DCS_TRIG_DAC_WR_0_SHIFT               2  /* DCS_TRIG_DAC_WR_0 */
+#define WM9090_DCS_TRIG_DAC_WR_0_WIDTH               1  /* DCS_TRIG_DAC_WR_0 */
+#define WM9090_DCS_ENA_CHAN_1                   0x0002  /* DCS_ENA_CHAN_1 */
+#define WM9090_DCS_ENA_CHAN_1_MASK              0x0002  /* DCS_ENA_CHAN_1 */
+#define WM9090_DCS_ENA_CHAN_1_SHIFT                  1  /* DCS_ENA_CHAN_1 */
+#define WM9090_DCS_ENA_CHAN_1_WIDTH                  1  /* DCS_ENA_CHAN_1 */
+#define WM9090_DCS_ENA_CHAN_0                   0x0001  /* DCS_ENA_CHAN_0 */
+#define WM9090_DCS_ENA_CHAN_0_MASK              0x0001  /* DCS_ENA_CHAN_0 */
+#define WM9090_DCS_ENA_CHAN_0_SHIFT                  0  /* DCS_ENA_CHAN_0 */
+#define WM9090_DCS_ENA_CHAN_0_WIDTH                  1  /* DCS_ENA_CHAN_0 */
+
+/*
+ * R85 (0x55) - DC Servo 1
+ */
+#define WM9090_DCS_SERIES_NO_01_MASK            0x0FE0  /* DCS_SERIES_NO_01 - [11:5] */
+#define WM9090_DCS_SERIES_NO_01_SHIFT                5  /* DCS_SERIES_NO_01 - [11:5] */
+#define WM9090_DCS_SERIES_NO_01_WIDTH                7  /* DCS_SERIES_NO_01 - [11:5] */
+#define WM9090_DCS_TIMER_PERIOD_01_MASK         0x000F  /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM9090_DCS_TIMER_PERIOD_01_SHIFT             0  /* DCS_TIMER_PERIOD_01 - [3:0] */
+#define WM9090_DCS_TIMER_PERIOD_01_WIDTH             4  /* DCS_TIMER_PERIOD_01 - [3:0] */
+
+/*
+ * R87 (0x57) - DC Servo 3
+ */
+#define WM9090_DCS_DAC_WR_VAL_1_MASK            0xFF00  /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM9090_DCS_DAC_WR_VAL_1_SHIFT                8  /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM9090_DCS_DAC_WR_VAL_1_WIDTH                8  /* DCS_DAC_WR_VAL_1 - [15:8] */
+#define WM9090_DCS_DAC_WR_VAL_0_MASK            0x00FF  /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_0_SHIFT                0  /* DCS_DAC_WR_VAL_0 - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_0_WIDTH                8  /* DCS_DAC_WR_VAL_0 - [7:0] */
+
+/*
+ * R88 (0x58) - DC Servo Readback 0
+ */
+#define WM9090_DCS_CAL_COMPLETE_MASK            0x0300  /* DCS_CAL_COMPLETE - [9:8] */
+#define WM9090_DCS_CAL_COMPLETE_SHIFT                8  /* DCS_CAL_COMPLETE - [9:8] */
+#define WM9090_DCS_CAL_COMPLETE_WIDTH                2  /* DCS_CAL_COMPLETE - [9:8] */
+#define WM9090_DCS_DAC_WR_COMPLETE_MASK         0x0030  /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM9090_DCS_DAC_WR_COMPLETE_SHIFT             4  /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM9090_DCS_DAC_WR_COMPLETE_WIDTH             2  /* DCS_DAC_WR_COMPLETE - [5:4] */
+#define WM9090_DCS_STARTUP_COMPLETE_MASK        0x0003  /* DCS_STARTUP_COMPLETE - [1:0] */
+#define WM9090_DCS_STARTUP_COMPLETE_SHIFT            0  /* DCS_STARTUP_COMPLETE - [1:0] */
+#define WM9090_DCS_STARTUP_COMPLETE_WIDTH            2  /* DCS_STARTUP_COMPLETE - [1:0] */
+
+/*
+ * R89 (0x59) - DC Servo Readback 1
+ */
+#define WM9090_DCS_DAC_WR_VAL_1_RD_MASK         0x00FF  /* DCS_DAC_WR_VAL_1_RD - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_1_RD_SHIFT             0  /* DCS_DAC_WR_VAL_1_RD - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_1_RD_WIDTH             8  /* DCS_DAC_WR_VAL_1_RD - [7:0] */
+
+/*
+ * R90 (0x5A) - DC Servo Readback 2
+ */
+#define WM9090_DCS_DAC_WR_VAL_0_RD_MASK         0x00FF  /* DCS_DAC_WR_VAL_0_RD - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_0_RD_SHIFT             0  /* DCS_DAC_WR_VAL_0_RD - [7:0] */
+#define WM9090_DCS_DAC_WR_VAL_0_RD_WIDTH             8  /* DCS_DAC_WR_VAL_0_RD - [7:0] */
+
+/*
+ * R96 (0x60) - Analogue HP 0
+ */
+#define WM9090_HPOUT1L_RMV_SHORT                0x0080  /* HPOUT1L_RMV_SHORT */
+#define WM9090_HPOUT1L_RMV_SHORT_MASK           0x0080  /* HPOUT1L_RMV_SHORT */
+#define WM9090_HPOUT1L_RMV_SHORT_SHIFT               7  /* HPOUT1L_RMV_SHORT */
+#define WM9090_HPOUT1L_RMV_SHORT_WIDTH               1  /* HPOUT1L_RMV_SHORT */
+#define WM9090_HPOUT1L_OUTP                     0x0040  /* HPOUT1L_OUTP */
+#define WM9090_HPOUT1L_OUTP_MASK                0x0040  /* HPOUT1L_OUTP */
+#define WM9090_HPOUT1L_OUTP_SHIFT                    6  /* HPOUT1L_OUTP */
+#define WM9090_HPOUT1L_OUTP_WIDTH                    1  /* HPOUT1L_OUTP */
+#define WM9090_HPOUT1L_DLY                      0x0020  /* HPOUT1L_DLY */
+#define WM9090_HPOUT1L_DLY_MASK                 0x0020  /* HPOUT1L_DLY */
+#define WM9090_HPOUT1L_DLY_SHIFT                     5  /* HPOUT1L_DLY */
+#define WM9090_HPOUT1L_DLY_WIDTH                     1  /* HPOUT1L_DLY */
+#define WM9090_HPOUT1R_RMV_SHORT                0x0008  /* HPOUT1R_RMV_SHORT */
+#define WM9090_HPOUT1R_RMV_SHORT_MASK           0x0008  /* HPOUT1R_RMV_SHORT */
+#define WM9090_HPOUT1R_RMV_SHORT_SHIFT               3  /* HPOUT1R_RMV_SHORT */
+#define WM9090_HPOUT1R_RMV_SHORT_WIDTH               1  /* HPOUT1R_RMV_SHORT */
+#define WM9090_HPOUT1R_OUTP                     0x0004  /* HPOUT1R_OUTP */
+#define WM9090_HPOUT1R_OUTP_MASK                0x0004  /* HPOUT1R_OUTP */
+#define WM9090_HPOUT1R_OUTP_SHIFT                    2  /* HPOUT1R_OUTP */
+#define WM9090_HPOUT1R_OUTP_WIDTH                    1  /* HPOUT1R_OUTP */
+#define WM9090_HPOUT1R_DLY                      0x0002  /* HPOUT1R_DLY */
+#define WM9090_HPOUT1R_DLY_MASK                 0x0002  /* HPOUT1R_DLY */
+#define WM9090_HPOUT1R_DLY_SHIFT                     1  /* HPOUT1R_DLY */
+#define WM9090_HPOUT1R_DLY_WIDTH                     1  /* HPOUT1R_DLY */
+
+/*
+ * R98 (0x62) - AGC Control 0
+ */
+#define WM9090_AGC_CLIP_ENA                     0x8000  /* AGC_CLIP_ENA */
+#define WM9090_AGC_CLIP_ENA_MASK                0x8000  /* AGC_CLIP_ENA */
+#define WM9090_AGC_CLIP_ENA_SHIFT                   15  /* AGC_CLIP_ENA */
+#define WM9090_AGC_CLIP_ENA_WIDTH                    1  /* AGC_CLIP_ENA */
+#define WM9090_AGC_CLIP_THR_MASK                0x0F00  /* AGC_CLIP_THR - [11:8] */
+#define WM9090_AGC_CLIP_THR_SHIFT                    8  /* AGC_CLIP_THR - [11:8] */
+#define WM9090_AGC_CLIP_THR_WIDTH                    4  /* AGC_CLIP_THR - [11:8] */
+#define WM9090_AGC_CLIP_ATK_MASK                0x0070  /* AGC_CLIP_ATK - [6:4] */
+#define WM9090_AGC_CLIP_ATK_SHIFT                    4  /* AGC_CLIP_ATK - [6:4] */
+#define WM9090_AGC_CLIP_ATK_WIDTH                    3  /* AGC_CLIP_ATK - [6:4] */
+#define WM9090_AGC_CLIP_DCY_MASK                0x0007  /* AGC_CLIP_DCY - [2:0] */
+#define WM9090_AGC_CLIP_DCY_SHIFT                    0  /* AGC_CLIP_DCY - [2:0] */
+#define WM9090_AGC_CLIP_DCY_WIDTH                    3  /* AGC_CLIP_DCY - [2:0] */
+
+/*
+ * R99 (0x63) - AGC Control 1
+ */
+#define WM9090_AGC_PWR_ENA                      0x8000  /* AGC_PWR_ENA */
+#define WM9090_AGC_PWR_ENA_MASK                 0x8000  /* AGC_PWR_ENA */
+#define WM9090_AGC_PWR_ENA_SHIFT                    15  /* AGC_PWR_ENA */
+#define WM9090_AGC_PWR_ENA_WIDTH                     1  /* AGC_PWR_ENA */
+#define WM9090_AGC_PWR_AVG                      0x1000  /* AGC_PWR_AVG */
+#define WM9090_AGC_PWR_AVG_MASK                 0x1000  /* AGC_PWR_AVG */
+#define WM9090_AGC_PWR_AVG_SHIFT                    12  /* AGC_PWR_AVG */
+#define WM9090_AGC_PWR_AVG_WIDTH                     1  /* AGC_PWR_AVG */
+#define WM9090_AGC_PWR_THR_MASK                 0x0F00  /* AGC_PWR_THR - [11:8] */
+#define WM9090_AGC_PWR_THR_SHIFT                     8  /* AGC_PWR_THR - [11:8] */
+#define WM9090_AGC_PWR_THR_WIDTH                     4  /* AGC_PWR_THR - [11:8] */
+#define WM9090_AGC_PWR_ATK_MASK                 0x0070  /* AGC_PWR_ATK - [6:4] */
+#define WM9090_AGC_PWR_ATK_SHIFT                     4  /* AGC_PWR_ATK - [6:4] */
+#define WM9090_AGC_PWR_ATK_WIDTH                     3  /* AGC_PWR_ATK - [6:4] */
+#define WM9090_AGC_PWR_DCY_MASK                 0x0007  /* AGC_PWR_DCY - [2:0] */
+#define WM9090_AGC_PWR_DCY_SHIFT                     0  /* AGC_PWR_DCY - [2:0] */
+#define WM9090_AGC_PWR_DCY_WIDTH                     3  /* AGC_PWR_DCY - [2:0] */
+
+/*
+ * R100 (0x64) - AGC Control 2
+ */
+#define WM9090_AGC_RAMP                         0x0100  /* AGC_RAMP */
+#define WM9090_AGC_RAMP_MASK                    0x0100  /* AGC_RAMP */
+#define WM9090_AGC_RAMP_SHIFT                        8  /* AGC_RAMP */
+#define WM9090_AGC_RAMP_WIDTH                        1  /* AGC_RAMP */
+#define WM9090_AGC_MINGAIN_MASK                 0x003F  /* AGC_MINGAIN - [5:0] */
+#define WM9090_AGC_MINGAIN_SHIFT                     0  /* AGC_MINGAIN - [5:0] */
+#define WM9090_AGC_MINGAIN_WIDTH                     6  /* AGC_MINGAIN - [5:0] */
+
+#endif
diff --git a/linux/sound/soc/codecs/wm9705.c b/linux/sound/soc/codecs/wm9705.c
new file mode 100644
index 0000000..a144acd
--- /dev/null
+++ b/linux/sound/soc/codecs/wm9705.c
@@ -0,0 +1,430 @@
+/*
+ * wm9705.c  --  ALSA Soc WM9705 codec support
+ *
+ * Copyright 2008 Ian Molton <spyro@f2s.com>
+ *
+ *  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; Version 2 of the  License only.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "wm9705.h"
+
+/*
+ * WM9705 register cache
+ */
+static const u16 wm9705_reg[] = {
+	0x6150, 0x8000, 0x8000, 0x8000, /* 0x0  */
+	0x0000, 0x8000, 0x8008, 0x8008, /* 0x8  */
+	0x8808, 0x8808, 0x8808, 0x8808, /* 0x10 */
+	0x8808, 0x0000, 0x8000, 0x0000, /* 0x18 */
+	0x0000, 0x0000, 0x0000, 0x000f, /* 0x20 */
+	0x0605, 0x0000, 0xbb80, 0x0000, /* 0x28 */
+	0x0000, 0xbb80, 0x0000, 0x0000, /* 0x30 */
+	0x0000, 0x2000, 0x0000, 0x0000, /* 0x38 */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 0x40 */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 0x48 */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 0x50 */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 0x58 */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 0x60 */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 0x68 */
+	0x0000, 0x0808, 0x0000, 0x0006, /* 0x70 */
+	0x0000, 0x0000, 0x574d, 0x4c05, /* 0x78 */
+};
+
+static const struct snd_kcontrol_new wm9705_snd_ac97_controls[] = {
+	SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+	SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1),
+	SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+	SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
+	SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
+	SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1),
+	SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
+	SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+	SOC_SINGLE("PCBeep Playback Volume", AC97_PC_BEEP, 1, 15, 1),
+	SOC_SINGLE("Phone Playback Volume", AC97_PHONE, 0, 31, 1),
+	SOC_DOUBLE("Line Playback Volume", AC97_LINE, 8, 0, 31, 1),
+	SOC_DOUBLE("CD Playback Volume", AC97_CD, 8, 0, 31, 1),
+	SOC_SINGLE("Mic Playback Volume", AC97_MIC, 0, 31, 1),
+	SOC_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 6, 1, 0),
+	SOC_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 15, 0),
+	SOC_SINGLE("Capture Switch", AC97_REC_GAIN, 15, 1, 1),
+};
+
+static const char *wm9705_mic[] = {"Mic 1", "Mic 2"};
+static const char *wm9705_rec_sel[] = {"Mic", "CD", "NC", "NC",
+	"Line", "Stereo Mix", "Mono Mix", "Phone"};
+
+static const struct soc_enum wm9705_enum_mic =
+	SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, wm9705_mic);
+static const struct soc_enum wm9705_enum_rec_l =
+	SOC_ENUM_SINGLE(AC97_REC_SEL, 8, 8, wm9705_rec_sel);
+static const struct soc_enum wm9705_enum_rec_r =
+	SOC_ENUM_SINGLE(AC97_REC_SEL, 0, 8, wm9705_rec_sel);
+
+/* Headphone Mixer */
+static const struct snd_kcontrol_new wm9705_hp_mixer_controls[] = {
+	SOC_DAPM_SINGLE("PCBeep Playback Switch", AC97_PC_BEEP, 15, 1, 1),
+	SOC_DAPM_SINGLE("CD Playback Switch", AC97_CD, 15, 1, 1),
+	SOC_DAPM_SINGLE("Mic Playback Switch", AC97_MIC, 15, 1, 1),
+	SOC_DAPM_SINGLE("Phone Playback Switch", AC97_PHONE, 15, 1, 1),
+	SOC_DAPM_SINGLE("Line Playback Switch", AC97_LINE, 15, 1, 1),
+};
+
+/* Mic source */
+static const struct snd_kcontrol_new wm9705_mic_src_controls =
+	SOC_DAPM_ENUM("Route", wm9705_enum_mic);
+
+/* Capture source */
+static const struct snd_kcontrol_new wm9705_capture_selectl_controls =
+	SOC_DAPM_ENUM("Route", wm9705_enum_rec_l);
+static const struct snd_kcontrol_new wm9705_capture_selectr_controls =
+	SOC_DAPM_ENUM("Route", wm9705_enum_rec_r);
+
+/* DAPM widgets */
+static const struct snd_soc_dapm_widget wm9705_dapm_widgets[] = {
+	SND_SOC_DAPM_MUX("Mic Source", SND_SOC_NOPM, 0, 0,
+		&wm9705_mic_src_controls),
+	SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0,
+		&wm9705_capture_selectl_controls),
+	SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0,
+		&wm9705_capture_selectr_controls),
+	SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback",
+		SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback",
+		SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_MIXER_NAMED_CTL("HP Mixer", SND_SOC_NOPM, 0, 0,
+		&wm9705_hp_mixer_controls[0],
+		ARRAY_SIZE(wm9705_hp_mixer_controls)),
+	SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_PGA("Headphone PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Speaker PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Line PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Line out PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Mono PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Phone PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("Mic PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("PCBEEP PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("CD PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("ADC PGA", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_OUTPUT("HPOUTL"),
+	SND_SOC_DAPM_OUTPUT("HPOUTR"),
+	SND_SOC_DAPM_OUTPUT("LOUT"),
+	SND_SOC_DAPM_OUTPUT("ROUT"),
+	SND_SOC_DAPM_OUTPUT("MONOOUT"),
+	SND_SOC_DAPM_INPUT("PHONE"),
+	SND_SOC_DAPM_INPUT("LINEINL"),
+	SND_SOC_DAPM_INPUT("LINEINR"),
+	SND_SOC_DAPM_INPUT("CDINL"),
+	SND_SOC_DAPM_INPUT("CDINR"),
+	SND_SOC_DAPM_INPUT("PCBEEP"),
+	SND_SOC_DAPM_INPUT("MIC1"),
+	SND_SOC_DAPM_INPUT("MIC2"),
+};
+
+/* Audio map
+ * WM9705 has no switches to disable the route from the inputs to the HP mixer
+ * so in order to prevent active inputs from forcing the audio outputs to be
+ * constantly enabled, we use the mutes on those inputs to simulate such
+ * controls.
+ */
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* HP mixer */
+	{"HP Mixer", "PCBeep Playback Switch", "PCBEEP PGA"},
+	{"HP Mixer", "CD Playback Switch", "CD PGA"},
+	{"HP Mixer", "Mic Playback Switch", "Mic PGA"},
+	{"HP Mixer", "Phone Playback Switch", "Phone PGA"},
+	{"HP Mixer", "Line Playback Switch", "Line PGA"},
+	{"HP Mixer", NULL, "Left DAC"},
+	{"HP Mixer", NULL, "Right DAC"},
+
+	/* mono mixer */
+	{"Mono Mixer", NULL, "HP Mixer"},
+
+	/* outputs */
+	{"Headphone PGA", NULL, "HP Mixer"},
+	{"HPOUTL", NULL, "Headphone PGA"},
+	{"HPOUTR", NULL, "Headphone PGA"},
+	{"Line out PGA", NULL, "HP Mixer"},
+	{"LOUT", NULL, "Line out PGA"},
+	{"ROUT", NULL, "Line out PGA"},
+	{"Mono PGA", NULL, "Mono Mixer"},
+	{"MONOOUT", NULL, "Mono PGA"},
+
+	/* inputs */
+	{"CD PGA", NULL, "CDINL"},
+	{"CD PGA", NULL, "CDINR"},
+	{"Line PGA", NULL, "LINEINL"},
+	{"Line PGA", NULL, "LINEINR"},
+	{"Phone PGA", NULL, "PHONE"},
+	{"Mic Source", "Mic 1", "MIC1"},
+	{"Mic Source", "Mic 2", "MIC2"},
+	{"Mic PGA", NULL, "Mic Source"},
+	{"PCBEEP PGA", NULL, "PCBEEP"},
+
+	/* Left capture selector */
+	{"Left Capture Source", "Mic", "Mic Source"},
+	{"Left Capture Source", "CD", "CDINL"},
+	{"Left Capture Source", "Line", "LINEINL"},
+	{"Left Capture Source", "Stereo Mix", "HP Mixer"},
+	{"Left Capture Source", "Mono Mix", "HP Mixer"},
+	{"Left Capture Source", "Phone", "PHONE"},
+
+	/* Right capture source */
+	{"Right Capture Source", "Mic", "Mic Source"},
+	{"Right Capture Source", "CD", "CDINR"},
+	{"Right Capture Source", "Line", "LINEINR"},
+	{"Right Capture Source", "Stereo Mix", "HP Mixer"},
+	{"Right Capture Source", "Mono Mix", "HP Mixer"},
+	{"Right Capture Source", "Phone", "PHONE"},
+
+	{"ADC PGA", NULL, "Left Capture Source"},
+	{"ADC PGA", NULL, "Right Capture Source"},
+
+	/* ADC's */
+	{"Left ADC",  NULL, "ADC PGA"},
+	{"Right ADC", NULL, "ADC PGA"},
+};
+
+static int wm9705_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm9705_dapm_widgets,
+					ARRAY_SIZE(wm9705_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+/* We use a register cache to enhance read performance. */
+static unsigned int ac97_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+
+	switch (reg) {
+	case AC97_RESET:
+	case AC97_VENDOR_ID1:
+	case AC97_VENDOR_ID2:
+		return soc_ac97_ops.read(codec->ac97, reg);
+	default:
+		reg = reg >> 1;
+
+		if (reg >= (ARRAY_SIZE(wm9705_reg)))
+			return -EIO;
+
+		return cache[reg];
+	}
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int val)
+{
+	u16 *cache = codec->reg_cache;
+
+	soc_ac97_ops.write(codec->ac97, reg, val);
+	reg = reg >> 1;
+	if (reg < (ARRAY_SIZE(wm9705_reg)))
+		cache[reg] = val;
+
+	return 0;
+}
+
+static int ac97_prepare(struct snd_pcm_substream *substream,
+			struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	int reg;
+	u16 vra;
+
+	vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+	ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		reg = AC97_PCM_FRONT_DAC_RATE;
+	else
+		reg = AC97_PCM_LR_ADC_RATE;
+
+	return ac97_write(codec, reg, runtime->rate);
+}
+
+#define WM9705_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
+			SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+			SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+			SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops wm9705_dai_ops = {
+	.prepare	= ac97_prepare,
+};
+
+static struct snd_soc_dai_driver wm9705_dai[] = {
+	{
+		.name = "wm9705-hifi",
+		.ac97_control = 1,
+		.playback = {
+			.stream_name = "HiFi Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = WM9705_AC97_RATES,
+			.formats = SND_SOC_STD_AC97_FMTS,
+		},
+		.capture = {
+			.stream_name = "HiFi Capture",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = WM9705_AC97_RATES,
+			.formats = SND_SOC_STD_AC97_FMTS,
+		},
+		.ops = &wm9705_dai_ops,
+	},
+	{
+		.name = "wm9705-aux",
+		.playback = {
+			.stream_name = "Aux Playback",
+			.channels_min = 1,
+			.channels_max = 1,
+			.rates = WM9705_AC97_RATES,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE,
+		},
+	}
+};
+
+static int wm9705_reset(struct snd_soc_codec *codec)
+{
+	if (soc_ac97_ops.reset) {
+		soc_ac97_ops.reset(codec->ac97);
+		if (ac97_read(codec, 0) == wm9705_reg[0])
+			return 0; /* Success */
+	}
+
+	return -EIO;
+}
+
+#ifdef CONFIG_PM
+static int wm9705_soc_suspend(struct snd_soc_codec *codec, pm_message_t msg)
+{
+	soc_ac97_ops.write(codec->ac97, AC97_POWERDOWN, 0xffff);
+
+	return 0;
+}
+
+static int wm9705_soc_resume(struct snd_soc_codec *codec)
+{
+	int i, ret;
+	u16 *cache = codec->reg_cache;
+
+	ret = wm9705_reset(codec);
+	if (ret < 0) {
+		printk(KERN_ERR "could not reset AC97 codec\n");
+		return ret;
+	}
+
+	for (i = 2; i < ARRAY_SIZE(wm9705_reg) << 1; i += 2) {
+		soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
+	}
+
+	return 0;
+}
+#else
+#define wm9705_soc_suspend NULL
+#define wm9705_soc_resume NULL
+#endif
+
+static int wm9705_soc_probe(struct snd_soc_codec *codec)
+{
+	int ret = 0;
+
+	printk(KERN_INFO "WM9705 SoC Audio Codec\n");
+
+	ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+	if (ret < 0) {
+		printk(KERN_ERR "wm9705: failed to register AC97 codec\n");
+		return ret;
+	}
+
+	ret = wm9705_reset(codec);
+	if (ret)
+		goto reset_err;
+
+	snd_soc_add_controls(codec, wm9705_snd_ac97_controls,
+				ARRAY_SIZE(wm9705_snd_ac97_controls));
+	wm9705_add_widgets(codec);
+
+	return 0;
+
+reset_err:
+	snd_soc_free_ac97_codec(codec);
+	return ret;
+}
+
+static int wm9705_soc_remove(struct snd_soc_codec *codec)
+{
+	snd_soc_free_ac97_codec(codec);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm9705 = {
+	.probe = 	wm9705_soc_probe,
+	.remove = 	wm9705_soc_remove,
+	.suspend =	wm9705_soc_suspend,
+	.resume =	wm9705_soc_resume,
+	.read = ac97_read,
+	.write = ac97_write,
+	.reg_cache_size = ARRAY_SIZE(wm9705_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_step = 2,
+	.reg_cache_default = wm9705_reg,
+};
+
+static __devinit int wm9705_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_wm9705, wm9705_dai, ARRAY_SIZE(wm9705_dai));
+}
+
+static int __devexit wm9705_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver wm9705_codec_driver = {
+	.driver = {
+			.name = "wm9705-codec",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = wm9705_probe,
+	.remove = __devexit_p(wm9705_remove),
+};
+
+static int __init wm9705_init(void)
+{
+	return platform_driver_register(&wm9705_codec_driver);
+}
+module_init(wm9705_init);
+
+static void __exit wm9705_exit(void)
+{
+	platform_driver_unregister(&wm9705_codec_driver);
+}
+module_exit(wm9705_exit);
+
+MODULE_DESCRIPTION("ASoC WM9705 driver");
+MODULE_AUTHOR("Ian Molton");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/codecs/wm9705.h b/linux/sound/soc/codecs/wm9705.h
new file mode 100644
index 0000000..23ea9ce
--- /dev/null
+++ b/linux/sound/soc/codecs/wm9705.h
@@ -0,0 +1,11 @@
+/*
+ * wm9705.h  --  WM9705 Soc Audio driver
+ */
+
+#ifndef _WM9705_H
+#define _WM9705_H
+
+#define WM9705_DAI_AC97_HIFI	0
+#define WM9705_DAI_AC97_AUX	1
+
+#endif
diff --git a/linux/sound/soc/codecs/wm9712.c b/linux/sound/soc/codecs/wm9712.c
new file mode 100644
index 0000000..d2f224d
--- /dev/null
+++ b/linux/sound/soc/codecs/wm9712.c
@@ -0,0 +1,719 @@
+/*
+ * wm9712.c  --  ALSA Soc WM9712 codec support
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include "wm9712.h"
+
+#define WM9712_VERSION "0.4"
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+	unsigned int reg);
+static int ac97_write(struct snd_soc_codec *codec,
+	unsigned int reg, unsigned int val);
+
+/*
+ * WM9712 register cache
+ */
+static const u16 wm9712_reg[] = {
+	0x6174, 0x8000, 0x8000, 0x8000, /*  6 */
+	0x0f0f, 0xaaa0, 0xc008, 0x6808, /*  e */
+	0xe808, 0xaaa0, 0xad00, 0x8000, /* 16 */
+	0xe808, 0x3000, 0x8000, 0x0000, /* 1e */
+	0x0000, 0x0000, 0x0000, 0x000f, /* 26 */
+	0x0405, 0x0410, 0xbb80, 0xbb80, /* 2e */
+	0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */
+	0x0000, 0x2000, 0x0000, 0x0000, /* 3e */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 46 */
+	0x0000, 0x0000, 0xf83e, 0xffff, /* 4e */
+	0x0000, 0x0000, 0x0000, 0xf83e, /* 56 */
+	0x0008, 0x0000, 0x0000, 0x0000, /* 5e */
+	0xb032, 0x3e00, 0x0000, 0x0000, /* 66 */
+	0x0000, 0x0000, 0x0000, 0x0000, /* 6e */
+	0x0000, 0x0000, 0x0000, 0x0006, /* 76 */
+	0x0001, 0x0000, 0x574d, 0x4c12, /* 7e */
+	0x0000, 0x0000 /* virtual hp mixers */
+};
+
+/* virtual HP mixers regs */
+#define HPL_MIXER	0x80
+#define HPR_MIXER	0x82
+
+static const char *wm9712_alc_select[] = {"None", "Left", "Right", "Stereo"};
+static const char *wm9712_alc_mux[] = {"Stereo", "Left", "Right", "None"};
+static const char *wm9712_out3_src[] = {"Left", "VREF", "Left + Right",
+	"Mono"};
+static const char *wm9712_spk_src[] = {"Speaker Mix", "Headphone Mix"};
+static const char *wm9712_rec_adc[] = {"Stereo", "Left", "Right", "Mute"};
+static const char *wm9712_base[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm9712_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
+static const char *wm9712_mic[] = {"Mic 1", "Differential", "Mic 2",
+	"Stereo"};
+static const char *wm9712_rec_sel[] = {"Mic", "NC", "NC", "Speaker Mixer",
+	"Line", "Headphone Mixer", "Phone Mixer", "Phone"};
+static const char *wm9712_ng_type[] = {"Constant Gain", "Mute"};
+static const char *wm9712_diff_sel[] = {"Mic", "Line"};
+
+static const struct soc_enum wm9712_enum[] = {
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9712_alc_select),
+SOC_ENUM_SINGLE(AC97_VIDEO, 12, 4, wm9712_alc_mux),
+SOC_ENUM_SINGLE(AC97_AUX, 9, 4, wm9712_out3_src),
+SOC_ENUM_SINGLE(AC97_AUX, 8, 2, wm9712_spk_src),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 12, 4, wm9712_rec_adc),
+SOC_ENUM_SINGLE(AC97_MASTER_TONE, 15, 2, wm9712_base),
+SOC_ENUM_DOUBLE(AC97_REC_GAIN, 14, 6, 2, wm9712_rec_gain),
+SOC_ENUM_SINGLE(AC97_MIC, 5, 4, wm9712_mic),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 8, 8, wm9712_rec_sel),
+SOC_ENUM_SINGLE(AC97_REC_SEL, 0, 8, wm9712_rec_sel),
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9712_ng_type),
+SOC_ENUM_SINGLE(0x5c, 8, 2, wm9712_diff_sel),
+};
+
+static const struct snd_kcontrol_new wm9712_snd_ac97_controls[] = {
+SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1),
+SOC_SINGLE("Speaker Playback Switch", AC97_MASTER, 15, 1, 1),
+SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1),
+SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1),
+SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1),
+
+SOC_SINGLE("Speaker Playback ZC Switch", AC97_MASTER, 7, 1, 0),
+SOC_SINGLE("Speaker Playback Invert Switch", AC97_MASTER, 6, 1, 0),
+SOC_SINGLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 7, 1, 0),
+SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_MONO, 7, 1, 0),
+SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+
+SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
+SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
+SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0),
+SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
+SOC_ENUM("ALC Function", wm9712_enum[0]),
+SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0),
+SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 1),
+SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
+SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
+SOC_ENUM("ALC NG Type", wm9712_enum[10]),
+SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 1),
+
+SOC_SINGLE("Mic Headphone  Volume", AC97_VIDEO, 12, 7, 1),
+SOC_SINGLE("ALC Headphone Volume", AC97_VIDEO, 7, 7, 1),
+
+SOC_SINGLE("Out3 Switch", AC97_AUX, 15, 1, 1),
+SOC_SINGLE("Out3 ZC Switch", AC97_AUX, 7, 1, 1),
+SOC_SINGLE("Out3 Volume", AC97_AUX, 0, 31, 1),
+
+SOC_SINGLE("PCBeep Bypass Headphone Volume", AC97_PC_BEEP, 12, 7, 1),
+SOC_SINGLE("PCBeep Bypass Speaker Volume", AC97_PC_BEEP, 8, 7, 1),
+SOC_SINGLE("PCBeep Bypass Phone Volume", AC97_PC_BEEP, 4, 7, 1),
+
+SOC_SINGLE("Aux Playback Headphone Volume", AC97_CD, 12, 7, 1),
+SOC_SINGLE("Aux Playback Speaker Volume", AC97_CD, 8, 7, 1),
+SOC_SINGLE("Aux Playback Phone Volume", AC97_CD, 4, 7, 1),
+
+SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 1),
+SOC_DOUBLE("Line Capture Volume", AC97_LINE, 8, 0, 31, 1),
+
+SOC_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0),
+SOC_SINGLE("Capture to Phone 20dB Boost Switch", AC97_REC_SEL, 11, 1, 1),
+
+SOC_SINGLE("3D Upper Cut-off Switch", AC97_3D_CONTROL, 5, 1, 1),
+SOC_SINGLE("3D Lower Cut-off Switch", AC97_3D_CONTROL, 4, 1, 1),
+SOC_SINGLE("3D Playback Volume", AC97_3D_CONTROL, 0, 15, 0),
+
+SOC_ENUM("Bass Control", wm9712_enum[5]),
+SOC_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1),
+SOC_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1),
+SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0),
+SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 1),
+SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 1),
+
+SOC_SINGLE("Capture ADC Switch", AC97_REC_GAIN, 15, 1, 1),
+SOC_ENUM("Capture Volume Steps", wm9712_enum[6]),
+SOC_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 63, 1),
+SOC_SINGLE("Capture ZC Switch", AC97_REC_GAIN, 7, 1, 0),
+
+SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1),
+SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1),
+SOC_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 7, 1, 0),
+};
+
+/* We have to create a fake left and right HP mixers because
+ * the codec only has a single control that is shared by both channels.
+ * This makes it impossible to determine the audio path.
+ */
+static int mixer_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *k, int event)
+{
+	u16 l, r, beep, line, phone, mic, pcm, aux;
+
+	l = ac97_read(w->codec, HPL_MIXER);
+	r = ac97_read(w->codec, HPR_MIXER);
+	beep = ac97_read(w->codec, AC97_PC_BEEP);
+	mic = ac97_read(w->codec, AC97_VIDEO);
+	phone = ac97_read(w->codec, AC97_PHONE);
+	line = ac97_read(w->codec, AC97_LINE);
+	pcm = ac97_read(w->codec, AC97_PCM);
+	aux = ac97_read(w->codec, AC97_CD);
+
+	if (l & 0x1 || r & 0x1)
+		ac97_write(w->codec, AC97_VIDEO, mic & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_VIDEO, mic | 0x8000);
+
+	if (l & 0x2 || r & 0x2)
+		ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
+
+	if (l & 0x4 || r & 0x4)
+		ac97_write(w->codec, AC97_LINE, line & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_LINE, line | 0x8000);
+
+	if (l & 0x8 || r & 0x8)
+		ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
+
+	if (l & 0x10 || r & 0x10)
+		ac97_write(w->codec, AC97_CD, aux & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_CD, aux | 0x8000);
+
+	if (l & 0x20 || r & 0x20)
+		ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
+
+	return 0;
+}
+
+/* Left Headphone Mixers */
+static const struct snd_kcontrol_new wm9712_hpl_mixer_controls[] = {
+	SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPL_MIXER, 5, 1, 0),
+	SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 4, 1, 0),
+	SOC_DAPM_SINGLE("Phone Bypass Switch", HPL_MIXER, 3, 1, 0),
+	SOC_DAPM_SINGLE("Line Bypass Switch", HPL_MIXER, 2, 1, 0),
+	SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 1, 1, 0),
+	SOC_DAPM_SINGLE("Mic Sidetone Switch", HPL_MIXER, 0, 1, 0),
+};
+
+/* Right Headphone Mixers */
+static const struct snd_kcontrol_new wm9712_hpr_mixer_controls[] = {
+	SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPR_MIXER, 5, 1, 0),
+	SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 4, 1, 0),
+	SOC_DAPM_SINGLE("Phone Bypass Switch", HPR_MIXER, 3, 1, 0),
+	SOC_DAPM_SINGLE("Line Bypass Switch", HPR_MIXER, 2, 1, 0),
+	SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 1, 1, 0),
+	SOC_DAPM_SINGLE("Mic Sidetone Switch", HPR_MIXER, 0, 1, 0),
+};
+
+/* Speaker Mixer */
+static const struct snd_kcontrol_new wm9712_speaker_mixer_controls[] = {
+	SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 11, 1, 1),
+	SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 11, 1, 1),
+	SOC_DAPM_SINGLE("Phone Bypass Switch", AC97_PHONE, 14, 1, 1),
+	SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 14, 1, 1),
+	SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 14, 1, 1),
+};
+
+/* Phone Mixer */
+static const struct snd_kcontrol_new wm9712_phone_mixer_controls[] = {
+	SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 7, 1, 1),
+	SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 7, 1, 1),
+	SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 13, 1, 1),
+	SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 13, 1, 1),
+	SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_MIC, 14, 1, 1),
+	SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_MIC, 13, 1, 1),
+};
+
+/* ALC headphone mux */
+static const struct snd_kcontrol_new wm9712_alc_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[1]);
+
+/* out 3 mux */
+static const struct snd_kcontrol_new wm9712_out3_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[2]);
+
+/* spk mux */
+static const struct snd_kcontrol_new wm9712_spk_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[3]);
+
+/* Capture to Phone mux */
+static const struct snd_kcontrol_new wm9712_capture_phone_mux_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[4]);
+
+/* Capture left select */
+static const struct snd_kcontrol_new wm9712_capture_selectl_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[8]);
+
+/* Capture right select */
+static const struct snd_kcontrol_new wm9712_capture_selectr_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[9]);
+
+/* Mic select */
+static const struct snd_kcontrol_new wm9712_mic_src_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[7]);
+
+/* diff select */
+static const struct snd_kcontrol_new wm9712_diff_sel_controls =
+SOC_DAPM_ENUM("Route", wm9712_enum[11]);
+
+static const struct snd_soc_dapm_widget wm9712_dapm_widgets[] = {
+SND_SOC_DAPM_MUX("ALC Sidetone Mux", SND_SOC_NOPM, 0, 0,
+	&wm9712_alc_mux_controls),
+SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0,
+	&wm9712_out3_mux_controls),
+SND_SOC_DAPM_MUX("Speaker Mux", SND_SOC_NOPM, 0, 0,
+	&wm9712_spk_mux_controls),
+SND_SOC_DAPM_MUX("Capture Phone Mux", SND_SOC_NOPM, 0, 0,
+	&wm9712_capture_phone_mux_controls),
+SND_SOC_DAPM_MUX("Left Capture Select", SND_SOC_NOPM, 0, 0,
+	&wm9712_capture_selectl_controls),
+SND_SOC_DAPM_MUX("Right Capture Select", SND_SOC_NOPM, 0, 0,
+	&wm9712_capture_selectr_controls),
+SND_SOC_DAPM_MUX("Mic Select Source", SND_SOC_NOPM, 0, 0,
+	&wm9712_mic_src_controls),
+SND_SOC_DAPM_MUX("Differential Source", SND_SOC_NOPM, 0, 0,
+	&wm9712_diff_sel_controls),
+SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_INT_PAGING, 9, 1,
+	&wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls),
+	mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_INT_PAGING, 8, 1,
+	&wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls),
+	 mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER("Phone Mixer", AC97_INT_PAGING, 6, 1,
+	&wm9712_phone_mixer_controls[0], ARRAY_SIZE(wm9712_phone_mixer_controls)),
+SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_INT_PAGING, 7, 1,
+	&wm9712_speaker_mixer_controls[0],
+	ARRAY_SIZE(wm9712_speaker_mixer_controls)),
+SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_INT_PAGING, 14, 1),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_INT_PAGING, 13, 1),
+SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", AC97_INT_PAGING, 12, 1),
+SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", AC97_INT_PAGING, 11, 1),
+SND_SOC_DAPM_PGA("Headphone PGA", AC97_INT_PAGING, 4, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Speaker PGA", AC97_INT_PAGING, 3, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 3 PGA", AC97_INT_PAGING, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Line PGA", AC97_INT_PAGING, 2, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Phone PGA", AC97_INT_PAGING, 1, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic PGA", AC97_INT_PAGING, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_INT_PAGING, 10, 1),
+SND_SOC_DAPM_OUTPUT("MONOOUT"),
+SND_SOC_DAPM_OUTPUT("HPOUTL"),
+SND_SOC_DAPM_OUTPUT("HPOUTR"),
+SND_SOC_DAPM_OUTPUT("LOUT2"),
+SND_SOC_DAPM_OUTPUT("ROUT2"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_INPUT("LINEINL"),
+SND_SOC_DAPM_INPUT("LINEINR"),
+SND_SOC_DAPM_INPUT("PHONE"),
+SND_SOC_DAPM_INPUT("PCBEEP"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* virtual mixer - mixes left & right channels for spk and mono */
+	{"AC97 Mixer", NULL, "Left DAC"},
+	{"AC97 Mixer", NULL, "Right DAC"},
+
+	/* Left HP mixer */
+	{"Left HP Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+	{"Left HP Mixer", "Aux Playback Switch",  "Aux DAC"},
+	{"Left HP Mixer", "Phone Bypass Switch",  "Phone PGA"},
+	{"Left HP Mixer", "Line Bypass Switch",   "Line PGA"},
+	{"Left HP Mixer", "PCM Playback Switch",  "Left DAC"},
+	{"Left HP Mixer", "Mic Sidetone Switch",  "Mic PGA"},
+	{"Left HP Mixer", NULL,  "ALC Sidetone Mux"},
+
+	/* Right HP mixer */
+	{"Right HP Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+	{"Right HP Mixer", "Aux Playback Switch",  "Aux DAC"},
+	{"Right HP Mixer", "Phone Bypass Switch",  "Phone PGA"},
+	{"Right HP Mixer", "Line Bypass Switch",   "Line PGA"},
+	{"Right HP Mixer", "PCM Playback Switch",  "Right DAC"},
+	{"Right HP Mixer", "Mic Sidetone Switch",  "Mic PGA"},
+	{"Right HP Mixer", NULL,  "ALC Sidetone Mux"},
+
+	/* speaker mixer */
+	{"Speaker Mixer", "PCBeep Bypass Switch", "PCBEEP"},
+	{"Speaker Mixer", "Line Bypass Switch",   "Line PGA"},
+	{"Speaker Mixer", "PCM Playback Switch",  "AC97 Mixer"},
+	{"Speaker Mixer", "Phone Bypass Switch",  "Phone PGA"},
+	{"Speaker Mixer", "Aux Playback Switch",  "Aux DAC"},
+
+	/* Phone mixer */
+	{"Phone Mixer", "PCBeep Bypass Switch",  "PCBEEP"},
+	{"Phone Mixer", "Line Bypass Switch",    "Line PGA"},
+	{"Phone Mixer", "Aux Playback Switch",   "Aux DAC"},
+	{"Phone Mixer", "PCM Playback Switch",   "AC97 Mixer"},
+	{"Phone Mixer", "Mic 1 Sidetone Switch", "Mic PGA"},
+	{"Phone Mixer", "Mic 2 Sidetone Switch", "Mic PGA"},
+
+	/* inputs */
+	{"Line PGA", NULL, "LINEINL"},
+	{"Line PGA", NULL, "LINEINR"},
+	{"Phone PGA", NULL, "PHONE"},
+	{"Mic PGA", NULL, "MIC1"},
+	{"Mic PGA", NULL, "MIC2"},
+
+	/* left capture selector */
+	{"Left Capture Select", "Mic", "MIC1"},
+	{"Left Capture Select", "Speaker Mixer", "Speaker Mixer"},
+	{"Left Capture Select", "Line", "LINEINL"},
+	{"Left Capture Select", "Headphone Mixer", "Left HP Mixer"},
+	{"Left Capture Select", "Phone Mixer", "Phone Mixer"},
+	{"Left Capture Select", "Phone", "PHONE"},
+
+	/* right capture selector */
+	{"Right Capture Select", "Mic", "MIC2"},
+	{"Right Capture Select", "Speaker Mixer", "Speaker Mixer"},
+	{"Right Capture Select", "Line", "LINEINR"},
+	{"Right Capture Select", "Headphone Mixer", "Right HP Mixer"},
+	{"Right Capture Select", "Phone Mixer", "Phone Mixer"},
+	{"Right Capture Select", "Phone", "PHONE"},
+
+	/* ALC Sidetone */
+	{"ALC Sidetone Mux", "Stereo", "Left Capture Select"},
+	{"ALC Sidetone Mux", "Stereo", "Right Capture Select"},
+	{"ALC Sidetone Mux", "Left", "Left Capture Select"},
+	{"ALC Sidetone Mux", "Right", "Right Capture Select"},
+
+	/* ADC's */
+	{"Left ADC", NULL, "Left Capture Select"},
+	{"Right ADC", NULL, "Right Capture Select"},
+
+	/* outputs */
+	{"MONOOUT", NULL, "Phone Mixer"},
+	{"HPOUTL", NULL, "Headphone PGA"},
+	{"Headphone PGA", NULL, "Left HP Mixer"},
+	{"HPOUTR", NULL, "Headphone PGA"},
+	{"Headphone PGA", NULL, "Right HP Mixer"},
+
+	/* mono mixer */
+	{"Mono Mixer", NULL, "Left HP Mixer"},
+	{"Mono Mixer", NULL, "Right HP Mixer"},
+
+	/* Out3 Mux */
+	{"Out3 Mux", "Left", "Left HP Mixer"},
+	{"Out3 Mux", "Mono", "Phone Mixer"},
+	{"Out3 Mux", "Left + Right", "Mono Mixer"},
+	{"Out 3 PGA", NULL, "Out3 Mux"},
+	{"OUT3", NULL, "Out 3 PGA"},
+
+	/* speaker Mux */
+	{"Speaker Mux", "Speaker Mix", "Speaker Mixer"},
+	{"Speaker Mux", "Headphone Mix", "Mono Mixer"},
+	{"Speaker PGA", NULL, "Speaker Mux"},
+	{"LOUT2", NULL, "Speaker PGA"},
+	{"ROUT2", NULL, "Speaker PGA"},
+};
+
+static int wm9712_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm9712_dapm_widgets,
+				  ARRAY_SIZE(wm9712_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+
+	if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+		reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 ||
+		reg == AC97_REC_GAIN)
+		return soc_ac97_ops.read(codec->ac97, reg);
+	else {
+		reg = reg >> 1;
+
+		if (reg >= (ARRAY_SIZE(wm9712_reg)))
+			return -EIO;
+
+		return cache[reg];
+	}
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int val)
+{
+	u16 *cache = codec->reg_cache;
+
+	if (reg < 0x7c)
+		soc_ac97_ops.write(codec->ac97, reg, val);
+	reg = reg >> 1;
+	if (reg < (ARRAY_SIZE(wm9712_reg)))
+		cache[reg] = val;
+
+	return 0;
+}
+
+static int ac97_prepare(struct snd_pcm_substream *substream,
+			struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec =rtd->codec;
+	int reg;
+	u16 vra;
+
+	vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+	ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		reg = AC97_PCM_FRONT_DAC_RATE;
+	else
+		reg = AC97_PCM_LR_ADC_RATE;
+
+	return ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_aux_prepare(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+	u16 vra, xsle;
+
+	vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+	ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+	xsle = ac97_read(codec, AC97_PCI_SID);
+	ac97_write(codec, AC97_PCI_SID, xsle | 0x8000);
+
+	if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+		return -ENODEV;
+
+	return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate);
+}
+
+#define WM9712_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+		SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\
+		SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops wm9712_dai_ops_hifi = {
+	.prepare	= ac97_prepare,
+};
+
+static struct snd_soc_dai_ops wm9712_dai_ops_aux = {
+	.prepare	= ac97_aux_prepare,
+};
+
+static struct snd_soc_dai_driver wm9712_dai[] = {
+{
+	.name = "wm9712-hifi",
+	.ac97_control = 1,
+	.playback = {
+		.stream_name = "HiFi Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM9712_AC97_RATES,
+		.formats = SND_SOC_STD_AC97_FMTS,},
+	.capture = {
+		.stream_name = "HiFi Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM9712_AC97_RATES,
+		.formats = SND_SOC_STD_AC97_FMTS,},
+	.ops = &wm9712_dai_ops_hifi,
+},
+{
+	.name = "wm9712-aux",
+	.playback = {
+		.stream_name = "Aux Playback",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = WM9712_AC97_RATES,
+		.formats = SND_SOC_STD_AC97_FMTS,},
+	.ops = &wm9712_dai_ops_aux,
+}
+};
+
+static int wm9712_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		ac97_write(codec, AC97_POWERDOWN, 0x0000);
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* disable everything including AC link */
+		ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+		ac97_write(codec, AC97_POWERDOWN, 0xffff);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+static int wm9712_reset(struct snd_soc_codec *codec, int try_warm)
+{
+	if (try_warm && soc_ac97_ops.warm_reset) {
+		soc_ac97_ops.warm_reset(codec->ac97);
+		if (ac97_read(codec, 0) == wm9712_reg[0])
+			return 1;
+	}
+
+	soc_ac97_ops.reset(codec->ac97);
+	if (soc_ac97_ops.warm_reset)
+		soc_ac97_ops.warm_reset(codec->ac97);
+	if (ac97_read(codec, 0) != wm9712_reg[0])
+		goto err;
+	return 0;
+
+err:
+	printk(KERN_ERR "WM9712 AC97 reset failed\n");
+	return -EIO;
+}
+
+static int wm9712_soc_suspend(struct snd_soc_codec *codec,
+	pm_message_t state)
+{
+	wm9712_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int wm9712_soc_resume(struct snd_soc_codec *codec)
+{
+	int i, ret;
+	u16 *cache = codec->reg_cache;
+
+	ret = wm9712_reset(codec, 1);
+	if (ret < 0) {
+		printk(KERN_ERR "could not reset AC97 codec\n");
+		return ret;
+	}
+
+	wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	if (ret == 0) {
+		/* Sync reg_cache with the hardware after cold reset */
+		for (i = 2; i < ARRAY_SIZE(wm9712_reg) << 1; i += 2) {
+			if (i == AC97_INT_PAGING || i == AC97_POWERDOWN ||
+			    (i > 0x58 && i != 0x5c))
+				continue;
+			soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
+		}
+	}
+
+	return ret;
+}
+
+static int wm9712_soc_probe(struct snd_soc_codec *codec)
+{
+	int ret = 0;
+
+	printk(KERN_INFO "WM9711/WM9712 SoC Audio Codec %s\n", WM9712_VERSION);
+
+	ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+	if (ret < 0) {
+		printk(KERN_ERR "wm9712: failed to register AC97 codec\n");
+		return ret;
+	}
+
+	ret = wm9712_reset(codec, 0);
+	if (ret < 0) {
+		printk(KERN_ERR "Failed to reset WM9712: AC97 link error\n");
+		goto reset_err;
+	}
+
+	/* set alc mux to none */
+	ac97_write(codec, AC97_VIDEO, ac97_read(codec, AC97_VIDEO) | 0x3000);
+
+	wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	snd_soc_add_controls(codec, wm9712_snd_ac97_controls,
+				ARRAY_SIZE(wm9712_snd_ac97_controls));
+	wm9712_add_widgets(codec);
+
+	return 0;
+
+reset_err:
+	snd_soc_free_ac97_codec(codec);
+	return ret;
+}
+
+static int wm9712_soc_remove(struct snd_soc_codec *codec)
+{
+	snd_soc_free_ac97_codec(codec);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm9712 = {
+	.probe = 	wm9712_soc_probe,
+	.remove = 	wm9712_soc_remove,
+	.suspend =	wm9712_soc_suspend,
+	.resume =	wm9712_soc_resume,
+	.read = ac97_read,
+	.write = ac97_write,
+	.set_bias_level = wm9712_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm9712_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_step = 2,
+	.reg_cache_default = wm9712_reg,
+};
+
+static __devinit int wm9712_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_wm9712, wm9712_dai, ARRAY_SIZE(wm9712_dai));
+}
+
+static int __devexit wm9712_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver wm9712_codec_driver = {
+	.driver = {
+			.name = "wm9712-codec",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = wm9712_probe,
+	.remove = __devexit_p(wm9712_remove),
+};
+
+static int __init wm9712_init(void)
+{
+	return platform_driver_register(&wm9712_codec_driver);
+}
+module_init(wm9712_init);
+
+static void __exit wm9712_exit(void)
+{
+	platform_driver_unregister(&wm9712_codec_driver);
+}
+module_exit(wm9712_exit);
+
+MODULE_DESCRIPTION("ASoC WM9711/WM9712 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm9712.h b/linux/sound/soc/codecs/wm9712.h
new file mode 100644
index 0000000..fb69c3a
--- /dev/null
+++ b/linux/sound/soc/codecs/wm9712.h
@@ -0,0 +1,11 @@
+/*
+ * wm9712.h  --  WM9712 Soc Audio driver
+ */
+
+#ifndef _WM9712_H
+#define _WM9712_H
+
+#define WM9712_DAI_AC97_HIFI	0
+#define WM9712_DAI_AC97_AUX		1
+
+#endif
diff --git a/linux/sound/soc/codecs/wm9713.c b/linux/sound/soc/codecs/wm9713.c
new file mode 100644
index 0000000..7da13b0
--- /dev/null
+++ b/linux/sound/soc/codecs/wm9713.c
@@ -0,0 +1,1302 @@
+/*
+ * wm9713.c  --  ALSA Soc WM9713 codec support
+ *
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ *  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.
+ *
+ *  Features:-
+ *
+ *   o Support for AC97 Codec, Voice DAC and Aux DAC
+ *   o Support for DAPM
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "wm9713.h"
+
+struct wm9713_priv {
+	u32 pll_in; /* PLL input frequency */
+};
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+	unsigned int reg);
+static int ac97_write(struct snd_soc_codec *codec,
+	unsigned int reg, unsigned int val);
+
+/*
+ * WM9713 register cache
+ * Reg 0x3c bit 15 is used by touch driver.
+ */
+static const u16 wm9713_reg[] = {
+	0x6174, 0x8080, 0x8080, 0x8080,
+	0xc880, 0xe808, 0xe808, 0x0808,
+	0x00da, 0x8000, 0xd600, 0xaaa0,
+	0xaaa0, 0xaaa0, 0x0000, 0x0000,
+	0x0f0f, 0x0040, 0x0000, 0x7f00,
+	0x0405, 0x0410, 0xbb80, 0xbb80,
+	0x0000, 0xbb80, 0x0000, 0x4523,
+	0x0000, 0x2000, 0x7eff, 0xffff,
+	0x0000, 0x0000, 0x0080, 0x0000,
+	0x0000, 0x0000, 0xfffe, 0xffff,
+	0x0000, 0x0000, 0x0000, 0xfffe,
+	0x4000, 0x0000, 0x0000, 0x0000,
+	0xb032, 0x3e00, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0000,
+	0x0000, 0x0000, 0x0000, 0x0006,
+	0x0001, 0x0000, 0x574d, 0x4c13,
+	0x0000, 0x0000, 0x0000
+};
+
+/* virtual HP mixers regs */
+#define HPL_MIXER	0x80
+#define HPR_MIXER	0x82
+#define MICB_MUX	0x82
+
+static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"};
+static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"};
+static const char *wm9713_rec_src[] =
+	{"Mic 1", "Mic 2", "Line", "Mono In", "Headphone", "Speaker",
+	"Mono Out", "Zh"};
+static const char *wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"};
+static const char *wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"};
+static const char *wm9713_mono_pga[] = {"Vmid", "Zh", "Mono", "Inv",
+	"Mono Vmid", "Inv Vmid"};
+static const char *wm9713_spk_pga[] =
+	{"Vmid", "Zh", "Headphone", "Speaker", "Inv", "Headphone Vmid",
+	"Speaker Vmid", "Inv Vmid"};
+static const char *wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone",
+	"Headphone Vmid"};
+static const char *wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "Inv 1 Vmid"};
+static const char *wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "Inv 2 Vmid"};
+static const char *wm9713_dac_inv[] =
+	{"Off", "Mono", "Speaker", "Left Headphone", "Right Headphone",
+	"Headphone Mono", "NC", "Vmid"};
+static const char *wm9713_bass[] = {"Linear Control", "Adaptive Boost"};
+static const char *wm9713_ng_type[] = {"Constant Gain", "Mute"};
+static const char *wm9713_mic_select[] = {"Mic 1", "Mic 2 A", "Mic 2 B"};
+static const char *wm9713_micb_select[] = {"MPB", "MPA"};
+
+static const struct soc_enum wm9713_enum[] = {
+SOC_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), /* record mic mixer 0 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), /* record mux hp 1 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux),  /* record mux mono 2 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src),  /* record mux left 3 */
+SOC_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src),  /* record mux right 4*/
+SOC_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain), /* record step size 5 */
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select), /* alc source select 6*/
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga), /* mono input select 7 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 11, 8, wm9713_spk_pga), /* speaker left input select 8 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 8, 8, wm9713_spk_pga), /* speaker right input select 9 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 6, 3, wm9713_hp_pga), /* headphone left input 10 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 4, 3, wm9713_hp_pga), /* headphone right input 11 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga), /* out 3 source 12 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga), /* out 4 source 13 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 13, 8, wm9713_dac_inv), /* dac invert 1 14 */
+SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */
+SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */
+SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */
+SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */
+SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */
+};
+
+static const DECLARE_TLV_DB_SCALE(out_tlv, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(main_tlv, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(misc_tlv, -1500, 300, 0);
+static unsigned int mic_tlv[] = {
+	TLV_DB_RANGE_HEAD(2),
+	0, 2, TLV_DB_SCALE_ITEM(1200, 600, 0),
+	3, 3, TLV_DB_SCALE_ITEM(3000, 0, 0),
+};
+
+static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = {
+SOC_DOUBLE_TLV("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1, out_tlv),
+SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1),
+SOC_DOUBLE_TLV("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1,
+	       out_tlv),
+SOC_DOUBLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 7, 1, 1),
+SOC_DOUBLE_TLV("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1, main_tlv),
+SOC_DOUBLE_TLV("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1, main_tlv),
+SOC_SINGLE_TLV("Mic 1 Volume", AC97_MIC, 8, 31, 1, main_tlv),
+SOC_SINGLE_TLV("Mic 2 Volume", AC97_MIC, 0, 31, 1, main_tlv),
+SOC_SINGLE_TLV("Mic 1 Preamp Volume", AC97_3D_CONTROL, 10, 3, 0, mic_tlv),
+SOC_SINGLE_TLV("Mic 2 Preamp Volume", AC97_3D_CONTROL, 12, 3, 0, mic_tlv),
+
+SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0),
+SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1),
+
+SOC_SINGLE("Capture Switch", AC97_CD, 15, 1, 1),
+SOC_ENUM("Capture Volume Steps", wm9713_enum[5]),
+SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0),
+SOC_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0),
+
+SOC_SINGLE_TLV("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1, misc_tlv),
+SOC_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0),
+SOC_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0),
+
+SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0),
+SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),
+SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0),
+SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0),
+SOC_ENUM("ALC Function", wm9713_enum[6]),
+SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0),
+SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0),
+SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0),
+SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0),
+SOC_ENUM("ALC NG Type", wm9713_enum[17]),
+SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0),
+
+SOC_DOUBLE("Speaker Playback ZC Switch", AC97_MASTER, 14, 6, 1, 0),
+SOC_DOUBLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0),
+
+SOC_SINGLE("Out4 Playback Switch", AC97_MASTER_MONO, 15, 1, 1),
+SOC_SINGLE("Out4 Playback ZC Switch", AC97_MASTER_MONO, 14, 1, 0),
+SOC_SINGLE_TLV("Out4 Playback Volume", AC97_MASTER_MONO, 8, 31, 1, out_tlv),
+
+SOC_SINGLE("Out3 Playback Switch", AC97_MASTER_MONO, 7, 1, 1),
+SOC_SINGLE("Out3 Playback ZC Switch", AC97_MASTER_MONO, 6, 1, 0),
+SOC_SINGLE_TLV("Out3 Playback Volume", AC97_MASTER_MONO, 0, 31, 1, out_tlv),
+
+SOC_SINGLE_TLV("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1, main_tlv),
+SOC_SINGLE("Mono Playback Switch", AC97_MASTER_TONE, 7, 1, 1),
+SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_TONE, 6, 1, 0),
+SOC_SINGLE_TLV("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1, out_tlv),
+
+SOC_SINGLE_TLV("Headphone Mixer Beep Playback Volume", AC97_AUX, 12, 7, 1,
+	       misc_tlv),
+SOC_SINGLE_TLV("Speaker Mixer Beep Playback Volume", AC97_AUX, 8, 7, 1,
+	       misc_tlv),
+SOC_SINGLE_TLV("Mono Mixer Beep Playback Volume", AC97_AUX, 4, 7, 1, misc_tlv),
+
+SOC_SINGLE_TLV("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1,
+	       misc_tlv),
+SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1),
+SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1),
+
+SOC_SINGLE_TLV("Headphone Mixer Aux Playback Volume", AC97_REC_SEL, 12, 7, 1,
+	       misc_tlv),
+
+SOC_SINGLE_TLV("Speaker Mixer Voice Playback Volume", AC97_PCM, 8, 7, 1,
+	       misc_tlv),
+SOC_SINGLE_TLV("Speaker Mixer Aux Playback Volume", AC97_REC_SEL, 8, 7, 1,
+	       misc_tlv),
+
+SOC_SINGLE_TLV("Mono Mixer Voice Playback Volume", AC97_PCM, 4, 7, 1,
+	       misc_tlv),
+SOC_SINGLE_TLV("Mono Mixer Aux Playback Volume", AC97_REC_SEL, 4, 7, 1,
+	       misc_tlv),
+
+SOC_SINGLE("Aux Playback Headphone Volume", AC97_REC_SEL, 12, 7, 1),
+SOC_SINGLE("Aux Playback Master Volume", AC97_REC_SEL, 8, 7, 1),
+
+SOC_ENUM("Bass Control", wm9713_enum[16]),
+SOC_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1),
+SOC_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1),
+SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0),
+SOC_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1),
+SOC_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1),
+
+SOC_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0),
+SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0),
+SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1),
+};
+
+static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w,
+				 struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	u16 status, rate;
+
+	BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
+
+	/* Gracefully shut down the voice interface. */
+	status = ac97_read(codec, AC97_EXTENDED_MID) | 0x1000;
+	rate = ac97_read(codec, AC97_HANDSET_RATE) & 0xF0FF;
+	ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0200);
+	schedule_timeout_interruptible(msecs_to_jiffies(1));
+	ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0F00);
+	ac97_write(codec, AC97_EXTENDED_MID, status);
+
+	return 0;
+}
+
+
+/* We have to create a fake left and right HP mixers because
+ * the codec only has a single control that is shared by both channels.
+ * This makes it impossible to determine the audio path using the current
+ * register map, thus we add a new (virtual) register to help determine the
+ * audio route within the device.
+ */
+static int mixer_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event)
+{
+	u16 l, r, beep, tone, phone, rec, pcm, aux;
+
+	l = ac97_read(w->codec, HPL_MIXER);
+	r = ac97_read(w->codec, HPR_MIXER);
+	beep = ac97_read(w->codec, AC97_PC_BEEP);
+	tone = ac97_read(w->codec, AC97_MASTER_TONE);
+	phone = ac97_read(w->codec, AC97_PHONE);
+	rec = ac97_read(w->codec, AC97_REC_SEL);
+	pcm = ac97_read(w->codec, AC97_PCM);
+	aux = ac97_read(w->codec, AC97_AUX);
+
+	if (event & SND_SOC_DAPM_PRE_REG)
+		return 0;
+	if ((l & 0x1) || (r & 0x1))
+		ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000);
+
+	if ((l & 0x2) || (r & 0x2))
+		ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000);
+
+	if ((l & 0x4) || (r & 0x4))
+		ac97_write(w->codec, AC97_PHONE, phone & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_PHONE, phone | 0x8000);
+
+	if ((l & 0x8) || (r & 0x8))
+		ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000);
+
+	if ((l & 0x10) || (r & 0x10))
+		ac97_write(w->codec, AC97_PCM, pcm & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_PCM, pcm | 0x8000);
+
+	if ((l & 0x20) || (r & 0x20))
+		ac97_write(w->codec, AC97_AUX, aux & 0x7fff);
+	else
+		ac97_write(w->codec, AC97_AUX, aux | 0x8000);
+
+	return 0;
+}
+
+/* Left Headphone Mixers */
+static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = {
+SOC_DAPM_SINGLE("Beep Playback Switch", HPL_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0),
+};
+
+/* Right Headphone Mixers */
+static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = {
+SOC_DAPM_SINGLE("Beep Playback Switch", HPR_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0),
+SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0),
+SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0),
+};
+
+/* headphone capture mux */
+static const struct snd_kcontrol_new wm9713_hp_rec_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[1]);
+
+/* headphone mic mux */
+static const struct snd_kcontrol_new wm9713_hp_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[0]);
+
+/* Speaker Mixer */
+static const struct snd_kcontrol_new wm9713_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("Beep Playback Switch", AC97_AUX, 11, 1, 1),
+SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 11, 1, 1),
+SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 11, 1, 1),
+SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 14, 1, 1),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 14, 1, 1),
+SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 14, 1, 1),
+};
+
+/* Mono Mixer */
+static const struct snd_kcontrol_new wm9713_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("Beep Playback Switch", AC97_AUX, 7, 1, 1),
+SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 7, 1, 1),
+SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 7, 1, 1),
+SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 13, 1, 1),
+SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 13, 1, 1),
+SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 13, 1, 1),
+SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_LINE, 7, 1, 1),
+SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_LINE, 6, 1, 1),
+};
+
+/* mono mic mux */
+static const struct snd_kcontrol_new wm9713_mono_mic_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[2]);
+
+/* mono output mux */
+static const struct snd_kcontrol_new wm9713_mono_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[7]);
+
+/* speaker left output mux */
+static const struct snd_kcontrol_new wm9713_hp_spkl_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[8]);
+
+/* speaker right output mux */
+static const struct snd_kcontrol_new wm9713_hp_spkr_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[9]);
+
+/* headphone left output mux */
+static const struct snd_kcontrol_new wm9713_hpl_out_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[10]);
+
+/* headphone right output mux */
+static const struct snd_kcontrol_new wm9713_hpr_out_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[11]);
+
+/* Out3 mux */
+static const struct snd_kcontrol_new wm9713_out3_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[12]);
+
+/* Out4 mux */
+static const struct snd_kcontrol_new wm9713_out4_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[13]);
+
+/* DAC inv mux 1 */
+static const struct snd_kcontrol_new wm9713_dac_inv1_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[14]);
+
+/* DAC inv mux 2 */
+static const struct snd_kcontrol_new wm9713_dac_inv2_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[15]);
+
+/* Capture source left */
+static const struct snd_kcontrol_new wm9713_rec_srcl_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[3]);
+
+/* Capture source right */
+static const struct snd_kcontrol_new wm9713_rec_srcr_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[4]);
+
+/* mic source */
+static const struct snd_kcontrol_new wm9713_mic_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[18]);
+
+/* mic source B virtual control */
+static const struct snd_kcontrol_new wm9713_micb_sel_mux_controls =
+SOC_DAPM_ENUM("Route", wm9713_enum[19]);
+
+static const struct snd_soc_dapm_widget wm9713_dapm_widgets[] = {
+SND_SOC_DAPM_MUX("Capture Headphone Mux", SND_SOC_NOPM, 0, 0,
+	&wm9713_hp_rec_mux_controls),
+SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0,
+	&wm9713_hp_mic_mux_controls),
+SND_SOC_DAPM_MUX("Capture Mono Mux", SND_SOC_NOPM, 0, 0,
+	&wm9713_mono_mic_mux_controls),
+SND_SOC_DAPM_MUX("Mono Out Mux", SND_SOC_NOPM, 0, 0,
+	&wm9713_mono_mux_controls),
+SND_SOC_DAPM_MUX("Left Speaker Out Mux", SND_SOC_NOPM, 0, 0,
+	&wm9713_hp_spkl_mux_controls),
+SND_SOC_DAPM_MUX("Right Speaker Out Mux", SND_SOC_NOPM, 0, 0,
+	&wm9713_hp_spkr_mux_controls),
+SND_SOC_DAPM_MUX("Left Headphone Out Mux", SND_SOC_NOPM, 0, 0,
+	&wm9713_hpl_out_mux_controls),
+SND_SOC_DAPM_MUX("Right Headphone Out Mux", SND_SOC_NOPM, 0, 0,
+	&wm9713_hpr_out_mux_controls),
+SND_SOC_DAPM_MUX("Out 3 Mux", SND_SOC_NOPM, 0, 0,
+	&wm9713_out3_mux_controls),
+SND_SOC_DAPM_MUX("Out 4 Mux", SND_SOC_NOPM, 0, 0,
+	&wm9713_out4_mux_controls),
+SND_SOC_DAPM_MUX("DAC Inv Mux 1", SND_SOC_NOPM, 0, 0,
+	&wm9713_dac_inv1_mux_controls),
+SND_SOC_DAPM_MUX("DAC Inv Mux 2", SND_SOC_NOPM, 0, 0,
+	&wm9713_dac_inv2_mux_controls),
+SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0,
+	&wm9713_rec_srcl_mux_controls),
+SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0,
+	&wm9713_rec_srcr_mux_controls),
+SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0,
+	&wm9713_mic_sel_mux_controls),
+SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0,
+	&wm9713_micb_sel_mux_controls),
+SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1,
+	&wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls),
+	mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1,
+	&wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls),
+	mixer_event, SND_SOC_DAPM_POST_REG),
+SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1,
+	&wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)),
+SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1,
+	&wm9713_speaker_mixer_controls[0],
+	ARRAY_SIZE(wm9713_speaker_mixer_controls)),
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_EXTENDED_MID, 7, 1),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_EXTENDED_MID, 6, 1),
+SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_DAC_E("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1,
+		   wm9713_voice_shutdown, SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1),
+SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0),
+SND_SOC_DAPM_ADC("Left HiFi ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Right HiFi ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Left Voice ADC", "Left Voice Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_ADC("Right Voice ADC", "Right Voice Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker", AC97_EXTENDED_MSTATUS, 7, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 3", AC97_EXTENDED_MSTATUS, 11, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Out 4", AC97_EXTENDED_MSTATUS, 12, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mono Out", AC97_EXTENDED_MSTATUS, 13, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Left Line In", AC97_EXTENDED_MSTATUS, 6, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Right Line In", AC97_EXTENDED_MSTATUS, 5, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mono In", AC97_EXTENDED_MSTATUS, 4, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic A PGA", AC97_EXTENDED_MSTATUS, 3, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic B PGA", AC97_EXTENDED_MSTATUS, 2, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic A Pre Amp", AC97_EXTENDED_MSTATUS, 1, 1, NULL, 0),
+SND_SOC_DAPM_PGA("Mic B Pre Amp", AC97_EXTENDED_MSTATUS, 0, 1, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_EXTENDED_MSTATUS, 14, 1),
+SND_SOC_DAPM_OUTPUT("MONO"),
+SND_SOC_DAPM_OUTPUT("HPL"),
+SND_SOC_DAPM_OUTPUT("HPR"),
+SND_SOC_DAPM_OUTPUT("SPKL"),
+SND_SOC_DAPM_OUTPUT("SPKR"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_INPUT("LINEL"),
+SND_SOC_DAPM_INPUT("LINER"),
+SND_SOC_DAPM_INPUT("MONOIN"),
+SND_SOC_DAPM_INPUT("PCBEEP"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2A"),
+SND_SOC_DAPM_INPUT("MIC2B"),
+SND_SOC_DAPM_VMID("VMID"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* left HP mixer */
+	{"Left HP Mixer", "Beep Playback Switch",    "PCBEEP"},
+	{"Left HP Mixer", "Voice Playback Switch",   "Voice DAC"},
+	{"Left HP Mixer", "Aux Playback Switch",     "Aux DAC"},
+	{"Left HP Mixer", "Bypass Playback Switch",  "Left Line In"},
+	{"Left HP Mixer", "PCM Playback Switch",     "Left DAC"},
+	{"Left HP Mixer", "MonoIn Playback Switch",  "Mono In"},
+	{"Left HP Mixer", NULL,  "Capture Headphone Mux"},
+
+	/* right HP mixer */
+	{"Right HP Mixer", "Beep Playback Switch",    "PCBEEP"},
+	{"Right HP Mixer", "Voice Playback Switch",   "Voice DAC"},
+	{"Right HP Mixer", "Aux Playback Switch",     "Aux DAC"},
+	{"Right HP Mixer", "Bypass Playback Switch",  "Right Line In"},
+	{"Right HP Mixer", "PCM Playback Switch",     "Right DAC"},
+	{"Right HP Mixer", "MonoIn Playback Switch",  "Mono In"},
+	{"Right HP Mixer", NULL,  "Capture Headphone Mux"},
+
+	/* virtual mixer - mixes left & right channels for spk and mono */
+	{"AC97 Mixer", NULL, "Left DAC"},
+	{"AC97 Mixer", NULL, "Right DAC"},
+	{"Line Mixer", NULL, "Right Line In"},
+	{"Line Mixer", NULL, "Left Line In"},
+	{"HP Mixer", NULL, "Left HP Mixer"},
+	{"HP Mixer", NULL, "Right HP Mixer"},
+	{"Capture Mixer", NULL, "Left Capture Source"},
+	{"Capture Mixer", NULL, "Right Capture Source"},
+
+	/* speaker mixer */
+	{"Speaker Mixer", "Beep Playback Switch",    "PCBEEP"},
+	{"Speaker Mixer", "Voice Playback Switch",   "Voice DAC"},
+	{"Speaker Mixer", "Aux Playback Switch",     "Aux DAC"},
+	{"Speaker Mixer", "Bypass Playback Switch",  "Line Mixer"},
+	{"Speaker Mixer", "PCM Playback Switch",     "AC97 Mixer"},
+	{"Speaker Mixer", "MonoIn Playback Switch",  "Mono In"},
+
+	/* mono mixer */
+	{"Mono Mixer", "Beep Playback Switch",    "PCBEEP"},
+	{"Mono Mixer", "Voice Playback Switch",   "Voice DAC"},
+	{"Mono Mixer", "Aux Playback Switch",     "Aux DAC"},
+	{"Mono Mixer", "Bypass Playback Switch",  "Line Mixer"},
+	{"Mono Mixer", "PCM Playback Switch",     "AC97 Mixer"},
+	{"Mono Mixer", "Mic 1 Sidetone Switch", "Mic A PGA"},
+	{"Mono Mixer", "Mic 2 Sidetone Switch", "Mic B PGA"},
+	{"Mono Mixer", NULL,  "Capture Mono Mux"},
+
+	/* DAC inv mux 1 */
+	{"DAC Inv Mux 1", "Mono", "Mono Mixer"},
+	{"DAC Inv Mux 1", "Speaker", "Speaker Mixer"},
+	{"DAC Inv Mux 1", "Left Headphone", "Left HP Mixer"},
+	{"DAC Inv Mux 1", "Right Headphone", "Right HP Mixer"},
+	{"DAC Inv Mux 1", "Headphone Mono", "HP Mixer"},
+
+	/* DAC inv mux 2 */
+	{"DAC Inv Mux 2", "Mono", "Mono Mixer"},
+	{"DAC Inv Mux 2", "Speaker", "Speaker Mixer"},
+	{"DAC Inv Mux 2", "Left Headphone", "Left HP Mixer"},
+	{"DAC Inv Mux 2", "Right Headphone", "Right HP Mixer"},
+	{"DAC Inv Mux 2", "Headphone Mono", "HP Mixer"},
+
+	/* headphone left mux */
+	{"Left Headphone Out Mux", "Headphone", "Left HP Mixer"},
+
+	/* headphone right mux */
+	{"Right Headphone Out Mux", "Headphone", "Right HP Mixer"},
+
+	/* speaker left mux */
+	{"Left Speaker Out Mux", "Headphone", "Left HP Mixer"},
+	{"Left Speaker Out Mux", "Speaker", "Speaker Mixer"},
+	{"Left Speaker Out Mux", "Inv", "DAC Inv Mux 1"},
+
+	/* speaker right mux */
+	{"Right Speaker Out Mux", "Headphone", "Right HP Mixer"},
+	{"Right Speaker Out Mux", "Speaker", "Speaker Mixer"},
+	{"Right Speaker Out Mux", "Inv", "DAC Inv Mux 2"},
+
+	/* mono mux */
+	{"Mono Out Mux", "Mono", "Mono Mixer"},
+	{"Mono Out Mux", "Inv", "DAC Inv Mux 1"},
+
+	/* out 3 mux */
+	{"Out 3 Mux", "Inv 1", "DAC Inv Mux 1"},
+
+	/* out 4 mux */
+	{"Out 4 Mux", "Inv 2", "DAC Inv Mux 2"},
+
+	/* output pga */
+	{"HPL", NULL, "Left Headphone"},
+	{"Left Headphone", NULL, "Left Headphone Out Mux"},
+	{"HPR", NULL, "Right Headphone"},
+	{"Right Headphone", NULL, "Right Headphone Out Mux"},
+	{"OUT3", NULL, "Out 3"},
+	{"Out 3", NULL, "Out 3 Mux"},
+	{"OUT4", NULL, "Out 4"},
+	{"Out 4", NULL, "Out 4 Mux"},
+	{"SPKL", NULL, "Left Speaker"},
+	{"Left Speaker", NULL, "Left Speaker Out Mux"},
+	{"SPKR", NULL, "Right Speaker"},
+	{"Right Speaker", NULL, "Right Speaker Out Mux"},
+	{"MONO", NULL, "Mono Out"},
+	{"Mono Out", NULL, "Mono Out Mux"},
+
+	/* input pga */
+	{"Left Line In", NULL, "LINEL"},
+	{"Right Line In", NULL, "LINER"},
+	{"Mono In", NULL, "MONOIN"},
+	{"Mic A PGA", NULL, "Mic A Pre Amp"},
+	{"Mic B PGA", NULL, "Mic B Pre Amp"},
+
+	/* left capture select */
+	{"Left Capture Source", "Mic 1", "Mic A Pre Amp"},
+	{"Left Capture Source", "Mic 2", "Mic B Pre Amp"},
+	{"Left Capture Source", "Line", "LINEL"},
+	{"Left Capture Source", "Mono In", "MONOIN"},
+	{"Left Capture Source", "Headphone", "Left HP Mixer"},
+	{"Left Capture Source", "Speaker", "Speaker Mixer"},
+	{"Left Capture Source", "Mono Out", "Mono Mixer"},
+
+	/* right capture select */
+	{"Right Capture Source", "Mic 1", "Mic A Pre Amp"},
+	{"Right Capture Source", "Mic 2", "Mic B Pre Amp"},
+	{"Right Capture Source", "Line", "LINER"},
+	{"Right Capture Source", "Mono In", "MONOIN"},
+	{"Right Capture Source", "Headphone", "Right HP Mixer"},
+	{"Right Capture Source", "Speaker", "Speaker Mixer"},
+	{"Right Capture Source", "Mono Out", "Mono Mixer"},
+
+	/* left ADC */
+	{"Left ADC", NULL, "Left Capture Source"},
+	{"Left Voice ADC", NULL, "Left ADC"},
+	{"Left HiFi ADC", NULL, "Left ADC"},
+
+	/* right ADC */
+	{"Right ADC", NULL, "Right Capture Source"},
+	{"Right Voice ADC", NULL, "Right ADC"},
+	{"Right HiFi ADC", NULL, "Right ADC"},
+
+	/* mic */
+	{"Mic A Pre Amp", NULL, "Mic A Source"},
+	{"Mic A Source", "Mic 1", "MIC1"},
+	{"Mic A Source", "Mic 2 A", "MIC2A"},
+	{"Mic A Source", "Mic 2 B", "Mic B Source"},
+	{"Mic B Pre Amp", "MPB", "Mic B Source"},
+	{"Mic B Source", NULL, "MIC2B"},
+
+	/* headphone capture */
+	{"Capture Headphone Mux", "Stereo", "Capture Mixer"},
+	{"Capture Headphone Mux", "Left", "Left Capture Source"},
+	{"Capture Headphone Mux", "Right", "Right Capture Source"},
+
+	/* mono capture */
+	{"Capture Mono Mux", "Stereo", "Capture Mixer"},
+	{"Capture Mono Mux", "Left", "Left Capture Source"},
+	{"Capture Mono Mux", "Right", "Right Capture Source"},
+};
+
+static int wm9713_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, wm9713_dapm_widgets,
+				  ARRAY_SIZE(wm9713_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+static unsigned int ac97_read(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+
+	if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
+		reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 ||
+		reg == AC97_CD)
+		return soc_ac97_ops.read(codec->ac97, reg);
+	else {
+		reg = reg >> 1;
+
+		if (reg >= (ARRAY_SIZE(wm9713_reg)))
+			return -EIO;
+
+		return cache[reg];
+	}
+}
+
+static int ac97_write(struct snd_soc_codec *codec, unsigned int reg,
+	unsigned int val)
+{
+	u16 *cache = codec->reg_cache;
+	if (reg < 0x7c)
+		soc_ac97_ops.write(codec->ac97, reg, val);
+	reg = reg >> 1;
+	if (reg < (ARRAY_SIZE(wm9713_reg)))
+		cache[reg] = val;
+
+	return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+	u32 divsel:1;
+	u32 divctl:1;
+	u32 lf:1;
+	u32 n:4;
+	u32 k:24;
+};
+
+/* The size in bits of the PLL divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 22) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int source)
+{
+	u64 Kpart;
+	unsigned int K, Ndiv, Nmod, target;
+
+	/* The the PLL output is always 98.304MHz. */
+	target = 98304000;
+
+	/* If the input frequency is over 14.4MHz then scale it down. */
+	if (source > 14400000) {
+		source >>= 1;
+		pll_div->divsel = 1;
+
+		if (source > 14400000) {
+			source >>= 1;
+			pll_div->divctl = 1;
+		} else
+			pll_div->divctl = 0;
+
+	} else {
+		pll_div->divsel = 0;
+		pll_div->divctl = 0;
+	}
+
+	/* Low frequency sources require an additional divide in the
+	 * loop.
+	 */
+	if (source < 8192000) {
+		pll_div->lf = 1;
+		target >>= 2;
+	} else
+		pll_div->lf = 0;
+
+	Ndiv = target / source;
+	if ((Ndiv < 5) || (Ndiv > 12))
+		printk(KERN_WARNING
+			"WM9713 PLL N value %u out of recommended range!\n",
+			Ndiv);
+
+	pll_div->n = Ndiv;
+	Nmod = target % source;
+	Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+	do_div(Kpart, source);
+
+	K = Kpart & 0xFFFFFFFF;
+
+	/* Check if we need to round */
+	if ((K % 10) >= 5)
+		K += 5;
+
+	/* Move down to proper range now rounding is done */
+	K /= 10;
+
+	pll_div->k = K;
+}
+
+/**
+ * Please note that changing the PLL input frequency may require
+ * resynchronisation with the AC97 controller.
+ */
+static int wm9713_set_pll(struct snd_soc_codec *codec,
+	int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+	struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
+	u16 reg, reg2;
+	struct _pll_div pll_div;
+
+	/* turn PLL off ? */
+	if (freq_in == 0) {
+		/* disable PLL power and select ext source */
+		reg = ac97_read(codec, AC97_HANDSET_RATE);
+		ac97_write(codec, AC97_HANDSET_RATE, reg | 0x0080);
+		reg = ac97_read(codec, AC97_EXTENDED_MID);
+		ac97_write(codec, AC97_EXTENDED_MID, reg | 0x0200);
+		wm9713->pll_in = 0;
+		return 0;
+	}
+
+	pll_factors(&pll_div, freq_in);
+
+	if (pll_div.k == 0) {
+		reg = (pll_div.n << 12) | (pll_div.lf << 11) |
+			(pll_div.divsel << 9) | (pll_div.divctl << 8);
+		ac97_write(codec, AC97_LINE1_LEVEL, reg);
+	} else {
+		/* write the fractional k to the reg 0x46 pages */
+		reg2 = (pll_div.n << 12) | (pll_div.lf << 11) | (1 << 10) |
+			(pll_div.divsel << 9) | (pll_div.divctl << 8);
+
+		/* K [21:20] */
+		reg = reg2 | (0x5 << 4) | (pll_div.k >> 20);
+		ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+		/* K [19:16] */
+		reg = reg2 | (0x4 << 4) | ((pll_div.k >> 16) & 0xf);
+		ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+		/* K [15:12] */
+		reg = reg2 | (0x3 << 4) | ((pll_div.k >> 12) & 0xf);
+		ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+		/* K [11:8] */
+		reg = reg2 | (0x2 << 4) | ((pll_div.k >> 8) & 0xf);
+		ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+		/* K [7:4] */
+		reg = reg2 | (0x1 << 4) | ((pll_div.k >> 4) & 0xf);
+		ac97_write(codec, AC97_LINE1_LEVEL, reg);
+
+		reg = reg2 | (0x0 << 4) | (pll_div.k & 0xf); /* K [3:0] */
+		ac97_write(codec, AC97_LINE1_LEVEL, reg);
+	}
+
+	/* turn PLL on and select as source */
+	reg = ac97_read(codec, AC97_EXTENDED_MID);
+	ac97_write(codec, AC97_EXTENDED_MID, reg & 0xfdff);
+	reg = ac97_read(codec, AC97_HANDSET_RATE);
+	ac97_write(codec, AC97_HANDSET_RATE, reg & 0xff7f);
+	wm9713->pll_in = freq_in;
+
+	/* wait 10ms AC97 link frames for the link to stabilise */
+	schedule_timeout_interruptible(msecs_to_jiffies(10));
+	return 0;
+}
+
+static int wm9713_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+		int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	return wm9713_set_pll(codec, pll_id, freq_in, freq_out);
+}
+
+/*
+ * Tristate the PCM DAI lines, tristate can be disabled by calling
+ * wm9713_set_dai_fmt()
+ */
+static int wm9713_set_dai_tristate(struct snd_soc_dai *codec_dai,
+	int tristate)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0x9fff;
+
+	if (tristate)
+		ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+
+	return 0;
+}
+
+/*
+ * Configure WM9713 clock dividers.
+ * Voice DAC needs 256 FS
+ */
+static int wm9713_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+		int div_id, int div)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 reg;
+
+	switch (div_id) {
+	case WM9713_PCMCLK_DIV:
+		reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xf0ff;
+		ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+		break;
+	case WM9713_CLKA_MULT:
+		reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffd;
+		ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+		break;
+	case WM9713_CLKB_MULT:
+		reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffb;
+		ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+		break;
+	case WM9713_HIFI_DIV:
+		reg = ac97_read(codec, AC97_HANDSET_RATE) & 0x8fff;
+		ac97_write(codec, AC97_HANDSET_RATE, reg | div);
+		break;
+	case WM9713_PCMBCLK_DIV:
+		reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xf1ff;
+		ac97_write(codec, AC97_CENTER_LFE_MASTER, reg | div);
+		break;
+	case WM9713_PCMCLK_PLL_DIV:
+		reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80;
+		ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x60 | div);
+		break;
+	case WM9713_HIFI_PLL_DIV:
+		reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80;
+		ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x70 | div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wm9713_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u16 gpio = ac97_read(codec, AC97_GPIO_CFG) & 0xffc5;
+	u16 reg = 0x8000;
+
+	/* clock masters */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		reg |= 0x4000;
+		gpio |= 0x0010;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		reg |= 0x6000;
+		gpio |= 0x0018;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		reg |= 0x2000;
+		gpio |= 0x001a;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		gpio |= 0x0012;
+		break;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_IB_IF:
+		reg |= 0x00c0;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		reg |= 0x0080;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		reg |= 0x0040;
+		break;
+	}
+
+	/* DAI format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		reg |= 0x0002;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		reg |= 0x0001;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		reg |= 0x0003;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		reg |= 0x0043;
+		break;
+	}
+
+	ac97_write(codec, AC97_GPIO_CFG, gpio);
+	ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+	return 0;
+}
+
+static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xfff3;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		reg |= 0x0004;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		reg |= 0x0008;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		reg |= 0x000c;
+		break;
+	}
+
+	/* enable PCM interface in master mode */
+	ac97_write(codec, AC97_CENTER_LFE_MASTER, reg);
+	return 0;
+}
+
+static int ac97_hifi_prepare(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int reg;
+	u16 vra;
+
+	vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+	ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		reg = AC97_PCM_FRONT_DAC_RATE;
+	else
+		reg = AC97_PCM_LR_ADC_RATE;
+
+	return ac97_write(codec, reg, runtime->rate);
+}
+
+static int ac97_aux_prepare(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	u16 vra, xsle;
+
+	vra = ac97_read(codec, AC97_EXTENDED_STATUS);
+	ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1);
+	xsle = ac97_read(codec, AC97_PCI_SID);
+	ac97_write(codec, AC97_PCI_SID, xsle | 0x8000);
+
+	if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+		return -ENODEV;
+
+	return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate);
+}
+
+#define WM9713_RATES (SNDRV_PCM_RATE_8000  |	\
+		      SNDRV_PCM_RATE_11025 |	\
+		      SNDRV_PCM_RATE_22050 |	\
+		      SNDRV_PCM_RATE_44100 |	\
+		      SNDRV_PCM_RATE_48000)
+
+#define WM9713_PCM_RATES (SNDRV_PCM_RATE_8000  |	\
+			  SNDRV_PCM_RATE_11025 |	\
+			  SNDRV_PCM_RATE_16000 |	\
+			  SNDRV_PCM_RATE_22050 |	\
+			  SNDRV_PCM_RATE_44100 |	\
+			  SNDRV_PCM_RATE_48000)
+
+#define WM9713_PCM_FORMATS \
+	(SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \
+	 SNDRV_PCM_FORMAT_S24_LE)
+
+static struct snd_soc_dai_ops wm9713_dai_ops_hifi = {
+	.prepare	= ac97_hifi_prepare,
+	.set_clkdiv	= wm9713_set_dai_clkdiv,
+	.set_pll	= wm9713_set_dai_pll,
+};
+
+static struct snd_soc_dai_ops wm9713_dai_ops_aux = {
+	.prepare	= ac97_aux_prepare,
+	.set_clkdiv	= wm9713_set_dai_clkdiv,
+	.set_pll	= wm9713_set_dai_pll,
+};
+
+static struct snd_soc_dai_ops wm9713_dai_ops_voice = {
+	.hw_params	= wm9713_pcm_hw_params,
+	.set_clkdiv	= wm9713_set_dai_clkdiv,
+	.set_pll	= wm9713_set_dai_pll,
+	.set_fmt	= wm9713_set_dai_fmt,
+	.set_tristate	= wm9713_set_dai_tristate,
+};
+
+static struct snd_soc_dai_driver wm9713_dai[] = {
+{
+	.name = "wm9713-hifi",
+	.ac97_control = 1,
+	.playback = {
+		.stream_name = "HiFi Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM9713_RATES,
+		.formats = SND_SOC_STD_AC97_FMTS,},
+	.capture = {
+		.stream_name = "HiFi Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM9713_RATES,
+		.formats = SND_SOC_STD_AC97_FMTS,},
+	.ops = &wm9713_dai_ops_hifi,
+	},
+	{
+	.name = "wm9713-aux",
+	.playback = {
+		.stream_name = "Aux Playback",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = WM9713_RATES,
+		.formats = SND_SOC_STD_AC97_FMTS,},
+	.ops = &wm9713_dai_ops_aux,
+	},
+	{
+	.name = "wm9713-voice",
+	.playback = {
+		.stream_name = "Voice Playback",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = WM9713_PCM_RATES,
+		.formats = WM9713_PCM_FORMATS,},
+	.capture = {
+		.stream_name = "Voice Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = WM9713_PCM_RATES,
+		.formats = WM9713_PCM_FORMATS,},
+	.ops = &wm9713_dai_ops_voice,
+	.symmetric_rates = 1,
+	},
+};
+
+int wm9713_reset(struct snd_soc_codec *codec, int try_warm)
+{
+	if (try_warm && soc_ac97_ops.warm_reset) {
+		soc_ac97_ops.warm_reset(codec->ac97);
+		if (ac97_read(codec, 0) == wm9713_reg[0])
+			return 1;
+	}
+
+	soc_ac97_ops.reset(codec->ac97);
+	if (soc_ac97_ops.warm_reset)
+		soc_ac97_ops.warm_reset(codec->ac97);
+	if (ac97_read(codec, 0) != wm9713_reg[0])
+		return -EIO;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wm9713_reset);
+
+static int wm9713_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level)
+{
+	u16 reg;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		/* enable thermal shutdown */
+		reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x1bff;
+		ac97_write(codec, AC97_EXTENDED_MID, reg);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		/* enable master bias and vmid */
+		reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x3bff;
+		ac97_write(codec, AC97_EXTENDED_MID, reg);
+		ac97_write(codec, AC97_POWERDOWN, 0x0000);
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* disable everything including AC link */
+		ac97_write(codec, AC97_EXTENDED_MID, 0xffff);
+		ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+		ac97_write(codec, AC97_POWERDOWN, 0xffff);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+static int wm9713_soc_suspend(struct snd_soc_codec *codec,
+	pm_message_t state)
+{
+	u16 reg;
+
+	/* Disable everything except touchpanel - that will be handled
+	 * by the touch driver and left disabled if touch is not in
+	 * use. */
+	reg = ac97_read(codec, AC97_EXTENDED_MID);
+	ac97_write(codec, AC97_EXTENDED_MID, reg | 0x7fff);
+	ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff);
+	ac97_write(codec, AC97_POWERDOWN, 0x6f00);
+	ac97_write(codec, AC97_POWERDOWN, 0xffff);
+
+	return 0;
+}
+
+static int wm9713_soc_resume(struct snd_soc_codec *codec)
+{
+	struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
+	int i, ret;
+	u16 *cache = codec->reg_cache;
+
+	ret = wm9713_reset(codec, 1);
+	if (ret < 0) {
+		printk(KERN_ERR "could not reset AC97 codec\n");
+		return ret;
+	}
+
+	wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* do we need to re-start the PLL ? */
+	if (wm9713->pll_in)
+		wm9713_set_pll(codec, 0, wm9713->pll_in, 0);
+
+	/* only synchronise the codec if warm reset failed */
+	if (ret == 0) {
+		for (i = 2; i < ARRAY_SIZE(wm9713_reg) << 1; i += 2) {
+			if (i == AC97_POWERDOWN || i == AC97_EXTENDED_MID ||
+				i == AC97_EXTENDED_MSTATUS || i > 0x66)
+				continue;
+			soc_ac97_ops.write(codec->ac97, i, cache[i>>1]);
+		}
+	}
+
+	return ret;
+}
+
+static int wm9713_soc_probe(struct snd_soc_codec *codec)
+{
+	struct wm9713_priv *wm9713;
+	int ret = 0, reg;
+
+	wm9713 = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL);
+	if (wm9713 == NULL)
+		return -ENOMEM;
+	snd_soc_codec_set_drvdata(codec, wm9713);
+
+	ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0);
+	if (ret < 0)
+		goto codec_err;
+
+	/* do a cold reset for the controller and then try
+	 * a warm reset followed by an optional cold reset for codec */
+	wm9713_reset(codec, 0);
+	ret = wm9713_reset(codec, 1);
+	if (ret < 0) {
+		printk(KERN_ERR "Failed to reset WM9713: AC97 link error\n");
+		goto reset_err;
+	}
+
+	wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* unmute the adc - move to kcontrol */
+	reg = ac97_read(codec, AC97_CD) & 0x7fff;
+	ac97_write(codec, AC97_CD, reg);
+
+	snd_soc_add_controls(codec, wm9713_snd_ac97_controls,
+				ARRAY_SIZE(wm9713_snd_ac97_controls));
+	wm9713_add_widgets(codec);
+
+	return 0;
+
+reset_err:
+	snd_soc_free_ac97_codec(codec);
+codec_err:
+	kfree(wm9713);
+	return ret;
+}
+
+static int wm9713_soc_remove(struct snd_soc_codec *codec)
+{
+	struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
+	snd_soc_free_ac97_codec(codec);
+	kfree(wm9713);
+	return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_wm9713 = {
+	.probe = 	wm9713_soc_probe,
+	.remove = 	wm9713_soc_remove,
+	.suspend =	wm9713_soc_suspend,
+	.resume = 	wm9713_soc_resume,
+	.read = ac97_read,
+	.write = ac97_write,
+	.set_bias_level = wm9713_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(wm9713_reg),
+	.reg_word_size = sizeof(u16),
+	.reg_cache_step = 2,
+	.reg_cache_default = wm9713_reg,
+};
+
+static __devinit int wm9713_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_codec(&pdev->dev,
+			&soc_codec_dev_wm9713, wm9713_dai, ARRAY_SIZE(wm9713_dai));
+}
+
+static int __devexit wm9713_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver wm9713_codec_driver = {
+	.driver = {
+			.name = "wm9713-codec",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = wm9713_probe,
+	.remove = __devexit_p(wm9713_remove),
+};
+
+static int __init wm9713_init(void)
+{
+	return platform_driver_register(&wm9713_codec_driver);
+}
+module_init(wm9713_init);
+
+static void __exit wm9713_exit(void)
+{
+	platform_driver_unregister(&wm9713_codec_driver);
+}
+module_exit(wm9713_exit);
+
+MODULE_DESCRIPTION("ASoC WM9713/WM9714 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm9713.h b/linux/sound/soc/codecs/wm9713.h
new file mode 100644
index 0000000..793da86
--- /dev/null
+++ b/linux/sound/soc/codecs/wm9713.h
@@ -0,0 +1,50 @@
+/*
+ * wm9713.h  --  WM9713 Soc Audio driver
+ */
+
+#ifndef _WM9713_H
+#define _WM9713_H
+
+/* clock inputs */
+#define WM9713_CLKA_PIN			0
+#define WM9713_CLKB_PIN			1
+
+/* clock divider ID's */
+#define WM9713_PCMCLK_DIV		0
+#define WM9713_CLKA_MULT		1
+#define WM9713_CLKB_MULT		2
+#define WM9713_HIFI_DIV			3
+#define WM9713_PCMBCLK_DIV		4
+#define WM9713_PCMCLK_PLL_DIV           5
+#define WM9713_HIFI_PLL_DIV             6
+
+/* Calculate the appropriate bit mask for the external PCM clock divider */
+#define WM9713_PCMDIV(x)	((x - 1) << 8)
+
+/* Calculate the appropriate bit mask for the external HiFi clock divider */
+#define WM9713_HIFIDIV(x)	((x - 1) << 12)
+
+/* MCLK clock mulitipliers */
+#define WM9713_CLKA_X1		(0 << 1)
+#define WM9713_CLKA_X2		(1 << 1)
+#define WM9713_CLKB_X1		(0 << 2)
+#define WM9713_CLKB_X2		(1 << 2)
+
+/* MCLK clock MUX */
+#define WM9713_CLK_MUX_A		(0 << 0)
+#define WM9713_CLK_MUX_B		(1 << 0)
+
+/* Voice DAI BCLK divider */
+#define WM9713_PCMBCLK_DIV_1	(0 << 9)
+#define WM9713_PCMBCLK_DIV_2	(1 << 9)
+#define WM9713_PCMBCLK_DIV_4	(2 << 9)
+#define WM9713_PCMBCLK_DIV_8	(3 << 9)
+#define WM9713_PCMBCLK_DIV_16	(4 << 9)
+
+#define WM9713_DAI_AC97_HIFI	0
+#define WM9713_DAI_AC97_AUX		1
+#define WM9713_DAI_PCM_VOICE	2
+
+int wm9713_reset(struct snd_soc_codec *codec,  int try_warm);
+
+#endif
diff --git a/linux/sound/soc/codecs/wm_hubs.c b/linux/sound/soc/codecs/wm_hubs.c
new file mode 100644
index 0000000..0e24092
--- /dev/null
+++ b/linux/sound/soc/codecs/wm_hubs.c
@@ -0,0 +1,899 @@
+/*
+ * wm_hubs.c  --  WM8993/4 common code
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "wm8993.h"
+#include "wm_hubs.h"
+
+const DECLARE_TLV_DB_SCALE(wm_hubs_spkmix_tlv, -300, 300, 0);
+EXPORT_SYMBOL_GPL(wm_hubs_spkmix_tlv);
+
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(inmix_sw_tlv, 0, 3000, 0);
+static const DECLARE_TLV_DB_SCALE(inmix_tlv, -1500, 300, 1);
+static const DECLARE_TLV_DB_SCALE(earpiece_tlv, -600, 600, 0);
+static const DECLARE_TLV_DB_SCALE(outmix_tlv, -2100, 300, 0);
+static const DECLARE_TLV_DB_SCALE(spkmixout_tlv, -1800, 600, 1);
+static const DECLARE_TLV_DB_SCALE(outpga_tlv, -5700, 100, 0);
+static const unsigned int spkboost_tlv[] = {
+	TLV_DB_RANGE_HEAD(7),
+	0, 6, TLV_DB_SCALE_ITEM(0, 150, 0),
+	7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(line_tlv, -600, 600, 0);
+
+static const char *speaker_ref_text[] = {
+	"SPKVDD/2",
+	"VMID",
+};
+
+static const struct soc_enum speaker_ref =
+	SOC_ENUM_SINGLE(WM8993_SPEAKER_MIXER, 8, 2, speaker_ref_text);
+
+static const char *speaker_mode_text[] = {
+	"Class D",
+	"Class AB",
+};
+
+static const struct soc_enum speaker_mode =
+	SOC_ENUM_SINGLE(WM8993_SPKMIXR_ATTENUATION, 8, 2, speaker_mode_text);
+
+static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op)
+{
+	unsigned int reg;
+	int count = 0;
+	unsigned int val;
+
+	val = op | WM8993_DCS_ENA_CHAN_0 | WM8993_DCS_ENA_CHAN_1;
+
+	/* Trigger the command */
+	snd_soc_write(codec, WM8993_DC_SERVO_0, val);
+
+	dev_dbg(codec->dev, "Waiting for DC servo...\n");
+
+	do {
+		count++;
+		msleep(1);
+		reg = snd_soc_read(codec, WM8993_DC_SERVO_0);
+		dev_dbg(codec->dev, "DC servo: %x\n", reg);
+	} while (reg & op && count < 400);
+
+	if (reg & op)
+		dev_err(codec->dev, "Timed out waiting for DC Servo\n");
+}
+
+/*
+ * Startup calibration of the DC servo
+ */
+static void calibrate_dc_servo(struct snd_soc_codec *codec)
+{
+	struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+	u16 reg, reg_l, reg_r, dcs_cfg;
+
+	/* Set for 32 series updates */
+	snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
+			    WM8993_DCS_SERIES_NO_01_MASK,
+			    32 << WM8993_DCS_SERIES_NO_01_SHIFT);
+	wait_for_dc_servo(codec,
+			  WM8993_DCS_TRIG_SERIES_0 | WM8993_DCS_TRIG_SERIES_1);
+
+	/* Apply correction to DC servo result */
+	if (hubs->dcs_codes) {
+		dev_dbg(codec->dev, "Applying %d code DC servo correction\n",
+			hubs->dcs_codes);
+
+		/* Different chips in the family support different
+		 * readback methods.
+		 */
+		switch (hubs->dcs_readback_mode) {
+		case 0:
+			reg_l = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_1)
+				& WM8993_DCS_INTEG_CHAN_0_MASK;;
+			reg_r = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2)
+				& WM8993_DCS_INTEG_CHAN_1_MASK;
+			break;
+		case 1:
+			reg = snd_soc_read(codec, WM8993_DC_SERVO_3);
+			reg_l = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK)
+				>> WM8993_DCS_DAC_WR_VAL_1_SHIFT;
+			reg_r = reg & WM8993_DCS_DAC_WR_VAL_0_MASK;
+			break;
+		default:
+			WARN(1, "Unknown DCS readback method\n");
+			break;
+		}
+
+		dev_dbg(codec->dev, "DCS input: %x %x\n", reg_l, reg_r);
+
+		/* HPOUT1L */
+		if (reg_l + hubs->dcs_codes > 0 &&
+		    reg_l + hubs->dcs_codes < 0xff)
+			reg_l += hubs->dcs_codes;
+		dcs_cfg = reg_l << WM8993_DCS_DAC_WR_VAL_1_SHIFT;
+
+		/* HPOUT1R */
+		if (reg_r + hubs->dcs_codes > 0 &&
+		    reg_r + hubs->dcs_codes < 0xff)
+			reg_r += hubs->dcs_codes;
+		dcs_cfg |= reg_r;
+
+		dev_dbg(codec->dev, "DCS result: %x\n", dcs_cfg);
+
+		/* Do it */
+		snd_soc_write(codec, WM8993_DC_SERVO_3, dcs_cfg);
+		wait_for_dc_servo(codec,
+				  WM8993_DCS_TRIG_DAC_WR_0 |
+				  WM8993_DCS_TRIG_DAC_WR_1);
+	}
+}
+
+/*
+ * Update the DC servo calibration on gain changes
+ */
+static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+
+	/* If we're applying an offset correction then updating the
+	 * callibration would be likely to introduce further offsets. */
+	if (hubs->dcs_codes)
+		return ret;
+
+	/* Only need to do this if the outputs are active */
+	if (snd_soc_read(codec, WM8993_POWER_MANAGEMENT_1)
+	    & (WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA))
+		snd_soc_update_bits(codec,
+				    WM8993_DC_SERVO_0,
+				    WM8993_DCS_TRIG_SINGLE_0 |
+				    WM8993_DCS_TRIG_SINGLE_1,
+				    WM8993_DCS_TRIG_SINGLE_0 |
+				    WM8993_DCS_TRIG_SINGLE_1);
+
+	return ret;
+}
+
+static const struct snd_kcontrol_new analogue_snd_controls[] = {
+SOC_SINGLE_TLV("IN1L Volume", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 0, 31, 0,
+	       inpga_tlv),
+SOC_SINGLE("IN1L Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN1L ZC Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 0),
+
+SOC_SINGLE_TLV("IN1R Volume", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 0, 31, 0,
+	       inpga_tlv),
+SOC_SINGLE("IN1R Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN1R ZC Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 0),
+
+
+SOC_SINGLE_TLV("IN2L Volume", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 0, 31, 0,
+	       inpga_tlv),
+SOC_SINGLE("IN2L Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN2L ZC Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 0),
+
+SOC_SINGLE_TLV("IN2R Volume", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 0, 31, 0,
+	       inpga_tlv),
+SOC_SINGLE("IN2R Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 1),
+SOC_SINGLE("IN2R ZC Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 0),
+
+SOC_SINGLE_TLV("MIXINL IN2L Volume", WM8993_INPUT_MIXER3, 7, 1, 0,
+	       inmix_sw_tlv),
+SOC_SINGLE_TLV("MIXINL IN1L Volume", WM8993_INPUT_MIXER3, 4, 1, 0,
+	       inmix_sw_tlv),
+SOC_SINGLE_TLV("MIXINL Output Record Volume", WM8993_INPUT_MIXER3, 0, 7, 0,
+	       inmix_tlv),
+SOC_SINGLE_TLV("MIXINL IN1LP Volume", WM8993_INPUT_MIXER5, 6, 7, 0, inmix_tlv),
+SOC_SINGLE_TLV("MIXINL Direct Voice Volume", WM8993_INPUT_MIXER5, 0, 6, 0,
+	       inmix_tlv),
+
+SOC_SINGLE_TLV("MIXINR IN2R Volume", WM8993_INPUT_MIXER4, 7, 1, 0,
+	       inmix_sw_tlv),
+SOC_SINGLE_TLV("MIXINR IN1R Volume", WM8993_INPUT_MIXER4, 4, 1, 0,
+	       inmix_sw_tlv),
+SOC_SINGLE_TLV("MIXINR Output Record Volume", WM8993_INPUT_MIXER4, 0, 7, 0,
+	       inmix_tlv),
+SOC_SINGLE_TLV("MIXINR IN1RP Volume", WM8993_INPUT_MIXER6, 6, 7, 0, inmix_tlv),
+SOC_SINGLE_TLV("MIXINR Direct Voice Volume", WM8993_INPUT_MIXER6, 0, 6, 0,
+	       inmix_tlv),
+
+SOC_SINGLE_TLV("Left Output Mixer IN2RN Volume", WM8993_OUTPUT_MIXER5, 6, 7, 1,
+	       outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer IN2LN Volume", WM8993_OUTPUT_MIXER3, 6, 7, 1,
+	       outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer IN2LP Volume", WM8993_OUTPUT_MIXER3, 9, 7, 1,
+	       outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer IN1L Volume", WM8993_OUTPUT_MIXER3, 0, 7, 1,
+	       outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer IN1R Volume", WM8993_OUTPUT_MIXER3, 3, 7, 1,
+	       outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer Right Input Volume",
+	       WM8993_OUTPUT_MIXER5, 3, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer Left Input Volume",
+	       WM8993_OUTPUT_MIXER5, 0, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Left Output Mixer DAC Volume", WM8993_OUTPUT_MIXER5, 9, 7, 1,
+	       outmix_tlv),
+
+SOC_SINGLE_TLV("Right Output Mixer IN2LN Volume",
+	       WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer IN2RN Volume",
+	       WM8993_OUTPUT_MIXER4, 6, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer IN1L Volume",
+	       WM8993_OUTPUT_MIXER4, 3, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer IN1R Volume",
+	       WM8993_OUTPUT_MIXER4, 0, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer IN2RP Volume",
+	       WM8993_OUTPUT_MIXER4, 9, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer Left Input Volume",
+	       WM8993_OUTPUT_MIXER6, 3, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer Right Input Volume",
+	       WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv),
+SOC_SINGLE_TLV("Right Output Mixer DAC Volume",
+	       WM8993_OUTPUT_MIXER6, 9, 7, 1, outmix_tlv),
+
+SOC_DOUBLE_R_TLV("Output Volume", WM8993_LEFT_OPGA_VOLUME,
+		 WM8993_RIGHT_OPGA_VOLUME, 0, 63, 0, outpga_tlv),
+SOC_DOUBLE_R("Output Switch", WM8993_LEFT_OPGA_VOLUME,
+	     WM8993_RIGHT_OPGA_VOLUME, 6, 1, 0),
+SOC_DOUBLE_R("Output ZC Switch", WM8993_LEFT_OPGA_VOLUME,
+	     WM8993_RIGHT_OPGA_VOLUME, 7, 1, 0),
+
+SOC_SINGLE("Earpiece Switch", WM8993_HPOUT2_VOLUME, 5, 1, 1),
+SOC_SINGLE_TLV("Earpiece Volume", WM8993_HPOUT2_VOLUME, 4, 1, 1, earpiece_tlv),
+
+SOC_SINGLE_TLV("SPKL Input Volume", WM8993_SPKMIXL_ATTENUATION,
+	       5, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKL IN1LP Volume", WM8993_SPKMIXL_ATTENUATION,
+	       4, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKL Output Volume", WM8993_SPKMIXL_ATTENUATION,
+	       3, 1, 1, wm_hubs_spkmix_tlv),
+
+SOC_SINGLE_TLV("SPKR Input Volume", WM8993_SPKMIXR_ATTENUATION,
+	       5, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKR IN1RP Volume", WM8993_SPKMIXR_ATTENUATION,
+	       4, 1, 1, wm_hubs_spkmix_tlv),
+SOC_SINGLE_TLV("SPKR Output Volume", WM8993_SPKMIXR_ATTENUATION,
+	       3, 1, 1, wm_hubs_spkmix_tlv),
+
+SOC_DOUBLE_R_TLV("Speaker Mixer Volume",
+		 WM8993_SPKMIXL_ATTENUATION, WM8993_SPKMIXR_ATTENUATION,
+		 0, 3, 1, spkmixout_tlv),
+SOC_DOUBLE_R_TLV("Speaker Volume",
+		 WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT,
+		 0, 63, 0, outpga_tlv),
+SOC_DOUBLE_R("Speaker Switch",
+	     WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT,
+	     6, 1, 0),
+SOC_DOUBLE_R("Speaker ZC Switch",
+	     WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT,
+	     7, 1, 0),
+SOC_DOUBLE_TLV("Speaker Boost Volume", WM8993_SPKOUT_BOOST, 3, 0, 7, 0,
+	       spkboost_tlv),
+SOC_ENUM("Speaker Reference", speaker_ref),
+SOC_ENUM("Speaker Mode", speaker_mode),
+
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Headphone Volume",
+	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+		 SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.tlv.p = outpga_tlv,
+	.info = snd_soc_info_volsw_2r,
+	.get = snd_soc_get_volsw_2r, .put = wm8993_put_dc_servo,
+	.private_value = (unsigned long)&(struct soc_mixer_control) {
+		.reg = WM8993_LEFT_OUTPUT_VOLUME,
+		.rreg = WM8993_RIGHT_OUTPUT_VOLUME,
+		.shift = 0, .max = 63
+	},
+},
+SOC_DOUBLE_R("Headphone Switch", WM8993_LEFT_OUTPUT_VOLUME,
+	     WM8993_RIGHT_OUTPUT_VOLUME, 6, 1, 0),
+SOC_DOUBLE_R("Headphone ZC Switch", WM8993_LEFT_OUTPUT_VOLUME,
+	     WM8993_RIGHT_OUTPUT_VOLUME, 7, 1, 0),
+
+SOC_SINGLE("LINEOUT1N Switch", WM8993_LINE_OUTPUTS_VOLUME, 6, 1, 1),
+SOC_SINGLE("LINEOUT1P Switch", WM8993_LINE_OUTPUTS_VOLUME, 5, 1, 1),
+SOC_SINGLE_TLV("LINEOUT1 Volume", WM8993_LINE_OUTPUTS_VOLUME, 4, 1, 1,
+	       line_tlv),
+
+SOC_SINGLE("LINEOUT2N Switch", WM8993_LINE_OUTPUTS_VOLUME, 2, 1, 1),
+SOC_SINGLE("LINEOUT2P Switch", WM8993_LINE_OUTPUTS_VOLUME, 1, 1, 1),
+SOC_SINGLE_TLV("LINEOUT2 Volume", WM8993_LINE_OUTPUTS_VOLUME, 0, 1, 1,
+	       line_tlv),
+};
+
+static int hp_supply_event(struct snd_soc_dapm_widget *w,
+			   struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		switch (hubs->hp_startup_mode) {
+		case 0:
+			break;
+		case 1:
+			/* Enable the headphone amp */
+			snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+					    WM8993_HPOUT1L_ENA |
+					    WM8993_HPOUT1R_ENA,
+					    WM8993_HPOUT1L_ENA |
+					    WM8993_HPOUT1R_ENA);
+
+			/* Enable the second stage */
+			snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
+					    WM8993_HPOUT1L_DLY |
+					    WM8993_HPOUT1R_DLY,
+					    WM8993_HPOUT1L_DLY |
+					    WM8993_HPOUT1R_DLY);
+			break;
+		default:
+			dev_err(codec->dev, "Unknown HP startup mode %d\n",
+				hubs->hp_startup_mode);
+			break;
+		}
+
+	case SND_SOC_DAPM_PRE_PMD:
+		snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
+				    WM8993_CP_ENA, 0);
+		break;
+	}
+
+	return 0;
+}
+
+static int hp_event(struct snd_soc_dapm_widget *w,
+		    struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	unsigned int reg = snd_soc_read(codec, WM8993_ANALOGUE_HP_0);
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
+				    WM8993_CP_ENA, WM8993_CP_ENA);
+
+		msleep(5);
+
+		snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+				    WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA,
+				    WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA);
+
+		reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY;
+		snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
+
+		/* Smallest supported update interval */
+		snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
+				    WM8993_DCS_TIMER_PERIOD_01_MASK, 1);
+
+		calibrate_dc_servo(codec);
+
+		reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT |
+			WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT;
+		snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
+				    WM8993_HPOUT1L_OUTP |
+				    WM8993_HPOUT1R_OUTP |
+				    WM8993_HPOUT1L_RMV_SHORT |
+				    WM8993_HPOUT1R_RMV_SHORT, 0);
+
+		snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0,
+				    WM8993_HPOUT1L_DLY |
+				    WM8993_HPOUT1R_DLY, 0);
+
+		snd_soc_write(codec, WM8993_DC_SERVO_0, 0);
+
+		snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
+				    WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA,
+				    0);
+		break;
+	}
+
+	return 0;
+}
+
+static int earpiece_event(struct snd_soc_dapm_widget *w,
+			  struct snd_kcontrol *control, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	u16 reg = snd_soc_read(codec, WM8993_ANTIPOP1) & ~WM8993_HPOUT2_IN_ENA;
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_PMU:
+		reg |= WM8993_HPOUT2_IN_ENA;
+		snd_soc_write(codec, WM8993_ANTIPOP1, reg);
+		udelay(50);
+		break;
+
+	case SND_SOC_DAPM_POST_PMD:
+		snd_soc_write(codec, WM8993_ANTIPOP1, reg);
+		break;
+
+	default:
+		BUG();
+		break;
+	}
+
+	return 0;
+}
+
+static const struct snd_kcontrol_new in1l_pga[] = {
+SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0),
+SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0),
+};
+
+static const struct snd_kcontrol_new in1r_pga[] = {
+SOC_DAPM_SINGLE("IN1RP Switch", WM8993_INPUT_MIXER2, 1, 1, 0),
+SOC_DAPM_SINGLE("IN1RN Switch", WM8993_INPUT_MIXER2, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new in2l_pga[] = {
+SOC_DAPM_SINGLE("IN2LP Switch", WM8993_INPUT_MIXER2, 7, 1, 0),
+SOC_DAPM_SINGLE("IN2LN Switch", WM8993_INPUT_MIXER2, 6, 1, 0),
+};
+
+static const struct snd_kcontrol_new in2r_pga[] = {
+SOC_DAPM_SINGLE("IN2RP Switch", WM8993_INPUT_MIXER2, 3, 1, 0),
+SOC_DAPM_SINGLE("IN2RN Switch", WM8993_INPUT_MIXER2, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixinl[] = {
+SOC_DAPM_SINGLE("IN2L Switch", WM8993_INPUT_MIXER3, 8, 1, 0),
+SOC_DAPM_SINGLE("IN1L Switch", WM8993_INPUT_MIXER3, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new mixinr[] = {
+SOC_DAPM_SINGLE("IN2R Switch", WM8993_INPUT_MIXER4, 8, 1, 0),
+SOC_DAPM_SINGLE("IN1R Switch", WM8993_INPUT_MIXER4, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_output_mixer[] = {
+SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0),
+SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0),
+SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0),
+SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0),
+SOC_DAPM_SINGLE("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0),
+SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0),
+SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_output_mixer[] = {
+SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0),
+SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0),
+SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0),
+SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0),
+SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0),
+SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new earpiece_mixer[] = {
+SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_HPOUT2_MIXER, 5, 1, 0),
+SOC_DAPM_SINGLE("Left Output Switch", WM8993_HPOUT2_MIXER, 4, 1, 0),
+SOC_DAPM_SINGLE("Right Output Switch", WM8993_HPOUT2_MIXER, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_speaker_boost[] = {
+SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 5, 1, 0),
+SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 4, 1, 0),
+SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_speaker_boost[] = {
+SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 2, 1, 0),
+SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 1, 1, 0),
+SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new line1_mix[] = {
+SOC_DAPM_SINGLE("IN1R Switch", WM8993_LINE_MIXER1, 2, 1, 0),
+SOC_DAPM_SINGLE("IN1L Switch", WM8993_LINE_MIXER1, 1, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new line1n_mix[] = {
+SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 6, 1, 0),
+SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER1, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new line1p_mix[] = {
+SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new line2_mix[] = {
+SOC_DAPM_SINGLE("IN2R Switch", WM8993_LINE_MIXER2, 2, 1, 0),
+SOC_DAPM_SINGLE("IN2L Switch", WM8993_LINE_MIXER2, 1, 1, 0),
+SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER2, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new line2n_mix[] = {
+SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER2, 6, 1, 0),
+SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new line2p_mix[] = {
+SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 0, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget analogue_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("IN1LN"),
+SND_SOC_DAPM_INPUT("IN1LP"),
+SND_SOC_DAPM_INPUT("IN2LN"),
+SND_SOC_DAPM_INPUT("IN2LP:VXRN"),
+SND_SOC_DAPM_INPUT("IN1RN"),
+SND_SOC_DAPM_INPUT("IN1RP"),
+SND_SOC_DAPM_INPUT("IN2RN"),
+SND_SOC_DAPM_INPUT("IN2RP:VXRP"),
+
+SND_SOC_DAPM_MICBIAS("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0),
+SND_SOC_DAPM_MICBIAS("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0),
+
+SND_SOC_DAPM_MIXER("IN1L PGA", WM8993_POWER_MANAGEMENT_2, 6, 0,
+		   in1l_pga, ARRAY_SIZE(in1l_pga)),
+SND_SOC_DAPM_MIXER("IN1R PGA", WM8993_POWER_MANAGEMENT_2, 4, 0,
+		   in1r_pga, ARRAY_SIZE(in1r_pga)),
+
+SND_SOC_DAPM_MIXER("IN2L PGA", WM8993_POWER_MANAGEMENT_2, 7, 0,
+		   in2l_pga, ARRAY_SIZE(in2l_pga)),
+SND_SOC_DAPM_MIXER("IN2R PGA", WM8993_POWER_MANAGEMENT_2, 5, 0,
+		   in2r_pga, ARRAY_SIZE(in2r_pga)),
+
+/* Dummy widgets to represent differential paths */
+SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("MIXINL", WM8993_POWER_MANAGEMENT_2, 9, 0,
+		   mixinl, ARRAY_SIZE(mixinl)),
+SND_SOC_DAPM_MIXER("MIXINR", WM8993_POWER_MANAGEMENT_2, 8, 0,
+		   mixinr, ARRAY_SIZE(mixinr)),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8993_POWER_MANAGEMENT_3, 5, 0,
+		   left_output_mixer, ARRAY_SIZE(left_output_mixer)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0,
+		   right_output_mixer, ARRAY_SIZE(right_output_mixer)),
+
+SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("Headphone Supply", SND_SOC_NOPM, 0, 0, hp_supply_event, 
+		    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0,
+		   NULL, 0,
+		   hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0,
+		   earpiece_mixer, ARRAY_SIZE(earpiece_mixer)),
+SND_SOC_DAPM_PGA_E("Earpiece Driver", WM8993_POWER_MANAGEMENT_1, 11, 0,
+		   NULL, 0, earpiece_event,
+		   SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+SND_SOC_DAPM_MIXER("SPKL Boost", SND_SOC_NOPM, 0, 0,
+		   left_speaker_boost, ARRAY_SIZE(left_speaker_boost)),
+SND_SOC_DAPM_MIXER("SPKR Boost", SND_SOC_NOPM, 0, 0,
+		   right_speaker_boost, ARRAY_SIZE(right_speaker_boost)),
+
+SND_SOC_DAPM_PGA("SPKL Driver", WM8993_POWER_MANAGEMENT_1, 12, 0,
+		 NULL, 0),
+SND_SOC_DAPM_PGA("SPKR Driver", WM8993_POWER_MANAGEMENT_1, 13, 0,
+		 NULL, 0),
+
+SND_SOC_DAPM_MIXER("LINEOUT1 Mixer", SND_SOC_NOPM, 0, 0,
+		   line1_mix, ARRAY_SIZE(line1_mix)),
+SND_SOC_DAPM_MIXER("LINEOUT2 Mixer", SND_SOC_NOPM, 0, 0,
+		   line2_mix, ARRAY_SIZE(line2_mix)),
+
+SND_SOC_DAPM_MIXER("LINEOUT1N Mixer", SND_SOC_NOPM, 0, 0,
+		   line1n_mix, ARRAY_SIZE(line1n_mix)),
+SND_SOC_DAPM_MIXER("LINEOUT1P Mixer", SND_SOC_NOPM, 0, 0,
+		   line1p_mix, ARRAY_SIZE(line1p_mix)),
+SND_SOC_DAPM_MIXER("LINEOUT2N Mixer", SND_SOC_NOPM, 0, 0,
+		   line2n_mix, ARRAY_SIZE(line2n_mix)),
+SND_SOC_DAPM_MIXER("LINEOUT2P Mixer", SND_SOC_NOPM, 0, 0,
+		   line2p_mix, ARRAY_SIZE(line2p_mix)),
+
+SND_SOC_DAPM_PGA("LINEOUT1N Driver", WM8993_POWER_MANAGEMENT_3, 13, 0,
+		 NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT1P Driver", WM8993_POWER_MANAGEMENT_3, 12, 0,
+		 NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT2N Driver", WM8993_POWER_MANAGEMENT_3, 11, 0,
+		 NULL, 0),
+SND_SOC_DAPM_PGA("LINEOUT2P Driver", WM8993_POWER_MANAGEMENT_3, 10, 0,
+		 NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("SPKOUTLP"),
+SND_SOC_DAPM_OUTPUT("SPKOUTLN"),
+SND_SOC_DAPM_OUTPUT("SPKOUTRP"),
+SND_SOC_DAPM_OUTPUT("SPKOUTRN"),
+SND_SOC_DAPM_OUTPUT("HPOUT1L"),
+SND_SOC_DAPM_OUTPUT("HPOUT1R"),
+SND_SOC_DAPM_OUTPUT("HPOUT2P"),
+SND_SOC_DAPM_OUTPUT("HPOUT2N"),
+SND_SOC_DAPM_OUTPUT("LINEOUT1P"),
+SND_SOC_DAPM_OUTPUT("LINEOUT1N"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2P"),
+SND_SOC_DAPM_OUTPUT("LINEOUT2N"),
+};
+
+static const struct snd_soc_dapm_route analogue_routes[] = {
+	{ "IN1L PGA", "IN1LP Switch", "IN1LP" },
+	{ "IN1L PGA", "IN1LN Switch", "IN1LN" },
+
+	{ "IN1R PGA", "IN1RP Switch", "IN1RP" },
+	{ "IN1R PGA", "IN1RN Switch", "IN1RN" },
+
+	{ "IN2L PGA", "IN2LP Switch", "IN2LP:VXRN" },
+	{ "IN2L PGA", "IN2LN Switch", "IN2LN" },
+
+	{ "IN2R PGA", "IN2RP Switch", "IN2RP:VXRP" },
+	{ "IN2R PGA", "IN2RN Switch", "IN2RN" },
+
+	{ "Direct Voice", NULL, "IN2LP:VXRN" },
+	{ "Direct Voice", NULL, "IN2RP:VXRP" },
+
+	{ "MIXINL", "IN1L Switch", "IN1L PGA" },
+	{ "MIXINL", "IN2L Switch", "IN2L PGA" },
+	{ "MIXINL", NULL, "Direct Voice" },
+	{ "MIXINL", NULL, "IN1LP" },
+	{ "MIXINL", NULL, "Left Output Mixer" },
+
+	{ "MIXINR", "IN1R Switch", "IN1R PGA" },
+	{ "MIXINR", "IN2R Switch", "IN2R PGA" },
+	{ "MIXINR", NULL, "Direct Voice" },
+	{ "MIXINR", NULL, "IN1RP" },
+	{ "MIXINR", NULL, "Right Output Mixer" },
+
+	{ "ADCL", NULL, "MIXINL" },
+	{ "ADCR", NULL, "MIXINR" },
+
+	{ "Left Output Mixer", "Left Input Switch", "MIXINL" },
+	{ "Left Output Mixer", "Right Input Switch", "MIXINR" },
+	{ "Left Output Mixer", "IN2RN Switch", "IN2RN" },
+	{ "Left Output Mixer", "IN2LN Switch", "IN2LN" },
+	{ "Left Output Mixer", "IN2LP Switch", "IN2LP:VXRN" },
+	{ "Left Output Mixer", "IN1L Switch", "IN1L PGA" },
+	{ "Left Output Mixer", "IN1R Switch", "IN1R PGA" },
+
+	{ "Right Output Mixer", "Left Input Switch", "MIXINL" },
+	{ "Right Output Mixer", "Right Input Switch", "MIXINR" },
+	{ "Right Output Mixer", "IN2LN Switch", "IN2LN" },
+	{ "Right Output Mixer", "IN2RN Switch", "IN2RN" },
+	{ "Right Output Mixer", "IN2RP Switch", "IN2RP:VXRP" },
+	{ "Right Output Mixer", "IN1L Switch", "IN1L PGA" },
+	{ "Right Output Mixer", "IN1R Switch", "IN1R PGA" },
+
+	{ "Left Output PGA", NULL, "Left Output Mixer" },
+	{ "Left Output PGA", NULL, "TOCLK" },
+
+	{ "Right Output PGA", NULL, "Right Output Mixer" },
+	{ "Right Output PGA", NULL, "TOCLK" },
+
+	{ "Earpiece Mixer", "Direct Voice Switch", "Direct Voice" },
+	{ "Earpiece Mixer", "Left Output Switch", "Left Output PGA" },
+	{ "Earpiece Mixer", "Right Output Switch", "Right Output PGA" },
+
+	{ "Earpiece Driver", NULL, "Earpiece Mixer" },
+	{ "HPOUT2N", NULL, "Earpiece Driver" },
+	{ "HPOUT2P", NULL, "Earpiece Driver" },
+
+	{ "SPKL", "Input Switch", "MIXINL" },
+	{ "SPKL", "IN1LP Switch", "IN1LP" },
+	{ "SPKL", "Output Switch", "Left Output Mixer" },
+	{ "SPKL", NULL, "TOCLK" },
+
+	{ "SPKR", "Input Switch", "MIXINR" },
+	{ "SPKR", "IN1RP Switch", "IN1RP" },
+	{ "SPKR", "Output Switch", "Right Output Mixer" },
+	{ "SPKR", NULL, "TOCLK" },
+
+	{ "SPKL Boost", "Direct Voice Switch", "Direct Voice" },
+	{ "SPKL Boost", "SPKL Switch", "SPKL" },
+	{ "SPKL Boost", "SPKR Switch", "SPKR" },
+
+	{ "SPKR Boost", "Direct Voice Switch", "Direct Voice" },
+	{ "SPKR Boost", "SPKR Switch", "SPKR" },
+	{ "SPKR Boost", "SPKL Switch", "SPKL" },
+
+	{ "SPKL Driver", NULL, "SPKL Boost" },
+	{ "SPKL Driver", NULL, "CLK_SYS" },
+
+	{ "SPKR Driver", NULL, "SPKR Boost" },
+	{ "SPKR Driver", NULL, "CLK_SYS" },
+
+	{ "SPKOUTLP", NULL, "SPKL Driver" },
+	{ "SPKOUTLN", NULL, "SPKL Driver" },
+	{ "SPKOUTRP", NULL, "SPKR Driver" },
+	{ "SPKOUTRN", NULL, "SPKR Driver" },
+
+	{ "Left Headphone Mux", "Mixer", "Left Output Mixer" },
+	{ "Right Headphone Mux", "Mixer", "Right Output Mixer" },
+
+	{ "Headphone PGA", NULL, "Left Headphone Mux" },
+	{ "Headphone PGA", NULL, "Right Headphone Mux" },
+	{ "Headphone PGA", NULL, "CLK_SYS" },
+	{ "Headphone PGA", NULL, "Headphone Supply" },
+
+	{ "HPOUT1L", NULL, "Headphone PGA" },
+	{ "HPOUT1R", NULL, "Headphone PGA" },
+
+	{ "LINEOUT1N", NULL, "LINEOUT1N Driver" },
+	{ "LINEOUT1P", NULL, "LINEOUT1P Driver" },
+	{ "LINEOUT2N", NULL, "LINEOUT2N Driver" },
+	{ "LINEOUT2P", NULL, "LINEOUT2P Driver" },
+};
+
+static const struct snd_soc_dapm_route lineout1_diff_routes[] = {
+	{ "LINEOUT1 Mixer", "IN1L Switch", "IN1L PGA" },
+	{ "LINEOUT1 Mixer", "IN1R Switch", "IN1R PGA" },
+	{ "LINEOUT1 Mixer", "Output Switch", "Left Output Mixer" },
+
+	{ "LINEOUT1N Driver", NULL, "LINEOUT1 Mixer" },
+	{ "LINEOUT1P Driver", NULL, "LINEOUT1 Mixer" },
+};
+
+static const struct snd_soc_dapm_route lineout1_se_routes[] = {
+	{ "LINEOUT1N Mixer", "Left Output Switch", "Left Output Mixer" },
+	{ "LINEOUT1N Mixer", "Right Output Switch", "Left Output Mixer" },
+
+	{ "LINEOUT1P Mixer", "Left Output Switch", "Left Output Mixer" },
+
+	{ "LINEOUT1N Driver", NULL, "LINEOUT1N Mixer" },
+	{ "LINEOUT1P Driver", NULL, "LINEOUT1P Mixer" },
+};
+
+static const struct snd_soc_dapm_route lineout2_diff_routes[] = {
+	{ "LINEOUT2 Mixer", "IN2L Switch", "IN2L PGA" },
+	{ "LINEOUT2 Mixer", "IN2R Switch", "IN2R PGA" },
+	{ "LINEOUT2 Mixer", "Output Switch", "Right Output Mixer" },
+
+	{ "LINEOUT2N Driver", NULL, "LINEOUT2 Mixer" },
+	{ "LINEOUT2P Driver", NULL, "LINEOUT2 Mixer" },
+};
+
+static const struct snd_soc_dapm_route lineout2_se_routes[] = {
+	{ "LINEOUT2N Mixer", "Left Output Switch", "Left Output Mixer" },
+	{ "LINEOUT2N Mixer", "Right Output Switch", "Left Output Mixer" },
+
+	{ "LINEOUT2P Mixer", "Right Output Switch", "Right Output Mixer" },
+
+	{ "LINEOUT2N Driver", NULL, "LINEOUT2N Mixer" },
+	{ "LINEOUT2P Driver", NULL, "LINEOUT2P Mixer" },
+};
+
+int wm_hubs_add_analogue_controls(struct snd_soc_codec *codec)
+{
+	/* Latch volume update bits & default ZC on */
+	snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_1_2_VOLUME,
+			    WM8993_IN1_VU, WM8993_IN1_VU);
+	snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_1_2_VOLUME,
+			    WM8993_IN1_VU, WM8993_IN1_VU);
+	snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_3_4_VOLUME,
+			    WM8993_IN2_VU, WM8993_IN2_VU);
+	snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_3_4_VOLUME,
+			    WM8993_IN2_VU, WM8993_IN2_VU);
+
+	snd_soc_update_bits(codec, WM8993_SPEAKER_VOLUME_RIGHT,
+			    WM8993_SPKOUT_VU, WM8993_SPKOUT_VU);
+
+	snd_soc_update_bits(codec, WM8993_LEFT_OUTPUT_VOLUME,
+			    WM8993_HPOUT1L_ZC, WM8993_HPOUT1L_ZC);
+	snd_soc_update_bits(codec, WM8993_RIGHT_OUTPUT_VOLUME,
+			    WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC,
+			    WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC);
+
+	snd_soc_update_bits(codec, WM8993_LEFT_OPGA_VOLUME,
+			    WM8993_MIXOUTL_ZC, WM8993_MIXOUTL_ZC);
+	snd_soc_update_bits(codec, WM8993_RIGHT_OPGA_VOLUME,
+			    WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU,
+			    WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU);
+
+	snd_soc_add_controls(codec, analogue_snd_controls,
+			     ARRAY_SIZE(analogue_snd_controls));
+
+	snd_soc_dapm_new_controls(codec, analogue_dapm_widgets,
+				  ARRAY_SIZE(analogue_dapm_widgets));
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_controls);
+
+int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec,
+				int lineout1_diff, int lineout2_diff)
+{
+	snd_soc_dapm_add_routes(codec, analogue_routes,
+				ARRAY_SIZE(analogue_routes));
+
+	if (lineout1_diff)
+		snd_soc_dapm_add_routes(codec,
+					lineout1_diff_routes,
+					ARRAY_SIZE(lineout1_diff_routes));
+	else
+		snd_soc_dapm_add_routes(codec,
+					lineout1_se_routes,
+					ARRAY_SIZE(lineout1_se_routes));
+
+	if (lineout2_diff)
+		snd_soc_dapm_add_routes(codec,
+					lineout2_diff_routes,
+					ARRAY_SIZE(lineout2_diff_routes));
+	else
+		snd_soc_dapm_add_routes(codec,
+					lineout2_se_routes,
+					ARRAY_SIZE(lineout2_se_routes));
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_routes);
+
+int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *codec,
+				  int lineout1_diff, int lineout2_diff,
+				  int lineout1fb, int lineout2fb,
+				  int jd_scthr, int jd_thr, int micbias1_lvl,
+				  int micbias2_lvl)
+{
+	if (!lineout1_diff)
+		snd_soc_update_bits(codec, WM8993_LINE_MIXER1,
+				    WM8993_LINEOUT1_MODE,
+				    WM8993_LINEOUT1_MODE);
+	if (!lineout2_diff)
+		snd_soc_update_bits(codec, WM8993_LINE_MIXER2,
+				    WM8993_LINEOUT2_MODE,
+				    WM8993_LINEOUT2_MODE);
+
+	/* If the line outputs are differential then we aren't presenting
+	 * VMID as an output and can disable it.
+	 */
+	if (lineout1_diff && lineout2_diff)
+		codec->idle_bias_off = 1;
+
+	if (lineout1fb)
+		snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL,
+				    WM8993_LINEOUT1_FB, WM8993_LINEOUT1_FB);
+
+	if (lineout2fb)
+		snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL,
+				    WM8993_LINEOUT2_FB, WM8993_LINEOUT2_FB);
+
+	snd_soc_update_bits(codec, WM8993_MICBIAS,
+			    WM8993_JD_SCTHR_MASK | WM8993_JD_THR_MASK |
+			    WM8993_MICB1_LVL | WM8993_MICB2_LVL,
+			    jd_scthr << WM8993_JD_SCTHR_SHIFT |
+			    jd_thr << WM8993_JD_THR_SHIFT |
+			    micbias1_lvl |
+			    micbias2_lvl << WM8993_MICB2_LVL_SHIFT);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wm_hubs_handle_analogue_pdata);
+
+MODULE_DESCRIPTION("Shared support for Wolfson hubs products");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/codecs/wm_hubs.h b/linux/sound/soc/codecs/wm_hubs.h
new file mode 100644
index 0000000..e51c166
--- /dev/null
+++ b/linux/sound/soc/codecs/wm_hubs.h
@@ -0,0 +1,36 @@
+/*
+ * wm_hubs.h  --  WM899x common code
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _WM_HUBS_H
+#define _WM_HUBS_H
+
+struct snd_soc_codec;
+
+extern const unsigned int wm_hubs_spkmix_tlv[];
+
+/* This *must* be the first element of the codec->private_data struct */
+struct wm_hubs_data {
+	int dcs_codes;
+	int dcs_readback_mode;
+	int hp_startup_mode;
+};
+
+extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *);
+extern int wm_hubs_add_analogue_routes(struct snd_soc_codec *, int, int);
+extern int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *,
+					 int lineout1_diff, int lineout2_diff,
+					 int lineout1fb, int lineout2fb,
+					 int jd_scthr, int jd_thr,
+					 int micbias1_lvl, int micbias2_lvl);
+
+#endif
diff --git a/linux/sound/soc/davinci/Kconfig b/linux/sound/soc/davinci/Kconfig
new file mode 100644
index 0000000..d8ab952
--- /dev/null
+++ b/linux/sound/soc/davinci/Kconfig
@@ -0,0 +1,103 @@
+config SND_DAVINCI_SOC
+	tristate "SoC Audio for the TI DAVINCI chip"
+	depends on ARCH_DAVINCI
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the DAVINCI AC97 or I2S interface. You will also need
+	  to select the audio interfaces to support below.
+
+config SND_TI81XX_SOC
+	tristate "SoC Audio for the TI81XX chip"
+	depends on ARCH_TI81XX
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the TI81XX I2S interface. You will also need
+	  to select the audio interfaces to support below.
+
+config SND_DAVINCI_SOC_I2S
+	tristate
+
+config SND_DAVINCI_SOC_MCASP
+	tristate
+
+config SND_DAVINCI_SOC_VCIF
+	tristate
+
+config SND_DAVINCI_SOC_EVM
+	tristate "SoC Audio support for DaVinci DM6446, DM355 or DM365 EVM"
+	depends on SND_DAVINCI_SOC
+	depends on MACH_DAVINCI_EVM || MACH_DAVINCI_DM355_EVM || MACH_DAVINCI_DM365_EVM
+	select SND_DAVINCI_SOC_I2S
+	select SND_SOC_TLV320AIC3X
+	help
+	  Say Y if you want to add support for SoC audio on TI
+	  DaVinci DM6446, DM355 or DM365 EVM platforms.
+
+choice
+	prompt "DM365 codec select"
+	depends on SND_DAVINCI_SOC_EVM
+	depends on MACH_DAVINCI_DM365_EVM
+	default SND_DM365_EXTERNAL_CODEC
+
+config SND_DM365_AIC3X_CODEC
+	bool "Audio Codec - AIC3101"
+	help
+	  Say Y if you want to add support for AIC3101 audio codec
+
+config SND_DM365_VOICE_CODEC
+	bool "Voice Codec - CQ93VC"
+	select MFD_DAVINCI_VOICECODEC
+	select SND_DAVINCI_SOC_VCIF
+	select SND_SOC_CQ0093VC
+	help
+	  Say Y if you want to add support for SoC On-chip voice codec
+endchoice
+
+config  SND_DM6467_SOC_EVM
+	tristate "SoC Audio support for DaVinci DM6467 EVM"
+	depends on SND_DAVINCI_SOC && MACH_DAVINCI_DM6467_EVM
+	select SND_DAVINCI_SOC_MCASP
+	select SND_SOC_TLV320AIC3X
+	select SND_SOC_SPDIF
+
+	help
+	  Say Y if you want to add support for SoC audio on TI
+
+config SND_DAVINCI_SOC_SFFSDR
+	tristate "SoC Audio support for SFFSDR"
+	depends on SND_DAVINCI_SOC && MACH_SFFSDR
+	select SND_DAVINCI_SOC_I2S
+	select SND_SOC_PCM3008
+	select SFFSDR_FPGA
+	help
+	  Say Y if you want to add support for SoC audio on
+	  Lyrtech SFFSDR board.
+
+config  SND_DA830_SOC_EVM
+	tristate "SoC Audio support for DA830/OMAP-L137 EVM"
+	depends on SND_DAVINCI_SOC && MACH_DAVINCI_DA830_EVM
+	select SND_DAVINCI_SOC_MCASP
+	select SND_SOC_TLV320AIC3X
+
+	help
+	  Say Y if you want to add support for SoC audio on TI
+	  DA830/OMAP-L137 EVM
+
+config  SND_DA850_SOC_EVM
+	tristate "SoC Audio support for DA850/OMAP-L138 EVM"
+	depends on SND_DAVINCI_SOC && MACH_DAVINCI_DA850_EVM
+	select SND_DAVINCI_SOC_MCASP
+	select SND_SOC_TLV320AIC3X
+	help
+	  Say Y if you want to add support for SoC audio on TI
+	  DA850/OMAP-L138 EVM
+
+config  SND_TI81XX_SOC_EVM
+	tristate "SoC Audio support for TI81XX EVM"
+	depends on SND_TI81XX_SOC
+	select SND_DAVINCI_SOC_MCASP
+	select SND_SOC_TLV320AIC3X
+	help
+	  Say Y if you want to add support for SoC audio on TI
+	  81XX EVM
+
diff --git a/linux/sound/soc/davinci/Makefile b/linux/sound/soc/davinci/Makefile
new file mode 100644
index 0000000..0c53a96
--- /dev/null
+++ b/linux/sound/soc/davinci/Makefile
@@ -0,0 +1,22 @@
+# DAVINCI Platform Support
+snd-soc-davinci-objs := davinci-pcm.o
+snd-soc-davinci-i2s-objs := davinci-i2s.o
+snd-soc-davinci-mcasp-objs:= davinci-mcasp.o
+snd-soc-davinci-vcif-objs:= davinci-vcif.o
+
+obj-$(CONFIG_SND_DAVINCI_SOC) += snd-soc-davinci.o
+obj-$(CONFIG_SND_TI81XX_SOC) += snd-soc-davinci.o
+obj-$(CONFIG_SND_DAVINCI_SOC_I2S) += snd-soc-davinci-i2s.o
+obj-$(CONFIG_SND_DAVINCI_SOC_MCASP) += snd-soc-davinci-mcasp.o
+obj-$(CONFIG_SND_DAVINCI_SOC_VCIF) += snd-soc-davinci-vcif.o
+
+# DAVINCI Machine Support
+snd-soc-evm-objs := davinci-evm.o
+snd-soc-sffsdr-objs := davinci-sffsdr.o
+
+obj-$(CONFIG_SND_DAVINCI_SOC_EVM) += snd-soc-evm.o
+obj-$(CONFIG_SND_DM6467_SOC_EVM) += snd-soc-evm.o
+obj-$(CONFIG_SND_DA830_SOC_EVM) += snd-soc-evm.o
+obj-$(CONFIG_SND_DA850_SOC_EVM) += snd-soc-evm.o
+obj-$(CONFIG_SND_TI81XX_SOC_EVM) += snd-soc-evm.o
+obj-$(CONFIG_SND_DAVINCI_SOC_SFFSDR) += snd-soc-sffsdr.o
diff --git a/linux/sound/soc/davinci/davinci-evm.c b/linux/sound/soc/davinci/davinci-evm.c
new file mode 100644
index 0000000..deafbce
--- /dev/null
+++ b/linux/sound/soc/davinci/davinci-evm.c
@@ -0,0 +1,351 @@
+/*
+ * ASoC driver for TI DAVINCI EVM platform
+ *
+ * Author:      Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright:   (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/dma.h>
+#include <asm/mach-types.h>
+
+#ifndef CONFIG_ARCH_TI81XX
+#include <mach/asp.h>
+#include <mach/edma.h>
+#include <mach/mux.h>
+#else
+#include <plat/asp.h>
+#include <asm/hardware/edma.h>
+#endif
+
+#include "../codecs/tlv320aic3x.h"
+#include "davinci-pcm.h"
+#include "davinci-i2s.h"
+#include "davinci-mcasp.h"
+
+#define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B | \
+		SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF)
+static int evm_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret = 0;
+	unsigned sysclk;
+
+	/* ASP1 on DM355 EVM is clocked by an external oscillator */
+	if (machine_is_davinci_dm355_evm() || machine_is_davinci_dm6467_evm() ||
+	    machine_is_davinci_dm365_evm())
+		sysclk = 27000000;
+
+	/* ASP0 in DM6446 EVM is clocked by U55, as configured by
+	 * board-dm644x-evm.c using GPIOs from U18.  There are six
+	 * options; here we "know" we use a 48 KHz sample rate.
+	 */
+	else if (machine_is_davinci_evm())
+		sysclk = 12288000;
+
+	else if (machine_is_davinci_da830_evm() ||
+				machine_is_davinci_da850_evm() ||
+				machine_is_ti8168evm() ||
+				machine_is_ti8148evm())
+		sysclk = 24576000;
+
+	else
+		return -EINVAL;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, AUDIO_FORMAT);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, AUDIO_FORMAT);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, sysclk, SND_SOC_CLOCK_OUT);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int evm_spdif_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	/* set cpu DAI configuration */
+	return snd_soc_dai_set_fmt(cpu_dai, AUDIO_FORMAT);
+}
+
+static struct snd_soc_ops evm_ops = {
+	.hw_params = evm_hw_params,
+};
+
+static struct snd_soc_ops evm_spdif_ops = {
+	.hw_params = evm_spdif_hw_params,
+};
+
+/* davinci-evm machine dapm widgets */
+static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_LINE("Line Out", NULL),
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+/* davinci-evm machine audio_mapnections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Headphone connected to HPLOUT, HPROUT */
+	{"Headphone Jack", NULL, "HPLOUT"},
+	{"Headphone Jack", NULL, "HPROUT"},
+
+	/* Line Out connected to LLOUT, RLOUT */
+	{"Line Out", NULL, "LLOUT"},
+	{"Line Out", NULL, "RLOUT"},
+
+	/* Mic connected to (MIC3L | MIC3R) */
+	{"MIC3L", NULL, "Mic Bias 2V"},
+	{"MIC3R", NULL, "Mic Bias 2V"},
+	{"Mic Bias 2V", NULL, "Mic Jack"},
+
+	/* Line In connected to (LINE1L | LINE2L), (LINE1R | LINE2R) */
+	{"LINE1L", NULL, "Line In"},
+	{"LINE2L", NULL, "Line In"},
+	{"LINE1R", NULL, "Line In"},
+	{"LINE2R", NULL, "Line In"},
+};
+
+/* Logic for a aic3x as connected on a davinci-evm */
+static int evm_aic3x_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	/* Add davinci-evm specific widgets */
+	snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets,
+				  ARRAY_SIZE(aic3x_dapm_widgets));
+
+	/* Set up davinci-evm specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	/* not connected */
+	snd_soc_dapm_disable_pin(codec, "MONO_LOUT");
+	snd_soc_dapm_disable_pin(codec, "HPLCOM");
+	snd_soc_dapm_disable_pin(codec, "HPRCOM");
+
+	/* always connected */
+	snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+	snd_soc_dapm_enable_pin(codec, "Line Out");
+	snd_soc_dapm_enable_pin(codec, "Mic Jack");
+	snd_soc_dapm_enable_pin(codec, "Line In");
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+/* davinci-evm digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link dm6446_evm_dai = {
+	.name = "TLV320AIC3X",
+	.stream_name = "AIC3X",
+	.cpu_dai_name = "davinci-mcbsp",
+	.codec_dai_name = "tlv320aic3x-hifi",
+	.codec_name = "tlv320aic3x-codec.1-001b",
+	.platform_name = "davinci-pcm-audio",
+	.init = evm_aic3x_init,
+	.ops = &evm_ops,
+};
+
+static struct snd_soc_dai_link dm355_evm_dai = {
+	.name = "TLV320AIC3X",
+	.stream_name = "AIC3X",
+	.cpu_dai_name = "davinci-mcbsp.1",
+	.codec_dai_name = "tlv320aic3x-hifi",
+	.codec_name = "tlv320aic3x-codec.1-001b",
+	.platform_name = "davinci-pcm-audio",
+	.init = evm_aic3x_init,
+	.ops = &evm_ops,
+};
+
+static struct snd_soc_dai_link dm365_evm_dai = {
+#ifdef CONFIG_SND_DM365_AIC3X_CODEC
+	.name = "TLV320AIC3X",
+	.stream_name = "AIC3X",
+	.cpu_dai_name = "davinci-mcbsp",
+	.codec_dai_name = "tlv320aic3x-hifi",
+	.init = evm_aic3x_init,
+	.codec_name = "tlv320aic3x-codec.1-0018",
+	.ops = &evm_ops,
+#elif defined(CONFIG_SND_DM365_VOICE_CODEC)
+	.name = "Voice Codec - CQ93VC",
+	.stream_name = "CQ93",
+	.cpu_dai_name = "davinci-vcif",
+	.codec_dai_name = "cq93vc-hifi",
+	.codec_name = "cq93vc-codec",
+#endif
+	.platform_name = "davinci-pcm-audio",
+};
+
+static struct snd_soc_dai_link dm6467_evm_dai[] = {
+	{
+		.name = "TLV320AIC3X",
+		.stream_name = "AIC3X",
+		.cpu_dai_name= "davinci-mcasp.0",
+		.codec_dai_name = "tlv320aic3x-hifi",
+		.platform_name ="davinci-pcm-audio",
+		.codec_name = "tlv320aic3x-codec.0-001a",
+		.init = evm_aic3x_init,
+		.ops = &evm_ops,
+	},
+	{
+		.name = "McASP",
+		.stream_name = "spdif",
+		.cpu_dai_name= "davinci-mcasp.1",
+		.codec_dai_name = "dit-hifi",
+		.codec_name = "spdif_dit",
+		.platform_name = "davinci-pcm-audio",
+		.ops = &evm_spdif_ops,
+	},
+};
+static struct snd_soc_dai_link da8xx_evm_dai = {
+	.name = "TLV320AIC3X",
+	.stream_name = "AIC3X",
+	.cpu_dai_name= "davinci-mcasp.0",
+	.codec_dai_name = "tlv320aic3x-hifi",
+	.codec_name = "tlv320aic3x-codec.0-001a",
+	.platform_name = "davinci-pcm-audio",
+	.init = evm_aic3x_init,
+	.ops = &evm_ops,
+};
+
+static struct snd_soc_dai_link ti81xx_evm_dai = {
+	.name = "TLV320AIC3X",
+	.stream_name = "AIC3X",
+	.cpu_dai_name= "davinci-mcasp.2",
+	.codec_dai_name = "tlv320aic3x-hifi",
+	.codec_name = "tlv320aic3x-codec.1-0018",
+	.platform_name = "davinci-pcm-audio",
+	.init = evm_aic3x_init,
+	.ops = &evm_ops,
+};
+
+/* davinci dm6446 evm audio machine driver */
+static struct snd_soc_card dm6446_snd_soc_card_evm = {
+	.name = "DaVinci DM6446 EVM",
+	.dai_link = &dm6446_evm_dai,
+	.num_links = 1,
+};
+
+/* davinci dm355 evm audio machine driver */
+static struct snd_soc_card dm355_snd_soc_card_evm = {
+	.name = "DaVinci DM355 EVM",
+	.dai_link = &dm355_evm_dai,
+	.num_links = 1,
+};
+
+/* davinci dm365 evm audio machine driver */
+static struct snd_soc_card dm365_snd_soc_card_evm = {
+	.name = "DaVinci DM365 EVM",
+	.dai_link = &dm365_evm_dai,
+	.num_links = 1,
+};
+
+/* davinci dm6467 evm audio machine driver */
+static struct snd_soc_card dm6467_snd_soc_card_evm = {
+	.name = "DaVinci DM6467 EVM",
+	.dai_link = dm6467_evm_dai,
+	.num_links = ARRAY_SIZE(dm6467_evm_dai),
+};
+
+static struct snd_soc_card da830_snd_soc_card = {
+	.name = "DA830/OMAP-L137 EVM",
+	.dai_link = &da8xx_evm_dai,
+	.num_links = 1,
+};
+
+static struct snd_soc_card da850_snd_soc_card = {
+	.name = "DA850/OMAP-L138 EVM",
+	.dai_link = &da8xx_evm_dai,
+	.num_links = 1,
+};
+
+static struct snd_soc_card ti81xx_snd_soc_card = {
+	.name = "TI81XX EVM",
+	.dai_link = &ti81xx_evm_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *evm_snd_device;
+
+static int __init evm_init(void)
+{
+	struct snd_soc_card *evm_snd_dev_data;
+	int index;
+	int ret;
+
+	if (machine_is_davinci_evm()) {
+		evm_snd_dev_data = &dm6446_snd_soc_card_evm;
+		index = 0;
+	} else if (machine_is_davinci_dm355_evm()) {
+		evm_snd_dev_data = &dm355_snd_soc_card_evm;
+		index = 1;
+	} else if (machine_is_davinci_dm365_evm()) {
+		evm_snd_dev_data = &dm365_snd_soc_card_evm;
+		index = 0;
+	} else if (machine_is_davinci_dm6467_evm()) {
+		evm_snd_dev_data = &dm6467_snd_soc_card_evm;
+		index = 0;
+	} else if (machine_is_davinci_da830_evm()) {
+		evm_snd_dev_data = &da830_snd_soc_card;
+		index = 1;
+	} else if (machine_is_davinci_da850_evm()) {
+		evm_snd_dev_data = &da850_snd_soc_card;
+		index = 0;
+	} else if (machine_is_ti8168evm() || machine_is_ti8148evm()) {
+		evm_snd_dev_data = &ti81xx_snd_soc_card;
+		index = 0;
+	} else
+		return -EINVAL;
+
+	evm_snd_device = platform_device_alloc("soc-audio", index);
+	if (!evm_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(evm_snd_device, evm_snd_dev_data);
+	ret = platform_device_add(evm_snd_device);
+	if (ret)
+		platform_device_put(evm_snd_device);
+
+	return ret;
+}
+
+static void __exit evm_exit(void)
+{
+	platform_device_unregister(evm_snd_device);
+}
+
+module_init(evm_init);
+module_exit(evm_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("TI DAVINCI EVM ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/davinci/davinci-i2s.c b/linux/sound/soc/davinci/davinci-i2s.c
new file mode 100644
index 0000000..d0d60b8
--- /dev/null
+++ b/linux/sound/soc/davinci/davinci-i2s.c
@@ -0,0 +1,788 @@
+/*
+ * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor
+ *
+ * Author:      Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright:   (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/asp.h>
+
+#include "davinci-pcm.h"
+#include "davinci-i2s.h"
+
+
+/*
+ * NOTE:  terminology here is confusing.
+ *
+ *  - This driver supports the "Audio Serial Port" (ASP),
+ *    found on dm6446, dm355, and other DaVinci chips.
+ *
+ *  - But it labels it a "Multi-channel Buffered Serial Port"
+ *    (McBSP) as on older chips like the dm642 ... which was
+ *    backward-compatible, possibly explaining that confusion.
+ *
+ *  - OMAP chips have a controller called McBSP, which is
+ *    incompatible with the DaVinci flavor of McBSP.
+ *
+ *  - Newer DaVinci chips have a controller called McASP,
+ *    incompatible with ASP and with either McBSP.
+ *
+ * In short:  this uses ASP to implement I2S, not McBSP.
+ * And it won't be the only DaVinci implemention of I2S.
+ */
+#define DAVINCI_MCBSP_DRR_REG	0x00
+#define DAVINCI_MCBSP_DXR_REG	0x04
+#define DAVINCI_MCBSP_SPCR_REG	0x08
+#define DAVINCI_MCBSP_RCR_REG	0x0c
+#define DAVINCI_MCBSP_XCR_REG	0x10
+#define DAVINCI_MCBSP_SRGR_REG	0x14
+#define DAVINCI_MCBSP_PCR_REG	0x24
+
+#define DAVINCI_MCBSP_SPCR_RRST		(1 << 0)
+#define DAVINCI_MCBSP_SPCR_RINTM(v)	((v) << 4)
+#define DAVINCI_MCBSP_SPCR_XRST		(1 << 16)
+#define DAVINCI_MCBSP_SPCR_XINTM(v)	((v) << 20)
+#define DAVINCI_MCBSP_SPCR_GRST		(1 << 22)
+#define DAVINCI_MCBSP_SPCR_FRST		(1 << 23)
+#define DAVINCI_MCBSP_SPCR_FREE		(1 << 25)
+
+#define DAVINCI_MCBSP_RCR_RWDLEN1(v)	((v) << 5)
+#define DAVINCI_MCBSP_RCR_RFRLEN1(v)	((v) << 8)
+#define DAVINCI_MCBSP_RCR_RDATDLY(v)	((v) << 16)
+#define DAVINCI_MCBSP_RCR_RFIG		(1 << 18)
+#define DAVINCI_MCBSP_RCR_RWDLEN2(v)	((v) << 21)
+#define DAVINCI_MCBSP_RCR_RFRLEN2(v)	((v) << 24)
+#define DAVINCI_MCBSP_RCR_RPHASE	BIT(31)
+
+#define DAVINCI_MCBSP_XCR_XWDLEN1(v)	((v) << 5)
+#define DAVINCI_MCBSP_XCR_XFRLEN1(v)	((v) << 8)
+#define DAVINCI_MCBSP_XCR_XDATDLY(v)	((v) << 16)
+#define DAVINCI_MCBSP_XCR_XFIG		(1 << 18)
+#define DAVINCI_MCBSP_XCR_XWDLEN2(v)	((v) << 21)
+#define DAVINCI_MCBSP_XCR_XFRLEN2(v)	((v) << 24)
+#define DAVINCI_MCBSP_XCR_XPHASE	BIT(31)
+
+#define DAVINCI_MCBSP_SRGR_FWID(v)	((v) << 8)
+#define DAVINCI_MCBSP_SRGR_FPER(v)	((v) << 16)
+#define DAVINCI_MCBSP_SRGR_FSGM		(1 << 28)
+#define DAVINCI_MCBSP_SRGR_CLKSM	BIT(29)
+
+#define DAVINCI_MCBSP_PCR_CLKRP		(1 << 0)
+#define DAVINCI_MCBSP_PCR_CLKXP		(1 << 1)
+#define DAVINCI_MCBSP_PCR_FSRP		(1 << 2)
+#define DAVINCI_MCBSP_PCR_FSXP		(1 << 3)
+#define DAVINCI_MCBSP_PCR_SCLKME	(1 << 7)
+#define DAVINCI_MCBSP_PCR_CLKRM		(1 << 8)
+#define DAVINCI_MCBSP_PCR_CLKXM		(1 << 9)
+#define DAVINCI_MCBSP_PCR_FSRM		(1 << 10)
+#define DAVINCI_MCBSP_PCR_FSXM		(1 << 11)
+
+enum {
+	DAVINCI_MCBSP_WORD_8 = 0,
+	DAVINCI_MCBSP_WORD_12,
+	DAVINCI_MCBSP_WORD_16,
+	DAVINCI_MCBSP_WORD_20,
+	DAVINCI_MCBSP_WORD_24,
+	DAVINCI_MCBSP_WORD_32,
+};
+
+static const unsigned char data_type[SNDRV_PCM_FORMAT_S32_LE + 1] = {
+	[SNDRV_PCM_FORMAT_S8]		= 1,
+	[SNDRV_PCM_FORMAT_S16_LE]	= 2,
+	[SNDRV_PCM_FORMAT_S32_LE]	= 4,
+};
+
+static const unsigned char asp_word_length[SNDRV_PCM_FORMAT_S32_LE + 1] = {
+	[SNDRV_PCM_FORMAT_S8]		= DAVINCI_MCBSP_WORD_8,
+	[SNDRV_PCM_FORMAT_S16_LE]	= DAVINCI_MCBSP_WORD_16,
+	[SNDRV_PCM_FORMAT_S32_LE]	= DAVINCI_MCBSP_WORD_32,
+};
+
+static const unsigned char double_fmt[SNDRV_PCM_FORMAT_S32_LE + 1] = {
+	[SNDRV_PCM_FORMAT_S8]		= SNDRV_PCM_FORMAT_S16_LE,
+	[SNDRV_PCM_FORMAT_S16_LE]	= SNDRV_PCM_FORMAT_S32_LE,
+};
+
+struct davinci_mcbsp_dev {
+	struct device *dev;
+	struct davinci_pcm_dma_params	dma_params[2];
+	void __iomem			*base;
+#define MOD_DSP_A	0
+#define MOD_DSP_B	1
+	int				mode;
+	u32				pcr;
+	struct clk			*clk;
+	/*
+	 * Combining both channels into 1 element will at least double the
+	 * amount of time between servicing the dma channel, increase
+	 * effiency, and reduce the chance of overrun/underrun. But,
+	 * it will result in the left & right channels being swapped.
+	 *
+	 * If relabeling the left and right channels is not possible,
+	 * you may want to let the codec know to swap them back.
+	 *
+	 * It may allow x10 the amount of time to service dma requests,
+	 * if the codec is master and is using an unnecessarily fast bit clock
+	 * (ie. tlvaic23b), independent of the sample rate. So, having an
+	 * entire frame at once means it can be serviced at the sample rate
+	 * instead of the bit clock rate.
+	 *
+	 * In the now unlikely case that an underrun still
+	 * occurs, both the left and right samples will be repeated
+	 * so that no pops are heard, and the left and right channels
+	 * won't end up being swapped because of the underrun.
+	 */
+	unsigned enable_channel_combine:1;
+
+	unsigned int fmt;
+	int clk_div;
+	int clk_input_pin;
+	bool i2s_accurate_sck;
+};
+
+static inline void davinci_mcbsp_write_reg(struct davinci_mcbsp_dev *dev,
+					   int reg, u32 val)
+{
+	__raw_writel(val, dev->base + reg);
+}
+
+static inline u32 davinci_mcbsp_read_reg(struct davinci_mcbsp_dev *dev, int reg)
+{
+	return __raw_readl(dev->base + reg);
+}
+
+static void toggle_clock(struct davinci_mcbsp_dev *dev, int playback)
+{
+	u32 m = playback ? DAVINCI_MCBSP_PCR_CLKXP : DAVINCI_MCBSP_PCR_CLKRP;
+	/* The clock needs to toggle to complete reset.
+	 * So, fake it by toggling the clk polarity.
+	 */
+	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, dev->pcr ^ m);
+	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, dev->pcr);
+}
+
+static void davinci_mcbsp_start(struct davinci_mcbsp_dev *dev,
+		struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_platform *platform = rtd->platform;
+	int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+	u32 spcr;
+	u32 mask = playback ? DAVINCI_MCBSP_SPCR_XRST : DAVINCI_MCBSP_SPCR_RRST;
+	spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+	if (spcr & mask) {
+		/* start off disabled */
+		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG,
+				spcr & ~mask);
+		toggle_clock(dev, playback);
+	}
+	if (dev->pcr & (DAVINCI_MCBSP_PCR_FSXM | DAVINCI_MCBSP_PCR_FSRM |
+			DAVINCI_MCBSP_PCR_CLKXM | DAVINCI_MCBSP_PCR_CLKRM)) {
+		/* Start the sample generator */
+		spcr |= DAVINCI_MCBSP_SPCR_GRST;
+		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+	}
+
+	if (playback) {
+		/* Stop the DMA to avoid data loss */
+		/* while the transmitter is out of reset to handle XSYNCERR */
+		if (platform->driver->ops->trigger) {
+			int ret = platform->driver->ops->trigger(substream,
+				SNDRV_PCM_TRIGGER_STOP);
+			if (ret < 0)
+				printk(KERN_DEBUG "Playback DMA stop failed\n");
+		}
+
+		/* Enable the transmitter */
+		spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+		spcr |= DAVINCI_MCBSP_SPCR_XRST;
+		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+
+		/* wait for any unexpected frame sync error to occur */
+		udelay(100);
+
+		/* Disable the transmitter to clear any outstanding XSYNCERR */
+		spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+		spcr &= ~DAVINCI_MCBSP_SPCR_XRST;
+		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+		toggle_clock(dev, playback);
+
+		/* Restart the DMA */
+		if (platform->driver->ops->trigger) {
+			int ret = platform->driver->ops->trigger(substream,
+				SNDRV_PCM_TRIGGER_START);
+			if (ret < 0)
+				printk(KERN_DEBUG "Playback DMA start failed\n");
+		}
+	}
+
+	/* Enable transmitter or receiver */
+	spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+	spcr |= mask;
+
+	if (dev->pcr & (DAVINCI_MCBSP_PCR_FSXM | DAVINCI_MCBSP_PCR_FSRM)) {
+		/* Start frame sync */
+		spcr |= DAVINCI_MCBSP_SPCR_FRST;
+	}
+	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+}
+
+static void davinci_mcbsp_stop(struct davinci_mcbsp_dev *dev, int playback)
+{
+	u32 spcr;
+
+	/* Reset transmitter/receiver and sample rate/frame sync generators */
+	spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+	spcr &= ~(DAVINCI_MCBSP_SPCR_GRST | DAVINCI_MCBSP_SPCR_FRST);
+	spcr &= playback ? ~DAVINCI_MCBSP_SPCR_XRST : ~DAVINCI_MCBSP_SPCR_RRST;
+	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+	toggle_clock(dev, playback);
+}
+
+#define DEFAULT_BITPERSAMPLE	16
+
+static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+				   unsigned int fmt)
+{
+	struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+	unsigned int pcr;
+	unsigned int srgr;
+	/* Attention srgr is updated by hw_params! */
+	srgr = DAVINCI_MCBSP_SRGR_FSGM |
+		DAVINCI_MCBSP_SRGR_FPER(DEFAULT_BITPERSAMPLE * 2 - 1) |
+		DAVINCI_MCBSP_SRGR_FWID(DEFAULT_BITPERSAMPLE - 1);
+
+	dev->fmt = fmt;
+	/* set master/slave audio interface */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		/* cpu is master */
+		pcr = DAVINCI_MCBSP_PCR_FSXM |
+			DAVINCI_MCBSP_PCR_FSRM |
+			DAVINCI_MCBSP_PCR_CLKXM |
+			DAVINCI_MCBSP_PCR_CLKRM;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		pcr = DAVINCI_MCBSP_PCR_FSRM | DAVINCI_MCBSP_PCR_FSXM;
+		/*
+		 * Selection of the clock input pin that is the
+		 * input for the Sample Rate Generator.
+		 * McBSP FSR and FSX are driven by the Sample Rate
+		 * Generator.
+		 */
+		switch (dev->clk_input_pin) {
+		case MCBSP_CLKS:
+			pcr |= DAVINCI_MCBSP_PCR_CLKXM |
+				DAVINCI_MCBSP_PCR_CLKRM;
+			break;
+		case MCBSP_CLKR:
+			pcr |= DAVINCI_MCBSP_PCR_SCLKME;
+			break;
+		default:
+			dev_err(dev->dev, "bad clk_input_pin\n");
+			return -EINVAL;
+		}
+
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		/* codec is master */
+		pcr = 0;
+		break;
+	default:
+		printk(KERN_ERR "%s:bad master\n", __func__);
+		return -EINVAL;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		/* Davinci doesn't support TRUE I2S, but some codecs will have
+		 * the left and right channels contiguous. This allows
+		 * dsp_a mode to be used with an inverted normal frame clk.
+		 * If your codec is master and does not have contiguous
+		 * channels, then you will have sound on only one channel.
+		 * Try using a different mode, or codec as slave.
+		 *
+		 * The TLV320AIC33 is an example of a codec where this works.
+		 * It has a variable bit clock frequency allowing it to have
+		 * valid data on every bit clock.
+		 *
+		 * The TLV320AIC23 is an example of a codec where this does not
+		 * work. It has a fixed bit clock frequency with progressively
+		 * more empty bit clock slots between channels as the sample
+		 * rate is lowered.
+		 */
+		fmt ^= SND_SOC_DAIFMT_NB_IF;
+	case SND_SOC_DAIFMT_DSP_A:
+		dev->mode = MOD_DSP_A;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		dev->mode = MOD_DSP_B;
+		break;
+	default:
+		printk(KERN_ERR "%s:bad format\n", __func__);
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		/* CLKRP Receive clock polarity,
+		 *	1 - sampled on rising edge of CLKR
+		 *	valid on rising edge
+		 * CLKXP Transmit clock polarity,
+		 *	1 - clocked on falling edge of CLKX
+		 *	valid on rising edge
+		 * FSRP  Receive frame sync pol, 0 - active high
+		 * FSXP  Transmit frame sync pol, 0 - active high
+		 */
+		pcr |= (DAVINCI_MCBSP_PCR_CLKXP | DAVINCI_MCBSP_PCR_CLKRP);
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		/* CLKRP Receive clock polarity,
+		 *	0 - sampled on falling edge of CLKR
+		 *	valid on falling edge
+		 * CLKXP Transmit clock polarity,
+		 *	0 - clocked on rising edge of CLKX
+		 *	valid on falling edge
+		 * FSRP  Receive frame sync pol, 1 - active low
+		 * FSXP  Transmit frame sync pol, 1 - active low
+		 */
+		pcr |= (DAVINCI_MCBSP_PCR_FSXP | DAVINCI_MCBSP_PCR_FSRP);
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		/* CLKRP Receive clock polarity,
+		 *	1 - sampled on rising edge of CLKR
+		 *	valid on rising edge
+		 * CLKXP Transmit clock polarity,
+		 *	1 - clocked on falling edge of CLKX
+		 *	valid on rising edge
+		 * FSRP  Receive frame sync pol, 1 - active low
+		 * FSXP  Transmit frame sync pol, 1 - active low
+		 */
+		pcr |= (DAVINCI_MCBSP_PCR_CLKXP | DAVINCI_MCBSP_PCR_CLKRP |
+			DAVINCI_MCBSP_PCR_FSXP | DAVINCI_MCBSP_PCR_FSRP);
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		/* CLKRP Receive clock polarity,
+		 *	0 - sampled on falling edge of CLKR
+		 *	valid on falling edge
+		 * CLKXP Transmit clock polarity,
+		 *	0 - clocked on rising edge of CLKX
+		 *	valid on falling edge
+		 * FSRP  Receive frame sync pol, 0 - active high
+		 * FSXP  Transmit frame sync pol, 0 - active high
+		 */
+		break;
+	default:
+		return -EINVAL;
+	}
+	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
+	dev->pcr = pcr;
+	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, pcr);
+	return 0;
+}
+
+static int davinci_i2s_dai_set_clkdiv(struct snd_soc_dai *cpu_dai,
+				int div_id, int div)
+{
+	struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+
+	if (div_id != DAVINCI_MCBSP_CLKGDV)
+		return -ENODEV;
+
+	dev->clk_div = div;
+	return 0;
+}
+
+static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai);
+	struct davinci_pcm_dma_params *dma_params =
+					&dev->dma_params[substream->stream];
+	struct snd_interval *i = NULL;
+	int mcbsp_word_length, master;
+	unsigned int rcr, xcr, srgr, clk_div, freq, framesize;
+	u32 spcr;
+	snd_pcm_format_t fmt;
+	unsigned element_cnt = 1;
+
+	/* general line settings */
+	spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		spcr |= DAVINCI_MCBSP_SPCR_RINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
+		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+	} else {
+		spcr |= DAVINCI_MCBSP_SPCR_XINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
+		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
+	}
+
+	master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK;
+	fmt = params_format(params);
+	mcbsp_word_length = asp_word_length[fmt];
+
+	switch (master) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		freq = clk_get_rate(dev->clk);
+		srgr = DAVINCI_MCBSP_SRGR_FSGM |
+		       DAVINCI_MCBSP_SRGR_CLKSM;
+		srgr |= DAVINCI_MCBSP_SRGR_FWID(mcbsp_word_length *
+						8 - 1);
+		if (dev->i2s_accurate_sck) {
+			clk_div = 256;
+			do {
+				framesize = (freq / (--clk_div)) /
+				params->rate_num *
+					params->rate_den;
+			} while (((framesize < 33) || (framesize > 4095)) &&
+				 (clk_div));
+			clk_div--;
+			srgr |= DAVINCI_MCBSP_SRGR_FPER(framesize - 1);
+		} else {
+			/* symmetric waveforms */
+			clk_div = freq / (mcbsp_word_length * 16) /
+				  params->rate_num * params->rate_den;
+			srgr |= DAVINCI_MCBSP_SRGR_FPER(mcbsp_word_length *
+							16 - 1);
+		}
+		clk_div &= 0xFF;
+		srgr |= clk_div;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		srgr = DAVINCI_MCBSP_SRGR_FSGM;
+		clk_div = dev->clk_div - 1;
+		srgr |= DAVINCI_MCBSP_SRGR_FWID(mcbsp_word_length * 8 - 1);
+		srgr |= DAVINCI_MCBSP_SRGR_FPER(mcbsp_word_length * 16 - 1);
+		clk_div &= 0xFF;
+		srgr |= clk_div;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		/* Clock and frame sync given from external sources */
+		i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
+		srgr = DAVINCI_MCBSP_SRGR_FSGM;
+		srgr |= DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1);
+		pr_debug("%s - %d  FWID set: re-read srgr = %X\n",
+			__func__, __LINE__, snd_interval_value(i) - 1);
+
+		i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS);
+		srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1);
+		break;
+	default:
+		return -EINVAL;
+	}
+	davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
+
+	rcr = DAVINCI_MCBSP_RCR_RFIG;
+	xcr = DAVINCI_MCBSP_XCR_XFIG;
+	if (dev->mode == MOD_DSP_B) {
+		rcr |= DAVINCI_MCBSP_RCR_RDATDLY(0);
+		xcr |= DAVINCI_MCBSP_XCR_XDATDLY(0);
+	} else {
+		rcr |= DAVINCI_MCBSP_RCR_RDATDLY(1);
+		xcr |= DAVINCI_MCBSP_XCR_XDATDLY(1);
+	}
+	/* Determine xfer data type */
+	fmt = params_format(params);
+	if ((fmt > SNDRV_PCM_FORMAT_S32_LE) || !data_type[fmt]) {
+		printk(KERN_WARNING "davinci-i2s: unsupported PCM format\n");
+		return -EINVAL;
+	}
+
+	if (params_channels(params) == 2) {
+		element_cnt = 2;
+		if (double_fmt[fmt] && dev->enable_channel_combine) {
+			element_cnt = 1;
+			fmt = double_fmt[fmt];
+		}
+		switch (master) {
+		case SND_SOC_DAIFMT_CBS_CFS:
+		case SND_SOC_DAIFMT_CBS_CFM:
+			rcr |= DAVINCI_MCBSP_RCR_RFRLEN2(0);
+			xcr |= DAVINCI_MCBSP_XCR_XFRLEN2(0);
+			rcr |= DAVINCI_MCBSP_RCR_RPHASE;
+			xcr |= DAVINCI_MCBSP_XCR_XPHASE;
+			break;
+		case SND_SOC_DAIFMT_CBM_CFM:
+		case SND_SOC_DAIFMT_CBM_CFS:
+			rcr |= DAVINCI_MCBSP_RCR_RFRLEN2(element_cnt - 1);
+			xcr |= DAVINCI_MCBSP_XCR_XFRLEN2(element_cnt - 1);
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	dma_params->acnt = dma_params->data_type = data_type[fmt];
+	dma_params->fifo_level = 0;
+	mcbsp_word_length = asp_word_length[fmt];
+
+	switch (master) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+	case SND_SOC_DAIFMT_CBS_CFM:
+		rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(0);
+		xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(0);
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+	case SND_SOC_DAIFMT_CBM_CFS:
+		rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(element_cnt - 1);
+		xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(element_cnt - 1);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	rcr |= DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) |
+		DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length);
+	xcr |= DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) |
+		DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr);
+	else
+		davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr);
+
+	pr_debug("%s - %d  srgr=%X\n", __func__, __LINE__, srgr);
+	pr_debug("%s - %d  xcr=%X\n", __func__, __LINE__, xcr);
+	pr_debug("%s - %d  rcr=%X\n", __func__, __LINE__, rcr);
+	return 0;
+}
+
+static int davinci_i2s_prepare(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai);
+	int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+	davinci_mcbsp_stop(dev, playback);
+	return 0;
+}
+
+static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+			       struct snd_soc_dai *dai)
+{
+	struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai);
+	int ret = 0;
+	int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		davinci_mcbsp_start(dev, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		davinci_mcbsp_stop(dev, playback);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+
+static int davinci_i2s_startup(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+	snd_soc_dai_set_dma_data(dai, substream, dev->dma_params);
+	return 0;
+}
+
+static void davinci_i2s_shutdown(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai);
+	int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+	davinci_mcbsp_stop(dev, playback);
+}
+
+#define DAVINCI_I2S_RATES	SNDRV_PCM_RATE_8000_96000
+
+static struct snd_soc_dai_ops davinci_i2s_dai_ops = {
+	.startup	= davinci_i2s_startup,
+	.shutdown	= davinci_i2s_shutdown,
+	.prepare	= davinci_i2s_prepare,
+	.trigger	= davinci_i2s_trigger,
+	.hw_params	= davinci_i2s_hw_params,
+	.set_fmt	= davinci_i2s_set_dai_fmt,
+	.set_clkdiv	= davinci_i2s_dai_set_clkdiv,
+
+};
+
+static struct snd_soc_dai_driver davinci_i2s_dai = {
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = DAVINCI_I2S_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = DAVINCI_I2S_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.ops = &davinci_i2s_dai_ops,
+
+};
+
+static int davinci_i2s_probe(struct platform_device *pdev)
+{
+	struct snd_platform_data *pdata = pdev->dev.platform_data;
+	struct davinci_mcbsp_dev *dev;
+	struct resource *mem, *ioarea, *res;
+	enum dma_event_q asp_chan_q = EVENTQ_0;
+	enum dma_event_q ram_chan_q = EVENTQ_1;
+	int ret;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem) {
+		dev_err(&pdev->dev, "no mem resource?\n");
+		return -ENODEV;
+	}
+
+	ioarea = request_mem_region(mem->start, resource_size(mem),
+				    pdev->name);
+	if (!ioarea) {
+		dev_err(&pdev->dev, "McBSP region already claimed\n");
+		return -EBUSY;
+	}
+
+	dev = kzalloc(sizeof(struct davinci_mcbsp_dev), GFP_KERNEL);
+	if (!dev) {
+		ret = -ENOMEM;
+		goto err_release_region;
+	}
+	if (pdata) {
+		dev->enable_channel_combine = pdata->enable_channel_combine;
+		dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].sram_size =
+			pdata->sram_size_playback;
+		dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].sram_size =
+			pdata->sram_size_capture;
+		dev->clk_input_pin = pdata->clk_input_pin;
+		dev->i2s_accurate_sck = pdata->i2s_accurate_sck;
+		asp_chan_q = pdata->asp_chan_q;
+		ram_chan_q = pdata->ram_chan_q;
+	}
+
+	dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].asp_chan_q	= asp_chan_q;
+	dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].ram_chan_q	= ram_chan_q;
+	dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].asp_chan_q	= asp_chan_q;
+	dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].ram_chan_q	= ram_chan_q;
+
+	dev->clk = clk_get(&pdev->dev, NULL);
+	if (IS_ERR(dev->clk)) {
+		ret = -ENODEV;
+		goto err_free_mem;
+	}
+	clk_enable(dev->clk);
+
+	dev->base = ioremap(mem->start, resource_size(mem));
+	if (!dev->base) {
+		dev_err(&pdev->dev, "ioremap failed\n");
+		ret = -ENOMEM;
+		goto err_release_clk;
+	}
+
+	dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].dma_addr =
+	    (dma_addr_t)(mem->start + DAVINCI_MCBSP_DXR_REG);
+
+	dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].dma_addr =
+	    (dma_addr_t)(mem->start + DAVINCI_MCBSP_DRR_REG);
+
+	/* first TX, then RX */
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "no DMA resource\n");
+		ret = -ENXIO;
+		goto err_iounmap;
+	}
+	dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].channel = res->start;
+
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!res) {
+		dev_err(&pdev->dev, "no DMA resource\n");
+		ret = -ENXIO;
+		goto err_iounmap;
+	}
+	dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].channel = res->start;
+	dev->dev = &pdev->dev;
+
+	dev_set_drvdata(&pdev->dev, dev);
+
+	ret = snd_soc_register_dai(&pdev->dev, &davinci_i2s_dai);
+	if (ret != 0)
+		goto err_iounmap;
+
+	return 0;
+
+err_iounmap:
+	iounmap(dev->base);
+err_release_clk:
+	clk_disable(dev->clk);
+	clk_put(dev->clk);
+err_free_mem:
+	kfree(dev);
+err_release_region:
+	release_mem_region(mem->start, resource_size(mem));
+
+	return ret;
+}
+
+static int davinci_i2s_remove(struct platform_device *pdev)
+{
+	struct davinci_mcbsp_dev *dev = dev_get_drvdata(&pdev->dev);
+	struct resource *mem;
+
+	snd_soc_unregister_dai(&pdev->dev);
+	clk_disable(dev->clk);
+	clk_put(dev->clk);
+	dev->clk = NULL;
+	kfree(dev);
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(mem->start, resource_size(mem));
+
+	return 0;
+}
+
+static struct platform_driver davinci_mcbsp_driver = {
+	.probe		= davinci_i2s_probe,
+	.remove		= davinci_i2s_remove,
+	.driver		= {
+		.name	= "davinci-mcbsp",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init davinci_i2s_init(void)
+{
+	return platform_driver_register(&davinci_mcbsp_driver);
+}
+module_init(davinci_i2s_init);
+
+static void __exit davinci_i2s_exit(void)
+{
+	platform_driver_unregister(&davinci_mcbsp_driver);
+}
+module_exit(davinci_i2s_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("TI DAVINCI I2S (McBSP) SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/davinci/davinci-i2s.h b/linux/sound/soc/davinci/davinci-i2s.h
new file mode 100644
index 0000000..48dac3e
--- /dev/null
+++ b/linux/sound/soc/davinci/davinci-i2s.h
@@ -0,0 +1,20 @@
+/*
+ * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor
+ *
+ * Author:      Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright:   (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _DAVINCI_I2S_H
+#define _DAVINCI_I2S_H
+
+/* McBSP dividers */
+enum davinci_mcbsp_div {
+	DAVINCI_MCBSP_CLKGDV,              /* Sample rate generator divider */
+};
+
+#endif
diff --git a/linux/sound/soc/davinci/davinci-mcasp.c b/linux/sound/soc/davinci/davinci-mcasp.c
new file mode 100644
index 0000000..0edd754
--- /dev/null
+++ b/linux/sound/soc/davinci/davinci-mcasp.c
@@ -0,0 +1,1009 @@
+/*
+ * ALSA SoC McASP Audio Layer for TI DAVINCI processor
+ *
+ * Multi-channel Audio Serial Port Driver
+ *
+ * Author: Nirmal Pandey <n-pandey@ti.com>,
+ *         Suresh Rajashekara <suresh.r@ti.com>
+ *         Steve Chen <schen@.mvista.com>
+ *
+ * Copyright:   (C) 2009 MontaVista Software, Inc., <source@mvista.com>
+ * Copyright:   (C) 2009  Texas Instruments, India
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "davinci-pcm.h"
+#include "davinci-mcasp.h"
+
+/*
+ * McASP register definitions
+ */
+#define DAVINCI_MCASP_PID_REG		0x00
+#define DAVINCI_MCASP_PWREMUMGT_REG	0x04
+
+#define DAVINCI_MCASP_PFUNC_REG		0x10
+#define DAVINCI_MCASP_PDIR_REG		0x14
+#define DAVINCI_MCASP_PDOUT_REG		0x18
+#define DAVINCI_MCASP_PDSET_REG		0x1c
+
+#define DAVINCI_MCASP_PDCLR_REG		0x20
+
+#define DAVINCI_MCASP_TLGC_REG		0x30
+#define DAVINCI_MCASP_TLMR_REG		0x34
+
+#define DAVINCI_MCASP_GBLCTL_REG	0x44
+#define DAVINCI_MCASP_AMUTE_REG		0x48
+#define DAVINCI_MCASP_LBCTL_REG		0x4c
+
+#define DAVINCI_MCASP_TXDITCTL_REG	0x50
+
+#define DAVINCI_MCASP_GBLCTLR_REG	0x60
+#define DAVINCI_MCASP_RXMASK_REG	0x64
+#define DAVINCI_MCASP_RXFMT_REG		0x68
+#define DAVINCI_MCASP_RXFMCTL_REG	0x6c
+
+#define DAVINCI_MCASP_ACLKRCTL_REG	0x70
+#define DAVINCI_MCASP_AHCLKRCTL_REG	0x74
+#define DAVINCI_MCASP_RXTDM_REG		0x78
+#define DAVINCI_MCASP_EVTCTLR_REG	0x7c
+
+#define DAVINCI_MCASP_RXSTAT_REG	0x80
+#define DAVINCI_MCASP_RXTDMSLOT_REG	0x84
+#define DAVINCI_MCASP_RXCLKCHK_REG	0x88
+#define DAVINCI_MCASP_REVTCTL_REG	0x8c
+
+#define DAVINCI_MCASP_GBLCTLX_REG	0xa0
+#define DAVINCI_MCASP_TXMASK_REG	0xa4
+#define DAVINCI_MCASP_TXFMT_REG		0xa8
+#define DAVINCI_MCASP_TXFMCTL_REG	0xac
+
+#define DAVINCI_MCASP_ACLKXCTL_REG	0xb0
+#define DAVINCI_MCASP_AHCLKXCTL_REG	0xb4
+#define DAVINCI_MCASP_TXTDM_REG		0xb8
+#define DAVINCI_MCASP_EVTCTLX_REG	0xbc
+
+#define DAVINCI_MCASP_TXSTAT_REG	0xc0
+#define DAVINCI_MCASP_TXTDMSLOT_REG	0xc4
+#define DAVINCI_MCASP_TXCLKCHK_REG	0xc8
+#define DAVINCI_MCASP_XEVTCTL_REG	0xcc
+
+/* Left(even TDM Slot) Channel Status Register File */
+#define DAVINCI_MCASP_DITCSRA_REG	0x100
+/* Right(odd TDM slot) Channel Status Register File */
+#define DAVINCI_MCASP_DITCSRB_REG	0x118
+/* Left(even TDM slot) User Data Register File */
+#define DAVINCI_MCASP_DITUDRA_REG	0x130
+/* Right(odd TDM Slot) User Data Register File */
+#define DAVINCI_MCASP_DITUDRB_REG	0x148
+
+/* Serializer n Control Register */
+#define DAVINCI_MCASP_XRSRCTL_BASE_REG	0x180
+#define DAVINCI_MCASP_XRSRCTL_REG(n)	(DAVINCI_MCASP_XRSRCTL_BASE_REG + \
+						(n << 2))
+
+/* Transmit Buffer for Serializer n */
+#define DAVINCI_MCASP_TXBUF_REG		0x200
+/* Receive Buffer for Serializer n */
+#define DAVINCI_MCASP_RXBUF_REG		0x280
+
+/* McASP FIFO Registers */
+#ifndef CONFIG_ARCH_TI81XX
+#define DAVINCI_MCASP_WFIFOCTL		(0x1010)
+#define DAVINCI_MCASP_WFIFOSTS		(0x1014)
+#define DAVINCI_MCASP_RFIFOCTL		(0x1018)
+#define DAVINCI_MCASP_RFIFOSTS		(0x101C)
+#else
+#define DAVINCI_MCASP_WFIFOCTL		(0x1000)
+#define DAVINCI_MCASP_WFIFOSTS		(0x1004)
+#define DAVINCI_MCASP_RFIFOCTL		(0x1008)
+#define DAVINCI_MCASP_RFIFOSTS		(0x100C)
+#endif
+
+/*
+ * DAVINCI_MCASP_PWREMUMGT_REG - Power Down and Emulation Management
+ *     Register Bits
+ */
+#define MCASP_FREE	BIT(0)
+#define MCASP_SOFT	BIT(1)
+
+/*
+ * DAVINCI_MCASP_PFUNC_REG - Pin Function / GPIO Enable Register Bits
+ */
+#define AXR(n)		(1<<n)
+#define PFUNC_AMUTE	BIT(25)
+#define ACLKX		BIT(26)
+#define AHCLKX		BIT(27)
+#define AFSX		BIT(28)
+#define ACLKR		BIT(29)
+#define AHCLKR		BIT(30)
+#define AFSR		BIT(31)
+
+/*
+ * DAVINCI_MCASP_PDIR_REG - Pin Direction Register Bits
+ */
+#define AXR(n)		(1<<n)
+#define PDIR_AMUTE	BIT(25)
+#define ACLKX		BIT(26)
+#define AHCLKX		BIT(27)
+#define AFSX		BIT(28)
+#define ACLKR		BIT(29)
+#define AHCLKR		BIT(30)
+#define AFSR		BIT(31)
+
+/*
+ * DAVINCI_MCASP_TXDITCTL_REG - Transmit DIT Control Register Bits
+ */
+#define DITEN	BIT(0)	/* Transmit DIT mode enable/disable */
+#define VA	BIT(2)
+#define VB	BIT(3)
+
+/*
+ * DAVINCI_MCASP_TXFMT_REG - Transmit Bitstream Format Register Bits
+ */
+#define TXROT(val)	(val)
+#define TXSEL		BIT(3)
+#define TXSSZ(val)	(val<<4)
+#define TXPBIT(val)	(val<<8)
+#define TXPAD(val)	(val<<13)
+#define TXORD		BIT(15)
+#define FSXDLY(val)	(val<<16)
+
+/*
+ * DAVINCI_MCASP_RXFMT_REG - Receive Bitstream Format Register Bits
+ */
+#define RXROT(val)	(val)
+#define RXSEL		BIT(3)
+#define RXSSZ(val)	(val<<4)
+#define RXPBIT(val)	(val<<8)
+#define RXPAD(val)	(val<<13)
+#define RXORD		BIT(15)
+#define FSRDLY(val)	(val<<16)
+
+/*
+ * DAVINCI_MCASP_TXFMCTL_REG -  Transmit Frame Control Register Bits
+ */
+#define FSXPOL		BIT(0)
+#define AFSXE		BIT(1)
+#define FSXDUR		BIT(4)
+#define FSXMOD(val)	(val<<7)
+
+/*
+ * DAVINCI_MCASP_RXFMCTL_REG - Receive Frame Control Register Bits
+ */
+#define FSRPOL		BIT(0)
+#define AFSRE		BIT(1)
+#define FSRDUR		BIT(4)
+#define FSRMOD(val)	(val<<7)
+
+/*
+ * DAVINCI_MCASP_ACLKXCTL_REG - Transmit Clock Control Register Bits
+ */
+#define ACLKXDIV(val)	(val)
+#define ACLKXE		BIT(5)
+#define TX_ASYNC	BIT(6)
+#define ACLKXPOL	BIT(7)
+
+/*
+ * DAVINCI_MCASP_ACLKRCTL_REG Receive Clock Control Register Bits
+ */
+#define ACLKRDIV(val)	(val)
+#define ACLKRE		BIT(5)
+#define RX_ASYNC	BIT(6)
+#define ACLKRPOL	BIT(7)
+
+/*
+ * DAVINCI_MCASP_AHCLKXCTL_REG - High Frequency Transmit Clock Control
+ *     Register Bits
+ */
+#define AHCLKXDIV(val)	(val)
+#define AHCLKXPOL	BIT(14)
+#define AHCLKXE		BIT(15)
+
+/*
+ * DAVINCI_MCASP_AHCLKRCTL_REG - High Frequency Receive Clock Control
+ *     Register Bits
+ */
+#define AHCLKRDIV(val)	(val)
+#define AHCLKRPOL	BIT(14)
+#define AHCLKRE		BIT(15)
+
+/*
+ * DAVINCI_MCASP_XRSRCTL_BASE_REG -  Serializer Control Register Bits
+ */
+#define MODE(val)	(val)
+#define DISMOD		(val)(val<<2)
+#define TXSTATE		BIT(4)
+#define RXSTATE		BIT(5)
+
+/*
+ * DAVINCI_MCASP_LBCTL_REG - Loop Back Control Register Bits
+ */
+#define LBEN		BIT(0)
+#define LBORD		BIT(1)
+#define LBGENMODE(val)	(val<<2)
+
+/*
+ * DAVINCI_MCASP_TXTDMSLOT_REG - Transmit TDM Slot Register configuration
+ */
+#define TXTDMS(n)	(1<<n)
+
+/*
+ * DAVINCI_MCASP_RXTDMSLOT_REG - Receive TDM Slot Register configuration
+ */
+#define RXTDMS(n)	(1<<n)
+
+/*
+ * DAVINCI_MCASP_GBLCTL_REG -  Global Control Register Bits
+ */
+#define RXCLKRST	BIT(0)	/* Receiver Clock Divider Reset */
+#define RXHCLKRST	BIT(1)	/* Receiver High Frequency Clock Divider */
+#define RXSERCLR	BIT(2)	/* Receiver Serializer Clear */
+#define RXSMRST		BIT(3)	/* Receiver State Machine Reset */
+#define RXFSRST		BIT(4)	/* Frame Sync Generator Reset */
+#define TXCLKRST	BIT(8)	/* Transmitter Clock Divider Reset */
+#define TXHCLKRST	BIT(9)	/* Transmitter High Frequency Clock Divider*/
+#define TXSERCLR	BIT(10)	/* Transmit Serializer Clear */
+#define TXSMRST		BIT(11)	/* Transmitter State Machine Reset */
+#define TXFSRST		BIT(12)	/* Frame Sync Generator Reset */
+
+/*
+ * DAVINCI_MCASP_AMUTE_REG -  Mute Control Register Bits
+ */
+#define MUTENA(val)	(val)
+#define MUTEINPOL	BIT(2)
+#define MUTEINENA	BIT(3)
+#define MUTEIN		BIT(4)
+#define MUTER		BIT(5)
+#define MUTEX		BIT(6)
+#define MUTEFSR		BIT(7)
+#define MUTEFSX		BIT(8)
+#define MUTEBADCLKR	BIT(9)
+#define MUTEBADCLKX	BIT(10)
+#define MUTERXDMAERR	BIT(11)
+#define MUTETXDMAERR	BIT(12)
+
+/*
+ * DAVINCI_MCASP_REVTCTL_REG - Receiver DMA Event Control Register bits
+ */
+#define RXDATADMADIS	BIT(0)
+
+/*
+ * DAVINCI_MCASP_XEVTCTL_REG - Transmitter DMA Event Control Register bits
+ */
+#define TXDATADMADIS	BIT(0)
+
+/*
+ * DAVINCI_MCASP_W[R]FIFOCTL - Write/Read FIFO Control Register bits
+ */
+#define FIFO_ENABLE	BIT(16)
+#define NUMEVT_MASK	(0xFF << 8)
+#define NUMDMA_MASK	(0xFF)
+
+#define DAVINCI_MCASP_NUM_SERIALIZER	16
+
+static inline void mcasp_set_bits(void __iomem *reg, u32 val)
+{
+	__raw_writel(__raw_readl(reg) | val, reg);
+}
+
+static inline void mcasp_clr_bits(void __iomem *reg, u32 val)
+{
+	__raw_writel((__raw_readl(reg) & ~(val)), reg);
+}
+
+static inline void mcasp_mod_bits(void __iomem *reg, u32 val, u32 mask)
+{
+	__raw_writel((__raw_readl(reg) & ~mask) | val, reg);
+}
+
+static inline void mcasp_set_reg(void __iomem *reg, u32 val)
+{
+	__raw_writel(val, reg);
+}
+
+static inline u32 mcasp_get_reg(void __iomem *reg)
+{
+	return (unsigned int)__raw_readl(reg);
+}
+
+static inline void mcasp_set_ctl_reg(void __iomem *regs, u32 val)
+{
+	int i = 0;
+
+	mcasp_set_bits(regs, val);
+
+	/* programming GBLCTL needs to read back from GBLCTL and verfiy */
+	/* loop count is to avoid the lock-up */
+	for (i = 0; i < 1000; i++) {
+		if ((mcasp_get_reg(regs) & val) == val)
+			break;
+	}
+
+	if (i == 1000 && ((mcasp_get_reg(regs) & val) != val))
+		printk(KERN_ERR "GBLCTL write error\n");
+}
+
+static void mcasp_start_rx(struct davinci_audio_dev *dev)
+{
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXHCLKRST);
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXCLKRST);
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSERCLR);
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_RXBUF_REG, 0);
+
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSMRST);
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXFSRST);
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_RXBUF_REG, 0);
+
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSMRST);
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXFSRST);
+}
+
+static void mcasp_start_tx(struct davinci_audio_dev *dev)
+{
+	u8 offset = 0, i;
+	u32 cnt;
+
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST);
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST);
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXSERCLR);
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0);
+
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXSMRST);
+	mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXFSRST);
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0);
+	for (i = 0; i < dev->num_serializer; i++) {
+		if (dev->serial_dir[i] == TX_MODE) {
+			offset = i;
+			break;
+		}
+	}
+
+	/* wait for TX ready */
+	cnt = 0;
+	while (!(mcasp_get_reg(dev->base + DAVINCI_MCASP_XRSRCTL_REG(offset)) &
+		 TXSTATE) && (cnt < 100000))
+		cnt++;
+
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0);
+}
+
+static void davinci_mcasp_start(struct davinci_audio_dev *dev, int stream)
+{
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (dev->txnumevt)	/* enable FIFO */
+			mcasp_set_bits(dev->base + DAVINCI_MCASP_WFIFOCTL,
+								FIFO_ENABLE);
+		mcasp_start_tx(dev);
+	} else {
+		if (dev->rxnumevt)	/* enable FIFO */
+			mcasp_set_bits(dev->base + DAVINCI_MCASP_RFIFOCTL,
+								FIFO_ENABLE);
+		mcasp_start_rx(dev);
+	}
+}
+
+static void mcasp_stop_rx(struct davinci_audio_dev *dev)
+{
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, 0);
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF);
+}
+
+static void mcasp_stop_tx(struct davinci_audio_dev *dev)
+{
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, 0);
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF);
+}
+
+static void davinci_mcasp_stop(struct davinci_audio_dev *dev, int stream)
+{
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (dev->txnumevt)	/* disable FIFO */
+			mcasp_clr_bits(dev->base + DAVINCI_MCASP_WFIFOCTL,
+								FIFO_ENABLE);
+		mcasp_stop_tx(dev);
+	} else {
+		if (dev->rxnumevt)	/* disable FIFO */
+			mcasp_clr_bits(dev->base + DAVINCI_MCASP_RFIFOCTL,
+								FIFO_ENABLE);
+		mcasp_stop_rx(dev);
+	}
+}
+
+static int davinci_mcasp_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+					 unsigned int fmt)
+{
+	struct davinci_audio_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+	void __iomem *base = dev->base;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		/* codec is clock and frame slave */
+		mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE);
+		mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE);
+
+		mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE);
+		mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE);
+
+		mcasp_set_bits(base + DAVINCI_MCASP_PDIR_REG, (0x7 << 26));
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		/* codec is clock master and frame slave */
+		mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE);
+		mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE);
+
+		mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE);
+		mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE);
+
+		mcasp_set_bits(base + DAVINCI_MCASP_PDIR_REG, (0x2d << 26));
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		/* codec is clock and frame master */
+		mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE);
+		mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE);
+
+		mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE);
+		mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE);
+
+		mcasp_clr_bits(base + DAVINCI_MCASP_PDIR_REG, (0x3f << 26));
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_IB_NF:
+		mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
+		mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
+
+		mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
+		mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
+		break;
+
+	case SND_SOC_DAIFMT_NB_IF:
+		mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
+		mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
+
+		mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
+		mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
+		break;
+
+	case SND_SOC_DAIFMT_IB_IF:
+		mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
+		mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
+
+		mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
+		mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
+		break;
+
+	case SND_SOC_DAIFMT_NB_NF:
+		mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
+		mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
+
+		mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
+		mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int davinci_config_channel_size(struct davinci_audio_dev *dev,
+				       int channel_size)
+{
+	u32 fmt = 0;
+	u32 mask, rotate;
+
+	switch (channel_size) {
+	case DAVINCI_AUDIO_WORD_8:
+		fmt = 0x03;
+		rotate = 6;
+		mask = 0x000000ff;
+		break;
+
+	case DAVINCI_AUDIO_WORD_12:
+		fmt = 0x05;
+		rotate = 5;
+		mask = 0x00000fff;
+		break;
+
+	case DAVINCI_AUDIO_WORD_16:
+		fmt = 0x07;
+		rotate = 4;
+		mask = 0x0000ffff;
+		break;
+
+	case DAVINCI_AUDIO_WORD_20:
+		fmt = 0x09;
+		rotate = 3;
+		mask = 0x000fffff;
+		break;
+
+	case DAVINCI_AUDIO_WORD_24:
+		fmt = 0x0B;
+		rotate = 2;
+		mask = 0x00ffffff;
+		break;
+
+	case DAVINCI_AUDIO_WORD_28:
+		fmt = 0x0D;
+		rotate = 1;
+		mask = 0x0fffffff;
+		break;
+
+	case DAVINCI_AUDIO_WORD_32:
+		fmt = 0x0F;
+		rotate = 0;
+		mask = 0xffffffff;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	mcasp_mod_bits(dev->base + DAVINCI_MCASP_RXFMT_REG,
+					RXSSZ(fmt), RXSSZ(0x0F));
+	mcasp_mod_bits(dev->base + DAVINCI_MCASP_TXFMT_REG,
+					TXSSZ(fmt), TXSSZ(0x0F));
+	mcasp_mod_bits(dev->base + DAVINCI_MCASP_TXFMT_REG, TXROT(rotate),
+							TXROT(7));
+	mcasp_mod_bits(dev->base + DAVINCI_MCASP_RXFMT_REG, RXROT(rotate),
+							RXROT(7));
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_TXMASK_REG, mask);
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_RXMASK_REG, mask);
+
+	return 0;
+}
+
+static void davinci_hw_common_param(struct davinci_audio_dev *dev, int stream)
+{
+	int i;
+	u8 tx_ser = 0;
+	u8 rx_ser = 0;
+
+	/* Default configuration */
+	mcasp_set_bits(dev->base + DAVINCI_MCASP_PWREMUMGT_REG, MCASP_SOFT);
+
+	/* All PINS as McASP */
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_PFUNC_REG, 0x00000000);
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		mcasp_set_reg(dev->base + DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF);
+		mcasp_clr_bits(dev->base + DAVINCI_MCASP_XEVTCTL_REG,
+				TXDATADMADIS);
+	} else {
+		mcasp_set_reg(dev->base + DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF);
+		mcasp_clr_bits(dev->base + DAVINCI_MCASP_REVTCTL_REG,
+				RXDATADMADIS);
+	}
+
+	for (i = 0; i < dev->num_serializer; i++) {
+		mcasp_set_bits(dev->base + DAVINCI_MCASP_XRSRCTL_REG(i),
+					dev->serial_dir[i]);
+		if (dev->serial_dir[i] == TX_MODE) {
+			mcasp_set_bits(dev->base + DAVINCI_MCASP_PDIR_REG,
+					AXR(i));
+			tx_ser++;
+		} else if (dev->serial_dir[i] == RX_MODE) {
+			mcasp_clr_bits(dev->base + DAVINCI_MCASP_PDIR_REG,
+					AXR(i));
+			rx_ser++;
+		}
+	}
+
+	if (dev->txnumevt && stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (dev->txnumevt * tx_ser > 64)
+			dev->txnumevt = 1;
+
+		mcasp_mod_bits(dev->base + DAVINCI_MCASP_WFIFOCTL, tx_ser,
+								NUMDMA_MASK);
+		mcasp_mod_bits(dev->base + DAVINCI_MCASP_WFIFOCTL,
+				((dev->txnumevt * tx_ser) << 8), NUMEVT_MASK);
+	}
+
+	if (dev->rxnumevt && stream == SNDRV_PCM_STREAM_CAPTURE) {
+		if (dev->rxnumevt * rx_ser > 64)
+			dev->rxnumevt = 1;
+
+		mcasp_mod_bits(dev->base + DAVINCI_MCASP_RFIFOCTL, rx_ser,
+								NUMDMA_MASK);
+		mcasp_mod_bits(dev->base + DAVINCI_MCASP_RFIFOCTL,
+				((dev->rxnumevt * rx_ser) << 8), NUMEVT_MASK);
+	}
+}
+
+static void davinci_hw_param(struct davinci_audio_dev *dev, int stream)
+{
+	int i, active_slots;
+	u32 mask = 0;
+
+	active_slots = (dev->tdm_slots > 31) ? 32 : dev->tdm_slots;
+	for (i = 0; i < active_slots; i++)
+		mask |= (1 << i);
+
+	mcasp_clr_bits(dev->base + DAVINCI_MCASP_ACLKXCTL_REG, TX_ASYNC);
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		/* bit stream is MSB first  with no delay */
+		/* DSP_B mode */
+		mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKXCTL_REG,
+				AHCLKXE);
+		mcasp_set_reg(dev->base + DAVINCI_MCASP_TXTDM_REG, mask);
+		mcasp_set_bits(dev->base + DAVINCI_MCASP_TXFMT_REG, TXORD);
+
+		if ((dev->tdm_slots >= 2) || (dev->tdm_slots <= 32))
+			mcasp_mod_bits(dev->base + DAVINCI_MCASP_TXFMCTL_REG,
+					FSXMOD(dev->tdm_slots), FSXMOD(0x1FF));
+		else
+			printk(KERN_ERR "playback tdm slot %d not supported\n",
+				dev->tdm_slots);
+
+		mcasp_clr_bits(dev->base + DAVINCI_MCASP_TXFMCTL_REG, FSXDUR);
+	} else {
+		/* bit stream is MSB first with no delay */
+		/* DSP_B mode */
+		mcasp_set_bits(dev->base + DAVINCI_MCASP_RXFMT_REG, RXORD);
+		mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKRCTL_REG,
+				AHCLKRE);
+		mcasp_set_reg(dev->base + DAVINCI_MCASP_RXTDM_REG, mask);
+
+		if ((dev->tdm_slots >= 2) || (dev->tdm_slots <= 32))
+			mcasp_mod_bits(dev->base + DAVINCI_MCASP_RXFMCTL_REG,
+					FSRMOD(dev->tdm_slots), FSRMOD(0x1FF));
+		else
+			printk(KERN_ERR "capture tdm slot %d not supported\n",
+				dev->tdm_slots);
+
+		mcasp_clr_bits(dev->base + DAVINCI_MCASP_RXFMCTL_REG, FSRDUR);
+	}
+}
+
+/* S/PDIF */
+static void davinci_hw_dit_param(struct davinci_audio_dev *dev)
+{
+	/* Set the PDIR for Serialiser as output */
+	mcasp_set_bits(dev->base + DAVINCI_MCASP_PDIR_REG, AFSX);
+
+	/* TXMASK for 24 bits */
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_TXMASK_REG, 0x00FFFFFF);
+
+	/* Set the TX format : 24 bit right rotation, 32 bit slot, Pad 0
+	   and LSB first */
+	mcasp_set_bits(dev->base + DAVINCI_MCASP_TXFMT_REG,
+						TXROT(6) | TXSSZ(15));
+
+	/* Set TX frame synch : DIT Mode, 1 bit width, internal, rising edge */
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_TXFMCTL_REG,
+						AFSXE | FSXMOD(0x180));
+
+	/* Set the TX tdm : for all the slots */
+	mcasp_set_reg(dev->base + DAVINCI_MCASP_TXTDM_REG, 0xFFFFFFFF);
+
+	/* Set the TX clock controls : div = 1 and internal */
+	mcasp_set_bits(dev->base + DAVINCI_MCASP_ACLKXCTL_REG,
+						ACLKXE | TX_ASYNC);
+
+	mcasp_clr_bits(dev->base + DAVINCI_MCASP_XEVTCTL_REG, TXDATADMADIS);
+
+	/* Only 44100 and 48000 are valid, both have the same setting */
+	mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKXCTL_REG, AHCLKXDIV(3));
+
+	/* Enable the DIT */
+	mcasp_set_bits(dev->base + DAVINCI_MCASP_TXDITCTL_REG, DITEN);
+}
+
+static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *params,
+					struct snd_soc_dai *cpu_dai)
+{
+	struct davinci_audio_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+	struct davinci_pcm_dma_params *dma_params =
+					&dev->dma_params[substream->stream];
+	int word_length;
+	u8 fifo_level;
+
+	davinci_hw_common_param(dev, substream->stream);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		fifo_level = dev->txnumevt;
+	else
+		fifo_level = dev->rxnumevt;
+
+	if (dev->op_mode == DAVINCI_MCASP_DIT_MODE)
+		davinci_hw_dit_param(dev);
+	else
+		davinci_hw_param(dev, substream->stream);
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		dma_params->data_type = 1;
+		word_length = DAVINCI_AUDIO_WORD_8;
+		break;
+
+	case SNDRV_PCM_FORMAT_S16_LE:
+		dma_params->data_type = 2;
+		word_length = DAVINCI_AUDIO_WORD_16;
+		break;
+
+	case SNDRV_PCM_FORMAT_S32_LE:
+		dma_params->data_type = 4;
+		word_length = DAVINCI_AUDIO_WORD_32;
+		break;
+
+	default:
+		printk(KERN_WARNING "davinci-mcasp: unsupported PCM format");
+		return -EINVAL;
+	}
+
+	if (dev->version == MCASP_VERSION_2 && !fifo_level)
+		dma_params->acnt = 4;
+	else
+		dma_params->acnt = dma_params->data_type;
+
+	dma_params->fifo_level = fifo_level;
+	davinci_config_channel_size(dev, word_length);
+
+	return 0;
+}
+
+static int davinci_mcasp_trigger(struct snd_pcm_substream *substream,
+				     int cmd, struct snd_soc_dai *cpu_dai)
+{
+	struct davinci_audio_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (!dev->clk_active) {
+			clk_enable(dev->clk);
+			dev->clk_active = 1;
+		}
+		davinci_mcasp_start(dev, substream->stream);
+		break;
+
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		davinci_mcasp_stop(dev, substream->stream);
+		if (dev->clk_active) {
+			clk_disable(dev->clk);
+			dev->clk_active = 0;
+		}
+
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		davinci_mcasp_stop(dev, substream->stream);
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int davinci_mcasp_startup(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct davinci_audio_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+	snd_soc_dai_set_dma_data(dai, substream, dev->dma_params);
+	return 0;
+}
+
+static struct snd_soc_dai_ops davinci_mcasp_dai_ops = {
+	.startup	= davinci_mcasp_startup,
+	.trigger	= davinci_mcasp_trigger,
+	.hw_params	= davinci_mcasp_hw_params,
+	.set_fmt	= davinci_mcasp_set_dai_fmt,
+
+};
+
+static struct snd_soc_dai_driver davinci_mcasp_dai[] = {
+	{
+		.name		= "davinci-mcasp.0",
+		.playback	= {
+			.channels_min	= 2,
+			.channels_max 	= 2,
+			.rates 		= DAVINCI_MCASP_RATES,
+			.formats 	= SNDRV_PCM_FMTBIT_S8 |
+						SNDRV_PCM_FMTBIT_S16_LE |
+						SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.capture 	= {
+			.channels_min 	= 2,
+			.channels_max 	= 2,
+			.rates 		= DAVINCI_MCASP_RATES,
+			.formats	= SNDRV_PCM_FMTBIT_S8 |
+						SNDRV_PCM_FMTBIT_S16_LE |
+						SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.ops 		= &davinci_mcasp_dai_ops,
+
+	},
+	{
+		"davinci-mcasp.1",
+		.playback 	= {
+			.channels_min	= 1,
+			.channels_max	= 384,
+			.rates		= DAVINCI_MCASP_RATES,
+			.formats	= SNDRV_PCM_FMTBIT_S16_LE,
+		},
+		.ops 		= &davinci_mcasp_dai_ops,
+	},
+
+};
+
+static int davinci_mcasp_probe(struct platform_device *pdev)
+{
+	struct davinci_pcm_dma_params *dma_data;
+	struct resource *mem, *ioarea, *res;
+	struct snd_platform_data *pdata;
+	struct davinci_audio_dev *dev;
+	int ret = 0;
+
+	dev = kzalloc(sizeof(struct davinci_audio_dev), GFP_KERNEL);
+	if (!dev)
+		return	-ENOMEM;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem) {
+		dev_err(&pdev->dev, "no mem resource?\n");
+		ret = -ENODEV;
+		goto err_release_data;
+	}
+
+	ioarea = request_mem_region(mem->start,
+			resource_size(mem), pdev->name);
+	if (!ioarea) {
+		dev_err(&pdev->dev, "Audio region already claimed\n");
+		ret = -EBUSY;
+		goto err_release_data;
+	}
+
+	pdata = pdev->dev.platform_data;
+	dev->clk = clk_get(&pdev->dev, NULL);
+	if (IS_ERR(dev->clk)) {
+		ret = -ENODEV;
+		goto err_release_region;
+	}
+
+	clk_enable(dev->clk);
+	dev->clk_active = 1;
+
+	dev->base = ioremap(mem->start, resource_size(mem));
+	if (!dev->base) {
+		dev_err(&pdev->dev, "ioremap failed\n");
+		ret = -ENOMEM;
+		goto err_release_clk;
+	}
+
+	dev->op_mode = pdata->op_mode;
+	dev->tdm_slots = pdata->tdm_slots;
+	dev->num_serializer = pdata->num_serializer;
+	dev->serial_dir = pdata->serial_dir;
+	dev->codec_fmt = pdata->codec_fmt;
+	dev->version = pdata->version;
+	dev->txnumevt = pdata->txnumevt;
+	dev->rxnumevt = pdata->rxnumevt;
+
+	dma_data = &dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK];
+	dma_data->asp_chan_q = pdata->asp_chan_q;
+	dma_data->ram_chan_q = pdata->ram_chan_q;
+	if (cpu_is_ti81xx()) {
+		dma_data->dma_addr = (dma_addr_t) (pdata->tx_dma_offset);
+	} else {
+		dma_data->dma_addr = (dma_addr_t) (pdata->tx_dma_offset + mem->start);
+	}
+
+	/* first TX, then RX */
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "no DMA resource\n");
+		ret = -ENODEV;
+		goto err_iounmap;
+	}
+
+	dma_data->channel = res->start;
+
+	dma_data = &dev->dma_params[SNDRV_PCM_STREAM_CAPTURE];
+	dma_data->asp_chan_q = pdata->asp_chan_q;
+	dma_data->ram_chan_q = pdata->ram_chan_q;
+	if (cpu_is_ti81xx()) {
+		dma_data->dma_addr = (dma_addr_t) (pdata->rx_dma_offset);
+	} else {
+		dma_data->dma_addr = (dma_addr_t) (pdata->rx_dma_offset + mem->start);
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!res) {
+		dev_err(&pdev->dev, "no DMA resource\n");
+		ret = -ENODEV;
+		goto err_iounmap;
+	}
+
+	dma_data->channel = res->start;
+	dev_set_drvdata(&pdev->dev, dev);
+	ret = snd_soc_register_dai(&pdev->dev, &davinci_mcasp_dai[pdata->op_mode]);
+
+	if (ret != 0)
+		goto err_iounmap;
+	return 0;
+
+err_iounmap:
+	iounmap(dev->base);
+err_release_clk:
+	clk_disable(dev->clk);
+	clk_put(dev->clk);
+err_release_region:
+	release_mem_region(mem->start, resource_size(mem));
+err_release_data:
+	kfree(dev);
+
+	return ret;
+}
+
+static int davinci_mcasp_remove(struct platform_device *pdev)
+{
+	struct davinci_audio_dev *dev = dev_get_drvdata(&pdev->dev);
+	struct resource *mem;
+
+	snd_soc_unregister_dai(&pdev->dev);
+	clk_disable(dev->clk);
+	clk_put(dev->clk);
+	dev->clk = NULL;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(mem->start, resource_size(mem));
+
+	kfree(dev);
+
+	return 0;
+}
+
+static struct platform_driver davinci_mcasp_driver = {
+	.probe		= davinci_mcasp_probe,
+	.remove		= davinci_mcasp_remove,
+	.driver		= {
+		.name	= "davinci-mcasp",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init davinci_mcasp_init(void)
+{
+	return platform_driver_register(&davinci_mcasp_driver);
+}
+module_init(davinci_mcasp_init);
+
+static void __exit davinci_mcasp_exit(void)
+{
+	platform_driver_unregister(&davinci_mcasp_driver);
+}
+module_exit(davinci_mcasp_exit);
+
+MODULE_AUTHOR("Steve Chen");
+MODULE_DESCRIPTION("TI DAVINCI McASP SoC Interface");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/davinci/davinci-mcasp.h b/linux/sound/soc/davinci/davinci-mcasp.h
new file mode 100644
index 0000000..87f29d9
--- /dev/null
+++ b/linux/sound/soc/davinci/davinci-mcasp.h
@@ -0,0 +1,65 @@
+/*
+ * ALSA SoC McASP Audio Layer for TI DAVINCI processor
+ *
+ * MCASP related definitions
+ *
+ * Author: Nirmal Pandey <n-pandey@ti.com>,
+ *         Suresh Rajashekara <suresh.r@ti.com>
+ *         Steve Chen <schen@.mvista.com>
+ *
+ * Copyright:   (C) 2009 MontaVista Software, Inc., <source@mvista.com>
+ * Copyright:   (C) 2009  Texas Instruments, India
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef DAVINCI_MCASP_H
+#define DAVINCI_MCASP_H
+
+#include <linux/io.h>
+
+#ifndef CONFIG_ARCH_TI81XX
+#include <mach/asp.h>
+#else
+#include <plat/asp.h>
+#endif
+
+#include "davinci-pcm.h"
+
+#define DAVINCI_MCASP_RATES	SNDRV_PCM_RATE_8000_96000
+#define DAVINCI_MCASP_I2S_DAI	0
+#define DAVINCI_MCASP_DIT_DAI	1
+
+enum {
+	DAVINCI_AUDIO_WORD_8 = 0,
+	DAVINCI_AUDIO_WORD_12,
+	DAVINCI_AUDIO_WORD_16,
+	DAVINCI_AUDIO_WORD_20,
+	DAVINCI_AUDIO_WORD_24,
+	DAVINCI_AUDIO_WORD_32,
+	DAVINCI_AUDIO_WORD_28,  /* This is only valid for McASP */
+};
+
+struct davinci_audio_dev {
+	struct davinci_pcm_dma_params dma_params[2];
+	void __iomem *base;
+	int sample_rate;
+	struct clk *clk;
+	unsigned int codec_fmt;
+	u8 clk_active;
+
+	/* McASP specific data */
+	int	tdm_slots;
+	u8	op_mode;
+	u8	num_serializer;
+	u8	*serial_dir;
+	u8	version;
+
+	/* McASP FIFO related */
+	u8	txnumevt;
+	u8	rxnumevt;
+};
+
+#endif	/* DAVINCI_MCASP_H */
diff --git a/linux/sound/soc/davinci/davinci-pcm.c b/linux/sound/soc/davinci/davinci-pcm.c
new file mode 100644
index 0000000..2621605
--- /dev/null
+++ b/linux/sound/soc/davinci/davinci-pcm.c
@@ -0,0 +1,897 @@
+/*
+ * ALSA PCM interface for the TI DAVINCI processor
+ *
+ * Author:      Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright:   (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ * added SRAM ping/pong (C) 2008 Troy Kisky <troy.kisky@boundarydevices.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/kernel.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+
+#ifndef CONFIG_ARCH_TI81XX
+#include <mach/edma.h>
+#include <mach/sram.h>
+#else
+#include <asm/hardware/edma.h>
+#endif
+
+
+#include "davinci-pcm.h"
+
+#ifdef DEBUG
+static void print_buf_info(int slot, char *name)
+{
+	struct edmacc_param p;
+	if (slot < 0)
+		return;
+	edma_read_slot(slot, &p);
+	printk(KERN_DEBUG "%s: 0x%x, opt=%x, src=%x, a_b_cnt=%x dst=%x\n",
+			name, slot, p.opt, p.src, p.a_b_cnt, p.dst);
+	printk(KERN_DEBUG "    src_dst_bidx=%x link_bcntrld=%x src_dst_cidx=%x ccnt=%x\n",
+			p.src_dst_bidx, p.link_bcntrld, p.src_dst_cidx, p.ccnt);
+}
+#else
+static void print_buf_info(int slot, char *name)
+{
+}
+#endif
+
+static struct snd_pcm_hardware pcm_hardware_playback = {
+	.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+	.formats = (SNDRV_PCM_FMTBIT_S16_LE),
+	.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+		  SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
+		  SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+		  SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
+		  SNDRV_PCM_RATE_KNOT),
+	.rate_min = 8000,
+	.rate_max = 96000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = 128 * 1024,
+	.period_bytes_min = 32,
+	.period_bytes_max = 8 * 1024,
+	.periods_min = 16,
+	.periods_max = 255,
+	.fifo_size = 0,
+};
+
+static struct snd_pcm_hardware pcm_hardware_capture = {
+	.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_PAUSE),
+	.formats = (SNDRV_PCM_FMTBIT_S16_LE),
+	.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+		  SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
+		  SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+		  SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
+		  SNDRV_PCM_RATE_KNOT),
+	.rate_min = 8000,
+	.rate_max = 96000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = 128 * 1024,
+	.period_bytes_min = 32,
+	.period_bytes_max = 8 * 1024,
+	.periods_min = 16,
+	.periods_max = 255,
+	.fifo_size = 0,
+};
+
+/*
+ * How ping/pong works....
+ *
+ * Playback:
+ * ram_params - copys 2*ping_size from start of SDRAM to iram,
+ * 	links to ram_link2
+ * ram_link2 - copys rest of SDRAM to iram in ping_size units,
+ * 	links to ram_link
+ * ram_link - copys entire SDRAM to iram in ping_size uints,
+ * 	links to self
+ *
+ * asp_params - same as asp_link[0]
+ * asp_link[0] - copys from lower half of iram to asp port
+ * 	links to asp_link[1], triggers iram copy event on completion
+ * asp_link[1] - copys from upper half of iram to asp port
+ * 	links to asp_link[0], triggers iram copy event on completion
+ * 	triggers interrupt only needed to let upper SOC levels update position
+ * 	in stream on completion
+ *
+ * When playback is started:
+ * 	ram_params started
+ * 	asp_params started
+ *
+ * Capture:
+ * ram_params - same as ram_link,
+ * 	links to ram_link
+ * ram_link - same as playback
+ * 	links to self
+ *
+ * asp_params - same as playback
+ * asp_link[0] - same as playback
+ * asp_link[1] - same as playback
+ *
+ * When capture is started:
+ * 	asp_params started
+ */
+struct davinci_runtime_data {
+	spinlock_t lock;
+	int period;		/* current DMA period */
+	int asp_channel;	/* Master DMA channel */
+	int asp_link[2];	/* asp parameter link channel, ping/pong */
+	struct davinci_pcm_dma_params *params;	/* DMA params */
+	int ram_channel;
+	int ram_link;
+	int ram_link2;
+	struct edmacc_param asp_params;
+	struct edmacc_param ram_params;
+};
+
+/*
+ * Not used with ping/pong
+ */
+static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream)
+{
+	struct davinci_runtime_data *prtd = substream->runtime->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int link = prtd->asp_link[0];
+	unsigned int period_size;
+	unsigned int dma_offset;
+	dma_addr_t dma_pos;
+	dma_addr_t src, dst;
+	unsigned short src_bidx, dst_bidx;
+	unsigned short src_cidx, dst_cidx;
+	unsigned int data_type;
+	unsigned short acnt;
+	unsigned int count;
+	unsigned int fifo_level;
+
+	period_size = snd_pcm_lib_period_bytes(substream);
+	dma_offset = prtd->period * period_size;
+	dma_pos = runtime->dma_addr + dma_offset;
+	fifo_level = prtd->params->fifo_level;
+
+	pr_debug("davinci_pcm: audio_set_dma_params_play channel = %d "
+		"dma_ptr = %x period_size=%x\n", link, dma_pos, period_size);
+
+	data_type = prtd->params->data_type;
+	count = period_size / data_type;
+	if (fifo_level)
+		count /= fifo_level;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		src = dma_pos;
+		dst = prtd->params->dma_addr;
+		src_bidx = data_type;
+		dst_bidx = 0;
+		src_cidx = data_type * fifo_level;
+		dst_cidx = 0;
+	} else {
+		src = prtd->params->dma_addr;
+		dst = dma_pos;
+		src_bidx = 0;
+		dst_bidx = data_type;
+		src_cidx = 0;
+		dst_cidx = data_type * fifo_level;
+	}
+
+	acnt = prtd->params->acnt;
+	edma_set_src(link, src, INCR, W8BIT);
+	edma_set_dest(link, dst, INCR, W8BIT);
+
+	edma_set_src_index(link, src_bidx, src_cidx);
+	edma_set_dest_index(link, dst_bidx, dst_cidx);
+
+	if (!fifo_level)
+		edma_set_transfer_params(link, acnt, count, 1, 0, ASYNC);
+	else
+		edma_set_transfer_params(link, acnt, fifo_level, count,
+							fifo_level, ABSYNC);
+
+	prtd->period++;
+	if (unlikely(prtd->period >= runtime->periods))
+		prtd->period = 0;
+}
+
+static void davinci_pcm_dma_irq(unsigned link, u16 ch_status, void *data)
+{
+	struct snd_pcm_substream *substream = data;
+	struct davinci_runtime_data *prtd = substream->runtime->private_data;
+
+	print_buf_info(prtd->ram_channel, "i ram_channel");
+	pr_debug("davinci_pcm: link=%d, status=0x%x\n", link, ch_status);
+
+	if (unlikely(ch_status != DMA_COMPLETE))
+		return;
+
+	if (snd_pcm_running(substream)) {
+		if (prtd->ram_channel < 0) {
+			/* No ping/pong must fix up link dma data*/
+			spin_lock(&prtd->lock);
+			davinci_pcm_enqueue_dma(substream);
+			spin_unlock(&prtd->lock);
+		}
+		snd_pcm_period_elapsed(substream);
+	}
+}
+
+static int allocate_sram(struct snd_pcm_substream *substream, unsigned size,
+		struct snd_pcm_hardware *ppcm)
+{
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+#if !defined(CONFIG_ARCH_TI81XX)
+	struct snd_dma_buffer *iram_dma = NULL;
+	dma_addr_t iram_phys = 0;
+	void *iram_virt = NULL;
+#endif
+	if (buf->private_data || !size)
+		return 0;
+
+#if !defined(CONFIG_ARCH_TI81XX)
+	ppcm->period_bytes_max = size;
+	iram_virt = sram_alloc(size, &iram_phys);
+	if (!iram_virt)
+		goto exit1;
+	iram_dma = kzalloc(sizeof(*iram_dma), GFP_KERNEL);
+	if (!iram_dma)
+		goto exit2;
+	iram_dma->area = iram_virt;
+	iram_dma->addr = iram_phys;
+	memset(iram_dma->area, 0, size);
+	iram_dma->bytes = size;
+	buf->private_data = iram_dma;
+	return 0;
+exit2:
+	if (iram_virt)
+		sram_free(iram_virt, size);
+exit1:
+	return -ENOMEM;
+#else
+	return 0;
+#endif
+}
+
+/*
+ * Only used with ping/pong.
+ * This is called after runtime->dma_addr, period_bytes and data_type are valid
+ */
+static int ping_pong_dma_setup(struct snd_pcm_substream *substream)
+{
+	unsigned short ram_src_cidx, ram_dst_cidx;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct davinci_runtime_data *prtd = runtime->private_data;
+	struct snd_dma_buffer *iram_dma =
+		(struct snd_dma_buffer *)substream->dma_buffer.private_data;
+	struct davinci_pcm_dma_params *params = prtd->params;
+	unsigned int data_type = params->data_type;
+	unsigned int acnt = params->acnt;
+	/* divide by 2 for ping/pong */
+	unsigned int ping_size = snd_pcm_lib_period_bytes(substream) >> 1;
+	int link = prtd->asp_link[1];
+	unsigned int fifo_level = prtd->params->fifo_level;
+	unsigned int count;
+	if ((data_type == 0) || (data_type > 4)) {
+		printk(KERN_ERR "%s: data_type=%i\n", __func__, data_type);
+		return -EINVAL;
+	}
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		dma_addr_t asp_src_pong = iram_dma->addr + ping_size;
+		ram_src_cidx = ping_size;
+		ram_dst_cidx = -ping_size;
+		edma_set_src(link, asp_src_pong, INCR, W8BIT);
+
+		link = prtd->asp_link[0];
+		edma_set_src_index(link, data_type, data_type * fifo_level);
+		link = prtd->asp_link[1];
+		edma_set_src_index(link, data_type, data_type * fifo_level);
+
+		link = prtd->ram_link;
+		edma_set_src(link, runtime->dma_addr, INCR, W32BIT);
+	} else {
+		dma_addr_t asp_dst_pong = iram_dma->addr + ping_size;
+		ram_src_cidx = -ping_size;
+		ram_dst_cidx = ping_size;
+		edma_set_dest(link, asp_dst_pong, INCR, W8BIT);
+
+		link = prtd->asp_link[0];
+		edma_set_dest_index(link, data_type, data_type * fifo_level);
+		link = prtd->asp_link[1];
+		edma_set_dest_index(link, data_type, data_type * fifo_level);
+
+		link = prtd->ram_link;
+		edma_set_dest(link, runtime->dma_addr, INCR, W32BIT);
+	}
+
+	if (!fifo_level) {
+		count = ping_size / data_type;
+		edma_set_transfer_params(prtd->asp_link[0], acnt, count,
+				1, 0, ASYNC);
+		edma_set_transfer_params(prtd->asp_link[1], acnt, count,
+				1, 0, ASYNC);
+	} else {
+		count = ping_size / (data_type * fifo_level);
+		edma_set_transfer_params(prtd->asp_link[0], acnt, fifo_level,
+				count, fifo_level, ABSYNC);
+		edma_set_transfer_params(prtd->asp_link[1], acnt, fifo_level,
+				count, fifo_level, ABSYNC);
+	}
+
+	link = prtd->ram_link;
+	edma_set_src_index(link, ping_size, ram_src_cidx);
+	edma_set_dest_index(link, ping_size, ram_dst_cidx);
+	edma_set_transfer_params(link, ping_size, 2,
+			runtime->periods, 2, ASYNC);
+
+	/* init master params */
+	edma_read_slot(prtd->asp_link[0], &prtd->asp_params);
+	edma_read_slot(prtd->ram_link, &prtd->ram_params);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		struct edmacc_param p_ram;
+		/* Copy entire iram buffer before playback started */
+		prtd->ram_params.a_b_cnt = (1 << 16) | (ping_size << 1);
+		/* 0 dst_bidx */
+		prtd->ram_params.src_dst_bidx = (ping_size << 1);
+		/* 0 dst_cidx */
+		prtd->ram_params.src_dst_cidx = (ping_size << 1);
+		prtd->ram_params.ccnt = 1;
+
+		/* Skip 1st period */
+		edma_read_slot(prtd->ram_link, &p_ram);
+		p_ram.src += (ping_size << 1);
+		p_ram.ccnt -= 1;
+		edma_write_slot(prtd->ram_link2, &p_ram);
+		/*
+		 * When 1st started, ram -> iram dma channel will fill the
+		 * entire iram.  Then, whenever a ping/pong asp buffer finishes,
+		 * 1/2 iram will be filled.
+		 */
+		prtd->ram_params.link_bcntrld =
+			EDMA_CHAN_SLOT(prtd->ram_link2) << 5;
+	}
+	return 0;
+}
+
+/* 1 asp tx or rx channel using 2 parameter channels
+ * 1 ram to/from iram channel using 1 parameter channel
+ *
+ * Playback
+ * ram copy channel kicks off first,
+ * 1st ram copy of entire iram buffer completion kicks off asp channel
+ * asp tcc always kicks off ram copy of 1/2 iram buffer
+ *
+ * Record
+ * asp channel starts, tcc kicks off ram copy
+ */
+static int request_ping_pong(struct snd_pcm_substream *substream,
+		struct davinci_runtime_data *prtd,
+		struct snd_dma_buffer *iram_dma)
+{
+	dma_addr_t asp_src_ping;
+	dma_addr_t asp_dst_ping;
+	int link;
+	struct davinci_pcm_dma_params *params = prtd->params;
+
+	/* Request ram master channel */
+	link = prtd->ram_channel = edma_alloc_channel(EDMA_CHANNEL_ANY,
+				  davinci_pcm_dma_irq, substream,
+				  prtd->params->ram_chan_q);
+	if (link < 0)
+		goto exit1;
+
+	/* Request ram link channel */
+	link = prtd->ram_link = edma_alloc_slot(
+			EDMA_CTLR(prtd->ram_channel), EDMA_SLOT_ANY);
+	if (link < 0)
+		goto exit2;
+
+	link = prtd->asp_link[1] = edma_alloc_slot(
+			EDMA_CTLR(prtd->asp_channel), EDMA_SLOT_ANY);
+	if (link < 0)
+		goto exit3;
+
+	prtd->ram_link2 = -1;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		link = prtd->ram_link2 = edma_alloc_slot(
+			EDMA_CTLR(prtd->ram_channel), EDMA_SLOT_ANY);
+		if (link < 0)
+			goto exit4;
+	}
+	/* circle ping-pong buffers */
+	edma_link(prtd->asp_link[0], prtd->asp_link[1]);
+	edma_link(prtd->asp_link[1], prtd->asp_link[0]);
+	/* circle ram buffers */
+	edma_link(prtd->ram_link, prtd->ram_link);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		asp_src_ping = iram_dma->addr;
+		asp_dst_ping = params->dma_addr;	/* fifo */
+	} else {
+		asp_src_ping = params->dma_addr;	/* fifo */
+		asp_dst_ping = iram_dma->addr;
+	}
+	/* ping */
+	link = prtd->asp_link[0];
+	edma_set_src(link, asp_src_ping, INCR, W16BIT);
+	edma_set_dest(link, asp_dst_ping, INCR, W16BIT);
+	edma_set_src_index(link, 0, 0);
+	edma_set_dest_index(link, 0, 0);
+
+	edma_read_slot(link, &prtd->asp_params);
+	prtd->asp_params.opt &= ~(TCCMODE | EDMA_TCC(0x3f) | TCINTEN);
+	prtd->asp_params.opt |= TCCHEN | EDMA_TCC(prtd->ram_channel & 0x3f);
+	edma_write_slot(link, &prtd->asp_params);
+
+	/* pong */
+	link = prtd->asp_link[1];
+	edma_set_src(link, asp_src_ping, INCR, W16BIT);
+	edma_set_dest(link, asp_dst_ping, INCR, W16BIT);
+	edma_set_src_index(link, 0, 0);
+	edma_set_dest_index(link, 0, 0);
+
+	edma_read_slot(link, &prtd->asp_params);
+	prtd->asp_params.opt &= ~(TCCMODE | EDMA_TCC(0x3f));
+	/* interrupt after every pong completion */
+	prtd->asp_params.opt |= TCINTEN | TCCHEN |
+		EDMA_TCC(EDMA_CHAN_SLOT(prtd->ram_channel));
+	edma_write_slot(link, &prtd->asp_params);
+
+	/* ram */
+	link = prtd->ram_link;
+	edma_set_src(link, iram_dma->addr, INCR, W32BIT);
+	edma_set_dest(link, iram_dma->addr, INCR, W32BIT);
+	pr_debug("%s: audio dma channels/slots in use for ram:%u %u %u,"
+		"for asp:%u %u %u\n", __func__,
+		prtd->ram_channel, prtd->ram_link, prtd->ram_link2,
+		prtd->asp_channel, prtd->asp_link[0],
+		prtd->asp_link[1]);
+	return 0;
+exit4:
+	edma_free_channel(prtd->asp_link[1]);
+	prtd->asp_link[1] = -1;
+exit3:
+	edma_free_channel(prtd->ram_link);
+	prtd->ram_link = -1;
+exit2:
+	edma_free_channel(prtd->ram_channel);
+	prtd->ram_channel = -1;
+exit1:
+	return link;
+}
+
+static int davinci_pcm_dma_request(struct snd_pcm_substream *substream)
+{
+	struct snd_dma_buffer *iram_dma;
+	struct davinci_runtime_data *prtd = substream->runtime->private_data;
+	struct davinci_pcm_dma_params *params = prtd->params;
+	int link;
+
+	if (!params)
+		return -ENODEV;
+
+	/* Request asp master DMA channel */
+	link = prtd->asp_channel = edma_alloc_channel(params->channel,
+			davinci_pcm_dma_irq, substream,
+			prtd->params->asp_chan_q);
+	if (link < 0)
+		goto exit1;
+
+	/* Request asp link channels */
+	link = prtd->asp_link[0] = edma_alloc_slot(
+			EDMA_CTLR(prtd->asp_channel), EDMA_SLOT_ANY);
+	if (link < 0)
+		goto exit2;
+
+	iram_dma = (struct snd_dma_buffer *)substream->dma_buffer.private_data;
+	if (iram_dma) {
+		if (request_ping_pong(substream, prtd, iram_dma) == 0)
+			return 0;
+		printk(KERN_WARNING "%s: dma channel allocation failed,"
+				"not using sram\n", __func__);
+	}
+
+	/* Issue transfer completion IRQ when the channel completes a
+	 * transfer, then always reload from the same slot (by a kind
+	 * of loopback link).  The completion IRQ handler will update
+	 * the reload slot with a new buffer.
+	 *
+	 * REVISIT save p_ram here after setting up everything except
+	 * the buffer and its length (ccnt) ... use it as a template
+	 * so davinci_pcm_enqueue_dma() takes less time in IRQ.
+	 */
+	edma_read_slot(link, &prtd->asp_params);
+	prtd->asp_params.opt |= TCINTEN |
+		EDMA_TCC(EDMA_CHAN_SLOT(prtd->asp_channel));
+	prtd->asp_params.link_bcntrld = EDMA_CHAN_SLOT(link) << 5;
+	edma_write_slot(link, &prtd->asp_params);
+	return 0;
+exit2:
+	edma_free_channel(prtd->asp_channel);
+	prtd->asp_channel = -1;
+exit1:
+	return link;
+}
+
+static int davinci_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct davinci_runtime_data *prtd = substream->runtime->private_data;
+	int ret = 0;
+
+	spin_lock(&prtd->lock);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		edma_resume(prtd->asp_channel);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		edma_pause(prtd->asp_channel);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	spin_unlock(&prtd->lock);
+
+	return ret;
+}
+
+static int davinci_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct davinci_runtime_data *prtd = substream->runtime->private_data;
+
+	if (prtd->ram_channel >= 0) {
+		int ret = ping_pong_dma_setup(substream);
+		if (ret < 0)
+			return ret;
+
+		edma_write_slot(prtd->ram_channel, &prtd->ram_params);
+		edma_write_slot(prtd->asp_channel, &prtd->asp_params);
+
+		print_buf_info(prtd->ram_channel, "ram_channel");
+		print_buf_info(prtd->ram_link, "ram_link");
+		print_buf_info(prtd->ram_link2, "ram_link2");
+		print_buf_info(prtd->asp_channel, "asp_channel");
+		print_buf_info(prtd->asp_link[0], "asp_link[0]");
+		print_buf_info(prtd->asp_link[1], "asp_link[1]");
+
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			/* copy 1st iram buffer */
+			edma_start(prtd->ram_channel);
+		}
+		edma_start(prtd->asp_channel);
+		return 0;
+	}
+	prtd->period = 0;
+	davinci_pcm_enqueue_dma(substream);
+
+	/* Copy self-linked parameter RAM entry into master channel */
+	edma_read_slot(prtd->asp_link[0], &prtd->asp_params);
+	edma_write_slot(prtd->asp_channel, &prtd->asp_params);
+	davinci_pcm_enqueue_dma(substream);
+	edma_start(prtd->asp_channel);
+
+	return 0;
+}
+
+static snd_pcm_uframes_t
+davinci_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct davinci_runtime_data *prtd = runtime->private_data;
+	unsigned int offset;
+	int asp_count;
+	dma_addr_t asp_src, asp_dst;
+
+	spin_lock(&prtd->lock);
+	if (prtd->ram_channel >= 0) {
+		int ram_count;
+		int mod_ram;
+		dma_addr_t ram_src, ram_dst;
+		unsigned int period_size = snd_pcm_lib_period_bytes(substream);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			/* reading ram before asp should be safe
+			 * as long as the asp transfers less than a ping size
+			 * of bytes between the 2 reads
+			 */
+			edma_get_position(prtd->ram_channel,
+					&ram_src, &ram_dst);
+			edma_get_position(prtd->asp_channel,
+					&asp_src, &asp_dst);
+			asp_count = asp_src - prtd->asp_params.src;
+			ram_count = ram_src - prtd->ram_params.src;
+			mod_ram = ram_count % period_size;
+			mod_ram -= asp_count;
+			if (mod_ram < 0)
+				mod_ram += period_size;
+			else if (mod_ram == 0) {
+				if (snd_pcm_running(substream))
+					mod_ram += period_size;
+			}
+			ram_count -= mod_ram;
+			if (ram_count < 0)
+				ram_count += period_size * runtime->periods;
+		} else {
+			edma_get_position(prtd->ram_channel,
+					&ram_src, &ram_dst);
+			ram_count = ram_dst - prtd->ram_params.dst;
+		}
+		asp_count = ram_count;
+	} else {
+		edma_get_position(prtd->asp_channel, &asp_src, &asp_dst);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			asp_count = asp_src - runtime->dma_addr;
+		else
+			asp_count = asp_dst - runtime->dma_addr;
+	}
+	spin_unlock(&prtd->lock);
+
+	offset = bytes_to_frames(runtime, asp_count);
+	if (offset >= runtime->buffer_size)
+		offset = 0;
+
+	return offset;
+}
+
+static int davinci_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct davinci_runtime_data *prtd;
+	struct snd_pcm_hardware *ppcm;
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct davinci_pcm_dma_params *pa;
+	struct davinci_pcm_dma_params *params;
+
+	pa = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+	if (!pa)
+		return -ENODEV;
+	params = &pa[substream->stream];
+
+	ppcm = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+			&pcm_hardware_playback : &pcm_hardware_capture;
+	allocate_sram(substream, params->sram_size, ppcm);
+	snd_soc_set_runtime_hwparams(substream, ppcm);
+	/* ensure that buffer size is a multiple of period size */
+	ret = snd_pcm_hw_constraint_integer(runtime,
+						SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0)
+		return ret;
+
+	prtd = kzalloc(sizeof(struct davinci_runtime_data), GFP_KERNEL);
+	if (prtd == NULL)
+		return -ENOMEM;
+
+	spin_lock_init(&prtd->lock);
+	prtd->params = params;
+	prtd->asp_channel = -1;
+	prtd->asp_link[0] = prtd->asp_link[1] = -1;
+	prtd->ram_channel = -1;
+	prtd->ram_link = -1;
+	prtd->ram_link2 = -1;
+
+	runtime->private_data = prtd;
+
+	ret = davinci_pcm_dma_request(substream);
+	if (ret) {
+		printk(KERN_ERR "davinci_pcm: Failed to get dma channels\n");
+		kfree(prtd);
+	}
+
+	return ret;
+}
+
+static int davinci_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct davinci_runtime_data *prtd = runtime->private_data;
+
+	if (prtd->ram_channel >= 0)
+		edma_stop(prtd->ram_channel);
+	if (prtd->asp_channel >= 0)
+		edma_stop(prtd->asp_channel);
+	if (prtd->asp_link[0] >= 0)
+		edma_unlink(prtd->asp_link[0]);
+	if (prtd->asp_link[1] >= 0)
+		edma_unlink(prtd->asp_link[1]);
+	if (prtd->ram_link >= 0)
+		edma_unlink(prtd->ram_link);
+
+	if (prtd->asp_link[0] >= 0)
+		edma_free_slot(prtd->asp_link[0]);
+	if (prtd->asp_link[1] >= 0)
+		edma_free_slot(prtd->asp_link[1]);
+	if (prtd->asp_channel >= 0)
+		edma_free_channel(prtd->asp_channel);
+	if (prtd->ram_link >= 0)
+		edma_free_slot(prtd->ram_link);
+	if (prtd->ram_link2 >= 0)
+		edma_free_slot(prtd->ram_link2);
+	if (prtd->ram_channel >= 0)
+		edma_free_channel(prtd->ram_channel);
+
+	kfree(prtd);
+
+	return 0;
+}
+
+static int davinci_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int davinci_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int davinci_pcm_mmap(struct snd_pcm_substream *substream,
+			    struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+				     runtime->dma_area,
+				     runtime->dma_addr,
+				     runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops davinci_pcm_ops = {
+	.open = 	davinci_pcm_open,
+	.close = 	davinci_pcm_close,
+	.ioctl = 	snd_pcm_lib_ioctl,
+	.hw_params = 	davinci_pcm_hw_params,
+	.hw_free = 	davinci_pcm_hw_free,
+	.prepare = 	davinci_pcm_prepare,
+	.trigger = 	davinci_pcm_trigger,
+	.pointer = 	davinci_pcm_pointer,
+	.mmap = 	davinci_pcm_mmap,
+};
+
+static int davinci_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream,
+		size_t size)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+	buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+					   &buf->addr, GFP_KERNEL);
+
+	pr_debug("davinci_pcm: preallocate_dma_buffer: area=%p, addr=%p, "
+		"size=%d\n", (void *) buf->area, (void *) buf->addr, size);
+
+	if (!buf->area)
+		return -ENOMEM;
+
+	buf->bytes = size;
+	return 0;
+}
+
+static void davinci_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++) {
+		struct snd_dma_buffer *iram_dma;
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+
+		dma_free_writecombine(pcm->card->dev, buf->bytes,
+				      buf->area, buf->addr);
+		buf->area = NULL;
+		iram_dma = buf->private_data;
+		if (iram_dma) {
+#if !defined(CONFIG_ARCH_TI81XX)
+			sram_free(iram_dma->area, iram_dma->bytes);
+			kfree(iram_dma);
+#endif
+		}
+	}
+}
+
+static u64 davinci_pcm_dmamask = 0xffffffff;
+
+static int davinci_pcm_new(struct snd_card *card,
+			   struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+	int ret;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &davinci_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = 0xffffffff;
+
+	if (dai->driver->playback.channels_min) {
+		ret = davinci_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_PLAYBACK,
+			pcm_hardware_playback.buffer_bytes_max);
+		if (ret)
+			return ret;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = davinci_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_CAPTURE,
+			pcm_hardware_capture.buffer_bytes_max);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_platform_driver davinci_soc_platform = {
+	.ops =		&davinci_pcm_ops,
+	.pcm_new = 	davinci_pcm_new,
+	.pcm_free = 	davinci_pcm_free,
+};
+
+static int __devinit davinci_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &davinci_soc_platform);
+}
+
+static int __devexit davinci_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver davinci_pcm_driver = {
+	.driver = {
+			.name = "davinci-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = davinci_soc_platform_probe,
+	.remove = __devexit_p(davinci_soc_platform_remove),
+};
+
+static int __init snd_davinci_pcm_init(void)
+{
+	return platform_driver_register(&davinci_pcm_driver);
+}
+module_init(snd_davinci_pcm_init);
+
+static void __exit snd_davinci_pcm_exit(void)
+{
+	platform_driver_unregister(&davinci_pcm_driver);
+}
+module_exit(snd_davinci_pcm_exit);
+
+MODULE_AUTHOR("Vladimir Barinov");
+MODULE_DESCRIPTION("TI DAVINCI PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/davinci/davinci-pcm.h b/linux/sound/soc/davinci/davinci-pcm.h
new file mode 100644
index 0000000..d3bbe7d
--- /dev/null
+++ b/linux/sound/soc/davinci/davinci-pcm.h
@@ -0,0 +1,36 @@
+/*
+ * ALSA PCM interface for the TI DAVINCI processor
+ *
+ * Author:      Vladimir Barinov, <vbarinov@embeddedalley.com>
+ * Copyright:   (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _DAVINCI_PCM_H
+#define _DAVINCI_PCM_H
+
+#ifndef CONFIG_ARCH_TI81XX
+#include <mach/edma.h>
+#include <mach/asp.h>
+#else
+#include <plat/asp.h>
+#include <asm/hardware/edma.h>
+#endif
+
+
+struct davinci_pcm_dma_params {
+	int channel;			/* sync dma channel ID */
+	unsigned short acnt;
+	dma_addr_t dma_addr;		/* device physical address for DMA */
+	unsigned sram_size;
+	enum dma_event_q asp_chan_q;	/* event queue number for ASP channel */
+	enum dma_event_q ram_chan_q;	/* event queue number for RAM channel */
+	unsigned char data_type;	/* xfer data type */
+	unsigned char convert_mono_stereo;
+	unsigned int fifo_level;
+};
+
+#endif
diff --git a/linux/sound/soc/davinci/davinci-sffsdr.c b/linux/sound/soc/davinci/davinci-sffsdr.c
new file mode 100644
index 0000000..6c6666a
--- /dev/null
+++ b/linux/sound/soc/davinci/davinci-sffsdr.c
@@ -0,0 +1,181 @@
+/*
+ * ASoC driver for Lyrtech SFFSDR board.
+ *
+ * Author:	Hugo Villeneuve
+ * Copyright (C) 2008 Lyrtech inc
+ *
+ * Based on ASoC driver for TI DAVINCI EVM platform, original copyright follow:
+ * Copyright:   (C) 2007 MontaVista Software, Inc., <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/dma.h>
+#include <asm/mach-types.h>
+#ifdef CONFIG_SFFSDR_FPGA
+#include <asm/plat-sffsdr/sffsdr-fpga.h>
+#endif
+
+#include <mach/edma.h>
+
+#include "../codecs/pcm3008.h"
+#include "davinci-pcm.h"
+#include "davinci-i2s.h"
+
+/*
+ * CLKX and CLKR are the inputs for the Sample Rate Generator.
+ * FSX and FSR are outputs, driven by the sample Rate Generator.
+ */
+#define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B |	\
+		      SND_SOC_DAIFMT_CBM_CFS |	\
+		      SND_SOC_DAIFMT_IB_NF)
+
+static int sffsdr_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int fs;
+	int ret = 0;
+
+	/* Fsref can be 32000, 44100 or 48000. */
+	fs = params_rate(params);
+
+#ifndef CONFIG_SFFSDR_FPGA
+	/* Without the FPGA module, the Fs is fixed at 44100 Hz */
+	if (fs != 44100) {
+		pr_debug("warning: only 44.1 kHz is supported without SFFSDR FPGA module\n");
+		return -EINVAL;
+	}
+#endif
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, AUDIO_FORMAT);
+	if (ret < 0)
+		return ret;
+
+	pr_debug("sffsdr_hw_params: rate = %d Hz\n", fs);
+
+#ifndef CONFIG_SFFSDR_FPGA
+	return 0;
+#else
+	return sffsdr_fpga_set_codec_fs(fs);
+#endif
+}
+
+static struct snd_soc_ops sffsdr_ops = {
+	.hw_params = sffsdr_hw_params,
+};
+
+/* davinci-sffsdr digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link sffsdr_dai = {
+	.name = "PCM3008", /* Codec name */
+	.stream_name = "PCM3008 HiFi",
+	.cpu_dai_name = "davinci-mcbsp",
+	.codec_dai_name = "pcm3008-hifi",
+	.codec_name = "pcm3008-codec",
+	.platform_name = "davinci-pcm-audio",
+	.ops = &sffsdr_ops,
+};
+
+/* davinci-sffsdr audio machine driver */
+static struct snd_soc_card snd_soc_sffsdr = {
+	.name = "DaVinci SFFSDR",
+	.dai_link = &sffsdr_dai,
+	.num_links = 1,
+};
+
+/* sffsdr audio private data */
+static struct pcm3008_setup_data sffsdr_pcm3008_setup = {
+	.dem0_pin = GPIO(45),
+	.dem1_pin = GPIO(46),
+	.pdad_pin = GPIO(47),
+	.pdda_pin = GPIO(38),
+};
+
+struct platform_device pcm3008_codec = {
+		.name = "pcm3008-codec",
+		.id = 0,
+		.dev = {
+				.platform_data = &sffsdr_pcm3008_setup,
+		},
+};
+
+static struct resource sffsdr_snd_resources[] = {
+	{
+		.start = DAVINCI_MCBSP_BASE,
+		.end = DAVINCI_MCBSP_BASE + SZ_8K - 1,
+		.flags = IORESOURCE_MEM,
+	},
+};
+
+static struct evm_snd_platform_data sffsdr_snd_data = {
+	.tx_dma_ch	= DAVINCI_DMA_MCBSP_TX,
+	.rx_dma_ch	= DAVINCI_DMA_MCBSP_RX,
+};
+
+static struct platform_device *sffsdr_snd_device;
+
+static int __init sffsdr_init(void)
+{
+	int ret;
+
+	if (!machine_is_sffsdr())
+		return -EINVAL;
+
+	platform_device_register(&pcm3008_codec);
+
+	sffsdr_snd_device = platform_device_alloc("soc-audio", 0);
+	if (!sffsdr_snd_device) {
+		printk(KERN_ERR "platform device allocation failed\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(sffsdr_snd_device, &snd_soc_sffsdr);
+	platform_device_add_data(sffsdr_snd_device, &sffsdr_snd_data,
+				 sizeof(sffsdr_snd_data));
+
+	ret = platform_device_add_resources(sffsdr_snd_device,
+					    sffsdr_snd_resources,
+					    ARRAY_SIZE(sffsdr_snd_resources));
+	if (ret) {
+		printk(KERN_ERR "platform device add resources failed\n");
+		goto error;
+	}
+
+	ret = platform_device_add(sffsdr_snd_device);
+	if (ret)
+		goto error;
+
+	return ret;
+
+error:
+	platform_device_put(sffsdr_snd_device);
+	return ret;
+}
+
+static void __exit sffsdr_exit(void)
+{
+	platform_device_unregister(sffsdr_snd_device);
+	platform_device_unregister(&pcm3008_codec);
+}
+
+module_init(sffsdr_init);
+module_exit(sffsdr_exit);
+
+MODULE_AUTHOR("Hugo Villeneuve");
+MODULE_DESCRIPTION("Lyrtech SFFSDR ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/davinci/davinci-vcif.c b/linux/sound/soc/davinci/davinci-vcif.c
new file mode 100644
index 0000000..9d2afcc
--- /dev/null
+++ b/linux/sound/soc/davinci/davinci-vcif.c
@@ -0,0 +1,281 @@
+/*
+ * ALSA SoC Voice Codec Interface for TI DAVINCI processor
+ *
+ * Copyright (C) 2010 Texas Instruments.
+ *
+ * Author: Miguel Aguilar <miguel.aguilar@ridgerun.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/mfd/davinci_voicecodec.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "davinci-pcm.h"
+#include "davinci-i2s.h"
+
+#define MOD_REG_BIT(val, mask, set) do { \
+	if (set) { \
+		val |= mask; \
+	} else { \
+		val &= ~mask; \
+	} \
+} while (0)
+
+struct davinci_vcif_dev {
+	struct davinci_vc *davinci_vc;
+	struct davinci_pcm_dma_params	dma_params[2];
+};
+
+static void davinci_vcif_start(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct davinci_vcif_dev *davinci_vcif_dev =
+			snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc;
+	u32 w;
+
+	/* Start the sample generator and enable transmitter/receiver */
+	w = readl(davinci_vc->base + DAVINCI_VC_CTRL);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTDAC, 1);
+	else
+		MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTADC, 1);
+
+	writel(w, davinci_vc->base + DAVINCI_VC_CTRL);
+}
+
+static void davinci_vcif_stop(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct davinci_vcif_dev *davinci_vcif_dev =
+			snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc;
+	u32 w;
+
+	/* Reset transmitter/receiver and sample rate/frame sync generators */
+	w = readl(davinci_vc->base + DAVINCI_VC_CTRL);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTDAC, 0);
+	else
+		MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTADC, 0);
+
+	writel(w, davinci_vc->base + DAVINCI_VC_CTRL);
+}
+
+static int davinci_vcif_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct davinci_vcif_dev *davinci_vcif_dev = snd_soc_dai_get_drvdata(dai);
+	struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc;
+	struct davinci_pcm_dma_params *dma_params =
+			&davinci_vcif_dev->dma_params[substream->stream];
+	u32 w;
+
+	/* Restart the codec before setup */
+	davinci_vcif_stop(substream);
+	davinci_vcif_start(substream);
+
+	/* General line settings */
+	writel(DAVINCI_VC_CTRL_MASK, davinci_vc->base + DAVINCI_VC_CTRL);
+
+	writel(DAVINCI_VC_INT_MASK, davinci_vc->base + DAVINCI_VC_INTCLR);
+
+	writel(DAVINCI_VC_INT_MASK, davinci_vc->base + DAVINCI_VC_INTEN);
+
+	w = readl(davinci_vc->base + DAVINCI_VC_CTRL);
+
+	/* Determine xfer data type */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_U8:
+		dma_params->data_type = 0;
+
+		MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 |
+			    DAVINCI_VC_CTRL_RD_UNSIGNED |
+			    DAVINCI_VC_CTRL_WD_BITS_8 |
+			    DAVINCI_VC_CTRL_WD_UNSIGNED, 1);
+		break;
+	case SNDRV_PCM_FORMAT_S8:
+		dma_params->data_type = 1;
+
+		MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 |
+			    DAVINCI_VC_CTRL_WD_BITS_8, 1);
+
+		MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_UNSIGNED |
+			    DAVINCI_VC_CTRL_WD_UNSIGNED, 0);
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		dma_params->data_type = 2;
+
+		MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 |
+			    DAVINCI_VC_CTRL_RD_UNSIGNED |
+			    DAVINCI_VC_CTRL_WD_BITS_8 |
+			    DAVINCI_VC_CTRL_WD_UNSIGNED, 0);
+		break;
+	default:
+		printk(KERN_WARNING "davinci-vcif: unsupported PCM format");
+		return -EINVAL;
+	}
+
+	dma_params->acnt  = dma_params->data_type;
+
+	writel(w, davinci_vc->base + DAVINCI_VC_CTRL);
+
+	return 0;
+}
+
+static int davinci_vcif_trigger(struct snd_pcm_substream *substream, int cmd,
+				struct snd_soc_dai *dai)
+{
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		davinci_vcif_start(substream);
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		davinci_vcif_stop(substream);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int davinci_vcif_startup(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	struct davinci_vcif_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+	snd_soc_dai_set_dma_data(dai, substream, dev->dma_params);
+	return 0;
+}
+
+#define DAVINCI_VCIF_RATES	SNDRV_PCM_RATE_8000_48000
+
+static struct snd_soc_dai_ops davinci_vcif_dai_ops = {
+	.startup	= davinci_vcif_startup,
+	.trigger	= davinci_vcif_trigger,
+	.hw_params	= davinci_vcif_hw_params,
+};
+
+static struct snd_soc_dai_driver davinci_vcif_dai = {
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = DAVINCI_VCIF_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = DAVINCI_VCIF_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.ops = &davinci_vcif_dai_ops,
+
+};
+
+static int davinci_vcif_probe(struct platform_device *pdev)
+{
+	struct davinci_vc *davinci_vc = platform_get_drvdata(pdev);
+	struct davinci_vcif_dev *davinci_vcif_dev;
+	int ret;
+
+	davinci_vcif_dev = kzalloc(sizeof(struct davinci_vcif_dev), GFP_KERNEL);
+	if (!davinci_vcif_dev) {
+		dev_dbg(&pdev->dev,
+			"could not allocate memory for private data\n");
+		return -ENOMEM;
+	}
+
+	/* DMA tx params */
+	davinci_vcif_dev->davinci_vc = davinci_vc;
+	davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].channel =
+					davinci_vc->davinci_vcif.dma_tx_channel;
+	davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].dma_addr =
+					davinci_vc->davinci_vcif.dma_tx_addr;
+
+	/* DMA rx params */
+	davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].channel =
+					davinci_vc->davinci_vcif.dma_rx_channel;
+	davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].dma_addr =
+					davinci_vc->davinci_vcif.dma_rx_addr;
+
+	dev_set_drvdata(&pdev->dev, davinci_vcif_dev);
+
+	ret = snd_soc_register_dai(&pdev->dev, &davinci_vcif_dai);
+	if (ret != 0) {
+		dev_err(&pdev->dev, "could not register dai\n");
+		goto fail;
+	}
+
+	return 0;
+
+fail:
+	kfree(davinci_vcif_dev);
+
+	return ret;
+}
+
+static int davinci_vcif_remove(struct platform_device *pdev)
+{
+	struct davinci_vcif_dev *davinci_vcif_dev = dev_get_drvdata(&pdev->dev);
+
+	snd_soc_unregister_dai(&pdev->dev);
+	kfree(davinci_vcif_dev);
+
+	return 0;
+}
+
+static struct platform_driver davinci_vcif_driver = {
+	.probe		= davinci_vcif_probe,
+	.remove		= davinci_vcif_remove,
+	.driver		= {
+		.name	= "davinci-vcif",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init davinci_vcif_init(void)
+{
+	return platform_driver_probe(&davinci_vcif_driver, davinci_vcif_probe);
+}
+module_init(davinci_vcif_init);
+
+static void __exit davinci_vcif_exit(void)
+{
+	platform_driver_unregister(&davinci_vcif_driver);
+}
+module_exit(davinci_vcif_exit);
+
+MODULE_AUTHOR("Miguel Aguilar");
+MODULE_DESCRIPTION("Texas Instruments DaVinci ASoC Voice Codec Interface");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/ep93xx/Kconfig b/linux/sound/soc/ep93xx/Kconfig
new file mode 100644
index 0000000..5742904
--- /dev/null
+++ b/linux/sound/soc/ep93xx/Kconfig
@@ -0,0 +1,32 @@
+config SND_EP93XX_SOC
+	tristate "SoC Audio support for the Cirrus Logic EP93xx series"
+	depends on ARCH_EP93XX && SND_SOC
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the EP93xx I2S or AC97 interfaces.
+
+config SND_EP93XX_SOC_I2S
+	tristate
+
+config SND_EP93XX_SOC_AC97
+	tristate
+	select AC97_BUS
+	select SND_SOC_AC97_BUS
+
+config SND_EP93XX_SOC_SNAPPERCL15
+        tristate "SoC Audio support for Bluewater Systems Snapper CL15 module"
+        depends on SND_EP93XX_SOC && MACH_SNAPPER_CL15
+        select SND_EP93XX_SOC_I2S
+        select SND_SOC_TLV320AIC23
+        help
+          Say Y or M here if you want to add support for I2S audio on the
+          Bluewater Systems Snapper CL15 module.
+
+config SND_EP93XX_SOC_SIMONE
+	tristate "SoC Audio support for Simplemachines Sim.One board"
+	depends on SND_EP93XX_SOC && MACH_SIM_ONE
+	select SND_EP93XX_SOC_AC97
+	select SND_SOC_AC97_CODEC
+	help
+	  Say Y or M here if you want to add support for AC97 audio on the
+	  Simplemachines Sim.One board.
diff --git a/linux/sound/soc/ep93xx/Makefile b/linux/sound/soc/ep93xx/Makefile
new file mode 100644
index 0000000..8e7977f
--- /dev/null
+++ b/linux/sound/soc/ep93xx/Makefile
@@ -0,0 +1,15 @@
+# EP93xx Platform Support
+snd-soc-ep93xx-objs				:= ep93xx-pcm.o
+snd-soc-ep93xx-i2s-objs	 			:= ep93xx-i2s.o
+snd-soc-ep93xx-ac97-objs 			:= ep93xx-ac97.o
+
+obj-$(CONFIG_SND_EP93XX_SOC)			+= snd-soc-ep93xx.o
+obj-$(CONFIG_SND_EP93XX_SOC_I2S)		+= snd-soc-ep93xx-i2s.o
+obj-$(CONFIG_SND_EP93XX_SOC_AC97)		+= snd-soc-ep93xx-ac97.o
+
+# EP93XX Machine Support
+snd-soc-snappercl15-objs			:= snappercl15.o
+snd-soc-simone-objs				:= simone.o
+
+obj-$(CONFIG_SND_EP93XX_SOC_SNAPPERCL15)	+= snd-soc-snappercl15.o
+obj-$(CONFIG_SND_EP93XX_SOC_SIMONE)		+= snd-soc-simone.o
diff --git a/linux/sound/soc/ep93xx/ep93xx-ac97.c b/linux/sound/soc/ep93xx/ep93xx-ac97.c
new file mode 100644
index 0000000..68a0bae
--- /dev/null
+++ b/linux/sound/soc/ep93xx/ep93xx-ac97.c
@@ -0,0 +1,468 @@
+/*
+ * ASoC driver for Cirrus Logic EP93xx AC97 controller.
+ *
+ * Copyright (c) 2010 Mika Westerberg
+ *
+ * Based on s3c-ac97 ASoC driver by Jaswinder Singh.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+#include "ep93xx-pcm.h"
+
+/*
+ * Per channel (1-4) registers.
+ */
+#define AC97CH(n)		(((n) - 1) * 0x20)
+
+#define AC97DR(n)		(AC97CH(n) + 0x0000)
+
+#define AC97RXCR(n)		(AC97CH(n) + 0x0004)
+#define AC97RXCR_REN		BIT(0)
+#define AC97RXCR_RX3		BIT(3)
+#define AC97RXCR_RX4		BIT(4)
+#define AC97RXCR_CM		BIT(15)
+
+#define AC97TXCR(n)		(AC97CH(n) + 0x0008)
+#define AC97TXCR_TEN		BIT(0)
+#define AC97TXCR_TX3		BIT(3)
+#define AC97TXCR_TX4		BIT(4)
+#define AC97TXCR_CM		BIT(15)
+
+#define AC97SR(n)		(AC97CH(n) + 0x000c)
+#define AC97SR_TXFE		BIT(1)
+#define AC97SR_TXUE		BIT(6)
+
+#define AC97RISR(n)		(AC97CH(n) + 0x0010)
+#define AC97ISR(n)		(AC97CH(n) + 0x0014)
+#define AC97IE(n)		(AC97CH(n) + 0x0018)
+
+/*
+ * Global AC97 controller registers.
+ */
+#define AC97S1DATA		0x0080
+#define AC97S2DATA		0x0084
+#define AC97S12DATA		0x0088
+
+#define AC97RGIS		0x008c
+#define AC97GIS			0x0090
+#define AC97IM			0x0094
+/*
+ * Common bits for RGIS, GIS and IM registers.
+ */
+#define AC97_SLOT2RXVALID	BIT(1)
+#define AC97_CODECREADY		BIT(5)
+#define AC97_SLOT2TXCOMPLETE	BIT(6)
+
+#define AC97EOI			0x0098
+#define AC97EOI_WINT		BIT(0)
+#define AC97EOI_CODECREADY	BIT(1)
+
+#define AC97GCR			0x009c
+#define AC97GCR_AC97IFE		BIT(0)
+
+#define AC97RESET		0x00a0
+#define AC97RESET_TIMEDRESET	BIT(0)
+
+#define AC97SYNC		0x00a4
+#define AC97SYNC_TIMEDSYNC	BIT(0)
+
+#define AC97_TIMEOUT		msecs_to_jiffies(5)
+
+/**
+ * struct ep93xx_ac97_info - EP93xx AC97 controller info structure
+ * @lock: mutex serializing access to the bus (slot 1 & 2 ops)
+ * @dev: pointer to the platform device dev structure
+ * @mem: physical memory resource for the registers
+ * @regs: mapped AC97 controller registers
+ * @irq: AC97 interrupt number
+ * @done: bus ops wait here for an interrupt
+ */
+struct ep93xx_ac97_info {
+	struct mutex		lock;
+	struct device		*dev;
+	struct resource		*mem;
+	void __iomem		*regs;
+	int			irq;
+	struct completion	done;
+};
+
+/* currently ALSA only supports a single AC97 device */
+static struct ep93xx_ac97_info *ep93xx_ac97_info;
+
+static struct ep93xx_pcm_dma_params ep93xx_ac97_pcm_out = {
+	.name		= "ac97-pcm-out",
+	.dma_port	= EP93XX_DMA_M2P_PORT_AAC1,
+};
+
+static struct ep93xx_pcm_dma_params ep93xx_ac97_pcm_in = {
+	.name		= "ac97-pcm-in",
+	.dma_port	= EP93XX_DMA_M2P_PORT_AAC1,
+};
+
+static inline unsigned ep93xx_ac97_read_reg(struct ep93xx_ac97_info *info,
+					    unsigned reg)
+{
+	return __raw_readl(info->regs + reg);
+}
+
+static inline void ep93xx_ac97_write_reg(struct ep93xx_ac97_info *info,
+					 unsigned reg, unsigned val)
+{
+	__raw_writel(val, info->regs + reg);
+}
+
+static unsigned short ep93xx_ac97_read(struct snd_ac97 *ac97,
+				       unsigned short reg)
+{
+	struct ep93xx_ac97_info *info = ep93xx_ac97_info;
+	unsigned short val;
+
+	mutex_lock(&info->lock);
+
+	ep93xx_ac97_write_reg(info, AC97S1DATA, reg);
+	ep93xx_ac97_write_reg(info, AC97IM, AC97_SLOT2RXVALID);
+	if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT)) {
+		dev_warn(info->dev, "timeout reading register %x\n", reg);
+		mutex_unlock(&info->lock);
+		return -ETIMEDOUT;
+	}
+	val = (unsigned short)ep93xx_ac97_read_reg(info, AC97S2DATA);
+
+	mutex_unlock(&info->lock);
+	return val;
+}
+
+static void ep93xx_ac97_write(struct snd_ac97 *ac97,
+			      unsigned short reg,
+			      unsigned short val)
+{
+	struct ep93xx_ac97_info *info = ep93xx_ac97_info;
+
+	mutex_lock(&info->lock);
+
+	/*
+	 * Writes to the codec need to be done so that slot 2 is filled in
+	 * before slot 1.
+	 */
+	ep93xx_ac97_write_reg(info, AC97S2DATA, val);
+	ep93xx_ac97_write_reg(info, AC97S1DATA, reg);
+
+	ep93xx_ac97_write_reg(info, AC97IM, AC97_SLOT2TXCOMPLETE);
+	if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT))
+		dev_warn(info->dev, "timeout writing register %x\n", reg);
+
+	mutex_unlock(&info->lock);
+}
+
+static void ep93xx_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+	struct ep93xx_ac97_info *info = ep93xx_ac97_info;
+
+	mutex_lock(&info->lock);
+
+	/*
+	 * We are assuming that before this functions gets called, the codec
+	 * BIT_CLK is stopped by forcing the codec into powerdown mode. We can
+	 * control the SYNC signal directly via AC97SYNC register. Using
+	 * TIMEDSYNC the controller will keep the SYNC high > 1us.
+	 */
+	ep93xx_ac97_write_reg(info, AC97SYNC, AC97SYNC_TIMEDSYNC);
+	ep93xx_ac97_write_reg(info, AC97IM, AC97_CODECREADY);
+	if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT))
+		dev_warn(info->dev, "codec warm reset timeout\n");
+
+	mutex_unlock(&info->lock);
+}
+
+static void ep93xx_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+	struct ep93xx_ac97_info *info = ep93xx_ac97_info;
+
+	mutex_lock(&info->lock);
+
+	/*
+	 * For doing cold reset, we disable the AC97 controller interface, clear
+	 * WINT and CODECREADY bits, and finally enable the interface again.
+	 */
+	ep93xx_ac97_write_reg(info, AC97GCR, 0);
+	ep93xx_ac97_write_reg(info, AC97EOI, AC97EOI_CODECREADY | AC97EOI_WINT);
+	ep93xx_ac97_write_reg(info, AC97GCR, AC97GCR_AC97IFE);
+
+	/*
+	 * Now, assert the reset and wait for the codec to become ready.
+	 */
+	ep93xx_ac97_write_reg(info, AC97RESET, AC97RESET_TIMEDRESET);
+	ep93xx_ac97_write_reg(info, AC97IM, AC97_CODECREADY);
+	if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT))
+		dev_warn(info->dev, "codec cold reset timeout\n");
+
+	/*
+	 * Give the codec some time to come fully out from the reset. This way
+	 * we ensure that the subsequent reads/writes will work.
+	 */
+	usleep_range(15000, 20000);
+
+	mutex_unlock(&info->lock);
+}
+
+static irqreturn_t ep93xx_ac97_interrupt(int irq, void *dev_id)
+{
+	struct ep93xx_ac97_info *info = dev_id;
+	unsigned status, mask;
+
+	/*
+	 * Just mask out the interrupt and wake up the waiting thread.
+	 * Interrupts are cleared via reading/writing to slot 1 & 2 registers by
+	 * the waiting thread.
+	 */
+	status = ep93xx_ac97_read_reg(info, AC97GIS);
+	mask = ep93xx_ac97_read_reg(info, AC97IM);
+	mask &= ~status;
+	ep93xx_ac97_write_reg(info, AC97IM, mask);
+
+	complete(&info->done);
+	return IRQ_HANDLED;
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read		= ep93xx_ac97_read,
+	.write		= ep93xx_ac97_write,
+	.reset		= ep93xx_ac97_cold_reset,
+	.warm_reset	= ep93xx_ac97_warm_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int ep93xx_ac97_trigger(struct snd_pcm_substream *substream,
+			       int cmd, struct snd_soc_dai *dai)
+{
+	struct ep93xx_ac97_info *info = snd_soc_dai_get_drvdata(dai);
+	unsigned v = 0;
+
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			/*
+			 * Enable compact mode, TX slots 3 & 4, and the TX FIFO
+			 * itself.
+			 */
+			v |= AC97TXCR_CM;
+			v |= AC97TXCR_TX3 | AC97TXCR_TX4;
+			v |= AC97TXCR_TEN;
+			ep93xx_ac97_write_reg(info, AC97TXCR(1), v);
+		} else {
+			/*
+			 * Enable compact mode, RX slots 3 & 4, and the RX FIFO
+			 * itself.
+			 */
+			v |= AC97RXCR_CM;
+			v |= AC97RXCR_RX3 | AC97RXCR_RX4;
+			v |= AC97RXCR_REN;
+			ep93xx_ac97_write_reg(info, AC97RXCR(1), v);
+		}
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			/*
+			 * As per Cirrus EP93xx errata described below:
+			 *
+			 * http://www.cirrus.com/en/pubs/errata/ER667E2B.pdf
+			 *
+			 * we will wait for the TX FIFO to be empty before
+			 * clearing the TEN bit.
+			 */
+			unsigned long timeout = jiffies + AC97_TIMEOUT;
+
+			do {
+				v = ep93xx_ac97_read_reg(info, AC97SR(1));
+				if (time_after(jiffies, timeout)) {
+					dev_warn(info->dev, "TX timeout\n");
+					break;
+				}
+			} while (!(v & (AC97SR_TXFE | AC97SR_TXUE)));
+
+			/* disable the TX FIFO */
+			ep93xx_ac97_write_reg(info, AC97TXCR(1), 0);
+		} else {
+			/* disable the RX FIFO */
+			ep93xx_ac97_write_reg(info, AC97RXCR(1), 0);
+		}
+		break;
+
+	default:
+		dev_warn(info->dev, "unknown command %d\n", cmd);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int ep93xx_ac97_startup(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct ep93xx_pcm_dma_params *dma_data;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = &ep93xx_ac97_pcm_out;
+	else
+		dma_data = &ep93xx_ac97_pcm_in;
+
+	snd_soc_dai_set_dma_data(dai, substream, dma_data);
+	return 0;
+}
+
+static struct snd_soc_dai_ops ep93xx_ac97_dai_ops = {
+	.startup	= ep93xx_ac97_startup,
+	.trigger	= ep93xx_ac97_trigger,
+};
+
+struct snd_soc_dai_driver ep93xx_ac97_dai = {
+	.name		= "ep93xx-ac97",
+	.id		= 0,
+	.ac97_control	= 1,
+	.playback	= {
+		.stream_name	= "AC97 Playback",
+		.channels_min	= 2,
+		.channels_max	= 2,
+		.rates		= SNDRV_PCM_RATE_8000_48000,
+		.formats	= SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.capture	= {
+		.stream_name	= "AC97 Capture",
+		.channels_min	= 2,
+		.channels_max	= 2,
+		.rates		= SNDRV_PCM_RATE_8000_48000,
+		.formats	= SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.ops			= &ep93xx_ac97_dai_ops,
+};
+
+static int __devinit ep93xx_ac97_probe(struct platform_device *pdev)
+{
+	struct ep93xx_ac97_info *info;
+	int ret;
+
+	info = kzalloc(sizeof(struct ep93xx_ac97_info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	dev_set_drvdata(&pdev->dev, info);
+
+	mutex_init(&info->lock);
+	init_completion(&info->done);
+	info->dev = &pdev->dev;
+
+	info->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!info->mem) {
+		ret = -ENXIO;
+		goto fail_free_info;
+	}
+
+	info->irq = platform_get_irq(pdev, 0);
+	if (!info->irq) {
+		ret = -ENXIO;
+		goto fail_free_info;
+	}
+
+	if (!request_mem_region(info->mem->start, resource_size(info->mem),
+				pdev->name)) {
+		ret = -EBUSY;
+		goto fail_free_info;
+	}
+
+	info->regs = ioremap(info->mem->start, resource_size(info->mem));
+	if (!info->regs) {
+		ret = -ENOMEM;
+		goto fail_release_mem;
+	}
+
+	ret = request_irq(info->irq, ep93xx_ac97_interrupt, IRQF_TRIGGER_HIGH,
+			  pdev->name, info);
+	if (ret)
+		goto fail_unmap_mem;
+
+	ep93xx_ac97_info = info;
+	platform_set_drvdata(pdev, info);
+
+	ret = snd_soc_register_dai(&pdev->dev, &ep93xx_ac97_dai);
+	if (ret)
+		goto fail_free_irq;
+
+	return 0;
+
+fail_free_irq:
+	platform_set_drvdata(pdev, NULL);
+	free_irq(info->irq, info);
+fail_unmap_mem:
+	iounmap(info->regs);
+fail_release_mem:
+	release_mem_region(info->mem->start, resource_size(info->mem));
+fail_free_info:
+	kfree(info);
+
+	return ret;
+}
+
+static int __devexit ep93xx_ac97_remove(struct platform_device *pdev)
+{
+	struct ep93xx_ac97_info	*info = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	/* disable the AC97 controller */
+	ep93xx_ac97_write_reg(info, AC97GCR, 0);
+
+	free_irq(info->irq, info);
+	iounmap(info->regs);
+	release_mem_region(info->mem->start, resource_size(info->mem));
+	platform_set_drvdata(pdev, NULL);
+	kfree(info);
+
+	return 0;
+}
+
+static struct platform_driver ep93xx_ac97_driver = {
+	.probe	= ep93xx_ac97_probe,
+	.remove	= __devexit_p(ep93xx_ac97_remove),
+	.driver = {
+		.name = "ep93xx-ac97",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init ep93xx_ac97_init(void)
+{
+	return platform_driver_register(&ep93xx_ac97_driver);
+}
+module_init(ep93xx_ac97_init);
+
+static void __exit ep93xx_ac97_exit(void)
+{
+	platform_driver_unregister(&ep93xx_ac97_driver);
+}
+module_exit(ep93xx_ac97_exit);
+
+MODULE_DESCRIPTION("EP93xx AC97 ASoC Driver");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ep93xx-ac97");
diff --git a/linux/sound/soc/ep93xx/ep93xx-i2s.c b/linux/sound/soc/ep93xx/ep93xx-i2s.c
new file mode 100644
index 0000000..4f48733
--- /dev/null
+++ b/linux/sound/soc/ep93xx/ep93xx-i2s.c
@@ -0,0 +1,479 @@
+/*
+ * linux/sound/soc/ep93xx-i2s.c
+ * EP93xx I2S driver
+ *
+ * Copyright (C) 2010 Ryan Mallon <ryan@bluewatersys.com>
+ *
+ * Based on the original driver by:
+ *   Copyright (C) 2007 Chase Douglas <chasedouglas@gmail>
+ *   Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+#include <mach/ep93xx-regs.h>
+#include <mach/dma.h>
+
+#include "ep93xx-pcm.h"
+
+#define EP93XX_I2S_TXCLKCFG		0x00
+#define EP93XX_I2S_RXCLKCFG		0x04
+#define EP93XX_I2S_GLCTRL		0x0C
+
+#define EP93XX_I2S_TXLINCTRLDATA	0x28
+#define EP93XX_I2S_TXCTRL		0x2C
+#define EP93XX_I2S_TXWRDLEN		0x30
+#define EP93XX_I2S_TX0EN		0x34
+
+#define EP93XX_I2S_RXLINCTRLDATA	0x58
+#define EP93XX_I2S_RXCTRL		0x5C
+#define EP93XX_I2S_RXWRDLEN		0x60
+#define EP93XX_I2S_RX0EN		0x64
+
+#define EP93XX_I2S_WRDLEN_16		(0 << 0)
+#define EP93XX_I2S_WRDLEN_24		(1 << 0)
+#define EP93XX_I2S_WRDLEN_32		(2 << 0)
+
+#define EP93XX_I2S_LINCTRLDATA_R_JUST	(1 << 2) /* Right justify */
+
+#define EP93XX_I2S_CLKCFG_LRS		(1 << 0) /* lrclk polarity */
+#define EP93XX_I2S_CLKCFG_CKP		(1 << 1) /* Bit clock polarity */
+#define EP93XX_I2S_CLKCFG_REL		(1 << 2) /* First bit transition */
+#define EP93XX_I2S_CLKCFG_MASTER	(1 << 3) /* Master mode */
+#define EP93XX_I2S_CLKCFG_NBCG		(1 << 4) /* Not bit clock gating */
+
+struct ep93xx_i2s_info {
+	struct clk			*mclk;
+	struct clk			*sclk;
+	struct clk			*lrclk;
+	struct ep93xx_pcm_dma_params	*dma_params;
+	struct resource			*mem;
+	void __iomem			*regs;
+};
+
+struct ep93xx_pcm_dma_params ep93xx_i2s_dma_params[] = {
+	[SNDRV_PCM_STREAM_PLAYBACK] = {
+		.name		= "i2s-pcm-out",
+		.dma_port	= EP93XX_DMA_M2P_PORT_I2S1,
+	},
+	[SNDRV_PCM_STREAM_CAPTURE] = {
+		.name		= "i2s-pcm-in",
+		.dma_port	= EP93XX_DMA_M2P_PORT_I2S1,
+	},
+};
+
+static inline void ep93xx_i2s_write_reg(struct ep93xx_i2s_info *info,
+					unsigned reg, unsigned val)
+{
+	__raw_writel(val, info->regs + reg);
+}
+
+static inline unsigned ep93xx_i2s_read_reg(struct ep93xx_i2s_info *info,
+					   unsigned reg)
+{
+	return __raw_readl(info->regs + reg);
+}
+
+static void ep93xx_i2s_enable(struct ep93xx_i2s_info *info, int stream)
+{
+	unsigned base_reg;
+	int i;
+
+	if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 &&
+	    (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) {
+		/* Enable clocks */
+		clk_enable(info->mclk);
+		clk_enable(info->sclk);
+		clk_enable(info->lrclk);
+
+		/* Enable i2s */
+		ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 1);
+	}
+
+	/* Enable fifos */
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+		base_reg = EP93XX_I2S_TX0EN;
+	else
+		base_reg = EP93XX_I2S_RX0EN;
+	for (i = 0; i < 3; i++)
+		ep93xx_i2s_write_reg(info, base_reg + (i * 4), 1);
+}
+
+static void ep93xx_i2s_disable(struct ep93xx_i2s_info *info, int stream)
+{
+	unsigned base_reg;
+	int i;
+
+	/* Disable fifos */
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+		base_reg = EP93XX_I2S_TX0EN;
+	else
+		base_reg = EP93XX_I2S_RX0EN;
+	for (i = 0; i < 3; i++)
+		ep93xx_i2s_write_reg(info, base_reg + (i * 4), 0);
+
+	if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 &&
+	    (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) {
+		/* Disable i2s */
+		ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 0);
+
+		/* Disable clocks */
+		clk_disable(info->lrclk);
+		clk_disable(info->sclk);
+		clk_disable(info->mclk);
+	}
+}
+
+static int ep93xx_i2s_startup(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream,
+				 &info->dma_params[substream->stream]);
+	return 0;
+}
+
+static void ep93xx_i2s_shutdown(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
+
+	ep93xx_i2s_disable(info, substream->stream);
+}
+
+static int ep93xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+				  unsigned int fmt)
+{
+	struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai);
+	unsigned int clk_cfg, lin_ctrl;
+
+	clk_cfg  = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXCLKCFG);
+	lin_ctrl = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXLINCTRLDATA);
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		clk_cfg |= EP93XX_I2S_CLKCFG_REL;
+		lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST;
+		break;
+
+	case SND_SOC_DAIFMT_LEFT_J:
+		clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
+		lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST;
+		break;
+
+	case SND_SOC_DAIFMT_RIGHT_J:
+		clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
+		lin_ctrl |= EP93XX_I2S_LINCTRLDATA_R_JUST;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		/* CPU is master */
+		clk_cfg |= EP93XX_I2S_CLKCFG_MASTER;
+		break;
+
+	case SND_SOC_DAIFMT_CBM_CFM:
+		/* Codec is master */
+		clk_cfg &= ~EP93XX_I2S_CLKCFG_MASTER;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		/* Negative bit clock, lrclk low on left word */
+		clk_cfg &= ~(EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_REL);
+		break;
+
+	case SND_SOC_DAIFMT_NB_IF:
+		/* Negative bit clock, lrclk low on right word */
+		clk_cfg &= ~EP93XX_I2S_CLKCFG_CKP;
+		clk_cfg |= EP93XX_I2S_CLKCFG_REL;
+		break;
+
+	case SND_SOC_DAIFMT_IB_NF:
+		/* Positive bit clock, lrclk low on left word */
+		clk_cfg |= EP93XX_I2S_CLKCFG_CKP;
+		clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
+		break;
+
+	case SND_SOC_DAIFMT_IB_IF:
+		/* Positive bit clock, lrclk low on right word */
+		clk_cfg |= EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_REL;
+		break;
+	}
+
+	/* Write new register values */
+	ep93xx_i2s_write_reg(info, EP93XX_I2S_RXCLKCFG, clk_cfg);
+	ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCLKCFG, clk_cfg);
+	ep93xx_i2s_write_reg(info, EP93XX_I2S_RXLINCTRLDATA, lin_ctrl);
+	ep93xx_i2s_write_reg(info, EP93XX_I2S_TXLINCTRLDATA, lin_ctrl);
+	return 0;
+}
+
+static int ep93xx_i2s_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
+	unsigned word_len, div, sdiv, lrdiv;
+	int found = 0, err;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		word_len = EP93XX_I2S_WRDLEN_16;
+		break;
+
+	case SNDRV_PCM_FORMAT_S24_LE:
+		word_len = EP93XX_I2S_WRDLEN_24;
+		break;
+
+	case SNDRV_PCM_FORMAT_S32_LE:
+		word_len = EP93XX_I2S_WRDLEN_32;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		ep93xx_i2s_write_reg(info, EP93XX_I2S_TXWRDLEN, word_len);
+	else
+		ep93xx_i2s_write_reg(info, EP93XX_I2S_RXWRDLEN, word_len);
+
+	/*
+	 * Calculate the sdiv (bit clock) and lrdiv (left/right clock) values.
+	 * If the lrclk is pulse length is larger than the word size, then the
+	 * bit clock will be gated for the unused bits.
+	 */
+	div = (clk_get_rate(info->mclk) / params_rate(params)) *
+		params_channels(params);
+	for (sdiv = 2; sdiv <= 4; sdiv += 2)
+		for (lrdiv = 32; lrdiv <= 128; lrdiv <<= 1)
+			if (sdiv * lrdiv == div) {
+				found = 1;
+				goto out;
+			}
+out:
+	if (!found)
+		return -EINVAL;
+
+	err = clk_set_rate(info->sclk, clk_get_rate(info->mclk) / sdiv);
+	if (err)
+		return err;
+
+	err = clk_set_rate(info->lrclk, clk_get_rate(info->sclk) / lrdiv);
+	if (err)
+		return err;
+
+	ep93xx_i2s_enable(info, substream->stream);
+	return 0;
+}
+
+static int ep93xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id,
+				 unsigned int freq, int dir)
+{
+	struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai);
+
+	if (dir == SND_SOC_CLOCK_IN || clk_id != 0)
+		return -EINVAL;
+
+	return clk_set_rate(info->mclk, freq);
+}
+
+#ifdef CONFIG_PM
+static int ep93xx_i2s_suspend(struct snd_soc_dai *dai)
+{
+	struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
+
+	if (!dai->active)
+		return;
+
+	ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK);
+	ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static int ep93xx_i2s_resume(struct snd_soc_dai *dai)
+{
+	struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
+
+	if (!dai->active)
+		return;
+
+	ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK);
+	ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE);
+}
+#else
+#define ep93xx_i2s_suspend	NULL
+#define ep93xx_i2s_resume	NULL
+#endif
+
+static struct snd_soc_dai_ops ep93xx_i2s_dai_ops = {
+	.startup	= ep93xx_i2s_startup,
+	.shutdown	= ep93xx_i2s_shutdown,
+	.hw_params	= ep93xx_i2s_hw_params,
+	.set_sysclk	= ep93xx_i2s_set_sysclk,
+	.set_fmt	= ep93xx_i2s_set_dai_fmt,
+};
+
+#define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+			    SNDRV_PCM_FMTBIT_S24_LE | \
+			    SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver ep93xx_i2s_dai = {
+	.symmetric_rates= 1,
+	.suspend	= ep93xx_i2s_suspend,
+	.resume		= ep93xx_i2s_resume,
+	.playback	= {
+		.channels_min	= 2,
+		.channels_max	= 2,
+		.rates		= SNDRV_PCM_RATE_8000_48000,
+		.formats	= EP93XX_I2S_FORMATS,
+	},
+	.capture	= {
+		 .channels_min	= 2,
+		 .channels_max	= 2,
+		 .rates		= SNDRV_PCM_RATE_8000_48000,
+		 .formats	= EP93XX_I2S_FORMATS,
+	},
+	.ops		= &ep93xx_i2s_dai_ops,
+};
+
+static int ep93xx_i2s_probe(struct platform_device *pdev)
+{
+	struct ep93xx_i2s_info *info;
+	struct resource *res;
+	int err;
+
+	info = kzalloc(sizeof(struct ep93xx_i2s_info), GFP_KERNEL);
+	if (!info) {
+		err = -ENOMEM;
+		goto fail;
+	}
+
+	dev_set_drvdata(&pdev->dev, info);
+	info->dma_params = ep93xx_i2s_dma_params;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		err = -ENODEV;
+		goto fail;
+	}
+
+	info->mem = request_mem_region(res->start, resource_size(res),
+				       pdev->name);
+	if (!info->mem) {
+		err = -EBUSY;
+		goto fail;
+	}
+
+	info->regs = ioremap(info->mem->start, resource_size(info->mem));
+	if (!info->regs) {
+		err = -ENXIO;
+		goto fail_release_mem;
+	}
+
+	info->mclk = clk_get(&pdev->dev, "mclk");
+	if (IS_ERR(info->mclk)) {
+		err = PTR_ERR(info->mclk);
+		goto fail_unmap_mem;
+	}
+
+	info->sclk = clk_get(&pdev->dev, "sclk");
+	if (IS_ERR(info->sclk)) {
+		err = PTR_ERR(info->sclk);
+		goto fail_put_mclk;
+	}
+
+	info->lrclk = clk_get(&pdev->dev, "lrclk");
+	if (IS_ERR(info->lrclk)) {
+		err = PTR_ERR(info->lrclk);
+		goto fail_put_sclk;
+	}
+
+	err = snd_soc_register_dai(&pdev->dev, &ep93xx_i2s_dai);
+	if (err)
+		goto fail_put_lrclk;
+
+	return 0;
+
+fail_put_lrclk:
+	clk_put(info->lrclk);
+fail_put_sclk:
+	clk_put(info->sclk);
+fail_put_mclk:
+	clk_put(info->mclk);
+fail_unmap_mem:
+	iounmap(info->regs);
+fail_release_mem:
+	release_mem_region(info->mem->start, resource_size(info->mem));
+	kfree(info);
+fail:
+	return err;
+}
+
+static int __devexit ep93xx_i2s_remove(struct platform_device *pdev)
+{
+	struct ep93xx_i2s_info *info = dev_get_drvdata(&pdev->dev);
+
+	snd_soc_unregister_dai(&pdev->dev);
+	clk_put(info->lrclk);
+	clk_put(info->sclk);
+	clk_put(info->mclk);
+	iounmap(info->regs);
+	release_mem_region(info->mem->start, resource_size(info->mem));
+	kfree(info);
+	return 0;
+}
+
+static struct platform_driver ep93xx_i2s_driver = {
+	.probe	= ep93xx_i2s_probe,
+	.remove	= __devexit_p(ep93xx_i2s_remove),
+	.driver	= {
+		.name	= "ep93xx-i2s",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init ep93xx_i2s_init(void)
+{
+	return platform_driver_register(&ep93xx_i2s_driver);
+}
+
+static void __exit ep93xx_i2s_exit(void)
+{
+	platform_driver_unregister(&ep93xx_i2s_driver);
+}
+
+module_init(ep93xx_i2s_init);
+module_exit(ep93xx_i2s_exit);
+
+MODULE_ALIAS("platform:ep93xx-i2s");
+MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_DESCRIPTION("EP93XX I2S driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/ep93xx/ep93xx-pcm.c b/linux/sound/soc/ep93xx/ep93xx-pcm.c
new file mode 100644
index 0000000..2f121dd
--- /dev/null
+++ b/linux/sound/soc/ep93xx/ep93xx-pcm.c
@@ -0,0 +1,338 @@
+/*
+ * linux/sound/arm/ep93xx-pcm.c - EP93xx ALSA PCM interface
+ *
+ * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
+ * Copyright (C) 2006 Applied Data Systems
+ *
+ * Rewritten for the SoC audio subsystem (Based on PXA2xx code):
+ *   Copyright (c) 2008 Ryan Mallon <ryan@bluewatersys.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+#include <mach/hardware.h>
+#include <mach/ep93xx-regs.h>
+
+#include "ep93xx-pcm.h"
+
+static const struct snd_pcm_hardware ep93xx_pcm_hardware = {
+	.info			= (SNDRV_PCM_INFO_MMAP		|
+				   SNDRV_PCM_INFO_MMAP_VALID	|
+				   SNDRV_PCM_INFO_INTERLEAVED	|
+				   SNDRV_PCM_INFO_BLOCK_TRANSFER),
+				   
+	.rates			= SNDRV_PCM_RATE_8000_48000,
+	.rate_min		= SNDRV_PCM_RATE_8000,
+	.rate_max		= SNDRV_PCM_RATE_48000,
+	
+	.formats		= (SNDRV_PCM_FMTBIT_S16_LE |
+				   SNDRV_PCM_FMTBIT_S24_LE |
+				   SNDRV_PCM_FMTBIT_S32_LE),
+	
+	.buffer_bytes_max	= 131072,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 32768,
+	.periods_min		= 1,
+	.periods_max		= 32,
+	.fifo_size		= 32,
+};
+
+struct ep93xx_runtime_data
+{
+	struct ep93xx_dma_m2p_client	cl;
+	struct ep93xx_pcm_dma_params	*params;
+	int				pointer_bytes;
+	struct tasklet_struct		period_tasklet;
+	int				periods;
+	struct ep93xx_dma_buffer	buf[32];
+};
+
+static void ep93xx_pcm_period_elapsed(unsigned long data)
+{
+	struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
+	snd_pcm_period_elapsed(substream);
+}
+
+static void ep93xx_pcm_buffer_started(void *cookie,
+				      struct ep93xx_dma_buffer *buf)
+{
+}
+
+static void ep93xx_pcm_buffer_finished(void *cookie, 
+				       struct ep93xx_dma_buffer *buf, 
+				       int bytes, int error)
+{
+	struct snd_pcm_substream *substream = cookie;
+	struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+
+	if (buf == rtd->buf + rtd->periods - 1)
+		rtd->pointer_bytes = 0;
+	else
+		rtd->pointer_bytes += buf->size;
+
+	if (!error) {
+		ep93xx_dma_m2p_submit_recursive(&rtd->cl, buf);
+		tasklet_schedule(&rtd->period_tasklet);
+	} else {
+		snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+	}
+}
+
+static int ep93xx_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = soc_rtd->cpu_dai;
+	struct ep93xx_pcm_dma_params *dma_params;
+	struct ep93xx_runtime_data *rtd;    
+	int ret;
+
+	dma_params = snd_soc_dai_get_dma_data(cpu_dai, substream);
+	snd_soc_set_runtime_hwparams(substream, &ep93xx_pcm_hardware);
+
+	rtd = kmalloc(sizeof(*rtd), GFP_KERNEL);
+	if (!rtd) 
+		return -ENOMEM;
+
+	memset(&rtd->period_tasklet, 0, sizeof(rtd->period_tasklet));
+	rtd->period_tasklet.func = ep93xx_pcm_period_elapsed;
+	rtd->period_tasklet.data = (unsigned long)substream;
+
+	rtd->cl.name = dma_params->name;
+	rtd->cl.flags = dma_params->dma_port | EP93XX_DMA_M2P_IGNORE_ERROR |
+		((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		 EP93XX_DMA_M2P_TX : EP93XX_DMA_M2P_RX);
+	rtd->cl.cookie = substream;
+	rtd->cl.buffer_started = ep93xx_pcm_buffer_started;
+	rtd->cl.buffer_finished = ep93xx_pcm_buffer_finished;
+	ret = ep93xx_dma_m2p_client_register(&rtd->cl);
+	if (ret < 0) {
+		kfree(rtd);
+		return ret;
+	}
+	
+	substream->runtime->private_data = rtd;
+	return 0;
+}
+
+static int ep93xx_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+
+	ep93xx_dma_m2p_client_unregister(&rtd->cl);
+	kfree(rtd);
+	return 0;
+}
+
+static int ep93xx_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ep93xx_runtime_data *rtd = runtime->private_data;
+	size_t totsize = params_buffer_bytes(params);
+	size_t period = params_period_bytes(params);
+	int i;
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+	runtime->dma_bytes = totsize;
+
+	rtd->periods = (totsize + period - 1) / period;
+	for (i = 0; i < rtd->periods; i++) {
+		rtd->buf[i].bus_addr = runtime->dma_addr + (i * period);
+		rtd->buf[i].size = period;
+		if ((i + 1) * period > totsize)
+			rtd->buf[i].size = totsize - (i * period);
+	}
+
+	return 0;
+}
+
+static int ep93xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_set_runtime_buffer(substream, NULL);
+	return 0;
+}
+
+static int ep93xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+	int ret;
+	int i;
+
+	ret = 0;
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		rtd->pointer_bytes = 0;
+		for (i = 0; i < rtd->periods; i++)
+			ep93xx_dma_m2p_submit(&rtd->cl, rtd->buf + i);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		ep93xx_dma_m2p_flush(&rtd->cl);
+		break;
+
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static snd_pcm_uframes_t ep93xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+
+	/* FIXME: implement this with sub-period granularity */
+	return bytes_to_frames(runtime, rtd->pointer_bytes);
+}
+
+static int ep93xx_pcm_mmap(struct snd_pcm_substream *substream,
+			   struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+				     runtime->dma_area,
+				     runtime->dma_addr,
+				     runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops ep93xx_pcm_ops = {
+	.open		= ep93xx_pcm_open,
+	.close		= ep93xx_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= ep93xx_pcm_hw_params,
+	.hw_free	= ep93xx_pcm_hw_free,
+	.trigger	= ep93xx_pcm_trigger,
+	.pointer	= ep93xx_pcm_pointer,
+	.mmap		= ep93xx_pcm_mmap,
+};
+
+static int ep93xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = ep93xx_pcm_hardware.buffer_bytes_max;
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+	buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+					   &buf->addr, GFP_KERNEL);
+	buf->bytes = size;
+
+	return (buf->area == NULL) ? -ENOMEM : 0;
+}
+
+static void ep93xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++) {		
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+		
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+
+		dma_free_writecombine(pcm->card->dev, buf->bytes, buf->area,
+				      buf->addr);
+		buf->area = NULL;
+	}
+}
+
+static u64 ep93xx_pcm_dmamask = 0xffffffff;
+
+static int ep93xx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+			  struct snd_pcm *pcm)
+{
+	int ret = 0;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &ep93xx_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = 0xffffffff;
+
+	if (dai->driver->playback.channels_min) {
+		ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
+					SNDRV_PCM_STREAM_PLAYBACK);
+		if (ret)
+			return ret;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
+					SNDRV_PCM_STREAM_CAPTURE);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_platform_driver ep93xx_soc_platform = {
+	.ops		= &ep93xx_pcm_ops,
+	.pcm_new	= &ep93xx_pcm_new,
+	.pcm_free	= &ep93xx_pcm_free_dma_buffers,
+};
+
+static int __devinit ep93xx_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &ep93xx_soc_platform);
+}
+
+static int __devexit ep93xx_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver ep93xx_pcm_driver = {
+	.driver = {
+			.name = "ep93xx-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = ep93xx_soc_platform_probe,
+	.remove = __devexit_p(ep93xx_soc_platform_remove),
+};
+
+static int __init ep93xx_soc_platform_init(void)
+{
+	return platform_driver_register(&ep93xx_pcm_driver);
+}
+
+static void __exit ep93xx_soc_platform_exit(void)
+{
+	platform_driver_unregister(&ep93xx_pcm_driver);
+}
+
+module_init(ep93xx_soc_platform_init);
+module_exit(ep93xx_soc_platform_exit);
+
+MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_DESCRIPTION("EP93xx ALSA PCM interface");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/ep93xx/ep93xx-pcm.h b/linux/sound/soc/ep93xx/ep93xx-pcm.h
new file mode 100644
index 0000000..111e112
--- /dev/null
+++ b/linux/sound/soc/ep93xx/ep93xx-pcm.h
@@ -0,0 +1,20 @@
+/*
+ * sound/soc/ep93xx/ep93xx-pcm.h - EP93xx ALSA PCM interface
+ *
+ * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
+ * Copyright (C) 2006 Applied Data Systems
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _EP93XX_SND_SOC_PCM_H
+#define _EP93XX_SND_SOC_PCM_H
+
+struct ep93xx_pcm_dma_params {
+	char	*name;
+	int	dma_port;
+};
+
+#endif /* _EP93XX_SND_SOC_PCM_H */
diff --git a/linux/sound/soc/ep93xx/simone.c b/linux/sound/soc/ep93xx/simone.c
new file mode 100644
index 0000000..2868179
--- /dev/null
+++ b/linux/sound/soc/ep93xx/simone.c
@@ -0,0 +1,91 @@
+/*
+ * simone.c -- ASoC audio for Simplemachines Sim.One board
+ *
+ * Copyright (c) 2010 Mika Westerberg
+ *
+ * Based on snappercl15 machine driver by Ryan Mallon.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+
+#include "ep93xx-pcm.h"
+
+static struct snd_soc_dai_link simone_dai = {
+	.name		= "AC97",
+	.stream_name	= "AC97 HiFi",
+	.cpu_dai_name	= "ep93xx-ac97",
+	.codec_dai_name	= "ac97-hifi",
+	.codec_name	= "ac97-codec",
+	.platform_name	= "ep93xx-pcm-audio",
+};
+
+static struct snd_soc_card snd_soc_simone = {
+	.name		= "Sim.One",
+	.dai_link	= &simone_dai,
+	.num_links	= 1,
+};
+
+static struct platform_device *simone_snd_ac97_device;
+static struct platform_device *simone_snd_device;
+
+static int __init simone_init(void)
+{
+	int ret;
+
+	if (!machine_is_sim_one())
+		return -ENODEV;
+
+	simone_snd_ac97_device = platform_device_alloc("ac97-codec", -1);
+	if (!simone_snd_ac97_device)
+		return -ENOMEM;
+
+	ret = platform_device_add(simone_snd_ac97_device);
+	if (ret)
+		goto fail1;
+
+	simone_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!simone_snd_device) {
+		ret = -ENOMEM;
+		goto fail2;
+	}
+
+	platform_set_drvdata(simone_snd_device, &snd_soc_simone);
+	ret = platform_device_add(simone_snd_device);
+	if (ret)
+		goto fail3;
+
+	return 0;
+
+fail3:
+	platform_device_put(simone_snd_device);
+fail2:
+	platform_device_del(simone_snd_ac97_device);
+fail1:
+	platform_device_put(simone_snd_ac97_device);
+	return ret;
+}
+module_init(simone_init);
+
+static void __exit simone_exit(void)
+{
+	platform_device_unregister(simone_snd_device);
+	platform_device_unregister(simone_snd_ac97_device);
+}
+module_exit(simone_exit);
+
+MODULE_DESCRIPTION("ALSA SoC Simplemachines Sim.One");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/ep93xx/snappercl15.c b/linux/sound/soc/ep93xx/snappercl15.c
new file mode 100644
index 0000000..28ab5ff
--- /dev/null
+++ b/linux/sound/soc/ep93xx/snappercl15.c
@@ -0,0 +1,146 @@
+/*
+ * snappercl15.c -- SoC audio for Bluewater Systems Snapper CL15 module
+ *
+ * Copyright (C) 2008 Bluewater Systems Ltd
+ * Author: Ryan Mallon <ryan@bluewatersys.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+
+#include "../codecs/tlv320aic23.h"
+#include "ep93xx-pcm.h"
+
+#define CODEC_CLOCK 5644800
+
+static int snappercl15_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int err;
+
+	err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_IF |
+				  SND_SOC_DAIFMT_CBS_CFS);
+
+	err = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | 
+				  SND_SOC_DAIFMT_NB_IF |		  
+				  SND_SOC_DAIFMT_CBS_CFS);
+	if (err)
+		return err;
+
+	err = snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, 
+				     SND_SOC_CLOCK_IN);
+	if (err)
+		return err;
+
+	err = snd_soc_dai_set_sysclk(cpu_dai, 0, CODEC_CLOCK, 
+				     SND_SOC_CLOCK_OUT);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static struct snd_soc_ops snappercl15_ops = {
+	.hw_params	= snappercl15_hw_params,
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"Headphone Jack", NULL, "LHPOUT"},
+	{"Headphone Jack", NULL, "RHPOUT"},
+
+	{"LLINEIN", NULL, "Line In"},
+	{"RLINEIN", NULL, "Line In"},
+
+	{"MICIN", NULL, "Mic Jack"},
+};
+
+static int snappercl15_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets,
+				  ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+	return 0;
+}
+
+static struct snd_soc_dai_link snappercl15_dai = {
+	.name		= "tlv320aic23",
+	.stream_name	= "AIC23",
+	.cpu_dai_name	= "ep93xx-i2s",
+	.codec_dai_name	= "tlv320aic23-hifi",
+	.codec_name	= "tlv320aic23-codec.0-001a",
+	.platform_name	=  "ep93xx-pcm-audio",
+	.init		= snappercl15_tlv320aic23_init,
+	.ops		= &snappercl15_ops,
+};
+
+static struct snd_soc_card snd_soc_snappercl15 = {
+	.name		= "Snapper CL15",
+	.dai_link	= &snappercl15_dai,
+	.num_links	= 1,
+};
+
+static struct platform_device *snappercl15_snd_device;
+
+static int __init snappercl15_init(void)
+{
+	int ret;
+
+	if (!machine_is_snapper_cl15())
+		return -ENODEV;
+
+	ret = ep93xx_i2s_acquire(EP93XX_SYSCON_DEVCFG_I2SONAC97,
+				 EP93XX_SYSCON_I2SCLKDIV_ORIDE |
+				 EP93XX_SYSCON_I2SCLKDIV_SPOL);
+	if (ret)
+		return ret;
+
+	snappercl15_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!snappercl15_snd_device)
+		return -ENOMEM;
+	
+	platform_set_drvdata(snappercl15_snd_device, &snd_soc_snappercl15);
+	ret = platform_device_add(snappercl15_snd_device);
+	if (ret)
+		platform_device_put(snappercl15_snd_device);
+
+	return ret;
+}
+
+static void __exit snappercl15_exit(void)
+{
+	platform_device_unregister(snappercl15_snd_device);
+	ep93xx_i2s_release();
+}
+
+module_init(snappercl15_init);
+module_exit(snappercl15_exit);
+
+MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_DESCRIPTION("ALSA SoC Snapper CL15");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/fsl/Kconfig b/linux/sound/soc/fsl/Kconfig
new file mode 100644
index 0000000..d754d34
--- /dev/null
+++ b/linux/sound/soc/fsl/Kconfig
@@ -0,0 +1,67 @@
+config SND_MPC52xx_DMA
+	tristate
+
+# ASoC platform support for the Freescale PowerPC SOCs that have an SSI and
+# an Elo DMA controller, such as the MPC8610 and P1022.  You will still need to
+# select a platform driver and a codec driver.
+config SND_SOC_POWERPC_SSI
+	tristate
+	depends on FSL_SOC
+
+config SND_SOC_MPC8610_HPCD
+	tristate "ALSA SoC support for the Freescale MPC8610 HPCD board"
+	# I2C is necessary for the CS4270 driver
+	depends on MPC8610_HPCD && I2C
+	select SND_SOC_POWERPC_SSI
+	select SND_SOC_CS4270
+	select SND_SOC_CS4270_VD33_ERRATA
+	default y if MPC8610_HPCD
+	help
+	  Say Y if you want to enable audio on the Freescale MPC8610 HPCD.
+
+config SND_SOC_P1022_DS
+	tristate "ALSA SoC support for the Freescale P1022 DS board"
+	# I2C is necessary for the WM8776 driver
+	depends on P1022_DS && I2C
+	select SND_SOC_POWERPC_SSI
+	select SND_SOC_WM8776
+	default y if P1022_DS
+	help
+	  Say Y if you want to enable audio on the Freescale P1022 DS board.
+	  This will also include the Wolfson Microelectronics WM8776 codec
+	  driver.
+
+config SND_SOC_MPC5200_I2S
+	tristate "Freescale MPC5200 PSC in I2S mode driver"
+	depends on PPC_MPC52xx && PPC_BESTCOMM
+	select SND_MPC52xx_DMA
+	select PPC_BESTCOMM_GEN_BD
+	help
+	  Say Y here to support the MPC5200 PSCs in I2S mode.
+
+config SND_SOC_MPC5200_AC97
+	tristate "Freescale MPC5200 PSC in AC97 mode driver"
+	depends on PPC_MPC52xx && PPC_BESTCOMM
+	select SND_SOC_AC97_BUS
+	select SND_MPC52xx_DMA
+	select PPC_BESTCOMM_GEN_BD
+	help
+	  Say Y here to support the MPC5200 PSCs in AC97 mode.
+
+config SND_MPC52xx_SOC_PCM030
+	tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712"
+	depends on PPC_MPC5200_SIMPLE
+	select SND_SOC_MPC5200_AC97
+	select SND_SOC_WM9712
+	help
+	  Say Y if you want to add support for sound on the Phytec pcm030
+	  baseboard.
+
+config SND_MPC52xx_SOC_EFIKA
+	tristate "SoC AC97 Audio support for bbplan Efika and STAC9766"
+	depends on PPC_EFIKA
+	select SND_SOC_MPC5200_AC97
+	select SND_SOC_STAC9766
+	help
+	  Say Y if you want to add support for sound on the Efika.
+
diff --git a/linux/sound/soc/fsl/Makefile b/linux/sound/soc/fsl/Makefile
new file mode 100644
index 0000000..b4a38c0
--- /dev/null
+++ b/linux/sound/soc/fsl/Makefile
@@ -0,0 +1,22 @@
+# MPC8610 HPCD Machine Support
+snd-soc-mpc8610-hpcd-objs := mpc8610_hpcd.o
+obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += snd-soc-mpc8610-hpcd.o
+
+# P1022 DS Machine Support
+snd-soc-p1022-ds-objs := p1022_ds.o
+obj-$(CONFIG_SND_SOC_P1022_DS) += snd-soc-p1022-ds.o
+
+# Freescale PowerPC SSI/DMA Platform Support
+snd-soc-fsl-ssi-objs := fsl_ssi.o
+snd-soc-fsl-dma-objs := fsl_dma.o
+obj-$(CONFIG_SND_SOC_POWERPC_SSI) += snd-soc-fsl-ssi.o snd-soc-fsl-dma.o
+
+# MPC5200 Platform Support
+obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
+obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o
+obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o
+
+# MPC5200 Machine Support
+obj-$(CONFIG_SND_MPC52xx_SOC_PCM030) += pcm030-audio-fabric.o
+obj-$(CONFIG_SND_MPC52xx_SOC_EFIKA) += efika-audio-fabric.o
+
diff --git a/linux/sound/soc/fsl/efika-audio-fabric.c b/linux/sound/soc/fsl/efika-audio-fabric.c
new file mode 100644
index 0000000..108b5d8
--- /dev/null
+++ b/linux/sound/soc/fsl/efika-audio-fabric.c
@@ -0,0 +1,91 @@
+/*
+ * Efika driver for the PSC of the Freescale MPC52xx
+ * configured as AC97 interface
+ *
+ * Copyright 2008 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "mpc5200_dma.h"
+#include "mpc5200_psc_ac97.h"
+#include "../codecs/stac9766.h"
+
+#define DRV_NAME "efika-audio-fabric"
+
+static struct snd_soc_card card;
+
+static struct snd_soc_dai_link efika_fabric_dai[] = {
+{
+	.name = "AC97",
+	.stream_name = "AC97 Analog",
+	.codec_dai_name = "stac9766-hifi-analog",
+	.cpu_dai_name = "mpc5200-psc-ac97.0",
+	.platform_name = "mpc5200-pcm-audio",
+	.codec_name = "stac9766-codec",
+},
+{
+	.name = "AC97",
+	.stream_name = "AC97 IEC958",
+	.codec_dai_name = "stac9766-hifi-IEC958",
+	.cpu_dai_name = "mpc5200-psc-ac97.1",
+	.platform_name = "mpc5200-pcm-audio",
+	.codec_name = "stac9766-codec",
+},
+};
+
+static __init int efika_fabric_init(void)
+{
+	struct platform_device *pdev;
+	int rc;
+
+	if (!of_machine_is_compatible("bplan,efika"))
+		return -ENODEV;
+
+	card.name = "Efika";
+	card.dai_link = efika_fabric_dai;
+	card.num_links = ARRAY_SIZE(efika_fabric_dai);
+
+
+	pdev = platform_device_alloc("soc-audio", 1);
+	if (!pdev) {
+		pr_err("efika_fabric_init: platform_device_alloc() failed\n");
+		return -ENODEV;
+	}
+
+	platform_set_drvdata(pdev, &card);
+
+	rc = platform_device_add(pdev);
+	if (rc) {
+		pr_err("efika_fabric_init: platform_device_add() failed\n");
+		platform_device_put(pdev);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+module_init(efika_fabric_init);
+
+
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_DESCRIPTION(DRV_NAME ": mpc5200 Efika fabric driver");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/fsl/fsl_dma.c b/linux/sound/soc/fsl/fsl_dma.c
new file mode 100644
index 0000000..4cf98c0
--- /dev/null
+++ b/linux/sound/soc/fsl/fsl_dma.c
@@ -0,0 +1,1009 @@
+/*
+ * Freescale DMA ALSA SoC PCM driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ * This driver implements ASoC support for the Elo DMA controller, which is
+ * the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms,
+ * the PCM driver is what handles the DMA buffer.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include <linux/of_platform.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/io.h>
+
+#include "fsl_dma.h"
+#include "fsl_ssi.h"	/* For the offset of stx0 and srx0 */
+
+/*
+ * The formats that the DMA controller supports, which is anything
+ * that is 8, 16, or 32 bits.
+ */
+#define FSLDMA_PCM_FORMATS (SNDRV_PCM_FMTBIT_S8 	| \
+			    SNDRV_PCM_FMTBIT_U8 	| \
+			    SNDRV_PCM_FMTBIT_S16_LE     | \
+			    SNDRV_PCM_FMTBIT_S16_BE     | \
+			    SNDRV_PCM_FMTBIT_U16_LE     | \
+			    SNDRV_PCM_FMTBIT_U16_BE     | \
+			    SNDRV_PCM_FMTBIT_S24_LE     | \
+			    SNDRV_PCM_FMTBIT_S24_BE     | \
+			    SNDRV_PCM_FMTBIT_U24_LE     | \
+			    SNDRV_PCM_FMTBIT_U24_BE     | \
+			    SNDRV_PCM_FMTBIT_S32_LE     | \
+			    SNDRV_PCM_FMTBIT_S32_BE     | \
+			    SNDRV_PCM_FMTBIT_U32_LE     | \
+			    SNDRV_PCM_FMTBIT_U32_BE)
+
+#define FSLDMA_PCM_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+			  SNDRV_PCM_RATE_CONTINUOUS)
+
+struct dma_object {
+	struct snd_soc_platform_driver dai;
+	dma_addr_t ssi_stx_phys;
+	dma_addr_t ssi_srx_phys;
+	unsigned int ssi_fifo_depth;
+	struct ccsr_dma_channel __iomem *channel;
+	unsigned int irq;
+	bool assigned;
+	char path[1];
+};
+
+/*
+ * The number of DMA links to use.  Two is the bare minimum, but if you
+ * have really small links you might need more.
+ */
+#define NUM_DMA_LINKS   2
+
+/** fsl_dma_private: p-substream DMA data
+ *
+ * Each substream has a 1-to-1 association with a DMA channel.
+ *
+ * The link[] array is first because it needs to be aligned on a 32-byte
+ * boundary, so putting it first will ensure alignment without padding the
+ * structure.
+ *
+ * @link[]: array of link descriptors
+ * @dma_channel: pointer to the DMA channel's registers
+ * @irq: IRQ for this DMA channel
+ * @substream: pointer to the substream object, needed by the ISR
+ * @ssi_sxx_phys: bus address of the STX or SRX register to use
+ * @ld_buf_phys: physical address of the LD buffer
+ * @current_link: index into link[] of the link currently being processed
+ * @dma_buf_phys: physical address of the DMA buffer
+ * @dma_buf_next: physical address of the next period to process
+ * @dma_buf_end: physical address of the byte after the end of the DMA
+ * @buffer period_size: the size of a single period
+ * @num_periods: the number of periods in the DMA buffer
+ */
+struct fsl_dma_private {
+	struct fsl_dma_link_descriptor link[NUM_DMA_LINKS];
+	struct ccsr_dma_channel __iomem *dma_channel;
+	unsigned int irq;
+	struct snd_pcm_substream *substream;
+	dma_addr_t ssi_sxx_phys;
+	unsigned int ssi_fifo_depth;
+	dma_addr_t ld_buf_phys;
+	unsigned int current_link;
+	dma_addr_t dma_buf_phys;
+	dma_addr_t dma_buf_next;
+	dma_addr_t dma_buf_end;
+	size_t period_size;
+	unsigned int num_periods;
+};
+
+/**
+ * fsl_dma_hardare: define characteristics of the PCM hardware.
+ *
+ * The PCM hardware is the Freescale DMA controller.  This structure defines
+ * the capabilities of that hardware.
+ *
+ * Since the sampling rate and data format are not controlled by the DMA
+ * controller, we specify no limits for those values.  The only exception is
+ * period_bytes_min, which is set to a reasonably low value to prevent the
+ * DMA controller from generating too many interrupts per second.
+ *
+ * Since each link descriptor has a 32-bit byte count field, we set
+ * period_bytes_max to the largest 32-bit number.  We also have no maximum
+ * number of periods.
+ *
+ * Note that we specify SNDRV_PCM_INFO_JOINT_DUPLEX here, but only because a
+ * limitation in the SSI driver requires the sample rates for playback and
+ * capture to be the same.
+ */
+static const struct snd_pcm_hardware fsl_dma_hardware = {
+
+	.info   		= SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_JOINT_DUPLEX |
+				  SNDRV_PCM_INFO_PAUSE,
+	.formats		= FSLDMA_PCM_FORMATS,
+	.rates  		= FSLDMA_PCM_RATES,
+	.rate_min       	= 5512,
+	.rate_max       	= 192000,
+	.period_bytes_min       = 512,  	/* A reasonable limit */
+	.period_bytes_max       = (u32) -1,
+	.periods_min    	= NUM_DMA_LINKS,
+	.periods_max    	= (unsigned int) -1,
+	.buffer_bytes_max       = 128 * 1024,   /* A reasonable limit */
+};
+
+/**
+ * fsl_dma_abort_stream: tell ALSA that the DMA transfer has aborted
+ *
+ * This function should be called by the ISR whenever the DMA controller
+ * halts data transfer.
+ */
+static void fsl_dma_abort_stream(struct snd_pcm_substream *substream)
+{
+	unsigned long flags;
+
+	snd_pcm_stream_lock_irqsave(substream, flags);
+
+	if (snd_pcm_running(substream))
+		snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+
+	snd_pcm_stream_unlock_irqrestore(substream, flags);
+}
+
+/**
+ * fsl_dma_update_pointers - update LD pointers to point to the next period
+ *
+ * As each period is completed, this function changes the the link
+ * descriptor pointers for that period to point to the next period.
+ */
+static void fsl_dma_update_pointers(struct fsl_dma_private *dma_private)
+{
+	struct fsl_dma_link_descriptor *link =
+		&dma_private->link[dma_private->current_link];
+
+	/* Update our link descriptors to point to the next period. On a 36-bit
+	 * system, we also need to update the ESAD bits.  We also set (keep) the
+	 * snoop bits.  See the comments in fsl_dma_hw_params() about snooping.
+	 */
+	if (dma_private->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		link->source_addr = cpu_to_be32(dma_private->dma_buf_next);
+#ifdef CONFIG_PHYS_64BIT
+		link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+			upper_32_bits(dma_private->dma_buf_next));
+#endif
+	} else {
+		link->dest_addr = cpu_to_be32(dma_private->dma_buf_next);
+#ifdef CONFIG_PHYS_64BIT
+		link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+			upper_32_bits(dma_private->dma_buf_next));
+#endif
+	}
+
+	/* Update our variables for next time */
+	dma_private->dma_buf_next += dma_private->period_size;
+
+	if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
+		dma_private->dma_buf_next = dma_private->dma_buf_phys;
+
+	if (++dma_private->current_link >= NUM_DMA_LINKS)
+		dma_private->current_link = 0;
+}
+
+/**
+ * fsl_dma_isr: interrupt handler for the DMA controller
+ *
+ * @irq: IRQ of the DMA channel
+ * @dev_id: pointer to the dma_private structure for this DMA channel
+ */
+static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
+{
+	struct fsl_dma_private *dma_private = dev_id;
+	struct snd_pcm_substream *substream = dma_private->substream;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct device *dev = rtd->platform->dev;
+	struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+	irqreturn_t ret = IRQ_NONE;
+	u32 sr, sr2 = 0;
+
+	/* We got an interrupt, so read the status register to see what we
+	   were interrupted for.
+	 */
+	sr = in_be32(&dma_channel->sr);
+
+	if (sr & CCSR_DMA_SR_TE) {
+		dev_err(dev, "dma transmit error\n");
+		fsl_dma_abort_stream(substream);
+		sr2 |= CCSR_DMA_SR_TE;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_CH)
+		ret = IRQ_HANDLED;
+
+	if (sr & CCSR_DMA_SR_PE) {
+		dev_err(dev, "dma programming error\n");
+		fsl_dma_abort_stream(substream);
+		sr2 |= CCSR_DMA_SR_PE;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_EOLNI) {
+		sr2 |= CCSR_DMA_SR_EOLNI;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_CB)
+		ret = IRQ_HANDLED;
+
+	if (sr & CCSR_DMA_SR_EOSI) {
+		/* Tell ALSA we completed a period. */
+		snd_pcm_period_elapsed(substream);
+
+		/*
+		 * Update our link descriptors to point to the next period. We
+		 * only need to do this if the number of periods is not equal to
+		 * the number of links.
+		 */
+		if (dma_private->num_periods != NUM_DMA_LINKS)
+			fsl_dma_update_pointers(dma_private);
+
+		sr2 |= CCSR_DMA_SR_EOSI;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_EOLSI) {
+		sr2 |= CCSR_DMA_SR_EOLSI;
+		ret = IRQ_HANDLED;
+	}
+
+	/* Clear the bits that we set */
+	if (sr2)
+		out_be32(&dma_channel->sr, sr2);
+
+	return ret;
+}
+
+/**
+ * fsl_dma_new: initialize this PCM driver.
+ *
+ * This function is called when the codec driver calls snd_soc_new_pcms(),
+ * once for each .dai_link in the machine driver's snd_soc_card
+ * structure.
+ *
+ * snd_dma_alloc_pages() is just a front-end to dma_alloc_coherent(), which
+ * (currently) always allocates the DMA buffer in lowmem, even if GFP_HIGHMEM
+ * is specified. Therefore, any DMA buffers we allocate will always be in low
+ * memory, but we support for 36-bit physical addresses anyway.
+ *
+ * Regardless of where the memory is actually allocated, since the device can
+ * technically DMA to any 36-bit address, we do need to set the DMA mask to 36.
+ */
+static int fsl_dma_new(struct snd_card *card, struct snd_soc_dai *dai,
+	struct snd_pcm *pcm)
+{
+	static u64 fsl_dma_dmamask = DMA_BIT_MASK(36);
+	int ret;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &fsl_dma_dmamask;
+
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = fsl_dma_dmamask;
+
+	/* Some codecs have separate DAIs for playback and capture, so we
+	 * should allocate a DMA buffer only for the streams that are valid.
+	 */
+
+	if (dai->driver->playback.channels_min) {
+		ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev,
+			fsl_dma_hardware.buffer_bytes_max,
+			&pcm->streams[0].substream->dma_buffer);
+		if (ret) {
+			dev_err(card->dev, "can't alloc playback dma buffer\n");
+			return ret;
+		}
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev,
+			fsl_dma_hardware.buffer_bytes_max,
+			&pcm->streams[1].substream->dma_buffer);
+		if (ret) {
+			snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
+			dev_err(card->dev, "can't alloc capture dma buffer\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_dma_open: open a new substream.
+ *
+ * Each substream has its own DMA buffer.
+ *
+ * ALSA divides the DMA buffer into N periods.  We create NUM_DMA_LINKS link
+ * descriptors that ping-pong from one period to the next.  For example, if
+ * there are six periods and two link descriptors, this is how they look
+ * before playback starts:
+ *
+ *      	   The last link descriptor
+ *   ____________  points back to the first
+ *  |   	 |
+ *  V   	 |
+ *  ___    ___   |
+ * |   |->|   |->|
+ * |___|  |___|
+ *   |      |
+ *   |      |
+ *   V      V
+ *  _________________________________________
+ * |      |      |      |      |      |      |  The DMA buffer is
+ * |      |      |      |      |      |      |    divided into 6 parts
+ * |______|______|______|______|______|______|
+ *
+ * and here's how they look after the first period is finished playing:
+ *
+ *   ____________
+ *  |   	 |
+ *  V   	 |
+ *  ___    ___   |
+ * |   |->|   |->|
+ * |___|  |___|
+ *   |      |
+ *   |______________
+ *          |       |
+ *          V       V
+ *  _________________________________________
+ * |      |      |      |      |      |      |
+ * |      |      |      |      |      |      |
+ * |______|______|______|______|______|______|
+ *
+ * The first link descriptor now points to the third period.  The DMA
+ * controller is currently playing the second period.  When it finishes, it
+ * will jump back to the first descriptor and play the third period.
+ *
+ * There are four reasons we do this:
+ *
+ * 1. The only way to get the DMA controller to automatically restart the
+ *    transfer when it gets to the end of the buffer is to use chaining
+ *    mode.  Basic direct mode doesn't offer that feature.
+ * 2. We need to receive an interrupt at the end of every period.  The DMA
+ *    controller can generate an interrupt at the end of every link transfer
+ *    (aka segment).  Making each period into a DMA segment will give us the
+ *    interrupts we need.
+ * 3. By creating only two link descriptors, regardless of the number of
+ *    periods, we do not need to reallocate the link descriptors if the
+ *    number of periods changes.
+ * 4. All of the audio data is still stored in a single, contiguous DMA
+ *    buffer, which is what ALSA expects.  We're just dividing it into
+ *    contiguous parts, and creating a link descriptor for each one.
+ */
+static int fsl_dma_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct device *dev = rtd->platform->dev;
+	struct dma_object *dma =
+		container_of(rtd->platform->driver, struct dma_object, dai);
+	struct fsl_dma_private *dma_private;
+	struct ccsr_dma_channel __iomem *dma_channel;
+	dma_addr_t ld_buf_phys;
+	u64 temp_link;  	/* Pointer to next link descriptor */
+	u32 mr;
+	unsigned int channel;
+	int ret = 0;
+	unsigned int i;
+
+	/*
+	 * Reject any DMA buffer whose size is not a multiple of the period
+	 * size.  We need to make sure that the DMA buffer can be evenly divided
+	 * into periods.
+	 */
+	ret = snd_pcm_hw_constraint_integer(runtime,
+		SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0) {
+		dev_err(dev, "invalid buffer size\n");
+		return ret;
+	}
+
+	channel = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+
+	if (dma->assigned) {
+		dev_err(dev, "dma channel already assigned\n");
+		return -EBUSY;
+	}
+
+	dma_private = dma_alloc_coherent(dev, sizeof(struct fsl_dma_private),
+					 &ld_buf_phys, GFP_KERNEL);
+	if (!dma_private) {
+		dev_err(dev, "can't allocate dma private data\n");
+		return -ENOMEM;
+	}
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_private->ssi_sxx_phys = dma->ssi_stx_phys;
+	else
+		dma_private->ssi_sxx_phys = dma->ssi_srx_phys;
+
+	dma_private->ssi_fifo_depth = dma->ssi_fifo_depth;
+	dma_private->dma_channel = dma->channel;
+	dma_private->irq = dma->irq;
+	dma_private->substream = substream;
+	dma_private->ld_buf_phys = ld_buf_phys;
+	dma_private->dma_buf_phys = substream->dma_buffer.addr;
+
+	ret = request_irq(dma_private->irq, fsl_dma_isr, 0, "DMA", dma_private);
+	if (ret) {
+		dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n",
+			dma_private->irq, ret);
+		dma_free_coherent(dev, sizeof(struct fsl_dma_private),
+			dma_private, dma_private->ld_buf_phys);
+		return ret;
+	}
+
+	dma->assigned = 1;
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+	snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware);
+	runtime->private_data = dma_private;
+
+	/* Program the fixed DMA controller parameters */
+
+	dma_channel = dma_private->dma_channel;
+
+	temp_link = dma_private->ld_buf_phys +
+		sizeof(struct fsl_dma_link_descriptor);
+
+	for (i = 0; i < NUM_DMA_LINKS; i++) {
+		dma_private->link[i].next = cpu_to_be64(temp_link);
+
+		temp_link += sizeof(struct fsl_dma_link_descriptor);
+	}
+	/* The last link descriptor points to the first */
+	dma_private->link[i - 1].next = cpu_to_be64(dma_private->ld_buf_phys);
+
+	/* Tell the DMA controller where the first link descriptor is */
+	out_be32(&dma_channel->clndar,
+		CCSR_DMA_CLNDAR_ADDR(dma_private->ld_buf_phys));
+	out_be32(&dma_channel->eclndar,
+		CCSR_DMA_ECLNDAR_ADDR(dma_private->ld_buf_phys));
+
+	/* The manual says the BCR must be clear before enabling EMP */
+	out_be32(&dma_channel->bcr, 0);
+
+	/*
+	 * Program the mode register for interrupts, external master control,
+	 * and source/destination hold.  Also clear the Channel Abort bit.
+	 */
+	mr = in_be32(&dma_channel->mr) &
+		~(CCSR_DMA_MR_CA | CCSR_DMA_MR_DAHE | CCSR_DMA_MR_SAHE);
+
+	/*
+	 * We want External Master Start and External Master Pause enabled,
+	 * because the SSI is controlling the DMA controller.  We want the DMA
+	 * controller to be set up in advance, and then we signal only the SSI
+	 * to start transferring.
+	 *
+	 * We want End-Of-Segment Interrupts enabled, because this will generate
+	 * an interrupt at the end of each segment (each link descriptor
+	 * represents one segment).  Each DMA segment is the same thing as an
+	 * ALSA period, so this is how we get an interrupt at the end of every
+	 * period.
+	 *
+	 * We want Error Interrupt enabled, so that we can get an error if
+	 * the DMA controller is mis-programmed somehow.
+	 */
+	mr |= CCSR_DMA_MR_EOSIE | CCSR_DMA_MR_EIE | CCSR_DMA_MR_EMP_EN |
+		CCSR_DMA_MR_EMS_EN;
+
+	/* For playback, we want the destination address to be held.  For
+	   capture, set the source address to be held. */
+	mr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		CCSR_DMA_MR_DAHE : CCSR_DMA_MR_SAHE;
+
+	out_be32(&dma_channel->mr, mr);
+
+	return 0;
+}
+
+/**
+ * fsl_dma_hw_params: continue initializing the DMA links
+ *
+ * This function obtains hardware parameters about the opened stream and
+ * programs the DMA controller accordingly.
+ *
+ * One drawback of big-endian is that when copying integers of different
+ * sizes to a fixed-sized register, the address to which the integer must be
+ * copied is dependent on the size of the integer.
+ *
+ * For example, if P is the address of a 32-bit register, and X is a 32-bit
+ * integer, then X should be copied to address P.  However, if X is a 16-bit
+ * integer, then it should be copied to P+2.  If X is an 8-bit register,
+ * then it should be copied to P+3.
+ *
+ * So for playback of 8-bit samples, the DMA controller must transfer single
+ * bytes from the DMA buffer to the last byte of the STX0 register, i.e.
+ * offset by 3 bytes. For 16-bit samples, the offset is two bytes.
+ *
+ * For 24-bit samples, the offset is 1 byte.  However, the DMA controller
+ * does not support 3-byte copies (the DAHTS register supports only 1, 2, 4,
+ * and 8 bytes at a time).  So we do not support packed 24-bit samples.
+ * 24-bit data must be padded to 32 bits.
+ */
+static int fsl_dma_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct device *dev = rtd->platform->dev;
+
+	/* Number of bits per sample */
+	unsigned int sample_bits =
+		snd_pcm_format_physical_width(params_format(hw_params));
+
+	/* Number of bytes per frame */
+	unsigned int sample_bytes = sample_bits / 8;
+
+	/* Bus address of SSI STX register */
+	dma_addr_t ssi_sxx_phys = dma_private->ssi_sxx_phys;
+
+	/* Size of the DMA buffer, in bytes */
+	size_t buffer_size = params_buffer_bytes(hw_params);
+
+	/* Number of bytes per period */
+	size_t period_size = params_period_bytes(hw_params);
+
+	/* Pointer to next period */
+	dma_addr_t temp_addr = substream->dma_buffer.addr;
+
+	/* Pointer to DMA controller */
+	struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+
+	u32 mr; /* DMA Mode Register */
+
+	unsigned int i;
+
+	/* Initialize our DMA tracking variables */
+	dma_private->period_size = period_size;
+	dma_private->num_periods = params_periods(hw_params);
+	dma_private->dma_buf_end = dma_private->dma_buf_phys + buffer_size;
+	dma_private->dma_buf_next = dma_private->dma_buf_phys +
+		(NUM_DMA_LINKS * period_size);
+
+	if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
+		/* This happens if the number of periods == NUM_DMA_LINKS */
+		dma_private->dma_buf_next = dma_private->dma_buf_phys;
+
+	mr = in_be32(&dma_channel->mr) & ~(CCSR_DMA_MR_BWC_MASK |
+		  CCSR_DMA_MR_SAHTS_MASK | CCSR_DMA_MR_DAHTS_MASK);
+
+	/* Due to a quirk of the SSI's STX register, the target address
+	 * for the DMA operations depends on the sample size.  So we calculate
+	 * that offset here.  While we're at it, also tell the DMA controller
+	 * how much data to transfer per sample.
+	 */
+	switch (sample_bits) {
+	case 8:
+		mr |= CCSR_DMA_MR_DAHTS_1 | CCSR_DMA_MR_SAHTS_1;
+		ssi_sxx_phys += 3;
+		break;
+	case 16:
+		mr |= CCSR_DMA_MR_DAHTS_2 | CCSR_DMA_MR_SAHTS_2;
+		ssi_sxx_phys += 2;
+		break;
+	case 32:
+		mr |= CCSR_DMA_MR_DAHTS_4 | CCSR_DMA_MR_SAHTS_4;
+		break;
+	default:
+		/* We should never get here */
+		dev_err(dev, "unsupported sample size %u\n", sample_bits);
+		return -EINVAL;
+	}
+
+	/*
+	 * BWC determines how many bytes are sent/received before the DMA
+	 * controller checks the SSI to see if it needs to stop. BWC should
+	 * always be a multiple of the frame size, so that we always transmit
+	 * whole frames.  Each frame occupies two slots in the FIFO.  The
+	 * parameter for CCSR_DMA_MR_BWC() is rounded down the next power of two
+	 * (MR[BWC] can only represent even powers of two).
+	 *
+	 * To simplify the process, we set BWC to the largest value that is
+	 * less than or equal to the FIFO watermark.  For playback, this ensures
+	 * that we transfer the maximum amount without overrunning the FIFO.
+	 * For capture, this ensures that we transfer the maximum amount without
+	 * underrunning the FIFO.
+	 *
+	 * f = SSI FIFO depth
+	 * w = SSI watermark value (which equals f - 2)
+	 * b = DMA bandwidth count (in bytes)
+	 * s = sample size (in bytes, which equals frame_size * 2)
+	 *
+	 * For playback, we never transmit more than the transmit FIFO
+	 * watermark, otherwise we might write more data than the FIFO can hold.
+	 * The watermark is equal to the FIFO depth minus two.
+	 *
+	 * For capture, two equations must hold:
+	 *	w > f - (b / s)
+	 *	w >= b / s
+	 *
+	 * So, b > 2 * s, but b must also be <= s * w.  To simplify, we set
+	 * b = s * w, which is equal to
+	 *      (dma_private->ssi_fifo_depth - 2) * sample_bytes.
+	 */
+	mr |= CCSR_DMA_MR_BWC((dma_private->ssi_fifo_depth - 2) * sample_bytes);
+
+	out_be32(&dma_channel->mr, mr);
+
+	for (i = 0; i < NUM_DMA_LINKS; i++) {
+		struct fsl_dma_link_descriptor *link = &dma_private->link[i];
+
+		link->count = cpu_to_be32(period_size);
+
+		/* The snoop bit tells the DMA controller whether it should tell
+		 * the ECM to snoop during a read or write to an address. For
+		 * audio, we use DMA to transfer data between memory and an I/O
+		 * device (the SSI's STX0 or SRX0 register). Snooping is only
+		 * needed if there is a cache, so we need to snoop memory
+		 * addresses only.  For playback, that means we snoop the source
+		 * but not the destination.  For capture, we snoop the
+		 * destination but not the source.
+		 *
+		 * Note that failing to snoop properly is unlikely to cause
+		 * cache incoherency if the period size is larger than the
+		 * size of L1 cache.  This is because filling in one period will
+		 * flush out the data for the previous period.  So if you
+		 * increased period_bytes_min to a large enough size, you might
+		 * get more performance by not snooping, and you'll still be
+		 * okay.  You'll need to update fsl_dma_update_pointers() also.
+		 */
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			link->source_addr = cpu_to_be32(temp_addr);
+			link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+				upper_32_bits(temp_addr));
+
+			link->dest_addr = cpu_to_be32(ssi_sxx_phys);
+			link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP |
+				upper_32_bits(ssi_sxx_phys));
+		} else {
+			link->source_addr = cpu_to_be32(ssi_sxx_phys);
+			link->source_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP |
+				upper_32_bits(ssi_sxx_phys));
+
+			link->dest_addr = cpu_to_be32(temp_addr);
+			link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+				upper_32_bits(temp_addr));
+		}
+
+		temp_addr += period_size;
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_dma_pointer: determine the current position of the DMA transfer
+ *
+ * This function is called by ALSA when ALSA wants to know where in the
+ * stream buffer the hardware currently is.
+ *
+ * For playback, the SAR register contains the physical address of the most
+ * recent DMA transfer.  For capture, the value is in the DAR register.
+ *
+ * The base address of the buffer is stored in the source_addr field of the
+ * first link descriptor.
+ */
+static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct device *dev = rtd->platform->dev;
+	struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+	dma_addr_t position;
+	snd_pcm_uframes_t frames;
+
+	/* Obtain the current DMA pointer, but don't read the ESAD bits if we
+	 * only have 32-bit DMA addresses.  This function is typically called
+	 * in interrupt context, so we need to optimize it.
+	 */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		position = in_be32(&dma_channel->sar);
+#ifdef CONFIG_PHYS_64BIT
+		position |= (u64)(in_be32(&dma_channel->satr) &
+				  CCSR_DMA_ATR_ESAD_MASK) << 32;
+#endif
+	} else {
+		position = in_be32(&dma_channel->dar);
+#ifdef CONFIG_PHYS_64BIT
+		position |= (u64)(in_be32(&dma_channel->datr) &
+				  CCSR_DMA_ATR_ESAD_MASK) << 32;
+#endif
+	}
+
+	/*
+	 * When capture is started, the SSI immediately starts to fill its FIFO.
+	 * This means that the DMA controller is not started until the FIFO is
+	 * full.  However, ALSA calls this function before that happens, when
+	 * MR.DAR is still zero.  In this case, just return zero to indicate
+	 * that nothing has been received yet.
+	 */
+	if (!position)
+		return 0;
+
+	if ((position < dma_private->dma_buf_phys) ||
+	    (position > dma_private->dma_buf_end)) {
+		dev_err(dev, "dma pointer is out of range, halting stream\n");
+		return SNDRV_PCM_POS_XRUN;
+	}
+
+	frames = bytes_to_frames(runtime, position - dma_private->dma_buf_phys);
+
+	/*
+	 * If the current address is just past the end of the buffer, wrap it
+	 * around.
+	 */
+	if (frames == runtime->buffer_size)
+		frames = 0;
+
+	return frames;
+}
+
+/**
+ * fsl_dma_hw_free: release resources allocated in fsl_dma_hw_params()
+ *
+ * Release the resources allocated in fsl_dma_hw_params() and de-program the
+ * registers.
+ *
+ * This function can be called multiple times.
+ */
+static int fsl_dma_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+
+	if (dma_private) {
+		struct ccsr_dma_channel __iomem *dma_channel;
+
+		dma_channel = dma_private->dma_channel;
+
+		/* Stop the DMA */
+		out_be32(&dma_channel->mr, CCSR_DMA_MR_CA);
+		out_be32(&dma_channel->mr, 0);
+
+		/* Reset all the other registers */
+		out_be32(&dma_channel->sr, -1);
+		out_be32(&dma_channel->clndar, 0);
+		out_be32(&dma_channel->eclndar, 0);
+		out_be32(&dma_channel->satr, 0);
+		out_be32(&dma_channel->sar, 0);
+		out_be32(&dma_channel->datr, 0);
+		out_be32(&dma_channel->dar, 0);
+		out_be32(&dma_channel->bcr, 0);
+		out_be32(&dma_channel->nlndar, 0);
+		out_be32(&dma_channel->enlndar, 0);
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_dma_close: close the stream.
+ */
+static int fsl_dma_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct device *dev = rtd->platform->dev;
+	struct dma_object *dma =
+		container_of(rtd->platform->driver, struct dma_object, dai);
+
+	if (dma_private) {
+		if (dma_private->irq)
+			free_irq(dma_private->irq, dma_private);
+
+		if (dma_private->ld_buf_phys) {
+			dma_unmap_single(dev, dma_private->ld_buf_phys,
+					 sizeof(dma_private->link),
+					 DMA_TO_DEVICE);
+		}
+
+		/* Deallocate the fsl_dma_private structure */
+		dma_free_coherent(dev, sizeof(struct fsl_dma_private),
+				  dma_private, dma_private->ld_buf_phys);
+		substream->runtime->private_data = NULL;
+	}
+
+	dma->assigned = 0;
+
+	return 0;
+}
+
+/*
+ * Remove this PCM driver.
+ */
+static void fsl_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) {
+		substream = pcm->streams[i].substream;
+		if (substream) {
+			snd_dma_free_pages(&substream->dma_buffer);
+			substream->dma_buffer.area = NULL;
+			substream->dma_buffer.addr = 0;
+		}
+	}
+}
+
+/**
+ * find_ssi_node -- returns the SSI node that points to his DMA channel node
+ *
+ * Although this DMA driver attempts to operate independently of the other
+ * devices, it still needs to determine some information about the SSI device
+ * that it's working with.  Unfortunately, the device tree does not contain
+ * a pointer from the DMA channel node to the SSI node -- the pointer goes the
+ * other way.  So we need to scan the device tree for SSI nodes until we find
+ * the one that points to the given DMA channel node.  It's ugly, but at least
+ * it's contained in this one function.
+ */
+static struct device_node *find_ssi_node(struct device_node *dma_channel_np)
+{
+	struct device_node *ssi_np, *np;
+
+	for_each_compatible_node(ssi_np, NULL, "fsl,mpc8610-ssi") {
+		/* Check each DMA phandle to see if it points to us.  We
+		 * assume that device_node pointers are a valid comparison.
+		 */
+		np = of_parse_phandle(ssi_np, "fsl,playback-dma", 0);
+		if (np == dma_channel_np)
+			return ssi_np;
+
+		np = of_parse_phandle(ssi_np, "fsl,capture-dma", 0);
+		if (np == dma_channel_np)
+			return ssi_np;
+	}
+
+	return NULL;
+}
+
+static struct snd_pcm_ops fsl_dma_ops = {
+	.open   	= fsl_dma_open,
+	.close  	= fsl_dma_close,
+	.ioctl  	= snd_pcm_lib_ioctl,
+	.hw_params      = fsl_dma_hw_params,
+	.hw_free	= fsl_dma_hw_free,
+	.pointer	= fsl_dma_pointer,
+};
+
+static int __devinit fsl_soc_dma_probe(struct platform_device *pdev,
+				       const struct of_device_id *match)
+ {
+	struct dma_object *dma;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *ssi_np;
+	struct resource res;
+	const uint32_t *iprop;
+	int ret;
+
+	/* Find the SSI node that points to us. */
+	ssi_np = find_ssi_node(np);
+	if (!ssi_np) {
+		dev_err(&pdev->dev, "cannot find parent SSI node\n");
+		return -ENODEV;
+	}
+
+	ret = of_address_to_resource(ssi_np, 0, &res);
+	if (ret) {
+		dev_err(&pdev->dev, "could not determine resources for %s\n",
+			ssi_np->full_name);
+		of_node_put(ssi_np);
+		return ret;
+	}
+
+	dma = kzalloc(sizeof(*dma) + strlen(np->full_name), GFP_KERNEL);
+	if (!dma) {
+		dev_err(&pdev->dev, "could not allocate dma object\n");
+		of_node_put(ssi_np);
+		return -ENOMEM;
+	}
+
+	strcpy(dma->path, np->full_name);
+	dma->dai.ops = &fsl_dma_ops;
+	dma->dai.pcm_new = fsl_dma_new;
+	dma->dai.pcm_free = fsl_dma_free_dma_buffers;
+
+	/* Store the SSI-specific information that we need */
+	dma->ssi_stx_phys = res.start + offsetof(struct ccsr_ssi, stx0);
+	dma->ssi_srx_phys = res.start + offsetof(struct ccsr_ssi, srx0);
+
+	iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL);
+	if (iprop)
+		dma->ssi_fifo_depth = *iprop;
+	else
+                /* Older 8610 DTs didn't have the fifo-depth property */
+		dma->ssi_fifo_depth = 8;
+
+	of_node_put(ssi_np);
+
+	ret = snd_soc_register_platform(&pdev->dev, &dma->dai);
+	if (ret) {
+		dev_err(&pdev->dev, "could not register platform\n");
+		kfree(dma);
+		return ret;
+	}
+
+	dma->channel = of_iomap(np, 0);
+	dma->irq = irq_of_parse_and_map(np, 0);
+
+	dev_set_drvdata(&pdev->dev, dma);
+
+	return 0;
+}
+
+static int __devexit fsl_soc_dma_remove(struct platform_device *pdev)
+{
+	struct dma_object *dma = dev_get_drvdata(&pdev->dev);
+
+	snd_soc_unregister_platform(&pdev->dev);
+	iounmap(dma->channel);
+	irq_dispose_mapping(dma->irq);
+	kfree(dma);
+
+	return 0;
+}
+
+static const struct of_device_id fsl_soc_dma_ids[] = {
+	{ .compatible = "fsl,ssi-dma-channel", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, fsl_soc_dma_ids);
+
+static struct of_platform_driver fsl_soc_dma_driver = {
+	.driver = {
+		.name = "fsl-pcm-audio",
+		.owner = THIS_MODULE,
+		.of_match_table = fsl_soc_dma_ids,
+	},
+	.probe = fsl_soc_dma_probe,
+	.remove = __devexit_p(fsl_soc_dma_remove),
+};
+
+static int __init fsl_soc_dma_init(void)
+{
+	pr_info("Freescale Elo DMA ASoC PCM Driver\n");
+
+	return of_register_platform_driver(&fsl_soc_dma_driver);
+}
+
+static void __exit fsl_soc_dma_exit(void)
+{
+	of_unregister_platform_driver(&fsl_soc_dma_driver);
+}
+
+module_init(fsl_soc_dma_init);
+module_exit(fsl_soc_dma_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/fsl/fsl_dma.h b/linux/sound/soc/fsl/fsl_dma.h
new file mode 100644
index 0000000..78fee97
--- /dev/null
+++ b/linux/sound/soc/fsl/fsl_dma.h
@@ -0,0 +1,129 @@
+/*
+ * mpc8610-pcm.h - ALSA PCM interface for the Freescale MPC8610 SoC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _MPC8610_PCM_H
+#define _MPC8610_PCM_H
+
+struct ccsr_dma {
+	u8 res0[0x100];
+	struct ccsr_dma_channel {
+		__be32 mr;      /* Mode register */
+		__be32 sr;      /* Status register */
+		__be32 eclndar; /* Current link descriptor extended addr reg */
+		__be32 clndar;  /* Current link descriptor address register */
+		__be32 satr;    /* Source attributes register */
+		__be32 sar;     /* Source address register */
+		__be32 datr;    /* Destination attributes register */
+		__be32 dar;     /* Destination address register */
+		__be32 bcr;     /* Byte count register */
+		__be32 enlndar; /* Next link descriptor extended address reg */
+		__be32 nlndar;  /* Next link descriptor address register */
+		u8 res1[4];
+		__be32 eclsdar; /* Current list descriptor extended addr reg */
+		__be32 clsdar;  /* Current list descriptor address register */
+		__be32 enlsdar; /* Next list descriptor extended address reg */
+		__be32 nlsdar;  /* Next list descriptor address register */
+		__be32 ssr;     /* Source stride register */
+		__be32 dsr;     /* Destination stride register */
+		u8 res2[0x38];
+	} channel[4];
+	__be32 dgsr;
+};
+
+#define CCSR_DMA_MR_BWC_DISABLED	0x0F000000
+#define CCSR_DMA_MR_BWC_SHIFT   	24
+#define CCSR_DMA_MR_BWC_MASK    	0x0F000000
+#define CCSR_DMA_MR_BWC(x) \
+	((ilog2(x) << CCSR_DMA_MR_BWC_SHIFT) & CCSR_DMA_MR_BWC_MASK)
+#define CCSR_DMA_MR_EMP_EN      	0x00200000
+#define CCSR_DMA_MR_EMS_EN      	0x00040000
+#define CCSR_DMA_MR_DAHTS_MASK  	0x00030000
+#define CCSR_DMA_MR_DAHTS_1     	0x00000000
+#define CCSR_DMA_MR_DAHTS_2     	0x00010000
+#define CCSR_DMA_MR_DAHTS_4     	0x00020000
+#define CCSR_DMA_MR_DAHTS_8     	0x00030000
+#define CCSR_DMA_MR_SAHTS_MASK  	0x0000C000
+#define CCSR_DMA_MR_SAHTS_1     	0x00000000
+#define CCSR_DMA_MR_SAHTS_2     	0x00004000
+#define CCSR_DMA_MR_SAHTS_4     	0x00008000
+#define CCSR_DMA_MR_SAHTS_8     	0x0000C000
+#define CCSR_DMA_MR_DAHE		0x00002000
+#define CCSR_DMA_MR_SAHE		0x00001000
+#define CCSR_DMA_MR_SRW 		0x00000400
+#define CCSR_DMA_MR_EOSIE       	0x00000200
+#define CCSR_DMA_MR_EOLNIE      	0x00000100
+#define CCSR_DMA_MR_EOLSIE      	0x00000080
+#define CCSR_DMA_MR_EIE 		0x00000040
+#define CCSR_DMA_MR_XFE 		0x00000020
+#define CCSR_DMA_MR_CDSM_SWSM   	0x00000010
+#define CCSR_DMA_MR_CA  		0x00000008
+#define CCSR_DMA_MR_CTM 		0x00000004
+#define CCSR_DMA_MR_CC  		0x00000002
+#define CCSR_DMA_MR_CS  		0x00000001
+
+#define CCSR_DMA_SR_TE  		0x00000080
+#define CCSR_DMA_SR_CH  		0x00000020
+#define CCSR_DMA_SR_PE  		0x00000010
+#define CCSR_DMA_SR_EOLNI       	0x00000008
+#define CCSR_DMA_SR_CB  		0x00000004
+#define CCSR_DMA_SR_EOSI		0x00000002
+#define CCSR_DMA_SR_EOLSI       	0x00000001
+
+/* ECLNDAR takes bits 32-36 of the CLNDAR register */
+static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x)
+{
+	return (x >> 32) & 0xf;
+}
+
+#define CCSR_DMA_CLNDAR_ADDR(x) ((x) & 0xFFFFFFFE)
+#define CCSR_DMA_CLNDAR_EOSIE   	0x00000008
+
+/* SATR and DATR, combined */
+#define CCSR_DMA_ATR_PBATMU     	0x20000000
+#define CCSR_DMA_ATR_TFLOWLVL_0 	0x00000000
+#define CCSR_DMA_ATR_TFLOWLVL_1 	0x06000000
+#define CCSR_DMA_ATR_TFLOWLVL_2 	0x08000000
+#define CCSR_DMA_ATR_TFLOWLVL_3 	0x0C000000
+#define CCSR_DMA_ATR_PCIORDER   	0x02000000
+#define CCSR_DMA_ATR_SME		0x01000000
+#define CCSR_DMA_ATR_NOSNOOP    	0x00040000
+#define CCSR_DMA_ATR_SNOOP      	0x00050000
+#define CCSR_DMA_ATR_ESAD_MASK  	0x0000000F
+
+/**
+ *  List Descriptor for extended chaining mode DMA operations.
+ *
+ *  The CLSDAR register points to the first (in a linked-list) List
+ *  Descriptor.  Each object must be aligned on a 32-byte boundary. Each
+ *  list descriptor points to a linked-list of link Descriptors.
+ */
+struct fsl_dma_list_descriptor {
+	__be64 next;    	/* Address of next list descriptor */
+	__be64 first_link;      /* Address of first link descriptor */
+	__be32 source;  	/* Source stride */
+	__be32 dest;    	/* Destination stride */
+	u8 res[8];      	/* Reserved */
+} __attribute__ ((aligned(32), packed));
+
+/**
+ *  Link Descriptor for basic and extended chaining mode DMA operations.
+ *
+ *  A Link Descriptor points to a single DMA buffer.  Each link descriptor
+ *  must be aligned on a 32-byte boundary.
+ */
+struct fsl_dma_link_descriptor {
+	__be32 source_attr;     /* Programmed into SATR register */
+	__be32 source_addr;     /* Programmed into SAR register */
+	__be32 dest_attr;       /* Programmed into DATR register */
+	__be32 dest_addr;       /* Programmed into DAR register */
+	__be64 next;    /* Address of next link descriptor */
+	__be32 count;   /* Byte count */
+	u8 res[4];      /* Reserved */
+} __attribute__ ((aligned(32), packed));
+
+#endif
diff --git a/linux/sound/soc/fsl/fsl_ssi.c b/linux/sound/soc/fsl/fsl_ssi.c
new file mode 100644
index 0000000..4cc167a
--- /dev/null
+++ b/linux/sound/soc/fsl/fsl_ssi.c
@@ -0,0 +1,804 @@
+/*
+ * Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/of_platform.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "fsl_ssi.h"
+
+/**
+ * FSLSSI_I2S_RATES: sample rates supported by the I2S
+ *
+ * This driver currently only supports the SSI running in I2S slave mode,
+ * which means the codec determines the sample rate.  Therefore, we tell
+ * ALSA that we support all rates and let the codec driver decide what rates
+ * are really supported.
+ */
+#define FSLSSI_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+			  SNDRV_PCM_RATE_CONTINUOUS)
+
+/**
+ * FSLSSI_I2S_FORMATS: audio formats supported by the SSI
+ *
+ * This driver currently only supports the SSI running in I2S slave mode.
+ *
+ * The SSI has a limitation in that the samples must be in the same byte
+ * order as the host CPU.  This is because when multiple bytes are written
+ * to the STX register, the bytes and bits must be written in the same
+ * order.  The STX is a shift register, so all the bits need to be aligned
+ * (bit-endianness must match byte-endianness).  Processors typically write
+ * the bits within a byte in the same order that the bytes of a word are
+ * written in.  So if the host CPU is big-endian, then only big-endian
+ * samples will be written to STX properly.
+ */
+#ifdef __BIG_ENDIAN
+#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
+	 SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_S20_3BE | \
+	 SNDRV_PCM_FMTBIT_S24_3BE | SNDRV_PCM_FMTBIT_S24_BE)
+#else
+#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
+	 SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+	 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE)
+#endif
+
+/* SIER bitflag of interrupts to enable */
+#define SIER_FLAGS (CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE | \
+		    CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN | \
+		    CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN | \
+		    CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE | \
+		    CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN)
+
+/**
+ * fsl_ssi_private: per-SSI private data
+ *
+ * @ssi: pointer to the SSI's registers
+ * @ssi_phys: physical address of the SSI registers
+ * @irq: IRQ of this SSI
+ * @first_stream: pointer to the stream that was opened first
+ * @second_stream: pointer to second stream
+ * @playback: the number of playback streams opened
+ * @capture: the number of capture streams opened
+ * @asynchronous: 0=synchronous mode, 1=asynchronous mode
+ * @cpu_dai: the CPU DAI for this device
+ * @dev_attr: the sysfs device attribute structure
+ * @stats: SSI statistics
+ * @name: name for this device
+ */
+struct fsl_ssi_private {
+	struct ccsr_ssi __iomem *ssi;
+	dma_addr_t ssi_phys;
+	unsigned int irq;
+	struct snd_pcm_substream *first_stream;
+	struct snd_pcm_substream *second_stream;
+	unsigned int playback;
+	unsigned int capture;
+	int asynchronous;
+	unsigned int fifo_depth;
+	struct snd_soc_dai_driver cpu_dai_drv;
+	struct device_attribute dev_attr;
+	struct platform_device *pdev;
+
+	struct {
+		unsigned int rfrc;
+		unsigned int tfrc;
+		unsigned int cmdau;
+		unsigned int cmddu;
+		unsigned int rxt;
+		unsigned int rdr1;
+		unsigned int rdr0;
+		unsigned int tde1;
+		unsigned int tde0;
+		unsigned int roe1;
+		unsigned int roe0;
+		unsigned int tue1;
+		unsigned int tue0;
+		unsigned int tfs;
+		unsigned int rfs;
+		unsigned int tls;
+		unsigned int rls;
+		unsigned int rff1;
+		unsigned int rff0;
+		unsigned int tfe1;
+		unsigned int tfe0;
+	} stats;
+
+	char name[1];
+};
+
+/**
+ * fsl_ssi_isr: SSI interrupt handler
+ *
+ * Although it's possible to use the interrupt handler to send and receive
+ * data to/from the SSI, we use the DMA instead.  Programming is more
+ * complicated, but the performance is much better.
+ *
+ * This interrupt handler is used only to gather statistics.
+ *
+ * @irq: IRQ of the SSI device
+ * @dev_id: pointer to the ssi_private structure for this SSI device
+ */
+static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
+{
+	struct fsl_ssi_private *ssi_private = dev_id;
+	struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+	irqreturn_t ret = IRQ_NONE;
+	__be32 sisr;
+	__be32 sisr2 = 0;
+
+	/* We got an interrupt, so read the status register to see what we
+	   were interrupted for.  We mask it with the Interrupt Enable register
+	   so that we only check for events that we're interested in.
+	 */
+	sisr = in_be32(&ssi->sisr) & SIER_FLAGS;
+
+	if (sisr & CCSR_SSI_SISR_RFRC) {
+		ssi_private->stats.rfrc++;
+		sisr2 |= CCSR_SSI_SISR_RFRC;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TFRC) {
+		ssi_private->stats.tfrc++;
+		sisr2 |= CCSR_SSI_SISR_TFRC;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_CMDAU) {
+		ssi_private->stats.cmdau++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_CMDDU) {
+		ssi_private->stats.cmddu++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RXT) {
+		ssi_private->stats.rxt++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RDR1) {
+		ssi_private->stats.rdr1++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RDR0) {
+		ssi_private->stats.rdr0++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TDE1) {
+		ssi_private->stats.tde1++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TDE0) {
+		ssi_private->stats.tde0++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_ROE1) {
+		ssi_private->stats.roe1++;
+		sisr2 |= CCSR_SSI_SISR_ROE1;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_ROE0) {
+		ssi_private->stats.roe0++;
+		sisr2 |= CCSR_SSI_SISR_ROE0;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TUE1) {
+		ssi_private->stats.tue1++;
+		sisr2 |= CCSR_SSI_SISR_TUE1;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TUE0) {
+		ssi_private->stats.tue0++;
+		sisr2 |= CCSR_SSI_SISR_TUE0;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TFS) {
+		ssi_private->stats.tfs++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RFS) {
+		ssi_private->stats.rfs++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TLS) {
+		ssi_private->stats.tls++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RLS) {
+		ssi_private->stats.rls++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RFF1) {
+		ssi_private->stats.rff1++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_RFF0) {
+		ssi_private->stats.rff0++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TFE1) {
+		ssi_private->stats.tfe1++;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sisr & CCSR_SSI_SISR_TFE0) {
+		ssi_private->stats.tfe0++;
+		ret = IRQ_HANDLED;
+	}
+
+	/* Clear the bits that we set */
+	if (sisr2)
+		out_be32(&ssi->sisr, sisr2);
+
+	return ret;
+}
+
+/**
+ * fsl_ssi_startup: create a new substream
+ *
+ * This is the first function called when a stream is opened.
+ *
+ * If this is the first stream open, then grab the IRQ and program most of
+ * the SSI registers.
+ */
+static int fsl_ssi_startup(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+	/*
+	 * If this is the first stream opened, then request the IRQ
+	 * and initialize the SSI registers.
+	 */
+	if (!ssi_private->playback && !ssi_private->capture) {
+		struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+		int ret;
+
+		/* The 'name' should not have any slashes in it. */
+		ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0,
+				  ssi_private->name, ssi_private);
+		if (ret < 0) {
+			dev_err(substream->pcm->card->dev,
+				"could not claim irq %u\n", ssi_private->irq);
+			return ret;
+		}
+
+		/*
+		 * Section 16.5 of the MPC8610 reference manual says that the
+		 * SSI needs to be disabled before updating the registers we set
+		 * here.
+		 */
+		clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+
+		/*
+		 * Program the SSI into I2S Slave Non-Network Synchronous mode.
+		 * Also enable the transmit and receive FIFO.
+		 *
+		 * FIXME: Little-endian samples require a different shift dir
+		 */
+		clrsetbits_be32(&ssi->scr,
+			CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN,
+			CCSR_SSI_SCR_TFR_CLK_DIS | CCSR_SSI_SCR_I2S_MODE_SLAVE
+			| (ssi_private->asynchronous ? 0 : CCSR_SSI_SCR_SYN));
+
+		out_be32(&ssi->stcr,
+			 CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 |
+			 CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS |
+			 CCSR_SSI_STCR_TSCKP);
+
+		out_be32(&ssi->srcr,
+			 CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 |
+			 CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS |
+			 CCSR_SSI_SRCR_RSCKP);
+
+		/*
+		 * The DC and PM bits are only used if the SSI is the clock
+		 * master.
+		 */
+
+		/* 4. Enable the interrupts and DMA requests */
+		out_be32(&ssi->sier, SIER_FLAGS);
+
+		/*
+		 * Set the watermark for transmit FIFI 0 and receive FIFO 0. We
+		 * don't use FIFO 1.  We program the transmit water to signal a
+		 * DMA transfer if there are only two (or fewer) elements left
+		 * in the FIFO.  Two elements equals one frame (left channel,
+		 * right channel).  This value, however, depends on the depth of
+		 * the transmit buffer.
+		 *
+		 * We program the receive FIFO to notify us if at least two
+		 * elements (one frame) have been written to the FIFO.  We could
+		 * make this value larger (and maybe we should), but this way
+		 * data will be written to memory as soon as it's available.
+		 */
+		out_be32(&ssi->sfcsr,
+			CCSR_SSI_SFCSR_TFWM0(ssi_private->fifo_depth - 2) |
+			CCSR_SSI_SFCSR_RFWM0(ssi_private->fifo_depth - 2));
+
+		/*
+		 * We keep the SSI disabled because if we enable it, then the
+		 * DMA controller will start.  It's not supposed to start until
+		 * the SCR.TE (or SCR.RE) bit is set, but it does anyway.  The
+		 * DMA controller will transfer one "BWC" of data (i.e. the
+		 * amount of data that the MR.BWC bits are set to).  The reason
+		 * this is bad is because at this point, the PCM driver has not
+		 * finished initializing the DMA controller.
+		 */
+	}
+
+	if (!ssi_private->first_stream)
+		ssi_private->first_stream = substream;
+	else {
+		/* This is the second stream open, so we need to impose sample
+		 * rate and maybe sample size constraints.  Note that this can
+		 * cause a race condition if the second stream is opened before
+		 * the first stream is fully initialized.
+		 *
+		 * We provide some protection by checking to make sure the first
+		 * stream is initialized, but it's not perfect.  ALSA sometimes
+		 * re-initializes the driver with a different sample rate or
+		 * size.  If the second stream is opened before the first stream
+		 * has received its final parameters, then the second stream may
+		 * be constrained to the wrong sample rate or size.
+		 *
+		 * FIXME: This code does not handle opening and closing streams
+		 * repeatedly.  If you open two streams and then close the first
+		 * one, you may not be able to open another stream until you
+		 * close the second one as well.
+		 */
+		struct snd_pcm_runtime *first_runtime =
+			ssi_private->first_stream->runtime;
+
+		if (!first_runtime->sample_bits) {
+			dev_err(substream->pcm->card->dev,
+				"set sample size in %s stream first\n",
+				substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+				? "capture" : "playback");
+			return -EAGAIN;
+		}
+
+		/* If we're in synchronous mode, then we need to constrain
+		 * the sample size as well.  We don't support independent sample
+		 * rates in asynchronous mode.
+		 */
+		if (!ssi_private->asynchronous)
+			snd_pcm_hw_constraint_minmax(substream->runtime,
+				SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+				first_runtime->sample_bits,
+				first_runtime->sample_bits);
+
+		ssi_private->second_stream = substream;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		ssi_private->playback++;
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		ssi_private->capture++;
+
+	return 0;
+}
+
+/**
+ * fsl_ssi_hw_params - program the sample size
+ *
+ * Most of the SSI registers have been programmed in the startup function,
+ * but the word length must be programmed here.  Unfortunately, programming
+ * the SxCCR.WL bits requires the SSI to be temporarily disabled.  This can
+ * cause a problem with supporting simultaneous playback and capture.  If
+ * the SSI is already playing a stream, then that stream may be temporarily
+ * stopped when you start capture.
+ *
+ * Note: The SxCCR.DC and SxCCR.PM bits are only used if the SSI is the
+ * clock master.
+ */
+static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *cpu_dai)
+{
+	struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai);
+
+	if (substream == ssi_private->first_stream) {
+		struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+		unsigned int sample_size =
+			snd_pcm_format_width(params_format(hw_params));
+		u32 wl = CCSR_SSI_SxCCR_WL(sample_size);
+
+		/* The SSI should always be disabled at this points (SSIEN=0) */
+
+		/* In synchronous mode, the SSI uses STCCR for capture */
+		if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ||
+		    !ssi_private->asynchronous)
+			clrsetbits_be32(&ssi->stccr,
+					CCSR_SSI_SxCCR_WL_MASK, wl);
+		else
+			clrsetbits_be32(&ssi->srccr,
+					CCSR_SSI_SxCCR_WL_MASK, wl);
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_ssi_trigger: start and stop the DMA transfer.
+ *
+ * This function is called by ALSA to start, stop, pause, and resume the DMA
+ * transfer of data.
+ *
+ * The DMA channel is in external master start and pause mode, which
+ * means the SSI completely controls the flow of data.
+ */
+static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			setbits32(&ssi->scr,
+				CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE);
+		else
+			setbits32(&ssi->scr,
+				CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			clrbits32(&ssi->scr, CCSR_SSI_SCR_TE);
+		else
+			clrbits32(&ssi->scr, CCSR_SSI_SCR_RE);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_ssi_shutdown: shutdown the SSI
+ *
+ * Shutdown the SSI if there are no other substreams open.
+ */
+static void fsl_ssi_shutdown(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		ssi_private->playback--;
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		ssi_private->capture--;
+
+	if (ssi_private->first_stream == substream)
+		ssi_private->first_stream = ssi_private->second_stream;
+
+	ssi_private->second_stream = NULL;
+
+	/*
+	 * If this is the last active substream, disable the SSI and release
+	 * the IRQ.
+	 */
+	if (!ssi_private->playback && !ssi_private->capture) {
+		struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+
+		clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN);
+
+		free_irq(ssi_private->irq, ssi_private);
+	}
+}
+
+static struct snd_soc_dai_ops fsl_ssi_dai_ops = {
+	.startup	= fsl_ssi_startup,
+	.hw_params	= fsl_ssi_hw_params,
+	.shutdown	= fsl_ssi_shutdown,
+	.trigger	= fsl_ssi_trigger,
+};
+
+/* Template for the CPU dai driver structure */
+static struct snd_soc_dai_driver fsl_ssi_dai_template = {
+	.playback = {
+		/* The SSI does not support monaural audio. */
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = FSLSSI_I2S_RATES,
+		.formats = FSLSSI_I2S_FORMATS,
+	},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = FSLSSI_I2S_RATES,
+		.formats = FSLSSI_I2S_FORMATS,
+	},
+	.ops = &fsl_ssi_dai_ops,
+};
+
+/* Show the statistics of a flag only if its interrupt is enabled.  The
+ * compiler will optimze this code to a no-op if the interrupt is not
+ * enabled.
+ */
+#define SIER_SHOW(flag, name) \
+	do { \
+		if (SIER_FLAGS & CCSR_SSI_SIER_##flag) \
+			length += sprintf(buf + length, #name "=%u\n", \
+				ssi_private->stats.name); \
+	} while (0)
+
+
+/**
+ * fsl_sysfs_ssi_show: display SSI statistics
+ *
+ * Display the statistics for the current SSI device.  To avoid confusion,
+ * we only show those counts that are enabled.
+ */
+static ssize_t fsl_sysfs_ssi_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct fsl_ssi_private *ssi_private =
+		container_of(attr, struct fsl_ssi_private, dev_attr);
+	ssize_t length = 0;
+
+	SIER_SHOW(RFRC_EN, rfrc);
+	SIER_SHOW(TFRC_EN, tfrc);
+	SIER_SHOW(CMDAU_EN, cmdau);
+	SIER_SHOW(CMDDU_EN, cmddu);
+	SIER_SHOW(RXT_EN, rxt);
+	SIER_SHOW(RDR1_EN, rdr1);
+	SIER_SHOW(RDR0_EN, rdr0);
+	SIER_SHOW(TDE1_EN, tde1);
+	SIER_SHOW(TDE0_EN, tde0);
+	SIER_SHOW(ROE1_EN, roe1);
+	SIER_SHOW(ROE0_EN, roe0);
+	SIER_SHOW(TUE1_EN, tue1);
+	SIER_SHOW(TUE0_EN, tue0);
+	SIER_SHOW(TFS_EN, tfs);
+	SIER_SHOW(RFS_EN, rfs);
+	SIER_SHOW(TLS_EN, tls);
+	SIER_SHOW(RLS_EN, rls);
+	SIER_SHOW(RFF1_EN, rff1);
+	SIER_SHOW(RFF0_EN, rff0);
+	SIER_SHOW(TFE1_EN, tfe1);
+	SIER_SHOW(TFE0_EN, tfe0);
+
+	return length;
+}
+
+/**
+ * Make every character in a string lower-case
+ */
+static void make_lowercase(char *s)
+{
+	char *p = s;
+	char c;
+
+	while ((c = *p)) {
+		if ((c >= 'A') && (c <= 'Z'))
+			*p = c + ('a' - 'A');
+		p++;
+	}
+}
+
+static int __devinit fsl_ssi_probe(struct platform_device *pdev,
+				   const struct of_device_id *match)
+{
+	struct fsl_ssi_private *ssi_private;
+	int ret = 0;
+	struct device_attribute *dev_attr = NULL;
+	struct device_node *np = pdev->dev.of_node;
+	const char *p, *sprop;
+	const uint32_t *iprop;
+	struct resource res;
+	char name[64];
+
+	/* SSIs that are not connected on the board should have a
+	 *      status = "disabled"
+	 * property in their device tree nodes.
+	 */
+	if (!of_device_is_available(np))
+		return -ENODEV;
+
+	/* Check for a codec-handle property. */
+	if (!of_get_property(np, "codec-handle", NULL)) {
+		dev_err(&pdev->dev, "missing codec-handle property\n");
+		return -ENODEV;
+	}
+
+	/* We only support the SSI in "I2S Slave" mode */
+	sprop = of_get_property(np, "fsl,mode", NULL);
+	if (!sprop || strcmp(sprop, "i2s-slave")) {
+		dev_notice(&pdev->dev, "mode %s is unsupported\n", sprop);
+		return -ENODEV;
+	}
+
+	/* The DAI name is the last part of the full name of the node. */
+	p = strrchr(np->full_name, '/') + 1;
+	ssi_private = kzalloc(sizeof(struct fsl_ssi_private) + strlen(p),
+			      GFP_KERNEL);
+	if (!ssi_private) {
+		dev_err(&pdev->dev, "could not allocate DAI object\n");
+		return -ENOMEM;
+	}
+
+	strcpy(ssi_private->name, p);
+
+	/* Initialize this copy of the CPU DAI driver structure */
+	memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_dai_template,
+	       sizeof(fsl_ssi_dai_template));
+	ssi_private->cpu_dai_drv.name = ssi_private->name;
+
+	/* Get the addresses and IRQ */
+	ret = of_address_to_resource(np, 0, &res);
+	if (ret) {
+		dev_err(&pdev->dev, "could not determine device resources\n");
+		kfree(ssi_private);
+		return ret;
+	}
+	ssi_private->ssi = ioremap(res.start, 1 + res.end - res.start);
+	ssi_private->ssi_phys = res.start;
+	ssi_private->irq = irq_of_parse_and_map(np, 0);
+
+	/* Are the RX and the TX clocks locked? */
+	if (of_find_property(np, "fsl,ssi-asynchronous", NULL))
+		ssi_private->asynchronous = 1;
+	else
+		ssi_private->cpu_dai_drv.symmetric_rates = 1;
+
+	/* Determine the FIFO depth. */
+	iprop = of_get_property(np, "fsl,fifo-depth", NULL);
+	if (iprop)
+		ssi_private->fifo_depth = *iprop;
+	else
+                /* Older 8610 DTs didn't have the fifo-depth property */
+		ssi_private->fifo_depth = 8;
+
+	/* Initialize the the device_attribute structure */
+	dev_attr = &ssi_private->dev_attr;
+	dev_attr->attr.name = "statistics";
+	dev_attr->attr.mode = S_IRUGO;
+	dev_attr->show = fsl_sysfs_ssi_show;
+
+	ret = device_create_file(&pdev->dev, dev_attr);
+	if (ret) {
+		dev_err(&pdev->dev, "could not create sysfs %s file\n",
+			ssi_private->dev_attr.attr.name);
+		goto error;
+	}
+
+	/* Register with ASoC */
+	dev_set_drvdata(&pdev->dev, ssi_private);
+
+	ret = snd_soc_register_dai(&pdev->dev, &ssi_private->cpu_dai_drv);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register DAI: %d\n", ret);
+		goto error;
+	}
+
+	/* Trigger the machine driver's probe function.  The platform driver
+	 * name of the machine driver is taken from the /model property of the
+	 * device tree.  We also pass the address of the CPU DAI driver
+	 * structure.
+	 */
+	sprop = of_get_property(of_find_node_by_path("/"), "model", NULL);
+	/* Sometimes the model name has a "fsl," prefix, so we strip that. */
+	p = strrchr(sprop, ',');
+	if (p)
+		sprop = p + 1;
+	snprintf(name, sizeof(name), "snd-soc-%s", sprop);
+	make_lowercase(name);
+
+	ssi_private->pdev =
+		platform_device_register_data(&pdev->dev, name, 0, NULL, 0);
+	if (IS_ERR(ssi_private->pdev)) {
+		ret = PTR_ERR(ssi_private->pdev);
+		dev_err(&pdev->dev, "failed to register platform: %d\n", ret);
+		goto error;
+	}
+
+	return 0;
+
+error:
+	snd_soc_unregister_dai(&pdev->dev);
+	dev_set_drvdata(&pdev->dev, NULL);
+	if (dev_attr)
+		device_remove_file(&pdev->dev, dev_attr);
+	irq_dispose_mapping(ssi_private->irq);
+	iounmap(ssi_private->ssi);
+	kfree(ssi_private);
+
+	return ret;
+}
+
+static int fsl_ssi_remove(struct platform_device *pdev)
+{
+	struct fsl_ssi_private *ssi_private = dev_get_drvdata(&pdev->dev);
+
+	platform_device_unregister(ssi_private->pdev);
+	snd_soc_unregister_dai(&pdev->dev);
+	device_remove_file(&pdev->dev, &ssi_private->dev_attr);
+
+	kfree(ssi_private);
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	return 0;
+}
+
+static const struct of_device_id fsl_ssi_ids[] = {
+	{ .compatible = "fsl,mpc8610-ssi", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, fsl_ssi_ids);
+
+static struct of_platform_driver fsl_ssi_driver = {
+	.driver = {
+		.name = "fsl-ssi-dai",
+		.owner = THIS_MODULE,
+		.of_match_table = fsl_ssi_ids,
+	},
+	.probe = fsl_ssi_probe,
+	.remove = fsl_ssi_remove,
+};
+
+static int __init fsl_ssi_init(void)
+{
+	printk(KERN_INFO "Freescale Synchronous Serial Interface (SSI) ASoC Driver\n");
+
+	return of_register_platform_driver(&fsl_ssi_driver);
+}
+
+static void __exit fsl_ssi_exit(void)
+{
+	of_unregister_platform_driver(&fsl_ssi_driver);
+}
+
+module_init(fsl_ssi_init);
+module_exit(fsl_ssi_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/fsl/fsl_ssi.h b/linux/sound/soc/fsl/fsl_ssi.h
new file mode 100644
index 0000000..2173000
--- /dev/null
+++ b/linux/sound/soc/fsl/fsl_ssi.h
@@ -0,0 +1,200 @@
+/*
+ * fsl_ssi.h - ALSA SSI interface for the Freescale MPC8610 SoC
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc.  This file is licensed
+ * under the terms of the GNU General Public License version 2.  This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
+ */
+
+#ifndef _MPC8610_I2S_H
+#define _MPC8610_I2S_H
+
+/* SSI Register Map */
+struct ccsr_ssi {
+	__be32 stx0;	/* 0x.0000 - SSI Transmit Data Register 0 */
+	__be32 stx1;	/* 0x.0004 - SSI Transmit Data Register 1 */
+	__be32 srx0;	/* 0x.0008 - SSI Receive Data Register 0 */
+	__be32 srx1;	/* 0x.000C - SSI Receive Data Register 1 */
+	__be32 scr;	/* 0x.0010 - SSI Control Register */
+	__be32 sisr;	/* 0x.0014 - SSI Interrupt Status Register Mixed */
+	__be32 sier;	/* 0x.0018 - SSI Interrupt Enable Register */
+	__be32 stcr;	/* 0x.001C - SSI Transmit Configuration Register */
+	__be32 srcr;	/* 0x.0020 - SSI Receive Configuration Register */
+	__be32 stccr;	/* 0x.0024 - SSI Transmit Clock Control Register */
+	__be32 srccr;	/* 0x.0028 - SSI Receive Clock Control Register */
+	__be32 sfcsr;	/* 0x.002C - SSI FIFO Control/Status Register */
+	__be32 str;	/* 0x.0030 - SSI Test Register */
+	__be32 sor;	/* 0x.0034 - SSI Option Register */
+	__be32 sacnt;	/* 0x.0038 - SSI AC97 Control Register */
+	__be32 sacadd;	/* 0x.003C - SSI AC97 Command Address Register */
+	__be32 sacdat;	/* 0x.0040 - SSI AC97 Command Data Register */
+	__be32 satag;	/* 0x.0044 - SSI AC97 Tag Register */
+	__be32 stmsk;	/* 0x.0048 - SSI Transmit Time Slot Mask Register */
+	__be32 srmsk;	/* 0x.004C - SSI Receive Time Slot Mask Register */
+	__be32 saccst;	/* 0x.0050 - SSI AC97 Channel Status Register */
+	__be32 saccen;	/* 0x.0054 - SSI AC97 Channel Enable Register */
+	__be32 saccdis; /* 0x.0058 - SSI AC97 Channel Disable Register */
+};
+
+#define CCSR_SSI_SCR_RFR_CLK_DIS	0x00000800
+#define CCSR_SSI_SCR_TFR_CLK_DIS	0x00000400
+#define CCSR_SSI_SCR_TCH_EN		0x00000100
+#define CCSR_SSI_SCR_SYS_CLK_EN		0x00000080
+#define CCSR_SSI_SCR_I2S_MODE_MASK	0x00000060
+#define CCSR_SSI_SCR_I2S_MODE_NORMAL	0x00000000
+#define CCSR_SSI_SCR_I2S_MODE_MASTER	0x00000020
+#define CCSR_SSI_SCR_I2S_MODE_SLAVE	0x00000040
+#define CCSR_SSI_SCR_SYN		0x00000010
+#define CCSR_SSI_SCR_NET		0x00000008
+#define CCSR_SSI_SCR_RE			0x00000004
+#define CCSR_SSI_SCR_TE			0x00000002
+#define CCSR_SSI_SCR_SSIEN		0x00000001
+
+#define CCSR_SSI_SISR_RFRC		0x01000000
+#define CCSR_SSI_SISR_TFRC		0x00800000
+#define CCSR_SSI_SISR_CMDAU		0x00040000
+#define CCSR_SSI_SISR_CMDDU		0x00020000
+#define CCSR_SSI_SISR_RXT		0x00010000
+#define CCSR_SSI_SISR_RDR1		0x00008000
+#define CCSR_SSI_SISR_RDR0		0x00004000
+#define CCSR_SSI_SISR_TDE1		0x00002000
+#define CCSR_SSI_SISR_TDE0		0x00001000
+#define CCSR_SSI_SISR_ROE1		0x00000800
+#define CCSR_SSI_SISR_ROE0		0x00000400
+#define CCSR_SSI_SISR_TUE1		0x00000200
+#define CCSR_SSI_SISR_TUE0		0x00000100
+#define CCSR_SSI_SISR_TFS		0x00000080
+#define CCSR_SSI_SISR_RFS		0x00000040
+#define CCSR_SSI_SISR_TLS		0x00000020
+#define CCSR_SSI_SISR_RLS		0x00000010
+#define CCSR_SSI_SISR_RFF1		0x00000008
+#define CCSR_SSI_SISR_RFF0		0x00000004
+#define CCSR_SSI_SISR_TFE1		0x00000002
+#define CCSR_SSI_SISR_TFE0		0x00000001
+
+#define CCSR_SSI_SIER_RFRC_EN		0x01000000
+#define CCSR_SSI_SIER_TFRC_EN		0x00800000
+#define CCSR_SSI_SIER_RDMAE		0x00400000
+#define CCSR_SSI_SIER_RIE		0x00200000
+#define CCSR_SSI_SIER_TDMAE		0x00100000
+#define CCSR_SSI_SIER_TIE		0x00080000
+#define CCSR_SSI_SIER_CMDAU_EN		0x00040000
+#define CCSR_SSI_SIER_CMDDU_EN		0x00020000
+#define CCSR_SSI_SIER_RXT_EN		0x00010000
+#define CCSR_SSI_SIER_RDR1_EN		0x00008000
+#define CCSR_SSI_SIER_RDR0_EN		0x00004000
+#define CCSR_SSI_SIER_TDE1_EN		0x00002000
+#define CCSR_SSI_SIER_TDE0_EN		0x00001000
+#define CCSR_SSI_SIER_ROE1_EN		0x00000800
+#define CCSR_SSI_SIER_ROE0_EN		0x00000400
+#define CCSR_SSI_SIER_TUE1_EN		0x00000200
+#define CCSR_SSI_SIER_TUE0_EN		0x00000100
+#define CCSR_SSI_SIER_TFS_EN		0x00000080
+#define CCSR_SSI_SIER_RFS_EN		0x00000040
+#define CCSR_SSI_SIER_TLS_EN		0x00000020
+#define CCSR_SSI_SIER_RLS_EN		0x00000010
+#define CCSR_SSI_SIER_RFF1_EN		0x00000008
+#define CCSR_SSI_SIER_RFF0_EN		0x00000004
+#define CCSR_SSI_SIER_TFE1_EN		0x00000002
+#define CCSR_SSI_SIER_TFE0_EN		0x00000001
+
+#define CCSR_SSI_STCR_TXBIT0		0x00000200
+#define CCSR_SSI_STCR_TFEN1		0x00000100
+#define CCSR_SSI_STCR_TFEN0		0x00000080
+#define CCSR_SSI_STCR_TFDIR		0x00000040
+#define CCSR_SSI_STCR_TXDIR		0x00000020
+#define CCSR_SSI_STCR_TSHFD		0x00000010
+#define CCSR_SSI_STCR_TSCKP		0x00000008
+#define CCSR_SSI_STCR_TFSI		0x00000004
+#define CCSR_SSI_STCR_TFSL		0x00000002
+#define CCSR_SSI_STCR_TEFS		0x00000001
+
+#define CCSR_SSI_SRCR_RXEXT		0x00000400
+#define CCSR_SSI_SRCR_RXBIT0		0x00000200
+#define CCSR_SSI_SRCR_RFEN1		0x00000100
+#define CCSR_SSI_SRCR_RFEN0		0x00000080
+#define CCSR_SSI_SRCR_RFDIR		0x00000040
+#define CCSR_SSI_SRCR_RXDIR		0x00000020
+#define CCSR_SSI_SRCR_RSHFD		0x00000010
+#define CCSR_SSI_SRCR_RSCKP		0x00000008
+#define CCSR_SSI_SRCR_RFSI		0x00000004
+#define CCSR_SSI_SRCR_RFSL		0x00000002
+#define CCSR_SSI_SRCR_REFS		0x00000001
+
+/* STCCR and SRCCR */
+#define CCSR_SSI_SxCCR_DIV2		0x00040000
+#define CCSR_SSI_SxCCR_PSR		0x00020000
+#define CCSR_SSI_SxCCR_WL_SHIFT		13
+#define CCSR_SSI_SxCCR_WL_MASK		0x0001E000
+#define CCSR_SSI_SxCCR_WL(x) \
+	(((((x) / 2) - 1) << CCSR_SSI_SxCCR_WL_SHIFT) & CCSR_SSI_SxCCR_WL_MASK)
+#define CCSR_SSI_SxCCR_DC_SHIFT		8
+#define CCSR_SSI_SxCCR_DC_MASK		0x00001F00
+#define CCSR_SSI_SxCCR_DC(x) \
+	((((x) - 1) << CCSR_SSI_SxCCR_DC_SHIFT) & CCSR_SSI_SxCCR_DC_MASK)
+#define CCSR_SSI_SxCCR_PM_SHIFT		0
+#define CCSR_SSI_SxCCR_PM_MASK		0x000000FF
+#define CCSR_SSI_SxCCR_PM(x) \
+	((((x) - 1) << CCSR_SSI_SxCCR_PM_SHIFT) & CCSR_SSI_SxCCR_PM_MASK)
+
+/*
+ * The xFCNT bits are read-only, and the xFWM bits are read/write.  Use the
+ * CCSR_SSI_SFCSR_xFCNTy() macros to read the FIFO counters, and use the
+ * CCSR_SSI_SFCSR_xFWMy() macros to set the watermarks.
+ */
+#define CCSR_SSI_SFCSR_RFCNT1_SHIFT	28
+#define CCSR_SSI_SFCSR_RFCNT1_MASK	0xF0000000
+#define CCSR_SSI_SFCSR_RFCNT1(x) \
+	(((x) & CCSR_SSI_SFCSR_RFCNT1_MASK) >> CCSR_SSI_SFCSR_RFCNT1_SHIFT)
+#define CCSR_SSI_SFCSR_TFCNT1_SHIFT	24
+#define CCSR_SSI_SFCSR_TFCNT1_MASK	0x0F000000
+#define CCSR_SSI_SFCSR_TFCNT1(x) \
+	(((x) & CCSR_SSI_SFCSR_TFCNT1_MASK) >> CCSR_SSI_SFCSR_TFCNT1_SHIFT)
+#define CCSR_SSI_SFCSR_RFWM1_SHIFT	20
+#define CCSR_SSI_SFCSR_RFWM1_MASK	0x00F00000
+#define CCSR_SSI_SFCSR_RFWM1(x)	\
+	(((x) << CCSR_SSI_SFCSR_RFWM1_SHIFT) & CCSR_SSI_SFCSR_RFWM1_MASK)
+#define CCSR_SSI_SFCSR_TFWM1_SHIFT	16
+#define CCSR_SSI_SFCSR_TFWM1_MASK	0x000F0000
+#define CCSR_SSI_SFCSR_TFWM1(x)	\
+	(((x) << CCSR_SSI_SFCSR_TFWM1_SHIFT) & CCSR_SSI_SFCSR_TFWM1_MASK)
+#define CCSR_SSI_SFCSR_RFCNT0_SHIFT	12
+#define CCSR_SSI_SFCSR_RFCNT0_MASK	0x0000F000
+#define CCSR_SSI_SFCSR_RFCNT0(x) \
+	(((x) & CCSR_SSI_SFCSR_RFCNT0_MASK) >> CCSR_SSI_SFCSR_RFCNT0_SHIFT)
+#define CCSR_SSI_SFCSR_TFCNT0_SHIFT	8
+#define CCSR_SSI_SFCSR_TFCNT0_MASK	0x00000F00
+#define CCSR_SSI_SFCSR_TFCNT0(x) \
+	(((x) & CCSR_SSI_SFCSR_TFCNT0_MASK) >> CCSR_SSI_SFCSR_TFCNT0_SHIFT)
+#define CCSR_SSI_SFCSR_RFWM0_SHIFT	4
+#define CCSR_SSI_SFCSR_RFWM0_MASK	0x000000F0
+#define CCSR_SSI_SFCSR_RFWM0(x)	\
+	(((x) << CCSR_SSI_SFCSR_RFWM0_SHIFT) & CCSR_SSI_SFCSR_RFWM0_MASK)
+#define CCSR_SSI_SFCSR_TFWM0_SHIFT	0
+#define CCSR_SSI_SFCSR_TFWM0_MASK	0x0000000F
+#define CCSR_SSI_SFCSR_TFWM0(x)	\
+	(((x) << CCSR_SSI_SFCSR_TFWM0_SHIFT) & CCSR_SSI_SFCSR_TFWM0_MASK)
+
+#define CCSR_SSI_STR_TEST		0x00008000
+#define CCSR_SSI_STR_RCK2TCK		0x00004000
+#define CCSR_SSI_STR_RFS2TFS		0x00002000
+#define CCSR_SSI_STR_RXSTATE(x) (((x) >> 8) & 0x1F)
+#define CCSR_SSI_STR_TXD2RXD		0x00000080
+#define CCSR_SSI_STR_TCK2RCK		0x00000040
+#define CCSR_SSI_STR_TFS2RFS		0x00000020
+#define CCSR_SSI_STR_TXSTATE(x) ((x) & 0x1F)
+
+#define CCSR_SSI_SOR_CLKOFF		0x00000040
+#define CCSR_SSI_SOR_RX_CLR		0x00000020
+#define CCSR_SSI_SOR_TX_CLR		0x00000010
+#define CCSR_SSI_SOR_INIT		0x00000008
+#define CCSR_SSI_SOR_WAIT_SHIFT		1
+#define CCSR_SSI_SOR_WAIT_MASK		0x00000006
+#define CCSR_SSI_SOR_WAIT(x) (((x) & 3) << CCSR_SSI_SOR_WAIT_SHIFT)
+#define CCSR_SSI_SOR_SYNRST 		0x00000001
+
+#endif
+
diff --git a/linux/sound/soc/fsl/mpc5200_dma.c b/linux/sound/soc/fsl/mpc5200_dma.c
new file mode 100644
index 0000000..f92dca0
--- /dev/null
+++ b/linux/sound/soc/fsl/mpc5200_dma.c
@@ -0,0 +1,544 @@
+/*
+ * Freescale MPC5200 PSC DMA
+ * ALSA SoC Platform driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ * Copyright (C) 2009 Jon Smirl, Digispeaker
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/of_platform.h>
+
+#include <sound/soc.h>
+
+#include <sysdev/bestcomm/bestcomm.h>
+#include <sysdev/bestcomm/gen_bd.h>
+#include <asm/mpc52xx_psc.h>
+
+#include "mpc5200_dma.h"
+
+/*
+ * Interrupt handlers
+ */
+static irqreturn_t psc_dma_status_irq(int irq, void *_psc_dma)
+{
+	struct psc_dma *psc_dma = _psc_dma;
+	struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+	u16 isr;
+
+	isr = in_be16(&regs->mpc52xx_psc_isr);
+
+	/* Playback underrun error */
+	if (psc_dma->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP))
+		psc_dma->stats.underrun_count++;
+
+	/* Capture overrun error */
+	if (psc_dma->capture.active && (isr & MPC52xx_PSC_IMR_ORERR))
+		psc_dma->stats.overrun_count++;
+
+	out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * psc_dma_bcom_enqueue_next_buffer - Enqueue another audio buffer
+ * @s: pointer to stream private data structure
+ *
+ * Enqueues another audio period buffer into the bestcomm queue.
+ *
+ * Note: The routine must only be called when there is space available in
+ * the queue.  Otherwise the enqueue will fail and the audio ring buffer
+ * will get out of sync
+ */
+static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s)
+{
+	struct bcom_bd *bd;
+
+	/* Prepare and enqueue the next buffer descriptor */
+	bd = bcom_prepare_next_buffer(s->bcom_task);
+	bd->status = s->period_bytes;
+	bd->data[0] = s->runtime->dma_addr + (s->period_next * s->period_bytes);
+	bcom_submit_next_buffer(s->bcom_task, NULL);
+
+	/* Update for next period */
+	s->period_next = (s->period_next + 1) % s->runtime->periods;
+}
+
+/* Bestcomm DMA irq handler */
+static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream)
+{
+	struct psc_dma_stream *s = _psc_dma_stream;
+
+	spin_lock(&s->psc_dma->lock);
+	/* For each finished period, dequeue the completed period buffer
+	 * and enqueue a new one in it's place. */
+	while (bcom_buffer_done(s->bcom_task)) {
+		bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
+
+		s->period_current = (s->period_current+1) % s->runtime->periods;
+		s->period_count++;
+
+		psc_dma_bcom_enqueue_next_buffer(s);
+	}
+	spin_unlock(&s->psc_dma->lock);
+
+	/* If the stream is active, then also inform the PCM middle layer
+	 * of the period finished event. */
+	if (s->active)
+		snd_pcm_period_elapsed(s->stream);
+
+	return IRQ_HANDLED;
+}
+
+static int psc_dma_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_set_runtime_buffer(substream, NULL);
+	return 0;
+}
+
+/**
+ * psc_dma_trigger: start and stop the DMA transfer.
+ *
+ * This function is called by ALSA to start, stop, pause, and resume the DMA
+ * transfer of data.
+ */
+static int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
+	struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+	u16 imr;
+	unsigned long flags;
+	int i;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		dev_dbg(psc_dma->dev, "START: stream=%i fbits=%u ps=%u #p=%u\n",
+			substream->pstr->stream, runtime->frame_bits,
+			(int)runtime->period_size, runtime->periods);
+		s->period_bytes = frames_to_bytes(runtime,
+						  runtime->period_size);
+		s->period_next = 0;
+		s->period_current = 0;
+		s->active = 1;
+		s->period_count = 0;
+		s->runtime = runtime;
+
+		/* Fill up the bestcomm bd queue and enable DMA.
+		 * This will begin filling the PSC's fifo.
+		 */
+		spin_lock_irqsave(&psc_dma->lock, flags);
+
+		if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+			bcom_gen_bd_rx_reset(s->bcom_task);
+		else
+			bcom_gen_bd_tx_reset(s->bcom_task);
+
+		for (i = 0; i < runtime->periods; i++)
+			if (!bcom_queue_full(s->bcom_task))
+				psc_dma_bcom_enqueue_next_buffer(s);
+
+		bcom_enable(s->bcom_task);
+		spin_unlock_irqrestore(&psc_dma->lock, flags);
+
+		out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
+
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		dev_dbg(psc_dma->dev, "STOP: stream=%i periods_count=%i\n",
+			substream->pstr->stream, s->period_count);
+		s->active = 0;
+
+		spin_lock_irqsave(&psc_dma->lock, flags);
+		bcom_disable(s->bcom_task);
+		if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+			bcom_gen_bd_rx_reset(s->bcom_task);
+		else
+			bcom_gen_bd_tx_reset(s->bcom_task);
+		spin_unlock_irqrestore(&psc_dma->lock, flags);
+
+		break;
+
+	default:
+		dev_dbg(psc_dma->dev, "unhandled trigger: stream=%i cmd=%i\n",
+			substream->pstr->stream, cmd);
+		return -EINVAL;
+	}
+
+	/* Update interrupt enable settings */
+	imr = 0;
+	if (psc_dma->playback.active)
+		imr |= MPC52xx_PSC_IMR_TXEMP;
+	if (psc_dma->capture.active)
+		imr |= MPC52xx_PSC_IMR_ORERR;
+	out_be16(&regs->isr_imr.imr, psc_dma->imr | imr);
+
+	return 0;
+}
+
+
+/* ---------------------------------------------------------------------
+ * The PSC DMA 'ASoC platform' driver
+ *
+ * Can be referenced by an 'ASoC machine' driver
+ * This driver only deals with the audio bus; it doesn't have any
+ * interaction with the attached codec
+ */
+
+static const struct snd_pcm_hardware psc_dma_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_BATCH,
+	.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |
+		SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE,
+	.rate_min = 8000,
+	.rate_max = 48000,
+	.channels_min = 1,
+	.channels_max = 2,
+	.period_bytes_max	= 1024 * 1024,
+	.period_bytes_min	= 32,
+	.periods_min		= 2,
+	.periods_max		= 256,
+	.buffer_bytes_max	= 2 * 1024 * 1024,
+	.fifo_size		= 512,
+};
+
+static int psc_dma_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct psc_dma_stream *s;
+	int rc;
+
+	dev_dbg(psc_dma->dev, "psc_dma_open(substream=%p)\n", substream);
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		s = &psc_dma->capture;
+	else
+		s = &psc_dma->playback;
+
+	snd_soc_set_runtime_hwparams(substream, &psc_dma_hardware);
+
+	rc = snd_pcm_hw_constraint_integer(runtime,
+		SNDRV_PCM_HW_PARAM_PERIODS);
+	if (rc < 0) {
+		dev_err(substream->pcm->card->dev, "invalid buffer size\n");
+		return rc;
+	}
+
+	s->stream = substream;
+	return 0;
+}
+
+static int psc_dma_close(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct psc_dma_stream *s;
+
+	dev_dbg(psc_dma->dev, "psc_dma_close(substream=%p)\n", substream);
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		s = &psc_dma->capture;
+	else
+		s = &psc_dma->playback;
+
+	if (!psc_dma->playback.active &&
+	    !psc_dma->capture.active) {
+
+		/* Disable all interrupts and reset the PSC */
+		out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
+		out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */
+	}
+	s->stream = NULL;
+	return 0;
+}
+
+static snd_pcm_uframes_t
+psc_dma_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct psc_dma_stream *s;
+	dma_addr_t count;
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		s = &psc_dma->capture;
+	else
+		s = &psc_dma->playback;
+
+	count = s->period_current * s->period_bytes;
+
+	return bytes_to_frames(substream->runtime, count);
+}
+
+static int
+psc_dma_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *params)
+{
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+	return 0;
+}
+
+static struct snd_pcm_ops psc_dma_ops = {
+	.open		= psc_dma_open,
+	.close		= psc_dma_close,
+	.hw_free	= psc_dma_hw_free,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.pointer	= psc_dma_pointer,
+	.trigger	= psc_dma_trigger,
+	.hw_params	= psc_dma_hw_params,
+};
+
+static u64 psc_dma_dmamask = 0xffffffff;
+static int psc_dma_new(struct snd_card *card, struct snd_soc_dai *dai,
+			   struct snd_pcm *pcm)
+{
+	struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	size_t size = psc_dma_hardware.buffer_bytes_max;
+	int rc = 0;
+
+	dev_dbg(rtd->platform->dev, "psc_dma_new(card=%p, dai=%p, pcm=%p)\n",
+		card, dai, pcm);
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &psc_dma_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = 0xffffffff;
+
+	if (pcm->streams[0].substream) {
+		rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+				size, &pcm->streams[0].substream->dma_buffer);
+		if (rc)
+			goto playback_alloc_err;
+	}
+
+	if (pcm->streams[1].substream) {
+		rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+				size, &pcm->streams[1].substream->dma_buffer);
+		if (rc)
+			goto capture_alloc_err;
+	}
+
+	if (rtd->codec->ac97)
+		rtd->codec->ac97->private_data = psc_dma;
+
+	return 0;
+
+ capture_alloc_err:
+	if (pcm->streams[0].substream)
+		snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
+
+ playback_alloc_err:
+	dev_err(card->dev, "Cannot allocate buffer(s)\n");
+
+	return -ENOMEM;
+}
+
+static void psc_dma_free(struct snd_pcm *pcm)
+{
+	struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+	struct snd_pcm_substream *substream;
+	int stream;
+
+	dev_dbg(rtd->platform->dev, "psc_dma_free(pcm=%p)\n", pcm);
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (substream) {
+			snd_dma_free_pages(&substream->dma_buffer);
+			substream->dma_buffer.area = NULL;
+			substream->dma_buffer.addr = 0;
+		}
+	}
+}
+
+static struct snd_soc_platform_driver mpc5200_audio_dma_platform = {
+	.ops		= &psc_dma_ops,
+	.pcm_new	= &psc_dma_new,
+	.pcm_free	= &psc_dma_free,
+};
+
+static int mpc5200_hpcd_probe(struct of_device *op,
+		const struct of_device_id *match)
+{
+	phys_addr_t fifo;
+	struct psc_dma *psc_dma;
+	struct resource res;
+	int size, irq, rc;
+	const __be32 *prop;
+	void __iomem *regs;
+	int ret;
+
+	/* Fetch the registers and IRQ of the PSC */
+	irq = irq_of_parse_and_map(op->dev.of_node, 0);
+	if (of_address_to_resource(op->dev.of_node, 0, &res)) {
+		dev_err(&op->dev, "Missing reg property\n");
+		return -ENODEV;
+	}
+	regs = ioremap(res.start, 1 + res.end - res.start);
+	if (!regs) {
+		dev_err(&op->dev, "Could not map registers\n");
+		return -ENODEV;
+	}
+
+	/* Allocate and initialize the driver private data */
+	psc_dma = kzalloc(sizeof *psc_dma, GFP_KERNEL);
+	if (!psc_dma) {
+		ret = -ENOMEM;
+		goto out_unmap;
+	}
+
+	/* Get the PSC ID */
+	prop = of_get_property(op->dev.of_node, "cell-index", &size);
+	if (!prop || size < sizeof *prop) {
+		ret = -ENODEV;
+		goto out_free;
+	}
+
+	spin_lock_init(&psc_dma->lock);
+	mutex_init(&psc_dma->mutex);
+	psc_dma->id = be32_to_cpu(*prop);
+	psc_dma->irq = irq;
+	psc_dma->psc_regs = regs;
+	psc_dma->fifo_regs = regs + sizeof *psc_dma->psc_regs;
+	psc_dma->dev = &op->dev;
+	psc_dma->playback.psc_dma = psc_dma;
+	psc_dma->capture.psc_dma = psc_dma;
+	snprintf(psc_dma->name, sizeof psc_dma->name, "PSC%u", psc_dma->id);
+
+	/* Find the address of the fifo data registers and setup the
+	 * DMA tasks */
+	fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32);
+	psc_dma->capture.bcom_task =
+		bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512);
+	psc_dma->playback.bcom_task =
+		bcom_psc_gen_bd_tx_init(psc_dma->id, 10, fifo);
+	if (!psc_dma->capture.bcom_task ||
+	    !psc_dma->playback.bcom_task) {
+		dev_err(&op->dev, "Could not allocate bestcomm tasks\n");
+		ret = -ENODEV;
+		goto out_free;
+	}
+
+	/* Disable all interrupts and reset the PSC */
+	out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
+	 /* reset receiver */
+	out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_RX);
+	 /* reset transmitter */
+	out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_TX);
+	 /* reset error */
+	out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_ERR_STAT);
+	 /* reset mode */
+	out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_SEL_MODE_REG_1);
+
+	/* Set up mode register;
+	 * First write: RxRdy (FIFO Alarm) generates rx FIFO irq
+	 * Second write: register Normal mode for non loopback
+	 */
+	out_8(&psc_dma->psc_regs->mode, 0);
+	out_8(&psc_dma->psc_regs->mode, 0);
+
+	/* Set the TX and RX fifo alarm thresholds */
+	out_be16(&psc_dma->fifo_regs->rfalarm, 0x100);
+	out_8(&psc_dma->fifo_regs->rfcntl, 0x4);
+	out_be16(&psc_dma->fifo_regs->tfalarm, 0x100);
+	out_8(&psc_dma->fifo_regs->tfcntl, 0x7);
+
+	/* Lookup the IRQ numbers */
+	psc_dma->playback.irq =
+		bcom_get_task_irq(psc_dma->playback.bcom_task);
+	psc_dma->capture.irq =
+		bcom_get_task_irq(psc_dma->capture.bcom_task);
+
+	rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED,
+			 "psc-dma-status", psc_dma);
+	rc |= request_irq(psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED,
+			  "psc-dma-capture", &psc_dma->capture);
+	rc |= request_irq(psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED,
+			  "psc-dma-playback", &psc_dma->playback);
+	if (rc) {
+		ret = -ENODEV;
+		goto out_irq;
+	}
+
+	/* Save what we've done so it can be found again later */
+	dev_set_drvdata(&op->dev, psc_dma);
+
+	/* Tell the ASoC OF helpers about it */
+	return snd_soc_register_platform(&op->dev, &mpc5200_audio_dma_platform);
+out_irq:
+	free_irq(psc_dma->irq, psc_dma);
+	free_irq(psc_dma->capture.irq, &psc_dma->capture);
+	free_irq(psc_dma->playback.irq, &psc_dma->playback);
+out_free:
+	kfree(psc_dma);
+out_unmap:
+	iounmap(regs);
+	return ret;
+}
+
+static int mpc5200_hpcd_remove(struct of_device *op)
+{
+	struct psc_dma *psc_dma = dev_get_drvdata(&op->dev);
+
+	dev_dbg(&op->dev, "mpc5200_audio_dma_destroy()\n");
+
+	snd_soc_unregister_platform(&op->dev);
+
+	bcom_gen_bd_rx_release(psc_dma->capture.bcom_task);
+	bcom_gen_bd_tx_release(psc_dma->playback.bcom_task);
+
+	/* Release irqs */
+	free_irq(psc_dma->irq, psc_dma);
+	free_irq(psc_dma->capture.irq, &psc_dma->capture);
+	free_irq(psc_dma->playback.irq, &psc_dma->playback);
+
+	iounmap(psc_dma->psc_regs);
+	kfree(psc_dma);
+	dev_set_drvdata(&op->dev, NULL);
+
+	return 0;
+}
+
+static struct of_device_id mpc5200_hpcd_match[] = {
+	{
+		.compatible = "fsl,mpc5200-pcm",
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, mpc5200_hpcd_match);
+
+static struct of_platform_driver mpc5200_hpcd_of_driver = {
+	.owner		= THIS_MODULE,
+	.name		= "mpc5200-pcm-audio",
+	.match_table    = mpc5200_hpcd_match,
+	.probe		= mpc5200_hpcd_probe,
+	.remove		= mpc5200_hpcd_remove,
+};
+
+static int __init mpc5200_hpcd_init(void)
+{
+	return of_register_platform_driver(&mpc5200_hpcd_of_driver);
+}
+
+static void __exit mpc5200_hpcd_exit(void)
+{
+	of_unregister_platform_driver(&mpc5200_hpcd_of_driver);
+}
+
+module_init(mpc5200_hpcd_init);
+module_exit(mpc5200_hpcd_exit);
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/fsl/mpc5200_dma.h b/linux/sound/soc/fsl/mpc5200_dma.h
new file mode 100644
index 0000000..a3c0cd5
--- /dev/null
+++ b/linux/sound/soc/fsl/mpc5200_dma.h
@@ -0,0 +1,84 @@
+/*
+ * Freescale MPC5200 Audio DMA driver
+ */
+
+#ifndef __SOUND_SOC_FSL_MPC5200_DMA_H__
+#define __SOUND_SOC_FSL_MPC5200_DMA_H__
+
+#define PSC_STREAM_NAME_LEN 32
+
+/**
+ * psc_ac97_stream - Data specific to a single stream (playback or capture)
+ * @active:		flag indicating if the stream is active
+ * @psc_dma:		pointer back to parent psc_dma data structure
+ * @bcom_task:		bestcomm task structure
+ * @irq:		irq number for bestcomm task
+ * @period_end:		physical address of end of DMA region
+ * @period_next_pt:	physical address of next DMA buffer to enqueue
+ * @period_bytes:	size of DMA period in bytes
+ * @ac97_slot_bits:	Enable bits for turning on the correct AC97 slot
+ */
+struct psc_dma_stream {
+	struct snd_pcm_runtime *runtime;
+	int active;
+	struct psc_dma *psc_dma;
+	struct bcom_task *bcom_task;
+	int irq;
+	struct snd_pcm_substream *stream;
+	int period_next;
+	int period_current;
+	int period_bytes;
+	int period_count;
+
+	/* AC97 state */
+	u32 ac97_slot_bits;
+};
+
+/**
+ * psc_dma - Private driver data
+ * @name: short name for this device ("PSC0", "PSC1", etc)
+ * @psc_regs: pointer to the PSC's registers
+ * @fifo_regs: pointer to the PSC's FIFO registers
+ * @irq: IRQ of this PSC
+ * @dev: struct device pointer
+ * @dai: the CPU DAI for this device
+ * @sicr: Base value used in serial interface control register; mode is ORed
+ *        with this value.
+ * @playback: Playback stream context data
+ * @capture: Capture stream context data
+ */
+struct psc_dma {
+	char name[32];
+	struct mpc52xx_psc __iomem *psc_regs;
+	struct mpc52xx_psc_fifo __iomem *fifo_regs;
+	unsigned int irq;
+	struct device *dev;
+	spinlock_t lock;
+	struct mutex mutex;
+	u32 sicr;
+	uint sysclk;
+	int imr;
+	int id;
+	unsigned int slots;
+
+	/* per-stream data */
+	struct psc_dma_stream playback;
+	struct psc_dma_stream capture;
+
+	/* Statistics */
+	struct {
+		unsigned long overrun_count;
+		unsigned long underrun_count;
+	} stats;
+};
+
+/* Utility for retrieving psc_dma_stream structure from a substream */
+static inline struct psc_dma_stream *
+to_psc_dma_stream(struct snd_pcm_substream *substream, struct psc_dma *psc_dma)
+{
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		return &psc_dma->capture;
+	return &psc_dma->playback;
+}
+
+#endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */
diff --git a/linux/sound/soc/fsl/mpc5200_psc_ac97.c b/linux/sound/soc/fsl/mpc5200_psc_ac97.c
new file mode 100644
index 0000000..40acc8e
--- /dev/null
+++ b/linux/sound/soc/fsl/mpc5200_psc_ac97.c
@@ -0,0 +1,348 @@
+/*
+ * linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip.
+ *
+ * Copyright (C) 2009 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/time.h>
+#include <asm/delay.h>
+#include <asm/mpc52xx.h>
+#include <asm/mpc52xx_psc.h>
+
+#include "mpc5200_dma.h"
+#include "mpc5200_psc_ac97.h"
+
+#define DRV_NAME "mpc5200-psc-ac97"
+
+/* ALSA only supports a single AC97 device so static is recommend here */
+static struct psc_dma *psc_dma;
+
+static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	int status;
+	unsigned int val;
+
+	mutex_lock(&psc_dma->mutex);
+
+	/* Wait for command send status zero = ready */
+	status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) &
+				MPC52xx_PSC_SR_CMDSEND), 100, 0);
+	if (status == 0) {
+		pr_err("timeout on ac97 bus (rdy)\n");
+		mutex_unlock(&psc_dma->mutex);
+		return -ENODEV;
+	}
+
+	/* Force clear the data valid bit */
+	in_be32(&psc_dma->psc_regs->ac97_data);
+
+	/* Send the read */
+	out_be32(&psc_dma->psc_regs->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24));
+
+	/* Wait for the answer */
+	status = spin_event_timeout((in_be16(&psc_dma->psc_regs->sr_csr.status) &
+				MPC52xx_PSC_SR_DATA_VAL), 100, 0);
+	if (status == 0) {
+		pr_err("timeout on ac97 read (val) %x\n",
+				in_be16(&psc_dma->psc_regs->sr_csr.status));
+		mutex_unlock(&psc_dma->mutex);
+		return -ENODEV;
+	}
+	/* Get the data */
+	val = in_be32(&psc_dma->psc_regs->ac97_data);
+	if (((val >> 24) & 0x7f) != reg) {
+		pr_err("reg echo error on ac97 read\n");
+		mutex_unlock(&psc_dma->mutex);
+		return -ENODEV;
+	}
+	val = (val >> 8) & 0xffff;
+
+	mutex_unlock(&psc_dma->mutex);
+	return (unsigned short) val;
+}
+
+static void psc_ac97_write(struct snd_ac97 *ac97,
+				unsigned short reg, unsigned short val)
+{
+	int status;
+
+	mutex_lock(&psc_dma->mutex);
+
+	/* Wait for command status zero = ready */
+	status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) &
+				MPC52xx_PSC_SR_CMDSEND), 100, 0);
+	if (status == 0) {
+		pr_err("timeout on ac97 bus (write)\n");
+		goto out;
+	}
+	/* Write data */
+	out_be32(&psc_dma->psc_regs->ac97_cmd,
+			((reg & 0x7f) << 24) | (val << 8));
+
+ out:
+	mutex_unlock(&psc_dma->mutex);
+}
+
+static void psc_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+	struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+
+	mutex_lock(&psc_dma->mutex);
+
+	out_be32(&regs->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR);
+	udelay(3);
+	out_be32(&regs->sicr, psc_dma->sicr);
+
+	mutex_unlock(&psc_dma->mutex);
+}
+
+static void psc_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+	struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+
+	mutex_lock(&psc_dma->mutex);
+	dev_dbg(psc_dma->dev, "cold reset\n");
+
+	mpc5200_psc_ac97_gpio_reset(psc_dma->id);
+
+	/* Notify the PSC that a reset has occurred */
+	out_be32(&regs->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB);
+
+	/* Re-enable RX and TX */
+	out_8(&regs->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
+
+	mutex_unlock(&psc_dma->mutex);
+
+	msleep(1);
+	psc_ac97_warm_reset(ac97);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read		= psc_ac97_read,
+	.write		= psc_ac97_write,
+	.reset		= psc_ac97_cold_reset,
+	.warm_reset	= psc_ac97_warm_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int psc_ac97_hw_analog_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *cpu_dai)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+	struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
+
+	dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
+		" periods=%i buffer_size=%i  buffer_bytes=%i channels=%i"
+		" rate=%i format=%i\n",
+		__func__, substream, params_period_size(params),
+		params_period_bytes(params), params_periods(params),
+		params_buffer_size(params), params_buffer_bytes(params),
+		params_channels(params), params_rate(params),
+		params_format(params));
+
+	/* Determine the set of enable bits to turn on */
+	s->ac97_slot_bits = (params_channels(params) == 1) ? 0x100 : 0x300;
+	if (substream->pstr->stream != SNDRV_PCM_STREAM_CAPTURE)
+		s->ac97_slot_bits <<= 16;
+	return 0;
+}
+
+static int psc_ac97_hw_digital_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *cpu_dai)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+
+	dev_dbg(psc_dma->dev, "%s(substream=%p)\n", __func__, substream);
+
+	if (params_channels(params) == 1)
+		out_be32(&psc_dma->psc_regs->ac97_slots, 0x01000000);
+	else
+		out_be32(&psc_dma->psc_regs->ac97_slots, 0x03000000);
+
+	return 0;
+}
+
+static int psc_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
+							struct snd_soc_dai *dai)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(dai);
+	struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		dev_dbg(psc_dma->dev, "AC97 START: stream=%i\n",
+			substream->pstr->stream);
+
+		/* Set the slot enable bits */
+		psc_dma->slots |= s->ac97_slot_bits;
+		out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		dev_dbg(psc_dma->dev, "AC97 STOP: stream=%i\n",
+			substream->pstr->stream);
+
+		/* Clear the slot enable bits */
+		psc_dma->slots &= ~(s->ac97_slot_bits);
+		out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots);
+		break;
+	}
+	return 0;
+}
+
+static int psc_ac97_probe(struct snd_soc_dai *cpu_dai)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+	struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+
+	/* Go */
+	out_8(&regs->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
+	return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * ALSA SoC Bindings
+ *
+ * - Digital Audio Interface (DAI) template
+ * - create/destroy dai hooks
+ */
+
+/**
+ * psc_ac97_dai_template: template CPU Digital Audio Interface
+ */
+static struct snd_soc_dai_ops psc_ac97_analog_ops = {
+	.hw_params	= psc_ac97_hw_analog_params,
+	.trigger	= psc_ac97_trigger,
+};
+
+static struct snd_soc_dai_ops psc_ac97_digital_ops = {
+	.hw_params	= psc_ac97_hw_digital_params,
+};
+
+static struct snd_soc_dai_driver psc_ac97_dai[] = {
+{
+	.ac97_control = 1,
+	.probe	= psc_ac97_probe,
+	.playback = {
+		.channels_min   = 1,
+		.channels_max   = 6,
+		.rates          = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_BE,
+	},
+	.capture = {
+		.channels_min   = 1,
+		.channels_max   = 2,
+		.rates          = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_BE,
+	},
+	.ops = &psc_ac97_analog_ops,
+},
+{
+	.ac97_control = 1,
+	.playback = {
+		.channels_min   = 1,
+		.channels_max   = 2,
+		.rates          = SNDRV_PCM_RATE_32000 | \
+			SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE,
+	},
+	.ops = &psc_ac97_digital_ops,
+} };
+
+
+
+/* ---------------------------------------------------------------------
+ * OF platform bus binding code:
+ * - Probe/remove operations
+ * - OF device match table
+ */
+static int __devinit psc_ac97_of_probe(struct platform_device *op,
+				      const struct of_device_id *match)
+{
+	int rc;
+	struct snd_ac97 ac97;
+	struct mpc52xx_psc __iomem *regs;
+
+	rc = snd_soc_register_dais(&op->dev, psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai));
+	if (rc != 0) {
+		dev_err(&op->dev, "Failed to register DAI\n");
+		return rc;
+	}
+
+	psc_dma = dev_get_drvdata(&op->dev);
+	regs = psc_dma->psc_regs;
+	ac97.private_data = psc_dma;
+
+	psc_dma->imr = 0;
+	out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
+
+	/* Configure the serial interface mode to AC97 */
+	psc_dma->sicr = MPC52xx_PSC_SICR_SIM_AC97 | MPC52xx_PSC_SICR_ENAC97;
+	out_be32(&regs->sicr, psc_dma->sicr);
+
+	/* No slots active */
+	out_be32(&regs->ac97_slots, 0x00000000);
+
+	return 0;
+}
+
+static int __devexit psc_ac97_of_remove(struct platform_device *op)
+{
+	snd_soc_unregister_dais(&op->dev, ARRAY_SIZE(psc_ac97_dai));
+	return 0;
+}
+
+/* Match table for of_platform binding */
+static struct of_device_id psc_ac97_match[] __devinitdata = {
+	{ .compatible = "fsl,mpc5200-psc-ac97", },
+	{ .compatible = "fsl,mpc5200b-psc-ac97", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, psc_ac97_match);
+
+static struct of_platform_driver psc_ac97_driver = {
+	.probe = psc_ac97_of_probe,
+	.remove = __devexit_p(psc_ac97_of_remove),
+	.driver = {
+		.name = "mpc5200-psc-ac97",
+		.owner = THIS_MODULE,
+		.of_match_table = psc_ac97_match,
+	},
+};
+
+/* ---------------------------------------------------------------------
+ * Module setup and teardown; simply register the of_platform driver
+ * for the PSC in AC97 mode.
+ */
+static int __init psc_ac97_init(void)
+{
+	return of_register_platform_driver(&psc_ac97_driver);
+}
+module_init(psc_ac97_init);
+
+static void __exit psc_ac97_exit(void)
+{
+	of_unregister_platform_driver(&psc_ac97_driver);
+}
+module_exit(psc_ac97_exit);
+
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_DESCRIPTION("mpc5200 AC97 module");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/fsl/mpc5200_psc_ac97.h b/linux/sound/soc/fsl/mpc5200_psc_ac97.h
new file mode 100644
index 0000000..e881e78
--- /dev/null
+++ b/linux/sound/soc/fsl/mpc5200_psc_ac97.h
@@ -0,0 +1,13 @@
+/*
+ * Freescale MPC5200 PSC in AC97 mode
+ * ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ */
+
+#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__
+#define __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__
+
+#define MPC5200_AC97_NORMAL 0
+#define MPC5200_AC97_SPDIF 1
+
+#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ */
diff --git a/linux/sound/soc/fsl/mpc5200_psc_i2s.c b/linux/sound/soc/fsl/mpc5200_psc_i2s.c
new file mode 100644
index 0000000..9018fa5
--- /dev/null
+++ b/linux/sound/soc/fsl/mpc5200_psc_i2s.c
@@ -0,0 +1,245 @@
+/*
+ * Freescale MPC5200 PSC in I2S mode
+ * ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ * Copyright (C) 2009 Jon Smirl, Digispeaker
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mpc52xx_psc.h>
+
+#include "mpc5200_dma.h"
+
+/**
+ * PSC_I2S_RATES: sample rates supported by the I2S
+ *
+ * This driver currently only supports the PSC running in I2S slave mode,
+ * which means the codec determines the sample rate.  Therefore, we tell
+ * ALSA that we support all rates and let the codec driver decide what rates
+ * are really supported.
+ */
+#define PSC_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
+			SNDRV_PCM_RATE_CONTINUOUS)
+
+/**
+ * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode
+ */
+#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
+			 SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
+
+static int psc_i2s_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	u32 mode;
+
+	dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
+		" periods=%i buffer_size=%i  buffer_bytes=%i\n",
+		__func__, substream, params_period_size(params),
+		params_period_bytes(params), params_periods(params),
+		params_buffer_size(params), params_buffer_bytes(params));
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		mode = MPC52xx_PSC_SICR_SIM_CODEC_8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE:
+		mode = MPC52xx_PSC_SICR_SIM_CODEC_16;
+		break;
+	case SNDRV_PCM_FORMAT_S24_BE:
+		mode = MPC52xx_PSC_SICR_SIM_CODEC_24;
+		break;
+	case SNDRV_PCM_FORMAT_S32_BE:
+		mode = MPC52xx_PSC_SICR_SIM_CODEC_32;
+		break;
+	default:
+		dev_dbg(psc_dma->dev, "invalid format\n");
+		return -EINVAL;
+	}
+	out_be32(&psc_dma->psc_regs->sicr, psc_dma->sicr | mode);
+
+	return 0;
+}
+
+/**
+ * psc_i2s_set_sysclk: set the clock frequency and direction
+ *
+ * This function is called by the machine driver to tell us what the clock
+ * frequency and direction are.
+ *
+ * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN),
+ * and we don't care about the frequency.  Return an error if the direction
+ * is not SND_SOC_CLOCK_IN.
+ *
+ * @clk_id: reserved, should be zero
+ * @freq: the frequency of the given clock ID, currently ignored
+ * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
+ */
+static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
+			      int clk_id, unsigned int freq, int dir)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+	dev_dbg(psc_dma->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n",
+				cpu_dai, dir);
+	return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
+}
+
+/**
+ * psc_i2s_set_fmt: set the serial format.
+ *
+ * This function is called by the machine driver to tell us what serial
+ * format to use.
+ *
+ * This driver only supports I2S mode.  Return an error if the format is
+ * not SND_SOC_DAIFMT_I2S.
+ *
+ * @format: one of SND_SOC_DAIFMT_xxx
+ */
+static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+	dev_dbg(psc_dma->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n",
+				cpu_dai, format);
+	return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
+}
+
+/* ---------------------------------------------------------------------
+ * ALSA SoC Bindings
+ *
+ * - Digital Audio Interface (DAI) template
+ * - create/destroy dai hooks
+ */
+
+/**
+ * psc_i2s_dai_template: template CPU Digital Audio Interface
+ */
+static struct snd_soc_dai_ops psc_i2s_dai_ops = {
+	.hw_params	= psc_i2s_hw_params,
+	.set_sysclk	= psc_i2s_set_sysclk,
+	.set_fmt	= psc_i2s_set_fmt,
+};
+
+static struct snd_soc_dai_driver psc_i2s_dai[] = {{
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = PSC_I2S_RATES,
+		.formats = PSC_I2S_FORMATS,
+	},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = PSC_I2S_RATES,
+		.formats = PSC_I2S_FORMATS,
+	},
+	.ops = &psc_i2s_dai_ops,
+} };
+
+/* ---------------------------------------------------------------------
+ * OF platform bus binding code:
+ * - Probe/remove operations
+ * - OF device match table
+ */
+static int __devinit psc_i2s_of_probe(struct platform_device *op,
+				      const struct of_device_id *match)
+{
+	int rc;
+	struct psc_dma *psc_dma;
+	struct mpc52xx_psc __iomem *regs;
+
+	rc = snd_soc_register_dais(&op->dev, psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai));
+	if (rc != 0) {
+		pr_err("Failed to register DAI\n");
+		return rc;
+	}
+
+	psc_dma = dev_get_drvdata(&op->dev);
+	regs = psc_dma->psc_regs;
+
+	/* Configure the serial interface mode; defaulting to CODEC8 mode */
+	psc_dma->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S |
+			MPC52xx_PSC_SICR_CLKPOL;
+	out_be32(&psc_dma->psc_regs->sicr,
+		 psc_dma->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8);
+
+	/* Check for the codec handle.  If it is not present then we
+	 * are done */
+	if (!of_get_property(op->dev.of_node, "codec-handle", NULL))
+		return 0;
+
+	/* Due to errata in the dma mode; need to line up enabling
+	 * the transmitter with a transition on the frame sync
+	 * line */
+
+	/* first make sure it is low */
+	while ((in_8(&regs->ipcr_acr.ipcr) & 0x80) != 0)
+		;
+	/* then wait for the transition to high */
+	while ((in_8(&regs->ipcr_acr.ipcr) & 0x80) == 0)
+		;
+	/* Finally, enable the PSC.
+	 * Receiver must always be enabled; even when we only want
+	 * transmit.  (see 15.3.2.3 of MPC5200B User's Guide) */
+
+	/* Go */
+	out_8(&psc_dma->psc_regs->command,
+			MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
+
+	return 0;
+
+}
+
+static int __devexit psc_i2s_of_remove(struct platform_device *op)
+{
+	snd_soc_unregister_dais(&op->dev, ARRAY_SIZE(psc_i2s_dai));
+	return 0;
+}
+
+/* Match table for of_platform binding */
+static struct of_device_id psc_i2s_match[] __devinitdata = {
+	{ .compatible = "fsl,mpc5200-psc-i2s", },
+	{ .compatible = "fsl,mpc5200b-psc-i2s", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, psc_i2s_match);
+
+static struct of_platform_driver psc_i2s_driver = {
+	.probe = psc_i2s_of_probe,
+	.remove = __devexit_p(psc_i2s_of_remove),
+	.driver = {
+		.name = "mpc5200-psc-i2s",
+		.owner = THIS_MODULE,
+		.of_match_table = psc_i2s_match,
+	},
+};
+
+/* ---------------------------------------------------------------------
+ * Module setup and teardown; simply register the of_platform driver
+ * for the PSC in I2S mode.
+ */
+static int __init psc_i2s_init(void)
+{
+	return of_register_platform_driver(&psc_i2s_driver);
+}
+module_init(psc_i2s_init);
+
+static void __exit psc_i2s_exit(void)
+{
+	of_unregister_platform_driver(&psc_i2s_driver);
+}
+module_exit(psc_i2s_exit);
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/fsl/mpc8610_hpcd.c b/linux/sound/soc/fsl/mpc8610_hpcd.c
new file mode 100644
index 0000000..7d7847a
--- /dev/null
+++ b/linux/sound/soc/fsl/mpc8610_hpcd.c
@@ -0,0 +1,590 @@
+/**
+ * Freescale MPC8610HPCD ALSA SoC Machine driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <asm/fsl_guts.h>
+
+#include "fsl_dma.h"
+#include "fsl_ssi.h"
+
+/* There's only one global utilities register */
+static phys_addr_t guts_phys;
+
+#define DAI_NAME_SIZE	32
+
+/**
+ * mpc8610_hpcd_data: machine-specific ASoC device data
+ *
+ * This structure contains data for a single sound platform device on an
+ * MPC8610 HPCD.  Some of the data is taken from the device tree.
+ */
+struct mpc8610_hpcd_data {
+	struct snd_soc_dai_link dai[2];
+	struct snd_soc_card card;
+	unsigned int dai_format;
+	unsigned int codec_clk_direction;
+	unsigned int cpu_clk_direction;
+	unsigned int clk_frequency;
+	unsigned int ssi_id;		/* 0 = SSI1, 1 = SSI2, etc */
+	unsigned int dma_id[2];		/* 0 = DMA1, 1 = DMA2, etc */
+	unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
+	char codec_dai_name[DAI_NAME_SIZE];
+	char codec_name[DAI_NAME_SIZE];
+	char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
+};
+
+/**
+ * mpc8610_hpcd_machine_probe: initialize the board
+ *
+ * This function is used to initialize the board-specific hardware.
+ *
+ * Here we program the DMACR and PMUXCR registers.
+ */
+static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device)
+{
+	struct snd_soc_card *card = platform_get_drvdata(sound_device);
+	struct mpc8610_hpcd_data *machine_data =
+		container_of(card, struct mpc8610_hpcd_data, card);
+	struct ccsr_guts_86xx __iomem *guts;
+
+	guts = ioremap(guts_phys, sizeof(struct ccsr_guts_86xx));
+	if (!guts) {
+		dev_err(card->dev, "could not map global utilities\n");
+		return -ENOMEM;
+	}
+
+	/* Program the signal routing between the SSI and the DMA */
+	guts_set_dmacr(guts, machine_data->dma_id[0],
+		       machine_data->dma_channel_id[0],
+		       CCSR_GUTS_DMACR_DEV_SSI);
+	guts_set_dmacr(guts, machine_data->dma_id[1],
+		       machine_data->dma_channel_id[1],
+		       CCSR_GUTS_DMACR_DEV_SSI);
+
+	guts_set_pmuxcr_dma(guts, machine_data->dma_id[0],
+			    machine_data->dma_channel_id[0], 0);
+	guts_set_pmuxcr_dma(guts, machine_data->dma_id[1],
+			    machine_data->dma_channel_id[1], 0);
+
+	switch (machine_data->ssi_id) {
+	case 0:
+		clrsetbits_be32(&guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
+		break;
+	case 1:
+		clrsetbits_be32(&guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
+		break;
+	}
+
+	iounmap(guts);
+
+	return 0;
+}
+
+/**
+ * mpc8610_hpcd_startup: program the board with various hardware parameters
+ *
+ * This function takes board-specific information, like clock frequencies
+ * and serial data formats, and passes that information to the codec and
+ * transport drivers.
+ */
+static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct mpc8610_hpcd_data *machine_data =
+		container_of(rtd->card, struct mpc8610_hpcd_data, card);
+	struct device *dev = rtd->card->dev;
+	int ret = 0;
+
+	/* Tell the codec driver what the serial protocol is. */
+	ret = snd_soc_dai_set_fmt(rtd->codec_dai, machine_data->dai_format);
+	if (ret < 0) {
+		dev_err(dev, "could not set codec driver audio format\n");
+		return ret;
+	}
+
+	/*
+	 * Tell the codec driver what the MCLK frequency is, and whether it's
+	 * a slave or master.
+	 */
+	ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0,
+				     machine_data->clk_frequency,
+				     machine_data->codec_clk_direction);
+	if (ret < 0) {
+		dev_err(dev, "could not set codec driver clock params\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * mpc8610_hpcd_machine_remove: Remove the sound device
+ *
+ * This function is called to remove the sound device for one SSI.  We
+ * de-program the DMACR and PMUXCR register.
+ */
+static int mpc8610_hpcd_machine_remove(struct platform_device *sound_device)
+{
+	struct snd_soc_card *card = platform_get_drvdata(sound_device);
+	struct mpc8610_hpcd_data *machine_data =
+		container_of(card, struct mpc8610_hpcd_data, card);
+	struct ccsr_guts_86xx __iomem *guts;
+
+	guts = ioremap(guts_phys, sizeof(struct ccsr_guts_86xx));
+	if (!guts) {
+		dev_err(card->dev, "could not map global utilities\n");
+		return -ENOMEM;
+	}
+
+	/* Restore the signal routing */
+
+	guts_set_dmacr(guts, machine_data->dma_id[0],
+		       machine_data->dma_channel_id[0], 0);
+	guts_set_dmacr(guts, machine_data->dma_id[1],
+		       machine_data->dma_channel_id[1], 0);
+
+	switch (machine_data->ssi_id) {
+	case 0:
+		clrsetbits_be32(&guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
+		break;
+	case 1:
+		clrsetbits_be32(&guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA);
+		break;
+	}
+
+	iounmap(guts);
+
+	return 0;
+}
+
+/**
+ * mpc8610_hpcd_ops: ASoC machine driver operations
+ */
+static struct snd_soc_ops mpc8610_hpcd_ops = {
+	.startup = mpc8610_hpcd_startup,
+};
+
+/**
+ * get_node_by_phandle_name - get a node by its phandle name
+ *
+ * This function takes a node, the name of a property in that node, and a
+ * compatible string.  Assuming the property is a phandle to another node,
+ * it returns that node, (optionally) if that node is compatible.
+ *
+ * If the property is not a phandle, or the node it points to is not compatible
+ * with the specific string, then NULL is returned.
+ */
+static struct device_node *get_node_by_phandle_name(struct device_node *np,
+					       const char *name,
+					       const char *compatible)
+{
+	const phandle *ph;
+	int len;
+
+	ph = of_get_property(np, name, &len);
+	if (!ph || (len != sizeof(phandle)))
+		return NULL;
+
+	np = of_find_node_by_phandle(*ph);
+	if (!np)
+		return NULL;
+
+	if (compatible && !of_device_is_compatible(np, compatible)) {
+		of_node_put(np);
+		return NULL;
+	}
+
+	return np;
+}
+
+/**
+ * get_parent_cell_index -- return the cell-index of the parent of a node
+ *
+ * Return the value of the cell-index property of the parent of the given
+ * node.  This is used for DMA channel nodes that need to know the DMA ID
+ * of the controller they are on.
+ */
+static int get_parent_cell_index(struct device_node *np)
+{
+	struct device_node *parent = of_get_parent(np);
+	const u32 *iprop;
+
+	if (!parent)
+		return -1;
+
+	iprop = of_get_property(parent, "cell-index", NULL);
+	of_node_put(parent);
+
+	if (!iprop)
+		return -1;
+
+	return *iprop;
+}
+
+/**
+ * codec_node_dev_name - determine the dev_name for a codec node
+ *
+ * This function determines the dev_name for an I2C node.  This is the name
+ * that would be returned by dev_name() if this device_node were part of a
+ * 'struct device'  It's ugly and hackish, but it works.
+ *
+ * The dev_name for such devices include the bus number and I2C address. For
+ * example, "cs4270-codec.0-004f".
+ */
+static int codec_node_dev_name(struct device_node *np, char *buf, size_t len)
+{
+	const u32 *iprop;
+	int bus, addr;
+	char temp[DAI_NAME_SIZE];
+
+	of_modalias_node(np, temp, DAI_NAME_SIZE);
+
+	iprop = of_get_property(np, "reg", NULL);
+	if (!iprop)
+		return -EINVAL;
+
+	addr = *iprop;
+
+	bus = get_parent_cell_index(np);
+	if (bus < 0)
+		return bus;
+
+	snprintf(buf, len, "%s-codec.%u-%04x", temp, bus, addr);
+
+	return 0;
+}
+
+static int get_dma_channel(struct device_node *ssi_np,
+			   const char *compatible,
+			   struct snd_soc_dai_link *dai,
+			   unsigned int *dma_channel_id,
+			   unsigned int *dma_id)
+{
+	struct resource res;
+	struct device_node *dma_channel_np;
+	const u32 *iprop;
+	int ret;
+
+	dma_channel_np = get_node_by_phandle_name(ssi_np, compatible,
+						  "fsl,ssi-dma-channel");
+	if (!dma_channel_np)
+		return -EINVAL;
+
+	/* Determine the dev_name for the device_node.  This code mimics the
+	 * behavior of of_device_make_bus_id(). We need this because ASoC uses
+	 * the dev_name() of the device to match the platform (DMA) device with
+	 * the CPU (SSI) device.  It's all ugly and hackish, but it works (for
+	 * now).
+	 *
+	 * dai->platform name should already point to an allocated buffer.
+	 */
+	ret = of_address_to_resource(dma_channel_np, 0, &res);
+	if (ret)
+		return ret;
+	snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s",
+		 (unsigned long long) res.start, dma_channel_np->name);
+
+	iprop = of_get_property(dma_channel_np, "cell-index", NULL);
+	if (!iprop) {
+		of_node_put(dma_channel_np);
+		return -EINVAL;
+	}
+
+	*dma_channel_id = *iprop;
+	*dma_id = get_parent_cell_index(dma_channel_np);
+	of_node_put(dma_channel_np);
+
+	return 0;
+}
+
+/**
+ * mpc8610_hpcd_probe: platform probe function for the machine driver
+ *
+ * Although this is a machine driver, the SSI node is the "master" node with
+ * respect to audio hardware connections.  Therefore, we create a new ASoC
+ * device for each new SSI node that has a codec attached.
+ */
+static int mpc8610_hpcd_probe(struct platform_device *pdev)
+{
+	struct device *dev = pdev->dev.parent;
+	/* ssi_pdev is the platform device for the SSI node that probed us */
+	struct platform_device *ssi_pdev =
+		container_of(dev, struct platform_device, dev);
+	struct device_node *np = ssi_pdev->dev.of_node;
+	struct device_node *codec_np = NULL;
+	struct platform_device *sound_device = NULL;
+	struct mpc8610_hpcd_data *machine_data;
+	int ret = -ENODEV;
+	const char *sprop;
+	const u32 *iprop;
+
+	/* We are only interested in SSIs with a codec phandle in them,
+	 * so let's make sure this SSI has one. The MPC8610 HPCD only
+	 * knows about the CS4270 codec, so reject anything else.
+	 */
+	codec_np = get_node_by_phandle_name(np, "codec-handle",
+					    "cirrus,cs4270");
+	if (!codec_np) {
+		dev_err(dev, "invalid codec node\n");
+		return -EINVAL;
+	}
+
+	machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
+	if (!machine_data)
+		return -ENOMEM;
+
+	machine_data->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
+	machine_data->dai[0].ops = &mpc8610_hpcd_ops;
+
+	/* Determine the codec name, it will be used as the codec DAI name */
+	ret = codec_node_dev_name(codec_np, machine_data->codec_name,
+				  DAI_NAME_SIZE);
+	if (ret) {
+		dev_err(&pdev->dev, "invalid codec node %s\n",
+			codec_np->full_name);
+		ret = -EINVAL;
+		goto error;
+	}
+	machine_data->dai[0].codec_name = machine_data->codec_name;
+
+	/* The DAI name from the codec (snd_soc_dai_driver.name) */
+	machine_data->dai[0].codec_dai_name = "cs4270-hifi";
+
+	/* We register two DAIs per SSI, one for playback and the other for
+	 * capture.  Currently, we only support codecs that have one DAI for
+	 * both playback and capture.
+	 */
+	memcpy(&machine_data->dai[1], &machine_data->dai[0],
+	       sizeof(struct snd_soc_dai_link));
+
+	/* Get the device ID */
+	iprop = of_get_property(np, "cell-index", NULL);
+	if (!iprop) {
+		dev_err(&pdev->dev, "cell-index property not found\n");
+		ret = -EINVAL;
+		goto error;
+	}
+	machine_data->ssi_id = *iprop;
+
+	/* Get the serial format and clock direction. */
+	sprop = of_get_property(np, "fsl,mode", NULL);
+	if (!sprop) {
+		dev_err(&pdev->dev, "fsl,mode property not found\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (strcasecmp(sprop, "i2s-slave") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_I2S;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+
+		/* In i2s-slave mode, the codec has its own clock source, so we
+		 * need to get the frequency from the device tree and pass it to
+		 * the codec driver.
+		 */
+		iprop = of_get_property(codec_np, "clock-frequency", NULL);
+		if (!iprop || !*iprop) {
+			dev_err(&pdev->dev, "codec bus-frequency "
+				"property is missing or invalid\n");
+			ret = -EINVAL;
+			goto error;
+		}
+		machine_data->clk_frequency = *iprop;
+	} else if (strcasecmp(sprop, "i2s-master") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_I2S;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "lj-slave") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "lj-master") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "rj-slave") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "rj-master") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "ac97-slave") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_AC97;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "ac97-master") == 0) {
+		machine_data->dai_format = SND_SOC_DAIFMT_AC97;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else {
+		dev_err(&pdev->dev,
+			"unrecognized fsl,mode property '%s'\n", sprop);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (!machine_data->clk_frequency) {
+		dev_err(&pdev->dev, "unknown clock frequency\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/* Find the playback DMA channel to use. */
+	machine_data->dai[0].platform_name = machine_data->platform_name[0];
+	ret = get_dma_channel(np, "fsl,playback-dma", &machine_data->dai[0],
+			      &machine_data->dma_channel_id[0],
+			      &machine_data->dma_id[0]);
+	if (ret) {
+		dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n");
+		goto error;
+	}
+
+	/* Find the capture DMA channel to use. */
+	machine_data->dai[1].platform_name = machine_data->platform_name[1];
+	ret = get_dma_channel(np, "fsl,capture-dma", &machine_data->dai[1],
+			      &machine_data->dma_channel_id[1],
+			      &machine_data->dma_id[1]);
+	if (ret) {
+		dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
+		goto error;
+	}
+
+	/* Initialize our DAI data structure.  */
+	machine_data->dai[0].stream_name = "playback";
+	machine_data->dai[1].stream_name = "capture";
+	machine_data->dai[0].name = machine_data->dai[0].stream_name;
+	machine_data->dai[1].name = machine_data->dai[1].stream_name;
+
+	machine_data->card.probe = mpc8610_hpcd_machine_probe;
+	machine_data->card.remove = mpc8610_hpcd_machine_remove;
+	machine_data->card.name = pdev->name; /* The platform driver name */
+	machine_data->card.num_links = 2;
+	machine_data->card.dai_link = machine_data->dai;
+
+	/* Allocate a new audio platform device structure */
+	sound_device = platform_device_alloc("soc-audio", -1);
+	if (!sound_device) {
+		dev_err(&pdev->dev, "platform device alloc failed\n");
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	/* Associate the card data with the sound device */
+	platform_set_drvdata(sound_device, &machine_data->card);
+
+	/* Register with ASoC */
+	ret = platform_device_add(sound_device);
+	if (ret) {
+		dev_err(&pdev->dev, "platform device add failed\n");
+		goto error;
+	}
+	dev_set_drvdata(&pdev->dev, sound_device);
+
+	of_node_put(codec_np);
+
+	return 0;
+
+error:
+	of_node_put(codec_np);
+
+	if (sound_device)
+		platform_device_unregister(sound_device);
+
+	kfree(machine_data);
+
+	return ret;
+}
+
+/**
+ * mpc8610_hpcd_remove: remove the platform device
+ *
+ * This function is called when the platform device is removed.
+ */
+static int __devexit mpc8610_hpcd_remove(struct platform_device *pdev)
+{
+	struct platform_device *sound_device = dev_get_drvdata(&pdev->dev);
+	struct snd_soc_card *card = platform_get_drvdata(sound_device);
+	struct mpc8610_hpcd_data *machine_data =
+		container_of(card, struct mpc8610_hpcd_data, card);
+
+	platform_device_unregister(sound_device);
+
+	kfree(machine_data);
+	sound_device->dev.platform_data = NULL;
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver mpc8610_hpcd_driver = {
+	.probe = mpc8610_hpcd_probe,
+	.remove = __devexit_p(mpc8610_hpcd_remove),
+	.driver = {
+		/* The name must match the 'model' property in the device tree,
+		 * in lowercase letters.
+		 */
+		.name = "snd-soc-mpc8610hpcd",
+		.owner = THIS_MODULE,
+	},
+};
+
+/**
+ * mpc8610_hpcd_init: machine driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init mpc8610_hpcd_init(void)
+{
+	struct device_node *guts_np;
+	struct resource res;
+
+	pr_info("Freescale MPC8610 HPCD ALSA SoC machine driver\n");
+
+	/* Get the physical address of the global utilities registers */
+	guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
+	if (of_address_to_resource(guts_np, 0, &res)) {
+		pr_err("mpc8610-hpcd: missing/invalid global utilities node\n");
+		return -EINVAL;
+	}
+	guts_phys = res.start;
+
+	return platform_driver_register(&mpc8610_hpcd_driver);
+}
+
+/**
+ * mpc8610_hpcd_exit: machine driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit mpc8610_hpcd_exit(void)
+{
+	platform_driver_unregister(&mpc8610_hpcd_driver);
+}
+
+module_init(mpc8610_hpcd_init);
+module_exit(mpc8610_hpcd_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC machine driver");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/fsl/p1022_ds.c b/linux/sound/soc/fsl/p1022_ds.c
new file mode 100644
index 0000000..026b756
--- /dev/null
+++ b/linux/sound/soc/fsl/p1022_ds.c
@@ -0,0 +1,592 @@
+/**
+ * Freescale P1022DS ALSA SoC Machine driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <asm/fsl_guts.h>
+
+#include "fsl_dma.h"
+#include "fsl_ssi.h"
+
+/* P1022-specific PMUXCR and DMUXCR bit definitions */
+
+#define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK	0x0001c000
+#define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI	0x00010000
+#define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI		0x00018000
+
+#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK	0x00000c00
+#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI	0x00000000
+
+#define CCSR_GUTS_DMUXCR_PAD	1	/* DMA controller/channel set to pad */
+#define CCSR_GUTS_DMUXCR_SSI	2	/* DMA controller/channel set to SSI */
+
+/*
+ * Set the DMACR register in the GUTS
+ *
+ * The DMACR register determines the source of initiated transfers for each
+ * channel on each DMA controller.  Rather than have a bunch of repetitive
+ * macros for the bit patterns, we just have a function that calculates
+ * them.
+ *
+ * guts: Pointer to GUTS structure
+ * co: The DMA controller (0 or 1)
+ * ch: The channel on the DMA controller (0, 1, 2, or 3)
+ * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx)
+ */
+static inline void guts_set_dmuxcr(struct ccsr_guts_85xx __iomem *guts,
+	unsigned int co, unsigned int ch, unsigned int device)
+{
+	unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch));
+
+	clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift);
+}
+
+/* There's only one global utilities register */
+static phys_addr_t guts_phys;
+
+#define DAI_NAME_SIZE	32
+
+/**
+ * machine_data: machine-specific ASoC device data
+ *
+ * This structure contains data for a single sound platform device on an
+ * P1022 DS.  Some of the data is taken from the device tree.
+ */
+struct machine_data {
+	struct snd_soc_dai_link dai[2];
+	struct snd_soc_card card;
+	unsigned int dai_format;
+	unsigned int codec_clk_direction;
+	unsigned int cpu_clk_direction;
+	unsigned int clk_frequency;
+	unsigned int ssi_id;		/* 0 = SSI1, 1 = SSI2, etc */
+	unsigned int dma_id[2];		/* 0 = DMA1, 1 = DMA2, etc */
+	unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
+	char codec_name[DAI_NAME_SIZE];
+	char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
+};
+
+/**
+ * p1022_ds_machine_probe: initialize the board
+ *
+ * This function is used to initialize the board-specific hardware.
+ *
+ * Here we program the DMACR and PMUXCR registers.
+ */
+static int p1022_ds_machine_probe(struct platform_device *sound_device)
+{
+	struct snd_soc_card *card = platform_get_drvdata(sound_device);
+	struct machine_data *mdata =
+		container_of(card, struct machine_data, card);
+	struct ccsr_guts_85xx __iomem *guts;
+
+	guts = ioremap(guts_phys, sizeof(struct ccsr_guts_85xx));
+	if (!guts) {
+		dev_err(card->dev, "could not map global utilities\n");
+		return -ENOMEM;
+	}
+
+	/* Enable SSI Tx signal */
+	clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK,
+			CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI);
+
+	/* Enable SSI Rx signal */
+	clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK,
+			CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI);
+
+	/* Enable DMA Channel for SSI */
+	guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0],
+			CCSR_GUTS_DMUXCR_SSI);
+
+	guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1],
+			CCSR_GUTS_DMUXCR_SSI);
+
+	iounmap(guts);
+
+	return 0;
+}
+
+/**
+ * p1022_ds_startup: program the board with various hardware parameters
+ *
+ * This function takes board-specific information, like clock frequencies
+ * and serial data formats, and passes that information to the codec and
+ * transport drivers.
+ */
+static int p1022_ds_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct machine_data *mdata =
+		container_of(rtd->card, struct machine_data, card);
+	struct device *dev = rtd->card->dev;
+	int ret = 0;
+
+	/* Tell the codec driver what the serial protocol is. */
+	ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format);
+	if (ret < 0) {
+		dev_err(dev, "could not set codec driver audio format\n");
+		return ret;
+	}
+
+	/*
+	 * Tell the codec driver what the MCLK frequency is, and whether it's
+	 * a slave or master.
+	 */
+	ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, mdata->clk_frequency,
+				     mdata->codec_clk_direction);
+	if (ret < 0) {
+		dev_err(dev, "could not set codec driver clock params\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * p1022_ds_machine_remove: Remove the sound device
+ *
+ * This function is called to remove the sound device for one SSI.  We
+ * de-program the DMACR and PMUXCR register.
+ */
+static int p1022_ds_machine_remove(struct platform_device *sound_device)
+{
+	struct snd_soc_card *card = platform_get_drvdata(sound_device);
+	struct machine_data *mdata =
+		container_of(card, struct machine_data, card);
+	struct ccsr_guts_85xx __iomem *guts;
+
+	guts = ioremap(guts_phys, sizeof(struct ccsr_guts_85xx));
+	if (!guts) {
+		dev_err(card->dev, "could not map global utilities\n");
+		return -ENOMEM;
+	}
+
+	/* Restore the signal routing */
+	clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK);
+	clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK);
+	guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0);
+	guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0);
+
+	iounmap(guts);
+
+	return 0;
+}
+
+/**
+ * p1022_ds_ops: ASoC machine driver operations
+ */
+static struct snd_soc_ops p1022_ds_ops = {
+	.startup = p1022_ds_startup,
+};
+
+/**
+ * get_node_by_phandle_name - get a node by its phandle name
+ *
+ * This function takes a node, the name of a property in that node, and a
+ * compatible string.  Assuming the property is a phandle to another node,
+ * it returns that node, (optionally) if that node is compatible.
+ *
+ * If the property is not a phandle, or the node it points to is not compatible
+ * with the specific string, then NULL is returned.
+ */
+static struct device_node *get_node_by_phandle_name(struct device_node *np,
+	const char *name, const char *compatible)
+{
+	np = of_parse_phandle(np, name, 0);
+	if (!np)
+		return NULL;
+
+	if (!of_device_is_compatible(np, compatible)) {
+		of_node_put(np);
+		return NULL;
+	}
+
+	return np;
+}
+
+/**
+ * get_parent_cell_index -- return the cell-index of the parent of a node
+ *
+ * Return the value of the cell-index property of the parent of the given
+ * node.  This is used for DMA channel nodes that need to know the DMA ID
+ * of the controller they are on.
+ */
+static int get_parent_cell_index(struct device_node *np)
+{
+	struct device_node *parent = of_get_parent(np);
+	const u32 *iprop;
+	int ret = -1;
+
+	if (!parent)
+		return -1;
+
+	iprop = of_get_property(parent, "cell-index", NULL);
+	if (iprop)
+		ret = *iprop;
+
+	of_node_put(parent);
+
+	return ret;
+}
+
+/**
+ * codec_node_dev_name - determine the dev_name for a codec node
+ *
+ * This function determines the dev_name for an I2C node.  This is the name
+ * that would be returned by dev_name() if this device_node were part of a
+ * 'struct device'  It's ugly and hackish, but it works.
+ *
+ * The dev_name for such devices include the bus number and I2C address. For
+ * example, "cs4270-codec.0-004f".
+ */
+static int codec_node_dev_name(struct device_node *np, char *buf, size_t len)
+{
+	const u32 *iprop;
+	int bus, addr;
+	char temp[DAI_NAME_SIZE];
+
+	of_modalias_node(np, temp, DAI_NAME_SIZE);
+
+	iprop = of_get_property(np, "reg", NULL);
+	if (!iprop)
+		return -EINVAL;
+
+	addr = *iprop;
+
+	bus = get_parent_cell_index(np);
+	if (bus < 0)
+		return bus;
+
+	snprintf(buf, len, "%s-codec.%u-%04x", temp, bus, addr);
+
+	return 0;
+}
+
+static int get_dma_channel(struct device_node *ssi_np,
+			   const char *compatible,
+			   struct snd_soc_dai_link *dai,
+			   unsigned int *dma_channel_id,
+			   unsigned int *dma_id)
+{
+	struct resource res;
+	struct device_node *dma_channel_np;
+	const u32 *iprop;
+	int ret;
+
+	dma_channel_np = get_node_by_phandle_name(ssi_np, compatible,
+						  "fsl,ssi-dma-channel");
+	if (!dma_channel_np)
+		return -EINVAL;
+
+	/* Determine the dev_name for the device_node.  This code mimics the
+	 * behavior of of_device_make_bus_id(). We need this because ASoC uses
+	 * the dev_name() of the device to match the platform (DMA) device with
+	 * the CPU (SSI) device.  It's all ugly and hackish, but it works (for
+	 * now).
+	 *
+	 * dai->platform name should already point to an allocated buffer.
+	 */
+	ret = of_address_to_resource(dma_channel_np, 0, &res);
+	if (ret)
+		return ret;
+	snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s",
+		 (unsigned long long) res.start, dma_channel_np->name);
+
+	iprop = of_get_property(dma_channel_np, "cell-index", NULL);
+	if (!iprop) {
+		of_node_put(dma_channel_np);
+		return -EINVAL;
+	}
+
+	*dma_channel_id = *iprop;
+	*dma_id = get_parent_cell_index(dma_channel_np);
+	of_node_put(dma_channel_np);
+
+	return 0;
+}
+
+/**
+ * p1022_ds_probe: platform probe function for the machine driver
+ *
+ * Although this is a machine driver, the SSI node is the "master" node with
+ * respect to audio hardware connections.  Therefore, we create a new ASoC
+ * device for each new SSI node that has a codec attached.
+ */
+static int p1022_ds_probe(struct platform_device *pdev)
+{
+	struct device *dev = pdev->dev.parent;
+	/* ssi_pdev is the platform device for the SSI node that probed us */
+	struct platform_device *ssi_pdev =
+		container_of(dev, struct platform_device, dev);
+	struct device_node *np = ssi_pdev->dev.of_node;
+	struct device_node *codec_np = NULL;
+	struct platform_device *sound_device = NULL;
+	struct machine_data *mdata;
+	int ret = -ENODEV;
+	const char *sprop;
+	const u32 *iprop;
+
+	/* Find the codec node for this SSI. */
+	codec_np = of_parse_phandle(np, "codec-handle", 0);
+	if (!codec_np) {
+		dev_err(dev, "could not find codec node\n");
+		return -EINVAL;
+	}
+
+	mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL);
+	if (!mdata) {
+		ret = -ENOMEM;
+		goto error_put;
+	}
+
+	mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
+	mdata->dai[0].ops = &p1022_ds_ops;
+
+	/* Determine the codec name, it will be used as the codec DAI name */
+	ret = codec_node_dev_name(codec_np, mdata->codec_name, DAI_NAME_SIZE);
+	if (ret) {
+		dev_err(&pdev->dev, "invalid codec node %s\n",
+			codec_np->full_name);
+		ret = -EINVAL;
+		goto error;
+	}
+	mdata->dai[0].codec_name = mdata->codec_name;
+
+	/* We register two DAIs per SSI, one for playback and the other for
+	 * capture.  We support codecs that have separate DAIs for both playback
+	 * and capture.
+	 */
+	memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link));
+
+	/* The DAI names from the codec (snd_soc_dai_driver.name) */
+	mdata->dai[0].codec_dai_name = "wm8776-hifi-playback";
+	mdata->dai[1].codec_dai_name = "wm8776-hifi-capture";
+
+	/* Get the device ID */
+	iprop = of_get_property(np, "cell-index", NULL);
+	if (!iprop) {
+		dev_err(&pdev->dev, "cell-index property not found\n");
+		ret = -EINVAL;
+		goto error;
+	}
+	mdata->ssi_id = *iprop;
+
+	/* Get the serial format and clock direction. */
+	sprop = of_get_property(np, "fsl,mode", NULL);
+	if (!sprop) {
+		dev_err(&pdev->dev, "fsl,mode property not found\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (strcasecmp(sprop, "i2s-slave") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_I2S;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+
+		/* In i2s-slave mode, the codec has its own clock source, so we
+		 * need to get the frequency from the device tree and pass it to
+		 * the codec driver.
+		 */
+		iprop = of_get_property(codec_np, "clock-frequency", NULL);
+		if (!iprop || !*iprop) {
+			dev_err(&pdev->dev, "codec bus-frequency "
+				"property is missing or invalid\n");
+			ret = -EINVAL;
+			goto error;
+		}
+		mdata->clk_frequency = *iprop;
+	} else if (strcasecmp(sprop, "i2s-master") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_I2S;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "lj-slave") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_LEFT_J;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "lj-master") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_LEFT_J;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "rj-slave") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "rj-master") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_RIGHT_J;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "ac97-slave") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_AC97;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "ac97-master") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_AC97;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else {
+		dev_err(&pdev->dev,
+			"unrecognized fsl,mode property '%s'\n", sprop);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (!mdata->clk_frequency) {
+		dev_err(&pdev->dev, "unknown clock frequency\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/* Find the playback DMA channel to use. */
+	mdata->dai[0].platform_name = mdata->platform_name[0];
+	ret = get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0],
+			      &mdata->dma_channel_id[0],
+			      &mdata->dma_id[0]);
+	if (ret) {
+		dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n");
+		goto error;
+	}
+
+	/* Find the capture DMA channel to use. */
+	mdata->dai[1].platform_name = mdata->platform_name[1];
+	ret = get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1],
+			      &mdata->dma_channel_id[1],
+			      &mdata->dma_id[1]);
+	if (ret) {
+		dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
+		goto error;
+	}
+
+	/* Initialize our DAI data structure.  */
+	mdata->dai[0].stream_name = "playback";
+	mdata->dai[1].stream_name = "capture";
+	mdata->dai[0].name = mdata->dai[0].stream_name;
+	mdata->dai[1].name = mdata->dai[1].stream_name;
+
+	mdata->card.probe = p1022_ds_machine_probe;
+	mdata->card.remove = p1022_ds_machine_remove;
+	mdata->card.name = pdev->name; /* The platform driver name */
+	mdata->card.num_links = 2;
+	mdata->card.dai_link = mdata->dai;
+
+	/* Allocate a new audio platform device structure */
+	sound_device = platform_device_alloc("soc-audio", -1);
+	if (!sound_device) {
+		dev_err(&pdev->dev, "platform device alloc failed\n");
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	/* Associate the card data with the sound device */
+	platform_set_drvdata(sound_device, &mdata->card);
+
+	/* Register with ASoC */
+	ret = platform_device_add(sound_device);
+	if (ret) {
+		dev_err(&pdev->dev, "platform device add failed\n");
+		goto error;
+	}
+	dev_set_drvdata(&pdev->dev, sound_device);
+
+	of_node_put(codec_np);
+
+	return 0;
+
+error:
+	if (sound_device)
+		platform_device_unregister(sound_device);
+
+	kfree(mdata);
+error_put:
+	of_node_put(codec_np);
+	return ret;
+}
+
+/**
+ * p1022_ds_remove: remove the platform device
+ *
+ * This function is called when the platform device is removed.
+ */
+static int __devexit p1022_ds_remove(struct platform_device *pdev)
+{
+	struct platform_device *sound_device = dev_get_drvdata(&pdev->dev);
+	struct snd_soc_card *card = platform_get_drvdata(sound_device);
+	struct machine_data *mdata =
+		container_of(card, struct machine_data, card);
+
+	platform_device_unregister(sound_device);
+
+	kfree(mdata);
+	sound_device->dev.platform_data = NULL;
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver p1022_ds_driver = {
+	.probe = p1022_ds_probe,
+	.remove = __devexit_p(p1022_ds_remove),
+	.driver = {
+		/* The name must match the 'model' property in the device tree,
+		 * in lowercase letters, but only the part after that last
+		 * comma.  This is because some model properties have a "fsl,"
+		 * prefix.
+		 */
+		.name = "snd-soc-p1022",
+		.owner = THIS_MODULE,
+	},
+};
+
+/**
+ * p1022_ds_init: machine driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init p1022_ds_init(void)
+{
+	struct device_node *guts_np;
+	struct resource res;
+
+	pr_info("Freescale P1022 DS ALSA SoC machine driver\n");
+
+	/* Get the physical address of the global utilities registers */
+	guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts");
+	if (of_address_to_resource(guts_np, 0, &res)) {
+		pr_err("p1022-ds: missing/invalid global utilities node\n");
+		return -EINVAL;
+	}
+	guts_phys = res.start;
+	of_node_put(guts_np);
+
+	return platform_driver_register(&p1022_ds_driver);
+}
+
+/**
+ * p1022_ds_exit: machine driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit p1022_ds_exit(void)
+{
+	platform_driver_unregister(&p1022_ds_driver);
+}
+
+module_init(p1022_ds_init);
+module_exit(p1022_ds_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/fsl/pcm030-audio-fabric.c b/linux/sound/soc/fsl/pcm030-audio-fabric.c
new file mode 100644
index 0000000..ba4d85e
--- /dev/null
+++ b/linux/sound/soc/fsl/pcm030-audio-fabric.c
@@ -0,0 +1,91 @@
+/*
+ * Phytec pcm030 driver for the PSC of the Freescale MPC52xx
+ * configured as AC97 interface
+ *
+ * Copyright 2008 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "mpc5200_dma.h"
+#include "mpc5200_psc_ac97.h"
+#include "../codecs/wm9712.h"
+
+#define DRV_NAME "pcm030-audio-fabric"
+
+static struct snd_soc_card card;
+
+static struct snd_soc_dai_link pcm030_fabric_dai[] = {
+{
+	.name = "AC97",
+	.stream_name = "AC97 Analog",
+	.codec_dai_name = "wm9712-hifi",
+	.cpu_dai_name = "mpc5200-psc-ac97.0",
+	.platform_name = "mpc5200-pcm-audio",
+	.codec_name = "wm9712-codec",
+},
+{
+	.name = "AC97",
+	.stream_name = "AC97 IEC958",
+	.codec_dai_name = "wm9712-aux",
+	.cpu_dai_name = "mpc5200-psc-ac97.1",
+	.platform_name = "mpc5200-pcm-audio",
+	.codec_name = "wm9712-codec",
+},
+};
+
+static __init int pcm030_fabric_init(void)
+{
+	struct platform_device *pdev;
+	int rc;
+
+	if (!of_machine_is_compatible("phytec,pcm030"))
+		return -ENODEV;
+
+
+	card.name = "pcm030";
+	card.dai_link = pcm030_fabric_dai;
+	card.num_links = ARRAY_SIZE(pcm030_fabric_dai);
+
+	pdev = platform_device_alloc("soc-audio", 1);
+	if (!pdev) {
+		pr_err("pcm030_fabric_init: platform_device_alloc() failed\n");
+		return -ENODEV;
+	}
+
+	platform_set_drvdata(pdev, &card);
+
+	rc = platform_device_add(pdev);
+	if (rc) {
+		pr_err("pcm030_fabric_init: platform_device_add() failed\n");
+		platform_device_put(pdev);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+module_init(pcm030_fabric_init);
+
+
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_DESCRIPTION(DRV_NAME ": mpc5200 pcm030 fabric driver");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/imx/Kconfig b/linux/sound/soc/imx/Kconfig
new file mode 100644
index 0000000..642270a
--- /dev/null
+++ b/linux/sound/soc/imx/Kconfig
@@ -0,0 +1,55 @@
+menuconfig SND_IMX_SOC
+	tristate "SoC Audio for Freescale i.MX CPUs"
+	depends on ARCH_MXC
+	select SND_PCM
+	select FIQ
+	select SND_SOC_AC97_BUS
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the i.MX SSI interface.
+
+
+if SND_IMX_SOC
+
+config SND_MXC_SOC_SSI
+	tristate
+
+config SND_MXC_SOC_FIQ
+	tristate
+
+config SND_MXC_SOC_MX2
+	tristate
+
+config SND_MXC_SOC_WM1133_EV1
+	tristate "Audio on the the i.MX31ADS with WM1133-EV1 fitted"
+	depends on MACH_MX31ADS_WM1133_EV1 && EXPERIMENTAL
+	select SND_SOC_WM8350
+	select SND_MXC_SOC_SSI
+	select SND_MXC_SOC_FIQ
+	help
+	  Enable support for audio on the i.MX31ADS with the WM1133-EV1
+	  PMIC board with WM8835x fitted.
+
+config SND_SOC_PHYCORE_AC97
+	tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards"
+	depends on MACH_PCM043 || MACH_PCA100
+	select SND_SOC_WM9712
+	select SND_MXC_SOC_SSI
+	select SND_MXC_SOC_FIQ
+	help
+	  Say Y if you want to add support for SoC audio on Phytec phyCORE
+	  and phyCARD boards in AC97 mode
+
+config SND_SOC_EUKREA_TLV320
+	tristate "Eukrea TLV320"
+	depends on MACH_EUKREA_MBIMX27_BASEBOARD \
+		|| MACH_EUKREA_MBIMXSD25_BASEBOARD \
+		|| MACH_EUKREA_MBIMXSD35_BASEBOARD
+	select SND_SOC_TLV320AIC23
+	select SND_MXC_SOC_SSI
+	select SND_MXC_SOC_FIQ
+	help
+	  Enable I2S based access to the TLV320AIC23B codec attached
+	  to the SSI interface
+
+endif	# SND_IMX_SOC
diff --git a/linux/sound/soc/imx/Makefile b/linux/sound/soc/imx/Makefile
new file mode 100644
index 0000000..b67fc02
--- /dev/null
+++ b/linux/sound/soc/imx/Makefile
@@ -0,0 +1,17 @@
+# i.MX Platform Support
+snd-soc-imx-objs := imx-ssi.o
+snd-soc-imx-fiq-objs := imx-pcm-fiq.o
+snd-soc-imx-mx2-objs := imx-pcm-dma-mx2.o
+
+obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o
+obj-$(CONFIG_SND_MXC_SOC_FIQ) += snd-soc-imx-fiq.o
+obj-$(CONFIG_SND_MXC_SOC_MX2) += snd-soc-imx-mx2.o
+
+# i.MX Machine Support
+snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
+snd-soc-phycore-ac97-objs := phycore-ac97.o
+snd-soc-wm1133-ev1-objs := wm1133-ev1.o
+
+obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
+obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
+obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
diff --git a/linux/sound/soc/imx/eukrea-tlv320.c b/linux/sound/soc/imx/eukrea-tlv320.c
new file mode 100644
index 0000000..dd4fffd
--- /dev/null
+++ b/linux/sound/soc/imx/eukrea-tlv320.c
@@ -0,0 +1,131 @@
+/*
+ * eukrea-tlv320.c  --  SoC audio for eukrea_cpuimxXX in I2S mode
+ *
+ * Copyright 2010 Eric Bénard, Eukréa Electromatique <eric@eukrea.com>
+ *
+ * based on sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c
+ * which is Copyright 2009 Simtec Electronics
+ * and on sound/soc/imx/phycore-ac97.c which is
+ * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ * 
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-types.h>
+
+#include "../codecs/tlv320aic23.h"
+#include "imx-ssi.h"
+
+#define CODEC_CLOCK 12000000
+
+static int eukrea_tlv320_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret) {
+		pr_err("%s: failed set cpu dai format\n", __func__);
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret) {
+		pr_err("%s: failed set codec dai format\n", __func__);
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+				     CODEC_CLOCK, SND_SOC_CLOCK_OUT);
+	if (ret) {
+		pr_err("%s: failed setting codec sysclk\n", __func__);
+		return ret;
+	}
+	snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0);
+
+	ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
+				SND_SOC_CLOCK_IN);
+	if (ret) {
+		pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops eukrea_tlv320_snd_ops = {
+	.hw_params	= eukrea_tlv320_hw_params,
+};
+
+static struct snd_soc_dai_link eukrea_tlv320_dai = {
+	.name		= "tlv320aic23",
+	.stream_name	= "TLV320AIC23",
+	.codec_dai_name	= "tlv320aic23-hifi",
+	.platform_name	= "imx-pcm-audio.0",
+	.codec_name	= "tlv320aic23-codec.0-001a",
+	.cpu_dai_name	= "imx-ssi.0",
+	.ops		= &eukrea_tlv320_snd_ops,
+};
+
+static struct snd_soc_card eukrea_tlv320 = {
+	.name		= "cpuimx-audio",
+	.dai_link	= &eukrea_tlv320_dai,
+	.num_links	= 1,
+};
+
+static struct platform_device *eukrea_tlv320_snd_device;
+
+static int __init eukrea_tlv320_init(void)
+{
+	int ret;
+
+	if (!machine_is_eukrea_cpuimx27() && !machine_is_eukrea_cpuimx25sd()
+		&& !machine_is_eukrea_cpuimx35sd())
+		/* return happy. We might run on a totally different machine */
+		return 0;
+
+	eukrea_tlv320_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!eukrea_tlv320_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(eukrea_tlv320_snd_device, &eukrea_tlv320);
+	ret = platform_device_add(eukrea_tlv320_snd_device);
+
+	if (ret) {
+		printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+		platform_device_put(eukrea_tlv320_snd_device);
+	}
+
+	return ret;
+}
+
+static void __exit eukrea_tlv320_exit(void)
+{
+	platform_device_unregister(eukrea_tlv320_snd_device);
+}
+
+module_init(eukrea_tlv320_init);
+module_exit(eukrea_tlv320_exit);
+
+MODULE_AUTHOR("Eric Bénard <eric@eukrea.com>");
+MODULE_DESCRIPTION("CPUIMX ALSA SoC driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/imx/imx-pcm-dma-mx2.c b/linux/sound/soc/imx/imx-pcm-dma-mx2.c
new file mode 100644
index 0000000..671ef8d
--- /dev/null
+++ b/linux/sound/soc/imx/imx-pcm-dma-mx2.c
@@ -0,0 +1,334 @@
+/*
+ * imx-pcm-dma-mx2.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+
+#include "imx-ssi.h"
+
+struct imx_pcm_runtime_data {
+	int period_bytes;
+	int periods;
+	int dma;
+	unsigned long offset;
+	unsigned long size;
+	void *buf;
+	int period_time;
+	struct dma_async_tx_descriptor *desc;
+	struct dma_chan *dma_chan;
+	struct imx_dma_data dma_data;
+};
+
+static void audio_dma_irq(void *data)
+{
+	struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	iprtd->offset += iprtd->period_bytes;
+	iprtd->offset %= iprtd->period_bytes * iprtd->periods;
+
+	snd_pcm_period_elapsed(substream);
+}
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+	struct imx_pcm_runtime_data *iprtd = param;
+
+	if (!imx_dma_is_general_purpose(chan))
+		return false;
+
+        chan->private = &iprtd->dma_data;
+
+        return true;
+}
+
+static int imx_ssi_dma_alloc(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct imx_pcm_dma_params *dma_params;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+	struct dma_slave_config slave_config;
+	dma_cap_mask_t mask;
+	enum dma_slave_buswidth buswidth;
+	int ret;
+
+	dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+	iprtd->dma_data.peripheral_type = IMX_DMATYPE_SSI;
+	iprtd->dma_data.priority = DMA_PRIO_HIGH;
+	iprtd->dma_data.dma_request = dma_params->dma;
+
+	/* Try to grab a DMA channel */
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+	iprtd->dma_chan = dma_request_channel(mask, filter, iprtd);
+	if (!iprtd->dma_chan)
+		return -EINVAL;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+	case SNDRV_PCM_FORMAT_S24_LE:
+		buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		break;
+	default:
+		return 0;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		slave_config.direction = DMA_TO_DEVICE;
+		slave_config.dst_addr = dma_params->dma_addr;
+		slave_config.dst_addr_width = buswidth;
+		slave_config.dst_maxburst = dma_params->burstsize;
+	} else {
+		slave_config.direction = DMA_FROM_DEVICE;
+		slave_config.src_addr = dma_params->dma_addr;
+		slave_config.src_addr_width = buswidth;
+		slave_config.src_maxburst = dma_params->burstsize;
+	}
+
+	ret = dmaengine_slave_config(iprtd->dma_chan, &slave_config);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+	unsigned long dma_addr;
+	struct dma_chan *chan;
+	struct imx_pcm_dma_params *dma_params;
+	int ret;
+
+	dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+	ret = imx_ssi_dma_alloc(substream, params);
+	if (ret)
+		return ret;
+	chan = iprtd->dma_chan;
+
+	iprtd->size = params_buffer_bytes(params);
+	iprtd->periods = params_periods(params);
+	iprtd->period_bytes = params_period_bytes(params);
+	iprtd->offset = 0;
+	iprtd->period_time = HZ / (params_rate(params) /
+			params_period_size(params));
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+	dma_addr = runtime->dma_addr;
+
+	iprtd->buf = (unsigned int *)substream->dma_buffer.area;
+
+	iprtd->desc = chan->device->device_prep_dma_cyclic(chan, dma_addr,
+			iprtd->period_bytes * iprtd->periods,
+			iprtd->period_bytes,
+			substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+			DMA_TO_DEVICE : DMA_FROM_DEVICE);
+	if (!iprtd->desc) {
+		dev_err(&chan->dev->device, "cannot prepare slave dma\n");
+		return -EINVAL;
+	}
+
+	iprtd->desc->callback = audio_dma_irq;
+	iprtd->desc->callback_param = substream;
+
+	return 0;
+}
+
+static int snd_imx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	if (iprtd->dma_chan) {
+		dma_release_channel(iprtd->dma_chan);
+		iprtd->dma_chan = NULL;
+	}
+
+	return 0;
+}
+
+static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct imx_pcm_dma_params *dma_params;
+
+	dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+	return 0;
+}
+
+static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		dmaengine_submit(iprtd->desc);
+
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		dmaengine_terminate_all(iprtd->dma_chan);
+
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	pr_debug("%s: %ld %ld\n", __func__, iprtd->offset,
+			bytes_to_frames(substream->runtime, iprtd->offset));
+
+	return bytes_to_frames(substream->runtime, iprtd->offset);
+}
+
+static struct snd_pcm_hardware snd_imx_hardware = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_RESUME,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rate_min = 8000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
+	.period_bytes_min = 128,
+	.period_bytes_max = 65535, /* Limited by SDMA engine */
+	.periods_min = 2,
+	.periods_max = 255,
+	.fifo_size = 0,
+};
+
+static int snd_imx_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd;
+	int ret;
+
+	iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
+	if (iprtd == NULL)
+		return -ENOMEM;
+	runtime->private_data = iprtd;
+
+	ret = snd_pcm_hw_constraint_integer(substream->runtime,
+			SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0) {
+		kfree(iprtd);
+		return ret;
+	}
+
+	snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+
+	return 0;
+}
+
+static int snd_imx_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	kfree(iprtd);
+
+	return 0;
+}
+
+static struct snd_pcm_ops imx_pcm_ops = {
+	.open		= snd_imx_open,
+	.close		= snd_imx_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= snd_imx_pcm_hw_params,
+	.hw_free	= snd_imx_pcm_hw_free,
+	.prepare	= snd_imx_pcm_prepare,
+	.trigger	= snd_imx_pcm_trigger,
+	.pointer	= snd_imx_pcm_pointer,
+	.mmap		= snd_imx_pcm_mmap,
+};
+
+static struct snd_soc_platform_driver imx_soc_platform_mx2 = {
+	.ops		= &imx_pcm_ops,
+	.pcm_new	= imx_pcm_new,
+	.pcm_free	= imx_pcm_free,
+};
+
+static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &imx_soc_platform_mx2);
+}
+
+static int __devexit imx_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver imx_pcm_driver = {
+	.driver = {
+			.name = "imx-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+	.probe = imx_soc_platform_probe,
+	.remove = __devexit_p(imx_soc_platform_remove),
+};
+
+static int __init snd_imx_pcm_init(void)
+{
+	return platform_driver_register(&imx_pcm_driver);
+}
+module_init(snd_imx_pcm_init);
+
+static void __exit snd_imx_pcm_exit(void)
+{
+	platform_driver_unregister(&imx_pcm_driver);
+}
+module_exit(snd_imx_pcm_exit);
diff --git a/linux/sound/soc/imx/imx-pcm-fiq.c b/linux/sound/soc/imx/imx-pcm-fiq.c
new file mode 100644
index 0000000..413b78d
--- /dev/null
+++ b/linux/sound/soc/imx/imx-pcm-fiq.c
@@ -0,0 +1,345 @@
+/*
+ * imx-pcm-fiq.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/fiq.h>
+
+#include <mach/ssi.h>
+
+#include "imx-ssi.h"
+
+struct imx_pcm_runtime_data {
+	int period;
+	int periods;
+	unsigned long offset;
+	unsigned long last_offset;
+	unsigned long size;
+	struct hrtimer hrt;
+	int poll_time_ns;
+	struct snd_pcm_substream *substream;
+	atomic_t running;
+};
+
+static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
+{
+	struct imx_pcm_runtime_data *iprtd =
+		container_of(hrt, struct imx_pcm_runtime_data, hrt);
+	struct snd_pcm_substream *substream = iprtd->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pt_regs regs;
+	unsigned long delta;
+
+	if (!atomic_read(&iprtd->running))
+		return HRTIMER_NORESTART;
+
+	get_fiq_regs(&regs);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		iprtd->offset = regs.ARM_r8 & 0xffff;
+	else
+		iprtd->offset = regs.ARM_r9 & 0xffff;
+
+	/* How much data have we transferred since the last period report? */
+	if (iprtd->offset >= iprtd->last_offset)
+		delta = iprtd->offset - iprtd->last_offset;
+	else
+		delta = runtime->buffer_size + iprtd->offset
+			- iprtd->last_offset;
+
+	/* If we've transferred at least a period then report it and
+	 * reset our poll time */
+	if (delta >= iprtd->period) {
+		snd_pcm_period_elapsed(substream);
+		iprtd->last_offset = iprtd->offset;
+	}
+
+	hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns));
+
+	return HRTIMER_RESTART;
+}
+
+static struct fiq_handler fh = {
+	.name		= DRV_NAME,
+};
+
+static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	iprtd->size = params_buffer_bytes(params);
+	iprtd->periods = params_periods(params);
+	iprtd->period = params_period_bytes(params) ;
+	iprtd->offset = 0;
+	iprtd->last_offset = 0;
+	iprtd->poll_time_ns = 1000000000 / params_rate(params) *
+				params_period_size(params);
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+	return 0;
+}
+
+static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+	struct pt_regs regs;
+
+	get_fiq_regs(&regs);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16;
+	else
+		regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16;
+
+	set_fiq_regs(&regs);
+
+	return 0;
+}
+
+static int fiq_enable;
+static int imx_pcm_fiq;
+
+static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		atomic_set(&iprtd->running, 1);
+		hrtimer_start(&iprtd->hrt, ns_to_ktime(iprtd->poll_time_ns),
+		      HRTIMER_MODE_REL);
+		if (++fiq_enable == 1)
+			enable_fiq(imx_pcm_fiq);
+
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		atomic_set(&iprtd->running, 0);
+
+		if (--fiq_enable == 0)
+			disable_fiq(imx_pcm_fiq);
+
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	return bytes_to_frames(substream->runtime, iprtd->offset);
+}
+
+static struct snd_pcm_hardware snd_imx_hardware = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_RESUME,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rate_min = 8000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
+	.period_bytes_min = 128,
+	.period_bytes_max = 16 * 1024,
+	.periods_min = 4,
+	.periods_max = 255,
+	.fifo_size = 0,
+};
+
+static int snd_imx_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd;
+	int ret;
+
+	iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
+	if (iprtd == NULL)
+		return -ENOMEM;
+	runtime->private_data = iprtd;
+
+	iprtd->substream = substream;
+
+	atomic_set(&iprtd->running, 0);
+	hrtimer_init(&iprtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	iprtd->hrt.function = snd_hrtimer_callback;
+
+	ret = snd_pcm_hw_constraint_integer(substream->runtime,
+			SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0) {
+		kfree(iprtd);
+		return ret;
+	}
+
+	snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+	return 0;
+}
+
+static int snd_imx_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	hrtimer_cancel(&iprtd->hrt);
+
+	kfree(iprtd);
+
+	return 0;
+}
+
+static struct snd_pcm_ops imx_pcm_ops = {
+	.open		= snd_imx_open,
+	.close		= snd_imx_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= snd_imx_pcm_hw_params,
+	.prepare	= snd_imx_pcm_prepare,
+	.trigger	= snd_imx_pcm_trigger,
+	.pointer	= snd_imx_pcm_pointer,
+	.mmap		= snd_imx_pcm_mmap,
+};
+
+static int ssi_irq = 0;
+
+static int imx_pcm_fiq_new(struct snd_card *card, struct snd_soc_dai *dai,
+	struct snd_pcm *pcm)
+{
+	int ret;
+
+	ret = imx_pcm_new(card, dai, pcm);
+	if (ret)
+		return ret;
+
+	if (dai->driver->playback.channels_min) {
+		struct snd_pcm_substream *substream =
+			pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+		struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+		imx_ssi_fiq_tx_buffer = (unsigned long)buf->area;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		struct snd_pcm_substream *substream =
+			pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+		struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+		imx_ssi_fiq_rx_buffer = (unsigned long)buf->area;
+	}
+
+	set_fiq_handler(&imx_ssi_fiq_start,
+		&imx_ssi_fiq_end - &imx_ssi_fiq_start);
+
+	return 0;
+}
+
+static void imx_pcm_fiq_free(struct snd_pcm *pcm)
+{
+	mxc_set_irq_fiq(ssi_irq, 0);
+	release_fiq(&fh);
+	imx_pcm_free(pcm);
+}
+
+static struct snd_soc_platform_driver imx_soc_platform_fiq = {
+	.ops		= &imx_pcm_ops,
+	.pcm_new	= imx_pcm_fiq_new,
+	.pcm_free	= imx_pcm_fiq_free,
+};
+
+static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
+{
+	struct imx_ssi *ssi = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = claim_fiq(&fh);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to claim fiq: %d", ret);
+		return ret;
+	}
+
+	mxc_set_irq_fiq(ssi->irq, 1);
+	ssi_irq = ssi->irq;
+
+	imx_pcm_fiq = ssi->irq;
+
+	imx_ssi_fiq_base = (unsigned long)ssi->base;
+
+	ssi->dma_params_tx.burstsize = 4;
+	ssi->dma_params_rx.burstsize = 6;
+
+	ret = snd_soc_register_platform(&pdev->dev, &imx_soc_platform_fiq);
+	if (ret)
+		goto failed_register;
+
+	return 0;
+
+failed_register:
+	mxc_set_irq_fiq(ssi_irq, 0);
+	release_fiq(&fh);
+
+	return ret;
+}
+
+static int __devexit imx_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver imx_pcm_driver = {
+	.driver = {
+			.name = "imx-fiq-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = imx_soc_platform_probe,
+	.remove = __devexit_p(imx_soc_platform_remove),
+};
+
+static int __init snd_imx_pcm_init(void)
+{
+	return platform_driver_register(&imx_pcm_driver);
+}
+module_init(snd_imx_pcm_init);
+
+static void __exit snd_imx_pcm_exit(void)
+{
+	platform_driver_unregister(&imx_pcm_driver);
+}
+module_exit(snd_imx_pcm_exit);
diff --git a/linux/sound/soc/imx/imx-ssi.c b/linux/sound/soc/imx/imx-ssi.c
new file mode 100644
index 0000000..390b6ff
--- /dev/null
+++ b/linux/sound/soc/imx/imx-ssi.c
@@ -0,0 +1,780 @@
+/*
+ * imx-ssi.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ *  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.
+ *
+ *
+ * The i.MX SSI core has some nasty limitations in AC97 mode. While most
+ * sane processor vendors have a FIFO per AC97 slot, the i.MX has only
+ * one FIFO which combines all valid receive slots. We cannot even select
+ * which slots we want to receive. The WM9712 with which this driver
+ * was developped with always sends GPIO status data in slot 12 which
+ * we receive in our (PCM-) data stream. The only chance we have is to
+ * manually skip this data in the FIQ handler. With sampling rates different
+ * from 48000Hz not every frame has valid receive data, so the ratio
+ * between pcm data and GPIO status data changes. Our FIQ handler is not
+ * able to handle this, hence this driver only works with 48000Hz sampling
+ * rate.
+ * Reading and writing AC97 registers is another challenge. The core
+ * provides us status bits when the read register is updated with *another*
+ * value. When we read the same register two times (and the register still
+ * contains the same value) these status bits are not set. We work
+ * around this by not polling these bits but only wait a fixed delay.
+ * 
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/ssi.h>
+#include <mach/hardware.h>
+
+#include "imx-ssi.h"
+
+#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV)
+
+/*
+ * SSI Network Mode or TDM slots configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+	unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 sccr;
+
+	sccr = readl(ssi->base + SSI_STCCR);
+	sccr &= ~SSI_STCCR_DC_MASK;
+	sccr |= SSI_STCCR_DC(slots - 1);
+	writel(sccr, ssi->base + SSI_STCCR);
+
+	sccr = readl(ssi->base + SSI_SRCCR);
+	sccr &= ~SSI_STCCR_DC_MASK;
+	sccr |= SSI_STCCR_DC(slots - 1);
+	writel(sccr, ssi->base + SSI_SRCCR);
+
+	writel(tx_mask, ssi->base + SSI_STMSK);
+	writel(rx_mask, ssi->base + SSI_SRMSK);
+
+	return 0;
+}
+
+/*
+ * SSI DAI format configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 strcr = 0, scr;
+
+	scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET);
+
+	/* DAI mode */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		/* data on rising edge of bclk, frame low 1clk before data */
+		strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
+		scr |= SSI_SCR_NET;
+		if (ssi->flags & IMX_SSI_USE_I2S_SLAVE) {
+			scr &= ~SSI_I2S_MODE_MASK;
+			scr |= SSI_SCR_I2S_MODE_SLAVE;
+		}
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		/* data on rising edge of bclk, frame high with data */
+		strcr |= SSI_STCR_TXBIT0;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		/* data on rising edge of bclk, frame high with data */
+		strcr |= SSI_STCR_TFSL;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		/* data on rising edge of bclk, frame high 1clk before data */
+		strcr |= SSI_STCR_TFSL | SSI_STCR_TEFS;
+		break;
+	}
+
+	/* DAI clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_IB_IF:
+		strcr |= SSI_STCR_TFSI;
+		strcr &= ~SSI_STCR_TSCKP;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		strcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
+		break;
+	case SND_SOC_DAIFMT_NB_NF:
+		strcr &= ~SSI_STCR_TFSI;
+		strcr |= SSI_STCR_TSCKP;
+		break;
+	}
+
+	/* DAI clock master masks */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		break;
+	default:
+		/* Master mode not implemented, needs handling of clocks. */
+		return -EINVAL;
+	}
+
+	strcr |= SSI_STCR_TFEN0;
+
+	if (ssi->flags & IMX_SSI_NET)
+		scr |= SSI_SCR_NET;
+	if (ssi->flags & IMX_SSI_SYN)
+		scr |= SSI_SCR_SYN;
+
+	writel(strcr, ssi->base + SSI_STCR);
+	writel(strcr, ssi->base + SSI_SRCR);
+	writel(scr, ssi->base + SSI_SCR);
+
+	return 0;
+}
+
+/*
+ * SSI system clock configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+				  int clk_id, unsigned int freq, int dir)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 scr;
+
+	scr = readl(ssi->base + SSI_SCR);
+
+	switch (clk_id) {
+	case IMX_SSP_SYS_CLK:
+		if (dir == SND_SOC_CLOCK_OUT)
+			scr |= SSI_SCR_SYS_CLK_EN;
+		else
+			scr &= ~SSI_SCR_SYS_CLK_EN;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	writel(scr, ssi->base + SSI_SCR);
+
+	return 0;
+}
+
+/*
+ * SSI Clock dividers
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+				  int div_id, int div)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 stccr, srccr;
+
+	stccr = readl(ssi->base + SSI_STCCR);
+	srccr = readl(ssi->base + SSI_SRCCR);
+
+	switch (div_id) {
+	case IMX_SSI_TX_DIV_2:
+		stccr &= ~SSI_STCCR_DIV2;
+		stccr |= div;
+		break;
+	case IMX_SSI_TX_DIV_PSR:
+		stccr &= ~SSI_STCCR_PSR;
+		stccr |= div;
+		break;
+	case IMX_SSI_TX_DIV_PM:
+		stccr &= ~0xff;
+		stccr |= SSI_STCCR_PM(div);
+		break;
+	case IMX_SSI_RX_DIV_2:
+		stccr &= ~SSI_STCCR_DIV2;
+		stccr |= div;
+		break;
+	case IMX_SSI_RX_DIV_PSR:
+		stccr &= ~SSI_STCCR_PSR;
+		stccr |= div;
+		break;
+	case IMX_SSI_RX_DIV_PM:
+		stccr &= ~0xff;
+		stccr |= SSI_STCCR_PM(div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	writel(stccr, ssi->base + SSI_STCCR);
+	writel(srccr, ssi->base + SSI_SRCCR);
+
+	return 0;
+}
+
+/*
+ * Should only be called when port is inactive (i.e. SSIEN = 0),
+ * although can be called multiple times by upper layers.
+ */
+static int imx_ssi_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *cpu_dai)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+	struct imx_pcm_dma_params *dma_data;
+	u32 reg, sccr;
+
+	/* Tx/Rx config */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		reg = SSI_STCCR;
+		dma_data = &ssi->dma_params_tx;
+	} else {
+		reg = SSI_SRCCR;
+		dma_data = &ssi->dma_params_rx;
+	}
+
+	if (ssi->flags & IMX_SSI_SYN)
+		reg = SSI_STCCR;
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+	sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK;
+
+	/* DAI data (word) size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		sccr |= SSI_SRCCR_WL(16);
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		sccr |= SSI_SRCCR_WL(20);
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		sccr |= SSI_SRCCR_WL(24);
+		break;
+	}
+
+	writel(sccr, ssi->base + reg);
+
+	return 0;
+}
+
+static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+		struct snd_soc_dai *dai)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(dai);
+	unsigned int sier_bits, sier;
+	unsigned int scr;
+
+	scr = readl(ssi->base + SSI_SCR);
+	sier = readl(ssi->base + SSI_SIER);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (ssi->flags & IMX_SSI_DMA)
+			sier_bits = SSI_SIER_TDMAE;
+		else
+			sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN;
+	} else {
+		if (ssi->flags & IMX_SSI_DMA)
+			sier_bits = SSI_SIER_RDMAE;
+		else
+			sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN;
+	}
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			scr |= SSI_SCR_TE;
+		else
+			scr |= SSI_SCR_RE;
+		sier |= sier_bits;
+
+		if (++ssi->enabled == 1)
+			scr |= SSI_SCR_SSIEN;
+
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			scr &= ~SSI_SCR_TE;
+		else
+			scr &= ~SSI_SCR_RE;
+		sier &= ~sier_bits;
+
+		if (--ssi->enabled == 0)
+			scr &= ~SSI_SCR_SSIEN;
+
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (!(ssi->flags & IMX_SSI_USE_AC97))
+		/* rx/tx are always enabled to access ac97 registers */
+		writel(scr, ssi->base + SSI_SCR);
+
+	writel(sier, ssi->base + SSI_SIER);
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
+	.hw_params	= imx_ssi_hw_params,
+	.set_fmt	= imx_ssi_set_dai_fmt,
+	.set_clkdiv	= imx_ssi_set_dai_clkdiv,
+	.set_sysclk	= imx_ssi_set_dai_sysclk,
+	.set_tdm_slot	= imx_ssi_set_dai_tdm_slot,
+	.trigger	= imx_ssi_trigger,
+};
+
+int snd_imx_pcm_mmap(struct snd_pcm_substream *substream,
+		struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int ret;
+
+	ret = dma_mmap_coherent(NULL, vma, runtime->dma_area,
+			runtime->dma_addr, runtime->dma_bytes);
+
+	pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret,
+			runtime->dma_area,
+			runtime->dma_addr,
+			runtime->dma_bytes);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_imx_pcm_mmap);
+
+static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = IMX_SSI_DMABUF_SIZE;
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+	buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+					   &buf->addr, GFP_KERNEL);
+	if (!buf->area)
+		return -ENOMEM;
+	buf->bytes = size;
+
+	return 0;
+}
+
+static u64 imx_pcm_dmamask = DMA_BIT_MASK(32);
+
+int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+	struct snd_pcm *pcm)
+{
+
+	int ret = 0;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &imx_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+	if (dai->driver->playback.channels_min) {
+		ret = imx_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_PLAYBACK);
+		if (ret)
+			goto out;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = imx_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_CAPTURE);
+		if (ret)
+			goto out;
+	}
+
+out:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(imx_pcm_new);
+
+void imx_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+
+		dma_free_writecombine(pcm->card->dev, buf->bytes,
+				      buf->area, buf->addr);
+		buf->area = NULL;
+	}
+}
+EXPORT_SYMBOL_GPL(imx_pcm_free);
+
+static int imx_ssi_dai_probe(struct snd_soc_dai *dai)
+{
+	struct imx_ssi *ssi = dev_get_drvdata(dai->dev);
+	uint32_t val;
+
+	snd_soc_dai_set_drvdata(dai, ssi);
+
+	val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.burstsize) |
+		SSI_SFCSR_RFWM0(ssi->dma_params_rx.burstsize);
+	writel(val, ssi->base + SSI_SFCSR);
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver imx_ssi_dai = {
+	.probe = imx_ssi_dai_probe,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.ops = &imx_ssi_pcm_dai_ops,
+};
+
+static struct snd_soc_dai_driver imx_ac97_dai = {
+	.probe = imx_ssi_dai_probe,
+	.ac97_control = 1,
+	.playback = {
+		.stream_name = "AC97 Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.capture = {
+		.stream_name = "AC97 Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.ops = &imx_ssi_pcm_dai_ops,
+};
+
+static void setup_channel_to_ac97(struct imx_ssi *imx_ssi)
+{
+	void __iomem *base = imx_ssi->base;
+
+	writel(0x0, base + SSI_SCR);
+	writel(0x0, base + SSI_STCR);
+	writel(0x0, base + SSI_SRCR);
+
+	writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR);
+
+	writel(SSI_SFCSR_RFWM0(8) |
+		SSI_SFCSR_TFWM0(8) |
+		SSI_SFCSR_RFWM1(8) |
+		SSI_SFCSR_TFWM1(8), base + SSI_SFCSR);
+
+	writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR);
+	writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR);
+
+	writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR);
+	writel(SSI_SOR_WAIT(3), base + SSI_SOR);
+
+	writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN |
+			SSI_SCR_TE | SSI_SCR_RE,
+			base + SSI_SCR);
+
+	writel(SSI_SACNT_DEFAULT, base + SSI_SACNT);
+	writel(0xff, base + SSI_SACCDIS);
+	writel(0x300, base + SSI_SACCEN);
+}
+
+static struct imx_ssi *ac97_ssi;
+
+static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+		unsigned short val)
+{
+	struct imx_ssi *imx_ssi = ac97_ssi;
+	void __iomem *base = imx_ssi->base;
+	unsigned int lreg;
+	unsigned int lval;
+
+	if (reg > 0x7f)
+		return;
+
+	pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+	lreg = reg <<  12;
+	writel(lreg, base + SSI_SACADD);
+
+	lval = val << 4;
+	writel(lval , base + SSI_SACDAT);
+
+	writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT);
+	udelay(100);
+}
+
+static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97,
+		unsigned short reg)
+{
+	struct imx_ssi *imx_ssi = ac97_ssi;
+	void __iomem *base = imx_ssi->base;
+
+	unsigned short val = -1;
+	unsigned int lreg;
+
+	lreg = (reg & 0x7f) <<  12 ;
+	writel(lreg, base + SSI_SACADD);
+	writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT);
+
+	udelay(100);
+
+	val = (readl(base + SSI_SACDAT) >> 4) & 0xffff;
+
+	pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+	return val;
+}
+
+static void imx_ssi_ac97_reset(struct snd_ac97 *ac97)
+{
+	struct imx_ssi *imx_ssi = ac97_ssi;
+
+	if (imx_ssi->ac97_reset)
+		imx_ssi->ac97_reset(ac97);
+}
+
+static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+	struct imx_ssi *imx_ssi = ac97_ssi;
+
+	if (imx_ssi->ac97_warm_reset)
+		imx_ssi->ac97_warm_reset(ac97);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read		= imx_ssi_ac97_read,
+	.write		= imx_ssi_ac97_write,
+	.reset		= imx_ssi_ac97_reset,
+	.warm_reset	= imx_ssi_ac97_warm_reset
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int imx_ssi_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct imx_ssi *ssi;
+	struct imx_ssi_platform_data *pdata = pdev->dev.platform_data;
+	int ret = 0;
+	struct snd_soc_dai_driver *dai;
+
+	ssi = kzalloc(sizeof(*ssi), GFP_KERNEL);
+	if (!ssi)
+		return -ENOMEM;
+	dev_set_drvdata(&pdev->dev, ssi);
+
+	if (pdata) {
+		ssi->ac97_reset = pdata->ac97_reset;
+		ssi->ac97_warm_reset = pdata->ac97_warm_reset;
+		ssi->flags = pdata->flags;
+	}
+
+	ssi->irq = platform_get_irq(pdev, 0);
+
+	ssi->clk = clk_get(&pdev->dev, NULL);
+	if (IS_ERR(ssi->clk)) {
+		ret = PTR_ERR(ssi->clk);
+		dev_err(&pdev->dev, "Cannot get the clock: %d\n",
+			ret);
+		goto failed_clk;
+	}
+	clk_enable(ssi->clk);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		ret = -ENODEV;
+		goto failed_get_resource;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) {
+		dev_err(&pdev->dev, "request_mem_region failed\n");
+		ret = -EBUSY;
+		goto failed_get_resource;
+	}
+
+	ssi->base = ioremap(res->start, resource_size(res));
+	if (!ssi->base) {
+		dev_err(&pdev->dev, "ioremap failed\n");
+		ret = -ENODEV;
+		goto failed_ioremap;
+	}
+
+	if (ssi->flags & IMX_SSI_USE_AC97) {
+		if (ac97_ssi) {
+			ret = -EBUSY;
+			goto failed_ac97;
+		}
+		ac97_ssi = ssi;
+		setup_channel_to_ac97(ssi);
+		dai = &imx_ac97_dai;
+	} else
+		dai = &imx_ssi_dai;
+
+	writel(0x0, ssi->base + SSI_SIER);
+
+	ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0;
+	ssi->dma_params_tx.dma_addr = res->start + SSI_STX0;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
+	if (res)
+		ssi->dma_params_tx.dma = res->start;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
+	if (res)
+		ssi->dma_params_rx.dma = res->start;
+
+	if ((cpu_is_mx27() || cpu_is_mx21()) &&
+			!(ssi->flags & IMX_SSI_USE_AC97) &&
+			(ssi->flags & IMX_SSI_DMA)) {
+		ssi->flags |= IMX_SSI_DMA;
+	}
+
+	platform_set_drvdata(pdev, ssi);
+
+	ret = snd_soc_register_dai(&pdev->dev, dai);
+	if (ret) {
+		dev_err(&pdev->dev, "register DAI failed\n");
+		goto failed_register;
+	}
+
+	ssi->soc_platform_pdev_fiq = platform_device_alloc("imx-fiq-pcm-audio", pdev->id);
+	if (!ssi->soc_platform_pdev_fiq) {
+		ret = -ENOMEM;
+		goto failed_pdev_fiq_alloc;
+	}
+
+	platform_set_drvdata(ssi->soc_platform_pdev_fiq, ssi);
+	ret = platform_device_add(ssi->soc_platform_pdev_fiq);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to add platform device\n");
+		goto failed_pdev_fiq_add;
+	}
+
+	ssi->soc_platform_pdev = platform_device_alloc("imx-pcm-audio", pdev->id);
+	if (!ssi->soc_platform_pdev) {
+		ret = -ENOMEM;
+		goto failed_pdev_alloc;
+	}
+
+	platform_set_drvdata(ssi->soc_platform_pdev, ssi);
+	ret = platform_device_add(ssi->soc_platform_pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to add platform device\n");
+		goto failed_pdev_add;
+	}
+
+	return 0;
+
+failed_pdev_add:
+	platform_device_put(ssi->soc_platform_pdev);
+failed_pdev_alloc:
+	platform_device_del(ssi->soc_platform_pdev_fiq);
+failed_pdev_fiq_add:
+	platform_device_put(ssi->soc_platform_pdev_fiq);
+failed_pdev_fiq_alloc:
+	snd_soc_unregister_dai(&pdev->dev);
+failed_register:
+failed_ac97:
+	iounmap(ssi->base);
+failed_ioremap:
+	release_mem_region(res->start, resource_size(res));
+failed_get_resource:
+	clk_disable(ssi->clk);
+	clk_put(ssi->clk);
+failed_clk:
+	kfree(ssi);
+
+	return ret;
+}
+
+static int __devexit imx_ssi_remove(struct platform_device *pdev)
+{
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct imx_ssi *ssi = platform_get_drvdata(pdev);
+
+	platform_device_unregister(ssi->soc_platform_pdev);
+	platform_device_unregister(ssi->soc_platform_pdev_fiq);
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	if (ssi->flags & IMX_SSI_USE_AC97)
+		ac97_ssi = NULL;
+
+	iounmap(ssi->base);
+	release_mem_region(res->start, resource_size(res));
+	clk_disable(ssi->clk);
+	clk_put(ssi->clk);
+	kfree(ssi);
+
+	return 0;
+}
+
+static struct platform_driver imx_ssi_driver = {
+	.probe = imx_ssi_probe,
+	.remove = __devexit_p(imx_ssi_remove),
+
+	.driver = {
+		.name = "imx-ssi",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init imx_ssi_init(void)
+{
+	return platform_driver_register(&imx_ssi_driver);
+}
+
+static void __exit imx_ssi_exit(void)
+{
+	platform_driver_unregister(&imx_ssi_driver);
+}
+
+module_init(imx_ssi_init);
+module_exit(imx_ssi_exit);
+
+/* Module information */
+MODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/imx/imx-ssi.h b/linux/sound/soc/imx/imx-ssi.h
new file mode 100644
index 0000000..a4406a1
--- /dev/null
+++ b/linux/sound/soc/imx/imx-ssi.h
@@ -0,0 +1,240 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _IMX_SSI_H
+#define _IMX_SSI_H
+
+#define SSI_STX0	0x00
+#define SSI_STX1	0x04
+#define SSI_SRX0	0x08
+#define SSI_SRX1	0x0c
+
+#define SSI_SCR		0x10
+#define SSI_SCR_CLK_IST		(1 << 9)
+#define SSI_SCR_CLK_IST_SHIFT	9
+#define SSI_SCR_TCH_EN		(1 << 8)
+#define SSI_SCR_SYS_CLK_EN	(1 << 7)
+#define SSI_SCR_I2S_MODE_NORM	(0 << 5)
+#define SSI_SCR_I2S_MODE_MSTR	(1 << 5)
+#define SSI_SCR_I2S_MODE_SLAVE	(2 << 5)
+#define SSI_I2S_MODE_MASK	(3 << 5)
+#define SSI_SCR_SYN		(1 << 4)
+#define SSI_SCR_NET		(1 << 3)
+#define SSI_SCR_RE		(1 << 2)
+#define SSI_SCR_TE		(1 << 1)
+#define SSI_SCR_SSIEN		(1 << 0)
+
+#define SSI_SISR	0x14
+#define SSI_SISR_MASK		((1 << 19) - 1)
+#define SSI_SISR_CMDAU		(1 << 18)
+#define SSI_SISR_CMDDU		(1 << 17)
+#define SSI_SISR_RXT		(1 << 16)
+#define SSI_SISR_RDR1		(1 << 15)
+#define SSI_SISR_RDR0		(1 << 14)
+#define SSI_SISR_TDE1		(1 << 13)
+#define SSI_SISR_TDE0		(1 << 12)
+#define SSI_SISR_ROE1		(1 << 11)
+#define SSI_SISR_ROE0		(1 << 10)
+#define SSI_SISR_TUE1		(1 << 9)
+#define SSI_SISR_TUE0		(1 << 8)
+#define SSI_SISR_TFS		(1 << 7)
+#define SSI_SISR_RFS		(1 << 6)
+#define SSI_SISR_TLS		(1 << 5)
+#define SSI_SISR_RLS		(1 << 4)
+#define SSI_SISR_RFF1		(1 << 3)
+#define SSI_SISR_RFF0		(1 << 2)
+#define SSI_SISR_TFE1		(1 << 1)
+#define SSI_SISR_TFE0		(1 << 0)
+
+#define SSI_SIER	0x18
+#define SSI_SIER_RDMAE		(1 << 22)
+#define SSI_SIER_RIE		(1 << 21)
+#define SSI_SIER_TDMAE		(1 << 20)
+#define SSI_SIER_TIE		(1 << 19)
+#define SSI_SIER_CMDAU_EN	(1 << 18)
+#define SSI_SIER_CMDDU_EN	(1 << 17)
+#define SSI_SIER_RXT_EN		(1 << 16)
+#define SSI_SIER_RDR1_EN	(1 << 15)
+#define SSI_SIER_RDR0_EN	(1 << 14)
+#define SSI_SIER_TDE1_EN	(1 << 13)
+#define SSI_SIER_TDE0_EN	(1 << 12)
+#define SSI_SIER_ROE1_EN	(1 << 11)
+#define SSI_SIER_ROE0_EN	(1 << 10)
+#define SSI_SIER_TUE1_EN	(1 << 9)
+#define SSI_SIER_TUE0_EN	(1 << 8)
+#define SSI_SIER_TFS_EN		(1 << 7)
+#define SSI_SIER_RFS_EN		(1 << 6)
+#define SSI_SIER_TLS_EN		(1 << 5)
+#define SSI_SIER_RLS_EN		(1 << 4)
+#define SSI_SIER_RFF1_EN	(1 << 3)
+#define SSI_SIER_RFF0_EN	(1 << 2)
+#define SSI_SIER_TFE1_EN	(1 << 1)
+#define SSI_SIER_TFE0_EN	(1 << 0)
+
+#define SSI_STCR	0x1c
+#define SSI_STCR_TXBIT0		(1 << 9)
+#define SSI_STCR_TFEN1		(1 << 8)
+#define SSI_STCR_TFEN0		(1 << 7)
+#define SSI_FIFO_ENABLE_0_SHIFT 7
+#define SSI_STCR_TFDIR		(1 << 6)
+#define SSI_STCR_TXDIR		(1 << 5)
+#define SSI_STCR_TSHFD		(1 << 4)
+#define SSI_STCR_TSCKP		(1 << 3)
+#define SSI_STCR_TFSI		(1 << 2)
+#define SSI_STCR_TFSL		(1 << 1)
+#define SSI_STCR_TEFS		(1 << 0)
+
+#define SSI_SRCR	0x20
+#define SSI_SRCR_RXBIT0		(1 << 9)
+#define SSI_SRCR_RFEN1		(1 << 8)
+#define SSI_SRCR_RFEN0		(1 << 7)
+#define SSI_FIFO_ENABLE_0_SHIFT 7
+#define SSI_SRCR_RFDIR		(1 << 6)
+#define SSI_SRCR_RXDIR		(1 << 5)
+#define SSI_SRCR_RSHFD		(1 << 4)
+#define SSI_SRCR_RSCKP		(1 << 3)
+#define SSI_SRCR_RFSI		(1 << 2)
+#define SSI_SRCR_RFSL		(1 << 1)
+#define SSI_SRCR_REFS		(1 << 0)
+
+#define SSI_SRCCR		0x28
+#define SSI_SRCCR_DIV2		(1 << 18)
+#define SSI_SRCCR_PSR		(1 << 17)
+#define SSI_SRCCR_WL(x)		((((x) - 2) >> 1) << 13)
+#define SSI_SRCCR_DC(x)		(((x) & 0x1f) << 8)
+#define SSI_SRCCR_PM(x)		(((x) & 0xff) << 0)
+#define SSI_SRCCR_WL_MASK	(0xf << 13)
+#define SSI_SRCCR_DC_MASK	(0x1f << 8)
+#define SSI_SRCCR_PM_MASK	(0xff << 0)
+
+#define SSI_STCCR		0x24
+#define SSI_STCCR_DIV2		(1 << 18)
+#define SSI_STCCR_PSR		(1 << 17)
+#define SSI_STCCR_WL(x)		((((x) - 2) >> 1) << 13)
+#define SSI_STCCR_DC(x)		(((x) & 0x1f) << 8)
+#define SSI_STCCR_PM(x)		(((x) & 0xff) << 0)
+#define SSI_STCCR_WL_MASK	(0xf << 13)
+#define SSI_STCCR_DC_MASK	(0x1f << 8)
+#define SSI_STCCR_PM_MASK	(0xff << 0)
+
+#define SSI_SFCSR	0x2c
+#define SSI_SFCSR_RFCNT1(x)	(((x) & 0xf) << 28)
+#define SSI_RX_FIFO_1_COUNT_SHIFT 28
+#define SSI_SFCSR_TFCNT1(x)	(((x) & 0xf) << 24)
+#define SSI_TX_FIFO_1_COUNT_SHIFT 24
+#define SSI_SFCSR_RFWM1(x)	(((x) & 0xf) << 20)
+#define SSI_SFCSR_TFWM1(x)	(((x) & 0xf) << 16)
+#define SSI_SFCSR_RFCNT0(x)	(((x) & 0xf) << 12)
+#define SSI_RX_FIFO_0_COUNT_SHIFT 12
+#define SSI_SFCSR_TFCNT0(x)	(((x) & 0xf) <<  8)
+#define SSI_TX_FIFO_0_COUNT_SHIFT 8
+#define SSI_SFCSR_RFWM0(x)	(((x) & 0xf) <<  4)
+#define SSI_SFCSR_TFWM0(x)	(((x) & 0xf) <<  0)
+#define SSI_SFCSR_RFWM0_MASK	(0xf <<  4)
+#define SSI_SFCSR_TFWM0_MASK	(0xf <<  0)
+
+#define SSI_STR		0x30
+#define SSI_STR_TEST		(1 << 15)
+#define SSI_STR_RCK2TCK		(1 << 14)
+#define SSI_STR_RFS2TFS		(1 << 13)
+#define SSI_STR_RXSTATE(x)	(((x) & 0xf) << 8)
+#define SSI_STR_TXD2RXD		(1 <<  7)
+#define SSI_STR_TCK2RCK		(1 <<  6)
+#define SSI_STR_TFS2RFS		(1 <<  5)
+#define SSI_STR_TXSTATE(x)	(((x) & 0xf) << 0)
+
+#define SSI_SOR		0x34
+#define SSI_SOR_CLKOFF		(1 << 6)
+#define SSI_SOR_RX_CLR		(1 << 5)
+#define SSI_SOR_TX_CLR		(1 << 4)
+#define SSI_SOR_INIT		(1 << 3)
+#define SSI_SOR_WAIT(x)		(((x) & 0x3) << 1)
+#define SSI_SOR_WAIT_MASK	(0x3 << 1)
+#define SSI_SOR_SYNRST		(1 << 0)
+
+#define SSI_SACNT	0x38
+#define SSI_SACNT_FRDIV(x)	(((x) & 0x3f) << 5)
+#define SSI_SACNT_WR		(1 << 4)
+#define SSI_SACNT_RD		(1 << 3)
+#define SSI_SACNT_TIF		(1 << 2)
+#define SSI_SACNT_FV		(1 << 1)
+#define SSI_SACNT_AC97EN	(1 << 0)
+
+#define SSI_SACADD	0x3c
+#define SSI_SACDAT	0x40
+#define SSI_SATAG	0x44
+#define SSI_STMSK	0x48
+#define SSI_SRMSK	0x4c
+#define SSI_SACCST	0x50
+#define SSI_SACCEN	0x54
+#define SSI_SACCDIS	0x58
+
+/* SSI clock sources */
+#define IMX_SSP_SYS_CLK		0
+
+/* SSI audio dividers */
+#define IMX_SSI_TX_DIV_2	0
+#define IMX_SSI_TX_DIV_PSR	1
+#define IMX_SSI_TX_DIV_PM	2
+#define IMX_SSI_RX_DIV_2	3
+#define IMX_SSI_RX_DIV_PSR	4
+#define IMX_SSI_RX_DIV_PM	5
+
+#define DRV_NAME "imx-ssi"
+
+#include <linux/dmaengine.h>
+#include <mach/dma.h>
+
+struct imx_pcm_dma_params {
+	int dma;
+	unsigned long dma_addr;
+	int burstsize;
+};
+
+struct imx_ssi {
+	struct platform_device *ac97_dev;
+
+	struct snd_soc_dai *imx_ac97;
+	struct clk *clk;
+	void __iomem *base;
+	int irq;
+	int fiq_enable;
+	unsigned int offset;
+
+	unsigned int flags;
+
+	void (*ac97_reset) (struct snd_ac97 *ac97);
+	void (*ac97_warm_reset)(struct snd_ac97 *ac97);
+
+	struct imx_pcm_dma_params	dma_params_rx;
+	struct imx_pcm_dma_params	dma_params_tx;
+
+	int enabled;
+
+	struct platform_device *soc_platform_pdev;
+	struct platform_device *soc_platform_pdev_fiq;
+};
+
+struct snd_soc_platform *imx_ssi_fiq_init(struct platform_device *pdev,
+		struct imx_ssi *ssi);
+void imx_ssi_fiq_exit(struct platform_device *pdev, struct imx_ssi *ssi);
+struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev,
+		struct imx_ssi *ssi);
+
+int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
+int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+	struct snd_pcm *pcm);
+void imx_pcm_free(struct snd_pcm *pcm);
+
+/*
+ * Do not change this as the FIQ handler depends on this size
+ */
+#define IMX_SSI_DMABUF_SIZE	(64 * 1024)
+
+#define DMA_RXFIFO_BURST      0x4
+#define DMA_TXFIFO_BURST      0x6
+
+#endif /* _IMX_SSI_H */
diff --git a/linux/sound/soc/imx/phycore-ac97.c b/linux/sound/soc/imx/phycore-ac97.c
new file mode 100644
index 0000000..9eabc28
--- /dev/null
+++ b/linux/sound/soc/imx/phycore-ac97.c
@@ -0,0 +1,100 @@
+/*
+ * phycore-ac97.c  --  SoC audio for imx_phycore in AC97 mode
+ *
+ * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-types.h>
+
+static struct snd_soc_card imx_phycore;
+
+static struct snd_soc_ops imx_phycore_hifi_ops = {
+};
+
+static struct snd_soc_dai_link imx_phycore_dai_ac97[] = {
+	{
+		.name		= "HiFi",
+		.stream_name	= "HiFi",
+		.codec_dai_name		= "wm9712-hifi",
+		.codec_name	= "wm9712-codec",
+		.cpu_dai_name	= "imx-ssi.0",
+		.platform_name	= "imx-fiq-pcm-audio.0",
+		.ops		= &imx_phycore_hifi_ops,
+	},
+};
+
+static struct snd_soc_card imx_phycore = {
+	.name		= "PhyCORE-ac97-audio",
+	.dai_link	= imx_phycore_dai_ac97,
+	.num_links	= ARRAY_SIZE(imx_phycore_dai_ac97),
+};
+
+static struct platform_device *imx_phycore_snd_ac97_device;
+static struct platform_device *imx_phycore_snd_device;
+
+static int __init imx_phycore_init(void)
+{
+	int ret;
+
+	if (!machine_is_pcm043() && !machine_is_pca100())
+		/* return happy. We might run on a totally different machine */
+		return 0;
+
+	imx_phycore_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+	if (!imx_phycore_snd_ac97_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(imx_phycore_snd_ac97_device, &imx_phycore);
+	ret = platform_device_add(imx_phycore_snd_ac97_device);
+	if (ret)
+		goto fail1;
+
+	imx_phycore_snd_device = platform_device_alloc("wm9712-codec", -1);
+	if (!imx_phycore_snd_device) {
+		ret = -ENOMEM;
+		goto fail2;
+	}
+	ret = platform_device_add(imx_phycore_snd_device);
+
+	if (ret) {
+		printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+		goto fail3;
+	}
+
+	return 0;
+
+fail3:
+	platform_device_put(imx_phycore_snd_device);
+fail2:
+	platform_device_del(imx_phycore_snd_ac97_device);
+fail1:
+	platform_device_put(imx_phycore_snd_ac97_device);
+	return ret;
+}
+
+static void __exit imx_phycore_exit(void)
+{
+	platform_device_unregister(imx_phycore_snd_device);
+	platform_device_unregister(imx_phycore_snd_ac97_device);
+}
+
+late_initcall(imx_phycore_init);
+module_exit(imx_phycore_exit);
+
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("PhyCORE ALSA SoC driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/imx/wm1133-ev1.c b/linux/sound/soc/imx/wm1133-ev1.c
new file mode 100644
index 0000000..30fdb15
--- /dev/null
+++ b/linux/sound/soc/imx/wm1133-ev1.c
@@ -0,0 +1,303 @@
+/*
+ *  wm1133-ev1.c - Audio for WM1133-EV1 on i.MX31ADS
+ *
+ *  Copyright (c) 2010 Wolfson Microelectronics plc
+ *  Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  Based on an earlier driver for the same hardware by Liam Girdwood.
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <mach/audmux.h>
+
+#include "imx-ssi.h"
+#include "../codecs/wm8350.h"
+
+/* There is a silicon mic on the board optionally connected via a solder pad
+ * SP1.  Define this to enable it.
+ */
+#undef USE_SIMIC
+
+struct _wm8350_audio {
+	unsigned int channels;
+	snd_pcm_format_t format;
+	unsigned int rate;
+	unsigned int sysclk;
+	unsigned int bclkdiv;
+	unsigned int clkdiv;
+	unsigned int lr_rate;
+};
+
+/* in order of power consumption per rate (lowest first) */
+static const struct _wm8350_audio wm8350_audio[] = {
+	/* 16bit mono modes */
+	{1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000 >> 1,
+	 WM8350_BCLK_DIV_48, WM8350_DACDIV_3, 16,},
+
+	/* 16 bit stereo modes */
+	{2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000,
+	 WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000,
+	 WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000,
+	 WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600,
+	 WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600,
+	 WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+
+	/* 24bit stereo modes */
+	{2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+	{2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+	{2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+	{2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+};
+
+static int wm1133_ev1_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int i, found = 0;
+	snd_pcm_format_t format = params_format(params);
+	unsigned int rate = params_rate(params);
+	unsigned int channels = params_channels(params);
+	u32 dai_format;
+
+	/* find the correct audio parameters */
+	for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) {
+		if (rate == wm8350_audio[i].rate &&
+		    format == wm8350_audio[i].format &&
+		    channels == wm8350_audio[i].channels) {
+			found = 1;
+			break;
+		}
+	}
+	if (!found)
+		return -EINVAL;
+
+	/* codec FLL input is 14.75 MHz from MCLK */
+	snd_soc_dai_set_pll(codec_dai, 0, 0, 14750000, wm8350_audio[i].sysclk);
+
+	dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+		SND_SOC_DAIFMT_CBM_CFM;
+
+	/* set codec DAI configuration */
+	snd_soc_dai_set_fmt(codec_dai, dai_format);
+
+	/* set cpu DAI configuration */
+	snd_soc_dai_set_fmt(cpu_dai, dai_format);
+
+	/* TODO: The SSI driver should figure this out for us */
+	switch (channels) {
+	case 2:
+		snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0);
+		break;
+	case 1:
+		snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffe, 0xffffffe, 1, 0);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* set MCLK as the codec system clock for DAC and ADC */
+	snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_MCLK,
+			       wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN);
+
+	/* set codec BCLK division for sample rate */
+	snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV,
+			       wm8350_audio[i].bclkdiv);
+
+	/* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_DACLR_CLKDIV, wm8350_audio[i].lr_rate);
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_ADCLR_CLKDIV, wm8350_audio[i].lr_rate);
+
+	/* now configure DAC and ADC clocks */
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv);
+
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv);
+
+	return 0;
+}
+
+static struct snd_soc_ops wm1133_ev1_ops = {
+	.hw_params = wm1133_ev1_hw_params,
+};
+
+static const struct snd_soc_dapm_widget wm1133_ev1_widgets[] = {
+#ifdef USE_SIMIC
+	SND_SOC_DAPM_MIC("SiMIC", NULL),
+#endif
+	SND_SOC_DAPM_MIC("Mic1 Jack", NULL),
+	SND_SOC_DAPM_MIC("Mic2 Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In Jack", NULL),
+	SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+/* imx32ads soc_card audio map */
+static const struct snd_soc_dapm_route wm1133_ev1_map[] = {
+
+#ifdef USE_SIMIC
+	/* SiMIC --> IN1LN (with automatic bias) via SP1 */
+	{ "IN1LN", NULL, "Mic Bias" },
+	{ "Mic Bias", NULL, "SiMIC" },
+#endif
+
+	/* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */
+	{ "IN1LN", NULL, "Mic Bias" },
+	{ "IN1LP", NULL, "Mic1 Jack" },
+	{ "Mic Bias", NULL, "Mic1 Jack" },
+
+	/* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */
+	{ "IN1RN", NULL, "Mic Bias" },
+	{ "IN1RP", NULL, "Mic2 Jack" },
+	{ "Mic Bias", NULL, "Mic2 Jack" },
+
+	/* Line in Jack --> AUX (L+R) */
+	{ "IN3R", NULL, "Line In Jack" },
+	{ "IN3L", NULL, "Line In Jack" },
+
+	/* Out1 --> Headphone Jack */
+	{ "Headphone Jack", NULL, "OUT1R" },
+	{ "Headphone Jack", NULL, "OUT1L" },
+
+	/* Out1 --> Line Out Jack */
+	{ "Line Out Jack", NULL, "OUT2R" },
+	{ "Line Out Jack", NULL, "OUT2L" },
+};
+
+static struct snd_soc_jack hp_jack;
+
+static struct snd_soc_jack_pin hp_jack_pins[] = {
+	{ .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE },
+};
+
+static struct snd_soc_jack mic_jack;
+
+static struct snd_soc_jack_pin mic_jack_pins[] = {
+	{ .pin = "Mic1 Jack", .mask = SND_JACK_MICROPHONE },
+	{ .pin = "Mic2 Jack", .mask = SND_JACK_MICROPHONE },
+};
+
+static int wm1133_ev1_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	snd_soc_dapm_new_controls(codec, wm1133_ev1_widgets,
+				  ARRAY_SIZE(wm1133_ev1_widgets));
+
+	snd_soc_dapm_add_routes(codec, wm1133_ev1_map,
+				ARRAY_SIZE(wm1133_ev1_map));
+
+	/* Headphone jack detection */
+	snd_soc_jack_new(codec, "Headphone", SND_JACK_HEADPHONE, &hp_jack);
+	snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins),
+			      hp_jack_pins);
+	wm8350_hp_jack_detect(codec, WM8350_JDR, &hp_jack, SND_JACK_HEADPHONE);
+
+	/* Microphone jack detection */
+	snd_soc_jack_new(codec, "Microphone",
+			 SND_JACK_MICROPHONE | SND_JACK_BTN_0, &mic_jack);
+	snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins),
+			      mic_jack_pins);
+	wm8350_mic_jack_detect(codec, &mic_jack, SND_JACK_MICROPHONE,
+			       SND_JACK_BTN_0);
+
+	snd_soc_dapm_force_enable_pin(codec, "Mic Bias");
+
+	return 0;
+}
+
+
+static struct snd_soc_dai_link wm1133_ev1_dai = {
+	.name = "WM1133-EV1",
+	.stream_name = "Audio",
+	.cpu_dai_name = "imx-ssi.0",
+	.codec_dai_name = "wm8350-hifi",
+	.platform_name = "imx-fiq-pcm-audio.0",
+	.codec_name = "wm8350-codec.0-0x1a",
+	.init = wm1133_ev1_init,
+	.ops = &wm1133_ev1_ops,
+	.symmetric_rates = 1,
+};
+
+static struct snd_soc_card wm1133_ev1 = {
+	.name = "WM1133-EV1",
+	.dai_link = &wm1133_ev1_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *wm1133_ev1_snd_device;
+
+static int __init wm1133_ev1_audio_init(void)
+{
+	int ret;
+	unsigned int ptcr, pdcr;
+
+	/* SSI0 mastered by port 5 */
+	ptcr = MXC_AUDMUX_V2_PTCR_SYN |
+		MXC_AUDMUX_V2_PTCR_TFSDIR |
+		MXC_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT5_SSI_PINS_5) |
+		MXC_AUDMUX_V2_PTCR_TCLKDIR |
+		MXC_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT5_SSI_PINS_5);
+	pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT5_SSI_PINS_5);
+	mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, ptcr, pdcr);
+
+	ptcr = MXC_AUDMUX_V2_PTCR_SYN;
+	pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0);
+	mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT5_SSI_PINS_5, ptcr, pdcr);
+
+	wm1133_ev1_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!wm1133_ev1_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(wm1133_ev1_snd_device, &wm1133_ev1);
+	ret = platform_device_add(wm1133_ev1_snd_device);
+
+	if (ret)
+		platform_device_put(wm1133_ev1_snd_device);
+
+	return ret;
+}
+module_init(wm1133_ev1_audio_init);
+
+static void __exit wm1133_ev1_audio_exit(void)
+{
+	platform_device_unregister(wm1133_ev1_snd_device);
+}
+module_exit(wm1133_ev1_audio_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("Audio for WM1133-EV1 on i.MX31ADS");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/jz4740/Kconfig b/linux/sound/soc/jz4740/Kconfig
new file mode 100644
index 0000000..5351cba
--- /dev/null
+++ b/linux/sound/soc/jz4740/Kconfig
@@ -0,0 +1,23 @@
+config SND_JZ4740_SOC
+	tristate "SoC Audio for Ingenic JZ4740 SoC"
+	depends on MACH_JZ4740 && SND_SOC
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the JZ4740 I2S interface. You will also need to select the audio
+	  interfaces to support below.
+
+config SND_JZ4740_SOC_I2S
+	depends on SND_JZ4740_SOC
+	tristate "SoC Audio (I2S protocol) for Ingenic JZ4740 SoC"
+	help
+	  Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740
+	  based boards.
+
+config SND_JZ4740_SOC_QI_LB60
+	tristate "SoC Audio support for Qi LB60"
+	depends on SND_JZ4740_SOC && JZ4740_QI_LB60
+	select SND_JZ4740_SOC_I2S
+    select SND_SOC_JZ4740_CODEC
+	help
+	  Say Y if you want to add support for ASoC audio on the Qi LB60 board
+	  a.k.a Qi Ben NanoNote.
diff --git a/linux/sound/soc/jz4740/Makefile b/linux/sound/soc/jz4740/Makefile
new file mode 100644
index 0000000..be873c1
--- /dev/null
+++ b/linux/sound/soc/jz4740/Makefile
@@ -0,0 +1,13 @@
+#
+# Jz4740 Platform Support
+#
+snd-soc-jz4740-objs := jz4740-pcm.o
+snd-soc-jz4740-i2s-objs := jz4740-i2s.o
+
+obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o
+obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o
+
+# Jz4740 Machine Support
+snd-soc-qi-lb60-objs := qi_lb60.o
+
+obj-$(CONFIG_SND_JZ4740_SOC_QI_LB60) += snd-soc-qi-lb60.o
diff --git a/linux/sound/soc/jz4740/jz4740-i2s.c b/linux/sound/soc/jz4740/jz4740-i2s.c
new file mode 100644
index 0000000..f3cffd1
--- /dev/null
+++ b/linux/sound/soc/jz4740/jz4740-i2s.c
@@ -0,0 +1,538 @@
+/*
+ *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.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.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "jz4740-i2s.h"
+#include "jz4740-pcm.h"
+
+#define JZ_REG_AIC_CONF		0x00
+#define JZ_REG_AIC_CTRL		0x04
+#define JZ_REG_AIC_I2S_FMT	0x10
+#define JZ_REG_AIC_FIFO_STATUS	0x14
+#define JZ_REG_AIC_I2S_STATUS	0x1c
+#define JZ_REG_AIC_CLK_DIV	0x30
+#define JZ_REG_AIC_FIFO		0x34
+
+#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_MASK (0xf << 12)
+#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_MASK (0xf <<  8)
+#define JZ_AIC_CONF_OVERFLOW_PLAY_LAST BIT(6)
+#define JZ_AIC_CONF_INTERNAL_CODEC BIT(5)
+#define JZ_AIC_CONF_I2S BIT(4)
+#define JZ_AIC_CONF_RESET BIT(3)
+#define JZ_AIC_CONF_BIT_CLK_MASTER BIT(2)
+#define JZ_AIC_CONF_SYNC_CLK_MASTER BIT(1)
+#define JZ_AIC_CONF_ENABLE BIT(0)
+
+#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 12
+#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 8
+
+#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19)
+#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16)
+#define JZ_AIC_CTRL_ENABLE_RX_DMA BIT(15)
+#define JZ_AIC_CTRL_ENABLE_TX_DMA BIT(14)
+#define JZ_AIC_CTRL_MONO_TO_STEREO BIT(11)
+#define JZ_AIC_CTRL_SWITCH_ENDIANNESS BIT(10)
+#define JZ_AIC_CTRL_SIGNED_TO_UNSIGNED BIT(9)
+#define JZ_AIC_CTRL_FLUSH		BIT(8)
+#define JZ_AIC_CTRL_ENABLE_ROR_INT BIT(6)
+#define JZ_AIC_CTRL_ENABLE_TUR_INT BIT(5)
+#define JZ_AIC_CTRL_ENABLE_RFS_INT BIT(4)
+#define JZ_AIC_CTRL_ENABLE_TFS_INT BIT(3)
+#define JZ_AIC_CTRL_ENABLE_LOOPBACK BIT(2)
+#define JZ_AIC_CTRL_ENABLE_PLAYBACK BIT(1)
+#define JZ_AIC_CTRL_ENABLE_CAPTURE BIT(0)
+
+#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET 19
+#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET  16
+
+#define JZ_AIC_I2S_FMT_DISABLE_BIT_CLK BIT(12)
+#define JZ_AIC_I2S_FMT_ENABLE_SYS_CLK BIT(4)
+#define JZ_AIC_I2S_FMT_MSB BIT(0)
+
+#define JZ_AIC_I2S_STATUS_BUSY BIT(2)
+
+#define JZ_AIC_CLK_DIV_MASK 0xf
+
+struct jz4740_i2s {
+	struct resource *mem;
+	void __iomem *base;
+	dma_addr_t phys_base;
+
+	struct clk *clk_aic;
+	struct clk *clk_i2s;
+
+	struct jz4740_pcm_config pcm_config_playback;
+	struct jz4740_pcm_config pcm_config_capture;
+};
+
+static inline uint32_t jz4740_i2s_read(const struct jz4740_i2s *i2s,
+	unsigned int reg)
+{
+	return readl(i2s->base + reg);
+}
+
+static inline void jz4740_i2s_write(const struct jz4740_i2s *i2s,
+	unsigned int reg, uint32_t value)
+{
+	writel(value, i2s->base + reg);
+}
+
+static int jz4740_i2s_startup(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	uint32_t conf, ctrl;
+
+	if (dai->active)
+		return 0;
+
+	ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+	ctrl |= JZ_AIC_CTRL_FLUSH;
+	jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+	clk_enable(i2s->clk_i2s);
+
+	conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+	conf |= JZ_AIC_CONF_ENABLE;
+	jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+	return 0;
+}
+
+static void jz4740_i2s_shutdown(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	uint32_t conf;
+
+	if (!dai->active)
+		return;
+
+	conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+	conf &= ~JZ_AIC_CONF_ENABLE;
+	jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+	clk_disable(i2s->clk_i2s);
+}
+
+static int jz4740_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+	struct snd_soc_dai *dai)
+{
+	struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+	uint32_t ctrl;
+	uint32_t mask;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		mask = JZ_AIC_CTRL_ENABLE_PLAYBACK | JZ_AIC_CTRL_ENABLE_TX_DMA;
+	else
+		mask = JZ_AIC_CTRL_ENABLE_CAPTURE | JZ_AIC_CTRL_ENABLE_RX_DMA;
+
+	ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		ctrl |= mask;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		ctrl &= ~mask;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+	return 0;
+}
+
+static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+	uint32_t format = 0;
+	uint32_t conf;
+
+	conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+
+	conf &= ~(JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		conf |= JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER;
+		format |= JZ_AIC_I2S_FMT_ENABLE_SYS_CLK;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		conf |= JZ_AIC_CONF_SYNC_CLK_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		conf |= JZ_AIC_CONF_BIT_CLK_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_MSB:
+		format |= JZ_AIC_I2S_FMT_MSB;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+	jz4740_i2s_write(i2s, JZ_REG_AIC_I2S_FMT, format);
+
+	return 0;
+}
+
+static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	enum jz4740_dma_width dma_width;
+	struct jz4740_pcm_config *pcm_config;
+	unsigned int sample_size;
+	uint32_t ctrl;
+
+	ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		sample_size = 0;
+		dma_width = JZ4740_DMA_WIDTH_8BIT;
+		break;
+	case SNDRV_PCM_FORMAT_S16:
+		sample_size = 1;
+		dma_width = JZ4740_DMA_WIDTH_16BIT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		ctrl &= ~JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK;
+		ctrl |= sample_size << JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET;
+		if (params_channels(params) == 1)
+			ctrl |= JZ_AIC_CTRL_MONO_TO_STEREO;
+		else
+			ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO;
+
+		pcm_config = &i2s->pcm_config_playback;
+		pcm_config->dma_config.dst_width = dma_width;
+
+	} else {
+		ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK;
+		ctrl |= sample_size << JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET;
+
+		pcm_config = &i2s->pcm_config_capture;
+		pcm_config->dma_config.src_width = dma_width;
+	}
+
+	jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+	snd_soc_dai_set_dma_data(dai, substream, pcm_config);
+
+	return 0;
+}
+
+static int jz4740_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+	unsigned int freq, int dir)
+{
+	struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	struct clk *parent;
+	int ret = 0;
+
+	switch (clk_id) {
+	case JZ4740_I2S_CLKSRC_EXT:
+		parent = clk_get(NULL, "ext");
+		clk_set_parent(i2s->clk_i2s, parent);
+		break;
+	case JZ4740_I2S_CLKSRC_PLL:
+		parent = clk_get(NULL, "pll half");
+		clk_set_parent(i2s->clk_i2s, parent);
+		ret = clk_set_rate(i2s->clk_i2s, freq);
+		break;
+	default:
+		return -EINVAL;
+	}
+	clk_put(parent);
+
+	return ret;
+}
+
+static int jz4740_i2s_suspend(struct snd_soc_dai *dai)
+{
+	struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	uint32_t conf;
+
+	if (dai->active) {
+		conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+		conf &= ~JZ_AIC_CONF_ENABLE;
+		jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+		clk_disable(i2s->clk_i2s);
+	}
+
+	clk_disable(i2s->clk_aic);
+
+	return 0;
+}
+
+static int jz4740_i2s_resume(struct snd_soc_dai *dai)
+{
+	struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	uint32_t conf;
+
+	clk_enable(i2s->clk_aic);
+
+	if (dai->active) {
+		clk_enable(i2s->clk_i2s);
+
+		conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+		conf |= JZ_AIC_CONF_ENABLE;
+		jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+	}
+
+	return 0;
+}
+
+static void jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s)
+{
+	struct jz4740_dma_config *dma_config;
+
+	/* Playback */
+	dma_config = &i2s->pcm_config_playback.dma_config;
+	dma_config->src_width = JZ4740_DMA_WIDTH_32BIT,
+	dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE;
+	dma_config->request_type = JZ4740_DMA_TYPE_AIC_TRANSMIT;
+	dma_config->flags = JZ4740_DMA_SRC_AUTOINC;
+	dma_config->mode = JZ4740_DMA_MODE_SINGLE;
+	i2s->pcm_config_playback.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO;
+
+	/* Capture */
+	dma_config = &i2s->pcm_config_capture.dma_config;
+	dma_config->dst_width = JZ4740_DMA_WIDTH_32BIT,
+	dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE;
+	dma_config->request_type = JZ4740_DMA_TYPE_AIC_RECEIVE;
+	dma_config->flags = JZ4740_DMA_DST_AUTOINC;
+	dma_config->mode = JZ4740_DMA_MODE_SINGLE;
+	i2s->pcm_config_capture.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO;
+}
+
+static int jz4740_i2s_dai_probe(struct snd_soc_dai *dai)
+{
+	struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	uint32_t conf;
+
+	clk_enable(i2s->clk_aic);
+
+	jz4740_i2c_init_pcm_config(i2s);
+
+	conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) |
+		(8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) |
+		JZ_AIC_CONF_OVERFLOW_PLAY_LAST |
+		JZ_AIC_CONF_I2S |
+		JZ_AIC_CONF_INTERNAL_CODEC;
+
+	jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, JZ_AIC_CONF_RESET);
+	jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+	return 0;
+}
+
+static int jz4740_i2s_dai_remove(struct snd_soc_dai *dai)
+{
+	struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+	clk_disable(i2s->clk_aic);
+	return 0;
+}
+
+static struct snd_soc_dai_ops jz4740_i2s_dai_ops = {
+	.startup = jz4740_i2s_startup,
+	.shutdown = jz4740_i2s_shutdown,
+	.trigger = jz4740_i2s_trigger,
+	.hw_params = jz4740_i2s_hw_params,
+	.set_fmt = jz4740_i2s_set_fmt,
+	.set_sysclk = jz4740_i2s_set_sysclk,
+};
+
+#define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \
+		SNDRV_PCM_FMTBIT_S16_LE)
+
+static struct snd_soc_dai_driver jz4740_i2s_dai = {
+	.probe = jz4740_i2s_dai_probe,
+	.remove = jz4740_i2s_dai_remove,
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = JZ4740_I2S_FMTS,
+	},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = JZ4740_I2S_FMTS,
+	},
+	.symmetric_rates = 1,
+	.ops = &jz4740_i2s_dai_ops,
+	.suspend = jz4740_i2s_suspend,
+	.resume = jz4740_i2s_resume,
+};
+
+static int __devinit jz4740_i2s_dev_probe(struct platform_device *pdev)
+{
+	struct jz4740_i2s *i2s;
+	int ret;
+
+	i2s = kzalloc(sizeof(*i2s), GFP_KERNEL);
+
+	if (!i2s)
+		return -ENOMEM;
+
+	i2s->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!i2s->mem) {
+		ret = -ENOENT;
+		goto err_free;
+	}
+
+	i2s->mem = request_mem_region(i2s->mem->start, resource_size(i2s->mem),
+				pdev->name);
+	if (!i2s->mem) {
+		ret = -EBUSY;
+		goto err_free;
+	}
+
+	i2s->base = ioremap_nocache(i2s->mem->start, resource_size(i2s->mem));
+	if (!i2s->base) {
+		ret = -EBUSY;
+		goto err_release_mem_region;
+	}
+
+	i2s->phys_base = i2s->mem->start;
+
+	i2s->clk_aic = clk_get(&pdev->dev, "aic");
+	if (IS_ERR(i2s->clk_aic)) {
+		ret = PTR_ERR(i2s->clk_aic);
+		goto err_iounmap;
+	}
+
+	i2s->clk_i2s = clk_get(&pdev->dev, "i2s");
+	if (IS_ERR(i2s->clk_i2s)) {
+		ret = PTR_ERR(i2s->clk_i2s);
+		goto err_clk_put_aic;
+	}
+
+	platform_set_drvdata(pdev, i2s);
+	ret = snd_soc_register_dai(&pdev->dev, &jz4740_i2s_dai);
+
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register DAI\n");
+		goto err_clk_put_i2s;
+	}
+
+	return 0;
+
+err_clk_put_i2s:
+	clk_put(i2s->clk_i2s);
+err_clk_put_aic:
+	clk_put(i2s->clk_aic);
+err_iounmap:
+	iounmap(i2s->base);
+err_release_mem_region:
+	release_mem_region(i2s->mem->start, resource_size(i2s->mem));
+err_free:
+	kfree(i2s);
+
+	return ret;
+}
+
+static int __devexit jz4740_i2s_dev_remove(struct platform_device *pdev)
+{
+	struct jz4740_i2s *i2s = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	clk_put(i2s->clk_i2s);
+	clk_put(i2s->clk_aic);
+
+	iounmap(i2s->base);
+	release_mem_region(i2s->mem->start, resource_size(i2s->mem));
+
+	platform_set_drvdata(pdev, NULL);
+	kfree(i2s);
+
+	return 0;
+}
+
+static struct platform_driver jz4740_i2s_driver = {
+	.probe = jz4740_i2s_dev_probe,
+	.remove = __devexit_p(jz4740_i2s_dev_remove),
+	.driver = {
+		.name = "jz4740-i2s",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init jz4740_i2s_init(void)
+{
+	return platform_driver_register(&jz4740_i2s_driver);
+}
+module_init(jz4740_i2s_init);
+
+static void __exit jz4740_i2s_exit(void)
+{
+	platform_driver_unregister(&jz4740_i2s_driver);
+}
+module_exit(jz4740_i2s_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen, <lars@metafoo.de>");
+MODULE_DESCRIPTION("Ingenic JZ4740 SoC I2S driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-i2s");
diff --git a/linux/sound/soc/jz4740/jz4740-i2s.h b/linux/sound/soc/jz4740/jz4740-i2s.h
new file mode 100644
index 0000000..5e49339
--- /dev/null
+++ b/linux/sound/soc/jz4740/jz4740-i2s.h
@@ -0,0 +1,16 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _JZ4740_I2S_H
+#define _JZ4740_I2S_H
+
+/* I2S clock source */
+#define JZ4740_I2S_CLKSRC_EXT 0
+#define JZ4740_I2S_CLKSRC_PLL 1
+
+#define JZ4740_I2S_BIT_CLK		0
+
+#endif
diff --git a/linux/sound/soc/jz4740/jz4740-pcm.c b/linux/sound/soc/jz4740/jz4740-pcm.c
new file mode 100644
index 0000000..fb1483f
--- /dev/null
+++ b/linux/sound/soc/jz4740/jz4740-pcm.c
@@ -0,0 +1,371 @@
+/*
+ *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.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.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mach-jz4740/dma.h>
+#include "jz4740-pcm.h"
+
+struct jz4740_runtime_data {
+	unsigned long dma_period;
+	dma_addr_t dma_start;
+	dma_addr_t dma_pos;
+	dma_addr_t dma_end;
+
+	struct jz4740_dma_chan *dma;
+
+	dma_addr_t fifo_addr;
+};
+
+/* identify hardware playback capabilities */
+static const struct snd_pcm_hardware jz4740_pcm_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+
+	.rates			= SNDRV_PCM_RATE_8000_48000,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	.period_bytes_min	= 16,
+	.period_bytes_max	= 2 * PAGE_SIZE,
+	.periods_min		= 2,
+	.periods_max		= 128,
+	.buffer_bytes_max	= 128 * 2 * PAGE_SIZE,
+	.fifo_size		= 32,
+};
+
+static void jz4740_pcm_start_transfer(struct jz4740_runtime_data *prtd,
+	struct snd_pcm_substream *substream)
+{
+	unsigned long count;
+
+	if (prtd->dma_pos == prtd->dma_end)
+		prtd->dma_pos = prtd->dma_start;
+
+	if (prtd->dma_pos + prtd->dma_period > prtd->dma_end)
+		count = prtd->dma_end - prtd->dma_pos;
+	else
+		count = prtd->dma_period;
+
+	jz4740_dma_disable(prtd->dma);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		jz4740_dma_set_src_addr(prtd->dma, prtd->dma_pos);
+		jz4740_dma_set_dst_addr(prtd->dma, prtd->fifo_addr);
+	} else {
+		jz4740_dma_set_src_addr(prtd->dma, prtd->fifo_addr);
+		jz4740_dma_set_dst_addr(prtd->dma, prtd->dma_pos);
+	}
+
+	jz4740_dma_set_transfer_count(prtd->dma, count);
+
+	prtd->dma_pos += count;
+
+	jz4740_dma_enable(prtd->dma);
+}
+
+static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err,
+	void *dev_id)
+{
+	struct snd_pcm_substream *substream = dev_id;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct jz4740_runtime_data *prtd = runtime->private_data;
+
+	snd_pcm_period_elapsed(substream);
+
+	jz4740_pcm_start_transfer(prtd, substream);
+}
+
+static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct jz4740_runtime_data *prtd = runtime->private_data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct jz4740_pcm_config *config;
+
+	config = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+	if (!config)
+		return 0;
+
+	if (!prtd->dma) {
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			prtd->dma = jz4740_dma_request(substream, "PCM Capture");
+		else
+			prtd->dma = jz4740_dma_request(substream, "PCM Playback");
+	}
+
+	if (!prtd->dma)
+		return -EBUSY;
+
+	jz4740_dma_configure(prtd->dma, &config->dma_config);
+	prtd->fifo_addr = config->fifo_addr;
+
+	jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done);
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+	runtime->dma_bytes = params_buffer_bytes(params);
+
+	prtd->dma_period = params_period_bytes(params);
+	prtd->dma_start = runtime->dma_addr;
+	prtd->dma_pos = prtd->dma_start;
+	prtd->dma_end = prtd->dma_start + runtime->dma_bytes;
+
+	return 0;
+}
+
+static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct jz4740_runtime_data *prtd = substream->runtime->private_data;
+
+	snd_pcm_set_runtime_buffer(substream, NULL);
+	if (prtd->dma) {
+		jz4740_dma_free(prtd->dma);
+		prtd->dma = NULL;
+	}
+
+	return 0;
+}
+
+static int jz4740_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct jz4740_runtime_data *prtd = substream->runtime->private_data;
+
+	if (!prtd->dma)
+		return -EBUSY;
+
+	prtd->dma_pos = prtd->dma_start;
+
+	return 0;
+}
+
+static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct jz4740_runtime_data *prtd = runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		jz4740_pcm_start_transfer(prtd, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		jz4740_dma_disable(prtd->dma);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct jz4740_runtime_data *prtd = runtime->private_data;
+	unsigned long byte_offset;
+	snd_pcm_uframes_t offset;
+	struct jz4740_dma_chan *dma = prtd->dma;
+
+	/* prtd->dma_pos points to the end of the current transfer. So by
+	 * subtracting prdt->dma_start we get the offset to the end of the
+	 * current period in bytes. By subtracting the residue of the transfer
+	 * we get the current offset in bytes. */
+	byte_offset = prtd->dma_pos - prtd->dma_start;
+	byte_offset -= jz4740_dma_get_residue(dma);
+
+	offset = bytes_to_frames(runtime, byte_offset);
+	if (offset >= runtime->buffer_size)
+		offset = 0;
+
+	return offset;
+}
+
+static int jz4740_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct jz4740_runtime_data *prtd;
+
+	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+	if (prtd == NULL)
+		return -ENOMEM;
+
+	snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware);
+
+	runtime->private_data = prtd;
+
+	return 0;
+}
+
+static int jz4740_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct jz4740_runtime_data *prtd = runtime->private_data;
+
+	kfree(prtd);
+
+	return 0;
+}
+
+static int jz4740_pcm_mmap(struct snd_pcm_substream *substream,
+	struct vm_area_struct *vma)
+{
+	return remap_pfn_range(vma, vma->vm_start,
+			substream->dma_buffer.addr >> PAGE_SHIFT,
+			vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+static struct snd_pcm_ops jz4740_pcm_ops = {
+	.open		= jz4740_pcm_open,
+	.close		= jz4740_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= jz4740_pcm_hw_params,
+	.hw_free	= jz4740_pcm_hw_free,
+	.prepare	= jz4740_pcm_prepare,
+	.trigger	= jz4740_pcm_trigger,
+	.pointer	= jz4740_pcm_pointer,
+	.mmap		= jz4740_pcm_mmap,
+};
+
+static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = jz4740_pcm_hardware.buffer_bytes_max;
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+
+	buf->area = dma_alloc_noncoherent(pcm->card->dev, size,
+					  &buf->addr, GFP_KERNEL);
+	if (!buf->area)
+		return -ENOMEM;
+
+	buf->bytes = size;
+
+	return 0;
+}
+
+static void jz4740_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) {
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+
+		dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area,
+				buf->addr);
+		buf->area = NULL;
+	}
+}
+
+static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32);
+
+int jz4740_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+	struct snd_pcm *pcm)
+{
+	int ret = 0;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &jz4740_pcm_dmamask;
+
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+	if (dai->driver->playback.channels_min) {
+		ret = jz4740_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_PLAYBACK);
+		if (ret)
+			goto err;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = jz4740_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_CAPTURE);
+		if (ret)
+			goto err;
+	}
+
+err:
+	return ret;
+}
+
+static struct snd_soc_platform_driver jz4740_soc_platform = {
+		.ops		= &jz4740_pcm_ops,
+		.pcm_new	= jz4740_pcm_new,
+		.pcm_free	= jz4740_pcm_free,
+};
+
+static int __devinit jz4740_pcm_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &jz4740_soc_platform);
+}
+
+static int __devexit jz4740_pcm_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver jz4740_pcm_driver = {
+	.probe = jz4740_pcm_probe,
+	.remove = __devexit_p(jz4740_pcm_remove),
+	.driver = {
+		.name = "jz4740-pcm-audio",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init jz4740_soc_platform_init(void)
+{
+	return platform_driver_register(&jz4740_pcm_driver);
+}
+module_init(jz4740_soc_platform_init);
+
+static void __exit jz4740_soc_platform_exit(void)
+{
+	return platform_driver_unregister(&jz4740_pcm_driver);
+}
+module_exit(jz4740_soc_platform_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/jz4740/jz4740-pcm.h b/linux/sound/soc/jz4740/jz4740-pcm.h
new file mode 100644
index 0000000..1220cbb
--- /dev/null
+++ b/linux/sound/soc/jz4740/jz4740-pcm.h
@@ -0,0 +1,20 @@
+/*
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _JZ4740_PCM_H
+#define _JZ4740_PCM_H
+
+#include <linux/dma-mapping.h>
+#include <asm/mach-jz4740/dma.h>
+
+
+struct jz4740_pcm_config {
+	struct jz4740_dma_config dma_config;
+	phys_addr_t fifo_addr;
+};
+
+#endif
diff --git a/linux/sound/soc/jz4740/qi_lb60.c b/linux/sound/soc/jz4740/qi_lb60.c
new file mode 100644
index 0000000..ef1a99e
--- /dev/null
+++ b/linux/sound/soc/jz4740/qi_lb60.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/gpio.h>
+
+#define QI_LB60_SND_GPIO JZ_GPIO_PORTB(29)
+#define QI_LB60_AMP_GPIO JZ_GPIO_PORTD(4)
+
+static int qi_lb60_spk_event(struct snd_soc_dapm_widget *widget,
+			     struct snd_kcontrol *ctrl, int event)
+{
+	int on = 0;
+	if (event & SND_SOC_DAPM_POST_PMU)
+		on = 1;
+	else if (event & SND_SOC_DAPM_PRE_PMD)
+		on = 0;
+
+	gpio_set_value(QI_LB60_SND_GPIO, on);
+	gpio_set_value(QI_LB60_AMP_GPIO, on);
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget qi_lb60_widgets[] = {
+	SND_SOC_DAPM_SPK("Speaker", qi_lb60_spk_event),
+	SND_SOC_DAPM_MIC("Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route qi_lb60_routes[] = {
+	{"Mic", NULL, "MIC"},
+	{"Speaker", NULL, "LOUT"},
+	{"Speaker", NULL, "ROUT"},
+};
+
+#define QI_LB60_DAIFMT (SND_SOC_DAIFMT_I2S | \
+			SND_SOC_DAIFMT_NB_NF | \
+			SND_SOC_DAIFMT_CBM_CFM)
+
+static int qi_lb60_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	snd_soc_dapm_nc_pin(codec, "LIN");
+	snd_soc_dapm_nc_pin(codec, "RIN");
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, QI_LB60_DAIFMT);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cpu dai format: %d\n", ret);
+		return ret;
+	}
+
+	snd_soc_dapm_new_controls(codec, qi_lb60_widgets, ARRAY_SIZE(qi_lb60_widgets));
+	snd_soc_dapm_add_routes(codec, qi_lb60_routes, ARRAY_SIZE(qi_lb60_routes));
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_dai_link qi_lb60_dai = {
+	.name = "jz4740",
+	.stream_name = "jz4740",
+	.cpu_dai_name = "jz4740-i2s",
+	.platform_name = "jz4740-pcm-audio",
+	.codec_dai_name = "jz4740-hifi",
+	.codec_name = "jz4740-codec",
+	.init = qi_lb60_codec_init,
+};
+
+static struct snd_soc_card qi_lb60 = {
+	.name = "QI LB60",
+	.dai_link = &qi_lb60_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *qi_lb60_snd_device;
+
+static int __init qi_lb60_init(void)
+{
+	int ret;
+
+	qi_lb60_snd_device = platform_device_alloc("soc-audio", -1);
+
+	if (!qi_lb60_snd_device)
+		return -ENOMEM;
+
+	ret = gpio_request(QI_LB60_SND_GPIO, "SND");
+	if (ret) {
+		pr_err("qi_lb60 snd: Failed to request SND GPIO(%d): %d\n",
+				QI_LB60_SND_GPIO, ret);
+		goto err_device_put;
+	}
+
+	ret = gpio_request(QI_LB60_AMP_GPIO, "AMP");
+	if (ret) {
+		pr_err("qi_lb60 snd: Failed to request AMP GPIO(%d): %d\n",
+				QI_LB60_AMP_GPIO, ret);
+		goto err_gpio_free_snd;
+	}
+
+	gpio_direction_output(QI_LB60_SND_GPIO, 0);
+	gpio_direction_output(QI_LB60_AMP_GPIO, 0);
+
+	platform_set_drvdata(qi_lb60_snd_device, &qi_lb60);
+
+	ret = platform_device_add(qi_lb60_snd_device);
+	if (ret) {
+		pr_err("qi_lb60 snd: Failed to add snd soc device: %d\n", ret);
+		goto err_unset_pdata;
+	}
+
+	 return 0;
+
+err_unset_pdata:
+	platform_set_drvdata(qi_lb60_snd_device, NULL);
+/*err_gpio_free_amp:*/
+	gpio_free(QI_LB60_AMP_GPIO);
+err_gpio_free_snd:
+	gpio_free(QI_LB60_SND_GPIO);
+err_device_put:
+	platform_device_put(qi_lb60_snd_device);
+
+	return ret;
+}
+module_init(qi_lb60_init);
+
+static void __exit qi_lb60_exit(void)
+{
+	gpio_free(QI_LB60_AMP_GPIO);
+	gpio_free(QI_LB60_SND_GPIO);
+	platform_device_unregister(qi_lb60_snd_device);
+}
+module_exit(qi_lb60_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/kirkwood/Kconfig b/linux/sound/soc/kirkwood/Kconfig
new file mode 100644
index 0000000..16ec2a2
--- /dev/null
+++ b/linux/sound/soc/kirkwood/Kconfig
@@ -0,0 +1,20 @@
+config SND_KIRKWOOD_SOC
+	tristate "SoC Audio for the Marvell Kirkwood chip"
+	depends on ARCH_KIRKWOOD
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the Kirkwood I2S interface. You will also need to select the
+	  audio interfaces to support below.
+
+config SND_KIRKWOOD_SOC_I2S
+	tristate
+
+config SND_KIRKWOOD_SOC_OPENRD
+	tristate "SoC Audio support for Kirkwood Openrd Client"
+	depends on SND_KIRKWOOD_SOC && MACH_OPENRD_CLIENT
+	select SND_KIRKWOOD_SOC_I2S
+	select SND_SOC_CS42L51
+	help
+	  Say Y if you want to add support for SoC audio on
+	  Openrd Client.
+
diff --git a/linux/sound/soc/kirkwood/Makefile b/linux/sound/soc/kirkwood/Makefile
new file mode 100644
index 0000000..33a16dc
--- /dev/null
+++ b/linux/sound/soc/kirkwood/Makefile
@@ -0,0 +1,9 @@
+snd-soc-kirkwood-objs := kirkwood-dma.o
+snd-soc-kirkwood-i2s-objs := kirkwood-i2s.o
+
+obj-$(CONFIG_SND_KIRKWOOD_SOC) += snd-soc-kirkwood.o
+obj-$(CONFIG_SND_KIRKWOOD_SOC_I2S) += snd-soc-kirkwood-i2s.o
+
+snd-soc-openrd-objs := kirkwood-openrd.o
+
+obj-$(CONFIG_SND_KIRKWOOD_SOC_OPENRD) += snd-soc-openrd.o
diff --git a/linux/sound/soc/kirkwood/kirkwood-dma.c b/linux/sound/soc/kirkwood/kirkwood-dma.c
new file mode 100644
index 0000000..0fd6a63
--- /dev/null
+++ b/linux/sound/soc/kirkwood/kirkwood-dma.c
@@ -0,0 +1,404 @@
+/*
+ * kirkwood-dma.c
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/mbus.h>
+#include <sound/soc.h>
+#include "kirkwood.h"
+
+#define KIRKWOOD_RATES \
+	(SNDRV_PCM_RATE_44100 | \
+	 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+#define KIRKWOOD_FORMATS \
+	(SNDRV_PCM_FMTBIT_S16_LE | \
+	 SNDRV_PCM_FMTBIT_S24_LE | \
+	 SNDRV_PCM_FMTBIT_S32_LE)
+
+struct kirkwood_dma_priv {
+	struct snd_pcm_substream *play_stream;
+	struct snd_pcm_substream *rec_stream;
+	struct kirkwood_dma_data *data;
+};
+
+static struct snd_pcm_hardware kirkwood_dma_snd_hw = {
+	.info = (SNDRV_PCM_INFO_INTERLEAVED |
+		 SNDRV_PCM_INFO_MMAP |
+		 SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 SNDRV_PCM_INFO_PAUSE),
+	.formats		= KIRKWOOD_FORMATS,
+	.rates			= KIRKWOOD_RATES,
+	.rate_min		= 44100,
+	.rate_max		= 96000,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= KIRKWOOD_SND_MAX_PERIOD_BYTES * KIRKWOOD_SND_MAX_PERIODS,
+	.period_bytes_min	= KIRKWOOD_SND_MIN_PERIOD_BYTES,
+	.period_bytes_max	= KIRKWOOD_SND_MAX_PERIOD_BYTES,
+	.periods_min		= KIRKWOOD_SND_MIN_PERIODS,
+	.periods_max		= KIRKWOOD_SND_MAX_PERIODS,
+	.fifo_size		= 0,
+};
+
+static u64 kirkwood_dma_dmamask = 0xFFFFFFFFUL;
+
+static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id)
+{
+	struct kirkwood_dma_priv *prdata = dev_id;
+	struct kirkwood_dma_data *priv = prdata->data;
+	unsigned long mask, status, cause;
+
+	mask = readl(priv->io + KIRKWOOD_INT_MASK);
+	status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask;
+
+	cause = readl(priv->io + KIRKWOOD_ERR_CAUSE);
+	if (unlikely(cause)) {
+		printk(KERN_WARNING "%s: got err interrupt 0x%lx\n",
+				__func__, cause);
+		writel(cause, priv->io + KIRKWOOD_ERR_CAUSE);
+		return IRQ_HANDLED;
+	}
+
+	/* we've enabled only bytes interrupts ... */
+	if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \
+			KIRKWOOD_INT_CAUSE_REC_BYTES)) {
+		printk(KERN_WARNING "%s: unexpected interrupt %lx\n",
+			__func__, status);
+		return IRQ_NONE;
+	}
+
+	/* ack int */
+	writel(status, priv->io + KIRKWOOD_INT_CAUSE);
+
+	if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES)
+		snd_pcm_period_elapsed(prdata->play_stream);
+
+	if (status & KIRKWOOD_INT_CAUSE_REC_BYTES)
+		snd_pcm_period_elapsed(prdata->rec_stream);
+
+	return IRQ_HANDLED;
+}
+
+static void kirkwood_dma_conf_mbus_windows(void __iomem *base, int win,
+					unsigned long dma,
+					struct mbus_dram_target_info *dram)
+{
+	int i;
+
+	/* First disable and clear windows */
+	writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
+	writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));
+
+	/* try to find matching cs for current dma address */
+	for (i = 0; i < dram->num_cs; i++) {
+		struct mbus_dram_window *cs = dram->cs + i;
+		if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) {
+			writel(cs->base & 0xffff0000,
+				base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));
+			writel(((cs->size - 1) & 0xffff0000) |
+				(cs->mbus_attr << 8) |
+				(dram->mbus_dram_target_id << 4) | 1,
+				base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
+		}
+	}
+}
+
+static int kirkwood_dma_open(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct snd_soc_platform *platform = soc_runtime->platform;
+	struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
+	struct kirkwood_dma_data *priv;
+	struct kirkwood_dma_priv *prdata = snd_soc_platform_get_drvdata(platform);
+	unsigned long addr;
+
+	priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+	snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw);
+
+	/* Ensure that all constraints linked to dma burst are fullfilled */
+	err = snd_pcm_hw_constraint_minmax(runtime,
+			SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+			priv->burst * 2,
+			KIRKWOOD_AUDIO_BUF_MAX-1);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_step(runtime, 0,
+			SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+			priv->burst);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_step(substream->runtime, 0,
+			 SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+			 priv->burst);
+	if (err < 0)
+		return err;
+
+	if (prdata == NULL) {
+		prdata = kzalloc(sizeof(struct kirkwood_dma_priv), GFP_KERNEL);
+		if (prdata == NULL)
+			return -ENOMEM;
+
+		prdata->data = priv;
+
+		err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED,
+				  "kirkwood-i2s", prdata);
+		if (err) {
+			kfree(prdata);
+			return -EBUSY;
+		}
+
+		snd_soc_platform_set_drvdata(platform, prdata);
+
+		/*
+		 * Enable Error interrupts. We're only ack'ing them but
+		 * it's usefull for diagnostics
+		 */
+		writel((unsigned long)-1, priv->io + KIRKWOOD_ERR_MASK);
+	}
+
+	addr = virt_to_phys(substream->dma_buffer.area);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		prdata->play_stream = substream;
+		kirkwood_dma_conf_mbus_windows(priv->io,
+			KIRKWOOD_PLAYBACK_WIN, addr, priv->dram);
+	} else {
+		prdata->rec_stream = substream;
+		kirkwood_dma_conf_mbus_windows(priv->io,
+			KIRKWOOD_RECORD_WIN, addr, priv->dram);
+	}
+
+	return 0;
+}
+
+static int kirkwood_dma_close(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
+	struct snd_soc_platform *platform = soc_runtime->platform;
+	struct kirkwood_dma_priv *prdata = snd_soc_platform_get_drvdata(platform);
+	struct kirkwood_dma_data *priv;
+
+	priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+	if (!prdata || !priv)
+		return 0;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		prdata->play_stream = NULL;
+	else
+		prdata->rec_stream = NULL;
+
+	if (!prdata->play_stream && !prdata->rec_stream) {
+		writel(0, priv->io + KIRKWOOD_ERR_MASK);
+		free_irq(priv->irq, prdata);
+		kfree(prdata);
+		snd_soc_platform_set_drvdata(platform, NULL);
+	}
+
+	return 0;
+}
+
+static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+	runtime->dma_bytes = params_buffer_bytes(params);
+
+	return 0;
+}
+
+static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_set_runtime_buffer(substream, NULL);
+	return 0;
+}
+
+static int kirkwood_dma_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
+	struct kirkwood_dma_data *priv;
+	unsigned long size, count;
+
+	priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+	/* compute buffer size in term of "words" as requested in specs */
+	size = frames_to_bytes(runtime, runtime->buffer_size);
+	size = (size>>2)-1;
+	count = snd_pcm_lib_period_bytes(substream);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT);
+		writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR);
+		writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE);
+	} else {
+		writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT);
+		writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR);
+		writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE);
+	}
+
+
+	return 0;
+}
+
+static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream
+						*substream)
+{
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;
+	struct kirkwood_dma_data *priv;
+	snd_pcm_uframes_t count;
+
+	priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		count = bytes_to_frames(substream->runtime,
+			readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT));
+	else
+		count = bytes_to_frames(substream->runtime,
+			readl(priv->io + KIRKWOOD_REC_BYTE_COUNT));
+
+	return count;
+}
+
+struct snd_pcm_ops kirkwood_dma_ops = {
+	.open =		kirkwood_dma_open,
+	.close =        kirkwood_dma_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	kirkwood_dma_hw_params,
+	.hw_free =      kirkwood_dma_hw_free,
+	.prepare =      kirkwood_dma_prepare,
+	.pointer =	kirkwood_dma_pointer,
+};
+
+static int kirkwood_dma_preallocate_dma_buffer(struct snd_pcm *pcm,
+		int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = kirkwood_dma_snd_hw.buffer_bytes_max;
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->area = dma_alloc_coherent(pcm->card->dev, size,
+			&buf->addr, GFP_KERNEL);
+	if (!buf->area)
+		return -ENOMEM;
+	buf->bytes = size;
+	buf->private_data = NULL;
+
+	return 0;
+}
+
+static int kirkwood_dma_new(struct snd_card *card,
+		struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+	int ret;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &kirkwood_dma_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = 0xffffffff;
+
+	if (dai->driver->playback.channels_min) {
+		ret = kirkwood_dma_preallocate_dma_buffer(pcm,
+				SNDRV_PCM_STREAM_PLAYBACK);
+		if (ret)
+			return ret;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = kirkwood_dma_preallocate_dma_buffer(pcm,
+				SNDRV_PCM_STREAM_CAPTURE);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static void kirkwood_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+
+		dma_free_coherent(pcm->card->dev, buf->bytes,
+				buf->area, buf->addr);
+		buf->area = NULL;
+	}
+}
+
+static struct snd_soc_platform_driver kirkwood_soc_platform = {
+	.ops		= &kirkwood_dma_ops,
+	.pcm_new	= kirkwood_dma_new,
+	.pcm_free	= kirkwood_dma_free_dma_buffers,
+};
+
+static int __devinit kirkwood_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &kirkwood_soc_platform);
+}
+
+static int __devexit kirkwood_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver kirkwood_pcm_driver = {
+	.driver = {
+			.name = "kirkwood-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = kirkwood_soc_platform_probe,
+	.remove = __devexit_p(kirkwood_soc_platform_remove),
+};
+
+static int __init kirkwood_pcm_init(void)
+{
+	return platform_driver_register(&kirkwood_pcm_driver);
+}
+module_init(kirkwood_pcm_init);
+
+static void __exit kirkwood_pcm_exit(void)
+{
+	platform_driver_unregister(&kirkwood_pcm_driver);
+}
+module_exit(kirkwood_pcm_exit);
+
+MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
+MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kirkwood-pcm-audio");
diff --git a/linux/sound/soc/kirkwood/kirkwood-i2s.c b/linux/sound/soc/kirkwood/kirkwood-i2s.c
new file mode 100644
index 0000000..a33fc51
--- /dev/null
+++ b/linux/sound/soc/kirkwood/kirkwood-i2s.c
@@ -0,0 +1,502 @@
+/*
+ * kirkwood-i2s.c
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/mbus.h>
+#include <linux/delay.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <plat/audio.h>
+#include "kirkwood.h"
+
+#define DRV_NAME	"kirkwood-i2s"
+
+#define KIRKWOOD_I2S_RATES \
+	(SNDRV_PCM_RATE_44100 | \
+	 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+#define KIRKWOOD_I2S_FORMATS \
+	(SNDRV_PCM_FMTBIT_S16_LE | \
+	 SNDRV_PCM_FMTBIT_S24_LE | \
+	 SNDRV_PCM_FMTBIT_S32_LE)
+
+static int kirkwood_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+		unsigned int fmt)
+{
+	struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	unsigned long mask;
+	unsigned long value;
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_RIGHT_J:
+		mask = KIRKWOOD_I2S_CTL_RJ;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		mask = KIRKWOOD_I2S_CTL_LJ;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		mask = KIRKWOOD_I2S_CTL_I2S;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/*
+	 * Set same format for playback and record
+	 * This avoids some troubles.
+	 */
+	value = readl(priv->io+KIRKWOOD_I2S_PLAYCTL);
+	value &= ~KIRKWOOD_I2S_CTL_JUST_MASK;
+	value |= mask;
+	writel(value, priv->io+KIRKWOOD_I2S_PLAYCTL);
+
+	value = readl(priv->io+KIRKWOOD_I2S_RECCTL);
+	value &= ~KIRKWOOD_I2S_CTL_JUST_MASK;
+	value |= mask;
+	writel(value, priv->io+KIRKWOOD_I2S_RECCTL);
+
+	return 0;
+}
+
+static inline void kirkwood_set_dco(void __iomem *io, unsigned long rate)
+{
+	unsigned long value;
+
+	value = KIRKWOOD_DCO_CTL_OFFSET_0;
+	switch (rate) {
+	default:
+	case 44100:
+		value |= KIRKWOOD_DCO_CTL_FREQ_11;
+		break;
+	case 48000:
+		value |= KIRKWOOD_DCO_CTL_FREQ_12;
+		break;
+	case 96000:
+		value |= KIRKWOOD_DCO_CTL_FREQ_24;
+		break;
+	}
+	writel(value, io + KIRKWOOD_DCO_CTL);
+
+	/* wait for dco locked */
+	do {
+		cpu_relax();
+		value = readl(io + KIRKWOOD_DCO_SPCR_STATUS);
+		value &= KIRKWOOD_DCO_SPCR_STATUS;
+	} while (value == 0);
+}
+
+static int kirkwood_i2s_startup(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);
+
+	snd_soc_dai_set_dma_data(dai, substream, priv);
+	return 0;
+}
+
+static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);
+	unsigned int i2s_reg, reg;
+	unsigned long i2s_value, value;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		i2s_reg = KIRKWOOD_I2S_PLAYCTL;
+		reg = KIRKWOOD_PLAYCTL;
+	} else {
+		i2s_reg = KIRKWOOD_I2S_RECCTL;
+		reg = KIRKWOOD_RECCTL;
+	}
+
+	/* set dco conf */
+	kirkwood_set_dco(priv->io, params_rate(params));
+
+	i2s_value = readl(priv->io+i2s_reg);
+	i2s_value &= ~KIRKWOOD_I2S_CTL_SIZE_MASK;
+
+	value = readl(priv->io+reg);
+	value &= ~KIRKWOOD_PLAYCTL_SIZE_MASK;
+
+	/*
+	 * Size settings in play/rec i2s control regs and play/rec control
+	 * regs must be the same.
+	 */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		i2s_value |= KIRKWOOD_I2S_CTL_SIZE_16;
+		value |= KIRKWOOD_PLAYCTL_SIZE_16_C;
+		break;
+	/*
+	 * doesn't work... S20_3LE != kirkwood 20bit format ?
+	 *
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		i2s_value |= KIRKWOOD_I2S_CTL_SIZE_20;
+		value |= KIRKWOOD_PLAYCTL_SIZE_20;
+		break;
+	*/
+	case SNDRV_PCM_FORMAT_S24_LE:
+		i2s_value |= KIRKWOOD_I2S_CTL_SIZE_24;
+		value |= KIRKWOOD_PLAYCTL_SIZE_24;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		i2s_value |= KIRKWOOD_I2S_CTL_SIZE_32;
+		value |= KIRKWOOD_PLAYCTL_SIZE_32;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		value &= ~KIRKWOOD_PLAYCTL_MONO_MASK;
+		if (params_channels(params) == 1)
+			value |= KIRKWOOD_PLAYCTL_MONO_BOTH;
+		else
+			value |= KIRKWOOD_PLAYCTL_MONO_OFF;
+	}
+
+	writel(i2s_value, priv->io+i2s_reg);
+	writel(value, priv->io+reg);
+
+	return 0;
+}
+
+static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream,
+				int cmd, struct snd_soc_dai *dai)
+{
+	struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);
+	unsigned long value;
+
+	/*
+	 * specs says KIRKWOOD_PLAYCTL must be read 2 times before
+	 * changing it. So read 1 time here and 1 later.
+	 */
+	value = readl(priv->io + KIRKWOOD_PLAYCTL);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* stop audio, enable interrupts */
+		value = readl(priv->io + KIRKWOOD_PLAYCTL);
+		value |= KIRKWOOD_PLAYCTL_PAUSE;
+		writel(value, priv->io + KIRKWOOD_PLAYCTL);
+
+		value = readl(priv->io + KIRKWOOD_INT_MASK);
+		value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES;
+		writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+		/* configure audio & enable i2s playback */
+		value = readl(priv->io + KIRKWOOD_PLAYCTL);
+		value &= ~KIRKWOOD_PLAYCTL_BURST_MASK;
+		value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE
+				| KIRKWOOD_PLAYCTL_SPDIF_EN);
+
+		if (priv->burst == 32)
+			value |= KIRKWOOD_PLAYCTL_BURST_32;
+		else
+			value |= KIRKWOOD_PLAYCTL_BURST_128;
+		value |= KIRKWOOD_PLAYCTL_I2S_EN;
+		writel(value, priv->io + KIRKWOOD_PLAYCTL);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		/* stop audio, disable interrupts */
+		value = readl(priv->io + KIRKWOOD_PLAYCTL);
+		value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE;
+		writel(value, priv->io + KIRKWOOD_PLAYCTL);
+
+		value = readl(priv->io + KIRKWOOD_INT_MASK);
+		value &= ~KIRKWOOD_INT_CAUSE_PLAY_BYTES;
+		writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+		/* disable all playbacks */
+		value = readl(priv->io + KIRKWOOD_PLAYCTL);
+		value &= ~(KIRKWOOD_PLAYCTL_I2S_EN | KIRKWOOD_PLAYCTL_SPDIF_EN);
+		writel(value, priv->io + KIRKWOOD_PLAYCTL);
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		value = readl(priv->io + KIRKWOOD_PLAYCTL);
+		value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE;
+		writel(value, priv->io + KIRKWOOD_PLAYCTL);
+		break;
+
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		value = readl(priv->io + KIRKWOOD_PLAYCTL);
+		value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE);
+		writel(value, priv->io + KIRKWOOD_PLAYCTL);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream,
+				int cmd, struct snd_soc_dai *dai)
+{
+	struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);
+	unsigned long value;
+
+	value = readl(priv->io + KIRKWOOD_RECCTL);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* stop audio, enable interrupts */
+		value = readl(priv->io + KIRKWOOD_RECCTL);
+		value |= KIRKWOOD_RECCTL_PAUSE;
+		writel(value, priv->io + KIRKWOOD_RECCTL);
+
+		value = readl(priv->io + KIRKWOOD_INT_MASK);
+		value |= KIRKWOOD_INT_CAUSE_REC_BYTES;
+		writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+		/* configure audio & enable i2s record */
+		value = readl(priv->io + KIRKWOOD_RECCTL);
+		value &= ~KIRKWOOD_RECCTL_BURST_MASK;
+		value &= ~KIRKWOOD_RECCTL_MONO;
+		value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE
+			| KIRKWOOD_RECCTL_SPDIF_EN);
+
+		if (priv->burst == 32)
+			value |= KIRKWOOD_RECCTL_BURST_32;
+		else
+			value |= KIRKWOOD_RECCTL_BURST_128;
+		value |= KIRKWOOD_RECCTL_I2S_EN;
+
+		writel(value, priv->io + KIRKWOOD_RECCTL);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		/* stop audio, disable interrupts */
+		value = readl(priv->io + KIRKWOOD_RECCTL);
+		value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE;
+		writel(value, priv->io + KIRKWOOD_RECCTL);
+
+		value = readl(priv->io + KIRKWOOD_INT_MASK);
+		value &= ~KIRKWOOD_INT_CAUSE_REC_BYTES;
+		writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+		/* disable all records */
+		value = readl(priv->io + KIRKWOOD_RECCTL);
+		value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN);
+		writel(value, priv->io + KIRKWOOD_RECCTL);
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		value = readl(priv->io + KIRKWOOD_RECCTL);
+		value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE;
+		writel(value, priv->io + KIRKWOOD_RECCTL);
+		break;
+
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		value = readl(priv->io + KIRKWOOD_RECCTL);
+		value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE);
+		writel(value, priv->io + KIRKWOOD_RECCTL);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int kirkwood_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+			       struct snd_soc_dai *dai)
+{
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return kirkwood_i2s_play_trigger(substream, cmd, dai);
+	else
+		return kirkwood_i2s_rec_trigger(substream, cmd, dai);
+
+	return 0;
+}
+
+static int kirkwood_i2s_probe(struct snd_soc_dai *dai)
+{
+	struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);
+	unsigned long value;
+	unsigned int reg_data;
+
+	/* put system in a "safe" state : */
+	/* disable audio interrupts */
+	writel(0xffffffff, priv->io + KIRKWOOD_INT_CAUSE);
+	writel(0, priv->io + KIRKWOOD_INT_MASK);
+
+	reg_data = readl(priv->io + 0x1200);
+	reg_data &= (~(0x333FF8));
+	reg_data |= 0x111D18;
+	writel(reg_data, priv->io + 0x1200);
+
+	msleep(500);
+
+	reg_data = readl(priv->io + 0x1200);
+	reg_data &= (~(0x333FF8));
+	reg_data |= 0x111D18;
+	writel(reg_data, priv->io + 0x1200);
+
+	/* disable playback/record */
+	value = readl(priv->io + KIRKWOOD_PLAYCTL);
+	value &= ~(KIRKWOOD_PLAYCTL_I2S_EN|KIRKWOOD_PLAYCTL_SPDIF_EN);
+	writel(value, priv->io + KIRKWOOD_PLAYCTL);
+
+	value = readl(priv->io + KIRKWOOD_RECCTL);
+	value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN);
+	writel(value, priv->io + KIRKWOOD_RECCTL);
+
+	return 0;
+
+}
+
+static int kirkwood_i2s_remove(struct snd_soc_dai *dai)
+{
+	return 0;
+}
+
+static struct snd_soc_dai_ops kirkwood_i2s_dai_ops = {
+	.startup	= kirkwood_i2s_startup,
+	.trigger	= kirkwood_i2s_trigger,
+	.hw_params      = kirkwood_i2s_hw_params,
+	.set_fmt        = kirkwood_i2s_set_fmt,
+};
+
+
+static struct snd_soc_dai_driver kirkwood_i2s_dai = {
+	.probe = kirkwood_i2s_probe,
+	.remove = kirkwood_i2s_remove,
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = KIRKWOOD_I2S_RATES,
+		.formats = KIRKWOOD_I2S_FORMATS,},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = KIRKWOOD_I2S_RATES,
+		.formats = KIRKWOOD_I2S_FORMATS,},
+	.ops = &kirkwood_i2s_dai_ops,
+};
+
+static __devinit int kirkwood_i2s_dev_probe(struct platform_device *pdev)
+{
+	struct resource *mem;
+	struct kirkwood_asoc_platform_data *data =
+		pdev->dev.platform_data;
+	struct kirkwood_dma_data *priv;
+	int err;
+
+	priv = kzalloc(sizeof(struct kirkwood_dma_data), GFP_KERNEL);
+	if (!priv) {
+		dev_err(&pdev->dev, "allocation failed\n");
+		err = -ENOMEM;
+		goto error;
+	}
+	dev_set_drvdata(&pdev->dev, priv);
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem) {
+		dev_err(&pdev->dev, "platform_get_resource failed\n");
+		err = -ENXIO;
+		goto err_alloc;
+	}
+
+	priv->mem = request_mem_region(mem->start, SZ_16K, DRV_NAME);
+	if (!priv->mem) {
+		dev_err(&pdev->dev, "request_mem_region failed\n");
+		err = -EBUSY;
+		goto error;
+	}
+
+	priv->io = ioremap(priv->mem->start, SZ_16K);
+	if (!priv->io) {
+		dev_err(&pdev->dev, "ioremap failed\n");
+		err = -ENOMEM;
+		goto err_iomem;
+	}
+
+	priv->irq = platform_get_irq(pdev, 0);
+	if (priv->irq <= 0) {
+		dev_err(&pdev->dev, "platform_get_irq failed\n");
+		err = -ENXIO;
+		goto err_ioremap;
+	}
+
+	if (!data || !data->dram) {
+		dev_err(&pdev->dev, "no platform data ?!\n");
+		err = -EINVAL;
+		goto err_ioremap;
+	}
+
+	priv->dram = data->dram;
+	priv->burst = data->burst;
+
+	return snd_soc_register_dai(&pdev->dev, &kirkwood_i2s_dai);
+
+err_ioremap:
+	iounmap(priv->io);
+err_iomem:
+	release_mem_region(priv->mem->start, SZ_16K);
+err_alloc:
+	kfree(priv);
+error:
+	return err;
+}
+
+static __devexit int kirkwood_i2s_dev_remove(struct platform_device *pdev)
+{
+	struct kirkwood_dma_data *priv = dev_get_drvdata(&pdev->dev);
+
+	snd_soc_unregister_dai(&pdev->dev);
+	iounmap(priv->io);
+	release_mem_region(priv->mem->start, SZ_16K);
+	kfree(priv);
+
+	return 0;
+}
+
+static struct platform_driver kirkwood_i2s_driver = {
+	.probe  = kirkwood_i2s_dev_probe,
+	.remove = kirkwood_i2s_dev_remove,
+	.driver = {
+		.name = DRV_NAME,
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init kirkwood_i2s_init(void)
+{
+	return platform_driver_register(&kirkwood_i2s_driver);
+}
+module_init(kirkwood_i2s_init);
+
+static void __exit kirkwood_i2s_exit(void)
+{
+	platform_driver_unregister(&kirkwood_i2s_driver);
+}
+module_exit(kirkwood_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Arnaud Patard, <arnaud.patard@rtp-net.org>");
+MODULE_DESCRIPTION("Kirkwood I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kirkwood-i2s");
diff --git a/linux/sound/soc/kirkwood/kirkwood-openrd.c b/linux/sound/soc/kirkwood/kirkwood-openrd.c
new file mode 100644
index 0000000..9d7c81e
--- /dev/null
+++ b/linux/sound/soc/kirkwood/kirkwood-openrd.c
@@ -0,0 +1,120 @@
+/*
+ * kirkwood-openrd.c
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <mach/kirkwood.h>
+#include <plat/audio.h>
+#include <asm/mach-types.h>
+#include "../codecs/cs42l51.h"
+
+static int openrd_client_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+	unsigned int freq, fmt;
+
+	fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
+	ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+	if (ret < 0)
+		return ret;
+
+	switch (params_rate(params)) {
+	default:
+	case 44100:
+		freq = 11289600;
+		break;
+	case 48000:
+		freq = 12288000;
+		break;
+	case 96000:
+		freq = 24576000;
+		break;
+	}
+
+	return snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN);
+
+}
+
+static struct snd_soc_ops openrd_client_ops = {
+	.hw_params = openrd_client_hw_params,
+};
+
+
+static struct snd_soc_dai_link openrd_client_dai[] = {
+{
+	.name = "CS42L51",
+	.stream_name = "CS42L51 HiFi",
+	.cpu_dai_name = "kirkwood-i2s",
+	.platform_name = "kirkwood-pcm-audio",
+	.codec_dai_name = "cs42l51-hifi",
+	.codec_name = "cs42l51-codec.0-004a",
+	.ops = &openrd_client_ops,
+},
+};
+
+
+static struct snd_soc_card openrd_client = {
+	.name = "OpenRD Client",
+	.dai_link = openrd_client_dai,
+	.num_links = ARRAY_SIZE(openrd_client_dai),
+};
+
+static struct platform_device *openrd_client_snd_device;
+
+static int __init openrd_client_init(void)
+{
+	int ret;
+
+	if (!machine_is_openrd_client())
+		return 0;
+
+	openrd_client_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!openrd_client_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(openrd_client_snd_device,
+			&openrd_client);
+
+	ret = platform_device_add(openrd_client_snd_device);
+	if (ret) {
+		printk(KERN_ERR "%s: platform_device_add failed\n", __func__);
+		platform_device_put(openrd_client_snd_device);
+	}
+
+	return ret;
+}
+
+static void __exit openrd_client_exit(void)
+{
+	platform_device_unregister(openrd_client_snd_device);
+}
+
+module_init(openrd_client_init);
+module_exit(openrd_client_exit);
+
+/* Module information */
+MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
+MODULE_DESCRIPTION("ALSA SoC OpenRD Client");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:soc-audio");
diff --git a/linux/sound/soc/kirkwood/kirkwood.h b/linux/sound/soc/kirkwood/kirkwood.h
new file mode 100644
index 0000000..bb6e6a5
--- /dev/null
+++ b/linux/sound/soc/kirkwood/kirkwood.h
@@ -0,0 +1,129 @@
+/*
+ * kirkwood.h
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ *
+ *  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.
+ */
+
+#ifndef _KIRKWOOD_AUDIO_H
+#define _KIRKWOOD_AUDIO_H
+
+#define KIRKWOOD_RECORD_WIN			0
+#define KIRKWOOD_PLAYBACK_WIN			1
+#define KIRKWOOD_MAX_AUDIO_WIN			2
+
+#define KIRKWOOD_AUDIO_WIN_BASE_REG(win)	(0xA00 + ((win)<<3))
+#define KIRKWOOD_AUDIO_WIN_CTRL_REG(win)	(0xA04 + ((win)<<3))
+
+
+#define KIRKWOOD_RECCTL			0x1000
+#define KIRKWOOD_RECCTL_SPDIF_EN		(1<<11)
+#define KIRKWOOD_RECCTL_I2S_EN			(1<<10)
+#define KIRKWOOD_RECCTL_PAUSE			(1<<9)
+#define KIRKWOOD_RECCTL_MUTE			(1<<8)
+#define KIRKWOOD_RECCTL_BURST_MASK		(3<<5)
+#define KIRKWOOD_RECCTL_BURST_128		(2<<5)
+#define KIRKWOOD_RECCTL_BURST_32		(1<<5)
+#define KIRKWOOD_RECCTL_MONO			(1<<4)
+#define KIRKWOOD_RECCTL_MONO_CHAN_RIGHT	(1<<3)
+#define KIRKWOOD_RECCTL_MONO_CHAN_LEFT		(0<<3)
+#define KIRKWOOD_RECCTL_SIZE_MASK		(7<<0)
+#define KIRKWOOD_RECCTL_SIZE_16		(7<<0)
+#define KIRKWOOD_RECCTL_SIZE_16_C		(3<<0)
+#define KIRKWOOD_RECCTL_SIZE_20		(2<<0)
+#define KIRKWOOD_RECCTL_SIZE_24		(1<<0)
+#define KIRKWOOD_RECCTL_SIZE_32		(0<<0)
+
+#define KIRKWOOD_REC_BUF_ADDR			0x1004
+#define KIRKWOOD_REC_BUF_SIZE			0x1008
+#define KIRKWOOD_REC_BYTE_COUNT			0x100C
+
+#define KIRKWOOD_PLAYCTL			0x1100
+#define KIRKWOOD_PLAYCTL_PLAY_BUSY		(1<<16)
+#define KIRKWOOD_PLAYCTL_BURST_MASK		(3<<11)
+#define KIRKWOOD_PLAYCTL_BURST_128		(2<<11)
+#define KIRKWOOD_PLAYCTL_BURST_32		(1<<11)
+#define KIRKWOOD_PLAYCTL_PAUSE			(1<<9)
+#define KIRKWOOD_PLAYCTL_SPDIF_MUTE		(1<<8)
+#define KIRKWOOD_PLAYCTL_MONO_MASK		(3<<5)
+#define KIRKWOOD_PLAYCTL_MONO_BOTH		(3<<5)
+#define KIRKWOOD_PLAYCTL_MONO_OFF		(0<<5)
+#define KIRKWOOD_PLAYCTL_I2S_MUTE		(1<<7)
+#define KIRKWOOD_PLAYCTL_SPDIF_EN		(1<<4)
+#define KIRKWOOD_PLAYCTL_I2S_EN		(1<<3)
+#define KIRKWOOD_PLAYCTL_SIZE_MASK		(7<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_16		(7<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_16_C		(3<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_20		(2<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_24		(1<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_32		(0<<0)
+
+#define KIRKWOOD_PLAY_BUF_ADDR			0x1104
+#define KIRKWOOD_PLAY_BUF_SIZE			0x1108
+#define KIRKWOOD_PLAY_BYTE_COUNT		0x110C
+
+#define KIRKWOOD_DCO_CTL			0x1204
+#define KIRKWOOD_DCO_CTL_OFFSET_MASK		(0xFFF<<2)
+#define KIRKWOOD_DCO_CTL_OFFSET_0		(0x800<<2)
+#define KIRKWOOD_DCO_CTL_FREQ_MASK		(3<<0)
+#define KIRKWOOD_DCO_CTL_FREQ_11		(0<<0)
+#define KIRKWOOD_DCO_CTL_FREQ_12		(1<<0)
+#define KIRKWOOD_DCO_CTL_FREQ_24		(2<<0)
+
+#define KIRKWOOD_DCO_SPCR_STATUS		0x120c
+#define KIRKWOOD_DCO_SPCR_STATUS_DCO_LOCK	(1<<16)
+
+#define KIRKWOOD_ERR_CAUSE			0x1300
+#define KIRKWOOD_ERR_MASK			0x1304
+
+#define KIRKWOOD_INT_CAUSE			0x1308
+#define KIRKWOOD_INT_MASK			0x130C
+#define KIRKWOOD_INT_CAUSE_PLAY_BYTES		(1<<14)
+#define KIRKWOOD_INT_CAUSE_REC_BYTES		(1<<13)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_END	(1<<7)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_3Q		(1<<6)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_HALF	(1<<5)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_1Q		(1<<4)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_END		(1<<3)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_3Q		(1<<2)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_HALF	(1<<1)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_1Q		(1<<0)
+
+#define KIRKWOOD_REC_BYTE_INT_COUNT		0x1310
+#define KIRKWOOD_PLAY_BYTE_INT_COUNT		0x1314
+#define KIRKWOOD_BYTE_INT_COUNT_MASK		0xffffff
+
+#define KIRKWOOD_I2S_PLAYCTL			0x2508
+#define KIRKWOOD_I2S_RECCTL			0x2408
+#define KIRKWOOD_I2S_CTL_JUST_MASK		(0xf<<26)
+#define KIRKWOOD_I2S_CTL_LJ			(0<<26)
+#define KIRKWOOD_I2S_CTL_I2S			(5<<26)
+#define KIRKWOOD_I2S_CTL_RJ			(8<<26)
+#define KIRKWOOD_I2S_CTL_SIZE_MASK		(3<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_16		(3<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_20		(2<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_24		(1<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_32		(0<<30)
+
+#define KIRKWOOD_AUDIO_BUF_MAX			(16*1024*1024)
+
+/* Theses values come from the marvell alsa driver */
+/* need to find where they come from               */
+#define KIRKWOOD_SND_MIN_PERIODS		8
+#define KIRKWOOD_SND_MAX_PERIODS		16
+#define KIRKWOOD_SND_MIN_PERIOD_BYTES		0x4000
+#define KIRKWOOD_SND_MAX_PERIOD_BYTES		0x4000
+
+struct kirkwood_dma_data {
+	struct resource *mem;
+	void __iomem *io;
+	int irq;
+	int burst;
+	struct mbus_dram_target_info *dram;
+};
+
+#endif
diff --git a/linux/sound/soc/nuc900/Kconfig b/linux/sound/soc/nuc900/Kconfig
new file mode 100644
index 0000000..a0ed1c6
--- /dev/null
+++ b/linux/sound/soc/nuc900/Kconfig
@@ -0,0 +1,27 @@
+##
+## NUC900 series AC97 API
+##
+config SND_SOC_NUC900
+	tristate "SoC Audio for NUC900 series"
+	depends on ARCH_W90X900
+	help
+	  This option enables support for AC97 mode on the NUC900 SoC.
+
+config SND_SOC_NUC900_AC97
+	tristate
+	select AC97_BUS
+	select SND_AC97_CODEC
+	select SND_SOC_AC97_BUS
+
+
+##
+## Boards
+##
+config SND_SOC_NUC900EVB
+	tristate "NUC900 AC97 support for demo board"
+	depends on SND_SOC_NUC900
+	select SND_SOC_NUC900_AC97
+	select SND_SOC_AC97_CODEC
+	help
+	  Select this option to enable audio (AC97) on the
+	  NUC900 demoboard.
diff --git a/linux/sound/soc/nuc900/Makefile b/linux/sound/soc/nuc900/Makefile
new file mode 100644
index 0000000..7e46c71
--- /dev/null
+++ b/linux/sound/soc/nuc900/Makefile
@@ -0,0 +1,11 @@
+# NUC900 series audio
+snd-soc-nuc900-pcm-objs := nuc900-pcm.o
+snd-soc-nuc900-ac97-objs := nuc900-ac97.o
+
+obj-$(CONFIG_SND_SOC_NUC900) += snd-soc-nuc900-pcm.o
+obj-$(CONFIG_SND_SOC_NUC900_AC97) += snd-soc-nuc900-ac97.o
+
+# Boards
+snd-soc-nuc900-audio-objs := nuc900-audio.o
+
+obj-$(CONFIG_SND_SOC_NUC900EVB) += snd-soc-nuc900-audio.o
diff --git a/linux/sound/soc/nuc900/nuc900-ac97.c b/linux/sound/soc/nuc900/nuc900-ac97.c
new file mode 100644
index 0000000..dac6732
--- /dev/null
+++ b/linux/sound/soc/nuc900/nuc900-ac97.c
@@ -0,0 +1,424 @@
+/*
+ * Copyright (c) 2009-2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.com>
+ *
+ * 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;version 2 of the License.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/suspend.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <linux/clk.h>
+
+#include <mach/mfp.h>
+
+#include "nuc900-audio.h"
+
+static DEFINE_MUTEX(ac97_mutex);
+struct nuc900_audio *nuc900_ac97_data;
+
+static int nuc900_checkready(void)
+{
+	struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+
+	if (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS0) & CODEC_READY))
+		return -EPERM;
+
+	return 0;
+}
+
+/* AC97 controller reads codec register */
+static unsigned short nuc900_ac97_read(struct snd_ac97 *ac97,
+					unsigned short reg)
+{
+	struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+	unsigned long timeout = 0x10000, val;
+
+	mutex_lock(&ac97_mutex);
+
+	val = nuc900_checkready();
+	if (val) {
+		dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
+		goto out;
+	}
+
+	/* set the R_WB bit and write register index */
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, R_WB | reg);
+
+	/* set the valid frame bit and valid slots */
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+	val |= (VALID_FRAME | SLOT1_VALID);
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val);
+
+	udelay(100);
+
+	/* polling the AC_R_FINISH */
+	while (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON) & AC_R_FINISH)
+								&& timeout--)
+		mdelay(1);
+
+	if (!timeout) {
+		dev_err(nuc900_audio->dev, "AC97 read register time out !\n");
+		val = -EPERM;
+		goto out;
+	}
+
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0) ;
+	val &= ~SLOT1_VALID;
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val);
+
+	if (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS1) >> 2 != reg) {
+		dev_err(nuc900_audio->dev,
+				"R_INDEX of REG_ACTL_ACIS1 not match!\n");
+	}
+
+	udelay(100);
+	val = (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS2) & 0xFFFF);
+
+out:
+	mutex_unlock(&ac97_mutex);
+	return val;
+}
+
+/* AC97 controller writes to codec register */
+static void nuc900_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+				unsigned short val)
+{
+	struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+	unsigned long tmp, timeout = 0x10000;
+
+	mutex_lock(&ac97_mutex);
+
+	tmp = nuc900_checkready();
+	if (tmp)
+		dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
+
+	/* clear the R_WB bit and write register index */
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, reg);
+
+	/* write register value */
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS2, val);
+
+	/* set the valid frame bit and valid slots */
+	tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+	tmp |= SLOT1_VALID | SLOT2_VALID | VALID_FRAME;
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+	udelay(100);
+
+	/* polling the AC_W_FINISH */
+	while ((AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON) & AC_W_FINISH)
+								&& timeout--)
+		mdelay(1);
+
+	if (!timeout)
+		dev_err(nuc900_audio->dev, "AC97 write register time out !\n");
+
+	tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+	tmp &= ~(SLOT1_VALID | SLOT2_VALID);
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+	mutex_unlock(&ac97_mutex);
+
+}
+
+static void nuc900_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+	struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+	unsigned long val;
+
+	mutex_lock(&ac97_mutex);
+
+	/* warm reset AC 97 */
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
+	val |= AC_W_RES;
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
+
+	udelay(100);
+
+	val = nuc900_checkready();
+	if (val)
+		dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
+
+	mutex_unlock(&ac97_mutex);
+}
+
+static void nuc900_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+	struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+	unsigned long val;
+
+	mutex_lock(&ac97_mutex);
+
+	/* reset Audio Controller */
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+	val |= ACTL_RESET_BIT;
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+	val &= (~ACTL_RESET_BIT);
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+	/* reset AC-link interface */
+
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+	val |= AC_RESET;
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+	val &= ~AC_RESET;
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+	/* cold reset AC 97 */
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
+	val |= AC_C_RES;
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
+
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
+	val &= (~AC_C_RES);
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
+
+	udelay(100);
+
+	mutex_unlock(&ac97_mutex);
+
+}
+
+/* AC97 controller operations */
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read		= nuc900_ac97_read,
+	.write		= nuc900_ac97_write,
+	.reset		= nuc900_ac97_cold_reset,
+	.warm_reset	= nuc900_ac97_warm_reset,
+}
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int nuc900_ac97_trigger(struct snd_pcm_substream *substream,
+				int cmd, struct snd_soc_dai *dai)
+{
+	struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+	int ret;
+	unsigned long val, tmp;
+
+	ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+			tmp |= (SLOT3_VALID | SLOT4_VALID | VALID_FRAME);
+			AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+			tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
+			tmp |= (P_DMA_END_IRQ | P_DMA_MIDDLE_IRQ);
+			AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, tmp);
+			val |= AC_PLAY;
+		} else {
+			tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
+			tmp |= (R_DMA_END_IRQ | R_DMA_MIDDLE_IRQ);
+
+			AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, tmp);
+			val |= AC_RECORD;
+		}
+
+		AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+			tmp &= ~(SLOT3_VALID | SLOT4_VALID);
+			AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+			AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, RESET_PRSR);
+			val &= ~AC_PLAY;
+		} else {
+			AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, RESET_PRSR);
+			val &= ~AC_RECORD;
+		}
+
+		AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int nuc900_ac97_probe(struct snd_soc_dai *dai)
+{
+	struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+	unsigned long val;
+
+	mutex_lock(&ac97_mutex);
+
+	/* enable unit clock */
+	clk_enable(nuc900_audio->clk);
+
+	/* enable audio controller and AC-link interface */
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+	val |= (IIS_AC_PIN_SEL | ACLINK_EN);
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
+
+	mutex_unlock(&ac97_mutex);
+
+	return 0;
+}
+
+static int nuc900_ac97_remove(struct snd_soc_dai *dai)
+{
+	struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+
+	clk_disable(nuc900_audio->clk);
+	return 0;
+}
+
+static struct snd_soc_dai_ops nuc900_ac97_dai_ops = {
+	.trigger	= nuc900_ac97_trigger,
+};
+
+static struct snd_soc_dai_driver nuc900_ac97_dai = {
+	.probe			= nuc900_ac97_probe,
+	.remove			= nuc900_ac97_remove,
+	.ac97_control		= 1,
+	.playback = {
+		.rates		= SNDRV_PCM_RATE_8000_48000,
+		.formats	= SNDRV_PCM_FMTBIT_S16_LE,
+		.channels_min	= 1,
+		.channels_max	= 2,
+	},
+	.capture = {
+		.rates		= SNDRV_PCM_RATE_8000_48000,
+		.formats	= SNDRV_PCM_FMTBIT_S16_LE,
+		.channels_min	= 1,
+		.channels_max	= 2,
+	},
+	.ops = &nuc900_ac97_dai_ops,
+};
+
+static int __devinit nuc900_ac97_drvprobe(struct platform_device *pdev)
+{
+	struct nuc900_audio *nuc900_audio;
+	int ret;
+
+	if (nuc900_ac97_data)
+		return -EBUSY;
+
+	nuc900_audio = kzalloc(sizeof(struct nuc900_audio), GFP_KERNEL);
+	if (!nuc900_audio)
+		return -ENOMEM;
+
+	spin_lock_init(&nuc900_audio->lock);
+
+	nuc900_audio->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!nuc900_audio->res) {
+		ret = -ENODEV;
+		goto out0;
+	}
+
+	if (!request_mem_region(nuc900_audio->res->start,
+			resource_size(nuc900_audio->res), pdev->name)) {
+		ret = -EBUSY;
+		goto out0;
+	}
+
+	nuc900_audio->mmio = ioremap(nuc900_audio->res->start,
+					resource_size(nuc900_audio->res));
+	if (!nuc900_audio->mmio) {
+		ret = -ENOMEM;
+		goto out1;
+	}
+
+	nuc900_audio->clk = clk_get(&pdev->dev, NULL);
+	if (IS_ERR(nuc900_audio->clk)) {
+		ret = PTR_ERR(nuc900_audio->clk);
+		goto out2;
+	}
+
+	nuc900_audio->irq_num = platform_get_irq(pdev, 0);
+	if (!nuc900_audio->irq_num) {
+		ret = -EBUSY;
+		goto out2;
+	}
+
+	nuc900_ac97_data = nuc900_audio;
+
+	ret = snd_soc_register_dai(&pdev->dev, &nuc900_ac97_dai);
+	if (ret)
+		goto out3;
+
+	mfp_set_groupg(nuc900_audio->dev); /* enbale ac97 multifunction pin*/
+
+	return 0;
+
+out3:
+	clk_put(nuc900_audio->clk);
+out2:
+	iounmap(nuc900_audio->mmio);
+out1:
+	release_mem_region(nuc900_audio->res->start,
+					resource_size(nuc900_audio->res));
+out0:
+	kfree(nuc900_audio);
+	return ret;
+}
+
+static int __devexit nuc900_ac97_drvremove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dai(&pdev->dev);
+
+	clk_put(nuc900_ac97_data->clk);
+	iounmap(nuc900_ac97_data->mmio);
+	release_mem_region(nuc900_ac97_data->res->start,
+				resource_size(nuc900_ac97_data->res));
+
+	kfree(nuc900_ac97_data);
+	nuc900_ac97_data = NULL;
+
+	return 0;
+}
+
+static struct platform_driver nuc900_ac97_driver = {
+	.driver	= {
+		.name	= "nuc900-ac97",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= nuc900_ac97_drvprobe,
+	.remove		= __devexit_p(nuc900_ac97_drvremove),
+};
+
+static int __init nuc900_ac97_init(void)
+{
+	return platform_driver_register(&nuc900_ac97_driver);
+}
+
+static void __exit nuc900_ac97_exit(void)
+{
+	platform_driver_unregister(&nuc900_ac97_driver);
+}
+
+module_init(nuc900_ac97_init);
+module_exit(nuc900_ac97_exit);
+
+MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>");
+MODULE_DESCRIPTION("NUC900 AC97 SoC driver!");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nuc900-ac97");
diff --git a/linux/sound/soc/nuc900/nuc900-audio.c b/linux/sound/soc/nuc900/nuc900-audio.c
new file mode 100644
index 0000000..161f5b6
--- /dev/null
+++ b/linux/sound/soc/nuc900/nuc900-audio.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.com>
+ *
+ * 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;version 2 of the License.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "nuc900-audio.h"
+
+static struct snd_soc_dai_link nuc900evb_ac97_dai = {
+	.name		= "AC97",
+	.stream_name	= "AC97 HiFi",
+	.cpu_dai_name	= "nuc900-ac97",
+	.codec_dai_name	= "ac97-hifi",
+	.codec_name	= "ac97-codec",
+	.platform_name	= "nuc900-pcm-audio",
+};
+
+static struct snd_soc_card nuc900evb_audio_machine = {
+	.name		= "NUC900EVB_AC97",
+	.dai_link	= &nuc900evb_ac97_dai,
+	.num_links	= 1,
+};
+
+static struct platform_device *nuc900evb_asoc_dev;
+
+static int __init nuc900evb_audio_init(void)
+{
+	int ret;
+
+	ret = -ENOMEM;
+	nuc900evb_asoc_dev = platform_device_alloc("soc-audio", -1);
+	if (!nuc900evb_asoc_dev)
+		goto out;
+
+	/* nuc900 board audio device */
+	platform_set_drvdata(nuc900evb_asoc_dev, &nuc900evb_audio_machine);
+
+	ret = platform_device_add(nuc900evb_asoc_dev);
+
+	if (ret) {
+		platform_device_put(nuc900evb_asoc_dev);
+		nuc900evb_asoc_dev = NULL;
+	}
+
+out:
+	return ret;
+}
+
+static void __exit nuc900evb_audio_exit(void)
+{
+	platform_device_unregister(nuc900evb_asoc_dev);
+}
+
+module_init(nuc900evb_audio_init);
+module_exit(nuc900evb_audio_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("NUC900 Series ASoC audio support");
+MODULE_AUTHOR("Wan ZongShun");
diff --git a/linux/sound/soc/nuc900/nuc900-audio.h b/linux/sound/soc/nuc900/nuc900-audio.h
new file mode 100644
index 0000000..59f7e8e
--- /dev/null
+++ b/linux/sound/soc/nuc900/nuc900-audio.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.com>
+ *
+ * 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;version 2 of the License.
+ *
+ */
+
+#ifndef _NUC900_AUDIO_H
+#define _NUC900_AUDIO_H
+
+#include <linux/io.h>
+
+/* Audio Control Registers */
+#define ACTL_CON		0x00
+#define ACTL_RESET		0x04
+#define ACTL_RDSTB		0x08
+#define ACTL_RDST_LENGTH	0x0C
+#define ACTL_RDSTC		0x10
+#define ACTL_RSR		0x14
+#define ACTL_PDSTB		0x18
+#define ACTL_PDST_LENGTH	0x1C
+#define ACTL_PDSTC		0x20
+#define ACTL_PSR		0x24
+#define ACTL_IISCON		0x28
+#define ACTL_ACCON		0x2C
+#define ACTL_ACOS0		0x30
+#define ACTL_ACOS1		0x34
+#define ACTL_ACOS2		0x38
+#define ACTL_ACIS0		0x3C
+#define ACTL_ACIS1		0x40
+#define ACTL_ACIS2		0x44
+#define ACTL_COUNTER		0x48
+
+/* bit definition of REG_ACTL_CON register */
+#define R_DMA_IRQ		0x1000
+#define T_DMA_IRQ		0x0800
+#define IIS_AC_PIN_SEL		0x0100
+#define FIFO_TH			0x0080
+#define ADC_EN			0x0010
+#define M80_EN			0x0008
+#define ACLINK_EN		0x0004
+#define IIS_EN			0x0002
+
+/* bit definition of REG_ACTL_RESET register */
+#define W5691_PLAY		0x20000
+#define ACTL_RESET_BIT		0x10000
+#define RECORD_RIGHT_CHNNEL	0x08000
+#define RECORD_LEFT_CHNNEL	0x04000
+#define PLAY_RIGHT_CHNNEL	0x02000
+#define PLAY_LEFT_CHNNEL	0x01000
+#define DAC_PLAY		0x00800
+#define ADC_RECORD		0x00400
+#define M80_PLAY		0x00200
+#define AC_RECORD		0x00100
+#define AC_PLAY			0x00080
+#define IIS_RECORD		0x00040
+#define IIS_PLAY		0x00020
+#define DAC_RESET		0x00010
+#define ADC_RESET		0x00008
+#define M80_RESET		0x00004
+#define AC_RESET		0x00002
+#define IIS_RESET		0x00001
+
+/* bit definition of REG_ACTL_ACCON register */
+#define AC_BCLK_PU_EN		0x20
+#define AC_R_FINISH		0x10
+#define AC_W_FINISH		0x08
+#define AC_W_RES		0x04
+#define AC_C_RES		0x02
+
+/* bit definition of ACTL_RSR register */
+#define R_FIFO_EMPTY		0x04
+#define R_DMA_END_IRQ		0x02
+#define R_DMA_MIDDLE_IRQ	0x01
+
+/* bit definition of ACTL_PSR register */
+#define P_FIFO_EMPTY		0x04
+#define P_DMA_END_IRQ		0x02
+#define P_DMA_MIDDLE_IRQ	0x01
+
+/* bit definition of ACTL_ACOS0 register */
+#define SLOT1_VALID		0x01
+#define SLOT2_VALID		0x02
+#define SLOT3_VALID		0x04
+#define SLOT4_VALID		0x08
+#define VALID_FRAME		0x10
+
+/* bit definition of ACTL_ACOS1 register */
+#define R_WB			0x80
+
+#define CODEC_READY		0x10
+#define RESET_PRSR		0x00
+#define AUDIO_WRITE(addr, val)	__raw_writel(val, addr)
+#define AUDIO_READ(addr)	__raw_readl(addr)
+
+struct nuc900_audio {
+	void __iomem *mmio;
+	spinlock_t lock;
+	dma_addr_t dma_addr[2];
+	unsigned long buffersize[2];
+	unsigned long irq_num;
+	struct snd_pcm_substream *substream;
+	struct resource *res;
+	struct clk *clk;
+	struct device *dev;
+
+};
+
+extern struct nuc900_audio *nuc900_ac97_data;
+
+#endif /*end _NUC900_AUDIO_H */
diff --git a/linux/sound/soc/nuc900/nuc900-pcm.c b/linux/sound/soc/nuc900/nuc900-pcm.c
new file mode 100644
index 0000000..8263f56
--- /dev/null
+++ b/linux/sound/soc/nuc900/nuc900-pcm.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright (c) 2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.com>
+ *
+ * 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;version 2 of the License.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+
+#include "nuc900-audio.h"
+
+static const struct snd_pcm_hardware nuc900_pcm_hardware = {
+	.info			= SNDRV_PCM_INFO_INTERLEAVED |
+					SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					SNDRV_PCM_INFO_MMAP |
+					SNDRV_PCM_INFO_MMAP_VALID |
+					SNDRV_PCM_INFO_PAUSE |
+					SNDRV_PCM_INFO_RESUME,
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= 4*1024,
+	.period_bytes_min	= 1*1024,
+	.period_bytes_max	= 4*1024,
+	.periods_min		= 1,
+	.periods_max		= 1024,
+};
+
+static int nuc900_dma_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nuc900_audio *nuc900_audio = runtime->private_data;
+	unsigned long flags;
+	int ret = 0;
+
+	ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+	if (ret < 0)
+		return ret;
+
+	spin_lock_irqsave(&nuc900_audio->lock, flags);
+
+	nuc900_audio->substream = substream;
+	nuc900_audio->dma_addr[substream->stream] = runtime->dma_addr;
+	nuc900_audio->buffersize[substream->stream] =
+						params_buffer_bytes(params);
+
+	spin_unlock_irqrestore(&nuc900_audio->lock, flags);
+
+	return ret;
+}
+
+static void nuc900_update_dma_register(struct snd_pcm_substream *substream,
+				dma_addr_t dma_addr, size_t count)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nuc900_audio *nuc900_audio = runtime->private_data;
+	void __iomem *mmio_addr, *mmio_len;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		mmio_addr = nuc900_audio->mmio + ACTL_PDSTB;
+		mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH;
+	} else {
+		mmio_addr = nuc900_audio->mmio + ACTL_RDSTB;
+		mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH;
+	}
+
+	AUDIO_WRITE(mmio_addr, dma_addr);
+	AUDIO_WRITE(mmio_len, count);
+}
+
+static void nuc900_dma_start(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nuc900_audio *nuc900_audio = runtime->private_data;
+	unsigned long val;
+
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+	val |= (T_DMA_IRQ | R_DMA_IRQ);
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
+}
+
+static void nuc900_dma_stop(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nuc900_audio *nuc900_audio = runtime->private_data;
+	unsigned long val;
+
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+	val &= ~(T_DMA_IRQ | R_DMA_IRQ);
+	AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
+}
+
+static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id)
+{
+	struct snd_pcm_substream *substream = dev_id;
+	struct nuc900_audio *nuc900_audio = substream->runtime->private_data;
+	unsigned long val;
+
+	spin_lock(&nuc900_audio->lock);
+
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+
+	if (val & R_DMA_IRQ) {
+		AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ);
+
+		val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
+
+		if (val & R_DMA_MIDDLE_IRQ) {
+			val |= R_DMA_MIDDLE_IRQ;
+			AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
+		}
+
+		if (val & R_DMA_END_IRQ) {
+			val |= R_DMA_END_IRQ;
+			AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
+		}
+	} else if (val & T_DMA_IRQ) {
+		AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ);
+
+		val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
+
+		if (val & P_DMA_MIDDLE_IRQ) {
+			val |= P_DMA_MIDDLE_IRQ;
+			AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
+		}
+
+		if (val & P_DMA_END_IRQ) {
+			val |= P_DMA_END_IRQ;
+			AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
+		}
+	} else {
+		dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n");
+		spin_unlock(&nuc900_audio->lock);
+		return IRQ_HANDLED;
+	}
+
+	spin_unlock(&nuc900_audio->lock);
+
+	snd_pcm_period_elapsed(substream);
+
+	return IRQ_HANDLED;
+}
+
+static int nuc900_dma_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int nuc900_dma_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nuc900_audio *nuc900_audio = runtime->private_data;
+	unsigned long flags, val;
+	int ret = 0;
+
+	spin_lock_irqsave(&nuc900_audio->lock, flags);
+
+	nuc900_update_dma_register(substream,
+				nuc900_audio->dma_addr[substream->stream],
+				nuc900_audio->buffersize[substream->stream]);
+
+	val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+
+	switch (runtime->channels) {
+	case 1:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
+			val |= PLAY_RIGHT_CHNNEL;
+		} else {
+			val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
+			val |= RECORD_RIGHT_CHNNEL;
+		}
+		AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+		break;
+	case 2:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
+		else
+			val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
+		AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	spin_unlock_irqrestore(&nuc900_audio->lock, flags);
+	return ret;
+}
+
+static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		nuc900_dma_start(substream);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		nuc900_dma_stop(substream);
+		break;
+
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+int nuc900_dma_getposition(struct snd_pcm_substream *substream,
+					dma_addr_t *src, dma_addr_t *dst)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nuc900_audio *nuc900_audio = runtime->private_data;
+
+	if (src != NULL)
+		*src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC);
+
+	if (dst != NULL)
+		*dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC);
+
+	return 0;
+}
+
+static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	dma_addr_t src, dst;
+	unsigned long res;
+
+	nuc900_dma_getposition(substream, &src, &dst);
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		res = dst - runtime->dma_addr;
+	else
+		res = src - runtime->dma_addr;
+
+	return bytes_to_frames(substream->runtime, res);
+}
+
+static int nuc900_dma_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nuc900_audio *nuc900_audio;
+
+	snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware);
+
+	nuc900_audio = nuc900_ac97_data;
+
+	if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt,
+			IRQF_DISABLED, "nuc900-dma", substream))
+		return -EBUSY;
+
+	runtime->private_data = nuc900_audio;
+
+	return 0;
+}
+
+static int nuc900_dma_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct nuc900_audio *nuc900_audio = runtime->private_data;
+
+	free_irq(nuc900_audio->irq_num, substream);
+
+	return 0;
+}
+
+static int nuc900_dma_mmap(struct snd_pcm_substream *substream,
+	struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+					runtime->dma_area,
+					runtime->dma_addr,
+					runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops nuc900_dma_ops = {
+	.open		= nuc900_dma_open,
+	.close		= nuc900_dma_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= nuc900_dma_hw_params,
+	.hw_free	= nuc900_dma_hw_free,
+	.prepare	= nuc900_dma_prepare,
+	.trigger	= nuc900_dma_trigger,
+	.pointer	= nuc900_dma_pointer,
+	.mmap		= nuc900_dma_mmap,
+};
+
+static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32);
+static int nuc900_dma_new(struct snd_card *card,
+	struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &nuc900_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+		card->dev, 4 * 1024, (4 * 1024) - 1);
+
+	return 0;
+}
+
+static struct snd_soc_platform_driver nuc900_soc_platform = {
+	.ops		= &nuc900_dma_ops,
+	.pcm_new	= nuc900_dma_new,
+	.pcm_free	= nuc900_dma_free_dma_buffers,
+};
+
+static int __devinit nuc900_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &nuc900_soc_platform);
+}
+
+static int __devexit nuc900_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver nuc900_pcm_driver = {
+	.driver = {
+			.name = "nuc900-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = nuc900_soc_platform_probe,
+	.remove = __devexit_p(nuc900_soc_platform_remove),
+};
+
+static int __init nuc900_pcm_init(void)
+{
+	return platform_driver_register(&nuc900_pcm_driver);
+}
+module_init(nuc900_pcm_init);
+
+static void __exit nuc900_pcm_exit(void)
+{
+	platform_driver_unregister(&nuc900_pcm_driver);
+}
+module_exit(nuc900_pcm_exit);
+
+MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>");
+MODULE_DESCRIPTION("nuc900 Audio DMA module");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/Kconfig b/linux/sound/soc/omap/Kconfig
new file mode 100644
index 0000000..a088db6
--- /dev/null
+++ b/linux/sound/soc/omap/Kconfig
@@ -0,0 +1,142 @@
+config SND_OMAP_SOC
+	tristate "SoC Audio for the Texas Instruments OMAP chips"
+	depends on ARCH_OMAP
+
+config SND_OMAP_SOC_MCBSP
+	tristate
+	select OMAP_MCBSP
+
+config SND_OMAP_SOC_MCPDM
+	tristate
+
+config SND_OMAP_SOC_N810
+	tristate "SoC Audio support for Nokia N810"
+	depends on SND_OMAP_SOC && MACH_NOKIA_N810 && I2C
+	depends on OMAP_MUX
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TLV320AIC3X
+	help
+	  Say Y if you want to add support for SoC audio on Nokia N810.
+
+config SND_OMAP_SOC_RX51
+	tristate "SoC Audio support for Nokia RX-51"
+	depends on SND_OMAP_SOC && MACH_NOKIA_RX51
+	select OMAP_MCBSP
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TLV320AIC3X
+	help
+	  Say Y if you want to add support for SoC audio on Nokia RX-51
+	  hardware. This is also known as Nokia N900 product.
+
+config SND_OMAP_SOC_AMS_DELTA
+	tristate "SoC Audio support for Amstrad E3 (Delta) videophone"
+	depends on SND_OMAP_SOC && MACH_AMS_DELTA
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_CX20442
+	help
+	  Say Y  if you want to add support  for SoC audio device  connected to
+	  a handset and a speakerphone found on Amstrad E3 (Delta) videophone.
+
+	  Note that in order to get those devices fully supported,  you have to
+	  build  the kernel  with  standard  serial port  driver  included  and
+	  configured for at least 4 ports.  Then, from userspace, you must load
+	  a line discipline #19 on the modem (ttyS3) serial line.  The simplest
+	  way to achieve this is to install util-linux-ng  and use the included
+	  ldattach  utility.  This  can be  started  automatically  from  udev,
+	  a simple rule like this one should do the trick (it does for me):
+	  	ACTION=="add", KERNEL=="controlC0", \
+				RUN+="/usr/sbin/ldattach 19 /dev/ttyS3"
+
+config SND_OMAP_SOC_OSK5912
+	tristate "SoC Audio support for omap osk5912"
+	depends on SND_OMAP_SOC && MACH_OMAP_OSK && I2C
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TLV320AIC23
+	help
+	  Say Y if you want to add support for SoC audio on osk5912.
+
+config SND_OMAP_SOC_OVERO
+	tristate "SoC Audio support for Gumstix Overo and CompuLab CM-T35"
+	depends on TWL4030_CORE && SND_OMAP_SOC && (MACH_OVERO || MACH_CM_T35)
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TWL4030
+	help
+	  Say Y if you want to add support for SoC audio on the
+	  Gumstix Overo or CompuLab CM-T35
+
+config SND_OMAP_SOC_OMAP2EVM
+	tristate "SoC Audio support for OMAP2EVM board"
+	depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP2EVM
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TWL4030
+	help
+	  Say Y if you want to add support for SoC audio on the omap2evm board.
+
+config SND_OMAP_SOC_OMAP3EVM
+	tristate "SoC Audio support for OMAP3EVM board"
+	depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3EVM
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TWL4030
+	help
+	  Say Y if you want to add support for SoC audio on the omap3evm board.
+
+config SND_OMAP_SOC_AM3517EVM
+	tristate "SoC Audio support for OMAP3517 / AM3517 EVM"
+	depends on SND_OMAP_SOC && MACH_OMAP3517EVM && I2C
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TLV320AIC23
+	help
+	  Say Y if you want to add support for SoC audio on the OMAP3517 / AM3517
+	  EVM.
+
+config SND_OMAP_SOC_SDP3430
+	tristate "SoC Audio support for Texas Instruments SDP3430"
+	depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_3430SDP
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TWL4030
+	help
+	  Say Y if you want to add support for SoC audio on Texas Instruments
+	  SDP3430.
+
+config SND_OMAP_SOC_SDP4430
+	tristate "SoC Audio support for Texas Instruments SDP4430"
+	depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_4430SDP
+	select SND_OMAP_SOC_MCPDM
+	select SND_SOC_TWL6040
+	help
+	  Say Y if you want to add support for SoC audio on Texas Instruments
+	  SDP4430.
+
+config SND_OMAP_SOC_OMAP3_PANDORA
+	tristate "SoC Audio support for OMAP3 Pandora"
+	depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3_PANDORA
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TWL4030
+	help
+	  Say Y if you want to add support for SoC audio on the OMAP3 Pandora.
+
+config SND_OMAP_SOC_OMAP3_BEAGLE
+	tristate "SoC Audio support for OMAP3 Beagle and Devkit8000"
+	depends on TWL4030_CORE && SND_OMAP_SOC
+	depends on (MACH_OMAP3_BEAGLE || MACH_DEVKIT8000)
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TWL4030
+	help
+	  Say Y if you want to add support for SoC audio on the Beagleboard or
+	  the clone Devkit8000.
+
+config SND_OMAP_SOC_ZOOM2
+	tristate "SoC Audio support for Zoom2"
+	depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_ZOOM2
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TWL4030
+	help
+	  Say Y if you want to add support for Soc audio on Zoom2 board.
+
+config SND_OMAP_SOC_IGEP0020
+	tristate "SoC Audio support for IGEP v2"
+	depends on TWL4030_CORE && SND_OMAP_SOC && MACH_IGEP0020
+	select SND_OMAP_SOC_MCBSP
+	select SND_SOC_TWL4030
+	help
+	  Say Y if you want to add support for Soc audio on IGEP v2 board.
diff --git a/linux/sound/soc/omap/Makefile b/linux/sound/soc/omap/Makefile
new file mode 100644
index 0000000..ba9fc65
--- /dev/null
+++ b/linux/sound/soc/omap/Makefile
@@ -0,0 +1,39 @@
+# OMAP Platform Support
+snd-soc-omap-objs := omap-pcm.o
+snd-soc-omap-mcbsp-objs := omap-mcbsp.o
+snd-soc-omap-mcpdm-objs := omap-mcpdm.o mcpdm.o
+
+obj-$(CONFIG_SND_OMAP_SOC) += snd-soc-omap.o
+obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o
+obj-$(CONFIG_SND_OMAP_SOC_MCPDM) += snd-soc-omap-mcpdm.o
+
+# OMAP Machine Support
+snd-soc-n810-objs := n810.o
+snd-soc-rx51-objs := rx51.o
+snd-soc-ams-delta-objs := ams-delta.o
+snd-soc-osk5912-objs := osk5912.o
+snd-soc-overo-objs := overo.o
+snd-soc-omap2evm-objs := omap2evm.o
+snd-soc-omap3evm-objs := omap3evm.o
+snd-soc-am3517evm-objs := am3517evm.o
+snd-soc-sdp3430-objs := sdp3430.o
+snd-soc-sdp4430-objs := sdp4430.o
+snd-soc-omap3pandora-objs := omap3pandora.o
+snd-soc-omap3beagle-objs := omap3beagle.o
+snd-soc-zoom2-objs := zoom2.o
+snd-soc-igep0020-objs := igep0020.o
+
+obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o
+obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o
+obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o
+obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o
+obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o
+obj-$(CONFIG_SND_OMAP_SOC_OMAP2EVM) += snd-soc-omap2evm.o
+obj-$(CONFIG_SND_OMAP_SOC_OMAP3EVM) += snd-soc-omap3evm.o
+obj-$(CONFIG_SND_OMAP_SOC_AM3517EVM) += snd-soc-am3517evm.o
+obj-$(CONFIG_SND_OMAP_SOC_SDP3430) += snd-soc-sdp3430.o
+obj-$(CONFIG_SND_OMAP_SOC_SDP4430) += snd-soc-sdp4430.o
+obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o
+obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o
+obj-$(CONFIG_SND_OMAP_SOC_ZOOM2) += snd-soc-zoom2.o
+obj-$(CONFIG_SND_OMAP_SOC_IGEP0020) += snd-soc-igep0020.o
diff --git a/linux/sound/soc/omap/am3517evm.c b/linux/sound/soc/omap/am3517evm.c
new file mode 100644
index 0000000..ddbd2a1
--- /dev/null
+++ b/linux/sound/soc/omap/am3517evm.c
@@ -0,0 +1,195 @@
+/*
+ * am3517evm.c  -- ALSA SoC support for OMAP3517 / AM3517 EVM
+ *
+ * Author: Anuj Aggarwal <anuj.aggarwal@ti.com>
+ *
+ * Based on sound/soc/omap/beagle.c by Steve Sakoman
+ *
+ * Copyright (C) 2009 Texas Instruments Incorporated
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
+ * whether express or implied; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#include "../codecs/tlv320aic23.h"
+
+#define CODEC_CLOCK 	12000000
+
+static int am3517evm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* Set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+				  SND_SOC_DAIFMT_DSP_B |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec DAI configuration\n");
+		return ret;
+	}
+
+	/* Set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+				  SND_SOC_DAIFMT_DSP_B |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set cpu DAI configuration\n");
+		return ret;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+			CODEC_CLOCK, SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_CLKR_SRC_CLKX, 0,
+				SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set CPU system clock OMAP_MCBSP_CLKR_SRC_CLKX\n");
+		return ret;
+	}
+
+	snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_FSR_SRC_FSX, 0,
+				SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set CPU system clock OMAP_MCBSP_FSR_SRC_FSX\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops am3517evm_ops = {
+	.hw_params = am3517evm_hw_params,
+};
+
+/* am3517evm machine dapm widgets */
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Line Out", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+	SND_SOC_DAPM_MIC("Mic In", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Line Out connected to LLOUT, RLOUT */
+	{"Line Out", NULL, "LOUT"},
+	{"Line Out", NULL, "ROUT"},
+
+	{"LLINEIN", NULL, "Line In"},
+	{"RLINEIN", NULL, "Line In"},
+
+	{"MICIN", NULL, "Mic In"},
+};
+
+static int am3517evm_aic23_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	/* Add am3517-evm specific widgets */
+	snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets,
+				  ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+	/* Set up davinci-evm specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	/* always connected */
+	snd_soc_dapm_enable_pin(codec, "Line Out");
+	snd_soc_dapm_enable_pin(codec, "Line In");
+	snd_soc_dapm_enable_pin(codec, "Mic In");
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link am3517evm_dai = {
+	.name = "TLV320AIC23",
+	.stream_name = "AIC23",
+	.cpu_dai_name ="omap-mcbsp-dai.0",
+	.codec_dai_name = "tlv320aic23-hifi",
+	.platform_name = "omap-pcm-audio",
+	.codec_name = "tlv320aic23-codec.2-001a",
+	.init = am3517evm_aic23_init,
+	.ops = &am3517evm_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_am3517evm = {
+	.name = "am3517evm",
+	.dai_link = &am3517evm_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *am3517evm_snd_device;
+
+static int __init am3517evm_soc_init(void)
+{
+	int ret;
+
+	if (!machine_is_omap3517evm())
+		return -ENODEV;
+	pr_info("OMAP3517 / AM3517 EVM SoC init\n");
+
+	am3517evm_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!am3517evm_snd_device) {
+		printk(KERN_ERR "Platform device allocation failed\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(am3517evm_snd_device, &snd_soc_am3517evm);
+
+	ret = platform_device_add(am3517evm_snd_device);
+	if (ret)
+		goto err1;
+
+	return 0;
+
+err1:
+	printk(KERN_ERR "Unable to add platform device\n");
+	platform_device_put(am3517evm_snd_device);
+
+	return ret;
+}
+
+static void __exit am3517evm_soc_exit(void)
+{
+	platform_device_unregister(am3517evm_snd_device);
+}
+
+module_init(am3517evm_soc_init);
+module_exit(am3517evm_soc_exit);
+
+MODULE_AUTHOR("Anuj Aggarwal <anuj.aggarwal@ti.com>");
+MODULE_DESCRIPTION("ALSA SoC OMAP3517 / AM3517 EVM");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/omap/ams-delta.c b/linux/sound/soc/omap/ams-delta.c
new file mode 100644
index 0000000..438146a
--- /dev/null
+++ b/linux/sound/soc/omap/ams-delta.c
@@ -0,0 +1,658 @@
+/*
+ * ams-delta.c  --  SoC audio for Amstrad E3 (Delta) videophone
+ *
+ * Copyright (C) 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
+ *
+ * Initially based on sound/soc/omap/osk5912.x
+ * Copyright (C) 2008 Mistral Solutions
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/gpio.h>
+#include <linux/spinlock.h>
+#include <linux/tty.h>
+
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+
+#include <plat/board-ams-delta.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+#include "../codecs/cx20442.h"
+
+
+/* Board specific DAPM widgets */
+static const struct snd_soc_dapm_widget ams_delta_dapm_widgets[] = {
+	/* Handset */
+	SND_SOC_DAPM_MIC("Mouthpiece", NULL),
+	SND_SOC_DAPM_HP("Earpiece", NULL),
+	/* Handsfree/Speakerphone */
+	SND_SOC_DAPM_MIC("Microphone", NULL),
+	SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+/* How they are connected to codec pins */
+static const struct snd_soc_dapm_route ams_delta_audio_map[] = {
+	{"TELIN", NULL, "Mouthpiece"},
+	{"Earpiece", NULL, "TELOUT"},
+
+	{"MIC", NULL, "Microphone"},
+	{"Speaker", NULL, "SPKOUT"},
+};
+
+/*
+ * Controls, functional after the modem line discipline is activated.
+ */
+
+/* Virtual switch: audio input/output constellations */
+static const char *ams_delta_audio_mode[] =
+	{"Mixed", "Handset", "Handsfree", "Speakerphone"};
+
+/* Selection <-> pin translation */
+#define AMS_DELTA_MOUTHPIECE	0
+#define AMS_DELTA_EARPIECE	1
+#define AMS_DELTA_MICROPHONE	2
+#define AMS_DELTA_SPEAKER	3
+#define AMS_DELTA_AGC		4
+
+#define AMS_DELTA_MIXED		((1 << AMS_DELTA_EARPIECE) | \
+						(1 << AMS_DELTA_MICROPHONE))
+#define AMS_DELTA_HANDSET	((1 << AMS_DELTA_MOUTHPIECE) | \
+						(1 << AMS_DELTA_EARPIECE))
+#define AMS_DELTA_HANDSFREE	((1 << AMS_DELTA_MICROPHONE) | \
+						(1 << AMS_DELTA_SPEAKER))
+#define AMS_DELTA_SPEAKERPHONE	(AMS_DELTA_HANDSFREE | (1 << AMS_DELTA_AGC))
+
+static const unsigned short ams_delta_audio_mode_pins[] = {
+	AMS_DELTA_MIXED,
+	AMS_DELTA_HANDSET,
+	AMS_DELTA_HANDSFREE,
+	AMS_DELTA_SPEAKERPHONE,
+};
+
+static unsigned short ams_delta_audio_agc;
+
+static int ams_delta_set_audio_mode(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+	struct soc_enum *control = (struct soc_enum *)kcontrol->private_value;
+	unsigned short pins;
+	int pin, changed = 0;
+
+	/* Refuse any mode changes if we are not able to control the codec. */
+	if (!codec->hw_write)
+		return -EUNATCH;
+
+	if (ucontrol->value.enumerated.item[0] >= control->max)
+		return -EINVAL;
+
+	mutex_lock(&codec->mutex);
+
+	/* Translate selection to bitmap */
+	pins = ams_delta_audio_mode_pins[ucontrol->value.enumerated.item[0]];
+
+	/* Setup pins after corresponding bits if changed */
+	pin = !!(pins & (1 << AMS_DELTA_MOUTHPIECE));
+	if (pin != snd_soc_dapm_get_pin_status(codec, "Mouthpiece")) {
+		changed = 1;
+		if (pin)
+			snd_soc_dapm_enable_pin(codec, "Mouthpiece");
+		else
+			snd_soc_dapm_disable_pin(codec, "Mouthpiece");
+	}
+	pin = !!(pins & (1 << AMS_DELTA_EARPIECE));
+	if (pin != snd_soc_dapm_get_pin_status(codec, "Earpiece")) {
+		changed = 1;
+		if (pin)
+			snd_soc_dapm_enable_pin(codec, "Earpiece");
+		else
+			snd_soc_dapm_disable_pin(codec, "Earpiece");
+	}
+	pin = !!(pins & (1 << AMS_DELTA_MICROPHONE));
+	if (pin != snd_soc_dapm_get_pin_status(codec, "Microphone")) {
+		changed = 1;
+		if (pin)
+			snd_soc_dapm_enable_pin(codec, "Microphone");
+		else
+			snd_soc_dapm_disable_pin(codec, "Microphone");
+	}
+	pin = !!(pins & (1 << AMS_DELTA_SPEAKER));
+	if (pin != snd_soc_dapm_get_pin_status(codec, "Speaker")) {
+		changed = 1;
+		if (pin)
+			snd_soc_dapm_enable_pin(codec, "Speaker");
+		else
+			snd_soc_dapm_disable_pin(codec, "Speaker");
+	}
+	pin = !!(pins & (1 << AMS_DELTA_AGC));
+	if (pin != ams_delta_audio_agc) {
+		ams_delta_audio_agc = pin;
+		changed = 1;
+		if (pin)
+			snd_soc_dapm_enable_pin(codec, "AGCIN");
+		else
+			snd_soc_dapm_disable_pin(codec, "AGCIN");
+	}
+	if (changed)
+		snd_soc_dapm_sync(codec);
+
+	mutex_unlock(&codec->mutex);
+
+	return changed;
+}
+
+static int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+	unsigned short pins, mode;
+
+	pins = ((snd_soc_dapm_get_pin_status(codec, "Mouthpiece") <<
+							AMS_DELTA_MOUTHPIECE) |
+			(snd_soc_dapm_get_pin_status(codec, "Earpiece") <<
+							AMS_DELTA_EARPIECE));
+	if (pins)
+		pins |= (snd_soc_dapm_get_pin_status(codec, "Microphone") <<
+							AMS_DELTA_MICROPHONE);
+	else
+		pins = ((snd_soc_dapm_get_pin_status(codec, "Microphone") <<
+							AMS_DELTA_MICROPHONE) |
+			(snd_soc_dapm_get_pin_status(codec, "Speaker") <<
+							AMS_DELTA_SPEAKER) |
+			(ams_delta_audio_agc << AMS_DELTA_AGC));
+
+	for (mode = 0; mode < ARRAY_SIZE(ams_delta_audio_mode); mode++)
+		if (pins == ams_delta_audio_mode_pins[mode])
+			break;
+
+	if (mode >= ARRAY_SIZE(ams_delta_audio_mode))
+		return -EINVAL;
+
+	ucontrol->value.enumerated.item[0] = mode;
+
+	return 0;
+}
+
+static const struct soc_enum ams_delta_audio_enum[] = {
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ams_delta_audio_mode),
+						ams_delta_audio_mode),
+};
+
+static const struct snd_kcontrol_new ams_delta_audio_controls[] = {
+	SOC_ENUM_EXT("Audio Mode", ams_delta_audio_enum[0],
+			ams_delta_get_audio_mode, ams_delta_set_audio_mode),
+};
+
+/* Hook switch */
+static struct snd_soc_jack ams_delta_hook_switch;
+static struct snd_soc_jack_gpio ams_delta_hook_switch_gpios[] = {
+	{
+		.gpio = 4,
+		.name = "hook_switch",
+		.report = SND_JACK_HEADSET,
+		.invert = 1,
+		.debounce_time = 150,
+	}
+};
+
+/* After we are able to control the codec over the modem,
+ * the hook switch can be used for dynamic DAPM reconfiguration. */
+static struct snd_soc_jack_pin ams_delta_hook_switch_pins[] = {
+	/* Handset */
+	{
+		.pin = "Mouthpiece",
+		.mask = SND_JACK_MICROPHONE,
+	},
+	{
+		.pin = "Earpiece",
+		.mask = SND_JACK_HEADPHONE,
+	},
+	/* Handsfree */
+	{
+		.pin = "Microphone",
+		.mask = SND_JACK_MICROPHONE,
+		.invert = 1,
+	},
+	{
+		.pin = "Speaker",
+		.mask = SND_JACK_HEADPHONE,
+		.invert = 1,
+	},
+};
+
+
+/*
+ * Modem line discipline, required for making above controls functional.
+ * Activated from userspace with ldattach, possibly invoked from udev rule.
+ */
+
+/* To actually apply any modem controlled configuration changes to the codec,
+ * we must connect codec DAI pins to the modem for a moment.  Be carefull not
+ * to interfere with our digital mute function that shares the same hardware. */
+static struct timer_list cx81801_timer;
+static bool cx81801_cmd_pending;
+static bool ams_delta_muted;
+static DEFINE_SPINLOCK(ams_delta_lock);
+
+static void cx81801_timeout(unsigned long data)
+{
+	int muted;
+
+	spin_lock(&ams_delta_lock);
+	cx81801_cmd_pending = 0;
+	muted = ams_delta_muted;
+	spin_unlock(&ams_delta_lock);
+
+	/* Reconnect the codec DAI back from the modem to the CPU DAI
+	 * only if digital mute still off */
+	if (!muted)
+		ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC, 0);
+}
+
+/*
+ * Used for passing a codec structure pointer
+ * from the board initialization code to the tty line discipline.
+ */
+static struct snd_soc_codec *cx20442_codec;
+
+/* Line discipline .open() */
+static int cx81801_open(struct tty_struct *tty)
+{
+	int ret;
+
+	if (!cx20442_codec)
+		return -ENODEV;
+
+	/*
+	 * Pass the codec structure pointer for use by other ldisc callbacks,
+	 * both the card and the codec specific parts.
+	 */
+	tty->disc_data = cx20442_codec;
+
+	ret = v253_ops.open(tty);
+
+	if (ret < 0)
+		tty->disc_data = NULL;
+
+	return ret;
+}
+
+/* Line discipline .close() */
+static void cx81801_close(struct tty_struct *tty)
+{
+	struct snd_soc_codec *codec = tty->disc_data;
+
+	del_timer_sync(&cx81801_timer);
+
+	/* Prevent the hook switch from further changing the DAPM pins */
+	INIT_LIST_HEAD(&ams_delta_hook_switch.pins);
+
+	if (!codec)
+		return;
+
+	v253_ops.close(tty);
+
+	/* Revert back to default audio input/output constellation */
+	snd_soc_dapm_disable_pin(codec, "Mouthpiece");
+	snd_soc_dapm_enable_pin(codec, "Earpiece");
+	snd_soc_dapm_enable_pin(codec, "Microphone");
+	snd_soc_dapm_disable_pin(codec, "Speaker");
+	snd_soc_dapm_disable_pin(codec, "AGCIN");
+	snd_soc_dapm_sync(codec);
+}
+
+/* Line discipline .hangup() */
+static int cx81801_hangup(struct tty_struct *tty)
+{
+	cx81801_close(tty);
+	return 0;
+}
+
+/* Line discipline .recieve_buf() */
+static void cx81801_receive(struct tty_struct *tty,
+				const unsigned char *cp, char *fp, int count)
+{
+	struct snd_soc_codec *codec = tty->disc_data;
+	const unsigned char *c;
+	int apply, ret;
+
+	if (!codec)
+		return;
+
+	if (!codec->hw_write) {
+		/* First modem response, complete setup procedure */
+
+		/* Initialize timer used for config pulse generation */
+		setup_timer(&cx81801_timer, cx81801_timeout, 0);
+
+		v253_ops.receive_buf(tty, cp, fp, count);
+
+		/* Link hook switch to DAPM pins */
+		ret = snd_soc_jack_add_pins(&ams_delta_hook_switch,
+					ARRAY_SIZE(ams_delta_hook_switch_pins),
+					ams_delta_hook_switch_pins);
+		if (ret)
+			dev_warn(codec->dev,
+				"Failed to link hook switch to DAPM pins, "
+				"will continue with hook switch unlinked.\n");
+
+		return;
+	}
+
+	v253_ops.receive_buf(tty, cp, fp, count);
+
+	for (c = &cp[count - 1]; c >= cp; c--) {
+		if (*c != '\r')
+			continue;
+		/* Complete modem response received, apply config to codec */
+
+		spin_lock_bh(&ams_delta_lock);
+		mod_timer(&cx81801_timer, jiffies + msecs_to_jiffies(150));
+		apply = !ams_delta_muted && !cx81801_cmd_pending;
+		cx81801_cmd_pending = 1;
+		spin_unlock_bh(&ams_delta_lock);
+
+		/* Apply config pulse by connecting the codec to the modem
+		 * if not already done */
+		if (apply)
+			ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC,
+						AMS_DELTA_LATCH2_MODEM_CODEC);
+		break;
+	}
+}
+
+/* Line discipline .write_wakeup() */
+static void cx81801_wakeup(struct tty_struct *tty)
+{
+	v253_ops.write_wakeup(tty);
+}
+
+static struct tty_ldisc_ops cx81801_ops = {
+	.magic = TTY_LDISC_MAGIC,
+	.name = "cx81801",
+	.owner = THIS_MODULE,
+	.open = cx81801_open,
+	.close = cx81801_close,
+	.hangup = cx81801_hangup,
+	.receive_buf = cx81801_receive,
+	.write_wakeup = cx81801_wakeup,
+};
+
+
+/*
+ * Even if not very usefull, the sound card can still work without any of the
+ * above functonality activated.  You can still control its audio input/output
+ * constellation and speakerphone gain from userspace by issueing AT commands
+ * over the modem port.
+ */
+
+static int ams_delta_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	/* Set cpu DAI configuration */
+	return snd_soc_dai_set_fmt(rtd->cpu_dai,
+				   SND_SOC_DAIFMT_DSP_A |
+				   SND_SOC_DAIFMT_NB_NF |
+				   SND_SOC_DAIFMT_CBM_CFM);
+}
+
+static struct snd_soc_ops ams_delta_ops = {
+	.hw_params = ams_delta_hw_params,
+};
+
+
+/* Board specific codec bias level control */
+static int ams_delta_set_bias_level(struct snd_soc_card *card,
+					enum snd_soc_bias_level level)
+{
+	struct snd_soc_codec *codec = card->rtd->codec;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+	case SND_SOC_BIAS_STANDBY:
+		if (codec->bias_level == SND_SOC_BIAS_OFF)
+			ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_NRESET,
+						AMS_DELTA_LATCH2_MODEM_NRESET);
+		break;
+	case SND_SOC_BIAS_OFF:
+		if (codec->bias_level != SND_SOC_BIAS_OFF)
+			ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_NRESET,
+						0);
+	}
+	codec->bias_level = level;
+
+	return 0;
+}
+
+/* Digital mute implemented using modem/CPU multiplexer.
+ * Shares hardware with codec config pulse generation */
+static bool ams_delta_muted = 1;
+
+static int ams_delta_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+	int apply;
+
+	if (ams_delta_muted == mute)
+		return 0;
+
+	spin_lock_bh(&ams_delta_lock);
+	ams_delta_muted = mute;
+	apply = !cx81801_cmd_pending;
+	spin_unlock_bh(&ams_delta_lock);
+
+	if (apply)
+		ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC,
+				mute ? AMS_DELTA_LATCH2_MODEM_CODEC : 0);
+	return 0;
+}
+
+/* Our codec DAI probably doesn't have its own .ops structure */
+static struct snd_soc_dai_ops ams_delta_dai_ops = {
+	.digital_mute = ams_delta_digital_mute,
+};
+
+/* Will be used if the codec ever has its own digital_mute function */
+static int ams_delta_startup(struct snd_pcm_substream *substream)
+{
+	return ams_delta_digital_mute(NULL, 0);
+}
+
+static void ams_delta_shutdown(struct snd_pcm_substream *substream)
+{
+	ams_delta_digital_mute(NULL, 1);
+}
+
+
+/*
+ * Card initialization
+ */
+
+static int ams_delta_cx20442_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_card *card = rtd->card;
+	int ret;
+	/* Codec is ready, now add/activate board specific controls */
+
+	/* Store a pointer to the codec structure for tty ldisc use */
+	cx20442_codec = codec;
+
+	/* Set up digital mute if not provided by the codec */
+	if (!codec_dai->driver->ops) {
+		codec_dai->driver->ops = &ams_delta_dai_ops;
+	} else if (!codec_dai->driver->ops->digital_mute) {
+		codec_dai->driver->ops->digital_mute = ams_delta_digital_mute;
+	} else {
+		ams_delta_ops.startup = ams_delta_startup;
+		ams_delta_ops.shutdown = ams_delta_shutdown;
+	}
+
+	/* Set codec bias level */
+	ams_delta_set_bias_level(card, SND_SOC_BIAS_STANDBY);
+
+	/* Add hook switch - can be used to control the codec from userspace
+	 * even if line discipline fails */
+	ret = snd_soc_jack_new(rtd->codec, "hook_switch",
+				SND_JACK_HEADSET, &ams_delta_hook_switch);
+	if (ret)
+		dev_warn(card->dev,
+				"Failed to allocate resources for hook switch, "
+				"will continue without one.\n");
+	else {
+		ret = snd_soc_jack_add_gpios(&ams_delta_hook_switch,
+					ARRAY_SIZE(ams_delta_hook_switch_gpios),
+					ams_delta_hook_switch_gpios);
+		if (ret)
+			dev_warn(card->dev,
+				"Failed to set up hook switch GPIO line, "
+				"will continue with hook switch inactive.\n");
+	}
+
+	/* Register optional line discipline for over the modem control */
+	ret = tty_register_ldisc(N_V253, &cx81801_ops);
+	if (ret) {
+		dev_warn(card->dev,
+				"Failed to register line discipline, "
+				"will continue without any controls.\n");
+		return 0;
+	}
+
+	/* Add board specific DAPM widgets and routes */
+	ret = snd_soc_dapm_new_controls(codec, ams_delta_dapm_widgets,
+					ARRAY_SIZE(ams_delta_dapm_widgets));
+	if (ret) {
+		dev_warn(card->dev,
+				"Failed to register DAPM controls, "
+				"will continue without any.\n");
+		return 0;
+	}
+
+	ret = snd_soc_dapm_add_routes(codec, ams_delta_audio_map,
+					ARRAY_SIZE(ams_delta_audio_map));
+	if (ret) {
+		dev_warn(card->dev,
+				"Failed to set up DAPM routes, "
+				"will continue with codec default map.\n");
+		return 0;
+	}
+
+	/* Set up initial pin constellation */
+	snd_soc_dapm_disable_pin(codec, "Mouthpiece");
+	snd_soc_dapm_enable_pin(codec, "Earpiece");
+	snd_soc_dapm_enable_pin(codec, "Microphone");
+	snd_soc_dapm_disable_pin(codec, "Speaker");
+	snd_soc_dapm_disable_pin(codec, "AGCIN");
+	snd_soc_dapm_disable_pin(codec, "AGCOUT");
+	snd_soc_dapm_sync(codec);
+
+	/* Add virtual switch */
+	ret = snd_soc_add_controls(codec, ams_delta_audio_controls,
+					ARRAY_SIZE(ams_delta_audio_controls));
+	if (ret)
+		dev_warn(card->dev,
+				"Failed to register audio mode control, "
+				"will continue without it.\n");
+
+	return 0;
+}
+
+/* DAI glue - connects codec <--> CPU */
+static struct snd_soc_dai_link ams_delta_dai_link = {
+	.name = "CX20442",
+	.stream_name = "CX20442",
+	.cpu_dai_name ="omap-mcbsp-dai.0",
+	.codec_dai_name = "cx20442-voice",
+	.init = ams_delta_cx20442_init,
+	.platform_name = "omap-pcm-audio",
+	.codec_name = "cx20442-codec",
+	.ops = &ams_delta_ops,
+};
+
+/* Audio card driver */
+static struct snd_soc_card ams_delta_audio_card = {
+	.name = "AMS_DELTA",
+	.dai_link = &ams_delta_dai_link,
+	.num_links = 1,
+	.set_bias_level = ams_delta_set_bias_level,
+};
+
+/* Module init/exit */
+static struct platform_device *ams_delta_audio_platform_device;
+static struct platform_device *cx20442_platform_device;
+
+static int __init ams_delta_module_init(void)
+{
+	int ret;
+
+	if (!(machine_is_ams_delta()))
+		return -ENODEV;
+
+	ams_delta_audio_platform_device =
+			platform_device_alloc("soc-audio", -1);
+	if (!ams_delta_audio_platform_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(ams_delta_audio_platform_device,
+				&ams_delta_audio_card);
+
+	ret = platform_device_add(ams_delta_audio_platform_device);
+	if (ret)
+		goto err;
+
+	/*
+	 * Codec platform device could be registered from elsewhere (board?),
+	 * but I do it here as it makes sense only if used with the card.
+	 */
+	cx20442_platform_device =
+		platform_device_register_simple("cx20442-codec", -1, NULL, 0);
+	return 0;
+err:
+	platform_device_put(ams_delta_audio_platform_device);
+	return ret;
+}
+module_init(ams_delta_module_init);
+
+static void __exit ams_delta_module_exit(void)
+{
+	if (tty_unregister_ldisc(N_V253) != 0)
+		dev_warn(&ams_delta_audio_platform_device->dev,
+			"failed to unregister V253 line discipline\n");
+
+	snd_soc_jack_free_gpios(&ams_delta_hook_switch,
+			ARRAY_SIZE(ams_delta_hook_switch_gpios),
+			ams_delta_hook_switch_gpios);
+
+	/* Keep modem power on */
+	ams_delta_set_bias_level(&ams_delta_audio_card, SND_SOC_BIAS_STANDBY);
+
+	platform_device_unregister(cx20442_platform_device);
+	platform_device_unregister(ams_delta_audio_platform_device);
+}
+module_exit(ams_delta_module_exit);
+
+MODULE_AUTHOR("Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>");
+MODULE_DESCRIPTION("ALSA SoC driver for Amstrad E3 (Delta) videophone");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/igep0020.c b/linux/sound/soc/omap/igep0020.c
new file mode 100644
index 0000000..fd3a40f
--- /dev/null
+++ b/linux/sound/soc/omap/igep0020.c
@@ -0,0 +1,138 @@
+/*
+ * igep0020.c  --  SoC audio for IGEP v2
+ *
+ * Based on sound/soc/omap/overo.c by Steve Sakoman
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+static int igep2_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* Set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec DAI configuration\n");
+		return ret;
+	}
+
+	/* Set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set cpu DAI configuration\n");
+		return ret;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+					    SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops igep2_ops = {
+	.hw_params = igep2_hw_params,
+};
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link igep2_dai = {
+	.name = "TWL4030",
+	.stream_name = "TWL4030",
+	.cpu_dai_name = "omap-mcbsp-dai.1",
+	.codec_dai_name = "twl4030-hifi",
+	.platform_name = "omap-pcm-audio",
+	.codec_name = "twl4030-codec",
+	.ops = &igep2_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_card_igep2 = {
+	.name = "igep2",
+	.dai_link = &igep2_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *igep2_snd_device;
+
+static int __init igep2_soc_init(void)
+{
+	int ret;
+
+	if (!machine_is_igep0020())
+		return -ENODEV;
+	printk(KERN_INFO "IGEP v2 SoC init\n");
+
+	igep2_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!igep2_snd_device) {
+		printk(KERN_ERR "Platform device allocation failed\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(igep2_snd_device, &snd_soc_card_igep2);
+
+	ret = platform_device_add(igep2_snd_device);
+	if (ret)
+		goto err1;
+
+	return 0;
+
+err1:
+	printk(KERN_ERR "Unable to add platform device\n");
+	platform_device_put(igep2_snd_device);
+
+	return ret;
+}
+module_init(igep2_soc_init);
+
+static void __exit igep2_soc_exit(void)
+{
+	platform_device_unregister(igep2_snd_device);
+}
+module_exit(igep2_soc_exit);
+
+MODULE_AUTHOR("Enric Balletbo i Serra <eballetbo@iseebcn.com>");
+MODULE_DESCRIPTION("ALSA SoC IGEP v2");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/mcpdm.c b/linux/sound/soc/omap/mcpdm.c
new file mode 100644
index 0000000..928f037
--- /dev/null
+++ b/linux/sound/soc/omap/mcpdm.c
@@ -0,0 +1,470 @@
+/*
+ * mcpdm.c  --	McPDM interface driver
+ *
+ * Author: Jorge Eduardo Candelaria <x0107209@ti.com>
+ * Copyright (C) 2009 - Texas Instruments, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+
+#include "mcpdm.h"
+
+static struct omap_mcpdm *mcpdm;
+
+static inline void omap_mcpdm_write(u16 reg, u32 val)
+{
+	__raw_writel(val, mcpdm->io_base + reg);
+}
+
+static inline int omap_mcpdm_read(u16 reg)
+{
+	return __raw_readl(mcpdm->io_base + reg);
+}
+
+static void omap_mcpdm_reg_dump(void)
+{
+	dev_dbg(mcpdm->dev, "***********************\n");
+	dev_dbg(mcpdm->dev, "IRQSTATUS_RAW:  0x%04x\n",
+			omap_mcpdm_read(MCPDM_IRQSTATUS_RAW));
+	dev_dbg(mcpdm->dev, "IRQSTATUS:	0x%04x\n",
+			omap_mcpdm_read(MCPDM_IRQSTATUS));
+	dev_dbg(mcpdm->dev, "IRQENABLE_SET:  0x%04x\n",
+			omap_mcpdm_read(MCPDM_IRQENABLE_SET));
+	dev_dbg(mcpdm->dev, "IRQENABLE_CLR:  0x%04x\n",
+			omap_mcpdm_read(MCPDM_IRQENABLE_CLR));
+	dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n",
+			omap_mcpdm_read(MCPDM_IRQWAKE_EN));
+	dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n",
+			omap_mcpdm_read(MCPDM_DMAENABLE_SET));
+	dev_dbg(mcpdm->dev, "DMAENABLE_CLR:  0x%04x\n",
+			omap_mcpdm_read(MCPDM_DMAENABLE_CLR));
+	dev_dbg(mcpdm->dev, "DMAWAKEEN:	0x%04x\n",
+			omap_mcpdm_read(MCPDM_DMAWAKEEN));
+	dev_dbg(mcpdm->dev, "CTRL:	0x%04x\n",
+			omap_mcpdm_read(MCPDM_CTRL));
+	dev_dbg(mcpdm->dev, "DN_DATA:  0x%04x\n",
+			omap_mcpdm_read(MCPDM_DN_DATA));
+	dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n",
+			omap_mcpdm_read(MCPDM_UP_DATA));
+	dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n",
+			omap_mcpdm_read(MCPDM_FIFO_CTRL_DN));
+	dev_dbg(mcpdm->dev, "FIFO_CTRL_UP:  0x%04x\n",
+			omap_mcpdm_read(MCPDM_FIFO_CTRL_UP));
+	dev_dbg(mcpdm->dev, "DN_OFFSET:	0x%04x\n",
+			omap_mcpdm_read(MCPDM_DN_OFFSET));
+	dev_dbg(mcpdm->dev, "***********************\n");
+}
+
+/*
+ * Takes the McPDM module in and out of reset state.
+ * Uplink and downlink can be reset individually.
+ */
+static void omap_mcpdm_reset_capture(int reset)
+{
+	int ctrl = omap_mcpdm_read(MCPDM_CTRL);
+
+	if (reset)
+		ctrl |= SW_UP_RST;
+	else
+		ctrl &= ~SW_UP_RST;
+
+	omap_mcpdm_write(MCPDM_CTRL, ctrl);
+}
+
+static void omap_mcpdm_reset_playback(int reset)
+{
+	int ctrl = omap_mcpdm_read(MCPDM_CTRL);
+
+	if (reset)
+		ctrl |= SW_DN_RST;
+	else
+		ctrl &= ~SW_DN_RST;
+
+	omap_mcpdm_write(MCPDM_CTRL, ctrl);
+}
+
+/*
+ * Enables the transfer through the PDM interface to/from the Phoenix
+ * codec by enabling the corresponding UP or DN channels.
+ */
+void omap_mcpdm_start(int stream)
+{
+	int ctrl = omap_mcpdm_read(MCPDM_CTRL);
+
+	if (stream)
+		ctrl |= mcpdm->up_channels;
+	else
+		ctrl |= mcpdm->dn_channels;
+
+	omap_mcpdm_write(MCPDM_CTRL, ctrl);
+}
+
+/*
+ * Disables the transfer through the PDM interface to/from the Phoenix
+ * codec by disabling the corresponding UP or DN channels.
+ */
+void omap_mcpdm_stop(int stream)
+{
+	int ctrl = omap_mcpdm_read(MCPDM_CTRL);
+
+	if (stream)
+		ctrl &= ~mcpdm->up_channels;
+	else
+		ctrl &= ~mcpdm->dn_channels;
+
+	omap_mcpdm_write(MCPDM_CTRL, ctrl);
+}
+
+/*
+ * Configures McPDM uplink for audio recording.
+ * This function should be called before omap_mcpdm_start.
+ */
+int omap_mcpdm_capture_open(struct omap_mcpdm_link *uplink)
+{
+	int irq_mask = 0;
+	int ctrl;
+
+	if (!uplink)
+		return -EINVAL;
+
+	mcpdm->uplink = uplink;
+
+	/* Enable irq request generation */
+	irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK;
+	omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask);
+
+	/* Configure uplink threshold */
+	if (uplink->threshold > UP_THRES_MAX)
+		uplink->threshold = UP_THRES_MAX;
+
+	omap_mcpdm_write(MCPDM_FIFO_CTRL_UP, uplink->threshold);
+
+	/* Configure DMA controller */
+	omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_UP_ENABLE);
+
+	/* Set pdm out format */
+	ctrl = omap_mcpdm_read(MCPDM_CTRL);
+	ctrl &= ~PDMOUTFORMAT;
+	ctrl |= uplink->format & PDMOUTFORMAT;
+
+	/* Uplink channels */
+	mcpdm->up_channels = uplink->channels & (PDM_UP_MASK | PDM_STATUS_MASK);
+
+	omap_mcpdm_write(MCPDM_CTRL, ctrl);
+
+	return 0;
+}
+
+/*
+ * Configures McPDM downlink for audio playback.
+ * This function should be called before omap_mcpdm_start.
+ */
+int omap_mcpdm_playback_open(struct omap_mcpdm_link *downlink)
+{
+	int irq_mask = 0;
+	int ctrl;
+
+	if (!downlink)
+		return -EINVAL;
+
+	mcpdm->downlink = downlink;
+
+	/* Enable irq request generation */
+	irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK;
+	omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask);
+
+	/* Configure uplink threshold */
+	if (downlink->threshold > DN_THRES_MAX)
+		downlink->threshold = DN_THRES_MAX;
+
+	omap_mcpdm_write(MCPDM_FIFO_CTRL_DN, downlink->threshold);
+
+	/* Enable DMA request generation */
+	omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_DN_ENABLE);
+
+	/* Set pdm out format */
+	ctrl = omap_mcpdm_read(MCPDM_CTRL);
+	ctrl &= ~PDMOUTFORMAT;
+	ctrl |= downlink->format & PDMOUTFORMAT;
+
+	/* Downlink channels */
+	mcpdm->dn_channels = downlink->channels & (PDM_DN_MASK | PDM_CMD_MASK);
+
+	omap_mcpdm_write(MCPDM_CTRL, ctrl);
+
+	return 0;
+}
+
+/*
+ * Cleans McPDM uplink configuration.
+ * This function should be called when the stream is closed.
+ */
+int omap_mcpdm_capture_close(struct omap_mcpdm_link *uplink)
+{
+	int irq_mask = 0;
+
+	if (!uplink)
+		return -EINVAL;
+
+	/* Disable irq request generation */
+	irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK;
+	omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask);
+
+	/* Disable DMA request generation */
+	omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_UP_ENABLE);
+
+	/* Clear Downlink channels */
+	mcpdm->up_channels = 0;
+
+	mcpdm->uplink = NULL;
+
+	return 0;
+}
+
+/*
+ * Cleans McPDM downlink configuration.
+ * This function should be called when the stream is closed.
+ */
+int omap_mcpdm_playback_close(struct omap_mcpdm_link *downlink)
+{
+	int irq_mask = 0;
+
+	if (!downlink)
+		return -EINVAL;
+
+	/* Disable irq request generation */
+	irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK;
+	omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask);
+
+	/* Disable DMA request generation */
+	omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_DN_ENABLE);
+
+	/* clear Downlink channels */
+	mcpdm->dn_channels = 0;
+
+	mcpdm->downlink = NULL;
+
+	return 0;
+}
+
+static irqreturn_t omap_mcpdm_irq_handler(int irq, void *dev_id)
+{
+	struct omap_mcpdm *mcpdm_irq = dev_id;
+	int irq_status;
+
+	irq_status = omap_mcpdm_read(MCPDM_IRQSTATUS);
+
+	/* Acknowledge irq event */
+	omap_mcpdm_write(MCPDM_IRQSTATUS, irq_status);
+
+	if (irq & MCPDM_DN_IRQ_FULL) {
+		dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status);
+		omap_mcpdm_reset_playback(1);
+		omap_mcpdm_playback_open(mcpdm_irq->downlink);
+		omap_mcpdm_reset_playback(0);
+	}
+
+	if (irq & MCPDM_DN_IRQ_EMPTY) {
+		dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status);
+		omap_mcpdm_reset_playback(1);
+		omap_mcpdm_playback_open(mcpdm_irq->downlink);
+		omap_mcpdm_reset_playback(0);
+	}
+
+	if (irq & MCPDM_DN_IRQ) {
+		dev_dbg(mcpdm_irq->dev, "DN write request\n");
+	}
+
+	if (irq & MCPDM_UP_IRQ_FULL) {
+		dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status);
+		omap_mcpdm_reset_capture(1);
+		omap_mcpdm_capture_open(mcpdm_irq->uplink);
+		omap_mcpdm_reset_capture(0);
+	}
+
+	if (irq & MCPDM_UP_IRQ_EMPTY) {
+		dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status);
+		omap_mcpdm_reset_capture(1);
+		omap_mcpdm_capture_open(mcpdm_irq->uplink);
+		omap_mcpdm_reset_capture(0);
+	}
+
+	if (irq & MCPDM_UP_IRQ) {
+		dev_dbg(mcpdm_irq->dev, "UP write request\n");
+	}
+
+	return IRQ_HANDLED;
+}
+
+int omap_mcpdm_request(void)
+{
+	int ret;
+
+	clk_enable(mcpdm->clk);
+
+	spin_lock(&mcpdm->lock);
+
+	if (!mcpdm->free) {
+		dev_err(mcpdm->dev, "McPDM interface is in use\n");
+		spin_unlock(&mcpdm->lock);
+		ret = -EBUSY;
+		goto err;
+	}
+	mcpdm->free = 0;
+
+	spin_unlock(&mcpdm->lock);
+
+	/* Disable lines while request is ongoing */
+	omap_mcpdm_write(MCPDM_CTRL, 0x00);
+
+	ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler,
+				0, "McPDM", (void *)mcpdm);
+	if (ret) {
+		dev_err(mcpdm->dev, "Request for McPDM IRQ failed\n");
+		goto err;
+	}
+
+	return 0;
+
+err:
+	clk_disable(mcpdm->clk);
+	return ret;
+}
+
+void omap_mcpdm_free(void)
+{
+	spin_lock(&mcpdm->lock);
+	if (mcpdm->free) {
+		dev_err(mcpdm->dev, "McPDM interface is already free\n");
+		spin_unlock(&mcpdm->lock);
+		return;
+	}
+	mcpdm->free = 1;
+	spin_unlock(&mcpdm->lock);
+
+	clk_disable(mcpdm->clk);
+
+	free_irq(mcpdm->irq, (void *)mcpdm);
+}
+
+/* Enable/disable DC offset cancelation for the analog
+ * headset path (PDM channels 1 and 2).
+ */
+int omap_mcpdm_set_offset(int offset1, int offset2)
+{
+	int offset;
+
+	if ((offset1 > DN_OFST_MAX) || (offset2 > DN_OFST_MAX))
+		return -EINVAL;
+
+	offset = (offset1 << DN_OFST_RX1) | (offset2 << DN_OFST_RX2);
+
+	/* offset cancellation for channel 1 */
+	if (offset1)
+		offset |= DN_OFST_RX1_EN;
+	else
+		offset &= ~DN_OFST_RX1_EN;
+
+	/* offset cancellation for channel 2 */
+	if (offset2)
+		offset |= DN_OFST_RX2_EN;
+	else
+		offset &= ~DN_OFST_RX2_EN;
+
+	omap_mcpdm_write(MCPDM_DN_OFFSET, offset);
+
+	return 0;
+}
+
+int __devinit omap_mcpdm_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int ret = 0;
+
+	mcpdm = kzalloc(sizeof(struct omap_mcpdm), GFP_KERNEL);
+	if (!mcpdm) {
+		ret = -ENOMEM;
+		goto exit;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "no resource\n");
+		goto err_resource;
+	}
+
+	spin_lock_init(&mcpdm->lock);
+	mcpdm->free = 1;
+	mcpdm->io_base = ioremap(res->start, resource_size(res));
+	if (!mcpdm->io_base) {
+		ret = -ENOMEM;
+		goto err_resource;
+	}
+
+	mcpdm->irq = platform_get_irq(pdev, 0);
+
+	mcpdm->clk = clk_get(&pdev->dev, "pdm_ck");
+	if (IS_ERR(mcpdm->clk)) {
+		ret = PTR_ERR(mcpdm->clk);
+		dev_err(&pdev->dev, "unable to get pdm_ck: %d\n", ret);
+		goto err_clk;
+	}
+
+	mcpdm->dev = &pdev->dev;
+	platform_set_drvdata(pdev, mcpdm);
+
+	return 0;
+
+err_clk:
+	iounmap(mcpdm->io_base);
+err_resource:
+	kfree(mcpdm);
+exit:
+	return ret;
+}
+
+int __devexit omap_mcpdm_remove(struct platform_device *pdev)
+{
+	struct omap_mcpdm *mcpdm_ptr = platform_get_drvdata(pdev);
+
+	platform_set_drvdata(pdev, NULL);
+
+	clk_put(mcpdm_ptr->clk);
+
+	iounmap(mcpdm_ptr->io_base);
+
+	mcpdm_ptr->clk = NULL;
+	mcpdm_ptr->free = 0;
+	mcpdm_ptr->dev = NULL;
+
+	kfree(mcpdm_ptr);
+
+	return 0;
+}
+
diff --git a/linux/sound/soc/omap/mcpdm.h b/linux/sound/soc/omap/mcpdm.h
new file mode 100644
index 0000000..df3e16f
--- /dev/null
+++ b/linux/sound/soc/omap/mcpdm.h
@@ -0,0 +1,153 @@
+/*
+ * mcpdm.h -- Defines for McPDM driver
+ *
+ * Author: Jorge Eduardo Candelaria <x0107209@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/* McPDM registers */
+
+#define MCPDM_REVISION         0x00
+#define MCPDM_SYSCONFIG                0x10
+#define MCPDM_IRQSTATUS_RAW    0x24
+#define MCPDM_IRQSTATUS                0x28
+#define MCPDM_IRQENABLE_SET    0x2C
+#define MCPDM_IRQENABLE_CLR    0x30
+#define MCPDM_IRQWAKE_EN       0x34
+#define MCPDM_DMAENABLE_SET    0x38
+#define MCPDM_DMAENABLE_CLR    0x3C
+#define MCPDM_DMAWAKEEN                0x40
+#define MCPDM_CTRL             0x44
+#define MCPDM_DN_DATA          0x48
+#define MCPDM_UP_DATA          0x4C
+#define MCPDM_FIFO_CTRL_DN     0x50
+#define MCPDM_FIFO_CTRL_UP     0x54
+#define MCPDM_DN_OFFSET                0x58
+
+/*
+ * MCPDM_IRQ bit fields
+ * IRQSTATUS_RAW, IRQSTATUS, IRQENABLE_SET, IRQENABLE_CLR
+ */
+
+#define MCPDM_DN_IRQ                   (1 << 0)
+#define MCPDM_DN_IRQ_EMPTY             (1 << 1)
+#define MCPDM_DN_IRQ_ALMST_EMPTY       (1 << 2)
+#define MCPDM_DN_IRQ_FULL              (1 << 3)
+
+#define MCPDM_UP_IRQ                   (1 << 8)
+#define MCPDM_UP_IRQ_EMPTY             (1 << 9)
+#define MCPDM_UP_IRQ_ALMST_FULL                (1 << 10)
+#define MCPDM_UP_IRQ_FULL              (1 << 11)
+
+#define MCPDM_DOWNLINK_IRQ_MASK                0x00F
+#define MCPDM_UPLINK_IRQ_MASK          0xF00
+
+/*
+ * MCPDM_DMAENABLE bit fields
+ */
+
+#define DMA_DN_ENABLE          0x1
+#define DMA_UP_ENABLE          0x2
+
+/*
+ * MCPDM_CTRL bit fields
+ */
+
+#define PDM_UP1_EN             0x0001
+#define PDM_UP2_EN             0x0002
+#define PDM_UP3_EN             0x0004
+#define PDM_DN1_EN             0x0008
+#define PDM_DN2_EN             0x0010
+#define PDM_DN3_EN             0x0020
+#define PDM_DN4_EN             0x0040
+#define PDM_DN5_EN             0x0080
+#define PDMOUTFORMAT           0x0100
+#define CMD_INT                        0x0200
+#define STATUS_INT             0x0400
+#define SW_UP_RST              0x0800
+#define SW_DN_RST              0x1000
+#define PDM_UP_MASK            0x007
+#define PDM_DN_MASK            0x0F8
+#define PDM_CMD_MASK           0x200
+#define PDM_STATUS_MASK                0x400
+
+
+#define PDMOUTFORMAT_LJUST     (0 << 8)
+#define PDMOUTFORMAT_RJUST     (1 << 8)
+
+/*
+ * MCPDM_FIFO_CTRL bit fields
+ */
+
+#define UP_THRES_MAX           0xF
+#define DN_THRES_MAX           0xF
+
+/*
+ * MCPDM_DN_OFFSET bit fields
+ */
+
+#define DN_OFST_RX1_EN         0x0001
+#define DN_OFST_RX2_EN         0x0100
+
+#define DN_OFST_RX1            1
+#define DN_OFST_RX2            9
+#define DN_OFST_MAX            0x1F
+
+#define MCPDM_UPLINK           1
+#define MCPDM_DOWNLINK         2
+
+struct omap_mcpdm_link {
+       int irq_mask;
+       int threshold;
+       int format;
+       int channels;
+};
+
+struct omap_mcpdm_platform_data {
+       unsigned long phys_base;
+       u16 irq;
+};
+
+struct omap_mcpdm {
+       struct device *dev;
+       unsigned long phys_base;
+       void __iomem *io_base;
+       u8 free;
+       int irq;
+
+       spinlock_t lock;
+       struct omap_mcpdm_platform_data *pdata;
+       struct clk *clk;
+       struct omap_mcpdm_link *downlink;
+       struct omap_mcpdm_link *uplink;
+       struct completion irq_completion;
+
+       int dn_channels;
+       int up_channels;
+};
+
+extern void omap_mcpdm_start(int stream);
+extern void omap_mcpdm_stop(int stream);
+extern int omap_mcpdm_capture_open(struct omap_mcpdm_link *uplink);
+extern int omap_mcpdm_playback_open(struct omap_mcpdm_link *downlink);
+extern int omap_mcpdm_capture_close(struct omap_mcpdm_link *uplink);
+extern int omap_mcpdm_playback_close(struct omap_mcpdm_link *downlink);
+extern int omap_mcpdm_request(void);
+extern void omap_mcpdm_free(void);
+extern int omap_mcpdm_set_offset(int offset1, int offset2);
+int __devinit omap_mcpdm_probe(struct platform_device *pdev);
+int __devexit omap_mcpdm_remove(struct platform_device *pdev);
diff --git a/linux/sound/soc/omap/n810.c b/linux/sound/soc/omap/n810.c
new file mode 100644
index 0000000..a3b6d89
--- /dev/null
+++ b/linux/sound/soc/omap/n810.c
@@ -0,0 +1,407 @@
+/*
+ * n810.c  --  SoC audio for Nokia N810
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jhnikula@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <linux/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+#include "../codecs/tlv320aic3x.h"
+
+#define N810_HEADSET_AMP_GPIO	10
+#define N810_SPEAKER_AMP_GPIO	101
+
+enum {
+	N810_JACK_DISABLED,
+	N810_JACK_HP,
+	N810_JACK_HS,
+	N810_JACK_MIC,
+};
+
+static struct clk *sys_clkout2;
+static struct clk *sys_clkout2_src;
+static struct clk *func96m_clk;
+
+static int n810_spk_func;
+static int n810_jack_func;
+static int n810_dmic_func;
+
+static void n810_ext_control(struct snd_soc_codec *codec)
+{
+	int hp = 0, line1l = 0;
+
+	switch (n810_jack_func) {
+	case N810_JACK_HS:
+		line1l = 1;
+	case N810_JACK_HP:
+		hp = 1;
+		break;
+	case N810_JACK_MIC:
+		line1l = 1;
+		break;
+	}
+
+	if (n810_spk_func)
+		snd_soc_dapm_enable_pin(codec, "Ext Spk");
+	else
+		snd_soc_dapm_disable_pin(codec, "Ext Spk");
+
+	if (hp)
+		snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+	else
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+	if (line1l)
+		snd_soc_dapm_enable_pin(codec, "LINE1L");
+	else
+		snd_soc_dapm_disable_pin(codec, "LINE1L");
+
+	if (n810_dmic_func)
+		snd_soc_dapm_enable_pin(codec, "DMic");
+	else
+		snd_soc_dapm_disable_pin(codec, "DMic");
+
+	snd_soc_dapm_sync(codec);
+}
+
+static int n810_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	snd_pcm_hw_constraint_minmax(runtime,
+				     SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2);
+
+	n810_ext_control(codec);
+	return clk_enable(sys_clkout2);
+}
+
+static void n810_shutdown(struct snd_pcm_substream *substream)
+{
+	clk_disable(sys_clkout2);
+}
+
+static int n810_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int err;
+
+	/* Set codec DAI configuration */
+	err = snd_soc_dai_set_fmt(codec_dai,
+					 SND_SOC_DAIFMT_I2S |
+					 SND_SOC_DAIFMT_NB_NF |
+					 SND_SOC_DAIFMT_CBM_CFM);
+	if (err < 0)
+		return err;
+
+	/* Set cpu DAI configuration */
+	err = snd_soc_dai_set_fmt(cpu_dai,
+				       SND_SOC_DAIFMT_I2S |
+				       SND_SOC_DAIFMT_NB_NF |
+				       SND_SOC_DAIFMT_CBM_CFM);
+	if (err < 0)
+		return err;
+
+	/* Set the codec system clock for DAC and ADC */
+	err = snd_soc_dai_set_sysclk(codec_dai, 0, 12000000,
+					    SND_SOC_CLOCK_IN);
+
+	return err;
+}
+
+static struct snd_soc_ops n810_ops = {
+	.startup = n810_startup,
+	.hw_params = n810_hw_params,
+	.shutdown = n810_shutdown,
+};
+
+static int n810_get_spk(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = n810_spk_func;
+
+	return 0;
+}
+
+static int n810_set_spk(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+
+	if (n810_spk_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	n810_spk_func = ucontrol->value.integer.value[0];
+	n810_ext_control(codec);
+
+	return 1;
+}
+
+static int n810_get_jack(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = n810_jack_func;
+
+	return 0;
+}
+
+static int n810_set_jack(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+
+	if (n810_jack_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	n810_jack_func = ucontrol->value.integer.value[0];
+	n810_ext_control(codec);
+
+	return 1;
+}
+
+static int n810_get_input(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = n810_dmic_func;
+
+	return 0;
+}
+
+static int n810_set_input(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+
+	if (n810_dmic_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	n810_dmic_func = ucontrol->value.integer.value[0];
+	n810_ext_control(codec);
+
+	return 1;
+}
+
+static int n810_spk_event(struct snd_soc_dapm_widget *w,
+			  struct snd_kcontrol *k, int event)
+{
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		gpio_set_value(N810_SPEAKER_AMP_GPIO, 1);
+	else
+		gpio_set_value(N810_SPEAKER_AMP_GPIO, 0);
+
+	return 0;
+}
+
+static int n810_jack_event(struct snd_soc_dapm_widget *w,
+			   struct snd_kcontrol *k, int event)
+{
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		gpio_set_value(N810_HEADSET_AMP_GPIO, 1);
+	else
+		gpio_set_value(N810_HEADSET_AMP_GPIO, 0);
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget aic33_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Ext Spk", n810_spk_event),
+	SND_SOC_DAPM_HP("Headphone Jack", n810_jack_event),
+	SND_SOC_DAPM_MIC("DMic", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"Headphone Jack", NULL, "HPLOUT"},
+	{"Headphone Jack", NULL, "HPROUT"},
+
+	{"Ext Spk", NULL, "LLOUT"},
+	{"Ext Spk", NULL, "RLOUT"},
+
+	{"DMic Rate 64", NULL, "Mic Bias 2V"},
+	{"Mic Bias 2V", NULL, "DMic"},
+};
+
+static const char *spk_function[] = {"Off", "On"};
+static const char *jack_function[] = {"Off", "Headphone", "Headset", "Mic"};
+static const char *input_function[] = {"ADC", "Digital Mic"};
+static const struct soc_enum n810_enum[] = {
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function),
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function),
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function),
+};
+
+static const struct snd_kcontrol_new aic33_n810_controls[] = {
+	SOC_ENUM_EXT("Speaker Function", n810_enum[0],
+		     n810_get_spk, n810_set_spk),
+	SOC_ENUM_EXT("Jack Function", n810_enum[1],
+		     n810_get_jack, n810_set_jack),
+	SOC_ENUM_EXT("Input Select",  n810_enum[2],
+		     n810_get_input, n810_set_input),
+};
+
+static int n810_aic33_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	/* Not connected */
+	snd_soc_dapm_nc_pin(codec, "MONO_LOUT");
+	snd_soc_dapm_nc_pin(codec, "HPLCOM");
+	snd_soc_dapm_nc_pin(codec, "HPRCOM");
+	snd_soc_dapm_nc_pin(codec, "MIC3L");
+	snd_soc_dapm_nc_pin(codec, "MIC3R");
+	snd_soc_dapm_nc_pin(codec, "LINE1R");
+	snd_soc_dapm_nc_pin(codec, "LINE2L");
+	snd_soc_dapm_nc_pin(codec, "LINE2R");
+
+	/* Add N810 specific controls */
+	err = snd_soc_add_controls(codec, aic33_n810_controls,
+				ARRAY_SIZE(aic33_n810_controls));
+	if (err < 0)
+		return err;
+
+	/* Add N810 specific widgets */
+	snd_soc_dapm_new_controls(codec, aic33_dapm_widgets,
+				  ARRAY_SIZE(aic33_dapm_widgets));
+
+	/* Set up N810 specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link n810_dai = {
+	.name = "TLV320AIC33",
+	.stream_name = "AIC33",
+	.cpu_dai_name = "omap-mcbsp-dai.1",
+	.platform_name = "omap-pcm-audio",
+	.codec_name = "tlv320aic3x-codec.2-0018",
+	.codec_dai_name = "tlv320aic3x-hifi",
+	.init = n810_aic33_init,
+	.ops = &n810_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_n810 = {
+	.name = "N810",
+	.dai_link = &n810_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *n810_snd_device;
+
+static int __init n810_soc_init(void)
+{
+	int err;
+	struct device *dev;
+
+	if (!(machine_is_nokia_n810() || machine_is_nokia_n810_wimax()))
+		return -ENODEV;
+
+	n810_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!n810_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(n810_snd_device, &snd_soc_n810);
+	err = platform_device_add(n810_snd_device);
+	if (err)
+		goto err1;
+
+	dev = &n810_snd_device->dev;
+
+	sys_clkout2_src = clk_get(dev, "sys_clkout2_src");
+	if (IS_ERR(sys_clkout2_src)) {
+		dev_err(dev, "Could not get sys_clkout2_src clock\n");
+		err = PTR_ERR(sys_clkout2_src);
+		goto err2;
+	}
+	sys_clkout2 = clk_get(dev, "sys_clkout2");
+	if (IS_ERR(sys_clkout2)) {
+		dev_err(dev, "Could not get sys_clkout2\n");
+		err = PTR_ERR(sys_clkout2);
+		goto err3;
+	}
+	/*
+	 * Configure 12 MHz output on SYS_CLKOUT2. Therefore we must use
+	 * 96 MHz as its parent in order to get 12 MHz
+	 */
+	func96m_clk = clk_get(dev, "func_96m_ck");
+	if (IS_ERR(func96m_clk)) {
+		dev_err(dev, "Could not get func 96M clock\n");
+		err = PTR_ERR(func96m_clk);
+		goto err4;
+	}
+	clk_set_parent(sys_clkout2_src, func96m_clk);
+	clk_set_rate(sys_clkout2, 12000000);
+
+	BUG_ON((gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) ||
+	       (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0));
+
+	gpio_direction_output(N810_HEADSET_AMP_GPIO, 0);
+	gpio_direction_output(N810_SPEAKER_AMP_GPIO, 0);
+
+	return 0;
+err4:
+	clk_put(sys_clkout2);
+err3:
+	clk_put(sys_clkout2_src);
+err2:
+	platform_device_del(n810_snd_device);
+err1:
+	platform_device_put(n810_snd_device);
+
+	return err;
+}
+
+static void __exit n810_soc_exit(void)
+{
+	gpio_free(N810_SPEAKER_AMP_GPIO);
+	gpio_free(N810_HEADSET_AMP_GPIO);
+	clk_put(sys_clkout2_src);
+	clk_put(sys_clkout2);
+	clk_put(func96m_clk);
+
+	platform_device_unregister(n810_snd_device);
+}
+
+module_init(n810_soc_init);
+module_exit(n810_soc_exit);
+
+MODULE_AUTHOR("Jarkko Nikula <jhnikula@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC Nokia N810");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/omap-mcbsp.c b/linux/sound/soc/omap/omap-mcbsp.c
new file mode 100644
index 0000000..7e84f24
--- /dev/null
+++ b/linux/sound/soc/omap/omap-mcbsp.c
@@ -0,0 +1,874 @@
+/*
+ * omap-mcbsp.c  --  OMAP ALSA SoC DAI driver using McBSP port
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jhnikula@gmail.com>
+ *          Peter Ujfalusi <peter.ujfalusi@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <plat/dma.h>
+#include <plat/mcbsp.h>
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#define OMAP_MCBSP_RATES	(SNDRV_PCM_RATE_8000_96000)
+
+#define OMAP_MCBSP_SOC_SINGLE_S16_EXT(xname, xmin, xmax, \
+	xhandler_get, xhandler_put) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = omap_mcbsp_st_info_volsw, \
+	.get = xhandler_get, .put = xhandler_put, \
+	.private_value = (unsigned long) &(struct soc_mixer_control) \
+	{.min = xmin, .max = xmax} }
+
+struct omap_mcbsp_data {
+	unsigned int			bus_id;
+	struct omap_mcbsp_reg_cfg	regs;
+	unsigned int			fmt;
+	/*
+	 * Flags indicating is the bus already activated and configured by
+	 * another substream
+	 */
+	int				active;
+	int				configured;
+	unsigned int			in_freq;
+	int				clk_div;
+	int				wlen;
+};
+
+static struct omap_mcbsp_data mcbsp_data[NUM_LINKS];
+
+/*
+ * Stream DMA parameters. DMA request line and port address are set runtime
+ * since they are different between OMAP1 and later OMAPs
+ */
+static struct omap_pcm_dma_data omap_mcbsp_dai_dma_params[NUM_LINKS][2];
+
+#if defined(CONFIG_ARCH_OMAP15XX) || defined(CONFIG_ARCH_OMAP16XX)
+static const int omap1_dma_reqs[][2] = {
+	{ OMAP_DMA_MCBSP1_TX, OMAP_DMA_MCBSP1_RX },
+	{ OMAP_DMA_MCBSP2_TX, OMAP_DMA_MCBSP2_RX },
+	{ OMAP_DMA_MCBSP3_TX, OMAP_DMA_MCBSP3_RX },
+};
+static const unsigned long omap1_mcbsp_port[][2] = {
+	{ OMAP1510_MCBSP1_BASE + OMAP_MCBSP_REG_DXR1,
+	  OMAP1510_MCBSP1_BASE + OMAP_MCBSP_REG_DRR1 },
+	{ OMAP1510_MCBSP2_BASE + OMAP_MCBSP_REG_DXR1,
+	  OMAP1510_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1 },
+	{ OMAP1510_MCBSP3_BASE + OMAP_MCBSP_REG_DXR1,
+	  OMAP1510_MCBSP3_BASE + OMAP_MCBSP_REG_DRR1 },
+};
+#else
+static const int omap1_dma_reqs[][2] = {};
+static const unsigned long omap1_mcbsp_port[][2] = {};
+#endif
+
+#if defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3)
+static const int omap24xx_dma_reqs[][2] = {
+	{ OMAP24XX_DMA_MCBSP1_TX, OMAP24XX_DMA_MCBSP1_RX },
+	{ OMAP24XX_DMA_MCBSP2_TX, OMAP24XX_DMA_MCBSP2_RX },
+#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3)
+	{ OMAP24XX_DMA_MCBSP3_TX, OMAP24XX_DMA_MCBSP3_RX },
+	{ OMAP24XX_DMA_MCBSP4_TX, OMAP24XX_DMA_MCBSP4_RX },
+	{ OMAP24XX_DMA_MCBSP5_TX, OMAP24XX_DMA_MCBSP5_RX },
+#endif
+};
+#else
+static const int omap24xx_dma_reqs[][2] = {};
+#endif
+
+#if defined(CONFIG_ARCH_OMAP2420)
+static const unsigned long omap2420_mcbsp_port[][2] = {
+	{ OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR1,
+	  OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR1 },
+	{ OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR1,
+	  OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1 },
+};
+#else
+static const unsigned long omap2420_mcbsp_port[][2] = {};
+#endif
+
+#if defined(CONFIG_ARCH_OMAP2430)
+static const unsigned long omap2430_mcbsp_port[][2] = {
+	{ OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR,
+	  OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR },
+	{ OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR,
+	  OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR },
+	{ OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DXR,
+	  OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DRR },
+	{ OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DXR,
+	  OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DRR },
+	{ OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DXR,
+	  OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DRR },
+};
+#else
+static const unsigned long omap2430_mcbsp_port[][2] = {};
+#endif
+
+#if defined(CONFIG_ARCH_OMAP3)
+static const unsigned long omap34xx_mcbsp_port[][2] = {
+	{ OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR,
+	  OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR },
+	{ OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR,
+	  OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR },
+	{ OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DXR,
+	  OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DRR },
+	{ OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DXR,
+	  OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DRR },
+	{ OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DXR,
+	  OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DRR },
+};
+#else
+static const unsigned long omap34xx_mcbsp_port[][2] = {};
+#endif
+
+static void omap_mcbsp_set_threshold(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+	struct omap_pcm_dma_data *dma_data;
+	int dma_op_mode = omap_mcbsp_get_dma_op_mode(mcbsp_data->bus_id);
+	int words;
+
+	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+	/* TODO: Currently, MODE_ELEMENT == MODE_FRAME */
+	if (dma_op_mode == MCBSP_DMA_MODE_THRESHOLD)
+		/*
+		 * Configure McBSP threshold based on either:
+		 * packet_size, when the sDMA is in packet mode, or
+		 * based on the period size.
+		 */
+		if (dma_data->packet_size)
+			words = dma_data->packet_size;
+		else
+			words = snd_pcm_lib_period_bytes(substream) /
+							(mcbsp_data->wlen / 8);
+	else
+		words = 1;
+
+	/* Configure McBSP internal buffer usage */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		omap_mcbsp_set_tx_threshold(mcbsp_data->bus_id, words);
+	else
+		omap_mcbsp_set_rx_threshold(mcbsp_data->bus_id, words);
+}
+
+static int omap_mcbsp_hwrule_min_buffersize(struct snd_pcm_hw_params *params,
+				    struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *buffer_size = hw_param_interval(params,
+					SNDRV_PCM_HW_PARAM_BUFFER_SIZE);
+	struct snd_interval *channels = hw_param_interval(params,
+					SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct omap_mcbsp_data *mcbsp_data = rule->private;
+	struct snd_interval frames;
+	int size;
+
+	snd_interval_any(&frames);
+	size = omap_mcbsp_get_fifo_size(mcbsp_data->bus_id);
+
+	frames.min = size / channels->min;
+	frames.integer = 1;
+	return snd_interval_refine(buffer_size, &frames);
+}
+
+static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream,
+				  struct snd_soc_dai *cpu_dai)
+{
+	struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+	int bus_id = mcbsp_data->bus_id;
+	int err = 0;
+
+	if (!cpu_dai->active)
+		err = omap_mcbsp_request(bus_id);
+
+	/*
+	 * OMAP3 McBSP FIFO is word structured.
+	 * McBSP2 has 1024 + 256 = 1280 word long buffer,
+	 * McBSP1,3,4,5 has 128 word long buffer
+	 * This means that the size of the FIFO depends on the sample format.
+	 * For example on McBSP3:
+	 * 16bit samples: size is 128 * 2 = 256 bytes
+	 * 32bit samples: size is 128 * 4 = 512 bytes
+	 * It is simpler to place constraint for buffer and period based on
+	 * channels.
+	 * McBSP3 as example again (16 or 32 bit samples):
+	 * 1 channel (mono): size is 128 frames (128 words)
+	 * 2 channels (stereo): size is 128 / 2 = 64 frames (2 * 64 words)
+	 * 4 channels: size is 128 / 4 = 32 frames (4 * 32 words)
+	 */
+	if (cpu_is_omap343x()) {
+		/*
+		* Rule for the buffer size. We should not allow
+		* smaller buffer than the FIFO size to avoid underruns
+		*/
+		snd_pcm_hw_rule_add(substream->runtime, 0,
+				    SNDRV_PCM_HW_PARAM_CHANNELS,
+				    omap_mcbsp_hwrule_min_buffersize,
+				    mcbsp_data,
+				    SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1);
+
+		/* Make sure, that the period size is always even */
+		snd_pcm_hw_constraint_step(substream->runtime, 0,
+					   SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2);
+	}
+
+	return err;
+}
+
+static void omap_mcbsp_dai_shutdown(struct snd_pcm_substream *substream,
+				    struct snd_soc_dai *cpu_dai)
+{
+	struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+
+	if (!cpu_dai->active) {
+		omap_mcbsp_free(mcbsp_data->bus_id);
+		mcbsp_data->configured = 0;
+	}
+}
+
+static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+				  struct snd_soc_dai *cpu_dai)
+{
+	struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+	int err = 0, play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		mcbsp_data->active++;
+		omap_mcbsp_start(mcbsp_data->bus_id, play, !play);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		omap_mcbsp_stop(mcbsp_data->bus_id, play, !play);
+		mcbsp_data->active--;
+		break;
+	default:
+		err = -EINVAL;
+	}
+
+	return err;
+}
+
+static snd_pcm_sframes_t omap_mcbsp_dai_delay(
+			struct snd_pcm_substream *substream,
+			struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+	u16 fifo_use;
+	snd_pcm_sframes_t delay;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		fifo_use = omap_mcbsp_get_tx_delay(mcbsp_data->bus_id);
+	else
+		fifo_use = omap_mcbsp_get_rx_delay(mcbsp_data->bus_id);
+
+	/*
+	 * Divide the used locations with the channel count to get the
+	 * FIFO usage in samples (don't care about partial samples in the
+	 * buffer).
+	 */
+	delay = fifo_use / substream->runtime->channels;
+
+	return delay;
+}
+
+static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *params,
+				    struct snd_soc_dai *cpu_dai)
+{
+	struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+	struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+	struct omap_pcm_dma_data *dma_data;
+	int dma, bus_id = mcbsp_data->bus_id;
+	int wlen, channels, wpf, sync_mode = OMAP_DMA_SYNC_ELEMENT;
+	int pkt_size = 0;
+	unsigned long port;
+	unsigned int format, div, framesize, master;
+
+	dma_data = &omap_mcbsp_dai_dma_params[cpu_dai->id][substream->stream];
+	if (cpu_class_is_omap1()) {
+		dma = omap1_dma_reqs[bus_id][substream->stream];
+		port = omap1_mcbsp_port[bus_id][substream->stream];
+	} else if (cpu_is_omap2420()) {
+		dma = omap24xx_dma_reqs[bus_id][substream->stream];
+		port = omap2420_mcbsp_port[bus_id][substream->stream];
+	} else if (cpu_is_omap2430()) {
+		dma = omap24xx_dma_reqs[bus_id][substream->stream];
+		port = omap2430_mcbsp_port[bus_id][substream->stream];
+	} else if (cpu_is_omap343x()) {
+		dma = omap24xx_dma_reqs[bus_id][substream->stream];
+		port = omap34xx_mcbsp_port[bus_id][substream->stream];
+	} else {
+		return -ENODEV;
+	}
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		dma_data->data_type = OMAP_DMA_DATA_TYPE_S16;
+		wlen = 16;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		dma_data->data_type = OMAP_DMA_DATA_TYPE_S32;
+		wlen = 32;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (cpu_is_omap343x()) {
+		dma_data->set_threshold = omap_mcbsp_set_threshold;
+		/* TODO: Currently, MODE_ELEMENT == MODE_FRAME */
+		if (omap_mcbsp_get_dma_op_mode(bus_id) ==
+						MCBSP_DMA_MODE_THRESHOLD) {
+			int period_words, max_thrsh;
+
+			period_words = params_period_bytes(params) / (wlen / 8);
+			if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+				max_thrsh = omap_mcbsp_get_max_tx_threshold(
+							    mcbsp_data->bus_id);
+			else
+				max_thrsh = omap_mcbsp_get_max_rx_threshold(
+							    mcbsp_data->bus_id);
+			/*
+			 * If the period contains less or equal number of words,
+			 * we are using the original threshold mode setup:
+			 * McBSP threshold = sDMA frame size = period_size
+			 * Otherwise we switch to sDMA packet mode:
+			 * McBSP threshold = sDMA packet size
+			 * sDMA frame size = period size
+			 */
+			if (period_words > max_thrsh) {
+				int divider = 0;
+
+				/*
+				 * Look for the biggest threshold value, which
+				 * divides the period size evenly.
+				 */
+				divider = period_words / max_thrsh;
+				if (period_words % max_thrsh)
+					divider++;
+				while (period_words % divider &&
+					divider < period_words)
+					divider++;
+				if (divider == period_words)
+					return -EINVAL;
+
+				pkt_size = period_words / divider;
+				sync_mode = OMAP_DMA_SYNC_PACKET;
+			} else {
+				sync_mode = OMAP_DMA_SYNC_FRAME;
+			}
+		}
+	}
+
+	dma_data->name = substream->stream ? "Audio Capture" : "Audio Playback";
+	dma_data->dma_req = dma;
+	dma_data->port_addr = port;
+	dma_data->sync_mode = sync_mode;
+	dma_data->packet_size = pkt_size;
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+	if (mcbsp_data->configured) {
+		/* McBSP already configured by another stream */
+		return 0;
+	}
+
+	format = mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+	wpf = channels = params_channels(params);
+	if (channels == 2 && (format == SND_SOC_DAIFMT_I2S ||
+			      format == SND_SOC_DAIFMT_LEFT_J)) {
+		/* Use dual-phase frames */
+		regs->rcr2	|= RPHASE;
+		regs->xcr2	|= XPHASE;
+		/* Set 1 word per (McBSP) frame for phase1 and phase2 */
+		wpf--;
+		regs->rcr2	|= RFRLEN2(wpf - 1);
+		regs->xcr2	|= XFRLEN2(wpf - 1);
+	}
+
+	regs->rcr1	|= RFRLEN1(wpf - 1);
+	regs->xcr1	|= XFRLEN1(wpf - 1);
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		/* Set word lengths */
+		regs->rcr2	|= RWDLEN2(OMAP_MCBSP_WORD_16);
+		regs->rcr1	|= RWDLEN1(OMAP_MCBSP_WORD_16);
+		regs->xcr2	|= XWDLEN2(OMAP_MCBSP_WORD_16);
+		regs->xcr1	|= XWDLEN1(OMAP_MCBSP_WORD_16);
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		/* Set word lengths */
+		regs->rcr2	|= RWDLEN2(OMAP_MCBSP_WORD_32);
+		regs->rcr1	|= RWDLEN1(OMAP_MCBSP_WORD_32);
+		regs->xcr2	|= XWDLEN2(OMAP_MCBSP_WORD_32);
+		regs->xcr1	|= XWDLEN1(OMAP_MCBSP_WORD_32);
+		break;
+	default:
+		/* Unsupported PCM format */
+		return -EINVAL;
+	}
+
+	/* In McBSP master modes, FRAME (i.e. sample rate) is generated
+	 * by _counting_ BCLKs. Calculate frame size in BCLKs */
+	master = mcbsp_data->fmt & SND_SOC_DAIFMT_MASTER_MASK;
+	if (master ==	SND_SOC_DAIFMT_CBS_CFS) {
+		div = mcbsp_data->clk_div ? mcbsp_data->clk_div : 1;
+		framesize = (mcbsp_data->in_freq / div) / params_rate(params);
+
+		if (framesize < wlen * channels) {
+			printk(KERN_ERR "%s: not enough bandwidth for desired rate and "
+					"channels\n", __func__);
+			return -EINVAL;
+		}
+	} else
+		framesize = wlen * channels;
+
+	/* Set FS period and length in terms of bit clock periods */
+	switch (format) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_LEFT_J:
+		regs->srgr2	|= FPER(framesize - 1);
+		regs->srgr1	|= FWID((framesize >> 1) - 1);
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+	case SND_SOC_DAIFMT_DSP_B:
+		regs->srgr2	|= FPER(framesize - 1);
+		regs->srgr1	|= FWID(0);
+		break;
+	}
+
+	omap_mcbsp_config(bus_id, &mcbsp_data->regs);
+	mcbsp_data->wlen = wlen;
+	mcbsp_data->configured = 1;
+
+	return 0;
+}
+
+/*
+ * This must be called before _set_clkdiv and _set_sysclk since McBSP register
+ * cache is initialized here
+ */
+static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+				      unsigned int fmt)
+{
+	struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+	struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+	unsigned int temp_fmt = fmt;
+
+	if (mcbsp_data->configured)
+		return 0;
+
+	mcbsp_data->fmt = fmt;
+	memset(regs, 0, sizeof(*regs));
+	/* Generic McBSP register settings */
+	regs->spcr2	|= XINTM(3) | FREE;
+	regs->spcr1	|= RINTM(3);
+	/* RFIG and XFIG are not defined in 34xx */
+	if (!cpu_is_omap34xx()) {
+		regs->rcr2	|= RFIG;
+		regs->xcr2	|= XFIG;
+	}
+	if (cpu_is_omap2430() || cpu_is_omap34xx()) {
+		regs->xccr = DXENDLY(1) | XDMAEN | XDISABLE;
+		regs->rccr = RFULL_CYCLE | RDMAEN | RDISABLE;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		/* 1-bit data delay */
+		regs->rcr2	|= RDATDLY(1);
+		regs->xcr2	|= XDATDLY(1);
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		/* 0-bit data delay */
+		regs->rcr2	|= RDATDLY(0);
+		regs->xcr2	|= XDATDLY(0);
+		regs->spcr1	|= RJUST(2);
+		/* Invert FS polarity configuration */
+		temp_fmt ^= SND_SOC_DAIFMT_NB_IF;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		/* 1-bit data delay */
+		regs->rcr2      |= RDATDLY(1);
+		regs->xcr2      |= XDATDLY(1);
+		/* Invert FS polarity configuration */
+		temp_fmt ^= SND_SOC_DAIFMT_NB_IF;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		/* 0-bit data delay */
+		regs->rcr2      |= RDATDLY(0);
+		regs->xcr2      |= XDATDLY(0);
+		/* Invert FS polarity configuration */
+		temp_fmt ^= SND_SOC_DAIFMT_NB_IF;
+		break;
+	default:
+		/* Unsupported data format */
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		/* McBSP master. Set FS and bit clocks as outputs */
+		regs->pcr0	|= FSXM | FSRM |
+				   CLKXM | CLKRM;
+		/* Sample rate generator drives the FS */
+		regs->srgr2	|= FSGM;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		/* McBSP slave */
+		break;
+	default:
+		/* Unsupported master/slave configuration */
+		return -EINVAL;
+	}
+
+	/* Set bit clock (CLKX/CLKR) and FS polarities */
+	switch (temp_fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		/*
+		 * Normal BCLK + FS.
+		 * FS active low. TX data driven on falling edge of bit clock
+		 * and RX data sampled on rising edge of bit clock.
+		 */
+		regs->pcr0	|= FSXP | FSRP |
+				   CLKXP | CLKRP;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		regs->pcr0	|= CLKXP | CLKRP;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		regs->pcr0	|= FSXP | FSRP;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int omap_mcbsp_dai_set_clkdiv(struct snd_soc_dai *cpu_dai,
+				     int div_id, int div)
+{
+	struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+	struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+
+	if (div_id != OMAP_MCBSP_CLKGDV)
+		return -ENODEV;
+
+	mcbsp_data->clk_div = div;
+	regs->srgr1	|= CLKGDV(div - 1);
+
+	return 0;
+}
+
+static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+					 int clk_id, unsigned int freq,
+					 int dir)
+{
+	struct omap_mcbsp_data *mcbsp_data = snd_soc_dai_get_drvdata(cpu_dai);
+	struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
+	int err = 0;
+
+	/* The McBSP signal muxing functions are only available on McBSP1 */
+	if (clk_id == OMAP_MCBSP_CLKR_SRC_CLKR ||
+	    clk_id == OMAP_MCBSP_CLKR_SRC_CLKX ||
+	    clk_id == OMAP_MCBSP_FSR_SRC_FSR ||
+	    clk_id == OMAP_MCBSP_FSR_SRC_FSX)
+		if (cpu_class_is_omap1() || mcbsp_data->bus_id != 0)
+			return -EINVAL;
+
+	mcbsp_data->in_freq = freq;
+
+	switch (clk_id) {
+	case OMAP_MCBSP_SYSCLK_CLK:
+		regs->srgr2	|= CLKSM;
+		break;
+	case OMAP_MCBSP_SYSCLK_CLKS_FCLK:
+		if (cpu_class_is_omap1()) {
+			err = -EINVAL;
+			break;
+		}
+		err = omap2_mcbsp_set_clks_src(mcbsp_data->bus_id,
+					       MCBSP_CLKS_PRCM_SRC);
+		break;
+	case OMAP_MCBSP_SYSCLK_CLKS_EXT:
+		if (cpu_class_is_omap1()) {
+			err = 0;
+			break;
+		}
+		err = omap2_mcbsp_set_clks_src(mcbsp_data->bus_id,
+					       MCBSP_CLKS_PAD_SRC);
+		break;
+
+	case OMAP_MCBSP_SYSCLK_CLKX_EXT:
+		regs->srgr2	|= CLKSM;
+	case OMAP_MCBSP_SYSCLK_CLKR_EXT:
+		regs->pcr0	|= SCLKME;
+		break;
+
+
+	case OMAP_MCBSP_CLKR_SRC_CLKR:
+		if (cpu_class_is_omap1())
+			break;
+		omap2_mcbsp1_mux_clkr_src(CLKR_SRC_CLKR);
+		break;
+	case OMAP_MCBSP_CLKR_SRC_CLKX:
+		if (cpu_class_is_omap1())
+			break;
+		omap2_mcbsp1_mux_clkr_src(CLKR_SRC_CLKX);
+		break;
+	case OMAP_MCBSP_FSR_SRC_FSR:
+		if (cpu_class_is_omap1())
+			break;
+		omap2_mcbsp1_mux_fsr_src(FSR_SRC_FSR);
+		break;
+	case OMAP_MCBSP_FSR_SRC_FSX:
+		if (cpu_class_is_omap1())
+			break;
+		omap2_mcbsp1_mux_fsr_src(FSR_SRC_FSX);
+		break;
+	default:
+		err = -ENODEV;
+	}
+
+	return err;
+}
+
+static struct snd_soc_dai_ops mcbsp_dai_ops = {
+	.startup	= omap_mcbsp_dai_startup,
+	.shutdown	= omap_mcbsp_dai_shutdown,
+	.trigger	= omap_mcbsp_dai_trigger,
+	.delay		= omap_mcbsp_dai_delay,
+	.hw_params	= omap_mcbsp_dai_hw_params,
+	.set_fmt	= omap_mcbsp_dai_set_dai_fmt,
+	.set_clkdiv	= omap_mcbsp_dai_set_clkdiv,
+	.set_sysclk	= omap_mcbsp_dai_set_dai_sysclk,
+};
+
+static int mcbsp_dai_probe(struct snd_soc_dai *dai)
+{
+	mcbsp_data[dai->id].bus_id = dai->id;
+	snd_soc_dai_set_drvdata(dai, &mcbsp_data[dai->id].bus_id);
+	return 0;
+}
+
+static struct snd_soc_dai_driver omap_mcbsp_dai =
+{
+	.probe = mcbsp_dai_probe,
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 16,
+		.rates = OMAP_MCBSP_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
+	},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 16,
+		.rates = OMAP_MCBSP_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
+	},
+	.ops = &mcbsp_dai_ops,
+};
+
+static int omap_mcbsp_st_info_volsw(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_info *uinfo)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	int max = mc->max;
+	int min = mc->min;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = min;
+	uinfo->value.integer.max = max;
+	return 0;
+}
+
+#define OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(id, channel)			\
+static int								\
+omap_mcbsp##id##_set_st_ch##channel##_volume(struct snd_kcontrol *kc,	\
+					struct snd_ctl_elem_value *uc)	\
+{									\
+	struct soc_mixer_control *mc =					\
+		(struct soc_mixer_control *)kc->private_value;		\
+	int max = mc->max;						\
+	int min = mc->min;						\
+	int val = uc->value.integer.value[0];				\
+									\
+	if (val < min || val > max)					\
+		return -EINVAL;						\
+									\
+	/* OMAP McBSP implementation uses index values 0..4 */		\
+	return omap_st_set_chgain((id)-1, channel, val);		\
+}
+
+#define OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(id, channel)			\
+static int								\
+omap_mcbsp##id##_get_st_ch##channel##_volume(struct snd_kcontrol *kc,	\
+					struct snd_ctl_elem_value *uc)	\
+{									\
+	s16 chgain;							\
+									\
+	if (omap_st_get_chgain((id)-1, channel, &chgain))		\
+		return -EAGAIN;						\
+									\
+	uc->value.integer.value[0] = chgain;				\
+	return 0;							\
+}
+
+OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(2, 0)
+OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(2, 1)
+OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(3, 0)
+OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(3, 1)
+OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(2, 0)
+OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(2, 1)
+OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(3, 0)
+OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(3, 1)
+
+static int omap_mcbsp_st_put_mode(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	u8 value = ucontrol->value.integer.value[0];
+
+	if (value == omap_st_is_enabled(mc->reg))
+		return 0;
+
+	if (value)
+		omap_st_enable(mc->reg);
+	else
+		omap_st_disable(mc->reg);
+
+	return 1;
+}
+
+static int omap_mcbsp_st_get_mode(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+
+	ucontrol->value.integer.value[0] = omap_st_is_enabled(mc->reg);
+	return 0;
+}
+
+static const struct snd_kcontrol_new omap_mcbsp2_st_controls[] = {
+	SOC_SINGLE_EXT("McBSP2 Sidetone Switch", 1, 0, 1, 0,
+			omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode),
+	OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP2 Sidetone Channel 0 Volume",
+				      -32768, 32767,
+				      omap_mcbsp2_get_st_ch0_volume,
+				      omap_mcbsp2_set_st_ch0_volume),
+	OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP2 Sidetone Channel 1 Volume",
+				      -32768, 32767,
+				      omap_mcbsp2_get_st_ch1_volume,
+				      omap_mcbsp2_set_st_ch1_volume),
+};
+
+static const struct snd_kcontrol_new omap_mcbsp3_st_controls[] = {
+	SOC_SINGLE_EXT("McBSP3 Sidetone Switch", 2, 0, 1, 0,
+			omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode),
+	OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP3 Sidetone Channel 0 Volume",
+				      -32768, 32767,
+				      omap_mcbsp3_get_st_ch0_volume,
+				      omap_mcbsp3_set_st_ch0_volume),
+	OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP3 Sidetone Channel 1 Volume",
+				      -32768, 32767,
+				      omap_mcbsp3_get_st_ch1_volume,
+				      omap_mcbsp3_set_st_ch1_volume),
+};
+
+int omap_mcbsp_st_add_controls(struct snd_soc_codec *codec, int mcbsp_id)
+{
+	if (!cpu_is_omap34xx())
+		return -ENODEV;
+
+	switch (mcbsp_id) {
+	case 1: /* McBSP 2 */
+		return snd_soc_add_controls(codec, omap_mcbsp2_st_controls,
+					ARRAY_SIZE(omap_mcbsp2_st_controls));
+	case 2: /* McBSP 3 */
+		return snd_soc_add_controls(codec, omap_mcbsp3_st_controls,
+					ARRAY_SIZE(omap_mcbsp3_st_controls));
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(omap_mcbsp_st_add_controls);
+
+static __devinit int asoc_mcbsp_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_dai(&pdev->dev, &omap_mcbsp_dai);
+}
+
+static int __devexit asoc_mcbsp_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dai(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver asoc_mcbsp_driver = {
+	.driver = {
+			.name = "omap-mcbsp-dai",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = asoc_mcbsp_probe,
+	.remove = __devexit_p(asoc_mcbsp_remove),
+};
+
+static int __init snd_omap_mcbsp_init(void)
+{
+	return platform_driver_register(&asoc_mcbsp_driver);
+}
+module_init(snd_omap_mcbsp_init);
+
+static void __exit snd_omap_mcbsp_exit(void)
+{
+	platform_driver_unregister(&asoc_mcbsp_driver);
+}
+module_exit(snd_omap_mcbsp_exit);
+
+MODULE_AUTHOR("Jarkko Nikula <jhnikula@gmail.com>");
+MODULE_DESCRIPTION("OMAP I2S SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/omap-mcbsp.h b/linux/sound/soc/omap/omap-mcbsp.h
new file mode 100644
index 0000000..ffdcc5a
--- /dev/null
+++ b/linux/sound/soc/omap/omap-mcbsp.h
@@ -0,0 +1,60 @@
+/*
+ * omap-mcbsp.h
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jhnikula@gmail.com>
+ *          Peter Ujfalusi <peter.ujfalusi@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __OMAP_I2S_H__
+#define __OMAP_I2S_H__
+
+/* Source clocks for McBSP sample rate generator */
+enum omap_mcbsp_clksrg_clk {
+	OMAP_MCBSP_SYSCLK_CLKS_FCLK,	/* Internal FCLK */
+	OMAP_MCBSP_SYSCLK_CLKS_EXT,	/* External CLKS pin */
+	OMAP_MCBSP_SYSCLK_CLK,		/* Internal ICLK */
+	OMAP_MCBSP_SYSCLK_CLKX_EXT,	/* External CLKX pin */
+	OMAP_MCBSP_SYSCLK_CLKR_EXT,	/* External CLKR pin */
+	OMAP_MCBSP_CLKR_SRC_CLKR,	/* CLKR from CLKR pin */
+	OMAP_MCBSP_CLKR_SRC_CLKX,	/* CLKR from CLKX pin */
+	OMAP_MCBSP_FSR_SRC_FSR,		/* FSR from FSR pin */
+	OMAP_MCBSP_FSR_SRC_FSX,		/* FSR from FSX pin */
+};
+
+/* McBSP dividers */
+enum omap_mcbsp_div {
+	OMAP_MCBSP_CLKGDV,		/* Sample rate generator divider */
+};
+
+#if defined(CONFIG_ARCH_OMAP2420)
+#define NUM_LINKS	2
+#endif
+#if defined(CONFIG_ARCH_OMAP15XX) || defined(CONFIG_ARCH_OMAP16XX)
+#undef  NUM_LINKS
+#define NUM_LINKS	3
+#endif
+#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3)
+#undef  NUM_LINKS
+#define NUM_LINKS	5
+#endif
+
+int omap_mcbsp_st_add_controls(struct snd_soc_codec *codec, int mcbsp_id);
+
+#endif
diff --git a/linux/sound/soc/omap/omap-mcpdm.c b/linux/sound/soc/omap/omap-mcpdm.c
new file mode 100644
index 0000000..bed09c2
--- /dev/null
+++ b/linux/sound/soc/omap/omap-mcpdm.c
@@ -0,0 +1,272 @@
+/*
+ * omap-mcpdm.c  --  OMAP ALSA SoC DAI driver using McPDM port
+ *
+ * Copyright (C) 2009 Texas Instruments
+ *
+ * Author: Misael Lopez Cruz <x0052729@ti.com>
+ * Contact: Jorge Eduardo Candelaria <x0107209@ti.com>
+ *          Margarita Olaya <magi.olaya@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <plat/dma.h>
+#include <plat/mcbsp.h>
+#include "mcpdm.h"
+#include "omap-pcm.h"
+
+struct omap_mcpdm_data {
+	struct omap_mcpdm_link *links;
+	int active;
+};
+
+static struct omap_mcpdm_link omap_mcpdm_links[] = {
+	/* downlink */
+	{
+		.irq_mask = MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL,
+		.threshold = 1,
+		.format = PDMOUTFORMAT_LJUST,
+	},
+	/* uplink */
+	{
+		.irq_mask = MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL,
+		.threshold = 1,
+		.format = PDMOUTFORMAT_LJUST,
+	},
+};
+
+static struct omap_mcpdm_data mcpdm_data = {
+	.links = omap_mcpdm_links,
+	.active = 0,
+};
+
+/*
+ * Stream DMA parameters
+ */
+static struct omap_pcm_dma_data omap_mcpdm_dai_dma_params[] = {
+	{
+		.name = "Audio playback",
+		.dma_req = OMAP44XX_DMA_MCPDM_DL,
+		.data_type = OMAP_DMA_DATA_TYPE_S32,
+		.sync_mode = OMAP_DMA_SYNC_PACKET,
+		.packet_size = 16,
+		.port_addr = OMAP44XX_MCPDM_L3_BASE + MCPDM_DN_DATA,
+	},
+	{
+		.name = "Audio capture",
+		.dma_req = OMAP44XX_DMA_MCPDM_UP,
+		.data_type = OMAP_DMA_DATA_TYPE_S32,
+		.sync_mode = OMAP_DMA_SYNC_PACKET,
+		.packet_size = 16,
+		.port_addr = OMAP44XX_MCPDM_L3_BASE + MCPDM_UP_DATA,
+	},
+};
+
+static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream,
+				  struct snd_soc_dai *dai)
+{
+	int err = 0;
+
+	if (!dai->active)
+		err = omap_mcpdm_request();
+
+	return err;
+}
+
+static void omap_mcpdm_dai_shutdown(struct snd_pcm_substream *substream,
+				    struct snd_soc_dai *dai)
+{
+	if (!dai->active)
+		omap_mcpdm_free();
+}
+
+static int omap_mcpdm_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+				  struct snd_soc_dai *dai)
+{
+	struct omap_mcpdm_data *mcpdm_priv = snd_soc_dai_get_drvdata(dai);
+	int stream = substream->stream;
+	int err = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (!mcpdm_priv->active++)
+			omap_mcpdm_start(stream);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (!--mcpdm_priv->active)
+			omap_mcpdm_stop(stream);
+		break;
+	default:
+		err = -EINVAL;
+	}
+
+	return err;
+}
+
+static int omap_mcpdm_dai_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *params,
+				    struct snd_soc_dai *dai)
+{
+	struct omap_mcpdm_data *mcpdm_priv = snd_soc_dai_get_drvdata(dai);
+	struct omap_mcpdm_link *mcpdm_links = mcpdm_priv->links;
+	int stream = substream->stream;
+	int channels, err, link_mask = 0;
+
+	snd_soc_dai_set_dma_data(dai, substream,
+				 &omap_mcpdm_dai_dma_params[stream]);
+
+	channels = params_channels(params);
+	switch (channels) {
+	case 4:
+		if (stream == SNDRV_PCM_STREAM_CAPTURE)
+			/* up to 2 channels for capture */
+			return -EINVAL;
+		link_mask |= 1 << 3;
+	case 3:
+		if (stream == SNDRV_PCM_STREAM_CAPTURE)
+			/* up to 2 channels for capture */
+			return -EINVAL;
+		link_mask |= 1 << 2;
+	case 2:
+		link_mask |= 1 << 1;
+	case 1:
+		link_mask |= 1 << 0;
+		break;
+	default:
+		/* unsupported number of channels */
+		return -EINVAL;
+	}
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		mcpdm_links[stream].channels = link_mask << 3;
+		err = omap_mcpdm_playback_open(&mcpdm_links[stream]);
+	} else {
+		mcpdm_links[stream].channels = link_mask << 0;
+		err = omap_mcpdm_capture_open(&mcpdm_links[stream]);
+	}
+
+	return err;
+}
+
+static int omap_mcpdm_dai_hw_free(struct snd_pcm_substream *substream,
+				  struct snd_soc_dai *dai)
+{
+	struct omap_mcpdm_data *mcpdm_priv = snd_soc_dai_get_drvdata(dai);
+	struct omap_mcpdm_link *mcpdm_links = mcpdm_priv->links;
+	int stream = substream->stream;
+	int err;
+
+	if (substream->stream ==  SNDRV_PCM_STREAM_PLAYBACK)
+		err = omap_mcpdm_playback_close(&mcpdm_links[stream]);
+	else
+		err = omap_mcpdm_capture_close(&mcpdm_links[stream]);
+
+	return err;
+}
+
+static struct snd_soc_dai_ops omap_mcpdm_dai_ops = {
+	.startup	= omap_mcpdm_dai_startup,
+	.shutdown	= omap_mcpdm_dai_shutdown,
+	.trigger	= omap_mcpdm_dai_trigger,
+	.hw_params	= omap_mcpdm_dai_hw_params,
+	.hw_free	= omap_mcpdm_dai_hw_free,
+};
+
+#define OMAP_MCPDM_RATES	(SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+#define OMAP_MCPDM_FORMATS	(SNDRV_PCM_FMTBIT_S32_LE)
+
+static int omap_mcpdm_dai_probe(struct snd_soc_dai *dai)
+{
+	snd_soc_dai_set_drvdata(dai, &mcpdm_data);
+	return 0;
+}
+
+static struct snd_soc_dai_driver omap_mcpdm_dai = {
+	.probe = omap_mcpdm_dai_probe,
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 4,
+		.rates = OMAP_MCPDM_RATES,
+		.formats = OMAP_MCPDM_FORMATS,
+	},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = OMAP_MCPDM_RATES,
+		.formats = OMAP_MCPDM_FORMATS,
+	},
+	.ops = &omap_mcpdm_dai_ops,
+};
+
+static __devinit int asoc_mcpdm_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = omap_mcpdm_probe(pdev);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_register_dai(&pdev->dev, &omap_mcpdm_dai);
+	if (ret < 0)
+		omap_mcpdm_remove(pdev);
+	return ret;
+}
+
+static int __devexit asoc_mcpdm_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dai(&pdev->dev);
+	omap_mcpdm_remove(pdev);
+	return 0;
+}
+
+static struct platform_driver asoc_mcpdm_driver = {
+	.driver = {
+			.name = "omap-mcpdm-dai",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = asoc_mcpdm_probe,
+	.remove = __devexit_p(asoc_mcpdm_remove),
+};
+
+static int __init snd_omap_mcpdm_init(void)
+{
+	return platform_driver_register(&asoc_mcpdm_driver);
+}
+module_init(snd_omap_mcpdm_init);
+
+static void __exit snd_omap_mcpdm_exit(void)
+{
+	platform_driver_unregister(&asoc_mcpdm_driver);
+}
+module_exit(snd_omap_mcpdm_exit);
+
+MODULE_AUTHOR("Misael Lopez Cruz <x0052729@ti.com>");
+MODULE_DESCRIPTION("OMAP PDM SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/omap-pcm.c b/linux/sound/soc/omap/omap-pcm.c
new file mode 100644
index 0000000..8caeb8d
--- /dev/null
+++ b/linux/sound/soc/omap/omap-pcm.c
@@ -0,0 +1,438 @@
+/*
+ * omap-pcm.c  --  ALSA PCM interface for the OMAP SoC
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jhnikula@gmail.com>
+ *          Peter Ujfalusi <peter.ujfalusi@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <plat/dma.h>
+#include "omap-pcm.h"
+
+static const struct snd_pcm_hardware omap_pcm_hardware = {
+	.info			= SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_PAUSE |
+				  SNDRV_PCM_INFO_RESUME,
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
+				  SNDRV_PCM_FMTBIT_S32_LE,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 64 * 1024,
+	.periods_min		= 2,
+	.periods_max		= 255,
+	.buffer_bytes_max	= 128 * 1024,
+};
+
+struct omap_runtime_data {
+	spinlock_t			lock;
+	struct omap_pcm_dma_data	*dma_data;
+	int				dma_ch;
+	int				period_index;
+};
+
+static void omap_pcm_dma_irq(int ch, u16 stat, void *data)
+{
+	struct snd_pcm_substream *substream = data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct omap_runtime_data *prtd = runtime->private_data;
+	unsigned long flags;
+
+	if ((cpu_is_omap1510())) {
+		/*
+		 * OMAP1510 doesn't fully support DMA progress counter
+		 * and there is no software emulation implemented yet,
+		 * so have to maintain our own progress counters
+		 * that can be used by omap_pcm_pointer() instead.
+		 */
+		spin_lock_irqsave(&prtd->lock, flags);
+		if ((stat == OMAP_DMA_LAST_IRQ) &&
+				(prtd->period_index == runtime->periods - 1)) {
+			/* we are in sync, do nothing */
+			spin_unlock_irqrestore(&prtd->lock, flags);
+			return;
+		}
+		if (prtd->period_index >= 0) {
+			if (stat & OMAP_DMA_BLOCK_IRQ) {
+				/* end of buffer reached, loop back */
+				prtd->period_index = 0;
+			} else if (stat & OMAP_DMA_LAST_IRQ) {
+				/* update the counter for the last period */
+				prtd->period_index = runtime->periods - 1;
+			} else if (++prtd->period_index >= runtime->periods) {
+				/* end of buffer missed? loop back */
+				prtd->period_index = 0;
+			}
+		}
+		spin_unlock_irqrestore(&prtd->lock, flags);
+	}
+
+	snd_pcm_period_elapsed(substream);
+}
+
+/* this may get called several times by oss emulation */
+static int omap_pcm_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct omap_runtime_data *prtd = runtime->private_data;
+	struct omap_pcm_dma_data *dma_data;
+
+	int err = 0;
+
+	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+	/* return if this is a bufferless transfer e.g.
+	 * codec <--> BT codec or GSM modem -- lg FIXME */
+	if (!dma_data)
+		return 0;
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+	runtime->dma_bytes = params_buffer_bytes(params);
+
+	if (prtd->dma_data)
+		return 0;
+	prtd->dma_data = dma_data;
+	err = omap_request_dma(dma_data->dma_req, dma_data->name,
+			       omap_pcm_dma_irq, substream, &prtd->dma_ch);
+	if (!err) {
+		/*
+		 * Link channel with itself so DMA doesn't need any
+		 * reprogramming while looping the buffer
+		 */
+		omap_dma_link_lch(prtd->dma_ch, prtd->dma_ch);
+	}
+
+	return err;
+}
+
+static int omap_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct omap_runtime_data *prtd = runtime->private_data;
+
+	if (prtd->dma_data == NULL)
+		return 0;
+
+	omap_dma_unlink_lch(prtd->dma_ch, prtd->dma_ch);
+	omap_free_dma(prtd->dma_ch);
+	prtd->dma_data = NULL;
+
+	snd_pcm_set_runtime_buffer(substream, NULL);
+
+	return 0;
+}
+
+static int omap_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct omap_runtime_data *prtd = runtime->private_data;
+	struct omap_pcm_dma_data *dma_data = prtd->dma_data;
+	struct omap_dma_channel_params dma_params;
+	int bytes;
+
+	/* return if this is a bufferless transfer e.g.
+	 * codec <--> BT codec or GSM modem -- lg FIXME */
+	if (!prtd->dma_data)
+		return 0;
+
+	memset(&dma_params, 0, sizeof(dma_params));
+	dma_params.data_type			= dma_data->data_type;
+	dma_params.trigger			= dma_data->dma_req;
+	dma_params.sync_mode			= dma_data->sync_mode;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		dma_params.src_amode		= OMAP_DMA_AMODE_POST_INC;
+		dma_params.dst_amode		= OMAP_DMA_AMODE_CONSTANT;
+		dma_params.src_or_dst_synch	= OMAP_DMA_DST_SYNC;
+		dma_params.src_start		= runtime->dma_addr;
+		dma_params.dst_start		= dma_data->port_addr;
+		dma_params.dst_port		= OMAP_DMA_PORT_MPUI;
+		dma_params.dst_fi		= dma_data->packet_size;
+	} else {
+		dma_params.src_amode		= OMAP_DMA_AMODE_CONSTANT;
+		dma_params.dst_amode		= OMAP_DMA_AMODE_POST_INC;
+		dma_params.src_or_dst_synch	= OMAP_DMA_SRC_SYNC;
+		dma_params.src_start		= dma_data->port_addr;
+		dma_params.dst_start		= runtime->dma_addr;
+		dma_params.src_port		= OMAP_DMA_PORT_MPUI;
+		dma_params.src_fi		= dma_data->packet_size;
+	}
+	/*
+	 * Set DMA transfer frame size equal to ALSA period size and frame
+	 * count as no. of ALSA periods. Then with DMA frame interrupt enabled,
+	 * we can transfer the whole ALSA buffer with single DMA transfer but
+	 * still can get an interrupt at each period bounary
+	 */
+	bytes = snd_pcm_lib_period_bytes(substream);
+	dma_params.elem_count	= bytes >> dma_data->data_type;
+	dma_params.frame_count	= runtime->periods;
+	omap_set_dma_params(prtd->dma_ch, &dma_params);
+
+	if ((cpu_is_omap1510()))
+		omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ |
+			      OMAP_DMA_LAST_IRQ | OMAP_DMA_BLOCK_IRQ);
+	else
+		omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ);
+
+	if (!(cpu_class_is_omap1())) {
+		omap_set_dma_src_burst_mode(prtd->dma_ch,
+						OMAP_DMA_DATA_BURST_16);
+		omap_set_dma_dest_burst_mode(prtd->dma_ch,
+						OMAP_DMA_DATA_BURST_16);
+	}
+
+	return 0;
+}
+
+static int omap_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct omap_runtime_data *prtd = runtime->private_data;
+	struct omap_pcm_dma_data *dma_data = prtd->dma_data;
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&prtd->lock, flags);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		prtd->period_index = 0;
+		/* Configure McBSP internal buffer usage */
+		if (dma_data->set_threshold)
+			dma_data->set_threshold(substream);
+
+		omap_start_dma(prtd->dma_ch);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		prtd->period_index = -1;
+		omap_stop_dma(prtd->dma_ch);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	spin_unlock_irqrestore(&prtd->lock, flags);
+
+	return ret;
+}
+
+static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct omap_runtime_data *prtd = runtime->private_data;
+	dma_addr_t ptr;
+	snd_pcm_uframes_t offset;
+
+	if (cpu_is_omap1510()) {
+		offset = prtd->period_index * runtime->period_size;
+	} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		ptr = omap_get_dma_dst_pos(prtd->dma_ch);
+		offset = bytes_to_frames(runtime, ptr - runtime->dma_addr);
+	} else {
+		ptr = omap_get_dma_src_pos(prtd->dma_ch);
+		offset = bytes_to_frames(runtime, ptr - runtime->dma_addr);
+	}
+
+	if (offset >= runtime->buffer_size)
+		offset = 0;
+
+	return offset;
+}
+
+static int omap_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct omap_runtime_data *prtd;
+	int ret;
+
+	snd_soc_set_runtime_hwparams(substream, &omap_pcm_hardware);
+
+	/* Ensure that buffer size is a multiple of period size */
+	ret = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0)
+		goto out;
+
+	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+	if (prtd == NULL) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	spin_lock_init(&prtd->lock);
+	runtime->private_data = prtd;
+
+out:
+	return ret;
+}
+
+static int omap_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	kfree(runtime->private_data);
+	return 0;
+}
+
+static int omap_pcm_mmap(struct snd_pcm_substream *substream,
+	struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+				     runtime->dma_area,
+				     runtime->dma_addr,
+				     runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops omap_pcm_ops = {
+	.open		= omap_pcm_open,
+	.close		= omap_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= omap_pcm_hw_params,
+	.hw_free	= omap_pcm_hw_free,
+	.prepare	= omap_pcm_prepare,
+	.trigger	= omap_pcm_trigger,
+	.pointer	= omap_pcm_pointer,
+	.mmap		= omap_pcm_mmap,
+};
+
+static u64 omap_pcm_dmamask = DMA_BIT_MASK(64);
+
+static int omap_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
+	int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = omap_pcm_hardware.buffer_bytes_max;
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+	buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+					   &buf->addr, GFP_KERNEL);
+	if (!buf->area)
+		return -ENOMEM;
+
+	buf->bytes = size;
+	return 0;
+}
+
+static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+
+		dma_free_writecombine(pcm->card->dev, buf->bytes,
+				      buf->area, buf->addr);
+		buf->area = NULL;
+	}
+}
+
+static int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+		 struct snd_pcm *pcm)
+{
+	int ret = 0;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &omap_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = DMA_BIT_MASK(64);
+
+	if (dai->driver->playback.channels_min) {
+		ret = omap_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_PLAYBACK);
+		if (ret)
+			goto out;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = omap_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_CAPTURE);
+		if (ret)
+			goto out;
+	}
+
+out:
+	return ret;
+}
+
+static struct snd_soc_platform_driver omap_soc_platform = {
+	.ops		= &omap_pcm_ops,
+	.pcm_new	= omap_pcm_new,
+	.pcm_free	= omap_pcm_free_dma_buffers,
+};
+
+static __devinit int omap_pcm_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev,
+			&omap_soc_platform);
+}
+
+static int __devexit omap_pcm_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver omap_pcm_driver = {
+	.driver = {
+			.name = "omap-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = omap_pcm_probe,
+	.remove = __devexit_p(omap_pcm_remove),
+};
+
+static int __init snd_omap_pcm_init(void)
+{
+	return platform_driver_register(&omap_pcm_driver);
+}
+module_init(snd_omap_pcm_init);
+
+static void __exit snd_omap_pcm_exit(void)
+{
+	platform_driver_unregister(&omap_pcm_driver);
+}
+module_exit(snd_omap_pcm_exit);
+
+MODULE_AUTHOR("Jarkko Nikula <jhnikula@gmail.com>");
+MODULE_DESCRIPTION("OMAP PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/omap-pcm.h b/linux/sound/soc/omap/omap-pcm.h
new file mode 100644
index 0000000..fea0515
--- /dev/null
+++ b/linux/sound/soc/omap/omap-pcm.h
@@ -0,0 +1,38 @@
+/*
+ * omap-pcm.h
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jhnikula@gmail.com>
+ *          Peter Ujfalusi <peter.ujfalusi@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __OMAP_PCM_H__
+#define __OMAP_PCM_H__
+
+struct omap_pcm_dma_data {
+	char		*name;		/* stream identifier */
+	int		dma_req;	/* DMA request line */
+	unsigned long	port_addr;	/* transmit/receive register */
+	void (*set_threshold)(struct snd_pcm_substream *substream);
+	int		data_type;	/* data type 8,16,32 */
+	int		sync_mode;	/* DMA sync mode */
+	int		packet_size;	/* packet size only in PACKET mode */
+};
+
+#endif
diff --git a/linux/sound/soc/omap/omap2evm.c b/linux/sound/soc/omap/omap2evm.c
new file mode 100644
index 0000000..cf3fc8a
--- /dev/null
+++ b/linux/sound/soc/omap/omap2evm.c
@@ -0,0 +1,140 @@
+/*
+ * omap2evm.c  --  SoC audio machine driver for omap2evm board
+ *
+ * Author: Arun KS <arunks@mistralsolutions.com>
+ *
+ * Based on sound/soc/omap/overo.c by Steve Sakoman
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+static int omap2evm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* Set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec DAI configuration\n");
+		return ret;
+	}
+
+	/* Set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set cpu DAI configuration\n");
+		return ret;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+					    SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops omap2evm_ops = {
+	.hw_params = omap2evm_hw_params,
+};
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link omap2evm_dai = {
+	.name = "TWL4030",
+	.stream_name = "TWL4030",
+	.cpu_dai_name = "omap-mcbsp-dai.1",
+	.codec_dai_name = "twl4030-hifi",
+	.platform_name = "omap-pcm-audio",
+	.codec_name = "twl4030-codec",
+	.ops = &omap2evm_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_omap2evm = {
+	.name = "omap2evm",
+	.dai_link = &omap2evm_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *omap2evm_snd_device;
+
+static int __init omap2evm_soc_init(void)
+{
+	int ret;
+
+	if (!machine_is_omap2evm())
+		return -ENODEV;
+	printk(KERN_INFO "omap2evm SoC init\n");
+
+	omap2evm_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!omap2evm_snd_device) {
+		printk(KERN_ERR "Platform device allocation failed\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(omap2evm_snd_device, &snd_soc_omap2evm);
+
+	ret = platform_device_add(omap2evm_snd_device);
+	if (ret)
+		goto err1;
+
+	return 0;
+
+err1:
+	printk(KERN_ERR "Unable to add platform device\n");
+	platform_device_put(omap2evm_snd_device);
+
+	return ret;
+}
+module_init(omap2evm_soc_init);
+
+static void __exit omap2evm_soc_exit(void)
+{
+	platform_device_unregister(omap2evm_snd_device);
+}
+module_exit(omap2evm_soc_exit);
+
+MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>");
+MODULE_DESCRIPTION("ALSA SoC omap2evm");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/omap3beagle.c b/linux/sound/soc/omap/omap3beagle.c
new file mode 100644
index 0000000..e56832b
--- /dev/null
+++ b/linux/sound/soc/omap/omap3beagle.c
@@ -0,0 +1,150 @@
+/*
+ * omap3beagle.c  --  SoC audio for OMAP3 Beagle
+ *
+ * Author: Steve Sakoman <steve@sakoman.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+static int omap3beagle_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int fmt;
+	int ret;
+
+	switch (params_channels(params)) {
+	case 2: /* Stereo I2S mode */
+		fmt =	SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_CBM_CFM;
+		break;
+	case 4: /* Four channel TDM mode */
+		fmt =	SND_SOC_DAIFMT_DSP_A |
+			SND_SOC_DAIFMT_IB_NF |
+			SND_SOC_DAIFMT_CBM_CFM;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec DAI configuration\n");
+		return ret;
+	}
+
+	/* Set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set cpu DAI configuration\n");
+		return ret;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+				     SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops omap3beagle_ops = {
+	.hw_params = omap3beagle_hw_params,
+};
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link omap3beagle_dai = {
+	.name = "TWL4030",
+	.stream_name = "TWL4030",
+	.cpu_dai_name = "omap-mcbsp-dai.1",
+	.platform_name = "omap-pcm-audio",
+	.codec_dai_name = "twl4030-hifi",
+	.codec_name = "twl4030-codec",
+	.ops = &omap3beagle_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_omap3beagle = {
+	.name = "omap3beagle",
+	.owner = THIS_MODULE,
+	.dai_link = &omap3beagle_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *omap3beagle_snd_device;
+
+static int __init omap3beagle_soc_init(void)
+{
+	int ret;
+
+	if (!(machine_is_omap3_beagle() || machine_is_devkit8000()))
+		return -ENODEV;
+	pr_info("OMAP3 Beagle/Devkit8000 SoC init\n");
+
+	omap3beagle_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!omap3beagle_snd_device) {
+		printk(KERN_ERR "Platform device allocation failed\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(omap3beagle_snd_device, &snd_soc_omap3beagle);
+
+	ret = platform_device_add(omap3beagle_snd_device);
+	if (ret)
+		goto err1;
+
+	return 0;
+
+err1:
+	printk(KERN_ERR "Unable to add platform device\n");
+	platform_device_put(omap3beagle_snd_device);
+
+	return ret;
+}
+
+static void __exit omap3beagle_soc_exit(void)
+{
+	platform_device_unregister(omap3beagle_snd_device);
+}
+
+module_init(omap3beagle_soc_init);
+module_exit(omap3beagle_soc_exit);
+
+MODULE_AUTHOR("Steve Sakoman <steve@sakoman.com>");
+MODULE_DESCRIPTION("ALSA SoC OMAP3 Beagle");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/omap3evm.c b/linux/sound/soc/omap/omap3evm.c
new file mode 100644
index 0000000..9dd368b
--- /dev/null
+++ b/linux/sound/soc/omap/omap3evm.c
@@ -0,0 +1,212 @@
+/*
+ * omap3evm.c  -- ALSA SoC support for OMAP3 EVM
+ *
+ * Author: Anuj Aggarwal <anuj.aggarwal@ti.com>
+ *
+ * Based on sound/soc/omap/beagle.c by Steve Sakoman
+ *
+ * Added support for MCBSP1 <-> WL1271 BT by Mistral
+ *
+ * Copyright (C) 2008 Texas Instruments, Incorporated
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
+ * whether express or implied; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#if defined(CONFIG_SND_SOC_WL1271BT)
+#include <plat/control.h>
+#include "../codecs/wl1271bt.h"
+#endif
+
+static int omap3evm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* Set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "Can't set codec DAI configuration\n");
+		return ret;
+	}
+
+	/* Set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "Can't set cpu DAI configuration\n");
+		return ret;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+				     SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "Can't set codec system clock\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops omap3evm_ops = {
+	.hw_params = omap3evm_hw_params,
+};
+
+#if defined(CONFIG_SND_SOC_WL1271BT)
+static int omap3evm_wl1271bt_pcm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+	int ret;
+
+	/* Set cpu DAI configuration for WL1271 Bluetooth codec */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+				  SND_SOC_DAIFMT_DSP_B |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "Can't set cpu DAI configuration for " \
+						"WL1271 Bluetooth codec \n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops omap3evm_wl1271bt_pcm_ops = {
+	.hw_params = omap3evm_wl1271bt_pcm_hw_params,
+};
+
+#endif
+
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link omap3evm_dai = {
+	.name 		= "TWL4030",
+	.stream_name 	= "TWL4030",
+	.cpu_dai_name = "omap-mcbsp-dai.1",
+	.codec_dai_name = "twl4030-hifi",
+	.platform_name = "omap-pcm-audio",
+	.codec_name = "twl4030-codec",
+	.ops 		= &omap3evm_ops,
+
+static struct snd_soc_dai_link omap3evm_dai[] = {
+	{
+		.name = "TWL4030",
+		.stream_name = "TWL4030",
+		.cpu_dai = &omap_mcbsp_dai[0],
+		.codec_dai = &twl4030_dai[TWL4030_DAI_HIFI],
+		.ops = &omap3evm_ops,
+	},
+
+#if defined(CONFIG_SND_SOC_WL1271BT)
+	/* Connects WL1271 Bluetooth codec <--> CPU */
+	{
+		.name = "WL1271BTPCM",
+		.stream_name = "WL1271 BT PCM",
+		.cpu_dai = &omap_mcbsp_dai[1],
+		.codec_dai = &wl1271bt_dai,
+		.ops = &omap3evm_wl1271bt_pcm_ops,
+	},
+#endif
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_omap3evm = {
+	.name = "omap3evm",
+	.dai_link = &omap3evm_dai[0],
+	.num_links = ARRAY_SIZE(omap3evm_dai),
+};
+
+static struct platform_device *omap3evm_snd_device;
+
+static int __init omap3evm_soc_init(void)
+{
+	int ret;
+#if defined(CONFIG_SND_SOC_WL1271BT)
+	u16 reg;
+	u32 val;
+#endif
+
+	if (!machine_is_omap3evm())
+		return -ENODEV;
+	pr_info("OMAP3 EVM SoC init\n");
+
+	omap3evm_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!omap3evm_snd_device) {
+		printk(KERN_ERR "Platform device allocation failed\n");
+		return -ENOMEM;
+	}
+
+#if defined(CONFIG_SND_SOC_WL1271BT)
+/*
+ * Set DEVCONF0 register to connect
+ * MCBSP1_CLKR -> MCBSP1_CLKX & MCBSP1_FSR -> MCBSP1_FSX
+ */
+	reg = OMAP2_CONTROL_DEVCONF0;
+	val = omap_ctrl_readl(reg);
+	val = val | 0x18;
+	omap_ctrl_writel(val, reg);
+#endif
+
+
+	platform_set_drvdata(omap3evm_snd_device, &snd_soc_omap3evm);
+	*(unsigned int *)omap3evm_dai[0].cpu_dai->private_data = 1; /* McBSP2 */
+#if defined(CONFIG_SND_SOC_WL1271BT)
+	*(unsigned int *)omap3evm_dai[1].cpu_dai->private_data = 0; /* McBSP1 */
+#endif
+	ret = platform_device_add(omap3evm_snd_device);
+	if (ret)
+		goto err1;
+
+	return 0;
+
+err1:
+	printk(KERN_ERR "Unable to add platform device\n");
+	platform_device_put(omap3evm_snd_device);
+
+	return ret;
+}
+
+static void __exit omap3evm_soc_exit(void)
+{
+	platform_device_unregister(omap3evm_snd_device);
+}
+
+module_init(omap3evm_soc_init);
+module_exit(omap3evm_soc_exit);
+
+MODULE_AUTHOR("Anuj Aggarwal <anuj.aggarwal@ti.com>");
+MODULE_DESCRIPTION("ALSA SoC OMAP3 EVM");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/omap/omap3pandora.c b/linux/sound/soc/omap/omap3pandora.c
new file mode 100644
index 0000000..4ee33ce
--- /dev/null
+++ b/linux/sound/soc/omap/omap3pandora.c
@@ -0,0 +1,338 @@
+/*
+ * omap3pandora.c  --  SoC audio for Pandora Handheld Console
+ *
+ * Author: Gražvydas Ignotas <notasas@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#define OMAP3_PANDORA_DAC_POWER_GPIO	118
+#define OMAP3_PANDORA_AMP_POWER_GPIO	14
+
+#define PREFIX "ASoC omap3pandora: "
+
+static struct regulator *omap3pandora_dac_reg;
+
+static int omap3pandora_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+		  SND_SOC_DAIFMT_CBS_CFS;
+	int ret;
+
+	/* Set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+	if (ret < 0) {
+		pr_err(PREFIX "can't set codec DAI configuration\n");
+		return ret;
+	}
+
+	/* Set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+	if (ret < 0) {
+		pr_err(PREFIX "can't set cpu DAI configuration\n");
+		return ret;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+					    SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		pr_err(PREFIX "can't set codec system clock\n");
+		return ret;
+	}
+
+	/* Set McBSP clock to external */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_EXT,
+				     256 * params_rate(params),
+				     SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		pr_err(PREFIX "can't set cpu system clock\n");
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, 8);
+	if (ret < 0) {
+		pr_err(PREFIX "can't set SRG clock divider\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int omap3pandora_dac_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *k, int event)
+{
+	/*
+	 * The PCM1773 DAC datasheet requires 1ms delay between switching
+	 * VCC power on/off and /PD pin high/low
+	 */
+	if (SND_SOC_DAPM_EVENT_ON(event)) {
+		regulator_enable(omap3pandora_dac_reg);
+		mdelay(1);
+		gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 1);
+	} else {
+		gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
+		mdelay(1);
+		regulator_disable(omap3pandora_dac_reg);
+	}
+
+	return 0;
+}
+
+static int omap3pandora_hp_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *k, int event)
+{
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 1);
+	else
+		gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
+
+	return 0;
+}
+
+/*
+ * Audio paths on Pandora board:
+ *
+ *  |O| ---> PCM DAC +-> AMP -> Headphone Jack
+ *  |M|         A    +--------> Line Out
+ *  |A| <~~clk~~+
+ *  |P| <--- TWL4030 <--------- Line In and MICs
+ */
+static const struct snd_soc_dapm_widget omap3pandora_out_dapm_widgets[] = {
+	SND_SOC_DAPM_DAC_E("PCM DAC", "HiFi Playback", SND_SOC_NOPM,
+			   0, 0, omap3pandora_dac_event,
+			   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_PGA_E("Headphone Amplifier", SND_SOC_NOPM,
+			   0, 0, NULL, 0, omap3pandora_hp_event,
+			   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_LINE("Line Out", NULL),
+};
+
+static const struct snd_soc_dapm_widget omap3pandora_in_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("Mic (internal)", NULL),
+	SND_SOC_DAPM_MIC("Mic (external)", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+static const struct snd_soc_dapm_route omap3pandora_out_map[] = {
+	{"PCM DAC", NULL, "APLL Enable"},
+	{"Headphone Amplifier", NULL, "PCM DAC"},
+	{"Line Out", NULL, "PCM DAC"},
+	{"Headphone Jack", NULL, "Headphone Amplifier"},
+};
+
+static const struct snd_soc_dapm_route omap3pandora_in_map[] = {
+	{"AUXL", NULL, "Line In"},
+	{"AUXR", NULL, "Line In"},
+
+	{"MAINMIC", NULL, "Mic Bias 1"},
+	{"Mic Bias 1", NULL, "Mic (internal)"},
+
+	{"SUBMIC", NULL, "Mic Bias 2"},
+	{"Mic Bias 2", NULL, "Mic (external)"},
+};
+
+static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int ret;
+
+	/* All TWL4030 output pins are floating */
+	snd_soc_dapm_nc_pin(codec, "EARPIECE");
+	snd_soc_dapm_nc_pin(codec, "PREDRIVEL");
+	snd_soc_dapm_nc_pin(codec, "PREDRIVER");
+	snd_soc_dapm_nc_pin(codec, "HSOL");
+	snd_soc_dapm_nc_pin(codec, "HSOR");
+	snd_soc_dapm_nc_pin(codec, "CARKITL");
+	snd_soc_dapm_nc_pin(codec, "CARKITR");
+	snd_soc_dapm_nc_pin(codec, "HFL");
+	snd_soc_dapm_nc_pin(codec, "HFR");
+	snd_soc_dapm_nc_pin(codec, "VIBRA");
+
+	ret = snd_soc_dapm_new_controls(codec, omap3pandora_out_dapm_widgets,
+				ARRAY_SIZE(omap3pandora_out_dapm_widgets));
+	if (ret < 0)
+		return ret;
+
+	snd_soc_dapm_add_routes(codec, omap3pandora_out_map,
+		ARRAY_SIZE(omap3pandora_out_map));
+
+	return snd_soc_dapm_sync(codec);
+}
+
+static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int ret;
+
+	/* Not comnnected */
+	snd_soc_dapm_nc_pin(codec, "HSMIC");
+	snd_soc_dapm_nc_pin(codec, "CARKITMIC");
+	snd_soc_dapm_nc_pin(codec, "DIGIMIC0");
+	snd_soc_dapm_nc_pin(codec, "DIGIMIC1");
+
+	ret = snd_soc_dapm_new_controls(codec, omap3pandora_in_dapm_widgets,
+				ARRAY_SIZE(omap3pandora_in_dapm_widgets));
+	if (ret < 0)
+		return ret;
+
+	snd_soc_dapm_add_routes(codec, omap3pandora_in_map,
+		ARRAY_SIZE(omap3pandora_in_map));
+
+	return snd_soc_dapm_sync(codec);
+}
+
+static struct snd_soc_ops omap3pandora_ops = {
+	.hw_params = omap3pandora_hw_params,
+};
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link omap3pandora_dai[] = {
+	{
+		.name = "PCM1773",
+		.stream_name = "HiFi Out",
+		.cpu_dai_name = "omap-mcbsp-dai.1",
+		.codec_dai_name = "twl4030-hifi",
+		.platform_name = "omap-pcm-audio",
+		.codec_name = "twl4030-codec",
+		.ops = &omap3pandora_ops,
+		.init = omap3pandora_out_init,
+	}, {
+		.name = "TWL4030",
+		.stream_name = "Line/Mic In",
+		.cpu_dai_name = "omap-mcbsp-dai.3",
+		.codec_dai_name = "twl4030-hifi",
+		.platform_name = "omap-pcm-audio",
+		.codec_name = "twl4030-codec",
+		.ops = &omap3pandora_ops,
+		.init = omap3pandora_in_init,
+	}
+};
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_omap3pandora = {
+	.name = "omap3pandora",
+	.dai_link = omap3pandora_dai,
+	.num_links = ARRAY_SIZE(omap3pandora_dai),
+};
+
+static struct platform_device *omap3pandora_snd_device;
+
+static int __init omap3pandora_soc_init(void)
+{
+	int ret;
+
+	if (!machine_is_omap3_pandora())
+		return -ENODEV;
+
+	pr_info("OMAP3 Pandora SoC init\n");
+
+	ret = gpio_request(OMAP3_PANDORA_DAC_POWER_GPIO, "dac_power");
+	if (ret) {
+		pr_err(PREFIX "Failed to get DAC power GPIO\n");
+		return ret;
+	}
+
+	ret = gpio_direction_output(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
+	if (ret) {
+		pr_err(PREFIX "Failed to set DAC power GPIO direction\n");
+		goto fail0;
+	}
+
+	ret = gpio_request(OMAP3_PANDORA_AMP_POWER_GPIO, "amp_power");
+	if (ret) {
+		pr_err(PREFIX "Failed to get amp power GPIO\n");
+		goto fail0;
+	}
+
+	ret = gpio_direction_output(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
+	if (ret) {
+		pr_err(PREFIX "Failed to set amp power GPIO direction\n");
+		goto fail1;
+	}
+
+	omap3pandora_snd_device = platform_device_alloc("soc-audio", -1);
+	if (omap3pandora_snd_device == NULL) {
+		pr_err(PREFIX "Platform device allocation failed\n");
+		ret = -ENOMEM;
+		goto fail1;
+	}
+
+	platform_set_drvdata(omap3pandora_snd_device, &snd_soc_card_omap3pandora);
+
+	ret = platform_device_add(omap3pandora_snd_device);
+	if (ret) {
+		pr_err(PREFIX "Unable to add platform device\n");
+		goto fail2;
+	}
+
+	omap3pandora_dac_reg = regulator_get(&omap3pandora_snd_device->dev, "vcc");
+	if (IS_ERR(omap3pandora_dac_reg)) {
+		pr_err(PREFIX "Failed to get DAC regulator from %s: %ld\n",
+			dev_name(&omap3pandora_snd_device->dev),
+			PTR_ERR(omap3pandora_dac_reg));
+		ret = PTR_ERR(omap3pandora_dac_reg);
+		goto fail3;
+	}
+
+	return 0;
+
+fail3:
+	platform_device_del(omap3pandora_snd_device);
+fail2:
+	platform_device_put(omap3pandora_snd_device);
+fail1:
+	gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO);
+fail0:
+	gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO);
+	return ret;
+}
+module_init(omap3pandora_soc_init);
+
+static void __exit omap3pandora_soc_exit(void)
+{
+	regulator_put(omap3pandora_dac_reg);
+	platform_device_unregister(omap3pandora_snd_device);
+	gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO);
+	gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO);
+}
+module_exit(omap3pandora_soc_exit);
+
+MODULE_AUTHOR("Grazvydas Ignotas <notasas@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC OMAP3 Pandora");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/osk5912.c b/linux/sound/soc/omap/osk5912.c
new file mode 100644
index 0000000..65ae00e
--- /dev/null
+++ b/linux/sound/soc/omap/osk5912.c
@@ -0,0 +1,223 @@
+/*
+ * osk5912.c  --  SoC audio for OSK 5912
+ *
+ * Copyright (C) 2008 Mistral Solutions
+ *
+ * Contact: Arun KS  <arunks@mistralsolutions.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <linux/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+#include "../codecs/tlv320aic23.h"
+
+#define CODEC_CLOCK 	12000000
+
+static struct clk *tlv320aic23_mclk;
+
+static int osk_startup(struct snd_pcm_substream *substream)
+{
+	return clk_enable(tlv320aic23_mclk);
+}
+
+static void osk_shutdown(struct snd_pcm_substream *substream)
+{
+	clk_disable(tlv320aic23_mclk);
+}
+
+static int osk_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int err;
+
+	/* Set codec DAI configuration */
+	err = snd_soc_dai_set_fmt(codec_dai,
+				  SND_SOC_DAIFMT_DSP_B |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (err < 0) {
+		printk(KERN_ERR "can't set codec DAI configuration\n");
+		return err;
+	}
+
+	/* Set cpu DAI configuration */
+	err = snd_soc_dai_set_fmt(cpu_dai,
+				  SND_SOC_DAIFMT_DSP_B |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (err < 0) {
+		printk(KERN_ERR "can't set cpu DAI configuration\n");
+		return err;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	err =
+	    snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN);
+
+	if (err < 0) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return err;
+	}
+
+	return err;
+}
+
+static struct snd_soc_ops osk_ops = {
+	.startup = osk_startup,
+	.hw_params = osk_hw_params,
+	.shutdown = osk_shutdown,
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"Headphone Jack", NULL, "LHPOUT"},
+	{"Headphone Jack", NULL, "RHPOUT"},
+
+	{"LLINEIN", NULL, "Line In"},
+	{"RLINEIN", NULL, "Line In"},
+
+	{"MICIN", NULL, "Mic Jack"},
+};
+
+static int osk_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	/* Add osk5912 specific widgets */
+	snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets,
+				  ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+	/* Set up osk5912 specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+	snd_soc_dapm_enable_pin(codec, "Line In");
+	snd_soc_dapm_enable_pin(codec, "Mic Jack");
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link osk_dai = {
+	.name = "TLV320AIC23",
+	.stream_name = "AIC23",
+	.cpu_dai_name = "omap-mcbsp-dai.0",
+	.codec_dai_name = "tlv320aic23-hifi",
+	.platform_name = "omap-pcm-audio",
+	.codec_name = "tlv320aic23-codec",
+	.init = osk_tlv320aic23_init,
+	.ops = &osk_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_card_osk = {
+	.name = "OSK5912",
+	.dai_link = &osk_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *osk_snd_device;
+
+static int __init osk_soc_init(void)
+{
+	int err;
+	u32 curRate;
+	struct device *dev;
+
+	if (!(machine_is_omap_osk()))
+		return -ENODEV;
+
+	osk_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!osk_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(osk_snd_device, &snd_soc_card_osk);
+	err = platform_device_add(osk_snd_device);
+	if (err)
+		goto err1;
+
+	dev = &osk_snd_device->dev;
+
+	tlv320aic23_mclk = clk_get(dev, "mclk");
+	if (IS_ERR(tlv320aic23_mclk)) {
+		printk(KERN_ERR "Could not get mclk clock\n");
+		err = PTR_ERR(tlv320aic23_mclk);
+		goto err2;
+	}
+
+	/*
+	 * Configure 12 MHz output on MCLK.
+	 */
+	curRate = (uint) clk_get_rate(tlv320aic23_mclk);
+	if (curRate != CODEC_CLOCK) {
+		if (clk_set_rate(tlv320aic23_mclk, CODEC_CLOCK)) {
+			printk(KERN_ERR "Cannot set MCLK for AIC23 CODEC\n");
+			err = -ECANCELED;
+			goto err3;
+		}
+	}
+
+	printk(KERN_INFO "MCLK = %d [%d]\n",
+	       (uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK);
+
+	return 0;
+
+err3:
+	clk_put(tlv320aic23_mclk);
+err2:
+	platform_device_del(osk_snd_device);
+err1:
+	platform_device_put(osk_snd_device);
+
+	return err;
+
+}
+
+static void __exit osk_soc_exit(void)
+{
+	clk_put(tlv320aic23_mclk);
+	platform_device_unregister(osk_snd_device);
+}
+
+module_init(osk_soc_init);
+module_exit(osk_soc_exit);
+
+MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>");
+MODULE_DESCRIPTION("ALSA SoC OSK 5912");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/overo.c b/linux/sound/soc/omap/overo.c
new file mode 100644
index 0000000..e95a607
--- /dev/null
+++ b/linux/sound/soc/omap/overo.c
@@ -0,0 +1,140 @@
+/*
+ * overo.c  --  SoC audio for Gumstix Overo
+ *
+ * Author: Steve Sakoman <steve@sakoman.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+static int overo_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* Set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec DAI configuration\n");
+		return ret;
+	}
+
+	/* Set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set cpu DAI configuration\n");
+		return ret;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+					    SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops overo_ops = {
+	.hw_params = overo_hw_params,
+};
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link overo_dai = {
+	.name = "TWL4030",
+	.stream_name = "TWL4030",
+	.cpu_dai_name = "omap-mcbsp-dai.1",
+	.codec_dai_name = "twl4030-hifi",
+	.platform_name = "omap-pcm-audio",
+	.codec_name = "twl4030-codec",
+	.ops = &overo_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_card_overo = {
+	.name = "overo",
+	.dai_link = &overo_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *overo_snd_device;
+
+static int __init overo_soc_init(void)
+{
+	int ret;
+
+	if (!(machine_is_overo() || machine_is_cm_t35())) {
+		pr_debug("Incomatible machine!\n");
+		return -ENODEV;
+	}
+	printk(KERN_INFO "overo SoC init\n");
+
+	overo_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!overo_snd_device) {
+		printk(KERN_ERR "Platform device allocation failed\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(overo_snd_device, &snd_soc_card_overo);
+
+	ret = platform_device_add(overo_snd_device);
+	if (ret)
+		goto err1;
+
+	return 0;
+
+err1:
+	printk(KERN_ERR "Unable to add platform device\n");
+	platform_device_put(overo_snd_device);
+
+	return ret;
+}
+module_init(overo_soc_init);
+
+static void __exit overo_soc_exit(void)
+{
+	platform_device_unregister(overo_snd_device);
+}
+module_exit(overo_soc_exit);
+
+MODULE_AUTHOR("Steve Sakoman <steve@sakoman.com>");
+MODULE_DESCRIPTION("ALSA SoC overo");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/rx51.c b/linux/sound/soc/omap/rx51.c
new file mode 100644
index 0000000..04b5723
--- /dev/null
+++ b/linux/sound/soc/omap/rx51.c
@@ -0,0 +1,352 @@
+/*
+ * rx51.c  --  SoC audio for Nokia RX-51
+ *
+ * Copyright (C) 2008 - 2009 Nokia Corporation
+ *
+ * Contact: Peter Ujfalusi <peter.ujfalusi@nokia.com>
+ *          Eduardo Valentin <eduardo.valentin@nokia.com>
+ *          Jarkko Nikula <jhnikula@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <plat/mcbsp.h>
+
+#include <asm/mach-types.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+#include "../codecs/tlv320aic3x.h"
+
+#define RX51_TVOUT_SEL_GPIO		40
+#define RX51_JACK_DETECT_GPIO		177
+/*
+ * REVISIT: TWL4030 GPIO base in RX-51. Now statically defined to 192. This
+ * gpio is reserved in arch/arm/mach-omap2/board-rx51-peripherals.c
+ */
+#define RX51_SPEAKER_AMP_TWL_GPIO	(192 + 7)
+
+enum {
+	RX51_JACK_DISABLED,
+	RX51_JACK_TVOUT,		/* tv-out */
+};
+
+static int rx51_spk_func;
+static int rx51_dmic_func;
+static int rx51_jack_func;
+
+static void rx51_ext_control(struct snd_soc_codec *codec)
+{
+	if (rx51_spk_func)
+		snd_soc_dapm_enable_pin(codec, "Ext Spk");
+	else
+		snd_soc_dapm_disable_pin(codec, "Ext Spk");
+	if (rx51_dmic_func)
+		snd_soc_dapm_enable_pin(codec, "DMic");
+	else
+		snd_soc_dapm_disable_pin(codec, "DMic");
+
+	gpio_set_value(RX51_TVOUT_SEL_GPIO,
+		       rx51_jack_func == RX51_JACK_TVOUT);
+
+	snd_soc_dapm_sync(codec);
+}
+
+static int rx51_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	snd_pcm_hw_constraint_minmax(runtime,
+				     SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2);
+	rx51_ext_control(codec);
+
+	return 0;
+}
+
+static int rx51_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int err;
+
+	/* Set codec DAI configuration */
+	err = snd_soc_dai_set_fmt(codec_dai,
+				  SND_SOC_DAIFMT_DSP_A |
+				  SND_SOC_DAIFMT_IB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (err < 0)
+		return err;
+
+	/* Set cpu DAI configuration */
+	err = snd_soc_dai_set_fmt(cpu_dai,
+				  SND_SOC_DAIFMT_DSP_A |
+				  SND_SOC_DAIFMT_IB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (err < 0)
+		return err;
+
+	/* Set the codec system clock for DAC and ADC */
+	return snd_soc_dai_set_sysclk(codec_dai, 0, 19200000,
+				      SND_SOC_CLOCK_IN);
+}
+
+static struct snd_soc_ops rx51_ops = {
+	.startup = rx51_startup,
+	.hw_params = rx51_hw_params,
+};
+
+static int rx51_get_spk(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = rx51_spk_func;
+
+	return 0;
+}
+
+static int rx51_set_spk(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	if (rx51_spk_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	rx51_spk_func = ucontrol->value.integer.value[0];
+	rx51_ext_control(codec);
+
+	return 1;
+}
+
+static int rx51_spk_event(struct snd_soc_dapm_widget *w,
+			  struct snd_kcontrol *k, int event)
+{
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		gpio_set_value_cansleep(RX51_SPEAKER_AMP_TWL_GPIO, 1);
+	else
+		gpio_set_value_cansleep(RX51_SPEAKER_AMP_TWL_GPIO, 0);
+
+	return 0;
+}
+
+static int rx51_get_input(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = rx51_dmic_func;
+
+	return 0;
+}
+
+static int rx51_set_input(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	if (rx51_dmic_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	rx51_dmic_func = ucontrol->value.integer.value[0];
+	rx51_ext_control(codec);
+
+	return 1;
+}
+
+static int rx51_get_jack(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = rx51_jack_func;
+
+	return 0;
+}
+
+static int rx51_set_jack(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	if (rx51_jack_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	rx51_jack_func = ucontrol->value.integer.value[0];
+	rx51_ext_control(codec);
+
+	return 1;
+}
+
+static struct snd_soc_jack rx51_av_jack;
+
+static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = {
+	{
+		.gpio = RX51_JACK_DETECT_GPIO,
+		.name = "avdet-gpio",
+		.report = SND_JACK_VIDEOOUT,
+		.invert = 1,
+		.debounce_time = 200,
+	},
+};
+
+static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event),
+	SND_SOC_DAPM_MIC("DMic", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"Ext Spk", NULL, "HPLOUT"},
+	{"Ext Spk", NULL, "HPROUT"},
+
+	{"DMic Rate 64", NULL, "Mic Bias 2V"},
+	{"Mic Bias 2V", NULL, "DMic"},
+};
+
+static const char *spk_function[] = {"Off", "On"};
+static const char *input_function[] = {"ADC", "Digital Mic"};
+static const char *jack_function[] = {"Off", "TV-OUT"};
+
+static const struct soc_enum rx51_enum[] = {
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function),
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function),
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function),
+};
+
+static const struct snd_kcontrol_new aic34_rx51_controls[] = {
+	SOC_ENUM_EXT("Speaker Function", rx51_enum[0],
+		     rx51_get_spk, rx51_set_spk),
+	SOC_ENUM_EXT("Input Select",  rx51_enum[1],
+		     rx51_get_input, rx51_set_input),
+	SOC_ENUM_EXT("Jack Function", rx51_enum[2],
+		     rx51_get_jack, rx51_set_jack),
+};
+
+static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	/* Set up NC codec pins */
+	snd_soc_dapm_nc_pin(codec, "MIC3L");
+	snd_soc_dapm_nc_pin(codec, "MIC3R");
+	snd_soc_dapm_nc_pin(codec, "LINE1R");
+
+	/* Add RX-51 specific controls */
+	err = snd_soc_add_controls(codec, aic34_rx51_controls,
+				   ARRAY_SIZE(aic34_rx51_controls));
+	if (err < 0)
+		return err;
+
+	/* Add RX-51 specific widgets */
+	snd_soc_dapm_new_controls(codec, aic34_dapm_widgets,
+				  ARRAY_SIZE(aic34_dapm_widgets));
+
+	/* Set up RX-51 specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_sync(codec);
+
+	/* AV jack detection */
+	err = snd_soc_jack_new(codec, "AV Jack",
+			       SND_JACK_VIDEOOUT, &rx51_av_jack);
+	if (err)
+		return err;
+	err = snd_soc_jack_add_gpios(&rx51_av_jack,
+				     ARRAY_SIZE(rx51_av_jack_gpios),
+				     rx51_av_jack_gpios);
+
+	return err;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link rx51_dai[] = {
+	{
+		.name = "TLV320AIC34",
+		.stream_name = "AIC34",
+		.cpu_dai_name = "omap-mcbsp-dai.1",
+		.codec_dai_name = "tlv320aic3x-hifi",
+		.platform_name = "omap-pcm-audio",
+		.codec_name = "tlv320aic3x-codec.2-0018",
+		.init = rx51_aic34_init,
+		.ops = &rx51_ops,
+	},
+};
+
+/* Audio card */
+static struct snd_soc_card rx51_sound_card = {
+	.name = "RX-51",
+	.dai_link = rx51_dai,
+	.num_links = ARRAY_SIZE(rx51_dai),
+};
+
+static struct platform_device *rx51_snd_device;
+
+static int __init rx51_soc_init(void)
+{
+	int err;
+
+	if (!machine_is_nokia_rx51())
+		return -ENODEV;
+
+	err = gpio_request(RX51_TVOUT_SEL_GPIO, "tvout_sel");
+	if (err)
+		goto err_gpio_tvout_sel;
+	gpio_direction_output(RX51_TVOUT_SEL_GPIO, 0);
+
+	rx51_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!rx51_snd_device) {
+		err = -ENOMEM;
+		goto err1;
+	}
+
+	platform_set_drvdata(rx51_snd_device, &rx51_sound_card);
+
+	err = platform_device_add(rx51_snd_device);
+	if (err)
+		goto err2;
+
+	return 0;
+err2:
+	platform_device_put(rx51_snd_device);
+err1:
+	gpio_free(RX51_TVOUT_SEL_GPIO);
+err_gpio_tvout_sel:
+
+	return err;
+}
+
+static void __exit rx51_soc_exit(void)
+{
+	snd_soc_jack_free_gpios(&rx51_av_jack, ARRAY_SIZE(rx51_av_jack_gpios),
+				rx51_av_jack_gpios);
+
+	platform_device_unregister(rx51_snd_device);
+	gpio_free(RX51_TVOUT_SEL_GPIO);
+}
+
+module_init(rx51_soc_init);
+module_exit(rx51_soc_exit);
+
+MODULE_AUTHOR("Nokia Corporation");
+MODULE_DESCRIPTION("ALSA SoC Nokia RX-51");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/omap/sdp3430.c b/linux/sound/soc/omap/sdp3430.c
new file mode 100644
index 0000000..07fbcf7
--- /dev/null
+++ b/linux/sound/soc/omap/sdp3430.c
@@ -0,0 +1,345 @@
+/*
+ * sdp3430.c  --  SoC audio for TI OMAP3430 SDP
+ *
+ * Author: Misael Lopez Cruz <x0052729@ti.com>
+ *
+ * Based on:
+ * Author: Steve Sakoman <steve@sakoman.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <plat/mcbsp.h>
+
+/* Register descriptions for twl4030 codec part */
+#include <linux/mfd/twl4030-codec.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+/* TWL4030 PMBR1 Register */
+#define TWL4030_INTBR_PMBR1		0x0D
+/* TWL4030 PMBR1 Register GPIO6 mux bit */
+#define TWL4030_GPIO6_PWM0_MUTE(value)	(value << 2)
+
+static struct snd_soc_card snd_soc_sdp3430;
+
+static int sdp3430_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* Set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec DAI configuration\n");
+		return ret;
+	}
+
+	/* Set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set cpu DAI configuration\n");
+		return ret;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+					    SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops sdp3430_ops = {
+	.hw_params = sdp3430_hw_params,
+};
+
+static int sdp3430_hw_voice_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* Set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+				SND_SOC_DAIFMT_DSP_A |
+				SND_SOC_DAIFMT_IB_NF |
+				SND_SOC_DAIFMT_CBM_CFM);
+	if (ret) {
+		printk(KERN_ERR "can't set codec DAI configuration\n");
+		return ret;
+	}
+
+	/* Set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+				SND_SOC_DAIFMT_DSP_A |
+				SND_SOC_DAIFMT_IB_NF |
+				SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set cpu DAI configuration\n");
+		return ret;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+					    SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops sdp3430_voice_ops = {
+	.hw_params = sdp3430_hw_voice_params,
+};
+
+/* Headset jack */
+static struct snd_soc_jack hs_jack;
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+	{
+		.pin = "Headset Mic",
+		.mask = SND_JACK_MICROPHONE,
+	},
+	{
+		.pin = "Headset Stereophone",
+		.mask = SND_JACK_HEADPHONE,
+	},
+};
+
+/* Headset jack detection gpios */
+static struct snd_soc_jack_gpio hs_jack_gpios[] = {
+	{
+		.gpio = (OMAP_MAX_GPIO_LINES + 2),
+		.name = "hsdet-gpio",
+		.report = SND_JACK_HEADSET,
+		.debounce_time = 200,
+	},
+};
+
+/* SDP3430 machine DAPM */
+static const struct snd_soc_dapm_widget sdp3430_twl4030_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("Ext Mic", NULL),
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* External Mics: MAINMIC, SUBMIC with bias*/
+	{"MAINMIC", NULL, "Mic Bias 1"},
+	{"SUBMIC", NULL, "Mic Bias 2"},
+	{"Mic Bias 1", NULL, "Ext Mic"},
+	{"Mic Bias 2", NULL, "Ext Mic"},
+
+	/* External Speakers: HFL, HFR */
+	{"Ext Spk", NULL, "HFL"},
+	{"Ext Spk", NULL, "HFR"},
+
+	/* Headset Mic: HSMIC with bias */
+	{"HSMIC", NULL, "Headset Mic Bias"},
+	{"Headset Mic Bias", NULL, "Headset Mic"},
+
+	/* Headset Stereophone (Headphone): HSOL, HSOR */
+	{"Headset Stereophone", NULL, "HSOL"},
+	{"Headset Stereophone", NULL, "HSOR"},
+};
+
+static int sdp3430_twl4030_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int ret;
+
+	/* Add SDP3430 specific widgets */
+	ret = snd_soc_dapm_new_controls(codec, sdp3430_twl4030_dapm_widgets,
+				ARRAY_SIZE(sdp3430_twl4030_dapm_widgets));
+	if (ret)
+		return ret;
+
+	/* Set up SDP3430 specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	/* SDP3430 connected pins */
+	snd_soc_dapm_enable_pin(codec, "Ext Mic");
+	snd_soc_dapm_enable_pin(codec, "Ext Spk");
+	snd_soc_dapm_disable_pin(codec, "Headset Mic");
+	snd_soc_dapm_disable_pin(codec, "Headset Stereophone");
+
+	/* TWL4030 not connected pins */
+	snd_soc_dapm_nc_pin(codec, "AUXL");
+	snd_soc_dapm_nc_pin(codec, "AUXR");
+	snd_soc_dapm_nc_pin(codec, "CARKITMIC");
+	snd_soc_dapm_nc_pin(codec, "DIGIMIC0");
+	snd_soc_dapm_nc_pin(codec, "DIGIMIC1");
+
+	snd_soc_dapm_nc_pin(codec, "OUTL");
+	snd_soc_dapm_nc_pin(codec, "OUTR");
+	snd_soc_dapm_nc_pin(codec, "EARPIECE");
+	snd_soc_dapm_nc_pin(codec, "PREDRIVEL");
+	snd_soc_dapm_nc_pin(codec, "PREDRIVER");
+	snd_soc_dapm_nc_pin(codec, "CARKITL");
+	snd_soc_dapm_nc_pin(codec, "CARKITR");
+
+	ret = snd_soc_dapm_sync(codec);
+	if (ret)
+		return ret;
+
+	/* Headset jack detection */
+	ret = snd_soc_jack_new(codec, "Headset Jack",
+				SND_JACK_HEADSET, &hs_jack);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+				hs_jack_pins);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios),
+				hs_jack_gpios);
+
+	return ret;
+}
+
+static int sdp3430_twl4030_voice_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	unsigned short reg;
+
+	/* Enable voice interface */
+	reg = codec->driver->read(codec, TWL4030_REG_VOICE_IF);
+	reg |= TWL4030_VIF_DIN_EN | TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN;
+	codec->driver->write(codec, TWL4030_REG_VOICE_IF, reg);
+
+	return 0;
+}
+
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link sdp3430_dai[] = {
+	{
+		.name = "TWL4030 I2S",
+		.stream_name = "TWL4030 Audio",
+		.cpu_dai_name = "omap-mcbsp-dai.1",
+		.codec_dai_name = "twl4030-hifi",
+		.platform_name = "omap-pcm-audio",
+		.codec_name = "twl4030-codec",
+		.init = sdp3430_twl4030_init,
+		.ops = &sdp3430_ops,
+	},
+	{
+		.name = "TWL4030 PCM",
+		.stream_name = "TWL4030 Voice",
+		.cpu_dai_name = "omap-mcbsp-dai.2",
+		.codec_dai_name = "twl4030-voice",
+		.platform_name = "omap-pcm-audio",
+		.codec_name = "twl4030-codec",
+		.init = sdp3430_twl4030_voice_init,
+		.ops = &sdp3430_voice_ops,
+	},
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_sdp3430 = {
+	.name = "SDP3430",
+	.dai_link = sdp3430_dai,
+	.num_links = ARRAY_SIZE(sdp3430_dai),
+};
+
+static struct platform_device *sdp3430_snd_device;
+
+static int __init sdp3430_soc_init(void)
+{
+	int ret;
+	u8 pin_mux;
+
+	if (!machine_is_omap_3430sdp())
+		return -ENODEV;
+	printk(KERN_INFO "SDP3430 SoC init\n");
+
+	sdp3430_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!sdp3430_snd_device) {
+		printk(KERN_ERR "Platform device allocation failed\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(sdp3430_snd_device, &snd_soc_sdp3430);
+
+	/* Set TWL4030 GPIO6 as EXTMUTE signal */
+	twl_i2c_read_u8(TWL4030_MODULE_INTBR, &pin_mux,
+						TWL4030_INTBR_PMBR1);
+	pin_mux &= ~TWL4030_GPIO6_PWM0_MUTE(0x03);
+	pin_mux |= TWL4030_GPIO6_PWM0_MUTE(0x02);
+	twl_i2c_write_u8(TWL4030_MODULE_INTBR, pin_mux,
+						TWL4030_INTBR_PMBR1);
+
+	ret = platform_device_add(sdp3430_snd_device);
+	if (ret)
+		goto err1;
+
+	return 0;
+
+err1:
+	printk(KERN_ERR "Unable to add platform device\n");
+	platform_device_put(sdp3430_snd_device);
+
+	return ret;
+}
+module_init(sdp3430_soc_init);
+
+static void __exit sdp3430_soc_exit(void)
+{
+	snd_soc_jack_free_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios),
+				hs_jack_gpios);
+
+	platform_device_unregister(sdp3430_snd_device);
+}
+module_exit(sdp3430_soc_exit);
+
+MODULE_AUTHOR("Misael Lopez Cruz <x0052729@ti.com>");
+MODULE_DESCRIPTION("ALSA SoC SDP3430");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/omap/sdp4430.c b/linux/sound/soc/omap/sdp4430.c
new file mode 100644
index 0000000..4b4463d
--- /dev/null
+++ b/linux/sound/soc/omap/sdp4430.c
@@ -0,0 +1,226 @@
+/*
+ * sdp4430.c  --  SoC audio for TI OMAP4430 SDP
+ *
+ * Author: Misael Lopez Cruz <x0052729@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <plat/hardware.h>
+#include <plat/mux.h>
+
+#include "mcpdm.h"
+#include "omap-pcm.h"
+#include "../codecs/twl6040.h"
+
+static int twl6040_power_mode;
+
+static int sdp4430_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	int clk_id, freq;
+	int ret;
+
+	if (twl6040_power_mode) {
+		clk_id = TWL6040_SYSCLK_SEL_HPPLL;
+		freq = 38400000;
+	} else {
+		clk_id = TWL6040_SYSCLK_SEL_LPPLL;
+		freq = 32768;
+	}
+
+	/* set the codec mclk */
+	ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, freq,
+				SND_SOC_CLOCK_IN);
+	if (ret) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return ret;
+	}
+	return ret;
+}
+
+static struct snd_soc_ops sdp4430_ops = {
+	.hw_params = sdp4430_hw_params,
+};
+
+static int sdp4430_get_power_mode(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = twl6040_power_mode;
+	return 0;
+}
+
+static int sdp4430_set_power_mode(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	if (twl6040_power_mode == ucontrol->value.integer.value[0])
+		return 0;
+
+	twl6040_power_mode = ucontrol->value.integer.value[0];
+
+	return 1;
+}
+
+static const char *power_texts[] = {"Low-Power", "High-Performance"};
+
+static const struct soc_enum sdp4430_enum[] = {
+	SOC_ENUM_SINGLE_EXT(2, power_texts),
+};
+
+static const struct snd_kcontrol_new sdp4430_controls[] = {
+	SOC_ENUM_EXT("TWL6040 Power Mode", sdp4430_enum[0],
+		sdp4430_get_power_mode, sdp4430_set_power_mode),
+};
+
+/* SDP4430 machine DAPM */
+static const struct snd_soc_dapm_widget sdp4430_twl6040_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("Ext Mic", NULL),
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+	SND_SOC_DAPM_SPK("Earphone Spk", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* External Mics: MAINMIC, SUBMIC with bias*/
+	{"MAINMIC", NULL, "Main Mic Bias"},
+	{"SUBMIC", NULL, "Main Mic Bias"},
+	{"Main Mic Bias", NULL, "Ext Mic"},
+
+	/* External Speakers: HFL, HFR */
+	{"Ext Spk", NULL, "HFL"},
+	{"Ext Spk", NULL, "HFR"},
+
+	/* Headset Mic: HSMIC with bias */
+	{"HSMIC", NULL, "Headset Mic Bias"},
+	{"Headset Mic Bias", NULL, "Headset Mic"},
+
+	/* Headset Stereophone (Headphone): HSOL, HSOR */
+	{"Headset Stereophone", NULL, "HSOL"},
+	{"Headset Stereophone", NULL, "HSOR"},
+
+	/* Earphone speaker */
+	{"Earphone Spk", NULL, "EP"},
+};
+
+static int sdp4430_twl6040_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int ret;
+
+	/* Add SDP4430 specific controls */
+	ret = snd_soc_add_controls(codec, sdp4430_controls,
+				ARRAY_SIZE(sdp4430_controls));
+	if (ret)
+		return ret;
+
+	/* Add SDP4430 specific widgets */
+	ret = snd_soc_dapm_new_controls(codec, sdp4430_twl6040_dapm_widgets,
+				ARRAY_SIZE(sdp4430_twl6040_dapm_widgets));
+	if (ret)
+		return ret;
+
+	/* Set up SDP4430 specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	/* SDP4430 connected pins */
+	snd_soc_dapm_enable_pin(codec, "Ext Mic");
+	snd_soc_dapm_enable_pin(codec, "Ext Spk");
+	snd_soc_dapm_enable_pin(codec, "Headset Mic");
+	snd_soc_dapm_enable_pin(codec, "Headset Stereophone");
+
+	/* TWL6040 not connected pins */
+	snd_soc_dapm_nc_pin(codec, "AFML");
+	snd_soc_dapm_nc_pin(codec, "AFMR");
+
+	ret = snd_soc_dapm_sync(codec);
+
+	return ret;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link sdp4430_dai = {
+	.name = "TWL6040",
+	.stream_name = "TWL6040",
+	.cpu_dai_name ="omap-mcpdm-dai",
+	.codec_dai_name = "twl6040-hifi",
+	.platform_name = "omap-pcm-audio",
+	.codec_name = "twl6040-codec",
+	.init = sdp4430_twl6040_init,
+	.ops = &sdp4430_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_sdp4430 = {
+	.name = "SDP4430",
+	.dai_link = &sdp4430_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *sdp4430_snd_device;
+
+static int __init sdp4430_soc_init(void)
+{
+	int ret;
+
+	if (!machine_is_omap_4430sdp())
+		return -ENODEV;
+	printk(KERN_INFO "SDP4430 SoC init\n");
+
+	sdp4430_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!sdp4430_snd_device) {
+		printk(KERN_ERR "Platform device allocation failed\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(sdp4430_snd_device, &snd_soc_sdp4430);
+
+	ret = platform_device_add(sdp4430_snd_device);
+	if (ret)
+		goto err;
+
+	/* Codec starts in HP mode */
+	twl6040_power_mode = 1;
+
+	return 0;
+
+err:
+	printk(KERN_ERR "Unable to add platform device\n");
+	platform_device_put(sdp4430_snd_device);
+	return ret;
+}
+module_init(sdp4430_soc_init);
+
+static void __exit sdp4430_soc_exit(void)
+{
+	platform_device_unregister(sdp4430_snd_device);
+}
+module_exit(sdp4430_soc_exit);
+
+MODULE_AUTHOR("Misael Lopez Cruz <x0052729@ti.com>");
+MODULE_DESCRIPTION("ALSA SoC SDP4430");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/omap/zoom2.c b/linux/sound/soc/omap/zoom2.c
new file mode 100644
index 0000000..718031e
--- /dev/null
+++ b/linux/sound/soc/omap/zoom2.c
@@ -0,0 +1,291 @@
+/*
+ * zoom2.c  --  SoC audio for Zoom2
+ *
+ * Author: Misael Lopez Cruz <x0052729@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/gpio.h>
+#include <mach/board-zoom.h>
+#include <plat/mcbsp.h>
+
+/* Register descriptions for twl4030 codec part */
+#include <linux/mfd/twl4030-codec.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+
+#define ZOOM2_HEADSET_MUX_GPIO		(OMAP_MAX_GPIO_LINES + 15)
+
+static int zoom2_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* Set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec DAI configuration\n");
+		return ret;
+	}
+
+	/* Set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+				  SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set cpu DAI configuration\n");
+		return ret;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+					SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops zoom2_ops = {
+	.hw_params = zoom2_hw_params,
+};
+
+static int zoom2_hw_voice_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* Set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+				SND_SOC_DAIFMT_DSP_A |
+				SND_SOC_DAIFMT_IB_NF |
+				SND_SOC_DAIFMT_CBM_CFM);
+	if (ret) {
+		printk(KERN_ERR "can't set codec DAI configuration\n");
+		return ret;
+	}
+
+	/* Set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+				SND_SOC_DAIFMT_DSP_A |
+				SND_SOC_DAIFMT_IB_NF |
+				SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set cpu DAI configuration\n");
+		return ret;
+	}
+
+	/* Set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
+					SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		printk(KERN_ERR "can't set codec system clock\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops zoom2_voice_ops = {
+	.hw_params = zoom2_hw_voice_params,
+};
+
+/* Zoom2 machine DAPM */
+static const struct snd_soc_dapm_widget zoom2_twl4030_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("Ext Mic", NULL),
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+	SND_SOC_DAPM_LINE("Aux In", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* External Mics: MAINMIC, SUBMIC with bias*/
+	{"MAINMIC", NULL, "Mic Bias 1"},
+	{"SUBMIC", NULL, "Mic Bias 2"},
+	{"Mic Bias 1", NULL, "Ext Mic"},
+	{"Mic Bias 2", NULL, "Ext Mic"},
+
+	/* External Speakers: HFL, HFR */
+	{"Ext Spk", NULL, "HFL"},
+	{"Ext Spk", NULL, "HFR"},
+
+	/* Headset Stereophone:  HSOL, HSOR */
+	{"Headset Stereophone", NULL, "HSOL"},
+	{"Headset Stereophone", NULL, "HSOR"},
+
+	/* Headset Mic: HSMIC with bias */
+	{"HSMIC", NULL, "Headset Mic Bias"},
+	{"Headset Mic Bias", NULL, "Headset Mic"},
+
+	/* Aux In: AUXL, AUXR */
+	{"Aux In", NULL, "AUXL"},
+	{"Aux In", NULL, "AUXR"},
+};
+
+static int zoom2_twl4030_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int ret;
+
+	/* Add Zoom2 specific widgets */
+	ret = snd_soc_dapm_new_controls(codec, zoom2_twl4030_dapm_widgets,
+				ARRAY_SIZE(zoom2_twl4030_dapm_widgets));
+	if (ret)
+		return ret;
+
+	/* Set up Zoom2 specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	/* Zoom2 connected pins */
+	snd_soc_dapm_enable_pin(codec, "Ext Mic");
+	snd_soc_dapm_enable_pin(codec, "Ext Spk");
+	snd_soc_dapm_enable_pin(codec, "Headset Mic");
+	snd_soc_dapm_enable_pin(codec, "Headset Stereophone");
+	snd_soc_dapm_enable_pin(codec, "Aux In");
+
+	/* TWL4030 not connected pins */
+	snd_soc_dapm_nc_pin(codec, "CARKITMIC");
+	snd_soc_dapm_nc_pin(codec, "DIGIMIC0");
+	snd_soc_dapm_nc_pin(codec, "DIGIMIC1");
+	snd_soc_dapm_nc_pin(codec, "EARPIECE");
+	snd_soc_dapm_nc_pin(codec, "PREDRIVEL");
+	snd_soc_dapm_nc_pin(codec, "PREDRIVER");
+	snd_soc_dapm_nc_pin(codec, "CARKITL");
+	snd_soc_dapm_nc_pin(codec, "CARKITR");
+
+	ret = snd_soc_dapm_sync(codec);
+
+	return ret;
+}
+
+static int zoom2_twl4030_voice_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	unsigned short reg;
+
+	/* Enable voice interface */
+	reg = codec->driver->read(codec, TWL4030_REG_VOICE_IF);
+	reg |= TWL4030_VIF_DIN_EN | TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN;
+	codec->driver->write(codec, TWL4030_REG_VOICE_IF, reg);
+
+	return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link zoom2_dai[] = {
+	{
+		.name = "TWL4030 I2S",
+		.stream_name = "TWL4030 Audio",
+		.cpu_dai_name = "omap-mcbsp-dai.1",
+		.codec_dai_name = "twl4030-hifi",
+		.platform_name = "omap-pcm-audio",
+		.codec_name = "twl4030-codec",
+		.init = zoom2_twl4030_init,
+		.ops = &zoom2_ops,
+	},
+	{
+		.name = "TWL4030 PCM",
+		.stream_name = "TWL4030 Voice",
+		.cpu_dai_name = "omap-mcbsp-dai.2",
+		.codec_dai_name = "twl4030-voice",
+		.platform_name = "omap-pcm-audio",
+		.codec_name = "twl4030-codec",
+		.init = zoom2_twl4030_voice_init,
+		.ops = &zoom2_voice_ops,
+	},
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_zoom2 = {
+	.name = "Zoom2",
+	.dai_link = zoom2_dai,
+	.num_links = ARRAY_SIZE(zoom2_dai),
+};
+
+static struct platform_device *zoom2_snd_device;
+
+static int __init zoom2_soc_init(void)
+{
+	int ret;
+
+	if (!machine_is_omap_zoom2())
+		return -ENODEV;
+	printk(KERN_INFO "Zoom2 SoC init\n");
+
+	zoom2_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!zoom2_snd_device) {
+		printk(KERN_ERR "Platform device allocation failed\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(zoom2_snd_device, &snd_soc_zoom2);
+	ret = platform_device_add(zoom2_snd_device);
+	if (ret)
+		goto err1;
+
+	BUG_ON(gpio_request(ZOOM2_HEADSET_MUX_GPIO, "hs_mux") < 0);
+	gpio_direction_output(ZOOM2_HEADSET_MUX_GPIO, 0);
+
+	BUG_ON(gpio_request(ZOOM2_HEADSET_EXTMUTE_GPIO, "ext_mute") < 0);
+	gpio_direction_output(ZOOM2_HEADSET_EXTMUTE_GPIO, 0);
+
+	return 0;
+
+err1:
+	printk(KERN_ERR "Unable to add platform device\n");
+	platform_device_put(zoom2_snd_device);
+
+	return ret;
+}
+module_init(zoom2_soc_init);
+
+static void __exit zoom2_soc_exit(void)
+{
+	gpio_free(ZOOM2_HEADSET_MUX_GPIO);
+	gpio_free(ZOOM2_HEADSET_EXTMUTE_GPIO);
+
+	platform_device_unregister(zoom2_snd_device);
+}
+module_exit(zoom2_soc_exit);
+
+MODULE_AUTHOR("Misael Lopez Cruz <x0052729@ti.com>");
+MODULE_DESCRIPTION("ALSA SoC Zoom2");
+MODULE_LICENSE("GPL");
+
diff --git a/linux/sound/soc/pxa/Kconfig b/linux/sound/soc/pxa/Kconfig
new file mode 100644
index 0000000..580f485
--- /dev/null
+++ b/linux/sound/soc/pxa/Kconfig
@@ -0,0 +1,184 @@
+config SND_PXA2XX_SOC
+	tristate "SoC Audio for the Intel PXA2xx chip"
+	depends on ARCH_PXA
+	select SND_ARM
+	select SND_PXA2XX_LIB
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the PXA2xx AC97, I2S or SSP interface. You will also need
+	  to select the audio interfaces to support below.
+
+config SND_PXA2XX_AC97
+	tristate
+	select SND_AC97_CODEC
+
+config SND_PXA2XX_SOC_AC97
+	tristate
+	select AC97_BUS
+	select SND_ARM
+	select SND_PXA2XX_LIB_AC97
+	select SND_SOC_AC97_BUS
+
+config SND_PXA2XX_SOC_I2S
+	tristate
+
+config SND_PXA_SOC_SSP
+	tristate
+	select PXA_SSP
+
+config SND_PXA2XX_SOC_CORGI
+	tristate "SoC Audio support for Sharp Zaurus SL-C7x0"
+	depends on SND_PXA2XX_SOC && PXA_SHARP_C7xx
+	select SND_PXA2XX_SOC_I2S
+	select SND_SOC_WM8731
+	help
+	  Say Y if you want to add support for SoC audio on Sharp
+	  Zaurus SL-C7x0 models (Corgi, Shepherd, Husky).
+
+config SND_PXA2XX_SOC_SPITZ
+	tristate "SoC Audio support for Sharp Zaurus SL-Cxx00"
+	depends on SND_PXA2XX_SOC && PXA_SHARP_Cxx00
+	select SND_PXA2XX_SOC_I2S
+	select SND_SOC_WM8750
+	help
+	  Say Y if you want to add support for SoC audio on Sharp
+	  Zaurus SL-Cxx00 models (Spitz, Borzoi and Akita).
+
+config SND_PXA2XX_SOC_Z2
+	tristate "SoC Audio support for Zipit Z2"
+	depends on SND_PXA2XX_SOC && MACH_ZIPIT2
+	select SND_PXA2XX_SOC_I2S
+	select SND_SOC_WM8750
+	help
+	  Say Y if you want to add support for SoC audio on Zipit Z2.
+
+config SND_PXA2XX_SOC_POODLE
+	tristate "SoC Audio support for Poodle"
+	depends on SND_PXA2XX_SOC && MACH_POODLE
+	select SND_PXA2XX_SOC_I2S
+	select SND_SOC_WM8731
+	help
+	  Say Y if you want to add support for SoC audio on Sharp
+	  Zaurus SL-5600 model (Poodle).
+
+config SND_PXA2XX_SOC_TOSA
+	tristate "SoC AC97 Audio support for Tosa"
+	depends on SND_PXA2XX_SOC && MACH_TOSA
+	depends on MFD_TC6393XB
+	select SND_PXA2XX_SOC_AC97
+	select SND_SOC_WM9712
+	help
+	  Say Y if you want to add support for SoC audio on Sharp
+	  Zaurus SL-C6000x models (Tosa).
+
+config SND_PXA2XX_SOC_E740
+	tristate "SoC AC97 Audio support for e740"
+	depends on SND_PXA2XX_SOC && MACH_E740
+	select SND_SOC_WM9705
+	select SND_PXA2XX_SOC_AC97
+	help
+	  Say Y if you want to add support for SoC audio on the
+	  toshiba e740 PDA
+
+config SND_PXA2XX_SOC_E750
+	tristate "SoC AC97 Audio support for e750"
+	depends on SND_PXA2XX_SOC && MACH_E750
+	select SND_SOC_WM9705
+	select SND_PXA2XX_SOC_AC97
+	help
+	  Say Y if you want to add support for SoC audio on the
+	  toshiba e750 PDA
+
+config SND_PXA2XX_SOC_E800
+	tristate "SoC AC97 Audio support for e800"
+	depends on SND_PXA2XX_SOC && MACH_E800
+	select SND_SOC_WM9712
+	select SND_PXA2XX_SOC_AC97
+	help
+	  Say Y if you want to add support for SoC audio on the
+	  Toshiba e800 PDA
+
+config SND_PXA2XX_SOC_EM_X270
+	tristate "SoC Audio support for CompuLab EM-x270, eXeda and CM-X300"
+	depends on SND_PXA2XX_SOC && (MACH_EM_X270 || MACH_EXEDA || \
+			MACH_CM_X300)
+	select SND_PXA2XX_SOC_AC97
+	select SND_SOC_WM9712
+	help
+	  Say Y if you want to add support for SoC audio on
+	  CompuLab EM-x270, eXeda and CM-X300 machines.
+
+config SND_PXA2XX_SOC_PALM27X
+	bool "SoC Audio support for Palm T|X, T5, E2 and LifeDrive"
+	depends on SND_PXA2XX_SOC && (MACH_PALMLD || MACH_PALMTX || \
+			MACH_PALMT5 || MACH_PALMTE2)
+	select SND_PXA2XX_SOC_AC97
+	select SND_SOC_WM9712
+	help
+	  Say Y if you want to add support for SoC audio on
+	  Palm T|X, T5, E2 or LifeDrive handheld computer.
+
+config SND_SOC_SAARB
+	tristate "SoC Audio support for Marvell Saarb"
+	depends on SND_PXA2XX_SOC && MACH_SAARB
+	select SND_PXA_SOC_SSP
+	select SND_SOC_88PM860X
+	help
+	  Say Y if you want to add support for SoC audio on the
+	  Marvell Saarb reference platform.
+
+config SND_SOC_TAVOREVB3
+	tristate "SoC Audio support for Marvell Tavor EVB3"
+	depends on SND_PXA2XX_SOC && MACH_TAVOREVB3
+	select SND_PXA_SOC_SSP
+	select SND_SOC_88PM860X
+	help
+	  Say Y if you want to add support for SoC audio on the
+	  Marvell Saarb reference platform.
+
+config SND_SOC_ZYLONITE
+	tristate "SoC Audio support for Marvell Zylonite"
+	depends on SND_PXA2XX_SOC && MACH_ZYLONITE
+	select SND_PXA2XX_SOC_AC97
+	select SND_PXA_SOC_SSP
+	select SND_SOC_WM9713
+	help
+	  Say Y if you want to add support for SoC audio on the
+	  Marvell Zylonite reference platform.
+
+config SND_SOC_RAUMFELD
+	tristate "SoC Audio support Raumfeld audio adapter"
+	depends on SND_PXA2XX_SOC && (MACH_RAUMFELD_SPEAKER || MACH_RAUMFELD_CONNECTOR)
+	select SND_PXA_SOC_SSP
+	select SND_SOC_CS4270
+	select SND_SOC_AK4104
+	help
+	  Say Y if you want to add support for SoC audio on Raumfeld devices
+
+config SND_PXA2XX_SOC_MAGICIAN
+	tristate "SoC Audio support for HTC Magician"
+	depends on SND_PXA2XX_SOC && MACH_MAGICIAN
+	select SND_PXA2XX_SOC_I2S
+	select SND_PXA_SOC_SSP
+	select SND_SOC_UDA1380
+	help
+	  Say Y if you want to add support for SoC audio on the
+	  HTC Magician.
+
+config SND_PXA2XX_SOC_MIOA701
+        tristate "SoC Audio support for MIO A701"
+        depends on SND_PXA2XX_SOC && MACH_MIOA701
+        select SND_PXA2XX_SOC_AC97
+        select SND_SOC_WM9713
+        help
+          Say Y if you want to add support for SoC audio on the
+          MIO A701.
+
+config SND_PXA2XX_SOC_IMOTE2
+       tristate "SoC Audio support for IMote 2"
+       depends on SND_PXA2XX_SOC && MACH_INTELMOTE2 && I2C
+       select SND_PXA2XX_SOC_I2S
+       select SND_SOC_WM8940
+       help
+         Say Y if you want to add support for SoC audio on the
+	 IMote 2.
diff --git a/linux/sound/soc/pxa/Makefile b/linux/sound/soc/pxa/Makefile
new file mode 100644
index 0000000..0766016
--- /dev/null
+++ b/linux/sound/soc/pxa/Makefile
@@ -0,0 +1,47 @@
+# PXA Platform Support
+snd-soc-pxa2xx-objs := pxa2xx-pcm.o
+snd-soc-pxa2xx-ac97-objs := pxa2xx-ac97.o
+snd-soc-pxa2xx-i2s-objs := pxa2xx-i2s.o
+snd-soc-pxa-ssp-objs := pxa-ssp.o
+
+obj-$(CONFIG_SND_PXA2XX_SOC) += snd-soc-pxa2xx.o
+obj-$(CONFIG_SND_PXA2XX_SOC_AC97) += snd-soc-pxa2xx-ac97.o
+obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o
+obj-$(CONFIG_SND_PXA_SOC_SSP) += snd-soc-pxa-ssp.o
+
+# PXA Machine Support
+snd-soc-corgi-objs := corgi.o
+snd-soc-poodle-objs := poodle.o
+snd-soc-tosa-objs := tosa.o
+snd-soc-e740-objs := e740_wm9705.o
+snd-soc-e750-objs := e750_wm9705.o
+snd-soc-e800-objs := e800_wm9712.o
+snd-soc-spitz-objs := spitz.o
+snd-soc-em-x270-objs := em-x270.o
+snd-soc-palm27x-objs := palm27x.o
+snd-soc-saarb-objs := saarb.o
+snd-soc-tavorevb3-objs := tavorevb3.o
+snd-soc-zylonite-objs := zylonite.o
+snd-soc-magician-objs := magician.o
+snd-soc-mioa701-objs := mioa701_wm9713.o
+snd-soc-z2-objs := z2.o
+snd-soc-imote2-objs := imote2.o
+snd-soc-raumfeld-objs := raumfeld.o
+
+obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o
+obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o
+obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o
+obj-$(CONFIG_SND_PXA2XX_SOC_E740) += snd-soc-e740.o
+obj-$(CONFIG_SND_PXA2XX_SOC_E750) += snd-soc-e750.o
+obj-$(CONFIG_SND_PXA2XX_SOC_E800) += snd-soc-e800.o
+obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o
+obj-$(CONFIG_SND_PXA2XX_SOC_EM_X270) += snd-soc-em-x270.o
+obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o
+obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o
+obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o
+obj-$(CONFIG_SND_PXA2XX_SOC_Z2) += snd-soc-z2.o
+obj-$(CONFIG_SND_SOC_SAARB) += snd-soc-saarb.o
+obj-$(CONFIG_SND_SOC_TAVOREVB3) += snd-soc-tavorevb3.o
+obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o
+obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o
+obj-$(CONFIG_SND_SOC_RAUMFELD) += snd-soc-raumfeld.o
diff --git a/linux/sound/soc/pxa/corgi.c b/linux/sound/soc/pxa/corgi.c
new file mode 100644
index 0000000..f451acd
--- /dev/null
+++ b/linux/sound/soc/pxa/corgi.c
@@ -0,0 +1,357 @@
+/*
+ * corgi.c  --  SoC audio for Corgi
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ *          Richard Purdie <richard@openedhand.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/corgi.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm8731.h"
+#include "pxa2xx-i2s.h"
+
+#define CORGI_HP        0
+#define CORGI_MIC       1
+#define CORGI_LINE      2
+#define CORGI_HEADSET   3
+#define CORGI_HP_OFF    4
+#define CORGI_SPK_ON    0
+#define CORGI_SPK_OFF   1
+
+ /* audio clock in Hz - rounded from 12.235MHz */
+#define CORGI_AUDIO_CLOCK 12288000
+
+static int corgi_jack_func;
+static int corgi_spk_func;
+
+static void corgi_ext_control(struct snd_soc_codec *codec)
+{
+	/* set up jack connection */
+	switch (corgi_jack_func) {
+	case CORGI_HP:
+		/* set = unmute headphone */
+		gpio_set_value(CORGI_GPIO_MUTE_L, 1);
+		gpio_set_value(CORGI_GPIO_MUTE_R, 1);
+		snd_soc_dapm_disable_pin(codec, "Mic Jack");
+		snd_soc_dapm_disable_pin(codec, "Line Jack");
+		snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+		snd_soc_dapm_disable_pin(codec, "Headset Jack");
+		break;
+	case CORGI_MIC:
+		/* reset = mute headphone */
+		gpio_set_value(CORGI_GPIO_MUTE_L, 0);
+		gpio_set_value(CORGI_GPIO_MUTE_R, 0);
+		snd_soc_dapm_enable_pin(codec, "Mic Jack");
+		snd_soc_dapm_disable_pin(codec, "Line Jack");
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+		snd_soc_dapm_disable_pin(codec, "Headset Jack");
+		break;
+	case CORGI_LINE:
+		gpio_set_value(CORGI_GPIO_MUTE_L, 0);
+		gpio_set_value(CORGI_GPIO_MUTE_R, 0);
+		snd_soc_dapm_disable_pin(codec, "Mic Jack");
+		snd_soc_dapm_enable_pin(codec, "Line Jack");
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+		snd_soc_dapm_disable_pin(codec, "Headset Jack");
+		break;
+	case CORGI_HEADSET:
+		gpio_set_value(CORGI_GPIO_MUTE_L, 0);
+		gpio_set_value(CORGI_GPIO_MUTE_R, 1);
+		snd_soc_dapm_enable_pin(codec, "Mic Jack");
+		snd_soc_dapm_disable_pin(codec, "Line Jack");
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+		snd_soc_dapm_enable_pin(codec, "Headset Jack");
+		break;
+	}
+
+	if (corgi_spk_func == CORGI_SPK_ON)
+		snd_soc_dapm_enable_pin(codec, "Ext Spk");
+	else
+		snd_soc_dapm_disable_pin(codec, "Ext Spk");
+
+	/* signal a DAPM event */
+	snd_soc_dapm_sync(codec);
+}
+
+static int corgi_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	mutex_lock(&codec->mutex);
+
+	/* check the jack status at stream startup */
+	corgi_ext_control(codec);
+
+	mutex_unlock(&codec->mutex);
+
+	return 0;
+}
+
+/* we need to unmute the HP at shutdown as the mute burns power on corgi */
+static void corgi_shutdown(struct snd_pcm_substream *substream)
+{
+	/* set = unmute headphone */
+	gpio_set_value(CORGI_GPIO_MUTE_L, 1);
+	gpio_set_value(CORGI_GPIO_MUTE_R, 1);
+}
+
+static int corgi_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int clk = 0;
+	int ret = 0;
+
+	switch (params_rate(params)) {
+	case 8000:
+	case 16000:
+	case 48000:
+	case 96000:
+		clk = 12288000;
+		break;
+	case 11025:
+	case 22050:
+	case 44100:
+		clk = 11289600;
+		break;
+	}
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, clk,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set the I2S system clock as input (unused) */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops corgi_ops = {
+	.startup = corgi_startup,
+	.hw_params = corgi_hw_params,
+	.shutdown = corgi_shutdown,
+};
+
+static int corgi_get_jack(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = corgi_jack_func;
+	return 0;
+}
+
+static int corgi_set_jack(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	if (corgi_jack_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	corgi_jack_func = ucontrol->value.integer.value[0];
+	corgi_ext_control(codec);
+	return 1;
+}
+
+static int corgi_get_spk(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = corgi_spk_func;
+	return 0;
+}
+
+static int corgi_set_spk(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+
+	if (corgi_spk_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	corgi_spk_func = ucontrol->value.integer.value[0];
+	corgi_ext_control(codec);
+	return 1;
+}
+
+static int corgi_amp_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *k, int event)
+{
+	gpio_set_value(CORGI_GPIO_APM_ON, SND_SOC_DAPM_EVENT_ON(event));
+	return 0;
+}
+
+static int corgi_mic_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *k, int event)
+{
+	gpio_set_value(CORGI_GPIO_MIC_BIAS, SND_SOC_DAPM_EVENT_ON(event));
+	return 0;
+}
+
+/* corgi machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+SND_SOC_DAPM_HP("Headphone Jack", NULL),
+SND_SOC_DAPM_MIC("Mic Jack", corgi_mic_event),
+SND_SOC_DAPM_SPK("Ext Spk", corgi_amp_event),
+SND_SOC_DAPM_LINE("Line Jack", NULL),
+SND_SOC_DAPM_HP("Headset Jack", NULL),
+};
+
+/* Corgi machine audio map (connections to the codec pins) */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+	/* headset Jack  - in = micin, out = LHPOUT*/
+	{"Headset Jack", NULL, "LHPOUT"},
+
+	/* headphone connected to LHPOUT1, RHPOUT1 */
+	{"Headphone Jack", NULL, "LHPOUT"},
+	{"Headphone Jack", NULL, "RHPOUT"},
+
+	/* speaker connected to LOUT, ROUT */
+	{"Ext Spk", NULL, "ROUT"},
+	{"Ext Spk", NULL, "LOUT"},
+
+	/* mic is connected to MICIN (via right channel of headphone jack) */
+	{"MICIN", NULL, "Mic Jack"},
+
+	/* Same as the above but no mic bias for line signals */
+	{"MICIN", NULL, "Line Jack"},
+};
+
+static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
+	"Off"};
+static const char *spk_function[] = {"On", "Off"};
+static const struct soc_enum corgi_enum[] = {
+	SOC_ENUM_SINGLE_EXT(5, jack_function),
+	SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new wm8731_corgi_controls[] = {
+	SOC_ENUM_EXT("Jack Function", corgi_enum[0], corgi_get_jack,
+		corgi_set_jack),
+	SOC_ENUM_EXT("Speaker Function", corgi_enum[1], corgi_get_spk,
+		corgi_set_spk),
+};
+
+/*
+ * Logic for a wm8731 as connected on a Sharp SL-C7x0 Device
+ */
+static int corgi_wm8731_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	snd_soc_dapm_nc_pin(codec, "LLINEIN");
+	snd_soc_dapm_nc_pin(codec, "RLINEIN");
+
+	/* Add corgi specific controls */
+	err = snd_soc_add_controls(codec, wm8731_corgi_controls,
+				ARRAY_SIZE(wm8731_corgi_controls));
+	if (err < 0)
+		return err;
+
+	/* Add corgi specific widgets */
+	snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets,
+				  ARRAY_SIZE(wm8731_dapm_widgets));
+
+	/* Set up corgi specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_sync(codec);
+	return 0;
+}
+
+/* corgi digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link corgi_dai = {
+	.name = "WM8731",
+	.stream_name = "WM8731",
+	.cpu_dai_name = "pxa-is2-dai",
+	.codec_dai_name = "wm8731-hifi",
+	.platform_name = "pxa-pcm-audio",
+	.codec_name = "wm8731-codec-0.001a",
+	.init = corgi_wm8731_init,
+	.ops = &corgi_ops,
+};
+
+/* corgi audio machine driver */
+static struct snd_soc_card snd_soc_corgi = {
+	.name = "Corgi",
+	.dai_link = &corgi_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *corgi_snd_device;
+
+static int __init corgi_init(void)
+{
+	int ret;
+
+	if (!(machine_is_corgi() || machine_is_shepherd() ||
+	      machine_is_husky()))
+		return -ENODEV;
+
+	corgi_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!corgi_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(corgi_snd_device, &snd_soc_corgi);
+	ret = platform_device_add(corgi_snd_device);
+
+	if (ret)
+		platform_device_put(corgi_snd_device);
+
+	return ret;
+}
+
+static void __exit corgi_exit(void)
+{
+	platform_device_unregister(corgi_snd_device);
+}
+
+module_init(corgi_init);
+module_exit(corgi_exit);
+
+/* Module information */
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Corgi");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/e740_wm9705.c b/linux/sound/soc/pxa/e740_wm9705.c
new file mode 100644
index 0000000..c82cedb
--- /dev/null
+++ b/linux/sound/soc/pxa/e740_wm9705.c
@@ -0,0 +1,212 @@
+/*
+ * e740-wm9705.c  --  SoC audio for e740
+ *
+ * Copyright 2007 (c) Ian Molton <spyro@f2s.com>
+ *
+ *  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; version 2 ONLY.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <mach/audio.h>
+#include <mach/eseries-gpio.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/wm9705.h"
+#include "pxa2xx-ac97.h"
+
+
+#define E740_AUDIO_OUT 1
+#define E740_AUDIO_IN  2
+
+static int e740_audio_power;
+
+static void e740_sync_audio_power(int status)
+{
+	gpio_set_value(GPIO_E740_WM9705_nAVDD2, !status);
+	gpio_set_value(GPIO_E740_AMP_ON, (status & E740_AUDIO_OUT) ? 1 : 0);
+	gpio_set_value(GPIO_E740_MIC_ON, (status & E740_AUDIO_IN) ? 1 : 0);
+}
+
+static int e740_mic_amp_event(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *kcontrol, int event)
+{
+	if (event & SND_SOC_DAPM_PRE_PMU)
+		e740_audio_power |= E740_AUDIO_IN;
+	else if (event & SND_SOC_DAPM_POST_PMD)
+		e740_audio_power &= ~E740_AUDIO_IN;
+
+	e740_sync_audio_power(e740_audio_power);
+
+	return 0;
+}
+
+static int e740_output_amp_event(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *kcontrol, int event)
+{
+	if (event & SND_SOC_DAPM_PRE_PMU)
+		e740_audio_power |= E740_AUDIO_OUT;
+	else if (event & SND_SOC_DAPM_POST_PMD)
+		e740_audio_power &= ~E740_AUDIO_OUT;
+
+	e740_sync_audio_power(e740_audio_power);
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget e740_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_SPK("Speaker", NULL),
+	SND_SOC_DAPM_MIC("Mic (Internal)", NULL),
+	SND_SOC_DAPM_PGA_E("Output Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+			e740_output_amp_event, SND_SOC_DAPM_PRE_PMU |
+			SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_PGA_E("Mic Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+			e740_mic_amp_event, SND_SOC_DAPM_PRE_PMU |
+			SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"Output Amp", NULL, "LOUT"},
+	{"Output Amp", NULL, "ROUT"},
+	{"Output Amp", NULL, "MONOOUT"},
+
+	{"Speaker", NULL, "Output Amp"},
+	{"Headphone Jack", NULL, "Output Amp"},
+
+	{"MIC1", NULL, "Mic Amp"},
+	{"Mic Amp", NULL, "Mic (Internal)"},
+};
+
+static int e740_ac97_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	snd_soc_dapm_nc_pin(codec, "HPOUTL");
+	snd_soc_dapm_nc_pin(codec, "HPOUTR");
+	snd_soc_dapm_nc_pin(codec, "PHONE");
+	snd_soc_dapm_nc_pin(codec, "LINEINL");
+	snd_soc_dapm_nc_pin(codec, "LINEINR");
+	snd_soc_dapm_nc_pin(codec, "CDINL");
+	snd_soc_dapm_nc_pin(codec, "CDINR");
+	snd_soc_dapm_nc_pin(codec, "PCBEEP");
+	snd_soc_dapm_nc_pin(codec, "MIC2");
+
+	snd_soc_dapm_new_controls(codec, e740_dapm_widgets,
+					ARRAY_SIZE(e740_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_dai_link e740_dai[] = {
+	{
+		.name = "AC97",
+		.stream_name = "AC97 HiFi",
+		.cpu_dai_name = "pxa-ac97.0",
+		.codec_dai_name = "wm9705-hifi",
+		.platform_name = "pxa-pcm-audio",
+		.codec_name = "wm9705-codec",
+		.init = e740_ac97_init,
+	},
+	{
+		.name = "AC97 Aux",
+		.stream_name = "AC97 Aux",
+		.cpu_dai_name = "pxa-ac97.1",
+		.codec_dai_name = "wm9705-aux",
+		.platform_name = "pxa-pcm-audio",
+		.codec_name = "wm9705-codec",
+	},
+};
+
+static struct snd_soc_card e740 = {
+	.name = "Toshiba e740",
+	.dai_link = e740_dai,
+	.num_links = ARRAY_SIZE(e740_dai),
+};
+
+static struct platform_device *e740_snd_device;
+
+static int __init e740_init(void)
+{
+	int ret;
+
+	if (!machine_is_e740())
+		return -ENODEV;
+
+	ret = gpio_request(GPIO_E740_MIC_ON,  "Mic amp");
+	if (ret)
+		return ret;
+
+	ret = gpio_request(GPIO_E740_AMP_ON, "Output amp");
+	if (ret)
+		goto free_mic_amp_gpio;
+
+	ret = gpio_request(GPIO_E740_WM9705_nAVDD2, "Audio power");
+	if (ret)
+		goto free_op_amp_gpio;
+
+	/* Disable audio */
+	ret = gpio_direction_output(GPIO_E740_MIC_ON, 0);
+	if (ret)
+		goto free_apwr_gpio;
+	ret = gpio_direction_output(GPIO_E740_AMP_ON, 0);
+	if (ret)
+		goto free_apwr_gpio;
+	ret = gpio_direction_output(GPIO_E740_WM9705_nAVDD2, 1);
+	if (ret)
+		goto free_apwr_gpio;
+
+	e740_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!e740_snd_device) {
+		ret = -ENOMEM;
+		goto free_apwr_gpio;
+	}
+
+	platform_set_drvdata(e740_snd_device, &e740);
+	ret = platform_device_add(e740_snd_device);
+
+	if (!ret)
+		return 0;
+
+/* Fail gracefully */
+	platform_device_put(e740_snd_device);
+free_apwr_gpio:
+	gpio_free(GPIO_E740_WM9705_nAVDD2);
+free_op_amp_gpio:
+	gpio_free(GPIO_E740_AMP_ON);
+free_mic_amp_gpio:
+	gpio_free(GPIO_E740_MIC_ON);
+
+	return ret;
+}
+
+static void __exit e740_exit(void)
+{
+	platform_device_unregister(e740_snd_device);
+	gpio_free(GPIO_E740_WM9705_nAVDD2);
+	gpio_free(GPIO_E740_AMP_ON);
+	gpio_free(GPIO_E740_MIC_ON);
+}
+
+module_init(e740_init);
+module_exit(e740_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ian Molton <spyro@f2s.com>");
+MODULE_DESCRIPTION("ALSA SoC driver for e740");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/pxa/e750_wm9705.c b/linux/sound/soc/pxa/e750_wm9705.c
new file mode 100644
index 0000000..4c14380
--- /dev/null
+++ b/linux/sound/soc/pxa/e750_wm9705.c
@@ -0,0 +1,185 @@
+/*
+ * e750-wm9705.c  --  SoC audio for e750
+ *
+ * Copyright 2007 (c) Ian Molton <spyro@f2s.com>
+ *
+ *  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; version 2 ONLY.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <mach/audio.h>
+#include <mach/eseries-gpio.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/wm9705.h"
+#include "pxa2xx-ac97.h"
+
+static int e750_spk_amp_event(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *kcontrol, int event)
+{
+	if (event & SND_SOC_DAPM_PRE_PMU)
+		gpio_set_value(GPIO_E750_SPK_AMP_OFF, 0);
+	else if (event & SND_SOC_DAPM_POST_PMD)
+		gpio_set_value(GPIO_E750_SPK_AMP_OFF, 1);
+
+	return 0;
+}
+
+static int e750_hp_amp_event(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *kcontrol, int event)
+{
+	if (event & SND_SOC_DAPM_PRE_PMU)
+		gpio_set_value(GPIO_E750_HP_AMP_OFF, 0);
+	else if (event & SND_SOC_DAPM_POST_PMD)
+		gpio_set_value(GPIO_E750_HP_AMP_OFF, 1);
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget e750_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_SPK("Speaker", NULL),
+	SND_SOC_DAPM_MIC("Mic (Internal)", NULL),
+	SND_SOC_DAPM_PGA_E("Headphone Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+			e750_hp_amp_event, SND_SOC_DAPM_PRE_PMU |
+			SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_PGA_E("Speaker Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+			e750_spk_amp_event, SND_SOC_DAPM_PRE_PMU |
+			SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"Headphone Amp", NULL, "HPOUTL"},
+	{"Headphone Amp", NULL, "HPOUTR"},
+	{"Headphone Jack", NULL, "Headphone Amp"},
+
+	{"Speaker Amp", NULL, "MONOOUT"},
+	{"Speaker", NULL, "Speaker Amp"},
+
+	{"MIC1", NULL, "Mic (Internal)"},
+};
+
+static int e750_ac97_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	snd_soc_dapm_nc_pin(codec, "LOUT");
+	snd_soc_dapm_nc_pin(codec, "ROUT");
+	snd_soc_dapm_nc_pin(codec, "PHONE");
+	snd_soc_dapm_nc_pin(codec, "LINEINL");
+	snd_soc_dapm_nc_pin(codec, "LINEINR");
+	snd_soc_dapm_nc_pin(codec, "CDINL");
+	snd_soc_dapm_nc_pin(codec, "CDINR");
+	snd_soc_dapm_nc_pin(codec, "PCBEEP");
+	snd_soc_dapm_nc_pin(codec, "MIC2");
+
+	snd_soc_dapm_new_controls(codec, e750_dapm_widgets,
+					ARRAY_SIZE(e750_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_dai_link e750_dai[] = {
+	{
+		.name = "AC97",
+		.stream_name = "AC97 HiFi",
+		.cpu_dai_name = "pxa-ac97.0",
+		.codec_dai_name = "wm9705-hifi",
+		.platform_name = "pxa-pcm-audio",
+		.codec_name = "wm9705-codec",
+		.init = e750_ac97_init,
+		/* use ops to check startup state */
+	},
+	{
+		.name = "AC97 Aux",
+		.stream_name = "AC97 Aux",
+		.cpu_dai_name = "pxa-ac97.1",
+		.codec_dai_name ="wm9705-aux",
+		.platform_name = "pxa-pcm-audio",
+		.codec_name = "wm9705-codec",
+	},
+};
+
+static struct snd_soc_card e750 = {
+	.name = "Toshiba e750",
+	.dai_link = e750_dai,
+	.num_links = ARRAY_SIZE(e750_dai),
+};
+
+static struct platform_device *e750_snd_device;
+
+static int __init e750_init(void)
+{
+	int ret;
+
+	if (!machine_is_e750())
+		return -ENODEV;
+
+	ret = gpio_request(GPIO_E750_HP_AMP_OFF,  "Headphone amp");
+	if (ret)
+		return ret;
+
+	ret = gpio_request(GPIO_E750_SPK_AMP_OFF, "Speaker amp");
+	if (ret)
+		goto free_hp_amp_gpio;
+
+	ret = gpio_direction_output(GPIO_E750_HP_AMP_OFF, 1);
+	if (ret)
+		goto free_spk_amp_gpio;
+
+	ret = gpio_direction_output(GPIO_E750_SPK_AMP_OFF, 1);
+	if (ret)
+		goto free_spk_amp_gpio;
+
+	e750_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!e750_snd_device) {
+		ret = -ENOMEM;
+		goto free_spk_amp_gpio;
+	}
+
+	platform_set_drvdata(e750_snd_device, &e750);
+	ret = platform_device_add(e750_snd_device);
+
+	if (!ret)
+		return 0;
+
+/* Fail gracefully */
+	platform_device_put(e750_snd_device);
+free_spk_amp_gpio:
+	gpio_free(GPIO_E750_SPK_AMP_OFF);
+free_hp_amp_gpio:
+	gpio_free(GPIO_E750_HP_AMP_OFF);
+
+	return ret;
+}
+
+static void __exit e750_exit(void)
+{
+	platform_device_unregister(e750_snd_device);
+	gpio_free(GPIO_E750_SPK_AMP_OFF);
+	gpio_free(GPIO_E750_HP_AMP_OFF);
+}
+
+module_init(e750_init);
+module_exit(e750_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ian Molton <spyro@f2s.com>");
+MODULE_DESCRIPTION("ALSA SoC driver for e750");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/pxa/e800_wm9712.c b/linux/sound/soc/pxa/e800_wm9712.c
new file mode 100644
index 0000000..d42e5fe
--- /dev/null
+++ b/linux/sound/soc/pxa/e800_wm9712.c
@@ -0,0 +1,172 @@
+/*
+ * e800-wm9712.c  --  SoC audio for e800
+ *
+ * Copyright 2007 (c) Ian Molton <spyro@f2s.com>
+ *
+ *  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; version 2 ONLY.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/audio.h>
+#include <mach/eseries-gpio.h>
+
+#include "../codecs/wm9712.h"
+#include "pxa2xx-ac97.h"
+
+static int e800_spk_amp_event(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *kcontrol, int event)
+{
+	if (event & SND_SOC_DAPM_PRE_PMU)
+		gpio_set_value(GPIO_E800_SPK_AMP_ON, 1);
+	else if (event & SND_SOC_DAPM_POST_PMD)
+		gpio_set_value(GPIO_E800_SPK_AMP_ON, 0);
+
+	return 0;
+}
+
+static int e800_hp_amp_event(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *kcontrol, int event)
+{
+	if (event & SND_SOC_DAPM_PRE_PMU)
+		gpio_set_value(GPIO_E800_HP_AMP_OFF, 0);
+	else if (event & SND_SOC_DAPM_POST_PMD)
+		gpio_set_value(GPIO_E800_HP_AMP_OFF, 1);
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget e800_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_MIC("Mic (Internal1)", NULL),
+	SND_SOC_DAPM_MIC("Mic (Internal2)", NULL),
+	SND_SOC_DAPM_SPK("Speaker", NULL),
+	SND_SOC_DAPM_PGA_E("Headphone Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+			e800_hp_amp_event, SND_SOC_DAPM_PRE_PMU |
+			SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_PGA_E("Speaker Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
+			e800_spk_amp_event, SND_SOC_DAPM_PRE_PMU |
+			SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"Headphone Jack", NULL, "HPOUTL"},
+	{"Headphone Jack", NULL, "HPOUTR"},
+	{"Headphone Jack", NULL, "Headphone Amp"},
+
+	{"Speaker Amp", NULL, "MONOOUT"},
+	{"Speaker", NULL, "Speaker Amp"},
+
+	{"MIC1", NULL, "Mic (Internal1)"},
+	{"MIC2", NULL, "Mic (Internal2)"},
+};
+
+static int e800_ac97_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	snd_soc_dapm_new_controls(codec, e800_dapm_widgets,
+					ARRAY_SIZE(e800_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_dai_link e800_dai[] = {
+	{
+		.name = "AC97",
+		.stream_name = "AC97 HiFi",
+		.cpu_dai_name = "pxa-ac97.0",
+		.codec_dai_name = "wm9712-hifi",
+		.platform_name = "pxa-pcm-audio",
+		.codec_name = "wm9712-codec",
+		.init = e800_ac97_init,
+	},
+	{
+		.name = "AC97 Aux",
+		.stream_name = "AC97 Aux",
+		.cpu_dai_name = "pxa-ac97.1",
+		.codec_dai_name ="wm9712-aux",
+		.platform_name = "pxa-pcm-audio",
+		.codec_name = "wm9712-codec",
+	},
+};
+
+static struct snd_soc_card e800 = {
+	.name = "Toshiba e800",
+	.dai_link = e800_dai,
+	.num_links = ARRAY_SIZE(e800_dai),
+};
+
+static struct platform_device *e800_snd_device;
+
+static int __init e800_init(void)
+{
+	int ret;
+
+	if (!machine_is_e800())
+		return -ENODEV;
+
+	ret = gpio_request(GPIO_E800_HP_AMP_OFF,  "Headphone amp");
+	if (ret)
+		return ret;
+
+	ret = gpio_request(GPIO_E800_SPK_AMP_ON, "Speaker amp");
+	if (ret)
+		goto free_hp_amp_gpio;
+
+	ret = gpio_direction_output(GPIO_E800_HP_AMP_OFF, 1);
+	if (ret)
+		goto free_spk_amp_gpio;
+
+	ret = gpio_direction_output(GPIO_E800_SPK_AMP_ON, 1);
+	if (ret)
+		goto free_spk_amp_gpio;
+
+	e800_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!e800_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(e800_snd_device, &e800);
+	ret = platform_device_add(e800_snd_device);
+
+	if (!ret)
+		return 0;
+
+/* Fail gracefully */
+	platform_device_put(e800_snd_device);
+free_spk_amp_gpio:
+	gpio_free(GPIO_E800_SPK_AMP_ON);
+free_hp_amp_gpio:
+	gpio_free(GPIO_E800_HP_AMP_OFF);
+
+	return ret;
+}
+
+static void __exit e800_exit(void)
+{
+	platform_device_unregister(e800_snd_device);
+	gpio_free(GPIO_E800_SPK_AMP_ON);
+	gpio_free(GPIO_E800_HP_AMP_OFF);
+}
+
+module_init(e800_init);
+module_exit(e800_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ian Molton <spyro@f2s.com>");
+MODULE_DESCRIPTION("ALSA SoC driver for e800");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/pxa/em-x270.c b/linux/sound/soc/pxa/em-x270.c
new file mode 100644
index 0000000..eadf9d3
--- /dev/null
+++ b/linux/sound/soc/pxa/em-x270.c
@@ -0,0 +1,96 @@
+/*
+ * SoC audio driver for EM-X270, eXeda and CM-X300
+ *
+ * Copyright 2007, 2009 CompuLab, Ltd.
+ *
+ * Author: Mike Rapoport <mike@compulab.co.il>
+ *
+ * Copied from tosa.c:
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ *          Richard Purdie <richard@openedhand.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm9712.h"
+#include "pxa2xx-ac97.h"
+
+static struct snd_soc_dai_link em_x270_dai[] = {
+	{
+		.name = "AC97",
+		.stream_name = "AC97 HiFi",
+		.cpu_dai_name = "pxa-ac97.0",
+		.codec_dai_name = "wm9712-hifi",
+		.platform_name = "pxa-pcm-audio",
+		.codec_name = "wm9712-codec",
+	},
+	{
+		.name = "AC97 Aux",
+		.stream_name = "AC97 Aux",
+		.cpu_dai_name = "pxa-ac97.1",
+		.codec_dai_name ="wm9712-aux",
+		.platform_name = "pxa-pcm-audio",
+		.codec_name = "wm9712-codec",
+	},
+};
+
+static struct snd_soc_card em_x270 = {
+	.name = "EM-X270",
+	.dai_link = em_x270_dai,
+	.num_links = ARRAY_SIZE(em_x270_dai),
+};
+
+static struct platform_device *em_x270_snd_device;
+
+static int __init em_x270_init(void)
+{
+	int ret;
+
+	if (!(machine_is_em_x270() || machine_is_exeda()
+	      || machine_is_cm_x300()))
+		return -ENODEV;
+
+	em_x270_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!em_x270_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(em_x270_snd_device, &em_x270);
+	ret = platform_device_add(em_x270_snd_device);
+
+	if (ret)
+		platform_device_put(em_x270_snd_device);
+
+	return ret;
+}
+
+static void __exit em_x270_exit(void)
+{
+	platform_device_unregister(em_x270_snd_device);
+}
+
+module_init(em_x270_init);
+module_exit(em_x270_exit);
+
+/* Module information */
+MODULE_AUTHOR("Mike Rapoport");
+MODULE_DESCRIPTION("ALSA SoC EM-X270, eXeda and CM-X300");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/imote2.c b/linux/sound/soc/pxa/imote2.c
new file mode 100644
index 0000000..154fc6f
--- /dev/null
+++ b/linux/sound/soc/pxa/imote2.c
@@ -0,0 +1,108 @@
+
+#include <linux/module.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/wm8940.h"
+#include "pxa2xx-i2s.h"
+
+static int imote2_asoc_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int clk = 0;
+	int ret;
+
+	switch (params_rate(params)) {
+	case 8000:
+	case 16000:
+	case 48000:
+	case 96000:
+		clk = 12288000;
+		break;
+	case 11025:
+	case 22050:
+	case 44100:
+		clk = 11289600;
+		break;
+	}
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+				  | SND_SOC_DAIFMT_NB_NF
+				  | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* CPU should be clock master */
+	ret = snd_soc_dai_set_fmt(cpu_dai,  SND_SOC_DAIFMT_I2S
+				  | SND_SOC_DAIFMT_NB_NF
+				  | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
+				     SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set the I2S system clock as input (unused) */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, clk,
+		SND_SOC_CLOCK_OUT);
+
+	return ret;
+}
+
+static struct snd_soc_ops imote2_asoc_ops = {
+	.hw_params = imote2_asoc_hw_params,
+};
+
+static struct snd_soc_dai_link imote2_dai = {
+	.name = "WM8940",
+	.stream_name = "WM8940",
+	.cpu_dai_name = "pxa2xx-i2s",
+	.codec_dai_name = "wm8940-hifi",
+	.platform_name = "pxa-pcm-audio",
+	.codec_name = "wm8940-codec.0-0034",
+	.ops = &imote2_asoc_ops,
+};
+
+static struct snd_soc_card snd_soc_imote2 = {
+	.name = "Imote2",
+	.dai_link = &imote2_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *imote2_snd_device;
+
+static int __init imote2_asoc_init(void)
+{
+	int ret;
+
+	if (!machine_is_intelmote2())
+		return -ENODEV;
+	imote2_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!imote2_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(imote2_snd_device, &snd_soc_imote2);
+	ret = platform_device_add(imote2_snd_device);
+	if (ret)
+		platform_device_put(imote2_snd_device);
+
+	return ret;
+}
+module_init(imote2_asoc_init);
+
+static void __exit imote2_asoc_exit(void)
+{
+	platform_device_unregister(imote2_snd_device);
+}
+module_exit(imote2_asoc_exit);
+
+MODULE_AUTHOR("Jonathan Cameron");
+MODULE_DESCRIPTION("ALSA SoC Imote 2");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/magician.c b/linux/sound/soc/pxa/magician.c
new file mode 100644
index 0000000..5ef0526
--- /dev/null
+++ b/linux/sound/soc/pxa/magician.c
@@ -0,0 +1,562 @@
+/*
+ * SoC audio for HTC Magician
+ *
+ * Copyright (c) 2006 Philipp Zabel <philipp.zabel@gmail.com>
+ *
+ * based on spitz.c,
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ *          Richard Purdie <richard@openedhand.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/uda1380.h>
+
+#include <mach/magician.h>
+#include <asm/mach-types.h>
+#include "../codecs/uda1380.h"
+#include "pxa2xx-i2s.h"
+#include "pxa-ssp.h"
+
+#define MAGICIAN_MIC       0
+#define MAGICIAN_MIC_EXT   1
+
+static int magician_hp_switch;
+static int magician_spk_switch = 1;
+static int magician_in_sel = MAGICIAN_MIC;
+
+static void magician_ext_control(struct snd_soc_codec *codec)
+{
+	if (magician_spk_switch)
+		snd_soc_dapm_enable_pin(codec, "Speaker");
+	else
+		snd_soc_dapm_disable_pin(codec, "Speaker");
+	if (magician_hp_switch)
+		snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+	else
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+
+	switch (magician_in_sel) {
+	case MAGICIAN_MIC:
+		snd_soc_dapm_disable_pin(codec, "Headset Mic");
+		snd_soc_dapm_enable_pin(codec, "Call Mic");
+		break;
+	case MAGICIAN_MIC_EXT:
+		snd_soc_dapm_disable_pin(codec, "Call Mic");
+		snd_soc_dapm_enable_pin(codec, "Headset Mic");
+		break;
+	}
+
+	snd_soc_dapm_sync(codec);
+}
+
+static int magician_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	mutex_lock(&codec->mutex);
+
+	/* check the jack status at stream startup */
+	magician_ext_control(codec);
+
+	mutex_unlock(&codec->mutex);
+
+	return 0;
+}
+
+/*
+ * Magician uses SSP port for playback.
+ */
+static int magician_playback_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int acps, acds, width, rate;
+	unsigned int div4 = PXA_SSP_CLK_SCDB_4;
+	int ret = 0;
+
+	rate = params_rate(params);
+	width = snd_pcm_format_physical_width(params_format(params));
+
+	/*
+	 * rate = SSPSCLK / (2 * width(16 or 32))
+	 * SSPSCLK = (ACPS / ACDS) / SSPSCLKDIV(div4 or div1)
+	 */
+	switch (params_rate(params)) {
+	case 8000:
+		/* off by a factor of 2: bug in the PXA27x audio clock? */
+		acps = 32842000;
+		switch (width) {
+		case 16:
+			/* 513156 Hz ~= _2_ * 8000 Hz * 32 (+0.23%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_16;
+			break;
+		default: /* 32 */
+			/* 1026312 Hz ~= _2_ * 8000 Hz * 64 (+0.23%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_8;
+		}
+		break;
+	case 11025:
+		acps = 5622000;
+		switch (width) {
+		case 16:
+			/* 351375 Hz ~= 11025 Hz * 32 (-0.41%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_4;
+			break;
+		default: /* 32 */
+			/* 702750 Hz ~= 11025 Hz * 64 (-0.41%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_2;
+		}
+		break;
+	case 22050:
+		acps = 5622000;
+		switch (width) {
+		case 16:
+			/* 702750 Hz ~= 22050 Hz * 32 (-0.41%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_2;
+			break;
+		default: /* 32 */
+			/* 1405500 Hz ~= 22050 Hz * 64 (-0.41%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_1;
+		}
+		break;
+	case 44100:
+		acps = 5622000;
+		switch (width) {
+		case 16:
+			/* 1405500 Hz ~= 44100 Hz * 32 (-0.41%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_2;
+			break;
+		default: /* 32 */
+			/* 2811000 Hz ~= 44100 Hz * 64 (-0.41%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_1;
+		}
+		break;
+	case 48000:
+		acps = 12235000;
+		switch (width) {
+		case 16:
+			/* 1529375 Hz ~= 48000 Hz * 32 (-0.44%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_2;
+			break;
+		default: /* 32 */
+			/* 3058750 Hz ~= 48000 Hz * 64 (-0.44%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_1;
+		}
+		break;
+	case 96000:
+	default:
+		acps = 12235000;
+		switch (width) {
+		case 16:
+			/* 3058750 Hz ~= 96000 Hz * 32 (-0.44%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_1;
+			break;
+		default: /* 32 */
+			/* 6117500 Hz ~= 96000 Hz * 64 (-0.44%) */
+			acds = PXA_SSP_CLK_AUDIO_DIV_2;
+			div4 = PXA_SSP_CLK_SCDB_1;
+			break;
+		}
+		break;
+	}
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_MSB |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
+			SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_tdm_slot(cpu_dai, 1, 0, 1, width);
+	if (ret < 0)
+		return ret;
+
+	/* set audio clock as clock source */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_AUDIO, 0,
+			SND_SOC_CLOCK_OUT);
+	if (ret < 0)
+		return ret;
+
+	/* set the SSP audio system clock ACDS divider */
+	ret = snd_soc_dai_set_clkdiv(cpu_dai,
+			PXA_SSP_AUDIO_DIV_ACDS, acds);
+	if (ret < 0)
+		return ret;
+
+	/* set the SSP audio system clock SCDB divider4 */
+	ret = snd_soc_dai_set_clkdiv(cpu_dai,
+			PXA_SSP_AUDIO_DIV_SCDB, div4);
+	if (ret < 0)
+		return ret;
+
+	/* set SSP audio pll clock */
+	ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, acps);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * Magician uses I2S for capture.
+ */
+static int magician_capture_hw_params(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret = 0;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+			SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+			SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set the I2S system clock as output */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+			SND_SOC_CLOCK_OUT);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops magician_capture_ops = {
+	.startup = magician_startup,
+	.hw_params = magician_capture_hw_params,
+};
+
+static struct snd_soc_ops magician_playback_ops = {
+	.startup = magician_startup,
+	.hw_params = magician_playback_hw_params,
+};
+
+static int magician_get_hp(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = magician_hp_switch;
+	return 0;
+}
+
+static int magician_set_hp(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	if (magician_hp_switch == ucontrol->value.integer.value[0])
+		return 0;
+
+	magician_hp_switch = ucontrol->value.integer.value[0];
+	magician_ext_control(codec);
+	return 1;
+}
+
+static int magician_get_spk(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = magician_spk_switch;
+	return 0;
+}
+
+static int magician_set_spk(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	if (magician_spk_switch == ucontrol->value.integer.value[0])
+		return 0;
+
+	magician_spk_switch = ucontrol->value.integer.value[0];
+	magician_ext_control(codec);
+	return 1;
+}
+
+static int magician_get_input(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = magician_in_sel;
+	return 0;
+}
+
+static int magician_set_input(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	if (magician_in_sel == ucontrol->value.integer.value[0])
+		return 0;
+
+	magician_in_sel = ucontrol->value.integer.value[0];
+
+	switch (magician_in_sel) {
+	case MAGICIAN_MIC:
+		gpio_set_value(EGPIO_MAGICIAN_IN_SEL1, 1);
+		break;
+	case MAGICIAN_MIC_EXT:
+		gpio_set_value(EGPIO_MAGICIAN_IN_SEL1, 0);
+	}
+
+	return 1;
+}
+
+static int magician_spk_power(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *k, int event)
+{
+	gpio_set_value(EGPIO_MAGICIAN_SPK_POWER, SND_SOC_DAPM_EVENT_ON(event));
+	return 0;
+}
+
+static int magician_hp_power(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *k, int event)
+{
+	gpio_set_value(EGPIO_MAGICIAN_EP_POWER, SND_SOC_DAPM_EVENT_ON(event));
+	return 0;
+}
+
+static int magician_mic_bias(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *k, int event)
+{
+	gpio_set_value(EGPIO_MAGICIAN_MIC_POWER, SND_SOC_DAPM_EVENT_ON(event));
+	return 0;
+}
+
+/* magician machine dapm widgets */
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", magician_hp_power),
+	SND_SOC_DAPM_SPK("Speaker", magician_spk_power),
+	SND_SOC_DAPM_MIC("Call Mic", magician_mic_bias),
+	SND_SOC_DAPM_MIC("Headset Mic", magician_mic_bias),
+};
+
+/* magician machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+	/* Headphone connected to VOUTL, VOUTR */
+	{"Headphone Jack", NULL, "VOUTL"},
+	{"Headphone Jack", NULL, "VOUTR"},
+
+	/* Speaker connected to VOUTL, VOUTR */
+	{"Speaker", NULL, "VOUTL"},
+	{"Speaker", NULL, "VOUTR"},
+
+	/* Mics are connected to VINM */
+	{"VINM", NULL, "Headset Mic"},
+	{"VINM", NULL, "Call Mic"},
+};
+
+static const char *input_select[] = {"Call Mic", "Headset Mic"};
+static const struct soc_enum magician_in_sel_enum =
+	SOC_ENUM_SINGLE_EXT(2, input_select);
+
+static const struct snd_kcontrol_new uda1380_magician_controls[] = {
+	SOC_SINGLE_BOOL_EXT("Headphone Switch",
+			(unsigned long)&magician_hp_switch,
+			magician_get_hp, magician_set_hp),
+	SOC_SINGLE_BOOL_EXT("Speaker Switch",
+			(unsigned long)&magician_spk_switch,
+			magician_get_spk, magician_set_spk),
+	SOC_ENUM_EXT("Input Select", magician_in_sel_enum,
+			magician_get_input, magician_set_input),
+};
+
+/*
+ * Logic for a uda1380 as connected on a HTC Magician
+ */
+static int magician_uda1380_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	/* NC codec pins */
+	snd_soc_dapm_nc_pin(codec, "VOUTLHP");
+	snd_soc_dapm_nc_pin(codec, "VOUTRHP");
+
+	/* FIXME: is anything connected here? */
+	snd_soc_dapm_nc_pin(codec, "VINL");
+	snd_soc_dapm_nc_pin(codec, "VINR");
+
+	/* Add magician specific controls */
+	err = snd_soc_add_controls(codec, uda1380_magician_controls,
+				ARRAY_SIZE(uda1380_magician_controls));
+	if (err < 0)
+		return err;
+
+	/* Add magician specific widgets */
+	snd_soc_dapm_new_controls(codec, uda1380_dapm_widgets,
+				  ARRAY_SIZE(uda1380_dapm_widgets));
+
+	/* Set up magician specific audio path interconnects */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_sync(codec);
+	return 0;
+}
+
+/* magician digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link magician_dai[] = {
+{
+	.name = "uda1380",
+	.stream_name = "UDA1380 Playback",
+	.cpu_dai_name = "pxa-ssp-dai.0",
+	.codec_dai_name = "uda1380-hifi-playback",
+	.platform_name = "pxa-pcm-audio",
+	.codec_name = "uda1380-codec.0-0018",
+	.init = magician_uda1380_init,
+	.ops = &magician_playback_ops,
+},
+{
+	.name = "uda1380",
+	.stream_name = "UDA1380 Capture",
+	.cpu_dai_name = "pxa2xx-i2s",
+	.codec_dai_name = "uda1380-hifi-capture",
+	.platform_name = "pxa-pcm-audio",
+	.codec_name = "uda1380-codec.0-0018",
+	.ops = &magician_capture_ops,
+}
+};
+
+/* magician audio machine driver */
+static struct snd_soc_card snd_soc_card_magician = {
+	.name = "Magician",
+	.dai_link = magician_dai,
+	.num_links = ARRAY_SIZE(magician_dai),
+
+};
+
+static struct platform_device *magician_snd_device;
+
+/*
+ * FIXME: move into magician board file once merged into the pxa tree
+ */
+static struct uda1380_platform_data uda1380_info = {
+	.gpio_power = EGPIO_MAGICIAN_CODEC_POWER,
+	.gpio_reset = EGPIO_MAGICIAN_CODEC_RESET,
+	.dac_clk    = UDA1380_DAC_CLK_WSPLL,
+};
+
+static struct i2c_board_info i2c_board_info[] = {
+	{
+		I2C_BOARD_INFO("uda1380", 0x18),
+		.platform_data = &uda1380_info,
+	},
+};
+
+static int __init magician_init(void)
+{
+	int ret;
+	struct i2c_adapter *adapter;
+	struct i2c_client *client;
+
+	if (!machine_is_magician())
+		return -ENODEV;
+
+	adapter = i2c_get_adapter(0);
+	if (!adapter)
+		return -ENODEV;
+	client = i2c_new_device(adapter, i2c_board_info);
+	i2c_put_adapter(adapter);
+	if (!client)
+		return -ENODEV;
+
+	ret = gpio_request(EGPIO_MAGICIAN_SPK_POWER, "SPK_POWER");
+	if (ret)
+		goto err_request_spk;
+	ret = gpio_request(EGPIO_MAGICIAN_EP_POWER, "EP_POWER");
+	if (ret)
+		goto err_request_ep;
+	ret = gpio_request(EGPIO_MAGICIAN_MIC_POWER, "MIC_POWER");
+	if (ret)
+		goto err_request_mic;
+	ret = gpio_request(EGPIO_MAGICIAN_IN_SEL0, "IN_SEL0");
+	if (ret)
+		goto err_request_in_sel0;
+	ret = gpio_request(EGPIO_MAGICIAN_IN_SEL1, "IN_SEL1");
+	if (ret)
+		goto err_request_in_sel1;
+
+	gpio_set_value(EGPIO_MAGICIAN_IN_SEL0, 0);
+
+	magician_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!magician_snd_device) {
+		ret = -ENOMEM;
+		goto err_pdev;
+	}
+
+	platform_set_drvdata(magician_snd_device, &snd_soc_card_magician);
+	ret = platform_device_add(magician_snd_device);
+	if (ret) {
+		platform_device_put(magician_snd_device);
+		goto err_pdev;
+	}
+
+	return 0;
+
+err_pdev:
+	gpio_free(EGPIO_MAGICIAN_IN_SEL1);
+err_request_in_sel1:
+	gpio_free(EGPIO_MAGICIAN_IN_SEL0);
+err_request_in_sel0:
+	gpio_free(EGPIO_MAGICIAN_MIC_POWER);
+err_request_mic:
+	gpio_free(EGPIO_MAGICIAN_EP_POWER);
+err_request_ep:
+	gpio_free(EGPIO_MAGICIAN_SPK_POWER);
+err_request_spk:
+	return ret;
+}
+
+static void __exit magician_exit(void)
+{
+	platform_device_unregister(magician_snd_device);
+
+	gpio_set_value(EGPIO_MAGICIAN_SPK_POWER, 0);
+	gpio_set_value(EGPIO_MAGICIAN_EP_POWER, 0);
+	gpio_set_value(EGPIO_MAGICIAN_MIC_POWER, 0);
+
+	gpio_free(EGPIO_MAGICIAN_IN_SEL1);
+	gpio_free(EGPIO_MAGICIAN_IN_SEL0);
+	gpio_free(EGPIO_MAGICIAN_MIC_POWER);
+	gpio_free(EGPIO_MAGICIAN_EP_POWER);
+	gpio_free(EGPIO_MAGICIAN_SPK_POWER);
+}
+
+module_init(magician_init);
+module_exit(magician_exit);
+
+MODULE_AUTHOR("Philipp Zabel");
+MODULE_DESCRIPTION("ALSA SoC Magician");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/mioa701_wm9713.c b/linux/sound/soc/pxa/mioa701_wm9713.c
new file mode 100644
index 0000000..f284cc5
--- /dev/null
+++ b/linux/sound/soc/pxa/mioa701_wm9713.c
@@ -0,0 +1,247 @@
+/*
+ * Handles the Mitac mioa701 SoC system
+ *
+ * Copyright (C) 2008 Robert Jarzmik
+ *
+ * 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 in version 2 of the License.
+ *
+ * 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 is a little schema of the sound interconnections :
+ *
+ *    Sagem X200                 Wolfson WM9713
+ *    +--------+             +-------------------+      Rear Speaker
+ *    |        |             |                   |           /-+
+ *    |        +--->----->---+MONOIN         SPKL+--->----+-+  |
+ *    |  GSM   |             |                   |        | |  |
+ *    |        +--->----->---+PCBEEP         SPKR+--->----+-+  |
+ *    |  CHIP  |             |                   |           \-+
+ *    |        +---<-----<---+MONO               |
+ *    |        |             |                   |      Front Speaker
+ *    +--------+             |                   |           /-+
+ *                           |                HPL+--->----+-+  |
+ *                           |                   |        | |  |
+ *                           |               OUT3+--->----+-+  |
+ *                           |                   |           \-+
+ *                           |                   |
+ *                           |                   |     Front Micro
+ *                           |                   |         +
+ *                           |               MIC1+-----<--+o+
+ *                           |                   |         +
+ *                           +-------------------+        ---
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+
+#include <asm/mach-types.h>
+#include <mach/audio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/ac97_codec.h>
+
+#include "pxa2xx-ac97.h"
+#include "../codecs/wm9713.h"
+
+#define ARRAY_AND_SIZE(x)	(x), ARRAY_SIZE(x)
+
+#define AC97_GPIO_PULL		0x58
+
+/* Use GPIO8 for rear speaker amplifier */
+static int rear_amp_power(struct snd_soc_codec *codec, int power)
+{
+	unsigned short reg;
+
+	if (power) {
+		reg = snd_soc_read(codec, AC97_GPIO_CFG);
+		snd_soc_write(codec, AC97_GPIO_CFG, reg | 0x0100);
+		reg = snd_soc_read(codec, AC97_GPIO_PULL);
+		snd_soc_write(codec, AC97_GPIO_PULL, reg | (1<<15));
+	} else {
+		reg = snd_soc_read(codec, AC97_GPIO_CFG);
+		snd_soc_write(codec, AC97_GPIO_CFG, reg & ~0x0100);
+		reg = snd_soc_read(codec, AC97_GPIO_PULL);
+		snd_soc_write(codec, AC97_GPIO_PULL, reg & ~(1<<15));
+	}
+
+	return 0;
+}
+
+static int rear_amp_event(struct snd_soc_dapm_widget *widget,
+			  struct snd_kcontrol *kctl, int event)
+{
+	struct snd_soc_codec *codec = widget->codec;
+
+	return rear_amp_power(codec, SND_SOC_DAPM_EVENT_ON(event));
+}
+
+/* mioa701 machine dapm widgets */
+static const struct snd_soc_dapm_widget mioa701_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Front Speaker", NULL),
+	SND_SOC_DAPM_SPK("Rear Speaker", rear_amp_event),
+	SND_SOC_DAPM_MIC("Headset", NULL),
+	SND_SOC_DAPM_LINE("GSM Line Out", NULL),
+	SND_SOC_DAPM_LINE("GSM Line In", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_MIC("Front Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Call Mic */
+	{"Mic Bias", NULL, "Front Mic"},
+	{"MIC1", NULL, "Mic Bias"},
+
+	/* Headset Mic */
+	{"LINEL", NULL, "Headset Mic"},
+	{"LINER", NULL, "Headset Mic"},
+
+	/* GSM Module */
+	{"MONOIN", NULL, "GSM Line Out"},
+	{"PCBEEP", NULL, "GSM Line Out"},
+	{"GSM Line In", NULL, "MONO"},
+
+	/* headphone connected to HPL, HPR */
+	{"Headset", NULL, "HPL"},
+	{"Headset", NULL, "HPR"},
+
+	/* front speaker connected to HPL, OUT3 */
+	{"Front Speaker", NULL, "HPL"},
+	{"Front Speaker", NULL, "OUT3"},
+
+	/* rear speaker connected to SPKL, SPKR */
+	{"Rear Speaker", NULL, "SPKL"},
+	{"Rear Speaker", NULL, "SPKR"},
+};
+
+static int mioa701_wm9713_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	unsigned short reg;
+
+	/* Add mioa701 specific widgets */
+	snd_soc_dapm_new_controls(codec, ARRAY_AND_SIZE(mioa701_dapm_widgets));
+
+	/* Set up mioa701 specific audio path audio_mapnects */
+	snd_soc_dapm_add_routes(codec, ARRAY_AND_SIZE(audio_map));
+
+	/* Prepare GPIO8 for rear speaker amplifier */
+	reg = codec->driver->read(codec, AC97_GPIO_CFG);
+	codec->driver->write(codec, AC97_GPIO_CFG, reg | 0x0100);
+
+	/* Prepare MIC input */
+	reg = codec->driver->read(codec, AC97_3D_CONTROL);
+	codec->driver->write(codec, AC97_3D_CONTROL, reg | 0xc000);
+
+	snd_soc_dapm_enable_pin(codec, "Front Speaker");
+	snd_soc_dapm_enable_pin(codec, "Rear Speaker");
+	snd_soc_dapm_enable_pin(codec, "Front Mic");
+	snd_soc_dapm_enable_pin(codec, "GSM Line In");
+	snd_soc_dapm_enable_pin(codec, "GSM Line Out");
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_ops mioa701_ops;
+
+static struct snd_soc_dai_link mioa701_dai[] = {
+	{
+		.name = "AC97",
+		.stream_name = "AC97 HiFi",
+		.cpu_dai_name = "pxa-ac97.0",
+		.codec_dai_name = "wm9713-hifi",
+		.codec_name = "wm9713-codec",
+		.init = mioa701_wm9713_init,
+		.platform_name = "pxa-pcm-audio",
+		.ops = &mioa701_ops,
+	},
+	{
+		.name = "AC97 Aux",
+		.stream_name = "AC97 Aux",
+		.cpu_dai_name = "pxa-ac97.1",
+		.codec_dai_name ="wm9713-aux",
+		.codec_name = "wm9713-codec",
+		.platform_name = "pxa-pcm-audio",
+		.ops = &mioa701_ops,
+	},
+};
+
+static struct snd_soc_card mioa701 = {
+	.name = "MioA701",
+	.dai_link = mioa701_dai,
+	.num_links = ARRAY_SIZE(mioa701_dai),
+};
+
+static struct platform_device *mioa701_snd_device;
+
+static int mioa701_wm9713_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	if (!machine_is_mioa701())
+		return -ENODEV;
+
+	dev_warn(&pdev->dev, "Be warned that incorrect mixers/muxes setup will"
+		 "lead to overheating and possible destruction of your device."
+		 "Do not use without a good knowledge of mio's board design!\n");
+
+	mioa701_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!mioa701_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(mioa701_snd_device, &mioa701);
+
+	ret = platform_device_add(mioa701_snd_device);
+	if (!ret)
+		return 0;
+
+	platform_device_put(mioa701_snd_device);
+	return ret;
+}
+
+static int __devexit mioa701_wm9713_remove(struct platform_device *pdev)
+{
+	platform_device_unregister(mioa701_snd_device);
+	return 0;
+}
+
+static struct platform_driver mioa701_wm9713_driver = {
+	.probe		= mioa701_wm9713_probe,
+	.remove		= __devexit_p(mioa701_wm9713_remove),
+	.driver		= {
+		.name		= "mioa701-wm9713",
+		.owner		= THIS_MODULE,
+	},
+};
+
+static int __init mioa701_asoc_init(void)
+{
+	return platform_driver_register(&mioa701_wm9713_driver);
+}
+
+static void __exit mioa701_asoc_exit(void)
+{
+	platform_driver_unregister(&mioa701_wm9713_driver);
+}
+
+module_init(mioa701_asoc_init);
+module_exit(mioa701_asoc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Robert Jarzmik (rjarzmik@free.fr)");
+MODULE_DESCRIPTION("ALSA SoC WM9713 MIO A701");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/palm27x.c b/linux/sound/soc/pxa/palm27x.c
new file mode 100644
index 0000000..13f6d48
--- /dev/null
+++ b/linux/sound/soc/pxa/palm27x.c
@@ -0,0 +1,224 @@
+/*
+ * linux/sound/soc/pxa/palm27x.c
+ *
+ * SoC Audio driver for Palm T|X, T5 and LifeDrive
+ *
+ * based on tosa.c
+ *
+ * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+#include <mach/audio.h>
+#include <mach/palmasoc.h>
+
+#include "../codecs/wm9712.h"
+#include "pxa2xx-ac97.h"
+
+static struct snd_soc_jack hs_jack;
+
+/* Headphones jack detection DAPM pins */
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+	{
+		.pin    = "Headphone Jack",
+		.mask   = SND_JACK_HEADPHONE,
+	},
+};
+
+/* Headphones jack detection gpios */
+static struct snd_soc_jack_gpio hs_jack_gpios[] = {
+	[0] = {
+		/* gpio is set on per-platform basis */
+		.name           = "hp-gpio",
+		.report         = SND_JACK_HEADPHONE,
+		.debounce_time	= 200,
+	},
+};
+
+/* Palm27x machine dapm widgets */
+static const struct snd_soc_dapm_widget palm27x_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_SPK("Ext. Speaker", NULL),
+	SND_SOC_DAPM_MIC("Ext. Microphone", NULL),
+};
+
+/* PalmTX audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* headphone connected to HPOUTL, HPOUTR */
+	{"Headphone Jack", NULL, "HPOUTL"},
+	{"Headphone Jack", NULL, "HPOUTR"},
+
+	/* ext speaker connected to ROUT2, LOUT2 */
+	{"Ext. Speaker", NULL, "LOUT2"},
+	{"Ext. Speaker", NULL, "ROUT2"},
+
+	/* mic connected to MIC1 */
+	{"Ext. Microphone", NULL, "MIC1"},
+};
+
+static struct snd_soc_card palm27x_asoc;
+
+static int palm27x_ac97_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	/* add palm27x specific widgets */
+	err = snd_soc_dapm_new_controls(codec, palm27x_dapm_widgets,
+				ARRAY_SIZE(palm27x_dapm_widgets));
+	if (err)
+		return err;
+
+	/* set up palm27x specific audio path audio_map */
+	err = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+	if (err)
+		return err;
+
+	/* connected pins */
+	if (machine_is_palmld())
+		snd_soc_dapm_enable_pin(codec, "MIC1");
+	snd_soc_dapm_enable_pin(codec, "HPOUTL");
+	snd_soc_dapm_enable_pin(codec, "HPOUTR");
+	snd_soc_dapm_enable_pin(codec, "LOUT2");
+	snd_soc_dapm_enable_pin(codec, "ROUT2");
+
+	/* not connected pins */
+	snd_soc_dapm_nc_pin(codec, "OUT3");
+	snd_soc_dapm_nc_pin(codec, "MONOOUT");
+	snd_soc_dapm_nc_pin(codec, "LINEINL");
+	snd_soc_dapm_nc_pin(codec, "LINEINR");
+	snd_soc_dapm_nc_pin(codec, "PCBEEP");
+	snd_soc_dapm_nc_pin(codec, "PHONE");
+	snd_soc_dapm_nc_pin(codec, "MIC2");
+
+	err = snd_soc_dapm_sync(codec);
+	if (err)
+		return err;
+
+	/* Jack detection API stuff */
+	err = snd_soc_jack_new(codec, "Headphone Jack",
+				SND_JACK_HEADPHONE, &hs_jack);
+	if (err)
+		return err;
+
+	err = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+				hs_jack_pins);
+	if (err)
+		return err;
+
+	err = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios),
+				hs_jack_gpios);
+
+	return err;
+}
+
+static struct snd_soc_dai_link palm27x_dai[] = {
+{
+	.name = "AC97 HiFi",
+	.stream_name = "AC97 HiFi",
+	.cpu_dai_name = "pxa-ac97.0",
+	.codec_dai_name =  "wm9712-hifi",
+	.codec_name = "wm9712-codec",
+	.platform_name = "pxa-pcm-audio",
+	.init = palm27x_ac97_init,
+},
+{
+	.name = "AC97 Aux",
+	.stream_name = "AC97 Aux",
+	.cpu_dai_name = "pxa-ac97.1",
+	.codec_dai_name = "wm9712-aux",
+	.codec_name = "wm9712-codec",
+	.platform_name = "pxa-pcm-audio",
+},
+};
+
+static struct snd_soc_card palm27x_asoc = {
+	.name = "Palm/PXA27x",
+	.dai_link = palm27x_dai,
+	.num_links = ARRAY_SIZE(palm27x_dai),
+};
+
+static struct platform_device *palm27x_snd_device;
+
+static int palm27x_asoc_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	if (!(machine_is_palmtx() || machine_is_palmt5() ||
+		machine_is_palmld() || machine_is_palmte2()))
+		return -ENODEV;
+
+	if (!pdev->dev.platform_data) {
+		dev_err(&pdev->dev, "please supply platform_data\n");
+		return -ENODEV;
+	}
+
+	hs_jack_gpios[0].gpio = ((struct palm27x_asoc_info *)
+			(pdev->dev.platform_data))->jack_gpio;
+
+	palm27x_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!palm27x_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(palm27x_snd_device, &palm27x_asoc);
+	ret = platform_device_add(palm27x_snd_device);
+
+	if (ret != 0)
+		goto put_device;
+
+	return 0;
+
+put_device:
+	platform_device_put(palm27x_snd_device);
+
+	return ret;
+}
+
+static int __devexit palm27x_asoc_remove(struct platform_device *pdev)
+{
+	platform_device_unregister(palm27x_snd_device);
+	return 0;
+}
+
+static struct platform_driver palm27x_wm9712_driver = {
+	.probe		= palm27x_asoc_probe,
+	.remove		= __devexit_p(palm27x_asoc_remove),
+	.driver		= {
+		.name		= "palm27x-asoc",
+		.owner		= THIS_MODULE,
+	},
+};
+
+static int __init palm27x_asoc_init(void)
+{
+	return platform_driver_register(&palm27x_wm9712_driver);
+}
+
+static void __exit palm27x_asoc_exit(void)
+{
+	platform_driver_unregister(&palm27x_wm9712_driver);
+}
+
+module_init(palm27x_asoc_init);
+module_exit(palm27x_asoc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC Palm T|X, T5 and LifeDrive");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/poodle.c b/linux/sound/soc/pxa/poodle.c
new file mode 100644
index 0000000..84edd03
--- /dev/null
+++ b/linux/sound/soc/pxa/poodle.c
@@ -0,0 +1,331 @@
+/*
+ * poodle.c  --  SoC audio for Poodle
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ *          Richard Purdie <richard@openedhand.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/hardware/locomo.h>
+#include <mach/poodle.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm8731.h"
+#include "pxa2xx-i2s.h"
+
+#define POODLE_HP        1
+#define POODLE_HP_OFF    0
+#define POODLE_SPK_ON    1
+#define POODLE_SPK_OFF   0
+
+ /* audio clock in Hz - rounded from 12.235MHz */
+#define POODLE_AUDIO_CLOCK 12288000
+
+static int poodle_jack_func;
+static int poodle_spk_func;
+
+static void poodle_ext_control(struct snd_soc_codec *codec)
+{
+	/* set up jack connection */
+	if (poodle_jack_func == POODLE_HP) {
+		/* set = unmute headphone */
+		locomo_gpio_write(&poodle_locomo_device.dev,
+			POODLE_LOCOMO_GPIO_MUTE_L, 1);
+		locomo_gpio_write(&poodle_locomo_device.dev,
+			POODLE_LOCOMO_GPIO_MUTE_R, 1);
+		snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+	} else {
+		locomo_gpio_write(&poodle_locomo_device.dev,
+			POODLE_LOCOMO_GPIO_MUTE_L, 0);
+		locomo_gpio_write(&poodle_locomo_device.dev,
+			POODLE_LOCOMO_GPIO_MUTE_R, 0);
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+	}
+
+	/* set the enpoints to their new connetion states */
+	if (poodle_spk_func == POODLE_SPK_ON)
+		snd_soc_dapm_enable_pin(codec, "Ext Spk");
+	else
+		snd_soc_dapm_disable_pin(codec, "Ext Spk");
+
+	/* signal a DAPM event */
+	snd_soc_dapm_sync(codec);
+}
+
+static int poodle_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	mutex_lock(&codec->mutex);
+
+	/* check the jack status at stream startup */
+	poodle_ext_control(codec);
+
+	mutex_unlock(&codec->mutex);
+
+	return 0;
+}
+
+/* we need to unmute the HP at shutdown as the mute burns power on poodle */
+static void poodle_shutdown(struct snd_pcm_substream *substream)
+{
+	/* set = unmute headphone */
+	locomo_gpio_write(&poodle_locomo_device.dev,
+		POODLE_LOCOMO_GPIO_MUTE_L, 1);
+	locomo_gpio_write(&poodle_locomo_device.dev,
+		POODLE_LOCOMO_GPIO_MUTE_R, 1);
+}
+
+static int poodle_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int clk = 0;
+	int ret = 0;
+
+	switch (params_rate(params)) {
+	case 8000:
+	case 16000:
+	case 48000:
+	case 96000:
+		clk = 12288000;
+		break;
+	case 11025:
+	case 22050:
+	case 44100:
+		clk = 11289600;
+		break;
+	}
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, clk,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set the I2S system clock as input (unused) */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops poodle_ops = {
+	.startup = poodle_startup,
+	.hw_params = poodle_hw_params,
+	.shutdown = poodle_shutdown,
+};
+
+static int poodle_get_jack(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = poodle_jack_func;
+	return 0;
+}
+
+static int poodle_set_jack(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+
+	if (poodle_jack_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	poodle_jack_func = ucontrol->value.integer.value[0];
+	poodle_ext_control(codec);
+	return 1;
+}
+
+static int poodle_get_spk(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = poodle_spk_func;
+	return 0;
+}
+
+static int poodle_set_spk(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+
+	if (poodle_spk_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	poodle_spk_func = ucontrol->value.integer.value[0];
+	poodle_ext_control(codec);
+	return 1;
+}
+
+static int poodle_amp_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *k, int event)
+{
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		locomo_gpio_write(&poodle_locomo_device.dev,
+			POODLE_LOCOMO_GPIO_AMP_ON, 0);
+	else
+		locomo_gpio_write(&poodle_locomo_device.dev,
+			POODLE_LOCOMO_GPIO_AMP_ON, 1);
+
+	return 0;
+}
+
+/* poodle machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+SND_SOC_DAPM_HP("Headphone Jack", NULL),
+SND_SOC_DAPM_SPK("Ext Spk", poodle_amp_event),
+};
+
+/* Corgi machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+	/* headphone connected to LHPOUT1, RHPOUT1 */
+	{"Headphone Jack", NULL, "LHPOUT"},
+	{"Headphone Jack", NULL, "RHPOUT"},
+
+	/* speaker connected to LOUT, ROUT */
+	{"Ext Spk", NULL, "ROUT"},
+	{"Ext Spk", NULL, "LOUT"},
+};
+
+static const char *jack_function[] = {"Off", "Headphone"};
+static const char *spk_function[] = {"Off", "On"};
+static const struct soc_enum poodle_enum[] = {
+	SOC_ENUM_SINGLE_EXT(2, jack_function),
+	SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new wm8731_poodle_controls[] = {
+	SOC_ENUM_EXT("Jack Function", poodle_enum[0], poodle_get_jack,
+		poodle_set_jack),
+	SOC_ENUM_EXT("Speaker Function", poodle_enum[1], poodle_get_spk,
+		poodle_set_spk),
+};
+
+/*
+ * Logic for a wm8731 as connected on a Sharp SL-C7x0 Device
+ */
+static int poodle_wm8731_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	snd_soc_dapm_nc_pin(codec, "LLINEIN");
+	snd_soc_dapm_nc_pin(codec, "RLINEIN");
+	snd_soc_dapm_enable_pin(codec, "MICIN");
+
+	/* Add poodle specific controls */
+	err = snd_soc_add_controls(codec, wm8731_poodle_controls,
+				ARRAY_SIZE(wm8731_poodle_controls));
+	if (err < 0)
+		return err;
+
+	/* Add poodle specific widgets */
+	snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets,
+				  ARRAY_SIZE(wm8731_dapm_widgets));
+
+	/* Set up poodle specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_sync(codec);
+	return 0;
+}
+
+/* poodle digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link poodle_dai = {
+	.name = "WM8731",
+	.stream_name = "WM8731",
+	.cpu_dai_name = "pxa2xx-i2s",
+	.codec_dai_name = "wm8731-hifi",
+	.platform_name = "pxa-pcm-audio",
+	.codec_name = "wm8731-codec.0-001a",
+	.init = poodle_wm8731_init,
+	.ops = &poodle_ops,
+};
+
+/* poodle audio machine driver */
+static struct snd_soc_card snd_soc_poodle = {
+	.name = "Poodle",
+	.dai_link = &poodle_dai,
+	.num_links = 1,
+	.owner = THIS_MODULE,
+};
+
+static struct platform_device *poodle_snd_device;
+
+static int __init poodle_init(void)
+{
+	int ret;
+
+	if (!machine_is_poodle())
+		return -ENODEV;
+
+	locomo_gpio_set_dir(&poodle_locomo_device.dev,
+		POODLE_LOCOMO_GPIO_AMP_ON, 0);
+	/* should we mute HP at startup - burning power ?*/
+	locomo_gpio_set_dir(&poodle_locomo_device.dev,
+		POODLE_LOCOMO_GPIO_MUTE_L, 0);
+	locomo_gpio_set_dir(&poodle_locomo_device.dev,
+		POODLE_LOCOMO_GPIO_MUTE_R, 0);
+
+	poodle_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!poodle_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(poodle_snd_device, &snd_soc_poodle);
+	ret = platform_device_add(poodle_snd_device);
+
+	if (ret)
+		platform_device_put(poodle_snd_device);
+
+	return ret;
+}
+
+static void __exit poodle_exit(void)
+{
+	platform_device_unregister(poodle_snd_device);
+}
+
+module_init(poodle_init);
+module_exit(poodle_exit);
+
+/* Module information */
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Poodle");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/pxa-ssp.c b/linux/sound/soc/pxa/pxa-ssp.c
new file mode 100644
index 0000000..b439eee
--- /dev/null
+++ b/linux/sound/soc/pxa/pxa-ssp.c
@@ -0,0 +1,843 @@
+/*
+ * pxa-ssp.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2005,2008 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ *         Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  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.
+ *
+ * TODO:
+ *  o Test network mode for > 16bit sample size
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <asm/irq.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+
+#include <mach/hardware.h>
+#include <mach/dma.h>
+#include <mach/audio.h>
+#include <plat/ssp.h>
+
+#include "../../arm/pxa2xx-pcm.h"
+#include "pxa-ssp.h"
+
+/*
+ * SSP audio private data
+ */
+struct ssp_priv {
+	struct ssp_device *ssp;
+	unsigned int sysclk;
+	int dai_fmt;
+#ifdef CONFIG_PM
+	uint32_t	cr0;
+	uint32_t	cr1;
+	uint32_t	to;
+	uint32_t	psp;
+#endif
+};
+
+static void dump_registers(struct ssp_device *ssp)
+{
+	dev_dbg(&ssp->pdev->dev, "SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x\n",
+		 pxa_ssp_read_reg(ssp, SSCR0), pxa_ssp_read_reg(ssp, SSCR1),
+		 pxa_ssp_read_reg(ssp, SSTO));
+
+	dev_dbg(&ssp->pdev->dev, "SSPSP 0x%08x SSSR 0x%08x SSACD 0x%08x\n",
+		 pxa_ssp_read_reg(ssp, SSPSP), pxa_ssp_read_reg(ssp, SSSR),
+		 pxa_ssp_read_reg(ssp, SSACD));
+}
+
+static void pxa_ssp_enable(struct ssp_device *ssp)
+{
+	uint32_t sscr0;
+
+	sscr0 = __raw_readl(ssp->mmio_base + SSCR0) | SSCR0_SSE;
+	__raw_writel(sscr0, ssp->mmio_base + SSCR0);
+}
+
+static void pxa_ssp_disable(struct ssp_device *ssp)
+{
+	uint32_t sscr0;
+
+	sscr0 = __raw_readl(ssp->mmio_base + SSCR0) & ~SSCR0_SSE;
+	__raw_writel(sscr0, ssp->mmio_base + SSCR0);
+}
+
+struct pxa2xx_pcm_dma_data {
+	struct pxa2xx_pcm_dma_params params;
+	char name[20];
+};
+
+static struct pxa2xx_pcm_dma_params *
+pxa_ssp_get_dma_params(struct ssp_device *ssp, int width4, int out)
+{
+	struct pxa2xx_pcm_dma_data *dma;
+
+	dma = kzalloc(sizeof(struct pxa2xx_pcm_dma_data), GFP_KERNEL);
+	if (dma == NULL)
+		return NULL;
+
+	snprintf(dma->name, 20, "SSP%d PCM %s %s", ssp->port_id,
+			width4 ? "32-bit" : "16-bit", out ? "out" : "in");
+
+	dma->params.name = dma->name;
+	dma->params.drcmr = &DRCMR(out ? ssp->drcmr_tx : ssp->drcmr_rx);
+	dma->params.dcmd = (out ? (DCMD_INCSRCADDR | DCMD_FLOWTRG) :
+				  (DCMD_INCTRGADDR | DCMD_FLOWSRC)) |
+			(width4 ? DCMD_WIDTH4 : DCMD_WIDTH2) | DCMD_BURST16;
+	dma->params.dev_addr = ssp->phys_base + SSDR;
+
+	return &dma->params;
+}
+
+static int pxa_ssp_startup(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *cpu_dai)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+	int ret = 0;
+
+	if (!cpu_dai->active) {
+		clk_enable(ssp->clk);
+		pxa_ssp_disable(ssp);
+	}
+
+	kfree(snd_soc_dai_get_dma_data(cpu_dai, substream));
+	snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
+
+	return ret;
+}
+
+static void pxa_ssp_shutdown(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *cpu_dai)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+
+	if (!cpu_dai->active) {
+		pxa_ssp_disable(ssp);
+		clk_disable(ssp->clk);
+	}
+
+	kfree(snd_soc_dai_get_dma_data(cpu_dai, substream));
+	snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
+}
+
+#ifdef CONFIG_PM
+
+static int pxa_ssp_suspend(struct snd_soc_dai *cpu_dai)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+
+	if (!cpu_dai->active)
+		clk_enable(ssp->clk);
+
+	priv->cr0 = __raw_readl(ssp->mmio_base + SSCR0);
+	priv->cr1 = __raw_readl(ssp->mmio_base + SSCR1);
+	priv->to  = __raw_readl(ssp->mmio_base + SSTO);
+	priv->psp = __raw_readl(ssp->mmio_base + SSPSP);
+
+	pxa_ssp_disable(ssp);
+	clk_disable(ssp->clk);
+	return 0;
+}
+
+static int pxa_ssp_resume(struct snd_soc_dai *cpu_dai)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+	uint32_t sssr = SSSR_ROR | SSSR_TUR | SSSR_BCE;
+
+	clk_enable(ssp->clk);
+
+	__raw_writel(sssr, ssp->mmio_base + SSSR);
+	__raw_writel(priv->cr0 & ~SSCR0_SSE, ssp->mmio_base + SSCR0);
+	__raw_writel(priv->cr1, ssp->mmio_base + SSCR1);
+	__raw_writel(priv->to,  ssp->mmio_base + SSTO);
+	__raw_writel(priv->psp, ssp->mmio_base + SSPSP);
+
+	if (cpu_dai->active)
+		pxa_ssp_enable(ssp);
+	else
+		clk_disable(ssp->clk);
+
+	return 0;
+}
+
+#else
+#define pxa_ssp_suspend	NULL
+#define pxa_ssp_resume	NULL
+#endif
+
+/**
+ * ssp_set_clkdiv - set SSP clock divider
+ * @div: serial clock rate divider
+ */
+static void pxa_ssp_set_scr(struct ssp_device *ssp, u32 div)
+{
+	u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
+
+	if (cpu_is_pxa25x() && ssp->type == PXA25x_SSP) {
+		sscr0 &= ~0x0000ff00;
+		sscr0 |= ((div - 2)/2) << 8; /* 2..512 */
+	} else {
+		sscr0 &= ~0x000fff00;
+		sscr0 |= (div - 1) << 8;     /* 1..4096 */
+	}
+	pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+}
+
+/**
+ * pxa_ssp_get_clkdiv - get SSP clock divider
+ */
+static u32 pxa_ssp_get_scr(struct ssp_device *ssp)
+{
+	u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
+	u32 div;
+
+	if (cpu_is_pxa25x() && ssp->type == PXA25x_SSP)
+		div = ((sscr0 >> 8) & 0xff) * 2 + 2;
+	else
+		div = ((sscr0 >> 8) & 0xfff) + 1;
+	return div;
+}
+
+/*
+ * Set the SSP ports SYSCLK.
+ */
+static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+	int clk_id, unsigned int freq, int dir)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+	int val;
+
+	u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0) &
+		~(SSCR0_ECS |  SSCR0_NCS | SSCR0_MOD | SSCR0_ACS);
+
+	dev_dbg(&ssp->pdev->dev,
+		"pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %u\n",
+		cpu_dai->id, clk_id, freq);
+
+	switch (clk_id) {
+	case PXA_SSP_CLK_NET_PLL:
+		sscr0 |= SSCR0_MOD;
+		break;
+	case PXA_SSP_CLK_PLL:
+		/* Internal PLL is fixed */
+		if (cpu_is_pxa25x())
+			priv->sysclk = 1843200;
+		else
+			priv->sysclk = 13000000;
+		break;
+	case PXA_SSP_CLK_EXT:
+		priv->sysclk = freq;
+		sscr0 |= SSCR0_ECS;
+		break;
+	case PXA_SSP_CLK_NET:
+		priv->sysclk = freq;
+		sscr0 |= SSCR0_NCS | SSCR0_MOD;
+		break;
+	case PXA_SSP_CLK_AUDIO:
+		priv->sysclk = 0;
+		pxa_ssp_set_scr(ssp, 1);
+		sscr0 |= SSCR0_ACS;
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	/* The SSP clock must be disabled when changing SSP clock mode
+	 * on PXA2xx.  On PXA3xx it must be enabled when doing so. */
+	if (!cpu_is_pxa3xx())
+		clk_disable(ssp->clk);
+	val = pxa_ssp_read_reg(ssp, SSCR0) | sscr0;
+	pxa_ssp_write_reg(ssp, SSCR0, val);
+	if (!cpu_is_pxa3xx())
+		clk_enable(ssp->clk);
+
+	return 0;
+}
+
+/*
+ * Set the SSP clock dividers.
+ */
+static int pxa_ssp_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+	int div_id, int div)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+	int val;
+
+	switch (div_id) {
+	case PXA_SSP_AUDIO_DIV_ACDS:
+		val = (pxa_ssp_read_reg(ssp, SSACD) & ~0x7) | SSACD_ACDS(div);
+		pxa_ssp_write_reg(ssp, SSACD, val);
+		break;
+	case PXA_SSP_AUDIO_DIV_SCDB:
+		val = pxa_ssp_read_reg(ssp, SSACD);
+		val &= ~SSACD_SCDB;
+#if defined(CONFIG_PXA3xx)
+		if (cpu_is_pxa3xx())
+			val &= ~SSACD_SCDX8;
+#endif
+		switch (div) {
+		case PXA_SSP_CLK_SCDB_1:
+			val |= SSACD_SCDB;
+			break;
+		case PXA_SSP_CLK_SCDB_4:
+			break;
+#if defined(CONFIG_PXA3xx)
+		case PXA_SSP_CLK_SCDB_8:
+			if (cpu_is_pxa3xx())
+				val |= SSACD_SCDX8;
+			else
+				return -EINVAL;
+			break;
+#endif
+		default:
+			return -EINVAL;
+		}
+		pxa_ssp_write_reg(ssp, SSACD, val);
+		break;
+	case PXA_SSP_DIV_SCR:
+		pxa_ssp_set_scr(ssp, div);
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+/*
+ * Configure the PLL frequency pxa27x and (afaik - pxa320 only)
+ */
+static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id,
+	int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+	u32 ssacd = pxa_ssp_read_reg(ssp, SSACD) & ~0x70;
+
+#if defined(CONFIG_PXA3xx)
+	if (cpu_is_pxa3xx())
+		pxa_ssp_write_reg(ssp, SSACDD, 0);
+#endif
+
+	switch (freq_out) {
+	case 5622000:
+		break;
+	case 11345000:
+		ssacd |= (0x1 << 4);
+		break;
+	case 12235000:
+		ssacd |= (0x2 << 4);
+		break;
+	case 14857000:
+		ssacd |= (0x3 << 4);
+		break;
+	case 32842000:
+		ssacd |= (0x4 << 4);
+		break;
+	case 48000000:
+		ssacd |= (0x5 << 4);
+		break;
+	case 0:
+		/* Disable */
+		break;
+
+	default:
+#ifdef CONFIG_PXA3xx
+		/* PXA3xx has a clock ditherer which can be used to generate
+		 * a wider range of frequencies - calculate a value for it.
+		 */
+		if (cpu_is_pxa3xx()) {
+			u32 val;
+			u64 tmp = 19968;
+			tmp *= 1000000;
+			do_div(tmp, freq_out);
+			val = tmp;
+
+			val = (val << 16) | 64;
+			pxa_ssp_write_reg(ssp, SSACDD, val);
+
+			ssacd |= (0x6 << 4);
+
+			dev_dbg(&ssp->pdev->dev,
+				"Using SSACDD %x to supply %uHz\n",
+				val, freq_out);
+			break;
+		}
+#endif
+
+		return -EINVAL;
+	}
+
+	pxa_ssp_write_reg(ssp, SSACD, ssacd);
+
+	return 0;
+}
+
+/*
+ * Set the active slots in TDM/Network mode
+ */
+static int pxa_ssp_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+	unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+	u32 sscr0;
+
+	sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
+	sscr0 &= ~(SSCR0_MOD | SSCR0_SlotsPerFrm(8) | SSCR0_EDSS | SSCR0_DSS);
+
+	/* set slot width */
+	if (slot_width > 16)
+		sscr0 |= SSCR0_EDSS | SSCR0_DataSize(slot_width - 16);
+	else
+		sscr0 |= SSCR0_DataSize(slot_width);
+
+	if (slots > 1) {
+		/* enable network mode */
+		sscr0 |= SSCR0_MOD;
+
+		/* set number of active slots */
+		sscr0 |= SSCR0_SlotsPerFrm(slots);
+
+		/* set active slot mask */
+		pxa_ssp_write_reg(ssp, SSTSA, tx_mask);
+		pxa_ssp_write_reg(ssp, SSRSA, rx_mask);
+	}
+	pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+
+	return 0;
+}
+
+/*
+ * Tristate the SSP DAI lines
+ */
+static int pxa_ssp_set_dai_tristate(struct snd_soc_dai *cpu_dai,
+	int tristate)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+	u32 sscr1;
+
+	sscr1 = pxa_ssp_read_reg(ssp, SSCR1);
+	if (tristate)
+		sscr1 &= ~SSCR1_TTE;
+	else
+		sscr1 |= SSCR1_TTE;
+	pxa_ssp_write_reg(ssp, SSCR1, sscr1);
+
+	return 0;
+}
+
+/*
+ * Set up the SSP DAI format.
+ * The SSP Port must be inactive before calling this function as the
+ * physical interface format is changed.
+ */
+static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+		unsigned int fmt)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+	u32 sscr0, sscr1, sspsp, scfr;
+
+	/* check if we need to change anything at all */
+	if (priv->dai_fmt == fmt)
+		return 0;
+
+	/* we can only change the settings if the port is not in use */
+	if (pxa_ssp_read_reg(ssp, SSCR0) & SSCR0_SSE) {
+		dev_err(&ssp->pdev->dev,
+			"can't change hardware dai format: stream is in use");
+		return -EINVAL;
+	}
+
+	/* reset port settings */
+	sscr0 = pxa_ssp_read_reg(ssp, SSCR0) &
+		~(SSCR0_ECS |  SSCR0_NCS | SSCR0_MOD | SSCR0_ACS);
+	sscr1 = SSCR1_RxTresh(8) | SSCR1_TxTresh(7);
+	sspsp = 0;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		sscr1 |= SSCR1_SCLKDIR | SSCR1_SFRMDIR | SSCR1_SCFR;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		sscr1 |= SSCR1_SCLKDIR | SSCR1_SCFR;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		sspsp |= SSPSP_SFRMP;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		sspsp |= SSPSP_SCMODE(2);
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		sscr0 |= SSCR0_PSP;
+		sscr1 |= SSCR1_RWOT | SSCR1_TRAIL;
+		/* See hw_params() */
+		break;
+
+	case SND_SOC_DAIFMT_DSP_A:
+		sspsp |= SSPSP_FSRT;
+	case SND_SOC_DAIFMT_DSP_B:
+		sscr0 |= SSCR0_MOD | SSCR0_PSP;
+		sscr1 |= SSCR1_TRAIL | SSCR1_RWOT;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+	pxa_ssp_write_reg(ssp, SSCR1, sscr1);
+	pxa_ssp_write_reg(ssp, SSPSP, sspsp);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+	case SND_SOC_DAIFMT_CBM_CFS:
+		scfr = pxa_ssp_read_reg(ssp, SSCR1) | SSCR1_SCFR;
+		pxa_ssp_write_reg(ssp, SSCR1, scfr);
+
+		while (pxa_ssp_read_reg(ssp, SSSR) & SSSR_BSY)
+			cpu_relax();
+		break;
+	}
+
+	dump_registers(ssp);
+
+	/* Since we are configuring the timings for the format by hand
+	 * we have to defer some things until hw_params() where we
+	 * know parameters like the sample size.
+	 */
+	priv->dai_fmt = fmt;
+
+	return 0;
+}
+
+/*
+ * Set the SSP audio DMA parameters and sample size.
+ * Can be called multiple times by oss emulation.
+ */
+static int pxa_ssp_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *cpu_dai)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+	int chn = params_channels(params);
+	u32 sscr0;
+	u32 sspsp;
+	int width = snd_pcm_format_physical_width(params_format(params));
+	int ttsa = pxa_ssp_read_reg(ssp, SSTSA) & 0xf;
+	struct pxa2xx_pcm_dma_params *dma_data;
+
+	dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+	/* generate correct DMA params */
+	kfree(dma_data);
+
+	/* Network mode with one active slot (ttsa == 1) can be used
+	 * to force 16-bit frame width on the wire (for S16_LE), even
+	 * with two channels. Use 16-bit DMA transfers for this case.
+	 */
+	dma_data = pxa_ssp_get_dma_params(ssp,
+			((chn == 2) && (ttsa != 1)) || (width == 32),
+			substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+	/* we can only change the settings if the port is not in use */
+	if (pxa_ssp_read_reg(ssp, SSCR0) & SSCR0_SSE)
+		return 0;
+
+	/* clear selected SSP bits */
+	sscr0 = pxa_ssp_read_reg(ssp, SSCR0) & ~(SSCR0_DSS | SSCR0_EDSS);
+
+	/* bit size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+#ifdef CONFIG_PXA3xx
+		if (cpu_is_pxa3xx())
+			sscr0 |= SSCR0_FPCKE;
+#endif
+		sscr0 |= SSCR0_DataSize(16);
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		sscr0 |= (SSCR0_EDSS | SSCR0_DataSize(8));
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		sscr0 |= (SSCR0_EDSS | SSCR0_DataSize(16));
+		break;
+	}
+	pxa_ssp_write_reg(ssp, SSCR0, sscr0);
+
+	switch (priv->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	       sspsp = pxa_ssp_read_reg(ssp, SSPSP);
+
+		if ((pxa_ssp_get_scr(ssp) == 4) && (width == 16)) {
+			/* This is a special case where the bitclk is 64fs
+			* and we're not dealing with 2*32 bits of audio
+			* samples.
+			*
+			* The SSP values used for that are all found out by
+			* trying and failing a lot; some of the registers
+			* needed for that mode are only available on PXA3xx.
+			*/
+
+#ifdef CONFIG_PXA3xx
+			if (!cpu_is_pxa3xx())
+				return -EINVAL;
+
+			sspsp |= SSPSP_SFRMWDTH(width * 2);
+			sspsp |= SSPSP_SFRMDLY(width * 4);
+			sspsp |= SSPSP_EDMYSTOP(3);
+			sspsp |= SSPSP_DMYSTOP(3);
+			sspsp |= SSPSP_DMYSTRT(1);
+#else
+			return -EINVAL;
+#endif
+		} else {
+			/* The frame width is the width the LRCLK is
+			 * asserted for; the delay is expressed in
+			 * half cycle units.  We need the extra cycle
+			 * because the data starts clocking out one BCLK
+			 * after LRCLK changes polarity.
+			 */
+			sspsp |= SSPSP_SFRMWDTH(width + 1);
+			sspsp |= SSPSP_SFRMDLY((width + 1) * 2);
+			sspsp |= SSPSP_DMYSTRT(1);
+		}
+
+		pxa_ssp_write_reg(ssp, SSPSP, sspsp);
+		break;
+	default:
+		break;
+	}
+
+	/* When we use a network mode, we always require TDM slots
+	 * - complain loudly and fail if they've not been set up yet.
+	 */
+	if ((sscr0 & SSCR0_MOD) && !ttsa) {
+		dev_err(&ssp->pdev->dev, "No TDM timeslot configured\n");
+		return -EINVAL;
+	}
+
+	dump_registers(ssp);
+
+	return 0;
+}
+
+static int pxa_ssp_trigger(struct snd_pcm_substream *substream, int cmd,
+			   struct snd_soc_dai *cpu_dai)
+{
+	int ret = 0;
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct ssp_device *ssp = priv->ssp;
+	int val;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_RESUME:
+		pxa_ssp_enable(ssp);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		val = pxa_ssp_read_reg(ssp, SSCR1);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			val |= SSCR1_TSRE;
+		else
+			val |= SSCR1_RSRE;
+		pxa_ssp_write_reg(ssp, SSCR1, val);
+		val = pxa_ssp_read_reg(ssp, SSSR);
+		pxa_ssp_write_reg(ssp, SSSR, val);
+		break;
+	case SNDRV_PCM_TRIGGER_START:
+		val = pxa_ssp_read_reg(ssp, SSCR1);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			val |= SSCR1_TSRE;
+		else
+			val |= SSCR1_RSRE;
+		pxa_ssp_write_reg(ssp, SSCR1, val);
+		pxa_ssp_enable(ssp);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		val = pxa_ssp_read_reg(ssp, SSCR1);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			val &= ~SSCR1_TSRE;
+		else
+			val &= ~SSCR1_RSRE;
+		pxa_ssp_write_reg(ssp, SSCR1, val);
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		pxa_ssp_disable(ssp);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		val = pxa_ssp_read_reg(ssp, SSCR1);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			val &= ~SSCR1_TSRE;
+		else
+			val &= ~SSCR1_RSRE;
+		pxa_ssp_write_reg(ssp, SSCR1, val);
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	dump_registers(ssp);
+
+	return ret;
+}
+
+static int pxa_ssp_probe(struct snd_soc_dai *dai)
+{
+	struct ssp_priv *priv;
+	int ret;
+
+	priv = kzalloc(sizeof(struct ssp_priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->ssp = pxa_ssp_request(dai->id + 1, "SoC audio");
+	if (priv->ssp == NULL) {
+		ret = -ENODEV;
+		goto err_priv;
+	}
+
+	priv->dai_fmt = (unsigned int) -1;
+	snd_soc_dai_set_drvdata(dai, priv);
+
+	return 0;
+
+err_priv:
+	kfree(priv);
+	return ret;
+}
+
+static int pxa_ssp_remove(struct snd_soc_dai *dai)
+{
+	struct ssp_priv *priv = snd_soc_dai_get_drvdata(dai);
+
+	pxa_ssp_free(priv->ssp);
+	kfree(priv);
+	return 0;
+}
+
+#define PXA_SSP_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+			  SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |	\
+			  SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |	\
+			  SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define PXA_SSP_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+			    SNDRV_PCM_FMTBIT_S24_LE |	\
+			    SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops pxa_ssp_dai_ops = {
+	.startup	= pxa_ssp_startup,
+	.shutdown	= pxa_ssp_shutdown,
+	.trigger	= pxa_ssp_trigger,
+	.hw_params	= pxa_ssp_hw_params,
+	.set_sysclk	= pxa_ssp_set_dai_sysclk,
+	.set_clkdiv	= pxa_ssp_set_dai_clkdiv,
+	.set_pll	= pxa_ssp_set_dai_pll,
+	.set_fmt	= pxa_ssp_set_dai_fmt,
+	.set_tdm_slot	= pxa_ssp_set_dai_tdm_slot,
+	.set_tristate	= pxa_ssp_set_dai_tristate,
+};
+
+static struct snd_soc_dai_driver pxa_ssp_dai = {
+		.probe = pxa_ssp_probe,
+		.remove = pxa_ssp_remove,
+		.suspend = pxa_ssp_suspend,
+		.resume = pxa_ssp_resume,
+		.playback = {
+			.channels_min = 1,
+			.channels_max = 8,
+			.rates = PXA_SSP_RATES,
+			.formats = PXA_SSP_FORMATS,
+		},
+		.capture = {
+			 .channels_min = 1,
+			 .channels_max = 8,
+			.rates = PXA_SSP_RATES,
+			.formats = PXA_SSP_FORMATS,
+		 },
+		.ops = &pxa_ssp_dai_ops,
+};
+
+static __devinit int asoc_ssp_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_dai(&pdev->dev, &pxa_ssp_dai);
+}
+
+static int __devexit asoc_ssp_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dai(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver asoc_ssp_driver = {
+	.driver = {
+			.name = "pxa-ssp-dai",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = asoc_ssp_probe,
+	.remove = __devexit_p(asoc_ssp_remove),
+};
+
+static int __init pxa_ssp_init(void)
+{
+	return platform_driver_register(&asoc_ssp_driver);
+}
+module_init(pxa_ssp_init);
+
+static void __exit pxa_ssp_exit(void)
+{
+	platform_driver_unregister(&asoc_ssp_driver);
+}
+module_exit(pxa_ssp_exit);
+
+/* Module information */
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("PXA SSP/PCM SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/pxa-ssp.h b/linux/sound/soc/pxa/pxa-ssp.h
new file mode 100644
index 0000000..bc79da2
--- /dev/null
+++ b/linux/sound/soc/pxa/pxa-ssp.h
@@ -0,0 +1,45 @@
+/*
+ * ASoC PXA SSP port support
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _PXA_SSP_H
+#define _PXA_SSP_H
+
+/* pxa DAI SSP IDs */
+#define PXA_DAI_SSP1			0
+#define PXA_DAI_SSP2			1
+#define PXA_DAI_SSP3			2
+#define PXA_DAI_SSP4			3
+
+/* SSP clock sources */
+#define PXA_SSP_CLK_PLL	0
+#define PXA_SSP_CLK_EXT	1
+#define PXA_SSP_CLK_NET	2
+#define PXA_SSP_CLK_AUDIO	3
+#define PXA_SSP_CLK_NET_PLL	4
+
+/* SSP audio dividers */
+#define PXA_SSP_AUDIO_DIV_ACDS		0
+#define PXA_SSP_AUDIO_DIV_SCDB		1
+#define PXA_SSP_DIV_SCR				2
+
+/* SSP ACDS audio dividers values */
+#define PXA_SSP_CLK_AUDIO_DIV_1		0
+#define PXA_SSP_CLK_AUDIO_DIV_2		1
+#define PXA_SSP_CLK_AUDIO_DIV_4		2
+#define PXA_SSP_CLK_AUDIO_DIV_8		3
+#define PXA_SSP_CLK_AUDIO_DIV_16	4
+#define PXA_SSP_CLK_AUDIO_DIV_32	5
+
+/* SSP divider bypass */
+#define PXA_SSP_CLK_SCDB_4		0
+#define PXA_SSP_CLK_SCDB_1		1
+#define PXA_SSP_CLK_SCDB_8		2
+
+#define PXA_SSP_PLL_OUT  0
+
+#endif
diff --git a/linux/sound/soc/pxa/pxa2xx-ac97.c b/linux/sound/soc/pxa/pxa2xx-ac97.c
new file mode 100644
index 0000000..ac51c6d
--- /dev/null
+++ b/linux/sound/soc/pxa/pxa2xx-ac97.c
@@ -0,0 +1,280 @@
+/*
+ * linux/sound/pxa2xx-ac97.c -- AC97 support for the Intel PXA2xx chip.
+ *
+ * Author:	Nicolas Pitre
+ * Created:	Dec 02, 2004
+ * Copyright:	MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+
+#include <mach/hardware.h>
+#include <mach/regs-ac97.h>
+#include <mach/dma.h>
+#include <mach/audio.h>
+
+#include "pxa2xx-ac97.h"
+
+static void pxa2xx_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+	pxa2xx_ac97_try_warm_reset(ac97);
+
+	pxa2xx_ac97_finish_reset(ac97);
+}
+
+static void pxa2xx_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+	pxa2xx_ac97_try_cold_reset(ac97);
+
+	pxa2xx_ac97_finish_reset(ac97);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read	= pxa2xx_ac97_read,
+	.write	= pxa2xx_ac97_write,
+	.warm_reset	= pxa2xx_ac97_warm_reset,
+	.reset	= pxa2xx_ac97_cold_reset,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_stereo_out = {
+	.name			= "AC97 PCM Stereo out",
+	.dev_addr		= __PREG(PCDR),
+	.drcmr			= &DRCMR(12),
+	.dcmd			= DCMD_INCSRCADDR | DCMD_FLOWTRG |
+				  DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_stereo_in = {
+	.name			= "AC97 PCM Stereo in",
+	.dev_addr		= __PREG(PCDR),
+	.drcmr			= &DRCMR(11),
+	.dcmd			= DCMD_INCTRGADDR | DCMD_FLOWSRC |
+				  DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_aux_mono_out = {
+	.name			= "AC97 Aux PCM (Slot 5) Mono out",
+	.dev_addr		= __PREG(MODR),
+	.drcmr			= &DRCMR(10),
+	.dcmd			= DCMD_INCSRCADDR | DCMD_FLOWTRG |
+				  DCMD_BURST16 | DCMD_WIDTH2,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_aux_mono_in = {
+	.name			= "AC97 Aux PCM (Slot 5) Mono in",
+	.dev_addr		= __PREG(MODR),
+	.drcmr			= &DRCMR(9),
+	.dcmd			= DCMD_INCTRGADDR | DCMD_FLOWSRC |
+				  DCMD_BURST16 | DCMD_WIDTH2,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_mic_mono_in = {
+	.name			= "AC97 Mic PCM (Slot 6) Mono in",
+	.dev_addr		= __PREG(MCDR),
+	.drcmr			= &DRCMR(8),
+	.dcmd			= DCMD_INCTRGADDR | DCMD_FLOWSRC |
+				  DCMD_BURST16 | DCMD_WIDTH2,
+};
+
+#ifdef CONFIG_PM
+static int pxa2xx_ac97_suspend(struct snd_soc_dai *dai)
+{
+	return pxa2xx_ac97_hw_suspend();
+}
+
+static int pxa2xx_ac97_resume(struct snd_soc_dai *dai)
+{
+	return pxa2xx_ac97_hw_resume();
+}
+
+#else
+#define pxa2xx_ac97_suspend	NULL
+#define pxa2xx_ac97_resume	NULL
+#endif
+
+static int pxa2xx_ac97_probe(struct snd_soc_dai *dai)
+{
+	return pxa2xx_ac97_hw_probe(to_platform_device(dai->dev));
+}
+
+static int pxa2xx_ac97_remove(struct snd_soc_dai *dai)
+{
+	pxa2xx_ac97_hw_remove(to_platform_device(dai->dev));
+	return 0;
+}
+
+static int pxa2xx_ac97_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *cpu_dai)
+{
+	struct pxa2xx_pcm_dma_params *dma_data;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = &pxa2xx_ac97_pcm_stereo_out;
+	else
+		dma_data = &pxa2xx_ac97_pcm_stereo_in;
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+	return 0;
+}
+
+static int pxa2xx_ac97_hw_aux_params(struct snd_pcm_substream *substream,
+				     struct snd_pcm_hw_params *params,
+				     struct snd_soc_dai *cpu_dai)
+{
+	struct pxa2xx_pcm_dma_params *dma_data;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = &pxa2xx_ac97_pcm_aux_mono_out;
+	else
+		dma_data = &pxa2xx_ac97_pcm_aux_mono_in;
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+	return 0;
+}
+
+static int pxa2xx_ac97_hw_mic_params(struct snd_pcm_substream *substream,
+				     struct snd_pcm_hw_params *params,
+				     struct snd_soc_dai *cpu_dai)
+{
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return -ENODEV;
+	else
+		snd_soc_dai_set_dma_data(cpu_dai, substream,
+					 &pxa2xx_ac97_pcm_mic_mono_in);
+
+	return 0;
+}
+
+#define PXA2XX_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+		SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+		SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_ops pxa_ac97_hifi_dai_ops = {
+	.hw_params	= pxa2xx_ac97_hw_params,
+};
+
+static struct snd_soc_dai_ops pxa_ac97_aux_dai_ops = {
+	.hw_params	= pxa2xx_ac97_hw_aux_params,
+};
+
+static struct snd_soc_dai_ops pxa_ac97_mic_dai_ops = {
+	.hw_params	= pxa2xx_ac97_hw_mic_params,
+};
+
+/*
+ * There is only 1 physical AC97 interface for pxa2xx, but it
+ * has extra fifo's that can be used for aux DACs and ADCs.
+ */
+static struct snd_soc_dai_driver pxa_ac97_dai[] = {
+{
+	.name = "pxa2xx-ac97",
+	.ac97_control = 1,
+	.probe = pxa2xx_ac97_probe,
+	.remove = pxa2xx_ac97_remove,
+	.suspend = pxa2xx_ac97_suspend,
+	.resume = pxa2xx_ac97_resume,
+	.playback = {
+		.stream_name = "AC97 Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = PXA2XX_AC97_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.stream_name = "AC97 Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = PXA2XX_AC97_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.ops = &pxa_ac97_hifi_dai_ops,
+},
+{
+	.name = "pxa2xx-ac97-aux",
+	.ac97_control = 1,
+	.playback = {
+		.stream_name = "AC97 Aux Playback",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = PXA2XX_AC97_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.stream_name = "AC97 Aux Capture",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = PXA2XX_AC97_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.ops = &pxa_ac97_aux_dai_ops,
+},
+{
+	.name = "pxa2xx-ac97-mic",
+	.ac97_control = 1,
+	.capture = {
+		.stream_name = "AC97 Mic Capture",
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = PXA2XX_AC97_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.ops = &pxa_ac97_mic_dai_ops,
+},
+};
+
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static __devinit int pxa2xx_ac97_dev_probe(struct platform_device *pdev)
+{
+	if (pdev->id != -1) {
+		dev_err(&pdev->dev, "PXA2xx has only one AC97 port.\n");
+		return -ENXIO;
+	}
+
+	/* Punt most of the init to the SoC probe; we may need the machine
+	 * driver to do interesting things with the clocking to get us up
+	 * and running.
+	 */
+	return snd_soc_register_dais(&pdev->dev, pxa_ac97_dai,
+			ARRAY_SIZE(pxa_ac97_dai));
+}
+
+static int __devexit pxa2xx_ac97_dev_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(pxa_ac97_dai));
+	return 0;
+}
+
+static struct platform_driver pxa2xx_ac97_driver = {
+	.probe		= pxa2xx_ac97_dev_probe,
+	.remove		= __devexit_p(pxa2xx_ac97_dev_remove),
+	.driver		= {
+		.name	= "pxa2xx-ac97",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init pxa_ac97_init(void)
+{
+	return platform_driver_register(&pxa2xx_ac97_driver);
+}
+module_init(pxa_ac97_init);
+
+static void __exit pxa_ac97_exit(void)
+{
+	platform_driver_unregister(&pxa2xx_ac97_driver);
+}
+module_exit(pxa_ac97_exit);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/pxa2xx-ac97.h b/linux/sound/soc/pxa/pxa2xx-ac97.h
new file mode 100644
index 0000000..eda891e
--- /dev/null
+++ b/linux/sound/soc/pxa/pxa2xx-ac97.h
@@ -0,0 +1,20 @@
+/*
+ * linux/sound/soc/pxa/pxa2xx-ac97.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _PXA2XX_AC97_H
+#define _PXA2XX_AC97_H
+
+/* pxa2xx DAI ID's */
+#define PXA2XX_DAI_AC97_HIFI	0
+#define PXA2XX_DAI_AC97_AUX		1
+#define PXA2XX_DAI_AC97_MIC		2
+
+/* platform data */
+extern struct snd_ac97_bus_ops pxa2xx_ac97_ops;
+
+#endif
diff --git a/linux/sound/soc/pxa/pxa2xx-i2s.c b/linux/sound/soc/pxa/pxa2xx-i2s.c
new file mode 100644
index 0000000..11be595
--- /dev/null
+++ b/linux/sound/soc/pxa/pxa2xx-i2s.c
@@ -0,0 +1,401 @@
+/*
+ * pxa2xx-i2s.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ *         lrg@slimlogic.co.uk
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+
+#include <mach/hardware.h>
+#include <mach/dma.h>
+#include <mach/audio.h>
+
+#include "pxa2xx-i2s.h"
+
+/*
+ * I2S Controller Register and Bit Definitions
+ */
+#define SACR0		__REG(0x40400000)  /* Global Control Register */
+#define SACR1		__REG(0x40400004)  /* Serial Audio I 2 S/MSB-Justified Control Register */
+#define SASR0		__REG(0x4040000C)  /* Serial Audio I 2 S/MSB-Justified Interface and FIFO Status Register */
+#define SAIMR		__REG(0x40400014)  /* Serial Audio Interrupt Mask Register */
+#define SAICR		__REG(0x40400018)  /* Serial Audio Interrupt Clear Register */
+#define SADIV		__REG(0x40400060)  /* Audio Clock Divider Register. */
+#define SADR		__REG(0x40400080)  /* Serial Audio Data Register (TX and RX FIFO access Register). */
+
+#define SACR0_RFTH(x)	((x) << 12)	/* Rx FIFO Interrupt or DMA Trigger Threshold */
+#define SACR0_TFTH(x)	((x) << 8)	/* Tx FIFO Interrupt or DMA Trigger Threshold */
+#define SACR0_STRF	(1 << 5)	/* FIFO Select for EFWR Special Function */
+#define SACR0_EFWR	(1 << 4)	/* Enable EFWR Function  */
+#define SACR0_RST	(1 << 3)	/* FIFO, i2s Register Reset */
+#define SACR0_BCKD	(1 << 2) 	/* Bit Clock Direction */
+#define SACR0_ENB	(1 << 0)	/* Enable I2S Link */
+#define SACR1_ENLBF	(1 << 5)	/* Enable Loopback */
+#define SACR1_DRPL	(1 << 4) 	/* Disable Replaying Function */
+#define SACR1_DREC	(1 << 3)	/* Disable Recording Function */
+#define SACR1_AMSL	(1 << 0)	/* Specify Alternate Mode */
+
+#define SASR0_I2SOFF	(1 << 7)	/* Controller Status */
+#define SASR0_ROR	(1 << 6)	/* Rx FIFO Overrun */
+#define SASR0_TUR	(1 << 5)	/* Tx FIFO Underrun */
+#define SASR0_RFS	(1 << 4)	/* Rx FIFO Service Request */
+#define SASR0_TFS	(1 << 3)	/* Tx FIFO Service Request */
+#define SASR0_BSY	(1 << 2)	/* I2S Busy */
+#define SASR0_RNE	(1 << 1)	/* Rx FIFO Not Empty */
+#define SASR0_TNF	(1 << 0) 	/* Tx FIFO Not Empty */
+
+#define SAICR_ROR	(1 << 6)	/* Clear Rx FIFO Overrun Interrupt */
+#define SAICR_TUR	(1 << 5)	/* Clear Tx FIFO Underrun Interrupt */
+
+#define SAIMR_ROR	(1 << 6)	/* Enable Rx FIFO Overrun Condition Interrupt */
+#define SAIMR_TUR	(1 << 5)	/* Enable Tx FIFO Underrun Condition Interrupt */
+#define SAIMR_RFS	(1 << 4)	/* Enable Rx FIFO Service Interrupt */
+#define SAIMR_TFS	(1 << 3)	/* Enable Tx FIFO Service Interrupt */
+
+struct pxa_i2s_port {
+	u32 sadiv;
+	u32 sacr0;
+	u32 sacr1;
+	u32 saimr;
+	int master;
+	u32 fmt;
+};
+static struct pxa_i2s_port pxa_i2s;
+static struct clk *clk_i2s;
+static int clk_ena = 0;
+
+static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_out = {
+	.name			= "I2S PCM Stereo out",
+	.dev_addr		= __PREG(SADR),
+	.drcmr			= &DRCMR(3),
+	.dcmd			= DCMD_INCSRCADDR | DCMD_FLOWTRG |
+				  DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_in = {
+	.name			= "I2S PCM Stereo in",
+	.dev_addr		= __PREG(SADR),
+	.drcmr			= &DRCMR(2),
+	.dcmd			= DCMD_INCTRGADDR | DCMD_FLOWSRC |
+				  DCMD_BURST32 | DCMD_WIDTH4,
+};
+
+static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	if (IS_ERR(clk_i2s))
+		return PTR_ERR(clk_i2s);
+
+	if (!cpu_dai->active)
+		SACR0 = 0;
+
+	return 0;
+}
+
+/* wait for I2S controller to be ready */
+static int pxa_i2s_wait(void)
+{
+	int i;
+
+	/* flush the Rx FIFO */
+	for(i = 0; i < 16; i++)
+		SADR;
+	return 0;
+}
+
+static int pxa2xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+		unsigned int fmt)
+{
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		pxa_i2s.fmt = 0;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		pxa_i2s.fmt = SACR1_AMSL;
+		break;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		pxa_i2s.master = 1;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		pxa_i2s.master = 0;
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int pxa2xx_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	if (clk_id != PXA2XX_I2S_SYSCLK)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct pxa2xx_pcm_dma_params *dma_data;
+
+	BUG_ON(IS_ERR(clk_i2s));
+	clk_enable(clk_i2s);
+	clk_ena = 1;
+	pxa_i2s_wait();
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = &pxa2xx_i2s_pcm_stereo_out;
+	else
+		dma_data = &pxa2xx_i2s_pcm_stereo_in;
+
+	snd_soc_dai_set_dma_data(dai, substream, dma_data);
+
+	/* is port used by another stream */
+	if (!(SACR0 & SACR0_ENB)) {
+		SACR0 = 0;
+		if (pxa_i2s.master)
+			SACR0 |= SACR0_BCKD;
+
+		SACR0 |= SACR0_RFTH(14) | SACR0_TFTH(1);
+		SACR1 |= pxa_i2s.fmt;
+	}
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		SAIMR |= SAIMR_TFS;
+	else
+		SAIMR |= SAIMR_RFS;
+
+	switch (params_rate(params)) {
+	case 8000:
+		SADIV = 0x48;
+		break;
+	case 11025:
+		SADIV = 0x34;
+		break;
+	case 16000:
+		SADIV = 0x24;
+		break;
+	case 22050:
+		SADIV = 0x1a;
+		break;
+	case 44100:
+		SADIV = 0xd;
+		break;
+	case 48000:
+		SADIV = 0xc;
+		break;
+	case 96000: /* not in manual and possibly slightly inaccurate */
+		SADIV = 0x6;
+		break;
+	}
+
+	return 0;
+}
+
+static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+			      struct snd_soc_dai *dai)
+{
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			SACR1 &= ~SACR1_DRPL;
+		else
+			SACR1 &= ~SACR1_DREC;
+		SACR0 |= SACR0_ENB;
+		break;
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		SACR1 |= SACR1_DRPL;
+		SAIMR &= ~SAIMR_TFS;
+	} else {
+		SACR1 |= SACR1_DREC;
+		SAIMR &= ~SAIMR_RFS;
+	}
+
+	if ((SACR1 & (SACR1_DREC | SACR1_DRPL)) == (SACR1_DREC | SACR1_DRPL)) {
+		SACR0 &= ~SACR0_ENB;
+		pxa_i2s_wait();
+		if (clk_ena) {
+			clk_disable(clk_i2s);
+			clk_ena = 0;
+		}
+	}
+}
+
+#ifdef CONFIG_PM
+static int pxa2xx_i2s_suspend(struct snd_soc_dai *dai)
+{
+	/* store registers */
+	pxa_i2s.sacr0 = SACR0;
+	pxa_i2s.sacr1 = SACR1;
+	pxa_i2s.saimr = SAIMR;
+	pxa_i2s.sadiv = SADIV;
+
+	/* deactivate link */
+	SACR0 &= ~SACR0_ENB;
+	pxa_i2s_wait();
+	return 0;
+}
+
+static int pxa2xx_i2s_resume(struct snd_soc_dai *dai)
+{
+	pxa_i2s_wait();
+
+	SACR0 = pxa_i2s.sacr0 & ~SACR0_ENB;
+	SACR1 = pxa_i2s.sacr1;
+	SAIMR = pxa_i2s.saimr;
+	SADIV = pxa_i2s.sadiv;
+
+	SACR0 = pxa_i2s.sacr0;
+
+	return 0;
+}
+
+#else
+#define pxa2xx_i2s_suspend	NULL
+#define pxa2xx_i2s_resume	NULL
+#endif
+
+static int pxa2xx_i2s_probe(struct snd_soc_dai *dai)
+{
+	clk_i2s = clk_get(dai->dev, "I2SCLK");
+	if (IS_ERR(clk_i2s))
+		return PTR_ERR(clk_i2s);
+
+	/*
+	 * PXA Developer's Manual:
+	 * If SACR0[ENB] is toggled in the middle of a normal operation,
+	 * the SACR0[RST] bit must also be set and cleared to reset all
+	 * I2S controller registers.
+	 */
+	SACR0 = SACR0_RST;
+	SACR0 = 0;
+	/* Make sure RPL and REC are disabled */
+	SACR1 = SACR1_DRPL | SACR1_DREC;
+	/* Along with FIFO servicing */
+	SAIMR &= ~(SAIMR_RFS | SAIMR_TFS);
+
+	return 0;
+}
+
+static int  pxa2xx_i2s_remove(struct snd_soc_dai *dai)
+{
+	clk_put(clk_i2s);
+	clk_i2s = ERR_PTR(-ENOENT);
+	return 0;
+}
+
+#define PXA2XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+		SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+		SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+
+static struct snd_soc_dai_ops pxa_i2s_dai_ops = {
+	.startup	= pxa2xx_i2s_startup,
+	.shutdown	= pxa2xx_i2s_shutdown,
+	.trigger	= pxa2xx_i2s_trigger,
+	.hw_params	= pxa2xx_i2s_hw_params,
+	.set_fmt	= pxa2xx_i2s_set_dai_fmt,
+	.set_sysclk	= pxa2xx_i2s_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver pxa_i2s_dai = {
+	.probe = pxa2xx_i2s_probe,
+	.remove = pxa2xx_i2s_remove,
+	.suspend = pxa2xx_i2s_suspend,
+	.resume = pxa2xx_i2s_resume,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = PXA2XX_I2S_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = PXA2XX_I2S_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.ops = &pxa_i2s_dai_ops,
+	.symmetric_rates = 1,
+};
+
+static int pxa2xx_i2s_drv_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_dai(&pdev->dev, &pxa_i2s_dai);
+}
+
+static int __devexit pxa2xx_i2s_drv_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dai(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver pxa2xx_i2s_driver = {
+	.probe = pxa2xx_i2s_drv_probe,
+	.remove = __devexit_p(pxa2xx_i2s_drv_remove),
+
+	.driver = {
+		.name = "pxa2xx-i2s",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init pxa2xx_i2s_init(void)
+{
+	clk_i2s = ERR_PTR(-ENOENT);
+	return platform_driver_register(&pxa2xx_i2s_driver);
+}
+
+static void __exit pxa2xx_i2s_exit(void)
+{
+	platform_driver_unregister(&pxa2xx_i2s_driver);
+}
+
+module_init(pxa2xx_i2s_init);
+module_exit(pxa2xx_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
+MODULE_DESCRIPTION("pxa2xx I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pxa2xx-i2s");
diff --git a/linux/sound/soc/pxa/pxa2xx-i2s.h b/linux/sound/soc/pxa/pxa2xx-i2s.h
new file mode 100644
index 0000000..070f3c6
--- /dev/null
+++ b/linux/sound/soc/pxa/pxa2xx-i2s.h
@@ -0,0 +1,18 @@
+/*
+ * linux/sound/soc/pxa/pxa2xx-i2s.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _PXA2XX_I2S_H
+#define _PXA2XX_I2S_H
+
+/* pxa2xx DAI ID's */
+#define PXA2XX_DAI_I2S			0
+
+/* I2S clock */
+#define PXA2XX_I2S_SYSCLK		0
+
+#endif
diff --git a/linux/sound/soc/pxa/pxa2xx-pcm.c b/linux/sound/soc/pxa/pxa2xx-pcm.c
new file mode 100644
index 0000000..02fb664
--- /dev/null
+++ b/linux/sound/soc/pxa/pxa2xx-pcm.c
@@ -0,0 +1,155 @@
+/*
+ * linux/sound/arm/pxa2xx-pcm.c -- ALSA PCM interface for the Intel PXA2xx chip
+ *
+ * Author:	Nicolas Pitre
+ * Created:	Nov 30, 2004
+ * Copyright:	(C) 2004 MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pxa2xx-lib.h>
+
+#include "../../arm/pxa2xx-pcm.h"
+
+static int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pxa2xx_runtime_data *prtd = runtime->private_data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct pxa2xx_pcm_dma_params *dma;
+	int ret;
+
+	dma = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+	/* return if this is a bufferless transfer e.g.
+	 * codec <--> BT codec or GSM modem -- lg FIXME */
+	if (!dma)
+		return 0;
+
+	/* this may get called several times by oss emulation
+	 * with different params */
+	if (prtd->params == NULL) {
+		prtd->params = dma;
+		ret = pxa_request_dma(prtd->params->name, DMA_PRIO_LOW,
+			      pxa2xx_pcm_dma_irq, substream);
+		if (ret < 0)
+			return ret;
+		prtd->dma_ch = ret;
+	} else if (prtd->params != dma) {
+		pxa_free_dma(prtd->dma_ch);
+		prtd->params = dma;
+		ret = pxa_request_dma(prtd->params->name, DMA_PRIO_LOW,
+			      pxa2xx_pcm_dma_irq, substream);
+		if (ret < 0)
+			return ret;
+		prtd->dma_ch = ret;
+	}
+
+	return __pxa2xx_pcm_hw_params(substream, params);
+}
+
+static int pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct pxa2xx_runtime_data *prtd = substream->runtime->private_data;
+
+	__pxa2xx_pcm_hw_free(substream);
+
+	if (prtd->dma_ch >= 0) {
+		pxa_free_dma(prtd->dma_ch);
+		prtd->dma_ch = -1;
+	}
+
+	return 0;
+}
+
+static struct snd_pcm_ops pxa2xx_pcm_ops = {
+	.open		= __pxa2xx_pcm_open,
+	.close		= __pxa2xx_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= pxa2xx_pcm_hw_params,
+	.hw_free	= pxa2xx_pcm_hw_free,
+	.prepare	= __pxa2xx_pcm_prepare,
+	.trigger	= pxa2xx_pcm_trigger,
+	.pointer	= pxa2xx_pcm_pointer,
+	.mmap		= pxa2xx_pcm_mmap,
+};
+
+static u64 pxa2xx_pcm_dmamask = DMA_BIT_MASK(32);
+
+static int pxa2xx_soc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+	struct snd_pcm *pcm)
+{
+	int ret = 0;
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &pxa2xx_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+	if (dai->driver->playback.channels_min) {
+		ret = pxa2xx_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_PLAYBACK);
+		if (ret)
+			goto out;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = pxa2xx_pcm_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_CAPTURE);
+		if (ret)
+			goto out;
+	}
+ out:
+	return ret;
+}
+
+static struct snd_soc_platform_driver pxa2xx_soc_platform = {
+	.ops 	= &pxa2xx_pcm_ops,
+	.pcm_new	= pxa2xx_soc_pcm_new,
+	.pcm_free	= pxa2xx_pcm_free_dma_buffers,
+};
+
+static int __devinit pxa2xx_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &pxa2xx_soc_platform);
+}
+
+static int __devexit pxa2xx_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver pxa_pcm_driver = {
+	.driver = {
+			.name = "pxa-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = pxa2xx_soc_platform_probe,
+	.remove = __devexit_p(pxa2xx_soc_platform_remove),
+};
+
+static int __init snd_pxa_pcm_init(void)
+{
+	return platform_driver_register(&pxa_pcm_driver);
+}
+module_init(snd_pxa_pcm_init);
+
+static void __exit snd_pxa_pcm_exit(void)
+{
+	platform_driver_unregister(&pxa_pcm_driver);
+}
+module_exit(snd_pxa_pcm_exit);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/raumfeld.c b/linux/sound/soc/pxa/raumfeld.c
new file mode 100644
index 0000000..2cda82b
--- /dev/null
+++ b/linux/sound/soc/pxa/raumfeld.c
@@ -0,0 +1,312 @@
+/*
+ * raumfeld_audio.c  --  SoC audio for Raumfeld audio devices
+ *
+ * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
+ *
+ * based on code from:
+ *
+ *    Wolfson Microelectronics PLC.
+ *    Openedhand Ltd.
+ *    Liam Girdwood <lrg@slimlogic.co.uk>
+ *    Richard Purdie <richard@openedhand.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+
+#include "pxa-ssp.h"
+
+#define GPIO_SPDIF_RESET	(38)
+#define GPIO_MCLK_RESET		(111)
+#define GPIO_CODEC_RESET	(120)
+
+static struct i2c_client *max9486_client;
+static struct i2c_board_info max9486_hwmon_info = {
+	I2C_BOARD_INFO("max9485", 0x63),
+};
+
+#define MAX9485_MCLK_FREQ_112896 0x22
+#define MAX9485_MCLK_FREQ_122880 0x23
+#define MAX9485_MCLK_FREQ_225792 0x32
+#define MAX9485_MCLK_FREQ_245760 0x33
+
+static void set_max9485_clk(char clk)
+{
+	i2c_master_send(max9486_client, &clk, 1);
+}
+
+static void raumfeld_enable_audio(bool en)
+{
+	if (en) {
+		gpio_set_value(GPIO_MCLK_RESET, 1);
+
+		/* wait some time to let the clocks become stable */
+		msleep(100);
+
+		gpio_set_value(GPIO_SPDIF_RESET, 1);
+		gpio_set_value(GPIO_CODEC_RESET, 1);
+	} else {
+		gpio_set_value(GPIO_MCLK_RESET, 0);
+		gpio_set_value(GPIO_SPDIF_RESET, 0);
+		gpio_set_value(GPIO_CODEC_RESET, 0);
+	}
+}
+
+/* CS4270 */
+static int raumfeld_cs4270_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+	/* set freq to 0 to enable all possible codec sample rates */
+	return snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0);
+}
+
+static void raumfeld_cs4270_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+	/* set freq to 0 to enable all possible codec sample rates */
+	snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0);
+}
+
+static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream,
+				     struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int fmt, clk = 0;
+	int ret = 0;
+
+	switch (params_rate(params)) {
+	case 44100:
+		set_max9485_clk(MAX9485_MCLK_FREQ_112896);
+		clk = 11289600;
+		break;
+	case 48000:
+		set_max9485_clk(MAX9485_MCLK_FREQ_122880);
+		clk = 12288000;
+		break;
+	case 88200:
+		set_max9485_clk(MAX9485_MCLK_FREQ_225792);
+		clk = 22579200;
+		break;
+	case 96000:
+		set_max9485_clk(MAX9485_MCLK_FREQ_245760);
+		clk = 24576000;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	fmt = SND_SOC_DAIFMT_I2S |
+	      SND_SOC_DAIFMT_NB_NF |
+	      SND_SOC_DAIFMT_CBS_CFS;
+
+	/* setup the CODEC DAI */
+	ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, 0);
+	if (ret < 0)
+		return ret;
+
+	/* setup the CPU DAI */
+	ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, clk);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, PXA_SSP_DIV_SCR, 4);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops raumfeld_cs4270_ops = {
+	.startup = raumfeld_cs4270_startup,
+	.shutdown = raumfeld_cs4270_shutdown,
+	.hw_params = raumfeld_cs4270_hw_params,
+};
+
+static int raumfeld_line_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	raumfeld_enable_audio(false);
+	return 0;
+}
+
+static int raumfeld_line_resume(struct platform_device *pdev)
+{
+	raumfeld_enable_audio(true);
+	return 0;
+}
+
+/* AK4104 */
+
+static int raumfeld_ak4104_hw_params(struct snd_pcm_substream *substream,
+				     struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int fmt, ret = 0, clk = 0;
+
+	switch (params_rate(params)) {
+	case 44100:
+		set_max9485_clk(MAX9485_MCLK_FREQ_112896);
+		clk = 11289600;
+		break;
+	case 48000:
+		set_max9485_clk(MAX9485_MCLK_FREQ_122880);
+		clk = 12288000;
+		break;
+	case 88200:
+		set_max9485_clk(MAX9485_MCLK_FREQ_225792);
+		clk = 22579200;
+		break;
+	case 96000:
+		set_max9485_clk(MAX9485_MCLK_FREQ_245760);
+		clk = 24576000;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
+
+	/* setup the CODEC DAI */
+	ret = snd_soc_dai_set_fmt(codec_dai, fmt | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* setup the CPU DAI */
+	ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, clk);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, fmt | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, PXA_SSP_DIV_SCR, 4);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops raumfeld_ak4104_ops = {
+	.hw_params = raumfeld_ak4104_hw_params,
+};
+
+static struct snd_soc_dai_link raumfeld_dai[] = {
+{
+	.name		= "ak4104",
+	.stream_name	= "Playback",
+	.cpu_dai_name = "pxa-ssp-dai.1",
+	.codec_dai_name = "ak4104-hifi",
+	.platform_name = "pxa-pcm-audio",
+	.ops		= &raumfeld_ak4104_ops,
+	.codec_name = "ak4104-codec.0",
+},
+{
+	.name		= "CS4270",
+	.stream_name	= "CS4270",
+	.cpu_dai_name = "pxa-ssp-dai.0",
+	.platform_name = "pxa-pcm-audio",
+	.codec_dai_name = "cs4270-hifi",
+	.codec_name = "cs4270-codec.0-0048",
+	.ops		= &raumfeld_cs4270_ops,
+},};
+
+static struct snd_soc_card snd_soc_raumfeld = {
+	.name		= "Raumfeld",
+	.dai_link	= raumfeld_dai,
+	.suspend_post	= raumfeld_line_suspend,
+	.resume_pre	= raumfeld_line_resume,
+	.num_links	= ARRAY_SIZE(raumfeld_dai),
+};
+
+static struct platform_device *raumfeld_audio_device;
+
+static int __init raumfeld_audio_init(void)
+{
+	int ret;
+
+	if (!machine_is_raumfeld_speaker() &&
+	    !machine_is_raumfeld_connector())
+		return 0;
+
+	max9486_client = i2c_new_device(i2c_get_adapter(0),
+					&max9486_hwmon_info);
+
+	if (!max9486_client)
+		return -ENOMEM;
+
+	set_max9485_clk(MAX9485_MCLK_FREQ_122880);
+
+	/* Register LINE and SPDIF */
+	raumfeld_audio_device = platform_device_alloc("soc-audio", 0);
+	if (!raumfeld_audio_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(raumfeld_audio_device,
+			     &snd_soc_raumfeld);
+	ret = platform_device_add(raumfeld_audio_device);
+
+	/* no S/PDIF on Speakers */
+	if (machine_is_raumfeld_speaker())
+		return ret;
+
+	raumfeld_enable_audio(true);
+
+	return ret;
+}
+
+static void __exit raumfeld_audio_exit(void)
+{
+	raumfeld_enable_audio(false);
+
+	platform_device_unregister(raumfeld_audio_device);
+
+	i2c_unregister_device(max9486_client);
+
+	gpio_free(GPIO_MCLK_RESET);
+	gpio_free(GPIO_CODEC_RESET);
+	gpio_free(GPIO_SPDIF_RESET);
+}
+
+module_init(raumfeld_audio_init);
+module_exit(raumfeld_audio_exit);
+
+/* Module information */
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_DESCRIPTION("Raumfeld audio SoC");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/saarb.c b/linux/sound/soc/pxa/saarb.c
new file mode 100644
index 0000000..d63cb47
--- /dev/null
+++ b/linux/sound/soc/pxa/saarb.c
@@ -0,0 +1,200 @@
+/*
+ * saarb.c -- SoC audio for saarb
+ *
+ * Copyright (C) 2010 Marvell International Ltd.
+ * 	Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/88pm860x-codec.h"
+#include "pxa-ssp.h"
+
+static int saarb_pm860x_init(struct snd_soc_pcm_runtime *rtd);
+
+static struct platform_device *saarb_snd_device;
+
+static struct snd_soc_jack hs_jack, mic_jack;
+
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+	{ .pin = "Headset Stereophone",	.mask = SND_JACK_HEADPHONE, },
+};
+
+static struct snd_soc_jack_pin mic_jack_pins[] = {
+	{ .pin = "Headset Mic 2",	.mask = SND_JACK_MICROPHONE, },
+};
+
+/* saarb machine dapm widgets */
+static const struct snd_soc_dapm_widget saarb_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Stereophone", NULL),
+	SND_SOC_DAPM_LINE("Lineout Out 1", NULL),
+	SND_SOC_DAPM_LINE("Lineout Out 2", NULL),
+	SND_SOC_DAPM_SPK("Ext Speaker", NULL),
+	SND_SOC_DAPM_MIC("Ext Mic 1", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_MIC("Ext Mic 3", NULL),
+};
+
+/* saarb machine audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"Headset Stereophone", NULL, "HS1"},
+	{"Headset Stereophone", NULL, "HS2"},
+
+	{"Ext Speaker", NULL, "LSP"},
+	{"Ext Speaker", NULL, "LSN"},
+
+	{"Lineout Out 1", NULL, "LINEOUT1"},
+	{"Lineout Out 2", NULL, "LINEOUT2"},
+
+	{"MIC1P", NULL, "Mic1 Bias"},
+	{"MIC1N", NULL, "Mic1 Bias"},
+	{"Mic1 Bias", NULL, "Ext Mic 1"},
+
+	{"MIC2P", NULL, "Mic1 Bias"},
+	{"MIC2N", NULL, "Mic1 Bias"},
+	{"Mic1 Bias", NULL, "Headset Mic 2"},
+
+	{"MIC3P", NULL, "Mic3 Bias"},
+	{"MIC3N", NULL, "Mic3 Bias"},
+	{"Mic3 Bias", NULL, "Ext Mic 3"},
+};
+
+static int saarb_i2s_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int width = snd_pcm_format_physical_width(params_format(params));
+	int ret;
+
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_NET_PLL, 0,
+				     PM860X_CLK_DIR_OUT);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 0, PM860X_CLK_DIR_OUT);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_tdm_slot(cpu_dai, 3, 3, 2, width);
+
+	return ret;
+}
+
+static struct snd_soc_ops saarb_i2s_ops = {
+	.hw_params	= saarb_i2s_hw_params,
+};
+
+static struct snd_soc_dai_link saarb_dai[] = {
+	{
+		.name		= "88PM860x I2S",
+		.stream_name	= "I2S Audio",
+		.cpu_dai_name	= "pxa-ssp-dai.1",
+		.codec_dai_name	= "88pm860x-i2s",
+		.platform_name	= "pxa-pcm-audio",
+		.codec_name	= "88pm860x-codec",
+		.init		= saarb_pm860x_init,
+		.ops		= &saarb_i2s_ops,
+	},
+};
+
+static struct snd_soc_card snd_soc_card_saarb = {
+	.name = "Saarb",
+	.dai_link = saarb_dai,
+	.num_links = ARRAY_SIZE(saarb_dai),
+};
+
+static int saarb_pm860x_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int ret;
+
+	snd_soc_dapm_new_controls(codec, saarb_dapm_widgets,
+				  ARRAY_SIZE(saarb_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	/* connected pins */
+	snd_soc_dapm_enable_pin(codec, "Ext Speaker");
+	snd_soc_dapm_enable_pin(codec, "Ext Mic 1");
+	snd_soc_dapm_enable_pin(codec, "Ext Mic 3");
+	snd_soc_dapm_disable_pin(codec, "Headset Mic 2");
+	snd_soc_dapm_disable_pin(codec, "Headset Stereophone");
+
+	ret = snd_soc_dapm_sync(codec);
+	if (ret)
+		return ret;
+
+	/* Headset jack detection */
+	snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE
+			| SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2,
+			&hs_jack);
+	snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+			      hs_jack_pins);
+	snd_soc_jack_new(codec, "Microphone Jack", SND_JACK_MICROPHONE,
+			 &mic_jack);
+	snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins),
+			      mic_jack_pins);
+
+	/* headphone, microphone detection & headset short detection */
+	pm860x_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADPHONE,
+			      SND_JACK_BTN_0, SND_JACK_BTN_1, SND_JACK_BTN_2);
+	pm860x_mic_jack_detect(codec, &hs_jack, SND_JACK_MICROPHONE);
+	return 0;
+}
+
+static int __init saarb_init(void)
+{
+	int ret;
+
+	if (!machine_is_saarb())
+		return -ENODEV;
+	saarb_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!saarb_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(saarb_snd_device, &snd_soc_card_saarb);
+
+	ret = platform_device_add(saarb_snd_device);
+	if (ret)
+		platform_device_put(saarb_snd_device);
+
+	return ret;
+}
+
+static void __exit saarb_exit(void)
+{
+	platform_device_unregister(saarb_snd_device);
+}
+
+module_init(saarb_init);
+module_exit(saarb_exit);
+
+MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
+MODULE_DESCRIPTION("ALSA SoC 88PM860x Saarb");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/spitz.c b/linux/sound/soc/pxa/spitz.c
new file mode 100644
index 0000000..0b30d7d
--- /dev/null
+++ b/linux/sound/soc/pxa/spitz.c
@@ -0,0 +1,363 @@
+/*
+ * spitz.c  --  SoC audio for Sharp SL-Cxx00 models Spitz, Borzoi and Akita
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ *          Richard Purdie <richard@openedhand.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/spitz.h>
+#include "../codecs/wm8750.h"
+#include "pxa2xx-i2s.h"
+
+#define SPITZ_HP        0
+#define SPITZ_MIC       1
+#define SPITZ_LINE      2
+#define SPITZ_HEADSET   3
+#define SPITZ_HP_OFF    4
+#define SPITZ_SPK_ON    0
+#define SPITZ_SPK_OFF   1
+
+ /* audio clock in Hz - rounded from 12.235MHz */
+#define SPITZ_AUDIO_CLOCK 12288000
+
+static int spitz_jack_func;
+static int spitz_spk_func;
+
+static void spitz_ext_control(struct snd_soc_codec *codec)
+{
+	if (spitz_spk_func == SPITZ_SPK_ON)
+		snd_soc_dapm_enable_pin(codec, "Ext Spk");
+	else
+		snd_soc_dapm_disable_pin(codec, "Ext Spk");
+
+	/* set up jack connection */
+	switch (spitz_jack_func) {
+	case SPITZ_HP:
+		/* enable and unmute hp jack, disable mic bias */
+		snd_soc_dapm_disable_pin(codec, "Headset Jack");
+		snd_soc_dapm_disable_pin(codec, "Mic Jack");
+		snd_soc_dapm_disable_pin(codec, "Line Jack");
+		snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+		gpio_set_value(SPITZ_GPIO_MUTE_L, 1);
+		gpio_set_value(SPITZ_GPIO_MUTE_R, 1);
+		break;
+	case SPITZ_MIC:
+		/* enable mic jack and bias, mute hp */
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+		snd_soc_dapm_disable_pin(codec, "Headset Jack");
+		snd_soc_dapm_disable_pin(codec, "Line Jack");
+		snd_soc_dapm_enable_pin(codec, "Mic Jack");
+		gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+		gpio_set_value(SPITZ_GPIO_MUTE_R, 0);
+		break;
+	case SPITZ_LINE:
+		/* enable line jack, disable mic bias and mute hp */
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+		snd_soc_dapm_disable_pin(codec, "Headset Jack");
+		snd_soc_dapm_disable_pin(codec, "Mic Jack");
+		snd_soc_dapm_enable_pin(codec, "Line Jack");
+		gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+		gpio_set_value(SPITZ_GPIO_MUTE_R, 0);
+		break;
+	case SPITZ_HEADSET:
+		/* enable and unmute headset jack enable mic bias, mute L hp */
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+		snd_soc_dapm_enable_pin(codec, "Mic Jack");
+		snd_soc_dapm_disable_pin(codec, "Line Jack");
+		snd_soc_dapm_enable_pin(codec, "Headset Jack");
+		gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+		gpio_set_value(SPITZ_GPIO_MUTE_R, 1);
+		break;
+	case SPITZ_HP_OFF:
+
+		/* jack removed, everything off */
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+		snd_soc_dapm_disable_pin(codec, "Headset Jack");
+		snd_soc_dapm_disable_pin(codec, "Mic Jack");
+		snd_soc_dapm_disable_pin(codec, "Line Jack");
+		gpio_set_value(SPITZ_GPIO_MUTE_L, 0);
+		gpio_set_value(SPITZ_GPIO_MUTE_R, 0);
+		break;
+	}
+	snd_soc_dapm_sync(codec);
+}
+
+static int spitz_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	mutex_lock(&codec->mutex);
+
+	/* check the jack status at stream startup */
+	spitz_ext_control(codec);
+
+	mutex_unlock(&codec->mutex);
+
+	return 0;
+}
+
+static int spitz_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int clk = 0;
+	int ret = 0;
+
+	switch (params_rate(params)) {
+	case 8000:
+	case 16000:
+	case 48000:
+	case 96000:
+		clk = 12288000;
+		break;
+	case 11025:
+	case 22050:
+	case 44100:
+		clk = 11289600;
+		break;
+	}
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set the I2S system clock as input (unused) */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops spitz_ops = {
+	.startup = spitz_startup,
+	.hw_params = spitz_hw_params,
+};
+
+static int spitz_get_jack(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = spitz_jack_func;
+	return 0;
+}
+
+static int spitz_set_jack(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	if (spitz_jack_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	spitz_jack_func = ucontrol->value.integer.value[0];
+	spitz_ext_control(codec);
+	return 1;
+}
+
+static int spitz_get_spk(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = spitz_spk_func;
+	return 0;
+}
+
+static int spitz_set_spk(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+
+	if (spitz_spk_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	spitz_spk_func = ucontrol->value.integer.value[0];
+	spitz_ext_control(codec);
+	return 1;
+}
+
+static int spitz_mic_bias(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *k, int event)
+{
+	if (machine_is_borzoi() || machine_is_spitz())
+		gpio_set_value(SPITZ_GPIO_MIC_BIAS,
+				SND_SOC_DAPM_EVENT_ON(event));
+
+	if (machine_is_akita())
+		gpio_set_value(AKITA_GPIO_MIC_BIAS,
+				SND_SOC_DAPM_EVENT_ON(event));
+
+	return 0;
+}
+
+/* spitz machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_MIC("Mic Jack", spitz_mic_bias),
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+	SND_SOC_DAPM_LINE("Line Jack", NULL),
+
+	/* headset is a mic and mono headphone */
+	SND_SOC_DAPM_HP("Headset Jack", NULL),
+};
+
+/* Spitz machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+	/* headphone connected to LOUT1, ROUT1 */
+	{"Headphone Jack", NULL, "LOUT1"},
+	{"Headphone Jack", NULL, "ROUT1"},
+
+	/* headset connected to ROUT1 and LINPUT1 with bias (def below) */
+	{"Headset Jack", NULL, "ROUT1"},
+
+	/* ext speaker connected to LOUT2, ROUT2  */
+	{"Ext Spk", NULL , "ROUT2"},
+	{"Ext Spk", NULL , "LOUT2"},
+
+	/* mic is connected to input 1 - with bias */
+	{"LINPUT1", NULL, "Mic Bias"},
+	{"Mic Bias", NULL, "Mic Jack"},
+
+	/* line is connected to input 1 - no bias */
+	{"LINPUT1", NULL, "Line Jack"},
+};
+
+static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
+	"Off"};
+static const char *spk_function[] = {"On", "Off"};
+static const struct soc_enum spitz_enum[] = {
+	SOC_ENUM_SINGLE_EXT(5, jack_function),
+	SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new wm8750_spitz_controls[] = {
+	SOC_ENUM_EXT("Jack Function", spitz_enum[0], spitz_get_jack,
+		spitz_set_jack),
+	SOC_ENUM_EXT("Speaker Function", spitz_enum[1], spitz_get_spk,
+		spitz_set_spk),
+};
+
+/*
+ * Logic for a wm8750 as connected on a Sharp SL-Cxx00 Device
+ */
+static int spitz_wm8750_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	/* NC codec pins */
+	snd_soc_dapm_nc_pin(codec, "RINPUT1");
+	snd_soc_dapm_nc_pin(codec, "LINPUT2");
+	snd_soc_dapm_nc_pin(codec, "RINPUT2");
+	snd_soc_dapm_nc_pin(codec, "LINPUT3");
+	snd_soc_dapm_nc_pin(codec, "RINPUT3");
+	snd_soc_dapm_nc_pin(codec, "OUT3");
+	snd_soc_dapm_nc_pin(codec, "MONO1");
+
+	/* Add spitz specific controls */
+	err = snd_soc_add_controls(codec, wm8750_spitz_controls,
+				ARRAY_SIZE(wm8750_spitz_controls));
+	if (err < 0)
+		return err;
+
+	/* Add spitz specific widgets */
+	snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets,
+				  ARRAY_SIZE(wm8750_dapm_widgets));
+
+	/* Set up spitz specific audio paths */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_sync(codec);
+	return 0;
+}
+
+/* spitz digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link spitz_dai = {
+	.name = "wm8750",
+	.stream_name = "WM8750",
+	.cpu_dai_name = "pxa-is2",
+	.codec_dai_name = "wm8750-hifi",
+	.platform_name = "pxa-pcm-audio",
+	.codec_name = "wm8750-codec.0-001a",
+	.init = spitz_wm8750_init,
+	.ops = &spitz_ops,
+};
+
+/* spitz audio machine driver */
+static struct snd_soc_card snd_soc_spitz = {
+	.name = "Spitz",
+	.dai_link = &spitz_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *spitz_snd_device;
+
+static int __init spitz_init(void)
+{
+	int ret;
+
+	if (!(machine_is_spitz() || machine_is_borzoi() || machine_is_akita()))
+		return -ENODEV;
+
+	spitz_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!spitz_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(spitz_snd_device, &snd_soc_spitz);
+	ret = platform_device_add(spitz_snd_device);
+
+	if (ret)
+		platform_device_put(spitz_snd_device);
+
+	return ret;
+}
+
+static void __exit spitz_exit(void)
+{
+	platform_device_unregister(spitz_snd_device);
+}
+
+module_init(spitz_init);
+module_exit(spitz_exit);
+
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Spitz");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/tavorevb3.c b/linux/sound/soc/pxa/tavorevb3.c
new file mode 100644
index 0000000..248c283
--- /dev/null
+++ b/linux/sound/soc/pxa/tavorevb3.c
@@ -0,0 +1,200 @@
+/*
+ * tavorevb3.c -- SoC audio for Tavor EVB3
+ *
+ * Copyright (C) 2010 Marvell International Ltd.
+ * 	Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+
+#include "../codecs/88pm860x-codec.h"
+#include "pxa-ssp.h"
+
+static int evb3_pm860x_init(struct snd_soc_pcm_runtime *rtd);
+
+static struct platform_device *evb3_snd_device;
+
+static struct snd_soc_jack hs_jack, mic_jack;
+
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+	{ .pin = "Headset Stereophone",	.mask = SND_JACK_HEADPHONE, },
+};
+
+static struct snd_soc_jack_pin mic_jack_pins[] = {
+	{ .pin = "Headset Mic 2",	.mask = SND_JACK_MICROPHONE, },
+};
+
+/* tavorevb3 machine dapm widgets */
+static const struct snd_soc_dapm_widget evb3_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+	SND_SOC_DAPM_LINE("Lineout Out 1", NULL),
+	SND_SOC_DAPM_LINE("Lineout Out 2", NULL),
+	SND_SOC_DAPM_SPK("Ext Speaker", NULL),
+	SND_SOC_DAPM_MIC("Ext Mic 1", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic 2", NULL),
+	SND_SOC_DAPM_MIC("Ext Mic 3", NULL),
+};
+
+/* tavorevb3 machine audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"Headset Stereophone", NULL, "HS1"},
+	{"Headset Stereophone", NULL, "HS2"},
+
+	{"Ext Speaker", NULL, "LSP"},
+	{"Ext Speaker", NULL, "LSN"},
+
+	{"Lineout Out 1", NULL, "LINEOUT1"},
+	{"Lineout Out 2", NULL, "LINEOUT2"},
+
+	{"MIC1P", NULL, "Mic1 Bias"},
+	{"MIC1N", NULL, "Mic1 Bias"},
+	{"Mic1 Bias", NULL, "Ext Mic 1"},
+
+	{"MIC2P", NULL, "Mic1 Bias"},
+	{"MIC2N", NULL, "Mic1 Bias"},
+	{"Mic1 Bias", NULL, "Headset Mic 2"},
+
+	{"MIC3P", NULL, "Mic3 Bias"},
+	{"MIC3N", NULL, "Mic3 Bias"},
+	{"Mic3 Bias", NULL, "Ext Mic 3"},
+};
+
+static int evb3_i2s_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int width = snd_pcm_format_physical_width(params_format(params));
+	int ret;
+
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_NET_PLL, 0,
+				     PM860X_CLK_DIR_OUT);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 0, PM860X_CLK_DIR_OUT);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_tdm_slot(cpu_dai, 3, 3, 2, width);
+	return ret;
+}
+
+static struct snd_soc_ops evb3_i2s_ops = {
+	.hw_params	= evb3_i2s_hw_params,
+};
+
+static struct snd_soc_dai_link evb3_dai[] = {
+	{
+		.name		= "88PM860x I2S",
+		.stream_name	= "I2S Audio",
+		.cpu_dai_name	= "pxa-ssp-dai.1",
+		.codec_dai_name	= "88pm860x-i2s",
+		.platform_name	= "pxa-pcm-audio",
+		.codec_name	= "88pm860x-codec",
+		.init		= evb3_pm860x_init,
+		.ops		= &evb3_i2s_ops,
+	},
+};
+
+static struct snd_soc_card snd_soc_card_evb3 = {
+	.name = "Tavor EVB3",
+	.dai_link = evb3_dai,
+	.num_links = ARRAY_SIZE(evb3_dai),
+};
+
+static int evb3_pm860x_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int ret;
+
+	snd_soc_dapm_new_controls(codec, evb3_dapm_widgets,
+				  ARRAY_SIZE(evb3_dapm_widgets));
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	/* connected pins */
+	snd_soc_dapm_enable_pin(codec, "Ext Speaker");
+	snd_soc_dapm_enable_pin(codec, "Ext Mic 1");
+	snd_soc_dapm_enable_pin(codec, "Ext Mic 3");
+	snd_soc_dapm_disable_pin(codec, "Headset Mic 2");
+	snd_soc_dapm_disable_pin(codec, "Headset Stereophone");
+
+	ret = snd_soc_dapm_sync(codec);
+	if (ret)
+		return ret;
+
+	/* Headset jack detection */
+	snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE
+			| SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2,
+			&hs_jack);
+	snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+			      hs_jack_pins);
+	snd_soc_jack_new(codec, "Microphone Jack", SND_JACK_MICROPHONE,
+			 &mic_jack);
+	snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins),
+			      mic_jack_pins);
+
+	/* headphone, microphone detection & headset short detection */
+	pm860x_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADPHONE,
+			      SND_JACK_BTN_0, SND_JACK_BTN_1, SND_JACK_BTN_2);
+	pm860x_mic_jack_detect(codec, &hs_jack, SND_JACK_MICROPHONE);
+	return 0;
+}
+
+static int __init tavorevb3_init(void)
+{
+	int ret;
+
+	if (!machine_is_tavorevb3())
+		return -ENODEV;
+	evb3_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!evb3_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(evb3_snd_device, &snd_soc_card_evb3);
+
+	ret = platform_device_add(evb3_snd_device);
+	if (ret)
+		platform_device_put(evb3_snd_device);
+
+	return ret;
+}
+
+static void __exit tavorevb3_exit(void)
+{
+	platform_device_unregister(evb3_snd_device);
+}
+
+module_init(tavorevb3_init);
+module_exit(tavorevb3_exit);
+
+MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
+MODULE_DESCRIPTION("ALSA SoC 88PM860x Tavor EVB3");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/tosa.c b/linux/sound/soc/pxa/tosa.c
new file mode 100644
index 0000000..7b983f9
--- /dev/null
+++ b/linux/sound/soc/pxa/tosa.c
@@ -0,0 +1,304 @@
+/*
+ * tosa.c  --  SoC audio for Tosa
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ *          Richard Purdie <richard@openedhand.com>
+ *
+ *  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.
+ *
+ * GPIO's
+ *  1 - Jack Insertion
+ *  5 - Hookswitch (headset answer/hang up switch)
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/tosa.h>
+#include <mach/audio.h>
+
+#include "../codecs/wm9712.h"
+#include "pxa2xx-ac97.h"
+
+static struct snd_soc_card tosa;
+
+#define TOSA_HP        0
+#define TOSA_MIC_INT   1
+#define TOSA_HEADSET   2
+#define TOSA_HP_OFF    3
+#define TOSA_SPK_ON    0
+#define TOSA_SPK_OFF   1
+
+static int tosa_jack_func;
+static int tosa_spk_func;
+
+static void tosa_ext_control(struct snd_soc_codec *codec)
+{
+	/* set up jack connection */
+	switch (tosa_jack_func) {
+	case TOSA_HP:
+		snd_soc_dapm_disable_pin(codec, "Mic (Internal)");
+		snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+		snd_soc_dapm_disable_pin(codec, "Headset Jack");
+		break;
+	case TOSA_MIC_INT:
+		snd_soc_dapm_enable_pin(codec, "Mic (Internal)");
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+		snd_soc_dapm_disable_pin(codec, "Headset Jack");
+		break;
+	case TOSA_HEADSET:
+		snd_soc_dapm_disable_pin(codec, "Mic (Internal)");
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+		snd_soc_dapm_enable_pin(codec, "Headset Jack");
+		break;
+	}
+
+	if (tosa_spk_func == TOSA_SPK_ON)
+		snd_soc_dapm_enable_pin(codec, "Speaker");
+	else
+		snd_soc_dapm_disable_pin(codec, "Speaker");
+
+	snd_soc_dapm_sync(codec);
+}
+
+static int tosa_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	mutex_lock(&codec->mutex);
+
+	/* check the jack status at stream startup */
+	tosa_ext_control(codec);
+
+	mutex_unlock(&codec->mutex);
+
+	return 0;
+}
+
+static struct snd_soc_ops tosa_ops = {
+	.startup = tosa_startup,
+};
+
+static int tosa_get_jack(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = tosa_jack_func;
+	return 0;
+}
+
+static int tosa_set_jack(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+
+	if (tosa_jack_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	tosa_jack_func = ucontrol->value.integer.value[0];
+	tosa_ext_control(codec);
+	return 1;
+}
+
+static int tosa_get_spk(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = tosa_spk_func;
+	return 0;
+}
+
+static int tosa_set_spk(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+
+	if (tosa_spk_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	tosa_spk_func = ucontrol->value.integer.value[0];
+	tosa_ext_control(codec);
+	return 1;
+}
+
+/* tosa dapm event handlers */
+static int tosa_hp_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *k, int event)
+{
+	gpio_set_value(TOSA_GPIO_L_MUTE, SND_SOC_DAPM_EVENT_ON(event) ? 1 :0);
+	return 0;
+}
+
+/* tosa machine dapm widgets */
+static const struct snd_soc_dapm_widget tosa_dapm_widgets[] = {
+SND_SOC_DAPM_HP("Headphone Jack", tosa_hp_event),
+SND_SOC_DAPM_HP("Headset Jack", NULL),
+SND_SOC_DAPM_MIC("Mic (Internal)", NULL),
+SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+/* tosa audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+	/* headphone connected to HPOUTL, HPOUTR */
+	{"Headphone Jack", NULL, "HPOUTL"},
+	{"Headphone Jack", NULL, "HPOUTR"},
+
+	/* ext speaker connected to LOUT2, ROUT2 */
+	{"Speaker", NULL, "LOUT2"},
+	{"Speaker", NULL, "ROUT2"},
+
+	/* internal mic is connected to mic1, mic2 differential - with bias */
+	{"MIC1", NULL, "Mic Bias"},
+	{"MIC2", NULL, "Mic Bias"},
+	{"Mic Bias", NULL, "Mic (Internal)"},
+
+	/* headset is connected to HPOUTR, and LINEINR with bias */
+	{"Headset Jack", NULL, "HPOUTR"},
+	{"LINEINR", NULL, "Mic Bias"},
+	{"Mic Bias", NULL, "Headset Jack"},
+};
+
+static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
+	"Off"};
+static const char *spk_function[] = {"On", "Off"};
+static const struct soc_enum tosa_enum[] = {
+	SOC_ENUM_SINGLE_EXT(5, jack_function),
+	SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new tosa_controls[] = {
+	SOC_ENUM_EXT("Jack Function", tosa_enum[0], tosa_get_jack,
+		tosa_set_jack),
+	SOC_ENUM_EXT("Speaker Function", tosa_enum[1], tosa_get_spk,
+		tosa_set_spk),
+};
+
+static int tosa_ac97_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	snd_soc_dapm_nc_pin(codec, "OUT3");
+	snd_soc_dapm_nc_pin(codec, "MONOOUT");
+
+	/* add tosa specific controls */
+	err = snd_soc_add_controls(codec, tosa_controls,
+				ARRAY_SIZE(tosa_controls));
+	if (err < 0)
+		return err;
+
+	/* add tosa specific widgets */
+	snd_soc_dapm_new_controls(codec, tosa_dapm_widgets,
+				  ARRAY_SIZE(tosa_dapm_widgets));
+
+	/* set up tosa specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_sync(codec);
+	return 0;
+}
+
+static struct snd_soc_dai_link tosa_dai[] = {
+{
+	.name = "AC97",
+	.stream_name = "AC97 HiFi",
+	.cpu_dai_name = "pxa-ac97.0",
+	.codec_dai_name = "wm9712-hifi",
+	.platform_name = "pxa-pcm-audio",
+	.codec_name = "wm9712-codec",
+	.init = tosa_ac97_init,
+	.ops = &tosa_ops,
+},
+{
+	.name = "AC97 Aux",
+	.stream_name = "AC97 Aux",
+	.cpu_dai_name = "pxa-ac97.1",
+	.codec_dai_name = "wm9712-aux",
+	.platform_name = "pxa-pcm-audio",
+	.codec_name = "wm9712-codec",
+	.ops = &tosa_ops,
+},
+};
+
+static int tosa_probe(struct platform_device *dev)
+{
+	int ret;
+
+	ret = gpio_request(TOSA_GPIO_L_MUTE, "Headphone Jack");
+	if (ret)
+		return ret;
+	ret = gpio_direction_output(TOSA_GPIO_L_MUTE, 0);
+	if (ret)
+		gpio_free(TOSA_GPIO_L_MUTE);
+
+	return ret;
+}
+
+static int tosa_remove(struct platform_device *dev)
+{
+	gpio_free(TOSA_GPIO_L_MUTE);
+	return 0;
+}
+
+static struct snd_soc_card tosa = {
+	.name = "Tosa",
+	.dai_link = tosa_dai,
+	.num_links = ARRAY_SIZE(tosa_dai),
+	.probe = tosa_probe,
+	.remove = tosa_remove,
+};
+
+static struct platform_device *tosa_snd_device;
+
+static int __init tosa_init(void)
+{
+	int ret;
+
+	if (!machine_is_tosa())
+		return -ENODEV;
+
+	tosa_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!tosa_snd_device) {
+		ret = -ENOMEM;
+		goto err_alloc;
+	}
+
+	platform_set_drvdata(tosa_snd_device, &tosa);
+	ret = platform_device_add(tosa_snd_device);
+
+	if (!ret)
+		return 0;
+
+	platform_device_put(tosa_snd_device);
+
+err_alloc:
+	return ret;
+}
+
+static void __exit tosa_exit(void)
+{
+	platform_device_unregister(tosa_snd_device);
+}
+
+module_init(tosa_init);
+module_exit(tosa_exit);
+
+/* Module information */
+MODULE_AUTHOR("Richard Purdie");
+MODULE_DESCRIPTION("ALSA SoC Tosa");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/z2.c b/linux/sound/soc/pxa/z2.c
new file mode 100644
index 0000000..4cc841b
--- /dev/null
+++ b/linux/sound/soc/pxa/z2.c
@@ -0,0 +1,240 @@
+/*
+ * linux/sound/soc/pxa/z2.c
+ *
+ * SoC Audio driver for Aeronix Zipit Z2
+ *
+ * Copyright (C) 2009 Ken McGuire <kenm@desertweyr.com>
+ * Copyright (C) 2010 Marek Vasut <marek.vasut@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/audio.h>
+#include <mach/z2.h>
+
+#include "../codecs/wm8750.h"
+#include "pxa2xx-i2s.h"
+
+static struct snd_soc_card snd_soc_z2;
+
+static int z2_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int clk = 0;
+	int ret = 0;
+
+	switch (params_rate(params)) {
+	case 8000:
+	case 16000:
+	case 48000:
+	case 96000:
+		clk = 12288000;
+		break;
+	case 11025:
+	case 22050:
+	case 44100:
+		clk = 11289600;
+		break;
+	}
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set the I2S system clock as input (unused) */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_jack hs_jack;
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+	{
+		.pin	= "Mic Jack",
+		.mask	= SND_JACK_MICROPHONE,
+	},
+	{
+		.pin	= "Headphone Jack",
+		.mask	= SND_JACK_HEADPHONE,
+	},
+};
+
+/* Headset jack detection gpios */
+static struct snd_soc_jack_gpio hs_jack_gpios[] = {
+	{
+		.gpio		= GPIO37_ZIPITZ2_HEADSET_DETECT,
+		.name		= "hsdet-gpio",
+		.report		= SND_JACK_HEADSET,
+		.debounce_time	= 200,
+	},
+};
+
+/* z2 machine dapm widgets */
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+
+	/* headset is a mic and mono headphone */
+	SND_SOC_DAPM_HP("Headset Jack", NULL),
+};
+
+/* Z2 machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+	/* headphone connected to LOUT1, ROUT1 */
+	{"Headphone Jack", NULL, "LOUT1"},
+	{"Headphone Jack", NULL, "ROUT1"},
+
+	/* ext speaker connected to LOUT2, ROUT2  */
+	{"Ext Spk", NULL , "ROUT2"},
+	{"Ext Spk", NULL , "LOUT2"},
+
+	/* mic is connected to R input 2 - with bias */
+	{"RINPUT2", NULL, "Mic Bias"},
+	{"Mic Bias", NULL, "Mic Jack"},
+};
+
+/*
+ * Logic for a wm8750 as connected on a Z2 Device
+ */
+static int z2_wm8750_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int ret;
+
+	/* NC codec pins */
+	snd_soc_dapm_disable_pin(codec, "LINPUT3");
+	snd_soc_dapm_disable_pin(codec, "RINPUT3");
+	snd_soc_dapm_disable_pin(codec, "OUT3");
+	snd_soc_dapm_disable_pin(codec, "MONO");
+
+	/* Add z2 specific widgets */
+	snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets,
+				 ARRAY_SIZE(wm8750_dapm_widgets));
+
+	/* Set up z2 specific audio paths */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	ret = snd_soc_dapm_sync(codec);
+	if (ret)
+		goto err;
+
+	/* Jack detection API stuff */
+	ret = snd_soc_jack_new(codec, "Headset Jack", SND_JACK_HEADSET,
+				&hs_jack);
+	if (ret)
+		goto err;
+
+	ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+				hs_jack_pins);
+	if (ret)
+		goto err;
+
+	ret = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios),
+				hs_jack_gpios);
+	if (ret)
+		goto err;
+
+	return 0;
+
+err:
+	return ret;
+}
+
+static struct snd_soc_ops z2_ops = {
+	.hw_params = z2_hw_params,
+};
+
+/* z2 digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link z2_dai = {
+	.name		= "wm8750",
+	.stream_name	= "WM8750",
+	.cpu_dai_name	= "pxa2xx-i2s",
+	.codec_dai_name	= "wm8750-hifi",
+	.platform_name = "pxa-pcm-audio",
+	.codec_name	= "wm8750-codec.0-001a",
+	.init		= z2_wm8750_init,
+	.ops		= &z2_ops,
+};
+
+/* z2 audio machine driver */
+static struct snd_soc_card snd_soc_z2 = {
+	.name		= "Z2",
+	.dai_link	= &z2_dai,
+	.num_links	= 1,
+};
+
+static struct platform_device *z2_snd_device;
+
+static int __init z2_init(void)
+{
+	int ret;
+
+	if (!machine_is_zipit2())
+		return -ENODEV;
+
+	z2_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!z2_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(z2_snd_device, &snd_soc_z2);
+	ret = platform_device_add(z2_snd_device);
+
+	if (ret)
+		platform_device_put(z2_snd_device);
+
+	return ret;
+}
+
+static void __exit z2_exit(void)
+{
+	platform_device_unregister(z2_snd_device);
+}
+
+module_init(z2_init);
+module_exit(z2_exit);
+
+MODULE_AUTHOR("Ken McGuire <kenm@desertweyr.com>, "
+		"Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC ZipitZ2");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/pxa/zylonite.c b/linux/sound/soc/pxa/zylonite.c
new file mode 100644
index 0000000..d27e05a
--- /dev/null
+++ b/linux/sound/soc/pxa/zylonite.c
@@ -0,0 +1,292 @@
+/*
+ * zylonite.c  --  SoC audio for Zylonite
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "../codecs/wm9713.h"
+#include "pxa2xx-ac97.h"
+#include "pxa-ssp.h"
+
+/*
+ * There is a physical switch SW15 on the board which changes the MCLK
+ * for the WM9713 between the standard AC97 master clock and the
+ * output of the CLK_POUT signal from the PXA.
+ */
+static int clk_pout;
+module_param(clk_pout, int, 0);
+MODULE_PARM_DESC(clk_pout, "Use CLK_POUT as WM9713 MCLK (SW15 on board).");
+
+static struct clk *pout;
+
+static struct snd_soc_card zylonite;
+
+static const struct snd_soc_dapm_widget zylonite_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone", NULL),
+	SND_SOC_DAPM_MIC("Headset Microphone", NULL),
+	SND_SOC_DAPM_MIC("Handset Microphone", NULL),
+	SND_SOC_DAPM_SPK("Multiactor", NULL),
+	SND_SOC_DAPM_SPK("Headset Earpiece", NULL),
+};
+
+/* Currently supported audio map */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+	/* Headphone output connected to HPL/HPR */
+	{ "Headphone", NULL,  "HPL" },
+	{ "Headphone", NULL,  "HPR" },
+
+	/* On-board earpiece */
+	{ "Headset Earpiece", NULL, "OUT3" },
+
+	/* Headphone mic */
+	{ "MIC2A", NULL, "Mic Bias" },
+	{ "Mic Bias", NULL, "Headset Microphone" },
+
+	/* On-board mic */
+	{ "MIC1", NULL, "Mic Bias" },
+	{ "Mic Bias", NULL, "Handset Microphone" },
+
+	/* Multiactor differentially connected over SPKL/SPKR */
+	{ "Multiactor", NULL, "SPKL" },
+	{ "Multiactor", NULL, "SPKR" },
+};
+
+static int zylonite_wm9713_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	if (clk_pout)
+		snd_soc_dai_set_pll(rtd->codec_dai, 0, 0,
+				    clk_get_rate(pout), 0);
+
+	snd_soc_dapm_new_controls(codec, zylonite_dapm_widgets,
+				  ARRAY_SIZE(zylonite_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	/* Static setup for now */
+	snd_soc_dapm_enable_pin(codec, "Headphone");
+	snd_soc_dapm_enable_pin(codec, "Headset Earpiece");
+
+	snd_soc_dapm_sync(codec);
+	return 0;
+}
+
+static int zylonite_voice_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int pll_out = 0;
+	unsigned int wm9713_div = 0;
+	int ret = 0;
+	int rate = params_rate(params);
+	int width = snd_pcm_format_physical_width(params_format(params));
+
+	/* Only support ratios that we can generate neatly from the AC97
+	 * based master clock - in particular, this excludes 44.1kHz.
+	 * In most applications the voice DAC will be used for telephony
+	 * data so multiples of 8kHz will be the common case.
+	 */
+	switch (rate) {
+	case 8000:
+		wm9713_div = 12;
+		break;
+	case 16000:
+		wm9713_div = 6;
+		break;
+	case 48000:
+		wm9713_div = 2;
+		break;
+	default:
+		/* Don't support OSS emulation */
+		return -EINVAL;
+	}
+
+	/* Add 1 to the width for the leading clock cycle */
+	pll_out = rate * (width + 1) * 8;
+
+	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_AUDIO, 0, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, pll_out);
+	if (ret < 0)
+		return ret;
+
+	if (clk_pout)
+		ret = snd_soc_dai_set_clkdiv(codec_dai, WM9713_PCMCLK_PLL_DIV,
+					     WM9713_PCMDIV(wm9713_div));
+	else
+		ret = snd_soc_dai_set_clkdiv(codec_dai, WM9713_PCMCLK_DIV,
+					     WM9713_PCMDIV(wm9713_div));
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops zylonite_voice_ops = {
+	.hw_params = zylonite_voice_hw_params,
+};
+
+static struct snd_soc_dai_link zylonite_dai[] = {
+{
+	.name = "AC97",
+	.stream_name = "AC97 HiFi",
+	.codec_name = "wm9713-codec",
+	.platform_name = "pxa-pcm-audio",
+	.cpu_dai_name = "pxa-ac97.0",
+	.codec_name = "wm9713-hifi",
+	.init = zylonite_wm9713_init,
+},
+{
+	.name = "AC97 Aux",
+	.stream_name = "AC97 Aux",
+	.codec_name = "wm9713-codec",
+	.platform_name = "pxa-pcm-audio",
+	.cpu_dai_name = "pxa-ac97.1",
+	.codec_name = "wm9713-aux",
+},
+{
+	.name = "WM9713 Voice",
+	.stream_name = "WM9713 Voice",
+	.codec_name = "wm9713-codec",
+	.platform_name = "pxa-pcm-audio",
+	.cpu_dai_name = "pxa-ssp-dai.2",
+	.codec_name = "wm9713-voice",
+	.ops = &zylonite_voice_ops,
+},
+};
+
+static int zylonite_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	if (clk_pout) {
+		pout = clk_get(NULL, "CLK_POUT");
+		if (IS_ERR(pout)) {
+			dev_err(&pdev->dev, "Unable to obtain CLK_POUT: %ld\n",
+				PTR_ERR(pout));
+			return PTR_ERR(pout);
+		}
+
+		ret = clk_enable(pout);
+		if (ret != 0) {
+			dev_err(&pdev->dev, "Unable to enable CLK_POUT: %d\n",
+				ret);
+			clk_put(pout);
+			return ret;
+		}
+
+		dev_dbg(&pdev->dev, "MCLK enabled at %luHz\n",
+			clk_get_rate(pout));
+	}
+
+	return 0;
+}
+
+static int zylonite_remove(struct platform_device *pdev)
+{
+	if (clk_pout) {
+		clk_disable(pout);
+		clk_put(pout);
+	}
+
+	return 0;
+}
+
+static int zylonite_suspend_post(struct platform_device *pdev,
+				 pm_message_t state)
+{
+	if (clk_pout)
+		clk_disable(pout);
+
+	return 0;
+}
+
+static int zylonite_resume_pre(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	if (clk_pout) {
+		ret = clk_enable(pout);
+		if (ret != 0)
+			dev_err(&pdev->dev, "Unable to enable CLK_POUT: %d\n",
+				ret);
+	}
+
+	return ret;
+}
+
+static struct snd_soc_card zylonite = {
+	.name = "Zylonite",
+	.probe = &zylonite_probe,
+	.remove = &zylonite_remove,
+	.suspend_post = &zylonite_suspend_post,
+	.resume_pre = &zylonite_resume_pre,
+	.dai_link = zylonite_dai,
+	.num_links = ARRAY_SIZE(zylonite_dai),
+	.owner = THIS_MODULE,
+};
+
+static struct platform_device *zylonite_snd_ac97_device;
+
+static int __init zylonite_init(void)
+{
+	int ret;
+
+	zylonite_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+	if (!zylonite_snd_ac97_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(zylonite_snd_ac97_device, &zylonite);
+
+	ret = platform_device_add(zylonite_snd_ac97_device);
+	if (ret != 0)
+		platform_device_put(zylonite_snd_ac97_device);
+
+	return ret;
+}
+
+static void __exit zylonite_exit(void)
+{
+	platform_device_unregister(zylonite_snd_ac97_device);
+}
+
+module_init(zylonite_init);
+module_exit(zylonite_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("ALSA SoC WM9713 Zylonite");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/Kconfig b/linux/sound/soc/s3c24xx/Kconfig
new file mode 100644
index 0000000..d85bf8a
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/Kconfig
@@ -0,0 +1,171 @@
+config SND_S3C24XX_SOC
+	tristate "SoC Audio for the Samsung S3CXXXX chips"
+	depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210
+	select S3C64XX_DMA if ARCH_S3C64XX
+	select S3C2410_DMA if ARCH_S3C2410
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the S3C24XX AC97 or I2S interfaces. You will also need to
+	  select the audio interfaces to support below.
+
+config SND_S3C24XX_SOC_I2S
+	tristate
+	select S3C2410_DMA
+
+config SND_S3C_I2SV2_SOC
+	tristate
+
+config SND_S3C2412_SOC_I2S
+	tristate
+	select SND_S3C_I2SV2_SOC
+	select S3C2410_DMA
+
+config SND_S3C64XX_SOC_I2S
+	tristate
+	select SND_S3C_I2SV2_SOC
+	select S3C64XX_DMA
+
+config SND_S3C64XX_SOC_I2S_V4
+	tristate
+	select SND_S3C_I2SV2_SOC
+	select S3C64XX_DMA
+
+config SND_S3C_SOC_PCM
+	tristate
+
+config SND_S3C_SOC_AC97
+	tristate
+	select SND_SOC_AC97_BUS
+
+config SND_S5P_SOC_SPDIF
+	tristate
+	select SND_SOC_SPDIF
+
+config SND_S3C24XX_SOC_NEO1973_WM8753
+	tristate "SoC I2S Audio support for NEO1973 - WM8753"
+	depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA01
+	select SND_S3C24XX_SOC_I2S
+	select SND_SOC_WM8753
+	help
+	  Say Y if you want to add support for SoC audio on smdk2440
+	  with the WM8753.
+
+config SND_S3C24XX_SOC_NEO1973_GTA02_WM8753
+	tristate "Audio support for the Openmoko Neo FreeRunner (GTA02)"
+	depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA02
+	select SND_S3C24XX_SOC_I2S
+	select SND_SOC_WM8753
+	help
+	  This driver provides audio support for the Openmoko Neo FreeRunner
+	  smartphone.
+	  
+config SND_S3C24XX_SOC_JIVE_WM8750
+	tristate "SoC I2S Audio support for Jive"
+	depends on SND_S3C24XX_SOC && MACH_JIVE
+	select SND_SOC_WM8750
+	select SND_S3C2412_SOC_I2S
+	help
+	  Sat Y if you want to add support for SoC audio on the Jive.
+
+config SND_S3C64XX_SOC_WM8580
+	tristate "SoC I2S Audio support for WM8580 on SMDK64XX"
+	depends on SND_S3C24XX_SOC && MACH_SMDK6410
+	select SND_SOC_WM8580
+	select SND_S3C64XX_SOC_I2S_V4
+	help
+	  Say Y if you want to add support for SoC audio on the SMDK6410.
+
+config SND_S3C24XX_SOC_SMDK2443_WM9710
+	tristate "SoC AC97 Audio support for SMDK2443 - WM9710"
+	depends on SND_S3C24XX_SOC && MACH_SMDK2443
+	select S3C2410_DMA
+	select AC97_BUS
+	select SND_SOC_AC97_CODEC
+	select SND_S3C_SOC_AC97
+	help
+	  Say Y if you want to add support for SoC audio on smdk2443
+	  with the WM9710.
+
+config SND_S3C24XX_SOC_LN2440SBC_ALC650
+	tristate "SoC AC97 Audio support for LN2440SBC - ALC650"
+	depends on SND_S3C24XX_SOC && ARCH_S3C2410
+	select S3C2410_DMA
+	select AC97_BUS
+	select SND_SOC_AC97_CODEC
+	select SND_S3C_SOC_AC97
+	help
+	  Say Y if you want to add support for SoC audio on ln2440sbc
+	  with the ALC650.
+
+config SND_S3C24XX_SOC_S3C24XX_UDA134X
+	tristate "SoC I2S Audio support UDA134X wired to a S3C24XX"
+       	depends on SND_S3C24XX_SOC && ARCH_S3C2410
+       	select SND_S3C24XX_SOC_I2S
+	select SND_SOC_L3
+       	select SND_SOC_UDA134X
+
+config SND_S3C24XX_SOC_SIMTEC
+	tristate
+	help
+	  Internal node for common S3C24XX/Simtec suppor
+
+config SND_S3C24XX_SOC_SIMTEC_TLV320AIC23
+	tristate "SoC I2S Audio support for TLV320AIC23 on Simtec boards"
+	depends on SND_S3C24XX_SOC && ARCH_S3C2410
+	select SND_S3C24XX_SOC_I2S
+	select SND_SOC_TLV320AIC23
+	select SND_S3C24XX_SOC_SIMTEC
+
+config SND_S3C24XX_SOC_SIMTEC_HERMES
+	tristate "SoC I2S Audio support for Simtec Hermes board"
+	depends on SND_S3C24XX_SOC && ARCH_S3C2410
+	select SND_S3C24XX_SOC_I2S
+	select SND_SOC_TLV320AIC3X
+	select SND_S3C24XX_SOC_SIMTEC
+
+config SND_S3C24XX_SOC_RX1950_UDA1380
+	tristate "Audio support for the HP iPAQ RX1950"
+	depends on SND_S3C24XX_SOC && MACH_RX1950
+	select SND_S3C24XX_SOC_I2S
+	select SND_SOC_UDA1380
+	help
+	  This driver provides audio support for HP iPAQ RX1950 PDA.
+
+config SND_SOC_SMDK_WM9713
+	tristate "SoC AC97 Audio support for SMDK with WM9713"
+	depends on SND_S3C24XX_SOC && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDKV210 || MACH_SMDKC110)
+	select SND_SOC_WM9713
+	select SND_S3C_SOC_AC97
+	help
+	  Sat Y if you want to add support for SoC audio on the SMDK.
+
+config SND_S3C64XX_SOC_SMARTQ
+	tristate "SoC I2S Audio support for SmartQ board"
+	depends on SND_S3C24XX_SOC && MACH_SMARTQ
+	select SND_S3C64XX_SOC_I2S
+	select SND_SOC_WM8750
+
+config SND_S5PC110_SOC_AQUILA_WM8994
+	tristate "SoC I2S Audio support for AQUILA - WM8994"
+	depends on SND_S3C24XX_SOC && MACH_AQUILA
+	select SND_S3C64XX_SOC_I2S_V4
+	select SND_SOC_WM8994
+	help
+	  Say Y if you want to add support for SoC audio on aquila
+	  with the WM8994.
+
+config SND_S5PV210_SOC_GONI_WM8994
+	tristate "SoC I2S Audio support for GONI - WM8994"
+	depends on SND_S3C24XX_SOC && MACH_GONI
+	select SND_S3C64XX_SOC_I2S_V4
+	select SND_SOC_WM8994
+	help
+	  Say Y if you want to add support for SoC audio on goni
+	  with the WM8994.
+
+config SND_SOC_SMDK_SPDIF
+	tristate "SoC S/PDIF Audio support for SMDK"
+	depends on SND_S3C24XX_SOC && (MACH_SMDKC100 || MACH_SMDKC110 || MACH_SMDKV210)
+	select SND_S5P_SOC_SPDIF
+	help
+	  Say Y if you want to add support for SoC S/PDIF audio on the SMDK.
diff --git a/linux/sound/soc/s3c24xx/Makefile b/linux/sound/soc/s3c24xx/Makefile
new file mode 100644
index 0000000..ee8f41d
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/Makefile
@@ -0,0 +1,55 @@
+# S3c24XX Platform Support
+snd-soc-s3c24xx-objs := s3c-dma.o
+snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o
+snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o
+snd-soc-s3c64xx-i2s-objs := s3c64xx-i2s.o
+snd-soc-s3c-ac97-objs := s3c-ac97.o
+snd-soc-s3c64xx-i2s-v4-objs := s3c64xx-i2s-v4.o
+snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o
+snd-soc-s3c-pcm-objs := s3c-pcm.o
+snd-soc-samsung-spdif-objs := spdif.o
+
+obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o
+obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o
+obj-$(CONFIG_SND_S3C_SOC_AC97) += snd-soc-s3c-ac97.o
+obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o
+obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += snd-soc-s3c64xx-i2s.o
+obj-$(CONFIG_SND_S3C64XX_SOC_I2S_V4) += snd-soc-s3c64xx-i2s-v4.o
+obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o
+obj-$(CONFIG_SND_S3C_SOC_PCM) += snd-soc-s3c-pcm.o
+obj-$(CONFIG_SND_S5P_SOC_SPDIF) += snd-soc-samsung-spdif.o
+
+# S3C24XX Machine Support
+snd-soc-jive-wm8750-objs := jive_wm8750.o
+snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o
+snd-soc-neo1973-gta02-wm8753-objs := neo1973_gta02_wm8753.o
+snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o
+snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o
+snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o
+snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o
+snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o
+snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o
+snd-soc-rx1950-uda1380-objs := rx1950_uda1380.o
+snd-soc-smdk64xx-wm8580-objs := smdk64xx_wm8580.o
+snd-soc-smdk-wm9713-objs := smdk_wm9713.o
+snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o
+snd-soc-aquila-wm8994-objs := aquila_wm8994.o
+snd-soc-goni-wm8994-objs := goni_wm8994.o
+snd-soc-smdk-spdif-objs := smdk_spdif.o
+
+obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o
+obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
+obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_GTA02_WM8753) += snd-soc-neo1973-gta02-wm8753.o
+obj-$(CONFIG_SND_S3C24XX_SOC_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o
+obj-$(CONFIG_SND_S3C24XX_SOC_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o
+obj-$(CONFIG_SND_S3C24XX_SOC_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o
+obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC) += snd-soc-s3c24xx-simtec.o
+obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o
+obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o
+obj-$(CONFIG_SND_S3C24XX_SOC_RX1950_UDA1380) += snd-soc-rx1950-uda1380.o
+obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o
+obj-$(CONFIG_SND_SOC_SMDK_WM9713) += snd-soc-smdk-wm9713.o
+obj-$(CONFIG_SND_S3C64XX_SOC_SMARTQ) += snd-soc-s3c64xx-smartq-wm8987.o
+obj-$(CONFIG_SND_S5PC110_SOC_AQUILA_WM8994) += snd-soc-aquila-wm8994.o
+obj-$(CONFIG_SND_S5PV210_SOC_GONI_WM8994) += snd-soc-goni-wm8994.o
+obj-$(CONFIG_SND_SOC_SMDK_SPDIF) += snd-soc-smdk-spdif.o
diff --git a/linux/sound/soc/s3c24xx/aquila_wm8994.c b/linux/sound/soc/s3c24xx/aquila_wm8994.c
new file mode 100644
index 0000000..235d197
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/aquila_wm8994.c
@@ -0,0 +1,295 @@
+/*
+ * aquila_wm8994.c
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ * Author: Chanwoo Choi <cw00.choi@samsung.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include <asm/mach-types.h>
+#include <mach/gpio.h>
+#include <mach/regs-clock.h>
+
+#include <linux/mfd/wm8994/core.h>
+#include <linux/mfd/wm8994/registers.h>
+#include "../codecs/wm8994.h"
+#include "s3c-dma.h"
+#include "s3c64xx-i2s.h"
+
+static struct snd_soc_card aquila;
+static struct platform_device *aquila_snd_device;
+
+/* 3.5 pie jack */
+static struct snd_soc_jack jack;
+
+/* 3.5 pie jack detection DAPM pins */
+static struct snd_soc_jack_pin jack_pins[] = {
+	{
+		.pin = "Headset Mic",
+		.mask = SND_JACK_MICROPHONE,
+	}, {
+		.pin = "Headset Stereophone",
+		.mask = SND_JACK_HEADPHONE | SND_JACK_MECHANICAL |
+			SND_JACK_AVOUT,
+	},
+};
+
+/* 3.5 pie jack detection gpios */
+static struct snd_soc_jack_gpio jack_gpios[] = {
+	{
+		.gpio = S5PV210_GPH0(6),
+		.name = "DET_3.5",
+		.report = SND_JACK_HEADSET | SND_JACK_MECHANICAL |
+			SND_JACK_AVOUT,
+		.debounce_time = 200,
+	},
+};
+
+static const struct snd_soc_dapm_widget aquila_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+	SND_SOC_DAPM_SPK("Ext Rcv", NULL),
+	SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_MIC("Main Mic", NULL),
+	SND_SOC_DAPM_MIC("2nd Mic", NULL),
+	SND_SOC_DAPM_LINE("Radio In", NULL),
+};
+
+static const struct snd_soc_dapm_route aquila_dapm_routes[] = {
+	{"Ext Spk", NULL, "SPKOUTLP"},
+	{"Ext Spk", NULL, "SPKOUTLN"},
+
+	{"Ext Rcv", NULL, "HPOUT2N"},
+	{"Ext Rcv", NULL, "HPOUT2P"},
+
+	{"Headset Stereophone", NULL, "HPOUT1L"},
+	{"Headset Stereophone", NULL, "HPOUT1R"},
+
+	{"IN1RN", NULL, "Headset Mic"},
+	{"IN1RP", NULL, "Headset Mic"},
+
+	{"IN1RN", NULL, "2nd Mic"},
+	{"IN1RP", NULL, "2nd Mic"},
+
+	{"IN1LN", NULL, "Main Mic"},
+	{"IN1LP", NULL, "Main Mic"},
+
+	{"IN2LN", NULL, "Radio In"},
+	{"IN2RN", NULL, "Radio In"},
+};
+
+static int aquila_wm8994_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int ret;
+
+	/* add aquila specific widgets */
+	snd_soc_dapm_new_controls(codec, aquila_dapm_widgets,
+			ARRAY_SIZE(aquila_dapm_widgets));
+
+	/* set up aquila specific audio routes */
+	snd_soc_dapm_add_routes(codec, aquila_dapm_routes,
+			ARRAY_SIZE(aquila_dapm_routes));
+
+	/* set endpoints to not connected */
+	snd_soc_dapm_nc_pin(codec, "IN2LP:VXRN");
+	snd_soc_dapm_nc_pin(codec, "IN2RP:VXRP");
+	snd_soc_dapm_nc_pin(codec, "LINEOUT1N");
+	snd_soc_dapm_nc_pin(codec, "LINEOUT1P");
+	snd_soc_dapm_nc_pin(codec, "LINEOUT2N");
+	snd_soc_dapm_nc_pin(codec, "LINEOUT2P");
+	snd_soc_dapm_nc_pin(codec, "SPKOUTRN");
+	snd_soc_dapm_nc_pin(codec, "SPKOUTRP");
+
+	snd_soc_dapm_sync(codec);
+
+	/* Headset jack detection */
+	ret = snd_soc_jack_new(&aquila, "Headset Jack",
+			SND_JACK_HEADSET | SND_JACK_MECHANICAL | SND_JACK_AVOUT,
+			&jack);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_jack_add_pins(&jack, ARRAY_SIZE(jack_pins), jack_pins);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_jack_add_gpios(&jack, ARRAY_SIZE(jack_gpios), jack_gpios);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int aquila_hifi_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int pll_out = 24000000;
+	int ret = 0;
+
+	/* set the cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set the cpu system clock */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, S3C64XX_CLKSRC_PCLK,
+			0, SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec FLL */
+	ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, pll_out,
+			params_rate(params) * 256);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
+			params_rate(params) * 256, SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops aquila_hifi_ops = {
+	.hw_params = aquila_hifi_hw_params,
+};
+
+static int aquila_voice_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	unsigned int pll_out = 24000000;
+	int ret = 0;
+
+	if (params_rate(params) != 8000)
+		return -EINVAL;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_LEFT_J |
+			SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec FLL */
+	ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, 0, pll_out,
+			params_rate(params) * 256);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2,
+			params_rate(params) * 256, SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver voice_dai = {
+	.name = "aquila-voice-dai",
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+};
+
+static struct snd_soc_ops aquila_voice_ops = {
+	.hw_params = aquila_voice_hw_params,
+};
+
+static struct snd_soc_dai_link aquila_dai[] = {
+{
+	.name = "WM8994",
+	.stream_name = "WM8994 HiFi",
+	.cpu_dai_name = "s3c64xx-i2s-v4",
+	.codec_dai_name = "wm8994-hifi",
+	.platform_name = "s3c24xx-pcm-audio",
+	.codec_name = "wm8994-codec.0-0x1a",
+	.init = aquila_wm8994_init,
+	.ops = &aquila_hifi_ops,
+}, {
+	.name = "WM8994 Voice",
+	.stream_name = "Voice",
+	.cpu_dai_name = "aquila-voice-dai",
+	.codec_dai_name = "wm8994-voice",
+	.platform_name = "s3c24xx-pcm-audio",
+	.codec_name = "wm8994-codec.0-0x1a",
+	.ops = &aquila_voice_ops,
+},
+};
+
+static struct snd_soc_card aquila = {
+	.name = "aquila",
+	.dai_link = aquila_dai,
+	.num_links = ARRAY_SIZE(aquila_dai),
+};
+
+static int __init aquila_init(void)
+{
+	int ret;
+
+	if (!machine_is_aquila())
+		return -ENODEV;
+
+	aquila_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!aquila_snd_device)
+		return -ENOMEM;
+
+	/* register voice DAI here */
+	ret = snd_soc_register_dai(&aquila_snd_device->dev, &voice_dai);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(aquila_snd_device, &aquila);
+	ret = platform_device_add(aquila_snd_device);
+
+	if (ret)
+		platform_device_put(aquila_snd_device);
+
+	return ret;
+}
+
+static void __exit aquila_exit(void)
+{
+	platform_device_unregister(aquila_snd_device);
+}
+
+module_init(aquila_init);
+module_exit(aquila_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC WM8994 Aquila(S5PC110)");
+MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/goni_wm8994.c b/linux/sound/soc/s3c24xx/goni_wm8994.c
new file mode 100644
index 0000000..694f702
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/goni_wm8994.c
@@ -0,0 +1,298 @@
+/*
+ * goni_wm8994.c
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ * Author: Chanwoo Choi <cw00.choi@samsung.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include <asm/mach-types.h>
+#include <mach/gpio.h>
+#include <mach/regs-clock.h>
+
+#include <linux/mfd/wm8994/core.h>
+#include <linux/mfd/wm8994/registers.h>
+#include "../codecs/wm8994.h"
+#include "s3c-dma.h"
+#include "s3c64xx-i2s.h"
+
+static struct snd_soc_card goni;
+static struct platform_device *goni_snd_device;
+
+/* 3.5 pie jack */
+static struct snd_soc_jack jack;
+
+/* 3.5 pie jack detection DAPM pins */
+static struct snd_soc_jack_pin jack_pins[] = {
+	{
+		.pin = "Headset Mic",
+		.mask = SND_JACK_MICROPHONE,
+	}, {
+		.pin = "Headset Stereophone",
+		.mask = SND_JACK_HEADPHONE | SND_JACK_MECHANICAL |
+			SND_JACK_AVOUT,
+	},
+};
+
+/* 3.5 pie jack detection gpios */
+static struct snd_soc_jack_gpio jack_gpios[] = {
+	{
+		.gpio = S5PV210_GPH0(6),
+		.name = "DET_3.5",
+		.report = SND_JACK_HEADSET | SND_JACK_MECHANICAL |
+			SND_JACK_AVOUT,
+		.debounce_time = 200,
+	},
+};
+
+static const struct snd_soc_dapm_widget goni_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Ext Left Spk", NULL),
+	SND_SOC_DAPM_SPK("Ext Right Spk", NULL),
+	SND_SOC_DAPM_SPK("Ext Rcv", NULL),
+	SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_MIC("Main Mic", NULL),
+	SND_SOC_DAPM_MIC("2nd Mic", NULL),
+	SND_SOC_DAPM_LINE("Radio In", NULL),
+};
+
+static const struct snd_soc_dapm_route goni_dapm_routes[] = {
+	{"Ext Left Spk", NULL, "SPKOUTLP"},
+	{"Ext Left Spk", NULL, "SPKOUTLN"},
+
+	{"Ext Right Spk", NULL, "SPKOUTRP"},
+	{"Ext Right Spk", NULL, "SPKOUTRN"},
+
+	{"Ext Rcv", NULL, "HPOUT2N"},
+	{"Ext Rcv", NULL, "HPOUT2P"},
+
+	{"Headset Stereophone", NULL, "HPOUT1L"},
+	{"Headset Stereophone", NULL, "HPOUT1R"},
+
+	{"IN1RN", NULL, "Headset Mic"},
+	{"IN1RP", NULL, "Headset Mic"},
+
+	{"IN1RN", NULL, "2nd Mic"},
+	{"IN1RP", NULL, "2nd Mic"},
+
+	{"IN1LN", NULL, "Main Mic"},
+	{"IN1LP", NULL, "Main Mic"},
+
+	{"IN2LN", NULL, "Radio In"},
+	{"IN2RN", NULL, "Radio In"},
+};
+
+static int goni_wm8994_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int ret;
+
+	/* add goni specific widgets */
+	snd_soc_dapm_new_controls(codec, goni_dapm_widgets,
+			ARRAY_SIZE(goni_dapm_widgets));
+
+	/* set up goni specific audio routes */
+	snd_soc_dapm_add_routes(codec, goni_dapm_routes,
+			ARRAY_SIZE(goni_dapm_routes));
+
+	/* set endpoints to not connected */
+	snd_soc_dapm_nc_pin(codec, "IN2LP:VXRN");
+	snd_soc_dapm_nc_pin(codec, "IN2RP:VXRP");
+	snd_soc_dapm_nc_pin(codec, "LINEOUT1N");
+	snd_soc_dapm_nc_pin(codec, "LINEOUT1P");
+	snd_soc_dapm_nc_pin(codec, "LINEOUT2N");
+	snd_soc_dapm_nc_pin(codec, "LINEOUT2P");
+
+	snd_soc_dapm_sync(codec);
+
+	/* Headset jack detection */
+	ret = snd_soc_jack_new(&goni, "Headset Jack",
+			SND_JACK_HEADSET | SND_JACK_MECHANICAL | SND_JACK_AVOUT,
+			&jack);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_jack_add_pins(&jack, ARRAY_SIZE(jack_pins), jack_pins);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_jack_add_gpios(&jack, ARRAY_SIZE(jack_gpios), jack_gpios);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int goni_hifi_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int pll_out = 24000000;
+	int ret = 0;
+
+	/* set the cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set the cpu system clock */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, S3C64XX_CLKSRC_PCLK,
+			0, SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec FLL */
+	ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, pll_out,
+			params_rate(params) * 256);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
+			params_rate(params) * 256, SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops goni_hifi_ops = {
+	.hw_params = goni_hifi_hw_params,
+};
+
+static int goni_voice_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	unsigned int pll_out = 24000000;
+	int ret = 0;
+
+	if (params_rate(params) != 8000)
+		return -EINVAL;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_LEFT_J |
+			SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec FLL */
+	ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, 0, pll_out,
+			params_rate(params) * 256);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2,
+			params_rate(params) * 256, SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver voice_dai = {
+	.name = "goni-voice-dai",
+	.id = 0,
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+};
+
+static struct snd_soc_ops goni_voice_ops = {
+	.hw_params = goni_voice_hw_params,
+};
+
+static struct snd_soc_dai_link goni_dai[] = {
+{
+	.name = "WM8994",
+	.stream_name = "WM8994 HiFi",
+	.cpu_dai_name = "s3c64xx-i2s-v4",
+	.codec_dai_name = "wm8994-hifi",
+	.platform_name = "s3c24xx-pcm-audio",
+	.codec_name = "wm8994-codec.0-0x1a",
+	.init = goni_wm8994_init,
+	.ops = &goni_hifi_ops,
+}, {
+	.name = "WM8994 Voice",
+	.stream_name = "Voice",
+	.cpu_dai_name = "goni-voice-dai",
+	.codec_dai_name = "wm8994-voice",
+	.platform_name = "s3c24xx-pcm-audio",
+	.codec_name = "wm8994-codec.0-0x1a",
+	.ops = &goni_voice_ops,
+},
+};
+
+static struct snd_soc_card goni = {
+	.name = "goni",
+	.dai_link = goni_dai,
+	.num_links = ARRAY_SIZE(goni_dai),
+};
+
+static int __init goni_init(void)
+{
+	int ret;
+
+	if (!machine_is_goni())
+		return -ENODEV;
+
+	goni_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!goni_snd_device)
+		return -ENOMEM;
+
+	/* register voice DAI here */
+	ret = snd_soc_register_dai(&goni_snd_device->dev, &voice_dai);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(goni_snd_device, &goni);
+	ret = platform_device_add(goni_snd_device);
+
+	if (ret)
+		platform_device_put(goni_snd_device);
+
+	return ret;
+}
+
+static void __exit goni_exit(void)
+{
+	platform_device_unregister(goni_snd_device);
+}
+
+module_init(goni_init);
+module_exit(goni_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC WM8994 GONI(S5PV210)");
+MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/jive_wm8750.c b/linux/sound/soc/s3c24xx/jive_wm8750.c
new file mode 100644
index 0000000..49605cd
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/jive_wm8750.c
@@ -0,0 +1,191 @@
+/* sound/soc/s3c24xx/jive_wm8750.c
+ *
+ * Copyright 2007,2008 Simtec Electronics
+ *
+ * Based on sound/soc/pxa/spitz.c
+ *	Copyright 2005 Wolfson Microelectronics PLC.
+ *	Copyright 2005 Openedhand Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+
+#include "s3c-dma.h"
+#include "s3c2412-i2s.h"
+
+#include "../codecs/wm8750.h"
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{ "Headphone Jack", NULL, "LOUT1" },
+	{ "Headphone Jack", NULL, "ROUT1" },
+	{ "Internal Speaker", NULL, "LOUT2" },
+	{ "Internal Speaker", NULL, "ROUT2" },
+	{ "LINPUT1", NULL, "Line Input" },
+	{ "RINPUT1", NULL, "Line Input" },
+};
+
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_SPK("Internal Speaker", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+static int jive_hw_params(struct snd_pcm_substream *substream,
+			  struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct s3c_i2sv2_rate_calc div;
+	unsigned int clk = 0;
+	int ret = 0;
+
+	switch (params_rate(params)) {
+	case 8000:
+	case 16000:
+	case 48000:
+	case 96000:
+		clk = 12288000;
+		break;
+	case 11025:
+	case 22050:
+	case 44100:
+		clk = 11289600;
+		break;
+	}
+
+	s3c_i2sv2_iis_calc_rate(&div, NULL, params_rate(params),
+				s3c_i2sv2_get_clock(cpu_dai));
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+				     SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_RCLK, div.fs_div);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_PRESCALER,
+				     div.clk_div - 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops jive_ops = {
+	.hw_params	= jive_hw_params,
+};
+
+static int jive_wm8750_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	/* These endpoints are not being used. */
+	snd_soc_dapm_nc_pin(codec, "LINPUT2");
+	snd_soc_dapm_nc_pin(codec, "RINPUT2");
+	snd_soc_dapm_nc_pin(codec, "LINPUT3");
+	snd_soc_dapm_nc_pin(codec, "RINPUT3");
+	snd_soc_dapm_nc_pin(codec, "OUT3");
+	snd_soc_dapm_nc_pin(codec, "MONO");
+
+	/* Add jive specific widgets */
+	err = snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets,
+					ARRAY_SIZE(wm8750_dapm_widgets));
+	if (err) {
+		printk(KERN_ERR "%s: failed to add widgets (%d)\n",
+		       __func__, err);
+		return err;
+	}
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_dai_link jive_dai = {
+	.name		= "wm8750",
+	.stream_name	= "WM8750",
+	.cpu_dai_name	= "s3c2412-i2s",
+	.codec_dai_name = "wm8750-hifi",
+	.platform_name	= "s3c24xx-pcm-audio",
+	.codec_name	= "wm8750-codec.0-0x1a",
+	.init		= jive_wm8750_init,
+	.ops		= &jive_ops,
+};
+
+/* jive audio machine driver */
+static struct snd_soc_card snd_soc_machine_jive = {
+	.name		= "Jive",
+	.dai_link	= &jive_dai,
+	.num_links	= 1,
+};
+
+static struct platform_device *jive_snd_device;
+
+static int __init jive_init(void)
+{
+	int ret;
+
+	if (!machine_is_jive())
+		return 0;
+
+	printk("JIVE WM8750 Audio support\n");
+
+	jive_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!jive_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(jive_snd_device, &snd_soc_machine_jive);
+	ret = platform_device_add(jive_snd_device);
+
+	if (ret)
+		platform_device_put(jive_snd_device);
+
+	return ret;
+}
+
+static void __exit jive_exit(void)
+{
+	platform_device_unregister(jive_snd_device);
+}
+
+module_init(jive_init);
+module_exit(jive_exit);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Jive Audio support");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/lm4857.h b/linux/sound/soc/s3c24xx/lm4857.h
new file mode 100644
index 0000000..0cf5b70
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/lm4857.h
@@ -0,0 +1,32 @@
+/*
+ * lm4857.h  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ *         graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ *  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.
+ *
+ *  Revision history
+ *    18th Jun 2007   Initial version.
+ */
+
+#ifndef LM4857_H_
+#define LM4857_H_
+
+/* The register offsets in the cache array */
+#define LM4857_MVOL 0
+#define LM4857_LVOL 1
+#define LM4857_RVOL 2
+#define LM4857_CTRL 3
+
+/* the shifts required to set these bits */
+#define LM4857_3D 5
+#define LM4857_WAKEUP 5
+#define LM4857_EPGAIN 4
+
+#endif /*LM4857_H_*/
+
diff --git a/linux/sound/soc/s3c24xx/ln2440sbc_alc650.c b/linux/sound/soc/s3c24xx/ln2440sbc_alc650.c
new file mode 100644
index 0000000..abe64ab
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/ln2440sbc_alc650.c
@@ -0,0 +1,78 @@
+/*
+ * SoC audio for ln2440sbc
+ *
+ * Copyright 2007 KonekTel, a.s.
+ * Author: Ivan Kuten
+ *         ivan.kuten@promwad.com
+ *
+ * Heavily based on smdk2443_wm9710.c
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ *         graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "s3c-dma.h"
+#include "s3c-ac97.h"
+
+static struct snd_soc_card ln2440sbc;
+
+static struct snd_soc_dai_link ln2440sbc_dai[] = {
+{
+	.name = "AC97",
+	.stream_name = "AC97 HiFi",
+	.cpu_dai_name = "s3c-ac97",
+	.codec_dai_name = "ac97-hifi",
+	.codec_name = "ac97-codec",
+	.platform_name = "s3c24xx-pcm-audio",
+},
+};
+
+static struct snd_soc_card ln2440sbc = {
+	.name = "LN2440SBC",
+	.dai_link = ln2440sbc_dai,
+	.num_links = ARRAY_SIZE(ln2440sbc_dai),
+};
+
+static struct platform_device *ln2440sbc_snd_ac97_device;
+
+static int __init ln2440sbc_init(void)
+{
+	int ret;
+
+	ln2440sbc_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+	if (!ln2440sbc_snd_ac97_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(ln2440sbc_snd_ac97_device, &ln2440sbc);
+	ret = platform_device_add(ln2440sbc_snd_ac97_device);
+
+	if (ret)
+		platform_device_put(ln2440sbc_snd_ac97_device);
+
+	return ret;
+}
+
+static void __exit ln2440sbc_exit(void)
+{
+	platform_device_unregister(ln2440sbc_snd_ac97_device);
+}
+
+module_init(ln2440sbc_init);
+module_exit(ln2440sbc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ivan Kuten");
+MODULE_DESCRIPTION("ALSA SoC ALC650 LN2440SBC");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/neo1973_gta02_wm8753.c b/linux/sound/soc/s3c24xx/neo1973_gta02_wm8753.c
new file mode 100644
index 0000000..e97bdf1
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/neo1973_gta02_wm8753.c
@@ -0,0 +1,504 @@
+/*
+ * neo1973_gta02_wm8753.c  --  SoC audio for Openmoko Freerunner(GTA02)
+ *
+ * Copyright 2007 Openmoko Inc
+ * Author: Graeme Gregory <graeme@openmoko.org>
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory <linux@wolfsonmicro.com>
+ * Copyright 2009 Wolfson Microelectronics
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+
+#include <plat/regs-iis.h>
+
+#include <mach/regs-clock.h>
+#include <asm/io.h>
+#include <mach/gta02.h>
+#include "../codecs/wm8753.h"
+#include "s3c-dma.h"
+#include "s3c24xx-i2s.h"
+
+static struct snd_soc_card neo1973_gta02;
+
+static int neo1973_gta02_hifi_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int pll_out = 0, bclk = 0;
+	int ret = 0;
+	unsigned long iis_clkrate;
+
+	iis_clkrate = s3c24xx_i2s_get_clockrate();
+
+	switch (params_rate(params)) {
+	case 8000:
+	case 16000:
+		pll_out = 12288000;
+		break;
+	case 48000:
+		bclk = WM8753_BCLK_DIV_4;
+		pll_out = 12288000;
+		break;
+	case 96000:
+		bclk = WM8753_BCLK_DIV_2;
+		pll_out = 12288000;
+		break;
+	case 11025:
+		bclk = WM8753_BCLK_DIV_16;
+		pll_out = 11289600;
+		break;
+	case 22050:
+		bclk = WM8753_BCLK_DIV_8;
+		pll_out = 11289600;
+		break;
+	case 44100:
+		bclk = WM8753_BCLK_DIV_4;
+		pll_out = 11289600;
+		break;
+	case 88200:
+		bclk = WM8753_BCLK_DIV_2;
+		pll_out = 11289600;
+		break;
+	}
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+		SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+		SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+		SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+		SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set MCLK division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
+		S3C2410_IISMOD_32FS);
+	if (ret < 0)
+		return ret;
+
+	/* set codec BCLK division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(codec_dai,
+					WM8753_BCLKDIV, bclk);
+	if (ret < 0)
+		return ret;
+
+	/* set prescaler division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+		S3C24XX_PRESCALE(4, 4));
+	if (ret < 0)
+		return ret;
+
+	/* codec PLL input is PCLK/4 */
+	ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0,
+		iis_clkrate / 4, pll_out);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int neo1973_gta02_hifi_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+	/* disable the PLL */
+	return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0);
+}
+
+/*
+ * Neo1973 WM8753 HiFi DAI opserations.
+ */
+static struct snd_soc_ops neo1973_gta02_hifi_ops = {
+	.hw_params = neo1973_gta02_hifi_hw_params,
+	.hw_free = neo1973_gta02_hifi_hw_free,
+};
+
+static int neo1973_gta02_voice_hw_params(
+	struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	unsigned int pcmdiv = 0;
+	int ret = 0;
+	unsigned long iis_clkrate;
+
+	iis_clkrate = s3c24xx_i2s_get_clockrate();
+
+	if (params_rate(params) != 8000)
+		return -EINVAL;
+	if (params_channels(params) != 1)
+		return -EINVAL;
+
+	pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */
+
+	/* todo: gg check mode (DSP_B) against CSR datasheet */
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK,
+		12288000, SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set codec PCM division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV,
+					pcmdiv);
+	if (ret < 0)
+		return ret;
+
+	/* configure and enable PLL for 12.288MHz output */
+	ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0,
+		iis_clkrate / 4, 12288000);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int neo1973_gta02_voice_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+	/* disable the PLL */
+	return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0);
+}
+
+static struct snd_soc_ops neo1973_gta02_voice_ops = {
+	.hw_params = neo1973_gta02_voice_hw_params,
+	.hw_free = neo1973_gta02_voice_hw_free,
+};
+
+#define LM4853_AMP 1
+#define LM4853_SPK 2
+
+static u8 lm4853_state;
+
+/* This has no effect, it exists only to maintain compatibility with
+ * existing ALSA state files.
+ */
+static int lm4853_set_state(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	int val = ucontrol->value.integer.value[0];
+
+	if (val)
+		lm4853_state |= LM4853_AMP;
+	else
+		lm4853_state &= ~LM4853_AMP;
+
+	return 0;
+}
+
+static int lm4853_get_state(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = lm4853_state & LM4853_AMP;
+
+	return 0;
+}
+
+static int lm4853_set_spk(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	int val = ucontrol->value.integer.value[0];
+
+	if (val) {
+		lm4853_state |= LM4853_SPK;
+		gpio_set_value(GTA02_GPIO_HP_IN, 0);
+	} else {
+		lm4853_state &= ~LM4853_SPK;
+		gpio_set_value(GTA02_GPIO_HP_IN, 1);
+	}
+
+	return 0;
+}
+
+static int lm4853_get_spk(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = (lm4853_state & LM4853_SPK) >> 1;
+
+	return 0;
+}
+
+static int lm4853_event(struct snd_soc_dapm_widget *w,
+			struct snd_kcontrol *k,
+			int event)
+{
+	gpio_set_value(GTA02_GPIO_AMP_SHUT, SND_SOC_DAPM_EVENT_OFF(event));
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Stereo Out", lm4853_event),
+	SND_SOC_DAPM_LINE("GSM Line Out", NULL),
+	SND_SOC_DAPM_LINE("GSM Line In", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_MIC("Handset Mic", NULL),
+	SND_SOC_DAPM_SPK("Handset Spk", NULL),
+};
+
+
+/* example machine audio_mapnections */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+	/* Connections to the lm4853 amp */
+	{"Stereo Out", NULL, "LOUT1"},
+	{"Stereo Out", NULL, "ROUT1"},
+
+	/* Connections to the GSM Module */
+	{"GSM Line Out", NULL, "MONO1"},
+	{"GSM Line Out", NULL, "MONO2"},
+	{"RXP", NULL, "GSM Line In"},
+	{"RXN", NULL, "GSM Line In"},
+
+	/* Connections to Headset */
+	{"MIC1", NULL, "Mic Bias"},
+	{"Mic Bias", NULL, "Headset Mic"},
+
+	/* Call Mic */
+	{"MIC2", NULL, "Mic Bias"},
+	{"MIC2N", NULL, "Mic Bias"},
+	{"Mic Bias", NULL, "Handset Mic"},
+
+	/* Call Speaker */
+	{"Handset Spk", NULL, "LOUT2"},
+	{"Handset Spk", NULL, "ROUT2"},
+
+	/* Connect the ALC pins */
+	{"ACIN", NULL, "ACOP"},
+};
+
+static const struct snd_kcontrol_new wm8753_neo1973_gta02_controls[] = {
+	SOC_DAPM_PIN_SWITCH("Stereo Out"),
+	SOC_DAPM_PIN_SWITCH("GSM Line Out"),
+	SOC_DAPM_PIN_SWITCH("GSM Line In"),
+	SOC_DAPM_PIN_SWITCH("Headset Mic"),
+	SOC_DAPM_PIN_SWITCH("Handset Mic"),
+	SOC_DAPM_PIN_SWITCH("Handset Spk"),
+
+	/* This has no effect, it exists only to maintain compatibility with
+	 * existing ALSA state files.
+	 */
+	SOC_SINGLE_EXT("Amp State Switch", 6, 0, 1, 0,
+		lm4853_get_state,
+		lm4853_set_state),
+	SOC_SINGLE_EXT("Amp Spk Switch", 7, 0, 1, 0,
+		lm4853_get_spk,
+		lm4853_set_spk),
+};
+
+/*
+ * This is an example machine initialisation for a wm8753 connected to a
+ * neo1973 GTA02.
+ */
+static int neo1973_gta02_wm8753_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	/* set up NC codec pins */
+	snd_soc_dapm_nc_pin(codec, "OUT3");
+	snd_soc_dapm_nc_pin(codec, "OUT4");
+	snd_soc_dapm_nc_pin(codec, "LINE1");
+	snd_soc_dapm_nc_pin(codec, "LINE2");
+
+	/* Add neo1973 gta02 specific widgets */
+	snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets,
+				  ARRAY_SIZE(wm8753_dapm_widgets));
+
+	/* add neo1973 gta02 specific controls */
+	err = snd_soc_add_controls(codec, wm8753_neo1973_gta02_controls,
+		ARRAY_SIZE(wm8753_neo1973_gta02_controls));
+
+	if (err < 0)
+		return err;
+
+	/* set up neo1973 gta02 specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	/* set endpoints to default off mode */
+	snd_soc_dapm_disable_pin(codec, "Stereo Out");
+	snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+	snd_soc_dapm_disable_pin(codec, "GSM Line In");
+	snd_soc_dapm_disable_pin(codec, "Headset Mic");
+	snd_soc_dapm_disable_pin(codec, "Handset Mic");
+	snd_soc_dapm_disable_pin(codec, "Handset Spk");
+
+	/* allow audio paths from the GSM modem to run during suspend */
+	snd_soc_dapm_ignore_suspend(codec, "Stereo Out");
+	snd_soc_dapm_ignore_suspend(codec, "GSM Line Out");
+	snd_soc_dapm_ignore_suspend(codec, "GSM Line In");
+	snd_soc_dapm_ignore_suspend(codec, "Headset Mic");
+	snd_soc_dapm_ignore_suspend(codec, "Handset Mic");
+	snd_soc_dapm_ignore_suspend(codec, "Handset Spk");
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+/*
+ * BT Codec DAI
+ */
+static struct snd_soc_dai_driver bt_dai = {
+	.name = "bluetooth-dai",
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+};
+
+static struct snd_soc_dai_link neo1973_gta02_dai[] = {
+{ /* Hifi Playback - for similatious use with voice below */
+	.name = "WM8753",
+	.stream_name = "WM8753 HiFi",
+	.cpu_dai_name = "s3c24xx-i2s",
+	.codec_dai_name = "wm8753-hifi",
+	.init = neo1973_gta02_wm8753_init,
+	.platform_name = "s3c24xx-pcm-audio",
+	.codec_name = "wm8753-codec.0-0x1a",
+	.ops = &neo1973_gta02_hifi_ops,
+},
+{ /* Voice via BT */
+	.name = "Bluetooth",
+	.stream_name = "Voice",
+	.cpu_dai_name = "bluetooth-dai",
+	.codec_dai_name = "wm8753-voice",
+	.ops = &neo1973_gta02_voice_ops,
+	.codec_name = "wm8753-codec.0-0x1a",
+	.platform_name = "s3c24xx-pcm-audio",
+},
+};
+
+static struct snd_soc_card neo1973_gta02 = {
+	.name = "neo1973-gta02",
+	.dai_link = neo1973_gta02_dai,
+	.num_links = ARRAY_SIZE(neo1973_gta02_dai),
+};
+
+static struct platform_device *neo1973_gta02_snd_device;
+
+static int __init neo1973_gta02_init(void)
+{
+	int ret;
+
+	if (!machine_is_neo1973_gta02()) {
+		printk(KERN_INFO
+		       "Only GTA02 is supported by this ASoC driver\n");
+		return -ENODEV;
+	}
+
+	neo1973_gta02_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!neo1973_gta02_snd_device)
+		return -ENOMEM;
+
+	/* register bluetooth DAI here */
+	ret = snd_soc_register_dai(&neo1973_gta02_snd_device->dev, -1, &bt_dai);
+	if (ret) {
+		platform_device_put(neo1973_gta02_snd_device);
+		return ret;
+	}
+
+	platform_set_drvdata(neo1973_gta02_snd_device, &neo1973_gta02);
+	ret = platform_device_add(neo1973_gta02_snd_device);
+
+	if (ret) {
+		platform_device_put(neo1973_gta02_snd_device);
+		return ret;
+	}
+
+	/* Initialise GPIOs used by amp */
+	ret = gpio_request(GTA02_GPIO_HP_IN, "GTA02_HP_IN");
+	if (ret) {
+		pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_HP_IN);
+		goto err_unregister_device;
+	}
+
+	ret = gpio_direction_output(GTA02_GPIO_HP_IN, 1);
+	if (ret) {
+		pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_HP_IN);
+		goto err_free_gpio_hp_in;
+	}
+
+	ret = gpio_request(GTA02_GPIO_AMP_SHUT, "GTA02_AMP_SHUT");
+	if (ret) {
+		pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_AMP_SHUT);
+		goto err_free_gpio_hp_in;
+	}
+
+	ret = gpio_direction_output(GTA02_GPIO_AMP_SHUT, 1);
+	if (ret) {
+		pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_AMP_SHUT);
+		goto err_free_gpio_amp_shut;
+	}
+
+	return 0;
+
+err_free_gpio_amp_shut:
+	gpio_free(GTA02_GPIO_AMP_SHUT);
+err_free_gpio_hp_in:
+	gpio_free(GTA02_GPIO_HP_IN);
+err_unregister_device:
+	platform_device_unregister(neo1973_gta02_snd_device);
+	return ret;
+}
+module_init(neo1973_gta02_init);
+
+static void __exit neo1973_gta02_exit(void)
+{
+	snd_soc_unregister_dai(&neo1973_gta02_snd_device->dev, -1);
+	platform_device_unregister(neo1973_gta02_snd_device);
+	gpio_free(GTA02_GPIO_HP_IN);
+	gpio_free(GTA02_GPIO_AMP_SHUT);
+}
+module_exit(neo1973_gta02_exit);
+
+/* Module information */
+MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org");
+MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 GTA02");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/neo1973_wm8753.c b/linux/sound/soc/s3c24xx/neo1973_wm8753.c
new file mode 100644
index 0000000..f4f2ee7
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/neo1973_wm8753.c
@@ -0,0 +1,704 @@
+/*
+ * neo1973_wm8753.c  --  SoC audio for Neo1973
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ *         graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+
+#include <asm/mach-types.h>
+#include <asm/hardware/scoop.h>
+#include <mach/regs-clock.h>
+#include <mach/regs-gpio.h>
+#include <mach/hardware.h>
+#include <linux/io.h>
+#include <mach/spi-gpio.h>
+
+#include <plat/regs-iis.h>
+
+#include "../codecs/wm8753.h"
+#include "lm4857.h"
+#include "s3c-dma.h"
+#include "s3c24xx-i2s.h"
+
+/* define the scenarios */
+#define NEO_AUDIO_OFF			0
+#define NEO_GSM_CALL_AUDIO_HANDSET	1
+#define NEO_GSM_CALL_AUDIO_HEADSET	2
+#define NEO_GSM_CALL_AUDIO_BLUETOOTH	3
+#define NEO_STEREO_TO_SPEAKERS		4
+#define NEO_STEREO_TO_HEADPHONES	5
+#define NEO_CAPTURE_HANDSET		6
+#define NEO_CAPTURE_HEADSET		7
+#define NEO_CAPTURE_BLUETOOTH		8
+
+static struct snd_soc_card neo1973;
+static struct i2c_client *i2c;
+
+static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int pll_out = 0, bclk = 0;
+	int ret = 0;
+	unsigned long iis_clkrate;
+
+	pr_debug("Entered %s\n", __func__);
+
+	iis_clkrate = s3c24xx_i2s_get_clockrate();
+
+	switch (params_rate(params)) {
+	case 8000:
+	case 16000:
+		pll_out = 12288000;
+		break;
+	case 48000:
+		bclk = WM8753_BCLK_DIV_4;
+		pll_out = 12288000;
+		break;
+	case 96000:
+		bclk = WM8753_BCLK_DIV_2;
+		pll_out = 12288000;
+		break;
+	case 11025:
+		bclk = WM8753_BCLK_DIV_16;
+		pll_out = 11289600;
+		break;
+	case 22050:
+		bclk = WM8753_BCLK_DIV_8;
+		pll_out = 11289600;
+		break;
+	case 44100:
+		bclk = WM8753_BCLK_DIV_4;
+		pll_out = 11289600;
+		break;
+	case 88200:
+		bclk = WM8753_BCLK_DIV_2;
+		pll_out = 11289600;
+		break;
+	}
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai,
+		SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+		SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai,
+		SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+		SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set MCLK division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
+		S3C2410_IISMOD_32FS);
+	if (ret < 0)
+		return ret;
+
+	/* set codec BCLK division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk);
+	if (ret < 0)
+		return ret;
+
+	/* set prescaler division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+		S3C24XX_PRESCALE(4, 4));
+	if (ret < 0)
+		return ret;
+
+	/* codec PLL input is PCLK/4 */
+	ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0,
+		iis_clkrate / 4, pll_out);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+	pr_debug("Entered %s\n", __func__);
+
+	/* disable the PLL */
+	return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0);
+}
+
+/*
+ * Neo1973 WM8753 HiFi DAI opserations.
+ */
+static struct snd_soc_ops neo1973_hifi_ops = {
+	.hw_params = neo1973_hifi_hw_params,
+	.hw_free = neo1973_hifi_hw_free,
+};
+
+static int neo1973_voice_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	unsigned int pcmdiv = 0;
+	int ret = 0;
+	unsigned long iis_clkrate;
+
+	pr_debug("Entered %s\n", __func__);
+
+	iis_clkrate = s3c24xx_i2s_get_clockrate();
+
+	if (params_rate(params) != 8000)
+		return -EINVAL;
+	if (params_channels(params) != 1)
+		return -EINVAL;
+
+	pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */
+
+	/* todo: gg check mode (DSP_B) against CSR datasheet */
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, 12288000,
+		SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set codec PCM division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv);
+	if (ret < 0)
+		return ret;
+
+	/* configure and enable PLL for 12.288MHz output */
+	ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0,
+		iis_clkrate / 4, 12288000);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int neo1973_voice_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+	pr_debug("Entered %s\n", __func__);
+
+	/* disable the PLL */
+	return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0);
+}
+
+static struct snd_soc_ops neo1973_voice_ops = {
+	.hw_params = neo1973_voice_hw_params,
+	.hw_free = neo1973_voice_hw_free,
+};
+
+static int neo1973_scenario;
+
+static int neo1973_get_scenario(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = neo1973_scenario;
+	return 0;
+}
+
+static int set_scenario_endpoints(struct snd_soc_codec *codec, int scenario)
+{
+	pr_debug("Entered %s\n", __func__);
+
+	switch (neo1973_scenario) {
+	case NEO_AUDIO_OFF:
+		snd_soc_dapm_disable_pin(codec, "Audio Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line In");
+		snd_soc_dapm_disable_pin(codec, "Headset Mic");
+		snd_soc_dapm_disable_pin(codec, "Call Mic");
+		break;
+	case NEO_GSM_CALL_AUDIO_HANDSET:
+		snd_soc_dapm_enable_pin(codec, "Audio Out");
+		snd_soc_dapm_enable_pin(codec, "GSM Line Out");
+		snd_soc_dapm_enable_pin(codec, "GSM Line In");
+		snd_soc_dapm_disable_pin(codec, "Headset Mic");
+		snd_soc_dapm_enable_pin(codec, "Call Mic");
+		break;
+	case NEO_GSM_CALL_AUDIO_HEADSET:
+		snd_soc_dapm_enable_pin(codec, "Audio Out");
+		snd_soc_dapm_enable_pin(codec, "GSM Line Out");
+		snd_soc_dapm_enable_pin(codec, "GSM Line In");
+		snd_soc_dapm_enable_pin(codec, "Headset Mic");
+		snd_soc_dapm_disable_pin(codec, "Call Mic");
+		break;
+	case NEO_GSM_CALL_AUDIO_BLUETOOTH:
+		snd_soc_dapm_disable_pin(codec, "Audio Out");
+		snd_soc_dapm_enable_pin(codec, "GSM Line Out");
+		snd_soc_dapm_enable_pin(codec, "GSM Line In");
+		snd_soc_dapm_disable_pin(codec, "Headset Mic");
+		snd_soc_dapm_disable_pin(codec, "Call Mic");
+		break;
+	case NEO_STEREO_TO_SPEAKERS:
+		snd_soc_dapm_enable_pin(codec, "Audio Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line In");
+		snd_soc_dapm_disable_pin(codec, "Headset Mic");
+		snd_soc_dapm_disable_pin(codec, "Call Mic");
+		break;
+	case NEO_STEREO_TO_HEADPHONES:
+		snd_soc_dapm_enable_pin(codec, "Audio Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line In");
+		snd_soc_dapm_disable_pin(codec, "Headset Mic");
+		snd_soc_dapm_disable_pin(codec, "Call Mic");
+		break;
+	case NEO_CAPTURE_HANDSET:
+		snd_soc_dapm_disable_pin(codec, "Audio Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line In");
+		snd_soc_dapm_disable_pin(codec, "Headset Mic");
+		snd_soc_dapm_enable_pin(codec, "Call Mic");
+		break;
+	case NEO_CAPTURE_HEADSET:
+		snd_soc_dapm_disable_pin(codec, "Audio Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line In");
+		snd_soc_dapm_enable_pin(codec, "Headset Mic");
+		snd_soc_dapm_disable_pin(codec, "Call Mic");
+		break;
+	case NEO_CAPTURE_BLUETOOTH:
+		snd_soc_dapm_disable_pin(codec, "Audio Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line In");
+		snd_soc_dapm_disable_pin(codec, "Headset Mic");
+		snd_soc_dapm_disable_pin(codec, "Call Mic");
+		break;
+	default:
+		snd_soc_dapm_disable_pin(codec, "Audio Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line Out");
+		snd_soc_dapm_disable_pin(codec, "GSM Line In");
+		snd_soc_dapm_disable_pin(codec, "Headset Mic");
+		snd_soc_dapm_disable_pin(codec, "Call Mic");
+	}
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static int neo1973_set_scenario(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	pr_debug("Entered %s\n", __func__);
+
+	if (neo1973_scenario == ucontrol->value.integer.value[0])
+		return 0;
+
+	neo1973_scenario = ucontrol->value.integer.value[0];
+	set_scenario_endpoints(codec, neo1973_scenario);
+	return 1;
+}
+
+static u8 lm4857_regs[4] = {0x00, 0x40, 0x80, 0xC0};
+
+static void lm4857_write_regs(void)
+{
+	pr_debug("Entered %s\n", __func__);
+
+	if (i2c_master_send(i2c, lm4857_regs, 4) != 4)
+		printk(KERN_ERR "lm4857: i2c write failed\n");
+}
+
+static int lm4857_get_reg(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	int reg = mc->reg;
+	int shift = mc->shift;
+	int mask = mc->max;
+
+	pr_debug("Entered %s\n", __func__);
+
+	ucontrol->value.integer.value[0] = (lm4857_regs[reg] >> shift) & mask;
+	return 0;
+}
+
+static int lm4857_set_reg(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	int reg = mc->reg;
+	int shift = mc->shift;
+	int mask = mc->max;
+
+	if (((lm4857_regs[reg] >> shift) & mask) ==
+		ucontrol->value.integer.value[0])
+		return 0;
+
+	lm4857_regs[reg] &= ~(mask << shift);
+	lm4857_regs[reg] |= ucontrol->value.integer.value[0] << shift;
+	lm4857_write_regs();
+	return 1;
+}
+
+static int lm4857_get_mode(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	u8 value = lm4857_regs[LM4857_CTRL] & 0x0F;
+
+	pr_debug("Entered %s\n", __func__);
+
+	if (value)
+		value -= 5;
+
+	ucontrol->value.integer.value[0] = value;
+	return 0;
+}
+
+static int lm4857_set_mode(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	u8 value = ucontrol->value.integer.value[0];
+
+	pr_debug("Entered %s\n", __func__);
+
+	if (value)
+		value += 5;
+
+	if ((lm4857_regs[LM4857_CTRL] & 0x0F) == value)
+		return 0;
+
+	lm4857_regs[LM4857_CTRL] &= 0xF0;
+	lm4857_regs[LM4857_CTRL] |= value;
+	lm4857_write_regs();
+	return 1;
+}
+
+static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = {
+	SND_SOC_DAPM_LINE("Audio Out", NULL),
+	SND_SOC_DAPM_LINE("GSM Line Out", NULL),
+	SND_SOC_DAPM_LINE("GSM Line In", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_MIC("Call Mic", NULL),
+};
+
+
+static const struct snd_soc_dapm_route dapm_routes[] = {
+
+	/* Connections to the lm4857 amp */
+	{"Audio Out", NULL, "LOUT1"},
+	{"Audio Out", NULL, "ROUT1"},
+
+	/* Connections to the GSM Module */
+	{"GSM Line Out", NULL, "MONO1"},
+	{"GSM Line Out", NULL, "MONO2"},
+	{"RXP", NULL, "GSM Line In"},
+	{"RXN", NULL, "GSM Line In"},
+
+	/* Connections to Headset */
+	{"MIC1", NULL, "Mic Bias"},
+	{"Mic Bias", NULL, "Headset Mic"},
+
+	/* Call Mic */
+	{"MIC2", NULL, "Mic Bias"},
+	{"MIC2N", NULL, "Mic Bias"},
+	{"Mic Bias", NULL, "Call Mic"},
+
+	/* Connect the ALC pins */
+	{"ACIN", NULL, "ACOP"},
+};
+
+static const char *lm4857_mode[] = {
+	"Off",
+	"Call Speaker",
+	"Stereo Speakers",
+	"Stereo Speakers + Headphones",
+	"Headphones"
+};
+
+static const struct soc_enum lm4857_mode_enum[] = {
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode),
+};
+
+static const char *neo_scenarios[] = {
+	"Off",
+	"GSM Handset",
+	"GSM Headset",
+	"GSM Bluetooth",
+	"Speakers",
+	"Headphones",
+	"Capture Handset",
+	"Capture Headset",
+	"Capture Bluetooth"
+};
+
+static const struct soc_enum neo_scenario_enum[] = {
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(neo_scenarios), neo_scenarios),
+};
+
+static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0);
+static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0);
+
+static const struct snd_kcontrol_new wm8753_neo1973_controls[] = {
+	SOC_SINGLE_EXT_TLV("Amp Left Playback Volume", LM4857_LVOL, 0, 31, 0,
+		lm4857_get_reg, lm4857_set_reg, stereo_tlv),
+	SOC_SINGLE_EXT_TLV("Amp Right Playback Volume", LM4857_RVOL, 0, 31, 0,
+		lm4857_get_reg, lm4857_set_reg, stereo_tlv),
+	SOC_SINGLE_EXT_TLV("Amp Mono Playback Volume", LM4857_MVOL, 0, 31, 0,
+		lm4857_get_reg, lm4857_set_reg, mono_tlv),
+	SOC_ENUM_EXT("Amp Mode", lm4857_mode_enum[0],
+		lm4857_get_mode, lm4857_set_mode),
+	SOC_ENUM_EXT("Neo Mode", neo_scenario_enum[0],
+		neo1973_get_scenario, neo1973_set_scenario),
+	SOC_SINGLE_EXT("Amp Spk 3D Playback Switch", LM4857_LVOL, 5, 1, 0,
+		lm4857_get_reg, lm4857_set_reg),
+	SOC_SINGLE_EXT("Amp HP 3d Playback Switch", LM4857_RVOL, 5, 1, 0,
+		lm4857_get_reg, lm4857_set_reg),
+	SOC_SINGLE_EXT("Amp Fast Wakeup Playback Switch", LM4857_CTRL, 5, 1, 0,
+		lm4857_get_reg, lm4857_set_reg),
+	SOC_SINGLE_EXT("Amp Earpiece 6dB Playback Switch", LM4857_CTRL, 4, 1, 0,
+		lm4857_get_reg, lm4857_set_reg),
+};
+
+/*
+ * This is an example machine initialisation for a wm8753 connected to a
+ * neo1973 II. It is missing logic to detect hp/mic insertions and logic
+ * to re-route the audio in such an event.
+ */
+static int neo1973_wm8753_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	pr_debug("Entered %s\n", __func__);
+
+	/* set up NC codec pins */
+	snd_soc_dapm_nc_pin(codec, "LOUT2");
+	snd_soc_dapm_nc_pin(codec, "ROUT2");
+	snd_soc_dapm_nc_pin(codec, "OUT3");
+	snd_soc_dapm_nc_pin(codec, "OUT4");
+	snd_soc_dapm_nc_pin(codec, "LINE1");
+	snd_soc_dapm_nc_pin(codec, "LINE2");
+
+	/* Add neo1973 specific widgets */
+	snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets,
+				  ARRAY_SIZE(wm8753_dapm_widgets));
+
+	/* set endpoints to default mode */
+	set_scenario_endpoints(codec, NEO_AUDIO_OFF);
+
+	/* add neo1973 specific controls */
+	err = snd_soc_add_controls(codec, wm8753_neo1973_controls,
+				ARRAY_SIZE(8753_neo1973_controls));
+	if (err < 0)
+		return err;
+
+	/* set up neo1973 specific audio routes */
+	err = snd_soc_dapm_add_routes(codec, dapm_routes,
+				      ARRAY_SIZE(dapm_routes));
+
+	snd_soc_dapm_sync(codec);
+	return 0;
+}
+
+/*
+ * BT Codec DAI
+ */
+static struct snd_soc_dai bt_dai = {
+	.name = "bluetooth-dai",
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 1,
+		.rates = SNDRV_PCM_RATE_8000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+};
+
+static struct snd_soc_dai_link neo1973_dai[] = {
+{ /* Hifi Playback - for similatious use with voice below */
+	.name = "WM8753",
+	.stream_name = "WM8753 HiFi",
+	.platform_name = "s3c24xx-pcm-audio",
+	.cpu_dai_name = "s3c24xx-i2s",
+	.codec_dai_name = "wm8753-hifi",
+	.codec_name = "wm8753-codec.0-0x1a",
+	.init = neo1973_wm8753_init,
+	.ops = &neo1973_hifi_ops,
+},
+{ /* Voice via BT */
+	.name = "Bluetooth",
+	.stream_name = "Voice",
+	.platform_name = "s3c24xx-pcm-audio",
+	.cpu_dai_name = "bluetooth-dai",
+	.codec_dai_name = "wm8753-voice",
+	.codec_name = "wm8753-codec.0-0x1a",
+	.ops = &neo1973_voice_ops,
+},
+};
+
+static struct snd_soc_card neo1973 = {
+	.name = "neo1973",
+	.dai_link = neo1973_dai,
+	.num_links = ARRAY_SIZE(neo1973_dai),
+};
+
+static int lm4857_i2c_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	pr_debug("Entered %s\n", __func__);
+
+	i2c = client;
+
+	lm4857_write_regs();
+	return 0;
+}
+
+static int lm4857_i2c_remove(struct i2c_client *client)
+{
+	pr_debug("Entered %s\n", __func__);
+
+	i2c = NULL;
+
+	return 0;
+}
+
+static u8 lm4857_state;
+
+static int lm4857_suspend(struct i2c_client *dev, pm_message_t state)
+{
+	pr_debug("Entered %s\n", __func__);
+
+	dev_dbg(&dev->dev, "lm4857_suspend\n");
+	lm4857_state = lm4857_regs[LM4857_CTRL] & 0xf;
+	if (lm4857_state) {
+		lm4857_regs[LM4857_CTRL] &= 0xf0;
+		lm4857_write_regs();
+	}
+	return 0;
+}
+
+static int lm4857_resume(struct i2c_client *dev)
+{
+	pr_debug("Entered %s\n", __func__);
+
+	if (lm4857_state) {
+		lm4857_regs[LM4857_CTRL] |= (lm4857_state & 0x0f);
+		lm4857_write_regs();
+	}
+	return 0;
+}
+
+static void lm4857_shutdown(struct i2c_client *dev)
+{
+	pr_debug("Entered %s\n", __func__);
+
+	dev_dbg(&dev->dev, "lm4857_shutdown\n");
+	lm4857_regs[LM4857_CTRL] &= 0xf0;
+	lm4857_write_regs();
+}
+
+static const struct i2c_device_id lm4857_i2c_id[] = {
+	{ "neo1973_lm4857", 0 },
+	{ }
+};
+
+static struct i2c_driver lm4857_i2c_driver = {
+	.driver = {
+		.name = "LM4857 I2C Amp",
+		.owner = THIS_MODULE,
+	},
+	.suspend =        lm4857_suspend,
+	.resume	=         lm4857_resume,
+	.shutdown =       lm4857_shutdown,
+	.probe =          lm4857_i2c_probe,
+	.remove =         lm4857_i2c_remove,
+	.id_table =       lm4857_i2c_id,
+};
+
+static struct platform_device *neo1973_snd_device;
+
+static int __init neo1973_init(void)
+{
+	int ret;
+
+	pr_debug("Entered %s\n", __func__);
+
+	if (!machine_is_neo1973_gta01()) {
+		printk(KERN_INFO
+			"Only GTA01 hardware supported by ASoC driver\n");
+		return -ENODEV;
+	}
+
+	neo1973_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!neo1973_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(neo1973_snd_device, &neo1973);
+	ret = platform_device_add(neo1973_snd_device);
+
+	if (ret) {
+		platform_device_put(neo1973_snd_device);
+		return ret;
+	}
+
+	ret = i2c_add_driver(&lm4857_i2c_driver);
+
+	if (ret != 0)
+		platform_device_unregister(neo1973_snd_device);
+
+	return ret;
+}
+
+static void __exit neo1973_exit(void)
+{
+	pr_debug("Entered %s\n", __func__);
+
+	i2c_del_driver(&lm4857_i2c_driver);
+	platform_device_unregister(neo1973_snd_device);
+}
+
+module_init(neo1973_init);
+module_exit(neo1973_exit);
+
+/* Module information */
+MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org, www.openmoko.org");
+MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/regs-i2s-v2.h b/linux/sound/soc/s3c24xx/regs-i2s-v2.h
new file mode 100644
index 0000000..5e5e568
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/regs-i2s-v2.h
@@ -0,0 +1,115 @@
+/* linux/include/asm-arm/plat-s3c24xx/regs-s3c2412-iis.h
+ *
+ * Copyright 2007 Simtec Electronics <linux@simtec.co.uk>
+ *	http://armlinux.simtec.co.uk/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * S3C2412 IIS register definition
+*/
+
+#ifndef __ASM_ARCH_REGS_S3C2412_IIS_H
+#define __ASM_ARCH_REGS_S3C2412_IIS_H
+
+#define S3C2412_IISCON			(0x00)
+#define S3C2412_IISMOD			(0x04)
+#define S3C2412_IISFIC			(0x08)
+#define S3C2412_IISPSR			(0x0C)
+#define S3C2412_IISTXD			(0x10)
+#define S3C2412_IISRXD			(0x14)
+
+#define S5PC1XX_IISFICS		0x18
+#define S5PC1XX_IISTXDS		0x1C
+
+#define S5PC1XX_IISCON_SW_RST		(1 << 31)
+#define S5PC1XX_IISCON_FRXOFSTATUS	(1 << 26)
+#define S5PC1XX_IISCON_FRXORINTEN	(1 << 25)
+#define S5PC1XX_IISCON_FTXSURSTAT	(1 << 24)
+#define S5PC1XX_IISCON_FTXSURINTEN	(1 << 23)
+#define S5PC1XX_IISCON_TXSDMAPAUSE	(1 << 20)
+#define S5PC1XX_IISCON_TXSDMACTIVE	(1 << 18)
+
+#define S3C64XX_IISCON_FTXURSTATUS	(1 << 17)
+#define S3C64XX_IISCON_FTXURINTEN	(1 << 16)
+#define S3C64XX_IISCON_TXFIFO2_EMPTY	(1 << 15)
+#define S3C64XX_IISCON_TXFIFO1_EMPTY	(1 << 14)
+#define S3C64XX_IISCON_TXFIFO2_FULL	(1 << 13)
+#define S3C64XX_IISCON_TXFIFO1_FULL	(1 << 12)
+
+#define S3C2412_IISCON_LRINDEX		(1 << 11)
+#define S3C2412_IISCON_TXFIFO_EMPTY	(1 << 10)
+#define S3C2412_IISCON_RXFIFO_EMPTY	(1 << 9)
+#define S3C2412_IISCON_TXFIFO_FULL	(1 << 8)
+#define S3C2412_IISCON_RXFIFO_FULL	(1 << 7)
+#define S3C2412_IISCON_TXDMA_PAUSE	(1 << 6)
+#define S3C2412_IISCON_RXDMA_PAUSE	(1 << 5)
+#define S3C2412_IISCON_TXCH_PAUSE	(1 << 4)
+#define S3C2412_IISCON_RXCH_PAUSE	(1 << 3)
+#define S3C2412_IISCON_TXDMA_ACTIVE	(1 << 2)
+#define S3C2412_IISCON_RXDMA_ACTIVE	(1 << 1)
+#define S3C2412_IISCON_IIS_ACTIVE	(1 << 0)
+
+#define S5PC1XX_IISMOD_OPCLK_CDCLK_OUT	(0 << 30)
+#define S5PC1XX_IISMOD_OPCLK_CDCLK_IN	(1 << 30)
+#define S5PC1XX_IISMOD_OPCLK_BCLK_OUT	(2 << 30)
+#define S5PC1XX_IISMOD_OPCLK_PCLK	(3 << 30)
+#define S5PC1XX_IISMOD_OPCLK_MASK	(3 << 30)
+#define S5PC1XX_IISMOD_TXS_IDMA		(1 << 28) /* Sec_TXFIFO use I-DMA */
+#define S5PC1XX_IISMOD_BLCS_MASK	0x3
+#define S5PC1XX_IISMOD_BLCS_SHIFT	26
+#define S5PC1XX_IISMOD_BLCP_MASK	0x3
+#define S5PC1XX_IISMOD_BLCP_SHIFT	24
+
+#define S3C64XX_IISMOD_C2DD_HHALF	(1 << 21) /* Discard Higher-half */
+#define S3C64XX_IISMOD_C2DD_LHALF	(1 << 20) /* Discard Lower-half */
+#define S3C64XX_IISMOD_C1DD_HHALF	(1 << 19)
+#define S3C64XX_IISMOD_C1DD_LHALF	(1 << 18)
+#define S3C64XX_IISMOD_DC2_EN		(1 << 17)
+#define S3C64XX_IISMOD_DC1_EN		(1 << 16)
+#define S3C64XX_IISMOD_BLC_16BIT	(0 << 13)
+#define S3C64XX_IISMOD_BLC_8BIT		(1 << 13)
+#define S3C64XX_IISMOD_BLC_24BIT	(2 << 13)
+#define S3C64XX_IISMOD_BLC_MASK		(3 << 13)
+
+#define S3C2412_IISMOD_IMS_SYSMUX	(1 << 10)
+#define S3C2412_IISMOD_SLAVE		(1 << 11)
+#define S3C2412_IISMOD_MODE_TXONLY	(0 << 8)
+#define S3C2412_IISMOD_MODE_RXONLY	(1 << 8)
+#define S3C2412_IISMOD_MODE_TXRX	(2 << 8)
+#define S3C2412_IISMOD_MODE_MASK	(3 << 8)
+#define S3C2412_IISMOD_LR_LLOW		(0 << 7)
+#define S3C2412_IISMOD_LR_RLOW		(1 << 7)
+#define S3C2412_IISMOD_SDF_IIS		(0 << 5)
+#define S3C2412_IISMOD_SDF_MSB		(1 << 5)
+#define S3C2412_IISMOD_SDF_LSB		(2 << 5)
+#define S3C2412_IISMOD_SDF_MASK		(3 << 5)
+#define S3C2412_IISMOD_RCLK_256FS	(0 << 3)
+#define S3C2412_IISMOD_RCLK_512FS	(1 << 3)
+#define S3C2412_IISMOD_RCLK_384FS	(2 << 3)
+#define S3C2412_IISMOD_RCLK_768FS	(3 << 3)
+#define S3C2412_IISMOD_RCLK_MASK 	(3 << 3)
+#define S3C2412_IISMOD_BCLK_32FS	(0 << 1)
+#define S3C2412_IISMOD_BCLK_48FS	(1 << 1)
+#define S3C2412_IISMOD_BCLK_16FS	(2 << 1)
+#define S3C2412_IISMOD_BCLK_24FS	(3 << 1)
+#define S3C2412_IISMOD_BCLK_MASK	(3 << 1)
+#define S3C2412_IISMOD_8BIT		(1 << 0)
+
+#define S3C64XX_IISMOD_CDCLKCON		(1 << 12)
+
+#define S3C2412_IISPSR_PSREN		(1 << 15)
+
+#define S3C64XX_IISFIC_TX2COUNT(x)	(((x) >>  24) & 0xf)
+#define S3C64XX_IISFIC_TX1COUNT(x)	(((x) >>  16) & 0xf)
+
+#define S3C2412_IISFIC_TXFLUSH		(1 << 15)
+#define S3C2412_IISFIC_RXFLUSH		(1 << 7)
+#define S3C2412_IISFIC_TXCOUNT(x)	(((x) >>  8) & 0xf)
+#define S3C2412_IISFIC_RXCOUNT(x)	(((x) >>  0) & 0xf)
+
+#define S5PC1XX_IISFICS_TXFLUSH		(1 << 15)
+#define S5PC1XX_IISFICS_TXCOUNT(x)	(((x) >>  8) & 0x7f)
+
+#endif /* __ASM_ARCH_REGS_S3C2412_IIS_H */
diff --git a/linux/sound/soc/s3c24xx/rx1950_uda1380.c b/linux/sound/soc/s3c24xx/rx1950_uda1380.c
new file mode 100644
index 0000000..468cc11
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/rx1950_uda1380.c
@@ -0,0 +1,319 @@
+/*
+ * rx1950.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com>
+ *
+ * Based on smdk2440.c and magician.c
+ *
+ * Authors: Graeme Gregory graeme.gregory@wolfsonmicro.com
+ *          Philipp Zabel <philipp.zabel@gmail.com>
+ *          Denis Grigoriev <dgreenday@gmail.com>
+ *          Vasily Khoruzhick <anarsoul@gmail.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/uda1380.h>
+#include <sound/jack.h>
+
+#include <plat/regs-iis.h>
+
+#include <mach/regs-clock.h>
+
+#include <asm/mach-types.h>
+
+#include "s3c-dma.h"
+#include "s3c24xx-i2s.h"
+#include "../codecs/uda1380.h"
+
+static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd);
+static int rx1950_startup(struct snd_pcm_substream *substream);
+static int rx1950_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params);
+static int rx1950_spk_power(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *kcontrol, int event);
+
+static unsigned int rates[] = {
+	16000,
+	44100,
+	48000,
+};
+
+static struct snd_pcm_hw_constraint_list hw_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static struct snd_soc_jack hp_jack;
+
+static struct snd_soc_jack_pin hp_jack_pins[] = {
+	{
+		.pin	= "Headphone Jack",
+		.mask	= SND_JACK_HEADPHONE,
+	},
+	{
+		.pin	= "Speaker",
+		.mask	= SND_JACK_HEADPHONE,
+		.invert	= 1,
+	},
+};
+
+static struct snd_soc_jack_gpio hp_jack_gpios[] = {
+	[0] = {
+		.gpio			= S3C2410_GPG(12),
+		.name			= "hp-gpio",
+		.report			= SND_JACK_HEADPHONE,
+		.invert			= 1,
+		.debounce_time		= 200,
+	},
+};
+
+static struct snd_soc_ops rx1950_ops = {
+	.startup	= rx1950_startup,
+	.hw_params	= rx1950_hw_params,
+};
+
+/* s3c24xx digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link rx1950_uda1380_dai[] = {
+	{
+		.name		= "uda1380",
+		.stream_name	= "UDA1380 Duplex",
+		.cpu_dai_name	= "s3c24xx-iis",
+		.codec_dai_name	= "uda1380-hifi",
+		.init		= rx1950_uda1380_init,
+		.platform_name	= "s3c24xx-pcm-audio",
+		.codec_name	= "uda1380-codec.0-001a",
+		.ops		= &rx1950_ops,
+	},
+};
+
+static struct snd_soc_card rx1950_asoc = {
+	.name = "rx1950",
+	.dai_link = rx1950_uda1380_dai,
+	.num_links = ARRAY_SIZE(rx1950_uda1380_dai),
+};
+
+/* rx1950 machine dapm widgets */
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+	SND_SOC_DAPM_SPK("Speaker", rx1950_spk_power),
+};
+
+/* rx1950 machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* headphone connected to VOUTLHP, VOUTRHP */
+	{"Headphone Jack", NULL, "VOUTLHP"},
+	{"Headphone Jack", NULL, "VOUTRHP"},
+
+	/* ext speaker connected to VOUTL, VOUTR  */
+	{"Speaker", NULL, "VOUTL"},
+	{"Speaker", NULL, "VOUTR"},
+
+	/* mic is connected to VINM */
+	{"VINM", NULL, "Mic Jack"},
+};
+
+static struct platform_device *s3c24xx_snd_device;
+
+static int rx1950_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw.rate_min = hw_rates.list[0];
+	runtime->hw.rate_max = hw_rates.list[hw_rates.count - 1];
+	runtime->hw.rates = SNDRV_PCM_RATE_KNOT;
+
+	return snd_pcm_hw_constraint_list(runtime, 0,
+					SNDRV_PCM_HW_PARAM_RATE,
+					&hw_rates);
+}
+
+static int rx1950_spk_power(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *kcontrol, int event)
+{
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		gpio_set_value(S3C2410_GPA(1), 1);
+	else
+		gpio_set_value(S3C2410_GPA(1), 0);
+
+	return 0;
+}
+
+static int rx1950_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	int div;
+	int ret;
+	unsigned int rate = params_rate(params);
+	int clk_source, fs_mode;
+
+	switch (rate) {
+	case 16000:
+	case 48000:
+		clk_source = S3C24XX_CLKSRC_PCLK;
+		fs_mode = S3C2410_IISMOD_256FS;
+		div = s3c24xx_i2s_get_clockrate() / (256 * rate);
+		if (s3c24xx_i2s_get_clockrate() % (256 * rate) > (128 * rate))
+			div++;
+		break;
+	case 44100:
+	case 88200:
+		clk_source = S3C24XX_CLKSRC_MPLL;
+		fs_mode = S3C2410_IISMOD_384FS;
+		div = 1;
+		break;
+	default:
+		printk(KERN_ERR "%s: rate %d is not supported\n",
+			__func__, rate);
+		return -EINVAL;
+	}
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* select clock source */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source, rate,
+			SND_SOC_CLOCK_OUT);
+	if (ret < 0)
+		return ret;
+
+	/* set MCLK division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
+		fs_mode);
+	if (ret < 0)
+		return ret;
+
+	/* set BCLK division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
+		S3C2410_IISMOD_32FS);
+	if (ret < 0)
+		return ret;
+
+	/* set prescaler division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+		S3C24XX_PRESCALE(div, div));
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	int err;
+
+	/* Add rx1950 specific widgets */
+	err = snd_soc_dapm_new_controls(codec, uda1380_dapm_widgets,
+				  ARRAY_SIZE(uda1380_dapm_widgets));
+
+	if (err)
+		return err;
+
+	/* Set up rx1950 specific audio path audio_mapnects */
+	err = snd_soc_dapm_add_routes(codec, audio_map,
+				      ARRAY_SIZE(audio_map));
+
+	if (err)
+		return err;
+
+	snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+	snd_soc_dapm_enable_pin(codec, "Speaker");
+
+	snd_soc_dapm_sync(codec);
+
+	snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE,
+		&hp_jack);
+
+	snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins),
+		hp_jack_pins);
+
+	snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
+		hp_jack_gpios);
+
+	return 0;
+}
+
+static int __init rx1950_init(void)
+{
+	int ret;
+
+	if (!machine_is_rx1950())
+		return -ENODEV;
+
+	/* configure some gpios */
+	ret = gpio_request(S3C2410_GPA(1), "speaker-power");
+	if (ret)
+		goto err_gpio;
+
+	ret = gpio_direction_output(S3C2410_GPA(1), 0);
+	if (ret)
+		goto err_gpio_conf;
+
+	s3c24xx_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!s3c24xx_snd_device) {
+		ret = -ENOMEM;
+		goto err_plat_alloc;
+	}
+
+	platform_set_drvdata(s3c24xx_snd_device, &rx1950_asoc);
+	ret = platform_device_add(s3c24xx_snd_device);
+
+	if (ret) {
+		platform_device_put(s3c24xx_snd_device);
+		goto err_plat_add;
+	}
+
+	return 0;
+
+err_plat_add:
+err_plat_alloc:
+err_gpio_conf:
+	gpio_free(S3C2410_GPA(1));
+
+err_gpio:
+	return ret;
+}
+
+static void __exit rx1950_exit(void)
+{
+	platform_device_unregister(s3c24xx_snd_device);
+	snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
+		hp_jack_gpios);
+	gpio_free(S3C2410_GPA(1));
+}
+
+module_init(rx1950_init);
+module_exit(rx1950_exit);
+
+/* Module information */
+MODULE_AUTHOR("Vasily Khoruzhick");
+MODULE_DESCRIPTION("ALSA SoC RX1950");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/s3c-ac97.c b/linux/sound/soc/s3c24xx/s3c-ac97.c
new file mode 100644
index 0000000..f891eb7
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c-ac97.c
@@ -0,0 +1,520 @@
+/* sound/soc/s3c24xx/s3c-ac97.c
+ *
+ * ALSA SoC Audio Layer - S3C AC97 Controller driver
+ * 	Evolved from s3c2443-ac97.c
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * 	Author: Jaswinder Singh <jassi.brar@samsung.com>
+ * 	Credits: Graeme Gregory, Sean Choi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+
+#include <sound/soc.h>
+
+#include <plat/regs-ac97.h>
+#include <mach/dma.h>
+#include <plat/audio.h>
+
+#include "s3c-dma.h"
+#include "s3c-ac97.h"
+
+#define AC_CMD_ADDR(x) (x << 16)
+#define AC_CMD_DATA(x) (x & 0xffff)
+
+struct s3c_ac97_info {
+	struct clk         *ac97_clk;
+	void __iomem	   *regs;
+	struct mutex       lock;
+	struct completion  done;
+};
+static struct s3c_ac97_info s3c_ac97;
+
+static struct s3c2410_dma_client s3c_dma_client_out = {
+	.name = "AC97 PCMOut"
+};
+
+static struct s3c2410_dma_client s3c_dma_client_in = {
+	.name = "AC97 PCMIn"
+};
+
+static struct s3c2410_dma_client s3c_dma_client_micin = {
+	.name = "AC97 MicIn"
+};
+
+static struct s3c_dma_params s3c_ac97_pcm_out = {
+	.client		= &s3c_dma_client_out,
+	.dma_size	= 4,
+};
+
+static struct s3c_dma_params s3c_ac97_pcm_in = {
+	.client		= &s3c_dma_client_in,
+	.dma_size	= 4,
+};
+
+static struct s3c_dma_params s3c_ac97_mic_in = {
+	.client		= &s3c_dma_client_micin,
+	.dma_size	= 4,
+};
+
+static void s3c_ac97_activate(struct snd_ac97 *ac97)
+{
+	u32 ac_glbctrl, stat;
+
+	stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
+	if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
+		return; /* Return if already active */
+
+	INIT_COMPLETION(s3c_ac97.done);
+
+	ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
+	writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	msleep(1);
+
+	ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
+	writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	msleep(1);
+
+	ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
+	writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+	if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
+		pr_err("AC97: Unable to activate!");
+}
+
+static unsigned short s3c_ac97_read(struct snd_ac97 *ac97,
+	unsigned short reg)
+{
+	u32 ac_glbctrl, ac_codec_cmd;
+	u32 stat, addr, data;
+
+	mutex_lock(&s3c_ac97.lock);
+
+	s3c_ac97_activate(ac97);
+
+	INIT_COMPLETION(s3c_ac97.done);
+
+	ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+	ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
+	writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+
+	udelay(50);
+
+	ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
+	writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+	if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
+		pr_err("AC97: Unable to read!");
+
+	stat = readl(s3c_ac97.regs + S3C_AC97_STAT);
+	addr = (stat >> 16) & 0x7f;
+	data = (stat & 0xffff);
+
+	if (addr != reg)
+		pr_err("s3c-ac97: req addr = %02x, rep addr = %02x\n",
+			reg, addr);
+
+	mutex_unlock(&s3c_ac97.lock);
+
+	return (unsigned short)data;
+}
+
+static void s3c_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+	unsigned short val)
+{
+	u32 ac_glbctrl, ac_codec_cmd;
+
+	mutex_lock(&s3c_ac97.lock);
+
+	s3c_ac97_activate(ac97);
+
+	INIT_COMPLETION(s3c_ac97.done);
+
+	ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+	ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
+	writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+
+	udelay(50);
+
+	ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
+	writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+	if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
+		pr_err("AC97: Unable to write!");
+
+	ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+	ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
+	writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
+
+	mutex_unlock(&s3c_ac97.lock);
+}
+
+static void s3c_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+	pr_debug("AC97: Cold reset\n");
+	writel(S3C_AC97_GLBCTRL_COLDRESET,
+			s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	msleep(1);
+
+	writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	msleep(1);
+}
+
+static void s3c_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+	u32 stat;
+
+	stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
+	if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE)
+		return; /* Return if already active */
+
+	pr_debug("AC97: Warm reset\n");
+
+	writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	msleep(1);
+
+	writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	msleep(1);
+
+	s3c_ac97_activate(ac97);
+}
+
+static irqreturn_t s3c_ac97_irq(int irq, void *dev_id)
+{
+	u32 ac_glbctrl, ac_glbstat;
+
+	ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
+
+	if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
+
+		ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+		ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
+		writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+		complete(&s3c_ac97.done);
+	}
+
+	ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	ac_glbctrl |= (1<<30); /* Clear interrupt */
+	writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+	return IRQ_HANDLED;
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read       = s3c_ac97_read,
+	.write      = s3c_ac97_write,
+	.warm_reset = s3c_ac97_warm_reset,
+	.reset      = s3c_ac97_cold_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int s3c_ac97_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct s3c_dma_params *dma_data;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = &s3c_ac97_pcm_out;
+	else
+		dma_data = &s3c_ac97_pcm_in;
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+	return 0;
+}
+
+static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
+				struct snd_soc_dai *dai)
+{
+	u32 ac_glbctrl;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct s3c_dma_params *dma_data =
+		snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+	ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
+	else
+		ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
+		else
+			ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA;
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		break;
+	}
+
+	writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+	s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED);
+
+	return 0;
+}
+
+static int s3c_ac97_hw_mic_params(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *params,
+				      struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return -ENODEV;
+	else
+		snd_soc_dai_set_dma_data(cpu_dai, substream, &s3c_ac97_mic_in);
+
+	return 0;
+}
+
+static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream,
+				    int cmd, struct snd_soc_dai *dai)
+{
+	u32 ac_glbctrl;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct s3c_dma_params *dma_data =
+		snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+	ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
+	ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		ac_glbctrl |= S3C_AC97_GLBCTRL_MICINTM_DMA;
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		break;
+	}
+
+	writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
+
+	s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED);
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops s3c_ac97_dai_ops = {
+	.hw_params	= s3c_ac97_hw_params,
+	.trigger	= s3c_ac97_trigger,
+};
+
+static struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = {
+	.hw_params	= s3c_ac97_hw_mic_params,
+	.trigger	= s3c_ac97_mic_trigger,
+};
+
+static struct snd_soc_dai_driver s3c_ac97_dai[] = {
+	[S3C_AC97_DAI_PCM] = {
+		.name =	"s3c-ac97",
+		.ac97_control = 1,
+		.playback = {
+			.stream_name = "AC97 Playback",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_48000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+		.capture = {
+			.stream_name = "AC97 Capture",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_48000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+		.ops = &s3c_ac97_dai_ops,
+	},
+	[S3C_AC97_DAI_MIC] = {
+		.name = "s3c-ac97-mic",
+		.ac97_control = 1,
+		.capture = {
+			.stream_name = "AC97 Mic Capture",
+			.channels_min = 1,
+			.channels_max = 1,
+			.rates = SNDRV_PCM_RATE_8000_48000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE,},
+		.ops = &s3c_ac97_mic_dai_ops,
+	},
+};
+
+static __devinit int s3c_ac97_probe(struct platform_device *pdev)
+{
+	struct resource *mem_res, *dmatx_res, *dmarx_res, *dmamic_res, *irq_res;
+	struct s3c_audio_pdata *ac97_pdata;
+	int ret;
+
+	ac97_pdata = pdev->dev.platform_data;
+	if (!ac97_pdata || !ac97_pdata->cfg_gpio) {
+		dev_err(&pdev->dev, "cfg_gpio callback not provided!\n");
+		return -EINVAL;
+	}
+
+	/* Check for availability of necessary resource */
+	dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!dmatx_res) {
+		dev_err(&pdev->dev, "Unable to get AC97-TX dma resource\n");
+		return -ENXIO;
+	}
+
+	dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!dmarx_res) {
+		dev_err(&pdev->dev, "Unable to get AC97-RX dma resource\n");
+		return -ENXIO;
+	}
+
+	dmamic_res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
+	if (!dmamic_res) {
+		dev_err(&pdev->dev, "Unable to get AC97-MIC dma resource\n");
+		return -ENXIO;
+	}
+
+	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem_res) {
+		dev_err(&pdev->dev, "Unable to get register resource\n");
+		return -ENXIO;
+	}
+
+	irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!irq_res) {
+		dev_err(&pdev->dev, "AC97 IRQ not provided!\n");
+		return -ENXIO;
+	}
+
+	if (!request_mem_region(mem_res->start,
+				resource_size(mem_res), "s3c-ac97")) {
+		dev_err(&pdev->dev, "Unable to request register region\n");
+		return -EBUSY;
+	}
+
+	s3c_ac97_pcm_out.channel = dmatx_res->start;
+	s3c_ac97_pcm_out.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
+	s3c_ac97_pcm_in.channel = dmarx_res->start;
+	s3c_ac97_pcm_in.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
+	s3c_ac97_mic_in.channel = dmamic_res->start;
+	s3c_ac97_mic_in.dma_addr = mem_res->start + S3C_AC97_MIC_DATA;
+
+	init_completion(&s3c_ac97.done);
+	mutex_init(&s3c_ac97.lock);
+
+	s3c_ac97.regs = ioremap(mem_res->start, resource_size(mem_res));
+	if (s3c_ac97.regs == NULL) {
+		dev_err(&pdev->dev, "Unable to ioremap register region\n");
+		ret = -ENXIO;
+		goto err1;
+	}
+
+	s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");
+	if (IS_ERR(s3c_ac97.ac97_clk)) {
+		dev_err(&pdev->dev, "s3c-ac97 failed to get ac97_clock\n");
+		ret = -ENODEV;
+		goto err2;
+	}
+	clk_enable(s3c_ac97.ac97_clk);
+
+	if (ac97_pdata->cfg_gpio(pdev)) {
+		dev_err(&pdev->dev, "Unable to configure gpio\n");
+		ret = -EINVAL;
+		goto err3;
+	}
+
+	ret = request_irq(irq_res->start, s3c_ac97_irq,
+					IRQF_DISABLED, "AC97", NULL);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "s3c-ac97: interrupt request failed.\n");
+		goto err4;
+	}
+
+	ret = snd_soc_register_dais(&pdev->dev, s3c_ac97_dai,
+			ARRAY_SIZE(s3c_ac97_dai));
+	if (ret)
+		goto err5;
+
+	return 0;
+
+err5:
+	free_irq(irq_res->start, NULL);
+err4:
+err3:
+	clk_disable(s3c_ac97.ac97_clk);
+	clk_put(s3c_ac97.ac97_clk);
+err2:
+	iounmap(s3c_ac97.regs);
+err1:
+	release_mem_region(mem_res->start, resource_size(mem_res));
+
+	return ret;
+}
+
+static __devexit int s3c_ac97_remove(struct platform_device *pdev)
+{
+	struct resource *mem_res, *irq_res;
+
+	snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(s3c_ac97_dai));
+
+	irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (irq_res)
+		free_irq(irq_res->start, NULL);
+
+	clk_disable(s3c_ac97.ac97_clk);
+	clk_put(s3c_ac97.ac97_clk);
+
+	iounmap(s3c_ac97.regs);
+
+	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (mem_res)
+		release_mem_region(mem_res->start, resource_size(mem_res));
+
+	return 0;
+}
+
+static struct platform_driver s3c_ac97_driver = {
+	.probe  = s3c_ac97_probe,
+	.remove = s3c_ac97_remove,
+	.driver = {
+		.name = "s3c-ac97",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init s3c_ac97_init(void)
+{
+	return platform_driver_register(&s3c_ac97_driver);
+}
+module_init(s3c_ac97_init);
+
+static void __exit s3c_ac97_exit(void)
+{
+	platform_driver_unregister(&s3c_ac97_driver);
+}
+module_exit(s3c_ac97_exit);
+
+MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("AC97 driver for the Samsung SoC");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:s3c-ac97");
diff --git a/linux/sound/soc/s3c24xx/s3c-ac97.h b/linux/sound/soc/s3c24xx/s3c-ac97.h
new file mode 100644
index 0000000..5dcedd0
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c-ac97.h
@@ -0,0 +1,21 @@
+/* sound/soc/s3c24xx/s3c-ac97.h
+ *
+ * ALSA SoC Audio Layer - S3C AC97 Controller driver
+ * 	Evolved from s3c2443-ac97.h
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * 	Author: Jaswinder Singh <jassi.brar@samsung.com>
+ * 	Credits: Graeme Gregory, Sean Choi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __S3C_AC97_H_
+#define __S3C_AC97_H_
+
+#define S3C_AC97_DAI_PCM 0
+#define S3C_AC97_DAI_MIC 1
+
+#endif /* __S3C_AC97_H_ */
diff --git a/linux/sound/soc/s3c24xx/s3c-dma.c b/linux/sound/soc/s3c24xx/s3c-dma.c
new file mode 100644
index 0000000..243f79b
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c-dma.c
@@ -0,0 +1,502 @@
+/*
+ * s3c-dma.c  --  ALSA Soc Audio Layer
+ *
+ * (c) 2006 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * Copyright 2004-2005 Simtec Electronics
+ *	http://armlinux.simtec.co.uk/
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <mach/hardware.h>
+#include <mach/dma.h>
+
+#include "s3c-dma.h"
+
+static const struct snd_pcm_hardware s3c_dma_hardware = {
+	.info			= SNDRV_PCM_INFO_INTERLEAVED |
+				    SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				    SNDRV_PCM_INFO_MMAP |
+				    SNDRV_PCM_INFO_MMAP_VALID |
+				    SNDRV_PCM_INFO_PAUSE |
+				    SNDRV_PCM_INFO_RESUME,
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
+				    SNDRV_PCM_FMTBIT_U16_LE |
+				    SNDRV_PCM_FMTBIT_U8 |
+				    SNDRV_PCM_FMTBIT_S8,
+	.channels_min		= 2,
+	.channels_max		= 2,
+	.buffer_bytes_max	= 128*1024,
+	.period_bytes_min	= PAGE_SIZE,
+	.period_bytes_max	= PAGE_SIZE*2,
+	.periods_min		= 2,
+	.periods_max		= 128,
+	.fifo_size		= 32,
+};
+
+struct s3c24xx_runtime_data {
+	spinlock_t lock;
+	int state;
+	unsigned int dma_loaded;
+	unsigned int dma_limit;
+	unsigned int dma_period;
+	dma_addr_t dma_start;
+	dma_addr_t dma_pos;
+	dma_addr_t dma_end;
+	struct s3c_dma_params *params;
+};
+
+/* s3c_dma_enqueue
+ *
+ * place a dma buffer onto the queue for the dma system
+ * to handle.
+*/
+static void s3c_dma_enqueue(struct snd_pcm_substream *substream)
+{
+	struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+	dma_addr_t pos = prtd->dma_pos;
+	unsigned int limit;
+	int ret;
+
+	pr_debug("Entered %s\n", __func__);
+
+	if (s3c_dma_has_circular())
+		limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period;
+	else
+		limit = prtd->dma_limit;
+
+	pr_debug("%s: loaded %d, limit %d\n",
+				__func__, prtd->dma_loaded, limit);
+
+	while (prtd->dma_loaded < limit) {
+		unsigned long len = prtd->dma_period;
+
+		pr_debug("dma_loaded: %d\n", prtd->dma_loaded);
+
+		if ((pos + len) > prtd->dma_end) {
+			len  = prtd->dma_end - pos;
+			pr_debug("%s: corrected dma len %ld\n", __func__, len);
+		}
+
+		ret = s3c2410_dma_enqueue(prtd->params->channel,
+			substream, pos, len);
+
+		if (ret == 0) {
+			prtd->dma_loaded++;
+			pos += prtd->dma_period;
+			if (pos >= prtd->dma_end)
+				pos = prtd->dma_start;
+		} else
+			break;
+	}
+
+	prtd->dma_pos = pos;
+}
+
+static void s3c24xx_audio_buffdone(struct s3c2410_dma_chan *channel,
+				void *dev_id, int size,
+				enum s3c2410_dma_buffresult result)
+{
+	struct snd_pcm_substream *substream = dev_id;
+	struct s3c24xx_runtime_data *prtd;
+
+	pr_debug("Entered %s\n", __func__);
+
+	if (result == S3C2410_RES_ABORT || result == S3C2410_RES_ERR)
+		return;
+
+	prtd = substream->runtime->private_data;
+
+	if (substream)
+		snd_pcm_period_elapsed(substream);
+
+	spin_lock(&prtd->lock);
+	if (prtd->state & ST_RUNNING && !s3c_dma_has_circular()) {
+		prtd->dma_loaded--;
+		s3c_dma_enqueue(substream);
+	}
+
+	spin_unlock(&prtd->lock);
+}
+
+static int s3c_dma_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct s3c24xx_runtime_data *prtd = runtime->private_data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	unsigned long totbytes = params_buffer_bytes(params);
+	struct s3c_dma_params *dma =
+		snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+	int ret = 0;
+
+
+	pr_debug("Entered %s\n", __func__);
+
+	/* return if this is a bufferless transfer e.g.
+	 * codec <--> BT codec or GSM modem -- lg FIXME */
+	if (!dma)
+		return 0;
+
+	/* this may get called several times by oss emulation
+	 * with different params -HW */
+	if (prtd->params == NULL) {
+		/* prepare DMA */
+		prtd->params = dma;
+
+		pr_debug("params %p, client %p, channel %d\n", prtd->params,
+			prtd->params->client, prtd->params->channel);
+
+		ret = s3c2410_dma_request(prtd->params->channel,
+					  prtd->params->client, NULL);
+
+		if (ret < 0) {
+			printk(KERN_ERR "failed to get dma channel\n");
+			return ret;
+		}
+
+		/* use the circular buffering if we have it available. */
+		if (s3c_dma_has_circular())
+			s3c2410_dma_setflags(prtd->params->channel,
+					     S3C2410_DMAF_CIRCULAR);
+	}
+
+	s3c2410_dma_set_buffdone_fn(prtd->params->channel,
+				    s3c24xx_audio_buffdone);
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+	runtime->dma_bytes = totbytes;
+
+	spin_lock_irq(&prtd->lock);
+	prtd->dma_loaded = 0;
+	prtd->dma_limit = runtime->hw.periods_min;
+	prtd->dma_period = params_period_bytes(params);
+	prtd->dma_start = runtime->dma_addr;
+	prtd->dma_pos = prtd->dma_start;
+	prtd->dma_end = prtd->dma_start + totbytes;
+	spin_unlock_irq(&prtd->lock);
+
+	return 0;
+}
+
+static int s3c_dma_hw_free(struct snd_pcm_substream *substream)
+{
+	struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+
+	pr_debug("Entered %s\n", __func__);
+
+	/* TODO - do we need to ensure DMA flushed */
+	snd_pcm_set_runtime_buffer(substream, NULL);
+
+	if (prtd->params) {
+		s3c2410_dma_free(prtd->params->channel, prtd->params->client);
+		prtd->params = NULL;
+	}
+
+	return 0;
+}
+
+static int s3c_dma_prepare(struct snd_pcm_substream *substream)
+{
+	struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+	int ret = 0;
+
+	pr_debug("Entered %s\n", __func__);
+
+	/* return if this is a bufferless transfer e.g.
+	 * codec <--> BT codec or GSM modem -- lg FIXME */
+	if (!prtd->params)
+		return 0;
+
+	/* channel needs configuring for mem=>device, increment memory addr,
+	 * sync to pclk, half-word transfers to the IIS-FIFO. */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		s3c2410_dma_devconfig(prtd->params->channel,
+				      S3C2410_DMASRC_MEM,
+				      prtd->params->dma_addr);
+	} else {
+		s3c2410_dma_devconfig(prtd->params->channel,
+				      S3C2410_DMASRC_HW,
+				      prtd->params->dma_addr);
+	}
+
+	s3c2410_dma_config(prtd->params->channel,
+			   prtd->params->dma_size);
+
+	/* flush the DMA channel */
+	s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH);
+	prtd->dma_loaded = 0;
+	prtd->dma_pos = prtd->dma_start;
+
+	/* enqueue dma buffers */
+	s3c_dma_enqueue(substream);
+
+	return ret;
+}
+
+static int s3c_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
+	int ret = 0;
+
+	pr_debug("Entered %s\n", __func__);
+
+	spin_lock(&prtd->lock);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		prtd->state |= ST_RUNNING;
+		s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		prtd->state &= ~ST_RUNNING;
+		s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STOP);
+		break;
+
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	spin_unlock(&prtd->lock);
+
+	return ret;
+}
+
+static snd_pcm_uframes_t
+s3c_dma_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct s3c24xx_runtime_data *prtd = runtime->private_data;
+	unsigned long res;
+	dma_addr_t src, dst;
+
+	pr_debug("Entered %s\n", __func__);
+
+	spin_lock(&prtd->lock);
+	s3c2410_dma_getposition(prtd->params->channel, &src, &dst);
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		res = dst - prtd->dma_start;
+	else
+		res = src - prtd->dma_start;
+
+	spin_unlock(&prtd->lock);
+
+	pr_debug("Pointer %x %x\n", src, dst);
+
+	/* we seem to be getting the odd error from the pcm library due
+	 * to out-of-bounds pointers. this is maybe due to the dma engine
+	 * not having loaded the new values for the channel before being
+	 * callled... (todo - fix )
+	 */
+
+	if (res >= snd_pcm_lib_buffer_bytes(substream)) {
+		if (res == snd_pcm_lib_buffer_bytes(substream))
+			res = 0;
+	}
+
+	return bytes_to_frames(substream->runtime, res);
+}
+
+static int s3c_dma_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct s3c24xx_runtime_data *prtd;
+
+	pr_debug("Entered %s\n", __func__);
+
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	snd_soc_set_runtime_hwparams(substream, &s3c_dma_hardware);
+
+	prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL);
+	if (prtd == NULL)
+		return -ENOMEM;
+
+	spin_lock_init(&prtd->lock);
+
+	runtime->private_data = prtd;
+	return 0;
+}
+
+static int s3c_dma_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct s3c24xx_runtime_data *prtd = runtime->private_data;
+
+	pr_debug("Entered %s\n", __func__);
+
+	if (!prtd)
+		pr_debug("s3c_dma_close called with prtd == NULL\n");
+
+	kfree(prtd);
+
+	return 0;
+}
+
+static int s3c_dma_mmap(struct snd_pcm_substream *substream,
+	struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	pr_debug("Entered %s\n", __func__);
+
+	return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+				     runtime->dma_area,
+				     runtime->dma_addr,
+				     runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops s3c_dma_ops = {
+	.open		= s3c_dma_open,
+	.close		= s3c_dma_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= s3c_dma_hw_params,
+	.hw_free	= s3c_dma_hw_free,
+	.prepare	= s3c_dma_prepare,
+	.trigger	= s3c_dma_trigger,
+	.pointer	= s3c_dma_pointer,
+	.mmap		= s3c_dma_mmap,
+};
+
+static int s3c_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = s3c_dma_hardware.buffer_bytes_max;
+
+	pr_debug("Entered %s\n", __func__);
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+	buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+					   &buf->addr, GFP_KERNEL);
+	if (!buf->area)
+		return -ENOMEM;
+	buf->bytes = size;
+	return 0;
+}
+
+static void s3c_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	pr_debug("Entered %s\n", __func__);
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+
+		dma_free_writecombine(pcm->card->dev, buf->bytes,
+				      buf->area, buf->addr);
+		buf->area = NULL;
+	}
+}
+
+static u64 s3c_dma_mask = DMA_BIT_MASK(32);
+
+static int s3c_dma_new(struct snd_card *card,
+	struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+	int ret = 0;
+
+	pr_debug("Entered %s\n", __func__);
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &s3c_dma_mask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = 0xffffffff;
+
+	if (dai->driver->playback.channels_min) {
+		ret = s3c_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_PLAYBACK);
+		if (ret)
+			goto out;
+	}
+
+	if (dai->driver->capture.channels_min) {
+		ret = s3c_preallocate_dma_buffer(pcm,
+			SNDRV_PCM_STREAM_CAPTURE);
+		if (ret)
+			goto out;
+	}
+ out:
+	return ret;
+}
+
+static struct snd_soc_platform_driver s3c24xx_soc_platform = {
+	.ops		= &s3c_dma_ops,
+	.pcm_new	= s3c_dma_new,
+	.pcm_free	= s3c_dma_free_dma_buffers,
+};
+
+static int __devinit s3c24xx_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &s3c24xx_soc_platform);
+}
+
+static int __devexit s3c24xx_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver s3c24xx_pcm_driver = {
+	.driver = {
+		.name = "s3c24xx-pcm-audio",
+		.owner = THIS_MODULE,
+	},
+
+	.probe = s3c24xx_soc_platform_probe,
+	.remove = __devexit_p(s3c24xx_soc_platform_remove),
+};
+
+static int __init snd_s3c24xx_pcm_init(void)
+{
+	return platform_driver_register(&s3c24xx_pcm_driver);
+}
+module_init(snd_s3c24xx_pcm_init);
+
+static void __exit snd_s3c24xx_pcm_exit(void)
+{
+	platform_driver_unregister(&s3c24xx_pcm_driver);
+}
+module_exit(snd_s3c24xx_pcm_exit);
+
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("Samsung S3C Audio DMA module");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:s3c24xx-pcm-audio");
diff --git a/linux/sound/soc/s3c24xx/s3c-dma.h b/linux/sound/soc/s3c24xx/s3c-dma.h
new file mode 100644
index 0000000..748c07d
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c-dma.h
@@ -0,0 +1,30 @@
+/*
+ *  s3c-dma.h --
+ *
+ *  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.
+ *
+ *  ALSA PCM interface for the Samsung S3C24xx CPU
+ */
+
+#ifndef _S3C_AUDIO_H
+#define _S3C_AUDIO_H
+
+#define ST_RUNNING		(1<<0)
+#define ST_OPENED		(1<<1)
+
+struct s3c_dma_params {
+	struct s3c2410_dma_client *client;	/* stream identifier */
+	int channel;				/* Channel ID */
+	dma_addr_t dma_addr;
+	int dma_size;			/* Size of the DMA transfer */
+};
+
+#define S3C24XX_DAI_I2S			0
+
+/* platform data */
+extern struct snd_ac97_bus_ops s3c24xx_ac97_ops;
+
+#endif
diff --git a/linux/sound/soc/s3c24xx/s3c-i2s-v2.c b/linux/sound/soc/s3c24xx/s3c-i2s-v2.c
new file mode 100644
index 0000000..b3866d5
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c-i2s-v2.c
@@ -0,0 +1,757 @@
+/* sound/soc/s3c24xx/s3c-i2c-v2.c
+ *
+ * ALSA Soc Audio Layer - I2S core for newer Samsung SoCs.
+ *
+ * Copyright (c) 2006 Wolfson Microelectronics PLC.
+ *	Graeme Gregory graeme.gregory@wolfsonmicro.com
+ *	linux@wolfsonmicro.com
+ *
+ * Copyright (c) 2008, 2007, 2004-2005 Simtec Electronics
+ *	http://armlinux.simtec.co.uk/
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+
+#include "regs-i2s-v2.h"
+#include "s3c-i2s-v2.h"
+#include "s3c-dma.h"
+
+#undef S3C_IIS_V2_SUPPORTED
+
+#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) \
+	|| defined(CONFIG_CPU_S5PV210)
+#define S3C_IIS_V2_SUPPORTED
+#endif
+
+#ifdef CONFIG_PLAT_S3C64XX
+#define S3C_IIS_V2_SUPPORTED
+#endif
+
+#ifndef S3C_IIS_V2_SUPPORTED
+#error Unsupported CPU model
+#endif
+
+#define S3C2412_I2S_DEBUG_CON 0
+
+static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai)
+{
+	return snd_soc_dai_get_drvdata(cpu_dai);
+}
+
+#define bit_set(v, b) (((v) & (b)) ? 1 : 0)
+
+#if S3C2412_I2S_DEBUG_CON
+static void dbg_showcon(const char *fn, u32 con)
+{
+	printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn,
+	       bit_set(con, S3C2412_IISCON_LRINDEX),
+	       bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY),
+	       bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY),
+	       bit_set(con, S3C2412_IISCON_TXFIFO_FULL),
+	       bit_set(con, S3C2412_IISCON_RXFIFO_FULL));
+
+	printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n",
+	       fn,
+	       bit_set(con, S3C2412_IISCON_TXDMA_PAUSE),
+	       bit_set(con, S3C2412_IISCON_RXDMA_PAUSE),
+	       bit_set(con, S3C2412_IISCON_TXCH_PAUSE),
+	       bit_set(con, S3C2412_IISCON_RXCH_PAUSE));
+	printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn,
+	       bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE),
+	       bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE),
+	       bit_set(con, S3C2412_IISCON_IIS_ACTIVE));
+}
+#else
+static inline void dbg_showcon(const char *fn, u32 con)
+{
+}
+#endif
+
+
+/* Turn on or off the transmission path. */
+static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on)
+{
+	void __iomem *regs = i2s->regs;
+	u32 fic, con, mod;
+
+	pr_debug("%s(%d)\n", __func__, on);
+
+	fic = readl(regs + S3C2412_IISFIC);
+	con = readl(regs + S3C2412_IISCON);
+	mod = readl(regs + S3C2412_IISMOD);
+
+	pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+
+	if (on) {
+		con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
+		con &= ~S3C2412_IISCON_TXDMA_PAUSE;
+		con &= ~S3C2412_IISCON_TXCH_PAUSE;
+
+		switch (mod & S3C2412_IISMOD_MODE_MASK) {
+		case S3C2412_IISMOD_MODE_TXONLY:
+		case S3C2412_IISMOD_MODE_TXRX:
+			/* do nothing, we are in the right mode */
+			break;
+
+		case S3C2412_IISMOD_MODE_RXONLY:
+			mod &= ~S3C2412_IISMOD_MODE_MASK;
+			mod |= S3C2412_IISMOD_MODE_TXRX;
+			break;
+
+		default:
+			dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n",
+				mod & S3C2412_IISMOD_MODE_MASK);
+			break;
+		}
+
+		writel(con, regs + S3C2412_IISCON);
+		writel(mod, regs + S3C2412_IISMOD);
+	} else {
+		/* Note, we do not have any indication that the FIFO problems
+		 * tha the S3C2410/2440 had apply here, so we should be able
+		 * to disable the DMA and TX without resetting the FIFOS.
+		 */
+
+		con |=  S3C2412_IISCON_TXDMA_PAUSE;
+		con |=  S3C2412_IISCON_TXCH_PAUSE;
+		con &= ~S3C2412_IISCON_TXDMA_ACTIVE;
+
+		switch (mod & S3C2412_IISMOD_MODE_MASK) {
+		case S3C2412_IISMOD_MODE_TXRX:
+			mod &= ~S3C2412_IISMOD_MODE_MASK;
+			mod |= S3C2412_IISMOD_MODE_RXONLY;
+			break;
+
+		case S3C2412_IISMOD_MODE_TXONLY:
+			mod &= ~S3C2412_IISMOD_MODE_MASK;
+			con &= ~S3C2412_IISCON_IIS_ACTIVE;
+			break;
+
+		default:
+			dev_err(i2s->dev, "TXDIS: Invalid MODE %x in IISMOD\n",
+				mod & S3C2412_IISMOD_MODE_MASK);
+			break;
+		}
+
+		writel(mod, regs + S3C2412_IISMOD);
+		writel(con, regs + S3C2412_IISCON);
+	}
+
+	fic = readl(regs + S3C2412_IISFIC);
+	dbg_showcon(__func__, con);
+	pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+}
+
+static void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on)
+{
+	void __iomem *regs = i2s->regs;
+	u32 fic, con, mod;
+
+	pr_debug("%s(%d)\n", __func__, on);
+
+	fic = readl(regs + S3C2412_IISFIC);
+	con = readl(regs + S3C2412_IISCON);
+	mod = readl(regs + S3C2412_IISMOD);
+
+	pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+
+	if (on) {
+		con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
+		con &= ~S3C2412_IISCON_RXDMA_PAUSE;
+		con &= ~S3C2412_IISCON_RXCH_PAUSE;
+
+		switch (mod & S3C2412_IISMOD_MODE_MASK) {
+		case S3C2412_IISMOD_MODE_TXRX:
+		case S3C2412_IISMOD_MODE_RXONLY:
+			/* do nothing, we are in the right mode */
+			break;
+
+		case S3C2412_IISMOD_MODE_TXONLY:
+			mod &= ~S3C2412_IISMOD_MODE_MASK;
+			mod |= S3C2412_IISMOD_MODE_TXRX;
+			break;
+
+		default:
+			dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n",
+				mod & S3C2412_IISMOD_MODE_MASK);
+		}
+
+		writel(mod, regs + S3C2412_IISMOD);
+		writel(con, regs + S3C2412_IISCON);
+	} else {
+		/* See txctrl notes on FIFOs. */
+
+		con &= ~S3C2412_IISCON_RXDMA_ACTIVE;
+		con |=  S3C2412_IISCON_RXDMA_PAUSE;
+		con |=  S3C2412_IISCON_RXCH_PAUSE;
+
+		switch (mod & S3C2412_IISMOD_MODE_MASK) {
+		case S3C2412_IISMOD_MODE_RXONLY:
+			con &= ~S3C2412_IISCON_IIS_ACTIVE;
+			mod &= ~S3C2412_IISMOD_MODE_MASK;
+			break;
+
+		case S3C2412_IISMOD_MODE_TXRX:
+			mod &= ~S3C2412_IISMOD_MODE_MASK;
+			mod |= S3C2412_IISMOD_MODE_TXONLY;
+			break;
+
+		default:
+			dev_err(i2s->dev, "RXDIS: Invalid MODE %x in IISMOD\n",
+				mod & S3C2412_IISMOD_MODE_MASK);
+		}
+
+		writel(con, regs + S3C2412_IISCON);
+		writel(mod, regs + S3C2412_IISMOD);
+	}
+
+	fic = readl(regs + S3C2412_IISFIC);
+	pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+}
+
+#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
+
+/*
+ * Wait for the LR signal to allow synchronisation to the L/R clock
+ * from the codec. May only be needed for slave mode.
+ */
+static int s3c2412_snd_lrsync(struct s3c_i2sv2_info *i2s)
+{
+	u32 iiscon;
+	unsigned long loops = msecs_to_loops(5);
+
+	pr_debug("Entered %s\n", __func__);
+
+	while (--loops) {
+		iiscon = readl(i2s->regs + S3C2412_IISCON);
+		if (iiscon & S3C2412_IISCON_LRINDEX)
+			break;
+
+		cpu_relax();
+	}
+
+	if (!loops) {
+		printk(KERN_ERR "%s: timeout\n", __func__);
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+/*
+ * Set S3C2412 I2S DAI format
+ */
+static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+			       unsigned int fmt)
+{
+	struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+	u32 iismod;
+
+	pr_debug("Entered %s\n", __func__);
+
+	iismod = readl(i2s->regs + S3C2412_IISMOD);
+	pr_debug("hw_params r: IISMOD: %x \n", iismod);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		i2s->master = 0;
+		iismod |= S3C2412_IISMOD_SLAVE;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		i2s->master = 1;
+		iismod &= ~S3C2412_IISMOD_SLAVE;
+		break;
+	default:
+		pr_err("unknwon master/slave format\n");
+		return -EINVAL;
+	}
+
+	iismod &= ~S3C2412_IISMOD_SDF_MASK;
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_RIGHT_J:
+		iismod |= S3C2412_IISMOD_LR_RLOW;
+		iismod |= S3C2412_IISMOD_SDF_MSB;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		iismod |= S3C2412_IISMOD_LR_RLOW;
+		iismod |= S3C2412_IISMOD_SDF_LSB;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		iismod &= ~S3C2412_IISMOD_LR_RLOW;
+		iismod |= S3C2412_IISMOD_SDF_IIS;
+		break;
+	default:
+		pr_err("Unknown data format\n");
+		return -EINVAL;
+	}
+
+	writel(iismod, i2s->regs + S3C2412_IISMOD);
+	pr_debug("hw_params w: IISMOD: %x \n", iismod);
+	return 0;
+}
+
+static int s3c_i2sv2_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct s3c_i2sv2_info *i2s = to_info(dai);
+	struct s3c_dma_params *dma_data;
+	u32 iismod;
+
+	pr_debug("Entered %s\n", __func__);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = i2s->dma_playback;
+	else
+		dma_data = i2s->dma_capture;
+
+	snd_soc_dai_set_dma_data(dai, substream, dma_data);
+
+	/* Working copies of register */
+	iismod = readl(i2s->regs + S3C2412_IISMOD);
+	pr_debug("%s: r: IISMOD: %x\n", __func__, iismod);
+
+	iismod &= ~S3C64XX_IISMOD_BLC_MASK;
+	/* Sample size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		iismod |= S3C64XX_IISMOD_BLC_8BIT;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iismod |= S3C64XX_IISMOD_BLC_24BIT;
+		break;
+	}
+
+	writel(iismod, i2s->regs + S3C2412_IISMOD);
+	pr_debug("%s: w: IISMOD: %x\n", __func__, iismod);
+
+	return 0;
+}
+
+static int s3c_i2sv2_set_sysclk(struct snd_soc_dai *cpu_dai,
+				  int clk_id, unsigned int freq, int dir)
+{
+	struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+	u32 iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+	pr_debug("Entered %s\n", __func__);
+	pr_debug("%s r: IISMOD: %x\n", __func__, iismod);
+
+	switch (clk_id) {
+	case S3C_I2SV2_CLKSRC_PCLK:
+		iismod &= ~S3C2412_IISMOD_IMS_SYSMUX;
+		break;
+
+	case S3C_I2SV2_CLKSRC_AUDIOBUS:
+		iismod |= S3C2412_IISMOD_IMS_SYSMUX;
+		break;
+
+	case S3C_I2SV2_CLKSRC_CDCLK:
+		/* Error if controller doesn't have the CDCLKCON bit */
+		if (!(i2s->feature & S3C_FEATURE_CDCLKCON))
+			return -EINVAL;
+
+		switch (dir) {
+		case SND_SOC_CLOCK_IN:
+			iismod |= S3C64XX_IISMOD_CDCLKCON;
+			break;
+		case SND_SOC_CLOCK_OUT:
+			iismod &= ~S3C64XX_IISMOD_CDCLKCON;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	writel(iismod, i2s->regs + S3C2412_IISMOD);
+	pr_debug("%s w: IISMOD: %x\n", __func__, iismod);
+
+	return 0;
+}
+
+static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+			       struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct s3c_i2sv2_info *i2s = to_info(rtd->cpu_dai);
+	int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+	unsigned long irqs;
+	int ret = 0;
+	struct s3c_dma_params *dma_data =
+		snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+	pr_debug("Entered %s\n", __func__);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* On start, ensure that the FIFOs are cleared and reset. */
+
+		writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH,
+		       i2s->regs + S3C2412_IISFIC);
+
+		/* clear again, just in case */
+		writel(0x0, i2s->regs + S3C2412_IISFIC);
+
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (!i2s->master) {
+			ret = s3c2412_snd_lrsync(i2s);
+			if (ret)
+				goto exit_err;
+		}
+
+		local_irq_save(irqs);
+
+		if (capture)
+			s3c2412_snd_rxctrl(i2s, 1);
+		else
+			s3c2412_snd_txctrl(i2s, 1);
+
+		local_irq_restore(irqs);
+
+		/*
+		 * Load the next buffer to DMA to meet the reqirement
+		 * of the auto reload mechanism of S3C24XX.
+		 * This call won't bother S3C64XX.
+		 */
+		s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED);
+
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		local_irq_save(irqs);
+
+		if (capture)
+			s3c2412_snd_rxctrl(i2s, 0);
+		else
+			s3c2412_snd_txctrl(i2s, 0);
+
+		local_irq_restore(irqs);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+exit_err:
+	return ret;
+}
+
+/*
+ * Set S3C2412 Clock dividers
+ */
+static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
+				  int div_id, int div)
+{
+	struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+	u32 reg;
+
+	pr_debug("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div);
+
+	switch (div_id) {
+	case S3C_I2SV2_DIV_BCLK:
+		switch (div) {
+		case 16:
+			div = S3C2412_IISMOD_BCLK_16FS;
+			break;
+
+		case 32:
+			div = S3C2412_IISMOD_BCLK_32FS;
+			break;
+
+		case 24:
+			div = S3C2412_IISMOD_BCLK_24FS;
+			break;
+
+		case 48:
+			div = S3C2412_IISMOD_BCLK_48FS;
+			break;
+
+		default:
+			return -EINVAL;
+		}
+
+		reg = readl(i2s->regs + S3C2412_IISMOD);
+		reg &= ~S3C2412_IISMOD_BCLK_MASK;
+		writel(reg | div, i2s->regs + S3C2412_IISMOD);
+
+		pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
+		break;
+
+	case S3C_I2SV2_DIV_RCLK:
+		switch (div) {
+		case 256:
+			div = S3C2412_IISMOD_RCLK_256FS;
+			break;
+
+		case 384:
+			div = S3C2412_IISMOD_RCLK_384FS;
+			break;
+
+		case 512:
+			div = S3C2412_IISMOD_RCLK_512FS;
+			break;
+
+		case 768:
+			div = S3C2412_IISMOD_RCLK_768FS;
+			break;
+
+		default:
+			return -EINVAL;
+		}
+
+		reg = readl(i2s->regs + S3C2412_IISMOD);
+		reg &= ~S3C2412_IISMOD_RCLK_MASK;
+		writel(reg | div, i2s->regs + S3C2412_IISMOD);
+		pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
+		break;
+
+	case S3C_I2SV2_DIV_PRESCALER:
+		if (div >= 0) {
+			writel((div << 8) | S3C2412_IISPSR_PSREN,
+			       i2s->regs + S3C2412_IISPSR);
+		} else {
+			writel(0x0, i2s->regs + S3C2412_IISPSR);
+		}
+		pr_debug("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR));
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_sframes_t s3c2412_i2s_delay(struct snd_pcm_substream *substream,
+					   struct snd_soc_dai *dai)
+{
+	struct s3c_i2sv2_info *i2s = to_info(dai);
+	u32 reg = readl(i2s->regs + S3C2412_IISFIC);
+	snd_pcm_sframes_t delay;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		delay = S3C2412_IISFIC_TXCOUNT(reg);
+	else
+		delay = S3C2412_IISFIC_RXCOUNT(reg);
+
+	return delay;
+}
+
+struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai)
+{
+	struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+	u32 iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+	if (iismod & S3C2412_IISMOD_IMS_SYSMUX)
+		return i2s->iis_cclk;
+	else
+		return i2s->iis_pclk;
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_get_clock);
+
+/* default table of all avaialable root fs divisors */
+static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 };
+
+int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
+			    unsigned int *fstab,
+			    unsigned int rate, struct clk *clk)
+{
+	unsigned long clkrate = clk_get_rate(clk);
+	unsigned int div;
+	unsigned int fsclk;
+	unsigned int actual;
+	unsigned int fs;
+	unsigned int fsdiv;
+	signed int deviation = 0;
+	unsigned int best_fs = 0;
+	unsigned int best_div = 0;
+	unsigned int best_rate = 0;
+	unsigned int best_deviation = INT_MAX;
+
+	pr_debug("Input clock rate %ldHz\n", clkrate);
+
+	if (fstab == NULL)
+		fstab = iis_fs_tab;
+
+	for (fs = 0; fs < ARRAY_SIZE(iis_fs_tab); fs++) {
+		fsdiv = iis_fs_tab[fs];
+
+		fsclk = clkrate / fsdiv;
+		div = fsclk / rate;
+
+		if ((fsclk % rate) > (rate / 2))
+			div++;
+
+		if (div <= 1)
+			continue;
+
+		actual = clkrate / (fsdiv * div);
+		deviation = actual - rate;
+
+		printk(KERN_DEBUG "%ufs: div %u => result %u, deviation %d\n",
+		       fsdiv, div, actual, deviation);
+
+		deviation = abs(deviation);
+
+		if (deviation < best_deviation) {
+			best_fs = fsdiv;
+			best_div = div;
+			best_rate = actual;
+			best_deviation = deviation;
+		}
+
+		if (deviation == 0)
+			break;
+	}
+
+	printk(KERN_DEBUG "best: fs=%u, div=%u, rate=%u\n",
+	       best_fs, best_div, best_rate);
+
+	info->fs_div = best_fs;
+	info->clk_div = best_div;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_iis_calc_rate);
+
+int s3c_i2sv2_probe(struct snd_soc_dai *dai,
+		    struct s3c_i2sv2_info *i2s,
+		    unsigned long base)
+{
+	struct device *dev = dai->dev;
+	unsigned int iismod;
+
+	i2s->dev = dev;
+
+	/* record our i2s structure for later use in the callbacks */
+	snd_soc_dai_set_drvdata(dai, i2s);
+
+	i2s->regs = ioremap(base, 0x100);
+	if (i2s->regs == NULL) {
+		dev_err(dev, "cannot ioremap registers\n");
+		return -ENXIO;
+	}
+
+	i2s->iis_pclk = clk_get(dev, "iis");
+	if (IS_ERR(i2s->iis_pclk)) {
+		dev_err(dev, "failed to get iis_clock\n");
+		iounmap(i2s->regs);
+		return -ENOENT;
+	}
+
+	clk_enable(i2s->iis_pclk);
+
+	/* Mark ourselves as in TXRX mode so we can run through our cleanup
+	 * process without warnings. */
+	iismod = readl(i2s->regs + S3C2412_IISMOD);
+	iismod |= S3C2412_IISMOD_MODE_TXRX;
+	writel(iismod, i2s->regs + S3C2412_IISMOD);
+	s3c2412_snd_txctrl(i2s, 0);
+	s3c2412_snd_rxctrl(i2s, 0);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_probe);
+
+#ifdef CONFIG_PM
+static int s3c2412_i2s_suspend(struct snd_soc_dai *dai)
+{
+	struct s3c_i2sv2_info *i2s = to_info(dai);
+	u32 iismod;
+
+	if (dai->active) {
+		i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD);
+		i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON);
+		i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR);
+
+		/* some basic suspend checks */
+
+		iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+		if (iismod & S3C2412_IISCON_RXDMA_ACTIVE)
+			pr_warning("%s: RXDMA active?\n", __func__);
+
+		if (iismod & S3C2412_IISCON_TXDMA_ACTIVE)
+			pr_warning("%s: TXDMA active?\n", __func__);
+
+		if (iismod & S3C2412_IISCON_IIS_ACTIVE)
+			pr_warning("%s: IIS active\n", __func__);
+	}
+
+	return 0;
+}
+
+static int s3c2412_i2s_resume(struct snd_soc_dai *dai)
+{
+	struct s3c_i2sv2_info *i2s = to_info(dai);
+
+	pr_info("dai_active %d, IISMOD %08x, IISCON %08x\n",
+		dai->active, i2s->suspend_iismod, i2s->suspend_iiscon);
+
+	if (dai->active) {
+		writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON);
+		writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD);
+		writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR);
+
+		writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH,
+		       i2s->regs + S3C2412_IISFIC);
+
+		ndelay(250);
+		writel(0x0, i2s->regs + S3C2412_IISFIC);
+	}
+
+	return 0;
+}
+#else
+#define s3c2412_i2s_suspend NULL
+#define s3c2412_i2s_resume  NULL
+#endif
+
+int s3c_i2sv2_register_dai(struct device *dev, int id,
+		struct snd_soc_dai_driver *drv)
+{
+	struct snd_soc_dai_ops *ops = drv->ops;
+
+	ops->trigger = s3c2412_i2s_trigger;
+	if (!ops->hw_params)
+		ops->hw_params = s3c_i2sv2_hw_params;
+	ops->set_fmt = s3c2412_i2s_set_fmt;
+	ops->set_clkdiv = s3c2412_i2s_set_clkdiv;
+	ops->set_sysclk = s3c_i2sv2_set_sysclk;
+
+	/* Allow overriding by (for example) IISv4 */
+	if (!ops->delay)
+		ops->delay = s3c2412_i2s_delay;
+
+	drv->suspend = s3c2412_i2s_suspend;
+	drv->resume = s3c2412_i2s_resume;
+
+	return snd_soc_register_dai(dev, drv);
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_register_dai);
+
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/s3c-i2s-v2.h b/linux/sound/soc/s3c24xx/s3c-i2s-v2.h
new file mode 100644
index 0000000..d458301
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c-i2s-v2.h
@@ -0,0 +1,106 @@
+/* sound/soc/s3c24xx/s3c-i2s-v2.h
+ *
+ * ALSA Soc Audio Layer - S3C_I2SV2 I2S driver
+ *
+ * Copyright (c) 2007 Simtec Electronics
+ *	http://armlinux.simtec.co.uk/
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ *  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 code is the core support for the I2S block found in a number of
+ * Samsung SoC devices which is unofficially named I2S-V2. Currently the
+ * S3C2412 and the S3C64XX series use this block to provide 1 or 2 I2S
+ * channels via configurable GPIO.
+ */
+
+#ifndef __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H
+#define __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H __FILE__
+
+#define S3C_I2SV2_DIV_BCLK	(1)
+#define S3C_I2SV2_DIV_RCLK	(2)
+#define S3C_I2SV2_DIV_PRESCALER	(3)
+
+#define S3C_I2SV2_CLKSRC_PCLK		0
+#define S3C_I2SV2_CLKSRC_AUDIOBUS	1
+#define S3C_I2SV2_CLKSRC_CDCLK		2
+
+/* Set this flag for I2S controllers that have the bit IISMOD[12]
+ * bridge/break RCLK signal and external Xi2sCDCLK pin.
+ */
+#define S3C_FEATURE_CDCLKCON	(1 << 0)
+
+/**
+ * struct s3c_i2sv2_info - S3C I2S-V2 information
+ * @dev: The parent device passed to use from the probe.
+ * @regs: The pointer to the device registe block.
+ * @feature: Set of bit-flags indicating features of the controller.
+ * @master: True if the I2S core is the I2S bit clock master.
+ * @dma_playback: DMA information for playback channel.
+ * @dma_capture: DMA information for capture channel.
+ * @suspend_iismod: PM save for the IISMOD register.
+ * @suspend_iiscon: PM save for the IISCON register.
+ * @suspend_iispsr: PM save for the IISPSR register.
+ *
+ * This is the private codec state for the hardware associated with an
+ * I2S channel such as the register mappings and clock sources.
+ */
+struct s3c_i2sv2_info {
+	struct device	*dev;
+	void __iomem	*regs;
+
+	u32		feature;
+
+	struct clk	*iis_pclk;
+	struct clk	*iis_cclk;
+
+	unsigned char	 master;
+
+	struct s3c_dma_params	*dma_playback;
+	struct s3c_dma_params	*dma_capture;
+
+	u32		 suspend_iismod;
+	u32		 suspend_iiscon;
+	u32		 suspend_iispsr;
+
+	unsigned long	base;
+};
+
+extern struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai);
+
+struct s3c_i2sv2_rate_calc {
+	unsigned int	clk_div;	/* for prescaler */
+	unsigned int	fs_div;		/* for root frame clock */
+};
+
+extern int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
+				   unsigned int *fstab,
+				   unsigned int rate, struct clk *clk);
+
+/**
+ * s3c_i2sv2_probe - probe for i2s device helper
+ * @dai: The ASoC DAI structure supplied to the original probe.
+ * @i2s: Our local i2s structure to fill in.
+ * @base: The base address for the registers.
+ */
+extern int s3c_i2sv2_probe(struct snd_soc_dai *dai,
+			   struct s3c_i2sv2_info *i2s,
+			   unsigned long base);
+
+/**
+ * s3c_i2sv2_register_dai - register dai with soc core
+ * @dev: DAI device
+ * @id: DAI ID
+ * @drv: The driver structure to register
+ *
+ * Fill in any missing fields and then register the given dai with the
+ * soc core.
+ */
+extern int s3c_i2sv2_register_dai(struct device *dev, int id,
+		struct snd_soc_dai_driver *drv);
+
+#endif /* __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H */
diff --git a/linux/sound/soc/s3c24xx/s3c-pcm.c b/linux/sound/soc/s3c24xx/s3c-pcm.c
new file mode 100644
index 0000000..2e020e1
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c-pcm.c
@@ -0,0 +1,552 @@
+/* sound/soc/s3c24xx/s3c-pcm.c
+ *
+ * ALSA SoC Audio Layer - S3C PCM-Controller driver
+ *
+ * Copyright (c) 2009 Samsung Electronics Co. Ltd
+ * Author: Jaswinder Singh <jassi.brar@samsung.com>
+ * based upon I2S drivers by Ben Dooks.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <plat/audio.h>
+#include <plat/dma.h>
+
+#include "s3c-dma.h"
+#include "s3c-pcm.h"
+
+static struct s3c2410_dma_client s3c_pcm_dma_client_out = {
+	.name		= "PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c_pcm_dma_client_in = {
+	.name		= "PCM Stereo in"
+};
+
+static struct s3c_dma_params s3c_pcm_stereo_out[] = {
+	[0] = {
+		.client		= &s3c_pcm_dma_client_out,
+		.dma_size	= 4,
+	},
+	[1] = {
+		.client		= &s3c_pcm_dma_client_out,
+		.dma_size	= 4,
+	},
+};
+
+static struct s3c_dma_params s3c_pcm_stereo_in[] = {
+	[0] = {
+		.client		= &s3c_pcm_dma_client_in,
+		.dma_size	= 4,
+	},
+	[1] = {
+		.client		= &s3c_pcm_dma_client_in,
+		.dma_size	= 4,
+	},
+};
+
+static struct s3c_pcm_info s3c_pcm[2];
+
+static void s3c_pcm_snd_txctrl(struct s3c_pcm_info *pcm, int on)
+{
+	void __iomem *regs = pcm->regs;
+	u32 ctl, clkctl;
+
+	clkctl = readl(regs + S3C_PCM_CLKCTL);
+	ctl = readl(regs + S3C_PCM_CTL);
+	ctl &= ~(S3C_PCM_CTL_TXDIPSTICK_MASK
+			 << S3C_PCM_CTL_TXDIPSTICK_SHIFT);
+
+	if (on) {
+		ctl |= S3C_PCM_CTL_TXDMA_EN;
+		ctl |= S3C_PCM_CTL_TXFIFO_EN;
+		ctl |= S3C_PCM_CTL_ENABLE;
+		ctl |= (0x4<<S3C_PCM_CTL_TXDIPSTICK_SHIFT);
+		clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+	} else {
+		ctl &= ~S3C_PCM_CTL_TXDMA_EN;
+		ctl &= ~S3C_PCM_CTL_TXFIFO_EN;
+
+		if (!(ctl & S3C_PCM_CTL_RXFIFO_EN)) {
+			ctl &= ~S3C_PCM_CTL_ENABLE;
+			if (!pcm->idleclk)
+				clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+		}
+	}
+
+	writel(clkctl, regs + S3C_PCM_CLKCTL);
+	writel(ctl, regs + S3C_PCM_CTL);
+}
+
+static void s3c_pcm_snd_rxctrl(struct s3c_pcm_info *pcm, int on)
+{
+	void __iomem *regs = pcm->regs;
+	u32 ctl, clkctl;
+
+	ctl = readl(regs + S3C_PCM_CTL);
+	clkctl = readl(regs + S3C_PCM_CLKCTL);
+	ctl &= ~(S3C_PCM_CTL_RXDIPSTICK_MASK
+			 << S3C_PCM_CTL_RXDIPSTICK_SHIFT);
+
+	if (on) {
+		ctl |= S3C_PCM_CTL_RXDMA_EN;
+		ctl |= S3C_PCM_CTL_RXFIFO_EN;
+		ctl |= S3C_PCM_CTL_ENABLE;
+		ctl |= (0x20<<S3C_PCM_CTL_RXDIPSTICK_SHIFT);
+		clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+	} else {
+		ctl &= ~S3C_PCM_CTL_RXDMA_EN;
+		ctl &= ~S3C_PCM_CTL_RXFIFO_EN;
+
+		if (!(ctl & S3C_PCM_CTL_TXFIFO_EN)) {
+			ctl &= ~S3C_PCM_CTL_ENABLE;
+			if (!pcm->idleclk)
+				clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+		}
+	}
+
+	writel(clkctl, regs + S3C_PCM_CLKCTL);
+	writel(ctl, regs + S3C_PCM_CTL);
+}
+
+static int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
+			       struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	unsigned long flags;
+
+	dev_dbg(pcm->dev, "Entered %s\n", __func__);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		spin_lock_irqsave(&pcm->lock, flags);
+
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			s3c_pcm_snd_rxctrl(pcm, 1);
+		else
+			s3c_pcm_snd_txctrl(pcm, 1);
+
+		spin_unlock_irqrestore(&pcm->lock, flags);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		spin_lock_irqsave(&pcm->lock, flags);
+
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			s3c_pcm_snd_rxctrl(pcm, 0);
+		else
+			s3c_pcm_snd_txctrl(pcm, 0);
+
+		spin_unlock_irqrestore(&pcm->lock, flags);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int s3c_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *socdai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct s3c_dma_params *dma_data;
+	void __iomem *regs = pcm->regs;
+	struct clk *clk;
+	int sclk_div, sync_div;
+	unsigned long flags;
+	u32 clkctl;
+
+	dev_dbg(pcm->dev, "Entered %s\n", __func__);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = pcm->dma_playback;
+	else
+		dma_data = pcm->dma_capture;
+
+	snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);
+
+	/* Strictly check for sample size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&pcm->lock, flags);
+
+	/* Get hold of the PCMSOURCE_CLK */
+	clkctl = readl(regs + S3C_PCM_CLKCTL);
+	if (clkctl & S3C_PCM_CLKCTL_SERCLKSEL_PCLK)
+		clk = pcm->pclk;
+	else
+		clk = pcm->cclk;
+
+	/* Set the SCLK divider */
+	sclk_div = clk_get_rate(clk) / pcm->sclk_per_fs /
+					params_rate(params) / 2 - 1;
+
+	clkctl &= ~(S3C_PCM_CLKCTL_SCLKDIV_MASK
+			<< S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
+	clkctl |= ((sclk_div & S3C_PCM_CLKCTL_SCLKDIV_MASK)
+			<< S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
+
+	/* Set the SYNC divider */
+	sync_div = pcm->sclk_per_fs - 1;
+
+	clkctl &= ~(S3C_PCM_CLKCTL_SYNCDIV_MASK
+				<< S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
+	clkctl |= ((sync_div & S3C_PCM_CLKCTL_SYNCDIV_MASK)
+				<< S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
+
+	writel(clkctl, regs + S3C_PCM_CLKCTL);
+
+	spin_unlock_irqrestore(&pcm->lock, flags);
+
+	dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs SCLK_DIV=%d SYNC_DIV=%d\n",
+				clk_get_rate(clk), pcm->sclk_per_fs,
+				sclk_div, sync_div);
+
+	return 0;
+}
+
+static int s3c_pcm_set_fmt(struct snd_soc_dai *cpu_dai,
+			       unsigned int fmt)
+{
+	struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
+	void __iomem *regs = pcm->regs;
+	unsigned long flags;
+	int ret = 0;
+	u32 ctl;
+
+	dev_dbg(pcm->dev, "Entered %s\n", __func__);
+
+	spin_lock_irqsave(&pcm->lock, flags);
+
+	ctl = readl(regs + S3C_PCM_CTL);
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		/* Nothing to do, NB_NF by default */
+		break;
+	default:
+		dev_err(pcm->dev, "Unsupported clock inversion!\n");
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		/* Nothing to do, Master by default */
+		break;
+	default:
+		dev_err(pcm->dev, "Unsupported master/slave format!\n");
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
+	case SND_SOC_DAIFMT_CONT:
+		pcm->idleclk = 1;
+		break;
+	case SND_SOC_DAIFMT_GATED:
+		pcm->idleclk = 0;
+		break;
+	default:
+		dev_err(pcm->dev, "Invalid Clock gating request!\n");
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_DSP_A:
+		ctl |= S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
+		ctl |= S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		ctl &= ~S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
+		ctl &= ~S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
+		break;
+	default:
+		dev_err(pcm->dev, "Unsupported data format!\n");
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	writel(ctl, regs + S3C_PCM_CTL);
+
+exit:
+	spin_unlock_irqrestore(&pcm->lock, flags);
+
+	return ret;
+}
+
+static int s3c_pcm_set_clkdiv(struct snd_soc_dai *cpu_dai,
+						int div_id, int div)
+{
+	struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
+
+	switch (div_id) {
+	case S3C_PCM_SCLK_PER_FS:
+		pcm->sclk_per_fs = div;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int s3c_pcm_set_sysclk(struct snd_soc_dai *cpu_dai,
+				  int clk_id, unsigned int freq, int dir)
+{
+	struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
+	void __iomem *regs = pcm->regs;
+	u32 clkctl = readl(regs + S3C_PCM_CLKCTL);
+
+	switch (clk_id) {
+	case S3C_PCM_CLKSRC_PCLK:
+		clkctl |= S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
+		break;
+
+	case S3C_PCM_CLKSRC_MUX:
+		clkctl &= ~S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
+
+		if (clk_get_rate(pcm->cclk) != freq)
+			clk_set_rate(pcm->cclk, freq);
+
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	writel(clkctl, regs + S3C_PCM_CLKCTL);
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops s3c_pcm_dai_ops = {
+	.set_sysclk	= s3c_pcm_set_sysclk,
+	.set_clkdiv	= s3c_pcm_set_clkdiv,
+	.trigger	= s3c_pcm_trigger,
+	.hw_params	= s3c_pcm_hw_params,
+	.set_fmt	= s3c_pcm_set_fmt,
+};
+
+#define S3C_PCM_RATES  SNDRV_PCM_RATE_8000_96000
+
+#define S3C_PCM_DAI_DECLARE			\
+	.symmetric_rates = 1,					\
+	.ops = &s3c_pcm_dai_ops,				\
+	.playback = {						\
+		.channels_min	= 2,				\
+		.channels_max	= 2,				\
+		.rates		= S3C_PCM_RATES,		\
+		.formats	= SNDRV_PCM_FMTBIT_S16_LE,	\
+	},							\
+	.capture = {						\
+		.channels_min	= 2,				\
+		.channels_max	= 2,				\
+		.rates		= S3C_PCM_RATES,		\
+		.formats	= SNDRV_PCM_FMTBIT_S16_LE,	\
+	}
+
+struct snd_soc_dai_driver s3c_pcm_dai[] = {
+	[0] = {
+		.name	= "samsung-pcm.0",
+		S3C_PCM_DAI_DECLARE,
+	},
+	[1] = {
+		.name	= "samsung-pcm.1",
+		S3C_PCM_DAI_DECLARE,
+	},
+};
+EXPORT_SYMBOL_GPL(s3c_pcm_dai);
+
+static __devinit int s3c_pcm_dev_probe(struct platform_device *pdev)
+{
+	struct s3c_pcm_info *pcm;
+	struct resource *mem_res, *dmatx_res, *dmarx_res;
+	struct s3c_audio_pdata *pcm_pdata;
+	int ret;
+
+	/* Check for valid device index */
+	if ((pdev->id < 0) || pdev->id >= ARRAY_SIZE(s3c_pcm)) {
+		dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
+		return -EINVAL;
+	}
+
+	pcm_pdata = pdev->dev.platform_data;
+
+	/* Check for availability of necessary resource */
+	dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!dmatx_res) {
+		dev_err(&pdev->dev, "Unable to get PCM-TX dma resource\n");
+		return -ENXIO;
+	}
+
+	dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!dmarx_res) {
+		dev_err(&pdev->dev, "Unable to get PCM-RX dma resource\n");
+		return -ENXIO;
+	}
+
+	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem_res) {
+		dev_err(&pdev->dev, "Unable to get register resource\n");
+		return -ENXIO;
+	}
+
+	if (pcm_pdata && pcm_pdata->cfg_gpio && pcm_pdata->cfg_gpio(pdev)) {
+		dev_err(&pdev->dev, "Unable to configure gpio\n");
+		return -EINVAL;
+	}
+
+	pcm = &s3c_pcm[pdev->id];
+	pcm->dev = &pdev->dev;
+
+	spin_lock_init(&pcm->lock);
+
+	/* Default is 128fs */
+	pcm->sclk_per_fs = 128;
+
+	pcm->cclk = clk_get(&pdev->dev, "audio-bus");
+	if (IS_ERR(pcm->cclk)) {
+		dev_err(&pdev->dev, "failed to get audio-bus\n");
+		ret = PTR_ERR(pcm->cclk);
+		goto err1;
+	}
+	clk_enable(pcm->cclk);
+
+	/* record our pcm structure for later use in the callbacks */
+	dev_set_drvdata(&pdev->dev, pcm);
+
+	if (!request_mem_region(mem_res->start,
+				resource_size(mem_res), "samsung-pcm")) {
+		dev_err(&pdev->dev, "Unable to request register region\n");
+		ret = -EBUSY;
+		goto err2;
+	}
+
+	pcm->regs = ioremap(mem_res->start, 0x100);
+	if (pcm->regs == NULL) {
+		dev_err(&pdev->dev, "cannot ioremap registers\n");
+		ret = -ENXIO;
+		goto err3;
+	}
+
+	pcm->pclk = clk_get(&pdev->dev, "pcm");
+	if (IS_ERR(pcm->pclk)) {
+		dev_err(&pdev->dev, "failed to get pcm_clock\n");
+		ret = -ENOENT;
+		goto err4;
+	}
+	clk_enable(pcm->pclk);
+
+	ret = snd_soc_register_dai(&pdev->dev, &s3c_pcm_dai[pdev->id]);
+	if (ret != 0) {
+		dev_err(&pdev->dev, "failed to get pcm_clock\n");
+		goto err5;
+	}
+
+	s3c_pcm_stereo_in[pdev->id].dma_addr = mem_res->start
+							+ S3C_PCM_RXFIFO;
+	s3c_pcm_stereo_out[pdev->id].dma_addr = mem_res->start
+							+ S3C_PCM_TXFIFO;
+
+	s3c_pcm_stereo_in[pdev->id].channel = dmarx_res->start;
+	s3c_pcm_stereo_out[pdev->id].channel = dmatx_res->start;
+
+	pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id];
+	pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id];
+
+	return 0;
+
+err5:
+	clk_disable(pcm->pclk);
+	clk_put(pcm->pclk);
+err4:
+	iounmap(pcm->regs);
+err3:
+	release_mem_region(mem_res->start, resource_size(mem_res));
+err2:
+	clk_disable(pcm->cclk);
+	clk_put(pcm->cclk);
+err1:
+	return ret;
+}
+
+static __devexit int s3c_pcm_dev_remove(struct platform_device *pdev)
+{
+	struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id];
+	struct resource *mem_res;
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	iounmap(pcm->regs);
+
+	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(mem_res->start, resource_size(mem_res));
+
+	clk_disable(pcm->cclk);
+	clk_disable(pcm->pclk);
+	clk_put(pcm->pclk);
+	clk_put(pcm->cclk);
+
+	return 0;
+}
+
+static struct platform_driver s3c_pcm_driver = {
+	.probe  = s3c_pcm_dev_probe,
+	.remove = s3c_pcm_dev_remove,
+	.driver = {
+		.name = "samsung-pcm",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init s3c_pcm_init(void)
+{
+	return platform_driver_register(&s3c_pcm_driver);
+}
+module_init(s3c_pcm_init);
+
+static void __exit s3c_pcm_exit(void)
+{
+	platform_driver_unregister(&s3c_pcm_driver);
+}
+module_exit(s3c_pcm_exit);
+
+/* Module information */
+MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("S3C PCM Controller Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:samsung-pcm");
diff --git a/linux/sound/soc/s3c24xx/s3c-pcm.h b/linux/sound/soc/s3c24xx/s3c-pcm.h
new file mode 100644
index 0000000..f60baa1
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c-pcm.h
@@ -0,0 +1,124 @@
+/*  sound/soc/s3c24xx/s3c-pcm.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __S3C_PCM_H
+#define __S3C_PCM_H __FILE__
+
+/*Register Offsets */
+#define S3C_PCM_CTL	(0x00)
+#define S3C_PCM_CLKCTL	(0x04)
+#define S3C_PCM_TXFIFO	(0x08)
+#define S3C_PCM_RXFIFO	(0x0C)
+#define S3C_PCM_IRQCTL	(0x10)
+#define S3C_PCM_IRQSTAT	(0x14)
+#define S3C_PCM_FIFOSTAT	(0x18)
+#define S3C_PCM_CLRINT	(0x20)
+
+/* PCM_CTL Bit-Fields */
+#define S3C_PCM_CTL_TXDIPSTICK_MASK		(0x3f)
+#define S3C_PCM_CTL_TXDIPSTICK_SHIFT	(13)
+#define S3C_PCM_CTL_RXDIPSTICK_MASK		(0x3f)
+#define S3C_PCM_CTL_RXDIPSTICK_SHIFT	(7)
+#define S3C_PCM_CTL_TXDMA_EN		(0x1<<6)
+#define S3C_PCM_CTL_RXDMA_EN		(0x1<<5)
+#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC	(0x1<<4)
+#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC	(0x1<<3)
+#define S3C_PCM_CTL_TXFIFO_EN		(0x1<<2)
+#define S3C_PCM_CTL_RXFIFO_EN		(0x1<<1)
+#define S3C_PCM_CTL_ENABLE			(0x1<<0)
+
+/* PCM_CLKCTL Bit-Fields */
+#define S3C_PCM_CLKCTL_SERCLK_EN		(0x1<<19)
+#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK	(0x1<<18)
+#define S3C_PCM_CLKCTL_SCLKDIV_MASK		(0x1ff)
+#define S3C_PCM_CLKCTL_SYNCDIV_MASK		(0x1ff)
+#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT	(9)
+#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT	(0)
+
+/* PCM_TXFIFO Bit-Fields */
+#define S3C_PCM_TXFIFO_DVALID	(0x1<<16)
+#define S3C_PCM_TXFIFO_DATA_MSK	(0xffff<<0)
+
+/* PCM_RXFIFO Bit-Fields */
+#define S3C_PCM_RXFIFO_DVALID	(0x1<<16)
+#define S3C_PCM_RXFIFO_DATA_MSK	(0xffff<<0)
+
+/* PCM_IRQCTL Bit-Fields */
+#define S3C_PCM_IRQCTL_IRQEN		(0x1<<14)
+#define S3C_PCM_IRQCTL_WRDEN		(0x1<<12)
+#define S3C_PCM_IRQCTL_TXEMPTYEN		(0x1<<11)
+#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN	(0x1<<10)
+#define S3C_PCM_IRQCTL_TXFULLEN		(0x1<<9)
+#define S3C_PCM_IRQCTL_TXALMSTFULLEN	(0x1<<8)
+#define S3C_PCM_IRQCTL_TXSTARVEN		(0x1<<7)
+#define S3C_PCM_IRQCTL_TXERROVRFLEN		(0x1<<6)
+#define S3C_PCM_IRQCTL_RXEMPTEN		(0x1<<5)
+#define S3C_PCM_IRQCTL_RXALMSTEMPTEN	(0x1<<4)
+#define S3C_PCM_IRQCTL_RXFULLEN		(0x1<<3)
+#define S3C_PCM_IRQCTL_RXALMSTFULLEN	(0x1<<2)
+#define S3C_PCM_IRQCTL_RXSTARVEN		(0x1<<1)
+#define S3C_PCM_IRQCTL_RXERROVRFLEN		(0x1<<0)
+
+/* PCM_IRQSTAT Bit-Fields */
+#define S3C_PCM_IRQSTAT_IRQPND		(0x1<<13)
+#define S3C_PCM_IRQSTAT_WRD_XFER		(0x1<<12)
+#define S3C_PCM_IRQSTAT_TXEMPTY		(0x1<<11)
+#define S3C_PCM_IRQSTAT_TXALMSTEMPTY	(0x1<<10)
+#define S3C_PCM_IRQSTAT_TXFULL		(0x1<<9)
+#define S3C_PCM_IRQSTAT_TXALMSTFULL		(0x1<<8)
+#define S3C_PCM_IRQSTAT_TXSTARV		(0x1<<7)
+#define S3C_PCM_IRQSTAT_TXERROVRFL		(0x1<<6)
+#define S3C_PCM_IRQSTAT_RXEMPT		(0x1<<5)
+#define S3C_PCM_IRQSTAT_RXALMSTEMPT		(0x1<<4)
+#define S3C_PCM_IRQSTAT_RXFULL		(0x1<<3)
+#define S3C_PCM_IRQSTAT_RXALMSTFULL		(0x1<<2)
+#define S3C_PCM_IRQSTAT_RXSTARV		(0x1<<1)
+#define S3C_PCM_IRQSTAT_RXERROVRFL		(0x1<<0)
+
+/* PCM_FIFOSTAT Bit-Fields */
+#define S3C_PCM_FIFOSTAT_TXCNT_MSK		(0x3f<<14)
+#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY	(0x1<<13)
+#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY	(0x1<<12)
+#define S3C_PCM_FIFOSTAT_TXFIFOFULL		(0x1<<11)
+#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL	(0x1<<10)
+#define S3C_PCM_FIFOSTAT_RXCNT_MSK		(0x3f<<4)
+#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY	(0x1<<3)
+#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY	(0x1<<2)
+#define S3C_PCM_FIFOSTAT_RXFIFOFULL		(0x1<<1)
+#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL	(0x1<<0)
+
+#define S3C_PCM_CLKSRC_PCLK	0
+#define S3C_PCM_CLKSRC_MUX	1
+
+#define S3C_PCM_SCLK_PER_FS	0
+
+/**
+ * struct s3c_pcm_info - S3C PCM Controller information
+ * @dev: The parent device passed to use from the probe.
+ * @regs: The pointer to the device register block.
+ * @dma_playback: DMA information for playback channel.
+ * @dma_capture: DMA information for capture channel.
+ */
+struct s3c_pcm_info {
+	spinlock_t lock;
+	struct device	*dev;
+	void __iomem	*regs;
+
+	unsigned int sclk_per_fs;
+
+	/* Whether to keep PCMSCLK enabled even when idle(no active xfer) */
+	unsigned int idleclk;
+
+	struct clk	*pclk;
+	struct clk	*cclk;
+
+	struct s3c_dma_params	*dma_playback;
+	struct s3c_dma_params	*dma_capture;
+};
+
+#endif /* __S3C_PCM_H */
diff --git a/linux/sound/soc/s3c24xx/s3c2412-i2s.c b/linux/sound/soc/s3c24xx/s3c2412-i2s.c
new file mode 100644
index 0000000..4a861cf
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c2412-i2s.c
@@ -0,0 +1,212 @@
+/* sound/soc/s3c24xx/s3c2412-i2s.c
+ *
+ * ALSA Soc Audio Layer - S3C2412 I2S driver
+ *
+ * Copyright (c) 2006 Wolfson Microelectronics PLC.
+ *	Graeme Gregory graeme.gregory@wolfsonmicro.com
+ *	linux@wolfsonmicro.com
+ *
+ * Copyright (c) 2007, 2004-2005 Simtec Electronics
+ *	http://armlinux.simtec.co.uk/
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <mach/hardware.h>
+
+#include <mach/regs-gpio.h>
+#include <mach/dma.h>
+
+#include "s3c-dma.h"
+#include "regs-i2s-v2.h"
+#include "s3c2412-i2s.h"
+
+#define S3C2412_I2S_DEBUG 0
+
+static struct s3c2410_dma_client s3c2412_dma_client_out = {
+	.name		= "I2S PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c2412_dma_client_in = {
+	.name		= "I2S PCM Stereo in"
+};
+
+static struct s3c_dma_params s3c2412_i2s_pcm_stereo_out = {
+	.client		= &s3c2412_dma_client_out,
+	.channel	= DMACH_I2S_OUT,
+	.dma_addr	= S3C2410_PA_IIS + S3C2412_IISTXD,
+	.dma_size	= 4,
+};
+
+static struct s3c_dma_params s3c2412_i2s_pcm_stereo_in = {
+	.client		= &s3c2412_dma_client_in,
+	.channel	= DMACH_I2S_IN,
+	.dma_addr	= S3C2410_PA_IIS + S3C2412_IISRXD,
+	.dma_size	= 4,
+};
+
+static struct s3c_i2sv2_info s3c2412_i2s;
+
+static int s3c2412_i2s_probe(struct snd_soc_dai *dai)
+{
+	int ret;
+
+	pr_debug("Entered %s\n", __func__);
+
+	ret = s3c_i2sv2_probe(dai, &s3c2412_i2s, S3C2410_PA_IIS);
+	if (ret)
+		return ret;
+
+	s3c2412_i2s.dma_capture = &s3c2412_i2s_pcm_stereo_in;
+	s3c2412_i2s.dma_playback = &s3c2412_i2s_pcm_stereo_out;
+
+	s3c2412_i2s.iis_cclk = clk_get(dai->dev, "i2sclk");
+	if (s3c2412_i2s.iis_cclk == NULL) {
+		pr_err("failed to get i2sclk clock\n");
+		iounmap(s3c2412_i2s.regs);
+		return -ENODEV;
+	}
+
+	/* Set MPLL as the source for IIS CLK */
+
+	clk_set_parent(s3c2412_i2s.iis_cclk, clk_get(NULL, "mpll"));
+	clk_enable(s3c2412_i2s.iis_cclk);
+
+	s3c2412_i2s.iis_cclk = s3c2412_i2s.iis_pclk;
+
+	/* Configure the I2S pins in correct mode */
+	s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
+	s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
+	s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
+	s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
+	s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
+
+	return 0;
+}
+
+static int s3c2412_i2s_remove(struct snd_soc_dai *dai)
+{
+	clk_disable(s3c2412_i2s.iis_cclk);
+	clk_put(s3c2412_i2s.iis_cclk);
+	iounmap(s3c2412_i2s.regs);
+
+	return 0;
+}
+
+static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *cpu_dai)
+{
+	struct s3c_i2sv2_info *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+	struct s3c_dma_params *dma_data;
+	u32 iismod;
+
+	pr_debug("Entered %s\n", __func__);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = i2s->dma_playback;
+	else
+		dma_data = i2s->dma_capture;
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+	iismod = readl(i2s->regs + S3C2412_IISMOD);
+	pr_debug("%s: r: IISMOD: %x\n", __func__, iismod);
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		iismod |= S3C2412_IISMOD_8BIT;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		iismod &= ~S3C2412_IISMOD_8BIT;
+		break;
+	}
+
+	writel(iismod, i2s->regs + S3C2412_IISMOD);
+	pr_debug("%s: w: IISMOD: %x\n", __func__, iismod);
+
+	return 0;
+}
+
+#define S3C2412_I2S_RATES \
+	(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
+	SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+	SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+static struct snd_soc_dai_ops s3c2412_i2s_dai_ops = {
+	.hw_params	= s3c2412_i2s_hw_params,
+};
+
+static struct snd_soc_dai_driver s3c2412_i2s_dai = {
+	.probe		= s3c2412_i2s_probe,
+	.remove	= s3c2412_i2s_remove,
+	.playback = {
+		.channels_min	= 2,
+		.channels_max	= 2,
+		.rates		= S3C2412_I2S_RATES,
+		.formats	= SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.capture = {
+		.channels_min	= 2,
+		.channels_max	= 2,
+		.rates		= S3C2412_I2S_RATES,
+		.formats	= SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.ops = &s3c2412_i2s_dai_ops,
+};
+
+static __devinit int s3c2412_iis_dev_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_dai(&pdev->dev, &s3c2412_i2s_dai);
+}
+
+static __devexit int s3c2412_iis_dev_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dai(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver s3c2412_iis_driver = {
+	.probe  = s3c2412_iis_dev_probe,
+	.remove = s3c2412_iis_dev_remove,
+	.driver = {
+		.name = "s3c2412-iis",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init s3c2412_i2s_init(void)
+{
+	return platform_driver_register(&s3c2412_iis_driver);
+}
+module_init(s3c2412_i2s_init);
+
+static void __exit s3c2412_i2s_exit(void)
+{
+	platform_driver_unregister(&s3c2412_iis_driver);
+}
+module_exit(s3c2412_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("S3C2412 I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:s3c2412-iis");
diff --git a/linux/sound/soc/s3c24xx/s3c2412-i2s.h b/linux/sound/soc/s3c24xx/s3c2412-i2s.h
new file mode 100644
index 0000000..01a0471
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c2412-i2s.h
@@ -0,0 +1,27 @@
+/* sound/soc/s3c24xx/s3c2412-i2s.c
+ *
+ * ALSA Soc Audio Layer - S3C2412 I2S driver
+ *
+ * Copyright (c) 2007 Simtec Electronics
+ *	http://armlinux.simtec.co.uk/
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ *  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.
+*/
+
+#ifndef __SND_SOC_S3C24XX_S3C2412_I2S_H
+#define __SND_SOC_S3C24XX_S3C2412_I2S_H __FILE__
+
+#include "s3c-i2s-v2.h"
+
+#define S3C2412_DIV_BCLK	S3C_I2SV2_DIV_BCLK
+#define S3C2412_DIV_RCLK	S3C_I2SV2_DIV_RCLK
+#define S3C2412_DIV_PRESCALER	S3C_I2SV2_DIV_PRESCALER
+
+#define S3C2412_CLKSRC_PCLK	S3C_I2SV2_CLKSRC_PCLK
+#define S3C2412_CLKSRC_I2SCLK	S3C_I2SV2_CLKSRC_AUDIOBUS
+
+#endif /* __SND_SOC_S3C24XX_S3C2412_I2S_H */
diff --git a/linux/sound/soc/s3c24xx/s3c24xx-i2s.c b/linux/sound/soc/s3c24xx/s3c24xx-i2s.c
new file mode 100644
index 0000000..e060daa
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c24xx-i2s.c
@@ -0,0 +1,519 @@
+/*
+ * s3c24xx-i2s.c  --  ALSA Soc Audio Layer
+ *
+ * (c) 2006 Wolfson Microelectronics PLC.
+ * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * Copyright 2004-2005 Simtec Electronics
+ *	http://armlinux.simtec.co.uk/
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/jiffies.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+#include <mach/regs-gpio.h>
+#include <mach/regs-clock.h>
+
+#include <asm/dma.h>
+#include <mach/dma.h>
+
+#include <plat/regs-iis.h>
+
+#include "s3c-dma.h"
+#include "s3c24xx-i2s.h"
+
+static struct s3c2410_dma_client s3c24xx_dma_client_out = {
+	.name = "I2S PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c24xx_dma_client_in = {
+	.name = "I2S PCM Stereo in"
+};
+
+static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_out = {
+	.client		= &s3c24xx_dma_client_out,
+	.channel	= DMACH_I2S_OUT,
+	.dma_addr	= S3C2410_PA_IIS + S3C2410_IISFIFO,
+	.dma_size	= 2,
+};
+
+static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_in = {
+	.client		= &s3c24xx_dma_client_in,
+	.channel	= DMACH_I2S_IN,
+	.dma_addr	= S3C2410_PA_IIS + S3C2410_IISFIFO,
+	.dma_size	= 2,
+};
+
+struct s3c24xx_i2s_info {
+	void __iomem	*regs;
+	struct clk	*iis_clk;
+	u32		iiscon;
+	u32		iismod;
+	u32		iisfcon;
+	u32		iispsr;
+};
+static struct s3c24xx_i2s_info s3c24xx_i2s;
+
+static void s3c24xx_snd_txctrl(int on)
+{
+	u32 iisfcon;
+	u32 iiscon;
+	u32 iismod;
+
+	pr_debug("Entered %s\n", __func__);
+
+	iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+	iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+	iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+	pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+
+	if (on) {
+		iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE;
+		iiscon  |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN;
+		iiscon  &= ~S3C2410_IISCON_TXIDLE;
+		iismod  |= S3C2410_IISMOD_TXMODE;
+
+		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
+		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
+	} else {
+		/* note, we have to disable the FIFOs otherwise bad things
+		 * seem to happen when the DMA stops. According to the
+		 * Samsung supplied kernel, this should allow the DMA
+		 * engine and FIFOs to reset. If this isn't allowed, the
+		 * DMA engine will simply freeze randomly.
+		 */
+
+		iisfcon &= ~S3C2410_IISFCON_TXENABLE;
+		iisfcon &= ~S3C2410_IISFCON_TXDMA;
+		iiscon  |=  S3C2410_IISCON_TXIDLE;
+		iiscon  &= ~S3C2410_IISCON_TXDMAEN;
+		iismod  &= ~S3C2410_IISMOD_TXMODE;
+
+		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
+		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
+	}
+
+	pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+}
+
+static void s3c24xx_snd_rxctrl(int on)
+{
+	u32 iisfcon;
+	u32 iiscon;
+	u32 iismod;
+
+	pr_debug("Entered %s\n", __func__);
+
+	iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+	iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+	iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+	pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+
+	if (on) {
+		iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE;
+		iiscon  |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
+		iiscon  &= ~S3C2410_IISCON_RXIDLE;
+		iismod  |= S3C2410_IISMOD_RXMODE;
+
+		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
+		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
+	} else {
+		/* note, we have to disable the FIFOs otherwise bad things
+		 * seem to happen when the DMA stops. According to the
+		 * Samsung supplied kernel, this should allow the DMA
+		 * engine and FIFOs to reset. If this isn't allowed, the
+		 * DMA engine will simply freeze randomly.
+		 */
+
+		iisfcon &= ~S3C2410_IISFCON_RXENABLE;
+		iisfcon &= ~S3C2410_IISFCON_RXDMA;
+		iiscon  |= S3C2410_IISCON_RXIDLE;
+		iiscon  &= ~S3C2410_IISCON_RXDMAEN;
+		iismod  &= ~S3C2410_IISMOD_RXMODE;
+
+		writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+		writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
+		writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
+	}
+
+	pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+}
+
+/*
+ * Wait for the LR signal to allow synchronisation to the L/R clock
+ * from the codec. May only be needed for slave mode.
+ */
+static int s3c24xx_snd_lrsync(void)
+{
+	u32 iiscon;
+	int timeout = 50; /* 5ms */
+
+	pr_debug("Entered %s\n", __func__);
+
+	while (1) {
+		iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+		if (iiscon & S3C2410_IISCON_LRINDEX)
+			break;
+
+		if (!timeout--)
+			return -ETIMEDOUT;
+		udelay(100);
+	}
+
+	return 0;
+}
+
+/*
+ * Check whether CPU is the master or slave
+ */
+static inline int s3c24xx_snd_is_clkmaster(void)
+{
+	pr_debug("Entered %s\n", __func__);
+
+	return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1;
+}
+
+/*
+ * Set S3C24xx I2S DAI format
+ */
+static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+		unsigned int fmt)
+{
+	u32 iismod;
+
+	pr_debug("Entered %s\n", __func__);
+
+	iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+	pr_debug("hw_params r: IISMOD: %x \n", iismod);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		iismod |= S3C2410_IISMOD_SLAVE;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		iismod &= ~S3C2410_IISMOD_SLAVE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_LEFT_J:
+		iismod |= S3C2410_IISMOD_MSB;
+		break;
+	case SND_SOC_DAIFMT_I2S:
+		iismod &= ~S3C2410_IISMOD_MSB;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+	pr_debug("hw_params w: IISMOD: %x \n", iismod);
+	return 0;
+}
+
+static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct s3c_dma_params *dma_data;
+	u32 iismod;
+
+	pr_debug("Entered %s\n", __func__);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = &s3c24xx_i2s_pcm_stereo_out;
+	else
+		dma_data = &s3c24xx_i2s_pcm_stereo_in;
+
+	snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);
+
+	/* Working copies of register */
+	iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+	pr_debug("hw_params r: IISMOD: %x\n", iismod);
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		iismod &= ~S3C2410_IISMOD_16BIT;
+		dma_data->dma_size = 1;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		iismod |= S3C2410_IISMOD_16BIT;
+		dma_data->dma_size = 2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+	pr_debug("hw_params w: IISMOD: %x\n", iismod);
+	return 0;
+}
+
+static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+			       struct snd_soc_dai *dai)
+{
+	int ret = 0;
+	struct s3c_dma_params *dma_data =
+		snd_soc_dai_get_dma_data(dai, substream);
+
+	pr_debug("Entered %s\n", __func__);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (!s3c24xx_snd_is_clkmaster()) {
+			ret = s3c24xx_snd_lrsync();
+			if (ret)
+				goto exit_err;
+		}
+
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			s3c24xx_snd_rxctrl(1);
+		else
+			s3c24xx_snd_txctrl(1);
+
+		s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+			s3c24xx_snd_rxctrl(0);
+		else
+			s3c24xx_snd_txctrl(0);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+exit_err:
+	return ret;
+}
+
+/*
+ * Set S3C24xx Clock source
+ */
+static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
+	int clk_id, unsigned int freq, int dir)
+{
+	u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+	pr_debug("Entered %s\n", __func__);
+
+	iismod &= ~S3C2440_IISMOD_MPLL;
+
+	switch (clk_id) {
+	case S3C24XX_CLKSRC_PCLK:
+		break;
+	case S3C24XX_CLKSRC_MPLL:
+		iismod |= S3C2440_IISMOD_MPLL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+	return 0;
+}
+
+/*
+ * Set S3C24xx Clock dividers
+ */
+static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
+	int div_id, int div)
+{
+	u32 reg;
+
+	pr_debug("Entered %s\n", __func__);
+
+	switch (div_id) {
+	case S3C24XX_DIV_BCLK:
+		reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
+		writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
+		break;
+	case S3C24XX_DIV_MCLK:
+		reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
+		writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
+		break;
+	case S3C24XX_DIV_PRESCALER:
+		writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
+		reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+		writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * To avoid duplicating clock code, allow machine driver to
+ * get the clockrate from here.
+ */
+u32 s3c24xx_i2s_get_clockrate(void)
+{
+	return clk_get_rate(s3c24xx_i2s.iis_clk);
+}
+EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate);
+
+static int s3c24xx_i2s_probe(struct snd_soc_dai *dai)
+{
+	pr_debug("Entered %s\n", __func__);
+
+	s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
+	if (s3c24xx_i2s.regs == NULL)
+		return -ENXIO;
+
+	s3c24xx_i2s.iis_clk = clk_get(dai->dev, "iis");
+	if (s3c24xx_i2s.iis_clk == NULL) {
+		pr_err("failed to get iis_clock\n");
+		iounmap(s3c24xx_i2s.regs);
+		return -ENODEV;
+	}
+	clk_enable(s3c24xx_i2s.iis_clk);
+
+	/* Configure the I2S pins in correct mode */
+	s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
+	s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
+	s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
+	s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
+	s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
+
+	writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
+
+	s3c24xx_snd_txctrl(0);
+	s3c24xx_snd_rxctrl(0);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3c24xx_i2s_suspend(struct snd_soc_dai *cpu_dai)
+{
+	pr_debug("Entered %s\n", __func__);
+
+	s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+	s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+	s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+	s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR);
+
+	clk_disable(s3c24xx_i2s.iis_clk);
+
+	return 0;
+}
+
+static int s3c24xx_i2s_resume(struct snd_soc_dai *cpu_dai)
+{
+	pr_debug("Entered %s\n", __func__);
+	clk_enable(s3c24xx_i2s.iis_clk);
+
+	writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+	writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+	writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+	writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR);
+
+	return 0;
+}
+#else
+#define s3c24xx_i2s_suspend NULL
+#define s3c24xx_i2s_resume NULL
+#endif
+
+
+#define S3C24XX_I2S_RATES \
+	(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
+	SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+	SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+static struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
+	.trigger	= s3c24xx_i2s_trigger,
+	.hw_params	= s3c24xx_i2s_hw_params,
+	.set_fmt	= s3c24xx_i2s_set_fmt,
+	.set_clkdiv	= s3c24xx_i2s_set_clkdiv,
+	.set_sysclk	= s3c24xx_i2s_set_sysclk,
+};
+
+static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
+	.probe = s3c24xx_i2s_probe,
+	.suspend = s3c24xx_i2s_suspend,
+	.resume = s3c24xx_i2s_resume,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = S3C24XX_I2S_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = S3C24XX_I2S_RATES,
+		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
+	.ops = &s3c24xx_i2s_dai_ops,
+};
+
+static __devinit int s3c24xx_iis_dev_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);
+}
+
+static __devexit int s3c24xx_iis_dev_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dai(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver s3c24xx_iis_driver = {
+	.probe  = s3c24xx_iis_dev_probe,
+	.remove = s3c24xx_iis_dev_remove,
+	.driver = {
+		.name = "s3c24xx-iis",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init s3c24xx_i2s_init(void)
+{
+	return platform_driver_register(&s3c24xx_iis_driver);
+}
+module_init(s3c24xx_i2s_init);
+
+static void __exit s3c24xx_i2s_exit(void)
+{
+	platform_driver_unregister(&s3c24xx_iis_driver);
+}
+module_exit(s3c24xx_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:s3c24xx-iis");
diff --git a/linux/sound/soc/s3c24xx/s3c24xx-i2s.h b/linux/sound/soc/s3c24xx/s3c24xx-i2s.h
new file mode 100644
index 0000000..f9ca04e
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c24xx-i2s.h
@@ -0,0 +1,35 @@
+/*
+ * s3c24xx-i2s.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ *         graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ *  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.
+ *
+ *  Revision history
+ *    10th Nov 2006   Initial version.
+ */
+
+#ifndef S3C24XXI2S_H_
+#define S3C24XXI2S_H_
+
+/* clock sources */
+#define S3C24XX_CLKSRC_PCLK 0
+#define S3C24XX_CLKSRC_MPLL 1
+
+/* Clock dividers */
+#define S3C24XX_DIV_MCLK	0
+#define S3C24XX_DIV_BCLK	1
+#define S3C24XX_DIV_PRESCALER	2
+
+/* prescaler */
+#define S3C24XX_PRESCALE(a,b) \
+	(((a - 1) << S3C2410_IISPSR_INTSHIFT) | ((b - 1) << S3C2410_IISPSR_EXTSHFIT))
+
+u32 s3c24xx_i2s_get_clockrate(void);
+
+#endif /*S3C24XXI2S_H_*/
diff --git a/linux/sound/soc/s3c24xx/s3c24xx_simtec.c b/linux/sound/soc/s3c24xx/s3c24xx_simtec.c
new file mode 100644
index 0000000..c4c1114
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c24xx_simtec.c
@@ -0,0 +1,395 @@
+/* sound/soc/s3c24xx/s3c24xx_simtec.c
+ *
+ * Copyright 2009 Simtec Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <plat/audio-simtec.h>
+
+#include "s3c-dma.h"
+#include "s3c24xx-i2s.h"
+#include "s3c24xx_simtec.h"
+
+static struct s3c24xx_audio_simtec_pdata *pdata;
+static struct clk *xtal_clk;
+
+static int spk_gain;
+static int spk_unmute;
+
+/**
+ * speaker_gain_get - read the speaker gain setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be updated.
+ *
+ * Read the value for the AMP gain control.
+ */
+static int speaker_gain_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = spk_gain;
+	return 0;
+}
+
+/**
+ * speaker_gain_set - set the value of the speaker amp gain
+ * @value: The value to write.
+ */
+static void speaker_gain_set(int value)
+{
+	gpio_set_value_cansleep(pdata->amp_gain[0], value & 1);
+	gpio_set_value_cansleep(pdata->amp_gain[1], value >> 1);
+}
+
+/**
+ * speaker_gain_put - set the speaker gain setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be set.
+ *
+ * Set the value of the speaker gain from the specified
+ * @ucontrol setting.
+ *
+ * Note, if the speaker amp is muted, then we do not set a gain value
+ * as at-least one of the ICs that is fitted will try and power up even
+ * if the main control is set to off.
+ */
+static int speaker_gain_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	int value = ucontrol->value.integer.value[0];
+
+	spk_gain = value;
+
+	if (!spk_unmute)
+		speaker_gain_set(value);
+
+	return 0;
+}
+
+static const struct snd_kcontrol_new amp_gain_controls[] = {
+	SOC_SINGLE_EXT("Speaker Gain", 0, 0, 3, 0,
+		       speaker_gain_get, speaker_gain_put),
+};
+
+/**
+ * spk_unmute_state - set the unmute state of the speaker
+ * @to: zero to unmute, non-zero to ununmute.
+ */
+static void spk_unmute_state(int to)
+{
+	pr_debug("%s: to=%d\n", __func__, to);
+
+	spk_unmute = to;
+	gpio_set_value(pdata->amp_gpio, to);
+
+	/* if we're umuting, also re-set the gain */
+	if (to && pdata->amp_gain[0] > 0)
+		speaker_gain_set(spk_gain);
+}
+
+/**
+ * speaker_unmute_get - read the speaker unmute setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be updated.
+ *
+ * Read the value for the AMP gain control.
+ */
+static int speaker_unmute_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = spk_unmute;
+	return 0;
+}
+
+/**
+ * speaker_unmute_put - set the speaker unmute setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be set.
+ *
+ * Set the value of the speaker gain from the specified
+ * @ucontrol setting.
+ */
+static int speaker_unmute_put(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	spk_unmute_state(ucontrol->value.integer.value[0]);
+	return 0;
+}
+
+/* This is added as a manual control as the speaker amps create clicks
+ * when their power state is changed, which are far more noticeable than
+ * anything produced by the CODEC itself.
+ */
+static const struct snd_kcontrol_new amp_unmute_controls[] = {
+	SOC_SINGLE_EXT("Speaker Switch", 0, 0, 1, 0,
+		       speaker_unmute_get, speaker_unmute_put),
+};
+
+void simtec_audio_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	if (pdata->amp_gpio > 0) {
+		pr_debug("%s: adding amp routes\n", __func__);
+
+		snd_soc_add_controls(codec, amp_unmute_controls,
+				     ARRAY_SIZE(amp_unmute_controls));
+	}
+
+	if (pdata->amp_gain[0] > 0) {
+		pr_debug("%s: adding amp controls\n", __func__);
+		snd_soc_add_controls(codec, amp_gain_controls,
+				     ARRAY_SIZE(amp_gain_controls));
+	}
+}
+EXPORT_SYMBOL_GPL(simtec_audio_init);
+
+#define CODEC_CLOCK 12000000
+
+/**
+ * simtec_hw_params - update hardware parameters
+ * @substream: The audio substream instance.
+ * @params: The parameters requested.
+ *
+ * Update the codec data routing and configuration  settings
+ * from the supplied data.
+ */
+static int simtec_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	/* Set the CODEC as the bus clock master, I2S */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret) {
+		pr_err("%s: failed set cpu dai format\n", __func__);
+		return ret;
+	}
+
+	/* Set the CODEC as the bus clock master */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+				  SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM);
+	if (ret) {
+		pr_err("%s: failed set codec dai format\n", __func__);
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+				     CODEC_CLOCK, SND_SOC_CLOCK_IN);
+	if (ret) {
+		pr_err( "%s: failed setting codec sysclk\n", __func__);
+		return ret;
+	}
+
+	if (pdata->use_mpllin) {
+		ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_MPLL,
+					     0, SND_SOC_CLOCK_OUT);
+
+		if (ret) {
+			pr_err("%s: failed to set MPLLin as clksrc\n",
+			       __func__);
+			return ret;
+		}
+	}
+
+	if (pdata->output_cdclk) {
+		int cdclk_scale;
+
+		cdclk_scale = clk_get_rate(xtal_clk) / CODEC_CLOCK;
+		cdclk_scale--;
+
+		ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+					     cdclk_scale);
+	}
+
+	return 0;
+}
+
+static int simtec_call_startup(struct s3c24xx_audio_simtec_pdata *pd)
+{
+	/* call any board supplied startup code, this currently only
+	 * covers the bast/vr1000 which have a CPLD in the way of the
+	 * LRCLK */
+	if (pd->startup)
+		pd->startup();
+
+	return 0;
+}
+
+static struct snd_soc_ops simtec_snd_ops = {
+	.hw_params	= simtec_hw_params,
+};
+
+/**
+ * attach_gpio_amp - get and configure the necessary gpios
+ * @dev: The device we're probing.
+ * @pd: The platform data supplied by the board.
+ *
+ * If there is a GPIO based amplifier attached to the board, claim
+ * the necessary GPIO lines for it, and set default values.
+ */
+static int attach_gpio_amp(struct device *dev,
+			   struct s3c24xx_audio_simtec_pdata *pd)
+{
+	int ret;
+
+	/* attach gpio amp gain (if any) */
+	if (pdata->amp_gain[0] > 0) {
+		ret = gpio_request(pd->amp_gain[0], "gpio-amp-gain0");
+		if (ret) {
+			dev_err(dev, "cannot get amp gpio gain0\n");
+			return ret;
+		}
+
+		ret = gpio_request(pd->amp_gain[1], "gpio-amp-gain1");
+		if (ret) {
+			dev_err(dev, "cannot get amp gpio gain1\n");
+			gpio_free(pdata->amp_gain[0]);
+			return ret;
+		}
+
+		gpio_direction_output(pd->amp_gain[0], 0);
+		gpio_direction_output(pd->amp_gain[1], 0);
+	}
+
+	/* note, currently we assume GPA0 isn't valid amp */
+	if (pdata->amp_gpio > 0) {
+		ret = gpio_request(pd->amp_gpio, "gpio-amp");
+		if (ret) {
+			dev_err(dev, "cannot get amp gpio %d (%d)\n",
+				pd->amp_gpio, ret);
+			goto err_amp;
+		}
+
+		/* set the amp off at startup */
+		spk_unmute_state(0);
+	}
+
+	return 0;
+
+err_amp:
+	if (pd->amp_gain[0] > 0) {
+		gpio_free(pd->amp_gain[0]);
+		gpio_free(pd->amp_gain[1]);
+	}
+
+	return ret;
+}
+
+static void detach_gpio_amp(struct s3c24xx_audio_simtec_pdata *pd)
+{
+	if (pd->amp_gain[0] > 0) {
+		gpio_free(pd->amp_gain[0]);
+		gpio_free(pd->amp_gain[1]);
+	}
+
+	if (pd->amp_gpio > 0)
+		gpio_free(pd->amp_gpio);
+}
+
+#ifdef CONFIG_PM
+int simtec_audio_resume(struct device *dev)
+{
+	simtec_call_startup(pdata);
+	return 0;
+}
+
+const struct dev_pm_ops simtec_audio_pmops = {
+	.resume	= simtec_audio_resume,
+};
+EXPORT_SYMBOL_GPL(simtec_audio_pmops);
+#endif
+
+int __devinit simtec_audio_core_probe(struct platform_device *pdev,
+				      struct snd_soc_card *card)
+{
+	struct platform_device *snd_dev;
+	int ret;
+
+	card->dai_link->ops = &simtec_snd_ops;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data supplied\n");
+		return -EINVAL;
+	}
+
+	simtec_call_startup(pdata);
+
+	xtal_clk = clk_get(&pdev->dev, "xtal");
+	if (IS_ERR(xtal_clk)) {
+		dev_err(&pdev->dev, "could not get clkout0\n");
+		return -EINVAL;
+	}
+
+	dev_info(&pdev->dev, "xtal rate is %ld\n", clk_get_rate(xtal_clk));
+
+	ret = attach_gpio_amp(&pdev->dev, pdata);
+	if (ret)
+		goto err_clk;
+
+	snd_dev = platform_device_alloc("soc-audio", -1);
+	if (!snd_dev) {
+		dev_err(&pdev->dev, "failed to alloc soc-audio devicec\n");
+		ret = -ENOMEM;
+		goto err_gpio;
+	}
+
+	platform_set_drvdata(snd_dev, card);
+
+	ret = platform_device_add(snd_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to add soc-audio dev\n");
+		goto err_pdev;
+	}
+
+	platform_set_drvdata(pdev, snd_dev);
+	return 0;
+
+err_pdev:
+	platform_device_put(snd_dev);
+
+err_gpio:
+	detach_gpio_amp(pdata);
+
+err_clk:
+	clk_put(xtal_clk);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(simtec_audio_core_probe);
+
+int __devexit simtec_audio_remove(struct platform_device *pdev)
+{
+	struct platform_device *snd_dev = platform_get_drvdata(pdev);
+
+	platform_device_unregister(snd_dev);
+
+	detach_gpio_amp(pdata);
+	clk_put(xtal_clk);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(simtec_audio_remove);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Simtec Audio common support");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/s3c24xx_simtec.h b/linux/sound/soc/s3c24xx/s3c24xx_simtec.h
new file mode 100644
index 0000000..e63d5ff
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c24xx_simtec.h
@@ -0,0 +1,22 @@
+/* sound/soc/s3c24xx/s3c24xx_simtec.h
+ *
+ * Copyright 2009 Simtec Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+extern void simtec_audio_init(struct snd_soc_pcm_runtime *rtd);
+
+extern int simtec_audio_core_probe(struct platform_device *pdev,
+				   struct snd_soc_card *card);
+
+extern int simtec_audio_remove(struct platform_device *pdev);
+
+#ifdef CONFIG_PM
+extern const struct dev_pm_ops simtec_audio_pmops;
+#define simtec_audio_pm &simtec_audio_pmops
+#else
+#define simtec_audio_pm NULL
+#endif
diff --git a/linux/sound/soc/s3c24xx/s3c24xx_simtec_hermes.c b/linux/sound/soc/s3c24xx/s3c24xx_simtec_hermes.c
new file mode 100644
index 0000000..f884537
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c24xx_simtec_hermes.c
@@ -0,0 +1,146 @@
+/* sound/soc/s3c24xx/s3c24xx_simtec_hermes.c
+ *
+ * Copyright 2009 Simtec Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <plat/audio-simtec.h>
+
+#include "s3c-dma.h"
+#include "s3c24xx-i2s.h"
+#include "s3c24xx_simtec.h"
+
+#include "../codecs/tlv320aic3x.h"
+
+static const struct snd_soc_dapm_widget dapm_widgets[] = {
+	SND_SOC_DAPM_LINE("GSM Out", NULL),
+	SND_SOC_DAPM_LINE("GSM In", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+	SND_SOC_DAPM_LINE("Line Out", NULL),
+	SND_SOC_DAPM_LINE("ZV", NULL),
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route base_map[] = {
+	/* Headphone connected to HP{L,R}OUT and HP{L,R}COM */
+
+	{ "Headphone Jack", NULL, "HPLOUT" },
+	{ "Headphone Jack", NULL, "HPLCOM" },
+	{ "Headphone Jack", NULL, "HPROUT" },
+	{ "Headphone Jack", NULL, "HPRCOM" },
+
+	/* ZV connected to Line1 */
+
+	{ "LINE1L", NULL, "ZV" },
+	{ "LINE1R", NULL, "ZV" },
+
+	/* Line In connected to Line2 */
+
+	{ "LINE2L", NULL, "Line In" },
+	{ "LINE2R", NULL, "Line In" },
+
+	/* Microphone connected to MIC3R and MIC_BIAS */
+
+	{ "MIC3L", NULL, "Mic Jack" },
+
+	/* GSM connected to MONO_LOUT and MIC3L (in) */
+
+	{ "GSM Out", NULL, "MONO_LOUT" },
+	{ "MIC3L", NULL, "GSM In" },
+
+	/* Speaker is connected to LINEOUT{LN,LP,RN,RP}, however we are
+	 * not using the DAPM to power it up and down as there it makes
+	 * a click when powering up. */
+};
+
+/**
+ * simtec_hermes_init - initialise and add controls
+ * @codec; The codec instance to attach to.
+ *
+ * Attach our controls and configure the necessary codec
+ * mappings for our sound card instance.
+*/
+static int simtec_hermes_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	snd_soc_dapm_new_controls(codec, dapm_widgets,
+				  ARRAY_SIZE(dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, base_map, ARRAY_SIZE(base_map));
+
+	snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+	snd_soc_dapm_enable_pin(codec, "Line In");
+	snd_soc_dapm_enable_pin(codec, "Line Out");
+	snd_soc_dapm_enable_pin(codec, "Mic Jack");
+
+	simtec_audio_init(rtd);
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_dai_link simtec_dai_aic33 = {
+	.name		= "tlv320aic33",
+	.stream_name	= "TLV320AIC33",
+	.codec_name	= "tlv320aic3x-codec.0-0x1a",
+	.cpu_dai_name	= "s3c24xx-i2s",
+	.codec_dai_name = "tlv320aic3x-hifi",
+	.platform_name	= "s3c24xx-pcm-audio",
+	.init		= simtec_hermes_init,
+};
+
+/* simtec audio machine driver */
+static struct snd_soc_card snd_soc_machine_simtec_aic33 = {
+	.name		= "Simtec-Hermes",
+	.dai_link	= &simtec_dai_aic33,
+	.num_links	= 1,
+};
+
+static int __devinit simtec_audio_hermes_probe(struct platform_device *pd)
+{
+	dev_info(&pd->dev, "probing....\n");
+	return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic33);
+}
+
+static struct platform_driver simtec_audio_hermes_platdrv = {
+	.driver	= {
+		.owner	= THIS_MODULE,
+		.name	= "s3c24xx-simtec-hermes-snd",
+		.pm	= simtec_audio_pm,
+	},
+	.probe	= simtec_audio_hermes_probe,
+	.remove	= __devexit_p(simtec_audio_remove),
+};
+
+MODULE_ALIAS("platform:s3c24xx-simtec-hermes-snd");
+
+static int __init simtec_hermes_modinit(void)
+{
+	return platform_driver_register(&simtec_audio_hermes_platdrv);
+}
+
+static void __exit simtec_hermes_modexit(void)
+{
+	platform_driver_unregister(&simtec_audio_hermes_platdrv);
+}
+
+module_init(simtec_hermes_modinit);
+module_exit(simtec_hermes_modexit);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Simtec Audio support");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c b/linux/sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c
new file mode 100644
index 0000000..c096759
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c
@@ -0,0 +1,134 @@
+/* sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c
+ *
+ * Copyright 2009 Simtec Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <plat/audio-simtec.h>
+
+#include "s3c-dma.h"
+#include "s3c24xx-i2s.h"
+#include "s3c24xx_simtec.h"
+
+#include "../codecs/tlv320aic23.h"
+
+/* supported machines:
+ *
+ * Machine	Connections		AMP
+ * -------	-----------		---
+ * BAST		MIC, HPOUT, LOUT, LIN	TPA2001D1 (HPOUTL,R) (gain hardwired)
+ * VR1000	HPOUT, LIN		None
+ * VR2000	LIN, LOUT, MIC, HP	LM4871 (HPOUTL,R)
+ * DePicture	LIN, LOUT, MIC, HP	LM4871 (HPOUTL,R)
+ * Anubis	LIN, LOUT, MIC, HP	TPA2001D1 (HPOUTL,R)
+ */
+
+static const struct snd_soc_dapm_widget dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+	SND_SOC_DAPM_LINE("Line Out", NULL),
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route base_map[] = {
+	{ "Headphone Jack", NULL, "LHPOUT"},
+	{ "Headphone Jack", NULL, "RHPOUT"},
+
+	{ "Line Out", NULL, "LOUT" },
+	{ "Line Out", NULL, "ROUT" },
+
+	{ "LLINEIN", NULL, "Line In"},
+	{ "RLINEIN", NULL, "Line In"},
+
+	{ "MICIN", NULL, "Mic Jack"},
+};
+
+/**
+ * simtec_tlv320aic23_init - initialise and add controls
+ * @codec; The codec instance to attach to.
+ *
+ * Attach our controls and configure the necessary codec
+ * mappings for our sound card instance.
+*/
+static int simtec_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	snd_soc_dapm_new_controls(codec, dapm_widgets,
+				  ARRAY_SIZE(dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, base_map, ARRAY_SIZE(base_map));
+
+	snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+	snd_soc_dapm_enable_pin(codec, "Line In");
+	snd_soc_dapm_enable_pin(codec, "Line Out");
+	snd_soc_dapm_enable_pin(codec, "Mic Jack");
+
+	simtec_audio_init(rtd);
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_dai_link simtec_dai_aic23 = {
+	.name		= "tlv320aic23",
+	.stream_name	= "TLV320AIC23",
+	.codec_name	= "tlv320aic3x-codec.0-0x1a",
+	.cpu_dai_name	= "s3c24xx-i2s",
+	.codec_dai_name = "tlv320aic3x-hifi",
+	.platform_name	= "s3c24xx-pcm-audio",
+	.init		= simtec_tlv320aic23_init,
+};
+
+/* simtec audio machine driver */
+static struct snd_soc_card snd_soc_machine_simtec_aic23 = {
+	.name		= "Simtec",
+	.dai_link	= &simtec_dai_aic23,
+	.num_links	= 1,
+};
+
+static int __devinit simtec_audio_tlv320aic23_probe(struct platform_device *pd)
+{
+	return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic23);
+}
+
+static struct platform_driver simtec_audio_tlv320aic23_platdrv = {
+	.driver	= {
+		.owner	= THIS_MODULE,
+		.name	= "s3c24xx-simtec-tlv320aic23",
+		.pm	= simtec_audio_pm,
+	},
+	.probe	= simtec_audio_tlv320aic23_probe,
+	.remove	= __devexit_p(simtec_audio_remove),
+};
+
+MODULE_ALIAS("platform:s3c24xx-simtec-tlv320aic23");
+
+static int __init simtec_tlv320aic23_modinit(void)
+{
+	return platform_driver_register(&simtec_audio_tlv320aic23_platdrv);
+}
+
+static void __exit simtec_tlv320aic23_modexit(void)
+{
+	platform_driver_unregister(&simtec_audio_tlv320aic23_platdrv);
+}
+
+module_init(simtec_tlv320aic23_modinit);
+module_exit(simtec_tlv320aic23_modexit);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Simtec Audio support");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/s3c24xx_uda134x.c b/linux/sound/soc/s3c24xx/s3c24xx_uda134x.c
new file mode 100644
index 0000000..bd48ffb
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c24xx_uda134x.c
@@ -0,0 +1,368 @@
+/*
+ * Modifications by Christian Pellegrin <chripell@evolware.org>
+ *
+ * s3c24xx_uda134x.c  --  S3C24XX_UDA134X ALSA SoC Audio board driver
+ *
+ * Copyright 2007 Dension Audio Systems Ltd.
+ * Author: Zoltan Devai
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/s3c24xx_uda134x.h>
+#include <sound/uda134x.h>
+
+#include <plat/regs-iis.h>
+
+#include "s3c-dma.h"
+#include "s3c24xx-i2s.h"
+#include "../codecs/uda134x.h"
+
+
+/* #define ENFORCE_RATES 1 */
+/*
+  Unfortunately the S3C24XX in master mode has a limited capacity of
+  generating the clock for the codec. If you define this only rates
+  that are really available will be enforced. But be careful, most
+  user level application just want the usual sampling frequencies (8,
+  11.025, 22.050, 44.1 kHz) and anyway resampling is a costly
+  operation for embedded systems. So if you aren't very lucky or your
+  hardware engineer wasn't very forward-looking it's better to leave
+  this undefined. If you do so an approximate value for the requested
+  sampling rate in the range -/+ 5% will be chosen. If this in not
+  possible an error will be returned.
+*/
+
+static struct clk *xtal;
+static struct clk *pclk;
+/* this is need because we don't have a place where to keep the
+ * pointers to the clocks in each substream. We get the clocks only
+ * when we are actually using them so we don't block stuff like
+ * frequency change or oscillator power-off */
+static int clk_users;
+static DEFINE_MUTEX(clk_lock);
+
+static unsigned int rates[33 * 2];
+#ifdef ENFORCE_RATES
+static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+	.count	= ARRAY_SIZE(rates),
+	.list	= rates,
+	.mask	= 0,
+};
+#endif
+
+static struct platform_device *s3c24xx_uda134x_snd_device;
+
+static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream)
+{
+	int ret = 0;
+#ifdef ENFORCE_RATES
+	struct snd_pcm_runtime *runtime = substream->runtime;
+#endif
+
+	mutex_lock(&clk_lock);
+	pr_debug("%s %d\n", __func__, clk_users);
+	if (clk_users == 0) {
+		xtal = clk_get(&s3c24xx_uda134x_snd_device->dev, "xtal");
+		if (!xtal) {
+			printk(KERN_ERR "%s cannot get xtal\n", __func__);
+			ret = -EBUSY;
+		} else {
+			pclk = clk_get(&s3c24xx_uda134x_snd_device->dev,
+				       "pclk");
+			if (!pclk) {
+				printk(KERN_ERR "%s cannot get pclk\n",
+				       __func__);
+				clk_put(xtal);
+				ret = -EBUSY;
+			}
+		}
+		if (!ret) {
+			int i, j;
+
+			for (i = 0; i < 2; i++) {
+				int fs = i ? 256 : 384;
+
+				rates[i*33] = clk_get_rate(xtal) / fs;
+				for (j = 1; j < 33; j++)
+					rates[i*33 + j] = clk_get_rate(pclk) /
+						(j * fs);
+			}
+		}
+	}
+	clk_users += 1;
+	mutex_unlock(&clk_lock);
+	if (!ret) {
+#ifdef ENFORCE_RATES
+		ret = snd_pcm_hw_constraint_list(runtime, 0,
+						 SNDRV_PCM_HW_PARAM_RATE,
+						 &hw_constraints_rates);
+		if (ret < 0)
+			printk(KERN_ERR "%s cannot set constraints\n",
+			       __func__);
+#endif
+	}
+	return ret;
+}
+
+static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream)
+{
+	mutex_lock(&clk_lock);
+	pr_debug("%s %d\n", __func__, clk_users);
+	clk_users -= 1;
+	if (clk_users == 0) {
+		clk_put(xtal);
+		xtal = NULL;
+		clk_put(pclk);
+		pclk = NULL;
+	}
+	mutex_unlock(&clk_lock);
+}
+
+static int s3c24xx_uda134x_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned int clk = 0;
+	int ret = 0;
+	int clk_source, fs_mode;
+	unsigned long rate = params_rate(params);
+	long err, cerr;
+	unsigned int div;
+	int i, bi;
+
+	err = 999999;
+	bi = 0;
+	for (i = 0; i < 2*33; i++) {
+		cerr = rates[i] - rate;
+		if (cerr < 0)
+			cerr = -cerr;
+		if (cerr < err) {
+			err = cerr;
+			bi = i;
+		}
+	}
+	if (bi / 33 == 1)
+		fs_mode = S3C2410_IISMOD_256FS;
+	else
+		fs_mode = S3C2410_IISMOD_384FS;
+	if (bi % 33 == 0) {
+		clk_source = S3C24XX_CLKSRC_MPLL;
+		div = 1;
+	} else {
+		clk_source = S3C24XX_CLKSRC_PCLK;
+		div = bi % 33;
+	}
+	pr_debug("%s desired rate %lu, %d\n", __func__, rate, bi);
+
+	clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate;
+	pr_debug("%s will use: %s %s %d sysclk %d err %ld\n", __func__,
+		 fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS",
+		 clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK",
+		 div, clk, err);
+
+	if ((err * 100 / rate) > 5) {
+		printk(KERN_ERR "S3C24XX_UDA134X: effective frequency "
+		       "too different from desired (%ld%%)\n",
+		       err * 100 / rate);
+		return -EINVAL;
+	}
+
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk,
+			SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
+			S3C2410_IISMOD_32FS);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+			S3C24XX_PRESCALE(div, div));
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
+			SND_SOC_CLOCK_OUT);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops s3c24xx_uda134x_ops = {
+	.startup = s3c24xx_uda134x_startup,
+	.shutdown = s3c24xx_uda134x_shutdown,
+	.hw_params = s3c24xx_uda134x_hw_params,
+};
+
+static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
+	.name = "UDA134X",
+	.stream_name = "UDA134X",
+	.codec_name = "uda134x-hifi",
+	.codec_dai_name = "uda134x-hifi",
+	.cpu_dai_name = "s3c24xx-i2s",
+	.ops = &s3c24xx_uda134x_ops,
+	.platform_name	= "s3c24xx-pcm-audio",
+};
+
+static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
+	.name = "S3C24XX_UDA134X",
+	.dai_link = &s3c24xx_uda134x_dai_link,
+	.num_links = 1,
+};
+
+static struct s3c24xx_uda134x_platform_data *s3c24xx_uda134x_l3_pins;
+
+static void setdat(int v)
+{
+	gpio_set_value(s3c24xx_uda134x_l3_pins->l3_data, v > 0);
+}
+
+static void setclk(int v)
+{
+	gpio_set_value(s3c24xx_uda134x_l3_pins->l3_clk, v > 0);
+}
+
+static void setmode(int v)
+{
+	gpio_set_value(s3c24xx_uda134x_l3_pins->l3_mode, v > 0);
+}
+
+/* FIXME - This must be codec platform data but in which board file ?? */
+static struct uda134x_platform_data s3c24xx_uda134x = {
+	.l3 = {
+		.setdat = setdat,
+		.setclk = setclk,
+		.setmode = setmode,
+		.data_hold = 1,
+		.data_setup = 1,
+		.clock_high = 1,
+		.mode_hold = 1,
+		.mode = 1,
+		.mode_setup = 1,
+	},
+};
+
+static int s3c24xx_uda134x_setup_pin(int pin, char *fun)
+{
+	if (gpio_request(pin, "s3c24xx_uda134x") < 0) {
+		printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
+		       "l3 %s pin already in use", fun);
+		return -EBUSY;
+	}
+	gpio_direction_output(pin, 0);
+	return 0;
+}
+
+static int s3c24xx_uda134x_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	printk(KERN_INFO "S3C24XX_UDA134X SoC Audio driver\n");
+
+	s3c24xx_uda134x_l3_pins = pdev->dev.platform_data;
+	if (s3c24xx_uda134x_l3_pins == NULL) {
+		printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
+		       "unable to find platform data\n");
+		return -ENODEV;
+	}
+	s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power;
+	s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model;
+
+	if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data,
+				      "data") < 0)
+		return -EBUSY;
+	if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,
+				      "clk") < 0) {
+		gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
+		return -EBUSY;
+	}
+	if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode,
+				      "mode") < 0) {
+		gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
+		gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
+		return -EBUSY;
+	}
+
+	s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!s3c24xx_uda134x_snd_device) {
+		printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
+		       "Unable to register\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(s3c24xx_uda134x_snd_device,
+			     &snd_soc_s3c24xx_uda134x);
+	ret = platform_device_add(s3c24xx_uda134x_snd_device);
+	if (ret) {
+		printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: Unable to add\n");
+		platform_device_put(s3c24xx_uda134x_snd_device);
+	}
+
+	return ret;
+}
+
+static int s3c24xx_uda134x_remove(struct platform_device *pdev)
+{
+	platform_device_unregister(s3c24xx_uda134x_snd_device);
+	gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
+	gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
+	gpio_free(s3c24xx_uda134x_l3_pins->l3_mode);
+	return 0;
+}
+
+static struct platform_driver s3c24xx_uda134x_driver = {
+	.probe  = s3c24xx_uda134x_probe,
+	.remove = s3c24xx_uda134x_remove,
+	.driver = {
+		.name = "s3c24xx_uda134x",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init s3c24xx_uda134x_init(void)
+{
+	return platform_driver_register(&s3c24xx_uda134x_driver);
+}
+
+static void __exit s3c24xx_uda134x_exit(void)
+{
+	platform_driver_unregister(&s3c24xx_uda134x_driver);
+}
+
+
+module_init(s3c24xx_uda134x_init);
+module_exit(s3c24xx_uda134x_exit);
+
+MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
+MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/s3c64xx-i2s-v4.c b/linux/sound/soc/s3c24xx/s3c64xx-i2s-v4.c
new file mode 100644
index 0000000..a962847
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c64xx-i2s-v4.c
@@ -0,0 +1,230 @@
+/* sound/soc/s3c24xx/s3c64xx-i2s-v4.c
+ *
+ * ALSA SoC Audio Layer - S3C64XX I2Sv4 driver
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * 	Author: Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <plat/audio.h>
+
+#include <mach/map.h>
+#include <mach/dma.h>
+
+#include "s3c-dma.h"
+#include "regs-i2s-v2.h"
+#include "s3c64xx-i2s.h"
+
+static struct s3c2410_dma_client s3c64xx_dma_client_out = {
+	.name		= "I2Sv4 PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c64xx_dma_client_in = {
+	.name		= "I2Sv4 PCM Stereo in"
+};
+
+static struct s3c_dma_params s3c64xx_i2sv4_pcm_stereo_out;
+static struct s3c_dma_params s3c64xx_i2sv4_pcm_stereo_in;
+static struct s3c_i2sv2_info s3c64xx_i2sv4;
+
+static int s3c64xx_i2sv4_probe(struct snd_soc_dai *dai)
+{
+	struct s3c_i2sv2_info *i2s = &s3c64xx_i2sv4;
+	int ret = 0;
+
+	snd_soc_dai_set_drvdata(dai, i2s);
+
+	ret = s3c_i2sv2_probe(dai, i2s, i2s->base);
+
+	return ret;
+}
+
+static int s3c_i2sv4_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *cpu_dai)
+{
+	struct s3c_i2sv2_info *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+	struct s3c_dma_params *dma_data;
+	u32 iismod;
+
+	dev_dbg(cpu_dai->dev, "Entered %s\n", __func__);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = i2s->dma_playback;
+	else
+		dma_data = i2s->dma_capture;
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+	iismod = readl(i2s->regs + S3C2412_IISMOD);
+	dev_dbg(cpu_dai->dev, "%s: r: IISMOD: %x\n", __func__, iismod);
+
+	iismod &= ~S3C64XX_IISMOD_BLC_MASK;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		iismod |= S3C64XX_IISMOD_BLC_8BIT;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		iismod |= S3C64XX_IISMOD_BLC_24BIT;
+		break;
+	}
+
+	writel(iismod, i2s->regs + S3C2412_IISMOD);
+	dev_dbg(cpu_dai->dev, "%s: w: IISMOD: %x\n", __func__, iismod);
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops s3c64xx_i2sv4_dai_ops = {
+	.hw_params	= s3c_i2sv4_hw_params,
+};
+
+static struct snd_soc_dai_driver s3c64xx_i2s_v4_dai = {
+	.symmetric_rates = 1,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = S3C64XX_I2S_RATES,
+		.formats = S3C64XX_I2S_FMTS,
+	},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = S3C64XX_I2S_RATES,
+		.formats = S3C64XX_I2S_FMTS,
+	},
+	.probe = s3c64xx_i2sv4_probe,
+	.ops = &s3c64xx_i2sv4_dai_ops,
+};
+
+static __devinit int s3c64xx_i2sv4_dev_probe(struct platform_device *pdev)
+{
+	struct s3c_audio_pdata *i2s_pdata;
+	struct s3c_i2sv2_info *i2s;
+	struct resource *res;
+	int ret;
+
+	i2s = &s3c64xx_i2sv4;
+
+	i2s->feature |= S3C_FEATURE_CDCLKCON;
+
+	i2s->dma_capture = &s3c64xx_i2sv4_pcm_stereo_in;
+	i2s->dma_playback = &s3c64xx_i2sv4_pcm_stereo_out;
+
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "Unable to get I2S-TX dma resource\n");
+		return -ENXIO;
+	}
+	i2s->dma_playback->channel = res->start;
+
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!res) {
+		dev_err(&pdev->dev, "Unable to get I2S-RX dma resource\n");
+		return -ENXIO;
+	}
+	i2s->dma_capture->channel = res->start;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "Unable to get I2S SFR address\n");
+		return -ENXIO;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"s3c64xx-i2s-v4")) {
+		dev_err(&pdev->dev, "Unable to request SFR region\n");
+		return -EBUSY;
+	}
+	i2s->dma_capture->dma_addr = res->start + S3C2412_IISRXD;
+	i2s->dma_playback->dma_addr = res->start + S3C2412_IISTXD;
+
+	i2s->dma_capture->client = &s3c64xx_dma_client_in;
+	i2s->dma_capture->dma_size = 4;
+	i2s->dma_playback->client = &s3c64xx_dma_client_out;
+	i2s->dma_playback->dma_size = 4;
+
+	i2s->base = res->start;
+
+	i2s_pdata = pdev->dev.platform_data;
+	if (i2s_pdata && i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) {
+		dev_err(&pdev->dev, "Unable to configure gpio\n");
+		return -EINVAL;
+	}
+
+	i2s->iis_cclk = clk_get(&pdev->dev, "audio-bus");
+	if (IS_ERR(i2s->iis_cclk)) {
+		dev_err(&pdev->dev, "failed to get audio-bus\n");
+		ret = PTR_ERR(i2s->iis_cclk);
+		goto err;
+	}
+
+	clk_enable(i2s->iis_cclk);
+
+	ret = s3c_i2sv2_register_dai(&pdev->dev, pdev->id, &s3c64xx_i2s_v4_dai);
+	if (ret != 0)
+		goto err_i2sv2;
+
+	return 0;
+
+err_i2sv2:
+	clk_put(i2s->iis_cclk);
+err:
+	return ret;
+}
+
+static __devexit int s3c64xx_i2sv4_dev_remove(struct platform_device *pdev)
+{
+	struct s3c_i2sv2_info *i2s = &s3c64xx_i2sv4;
+	struct resource *res;
+
+	snd_soc_unregister_dai(&pdev->dev);
+	clk_put(i2s->iis_cclk);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res)
+		release_mem_region(res->start, resource_size(res));
+	else
+		dev_warn(&pdev->dev, "Unable to get I2S SFR address\n");
+		
+	return 0;
+}
+
+static struct platform_driver s3c64xx_i2sv4_driver = {
+	.probe  = s3c64xx_i2sv4_dev_probe,
+	.remove = s3c64xx_i2sv4_dev_remove,
+	.driver = {
+		.name = "s3c64xx-iis-v4",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init s3c64xx_i2sv4_init(void)
+{
+	return platform_driver_register(&s3c64xx_i2sv4_driver);
+}
+module_init(s3c64xx_i2sv4_init);
+
+static void __exit s3c64xx_i2sv4_exit(void)
+{
+	platform_driver_unregister(&s3c64xx_i2sv4_driver);
+}
+module_exit(s3c64xx_i2sv4_exit);
+
+/* Module information */
+MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>");
+MODULE_DESCRIPTION("S3C64XX I2Sv4 SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:s3c64xx-iis-v4");
diff --git a/linux/sound/soc/s3c24xx/s3c64xx-i2s.c b/linux/sound/soc/s3c24xx/s3c64xx-i2s.c
new file mode 100644
index 0000000..ae7acb6
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c64xx-i2s.c
@@ -0,0 +1,242 @@
+/* sound/soc/s3c24xx/s3c64xx-i2s.c
+ *
+ * ALSA SoC Audio Layer - S3C64XX I2S driver
+ *
+ * Copyright 2008 Openmoko, Inc.
+ * Copyright 2008 Simtec Electronics
+ *      Ben Dooks <ben@simtec.co.uk>
+ *      http://armlinux.simtec.co.uk/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <sound/soc.h>
+
+#include <plat/audio.h>
+
+#include <mach/map.h>
+#include <mach/dma.h>
+
+#include "s3c-dma.h"
+#include "regs-i2s-v2.h"
+#include "s3c64xx-i2s.h"
+
+/* The value should be set to maximum of the total number
+ * of I2Sv3 controllers that any supported SoC has.
+ */
+#define MAX_I2SV3	2
+
+static struct s3c2410_dma_client s3c64xx_dma_client_out = {
+	.name		= "I2S PCM Stereo out"
+};
+
+static struct s3c2410_dma_client s3c64xx_dma_client_in = {
+	.name		= "I2S PCM Stereo in"
+};
+
+static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_out[MAX_I2SV3];
+static struct s3c_dma_params s3c64xx_i2s_pcm_stereo_in[MAX_I2SV3];
+static struct s3c_i2sv2_info s3c64xx_i2s[MAX_I2SV3];
+
+struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai)
+{
+	struct s3c_i2sv2_info *i2s = snd_soc_dai_get_drvdata(dai);
+	u32 iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+	if (iismod & S3C2412_IISMOD_IMS_SYSMUX)
+		return i2s->iis_cclk;
+	else
+		return i2s->iis_pclk;
+}
+EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clock);
+
+static int s3c64xx_i2s_probe(struct snd_soc_dai *dai)
+{
+	struct s3c_i2sv2_info *i2s;
+	int ret;
+
+	if (dai->id >= MAX_I2SV3) {
+		dev_err(dai->dev, "id %d out of range\n", dai->id);
+		return -EINVAL;
+	}
+
+	i2s = &s3c64xx_i2s[dai->id];
+	snd_soc_dai_set_drvdata(dai, i2s);
+
+	i2s->iis_cclk = clk_get(dai->dev, "audio-bus");
+	if (IS_ERR(i2s->iis_cclk)) {
+		dev_err(dai->dev, "failed to get audio-bus\n");
+		ret = PTR_ERR(i2s->iis_cclk);
+		goto err;
+	}
+
+	clk_enable(i2s->iis_cclk);
+
+	ret = s3c_i2sv2_probe(dai, i2s, i2s->base);
+	if (ret)
+		goto err_clk;
+
+	return 0;
+
+err_clk:
+	clk_disable(i2s->iis_cclk);
+	clk_put(i2s->iis_cclk);
+err:
+	kfree(i2s);
+	return ret;
+}
+
+static int s3c64xx_i2s_remove(struct snd_soc_dai *dai)
+{
+	struct s3c_i2sv2_info *i2s = snd_soc_dai_get_drvdata(dai);
+
+	clk_disable(i2s->iis_cclk);
+	clk_put(i2s->iis_cclk);
+	kfree(i2s);
+	return 0;
+}
+
+static struct snd_soc_dai_ops s3c64xx_i2s_dai_ops;
+
+static struct snd_soc_dai_driver s3c64xx_i2s_dai[MAX_I2SV3] = {
+{
+	.name = "s3c64xx-i2s-0",
+	.probe = s3c64xx_i2s_probe,
+	.remove = s3c64xx_i2s_remove,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = S3C64XX_I2S_RATES,
+		.formats = S3C64XX_I2S_FMTS,},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = S3C64XX_I2S_RATES,
+		.formats = S3C64XX_I2S_FMTS,},
+	.ops = &s3c64xx_i2s_dai_ops,
+	.symmetric_rates = 1,
+}, {
+	.name = "s3c64xx-i2s-1",
+	.probe = s3c64xx_i2s_probe,
+	.remove = s3c64xx_i2s_remove,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = S3C64XX_I2S_RATES,
+		.formats = S3C64XX_I2S_FMTS,},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = S3C64XX_I2S_RATES,
+		.formats = S3C64XX_I2S_FMTS,},
+	.ops = &s3c64xx_i2s_dai_ops,
+	.symmetric_rates = 1,
+},};
+
+static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev)
+{
+	struct s3c_audio_pdata *i2s_pdata;
+	struct s3c_i2sv2_info *i2s;
+	struct resource *res;
+	int i, ret;
+
+	if (pdev->id >= MAX_I2SV3) {
+		dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
+		return -EINVAL;
+	}
+
+	i2s = &s3c64xx_i2s[pdev->id];
+
+	i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id];
+	i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id];
+
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "Unable to get I2S-TX dma resource\n");
+		return -ENXIO;
+	}
+	i2s->dma_playback->channel = res->start;
+
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!res) {
+		dev_err(&pdev->dev, "Unable to get I2S-RX dma resource\n");
+		return -ENXIO;
+	}
+	i2s->dma_capture->channel = res->start;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "Unable to get I2S SFR address\n");
+		return -ENXIO;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"s3c64xx-i2s")) {
+		dev_err(&pdev->dev, "Unable to request SFR region\n");
+		return -EBUSY;
+	}
+	i2s->base = res->start;
+
+	i2s_pdata = pdev->dev.platform_data;
+	if (i2s_pdata && i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) {
+		dev_err(&pdev->dev, "Unable to configure gpio\n");
+		return -EINVAL;
+	}
+	i2s->dma_capture->dma_addr = res->start + S3C2412_IISRXD;
+	i2s->dma_playback->dma_addr = res->start + S3C2412_IISTXD;
+
+	i2s->dma_capture->client = &s3c64xx_dma_client_in;
+	i2s->dma_capture->dma_size = 4;
+	i2s->dma_playback->client = &s3c64xx_dma_client_out;
+	i2s->dma_playback->dma_size = 4;
+
+	for (i = 0; i < ARRAY_SIZE(s3c64xx_i2s_dai); i++) {
+		ret = s3c_i2sv2_register_dai(&pdev->dev, i,
+						&s3c64xx_i2s_dai[i]);
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static __devexit int s3c64xx_iis_dev_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(s3c64xx_i2s_dai));
+	return 0;
+}
+
+static struct platform_driver s3c64xx_iis_driver = {
+	.probe  = s3c64xx_iis_dev_probe,
+	.remove = s3c64xx_iis_dev_remove,
+	.driver = {
+		.name = "s3c64xx-iis",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init s3c64xx_i2s_init(void)
+{
+	return platform_driver_register(&s3c64xx_iis_driver);
+}
+module_init(s3c64xx_i2s_init);
+
+static void __exit s3c64xx_i2s_exit(void)
+{
+	platform_driver_unregister(&s3c64xx_iis_driver);
+}
+module_exit(s3c64xx_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("S3C64XX I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:s3c64xx-iis");
diff --git a/linux/sound/soc/s3c24xx/s3c64xx-i2s.h b/linux/sound/soc/s3c24xx/s3c64xx-i2s.h
new file mode 100644
index 0000000..de4075d
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/s3c64xx-i2s.h
@@ -0,0 +1,41 @@
+/* sound/soc/s3c24xx/s3c64xx-i2s.h
+ *
+ * ALSA SoC Audio Layer - S3C64XX I2S driver
+ *
+ * Copyright 2008 Openmoko, Inc.
+ * Copyright 2008 Simtec Electronics
+ *      Ben Dooks <ben@simtec.co.uk>
+ *      http://armlinux.simtec.co.uk/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __SND_SOC_S3C24XX_S3C64XX_I2S_H
+#define __SND_SOC_S3C24XX_S3C64XX_I2S_H __FILE__
+
+struct clk;
+
+#include "s3c-i2s-v2.h"
+
+#define S3C64XX_DIV_BCLK	S3C_I2SV2_DIV_BCLK
+#define S3C64XX_DIV_RCLK	S3C_I2SV2_DIV_RCLK
+#define S3C64XX_DIV_PRESCALER	S3C_I2SV2_DIV_PRESCALER
+
+#define S3C64XX_CLKSRC_PCLK	S3C_I2SV2_CLKSRC_PCLK
+#define S3C64XX_CLKSRC_MUX	S3C_I2SV2_CLKSRC_AUDIOBUS
+#define S3C64XX_CLKSRC_CDCLK    S3C_I2SV2_CLKSRC_CDCLK
+
+#define S3C64XX_I2S_RATES \
+	(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
+	SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+	SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+#define S3C64XX_I2S_FMTS \
+	(SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
+	 SNDRV_PCM_FMTBIT_S24_LE)
+
+struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai);
+
+#endif /* __SND_SOC_S3C24XX_S3C64XX_I2S_H */
diff --git a/linux/sound/soc/s3c24xx/smartq_wm8987.c b/linux/sound/soc/s3c24xx/smartq_wm8987.c
new file mode 100644
index 0000000..dd20ca7
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/smartq_wm8987.c
@@ -0,0 +1,290 @@
+/* sound/soc/s3c24xx/smartq_wm8987.c
+ *
+ * Copyright 2010 Maurus Cuelenaere <mcuelenaere@gmail.com>
+ *
+ * Based on smdk6410_wm8987.c
+ *     Copyright 2007 Wolfson Microelectronics PLC. - linux@wolfsonmicro.com
+ *     Graeme Gregory - graeme.gregory@wolfsonmicro.com
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+
+#include "s3c-dma.h"
+#include "s3c64xx-i2s.h"
+
+#include "../codecs/wm8750.h"
+
+/*
+ * WM8987 is register compatible with WM8750, so using that as base driver.
+ */
+
+static struct snd_soc_card snd_soc_smartq;
+
+static int smartq_hifi_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+	struct s3c_i2sv2_rate_calc div;
+	unsigned int clk = 0;
+	int ret;
+
+	s3c_i2sv2_iis_calc_rate(&div, NULL, params_rate(params),
+				s3c_i2sv2_get_clock(cpu_dai));
+
+	switch (params_rate(params)) {
+	case 8000:
+	case 16000:
+	case 32000:
+	case 48000:
+	case 96000:
+		clk = 12288000;
+		break;
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+		clk = 11289600;
+		break;
+	}
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+					     SND_SOC_DAIFMT_NB_NF |
+					     SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+					   SND_SOC_DAIFMT_NB_NF |
+					   SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock for DAC and ADC */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+				     SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* set MCLK division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_RCLK, div.fs_div);
+	if (ret < 0)
+		return ret;
+
+	/* set prescaler division for sample rate */
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_PRESCALER,
+				     div.clk_div - 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * SmartQ WM8987 HiFi DAI operations.
+ */
+static struct snd_soc_ops smartq_hifi_ops = {
+	.hw_params = smartq_hifi_hw_params,
+};
+
+static struct snd_soc_jack smartq_jack;
+
+static struct snd_soc_jack_pin smartq_jack_pins[] = {
+	/* Disable speaker when headphone is plugged in */
+	{
+		.pin	= "Internal Speaker",
+		.mask	= SND_JACK_HEADPHONE,
+	},
+};
+
+static struct snd_soc_jack_gpio smartq_jack_gpios[] = {
+	{
+		.gpio		= S3C64XX_GPL(12),
+		.name		= "headphone detect",
+		.report		= SND_JACK_HEADPHONE,
+		.debounce_time	= 200,
+	},
+};
+
+static const struct snd_kcontrol_new wm8987_smartq_controls[] = {
+	SOC_DAPM_PIN_SWITCH("Internal Speaker"),
+	SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+	SOC_DAPM_PIN_SWITCH("Internal Mic"),
+};
+
+static int smartq_speaker_event(struct snd_soc_dapm_widget *w,
+				struct snd_kcontrol *k,
+				int event)
+{
+	gpio_set_value(S3C64XX_GPK(12), SND_SOC_DAPM_EVENT_OFF(event));
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget wm8987_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Internal Speaker", smartq_speaker_event),
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_MIC("Internal Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"Headphone Jack", NULL, "LOUT2"},
+	{"Headphone Jack", NULL, "ROUT2"},
+
+	{"Internal Speaker", NULL, "LOUT2"},
+	{"Internal Speaker", NULL, "ROUT2"},
+
+	{"Mic Bias", NULL, "Internal Mic"},
+	{"LINPUT2", NULL, "Mic Bias"},
+};
+
+static int smartq_wm8987_init(struct snd_soc_codec *codec)
+{
+	int err = 0;
+
+	/* Add SmartQ specific widgets */
+	snd_soc_dapm_new_controls(codec, wm8987_dapm_widgets,
+				  ARRAY_SIZE(wm8987_dapm_widgets));
+
+	/* add SmartQ specific controls */
+	err = snd_soc_add_controls(codec, wm8987_smartq_controls,
+				   ARRAY_SIZE(wm8987_smartq_controls));
+
+	if (err < 0)
+		return err;
+
+	/* setup SmartQ specific audio path */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	/* set endpoints to not connected */
+	snd_soc_dapm_nc_pin(codec, "LINPUT1");
+	snd_soc_dapm_nc_pin(codec, "RINPUT1");
+	snd_soc_dapm_nc_pin(codec, "OUT3");
+	snd_soc_dapm_nc_pin(codec, "ROUT1");
+
+	/* set endpoints to default off mode */
+	snd_soc_dapm_enable_pin(codec, "Internal Speaker");
+	snd_soc_dapm_enable_pin(codec, "Internal Mic");
+	snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+
+	err = snd_soc_dapm_sync(codec);
+	if (err)
+		return err;
+
+	/* Headphone jack detection */
+	err = snd_soc_jack_new(&snd_soc_smartq, "Headphone Jack",
+			       SND_JACK_HEADPHONE, &smartq_jack);
+	if (err)
+		return err;
+
+	err = snd_soc_jack_add_pins(&smartq_jack, ARRAY_SIZE(smartq_jack_pins),
+				    smartq_jack_pins);
+	if (err)
+		return err;
+
+	err = snd_soc_jack_add_gpios(&smartq_jack,
+				     ARRAY_SIZE(smartq_jack_gpios),
+				     smartq_jack_gpios);
+
+	return err;
+}
+
+static struct snd_soc_dai_link smartq_dai[] = {
+	{
+		.name		= "wm8987",
+		.stream_name	= "SmartQ Hi-Fi",
+		.cpu_dai_name	= "s3c64xx-i2s.0",
+		.codec_dai_name	= "wm8750-hifi",
+		.platform_name	= "s3c24xx-pcm-audio",
+		.codec_name	= "wm8750-codec.0-0x1a",
+		.init		= smartq_wm8987_init,
+		.ops		= &smartq_hifi_ops,
+	},
+};
+
+static struct snd_soc_card snd_soc_smartq = {
+	.name = "SmartQ",
+	.dai_link = smartq_dai,
+	.num_links = ARRAY_SIZE(smartq_dai),
+};
+
+static struct platform_device *smartq_snd_device;
+
+static int __init smartq_init(void)
+{
+	int ret;
+
+	if (!machine_is_smartq7() && !machine_is_smartq5()) {
+		pr_info("Only SmartQ is supported by this ASoC driver\n");
+		return -ENODEV;
+	}
+
+	smartq_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!smartq_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(smartq_snd_device, &snd_soc_smartq);
+
+	ret = platform_device_add(smartq_snd_device);
+	if (ret) {
+		platform_device_put(smartq_snd_device);
+		return ret;
+	}
+
+	/* Initialise GPIOs used by amplifiers */
+	ret = gpio_request(S3C64XX_GPK(12), "amplifiers shutdown");
+	if (ret) {
+		dev_err(&smartq_snd_device->dev, "Failed to register GPK12\n");
+		goto err_unregister_device;
+	}
+
+	/* Disable amplifiers */
+	ret = gpio_direction_output(S3C64XX_GPK(12), 1);
+	if (ret) {
+		dev_err(&smartq_snd_device->dev, "Failed to configure GPK12\n");
+		goto err_free_gpio_amp_shut;
+	}
+
+	return 0;
+
+err_free_gpio_amp_shut:
+	gpio_free(S3C64XX_GPK(12));
+err_unregister_device:
+	platform_device_unregister(smartq_snd_device);
+
+	return ret;
+}
+
+static void __exit smartq_exit(void)
+{
+	snd_soc_jack_free_gpios(&smartq_jack, ARRAY_SIZE(smartq_jack_gpios),
+				smartq_jack_gpios);
+
+	platform_device_unregister(smartq_snd_device);
+}
+
+module_init(smartq_init);
+module_exit(smartq_exit);
+
+/* Module information */
+MODULE_AUTHOR("Maurus Cuelenaere <mcuelenaere@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC SmartQ WM8987");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/smdk2443_wm9710.c b/linux/sound/soc/s3c24xx/smdk2443_wm9710.c
new file mode 100644
index 0000000..4613288
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/smdk2443_wm9710.c
@@ -0,0 +1,74 @@
+/*
+ * smdk2443_wm9710.c  --  SoC audio for smdk2443
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ *         graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "s3c-dma.h"
+#include "s3c-ac97.h"
+
+static struct snd_soc_card smdk2443;
+
+static struct snd_soc_dai_link smdk2443_dai[] = {
+{
+	.name = "AC97",
+	.stream_name = "AC97 HiFi",
+	.cpu_dai_name = "s3c-ac97",
+	.codec_dai_name = "ac97-hifi",
+	.codec_name = "ac97-codec",
+	.platform_name = "s3c24xx-pcm-audio",
+},
+};
+
+static struct snd_soc_card smdk2443 = {
+	.name = "SMDK2443",
+	.dai_link = smdk2443_dai,
+	.num_links = ARRAY_SIZE(smdk2443_dai),
+};
+
+static struct platform_device *smdk2443_snd_ac97_device;
+
+static int __init smdk2443_init(void)
+{
+	int ret;
+
+	smdk2443_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+	if (!smdk2443_snd_ac97_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(smdk2443_snd_ac97_device, &smdk2443);
+	ret = platform_device_add(smdk2443_snd_ac97_device);
+
+	if (ret)
+		platform_device_put(smdk2443_snd_ac97_device);
+
+	return ret;
+}
+
+static void __exit smdk2443_exit(void)
+{
+	platform_device_unregister(smdk2443_snd_ac97_device);
+}
+
+module_init(smdk2443_init);
+module_exit(smdk2443_exit);
+
+/* Module information */
+MODULE_AUTHOR("Graeme Gregory, graeme.gregory@wolfsonmicro.com, www.wolfsonmicro.com");
+MODULE_DESCRIPTION("ALSA SoC WM9710 SMDK2443");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/smdk64xx_wm8580.c b/linux/sound/soc/s3c24xx/smdk64xx_wm8580.c
new file mode 100644
index 0000000..052e499
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/smdk64xx_wm8580.c
@@ -0,0 +1,272 @@
+/*
+ *  smdk64xx_wm8580.c
+ *
+ *  Copyright (c) 2009 Samsung Electronics Co. Ltd
+ *  Author: Jaswinder Singh <jassi.brar@samsung.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "../codecs/wm8580.h"
+#include "s3c-dma.h"
+#include "s3c64xx-i2s.h"
+
+/*
+ * Default CFG switch settings to use this driver:
+ *
+ *   SMDK6410: Set CFG1 1-3 Off, CFG2 1-4 On
+ */
+
+/* SMDK64XX has a 12MHZ crystal attached to WM8580 */
+#define SMDK64XX_WM8580_FREQ 12000000
+
+static int smdk64xx_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	unsigned int pll_out;
+	int bfs, rfs, ret;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_U8:
+	case SNDRV_PCM_FORMAT_S8:
+		bfs = 16;
+		break;
+	case SNDRV_PCM_FORMAT_U16_LE:
+	case SNDRV_PCM_FORMAT_S16_LE:
+		bfs = 32;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* The Fvco for WM8580 PLLs must fall within [90,100]MHz.
+	 * This criterion can't be met if we request PLL output
+	 * as {8000x256, 64000x256, 11025x256}Hz.
+	 * As a wayout, we rather change rfs to a minimum value that
+	 * results in (params_rate(params) * rfs), and itself, acceptable
+	 * to both - the CODEC and the CPU.
+	 */
+	switch (params_rate(params)) {
+	case 16000:
+	case 22050:
+	case 32000:
+	case 44100:
+	case 48000:
+	case 88200:
+	case 96000:
+		rfs = 256;
+		break;
+	case 64000:
+		rfs = 384;
+		break;
+	case 8000:
+	case 11025:
+		rfs = 512;
+		break;
+	default:
+		return -EINVAL;
+	}
+	pll_out = params_rate(params) * rfs;
+
+	/* Set the Codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
+					 | SND_SOC_DAIFMT_NB_NF
+					 | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* Set the AP DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
+					 | SND_SOC_DAIFMT_NB_NF
+					 | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(cpu_dai, S3C64XX_CLKSRC_CDCLK,
+					0, SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* We use PCLK for basic ops in SoC-Slave mode */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, S3C64XX_CLKSRC_PCLK,
+					0, SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	/* Set WM8580 to drive MCLK from its PLLA */
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK,
+					WM8580_CLKSRC_PLLA);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_pll(codec_dai, WM8580_PLLA, 0,
+					SMDK64XX_WM8580_FREQ, pll_out);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_PLLA,
+				     pll_out, SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_BCLK, bfs);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_RCLK, rfs);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * SMDK64XX WM8580 DAI operations.
+ */
+static struct snd_soc_ops smdk64xx_ops = {
+	.hw_params = smdk64xx_hw_params,
+};
+
+/* SMDK64xx Playback widgets */
+static const struct snd_soc_dapm_widget wm8580_dapm_widgets_pbk[] = {
+	SND_SOC_DAPM_HP("Front", NULL),
+	SND_SOC_DAPM_HP("Center+Sub", NULL),
+	SND_SOC_DAPM_HP("Rear", NULL),
+};
+
+/* SMDK64xx Capture widgets */
+static const struct snd_soc_dapm_widget wm8580_dapm_widgets_cpt[] = {
+	SND_SOC_DAPM_MIC("MicIn", NULL),
+	SND_SOC_DAPM_LINE("LineIn", NULL),
+};
+
+/* SMDK-PAIFTX connections */
+static const struct snd_soc_dapm_route audio_map_tx[] = {
+	/* MicIn feeds AINL */
+	{"AINL", NULL, "MicIn"},
+
+	/* LineIn feeds AINL/R */
+	{"AINL", NULL, "LineIn"},
+	{"AINR", NULL, "LineIn"},
+};
+
+/* SMDK-PAIFRX connections */
+static const struct snd_soc_dapm_route audio_map_rx[] = {
+	/* Front Left/Right are fed VOUT1L/R */
+	{"Front", NULL, "VOUT1L"},
+	{"Front", NULL, "VOUT1R"},
+
+	/* Center/Sub are fed VOUT2L/R */
+	{"Center+Sub", NULL, "VOUT2L"},
+	{"Center+Sub", NULL, "VOUT2R"},
+
+	/* Rear Left/Right are fed VOUT3L/R */
+	{"Rear", NULL, "VOUT3L"},
+	{"Rear", NULL, "VOUT3R"},
+};
+
+static int smdk64xx_wm8580_init_paiftx(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	/* Add smdk64xx specific Capture widgets */
+	snd_soc_dapm_new_controls(codec, wm8580_dapm_widgets_cpt,
+				  ARRAY_SIZE(wm8580_dapm_widgets_cpt));
+
+	/* Set up PAIFTX audio path */
+	snd_soc_dapm_add_routes(codec, audio_map_tx, ARRAY_SIZE(audio_map_tx));
+
+	/* Enabling the microphone requires the fitting of a 0R
+	 * resistor to connect the line from the microphone jack.
+	 */
+	snd_soc_dapm_disable_pin(codec, "MicIn");
+
+	/* signal a DAPM event */
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static int smdk64xx_wm8580_init_paifrx(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	/* Add smdk64xx specific Playback widgets */
+	snd_soc_dapm_new_controls(codec, wm8580_dapm_widgets_pbk,
+				  ARRAY_SIZE(wm8580_dapm_widgets_pbk));
+
+	/* Set up PAIFRX audio path */
+	snd_soc_dapm_add_routes(codec, audio_map_rx, ARRAY_SIZE(audio_map_rx));
+
+	/* signal a DAPM event */
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_dai_link smdk64xx_dai[] = {
+{ /* Primary Playback i/f */
+	.name = "WM8580 PAIF RX",
+	.stream_name = "Playback",
+	.cpu_dai_name = "s3c64xx-iis-v4",
+	.codec_dai_name = "wm8580-hifi-playback",
+	.platform_name = "s3c24xx-pcm-audio",
+	.codec_name = "wm8580-codec.0-001b",
+	.init = smdk64xx_wm8580_init_paifrx,
+	.ops = &smdk64xx_ops,
+},
+{ /* Primary Capture i/f */
+	.name = "WM8580 PAIF TX",
+	.stream_name = "Capture",
+	.cpu_dai_name = "s3c64xx-iis-v4",
+	.codec_dai_name = "wm8580-hifi-capture",
+	.platform_name = "s3c24xx-pcm-audio",
+	.codec_name = "wm8580-codec.0-001b",
+	.init = smdk64xx_wm8580_init_paiftx,
+	.ops = &smdk64xx_ops,
+},
+};
+
+static struct snd_soc_card smdk64xx = {
+	.name = "SMDK64xx 5.1",
+	.dai_link = smdk64xx_dai,
+	.num_links = ARRAY_SIZE(smdk64xx_dai),
+};
+
+static struct platform_device *smdk64xx_snd_device;
+
+static int __init smdk64xx_audio_init(void)
+{
+	int ret;
+
+	smdk64xx_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!smdk64xx_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(smdk64xx_snd_device, &smdk64xx);
+	ret = platform_device_add(smdk64xx_snd_device);
+
+	if (ret)
+		platform_device_put(smdk64xx_snd_device);
+
+	return ret;
+}
+module_init(smdk64xx_audio_init);
+
+MODULE_AUTHOR("Jaswinder Singh, jassi.brar@samsung.com");
+MODULE_DESCRIPTION("ALSA SoC SMDK64XX WM8580");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/smdk_spdif.c b/linux/sound/soc/s3c24xx/smdk_spdif.c
new file mode 100644
index 0000000..c8bd904
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/smdk_spdif.c
@@ -0,0 +1,223 @@
+/*
+ * smdk_spdif.c  --  S/PDIF audio for SMDK
+ *
+ * Copyright 2010 Samsung Electronics Co. Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+
+#include <plat/devs.h>
+
+#include <sound/soc.h>
+
+#include "s3c-dma.h"
+#include "spdif.h"
+
+/* Audio clock settings are belonged to board specific part. Every
+ * board can set audio source clock setting which is matched with H/W
+ * like this function-'set_audio_clock_heirachy'.
+ */
+static int set_audio_clock_heirachy(struct platform_device *pdev)
+{
+	struct clk *fout_epll, *mout_epll, *sclk_audio0, *sclk_spdif;
+	int ret;
+
+	fout_epll = clk_get(NULL, "fout_epll");
+	if (IS_ERR(fout_epll)) {
+		printk(KERN_WARNING "%s: Cannot find fout_epll.\n",
+				__func__);
+		return -EINVAL;
+	}
+
+	mout_epll = clk_get(NULL, "mout_epll");
+	if (IS_ERR(mout_epll)) {
+		printk(KERN_WARNING "%s: Cannot find mout_epll.\n",
+				__func__);
+		ret = -EINVAL;
+		goto out1;
+	}
+
+	sclk_audio0 = clk_get(&pdev->dev, "sclk_audio");
+	if (IS_ERR(sclk_audio0)) {
+		printk(KERN_WARNING "%s: Cannot find sclk_audio.\n",
+				__func__);
+		ret = -EINVAL;
+		goto out2;
+	}
+
+	sclk_spdif = clk_get(NULL, "sclk_spdif");
+	if (IS_ERR(sclk_spdif)) {
+		printk(KERN_WARNING "%s: Cannot find sclk_spdif.\n",
+				__func__);
+		ret = -EINVAL;
+		goto out3;
+	}
+
+	/* Set audio clock heirachy for S/PDIF */
+	clk_set_parent(mout_epll, fout_epll);
+	clk_set_parent(sclk_audio0, mout_epll);
+	clk_set_parent(sclk_spdif, sclk_audio0);
+
+	clk_put(sclk_spdif);
+out3:
+	clk_put(sclk_audio0);
+out2:
+	clk_put(mout_epll);
+out1:
+	clk_put(fout_epll);
+
+	return ret;
+}
+
+/* We should haved to set clock directly on this part because of clock
+ * scheme of Samsudng SoCs did not support to set rates from abstrct
+ * clock of it's heirachy.
+ */
+static int set_audio_clock_rate(unsigned long epll_rate,
+				unsigned long audio_rate)
+{
+	struct clk *fout_epll, *sclk_spdif;
+
+	fout_epll = clk_get(NULL, "fout_epll");
+	if (IS_ERR(fout_epll)) {
+		printk(KERN_ERR "%s: failed to get fout_epll\n", __func__);
+		return -ENOENT;
+	}
+
+	clk_set_rate(fout_epll, epll_rate);
+	clk_put(fout_epll);
+
+	sclk_spdif = clk_get(NULL, "sclk_spdif");
+	if (IS_ERR(sclk_spdif)) {
+		printk(KERN_ERR "%s: failed to get sclk_spdif\n", __func__);
+		return -ENOENT;
+	}
+
+	clk_set_rate(sclk_spdif, audio_rate);
+	clk_put(sclk_spdif);
+
+	return 0;
+}
+
+static int smdk_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	unsigned long pll_out, rclk_rate;
+	int ret, ratio;
+
+	switch (params_rate(params)) {
+	case 44100:
+		pll_out = 45158400;
+		break;
+	case 32000:
+	case 48000:
+	case 96000:
+		pll_out = 49152000;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Setting ratio to 512fs helps to use S/PDIF with HDMI without
+	 * modify S/PDIF ASoC machine driver.
+	 */
+	ratio = 512;
+	rclk_rate = params_rate(params) * ratio;
+
+	/* Set audio source clock rates */
+	ret = set_audio_clock_rate(pll_out, rclk_rate);
+	if (ret < 0)
+		return ret;
+
+	/* Set S/PDIF uses internal source clock */
+	ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK,
+					rclk_rate, SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+static struct snd_soc_ops smdk_spdif_ops = {
+	.hw_params = smdk_hw_params,
+};
+
+static struct snd_soc_card smdk;
+
+static struct snd_soc_dai_link smdk_dai = {
+	.name = "S/PDIF",
+	.stream_name = "S/PDIF PCM Playback",
+	.platform_name = "s3c24xx-pcm-audio",
+	.cpu_dai_name = "samsung-spdif",
+	.codec_dai_name = "dit-hifi",
+	.codec_name = "spdif-dit",
+	.ops = &smdk_spdif_ops,
+};
+
+static struct snd_soc_card smdk = {
+	.name = "SMDK-S/PDIF",
+	.dai_link = &smdk_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *smdk_snd_spdif_dit_device;
+static struct platform_device *smdk_snd_spdif_device;
+
+static int __init smdk_init(void)
+{
+	int ret;
+
+	smdk_snd_spdif_dit_device = platform_device_alloc("spdif-dit", -1);
+	if (!smdk_snd_spdif_dit_device)
+		return -ENOMEM;
+
+	ret = platform_device_add(smdk_snd_spdif_dit_device);
+	if (ret)
+		goto err2;
+
+	smdk_snd_spdif_device = platform_device_alloc("soc-audio", -1);
+	if (!smdk_snd_spdif_device) {
+		ret = -ENOMEM;
+		goto err2;
+	}
+
+	platform_set_drvdata(smdk_snd_spdif_device, &smdk);
+
+	ret = platform_device_add(smdk_snd_spdif_device);
+	if (ret)
+		goto err1;
+
+	/* Set audio clock heirachy manually */
+	ret = set_audio_clock_heirachy(smdk_snd_spdif_device);
+	if (ret)
+		goto err1;
+
+	return 0;
+err1:
+	platform_device_put(smdk_snd_spdif_device);
+err2:
+	platform_device_put(smdk_snd_spdif_dit_device);
+	return ret;
+}
+
+static void __exit smdk_exit(void)
+{
+	platform_device_unregister(smdk_snd_spdif_device);
+}
+
+module_init(smdk_init);
+module_exit(smdk_exit);
+
+MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
+MODULE_DESCRIPTION("ALSA SoC SMDK+S/PDIF");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/smdk_wm9713.c b/linux/sound/soc/s3c24xx/smdk_wm9713.c
new file mode 100644
index 0000000..33ba8fd
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/smdk_wm9713.c
@@ -0,0 +1,107 @@
+/*
+ * smdk_wm9713.c  --  SoC audio for SMDK
+ *
+ * Copyright 2010 Samsung Electronics Co. Ltd.
+ * Author: Jaswinder Singh Brar <jassi.brar@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/soc.h>
+
+#include "s3c-dma.h"
+#include "s3c-ac97.h"
+
+static struct snd_soc_card smdk;
+
+/*
+ * Default CFG switch settings to use this driver:
+ *
+ *   SMDK6410: Set CFG1 1-3 On, CFG2 1-4 Off
+ *   SMDKC100: Set CFG6 1-3 On, CFG7 1   On
+ *   SMDKC110: Set CFGB10 1-2 Off, CFGB12 1-3 On
+ *   SMDKV210: Set CFGB10 1-2 Off, CFGB12 1-3 On
+ */
+
+/*
+ Playback (HeadPhone):-
+	$ amixer sset 'Headphone' unmute
+	$ amixer sset 'Right Headphone Out Mux' 'Headphone'
+	$ amixer sset 'Left Headphone Out Mux' 'Headphone'
+	$ amixer sset 'Right HP Mixer PCM' unmute
+	$ amixer sset 'Left HP Mixer PCM' unmute
+
+ Capture (LineIn):-
+	$ amixer sset 'Right Capture Source' 'Line'
+	$ amixer sset 'Left Capture Source' 'Line'
+*/
+
+static struct snd_soc_dai_link smdk_dai = {
+	.name = "AC97",
+	.stream_name = "AC97 PCM",
+	.platform_name = "s3c24xx-pcm-audio",
+	.cpu_dai_name = "s3c-ac97",
+	.codec_dai_name = "wm9713-hifi",
+	.codec_name = "wm9713-codec",
+};
+
+static struct snd_soc_card smdk = {
+	.name = "SMDK WM9713",
+	.dai_link = &smdk_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *smdk_snd_wm9713_device;
+static struct platform_device *smdk_snd_ac97_device;
+
+static int __init smdk_init(void)
+{
+	int ret;
+
+	smdk_snd_wm9713_device = platform_device_alloc("wm9713-codec", -1);
+	if (!smdk_snd_wm9713_device)
+		return -ENOMEM;
+
+	ret = platform_device_add(smdk_snd_wm9713_device);
+	if (ret)
+		goto err;
+
+	smdk_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+	if (!smdk_snd_ac97_device) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	platform_set_drvdata(smdk_snd_ac97_device, &smdk);
+
+	ret = platform_device_add(smdk_snd_ac97_device);
+	if (ret) {
+		platform_device_put(smdk_snd_ac97_device);
+		goto err;
+	}
+
+	return 0;
+err:
+	platform_device_put(smdk_snd_wm9713_device);
+	return ret;
+}
+
+static void __exit smdk_exit(void)
+{
+	platform_device_unregister(smdk_snd_ac97_device);
+	platform_device_unregister(smdk_snd_wm9713_device);
+}
+
+module_init(smdk_init);
+module_exit(smdk_exit);
+
+/* Module information */
+MODULE_AUTHOR("Jaswinder Singh Brar, jassi.brar@samsung.com");
+MODULE_DESCRIPTION("ALSA SoC SMDK+WM9713");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s3c24xx/spdif.c b/linux/sound/soc/s3c24xx/spdif.c
new file mode 100644
index 0000000..ce554e9
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/spdif.c
@@ -0,0 +1,501 @@
+/* sound/soc/s3c24xx/spdif.c
+ *
+ * ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ *		http://www.samsung.com/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <plat/audio.h>
+#include <mach/dma.h>
+
+#include "s3c-dma.h"
+#include "spdif.h"
+
+/* Registers */
+#define CLKCON				0x00
+#define CON				0x04
+#define BSTAS				0x08
+#define CSTAS				0x0C
+#define DATA_OUTBUF			0x10
+#define DCNT				0x14
+#define BSTAS_S				0x18
+#define DCNT_S				0x1C
+
+#define CLKCTL_MASK			0x7
+#define CLKCTL_MCLK_EXT			(0x1 << 2)
+#define CLKCTL_PWR_ON			(0x1 << 0)
+
+#define CON_MASK			0x3ffffff
+#define CON_FIFO_TH_SHIFT		19
+#define CON_FIFO_TH_MASK		(0x7 << 19)
+#define CON_USERDATA_23RDBIT		(0x1 << 12)
+
+#define CON_SW_RESET			(0x1 << 5)
+
+#define CON_MCLKDIV_MASK		(0x3 << 3)
+#define CON_MCLKDIV_256FS		(0x0 << 3)
+#define CON_MCLKDIV_384FS		(0x1 << 3)
+#define CON_MCLKDIV_512FS		(0x2 << 3)
+
+#define CON_PCM_MASK			(0x3 << 1)
+#define CON_PCM_16BIT			(0x0 << 1)
+#define CON_PCM_20BIT			(0x1 << 1)
+#define CON_PCM_24BIT			(0x2 << 1)
+
+#define CON_PCM_DATA			(0x1 << 0)
+
+#define CSTAS_MASK			0x3fffffff
+#define CSTAS_SAMP_FREQ_MASK		(0xF << 24)
+#define CSTAS_SAMP_FREQ_44		(0x0 << 24)
+#define CSTAS_SAMP_FREQ_48		(0x2 << 24)
+#define CSTAS_SAMP_FREQ_32		(0x3 << 24)
+#define CSTAS_SAMP_FREQ_96		(0xA << 24)
+
+#define CSTAS_CATEGORY_MASK		(0xFF << 8)
+#define CSTAS_CATEGORY_CODE_CDP		(0x01 << 8)
+
+#define CSTAS_NO_COPYRIGHT		(0x1 << 2)
+
+/**
+ * struct samsung_spdif_info - Samsung S/PDIF Controller information
+ * @lock: Spin lock for S/PDIF.
+ * @dev: The parent device passed to use from the probe.
+ * @regs: The pointer to the device register block.
+ * @clk_rate: Current clock rate for calcurate ratio.
+ * @pclk: The peri-clock pointer for spdif master operation.
+ * @sclk: The source clock pointer for making sync signals.
+ * @save_clkcon: Backup clkcon reg. in suspend.
+ * @save_con: Backup con reg. in suspend.
+ * @save_cstas: Backup cstas reg. in suspend.
+ * @dma_playback: DMA information for playback channel.
+ */
+struct samsung_spdif_info {
+	spinlock_t	lock;
+	struct device	*dev;
+	void __iomem	*regs;
+	unsigned long	clk_rate;
+	struct clk	*pclk;
+	struct clk	*sclk;
+	u32		saved_clkcon;
+	u32		saved_con;
+	u32		saved_cstas;
+	struct s3c_dma_params	*dma_playback;
+};
+
+static struct s3c2410_dma_client spdif_dma_client_out = {
+	.name		= "S/PDIF Stereo out",
+};
+
+static struct s3c_dma_params spdif_stereo_out;
+static struct samsung_spdif_info spdif_info;
+
+static inline struct samsung_spdif_info *to_info(struct snd_soc_dai *cpu_dai)
+{
+	return snd_soc_dai_get_drvdata(cpu_dai);
+}
+
+static void spdif_snd_txctrl(struct samsung_spdif_info *spdif, int on)
+{
+	void __iomem *regs = spdif->regs;
+	u32 clkcon;
+
+	dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+	clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
+	if (on)
+		writel(clkcon | CLKCTL_PWR_ON, regs + CLKCON);
+	else
+		writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
+}
+
+static int spdif_set_sysclk(struct snd_soc_dai *cpu_dai,
+				int clk_id, unsigned int freq, int dir)
+{
+	struct samsung_spdif_info *spdif = to_info(cpu_dai);
+	u32 clkcon;
+
+	dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+	clkcon = readl(spdif->regs + CLKCON);
+
+	if (clk_id == SND_SOC_SPDIF_INT_MCLK)
+		clkcon &= ~CLKCTL_MCLK_EXT;
+	else
+		clkcon |= CLKCTL_MCLK_EXT;
+
+	writel(clkcon, spdif->regs + CLKCON);
+
+	spdif->clk_rate = freq;
+
+	return 0;
+}
+
+static int spdif_trigger(struct snd_pcm_substream *substream, int cmd,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai);
+	unsigned long flags;
+
+	dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		spin_lock_irqsave(&spdif->lock, flags);
+		spdif_snd_txctrl(spdif, 1);
+		spin_unlock_irqrestore(&spdif->lock, flags);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		spin_lock_irqsave(&spdif->lock, flags);
+		spdif_snd_txctrl(spdif, 0);
+		spin_unlock_irqrestore(&spdif->lock, flags);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int spdif_sysclk_ratios[] = {
+	512, 384, 256,
+};
+
+static int spdif_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *socdai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai);
+	void __iomem *regs = spdif->regs;
+	struct s3c_dma_params *dma_data;
+	u32 con, clkcon, cstas;
+	unsigned long flags;
+	int i, ratio;
+
+	dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = spdif->dma_playback;
+	else {
+		dev_err(spdif->dev, "Capture is not supported\n");
+		return -EINVAL;
+	}
+
+	snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);
+
+	spin_lock_irqsave(&spdif->lock, flags);
+
+	con = readl(regs + CON) & CON_MASK;
+	cstas = readl(regs + CSTAS) & CSTAS_MASK;
+	clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
+
+	con &= ~CON_FIFO_TH_MASK;
+	con |= (0x7 << CON_FIFO_TH_SHIFT);
+	con |= CON_USERDATA_23RDBIT;
+	con |= CON_PCM_DATA;
+
+	con &= ~CON_PCM_MASK;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		con |= CON_PCM_16BIT;
+		break;
+	default:
+		dev_err(spdif->dev, "Unsupported data size.\n");
+		goto err;
+	}
+
+	ratio = spdif->clk_rate / params_rate(params);
+	for (i = 0; i < ARRAY_SIZE(spdif_sysclk_ratios); i++)
+		if (ratio == spdif_sysclk_ratios[i])
+			break;
+	if (i == ARRAY_SIZE(spdif_sysclk_ratios)) {
+		dev_err(spdif->dev, "Invalid clock ratio %ld/%d\n",
+				spdif->clk_rate, params_rate(params));
+		goto err;
+	}
+
+	con &= ~CON_MCLKDIV_MASK;
+	switch (ratio) {
+	case 256:
+		con |= CON_MCLKDIV_256FS;
+		break;
+	case 384:
+		con |= CON_MCLKDIV_384FS;
+		break;
+	case 512:
+		con |= CON_MCLKDIV_512FS;
+		break;
+	}
+
+	cstas &= ~CSTAS_SAMP_FREQ_MASK;
+	switch (params_rate(params)) {
+	case 44100:
+		cstas |= CSTAS_SAMP_FREQ_44;
+		break;
+	case 48000:
+		cstas |= CSTAS_SAMP_FREQ_48;
+		break;
+	case 32000:
+		cstas |= CSTAS_SAMP_FREQ_32;
+		break;
+	case 96000:
+		cstas |= CSTAS_SAMP_FREQ_96;
+		break;
+	default:
+		dev_err(spdif->dev, "Invalid sampling rate %d\n",
+				params_rate(params));
+		goto err;
+	}
+
+	cstas &= ~CSTAS_CATEGORY_MASK;
+	cstas |= CSTAS_CATEGORY_CODE_CDP;
+	cstas |= CSTAS_NO_COPYRIGHT;
+
+	writel(con, regs + CON);
+	writel(cstas, regs + CSTAS);
+	writel(clkcon, regs + CLKCON);
+
+	spin_unlock_irqrestore(&spdif->lock, flags);
+
+	return 0;
+err:
+	spin_unlock_irqrestore(&spdif->lock, flags);
+	return -EINVAL;
+}
+
+static void spdif_shutdown(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai);
+	void __iomem *regs = spdif->regs;
+	u32 con, clkcon;
+
+	dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+	con = readl(regs + CON) & CON_MASK;
+	clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
+
+	writel(con | CON_SW_RESET, regs + CON);
+	cpu_relax();
+
+	writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
+}
+
+#ifdef CONFIG_PM
+static int spdif_suspend(struct snd_soc_dai *cpu_dai)
+{
+	struct samsung_spdif_info *spdif = to_info(cpu_dai);
+	u32 con = spdif->saved_con;
+
+	dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+	spdif->saved_clkcon = readl(spdif->regs	+ CLKCON) & CLKCTL_MASK;
+	spdif->saved_con = readl(spdif->regs + CON) & CON_MASK;
+	spdif->saved_cstas = readl(spdif->regs + CSTAS) & CSTAS_MASK;
+
+	writel(con | CON_SW_RESET, spdif->regs + CON);
+	cpu_relax();
+
+	return 0;
+}
+
+static int spdif_resume(struct snd_soc_dai *cpu_dai)
+{
+	struct samsung_spdif_info *spdif = to_info(cpu_dai);
+
+	dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+	writel(spdif->saved_clkcon, spdif->regs	+ CLKCON);
+	writel(spdif->saved_con, spdif->regs + CON);
+	writel(spdif->saved_cstas, spdif->regs + CSTAS);
+
+	return 0;
+}
+#else
+#define spdif_suspend NULL
+#define spdif_resume NULL
+#endif
+
+static struct snd_soc_dai_ops spdif_dai_ops = {
+	.set_sysclk	= spdif_set_sysclk,
+	.trigger	= spdif_trigger,
+	.hw_params	= spdif_hw_params,
+	.shutdown	= spdif_shutdown,
+};
+
+struct snd_soc_dai_driver samsung_spdif_dai = {
+	.name = "samsung-spdif",
+	.playback = {
+		.stream_name = "S/PDIF Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = (SNDRV_PCM_RATE_32000 |
+				SNDRV_PCM_RATE_44100 |
+				SNDRV_PCM_RATE_48000 |
+				SNDRV_PCM_RATE_96000),
+		.formats = SNDRV_PCM_FMTBIT_S16_LE, },
+	.ops = &spdif_dai_ops,
+	.suspend = spdif_suspend,
+	.resume = spdif_resume,
+};
+
+static __devinit int spdif_probe(struct platform_device *pdev)
+{
+	struct s3c_audio_pdata *spdif_pdata;
+	struct resource *mem_res, *dma_res;
+	struct samsung_spdif_info *spdif;
+	int ret;
+
+	spdif_pdata = pdev->dev.platform_data;
+
+	dev_dbg(&pdev->dev, "Entered %s\n", __func__);
+
+	dma_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!dma_res) {
+		dev_err(&pdev->dev, "Unable to get dma resource.\n");
+		return -ENXIO;
+	}
+
+	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem_res) {
+		dev_err(&pdev->dev, "Unable to get register resource.\n");
+		return -ENXIO;
+	}
+
+	if (spdif_pdata && spdif_pdata->cfg_gpio
+			&& spdif_pdata->cfg_gpio(pdev)) {
+		dev_err(&pdev->dev, "Unable to configure GPIO pins\n");
+		return -EINVAL;
+	}
+
+	spdif = &spdif_info;
+	spdif->dev = &pdev->dev;
+
+	spin_lock_init(&spdif->lock);
+
+	spdif->pclk = clk_get(&pdev->dev, "spdif");
+	if (IS_ERR(spdif->pclk)) {
+		dev_err(&pdev->dev, "failed to get peri-clock\n");
+		ret = -ENOENT;
+		goto err0;
+	}
+	clk_enable(spdif->pclk);
+
+	spdif->sclk = clk_get(&pdev->dev, "sclk_spdif");
+	if (IS_ERR(spdif->sclk)) {
+		dev_err(&pdev->dev, "failed to get internal source clock\n");
+		ret = -ENOENT;
+		goto err1;
+	}
+	clk_enable(spdif->sclk);
+
+	/* Request S/PDIF Register's memory region */
+	if (!request_mem_region(mem_res->start,
+				resource_size(mem_res), "samsung-spdif")) {
+		dev_err(&pdev->dev, "Unable to request register region\n");
+		ret = -EBUSY;
+		goto err2;
+	}
+
+	spdif->regs = ioremap(mem_res->start, 0x100);
+	if (spdif->regs == NULL) {
+		dev_err(&pdev->dev, "Cannot ioremap registers\n");
+		ret = -ENXIO;
+		goto err3;
+	}
+
+	dev_set_drvdata(&pdev->dev, spdif);
+
+	ret = snd_soc_register_dai(&pdev->dev, &samsung_spdif_dai);
+	if (ret != 0) {
+		dev_err(&pdev->dev, "fail to register dai\n");
+		goto err4;
+	}
+
+	spdif_stereo_out.dma_size = 2;
+	spdif_stereo_out.client = &spdif_dma_client_out;
+	spdif_stereo_out.dma_addr = mem_res->start + DATA_OUTBUF;
+	spdif_stereo_out.channel = dma_res->start;
+
+	spdif->dma_playback = &spdif_stereo_out;
+
+	return 0;
+
+err4:
+	iounmap(spdif->regs);
+err3:
+	release_mem_region(mem_res->start, resource_size(mem_res));
+err2:
+	clk_disable(spdif->sclk);
+	clk_put(spdif->sclk);
+err1:
+	clk_disable(spdif->pclk);
+	clk_put(spdif->pclk);
+err0:
+	return ret;
+}
+
+static __devexit int spdif_remove(struct platform_device *pdev)
+{
+	struct samsung_spdif_info *spdif = &spdif_info;
+	struct resource *mem_res;
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	iounmap(spdif->regs);
+
+	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (mem_res)
+		release_mem_region(mem_res->start, resource_size(mem_res));
+
+	clk_disable(spdif->sclk);
+	clk_put(spdif->sclk);
+	clk_disable(spdif->pclk);
+	clk_put(spdif->pclk);
+
+	return 0;
+}
+
+static struct platform_driver samsung_spdif_driver = {
+	.probe	= spdif_probe,
+	.remove	= spdif_remove,
+	.driver	= {
+		.name	= "samsung-spdif",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init spdif_init(void)
+{
+	return platform_driver_register(&samsung_spdif_driver);
+}
+module_init(spdif_init);
+
+static void __exit spdif_exit(void)
+{
+	platform_driver_unregister(&samsung_spdif_driver);
+}
+module_exit(spdif_exit);
+
+MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
+MODULE_DESCRIPTION("Samsung S/PDIF Controller Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:samsung-spdif");
diff --git a/linux/sound/soc/s3c24xx/spdif.h b/linux/sound/soc/s3c24xx/spdif.h
new file mode 100644
index 0000000..3ed5559
--- /dev/null
+++ b/linux/sound/soc/s3c24xx/spdif.h
@@ -0,0 +1,19 @@
+/* sound/soc/s3c24xx/spdif.h
+ *
+ * ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ *		http://www.samsung.com/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __SND_SOC_SAMSUNG_SPDIF_H
+#define __SND_SOC_SAMSUNG_SPDIF_H	__FILE__
+
+#define SND_SOC_SPDIF_INT_MCLK		0
+#define SND_SOC_SPDIF_EXT_MCLK		1
+
+#endif	/* __SND_SOC_SAMSUNG_SPDIF_H */
diff --git a/linux/sound/soc/s6000/Kconfig b/linux/sound/soc/s6000/Kconfig
new file mode 100644
index 0000000..c74eb3d
--- /dev/null
+++ b/linux/sound/soc/s6000/Kconfig
@@ -0,0 +1,19 @@
+config SND_S6000_SOC
+	tristate "SoC Audio for the Stretch s6000 family"
+	depends on XTENSA_VARIANT_S6000
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  s6000 family chips. You will also need to select the platform
+	  to support below.
+
+config SND_S6000_SOC_I2S
+	tristate
+
+config SND_S6000_SOC_S6IPCAM
+	tristate "SoC Audio support for Stretch 6105 IP Camera"
+	depends on SND_S6000_SOC && XTENSA_PLATFORM_S6105
+	select SND_S6000_SOC_I2S
+	select SND_SOC_TLV320AIC3X
+	help
+	  Say Y if you want to add support for SoC audio on the
+	  Stretch s6105 IP Camera Reference Design.
diff --git a/linux/sound/soc/s6000/Makefile b/linux/sound/soc/s6000/Makefile
new file mode 100644
index 0000000..7a61361
--- /dev/null
+++ b/linux/sound/soc/s6000/Makefile
@@ -0,0 +1,11 @@
+# s6000 Platform Support
+snd-soc-s6000-objs := s6000-pcm.o
+snd-soc-s6000-i2s-objs := s6000-i2s.o
+
+obj-$(CONFIG_SND_S6000_SOC) += snd-soc-s6000.o
+obj-$(CONFIG_SND_S6000_SOC_I2S) += snd-soc-s6000-i2s.o
+
+# s6105 Machine Support
+snd-soc-s6ipcam-objs := s6105-ipcam.o
+
+obj-$(CONFIG_SND_S6000_SOC_S6IPCAM) += snd-soc-s6ipcam.o
diff --git a/linux/sound/soc/s6000/s6000-i2s.c b/linux/sound/soc/s6000/s6000-i2s.c
new file mode 100644
index 0000000..3052f64
--- /dev/null
+++ b/linux/sound/soc/s6000/s6000-i2s.c
@@ -0,0 +1,621 @@
+/*
+ * ALSA SoC I2S Audio Layer for the Stretch S6000 family
+ *
+ * Author:      Daniel Gloeckner, <dg@emlix.com>
+ * Copyright:   (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "s6000-i2s.h"
+#include "s6000-pcm.h"
+
+struct s6000_i2s_dev {
+	dma_addr_t sifbase;
+	u8 __iomem *scbbase;
+	unsigned int wide;
+	unsigned int channel_in;
+	unsigned int channel_out;
+	unsigned int lines_in;
+	unsigned int lines_out;
+	struct s6000_pcm_dma_params dma_params;
+};
+
+#define S6_I2S_INTERRUPT_STATUS	0x00
+#define   S6_I2S_INT_OVERRUN	1
+#define   S6_I2S_INT_UNDERRUN	2
+#define   S6_I2S_INT_ALIGNMENT	4
+#define S6_I2S_INTERRUPT_ENABLE	0x04
+#define S6_I2S_INTERRUPT_RAW	0x08
+#define S6_I2S_INTERRUPT_CLEAR	0x0C
+#define S6_I2S_INTERRUPT_SET	0x10
+#define S6_I2S_MODE		0x20
+#define   S6_I2S_DUAL		0
+#define   S6_I2S_WIDE		1
+#define S6_I2S_TX_DEFAULT	0x24
+#define S6_I2S_DATA_CFG(c)	(0x40 + 0x10 * (c))
+#define   S6_I2S_IN		0
+#define   S6_I2S_OUT		1
+#define   S6_I2S_UNUSED		2
+#define S6_I2S_INTERFACE_CFG(c)	(0x44 + 0x10 * (c))
+#define   S6_I2S_DIV_MASK	0x001fff
+#define   S6_I2S_16BIT		0x000000
+#define   S6_I2S_20BIT		0x002000
+#define   S6_I2S_24BIT		0x004000
+#define   S6_I2S_32BIT		0x006000
+#define   S6_I2S_BITS_MASK	0x006000
+#define   S6_I2S_MEM_16BIT	0x000000
+#define   S6_I2S_MEM_32BIT	0x008000
+#define   S6_I2S_MEM_MASK	0x008000
+#define   S6_I2S_CHANNELS_SHIFT	16
+#define   S6_I2S_CHANNELS_MASK	0x030000
+#define   S6_I2S_SCK_IN		0x000000
+#define   S6_I2S_SCK_OUT	0x040000
+#define   S6_I2S_SCK_DIR	0x040000
+#define   S6_I2S_WS_IN		0x000000
+#define   S6_I2S_WS_OUT		0x080000
+#define   S6_I2S_WS_DIR		0x080000
+#define   S6_I2S_LEFT_FIRST	0x000000
+#define   S6_I2S_RIGHT_FIRST	0x100000
+#define   S6_I2S_FIRST		0x100000
+#define   S6_I2S_CUR_SCK	0x200000
+#define   S6_I2S_CUR_WS		0x400000
+#define S6_I2S_ENABLE(c)	(0x48 + 0x10 * (c))
+#define   S6_I2S_DISABLE_IF	0x02
+#define   S6_I2S_ENABLE_IF	0x03
+#define   S6_I2S_IS_BUSY	0x04
+#define   S6_I2S_DMA_ACTIVE	0x08
+#define   S6_I2S_IS_ENABLED	0x10
+
+#define S6_I2S_NUM_LINES	4
+
+#define S6_I2S_SIF_PORT0	0x0000000
+#define S6_I2S_SIF_PORT1	0x0000080 /* docs say 0x0000010 */
+
+static inline void s6_i2s_write_reg(struct s6000_i2s_dev *dev, int reg, u32 val)
+{
+	writel(val, dev->scbbase + reg);
+}
+
+static inline u32 s6_i2s_read_reg(struct s6000_i2s_dev *dev, int reg)
+{
+	return readl(dev->scbbase + reg);
+}
+
+static inline void s6_i2s_mod_reg(struct s6000_i2s_dev *dev, int reg,
+				  u32 mask, u32 val)
+{
+	val ^= s6_i2s_read_reg(dev, reg) & ~mask;
+	s6_i2s_write_reg(dev, reg, val);
+}
+
+static void s6000_i2s_start_channel(struct s6000_i2s_dev *dev, int channel)
+{
+	int i, j, cur, prev;
+
+	/*
+	 * Wait for WCLK to toggle 5 times before enabling the channel
+	 * s6000 Family Datasheet 3.6.4:
+	 *   "At least two cycles of WS must occur between commands
+	 *    to disable or enable the interface"
+	 */
+	j = 0;
+	prev = ~S6_I2S_CUR_WS;
+	for (i = 1000000; --i && j < 6; ) {
+		cur = s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(channel))
+		       & S6_I2S_CUR_WS;
+		if (prev != cur) {
+			prev = cur;
+			j++;
+		}
+	}
+	if (j < 6)
+		printk(KERN_WARNING "s6000-i2s: timeout waiting for WCLK\n");
+
+	s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_ENABLE_IF);
+}
+
+static void s6000_i2s_stop_channel(struct s6000_i2s_dev *dev, int channel)
+{
+	s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_DISABLE_IF);
+}
+
+static void s6000_i2s_start(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	int channel;
+
+	channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+			dev->channel_out : dev->channel_in;
+
+	s6000_i2s_start_channel(dev, channel);
+}
+
+static void s6000_i2s_stop(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	int channel;
+
+	channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+			dev->channel_out : dev->channel_in;
+
+	s6000_i2s_stop_channel(dev, channel);
+}
+
+static int s6000_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+			     int after)
+{
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) ^ !after)
+			s6000_i2s_start(substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (!after)
+			s6000_i2s_stop(substream);
+	}
+	return 0;
+}
+
+static unsigned int s6000_i2s_int_sources(struct s6000_i2s_dev *dev)
+{
+	unsigned int pending;
+	pending = s6_i2s_read_reg(dev, S6_I2S_INTERRUPT_RAW);
+	pending &= S6_I2S_INT_ALIGNMENT |
+		   S6_I2S_INT_UNDERRUN |
+		   S6_I2S_INT_OVERRUN;
+	s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, pending);
+
+	return pending;
+}
+
+static unsigned int s6000_i2s_check_xrun(struct snd_soc_dai *cpu_dai)
+{
+	struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+	unsigned int errors;
+	unsigned int ret;
+
+	errors = s6000_i2s_int_sources(dev);
+	if (likely(!errors))
+		return 0;
+
+	ret = 0;
+	if (errors & S6_I2S_INT_ALIGNMENT)
+		printk(KERN_ERR "s6000-i2s: WCLK misaligned\n");
+	if (errors & S6_I2S_INT_UNDERRUN)
+		ret |= 1 << SNDRV_PCM_STREAM_PLAYBACK;
+	if (errors & S6_I2S_INT_OVERRUN)
+		ret |= 1 << SNDRV_PCM_STREAM_CAPTURE;
+	return ret;
+}
+
+static void s6000_i2s_wait_disabled(struct s6000_i2s_dev *dev)
+{
+	int channel;
+	int n = 50;
+	for (channel = 0; channel < 2; channel++) {
+		while (--n >= 0) {
+			int v = s6_i2s_read_reg(dev, S6_I2S_ENABLE(channel));
+			if ((v & S6_I2S_IS_ENABLED)
+			    || !(v & (S6_I2S_DMA_ACTIVE | S6_I2S_IS_BUSY)))
+				break;
+			udelay(20);
+		}
+	}
+	if (n < 0)
+		printk(KERN_WARNING "s6000-i2s: timeout disabling interfaces");
+}
+
+static int s6000_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+				   unsigned int fmt)
+{
+	struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 w;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		w = S6_I2S_SCK_IN | S6_I2S_WS_IN;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		w = S6_I2S_SCK_OUT | S6_I2S_WS_IN;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		w = S6_I2S_SCK_IN | S6_I2S_WS_OUT;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		w = S6_I2S_SCK_OUT | S6_I2S_WS_OUT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		w |= S6_I2S_LEFT_FIRST;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		w |= S6_I2S_RIGHT_FIRST;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(0),
+		       S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);
+	s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(1),
+		       S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);
+
+	return 0;
+}
+
+static int s6000_i2s_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
+{
+	struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+	if (!div || (div & 1) || div > (S6_I2S_DIV_MASK + 1) * 2)
+		return -EINVAL;
+
+	s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(div_id),
+		       S6_I2S_DIV_MASK, div / 2 - 1);
+	return 0;
+}
+
+static int s6000_i2s_hw_params(struct snd_pcm_substream *substream,
+			       struct snd_pcm_hw_params *params,
+			       struct snd_soc_dai *dai)
+{
+	struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+	int interf;
+	u32 w = 0;
+
+	if (dev->wide)
+		interf = 0;
+	else {
+		w |= (((params_channels(params) - 2) / 2)
+		      << S6_I2S_CHANNELS_SHIFT) & S6_I2S_CHANNELS_MASK;
+		interf = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+				? dev->channel_out : dev->channel_in;
+	}
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		w |= S6_I2S_16BIT | S6_I2S_MEM_16BIT;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		w |= S6_I2S_32BIT | S6_I2S_MEM_32BIT;
+		break;
+	default:
+		printk(KERN_WARNING "s6000-i2s: unsupported PCM format %x\n",
+		       params_format(params));
+		return -EINVAL;
+	}
+
+	if (s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(interf))
+	     & S6_I2S_IS_ENABLED) {
+		printk(KERN_ERR "s6000-i2s: interface already enabled\n");
+		return -EBUSY;
+	}
+
+	s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(interf),
+		       S6_I2S_CHANNELS_MASK|S6_I2S_MEM_MASK|S6_I2S_BITS_MASK,
+		       w);
+
+	return 0;
+}
+
+static int s6000_i2s_dai_probe(struct snd_soc_dai *dai)
+{
+	struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+	struct s6000_snd_platform_data *pdata = dai->dev->platform_data;
+
+	if (!pdata)
+		return -EINVAL;
+
+	dai->capture_dma_data = &dev->dma_params;
+	dai->playback_dma_data = &dev->dma_params;
+
+	dev->wide = pdata->wide;
+	dev->channel_in = pdata->channel_in;
+	dev->channel_out = pdata->channel_out;
+	dev->lines_in = pdata->lines_in;
+	dev->lines_out = pdata->lines_out;
+
+	s6_i2s_write_reg(dev, S6_I2S_MODE,
+			 dev->wide ? S6_I2S_WIDE : S6_I2S_DUAL);
+
+	if (dev->wide) {
+		int i;
+
+		if (dev->lines_in + dev->lines_out > S6_I2S_NUM_LINES)
+			return -EINVAL;
+
+		dev->channel_in = 0;
+		dev->channel_out = 1;
+		dai->driver->capture.channels_min = 2 * dev->lines_in;
+		dai->driver->capture.channels_max = dai->driver->capture.channels_min;
+		dai->driver->playback.channels_min = 2 * dev->lines_out;
+		dai->driver->playback.channels_max = dai->driver->playback.channels_min;
+
+		for (i = 0; i < dev->lines_out; i++)
+			s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_OUT);
+
+		for (; i < S6_I2S_NUM_LINES - dev->lines_in; i++)
+			s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i),
+					 S6_I2S_UNUSED);
+
+		for (; i < S6_I2S_NUM_LINES; i++)
+			s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_IN);
+	} else {
+		unsigned int cfg[2] = {S6_I2S_UNUSED, S6_I2S_UNUSED};
+
+		if (dev->lines_in > 1 || dev->lines_out > 1)
+			return -EINVAL;
+
+		dai->driver->capture.channels_min = 2 * dev->lines_in;
+		dai->driver->capture.channels_max = 8 * dev->lines_in;
+		dai->driver->playback.channels_min = 2 * dev->lines_out;
+		dai->driver->playback.channels_max = 8 * dev->lines_out;
+
+		if (dev->lines_in)
+			cfg[dev->channel_in] = S6_I2S_IN;
+		if (dev->lines_out)
+			cfg[dev->channel_out] = S6_I2S_OUT;
+
+		s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(0), cfg[0]);
+		s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(1), cfg[1]);
+	}
+
+	if (dev->lines_out) {
+		if (dev->lines_in) {
+			if (!dev->dma_params.dma_out)
+				return -ENODEV;
+		} else {
+			dev->dma_params.dma_out = dev->dma_params.dma_in;
+			dev->dma_params.dma_in = 0;
+		}
+	}
+	dev->dma_params.sif_in = dev->sifbase + (dev->channel_in ?
+					S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);
+	dev->dma_params.sif_out = dev->sifbase + (dev->channel_out ?
+					S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);
+	dev->dma_params.same_rate = pdata->same_rate | pdata->wide;
+	return 0;
+}
+
+#define S6000_I2S_RATES	(SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \
+			 SNDRV_PCM_RATE_8000_192000)
+#define S6000_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops s6000_i2s_dai_ops = {
+	.set_fmt = s6000_i2s_set_dai_fmt,
+	.set_clkdiv = s6000_i2s_set_clkdiv,
+	.hw_params = s6000_i2s_hw_params,
+};
+
+static struct snd_soc_dai_driver s6000_i2s_dai = {
+	.probe = s6000_i2s_dai_probe,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 8,
+		.formats = S6000_I2S_FORMATS,
+		.rates = S6000_I2S_RATES,
+		.rate_min = 0,
+		.rate_max = 1562500,
+	},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 8,
+		.formats = S6000_I2S_FORMATS,
+		.rates = S6000_I2S_RATES,
+		.rate_min = 0,
+		.rate_max = 1562500,
+	},
+	.ops = &s6000_i2s_dai_ops,
+};
+
+static int __devinit s6000_i2s_probe(struct platform_device *pdev)
+{
+	struct s6000_i2s_dev *dev;
+	struct resource *scbmem, *sifmem, *region, *dma1, *dma2;
+	u8 __iomem *mmio;
+	int ret;
+
+	scbmem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!scbmem) {
+		dev_err(&pdev->dev, "no mem resource?\n");
+		ret = -ENODEV;
+		goto err_release_none;
+	}
+
+	region = request_mem_region(scbmem->start, resource_size(scbmem),
+								pdev->name);
+	if (!region) {
+		dev_err(&pdev->dev, "I2S SCB region already claimed\n");
+		ret = -EBUSY;
+		goto err_release_none;
+	}
+
+	mmio = ioremap(scbmem->start, resource_size(scbmem));
+	if (!mmio) {
+		dev_err(&pdev->dev, "can't ioremap SCB region\n");
+		ret = -ENOMEM;
+		goto err_release_scb;
+	}
+
+	sifmem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (!sifmem) {
+		dev_err(&pdev->dev, "no second mem resource?\n");
+		ret = -ENODEV;
+		goto err_release_map;
+	}
+
+	region = request_mem_region(sifmem->start, resource_size(sifmem),
+								pdev->name);
+	if (!region) {
+		dev_err(&pdev->dev, "I2S SIF region already claimed\n");
+		ret = -EBUSY;
+		goto err_release_map;
+	}
+
+	dma1 = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!dma1) {
+		dev_err(&pdev->dev, "no dma resource?\n");
+		ret = -ENODEV;
+		goto err_release_sif;
+	}
+
+	region = request_mem_region(dma1->start, resource_size(dma1),
+								pdev->name);
+	if (!region) {
+		dev_err(&pdev->dev, "I2S DMA region already claimed\n");
+		ret = -EBUSY;
+		goto err_release_sif;
+	}
+
+	dma2 = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (dma2) {
+		region = request_mem_region(dma2->start, resource_size(dma2),
+								pdev->name);
+		if (!region) {
+			dev_err(&pdev->dev,
+				"I2S DMA region already claimed\n");
+			ret = -EBUSY;
+			goto err_release_dma1;
+		}
+	}
+
+	dev = kzalloc(sizeof(struct s6000_i2s_dev), GFP_KERNEL);
+	if (!dev) {
+		ret = -ENOMEM;
+		goto err_release_dma2;
+	}
+	dev_set_drvdata(&pdev->dev, dev);
+
+	dev->sifbase = sifmem->start;
+	dev->scbbase = mmio;
+
+	s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);
+	s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR,
+			 S6_I2S_INT_ALIGNMENT |
+			 S6_I2S_INT_UNDERRUN |
+			 S6_I2S_INT_OVERRUN);
+
+	s6000_i2s_stop_channel(dev, 0);
+	s6000_i2s_stop_channel(dev, 1);
+	s6000_i2s_wait_disabled(dev);
+
+	dev->dma_params.check_xrun = s6000_i2s_check_xrun;
+	dev->dma_params.trigger = s6000_i2s_trigger;
+	dev->dma_params.dma_in = dma1->start;
+	dev->dma_params.dma_out = dma2 ? dma2->start : 0;
+	dev->dma_params.irq = platform_get_irq(pdev, 0);
+	if (dev->dma_params.irq < 0) {
+		dev_err(&pdev->dev, "no irq resource?\n");
+		ret = -ENODEV;
+		goto err_release_dev;
+	}
+
+	s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE,
+			 S6_I2S_INT_ALIGNMENT |
+			 S6_I2S_INT_UNDERRUN |
+			 S6_I2S_INT_OVERRUN);
+
+	ret = snd_soc_register_dai(&pdev->dev, &s6000_i2s_dai);
+	if (ret)
+		goto err_release_dev;
+
+	return 0;
+
+err_release_dev:
+	kfree(dev);
+err_release_dma2:
+	if (dma2)
+		release_mem_region(dma2->start, resource_size(dma2));
+err_release_dma1:
+	release_mem_region(dma1->start, resource_size(dma1));
+err_release_sif:
+	release_mem_region(sifmem->start, resource_size(sifmem));
+err_release_map:
+	iounmap(mmio);
+err_release_scb:
+	release_mem_region(scbmem->start, resource_size(scbmem));
+err_release_none:
+	return ret;
+}
+
+static void __devexit s6000_i2s_remove(struct platform_device *pdev)
+{
+	struct s6000_i2s_dev *dev = dev_get_drvdata(&pdev->dev);
+	struct resource *region;
+	void __iomem *mmio = dev->scbbase;
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	s6000_i2s_stop_channel(dev, 0);
+	s6000_i2s_stop_channel(dev, 1);
+
+	s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);
+	kfree(dev);
+
+	region = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	release_mem_region(region->start, resource_size(region));
+
+	region = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (region)
+		release_mem_region(region->start, resource_size(region));
+
+	region = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(region->start, resource_size(region));
+
+	iounmap(mmio);
+	region = platform_get_resource(pdev, IORESOURCE_IO, 0);
+	release_mem_region(region->start, resource_size(region));
+}
+
+static struct platform_driver s6000_i2s_driver = {
+	.probe  = s6000_i2s_probe,
+	.remove = __devexit_p(s6000_i2s_remove),
+	.driver = {
+		.name   = "s6000-i2s",
+		.owner  = THIS_MODULE,
+	},
+};
+
+static int __init s6000_i2s_init(void)
+{
+	return platform_driver_register(&s6000_i2s_driver);
+}
+module_init(s6000_i2s_init);
+
+static void __exit s6000_i2s_exit(void)
+{
+	platform_driver_unregister(&s6000_i2s_driver);
+}
+module_exit(s6000_i2s_exit);
+
+MODULE_AUTHOR("Daniel Gloeckner");
+MODULE_DESCRIPTION("Stretch s6000 family I2S SoC Interface");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s6000/s6000-i2s.h b/linux/sound/soc/s6000/s6000-i2s.h
new file mode 100644
index 0000000..86aa192
--- /dev/null
+++ b/linux/sound/soc/s6000/s6000-i2s.h
@@ -0,0 +1,23 @@
+/*
+ * ALSA SoC I2S Audio Layer for the Stretch s6000 family
+ *
+ * Author:      Daniel Gloeckner, <dg@emlix.com>
+ * Copyright:   (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _S6000_I2S_H
+#define _S6000_I2S_H
+
+struct s6000_snd_platform_data {
+	int lines_in;
+	int lines_out;
+	int channel_in;
+	int channel_out;
+	int wide;
+	int same_rate;
+};
+#endif
diff --git a/linux/sound/soc/s6000/s6000-pcm.c b/linux/sound/soc/s6000/s6000-pcm.c
new file mode 100644
index 0000000..ab3ccae
--- /dev/null
+++ b/linux/sound/soc/s6000/s6000-pcm.c
@@ -0,0 +1,537 @@
+/*
+ * ALSA PCM interface for the Stetch s6000 family
+ *
+ * Author:      Daniel Gloeckner, <dg@emlix.com>
+ * Copyright:   (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/dma.h>
+#include <variant/dmac.h>
+
+#include "s6000-pcm.h"
+
+#define S6_PCM_PREALLOCATE_SIZE (96 * 1024)
+#define S6_PCM_PREALLOCATE_MAX  (2048 * 1024)
+
+static struct snd_pcm_hardware s6000_pcm_hardware = {
+	.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+		 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_JOINT_DUPLEX),
+	.formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE),
+	.rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \
+		  SNDRV_PCM_RATE_8000_192000),
+	.rate_min = 0,
+	.rate_max = 1562500,
+	.channels_min = 2,
+	.channels_max = 8,
+	.buffer_bytes_max = 0x7ffffff0,
+	.period_bytes_min = 16,
+	.period_bytes_max = 0xfffff0,
+	.periods_min = 2,
+	.periods_max = 1024, /* no limit */
+	.fifo_size = 0,
+};
+
+struct s6000_runtime_data {
+	spinlock_t lock;
+	int period;		/* current DMA period */
+};
+
+static void s6000_pcm_enqueue_dma(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct s6000_runtime_data *prtd = runtime->private_data;
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct s6000_pcm_dma_params *par;
+	int channel;
+	unsigned int period_size;
+	unsigned int dma_offset;
+	dma_addr_t dma_pos;
+	dma_addr_t src, dst;
+
+	par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+	period_size = snd_pcm_lib_period_bytes(substream);
+	dma_offset = prtd->period * period_size;
+	dma_pos = runtime->dma_addr + dma_offset;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		src = dma_pos;
+		dst = par->sif_out;
+		channel = par->dma_out;
+	} else {
+		src = par->sif_in;
+		dst = dma_pos;
+		channel = par->dma_in;
+	}
+
+	if (!s6dmac_channel_enabled(DMA_MASK_DMAC(channel),
+				    DMA_INDEX_CHNL(channel)))
+		return;
+
+	if (s6dmac_fifo_full(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel))) {
+		printk(KERN_ERR "s6000-pcm: fifo full\n");
+		return;
+	}
+
+	BUG_ON(period_size & 15);
+	s6dmac_put_fifo(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel),
+			src, dst, period_size);
+
+	prtd->period++;
+	if (unlikely(prtd->period >= runtime->periods))
+		prtd->period = 0;
+}
+
+static irqreturn_t s6000_pcm_irq(int irq, void *data)
+{
+	struct snd_pcm *pcm = data;
+	struct snd_soc_pcm_runtime *runtime = pcm->private_data;
+	struct s6000_runtime_data *prtd;
+	unsigned int has_xrun;
+	int i, ret = IRQ_NONE;
+
+	for (i = 0; i < 2; ++i) {
+		struct snd_pcm_substream *substream = pcm->streams[i].substream;
+		struct s6000_pcm_dma_params *params =
+					snd_soc_dai_get_dma_data(runtime->cpu_dai, substream);
+		u32 channel;
+		unsigned int pending;
+
+		if (substream == SNDRV_PCM_STREAM_PLAYBACK)
+			channel = params->dma_out;
+		else
+			channel = params->dma_in;
+
+		has_xrun = params->check_xrun(runtime->cpu_dai);
+
+		if (!channel)
+			continue;
+
+		if (unlikely(has_xrun & (1 << i)) &&
+		    substream->runtime &&
+		    snd_pcm_running(substream)) {
+			dev_dbg(pcm->dev, "xrun\n");
+			snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+			ret = IRQ_HANDLED;
+		}
+
+		pending = s6dmac_int_sources(DMA_MASK_DMAC(channel),
+					     DMA_INDEX_CHNL(channel));
+
+		if (pending & 1) {
+			ret = IRQ_HANDLED;
+			if (likely(substream->runtime &&
+				   snd_pcm_running(substream))) {
+				snd_pcm_period_elapsed(substream);
+				dev_dbg(pcm->dev, "period elapsed %x %x\n",
+				       s6dmac_cur_src(DMA_MASK_DMAC(channel),
+						   DMA_INDEX_CHNL(channel)),
+				       s6dmac_cur_dst(DMA_MASK_DMAC(channel),
+						   DMA_INDEX_CHNL(channel)));
+				prtd = substream->runtime->private_data;
+				spin_lock(&prtd->lock);
+				s6000_pcm_enqueue_dma(substream);
+				spin_unlock(&prtd->lock);
+			}
+		}
+
+		if (unlikely(pending & ~7)) {
+			if (pending & (1 << 3))
+				printk(KERN_WARNING
+				       "s6000-pcm: DMA %x Underflow\n",
+				       channel);
+			if (pending & (1 << 4))
+				printk(KERN_WARNING
+				       "s6000-pcm: DMA %x Overflow\n",
+				       channel);
+			if (pending & 0x1e0)
+				printk(KERN_WARNING
+				       "s6000-pcm: DMA %x Master Error "
+				       "(mask %x)\n",
+				       channel, pending >> 5);
+
+		}
+	}
+
+	return ret;
+}
+
+static int s6000_pcm_start(struct snd_pcm_substream *substream)
+{
+	struct s6000_runtime_data *prtd = substream->runtime->private_data;
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct s6000_pcm_dma_params *par;
+	unsigned long flags;
+	int srcinc;
+	u32 dma;
+
+	par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+	spin_lock_irqsave(&prtd->lock, flags);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		srcinc = 1;
+		dma = par->dma_out;
+	} else {
+		srcinc = 0;
+		dma = par->dma_in;
+	}
+	s6dmac_enable_chan(DMA_MASK_DMAC(dma), DMA_INDEX_CHNL(dma),
+			   1 /* priority 1 (0 is max) */,
+			   0 /* peripheral requests w/o xfer length mode */,
+			   srcinc /* source address increment */,
+			   srcinc^1 /* destination address increment */,
+			   0 /* chunksize 0 (skip impossible on this dma) */,
+			   0 /* source skip after chunk (impossible) */,
+			   0 /* destination skip after chunk (impossible) */,
+			   4 /* 16 byte burst size */,
+			   -1 /* don't conserve bandwidth */,
+			   0 /* low watermark irq descriptor threshold */,
+			   0 /* disable hardware timestamps */,
+			   1 /* enable channel */);
+
+	s6000_pcm_enqueue_dma(substream);
+	s6000_pcm_enqueue_dma(substream);
+
+	spin_unlock_irqrestore(&prtd->lock, flags);
+
+	return 0;
+}
+
+static int s6000_pcm_stop(struct snd_pcm_substream *substream)
+{
+	struct s6000_runtime_data *prtd = substream->runtime->private_data;
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct s6000_pcm_dma_params *par;
+	unsigned long flags;
+	u32 channel;
+
+	par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		channel = par->dma_out;
+	else
+		channel = par->dma_in;
+
+	s6dmac_set_terminal_count(DMA_MASK_DMAC(channel),
+				  DMA_INDEX_CHNL(channel), 0);
+
+	spin_lock_irqsave(&prtd->lock, flags);
+
+	s6dmac_disable_chan(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel));
+
+	spin_unlock_irqrestore(&prtd->lock, flags);
+
+	return 0;
+}
+
+static int s6000_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct s6000_pcm_dma_params *par;
+	int ret;
+
+	par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+	ret = par->trigger(substream, cmd, 0);
+	if (ret < 0)
+		return ret;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		ret = s6000_pcm_start(substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		ret = s6000_pcm_stop(substream);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	if (ret < 0)
+		return ret;
+
+	return par->trigger(substream, cmd, 1);
+}
+
+static int s6000_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct s6000_runtime_data *prtd = substream->runtime->private_data;
+
+	prtd->period = 0;
+
+	return 0;
+}
+
+static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct s6000_pcm_dma_params *par;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct s6000_runtime_data *prtd = runtime->private_data;
+	unsigned long flags;
+	unsigned int offset;
+	dma_addr_t count;
+
+	par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+	spin_lock_irqsave(&prtd->lock, flags);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		count = s6dmac_cur_src(DMA_MASK_DMAC(par->dma_out),
+				       DMA_INDEX_CHNL(par->dma_out));
+	else
+		count = s6dmac_cur_dst(DMA_MASK_DMAC(par->dma_in),
+				       DMA_INDEX_CHNL(par->dma_in));
+
+	count -= runtime->dma_addr;
+
+	spin_unlock_irqrestore(&prtd->lock, flags);
+
+	offset = bytes_to_frames(runtime, count);
+	if (unlikely(offset >= runtime->buffer_size))
+		offset = 0;
+
+	return offset;
+}
+
+static int s6000_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct s6000_pcm_dma_params *par;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct s6000_runtime_data *prtd;
+	int ret;
+
+	par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+	snd_soc_set_runtime_hwparams(substream, &s6000_pcm_hardware);
+
+	ret = snd_pcm_hw_constraint_step(runtime, 0,
+					 SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 16);
+	if (ret < 0)
+		return ret;
+	ret = snd_pcm_hw_constraint_step(runtime, 0,
+					 SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16);
+	if (ret < 0)
+		return ret;
+	ret = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0)
+		return ret;
+
+	if (par->same_rate) {
+		int rate;
+		spin_lock(&par->lock); /* needed? */
+		rate = par->rate;
+		spin_unlock(&par->lock);
+		if (rate != -1) {
+			ret = snd_pcm_hw_constraint_minmax(runtime,
+							SNDRV_PCM_HW_PARAM_RATE,
+							rate, rate);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	prtd = kzalloc(sizeof(struct s6000_runtime_data), GFP_KERNEL);
+	if (prtd == NULL)
+		return -ENOMEM;
+
+	spin_lock_init(&prtd->lock);
+
+	runtime->private_data = prtd;
+
+	return 0;
+}
+
+static int s6000_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct s6000_runtime_data *prtd = runtime->private_data;
+
+	kfree(prtd);
+
+	return 0;
+}
+
+static int s6000_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct s6000_pcm_dma_params *par;
+	int ret;
+	ret = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+	if (ret < 0) {
+		printk(KERN_WARNING "s6000-pcm: allocation of memory failed\n");
+		return ret;
+	}
+
+	par = snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+	if (par->same_rate) {
+		spin_lock(&par->lock);
+		if (par->rate == -1 ||
+		    !(par->in_use & ~(1 << substream->stream))) {
+			par->rate = params_rate(hw_params);
+			par->in_use |= 1 << substream->stream;
+		} else if (params_rate(hw_params) != par->rate) {
+			snd_pcm_lib_free_pages(substream);
+			par->in_use &= ~(1 << substream->stream);
+			ret = -EBUSY;
+		}
+		spin_unlock(&par->lock);
+	}
+	return ret;
+}
+
+static int s6000_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+	struct s6000_pcm_dma_params *par =
+		snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream);
+
+	spin_lock(&par->lock);
+	par->in_use &= ~(1 << substream->stream);
+	if (!par->in_use)
+		par->rate = -1;
+	spin_unlock(&par->lock);
+
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static struct snd_pcm_ops s6000_pcm_ops = {
+	.open = 	s6000_pcm_open,
+	.close = 	s6000_pcm_close,
+	.ioctl = 	snd_pcm_lib_ioctl,
+	.hw_params = 	s6000_pcm_hw_params,
+	.hw_free = 	s6000_pcm_hw_free,
+	.trigger =	s6000_pcm_trigger,
+	.prepare = 	s6000_pcm_prepare,
+	.pointer = 	s6000_pcm_pointer,
+};
+
+static void s6000_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_soc_pcm_runtime *runtime = pcm->private_data;
+	struct s6000_pcm_dma_params *params =
+		snd_soc_dai_get_dma_data(runtime->cpu_dai, pcm->streams[0].substream);
+
+	free_irq(params->irq, pcm);
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static u64 s6000_pcm_dmamask = DMA_BIT_MASK(32);
+
+static int s6000_pcm_new(struct snd_card *card,
+			 struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+	struct snd_soc_pcm_runtime *runtime = pcm->private_data;
+	struct s6000_pcm_dma_params *params;
+	int res;
+
+	params = snd_soc_dai_get_dma_data(runtime->cpu_dai,
+			pcm->streams[0].substream);
+
+	if (!card->dev->dma_mask)
+		card->dev->dma_mask = &s6000_pcm_dmamask;
+	if (!card->dev->coherent_dma_mask)
+		card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+	if (params->dma_in) {
+		s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_in),
+				    DMA_INDEX_CHNL(params->dma_in));
+		s6dmac_int_sources(DMA_MASK_DMAC(params->dma_in),
+				   DMA_INDEX_CHNL(params->dma_in));
+	}
+
+	if (params->dma_out) {
+		s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_out),
+				    DMA_INDEX_CHNL(params->dma_out));
+		s6dmac_int_sources(DMA_MASK_DMAC(params->dma_out),
+				   DMA_INDEX_CHNL(params->dma_out));
+	}
+
+	res = request_irq(params->irq, s6000_pcm_irq, IRQF_SHARED,
+			  "s6000-audio", pcm);
+	if (res) {
+		printk(KERN_ERR "s6000-pcm couldn't get IRQ\n");
+		return res;
+	}
+
+	res = snd_pcm_lib_preallocate_pages_for_all(pcm,
+						    SNDRV_DMA_TYPE_DEV,
+						    card->dev,
+						    S6_PCM_PREALLOCATE_SIZE,
+						    S6_PCM_PREALLOCATE_MAX);
+	if (res)
+		printk(KERN_WARNING "s6000-pcm: preallocation failed\n");
+
+	spin_lock_init(&params->lock);
+	params->in_use = 0;
+	params->rate = -1;
+	return 0;
+}
+
+static struct snd_soc_platform_driver s6000_soc_platform = {
+	.ops =		&s6000_pcm_ops,
+	.pcm_new = 	s6000_pcm_new,
+	.pcm_free = 	s6000_pcm_free,
+};
+
+static int __devinit s6000_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &s6000_soc_platform);
+}
+
+static int __devexit s6000_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver s6000_pcm_driver = {
+	.driver = {
+			.name = "s6000-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = s6000_soc_platform_probe,
+	.remove = __devexit_p(s6000_soc_platform_remove),
+};
+
+static int __init snd_s6000_pcm_init(void)
+{
+	return platform_driver_register(&s6000_pcm_driver);
+}
+module_init(snd_s6000_pcm_init);
+
+static void __exit snd_s6000_pcm_exit(void)
+{
+	platform_driver_unregister(&s6000_pcm_driver);
+}
+module_exit(snd_s6000_pcm_exit);
+
+MODULE_AUTHOR("Daniel Gloeckner");
+MODULE_DESCRIPTION("Stretch s6000 family PCM DMA module");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/s6000/s6000-pcm.h b/linux/sound/soc/s6000/s6000-pcm.h
new file mode 100644
index 0000000..09d9b88
--- /dev/null
+++ b/linux/sound/soc/s6000/s6000-pcm.h
@@ -0,0 +1,33 @@
+/*
+ * ALSA PCM interface for the Stretch s6000 family
+ *
+ * Author:      Daniel Gloeckner, <dg@emlix.com>
+ * Copyright:   (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _S6000_PCM_H
+#define _S6000_PCM_H
+
+struct snd_soc_dai;
+struct snd_pcm_substream;
+
+struct s6000_pcm_dma_params {
+	unsigned int (*check_xrun)(struct snd_soc_dai *cpu_dai);
+	int (*trigger)(struct snd_pcm_substream *substream, int cmd, int after);
+	dma_addr_t sif_in;
+	dma_addr_t sif_out;
+	u32 dma_in;
+	u32 dma_out;
+	int irq;
+	int same_rate;
+
+	spinlock_t lock;
+	int in_use;
+	int rate;
+};
+
+#endif
diff --git a/linux/sound/soc/s6000/s6105-ipcam.c b/linux/sound/soc/s6000/s6105-ipcam.c
new file mode 100644
index 0000000..c1244c5
--- /dev/null
+++ b/linux/sound/soc/s6000/s6105-ipcam.c
@@ -0,0 +1,243 @@
+/*
+ * ASoC driver for Stretch s6105 IP camera platform
+ *
+ * Author:      Daniel Gloeckner, <dg@emlix.com>
+ * Copyright:   (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <variant/dmac.h>
+
+#include "../codecs/tlv320aic3x.h"
+#include "s6000-pcm.h"
+#include "s6000-i2s.h"
+
+#define S6105_CAM_CODEC_CLOCK 12288000
+
+static int s6105_hw_params(struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret = 0;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+					     SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM |
+					   SND_SOC_DAIFMT_NB_NF);
+	if (ret < 0)
+		return ret;
+
+	/* set the codec system clock */
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0, S6105_CAM_CODEC_CLOCK,
+					    SND_SOC_CLOCK_OUT);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct snd_soc_ops s6105_ops = {
+	.hw_params = s6105_hw_params,
+};
+
+/* s6105 machine dapm widgets */
+static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
+	SND_SOC_DAPM_LINE("Audio Out Differential", NULL),
+	SND_SOC_DAPM_LINE("Audio Out Stereo", NULL),
+	SND_SOC_DAPM_LINE("Audio In", NULL),
+};
+
+/* s6105 machine audio_mapnections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Audio Out connected to HPLOUT, HPLCOM, HPROUT */
+	{"Audio Out Differential", NULL, "HPLOUT"},
+	{"Audio Out Differential", NULL, "HPLCOM"},
+	{"Audio Out Stereo", NULL, "HPLOUT"},
+	{"Audio Out Stereo", NULL, "HPROUT"},
+
+	/* Audio In connected to LINE1L, LINE1R */
+	{"LINE1L", NULL, "Audio In"},
+	{"LINE1R", NULL, "Audio In"},
+};
+
+static int output_type_info(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item) {
+		uinfo->value.enumerated.item = 1;
+		strcpy(uinfo->value.enumerated.name, "HPLOUT/HPROUT");
+	} else {
+		strcpy(uinfo->value.enumerated.name, "HPLOUT/HPLCOM");
+	}
+	return 0;
+}
+
+static int output_type_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.enumerated.item[0] = kcontrol->private_value;
+	return 0;
+}
+
+static int output_type_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = kcontrol->private_data;
+	unsigned int val = (ucontrol->value.enumerated.item[0] != 0);
+	char *differential = "Audio Out Differential";
+	char *stereo = "Audio Out Stereo";
+
+	if (kcontrol->private_value == val)
+		return 0;
+	kcontrol->private_value = val;
+	snd_soc_dapm_disable_pin(codec, val ? differential : stereo);
+	snd_soc_dapm_sync(codec);
+	snd_soc_dapm_enable_pin(codec, val ? stereo : differential);
+	snd_soc_dapm_sync(codec);
+
+	return 1;
+}
+
+static const struct snd_kcontrol_new audio_out_mux = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Master Output Mux",
+	.index = 0,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info = output_type_info,
+	.get = output_type_get,
+	.put = output_type_put,
+	.private_value = 1 /* default to stereo */
+};
+
+/* Logic for a aic3x as connected on the s6105 ip camera ref design */
+static int s6105_aic3x_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	/* Add s6105 specific widgets */
+	snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets,
+				  ARRAY_SIZE(aic3x_dapm_widgets));
+
+	/* Set up s6105 specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	/* not present */
+	snd_soc_dapm_nc_pin(codec, "MONO_LOUT");
+	snd_soc_dapm_nc_pin(codec, "LINE2L");
+	snd_soc_dapm_nc_pin(codec, "LINE2R");
+
+	/* not connected */
+	snd_soc_dapm_nc_pin(codec, "MIC3L"); /* LINE2L on this chip */
+	snd_soc_dapm_nc_pin(codec, "MIC3R"); /* LINE2R on this chip */
+	snd_soc_dapm_nc_pin(codec, "LLOUT");
+	snd_soc_dapm_nc_pin(codec, "RLOUT");
+	snd_soc_dapm_nc_pin(codec, "HPRCOM");
+
+	/* always connected */
+	snd_soc_dapm_enable_pin(codec, "Audio In");
+
+	/* must correspond to audio_out_mux.private_value initializer */
+	snd_soc_dapm_disable_pin(codec, "Audio Out Differential");
+	snd_soc_dapm_sync(codec);
+	snd_soc_dapm_enable_pin(codec, "Audio Out Stereo");
+
+	snd_soc_dapm_sync(codec);
+
+	snd_ctl_add(codec->card->snd_card, snd_ctl_new1(&audio_out_mux, codec));
+
+	return 0;
+}
+
+/* s6105 digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link s6105_dai = {
+	.name = "TLV320AIC31",
+	.stream_name = "AIC31",
+	.cpu_dai_name = "s6000-i2s",
+	.codec_dai_name = "tlv320aic3x-hifi",
+	.platform_name = "s6000-pcm-audio",
+	.codec_name = "tlv320aic3x-codec.0-001a",
+	.init = s6105_aic3x_init,
+	.ops = &s6105_ops,
+};
+
+/* s6105 audio machine driver */
+static struct snd_soc_card snd_soc_card_s6105 = {
+	.name = "Stretch IP Camera",
+	.dai_link = &s6105_dai,
+	.num_links = 1,
+};
+
+static struct s6000_snd_platform_data __initdata s6105_snd_data = {
+	.wide		= 0,
+	.channel_in	= 0,
+	.channel_out	= 1,
+	.lines_in	= 1,
+	.lines_out	= 1,
+	.same_rate	= 1,
+};
+
+static struct platform_device *s6105_snd_device;
+
+/* temporary i2c device creation until this can be moved into the machine
+ * support file.
+*/
+static struct i2c_board_info i2c_device[] = {
+	{ I2C_BOARD_INFO("tlv320aic33", 0x18), }
+};
+
+static int __init s6105_init(void)
+{
+	int ret;
+
+	i2c_register_board_info(0, i2c_device, ARRAY_SIZE(i2c_device));
+
+	s6105_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!s6105_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(s6105_snd_device, &snd_soc_card_s6105);
+	platform_device_add_data(s6105_snd_device, &s6105_snd_data,
+				 sizeof(s6105_snd_data));
+
+	ret = platform_device_add(s6105_snd_device);
+	if (ret)
+		platform_device_put(s6105_snd_device);
+
+	return ret;
+}
+
+static void __exit s6105_exit(void)
+{
+	platform_device_unregister(s6105_snd_device);
+}
+
+module_init(s6105_init);
+module_exit(s6105_exit);
+
+MODULE_AUTHOR("Daniel Gloeckner");
+MODULE_DESCRIPTION("Stretch s6105 IP camera ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/sh/Kconfig b/linux/sound/soc/sh/Kconfig
new file mode 100644
index 0000000..7f0a496
--- /dev/null
+++ b/linux/sound/soc/sh/Kconfig
@@ -0,0 +1,80 @@
+menu "SoC Audio support for SuperH"
+	depends on SUPERH || ARCH_SHMOBILE
+
+config SND_SOC_PCM_SH7760
+	tristate "SoC Audio support for Renesas SH7760"
+	depends on CPU_SUBTYPE_SH7760 && SH_DMABRG
+	help
+	  Enable this option for SH7760 AC97/I2S audio support.
+
+
+##
+## Audio unit modules
+##
+
+config SND_SOC_SH4_HAC
+	tristate
+	select AC97_BUS
+	select SND_SOC_AC97_BUS
+
+config SND_SOC_SH4_SSI
+	tristate
+
+config SND_SOC_SH4_FSI
+	tristate "SH4 FSI support"
+	help
+	  This option enables FSI sound support
+
+config SND_SOC_SH4_SIU
+	tristate
+	depends on (SUPERH || ARCH_SHMOBILE) && HAVE_CLK
+	select DMA_ENGINE
+	select DMADEVICES
+	select SH_DMAE
+	select FW_LOADER
+
+##
+## Boards
+##
+
+config SND_SH7760_AC97
+	tristate "SH7760 AC97 sound support"
+	depends on CPU_SUBTYPE_SH7760 && SND_SOC_PCM_SH7760
+	select SND_SOC_SH4_HAC
+	select SND_SOC_AC97_CODEC
+	help
+	  This option enables generic sound support for the first
+	  AC97 unit of the SH7760.
+
+config SND_FSI_AK4642
+	tristate "FSI-AK4642 sound support"
+	depends on SND_SOC_SH4_FSI && I2C_SH_MOBILE
+	select SND_SOC_AK4642
+	help
+	  This option enables generic sound support for the
+	  FSI - AK4642 unit
+
+config SND_FSI_DA7210
+	tristate "FSI-DA7210 sound support"
+	depends on SND_SOC_SH4_FSI && I2C_SH_MOBILE
+	select SND_SOC_DA7210
+	help
+	  This option enables generic sound support for the
+	  FSI - DA7210 unit
+
+config SND_FSI_HDMI
+	tristate "FSI-HDMI sound support"
+	depends on SND_SOC_SH4_FSI && FB_SH_MOBILE_HDMI
+	help
+	  This option enables generic sound support for the
+	  FSI - HDMI unit
+
+config SND_SIU_MIGOR
+	tristate "SIU sound support on Migo-R"
+	depends on SH_MIGOR
+	select SND_SOC_SH4_SIU
+	select SND_SOC_WM8978
+	help
+	  This option enables sound support for the SH7722 Migo-R board
+
+endmenu
diff --git a/linux/sound/soc/sh/Makefile b/linux/sound/soc/sh/Makefile
new file mode 100644
index 0000000..94476d4
--- /dev/null
+++ b/linux/sound/soc/sh/Makefile
@@ -0,0 +1,26 @@
+## DMA engines
+snd-soc-dma-sh7760-objs	:= dma-sh7760.o
+obj-$(CONFIG_SND_SOC_PCM_SH7760)	+= snd-soc-dma-sh7760.o
+
+## audio units found on some SH-4
+snd-soc-hac-objs	:= hac.o
+snd-soc-ssi-objs	:= ssi.o
+snd-soc-fsi-objs	:= fsi.o
+snd-soc-siu-objs	:= siu_pcm.o siu_dai.o
+obj-$(CONFIG_SND_SOC_SH4_HAC)	+= snd-soc-hac.o
+obj-$(CONFIG_SND_SOC_SH4_SSI)	+= snd-soc-ssi.o
+obj-$(CONFIG_SND_SOC_SH4_FSI)	+= snd-soc-fsi.o
+obj-$(CONFIG_SND_SOC_SH4_SIU)	+= snd-soc-siu.o
+
+## boards
+snd-soc-sh7760-ac97-objs	:= sh7760-ac97.o
+snd-soc-fsi-ak4642-objs		:= fsi-ak4642.o
+snd-soc-fsi-da7210-objs		:= fsi-da7210.o
+snd-soc-fsi-hdmi-objs		:= fsi-hdmi.o
+snd-soc-migor-objs		:= migor.o
+
+obj-$(CONFIG_SND_SH7760_AC97)	+= snd-soc-sh7760-ac97.o
+obj-$(CONFIG_SND_FSI_AK4642)	+= snd-soc-fsi-ak4642.o
+obj-$(CONFIG_SND_FSI_DA7210)	+= snd-soc-fsi-da7210.o
+obj-$(CONFIG_SND_FSI_HDMI)	+= snd-soc-fsi-hdmi.o
+obj-$(CONFIG_SND_SIU_MIGOR)	+= snd-soc-migor.o
diff --git a/linux/sound/soc/sh/dma-sh7760.c b/linux/sound/soc/sh/dma-sh7760.c
new file mode 100644
index 0000000..c326d29
--- /dev/null
+++ b/linux/sound/soc/sh/dma-sh7760.c
@@ -0,0 +1,386 @@
+/*
+ * SH7760 ("camelot") DMABRG audio DMA unit support
+ *
+ * Copyright (C) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
+ *  licensed under the terms outlined in the file COPYING at the root
+ *  of the linux kernel sources.
+ *
+ * The SH7760 DMABRG provides 4 dma channels (2x rec, 2x play), which
+ * trigger an interrupt when one half of the programmed transfer size
+ * has been xmitted.
+ *
+ * FIXME: little-endian only for now
+ */
+
+#include <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <asm/dmabrg.h>
+
+
+/* registers and bits */
+#define BRGATXSAR	0x00
+#define BRGARXDAR	0x04
+#define BRGATXTCR	0x08
+#define BRGARXTCR	0x0C
+#define BRGACR		0x10
+#define BRGATXTCNT	0x14
+#define BRGARXTCNT	0x18
+
+#define ACR_RAR		(1 << 18)
+#define ACR_RDS		(1 << 17)
+#define ACR_RDE		(1 << 16)
+#define ACR_TAR		(1 << 2)
+#define ACR_TDS		(1 << 1)
+#define ACR_TDE		(1 << 0)
+
+/* receiver/transmitter data alignment */
+#define ACR_RAM_NONE	(0 << 24)
+#define ACR_RAM_4BYTE	(1 << 24)
+#define ACR_RAM_2WORD	(2 << 24)
+#define ACR_TAM_NONE	(0 << 8)
+#define ACR_TAM_4BYTE	(1 << 8)
+#define ACR_TAM_2WORD	(2 << 8)
+
+
+struct camelot_pcm {
+	unsigned long mmio;  /* DMABRG audio channel control reg MMIO */
+	unsigned int txid;    /* ID of first DMABRG IRQ for this unit */
+
+	struct snd_pcm_substream *tx_ss;
+	unsigned long tx_period_size;
+	unsigned int  tx_period;
+
+	struct snd_pcm_substream *rx_ss;
+	unsigned long rx_period_size;
+	unsigned int  rx_period;
+
+} cam_pcm_data[2] = {
+	{
+		.mmio	=	0xFE3C0040,
+		.txid	=	DMABRGIRQ_A0TXF,
+	},
+	{
+		.mmio	=	0xFE3C0060,
+		.txid	=	DMABRGIRQ_A1TXF,
+	},
+};
+
+#define BRGREG(x)	(*(unsigned long *)(cam->mmio + (x)))
+
+/*
+ * set a minimum of 16kb per period, to avoid interrupt-"storm" and
+ * resulting skipping. In general, the bigger the minimum size, the
+ * better for overall system performance. (The SH7760 is a puny CPU
+ * with a slow SDRAM interface and poor internal bus bandwidth,
+ * *especially* when the LCDC is active).  The minimum for the DMAC
+ * is 8 bytes; 16kbytes are enough to get skip-free playback of a
+ * 44kHz/16bit/stereo MP3 on a lightly loaded system, and maintain
+ * reasonable responsiveness in MPlayer.
+ */
+#define DMABRG_PERIOD_MIN		16 * 1024
+#define DMABRG_PERIOD_MAX		0x03fffffc
+#define DMABRG_PREALLOC_BUFFER		32 * 1024
+#define DMABRG_PREALLOC_BUFFER_MAX	32 * 1024
+
+/* support everything the SSI supports */
+#define DMABRG_RATES	\
+	SNDRV_PCM_RATE_8000_192000
+
+#define DMABRG_FMTS	\
+	(SNDRV_PCM_FMTBIT_S8      | SNDRV_PCM_FMTBIT_U8      |	\
+	 SNDRV_PCM_FMTBIT_S16_LE  | SNDRV_PCM_FMTBIT_U16_LE  |	\
+	 SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE |	\
+	 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE |	\
+	 SNDRV_PCM_FMTBIT_S32_LE  | SNDRV_PCM_FMTBIT_U32_LE)
+
+static struct snd_pcm_hardware camelot_pcm_hardware = {
+	.info = (SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BATCH),
+	.formats =	DMABRG_FMTS,
+	.rates =	DMABRG_RATES,
+	.rate_min =		8000,
+	.rate_max =		192000,
+	.channels_min =		2,
+	.channels_max =		8,		/* max of the SSI */
+	.buffer_bytes_max =	DMABRG_PERIOD_MAX,
+	.period_bytes_min =	DMABRG_PERIOD_MIN,
+	.period_bytes_max =	DMABRG_PERIOD_MAX / 2,
+	.periods_min =		2,
+	.periods_max =		2,
+	.fifo_size =		128,
+};
+
+static void camelot_txdma(void *data)
+{
+	struct camelot_pcm *cam = data;
+	cam->tx_period ^= 1;
+	snd_pcm_period_elapsed(cam->tx_ss);
+}
+
+static void camelot_rxdma(void *data)
+{
+	struct camelot_pcm *cam = data;
+	cam->rx_period ^= 1;
+	snd_pcm_period_elapsed(cam->rx_ss);
+}
+
+static int camelot_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+	int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+	int ret, dmairq;
+
+	snd_soc_set_runtime_hwparams(substream, &camelot_pcm_hardware);
+
+	/* DMABRG buffer half/full events */
+	dmairq = (recv) ? cam->txid + 2 : cam->txid;
+	if (recv) {
+		cam->rx_ss = substream;
+		ret = dmabrg_request_irq(dmairq, camelot_rxdma, cam);
+		if (unlikely(ret)) {
+			pr_debug("audio unit %d irqs already taken!\n",
+			     rtd->cpu_dai->id);
+			return -EBUSY;
+		}
+		(void)dmabrg_request_irq(dmairq + 1,camelot_rxdma, cam);
+	} else {
+		cam->tx_ss = substream;
+		ret = dmabrg_request_irq(dmairq, camelot_txdma, cam);
+		if (unlikely(ret)) {
+			pr_debug("audio unit %d irqs already taken!\n",
+			     rtd->cpu_dai->id);
+			return -EBUSY;
+		}
+		(void)dmabrg_request_irq(dmairq + 1, camelot_txdma, cam);
+	}
+	return 0;
+}
+
+static int camelot_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+	int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+	int dmairq;
+
+	dmairq = (recv) ? cam->txid + 2 : cam->txid;
+
+	if (recv)
+		cam->rx_ss = NULL;
+	else
+		cam->tx_ss = NULL;
+
+	dmabrg_free_irq(dmairq + 1);
+	dmabrg_free_irq(dmairq);
+
+	return 0;
+}
+
+static int camelot_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+	int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+	int ret;
+
+	ret = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+	if (ret < 0)
+		return ret;
+
+	if (recv) {
+		cam->rx_period_size = params_period_bytes(hw_params);
+		cam->rx_period = 0;
+	} else {
+		cam->tx_period_size = params_period_bytes(hw_params);
+		cam->tx_period = 0;
+	}
+	return 0;
+}
+
+static int camelot_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int camelot_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+
+	pr_debug("PCM data: addr 0x%08ulx len %d\n",
+		 (u32)runtime->dma_addr, runtime->dma_bytes);
+ 
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		BRGREG(BRGATXSAR) = (unsigned long)runtime->dma_area;
+		BRGREG(BRGATXTCR) = runtime->dma_bytes;
+	} else {
+		BRGREG(BRGARXDAR) = (unsigned long)runtime->dma_area;
+		BRGREG(BRGARXTCR) = runtime->dma_bytes;
+	}
+
+	return 0;
+}
+
+static inline void dmabrg_play_dma_start(struct camelot_pcm *cam)
+{
+	unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+	/* start DMABRG engine: XFER start, auto-addr-reload */
+	BRGREG(BRGACR) = acr | ACR_TDE | ACR_TAR | ACR_TAM_2WORD;
+}
+
+static inline void dmabrg_play_dma_stop(struct camelot_pcm *cam)
+{
+	unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+	/* forcibly terminate data transmission */
+	BRGREG(BRGACR) = acr | ACR_TDS;
+}
+
+static inline void dmabrg_rec_dma_start(struct camelot_pcm *cam)
+{
+	unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+	/* start DMABRG engine: recv start, auto-reload */
+	BRGREG(BRGACR) = acr | ACR_RDE | ACR_RAR | ACR_RAM_2WORD;
+}
+
+static inline void dmabrg_rec_dma_stop(struct camelot_pcm *cam)
+{
+	unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);
+	/* forcibly terminate data receiver */
+	BRGREG(BRGACR) = acr | ACR_RDS;
+}
+
+static int camelot_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+	int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (recv)
+			dmabrg_rec_dma_start(cam);
+		else
+			dmabrg_play_dma_start(cam);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (recv)
+			dmabrg_rec_dma_stop(cam);
+		else
+			dmabrg_play_dma_stop(cam);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t camelot_pos(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id];
+	int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;
+	unsigned long pos;
+
+	/* cannot use the DMABRG pointer register: under load, by the
+	 * time ALSA comes around to read the register, it is already
+	 * far ahead (or worse, already done with the fragment) of the
+	 * position at the time the IRQ was triggered, which results in
+	 * fast-playback sound in my test application (ScummVM)
+	 */
+	if (recv)
+		pos = cam->rx_period ? cam->rx_period_size : 0;
+	else
+		pos = cam->tx_period ? cam->tx_period_size : 0;
+
+	return bytes_to_frames(runtime, pos);
+}
+
+static struct snd_pcm_ops camelot_pcm_ops = {
+	.open		= camelot_pcm_open,
+	.close		= camelot_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= camelot_hw_params,
+	.hw_free	= camelot_hw_free,
+	.prepare	= camelot_prepare,
+	.trigger	= camelot_trigger,
+	.pointer	= camelot_pos,
+};
+
+static void camelot_pcm_free(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int camelot_pcm_new(struct snd_card *card,
+			   struct snd_soc_dai *dai,
+			   struct snd_pcm *pcm)
+{
+	/* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel
+	 * in MMAP mode (i.e. aplay -M)
+	 */
+	snd_pcm_lib_preallocate_pages_for_all(pcm,
+		SNDRV_DMA_TYPE_CONTINUOUS,
+		snd_dma_continuous_data(GFP_KERNEL),
+		DMABRG_PREALLOC_BUFFER,	DMABRG_PREALLOC_BUFFER_MAX);
+
+	return 0;
+}
+
+static struct snd_soc_platform sh7760_soc_platform = {
+	.pcm_ops 	= &camelot_pcm_ops,
+	.pcm_new	= camelot_pcm_new,
+	.pcm_free	= camelot_pcm_free,
+};
+
+static int __devinit sh7760_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &sh7760_soc_platform);
+}
+
+static int __devexit sh7760_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver sh7760_pcm_driver = {
+	.driver = {
+			.name = "sh7760-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = sh7760_soc_platform_probe,
+	.remove = __devexit_p(sh7760_soc_platform_remove),
+};
+
+static int __init snd_sh7760_pcm_init(void)
+{
+	return platform_driver_register(&sh7760_pcm_driver);
+}
+module_init(snd_sh7760_pcm_init);
+
+static void __exit snd_sh7760_pcm_exit(void)
+{
+	platform_driver_unregister(&sh7760_pcm_driver);
+}
+module_exit(snd_sh7760_pcm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SH7760 Audio DMA (DMABRG) driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/linux/sound/soc/sh/fsi-ak4642.c b/linux/sound/soc/sh/fsi-ak4642.c
new file mode 100644
index 0000000..d96602d
--- /dev/null
+++ b/linux/sound/soc/sh/fsi-ak4642.c
@@ -0,0 +1,81 @@
+/*
+ * FSI-AK464x sound support for ms7724se
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/platform_device.h>
+#include <sound/sh_fsi.h>
+
+static int fsi_ak4642_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *dai = rtd->codec_dai;
+	int ret;
+
+	ret = snd_soc_dai_set_fmt(dai, SND_SOC_DAIFMT_CBM_CFM);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(dai, 0, 11289600, 0);
+
+	return ret;
+}
+
+static struct snd_soc_dai_link fsi_dai_link = {
+	.name		= "AK4642",
+	.stream_name	= "AK4642",
+	.cpu_dai_name	= "fsia-dai", /* fsi A */
+	.codec_dai_name	= "ak4642-hifi",
+#ifdef CONFIG_MACH_AP4EVB
+	.platform_name	= "sh_fsi2",
+	.codec_name	= "ak4642-codec.0-0013",
+#else
+	.platform_name	= "sh_fsi.0",
+	.codec_name	= "ak4642-codec.0-0012",
+#endif
+	.init		= fsi_ak4642_dai_init,
+	.ops		= NULL,
+};
+
+static struct snd_soc_card fsi_soc_card  = {
+	.name		= "FSI (AK4642)",
+	.dai_link	= &fsi_dai_link,
+	.num_links	= 1,
+};
+
+static struct platform_device *fsi_snd_device;
+
+static int __init fsi_ak4642_init(void)
+{
+	int ret = -ENOMEM;
+
+	fsi_snd_device = platform_device_alloc("soc-audio", FSI_PORT_A);
+	if (!fsi_snd_device)
+		goto out;
+
+	platform_set_drvdata(fsi_snd_device, &fsi_soc_card);
+	ret = platform_device_add(fsi_snd_device);
+
+	if (ret)
+		platform_device_put(fsi_snd_device);
+
+out:
+	return ret;
+}
+
+static void __exit fsi_ak4642_exit(void)
+{
+	platform_device_unregister(fsi_snd_device);
+}
+
+module_init(fsi_ak4642_init);
+module_exit(fsi_ak4642_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Generic SH4 FSI-AK4642 sound card");
+MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
diff --git a/linux/sound/soc/sh/fsi-da7210.c b/linux/sound/soc/sh/fsi-da7210.c
new file mode 100644
index 0000000..a6adb6e
--- /dev/null
+++ b/linux/sound/soc/sh/fsi-da7210.c
@@ -0,0 +1,70 @@
+/*
+ * fsi-da7210.c
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/platform_device.h>
+#include <sound/sh_fsi.h>
+
+static int fsi_da7210_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *dai = rtd->codec_dai;
+
+	return snd_soc_dai_set_fmt(dai,
+				   SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				   SND_SOC_DAIFMT_CBM_CFM);
+}
+
+static struct snd_soc_dai_link fsi_da7210_dai = {
+	.name		= "DA7210",
+	.stream_name	= "DA7210",
+	.cpu_dai_name	= "fsib-dai", /* FSI B */
+	.codec_dai_name	= "da7210-hifi",
+	.platform_name	= "sh_fsi.0",
+	.codec_name	= "da7210-codec.0-001a",
+	.init		= fsi_da7210_init,
+};
+
+static struct snd_soc_card fsi_soc_card = {
+	.name		= "FSI (DA7210)",
+	.dai_link	= &fsi_da7210_dai,
+	.num_links	= 1,
+};
+
+static struct platform_device *fsi_da7210_snd_device;
+
+static int __init fsi_da7210_sound_init(void)
+{
+	int ret;
+
+	fsi_da7210_snd_device = platform_device_alloc("soc-audio", FSI_PORT_B);
+	if (!fsi_da7210_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(fsi_da7210_snd_device, &fsi_soc_card);
+	ret = platform_device_add(fsi_da7210_snd_device);
+	if (ret)
+		platform_device_put(fsi_da7210_snd_device);
+
+	return ret;
+}
+
+static void __exit fsi_da7210_sound_exit(void)
+{
+	platform_device_unregister(fsi_da7210_snd_device);
+}
+
+module_init(fsi_da7210_sound_init);
+module_exit(fsi_da7210_sound_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("ALSA SoC FSI DA2710");
+MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/sh/fsi-hdmi.c b/linux/sound/soc/sh/fsi-hdmi.c
new file mode 100644
index 0000000..a52dd8e
--- /dev/null
+++ b/linux/sound/soc/sh/fsi-hdmi.c
@@ -0,0 +1,60 @@
+/*
+ * FSI - HDMI sound support
+ *
+ * Copyright (C) 2010 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/platform_device.h>
+#include <sound/sh_fsi.h>
+
+static struct snd_soc_dai_link fsi_dai_link = {
+	.name		= "HDMI",
+	.stream_name	= "HDMI",
+	.cpu_dai_name	= "fsib-dai", /* fsi B */
+	.codec_dai_name	= "sh_mobile_hdmi-hifi",
+	.platform_name	= "sh_fsi2",
+	.codec_name	= "sh-mobile-hdmi",
+};
+
+static struct snd_soc_card fsi_soc_card  = {
+	.name		= "FSI (SH MOBILE HDMI)",
+	.dai_link	= &fsi_dai_link,
+	.num_links	= 1,
+};
+
+static struct platform_device *fsi_snd_device;
+
+static int __init fsi_hdmi_init(void)
+{
+	int ret = -ENOMEM;
+
+	fsi_snd_device = platform_device_alloc("soc-audio", FSI_PORT_B);
+	if (!fsi_snd_device)
+		goto out;
+
+	platform_set_drvdata(fsi_snd_device, &fsi_soc_card);
+	ret = platform_device_add(fsi_snd_device);
+
+	if (ret)
+		platform_device_put(fsi_snd_device);
+
+out:
+	return ret;
+}
+
+static void __exit fsi_hdmi_exit(void)
+{
+	platform_device_unregister(fsi_snd_device);
+}
+
+module_init(fsi_hdmi_init);
+module_exit(fsi_hdmi_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Generic SH4 FSI-HDMI sound card");
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
diff --git a/linux/sound/soc/sh/fsi.c b/linux/sound/soc/sh/fsi.c
new file mode 100644
index 0000000..4c2404b
--- /dev/null
+++ b/linux/sound/soc/sh/fsi.c
@@ -0,0 +1,1303 @@
+/*
+ * Fifo-attached Serial Interface (FSI) support for SH7724
+ *
+ * Copyright (C) 2009 Renesas Solutions Corp.
+ * Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ * Based on ssi.c
+ * Copyright (c) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/sh_fsi.h>
+
+#define DO_FMT		0x0000
+#define DOFF_CTL	0x0004
+#define DOFF_ST		0x0008
+#define DI_FMT		0x000C
+#define DIFF_CTL	0x0010
+#define DIFF_ST		0x0014
+#define CKG1		0x0018
+#define CKG2		0x001C
+#define DIDT		0x0020
+#define DODT		0x0024
+#define MUTE_ST		0x0028
+#define OUT_SEL		0x0030
+#define REG_END		OUT_SEL
+
+#define A_MST_CTLR	0x0180
+#define B_MST_CTLR	0x01A0
+#define CPU_INT_ST	0x01F4
+#define CPU_IEMSK	0x01F8
+#define CPU_IMSK	0x01FC
+#define INT_ST		0x0200
+#define IEMSK		0x0204
+#define IMSK		0x0208
+#define MUTE		0x020C
+#define CLK_RST		0x0210
+#define SOFT_RST	0x0214
+#define FIFO_SZ		0x0218
+#define MREG_START	A_MST_CTLR
+#define MREG_END	FIFO_SZ
+
+/* DO_FMT */
+/* DI_FMT */
+#define CR_MONO		(0x0 << 4)
+#define CR_MONO_D	(0x1 << 4)
+#define CR_PCM		(0x2 << 4)
+#define CR_I2S		(0x3 << 4)
+#define CR_TDM		(0x4 << 4)
+#define CR_TDM_D	(0x5 << 4)
+#define CR_SPDIF	0x00100120
+
+/* DOFF_CTL */
+/* DIFF_CTL */
+#define IRQ_HALF	0x00100000
+#define FIFO_CLR	0x00000001
+
+/* DOFF_ST */
+#define ERR_OVER	0x00000010
+#define ERR_UNDER	0x00000001
+#define ST_ERR		(ERR_OVER | ERR_UNDER)
+
+/* CKG1 */
+#define ACKMD_MASK	0x00007000
+#define BPFMD_MASK	0x00000700
+
+/* A/B MST_CTLR */
+#define BP	(1 << 4)	/* Fix the signal of Biphase output */
+#define SE	(1 << 0)	/* Fix the master clock */
+
+/* CLK_RST */
+#define B_CLK		0x00000010
+#define A_CLK		0x00000001
+
+/* IO SHIFT / MACRO */
+#define BI_SHIFT	12
+#define BO_SHIFT	8
+#define AI_SHIFT	4
+#define AO_SHIFT	0
+#define AB_IO(param, shift)	(param << shift)
+
+/* SOFT_RST */
+#define PBSR		(1 << 12) /* Port B Software Reset */
+#define PASR		(1 <<  8) /* Port A Software Reset */
+#define IR		(1 <<  4) /* Interrupt Reset */
+#define FSISR		(1 <<  0) /* Software Reset */
+
+/* FIFO_SZ */
+#define FIFO_SZ_MASK	0x7
+
+#define FSI_RATES SNDRV_PCM_RATE_8000_96000
+
+#define FSI_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
+
+/*
+ * FSI driver use below type name for variable
+ *
+ * xxx_len	: data length
+ * xxx_width	: data width
+ * xxx_offset	: data offset
+ * xxx_num	: number of data
+ */
+
+/*
+ *		struct
+ */
+
+struct fsi_stream {
+	struct snd_pcm_substream *substream;
+
+	int fifo_max_num;
+	int chan_num;
+
+	int buff_offset;
+	int buff_len;
+	int period_len;
+	int period_num;
+};
+
+struct fsi_priv {
+	void __iomem *base;
+	struct fsi_master *master;
+
+	struct fsi_stream playback;
+	struct fsi_stream capture;
+
+	long rate;
+
+	u32 mst_ctrl;
+};
+
+struct fsi_core {
+	int ver;
+
+	u32 int_st;
+	u32 iemsk;
+	u32 imsk;
+};
+
+struct fsi_master {
+	void __iomem *base;
+	int irq;
+	struct fsi_priv fsia;
+	struct fsi_priv fsib;
+	struct fsi_core *core;
+	struct sh_fsi_platform_info *info;
+	spinlock_t lock;
+};
+
+/*
+ *		basic read write function
+ */
+
+static void __fsi_reg_write(u32 reg, u32 data)
+{
+	/* valid data area is 24bit */
+	data &= 0x00ffffff;
+
+	__raw_writel(data, reg);
+}
+
+static u32 __fsi_reg_read(u32 reg)
+{
+	return __raw_readl(reg);
+}
+
+static void __fsi_reg_mask_set(u32 reg, u32 mask, u32 data)
+{
+	u32 val = __fsi_reg_read(reg);
+
+	val &= ~mask;
+	val |= data & mask;
+
+	__fsi_reg_write(reg, val);
+}
+
+static void fsi_reg_write(struct fsi_priv *fsi, u32 reg, u32 data)
+{
+	if (reg > REG_END) {
+		pr_err("fsi: register access err (%s)\n", __func__);
+		return;
+	}
+
+	__fsi_reg_write((u32)(fsi->base + reg), data);
+}
+
+static u32 fsi_reg_read(struct fsi_priv *fsi, u32 reg)
+{
+	if (reg > REG_END) {
+		pr_err("fsi: register access err (%s)\n", __func__);
+		return 0;
+	}
+
+	return __fsi_reg_read((u32)(fsi->base + reg));
+}
+
+static void fsi_reg_mask_set(struct fsi_priv *fsi, u32 reg, u32 mask, u32 data)
+{
+	if (reg > REG_END) {
+		pr_err("fsi: register access err (%s)\n", __func__);
+		return;
+	}
+
+	__fsi_reg_mask_set((u32)(fsi->base + reg), mask, data);
+}
+
+static void fsi_master_write(struct fsi_master *master, u32 reg, u32 data)
+{
+	unsigned long flags;
+
+	if ((reg < MREG_START) ||
+	    (reg > MREG_END)) {
+		pr_err("fsi: register access err (%s)\n", __func__);
+		return;
+	}
+
+	spin_lock_irqsave(&master->lock, flags);
+	__fsi_reg_write((u32)(master->base + reg), data);
+	spin_unlock_irqrestore(&master->lock, flags);
+}
+
+static u32 fsi_master_read(struct fsi_master *master, u32 reg)
+{
+	u32 ret;
+	unsigned long flags;
+
+	if ((reg < MREG_START) ||
+	    (reg > MREG_END)) {
+		pr_err("fsi: register access err (%s)\n", __func__);
+		return 0;
+	}
+
+	spin_lock_irqsave(&master->lock, flags);
+	ret = __fsi_reg_read((u32)(master->base + reg));
+	spin_unlock_irqrestore(&master->lock, flags);
+
+	return ret;
+}
+
+static void fsi_master_mask_set(struct fsi_master *master,
+			       u32 reg, u32 mask, u32 data)
+{
+	unsigned long flags;
+
+	if ((reg < MREG_START) ||
+	    (reg > MREG_END)) {
+		pr_err("fsi: register access err (%s)\n", __func__);
+		return;
+	}
+
+	spin_lock_irqsave(&master->lock, flags);
+	__fsi_reg_mask_set((u32)(master->base + reg), mask, data);
+	spin_unlock_irqrestore(&master->lock, flags);
+}
+
+/*
+ *		basic function
+ */
+
+static struct fsi_master *fsi_get_master(struct fsi_priv *fsi)
+{
+	return fsi->master;
+}
+
+static int fsi_is_port_a(struct fsi_priv *fsi)
+{
+	return fsi->master->base == fsi->base;
+}
+
+static struct snd_soc_dai *fsi_get_dai(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	return  rtd->cpu_dai;
+}
+
+static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_dai *dai = fsi_get_dai(substream);
+	struct fsi_master *master = snd_soc_dai_get_drvdata(dai);
+
+	if (dai->id == 0)
+		return &master->fsia;
+	else
+		return &master->fsib;
+}
+
+static u32 fsi_get_info_flags(struct fsi_priv *fsi)
+{
+	int is_porta = fsi_is_port_a(fsi);
+	struct fsi_master *master = fsi_get_master(fsi);
+
+	return is_porta ? master->info->porta_flags :
+		master->info->portb_flags;
+}
+
+static inline int fsi_stream_is_play(int stream)
+{
+	return stream == SNDRV_PCM_STREAM_PLAYBACK;
+}
+
+static inline int fsi_is_play(struct snd_pcm_substream *substream)
+{
+	return fsi_stream_is_play(substream->stream);
+}
+
+static inline struct fsi_stream *fsi_get_stream(struct fsi_priv *fsi,
+						int is_play)
+{
+	return is_play ? &fsi->playback : &fsi->capture;
+}
+
+static int fsi_is_master_mode(struct fsi_priv *fsi, int is_play)
+{
+	u32 mode;
+	u32 flags = fsi_get_info_flags(fsi);
+
+	mode = is_play ? SH_FSI_OUT_SLAVE_MODE : SH_FSI_IN_SLAVE_MODE;
+
+	/* return
+	 * 1 : master mode
+	 * 0 : slave mode
+	 */
+
+	return (mode & flags) != mode;
+}
+
+static u32 fsi_get_port_shift(struct fsi_priv *fsi, int is_play)
+{
+	int is_porta = fsi_is_port_a(fsi);
+	u32 shift;
+
+	if (is_porta)
+		shift = is_play ? AO_SHIFT : AI_SHIFT;
+	else
+		shift = is_play ? BO_SHIFT : BI_SHIFT;
+
+	return shift;
+}
+
+static void fsi_stream_push(struct fsi_priv *fsi,
+			    int is_play,
+			    struct snd_pcm_substream *substream,
+			    u32 buffer_len,
+			    u32 period_len)
+{
+	struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+
+	io->substream	= substream;
+	io->buff_len	= buffer_len;
+	io->buff_offset	= 0;
+	io->period_len	= period_len;
+	io->period_num	= 0;
+}
+
+static void fsi_stream_pop(struct fsi_priv *fsi, int is_play)
+{
+	struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+
+	io->substream	= NULL;
+	io->buff_len	= 0;
+	io->buff_offset	= 0;
+	io->period_len	= 0;
+	io->period_num	= 0;
+}
+
+static int fsi_get_fifo_data_num(struct fsi_priv *fsi, int is_play)
+{
+	u32 status;
+	u32 reg = is_play ? DOFF_ST : DIFF_ST;
+	struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+	int data_num;
+
+	status = fsi_reg_read(fsi, reg);
+	data_num = 0x1ff & (status >> 8);
+	data_num *= io->chan_num;
+
+	return data_num;
+}
+
+static int fsi_len2num(int len, int width)
+{
+	return len / width;
+}
+
+#define fsi_num2offset(a, b) fsi_num2len(a, b)
+static int fsi_num2len(int num, int width)
+{
+	return num * width;
+}
+
+static int fsi_get_frame_width(struct fsi_priv *fsi, int is_play)
+{
+	struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+	struct snd_pcm_substream *substream = io->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	return frames_to_bytes(runtime, 1) / io->chan_num;
+}
+
+/*
+ *		dma function
+ */
+
+static u8 *fsi_dma_get_area(struct fsi_priv *fsi, int stream)
+{
+	int is_play = fsi_stream_is_play(stream);
+	struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+
+	return io->substream->runtime->dma_area + io->buff_offset;
+}
+
+static void fsi_dma_soft_push16(struct fsi_priv *fsi, int num)
+{
+	u16 *start;
+	int i;
+
+	start  = (u16 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_PLAYBACK);
+
+	for (i = 0; i < num; i++)
+		fsi_reg_write(fsi, DODT, ((u32)*(start + i) << 8));
+}
+
+static void fsi_dma_soft_pop16(struct fsi_priv *fsi, int num)
+{
+	u16 *start;
+	int i;
+
+	start  = (u16 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_CAPTURE);
+
+
+	for (i = 0; i < num; i++)
+		*(start + i) = (u16)(fsi_reg_read(fsi, DIDT) >> 8);
+}
+
+static void fsi_dma_soft_push32(struct fsi_priv *fsi, int num)
+{
+	u32 *start;
+	int i;
+
+	start  = (u32 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_PLAYBACK);
+
+
+	for (i = 0; i < num; i++)
+		fsi_reg_write(fsi, DODT, *(start + i));
+}
+
+static void fsi_dma_soft_pop32(struct fsi_priv *fsi, int num)
+{
+	u32 *start;
+	int i;
+
+	start  = (u32 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_CAPTURE);
+
+	for (i = 0; i < num; i++)
+		*(start + i) = fsi_reg_read(fsi, DIDT);
+}
+
+/*
+ *		irq function
+ */
+
+static void fsi_irq_enable(struct fsi_priv *fsi, int is_play)
+{
+	u32 data = AB_IO(1, fsi_get_port_shift(fsi, is_play));
+	struct fsi_master *master = fsi_get_master(fsi);
+
+	fsi_master_mask_set(master, master->core->imsk,  data, data);
+	fsi_master_mask_set(master, master->core->iemsk, data, data);
+}
+
+static void fsi_irq_disable(struct fsi_priv *fsi, int is_play)
+{
+	u32 data = AB_IO(1, fsi_get_port_shift(fsi, is_play));
+	struct fsi_master *master = fsi_get_master(fsi);
+
+	fsi_master_mask_set(master, master->core->imsk,  data, 0);
+	fsi_master_mask_set(master, master->core->iemsk, data, 0);
+}
+
+static u32 fsi_irq_get_status(struct fsi_master *master)
+{
+	return fsi_master_read(master, master->core->int_st);
+}
+
+static void fsi_irq_clear_all_status(struct fsi_master *master)
+{
+	fsi_master_write(master, master->core->int_st, 0);
+}
+
+static void fsi_irq_clear_status(struct fsi_priv *fsi)
+{
+	u32 data = 0;
+	struct fsi_master *master = fsi_get_master(fsi);
+
+	data |= AB_IO(1, fsi_get_port_shift(fsi, 0));
+	data |= AB_IO(1, fsi_get_port_shift(fsi, 1));
+
+	/* clear interrupt factor */
+	fsi_master_mask_set(master, master->core->int_st, data, 0);
+}
+
+/*
+ *		SPDIF master clock function
+ *
+ * These functions are used later FSI2
+ */
+static void fsi_spdif_clk_ctrl(struct fsi_priv *fsi, int enable)
+{
+	struct fsi_master *master = fsi_get_master(fsi);
+	u32 val = BP | SE;
+
+	if (master->core->ver < 2) {
+		pr_err("fsi: register access err (%s)\n", __func__);
+		return;
+	}
+
+	if (enable)
+		fsi_master_mask_set(master, fsi->mst_ctrl, val, val);
+	else
+		fsi_master_mask_set(master, fsi->mst_ctrl, val, 0);
+}
+
+/*
+ *		ctrl function
+ */
+
+static void fsi_clk_ctrl(struct fsi_priv *fsi, int enable)
+{
+	u32 val = fsi_is_port_a(fsi) ? (1 << 0) : (1 << 4);
+	struct fsi_master *master = fsi_get_master(fsi);
+
+	if (enable)
+		fsi_master_mask_set(master, CLK_RST, val, val);
+	else
+		fsi_master_mask_set(master, CLK_RST, val, 0);
+}
+
+static void fsi_fifo_init(struct fsi_priv *fsi,
+			  int is_play,
+			  struct snd_soc_dai *dai)
+{
+	struct fsi_master *master = fsi_get_master(fsi);
+	struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+	u32 ctrl, shift, i;
+
+	/* get on-chip RAM capacity */
+	shift = fsi_master_read(master, FIFO_SZ);
+	shift >>= fsi_get_port_shift(fsi, is_play);
+	shift &= FIFO_SZ_MASK;
+	io->fifo_max_num = 256 << shift;
+	dev_dbg(dai->dev, "fifo = %d words\n", io->fifo_max_num);
+
+	/*
+	 * The maximum number of sample data varies depending
+	 * on the number of channels selected for the format.
+	 *
+	 * FIFOs are used in 4-channel units in 3-channel mode
+	 * and in 8-channel units in 5- to 7-channel mode
+	 * meaning that more FIFOs than the required size of DPRAM
+	 * are used.
+	 *
+	 * ex) if 256 words of DP-RAM is connected
+	 * 1 channel:  256 (256 x 1 = 256)
+	 * 2 channels: 128 (128 x 2 = 256)
+	 * 3 channels:  64 ( 64 x 3 = 192)
+	 * 4 channels:  64 ( 64 x 4 = 256)
+	 * 5 channels:  32 ( 32 x 5 = 160)
+	 * 6 channels:  32 ( 32 x 6 = 192)
+	 * 7 channels:  32 ( 32 x 7 = 224)
+	 * 8 channels:  32 ( 32 x 8 = 256)
+	 */
+	for (i = 1; i < io->chan_num; i <<= 1)
+		io->fifo_max_num >>= 1;
+	dev_dbg(dai->dev, "%d channel %d store\n",
+		io->chan_num, io->fifo_max_num);
+
+	ctrl = is_play ? DOFF_CTL : DIFF_CTL;
+
+	/* set interrupt generation factor */
+	fsi_reg_write(fsi, ctrl, IRQ_HALF);
+
+	/* clear FIFO */
+	fsi_reg_mask_set(fsi, ctrl, FIFO_CLR, FIFO_CLR);
+}
+
+static void fsi_soft_all_reset(struct fsi_master *master)
+{
+	/* port AB reset */
+	fsi_master_mask_set(master, SOFT_RST, PASR | PBSR, 0);
+	mdelay(10);
+
+	/* soft reset */
+	fsi_master_mask_set(master, SOFT_RST, FSISR, 0);
+	fsi_master_mask_set(master, SOFT_RST, FSISR, FSISR);
+	mdelay(10);
+}
+
+static int fsi_fifo_data_ctrl(struct fsi_priv *fsi, int startup, int stream)
+{
+	struct snd_pcm_runtime *runtime;
+	struct snd_pcm_substream *substream = NULL;
+	int is_play = fsi_stream_is_play(stream);
+	struct fsi_stream *io = fsi_get_stream(fsi, is_play);
+	u32 status_reg = is_play ? DOFF_ST : DIFF_ST;
+	int data_residue_num;
+	int data_num;
+	int data_num_max;
+	int ch_width;
+	int over_period;
+	void (*fn)(struct fsi_priv *fsi, int size);
+
+	if (!fsi			||
+	    !io->substream		||
+	    !io->substream->runtime)
+		return -EINVAL;
+
+	over_period	= 0;
+	substream	= io->substream;
+	runtime		= substream->runtime;
+
+	/* FSI FIFO has limit.
+	 * So, this driver can not send periods data at a time
+	 */
+	if (io->buff_offset >=
+	    fsi_num2offset(io->period_num + 1, io->period_len)) {
+
+		over_period = 1;
+		io->period_num = (io->period_num + 1) % runtime->periods;
+
+		if (0 == io->period_num)
+			io->buff_offset = 0;
+	}
+
+	/* get 1 channel data width */
+	ch_width = fsi_get_frame_width(fsi, is_play);
+
+	/* get residue data number of alsa */
+	data_residue_num = fsi_len2num(io->buff_len - io->buff_offset,
+				       ch_width);
+
+	if (is_play) {
+		/*
+		 * for play-back
+		 *
+		 * data_num_max	: number of FSI fifo free space
+		 * data_num	: number of ALSA residue data
+		 */
+		data_num_max  = io->fifo_max_num * io->chan_num;
+		data_num_max -= fsi_get_fifo_data_num(fsi, is_play);
+
+		data_num = data_residue_num;
+
+		switch (ch_width) {
+		case 2:
+			fn = fsi_dma_soft_push16;
+			break;
+		case 4:
+			fn = fsi_dma_soft_push32;
+			break;
+		default:
+			return -EINVAL;
+		}
+	} else {
+		/*
+		 * for capture
+		 *
+		 * data_num_max	: number of ALSA free space
+		 * data_num	: number of data in FSI fifo
+		 */
+		data_num_max = data_residue_num;
+		data_num     = fsi_get_fifo_data_num(fsi, is_play);
+
+		switch (ch_width) {
+		case 2:
+			fn = fsi_dma_soft_pop16;
+			break;
+		case 4:
+			fn = fsi_dma_soft_pop32;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	data_num = min(data_num, data_num_max);
+
+	fn(fsi, data_num);
+
+	/* update buff_offset */
+	io->buff_offset += fsi_num2offset(data_num, ch_width);
+
+	/* check fifo status */
+	if (!startup) {
+		struct snd_soc_dai *dai = fsi_get_dai(substream);
+		u32 status = fsi_reg_read(fsi, status_reg);
+
+		if (status & ERR_OVER)
+			dev_err(dai->dev, "over run\n");
+		if (status & ERR_UNDER)
+			dev_err(dai->dev, "under run\n");
+	}
+	fsi_reg_write(fsi, status_reg, 0);
+
+	/* re-enable irq */
+	fsi_irq_enable(fsi, is_play);
+
+	if (over_period)
+		snd_pcm_period_elapsed(substream);
+
+	return 0;
+}
+
+static int fsi_data_pop(struct fsi_priv *fsi, int startup)
+{
+	return fsi_fifo_data_ctrl(fsi, startup, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static int fsi_data_push(struct fsi_priv *fsi, int startup)
+{
+	return fsi_fifo_data_ctrl(fsi, startup, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+static irqreturn_t fsi_interrupt(int irq, void *data)
+{
+	struct fsi_master *master = data;
+	u32 int_st = fsi_irq_get_status(master);
+
+	/* clear irq status */
+	fsi_master_mask_set(master, SOFT_RST, IR, 0);
+	fsi_master_mask_set(master, SOFT_RST, IR, IR);
+
+	if (int_st & AB_IO(1, AO_SHIFT))
+		fsi_data_push(&master->fsia, 0);
+	if (int_st & AB_IO(1, BO_SHIFT))
+		fsi_data_push(&master->fsib, 0);
+	if (int_st & AB_IO(1, AI_SHIFT))
+		fsi_data_pop(&master->fsia, 0);
+	if (int_st & AB_IO(1, BI_SHIFT))
+		fsi_data_pop(&master->fsib, 0);
+
+	fsi_irq_clear_all_status(master);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ *		dai ops
+ */
+
+static int fsi_dai_startup(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct fsi_priv *fsi = fsi_get_priv(substream);
+	struct fsi_master *master = fsi_get_master(fsi);
+	struct fsi_stream *io;
+	u32 flags = fsi_get_info_flags(fsi);
+	u32 fmt;
+	u32 reg;
+	u32 data;
+	int is_play = fsi_is_play(substream);
+	int is_master;
+
+	io = fsi_get_stream(fsi, is_play);
+
+	pm_runtime_get_sync(dai->dev);
+
+	/* CKG1 */
+	data = is_play ? (1 << 0) : (1 << 4);
+	is_master = fsi_is_master_mode(fsi, is_play);
+	if (is_master)
+		fsi_reg_mask_set(fsi, CKG1, data, data);
+	else
+		fsi_reg_mask_set(fsi, CKG1, data, 0);
+
+	/* clock inversion (CKG2) */
+	data = 0;
+	if (SH_FSI_LRM_INV & flags)
+		data |= 1 << 12;
+	if (SH_FSI_BRM_INV & flags)
+		data |= 1 << 8;
+	if (SH_FSI_LRS_INV & flags)
+		data |= 1 << 4;
+	if (SH_FSI_BRS_INV & flags)
+		data |= 1 << 0;
+
+	fsi_reg_write(fsi, CKG2, data);
+
+	/* do fmt, di fmt */
+	data = 0;
+	reg = is_play ? DO_FMT : DI_FMT;
+	fmt = is_play ? SH_FSI_GET_OFMT(flags) : SH_FSI_GET_IFMT(flags);
+	switch (fmt) {
+	case SH_FSI_FMT_MONO:
+		data = CR_MONO;
+		io->chan_num = 1;
+		break;
+	case SH_FSI_FMT_MONO_DELAY:
+		data = CR_MONO_D;
+		io->chan_num = 1;
+		break;
+	case SH_FSI_FMT_PCM:
+		data = CR_PCM;
+		io->chan_num = 2;
+		break;
+	case SH_FSI_FMT_I2S:
+		data = CR_I2S;
+		io->chan_num = 2;
+		break;
+	case SH_FSI_FMT_TDM:
+		io->chan_num = is_play ?
+			SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags);
+		data = CR_TDM | (io->chan_num - 1);
+		break;
+	case SH_FSI_FMT_TDM_DELAY:
+		io->chan_num = is_play ?
+			SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags);
+		data = CR_TDM_D | (io->chan_num - 1);
+		break;
+	case SH_FSI_FMT_SPDIF:
+		if (master->core->ver < 2) {
+			dev_err(dai->dev, "This FSI can not use SPDIF\n");
+			return -EINVAL;
+		}
+		data = CR_SPDIF;
+		io->chan_num = 2;
+		fsi_spdif_clk_ctrl(fsi, 1);
+		fsi_reg_mask_set(fsi, OUT_SEL, 0x0010, 0x0010);
+		break;
+	default:
+		dev_err(dai->dev, "unknown format.\n");
+		return -EINVAL;
+	}
+	fsi_reg_write(fsi, reg, data);
+
+	/* irq clear */
+	fsi_irq_disable(fsi, is_play);
+	fsi_irq_clear_status(fsi);
+
+	/* fifo init */
+	fsi_fifo_init(fsi, is_play, dai);
+
+	return 0;
+}
+
+static void fsi_dai_shutdown(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct fsi_priv *fsi = fsi_get_priv(substream);
+	int is_play = fsi_is_play(substream);
+	struct fsi_master *master = fsi_get_master(fsi);
+	int (*set_rate)(struct device *dev, int is_porta, int rate, int enable);
+
+	fsi_irq_disable(fsi, is_play);
+	fsi_clk_ctrl(fsi, 0);
+
+	set_rate = master->info->set_rate;
+	if (set_rate && fsi->rate)
+		set_rate(dai->dev, fsi_is_port_a(fsi), fsi->rate, 0);
+	fsi->rate = 0;
+
+	pm_runtime_put_sync(dai->dev);
+}
+
+static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+			   struct snd_soc_dai *dai)
+{
+	struct fsi_priv *fsi = fsi_get_priv(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int is_play = fsi_is_play(substream);
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		fsi_stream_push(fsi, is_play, substream,
+				frames_to_bytes(runtime, runtime->buffer_size),
+				frames_to_bytes(runtime, runtime->period_size));
+		ret = is_play ? fsi_data_push(fsi, 1) : fsi_data_pop(fsi, 1);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		fsi_irq_disable(fsi, is_play);
+		fsi_stream_pop(fsi, is_play);
+		break;
+	}
+
+	return ret;
+}
+
+static int fsi_dai_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *dai)
+{
+	struct fsi_priv *fsi = fsi_get_priv(substream);
+	struct fsi_master *master = fsi_get_master(fsi);
+	int (*set_rate)(struct device *dev, int is_porta, int rate, int enable);
+	int fsi_ver = master->core->ver;
+	long rate = params_rate(params);
+	int ret;
+
+	set_rate = master->info->set_rate;
+	if (!set_rate)
+		return 0;
+
+	ret = set_rate(dai->dev, fsi_is_port_a(fsi), rate, 1);
+	if (ret < 0) /* error */
+		return ret;
+
+	fsi->rate = rate;
+	if (ret > 0) {
+		u32 data = 0;
+
+		switch (ret & SH_FSI_ACKMD_MASK) {
+		default:
+			/* FALL THROUGH */
+		case SH_FSI_ACKMD_512:
+			data |= (0x0 << 12);
+			break;
+		case SH_FSI_ACKMD_256:
+			data |= (0x1 << 12);
+			break;
+		case SH_FSI_ACKMD_128:
+			data |= (0x2 << 12);
+			break;
+		case SH_FSI_ACKMD_64:
+			data |= (0x3 << 12);
+			break;
+		case SH_FSI_ACKMD_32:
+			if (fsi_ver < 2)
+				dev_err(dai->dev, "unsupported ACKMD\n");
+			else
+				data |= (0x4 << 12);
+			break;
+		}
+
+		switch (ret & SH_FSI_BPFMD_MASK) {
+		default:
+			/* FALL THROUGH */
+		case SH_FSI_BPFMD_32:
+			data |= (0x0 << 8);
+			break;
+		case SH_FSI_BPFMD_64:
+			data |= (0x1 << 8);
+			break;
+		case SH_FSI_BPFMD_128:
+			data |= (0x2 << 8);
+			break;
+		case SH_FSI_BPFMD_256:
+			data |= (0x3 << 8);
+			break;
+		case SH_FSI_BPFMD_512:
+			data |= (0x4 << 8);
+			break;
+		case SH_FSI_BPFMD_16:
+			if (fsi_ver < 2)
+				dev_err(dai->dev, "unsupported ACKMD\n");
+			else
+				data |= (0x7 << 8);
+			break;
+		}
+
+		fsi_reg_mask_set(fsi, CKG1, (ACKMD_MASK | BPFMD_MASK) , data);
+		udelay(10);
+		fsi_clk_ctrl(fsi, 1);
+		ret = 0;
+	}
+
+	return ret;
+
+}
+
+static struct snd_soc_dai_ops fsi_dai_ops = {
+	.startup	= fsi_dai_startup,
+	.shutdown	= fsi_dai_shutdown,
+	.trigger	= fsi_dai_trigger,
+	.hw_params	= fsi_dai_hw_params,
+};
+
+/*
+ *		pcm ops
+ */
+
+static struct snd_pcm_hardware fsi_pcm_hardware = {
+	.info =		SNDRV_PCM_INFO_INTERLEAVED	|
+			SNDRV_PCM_INFO_MMAP		|
+			SNDRV_PCM_INFO_MMAP_VALID	|
+			SNDRV_PCM_INFO_PAUSE,
+	.formats		= FSI_FMTS,
+	.rates			= FSI_RATES,
+	.rate_min		= 8000,
+	.rate_max		= 192000,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= 64 * 1024,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 8192,
+	.periods_min		= 1,
+	.periods_max		= 32,
+	.fifo_size		= 256,
+};
+
+static int fsi_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int ret = 0;
+
+	snd_soc_set_runtime_hwparams(substream, &fsi_pcm_hardware);
+
+	ret = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+
+	return ret;
+}
+
+static int fsi_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int fsi_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static snd_pcm_uframes_t fsi_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsi_priv *fsi = fsi_get_priv(substream);
+	struct fsi_stream *io = fsi_get_stream(fsi, fsi_is_play(substream));
+	long location;
+
+	location = (io->buff_offset - 1);
+	if (location < 0)
+		location = 0;
+
+	return bytes_to_frames(runtime, location);
+}
+
+static struct snd_pcm_ops fsi_pcm_ops = {
+	.open		= fsi_pcm_open,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= fsi_hw_params,
+	.hw_free	= fsi_hw_free,
+	.pointer	= fsi_pointer,
+};
+
+/*
+ *		snd_soc_platform
+ */
+
+#define PREALLOC_BUFFER		(32 * 1024)
+#define PREALLOC_BUFFER_MAX	(32 * 1024)
+
+static void fsi_pcm_free(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int fsi_pcm_new(struct snd_card *card,
+		       struct snd_soc_dai *dai,
+		       struct snd_pcm *pcm)
+{
+	/*
+	 * dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel
+	 * in MMAP mode (i.e. aplay -M)
+	 */
+	return snd_pcm_lib_preallocate_pages_for_all(
+		pcm,
+		SNDRV_DMA_TYPE_CONTINUOUS,
+		snd_dma_continuous_data(GFP_KERNEL),
+		PREALLOC_BUFFER, PREALLOC_BUFFER_MAX);
+}
+
+/*
+ *		alsa struct
+ */
+
+static struct snd_soc_dai_driver fsi_soc_dai[] = {
+	{
+		.name			= "fsia-dai",
+		.playback = {
+			.rates		= FSI_RATES,
+			.formats	= FSI_FMTS,
+			.channels_min	= 1,
+			.channels_max	= 8,
+		},
+		.capture = {
+			.rates		= FSI_RATES,
+			.formats	= FSI_FMTS,
+			.channels_min	= 1,
+			.channels_max	= 8,
+		},
+		.ops = &fsi_dai_ops,
+	},
+	{
+		.name			= "fsib-dai",
+		.playback = {
+			.rates		= FSI_RATES,
+			.formats	= FSI_FMTS,
+			.channels_min	= 1,
+			.channels_max	= 8,
+		},
+		.capture = {
+			.rates		= FSI_RATES,
+			.formats	= FSI_FMTS,
+			.channels_min	= 1,
+			.channels_max	= 8,
+		},
+		.ops = &fsi_dai_ops,
+	},
+};
+
+static struct snd_soc_platform_driver fsi_soc_platform = {
+	.ops		= &fsi_pcm_ops,
+	.pcm_new	= fsi_pcm_new,
+	.pcm_free	= fsi_pcm_free,
+};
+
+/*
+ *		platform function
+ */
+
+static int fsi_probe(struct platform_device *pdev)
+{
+	struct fsi_master *master;
+	const struct platform_device_id	*id_entry;
+	struct resource *res;
+	unsigned int irq;
+	int ret;
+
+	id_entry = pdev->id_entry;
+	if (!id_entry) {
+		dev_err(&pdev->dev, "unknown fsi device\n");
+		return -ENODEV;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	irq = platform_get_irq(pdev, 0);
+	if (!res || (int)irq <= 0) {
+		dev_err(&pdev->dev, "Not enough FSI platform resources.\n");
+		ret = -ENODEV;
+		goto exit;
+	}
+
+	master = kzalloc(sizeof(*master), GFP_KERNEL);
+	if (!master) {
+		dev_err(&pdev->dev, "Could not allocate master\n");
+		ret = -ENOMEM;
+		goto exit;
+	}
+
+	master->base = ioremap_nocache(res->start, resource_size(res));
+	if (!master->base) {
+		ret = -ENXIO;
+		dev_err(&pdev->dev, "Unable to ioremap FSI registers.\n");
+		goto exit_kfree;
+	}
+
+	/* master setting */
+	master->irq		= irq;
+	master->info		= pdev->dev.platform_data;
+	master->core		= (struct fsi_core *)id_entry->driver_data;
+	spin_lock_init(&master->lock);
+
+	/* FSI A setting */
+	master->fsia.base	= master->base;
+	master->fsia.master	= master;
+	master->fsia.mst_ctrl	= A_MST_CTLR;
+
+	/* FSI B setting */
+	master->fsib.base	= master->base + 0x40;
+	master->fsib.master	= master;
+	master->fsib.mst_ctrl	= B_MST_CTLR;
+
+	pm_runtime_enable(&pdev->dev);
+	pm_runtime_resume(&pdev->dev);
+	dev_set_drvdata(&pdev->dev, master);
+
+	fsi_soft_all_reset(master);
+
+	ret = request_irq(irq, &fsi_interrupt, IRQF_DISABLED,
+			  id_entry->name, master);
+	if (ret) {
+		dev_err(&pdev->dev, "irq request err\n");
+		goto exit_iounmap;
+	}
+
+	ret = snd_soc_register_platform(&pdev->dev, &fsi_soc_platform);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "cannot snd soc register\n");
+		goto exit_free_irq;
+	}
+
+	return snd_soc_register_dais(&pdev->dev, fsi_soc_dai, ARRAY_SIZE(fsi_soc_dai));
+
+exit_free_irq:
+	free_irq(irq, master);
+exit_iounmap:
+	iounmap(master->base);
+	pm_runtime_disable(&pdev->dev);
+exit_kfree:
+	kfree(master);
+	master = NULL;
+exit:
+	return ret;
+}
+
+static int fsi_remove(struct platform_device *pdev)
+{
+	struct fsi_master *master;
+
+	master = dev_get_drvdata(&pdev->dev);
+
+	snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(fsi_soc_dai));
+	snd_soc_unregister_platform(&pdev->dev);
+
+	pm_runtime_disable(&pdev->dev);
+
+	free_irq(master->irq, master);
+
+	iounmap(master->base);
+	kfree(master);
+
+	return 0;
+}
+
+static int fsi_runtime_nop(struct device *dev)
+{
+	/* Runtime PM callback shared between ->runtime_suspend()
+	 * and ->runtime_resume(). Simply returns success.
+	 *
+	 * This driver re-initializes all registers after
+	 * pm_runtime_get_sync() anyway so there is no need
+	 * to save and restore registers here.
+	 */
+	return 0;
+}
+
+static struct dev_pm_ops fsi_pm_ops = {
+	.runtime_suspend	= fsi_runtime_nop,
+	.runtime_resume		= fsi_runtime_nop,
+};
+
+static struct fsi_core fsi1_core = {
+	.ver	= 1,
+
+	/* Interrupt */
+	.int_st	= INT_ST,
+	.iemsk	= IEMSK,
+	.imsk	= IMSK,
+};
+
+static struct fsi_core fsi2_core = {
+	.ver	= 2,
+
+	/* Interrupt */
+	.int_st	= CPU_INT_ST,
+	.iemsk	= CPU_IEMSK,
+	.imsk	= CPU_IMSK,
+};
+
+static struct platform_device_id fsi_id_table[] = {
+	{ "sh_fsi",	(kernel_ulong_t)&fsi1_core },
+	{ "sh_fsi2",	(kernel_ulong_t)&fsi2_core },
+	{},
+};
+MODULE_DEVICE_TABLE(platform, fsi_id_table);
+
+static struct platform_driver fsi_driver = {
+	.driver 	= {
+		.name	= "fsi-pcm-audio",
+		.pm	= &fsi_pm_ops,
+	},
+	.probe		= fsi_probe,
+	.remove		= fsi_remove,
+	.id_table	= fsi_id_table,
+};
+
+static int __init fsi_mobile_init(void)
+{
+	return platform_driver_register(&fsi_driver);
+}
+
+static void __exit fsi_mobile_exit(void)
+{
+	platform_driver_unregister(&fsi_driver);
+}
+
+module_init(fsi_mobile_init);
+module_exit(fsi_mobile_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SuperH onchip FSI audio driver");
+MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
diff --git a/linux/sound/soc/sh/hac.c b/linux/sound/soc/sh/hac.c
new file mode 100644
index 0000000..c87e3ff
--- /dev/null
+++ b/linux/sound/soc/sh/hac.c
@@ -0,0 +1,349 @@
+/*
+ * Hitachi Audio Controller (AC97) support for SH7760/SH7780
+ *
+ * Copyright (c) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
+ *  licensed under the terms outlined in the file COPYING at the root
+ *  of the linux kernel sources.
+ *
+ * dont forget to set IPSEL/OMSEL register bits (in your board code) to
+ * enable HAC output pins!
+ */
+
+/* BIG FAT FIXME: although the SH7760 has 2 independent AC97 units, only
+ * the FIRST can be used since ASoC does not pass any information to the
+ * ac97_read/write() functions regarding WHICH unit to use.  You'll have
+ * to edit the code a bit to use the other AC97 unit.		--mlau
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+/* regs and bits */
+#define HACCR		0x08
+#define HACCSAR		0x20
+#define HACCSDR		0x24
+#define HACPCML		0x28
+#define HACPCMR		0x2C
+#define HACTIER		0x50
+#define	HACTSR		0x54
+#define HACRIER		0x58
+#define HACRSR		0x5C
+#define HACACR		0x60
+
+#define CR_CR		(1 << 15)	/* "codec-ready" indicator */
+#define CR_CDRT		(1 << 11)	/* cold reset */
+#define CR_WMRT		(1 << 10)	/* warm reset */
+#define CR_B9		(1 << 9)	/* the mysterious "bit 9" */
+#define CR_ST		(1 << 5)	/* AC97 link start bit */
+
+#define CSAR_RD		(1 << 19)	/* AC97 data read bit */
+#define CSAR_WR		(0)
+
+#define TSR_CMDAMT	(1 << 31)
+#define TSR_CMDDMT	(1 << 30)
+
+#define RSR_STARY	(1 << 22)
+#define RSR_STDRY	(1 << 21)
+
+#define ACR_DMARX16	(1 << 30)
+#define ACR_DMATX16	(1 << 29)
+#define ACR_TX12ATOM	(1 << 26)
+#define ACR_DMARX20	((1 << 24) | (1 << 22))
+#define ACR_DMATX20	((1 << 23) | (1 << 21))
+
+#define CSDR_SHIFT	4
+#define CSDR_MASK	(0xffff << CSDR_SHIFT)
+#define CSAR_SHIFT	12
+#define CSAR_MASK	(0x7f << CSAR_SHIFT)
+
+#define AC97_WRITE_RETRY	1
+#define AC97_READ_RETRY		5
+
+/* manual-suggested AC97 codec access timeouts (us) */
+#define TMO_E1	500	/* 21 < E1 < 1000 */
+#define TMO_E2	13	/* 13 < E2 */
+#define TMO_E3	21	/* 21 < E3 */
+#define TMO_E4	500	/* 21 < E4 < 1000 */
+
+struct hac_priv {
+	unsigned long mmio;	/* HAC base address */
+} hac_cpu_data[] = {
+#if defined(CONFIG_CPU_SUBTYPE_SH7760)
+	{
+		.mmio	= 0xFE240000,
+	},
+	{
+		.mmio	= 0xFE250000,
+	},
+#elif defined(CONFIG_CPU_SUBTYPE_SH7780)
+	{
+		.mmio	= 0xFFE40000,
+	},
+#else
+#error "Unsupported SuperH SoC"
+#endif
+};
+
+#define HACREG(reg)	(*(unsigned long *)(hac->mmio + (reg)))
+
+/*
+ * AC97 read/write flow as outlined in the SH7760 manual (pages 903-906)
+ */
+static int hac_get_codec_data(struct hac_priv *hac, unsigned short r,
+			      unsigned short *v)
+{
+	unsigned int to1, to2, i;
+	unsigned short adr;
+
+	for (i = AC97_READ_RETRY; i; i--) {
+		*v = 0;
+		/* wait for HAC to receive something from the codec */
+		for (to1 = TMO_E4;
+		     to1 && !(HACREG(HACRSR) & RSR_STARY);
+		     --to1)
+			udelay(1);
+		for (to2 = TMO_E4; 
+		     to2 && !(HACREG(HACRSR) & RSR_STDRY);
+		     --to2)
+			udelay(1);
+
+		if (!to1 && !to2)
+			return 0;	/* codec comm is down */
+
+		adr = ((HACREG(HACCSAR) & CSAR_MASK) >> CSAR_SHIFT);
+		*v  = ((HACREG(HACCSDR) & CSDR_MASK) >> CSDR_SHIFT);
+
+		HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY);
+
+		if (r == adr)
+			break;
+
+		/* manual says: wait at least 21 usec before retrying */
+		udelay(21);
+	}
+	HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY);
+	return i;
+}
+
+static unsigned short hac_read_codec_aux(struct hac_priv *hac,
+					 unsigned short reg)
+{
+	unsigned short val;
+	unsigned int i, to;
+
+	for (i = AC97_READ_RETRY; i; i--) {
+		/* send_read_request */
+		local_irq_disable();
+		HACREG(HACTSR) &= ~(TSR_CMDAMT);
+		HACREG(HACCSAR) = (reg << CSAR_SHIFT) | CSAR_RD;
+		local_irq_enable();
+
+		for (to = TMO_E3;
+		     to && !(HACREG(HACTSR) & TSR_CMDAMT);
+		     --to)
+			udelay(1);
+
+		HACREG(HACTSR) &= ~TSR_CMDAMT;
+		val = 0;
+		if (hac_get_codec_data(hac, reg, &val) != 0)
+			break;
+	}
+
+	return i ? val : ~0;
+}
+
+static void hac_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+			   unsigned short val)
+{
+	int unit_id = 0 /* ac97->private_data */;
+	struct hac_priv *hac = &hac_cpu_data[unit_id];
+	unsigned int i, to;
+	/* write_codec_aux */
+	for (i = AC97_WRITE_RETRY; i; i--) {
+		/* send_write_request */
+		local_irq_disable();
+		HACREG(HACTSR) &= ~(TSR_CMDDMT | TSR_CMDAMT);
+		HACREG(HACCSDR) = (val << CSDR_SHIFT);
+		HACREG(HACCSAR) = (reg << CSAR_SHIFT) & (~CSAR_RD);
+		local_irq_enable();
+
+		/* poll-wait for CMDAMT and CMDDMT */
+		for (to = TMO_E1;
+		     to && !(HACREG(HACTSR) & (TSR_CMDAMT|TSR_CMDDMT));
+		     --to)
+			udelay(1);
+
+		HACREG(HACTSR) &= ~(TSR_CMDAMT | TSR_CMDDMT);
+		if (to)
+			break;
+		/* timeout, try again */
+	}
+}
+
+static unsigned short hac_ac97_read(struct snd_ac97 *ac97,
+				    unsigned short reg)
+{
+	int unit_id = 0 /* ac97->private_data */;
+	struct hac_priv *hac = &hac_cpu_data[unit_id];
+	return hac_read_codec_aux(hac, reg);
+}
+
+static void hac_ac97_warmrst(struct snd_ac97 *ac97)
+{
+	int unit_id = 0 /* ac97->private_data */;
+	struct hac_priv *hac = &hac_cpu_data[unit_id];
+	unsigned int tmo;
+
+	HACREG(HACCR) = CR_WMRT | CR_ST | CR_B9;
+	msleep(10);
+	HACREG(HACCR) = CR_ST | CR_B9;
+	for (tmo = 1000; (tmo > 0) && !(HACREG(HACCR) & CR_CR); tmo--)
+		udelay(1);
+
+	if (!tmo)
+		printk(KERN_INFO "hac: reset: AC97 link down!\n");
+	/* settings this bit lets us have a conversation with codec */
+	HACREG(HACACR) |= ACR_TX12ATOM;
+}
+
+static void hac_ac97_coldrst(struct snd_ac97 *ac97)
+{
+	int unit_id = 0 /* ac97->private_data */;
+	struct hac_priv *hac;
+	hac = &hac_cpu_data[unit_id];
+
+	HACREG(HACCR) = 0;
+	HACREG(HACCR) = CR_CDRT | CR_ST | CR_B9;
+	msleep(10);
+	hac_ac97_warmrst(ac97);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read	= hac_ac97_read,
+	.write	= hac_ac97_write,
+	.reset	= hac_ac97_coldrst,
+	.warm_reset = hac_ac97_warmrst,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int hac_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *params,
+			 struct snd_soc_dai *dai)
+{
+	struct hac_priv *hac = &hac_cpu_data[dai->id];
+	int d = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+
+	switch (params->msbits) {
+	case 16:
+		HACREG(HACACR) |= d ?  ACR_DMARX16 :  ACR_DMATX16;
+		HACREG(HACACR) &= d ? ~ACR_DMARX20 : ~ACR_DMATX20;
+		break;
+	case 20:
+		HACREG(HACACR) &= d ? ~ACR_DMARX16 : ~ACR_DMATX16;
+		HACREG(HACACR) |= d ?  ACR_DMARX20 :  ACR_DMATX20;
+		break;
+	default:
+		pr_debug("hac: invalid depth %d bit\n", params->msbits);
+		return -EINVAL;
+		break;
+	}
+
+	return 0;
+}
+
+#define AC97_RATES	\
+	SNDRV_PCM_RATE_8000_192000
+
+#define AC97_FMTS	\
+	SNDRV_PCM_FMTBIT_S16_LE
+
+static struct snd_soc_dai_ops hac_dai_ops = {
+	.hw_params	= hac_hw_params,
+};
+
+static struct snd_soc_dai_driver sh4_hac_dai[] = {
+{
+	.name			= "hac-dai.0",
+	.ac97_control		= 1,
+	.playback = {
+		.rates		= AC97_RATES,
+		.formats	= AC97_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.capture = {
+		.rates		= AC97_RATES,
+		.formats	= AC97_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.ops = &hac_dai_ops,
+},
+#ifdef CONFIG_CPU_SUBTYPE_SH7760
+{
+	.name			= "hac-dai.1",
+	.id			= 1,
+	.playback = {
+		.rates		= AC97_RATES,
+		.formats	= AC97_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.capture = {
+		.rates		= AC97_RATES,
+		.formats	= AC97_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.ops = &hac_dai_ops,
+
+},
+#endif
+};
+
+static int __devinit hac_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_dais(&pdev->dev, sh4_hac_dai,
+			ARRAY_SIZE(sh4_hac_dai));
+}
+
+static int __devexit hac_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(sh4_hac_dai));
+	return 0;
+}
+
+static struct platform_driver hac_pcm_driver = {
+	.driver = {
+			.name = "hac-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = hac_soc_platform_probe,
+	.remove = __devexit_p(hac_soc_platform_remove),
+};
+
+static int __init sh4_hac_pcm_init(void)
+{
+	return platform_driver_register(&hac_pcm_driver);
+}
+module_init(sh4_hac_pcm_init);
+
+static void __exit sh4_hac_pcm_exit(void)
+{
+	platform_driver_unregister(&hac_pcm_driver);
+}
+module_exit(sh4_hac_pcm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SuperH onchip HAC (AC97) audio driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/linux/sound/soc/sh/migor.c b/linux/sound/soc/sh/migor.c
new file mode 100644
index 0000000..ac6c49c
--- /dev/null
+++ b/linux/sound/soc/sh/migor.c
@@ -0,0 +1,224 @@
+/*
+ * ALSA SoC driver for Migo-R
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+
+#include <asm/clkdev.h>
+#include <asm/clock.h>
+
+#include <cpu/sh7722.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "../codecs/wm8978.h"
+#include "siu.h"
+
+/* Default 8000Hz sampling frequency */
+static unsigned long codec_freq = 8000 * 512;
+
+static unsigned int use_count;
+
+/* External clock, sourced from the codec at the SIUMCKB pin */
+static unsigned long siumckb_recalc(struct clk *clk)
+{
+	return codec_freq;
+}
+
+static struct clk_ops siumckb_clk_ops = {
+	.recalc = siumckb_recalc,
+};
+
+static struct clk siumckb_clk = {
+	.ops		= &siumckb_clk_ops,
+	.rate		= 0, /* initialised at run-time */
+};
+
+static struct clk_lookup *siumckb_lookup;
+
+static int migor_hw_params(struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	int ret;
+	unsigned int rate = params_rate(params);
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 13000000,
+				     SND_SOC_CLOCK_IN);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKRATE, rate * 512);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_NB_IF |
+				  SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(rtd->cpu_dai, SND_SOC_DAIFMT_NB_IF |
+				  SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
+	if (ret < 0)
+		return ret;
+
+	codec_freq = rate * 512;
+	/*
+	 * This propagates the parent frequency change to children and
+	 * recalculates the frequency table
+	 */
+	clk_set_rate(&siumckb_clk, codec_freq);
+	dev_dbg(codec_dai->dev, "%s: configure %luHz\n", __func__, codec_freq);
+
+	ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, SIU_CLKB_EXT,
+				     codec_freq / 2, SND_SOC_CLOCK_IN);
+
+	if (!ret)
+		use_count++;
+
+	return ret;
+}
+
+static int migor_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+	if (use_count) {
+		use_count--;
+
+		if (!use_count)
+			snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 0,
+					       SND_SOC_CLOCK_IN);
+	} else {
+		dev_dbg(codec_dai->dev, "Unbalanced hw_free!\n");
+	}
+
+	return 0;
+}
+
+static struct snd_soc_ops migor_dai_ops = {
+	.hw_params = migor_hw_params,
+	.hw_free = migor_hw_free,
+};
+
+static const struct snd_soc_dapm_widget migor_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone", NULL),
+	SND_SOC_DAPM_MIC("Onboard Microphone", NULL),
+	SND_SOC_DAPM_MIC("External Microphone", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Headphone output connected to LHP/RHP, enable OUT4 for VMID */
+	{ "Headphone", NULL,  "OUT4 VMID" },
+	{ "OUT4 VMID", NULL,  "LHP" },
+	{ "OUT4 VMID", NULL,  "RHP" },
+
+	/* On-board microphone */
+	{ "RMICN", NULL, "Mic Bias" },
+	{ "RMICP", NULL, "Mic Bias" },
+	{ "Mic Bias", NULL, "Onboard Microphone" },
+
+	/* External microphone */
+	{ "LMICN", NULL, "Mic Bias" },
+	{ "LMICP", NULL, "Mic Bias" },
+	{ "Mic Bias", NULL, "External Microphone" },
+};
+
+static int migor_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+
+	snd_soc_dapm_new_controls(codec, migor_dapm_widgets,
+				  ARRAY_SIZE(migor_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	return 0;
+}
+
+/* migor digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link migor_dai = {
+	.name = "wm8978",
+	.stream_name = "WM8978",
+	.cpu_dai_name = "siu-i2s-dai",
+	.codec_dai_name = "wm8978-hifi",
+	.platform_name = "siu-pcm-audio",
+	.codec_name = "wm8978.0-001a",
+	.ops = &migor_dai_ops,
+	.init = migor_dai_init,
+};
+
+/* migor audio machine driver */
+static struct snd_soc_card snd_soc_migor = {
+	.name = "Migo-R",
+	.dai_link = &migor_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *migor_snd_device;
+
+static int __init migor_init(void)
+{
+	int ret;
+
+	ret = clk_register(&siumckb_clk);
+	if (ret < 0)
+		return ret;
+
+	siumckb_lookup = clkdev_alloc(&siumckb_clk, "siumckb_clk", NULL);
+	if (!siumckb_lookup) {
+		ret = -ENOMEM;
+		goto eclkdevalloc;
+	}
+	clkdev_add(siumckb_lookup);
+
+	/* Port number used on this machine: port B */
+	migor_snd_device = platform_device_alloc("soc-audio", 1);
+	if (!migor_snd_device) {
+		ret = -ENOMEM;
+		goto epdevalloc;
+	}
+
+	platform_set_drvdata(migor_snd_device, &snd_soc_migor);
+
+	ret = platform_device_add(migor_snd_device);
+	if (ret)
+		goto epdevadd;
+
+	return 0;
+
+epdevadd:
+	platform_device_put(migor_snd_device);
+epdevalloc:
+	clkdev_drop(siumckb_lookup);
+eclkdevalloc:
+	clk_unregister(&siumckb_clk);
+	return ret;
+}
+
+static void __exit migor_exit(void)
+{
+	clkdev_drop(siumckb_lookup);
+	clk_unregister(&siumckb_clk);
+	platform_device_unregister(migor_snd_device);
+}
+
+module_init(migor_init);
+module_exit(migor_exit);
+
+MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
+MODULE_DESCRIPTION("ALSA SoC Migor");
+MODULE_LICENSE("GPL v2");
diff --git a/linux/sound/soc/sh/sh7760-ac97.c b/linux/sound/soc/sh/sh7760-ac97.c
new file mode 100644
index 0000000..f8e0ab8
--- /dev/null
+++ b/linux/sound/soc/sh/sh7760-ac97.c
@@ -0,0 +1,84 @@
+/*
+ * Generic AC97 sound support for SH7760
+ *
+ * (c) 2007 Manuel Lauss
+ *
+ * Licensed under the GPLv2.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/io.h>
+
+#define IPSEL 0xFE400034
+
+/* platform specific structs can be declared here */
+extern struct snd_soc_dai_driver sh4_hac_dai[2];
+extern struct snd_soc_platform_driver sh7760_soc_platform;
+
+static int machine_init(struct snd_soc_pcm_runtime *rtd)
+{
+	snd_soc_dapm_sync(rtd->codec);
+	return 0;
+}
+
+static struct snd_soc_dai_link sh7760_ac97_dai = {
+	.name = "AC97",
+	.stream_name = "AC97 HiFi",
+	.cpu_dai_name = "hac-dai.0",	/* HAC0 */
+	.codec_dai_name = "ac97-hifi",
+	.platform_name = "sh7760-pcm-audio",
+	.codec_name = "ac97-codec",
+	.init = machine_init,
+	.ops = NULL,
+};
+
+static struct snd_soc_card sh7760_ac97_soc_machine  = {
+	.name = "SH7760 AC97",
+	.dai_link = &sh7760_ac97_dai,
+	.num_links = 1,
+};
+
+static struct platform_device *sh7760_ac97_snd_device;
+
+static int __init sh7760_ac97_init(void)
+{
+	int ret;
+	unsigned short ipsel;
+
+	/* enable both AC97 controllers in pinmux reg */
+	ipsel = __raw_readw(IPSEL);
+	__raw_writew(ipsel | (3 << 10), IPSEL);
+
+	ret = -ENOMEM;
+	sh7760_ac97_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!sh7760_ac97_snd_device)
+		goto out;
+
+	platform_set_drvdata(sh7760_ac97_snd_device,
+			     &sh7760_ac97_soc_machine);
+	ret = platform_device_add(sh7760_ac97_snd_device);
+
+	if (ret)
+		platform_device_put(sh7760_ac97_snd_device);
+
+out:
+	return ret;
+}
+
+static void __exit sh7760_ac97_exit(void)
+{
+	platform_device_unregister(sh7760_ac97_snd_device);
+}
+
+module_init(sh7760_ac97_init);
+module_exit(sh7760_ac97_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Generic SH7760 AC97 sound machine");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/linux/sound/soc/sh/siu.h b/linux/sound/soc/sh/siu.h
new file mode 100644
index 0000000..9f4dcb9
--- /dev/null
+++ b/linux/sound/soc/sh/siu.h
@@ -0,0 +1,194 @@
+/*
+ * siu.h - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral.
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2006 Carlos Munoz <carlos@kenati.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef SIU_H
+#define SIU_H
+
+/* Common kernel and user-space firmware-building defines and types */
+
+#define YRAM0_SIZE		(0x0040 / 4)		/* 16 */
+#define YRAM1_SIZE		(0x0080 / 4)		/* 32 */
+#define YRAM2_SIZE		(0x0040 / 4)		/* 16 */
+#define YRAM3_SIZE		(0x0080 / 4)		/* 32 */
+#define YRAM4_SIZE		(0x0080 / 4)		/* 32 */
+#define YRAM_DEF_SIZE		(YRAM0_SIZE + YRAM1_SIZE + YRAM2_SIZE + \
+				 YRAM3_SIZE + YRAM4_SIZE)
+#define YRAM_FIR_SIZE		(0x0400 / 4)		/* 256 */
+#define YRAM_IIR_SIZE		(0x0200 / 4)		/* 128 */
+
+#define XRAM0_SIZE		(0x0400 / 4)		/* 256 */
+#define XRAM1_SIZE		(0x0200 / 4)		/* 128 */
+#define XRAM2_SIZE		(0x0200 / 4)		/* 128 */
+
+/* PRAM program array size */
+#define PRAM0_SIZE		(0x0100 / 4)		/* 64 */
+#define PRAM1_SIZE		((0x2000 - 0x0100) / 4)	/* 1984 */
+
+#include <linux/types.h>
+
+struct siu_spb_param {
+	__u32	ab1a;	/* input FIFO address */
+	__u32	ab0a;	/* output FIFO address */
+	__u32	dir;	/* 0=the ather except CPUOUTPUT, 1=CPUINPUT */
+	__u32	event;	/* SPB program starting conditions */
+	__u32	stfifo;	/* STFIFO register setting value */
+	__u32	trdat;	/* TRDAT register setting value */
+};
+
+struct siu_firmware {
+	__u32			yram_fir_coeff[YRAM_FIR_SIZE];
+	__u32			pram0[PRAM0_SIZE];
+	__u32			pram1[PRAM1_SIZE];
+	__u32			yram0[YRAM0_SIZE];
+	__u32			yram1[YRAM1_SIZE];
+	__u32			yram2[YRAM2_SIZE];
+	__u32			yram3[YRAM3_SIZE];
+	__u32			yram4[YRAM4_SIZE];
+	__u32			spbpar_num;
+	struct siu_spb_param	spbpar[32];
+};
+
+#ifdef __KERNEL__
+
+#include <linux/dmaengine.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/sh_dma.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc-dai.h>
+
+#define SIU_PERIOD_BYTES_MAX	8192		/* DMA transfer/period size */
+#define SIU_PERIOD_BYTES_MIN	256		/* DMA transfer/period size */
+#define SIU_PERIODS_MAX		64		/* Max periods in buffer */
+#define SIU_PERIODS_MIN		4		/* Min periods in buffer */
+#define SIU_BUFFER_BYTES_MAX	(SIU_PERIOD_BYTES_MAX * SIU_PERIODS_MAX)
+
+/* SIU ports: only one can be used at a time */
+enum {
+	SIU_PORT_A,
+	SIU_PORT_B,
+	SIU_PORT_NUM,
+};
+
+/* SIU clock configuration */
+enum {
+	SIU_CLKA_PLL,
+	SIU_CLKA_EXT,
+	SIU_CLKB_PLL,
+	SIU_CLKB_EXT
+};
+
+struct device;
+struct siu_info {
+	struct device		*dev;
+	int			port_id;
+	u32 __iomem		*pram;
+	u32 __iomem		*xram;
+	u32 __iomem		*yram;
+	u32 __iomem		*reg;
+	struct siu_firmware	fw;
+};
+
+struct siu_stream {
+	struct tasklet_struct		tasklet;
+	struct snd_pcm_substream	*substream;
+	snd_pcm_format_t		format;
+	size_t				buf_bytes;
+	size_t				period_bytes;
+	int				cur_period;	/* Period currently in dma */
+	u32				volume;
+	snd_pcm_sframes_t		xfer_cnt;	/* Number of frames */
+	u8				rw_flg;		/* transfer status */
+	/* DMA status */
+	struct dma_chan			*chan;		/* DMA channel */
+	struct dma_async_tx_descriptor	*tx_desc;
+	dma_cookie_t			cookie;
+	struct sh_dmae_slave		param;
+};
+
+struct siu_port {
+	unsigned long		play_cap;	/* Used to track full duplex */
+	struct snd_pcm		*pcm;
+	struct siu_stream	playback;
+	struct siu_stream	capture;
+	u32			stfifo;		/* STFIFO value from firmware */
+	u32			trdat;		/* TRDAT value from firmware */
+};
+
+extern struct siu_port *siu_ports[SIU_PORT_NUM];
+
+static inline struct siu_port *siu_port_info(struct snd_pcm_substream *substream)
+{
+	struct platform_device *pdev =
+		to_platform_device(substream->pcm->card->dev);
+	return siu_ports[pdev->id];
+}
+
+/* Register access */
+static inline void siu_write32(u32 __iomem *addr, u32 val)
+{
+	__raw_writel(val, addr);
+}
+
+static inline u32 siu_read32(u32 __iomem *addr)
+{
+	return __raw_readl(addr);
+}
+
+/* SIU registers */
+#define SIU_IFCTL	(0x000 / sizeof(u32))
+#define SIU_SRCTL	(0x004 / sizeof(u32))
+#define SIU_SFORM	(0x008 / sizeof(u32))
+#define SIU_CKCTL	(0x00c / sizeof(u32))
+#define SIU_TRDAT	(0x010 / sizeof(u32))
+#define SIU_STFIFO	(0x014 / sizeof(u32))
+#define SIU_DPAK	(0x01c / sizeof(u32))
+#define SIU_CKREV	(0x020 / sizeof(u32))
+#define SIU_EVNTC	(0x028 / sizeof(u32))
+#define SIU_SBCTL	(0x040 / sizeof(u32))
+#define SIU_SBPSET	(0x044 / sizeof(u32))
+#define SIU_SBFSTS	(0x068 / sizeof(u32))
+#define SIU_SBDVCA	(0x06c / sizeof(u32))
+#define SIU_SBDVCB	(0x070 / sizeof(u32))
+#define SIU_SBACTIV	(0x074 / sizeof(u32))
+#define SIU_DMAIA	(0x090 / sizeof(u32))
+#define SIU_DMAIB	(0x094 / sizeof(u32))
+#define SIU_DMAOA	(0x098 / sizeof(u32))
+#define SIU_DMAOB	(0x09c / sizeof(u32))
+#define SIU_DMAML	(0x0a0 / sizeof(u32))
+#define SIU_SPSTS	(0x0cc / sizeof(u32))
+#define SIU_SPCTL	(0x0d0 / sizeof(u32))
+#define SIU_BRGASEL	(0x100 / sizeof(u32))
+#define SIU_BRRA	(0x104 / sizeof(u32))
+#define SIU_BRGBSEL	(0x108 / sizeof(u32))
+#define SIU_BRRB	(0x10c / sizeof(u32))
+
+extern struct snd_soc_platform_driver siu_platform;
+extern struct siu_info *siu_i2s_data;
+
+int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card);
+void siu_free_port(struct siu_port *port_info);
+
+#endif
+
+#endif /* SIU_H */
diff --git a/linux/sound/soc/sh/siu_dai.c b/linux/sound/soc/sh/siu_dai.c
new file mode 100644
index 0000000..af53b64
--- /dev/null
+++ b/linux/sound/soc/sh/siu_dai.c
@@ -0,0 +1,869 @@
+/*
+ * siu_dai.c - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral.
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2006 Carlos Munoz <carlos@kenati.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <asm/clock.h>
+#include <asm/siu.h>
+
+#include <sound/control.h>
+#include <sound/soc-dai.h>
+
+#include "siu.h"
+
+/* Board specifics */
+#if defined(CONFIG_CPU_SUBTYPE_SH7722)
+# define SIU_MAX_VOLUME		0x1000
+#else
+# define SIU_MAX_VOLUME		0x7fff
+#endif
+
+#define PRAM_SIZE	0x2000
+#define XRAM_SIZE	0x800
+#define YRAM_SIZE	0x800
+
+#define XRAM_OFFSET	0x4000
+#define YRAM_OFFSET	0x6000
+#define REG_OFFSET	0xc000
+
+#define PLAYBACK_ENABLED	1
+#define CAPTURE_ENABLED		2
+
+#define VOLUME_CAPTURE		0
+#define VOLUME_PLAYBACK		1
+#define DFLT_VOLUME_LEVEL	0x08000800
+
+/*
+ * SPDIF is only available on port A and on some SIU implementations it is only
+ * available for input. Due to the lack of hardware to test it, SPDIF is left
+ * disabled in this driver version
+ */
+struct format_flag {
+	u32	i2s;
+	u32	pcm;
+	u32	spdif;
+	u32	mask;
+};
+
+struct port_flag {
+	struct format_flag	playback;
+	struct format_flag	capture;
+};
+
+struct siu_info *siu_i2s_data;
+
+static struct port_flag siu_flags[SIU_PORT_NUM] = {
+	[SIU_PORT_A] = {
+		.playback = {
+			.i2s	= 0x50000000,
+			.pcm	= 0x40000000,
+			.spdif	= 0x80000000,	/* not on all SIU versions */
+			.mask	= 0xd0000000,
+		},
+		.capture = {
+			.i2s	= 0x05000000,
+			.pcm	= 0x04000000,
+			.spdif	= 0x08000000,
+			.mask	= 0x0d000000,
+		},
+	},
+	[SIU_PORT_B] = {
+		.playback = {
+			.i2s	= 0x00500000,
+			.pcm	= 0x00400000,
+			.spdif	= 0,		/* impossible - turn off */
+			.mask	= 0x00500000,
+		},
+		.capture = {
+			.i2s	= 0x00050000,
+			.pcm	= 0x00040000,
+			.spdif	= 0,		/* impossible - turn off */
+			.mask	= 0x00050000,
+		},
+	},
+};
+
+static void siu_dai_start(struct siu_port *port_info)
+{
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+
+	dev_dbg(port_info->pcm->card->dev, "%s\n", __func__);
+
+	/* Turn on SIU clock */
+	pm_runtime_get_sync(info->dev);
+
+	/* Issue software reset to siu */
+	siu_write32(base + SIU_SRCTL, 0);
+
+	/* Wait for the reset to take effect */
+	udelay(1);
+
+	port_info->stfifo = 0;
+	port_info->trdat = 0;
+
+	/* portA, portB, SIU operate */
+	siu_write32(base + SIU_SRCTL, 0x301);
+
+	/* portA=256fs, portB=256fs */
+	siu_write32(base + SIU_CKCTL, 0x40400000);
+
+	/* portA's BRG does not divide SIUCKA */
+	siu_write32(base + SIU_BRGASEL, 0);
+	siu_write32(base + SIU_BRRA, 0);
+
+	/* portB's BRG divides SIUCKB by half */
+	siu_write32(base + SIU_BRGBSEL, 1);
+	siu_write32(base + SIU_BRRB, 0);
+
+	siu_write32(base + SIU_IFCTL, 0x44440000);
+
+	/* portA: 32 bit/fs, master; portB: 32 bit/fs, master */
+	siu_write32(base + SIU_SFORM, 0x0c0c0000);
+
+	/*
+	 * Volume levels: looks like the DSP firmware implements volume controls
+	 * differently from what's described in the datasheet
+	 */
+	siu_write32(base + SIU_SBDVCA, port_info->playback.volume);
+	siu_write32(base + SIU_SBDVCB, port_info->capture.volume);
+}
+
+static void siu_dai_stop(struct siu_port *port_info)
+{
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+
+	/* SIU software reset */
+	siu_write32(base + SIU_SRCTL, 0);
+
+	/* Turn off SIU clock */
+	pm_runtime_put_sync(info->dev);
+}
+
+static void siu_dai_spbAselect(struct siu_port *port_info)
+{
+	struct siu_info *info = siu_i2s_data;
+	struct siu_firmware *fw = &info->fw;
+	u32 *ydef = fw->yram0;
+	u32 idx;
+
+	/* path A use */
+	if (!info->port_id)
+		idx = 1;		/* portA */
+	else
+		idx = 2;		/* portB */
+
+	ydef[0] = (fw->spbpar[idx].ab1a << 16) |
+		(fw->spbpar[idx].ab0a << 8) |
+		(fw->spbpar[idx].dir << 7) | 3;
+	ydef[1] = fw->yram0[1];	/* 0x03000300 */
+	ydef[2] = (16 / 2) << 24;
+	ydef[3] = fw->yram0[3];	/* 0 */
+	ydef[4] = fw->yram0[4];	/* 0 */
+	ydef[7] = fw->spbpar[idx].event;
+	port_info->stfifo |= fw->spbpar[idx].stfifo;
+	port_info->trdat |= fw->spbpar[idx].trdat;
+}
+
+static void siu_dai_spbBselect(struct siu_port *port_info)
+{
+	struct siu_info *info = siu_i2s_data;
+	struct siu_firmware *fw = &info->fw;
+	u32 *ydef = fw->yram0;
+	u32 idx;
+
+	/* path B use */
+	if (!info->port_id)
+		idx = 7;		/* portA */
+	else
+		idx = 8;		/* portB */
+
+	ydef[5] = (fw->spbpar[idx].ab1a << 16) |
+		(fw->spbpar[idx].ab0a << 8) | 1;
+	ydef[6] = fw->spbpar[idx].event;
+	port_info->stfifo |= fw->spbpar[idx].stfifo;
+	port_info->trdat |= fw->spbpar[idx].trdat;
+}
+
+static void siu_dai_open(struct siu_stream *siu_stream)
+{
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+	u32 srctl, ifctl;
+
+	srctl = siu_read32(base + SIU_SRCTL);
+	ifctl = siu_read32(base + SIU_IFCTL);
+
+	switch (info->port_id) {
+	case SIU_PORT_A:
+		/* portA operates */
+		srctl |= 0x200;
+		ifctl &= ~0xc2;
+		break;
+	case SIU_PORT_B:
+		/* portB operates */
+		srctl |= 0x100;
+		ifctl &= ~0x31;
+		break;
+	}
+
+	siu_write32(base + SIU_SRCTL, srctl);
+	/* Unmute and configure portA */
+	siu_write32(base + SIU_IFCTL, ifctl);
+}
+
+/*
+ * At the moment only fixed Left-upper, Left-lower, Right-upper, Right-lower
+ * packing is supported
+ */
+static void siu_dai_pcmdatapack(struct siu_stream *siu_stream)
+{
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+	u32 dpak;
+
+	dpak = siu_read32(base + SIU_DPAK);
+
+	switch (info->port_id) {
+	case SIU_PORT_A:
+		dpak &= ~0xc0000000;
+		break;
+	case SIU_PORT_B:
+		dpak &= ~0x00c00000;
+		break;
+	}
+
+	siu_write32(base + SIU_DPAK, dpak);
+}
+
+static int siu_dai_spbstart(struct siu_port *port_info)
+{
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+	struct siu_firmware *fw = &info->fw;
+	u32 *ydef = fw->yram0;
+	int cnt;
+	u32 __iomem *add;
+	u32 *ptr;
+
+	/* Load SPB Program in PRAM */
+	ptr = fw->pram0;
+	add = info->pram;
+	for (cnt = 0; cnt < PRAM0_SIZE; cnt++, add++, ptr++)
+		siu_write32(add, *ptr);
+
+	ptr = fw->pram1;
+	add = info->pram + (0x0100 / sizeof(u32));
+	for (cnt = 0; cnt < PRAM1_SIZE; cnt++, add++, ptr++)
+		siu_write32(add, *ptr);
+
+	/* XRAM initialization */
+	add = info->xram;
+	for (cnt = 0; cnt < XRAM0_SIZE + XRAM1_SIZE + XRAM2_SIZE; cnt++, add++)
+		siu_write32(add, 0);
+
+	/* YRAM variable area initialization */
+	add = info->yram;
+	for (cnt = 0; cnt < YRAM_DEF_SIZE; cnt++, add++)
+		siu_write32(add, ydef[cnt]);
+
+	/* YRAM FIR coefficient area initialization */
+	add = info->yram + (0x0200 / sizeof(u32));
+	for (cnt = 0; cnt < YRAM_FIR_SIZE; cnt++, add++)
+		siu_write32(add, fw->yram_fir_coeff[cnt]);
+
+	/* YRAM IIR coefficient area initialization */
+	add = info->yram + (0x0600 / sizeof(u32));
+	for (cnt = 0; cnt < YRAM_IIR_SIZE; cnt++, add++)
+		siu_write32(add, 0);
+
+	siu_write32(base + SIU_TRDAT, port_info->trdat);
+	port_info->trdat = 0x0;
+
+
+	/* SPB start condition: software */
+	siu_write32(base + SIU_SBACTIV, 0);
+	/* Start SPB */
+	siu_write32(base + SIU_SBCTL, 0xc0000000);
+	/* Wait for program to halt */
+	cnt = 0x10000;
+	while (--cnt && siu_read32(base + SIU_SBCTL) != 0x80000000)
+		cpu_relax();
+
+	if (!cnt)
+		return -EBUSY;
+
+	/* SPB program start address setting */
+	siu_write32(base + SIU_SBPSET, 0x00400000);
+	/* SPB hardware start(FIFOCTL source) */
+	siu_write32(base + SIU_SBACTIV, 0xc0000000);
+
+	return 0;
+}
+
+static void siu_dai_spbstop(struct siu_port *port_info)
+{
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+
+	siu_write32(base + SIU_SBACTIV, 0);
+	/* SPB stop */
+	siu_write32(base + SIU_SBCTL, 0);
+
+	port_info->stfifo = 0;
+}
+
+/*		API functions		*/
+
+/* Playback and capture hardware properties are identical */
+static struct snd_pcm_hardware siu_dai_pcm_hw = {
+	.info			= SNDRV_PCM_INFO_INTERLEAVED,
+	.formats		= SNDRV_PCM_FMTBIT_S16,
+	.rates			= SNDRV_PCM_RATE_8000_48000,
+	.rate_min		= 8000,
+	.rate_max		= 48000,
+	.channels_min		= 2,
+	.channels_max		= 2,
+	.buffer_bytes_max	= SIU_BUFFER_BYTES_MAX,
+	.period_bytes_min	= SIU_PERIOD_BYTES_MIN,
+	.period_bytes_max	= SIU_PERIOD_BYTES_MAX,
+	.periods_min		= SIU_PERIODS_MIN,
+	.periods_max		= SIU_PERIODS_MAX,
+};
+
+static int siu_dai_info_volume(struct snd_kcontrol *kctrl,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	struct siu_port *port_info = snd_kcontrol_chip(kctrl);
+
+	dev_dbg(port_info->pcm->card->dev, "%s\n", __func__);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = SIU_MAX_VOLUME;
+
+	return 0;
+}
+
+static int siu_dai_get_volume(struct snd_kcontrol *kctrl,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct siu_port *port_info = snd_kcontrol_chip(kctrl);
+	struct device *dev = port_info->pcm->card->dev;
+	u32 vol;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	switch (kctrl->private_value) {
+	case VOLUME_PLAYBACK:
+		/* Playback is always on port 0 */
+		vol = port_info->playback.volume;
+		ucontrol->value.integer.value[0] = vol & 0xffff;
+		ucontrol->value.integer.value[1] = vol >> 16 & 0xffff;
+		break;
+	case VOLUME_CAPTURE:
+		/* Capture is always on port 1 */
+		vol = port_info->capture.volume;
+		ucontrol->value.integer.value[0] = vol & 0xffff;
+		ucontrol->value.integer.value[1] = vol >> 16 & 0xffff;
+		break;
+	default:
+		dev_err(dev, "%s() invalid private_value=%ld\n",
+			__func__, kctrl->private_value);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int siu_dai_put_volume(struct snd_kcontrol *kctrl,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct siu_port *port_info = snd_kcontrol_chip(kctrl);
+	struct device *dev = port_info->pcm->card->dev;
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+	u32 new_vol;
+	u32 cur_vol;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	if (ucontrol->value.integer.value[0] < 0 ||
+	    ucontrol->value.integer.value[0] > SIU_MAX_VOLUME ||
+	    ucontrol->value.integer.value[1] < 0 ||
+	    ucontrol->value.integer.value[1] > SIU_MAX_VOLUME)
+		return -EINVAL;
+
+	new_vol = ucontrol->value.integer.value[0] |
+		ucontrol->value.integer.value[1] << 16;
+
+	/* See comment above - DSP firmware implementation */
+	switch (kctrl->private_value) {
+	case VOLUME_PLAYBACK:
+		/* Playback is always on port 0 */
+		cur_vol = port_info->playback.volume;
+		siu_write32(base + SIU_SBDVCA, new_vol);
+		port_info->playback.volume = new_vol;
+		break;
+	case VOLUME_CAPTURE:
+		/* Capture is always on port 1 */
+		cur_vol = port_info->capture.volume;
+		siu_write32(base + SIU_SBDVCB, new_vol);
+		port_info->capture.volume = new_vol;
+		break;
+	default:
+		dev_err(dev, "%s() invalid private_value=%ld\n",
+			__func__, kctrl->private_value);
+		return -EINVAL;
+	}
+
+	if (cur_vol != new_vol)
+		return 1;
+
+	return 0;
+}
+
+static struct snd_kcontrol_new playback_controls = {
+	.iface		= SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name		= "PCM Playback Volume",
+	.index		= 0,
+	.info		= siu_dai_info_volume,
+	.get		= siu_dai_get_volume,
+	.put		= siu_dai_put_volume,
+	.private_value	= VOLUME_PLAYBACK,
+};
+
+static struct snd_kcontrol_new capture_controls = {
+	.iface		= SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name		= "PCM Capture Volume",
+	.index		= 0,
+	.info		= siu_dai_info_volume,
+	.get		= siu_dai_get_volume,
+	.put		= siu_dai_put_volume,
+	.private_value	= VOLUME_CAPTURE,
+};
+
+int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card)
+{
+	struct device *dev = card->dev;
+	struct snd_kcontrol *kctrl;
+	int ret;
+
+	*port_info = kzalloc(sizeof(**port_info), GFP_KERNEL);
+	if (!*port_info)
+		return -ENOMEM;
+
+	dev_dbg(dev, "%s: port #%d@%p\n", __func__, port, *port_info);
+
+	(*port_info)->playback.volume = DFLT_VOLUME_LEVEL;
+	(*port_info)->capture.volume = DFLT_VOLUME_LEVEL;
+
+	/*
+	 * Add mixer support. The SPB is used to change the volume. Both
+	 * ports use the same SPB. Therefore, we only register one
+	 * control instance since it will be used by both channels.
+	 * In error case we continue without controls.
+	 */
+	kctrl = snd_ctl_new1(&playback_controls, *port_info);
+	ret = snd_ctl_add(card, kctrl);
+	if (ret < 0)
+		dev_err(dev,
+			"failed to add playback controls %p port=%d err=%d\n",
+			kctrl, port, ret);
+
+	kctrl = snd_ctl_new1(&capture_controls, *port_info);
+	ret = snd_ctl_add(card, kctrl);
+	if (ret < 0)
+		dev_err(dev,
+			"failed to add capture controls %p port=%d err=%d\n",
+			kctrl, port, ret);
+
+	return 0;
+}
+
+void siu_free_port(struct siu_port *port_info)
+{
+	kfree(port_info);
+}
+
+static int siu_dai_startup(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct siu_info *info = snd_soc_dai_get_drvdata(dai);
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct siu_port	*port_info = siu_port_info(substream);
+	int ret;
+
+	dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__,
+		info->port_id, port_info);
+
+	snd_soc_set_runtime_hwparams(substream, &siu_dai_pcm_hw);
+
+	ret = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
+	if (unlikely(ret < 0))
+		return ret;
+
+	siu_dai_start(port_info);
+
+	return 0;
+}
+
+static void siu_dai_shutdown(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct siu_info *info = snd_soc_dai_get_drvdata(dai);
+	struct siu_port	*port_info = siu_port_info(substream);
+
+	dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__,
+		info->port_id, port_info);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		port_info->play_cap &= ~PLAYBACK_ENABLED;
+	else
+		port_info->play_cap &= ~CAPTURE_ENABLED;
+
+	/* Stop the siu if the other stream is not using it */
+	if (!port_info->play_cap) {
+		/* during stmread or stmwrite ? */
+		BUG_ON(port_info->playback.rw_flg || port_info->capture.rw_flg);
+		siu_dai_spbstop(port_info);
+		siu_dai_stop(port_info);
+	}
+}
+
+/* PCM part of siu_dai_playback_prepare() / siu_dai_capture_prepare() */
+static int siu_dai_prepare(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct siu_info *info = snd_soc_dai_get_drvdata(dai);
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct siu_port *port_info = siu_port_info(substream);
+	struct siu_stream *siu_stream;
+	int self, ret;
+
+	dev_dbg(substream->pcm->card->dev,
+		"%s: port %d, active streams %lx, %d channels\n",
+		__func__, info->port_id, port_info->play_cap, rt->channels);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		self = PLAYBACK_ENABLED;
+		siu_stream = &port_info->playback;
+	} else {
+		self = CAPTURE_ENABLED;
+		siu_stream = &port_info->capture;
+	}
+
+	/* Set up the siu if not already done */
+	if (!port_info->play_cap) {
+		siu_stream->rw_flg = 0;	/* stream-data transfer flag */
+
+		siu_dai_spbAselect(port_info);
+		siu_dai_spbBselect(port_info);
+
+		siu_dai_open(siu_stream);
+
+		siu_dai_pcmdatapack(siu_stream);
+
+		ret = siu_dai_spbstart(port_info);
+		if (ret < 0)
+			goto fail;
+	} else {
+		ret = 0;
+	}
+
+	port_info->play_cap |= self;
+
+fail:
+	return ret;
+}
+
+/*
+ * SIU can set bus format to I2S / PCM / SPDIF independently for playback and
+ * capture, however, the current API sets the bus format globally for a DAI.
+ */
+static int siu_dai_set_fmt(struct snd_soc_dai *dai,
+			   unsigned int fmt)
+{
+	struct siu_info *info = snd_soc_dai_get_drvdata(dai);
+	u32 __iomem *base = info->reg;
+	u32 ifctl;
+
+	dev_dbg(dai->dev, "%s: fmt 0x%x on port %d\n",
+		__func__, fmt, info->port_id);
+
+	if (info->port_id < 0)
+		return -ENODEV;
+
+	/* Here select between I2S / PCM / SPDIF */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		ifctl = siu_flags[info->port_id].playback.i2s |
+			siu_flags[info->port_id].capture.i2s;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		ifctl = siu_flags[info->port_id].playback.pcm |
+			siu_flags[info->port_id].capture.pcm;
+		break;
+	/* SPDIF disabled - see comment at the top */
+	default:
+		return -EINVAL;
+	}
+
+	ifctl |= ~(siu_flags[info->port_id].playback.mask |
+		   siu_flags[info->port_id].capture.mask) &
+		siu_read32(base + SIU_IFCTL);
+	siu_write32(base + SIU_IFCTL, ifctl);
+
+	return 0;
+}
+
+static int siu_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+			      unsigned int freq, int dir)
+{
+	struct clk *siu_clk, *parent_clk;
+	char *siu_name, *parent_name;
+	int ret;
+
+	if (dir != SND_SOC_CLOCK_IN)
+		return -EINVAL;
+
+	dev_dbg(dai->dev, "%s: using clock %d\n", __func__, clk_id);
+
+	switch (clk_id) {
+	case SIU_CLKA_PLL:
+		siu_name = "siua_clk";
+		parent_name = "pll_clk";
+		break;
+	case SIU_CLKA_EXT:
+		siu_name = "siua_clk";
+		parent_name = "siumcka_clk";
+		break;
+	case SIU_CLKB_PLL:
+		siu_name = "siub_clk";
+		parent_name = "pll_clk";
+		break;
+	case SIU_CLKB_EXT:
+		siu_name = "siub_clk";
+		parent_name = "siumckb_clk";
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	siu_clk = clk_get(dai->dev, siu_name);
+	if (IS_ERR(siu_clk)) {
+		dev_err(dai->dev, "%s: cannot get a SIU clock: %ld\n", __func__,
+			PTR_ERR(siu_clk));
+		return PTR_ERR(siu_clk);
+	}
+
+	parent_clk = clk_get(dai->dev, parent_name);
+	if (IS_ERR(parent_clk)) {
+		ret = PTR_ERR(parent_clk);
+		dev_err(dai->dev, "cannot get a SIU clock parent: %d\n", ret);
+		goto epclkget;
+	}
+
+	ret = clk_set_parent(siu_clk, parent_clk);
+	if (ret < 0) {
+		dev_err(dai->dev, "cannot reparent the SIU clock: %d\n", ret);
+		goto eclksetp;
+	}
+
+	ret = clk_set_rate(siu_clk, freq);
+	if (ret < 0)
+		dev_err(dai->dev, "cannot set SIU clock rate: %d\n", ret);
+
+	/* TODO: when clkdev gets reference counting we'll move these to siu_dai_shutdown() */
+eclksetp:
+	clk_put(parent_clk);
+epclkget:
+	clk_put(siu_clk);
+
+	return ret;
+}
+
+static struct snd_soc_dai_ops siu_dai_ops = {
+	.startup	= siu_dai_startup,
+	.shutdown	= siu_dai_shutdown,
+	.prepare	= siu_dai_prepare,
+	.set_sysclk	= siu_dai_set_sysclk,
+	.set_fmt	= siu_dai_set_fmt,
+};
+
+static struct snd_soc_dai_driver siu_i2s_dai = {
+	.name	= "siu-i2s-dai",
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.formats = SNDRV_PCM_FMTBIT_S16,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+	},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.formats = SNDRV_PCM_FMTBIT_S16,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+	 },
+	.ops = &siu_dai_ops,
+};
+
+static int __devinit siu_probe(struct platform_device *pdev)
+{
+	const struct firmware *fw_entry;
+	struct resource *res, *region;
+	struct siu_info *info;
+	int ret;
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+	siu_i2s_data = info;
+	info->dev = &pdev->dev;
+
+	ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev);
+	if (ret)
+		goto ereqfw;
+
+	/*
+	 * Loaded firmware is "const" - read only, but we have to modify it in
+	 * snd_siu_sh7343_spbAselect() and snd_siu_sh7343_spbBselect()
+	 */
+	memcpy(&info->fw, fw_entry->data, fw_entry->size);
+
+	release_firmware(fw_entry);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		ret = -ENODEV;
+		goto egetres;
+	}
+
+	region = request_mem_region(res->start, resource_size(res),
+				    pdev->name);
+	if (!region) {
+		dev_err(&pdev->dev, "SIU region already claimed\n");
+		ret = -EBUSY;
+		goto ereqmemreg;
+	}
+
+	ret = -ENOMEM;
+	info->pram = ioremap(res->start, PRAM_SIZE);
+	if (!info->pram)
+		goto emappram;
+	info->xram = ioremap(res->start + XRAM_OFFSET, XRAM_SIZE);
+	if (!info->xram)
+		goto emapxram;
+	info->yram = ioremap(res->start + YRAM_OFFSET, YRAM_SIZE);
+	if (!info->yram)
+		goto emapyram;
+	info->reg = ioremap(res->start + REG_OFFSET, resource_size(res) -
+			    REG_OFFSET);
+	if (!info->reg)
+		goto emapreg;
+
+	dev_set_drvdata(&pdev->dev, info);
+
+	/* register using ARRAY version so we can keep dai name */
+	ret = snd_soc_register_dais(&pdev->dev, &siu_i2s_dai, 1);
+	if (ret < 0)
+		goto edaiinit;
+
+	ret = snd_soc_register_platform(&pdev->dev, &siu_platform);
+	if (ret < 0)
+		goto esocregp;
+
+	pm_runtime_enable(&pdev->dev);
+
+	return ret;
+
+esocregp:
+	snd_soc_unregister_dai(&pdev->dev);
+edaiinit:
+	iounmap(info->reg);
+emapreg:
+	iounmap(info->yram);
+emapyram:
+	iounmap(info->xram);
+emapxram:
+	iounmap(info->pram);
+emappram:
+	release_mem_region(res->start, resource_size(res));
+ereqmemreg:
+egetres:
+ereqfw:
+	kfree(info);
+
+	return ret;
+}
+
+static int __devexit siu_remove(struct platform_device *pdev)
+{
+	struct siu_info *info = dev_get_drvdata(&pdev->dev);
+	struct resource *res;
+
+	pm_runtime_disable(&pdev->dev);
+
+	snd_soc_unregister_platform(&pdev->dev);
+	snd_soc_unregister_dai(&pdev->dev);
+
+	iounmap(info->reg);
+	iounmap(info->yram);
+	iounmap(info->xram);
+	iounmap(info->pram);
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res)
+		release_mem_region(res->start, resource_size(res));
+	kfree(info);
+
+	return 0;
+}
+
+static struct platform_driver siu_driver = {
+	.driver 	= {
+		.owner	= THIS_MODULE,
+		.name	= "siu-pcm-audio",
+	},
+	.probe		= siu_probe,
+	.remove		= __devexit_p(siu_remove),
+};
+
+static int __init siu_init(void)
+{
+	return platform_driver_register(&siu_driver);
+}
+
+static void __exit siu_exit(void)
+{
+	platform_driver_unregister(&siu_driver);
+}
+
+module_init(siu_init)
+module_exit(siu_exit)
+
+MODULE_AUTHOR("Carlos Munoz <carlos@kenati.com>");
+MODULE_DESCRIPTION("ALSA SoC SH7722 SIU driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/sh/siu_pcm.c b/linux/sound/soc/sh/siu_pcm.c
new file mode 100644
index 0000000..ed29c9e
--- /dev/null
+++ b/linux/sound/soc/sh/siu_pcm.c
@@ -0,0 +1,616 @@
+/*
+ * siu_pcm.c - ALSA driver for Renesas SH7343, SH7722 SIU peripheral.
+ *
+ * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ * Copyright (C) 2006 Carlos Munoz <carlos@kenati.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dai.h>
+
+#include <asm/siu.h>
+
+#include "siu.h"
+
+#define GET_MAX_PERIODS(buf_bytes, period_bytes) \
+				((buf_bytes) / (period_bytes))
+#define PERIOD_OFFSET(buf_addr, period_num, period_bytes) \
+				((buf_addr) + ((period_num) * (period_bytes)))
+
+#define RWF_STM_RD		0x01		/* Read in progress */
+#define RWF_STM_WT		0x02		/* Write in progress */
+
+struct siu_port *siu_ports[SIU_PORT_NUM];
+
+/* transfersize is number of u32 dma transfers per period */
+static int siu_pcm_stmwrite_stop(struct siu_port *port_info)
+{
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+	struct siu_stream *siu_stream = &port_info->playback;
+	u32 stfifo;
+
+	if (!siu_stream->rw_flg)
+		return -EPERM;
+
+	/* output FIFO disable */
+	stfifo = siu_read32(base + SIU_STFIFO);
+	siu_write32(base + SIU_STFIFO, stfifo & ~0x0c180c18);
+	pr_debug("%s: STFIFO %x -> %x\n", __func__,
+		 stfifo, stfifo & ~0x0c180c18);
+
+	/* during stmwrite clear */
+	siu_stream->rw_flg = 0;
+
+	return 0;
+}
+
+static int siu_pcm_stmwrite_start(struct siu_port *port_info)
+{
+	struct siu_stream *siu_stream = &port_info->playback;
+
+	if (siu_stream->rw_flg)
+		return -EPERM;
+
+	/* Current period in buffer */
+	port_info->playback.cur_period = 0;
+
+	/* during stmwrite flag set */
+	siu_stream->rw_flg = RWF_STM_WT;
+
+	/* DMA transfer start */
+	tasklet_schedule(&siu_stream->tasklet);
+
+	return 0;
+}
+
+static void siu_dma_tx_complete(void *arg)
+{
+	struct siu_stream *siu_stream = arg;
+
+	if (!siu_stream->rw_flg)
+		return;
+
+	/* Update completed period count */
+	if (++siu_stream->cur_period >=
+	    GET_MAX_PERIODS(siu_stream->buf_bytes,
+			    siu_stream->period_bytes))
+		siu_stream->cur_period = 0;
+
+	pr_debug("%s: done period #%d (%u/%u bytes), cookie %d\n",
+		__func__, siu_stream->cur_period,
+		siu_stream->cur_period * siu_stream->period_bytes,
+		siu_stream->buf_bytes, siu_stream->cookie);
+
+	tasklet_schedule(&siu_stream->tasklet);
+
+	/* Notify alsa: a period is done */
+	snd_pcm_period_elapsed(siu_stream->substream);
+}
+
+static int siu_pcm_wr_set(struct siu_port *port_info,
+			  dma_addr_t buff, u32 size)
+{
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+	struct siu_stream *siu_stream = &port_info->playback;
+	struct snd_pcm_substream *substream = siu_stream->substream;
+	struct device *dev = substream->pcm->card->dev;
+	struct dma_async_tx_descriptor *desc;
+	dma_cookie_t cookie;
+	struct scatterlist sg;
+	u32 stfifo;
+
+	sg_init_table(&sg, 1);
+	sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)),
+		    size, offset_in_page(buff));
+	sg_dma_len(&sg) = size;
+	sg_dma_address(&sg) = buff;
+
+	desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan,
+		&sg, 1, DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	if (!desc) {
+		dev_err(dev, "Failed to allocate a dma descriptor\n");
+		return -ENOMEM;
+	}
+
+	desc->callback = siu_dma_tx_complete;
+	desc->callback_param = siu_stream;
+	cookie = desc->tx_submit(desc);
+	if (cookie < 0) {
+		dev_err(dev, "Failed to submit a dma transfer\n");
+		return cookie;
+	}
+
+	siu_stream->tx_desc = desc;
+	siu_stream->cookie = cookie;
+
+	dma_async_issue_pending(siu_stream->chan);
+
+	/* only output FIFO enable */
+	stfifo = siu_read32(base + SIU_STFIFO);
+	siu_write32(base + SIU_STFIFO, stfifo | (port_info->stfifo & 0x0c180c18));
+	dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
+		stfifo, stfifo | (port_info->stfifo & 0x0c180c18));
+
+	return 0;
+}
+
+static int siu_pcm_rd_set(struct siu_port *port_info,
+			  dma_addr_t buff, size_t size)
+{
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+	struct siu_stream *siu_stream = &port_info->capture;
+	struct snd_pcm_substream *substream = siu_stream->substream;
+	struct device *dev = substream->pcm->card->dev;
+	struct dma_async_tx_descriptor *desc;
+	dma_cookie_t cookie;
+	struct scatterlist sg;
+	u32 stfifo;
+
+	dev_dbg(dev, "%s: %u@%llx\n", __func__, size, (unsigned long long)buff);
+
+	sg_init_table(&sg, 1);
+	sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)),
+		    size, offset_in_page(buff));
+	sg_dma_len(&sg) = size;
+	sg_dma_address(&sg) = buff;
+
+	desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan,
+		&sg, 1, DMA_FROM_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	if (!desc) {
+		dev_err(dev, "Failed to allocate dma descriptor\n");
+		return -ENOMEM;
+	}
+
+	desc->callback = siu_dma_tx_complete;
+	desc->callback_param = siu_stream;
+	cookie = desc->tx_submit(desc);
+	if (cookie < 0) {
+		dev_err(dev, "Failed to submit dma descriptor\n");
+		return cookie;
+	}
+
+	siu_stream->tx_desc = desc;
+	siu_stream->cookie = cookie;
+
+	dma_async_issue_pending(siu_stream->chan);
+
+	/* only input FIFO enable */
+	stfifo = siu_read32(base + SIU_STFIFO);
+	siu_write32(base + SIU_STFIFO, siu_read32(base + SIU_STFIFO) |
+		    (port_info->stfifo & 0x13071307));
+	dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
+		stfifo, stfifo | (port_info->stfifo & 0x13071307));
+
+	return 0;
+}
+
+static void siu_io_tasklet(unsigned long data)
+{
+	struct siu_stream *siu_stream = (struct siu_stream *)data;
+	struct snd_pcm_substream *substream = siu_stream->substream;
+	struct device *dev = substream->pcm->card->dev;
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct siu_port *port_info = siu_port_info(substream);
+
+	dev_dbg(dev, "%s: flags %x\n", __func__, siu_stream->rw_flg);
+
+	if (!siu_stream->rw_flg) {
+		dev_dbg(dev, "%s: stream inactive\n", __func__);
+		return;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		dma_addr_t buff;
+		size_t count;
+		u8 *virt;
+
+		buff = (dma_addr_t)PERIOD_OFFSET(rt->dma_addr,
+						siu_stream->cur_period,
+						siu_stream->period_bytes);
+		virt = PERIOD_OFFSET(rt->dma_area,
+				     siu_stream->cur_period,
+				     siu_stream->period_bytes);
+		count = siu_stream->period_bytes;
+
+		/* DMA transfer start */
+		siu_pcm_rd_set(port_info, buff, count);
+	} else {
+		siu_pcm_wr_set(port_info,
+			       (dma_addr_t)PERIOD_OFFSET(rt->dma_addr,
+						siu_stream->cur_period,
+						siu_stream->period_bytes),
+			       siu_stream->period_bytes);
+	}
+}
+
+/* Capture */
+static int siu_pcm_stmread_start(struct siu_port *port_info)
+{
+	struct siu_stream *siu_stream = &port_info->capture;
+
+	if (siu_stream->xfer_cnt > 0x1000000)
+		return -EINVAL;
+	if (siu_stream->rw_flg)
+		return -EPERM;
+
+	/* Current period in buffer */
+	siu_stream->cur_period = 0;
+
+	/* during stmread flag set */
+	siu_stream->rw_flg = RWF_STM_RD;
+
+	tasklet_schedule(&siu_stream->tasklet);
+
+	return 0;
+}
+
+static int siu_pcm_stmread_stop(struct siu_port *port_info)
+{
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+	struct siu_stream *siu_stream = &port_info->capture;
+	struct device *dev = siu_stream->substream->pcm->card->dev;
+	u32 stfifo;
+
+	if (!siu_stream->rw_flg)
+		return -EPERM;
+
+	/* input FIFO disable */
+	stfifo = siu_read32(base + SIU_STFIFO);
+	siu_write32(base + SIU_STFIFO, stfifo & ~0x13071307);
+	dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__,
+		stfifo, stfifo & ~0x13071307);
+
+	/* during stmread flag clear */
+	siu_stream->rw_flg = 0;
+
+	return 0;
+}
+
+static int siu_pcm_hw_params(struct snd_pcm_substream *ss,
+			     struct snd_pcm_hw_params *hw_params)
+{
+	struct siu_info *info = siu_i2s_data;
+	struct device *dev = ss->pcm->card->dev;
+	int ret;
+
+	dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
+
+	ret = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params));
+	if (ret < 0)
+		dev_err(dev, "snd_pcm_lib_malloc_pages() failed\n");
+
+	return ret;
+}
+
+static int siu_pcm_hw_free(struct snd_pcm_substream *ss)
+{
+	struct siu_info *info = siu_i2s_data;
+	struct siu_port	*port_info = siu_port_info(ss);
+	struct device *dev = ss->pcm->card->dev;
+	struct siu_stream *siu_stream;
+
+	if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		siu_stream = &port_info->playback;
+	else
+		siu_stream = &port_info->capture;
+
+	dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
+
+	return snd_pcm_lib_free_pages(ss);
+}
+
+static bool filter(struct dma_chan *chan, void *slave)
+{
+	struct sh_dmae_slave *param = slave;
+
+	pr_debug("%s: slave ID %d\n", __func__, param->slave_id);
+
+	if (unlikely(param->dma_dev != chan->device->dev))
+		return false;
+
+	chan->private = param;
+	return true;
+}
+
+static int siu_pcm_open(struct snd_pcm_substream *ss)
+{
+	/* Playback / Capture */
+	struct snd_soc_pcm_runtime *rtd = ss->private_data;
+	struct siu_platform *pdata = rtd->platform->dev->platform_data;
+	struct siu_info *info = siu_i2s_data;
+	struct siu_port *port_info = siu_port_info(ss);
+	struct siu_stream *siu_stream;
+	u32 port = info->port_id;
+	struct device *dev = ss->pcm->card->dev;
+	dma_cap_mask_t mask;
+	struct sh_dmae_slave *param;
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	dev_dbg(dev, "%s, port=%d@%p\n", __func__, port, port_info);
+
+	if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		siu_stream = &port_info->playback;
+		param = &siu_stream->param;
+		param->slave_id = port ? pdata->dma_slave_tx_b :
+			pdata->dma_slave_tx_a;
+	} else {
+		siu_stream = &port_info->capture;
+		param = &siu_stream->param;
+		param->slave_id = port ? pdata->dma_slave_rx_b :
+			pdata->dma_slave_rx_a;
+	}
+
+	param->dma_dev = pdata->dma_dev;
+	/* Get DMA channel */
+	siu_stream->chan = dma_request_channel(mask, filter, param);
+	if (!siu_stream->chan) {
+		dev_err(dev, "DMA channel allocation failed!\n");
+		return -EBUSY;
+	}
+
+	siu_stream->substream = ss;
+
+	return 0;
+}
+
+static int siu_pcm_close(struct snd_pcm_substream *ss)
+{
+	struct siu_info *info = siu_i2s_data;
+	struct device *dev = ss->pcm->card->dev;
+	struct siu_port *port_info = siu_port_info(ss);
+	struct siu_stream *siu_stream;
+
+	dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id);
+
+	if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		siu_stream = &port_info->playback;
+	else
+		siu_stream = &port_info->capture;
+
+	dma_release_channel(siu_stream->chan);
+	siu_stream->chan = NULL;
+
+	siu_stream->substream = NULL;
+
+	return 0;
+}
+
+static int siu_pcm_prepare(struct snd_pcm_substream *ss)
+{
+	struct siu_info *info = siu_i2s_data;
+	struct siu_port *port_info = siu_port_info(ss);
+	struct device *dev = ss->pcm->card->dev;
+	struct snd_pcm_runtime 	*rt = ss->runtime;
+	struct siu_stream *siu_stream;
+	snd_pcm_sframes_t xfer_cnt;
+
+	if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		siu_stream = &port_info->playback;
+	else
+		siu_stream = &port_info->capture;
+
+	rt = siu_stream->substream->runtime;
+
+	siu_stream->buf_bytes = snd_pcm_lib_buffer_bytes(ss);
+	siu_stream->period_bytes = snd_pcm_lib_period_bytes(ss);
+
+	dev_dbg(dev, "%s: port=%d, %d channels, period=%u bytes\n", __func__,
+		info->port_id, rt->channels, siu_stream->period_bytes);
+
+	/* We only support buffers that are multiples of the period */
+	if (siu_stream->buf_bytes % siu_stream->period_bytes) {
+		dev_err(dev, "%s() - buffer=%d not multiple of period=%d\n",
+		       __func__, siu_stream->buf_bytes,
+		       siu_stream->period_bytes);
+		return -EINVAL;
+	}
+
+	xfer_cnt = bytes_to_frames(rt, siu_stream->period_bytes);
+	if (!xfer_cnt || xfer_cnt > 0x1000000)
+		return -EINVAL;
+
+	siu_stream->format = rt->format;
+	siu_stream->xfer_cnt = xfer_cnt;
+
+	dev_dbg(dev, "port=%d buf=%lx buf_bytes=%d period_bytes=%d "
+		"format=%d channels=%d xfer_cnt=%d\n", info->port_id,
+		(unsigned long)rt->dma_addr, siu_stream->buf_bytes,
+		siu_stream->period_bytes,
+		siu_stream->format, rt->channels, (int)xfer_cnt);
+
+	return 0;
+}
+
+static int siu_pcm_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+	struct siu_info *info = siu_i2s_data;
+	struct device *dev = ss->pcm->card->dev;
+	struct siu_port *port_info = siu_port_info(ss);
+	int ret;
+
+	dev_dbg(dev, "%s: port=%d@%p, cmd=%d\n", __func__,
+		info->port_id, port_info, cmd);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			ret = siu_pcm_stmwrite_start(port_info);
+		else
+			ret = siu_pcm_stmread_start(port_info);
+
+		if (ret < 0)
+			dev_warn(dev, "%s: start failed on port=%d\n",
+				 __func__, info->port_id);
+
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			siu_pcm_stmwrite_stop(port_info);
+		else
+			siu_pcm_stmread_stop(port_info);
+		ret = 0;
+
+		break;
+	default:
+		dev_err(dev, "%s() unsupported cmd=%d\n", __func__, cmd);
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+/*
+ * So far only resolution of one period is supported, subject to extending the
+ * dmangine API
+ */
+static snd_pcm_uframes_t siu_pcm_pointer_dma(struct snd_pcm_substream *ss)
+{
+	struct device *dev = ss->pcm->card->dev;
+	struct siu_info *info = siu_i2s_data;
+	u32 __iomem *base = info->reg;
+	struct siu_port *port_info = siu_port_info(ss);
+	struct snd_pcm_runtime *rt = ss->runtime;
+	size_t ptr;
+	struct siu_stream *siu_stream;
+
+	if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		siu_stream = &port_info->playback;
+	else
+		siu_stream = &port_info->capture;
+
+	/*
+	 * ptr is the offset into the buffer where the dma is currently at. We
+	 * check if the dma buffer has just wrapped.
+	 */
+	ptr = PERIOD_OFFSET(rt->dma_addr,
+			    siu_stream->cur_period,
+			    siu_stream->period_bytes) - rt->dma_addr;
+
+	dev_dbg(dev,
+		"%s: port=%d, events %x, FSTS %x, xferred %u/%u, cookie %d\n",
+		__func__, info->port_id, siu_read32(base + SIU_EVNTC),
+		siu_read32(base + SIU_SBFSTS), ptr, siu_stream->buf_bytes,
+		siu_stream->cookie);
+
+	if (ptr >= siu_stream->buf_bytes)
+		ptr = 0;
+
+	return bytes_to_frames(ss->runtime, ptr);
+}
+
+static int siu_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+		       struct snd_pcm *pcm)
+{
+	/* card->dev == socdev->dev, see snd_soc_new_pcms() */
+	struct siu_info *info = siu_i2s_data;
+	struct platform_device *pdev = to_platform_device(card->dev);
+	int ret;
+	int i;
+
+	/* pdev->id selects between SIUA and SIUB */
+	if (pdev->id < 0 || pdev->id >= SIU_PORT_NUM)
+		return -EINVAL;
+
+	info->port_id = pdev->id;
+
+	/*
+	 * While the siu has 2 ports, only one port can be on at a time (only 1
+	 * SPB). So far all the boards using the siu had only one of the ports
+	 * wired to a codec. To simplify things, we only register one port with
+	 * alsa. In case both ports are needed, it should be changed here
+	 */
+	for (i = pdev->id; i < pdev->id + 1; i++) {
+		struct siu_port **port_info = &siu_ports[i];
+
+		ret = siu_init_port(i, port_info, card);
+		if (ret < 0)
+			return ret;
+
+		ret = snd_pcm_lib_preallocate_pages_for_all(pcm,
+				SNDRV_DMA_TYPE_DEV, NULL,
+				SIU_BUFFER_BYTES_MAX, SIU_BUFFER_BYTES_MAX);
+		if (ret < 0) {
+			dev_err(card->dev,
+			       "snd_pcm_lib_preallocate_pages_for_all() err=%d",
+				ret);
+			goto fail;
+		}
+
+		(*port_info)->pcm = pcm;
+
+		/* IO tasklets */
+		tasklet_init(&(*port_info)->playback.tasklet, siu_io_tasklet,
+			     (unsigned long)&(*port_info)->playback);
+		tasklet_init(&(*port_info)->capture.tasklet, siu_io_tasklet,
+			     (unsigned long)&(*port_info)->capture);
+	}
+
+	dev_info(card->dev, "SuperH SIU driver initialized.\n");
+	return 0;
+
+fail:
+	siu_free_port(siu_ports[pdev->id]);
+	dev_err(card->dev, "SIU: failed to initialize.\n");
+	return ret;
+}
+
+static void siu_pcm_free(struct snd_pcm *pcm)
+{
+	struct platform_device *pdev = to_platform_device(pcm->card->dev);
+	struct siu_port *port_info = siu_ports[pdev->id];
+
+	tasklet_kill(&port_info->capture.tasklet);
+	tasklet_kill(&port_info->playback.tasklet);
+
+	siu_free_port(port_info);
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+
+	dev_dbg(pcm->card->dev, "%s\n", __func__);
+}
+
+static struct snd_pcm_ops siu_pcm_ops = {
+	.open		= siu_pcm_open,
+	.close		= siu_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= siu_pcm_hw_params,
+	.hw_free	= siu_pcm_hw_free,
+	.prepare	= siu_pcm_prepare,
+	.trigger	= siu_pcm_trigger,
+	.pointer	= siu_pcm_pointer_dma,
+};
+
+struct snd_soc_platform_driver siu_platform = {
+	.ops			= &siu_pcm_ops,
+	.pcm_new	= siu_pcm_new,
+	.pcm_free	= siu_pcm_free,
+};
+EXPORT_SYMBOL_GPL(siu_platform);
diff --git a/linux/sound/soc/sh/ssi.c b/linux/sound/soc/sh/ssi.c
new file mode 100644
index 0000000..05192d9
--- /dev/null
+++ b/linux/sound/soc/sh/ssi.c
@@ -0,0 +1,418 @@
+/*
+ * Serial Sound Interface (I2S) support for SH7760/SH7780
+ *
+ * Copyright (c) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
+ *
+ *  licensed under the terms outlined in the file COPYING at the root
+ *  of the linux kernel sources.
+ *
+ * dont forget to set IPSEL/OMSEL register bits (in your board code) to
+ * enable SSI output pins!
+ */
+
+/*
+ * LIMITATIONS:
+ *	The SSI unit has only one physical data line, so full duplex is
+ *	impossible.  This can be remedied  on the  SH7760 by  using the
+ *	other SSI unit for recording; however the SH7780 has only 1 SSI
+ *	unit, and its pins are shared with the AC97 unit,  among others.
+ *
+ * FEATURES:
+ *	The SSI features "compressed mode": in this mode it continuously
+ *	streams PCM data over the I2S lines and uses LRCK as a handshake
+ *	signal.  Can be used to send compressed data (AC3/DTS) to a DSP.
+ *	The number of bits sent over the wire in a frame can be adjusted
+ *	and can be independent from the actual sample bit depth. This is
+ *	useful to support TDM mode codecs like the AD1939 which have a
+ *	fixed TDM slot size, regardless of sample resolution.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <asm/io.h>
+
+#define SSICR	0x00
+#define SSISR	0x04
+
+#define CR_DMAEN	(1 << 28)
+#define CR_CHNL_SHIFT	22
+#define CR_CHNL_MASK	(3 << CR_CHNL_SHIFT)
+#define CR_DWL_SHIFT	19
+#define CR_DWL_MASK	(7 << CR_DWL_SHIFT)
+#define CR_SWL_SHIFT	16
+#define CR_SWL_MASK	(7 << CR_SWL_SHIFT)
+#define CR_SCK_MASTER	(1 << 15)	/* bitclock master bit */
+#define CR_SWS_MASTER	(1 << 14)	/* wordselect master bit */
+#define CR_SCKP		(1 << 13)	/* I2Sclock polarity */
+#define CR_SWSP		(1 << 12)	/* LRCK polarity */
+#define CR_SPDP		(1 << 11)
+#define CR_SDTA		(1 << 10)	/* i2s alignment (msb/lsb) */
+#define CR_PDTA		(1 << 9)	/* fifo data alignment */
+#define CR_DEL		(1 << 8)	/* delay data by 1 i2sclk */
+#define CR_BREN		(1 << 7)	/* clock gating in burst mode */
+#define CR_CKDIV_SHIFT	4
+#define CR_CKDIV_MASK	(7 << CR_CKDIV_SHIFT)	/* bitclock divider */
+#define CR_MUTE		(1 << 3)	/* SSI mute */
+#define CR_CPEN		(1 << 2)	/* compressed mode */
+#define CR_TRMD		(1 << 1)	/* transmit/receive select */
+#define CR_EN		(1 << 0)	/* enable SSI */
+
+#define SSIREG(reg)	(*(unsigned long *)(ssi->mmio + (reg)))
+
+struct ssi_priv {
+	unsigned long mmio;
+	unsigned long sysclk;
+	int inuse;
+} ssi_cpu_data[] = {
+#if defined(CONFIG_CPU_SUBTYPE_SH7760)
+	{
+		.mmio	= 0xFE680000,
+	},
+	{
+		.mmio	= 0xFE690000,
+	},
+#elif defined(CONFIG_CPU_SUBTYPE_SH7780)
+	{
+		.mmio	= 0xFFE70000,
+	},
+#else
+#error "Unsupported SuperH SoC"
+#endif
+};
+
+/*
+ * track usage of the SSI; it is simplex-only so prevent attempts of
+ * concurrent playback + capture. FIXME: any locking required?
+ */
+static int ssi_startup(struct snd_pcm_substream *substream,
+		       struct snd_soc_dai *dai)
+{
+	struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+	if (ssi->inuse) {
+		pr_debug("ssi: already in use!\n");
+		return -EBUSY;
+	} else
+		ssi->inuse = 1;
+	return 0;
+}
+
+static void ssi_shutdown(struct snd_pcm_substream *substream,
+			 struct snd_soc_dai *dai)
+{
+	struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+
+	ssi->inuse = 0;
+}
+
+static int ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+		       struct snd_soc_dai *dai)
+{
+	struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		SSIREG(SSICR) |= CR_DMAEN | CR_EN;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		SSIREG(SSICR) &= ~(CR_DMAEN | CR_EN);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int ssi_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *params,
+			 struct snd_soc_dai *dai)
+{
+	struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+	unsigned long ssicr = SSIREG(SSICR);
+	unsigned int bits, channels, swl, recv, i;
+
+	channels = params_channels(params);
+	bits = params->msbits;
+	recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1;
+
+	pr_debug("ssi_hw_params() enter\nssicr was    %08lx\n", ssicr);
+	pr_debug("bits: %u channels: %u\n", bits, channels);
+
+	ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA |
+		   CR_SWL_MASK);
+
+	/* direction (send/receive) */
+	if (!recv)
+		ssicr |= CR_TRMD;	/* transmit */
+
+	/* channels */
+	if ((channels < 2) || (channels > 8) || (channels & 1)) {
+		pr_debug("ssi: invalid number of channels\n");
+		return -EINVAL;
+	}
+	ssicr |= ((channels >> 1) - 1) << CR_CHNL_SHIFT;
+
+	/* DATA WORD LENGTH (DWL): databits in audio sample */
+	i = 0;
+	switch (bits) {
+	case 32: ++i;
+	case 24: ++i;
+	case 22: ++i;
+	case 20: ++i;
+	case 18: ++i;
+	case 16: ++i;
+		 ssicr |= i << CR_DWL_SHIFT;
+	case 8:	 break;
+	default:
+		pr_debug("ssi: invalid sample width\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * SYSTEM WORD LENGTH: size in bits of half a frame over the I2S
+	 * wires. This is usually bits_per_sample x channels/2;  i.e. in
+	 * Stereo mode  the SWL equals DWL.  SWL can  be bigger than the
+	 * product of (channels_per_slot x samplebits), e.g.  for codecs
+	 * like the AD1939 which  only accept 32bit wide TDM slots.  For
+	 * "standard" I2S operation we set SWL = chans / 2 * DWL here.
+	 * Waiting for ASoC to get TDM support ;-)
+	 */
+	if ((bits > 16) && (bits <= 24)) {
+		bits = 24;	/* these are padded by the SSI */
+		/*ssicr |= CR_PDTA;*/ /* cpu/data endianness ? */
+	}
+	i = 0;
+	swl = (bits * channels) / 2;
+	switch (swl) {
+	case 256: ++i;
+	case 128: ++i;
+	case 64:  ++i;
+	case 48:  ++i;
+	case 32:  ++i;
+	case 16:  ++i;
+		  ssicr |= i << CR_SWL_SHIFT;
+	case 8:   break;
+	default:
+		pr_debug("ssi: invalid system word length computed\n");
+		return -EINVAL;
+	}
+
+	SSIREG(SSICR) = ssicr;
+
+	pr_debug("ssi_hw_params() leave\nssicr is now %08lx\n", ssicr);
+	return 0;
+}
+
+static int ssi_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id,
+			  unsigned int freq, int dir)
+{
+	struct ssi_priv *ssi = &ssi_cpu_data[cpu_dai->id];
+
+	ssi->sysclk = freq;
+
+	return 0;
+}
+
+/*
+ * This divider is used to generate the SSI_SCK (I2S bitclock) from the
+ * clock at the HAC_BIT_CLK ("oversampling clock") pin.
+ */
+static int ssi_set_clkdiv(struct snd_soc_dai *dai, int did, int div)
+{
+	struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+	unsigned long ssicr;
+	int i;
+
+	i = 0;
+	ssicr = SSIREG(SSICR) & ~CR_CKDIV_MASK;
+	switch (div) {
+	case 16: ++i;
+	case 8:  ++i;
+	case 4:  ++i;
+	case 2:  ++i;
+		 SSIREG(SSICR) = ssicr | (i << CR_CKDIV_SHIFT);
+	case 1:  break;
+	default:
+		pr_debug("ssi: invalid sck divider %d\n", div);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int ssi_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct ssi_priv *ssi = &ssi_cpu_data[dai->id];
+	unsigned long ssicr = SSIREG(SSICR);
+
+	pr_debug("ssi_set_fmt()\nssicr was    0x%08lx\n", ssicr);
+
+	ssicr &= ~(CR_DEL | CR_PDTA | CR_BREN | CR_SWSP | CR_SCKP |
+		   CR_SWS_MASTER | CR_SCK_MASTER);
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		ssicr |= CR_DEL | CR_PDTA;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		ssicr |= CR_DEL;
+		break;
+	default:
+		pr_debug("ssi: unsupported format\n");
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
+	case SND_SOC_DAIFMT_CONT:
+		break;
+	case SND_SOC_DAIFMT_GATED:
+		ssicr |= CR_BREN;
+		break;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		ssicr |= CR_SCKP;	/* sample data at low clkedge */
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		ssicr |= CR_SCKP | CR_SWSP;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		ssicr |= CR_SWSP;	/* word select starts low */
+		break;
+	default:
+		pr_debug("ssi: invalid inversion\n");
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		ssicr |= CR_SCK_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		ssicr |= CR_SWS_MASTER;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		ssicr |= CR_SWS_MASTER | CR_SCK_MASTER;
+		break;
+	default:
+		pr_debug("ssi: invalid master/slave configuration\n");
+		return -EINVAL;
+	}
+
+	SSIREG(SSICR) = ssicr;
+	pr_debug("ssi_set_fmt() leave\nssicr is now 0x%08lx\n", ssicr);
+
+	return 0;
+}
+
+/* the SSI depends on an external clocksource (at HAC_BIT_CLK) even in
+ * Master mode,  so really this is board specific;  the SSI can do any
+ * rate with the right bitclk and divider settings.
+ */
+#define SSI_RATES	\
+	SNDRV_PCM_RATE_8000_192000
+
+/* the SSI can do 8-32 bit samples, with 8 possible channels */
+#define SSI_FMTS	\
+	(SNDRV_PCM_FMTBIT_S8      | SNDRV_PCM_FMTBIT_U8      |	\
+	 SNDRV_PCM_FMTBIT_S16_LE  | SNDRV_PCM_FMTBIT_U16_LE  |	\
+	 SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE |	\
+	 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE |	\
+	 SNDRV_PCM_FMTBIT_S32_LE  | SNDRV_PCM_FMTBIT_U32_LE)
+
+static struct snd_soc_dai_ops ssi_dai_ops = {
+	.startup	= ssi_startup,
+	.shutdown	= ssi_shutdown,
+	.trigger	= ssi_trigger,
+	.hw_params	= ssi_hw_params,
+	.set_sysclk	= ssi_set_sysclk,
+	.set_clkdiv	= ssi_set_clkdiv,
+	.set_fmt	= ssi_set_fmt,
+};
+
+struct snd_soc_dai_driver sh4_ssi_dai[] = {
+{
+	.name			= "ssi-dai.0",
+	.playback = {
+		.rates		= SSI_RATES,
+		.formats	= SSI_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 8,
+	},
+	.capture = {
+		.rates		= SSI_RATES,
+		.formats	= SSI_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 8,
+	},
+	.ops = &ssi_dai_ops,
+},
+#ifdef CONFIG_CPU_SUBTYPE_SH7760
+{
+	.name			= "ssi-dai.1",
+	.playback = {
+		.rates		= SSI_RATES,
+		.formats	= SSI_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 8,
+	},
+	.capture = {
+		.rates		= SSI_RATES,
+		.formats	= SSI_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 8,
+	},
+	.ops = &ssi_dai_ops,
+},
+#endif
+};
+
+static int __devinit sh4_soc_dai_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_dais(&pdev->dev, sh4_ssi_dai,
+			ARRAY_SIZE(sh4_ssi_dai));
+}
+
+static int __devexit sh4_soc_dai_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(sh4_ssi_dai));
+	return 0;
+}
+
+static struct platform_driver sh4_ssi_driver = {
+	.driver = {
+			.name = "sh4-ssi-dai",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = sh4_soc_dai_probe,
+	.remove = __devexit_p(sh4_soc_dai_remove),
+};
+
+static int __init snd_sh4_ssi_init(void)
+{
+	return platform_driver_register(&sh4_ssi_driver);
+}
+module_init(snd_sh4_ssi_init);
+
+static void __exit snd_sh4_ssi_exit(void)
+{
+	platform_driver_unregister(&sh4_ssi_driver);
+}
+module_exit(snd_sh4_ssi_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SuperH onchip SSI (I2S) audio driver");
+MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
diff --git a/linux/sound/soc/soc-cache.c b/linux/sound/soc/soc-cache.c
new file mode 100644
index 0000000..d214f02
--- /dev/null
+++ b/linux/sound/soc/soc-cache.c
@@ -0,0 +1,726 @@
+/*
+ * soc-cache.c  --  ASoC register cache helpers
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <sound/soc.h>
+
+static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec,
+				     unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+
+	if (reg >= codec->driver->reg_cache_size ||
+		snd_soc_codec_volatile_register(codec, reg)) {
+			if (codec->cache_only)
+				return -1;
+
+			return codec->hw_read(codec, reg);
+	}
+
+	return cache[reg];
+}
+
+static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg,
+			     unsigned int value)
+{
+	u16 *cache = codec->reg_cache;
+	u8 data[2];
+	int ret;
+
+	data[0] = (reg << 4) | ((value >> 8) & 0x000f);
+	data[1] = value & 0x00ff;
+
+	if (!snd_soc_codec_volatile_register(codec, reg) &&
+		reg < codec->driver->reg_cache_size)
+			cache[reg] = value;
+
+	if (codec->cache_only) {
+		codec->cache_sync = 1;
+		return 0;
+	}
+
+	dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value);
+
+	ret = codec->hw_write(codec->control_data, data, 2);
+	if (ret == 2)
+		return 0;
+	if (ret < 0)
+		return ret;
+	else
+		return -EIO;
+}
+
+#if defined(CONFIG_SPI_MASTER)
+static int snd_soc_4_12_spi_write(void *control_data, const char *data,
+				 int len)
+{
+	struct spi_device *spi = control_data;
+	struct spi_transfer t;
+	struct spi_message m;
+	u8 msg[2];
+
+	if (len <= 0)
+		return 0;
+
+	msg[0] = data[1];
+	msg[1] = data[0];
+
+	spi_message_init(&m);
+	memset(&t, 0, (sizeof t));
+
+	t.tx_buf = &msg[0];
+	t.len = len;
+
+	spi_message_add_tail(&t, &m);
+	spi_sync(spi, &m);
+
+	return len;
+}
+#else
+#define snd_soc_4_12_spi_write NULL
+#endif
+
+static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec,
+				     unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+
+	if (reg >= codec->driver->reg_cache_size ||
+		snd_soc_codec_volatile_register(codec, reg)) {
+			if (codec->cache_only)
+				return -1;
+
+			return codec->hw_read(codec, reg);
+	}
+
+	return cache[reg];
+}
+
+static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg,
+			     unsigned int value)
+{
+	u16 *cache = codec->reg_cache;
+	u8 data[2];
+	int ret;
+
+	data[0] = (reg << 1) | ((value >> 8) & 0x0001);
+	data[1] = value & 0x00ff;
+
+	if (!snd_soc_codec_volatile_register(codec, reg) &&
+		reg < codec->driver->reg_cache_size)
+			cache[reg] = value;
+
+	if (codec->cache_only) {
+		codec->cache_sync = 1;
+		return 0;
+	}
+
+	dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value);
+
+	ret = codec->hw_write(codec->control_data, data, 2);
+	if (ret == 2)
+		return 0;
+	if (ret < 0)
+		return ret;
+	else
+		return -EIO;
+}
+
+#if defined(CONFIG_SPI_MASTER)
+static int snd_soc_7_9_spi_write(void *control_data, const char *data,
+				 int len)
+{
+	struct spi_device *spi = control_data;
+	struct spi_transfer t;
+	struct spi_message m;
+	u8 msg[2];
+
+	if (len <= 0)
+		return 0;
+
+	msg[0] = data[0];
+	msg[1] = data[1];
+
+	spi_message_init(&m);
+	memset(&t, 0, (sizeof t));
+
+	t.tx_buf = &msg[0];
+	t.len = len;
+
+	spi_message_add_tail(&t, &m);
+	spi_sync(spi, &m);
+
+	return len;
+}
+#else
+#define snd_soc_7_9_spi_write NULL
+#endif
+
+static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg,
+			     unsigned int value)
+{
+	u8 *cache = codec->reg_cache;
+	u8 data[2];
+
+	reg &= 0xff;
+	data[0] = reg;
+	data[1] = value & 0xff;
+
+	if (!snd_soc_codec_volatile_register(codec, reg) &&
+		reg < codec->driver->reg_cache_size)
+			cache[reg] = value;
+
+	if (codec->cache_only) {
+		codec->cache_sync = 1;
+		return 0;
+	}
+
+	dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value);
+
+	if (codec->hw_write(codec->control_data, data, 2) == 2)
+		return 0;
+	else
+		return -EIO;
+}
+
+static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec,
+				     unsigned int reg)
+{
+	u8 *cache = codec->reg_cache;
+
+	reg &= 0xff;
+	if (reg >= codec->driver->reg_cache_size ||
+		snd_soc_codec_volatile_register(codec, reg)) {
+			if (codec->cache_only)
+				return -1;
+
+			return codec->hw_read(codec, reg);
+	}
+
+	return cache[reg];
+}
+
+#if defined(CONFIG_SPI_MASTER)
+static int snd_soc_8_8_spi_write(void *control_data, const char *data,
+				 int len)
+{
+	struct spi_device *spi = control_data;
+	struct spi_transfer t;
+	struct spi_message m;
+	u8 msg[2];
+
+	if (len <= 0)
+		return 0;
+
+	msg[0] = data[0];
+	msg[1] = data[1];
+
+	spi_message_init(&m);
+	memset(&t, 0, (sizeof t));
+
+	t.tx_buf = &msg[0];
+	t.len = len;
+
+	spi_message_add_tail(&t, &m);
+	spi_sync(spi, &m);
+
+	return len;
+}
+#else
+#define snd_soc_8_8_spi_write NULL
+#endif
+
+static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg,
+			      unsigned int value)
+{
+	u16 *reg_cache = codec->reg_cache;
+	u8 data[3];
+
+	data[0] = reg;
+	data[1] = (value >> 8) & 0xff;
+	data[2] = value & 0xff;
+
+	if (!snd_soc_codec_volatile_register(codec, reg) &&
+	    reg < codec->driver->reg_cache_size)
+		reg_cache[reg] = value;
+
+	if (codec->cache_only) {
+		codec->cache_sync = 1;
+		return 0;
+	}
+
+	dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value);
+
+	if (codec->hw_write(codec->control_data, data, 3) == 3)
+		return 0;
+	else
+		return -EIO;
+}
+
+static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec,
+				      unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+
+	if (reg >= codec->driver->reg_cache_size ||
+	    snd_soc_codec_volatile_register(codec, reg)) {
+		if (codec->cache_only)
+			return -1;
+
+		return codec->hw_read(codec, reg);
+	} else {
+		return cache[reg];
+	}
+}
+
+#if defined(CONFIG_SPI_MASTER)
+static int snd_soc_8_16_spi_write(void *control_data, const char *data,
+				 int len)
+{
+	struct spi_device *spi = control_data;
+	struct spi_transfer t;
+	struct spi_message m;
+	u8 msg[3];
+
+	if (len <= 0)
+		return 0;
+
+	msg[0] = data[0];
+	msg[1] = data[1];
+	msg[2] = data[2];
+
+	spi_message_init(&m);
+	memset(&t, 0, (sizeof t));
+
+	t.tx_buf = &msg[0];
+	t.len = len;
+
+	spi_message_add_tail(&t, &m);
+	spi_sync(spi, &m);
+
+	return len;
+}
+#else
+#define snd_soc_8_16_spi_write NULL
+#endif
+
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+static unsigned int snd_soc_8_8_read_i2c(struct snd_soc_codec *codec,
+					  unsigned int r)
+{
+	struct i2c_msg xfer[2];
+	u8 reg = r;
+	u8 data;
+	int ret;
+	struct i2c_client *client = codec->control_data;
+
+	/* Write register */
+	xfer[0].addr = client->addr;
+	xfer[0].flags = 0;
+	xfer[0].len = 1;
+	xfer[0].buf = &reg;
+
+	/* Read data */
+	xfer[1].addr = client->addr;
+	xfer[1].flags = I2C_M_RD;
+	xfer[1].len = 1;
+	xfer[1].buf = &data;
+
+	ret = i2c_transfer(client->adapter, xfer, 2);
+	if (ret != 2) {
+		dev_err(&client->dev, "i2c_transfer() returned %d\n", ret);
+		return 0;
+	}
+
+	return data;
+}
+#else
+#define snd_soc_8_8_read_i2c NULL
+#endif
+
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec,
+					  unsigned int r)
+{
+	struct i2c_msg xfer[2];
+	u8 reg = r;
+	u16 data;
+	int ret;
+	struct i2c_client *client = codec->control_data;
+
+	/* Write register */
+	xfer[0].addr = client->addr;
+	xfer[0].flags = 0;
+	xfer[0].len = 1;
+	xfer[0].buf = &reg;
+
+	/* Read data */
+	xfer[1].addr = client->addr;
+	xfer[1].flags = I2C_M_RD;
+	xfer[1].len = 2;
+	xfer[1].buf = (u8 *)&data;
+
+	ret = i2c_transfer(client->adapter, xfer, 2);
+	if (ret != 2) {
+		dev_err(&client->dev, "i2c_transfer() returned %d\n", ret);
+		return 0;
+	}
+
+	return (data >> 8) | ((data & 0xff) << 8);
+}
+#else
+#define snd_soc_8_16_read_i2c NULL
+#endif
+
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+static unsigned int snd_soc_16_8_read_i2c(struct snd_soc_codec *codec,
+					  unsigned int r)
+{
+	struct i2c_msg xfer[2];
+	u16 reg = r;
+	u8 data;
+	int ret;
+	struct i2c_client *client = codec->control_data;
+
+	/* Write register */
+	xfer[0].addr = client->addr;
+	xfer[0].flags = 0;
+	xfer[0].len = 2;
+	xfer[0].buf = (u8 *)&reg;
+
+	/* Read data */
+	xfer[1].addr = client->addr;
+	xfer[1].flags = I2C_M_RD;
+	xfer[1].len = 1;
+	xfer[1].buf = &data;
+
+	ret = i2c_transfer(client->adapter, xfer, 2);
+	if (ret != 2) {
+		dev_err(&client->dev, "i2c_transfer() returned %d\n", ret);
+		return 0;
+	}
+
+	return data;
+}
+#else
+#define snd_soc_16_8_read_i2c NULL
+#endif
+
+static unsigned int snd_soc_16_8_read(struct snd_soc_codec *codec,
+				     unsigned int reg)
+{
+	u8 *cache = codec->reg_cache;
+
+	reg &= 0xff;
+	if (reg >= codec->driver->reg_cache_size ||
+		snd_soc_codec_volatile_register(codec, reg)) {
+			if (codec->cache_only)
+				return -1;
+
+			return codec->hw_read(codec, reg);
+	}
+
+	return cache[reg];
+}
+
+static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg,
+			     unsigned int value)
+{
+	u8 *cache = codec->reg_cache;
+	u8 data[3];
+	int ret;
+
+	data[0] = (reg >> 8) & 0xff;
+	data[1] = reg & 0xff;
+	data[2] = value;
+
+	reg &= 0xff;
+	if (!snd_soc_codec_volatile_register(codec, reg) &&
+		reg < codec->driver->reg_cache_size)
+			cache[reg] = value;
+
+	if (codec->cache_only) {
+		codec->cache_sync = 1;
+		return 0;
+	}
+
+	dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value);
+
+	ret = codec->hw_write(codec->control_data, data, 3);
+	if (ret == 3)
+		return 0;
+	if (ret < 0)
+		return ret;
+	else
+		return -EIO;
+}
+
+#if defined(CONFIG_SPI_MASTER)
+static int snd_soc_16_8_spi_write(void *control_data, const char *data,
+				 int len)
+{
+	struct spi_device *spi = control_data;
+	struct spi_transfer t;
+	struct spi_message m;
+	u8 msg[3];
+
+	if (len <= 0)
+		return 0;
+
+	msg[0] = data[0];
+	msg[1] = data[1];
+	msg[2] = data[2];
+
+	spi_message_init(&m);
+	memset(&t, 0, (sizeof t));
+
+	t.tx_buf = &msg[0];
+	t.len = len;
+
+	spi_message_add_tail(&t, &m);
+	spi_sync(spi, &m);
+
+	return len;
+}
+#else
+#define snd_soc_16_8_spi_write NULL
+#endif
+
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+static unsigned int snd_soc_16_16_read_i2c(struct snd_soc_codec *codec,
+					   unsigned int r)
+{
+	struct i2c_msg xfer[2];
+	u16 reg = cpu_to_be16(r);
+	u16 data;
+	int ret;
+	struct i2c_client *client = codec->control_data;
+
+	/* Write register */
+	xfer[0].addr = client->addr;
+	xfer[0].flags = 0;
+	xfer[0].len = 2;
+	xfer[0].buf = (u8 *)&reg;
+
+	/* Read data */
+	xfer[1].addr = client->addr;
+	xfer[1].flags = I2C_M_RD;
+	xfer[1].len = 2;
+	xfer[1].buf = (u8 *)&data;
+
+	ret = i2c_transfer(client->adapter, xfer, 2);
+	if (ret != 2) {
+		dev_err(&client->dev, "i2c_transfer() returned %d\n", ret);
+		return 0;
+	}
+
+	return be16_to_cpu(data);
+}
+#else
+#define snd_soc_16_16_read_i2c NULL
+#endif
+
+static unsigned int snd_soc_16_16_read(struct snd_soc_codec *codec,
+				       unsigned int reg)
+{
+	u16 *cache = codec->reg_cache;
+
+	if (reg >= codec->driver->reg_cache_size ||
+	    snd_soc_codec_volatile_register(codec, reg)) {
+		if (codec->cache_only)
+			return -1;
+
+		return codec->hw_read(codec, reg);
+	}
+
+	return cache[reg];
+}
+
+static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg,
+			       unsigned int value)
+{
+	u16 *cache = codec->reg_cache;
+	u8 data[4];
+	int ret;
+
+	data[0] = (reg >> 8) & 0xff;
+	data[1] = reg & 0xff;
+	data[2] = (value >> 8) & 0xff;
+	data[3] = value & 0xff;
+
+	if (!snd_soc_codec_volatile_register(codec, reg) &&
+		reg < codec->driver->reg_cache_size)
+			cache[reg] = value;
+
+	if (codec->cache_only) {
+		codec->cache_sync = 1;
+		return 0;
+	}
+
+	dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value);
+
+	ret = codec->hw_write(codec->control_data, data, 4);
+	if (ret == 4)
+		return 0;
+	if (ret < 0)
+		return ret;
+	else
+		return -EIO;
+}
+
+#if defined(CONFIG_SPI_MASTER)
+static int snd_soc_16_16_spi_write(void *control_data, const char *data,
+				 int len)
+{
+	struct spi_device *spi = control_data;
+	struct spi_transfer t;
+	struct spi_message m;
+	u8 msg[4];
+
+	if (len <= 0)
+		return 0;
+
+	msg[0] = data[0];
+	msg[1] = data[1];
+	msg[2] = data[2];
+	msg[3] = data[3];
+
+	spi_message_init(&m);
+	memset(&t, 0, (sizeof t));
+
+	t.tx_buf = &msg[0];
+	t.len = len;
+
+	spi_message_add_tail(&t, &m);
+	spi_sync(spi, &m);
+
+	return len;
+}
+#else
+#define snd_soc_16_16_spi_write NULL
+#endif
+
+static struct {
+	int addr_bits;
+	int data_bits;
+	int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int);
+	int (*spi_write)(void *, const char *, int);
+	unsigned int (*read)(struct snd_soc_codec *, unsigned int);
+	unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int);
+} io_types[] = {
+	{
+		.addr_bits = 4, .data_bits = 12,
+		.write = snd_soc_4_12_write, .read = snd_soc_4_12_read,
+		.spi_write = snd_soc_4_12_spi_write,
+	},
+	{
+		.addr_bits = 7, .data_bits = 9,
+		.write = snd_soc_7_9_write, .read = snd_soc_7_9_read,
+		.spi_write = snd_soc_7_9_spi_write,
+	},
+	{
+		.addr_bits = 8, .data_bits = 8,
+		.write = snd_soc_8_8_write, .read = snd_soc_8_8_read,
+		.i2c_read = snd_soc_8_8_read_i2c,
+		.spi_write = snd_soc_8_8_spi_write,
+	},
+	{
+		.addr_bits = 8, .data_bits = 16,
+		.write = snd_soc_8_16_write, .read = snd_soc_8_16_read,
+		.i2c_read = snd_soc_8_16_read_i2c,
+		.spi_write = snd_soc_8_16_spi_write,
+	},
+	{
+		.addr_bits = 16, .data_bits = 8,
+		.write = snd_soc_16_8_write, .read = snd_soc_16_8_read,
+		.i2c_read = snd_soc_16_8_read_i2c,
+		.spi_write = snd_soc_16_8_spi_write,
+	},
+	{
+		.addr_bits = 16, .data_bits = 16,
+		.write = snd_soc_16_16_write, .read = snd_soc_16_16_read,
+		.i2c_read = snd_soc_16_16_read_i2c,
+		.spi_write = snd_soc_16_16_spi_write,
+	},
+};
+
+/**
+ * snd_soc_codec_set_cache_io: Set up standard I/O functions.
+ *
+ * @codec: CODEC to configure.
+ * @type: Type of cache.
+ * @addr_bits: Number of bits of register address data.
+ * @data_bits: Number of bits of data per register.
+ * @control: Control bus used.
+ *
+ * Register formats are frequently shared between many I2C and SPI
+ * devices.  In order to promote code reuse the ASoC core provides
+ * some standard implementations of CODEC read and write operations
+ * which can be set up using this function.
+ *
+ * The caller is responsible for allocating and initialising the
+ * actual cache.
+ *
+ * Note that at present this code cannot be used by CODECs with
+ * volatile registers.
+ */
+int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
+			       int addr_bits, int data_bits,
+			       enum snd_soc_control_type control)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(io_types); i++)
+		if (io_types[i].addr_bits == addr_bits &&
+		    io_types[i].data_bits == data_bits)
+			break;
+	if (i == ARRAY_SIZE(io_types)) {
+		printk(KERN_ERR
+		       "No I/O functions for %d bit address %d bit data\n",
+		       addr_bits, data_bits);
+		return -EINVAL;
+	}
+
+	codec->driver->write = io_types[i].write;
+	codec->driver->read = io_types[i].read;
+
+	switch (control) {
+	case SND_SOC_CUSTOM:
+		break;
+
+	case SND_SOC_I2C:
+#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
+		codec->hw_write = (hw_write_t)i2c_master_send;
+#endif
+		if (io_types[i].i2c_read)
+			codec->hw_read = io_types[i].i2c_read;
+
+		codec->control_data = container_of(codec->dev,
+						   struct i2c_client,
+						   dev);
+		break;
+
+	case SND_SOC_SPI:
+		if (io_types[i].spi_write)
+			codec->hw_write = io_types[i].spi_write;
+
+		codec->control_data = container_of(codec->dev,
+						   struct spi_device,
+						   dev);
+		break;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io);
diff --git a/linux/sound/soc/soc-core.c b/linux/sound/soc/soc-core.c
new file mode 100644
index 0000000..85b7d54
--- /dev/null
+++ b/linux/sound/soc/soc-core.c
@@ -0,0 +1,3354 @@
+/*
+ * soc-core.c  --  ALSA SoC Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Copyright 2005 Openedhand Ltd.
+ * Copyright (C) 2010 Slimlogic Ltd.
+ * Copyright (C) 2010 Texas Instruments Inc.
+ *
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *         with code, comments and ideas from :-
+ *         Richard Purdie <richard@openedhand.com>
+ *
+ *  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.
+ *
+ *  TODO:
+ *   o Add hw rules to enforce rates, etc.
+ *   o More testing with other codecs/machines.
+ *   o Add more codecs and platforms to ensure good API coverage.
+ *   o Support TDM on PCM and I2S
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/ac97_codec.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#define NAME_SIZE	32
+
+static DEFINE_MUTEX(pcm_mutex);
+static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq);
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *debugfs_root;
+#endif
+
+static DEFINE_MUTEX(client_mutex);
+static LIST_HEAD(card_list);
+static LIST_HEAD(dai_list);
+static LIST_HEAD(platform_list);
+static LIST_HEAD(codec_list);
+
+static int snd_soc_register_card(struct snd_soc_card *card);
+static int snd_soc_unregister_card(struct snd_soc_card *card);
+static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num);
+
+/*
+ * This is a timeout to do a DAPM powerdown after a stream is closed().
+ * It can be used to eliminate pops between different playback streams, e.g.
+ * between two audio tracks.
+ */
+static int pmdown_time = 5000;
+module_param(pmdown_time, int, 0);
+MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");
+
+/*
+ * This function forces any delayed work to be queued and run.
+ */
+static int run_delayed_work(struct delayed_work *dwork)
+{
+	int ret;
+
+	/* cancel any work waiting to be queued. */
+	ret = cancel_delayed_work(dwork);
+
+	/* if there was any work waiting then we run it now and
+	 * wait for it's completion */
+	if (ret) {
+		schedule_delayed_work(dwork, 0);
+		flush_scheduled_work();
+	}
+	return ret;
+}
+
+/* codec register dump */
+static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf)
+{
+	int ret, i, step = 1, count = 0;
+
+	if (!codec->driver->reg_cache_size)
+		return 0;
+
+	if (codec->driver->reg_cache_step)
+		step = codec->driver->reg_cache_step;
+
+	count += sprintf(buf, "%s registers\n", codec->name);
+	for (i = 0; i < codec->driver->reg_cache_size; i += step) {
+		if (codec->driver->readable_register && !codec->driver->readable_register(i))
+			continue;
+
+		count += sprintf(buf + count, "%2x: ", i);
+		if (count >= PAGE_SIZE - 1)
+			break;
+
+		if (codec->driver->display_register) {
+			count += codec->driver->display_register(codec, buf + count,
+							 PAGE_SIZE - count, i);
+		} else {
+			/* If the read fails it's almost certainly due to
+			 * the register being volatile and the device being
+			 * powered off.
+			 */
+			ret = codec->driver->read(codec, i);
+			if (ret >= 0)
+				count += snprintf(buf + count,
+						  PAGE_SIZE - count,
+						  "%4x", ret);
+			else
+				count += snprintf(buf + count,
+						  PAGE_SIZE - count,
+						  "<no data: %d>", ret);
+		}
+
+		if (count >= PAGE_SIZE - 1)
+			break;
+
+		count += snprintf(buf + count, PAGE_SIZE - count, "\n");
+		if (count >= PAGE_SIZE - 1)
+			break;
+	}
+
+	/* Truncate count; min() would cause a warning */
+	if (count >= PAGE_SIZE)
+		count = PAGE_SIZE - 1;
+
+	return count;
+}
+static ssize_t codec_reg_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct snd_soc_pcm_runtime *rtd =
+			container_of(dev, struct snd_soc_pcm_runtime, dev);
+
+	return soc_codec_reg_show(rtd->codec, buf);
+}
+
+static DEVICE_ATTR(codec_reg, 0444, codec_reg_show, NULL);
+
+static ssize_t pmdown_time_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct snd_soc_pcm_runtime *rtd =
+			container_of(dev, struct snd_soc_pcm_runtime, dev);
+
+	return sprintf(buf, "%ld\n", rtd->pmdown_time);
+}
+
+static ssize_t pmdown_time_set(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct snd_soc_pcm_runtime *rtd =
+			container_of(dev, struct snd_soc_pcm_runtime, dev);
+	int ret;
+
+	ret = strict_strtol(buf, 10, &rtd->pmdown_time);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(pmdown_time, 0644, pmdown_time_show, pmdown_time_set);
+
+#ifdef CONFIG_DEBUG_FS
+static int codec_reg_open_file(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+	return 0;
+}
+
+static ssize_t codec_reg_read_file(struct file *file, char __user *user_buf,
+			       size_t count, loff_t *ppos)
+{
+	ssize_t ret;
+	struct snd_soc_codec *codec = file->private_data;
+	char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	ret = soc_codec_reg_show(codec, buf);
+	if (ret >= 0)
+		ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+	kfree(buf);
+	return ret;
+}
+
+static ssize_t codec_reg_write_file(struct file *file,
+		const char __user *user_buf, size_t count, loff_t *ppos)
+{
+	char buf[32];
+	int buf_size;
+	char *start = buf;
+	unsigned long reg, value;
+	int step = 1;
+	struct snd_soc_codec *codec = file->private_data;
+
+	buf_size = min(count, (sizeof(buf)-1));
+	if (copy_from_user(buf, user_buf, buf_size))
+		return -EFAULT;
+	buf[buf_size] = 0;
+
+	if (codec->driver->reg_cache_step)
+		step = codec->driver->reg_cache_step;
+
+	while (*start == ' ')
+		start++;
+	reg = simple_strtoul(start, &start, 16);
+	if ((reg >= codec->driver->reg_cache_size) || (reg % step))
+		return -EINVAL;
+	while (*start == ' ')
+		start++;
+	if (strict_strtoul(start, 16, &value))
+		return -EINVAL;
+	codec->driver->write(codec, reg, value);
+	return buf_size;
+}
+
+static const struct file_operations codec_reg_fops = {
+	.open = codec_reg_open_file,
+	.read = codec_reg_read_file,
+	.write = codec_reg_write_file,
+	.llseek = default_llseek,
+};
+
+static void soc_init_codec_debugfs(struct snd_soc_codec *codec)
+{
+	codec->debugfs_codec_root = debugfs_create_dir(codec->name ,
+						       debugfs_root);
+	if (!codec->debugfs_codec_root) {
+		printk(KERN_WARNING
+		       "ASoC: Failed to create codec debugfs directory\n");
+		return;
+	}
+
+	codec->debugfs_reg = debugfs_create_file("codec_reg", 0644,
+						 codec->debugfs_codec_root,
+						 codec, &codec_reg_fops);
+	if (!codec->debugfs_reg)
+		printk(KERN_WARNING
+		       "ASoC: Failed to create codec register debugfs file\n");
+
+	codec->debugfs_pop_time = debugfs_create_u32("dapm_pop_time", 0644,
+						     codec->debugfs_codec_root,
+						     &codec->pop_time);
+	if (!codec->debugfs_pop_time)
+		printk(KERN_WARNING
+		       "Failed to create pop time debugfs file\n");
+
+	codec->debugfs_dapm = debugfs_create_dir("dapm",
+						 codec->debugfs_codec_root);
+	if (!codec->debugfs_dapm)
+		printk(KERN_WARNING
+		       "Failed to create DAPM debugfs directory\n");
+
+	snd_soc_dapm_debugfs_init(codec);
+}
+
+static void soc_cleanup_codec_debugfs(struct snd_soc_codec *codec)
+{
+	debugfs_remove_recursive(codec->debugfs_codec_root);
+}
+
+static ssize_t codec_list_read_file(struct file *file, char __user *user_buf,
+				    size_t count, loff_t *ppos)
+{
+	char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	ssize_t len, ret = 0;
+	struct snd_soc_codec *codec;
+
+	if (!buf)
+		return -ENOMEM;
+
+	list_for_each_entry(codec, &codec_list, list) {
+		len = snprintf(buf + ret, PAGE_SIZE - ret, "%s\n",
+			       codec->name);
+		if (len >= 0)
+			ret += len;
+		if (ret > PAGE_SIZE) {
+			ret = PAGE_SIZE;
+			break;
+		}
+	}
+
+	if (ret >= 0)
+		ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	kfree(buf);
+
+	return ret;
+}
+
+static const struct file_operations codec_list_fops = {
+	.read = codec_list_read_file,
+	.llseek = default_llseek,/* read accesses f_pos */
+};
+
+static ssize_t dai_list_read_file(struct file *file, char __user *user_buf,
+				  size_t count, loff_t *ppos)
+{
+	char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	ssize_t len, ret = 0;
+	struct snd_soc_dai *dai;
+
+	if (!buf)
+		return -ENOMEM;
+
+	list_for_each_entry(dai, &dai_list, list) {
+		len = snprintf(buf + ret, PAGE_SIZE - ret, "%s\n", dai->name);
+		if (len >= 0)
+			ret += len;
+		if (ret > PAGE_SIZE) {
+			ret = PAGE_SIZE;
+			break;
+		}
+	}
+
+	ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	kfree(buf);
+
+	return ret;
+}
+
+static const struct file_operations dai_list_fops = {
+	.read = dai_list_read_file,
+	.llseek = default_llseek,/* read accesses f_pos */
+};
+
+static ssize_t platform_list_read_file(struct file *file,
+				       char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	ssize_t len, ret = 0;
+	struct snd_soc_platform *platform;
+
+	if (!buf)
+		return -ENOMEM;
+
+	list_for_each_entry(platform, &platform_list, list) {
+		len = snprintf(buf + ret, PAGE_SIZE - ret, "%s\n",
+			       platform->name);
+		if (len >= 0)
+			ret += len;
+		if (ret > PAGE_SIZE) {
+			ret = PAGE_SIZE;
+			break;
+		}
+	}
+
+	ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	kfree(buf);
+
+	return ret;
+}
+
+static const struct file_operations platform_list_fops = {
+	.read = platform_list_read_file,
+	.llseek = default_llseek,/* read accesses f_pos */
+};
+
+#else
+
+static inline void soc_init_codec_debugfs(struct snd_soc_codec *codec)
+{
+}
+
+static inline void soc_cleanup_codec_debugfs(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+#ifdef CONFIG_SND_SOC_AC97_BUS
+/* unregister ac97 codec */
+static int soc_ac97_dev_unregister(struct snd_soc_codec *codec)
+{
+	if (codec->ac97->dev.bus)
+		device_unregister(&codec->ac97->dev);
+	return 0;
+}
+
+/* stop no dev release warning */
+static void soc_ac97_device_release(struct device *dev){}
+
+/* register ac97 codec to bus */
+static int soc_ac97_dev_register(struct snd_soc_codec *codec)
+{
+	int err;
+
+	codec->ac97->dev.bus = &ac97_bus_type;
+	codec->ac97->dev.parent = codec->card->dev;
+	codec->ac97->dev.release = soc_ac97_device_release;
+
+	dev_set_name(&codec->ac97->dev, "%d-%d:%s",
+		     codec->card->snd_card->number, 0, codec->name);
+	err = device_register(&codec->ac97->dev);
+	if (err < 0) {
+		snd_printk(KERN_ERR "Can't register ac97 bus\n");
+		codec->ac97->dev.bus = NULL;
+		return err;
+	}
+	return 0;
+}
+#endif
+
+static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	int ret;
+
+	if (codec_dai->driver->symmetric_rates || cpu_dai->driver->symmetric_rates ||
+			rtd->dai_link->symmetric_rates) {
+		dev_dbg(&rtd->dev, "Symmetry forces %dHz rate\n",
+				rtd->rate);
+
+		ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+						   SNDRV_PCM_HW_PARAM_RATE,
+						   rtd->rate,
+						   rtd->rate);
+		if (ret < 0) {
+			dev_err(&rtd->dev,
+				"Unable to apply rate symmetry constraint: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Called by ALSA when a PCM substream is opened, the runtime->hw record is
+ * then initialized and any private data can be allocated. This also calls
+ * startup for the cpu DAI, platform, machine and codec DAI.
+ */
+static int soc_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_platform *platform = rtd->platform;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver;
+	struct snd_soc_dai_driver *codec_dai_drv = codec_dai->driver;
+	int ret = 0;
+
+	mutex_lock(&pcm_mutex);
+
+	/* startup the audio subsystem */
+	if (cpu_dai->driver->ops->startup) {
+		ret = cpu_dai->driver->ops->startup(substream, cpu_dai);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: can't open interface %s\n",
+				cpu_dai->name);
+			goto out;
+		}
+	}
+
+	if (platform->driver->ops->open) {
+		ret = platform->driver->ops->open(substream);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: can't open platform %s\n", platform->name);
+			goto platform_err;
+		}
+	}
+
+	if (codec_dai->driver->ops->startup) {
+		ret = codec_dai->driver->ops->startup(substream, codec_dai);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: can't open codec %s\n",
+				codec_dai->name);
+			goto codec_dai_err;
+		}
+	}
+
+	if (rtd->dai_link->ops && rtd->dai_link->ops->startup) {
+		ret = rtd->dai_link->ops->startup(substream);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: %s startup failed\n", rtd->dai_link->name);
+			goto machine_err;
+		}
+	}
+
+	/* Check that the codec and cpu DAI's are compatible */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		runtime->hw.rate_min =
+			max(codec_dai_drv->playback.rate_min,
+			    cpu_dai_drv->playback.rate_min);
+		runtime->hw.rate_max =
+			min(codec_dai_drv->playback.rate_max,
+			    cpu_dai_drv->playback.rate_max);
+		runtime->hw.channels_min =
+			max(codec_dai_drv->playback.channels_min,
+				cpu_dai_drv->playback.channels_min);
+		runtime->hw.channels_max =
+			min(codec_dai_drv->playback.channels_max,
+				cpu_dai_drv->playback.channels_max);
+		runtime->hw.formats =
+			codec_dai_drv->playback.formats & cpu_dai_drv->playback.formats;
+		runtime->hw.rates =
+			codec_dai_drv->playback.rates & cpu_dai_drv->playback.rates;
+		if (codec_dai_drv->playback.rates
+			   & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
+			runtime->hw.rates |= cpu_dai_drv->playback.rates;
+		if (cpu_dai_drv->playback.rates
+			   & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
+			runtime->hw.rates |= codec_dai_drv->playback.rates;
+	} else {
+		runtime->hw.rate_min =
+			max(codec_dai_drv->capture.rate_min,
+			    cpu_dai_drv->capture.rate_min);
+		runtime->hw.rate_max =
+			min(codec_dai_drv->capture.rate_max,
+			    cpu_dai_drv->capture.rate_max);
+		runtime->hw.channels_min =
+			max(codec_dai_drv->capture.channels_min,
+				cpu_dai_drv->capture.channels_min);
+		runtime->hw.channels_max =
+			min(codec_dai_drv->capture.channels_max,
+				cpu_dai_drv->capture.channels_max);
+		runtime->hw.formats =
+			codec_dai_drv->capture.formats & cpu_dai_drv->capture.formats;
+		runtime->hw.rates =
+			codec_dai_drv->capture.rates & cpu_dai_drv->capture.rates;
+		if (codec_dai_drv->capture.rates
+			   & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
+			runtime->hw.rates |= cpu_dai_drv->capture.rates;
+		if (cpu_dai_drv->capture.rates
+			   & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
+			runtime->hw.rates |= codec_dai_drv->capture.rates;
+	}
+
+	snd_pcm_limit_hw_rates(runtime);
+	if (!runtime->hw.rates) {
+		printk(KERN_ERR "asoc: %s <-> %s No matching rates\n",
+			codec_dai->name, cpu_dai->name);
+		goto config_err;
+	}
+	if (!runtime->hw.formats) {
+		printk(KERN_ERR "asoc: %s <-> %s No matching formats\n",
+			codec_dai->name, cpu_dai->name);
+		goto config_err;
+	}
+	if (!runtime->hw.channels_min || !runtime->hw.channels_max) {
+		printk(KERN_ERR "asoc: %s <-> %s No matching channels\n",
+				codec_dai->name, cpu_dai->name);
+		goto config_err;
+	}
+
+	/* Symmetry only applies if we've already got an active stream. */
+	if (cpu_dai->active || codec_dai->active) {
+		ret = soc_pcm_apply_symmetry(substream);
+		if (ret != 0)
+			goto config_err;
+	}
+
+	pr_debug("asoc: %s <-> %s info:\n",
+			codec_dai->name, cpu_dai->name);
+	pr_debug("asoc: rate mask 0x%x\n", runtime->hw.rates);
+	pr_debug("asoc: min ch %d max ch %d\n", runtime->hw.channels_min,
+		 runtime->hw.channels_max);
+	pr_debug("asoc: min rate %d max rate %d\n", runtime->hw.rate_min,
+		 runtime->hw.rate_max);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		cpu_dai->playback_active++;
+		codec_dai->playback_active++;
+	} else {
+		cpu_dai->capture_active++;
+		codec_dai->capture_active++;
+	}
+	cpu_dai->active++;
+	codec_dai->active++;
+	rtd->codec->active++;
+	mutex_unlock(&pcm_mutex);
+	return 0;
+
+config_err:
+	if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown)
+		rtd->dai_link->ops->shutdown(substream);
+
+machine_err:
+	if (codec_dai->driver->ops->shutdown)
+		codec_dai->driver->ops->shutdown(substream, codec_dai);
+
+codec_dai_err:
+	if (platform->driver->ops->close)
+		platform->driver->ops->close(substream);
+
+platform_err:
+	if (cpu_dai->driver->ops->shutdown)
+		cpu_dai->driver->ops->shutdown(substream, cpu_dai);
+out:
+	mutex_unlock(&pcm_mutex);
+	return ret;
+}
+
+/*
+ * Power down the audio subsystem pmdown_time msecs after close is called.
+ * This is to ensure there are no pops or clicks in between any music tracks
+ * due to DAPM power cycling.
+ */
+static void close_delayed_work(struct work_struct *work)
+{
+	struct snd_soc_pcm_runtime *rtd =
+			container_of(work, struct snd_soc_pcm_runtime, delayed_work.work);
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+
+	mutex_lock(&pcm_mutex);
+
+	pr_debug("pop wq checking: %s status: %s waiting: %s\n",
+		 codec_dai->driver->playback.stream_name,
+		 codec_dai->playback_active ? "active" : "inactive",
+		 codec_dai->pop_wait ? "yes" : "no");
+
+	/* are we waiting on this codec DAI stream */
+	if (codec_dai->pop_wait == 1) {
+		codec_dai->pop_wait = 0;
+		snd_soc_dapm_stream_event(rtd,
+			codec_dai->driver->playback.stream_name,
+			SND_SOC_DAPM_STREAM_STOP);
+	}
+
+	mutex_unlock(&pcm_mutex);
+}
+
+/*
+ * Called by ALSA when a PCM substream is closed. Private data can be
+ * freed here. The cpu DAI, codec DAI, machine and platform are also
+ * shutdown.
+ */
+static int soc_codec_close(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_platform *platform = rtd->platform;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	mutex_lock(&pcm_mutex);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		cpu_dai->playback_active--;
+		codec_dai->playback_active--;
+	} else {
+		cpu_dai->capture_active--;
+		codec_dai->capture_active--;
+	}
+
+	cpu_dai->active--;
+	codec_dai->active--;
+	codec->active--;
+
+	/* Muting the DAC suppresses artifacts caused during digital
+	 * shutdown, for example from stopping clocks.
+	 */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		snd_soc_dai_digital_mute(codec_dai, 1);
+
+	if (cpu_dai->driver->ops->shutdown)
+		cpu_dai->driver->ops->shutdown(substream, cpu_dai);
+
+	if (codec_dai->driver->ops->shutdown)
+		codec_dai->driver->ops->shutdown(substream, codec_dai);
+
+	if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown)
+		rtd->dai_link->ops->shutdown(substream);
+
+	if (platform->driver->ops->close)
+		platform->driver->ops->close(substream);
+	cpu_dai->runtime = NULL;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		/* start delayed pop wq here for playback streams */
+		codec_dai->pop_wait = 1;
+		schedule_delayed_work(&rtd->delayed_work,
+			msecs_to_jiffies(rtd->pmdown_time));
+	} else {
+		/* capture streams can be powered down now */
+		snd_soc_dapm_stream_event(rtd,
+			codec_dai->driver->capture.stream_name,
+			SND_SOC_DAPM_STREAM_STOP);
+	}
+
+	mutex_unlock(&pcm_mutex);
+	return 0;
+}
+
+/*
+ * Called by ALSA when the PCM substream is prepared, can set format, sample
+ * rate, etc.  This function is non atomic and can be called multiple times,
+ * it can refer to the runtime info.
+ */
+static int soc_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_platform *platform = rtd->platform;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	int ret = 0;
+
+	mutex_lock(&pcm_mutex);
+
+	if (rtd->dai_link->ops && rtd->dai_link->ops->prepare) {
+		ret = rtd->dai_link->ops->prepare(substream);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: machine prepare error\n");
+			goto out;
+		}
+	}
+
+	if (platform->driver->ops->prepare) {
+		ret = platform->driver->ops->prepare(substream);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: platform prepare error\n");
+			goto out;
+		}
+	}
+
+	if (codec_dai->driver->ops->prepare) {
+		ret = codec_dai->driver->ops->prepare(substream, codec_dai);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: codec DAI prepare error\n");
+			goto out;
+		}
+	}
+
+	if (cpu_dai->driver->ops->prepare) {
+		ret = cpu_dai->driver->ops->prepare(substream, cpu_dai);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: cpu DAI prepare error\n");
+			goto out;
+		}
+	}
+
+	/* cancel any delayed stream shutdown that is pending */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+	    codec_dai->pop_wait) {
+		codec_dai->pop_wait = 0;
+		cancel_delayed_work(&rtd->delayed_work);
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		snd_soc_dapm_stream_event(rtd,
+					  codec_dai->driver->playback.stream_name,
+					  SND_SOC_DAPM_STREAM_START);
+	else
+		snd_soc_dapm_stream_event(rtd,
+					  codec_dai->driver->capture.stream_name,
+					  SND_SOC_DAPM_STREAM_START);
+
+	snd_soc_dai_digital_mute(codec_dai, 0);
+
+out:
+	mutex_unlock(&pcm_mutex);
+	return ret;
+}
+
+/*
+ * Called by ALSA when the hardware params are set by application. This
+ * function can also be called multiple times and can allocate buffers
+ * (using snd_pcm_lib_* ). It's non-atomic.
+ */
+static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_platform *platform = rtd->platform;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	int ret = 0;
+
+	mutex_lock(&pcm_mutex);
+
+	if (rtd->dai_link->ops && rtd->dai_link->ops->hw_params) {
+		ret = rtd->dai_link->ops->hw_params(substream, params);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: machine hw_params failed\n");
+			goto out;
+		}
+	}
+
+	if (codec_dai->driver->ops->hw_params) {
+		ret = codec_dai->driver->ops->hw_params(substream, params, codec_dai);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: can't set codec %s hw params\n",
+				codec_dai->name);
+			goto codec_err;
+		}
+	}
+
+	if (cpu_dai->driver->ops->hw_params) {
+		ret = cpu_dai->driver->ops->hw_params(substream, params, cpu_dai);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: interface %s hw params failed\n",
+				cpu_dai->name);
+			goto interface_err;
+		}
+	}
+
+	if (platform->driver->ops->hw_params) {
+		ret = platform->driver->ops->hw_params(substream, params);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: platform %s hw params failed\n",
+				platform->name);
+			goto platform_err;
+		}
+	}
+
+	rtd->rate = params_rate(params);
+
+out:
+	mutex_unlock(&pcm_mutex);
+	return ret;
+
+platform_err:
+	if (cpu_dai->driver->ops->hw_free)
+		cpu_dai->driver->ops->hw_free(substream, cpu_dai);
+
+interface_err:
+	if (codec_dai->driver->ops->hw_free)
+		codec_dai->driver->ops->hw_free(substream, codec_dai);
+
+codec_err:
+	if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free)
+		rtd->dai_link->ops->hw_free(substream);
+
+	mutex_unlock(&pcm_mutex);
+	return ret;
+}
+
+/*
+ * Free's resources allocated by hw_params, can be called multiple times
+ */
+static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_platform *platform = rtd->platform;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_codec *codec = rtd->codec;
+
+	mutex_lock(&pcm_mutex);
+
+	/* apply codec digital mute */
+	if (!codec->active)
+		snd_soc_dai_digital_mute(codec_dai, 1);
+
+	/* free any machine hw params */
+	if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free)
+		rtd->dai_link->ops->hw_free(substream);
+
+	/* free any DMA resources */
+	if (platform->driver->ops->hw_free)
+		platform->driver->ops->hw_free(substream);
+
+	/* now free hw params for the DAI's  */
+	if (codec_dai->driver->ops->hw_free)
+		codec_dai->driver->ops->hw_free(substream, codec_dai);
+
+	if (cpu_dai->driver->ops->hw_free)
+		cpu_dai->driver->ops->hw_free(substream, cpu_dai);
+
+	mutex_unlock(&pcm_mutex);
+	return 0;
+}
+
+static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_platform *platform = rtd->platform;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	int ret;
+
+	if (codec_dai->driver->ops->trigger) {
+		ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (platform->driver->ops->trigger) {
+		ret = platform->driver->ops->trigger(substream, cmd);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (cpu_dai->driver->ops->trigger) {
+		ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+/*
+ * soc level wrapper for pointer callback
+ * If cpu_dai, codec_dai, platform driver has the delay callback, than
+ * the runtime->delay will be updated accordingly.
+ */
+static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_platform *platform = rtd->platform;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_uframes_t offset = 0;
+	snd_pcm_sframes_t delay = 0;
+
+	if (platform->driver->ops->pointer)
+		offset = platform->driver->ops->pointer(substream);
+
+	if (cpu_dai->driver->ops->delay)
+		delay += cpu_dai->driver->ops->delay(substream, cpu_dai);
+
+	if (codec_dai->driver->ops->delay)
+		delay += codec_dai->driver->ops->delay(substream, codec_dai);
+
+	if (platform->driver->delay)
+		delay += platform->driver->delay(substream, codec_dai);
+
+	runtime->delay = delay;
+
+	return offset;
+}
+
+/* ASoC PCM operations */
+static struct snd_pcm_ops soc_pcm_ops = {
+	.open		= soc_pcm_open,
+	.close		= soc_codec_close,
+	.hw_params	= soc_pcm_hw_params,
+	.hw_free	= soc_pcm_hw_free,
+	.prepare	= soc_pcm_prepare,
+	.trigger	= soc_pcm_trigger,
+	.pointer	= soc_pcm_pointer,
+};
+
+#ifdef CONFIG_PM
+/* powers down audio subsystem for suspend */
+static int soc_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	int i;
+
+	/* If the initialization of this soc device failed, there is no codec
+	 * associated with it. Just bail out in this case.
+	 */
+	if (list_empty(&card->codec_dev_list))
+		return 0;
+
+	/* Due to the resume being scheduled into a workqueue we could
+	* suspend before that's finished - wait for it to complete.
+	 */
+	snd_power_lock(card->snd_card);
+	snd_power_wait(card->snd_card, SNDRV_CTL_POWER_D0);
+	snd_power_unlock(card->snd_card);
+
+	/* we're going to block userspace touching us until resume completes */
+	snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D3hot);
+
+	/* mute any active DAC's */
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_dai *dai = card->rtd[i].codec_dai;
+		struct snd_soc_dai_driver *drv = dai->driver;
+
+		if (card->rtd[i].dai_link->ignore_suspend)
+			continue;
+
+		if (drv->ops->digital_mute && dai->playback_active)
+			drv->ops->digital_mute(dai, 1);
+	}
+
+	/* suspend all pcms */
+	for (i = 0; i < card->num_rtd; i++) {
+		if (card->rtd[i].dai_link->ignore_suspend)
+			continue;
+
+		snd_pcm_suspend_all(card->rtd[i].pcm);
+	}
+
+	if (card->suspend_pre)
+		card->suspend_pre(pdev, PMSG_SUSPEND);
+
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
+		struct snd_soc_platform *platform = card->rtd[i].platform;
+
+		if (card->rtd[i].dai_link->ignore_suspend)
+			continue;
+
+		if (cpu_dai->driver->suspend && !cpu_dai->driver->ac97_control)
+			cpu_dai->driver->suspend(cpu_dai);
+		if (platform->driver->suspend && !platform->suspended) {
+			platform->driver->suspend(cpu_dai);
+			platform->suspended = 1;
+		}
+	}
+
+	/* close any waiting streams and save state */
+	for (i = 0; i < card->num_rtd; i++) {
+		run_delayed_work(&card->rtd[i].delayed_work);
+		card->rtd[i].codec->suspend_bias_level = card->rtd[i].codec->bias_level;
+	}
+
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_dai_driver *driver = card->rtd[i].codec_dai->driver;
+
+		if (card->rtd[i].dai_link->ignore_suspend)
+			continue;
+
+		if (driver->playback.stream_name != NULL)
+			snd_soc_dapm_stream_event(&card->rtd[i], driver->playback.stream_name,
+				SND_SOC_DAPM_STREAM_SUSPEND);
+
+		if (driver->capture.stream_name != NULL)
+			snd_soc_dapm_stream_event(&card->rtd[i], driver->capture.stream_name,
+				SND_SOC_DAPM_STREAM_SUSPEND);
+	}
+
+	/* suspend all CODECs */
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_codec *codec = card->rtd[i].codec;
+		/* If there are paths active then the CODEC will be held with
+		 * bias _ON and should not be suspended. */
+		if (!codec->suspended && codec->driver->suspend) {
+			switch (codec->bias_level) {
+			case SND_SOC_BIAS_STANDBY:
+			case SND_SOC_BIAS_OFF:
+				codec->driver->suspend(codec, PMSG_SUSPEND);
+				codec->suspended = 1;
+				break;
+			default:
+				dev_dbg(codec->dev, "CODEC is on over suspend\n");
+				break;
+			}
+		}
+	}
+
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
+
+		if (card->rtd[i].dai_link->ignore_suspend)
+			continue;
+
+		if (cpu_dai->driver->suspend && cpu_dai->driver->ac97_control)
+			cpu_dai->driver->suspend(cpu_dai);
+	}
+
+	if (card->suspend_post)
+		card->suspend_post(pdev, PMSG_SUSPEND);
+
+	return 0;
+}
+
+/* deferred resume work, so resume can complete before we finished
+ * setting our codec back up, which can be very slow on I2C
+ */
+static void soc_resume_deferred(struct work_struct *work)
+{
+	struct snd_soc_card *card =
+			container_of(work, struct snd_soc_card, deferred_resume_work);
+	struct platform_device *pdev = to_platform_device(card->dev);
+	int i;
+
+	/* our power state is still SNDRV_CTL_POWER_D3hot from suspend time,
+	 * so userspace apps are blocked from touching us
+	 */
+
+	dev_dbg(card->dev, "starting resume work\n");
+
+	/* Bring us up into D2 so that DAPM starts enabling things */
+	snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D2);
+
+	if (card->resume_pre)
+		card->resume_pre(pdev);
+
+	/* resume AC97 DAIs */
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
+
+		if (card->rtd[i].dai_link->ignore_suspend)
+			continue;
+
+		if (cpu_dai->driver->resume && cpu_dai->driver->ac97_control)
+			cpu_dai->driver->resume(cpu_dai);
+	}
+
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_codec *codec = card->rtd[i].codec;
+		/* If the CODEC was idle over suspend then it will have been
+		 * left with bias OFF or STANDBY and suspended so we must now
+		 * resume.  Otherwise the suspend was suppressed.
+		 */
+		if (codec->driver->resume && codec->suspended) {
+			switch (codec->bias_level) {
+			case SND_SOC_BIAS_STANDBY:
+			case SND_SOC_BIAS_OFF:
+				codec->driver->resume(codec);
+				codec->suspended = 0;
+				break;
+			default:
+				dev_dbg(codec->dev, "CODEC was on over suspend\n");
+				break;
+			}
+		}
+	}
+
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_dai_driver *driver = card->rtd[i].codec_dai->driver;
+
+		if (card->rtd[i].dai_link->ignore_suspend)
+			continue;
+
+		if (driver->playback.stream_name != NULL)
+			snd_soc_dapm_stream_event(&card->rtd[i], driver->playback.stream_name,
+				SND_SOC_DAPM_STREAM_RESUME);
+
+		if (driver->capture.stream_name != NULL)
+			snd_soc_dapm_stream_event(&card->rtd[i], driver->capture.stream_name,
+				SND_SOC_DAPM_STREAM_RESUME);
+	}
+
+	/* unmute any active DACs */
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_dai *dai = card->rtd[i].codec_dai;
+		struct snd_soc_dai_driver *drv = dai->driver;
+
+		if (card->rtd[i].dai_link->ignore_suspend)
+			continue;
+
+		if (drv->ops->digital_mute && dai->playback_active)
+			drv->ops->digital_mute(dai, 0);
+	}
+
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
+		struct snd_soc_platform *platform = card->rtd[i].platform;
+
+		if (card->rtd[i].dai_link->ignore_suspend)
+			continue;
+
+		if (cpu_dai->driver->resume && !cpu_dai->driver->ac97_control)
+			cpu_dai->driver->resume(cpu_dai);
+		if (platform->driver->resume && platform->suspended) {
+			platform->driver->resume(cpu_dai);
+			platform->suspended = 0;
+		}
+	}
+
+	if (card->resume_post)
+		card->resume_post(pdev);
+
+	dev_dbg(card->dev, "resume work completed\n");
+
+	/* userspace can access us now we are back as we were before */
+	snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D0);
+}
+
+/* powers up audio subsystem after a suspend */
+static int soc_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	int i;
+
+	/* AC97 devices might have other drivers hanging off them so
+	 * need to resume immediately.  Other drivers don't have that
+	 * problem and may take a substantial amount of time to resume
+	 * due to I/O costs and anti-pop so handle them out of line.
+	 */
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
+		if (cpu_dai->driver->ac97_control) {
+			dev_dbg(dev, "Resuming AC97 immediately\n");
+			soc_resume_deferred(&card->deferred_resume_work);
+		} else {
+			dev_dbg(dev, "Scheduling resume work\n");
+			if (!schedule_work(&card->deferred_resume_work))
+				dev_err(dev, "resume work item may be lost\n");
+		}
+	}
+
+	return 0;
+}
+#else
+#define soc_suspend	NULL
+#define soc_resume	NULL
+#endif
+
+static struct snd_soc_dai_ops null_dai_ops = {
+};
+
+static int soc_bind_dai_link(struct snd_soc_card *card, int num)
+{
+	struct snd_soc_dai_link *dai_link = &card->dai_link[num];
+	struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
+	struct snd_soc_codec *codec;
+	struct snd_soc_platform *platform;
+	struct snd_soc_dai *codec_dai, *cpu_dai;
+
+	if (rtd->complete)
+		return 1;
+	dev_dbg(card->dev, "binding %s at idx %d\n", dai_link->name, num);
+
+	/* do we already have the CPU DAI for this link ? */
+	if (rtd->cpu_dai) {
+		goto find_codec;
+	}
+	/* no, then find CPU DAI from registered DAIs*/
+	list_for_each_entry(cpu_dai, &dai_list, list) {
+		if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) {
+
+			if (!try_module_get(cpu_dai->dev->driver->owner))
+				return -ENODEV;
+
+			rtd->cpu_dai = cpu_dai;
+			goto find_codec;
+		}
+	}
+	dev_dbg(card->dev, "CPU DAI %s not registered\n",
+			dai_link->cpu_dai_name);
+
+find_codec:
+	/* do we already have the CODEC for this link ? */
+	if (rtd->codec) {
+		goto find_platform;
+	}
+
+	/* no, then find CODEC from registered CODECs*/
+	list_for_each_entry(codec, &codec_list, list) {
+		if (!strcmp(codec->name, dai_link->codec_name)) {
+			rtd->codec = codec;
+
+			if (!try_module_get(codec->dev->driver->owner))
+				return -ENODEV;
+
+			/* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
+			list_for_each_entry(codec_dai, &dai_list, list) {
+				if (codec->dev == codec_dai->dev &&
+						!strcmp(codec_dai->name, dai_link->codec_dai_name)) {
+					rtd->codec_dai = codec_dai;
+					goto find_platform;
+				}
+			}
+			dev_dbg(card->dev, "CODEC DAI %s not registered\n",
+					dai_link->codec_dai_name);
+
+			goto find_platform;
+		}
+	}
+	dev_dbg(card->dev, "CODEC %s not registered\n",
+			dai_link->codec_name);
+
+find_platform:
+	/* do we already have the CODEC DAI for this link ? */
+	if (rtd->platform) {
+		goto out;
+	}
+	/* no, then find CPU DAI from registered DAIs*/
+	list_for_each_entry(platform, &platform_list, list) {
+		if (!strcmp(platform->name, dai_link->platform_name)) {
+
+			if (!try_module_get(platform->dev->driver->owner))
+				return -ENODEV;
+
+			rtd->platform = platform;
+			goto out;
+		}
+	}
+
+	dev_dbg(card->dev, "platform %s not registered\n",
+			dai_link->platform_name);
+	return 0;
+
+out:
+	/* mark rtd as complete if we found all 4 of our client devices */
+	if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) {
+		rtd->complete = 1;
+		card->num_rtd++;
+	}
+	return 1;
+}
+
+static void soc_remove_dai_link(struct snd_soc_card *card, int num)
+{
+	struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
+	struct snd_soc_codec *codec = rtd->codec;
+	struct snd_soc_platform *platform = rtd->platform;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;
+	int err;
+
+	/* unregister the rtd device */
+	if (rtd->dev_registered) {
+		device_remove_file(&rtd->dev, &dev_attr_pmdown_time);
+		device_unregister(&rtd->dev);
+		rtd->dev_registered = 0;
+	}
+
+	/* remove the CODEC DAI */
+	if (codec_dai && codec_dai->probed) {
+		if (codec_dai->driver->remove) {
+			err = codec_dai->driver->remove(codec_dai);
+			if (err < 0)
+				printk(KERN_ERR "asoc: failed to remove %s\n", codec_dai->name);
+		}
+		codec_dai->probed = 0;
+		list_del(&codec_dai->card_list);
+	}
+
+	/* remove the platform */
+	if (platform && platform->probed) {
+		if (platform->driver->remove) {
+			err = platform->driver->remove(platform);
+			if (err < 0)
+				printk(KERN_ERR "asoc: failed to remove %s\n", platform->name);
+		}
+		platform->probed = 0;
+		list_del(&platform->card_list);
+		module_put(platform->dev->driver->owner);
+	}
+
+	/* remove the CODEC */
+	if (codec && codec->probed) {
+		if (codec->driver->remove) {
+			err = codec->driver->remove(codec);
+			if (err < 0)
+				printk(KERN_ERR "asoc: failed to remove %s\n", codec->name);
+		}
+
+		/* Make sure all DAPM widgets are freed */
+		snd_soc_dapm_free(codec);
+
+		soc_cleanup_codec_debugfs(codec);
+		device_remove_file(&rtd->dev, &dev_attr_codec_reg);
+		codec->probed = 0;
+		list_del(&codec->card_list);
+		module_put(codec->dev->driver->owner);
+	}
+
+	/* remove the cpu_dai */
+	if (cpu_dai && cpu_dai->probed) {
+		if (cpu_dai->driver->remove) {
+			err = cpu_dai->driver->remove(cpu_dai);
+			if (err < 0)
+				printk(KERN_ERR "asoc: failed to remove %s\n", cpu_dai->name);
+		}
+		cpu_dai->probed = 0;
+		list_del(&cpu_dai->card_list);
+		module_put(cpu_dai->dev->driver->owner);
+	}
+}
+
+static void rtd_release(struct device *dev) {}
+
+static int soc_probe_dai_link(struct snd_soc_card *card, int num)
+{
+	struct snd_soc_dai_link *dai_link = &card->dai_link[num];
+	struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
+	struct snd_soc_codec *codec = rtd->codec;
+	struct snd_soc_platform *platform = rtd->platform;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	dev_dbg(card->dev, "probe %s dai link %d\n", card->name, num);
+
+	/* config components */
+	codec_dai->codec = codec;
+	codec->card = card;
+	cpu_dai->platform = platform;
+	rtd->card = card;
+	rtd->dev.parent = card->dev;
+	codec_dai->card = card;
+	cpu_dai->card = card;
+
+	/* set default power off timeout */
+	rtd->pmdown_time = pmdown_time;
+
+	/* probe the cpu_dai */
+	if (!cpu_dai->probed) {
+		if (cpu_dai->driver->probe) {
+			ret = cpu_dai->driver->probe(cpu_dai);
+			if (ret < 0) {
+				printk(KERN_ERR "asoc: failed to probe CPU DAI %s\n",
+						cpu_dai->name);
+				return ret;
+			}
+		}
+		cpu_dai->probed = 1;
+		/* mark cpu_dai as probed and add to card cpu_dai list */
+		list_add(&cpu_dai->card_list, &card->dai_dev_list);
+	}
+
+	/* probe the CODEC */
+	if (!codec->probed) {
+		if (codec->driver->probe) {
+			ret = codec->driver->probe(codec);
+			if (ret < 0) {
+				printk(KERN_ERR "asoc: failed to probe CODEC %s\n",
+						codec->name);
+				return ret;
+			}
+		}
+
+		soc_init_codec_debugfs(codec);
+
+		/* mark codec as probed and add to card codec list */
+		codec->probed = 1;
+		list_add(&codec->card_list, &card->codec_dev_list);
+	}
+
+	/* probe the platform */
+	if (!platform->probed) {
+		if (platform->driver->probe) {
+			ret = platform->driver->probe(platform);
+			if (ret < 0) {
+				printk(KERN_ERR "asoc: failed to probe platform %s\n",
+						platform->name);
+				return ret;
+			}
+		}
+		/* mark platform as probed and add to card platform list */
+		platform->probed = 1;
+		list_add(&platform->card_list, &card->platform_dev_list);
+	}
+
+	/* probe the CODEC DAI */
+	if (!codec_dai->probed) {
+		if (codec_dai->driver->probe) {
+			ret = codec_dai->driver->probe(codec_dai);
+			if (ret < 0) {
+				printk(KERN_ERR "asoc: failed to probe CODEC DAI %s\n",
+						codec_dai->name);
+				return ret;
+			}
+		}
+
+		/* mark cpu_dai as probed and add to card cpu_dai list */
+		codec_dai->probed = 1;
+		list_add(&codec_dai->card_list, &card->dai_dev_list);
+	}
+
+	/* DAPM dai link stream work */
+	INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
+
+	/* now that all clients have probed, initialise the DAI link */
+	if (dai_link->init) {
+		ret = dai_link->init(rtd);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: failed to init %s\n", dai_link->stream_name);
+			return ret;
+		}
+	}
+
+	/* Make sure all DAPM widgets are instantiated */
+	snd_soc_dapm_new_widgets(codec);
+	snd_soc_dapm_sync(codec);
+
+	/* register the rtd device */
+	rtd->dev.release = rtd_release;
+	rtd->dev.init_name = dai_link->name;
+	ret = device_register(&rtd->dev);
+	if (ret < 0) {
+		printk(KERN_ERR "asoc: failed to register DAI runtime device %d\n", ret);
+		return ret;
+	}
+
+	rtd->dev_registered = 1;
+	ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time);
+	if (ret < 0)
+		printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n");
+
+	/* add DAPM sysfs entries for this codec */
+	ret = snd_soc_dapm_sys_add(&rtd->dev);
+	if (ret < 0)
+		printk(KERN_WARNING "asoc: failed to add codec dapm sysfs entries\n");
+
+	/* add codec sysfs entries */
+	ret = device_create_file(&rtd->dev, &dev_attr_codec_reg);
+	if (ret < 0)
+		printk(KERN_WARNING "asoc: failed to add codec sysfs files\n");
+
+	/* create the pcm */
+	ret = soc_new_pcm(rtd, num);
+	if (ret < 0) {
+		printk(KERN_ERR "asoc: can't create pcm %s\n", dai_link->stream_name);
+		return ret;
+	}
+
+	/* add platform data for AC97 devices */
+	if (rtd->codec_dai->driver->ac97_control)
+		snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);
+
+	return 0;
+}
+
+#ifdef CONFIG_SND_SOC_AC97_BUS
+static int soc_register_ac97_dai_link(struct snd_soc_pcm_runtime *rtd)
+{
+	int ret;
+
+	/* Only instantiate AC97 if not already done by the adaptor
+	 * for the generic AC97 subsystem.
+	 */
+	if (rtd->codec_dai->driver->ac97_control && !rtd->codec->ac97_registered) {
+		/*
+		 * It is possible that the AC97 device is already registered to
+		 * the device subsystem. This happens when the device is created
+		 * via snd_ac97_mixer(). Currently only SoC codec that does so
+		 * is the generic AC97 glue but others migh emerge.
+		 *
+		 * In those cases we don't try to register the device again.
+		 */
+		if (!rtd->codec->ac97_created)
+			return 0;
+
+		ret = soc_ac97_dev_register(rtd->codec);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: AC97 device register failed\n");
+			return ret;
+		}
+
+		rtd->codec->ac97_registered = 1;
+	}
+	return 0;
+}
+
+static void soc_unregister_ac97_dai_link(struct snd_soc_codec *codec)
+{
+	if (codec->ac97_registered) {
+		soc_ac97_dev_unregister(codec);
+		codec->ac97_registered = 0;
+	}
+}
+#endif
+
+static void snd_soc_instantiate_card(struct snd_soc_card *card)
+{
+	struct platform_device *pdev = to_platform_device(card->dev);
+	int ret, i;
+
+	mutex_lock(&card->mutex);
+
+	if (card->instantiated) {
+		mutex_unlock(&card->mutex);
+		return;
+	}
+
+	/* bind DAIs */
+	for (i = 0; i < card->num_links; i++)
+		soc_bind_dai_link(card, i);
+
+	/* bind completed ? */
+	if (card->num_rtd != card->num_links) {
+		mutex_unlock(&card->mutex);
+		return;
+	}
+
+	/* card bind complete so register a sound card */
+	ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+			card->owner, 0, &card->snd_card);
+	if (ret < 0) {
+		printk(KERN_ERR "asoc: can't create sound card for card %s\n",
+			card->name);
+		mutex_unlock(&card->mutex);
+		return;
+	}
+	card->snd_card->dev = card->dev;
+
+#ifdef CONFIG_PM
+	/* deferred resume work */
+	INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
+#endif
+
+	/* initialise the sound card only once */
+	if (card->probe) {
+		ret = card->probe(pdev);
+		if (ret < 0)
+			goto card_probe_error;
+	}
+
+	for (i = 0; i < card->num_links; i++) {
+		ret = soc_probe_dai_link(card, i);
+		if (ret < 0) {
+			pr_err("asoc: failed to instantiate card %s: %d\n",
+			       card->name, ret);
+			goto probe_dai_err;
+		}
+	}
+
+	snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
+		 "%s",  card->name);
+	snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
+		 "%s", card->name);
+
+	ret = snd_card_register(card->snd_card);
+	if (ret < 0) {
+		printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
+		goto probe_dai_err;
+	}
+
+#ifdef CONFIG_SND_SOC_AC97_BUS
+	/* register any AC97 codecs */
+	for (i = 0; i < card->num_rtd; i++) {
+		ret = soc_register_ac97_dai_link(&card->rtd[i]);
+		if (ret < 0) {
+			printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name);
+			while (--i >= 0)
+				soc_unregister_ac97_dai_link(&card->rtd[i]);
+			goto probe_dai_err;
+		}
+	}
+#endif
+
+	card->instantiated = 1;
+	mutex_unlock(&card->mutex);
+	return;
+
+probe_dai_err:
+	for (i = 0; i < card->num_links; i++)
+		soc_remove_dai_link(card, i);
+
+card_probe_error:
+	if (card->remove)
+		card->remove(pdev);
+
+	snd_card_free(card->snd_card);
+
+	mutex_unlock(&card->mutex);
+}
+
+/*
+ * Attempt to initialise any uninitialised cards.  Must be called with
+ * client_mutex.
+ */
+static void snd_soc_instantiate_cards(void)
+{
+	struct snd_soc_card *card;
+	list_for_each_entry(card, &card_list, list)
+		snd_soc_instantiate_card(card);
+}
+
+/* probes a new socdev */
+static int soc_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	int ret = 0;
+
+	/* Bodge while we unpick instantiation */
+	card->dev = &pdev->dev;
+	INIT_LIST_HEAD(&card->dai_dev_list);
+	INIT_LIST_HEAD(&card->codec_dev_list);
+	INIT_LIST_HEAD(&card->platform_dev_list);
+
+	ret = snd_soc_register_card(card);
+	if (ret != 0) {
+		dev_err(&pdev->dev, "Failed to register card\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/* removes a socdev */
+static int soc_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	int i;
+
+		if (card->instantiated) {
+
+		/* make sure any delayed work runs */
+		for (i = 0; i < card->num_rtd; i++) {
+			struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
+			run_delayed_work(&rtd->delayed_work);
+		}
+
+		/* remove and free each DAI */
+		for (i = 0; i < card->num_rtd; i++)
+			soc_remove_dai_link(card, i);
+
+		/* remove the card */
+		if (card->remove)
+			card->remove(pdev);
+
+		kfree(card->rtd);
+		snd_card_free(card->snd_card);
+	}
+	snd_soc_unregister_card(card);
+	return 0;
+}
+
+static int soc_poweroff(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	int i;
+
+	if (!card->instantiated)
+		return 0;
+
+	/* Flush out pmdown_time work - we actually do want to run it
+	 * now, we're shutting down so no imminent restart. */
+	for (i = 0; i < card->num_rtd; i++) {
+		struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
+		run_delayed_work(&rtd->delayed_work);
+	}
+
+	snd_soc_dapm_shutdown(card);
+
+	return 0;
+}
+
+static const struct dev_pm_ops soc_pm_ops = {
+	.suspend = soc_suspend,
+	.resume = soc_resume,
+	.poweroff = soc_poweroff,
+};
+
+/* ASoC platform driver */
+static struct platform_driver soc_driver = {
+	.driver		= {
+		.name		= "soc-audio",
+		.owner		= THIS_MODULE,
+		.pm		= &soc_pm_ops,
+	},
+	.probe		= soc_probe,
+	.remove		= soc_remove,
+};
+
+/* create a new pcm */
+static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	struct snd_soc_platform *platform = rtd->platform;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_pcm *pcm;
+	char new_name[64];
+	int ret = 0, playback = 0, capture = 0;
+
+	/* check client and interface hw capabilities */
+	snprintf(new_name, sizeof(new_name), "%s %s-%d",
+			rtd->dai_link->stream_name, codec_dai->name, num);
+
+	if (codec_dai->driver->playback.channels_min)
+		playback = 1;
+	if (codec_dai->driver->capture.channels_min)
+		capture = 1;
+
+	dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name);
+	ret = snd_pcm_new(rtd->card->snd_card, new_name,
+			num, playback, capture, &pcm);
+	if (ret < 0) {
+		printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name);
+		return ret;
+	}
+
+	rtd->pcm = pcm;
+	pcm->private_data = rtd;
+	soc_pcm_ops.mmap = platform->driver->ops->mmap;
+	soc_pcm_ops.pointer = platform->driver->ops->pointer;
+	soc_pcm_ops.ioctl = platform->driver->ops->ioctl;
+	soc_pcm_ops.copy = platform->driver->ops->copy;
+	soc_pcm_ops.silence = platform->driver->ops->silence;
+	soc_pcm_ops.ack = platform->driver->ops->ack;
+	soc_pcm_ops.page = platform->driver->ops->page;
+
+	if (playback)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);
+
+	if (capture)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);
+
+	ret = platform->driver->pcm_new(rtd->card->snd_card, codec_dai, pcm);
+	if (ret < 0) {
+		printk(KERN_ERR "asoc: platform pcm constructor failed\n");
+		return ret;
+	}
+
+	pcm->private_free = platform->driver->pcm_free;
+	printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,
+		cpu_dai->name);
+	return ret;
+}
+
+/**
+ * snd_soc_codec_volatile_register: Report if a register is volatile.
+ *
+ * @codec: CODEC to query.
+ * @reg: Register to query.
+ *
+ * Boolean function indiciating if a CODEC register is volatile.
+ */
+int snd_soc_codec_volatile_register(struct snd_soc_codec *codec, int reg)
+{
+	if (codec->driver->volatile_register)
+		return codec->driver->volatile_register(reg);
+	else
+		return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_volatile_register);
+
+/**
+ * snd_soc_new_ac97_codec - initailise AC97 device
+ * @codec: audio codec
+ * @ops: AC97 bus operations
+ * @num: AC97 codec number
+ *
+ * Initialises AC97 codec resources for use by ad-hoc devices only.
+ */
+int snd_soc_new_ac97_codec(struct snd_soc_codec *codec,
+	struct snd_ac97_bus_ops *ops, int num)
+{
+	mutex_lock(&codec->mutex);
+
+	codec->ac97 = kzalloc(sizeof(struct snd_ac97), GFP_KERNEL);
+	if (codec->ac97 == NULL) {
+		mutex_unlock(&codec->mutex);
+		return -ENOMEM;
+	}
+
+	codec->ac97->bus = kzalloc(sizeof(struct snd_ac97_bus), GFP_KERNEL);
+	if (codec->ac97->bus == NULL) {
+		kfree(codec->ac97);
+		codec->ac97 = NULL;
+		mutex_unlock(&codec->mutex);
+		return -ENOMEM;
+	}
+
+	codec->ac97->bus->ops = ops;
+	codec->ac97->num = num;
+
+	/*
+	 * Mark the AC97 device to be created by us. This way we ensure that the
+	 * device will be registered with the device subsystem later on.
+	 */
+	codec->ac97_created = 1;
+
+	mutex_unlock(&codec->mutex);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_new_ac97_codec);
+
+/**
+ * snd_soc_free_ac97_codec - free AC97 codec device
+ * @codec: audio codec
+ *
+ * Frees AC97 codec device resources.
+ */
+void snd_soc_free_ac97_codec(struct snd_soc_codec *codec)
+{
+	mutex_lock(&codec->mutex);
+#ifdef CONFIG_SND_SOC_AC97_BUS
+	soc_unregister_ac97_dai_link(codec);
+#endif
+	kfree(codec->ac97->bus);
+	kfree(codec->ac97);
+	codec->ac97 = NULL;
+	codec->ac97_created = 0;
+	mutex_unlock(&codec->mutex);
+}
+EXPORT_SYMBOL_GPL(snd_soc_free_ac97_codec);
+
+/**
+ * snd_soc_update_bits - update codec register bits
+ * @codec: audio codec
+ * @reg: codec register
+ * @mask: register mask
+ * @value: new value
+ *
+ * Writes new register value.
+ *
+ * Returns 1 for change else 0.
+ */
+int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
+				unsigned int mask, unsigned int value)
+{
+	int change;
+	unsigned int old, new;
+
+	old = snd_soc_read(codec, reg);
+	new = (old & ~mask) | value;
+	change = old != new;
+	if (change)
+		snd_soc_write(codec, reg, new);
+
+	return change;
+}
+EXPORT_SYMBOL_GPL(snd_soc_update_bits);
+
+/**
+ * snd_soc_update_bits_locked - update codec register bits
+ * @codec: audio codec
+ * @reg: codec register
+ * @mask: register mask
+ * @value: new value
+ *
+ * Writes new register value, and takes the codec mutex.
+ *
+ * Returns 1 for change else 0.
+ */
+int snd_soc_update_bits_locked(struct snd_soc_codec *codec,
+			       unsigned short reg, unsigned int mask,
+			       unsigned int value)
+{
+	int change;
+
+	mutex_lock(&codec->mutex);
+	change = snd_soc_update_bits(codec, reg, mask, value);
+	mutex_unlock(&codec->mutex);
+
+	return change;
+}
+EXPORT_SYMBOL_GPL(snd_soc_update_bits_locked);
+
+/**
+ * snd_soc_test_bits - test register for change
+ * @codec: audio codec
+ * @reg: codec register
+ * @mask: register mask
+ * @value: new value
+ *
+ * Tests a register with a new value and checks if the new value is
+ * different from the old value.
+ *
+ * Returns 1 for change else 0.
+ */
+int snd_soc_test_bits(struct snd_soc_codec *codec, unsigned short reg,
+				unsigned int mask, unsigned int value)
+{
+	int change;
+	unsigned int old, new;
+
+	old = snd_soc_read(codec, reg);
+	new = (old & ~mask) | value;
+	change = old != new;
+
+	return change;
+}
+EXPORT_SYMBOL_GPL(snd_soc_test_bits);
+
+/**
+ * snd_soc_set_runtime_hwparams - set the runtime hardware parameters
+ * @substream: the pcm substream
+ * @hw: the hardware parameters
+ *
+ * Sets the substream runtime hardware parameters.
+ */
+int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,
+	const struct snd_pcm_hardware *hw)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	runtime->hw.info = hw->info;
+	runtime->hw.formats = hw->formats;
+	runtime->hw.period_bytes_min = hw->period_bytes_min;
+	runtime->hw.period_bytes_max = hw->period_bytes_max;
+	runtime->hw.periods_min = hw->periods_min;
+	runtime->hw.periods_max = hw->periods_max;
+	runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;
+	runtime->hw.fifo_size = hw->fifo_size;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_set_runtime_hwparams);
+
+/**
+ * snd_soc_cnew - create new control
+ * @_template: control template
+ * @data: control private data
+ * @long_name: control long name
+ *
+ * Create a new mixer control from a template control.
+ *
+ * Returns 0 for success, else error.
+ */
+struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
+	void *data, char *long_name)
+{
+	struct snd_kcontrol_new template;
+
+	memcpy(&template, _template, sizeof(template));
+	if (long_name)
+		template.name = long_name;
+	template.index = 0;
+
+	return snd_ctl_new1(&template, data);
+}
+EXPORT_SYMBOL_GPL(snd_soc_cnew);
+
+/**
+ * snd_soc_add_controls - add an array of controls to a codec.
+ * Convienience function to add a list of controls. Many codecs were
+ * duplicating this code.
+ *
+ * @codec: codec to add controls to
+ * @controls: array of controls to add
+ * @num_controls: number of elements in the array
+ *
+ * Return 0 for success, else error.
+ */
+int snd_soc_add_controls(struct snd_soc_codec *codec,
+	const struct snd_kcontrol_new *controls, int num_controls)
+{
+	struct snd_card *card = codec->card->snd_card;
+	int err, i;
+
+	for (i = 0; i < num_controls; i++) {
+		const struct snd_kcontrol_new *control = &controls[i];
+		err = snd_ctl_add(card, snd_soc_cnew(control, codec, NULL));
+		if (err < 0) {
+			dev_err(codec->dev, "%s: Failed to add %s: %d\n",
+				codec->name, control->name, err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_add_controls);
+
+/**
+ * snd_soc_info_enum_double - enumerated double mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a double enumerated
+ * mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = e->shift_l == e->shift_r ? 1 : 2;
+	uinfo->value.enumerated.items = e->max;
+
+	if (uinfo->value.enumerated.item > e->max - 1)
+		uinfo->value.enumerated.item = e->max - 1;
+	strcpy(uinfo->value.enumerated.name,
+		e->texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_enum_double);
+
+/**
+ * snd_soc_get_enum_double - enumerated double mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a double enumerated mixer.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned int val, bitmask;
+
+	for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+		;
+	val = snd_soc_read(codec, e->reg);
+	ucontrol->value.enumerated.item[0]
+		= (val >> e->shift_l) & (bitmask - 1);
+	if (e->shift_l != e->shift_r)
+		ucontrol->value.enumerated.item[1] =
+			(val >> e->shift_r) & (bitmask - 1);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_enum_double);
+
+/**
+ * snd_soc_put_enum_double - enumerated double mixer put callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a double enumerated mixer.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned int val;
+	unsigned int mask, bitmask;
+
+	for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+		;
+	if (ucontrol->value.enumerated.item[0] > e->max - 1)
+		return -EINVAL;
+	val = ucontrol->value.enumerated.item[0] << e->shift_l;
+	mask = (bitmask - 1) << e->shift_l;
+	if (e->shift_l != e->shift_r) {
+		if (ucontrol->value.enumerated.item[1] > e->max - 1)
+			return -EINVAL;
+		val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+		mask |= (bitmask - 1) << e->shift_r;
+	}
+
+	return snd_soc_update_bits_locked(codec, e->reg, mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_enum_double);
+
+/**
+ * snd_soc_get_value_enum_double - semi enumerated double mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a double semi enumerated mixer.
+ *
+ * Semi enumerated mixer: the enumerated items are referred as values. Can be
+ * used for handling bitfield coded enumeration for example.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_value_enum_double(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned int reg_val, val, mux;
+
+	reg_val = snd_soc_read(codec, e->reg);
+	val = (reg_val >> e->shift_l) & e->mask;
+	for (mux = 0; mux < e->max; mux++) {
+		if (val == e->values[mux])
+			break;
+	}
+	ucontrol->value.enumerated.item[0] = mux;
+	if (e->shift_l != e->shift_r) {
+		val = (reg_val >> e->shift_r) & e->mask;
+		for (mux = 0; mux < e->max; mux++) {
+			if (val == e->values[mux])
+				break;
+		}
+		ucontrol->value.enumerated.item[1] = mux;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_value_enum_double);
+
+/**
+ * snd_soc_put_value_enum_double - semi enumerated double mixer put callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a double semi enumerated mixer.
+ *
+ * Semi enumerated mixer: the enumerated items are referred as values. Can be
+ * used for handling bitfield coded enumeration for example.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_value_enum_double(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned int val;
+	unsigned int mask;
+
+	if (ucontrol->value.enumerated.item[0] > e->max - 1)
+		return -EINVAL;
+	val = e->values[ucontrol->value.enumerated.item[0]] << e->shift_l;
+	mask = e->mask << e->shift_l;
+	if (e->shift_l != e->shift_r) {
+		if (ucontrol->value.enumerated.item[1] > e->max - 1)
+			return -EINVAL;
+		val |= e->values[ucontrol->value.enumerated.item[1]] << e->shift_r;
+		mask |= e->mask << e->shift_r;
+	}
+
+	return snd_soc_update_bits_locked(codec, e->reg, mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_value_enum_double);
+
+/**
+ * snd_soc_info_enum_ext - external enumerated single mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about an external enumerated
+ * single mixer.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_enum_ext(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = e->max;
+
+	if (uinfo->value.enumerated.item > e->max - 1)
+		uinfo->value.enumerated.item = e->max - 1;
+	strcpy(uinfo->value.enumerated.name,
+		e->texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_enum_ext);
+
+/**
+ * snd_soc_info_volsw_ext - external single mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a single external mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_ext(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	int max = kcontrol->private_value;
+
+	if (max == 1 && !strstr(kcontrol->id.name, " Volume"))
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	else
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = max;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext);
+
+/**
+ * snd_soc_info_volsw - single mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a single mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	int platform_max;
+	unsigned int shift = mc->shift;
+	unsigned int rshift = mc->rshift;
+
+	if (!mc->platform_max)
+		mc->platform_max = mc->max;
+	platform_max = mc->platform_max;
+
+	if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume"))
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	else
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+	uinfo->count = shift == rshift ? 1 : 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = platform_max;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw);
+
+/**
+ * snd_soc_get_volsw - single mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a single mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int shift = mc->shift;
+	unsigned int rshift = mc->rshift;
+	int max = mc->max;
+	unsigned int mask = (1 << fls(max)) - 1;
+	unsigned int invert = mc->invert;
+
+	ucontrol->value.integer.value[0] =
+		(snd_soc_read(codec, reg) >> shift) & mask;
+	if (shift != rshift)
+		ucontrol->value.integer.value[1] =
+			(snd_soc_read(codec, reg) >> rshift) & mask;
+	if (invert) {
+		ucontrol->value.integer.value[0] =
+			max - ucontrol->value.integer.value[0];
+		if (shift != rshift)
+			ucontrol->value.integer.value[1] =
+				max - ucontrol->value.integer.value[1];
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw);
+
+/**
+ * snd_soc_put_volsw - single mixer put callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a single mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int shift = mc->shift;
+	unsigned int rshift = mc->rshift;
+	int max = mc->max;
+	unsigned int mask = (1 << fls(max)) - 1;
+	unsigned int invert = mc->invert;
+	unsigned int val, val2, val_mask;
+
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = max - val;
+	val_mask = mask << shift;
+	val = val << shift;
+	if (shift != rshift) {
+		val2 = (ucontrol->value.integer.value[1] & mask);
+		if (invert)
+			val2 = max - val2;
+		val_mask |= mask << rshift;
+		val |= val2 << rshift;
+	}
+	return snd_soc_update_bits_locked(codec, reg, val_mask, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw);
+
+/**
+ * snd_soc_info_volsw_2r - double mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a double mixer control that
+ * spans 2 codec registers.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	int platform_max;
+
+	if (!mc->platform_max)
+		mc->platform_max = mc->max;
+	platform_max = mc->platform_max;
+
+	if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume"))
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	else
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = platform_max;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r);
+
+/**
+ * snd_soc_get_volsw_2r - double mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a double mixer control that spans 2 registers.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int reg2 = mc->rreg;
+	unsigned int shift = mc->shift;
+	int max = mc->max;
+	unsigned int mask = (1 << fls(max)) - 1;
+	unsigned int invert = mc->invert;
+
+	ucontrol->value.integer.value[0] =
+		(snd_soc_read(codec, reg) >> shift) & mask;
+	ucontrol->value.integer.value[1] =
+		(snd_soc_read(codec, reg2) >> shift) & mask;
+	if (invert) {
+		ucontrol->value.integer.value[0] =
+			max - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] =
+			max - ucontrol->value.integer.value[1];
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r);
+
+/**
+ * snd_soc_put_volsw_2r - double mixer set callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a double mixer control that spans 2 registers.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	unsigned int reg2 = mc->rreg;
+	unsigned int shift = mc->shift;
+	int max = mc->max;
+	unsigned int mask = (1 << fls(max)) - 1;
+	unsigned int invert = mc->invert;
+	int err;
+	unsigned int val, val2, val_mask;
+
+	val_mask = mask << shift;
+	val = (ucontrol->value.integer.value[0] & mask);
+	val2 = (ucontrol->value.integer.value[1] & mask);
+
+	if (invert) {
+		val = max - val;
+		val2 = max - val2;
+	}
+
+	val = val << shift;
+	val2 = val2 << shift;
+
+	err = snd_soc_update_bits_locked(codec, reg, val_mask, val);
+	if (err < 0)
+		return err;
+
+	err = snd_soc_update_bits_locked(codec, reg2, val_mask, val2);
+	return err;
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r);
+
+/**
+ * snd_soc_info_volsw_s8 - signed mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a signed mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	int platform_max;
+	int min = mc->min;
+
+	if (!mc->platform_max)
+		mc->platform_max = mc->max;
+	platform_max = mc->platform_max;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = platform_max - min;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_s8);
+
+/**
+ * snd_soc_get_volsw_s8 - signed mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a signed mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	int min = mc->min;
+	int val = snd_soc_read(codec, reg);
+
+	ucontrol->value.integer.value[0] =
+		((signed char)(val & 0xff))-min;
+	ucontrol->value.integer.value[1] =
+		((signed char)((val >> 8) & 0xff))-min;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw_s8);
+
+/**
+ * snd_soc_put_volsw_sgn - signed mixer put callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a signed mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int reg = mc->reg;
+	int min = mc->min;
+	unsigned int val;
+
+	val = (ucontrol->value.integer.value[0]+min) & 0xff;
+	val |= ((ucontrol->value.integer.value[1]+min) & 0xff) << 8;
+
+	return snd_soc_update_bits_locked(codec, reg, 0xffff, val);
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw_s8);
+
+/**
+ * snd_soc_limit_volume - Set new limit to an existing volume control.
+ *
+ * @codec: where to look for the control
+ * @name: Name of the control
+ * @max: new maximum limit
+ *
+ * Return 0 for success, else error.
+ */
+int snd_soc_limit_volume(struct snd_soc_codec *codec,
+	const char *name, int max)
+{
+	struct snd_card *card = codec->card->snd_card;
+	struct snd_kcontrol *kctl;
+	struct soc_mixer_control *mc;
+	int found = 0;
+	int ret = -EINVAL;
+
+	/* Sanity check for name and max */
+	if (unlikely(!name || max <= 0))
+		return -EINVAL;
+
+	list_for_each_entry(kctl, &card->controls, list) {
+		if (!strncmp(kctl->id.name, name, sizeof(kctl->id.name))) {
+			found = 1;
+			break;
+		}
+	}
+	if (found) {
+		mc = (struct soc_mixer_control *)kctl->private_value;
+		if (max <= mc->max) {
+			mc->platform_max = max;
+			ret = 0;
+		}
+	}
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_limit_volume);
+
+/**
+ * snd_soc_info_volsw_2r_sx - double with tlv and variable data size
+ *  mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_info *uinfo)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	int max = mc->max;
+	int min = mc->min;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = max-min;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r_sx);
+
+/**
+ * snd_soc_get_volsw_2r_sx - double with tlv and variable data size
+ *  mixer get callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int mask = (1<<mc->shift)-1;
+	int min = mc->min;
+	int val = snd_soc_read(codec, mc->reg) & mask;
+	int valr = snd_soc_read(codec, mc->rreg) & mask;
+
+	ucontrol->value.integer.value[0] = ((val & 0xff)-min) & mask;
+	ucontrol->value.integer.value[1] = ((valr & 0xff)-min) & mask;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx);
+
+/**
+ * snd_soc_put_volsw_2r_sx - double with tlv and variable data size
+ *  mixer put callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned int mask = (1<<mc->shift)-1;
+	int min = mc->min;
+	int ret;
+	unsigned int val, valr, oval, ovalr;
+
+	val = ((ucontrol->value.integer.value[0]+min) & 0xff);
+	val &= mask;
+	valr = ((ucontrol->value.integer.value[1]+min) & 0xff);
+	valr &= mask;
+
+	oval = snd_soc_read(codec, mc->reg) & mask;
+	ovalr = snd_soc_read(codec, mc->rreg) & mask;
+
+	ret = 0;
+	if (oval != val) {
+		ret = snd_soc_write(codec, mc->reg, val);
+		if (ret < 0)
+			return ret;
+	}
+	if (ovalr != valr) {
+		ret = snd_soc_write(codec, mc->rreg, valr);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r_sx);
+
+/**
+ * snd_soc_dai_set_sysclk - configure DAI system or master clock.
+ * @dai: DAI
+ * @clk_id: DAI specific clock ID
+ * @freq: new clock frequency in Hz
+ * @dir: new clock direction - input/output.
+ *
+ * Configures the DAI master (MCLK) or system (SYSCLK) clocking.
+ */
+int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+	unsigned int freq, int dir)
+{
+	if (dai->driver && dai->driver->ops->set_sysclk)
+		return dai->driver->ops->set_sysclk(dai, clk_id, freq, dir);
+	else
+		return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk);
+
+/**
+ * snd_soc_dai_set_clkdiv - configure DAI clock dividers.
+ * @dai: DAI
+ * @div_id: DAI specific clock divider ID
+ * @div: new clock divisor.
+ *
+ * Configures the clock dividers. This is used to derive the best DAI bit and
+ * frame clocks from the system or master clock. It's best to set the DAI bit
+ * and frame clocks as low as possible to save system power.
+ */
+int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai,
+	int div_id, int div)
+{
+	if (dai->driver && dai->driver->ops->set_clkdiv)
+		return dai->driver->ops->set_clkdiv(dai, div_id, div);
+	else
+		return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv);
+
+/**
+ * snd_soc_dai_set_pll - configure DAI PLL.
+ * @dai: DAI
+ * @pll_id: DAI specific PLL ID
+ * @source: DAI specific source for the PLL
+ * @freq_in: PLL input clock frequency in Hz
+ * @freq_out: requested PLL output clock frequency in Hz
+ *
+ * Configures and enables PLL to generate output clock based on input clock.
+ */
+int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source,
+	unsigned int freq_in, unsigned int freq_out)
+{
+	if (dai->driver && dai->driver->ops->set_pll)
+		return dai->driver->ops->set_pll(dai, pll_id, source,
+					 freq_in, freq_out);
+	else
+		return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll);
+
+/**
+ * snd_soc_dai_set_fmt - configure DAI hardware audio format.
+ * @dai: DAI
+ * @fmt: SND_SOC_DAIFMT_ format value.
+ *
+ * Configures the DAI hardware format and clocking.
+ */
+int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	if (dai->driver && dai->driver->ops->set_fmt)
+		return dai->driver->ops->set_fmt(dai, fmt);
+	else
+		return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt);
+
+/**
+ * snd_soc_dai_set_tdm_slot - configure DAI TDM.
+ * @dai: DAI
+ * @tx_mask: bitmask representing active TX slots.
+ * @rx_mask: bitmask representing active RX slots.
+ * @slots: Number of slots in use.
+ * @slot_width: Width in bits for each slot.
+ *
+ * Configures a DAI for TDM operation. Both mask and slots are codec and DAI
+ * specific.
+ */
+int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai,
+	unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+	if (dai->driver && dai->driver->ops->set_tdm_slot)
+		return dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask,
+				slots, slot_width);
+	else
+		return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot);
+
+/**
+ * snd_soc_dai_set_channel_map - configure DAI audio channel map
+ * @dai: DAI
+ * @tx_num: how many TX channels
+ * @tx_slot: pointer to an array which imply the TX slot number channel
+ *           0~num-1 uses
+ * @rx_num: how many RX channels
+ * @rx_slot: pointer to an array which imply the RX slot number channel
+ *           0~num-1 uses
+ *
+ * configure the relationship between channel number and TDM slot number.
+ */
+int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai,
+	unsigned int tx_num, unsigned int *tx_slot,
+	unsigned int rx_num, unsigned int *rx_slot)
+{
+	if (dai->driver && dai->driver->ops->set_channel_map)
+		return dai->driver->ops->set_channel_map(dai, tx_num, tx_slot,
+			rx_num, rx_slot);
+	else
+		return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map);
+
+/**
+ * snd_soc_dai_set_tristate - configure DAI system or master clock.
+ * @dai: DAI
+ * @tristate: tristate enable
+ *
+ * Tristates the DAI so that others can use it.
+ */
+int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+	if (dai->driver && dai->driver->ops->set_tristate)
+		return dai->driver->ops->set_tristate(dai, tristate);
+	else
+		return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate);
+
+/**
+ * snd_soc_dai_digital_mute - configure DAI system or master clock.
+ * @dai: DAI
+ * @mute: mute enable
+ *
+ * Mutes the DAI DAC.
+ */
+int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+	if (dai->driver && dai->driver->ops->digital_mute)
+		return dai->driver->ops->digital_mute(dai, mute);
+	else
+		return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute);
+
+/**
+ * snd_soc_register_card - Register a card with the ASoC core
+ *
+ * @card: Card to register
+ *
+ * Note that currently this is an internal only function: it will be
+ * exposed to machine drivers after further backporting of ASoC v2
+ * registration APIs.
+ */
+static int snd_soc_register_card(struct snd_soc_card *card)
+{
+	int i;
+
+	if (!card->name || !card->dev)
+		return -EINVAL;
+
+	card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * card->num_links,
+			GFP_KERNEL);
+	if (card->rtd == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < card->num_links; i++)
+		card->rtd[i].dai_link = &card->dai_link[i];
+
+	INIT_LIST_HEAD(&card->list);
+	card->instantiated = 0;
+	mutex_init(&card->mutex);
+
+	mutex_lock(&client_mutex);
+	list_add(&card->list, &card_list);
+	snd_soc_instantiate_cards();
+	mutex_unlock(&client_mutex);
+
+	dev_dbg(card->dev, "Registered card '%s'\n", card->name);
+
+	return 0;
+}
+
+/**
+ * snd_soc_unregister_card - Unregister a card with the ASoC core
+ *
+ * @card: Card to unregister
+ *
+ * Note that currently this is an internal only function: it will be
+ * exposed to machine drivers after further backporting of ASoC v2
+ * registration APIs.
+ */
+static int snd_soc_unregister_card(struct snd_soc_card *card)
+{
+	mutex_lock(&client_mutex);
+	list_del(&card->list);
+	mutex_unlock(&client_mutex);
+	dev_dbg(card->dev, "Unregistered card '%s'\n", card->name);
+
+	return 0;
+}
+
+/*
+ * Simplify DAI link configuration by removing ".-1" from device names
+ * and sanitizing names.
+ */
+static inline char *fmt_single_name(struct device *dev, int *id)
+{
+	char *found, name[NAME_SIZE];
+	int id1, id2;
+
+	if (dev_name(dev) == NULL)
+		return NULL;
+
+	strncpy(name, dev_name(dev), NAME_SIZE);
+
+	/* are we a "%s.%d" name (platform and SPI components) */
+	found = strstr(name, dev->driver->name);
+	if (found) {
+		/* get ID */
+		if (sscanf(&found[strlen(dev->driver->name)], ".%d", id) == 1) {
+
+			/* discard ID from name if ID == -1 */
+			if (*id == -1)
+				found[strlen(dev->driver->name)] = '\0';
+		}
+
+	} else {
+		/* I2C component devices are named "bus-addr"  */
+		if (sscanf(name, "%x-%x", &id1, &id2) == 2) {
+			char tmp[NAME_SIZE];
+
+			/* create unique ID number from I2C addr and bus */
+			*id = ((id1 & 0xffff) << 16) + id2;
+
+			/* sanitize component name for DAI link creation */
+			snprintf(tmp, NAME_SIZE, "%s.%s", dev->driver->name, name);
+			strncpy(name, tmp, NAME_SIZE);
+		} else
+			*id = 0;
+	}
+
+	return kstrdup(name, GFP_KERNEL);
+}
+
+/*
+ * Simplify DAI link naming for single devices with multiple DAIs by removing
+ * any ".-1" and using the DAI name (instead of device name).
+ */
+static inline char *fmt_multiple_name(struct device *dev,
+		struct snd_soc_dai_driver *dai_drv)
+{
+	if (dai_drv->name == NULL) {
+		printk(KERN_ERR "asoc: error - multiple DAI %s registered with no name\n",
+				dev_name(dev));
+		return NULL;
+	}
+
+	return kstrdup(dai_drv->name, GFP_KERNEL);
+}
+
+/**
+ * snd_soc_register_dai - Register a DAI with the ASoC core
+ *
+ * @dai: DAI to register
+ */
+int snd_soc_register_dai(struct device *dev,
+		struct snd_soc_dai_driver *dai_drv)
+{
+	struct snd_soc_dai *dai;
+
+	dev_dbg(dev, "dai register %s\n", dev_name(dev));
+
+	dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
+	if (dai == NULL)
+			return -ENOMEM;
+
+	/* create DAI component name */
+	dai->name = fmt_single_name(dev, &dai->id);
+	if (dai->name == NULL) {
+		kfree(dai);
+		return -ENOMEM;
+	}
+
+	dai->dev = dev;
+	dai->driver = dai_drv;
+	if (!dai->driver->ops)
+		dai->driver->ops = &null_dai_ops;
+
+	mutex_lock(&client_mutex);
+	list_add(&dai->list, &dai_list);
+	snd_soc_instantiate_cards();
+	mutex_unlock(&client_mutex);
+
+	pr_debug("Registered DAI '%s'\n", dai->name);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_register_dai);
+
+/**
+ * snd_soc_unregister_dai - Unregister a DAI from the ASoC core
+ *
+ * @dai: DAI to unregister
+ */
+void snd_soc_unregister_dai(struct device *dev)
+{
+	struct snd_soc_dai *dai;
+
+	list_for_each_entry(dai, &dai_list, list) {
+		if (dev == dai->dev)
+			goto found;
+	}
+	return;
+
+found:
+	mutex_lock(&client_mutex);
+	list_del(&dai->list);
+	mutex_unlock(&client_mutex);
+
+	pr_debug("Unregistered DAI '%s'\n", dai->name);
+	kfree(dai->name);
+	kfree(dai);
+}
+EXPORT_SYMBOL_GPL(snd_soc_unregister_dai);
+
+/**
+ * snd_soc_register_dais - Register multiple DAIs with the ASoC core
+ *
+ * @dai: Array of DAIs to register
+ * @count: Number of DAIs
+ */
+int snd_soc_register_dais(struct device *dev,
+		struct snd_soc_dai_driver *dai_drv, size_t count)
+{
+	struct snd_soc_dai *dai;
+	int i, ret = 0;
+
+	dev_dbg(dev, "dai register %s #%Zu\n", dev_name(dev), count);
+
+	for (i = 0; i < count; i++) {
+
+		dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
+		if (dai == NULL) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		/* create DAI component name */
+		dai->name = fmt_multiple_name(dev, &dai_drv[i]);
+		if (dai->name == NULL) {
+			kfree(dai);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		dai->dev = dev;
+		dai->driver = &dai_drv[i];
+		if (dai->driver->id)
+			dai->id = dai->driver->id;
+		else
+			dai->id = i;
+		if (!dai->driver->ops)
+			dai->driver->ops = &null_dai_ops;
+
+		mutex_lock(&client_mutex);
+		list_add(&dai->list, &dai_list);
+		mutex_unlock(&client_mutex);
+
+		pr_debug("Registered DAI '%s'\n", dai->name);
+	}
+
+	mutex_lock(&client_mutex);
+	snd_soc_instantiate_cards();
+	mutex_unlock(&client_mutex);
+	return 0;
+
+err:
+	for (i--; i >= 0; i--)
+		snd_soc_unregister_dai(dev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_register_dais);
+
+/**
+ * snd_soc_unregister_dais - Unregister multiple DAIs from the ASoC core
+ *
+ * @dai: Array of DAIs to unregister
+ * @count: Number of DAIs
+ */
+void snd_soc_unregister_dais(struct device *dev, size_t count)
+{
+	int i;
+
+	for (i = 0; i < count; i++)
+		snd_soc_unregister_dai(dev);
+}
+EXPORT_SYMBOL_GPL(snd_soc_unregister_dais);
+
+/**
+ * snd_soc_register_platform - Register a platform with the ASoC core
+ *
+ * @platform: platform to register
+ */
+int snd_soc_register_platform(struct device *dev,
+		struct snd_soc_platform_driver *platform_drv)
+{
+	struct snd_soc_platform *platform;
+
+	dev_dbg(dev, "platform register %s\n", dev_name(dev));
+
+	platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
+	if (platform == NULL)
+			return -ENOMEM;
+
+	/* create platform component name */
+	platform->name = fmt_single_name(dev, &platform->id);
+	if (platform->name == NULL) {
+		kfree(platform);
+		return -ENOMEM;
+	}
+
+	platform->dev = dev;
+	platform->driver = platform_drv;
+
+	mutex_lock(&client_mutex);
+	list_add(&platform->list, &platform_list);
+	snd_soc_instantiate_cards();
+	mutex_unlock(&client_mutex);
+
+	pr_debug("Registered platform '%s'\n", platform->name);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_register_platform);
+
+/**
+ * snd_soc_unregister_platform - Unregister a platform from the ASoC core
+ *
+ * @platform: platform to unregister
+ */
+void snd_soc_unregister_platform(struct device *dev)
+{
+	struct snd_soc_platform *platform;
+
+	list_for_each_entry(platform, &platform_list, list) {
+		if (dev == platform->dev)
+			goto found;
+	}
+	return;
+
+found:
+	mutex_lock(&client_mutex);
+	list_del(&platform->list);
+	mutex_unlock(&client_mutex);
+
+	pr_debug("Unregistered platform '%s'\n", platform->name);
+	kfree(platform->name);
+	kfree(platform);
+}
+EXPORT_SYMBOL_GPL(snd_soc_unregister_platform);
+
+static u64 codec_format_map[] = {
+	SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE,
+	SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE,
+	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE,
+	SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE,
+	SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE,
+	SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE,
+	SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3BE,
+	SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_U24_3BE,
+	SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE,
+	SNDRV_PCM_FMTBIT_U20_3LE | SNDRV_PCM_FMTBIT_U20_3BE,
+	SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE,
+	SNDRV_PCM_FMTBIT_U18_3LE | SNDRV_PCM_FMTBIT_U18_3BE,
+	SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE,
+	SNDRV_PCM_FMTBIT_FLOAT64_LE | SNDRV_PCM_FMTBIT_FLOAT64_BE,
+	SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE
+	| SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE,
+};
+
+/* Fix up the DAI formats for endianness: codecs don't actually see
+ * the endianness of the data but we're using the CPU format
+ * definitions which do need to include endianness so we ensure that
+ * codec DAIs always have both big and little endian variants set.
+ */
+static void fixup_codec_formats(struct snd_soc_pcm_stream *stream)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(codec_format_map); i++)
+		if (stream->formats & codec_format_map[i])
+			stream->formats |= codec_format_map[i];
+}
+
+/**
+ * snd_soc_register_codec - Register a codec with the ASoC core
+ *
+ * @codec: codec to register
+ */
+int snd_soc_register_codec(struct device *dev,
+		struct snd_soc_codec_driver *codec_drv,
+		struct snd_soc_dai_driver *dai_drv, int num_dai)
+{
+	struct snd_soc_codec *codec;
+	int ret, i;
+
+	dev_dbg(dev, "codec register %s\n", dev_name(dev));
+
+	codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+	if (codec == NULL)
+		return -ENOMEM;
+
+	/* create CODEC component name */
+	codec->name = fmt_single_name(dev, &codec->id);
+	if (codec->name == NULL) {
+		kfree(codec);
+		return -ENOMEM;
+	}
+
+	/* allocate CODEC register cache */
+	if (codec_drv->reg_cache_size && codec_drv->reg_word_size) {
+
+		if (codec_drv->reg_cache_default)
+			codec->reg_cache = kmemdup(codec_drv->reg_cache_default,
+				codec_drv->reg_cache_size * codec_drv->reg_word_size, GFP_KERNEL);
+		else
+			codec->reg_cache = kzalloc(codec_drv->reg_cache_size *
+				codec_drv->reg_word_size, GFP_KERNEL);
+
+		if (codec->reg_cache == NULL) {
+			kfree(codec->name);
+			kfree(codec);
+			return -ENOMEM;
+		}
+	}
+
+	codec->dev = dev;
+	codec->driver = codec_drv;
+	codec->bias_level = SND_SOC_BIAS_OFF;
+	codec->num_dai = num_dai;
+	mutex_init(&codec->mutex);
+	INIT_LIST_HEAD(&codec->dapm_widgets);
+	INIT_LIST_HEAD(&codec->dapm_paths);
+
+	for (i = 0; i < num_dai; i++) {
+		fixup_codec_formats(&dai_drv[i].playback);
+		fixup_codec_formats(&dai_drv[i].capture);
+	}
+
+	/* register any DAIs */
+	if (num_dai) {
+		ret = snd_soc_register_dais(dev, dai_drv, num_dai);
+		if (ret < 0)
+			goto error;
+	}
+
+	mutex_lock(&client_mutex);
+	list_add(&codec->list, &codec_list);
+	snd_soc_instantiate_cards();
+	mutex_unlock(&client_mutex);
+
+	pr_debug("Registered codec '%s'\n", codec->name);
+	return 0;
+
+error:
+	if (codec->reg_cache)
+		kfree(codec->reg_cache);
+	kfree(codec->name);
+	kfree(codec);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_register_codec);
+
+/**
+ * snd_soc_unregister_codec - Unregister a codec from the ASoC core
+ *
+ * @codec: codec to unregister
+ */
+void snd_soc_unregister_codec(struct device *dev)
+{
+	struct snd_soc_codec *codec;
+	int i;
+
+	list_for_each_entry(codec, &codec_list, list) {
+		if (dev == codec->dev)
+			goto found;
+	}
+	return;
+
+found:
+	if (codec->num_dai)
+		for (i = 0; i < codec->num_dai; i++)
+			snd_soc_unregister_dai(dev);
+
+	mutex_lock(&client_mutex);
+	list_del(&codec->list);
+	mutex_unlock(&client_mutex);
+
+	pr_debug("Unregistered codec '%s'\n", codec->name);
+
+	if (codec->reg_cache)
+		kfree(codec->reg_cache);
+	kfree(codec->name);
+	kfree(codec);
+}
+EXPORT_SYMBOL_GPL(snd_soc_unregister_codec);
+
+static int __init snd_soc_init(void)
+{
+#ifdef CONFIG_DEBUG_FS
+	debugfs_root = debugfs_create_dir("asoc", NULL);
+	if (IS_ERR(debugfs_root) || !debugfs_root) {
+		printk(KERN_WARNING
+		       "ASoC: Failed to create debugfs directory\n");
+		debugfs_root = NULL;
+	}
+
+	if (!debugfs_create_file("codecs", 0444, debugfs_root, NULL,
+				 &codec_list_fops))
+		pr_warn("ASoC: Failed to create CODEC list debugfs file\n");
+
+	if (!debugfs_create_file("dais", 0444, debugfs_root, NULL,
+				 &dai_list_fops))
+		pr_warn("ASoC: Failed to create DAI list debugfs file\n");
+
+	if (!debugfs_create_file("platforms", 0444, debugfs_root, NULL,
+				 &platform_list_fops))
+		pr_warn("ASoC: Failed to create platform list debugfs file\n");
+#endif
+
+	return platform_driver_register(&soc_driver);
+}
+module_init(snd_soc_init);
+
+static void __exit snd_soc_exit(void)
+{
+#ifdef CONFIG_DEBUG_FS
+	debugfs_remove_recursive(debugfs_root);
+#endif
+	platform_driver_unregister(&soc_driver);
+}
+module_exit(snd_soc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
+MODULE_DESCRIPTION("ALSA SoC Core");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:soc-audio");
diff --git a/linux/sound/soc/soc-dapm.c b/linux/sound/soc/soc-dapm.c
new file mode 100644
index 0000000..c721502
--- /dev/null
+++ b/linux/sound/soc/soc-dapm.c
@@ -0,0 +1,2223 @@
+/*
+ * soc-dapm.c  --  ALSA SoC Dynamic Audio Power Management
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood <lrg@slimlogic.co.uk>
+ *
+ *  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.
+ *
+ *  Features:
+ *    o Changes power status of internal codec blocks depending on the
+ *      dynamic configuration of codec internal audio paths and active
+ *      DACs/ADCs.
+ *    o Platform power domain - can support external components i.e. amps and
+ *      mic/meadphone insertion events.
+ *    o Automatic Mic Bias support
+ *    o Jack insertion power event initiation - e.g. hp insertion will enable
+ *      sinks, dacs, etc
+ *    o Delayed powerdown of audio susbsystem to reduce pops between a quick
+ *      device reopen.
+ *
+ *  Todo:
+ *    o DAPM power change sequencing - allow for configurable per
+ *      codec sequences.
+ *    o Support for analogue bias optimisation.
+ *    o Support for reduced codec oversampling rates.
+ *    o Support for reduced codec bias currents.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/jiffies.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+/* dapm power sequences - make this per codec in the future */
+static int dapm_up_seq[] = {
+	[snd_soc_dapm_pre] = 0,
+	[snd_soc_dapm_supply] = 1,
+	[snd_soc_dapm_micbias] = 2,
+	[snd_soc_dapm_aif_in] = 3,
+	[snd_soc_dapm_aif_out] = 3,
+	[snd_soc_dapm_mic] = 4,
+	[snd_soc_dapm_mux] = 5,
+	[snd_soc_dapm_value_mux] = 5,
+	[snd_soc_dapm_dac] = 6,
+	[snd_soc_dapm_mixer] = 7,
+	[snd_soc_dapm_mixer_named_ctl] = 7,
+	[snd_soc_dapm_pga] = 8,
+	[snd_soc_dapm_adc] = 9,
+	[snd_soc_dapm_hp] = 10,
+	[snd_soc_dapm_spk] = 10,
+	[snd_soc_dapm_post] = 11,
+};
+
+static int dapm_down_seq[] = {
+	[snd_soc_dapm_pre] = 0,
+	[snd_soc_dapm_adc] = 1,
+	[snd_soc_dapm_hp] = 2,
+	[snd_soc_dapm_spk] = 2,
+	[snd_soc_dapm_pga] = 4,
+	[snd_soc_dapm_mixer_named_ctl] = 5,
+	[snd_soc_dapm_mixer] = 5,
+	[snd_soc_dapm_dac] = 6,
+	[snd_soc_dapm_mic] = 7,
+	[snd_soc_dapm_micbias] = 8,
+	[snd_soc_dapm_mux] = 9,
+	[snd_soc_dapm_value_mux] = 9,
+	[snd_soc_dapm_aif_in] = 10,
+	[snd_soc_dapm_aif_out] = 10,
+	[snd_soc_dapm_supply] = 11,
+	[snd_soc_dapm_post] = 12,
+};
+
+static void pop_wait(u32 pop_time)
+{
+	if (pop_time)
+		schedule_timeout_uninterruptible(msecs_to_jiffies(pop_time));
+}
+
+static void pop_dbg(u32 pop_time, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+
+	if (pop_time) {
+		vprintk(fmt, args);
+	}
+
+	va_end(args);
+}
+
+/* create a new dapm widget */
+static inline struct snd_soc_dapm_widget *dapm_cnew_widget(
+	const struct snd_soc_dapm_widget *_widget)
+{
+	return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL);
+}
+
+/**
+ * snd_soc_dapm_set_bias_level - set the bias level for the system
+ * @card: audio device
+ * @level: level to configure
+ *
+ * Configure the bias (power) levels for the SoC audio device.
+ *
+ * Returns 0 for success else error.
+ */
+static int snd_soc_dapm_set_bias_level(struct snd_soc_card *card,
+		struct snd_soc_codec *codec, enum snd_soc_bias_level level)
+{
+	int ret = 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		dev_dbg(codec->dev, "Setting full bias\n");
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		dev_dbg(codec->dev, "Setting bias prepare\n");
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		dev_dbg(codec->dev, "Setting standby bias\n");
+		break;
+	case SND_SOC_BIAS_OFF:
+		dev_dbg(codec->dev, "Setting bias off\n");
+		break;
+	default:
+		dev_err(codec->dev, "Setting invalid bias %d\n", level);
+		return -EINVAL;
+	}
+
+	if (card && card->set_bias_level)
+		ret = card->set_bias_level(card, level);
+	if (ret == 0) {
+		if (codec->driver->set_bias_level)
+			ret = codec->driver->set_bias_level(codec, level);
+		else
+			codec->bias_level = level;
+	}
+
+	return ret;
+}
+
+/* set up initial codec paths */
+static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
+	struct snd_soc_dapm_path *p, int i)
+{
+	switch (w->id) {
+	case snd_soc_dapm_switch:
+	case snd_soc_dapm_mixer:
+	case snd_soc_dapm_mixer_named_ctl: {
+		int val;
+		struct soc_mixer_control *mc = (struct soc_mixer_control *)
+			w->kcontrols[i].private_value;
+		unsigned int reg = mc->reg;
+		unsigned int shift = mc->shift;
+		int max = mc->max;
+		unsigned int mask = (1 << fls(max)) - 1;
+		unsigned int invert = mc->invert;
+
+		val = snd_soc_read(w->codec, reg);
+		val = (val >> shift) & mask;
+
+		if ((invert && !val) || (!invert && val))
+			p->connect = 1;
+		else
+			p->connect = 0;
+	}
+	break;
+	case snd_soc_dapm_mux: {
+		struct soc_enum *e = (struct soc_enum *)w->kcontrols[i].private_value;
+		int val, item, bitmask;
+
+		for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+		;
+		val = snd_soc_read(w->codec, e->reg);
+		item = (val >> e->shift_l) & (bitmask - 1);
+
+		p->connect = 0;
+		for (i = 0; i < e->max; i++) {
+			if (!(strcmp(p->name, e->texts[i])) && item == i)
+				p->connect = 1;
+		}
+	}
+	break;
+	case snd_soc_dapm_value_mux: {
+		struct soc_enum *e = (struct soc_enum *)
+			w->kcontrols[i].private_value;
+		int val, item;
+
+		val = snd_soc_read(w->codec, e->reg);
+		val = (val >> e->shift_l) & e->mask;
+		for (item = 0; item < e->max; item++) {
+			if (val == e->values[item])
+				break;
+		}
+
+		p->connect = 0;
+		for (i = 0; i < e->max; i++) {
+			if (!(strcmp(p->name, e->texts[i])) && item == i)
+				p->connect = 1;
+		}
+	}
+	break;
+	/* does not effect routing - always connected */
+	case snd_soc_dapm_pga:
+	case snd_soc_dapm_output:
+	case snd_soc_dapm_adc:
+	case snd_soc_dapm_input:
+	case snd_soc_dapm_dac:
+	case snd_soc_dapm_micbias:
+	case snd_soc_dapm_vmid:
+	case snd_soc_dapm_supply:
+	case snd_soc_dapm_aif_in:
+	case snd_soc_dapm_aif_out:
+		p->connect = 1;
+	break;
+	/* does effect routing - dynamically connected */
+	case snd_soc_dapm_hp:
+	case snd_soc_dapm_mic:
+	case snd_soc_dapm_spk:
+	case snd_soc_dapm_line:
+	case snd_soc_dapm_pre:
+	case snd_soc_dapm_post:
+		p->connect = 0;
+	break;
+	}
+}
+
+/* connect mux widget to its interconnecting audio paths */
+static int dapm_connect_mux(struct snd_soc_codec *codec,
+	struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
+	struct snd_soc_dapm_path *path, const char *control_name,
+	const struct snd_kcontrol_new *kcontrol)
+{
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	int i;
+
+	for (i = 0; i < e->max; i++) {
+		if (!(strcmp(control_name, e->texts[i]))) {
+			list_add(&path->list, &codec->dapm_paths);
+			list_add(&path->list_sink, &dest->sources);
+			list_add(&path->list_source, &src->sinks);
+			path->name = (char*)e->texts[i];
+			dapm_set_path_status(dest, path, 0);
+			return 0;
+		}
+	}
+
+	return -ENODEV;
+}
+
+/* connect mixer widget to its interconnecting audio paths */
+static int dapm_connect_mixer(struct snd_soc_codec *codec,
+	struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
+	struct snd_soc_dapm_path *path, const char *control_name)
+{
+	int i;
+
+	/* search for mixer kcontrol */
+	for (i = 0; i < dest->num_kcontrols; i++) {
+		if (!strcmp(control_name, dest->kcontrols[i].name)) {
+			list_add(&path->list, &codec->dapm_paths);
+			list_add(&path->list_sink, &dest->sources);
+			list_add(&path->list_source, &src->sinks);
+			path->name = dest->kcontrols[i].name;
+			dapm_set_path_status(dest, path, i);
+			return 0;
+		}
+	}
+	return -ENODEV;
+}
+
+/* update dapm codec register bits */
+static int dapm_update_bits(struct snd_soc_dapm_widget *widget)
+{
+	int change, power;
+	unsigned int old, new;
+	struct snd_soc_codec *codec = widget->codec;
+
+	/* check for valid widgets */
+	if (widget->reg < 0 || widget->id == snd_soc_dapm_input ||
+		widget->id == snd_soc_dapm_output ||
+		widget->id == snd_soc_dapm_hp ||
+		widget->id == snd_soc_dapm_mic ||
+		widget->id == snd_soc_dapm_line ||
+		widget->id == snd_soc_dapm_spk)
+		return 0;
+
+	power = widget->power;
+	if (widget->invert)
+		power = (power ? 0:1);
+
+	old = snd_soc_read(codec, widget->reg);
+	new = (old & ~(0x1 << widget->shift)) | (power << widget->shift);
+
+	change = old != new;
+	if (change) {
+		pop_dbg(codec->pop_time, "pop test %s : %s in %d ms\n",
+			widget->name, widget->power ? "on" : "off",
+			codec->pop_time);
+		pop_wait(codec->pop_time);
+		snd_soc_write(codec, widget->reg, new);
+	}
+	pr_debug("reg %x old %x new %x change %d\n", widget->reg,
+		 old, new, change);
+	return change;
+}
+
+/* create new dapm mixer control */
+static int dapm_new_mixer(struct snd_soc_codec *codec,
+	struct snd_soc_dapm_widget *w)
+{
+	int i, ret = 0;
+	size_t name_len;
+	struct snd_soc_dapm_path *path;
+
+	/* add kcontrol */
+	for (i = 0; i < w->num_kcontrols; i++) {
+
+		/* match name */
+		list_for_each_entry(path, &w->sources, list_sink) {
+
+			/* mixer/mux paths name must match control name */
+			if (path->name != (char*)w->kcontrols[i].name)
+				continue;
+
+			/* add dapm control with long name.
+			 * for dapm_mixer this is the concatenation of the
+			 * mixer and kcontrol name.
+			 * for dapm_mixer_named_ctl this is simply the
+			 * kcontrol name.
+			 */
+			name_len = strlen(w->kcontrols[i].name) + 1;
+			if (w->id != snd_soc_dapm_mixer_named_ctl)
+				name_len += 1 + strlen(w->name);
+
+			path->long_name = kmalloc(name_len, GFP_KERNEL);
+
+			if (path->long_name == NULL)
+				return -ENOMEM;
+
+			switch (w->id) {
+			default:
+				snprintf(path->long_name, name_len, "%s %s",
+					 w->name, w->kcontrols[i].name);
+				break;
+			case snd_soc_dapm_mixer_named_ctl:
+				snprintf(path->long_name, name_len, "%s",
+					 w->kcontrols[i].name);
+				break;
+			}
+
+			path->long_name[name_len - 1] = '\0';
+
+			path->kcontrol = snd_soc_cnew(&w->kcontrols[i], w,
+				path->long_name);
+			ret = snd_ctl_add(codec->card->snd_card, path->kcontrol);
+			if (ret < 0) {
+				printk(KERN_ERR "asoc: failed to add dapm kcontrol %s: %d\n",
+				       path->long_name,
+				       ret);
+				kfree(path->long_name);
+				path->long_name = NULL;
+				return ret;
+			}
+		}
+	}
+	return ret;
+}
+
+/* create new dapm mux control */
+static int dapm_new_mux(struct snd_soc_codec *codec,
+	struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dapm_path *path = NULL;
+	struct snd_kcontrol *kcontrol;
+	int ret = 0;
+
+	if (!w->num_kcontrols) {
+		printk(KERN_ERR "asoc: mux %s has no controls\n", w->name);
+		return -EINVAL;
+	}
+
+	kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name);
+	ret = snd_ctl_add(codec->card->snd_card, kcontrol);
+	if (ret < 0)
+		goto err;
+
+	list_for_each_entry(path, &w->sources, list_sink)
+		path->kcontrol = kcontrol;
+
+	return ret;
+
+err:
+	printk(KERN_ERR "asoc: failed to add kcontrol %s\n", w->name);
+	return ret;
+}
+
+/* create new dapm volume control */
+static int dapm_new_pga(struct snd_soc_codec *codec,
+	struct snd_soc_dapm_widget *w)
+{
+	if (w->num_kcontrols)
+		pr_err("asoc: PGA controls not supported: '%s'\n", w->name);
+
+	return 0;
+}
+
+/* reset 'walked' bit for each dapm path */
+static inline void dapm_clear_walk(struct snd_soc_codec *codec)
+{
+	struct snd_soc_dapm_path *p;
+
+	list_for_each_entry(p, &codec->dapm_paths, list)
+		p->walked = 0;
+}
+
+/* We implement power down on suspend by checking the power state of
+ * the ALSA card - when we are suspending the ALSA state for the card
+ * is set to D3.
+ */
+static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget)
+{
+	int level = snd_power_get_state(widget->codec->card->snd_card);
+
+	switch (level) {
+	case SNDRV_CTL_POWER_D3hot:
+	case SNDRV_CTL_POWER_D3cold:
+		if (widget->ignore_suspend)
+			pr_debug("%s ignoring suspend\n", widget->name);
+		return widget->ignore_suspend;
+	default:
+		return 1;
+	}
+}
+
+/*
+ * Recursively check for a completed path to an active or physically connected
+ * output widget. Returns number of complete paths.
+ */
+static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
+{
+	struct snd_soc_dapm_path *path;
+	int con = 0;
+
+	if (widget->id == snd_soc_dapm_supply)
+		return 0;
+
+	switch (widget->id) {
+	case snd_soc_dapm_adc:
+	case snd_soc_dapm_aif_out:
+		if (widget->active)
+			return snd_soc_dapm_suspend_check(widget);
+	default:
+		break;
+	}
+
+	if (widget->connected) {
+		/* connected pin ? */
+		if (widget->id == snd_soc_dapm_output && !widget->ext)
+			return snd_soc_dapm_suspend_check(widget);
+
+		/* connected jack or spk ? */
+		if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk ||
+		    (widget->id == snd_soc_dapm_line && !list_empty(&widget->sources)))
+			return snd_soc_dapm_suspend_check(widget);
+	}
+
+	list_for_each_entry(path, &widget->sinks, list_source) {
+		if (path->walked)
+			continue;
+
+		if (path->sink && path->connect) {
+			path->walked = 1;
+			con += is_connected_output_ep(path->sink);
+		}
+	}
+
+	return con;
+}
+
+/*
+ * Recursively check for a completed path to an active or physically connected
+ * input widget. Returns number of complete paths.
+ */
+static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
+{
+	struct snd_soc_dapm_path *path;
+	int con = 0;
+
+	if (widget->id == snd_soc_dapm_supply)
+		return 0;
+
+	/* active stream ? */
+	switch (widget->id) {
+	case snd_soc_dapm_dac:
+	case snd_soc_dapm_aif_in:
+		if (widget->active)
+			return snd_soc_dapm_suspend_check(widget);
+	default:
+		break;
+	}
+
+	if (widget->connected) {
+		/* connected pin ? */
+		if (widget->id == snd_soc_dapm_input && !widget->ext)
+			return snd_soc_dapm_suspend_check(widget);
+
+		/* connected VMID/Bias for lower pops */
+		if (widget->id == snd_soc_dapm_vmid)
+			return snd_soc_dapm_suspend_check(widget);
+
+		/* connected jack ? */
+		if (widget->id == snd_soc_dapm_mic ||
+		    (widget->id == snd_soc_dapm_line && !list_empty(&widget->sinks)))
+			return snd_soc_dapm_suspend_check(widget);
+	}
+
+	list_for_each_entry(path, &widget->sources, list_sink) {
+		if (path->walked)
+			continue;
+
+		if (path->source && path->connect) {
+			path->walked = 1;
+			con += is_connected_input_ep(path->source);
+		}
+	}
+
+	return con;
+}
+
+/*
+ * Handler for generic register modifier widget.
+ */
+int dapm_reg_event(struct snd_soc_dapm_widget *w,
+		   struct snd_kcontrol *kcontrol, int event)
+{
+	unsigned int val;
+
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		val = w->on_val;
+	else
+		val = w->off_val;
+
+	snd_soc_update_bits(w->codec, -(w->reg + 1),
+			    w->mask << w->shift, val << w->shift);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(dapm_reg_event);
+
+/* Standard power change method, used to apply power changes to most
+ * widgets.
+ */
+static int dapm_generic_apply_power(struct snd_soc_dapm_widget *w)
+{
+	int ret;
+
+	/* call any power change event handlers */
+	if (w->event)
+		pr_debug("power %s event for %s flags %x\n",
+			 w->power ? "on" : "off",
+			 w->name, w->event_flags);
+
+	/* power up pre event */
+	if (w->power && w->event &&
+	    (w->event_flags & SND_SOC_DAPM_PRE_PMU)) {
+		ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* power down pre event */
+	if (!w->power && w->event &&
+	    (w->event_flags & SND_SOC_DAPM_PRE_PMD)) {
+		ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD);
+		if (ret < 0)
+			return ret;
+	}
+
+	dapm_update_bits(w);
+
+	/* power up post event */
+	if (w->power && w->event &&
+	    (w->event_flags & SND_SOC_DAPM_POST_PMU)) {
+		ret = w->event(w,
+			       NULL, SND_SOC_DAPM_POST_PMU);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* power down post event */
+	if (!w->power && w->event &&
+	    (w->event_flags & SND_SOC_DAPM_POST_PMD)) {
+		ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* Generic check to see if a widget should be powered.
+ */
+static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
+{
+	int in, out;
+
+	in = is_connected_input_ep(w);
+	dapm_clear_walk(w->codec);
+	out = is_connected_output_ep(w);
+	dapm_clear_walk(w->codec);
+	return out != 0 && in != 0;
+}
+
+/* Check to see if an ADC has power */
+static int dapm_adc_check_power(struct snd_soc_dapm_widget *w)
+{
+	int in;
+
+	if (w->active) {
+		in = is_connected_input_ep(w);
+		dapm_clear_walk(w->codec);
+		return in != 0;
+	} else {
+		return dapm_generic_check_power(w);
+	}
+}
+
+/* Check to see if a DAC has power */
+static int dapm_dac_check_power(struct snd_soc_dapm_widget *w)
+{
+	int out;
+
+	if (w->active) {
+		out = is_connected_output_ep(w);
+		dapm_clear_walk(w->codec);
+		return out != 0;
+	} else {
+		return dapm_generic_check_power(w);
+	}
+}
+
+/* Check to see if a power supply is needed */
+static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
+{
+	struct snd_soc_dapm_path *path;
+	int power = 0;
+
+	/* Check if one of our outputs is connected */
+	list_for_each_entry(path, &w->sinks, list_source) {
+		if (path->connected &&
+		    !path->connected(path->source, path->sink))
+			continue;
+
+		if (path->sink && path->sink->power_check &&
+		    path->sink->power_check(path->sink)) {
+			power = 1;
+			break;
+		}
+	}
+
+	dapm_clear_walk(w->codec);
+
+	return power;
+}
+
+static int dapm_seq_compare(struct snd_soc_dapm_widget *a,
+			    struct snd_soc_dapm_widget *b,
+			    int sort[])
+{
+	if (sort[a->id] != sort[b->id])
+		return sort[a->id] - sort[b->id];
+	if (a->reg != b->reg)
+		return a->reg - b->reg;
+	if (a->codec != b->codec)
+		return (unsigned long)a->codec - (unsigned long)b->codec;
+
+	return 0;
+}
+
+/* Insert a widget in order into a DAPM power sequence. */
+static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget,
+			    struct list_head *list,
+			    int sort[])
+{
+	struct snd_soc_dapm_widget *w;
+
+	list_for_each_entry(w, list, power_list)
+		if (dapm_seq_compare(new_widget, w, sort) < 0) {
+			list_add_tail(&new_widget->power_list, &w->power_list);
+			return;
+		}
+
+	list_add_tail(&new_widget->power_list, list);
+}
+
+/* Apply the coalesced changes from a DAPM sequence */
+static void dapm_seq_run_coalesced(struct snd_soc_codec *codec,
+				   struct list_head *pending)
+{
+	struct snd_soc_dapm_widget *w;
+	int reg, power, ret;
+	unsigned int value = 0;
+	unsigned int mask = 0;
+	unsigned int cur_mask;
+
+	reg = list_first_entry(pending, struct snd_soc_dapm_widget,
+			       power_list)->reg;
+
+	list_for_each_entry(w, pending, power_list) {
+		cur_mask = 1 << w->shift;
+		BUG_ON(reg != w->reg);
+
+		if (w->invert)
+			power = !w->power;
+		else
+			power = w->power;
+
+		mask |= cur_mask;
+		if (power)
+			value |= cur_mask;
+
+		pop_dbg(codec->pop_time,
+			"pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n",
+			w->name, reg, value, mask);
+
+		/* power up pre event */
+		if (w->power && w->event &&
+		    (w->event_flags & SND_SOC_DAPM_PRE_PMU)) {
+			pop_dbg(codec->pop_time, "pop test : %s PRE_PMU\n",
+				w->name);
+			ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU);
+			if (ret < 0)
+				pr_err("%s: pre event failed: %d\n",
+				       w->name, ret);
+		}
+
+		/* power down pre event */
+		if (!w->power && w->event &&
+		    (w->event_flags & SND_SOC_DAPM_PRE_PMD)) {
+			pop_dbg(codec->pop_time, "pop test : %s PRE_PMD\n",
+				w->name);
+			ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD);
+			if (ret < 0)
+				pr_err("%s: pre event failed: %d\n",
+				       w->name, ret);
+		}
+	}
+
+	if (reg >= 0) {
+		pop_dbg(codec->pop_time,
+			"pop test : Applying 0x%x/0x%x to %x in %dms\n",
+			value, mask, reg, codec->pop_time);
+		pop_wait(codec->pop_time);
+		snd_soc_update_bits(codec, reg, mask, value);
+	}
+
+	list_for_each_entry(w, pending, power_list) {
+		/* power up post event */
+		if (w->power && w->event &&
+		    (w->event_flags & SND_SOC_DAPM_POST_PMU)) {
+			pop_dbg(codec->pop_time, "pop test : %s POST_PMU\n",
+				w->name);
+			ret = w->event(w,
+				       NULL, SND_SOC_DAPM_POST_PMU);
+			if (ret < 0)
+				pr_err("%s: post event failed: %d\n",
+				       w->name, ret);
+		}
+
+		/* power down post event */
+		if (!w->power && w->event &&
+		    (w->event_flags & SND_SOC_DAPM_POST_PMD)) {
+			pop_dbg(codec->pop_time, "pop test : %s POST_PMD\n",
+				w->name);
+			ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD);
+			if (ret < 0)
+				pr_err("%s: post event failed: %d\n",
+				       w->name, ret);
+		}
+	}
+}
+
+/* Apply a DAPM power sequence.
+ *
+ * We walk over a pre-sorted list of widgets to apply power to.  In
+ * order to minimise the number of writes to the device required
+ * multiple widgets will be updated in a single write where possible.
+ * Currently anything that requires more than a single write is not
+ * handled.
+ */
+static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list,
+			 int event, int sort[])
+{
+	struct snd_soc_dapm_widget *w, *n;
+	LIST_HEAD(pending);
+	int cur_sort = -1;
+	int cur_reg = SND_SOC_NOPM;
+	int ret;
+
+	list_for_each_entry_safe(w, n, list, power_list) {
+		ret = 0;
+
+		/* Do we need to apply any queued changes? */
+		if (sort[w->id] != cur_sort || w->reg != cur_reg) {
+			if (!list_empty(&pending))
+				dapm_seq_run_coalesced(codec, &pending);
+
+			INIT_LIST_HEAD(&pending);
+			cur_sort = -1;
+			cur_reg = SND_SOC_NOPM;
+		}
+
+		switch (w->id) {
+		case snd_soc_dapm_pre:
+			if (!w->event)
+				list_for_each_entry_safe_continue(w, n, list,
+								  power_list);
+
+			if (event == SND_SOC_DAPM_STREAM_START)
+				ret = w->event(w,
+					       NULL, SND_SOC_DAPM_PRE_PMU);
+			else if (event == SND_SOC_DAPM_STREAM_STOP)
+				ret = w->event(w,
+					       NULL, SND_SOC_DAPM_PRE_PMD);
+			break;
+
+		case snd_soc_dapm_post:
+			if (!w->event)
+				list_for_each_entry_safe_continue(w, n, list,
+								  power_list);
+
+			if (event == SND_SOC_DAPM_STREAM_START)
+				ret = w->event(w,
+					       NULL, SND_SOC_DAPM_POST_PMU);
+			else if (event == SND_SOC_DAPM_STREAM_STOP)
+				ret = w->event(w,
+					       NULL, SND_SOC_DAPM_POST_PMD);
+			break;
+
+		case snd_soc_dapm_input:
+		case snd_soc_dapm_output:
+		case snd_soc_dapm_hp:
+		case snd_soc_dapm_mic:
+		case snd_soc_dapm_line:
+		case snd_soc_dapm_spk:
+			/* No register support currently */
+			ret = dapm_generic_apply_power(w);
+			break;
+
+		default:
+			/* Queue it up for application */
+			cur_sort = sort[w->id];
+			cur_reg = w->reg;
+			list_move(&w->power_list, &pending);
+			break;
+		}
+
+		if (ret < 0)
+			pr_err("Failed to apply widget power: %d\n",
+			       ret);
+	}
+
+	if (!list_empty(&pending))
+		dapm_seq_run_coalesced(codec, &pending);
+}
+
+/*
+ * Scan each dapm widget for complete audio path.
+ * A complete path is a route that has valid endpoints i.e.:-
+ *
+ *  o DAC to output pin.
+ *  o Input Pin to ADC.
+ *  o Input pin to Output pin (bypass, sidetone)
+ *  o DAC to ADC (loopback).
+ */
+static int dapm_power_widgets(struct snd_soc_codec *codec, int event)
+{
+	struct snd_soc_card *card = codec->card;
+	struct snd_soc_dapm_widget *w;
+	LIST_HEAD(up_list);
+	LIST_HEAD(down_list);
+	int ret = 0;
+	int power;
+	int sys_power = 0;
+
+	/* Check which widgets we need to power and store them in
+	 * lists indicating if they should be powered up or down.
+	 */
+	list_for_each_entry(w, &codec->dapm_widgets, list) {
+		switch (w->id) {
+		case snd_soc_dapm_pre:
+			dapm_seq_insert(w, &down_list, dapm_down_seq);
+			break;
+		case snd_soc_dapm_post:
+			dapm_seq_insert(w, &up_list, dapm_up_seq);
+			break;
+
+		default:
+			if (!w->power_check)
+				continue;
+
+			if (!w->force)
+				power = w->power_check(w);
+			else
+				power = 1;
+			if (power)
+				sys_power = 1;
+
+			if (w->power == power)
+				continue;
+
+			if (power)
+				dapm_seq_insert(w, &up_list, dapm_up_seq);
+			else
+				dapm_seq_insert(w, &down_list, dapm_down_seq);
+
+			w->power = power;
+			break;
+		}
+	}
+
+	/* If there are no DAPM widgets then try to figure out power from the
+	 * event type.
+	 */
+	if (list_empty(&codec->dapm_widgets)) {
+		switch (event) {
+		case SND_SOC_DAPM_STREAM_START:
+		case SND_SOC_DAPM_STREAM_RESUME:
+			sys_power = 1;
+			break;
+		case SND_SOC_DAPM_STREAM_STOP:
+			sys_power = !!codec->active;
+			break;
+		case SND_SOC_DAPM_STREAM_SUSPEND:
+			sys_power = 0;
+			break;
+		case SND_SOC_DAPM_STREAM_NOP:
+			switch (codec->bias_level) {
+				case SND_SOC_BIAS_STANDBY:
+				case SND_SOC_BIAS_OFF:
+					sys_power = 0;
+					break;
+				default:
+					sys_power = 1;
+					break;
+			}
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (sys_power && codec->bias_level == SND_SOC_BIAS_OFF) {
+		ret = snd_soc_dapm_set_bias_level(card, codec,
+						  SND_SOC_BIAS_STANDBY);
+		if (ret != 0)
+			pr_err("Failed to turn on bias: %d\n", ret);
+	}
+
+	/* If we're changing to all on or all off then prepare */
+	if ((sys_power && codec->bias_level == SND_SOC_BIAS_STANDBY) ||
+	    (!sys_power && codec->bias_level == SND_SOC_BIAS_ON)) {
+		ret = snd_soc_dapm_set_bias_level(card, codec, SND_SOC_BIAS_PREPARE);
+		if (ret != 0)
+			pr_err("Failed to prepare bias: %d\n", ret);
+	}
+
+	/* Power down widgets first; try to avoid amplifying pops. */
+	dapm_seq_run(codec, &down_list, event, dapm_down_seq);
+
+	/* Now power up. */
+	dapm_seq_run(codec, &up_list, event, dapm_up_seq);
+
+	/* If we just powered the last thing off drop to standby bias */
+	if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) {
+		ret = snd_soc_dapm_set_bias_level(card, codec, SND_SOC_BIAS_STANDBY);
+		if (ret != 0)
+			pr_err("Failed to apply standby bias: %d\n", ret);
+	}
+
+	/* If we're in standby and can support bias off then do that */
+	if (codec->bias_level == SND_SOC_BIAS_STANDBY &&
+	    codec->idle_bias_off) {
+		ret = snd_soc_dapm_set_bias_level(card, codec, SND_SOC_BIAS_OFF);
+		if (ret != 0)
+			pr_err("Failed to turn off bias: %d\n", ret);
+	}
+
+	/* If we just powered up then move to active bias */
+	if (codec->bias_level == SND_SOC_BIAS_PREPARE && sys_power) {
+		ret = snd_soc_dapm_set_bias_level(card, codec, SND_SOC_BIAS_ON);
+		if (ret != 0)
+			pr_err("Failed to apply active bias: %d\n", ret);
+	}
+
+	pop_dbg(codec->pop_time, "DAPM sequencing finished, waiting %dms\n",
+		codec->pop_time);
+	pop_wait(codec->pop_time);
+
+	return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int dapm_widget_power_open_file(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+	return 0;
+}
+
+static ssize_t dapm_widget_power_read_file(struct file *file,
+					   char __user *user_buf,
+					   size_t count, loff_t *ppos)
+{
+	struct snd_soc_dapm_widget *w = file->private_data;
+	char *buf;
+	int in, out;
+	ssize_t ret;
+	struct snd_soc_dapm_path *p = NULL;
+
+	buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	in = is_connected_input_ep(w);
+	dapm_clear_walk(w->codec);
+	out = is_connected_output_ep(w);
+	dapm_clear_walk(w->codec);
+
+	ret = snprintf(buf, PAGE_SIZE, "%s: %s  in %d out %d",
+		       w->name, w->power ? "On" : "Off", in, out);
+
+	if (w->reg >= 0)
+		ret += snprintf(buf + ret, PAGE_SIZE - ret,
+				" - R%d(0x%x) bit %d",
+				w->reg, w->reg, w->shift);
+
+	ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
+
+	if (w->sname)
+		ret += snprintf(buf + ret, PAGE_SIZE - ret, " stream %s %s\n",
+				w->sname,
+				w->active ? "active" : "inactive");
+
+	list_for_each_entry(p, &w->sources, list_sink) {
+		if (p->connected && !p->connected(w, p->sink))
+			continue;
+
+		if (p->connect)
+			ret += snprintf(buf + ret, PAGE_SIZE - ret,
+					" in  %s %s\n",
+					p->name ? p->name : "static",
+					p->source->name);
+	}
+	list_for_each_entry(p, &w->sinks, list_source) {
+		if (p->connected && !p->connected(w, p->sink))
+			continue;
+
+		if (p->connect)
+			ret += snprintf(buf + ret, PAGE_SIZE - ret,
+					" out %s %s\n",
+					p->name ? p->name : "static",
+					p->sink->name);
+	}
+
+	ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	kfree(buf);
+	return ret;
+}
+
+static const struct file_operations dapm_widget_power_fops = {
+	.open = dapm_widget_power_open_file,
+	.read = dapm_widget_power_read_file,
+	.llseek = default_llseek,
+};
+
+void snd_soc_dapm_debugfs_init(struct snd_soc_codec *codec)
+{
+	struct snd_soc_dapm_widget *w;
+	struct dentry *d;
+
+	if (!codec->debugfs_dapm)
+		return;
+
+	list_for_each_entry(w, &codec->dapm_widgets, list) {
+		if (!w->name)
+			continue;
+
+		d = debugfs_create_file(w->name, 0444,
+					codec->debugfs_dapm, w,
+					&dapm_widget_power_fops);
+		if (!d)
+			printk(KERN_WARNING
+			       "ASoC: Failed to create %s debugfs file\n",
+			       w->name);
+	}
+}
+#else
+void snd_soc_dapm_debugfs_init(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+/* test and update the power status of a mux widget */
+static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
+				 struct snd_kcontrol *kcontrol, int change,
+				 int mux, struct soc_enum *e)
+{
+	struct snd_soc_dapm_path *path;
+	int found = 0;
+
+	if (widget->id != snd_soc_dapm_mux &&
+	    widget->id != snd_soc_dapm_value_mux)
+		return -ENODEV;
+
+	if (!change)
+		return 0;
+
+	/* find dapm widget path assoc with kcontrol */
+	list_for_each_entry(path, &widget->codec->dapm_paths, list) {
+		if (path->kcontrol != kcontrol)
+			continue;
+
+		if (!path->name || !e->texts[mux])
+			continue;
+
+		found = 1;
+		/* we now need to match the string in the enum to the path */
+		if (!(strcmp(path->name, e->texts[mux])))
+			path->connect = 1; /* new connection */
+		else
+			path->connect = 0; /* old connection must be powered down */
+	}
+
+	if (found)
+		dapm_power_widgets(widget->codec, SND_SOC_DAPM_STREAM_NOP);
+
+	return 0;
+}
+
+/* test and update the power status of a mixer or switch widget */
+static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
+				   struct snd_kcontrol *kcontrol, int connect)
+{
+	struct snd_soc_dapm_path *path;
+	int found = 0;
+
+	if (widget->id != snd_soc_dapm_mixer &&
+	    widget->id != snd_soc_dapm_mixer_named_ctl &&
+	    widget->id != snd_soc_dapm_switch)
+		return -ENODEV;
+
+	/* find dapm widget path assoc with kcontrol */
+	list_for_each_entry(path, &widget->codec->dapm_paths, list) {
+		if (path->kcontrol != kcontrol)
+			continue;
+
+		/* found, now check type */
+		found = 1;
+		path->connect = connect;
+		break;
+	}
+
+	if (found)
+		dapm_power_widgets(widget->codec, SND_SOC_DAPM_STREAM_NOP);
+
+	return 0;
+}
+
+/* show dapm widget status in sys fs */
+static ssize_t dapm_widget_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct snd_soc_pcm_runtime *rtd =
+			container_of(dev, struct snd_soc_pcm_runtime, dev);
+	struct snd_soc_codec *codec =rtd->codec;
+	struct snd_soc_dapm_widget *w;
+	int count = 0;
+	char *state = "not set";
+
+	list_for_each_entry(w, &codec->dapm_widgets, list) {
+
+		/* only display widgets that burnm power */
+		switch (w->id) {
+		case snd_soc_dapm_hp:
+		case snd_soc_dapm_mic:
+		case snd_soc_dapm_spk:
+		case snd_soc_dapm_line:
+		case snd_soc_dapm_micbias:
+		case snd_soc_dapm_dac:
+		case snd_soc_dapm_adc:
+		case snd_soc_dapm_pga:
+		case snd_soc_dapm_mixer:
+		case snd_soc_dapm_mixer_named_ctl:
+		case snd_soc_dapm_supply:
+			if (w->name)
+				count += sprintf(buf + count, "%s: %s\n",
+					w->name, w->power ? "On":"Off");
+		break;
+		default:
+		break;
+		}
+	}
+
+	switch (codec->bias_level) {
+	case SND_SOC_BIAS_ON:
+		state = "On";
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		state = "Prepare";
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		state = "Standby";
+		break;
+	case SND_SOC_BIAS_OFF:
+		state = "Off";
+		break;
+	}
+	count += sprintf(buf + count, "PM State: %s\n", state);
+
+	return count;
+}
+
+static DEVICE_ATTR(dapm_widget, 0444, dapm_widget_show, NULL);
+
+int snd_soc_dapm_sys_add(struct device *dev)
+{
+	return device_create_file(dev, &dev_attr_dapm_widget);
+}
+
+static void snd_soc_dapm_sys_remove(struct device *dev)
+{
+	device_remove_file(dev, &dev_attr_dapm_widget);
+}
+
+/* free all dapm widgets and resources */
+static void dapm_free_widgets(struct snd_soc_codec *codec)
+{
+	struct snd_soc_dapm_widget *w, *next_w;
+	struct snd_soc_dapm_path *p, *next_p;
+
+	list_for_each_entry_safe(w, next_w, &codec->dapm_widgets, list) {
+		list_del(&w->list);
+		kfree(w);
+	}
+
+	list_for_each_entry_safe(p, next_p, &codec->dapm_paths, list) {
+		list_del(&p->list);
+		kfree(p->long_name);
+		kfree(p);
+	}
+}
+
+static int snd_soc_dapm_set_pin(struct snd_soc_codec *codec,
+				const char *pin, int status)
+{
+	struct snd_soc_dapm_widget *w;
+
+	list_for_each_entry(w, &codec->dapm_widgets, list) {
+		if (!strcmp(w->name, pin)) {
+			pr_debug("dapm: %s: pin %s\n", codec->name, pin);
+			w->connected = status;
+			/* Allow disabling of forced pins */
+			if (status == 0)
+				w->force = 0;
+			return 0;
+		}
+	}
+
+	pr_err("dapm: %s: configuring unknown pin %s\n", codec->name, pin);
+	return -EINVAL;
+}
+
+/**
+ * snd_soc_dapm_sync - scan and power dapm paths
+ * @codec: audio codec
+ *
+ * Walks all dapm audio paths and powers widgets according to their
+ * stream or path usage.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_sync(struct snd_soc_codec *codec)
+{
+	return dapm_power_widgets(codec, SND_SOC_DAPM_STREAM_NOP);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_sync);
+
+static int snd_soc_dapm_add_route(struct snd_soc_codec *codec,
+				  const struct snd_soc_dapm_route *route)
+{
+	struct snd_soc_dapm_path *path;
+	struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
+	const char *sink = route->sink;
+	const char *control = route->control;
+	const char *source = route->source;
+	int ret = 0;
+
+	/* find src and dest widgets */
+	list_for_each_entry(w, &codec->dapm_widgets, list) {
+
+		if (!wsink && !(strcmp(w->name, sink))) {
+			wsink = w;
+			continue;
+		}
+		if (!wsource && !(strcmp(w->name, source))) {
+			wsource = w;
+		}
+	}
+
+	if (wsource == NULL || wsink == NULL)
+		return -ENODEV;
+
+	path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
+	if (!path)
+		return -ENOMEM;
+
+	path->source = wsource;
+	path->sink = wsink;
+	path->connected = route->connected;
+	INIT_LIST_HEAD(&path->list);
+	INIT_LIST_HEAD(&path->list_source);
+	INIT_LIST_HEAD(&path->list_sink);
+
+	/* check for external widgets */
+	if (wsink->id == snd_soc_dapm_input) {
+		if (wsource->id == snd_soc_dapm_micbias ||
+			wsource->id == snd_soc_dapm_mic ||
+			wsource->id == snd_soc_dapm_line ||
+			wsource->id == snd_soc_dapm_output)
+			wsink->ext = 1;
+	}
+	if (wsource->id == snd_soc_dapm_output) {
+		if (wsink->id == snd_soc_dapm_spk ||
+			wsink->id == snd_soc_dapm_hp ||
+			wsink->id == snd_soc_dapm_line ||
+			wsink->id == snd_soc_dapm_input)
+			wsource->ext = 1;
+	}
+
+	/* connect static paths */
+	if (control == NULL) {
+		list_add(&path->list, &codec->dapm_paths);
+		list_add(&path->list_sink, &wsink->sources);
+		list_add(&path->list_source, &wsource->sinks);
+		path->connect = 1;
+		return 0;
+	}
+
+	/* connect dynamic paths */
+	switch(wsink->id) {
+	case snd_soc_dapm_adc:
+	case snd_soc_dapm_dac:
+	case snd_soc_dapm_pga:
+	case snd_soc_dapm_input:
+	case snd_soc_dapm_output:
+	case snd_soc_dapm_micbias:
+	case snd_soc_dapm_vmid:
+	case snd_soc_dapm_pre:
+	case snd_soc_dapm_post:
+	case snd_soc_dapm_supply:
+	case snd_soc_dapm_aif_in:
+	case snd_soc_dapm_aif_out:
+		list_add(&path->list, &codec->dapm_paths);
+		list_add(&path->list_sink, &wsink->sources);
+		list_add(&path->list_source, &wsource->sinks);
+		path->connect = 1;
+		return 0;
+	case snd_soc_dapm_mux:
+	case snd_soc_dapm_value_mux:
+		ret = dapm_connect_mux(codec, wsource, wsink, path, control,
+			&wsink->kcontrols[0]);
+		if (ret != 0)
+			goto err;
+		break;
+	case snd_soc_dapm_switch:
+	case snd_soc_dapm_mixer:
+	case snd_soc_dapm_mixer_named_ctl:
+		ret = dapm_connect_mixer(codec, wsource, wsink, path, control);
+		if (ret != 0)
+			goto err;
+		break;
+	case snd_soc_dapm_hp:
+	case snd_soc_dapm_mic:
+	case snd_soc_dapm_line:
+	case snd_soc_dapm_spk:
+		list_add(&path->list, &codec->dapm_paths);
+		list_add(&path->list_sink, &wsink->sources);
+		list_add(&path->list_source, &wsource->sinks);
+		path->connect = 0;
+		return 0;
+	}
+	return 0;
+
+err:
+	printk(KERN_WARNING "asoc: no dapm match for %s --> %s --> %s\n", source,
+		control, sink);
+	kfree(path);
+	return ret;
+}
+
+/**
+ * snd_soc_dapm_add_routes - Add routes between DAPM widgets
+ * @codec: codec
+ * @route: audio routes
+ * @num: number of routes
+ *
+ * Connects 2 dapm widgets together via a named audio path. The sink is
+ * the widget receiving the audio signal, whilst the source is the sender
+ * of the audio signal.
+ *
+ * Returns 0 for success else error. On error all resources can be freed
+ * with a call to snd_soc_card_free().
+ */
+int snd_soc_dapm_add_routes(struct snd_soc_codec *codec,
+			    const struct snd_soc_dapm_route *route, int num)
+{
+	int i, ret;
+
+	for (i = 0; i < num; i++) {
+		ret = snd_soc_dapm_add_route(codec, route);
+		if (ret < 0) {
+			printk(KERN_ERR "Failed to add route %s->%s\n",
+			       route->source,
+			       route->sink);
+			return ret;
+		}
+		route++;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_add_routes);
+
+/**
+ * snd_soc_dapm_new_widgets - add new dapm widgets
+ * @codec: audio codec
+ *
+ * Checks the codec for any new dapm widgets and creates them if found.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec)
+{
+	struct snd_soc_dapm_widget *w;
+
+	list_for_each_entry(w, &codec->dapm_widgets, list)
+	{
+		if (w->new)
+			continue;
+
+		switch(w->id) {
+		case snd_soc_dapm_switch:
+		case snd_soc_dapm_mixer:
+		case snd_soc_dapm_mixer_named_ctl:
+			w->power_check = dapm_generic_check_power;
+			dapm_new_mixer(codec, w);
+			break;
+		case snd_soc_dapm_mux:
+		case snd_soc_dapm_value_mux:
+			w->power_check = dapm_generic_check_power;
+			dapm_new_mux(codec, w);
+			break;
+		case snd_soc_dapm_adc:
+		case snd_soc_dapm_aif_out:
+			w->power_check = dapm_adc_check_power;
+			break;
+		case snd_soc_dapm_dac:
+		case snd_soc_dapm_aif_in:
+			w->power_check = dapm_dac_check_power;
+			break;
+		case snd_soc_dapm_pga:
+			w->power_check = dapm_generic_check_power;
+			dapm_new_pga(codec, w);
+			break;
+		case snd_soc_dapm_input:
+		case snd_soc_dapm_output:
+		case snd_soc_dapm_micbias:
+		case snd_soc_dapm_spk:
+		case snd_soc_dapm_hp:
+		case snd_soc_dapm_mic:
+		case snd_soc_dapm_line:
+			w->power_check = dapm_generic_check_power;
+			break;
+		case snd_soc_dapm_supply:
+			w->power_check = dapm_supply_check_power;
+		case snd_soc_dapm_vmid:
+		case snd_soc_dapm_pre:
+		case snd_soc_dapm_post:
+			break;
+		}
+		w->new = 1;
+	}
+
+	dapm_power_widgets(codec, SND_SOC_DAPM_STREAM_NOP);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_new_widgets);
+
+/**
+ * snd_soc_dapm_get_volsw - dapm mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a dapm mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int shift = mc->shift;
+	unsigned int rshift = mc->rshift;
+	int max = mc->max;
+	unsigned int invert = mc->invert;
+	unsigned int mask = (1 << fls(max)) - 1;
+
+	ucontrol->value.integer.value[0] =
+		(snd_soc_read(widget->codec, reg) >> shift) & mask;
+	if (shift != rshift)
+		ucontrol->value.integer.value[1] =
+			(snd_soc_read(widget->codec, reg) >> rshift) & mask;
+	if (invert) {
+		ucontrol->value.integer.value[0] =
+			max - ucontrol->value.integer.value[0];
+		if (shift != rshift)
+			ucontrol->value.integer.value[1] =
+				max - ucontrol->value.integer.value[1];
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_volsw);
+
+/**
+ * snd_soc_dapm_put_volsw - dapm mixer set callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a dapm mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+	unsigned int shift = mc->shift;
+	unsigned int rshift = mc->rshift;
+	int max = mc->max;
+	unsigned int mask = (1 << fls(max)) - 1;
+	unsigned int invert = mc->invert;
+	unsigned int val, val2, val_mask;
+	int connect;
+	int ret;
+
+	val = (ucontrol->value.integer.value[0] & mask);
+
+	if (invert)
+		val = max - val;
+	val_mask = mask << shift;
+	val = val << shift;
+	if (shift != rshift) {
+		val2 = (ucontrol->value.integer.value[1] & mask);
+		if (invert)
+			val2 = max - val2;
+		val_mask |= mask << rshift;
+		val |= val2 << rshift;
+	}
+
+	mutex_lock(&widget->codec->mutex);
+	widget->value = val;
+
+	if (snd_soc_test_bits(widget->codec, reg, val_mask, val)) {
+		if (val)
+			/* new connection */
+			connect = invert ? 0:1;
+		else
+			/* old connection must be powered down */
+			connect = invert ? 1:0;
+
+		dapm_mixer_update_power(widget, kcontrol, connect);
+	}
+
+	if (widget->event) {
+		if (widget->event_flags & SND_SOC_DAPM_PRE_REG) {
+			ret = widget->event(widget, kcontrol,
+						SND_SOC_DAPM_PRE_REG);
+			if (ret < 0) {
+				ret = 1;
+				goto out;
+			}
+		}
+		ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
+		if (widget->event_flags & SND_SOC_DAPM_POST_REG)
+			ret = widget->event(widget, kcontrol,
+						SND_SOC_DAPM_POST_REG);
+	} else
+		ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
+
+out:
+	mutex_unlock(&widget->codec->mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw);
+
+/**
+ * snd_soc_dapm_get_enum_double - dapm enumerated double mixer get callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a dapm enumerated double mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned int val, bitmask;
+
+	for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+		;
+	val = snd_soc_read(widget->codec, e->reg);
+	ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (bitmask - 1);
+	if (e->shift_l != e->shift_r)
+		ucontrol->value.enumerated.item[1] =
+			(val >> e->shift_r) & (bitmask - 1);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_double);
+
+/**
+ * snd_soc_dapm_put_enum_double - dapm enumerated double mixer set callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a dapm enumerated double mixer control.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned int val, mux, change;
+	unsigned int mask, bitmask;
+	int ret = 0;
+
+	for (bitmask = 1; bitmask < e->max; bitmask <<= 1)
+		;
+	if (ucontrol->value.enumerated.item[0] > e->max - 1)
+		return -EINVAL;
+	mux = ucontrol->value.enumerated.item[0];
+	val = mux << e->shift_l;
+	mask = (bitmask - 1) << e->shift_l;
+	if (e->shift_l != e->shift_r) {
+		if (ucontrol->value.enumerated.item[1] > e->max - 1)
+			return -EINVAL;
+		val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+		mask |= (bitmask - 1) << e->shift_r;
+	}
+
+	mutex_lock(&widget->codec->mutex);
+	widget->value = val;
+	change = snd_soc_test_bits(widget->codec, e->reg, mask, val);
+	dapm_mux_update_power(widget, kcontrol, change, mux, e);
+
+	if (widget->event_flags & SND_SOC_DAPM_PRE_REG) {
+		ret = widget->event(widget,
+				    kcontrol, SND_SOC_DAPM_PRE_REG);
+		if (ret < 0)
+			goto out;
+	}
+
+	ret = snd_soc_update_bits(widget->codec, e->reg, mask, val);
+
+	if (widget->event_flags & SND_SOC_DAPM_POST_REG)
+		ret = widget->event(widget,
+				    kcontrol, SND_SOC_DAPM_POST_REG);
+
+out:
+	mutex_unlock(&widget->codec->mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_double);
+
+/**
+ * snd_soc_dapm_get_enum_virt - Get virtual DAPM mux
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_get_enum_virt(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = widget->value;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_virt);
+
+/**
+ * snd_soc_dapm_put_enum_virt - Set virtual DAPM mux
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+	struct soc_enum *e =
+		(struct soc_enum *)kcontrol->private_value;
+	int change;
+	int ret = 0;
+
+	if (ucontrol->value.enumerated.item[0] >= e->max)
+		return -EINVAL;
+
+	mutex_lock(&widget->codec->mutex);
+
+	change = widget->value != ucontrol->value.enumerated.item[0];
+	widget->value = ucontrol->value.enumerated.item[0];
+	dapm_mux_update_power(widget, kcontrol, change, widget->value, e);
+
+	mutex_unlock(&widget->codec->mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_virt);
+
+/**
+ * snd_soc_dapm_get_value_enum_double - dapm semi enumerated double mixer get
+ *					callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to get the value of a dapm semi enumerated double mixer control.
+ *
+ * Semi enumerated mixer: the enumerated items are referred as values. Can be
+ * used for handling bitfield coded enumeration for example.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_get_value_enum_double(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned int reg_val, val, mux;
+
+	reg_val = snd_soc_read(widget->codec, e->reg);
+	val = (reg_val >> e->shift_l) & e->mask;
+	for (mux = 0; mux < e->max; mux++) {
+		if (val == e->values[mux])
+			break;
+	}
+	ucontrol->value.enumerated.item[0] = mux;
+	if (e->shift_l != e->shift_r) {
+		val = (reg_val >> e->shift_r) & e->mask;
+		for (mux = 0; mux < e->max; mux++) {
+			if (val == e->values[mux])
+				break;
+		}
+		ucontrol->value.enumerated.item[1] = mux;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_value_enum_double);
+
+/**
+ * snd_soc_dapm_put_value_enum_double - dapm semi enumerated double mixer set
+ *					callback
+ * @kcontrol: mixer control
+ * @ucontrol: control element information
+ *
+ * Callback to set the value of a dapm semi enumerated double mixer control.
+ *
+ * Semi enumerated mixer: the enumerated items are referred as values. Can be
+ * used for handling bitfield coded enumeration for example.
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned int val, mux, change;
+	unsigned int mask;
+	int ret = 0;
+
+	if (ucontrol->value.enumerated.item[0] > e->max - 1)
+		return -EINVAL;
+	mux = ucontrol->value.enumerated.item[0];
+	val = e->values[ucontrol->value.enumerated.item[0]] << e->shift_l;
+	mask = e->mask << e->shift_l;
+	if (e->shift_l != e->shift_r) {
+		if (ucontrol->value.enumerated.item[1] > e->max - 1)
+			return -EINVAL;
+		val |= e->values[ucontrol->value.enumerated.item[1]] << e->shift_r;
+		mask |= e->mask << e->shift_r;
+	}
+
+	mutex_lock(&widget->codec->mutex);
+	widget->value = val;
+	change = snd_soc_test_bits(widget->codec, e->reg, mask, val);
+	dapm_mux_update_power(widget, kcontrol, change, mux, e);
+
+	if (widget->event_flags & SND_SOC_DAPM_PRE_REG) {
+		ret = widget->event(widget,
+				    kcontrol, SND_SOC_DAPM_PRE_REG);
+		if (ret < 0)
+			goto out;
+	}
+
+	ret = snd_soc_update_bits(widget->codec, e->reg, mask, val);
+
+	if (widget->event_flags & SND_SOC_DAPM_POST_REG)
+		ret = widget->event(widget,
+				    kcontrol, SND_SOC_DAPM_POST_REG);
+
+out:
+	mutex_unlock(&widget->codec->mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_value_enum_double);
+
+/**
+ * snd_soc_dapm_info_pin_switch - Info for a pin switch
+ *
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Callback to provide information about a pin switch control.
+ */
+int snd_soc_dapm_info_pin_switch(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_info_pin_switch);
+
+/**
+ * snd_soc_dapm_get_pin_switch - Get information for a pin switch
+ *
+ * @kcontrol: mixer control
+ * @ucontrol: Value
+ */
+int snd_soc_dapm_get_pin_switch(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	const char *pin = (const char *)kcontrol->private_value;
+
+	mutex_lock(&codec->mutex);
+
+	ucontrol->value.integer.value[0] =
+		snd_soc_dapm_get_pin_status(codec, pin);
+
+	mutex_unlock(&codec->mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_switch);
+
+/**
+ * snd_soc_dapm_put_pin_switch - Set information for a pin switch
+ *
+ * @kcontrol: mixer control
+ * @ucontrol: Value
+ */
+int snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	const char *pin = (const char *)kcontrol->private_value;
+
+	mutex_lock(&codec->mutex);
+
+	if (ucontrol->value.integer.value[0])
+		snd_soc_dapm_enable_pin(codec, pin);
+	else
+		snd_soc_dapm_disable_pin(codec, pin);
+
+	snd_soc_dapm_sync(codec);
+
+	mutex_unlock(&codec->mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_put_pin_switch);
+
+/**
+ * snd_soc_dapm_new_control - create new dapm control
+ * @codec: audio codec
+ * @widget: widget template
+ *
+ * Creates a new dapm control based upon the template.
+ *
+ * Returns 0 for success else error.
+ */
+int snd_soc_dapm_new_control(struct snd_soc_codec *codec,
+	const struct snd_soc_dapm_widget *widget)
+{
+	struct snd_soc_dapm_widget *w;
+
+	if ((w = dapm_cnew_widget(widget)) == NULL)
+		return -ENOMEM;
+
+	w->codec = codec;
+	INIT_LIST_HEAD(&w->sources);
+	INIT_LIST_HEAD(&w->sinks);
+	INIT_LIST_HEAD(&w->list);
+	list_add(&w->list, &codec->dapm_widgets);
+
+	/* machine layer set ups unconnected pins and insertions */
+	w->connected = 1;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control);
+
+/**
+ * snd_soc_dapm_new_controls - create new dapm controls
+ * @codec: audio codec
+ * @widget: widget array
+ * @num: number of widgets
+ *
+ * Creates new DAPM controls based upon the templates.
+ *
+ * Returns 0 for success else error.
+ */
+int snd_soc_dapm_new_controls(struct snd_soc_codec *codec,
+	const struct snd_soc_dapm_widget *widget,
+	int num)
+{
+	int i, ret;
+
+	for (i = 0; i < num; i++) {
+		ret = snd_soc_dapm_new_control(codec, widget);
+		if (ret < 0) {
+			printk(KERN_ERR
+			       "ASoC: Failed to create DAPM control %s: %d\n",
+			       widget->name, ret);
+			return ret;
+		}
+		widget++;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls);
+
+
+/**
+ * snd_soc_dapm_stream_event - send a stream event to the dapm core
+ * @codec: audio codec
+ * @stream: stream name
+ * @event: stream event
+ *
+ * Sends a stream event to the dapm core. The core then makes any
+ * necessary widget power changes.
+ *
+ * Returns 0 for success else error.
+ */
+int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd,
+	const char *stream, int event)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	struct snd_soc_dapm_widget *w;
+
+	if (stream == NULL)
+		return 0;
+
+	mutex_lock(&codec->mutex);
+	list_for_each_entry(w, &codec->dapm_widgets, list)
+	{
+		if (!w->sname)
+			continue;
+		pr_debug("widget %s\n %s stream %s event %d\n",
+			 w->name, w->sname, stream, event);
+		if (strstr(w->sname, stream)) {
+			switch(event) {
+			case SND_SOC_DAPM_STREAM_START:
+				w->active = 1;
+				break;
+			case SND_SOC_DAPM_STREAM_STOP:
+				w->active = 0;
+				break;
+			case SND_SOC_DAPM_STREAM_SUSPEND:
+			case SND_SOC_DAPM_STREAM_RESUME:
+			case SND_SOC_DAPM_STREAM_PAUSE_PUSH:
+			case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
+				break;
+			}
+		}
+	}
+
+	dapm_power_widgets(codec, event);
+	mutex_unlock(&codec->mutex);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event);
+
+/**
+ * snd_soc_dapm_enable_pin - enable pin.
+ * @codec: SoC codec
+ * @pin: pin name
+ *
+ * Enables input/output pin and its parents or children widgets iff there is
+ * a valid audio route and active audio stream.
+ * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
+ * do any widget power switching.
+ */
+int snd_soc_dapm_enable_pin(struct snd_soc_codec *codec, const char *pin)
+{
+	return snd_soc_dapm_set_pin(codec, pin, 1);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin);
+
+/**
+ * snd_soc_dapm_force_enable_pin - force a pin to be enabled
+ * @codec: SoC codec
+ * @pin: pin name
+ *
+ * Enables input/output pin regardless of any other state.  This is
+ * intended for use with microphone bias supplies used in microphone
+ * jack detection.
+ *
+ * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
+ * do any widget power switching.
+ */
+int snd_soc_dapm_force_enable_pin(struct snd_soc_codec *codec, const char *pin)
+{
+	struct snd_soc_dapm_widget *w;
+
+	list_for_each_entry(w, &codec->dapm_widgets, list) {
+		if (!strcmp(w->name, pin)) {
+			pr_debug("dapm: %s: pin %s\n", codec->name, pin);
+			w->connected = 1;
+			w->force = 1;
+			return 0;
+		}
+	}
+
+	pr_err("dapm: %s: configuring unknown pin %s\n", codec->name, pin);
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_force_enable_pin);
+
+/**
+ * snd_soc_dapm_disable_pin - disable pin.
+ * @codec: SoC codec
+ * @pin: pin name
+ *
+ * Disables input/output pin and its parents or children widgets.
+ * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
+ * do any widget power switching.
+ */
+int snd_soc_dapm_disable_pin(struct snd_soc_codec *codec, const char *pin)
+{
+	return snd_soc_dapm_set_pin(codec, pin, 0);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_disable_pin);
+
+/**
+ * snd_soc_dapm_nc_pin - permanently disable pin.
+ * @codec: SoC codec
+ * @pin: pin name
+ *
+ * Marks the specified pin as being not connected, disabling it along
+ * any parent or child widgets.  At present this is identical to
+ * snd_soc_dapm_disable_pin() but in future it will be extended to do
+ * additional things such as disabling controls which only affect
+ * paths through the pin.
+ *
+ * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to
+ * do any widget power switching.
+ */
+int snd_soc_dapm_nc_pin(struct snd_soc_codec *codec, const char *pin)
+{
+	return snd_soc_dapm_set_pin(codec, pin, 0);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_nc_pin);
+
+/**
+ * snd_soc_dapm_get_pin_status - get audio pin status
+ * @codec: audio codec
+ * @pin: audio signal pin endpoint (or start point)
+ *
+ * Get audio pin status - connected or disconnected.
+ *
+ * Returns 1 for connected otherwise 0.
+ */
+int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, const char *pin)
+{
+	struct snd_soc_dapm_widget *w;
+
+	list_for_each_entry(w, &codec->dapm_widgets, list) {
+		if (!strcmp(w->name, pin))
+			return w->connected;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_status);
+
+/**
+ * snd_soc_dapm_ignore_suspend - ignore suspend status for DAPM endpoint
+ * @codec: audio codec
+ * @pin: audio signal pin endpoint (or start point)
+ *
+ * Mark the given endpoint or pin as ignoring suspend.  When the
+ * system is disabled a path between two endpoints flagged as ignoring
+ * suspend will not be disabled.  The path must already be enabled via
+ * normal means at suspend time, it will not be turned on if it was not
+ * already enabled.
+ */
+int snd_soc_dapm_ignore_suspend(struct snd_soc_codec *codec, const char *pin)
+{
+	struct snd_soc_dapm_widget *w;
+
+	list_for_each_entry(w, &codec->dapm_widgets, list) {
+		if (!strcmp(w->name, pin)) {
+			w->ignore_suspend = 1;
+			return 0;
+		}
+	}
+
+	pr_err("Unknown DAPM pin: %s\n", pin);
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_ignore_suspend);
+
+/**
+ * snd_soc_dapm_free - free dapm resources
+ * @card: SoC device
+ *
+ * Free all dapm widgets and resources.
+ */
+void snd_soc_dapm_free(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_sys_remove(codec->dev);
+	dapm_free_widgets(codec);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dapm_free);
+
+static void soc_dapm_shutdown_codec(struct snd_soc_codec *codec)
+{
+	struct snd_soc_dapm_widget *w;
+	LIST_HEAD(down_list);
+	int powerdown = 0;
+
+	list_for_each_entry(w, &codec->dapm_widgets, list) {
+		if (w->power) {
+			dapm_seq_insert(w, &down_list, dapm_down_seq);
+			w->power = 0;
+			powerdown = 1;
+		}
+	}
+
+	/* If there were no widgets to power down we're already in
+	 * standby.
+	 */
+	if (powerdown) {
+		snd_soc_dapm_set_bias_level(NULL, codec, SND_SOC_BIAS_PREPARE);
+		dapm_seq_run(codec, &down_list, 0, dapm_down_seq);
+		snd_soc_dapm_set_bias_level(NULL, codec, SND_SOC_BIAS_STANDBY);
+	}
+}
+
+/*
+ * snd_soc_dapm_shutdown - callback for system shutdown
+ */
+void snd_soc_dapm_shutdown(struct snd_soc_card *card)
+{
+	struct snd_soc_codec *codec;
+
+	list_for_each_entry(codec, &card->codec_dev_list, list)
+		soc_dapm_shutdown_codec(codec);
+
+	snd_soc_dapm_set_bias_level(card, codec, SND_SOC_BIAS_OFF);
+}
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
+MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/soc-jack.c b/linux/sound/soc/soc-jack.c
new file mode 100644
index 0000000..8a0a920
--- /dev/null
+++ b/linux/sound/soc/soc-jack.c
@@ -0,0 +1,319 @@
+/*
+ * soc-jack.c  --  ALSA SoC jack handling
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+
+/**
+ * snd_soc_jack_new - Create a new jack
+ * @card:  ASoC card
+ * @id:    an identifying string for this jack
+ * @type:  a bitmask of enum snd_jack_type values that can be detected by
+ *         this jack
+ * @jack:  structure to use for the jack
+ *
+ * Creates a new jack object.
+ *
+ * Returns zero if successful, or a negative error code on failure.
+ * On success jack will be initialised.
+ */
+int snd_soc_jack_new(struct snd_soc_codec *codec, const char *id, int type,
+		     struct snd_soc_jack *jack)
+{
+	jack->codec = codec;
+	INIT_LIST_HEAD(&jack->pins);
+	BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier);
+
+	return snd_jack_new(codec->card->snd_card, id, type, &jack->jack);
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_new);
+
+/**
+ * snd_soc_jack_report - Report the current status for a jack
+ *
+ * @jack:   the jack
+ * @status: a bitmask of enum snd_jack_type values that are currently detected.
+ * @mask:   a bitmask of enum snd_jack_type values that being reported.
+ *
+ * If configured using snd_soc_jack_add_pins() then the associated
+ * DAPM pins will be enabled or disabled as appropriate and DAPM
+ * synchronised.
+ *
+ * Note: This function uses mutexes and should be called from a
+ * context which can sleep (such as a workqueue).
+ */
+void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask)
+{
+	struct snd_soc_codec *codec;
+	struct snd_soc_jack_pin *pin;
+	int enable;
+	int oldstatus;
+
+	if (!jack)
+		return;
+
+	codec = jack->codec;
+
+	mutex_lock(&codec->mutex);
+
+	oldstatus = jack->status;
+
+	jack->status &= ~mask;
+	jack->status |= status & mask;
+
+	/* The DAPM sync is expensive enough to be worth skipping.
+	 * However, empty mask means pin synchronization is desired. */
+	if (mask && (jack->status == oldstatus))
+		goto out;
+
+	list_for_each_entry(pin, &jack->pins, list) {
+		enable = pin->mask & jack->status;
+
+		if (pin->invert)
+			enable = !enable;
+
+		if (enable)
+			snd_soc_dapm_enable_pin(codec, pin->pin);
+		else
+			snd_soc_dapm_disable_pin(codec, pin->pin);
+	}
+
+	/* Report before the DAPM sync to help users updating micbias status */
+	blocking_notifier_call_chain(&jack->notifier, status, NULL);
+
+	snd_soc_dapm_sync(codec);
+
+	snd_jack_report(jack->jack, status);
+
+out:
+	mutex_unlock(&codec->mutex);
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_report);
+
+/**
+ * snd_soc_jack_add_pins - Associate DAPM pins with an ASoC jack
+ *
+ * @jack:  ASoC jack
+ * @count: Number of pins
+ * @pins:  Array of pins
+ *
+ * After this function has been called the DAPM pins specified in the
+ * pins array will have their status updated to reflect the current
+ * state of the jack whenever the jack status is updated.
+ */
+int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count,
+			  struct snd_soc_jack_pin *pins)
+{
+	int i;
+
+	for (i = 0; i < count; i++) {
+		if (!pins[i].pin) {
+			printk(KERN_ERR "No name for pin %d\n", i);
+			return -EINVAL;
+		}
+		if (!pins[i].mask) {
+			printk(KERN_ERR "No mask for pin %d (%s)\n", i,
+			       pins[i].pin);
+			return -EINVAL;
+		}
+
+		INIT_LIST_HEAD(&pins[i].list);
+		list_add(&(pins[i].list), &jack->pins);
+	}
+
+	/* Update to reflect the last reported status; canned jack
+	 * implementations are likely to set their state before the
+	 * card has an opportunity to associate pins.
+	 */
+	snd_soc_jack_report(jack, 0, 0);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_add_pins);
+
+/**
+ * snd_soc_jack_notifier_register - Register a notifier for jack status
+ *
+ * @jack:  ASoC jack
+ * @nb:    Notifier block to register
+ *
+ * Register for notification of the current status of the jack.  Note
+ * that it is not possible to report additional jack events in the
+ * callback from the notifier, this is intended to support
+ * applications such as enabling electrical detection only when a
+ * mechanical detection event has occurred.
+ */
+void snd_soc_jack_notifier_register(struct snd_soc_jack *jack,
+				    struct notifier_block *nb)
+{
+	blocking_notifier_chain_register(&jack->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_register);
+
+/**
+ * snd_soc_jack_notifier_unregister - Unregister a notifier for jack status
+ *
+ * @jack:  ASoC jack
+ * @nb:    Notifier block to unregister
+ *
+ * Stop notifying for status changes.
+ */
+void snd_soc_jack_notifier_unregister(struct snd_soc_jack *jack,
+				      struct notifier_block *nb)
+{
+	blocking_notifier_chain_unregister(&jack->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_unregister);
+
+#ifdef CONFIG_GPIOLIB
+/* gpio detect */
+static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio)
+{
+	struct snd_soc_jack *jack = gpio->jack;
+	int enable;
+	int report;
+
+	enable = gpio_get_value(gpio->gpio);
+	if (gpio->invert)
+		enable = !enable;
+
+	if (enable)
+		report = gpio->report;
+	else
+		report = 0;
+
+	if (gpio->jack_status_check)
+		report = gpio->jack_status_check();
+
+	snd_soc_jack_report(jack, report, gpio->report);
+}
+
+/* irq handler for gpio pin */
+static irqreturn_t gpio_handler(int irq, void *data)
+{
+	struct snd_soc_jack_gpio *gpio = data;
+
+	schedule_delayed_work(&gpio->work,
+			      msecs_to_jiffies(gpio->debounce_time));
+
+	return IRQ_HANDLED;
+}
+
+/* gpio work */
+static void gpio_work(struct work_struct *work)
+{
+	struct snd_soc_jack_gpio *gpio;
+
+	gpio = container_of(work, struct snd_soc_jack_gpio, work.work);
+	snd_soc_jack_gpio_detect(gpio);
+}
+
+/**
+ * snd_soc_jack_add_gpios - Associate GPIO pins with an ASoC jack
+ *
+ * @jack:  ASoC jack
+ * @count: number of pins
+ * @gpios: array of gpio pins
+ *
+ * This function will request gpio, set data direction and request irq
+ * for each gpio in the array.
+ */
+int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count,
+			struct snd_soc_jack_gpio *gpios)
+{
+	int i, ret;
+
+	for (i = 0; i < count; i++) {
+		if (!gpio_is_valid(gpios[i].gpio)) {
+			printk(KERN_ERR "Invalid gpio %d\n",
+				gpios[i].gpio);
+			ret = -EINVAL;
+			goto undo;
+		}
+		if (!gpios[i].name) {
+			printk(KERN_ERR "No name for gpio %d\n",
+				gpios[i].gpio);
+			ret = -EINVAL;
+			goto undo;
+		}
+
+		ret = gpio_request(gpios[i].gpio, gpios[i].name);
+		if (ret)
+			goto undo;
+
+		ret = gpio_direction_input(gpios[i].gpio);
+		if (ret)
+			goto err;
+
+		INIT_DELAYED_WORK(&gpios[i].work, gpio_work);
+		gpios[i].jack = jack;
+
+		ret = request_irq(gpio_to_irq(gpios[i].gpio),
+				gpio_handler,
+				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+				jack->codec->dev->driver->name,
+				&gpios[i]);
+		if (ret)
+			goto err;
+
+#ifdef CONFIG_GPIO_SYSFS
+		/* Expose GPIO value over sysfs for diagnostic purposes */
+		gpio_export(gpios[i].gpio, false);
+#endif
+
+		/* Update initial jack status */
+		snd_soc_jack_gpio_detect(&gpios[i]);
+	}
+
+	return 0;
+
+err:
+	gpio_free(gpios[i].gpio);
+undo:
+	snd_soc_jack_free_gpios(jack, i, gpios);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_add_gpios);
+
+/**
+ * snd_soc_jack_free_gpios - Release GPIO pins' resources of an ASoC jack
+ *
+ * @jack:  ASoC jack
+ * @count: number of pins
+ * @gpios: array of gpio pins
+ *
+ * Release gpio and irq resources for gpio pins associated with an ASoC jack.
+ */
+void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count,
+			struct snd_soc_jack_gpio *gpios)
+{
+	int i;
+
+	for (i = 0; i < count; i++) {
+#ifdef CONFIG_GPIO_SYSFS
+		gpio_unexport(gpios[i].gpio);
+#endif
+		free_irq(gpio_to_irq(gpios[i].gpio), &gpios[i]);
+		cancel_delayed_work_sync(&gpios[i].work);
+		gpio_free(gpios[i].gpio);
+		gpios[i].jack = NULL;
+	}
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_free_gpios);
+#endif	/* CONFIG_GPIOLIB */
diff --git a/linux/sound/soc/soc-utils.c b/linux/sound/soc/soc-utils.c
new file mode 100644
index 0000000..1d07b93
--- /dev/null
+++ b/linux/sound/soc/soc-utils.c
@@ -0,0 +1,74 @@
+/*
+ * soc-util.c  --  ALSA SoC Audio Layer utility functions
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *         Liam Girdwood <lrg@slimlogic.co.uk>
+ *         
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots)
+{
+	return sample_size * channels * tdm_slots;
+}
+EXPORT_SYMBOL_GPL(snd_soc_calc_frame_size);
+
+int snd_soc_params_to_frame_size(struct snd_pcm_hw_params *params)
+{
+	int sample_size;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+	case SNDRV_PCM_FORMAT_S16_BE:
+		sample_size = 16;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+	case SNDRV_PCM_FORMAT_S20_3BE:
+		sample_size = 20;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+	case SNDRV_PCM_FORMAT_S24_BE:
+		sample_size = 24;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+	case SNDRV_PCM_FORMAT_S32_BE:
+		sample_size = 32;
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	return snd_soc_calc_frame_size(sample_size, params_channels(params),
+				       1);
+}
+EXPORT_SYMBOL_GPL(snd_soc_params_to_frame_size);
+
+int snd_soc_calc_bclk(int fs, int sample_size, int channels, int tdm_slots)
+{
+	return fs * snd_soc_calc_frame_size(sample_size, channels, tdm_slots);
+}
+EXPORT_SYMBOL_GPL(snd_soc_calc_bclk);
+
+int snd_soc_params_to_bclk(struct snd_pcm_hw_params *params)
+{
+	int ret;
+
+	ret = snd_soc_params_to_frame_size(params);
+
+	if (ret > 0)
+		return ret * params_rate(params);
+	else
+		return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_params_to_bclk);
diff --git a/linux/sound/soc/txx9/Kconfig b/linux/sound/soc/txx9/Kconfig
new file mode 100644
index 0000000..ebc9327
--- /dev/null
+++ b/linux/sound/soc/txx9/Kconfig
@@ -0,0 +1,29 @@
+##
+## TXx9 ACLC
+##
+config SND_SOC_TXX9ACLC
+	tristate "SoC Audio for TXx9"
+	depends on HAS_TXX9_ACLC && TXX9_DMAC
+	help
+	  This option enables support for the AC Link Controllers in TXx9 SoC.
+
+config HAS_TXX9_ACLC
+	bool
+
+config SND_SOC_TXX9ACLC_AC97
+	tristate
+	select AC97_BUS
+	select SND_AC97_CODEC
+	select SND_SOC_AC97_BUS
+
+
+##
+## Boards
+##
+config SND_SOC_TXX9ACLC_GENERIC
+	tristate "Generic TXx9 ACLC sound machine"
+	depends on SND_SOC_TXX9ACLC
+	select SND_SOC_TXX9ACLC_AC97
+	select SND_SOC_AC97_CODEC
+	help
+	  This is a generic AC97 sound machine for use in TXx9 based systems.
diff --git a/linux/sound/soc/txx9/Makefile b/linux/sound/soc/txx9/Makefile
new file mode 100644
index 0000000..551f16c
--- /dev/null
+++ b/linux/sound/soc/txx9/Makefile
@@ -0,0 +1,11 @@
+# Platform
+snd-soc-txx9aclc-objs := txx9aclc.o
+snd-soc-txx9aclc-ac97-objs := txx9aclc-ac97.o
+
+obj-$(CONFIG_SND_SOC_TXX9ACLC) += snd-soc-txx9aclc.o
+obj-$(CONFIG_SND_SOC_TXX9ACLC_AC97) += snd-soc-txx9aclc-ac97.o
+
+# Machine
+snd-soc-txx9aclc-generic-objs := txx9aclc-generic.o
+
+obj-$(CONFIG_SND_SOC_TXX9ACLC_GENERIC) += snd-soc-txx9aclc-generic.o
diff --git a/linux/sound/soc/txx9/txx9aclc-ac97.c b/linux/sound/soc/txx9/txx9aclc-ac97.c
new file mode 100644
index 0000000..743d07b
--- /dev/null
+++ b/linux/sound/soc/txx9/txx9aclc-ac97.c
@@ -0,0 +1,242 @@
+/*
+ * TXx9 ACLC AC97 driver
+ *
+ * Copyright (C) 2009 Atsushi Nemoto
+ *
+ * Based on RBTX49xx patch from CELF patch archive.
+ * (C) Copyright TOSHIBA CORPORATION 2004-2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/gfp.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include "txx9aclc.h"
+
+#define AC97_DIR	\
+	(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
+
+#define AC97_RATES	\
+	SNDRV_PCM_RATE_8000_48000
+
+#ifdef __BIG_ENDIAN
+#define AC97_FMTS	SNDRV_PCM_FMTBIT_S16_BE
+#else
+#define AC97_FMTS	SNDRV_PCM_FMTBIT_S16_LE
+#endif
+
+static DECLARE_WAIT_QUEUE_HEAD(ac97_waitq);
+
+/* REVISIT: How to find txx9aclc_drvdata from snd_ac97? */
+static struct txx9aclc_plat_drvdata *txx9aclc_drvdata;
+
+static int txx9aclc_regready(struct txx9aclc_plat_drvdata *drvdata)
+{
+	return __raw_readl(drvdata->base + ACINTSTS) & ACINT_REGACCRDY;
+}
+
+/* AC97 controller reads codec register */
+static unsigned short txx9aclc_ac97_read(struct snd_ac97 *ac97,
+					 unsigned short reg)
+{
+	struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
+	void __iomem *base = drvdata->base;
+	u32 dat;
+
+	if (!(__raw_readl(base + ACINTSTS) & ACINT_CODECRDY(ac97->num)))
+		return 0xffff;
+	reg |= ac97->num << 7;
+	dat = (reg << ACREGACC_REG_SHIFT) | ACREGACC_READ;
+	__raw_writel(dat, base + ACREGACC);
+	__raw_writel(ACINT_REGACCRDY, base + ACINTEN);
+	if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(txx9aclc_drvdata), HZ)) {
+		__raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
+		printk(KERN_ERR "ac97 read timeout (reg %#x)\n", reg);
+		dat = 0xffff;
+		goto done;
+	}
+	dat = __raw_readl(base + ACREGACC);
+	if (((dat >> ACREGACC_REG_SHIFT) & 0xff) != reg) {
+		printk(KERN_ERR "reg mismatch %x with %x\n",
+			dat, reg);
+		dat = 0xffff;
+		goto done;
+	}
+	dat = (dat >> ACREGACC_DAT_SHIFT) & 0xffff;
+done:
+	__raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
+	return dat;
+}
+
+/* AC97 controller writes to codec register */
+static void txx9aclc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+				unsigned short val)
+{
+	struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
+	void __iomem *base = drvdata->base;
+
+	__raw_writel(((reg | (ac97->num << 7)) << ACREGACC_REG_SHIFT) |
+		     (val << ACREGACC_DAT_SHIFT),
+		     base + ACREGACC);
+	__raw_writel(ACINT_REGACCRDY, base + ACINTEN);
+	if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(txx9aclc_drvdata), HZ)) {
+		printk(KERN_ERR
+			"ac97 write timeout (reg %#x)\n", reg);
+	}
+	__raw_writel(ACINT_REGACCRDY, base + ACINTDIS);
+}
+
+static void txx9aclc_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+	struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
+	void __iomem *base = drvdata->base;
+	u32 ready = ACINT_CODECRDY(ac97->num) | ACINT_REGACCRDY;
+
+	__raw_writel(ACCTL_ENLINK, base + ACCTLDIS);
+	mmiowb();
+	udelay(1);
+	__raw_writel(ACCTL_ENLINK, base + ACCTLEN);
+	/* wait for primary codec ready status */
+	__raw_writel(ready, base + ACINTEN);
+	if (!wait_event_timeout(ac97_waitq,
+				(__raw_readl(base + ACINTSTS) & ready) == ready,
+				HZ)) {
+		dev_err(&ac97->dev, "primary codec is not ready "
+			"(status %#x)\n",
+			__raw_readl(base + ACINTSTS));
+	}
+	__raw_writel(ACINT_REGACCRDY, base + ACINTSTS);
+	__raw_writel(ready, base + ACINTDIS);
+}
+
+/* AC97 controller operations */
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read		= txx9aclc_ac97_read,
+	.write		= txx9aclc_ac97_write,
+	.reset		= txx9aclc_ac97_cold_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static irqreturn_t txx9aclc_ac97_irq(int irq, void *dev_id)
+{
+	struct txx9aclc_plat_drvdata *drvdata = dev_id;
+	void __iomem *base = drvdata->base;
+
+	__raw_writel(__raw_readl(base + ACINTMSTS), base + ACINTDIS);
+	wake_up(&ac97_waitq);
+	return IRQ_HANDLED;
+}
+
+static int txx9aclc_ac97_probe(struct snd_soc_dai *dai)
+{
+	txx9aclc_drvdata = snd_soc_dai_get_drvdata(dai);
+	return 0;
+}
+
+static int txx9aclc_ac97_remove(struct snd_soc_dai *dai)
+{
+	struct txx9aclc_plat_drvdata *drvdata = snd_soc_dai_get_drvdata(dai);
+
+	/* disable AC-link */
+	__raw_writel(ACCTL_ENLINK, drvdata->base + ACCTLDIS);
+	txx9aclc_drvdata = NULL;
+	return 0;
+}
+
+static struct snd_soc_dai_driver txx9aclc_ac97_dai = {
+	.ac97_control		= 1,
+	.probe			= txx9aclc_ac97_probe,
+	.remove			= txx9aclc_ac97_remove,
+	.playback = {
+		.rates		= AC97_RATES,
+		.formats	= AC97_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.capture = {
+		.rates		= AC97_RATES,
+		.formats	= AC97_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+};
+
+static int __devinit txx9aclc_ac97_dev_probe(struct platform_device *pdev)
+{
+	struct txx9aclc_plat_drvdata *drvdata;
+	struct resource *r;
+	int err;
+	int irq;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r)
+		return -EBUSY;
+
+	if (!devm_request_mem_region(&pdev->dev, r->start, resource_size(r),
+				     dev_name(&pdev->dev)))
+		return -EBUSY;
+
+	drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, drvdata);
+	drvdata->physbase = r->start;
+	if (sizeof(drvdata->physbase) > sizeof(r->start) &&
+	    r->start >= TXX9_DIRECTMAP_BASE &&
+	    r->start < TXX9_DIRECTMAP_BASE + 0x400000)
+		drvdata->physbase |= 0xf00000000ull;
+	drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
+	if (!drvdata->base)
+		return -EBUSY;
+	err = devm_request_irq(&pdev->dev, irq, txx9aclc_ac97_irq,
+			       IRQF_DISABLED, dev_name(&pdev->dev), drvdata);
+	if (err < 0)
+		return err;
+
+	return snd_soc_register_dai(&pdev->dev, &txx9aclc_ac97_dai);
+}
+
+static int __devexit txx9aclc_ac97_dev_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_dai(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver txx9aclc_ac97_driver = {
+	.probe		= txx9aclc_ac97_dev_probe,
+	.remove		= __devexit_p(txx9aclc_ac97_dev_remove),
+	.driver		= {
+		.name	= "txx9aclc-ac97",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init txx9aclc_ac97_init(void)
+{
+	return platform_driver_register(&txx9aclc_ac97_driver);
+}
+
+static void __exit txx9aclc_ac97_exit(void)
+{
+	platform_driver_unregister(&txx9aclc_ac97_driver);
+}
+
+module_init(txx9aclc_ac97_init);
+module_exit(txx9aclc_ac97_exit);
+
+MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
+MODULE_DESCRIPTION("TXx9 ACLC AC97 driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:txx9aclc-ac97");
diff --git a/linux/sound/soc/txx9/txx9aclc-generic.c b/linux/sound/soc/txx9/txx9aclc-generic.c
new file mode 100644
index 0000000..6770e71
--- /dev/null
+++ b/linux/sound/soc/txx9/txx9aclc-generic.c
@@ -0,0 +1,89 @@
+/*
+ * Generic TXx9 ACLC machine driver
+ *
+ * Copyright (C) 2009 Atsushi Nemoto
+ *
+ * Based on RBTX49xx patch from CELF patch archive.
+ * (C) Copyright TOSHIBA CORPORATION 2004-2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This is a very generic AC97 sound machine driver for boards which
+ * have (AC97) audio at ACLC (e.g. RBTX49XX boards).
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include "txx9aclc.h"
+
+static struct snd_soc_dai_link txx9aclc_generic_dai = {
+	.name = "AC97",
+	.stream_name = "AC97 HiFi",
+	.cpu_dai_name = "txx9aclc-ac97",
+	.codec_dai_name = "ac97-hifi",
+	.platform_name	= "txx9aclc-pcm-audio",
+	.codec_name	= "ac97-codec",
+};
+
+static struct snd_soc_card txx9aclc_generic_card = {
+	.name		= "Generic TXx9 ACLC Audio",
+	.dai_link	= &txx9aclc_generic_dai,
+	.num_links	= 1,
+};
+
+static struct platform_device *soc_pdev;
+
+static int __init txx9aclc_generic_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	soc_pdev = platform_device_alloc("soc-audio", -1);
+	if (!soc_pdev)
+		return -ENOMEM;
+	platform_set_drvdata(soc_pdev, &txx9aclc_generic_card);
+	ret = platform_device_add(soc_pdev);
+	if (ret) {
+		platform_device_put(soc_pdev);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int __exit txx9aclc_generic_remove(struct platform_device *pdev)
+{
+	platform_device_unregister(soc_pdev);
+	return 0;
+}
+
+static struct platform_driver txx9aclc_generic_driver = {
+	.remove = txx9aclc_generic_remove,
+	.driver = {
+		.name = "txx9aclc-generic",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init txx9aclc_generic_init(void)
+{
+	return platform_driver_probe(&txx9aclc_generic_driver,
+				     txx9aclc_generic_probe);
+}
+
+static void __exit txx9aclc_generic_exit(void)
+{
+	platform_driver_unregister(&txx9aclc_generic_driver);
+}
+
+module_init(txx9aclc_generic_init);
+module_exit(txx9aclc_generic_exit);
+
+MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
+MODULE_DESCRIPTION("Generic TXx9 ACLC ALSA SoC audio driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:txx9aclc-generic");
diff --git a/linux/sound/soc/txx9/txx9aclc.c b/linux/sound/soc/txx9/txx9aclc.c
new file mode 100644
index 0000000..f4aa4e0
--- /dev/null
+++ b/linux/sound/soc/txx9/txx9aclc.c
@@ -0,0 +1,453 @@
+/*
+ * Generic TXx9 ACLC platform driver
+ *
+ * Copyright (C) 2009 Atsushi Nemoto
+ *
+ * Based on RBTX49xx patch from CELF patch archive.
+ * (C) Copyright TOSHIBA CORPORATION 2004-2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "txx9aclc.h"
+
+static struct txx9aclc_soc_device {
+	struct txx9aclc_dmadata dmadata[2];
+} txx9aclc_soc_device;
+
+/* REVISIT: How to find txx9aclc_drvdata from snd_ac97? */
+static struct txx9aclc_plat_drvdata *txx9aclc_drvdata;
+
+static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev,
+			     struct txx9aclc_dmadata *dmadata);
+
+static const struct snd_pcm_hardware txx9aclc_pcm_hardware = {
+	/*
+	 * REVISIT: SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
+	 * needs more works for noncoherent MIPS.
+	 */
+	.info		  = SNDRV_PCM_INFO_INTERLEAVED |
+			    SNDRV_PCM_INFO_BATCH |
+			    SNDRV_PCM_INFO_PAUSE,
+#ifdef __BIG_ENDIAN
+	.formats	  = SNDRV_PCM_FMTBIT_S16_BE,
+#else
+	.formats	  = SNDRV_PCM_FMTBIT_S16_LE,
+#endif
+	.period_bytes_min = 1024,
+	.period_bytes_max = 8 * 1024,
+	.periods_min	  = 2,
+	.periods_max	  = 4096,
+	.buffer_bytes_max = 32 * 1024,
+};
+
+static int txx9aclc_pcm_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct txx9aclc_dmadata *dmadata = runtime->private_data;
+	int ret;
+
+	ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(rtd->platform->dev,
+		"runtime->dma_area = %#lx dma_addr = %#lx dma_bytes = %zd "
+		"runtime->min_align %ld\n",
+		(unsigned long)runtime->dma_area,
+		(unsigned long)runtime->dma_addr, runtime->dma_bytes,
+		runtime->min_align);
+	dev_dbg(rtd->platform->dev,
+		"periods %d period_bytes %d stream %d\n",
+		params_periods(params), params_period_bytes(params),
+		substream->stream);
+
+	dmadata->substream = substream;
+	dmadata->pos = 0;
+	return 0;
+}
+
+static int txx9aclc_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int txx9aclc_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct txx9aclc_dmadata *dmadata = runtime->private_data;
+
+	dmadata->dma_addr = runtime->dma_addr;
+	dmadata->buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
+	dmadata->period_bytes = snd_pcm_lib_period_bytes(substream);
+
+	if (dmadata->buffer_bytes == dmadata->period_bytes) {
+		dmadata->frag_bytes = dmadata->period_bytes >> 1;
+		dmadata->frags = 2;
+	} else {
+		dmadata->frag_bytes = dmadata->period_bytes;
+		dmadata->frags = dmadata->buffer_bytes / dmadata->period_bytes;
+	}
+	dmadata->frag_count = 0;
+	dmadata->pos = 0;
+	return 0;
+}
+
+static void txx9aclc_dma_complete(void *arg)
+{
+	struct txx9aclc_dmadata *dmadata = arg;
+	unsigned long flags;
+
+	/* dma completion handler cannot submit new operations */
+	spin_lock_irqsave(&dmadata->dma_lock, flags);
+	if (dmadata->frag_count >= 0) {
+		dmadata->dmacount--;
+		BUG_ON(dmadata->dmacount < 0);
+		tasklet_schedule(&dmadata->tasklet);
+	}
+	spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+}
+
+static struct dma_async_tx_descriptor *
+txx9aclc_dma_submit(struct txx9aclc_dmadata *dmadata, dma_addr_t buf_dma_addr)
+{
+	struct dma_chan *chan = dmadata->dma_chan;
+	struct dma_async_tx_descriptor *desc;
+	struct scatterlist sg;
+
+	sg_init_table(&sg, 1);
+	sg_set_page(&sg, pfn_to_page(PFN_DOWN(buf_dma_addr)),
+		    dmadata->frag_bytes, buf_dma_addr & (PAGE_SIZE - 1));
+	sg_dma_address(&sg) = buf_dma_addr;
+	desc = chan->device->device_prep_slave_sg(chan, &sg, 1,
+		dmadata->substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+		DMA_TO_DEVICE : DMA_FROM_DEVICE,
+		DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	if (!desc) {
+		dev_err(&chan->dev->device, "cannot prepare slave dma\n");
+		return NULL;
+	}
+	desc->callback = txx9aclc_dma_complete;
+	desc->callback_param = dmadata;
+	desc->tx_submit(desc);
+	return desc;
+}
+
+#define NR_DMA_CHAIN		2
+
+static void txx9aclc_dma_tasklet(unsigned long data)
+{
+	struct txx9aclc_dmadata *dmadata = (struct txx9aclc_dmadata *)data;
+	struct dma_chan *chan = dmadata->dma_chan;
+	struct dma_async_tx_descriptor *desc;
+	struct snd_pcm_substream *substream = dmadata->substream;
+	u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+		ACCTL_AUDODMA : ACCTL_AUDIDMA;
+	int i;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dmadata->dma_lock, flags);
+	if (dmadata->frag_count < 0) {
+		struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
+		void __iomem *base = drvdata->base;
+
+		spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+		chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
+		/* first time */
+		for (i = 0; i < NR_DMA_CHAIN; i++) {
+			desc = txx9aclc_dma_submit(dmadata,
+				dmadata->dma_addr + i * dmadata->frag_bytes);
+			if (!desc)
+				return;
+		}
+		dmadata->dmacount = NR_DMA_CHAIN;
+		chan->device->device_issue_pending(chan);
+		spin_lock_irqsave(&dmadata->dma_lock, flags);
+		__raw_writel(ctlbit, base + ACCTLEN);
+		dmadata->frag_count = NR_DMA_CHAIN % dmadata->frags;
+		spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+		return;
+	}
+	BUG_ON(dmadata->dmacount >= NR_DMA_CHAIN);
+	while (dmadata->dmacount < NR_DMA_CHAIN) {
+		dmadata->dmacount++;
+		spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+		desc = txx9aclc_dma_submit(dmadata,
+			dmadata->dma_addr +
+			dmadata->frag_count * dmadata->frag_bytes);
+		if (!desc)
+			return;
+		chan->device->device_issue_pending(chan);
+
+		spin_lock_irqsave(&dmadata->dma_lock, flags);
+		dmadata->frag_count++;
+		dmadata->frag_count %= dmadata->frags;
+		dmadata->pos += dmadata->frag_bytes;
+		dmadata->pos %= dmadata->buffer_bytes;
+		if ((dmadata->frag_count * dmadata->frag_bytes) %
+		    dmadata->period_bytes == 0)
+			snd_pcm_period_elapsed(substream);
+	}
+	spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+}
+
+static int txx9aclc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct txx9aclc_dmadata *dmadata = substream->runtime->private_data;
+	struct txx9aclc_plat_drvdata *drvdata =txx9aclc_drvdata;
+	void __iomem *base = drvdata->base;
+	unsigned long flags;
+	int ret = 0;
+	u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+		ACCTL_AUDODMA : ACCTL_AUDIDMA;
+
+	spin_lock_irqsave(&dmadata->dma_lock, flags);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		dmadata->frag_count = -1;
+		tasklet_schedule(&dmadata->tasklet);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		__raw_writel(ctlbit, base + ACCTLDIS);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		__raw_writel(ctlbit, base + ACCTLEN);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	spin_unlock_irqrestore(&dmadata->dma_lock, flags);
+	return ret;
+}
+
+static snd_pcm_uframes_t
+txx9aclc_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct txx9aclc_dmadata *dmadata = substream->runtime->private_data;
+
+	return bytes_to_frames(substream->runtime, dmadata->pos);
+}
+
+static int txx9aclc_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct txx9aclc_soc_device *dev = &txx9aclc_soc_device;
+	struct txx9aclc_dmadata *dmadata = &dev->dmadata[substream->stream];
+	int ret;
+
+	ret = snd_soc_set_runtime_hwparams(substream, &txx9aclc_pcm_hardware);
+	if (ret)
+		return ret;
+	/* ensure that buffer size is a multiple of period size */
+	ret = snd_pcm_hw_constraint_integer(substream->runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0)
+		return ret;
+	substream->runtime->private_data = dmadata;
+	return 0;
+}
+
+static int txx9aclc_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct txx9aclc_dmadata *dmadata = substream->runtime->private_data;
+	struct dma_chan *chan = dmadata->dma_chan;
+
+	dmadata->frag_count = -1;
+	chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
+	return 0;
+}
+
+static struct snd_pcm_ops txx9aclc_pcm_ops = {
+	.open		= txx9aclc_pcm_open,
+	.close		= txx9aclc_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= txx9aclc_pcm_hw_params,
+	.hw_free	= txx9aclc_pcm_hw_free,
+	.prepare	= txx9aclc_pcm_prepare,
+	.trigger	= txx9aclc_pcm_trigger,
+	.pointer	= txx9aclc_pcm_pointer,
+};
+
+static void txx9aclc_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int txx9aclc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+			    struct snd_pcm *pcm)
+{
+	struct platform_device *pdev = to_platform_device(dai->platform->dev);
+	struct txx9aclc_soc_device *dev;
+	struct resource *r;
+	int i;
+	int ret;
+
+	/* at this point onwards the AC97 component has probed and this will be valid */
+	dev = snd_soc_dai_get_drvdata(dai);
+
+	dev->dmadata[0].stream = SNDRV_PCM_STREAM_PLAYBACK;
+	dev->dmadata[1].stream = SNDRV_PCM_STREAM_CAPTURE;
+	for (i = 0; i < 2; i++) {
+		r = platform_get_resource(pdev, IORESOURCE_DMA, i);
+		if (!r) {
+			ret = -EBUSY;
+			goto exit;
+		}
+		dev->dmadata[i].dma_res = r;
+		ret = txx9aclc_dma_init(dev, &dev->dmadata[i]);
+		if (ret)
+			goto exit;
+	}
+	return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+		card->dev, 64 * 1024, 4 * 1024 * 1024);
+
+exit:
+	for (i = 0; i < 2; i++) {
+		if (dev->dmadata[i].dma_chan)
+			dma_release_channel(dev->dmadata[i].dma_chan);
+		dev->dmadata[i].dma_chan = NULL;
+	}
+	return ret;
+}
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+	struct txx9aclc_dmadata *dmadata = param;
+	char *devname;
+	bool found = false;
+
+	devname = kasprintf(GFP_KERNEL, "%s.%d", dmadata->dma_res->name,
+		(int)dmadata->dma_res->start);
+	if (strcmp(dev_name(chan->device->dev), devname) == 0) {
+		chan->private = &dmadata->dma_slave;
+		found = true;
+	}
+	kfree(devname);
+	return found;
+}
+
+static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev,
+			     struct txx9aclc_dmadata *dmadata)
+{
+	struct txx9aclc_plat_drvdata *drvdata =txx9aclc_drvdata;
+	struct txx9dmac_slave *ds = &dmadata->dma_slave;
+	dma_cap_mask_t mask;
+
+	spin_lock_init(&dmadata->dma_lock);
+
+	ds->reg_width = sizeof(u32);
+	if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		ds->tx_reg = drvdata->physbase + ACAUDODAT;
+		ds->rx_reg = 0;
+	} else {
+		ds->tx_reg = 0;
+		ds->rx_reg = drvdata->physbase + ACAUDIDAT;
+	}
+
+	/* Try to grab a DMA channel */
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+	dmadata->dma_chan = dma_request_channel(mask, filter, dmadata);
+	if (!dmadata->dma_chan) {
+		printk(KERN_ERR
+			"DMA channel for %s is not available\n",
+			dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+			"playback" : "capture");
+		return -EBUSY;
+	}
+	tasklet_init(&dmadata->tasklet, txx9aclc_dma_tasklet,
+		     (unsigned long)dmadata);
+	return 0;
+}
+
+static int txx9aclc_pcm_probe(struct snd_soc_platform *platform)
+{
+	snd_soc_platform_set_drvdata(platform, &txx9aclc_soc_device);
+	return 0;
+}
+
+static int txx9aclc_pcm_remove(struct snd_soc_platform *platform)
+{
+	struct txx9aclc_soc_device *dev = snd_soc_platform_get_drvdata(platform);
+	struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata;
+	void __iomem *base = drvdata->base;
+	int i;
+
+	/* disable all FIFO DMAs */
+	__raw_writel(ACCTL_AUDODMA | ACCTL_AUDIDMA, base + ACCTLDIS);
+	/* dummy R/W to clear pending DMAREQ if any */
+	__raw_writel(__raw_readl(base + ACAUDIDAT), base + ACAUDODAT);
+
+	for (i = 0; i < 2; i++) {
+		struct txx9aclc_dmadata *dmadata = &dev->dmadata[i];
+		struct dma_chan *chan = dmadata->dma_chan;
+		if (chan) {
+			dmadata->frag_count = -1;
+			chan->device->device_control(chan,
+						     DMA_TERMINATE_ALL, 0);
+			dma_release_channel(chan);
+		}
+		dev->dmadata[i].dma_chan = NULL;
+	}
+	return 0;
+}
+
+static struct snd_soc_platform_driver txx9aclc_soc_platform = {
+	.probe		= txx9aclc_pcm_probe,
+	.remove		= txx9aclc_pcm_remove,
+	.ops		= &txx9aclc_pcm_ops,
+	.pcm_new	= txx9aclc_pcm_new,
+	.pcm_free	= txx9aclc_pcm_free_dma_buffers,
+};
+
+static int __devinit txx9aclc_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &txx9aclc_soc_platform);
+}
+
+static int __devexit txx9aclc_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver txx9aclc_pcm_driver = {
+	.driver = {
+			.name = "txx9aclc-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+
+	.probe = txx9aclc_soc_platform_probe,
+	.remove = __devexit_p(txx9aclc_soc_platform_remove),
+};
+
+static int __init snd_txx9aclc_pcm_init(void)
+{
+	return platform_driver_register(&txx9aclc_pcm_driver);
+}
+module_init(snd_txx9aclc_pcm_init);
+
+static void __exit snd_txx9aclc_pcm_exit(void)
+{
+	platform_driver_unregister(&txx9aclc_pcm_driver);
+}
+module_exit(snd_txx9aclc_pcm_exit);
+
+MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
+MODULE_DESCRIPTION("TXx9 ACLC Audio DMA driver");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/soc/txx9/txx9aclc.h b/linux/sound/soc/txx9/txx9aclc.h
new file mode 100644
index 0000000..9c2de84
--- /dev/null
+++ b/linux/sound/soc/txx9/txx9aclc.h
@@ -0,0 +1,74 @@
+/*
+ * TXx9 SoC AC Link Controller
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __TXX9ACLC_H
+#define __TXX9ACLC_H
+
+#include <linux/interrupt.h>
+#include <asm/txx9/dmac.h>
+
+#define ACCTLEN			0x00	/* control enable */
+#define ACCTLDIS		0x04	/* control disable */
+#define   ACCTL_ENLINK		0x00000001	/* enable/disable AC-link */
+#define   ACCTL_AUDODMA		0x00000100	/* AUDODMA enable/disable */
+#define   ACCTL_AUDIDMA		0x00001000	/* AUDIDMA enable/disable */
+#define   ACCTL_AUDOEHLT	0x00010000	/* AUDO error halt
+						   enable/disable */
+#define   ACCTL_AUDIEHLT	0x00100000	/* AUDI error halt
+						   enable/disable */
+#define ACREGACC		0x08	/* codec register access */
+#define   ACREGACC_DAT_SHIFT	0	/* data field */
+#define   ACREGACC_REG_SHIFT	16	/* address field */
+#define   ACREGACC_CODECID_SHIFT	24	/* CODEC ID field */
+#define   ACREGACC_READ		0x80000000	/* CODEC read */
+#define   ACREGACC_WRITE	0x00000000	/* CODEC write */
+#define ACINTSTS		0x10	/* interrupt status */
+#define ACINTMSTS		0x14	/* interrupt masked status */
+#define ACINTEN			0x18	/* interrupt enable */
+#define ACINTDIS		0x1c	/* interrupt disable */
+#define   ACINT_CODECRDY(n)	(0x00000001 << (n))	/* CODECn ready */
+#define   ACINT_REGACCRDY	0x00000010	/* ACREGACC ready */
+#define   ACINT_AUDOERR		0x00000100	/* AUDO underrun error */
+#define   ACINT_AUDIERR		0x00001000	/* AUDI overrun error */
+#define ACDMASTS		0x80	/* DMA request status */
+#define   ACDMA_AUDO		0x00000001	/* AUDODMA pending */
+#define   ACDMA_AUDI		0x00000010	/* AUDIDMA pending */
+#define ACAUDODAT		0xa0	/* audio out data */
+#define ACAUDIDAT		0xb0	/* audio in data */
+#define ACREVID			0xfc	/* revision ID */
+
+struct txx9aclc_dmadata {
+	struct resource *dma_res;
+	struct txx9dmac_slave dma_slave;
+	struct dma_chan *dma_chan;
+	struct tasklet_struct tasklet;
+	spinlock_t dma_lock;
+	int stream; /* SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE */
+	struct snd_pcm_substream *substream;
+	unsigned long pos;
+	dma_addr_t dma_addr;
+	unsigned long buffer_bytes;
+	unsigned long period_bytes;
+	unsigned long frag_bytes;
+	int frags;
+	int frag_count;
+	int dmacount;
+};
+
+struct txx9aclc_plat_drvdata {
+	void __iomem *base;
+	u64 physbase;
+};
+
+static inline struct txx9aclc_plat_drvdata *txx9aclc_get_plat_drvdata(
+	struct snd_soc_dai *dai)
+{
+	return dev_get_drvdata(dai->dev);
+}
+
+#endif /* __TXX9ACLC_H */
diff --git a/linux/sound/sound_core.c b/linux/sound/sound_core.c
new file mode 100644
index 0000000..5580ace
--- /dev/null
+++ b/linux/sound/sound_core.c
@@ -0,0 +1,667 @@
+/*
+ *	Sound core.  This file is composed of two parts.  sound_class
+ *	which is common to both OSS and ALSA and OSS sound core which
+ *	is used OSS or emulation of it.
+ */
+
+/*
+ * First, the common part.
+ */
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kdev_t.h>
+#include <linux/major.h>
+#include <sound/core.h>
+
+#ifdef CONFIG_SOUND_OSS_CORE
+static int __init init_oss_soundcore(void);
+static void cleanup_oss_soundcore(void);
+#else
+static inline int init_oss_soundcore(void)	{ return 0; }
+static inline void cleanup_oss_soundcore(void)	{ }
+#endif
+
+struct class *sound_class;
+EXPORT_SYMBOL(sound_class);
+
+MODULE_DESCRIPTION("Core sound module");
+MODULE_AUTHOR("Alan Cox");
+MODULE_LICENSE("GPL");
+
+static char *sound_devnode(struct device *dev, mode_t *mode)
+{
+	if (MAJOR(dev->devt) == SOUND_MAJOR)
+		return NULL;
+	return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev));
+}
+
+static int __init init_soundcore(void)
+{
+	int rc;
+
+	rc = init_oss_soundcore();
+	if (rc)
+		return rc;
+
+	sound_class = class_create(THIS_MODULE, "sound");
+	if (IS_ERR(sound_class)) {
+		cleanup_oss_soundcore();
+		return PTR_ERR(sound_class);
+	}
+
+	sound_class->devnode = sound_devnode;
+
+	return 0;
+}
+
+static void __exit cleanup_soundcore(void)
+{
+	cleanup_oss_soundcore();
+	class_destroy(sound_class);
+}
+
+subsys_initcall(init_soundcore);
+module_exit(cleanup_soundcore);
+
+
+#ifdef CONFIG_SOUND_OSS_CORE
+/*
+ *	OSS sound core handling. Breaks out sound functions to submodules
+ *	
+ *	Author:		Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ *	Fixes:
+ *
+ *
+ *	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.
+ *
+ *                         --------------------
+ * 
+ *	Top level handler for the sound subsystem. Various devices can
+ *	plug into this. The fact they don't all go via OSS doesn't mean 
+ *	they don't have to implement the OSS API. There is a lot of logic
+ *	to keeping much of the OSS weight out of the code in a compatibility
+ *	module, but it's up to the driver to rember to load it...
+ *
+ *	The code provides a set of functions for registration of devices
+ *	by type. This is done rather than providing a single call so that
+ *	we can hide any future changes in the internals (eg when we go to
+ *	32bit dev_t) from the modules and their interface.
+ *
+ *	Secondly we need to allocate the dsp, dsp16 and audio devices as
+ *	one. Thus we misuse the chains a bit to simplify this.
+ *
+ *	Thirdly to make it more fun and for 2.3.x and above we do all
+ *	of this using fine grained locking.
+ *
+ *	FIXME: we have to resolve modules and fine grained load/unload
+ *	locking at some point in 2.3.x.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sound.h>
+#include <linux/kmod.h>
+
+#define SOUND_STEP 16
+
+struct sound_unit
+{
+	int unit_minor;
+	const struct file_operations *unit_fops;
+	struct sound_unit *next;
+	char name[32];
+};
+
+#ifdef CONFIG_SOUND_MSNDCLAS
+extern int msnd_classic_init(void);
+#endif
+#ifdef CONFIG_SOUND_MSNDPIN
+extern int msnd_pinnacle_init(void);
+#endif
+
+/*
+ * By default, OSS sound_core claims full legacy minor range (0-255)
+ * of SOUND_MAJOR to trap open attempts to any sound minor and
+ * requests modules using custom sound-slot/service-* module aliases.
+ * The only benefit of doing this is allowing use of custom module
+ * aliases instead of the standard char-major-* ones.  This behavior
+ * prevents alternative OSS implementation and is scheduled to be
+ * removed.
+ *
+ * CONFIG_SOUND_OSS_CORE_PRECLAIM and soundcore.preclaim_oss kernel
+ * parameter are added to allow distros and developers to try and
+ * switch to alternative implementations without needing to rebuild
+ * the kernel in the meantime.  If preclaim_oss is non-zero, the
+ * kernel will behave the same as before.  All SOUND_MAJOR minors are
+ * preclaimed and the custom module aliases along with standard chrdev
+ * ones are emitted if a missing device is opened.  If preclaim_oss is
+ * zero, sound_core only grabs what's actually in use and for missing
+ * devices only the standard chrdev aliases are requested.
+ *
+ * All these clutters are scheduled to be removed along with
+ * sound-slot/service-* module aliases.  Please take a look at
+ * feature-removal-schedule.txt for details.
+ */
+#ifdef CONFIG_SOUND_OSS_CORE_PRECLAIM
+static int preclaim_oss = 1;
+#else
+static int preclaim_oss = 0;
+#endif
+
+module_param(preclaim_oss, int, 0444);
+
+static int soundcore_open(struct inode *, struct file *);
+
+static const struct file_operations soundcore_fops =
+{
+	/* We must have an owner or the module locking fails */
+	.owner	= THIS_MODULE,
+	.open	= soundcore_open,
+	.llseek = noop_llseek,
+};
+
+/*
+ *	Low level list operator. Scan the ordered list, find a hole and
+ *	join into it. Called with the lock asserted
+ */
+
+static int __sound_insert_unit(struct sound_unit * s, struct sound_unit **list, const struct file_operations *fops, int index, int low, int top)
+{
+	int n=low;
+
+	if (index < 0) {	/* first free */
+
+		while (*list && (*list)->unit_minor<n)
+			list=&((*list)->next);
+
+		while(n<top)
+		{
+			/* Found a hole ? */
+			if(*list==NULL || (*list)->unit_minor>n)
+				break;
+			list=&((*list)->next);
+			n+=SOUND_STEP;
+		}
+
+		if(n>=top)
+			return -ENOENT;
+	} else {
+		n = low+(index*16);
+		while (*list) {
+			if ((*list)->unit_minor==n)
+				return -EBUSY;
+			if ((*list)->unit_minor>n)
+				break;
+			list=&((*list)->next);
+		}
+	}	
+		
+	/*
+	 *	Fill it in
+	 */
+	 
+	s->unit_minor=n;
+	s->unit_fops=fops;
+	
+	/*
+	 *	Link it
+	 */
+	 
+	s->next=*list;
+	*list=s;
+	
+	
+	return n;
+}
+
+/*
+ *	Remove a node from the chain. Called with the lock asserted
+ */
+ 
+static struct sound_unit *__sound_remove_unit(struct sound_unit **list, int unit)
+{
+	while(*list)
+	{
+		struct sound_unit *p=*list;
+		if(p->unit_minor==unit)
+		{
+			*list=p->next;
+			return p;
+		}
+		list=&(p->next);
+	}
+	printk(KERN_ERR "Sound device %d went missing!\n", unit);
+	return NULL;
+}
+
+/*
+ *	This lock guards the sound loader list.
+ */
+
+static DEFINE_SPINLOCK(sound_loader_lock);
+
+/*
+ *	Allocate the controlling structure and add it to the sound driver
+ *	list. Acquires locks as needed
+ */
+
+static int sound_insert_unit(struct sound_unit **list, const struct file_operations *fops, int index, int low, int top, const char *name, umode_t mode, struct device *dev)
+{
+	struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL);
+	int r;
+
+	if (!s)
+		return -ENOMEM;
+
+	spin_lock(&sound_loader_lock);
+retry:
+	r = __sound_insert_unit(s, list, fops, index, low, top);
+	spin_unlock(&sound_loader_lock);
+	
+	if (r < 0)
+		goto fail;
+	else if (r < SOUND_STEP)
+		sprintf(s->name, "sound/%s", name);
+	else
+		sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP);
+
+	if (!preclaim_oss) {
+		/*
+		 * Something else might have grabbed the minor.  If
+		 * first free slot is requested, rescan with @low set
+		 * to the next unit; otherwise, -EBUSY.
+		 */
+		r = __register_chrdev(SOUND_MAJOR, s->unit_minor, 1, s->name,
+				      &soundcore_fops);
+		if (r < 0) {
+			spin_lock(&sound_loader_lock);
+			__sound_remove_unit(list, s->unit_minor);
+			if (index < 0) {
+				low = s->unit_minor + SOUND_STEP;
+				goto retry;
+			}
+			spin_unlock(&sound_loader_lock);
+			return -EBUSY;
+		}
+	}
+
+	device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor),
+		      NULL, s->name+6);
+	return s->unit_minor;
+
+fail:
+	kfree(s);
+	return r;
+}
+
+/*
+ *	Remove a unit. Acquires locks as needed. The drivers MUST have
+ *	completed the removal before their file operations become
+ *	invalid.
+ */
+ 	
+static void sound_remove_unit(struct sound_unit **list, int unit)
+{
+	struct sound_unit *p;
+
+	spin_lock(&sound_loader_lock);
+	p = __sound_remove_unit(list, unit);
+	spin_unlock(&sound_loader_lock);
+	if (p) {
+		if (!preclaim_oss)
+			__unregister_chrdev(SOUND_MAJOR, p->unit_minor, 1,
+					    p->name);
+		device_destroy(sound_class, MKDEV(SOUND_MAJOR, p->unit_minor));
+		kfree(p);
+	}
+}
+
+/*
+ *	Allocations
+ *
+ *	0	*16		Mixers
+ *	1	*8		Sequencers
+ *	2	*16		Midi
+ *	3	*16		DSP
+ *	4	*16		SunDSP
+ *	5	*16		DSP16
+ *	6	--		sndstat (obsolete)
+ *	7	*16		unused
+ *	8	--		alternate sequencer (see above)
+ *	9	*16		raw synthesizer access
+ *	10	*16		unused
+ *	11	*16		unused
+ *	12	*16		unused
+ *	13	*16		unused
+ *	14	*16		unused
+ *	15	*16		unused
+ */
+
+static struct sound_unit *chains[SOUND_STEP];
+
+/**
+ *	register_sound_special_device - register a special sound node
+ *	@fops: File operations for the driver
+ *	@unit: Unit number to allocate
+ *      @dev: device pointer
+ *
+ *	Allocate a special sound device by minor number from the sound
+ *	subsystem. The allocated number is returned on success. On failure
+ *	a negative error code is returned.
+ */
+ 
+int register_sound_special_device(const struct file_operations *fops, int unit,
+				  struct device *dev)
+{
+	const int chain = unit % SOUND_STEP;
+	int max_unit = 128 + chain;
+	const char *name;
+	char _name[16];
+
+	switch (chain) {
+	    case 0:
+		name = "mixer";
+		break;
+	    case 1:
+		name = "sequencer";
+		if (unit >= SOUND_STEP)
+			goto __unknown;
+		max_unit = unit + 1;
+		break;
+	    case 2:
+		name = "midi";
+		break;
+	    case 3:
+		name = "dsp";
+		break;
+	    case 4:
+		name = "audio";
+		break;
+	    case 8:
+		name = "sequencer2";
+		if (unit >= SOUND_STEP)
+			goto __unknown;
+		max_unit = unit + 1;
+		break;
+	    case 9:
+		name = "dmmidi";
+		break;
+	    case 10:
+		name = "dmfm";
+		break;
+	    case 12:
+		name = "adsp";
+		break;
+	    case 13:
+		name = "amidi";
+		break;
+	    case 14:
+		name = "admmidi";
+		break;
+	    default:
+	    	{
+		    __unknown:
+			sprintf(_name, "unknown%d", chain);
+		    	if (unit >= SOUND_STEP)
+		    		strcat(_name, "-");
+		    	name = _name;
+		}
+		break;
+	}
+	return sound_insert_unit(&chains[chain], fops, -1, unit, max_unit,
+				 name, S_IRUSR | S_IWUSR, dev);
+}
+ 
+EXPORT_SYMBOL(register_sound_special_device);
+
+int register_sound_special(const struct file_operations *fops, int unit)
+{
+	return register_sound_special_device(fops, unit, NULL);
+}
+
+EXPORT_SYMBOL(register_sound_special);
+
+/**
+ *	register_sound_mixer - register a mixer device
+ *	@fops: File operations for the driver
+ *	@dev: Unit number to allocate
+ *
+ *	Allocate a mixer device. Unit is the number of the mixer requested.
+ *	Pass -1 to request the next free mixer unit. On success the allocated
+ *	number is returned, on failure a negative error code is returned.
+ */
+
+int register_sound_mixer(const struct file_operations *fops, int dev)
+{
+	return sound_insert_unit(&chains[0], fops, dev, 0, 128,
+				 "mixer", S_IRUSR | S_IWUSR, NULL);
+}
+
+EXPORT_SYMBOL(register_sound_mixer);
+
+/**
+ *	register_sound_midi - register a midi device
+ *	@fops: File operations for the driver
+ *	@dev: Unit number to allocate
+ *
+ *	Allocate a midi device. Unit is the number of the midi device requested.
+ *	Pass -1 to request the next free midi unit. On success the allocated
+ *	number is returned, on failure a negative error code is returned.
+ */
+
+int register_sound_midi(const struct file_operations *fops, int dev)
+{
+	return sound_insert_unit(&chains[2], fops, dev, 2, 130,
+				 "midi", S_IRUSR | S_IWUSR, NULL);
+}
+
+EXPORT_SYMBOL(register_sound_midi);
+
+/*
+ *	DSP's are registered as a triple. Register only one and cheat
+ *	in open - see below.
+ */
+ 
+/**
+ *	register_sound_dsp - register a DSP device
+ *	@fops: File operations for the driver
+ *	@dev: Unit number to allocate
+ *
+ *	Allocate a DSP device. Unit is the number of the DSP requested.
+ *	Pass -1 to request the next free DSP unit. On success the allocated
+ *	number is returned, on failure a negative error code is returned.
+ *
+ *	This function allocates both the audio and dsp device entries together
+ *	and will always allocate them as a matching pair - eg dsp3/audio3
+ */
+
+int register_sound_dsp(const struct file_operations *fops, int dev)
+{
+	return sound_insert_unit(&chains[3], fops, dev, 3, 131,
+				 "dsp", S_IWUSR | S_IRUSR, NULL);
+}
+
+EXPORT_SYMBOL(register_sound_dsp);
+
+/**
+ *	unregister_sound_special - unregister a special sound device
+ *	@unit: unit number to allocate
+ *
+ *	Release a sound device that was allocated with
+ *	register_sound_special(). The unit passed is the return value from
+ *	the register function.
+ */
+
+
+void unregister_sound_special(int unit)
+{
+	sound_remove_unit(&chains[unit % SOUND_STEP], unit);
+}
+ 
+EXPORT_SYMBOL(unregister_sound_special);
+
+/**
+ *	unregister_sound_mixer - unregister a mixer
+ *	@unit: unit number to allocate
+ *
+ *	Release a sound device that was allocated with register_sound_mixer().
+ *	The unit passed is the return value from the register function.
+ */
+
+void unregister_sound_mixer(int unit)
+{
+	sound_remove_unit(&chains[0], unit);
+}
+
+EXPORT_SYMBOL(unregister_sound_mixer);
+
+/**
+ *	unregister_sound_midi - unregister a midi device
+ *	@unit: unit number to allocate
+ *
+ *	Release a sound device that was allocated with register_sound_midi().
+ *	The unit passed is the return value from the register function.
+ */
+
+void unregister_sound_midi(int unit)
+{
+	sound_remove_unit(&chains[2], unit);
+}
+
+EXPORT_SYMBOL(unregister_sound_midi);
+
+/**
+ *	unregister_sound_dsp - unregister a DSP device
+ *	@unit: unit number to allocate
+ *
+ *	Release a sound device that was allocated with register_sound_dsp().
+ *	The unit passed is the return value from the register function.
+ *
+ *	Both of the allocated units are released together automatically.
+ */
+
+void unregister_sound_dsp(int unit)
+{
+	sound_remove_unit(&chains[3], unit);
+}
+
+
+EXPORT_SYMBOL(unregister_sound_dsp);
+
+static struct sound_unit *__look_for_unit(int chain, int unit)
+{
+	struct sound_unit *s;
+	
+	s=chains[chain];
+	while(s && s->unit_minor <= unit)
+	{
+		if(s->unit_minor==unit)
+			return s;
+		s=s->next;
+	}
+	return NULL;
+}
+
+static int soundcore_open(struct inode *inode, struct file *file)
+{
+	int chain;
+	int unit = iminor(inode);
+	struct sound_unit *s;
+	const struct file_operations *new_fops = NULL;
+
+	chain=unit&0x0F;
+	if(chain==4 || chain==5)	/* dsp/audio/dsp16 */
+	{
+		unit&=0xF0;
+		unit|=3;
+		chain=3;
+	}
+	
+	spin_lock(&sound_loader_lock);
+	s = __look_for_unit(chain, unit);
+	if (s)
+		new_fops = fops_get(s->unit_fops);
+	if (preclaim_oss && !new_fops) {
+		spin_unlock(&sound_loader_lock);
+
+		/*
+		 *  Please, don't change this order or code.
+		 *  For ALSA slot means soundcard and OSS emulation code
+		 *  comes as add-on modules which aren't depend on
+		 *  ALSA toplevel modules for soundcards, thus we need
+		 *  load them at first.	  [Jaroslav Kysela <perex@jcu.cz>]
+		 */
+		request_module("sound-slot-%i", unit>>4);
+		request_module("sound-service-%i-%i", unit>>4, chain);
+
+		/*
+		 * sound-slot/service-* module aliases are scheduled
+		 * for removal in favor of the standard char-major-*
+		 * module aliases.  For the time being, generate both
+		 * the legacy and standard module aliases to ease
+		 * transition.
+		 */
+		if (request_module("char-major-%d-%d", SOUND_MAJOR, unit) > 0)
+			request_module("char-major-%d", SOUND_MAJOR);
+
+		spin_lock(&sound_loader_lock);
+		s = __look_for_unit(chain, unit);
+		if (s)
+			new_fops = fops_get(s->unit_fops);
+	}
+	if (new_fops) {
+		/*
+		 * We rely upon the fact that we can't be unloaded while the
+		 * subdriver is there, so if ->open() is successful we can
+		 * safely drop the reference counter and if it is not we can
+		 * revert to old ->f_op. Ugly, indeed, but that's the cost of
+		 * switching ->f_op in the first place.
+		 */
+		int err = 0;
+		const struct file_operations *old_fops = file->f_op;
+		file->f_op = new_fops;
+		spin_unlock(&sound_loader_lock);
+
+		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;
+	}
+	spin_unlock(&sound_loader_lock);
+	return -ENODEV;
+}
+
+MODULE_ALIAS_CHARDEV_MAJOR(SOUND_MAJOR);
+
+static void cleanup_oss_soundcore(void)
+{
+	/* We have nothing to really do here - we know the lists must be
+	   empty */
+	unregister_chrdev(SOUND_MAJOR, "sound");
+}
+
+static int __init init_oss_soundcore(void)
+{
+	if (preclaim_oss &&
+	    register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops) == -1) {
+		printk(KERN_ERR "soundcore: sound device already in use.\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+#endif /* CONFIG_SOUND_OSS_CORE */
diff --git a/linux/sound/sound_firmware.c b/linux/sound/sound_firmware.c
new file mode 100644
index 0000000..340a0bc
--- /dev/null
+++ b/linux/sound/sound_firmware.c
@@ -0,0 +1,78 @@
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <asm/uaccess.h>
+#include "oss/sound_firmware.h"
+
+static int do_mod_firmware_load(const char *fn, char **fp)
+{
+	struct file* filp;
+	long l;
+	char *dp;
+	loff_t pos;
+
+	filp = filp_open(fn, 0, 0);
+	if (IS_ERR(filp))
+	{
+		printk(KERN_INFO "Unable to load '%s'.\n", fn);
+		return 0;
+	}
+	l = filp->f_path.dentry->d_inode->i_size;
+	if (l <= 0 || l > 131072)
+	{
+		printk(KERN_INFO "Invalid firmware '%s'\n", fn);
+		filp_close(filp, current->files);
+		return 0;
+	}
+	dp = vmalloc(l);
+	if (dp == NULL)
+	{
+		printk(KERN_INFO "Out of memory loading '%s'.\n", fn);
+		filp_close(filp, current->files);
+		return 0;
+	}
+	pos = 0;
+	if (vfs_read(filp, dp, l, &pos) != l)
+	{
+		printk(KERN_INFO "Failed to read '%s'.\n", fn);
+		vfree(dp);
+		filp_close(filp, current->files);
+		return 0;
+	}
+	filp_close(filp, current->files);
+	*fp = dp;
+	return (int) l;
+}
+
+/**
+ *	mod_firmware_load - load sound driver firmware
+ *	@fn: filename
+ *	@fp: return for the buffer.
+ *
+ *	Load the firmware for a sound module (up to 128K) into a buffer.
+ *	The buffer is returned in *fp. It is allocated with vmalloc so is
+ *	virtually linear and not DMAable. The caller should free it with
+ *	vfree when finished.
+ *
+ *	The length of the buffer is returned on a successful load, the
+ *	value zero on a failure.
+ *
+ *	Caution: This API is not recommended. Firmware should be loaded via
+ *	request_firmware.
+ */
+ 
+int mod_firmware_load(const char *fn, char **fp)
+{
+	int r;
+	mm_segment_t fs = get_fs();
+
+	set_fs(get_ds());
+	r = do_mod_firmware_load(fn, fp);
+	set_fs(fs);
+	return r;
+}
+EXPORT_SYMBOL(mod_firmware_load);
+
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/sparc/Kconfig b/linux/sound/sparc/Kconfig
new file mode 100644
index 0000000..d75deba
--- /dev/null
+++ b/linux/sound/sparc/Kconfig
@@ -0,0 +1,41 @@
+# ALSA Sparc drivers
+
+menuconfig SND_SPARC
+	bool "Sparc sound devices"
+	depends on SPARC
+	default y
+	help
+	  Support for sound devices specific to Sun SPARC architectures.
+
+if SND_SPARC
+
+config SND_SUN_AMD7930
+	tristate "Sun AMD7930"
+	depends on SBUS
+	select SND_PCM
+	help
+	  Say Y here to include support for AMD7930 sound device on Sun.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sun-amd7930.
+
+config SND_SUN_CS4231
+	tristate "Sun CS4231"
+	select SND_PCM
+	help
+	  Say Y here to include support for CS4231 sound device on Sun.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sun-cs4231.
+
+config SND_SUN_DBRI
+	tristate "Sun DBRI"
+	depends on SBUS
+	select SND_PCM
+	help
+	  Say Y here to include support for DBRI sound device on Sun.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-sun-dbri.
+
+endif	# SND_SPARC
diff --git a/linux/sound/sparc/Makefile b/linux/sound/sparc/Makefile
new file mode 100644
index 0000000..3cd89c6
--- /dev/null
+++ b/linux/sound/sparc/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2002 by David S. Miller <davem@redhat.com>
+#
+
+snd-sun-amd7930-objs := amd7930.o
+snd-sun-cs4231-objs := cs4231.o
+snd-sun-dbri-objs := dbri.o
+
+obj-$(CONFIG_SND_SUN_AMD7930) += snd-sun-amd7930.o
+obj-$(CONFIG_SND_SUN_CS4231) += snd-sun-cs4231.o
+obj-$(CONFIG_SND_SUN_DBRI) += snd-sun-dbri.o
diff --git a/linux/sound/sparc/amd7930.c b/linux/sound/sparc/amd7930.c
new file mode 100644
index 0000000..f8bcfc3
--- /dev/null
+++ b/linux/sound/sparc/amd7930.c
@@ -0,0 +1,1099 @@
+/*
+ * Driver for AMD7930 sound chips found on Sparcs.
+ * Copyright (C) 2002, 2008 David S. Miller <davem@davemloft.net>
+ *
+ * Based entirely upon drivers/sbus/audio/amd7930.c which is:
+ * Copyright (C) 1996,1997 Thomas K. Dyas (tdyas@eden.rutgers.edu)
+ *
+ * --- Notes from Thomas's original driver ---
+ * This is the lowlevel driver for the AMD7930 audio chip found on all
+ * sun4c machines and some sun4m machines.
+ *
+ * The amd7930 is actually an ISDN chip which has a very simple
+ * integrated audio encoder/decoder. When Sun decided on what chip to
+ * use for audio, they had the brilliant idea of using the amd7930 and
+ * only connecting the audio encoder/decoder pins.
+ *
+ * Thanks to the AMD engineer who was able to get us the AMD79C30
+ * databook which has all the programming information and gain tables.
+ *
+ * Advanced Micro Devices' Am79C30A is an ISDN/audio chip used in the
+ * SparcStation 1+.  The chip provides microphone and speaker interfaces
+ * which provide mono-channel audio at 8K samples per second via either
+ * 8-bit A-law or 8-bit mu-law encoding.  Also, the chip features an
+ * ISDN BRI Line Interface Unit (LIU), I.430 S/T physical interface,
+ * which performs basic D channel LAPD processing and provides raw
+ * B channel data.  The digital audio channel, the two ISDN B channels,
+ * and two 64 Kbps channels to the microprocessor are all interconnected
+ * via a multiplexer.
+ * --- End of notes from Thoamas's original driver ---
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/prom.h>
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Sun AMD7930 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Sun AMD7930 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Sun AMD7930 soundcard.");
+MODULE_AUTHOR("Thomas K. Dyas and David S. Miller");
+MODULE_DESCRIPTION("Sun AMD7930");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Sun,AMD7930}}");
+
+/* Device register layout.  */
+
+/* Register interface presented to the CPU by the amd7930. */
+#define AMD7930_CR	0x00UL		/* Command Register (W) */
+#define AMD7930_IR	AMD7930_CR	/* Interrupt Register (R) */
+#define AMD7930_DR	0x01UL		/* Data Register (R/W) */
+#define AMD7930_DSR1	0x02UL		/* D-channel Status Register 1 (R) */
+#define AMD7930_DER	0x03UL		/* D-channel Error Register (R) */
+#define AMD7930_DCTB	0x04UL		/* D-channel Transmit Buffer (W) */
+#define AMD7930_DCRB	AMD7930_DCTB	/* D-channel Receive Buffer (R) */
+#define AMD7930_BBTB	0x05UL		/* Bb-channel Transmit Buffer (W) */
+#define AMD7930_BBRB	AMD7930_BBTB	/* Bb-channel Receive Buffer (R) */
+#define AMD7930_BCTB	0x06UL		/* Bc-channel Transmit Buffer (W) */
+#define AMD7930_BCRB	AMD7930_BCTB	/* Bc-channel Receive Buffer (R) */
+#define AMD7930_DSR2	0x07UL		/* D-channel Status Register 2 (R) */
+
+/* Indirect registers in the Main Audio Processor. */
+struct amd7930_map {
+	__u16	x[8];
+	__u16	r[8];
+	__u16	gx;
+	__u16	gr;
+	__u16	ger;
+	__u16	stgr;
+	__u16	ftgr;
+	__u16	atgr;
+	__u8	mmr1;
+	__u8	mmr2;
+};
+
+/* After an amd7930 interrupt, reading the Interrupt Register (ir)
+ * clears the interrupt and returns a bitmask indicating which
+ * interrupt source(s) require service.
+ */
+
+#define AMR_IR_DTTHRSH			0x01 /* D-channel xmit threshold */
+#define AMR_IR_DRTHRSH			0x02 /* D-channel recv threshold */
+#define AMR_IR_DSRI			0x04 /* D-channel packet status */
+#define AMR_IR_DERI			0x08 /* D-channel error */
+#define AMR_IR_BBUF			0x10 /* B-channel data xfer */
+#define AMR_IR_LSRI			0x20 /* LIU status */
+#define AMR_IR_DSR2I			0x40 /* D-channel buffer status */
+#define AMR_IR_MLTFRMI			0x80 /* multiframe or PP */
+
+/* The amd7930 has "indirect registers" which are accessed by writing
+ * the register number into the Command Register and then reading or
+ * writing values from the Data Register as appropriate. We define the
+ * AMR_* macros to be the indirect register numbers and AM_* macros to
+ * be bits in whatever register is referred to.
+ */
+
+/* Initialization */
+#define	AMR_INIT			0x21
+#define		AM_INIT_ACTIVE			0x01
+#define		AM_INIT_DATAONLY		0x02
+#define		AM_INIT_POWERDOWN		0x03
+#define		AM_INIT_DISABLE_INTS		0x04
+#define AMR_INIT2			0x20
+#define		AM_INIT2_ENABLE_POWERDOWN	0x20
+#define		AM_INIT2_ENABLE_MULTIFRAME	0x10
+
+/* Line Interface Unit */
+#define	AMR_LIU_LSR			0xA1
+#define		AM_LIU_LSR_STATE		0x07
+#define		AM_LIU_LSR_F3			0x08
+#define		AM_LIU_LSR_F7			0x10
+#define		AM_LIU_LSR_F8			0x20
+#define		AM_LIU_LSR_HSW			0x40
+#define		AM_LIU_LSR_HSW_CHG		0x80
+#define	AMR_LIU_LPR			0xA2
+#define	AMR_LIU_LMR1			0xA3
+#define		AM_LIU_LMR1_B1_ENABL		0x01
+#define		AM_LIU_LMR1_B2_ENABL		0x02
+#define		AM_LIU_LMR1_F_DISABL		0x04
+#define		AM_LIU_LMR1_FA_DISABL		0x08
+#define		AM_LIU_LMR1_REQ_ACTIV		0x10
+#define		AM_LIU_LMR1_F8_F3		0x20
+#define		AM_LIU_LMR1_LIU_ENABL		0x40
+#define	AMR_LIU_LMR2			0xA4
+#define		AM_LIU_LMR2_DECHO		0x01
+#define		AM_LIU_LMR2_DLOOP		0x02
+#define		AM_LIU_LMR2_DBACKOFF		0x04
+#define		AM_LIU_LMR2_EN_F3_INT		0x08
+#define		AM_LIU_LMR2_EN_F8_INT		0x10
+#define		AM_LIU_LMR2_EN_HSW_INT		0x20
+#define		AM_LIU_LMR2_EN_F7_INT		0x40
+#define	AMR_LIU_2_4			0xA5
+#define	AMR_LIU_MF			0xA6
+#define	AMR_LIU_MFSB			0xA7
+#define	AMR_LIU_MFQB			0xA8
+
+/* Multiplexor */
+#define	AMR_MUX_MCR1			0x41
+#define	AMR_MUX_MCR2			0x42
+#define	AMR_MUX_MCR3			0x43
+#define		AM_MUX_CHANNEL_B1		0x01
+#define		AM_MUX_CHANNEL_B2		0x02
+#define		AM_MUX_CHANNEL_Ba		0x03
+#define		AM_MUX_CHANNEL_Bb		0x04
+#define		AM_MUX_CHANNEL_Bc		0x05
+#define		AM_MUX_CHANNEL_Bd		0x06
+#define		AM_MUX_CHANNEL_Be		0x07
+#define		AM_MUX_CHANNEL_Bf		0x08
+#define	AMR_MUX_MCR4			0x44
+#define		AM_MUX_MCR4_ENABLE_INTS		0x08
+#define		AM_MUX_MCR4_REVERSE_Bb		0x10
+#define		AM_MUX_MCR4_REVERSE_Bc		0x20
+#define	AMR_MUX_1_4			0x45
+
+/* Main Audio Processor */
+#define	AMR_MAP_X			0x61
+#define	AMR_MAP_R			0x62
+#define	AMR_MAP_GX			0x63
+#define	AMR_MAP_GR			0x64
+#define	AMR_MAP_GER			0x65
+#define	AMR_MAP_STGR			0x66
+#define	AMR_MAP_FTGR_1_2		0x67
+#define	AMR_MAP_ATGR_1_2		0x68
+#define	AMR_MAP_MMR1			0x69
+#define		AM_MAP_MMR1_ALAW		0x01
+#define		AM_MAP_MMR1_GX			0x02
+#define		AM_MAP_MMR1_GR			0x04
+#define		AM_MAP_MMR1_GER			0x08
+#define		AM_MAP_MMR1_X			0x10
+#define		AM_MAP_MMR1_R			0x20
+#define		AM_MAP_MMR1_STG			0x40
+#define		AM_MAP_MMR1_LOOPBACK		0x80
+#define	AMR_MAP_MMR2			0x6A
+#define		AM_MAP_MMR2_AINB		0x01
+#define		AM_MAP_MMR2_LS			0x02
+#define		AM_MAP_MMR2_ENABLE_DTMF		0x04
+#define		AM_MAP_MMR2_ENABLE_TONEGEN	0x08
+#define		AM_MAP_MMR2_ENABLE_TONERING	0x10
+#define		AM_MAP_MMR2_DISABLE_HIGHPASS	0x20
+#define		AM_MAP_MMR2_DISABLE_AUTOZERO	0x40
+#define	AMR_MAP_1_10			0x6B
+#define	AMR_MAP_MMR3			0x6C
+#define	AMR_MAP_STRA			0x6D
+#define	AMR_MAP_STRF			0x6E
+#define	AMR_MAP_PEAKX			0x70
+#define	AMR_MAP_PEAKR			0x71
+#define	AMR_MAP_15_16			0x72
+
+/* Data Link Controller */
+#define	AMR_DLC_FRAR_1_2_3		0x81
+#define	AMR_DLC_SRAR_1_2_3		0x82
+#define	AMR_DLC_TAR			0x83
+#define	AMR_DLC_DRLR			0x84
+#define	AMR_DLC_DTCR			0x85
+#define	AMR_DLC_DMR1			0x86
+#define		AMR_DLC_DMR1_DTTHRSH_INT	0x01
+#define		AMR_DLC_DMR1_DRTHRSH_INT	0x02
+#define		AMR_DLC_DMR1_TAR_ENABL		0x04
+#define		AMR_DLC_DMR1_EORP_INT		0x08
+#define		AMR_DLC_DMR1_EN_ADDR1		0x10
+#define		AMR_DLC_DMR1_EN_ADDR2		0x20
+#define		AMR_DLC_DMR1_EN_ADDR3		0x40
+#define		AMR_DLC_DMR1_EN_ADDR4		0x80
+#define		AMR_DLC_DMR1_EN_ADDRS		0xf0
+#define	AMR_DLC_DMR2			0x87
+#define		AMR_DLC_DMR2_RABRT_INT		0x01
+#define		AMR_DLC_DMR2_RESID_INT		0x02
+#define		AMR_DLC_DMR2_COLL_INT		0x04
+#define		AMR_DLC_DMR2_FCS_INT		0x08
+#define		AMR_DLC_DMR2_OVFL_INT		0x10
+#define		AMR_DLC_DMR2_UNFL_INT		0x20
+#define		AMR_DLC_DMR2_OVRN_INT		0x40
+#define		AMR_DLC_DMR2_UNRN_INT		0x80
+#define	AMR_DLC_1_7			0x88
+#define	AMR_DLC_DRCR			0x89
+#define	AMR_DLC_RNGR1			0x8A
+#define	AMR_DLC_RNGR2			0x8B
+#define	AMR_DLC_FRAR4			0x8C
+#define	AMR_DLC_SRAR4			0x8D
+#define	AMR_DLC_DMR3			0x8E
+#define		AMR_DLC_DMR3_VA_INT		0x01
+#define		AMR_DLC_DMR3_EOTP_INT		0x02
+#define		AMR_DLC_DMR3_LBRP_INT		0x04
+#define		AMR_DLC_DMR3_RBA_INT		0x08
+#define		AMR_DLC_DMR3_LBT_INT		0x10
+#define		AMR_DLC_DMR3_TBE_INT		0x20
+#define		AMR_DLC_DMR3_RPLOST_INT		0x40
+#define		AMR_DLC_DMR3_KEEP_FCS		0x80
+#define	AMR_DLC_DMR4			0x8F
+#define		AMR_DLC_DMR4_RCV_1		0x00
+#define		AMR_DLC_DMR4_RCV_2		0x01
+#define		AMR_DLC_DMR4_RCV_4		0x02
+#define		AMR_DLC_DMR4_RCV_8		0x03
+#define		AMR_DLC_DMR4_RCV_16		0x01
+#define		AMR_DLC_DMR4_RCV_24		0x02
+#define		AMR_DLC_DMR4_RCV_30		0x03
+#define		AMR_DLC_DMR4_XMT_1		0x00
+#define		AMR_DLC_DMR4_XMT_2		0x04
+#define		AMR_DLC_DMR4_XMT_4		0x08
+#define		AMR_DLC_DMR4_XMT_8		0x0c
+#define		AMR_DLC_DMR4_XMT_10		0x08
+#define		AMR_DLC_DMR4_XMT_14		0x0c
+#define		AMR_DLC_DMR4_IDLE_MARK		0x00
+#define		AMR_DLC_DMR4_IDLE_FLAG		0x10
+#define		AMR_DLC_DMR4_ADDR_BOTH		0x00
+#define		AMR_DLC_DMR4_ADDR_1ST		0x20
+#define		AMR_DLC_DMR4_ADDR_2ND		0xa0
+#define		AMR_DLC_DMR4_CR_ENABLE		0x40
+#define	AMR_DLC_12_15			0x90
+#define	AMR_DLC_ASR			0x91
+#define	AMR_DLC_EFCR			0x92
+#define		AMR_DLC_EFCR_EXTEND_FIFO	0x01
+#define		AMR_DLC_EFCR_SEC_PKT_INT	0x02
+
+#define AMR_DSR1_VADDR			0x01
+#define AMR_DSR1_EORP			0x02
+#define AMR_DSR1_PKT_IP			0x04
+#define AMR_DSR1_DECHO_ON		0x08
+#define AMR_DSR1_DLOOP_ON		0x10
+#define AMR_DSR1_DBACK_OFF		0x20
+#define AMR_DSR1_EOTP			0x40
+#define AMR_DSR1_CXMT_ABRT		0x80
+
+#define AMR_DSR2_LBRP			0x01
+#define AMR_DSR2_RBA			0x02
+#define AMR_DSR2_RPLOST			0x04
+#define AMR_DSR2_LAST_BYTE		0x08
+#define AMR_DSR2_TBE			0x10
+#define AMR_DSR2_MARK_IDLE		0x20
+#define AMR_DSR2_FLAG_IDLE		0x40
+#define AMR_DSR2_SECOND_PKT		0x80
+
+#define AMR_DER_RABRT			0x01
+#define AMR_DER_RFRAME			0x02
+#define AMR_DER_COLLISION		0x04
+#define AMR_DER_FCS			0x08
+#define AMR_DER_OVFL			0x10
+#define AMR_DER_UNFL			0x20
+#define AMR_DER_OVRN			0x40
+#define AMR_DER_UNRN			0x80
+
+/* Peripheral Port */
+#define	AMR_PP_PPCR1			0xC0
+#define	AMR_PP_PPSR			0xC1
+#define	AMR_PP_PPIER			0xC2
+#define	AMR_PP_MTDR			0xC3
+#define	AMR_PP_MRDR			0xC3
+#define	AMR_PP_CITDR0			0xC4
+#define	AMR_PP_CIRDR0			0xC4
+#define	AMR_PP_CITDR1			0xC5
+#define	AMR_PP_CIRDR1			0xC5
+#define	AMR_PP_PPCR2			0xC8
+#define	AMR_PP_PPCR3			0xC9
+
+struct snd_amd7930 {
+	spinlock_t		lock;
+	void __iomem		*regs;
+	u32			flags;
+#define AMD7930_FLAG_PLAYBACK	0x00000001
+#define AMD7930_FLAG_CAPTURE	0x00000002
+
+	struct amd7930_map	map;
+
+	struct snd_card		*card;
+	struct snd_pcm		*pcm;
+	struct snd_pcm_substream	*playback_substream;
+	struct snd_pcm_substream	*capture_substream;
+
+	/* Playback/Capture buffer state. */
+	unsigned char		*p_orig, *p_cur;
+	int			p_left;
+	unsigned char		*c_orig, *c_cur;
+	int			c_left;
+
+	int			rgain;
+	int			pgain;
+	int			mgain;
+
+	struct platform_device	*op;
+	unsigned int		irq;
+	struct snd_amd7930	*next;
+};
+
+static struct snd_amd7930 *amd7930_list;
+
+/* Idle the AMD7930 chip.  The amd->lock is not held.  */
+static __inline__ void amd7930_idle(struct snd_amd7930 *amd)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&amd->lock, flags);
+	sbus_writeb(AMR_INIT, amd->regs + AMD7930_CR);
+	sbus_writeb(0, amd->regs + AMD7930_DR);
+	spin_unlock_irqrestore(&amd->lock, flags);
+}
+
+/* Enable chip interrupts.  The amd->lock is not held.  */
+static __inline__ void amd7930_enable_ints(struct snd_amd7930 *amd)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&amd->lock, flags);
+	sbus_writeb(AMR_INIT, amd->regs + AMD7930_CR);
+	sbus_writeb(AM_INIT_ACTIVE, amd->regs + AMD7930_DR);
+	spin_unlock_irqrestore(&amd->lock, flags);
+}
+
+/* Disable chip interrupts.  The amd->lock is not held.  */
+static __inline__ void amd7930_disable_ints(struct snd_amd7930 *amd)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&amd->lock, flags);
+	sbus_writeb(AMR_INIT, amd->regs + AMD7930_CR);
+	sbus_writeb(AM_INIT_ACTIVE | AM_INIT_DISABLE_INTS, amd->regs + AMD7930_DR);
+	spin_unlock_irqrestore(&amd->lock, flags);
+}
+
+/* Commit amd7930_map settings to the hardware.
+ * The amd->lock is held and local interrupts are disabled.
+ */
+static void __amd7930_write_map(struct snd_amd7930 *amd)
+{
+	struct amd7930_map *map = &amd->map;
+
+	sbus_writeb(AMR_MAP_GX, amd->regs + AMD7930_CR);
+	sbus_writeb(((map->gx >> 0) & 0xff), amd->regs + AMD7930_DR);
+	sbus_writeb(((map->gx >> 8) & 0xff), amd->regs + AMD7930_DR);
+
+	sbus_writeb(AMR_MAP_GR, amd->regs + AMD7930_CR);
+	sbus_writeb(((map->gr >> 0) & 0xff), amd->regs + AMD7930_DR);
+	sbus_writeb(((map->gr >> 8) & 0xff), amd->regs + AMD7930_DR);
+
+	sbus_writeb(AMR_MAP_STGR, amd->regs + AMD7930_CR);
+	sbus_writeb(((map->stgr >> 0) & 0xff), amd->regs + AMD7930_DR);
+	sbus_writeb(((map->stgr >> 8) & 0xff), amd->regs + AMD7930_DR);
+
+	sbus_writeb(AMR_MAP_GER, amd->regs + AMD7930_CR);
+	sbus_writeb(((map->ger >> 0) & 0xff), amd->regs + AMD7930_DR);
+	sbus_writeb(((map->ger >> 8) & 0xff), amd->regs + AMD7930_DR);
+
+	sbus_writeb(AMR_MAP_MMR1, amd->regs + AMD7930_CR);
+	sbus_writeb(map->mmr1, amd->regs + AMD7930_DR);
+
+	sbus_writeb(AMR_MAP_MMR2, amd->regs + AMD7930_CR);
+	sbus_writeb(map->mmr2, amd->regs + AMD7930_DR);
+}
+
+/* gx, gr & stg gains.  this table must contain 256 elements with
+ * the 0th being "infinity" (the magic value 9008).  The remaining
+ * elements match sun's gain curve (but with higher resolution):
+ * -18 to 0dB in .16dB steps then 0 to 12dB in .08dB steps.
+ */
+static __const__ __u16 gx_coeff[256] = {
+	0x9008, 0x8b7c, 0x8b51, 0x8b45, 0x8b42, 0x8b3b, 0x8b36, 0x8b33,
+	0x8b32, 0x8b2a, 0x8b2b, 0x8b2c, 0x8b25, 0x8b23, 0x8b22, 0x8b22,
+	0x9122, 0x8b1a, 0x8aa3, 0x8aa3, 0x8b1c, 0x8aa6, 0x912d, 0x912b,
+	0x8aab, 0x8b12, 0x8aaa, 0x8ab2, 0x9132, 0x8ab4, 0x913c, 0x8abb,
+	0x9142, 0x9144, 0x9151, 0x8ad5, 0x8aeb, 0x8a79, 0x8a5a, 0x8a4a,
+	0x8b03, 0x91c2, 0x91bb, 0x8a3f, 0x8a33, 0x91b2, 0x9212, 0x9213,
+	0x8a2c, 0x921d, 0x8a23, 0x921a, 0x9222, 0x9223, 0x922d, 0x9231,
+	0x9234, 0x9242, 0x925b, 0x92dd, 0x92c1, 0x92b3, 0x92ab, 0x92a4,
+	0x92a2, 0x932b, 0x9341, 0x93d3, 0x93b2, 0x93a2, 0x943c, 0x94b2,
+	0x953a, 0x9653, 0x9782, 0x9e21, 0x9d23, 0x9cd2, 0x9c23, 0x9baa,
+	0x9bde, 0x9b33, 0x9b22, 0x9b1d, 0x9ab2, 0xa142, 0xa1e5, 0x9a3b,
+	0xa213, 0xa1a2, 0xa231, 0xa2eb, 0xa313, 0xa334, 0xa421, 0xa54b,
+	0xada4, 0xac23, 0xab3b, 0xaaab, 0xaa5c, 0xb1a3, 0xb2ca, 0xb3bd,
+	0xbe24, 0xbb2b, 0xba33, 0xc32b, 0xcb5a, 0xd2a2, 0xe31d, 0x0808,
+	0x72ba, 0x62c2, 0x5c32, 0x52db, 0x513e, 0x4cce, 0x43b2, 0x4243,
+	0x41b4, 0x3b12, 0x3bc3, 0x3df2, 0x34bd, 0x3334, 0x32c2, 0x3224,
+	0x31aa, 0x2a7b, 0x2aaa, 0x2b23, 0x2bba, 0x2c42, 0x2e23, 0x25bb,
+	0x242b, 0x240f, 0x231a, 0x22bb, 0x2241, 0x2223, 0x221f, 0x1a33,
+	0x1a4a, 0x1acd, 0x2132, 0x1b1b, 0x1b2c, 0x1b62, 0x1c12, 0x1c32,
+	0x1d1b, 0x1e71, 0x16b1, 0x1522, 0x1434, 0x1412, 0x1352, 0x1323,
+	0x1315, 0x12bc, 0x127a, 0x1235, 0x1226, 0x11a2, 0x1216, 0x0a2a,
+	0x11bc, 0x11d1, 0x1163, 0x0ac2, 0x0ab2, 0x0aab, 0x0b1b, 0x0b23,
+	0x0b33, 0x0c0f, 0x0bb3, 0x0c1b, 0x0c3e, 0x0cb1, 0x0d4c, 0x0ec1,
+	0x079a, 0x0614, 0x0521, 0x047c, 0x0422, 0x03b1, 0x03e3, 0x0333,
+	0x0322, 0x031c, 0x02aa, 0x02ba, 0x02f2, 0x0242, 0x0232, 0x0227,
+	0x0222, 0x021b, 0x01ad, 0x0212, 0x01b2, 0x01bb, 0x01cb, 0x01f6,
+	0x0152, 0x013a, 0x0133, 0x0131, 0x012c, 0x0123, 0x0122, 0x00a2,
+	0x011b, 0x011e, 0x0114, 0x00b1, 0x00aa, 0x00b3, 0x00bd, 0x00ba,
+	0x00c5, 0x00d3, 0x00f3, 0x0062, 0x0051, 0x0042, 0x003b, 0x0033,
+	0x0032, 0x002a, 0x002c, 0x0025, 0x0023, 0x0022, 0x001a, 0x0021,
+	0x001b, 0x001b, 0x001d, 0x0015, 0x0013, 0x0013, 0x0012, 0x0012,
+	0x000a, 0x000a, 0x0011, 0x0011, 0x000b, 0x000b, 0x000c, 0x000e,
+};
+
+static __const__ __u16 ger_coeff[] = {
+	0x431f, /* 5. dB */
+	0x331f, /* 5.5 dB */
+	0x40dd, /* 6. dB */
+	0x11dd, /* 6.5 dB */
+	0x440f, /* 7. dB */
+	0x411f, /* 7.5 dB */
+	0x311f, /* 8. dB */
+	0x5520, /* 8.5 dB */
+	0x10dd, /* 9. dB */
+	0x4211, /* 9.5 dB */
+	0x410f, /* 10. dB */
+	0x111f, /* 10.5 dB */
+	0x600b, /* 11. dB */
+	0x00dd, /* 11.5 dB */
+	0x4210, /* 12. dB */
+	0x110f, /* 13. dB */
+	0x7200, /* 14. dB */
+	0x2110, /* 15. dB */
+	0x2200, /* 15.9 dB */
+	0x000b, /* 16.9 dB */
+	0x000f  /* 18. dB */
+};
+
+/* Update amd7930_map settings and program them into the hardware.
+ * The amd->lock is held and local interrupts are disabled.
+ */
+static void __amd7930_update_map(struct snd_amd7930 *amd)
+{
+	struct amd7930_map *map = &amd->map;
+	int level;
+
+	map->gx = gx_coeff[amd->rgain];
+	map->stgr = gx_coeff[amd->mgain];
+	level = (amd->pgain * (256 + ARRAY_SIZE(ger_coeff))) >> 8;
+	if (level >= 256) {
+		map->ger = ger_coeff[level - 256];
+		map->gr = gx_coeff[255];
+	} else {
+		map->ger = ger_coeff[0];
+		map->gr = gx_coeff[level];
+	}
+	__amd7930_write_map(amd);
+}
+
+static irqreturn_t snd_amd7930_interrupt(int irq, void *dev_id)
+{
+	struct snd_amd7930 *amd = dev_id;
+	unsigned int elapsed;
+	u8 ir;
+
+	spin_lock(&amd->lock);
+
+	elapsed = 0;
+
+	ir = sbus_readb(amd->regs + AMD7930_IR);
+	if (ir & AMR_IR_BBUF) {
+		u8 byte;
+
+		if (amd->flags & AMD7930_FLAG_PLAYBACK) {
+			if (amd->p_left > 0) {
+				byte = *(amd->p_cur++);
+				amd->p_left--;
+				sbus_writeb(byte, amd->regs + AMD7930_BBTB);
+				if (amd->p_left == 0)
+					elapsed |= AMD7930_FLAG_PLAYBACK;
+			} else
+				sbus_writeb(0, amd->regs + AMD7930_BBTB);
+		} else if (amd->flags & AMD7930_FLAG_CAPTURE) {
+			byte = sbus_readb(amd->regs + AMD7930_BBRB);
+			if (amd->c_left > 0) {
+				*(amd->c_cur++) = byte;
+				amd->c_left--;
+				if (amd->c_left == 0)
+					elapsed |= AMD7930_FLAG_CAPTURE;
+			}
+		}
+	}
+	spin_unlock(&amd->lock);
+
+	if (elapsed & AMD7930_FLAG_PLAYBACK)
+		snd_pcm_period_elapsed(amd->playback_substream);
+	else
+		snd_pcm_period_elapsed(amd->capture_substream);
+
+	return IRQ_HANDLED;
+}
+
+static int snd_amd7930_trigger(struct snd_amd7930 *amd, unsigned int flag, int cmd)
+{
+	unsigned long flags;
+	int result = 0;
+
+	spin_lock_irqsave(&amd->lock, flags);
+	if (cmd == SNDRV_PCM_TRIGGER_START) {
+		if (!(amd->flags & flag)) {
+			amd->flags |= flag;
+
+			/* Enable B channel interrupts.  */
+			sbus_writeb(AMR_MUX_MCR4, amd->regs + AMD7930_CR);
+			sbus_writeb(AM_MUX_MCR4_ENABLE_INTS, amd->regs + AMD7930_DR);
+		}
+	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+		if (amd->flags & flag) {
+			amd->flags &= ~flag;
+
+			/* Disable B channel interrupts.  */
+			sbus_writeb(AMR_MUX_MCR4, amd->regs + AMD7930_CR);
+			sbus_writeb(0, amd->regs + AMD7930_DR);
+		}
+	} else {
+		result = -EINVAL;
+	}
+	spin_unlock_irqrestore(&amd->lock, flags);
+
+	return result;
+}
+
+static int snd_amd7930_playback_trigger(struct snd_pcm_substream *substream,
+					int cmd)
+{
+	struct snd_amd7930 *amd = snd_pcm_substream_chip(substream);
+	return snd_amd7930_trigger(amd, AMD7930_FLAG_PLAYBACK, cmd);
+}
+
+static int snd_amd7930_capture_trigger(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	struct snd_amd7930 *amd = snd_pcm_substream_chip(substream);
+	return snd_amd7930_trigger(amd, AMD7930_FLAG_CAPTURE, cmd);
+}
+
+static int snd_amd7930_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_amd7930 *amd = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned long flags;
+	u8 new_mmr1;
+
+	spin_lock_irqsave(&amd->lock, flags);
+
+	amd->flags |= AMD7930_FLAG_PLAYBACK;
+
+	/* Setup the pseudo-dma transfer pointers.  */
+	amd->p_orig = amd->p_cur = runtime->dma_area;
+	amd->p_left = size;
+
+	/* Put the chip into the correct encoding format.  */
+	new_mmr1 = amd->map.mmr1;
+	if (runtime->format == SNDRV_PCM_FORMAT_A_LAW)
+		new_mmr1 |= AM_MAP_MMR1_ALAW;
+	else
+		new_mmr1 &= ~AM_MAP_MMR1_ALAW;
+	if (new_mmr1 != amd->map.mmr1) {
+		amd->map.mmr1 = new_mmr1;
+		__amd7930_update_map(amd);
+	}
+
+	spin_unlock_irqrestore(&amd->lock, flags);
+
+	return 0;
+}
+
+static int snd_amd7930_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_amd7930 *amd = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+	unsigned long flags;
+	u8 new_mmr1;
+
+	spin_lock_irqsave(&amd->lock, flags);
+
+	amd->flags |= AMD7930_FLAG_CAPTURE;
+
+	/* Setup the pseudo-dma transfer pointers.  */
+	amd->c_orig = amd->c_cur = runtime->dma_area;
+	amd->c_left = size;
+
+	/* Put the chip into the correct encoding format.  */
+	new_mmr1 = amd->map.mmr1;
+	if (runtime->format == SNDRV_PCM_FORMAT_A_LAW)
+		new_mmr1 |= AM_MAP_MMR1_ALAW;
+	else
+		new_mmr1 &= ~AM_MAP_MMR1_ALAW;
+	if (new_mmr1 != amd->map.mmr1) {
+		amd->map.mmr1 = new_mmr1;
+		__amd7930_update_map(amd);
+	}
+
+	spin_unlock_irqrestore(&amd->lock, flags);
+
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_amd7930_playback_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_amd7930 *amd = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(amd->flags & AMD7930_FLAG_PLAYBACK))
+		return 0;
+	ptr = amd->p_cur - amd->p_orig;
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_amd7930_capture_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_amd7930 *amd = snd_pcm_substream_chip(substream);
+	size_t ptr;
+
+	if (!(amd->flags & AMD7930_FLAG_CAPTURE))
+		return 0;
+
+	ptr = amd->c_cur - amd->c_orig;
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+/* Playback and capture have identical properties.  */
+static struct snd_pcm_hardware snd_amd7930_pcm_hw =
+{
+	.info			= (SNDRV_PCM_INFO_MMAP |
+				   SNDRV_PCM_INFO_MMAP_VALID |
+				   SNDRV_PCM_INFO_INTERLEAVED |
+				   SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				   SNDRV_PCM_INFO_HALF_DUPLEX),
+	.formats		= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW,
+	.rates			= SNDRV_PCM_RATE_8000,
+	.rate_min		= 8000,
+	.rate_max		= 8000,
+	.channels_min		= 1,
+	.channels_max		= 1,
+	.buffer_bytes_max	= (64*1024),
+	.period_bytes_min	= 1,
+	.period_bytes_max	= (64*1024),
+	.periods_min		= 1,
+	.periods_max		= 1024,
+};
+
+static int snd_amd7930_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_amd7930 *amd = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	amd->playback_substream = substream;
+	runtime->hw = snd_amd7930_pcm_hw;
+	return 0;
+}
+
+static int snd_amd7930_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_amd7930 *amd = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	amd->capture_substream = substream;
+	runtime->hw = snd_amd7930_pcm_hw;
+	return 0;
+}
+
+static int snd_amd7930_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_amd7930 *amd = snd_pcm_substream_chip(substream);
+
+	amd->playback_substream = NULL;
+	return 0;
+}
+
+static int snd_amd7930_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_amd7930 *amd = snd_pcm_substream_chip(substream);
+
+	amd->capture_substream = NULL;
+	return 0;
+}
+
+static int snd_amd7930_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int snd_amd7930_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static struct snd_pcm_ops snd_amd7930_playback_ops = {
+	.open		=	snd_amd7930_playback_open,
+	.close		=	snd_amd7930_playback_close,
+	.ioctl		=	snd_pcm_lib_ioctl,
+	.hw_params	=	snd_amd7930_hw_params,
+	.hw_free	=	snd_amd7930_hw_free,
+	.prepare	=	snd_amd7930_playback_prepare,
+	.trigger	=	snd_amd7930_playback_trigger,
+	.pointer	=	snd_amd7930_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_amd7930_capture_ops = {
+	.open		=	snd_amd7930_capture_open,
+	.close		=	snd_amd7930_capture_close,
+	.ioctl		=	snd_pcm_lib_ioctl,
+	.hw_params	=	snd_amd7930_hw_params,
+	.hw_free	=	snd_amd7930_hw_free,
+	.prepare	=	snd_amd7930_capture_prepare,
+	.trigger	=	snd_amd7930_capture_trigger,
+	.pointer	=	snd_amd7930_capture_pointer,
+};
+
+static int __devinit snd_amd7930_pcm(struct snd_amd7930 *amd)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(amd->card,
+			       /* ID */             "sun_amd7930",
+			       /* device */         0,
+			       /* playback count */ 1,
+			       /* capture count */  1, &pcm)) < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_amd7930_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_amd7930_capture_ops);
+
+	pcm->private_data = amd;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, amd->card->shortname);
+	amd->pcm = pcm;
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+					      snd_dma_continuous_data(GFP_KERNEL),
+					      64*1024, 64*1024);
+
+	return 0;
+}
+
+#define VOLUME_MONITOR	0
+#define VOLUME_CAPTURE	1
+#define VOLUME_PLAYBACK	2
+
+static int snd_amd7930_info_volume(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 255;
+
+	return 0;
+}
+
+static int snd_amd7930_get_volume(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_amd7930 *amd = snd_kcontrol_chip(kctl);
+	int type = kctl->private_value;
+	int *swval;
+
+	switch (type) {
+	case VOLUME_MONITOR:
+		swval = &amd->mgain;
+		break;
+	case VOLUME_CAPTURE:
+		swval = &amd->rgain;
+		break;
+	case VOLUME_PLAYBACK:
+	default:
+		swval = &amd->pgain;
+		break;
+	};
+
+	ucontrol->value.integer.value[0] = *swval;
+
+	return 0;
+}
+
+static int snd_amd7930_put_volume(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_amd7930 *amd = snd_kcontrol_chip(kctl);
+	unsigned long flags;
+	int type = kctl->private_value;
+	int *swval, change;
+
+	switch (type) {
+	case VOLUME_MONITOR:
+		swval = &amd->mgain;
+		break;
+	case VOLUME_CAPTURE:
+		swval = &amd->rgain;
+		break;
+	case VOLUME_PLAYBACK:
+	default:
+		swval = &amd->pgain;
+		break;
+	};
+
+	spin_lock_irqsave(&amd->lock, flags);
+
+	if (*swval != ucontrol->value.integer.value[0]) {
+		*swval = ucontrol->value.integer.value[0] & 0xff;
+		__amd7930_update_map(amd);
+		change = 1;
+	} else
+		change = 0;
+
+	spin_unlock_irqrestore(&amd->lock, flags);
+
+	return change;
+}
+
+static struct snd_kcontrol_new amd7930_controls[] __devinitdata = {
+	{
+		.iface		=	SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name		=	"Monitor Volume",
+		.index		=	0,
+		.info		=	snd_amd7930_info_volume,
+		.get		=	snd_amd7930_get_volume,
+		.put		=	snd_amd7930_put_volume,
+		.private_value	=	VOLUME_MONITOR,
+	},
+	{
+		.iface		=	SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name		=	"Capture Volume",
+		.index		=	0,
+		.info		=	snd_amd7930_info_volume,
+		.get		=	snd_amd7930_get_volume,
+		.put		=	snd_amd7930_put_volume,
+		.private_value	=	VOLUME_CAPTURE,
+	},
+	{
+		.iface		=	SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name		=	"Playback Volume",
+		.index		=	0,
+		.info		=	snd_amd7930_info_volume,
+		.get		=	snd_amd7930_get_volume,
+		.put		=	snd_amd7930_put_volume,
+		.private_value	=	VOLUME_PLAYBACK,
+	},
+};
+
+static int __devinit snd_amd7930_mixer(struct snd_amd7930 *amd)
+{
+	struct snd_card *card;
+	int idx, err;
+
+	if (snd_BUG_ON(!amd || !amd->card))
+		return -EINVAL;
+
+	card = amd->card;
+	strcpy(card->mixername, card->shortname);
+
+	for (idx = 0; idx < ARRAY_SIZE(amd7930_controls); idx++) {
+		if ((err = snd_ctl_add(card,
+				       snd_ctl_new1(&amd7930_controls[idx], amd))) < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int snd_amd7930_free(struct snd_amd7930 *amd)
+{
+	struct platform_device *op = amd->op;
+
+	amd7930_idle(amd);
+
+	if (amd->irq)
+		free_irq(amd->irq, amd);
+
+	if (amd->regs)
+		of_iounmap(&op->resource[0], amd->regs,
+			   resource_size(&op->resource[0]));
+
+	kfree(amd);
+
+	return 0;
+}
+
+static int snd_amd7930_dev_free(struct snd_device *device)
+{
+	struct snd_amd7930 *amd = device->device_data;
+
+	return snd_amd7930_free(amd);
+}
+
+static struct snd_device_ops snd_amd7930_dev_ops = {
+	.dev_free	=	snd_amd7930_dev_free,
+};
+
+static int __devinit snd_amd7930_create(struct snd_card *card,
+					struct platform_device *op,
+					int irq, int dev,
+					struct snd_amd7930 **ramd)
+{
+	struct snd_amd7930 *amd;
+	unsigned long flags;
+	int err;
+
+	*ramd = NULL;
+	amd = kzalloc(sizeof(*amd), GFP_KERNEL);
+	if (amd == NULL)
+		return -ENOMEM;
+
+	spin_lock_init(&amd->lock);
+	amd->card = card;
+	amd->op = op;
+
+	amd->regs = of_ioremap(&op->resource[0], 0,
+			       resource_size(&op->resource[0]), "amd7930");
+	if (!amd->regs) {
+		snd_printk(KERN_ERR
+			   "amd7930-%d: Unable to map chip registers.\n", dev);
+		return -EIO;
+	}
+
+	amd7930_idle(amd);
+
+	if (request_irq(irq, snd_amd7930_interrupt,
+			IRQF_DISABLED | IRQF_SHARED, "amd7930", amd)) {
+		snd_printk(KERN_ERR "amd7930-%d: Unable to grab IRQ %d\n",
+			   dev, irq);
+		snd_amd7930_free(amd);
+		return -EBUSY;
+	}
+	amd->irq = irq;
+
+	amd7930_enable_ints(amd);
+
+	spin_lock_irqsave(&amd->lock, flags);
+
+	amd->rgain = 128;
+	amd->pgain = 200;
+	amd->mgain = 0;
+
+	memset(&amd->map, 0, sizeof(amd->map));
+	amd->map.mmr1 = (AM_MAP_MMR1_GX | AM_MAP_MMR1_GER |
+			 AM_MAP_MMR1_GR | AM_MAP_MMR1_STG);
+	amd->map.mmr2 = (AM_MAP_MMR2_LS | AM_MAP_MMR2_AINB);
+
+	__amd7930_update_map(amd);
+
+	/* Always MUX audio (Ba) to channel Bb. */
+	sbus_writeb(AMR_MUX_MCR1, amd->regs + AMD7930_CR);
+	sbus_writeb(AM_MUX_CHANNEL_Ba | (AM_MUX_CHANNEL_Bb << 4),
+		    amd->regs + AMD7930_DR);
+
+	spin_unlock_irqrestore(&amd->lock, flags);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+				  amd, &snd_amd7930_dev_ops)) < 0) {
+		snd_amd7930_free(amd);
+		return err;
+	}
+
+	*ramd = amd;
+	return 0;
+}
+
+static int __devinit amd7930_sbus_probe(struct platform_device *op, const struct of_device_id *match)
+{
+	struct resource *rp = &op->resource[0];
+	static int dev_num;
+	struct snd_card *card;
+	struct snd_amd7930 *amd;
+	int err, irq;
+
+	irq = op->archdata.irqs[0];
+
+	if (dev_num >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev_num]) {
+		dev_num++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev_num], id[dev_num], THIS_MODULE, 0,
+			      &card);
+	if (err < 0)
+		return err;
+
+	strcpy(card->driver, "AMD7930");
+	strcpy(card->shortname, "Sun AMD7930");
+	sprintf(card->longname, "%s at 0x%02lx:0x%08Lx, irq %d",
+		card->shortname,
+		rp->flags & 0xffL,
+		(unsigned long long)rp->start,
+		irq);
+
+	if ((err = snd_amd7930_create(card, op,
+				      irq, dev_num, &amd)) < 0)
+		goto out_err;
+
+	if ((err = snd_amd7930_pcm(amd)) < 0)
+		goto out_err;
+
+	if ((err = snd_amd7930_mixer(amd)) < 0)
+		goto out_err;
+
+	if ((err = snd_card_register(card)) < 0)
+		goto out_err;
+
+	amd->next = amd7930_list;
+	amd7930_list = amd;
+
+	dev_num++;
+
+	return 0;
+
+out_err:
+	snd_card_free(card);
+	return err;
+}
+
+static const struct of_device_id amd7930_match[] = {
+	{
+		.name = "audio",
+	},
+	{},
+};
+
+static struct of_platform_driver amd7930_sbus_driver = {
+	.driver = {
+		.name = "audio",
+		.owner = THIS_MODULE,
+		.of_match_table = amd7930_match,
+	},
+	.probe		= amd7930_sbus_probe,
+};
+
+static int __init amd7930_init(void)
+{
+	return of_register_platform_driver(&amd7930_sbus_driver);
+}
+
+static void __exit amd7930_exit(void)
+{
+	struct snd_amd7930 *p = amd7930_list;
+
+	while (p != NULL) {
+		struct snd_amd7930 *next = p->next;
+
+		snd_card_free(p->card);
+
+		p = next;
+	}
+
+	amd7930_list = NULL;
+
+	of_unregister_platform_driver(&amd7930_sbus_driver);
+}
+
+module_init(amd7930_init);
+module_exit(amd7930_exit);
diff --git a/linux/sound/sparc/cs4231.c b/linux/sound/sparc/cs4231.c
new file mode 100644
index 0000000..c276086
--- /dev/null
+++ b/linux/sound/sparc/cs4231.c
@@ -0,0 +1,2132 @@
+/*
+ * Driver for CS4231 sound chips found on Sparcs.
+ * Copyright (C) 2002, 2008 David S. Miller <davem@davemloft.net>
+ *
+ * Based entirely upon drivers/sbus/audio/cs4231.c which is:
+ * Copyright (C) 1996, 1997, 1998 Derrick J Brashear (shadow@andrew.cmu.edu)
+ * and also sound/isa/cs423x/cs4231_lib.c which is:
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/timer.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+
+#ifdef CONFIG_SBUS
+#define SBUS_SUPPORT
+#endif
+
+#if defined(CONFIG_PCI) && defined(CONFIG_SPARC64)
+#define EBUS_SUPPORT
+#include <linux/pci.h>
+#include <asm/ebus_dma.h>
+#endif
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+/* Enable this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Sun CS4231 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Sun CS4231 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Sun CS4231 soundcard.");
+MODULE_AUTHOR("Jaroslav Kysela, Derrick J. Brashear and David S. Miller");
+MODULE_DESCRIPTION("Sun CS4231");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Sun,CS4231}}");
+
+#ifdef SBUS_SUPPORT
+struct sbus_dma_info {
+       spinlock_t	lock;	/* DMA access lock */
+       int		dir;
+       void __iomem	*regs;
+};
+#endif
+
+struct snd_cs4231;
+struct cs4231_dma_control {
+	void		(*prepare)(struct cs4231_dma_control *dma_cont,
+				   int dir);
+	void		(*enable)(struct cs4231_dma_control *dma_cont, int on);
+	int		(*request)(struct cs4231_dma_control *dma_cont,
+				   dma_addr_t bus_addr, size_t len);
+	unsigned int	(*address)(struct cs4231_dma_control *dma_cont);
+#ifdef EBUS_SUPPORT
+	struct		ebus_dma_info	ebus_info;
+#endif
+#ifdef SBUS_SUPPORT
+	struct		sbus_dma_info	sbus_info;
+#endif
+};
+
+struct snd_cs4231 {
+	spinlock_t		lock;	/* registers access lock */
+	void __iomem		*port;
+
+	struct cs4231_dma_control	p_dma;
+	struct cs4231_dma_control	c_dma;
+
+	u32			flags;
+#define CS4231_FLAG_EBUS	0x00000001
+#define CS4231_FLAG_PLAYBACK	0x00000002
+#define CS4231_FLAG_CAPTURE	0x00000004
+
+	struct snd_card		*card;
+	struct snd_pcm		*pcm;
+	struct snd_pcm_substream	*playback_substream;
+	unsigned int		p_periods_sent;
+	struct snd_pcm_substream	*capture_substream;
+	unsigned int		c_periods_sent;
+	struct snd_timer	*timer;
+
+	unsigned short mode;
+#define CS4231_MODE_NONE	0x0000
+#define CS4231_MODE_PLAY	0x0001
+#define CS4231_MODE_RECORD	0x0002
+#define CS4231_MODE_TIMER	0x0004
+#define CS4231_MODE_OPEN	(CS4231_MODE_PLAY | CS4231_MODE_RECORD | \
+				 CS4231_MODE_TIMER)
+
+	unsigned char		image[32];	/* registers image */
+	int			mce_bit;
+	int			calibrate_mute;
+	struct mutex		mce_mutex;	/* mutex for mce register */
+	struct mutex		open_mutex;	/* mutex for ALSA open/close */
+
+	struct platform_device	*op;
+	unsigned int		irq[2];
+	unsigned int		regs_size;
+	struct snd_cs4231	*next;
+};
+
+/* Eventually we can use sound/isa/cs423x/cs4231_lib.c directly, but for
+ * now....  -DaveM
+ */
+
+/* IO ports */
+#include <sound/cs4231-regs.h>
+
+/* XXX offsets are different than PC ISA chips... */
+#define CS4231U(chip, x)	((chip)->port + ((c_d_c_CS4231##x) << 2))
+
+/* SBUS DMA register defines.  */
+
+#define APCCSR	0x10UL	/* APC DMA CSR */
+#define APCCVA	0x20UL	/* APC Capture DMA Address */
+#define APCCC	0x24UL	/* APC Capture Count */
+#define APCCNVA	0x28UL	/* APC Capture DMA Next Address */
+#define APCCNC	0x2cUL	/* APC Capture Next Count */
+#define APCPVA	0x30UL	/* APC Play DMA Address */
+#define APCPC	0x34UL	/* APC Play Count */
+#define APCPNVA	0x38UL	/* APC Play DMA Next Address */
+#define APCPNC	0x3cUL	/* APC Play Next Count */
+
+/* Defines for SBUS DMA-routines */
+
+#define APCVA  0x0UL	/* APC DMA Address */
+#define APCC   0x4UL	/* APC Count */
+#define APCNVA 0x8UL	/* APC DMA Next Address */
+#define APCNC  0xcUL	/* APC Next Count */
+#define APC_PLAY 0x30UL	/* Play registers start at 0x30 */
+#define APC_RECORD 0x20UL /* Record registers start at 0x20 */
+
+/* APCCSR bits */
+
+#define APC_INT_PENDING 0x800000 /* Interrupt Pending */
+#define APC_PLAY_INT    0x400000 /* Playback interrupt */
+#define APC_CAPT_INT    0x200000 /* Capture interrupt */
+#define APC_GENL_INT    0x100000 /* General interrupt */
+#define APC_XINT_ENA    0x80000  /* General ext int. enable */
+#define APC_XINT_PLAY   0x40000  /* Playback ext intr */
+#define APC_XINT_CAPT   0x20000  /* Capture ext intr */
+#define APC_XINT_GENL   0x10000  /* Error ext intr */
+#define APC_XINT_EMPT   0x8000   /* Pipe empty interrupt (0 write to pva) */
+#define APC_XINT_PEMP   0x4000   /* Play pipe empty (pva and pnva not set) */
+#define APC_XINT_PNVA   0x2000   /* Playback NVA dirty */
+#define APC_XINT_PENA   0x1000   /* play pipe empty Int enable */
+#define APC_XINT_COVF   0x800    /* Cap data dropped on floor */
+#define APC_XINT_CNVA   0x400    /* Capture NVA dirty */
+#define APC_XINT_CEMP   0x200    /* Capture pipe empty (cva and cnva not set) */
+#define APC_XINT_CENA   0x100    /* Cap. pipe empty int enable */
+#define APC_PPAUSE      0x80     /* Pause the play DMA */
+#define APC_CPAUSE      0x40     /* Pause the capture DMA */
+#define APC_CDC_RESET   0x20     /* CODEC RESET */
+#define APC_PDMA_READY  0x08     /* Play DMA Go */
+#define APC_CDMA_READY  0x04     /* Capture DMA Go */
+#define APC_CHIP_RESET  0x01     /* Reset the chip */
+
+/* EBUS DMA register offsets  */
+
+#define EBDMA_CSR	0x00UL	/* Control/Status */
+#define EBDMA_ADDR	0x04UL	/* DMA Address */
+#define EBDMA_COUNT	0x08UL	/* DMA Count */
+
+/*
+ *  Some variables
+ */
+
+static unsigned char freq_bits[14] = {
+	/* 5510 */	0x00 | CS4231_XTAL2,
+	/* 6620 */	0x0E | CS4231_XTAL2,
+	/* 8000 */	0x00 | CS4231_XTAL1,
+	/* 9600 */	0x0E | CS4231_XTAL1,
+	/* 11025 */	0x02 | CS4231_XTAL2,
+	/* 16000 */	0x02 | CS4231_XTAL1,
+	/* 18900 */	0x04 | CS4231_XTAL2,
+	/* 22050 */	0x06 | CS4231_XTAL2,
+	/* 27042 */	0x04 | CS4231_XTAL1,
+	/* 32000 */	0x06 | CS4231_XTAL1,
+	/* 33075 */	0x0C | CS4231_XTAL2,
+	/* 37800 */	0x08 | CS4231_XTAL2,
+	/* 44100 */	0x0A | CS4231_XTAL2,
+	/* 48000 */	0x0C | CS4231_XTAL1
+};
+
+static unsigned int rates[14] = {
+	5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050,
+	27042, 32000, 33075, 37800, 44100, 48000
+};
+
+static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+	.count	= ARRAY_SIZE(rates),
+	.list	= rates,
+};
+
+static int snd_cs4231_xrate(struct snd_pcm_runtime *runtime)
+{
+	return snd_pcm_hw_constraint_list(runtime, 0,
+					  SNDRV_PCM_HW_PARAM_RATE,
+					  &hw_constraints_rates);
+}
+
+static unsigned char snd_cs4231_original_image[32] =
+{
+	0x00,			/* 00/00 - lic */
+	0x00,			/* 01/01 - ric */
+	0x9f,			/* 02/02 - la1ic */
+	0x9f,			/* 03/03 - ra1ic */
+	0x9f,			/* 04/04 - la2ic */
+	0x9f,			/* 05/05 - ra2ic */
+	0xbf,			/* 06/06 - loc */
+	0xbf,			/* 07/07 - roc */
+	0x20,			/* 08/08 - pdfr */
+	CS4231_AUTOCALIB,	/* 09/09 - ic */
+	0x00,			/* 0a/10 - pc */
+	0x00,			/* 0b/11 - ti */
+	CS4231_MODE2,		/* 0c/12 - mi */
+	0x00,			/* 0d/13 - lbc */
+	0x00,			/* 0e/14 - pbru */
+	0x00,			/* 0f/15 - pbrl */
+	0x80,			/* 10/16 - afei */
+	0x01,			/* 11/17 - afeii */
+	0x9f,			/* 12/18 - llic */
+	0x9f,			/* 13/19 - rlic */
+	0x00,			/* 14/20 - tlb */
+	0x00,			/* 15/21 - thb */
+	0x00,			/* 16/22 - la3mic/reserved */
+	0x00,			/* 17/23 - ra3mic/reserved */
+	0x00,			/* 18/24 - afs */
+	0x00,			/* 19/25 - lamoc/version */
+	0x00,			/* 1a/26 - mioc */
+	0x00,			/* 1b/27 - ramoc/reserved */
+	0x20,			/* 1c/28 - cdfr */
+	0x00,			/* 1d/29 - res4 */
+	0x00,			/* 1e/30 - cbru */
+	0x00,			/* 1f/31 - cbrl */
+};
+
+static u8 __cs4231_readb(struct snd_cs4231 *cp, void __iomem *reg_addr)
+{
+	if (cp->flags & CS4231_FLAG_EBUS)
+		return readb(reg_addr);
+	else
+		return sbus_readb(reg_addr);
+}
+
+static void __cs4231_writeb(struct snd_cs4231 *cp, u8 val,
+			    void __iomem *reg_addr)
+{
+	if (cp->flags & CS4231_FLAG_EBUS)
+		return writeb(val, reg_addr);
+	else
+		return sbus_writeb(val, reg_addr);
+}
+
+/*
+ *  Basic I/O functions
+ */
+
+static void snd_cs4231_ready(struct snd_cs4231 *chip)
+{
+	int timeout;
+
+	for (timeout = 250; timeout > 0; timeout--) {
+		int val = __cs4231_readb(chip, CS4231U(chip, REGSEL));
+		if ((val & CS4231_INIT) == 0)
+			break;
+		udelay(100);
+	}
+}
+
+static void snd_cs4231_dout(struct snd_cs4231 *chip, unsigned char reg,
+			    unsigned char value)
+{
+	snd_cs4231_ready(chip);
+#ifdef CONFIG_SND_DEBUG
+	if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT)
+		snd_printdd("out: auto calibration time out - reg = 0x%x, "
+			    "value = 0x%x\n",
+			    reg, value);
+#endif
+	__cs4231_writeb(chip, chip->mce_bit | reg, CS4231U(chip, REGSEL));
+	wmb();
+	__cs4231_writeb(chip, value, CS4231U(chip, REG));
+	mb();
+}
+
+static inline void snd_cs4231_outm(struct snd_cs4231 *chip, unsigned char reg,
+		     unsigned char mask, unsigned char value)
+{
+	unsigned char tmp = (chip->image[reg] & mask) | value;
+
+	chip->image[reg] = tmp;
+	if (!chip->calibrate_mute)
+		snd_cs4231_dout(chip, reg, tmp);
+}
+
+static void snd_cs4231_out(struct snd_cs4231 *chip, unsigned char reg,
+			   unsigned char value)
+{
+	snd_cs4231_dout(chip, reg, value);
+	chip->image[reg] = value;
+	mb();
+}
+
+static unsigned char snd_cs4231_in(struct snd_cs4231 *chip, unsigned char reg)
+{
+	snd_cs4231_ready(chip);
+#ifdef CONFIG_SND_DEBUG
+	if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT)
+		snd_printdd("in: auto calibration time out - reg = 0x%x\n",
+			    reg);
+#endif
+	__cs4231_writeb(chip, chip->mce_bit | reg, CS4231U(chip, REGSEL));
+	mb();
+	return __cs4231_readb(chip, CS4231U(chip, REG));
+}
+
+/*
+ *  CS4231 detection / MCE routines
+ */
+
+static void snd_cs4231_busy_wait(struct snd_cs4231 *chip)
+{
+	int timeout;
+
+	/* looks like this sequence is proper for CS4231A chip (GUS MAX) */
+	for (timeout = 5; timeout > 0; timeout--)
+		__cs4231_readb(chip, CS4231U(chip, REGSEL));
+
+	/* end of cleanup sequence */
+	for (timeout = 500; timeout > 0; timeout--) {
+		int val = __cs4231_readb(chip, CS4231U(chip, REGSEL));
+		if ((val & CS4231_INIT) == 0)
+			break;
+		msleep(1);
+	}
+}
+
+static void snd_cs4231_mce_up(struct snd_cs4231 *chip)
+{
+	unsigned long flags;
+	int timeout;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	snd_cs4231_ready(chip);
+#ifdef CONFIG_SND_DEBUG
+	if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT)
+		snd_printdd("mce_up - auto calibration time out (0)\n");
+#endif
+	chip->mce_bit |= CS4231_MCE;
+	timeout = __cs4231_readb(chip, CS4231U(chip, REGSEL));
+	if (timeout == 0x80)
+		snd_printdd("mce_up [%p]: serious init problem - "
+			    "codec still busy\n",
+			    chip->port);
+	if (!(timeout & CS4231_MCE))
+		__cs4231_writeb(chip, chip->mce_bit | (timeout & 0x1f),
+				CS4231U(chip, REGSEL));
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static void snd_cs4231_mce_down(struct snd_cs4231 *chip)
+{
+	unsigned long flags, timeout;
+	int reg;
+
+	snd_cs4231_busy_wait(chip);
+	spin_lock_irqsave(&chip->lock, flags);
+#ifdef CONFIG_SND_DEBUG
+	if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT)
+		snd_printdd("mce_down [%p] - auto calibration time out (0)\n",
+			    CS4231U(chip, REGSEL));
+#endif
+	chip->mce_bit &= ~CS4231_MCE;
+	reg = __cs4231_readb(chip, CS4231U(chip, REGSEL));
+	__cs4231_writeb(chip, chip->mce_bit | (reg & 0x1f),
+			CS4231U(chip, REGSEL));
+	if (reg == 0x80)
+		snd_printdd("mce_down [%p]: serious init problem "
+			    "- codec still busy\n", chip->port);
+	if ((reg & CS4231_MCE) == 0) {
+		spin_unlock_irqrestore(&chip->lock, flags);
+		return;
+	}
+
+	/*
+	 * Wait for auto-calibration (AC) process to finish, i.e. ACI to go low.
+	 */
+	timeout = jiffies + msecs_to_jiffies(250);
+	do {
+		spin_unlock_irqrestore(&chip->lock, flags);
+		msleep(1);
+		spin_lock_irqsave(&chip->lock, flags);
+		reg = snd_cs4231_in(chip, CS4231_TEST_INIT);
+		reg &= CS4231_CALIB_IN_PROGRESS;
+	} while (reg && time_before(jiffies, timeout));
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	if (reg)
+		snd_printk(KERN_ERR
+			   "mce_down - auto calibration time out (2)\n");
+}
+
+static void snd_cs4231_advance_dma(struct cs4231_dma_control *dma_cont,
+				   struct snd_pcm_substream *substream,
+				   unsigned int *periods_sent)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	while (1) {
+		unsigned int period_size = snd_pcm_lib_period_bytes(substream);
+		unsigned int offset = period_size * (*periods_sent);
+
+		BUG_ON(period_size >= (1 << 24));
+
+		if (dma_cont->request(dma_cont,
+				      runtime->dma_addr + offset, period_size))
+			return;
+		(*periods_sent) = ((*periods_sent) + 1) % runtime->periods;
+	}
+}
+
+static void cs4231_dma_trigger(struct snd_pcm_substream *substream,
+			       unsigned int what, int on)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+	struct cs4231_dma_control *dma_cont;
+
+	if (what & CS4231_PLAYBACK_ENABLE) {
+		dma_cont = &chip->p_dma;
+		if (on) {
+			dma_cont->prepare(dma_cont, 0);
+			dma_cont->enable(dma_cont, 1);
+			snd_cs4231_advance_dma(dma_cont,
+				chip->playback_substream,
+				&chip->p_periods_sent);
+		} else {
+			dma_cont->enable(dma_cont, 0);
+		}
+	}
+	if (what & CS4231_RECORD_ENABLE) {
+		dma_cont = &chip->c_dma;
+		if (on) {
+			dma_cont->prepare(dma_cont, 1);
+			dma_cont->enable(dma_cont, 1);
+			snd_cs4231_advance_dma(dma_cont,
+				chip->capture_substream,
+				&chip->c_periods_sent);
+		} else {
+			dma_cont->enable(dma_cont, 0);
+		}
+	}
+}
+
+static int snd_cs4231_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+	int result = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_STOP:
+	{
+		unsigned int what = 0;
+		struct snd_pcm_substream *s;
+		unsigned long flags;
+
+		snd_pcm_group_for_each_entry(s, substream) {
+			if (s == chip->playback_substream) {
+				what |= CS4231_PLAYBACK_ENABLE;
+				snd_pcm_trigger_done(s, substream);
+			} else if (s == chip->capture_substream) {
+				what |= CS4231_RECORD_ENABLE;
+				snd_pcm_trigger_done(s, substream);
+			}
+		}
+
+		spin_lock_irqsave(&chip->lock, flags);
+		if (cmd == SNDRV_PCM_TRIGGER_START) {
+			cs4231_dma_trigger(substream, what, 1);
+			chip->image[CS4231_IFACE_CTRL] |= what;
+		} else {
+			cs4231_dma_trigger(substream, what, 0);
+			chip->image[CS4231_IFACE_CTRL] &= ~what;
+		}
+		snd_cs4231_out(chip, CS4231_IFACE_CTRL,
+			       chip->image[CS4231_IFACE_CTRL]);
+		spin_unlock_irqrestore(&chip->lock, flags);
+		break;
+	}
+	default:
+		result = -EINVAL;
+		break;
+	}
+
+	return result;
+}
+
+/*
+ *  CODEC I/O
+ */
+
+static unsigned char snd_cs4231_get_rate(unsigned int rate)
+{
+	int i;
+
+	for (i = 0; i < 14; i++)
+		if (rate == rates[i])
+			return freq_bits[i];
+
+	return freq_bits[13];
+}
+
+static unsigned char snd_cs4231_get_format(struct snd_cs4231 *chip, int format,
+					   int channels)
+{
+	unsigned char rformat;
+
+	rformat = CS4231_LINEAR_8;
+	switch (format) {
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		rformat = CS4231_ULAW_8;
+		break;
+	case SNDRV_PCM_FORMAT_A_LAW:
+		rformat = CS4231_ALAW_8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_LE:
+		rformat = CS4231_LINEAR_16;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE:
+		rformat = CS4231_LINEAR_16_BIG;
+		break;
+	case SNDRV_PCM_FORMAT_IMA_ADPCM:
+		rformat = CS4231_ADPCM_16;
+		break;
+	}
+	if (channels > 1)
+		rformat |= CS4231_STEREO;
+	return rformat;
+}
+
+static void snd_cs4231_calibrate_mute(struct snd_cs4231 *chip, int mute)
+{
+	unsigned long flags;
+
+	mute = mute ? 1 : 0;
+	spin_lock_irqsave(&chip->lock, flags);
+	if (chip->calibrate_mute == mute) {
+		spin_unlock_irqrestore(&chip->lock, flags);
+		return;
+	}
+	if (!mute) {
+		snd_cs4231_dout(chip, CS4231_LEFT_INPUT,
+				chip->image[CS4231_LEFT_INPUT]);
+		snd_cs4231_dout(chip, CS4231_RIGHT_INPUT,
+				chip->image[CS4231_RIGHT_INPUT]);
+		snd_cs4231_dout(chip, CS4231_LOOPBACK,
+				chip->image[CS4231_LOOPBACK]);
+	}
+	snd_cs4231_dout(chip, CS4231_AUX1_LEFT_INPUT,
+			mute ? 0x80 : chip->image[CS4231_AUX1_LEFT_INPUT]);
+	snd_cs4231_dout(chip, CS4231_AUX1_RIGHT_INPUT,
+			mute ? 0x80 : chip->image[CS4231_AUX1_RIGHT_INPUT]);
+	snd_cs4231_dout(chip, CS4231_AUX2_LEFT_INPUT,
+			mute ? 0x80 : chip->image[CS4231_AUX2_LEFT_INPUT]);
+	snd_cs4231_dout(chip, CS4231_AUX2_RIGHT_INPUT,
+			mute ? 0x80 : chip->image[CS4231_AUX2_RIGHT_INPUT]);
+	snd_cs4231_dout(chip, CS4231_LEFT_OUTPUT,
+			mute ? 0x80 : chip->image[CS4231_LEFT_OUTPUT]);
+	snd_cs4231_dout(chip, CS4231_RIGHT_OUTPUT,
+			mute ? 0x80 : chip->image[CS4231_RIGHT_OUTPUT]);
+	snd_cs4231_dout(chip, CS4231_LEFT_LINE_IN,
+			mute ? 0x80 : chip->image[CS4231_LEFT_LINE_IN]);
+	snd_cs4231_dout(chip, CS4231_RIGHT_LINE_IN,
+			mute ? 0x80 : chip->image[CS4231_RIGHT_LINE_IN]);
+	snd_cs4231_dout(chip, CS4231_MONO_CTRL,
+			mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]);
+	chip->calibrate_mute = mute;
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static void snd_cs4231_playback_format(struct snd_cs4231 *chip,
+				       struct snd_pcm_hw_params *params,
+				       unsigned char pdfr)
+{
+	unsigned long flags;
+
+	mutex_lock(&chip->mce_mutex);
+	snd_cs4231_calibrate_mute(chip, 1);
+
+	snd_cs4231_mce_up(chip);
+
+	spin_lock_irqsave(&chip->lock, flags);
+	snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT,
+		       (chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE) ?
+		       (pdfr & 0xf0) | (chip->image[CS4231_REC_FORMAT] & 0x0f) :
+		       pdfr);
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	snd_cs4231_mce_down(chip);
+
+	snd_cs4231_calibrate_mute(chip, 0);
+	mutex_unlock(&chip->mce_mutex);
+}
+
+static void snd_cs4231_capture_format(struct snd_cs4231 *chip,
+				      struct snd_pcm_hw_params *params,
+				      unsigned char cdfr)
+{
+	unsigned long flags;
+
+	mutex_lock(&chip->mce_mutex);
+	snd_cs4231_calibrate_mute(chip, 1);
+
+	snd_cs4231_mce_up(chip);
+
+	spin_lock_irqsave(&chip->lock, flags);
+	if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) {
+		snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT,
+			       ((chip->image[CS4231_PLAYBK_FORMAT]) & 0xf0) |
+			       (cdfr & 0x0f));
+		spin_unlock_irqrestore(&chip->lock, flags);
+		snd_cs4231_mce_down(chip);
+		snd_cs4231_mce_up(chip);
+		spin_lock_irqsave(&chip->lock, flags);
+	}
+	snd_cs4231_out(chip, CS4231_REC_FORMAT, cdfr);
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	snd_cs4231_mce_down(chip);
+
+	snd_cs4231_calibrate_mute(chip, 0);
+	mutex_unlock(&chip->mce_mutex);
+}
+
+/*
+ *  Timer interface
+ */
+
+static unsigned long snd_cs4231_timer_resolution(struct snd_timer *timer)
+{
+	struct snd_cs4231 *chip = snd_timer_chip(timer);
+
+	return chip->image[CS4231_PLAYBK_FORMAT] & 1 ? 9969 : 9920;
+}
+
+static int snd_cs4231_timer_start(struct snd_timer *timer)
+{
+	unsigned long flags;
+	unsigned int ticks;
+	struct snd_cs4231 *chip = snd_timer_chip(timer);
+
+	spin_lock_irqsave(&chip->lock, flags);
+	ticks = timer->sticks;
+	if ((chip->image[CS4231_ALT_FEATURE_1] & CS4231_TIMER_ENABLE) == 0 ||
+	    (unsigned char)(ticks >> 8) != chip->image[CS4231_TIMER_HIGH] ||
+	    (unsigned char)ticks != chip->image[CS4231_TIMER_LOW]) {
+		snd_cs4231_out(chip, CS4231_TIMER_HIGH,
+			       chip->image[CS4231_TIMER_HIGH] =
+			       (unsigned char) (ticks >> 8));
+		snd_cs4231_out(chip, CS4231_TIMER_LOW,
+			       chip->image[CS4231_TIMER_LOW] =
+			       (unsigned char) ticks);
+		snd_cs4231_out(chip, CS4231_ALT_FEATURE_1,
+			       chip->image[CS4231_ALT_FEATURE_1] |
+					CS4231_TIMER_ENABLE);
+	}
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return 0;
+}
+
+static int snd_cs4231_timer_stop(struct snd_timer *timer)
+{
+	unsigned long flags;
+	struct snd_cs4231 *chip = snd_timer_chip(timer);
+
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->image[CS4231_ALT_FEATURE_1] &= ~CS4231_TIMER_ENABLE;
+	snd_cs4231_out(chip, CS4231_ALT_FEATURE_1,
+		       chip->image[CS4231_ALT_FEATURE_1]);
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return 0;
+}
+
+static void __devinit snd_cs4231_init(struct snd_cs4231 *chip)
+{
+	unsigned long flags;
+
+	snd_cs4231_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printdd("init: (1)\n");
+#endif
+	snd_cs4231_mce_up(chip);
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE |
+					    CS4231_PLAYBACK_PIO |
+					    CS4231_RECORD_ENABLE |
+					    CS4231_RECORD_PIO |
+					    CS4231_CALIB_MODE);
+	chip->image[CS4231_IFACE_CTRL] |= CS4231_AUTOCALIB;
+	snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	snd_cs4231_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printdd("init: (2)\n");
+#endif
+
+	snd_cs4231_mce_up(chip);
+	spin_lock_irqsave(&chip->lock, flags);
+	snd_cs4231_out(chip, CS4231_ALT_FEATURE_1,
+			chip->image[CS4231_ALT_FEATURE_1]);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	snd_cs4231_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printdd("init: (3) - afei = 0x%x\n",
+		    chip->image[CS4231_ALT_FEATURE_1]);
+#endif
+
+	spin_lock_irqsave(&chip->lock, flags);
+	snd_cs4231_out(chip, CS4231_ALT_FEATURE_2,
+			chip->image[CS4231_ALT_FEATURE_2]);
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	snd_cs4231_mce_up(chip);
+	spin_lock_irqsave(&chip->lock, flags);
+	snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT,
+			chip->image[CS4231_PLAYBK_FORMAT]);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	snd_cs4231_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printdd("init: (4)\n");
+#endif
+
+	snd_cs4231_mce_up(chip);
+	spin_lock_irqsave(&chip->lock, flags);
+	snd_cs4231_out(chip, CS4231_REC_FORMAT, chip->image[CS4231_REC_FORMAT]);
+	spin_unlock_irqrestore(&chip->lock, flags);
+	snd_cs4231_mce_down(chip);
+
+#ifdef SNDRV_DEBUG_MCE
+	snd_printdd("init: (5)\n");
+#endif
+}
+
+static int snd_cs4231_open(struct snd_cs4231 *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	mutex_lock(&chip->open_mutex);
+	if ((chip->mode & mode)) {
+		mutex_unlock(&chip->open_mutex);
+		return -EAGAIN;
+	}
+	if (chip->mode & CS4231_MODE_OPEN) {
+		chip->mode |= mode;
+		mutex_unlock(&chip->open_mutex);
+		return 0;
+	}
+	/* ok. now enable and ack CODEC IRQ */
+	spin_lock_irqsave(&chip->lock, flags);
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ |
+		       CS4231_RECORD_IRQ |
+		       CS4231_TIMER_IRQ);
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
+	__cs4231_writeb(chip, 0, CS4231U(chip, STATUS));	/* clear IRQ */
+	__cs4231_writeb(chip, 0, CS4231U(chip, STATUS));	/* clear IRQ */
+
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ |
+		       CS4231_RECORD_IRQ |
+		       CS4231_TIMER_IRQ);
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	chip->mode = mode;
+	mutex_unlock(&chip->open_mutex);
+	return 0;
+}
+
+static void snd_cs4231_close(struct snd_cs4231 *chip, unsigned int mode)
+{
+	unsigned long flags;
+
+	mutex_lock(&chip->open_mutex);
+	chip->mode &= ~mode;
+	if (chip->mode & CS4231_MODE_OPEN) {
+		mutex_unlock(&chip->open_mutex);
+		return;
+	}
+	snd_cs4231_calibrate_mute(chip, 1);
+
+	/* disable IRQ */
+	spin_lock_irqsave(&chip->lock, flags);
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
+	__cs4231_writeb(chip, 0, CS4231U(chip, STATUS));	/* clear IRQ */
+	__cs4231_writeb(chip, 0, CS4231U(chip, STATUS));	/* clear IRQ */
+
+	/* now disable record & playback */
+
+	if (chip->image[CS4231_IFACE_CTRL] &
+	    (CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
+	     CS4231_RECORD_ENABLE | CS4231_RECORD_PIO)) {
+		spin_unlock_irqrestore(&chip->lock, flags);
+		snd_cs4231_mce_up(chip);
+		spin_lock_irqsave(&chip->lock, flags);
+		chip->image[CS4231_IFACE_CTRL] &=
+			~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
+			  CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
+		snd_cs4231_out(chip, CS4231_IFACE_CTRL,
+				chip->image[CS4231_IFACE_CTRL]);
+		spin_unlock_irqrestore(&chip->lock, flags);
+		snd_cs4231_mce_down(chip);
+		spin_lock_irqsave(&chip->lock, flags);
+	}
+
+	/* clear IRQ again */
+	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
+	__cs4231_writeb(chip, 0, CS4231U(chip, STATUS));	/* clear IRQ */
+	__cs4231_writeb(chip, 0, CS4231U(chip, STATUS));	/* clear IRQ */
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	snd_cs4231_calibrate_mute(chip, 0);
+
+	chip->mode = 0;
+	mutex_unlock(&chip->open_mutex);
+}
+
+/*
+ *  timer open/close
+ */
+
+static int snd_cs4231_timer_open(struct snd_timer *timer)
+{
+	struct snd_cs4231 *chip = snd_timer_chip(timer);
+	snd_cs4231_open(chip, CS4231_MODE_TIMER);
+	return 0;
+}
+
+static int snd_cs4231_timer_close(struct snd_timer *timer)
+{
+	struct snd_cs4231 *chip = snd_timer_chip(timer);
+	snd_cs4231_close(chip, CS4231_MODE_TIMER);
+	return 0;
+}
+
+static struct snd_timer_hardware snd_cs4231_timer_table = {
+	.flags		=	SNDRV_TIMER_HW_AUTO,
+	.resolution	=	9945,
+	.ticks		=	65535,
+	.open		=	snd_cs4231_timer_open,
+	.close		=	snd_cs4231_timer_close,
+	.c_resolution	=	snd_cs4231_timer_resolution,
+	.start		=	snd_cs4231_timer_start,
+	.stop		=	snd_cs4231_timer_stop,
+};
+
+/*
+ *  ok.. exported functions..
+ */
+
+static int snd_cs4231_playback_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+	unsigned char new_pdfr;
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	new_pdfr = snd_cs4231_get_format(chip, params_format(hw_params),
+					 params_channels(hw_params)) |
+		snd_cs4231_get_rate(params_rate(hw_params));
+	snd_cs4231_playback_format(chip, hw_params, new_pdfr);
+
+	return 0;
+}
+
+static int snd_cs4231_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE |
+					    CS4231_PLAYBACK_PIO);
+
+	BUG_ON(runtime->period_size > 0xffff + 1);
+
+	chip->p_periods_sent = 0;
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return 0;
+}
+
+static int snd_cs4231_capture_hw_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+	unsigned char new_cdfr;
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	new_cdfr = snd_cs4231_get_format(chip, params_format(hw_params),
+					 params_channels(hw_params)) |
+		snd_cs4231_get_rate(params_rate(hw_params));
+	snd_cs4231_capture_format(chip, hw_params, new_cdfr);
+
+	return 0;
+}
+
+static int snd_cs4231_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_RECORD_ENABLE |
+					    CS4231_RECORD_PIO);
+
+
+	chip->c_periods_sent = 0;
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return 0;
+}
+
+static void snd_cs4231_overrange(struct snd_cs4231 *chip)
+{
+	unsigned long flags;
+	unsigned char res;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	res = snd_cs4231_in(chip, CS4231_TEST_INIT);
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	/* detect overrange only above 0dB; may be user selectable? */
+	if (res & (0x08 | 0x02))
+		chip->capture_substream->runtime->overrange++;
+}
+
+static void snd_cs4231_play_callback(struct snd_cs4231 *chip)
+{
+	if (chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE) {
+		snd_pcm_period_elapsed(chip->playback_substream);
+		snd_cs4231_advance_dma(&chip->p_dma, chip->playback_substream,
+					    &chip->p_periods_sent);
+	}
+}
+
+static void snd_cs4231_capture_callback(struct snd_cs4231 *chip)
+{
+	if (chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE) {
+		snd_pcm_period_elapsed(chip->capture_substream);
+		snd_cs4231_advance_dma(&chip->c_dma, chip->capture_substream,
+					    &chip->c_periods_sent);
+	}
+}
+
+static snd_pcm_uframes_t snd_cs4231_playback_pointer(
+					struct snd_pcm_substream *substream)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+	struct cs4231_dma_control *dma_cont = &chip->p_dma;
+	size_t ptr;
+
+	if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE))
+		return 0;
+	ptr = dma_cont->address(dma_cont);
+	if (ptr != 0)
+		ptr -= substream->runtime->dma_addr;
+
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_cs4231_capture_pointer(
+					struct snd_pcm_substream *substream)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+	struct cs4231_dma_control *dma_cont = &chip->c_dma;
+	size_t ptr;
+
+	if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE))
+		return 0;
+	ptr = dma_cont->address(dma_cont);
+	if (ptr != 0)
+		ptr -= substream->runtime->dma_addr;
+
+	return bytes_to_frames(substream->runtime, ptr);
+}
+
+static int __devinit snd_cs4231_probe(struct snd_cs4231 *chip)
+{
+	unsigned long flags;
+	int i;
+	int id = 0;
+	int vers = 0;
+	unsigned char *ptr;
+
+	for (i = 0; i < 50; i++) {
+		mb();
+		if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT)
+			msleep(2);
+		else {
+			spin_lock_irqsave(&chip->lock, flags);
+			snd_cs4231_out(chip, CS4231_MISC_INFO, CS4231_MODE2);
+			id = snd_cs4231_in(chip, CS4231_MISC_INFO) & 0x0f;
+			vers = snd_cs4231_in(chip, CS4231_VERSION);
+			spin_unlock_irqrestore(&chip->lock, flags);
+			if (id == 0x0a)
+				break;	/* this is valid value */
+		}
+	}
+	snd_printdd("cs4231: port = %p, id = 0x%x\n", chip->port, id);
+	if (id != 0x0a)
+		return -ENODEV;	/* no valid device found */
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	/* clear any pendings IRQ */
+	__cs4231_readb(chip, CS4231U(chip, STATUS));
+	__cs4231_writeb(chip, 0, CS4231U(chip, STATUS));
+	mb();
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	chip->image[CS4231_MISC_INFO] = CS4231_MODE2;
+	chip->image[CS4231_IFACE_CTRL] =
+		chip->image[CS4231_IFACE_CTRL] & ~CS4231_SINGLE_DMA;
+	chip->image[CS4231_ALT_FEATURE_1] = 0x80;
+	chip->image[CS4231_ALT_FEATURE_2] = 0x01;
+	if (vers & 0x20)
+		chip->image[CS4231_ALT_FEATURE_2] |= 0x02;
+
+	ptr = (unsigned char *) &chip->image;
+
+	snd_cs4231_mce_down(chip);
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	for (i = 0; i < 32; i++)	/* ok.. fill all CS4231 registers */
+		snd_cs4231_out(chip, i, *ptr++);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	snd_cs4231_mce_up(chip);
+
+	snd_cs4231_mce_down(chip);
+
+	mdelay(2);
+
+	return 0;		/* all things are ok.. */
+}
+
+static struct snd_pcm_hardware snd_cs4231_playback = {
+	.info			= SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_SYNC_START,
+	.formats		= SNDRV_PCM_FMTBIT_MU_LAW |
+				  SNDRV_PCM_FMTBIT_A_LAW |
+				  SNDRV_PCM_FMTBIT_IMA_ADPCM |
+				  SNDRV_PCM_FMTBIT_U8 |
+				  SNDRV_PCM_FMTBIT_S16_LE |
+				  SNDRV_PCM_FMTBIT_S16_BE,
+	.rates			= SNDRV_PCM_RATE_KNOT |
+				  SNDRV_PCM_RATE_8000_48000,
+	.rate_min		= 5510,
+	.rate_max		= 48000,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= 32 * 1024,
+	.period_bytes_min	= 64,
+	.period_bytes_max	= 32 * 1024,
+	.periods_min		= 1,
+	.periods_max		= 1024,
+};
+
+static struct snd_pcm_hardware snd_cs4231_capture = {
+	.info			= SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_SYNC_START,
+	.formats		= SNDRV_PCM_FMTBIT_MU_LAW |
+				  SNDRV_PCM_FMTBIT_A_LAW |
+				  SNDRV_PCM_FMTBIT_IMA_ADPCM |
+				  SNDRV_PCM_FMTBIT_U8 |
+				  SNDRV_PCM_FMTBIT_S16_LE |
+				  SNDRV_PCM_FMTBIT_S16_BE,
+	.rates			= SNDRV_PCM_RATE_KNOT |
+				  SNDRV_PCM_RATE_8000_48000,
+	.rate_min		= 5510,
+	.rate_max		= 48000,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= 32 * 1024,
+	.period_bytes_min	= 64,
+	.period_bytes_max	= 32 * 1024,
+	.periods_min		= 1,
+	.periods_max		= 1024,
+};
+
+static int snd_cs4231_playback_open(struct snd_pcm_substream *substream)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	runtime->hw = snd_cs4231_playback;
+
+	err = snd_cs4231_open(chip, CS4231_MODE_PLAY);
+	if (err < 0) {
+		snd_free_pages(runtime->dma_area, runtime->dma_bytes);
+		return err;
+	}
+	chip->playback_substream = substream;
+	chip->p_periods_sent = 0;
+	snd_pcm_set_sync(substream);
+	snd_cs4231_xrate(runtime);
+
+	return 0;
+}
+
+static int snd_cs4231_capture_open(struct snd_pcm_substream *substream)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	runtime->hw = snd_cs4231_capture;
+
+	err = snd_cs4231_open(chip, CS4231_MODE_RECORD);
+	if (err < 0) {
+		snd_free_pages(runtime->dma_area, runtime->dma_bytes);
+		return err;
+	}
+	chip->capture_substream = substream;
+	chip->c_periods_sent = 0;
+	snd_pcm_set_sync(substream);
+	snd_cs4231_xrate(runtime);
+
+	return 0;
+}
+
+static int snd_cs4231_playback_close(struct snd_pcm_substream *substream)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+
+	snd_cs4231_close(chip, CS4231_MODE_PLAY);
+	chip->playback_substream = NULL;
+
+	return 0;
+}
+
+static int snd_cs4231_capture_close(struct snd_pcm_substream *substream)
+{
+	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
+
+	snd_cs4231_close(chip, CS4231_MODE_RECORD);
+	chip->capture_substream = NULL;
+
+	return 0;
+}
+
+/* XXX We can do some power-management, in particular on EBUS using
+ * XXX the audio AUXIO register...
+ */
+
+static struct snd_pcm_ops snd_cs4231_playback_ops = {
+	.open		=	snd_cs4231_playback_open,
+	.close		=	snd_cs4231_playback_close,
+	.ioctl		=	snd_pcm_lib_ioctl,
+	.hw_params	=	snd_cs4231_playback_hw_params,
+	.hw_free	=	snd_pcm_lib_free_pages,
+	.prepare	=	snd_cs4231_playback_prepare,
+	.trigger	=	snd_cs4231_trigger,
+	.pointer	=	snd_cs4231_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_cs4231_capture_ops = {
+	.open		=	snd_cs4231_capture_open,
+	.close		=	snd_cs4231_capture_close,
+	.ioctl		=	snd_pcm_lib_ioctl,
+	.hw_params	=	snd_cs4231_capture_hw_params,
+	.hw_free	=	snd_pcm_lib_free_pages,
+	.prepare	=	snd_cs4231_capture_prepare,
+	.trigger	=	snd_cs4231_trigger,
+	.pointer	=	snd_cs4231_capture_pointer,
+};
+
+static int __devinit snd_cs4231_pcm(struct snd_card *card)
+{
+	struct snd_cs4231 *chip = card->private_data;
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(card, "CS4231", 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+			&snd_cs4231_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&snd_cs4231_capture_ops);
+
+	/* global setup */
+	pcm->private_data = chip;
+	pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX;
+	strcpy(pcm->name, "CS4231");
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					      &chip->op->dev,
+					      64 * 1024, 128 * 1024);
+
+	chip->pcm = pcm;
+
+	return 0;
+}
+
+static int __devinit snd_cs4231_timer(struct snd_card *card)
+{
+	struct snd_cs4231 *chip = card->private_data;
+	struct snd_timer *timer;
+	struct snd_timer_id tid;
+	int err;
+
+	/* Timer initialization */
+	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
+	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
+	tid.card = card->number;
+	tid.device = 0;
+	tid.subdevice = 0;
+	err = snd_timer_new(card, "CS4231", &tid, &timer);
+	if (err < 0)
+		return err;
+	strcpy(timer->name, "CS4231");
+	timer->private_data = chip;
+	timer->hw = snd_cs4231_timer_table;
+	chip->timer = timer;
+
+	return 0;
+}
+
+/*
+ *  MIXER part
+ */
+
+static int snd_cs4231_info_mux(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = {
+		"Line", "CD", "Mic", "Mix"
+	};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 2;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3)
+		uinfo->value.enumerated.item = 3;
+	strcpy(uinfo->value.enumerated.name,
+		texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int snd_cs4231_get_mux(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	ucontrol->value.enumerated.item[0] =
+		(chip->image[CS4231_LEFT_INPUT] & CS4231_MIXS_ALL) >> 6;
+	ucontrol->value.enumerated.item[1] =
+		(chip->image[CS4231_RIGHT_INPUT] & CS4231_MIXS_ALL) >> 6;
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return 0;
+}
+
+static int snd_cs4231_put_mux(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	unsigned short left, right;
+	int change;
+
+	if (ucontrol->value.enumerated.item[0] > 3 ||
+	    ucontrol->value.enumerated.item[1] > 3)
+		return -EINVAL;
+	left = ucontrol->value.enumerated.item[0] << 6;
+	right = ucontrol->value.enumerated.item[1] << 6;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	left = (chip->image[CS4231_LEFT_INPUT] & ~CS4231_MIXS_ALL) | left;
+	right = (chip->image[CS4231_RIGHT_INPUT] & ~CS4231_MIXS_ALL) | right;
+	change = left != chip->image[CS4231_LEFT_INPUT] ||
+		 right != chip->image[CS4231_RIGHT_INPUT];
+	snd_cs4231_out(chip, CS4231_LEFT_INPUT, left);
+	snd_cs4231_out(chip, CS4231_RIGHT_INPUT, right);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return change;
+}
+
+static int snd_cs4231_info_single(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = (mask == 1) ?
+		SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+
+	return 0;
+}
+
+static int snd_cs4231_get_single(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask;
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	if (invert)
+		ucontrol->value.integer.value[0] =
+			(mask - ucontrol->value.integer.value[0]);
+
+	return 0;
+}
+
+static int snd_cs4231_put_single(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change;
+	unsigned short val;
+
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	val = (chip->image[reg] & ~(mask << shift)) | val;
+	change = val != chip->image[reg];
+	snd_cs4231_out(chip, reg, val);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return change;
+}
+
+static int snd_cs4231_info_double(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	uinfo->type = mask == 1 ?
+		SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+
+	return 0;
+}
+
+static int snd_cs4231_get_double(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	ucontrol->value.integer.value[0] =
+		(chip->image[left_reg] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] =
+		(chip->image[right_reg] >> shift_right) & mask;
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	if (invert) {
+		ucontrol->value.integer.value[0] =
+			(mask - ucontrol->value.integer.value[0]);
+		ucontrol->value.integer.value[1] =
+			(mask - ucontrol->value.integer.value[1]);
+	}
+
+	return 0;
+}
+
+static int snd_cs4231_put_double(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
+	unsigned long flags;
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change;
+	unsigned short val1, val2;
+
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
+	val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2;
+	change = val1 != chip->image[left_reg];
+	change |= val2 != chip->image[right_reg];
+	snd_cs4231_out(chip, left_reg, val1);
+	snd_cs4231_out(chip, right_reg, val2);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return change;
+}
+
+#define CS4231_SINGLE(xname, xindex, reg, shift, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), .index = (xindex), \
+  .info = snd_cs4231_info_single,	\
+  .get = snd_cs4231_get_single, .put = snd_cs4231_put_single,	\
+  .private_value = (reg) | ((shift) << 8) | ((mask) << 16) | ((invert) << 24) }
+
+#define CS4231_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, \
+			shift_right, mask, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), .index = (xindex), \
+  .info = snd_cs4231_info_double,	\
+  .get = snd_cs4231_get_double, .put = snd_cs4231_put_double,	\
+  .private_value = (left_reg) | ((right_reg) << 8) | ((shift_left) << 16) | \
+		   ((shift_right) << 19) | ((mask) << 24) | ((invert) << 22) }
+
+static struct snd_kcontrol_new snd_cs4231_controls[] __devinitdata = {
+CS4231_DOUBLE("PCM Playback Switch", 0, CS4231_LEFT_OUTPUT,
+		CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("PCM Playback Volume", 0, CS4231_LEFT_OUTPUT,
+		CS4231_RIGHT_OUTPUT, 0, 0, 63, 1),
+CS4231_DOUBLE("Line Playback Switch", 0, CS4231_LEFT_LINE_IN,
+		CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
+CS4231_DOUBLE("Line Playback Volume", 0, CS4231_LEFT_LINE_IN,
+		CS4231_RIGHT_LINE_IN, 0, 0, 31, 1),
+CS4231_DOUBLE("Aux Playback Switch", 0, CS4231_AUX1_LEFT_INPUT,
+		CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("Aux Playback Volume", 0, CS4231_AUX1_LEFT_INPUT,
+		CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1),
+CS4231_DOUBLE("Aux Playback Switch", 1, CS4231_AUX2_LEFT_INPUT,
+		CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+CS4231_DOUBLE("Aux Playback Volume", 1, CS4231_AUX2_LEFT_INPUT,
+		CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1),
+CS4231_SINGLE("Mono Playback Switch", 0, CS4231_MONO_CTRL, 7, 1, 1),
+CS4231_SINGLE("Mono Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1),
+CS4231_SINGLE("Mono Output Playback Switch", 0, CS4231_MONO_CTRL, 6, 1, 1),
+CS4231_SINGLE("Mono Output Playback Bypass", 0, CS4231_MONO_CTRL, 5, 1, 0),
+CS4231_DOUBLE("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 0, 0,
+		15, 0),
+{
+	.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name	= "Capture Source",
+	.info	= snd_cs4231_info_mux,
+	.get	= snd_cs4231_get_mux,
+	.put	= snd_cs4231_put_mux,
+},
+CS4231_DOUBLE("Mic Boost", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 5, 5,
+		1, 0),
+CS4231_SINGLE("Loopback Capture Switch", 0, CS4231_LOOPBACK, 0, 1, 0),
+CS4231_SINGLE("Loopback Capture Volume", 0, CS4231_LOOPBACK, 2, 63, 1),
+/* SPARC specific uses of XCTL{0,1} general purpose outputs.  */
+CS4231_SINGLE("Line Out Switch", 0, CS4231_PIN_CTRL, 6, 1, 1),
+CS4231_SINGLE("Headphone Out Switch", 0, CS4231_PIN_CTRL, 7, 1, 1)
+};
+
+static int __devinit snd_cs4231_mixer(struct snd_card *card)
+{
+	struct snd_cs4231 *chip = card->private_data;
+	int err, idx;
+
+	if (snd_BUG_ON(!chip || !chip->pcm))
+		return -EINVAL;
+
+	strcpy(card->mixername, chip->pcm->name);
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_cs4231_controls); idx++) {
+		err = snd_ctl_add(card,
+				 snd_ctl_new1(&snd_cs4231_controls[idx], chip));
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int dev;
+
+static int __devinit cs4231_attach_begin(struct snd_card **rcard)
+{
+	struct snd_card *card;
+	struct snd_cs4231 *chip;
+	int err;
+
+	*rcard = NULL;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_cs4231), &card);
+	if (err < 0)
+		return err;
+
+	strcpy(card->driver, "CS4231");
+	strcpy(card->shortname, "Sun CS4231");
+
+	chip = card->private_data;
+	chip->card = card;
+
+	*rcard = card;
+	return 0;
+}
+
+static int __devinit cs4231_attach_finish(struct snd_card *card)
+{
+	struct snd_cs4231 *chip = card->private_data;
+	int err;
+
+	err = snd_cs4231_pcm(card);
+	if (err < 0)
+		goto out_err;
+
+	err = snd_cs4231_mixer(card);
+	if (err < 0)
+		goto out_err;
+
+	err = snd_cs4231_timer(card);
+	if (err < 0)
+		goto out_err;
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto out_err;
+
+	dev_set_drvdata(&chip->op->dev, chip);
+
+	dev++;
+	return 0;
+
+out_err:
+	snd_card_free(card);
+	return err;
+}
+
+#ifdef SBUS_SUPPORT
+
+static irqreturn_t snd_cs4231_sbus_interrupt(int irq, void *dev_id)
+{
+	unsigned long flags;
+	unsigned char status;
+	u32 csr;
+	struct snd_cs4231 *chip = dev_id;
+
+	/*This is IRQ is not raised by the cs4231*/
+	if (!(__cs4231_readb(chip, CS4231U(chip, STATUS)) & CS4231_GLOBALIRQ))
+		return IRQ_NONE;
+
+	/* ACK the APC interrupt. */
+	csr = sbus_readl(chip->port + APCCSR);
+
+	sbus_writel(csr, chip->port + APCCSR);
+
+	if ((csr & APC_PDMA_READY) &&
+	    (csr & APC_PLAY_INT) &&
+	    (csr & APC_XINT_PNVA) &&
+	    !(csr & APC_XINT_EMPT))
+			snd_cs4231_play_callback(chip);
+
+	if ((csr & APC_CDMA_READY) &&
+	    (csr & APC_CAPT_INT) &&
+	    (csr & APC_XINT_CNVA) &&
+	    !(csr & APC_XINT_EMPT))
+			snd_cs4231_capture_callback(chip);
+
+	status = snd_cs4231_in(chip, CS4231_IRQ_STATUS);
+
+	if (status & CS4231_TIMER_IRQ) {
+		if (chip->timer)
+			snd_timer_interrupt(chip->timer, chip->timer->sticks);
+	}
+
+	if ((status & CS4231_RECORD_IRQ) && (csr & APC_CDMA_READY))
+		snd_cs4231_overrange(chip);
+
+	/* ACK the CS4231 interrupt. */
+	spin_lock_irqsave(&chip->lock, flags);
+	snd_cs4231_outm(chip, CS4231_IRQ_STATUS, ~CS4231_ALL_IRQS | ~status, 0);
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * SBUS DMA routines
+ */
+
+static int sbus_dma_request(struct cs4231_dma_control *dma_cont,
+			    dma_addr_t bus_addr, size_t len)
+{
+	unsigned long flags;
+	u32 test, csr;
+	int err;
+	struct sbus_dma_info *base = &dma_cont->sbus_info;
+
+	if (len >= (1 << 24))
+		return -EINVAL;
+	spin_lock_irqsave(&base->lock, flags);
+	csr = sbus_readl(base->regs + APCCSR);
+	err = -EINVAL;
+	test = APC_CDMA_READY;
+	if (base->dir == APC_PLAY)
+		test = APC_PDMA_READY;
+	if (!(csr & test))
+		goto out;
+	err = -EBUSY;
+	test = APC_XINT_CNVA;
+	if (base->dir == APC_PLAY)
+		test = APC_XINT_PNVA;
+	if (!(csr & test))
+		goto out;
+	err = 0;
+	sbus_writel(bus_addr, base->regs + base->dir + APCNVA);
+	sbus_writel(len, base->regs + base->dir + APCNC);
+out:
+	spin_unlock_irqrestore(&base->lock, flags);
+	return err;
+}
+
+static void sbus_dma_prepare(struct cs4231_dma_control *dma_cont, int d)
+{
+	unsigned long flags;
+	u32 csr, test;
+	struct sbus_dma_info *base = &dma_cont->sbus_info;
+
+	spin_lock_irqsave(&base->lock, flags);
+	csr = sbus_readl(base->regs + APCCSR);
+	test =  APC_GENL_INT | APC_PLAY_INT | APC_XINT_ENA |
+		APC_XINT_PLAY | APC_XINT_PEMP | APC_XINT_GENL |
+		 APC_XINT_PENA;
+	if (base->dir == APC_RECORD)
+		test = APC_GENL_INT | APC_CAPT_INT | APC_XINT_ENA |
+			APC_XINT_CAPT | APC_XINT_CEMP | APC_XINT_GENL;
+	csr |= test;
+	sbus_writel(csr, base->regs + APCCSR);
+	spin_unlock_irqrestore(&base->lock, flags);
+}
+
+static void sbus_dma_enable(struct cs4231_dma_control *dma_cont, int on)
+{
+	unsigned long flags;
+	u32 csr, shift;
+	struct sbus_dma_info *base = &dma_cont->sbus_info;
+
+	spin_lock_irqsave(&base->lock, flags);
+	if (!on) {
+		sbus_writel(0, base->regs + base->dir + APCNC);
+		sbus_writel(0, base->regs + base->dir + APCNVA);
+		if (base->dir == APC_PLAY) {
+			sbus_writel(0, base->regs + base->dir + APCC);
+			sbus_writel(0, base->regs + base->dir + APCVA);
+		}
+
+		udelay(1200);
+	}
+	csr = sbus_readl(base->regs + APCCSR);
+	shift = 0;
+	if (base->dir == APC_PLAY)
+		shift = 1;
+	if (on)
+		csr &= ~(APC_CPAUSE << shift);
+	else
+		csr |= (APC_CPAUSE << shift);
+	sbus_writel(csr, base->regs + APCCSR);
+	if (on)
+		csr |= (APC_CDMA_READY << shift);
+	else
+		csr &= ~(APC_CDMA_READY << shift);
+	sbus_writel(csr, base->regs + APCCSR);
+
+	spin_unlock_irqrestore(&base->lock, flags);
+}
+
+static unsigned int sbus_dma_addr(struct cs4231_dma_control *dma_cont)
+{
+	struct sbus_dma_info *base = &dma_cont->sbus_info;
+
+	return sbus_readl(base->regs + base->dir + APCVA);
+}
+
+/*
+ * Init and exit routines
+ */
+
+static int snd_cs4231_sbus_free(struct snd_cs4231 *chip)
+{
+	struct platform_device *op = chip->op;
+
+	if (chip->irq[0])
+		free_irq(chip->irq[0], chip);
+
+	if (chip->port)
+		of_iounmap(&op->resource[0], chip->port, chip->regs_size);
+
+	return 0;
+}
+
+static int snd_cs4231_sbus_dev_free(struct snd_device *device)
+{
+	struct snd_cs4231 *cp = device->device_data;
+
+	return snd_cs4231_sbus_free(cp);
+}
+
+static struct snd_device_ops snd_cs4231_sbus_dev_ops = {
+	.dev_free	=	snd_cs4231_sbus_dev_free,
+};
+
+static int __devinit snd_cs4231_sbus_create(struct snd_card *card,
+					    struct platform_device *op,
+					    int dev)
+{
+	struct snd_cs4231 *chip = card->private_data;
+	int err;
+
+	spin_lock_init(&chip->lock);
+	spin_lock_init(&chip->c_dma.sbus_info.lock);
+	spin_lock_init(&chip->p_dma.sbus_info.lock);
+	mutex_init(&chip->mce_mutex);
+	mutex_init(&chip->open_mutex);
+	chip->op = op;
+	chip->regs_size = resource_size(&op->resource[0]);
+	memcpy(&chip->image, &snd_cs4231_original_image,
+	       sizeof(snd_cs4231_original_image));
+
+	chip->port = of_ioremap(&op->resource[0], 0,
+				chip->regs_size, "cs4231");
+	if (!chip->port) {
+		snd_printdd("cs4231-%d: Unable to map chip registers.\n", dev);
+		return -EIO;
+	}
+
+	chip->c_dma.sbus_info.regs = chip->port;
+	chip->p_dma.sbus_info.regs = chip->port;
+	chip->c_dma.sbus_info.dir = APC_RECORD;
+	chip->p_dma.sbus_info.dir = APC_PLAY;
+
+	chip->p_dma.prepare = sbus_dma_prepare;
+	chip->p_dma.enable = sbus_dma_enable;
+	chip->p_dma.request = sbus_dma_request;
+	chip->p_dma.address = sbus_dma_addr;
+
+	chip->c_dma.prepare = sbus_dma_prepare;
+	chip->c_dma.enable = sbus_dma_enable;
+	chip->c_dma.request = sbus_dma_request;
+	chip->c_dma.address = sbus_dma_addr;
+
+	if (request_irq(op->archdata.irqs[0], snd_cs4231_sbus_interrupt,
+			IRQF_SHARED, "cs4231", chip)) {
+		snd_printdd("cs4231-%d: Unable to grab SBUS IRQ %d\n",
+			    dev, op->archdata.irqs[0]);
+		snd_cs4231_sbus_free(chip);
+		return -EBUSY;
+	}
+	chip->irq[0] = op->archdata.irqs[0];
+
+	if (snd_cs4231_probe(chip) < 0) {
+		snd_cs4231_sbus_free(chip);
+		return -ENODEV;
+	}
+	snd_cs4231_init(chip);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+				  chip, &snd_cs4231_sbus_dev_ops)) < 0) {
+		snd_cs4231_sbus_free(chip);
+		return err;
+	}
+
+	return 0;
+}
+
+static int __devinit cs4231_sbus_probe(struct platform_device *op, const struct of_device_id *match)
+{
+	struct resource *rp = &op->resource[0];
+	struct snd_card *card;
+	int err;
+
+	err = cs4231_attach_begin(&card);
+	if (err)
+		return err;
+
+	sprintf(card->longname, "%s at 0x%02lx:0x%016Lx, irq %d",
+		card->shortname,
+		rp->flags & 0xffL,
+		(unsigned long long)rp->start,
+		op->archdata.irqs[0]);
+
+	err = snd_cs4231_sbus_create(card, op, dev);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	return cs4231_attach_finish(card);
+}
+#endif
+
+#ifdef EBUS_SUPPORT
+
+static void snd_cs4231_ebus_play_callback(struct ebus_dma_info *p, int event,
+					  void *cookie)
+{
+	struct snd_cs4231 *chip = cookie;
+
+	snd_cs4231_play_callback(chip);
+}
+
+static void snd_cs4231_ebus_capture_callback(struct ebus_dma_info *p,
+					     int event, void *cookie)
+{
+	struct snd_cs4231 *chip = cookie;
+
+	snd_cs4231_capture_callback(chip);
+}
+
+/*
+ * EBUS DMA wrappers
+ */
+
+static int _ebus_dma_request(struct cs4231_dma_control *dma_cont,
+			     dma_addr_t bus_addr, size_t len)
+{
+	return ebus_dma_request(&dma_cont->ebus_info, bus_addr, len);
+}
+
+static void _ebus_dma_enable(struct cs4231_dma_control *dma_cont, int on)
+{
+	ebus_dma_enable(&dma_cont->ebus_info, on);
+}
+
+static void _ebus_dma_prepare(struct cs4231_dma_control *dma_cont, int dir)
+{
+	ebus_dma_prepare(&dma_cont->ebus_info, dir);
+}
+
+static unsigned int _ebus_dma_addr(struct cs4231_dma_control *dma_cont)
+{
+	return ebus_dma_addr(&dma_cont->ebus_info);
+}
+
+/*
+ * Init and exit routines
+ */
+
+static int snd_cs4231_ebus_free(struct snd_cs4231 *chip)
+{
+	struct platform_device *op = chip->op;
+
+	if (chip->c_dma.ebus_info.regs) {
+		ebus_dma_unregister(&chip->c_dma.ebus_info);
+		of_iounmap(&op->resource[2], chip->c_dma.ebus_info.regs, 0x10);
+	}
+	if (chip->p_dma.ebus_info.regs) {
+		ebus_dma_unregister(&chip->p_dma.ebus_info);
+		of_iounmap(&op->resource[1], chip->p_dma.ebus_info.regs, 0x10);
+	}
+
+	if (chip->port)
+		of_iounmap(&op->resource[0], chip->port, 0x10);
+
+	return 0;
+}
+
+static int snd_cs4231_ebus_dev_free(struct snd_device *device)
+{
+	struct snd_cs4231 *cp = device->device_data;
+
+	return snd_cs4231_ebus_free(cp);
+}
+
+static struct snd_device_ops snd_cs4231_ebus_dev_ops = {
+	.dev_free	=	snd_cs4231_ebus_dev_free,
+};
+
+static int __devinit snd_cs4231_ebus_create(struct snd_card *card,
+					    struct platform_device *op,
+					    int dev)
+{
+	struct snd_cs4231 *chip = card->private_data;
+	int err;
+
+	spin_lock_init(&chip->lock);
+	spin_lock_init(&chip->c_dma.ebus_info.lock);
+	spin_lock_init(&chip->p_dma.ebus_info.lock);
+	mutex_init(&chip->mce_mutex);
+	mutex_init(&chip->open_mutex);
+	chip->flags |= CS4231_FLAG_EBUS;
+	chip->op = op;
+	memcpy(&chip->image, &snd_cs4231_original_image,
+	       sizeof(snd_cs4231_original_image));
+	strcpy(chip->c_dma.ebus_info.name, "cs4231(capture)");
+	chip->c_dma.ebus_info.flags = EBUS_DMA_FLAG_USE_EBDMA_HANDLER;
+	chip->c_dma.ebus_info.callback = snd_cs4231_ebus_capture_callback;
+	chip->c_dma.ebus_info.client_cookie = chip;
+	chip->c_dma.ebus_info.irq = op->archdata.irqs[0];
+	strcpy(chip->p_dma.ebus_info.name, "cs4231(play)");
+	chip->p_dma.ebus_info.flags = EBUS_DMA_FLAG_USE_EBDMA_HANDLER;
+	chip->p_dma.ebus_info.callback = snd_cs4231_ebus_play_callback;
+	chip->p_dma.ebus_info.client_cookie = chip;
+	chip->p_dma.ebus_info.irq = op->archdata.irqs[1];
+
+	chip->p_dma.prepare = _ebus_dma_prepare;
+	chip->p_dma.enable = _ebus_dma_enable;
+	chip->p_dma.request = _ebus_dma_request;
+	chip->p_dma.address = _ebus_dma_addr;
+
+	chip->c_dma.prepare = _ebus_dma_prepare;
+	chip->c_dma.enable = _ebus_dma_enable;
+	chip->c_dma.request = _ebus_dma_request;
+	chip->c_dma.address = _ebus_dma_addr;
+
+	chip->port = of_ioremap(&op->resource[0], 0, 0x10, "cs4231");
+	chip->p_dma.ebus_info.regs =
+		of_ioremap(&op->resource[1], 0, 0x10, "cs4231_pdma");
+	chip->c_dma.ebus_info.regs =
+		of_ioremap(&op->resource[2], 0, 0x10, "cs4231_cdma");
+	if (!chip->port || !chip->p_dma.ebus_info.regs ||
+	    !chip->c_dma.ebus_info.regs) {
+		snd_cs4231_ebus_free(chip);
+		snd_printdd("cs4231-%d: Unable to map chip registers.\n", dev);
+		return -EIO;
+	}
+
+	if (ebus_dma_register(&chip->c_dma.ebus_info)) {
+		snd_cs4231_ebus_free(chip);
+		snd_printdd("cs4231-%d: Unable to register EBUS capture DMA\n",
+			    dev);
+		return -EBUSY;
+	}
+	if (ebus_dma_irq_enable(&chip->c_dma.ebus_info, 1)) {
+		snd_cs4231_ebus_free(chip);
+		snd_printdd("cs4231-%d: Unable to enable EBUS capture IRQ\n",
+			    dev);
+		return -EBUSY;
+	}
+
+	if (ebus_dma_register(&chip->p_dma.ebus_info)) {
+		snd_cs4231_ebus_free(chip);
+		snd_printdd("cs4231-%d: Unable to register EBUS play DMA\n",
+			    dev);
+		return -EBUSY;
+	}
+	if (ebus_dma_irq_enable(&chip->p_dma.ebus_info, 1)) {
+		snd_cs4231_ebus_free(chip);
+		snd_printdd("cs4231-%d: Unable to enable EBUS play IRQ\n", dev);
+		return -EBUSY;
+	}
+
+	if (snd_cs4231_probe(chip) < 0) {
+		snd_cs4231_ebus_free(chip);
+		return -ENODEV;
+	}
+	snd_cs4231_init(chip);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+				  chip, &snd_cs4231_ebus_dev_ops)) < 0) {
+		snd_cs4231_ebus_free(chip);
+		return err;
+	}
+
+	return 0;
+}
+
+static int __devinit cs4231_ebus_probe(struct platform_device *op, const struct of_device_id *match)
+{
+	struct snd_card *card;
+	int err;
+
+	err = cs4231_attach_begin(&card);
+	if (err)
+		return err;
+
+	sprintf(card->longname, "%s at 0x%llx, irq %d",
+		card->shortname,
+		op->resource[0].start,
+		op->archdata.irqs[0]);
+
+	err = snd_cs4231_ebus_create(card, op, dev);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	return cs4231_attach_finish(card);
+}
+#endif
+
+static int __devinit cs4231_probe(struct platform_device *op, const struct of_device_id *match)
+{
+#ifdef EBUS_SUPPORT
+	if (!strcmp(op->dev.of_node->parent->name, "ebus"))
+		return cs4231_ebus_probe(op, match);
+#endif
+#ifdef SBUS_SUPPORT
+	if (!strcmp(op->dev.of_node->parent->name, "sbus") ||
+	    !strcmp(op->dev.of_node->parent->name, "sbi"))
+		return cs4231_sbus_probe(op, match);
+#endif
+	return -ENODEV;
+}
+
+static int __devexit cs4231_remove(struct platform_device *op)
+{
+	struct snd_cs4231 *chip = dev_get_drvdata(&op->dev);
+
+	snd_card_free(chip->card);
+
+	return 0;
+}
+
+static const struct of_device_id cs4231_match[] = {
+	{
+		.name = "SUNW,CS4231",
+	},
+	{
+		.name = "audio",
+		.compatible = "SUNW,CS4231",
+	},
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, cs4231_match);
+
+static struct of_platform_driver cs4231_driver = {
+	.driver = {
+		.name = "audio",
+		.owner = THIS_MODULE,
+		.of_match_table = cs4231_match,
+	},
+	.probe		= cs4231_probe,
+	.remove		= __devexit_p(cs4231_remove),
+};
+
+static int __init cs4231_init(void)
+{
+	return of_register_platform_driver(&cs4231_driver);
+}
+
+static void __exit cs4231_exit(void)
+{
+	of_unregister_platform_driver(&cs4231_driver);
+}
+
+module_init(cs4231_init);
+module_exit(cs4231_exit);
diff --git a/linux/sound/sparc/dbri.c b/linux/sound/sparc/dbri.c
new file mode 100644
index 0000000..39cd5d6
--- /dev/null
+++ b/linux/sound/sparc/dbri.c
@@ -0,0 +1,2711 @@
+/*
+ * Driver for DBRI sound chip found on Sparcs.
+ * Copyright (C) 2004, 2005 Martin Habets (mhabets@users.sourceforge.net)
+ *
+ * Converted to ring buffered version by Krzysztof Helt (krzysztof.h1@wp.pl)
+ *
+ * Based entirely upon drivers/sbus/audio/dbri.c which is:
+ * Copyright (C) 1997 Rudolf Koenig (rfkoenig@immd4.informatik.uni-erlangen.de)
+ * Copyright (C) 1998, 1999 Brent Baccala (baccala@freesoft.org)
+ *
+ * This is the low level driver for the DBRI & MMCODEC duo used for ISDN & AUDIO
+ * on Sun SPARCStation 10, 20, LX and Voyager models.
+ *
+ * - DBRI: AT&T T5900FX Dual Basic Rates ISDN Interface. It is a 32 channel
+ *   data time multiplexer with ISDN support (aka T7259)
+ *   Interfaces: SBus,ISDN NT & TE, CHI, 4 bits parallel.
+ *   CHI: (spelled ki) Concentration Highway Interface (AT&T or Intel bus ?).
+ *   Documentation:
+ *   - "STP 4000SBus Dual Basic Rate ISDN (DBRI) Transceiver" from
+ *     Sparc Technology Business (courtesy of Sun Support)
+ *   - Data sheet of the T7903, a newer but very similar ISA bus equivalent
+ *     available from the Lucent (formerly AT&T microelectronics) home
+ *     page.
+ *   - http://www.freesoft.org/Linux/DBRI/
+ * - MMCODEC: Crystal Semiconductor CS4215 16 bit Multimedia Audio Codec
+ *   Interfaces: CHI, Audio In & Out, 2 bits parallel
+ *   Documentation: from the Crystal Semiconductor home page.
+ *
+ * The DBRI is a 32 pipe machine, each pipe can transfer some bits between
+ * memory and a serial device (long pipes, no. 0-15) or between two serial
+ * devices (short pipes, no. 16-31), or simply send a fixed data to a serial
+ * device (short pipes).
+ * A timeslot defines the bit-offset and no. of bits read from a serial device.
+ * The timeslots are linked to 6 circular lists, one for each direction for
+ * each serial device (NT,TE,CHI). A timeslot is associated to 1 or 2 pipes
+ * (the second one is a monitor/tee pipe, valid only for serial input).
+ *
+ * The mmcodec is connected via the CHI bus and needs the data & some
+ * parameters (volume, output selection) time multiplexed in 8 byte
+ * chunks. It also has a control mode, which serves for audio format setting.
+ *
+ * Looking at the CS4215 data sheet it is easy to set up 2 or 4 codecs on
+ * the same CHI bus, so I thought perhaps it is possible to use the on-board
+ * & the speakerbox codec simultaneously, giving 2 (not very independent :-)
+ * audio devices. But the SUN HW group decided against it, at least on my
+ * LX the speakerbox connector has at least 1 pin missing and 1 wrongly
+ * connected.
+ *
+ * I've tried to stick to the following function naming conventions:
+ * snd_*	ALSA stuff
+ * cs4215_*	CS4215 codec specific stuff
+ * dbri_*	DBRI high-level stuff
+ * other	DBRI low-level stuff
+ */
+
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/gfp.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <asm/atomic.h>
+
+MODULE_AUTHOR("Rudolf Koenig, Brent Baccala and Martin Habets");
+MODULE_DESCRIPTION("Sun DBRI");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Sun,DBRI}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+/* Enable this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Sun DBRI soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Sun DBRI soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Sun DBRI soundcard.");
+
+#undef DBRI_DEBUG
+
+#define D_INT	(1<<0)
+#define D_GEN	(1<<1)
+#define D_CMD	(1<<2)
+#define D_MM	(1<<3)
+#define D_USR	(1<<4)
+#define D_DESC	(1<<5)
+
+static int dbri_debug;
+module_param(dbri_debug, int, 0644);
+MODULE_PARM_DESC(dbri_debug, "Debug value for Sun DBRI soundcard.");
+
+#ifdef DBRI_DEBUG
+static char *cmds[] = {
+	"WAIT", "PAUSE", "JUMP", "IIQ", "REX", "SDP", "CDP", "DTS",
+	"SSP", "CHI", "NT", "TE", "CDEC", "TEST", "CDM", "RESRV"
+};
+
+#define dprintk(a, x...) if (dbri_debug & a) printk(KERN_DEBUG x)
+
+#else
+#define dprintk(a, x...) do { } while (0)
+
+#endif				/* DBRI_DEBUG */
+
+#define DBRI_CMD(cmd, intr, value) ((cmd << 28) |	\
+				    (intr << 27) |	\
+				    value)
+
+/***************************************************************************
+	CS4215 specific definitions and structures
+****************************************************************************/
+
+struct cs4215 {
+	__u8 data[4];		/* Data mode: Time slots 5-8 */
+	__u8 ctrl[4];		/* Ctrl mode: Time slots 1-4 */
+	__u8 onboard;
+	__u8 offset;		/* Bit offset from frame sync to time slot 1 */
+	volatile __u32 status;
+	volatile __u32 version;
+	__u8 precision;		/* In bits, either 8 or 16 */
+	__u8 channels;		/* 1 or 2 */
+};
+
+/*
+ * Control mode first
+ */
+
+/* Time Slot 1, Status register */
+#define CS4215_CLB	(1<<2)	/* Control Latch Bit */
+#define CS4215_OLB	(1<<3)	/* 1: line: 2.0V, speaker 4V */
+				/* 0: line: 2.8V, speaker 8V */
+#define CS4215_MLB	(1<<4)	/* 1: Microphone: 20dB gain disabled */
+#define CS4215_RSRVD_1  (1<<5)
+
+/* Time Slot 2, Data Format Register */
+#define CS4215_DFR_LINEAR16	0
+#define CS4215_DFR_ULAW		1
+#define CS4215_DFR_ALAW		2
+#define CS4215_DFR_LINEAR8	3
+#define CS4215_DFR_STEREO	(1<<2)
+static struct {
+	unsigned short freq;
+	unsigned char xtal;
+	unsigned char csval;
+} CS4215_FREQ[] = {
+	{  8000, (1 << 4), (0 << 3) },
+	{ 16000, (1 << 4), (1 << 3) },
+	{ 27429, (1 << 4), (2 << 3) },	/* Actually 24428.57 */
+	{ 32000, (1 << 4), (3 << 3) },
+     /* {    NA, (1 << 4), (4 << 3) }, */
+     /* {    NA, (1 << 4), (5 << 3) }, */
+	{ 48000, (1 << 4), (6 << 3) },
+	{  9600, (1 << 4), (7 << 3) },
+	{  5512, (2 << 4), (0 << 3) },	/* Actually 5512.5 */
+	{ 11025, (2 << 4), (1 << 3) },
+	{ 18900, (2 << 4), (2 << 3) },
+	{ 22050, (2 << 4), (3 << 3) },
+	{ 37800, (2 << 4), (4 << 3) },
+	{ 44100, (2 << 4), (5 << 3) },
+	{ 33075, (2 << 4), (6 << 3) },
+	{  6615, (2 << 4), (7 << 3) },
+	{ 0, 0, 0}
+};
+
+#define CS4215_HPF	(1<<7)	/* High Pass Filter, 1: Enabled */
+
+#define CS4215_12_MASK	0xfcbf	/* Mask off reserved bits in slot 1 & 2 */
+
+/* Time Slot 3, Serial Port Control register */
+#define CS4215_XEN	(1<<0)	/* 0: Enable serial output */
+#define CS4215_XCLK	(1<<1)	/* 1: Master mode: Generate SCLK */
+#define CS4215_BSEL_64	(0<<2)	/* Bitrate: 64 bits per frame */
+#define CS4215_BSEL_128	(1<<2)
+#define CS4215_BSEL_256	(2<<2)
+#define CS4215_MCK_MAST (0<<4)	/* Master clock */
+#define CS4215_MCK_XTL1 (1<<4)	/* 24.576 MHz clock source */
+#define CS4215_MCK_XTL2 (2<<4)	/* 16.9344 MHz clock source */
+#define CS4215_MCK_CLK1 (3<<4)	/* Clockin, 256 x Fs */
+#define CS4215_MCK_CLK2 (4<<4)	/* Clockin, see DFR */
+
+/* Time Slot 4, Test Register */
+#define CS4215_DAD	(1<<0)	/* 0:Digital-Dig loop, 1:Dig-Analog-Dig loop */
+#define CS4215_ENL	(1<<1)	/* Enable Loopback Testing */
+
+/* Time Slot 5, Parallel Port Register */
+/* Read only here and the same as the in data mode */
+
+/* Time Slot 6, Reserved  */
+
+/* Time Slot 7, Version Register  */
+#define CS4215_VERSION_MASK 0xf	/* Known versions 0/C, 1/D, 2/E */
+
+/* Time Slot 8, Reserved  */
+
+/*
+ * Data mode
+ */
+/* Time Slot 1-2: Left Channel Data, 2-3: Right Channel Data  */
+
+/* Time Slot 5, Output Setting  */
+#define CS4215_LO(v)	v	/* Left Output Attenuation 0x3f: -94.5 dB */
+#define CS4215_LE	(1<<6)	/* Line Out Enable */
+#define CS4215_HE	(1<<7)	/* Headphone Enable */
+
+/* Time Slot 6, Output Setting  */
+#define CS4215_RO(v)	v	/* Right Output Attenuation 0x3f: -94.5 dB */
+#define CS4215_SE	(1<<6)	/* Speaker Enable */
+#define CS4215_ADI	(1<<7)	/* A/D Data Invalid: Busy in calibration */
+
+/* Time Slot 7, Input Setting */
+#define CS4215_LG(v)	v	/* Left Gain Setting 0xf: 22.5 dB */
+#define CS4215_IS	(1<<4)	/* Input Select: 1=Microphone, 0=Line */
+#define CS4215_OVR	(1<<5)	/* 1: Over range condition occurred */
+#define CS4215_PIO0	(1<<6)	/* Parallel I/O 0 */
+#define CS4215_PIO1	(1<<7)
+
+/* Time Slot 8, Input Setting */
+#define CS4215_RG(v)	v	/* Right Gain Setting 0xf: 22.5 dB */
+#define CS4215_MA(v)	(v<<4)	/* Monitor Path Attenuation 0xf: mute */
+
+/***************************************************************************
+		DBRI specific definitions and structures
+****************************************************************************/
+
+/* DBRI main registers */
+#define REG0	0x00		/* Status and Control */
+#define REG1	0x04		/* Mode and Interrupt */
+#define REG2	0x08		/* Parallel IO */
+#define REG3	0x0c		/* Test */
+#define REG8	0x20		/* Command Queue Pointer */
+#define REG9	0x24		/* Interrupt Queue Pointer */
+
+#define DBRI_NO_CMDS	64
+#define DBRI_INT_BLK	64
+#define DBRI_NO_DESCS	64
+#define DBRI_NO_PIPES	32
+#define DBRI_MAX_PIPE	(DBRI_NO_PIPES - 1)
+
+#define DBRI_REC	0
+#define DBRI_PLAY	1
+#define DBRI_NO_STREAMS	2
+
+/* One transmit/receive descriptor */
+/* When ba != 0 descriptor is used */
+struct dbri_mem {
+	volatile __u32 word1;
+	__u32 ba;	/* Transmit/Receive Buffer Address */
+	__u32 nda;	/* Next Descriptor Address */
+	volatile __u32 word4;
+};
+
+/* This structure is in a DMA region where it can accessed by both
+ * the CPU and the DBRI
+ */
+struct dbri_dma {
+	s32 cmd[DBRI_NO_CMDS];			/* Place for commands */
+	volatile s32 intr[DBRI_INT_BLK];	/* Interrupt field  */
+	struct dbri_mem desc[DBRI_NO_DESCS];	/* Xmit/receive descriptors */
+};
+
+#define dbri_dma_off(member, elem)	\
+	((u32)(unsigned long)		\
+	 (&(((struct dbri_dma *)0)->member[elem])))
+
+enum in_or_out { PIPEinput, PIPEoutput };
+
+struct dbri_pipe {
+	u32 sdp;		/* SDP command word */
+	int nextpipe;		/* Next pipe in linked list */
+	int length;		/* Length of timeslot (bits) */
+	int first_desc;		/* Index of first descriptor */
+	int desc;		/* Index of active descriptor */
+	volatile __u32 *recv_fixed_ptr;	/* Ptr to receive fixed data */
+};
+
+/* Per stream (playback or record) information */
+struct dbri_streaminfo {
+	struct snd_pcm_substream *substream;
+	u32 dvma_buffer;	/* Device view of ALSA DMA buffer */
+	int size;		/* Size of DMA buffer             */
+	size_t offset;		/* offset in user buffer          */
+	int pipe;		/* Data pipe used                 */
+	int left_gain;		/* mixer elements                 */
+	int right_gain;
+};
+
+/* This structure holds the information for both chips (DBRI & CS4215) */
+struct snd_dbri {
+	int regs_size, irq;	/* Needed for unload */
+	struct platform_device *op;	/* OF device info */
+	spinlock_t lock;
+
+	struct dbri_dma *dma;	/* Pointer to our DMA block */
+	u32 dma_dvma;		/* DBRI visible DMA address */
+
+	void __iomem *regs;	/* dbri HW regs */
+	int dbri_irqp;		/* intr queue pointer */
+
+	struct dbri_pipe pipes[DBRI_NO_PIPES];	/* DBRI's 32 data pipes */
+	int next_desc[DBRI_NO_DESCS];		/* Index of next desc, or -1 */
+	spinlock_t cmdlock;	/* Protects cmd queue accesses */
+	s32 *cmdptr;		/* Pointer to the last queued cmd */
+
+	int chi_bpf;
+
+	struct cs4215 mm;	/* mmcodec special info */
+				/* per stream (playback/record) info */
+	struct dbri_streaminfo stream_info[DBRI_NO_STREAMS];
+};
+
+#define DBRI_MAX_VOLUME		63	/* Output volume */
+#define DBRI_MAX_GAIN		15	/* Input gain */
+
+/* DBRI Reg0 - Status Control Register - defines. (Page 17) */
+#define D_P		(1<<15)	/* Program command & queue pointer valid */
+#define D_G		(1<<14)	/* Allow 4-Word SBus Burst */
+#define D_S		(1<<13)	/* Allow 16-Word SBus Burst */
+#define D_E		(1<<12)	/* Allow 8-Word SBus Burst */
+#define D_X		(1<<7)	/* Sanity Timer Disable */
+#define D_T		(1<<6)	/* Permit activation of the TE interface */
+#define D_N		(1<<5)	/* Permit activation of the NT interface */
+#define D_C		(1<<4)	/* Permit activation of the CHI interface */
+#define D_F		(1<<3)	/* Force Sanity Timer Time-Out */
+#define D_D		(1<<2)	/* Disable Master Mode */
+#define D_H		(1<<1)	/* Halt for Analysis */
+#define D_R		(1<<0)	/* Soft Reset */
+
+/* DBRI Reg1 - Mode and Interrupt Register - defines. (Page 18) */
+#define D_LITTLE_END	(1<<8)	/* Byte Order */
+#define D_BIG_END	(0<<8)	/* Byte Order */
+#define D_MRR		(1<<4)	/* Multiple Error Ack on SBus (read only) */
+#define D_MLE		(1<<3)	/* Multiple Late Error on SBus (read only) */
+#define D_LBG		(1<<2)	/* Lost Bus Grant on SBus (read only) */
+#define D_MBE		(1<<1)	/* Burst Error on SBus (read only) */
+#define D_IR		(1<<0)	/* Interrupt Indicator (read only) */
+
+/* DBRI Reg2 - Parallel IO Register - defines. (Page 18) */
+#define D_ENPIO3	(1<<7)	/* Enable Pin 3 */
+#define D_ENPIO2	(1<<6)	/* Enable Pin 2 */
+#define D_ENPIO1	(1<<5)	/* Enable Pin 1 */
+#define D_ENPIO0	(1<<4)	/* Enable Pin 0 */
+#define D_ENPIO		(0xf0)	/* Enable all the pins */
+#define D_PIO3		(1<<3)	/* Pin 3: 1: Data mode, 0: Ctrl mode */
+#define D_PIO2		(1<<2)	/* Pin 2: 1: Onboard PDN */
+#define D_PIO1		(1<<1)	/* Pin 1: 0: Reset */
+#define D_PIO0		(1<<0)	/* Pin 0: 1: Speakerbox PDN */
+
+/* DBRI Commands (Page 20) */
+#define D_WAIT		0x0	/* Stop execution */
+#define D_PAUSE		0x1	/* Flush long pipes */
+#define D_JUMP		0x2	/* New command queue */
+#define D_IIQ		0x3	/* Initialize Interrupt Queue */
+#define D_REX		0x4	/* Report command execution via interrupt */
+#define D_SDP		0x5	/* Setup Data Pipe */
+#define D_CDP		0x6	/* Continue Data Pipe (reread NULL Pointer) */
+#define D_DTS		0x7	/* Define Time Slot */
+#define D_SSP		0x8	/* Set short Data Pipe */
+#define D_CHI		0x9	/* Set CHI Global Mode */
+#define D_NT		0xa	/* NT Command */
+#define D_TE		0xb	/* TE Command */
+#define D_CDEC		0xc	/* Codec setup */
+#define D_TEST		0xd	/* No comment */
+#define D_CDM		0xe	/* CHI Data mode command */
+
+/* Special bits for some commands */
+#define D_PIPE(v)      ((v)<<0)	/* Pipe No.: 0-15 long, 16-21 short */
+
+/* Setup Data Pipe */
+/* IRM */
+#define D_SDP_2SAME	(1<<18)	/* Report 2nd time in a row value received */
+#define D_SDP_CHANGE	(2<<18)	/* Report any changes */
+#define D_SDP_EVERY	(3<<18)	/* Report any changes */
+#define D_SDP_EOL	(1<<17)	/* EOL interrupt enable */
+#define D_SDP_IDLE	(1<<16)	/* HDLC idle interrupt enable */
+
+/* Pipe data MODE */
+#define D_SDP_MEM	(0<<13)	/* To/from memory */
+#define D_SDP_HDLC	(2<<13)
+#define D_SDP_HDLC_D	(3<<13)	/* D Channel (prio control) */
+#define D_SDP_SER	(4<<13)	/* Serial to serial */
+#define D_SDP_FIXED	(6<<13)	/* Short only */
+#define D_SDP_MODE(v)	((v)&(7<<13))
+
+#define D_SDP_TO_SER	(1<<12)	/* Direction */
+#define D_SDP_FROM_SER	(0<<12)	/* Direction */
+#define D_SDP_MSB	(1<<11)	/* Bit order within Byte */
+#define D_SDP_LSB	(0<<11)	/* Bit order within Byte */
+#define D_SDP_P		(1<<10)	/* Pointer Valid */
+#define D_SDP_A		(1<<8)	/* Abort */
+#define D_SDP_C		(1<<7)	/* Clear */
+
+/* Define Time Slot */
+#define D_DTS_VI	(1<<17)	/* Valid Input Time-Slot Descriptor */
+#define D_DTS_VO	(1<<16)	/* Valid Output Time-Slot Descriptor */
+#define D_DTS_INS	(1<<15)	/* Insert Time Slot */
+#define D_DTS_DEL	(0<<15)	/* Delete Time Slot */
+#define D_DTS_PRVIN(v) ((v)<<10)	/* Previous In Pipe */
+#define D_DTS_PRVOUT(v)        ((v)<<5)	/* Previous Out Pipe */
+
+/* Time Slot defines */
+#define D_TS_LEN(v)	((v)<<24)	/* Number of bits in this time slot */
+#define D_TS_CYCLE(v)	((v)<<14)	/* Bit Count at start of TS */
+#define D_TS_DI		(1<<13)	/* Data Invert */
+#define D_TS_1CHANNEL	(0<<10)	/* Single Channel / Normal mode */
+#define D_TS_MONITOR	(2<<10)	/* Monitor pipe */
+#define D_TS_NONCONTIG	(3<<10)	/* Non contiguous mode */
+#define D_TS_ANCHOR	(7<<10)	/* Starting short pipes */
+#define D_TS_MON(v)    ((v)<<5)	/* Monitor Pipe */
+#define D_TS_NEXT(v)   ((v)<<0)	/* Pipe no.: 0-15 long, 16-21 short */
+
+/* Concentration Highway Interface Modes */
+#define D_CHI_CHICM(v)	((v)<<16)	/* Clock mode */
+#define D_CHI_IR	(1<<15)	/* Immediate Interrupt Report */
+#define D_CHI_EN	(1<<14)	/* CHIL Interrupt enabled */
+#define D_CHI_OD	(1<<13)	/* Open Drain Enable */
+#define D_CHI_FE	(1<<12)	/* Sample CHIFS on Rising Frame Edge */
+#define D_CHI_FD	(1<<11)	/* Frame Drive */
+#define D_CHI_BPF(v)	((v)<<0)	/* Bits per Frame */
+
+/* NT: These are here for completeness */
+#define D_NT_FBIT	(1<<17)	/* Frame Bit */
+#define D_NT_NBF	(1<<16)	/* Number of bad frames to loose framing */
+#define D_NT_IRM_IMM	(1<<15)	/* Interrupt Report & Mask: Immediate */
+#define D_NT_IRM_EN	(1<<14)	/* Interrupt Report & Mask: Enable */
+#define D_NT_ISNT	(1<<13)	/* Configure interface as NT */
+#define D_NT_FT		(1<<12)	/* Fixed Timing */
+#define D_NT_EZ		(1<<11)	/* Echo Channel is Zeros */
+#define D_NT_IFA	(1<<10)	/* Inhibit Final Activation */
+#define D_NT_ACT	(1<<9)	/* Activate Interface */
+#define D_NT_MFE	(1<<8)	/* Multiframe Enable */
+#define D_NT_RLB(v)	((v)<<5)	/* Remote Loopback */
+#define D_NT_LLB(v)	((v)<<2)	/* Local Loopback */
+#define D_NT_FACT	(1<<1)	/* Force Activation */
+#define D_NT_ABV	(1<<0)	/* Activate Bipolar Violation */
+
+/* Codec Setup */
+#define D_CDEC_CK(v)	((v)<<24)	/* Clock Select */
+#define D_CDEC_FED(v)	((v)<<12)	/* FSCOD Falling Edge Delay */
+#define D_CDEC_RED(v)	((v)<<0)	/* FSCOD Rising Edge Delay */
+
+/* Test */
+#define D_TEST_RAM(v)	((v)<<16)	/* RAM Pointer */
+#define D_TEST_SIZE(v)	((v)<<11)	/* */
+#define D_TEST_ROMONOFF	0x5	/* Toggle ROM opcode monitor on/off */
+#define D_TEST_PROC	0x6	/* Microprocessor test */
+#define D_TEST_SER	0x7	/* Serial-Controller test */
+#define D_TEST_RAMREAD	0x8	/* Copy from Ram to system memory */
+#define D_TEST_RAMWRITE	0x9	/* Copy into Ram from system memory */
+#define D_TEST_RAMBIST	0xa	/* RAM Built-In Self Test */
+#define D_TEST_MCBIST	0xb	/* Microcontroller Built-In Self Test */
+#define D_TEST_DUMP	0xe	/* ROM Dump */
+
+/* CHI Data Mode */
+#define D_CDM_THI	(1 << 8)	/* Transmit Data on CHIDR Pin */
+#define D_CDM_RHI	(1 << 7)	/* Receive Data on CHIDX Pin */
+#define D_CDM_RCE	(1 << 6)	/* Receive on Rising Edge of CHICK */
+#define D_CDM_XCE	(1 << 2) /* Transmit Data on Rising Edge of CHICK */
+#define D_CDM_XEN	(1 << 1)	/* Transmit Highway Enable */
+#define D_CDM_REN	(1 << 0)	/* Receive Highway Enable */
+
+/* The Interrupts */
+#define D_INTR_BRDY	1	/* Buffer Ready for processing */
+#define D_INTR_MINT	2	/* Marked Interrupt in RD/TD */
+#define D_INTR_IBEG	3	/* Flag to idle transition detected (HDLC) */
+#define D_INTR_IEND	4	/* Idle to flag transition detected (HDLC) */
+#define D_INTR_EOL	5	/* End of List */
+#define D_INTR_CMDI	6	/* Command has bean read */
+#define D_INTR_XCMP	8	/* Transmission of frame complete */
+#define D_INTR_SBRI	9	/* BRI status change info */
+#define D_INTR_FXDT	10	/* Fixed data change */
+#define D_INTR_CHIL	11	/* CHI lost frame sync (channel 36 only) */
+#define D_INTR_COLL	11	/* Unrecoverable D-Channel collision */
+#define D_INTR_DBYT	12	/* Dropped by frame slip */
+#define D_INTR_RBYT	13	/* Repeated by frame slip */
+#define D_INTR_LINT	14	/* Lost Interrupt */
+#define D_INTR_UNDR	15	/* DMA underrun */
+
+#define D_INTR_TE	32
+#define D_INTR_NT	34
+#define D_INTR_CHI	36
+#define D_INTR_CMD	38
+
+#define D_INTR_GETCHAN(v)	(((v) >> 24) & 0x3f)
+#define D_INTR_GETCODE(v)	(((v) >> 20) & 0xf)
+#define D_INTR_GETCMD(v)	(((v) >> 16) & 0xf)
+#define D_INTR_GETVAL(v)	((v) & 0xffff)
+#define D_INTR_GETRVAL(v)	((v) & 0xfffff)
+
+#define D_P_0		0	/* TE receive anchor */
+#define D_P_1		1	/* TE transmit anchor */
+#define D_P_2		2	/* NT transmit anchor */
+#define D_P_3		3	/* NT receive anchor */
+#define D_P_4		4	/* CHI send data */
+#define D_P_5		5	/* CHI receive data */
+#define D_P_6		6	/* */
+#define D_P_7		7	/* */
+#define D_P_8		8	/* */
+#define D_P_9		9	/* */
+#define D_P_10		10	/* */
+#define D_P_11		11	/* */
+#define D_P_12		12	/* */
+#define D_P_13		13	/* */
+#define D_P_14		14	/* */
+#define D_P_15		15	/* */
+#define D_P_16		16	/* CHI anchor pipe */
+#define D_P_17		17	/* CHI send */
+#define D_P_18		18	/* CHI receive */
+#define D_P_19		19	/* CHI receive */
+#define D_P_20		20	/* CHI receive */
+#define D_P_21		21	/* */
+#define D_P_22		22	/* */
+#define D_P_23		23	/* */
+#define D_P_24		24	/* */
+#define D_P_25		25	/* */
+#define D_P_26		26	/* */
+#define D_P_27		27	/* */
+#define D_P_28		28	/* */
+#define D_P_29		29	/* */
+#define D_P_30		30	/* */
+#define D_P_31		31	/* */
+
+/* Transmit descriptor defines */
+#define DBRI_TD_F	(1 << 31)	/* End of Frame */
+#define DBRI_TD_D	(1 << 30)	/* Do not append CRC */
+#define DBRI_TD_CNT(v)	((v) << 16) /* Number of valid bytes in the buffer */
+#define DBRI_TD_B	(1 << 15)	/* Final interrupt */
+#define DBRI_TD_M	(1 << 14)	/* Marker interrupt */
+#define DBRI_TD_I	(1 << 13)	/* Transmit Idle Characters */
+#define DBRI_TD_FCNT(v)	(v)		/* Flag Count */
+#define DBRI_TD_UNR	(1 << 3) /* Underrun: transmitter is out of data */
+#define DBRI_TD_ABT	(1 << 2)	/* Abort: frame aborted */
+#define DBRI_TD_TBC	(1 << 0)	/* Transmit buffer Complete */
+#define DBRI_TD_STATUS(v)       ((v) & 0xff)	/* Transmit status */
+			/* Maximum buffer size per TD: almost 8KB */
+#define DBRI_TD_MAXCNT	((1 << 13) - 4)
+
+/* Receive descriptor defines */
+#define DBRI_RD_F	(1 << 31)	/* End of Frame */
+#define DBRI_RD_C	(1 << 30)	/* Completed buffer */
+#define DBRI_RD_B	(1 << 15)	/* Final interrupt */
+#define DBRI_RD_M	(1 << 14)	/* Marker interrupt */
+#define DBRI_RD_BCNT(v)	(v)		/* Buffer size */
+#define DBRI_RD_CRC	(1 << 7)	/* 0: CRC is correct */
+#define DBRI_RD_BBC	(1 << 6)	/* 1: Bad Byte received */
+#define DBRI_RD_ABT	(1 << 5)	/* Abort: frame aborted */
+#define DBRI_RD_OVRN	(1 << 3)	/* Overrun: data lost */
+#define DBRI_RD_STATUS(v)      ((v) & 0xff)	/* Receive status */
+#define DBRI_RD_CNT(v) (((v) >> 16) & 0x1fff)	/* Valid bytes in the buffer */
+
+/* stream_info[] access */
+/* Translate the ALSA direction into the array index */
+#define DBRI_STREAMNO(substream)				\
+		(substream->stream ==				\
+		 SNDRV_PCM_STREAM_PLAYBACK ? DBRI_PLAY: DBRI_REC)
+
+/* Return a pointer to dbri_streaminfo */
+#define DBRI_STREAM(dbri, substream)	\
+		&dbri->stream_info[DBRI_STREAMNO(substream)]
+
+/*
+ * Short data pipes transmit LSB first. The CS4215 receives MSB first. Grrr.
+ * So we have to reverse the bits. Note: not all bit lengths are supported
+ */
+static __u32 reverse_bytes(__u32 b, int len)
+{
+	switch (len) {
+	case 32:
+		b = ((b & 0xffff0000) >> 16) | ((b & 0x0000ffff) << 16);
+	case 16:
+		b = ((b & 0xff00ff00) >> 8) | ((b & 0x00ff00ff) << 8);
+	case 8:
+		b = ((b & 0xf0f0f0f0) >> 4) | ((b & 0x0f0f0f0f) << 4);
+	case 4:
+		b = ((b & 0xcccccccc) >> 2) | ((b & 0x33333333) << 2);
+	case 2:
+		b = ((b & 0xaaaaaaaa) >> 1) | ((b & 0x55555555) << 1);
+	case 1:
+	case 0:
+		break;
+	default:
+		printk(KERN_ERR "DBRI reverse_bytes: unsupported length\n");
+	};
+
+	return b;
+}
+
+/*
+****************************************************************************
+************** DBRI initialization and command synchronization *************
+****************************************************************************
+
+Commands are sent to the DBRI by building a list of them in memory,
+then writing the address of the first list item to DBRI register 8.
+The list is terminated with a WAIT command, which generates a
+CPU interrupt to signal completion.
+
+Since the DBRI can run in parallel with the CPU, several means of
+synchronization present themselves. The method implemented here uses
+the dbri_cmdwait() to wait for execution of batch of sent commands.
+
+A circular command buffer is used here. A new command is being added
+while another can be executed. The scheme works by adding two WAIT commands
+after each sent batch of commands. When the next batch is prepared it is
+added after the WAIT commands then the WAITs are replaced with single JUMP
+command to the new batch. The the DBRI is forced to reread the last WAIT
+command (replaced by the JUMP by then). If the DBRI is still executing
+previous commands the request to reread the WAIT command is ignored.
+
+Every time a routine wants to write commands to the DBRI, it must
+first call dbri_cmdlock() and get pointer to a free space in
+dbri->dma->cmd buffer. After this, the commands can be written to
+the buffer, and dbri_cmdsend() is called with the final pointer value
+to send them to the DBRI.
+
+*/
+
+#define MAXLOOPS 20
+/*
+ * Wait for the current command string to execute
+ */
+static void dbri_cmdwait(struct snd_dbri *dbri)
+{
+	int maxloops = MAXLOOPS;
+	unsigned long flags;
+
+	/* Delay if previous commands are still being processed */
+	spin_lock_irqsave(&dbri->lock, flags);
+	while ((--maxloops) > 0 && (sbus_readl(dbri->regs + REG0) & D_P)) {
+		spin_unlock_irqrestore(&dbri->lock, flags);
+		msleep_interruptible(1);
+		spin_lock_irqsave(&dbri->lock, flags);
+	}
+	spin_unlock_irqrestore(&dbri->lock, flags);
+
+	if (maxloops == 0)
+		printk(KERN_ERR "DBRI: Chip never completed command buffer\n");
+	else
+		dprintk(D_CMD, "Chip completed command buffer (%d)\n",
+			MAXLOOPS - maxloops - 1);
+}
+/*
+ * Lock the command queue and return pointer to space for len cmd words
+ * It locks the cmdlock spinlock.
+ */
+static s32 *dbri_cmdlock(struct snd_dbri *dbri, int len)
+{
+	/* Space for 2 WAIT cmds (replaced later by 1 JUMP cmd) */
+	len += 2;
+	spin_lock(&dbri->cmdlock);
+	if (dbri->cmdptr - dbri->dma->cmd + len < DBRI_NO_CMDS - 2)
+		return dbri->cmdptr + 2;
+	else if (len < sbus_readl(dbri->regs + REG8) - dbri->dma_dvma)
+		return dbri->dma->cmd;
+	else
+		printk(KERN_ERR "DBRI: no space for commands.");
+
+	return NULL;
+}
+
+/*
+ * Send prepared cmd string. It works by writing a JUMP cmd into
+ * the last WAIT cmd and force DBRI to reread the cmd.
+ * The JUMP cmd points to the new cmd string.
+ * It also releases the cmdlock spinlock.
+ *
+ * Lock must be held before calling this.
+ */
+static void dbri_cmdsend(struct snd_dbri *dbri, s32 *cmd, int len)
+{
+	s32 tmp, addr;
+	static int wait_id = 0;
+
+	wait_id++;
+	wait_id &= 0xffff;	/* restrict it to a 16 bit counter. */
+	*(cmd) = DBRI_CMD(D_WAIT, 1, wait_id);
+	*(cmd+1) = DBRI_CMD(D_WAIT, 1, wait_id);
+
+	/* Replace the last command with JUMP */
+	addr = dbri->dma_dvma + (cmd - len - dbri->dma->cmd) * sizeof(s32);
+	*(dbri->cmdptr+1) = addr;
+	*(dbri->cmdptr) = DBRI_CMD(D_JUMP, 0, 0);
+
+#ifdef DBRI_DEBUG
+	if (cmd > dbri->cmdptr) {
+		s32 *ptr;
+
+		for (ptr = dbri->cmdptr; ptr < cmd+2; ptr++)
+			dprintk(D_CMD, "cmd: %lx:%08x\n",
+				(unsigned long)ptr, *ptr);
+	} else {
+		s32 *ptr = dbri->cmdptr;
+
+		dprintk(D_CMD, "cmd: %lx:%08x\n", (unsigned long)ptr, *ptr);
+		ptr++;
+		dprintk(D_CMD, "cmd: %lx:%08x\n", (unsigned long)ptr, *ptr);
+		for (ptr = dbri->dma->cmd; ptr < cmd+2; ptr++)
+			dprintk(D_CMD, "cmd: %lx:%08x\n",
+				(unsigned long)ptr, *ptr);
+	}
+#endif
+
+	/* Reread the last command */
+	tmp = sbus_readl(dbri->regs + REG0);
+	tmp |= D_P;
+	sbus_writel(tmp, dbri->regs + REG0);
+
+	dbri->cmdptr = cmd;
+	spin_unlock(&dbri->cmdlock);
+}
+
+/* Lock must be held when calling this */
+static void dbri_reset(struct snd_dbri *dbri)
+{
+	int i;
+	u32 tmp;
+
+	dprintk(D_GEN, "reset 0:%x 2:%x 8:%x 9:%x\n",
+		sbus_readl(dbri->regs + REG0),
+		sbus_readl(dbri->regs + REG2),
+		sbus_readl(dbri->regs + REG8), sbus_readl(dbri->regs + REG9));
+
+	sbus_writel(D_R, dbri->regs + REG0);	/* Soft Reset */
+	for (i = 0; (sbus_readl(dbri->regs + REG0) & D_R) && i < 64; i++)
+		udelay(10);
+
+	/* A brute approach - DBRI falls back to working burst size by itself
+	 * On SS20 D_S does not work, so do not try so high. */
+	tmp = sbus_readl(dbri->regs + REG0);
+	tmp |= D_G | D_E;
+	tmp &= ~D_S;
+	sbus_writel(tmp, dbri->regs + REG0);
+}
+
+/* Lock must not be held before calling this */
+static void __devinit dbri_initialize(struct snd_dbri *dbri)
+{
+	s32 *cmd;
+	u32 dma_addr;
+	unsigned long flags;
+	int n;
+
+	spin_lock_irqsave(&dbri->lock, flags);
+
+	dbri_reset(dbri);
+
+	/* Initialize pipes */
+	for (n = 0; n < DBRI_NO_PIPES; n++)
+		dbri->pipes[n].desc = dbri->pipes[n].first_desc = -1;
+
+	spin_lock_init(&dbri->cmdlock);
+	/*
+	 * Initialize the interrupt ring buffer.
+	 */
+	dma_addr = dbri->dma_dvma + dbri_dma_off(intr, 0);
+	dbri->dma->intr[0] = dma_addr;
+	dbri->dbri_irqp = 1;
+	/*
+	 * Set up the interrupt queue
+	 */
+	spin_lock(&dbri->cmdlock);
+	cmd = dbri->cmdptr = dbri->dma->cmd;
+	*(cmd++) = DBRI_CMD(D_IIQ, 0, 0);
+	*(cmd++) = dma_addr;
+	*(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
+	dbri->cmdptr = cmd;
+	*(cmd++) = DBRI_CMD(D_WAIT, 1, 0);
+	*(cmd++) = DBRI_CMD(D_WAIT, 1, 0);
+	dma_addr = dbri->dma_dvma + dbri_dma_off(cmd, 0);
+	sbus_writel(dma_addr, dbri->regs + REG8);
+	spin_unlock(&dbri->cmdlock);
+
+	spin_unlock_irqrestore(&dbri->lock, flags);
+	dbri_cmdwait(dbri);
+}
+
+/*
+****************************************************************************
+************************** DBRI data pipe management ***********************
+****************************************************************************
+
+While DBRI control functions use the command and interrupt buffers, the
+main data path takes the form of data pipes, which can be short (command
+and interrupt driven), or long (attached to DMA buffers).  These functions
+provide a rudimentary means of setting up and managing the DBRI's pipes,
+but the calling functions have to make sure they respect the pipes' linked
+list ordering, among other things.  The transmit and receive functions
+here interface closely with the transmit and receive interrupt code.
+
+*/
+static inline int pipe_active(struct snd_dbri *dbri, int pipe)
+{
+	return ((pipe >= 0) && (dbri->pipes[pipe].desc != -1));
+}
+
+/* reset_pipe(dbri, pipe)
+ *
+ * Called on an in-use pipe to clear anything being transmitted or received
+ * Lock must be held before calling this.
+ */
+static void reset_pipe(struct snd_dbri *dbri, int pipe)
+{
+	int sdp;
+	int desc;
+	s32 *cmd;
+
+	if (pipe < 0 || pipe > DBRI_MAX_PIPE) {
+		printk(KERN_ERR "DBRI: reset_pipe called with "
+			"illegal pipe number\n");
+		return;
+	}
+
+	sdp = dbri->pipes[pipe].sdp;
+	if (sdp == 0) {
+		printk(KERN_ERR "DBRI: reset_pipe called "
+			"on uninitialized pipe\n");
+		return;
+	}
+
+	cmd = dbri_cmdlock(dbri, 3);
+	*(cmd++) = DBRI_CMD(D_SDP, 0, sdp | D_SDP_C | D_SDP_P);
+	*(cmd++) = 0;
+	*(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
+	dbri_cmdsend(dbri, cmd, 3);
+
+	desc = dbri->pipes[pipe].first_desc;
+	if (desc >= 0)
+		do {
+			dbri->dma->desc[desc].ba = 0;
+			dbri->dma->desc[desc].nda = 0;
+			desc = dbri->next_desc[desc];
+		} while (desc != -1 && desc != dbri->pipes[pipe].first_desc);
+
+	dbri->pipes[pipe].desc = -1;
+	dbri->pipes[pipe].first_desc = -1;
+}
+
+/*
+ * Lock must be held before calling this.
+ */
+static void setup_pipe(struct snd_dbri *dbri, int pipe, int sdp)
+{
+	if (pipe < 0 || pipe > DBRI_MAX_PIPE) {
+		printk(KERN_ERR "DBRI: setup_pipe called "
+			"with illegal pipe number\n");
+		return;
+	}
+
+	if ((sdp & 0xf800) != sdp) {
+		printk(KERN_ERR "DBRI: setup_pipe called "
+			"with strange SDP value\n");
+		/* sdp &= 0xf800; */
+	}
+
+	/* If this is a fixed receive pipe, arrange for an interrupt
+	 * every time its data changes
+	 */
+	if (D_SDP_MODE(sdp) == D_SDP_FIXED && !(sdp & D_SDP_TO_SER))
+		sdp |= D_SDP_CHANGE;
+
+	sdp |= D_PIPE(pipe);
+	dbri->pipes[pipe].sdp = sdp;
+	dbri->pipes[pipe].desc = -1;
+	dbri->pipes[pipe].first_desc = -1;
+
+	reset_pipe(dbri, pipe);
+}
+
+/*
+ * Lock must be held before calling this.
+ */
+static void link_time_slot(struct snd_dbri *dbri, int pipe,
+			   int prevpipe, int nextpipe,
+			   int length, int cycle)
+{
+	s32 *cmd;
+	int val;
+
+	if (pipe < 0 || pipe > DBRI_MAX_PIPE
+			|| prevpipe < 0 || prevpipe > DBRI_MAX_PIPE
+			|| nextpipe < 0 || nextpipe > DBRI_MAX_PIPE) {
+		printk(KERN_ERR
+		    "DBRI: link_time_slot called with illegal pipe number\n");
+		return;
+	}
+
+	if (dbri->pipes[pipe].sdp == 0
+			|| dbri->pipes[prevpipe].sdp == 0
+			|| dbri->pipes[nextpipe].sdp == 0) {
+		printk(KERN_ERR "DBRI: link_time_slot called "
+			"on uninitialized pipe\n");
+		return;
+	}
+
+	dbri->pipes[prevpipe].nextpipe = pipe;
+	dbri->pipes[pipe].nextpipe = nextpipe;
+	dbri->pipes[pipe].length = length;
+
+	cmd = dbri_cmdlock(dbri, 4);
+
+	if (dbri->pipes[pipe].sdp & D_SDP_TO_SER) {
+		/* Deal with CHI special case:
+		 * "If transmission on edges 0 or 1 is desired, then cycle n
+		 *  (where n = # of bit times per frame...) must be used."
+		 *                  - DBRI data sheet, page 11
+		 */
+		if (prevpipe == 16 && cycle == 0)
+			cycle = dbri->chi_bpf;
+
+		val = D_DTS_VO | D_DTS_INS | D_DTS_PRVOUT(prevpipe) | pipe;
+		*(cmd++) = DBRI_CMD(D_DTS, 0, val);
+		*(cmd++) = 0;
+		*(cmd++) =
+		    D_TS_LEN(length) | D_TS_CYCLE(cycle) | D_TS_NEXT(nextpipe);
+	} else {
+		val = D_DTS_VI | D_DTS_INS | D_DTS_PRVIN(prevpipe) | pipe;
+		*(cmd++) = DBRI_CMD(D_DTS, 0, val);
+		*(cmd++) =
+		    D_TS_LEN(length) | D_TS_CYCLE(cycle) | D_TS_NEXT(nextpipe);
+		*(cmd++) = 0;
+	}
+	*(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
+
+	dbri_cmdsend(dbri, cmd, 4);
+}
+
+#if 0
+/*
+ * Lock must be held before calling this.
+ */
+static void unlink_time_slot(struct snd_dbri *dbri, int pipe,
+			     enum in_or_out direction, int prevpipe,
+			     int nextpipe)
+{
+	s32 *cmd;
+	int val;
+
+	if (pipe < 0 || pipe > DBRI_MAX_PIPE
+			|| prevpipe < 0 || prevpipe > DBRI_MAX_PIPE
+			|| nextpipe < 0 || nextpipe > DBRI_MAX_PIPE) {
+		printk(KERN_ERR
+		    "DBRI: unlink_time_slot called with illegal pipe number\n");
+		return;
+	}
+
+	cmd = dbri_cmdlock(dbri, 4);
+
+	if (direction == PIPEinput) {
+		val = D_DTS_VI | D_DTS_DEL | D_DTS_PRVIN(prevpipe) | pipe;
+		*(cmd++) = DBRI_CMD(D_DTS, 0, val);
+		*(cmd++) = D_TS_NEXT(nextpipe);
+		*(cmd++) = 0;
+	} else {
+		val = D_DTS_VO | D_DTS_DEL | D_DTS_PRVOUT(prevpipe) | pipe;
+		*(cmd++) = DBRI_CMD(D_DTS, 0, val);
+		*(cmd++) = 0;
+		*(cmd++) = D_TS_NEXT(nextpipe);
+	}
+	*(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
+
+	dbri_cmdsend(dbri, cmd, 4);
+}
+#endif
+
+/* xmit_fixed() / recv_fixed()
+ *
+ * Transmit/receive data on a "fixed" pipe - i.e, one whose contents are not
+ * expected to change much, and which we don't need to buffer.
+ * The DBRI only interrupts us when the data changes (receive pipes),
+ * or only changes the data when this function is called (transmit pipes).
+ * Only short pipes (numbers 16-31) can be used in fixed data mode.
+ *
+ * These function operate on a 32-bit field, no matter how large
+ * the actual time slot is.  The interrupt handler takes care of bit
+ * ordering and alignment.  An 8-bit time slot will always end up
+ * in the low-order 8 bits, filled either MSB-first or LSB-first,
+ * depending on the settings passed to setup_pipe().
+ *
+ * Lock must not be held before calling it.
+ */
+static void xmit_fixed(struct snd_dbri *dbri, int pipe, unsigned int data)
+{
+	s32 *cmd;
+	unsigned long flags;
+
+	if (pipe < 16 || pipe > DBRI_MAX_PIPE) {
+		printk(KERN_ERR "DBRI: xmit_fixed: Illegal pipe number\n");
+		return;
+	}
+
+	if (D_SDP_MODE(dbri->pipes[pipe].sdp) == 0) {
+		printk(KERN_ERR "DBRI: xmit_fixed: "
+			"Uninitialized pipe %d\n", pipe);
+		return;
+	}
+
+	if (D_SDP_MODE(dbri->pipes[pipe].sdp) != D_SDP_FIXED) {
+		printk(KERN_ERR "DBRI: xmit_fixed: Non-fixed pipe %d\n", pipe);
+		return;
+	}
+
+	if (!(dbri->pipes[pipe].sdp & D_SDP_TO_SER)) {
+		printk(KERN_ERR "DBRI: xmit_fixed: Called on receive pipe %d\n",
+			pipe);
+		return;
+	}
+
+	/* DBRI short pipes always transmit LSB first */
+
+	if (dbri->pipes[pipe].sdp & D_SDP_MSB)
+		data = reverse_bytes(data, dbri->pipes[pipe].length);
+
+	cmd = dbri_cmdlock(dbri, 3);
+
+	*(cmd++) = DBRI_CMD(D_SSP, 0, pipe);
+	*(cmd++) = data;
+	*(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
+
+	spin_lock_irqsave(&dbri->lock, flags);
+	dbri_cmdsend(dbri, cmd, 3);
+	spin_unlock_irqrestore(&dbri->lock, flags);
+	dbri_cmdwait(dbri);
+
+}
+
+static void recv_fixed(struct snd_dbri *dbri, int pipe, volatile __u32 *ptr)
+{
+	if (pipe < 16 || pipe > DBRI_MAX_PIPE) {
+		printk(KERN_ERR "DBRI: recv_fixed called with "
+			"illegal pipe number\n");
+		return;
+	}
+
+	if (D_SDP_MODE(dbri->pipes[pipe].sdp) != D_SDP_FIXED) {
+		printk(KERN_ERR "DBRI: recv_fixed called on "
+			"non-fixed pipe %d\n", pipe);
+		return;
+	}
+
+	if (dbri->pipes[pipe].sdp & D_SDP_TO_SER) {
+		printk(KERN_ERR "DBRI: recv_fixed called on "
+			"transmit pipe %d\n", pipe);
+		return;
+	}
+
+	dbri->pipes[pipe].recv_fixed_ptr = ptr;
+}
+
+/* setup_descs()
+ *
+ * Setup transmit/receive data on a "long" pipe - i.e, one associated
+ * with a DMA buffer.
+ *
+ * Only pipe numbers 0-15 can be used in this mode.
+ *
+ * This function takes a stream number pointing to a data buffer,
+ * and work by building chains of descriptors which identify the
+ * data buffers.  Buffers too large for a single descriptor will
+ * be spread across multiple descriptors.
+ *
+ * All descriptors create a ring buffer.
+ *
+ * Lock must be held before calling this.
+ */
+static int setup_descs(struct snd_dbri *dbri, int streamno, unsigned int period)
+{
+	struct dbri_streaminfo *info = &dbri->stream_info[streamno];
+	__u32 dvma_buffer;
+	int desc;
+	int len;
+	int first_desc = -1;
+	int last_desc = -1;
+
+	if (info->pipe < 0 || info->pipe > 15) {
+		printk(KERN_ERR "DBRI: setup_descs: Illegal pipe number\n");
+		return -2;
+	}
+
+	if (dbri->pipes[info->pipe].sdp == 0) {
+		printk(KERN_ERR "DBRI: setup_descs: Uninitialized pipe %d\n",
+		       info->pipe);
+		return -2;
+	}
+
+	dvma_buffer = info->dvma_buffer;
+	len = info->size;
+
+	if (streamno == DBRI_PLAY) {
+		if (!(dbri->pipes[info->pipe].sdp & D_SDP_TO_SER)) {
+			printk(KERN_ERR "DBRI: setup_descs: "
+				"Called on receive pipe %d\n", info->pipe);
+			return -2;
+		}
+	} else {
+		if (dbri->pipes[info->pipe].sdp & D_SDP_TO_SER) {
+			printk(KERN_ERR
+			    "DBRI: setup_descs: Called on transmit pipe %d\n",
+			     info->pipe);
+			return -2;
+		}
+		/* Should be able to queue multiple buffers
+		 * to receive on a pipe
+		 */
+		if (pipe_active(dbri, info->pipe)) {
+			printk(KERN_ERR "DBRI: recv_on_pipe: "
+				"Called on active pipe %d\n", info->pipe);
+			return -2;
+		}
+
+		/* Make sure buffer size is multiple of four */
+		len &= ~3;
+	}
+
+	/* Free descriptors if pipe has any */
+	desc = dbri->pipes[info->pipe].first_desc;
+	if (desc >= 0)
+		do {
+			dbri->dma->desc[desc].ba = 0;
+			dbri->dma->desc[desc].nda = 0;
+			desc = dbri->next_desc[desc];
+		} while (desc != -1 &&
+			 desc != dbri->pipes[info->pipe].first_desc);
+
+	dbri->pipes[info->pipe].desc = -1;
+	dbri->pipes[info->pipe].first_desc = -1;
+
+	desc = 0;
+	while (len > 0) {
+		int mylen;
+
+		for (; desc < DBRI_NO_DESCS; desc++) {
+			if (!dbri->dma->desc[desc].ba)
+				break;
+		}
+
+		if (desc == DBRI_NO_DESCS) {
+			printk(KERN_ERR "DBRI: setup_descs: No descriptors\n");
+			return -1;
+		}
+
+		if (len > DBRI_TD_MAXCNT)
+			mylen = DBRI_TD_MAXCNT;	/* 8KB - 4 */
+		else
+			mylen = len;
+
+		if (mylen > period)
+			mylen = period;
+
+		dbri->next_desc[desc] = -1;
+		dbri->dma->desc[desc].ba = dvma_buffer;
+		dbri->dma->desc[desc].nda = 0;
+
+		if (streamno == DBRI_PLAY) {
+			dbri->dma->desc[desc].word1 = DBRI_TD_CNT(mylen);
+			dbri->dma->desc[desc].word4 = 0;
+			dbri->dma->desc[desc].word1 |= DBRI_TD_F | DBRI_TD_B;
+		} else {
+			dbri->dma->desc[desc].word1 = 0;
+			dbri->dma->desc[desc].word4 =
+			    DBRI_RD_B | DBRI_RD_BCNT(mylen);
+		}
+
+		if (first_desc == -1)
+			first_desc = desc;
+		else {
+			dbri->next_desc[last_desc] = desc;
+			dbri->dma->desc[last_desc].nda =
+			    dbri->dma_dvma + dbri_dma_off(desc, desc);
+		}
+
+		last_desc = desc;
+		dvma_buffer += mylen;
+		len -= mylen;
+	}
+
+	if (first_desc == -1 || last_desc == -1) {
+		printk(KERN_ERR "DBRI: setup_descs: "
+			" Not enough descriptors available\n");
+		return -1;
+	}
+
+	dbri->dma->desc[last_desc].nda =
+	    dbri->dma_dvma + dbri_dma_off(desc, first_desc);
+	dbri->next_desc[last_desc] = first_desc;
+	dbri->pipes[info->pipe].first_desc = first_desc;
+	dbri->pipes[info->pipe].desc = first_desc;
+
+#ifdef DBRI_DEBUG
+	for (desc = first_desc; desc != -1;) {
+		dprintk(D_DESC, "DESC %d: %08x %08x %08x %08x\n",
+			desc,
+			dbri->dma->desc[desc].word1,
+			dbri->dma->desc[desc].ba,
+			dbri->dma->desc[desc].nda, dbri->dma->desc[desc].word4);
+			desc = dbri->next_desc[desc];
+			if (desc == first_desc)
+				break;
+	}
+#endif
+	return 0;
+}
+
+/*
+****************************************************************************
+************************** DBRI - CHI interface ****************************
+****************************************************************************
+
+The CHI is a four-wire (clock, frame sync, data in, data out) time-division
+multiplexed serial interface which the DBRI can operate in either master
+(give clock/frame sync) or slave (take clock/frame sync) mode.
+
+*/
+
+enum master_or_slave { CHImaster, CHIslave };
+
+/*
+ * Lock must not be held before calling it.
+ */
+static void reset_chi(struct snd_dbri *dbri,
+		      enum master_or_slave master_or_slave,
+		      int bits_per_frame)
+{
+	s32 *cmd;
+	int val;
+
+	/* Set CHI Anchor: Pipe 16 */
+
+	cmd = dbri_cmdlock(dbri, 4);
+	val = D_DTS_VO | D_DTS_VI | D_DTS_INS
+		| D_DTS_PRVIN(16) | D_PIPE(16) | D_DTS_PRVOUT(16);
+	*(cmd++) = DBRI_CMD(D_DTS, 0, val);
+	*(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16);
+	*(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16);
+	*(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
+	dbri_cmdsend(dbri, cmd, 4);
+
+	dbri->pipes[16].sdp = 1;
+	dbri->pipes[16].nextpipe = 16;
+
+	cmd = dbri_cmdlock(dbri, 4);
+
+	if (master_or_slave == CHIslave) {
+		/* Setup DBRI for CHI Slave - receive clock, frame sync (FS)
+		 *
+		 * CHICM  = 0 (slave mode, 8 kHz frame rate)
+		 * IR     = give immediate CHI status interrupt
+		 * EN     = give CHI status interrupt upon change
+		 */
+		*(cmd++) = DBRI_CMD(D_CHI, 0, D_CHI_CHICM(0));
+	} else {
+		/* Setup DBRI for CHI Master - generate clock, FS
+		 *
+		 * BPF				=  bits per 8 kHz frame
+		 * 12.288 MHz / CHICM_divisor	= clock rate
+		 * FD = 1 - drive CHIFS on rising edge of CHICK
+		 */
+		int clockrate = bits_per_frame * 8;
+		int divisor = 12288 / clockrate;
+
+		if (divisor > 255 || divisor * clockrate != 12288)
+			printk(KERN_ERR "DBRI: illegal bits_per_frame "
+				"in setup_chi\n");
+
+		*(cmd++) = DBRI_CMD(D_CHI, 0, D_CHI_CHICM(divisor) | D_CHI_FD
+				    | D_CHI_BPF(bits_per_frame));
+	}
+
+	dbri->chi_bpf = bits_per_frame;
+
+	/* CHI Data Mode
+	 *
+	 * RCE   =  0 - receive on falling edge of CHICK
+	 * XCE   =  1 - transmit on rising edge of CHICK
+	 * XEN   =  1 - enable transmitter
+	 * REN   =  1 - enable receiver
+	 */
+
+	*(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
+	*(cmd++) = DBRI_CMD(D_CDM, 0, D_CDM_XCE | D_CDM_XEN | D_CDM_REN);
+	*(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
+
+	dbri_cmdsend(dbri, cmd, 4);
+}
+
+/*
+****************************************************************************
+*********************** CS4215 audio codec management **********************
+****************************************************************************
+
+In the standard SPARC audio configuration, the CS4215 codec is attached
+to the DBRI via the CHI interface and few of the DBRI's PIO pins.
+
+ * Lock must not be held before calling it.
+
+*/
+static __devinit void cs4215_setup_pipes(struct snd_dbri *dbri)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dbri->lock, flags);
+	/*
+	 * Data mode:
+	 * Pipe  4: Send timeslots 1-4 (audio data)
+	 * Pipe 20: Send timeslots 5-8 (part of ctrl data)
+	 * Pipe  6: Receive timeslots 1-4 (audio data)
+	 * Pipe 21: Receive timeslots 6-7. We can only receive 20 bits via
+	 *          interrupt, and the rest of the data (slot 5 and 8) is
+	 *          not relevant for us (only for doublechecking).
+	 *
+	 * Control mode:
+	 * Pipe 17: Send timeslots 1-4 (slots 5-8 are read only)
+	 * Pipe 18: Receive timeslot 1 (clb).
+	 * Pipe 19: Receive timeslot 7 (version).
+	 */
+
+	setup_pipe(dbri, 4, D_SDP_MEM | D_SDP_TO_SER | D_SDP_MSB);
+	setup_pipe(dbri, 20, D_SDP_FIXED | D_SDP_TO_SER | D_SDP_MSB);
+	setup_pipe(dbri, 6, D_SDP_MEM | D_SDP_FROM_SER | D_SDP_MSB);
+	setup_pipe(dbri, 21, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB);
+
+	setup_pipe(dbri, 17, D_SDP_FIXED | D_SDP_TO_SER | D_SDP_MSB);
+	setup_pipe(dbri, 18, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB);
+	setup_pipe(dbri, 19, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB);
+	spin_unlock_irqrestore(&dbri->lock, flags);
+
+	dbri_cmdwait(dbri);
+}
+
+static __devinit int cs4215_init_data(struct cs4215 *mm)
+{
+	/*
+	 * No action, memory resetting only.
+	 *
+	 * Data Time Slot 5-8
+	 * Speaker,Line and Headphone enable. Gain set to the half.
+	 * Input is mike.
+	 */
+	mm->data[0] = CS4215_LO(0x20) | CS4215_HE | CS4215_LE;
+	mm->data[1] = CS4215_RO(0x20) | CS4215_SE;
+	mm->data[2] = CS4215_LG(0x8) | CS4215_IS | CS4215_PIO0 | CS4215_PIO1;
+	mm->data[3] = CS4215_RG(0x8) | CS4215_MA(0xf);
+
+	/*
+	 * Control Time Slot 1-4
+	 * 0: Default I/O voltage scale
+	 * 1: 8 bit ulaw, 8kHz, mono, high pass filter disabled
+	 * 2: Serial enable, CHI master, 128 bits per frame, clock 1
+	 * 3: Tests disabled
+	 */
+	mm->ctrl[0] = CS4215_RSRVD_1 | CS4215_MLB;
+	mm->ctrl[1] = CS4215_DFR_ULAW | CS4215_FREQ[0].csval;
+	mm->ctrl[2] = CS4215_XCLK | CS4215_BSEL_128 | CS4215_FREQ[0].xtal;
+	mm->ctrl[3] = 0;
+
+	mm->status = 0;
+	mm->version = 0xff;
+	mm->precision = 8;	/* For ULAW */
+	mm->channels = 1;
+
+	return 0;
+}
+
+static void cs4215_setdata(struct snd_dbri *dbri, int muted)
+{
+	if (muted) {
+		dbri->mm.data[0] |= 63;
+		dbri->mm.data[1] |= 63;
+		dbri->mm.data[2] &= ~15;
+		dbri->mm.data[3] &= ~15;
+	} else {
+		/* Start by setting the playback attenuation. */
+		struct dbri_streaminfo *info = &dbri->stream_info[DBRI_PLAY];
+		int left_gain = info->left_gain & 0x3f;
+		int right_gain = info->right_gain & 0x3f;
+
+		dbri->mm.data[0] &= ~0x3f;	/* Reset the volume bits */
+		dbri->mm.data[1] &= ~0x3f;
+		dbri->mm.data[0] |= (DBRI_MAX_VOLUME - left_gain);
+		dbri->mm.data[1] |= (DBRI_MAX_VOLUME - right_gain);
+
+		/* Now set the recording gain. */
+		info = &dbri->stream_info[DBRI_REC];
+		left_gain = info->left_gain & 0xf;
+		right_gain = info->right_gain & 0xf;
+		dbri->mm.data[2] |= CS4215_LG(left_gain);
+		dbri->mm.data[3] |= CS4215_RG(right_gain);
+	}
+
+	xmit_fixed(dbri, 20, *(int *)dbri->mm.data);
+}
+
+/*
+ * Set the CS4215 to data mode.
+ */
+static void cs4215_open(struct snd_dbri *dbri)
+{
+	int data_width;
+	u32 tmp;
+	unsigned long flags;
+
+	dprintk(D_MM, "cs4215_open: %d channels, %d bits\n",
+		dbri->mm.channels, dbri->mm.precision);
+
+	/* Temporarily mute outputs, and wait 1/8000 sec (125 us)
+	 * to make sure this takes.  This avoids clicking noises.
+	 */
+
+	cs4215_setdata(dbri, 1);
+	udelay(125);
+
+	/*
+	 * Data mode:
+	 * Pipe  4: Send timeslots 1-4 (audio data)
+	 * Pipe 20: Send timeslots 5-8 (part of ctrl data)
+	 * Pipe  6: Receive timeslots 1-4 (audio data)
+	 * Pipe 21: Receive timeslots 6-7. We can only receive 20 bits via
+	 *          interrupt, and the rest of the data (slot 5 and 8) is
+	 *          not relevant for us (only for doublechecking).
+	 *
+	 * Just like in control mode, the time slots are all offset by eight
+	 * bits.  The CS4215, it seems, observes TSIN (the delayed signal)
+	 * even if it's the CHI master.  Don't ask me...
+	 */
+	spin_lock_irqsave(&dbri->lock, flags);
+	tmp = sbus_readl(dbri->regs + REG0);
+	tmp &= ~(D_C);		/* Disable CHI */
+	sbus_writel(tmp, dbri->regs + REG0);
+
+	/* Switch CS4215 to data mode - set PIO3 to 1 */
+	sbus_writel(D_ENPIO | D_PIO1 | D_PIO3 |
+		    (dbri->mm.onboard ? D_PIO0 : D_PIO2), dbri->regs + REG2);
+
+	reset_chi(dbri, CHIslave, 128);
+
+	/* Note: this next doesn't work for 8-bit stereo, because the two
+	 * channels would be on timeslots 1 and 3, with 2 and 4 idle.
+	 * (See CS4215 datasheet Fig 15)
+	 *
+	 * DBRI non-contiguous mode would be required to make this work.
+	 */
+	data_width = dbri->mm.channels * dbri->mm.precision;
+
+	link_time_slot(dbri, 4, 16, 16, data_width, dbri->mm.offset);
+	link_time_slot(dbri, 20, 4, 16, 32, dbri->mm.offset + 32);
+	link_time_slot(dbri, 6, 16, 16, data_width, dbri->mm.offset);
+	link_time_slot(dbri, 21, 6, 16, 16, dbri->mm.offset + 40);
+
+	/* FIXME: enable CHI after _setdata? */
+	tmp = sbus_readl(dbri->regs + REG0);
+	tmp |= D_C;		/* Enable CHI */
+	sbus_writel(tmp, dbri->regs + REG0);
+	spin_unlock_irqrestore(&dbri->lock, flags);
+
+	cs4215_setdata(dbri, 0);
+}
+
+/*
+ * Send the control information (i.e. audio format)
+ */
+static int cs4215_setctrl(struct snd_dbri *dbri)
+{
+	int i, val;
+	u32 tmp;
+	unsigned long flags;
+
+	/* FIXME - let the CPU do something useful during these delays */
+
+	/* Temporarily mute outputs, and wait 1/8000 sec (125 us)
+	 * to make sure this takes.  This avoids clicking noises.
+	 */
+	cs4215_setdata(dbri, 1);
+	udelay(125);
+
+	/*
+	 * Enable Control mode: Set DBRI's PIO3 (4215's D/~C) to 0, then wait
+	 * 12 cycles <= 12/(5512.5*64) sec = 34.01 usec
+	 */
+	val = D_ENPIO | D_PIO1 | (dbri->mm.onboard ? D_PIO0 : D_PIO2);
+	sbus_writel(val, dbri->regs + REG2);
+	dprintk(D_MM, "cs4215_setctrl: reg2=0x%x\n", val);
+	udelay(34);
+
+	/* In Control mode, the CS4215 is a slave device, so the DBRI must
+	 * operate as CHI master, supplying clocking and frame synchronization.
+	 *
+	 * In Data mode, however, the CS4215 must be CHI master to insure
+	 * that its data stream is synchronous with its codec.
+	 *
+	 * The upshot of all this?  We start by putting the DBRI into master
+	 * mode, program the CS4215 in Control mode, then switch the CS4215
+	 * into Data mode and put the DBRI into slave mode.  Various timing
+	 * requirements must be observed along the way.
+	 *
+	 * Oh, and one more thing, on a SPARCStation 20 (and maybe
+	 * others?), the addressing of the CS4215's time slots is
+	 * offset by eight bits, so we add eight to all the "cycle"
+	 * values in the Define Time Slot (DTS) commands.  This is
+	 * done in hardware by a TI 248 that delays the DBRI->4215
+	 * frame sync signal by eight clock cycles.  Anybody know why?
+	 */
+	spin_lock_irqsave(&dbri->lock, flags);
+	tmp = sbus_readl(dbri->regs + REG0);
+	tmp &= ~D_C;		/* Disable CHI */
+	sbus_writel(tmp, dbri->regs + REG0);
+
+	reset_chi(dbri, CHImaster, 128);
+
+	/*
+	 * Control mode:
+	 * Pipe 17: Send timeslots 1-4 (slots 5-8 are read only)
+	 * Pipe 18: Receive timeslot 1 (clb).
+	 * Pipe 19: Receive timeslot 7 (version).
+	 */
+
+	link_time_slot(dbri, 17, 16, 16, 32, dbri->mm.offset);
+	link_time_slot(dbri, 18, 16, 16, 8, dbri->mm.offset);
+	link_time_slot(dbri, 19, 18, 16, 8, dbri->mm.offset + 48);
+	spin_unlock_irqrestore(&dbri->lock, flags);
+
+	/* Wait for the chip to echo back CLB (Control Latch Bit) as zero */
+	dbri->mm.ctrl[0] &= ~CS4215_CLB;
+	xmit_fixed(dbri, 17, *(int *)dbri->mm.ctrl);
+
+	spin_lock_irqsave(&dbri->lock, flags);
+	tmp = sbus_readl(dbri->regs + REG0);
+	tmp |= D_C;		/* Enable CHI */
+	sbus_writel(tmp, dbri->regs + REG0);
+	spin_unlock_irqrestore(&dbri->lock, flags);
+
+	for (i = 10; ((dbri->mm.status & 0xe4) != 0x20); --i)
+		msleep_interruptible(1);
+
+	if (i == 0) {
+		dprintk(D_MM, "CS4215 didn't respond to CLB (0x%02x)\n",
+			dbri->mm.status);
+		return -1;
+	}
+
+	/* Disable changes to our copy of the version number, as we are about
+	 * to leave control mode.
+	 */
+	recv_fixed(dbri, 19, NULL);
+
+	/* Terminate CS4215 control mode - data sheet says
+	 * "Set CLB=1 and send two more frames of valid control info"
+	 */
+	dbri->mm.ctrl[0] |= CS4215_CLB;
+	xmit_fixed(dbri, 17, *(int *)dbri->mm.ctrl);
+
+	/* Two frames of control info @ 8kHz frame rate = 250 us delay */
+	udelay(250);
+
+	cs4215_setdata(dbri, 0);
+
+	return 0;
+}
+
+/*
+ * Setup the codec with the sampling rate, audio format and number of
+ * channels.
+ * As part of the process we resend the settings for the data
+ * timeslots as well.
+ */
+static int cs4215_prepare(struct snd_dbri *dbri, unsigned int rate,
+			  snd_pcm_format_t format, unsigned int channels)
+{
+	int freq_idx;
+	int ret = 0;
+
+	/* Lookup index for this rate */
+	for (freq_idx = 0; CS4215_FREQ[freq_idx].freq != 0; freq_idx++) {
+		if (CS4215_FREQ[freq_idx].freq == rate)
+			break;
+	}
+	if (CS4215_FREQ[freq_idx].freq != rate) {
+		printk(KERN_WARNING "DBRI: Unsupported rate %d Hz\n", rate);
+		return -1;
+	}
+
+	switch (format) {
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		dbri->mm.ctrl[1] = CS4215_DFR_ULAW;
+		dbri->mm.precision = 8;
+		break;
+	case SNDRV_PCM_FORMAT_A_LAW:
+		dbri->mm.ctrl[1] = CS4215_DFR_ALAW;
+		dbri->mm.precision = 8;
+		break;
+	case SNDRV_PCM_FORMAT_U8:
+		dbri->mm.ctrl[1] = CS4215_DFR_LINEAR8;
+		dbri->mm.precision = 8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE:
+		dbri->mm.ctrl[1] = CS4215_DFR_LINEAR16;
+		dbri->mm.precision = 16;
+		break;
+	default:
+		printk(KERN_WARNING "DBRI: Unsupported format %d\n", format);
+		return -1;
+	}
+
+	/* Add rate parameters */
+	dbri->mm.ctrl[1] |= CS4215_FREQ[freq_idx].csval;
+	dbri->mm.ctrl[2] = CS4215_XCLK |
+	    CS4215_BSEL_128 | CS4215_FREQ[freq_idx].xtal;
+
+	dbri->mm.channels = channels;
+	if (channels == 2)
+		dbri->mm.ctrl[1] |= CS4215_DFR_STEREO;
+
+	ret = cs4215_setctrl(dbri);
+	if (ret == 0)
+		cs4215_open(dbri);	/* set codec to data mode */
+
+	return ret;
+}
+
+/*
+ *
+ */
+static __devinit int cs4215_init(struct snd_dbri *dbri)
+{
+	u32 reg2 = sbus_readl(dbri->regs + REG2);
+	dprintk(D_MM, "cs4215_init: reg2=0x%x\n", reg2);
+
+	/* Look for the cs4215 chips */
+	if (reg2 & D_PIO2) {
+		dprintk(D_MM, "Onboard CS4215 detected\n");
+		dbri->mm.onboard = 1;
+	}
+	if (reg2 & D_PIO0) {
+		dprintk(D_MM, "Speakerbox detected\n");
+		dbri->mm.onboard = 0;
+
+		if (reg2 & D_PIO2) {
+			printk(KERN_INFO "DBRI: Using speakerbox / "
+			       "ignoring onboard mmcodec.\n");
+			sbus_writel(D_ENPIO2, dbri->regs + REG2);
+		}
+	}
+
+	if (!(reg2 & (D_PIO0 | D_PIO2))) {
+		printk(KERN_ERR "DBRI: no mmcodec found.\n");
+		return -EIO;
+	}
+
+	cs4215_setup_pipes(dbri);
+	cs4215_init_data(&dbri->mm);
+
+	/* Enable capture of the status & version timeslots. */
+	recv_fixed(dbri, 18, &dbri->mm.status);
+	recv_fixed(dbri, 19, &dbri->mm.version);
+
+	dbri->mm.offset = dbri->mm.onboard ? 0 : 8;
+	if (cs4215_setctrl(dbri) == -1 || dbri->mm.version == 0xff) {
+		dprintk(D_MM, "CS4215 failed probe at offset %d\n",
+			dbri->mm.offset);
+		return -EIO;
+	}
+	dprintk(D_MM, "Found CS4215 at offset %d\n", dbri->mm.offset);
+
+	return 0;
+}
+
+/*
+****************************************************************************
+*************************** DBRI interrupt handler *************************
+****************************************************************************
+
+The DBRI communicates with the CPU mainly via a circular interrupt
+buffer.  When an interrupt is signaled, the CPU walks through the
+buffer and calls dbri_process_one_interrupt() for each interrupt word.
+Complicated interrupts are handled by dedicated functions (which
+appear first in this file).  Any pending interrupts can be serviced by
+calling dbri_process_interrupt_buffer(), which works even if the CPU's
+interrupts are disabled.
+
+*/
+
+/* xmit_descs()
+ *
+ * Starts transmitting the current TD's for recording/playing.
+ * For playback, ALSA has filled the DMA memory with new data (we hope).
+ */
+static void xmit_descs(struct snd_dbri *dbri)
+{
+	struct dbri_streaminfo *info;
+	s32 *cmd;
+	unsigned long flags;
+	int first_td;
+
+	if (dbri == NULL)
+		return;		/* Disabled */
+
+	info = &dbri->stream_info[DBRI_REC];
+	spin_lock_irqsave(&dbri->lock, flags);
+
+	if (info->pipe >= 0) {
+		first_td = dbri->pipes[info->pipe].first_desc;
+
+		dprintk(D_DESC, "xmit_descs rec @ TD %d\n", first_td);
+
+		/* Stream could be closed by the time we run. */
+		if (first_td >= 0) {
+			cmd = dbri_cmdlock(dbri, 2);
+			*(cmd++) = DBRI_CMD(D_SDP, 0,
+					    dbri->pipes[info->pipe].sdp
+					    | D_SDP_P | D_SDP_EVERY | D_SDP_C);
+			*(cmd++) = dbri->dma_dvma +
+				   dbri_dma_off(desc, first_td);
+			dbri_cmdsend(dbri, cmd, 2);
+
+			/* Reset our admin of the pipe. */
+			dbri->pipes[info->pipe].desc = first_td;
+		}
+	}
+
+	info = &dbri->stream_info[DBRI_PLAY];
+
+	if (info->pipe >= 0) {
+		first_td = dbri->pipes[info->pipe].first_desc;
+
+		dprintk(D_DESC, "xmit_descs play @ TD %d\n", first_td);
+
+		/* Stream could be closed by the time we run. */
+		if (first_td >= 0) {
+			cmd = dbri_cmdlock(dbri, 2);
+			*(cmd++) = DBRI_CMD(D_SDP, 0,
+					    dbri->pipes[info->pipe].sdp
+					    | D_SDP_P | D_SDP_EVERY | D_SDP_C);
+			*(cmd++) = dbri->dma_dvma +
+				   dbri_dma_off(desc, first_td);
+			dbri_cmdsend(dbri, cmd, 2);
+
+			/* Reset our admin of the pipe. */
+			dbri->pipes[info->pipe].desc = first_td;
+		}
+	}
+
+	spin_unlock_irqrestore(&dbri->lock, flags);
+}
+
+/* transmission_complete_intr()
+ *
+ * Called by main interrupt handler when DBRI signals transmission complete
+ * on a pipe (interrupt triggered by the B bit in a transmit descriptor).
+ *
+ * Walks through the pipe's list of transmit buffer descriptors and marks
+ * them as available. Stops when the first descriptor is found without
+ * TBC (Transmit Buffer Complete) set, or we've run through them all.
+ *
+ * The DMA buffers are not released. They form a ring buffer and
+ * they are filled by ALSA while others are transmitted by DMA.
+ *
+ */
+
+static void transmission_complete_intr(struct snd_dbri *dbri, int pipe)
+{
+	struct dbri_streaminfo *info = &dbri->stream_info[DBRI_PLAY];
+	int td = dbri->pipes[pipe].desc;
+	int status;
+
+	while (td >= 0) {
+		if (td >= DBRI_NO_DESCS) {
+			printk(KERN_ERR "DBRI: invalid td on pipe %d\n", pipe);
+			return;
+		}
+
+		status = DBRI_TD_STATUS(dbri->dma->desc[td].word4);
+		if (!(status & DBRI_TD_TBC))
+			break;
+
+		dprintk(D_INT, "TD %d, status 0x%02x\n", td, status);
+
+		dbri->dma->desc[td].word4 = 0;	/* Reset it for next time. */
+		info->offset += DBRI_RD_CNT(dbri->dma->desc[td].word1);
+
+		td = dbri->next_desc[td];
+		dbri->pipes[pipe].desc = td;
+	}
+
+	/* Notify ALSA */
+	spin_unlock(&dbri->lock);
+	snd_pcm_period_elapsed(info->substream);
+	spin_lock(&dbri->lock);
+}
+
+static void reception_complete_intr(struct snd_dbri *dbri, int pipe)
+{
+	struct dbri_streaminfo *info;
+	int rd = dbri->pipes[pipe].desc;
+	s32 status;
+
+	if (rd < 0 || rd >= DBRI_NO_DESCS) {
+		printk(KERN_ERR "DBRI: invalid rd on pipe %d\n", pipe);
+		return;
+	}
+
+	dbri->pipes[pipe].desc = dbri->next_desc[rd];
+	status = dbri->dma->desc[rd].word1;
+	dbri->dma->desc[rd].word1 = 0;	/* Reset it for next time. */
+
+	info = &dbri->stream_info[DBRI_REC];
+	info->offset += DBRI_RD_CNT(status);
+
+	/* FIXME: Check status */
+
+	dprintk(D_INT, "Recv RD %d, status 0x%02x, len %d\n",
+		rd, DBRI_RD_STATUS(status), DBRI_RD_CNT(status));
+
+	/* Notify ALSA */
+	spin_unlock(&dbri->lock);
+	snd_pcm_period_elapsed(info->substream);
+	spin_lock(&dbri->lock);
+}
+
+static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x)
+{
+	int val = D_INTR_GETVAL(x);
+	int channel = D_INTR_GETCHAN(x);
+	int command = D_INTR_GETCMD(x);
+	int code = D_INTR_GETCODE(x);
+#ifdef DBRI_DEBUG
+	int rval = D_INTR_GETRVAL(x);
+#endif
+
+	if (channel == D_INTR_CMD) {
+		dprintk(D_CMD, "INTR: Command: %-5s  Value:%d\n",
+			cmds[command], val);
+	} else {
+		dprintk(D_INT, "INTR: Chan:%d Code:%d Val:%#x\n",
+			channel, code, rval);
+	}
+
+	switch (code) {
+	case D_INTR_CMDI:
+		if (command != D_WAIT)
+			printk(KERN_ERR "DBRI: Command read interrupt\n");
+		break;
+	case D_INTR_BRDY:
+		reception_complete_intr(dbri, channel);
+		break;
+	case D_INTR_XCMP:
+	case D_INTR_MINT:
+		transmission_complete_intr(dbri, channel);
+		break;
+	case D_INTR_UNDR:
+		/* UNDR - Transmission underrun
+		 * resend SDP command with clear pipe bit (C) set
+		 */
+		{
+	/* FIXME: do something useful in case of underrun */
+			printk(KERN_ERR "DBRI: Underrun error\n");
+#if 0
+			s32 *cmd;
+			int pipe = channel;
+			int td = dbri->pipes[pipe].desc;
+
+			dbri->dma->desc[td].word4 = 0;
+			cmd = dbri_cmdlock(dbri, NoGetLock);
+			*(cmd++) = DBRI_CMD(D_SDP, 0,
+					    dbri->pipes[pipe].sdp
+					    | D_SDP_P | D_SDP_C | D_SDP_2SAME);
+			*(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, td);
+			dbri_cmdsend(dbri, cmd);
+#endif
+		}
+		break;
+	case D_INTR_FXDT:
+		/* FXDT - Fixed data change */
+		if (dbri->pipes[channel].sdp & D_SDP_MSB)
+			val = reverse_bytes(val, dbri->pipes[channel].length);
+
+		if (dbri->pipes[channel].recv_fixed_ptr)
+			*(dbri->pipes[channel].recv_fixed_ptr) = val;
+		break;
+	default:
+		if (channel != D_INTR_CMD)
+			printk(KERN_WARNING
+			       "DBRI: Ignored Interrupt: %d (0x%x)\n", code, x);
+	}
+}
+
+/* dbri_process_interrupt_buffer advances through the DBRI's interrupt
+ * buffer until it finds a zero word (indicating nothing more to do
+ * right now).  Non-zero words require processing and are handed off
+ * to dbri_process_one_interrupt AFTER advancing the pointer.
+ */
+static void dbri_process_interrupt_buffer(struct snd_dbri *dbri)
+{
+	s32 x;
+
+	while ((x = dbri->dma->intr[dbri->dbri_irqp]) != 0) {
+		dbri->dma->intr[dbri->dbri_irqp] = 0;
+		dbri->dbri_irqp++;
+		if (dbri->dbri_irqp == DBRI_INT_BLK)
+			dbri->dbri_irqp = 1;
+
+		dbri_process_one_interrupt(dbri, x);
+	}
+}
+
+static irqreturn_t snd_dbri_interrupt(int irq, void *dev_id)
+{
+	struct snd_dbri *dbri = dev_id;
+	static int errcnt = 0;
+	int x;
+
+	if (dbri == NULL)
+		return IRQ_NONE;
+	spin_lock(&dbri->lock);
+
+	/*
+	 * Read it, so the interrupt goes away.
+	 */
+	x = sbus_readl(dbri->regs + REG1);
+
+	if (x & (D_MRR | D_MLE | D_LBG | D_MBE)) {
+		u32 tmp;
+
+		if (x & D_MRR)
+			printk(KERN_ERR
+			       "DBRI: Multiple Error Ack on SBus reg1=0x%x\n",
+			       x);
+		if (x & D_MLE)
+			printk(KERN_ERR
+			       "DBRI: Multiple Late Error on SBus reg1=0x%x\n",
+			       x);
+		if (x & D_LBG)
+			printk(KERN_ERR
+			       "DBRI: Lost Bus Grant on SBus reg1=0x%x\n", x);
+		if (x & D_MBE)
+			printk(KERN_ERR
+			       "DBRI: Burst Error on SBus reg1=0x%x\n", x);
+
+		/* Some of these SBus errors cause the chip's SBus circuitry
+		 * to be disabled, so just re-enable and try to keep going.
+		 *
+		 * The only one I've seen is MRR, which will be triggered
+		 * if you let a transmit pipe underrun, then try to CDP it.
+		 *
+		 * If these things persist, we reset the chip.
+		 */
+		if ((++errcnt) % 10 == 0) {
+			dprintk(D_INT, "Interrupt errors exceeded.\n");
+			dbri_reset(dbri);
+		} else {
+			tmp = sbus_readl(dbri->regs + REG0);
+			tmp &= ~(D_D);
+			sbus_writel(tmp, dbri->regs + REG0);
+		}
+	}
+
+	dbri_process_interrupt_buffer(dbri);
+
+	spin_unlock(&dbri->lock);
+
+	return IRQ_HANDLED;
+}
+
+/****************************************************************************
+		PCM Interface
+****************************************************************************/
+static struct snd_pcm_hardware snd_dbri_pcm_hw = {
+	.info		= SNDRV_PCM_INFO_MMAP |
+			  SNDRV_PCM_INFO_INTERLEAVED |
+			  SNDRV_PCM_INFO_BLOCK_TRANSFER |
+			  SNDRV_PCM_INFO_MMAP_VALID |
+			  SNDRV_PCM_INFO_BATCH,
+	.formats	= SNDRV_PCM_FMTBIT_MU_LAW |
+			  SNDRV_PCM_FMTBIT_A_LAW |
+			  SNDRV_PCM_FMTBIT_U8 |
+			  SNDRV_PCM_FMTBIT_S16_BE,
+	.rates		= SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_5512,
+	.rate_min		= 5512,
+	.rate_max		= 48000,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	.buffer_bytes_max	= 64 * 1024,
+	.period_bytes_min	= 1,
+	.period_bytes_max	= DBRI_TD_MAXCNT,
+	.periods_min		= 1,
+	.periods_max		= 1024,
+};
+
+static int snd_hw_rule_format(struct snd_pcm_hw_params *params,
+			      struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *c = hw_param_interval(params,
+				SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	struct snd_mask fmt;
+
+	snd_mask_any(&fmt);
+	if (c->min > 1) {
+		fmt.bits[0] &= SNDRV_PCM_FMTBIT_S16_BE;
+		return snd_mask_refine(f, &fmt);
+	}
+	return 0;
+}
+
+static int snd_hw_rule_channels(struct snd_pcm_hw_params *params,
+				struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *c = hw_param_interval(params,
+				SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	struct snd_interval ch;
+
+	snd_interval_any(&ch);
+	if (!(f->bits[0] & SNDRV_PCM_FMTBIT_S16_BE)) {
+		ch.min = 1;
+		ch.max = 1;
+		ch.integer = 1;
+		return snd_interval_refine(c, &ch);
+	}
+	return 0;
+}
+
+static int snd_dbri_open(struct snd_pcm_substream *substream)
+{
+	struct snd_dbri *dbri = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream);
+	unsigned long flags;
+
+	dprintk(D_USR, "open audio output.\n");
+	runtime->hw = snd_dbri_pcm_hw;
+
+	spin_lock_irqsave(&dbri->lock, flags);
+	info->substream = substream;
+	info->offset = 0;
+	info->dvma_buffer = 0;
+	info->pipe = -1;
+	spin_unlock_irqrestore(&dbri->lock, flags);
+
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+			    snd_hw_rule_format, NULL, SNDRV_PCM_HW_PARAM_FORMAT,
+			    -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
+			    snd_hw_rule_channels, NULL,
+			    SNDRV_PCM_HW_PARAM_CHANNELS,
+			    -1);
+
+	cs4215_open(dbri);
+
+	return 0;
+}
+
+static int snd_dbri_close(struct snd_pcm_substream *substream)
+{
+	struct snd_dbri *dbri = snd_pcm_substream_chip(substream);
+	struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream);
+
+	dprintk(D_USR, "close audio output.\n");
+	info->substream = NULL;
+	info->offset = 0;
+
+	return 0;
+}
+
+static int snd_dbri_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_dbri *dbri = snd_pcm_substream_chip(substream);
+	struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream);
+	int direction;
+	int ret;
+
+	/* set sampling rate, audio format and number of channels */
+	ret = cs4215_prepare(dbri, params_rate(hw_params),
+			     params_format(hw_params),
+			     params_channels(hw_params));
+	if (ret != 0)
+		return ret;
+
+	if ((ret = snd_pcm_lib_malloc_pages(substream,
+				params_buffer_bytes(hw_params))) < 0) {
+		printk(KERN_ERR "malloc_pages failed with %d\n", ret);
+		return ret;
+	}
+
+	/* hw_params can get called multiple times. Only map the DMA once.
+	 */
+	if (info->dvma_buffer == 0) {
+		if (DBRI_STREAMNO(substream) == DBRI_PLAY)
+			direction = DMA_TO_DEVICE;
+		else
+			direction = DMA_FROM_DEVICE;
+
+		info->dvma_buffer =
+			dma_map_single(&dbri->op->dev,
+				       runtime->dma_area,
+				       params_buffer_bytes(hw_params),
+				       direction);
+	}
+
+	direction = params_buffer_bytes(hw_params);
+	dprintk(D_USR, "hw_params: %d bytes, dvma=%x\n",
+		direction, info->dvma_buffer);
+	return 0;
+}
+
+static int snd_dbri_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_dbri *dbri = snd_pcm_substream_chip(substream);
+	struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream);
+	int direction;
+
+	dprintk(D_USR, "hw_free.\n");
+
+	/* hw_free can get called multiple times. Only unmap the DMA once.
+	 */
+	if (info->dvma_buffer) {
+		if (DBRI_STREAMNO(substream) == DBRI_PLAY)
+			direction = DMA_TO_DEVICE;
+		else
+			direction = DMA_FROM_DEVICE;
+
+		dma_unmap_single(&dbri->op->dev, info->dvma_buffer,
+				 substream->runtime->buffer_size, direction);
+		info->dvma_buffer = 0;
+	}
+	if (info->pipe != -1) {
+		reset_pipe(dbri, info->pipe);
+		info->pipe = -1;
+	}
+
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_dbri_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_dbri *dbri = snd_pcm_substream_chip(substream);
+	struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream);
+	int ret;
+
+	info->size = snd_pcm_lib_buffer_bytes(substream);
+	if (DBRI_STREAMNO(substream) == DBRI_PLAY)
+		info->pipe = 4;	/* Send pipe */
+	else
+		info->pipe = 6;	/* Receive pipe */
+
+	spin_lock_irq(&dbri->lock);
+	info->offset = 0;
+
+	/* Setup the all the transmit/receive descriptors to cover the
+	 * whole DMA buffer.
+	 */
+	ret = setup_descs(dbri, DBRI_STREAMNO(substream),
+			  snd_pcm_lib_period_bytes(substream));
+
+	spin_unlock_irq(&dbri->lock);
+
+	dprintk(D_USR, "prepare audio output. %d bytes\n", info->size);
+	return ret;
+}
+
+static int snd_dbri_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_dbri *dbri = snd_pcm_substream_chip(substream);
+	struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream);
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		dprintk(D_USR, "start audio, period is %d bytes\n",
+			(int)snd_pcm_lib_period_bytes(substream));
+		/* Re-submit the TDs. */
+		xmit_descs(dbri);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		dprintk(D_USR, "stop audio.\n");
+		reset_pipe(dbri, info->pipe);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static snd_pcm_uframes_t snd_dbri_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_dbri *dbri = snd_pcm_substream_chip(substream);
+	struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream);
+	snd_pcm_uframes_t ret;
+
+	ret = bytes_to_frames(substream->runtime, info->offset)
+		% substream->runtime->buffer_size;
+	dprintk(D_USR, "I/O pointer: %ld frames of %ld.\n",
+		ret, substream->runtime->buffer_size);
+	return ret;
+}
+
+static struct snd_pcm_ops snd_dbri_ops = {
+	.open = snd_dbri_open,
+	.close = snd_dbri_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_dbri_hw_params,
+	.hw_free = snd_dbri_hw_free,
+	.prepare = snd_dbri_prepare,
+	.trigger = snd_dbri_trigger,
+	.pointer = snd_dbri_pointer,
+};
+
+static int __devinit snd_dbri_pcm(struct snd_card *card)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	if ((err = snd_pcm_new(card,
+			       /* ID */		    "sun_dbri",
+			       /* device */	    0,
+			       /* playback count */ 1,
+			       /* capture count */  1, &pcm)) < 0)
+		return err;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_dbri_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_dbri_ops);
+
+	pcm->private_data = card->private_data;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, card->shortname);
+
+	if ((err = snd_pcm_lib_preallocate_pages_for_all(pcm,
+			SNDRV_DMA_TYPE_CONTINUOUS,
+			snd_dma_continuous_data(GFP_KERNEL),
+			64 * 1024, 64 * 1024)) < 0)
+		return err;
+
+	return 0;
+}
+
+/*****************************************************************************
+			Mixer interface
+*****************************************************************************/
+
+static int snd_cs4215_info_volume(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	if (kcontrol->private_value == DBRI_PLAY)
+		uinfo->value.integer.max = DBRI_MAX_VOLUME;
+	else
+		uinfo->value.integer.max = DBRI_MAX_GAIN;
+	return 0;
+}
+
+static int snd_cs4215_get_volume(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_dbri *dbri = snd_kcontrol_chip(kcontrol);
+	struct dbri_streaminfo *info;
+
+	if (snd_BUG_ON(!dbri))
+		return -EINVAL;
+	info = &dbri->stream_info[kcontrol->private_value];
+
+	ucontrol->value.integer.value[0] = info->left_gain;
+	ucontrol->value.integer.value[1] = info->right_gain;
+	return 0;
+}
+
+static int snd_cs4215_put_volume(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_dbri *dbri = snd_kcontrol_chip(kcontrol);
+	struct dbri_streaminfo *info =
+				&dbri->stream_info[kcontrol->private_value];
+	unsigned int vol[2];
+	int changed = 0;
+
+	vol[0] = ucontrol->value.integer.value[0];
+	vol[1] = ucontrol->value.integer.value[1];
+	if (kcontrol->private_value == DBRI_PLAY) {
+		if (vol[0] > DBRI_MAX_VOLUME || vol[1] > DBRI_MAX_VOLUME)
+			return -EINVAL;
+	} else {
+		if (vol[0] > DBRI_MAX_GAIN || vol[1] > DBRI_MAX_GAIN)
+			return -EINVAL;
+	}
+
+	if (info->left_gain != vol[0]) {
+		info->left_gain = vol[0];
+		changed = 1;
+	}
+	if (info->right_gain != vol[1]) {
+		info->right_gain = vol[1];
+		changed = 1;
+	}
+	if (changed) {
+		/* First mute outputs, and wait 1/8000 sec (125 us)
+		 * to make sure this takes.  This avoids clicking noises.
+		 */
+		cs4215_setdata(dbri, 1);
+		udelay(125);
+		cs4215_setdata(dbri, 0);
+	}
+	return changed;
+}
+
+static int snd_cs4215_info_single(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+
+	uinfo->type = (mask == 1) ?
+	    SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+	return 0;
+}
+
+static int snd_cs4215_get_single(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_dbri *dbri = snd_kcontrol_chip(kcontrol);
+	int elem = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 1;
+
+	if (snd_BUG_ON(!dbri))
+		return -EINVAL;
+
+	if (elem < 4)
+		ucontrol->value.integer.value[0] =
+		    (dbri->mm.data[elem] >> shift) & mask;
+	else
+		ucontrol->value.integer.value[0] =
+		    (dbri->mm.ctrl[elem - 4] >> shift) & mask;
+
+	if (invert == 1)
+		ucontrol->value.integer.value[0] =
+		    mask - ucontrol->value.integer.value[0];
+	return 0;
+}
+
+static int snd_cs4215_put_single(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_dbri *dbri = snd_kcontrol_chip(kcontrol);
+	int elem = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 1;
+	int changed = 0;
+	unsigned short val;
+
+	if (snd_BUG_ON(!dbri))
+		return -EINVAL;
+
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert == 1)
+		val = mask - val;
+	val <<= shift;
+
+	if (elem < 4) {
+		dbri->mm.data[elem] = (dbri->mm.data[elem] &
+				       ~(mask << shift)) | val;
+		changed = (val != dbri->mm.data[elem]);
+	} else {
+		dbri->mm.ctrl[elem - 4] = (dbri->mm.ctrl[elem - 4] &
+					   ~(mask << shift)) | val;
+		changed = (val != dbri->mm.ctrl[elem - 4]);
+	}
+
+	dprintk(D_GEN, "put_single: mask=0x%x, changed=%d, "
+		"mixer-value=%ld, mm-value=0x%x\n",
+		mask, changed, ucontrol->value.integer.value[0],
+		dbri->mm.data[elem & 3]);
+
+	if (changed) {
+		/* First mute outputs, and wait 1/8000 sec (125 us)
+		 * to make sure this takes.  This avoids clicking noises.
+		 */
+		cs4215_setdata(dbri, 1);
+		udelay(125);
+		cs4215_setdata(dbri, 0);
+	}
+	return changed;
+}
+
+/* Entries 0-3 map to the 4 data timeslots, entries 4-7 map to the 4 control
+   timeslots. Shift is the bit offset in the timeslot, mask defines the
+   number of bits. invert is a boolean for use with attenuation.
+ */
+#define CS4215_SINGLE(xname, entry, shift, mask, invert)	\
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),		\
+  .info = snd_cs4215_info_single,				\
+  .get = snd_cs4215_get_single, .put = snd_cs4215_put_single,	\
+  .private_value = (entry) | ((shift) << 8) | ((mask) << 16) |	\
+			((invert) << 24) },
+
+static struct snd_kcontrol_new dbri_controls[] __devinitdata = {
+	{
+	 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	 .name  = "Playback Volume",
+	 .info  = snd_cs4215_info_volume,
+	 .get   = snd_cs4215_get_volume,
+	 .put   = snd_cs4215_put_volume,
+	 .private_value = DBRI_PLAY,
+	 },
+	CS4215_SINGLE("Headphone switch", 0, 7, 1, 0)
+	CS4215_SINGLE("Line out switch", 0, 6, 1, 0)
+	CS4215_SINGLE("Speaker switch", 1, 6, 1, 0)
+	{
+	 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	 .name  = "Capture Volume",
+	 .info  = snd_cs4215_info_volume,
+	 .get   = snd_cs4215_get_volume,
+	 .put   = snd_cs4215_put_volume,
+	 .private_value = DBRI_REC,
+	 },
+	/* FIXME: mic/line switch */
+	CS4215_SINGLE("Line in switch", 2, 4, 1, 0)
+	CS4215_SINGLE("High Pass Filter switch", 5, 7, 1, 0)
+	CS4215_SINGLE("Monitor Volume", 3, 4, 0xf, 1)
+	CS4215_SINGLE("Mic boost", 4, 4, 1, 1)
+};
+
+static int __devinit snd_dbri_mixer(struct snd_card *card)
+{
+	int idx, err;
+	struct snd_dbri *dbri;
+
+	if (snd_BUG_ON(!card || !card->private_data))
+		return -EINVAL;
+	dbri = card->private_data;
+
+	strcpy(card->mixername, card->shortname);
+
+	for (idx = 0; idx < ARRAY_SIZE(dbri_controls); idx++) {
+		err = snd_ctl_add(card,
+				snd_ctl_new1(&dbri_controls[idx], dbri));
+		if (err < 0)
+			return err;
+	}
+
+	for (idx = DBRI_REC; idx < DBRI_NO_STREAMS; idx++) {
+		dbri->stream_info[idx].left_gain = 0;
+		dbri->stream_info[idx].right_gain = 0;
+	}
+
+	return 0;
+}
+
+/****************************************************************************
+			/proc interface
+****************************************************************************/
+static void dbri_regs_read(struct snd_info_entry *entry,
+			   struct snd_info_buffer *buffer)
+{
+	struct snd_dbri *dbri = entry->private_data;
+
+	snd_iprintf(buffer, "REG0: 0x%x\n", sbus_readl(dbri->regs + REG0));
+	snd_iprintf(buffer, "REG2: 0x%x\n", sbus_readl(dbri->regs + REG2));
+	snd_iprintf(buffer, "REG8: 0x%x\n", sbus_readl(dbri->regs + REG8));
+	snd_iprintf(buffer, "REG9: 0x%x\n", sbus_readl(dbri->regs + REG9));
+}
+
+#ifdef DBRI_DEBUG
+static void dbri_debug_read(struct snd_info_entry *entry,
+			    struct snd_info_buffer *buffer)
+{
+	struct snd_dbri *dbri = entry->private_data;
+	int pipe;
+	snd_iprintf(buffer, "debug=%d\n", dbri_debug);
+
+	for (pipe = 0; pipe < 32; pipe++) {
+		if (pipe_active(dbri, pipe)) {
+			struct dbri_pipe *pptr = &dbri->pipes[pipe];
+			snd_iprintf(buffer,
+				    "Pipe %d: %s SDP=0x%x desc=%d, "
+				    "len=%d next %d\n",
+				    pipe,
+				   (pptr->sdp & D_SDP_TO_SER) ? "output" :
+								 "input",
+				    pptr->sdp, pptr->desc,
+				    pptr->length, pptr->nextpipe);
+		}
+	}
+}
+#endif
+
+static void __devinit snd_dbri_proc(struct snd_card *card)
+{
+	struct snd_dbri *dbri = card->private_data;
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(card, "regs", &entry))
+		snd_info_set_text_ops(entry, dbri, dbri_regs_read);
+
+#ifdef DBRI_DEBUG
+	if (!snd_card_proc_new(card, "debug", &entry)) {
+		snd_info_set_text_ops(entry, dbri, dbri_debug_read);
+		entry->mode = S_IFREG | S_IRUGO;	/* Readable only. */
+	}
+#endif
+}
+
+/*
+****************************************************************************
+**************************** Initialization ********************************
+****************************************************************************
+*/
+static void snd_dbri_free(struct snd_dbri *dbri);
+
+static int __devinit snd_dbri_create(struct snd_card *card,
+				     struct platform_device *op,
+				     int irq, int dev)
+{
+	struct snd_dbri *dbri = card->private_data;
+	int err;
+
+	spin_lock_init(&dbri->lock);
+	dbri->op = op;
+	dbri->irq = irq;
+
+	dbri->dma = dma_alloc_coherent(&op->dev,
+				       sizeof(struct dbri_dma),
+				       &dbri->dma_dvma, GFP_ATOMIC);
+	if (!dbri->dma)
+		return -ENOMEM;
+	memset((void *)dbri->dma, 0, sizeof(struct dbri_dma));
+
+	dprintk(D_GEN, "DMA Cmd Block 0x%p (0x%08x)\n",
+		dbri->dma, dbri->dma_dvma);
+
+	/* Map the registers into memory. */
+	dbri->regs_size = resource_size(&op->resource[0]);
+	dbri->regs = of_ioremap(&op->resource[0], 0,
+				dbri->regs_size, "DBRI Registers");
+	if (!dbri->regs) {
+		printk(KERN_ERR "DBRI: could not allocate registers\n");
+		dma_free_coherent(&op->dev, sizeof(struct dbri_dma),
+				  (void *)dbri->dma, dbri->dma_dvma);
+		return -EIO;
+	}
+
+	err = request_irq(dbri->irq, snd_dbri_interrupt, IRQF_SHARED,
+			  "DBRI audio", dbri);
+	if (err) {
+		printk(KERN_ERR "DBRI: Can't get irq %d\n", dbri->irq);
+		of_iounmap(&op->resource[0], dbri->regs, dbri->regs_size);
+		dma_free_coherent(&op->dev, sizeof(struct dbri_dma),
+				  (void *)dbri->dma, dbri->dma_dvma);
+		return err;
+	}
+
+	/* Do low level initialization of the DBRI and CS4215 chips */
+	dbri_initialize(dbri);
+	err = cs4215_init(dbri);
+	if (err) {
+		snd_dbri_free(dbri);
+		return err;
+	}
+
+	return 0;
+}
+
+static void snd_dbri_free(struct snd_dbri *dbri)
+{
+	dprintk(D_GEN, "snd_dbri_free\n");
+	dbri_reset(dbri);
+
+	if (dbri->irq)
+		free_irq(dbri->irq, dbri);
+
+	if (dbri->regs)
+		of_iounmap(&dbri->op->resource[0], dbri->regs, dbri->regs_size);
+
+	if (dbri->dma)
+		dma_free_coherent(&dbri->op->dev,
+				  sizeof(struct dbri_dma),
+				  (void *)dbri->dma, dbri->dma_dvma);
+}
+
+static int __devinit dbri_probe(struct platform_device *op, const struct of_device_id *match)
+{
+	struct snd_dbri *dbri;
+	struct resource *rp;
+	struct snd_card *card;
+	static int dev = 0;
+	int irq;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	irq = op->archdata.irqs[0];
+	if (irq <= 0) {
+		printk(KERN_ERR "DBRI-%d: No IRQ.\n", dev);
+		return -ENODEV;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct snd_dbri), &card);
+	if (err < 0)
+		return err;
+
+	strcpy(card->driver, "DBRI");
+	strcpy(card->shortname, "Sun DBRI");
+	rp = &op->resource[0];
+	sprintf(card->longname, "%s at 0x%02lx:0x%016Lx, irq %d",
+		card->shortname,
+		rp->flags & 0xffL, (unsigned long long)rp->start, irq);
+
+	err = snd_dbri_create(card, op, irq, dev);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	dbri = card->private_data;
+	err = snd_dbri_pcm(card);
+	if (err < 0)
+		goto _err;
+
+	err = snd_dbri_mixer(card);
+	if (err < 0)
+		goto _err;
+
+	/* /proc file handling */
+	snd_dbri_proc(card);
+	dev_set_drvdata(&op->dev, card);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto _err;
+
+	printk(KERN_INFO "audio%d at %p (irq %d) is DBRI(%c)+CS4215(%d)\n",
+	       dev, dbri->regs,
+	       dbri->irq, op->dev.of_node->name[9], dbri->mm.version);
+	dev++;
+
+	return 0;
+
+_err:
+	snd_dbri_free(dbri);
+	snd_card_free(card);
+	return err;
+}
+
+static int __devexit dbri_remove(struct platform_device *op)
+{
+	struct snd_card *card = dev_get_drvdata(&op->dev);
+
+	snd_dbri_free(card->private_data);
+	snd_card_free(card);
+
+	dev_set_drvdata(&op->dev, NULL);
+
+	return 0;
+}
+
+static const struct of_device_id dbri_match[] = {
+	{
+		.name = "SUNW,DBRIe",
+	},
+	{
+		.name = "SUNW,DBRIf",
+	},
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, dbri_match);
+
+static struct of_platform_driver dbri_sbus_driver = {
+	.driver = {
+		.name = "dbri",
+		.owner = THIS_MODULE,
+		.of_match_table = dbri_match,
+	},
+	.probe		= dbri_probe,
+	.remove		= __devexit_p(dbri_remove),
+};
+
+/* Probe for the dbri chip and then attach the driver. */
+static int __init dbri_init(void)
+{
+	return of_register_platform_driver(&dbri_sbus_driver);
+}
+
+static void __exit dbri_exit(void)
+{
+	of_unregister_platform_driver(&dbri_sbus_driver);
+}
+
+module_init(dbri_init);
+module_exit(dbri_exit);
diff --git a/linux/sound/spi/Kconfig b/linux/sound/spi/Kconfig
new file mode 100644
index 0000000..e6485be
--- /dev/null
+++ b/linux/sound/spi/Kconfig
@@ -0,0 +1,38 @@
+#SPI drivers
+
+menuconfig SND_SPI
+	bool "SPI sound devices"
+	depends on SPI
+	default y
+	help
+	  Support for sound devices connected via the SPI bus.
+
+if SND_SPI
+
+config SND_AT73C213
+	tristate "Atmel AT73C213 DAC driver"
+	depends on ATMEL_SSC
+	select SND_PCM
+	help
+	  Say Y here if you want to use the Atmel AT73C213 external DAC. This
+	  DAC can be found on Atmel development boards.
+
+	  This driver requires the Atmel SSC driver for sound sink, a
+	  peripheral found on most AT91 and AVR32 microprocessors.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called snd-at73c213.
+
+config SND_AT73C213_TARGET_BITRATE
+	int "Target bitrate for AT73C213"
+	depends on SND_AT73C213
+	default "48000"
+	range 8000 50000
+	help
+	  Sets the target bitrate for the bitrate calculator in the driver.
+	  Limited by hardware to be between 8000 Hz and 50000 Hz.
+
+	  Set to 48000 Hz by default.
+
+endif	# SND_SPI
+
diff --git a/linux/sound/spi/Makefile b/linux/sound/spi/Makefile
new file mode 100644
index 0000000..026fb73
--- /dev/null
+++ b/linux/sound/spi/Makefile
@@ -0,0 +1,5 @@
+# Makefile for SPI drivers
+
+snd-at73c213-objs		:= at73c213.o
+
+obj-$(CONFIG_SND_AT73C213)	+= snd-at73c213.o
diff --git a/linux/sound/spi/at73c213.c b/linux/sound/spi/at73c213.c
new file mode 100644
index 0000000..337a002
--- /dev/null
+++ b/linux/sound/spi/at73c213.c
@@ -0,0 +1,1129 @@
+/*
+ * Driver for AT73C213 16-bit stereo DAC connected to Atmel SSC
+ *
+ * Copyright (C) 2006-2007 Atmel Norway
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+/*#define DEBUG*/
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+
+#include <sound/initval.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include <linux/atmel-ssc.h>
+
+#include <linux/spi/spi.h>
+#include <linux/spi/at73c213.h>
+
+#include "at73c213.h"
+
+#define BITRATE_MIN	 8000 /* Hardware limit? */
+#define BITRATE_TARGET	CONFIG_SND_AT73C213_TARGET_BITRATE
+#define BITRATE_MAX	50000 /* Hardware limit. */
+
+/* Initial (hardware reset) AT73C213 register values. */
+static u8 snd_at73c213_original_image[18] =
+{
+	0x00,	/* 00 - CTRL    */
+	0x05,	/* 01 - LLIG    */
+	0x05,	/* 02 - RLIG    */
+	0x08,	/* 03 - LPMG    */
+	0x08,	/* 04 - RPMG    */
+	0x00,	/* 05 - LLOG    */
+	0x00,	/* 06 - RLOG    */
+	0x22,	/* 07 - OLC     */
+	0x09,	/* 08 - MC      */
+	0x00,	/* 09 - CSFC    */
+	0x00,	/* 0A - MISC    */
+	0x00,	/* 0B -         */
+	0x00,	/* 0C - PRECH   */
+	0x05,	/* 0D - AUXG    */
+	0x00,	/* 0E -         */
+	0x00,	/* 0F -         */
+	0x00,	/* 10 - RST     */
+	0x00,	/* 11 - PA_CTRL */
+};
+
+struct snd_at73c213 {
+	struct snd_card			*card;
+	struct snd_pcm			*pcm;
+	struct snd_pcm_substream	*substream;
+	struct at73c213_board_info	*board;
+	int				irq;
+	int				period;
+	unsigned long			bitrate;
+	struct ssc_device		*ssc;
+	struct spi_device		*spi;
+	u8				spi_wbuffer[2];
+	u8				spi_rbuffer[2];
+	/* Image of the SPI registers in AT73C213. */
+	u8				reg_image[18];
+	/* Protect SSC registers against concurrent access. */
+	spinlock_t			lock;
+	/* Protect mixer registers against concurrent access. */
+	struct mutex			mixer_lock;
+};
+
+#define get_chip(card) ((struct snd_at73c213 *)card->private_data)
+
+static int
+snd_at73c213_write_reg(struct snd_at73c213 *chip, u8 reg, u8 val)
+{
+	struct spi_message msg;
+	struct spi_transfer msg_xfer = {
+		.len		= 2,
+		.cs_change	= 0,
+	};
+	int retval;
+
+	spi_message_init(&msg);
+
+	chip->spi_wbuffer[0] = reg;
+	chip->spi_wbuffer[1] = val;
+
+	msg_xfer.tx_buf = chip->spi_wbuffer;
+	msg_xfer.rx_buf = chip->spi_rbuffer;
+	spi_message_add_tail(&msg_xfer, &msg);
+
+	retval = spi_sync(chip->spi, &msg);
+
+	if (!retval)
+		chip->reg_image[reg] = val;
+
+	return retval;
+}
+
+static struct snd_pcm_hardware snd_at73c213_playback_hw = {
+	.info		= SNDRV_PCM_INFO_INTERLEAVED |
+			  SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.formats	= SNDRV_PCM_FMTBIT_S16_BE,
+	.rates		= SNDRV_PCM_RATE_CONTINUOUS,
+	.rate_min	= 8000,  /* Replaced by chip->bitrate later. */
+	.rate_max	= 50000, /* Replaced by chip->bitrate later. */
+	.channels_min	= 1,
+	.channels_max	= 2,
+	.buffer_bytes_max = 64 * 1024 - 1,
+	.period_bytes_min = 512,
+	.period_bytes_max = 64 * 1024 - 1,
+	.periods_min	= 4,
+	.periods_max	= 1024,
+};
+
+/*
+ * Calculate and set bitrate and divisions.
+ */
+static int snd_at73c213_set_bitrate(struct snd_at73c213 *chip)
+{
+	unsigned long ssc_rate = clk_get_rate(chip->ssc->clk);
+	unsigned long dac_rate_new, ssc_div;
+	int status;
+	unsigned long ssc_div_max, ssc_div_min;
+	int max_tries;
+
+	/*
+	 * We connect two clocks here, picking divisors so the I2S clocks
+	 * out data at the same rate the DAC clocks it in ... and as close
+	 * as practical to the desired target rate.
+	 *
+	 * The DAC master clock (MCLK) is programmable, and is either 256
+	 * or (not here) 384 times the I2S output clock (BCLK).
+	 */
+
+	/* SSC clock / (bitrate * stereo * 16-bit). */
+	ssc_div = ssc_rate / (BITRATE_TARGET * 2 * 16);
+	ssc_div_min = ssc_rate / (BITRATE_MAX * 2 * 16);
+	ssc_div_max = ssc_rate / (BITRATE_MIN * 2 * 16);
+	max_tries = (ssc_div_max - ssc_div_min) / 2;
+
+	if (max_tries < 1)
+		max_tries = 1;
+
+	/* ssc_div must be even. */
+	ssc_div = (ssc_div + 1) & ~1UL;
+
+	if ((ssc_rate / (ssc_div * 2 * 16)) < BITRATE_MIN) {
+		ssc_div -= 2;
+		if ((ssc_rate / (ssc_div * 2 * 16)) > BITRATE_MAX)
+			return -ENXIO;
+	}
+
+	/* Search for a possible bitrate. */
+	do {
+		/* SSC clock / (ssc divider * 16-bit * stereo). */
+		if ((ssc_rate / (ssc_div * 2 * 16)) < BITRATE_MIN)
+			return -ENXIO;
+
+		/* 256 / (2 * 16) = 8 */
+		dac_rate_new = 8 * (ssc_rate / ssc_div);
+
+		status = clk_round_rate(chip->board->dac_clk, dac_rate_new);
+		if (status < 0)
+			return status;
+
+		/* Ignore difference smaller than 256 Hz. */
+		if ((status/256) == (dac_rate_new/256))
+			goto set_rate;
+
+		ssc_div += 2;
+	} while (--max_tries);
+
+	/* Not able to find a valid bitrate. */
+	return -ENXIO;
+
+set_rate:
+	status = clk_set_rate(chip->board->dac_clk, status);
+	if (status < 0)
+		return status;
+
+	/* Set divider in SSC device. */
+	ssc_writel(chip->ssc->regs, CMR, ssc_div/2);
+
+	/* SSC clock / (ssc divider * 16-bit * stereo). */
+	chip->bitrate = ssc_rate / (ssc_div * 16 * 2);
+
+	dev_info(&chip->spi->dev,
+			"at73c213: supported bitrate is %lu (%lu divider)\n",
+			chip->bitrate, ssc_div);
+
+	return 0;
+}
+
+static int snd_at73c213_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_at73c213 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	/* ensure buffer_size is a multiple of period_size */
+	err = snd_pcm_hw_constraint_integer(runtime,
+					SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0)
+		return err;
+	snd_at73c213_playback_hw.rate_min = chip->bitrate;
+	snd_at73c213_playback_hw.rate_max = chip->bitrate;
+	runtime->hw = snd_at73c213_playback_hw;
+	chip->substream = substream;
+
+	return 0;
+}
+
+static int snd_at73c213_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_at73c213 *chip = snd_pcm_substream_chip(substream);
+	chip->substream = NULL;
+	return 0;
+}
+
+static int snd_at73c213_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_at73c213 *chip = snd_pcm_substream_chip(substream);
+	int channels = params_channels(hw_params);
+	int val;
+
+	val = ssc_readl(chip->ssc->regs, TFMR);
+	val = SSC_BFINS(TFMR_DATNB, channels - 1, val);
+	ssc_writel(chip->ssc->regs, TFMR, val);
+
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int snd_at73c213_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_at73c213_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_at73c213 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int block_size;
+
+	block_size = frames_to_bytes(runtime, runtime->period_size);
+
+	chip->period = 0;
+
+	ssc_writel(chip->ssc->regs, PDC_TPR,
+			(long)runtime->dma_addr);
+	ssc_writel(chip->ssc->regs, PDC_TCR,
+			runtime->period_size * runtime->channels);
+	ssc_writel(chip->ssc->regs, PDC_TNPR,
+			(long)runtime->dma_addr + block_size);
+	ssc_writel(chip->ssc->regs, PDC_TNCR,
+			runtime->period_size * runtime->channels);
+
+	return 0;
+}
+
+static int snd_at73c213_pcm_trigger(struct snd_pcm_substream *substream,
+				   int cmd)
+{
+	struct snd_at73c213 *chip = snd_pcm_substream_chip(substream);
+	int retval = 0;
+
+	spin_lock(&chip->lock);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		ssc_writel(chip->ssc->regs, IER, SSC_BIT(IER_ENDTX));
+		ssc_writel(chip->ssc->regs, PDC_PTCR, SSC_BIT(PDC_PTCR_TXTEN));
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		ssc_writel(chip->ssc->regs, PDC_PTCR, SSC_BIT(PDC_PTCR_TXTDIS));
+		ssc_writel(chip->ssc->regs, IDR, SSC_BIT(IDR_ENDTX));
+		break;
+	default:
+		dev_dbg(&chip->spi->dev, "spurious command %x\n", cmd);
+		retval = -EINVAL;
+		break;
+	}
+
+	spin_unlock(&chip->lock);
+
+	return retval;
+}
+
+static snd_pcm_uframes_t
+snd_at73c213_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_at73c213 *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	snd_pcm_uframes_t pos;
+	unsigned long bytes;
+
+	bytes = ssc_readl(chip->ssc->regs, PDC_TPR)
+		- (unsigned long)runtime->dma_addr;
+
+	pos = bytes_to_frames(runtime, bytes);
+	if (pos >= runtime->buffer_size)
+		pos -= runtime->buffer_size;
+
+	return pos;
+}
+
+static struct snd_pcm_ops at73c213_playback_ops = {
+	.open		= snd_at73c213_pcm_open,
+	.close		= snd_at73c213_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= snd_at73c213_pcm_hw_params,
+	.hw_free	= snd_at73c213_pcm_hw_free,
+	.prepare	= snd_at73c213_pcm_prepare,
+	.trigger	= snd_at73c213_pcm_trigger,
+	.pointer	= snd_at73c213_pcm_pointer,
+};
+
+static int __devinit snd_at73c213_pcm_new(struct snd_at73c213 *chip, int device)
+{
+	struct snd_pcm *pcm;
+	int retval;
+
+	retval = snd_pcm_new(chip->card, chip->card->shortname,
+			device, 1, 0, &pcm);
+	if (retval < 0)
+		goto out;
+
+	pcm->private_data = chip;
+	pcm->info_flags = SNDRV_PCM_INFO_BLOCK_TRANSFER;
+	strcpy(pcm->name, "at73c213");
+	chip->pcm = pcm;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &at73c213_playback_ops);
+
+	retval = snd_pcm_lib_preallocate_pages_for_all(chip->pcm,
+			SNDRV_DMA_TYPE_DEV, &chip->ssc->pdev->dev,
+			64 * 1024, 64 * 1024);
+out:
+	return retval;
+}
+
+static irqreturn_t snd_at73c213_interrupt(int irq, void *dev_id)
+{
+	struct snd_at73c213 *chip = dev_id;
+	struct snd_pcm_runtime *runtime = chip->substream->runtime;
+	u32 status;
+	int offset;
+	int block_size;
+	int next_period;
+	int retval = IRQ_NONE;
+
+	spin_lock(&chip->lock);
+
+	block_size = frames_to_bytes(runtime, runtime->period_size);
+	status = ssc_readl(chip->ssc->regs, IMR);
+
+	if (status & SSC_BIT(IMR_ENDTX)) {
+		chip->period++;
+		if (chip->period == runtime->periods)
+			chip->period = 0;
+		next_period = chip->period + 1;
+		if (next_period == runtime->periods)
+			next_period = 0;
+
+		offset = block_size * next_period;
+
+		ssc_writel(chip->ssc->regs, PDC_TNPR,
+				(long)runtime->dma_addr + offset);
+		ssc_writel(chip->ssc->regs, PDC_TNCR,
+				runtime->period_size * runtime->channels);
+		retval = IRQ_HANDLED;
+	}
+
+	ssc_readl(chip->ssc->regs, IMR);
+	spin_unlock(&chip->lock);
+
+	if (status & SSC_BIT(IMR_ENDTX))
+		snd_pcm_period_elapsed(chip->substream);
+
+	return retval;
+}
+
+/*
+ * Mixer functions.
+ */
+static int snd_at73c213_mono_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+
+	mutex_lock(&chip->mixer_lock);
+
+	ucontrol->value.integer.value[0] =
+		(chip->reg_image[reg] >> shift) & mask;
+
+	if (invert)
+		ucontrol->value.integer.value[0] =
+			mask - ucontrol->value.integer.value[0];
+
+	mutex_unlock(&chip->mixer_lock);
+
+	return 0;
+}
+
+static int snd_at73c213_mono_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change, retval;
+	unsigned short val;
+
+	val = (ucontrol->value.integer.value[0] & mask);
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+
+	mutex_lock(&chip->mixer_lock);
+
+	val = (chip->reg_image[reg] & ~(mask << shift)) | val;
+	change = val != chip->reg_image[reg];
+	retval = snd_at73c213_write_reg(chip, reg, val);
+
+	mutex_unlock(&chip->mixer_lock);
+
+	if (retval)
+		return retval;
+
+	return change;
+}
+
+static int snd_at73c213_stereo_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+
+	if (mask == 1)
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	else
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = mask;
+
+	return 0;
+}
+
+static int snd_at73c213_stereo_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+
+	mutex_lock(&chip->mixer_lock);
+
+	ucontrol->value.integer.value[0] =
+		(chip->reg_image[left_reg] >> shift_left) & mask;
+	ucontrol->value.integer.value[1] =
+		(chip->reg_image[right_reg] >> shift_right) & mask;
+
+	if (invert) {
+		ucontrol->value.integer.value[0] =
+			mask - ucontrol->value.integer.value[0];
+		ucontrol->value.integer.value[1] =
+			mask - ucontrol->value.integer.value[1];
+	}
+
+	mutex_unlock(&chip->mixer_lock);
+
+	return 0;
+}
+
+static int snd_at73c213_stereo_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
+	int left_reg = kcontrol->private_value & 0xff;
+	int right_reg = (kcontrol->private_value >> 8) & 0xff;
+	int shift_left = (kcontrol->private_value >> 16) & 0x07;
+	int shift_right = (kcontrol->private_value >> 19) & 0x07;
+	int mask = (kcontrol->private_value >> 24) & 0xff;
+	int invert = (kcontrol->private_value >> 22) & 1;
+	int change, retval;
+	unsigned short val1, val2;
+
+	val1 = ucontrol->value.integer.value[0] & mask;
+	val2 = ucontrol->value.integer.value[1] & mask;
+	if (invert) {
+		val1 = mask - val1;
+		val2 = mask - val2;
+	}
+	val1 <<= shift_left;
+	val2 <<= shift_right;
+
+	mutex_lock(&chip->mixer_lock);
+
+	val1 = (chip->reg_image[left_reg] & ~(mask << shift_left)) | val1;
+	val2 = (chip->reg_image[right_reg] & ~(mask << shift_right)) | val2;
+	change = val1 != chip->reg_image[left_reg]
+		|| val2 != chip->reg_image[right_reg];
+	retval = snd_at73c213_write_reg(chip, left_reg, val1);
+	if (retval) {
+		mutex_unlock(&chip->mixer_lock);
+		goto out;
+	}
+	retval = snd_at73c213_write_reg(chip, right_reg, val2);
+	if (retval) {
+		mutex_unlock(&chip->mixer_lock);
+		goto out;
+	}
+
+	mutex_unlock(&chip->mixer_lock);
+
+	return change;
+
+out:
+	return retval;
+}
+
+#define snd_at73c213_mono_switch_info	snd_ctl_boolean_mono_info
+
+static int snd_at73c213_mono_switch_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+
+	mutex_lock(&chip->mixer_lock);
+
+	ucontrol->value.integer.value[0] =
+		(chip->reg_image[reg] >> shift) & 0x01;
+
+	if (invert)
+		ucontrol->value.integer.value[0] =
+			0x01 - ucontrol->value.integer.value[0];
+
+	mutex_unlock(&chip->mixer_lock);
+
+	return 0;
+}
+
+static int snd_at73c213_mono_switch_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol);
+	int reg = kcontrol->private_value & 0xff;
+	int shift = (kcontrol->private_value >> 8) & 0xff;
+	int mask = (kcontrol->private_value >> 16) & 0xff;
+	int invert = (kcontrol->private_value >> 24) & 0xff;
+	int change, retval;
+	unsigned short val;
+
+	if (ucontrol->value.integer.value[0])
+		val = mask;
+	else
+		val = 0;
+
+	if (invert)
+		val = mask - val;
+	val <<= shift;
+
+	mutex_lock(&chip->mixer_lock);
+
+	val |= (chip->reg_image[reg] & ~(mask << shift));
+	change = val != chip->reg_image[reg];
+
+	retval = snd_at73c213_write_reg(chip, reg, val);
+
+	mutex_unlock(&chip->mixer_lock);
+
+	if (retval)
+		return retval;
+
+	return change;
+}
+
+static int snd_at73c213_pa_volume_info(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = ((kcontrol->private_value >> 16) & 0xff) - 1;
+
+	return 0;
+}
+
+static int snd_at73c213_line_capture_volume_info(
+		struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	/* When inverted will give values 0x10001 => 0. */
+	uinfo->value.integer.min = 14;
+	uinfo->value.integer.max = 31;
+
+	return 0;
+}
+
+static int snd_at73c213_aux_capture_volume_info(
+		struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	/* When inverted will give values 0x10001 => 0. */
+	uinfo->value.integer.min = 14;
+	uinfo->value.integer.max = 31;
+
+	return 0;
+}
+
+#define AT73C213_MONO_SWITCH(xname, xindex, reg, shift, mask, invert)	\
+{									\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,				\
+	.name = xname,							\
+	.index = xindex,						\
+	.info = snd_at73c213_mono_switch_info,				\
+	.get = snd_at73c213_mono_switch_get,				\
+	.put = snd_at73c213_mono_switch_put,				\
+	.private_value = (reg | (shift << 8) | (mask << 16) | (invert << 24)) \
+}
+
+#define AT73C213_STEREO(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
+{									\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,				\
+	.name = xname,							\
+	.index = xindex,						\
+	.info = snd_at73c213_stereo_info,				\
+	.get = snd_at73c213_stereo_get,					\
+	.put = snd_at73c213_stereo_put,					\
+	.private_value = (left_reg | (right_reg << 8)			\
+			| (shift_left << 16) | (shift_right << 19)	\
+			| (mask << 24) | (invert << 22))		\
+}
+
+static struct snd_kcontrol_new snd_at73c213_controls[] __devinitdata = {
+AT73C213_STEREO("Master Playback Volume", 0, DAC_LMPG, DAC_RMPG, 0, 0, 0x1f, 1),
+AT73C213_STEREO("Master Playback Switch", 0, DAC_LMPG, DAC_RMPG, 5, 5, 1, 1),
+AT73C213_STEREO("PCM Playback Volume", 0, DAC_LLOG, DAC_RLOG, 0, 0, 0x1f, 1),
+AT73C213_STEREO("PCM Playback Switch", 0, DAC_LLOG, DAC_RLOG, 5, 5, 1, 1),
+AT73C213_MONO_SWITCH("Mono PA Playback Switch", 0, DAC_CTRL, DAC_CTRL_ONPADRV,
+		     0x01, 0),
+{
+	.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name	= "PA Playback Volume",
+	.index	= 0,
+	.info	= snd_at73c213_pa_volume_info,
+	.get	= snd_at73c213_mono_get,
+	.put	= snd_at73c213_mono_put,
+	.private_value	= PA_CTRL | (PA_CTRL_APAGAIN << 8) | \
+		(0x0f << 16) | (1 << 24),
+},
+AT73C213_MONO_SWITCH("PA High Gain Playback Switch", 0, PA_CTRL, PA_CTRL_APALP,
+		     0x01, 1),
+AT73C213_MONO_SWITCH("PA Playback Switch", 0, PA_CTRL, PA_CTRL_APAON, 0x01, 0),
+{
+	.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name	= "Aux Capture Volume",
+	.index	= 0,
+	.info	= snd_at73c213_aux_capture_volume_info,
+	.get	= snd_at73c213_mono_get,
+	.put	= snd_at73c213_mono_put,
+	.private_value	= DAC_AUXG | (0 << 8) | (0x1f << 16) | (1 << 24),
+},
+AT73C213_MONO_SWITCH("Aux Capture Switch", 0, DAC_CTRL, DAC_CTRL_ONAUXIN,
+		     0x01, 0),
+{
+	.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name	= "Line Capture Volume",
+	.index	= 0,
+	.info	= snd_at73c213_line_capture_volume_info,
+	.get	= snd_at73c213_stereo_get,
+	.put	= snd_at73c213_stereo_put,
+	.private_value	= DAC_LLIG | (DAC_RLIG << 8) | (0 << 16) | (0 << 19)
+		| (0x1f << 24) | (1 << 22),
+},
+AT73C213_MONO_SWITCH("Line Capture Switch", 0, DAC_CTRL, 0, 0x03, 0),
+};
+
+static int __devinit snd_at73c213_mixer(struct snd_at73c213 *chip)
+{
+	struct snd_card *card;
+	int errval, idx;
+
+	if (chip == NULL || chip->pcm == NULL)
+		return -EINVAL;
+
+	card = chip->card;
+
+	strcpy(card->mixername, chip->pcm->name);
+
+	for (idx = 0; idx < ARRAY_SIZE(snd_at73c213_controls); idx++) {
+		errval = snd_ctl_add(card,
+				snd_ctl_new1(&snd_at73c213_controls[idx],
+					chip));
+		if (errval < 0)
+			goto cleanup;
+	}
+
+	return 0;
+
+cleanup:
+	for (idx = 1; idx < ARRAY_SIZE(snd_at73c213_controls) + 1; idx++) {
+		struct snd_kcontrol *kctl;
+		kctl = snd_ctl_find_numid(card, idx);
+		if (kctl)
+			snd_ctl_remove(card, kctl);
+	}
+	return errval;
+}
+
+/*
+ * Device functions
+ */
+static int __devinit snd_at73c213_ssc_init(struct snd_at73c213 *chip)
+{
+	/*
+	 * Continuous clock output.
+	 * Starts on falling TF.
+	 * Delay 1 cycle (1 bit).
+	 * Periode is 16 bit (16 - 1).
+	 */
+	ssc_writel(chip->ssc->regs, TCMR,
+			SSC_BF(TCMR_CKO, 1)
+			| SSC_BF(TCMR_START, 4)
+			| SSC_BF(TCMR_STTDLY, 1)
+			| SSC_BF(TCMR_PERIOD, 16 - 1));
+	/*
+	 * Data length is 16 bit (16 - 1).
+	 * Transmit MSB first.
+	 * Transmit 2 words each transfer.
+	 * Frame sync length is 16 bit (16 - 1).
+	 * Frame starts on negative pulse.
+	 */
+	ssc_writel(chip->ssc->regs, TFMR,
+			SSC_BF(TFMR_DATLEN, 16 - 1)
+			| SSC_BIT(TFMR_MSBF)
+			| SSC_BF(TFMR_DATNB, 1)
+			| SSC_BF(TFMR_FSLEN, 16 - 1)
+			| SSC_BF(TFMR_FSOS, 1));
+
+	return 0;
+}
+
+static int __devinit snd_at73c213_chip_init(struct snd_at73c213 *chip)
+{
+	int retval;
+	unsigned char dac_ctrl = 0;
+
+	retval = snd_at73c213_set_bitrate(chip);
+	if (retval)
+		goto out;
+
+	/* Enable DAC master clock. */
+	clk_enable(chip->board->dac_clk);
+
+	/* Initialize at73c213 on SPI bus. */
+	retval = snd_at73c213_write_reg(chip, DAC_RST, 0x04);
+	if (retval)
+		goto out_clk;
+	msleep(1);
+	retval = snd_at73c213_write_reg(chip, DAC_RST, 0x03);
+	if (retval)
+		goto out_clk;
+
+	/* Precharge everything. */
+	retval = snd_at73c213_write_reg(chip, DAC_PRECH, 0xff);
+	if (retval)
+		goto out_clk;
+	retval = snd_at73c213_write_reg(chip, PA_CTRL, (1<<PA_CTRL_APAPRECH));
+	if (retval)
+		goto out_clk;
+	retval = snd_at73c213_write_reg(chip, DAC_CTRL,
+			(1<<DAC_CTRL_ONLNOL) | (1<<DAC_CTRL_ONLNOR));
+	if (retval)
+		goto out_clk;
+
+	msleep(50);
+
+	/* Stop precharging PA. */
+	retval = snd_at73c213_write_reg(chip, PA_CTRL,
+			(1<<PA_CTRL_APALP) | 0x0f);
+	if (retval)
+		goto out_clk;
+
+	msleep(450);
+
+	/* Stop precharging DAC, turn on master power. */
+	retval = snd_at73c213_write_reg(chip, DAC_PRECH, (1<<DAC_PRECH_ONMSTR));
+	if (retval)
+		goto out_clk;
+
+	msleep(1);
+
+	/* Turn on DAC. */
+	dac_ctrl = (1<<DAC_CTRL_ONDACL) | (1<<DAC_CTRL_ONDACR)
+		| (1<<DAC_CTRL_ONLNOL) | (1<<DAC_CTRL_ONLNOR);
+
+	retval = snd_at73c213_write_reg(chip, DAC_CTRL, dac_ctrl);
+	if (retval)
+		goto out_clk;
+
+	/* Mute sound. */
+	retval = snd_at73c213_write_reg(chip, DAC_LMPG, 0x3f);
+	if (retval)
+		goto out_clk;
+	retval = snd_at73c213_write_reg(chip, DAC_RMPG, 0x3f);
+	if (retval)
+		goto out_clk;
+	retval = snd_at73c213_write_reg(chip, DAC_LLOG, 0x3f);
+	if (retval)
+		goto out_clk;
+	retval = snd_at73c213_write_reg(chip, DAC_RLOG, 0x3f);
+	if (retval)
+		goto out_clk;
+	retval = snd_at73c213_write_reg(chip, DAC_LLIG, 0x11);
+	if (retval)
+		goto out_clk;
+	retval = snd_at73c213_write_reg(chip, DAC_RLIG, 0x11);
+	if (retval)
+		goto out_clk;
+	retval = snd_at73c213_write_reg(chip, DAC_AUXG, 0x11);
+	if (retval)
+		goto out_clk;
+
+	/* Enable I2S device, i.e. clock output. */
+	ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXEN));
+
+	goto out;
+
+out_clk:
+	clk_disable(chip->board->dac_clk);
+out:
+	return retval;
+}
+
+static int snd_at73c213_dev_free(struct snd_device *device)
+{
+	struct snd_at73c213 *chip = device->device_data;
+
+	ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXDIS));
+	if (chip->irq >= 0) {
+		free_irq(chip->irq, chip);
+		chip->irq = -1;
+	}
+
+	return 0;
+}
+
+static int __devinit snd_at73c213_dev_init(struct snd_card *card,
+					 struct spi_device *spi)
+{
+	static struct snd_device_ops ops = {
+		.dev_free	= snd_at73c213_dev_free,
+	};
+	struct snd_at73c213 *chip = get_chip(card);
+	int irq, retval;
+
+	irq = chip->ssc->irq;
+	if (irq < 0)
+		return irq;
+
+	spin_lock_init(&chip->lock);
+	mutex_init(&chip->mixer_lock);
+	chip->card = card;
+	chip->irq = -1;
+
+	retval = request_irq(irq, snd_at73c213_interrupt, 0, "at73c213", chip);
+	if (retval) {
+		dev_dbg(&chip->spi->dev, "unable to request irq %d\n", irq);
+		goto out;
+	}
+	chip->irq = irq;
+
+	memcpy(&chip->reg_image, &snd_at73c213_original_image,
+			sizeof(snd_at73c213_original_image));
+
+	retval = snd_at73c213_ssc_init(chip);
+	if (retval)
+		goto out_irq;
+
+	retval = snd_at73c213_chip_init(chip);
+	if (retval)
+		goto out_irq;
+
+	retval = snd_at73c213_pcm_new(chip, 0);
+	if (retval)
+		goto out_irq;
+
+	retval = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (retval)
+		goto out_irq;
+
+	retval = snd_at73c213_mixer(chip);
+	if (retval)
+		goto out_snd_dev;
+
+	snd_card_set_dev(card, &spi->dev);
+
+	goto out;
+
+out_snd_dev:
+	snd_device_free(card, chip);
+out_irq:
+	free_irq(chip->irq, chip);
+	chip->irq = -1;
+out:
+	return retval;
+}
+
+static int __devinit snd_at73c213_probe(struct spi_device *spi)
+{
+	struct snd_card			*card;
+	struct snd_at73c213		*chip;
+	struct at73c213_board_info	*board;
+	int				retval;
+	char				id[16];
+
+	board = spi->dev.platform_data;
+	if (!board) {
+		dev_dbg(&spi->dev, "no platform_data\n");
+		return -ENXIO;
+	}
+
+	if (!board->dac_clk) {
+		dev_dbg(&spi->dev, "no DAC clk\n");
+		return -ENXIO;
+	}
+
+	if (IS_ERR(board->dac_clk)) {
+		dev_dbg(&spi->dev, "no DAC clk\n");
+		return PTR_ERR(board->dac_clk);
+	}
+
+	/* Allocate "card" using some unused identifiers. */
+	snprintf(id, sizeof id, "at73c213_%d", board->ssc_id);
+	retval = snd_card_create(-1, id, THIS_MODULE,
+				 sizeof(struct snd_at73c213), &card);
+	if (retval < 0)
+		goto out;
+
+	chip = card->private_data;
+	chip->spi = spi;
+	chip->board = board;
+
+	chip->ssc = ssc_request(board->ssc_id);
+	if (IS_ERR(chip->ssc)) {
+		dev_dbg(&spi->dev, "could not get ssc%d device\n",
+				board->ssc_id);
+		retval = PTR_ERR(chip->ssc);
+		goto out_card;
+	}
+
+	retval = snd_at73c213_dev_init(card, spi);
+	if (retval)
+		goto out_ssc;
+
+	strcpy(card->driver, "at73c213");
+	strcpy(card->shortname, board->shortname);
+	sprintf(card->longname, "%s on irq %d", card->shortname, chip->irq);
+
+	retval = snd_card_register(card);
+	if (retval)
+		goto out_ssc;
+
+	dev_set_drvdata(&spi->dev, card);
+
+	goto out;
+
+out_ssc:
+	ssc_free(chip->ssc);
+out_card:
+	snd_card_free(card);
+out:
+	return retval;
+}
+
+static int __devexit snd_at73c213_remove(struct spi_device *spi)
+{
+	struct snd_card *card = dev_get_drvdata(&spi->dev);
+	struct snd_at73c213 *chip = card->private_data;
+	int retval;
+
+	/* Stop playback. */
+	ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXDIS));
+
+	/* Mute sound. */
+	retval = snd_at73c213_write_reg(chip, DAC_LMPG, 0x3f);
+	if (retval)
+		goto out;
+	retval = snd_at73c213_write_reg(chip, DAC_RMPG, 0x3f);
+	if (retval)
+		goto out;
+	retval = snd_at73c213_write_reg(chip, DAC_LLOG, 0x3f);
+	if (retval)
+		goto out;
+	retval = snd_at73c213_write_reg(chip, DAC_RLOG, 0x3f);
+	if (retval)
+		goto out;
+	retval = snd_at73c213_write_reg(chip, DAC_LLIG, 0x11);
+	if (retval)
+		goto out;
+	retval = snd_at73c213_write_reg(chip, DAC_RLIG, 0x11);
+	if (retval)
+		goto out;
+	retval = snd_at73c213_write_reg(chip, DAC_AUXG, 0x11);
+	if (retval)
+		goto out;
+
+	/* Turn off PA. */
+	retval = snd_at73c213_write_reg(chip, PA_CTRL,
+					chip->reg_image[PA_CTRL] | 0x0f);
+	if (retval)
+		goto out;
+	msleep(10);
+	retval = snd_at73c213_write_reg(chip, PA_CTRL,
+					(1 << PA_CTRL_APALP) | 0x0f);
+	if (retval)
+		goto out;
+
+	/* Turn off external DAC. */
+	retval = snd_at73c213_write_reg(chip, DAC_CTRL, 0x0c);
+	if (retval)
+		goto out;
+	msleep(2);
+	retval = snd_at73c213_write_reg(chip, DAC_CTRL, 0x00);
+	if (retval)
+		goto out;
+
+	/* Turn off master power. */
+	retval = snd_at73c213_write_reg(chip, DAC_PRECH, 0x00);
+	if (retval)
+		goto out;
+
+out:
+	/* Stop DAC master clock. */
+	clk_disable(chip->board->dac_clk);
+
+	ssc_free(chip->ssc);
+	snd_card_free(card);
+	dev_set_drvdata(&spi->dev, NULL);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_at73c213_suspend(struct spi_device *spi, pm_message_t msg)
+{
+	struct snd_card *card = dev_get_drvdata(&spi->dev);
+	struct snd_at73c213 *chip = card->private_data;
+
+	ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXDIS));
+	clk_disable(chip->board->dac_clk);
+
+	return 0;
+}
+
+static int snd_at73c213_resume(struct spi_device *spi)
+{
+	struct snd_card *card = dev_get_drvdata(&spi->dev);
+	struct snd_at73c213 *chip = card->private_data;
+
+	clk_enable(chip->board->dac_clk);
+	ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXEN));
+
+	return 0;
+}
+#else
+#define snd_at73c213_suspend NULL
+#define snd_at73c213_resume NULL
+#endif
+
+static struct spi_driver at73c213_driver = {
+	.driver		= {
+		.name	= "at73c213",
+	},
+	.probe		= snd_at73c213_probe,
+	.suspend	= snd_at73c213_suspend,
+	.resume		= snd_at73c213_resume,
+	.remove		= __devexit_p(snd_at73c213_remove),
+};
+
+static int __init at73c213_init(void)
+{
+	return spi_register_driver(&at73c213_driver);
+}
+module_init(at73c213_init);
+
+static void __exit at73c213_exit(void)
+{
+	spi_unregister_driver(&at73c213_driver);
+}
+module_exit(at73c213_exit);
+
+MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>");
+MODULE_DESCRIPTION("Sound driver for AT73C213 with Atmel SSC");
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/spi/at73c213.h b/linux/sound/spi/at73c213.h
new file mode 100644
index 0000000..fd8b372
--- /dev/null
+++ b/linux/sound/spi/at73c213.h
@@ -0,0 +1,119 @@
+/*
+ * Driver for the AT73C213 16-bit stereo DAC on Atmel ATSTK1000
+ *
+ * Copyright (C) 2006 - 2007 Atmel Corporation
+ *
+ * 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.
+ *
+ * The full GNU General Public License is included in this
+ * distribution in the file called COPYING.
+ */
+
+#ifndef _SND_AT73C213_H
+#define _SND_AT73C213_H
+
+/* DAC control register */
+#define DAC_CTRL		0x00
+#define DAC_CTRL_ONPADRV	7
+#define DAC_CTRL_ONAUXIN	6
+#define DAC_CTRL_ONDACR		5
+#define DAC_CTRL_ONDACL		4
+#define DAC_CTRL_ONLNOR		3
+#define DAC_CTRL_ONLNOL		2
+#define DAC_CTRL_ONLNIR		1
+#define DAC_CTRL_ONLNIL		0
+
+/* DAC left line in gain register */
+#define DAC_LLIG		0x01
+#define DAC_LLIG_LLIG		0
+
+/* DAC right line in gain register */
+#define DAC_RLIG		0x02
+#define DAC_RLIG_RLIG		0
+
+/* DAC Left Master Playback Gain Register */
+#define DAC_LMPG		0x03
+#define DAC_LMPG_LMPG		0
+
+/* DAC Right Master Playback Gain Register */
+#define DAC_RMPG		0x04
+#define DAC_RMPG_RMPG		0
+
+/* DAC Left Line Out Gain Register */
+#define DAC_LLOG		0x05
+#define DAC_LLOG_LLOG		0
+
+/* DAC Right Line Out Gain Register */
+#define DAC_RLOG		0x06
+#define DAC_RLOG_RLOG		0
+
+/* DAC Output Level Control Register */
+#define DAC_OLC			0x07
+#define DAC_OLC_RSHORT		7
+#define DAC_OLC_ROLC		4
+#define DAC_OLC_LSHORT		3
+#define DAC_OLC_LOLC		0
+
+/* DAC Mixer Control Register */
+#define DAC_MC			0x08
+#define DAC_MC_INVR		5
+#define DAC_MC_INVL		4
+#define DAC_MC_RMSMIN2		3
+#define DAC_MC_RMSMIN1		2
+#define DAC_MC_LMSMIN2		1
+#define DAC_MC_LMSMIN1		0
+
+/* DAC Clock and Sampling Frequency Control Register */
+#define DAC_CSFC		0x09
+#define DAC_CSFC_OVRSEL		4
+
+/* DAC Miscellaneous Register */
+#define DAC_MISC		0x0A
+#define DAC_MISC_VCMCAPSEL	7
+#define DAC_MISC_DINTSEL	4
+#define DAC_MISC_DITHEN		3
+#define DAC_MISC_DEEMPEN	2
+#define DAC_MISC_NBITS		0
+
+/* DAC Precharge Control Register */
+#define DAC_PRECH		0x0C
+#define DAC_PRECH_PRCHGPDRV	7
+#define DAC_PRECH_PRCHGAUX1	6
+#define DAC_PRECH_PRCHGLNOR	5
+#define DAC_PRECH_PRCHGLNOL	4
+#define DAC_PRECH_PRCHGLNIR	3
+#define DAC_PRECH_PRCHGLNIL	2
+#define DAC_PRECH_PRCHG		1
+#define DAC_PRECH_ONMSTR	0
+
+/* DAC Auxiliary Input Gain Control Register */
+#define DAC_AUXG		0x0D
+#define DAC_AUXG_AUXG		0
+
+/* DAC Reset Register */
+#define DAC_RST			0x10
+#define DAC_RST_RESMASK		2
+#define DAC_RST_RESFILZ		1
+#define DAC_RST_RSTZ		0
+
+/* Power Amplifier Control Register */
+#define PA_CTRL			0x11
+#define PA_CTRL_APAON		6
+#define PA_CTRL_APAPRECH	5
+#define PA_CTRL_APALP		4
+#define PA_CTRL_APAGAIN		0
+
+#endif /* _SND_AT73C213_H */
diff --git a/linux/sound/synth/Makefile b/linux/sound/synth/Makefile
new file mode 100644
index 0000000..11eb06a
--- /dev/null
+++ b/linux/sound/synth/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-util-mem-objs := util_mem.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_EMU10K1) += snd-util-mem.o
+obj-$(CONFIG_SND_TRIDENT) += snd-util-mem.o
+obj-$(CONFIG_SND_SBAWE_SEQ) += snd-util-mem.o
+obj-$(CONFIG_SND_SEQUENCER) += emux/
diff --git a/linux/sound/synth/emux/Makefile b/linux/sound/synth/emux/Makefile
new file mode 100644
index 0000000..328594e
--- /dev/null
+++ b/linux/sound/synth/emux/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-emux-synth-objs := emux.o emux_synth.o emux_seq.o emux_nrpn.o \
+		       emux_effect.o emux_proc.o emux_hwdep.o soundfont.o \
+		       $(if $(CONFIG_SND_SEQUENCER_OSS),emux_oss.o)
+
+# Toplevel Module Dependencies
+obj-$(CONFIG_SND_SBAWE_SEQ) += snd-emux-synth.o
+obj-$(CONFIG_SND_EMU10K1_SEQ) += snd-emux-synth.o
diff --git a/linux/sound/synth/emux/emux.c b/linux/sound/synth/emux/emux.c
new file mode 100644
index 0000000..f16a3fc
--- /dev/null
+++ b/linux/sound/synth/emux/emux.c
@@ -0,0 +1,191 @@
+/*
+ *  Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Routines for control of EMU WaveTable chip
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/emux_synth.h>
+#include <linux/init.h>
+#include "emux_voice.h"
+
+MODULE_AUTHOR("Takashi Iwai");
+MODULE_DESCRIPTION("Routines for control of EMU WaveTable chip");
+MODULE_LICENSE("GPL");
+
+/*
+ * create a new hardware dependent device for Emu8000/Emu10k1
+ */
+int snd_emux_new(struct snd_emux **remu)
+{
+	struct snd_emux *emu;
+
+	*remu = NULL;
+	emu = kzalloc(sizeof(*emu), GFP_KERNEL);
+	if (emu == NULL)
+		return -ENOMEM;
+
+	spin_lock_init(&emu->voice_lock);
+	mutex_init(&emu->register_mutex);
+
+	emu->client = -1;
+#ifdef CONFIG_SND_SEQUENCER_OSS
+	emu->oss_synth = NULL;
+#endif
+	emu->max_voices = 0;
+	emu->use_time = 0;
+
+	init_timer(&emu->tlist);
+	emu->tlist.function = snd_emux_timer_callback;
+	emu->tlist.data = (unsigned long)emu;
+	emu->timer_active = 0;
+
+	*remu = emu;
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_emux_new);
+
+/*
+ */
+static int sf_sample_new(void *private_data, struct snd_sf_sample *sp,
+				  struct snd_util_memhdr *hdr,
+				  const void __user *buf, long count)
+{
+	struct snd_emux *emu = private_data;
+	return emu->ops.sample_new(emu, sp, hdr, buf, count);
+	
+}
+
+static int sf_sample_free(void *private_data, struct snd_sf_sample *sp,
+				   struct snd_util_memhdr *hdr)
+{
+	struct snd_emux *emu = private_data;
+	return emu->ops.sample_free(emu, sp, hdr);
+	
+}
+
+static void sf_sample_reset(void *private_data)
+{
+	struct snd_emux *emu = private_data;
+	emu->ops.sample_reset(emu);
+}
+
+int snd_emux_register(struct snd_emux *emu, struct snd_card *card, int index, char *name)
+{
+	int err;
+	struct snd_sf_callback sf_cb;
+
+	if (snd_BUG_ON(!emu->hw || emu->max_voices <= 0))
+		return -EINVAL;
+	if (snd_BUG_ON(!card || !name))
+		return -EINVAL;
+
+	emu->card = card;
+	emu->name = kstrdup(name, GFP_KERNEL);
+	emu->voices = kcalloc(emu->max_voices, sizeof(struct snd_emux_voice),
+			      GFP_KERNEL);
+	if (emu->voices == NULL)
+		return -ENOMEM;
+
+	/* create soundfont list */
+	memset(&sf_cb, 0, sizeof(sf_cb));
+	sf_cb.private_data = emu;
+	if (emu->ops.sample_new)
+		sf_cb.sample_new = sf_sample_new;
+	if (emu->ops.sample_free)
+		sf_cb.sample_free = sf_sample_free;
+	if (emu->ops.sample_reset)
+		sf_cb.sample_reset = sf_sample_reset;
+	emu->sflist = snd_sf_new(&sf_cb, emu->memhdr);
+	if (emu->sflist == NULL)
+		return -ENOMEM;
+
+	if ((err = snd_emux_init_hwdep(emu)) < 0)
+		return err;
+
+	snd_emux_init_voices(emu);
+
+	snd_emux_init_seq(emu, card, index);
+#ifdef CONFIG_SND_SEQUENCER_OSS
+	snd_emux_init_seq_oss(emu);
+#endif
+	snd_emux_init_virmidi(emu, card);
+
+#ifdef CONFIG_PROC_FS
+	snd_emux_proc_init(emu, card, index);
+#endif
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_emux_register);
+
+/*
+ */
+int snd_emux_free(struct snd_emux *emu)
+{
+	unsigned long flags;
+
+	if (! emu)
+		return -EINVAL;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	if (emu->timer_active)
+		del_timer(&emu->tlist);
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+
+#ifdef CONFIG_PROC_FS
+	snd_emux_proc_free(emu);
+#endif
+	snd_emux_delete_virmidi(emu);
+#ifdef CONFIG_SND_SEQUENCER_OSS
+	snd_emux_detach_seq_oss(emu);
+#endif
+	snd_emux_detach_seq(emu);
+
+	snd_emux_delete_hwdep(emu);
+
+	if (emu->sflist)
+		snd_sf_free(emu->sflist);
+
+	kfree(emu->voices);
+	kfree(emu->name);
+	kfree(emu);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_emux_free);
+
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_emux_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_emux_exit(void)
+{
+}
+
+module_init(alsa_emux_init)
+module_exit(alsa_emux_exit)
diff --git a/linux/sound/synth/emux/emux_effect.c b/linux/sound/synth/emux/emux_effect.c
new file mode 100644
index 0000000..a447218
--- /dev/null
+++ b/linux/sound/synth/emux/emux_effect.c
@@ -0,0 +1,310 @@
+/*
+ *  Midi synth routines for the Emu8k/Emu10k1
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Contains code 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
+ *
+ */
+
+#include "emux_voice.h"
+#include <linux/slab.h>
+
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+/*
+ * effects table
+ */
+
+#define xoffsetof(type,tag)	((long)(&((type)NULL)->tag) - (long)(NULL))
+
+#define parm_offset(tag)	xoffsetof(struct soundfont_voice_parm *, tag)
+
+#define PARM_IS_BYTE		(1 << 0)
+#define PARM_IS_WORD		(1 << 1)
+#define PARM_IS_ALIGNED		(3 << 2)
+#define PARM_IS_ALIGN_HI	(1 << 2)
+#define PARM_IS_ALIGN_LO	(2 << 2)
+#define PARM_IS_SIGNED		(1 << 4)
+
+#define PARM_WORD	(PARM_IS_WORD)
+#define PARM_BYTE_LO	(PARM_IS_BYTE|PARM_IS_ALIGN_LO)
+#define PARM_BYTE_HI	(PARM_IS_BYTE|PARM_IS_ALIGN_HI)
+#define PARM_BYTE	(PARM_IS_BYTE)
+#define PARM_SIGN_LO	(PARM_IS_BYTE|PARM_IS_ALIGN_LO|PARM_IS_SIGNED)
+#define PARM_SIGN_HI	(PARM_IS_BYTE|PARM_IS_ALIGN_HI|PARM_IS_SIGNED)
+
+static struct emux_parm_defs {
+	int type;	/* byte or word */
+	int low, high;	/* value range */
+	long offset;	/* offset in parameter record (-1 = not written) */
+	int update;	/* flgas for real-time update */
+} parm_defs[EMUX_NUM_EFFECTS] = {
+	{PARM_WORD, 0, 0x8000, parm_offset(moddelay), 0},	/* env1 delay */
+	{PARM_BYTE_LO, 1, 0x80, parm_offset(modatkhld), 0},	/* env1 attack */
+	{PARM_BYTE_HI, 0, 0x7e, parm_offset(modatkhld), 0},	/* env1 hold */
+	{PARM_BYTE_LO, 1, 0x7f, parm_offset(moddcysus), 0},	/* env1 decay */
+	{PARM_BYTE_LO, 1, 0x7f, parm_offset(modrelease), 0},	/* env1 release */
+	{PARM_BYTE_HI, 0, 0x7f, parm_offset(moddcysus), 0},	/* env1 sustain */
+	{PARM_BYTE_HI, 0, 0xff, parm_offset(pefe), 0},	/* env1 pitch */
+	{PARM_BYTE_LO, 0, 0xff, parm_offset(pefe), 0},	/* env1 fc */
+
+	{PARM_WORD, 0, 0x8000, parm_offset(voldelay), 0},	/* env2 delay */
+	{PARM_BYTE_LO, 1, 0x80, parm_offset(volatkhld), 0},	/* env2 attack */
+	{PARM_BYTE_HI, 0, 0x7e, parm_offset(volatkhld), 0},	/* env2 hold */
+	{PARM_BYTE_LO, 1, 0x7f, parm_offset(voldcysus), 0},	/* env2 decay */
+	{PARM_BYTE_LO, 1, 0x7f, parm_offset(volrelease), 0},	/* env2 release */
+	{PARM_BYTE_HI, 0, 0x7f, parm_offset(voldcysus), 0},	/* env2 sustain */
+
+	{PARM_WORD, 0, 0x8000, parm_offset(lfo1delay), 0},	/* lfo1 delay */
+	{PARM_BYTE_LO, 0, 0xff, parm_offset(tremfrq), SNDRV_EMUX_UPDATE_TREMFREQ},	/* lfo1 freq */
+	{PARM_SIGN_HI, -128, 127, parm_offset(tremfrq), SNDRV_EMUX_UPDATE_TREMFREQ},	/* lfo1 vol */
+	{PARM_SIGN_HI, -128, 127, parm_offset(fmmod), SNDRV_EMUX_UPDATE_FMMOD},	/* lfo1 pitch */
+	{PARM_BYTE_LO, 0, 0xff, parm_offset(fmmod), SNDRV_EMUX_UPDATE_FMMOD},	/* lfo1 cutoff */
+
+	{PARM_WORD, 0, 0x8000, parm_offset(lfo2delay), 0},	/* lfo2 delay */
+	{PARM_BYTE_LO, 0, 0xff, parm_offset(fm2frq2), SNDRV_EMUX_UPDATE_FM2FRQ2},	/* lfo2 freq */
+	{PARM_SIGN_HI, -128, 127, parm_offset(fm2frq2), SNDRV_EMUX_UPDATE_FM2FRQ2},	/* lfo2 pitch */
+
+	{PARM_WORD, 0, 0xffff, -1, SNDRV_EMUX_UPDATE_PITCH},	/* initial pitch */
+	{PARM_BYTE, 0, 0xff, parm_offset(chorus), 0},	/* chorus */
+	{PARM_BYTE, 0, 0xff, parm_offset(reverb), 0},	/* reverb */
+	{PARM_BYTE, 0, 0xff, parm_offset(cutoff), SNDRV_EMUX_UPDATE_VOLUME},	/* cutoff */
+	{PARM_BYTE, 0, 15, parm_offset(filterQ), SNDRV_EMUX_UPDATE_Q},	/* resonance */
+
+	{PARM_WORD, 0, 0xffff, -1, 0},	/* sample start */
+	{PARM_WORD, 0, 0xffff, -1, 0},	/* loop start */
+	{PARM_WORD, 0, 0xffff, -1, 0},	/* loop end */
+	{PARM_WORD, 0, 0xffff, -1, 0},	/* coarse sample start */
+	{PARM_WORD, 0, 0xffff, -1, 0},	/* coarse loop start */
+	{PARM_WORD, 0, 0xffff, -1, 0},	/* coarse loop end */
+	{PARM_BYTE, 0, 0xff, -1, SNDRV_EMUX_UPDATE_VOLUME},	/* initial attenuation */
+};
+
+/* set byte effect value */
+static void
+effect_set_byte(unsigned char *valp, struct snd_midi_channel *chan, int type)
+{
+	short effect;
+	struct snd_emux_effect_table *fx = chan->private;
+
+	effect = fx->val[type];
+	if (fx->flag[type] == EMUX_FX_FLAG_ADD) {
+		if (parm_defs[type].type & PARM_IS_SIGNED)
+			effect += *(char*)valp;
+		else
+			effect += *valp;
+	}
+	if (effect < parm_defs[type].low)
+		effect = parm_defs[type].low;
+	else if (effect > parm_defs[type].high)
+		effect = parm_defs[type].high;
+	*valp = (unsigned char)effect;
+}
+
+/* set word effect value */
+static void
+effect_set_word(unsigned short *valp, struct snd_midi_channel *chan, int type)
+{
+	int effect;
+	struct snd_emux_effect_table *fx = chan->private;
+
+	effect = *(unsigned short*)&fx->val[type];
+	if (fx->flag[type] == EMUX_FX_FLAG_ADD)
+		effect += *valp;
+	if (effect < parm_defs[type].low)
+		effect = parm_defs[type].low;
+	else if (effect > parm_defs[type].high)
+		effect = parm_defs[type].high;
+	*valp = (unsigned short)effect;
+}
+
+/* address offset */
+static int
+effect_get_offset(struct snd_midi_channel *chan, int lo, int hi, int mode)
+{
+	int addr = 0;
+	struct snd_emux_effect_table *fx = chan->private;
+
+	if (fx->flag[hi])
+		addr = (short)fx->val[hi];
+	addr = addr << 15;
+	if (fx->flag[lo])
+		addr += (short)fx->val[lo];
+	if (!(mode & SNDRV_SFNT_SAMPLE_8BITS))
+		addr /= 2;
+	return addr;
+}
+
+#ifdef CONFIG_SND_SEQUENCER_OSS
+/* change effects - for OSS sequencer compatibility */
+void
+snd_emux_send_effect_oss(struct snd_emux_port *port,
+			 struct snd_midi_channel *chan, int type, int val)
+{
+	int mode;
+
+	if (type & 0x40)
+		mode = EMUX_FX_FLAG_OFF;
+	else if (type & 0x80)
+		mode = EMUX_FX_FLAG_ADD;
+	else
+		mode = EMUX_FX_FLAG_SET;
+	type &= 0x3f;
+
+	snd_emux_send_effect(port, chan, type, val, mode);
+}
+#endif
+
+/* Modify the effect value.
+ * if update is necessary, call emu8000_control
+ */
+void
+snd_emux_send_effect(struct snd_emux_port *port, struct snd_midi_channel *chan,
+		     int type, int val, int mode)
+{
+	int i;
+	int offset;
+	unsigned char *srcp, *origp;
+	struct snd_emux *emu;
+	struct snd_emux_effect_table *fx;
+	unsigned long flags;
+
+	emu = port->emu;
+	fx = chan->private;
+	if (emu == NULL || fx == NULL)
+		return;
+	if (type < 0 || type >= EMUX_NUM_EFFECTS)
+		return;
+
+	fx->val[type] = val;
+	fx->flag[type] = mode;
+
+	/* do we need to modify the register in realtime ? */
+	if (! parm_defs[type].update || (offset = parm_defs[type].offset) < 0)
+		return;
+
+#ifdef SNDRV_LITTLE_ENDIAN
+	if (parm_defs[type].type & PARM_IS_ALIGN_HI)
+		offset++;
+#else
+	if (parm_defs[type].type & PARM_IS_ALIGN_LO)
+		offset++;
+#endif
+	/* modify the register values */
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (i = 0; i < emu->max_voices; i++) {
+		struct snd_emux_voice *vp = &emu->voices[i];
+		if (!STATE_IS_PLAYING(vp->state) || vp->chan != chan)
+			continue;
+		srcp = (unsigned char*)&vp->reg.parm + offset;
+		origp = (unsigned char*)&vp->zone->v.parm + offset;
+		if (parm_defs[i].type & PARM_IS_BYTE) {
+			*srcp = *origp;
+			effect_set_byte(srcp, chan, type);
+		} else {
+			*(unsigned short*)srcp = *(unsigned short*)origp;
+			effect_set_word((unsigned short*)srcp, chan, type);
+		}
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+
+	/* activate them */
+	snd_emux_update_channel(port, chan, parm_defs[type].update);
+}
+
+
+/* copy wavetable registers to voice table */
+void
+snd_emux_setup_effect(struct snd_emux_voice *vp)
+{
+	struct snd_midi_channel *chan = vp->chan;
+	struct snd_emux_effect_table *fx;
+	unsigned char *srcp;
+	int i;
+
+	if (! (fx = chan->private))
+		return;
+
+	/* modify the register values via effect table */
+	for (i = 0; i < EMUX_FX_END; i++) {
+		int offset;
+		if (! fx->flag[i] || (offset = parm_defs[i].offset) < 0)
+			continue;
+#ifdef SNDRV_LITTLE_ENDIAN
+		if (parm_defs[i].type & PARM_IS_ALIGN_HI)
+			offset++;
+#else
+		if (parm_defs[i].type & PARM_IS_ALIGN_LO)
+			offset++;
+#endif
+		srcp = (unsigned char*)&vp->reg.parm + offset;
+		if (parm_defs[i].type & PARM_IS_BYTE)
+			effect_set_byte(srcp, chan, i);
+		else
+			effect_set_word((unsigned short*)srcp, chan, i);
+	}
+
+	/* correct sample and loop points */
+	vp->reg.start += effect_get_offset(chan, EMUX_FX_SAMPLE_START,
+					   EMUX_FX_COARSE_SAMPLE_START,
+					   vp->reg.sample_mode);
+
+	vp->reg.loopstart += effect_get_offset(chan, EMUX_FX_LOOP_START,
+					       EMUX_FX_COARSE_LOOP_START,
+					       vp->reg.sample_mode);
+
+	vp->reg.loopend += effect_get_offset(chan, EMUX_FX_LOOP_END,
+					     EMUX_FX_COARSE_LOOP_END,
+					     vp->reg.sample_mode);
+}
+
+/*
+ * effect table
+ */
+void
+snd_emux_create_effect(struct snd_emux_port *p)
+{
+	int i;
+	p->effect = kcalloc(p->chset.max_channels,
+			    sizeof(struct snd_emux_effect_table), GFP_KERNEL);
+	if (p->effect) {
+		for (i = 0; i < p->chset.max_channels; i++)
+			p->chset.channels[i].private = p->effect + i;
+	} else {
+		for (i = 0; i < p->chset.max_channels; i++)
+			p->chset.channels[i].private = NULL;
+	}
+}
+
+void
+snd_emux_delete_effect(struct snd_emux_port *p)
+{
+	kfree(p->effect);
+	p->effect = NULL;
+}
+
+void
+snd_emux_clear_effect(struct snd_emux_port *p)
+{
+	if (p->effect) {
+		memset(p->effect, 0, sizeof(struct snd_emux_effect_table) *
+		       p->chset.max_channels);
+	}
+}
+
+#endif /* SNDRV_EMUX_USE_RAW_EFFECT */
diff --git a/linux/sound/synth/emux/emux_hwdep.c b/linux/sound/synth/emux/emux_hwdep.c
new file mode 100644
index 0000000..5ae1eae
--- /dev/null
+++ b/linux/sound/synth/emux/emux_hwdep.c
@@ -0,0 +1,153 @@
+/*
+ *  Interface for hwdep device
+ *
+ *  Copyright (C) 2004 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/core.h>
+#include <sound/hwdep.h>
+#include <asm/uaccess.h>
+#include "emux_voice.h"
+
+
+#define TMP_CLIENT_ID	0x1001
+
+/*
+ * load patch
+ */
+static int
+snd_emux_hwdep_load_patch(struct snd_emux *emu, void __user *arg)
+{
+	int err;
+	struct soundfont_patch_info patch;
+
+	if (copy_from_user(&patch, arg, sizeof(patch)))
+		return -EFAULT;
+
+	if (patch.type >= SNDRV_SFNT_LOAD_INFO &&
+	    patch.type <= SNDRV_SFNT_PROBE_DATA) {
+		err = snd_soundfont_load(emu->sflist, arg, patch.len + sizeof(patch), TMP_CLIENT_ID);
+		if (err < 0)
+			return err;
+	} else {
+		if (emu->ops.load_fx)
+			return emu->ops.load_fx(emu, patch.type, patch.optarg, arg, patch.len + sizeof(patch));
+		else
+			return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * set misc mode
+ */
+static int
+snd_emux_hwdep_misc_mode(struct snd_emux *emu, void __user *arg)
+{
+	struct snd_emux_misc_mode info;
+	int i;
+
+	if (copy_from_user(&info, arg, sizeof(info)))
+		return -EFAULT;
+	if (info.mode < 0 || info.mode >= EMUX_MD_END)
+		return -EINVAL;
+
+	if (info.port < 0) {
+		for (i = 0; i < emu->num_ports; i++)
+			emu->portptrs[i]->ctrls[info.mode] = info.value;
+	} else {
+		if (info.port < emu->num_ports)
+			emu->portptrs[info.port]->ctrls[info.mode] = info.value;
+	}
+	return 0;
+}
+
+
+/*
+ * ioctl
+ */
+static int
+snd_emux_hwdep_ioctl(struct snd_hwdep * hw, struct file *file,
+		     unsigned int cmd, unsigned long arg)
+{
+	struct snd_emux *emu = hw->private_data;
+
+	switch (cmd) {
+	case SNDRV_EMUX_IOCTL_VERSION:
+		return put_user(SNDRV_EMUX_VERSION, (unsigned int __user *)arg);
+	case SNDRV_EMUX_IOCTL_LOAD_PATCH:
+		return snd_emux_hwdep_load_patch(emu, (void __user *)arg);
+	case SNDRV_EMUX_IOCTL_RESET_SAMPLES:
+		snd_soundfont_remove_samples(emu->sflist);
+		break;
+	case SNDRV_EMUX_IOCTL_REMOVE_LAST_SAMPLES:
+		snd_soundfont_remove_unlocked(emu->sflist);
+		break;
+	case SNDRV_EMUX_IOCTL_MEM_AVAIL:
+		if (emu->memhdr) {
+			int size = snd_util_mem_avail(emu->memhdr);
+			return put_user(size, (unsigned int __user *)arg);
+		}
+		break;
+	case SNDRV_EMUX_IOCTL_MISC_MODE:
+		return snd_emux_hwdep_misc_mode(emu, (void __user *)arg);
+	}
+
+	return 0;
+}
+
+
+/*
+ * register hwdep device
+ */
+
+int
+snd_emux_init_hwdep(struct snd_emux *emu)
+{
+	struct snd_hwdep *hw;
+	int err;
+
+	if ((err = snd_hwdep_new(emu->card, SNDRV_EMUX_HWDEP_NAME, emu->hwdep_idx, &hw)) < 0)
+		return err;
+	emu->hwdep = hw;
+	strcpy(hw->name, SNDRV_EMUX_HWDEP_NAME);
+	hw->iface = SNDRV_HWDEP_IFACE_EMUX_WAVETABLE;
+	hw->ops.ioctl = snd_emux_hwdep_ioctl;
+	/* The ioctl parameter types are compatible between 32- and
+	 * 64-bit architectures, so use the same function. */
+	hw->ops.ioctl_compat = snd_emux_hwdep_ioctl;
+	hw->exclusive = 1;
+	hw->private_data = emu;
+	if ((err = snd_card_register(emu->card)) < 0)
+		return err;
+
+	return 0;
+}
+
+
+/*
+ * unregister
+ */
+void
+snd_emux_delete_hwdep(struct snd_emux *emu)
+{
+	if (emu->hwdep) {
+		snd_device_free(emu->card, emu->hwdep);
+		emu->hwdep = NULL;
+	}
+}
diff --git a/linux/sound/synth/emux/emux_nrpn.c b/linux/sound/synth/emux/emux_nrpn.c
new file mode 100644
index 0000000..00fc005
--- /dev/null
+++ b/linux/sound/synth/emux/emux_nrpn.c
@@ -0,0 +1,396 @@
+/*
+ *  NRPN / SYSEX callbacks for Emu8k/Emu10k1
+ *
+ *  Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "emux_voice.h"
+#include <sound/asoundef.h>
+
+/*
+ * conversion from NRPN/control parameters to Emu8000 raw parameters
+ */
+
+/* NRPN / CC -> Emu8000 parameter converter */
+struct nrpn_conv_table {
+	int control;
+	int effect;
+	int (*convert)(int val);
+};
+
+/* effect sensitivity */
+
+#define FX_CUTOFF	0
+#define FX_RESONANCE	1
+#define FX_ATTACK	2
+#define FX_RELEASE	3
+#define FX_VIBRATE	4
+#define FX_VIBDEPTH	5
+#define FX_VIBDELAY	6
+#define FX_NUMS		7
+
+/*
+ * convert NRPN/control values
+ */
+
+static int send_converted_effect(struct nrpn_conv_table *table, int num_tables,
+				 struct snd_emux_port *port,
+				 struct snd_midi_channel *chan,
+				 int type, int val, int mode)
+{
+	int i, cval;
+	for (i = 0; i < num_tables; i++) {
+		if (table[i].control == type) {
+			cval = table[i].convert(val);
+			snd_emux_send_effect(port, chan, table[i].effect,
+					     cval, mode);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+#define DEF_FX_CUTOFF		170
+#define DEF_FX_RESONANCE	6
+#define DEF_FX_ATTACK		50
+#define DEF_FX_RELEASE		50
+#define DEF_FX_VIBRATE		30
+#define DEF_FX_VIBDEPTH		4
+#define DEF_FX_VIBDELAY		1500
+
+/* effect sensitivities for GS NRPN:
+ *  adjusted for chaos 8MB soundfonts
+ */
+static int gs_sense[] = 
+{
+	DEF_FX_CUTOFF, DEF_FX_RESONANCE, DEF_FX_ATTACK, DEF_FX_RELEASE,
+	DEF_FX_VIBRATE, DEF_FX_VIBDEPTH, DEF_FX_VIBDELAY
+};
+
+/* effect sensitivies for XG controls:
+ * adjusted for chaos 8MB soundfonts
+ */
+static int xg_sense[] = 
+{
+	DEF_FX_CUTOFF, DEF_FX_RESONANCE, DEF_FX_ATTACK, DEF_FX_RELEASE,
+	DEF_FX_VIBRATE, DEF_FX_VIBDEPTH, DEF_FX_VIBDELAY
+};
+
+
+/*
+ * AWE32 NRPN effects
+ */
+
+static int fx_delay(int val);
+static int fx_attack(int val);
+static int fx_hold(int val);
+static int fx_decay(int val);
+static int fx_the_value(int val);
+static int fx_twice_value(int val);
+static int fx_conv_pitch(int val);
+static int fx_conv_Q(int val);
+
+/* function for each NRPN */		/* [range]  units */
+#define fx_env1_delay	fx_delay	/* [0,5900] 4msec */
+#define fx_env1_attack	fx_attack	/* [0,5940] 1msec */
+#define fx_env1_hold	fx_hold		/* [0,8191] 1msec */
+#define fx_env1_decay	fx_decay	/* [0,5940] 4msec */
+#define fx_env1_release	fx_decay	/* [0,5940] 4msec */
+#define fx_env1_sustain	fx_the_value	/* [0,127] 0.75dB */
+#define fx_env1_pitch	fx_the_value	/* [-127,127] 9.375cents */
+#define fx_env1_cutoff	fx_the_value	/* [-127,127] 56.25cents */
+
+#define fx_env2_delay	fx_delay	/* [0,5900] 4msec */
+#define fx_env2_attack	fx_attack	/* [0,5940] 1msec */
+#define fx_env2_hold	fx_hold		/* [0,8191] 1msec */
+#define fx_env2_decay	fx_decay	/* [0,5940] 4msec */
+#define fx_env2_release	fx_decay	/* [0,5940] 4msec */
+#define fx_env2_sustain	fx_the_value	/* [0,127] 0.75dB */
+
+#define fx_lfo1_delay	fx_delay	/* [0,5900] 4msec */
+#define fx_lfo1_freq	fx_twice_value	/* [0,127] 84mHz */
+#define fx_lfo1_volume	fx_twice_value	/* [0,127] 0.1875dB */
+#define fx_lfo1_pitch	fx_the_value	/* [-127,127] 9.375cents */
+#define fx_lfo1_cutoff	fx_twice_value	/* [-64,63] 56.25cents */
+
+#define fx_lfo2_delay	fx_delay	/* [0,5900] 4msec */
+#define fx_lfo2_freq	fx_twice_value	/* [0,127] 84mHz */
+#define fx_lfo2_pitch	fx_the_value	/* [-127,127] 9.375cents */
+
+#define fx_init_pitch	fx_conv_pitch	/* [-8192,8192] cents */
+#define fx_chorus	fx_the_value	/* [0,255] -- */
+#define fx_reverb	fx_the_value	/* [0,255] -- */
+#define fx_cutoff	fx_twice_value	/* [0,127] 62Hz */
+#define fx_filterQ	fx_conv_Q	/* [0,127] -- */
+
+static int fx_delay(int val)
+{
+	return (unsigned short)snd_sf_calc_parm_delay(val);
+}
+
+static int fx_attack(int val)
+{
+	return (unsigned short)snd_sf_calc_parm_attack(val);
+}
+
+static int fx_hold(int val)
+{
+	return (unsigned short)snd_sf_calc_parm_hold(val);
+}
+
+static int fx_decay(int val)
+{
+	return (unsigned short)snd_sf_calc_parm_decay(val);
+}
+
+static int fx_the_value(int val)
+{
+	return (unsigned short)(val & 0xff);
+}
+
+static int fx_twice_value(int val)
+{
+	return (unsigned short)((val * 2) & 0xff);
+}
+
+static int fx_conv_pitch(int val)
+{
+	return (short)(val * 4096 / 1200);
+}
+
+static int fx_conv_Q(int val)
+{
+	return (unsigned short)((val / 8) & 0xff);
+}
+
+
+static struct nrpn_conv_table awe_effects[] =
+{
+	{ 0, EMUX_FX_LFO1_DELAY,	fx_lfo1_delay},
+	{ 1, EMUX_FX_LFO1_FREQ,	fx_lfo1_freq},
+	{ 2, EMUX_FX_LFO2_DELAY,	fx_lfo2_delay},
+	{ 3, EMUX_FX_LFO2_FREQ,	fx_lfo2_freq},
+
+	{ 4, EMUX_FX_ENV1_DELAY,	fx_env1_delay},
+	{ 5, EMUX_FX_ENV1_ATTACK,fx_env1_attack},
+	{ 6, EMUX_FX_ENV1_HOLD,	fx_env1_hold},
+	{ 7, EMUX_FX_ENV1_DECAY,	fx_env1_decay},
+	{ 8, EMUX_FX_ENV1_SUSTAIN,	fx_env1_sustain},
+	{ 9, EMUX_FX_ENV1_RELEASE,	fx_env1_release},
+
+	{10, EMUX_FX_ENV2_DELAY,	fx_env2_delay},
+	{11, EMUX_FX_ENV2_ATTACK,	fx_env2_attack},
+	{12, EMUX_FX_ENV2_HOLD,	fx_env2_hold},
+	{13, EMUX_FX_ENV2_DECAY,	fx_env2_decay},
+	{14, EMUX_FX_ENV2_SUSTAIN,	fx_env2_sustain},
+	{15, EMUX_FX_ENV2_RELEASE,	fx_env2_release},
+
+	{16, EMUX_FX_INIT_PITCH,	fx_init_pitch},
+	{17, EMUX_FX_LFO1_PITCH,	fx_lfo1_pitch},
+	{18, EMUX_FX_LFO2_PITCH,	fx_lfo2_pitch},
+	{19, EMUX_FX_ENV1_PITCH,	fx_env1_pitch},
+	{20, EMUX_FX_LFO1_VOLUME,	fx_lfo1_volume},
+	{21, EMUX_FX_CUTOFF,		fx_cutoff},
+	{22, EMUX_FX_FILTERQ,	fx_filterQ},
+	{23, EMUX_FX_LFO1_CUTOFF,	fx_lfo1_cutoff},
+	{24, EMUX_FX_ENV1_CUTOFF,	fx_env1_cutoff},
+	{25, EMUX_FX_CHORUS,		fx_chorus},
+	{26, EMUX_FX_REVERB,		fx_reverb},
+};
+
+
+/*
+ * GS(SC88) NRPN effects; still experimental
+ */
+
+/* cutoff: quarter semitone step, max=255 */
+static int gs_cutoff(int val)
+{
+	return (val - 64) * gs_sense[FX_CUTOFF] / 50;
+}
+
+/* resonance: 0 to 15(max) */
+static int gs_filterQ(int val)
+{
+	return (val - 64) * gs_sense[FX_RESONANCE] / 50;
+}
+
+/* attack: */
+static int gs_attack(int val)
+{
+	return -(val - 64) * gs_sense[FX_ATTACK] / 50;
+}
+
+/* decay: */
+static int gs_decay(int val)
+{
+	return -(val - 64) * gs_sense[FX_RELEASE] / 50;
+}
+
+/* release: */
+static int gs_release(int val)
+{
+	return -(val - 64) * gs_sense[FX_RELEASE] / 50;
+}
+
+/* vibrato freq: 0.042Hz step, max=255 */
+static int gs_vib_rate(int val)
+{
+	return (val - 64) * gs_sense[FX_VIBRATE] / 50;
+}
+
+/* vibrato depth: max=127, 1 octave */
+static int gs_vib_depth(int val)
+{
+	return (val - 64) * gs_sense[FX_VIBDEPTH] / 50;
+}
+
+/* vibrato delay: -0.725msec step */
+static int gs_vib_delay(int val)
+{
+	return -(val - 64) * gs_sense[FX_VIBDELAY] / 50;
+}
+
+static struct nrpn_conv_table gs_effects[] =
+{
+	{32, EMUX_FX_CUTOFF,	gs_cutoff},
+	{33, EMUX_FX_FILTERQ,	gs_filterQ},
+	{99, EMUX_FX_ENV2_ATTACK, gs_attack},
+	{100, EMUX_FX_ENV2_DECAY, gs_decay},
+	{102, EMUX_FX_ENV2_RELEASE, gs_release},
+	{8, EMUX_FX_LFO1_FREQ, gs_vib_rate},
+	{9, EMUX_FX_LFO1_VOLUME, gs_vib_depth},
+	{10, EMUX_FX_LFO1_DELAY, gs_vib_delay},
+};
+
+
+/*
+ * NRPN events
+ */
+void
+snd_emux_nrpn(void *p, struct snd_midi_channel *chan,
+	      struct snd_midi_channel_set *chset)
+{
+	struct snd_emux_port *port;
+
+	port = p;
+	if (snd_BUG_ON(!port || !chan))
+		return;
+
+	if (chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB] == 127 &&
+	    chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB] <= 26) {
+		int val;
+		/* Win/DOS AWE32 specific NRPNs */
+		/* both MSB/LSB necessary */
+		val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) |
+			chan->control[MIDI_CTL_LSB_DATA_ENTRY]; 
+		val -= 8192;
+		send_converted_effect
+			(awe_effects, ARRAY_SIZE(awe_effects),
+			 port, chan, chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB],
+			 val, EMUX_FX_FLAG_SET);
+		return;
+	}
+
+	if (port->chset.midi_mode == SNDRV_MIDI_MODE_GS &&
+	    chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB] == 1) {
+		int val;
+		/* GS specific NRPNs */
+		/* only MSB is valid */
+		val = chan->control[MIDI_CTL_MSB_DATA_ENTRY];
+		send_converted_effect
+			(gs_effects, ARRAY_SIZE(gs_effects),
+			 port, chan, chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB],
+			 val, EMUX_FX_FLAG_ADD);
+		return;
+	}
+}
+
+
+/*
+ * XG control effects; still experimental
+ */
+
+/* cutoff: quarter semitone step, max=255 */
+static int xg_cutoff(int val)
+{
+	return (val - 64) * xg_sense[FX_CUTOFF] / 64;
+}
+
+/* resonance: 0(open) to 15(most nasal) */
+static int xg_filterQ(int val)
+{
+	return (val - 64) * xg_sense[FX_RESONANCE] / 64;
+}
+
+/* attack: */
+static int xg_attack(int val)
+{
+	return -(val - 64) * xg_sense[FX_ATTACK] / 64;
+}
+
+/* release: */
+static int xg_release(int val)
+{
+	return -(val - 64) * xg_sense[FX_RELEASE] / 64;
+}
+
+static struct nrpn_conv_table xg_effects[] =
+{
+	{71, EMUX_FX_CUTOFF,	xg_cutoff},
+	{74, EMUX_FX_FILTERQ,	xg_filterQ},
+	{72, EMUX_FX_ENV2_RELEASE, xg_release},
+	{73, EMUX_FX_ENV2_ATTACK, xg_attack},
+};
+
+int
+snd_emux_xg_control(struct snd_emux_port *port, struct snd_midi_channel *chan,
+		    int param)
+{
+	return send_converted_effect(xg_effects, ARRAY_SIZE(xg_effects),
+				     port, chan, param,
+				     chan->control[param],
+				     EMUX_FX_FLAG_ADD);
+}
+
+/*
+ * receive sysex
+ */
+void
+snd_emux_sysex(void *p, unsigned char *buf, int len, int parsed,
+	       struct snd_midi_channel_set *chset)
+{
+	struct snd_emux_port *port;
+	struct snd_emux *emu;
+
+	port = p;
+	if (snd_BUG_ON(!port || !chset))
+		return;
+	emu = port->emu;
+
+	switch (parsed) {
+	case SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME:
+		snd_emux_update_port(port, SNDRV_EMUX_UPDATE_VOLUME);
+		break;
+	default:
+		if (emu->ops.sysex)
+			emu->ops.sysex(emu, buf, len, parsed, chset);
+		break;
+	}
+}
+
diff --git a/linux/sound/synth/emux/emux_oss.c b/linux/sound/synth/emux/emux_oss.c
new file mode 100644
index 0000000..87e4220
--- /dev/null
+++ b/linux/sound/synth/emux/emux_oss.c
@@ -0,0 +1,516 @@
+/*
+ *  Interface for OSS sequencer emulation
+ *
+ *  Copyright (C) 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
+ *
+ * Changes
+ * 19990227   Steve Ratcliffe   Made separate file and merged in latest
+ * 				midi emulation.
+ */
+
+
+#ifdef CONFIG_SND_SEQUENCER_OSS
+
+#include <asm/uaccess.h>
+#include <sound/core.h>
+#include "emux_voice.h"
+#include <sound/asoundef.h>
+
+static int snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure);
+static int snd_emux_close_seq_oss(struct snd_seq_oss_arg *arg);
+static int snd_emux_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd,
+				  unsigned long ioarg);
+static int snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format,
+				       const char __user *buf, int offs, int count);
+static int snd_emux_reset_seq_oss(struct snd_seq_oss_arg *arg);
+static int snd_emux_event_oss_input(struct snd_seq_event *ev, int direct,
+				    void *private, int atomic, int hop);
+static void reset_port_mode(struct snd_emux_port *port, int midi_mode);
+static void emuspec_control(struct snd_emux *emu, struct snd_emux_port *port,
+			    int cmd, unsigned char *event, int atomic, int hop);
+static void gusspec_control(struct snd_emux *emu, struct snd_emux_port *port,
+			    int cmd, unsigned char *event, int atomic, int hop);
+static void fake_event(struct snd_emux *emu, struct snd_emux_port *port,
+		       int ch, int param, int val, int atomic, int hop);
+
+/* operators */
+static struct snd_seq_oss_callback oss_callback = {
+	.owner = THIS_MODULE,
+	.open = snd_emux_open_seq_oss,
+	.close = snd_emux_close_seq_oss,
+	.ioctl = snd_emux_ioctl_seq_oss,
+	.load_patch = snd_emux_load_patch_seq_oss,
+	.reset = snd_emux_reset_seq_oss,
+};
+
+
+/*
+ * register OSS synth
+ */
+
+void
+snd_emux_init_seq_oss(struct snd_emux *emu)
+{
+	struct snd_seq_oss_reg *arg;
+	struct snd_seq_device *dev;
+
+	if (snd_seq_device_new(emu->card, 0, SNDRV_SEQ_DEV_ID_OSS,
+			       sizeof(struct snd_seq_oss_reg), &dev) < 0)
+		return;
+
+	emu->oss_synth = dev;
+	strcpy(dev->name, emu->name);
+	arg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
+	arg->type = SYNTH_TYPE_SAMPLE;
+	arg->subtype = SAMPLE_TYPE_AWE32;
+	arg->nvoices = emu->max_voices;
+	arg->oper = oss_callback;
+	arg->private_data = emu;
+
+	/* register to OSS synth table */
+	snd_device_register(emu->card, dev);
+}
+
+
+/*
+ * unregister
+ */
+void
+snd_emux_detach_seq_oss(struct snd_emux *emu)
+{
+	if (emu->oss_synth) {
+		snd_device_free(emu->card, emu->oss_synth);
+		emu->oss_synth = NULL;
+	}
+}
+
+
+/* use port number as a unique soundfont client number */
+#define SF_CLIENT_NO(p)	((p) + 0x1000)
+
+/*
+ * open port for OSS sequencer
+ */
+static int
+snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure)
+{
+	struct snd_emux *emu;
+	struct snd_emux_port *p;
+	struct snd_seq_port_callback callback;
+	char tmpname[64];
+
+	emu = closure;
+	if (snd_BUG_ON(!arg || !emu))
+		return -ENXIO;
+
+	mutex_lock(&emu->register_mutex);
+
+	if (!snd_emux_inc_count(emu)) {
+		mutex_unlock(&emu->register_mutex);
+		return -EFAULT;
+	}
+
+	memset(&callback, 0, sizeof(callback));
+	callback.owner = THIS_MODULE;
+	callback.event_input = snd_emux_event_oss_input;
+
+	sprintf(tmpname, "%s OSS Port", emu->name);
+	p = snd_emux_create_port(emu, tmpname, 32,
+				 1, &callback);
+	if (p == NULL) {
+		snd_printk(KERN_ERR "can't create port\n");
+		snd_emux_dec_count(emu);
+		mutex_unlock(&emu->register_mutex);
+		return -ENOMEM;
+	}
+
+	/* fill the argument data */
+	arg->private_data = p;
+	arg->addr.client = p->chset.client;
+	arg->addr.port = p->chset.port;
+	p->oss_arg = arg;
+
+	reset_port_mode(p, arg->seq_mode);
+
+	snd_emux_reset_port(p);
+
+	mutex_unlock(&emu->register_mutex);
+	return 0;
+}
+
+
+#define DEFAULT_DRUM_FLAGS	((1<<9) | (1<<25))
+
+/*
+ * reset port mode
+ */
+static void
+reset_port_mode(struct snd_emux_port *port, int midi_mode)
+{
+	if (midi_mode) {
+		port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_MIDI;
+		port->drum_flags = DEFAULT_DRUM_FLAGS;
+		port->volume_atten = 0;
+		port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_KEYPRESS;
+	} else {
+		port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_SYNTH;
+		port->drum_flags = 0;
+		port->volume_atten = 32;
+		port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS;
+	}
+}
+
+
+/*
+ * close port
+ */
+static int
+snd_emux_close_seq_oss(struct snd_seq_oss_arg *arg)
+{
+	struct snd_emux *emu;
+	struct snd_emux_port *p;
+
+	if (snd_BUG_ON(!arg))
+		return -ENXIO;
+	p = arg->private_data;
+	if (snd_BUG_ON(!p))
+		return -ENXIO;
+
+	emu = p->emu;
+	if (snd_BUG_ON(!emu))
+		return -ENXIO;
+
+	mutex_lock(&emu->register_mutex);
+	snd_emux_sounds_off_all(p);
+	snd_soundfont_close_check(emu->sflist, SF_CLIENT_NO(p->chset.port));
+	snd_seq_event_port_detach(p->chset.client, p->chset.port);
+	snd_emux_dec_count(emu);
+
+	mutex_unlock(&emu->register_mutex);
+	return 0;
+}
+
+
+/*
+ * load patch
+ */
+static int
+snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format,
+			    const char __user *buf, int offs, int count)
+{
+	struct snd_emux *emu;
+	struct snd_emux_port *p;
+	int rc;
+
+	if (snd_BUG_ON(!arg))
+		return -ENXIO;
+	p = arg->private_data;
+	if (snd_BUG_ON(!p))
+		return -ENXIO;
+
+	emu = p->emu;
+	if (snd_BUG_ON(!emu))
+		return -ENXIO;
+
+	if (format == GUS_PATCH)
+		rc = snd_soundfont_load_guspatch(emu->sflist, buf, count,
+						 SF_CLIENT_NO(p->chset.port));
+	else if (format == SNDRV_OSS_SOUNDFONT_PATCH) {
+		struct soundfont_patch_info patch;
+		if (count < (int)sizeof(patch))
+			rc = -EINVAL;
+		if (copy_from_user(&patch, buf, sizeof(patch)))
+			rc = -EFAULT;
+		if (patch.type >= SNDRV_SFNT_LOAD_INFO &&
+		    patch.type <= SNDRV_SFNT_PROBE_DATA)
+			rc = snd_soundfont_load(emu->sflist, buf, count, SF_CLIENT_NO(p->chset.port));
+		else {
+			if (emu->ops.load_fx)
+				rc = emu->ops.load_fx(emu, patch.type, patch.optarg, buf, count);
+			else
+				rc = -EINVAL;
+		}
+	} else
+		rc = 0;
+	return rc;
+}
+
+
+/*
+ * ioctl
+ */
+static int
+snd_emux_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, unsigned long ioarg)
+{
+	struct snd_emux_port *p;
+	struct snd_emux *emu;
+
+	if (snd_BUG_ON(!arg))
+		return -ENXIO;
+	p = arg->private_data;
+	if (snd_BUG_ON(!p))
+		return -ENXIO;
+
+	emu = p->emu;
+	if (snd_BUG_ON(!emu))
+		return -ENXIO;
+
+	switch (cmd) {
+	case SNDCTL_SEQ_RESETSAMPLES:
+		snd_soundfont_remove_samples(emu->sflist);
+		return 0;
+			
+	case SNDCTL_SYNTH_MEMAVL:
+		if (emu->memhdr)
+			return snd_util_mem_avail(emu->memhdr);
+		return 0;
+	}
+
+	return 0;
+}
+
+
+/*
+ * reset device
+ */
+static int
+snd_emux_reset_seq_oss(struct snd_seq_oss_arg *arg)
+{
+	struct snd_emux_port *p;
+
+	if (snd_BUG_ON(!arg))
+		return -ENXIO;
+	p = arg->private_data;
+	if (snd_BUG_ON(!p))
+		return -ENXIO;
+	snd_emux_reset_port(p);
+	return 0;
+}
+
+
+/*
+ * receive raw events: only SEQ_PRIVATE is accepted.
+ */
+static int
+snd_emux_event_oss_input(struct snd_seq_event *ev, int direct, void *private_data,
+			 int atomic, int hop)
+{
+	struct snd_emux *emu;
+	struct snd_emux_port *p;
+	unsigned char cmd, *data;
+
+	p = private_data;
+	if (snd_BUG_ON(!p))
+		return -EINVAL;
+	emu = p->emu;
+	if (snd_BUG_ON(!emu))
+		return -EINVAL;
+	if (ev->type != SNDRV_SEQ_EVENT_OSS)
+		return snd_emux_event_input(ev, direct, private_data, atomic, hop);
+
+	data = ev->data.raw8.d;
+	/* only SEQ_PRIVATE is accepted */
+	if (data[0] != 0xfe)
+		return 0;
+	cmd = data[2] & _EMUX_OSS_MODE_VALUE_MASK;
+	if (data[2] & _EMUX_OSS_MODE_FLAG)
+		emuspec_control(emu, p, cmd, data, atomic, hop);
+	else
+		gusspec_control(emu, p, cmd, data, atomic, hop);
+	return 0;
+}
+
+
+/*
+ * OSS/AWE driver specific h/w controls
+ */
+static void
+emuspec_control(struct snd_emux *emu, struct snd_emux_port *port, int cmd,
+		unsigned char *event, int atomic, int hop)
+{
+	int voice;
+	unsigned short p1;
+	short p2;
+	int i;
+	struct snd_midi_channel *chan;
+
+	voice = event[3];
+	if (voice < 0 || voice >= port->chset.max_channels)
+		chan = NULL;
+	else
+		chan = &port->chset.channels[voice];
+
+	p1 = *(unsigned short *) &event[4];
+	p2 = *(short *) &event[6];
+
+	switch (cmd) {
+#if 0 /* don't do this atomically */
+	case _EMUX_OSS_REMOVE_LAST_SAMPLES:
+		snd_soundfont_remove_unlocked(emu->sflist);
+		break;
+#endif
+	case _EMUX_OSS_SEND_EFFECT:
+		if (chan)
+			snd_emux_send_effect_oss(port, chan, p1, p2);
+		break;
+		
+	case _EMUX_OSS_TERMINATE_ALL:
+		snd_emux_terminate_all(emu);
+		break;
+
+	case _EMUX_OSS_TERMINATE_CHANNEL:
+		/*snd_emux_mute_channel(emu, chan);*/
+		break;
+	case _EMUX_OSS_RESET_CHANNEL:
+		/*snd_emux_channel_init(chset, chan);*/
+		break;
+
+	case _EMUX_OSS_RELEASE_ALL:
+		fake_event(emu, port, voice, MIDI_CTL_ALL_NOTES_OFF, 0, atomic, hop);
+		break;
+	case _EMUX_OSS_NOTEOFF_ALL:
+		fake_event(emu, port, voice, MIDI_CTL_ALL_SOUNDS_OFF, 0, atomic, hop);
+		break;
+
+	case _EMUX_OSS_INITIAL_VOLUME:
+		if (p2) {
+			port->volume_atten = (short)p1;
+			snd_emux_update_port(port, SNDRV_EMUX_UPDATE_VOLUME);
+		}
+		break;
+
+	case _EMUX_OSS_CHN_PRESSURE:
+		if (chan) {
+			chan->midi_pressure = p1;
+			snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_FMMOD|SNDRV_EMUX_UPDATE_FM2FRQ2);
+		}
+		break;
+
+	case _EMUX_OSS_CHANNEL_MODE:
+		reset_port_mode(port, p1);
+		snd_emux_reset_port(port);
+		break;
+
+	case _EMUX_OSS_DRUM_CHANNELS:
+		port->drum_flags = *(unsigned int*)&event[4];
+		for (i = 0; i < port->chset.max_channels; i++) {
+			chan = &port->chset.channels[i];
+			chan->drum_channel = ((port->drum_flags >> i) & 1) ? 1 : 0;
+		}
+		break;
+
+	case _EMUX_OSS_MISC_MODE:
+		if (p1 < EMUX_MD_END)
+			port->ctrls[p1] = p2;
+		break;
+	case _EMUX_OSS_DEBUG_MODE:
+		break;
+
+	default:
+		if (emu->ops.oss_ioctl)
+			emu->ops.oss_ioctl(emu, cmd, p1, p2);
+		break;
+	}
+}
+
+/*
+ * GUS specific h/w controls
+ */
+
+#include <linux/ultrasound.h>
+
+static void
+gusspec_control(struct snd_emux *emu, struct snd_emux_port *port, int cmd,
+		unsigned char *event, int atomic, int hop)
+{
+	int voice;
+	unsigned short p1;
+	short p2;
+	int plong;
+	struct snd_midi_channel *chan;
+
+	if (port->port_mode != SNDRV_EMUX_PORT_MODE_OSS_SYNTH)
+		return;
+	if (cmd == _GUS_NUMVOICES)
+		return;
+	voice = event[3];
+	if (voice < 0 || voice >= port->chset.max_channels)
+		return;
+
+	chan = &port->chset.channels[voice];
+
+	p1 = *(unsigned short *) &event[4];
+	p2 = *(short *) &event[6];
+	plong = *(int*) &event[4];
+
+	switch (cmd) {
+	case _GUS_VOICESAMPLE:
+		chan->midi_program = p1;
+		return;
+
+	case _GUS_VOICEBALA:
+		/* 0 to 15 --> 0 to 127 */
+		chan->control[MIDI_CTL_MSB_PAN] = (int)p1 << 3;
+		snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PAN);
+		return;
+
+	case _GUS_VOICEVOL:
+	case _GUS_VOICEVOL2:
+		/* not supported yet */
+		return;
+
+	case _GUS_RAMPRANGE:
+	case _GUS_RAMPRATE:
+	case _GUS_RAMPMODE:
+	case _GUS_RAMPON:
+	case _GUS_RAMPOFF:
+		/* volume ramping not supported */
+		return;
+
+	case _GUS_VOLUME_SCALE:
+		return;
+
+	case _GUS_VOICE_POS:
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+		snd_emux_send_effect(port, chan, EMUX_FX_SAMPLE_START,
+				     (short)(plong & 0x7fff),
+				     EMUX_FX_FLAG_SET);
+		snd_emux_send_effect(port, chan, EMUX_FX_COARSE_SAMPLE_START,
+				     (plong >> 15) & 0xffff,
+				     EMUX_FX_FLAG_SET);
+#endif
+		return;
+	}
+}
+
+
+/*
+ * send an event to midi emulation
+ */
+static void
+fake_event(struct snd_emux *emu, struct snd_emux_port *port, int ch, int param, int val, int atomic, int hop)
+{
+	struct snd_seq_event ev;
+	memset(&ev, 0, sizeof(ev));
+	ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
+	ev.data.control.channel = ch;
+	ev.data.control.param = param;
+	ev.data.control.value = val;
+	snd_emux_event_input(&ev, 0, port, atomic, hop);
+}
+
+#endif /* CONFIG_SND_SEQUENCER_OSS */
diff --git a/linux/sound/synth/emux/emux_proc.c b/linux/sound/synth/emux/emux_proc.c
new file mode 100644
index 0000000..58a32a1
--- /dev/null
+++ b/linux/sound/synth/emux/emux_proc.c
@@ -0,0 +1,132 @@
+/*
+ *  Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Proc interface for Emu8k/Emu10k1 WaveTable synth
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/emux_synth.h>
+#include <sound/info.h>
+#include "emux_voice.h"
+
+#ifdef CONFIG_PROC_FS
+
+static void
+snd_emux_proc_info_read(struct snd_info_entry *entry, 
+			struct snd_info_buffer *buf)
+{
+	struct snd_emux *emu;
+	int i;
+
+	emu = entry->private_data;
+	mutex_lock(&emu->register_mutex);
+	if (emu->name)
+		snd_iprintf(buf, "Device: %s\n", emu->name);
+	snd_iprintf(buf, "Ports: %d\n", emu->num_ports);
+	snd_iprintf(buf, "Addresses:");
+	for (i = 0; i < emu->num_ports; i++)
+		snd_iprintf(buf, " %d:%d", emu->client, emu->ports[i]);
+	snd_iprintf(buf, "\n");
+	snd_iprintf(buf, "Use Counter: %d\n", emu->used);
+	snd_iprintf(buf, "Max Voices: %d\n", emu->max_voices);
+	snd_iprintf(buf, "Allocated Voices: %d\n", emu->num_voices);
+	if (emu->memhdr) {
+		snd_iprintf(buf, "Memory Size: %d\n", emu->memhdr->size);
+		snd_iprintf(buf, "Memory Available: %d\n", snd_util_mem_avail(emu->memhdr));
+		snd_iprintf(buf, "Allocated Blocks: %d\n", emu->memhdr->nblocks);
+	} else {
+		snd_iprintf(buf, "Memory Size: 0\n");
+	}
+	if (emu->sflist) {
+		mutex_lock(&emu->sflist->presets_mutex);
+		snd_iprintf(buf, "SoundFonts: %d\n", emu->sflist->fonts_size);
+		snd_iprintf(buf, "Instruments: %d\n", emu->sflist->zone_counter);
+		snd_iprintf(buf, "Samples: %d\n", emu->sflist->sample_counter);
+		snd_iprintf(buf, "Locked Instruments: %d\n", emu->sflist->zone_locked);
+		snd_iprintf(buf, "Locked Samples: %d\n", emu->sflist->sample_locked);
+		mutex_unlock(&emu->sflist->presets_mutex);
+	}
+#if 0  /* debug */
+	if (emu->voices[0].state != SNDRV_EMUX_ST_OFF && emu->voices[0].ch >= 0) {
+		struct snd_emux_voice *vp = &emu->voices[0];
+		snd_iprintf(buf, "voice 0: on\n");
+		snd_iprintf(buf, "mod delay=%x, atkhld=%x, dcysus=%x, rel=%x\n",
+			    vp->reg.parm.moddelay,
+			    vp->reg.parm.modatkhld,
+			    vp->reg.parm.moddcysus,
+			    vp->reg.parm.modrelease);
+		snd_iprintf(buf, "vol delay=%x, atkhld=%x, dcysus=%x, rel=%x\n",
+			    vp->reg.parm.voldelay,
+			    vp->reg.parm.volatkhld,
+			    vp->reg.parm.voldcysus,
+			    vp->reg.parm.volrelease);
+		snd_iprintf(buf, "lfo1 delay=%x, lfo2 delay=%x, pefe=%x\n",
+			    vp->reg.parm.lfo1delay,
+			    vp->reg.parm.lfo2delay,
+			    vp->reg.parm.pefe);
+		snd_iprintf(buf, "fmmod=%x, tremfrq=%x, fm2frq2=%x\n",
+			    vp->reg.parm.fmmod,
+			    vp->reg.parm.tremfrq,
+			    vp->reg.parm.fm2frq2);
+		snd_iprintf(buf, "cutoff=%x, filterQ=%x, chorus=%x, reverb=%x\n",
+			    vp->reg.parm.cutoff,
+			    vp->reg.parm.filterQ,
+			    vp->reg.parm.chorus,
+			    vp->reg.parm.reverb);
+		snd_iprintf(buf, "avol=%x, acutoff=%x, apitch=%x\n",
+			    vp->avol, vp->acutoff, vp->apitch);
+		snd_iprintf(buf, "apan=%x, aaux=%x, ptarget=%x, vtarget=%x, ftarget=%x\n",
+			    vp->apan, vp->aaux,
+			    vp->ptarget,
+			    vp->vtarget,
+			    vp->ftarget);
+		snd_iprintf(buf, "start=%x, end=%x, loopstart=%x, loopend=%x\n",
+			    vp->reg.start, vp->reg.end, vp->reg.loopstart, vp->reg.loopend);
+		snd_iprintf(buf, "sample_mode=%x, rate=%x\n", vp->reg.sample_mode, vp->reg.rate_offset);
+	}
+#endif
+	mutex_unlock(&emu->register_mutex);
+}
+
+
+void snd_emux_proc_init(struct snd_emux *emu, struct snd_card *card, int device)
+{
+	struct snd_info_entry *entry;
+	char name[64];
+
+	sprintf(name, "wavetableD%d", device);
+	entry = snd_info_create_card_entry(card, name, card->proc_root);
+	if (entry == NULL)
+		return;
+
+	entry->content = SNDRV_INFO_CONTENT_TEXT;
+	entry->private_data = emu;
+	entry->c.text.read = snd_emux_proc_info_read;
+	if (snd_info_register(entry) < 0)
+		snd_info_free_entry(entry);
+	else
+		emu->proc = entry;
+}
+
+void snd_emux_proc_free(struct snd_emux *emu)
+{
+	snd_info_free_entry(emu->proc);
+	emu->proc = NULL;
+}
+
+#endif /* CONFIG_PROC_FS */
diff --git a/linux/sound/synth/emux/emux_seq.c b/linux/sound/synth/emux/emux_seq.c
new file mode 100644
index 0000000..ca5f7ef
--- /dev/null
+++ b/linux/sound/synth/emux/emux_seq.c
@@ -0,0 +1,403 @@
+/*
+ *  Midi Sequencer interface routines.
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "emux_voice.h"
+#include <linux/slab.h>
+
+
+/* Prototypes for static functions */
+static void free_port(void *private);
+static void snd_emux_init_port(struct snd_emux_port *p);
+static int snd_emux_use(void *private_data, struct snd_seq_port_subscribe *info);
+static int snd_emux_unuse(void *private_data, struct snd_seq_port_subscribe *info);
+
+/*
+ * MIDI emulation operators
+ */
+static struct snd_midi_op emux_ops = {
+	snd_emux_note_on,
+	snd_emux_note_off,
+	snd_emux_key_press,
+	snd_emux_terminate_note,
+	snd_emux_control,
+	snd_emux_nrpn,
+	snd_emux_sysex,
+};
+
+
+/*
+ * number of MIDI channels
+ */
+#define MIDI_CHANNELS		16
+
+/*
+ * type flags for MIDI sequencer port
+ */
+#define DEFAULT_MIDI_TYPE	(SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |\
+				 SNDRV_SEQ_PORT_TYPE_MIDI_GM |\
+				 SNDRV_SEQ_PORT_TYPE_MIDI_GS |\
+				 SNDRV_SEQ_PORT_TYPE_MIDI_XG |\
+				 SNDRV_SEQ_PORT_TYPE_HARDWARE |\
+				 SNDRV_SEQ_PORT_TYPE_SYNTHESIZER)
+
+/*
+ * Initialise the EMUX Synth by creating a client and registering
+ * a series of ports.
+ * Each of the ports will contain the 16 midi channels.  Applications
+ * can connect to these ports to play midi data.
+ */
+int
+snd_emux_init_seq(struct snd_emux *emu, struct snd_card *card, int index)
+{
+	int  i;
+	struct snd_seq_port_callback pinfo;
+	char tmpname[64];
+
+	emu->client = snd_seq_create_kernel_client(card, index,
+						   "%s WaveTable", emu->name);
+	if (emu->client < 0) {
+		snd_printk(KERN_ERR "can't create client\n");
+		return -ENODEV;
+	}
+
+	if (emu->num_ports < 0) {
+		snd_printk(KERN_WARNING "seqports must be greater than zero\n");
+		emu->num_ports = 1;
+	} else if (emu->num_ports >= SNDRV_EMUX_MAX_PORTS) {
+		snd_printk(KERN_WARNING "too many ports."
+			   "limited max. ports %d\n", SNDRV_EMUX_MAX_PORTS);
+		emu->num_ports = SNDRV_EMUX_MAX_PORTS;
+	}
+
+	memset(&pinfo, 0, sizeof(pinfo));
+	pinfo.owner = THIS_MODULE;
+	pinfo.use = snd_emux_use;
+	pinfo.unuse = snd_emux_unuse;
+	pinfo.event_input = snd_emux_event_input;
+
+	for (i = 0; i < emu->num_ports; i++) {
+		struct snd_emux_port *p;
+
+		sprintf(tmpname, "%s Port %d", emu->name, i);
+		p = snd_emux_create_port(emu, tmpname, MIDI_CHANNELS,
+					 0, &pinfo);
+		if (p == NULL) {
+			snd_printk(KERN_ERR "can't create port\n");
+			return -ENOMEM;
+		}
+
+		p->port_mode =  SNDRV_EMUX_PORT_MODE_MIDI;
+		snd_emux_init_port(p);
+		emu->ports[i] = p->chset.port;
+		emu->portptrs[i] = p;
+	}
+
+	return 0;
+}
+
+
+/*
+ * Detach from the ports that were set up for this synthesizer and
+ * destroy the kernel client.
+ */
+void
+snd_emux_detach_seq(struct snd_emux *emu)
+{
+	if (emu->voices)
+		snd_emux_terminate_all(emu);
+		
+	mutex_lock(&emu->register_mutex);
+	if (emu->client >= 0) {
+		snd_seq_delete_kernel_client(emu->client);
+		emu->client = -1;
+	}
+	mutex_unlock(&emu->register_mutex);
+}
+
+
+/*
+ * create a sequencer port and channel_set
+ */
+
+struct snd_emux_port *
+snd_emux_create_port(struct snd_emux *emu, char *name,
+		     int max_channels, int oss_port,
+		     struct snd_seq_port_callback *callback)
+{
+	struct snd_emux_port *p;
+	int i, type, cap;
+
+	/* Allocate structures for this channel */
+	if ((p = kzalloc(sizeof(*p), GFP_KERNEL)) == NULL) {
+		snd_printk(KERN_ERR "no memory\n");
+		return NULL;
+	}
+	p->chset.channels = kcalloc(max_channels, sizeof(struct snd_midi_channel), GFP_KERNEL);
+	if (p->chset.channels == NULL) {
+		snd_printk(KERN_ERR "no memory\n");
+		kfree(p);
+		return NULL;
+	}
+	for (i = 0; i < max_channels; i++)
+		p->chset.channels[i].number = i;
+	p->chset.private_data = p;
+	p->chset.max_channels = max_channels;
+	p->emu = emu;
+	p->chset.client = emu->client;
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+	snd_emux_create_effect(p);
+#endif
+	callback->private_free = free_port;
+	callback->private_data = p;
+
+	cap = SNDRV_SEQ_PORT_CAP_WRITE;
+	if (oss_port) {
+		type = SNDRV_SEQ_PORT_TYPE_SPECIFIC;
+	} else {
+		type = DEFAULT_MIDI_TYPE;
+		cap |= SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
+	}
+
+	p->chset.port = snd_seq_event_port_attach(emu->client, callback,
+						  cap, type, max_channels,
+						  emu->max_voices, name);
+
+	return p;
+}
+
+
+/*
+ * release memory block for port
+ */
+static void
+free_port(void *private_data)
+{
+	struct snd_emux_port *p;
+
+	p = private_data;
+	if (p) {
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+		snd_emux_delete_effect(p);
+#endif
+		kfree(p->chset.channels);
+		kfree(p);
+	}
+}
+
+
+#define DEFAULT_DRUM_FLAGS	(1<<9)
+
+/*
+ * initialize the port specific parameters
+ */
+static void
+snd_emux_init_port(struct snd_emux_port *p)
+{
+	p->drum_flags = DEFAULT_DRUM_FLAGS;
+	p->volume_atten = 0;
+
+	snd_emux_reset_port(p);
+}
+
+
+/*
+ * reset port
+ */
+void
+snd_emux_reset_port(struct snd_emux_port *port)
+{
+	int i;
+
+	/* stop all sounds */
+	snd_emux_sounds_off_all(port);
+
+	snd_midi_channel_set_clear(&port->chset);
+
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+	snd_emux_clear_effect(port);
+#endif
+
+	/* set port specific control parameters */
+	port->ctrls[EMUX_MD_DEF_BANK] = 0;
+	port->ctrls[EMUX_MD_DEF_DRUM] = 0;
+	port->ctrls[EMUX_MD_REALTIME_PAN] = 1;
+
+	for (i = 0; i < port->chset.max_channels; i++) {
+		struct snd_midi_channel *chan = port->chset.channels + i;
+		chan->drum_channel = ((port->drum_flags >> i) & 1) ? 1 : 0;
+	}
+}
+
+
+/*
+ * input sequencer event
+ */
+int
+snd_emux_event_input(struct snd_seq_event *ev, int direct, void *private_data,
+		     int atomic, int hop)
+{
+	struct snd_emux_port *port;
+
+	port = private_data;
+	if (snd_BUG_ON(!port || !ev))
+		return -EINVAL;
+
+	snd_midi_process_event(&emux_ops, ev, &port->chset);
+
+	return 0;
+}
+
+
+/*
+ * increment usage count
+ */
+int
+snd_emux_inc_count(struct snd_emux *emu)
+{
+	emu->used++;
+	if (!try_module_get(emu->ops.owner))
+		goto __error;
+	if (!try_module_get(emu->card->module)) {
+		module_put(emu->ops.owner);
+	      __error:
+		emu->used--;
+		return 0;
+	}
+	return 1;
+}
+
+
+/*
+ * decrease usage count
+ */
+void
+snd_emux_dec_count(struct snd_emux *emu)
+{
+	module_put(emu->card->module);
+	emu->used--;
+	if (emu->used <= 0)
+		snd_emux_terminate_all(emu);
+	module_put(emu->ops.owner);
+}
+
+
+/*
+ * Routine that is called upon a first use of a particular port
+ */
+static int
+snd_emux_use(void *private_data, struct snd_seq_port_subscribe *info)
+{
+	struct snd_emux_port *p;
+	struct snd_emux *emu;
+
+	p = private_data;
+	if (snd_BUG_ON(!p))
+		return -EINVAL;
+	emu = p->emu;
+	if (snd_BUG_ON(!emu))
+		return -EINVAL;
+
+	mutex_lock(&emu->register_mutex);
+	snd_emux_init_port(p);
+	snd_emux_inc_count(emu);
+	mutex_unlock(&emu->register_mutex);
+	return 0;
+}
+
+/*
+ * Routine that is called upon the last unuse() of a particular port.
+ */
+static int
+snd_emux_unuse(void *private_data, struct snd_seq_port_subscribe *info)
+{
+	struct snd_emux_port *p;
+	struct snd_emux *emu;
+
+	p = private_data;
+	if (snd_BUG_ON(!p))
+		return -EINVAL;
+	emu = p->emu;
+	if (snd_BUG_ON(!emu))
+		return -EINVAL;
+
+	mutex_lock(&emu->register_mutex);
+	snd_emux_sounds_off_all(p);
+	snd_emux_dec_count(emu);
+	mutex_unlock(&emu->register_mutex);
+	return 0;
+}
+
+
+/*
+ * attach virtual rawmidi devices
+ */
+int snd_emux_init_virmidi(struct snd_emux *emu, struct snd_card *card)
+{
+	int i;
+
+	emu->vmidi = NULL;
+	if (emu->midi_ports <= 0)
+		return 0;
+
+	emu->vmidi = kcalloc(emu->midi_ports, sizeof(struct snd_rawmidi *), GFP_KERNEL);
+	if (emu->vmidi == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < emu->midi_ports; i++) {
+		struct snd_rawmidi *rmidi;
+		struct snd_virmidi_dev *rdev;
+		if (snd_virmidi_new(card, emu->midi_devidx + i, &rmidi) < 0)
+			goto __error;
+		rdev = rmidi->private_data;
+		sprintf(rmidi->name, "%s Synth MIDI", emu->name);
+		rdev->seq_mode = SNDRV_VIRMIDI_SEQ_ATTACH;
+		rdev->client = emu->client;
+		rdev->port = emu->ports[i];
+		if (snd_device_register(card, rmidi) < 0) {
+			snd_device_free(card, rmidi);
+			goto __error;
+		}
+		emu->vmidi[i] = rmidi;
+		/* snd_printk(KERN_DEBUG "virmidi %d ok\n", i); */
+	}
+	return 0;
+
+__error:
+	/* snd_printk(KERN_DEBUG "error init..\n"); */
+	snd_emux_delete_virmidi(emu);
+	return -ENOMEM;
+}
+
+int snd_emux_delete_virmidi(struct snd_emux *emu)
+{
+	int i;
+
+	if (emu->vmidi == NULL)
+		return 0;
+
+	for (i = 0; i < emu->midi_ports; i++) {
+		if (emu->vmidi[i])
+			snd_device_free(emu->card, emu->vmidi[i]);
+	}
+	kfree(emu->vmidi);
+	emu->vmidi = NULL;
+	return 0;
+}
diff --git a/linux/sound/synth/emux/emux_synth.c b/linux/sound/synth/emux/emux_synth.c
new file mode 100644
index 0000000..3e921b3
--- /dev/null
+++ b/linux/sound/synth/emux/emux_synth.c
@@ -0,0 +1,983 @@
+/*
+ *  Midi synth routines for the Emu8k/Emu10k1
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Contains code 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
+ *
+ */
+
+#include "emux_voice.h"
+#include <sound/asoundef.h>
+
+/*
+ * Prototypes
+ */
+
+/*
+ * Ensure a value is between two points
+ * macro evaluates its args more than once, so changed to upper-case.
+ */
+#define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0)
+#define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0)
+
+static int get_zone(struct snd_emux *emu, struct snd_emux_port *port,
+		    int *notep, int vel, struct snd_midi_channel *chan,
+		    struct snd_sf_zone **table);
+static int get_bank(struct snd_emux_port *port, struct snd_midi_channel *chan);
+static void terminate_note1(struct snd_emux *emu, int note,
+			    struct snd_midi_channel *chan, int free);
+static void exclusive_note_off(struct snd_emux *emu, struct snd_emux_port *port,
+			       int exclass);
+static void terminate_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int free);
+static void update_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int update);
+static void setup_voice(struct snd_emux_voice *vp);
+static int calc_pan(struct snd_emux_voice *vp);
+static int calc_volume(struct snd_emux_voice *vp);
+static int calc_pitch(struct snd_emux_voice *vp);
+
+
+/*
+ * Start a note.
+ */
+void
+snd_emux_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
+{
+	struct snd_emux *emu;
+	int i, key, nvoices;
+	struct snd_emux_voice *vp;
+	struct snd_sf_zone *table[SNDRV_EMUX_MAX_MULTI_VOICES];
+	unsigned long flags;
+	struct snd_emux_port *port;
+
+	port = p;
+	if (snd_BUG_ON(!port || !chan))
+		return;
+
+	emu = port->emu;
+	if (snd_BUG_ON(!emu || !emu->ops.get_voice || !emu->ops.trigger))
+		return;
+
+	key = note; /* remember the original note */
+	nvoices = get_zone(emu, port, &note, vel, chan, table);
+	if (! nvoices)
+		return;
+
+	/* exclusive note off */
+	for (i = 0; i < nvoices; i++) {
+		struct snd_sf_zone *zp = table[i];
+		if (zp && zp->v.exclusiveClass)
+			exclusive_note_off(emu, port, zp->v.exclusiveClass);
+	}
+
+#if 0 // seems not necessary
+	/* Turn off the same note on the same channel. */
+	terminate_note1(emu, key, chan, 0);
+#endif
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (i = 0; i < nvoices; i++) {
+
+		/* set up each voice parameter */
+		/* at this stage, we don't trigger the voice yet. */
+
+		if (table[i] == NULL)
+			continue;
+
+		vp = emu->ops.get_voice(emu, port);
+		if (vp == NULL || vp->ch < 0)
+			continue;
+		if (STATE_IS_PLAYING(vp->state))
+			emu->ops.terminate(vp);
+
+		vp->time = emu->use_time++;
+		vp->chan = chan;
+		vp->port = port;
+		vp->key = key;
+		vp->note = note;
+		vp->velocity = vel;
+		vp->zone = table[i];
+		if (vp->zone->sample)
+			vp->block = vp->zone->sample->block;
+		else
+			vp->block = NULL;
+
+		setup_voice(vp);
+
+		vp->state = SNDRV_EMUX_ST_STANDBY;
+		if (emu->ops.prepare) {
+			vp->state = SNDRV_EMUX_ST_OFF;
+			if (emu->ops.prepare(vp) >= 0)
+				vp->state = SNDRV_EMUX_ST_STANDBY;
+		}
+	}
+
+	/* start envelope now */
+	for (i = 0; i < emu->max_voices; i++) {
+		vp = &emu->voices[i];
+		if (vp->state == SNDRV_EMUX_ST_STANDBY &&
+		    vp->chan == chan) {
+			emu->ops.trigger(vp);
+			vp->state = SNDRV_EMUX_ST_ON;
+			vp->ontime = jiffies; /* remember the trigger timing */
+		}
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+	if (port->port_mode == SNDRV_EMUX_PORT_MODE_OSS_SYNTH) {
+		/* clear voice position for the next note on this channel */
+		struct snd_emux_effect_table *fx = chan->private;
+		if (fx) {
+			fx->flag[EMUX_FX_SAMPLE_START] = 0;
+			fx->flag[EMUX_FX_COARSE_SAMPLE_START] = 0;
+		}
+	}
+#endif
+}
+
+/*
+ * Release a note in response to a midi note off.
+ */
+void
+snd_emux_note_off(void *p, int note, int vel, struct snd_midi_channel *chan)
+{
+	int ch;
+	struct snd_emux *emu;
+	struct snd_emux_voice *vp;
+	unsigned long flags;
+	struct snd_emux_port *port;
+
+	port = p;
+	if (snd_BUG_ON(!port || !chan))
+		return;
+
+	emu = port->emu;
+	if (snd_BUG_ON(!emu || !emu->ops.release))
+		return;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (ch = 0; ch < emu->max_voices; ch++) {
+		vp = &emu->voices[ch];
+		if (STATE_IS_PLAYING(vp->state) &&
+		    vp->chan == chan && vp->key == note) {
+			vp->state = SNDRV_EMUX_ST_RELEASED;
+			if (vp->ontime == jiffies) {
+				/* if note-off is sent too shortly after
+				 * note-on, emuX engine cannot produce the sound
+				 * correctly.  so we'll release this note
+				 * a bit later via timer callback.
+				 */
+				vp->state = SNDRV_EMUX_ST_PENDING;
+				if (! emu->timer_active) {
+					emu->tlist.expires = jiffies + 1;
+					add_timer(&emu->tlist);
+					emu->timer_active = 1;
+				}
+			} else
+				/* ok now release the note */
+				emu->ops.release(vp);
+		}
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+/*
+ * timer callback
+ *
+ * release the pending note-offs
+ */
+void snd_emux_timer_callback(unsigned long data)
+{
+	struct snd_emux *emu = (struct snd_emux *) data;
+	struct snd_emux_voice *vp;
+	unsigned long flags;
+	int ch, do_again = 0;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (ch = 0; ch < emu->max_voices; ch++) {
+		vp = &emu->voices[ch];
+		if (vp->state == SNDRV_EMUX_ST_PENDING) {
+			if (vp->ontime == jiffies)
+				do_again++; /* release this at the next interrupt */
+			else {
+				emu->ops.release(vp);
+				vp->state = SNDRV_EMUX_ST_RELEASED;
+			}
+		}
+	}
+	if (do_again) {
+		emu->tlist.expires = jiffies + 1;
+		add_timer(&emu->tlist);
+		emu->timer_active = 1;
+	} else
+		emu->timer_active = 0;
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+/*
+ * key pressure change
+ */
+void
+snd_emux_key_press(void *p, int note, int vel, struct snd_midi_channel *chan)
+{
+	int ch;
+	struct snd_emux *emu;
+	struct snd_emux_voice *vp;
+	unsigned long flags;
+	struct snd_emux_port *port;
+
+	port = p;
+	if (snd_BUG_ON(!port || !chan))
+		return;
+
+	emu = port->emu;
+	if (snd_BUG_ON(!emu || !emu->ops.update))
+		return;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (ch = 0; ch < emu->max_voices; ch++) {
+		vp = &emu->voices[ch];
+		if (vp->state == SNDRV_EMUX_ST_ON &&
+		    vp->chan == chan && vp->key == note) {
+			vp->velocity = vel;
+			update_voice(emu, vp, SNDRV_EMUX_UPDATE_VOLUME);
+		}
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+
+/*
+ * Modulate the voices which belong to the channel
+ */
+void
+snd_emux_update_channel(struct snd_emux_port *port, struct snd_midi_channel *chan, int update)
+{
+	struct snd_emux *emu;
+	struct snd_emux_voice *vp;
+	int i;
+	unsigned long flags;
+
+	if (! update)
+		return;
+
+	emu = port->emu;
+	if (snd_BUG_ON(!emu || !emu->ops.update))
+		return;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (i = 0; i < emu->max_voices; i++) {
+		vp = &emu->voices[i];
+		if (vp->chan == chan)
+			update_voice(emu, vp, update);
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+/*
+ * Modulate all the voices which belong to the port.
+ */
+void
+snd_emux_update_port(struct snd_emux_port *port, int update)
+{
+	struct snd_emux *emu; 
+	struct snd_emux_voice *vp;
+	int i;
+	unsigned long flags;
+
+	if (! update)
+		return;
+
+	emu = port->emu;
+	if (snd_BUG_ON(!emu || !emu->ops.update))
+		return;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (i = 0; i < emu->max_voices; i++) {
+		vp = &emu->voices[i];
+		if (vp->port == port)
+			update_voice(emu, vp, update);
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+
+/*
+ * Deal with a controller type event.  This includes all types of
+ * control events, not just the midi controllers
+ */
+void
+snd_emux_control(void *p, int type, struct snd_midi_channel *chan)
+{
+	struct snd_emux_port *port;
+
+	port = p;
+	if (snd_BUG_ON(!port || !chan))
+		return;
+
+	switch (type) {
+	case MIDI_CTL_MSB_MAIN_VOLUME:
+	case MIDI_CTL_MSB_EXPRESSION:
+		snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_VOLUME);
+		break;
+		
+	case MIDI_CTL_MSB_PAN:
+		snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PAN);
+		break;
+
+	case MIDI_CTL_SOFT_PEDAL:
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+		/* FIXME: this is an emulation */
+		if (chan->control[type] >= 64)
+			snd_emux_send_effect(port, chan, EMUX_FX_CUTOFF, -160,
+				     EMUX_FX_FLAG_ADD);
+		else
+			snd_emux_send_effect(port, chan, EMUX_FX_CUTOFF, 0,
+				     EMUX_FX_FLAG_OFF);
+#endif
+		break;
+
+	case MIDI_CTL_PITCHBEND:
+		snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PITCH);
+		break;
+
+	case MIDI_CTL_MSB_MODWHEEL:
+	case MIDI_CTL_CHAN_PRESSURE:
+		snd_emux_update_channel(port, chan,
+					SNDRV_EMUX_UPDATE_FMMOD |
+					SNDRV_EMUX_UPDATE_FM2FRQ2);
+		break;
+
+	}
+
+	if (port->chset.midi_mode == SNDRV_MIDI_MODE_XG) {
+		snd_emux_xg_control(port, chan, type);
+	}
+}
+
+
+/*
+ * terminate note - if free flag is true, free the terminated voice
+ */
+static void
+terminate_note1(struct snd_emux *emu, int note, struct snd_midi_channel *chan, int free)
+{
+	int  i;
+	struct snd_emux_voice *vp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (i = 0; i < emu->max_voices; i++) {
+		vp = &emu->voices[i];
+		if (STATE_IS_PLAYING(vp->state) && vp->chan == chan &&
+		    vp->key == note)
+			terminate_voice(emu, vp, free);
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+
+/*
+ * terminate note - exported for midi emulation
+ */
+void
+snd_emux_terminate_note(void *p, int note, struct snd_midi_channel *chan)
+{
+	struct snd_emux *emu;
+	struct snd_emux_port *port;
+
+	port = p;
+	if (snd_BUG_ON(!port || !chan))
+		return;
+
+	emu = port->emu;
+	if (snd_BUG_ON(!emu || !emu->ops.terminate))
+		return;
+
+	terminate_note1(emu, note, chan, 1);
+}
+
+
+/*
+ * Terminate all the notes
+ */
+void
+snd_emux_terminate_all(struct snd_emux *emu)
+{
+	int i;
+	struct snd_emux_voice *vp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (i = 0; i < emu->max_voices; i++) {
+		vp = &emu->voices[i];
+		if (STATE_IS_PLAYING(vp->state))
+			terminate_voice(emu, vp, 0);
+		if (vp->state == SNDRV_EMUX_ST_OFF) {
+			if (emu->ops.free_voice)
+				emu->ops.free_voice(vp);
+			if (emu->ops.reset)
+				emu->ops.reset(emu, i);
+		}
+		vp->time = 0;
+	}
+	/* initialize allocation time */
+	emu->use_time = 0;
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+EXPORT_SYMBOL(snd_emux_terminate_all);
+
+/*
+ * Terminate all voices associated with the given port
+ */
+void
+snd_emux_sounds_off_all(struct snd_emux_port *port)
+{
+	int i;
+	struct snd_emux *emu;
+	struct snd_emux_voice *vp;
+	unsigned long flags;
+
+	if (snd_BUG_ON(!port))
+		return;
+	emu = port->emu;
+	if (snd_BUG_ON(!emu || !emu->ops.terminate))
+		return;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (i = 0; i < emu->max_voices; i++) {
+		vp = &emu->voices[i];
+		if (STATE_IS_PLAYING(vp->state) &&
+		    vp->port == port)
+			terminate_voice(emu, vp, 0);
+		if (vp->state == SNDRV_EMUX_ST_OFF) {
+			if (emu->ops.free_voice)
+				emu->ops.free_voice(vp);
+			if (emu->ops.reset)
+				emu->ops.reset(emu, i);
+		}
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+
+/*
+ * Terminate all voices that have the same exclusive class.  This
+ * is mainly for drums.
+ */
+static void
+exclusive_note_off(struct snd_emux *emu, struct snd_emux_port *port, int exclass)
+{
+	struct snd_emux_voice *vp;
+	int  i;
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (i = 0; i < emu->max_voices; i++) {
+		vp = &emu->voices[i];
+		if (STATE_IS_PLAYING(vp->state) && vp->port == port &&
+		    vp->reg.exclusiveClass == exclass) {
+			terminate_voice(emu, vp, 0);
+		}
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+/*
+ * terminate a voice
+ * if free flag is true, call free_voice after termination
+ */
+static void
+terminate_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int free)
+{
+	emu->ops.terminate(vp);
+	vp->time = emu->use_time++;
+	vp->chan = NULL;
+	vp->port = NULL;
+	vp->zone = NULL;
+	vp->block = NULL;
+	vp->state = SNDRV_EMUX_ST_OFF;
+	if (free && emu->ops.free_voice)
+		emu->ops.free_voice(vp);
+}
+
+
+/*
+ * Modulate the voice
+ */
+static void
+update_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int update)
+{
+	if (!STATE_IS_PLAYING(vp->state))
+		return;
+
+	if (vp->chan == NULL || vp->port == NULL)
+		return;
+	if (update & SNDRV_EMUX_UPDATE_VOLUME)
+		calc_volume(vp);
+	if (update & SNDRV_EMUX_UPDATE_PITCH)
+		calc_pitch(vp);
+	if (update & SNDRV_EMUX_UPDATE_PAN) {
+		if (! calc_pan(vp) && (update == SNDRV_EMUX_UPDATE_PAN))
+			return;
+	}
+	emu->ops.update(vp, update);
+}
+
+
+#if 0 // not used
+/* table for volume target calculation */
+static unsigned short voltarget[16] = { 
+	0xEAC0, 0xE0C8, 0xD740, 0xCE20, 0xC560, 0xBD08, 0xB500, 0xAD58,
+	0xA5F8, 0x9EF0, 0x9830, 0x91C0, 0x8B90, 0x85A8, 0x8000, 0x7A90
+};
+#endif
+
+#define LO_BYTE(v)	((v) & 0xff)
+#define HI_BYTE(v)	(((v) >> 8) & 0xff)
+
+/*
+ * Sets up the voice structure by calculating some values that
+ * will be needed later.
+ */
+static void
+setup_voice(struct snd_emux_voice *vp)
+{
+	struct soundfont_voice_parm *parm;
+	int pitch;
+
+	/* copy the original register values */
+	vp->reg = vp->zone->v;
+
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+	snd_emux_setup_effect(vp);
+#endif
+
+	/* reset status */
+	vp->apan = -1;
+	vp->avol = -1;
+	vp->apitch = -1;
+
+	calc_volume(vp);
+	calc_pitch(vp);
+	calc_pan(vp);
+
+	parm = &vp->reg.parm;
+
+	/* compute filter target and correct modulation parameters */
+	if (LO_BYTE(parm->modatkhld) >= 0x80 && parm->moddelay >= 0x8000) {
+		parm->moddelay = 0xbfff;
+		pitch = (HI_BYTE(parm->pefe) << 4) + vp->apitch;
+		if (pitch > 0xffff)
+			pitch = 0xffff;
+		/* calculate filter target */
+		vp->ftarget = parm->cutoff + LO_BYTE(parm->pefe);
+		LIMITVALUE(vp->ftarget, 0, 255);
+		vp->ftarget <<= 8;
+	} else {
+		vp->ftarget = parm->cutoff;
+		vp->ftarget <<= 8;
+		pitch = vp->apitch;
+	}
+
+	/* compute pitch target */
+	if (pitch != 0xffff) {
+		vp->ptarget = 1 << (pitch >> 12);
+		if (pitch & 0x800) vp->ptarget += (vp->ptarget*0x102e)/0x2710;
+		if (pitch & 0x400) vp->ptarget += (vp->ptarget*0x764)/0x2710;
+		if (pitch & 0x200) vp->ptarget += (vp->ptarget*0x389)/0x2710;
+		vp->ptarget += (vp->ptarget >> 1);
+		if (vp->ptarget > 0xffff) vp->ptarget = 0xffff;
+	} else
+		vp->ptarget = 0xffff;
+
+	if (LO_BYTE(parm->modatkhld) >= 0x80) {
+		parm->modatkhld &= ~0xff;
+		parm->modatkhld |= 0x7f;
+	}
+
+	/* compute volume target and correct volume parameters */
+	vp->vtarget = 0;
+#if 0 /* FIXME: this leads to some clicks.. */
+	if (LO_BYTE(parm->volatkhld) >= 0x80 && parm->voldelay >= 0x8000) {
+		parm->voldelay = 0xbfff;
+		vp->vtarget = voltarget[vp->avol % 0x10] >> (vp->avol >> 4);
+	}
+#endif
+
+	if (LO_BYTE(parm->volatkhld) >= 0x80) {
+		parm->volatkhld &= ~0xff;
+		parm->volatkhld |= 0x7f;
+	}
+}
+
+/*
+ * calculate pitch parameter
+ */
+static unsigned char pan_volumes[256] = {
+0x00,0x03,0x06,0x09,0x0c,0x0f,0x12,0x14,0x17,0x1a,0x1d,0x20,0x22,0x25,0x28,0x2a,
+0x2d,0x30,0x32,0x35,0x37,0x3a,0x3c,0x3f,0x41,0x44,0x46,0x49,0x4b,0x4d,0x50,0x52,
+0x54,0x57,0x59,0x5b,0x5d,0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6f,0x71,0x73,0x75,
+0x77,0x79,0x7b,0x7c,0x7e,0x80,0x82,0x84,0x86,0x88,0x89,0x8b,0x8d,0x8f,0x90,0x92,
+0x94,0x96,0x97,0x99,0x9a,0x9c,0x9e,0x9f,0xa1,0xa2,0xa4,0xa5,0xa7,0xa8,0xaa,0xab,
+0xad,0xae,0xaf,0xb1,0xb2,0xb3,0xb5,0xb6,0xb7,0xb9,0xba,0xbb,0xbc,0xbe,0xbf,0xc0,
+0xc1,0xc2,0xc3,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,0xd0,0xd1,
+0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdc,0xdd,0xde,0xdf,
+0xdf,0xe0,0xe1,0xe2,0xe2,0xe3,0xe4,0xe4,0xe5,0xe6,0xe6,0xe7,0xe8,0xe8,0xe9,0xe9,
+0xea,0xeb,0xeb,0xec,0xec,0xed,0xed,0xee,0xee,0xef,0xef,0xf0,0xf0,0xf1,0xf1,0xf1,
+0xf2,0xf2,0xf3,0xf3,0xf3,0xf4,0xf4,0xf5,0xf5,0xf5,0xf6,0xf6,0xf6,0xf7,0xf7,0xf7,
+0xf7,0xf8,0xf8,0xf8,0xf9,0xf9,0xf9,0xf9,0xf9,0xfa,0xfa,0xfa,0xfa,0xfb,0xfb,0xfb,
+0xfb,0xfb,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfd,0xfd,0xfd,0xfd,0xfd,0xfd,0xfd,
+0xfd,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+};
+
+static int
+calc_pan(struct snd_emux_voice *vp)
+{
+	struct snd_midi_channel *chan = vp->chan;
+	int pan;
+
+	/* pan & loop start (pan 8bit, MSB, 0:right, 0xff:left) */
+	if (vp->reg.fixpan > 0)	/* 0-127 */
+		pan = 255 - (int)vp->reg.fixpan * 2;
+	else {
+		pan = chan->control[MIDI_CTL_MSB_PAN] - 64;
+		if (vp->reg.pan >= 0) /* 0-127 */
+			pan += vp->reg.pan - 64;
+		pan = 127 - (int)pan * 2;
+	}
+	LIMITVALUE(pan, 0, 255);
+
+	if (vp->emu->linear_panning) {
+		/* assuming linear volume */
+		if (pan != vp->apan) {
+			vp->apan = pan;
+			if (pan == 0)
+				vp->aaux = 0xff;
+			else
+				vp->aaux = (-pan) & 0xff;
+			return 1;
+		} else
+			return 0;
+	} else {
+		/* using volume table */
+		if (vp->apan != (int)pan_volumes[pan]) {
+			vp->apan = pan_volumes[pan];
+			vp->aaux = pan_volumes[255 - pan];
+			return 1;
+		}
+		return 0;
+	}
+}
+
+
+/*
+ * calculate volume attenuation
+ *
+ * Voice volume is controlled by volume attenuation parameter.
+ * So volume becomes maximum when avol is 0 (no attenuation), and
+ * minimum when 255 (-96dB or silence).
+ */
+
+/* tables for volume->attenuation calculation */
+static unsigned char voltab1[128] = {
+   0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+   0x63, 0x2b, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22,
+   0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a,
+   0x19, 0x19, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14,
+   0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10,
+   0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0e, 0x0d,
+   0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b,
+   0x0b, 0x0a, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09,
+   0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06,
+   0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04,
+   0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02,
+   0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01,
+   0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static unsigned char voltab2[128] = {
+   0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x2a,
+   0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x24, 0x23, 0x22, 0x21,
+   0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1c, 0x1b, 0x1a,
+   0x1a, 0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15, 0x15,
+   0x14, 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x10,
+   0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d,
+   0x0d, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0b, 0x0a, 0x0a,
+   0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08,
+   0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06,
+   0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+   0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03,
+   0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01,
+   0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static unsigned char expressiontab[128] = {
+   0x7f, 0x6c, 0x62, 0x5a, 0x54, 0x50, 0x4b, 0x48, 0x45, 0x42,
+   0x40, 0x3d, 0x3b, 0x39, 0x38, 0x36, 0x34, 0x33, 0x31, 0x30,
+   0x2f, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25,
+   0x24, 0x24, 0x23, 0x22, 0x21, 0x21, 0x20, 0x1f, 0x1e, 0x1e,
+   0x1d, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a, 0x1a, 0x19, 0x18, 0x18,
+   0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x15, 0x14, 0x14, 0x13,
+   0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10, 0x10, 0x0f, 0x0f,
+   0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c,
+   0x0b, 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09,
+   0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06,
+   0x06, 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03,
+   0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01,
+   0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/*
+ * Magic to calculate the volume (actually attenuation) from all the
+ * voice and channels parameters.
+ */
+static int
+calc_volume(struct snd_emux_voice *vp)
+{
+	int vol;
+	int main_vol, expression_vol, master_vol;
+	struct snd_midi_channel *chan = vp->chan;
+	struct snd_emux_port *port = vp->port;
+
+	expression_vol = chan->control[MIDI_CTL_MSB_EXPRESSION];
+	LIMITMAX(vp->velocity, 127);
+	LIMITVALUE(expression_vol, 0, 127);
+	if (port->port_mode == SNDRV_EMUX_PORT_MODE_OSS_SYNTH) {
+		/* 0 - 127 */
+		main_vol = chan->control[MIDI_CTL_MSB_MAIN_VOLUME];
+		vol = (vp->velocity * main_vol * expression_vol) / (127*127);
+		vol = vol * vp->reg.amplitude / 127;
+
+		LIMITVALUE(vol, 0, 127);
+
+		/* calc to attenuation */
+		vol = snd_sf_vol_table[vol];
+
+	} else {
+		main_vol = chan->control[MIDI_CTL_MSB_MAIN_VOLUME] * vp->reg.amplitude / 127;
+		LIMITVALUE(main_vol, 0, 127);
+
+		vol = voltab1[main_vol] + voltab2[vp->velocity];
+		vol = (vol * 8) / 3;
+		vol += vp->reg.attenuation;
+		vol += ((0x100 - vol) * expressiontab[expression_vol])/128;
+	}
+
+	master_vol = port->chset.gs_master_volume;
+	LIMITVALUE(master_vol, 0, 127);
+	vol += snd_sf_vol_table[master_vol];
+	vol += port->volume_atten;
+
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+	if (chan->private) {
+		struct snd_emux_effect_table *fx = chan->private;
+		vol += fx->val[EMUX_FX_ATTEN];
+	}
+#endif
+
+	LIMITVALUE(vol, 0, 255);
+	if (vp->avol == vol)
+		return 0; /* value unchanged */
+
+	vp->avol = vol;
+	if (!SF_IS_DRUM_BANK(get_bank(port, chan))
+	    && LO_BYTE(vp->reg.parm.volatkhld) < 0x7d) {
+		int atten;
+		if (vp->velocity < 70)
+			atten = 70;
+		else
+			atten = vp->velocity;
+		vp->acutoff = (atten * vp->reg.parm.cutoff + 0xa0) >> 7;
+	} else {
+		vp->acutoff = vp->reg.parm.cutoff;
+	}
+
+	return 1; /* value changed */
+}
+
+/*
+ * calculate pitch offset
+ *
+ * 0xE000 is no pitch offset at 44100Hz sample.
+ * Every 4096 is one octave.
+ */
+
+static int
+calc_pitch(struct snd_emux_voice *vp)
+{
+	struct snd_midi_channel *chan = vp->chan;
+	int offset;
+
+	/* calculate offset */
+	if (vp->reg.fixkey >= 0) {
+		offset = (vp->reg.fixkey - vp->reg.root) * 4096 / 12;
+	} else {
+		offset = (vp->note - vp->reg.root) * 4096 / 12;
+	}
+	offset = (offset * vp->reg.scaleTuning) / 100;
+	offset += vp->reg.tune * 4096 / 1200;
+	if (chan->midi_pitchbend != 0) {
+		/* (128 * 8192: 1 semitone) ==> (4096: 12 semitones) */
+		offset += chan->midi_pitchbend * chan->gm_rpn_pitch_bend_range / 3072;
+	}
+
+	/* tuning via RPN:
+	 *   coarse = -8192 to 8192 (100 cent per 128)
+	 *   fine = -8192 to 8192 (max=100cent)
+	 */
+	/* 4096 = 1200 cents in emu8000 parameter */
+	offset += chan->gm_rpn_coarse_tuning * 4096 / (12 * 128);
+	offset += chan->gm_rpn_fine_tuning / 24;
+
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+	/* add initial pitch correction */
+	if (chan->private) {
+		struct snd_emux_effect_table *fx = chan->private;
+		if (fx->flag[EMUX_FX_INIT_PITCH])
+			offset += fx->val[EMUX_FX_INIT_PITCH];
+	}
+#endif
+
+	/* 0xe000: root pitch */
+	offset += 0xe000 + vp->reg.rate_offset;
+	offset += vp->emu->pitch_shift;
+	LIMITVALUE(offset, 0, 0xffff);
+	if (offset == vp->apitch)
+		return 0; /* unchanged */
+	vp->apitch = offset;
+	return 1; /* value changed */
+}
+
+/*
+ * Get the bank number assigned to the channel
+ */
+static int
+get_bank(struct snd_emux_port *port, struct snd_midi_channel *chan)
+{
+	int val;
+
+	switch (port->chset.midi_mode) {
+	case SNDRV_MIDI_MODE_XG:
+		val = chan->control[MIDI_CTL_MSB_BANK];
+		if (val == 127)
+			return 128; /* return drum bank */
+		return chan->control[MIDI_CTL_LSB_BANK];
+
+	case SNDRV_MIDI_MODE_GS:
+		if (chan->drum_channel)
+			return 128;
+		/* ignore LSB (bank map) */
+		return chan->control[MIDI_CTL_MSB_BANK];
+		
+	default:
+		if (chan->drum_channel)
+			return 128;
+		return chan->control[MIDI_CTL_MSB_BANK];
+	}
+}
+
+
+/* Look for the zones matching with the given note and velocity.
+ * The resultant zones are stored on table.
+ */
+static int
+get_zone(struct snd_emux *emu, struct snd_emux_port *port,
+	 int *notep, int vel, struct snd_midi_channel *chan,
+	 struct snd_sf_zone **table)
+{
+	int preset, bank, def_preset, def_bank;
+
+	bank = get_bank(port, chan);
+	preset = chan->midi_program;
+
+	if (SF_IS_DRUM_BANK(bank)) {
+		def_preset = port->ctrls[EMUX_MD_DEF_DRUM];
+		def_bank = bank;
+	} else {
+		def_preset = preset;
+		def_bank = port->ctrls[EMUX_MD_DEF_BANK];
+	}
+
+	return snd_soundfont_search_zone(emu->sflist, notep, vel, preset, bank,
+					 def_preset, def_bank,
+					 table, SNDRV_EMUX_MAX_MULTI_VOICES);
+}
+
+/*
+ */
+void
+snd_emux_init_voices(struct snd_emux *emu)
+{
+	struct snd_emux_voice *vp;
+	int i;
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	for (i = 0; i < emu->max_voices; i++) {
+		vp = &emu->voices[i];
+		vp->ch = -1; /* not used */
+		vp->state = SNDRV_EMUX_ST_OFF;
+		vp->chan = NULL;
+		vp->port = NULL;
+		vp->time = 0;
+		vp->emu = emu;
+		vp->hw = emu->hw;
+	}
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+/*
+ */
+void snd_emux_lock_voice(struct snd_emux *emu, int voice)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	if (emu->voices[voice].state == SNDRV_EMUX_ST_OFF)
+		emu->voices[voice].state = SNDRV_EMUX_ST_LOCKED;
+	else
+		snd_printk(KERN_WARNING
+			   "invalid voice for lock %d (state = %x)\n",
+			   voice, emu->voices[voice].state);
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+EXPORT_SYMBOL(snd_emux_lock_voice);
+
+/*
+ */
+void snd_emux_unlock_voice(struct snd_emux *emu, int voice)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&emu->voice_lock, flags);
+	if (emu->voices[voice].state == SNDRV_EMUX_ST_LOCKED)
+		emu->voices[voice].state = SNDRV_EMUX_ST_OFF;
+	else
+		snd_printk(KERN_WARNING
+			   "invalid voice for unlock %d (state = %x)\n",
+			   voice, emu->voices[voice].state);
+	spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+EXPORT_SYMBOL(snd_emux_unlock_voice);
diff --git a/linux/sound/synth/emux/emux_voice.h b/linux/sound/synth/emux/emux_voice.h
new file mode 100644
index 0000000..09711f8
--- /dev/null
+++ b/linux/sound/synth/emux/emux_voice.h
@@ -0,0 +1,96 @@
+#ifndef __EMUX_VOICE_H
+#define __EMUX_VOICE_H
+
+/*
+ * A structure to keep track of each hardware voice
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <sound/core.h>
+#include <sound/emux_synth.h>
+
+/* Prototypes for emux_seq.c */
+int snd_emux_init_seq(struct snd_emux *emu, struct snd_card *card, int index);
+void snd_emux_detach_seq(struct snd_emux *emu);
+struct snd_emux_port *snd_emux_create_port(struct snd_emux *emu, char *name,
+					   int max_channels, int type,
+					   struct snd_seq_port_callback *callback);
+void snd_emux_reset_port(struct snd_emux_port *port);
+int snd_emux_event_input(struct snd_seq_event *ev, int direct, void *private,
+			 int atomic, int hop);
+int snd_emux_inc_count(struct snd_emux *emu);
+void snd_emux_dec_count(struct snd_emux *emu);
+int snd_emux_init_virmidi(struct snd_emux *emu, struct snd_card *card);
+int snd_emux_delete_virmidi(struct snd_emux *emu);
+
+/* Prototypes for emux_synth.c */
+void snd_emux_init_voices(struct snd_emux *emu);
+
+void snd_emux_note_on(void *p, int note, int vel, struct snd_midi_channel *chan);
+void snd_emux_note_off(void *p, int note, int vel, struct snd_midi_channel *chan);
+void snd_emux_key_press(void *p, int note, int vel, struct snd_midi_channel *chan);
+void snd_emux_terminate_note(void *p, int note, struct snd_midi_channel *chan);
+void snd_emux_control(void *p, int type, struct snd_midi_channel *chan);
+
+void snd_emux_sounds_off_all(struct snd_emux_port *port);
+void snd_emux_update_channel(struct snd_emux_port *port,
+			     struct snd_midi_channel *chan, int update);
+void snd_emux_update_port(struct snd_emux_port *port, int update);
+
+void snd_emux_timer_callback(unsigned long data);
+
+/* emux_effect.c */
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+void snd_emux_create_effect(struct snd_emux_port *p);
+void snd_emux_delete_effect(struct snd_emux_port *p);
+void snd_emux_clear_effect(struct snd_emux_port *p);
+void snd_emux_setup_effect(struct snd_emux_voice *vp);
+void snd_emux_send_effect_oss(struct snd_emux_port *port,
+			      struct snd_midi_channel *chan, int type, int val);
+void snd_emux_send_effect(struct snd_emux_port *port,
+			  struct snd_midi_channel *chan, int type, int val, int mode);
+#endif
+
+/* emux_nrpn.c */
+void snd_emux_sysex(void *private_data, unsigned char *buf, int len,
+		    int parsed, struct snd_midi_channel_set *chset);
+int snd_emux_xg_control(struct snd_emux_port *port,
+			struct snd_midi_channel *chan, int param);
+void snd_emux_nrpn(void *private_data, struct snd_midi_channel *chan,
+		   struct snd_midi_channel_set *chset);
+
+/* emux_oss.c */
+void snd_emux_init_seq_oss(struct snd_emux *emu);
+void snd_emux_detach_seq_oss(struct snd_emux *emu);
+
+/* emux_proc.c */
+#ifdef CONFIG_PROC_FS
+void snd_emux_proc_init(struct snd_emux *emu, struct snd_card *card, int device);
+void snd_emux_proc_free(struct snd_emux *emu);
+#endif
+
+#define STATE_IS_PLAYING(s) ((s) & SNDRV_EMUX_ST_ON)
+
+/* emux_hwdep.c */
+int snd_emux_init_hwdep(struct snd_emux *emu);
+void snd_emux_delete_hwdep(struct snd_emux *emu);
+
+#endif
diff --git a/linux/sound/synth/emux/soundfont.c b/linux/sound/synth/emux/soundfont.c
new file mode 100644
index 0000000..67c9123
--- /dev/null
+++ b/linux/sound/synth/emux/soundfont.c
@@ -0,0 +1,1495 @@
+/*
+ *  Soundfont generic routines.
+ *	It is intended that these should be used by any driver that is willing
+ *	to accept soundfont patches.
+ *
+ *  Copyright (C) 1999 Steve Ratcliffe
+ *  Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+/*
+ * Deal with reading in of a soundfont.  Code follows the OSS way
+ * of doing things so that the old sfxload utility can be used.
+ * Everything may change when there is an alsa way of doing things.
+ */
+#include <asm/uaccess.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soundfont.h>
+#include <sound/seq_oss_legacy.h>
+
+/* Prototypes for static functions */
+
+static int open_patch(struct snd_sf_list *sflist, const char __user *data,
+		      int count, int client);
+static struct snd_soundfont *newsf(struct snd_sf_list *sflist, int type, char *name);
+static int is_identical_font(struct snd_soundfont *sf, int type, unsigned char *name);
+static int close_patch(struct snd_sf_list *sflist);
+static int probe_data(struct snd_sf_list *sflist, int sample_id);
+static void set_zone_counter(struct snd_sf_list *sflist,
+			     struct snd_soundfont *sf, struct snd_sf_zone *zp);
+static struct snd_sf_zone *sf_zone_new(struct snd_sf_list *sflist,
+				       struct snd_soundfont *sf);
+static void set_sample_counter(struct snd_sf_list *sflist,
+			       struct snd_soundfont *sf, struct snd_sf_sample *sp);
+static struct snd_sf_sample *sf_sample_new(struct snd_sf_list *sflist,
+					   struct snd_soundfont *sf);
+static void sf_sample_delete(struct snd_sf_list *sflist,
+			     struct snd_soundfont *sf, struct snd_sf_sample *sp);
+static int load_map(struct snd_sf_list *sflist, const void __user *data, int count);
+static int load_info(struct snd_sf_list *sflist, const void __user *data, long count);
+static int remove_info(struct snd_sf_list *sflist, struct snd_soundfont *sf,
+		       int bank, int instr);
+static void init_voice_info(struct soundfont_voice_info *avp);
+static void init_voice_parm(struct soundfont_voice_parm *pp);
+static struct snd_sf_sample *set_sample(struct snd_soundfont *sf,
+					struct soundfont_voice_info *avp);
+static struct snd_sf_sample *find_sample(struct snd_soundfont *sf, int sample_id);
+static int load_data(struct snd_sf_list *sflist, const void __user *data, long count);
+static void rebuild_presets(struct snd_sf_list *sflist);
+static void add_preset(struct snd_sf_list *sflist, struct snd_sf_zone *cur);
+static void delete_preset(struct snd_sf_list *sflist, struct snd_sf_zone *zp);
+static struct snd_sf_zone *search_first_zone(struct snd_sf_list *sflist,
+					     int bank, int preset, int key);
+static int search_zones(struct snd_sf_list *sflist, int *notep, int vel,
+			int preset, int bank, struct snd_sf_zone **table,
+			int max_layers, int level);
+static int get_index(int bank, int instr, int key);
+static void snd_sf_init(struct snd_sf_list *sflist);
+static void snd_sf_clear(struct snd_sf_list *sflist);
+
+/*
+ * lock access to sflist
+ */
+static void
+lock_preset(struct snd_sf_list *sflist)
+{
+	unsigned long flags;
+	mutex_lock(&sflist->presets_mutex);
+	spin_lock_irqsave(&sflist->lock, flags);
+	sflist->presets_locked = 1;
+	spin_unlock_irqrestore(&sflist->lock, flags);
+}
+
+
+/*
+ * remove lock
+ */
+static void
+unlock_preset(struct snd_sf_list *sflist)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&sflist->lock, flags);
+	sflist->presets_locked = 0;
+	spin_unlock_irqrestore(&sflist->lock, flags);
+	mutex_unlock(&sflist->presets_mutex);
+}
+
+
+/*
+ * close the patch if the patch was opened by this client.
+ */
+int
+snd_soundfont_close_check(struct snd_sf_list *sflist, int client)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&sflist->lock, flags);
+	if (sflist->open_client == client)  {
+		spin_unlock_irqrestore(&sflist->lock, flags);
+		return close_patch(sflist);
+	}
+	spin_unlock_irqrestore(&sflist->lock, flags);
+	return 0;
+}
+
+
+/*
+ * Deal with a soundfont patch.  Any driver could use these routines
+ * although it was designed for the AWE64.
+ *
+ * The sample_write and callargs pararameters allow a callback into
+ * the actual driver to write sample data to the board or whatever
+ * it wants to do with it.
+ */
+int
+snd_soundfont_load(struct snd_sf_list *sflist, const void __user *data,
+		   long count, int client)
+{
+	struct soundfont_patch_info patch;
+	unsigned long flags;
+	int  rc;
+
+	if (count < (long)sizeof(patch)) {
+		snd_printk(KERN_ERR "patch record too small %ld\n", count);
+		return -EINVAL;
+	}
+	if (copy_from_user(&patch, data, sizeof(patch)))
+		return -EFAULT;
+
+	count -= sizeof(patch);
+	data += sizeof(patch);
+
+	if (patch.key != SNDRV_OSS_SOUNDFONT_PATCH) {
+		snd_printk(KERN_ERR "The wrong kind of patch %x\n", patch.key);
+		return -EINVAL;
+	}
+	if (count < patch.len) {
+		snd_printk(KERN_ERR "Patch too short %ld, need %d\n",
+			   count, patch.len);
+		return -EINVAL;
+	}
+	if (patch.len < 0) {
+		snd_printk(KERN_ERR "poor length %d\n", patch.len);
+		return -EINVAL;
+	}
+
+	if (patch.type == SNDRV_SFNT_OPEN_PATCH) {
+		/* grab sflist to open */
+		lock_preset(sflist);
+		rc = open_patch(sflist, data, count, client);
+		unlock_preset(sflist);
+		return rc;
+	}
+
+	/* check if other client already opened patch */
+	spin_lock_irqsave(&sflist->lock, flags);
+	if (sflist->open_client != client) {
+		spin_unlock_irqrestore(&sflist->lock, flags);
+		return -EBUSY;
+	}
+	spin_unlock_irqrestore(&sflist->lock, flags);
+
+	lock_preset(sflist);
+	rc = -EINVAL;
+	switch (patch.type) {
+	case SNDRV_SFNT_LOAD_INFO:
+		rc = load_info(sflist, data, count);
+		break;
+	case SNDRV_SFNT_LOAD_DATA:
+		rc = load_data(sflist, data, count);
+		break;
+	case SNDRV_SFNT_CLOSE_PATCH:
+		rc = close_patch(sflist);
+		break;
+	case SNDRV_SFNT_REPLACE_DATA:
+		/*rc = replace_data(&patch, data, count);*/
+		break;
+	case SNDRV_SFNT_MAP_PRESET:
+		rc = load_map(sflist, data, count);
+		break;
+	case SNDRV_SFNT_PROBE_DATA:
+		rc = probe_data(sflist, patch.optarg);
+		break;
+	case SNDRV_SFNT_REMOVE_INFO:
+		/* patch must be opened */
+		if (!sflist->currsf) {
+			snd_printk(KERN_ERR "soundfont: remove_info: "
+				   "patch not opened\n");
+			rc = -EINVAL;
+		} else {
+			int bank, instr;
+			bank = ((unsigned short)patch.optarg >> 8) & 0xff;
+			instr = (unsigned short)patch.optarg & 0xff;
+			if (! remove_info(sflist, sflist->currsf, bank, instr))
+				rc = -EINVAL;
+			else
+				rc = 0;
+		}
+		break;
+	}
+	unlock_preset(sflist);
+
+	return rc;
+}
+
+
+/* check if specified type is special font (GUS or preset-alias) */
+static inline int
+is_special_type(int type)
+{
+	type &= 0x0f;
+	return (type == SNDRV_SFNT_PAT_TYPE_GUS ||
+		type == SNDRV_SFNT_PAT_TYPE_MAP);
+}
+
+
+/* open patch; create sf list */
+static int
+open_patch(struct snd_sf_list *sflist, const char __user *data,
+	   int count, int client)
+{
+	struct soundfont_open_parm parm;
+	struct snd_soundfont *sf;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sflist->lock, flags);
+	if (sflist->open_client >= 0 || sflist->currsf) {
+		spin_unlock_irqrestore(&sflist->lock, flags);
+		return -EBUSY;
+	}
+	spin_unlock_irqrestore(&sflist->lock, flags);
+
+	if (copy_from_user(&parm, data, sizeof(parm)))
+		return -EFAULT;
+
+	if (is_special_type(parm.type)) {
+		parm.type |= SNDRV_SFNT_PAT_SHARED;
+		sf = newsf(sflist, parm.type, NULL);
+	} else 
+		sf = newsf(sflist, parm.type, parm.name);
+	if (sf == NULL) {
+		return -ENOMEM;
+	}
+
+	spin_lock_irqsave(&sflist->lock, flags);
+	sflist->open_client = client;
+	sflist->currsf = sf;
+	spin_unlock_irqrestore(&sflist->lock, flags);
+
+	return 0;
+}
+
+/*
+ * Allocate a new soundfont structure.
+ */
+static struct snd_soundfont *
+newsf(struct snd_sf_list *sflist, int type, char *name)
+{
+	struct snd_soundfont *sf;
+
+	/* check the shared fonts */
+	if (type & SNDRV_SFNT_PAT_SHARED) {
+		for (sf = sflist->fonts; sf; sf = sf->next) {
+			if (is_identical_font(sf, type, name)) {
+				return sf;
+			}
+		}
+	}
+
+	/* not found -- create a new one */
+	sf = kzalloc(sizeof(*sf), GFP_KERNEL);
+	if (sf == NULL)
+		return NULL;
+	sf->id = sflist->fonts_size;
+	sflist->fonts_size++;
+
+	/* prepend this record */
+	sf->next = sflist->fonts;
+	sflist->fonts = sf;
+
+	sf->type = type;
+	sf->zones = NULL;
+	sf->samples = NULL;
+	if (name)
+		memcpy(sf->name, name, SNDRV_SFNT_PATCH_NAME_LEN);
+
+	return sf;
+}
+
+/* check if the given name matches to the existing list */
+static int
+is_identical_font(struct snd_soundfont *sf, int type, unsigned char *name)
+{
+	return ((sf->type & SNDRV_SFNT_PAT_SHARED) &&
+		(sf->type & 0x0f) == (type & 0x0f) &&
+		(name == NULL ||
+		 memcmp(sf->name, name, SNDRV_SFNT_PATCH_NAME_LEN) == 0));
+}
+
+/*
+ * Close the current patch.
+ */
+static int
+close_patch(struct snd_sf_list *sflist)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&sflist->lock, flags);
+	sflist->currsf = NULL;
+	sflist->open_client = -1;
+	spin_unlock_irqrestore(&sflist->lock, flags);
+
+	rebuild_presets(sflist);
+
+	return 0;
+
+}
+
+/* probe sample in the current list -- nothing to be loaded */
+static int
+probe_data(struct snd_sf_list *sflist, int sample_id)
+{
+	/* patch must be opened */
+	if (sflist->currsf) {
+		/* search the specified sample by optarg */
+		if (find_sample(sflist->currsf, sample_id))
+			return 0;
+	}
+	return -EINVAL;
+}
+
+/*
+ * increment zone counter
+ */
+static void
+set_zone_counter(struct snd_sf_list *sflist, struct snd_soundfont *sf,
+		 struct snd_sf_zone *zp)
+{
+	zp->counter = sflist->zone_counter++;
+	if (sf->type & SNDRV_SFNT_PAT_LOCKED)
+		sflist->zone_locked = sflist->zone_counter;
+}
+
+/*
+ * allocate a new zone record
+ */
+static struct snd_sf_zone *
+sf_zone_new(struct snd_sf_list *sflist, struct snd_soundfont *sf)
+{
+	struct snd_sf_zone *zp;
+
+	if ((zp = kzalloc(sizeof(*zp), GFP_KERNEL)) == NULL)
+		return NULL;
+	zp->next = sf->zones;
+	sf->zones = zp;
+
+	init_voice_info(&zp->v);
+
+	set_zone_counter(sflist, sf, zp);
+	return zp;
+}
+
+
+/*
+ * increment sample counter
+ */
+static void
+set_sample_counter(struct snd_sf_list *sflist, struct snd_soundfont *sf,
+		   struct snd_sf_sample *sp)
+{
+	sp->counter = sflist->sample_counter++;
+	if (sf->type & SNDRV_SFNT_PAT_LOCKED)
+		sflist->sample_locked = sflist->sample_counter;
+}
+
+/*
+ * allocate a new sample list record
+ */
+static struct snd_sf_sample *
+sf_sample_new(struct snd_sf_list *sflist, struct snd_soundfont *sf)
+{
+	struct snd_sf_sample *sp;
+
+	if ((sp = kzalloc(sizeof(*sp), GFP_KERNEL)) == NULL)
+		return NULL;
+
+	sp->next = sf->samples;
+	sf->samples = sp;
+
+	set_sample_counter(sflist, sf, sp);
+	return sp;
+}
+
+/*
+ * delete sample list -- this is an exceptional job.
+ * only the last allocated sample can be deleted.
+ */
+static void
+sf_sample_delete(struct snd_sf_list *sflist, struct snd_soundfont *sf,
+		 struct snd_sf_sample *sp)
+{
+	/* only last sample is accepted */
+	if (sp == sf->samples) {
+		sf->samples = sp->next;
+		kfree(sp);
+	}
+}
+
+
+/* load voice map */
+static int
+load_map(struct snd_sf_list *sflist, const void __user *data, int count)
+{
+	struct snd_sf_zone *zp, *prevp;
+	struct snd_soundfont *sf;
+	struct soundfont_voice_map map;
+
+	/* get the link info */
+	if (count < (int)sizeof(map))
+		return -EINVAL;
+	if (copy_from_user(&map, data, sizeof(map)))
+		return -EFAULT;
+
+	if (map.map_instr < 0 || map.map_instr >= SF_MAX_INSTRUMENTS)
+		return -EINVAL;
+	
+	sf = newsf(sflist, SNDRV_SFNT_PAT_TYPE_MAP|SNDRV_SFNT_PAT_SHARED, NULL);
+	if (sf == NULL)
+		return -ENOMEM;
+
+	prevp = NULL;
+	for (zp = sf->zones; zp; prevp = zp, zp = zp->next) {
+		if (zp->mapped &&
+		    zp->instr == map.map_instr &&
+		    zp->bank == map.map_bank &&
+		    zp->v.low == map.map_key &&
+		    zp->v.start == map.src_instr &&
+		    zp->v.end == map.src_bank &&
+		    zp->v.fixkey == map.src_key) {
+			/* the same mapping is already present */
+			/* relink this record to the link head */
+			if (prevp) {
+				prevp->next = zp->next;
+				zp->next = sf->zones;
+				sf->zones = zp;
+			}
+			/* update the counter */
+			set_zone_counter(sflist, sf, zp);
+			return 0;
+		}
+	}
+
+	/* create a new zone */
+	if ((zp = sf_zone_new(sflist, sf)) == NULL)
+		return -ENOMEM;
+
+	zp->bank = map.map_bank;
+	zp->instr = map.map_instr;
+	zp->mapped = 1;
+	if (map.map_key >= 0) {
+		zp->v.low = map.map_key;
+		zp->v.high = map.map_key;
+	}
+	zp->v.start = map.src_instr;
+	zp->v.end = map.src_bank;
+	zp->v.fixkey = map.src_key;
+	zp->v.sf_id = sf->id;
+
+	add_preset(sflist, zp);
+
+	return 0;
+}
+
+
+/* remove the present instrument layers */
+static int
+remove_info(struct snd_sf_list *sflist, struct snd_soundfont *sf,
+	    int bank, int instr)
+{
+	struct snd_sf_zone *prev, *next, *p;
+	int removed = 0;
+
+	prev = NULL;
+	for (p = sf->zones; p; p = next) {
+		next = p->next;
+		if (! p->mapped &&
+		    p->bank == bank && p->instr == instr) {
+			/* remove this layer */
+			if (prev)
+				prev->next = next;
+			else
+				sf->zones = next;
+			removed++;
+			kfree(p);
+		} else
+			prev = p;
+	}
+	if (removed)
+		rebuild_presets(sflist);
+	return removed;
+}
+
+
+/*
+ * Read an info record from the user buffer and save it on the current
+ * open soundfont.
+ */
+static int
+load_info(struct snd_sf_list *sflist, const void __user *data, long count)
+{
+	struct snd_soundfont *sf;
+	struct snd_sf_zone *zone;
+	struct soundfont_voice_rec_hdr hdr;
+	int i;
+
+	/* patch must be opened */
+	if ((sf = sflist->currsf) == NULL)
+		return -EINVAL;
+
+	if (is_special_type(sf->type))
+		return -EINVAL;
+
+	if (count < (long)sizeof(hdr)) {
+		printk(KERN_ERR "Soundfont error: invalid patch zone length\n");
+		return -EINVAL;
+	}
+	if (copy_from_user((char*)&hdr, data, sizeof(hdr)))
+		return -EFAULT;
+	
+	data += sizeof(hdr);
+	count -= sizeof(hdr);
+
+	if (hdr.nvoices <= 0 || hdr.nvoices >= 100) {
+		printk(KERN_ERR "Soundfont error: Illegal voice number %d\n",
+		       hdr.nvoices);
+		return -EINVAL;
+	}
+
+	if (count < (long)sizeof(struct soundfont_voice_info) * hdr.nvoices) {
+		printk(KERN_ERR "Soundfont Error: "
+		       "patch length(%ld) is smaller than nvoices(%d)\n",
+		       count, hdr.nvoices);
+		return -EINVAL;
+	}
+
+	switch (hdr.write_mode) {
+	case SNDRV_SFNT_WR_EXCLUSIVE:
+		/* exclusive mode - if the instrument already exists,
+		   return error */
+		for (zone = sf->zones; zone; zone = zone->next) {
+			if (!zone->mapped &&
+			    zone->bank == hdr.bank &&
+			    zone->instr == hdr.instr)
+				return -EINVAL;
+		}
+		break;
+	case SNDRV_SFNT_WR_REPLACE:
+		/* replace mode - remove the instrument if it already exists */
+		remove_info(sflist, sf, hdr.bank, hdr.instr);
+		break;
+	}
+
+	for (i = 0; i < hdr.nvoices; i++) {
+		struct snd_sf_zone tmpzone;
+
+		/* copy awe_voice_info parameters */
+		if (copy_from_user(&tmpzone.v, data, sizeof(tmpzone.v))) {
+			return -EFAULT;
+		}
+
+		data += sizeof(tmpzone.v);
+		count -= sizeof(tmpzone.v);
+
+		tmpzone.bank = hdr.bank;
+		tmpzone.instr = hdr.instr;
+		tmpzone.mapped = 0;
+		tmpzone.v.sf_id = sf->id;
+		if (tmpzone.v.mode & SNDRV_SFNT_MODE_INIT_PARM)
+			init_voice_parm(&tmpzone.v.parm);
+
+		/* create a new zone */
+		if ((zone = sf_zone_new(sflist, sf)) == NULL) {
+			return -ENOMEM;
+		}
+
+		/* copy the temporary data */
+		zone->bank = tmpzone.bank;
+		zone->instr = tmpzone.instr;
+		zone->v = tmpzone.v;
+
+		/* look up the sample */
+		zone->sample = set_sample(sf, &zone->v);
+	}
+
+	return 0;
+}
+
+
+/* initialize voice_info record */
+static void
+init_voice_info(struct soundfont_voice_info *avp)
+{
+	memset(avp, 0, sizeof(*avp));
+
+	avp->root = 60;
+	avp->high = 127;
+	avp->velhigh = 127;
+	avp->fixkey = -1;
+	avp->fixvel = -1;
+	avp->fixpan = -1;
+	avp->pan = -1;
+	avp->amplitude = 127;
+	avp->scaleTuning = 100;
+
+	init_voice_parm(&avp->parm);
+}
+
+/* initialize voice_parm record:
+ * Env1/2: delay=0, attack=0, hold=0, sustain=0, decay=0, release=0.
+ * Vibrato and Tremolo effects are zero.
+ * Cutoff is maximum.
+ * Chorus and Reverb effects are zero.
+ */
+static void
+init_voice_parm(struct soundfont_voice_parm *pp)
+{
+	memset(pp, 0, sizeof(*pp));
+
+	pp->moddelay = 0x8000;
+	pp->modatkhld = 0x7f7f;
+	pp->moddcysus = 0x7f7f;
+	pp->modrelease = 0x807f;
+
+	pp->voldelay = 0x8000;
+	pp->volatkhld = 0x7f7f;
+	pp->voldcysus = 0x7f7f;
+	pp->volrelease = 0x807f;
+
+	pp->lfo1delay = 0x8000;
+	pp->lfo2delay = 0x8000;
+
+	pp->cutoff = 0xff;
+}	
+
+/* search the specified sample */
+static struct snd_sf_sample *
+set_sample(struct snd_soundfont *sf, struct soundfont_voice_info *avp)
+{
+	struct snd_sf_sample *sample;
+
+	sample = find_sample(sf, avp->sample);
+	if (sample == NULL)
+		return NULL;
+
+	/* add in the actual sample offsets:
+	 * The voice_info addresses define only the relative offset
+	 * from sample pointers.  Here we calculate the actual DRAM
+	 * offset from sample pointers.
+	 */
+	avp->start += sample->v.start;
+	avp->end += sample->v.end;
+	avp->loopstart += sample->v.loopstart;
+	avp->loopend += sample->v.loopend;
+
+	/* copy mode flags */
+	avp->sample_mode = sample->v.mode_flags;
+
+	return sample;
+}
+
+/* find the sample pointer with the given id in the soundfont */
+static struct snd_sf_sample *
+find_sample(struct snd_soundfont *sf, int sample_id)
+{
+	struct snd_sf_sample *p;
+
+	if (sf == NULL)
+		return NULL;
+
+	for (p = sf->samples; p; p = p->next) {
+		if (p->v.sample == sample_id)
+			return p;
+	}
+	return NULL;
+}
+
+
+/*
+ * Load sample information, this can include data to be loaded onto
+ * the soundcard.  It can also just be a pointer into soundcard ROM.
+ * If there is data it will be written to the soundcard via the callback
+ * routine.
+ */
+static int
+load_data(struct snd_sf_list *sflist, const void __user *data, long count)
+{
+	struct snd_soundfont *sf;
+	struct soundfont_sample_info sample_info;
+	struct snd_sf_sample *sp;
+	long off;
+
+	/* patch must be opened */
+	if ((sf = sflist->currsf) == NULL)
+		return -EINVAL;
+
+	if (is_special_type(sf->type))
+		return -EINVAL;
+
+	if (copy_from_user(&sample_info, data, sizeof(sample_info)))
+		return -EFAULT;
+
+	off = sizeof(sample_info);
+
+	if (sample_info.size != (count-off)/2)
+		return -EINVAL;
+
+	/* Check for dup */
+	if (find_sample(sf, sample_info.sample)) {
+		/* if shared sample, skip this data */
+		if (sf->type & SNDRV_SFNT_PAT_SHARED)
+			return 0;
+		return -EINVAL;
+	}
+
+	/* Allocate a new sample structure */
+	if ((sp = sf_sample_new(sflist, sf)) == NULL)
+		return -ENOMEM;
+
+	sp->v = sample_info;
+	sp->v.sf_id = sf->id;
+	sp->v.dummy = 0;
+	sp->v.truesize = sp->v.size;
+
+	/*
+	 * If there is wave data then load it.
+	 */
+	if (sp->v.size > 0) {
+		int  rc;
+		rc = sflist->callback.sample_new
+			(sflist->callback.private_data, sp, sflist->memhdr,
+			 data + off, count - off);
+		if (rc < 0) {
+			sf_sample_delete(sflist, sf, sp);
+			return rc;
+		}
+		sflist->mem_used += sp->v.truesize;
+	}
+
+	return count;
+}
+
+
+/* log2_tbl[i] = log2(i+128) * 0x10000 */
+static int log_tbl[129] = {
+	0x70000, 0x702df, 0x705b9, 0x7088e, 0x70b5d, 0x70e26, 0x710eb, 0x713aa,
+	0x71663, 0x71918, 0x71bc8, 0x71e72, 0x72118, 0x723b9, 0x72655, 0x728ed,
+	0x72b80, 0x72e0e, 0x73098, 0x7331d, 0x7359e, 0x7381b, 0x73a93, 0x73d08,
+	0x73f78, 0x741e4, 0x7444c, 0x746b0, 0x74910, 0x74b6c, 0x74dc4, 0x75019,
+	0x75269, 0x754b6, 0x75700, 0x75946, 0x75b88, 0x75dc7, 0x76002, 0x7623a,
+	0x7646e, 0x766a0, 0x768cd, 0x76af8, 0x76d1f, 0x76f43, 0x77164, 0x77382,
+	0x7759d, 0x777b4, 0x779c9, 0x77bdb, 0x77dea, 0x77ff5, 0x781fe, 0x78404,
+	0x78608, 0x78808, 0x78a06, 0x78c01, 0x78df9, 0x78fef, 0x791e2, 0x793d2,
+	0x795c0, 0x797ab, 0x79993, 0x79b79, 0x79d5d, 0x79f3e, 0x7a11d, 0x7a2f9,
+	0x7a4d3, 0x7a6ab, 0x7a880, 0x7aa53, 0x7ac24, 0x7adf2, 0x7afbe, 0x7b188,
+	0x7b350, 0x7b515, 0x7b6d8, 0x7b899, 0x7ba58, 0x7bc15, 0x7bdd0, 0x7bf89,
+	0x7c140, 0x7c2f5, 0x7c4a7, 0x7c658, 0x7c807, 0x7c9b3, 0x7cb5e, 0x7cd07,
+	0x7ceae, 0x7d053, 0x7d1f7, 0x7d398, 0x7d538, 0x7d6d6, 0x7d872, 0x7da0c,
+	0x7dba4, 0x7dd3b, 0x7ded0, 0x7e063, 0x7e1f4, 0x7e384, 0x7e512, 0x7e69f,
+	0x7e829, 0x7e9b3, 0x7eb3a, 0x7ecc0, 0x7ee44, 0x7efc7, 0x7f148, 0x7f2c8,
+	0x7f446, 0x7f5c2, 0x7f73d, 0x7f8b7, 0x7fa2f, 0x7fba5, 0x7fd1a, 0x7fe8d,
+	0x80000,
+};
+
+/* convert from linear to log value
+ *
+ * conversion: value = log2(amount / base) * ratio
+ *
+ * argument:
+ *   amount = linear value (unsigned, 32bit max)
+ *   offset = base offset (:= log2(base) * 0x10000)
+ *   ratio = division ratio
+ *
+ */
+int
+snd_sf_linear_to_log(unsigned int amount, int offset, int ratio)
+{
+	int v;
+	int s, low, bit;
+	
+	if (amount < 2)
+		return 0;
+	for (bit = 0; ! (amount & 0x80000000L); bit++)
+		amount <<= 1;
+	s = (amount >> 24) & 0x7f;
+	low = (amount >> 16) & 0xff;
+	/* linear approxmimation by lower 8 bit */
+	v = (log_tbl[s + 1] * low + log_tbl[s] * (0x100 - low)) >> 8;
+	v -= offset;
+	v = (v * ratio) >> 16;
+	v += (24 - bit) * ratio;
+	return v;
+}
+
+EXPORT_SYMBOL(snd_sf_linear_to_log);
+
+
+#define OFFSET_MSEC		653117		/* base = 1000 */
+#define OFFSET_ABSCENT		851781		/* base = 8176 */
+#define OFFSET_SAMPLERATE	1011119		/* base = 44100 */
+
+#define ABSCENT_RATIO		1200
+#define TIMECENT_RATIO		1200
+#define SAMPLERATE_RATIO	4096
+
+/*
+ * mHz to abscent
+ * conversion: abscent = log2(MHz / 8176) * 1200
+ */
+static int
+freq_to_note(int mhz)
+{
+	return snd_sf_linear_to_log(mhz, OFFSET_ABSCENT, ABSCENT_RATIO);
+}
+
+/* convert Hz to AWE32 rate offset:
+ * sample pitch offset for the specified sample rate
+ * rate=44100 is no offset, each 4096 is 1 octave (twice).
+ * eg, when rate is 22050, this offset becomes -4096.
+ *
+ * conversion: offset = log2(Hz / 44100) * 4096
+ */
+static int
+calc_rate_offset(int hz)
+{
+	return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO);
+}
+
+
+/* calculate GUS envelope time */
+static int
+calc_gus_envelope_time(int rate, int start, int end)
+{
+	int r, p, t;
+	r = (3 - ((rate >> 6) & 3)) * 3;
+	p = rate & 0x3f;
+	t = end - start;
+	if (t < 0) t = -t;
+	if (13 > r)
+		t = t << (13 - r);
+	else
+		t = t >> (r - 13);
+	return (t * 10) / (p * 441);
+}
+
+/* convert envelope time parameter to soundfont parameters */
+
+/* attack & decay/release time table (msec) */
+static short attack_time_tbl[128] = {
+32767, 32767, 5989, 4235, 2994, 2518, 2117, 1780, 1497, 1373, 1259, 1154, 1058, 970, 890, 816,
+707, 691, 662, 634, 607, 581, 557, 533, 510, 489, 468, 448, 429, 411, 393, 377,
+361, 345, 331, 317, 303, 290, 278, 266, 255, 244, 234, 224, 214, 205, 196, 188,
+180, 172, 165, 158, 151, 145, 139, 133, 127, 122, 117, 112, 107, 102, 98, 94,
+90, 86, 82, 79, 75, 72, 69, 66, 63, 61, 58, 56, 53, 51, 49, 47,
+45, 43, 41, 39, 37, 36, 34, 33, 31, 30, 29, 28, 26, 25, 24, 23,
+22, 21, 20, 19, 19, 18, 17, 16, 16, 15, 15, 14, 13, 13, 12, 12,
+11, 11, 10, 10, 10, 9, 9, 8, 8, 8, 8, 7, 7, 7, 6, 0,
+};
+
+static short decay_time_tbl[128] = {
+32767, 32767, 22614, 15990, 11307, 9508, 7995, 6723, 5653, 5184, 4754, 4359, 3997, 3665, 3361, 3082,
+2828, 2765, 2648, 2535, 2428, 2325, 2226, 2132, 2042, 1955, 1872, 1793, 1717, 1644, 1574, 1507,
+1443, 1382, 1324, 1267, 1214, 1162, 1113, 1066, 978, 936, 897, 859, 822, 787, 754, 722,
+691, 662, 634, 607, 581, 557, 533, 510, 489, 468, 448, 429, 411, 393, 377, 361,
+345, 331, 317, 303, 290, 278, 266, 255, 244, 234, 224, 214, 205, 196, 188, 180,
+172, 165, 158, 151, 145, 139, 133, 127, 122, 117, 112, 107, 102, 98, 94, 90,
+86, 82, 79, 75, 72, 69, 66, 63, 61, 58, 56, 53, 51, 49, 47, 45,
+43, 41, 39, 37, 36, 34, 33, 31, 30, 29, 28, 26, 25, 24, 23, 22,
+};
+
+/* delay time = 0x8000 - msec/92 */
+int
+snd_sf_calc_parm_hold(int msec)
+{
+	int val = (0x7f * 92 - msec) / 92;
+	if (val < 1) val = 1;
+	if (val >= 126) val = 126;
+	return val;
+}
+
+/* search an index for specified time from given time table */
+static int
+calc_parm_search(int msec, short *table)
+{
+	int left = 1, right = 127, mid;
+	while (left < right) {
+		mid = (left + right) / 2;
+		if (msec < (int)table[mid])
+			left = mid + 1;
+		else
+			right = mid;
+	}
+	return left;
+}
+
+/* attack time: search from time table */
+int
+snd_sf_calc_parm_attack(int msec)
+{
+	return calc_parm_search(msec, attack_time_tbl);
+}
+
+/* decay/release time: search from time table */
+int
+snd_sf_calc_parm_decay(int msec)
+{
+	return calc_parm_search(msec, decay_time_tbl);
+}
+
+int snd_sf_vol_table[128] = {
+	255,111,95,86,79,74,70,66,63,61,58,56,54,52,50,49,
+	47,46,45,43,42,41,40,39,38,37,36,35,34,34,33,32,
+	31,31,30,29,29,28,27,27,26,26,25,24,24,23,23,22,
+	22,21,21,21,20,20,19,19,18,18,18,17,17,16,16,16,
+	15,15,15,14,14,14,13,13,13,12,12,12,11,11,11,10,
+	10,10,10,9,9,9,8,8,8,8,7,7,7,7,6,6,
+	6,6,5,5,5,5,5,4,4,4,4,3,3,3,3,3,
+	2,2,2,2,2,1,1,1,1,1,0,0,0,0,0,0,
+};
+
+
+#define calc_gus_sustain(val)  (0x7f - snd_sf_vol_table[(val)/2])
+#define calc_gus_attenuation(val)	snd_sf_vol_table[(val)/2]
+
+/* load GUS patch */
+static int
+load_guspatch(struct snd_sf_list *sflist, const char __user *data,
+	      long count, int client)
+{
+	struct patch_info patch;
+	struct snd_soundfont *sf;
+	struct snd_sf_zone *zone;
+	struct snd_sf_sample *smp;
+	int note, sample_id;
+	int rc;
+
+	if (count < (long)sizeof(patch)) {
+		snd_printk(KERN_ERR "patch record too small %ld\n", count);
+		return -EINVAL;
+	}
+	if (copy_from_user(&patch, data, sizeof(patch)))
+		return -EFAULT;
+	
+	count -= sizeof(patch);
+	data += sizeof(patch);
+
+	sf = newsf(sflist, SNDRV_SFNT_PAT_TYPE_GUS|SNDRV_SFNT_PAT_SHARED, NULL);
+	if (sf == NULL)
+		return -ENOMEM;
+	if ((smp = sf_sample_new(sflist, sf)) == NULL)
+		return -ENOMEM;
+	sample_id = sflist->sample_counter;
+	smp->v.sample = sample_id;
+	smp->v.start = 0;
+	smp->v.end = patch.len;
+	smp->v.loopstart = patch.loop_start;
+	smp->v.loopend = patch.loop_end;
+	smp->v.size = patch.len;
+
+	/* set up mode flags */
+	smp->v.mode_flags = 0;
+	if (!(patch.mode & WAVE_16_BITS))
+		smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_8BITS;
+	if (patch.mode & WAVE_UNSIGNED)
+		smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_UNSIGNED;
+	smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_NO_BLANK;
+	if (!(patch.mode & (WAVE_LOOPING|WAVE_BIDIR_LOOP|WAVE_LOOP_BACK)))
+		smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_SINGLESHOT;
+	if (patch.mode & WAVE_BIDIR_LOOP)
+		smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_BIDIR_LOOP;
+	if (patch.mode & WAVE_LOOP_BACK)
+		smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_REVERSE_LOOP;
+
+	if (patch.mode & WAVE_16_BITS) {
+		/* convert to word offsets */
+		smp->v.size /= 2;
+		smp->v.end /= 2;
+		smp->v.loopstart /= 2;
+		smp->v.loopend /= 2;
+	}
+	/*smp->v.loopend++;*/
+
+	smp->v.dummy = 0;
+	smp->v.truesize = 0;
+	smp->v.sf_id = sf->id;
+
+	/* set up voice info */
+	if ((zone = sf_zone_new(sflist, sf)) == NULL) {
+		sf_sample_delete(sflist, sf, smp);
+		return -ENOMEM;
+	}
+
+	/*
+	 * load wave data
+	 */
+	if (sflist->callback.sample_new) {
+		rc = sflist->callback.sample_new
+			(sflist->callback.private_data, smp, sflist->memhdr,
+			 data, count);
+		if (rc < 0) {
+			sf_sample_delete(sflist, sf, smp);
+			return rc;
+		}
+		/* memory offset is updated after */
+	}
+
+	/* update the memory offset here */
+	sflist->mem_used += smp->v.truesize;
+
+	zone->v.sample = sample_id; /* the last sample */
+	zone->v.rate_offset = calc_rate_offset(patch.base_freq);
+	note = freq_to_note(patch.base_note);
+	zone->v.root = note / 100;
+	zone->v.tune = -(note % 100);
+	zone->v.low = (freq_to_note(patch.low_note) + 99) / 100;
+	zone->v.high = freq_to_note(patch.high_note) / 100;
+	/* panning position; -128 - 127 => 0-127 */
+	zone->v.pan = (patch.panning + 128) / 2;
+#if 0
+	snd_printk(KERN_DEBUG
+		   "gus: basefrq=%d (ofs=%d) root=%d,tune=%d, range:%d-%d\n",
+		   (int)patch.base_freq, zone->v.rate_offset,
+		   zone->v.root, zone->v.tune, zone->v.low, zone->v.high);
+#endif
+
+	/* detuning is ignored */
+	/* 6points volume envelope */
+	if (patch.mode & WAVE_ENVELOPES) {
+		int attack, hold, decay, release;
+		attack = calc_gus_envelope_time
+			(patch.env_rate[0], 0, patch.env_offset[0]);
+		hold = calc_gus_envelope_time
+			(patch.env_rate[1], patch.env_offset[0],
+			 patch.env_offset[1]);
+		decay = calc_gus_envelope_time
+			(patch.env_rate[2], patch.env_offset[1],
+			 patch.env_offset[2]);
+		release = calc_gus_envelope_time
+			(patch.env_rate[3], patch.env_offset[1],
+			 patch.env_offset[4]);
+		release += calc_gus_envelope_time
+			(patch.env_rate[4], patch.env_offset[3],
+			 patch.env_offset[4]);
+		release += calc_gus_envelope_time
+			(patch.env_rate[5], patch.env_offset[4],
+			 patch.env_offset[5]);
+		zone->v.parm.volatkhld = 
+			(snd_sf_calc_parm_hold(hold) << 8) |
+			snd_sf_calc_parm_attack(attack);
+		zone->v.parm.voldcysus = (calc_gus_sustain(patch.env_offset[2]) << 8) |
+			snd_sf_calc_parm_decay(decay);
+		zone->v.parm.volrelease = 0x8000 | snd_sf_calc_parm_decay(release);
+		zone->v.attenuation = calc_gus_attenuation(patch.env_offset[0]);
+#if 0
+		snd_printk(KERN_DEBUG
+			   "gus: atkhld=%x, dcysus=%x, volrel=%x, att=%d\n",
+			   zone->v.parm.volatkhld,
+			   zone->v.parm.voldcysus,
+			   zone->v.parm.volrelease,
+			   zone->v.attenuation);
+#endif
+	}
+
+	/* fast release */
+	if (patch.mode & WAVE_FAST_RELEASE) {
+		zone->v.parm.volrelease = 0x807f;
+	}
+
+	/* tremolo effect */
+	if (patch.mode & WAVE_TREMOLO) {
+		int rate = (patch.tremolo_rate * 1000 / 38) / 42;
+		zone->v.parm.tremfrq = ((patch.tremolo_depth / 2) << 8) | rate;
+	}
+	/* vibrato effect */
+	if (patch.mode & WAVE_VIBRATO) {
+		int rate = (patch.vibrato_rate * 1000 / 38) / 42;
+		zone->v.parm.fm2frq2 = ((patch.vibrato_depth / 6) << 8) | rate;
+	}
+	
+	/* scale_freq, scale_factor, volume, and fractions not implemented */
+
+	if (!(smp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT))
+		zone->v.mode = SNDRV_SFNT_MODE_LOOPING;
+	else
+		zone->v.mode = 0;
+
+	/* append to the tail of the list */
+	/*zone->bank = ctrls[AWE_MD_GUS_BANK];*/
+	zone->bank = 0;
+	zone->instr = patch.instr_no;
+	zone->mapped = 0;
+	zone->v.sf_id = sf->id;
+
+	zone->sample = set_sample(sf, &zone->v);
+
+	/* rebuild preset now */
+	add_preset(sflist, zone);
+
+	return 0;
+}
+
+/* load GUS patch */
+int
+snd_soundfont_load_guspatch(struct snd_sf_list *sflist, const char __user *data,
+			    long count, int client)
+{
+	int rc;
+	lock_preset(sflist);
+	rc = load_guspatch(sflist, data, count, client);
+	unlock_preset(sflist);
+	return rc;
+}
+
+
+/*
+ * Rebuild the preset table.  This is like a hash table in that it allows
+ * quick access to the zone information.  For each preset there are zone
+ * structures linked by next_instr and by next_zone.  Former is the whole
+ * link for this preset, and latter is the link for zone (i.e. instrument/
+ * bank/key combination).
+ */
+static void
+rebuild_presets(struct snd_sf_list *sflist)
+{
+	struct snd_soundfont *sf;
+	struct snd_sf_zone *cur;
+
+	/* clear preset table */
+	memset(sflist->presets, 0, sizeof(sflist->presets));
+
+	/* search all fonts and insert each font */
+	for (sf = sflist->fonts; sf; sf = sf->next) {
+		for (cur = sf->zones; cur; cur = cur->next) {
+			if (! cur->mapped && cur->sample == NULL) {
+				/* try again to search the corresponding sample */
+				cur->sample = set_sample(sf, &cur->v);
+				if (cur->sample == NULL)
+					continue;
+			}
+
+			add_preset(sflist, cur);
+		}
+	}
+}
+
+
+/*
+ * add the given zone to preset table
+ */
+static void
+add_preset(struct snd_sf_list *sflist, struct snd_sf_zone *cur)
+{
+	struct snd_sf_zone *zone;
+	int index;
+
+	zone = search_first_zone(sflist, cur->bank, cur->instr, cur->v.low);
+	if (zone && zone->v.sf_id != cur->v.sf_id) {
+		/* different instrument was already defined */
+		struct snd_sf_zone *p;
+		/* compare the allocated time */
+		for (p = zone; p; p = p->next_zone) {
+			if (p->counter > cur->counter)
+				/* the current is older.. skipped */
+				return;
+		}
+		/* remove old zones */
+		delete_preset(sflist, zone);
+		zone = NULL; /* do not forget to clear this! */
+	}
+
+	/* prepend this zone */
+	if ((index = get_index(cur->bank, cur->instr, cur->v.low)) < 0)
+		return;
+	cur->next_zone = zone; /* zone link */
+	cur->next_instr = sflist->presets[index]; /* preset table link */
+	sflist->presets[index] = cur;
+}
+
+/*
+ * delete the given zones from preset_table
+ */
+static void
+delete_preset(struct snd_sf_list *sflist, struct snd_sf_zone *zp)
+{
+	int index;
+	struct snd_sf_zone *p;
+
+	if ((index = get_index(zp->bank, zp->instr, zp->v.low)) < 0)
+		return;
+	for (p = sflist->presets[index]; p; p = p->next_instr) {
+		while (p->next_instr == zp) {
+			p->next_instr = zp->next_instr;
+			zp = zp->next_zone;
+			if (zp == NULL)
+				return;
+		}
+	}
+}
+
+
+/*
+ * Search matching zones from preset table.
+ * The note can be rewritten by preset mapping (alias).
+ * The found zones are stored on 'table' array.  max_layers defines
+ * the maximum number of elements in this array.
+ * This function returns the number of found zones.  0 if not found.
+ */
+int
+snd_soundfont_search_zone(struct snd_sf_list *sflist, int *notep, int vel,
+			  int preset, int bank,
+			  int def_preset, int def_bank,
+			  struct snd_sf_zone **table, int max_layers)
+{
+	int nvoices;
+	unsigned long flags;
+
+	/* this function is supposed to be called atomically,
+	 * so we check the lock.  if it's busy, just returns 0 to
+	 * tell the caller the busy state
+	 */
+	spin_lock_irqsave(&sflist->lock, flags);
+	if (sflist->presets_locked) {
+		spin_unlock_irqrestore(&sflist->lock, flags);
+		return 0;
+	}
+	nvoices = search_zones(sflist, notep, vel, preset, bank,
+			       table, max_layers, 0);
+	if (! nvoices) {
+		if (preset != def_preset || bank != def_bank)
+			nvoices = search_zones(sflist, notep, vel,
+					       def_preset, def_bank,
+					       table, max_layers, 0);
+	}
+	spin_unlock_irqrestore(&sflist->lock, flags);
+	return nvoices;
+}
+
+
+/*
+ * search the first matching zone
+ */
+static struct snd_sf_zone *
+search_first_zone(struct snd_sf_list *sflist, int bank, int preset, int key)
+{
+	int index;
+	struct snd_sf_zone *zp;
+
+	if ((index = get_index(bank, preset, key)) < 0)
+		return NULL;
+	for (zp = sflist->presets[index]; zp; zp = zp->next_instr) {
+		if (zp->instr == preset && zp->bank == bank)
+			return zp;
+	}
+	return NULL;
+}
+
+
+/*
+ * search matching zones from sflist.  can be called recursively.
+ */
+static int
+search_zones(struct snd_sf_list *sflist, int *notep, int vel,
+	     int preset, int bank, struct snd_sf_zone **table,
+	     int max_layers, int level)
+{
+	struct snd_sf_zone *zp;
+	int nvoices;
+
+	zp = search_first_zone(sflist, bank, preset, *notep);
+	nvoices = 0;
+	for (; zp; zp = zp->next_zone) {
+		if (*notep >= zp->v.low && *notep <= zp->v.high &&
+		    vel >= zp->v.vellow && vel <= zp->v.velhigh) {
+			if (zp->mapped) {
+				/* search preset mapping (aliasing) */
+				int key = zp->v.fixkey;
+				preset = zp->v.start;
+				bank = zp->v.end;
+
+				if (level > 5) /* too deep alias level */
+					return 0;
+				if (key < 0)
+					key = *notep;
+				nvoices = search_zones(sflist, &key, vel,
+						       preset, bank, table,
+						       max_layers, level + 1);
+				if (nvoices > 0)
+					*notep = key;
+				break;
+			}
+			table[nvoices++] = zp;
+			if (nvoices >= max_layers)
+				break;
+		}
+	}
+
+	return nvoices;
+}
+
+
+/* calculate the index of preset table:
+ * drums are mapped from 128 to 255 according to its note key.
+ * other instruments are mapped from 0 to 127.
+ * if the index is out of range, return -1.
+ */
+static int
+get_index(int bank, int instr, int key)
+{
+	int index;
+	if (SF_IS_DRUM_BANK(bank))
+		index = key + SF_MAX_INSTRUMENTS;
+	else
+		index = instr;
+	index = index % SF_MAX_PRESETS;
+	if (index < 0)
+		return -1;
+	return index;
+}
+
+/*
+ * Initialise the sflist structure.
+ */
+static void
+snd_sf_init(struct snd_sf_list *sflist)
+{
+	memset(sflist->presets, 0, sizeof(sflist->presets));
+
+	sflist->mem_used = 0;
+	sflist->currsf = NULL;
+	sflist->open_client = -1;
+	sflist->fonts = NULL;
+	sflist->fonts_size = 0;
+	sflist->zone_counter = 0;
+	sflist->sample_counter = 0;
+	sflist->zone_locked = 0;
+	sflist->sample_locked = 0;
+}
+
+/*
+ * Release all list records
+ */
+static void
+snd_sf_clear(struct snd_sf_list *sflist)
+{
+	struct snd_soundfont *sf, *nextsf;
+	struct snd_sf_zone *zp, *nextzp;
+	struct snd_sf_sample *sp, *nextsp;
+
+	for (sf = sflist->fonts; sf; sf = nextsf) {
+		nextsf = sf->next;
+		for (zp = sf->zones; zp; zp = nextzp) {
+			nextzp = zp->next;
+			kfree(zp);
+		}
+		for (sp = sf->samples; sp; sp = nextsp) {
+			nextsp = sp->next;
+			if (sflist->callback.sample_free)
+				sflist->callback.sample_free(sflist->callback.private_data,
+							     sp, sflist->memhdr);
+			kfree(sp);
+		}
+		kfree(sf);
+	}
+
+	snd_sf_init(sflist);
+}
+
+
+/*
+ * Create a new sflist structure
+ */
+struct snd_sf_list *
+snd_sf_new(struct snd_sf_callback *callback, struct snd_util_memhdr *hdr)
+{
+	struct snd_sf_list *sflist;
+
+	if ((sflist = kzalloc(sizeof(*sflist), GFP_KERNEL)) == NULL)
+		return NULL;
+
+	mutex_init(&sflist->presets_mutex);
+	spin_lock_init(&sflist->lock);
+	sflist->memhdr = hdr;
+
+	if (callback)
+		sflist->callback = *callback;
+
+	snd_sf_init(sflist);
+	return sflist;
+}
+
+
+/*
+ * Free everything allocated off the sflist structure.
+ */
+void
+snd_sf_free(struct snd_sf_list *sflist)
+{
+	if (sflist == NULL)
+		return;
+	
+	lock_preset(sflist);
+	if (sflist->callback.sample_reset)
+		sflist->callback.sample_reset(sflist->callback.private_data);
+	snd_sf_clear(sflist);
+	unlock_preset(sflist);
+
+	kfree(sflist);
+}
+
+/*
+ * Remove all samples
+ * The soundcard should be silet before calling this function.
+ */
+int
+snd_soundfont_remove_samples(struct snd_sf_list *sflist)
+{
+	lock_preset(sflist);
+	if (sflist->callback.sample_reset)
+		sflist->callback.sample_reset(sflist->callback.private_data);
+	snd_sf_clear(sflist);
+	unlock_preset(sflist);
+
+	return 0;
+}
+
+/*
+ * Remove unlocked samples.
+ * The soundcard should be silent before calling this function.
+ */
+int
+snd_soundfont_remove_unlocked(struct snd_sf_list *sflist)
+{
+	struct snd_soundfont *sf;
+	struct snd_sf_zone *zp, *nextzp;
+	struct snd_sf_sample *sp, *nextsp;
+
+	lock_preset(sflist);
+
+	if (sflist->callback.sample_reset)
+		sflist->callback.sample_reset(sflist->callback.private_data);
+
+	/* to be sure */
+	memset(sflist->presets, 0, sizeof(sflist->presets));
+
+	for (sf = sflist->fonts; sf; sf = sf->next) {
+		for (zp = sf->zones; zp; zp = nextzp) {
+			if (zp->counter < sflist->zone_locked)
+				break;
+			nextzp = zp->next;
+			sf->zones = nextzp;
+			kfree(zp);
+		}
+
+		for (sp = sf->samples; sp; sp = nextsp) {
+			if (sp->counter < sflist->sample_locked)
+				break;
+			nextsp = sp->next;
+			sf->samples = nextsp;
+			sflist->mem_used -= sp->v.truesize;
+			if (sflist->callback.sample_free)
+				sflist->callback.sample_free(sflist->callback.private_data,
+							     sp, sflist->memhdr);
+			kfree(sp);
+		}
+	}
+
+	sflist->zone_counter = sflist->zone_locked;
+	sflist->sample_counter = sflist->sample_locked;
+
+	rebuild_presets(sflist);
+
+	unlock_preset(sflist);
+	return 0;
+}
diff --git a/linux/sound/synth/util_mem.c b/linux/sound/synth/util_mem.c
new file mode 100644
index 0000000..c85522e
--- /dev/null
+++ b/linux/sound/synth/util_mem.c
@@ -0,0 +1,210 @@
+/*
+ *  Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ *
+ *  Generic memory management routines for soundcard memory allocation
+ *
+ *   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/mutex.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/util_mem.h>
+
+MODULE_AUTHOR("Takashi Iwai");
+MODULE_DESCRIPTION("Generic memory management routines for soundcard memory allocation");
+MODULE_LICENSE("GPL");
+
+#define get_memblk(p)	list_entry(p, struct snd_util_memblk, list)
+
+/*
+ * create a new memory manager
+ */
+struct snd_util_memhdr *
+snd_util_memhdr_new(int memsize)
+{
+	struct snd_util_memhdr *hdr;
+
+	hdr = kzalloc(sizeof(*hdr), GFP_KERNEL);
+	if (hdr == NULL)
+		return NULL;
+	hdr->size = memsize;
+	mutex_init(&hdr->block_mutex);
+	INIT_LIST_HEAD(&hdr->block);
+
+	return hdr;
+}
+
+/*
+ * free a memory manager
+ */
+void snd_util_memhdr_free(struct snd_util_memhdr *hdr)
+{
+	struct list_head *p;
+
+	if (!hdr)
+		return;
+	/* release all blocks */
+	while ((p = hdr->block.next) != &hdr->block) {
+		list_del(p);
+		kfree(get_memblk(p));
+	}
+	kfree(hdr);
+}
+
+/*
+ * allocate a memory block (without mutex)
+ */
+struct snd_util_memblk *
+__snd_util_mem_alloc(struct snd_util_memhdr *hdr, int size)
+{
+	struct snd_util_memblk *blk;
+	unsigned int units, prev_offset;
+	struct list_head *p;
+
+	if (snd_BUG_ON(!hdr || size <= 0))
+		return NULL;
+
+	/* word alignment */
+	units = size;
+	if (units & 1)
+		units++;
+	if (units > hdr->size)
+		return NULL;
+
+	/* look for empty block */
+	prev_offset = 0;
+	list_for_each(p, &hdr->block) {
+		blk = get_memblk(p);
+		if (blk->offset - prev_offset >= units)
+			goto __found;
+		prev_offset = blk->offset + blk->size;
+	}
+	if (hdr->size - prev_offset < units)
+		return NULL;
+
+__found:
+	return __snd_util_memblk_new(hdr, units, p->prev);
+}
+
+
+/*
+ * create a new memory block with the given size
+ * the block is linked next to prev
+ */
+struct snd_util_memblk *
+__snd_util_memblk_new(struct snd_util_memhdr *hdr, unsigned int units,
+		      struct list_head *prev)
+{
+	struct snd_util_memblk *blk;
+
+	blk = kmalloc(sizeof(struct snd_util_memblk) + hdr->block_extra_size,
+		      GFP_KERNEL);
+	if (blk == NULL)
+		return NULL;
+
+	if (prev == &hdr->block)
+		blk->offset = 0;
+	else {
+		struct snd_util_memblk *p = get_memblk(prev);
+		blk->offset = p->offset + p->size;
+	}
+	blk->size = units;
+	list_add(&blk->list, prev);
+	hdr->nblocks++;
+	hdr->used += units;
+	return blk;
+}
+
+
+/*
+ * allocate a memory block (with mutex)
+ */
+struct snd_util_memblk *
+snd_util_mem_alloc(struct snd_util_memhdr *hdr, int size)
+{
+	struct snd_util_memblk *blk;
+	mutex_lock(&hdr->block_mutex);
+	blk = __snd_util_mem_alloc(hdr, size);
+	mutex_unlock(&hdr->block_mutex);
+	return blk;
+}
+
+
+/*
+ * remove the block from linked-list and free resource
+ * (without mutex)
+ */
+void
+__snd_util_mem_free(struct snd_util_memhdr *hdr, struct snd_util_memblk *blk)
+{
+	list_del(&blk->list);
+	hdr->nblocks--;
+	hdr->used -= blk->size;
+	kfree(blk);
+}
+
+/*
+ * free a memory block (with mutex)
+ */
+int snd_util_mem_free(struct snd_util_memhdr *hdr, struct snd_util_memblk *blk)
+{
+	if (snd_BUG_ON(!hdr || !blk))
+		return -EINVAL;
+
+	mutex_lock(&hdr->block_mutex);
+	__snd_util_mem_free(hdr, blk);
+	mutex_unlock(&hdr->block_mutex);
+	return 0;
+}
+
+/*
+ * return available memory size
+ */
+int snd_util_mem_avail(struct snd_util_memhdr *hdr)
+{
+	unsigned int size;
+	mutex_lock(&hdr->block_mutex);
+	size = hdr->size - hdr->used;
+	mutex_unlock(&hdr->block_mutex);
+	return size;
+}
+
+
+EXPORT_SYMBOL(snd_util_memhdr_new);
+EXPORT_SYMBOL(snd_util_memhdr_free);
+EXPORT_SYMBOL(snd_util_mem_alloc);
+EXPORT_SYMBOL(snd_util_mem_free);
+EXPORT_SYMBOL(snd_util_mem_avail);
+EXPORT_SYMBOL(__snd_util_mem_alloc);
+EXPORT_SYMBOL(__snd_util_mem_free);
+EXPORT_SYMBOL(__snd_util_memblk_new);
+
+/*
+ *  INIT part
+ */
+
+static int __init alsa_util_mem_init(void)
+{
+	return 0;
+}
+
+static void __exit alsa_util_mem_exit(void)
+{
+}
+
+module_init(alsa_util_mem_init)
+module_exit(alsa_util_mem_exit)
diff --git a/linux/sound/usb/Kconfig b/linux/sound/usb/Kconfig
new file mode 100644
index 0000000..112984f
--- /dev/null
+++ b/linux/sound/usb/Kconfig
@@ -0,0 +1,101 @@
+# ALSA USB drivers
+
+menuconfig SND_USB
+	bool "USB sound devices"
+	depends on USB
+	default y
+	help
+	  Support for sound devices connected via the USB bus.
+
+if SND_USB && USB
+
+config SND_USB_AUDIO
+	tristate "USB Audio/MIDI driver"
+	select SND_HWDEP
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say Y here to include support for USB audio and USB MIDI
+	  devices.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-audio.
+
+config SND_USB_UA101
+	tristate "Edirol UA-101/UA-1000 driver"
+	select SND_PCM
+	select SND_RAWMIDI
+	help
+	  Say Y here to include support for the Edirol UA-101 and UA-1000
+	  audio/MIDI interfaces.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-ua101.
+
+config SND_USB_USX2Y
+	tristate "Tascam US-122, US-224 and US-428 USB driver"
+	depends on X86 || PPC || ALPHA
+	select SND_HWDEP
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	  Say Y here to include support for Tascam USB Audio/MIDI
+	  interfaces or controllers US-122, US-224 and US-428.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-usx2y.
+
+config SND_USB_CAIAQ
+	tristate "Native Instruments USB audio devices"
+	select SND_HWDEP
+	select SND_RAWMIDI
+	select SND_PCM
+	help
+	   Say Y here to include support for caiaq USB audio interfaces,
+	   namely:
+
+	    * Native Instruments RigKontrol2
+	    * Native Instruments RigKontrol3
+	    * Native Instruments Kore Controller
+	    * Native Instruments Kore Controller 2
+	    * Native Instruments Audio Kontrol 1
+	    * Native Instruments Audio 2 DJ
+	    * Native Instruments Audio 4 DJ
+	    * Native Instruments Audio 8 DJ
+	    * Native Instruments Guitar Rig Session I/O
+	    * Native Instruments Guitar Rig mobile
+	    * Native Instruments Traktor Kontrol X1
+	    * Native Instruments Traktor Kontrol S4
+
+	   To compile this driver as a module, choose M here: the module
+	   will be called snd-usb-caiaq.
+
+config SND_USB_CAIAQ_INPUT
+	bool "enable input device for controllers"
+	depends on SND_USB_CAIAQ
+	depends on INPUT=y || INPUT=SND_USB_CAIAQ
+	help
+	  Say Y here to support input controllers like buttons, knobs,
+	  alpha dials and analog pedals on the following products:
+
+	   * Native Instruments RigKontrol2
+	   * Native Instruments RigKontrol3
+	   * Native Instruments Kore Controller
+	   * Native Instruments Kore Controller 2
+	   * Native Instruments Audio Kontrol 1
+	   * Native Instruments Traktor Kontrol S4
+
+config SND_USB_US122L
+	tristate "Tascam US-122L USB driver"
+	depends on X86 && EXPERIMENTAL
+	select SND_HWDEP
+	select SND_RAWMIDI
+	help
+	  Say Y here to include support for Tascam US-122L USB Audio/MIDI
+	  interfaces.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-us122l.
+
+endif	# SND_USB
+
diff --git a/linux/sound/usb/Makefile b/linux/sound/usb/Makefile
new file mode 100644
index 0000000..1e362bf
--- /dev/null
+++ b/linux/sound/usb/Makefile
@@ -0,0 +1,26 @@
+#
+# Makefile for ALSA
+#
+
+snd-usb-audio-objs := 	card.o \
+			mixer.o \
+			mixer_quirks.o \
+			proc.o \
+			quirks.o \
+			format.o \
+			endpoint.o \
+			urb.o \
+			pcm.o \
+			helper.o \
+			clock.o
+
+snd-usbmidi-lib-objs := midi.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o
+
+obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
+obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
+obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
+
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/
diff --git a/linux/sound/usb/caiaq/Makefile b/linux/sound/usb/caiaq/Makefile
new file mode 100644
index 0000000..3889996
--- /dev/null
+++ b/linux/sound/usb/caiaq/Makefile
@@ -0,0 +1,4 @@
+snd-usb-caiaq-y := device.o audio.o midi.o control.o
+snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o
+
+obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o
diff --git a/linux/sound/usb/caiaq/audio.c b/linux/sound/usb/caiaq/audio.c
new file mode 100644
index 0000000..68b9747
--- /dev/null
+++ b/linux/sound/usb/caiaq/audio.c
@@ -0,0 +1,860 @@
+/*
+ *   Copyright (c) 2006-2008 Daniel Mack, Karsten Wiese
+ *
+ *   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/spinlock.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "audio.h"
+
+#define N_URBS			32
+#define CLOCK_DRIFT_TOLERANCE	5
+#define FRAMES_PER_URB		8
+#define BYTES_PER_FRAME		512
+#define CHANNELS_PER_STREAM	2
+#define BYTES_PER_SAMPLE	3
+#define BYTES_PER_SAMPLE_USB	4
+#define MAX_BUFFER_SIZE		(128*1024)
+#define MAX_ENDPOINT_SIZE	512
+
+#define ENDPOINT_CAPTURE	2
+#define ENDPOINT_PLAYBACK	6
+
+#define MAKE_CHECKBYTE(dev,stream,i) \
+	(stream << 1) | (~(i / (dev->n_streams * BYTES_PER_SAMPLE_USB)) & 1)
+
+static struct snd_pcm_hardware snd_usb_caiaq_pcm_hardware = {
+	.info 		= (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+			   SNDRV_PCM_INFO_BLOCK_TRANSFER),
+	.formats 	= SNDRV_PCM_FMTBIT_S24_3BE,
+	.rates 		= (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+			   SNDRV_PCM_RATE_96000),
+	.rate_min	= 44100,
+	.rate_max	= 0, /* will overwrite later */
+	.channels_min	= CHANNELS_PER_STREAM,
+	.channels_max	= CHANNELS_PER_STREAM,
+	.buffer_bytes_max = MAX_BUFFER_SIZE,
+	.period_bytes_min = 128,
+	.period_bytes_max = MAX_BUFFER_SIZE,
+	.periods_min	= 1,
+	.periods_max	= 1024,
+};
+
+static void
+activate_substream(struct snd_usb_caiaqdev *dev,
+	           struct snd_pcm_substream *sub)
+{
+	spin_lock(&dev->spinlock);
+
+	if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dev->sub_playback[sub->number] = sub;
+	else
+		dev->sub_capture[sub->number] = sub;
+
+	spin_unlock(&dev->spinlock);
+}
+
+static void
+deactivate_substream(struct snd_usb_caiaqdev *dev,
+		     struct snd_pcm_substream *sub)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&dev->spinlock, flags);
+
+	if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dev->sub_playback[sub->number] = NULL;
+	else
+		dev->sub_capture[sub->number] = NULL;
+
+	spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+static int
+all_substreams_zero(struct snd_pcm_substream **subs)
+{
+	int i;
+	for (i = 0; i < MAX_STREAMS; i++)
+		if (subs[i] != NULL)
+			return 0;
+	return 1;
+}
+
+static int stream_start(struct snd_usb_caiaqdev *dev)
+{
+	int i, ret;
+
+	debug("%s(%p)\n", __func__, dev);
+
+	if (dev->streaming)
+		return -EINVAL;
+
+	memset(dev->sub_playback, 0, sizeof(dev->sub_playback));
+	memset(dev->sub_capture, 0, sizeof(dev->sub_capture));
+	dev->input_panic = 0;
+	dev->output_panic = 0;
+	dev->first_packet = 4;
+	dev->streaming = 1;
+	dev->warned = 0;
+
+	for (i = 0; i < N_URBS; i++) {
+		ret = usb_submit_urb(dev->data_urbs_in[i], GFP_ATOMIC);
+		if (ret) {
+			log("unable to trigger read #%d! (ret %d)\n", i, ret);
+			dev->streaming = 0;
+			return -EPIPE;
+		}
+	}
+
+	return 0;
+}
+
+static void stream_stop(struct snd_usb_caiaqdev *dev)
+{
+	int i;
+
+	debug("%s(%p)\n", __func__, dev);
+	if (!dev->streaming)
+		return;
+
+	dev->streaming = 0;
+
+	for (i = 0; i < N_URBS; i++) {
+		usb_kill_urb(dev->data_urbs_in[i]);
+		usb_kill_urb(dev->data_urbs_out[i]);
+	}
+}
+
+static int snd_usb_caiaq_substream_open(struct snd_pcm_substream *substream)
+{
+	struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(substream);
+	debug("%s(%p)\n", __func__, substream);
+	substream->runtime->hw = dev->pcm_info;
+	snd_pcm_limit_hw_rates(substream->runtime);
+	return 0;
+}
+
+static int snd_usb_caiaq_substream_close(struct snd_pcm_substream *substream)
+{
+	struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(substream);
+
+	debug("%s(%p)\n", __func__, substream);
+	if (all_substreams_zero(dev->sub_playback) &&
+	    all_substreams_zero(dev->sub_capture)) {
+		/* when the last client has stopped streaming,
+		 * all sample rates are allowed again */
+		stream_stop(dev);
+		dev->pcm_info.rates = dev->samplerates;
+	}
+
+	return 0;
+}
+
+static int snd_usb_caiaq_pcm_hw_params(struct snd_pcm_substream *sub,
+				       struct snd_pcm_hw_params *hw_params)
+{
+	debug("%s(%p)\n", __func__, sub);
+	return snd_pcm_lib_malloc_pages(sub, params_buffer_bytes(hw_params));
+}
+
+static int snd_usb_caiaq_pcm_hw_free(struct snd_pcm_substream *sub)
+{
+	struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(sub);
+	debug("%s(%p)\n", __func__, sub);
+	deactivate_substream(dev, sub);
+	return snd_pcm_lib_free_pages(sub);
+}
+
+/* this should probably go upstream */
+#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_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	int bytes_per_sample, bpp, ret, i;
+	int index = substream->number;
+	struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	debug("%s(%p)\n", __func__, substream);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		int out_pos;
+
+		switch (dev->spec.data_alignment) {
+		case 0:
+		case 2:
+			out_pos = BYTES_PER_SAMPLE + 1;
+			break;
+		case 3:
+		default:
+			out_pos = 0;
+			break;
+		}
+
+		dev->period_out_count[index] = out_pos;
+		dev->audio_out_buf_pos[index] = out_pos;
+	} else {
+		int in_pos;
+
+		switch (dev->spec.data_alignment) {
+		case 0:
+			in_pos = BYTES_PER_SAMPLE + 2;
+			break;
+		case 2:
+			in_pos = BYTES_PER_SAMPLE;
+			break;
+		case 3:
+		default:
+			in_pos = 0;
+			break;
+		}
+
+		dev->period_in_count[index] = in_pos;
+		dev->audio_in_buf_pos[index] = in_pos;
+	}
+
+	if (dev->streaming)
+		return 0;
+
+	/* the first client that opens a stream defines the sample rate
+	 * setting for all subsequent calls, until the last client closed. */
+	for (i=0; i < ARRAY_SIZE(rates); i++)
+		if (runtime->rate == rates[i])
+			dev->pcm_info.rates = 1 << i;
+
+	snd_pcm_limit_hw_rates(runtime);
+
+	bytes_per_sample = BYTES_PER_SAMPLE;
+	if (dev->spec.data_alignment >= 2)
+		bytes_per_sample++;
+
+	bpp = ((runtime->rate / 8000) + CLOCK_DRIFT_TOLERANCE)
+		* bytes_per_sample * CHANNELS_PER_STREAM * dev->n_streams;
+
+	if (bpp > MAX_ENDPOINT_SIZE)
+		bpp = MAX_ENDPOINT_SIZE;
+
+	ret = snd_usb_caiaq_set_audio_params(dev, runtime->rate,
+					     runtime->sample_bits, bpp);
+	if (ret)
+		return ret;
+
+	ret = stream_start(dev);
+	if (ret)
+		return ret;
+
+	dev->output_running = 0;
+	wait_event_timeout(dev->prepare_wait_queue, dev->output_running, HZ);
+	if (!dev->output_running) {
+		stream_stop(dev);
+		return -EPIPE;
+	}
+
+	return 0;
+}
+
+static int snd_usb_caiaq_pcm_trigger(struct snd_pcm_substream *sub, int cmd)
+{
+	struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(sub);
+
+	debug("%s(%p) cmd %d\n", __func__, sub, cmd);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		activate_substream(dev, sub);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		deactivate_substream(dev, sub);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_usb_caiaq_pcm_pointer(struct snd_pcm_substream *sub)
+{
+	int index = sub->number;
+	struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(sub);
+	snd_pcm_uframes_t ptr;
+
+	spin_lock(&dev->spinlock);
+
+	if (dev->input_panic || dev->output_panic)
+		ptr = SNDRV_PCM_POS_XRUN;
+
+	if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		ptr = bytes_to_frames(sub->runtime,
+					dev->audio_out_buf_pos[index]);
+	else
+		ptr = bytes_to_frames(sub->runtime,
+					dev->audio_in_buf_pos[index]);
+
+	spin_unlock(&dev->spinlock);
+	return ptr;
+}
+
+/* operators for both playback and capture */
+static struct snd_pcm_ops snd_usb_caiaq_ops = {
+	.open =		snd_usb_caiaq_substream_open,
+	.close =	snd_usb_caiaq_substream_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usb_caiaq_pcm_hw_params,
+	.hw_free =	snd_usb_caiaq_pcm_hw_free,
+	.prepare =	snd_usb_caiaq_pcm_prepare,
+	.trigger =	snd_usb_caiaq_pcm_trigger,
+	.pointer =	snd_usb_caiaq_pcm_pointer
+};
+
+static void check_for_elapsed_periods(struct snd_usb_caiaqdev *dev,
+				      struct snd_pcm_substream **subs)
+{
+	int stream, pb, *cnt;
+	struct snd_pcm_substream *sub;
+
+	for (stream = 0; stream < dev->n_streams; stream++) {
+		sub = subs[stream];
+		if (!sub)
+			continue;
+
+		pb = snd_pcm_lib_period_bytes(sub);
+		cnt = (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+					&dev->period_out_count[stream] :
+					&dev->period_in_count[stream];
+
+		if (*cnt >= pb) {
+			snd_pcm_period_elapsed(sub);
+			*cnt %= pb;
+		}
+	}
+}
+
+static void read_in_urb_mode0(struct snd_usb_caiaqdev *dev,
+			      const struct urb *urb,
+			      const struct usb_iso_packet_descriptor *iso)
+{
+	unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+	struct snd_pcm_substream *sub;
+	int stream, i;
+
+	if (all_substreams_zero(dev->sub_capture))
+		return;
+
+	for (i = 0; i < iso->actual_length;) {
+		for (stream = 0; stream < dev->n_streams; stream++, i++) {
+			sub = dev->sub_capture[stream];
+			if (sub) {
+				struct snd_pcm_runtime *rt = sub->runtime;
+				char *audio_buf = rt->dma_area;
+				int sz = frames_to_bytes(rt, rt->buffer_size);
+				audio_buf[dev->audio_in_buf_pos[stream]++]
+					= usb_buf[i];
+				dev->period_in_count[stream]++;
+				if (dev->audio_in_buf_pos[stream] == sz)
+					dev->audio_in_buf_pos[stream] = 0;
+			}
+		}
+	}
+}
+
+static void read_in_urb_mode2(struct snd_usb_caiaqdev *dev,
+			      const struct urb *urb,
+			      const struct usb_iso_packet_descriptor *iso)
+{
+	unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+	unsigned char check_byte;
+	struct snd_pcm_substream *sub;
+	int stream, i;
+
+	for (i = 0; i < iso->actual_length;) {
+		if (i % (dev->n_streams * BYTES_PER_SAMPLE_USB) == 0) {
+			for (stream = 0;
+			     stream < dev->n_streams;
+			     stream++, i++) {
+				if (dev->first_packet)
+					continue;
+
+				check_byte = MAKE_CHECKBYTE(dev, stream, i);
+
+				if ((usb_buf[i] & 0x3f) != check_byte)
+					dev->input_panic = 1;
+
+				if (usb_buf[i] & 0x80)
+					dev->output_panic = 1;
+			}
+		}
+		dev->first_packet = 0;
+
+		for (stream = 0; stream < dev->n_streams; stream++, i++) {
+			sub = dev->sub_capture[stream];
+			if (dev->input_panic)
+				usb_buf[i] = 0;
+
+			if (sub) {
+				struct snd_pcm_runtime *rt = sub->runtime;
+				char *audio_buf = rt->dma_area;
+				int sz = frames_to_bytes(rt, rt->buffer_size);
+				audio_buf[dev->audio_in_buf_pos[stream]++] =
+					usb_buf[i];
+				dev->period_in_count[stream]++;
+				if (dev->audio_in_buf_pos[stream] == sz)
+					dev->audio_in_buf_pos[stream] = 0;
+			}
+		}
+	}
+}
+
+static void read_in_urb_mode3(struct snd_usb_caiaqdev *dev,
+			      const struct urb *urb,
+			      const struct usb_iso_packet_descriptor *iso)
+{
+	unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+	int stream, i;
+
+	/* paranoia check */
+	if (iso->actual_length % (BYTES_PER_SAMPLE_USB * CHANNELS_PER_STREAM))
+		return;
+
+	for (i = 0; i < iso->actual_length;) {
+		for (stream = 0; stream < dev->n_streams; stream++) {
+			struct snd_pcm_substream *sub = dev->sub_capture[stream];
+			char *audio_buf = NULL;
+			int c, n, sz = 0;
+
+			if (sub && !dev->input_panic) {
+				struct snd_pcm_runtime *rt = sub->runtime;
+				audio_buf = rt->dma_area;
+				sz = frames_to_bytes(rt, rt->buffer_size);
+			}
+
+			for (c = 0; c < CHANNELS_PER_STREAM; c++) {
+				/* 3 audio data bytes, followed by 1 check byte */
+				if (audio_buf) {
+					for (n = 0; n < BYTES_PER_SAMPLE; n++) {
+						audio_buf[dev->audio_in_buf_pos[stream]++] = usb_buf[i+n];
+
+						if (dev->audio_in_buf_pos[stream] == sz)
+							dev->audio_in_buf_pos[stream] = 0;
+					}
+
+					dev->period_in_count[stream] += BYTES_PER_SAMPLE;
+				}
+
+				i += BYTES_PER_SAMPLE;
+
+				if (usb_buf[i] != ((stream << 1) | c) &&
+				    !dev->first_packet) {
+					if (!dev->input_panic)
+						printk(" EXPECTED: %02x got %02x, c %d, stream %d, i %d\n",
+							((stream << 1) | c), usb_buf[i], c, stream, i);
+					dev->input_panic = 1;
+				}
+
+				i++;
+			}
+		}
+	}
+
+	if (dev->first_packet > 0)
+		dev->first_packet--;
+}
+
+static void read_in_urb(struct snd_usb_caiaqdev *dev,
+			const struct urb *urb,
+			const struct usb_iso_packet_descriptor *iso)
+{
+	if (!dev->streaming)
+		return;
+
+	if (iso->actual_length < dev->bpp)
+		return;
+
+	switch (dev->spec.data_alignment) {
+	case 0:
+		read_in_urb_mode0(dev, urb, iso);
+		break;
+	case 2:
+		read_in_urb_mode2(dev, urb, iso);
+		break;
+	case 3:
+		read_in_urb_mode3(dev, urb, iso);
+		break;
+	}
+
+	if ((dev->input_panic || dev->output_panic) && !dev->warned) {
+		debug("streaming error detected %s %s\n",
+				dev->input_panic ? "(input)" : "",
+				dev->output_panic ? "(output)" : "");
+		dev->warned = 1;
+	}
+}
+
+static void fill_out_urb_mode_0(struct snd_usb_caiaqdev *dev,
+				struct urb *urb,
+				const struct usb_iso_packet_descriptor *iso)
+{
+	unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+	struct snd_pcm_substream *sub;
+	int stream, i;
+
+	for (i = 0; i < iso->length;) {
+		for (stream = 0; stream < dev->n_streams; stream++, i++) {
+			sub = dev->sub_playback[stream];
+			if (sub) {
+				struct snd_pcm_runtime *rt = sub->runtime;
+				char *audio_buf = rt->dma_area;
+				int sz = frames_to_bytes(rt, rt->buffer_size);
+				usb_buf[i] =
+					audio_buf[dev->audio_out_buf_pos[stream]];
+				dev->period_out_count[stream]++;
+				dev->audio_out_buf_pos[stream]++;
+				if (dev->audio_out_buf_pos[stream] == sz)
+					dev->audio_out_buf_pos[stream] = 0;
+			} else
+				usb_buf[i] = 0;
+		}
+
+		/* fill in the check bytes */
+		if (dev->spec.data_alignment == 2 &&
+		    i % (dev->n_streams * BYTES_PER_SAMPLE_USB) ==
+		        (dev->n_streams * CHANNELS_PER_STREAM))
+			for (stream = 0; stream < dev->n_streams; stream++, i++)
+				usb_buf[i] = MAKE_CHECKBYTE(dev, stream, i);
+	}
+}
+
+static void fill_out_urb_mode_3(struct snd_usb_caiaqdev *dev,
+				struct urb *urb,
+				const struct usb_iso_packet_descriptor *iso)
+{
+	unsigned char *usb_buf = urb->transfer_buffer + iso->offset;
+	int stream, i;
+
+	for (i = 0; i < iso->length;) {
+		for (stream = 0; stream < dev->n_streams; stream++) {
+			struct snd_pcm_substream *sub = dev->sub_playback[stream];
+			char *audio_buf = NULL;
+			int c, n, sz = 0;
+
+			if (sub) {
+				struct snd_pcm_runtime *rt = sub->runtime;
+				audio_buf = rt->dma_area;
+				sz = frames_to_bytes(rt, rt->buffer_size);
+			}
+
+			for (c = 0; c < CHANNELS_PER_STREAM; c++) {
+				for (n = 0; n < BYTES_PER_SAMPLE; n++) {
+					if (audio_buf) {
+						usb_buf[i+n] = audio_buf[dev->audio_out_buf_pos[stream]++];
+
+						if (dev->audio_out_buf_pos[stream] == sz)
+							dev->audio_out_buf_pos[stream] = 0;
+					} else {
+						usb_buf[i+n] = 0;
+					}
+				}
+
+				if (audio_buf)
+					dev->period_out_count[stream] += BYTES_PER_SAMPLE;
+
+				i += BYTES_PER_SAMPLE;
+
+				/* fill in the check byte pattern */
+				usb_buf[i++] = (stream << 1) | c;
+			}
+		}
+	}
+}
+
+static inline void fill_out_urb(struct snd_usb_caiaqdev *dev,
+				struct urb *urb,
+				const struct usb_iso_packet_descriptor *iso)
+{
+	switch (dev->spec.data_alignment) {
+	case 0:
+	case 2:
+		fill_out_urb_mode_0(dev, urb, iso);
+		break;
+	case 3:
+		fill_out_urb_mode_3(dev, urb, iso);
+		break;
+	}
+}
+
+static void read_completed(struct urb *urb)
+{
+	struct snd_usb_caiaq_cb_info *info = urb->context;
+	struct snd_usb_caiaqdev *dev;
+	struct urb *out;
+	int frame, len, send_it = 0, outframe = 0;
+
+	if (urb->status || !info)
+		return;
+
+	dev = info->dev;
+
+	if (!dev->streaming)
+		return;
+
+	out = dev->data_urbs_out[info->index];
+
+	/* read the recently received packet and send back one which has
+	 * the same layout */
+	for (frame = 0; frame < FRAMES_PER_URB; frame++) {
+		if (urb->iso_frame_desc[frame].status)
+			continue;
+
+		len = urb->iso_frame_desc[outframe].actual_length;
+		out->iso_frame_desc[outframe].length = len;
+		out->iso_frame_desc[outframe].actual_length = 0;
+		out->iso_frame_desc[outframe].offset = BYTES_PER_FRAME * frame;
+
+		if (len > 0) {
+			spin_lock(&dev->spinlock);
+			fill_out_urb(dev, out, &out->iso_frame_desc[outframe]);
+			read_in_urb(dev, urb, &urb->iso_frame_desc[frame]);
+			spin_unlock(&dev->spinlock);
+			check_for_elapsed_periods(dev, dev->sub_playback);
+			check_for_elapsed_periods(dev, dev->sub_capture);
+			send_it = 1;
+		}
+
+		outframe++;
+	}
+
+	if (send_it) {
+		out->number_of_packets = FRAMES_PER_URB;
+		out->transfer_flags = URB_ISO_ASAP;
+		usb_submit_urb(out, GFP_ATOMIC);
+	}
+
+	/* re-submit inbound urb */
+	for (frame = 0; frame < FRAMES_PER_URB; frame++) {
+		urb->iso_frame_desc[frame].offset = BYTES_PER_FRAME * frame;
+		urb->iso_frame_desc[frame].length = BYTES_PER_FRAME;
+		urb->iso_frame_desc[frame].actual_length = 0;
+	}
+
+	urb->number_of_packets = FRAMES_PER_URB;
+	urb->transfer_flags = URB_ISO_ASAP;
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static void write_completed(struct urb *urb)
+{
+	struct snd_usb_caiaq_cb_info *info = urb->context;
+	struct snd_usb_caiaqdev *dev = info->dev;
+
+	if (!dev->output_running) {
+		dev->output_running = 1;
+		wake_up(&dev->prepare_wait_queue);
+	}
+}
+
+static struct urb **alloc_urbs(struct snd_usb_caiaqdev *dev, int dir, int *ret)
+{
+	int i, frame;
+	struct urb **urbs;
+	struct usb_device *usb_dev = dev->chip.dev;
+	unsigned int pipe;
+
+	pipe = (dir == SNDRV_PCM_STREAM_PLAYBACK) ?
+		usb_sndisocpipe(usb_dev, ENDPOINT_PLAYBACK) :
+		usb_rcvisocpipe(usb_dev, ENDPOINT_CAPTURE);
+
+	urbs = kmalloc(N_URBS * sizeof(*urbs), GFP_KERNEL);
+	if (!urbs) {
+		log("unable to kmalloc() urbs, OOM!?\n");
+		*ret = -ENOMEM;
+		return NULL;
+	}
+
+	for (i = 0; i < N_URBS; i++) {
+		urbs[i] = usb_alloc_urb(FRAMES_PER_URB, GFP_KERNEL);
+		if (!urbs[i]) {
+			log("unable to usb_alloc_urb(), OOM!?\n");
+			*ret = -ENOMEM;
+			return urbs;
+		}
+
+		urbs[i]->transfer_buffer =
+			kmalloc(FRAMES_PER_URB * BYTES_PER_FRAME, GFP_KERNEL);
+		if (!urbs[i]->transfer_buffer) {
+			log("unable to kmalloc() transfer buffer, OOM!?\n");
+			*ret = -ENOMEM;
+			return urbs;
+		}
+
+		for (frame = 0; frame < FRAMES_PER_URB; frame++) {
+			struct usb_iso_packet_descriptor *iso =
+				&urbs[i]->iso_frame_desc[frame];
+
+			iso->offset = BYTES_PER_FRAME * frame;
+			iso->length = BYTES_PER_FRAME;
+		}
+
+		urbs[i]->dev = usb_dev;
+		urbs[i]->pipe = pipe;
+		urbs[i]->transfer_buffer_length = FRAMES_PER_URB
+						* BYTES_PER_FRAME;
+		urbs[i]->context = &dev->data_cb_info[i];
+		urbs[i]->interval = 1;
+		urbs[i]->transfer_flags = URB_ISO_ASAP;
+		urbs[i]->number_of_packets = FRAMES_PER_URB;
+		urbs[i]->complete = (dir == SNDRV_PCM_STREAM_CAPTURE) ?
+					read_completed : write_completed;
+	}
+
+	*ret = 0;
+	return urbs;
+}
+
+static void free_urbs(struct urb **urbs)
+{
+	int i;
+
+	if (!urbs)
+		return;
+
+	for (i = 0; i < N_URBS; i++) {
+		if (!urbs[i])
+			continue;
+
+		usb_kill_urb(urbs[i]);
+		kfree(urbs[i]->transfer_buffer);
+		usb_free_urb(urbs[i]);
+	}
+
+	kfree(urbs);
+}
+
+int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev)
+{
+	int i, ret;
+
+	dev->n_audio_in  = max(dev->spec.num_analog_audio_in,
+			       dev->spec.num_digital_audio_in) /
+				CHANNELS_PER_STREAM;
+	dev->n_audio_out = max(dev->spec.num_analog_audio_out,
+			       dev->spec.num_digital_audio_out) /
+				CHANNELS_PER_STREAM;
+	dev->n_streams = max(dev->n_audio_in, dev->n_audio_out);
+
+	debug("dev->n_audio_in = %d\n", dev->n_audio_in);
+	debug("dev->n_audio_out = %d\n", dev->n_audio_out);
+	debug("dev->n_streams = %d\n", dev->n_streams);
+
+	if (dev->n_streams > MAX_STREAMS) {
+		log("unable to initialize device, too many streams.\n");
+		return -EINVAL;
+	}
+
+	ret = snd_pcm_new(dev->chip.card, dev->product_name, 0,
+			dev->n_audio_out, dev->n_audio_in, &dev->pcm);
+
+	if (ret < 0) {
+		log("snd_pcm_new() returned %d\n", ret);
+		return ret;
+	}
+
+	dev->pcm->private_data = dev;
+	strcpy(dev->pcm->name, dev->product_name);
+
+	memset(dev->sub_playback, 0, sizeof(dev->sub_playback));
+	memset(dev->sub_capture, 0, sizeof(dev->sub_capture));
+
+	memcpy(&dev->pcm_info, &snd_usb_caiaq_pcm_hardware,
+			sizeof(snd_usb_caiaq_pcm_hardware));
+
+	/* setup samplerates */
+	dev->samplerates = dev->pcm_info.rates;
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_SESSIONIO):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_GUITARRIGMOBILE):
+		dev->samplerates |= SNDRV_PCM_RATE_192000;
+		/* fall thru */
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO2DJ):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+		dev->samplerates |= SNDRV_PCM_RATE_88200;
+		break;
+	}
+
+	snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				&snd_usb_caiaq_ops);
+	snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE,
+				&snd_usb_caiaq_ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(dev->pcm,
+					SNDRV_DMA_TYPE_CONTINUOUS,
+					snd_dma_continuous_data(GFP_KERNEL),
+					MAX_BUFFER_SIZE, MAX_BUFFER_SIZE);
+
+	dev->data_cb_info =
+		kmalloc(sizeof(struct snd_usb_caiaq_cb_info) * N_URBS,
+					GFP_KERNEL);
+
+	if (!dev->data_cb_info)
+		return -ENOMEM;
+
+	for (i = 0; i < N_URBS; i++) {
+		dev->data_cb_info[i].dev = dev;
+		dev->data_cb_info[i].index = i;
+	}
+
+	dev->data_urbs_in = alloc_urbs(dev, SNDRV_PCM_STREAM_CAPTURE, &ret);
+	if (ret < 0) {
+		kfree(dev->data_cb_info);
+		free_urbs(dev->data_urbs_in);
+		return ret;
+	}
+
+	dev->data_urbs_out = alloc_urbs(dev, SNDRV_PCM_STREAM_PLAYBACK, &ret);
+	if (ret < 0) {
+		kfree(dev->data_cb_info);
+		free_urbs(dev->data_urbs_in);
+		free_urbs(dev->data_urbs_out);
+		return ret;
+	}
+
+	return 0;
+}
+
+void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *dev)
+{
+	debug("%s(%p)\n", __func__, dev);
+	stream_stop(dev);
+	free_urbs(dev->data_urbs_in);
+	free_urbs(dev->data_urbs_out);
+	kfree(dev->data_cb_info);
+}
+
diff --git a/linux/sound/usb/caiaq/audio.h b/linux/sound/usb/caiaq/audio.h
new file mode 100644
index 0000000..8ab1f8d
--- /dev/null
+++ b/linux/sound/usb/caiaq/audio.h
@@ -0,0 +1,7 @@
+#ifndef CAIAQ_AUDIO_H
+#define CAIAQ_AUDIO_H
+
+int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *dev);
+void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *dev);
+
+#endif /* CAIAQ_AUDIO_H */
diff --git a/linux/sound/usb/caiaq/control.c b/linux/sound/usb/caiaq/control.c
new file mode 100644
index 0000000..00e5d0a
--- /dev/null
+++ b/linux/sound/usb/caiaq/control.c
@@ -0,0 +1,559 @@
+/*
+ *   Copyright (c) 2007 Daniel Mack
+ *   friendly supported by NI.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "control.h"
+
+#define CNT_INTVAL 0x10000
+
+static int control_info(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
+	struct snd_usb_caiaqdev *dev = caiaqdev(chip->card);
+	int pos = kcontrol->private_value;
+	int is_intval = pos & CNT_INTVAL;
+	int maxval = 63;
+
+	uinfo->count = 1;
+	pos &= ~CNT_INTVAL;
+
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
+		if (pos == 0) {
+			/* current input mode of A8DJ and A4DJ */
+			uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+			uinfo->value.integer.min = 0;
+			uinfo->value.integer.max = 2;
+			return 0;
+		}
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		maxval = 127;
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+		maxval = 31;
+		break;
+	}
+
+	if (is_intval) {
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = maxval;
+	} else {
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = 1;
+	}
+
+	return 0;
+}
+
+static int control_get(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
+	struct snd_usb_caiaqdev *dev = caiaqdev(chip->card);
+	int pos = kcontrol->private_value;
+
+	if (pos & CNT_INTVAL)
+		ucontrol->value.integer.value[0]
+			= dev->control_state[pos & ~CNT_INTVAL];
+	else
+		ucontrol->value.integer.value[0]
+			= !!(dev->control_state[pos / 8] & (1 << pos % 8));
+
+	return 0;
+}
+
+static int control_put(struct snd_kcontrol *kcontrol,
+		       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol);
+	struct snd_usb_caiaqdev *dev = caiaqdev(chip->card);
+	int pos = kcontrol->private_value;
+	int v = ucontrol->value.integer.value[0];
+	unsigned char cmd = EP1_CMD_WRITE_IO;
+
+	if (dev->chip.usb_id ==
+		USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1))
+		cmd = EP1_CMD_DIMM_LEDS;
+
+	if (pos & CNT_INTVAL) {
+		int i = pos & ~CNT_INTVAL;
+
+		dev->control_state[i] = v;
+
+		if (dev->chip.usb_id ==
+			USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4)) {
+			int actual_len;
+
+			dev->ep8_out_buf[0] = i;
+			dev->ep8_out_buf[1] = v;
+
+			usb_bulk_msg(dev->chip.dev,
+				     usb_sndbulkpipe(dev->chip.dev, 8),
+				     dev->ep8_out_buf, sizeof(dev->ep8_out_buf),
+				     &actual_len, 200);
+		} else {
+			snd_usb_caiaq_send_command(dev, cmd,
+					dev->control_state, sizeof(dev->control_state));
+		}
+	} else {
+		if (v)
+			dev->control_state[pos / 8] |= 1 << (pos % 8);
+		else
+			dev->control_state[pos / 8] &= ~(1 << (pos % 8));
+
+		snd_usb_caiaq_send_command(dev, cmd,
+				dev->control_state, sizeof(dev->control_state));
+	}
+
+	return 1;
+}
+
+static struct snd_kcontrol_new kcontrol_template __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_HWDEP,
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.index = 0,
+	.info = control_info,
+	.get  = control_get,
+	.put  = control_put,
+	/* name and private_value filled later */
+};
+
+struct caiaq_controller {
+	char *name;
+	int index;
+};
+
+static struct caiaq_controller ak1_controller[] = {
+	{ "LED left", 	2 },
+	{ "LED middle", 1 },
+	{ "LED right", 	0 },
+	{ "LED ring", 	3 }
+};
+
+static struct caiaq_controller rk2_controller[] = {
+	{ "LED 1",		5  },
+	{ "LED 2",		4  },
+	{ "LED 3",		3  },
+	{ "LED 4",		2  },
+	{ "LED 5",		1  },
+	{ "LED 6",		0  },
+	{ "LED pedal",		6  },
+	{ "LED 7seg_1b",	8  },
+	{ "LED 7seg_1c",	9  },
+	{ "LED 7seg_2a",	10 },
+	{ "LED 7seg_2b",	11 },
+	{ "LED 7seg_2c",	12 },
+	{ "LED 7seg_2d",	13 },
+	{ "LED 7seg_2e",	14 },
+	{ "LED 7seg_2f",	15 },
+	{ "LED 7seg_2g",	16 },
+	{ "LED 7seg_3a",	17 },
+	{ "LED 7seg_3b",	18 },
+	{ "LED 7seg_3c",	19 },
+	{ "LED 7seg_3d",	20 },
+	{ "LED 7seg_3e",	21 },
+	{ "LED 7seg_3f",	22 },
+	{ "LED 7seg_3g",	23 }
+};
+
+static struct caiaq_controller rk3_controller[] = {
+	{ "LED 7seg_1a",        0 + 0 },
+	{ "LED 7seg_1b",        0 + 1 },
+	{ "LED 7seg_1c",        0 + 2 },
+	{ "LED 7seg_1d",        0 + 3 },
+	{ "LED 7seg_1e",        0 + 4 },
+	{ "LED 7seg_1f",        0 + 5 },
+	{ "LED 7seg_1g",        0 + 6 },
+	{ "LED 7seg_1p",        0 + 7 },
+
+	{ "LED 7seg_2a",        8 + 0 },
+	{ "LED 7seg_2b",        8 + 1 },
+	{ "LED 7seg_2c",        8 + 2 },
+	{ "LED 7seg_2d",        8 + 3 },
+	{ "LED 7seg_2e",        8 + 4 },
+	{ "LED 7seg_2f",        8 + 5 },
+	{ "LED 7seg_2g",        8 + 6 },
+	{ "LED 7seg_2p",        8 + 7 },
+
+	{ "LED 7seg_3a",        16 + 0 },
+	{ "LED 7seg_3b",        16 + 1 },
+	{ "LED 7seg_3c",        16 + 2 },
+	{ "LED 7seg_3d",        16 + 3 },
+	{ "LED 7seg_3e",        16 + 4 },
+	{ "LED 7seg_3f",        16 + 5 },
+	{ "LED 7seg_3g",        16 + 6 },
+	{ "LED 7seg_3p",        16 + 7 },
+
+	{ "LED 7seg_4a",        24 + 0 },
+	{ "LED 7seg_4b",        24 + 1 },
+	{ "LED 7seg_4c",        24 + 2 },
+	{ "LED 7seg_4d",        24 + 3 },
+	{ "LED 7seg_4e",        24 + 4 },
+	{ "LED 7seg_4f",        24 + 5 },
+	{ "LED 7seg_4g",        24 + 6 },
+	{ "LED 7seg_4p",        24 + 7 },
+
+	{ "LED 1",		32 + 0 },
+	{ "LED 2",		32 + 1 },
+	{ "LED 3",		32 + 2 },
+	{ "LED 4",		32 + 3 },
+	{ "LED 5",		32 + 4 },
+	{ "LED 6",		32 + 5 },
+	{ "LED 7",		32 + 6 },
+	{ "LED 8",		32 + 7 },
+	{ "LED pedal",		32 + 8 }
+};
+
+static struct caiaq_controller kore_controller[] = {
+	{ "LED F1",		8   | CNT_INTVAL },
+	{ "LED F2",		12  | CNT_INTVAL },
+	{ "LED F3",		0   | CNT_INTVAL },
+	{ "LED F4",		4   | CNT_INTVAL },
+	{ "LED F5",		11  | CNT_INTVAL },
+	{ "LED F6",		15  | CNT_INTVAL },
+	{ "LED F7",		3   | CNT_INTVAL },
+	{ "LED F8",		7   | CNT_INTVAL },
+	{ "LED touch1",	     	10  | CNT_INTVAL },
+	{ "LED touch2",	     	14  | CNT_INTVAL },
+	{ "LED touch3",	     	2   | CNT_INTVAL },
+	{ "LED touch4",	     	6   | CNT_INTVAL },
+	{ "LED touch5",	     	9   | CNT_INTVAL },
+	{ "LED touch6",	     	13  | CNT_INTVAL },
+	{ "LED touch7",	     	1   | CNT_INTVAL },
+	{ "LED touch8",	     	5   | CNT_INTVAL },
+	{ "LED left",	       	18  | CNT_INTVAL },
+	{ "LED right",	     	22  | CNT_INTVAL },
+	{ "LED up",		16  | CNT_INTVAL },
+	{ "LED down",	       	20  | CNT_INTVAL },
+	{ "LED stop",	       	23  | CNT_INTVAL },
+	{ "LED play",	       	21  | CNT_INTVAL },
+	{ "LED record",	     	19  | CNT_INTVAL },
+	{ "LED listen",		17  | CNT_INTVAL },
+	{ "LED lcd",		30  | CNT_INTVAL },
+	{ "LED menu",		28  | CNT_INTVAL },
+	{ "LED sound",	 	31  | CNT_INTVAL },
+	{ "LED esc",		29  | CNT_INTVAL },
+	{ "LED view",		27  | CNT_INTVAL },
+	{ "LED enter",		24  | CNT_INTVAL },
+	{ "LED control",	26  | CNT_INTVAL }
+};
+
+static struct caiaq_controller a8dj_controller[] = {
+	{ "Current input mode",			0 | CNT_INTVAL 	},
+	{ "GND lift for TC Vinyl mode", 	24 + 0 		},
+	{ "GND lift for TC CD/Line mode", 	24 + 1 		},
+	{ "GND lift for phono mode", 		24 + 2 		},
+	{ "Software lock", 			40 		}
+};
+
+static struct caiaq_controller a4dj_controller[] = {
+	{ "Current input mode",	0 | CNT_INTVAL 	}
+};
+
+static struct caiaq_controller kontrolx1_controller[] = {
+	{ "LED FX A: ON",		7 | CNT_INTVAL	},
+	{ "LED FX A: 1",		6 | CNT_INTVAL	},
+	{ "LED FX A: 2",		5 | CNT_INTVAL	},
+	{ "LED FX A: 3",		4 | CNT_INTVAL	},
+	{ "LED FX B: ON",		3 | CNT_INTVAL	},
+	{ "LED FX B: 1",		2 | CNT_INTVAL	},
+	{ "LED FX B: 2",		1 | CNT_INTVAL	},
+	{ "LED FX B: 3",		0 | CNT_INTVAL	},
+
+	{ "LED Hotcue",			28 | CNT_INTVAL	},
+	{ "LED Shift (white)",		29 | CNT_INTVAL	},
+	{ "LED Shift (green)",		30 | CNT_INTVAL	},
+
+	{ "LED Deck A: FX1",		24 | CNT_INTVAL	},
+	{ "LED Deck A: FX2",		25 | CNT_INTVAL	},
+	{ "LED Deck A: IN",		17 | CNT_INTVAL	},
+	{ "LED Deck A: OUT",		16 | CNT_INTVAL	},
+	{ "LED Deck A: < BEAT",		19 | CNT_INTVAL	},
+	{ "LED Deck A: BEAT >",		18 | CNT_INTVAL	},
+	{ "LED Deck A: CUE/ABS",	21 | CNT_INTVAL	},
+	{ "LED Deck A: CUP/REL",	20 | CNT_INTVAL	},
+	{ "LED Deck A: PLAY",		23 | CNT_INTVAL	},
+	{ "LED Deck A: SYNC",		22 | CNT_INTVAL	},
+
+	{ "LED Deck B: FX1",		26 | CNT_INTVAL	},
+	{ "LED Deck B: FX2",		27 | CNT_INTVAL	},
+	{ "LED Deck B: IN",		15 | CNT_INTVAL	},
+	{ "LED Deck B: OUT",		14 | CNT_INTVAL	},
+	{ "LED Deck B: < BEAT",		13 | CNT_INTVAL	},
+	{ "LED Deck B: BEAT >",		12 | CNT_INTVAL	},
+	{ "LED Deck B: CUE/ABS",	11 | CNT_INTVAL	},
+	{ "LED Deck B: CUP/REL",	10 | CNT_INTVAL	},
+	{ "LED Deck B: PLAY",		9  | CNT_INTVAL	},
+	{ "LED Deck B: SYNC",		8  | CNT_INTVAL	},
+};
+
+static struct caiaq_controller kontrols4_controller[] = {
+	{ "LED: Master: Quant",			10  | CNT_INTVAL },
+	{ "LED: Master: Headphone",		11  | CNT_INTVAL },
+	{ "LED: Master: Master",		12  | CNT_INTVAL },
+	{ "LED: Master: Snap",			14  | CNT_INTVAL },
+	{ "LED: Master: Warning",		15  | CNT_INTVAL },
+	{ "LED: Master: Master button",		112 | CNT_INTVAL },
+	{ "LED: Master: Snap button",		113 | CNT_INTVAL },
+	{ "LED: Master: Rec",			118 | CNT_INTVAL },
+	{ "LED: Master: Size",			119 | CNT_INTVAL },
+	{ "LED: Master: Quant button",		120 | CNT_INTVAL },
+	{ "LED: Master: Browser button",	121 | CNT_INTVAL },
+	{ "LED: Master: Play button",		126 | CNT_INTVAL },
+	{ "LED: Master: Undo button",		127 | CNT_INTVAL },
+
+	{ "LED: Channel A: >",			4   | CNT_INTVAL },
+	{ "LED: Channel A: <",			5   | CNT_INTVAL },
+	{ "LED: Channel A: Meter 1",		97  | CNT_INTVAL },
+	{ "LED: Channel A: Meter 2",		98  | CNT_INTVAL },
+	{ "LED: Channel A: Meter 3",		99  | CNT_INTVAL },
+	{ "LED: Channel A: Meter 4",		100 | CNT_INTVAL },
+	{ "LED: Channel A: Meter 5",		101 | CNT_INTVAL },
+	{ "LED: Channel A: Meter 6",		102 | CNT_INTVAL },
+	{ "LED: Channel A: Meter clip",		103 | CNT_INTVAL },
+	{ "LED: Channel A: Active",		114 | CNT_INTVAL },
+	{ "LED: Channel A: Cue",		116 | CNT_INTVAL },
+	{ "LED: Channel A: FX1",		149 | CNT_INTVAL },
+	{ "LED: Channel A: FX2",		148 | CNT_INTVAL },
+
+	{ "LED: Channel B: >",			2   | CNT_INTVAL },
+	{ "LED: Channel B: <",			3   | CNT_INTVAL },
+	{ "LED: Channel B: Meter 1",		89  | CNT_INTVAL },
+	{ "LED: Channel B: Meter 2",		90  | CNT_INTVAL },
+	{ "LED: Channel B: Meter 3",		91  | CNT_INTVAL },
+	{ "LED: Channel B: Meter 4",		92  | CNT_INTVAL },
+	{ "LED: Channel B: Meter 5",		93  | CNT_INTVAL },
+	{ "LED: Channel B: Meter 6",		94  | CNT_INTVAL },
+	{ "LED: Channel B: Meter clip",		95  | CNT_INTVAL },
+	{ "LED: Channel B: Active",		122 | CNT_INTVAL },
+	{ "LED: Channel B: Cue",		125 | CNT_INTVAL },
+	{ "LED: Channel B: FX1",		147 | CNT_INTVAL },
+	{ "LED: Channel B: FX2",		146 | CNT_INTVAL },
+
+	{ "LED: Channel C: >",			6   | CNT_INTVAL },
+	{ "LED: Channel C: <",			7   | CNT_INTVAL },
+	{ "LED: Channel C: Meter 1",		105 | CNT_INTVAL },
+	{ "LED: Channel C: Meter 2",		106 | CNT_INTVAL },
+	{ "LED: Channel C: Meter 3",		107 | CNT_INTVAL },
+	{ "LED: Channel C: Meter 4",		108 | CNT_INTVAL },
+	{ "LED: Channel C: Meter 5",		109 | CNT_INTVAL },
+	{ "LED: Channel C: Meter 6",		110 | CNT_INTVAL },
+	{ "LED: Channel C: Meter clip",		111 | CNT_INTVAL },
+	{ "LED: Channel C: Active",		115 | CNT_INTVAL },
+	{ "LED: Channel C: Cue",		117 | CNT_INTVAL },
+	{ "LED: Channel C: FX1",		151 | CNT_INTVAL },
+	{ "LED: Channel C: FX2",		150 | CNT_INTVAL },
+
+	{ "LED: Channel D: >",			0   | CNT_INTVAL },
+	{ "LED: Channel D: <",			1   | CNT_INTVAL },
+	{ "LED: Channel D: Meter 1",		81  | CNT_INTVAL },
+	{ "LED: Channel D: Meter 2",		82  | CNT_INTVAL },
+	{ "LED: Channel D: Meter 3",		83  | CNT_INTVAL },
+	{ "LED: Channel D: Meter 4",		84  | CNT_INTVAL },
+	{ "LED: Channel D: Meter 5",		85  | CNT_INTVAL },
+	{ "LED: Channel D: Meter 6",		86  | CNT_INTVAL },
+	{ "LED: Channel D: Meter clip",		87  | CNT_INTVAL },
+	{ "LED: Channel D: Active",		123 | CNT_INTVAL },
+	{ "LED: Channel D: Cue",		124 | CNT_INTVAL },
+	{ "LED: Channel D: FX1",		145 | CNT_INTVAL },
+	{ "LED: Channel D: FX2",		144 | CNT_INTVAL },
+
+	{ "LED: Deck A: 1 (blue)",		22  | CNT_INTVAL },
+	{ "LED: Deck A: 1 (green)",		23  | CNT_INTVAL },
+	{ "LED: Deck A: 2 (blue)",		20  | CNT_INTVAL },
+	{ "LED: Deck A: 2 (green)",		21  | CNT_INTVAL },
+	{ "LED: Deck A: 3 (blue)",		18  | CNT_INTVAL },
+	{ "LED: Deck A: 3 (green)",		19  | CNT_INTVAL },
+	{ "LED: Deck A: 4 (blue)",		16  | CNT_INTVAL },
+	{ "LED: Deck A: 4 (green)",		17  | CNT_INTVAL },
+	{ "LED: Deck A: Load",			44  | CNT_INTVAL },
+	{ "LED: Deck A: Deck C button",		45  | CNT_INTVAL },
+	{ "LED: Deck A: In",			47  | CNT_INTVAL },
+	{ "LED: Deck A: Out",			46  | CNT_INTVAL },
+	{ "LED: Deck A: Shift",			24  | CNT_INTVAL },
+	{ "LED: Deck A: Sync",			27  | CNT_INTVAL },
+	{ "LED: Deck A: Cue",			26  | CNT_INTVAL },
+	{ "LED: Deck A: Play",			25  | CNT_INTVAL },
+	{ "LED: Deck A: Tempo up",		33  | CNT_INTVAL },
+	{ "LED: Deck A: Tempo down",		32  | CNT_INTVAL },
+	{ "LED: Deck A: Master",		34  | CNT_INTVAL },
+	{ "LED: Deck A: Keylock",		35  | CNT_INTVAL },
+	{ "LED: Deck A: Deck A",		37  | CNT_INTVAL },
+	{ "LED: Deck A: Deck C",		36  | CNT_INTVAL },
+	{ "LED: Deck A: Samples",		38  | CNT_INTVAL },
+	{ "LED: Deck A: On Air",		39  | CNT_INTVAL },
+	{ "LED: Deck A: Sample 1",		31  | CNT_INTVAL },
+	{ "LED: Deck A: Sample 2",		30  | CNT_INTVAL },
+	{ "LED: Deck A: Sample 3",		29  | CNT_INTVAL },
+	{ "LED: Deck A: Sample 4",		28  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - A",		55  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - B",		54  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - C",		53  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - D",		52  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - E",		51  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - F",		50  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - G",		49  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 1 - dot",		48  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - A",		63  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - B",		62  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - C",		61  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - D",		60  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - E",		59  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - F",		58  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - G",		57  | CNT_INTVAL },
+	{ "LED: Deck A: Digit 2 - dot",		56  | CNT_INTVAL },
+
+	{ "LED: Deck B: 1 (blue)",		78  | CNT_INTVAL },
+	{ "LED: Deck B: 1 (green)",		79  | CNT_INTVAL },
+	{ "LED: Deck B: 2 (blue)",		76  | CNT_INTVAL },
+	{ "LED: Deck B: 2 (green)",		77  | CNT_INTVAL },
+	{ "LED: Deck B: 3 (blue)",		74  | CNT_INTVAL },
+	{ "LED: Deck B: 3 (green)",		75  | CNT_INTVAL },
+	{ "LED: Deck B: 4 (blue)",		72  | CNT_INTVAL },
+	{ "LED: Deck B: 4 (green)",		73  | CNT_INTVAL },
+	{ "LED: Deck B: Load",			180 | CNT_INTVAL },
+	{ "LED: Deck B: Deck D button",		181 | CNT_INTVAL },
+	{ "LED: Deck B: In",			183 | CNT_INTVAL },
+	{ "LED: Deck B: Out",			182 | CNT_INTVAL },
+	{ "LED: Deck B: Shift",			64  | CNT_INTVAL },
+	{ "LED: Deck B: Sync",			67  | CNT_INTVAL },
+	{ "LED: Deck B: Cue",			66  | CNT_INTVAL },
+	{ "LED: Deck B: Play",			65  | CNT_INTVAL },
+	{ "LED: Deck B: Tempo up",		185 | CNT_INTVAL },
+	{ "LED: Deck B: Tempo down",		184 | CNT_INTVAL },
+	{ "LED: Deck B: Master",		186 | CNT_INTVAL },
+	{ "LED: Deck B: Keylock",		187 | CNT_INTVAL },
+	{ "LED: Deck B: Deck B",		189 | CNT_INTVAL },
+	{ "LED: Deck B: Deck D",		188 | CNT_INTVAL },
+	{ "LED: Deck B: Samples",		190 | CNT_INTVAL },
+	{ "LED: Deck B: On Air",		191 | CNT_INTVAL },
+	{ "LED: Deck B: Sample 1",		71  | CNT_INTVAL },
+	{ "LED: Deck B: Sample 2",		70  | CNT_INTVAL },
+	{ "LED: Deck B: Sample 3",		69  | CNT_INTVAL },
+	{ "LED: Deck B: Sample 4",		68  | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - A",		175 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - B",		174 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - C",		173 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - D",		172 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - E",		171 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - F",		170 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - G",		169 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 1 - dot",		168 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - A",		167 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - B",		166 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - C",		165 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - D",		164 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - E",		163 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - F",		162 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - G",		161 | CNT_INTVAL },
+	{ "LED: Deck B: Digit 2 - dot",		160 | CNT_INTVAL },
+
+	{ "LED: FX1: dry/wet",			153 | CNT_INTVAL },
+	{ "LED: FX1: 1",			154 | CNT_INTVAL },
+	{ "LED: FX1: 2",			155 | CNT_INTVAL },
+	{ "LED: FX1: 3",			156 | CNT_INTVAL },
+	{ "LED: FX1: Mode",			157 | CNT_INTVAL },
+	{ "LED: FX2: dry/wet",			129 | CNT_INTVAL },
+	{ "LED: FX2: 1",			130 | CNT_INTVAL },
+	{ "LED: FX2: 2",			131 | CNT_INTVAL },
+	{ "LED: FX2: 3",			132 | CNT_INTVAL },
+	{ "LED: FX2: Mode",			133 | CNT_INTVAL },
+};
+
+static int __devinit add_controls(struct caiaq_controller *c, int num,
+				  struct snd_usb_caiaqdev *dev)
+{
+	int i, ret;
+	struct snd_kcontrol *kc;
+
+	for (i = 0; i < num; i++, c++) {
+		kcontrol_template.name = c->name;
+		kcontrol_template.private_value = c->index;
+		kc = snd_ctl_new1(&kcontrol_template, dev);
+		ret = snd_ctl_add(dev->chip.card, kc);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+int __devinit snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *dev)
+{
+	int ret = 0;
+
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+		ret = add_controls(ak1_controller,
+			ARRAY_SIZE(ak1_controller), dev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+		ret = add_controls(rk2_controller,
+			ARRAY_SIZE(rk2_controller), dev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+		ret = add_controls(rk3_controller,
+			ARRAY_SIZE(rk3_controller), dev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+		ret = add_controls(kore_controller,
+			ARRAY_SIZE(kore_controller), dev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+		ret = add_controls(a8dj_controller,
+			ARRAY_SIZE(a8dj_controller), dev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ):
+		ret = add_controls(a4dj_controller,
+			ARRAY_SIZE(a4dj_controller), dev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		ret = add_controls(kontrolx1_controller,
+			ARRAY_SIZE(kontrolx1_controller), dev);
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+		ret = add_controls(kontrols4_controller,
+			ARRAY_SIZE(kontrols4_controller), dev);
+		break;
+	}
+
+	return ret;
+}
+
diff --git a/linux/sound/usb/caiaq/control.h b/linux/sound/usb/caiaq/control.h
new file mode 100644
index 0000000..2e7ab1a
--- /dev/null
+++ b/linux/sound/usb/caiaq/control.h
@@ -0,0 +1,6 @@
+#ifndef CAIAQ_CONTROL_H
+#define CAIAQ_CONTROL_H
+
+int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *dev);
+
+#endif /* CAIAQ_CONTROL_H */
diff --git a/linux/sound/usb/caiaq/device.c b/linux/sound/usb/caiaq/device.c
new file mode 100644
index 0000000..6480c32
--- /dev/null
+++ b/linux/sound/usb/caiaq/device.c
@@ -0,0 +1,541 @@
+/*
+ * caiaq.c: ALSA driver for caiaq/NativeInstruments devices
+ *
+ *   Copyright (c) 2007 Daniel Mack <daniel@caiaq.de>
+ *                      Karsten Wiese <fzu@wemgehoertderstaat.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/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/gfp.h>
+#include <linux/usb.h>
+#include <sound/initval.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "audio.h"
+#include "midi.h"
+#include "control.h"
+#include "input.h"
+
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_DESCRIPTION("caiaq USB audio");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Native Instruments, RigKontrol2},"
+			 "{Native Instruments, RigKontrol3},"
+			 "{Native Instruments, Kore Controller},"
+			 "{Native Instruments, Kore Controller 2},"
+			 "{Native Instruments, Audio Kontrol 1},"
+			 "{Native Instruments, Audio 2 DJ},"
+			 "{Native Instruments, Audio 4 DJ},"
+			 "{Native Instruments, Audio 8 DJ},"
+			 "{Native Instruments, Session I/O},"
+			 "{Native Instruments, GuitarRig mobile}"
+			 "{Native Instruments, Traktor Kontrol X1}"
+			 "{Native Instruments, Traktor Kontrol S4}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+static int snd_card_used[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the caiaq sound device");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the caiaq soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable the caiaq soundcard.");
+
+enum {
+	SAMPLERATE_44100	= 0,
+	SAMPLERATE_48000	= 1,
+	SAMPLERATE_96000	= 2,
+	SAMPLERATE_192000	= 3,
+	SAMPLERATE_88200	= 4,
+	SAMPLERATE_INVALID	= 0xff
+};
+
+enum {
+	DEPTH_NONE	= 0,
+	DEPTH_16	= 1,
+	DEPTH_24	= 2,
+	DEPTH_32	= 3
+};
+
+static struct usb_device_id snd_usb_id_table[] = {
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =	USB_PID_RIGKONTROL2
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =	USB_PID_RIGKONTROL3
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =	USB_PID_KORECONTROLLER
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =	USB_PID_KORECONTROLLER2
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_AK1
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_AUDIO8DJ
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_SESSIONIO
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_GUITARRIGMOBILE
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_AUDIO4DJ
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_AUDIO2DJ
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_TRAKTORKONTROLX1
+	},
+	{
+		.match_flags =  USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =     USB_VID_NATIVEINSTRUMENTS,
+		.idProduct =    USB_PID_TRAKTORKONTROLS4
+	},
+	{ /* terminator */ }
+};
+
+static void usb_ep1_command_reply_dispatch (struct urb* urb)
+{
+	int ret;
+	struct snd_usb_caiaqdev *dev = urb->context;
+	unsigned char *buf = urb->transfer_buffer;
+
+	if (urb->status || !dev) {
+		log("received EP1 urb->status = %i\n", urb->status);
+		return;
+	}
+
+	switch(buf[0]) {
+	case EP1_CMD_GET_DEVICE_INFO:
+	 	memcpy(&dev->spec, buf+1, sizeof(struct caiaq_device_spec));
+		dev->spec.fw_version = le16_to_cpu(dev->spec.fw_version);
+		debug("device spec (firmware %d): audio: %d in, %d out, "
+			"MIDI: %d in, %d out, data alignment %d\n",
+			dev->spec.fw_version,
+			dev->spec.num_analog_audio_in,
+			dev->spec.num_analog_audio_out,
+			dev->spec.num_midi_in,
+			dev->spec.num_midi_out,
+			dev->spec.data_alignment);
+
+		dev->spec_received++;
+		wake_up(&dev->ep1_wait_queue);
+		break;
+	case EP1_CMD_AUDIO_PARAMS:
+		dev->audio_parm_answer = buf[1];
+		wake_up(&dev->ep1_wait_queue);
+		break;
+	case EP1_CMD_MIDI_READ:
+		snd_usb_caiaq_midi_handle_input(dev, buf[1], buf + 3, buf[2]);
+		break;
+	case EP1_CMD_READ_IO:
+		if (dev->chip.usb_id ==
+			USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ)) {
+			if (urb->actual_length > sizeof(dev->control_state))
+				urb->actual_length = sizeof(dev->control_state);
+			memcpy(dev->control_state, buf + 1, urb->actual_length);
+			wake_up(&dev->ep1_wait_queue);
+			break;
+		}
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	case EP1_CMD_READ_ERP:
+	case EP1_CMD_READ_ANALOG:
+		snd_usb_caiaq_input_dispatch(dev, buf, urb->actual_length);
+#endif
+		break;
+	}
+
+	dev->ep1_in_urb.actual_length = 0;
+	ret = usb_submit_urb(&dev->ep1_in_urb, GFP_ATOMIC);
+	if (ret < 0)
+		log("unable to submit urb. OOM!?\n");
+}
+
+int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *dev,
+			       unsigned char command,
+			       const unsigned char *buffer,
+			       int len)
+{
+	int actual_len;
+	struct usb_device *usb_dev = dev->chip.dev;
+
+	if (!usb_dev)
+		return -EIO;
+
+	if (len > EP1_BUFSIZE - 1)
+		len = EP1_BUFSIZE - 1;
+
+	if (buffer && len > 0)
+		memcpy(dev->ep1_out_buf+1, buffer, len);
+
+	dev->ep1_out_buf[0] = command;
+	return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1),
+			   dev->ep1_out_buf, len+1, &actual_len, 200);
+}
+
+int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *dev,
+		   		    int rate, int depth, int bpp)
+{
+	int ret;
+	char tmp[5];
+
+	switch (rate) {
+	case 44100:	tmp[0] = SAMPLERATE_44100;   break;
+	case 48000:	tmp[0] = SAMPLERATE_48000;   break;
+	case 88200:	tmp[0] = SAMPLERATE_88200;   break;
+	case 96000:	tmp[0] = SAMPLERATE_96000;   break;
+	case 192000:	tmp[0] = SAMPLERATE_192000;  break;
+	default:	return -EINVAL;
+	}
+
+	switch (depth) {
+	case 16:	tmp[1] = DEPTH_16;   break;
+	case 24:	tmp[1] = DEPTH_24;   break;
+	default:	return -EINVAL;
+	}
+
+	tmp[2] = bpp & 0xff;
+	tmp[3] = bpp >> 8;
+	tmp[4] = 1; /* packets per microframe */
+
+	debug("setting audio params: %d Hz, %d bits, %d bpp\n",
+		rate, depth, bpp);
+
+	dev->audio_parm_answer = -1;
+	ret = snd_usb_caiaq_send_command(dev, EP1_CMD_AUDIO_PARAMS,
+					 tmp, sizeof(tmp));
+
+	if (ret)
+		return ret;
+
+	if (!wait_event_timeout(dev->ep1_wait_queue,
+	    dev->audio_parm_answer >= 0, HZ))
+		return -EPIPE;
+
+	if (dev->audio_parm_answer != 1)
+		debug("unable to set the device's audio params\n");
+	else
+		dev->bpp = bpp;
+
+	return dev->audio_parm_answer == 1 ? 0 : -EINVAL;
+}
+
+int snd_usb_caiaq_set_auto_msg(struct snd_usb_caiaqdev *dev,
+			       int digital, int analog, int erp)
+{
+	char tmp[3] = { digital, analog, erp };
+	return snd_usb_caiaq_send_command(dev, EP1_CMD_AUTO_MSG,
+					  tmp, sizeof(tmp));
+}
+
+static void __devinit setup_card(struct snd_usb_caiaqdev *dev)
+{
+	int ret;
+	char val[4];
+
+	/* device-specific startup specials */
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+		/* RigKontrol2 - display centered dash ('-') */
+		val[0] = 0x00;
+		val[1] = 0x00;
+		val[2] = 0x01;
+		snd_usb_caiaq_send_command(dev, EP1_CMD_WRITE_IO, val, 3);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+		/* RigKontrol2 - display two centered dashes ('--') */
+		val[0] = 0x00;
+		val[1] = 0x40;
+		val[2] = 0x40;
+		val[3] = 0x00;
+		snd_usb_caiaq_send_command(dev, EP1_CMD_WRITE_IO, val, 4);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+		/* Audio Kontrol 1 - make USB-LED stop blinking */
+		val[0] = 0x00;
+		snd_usb_caiaq_send_command(dev, EP1_CMD_WRITE_IO, val, 1);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ):
+		/* Audio 8 DJ - trigger read of current settings */
+		dev->control_state[0] = 0xff;
+		snd_usb_caiaq_set_auto_msg(dev, 1, 0, 0);
+		snd_usb_caiaq_send_command(dev, EP1_CMD_READ_IO, NULL, 0);
+
+		if (!wait_event_timeout(dev->ep1_wait_queue,
+					dev->control_state[0] != 0xff, HZ))
+			return;
+
+		/* fix up some defaults */
+		if ((dev->control_state[1] != 2) ||
+		    (dev->control_state[2] != 3) ||
+		    (dev->control_state[4] != 2)) {
+			dev->control_state[1] = 2;
+			dev->control_state[2] = 3;
+			dev->control_state[4] = 2;
+			snd_usb_caiaq_send_command(dev,
+				EP1_CMD_WRITE_IO, dev->control_state, 6);
+		}
+
+		break;
+	}
+
+	if (dev->spec.num_analog_audio_out +
+	    dev->spec.num_analog_audio_in +
+	    dev->spec.num_digital_audio_out +
+	    dev->spec.num_digital_audio_in > 0) {
+		ret = snd_usb_caiaq_audio_init(dev);
+		if (ret < 0)
+			log("Unable to set up audio system (ret=%d)\n", ret);
+	}
+
+	if (dev->spec.num_midi_in +
+	    dev->spec.num_midi_out > 0) {
+		ret = snd_usb_caiaq_midi_init(dev);
+		if (ret < 0)
+			log("Unable to set up MIDI system (ret=%d)\n", ret);
+	}
+
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	ret = snd_usb_caiaq_input_init(dev);
+	if (ret < 0)
+		log("Unable to set up input system (ret=%d)\n", ret);
+#endif
+
+	/* finally, register the card and all its sub-instances */
+	ret = snd_card_register(dev->chip.card);
+	if (ret < 0) {
+		log("snd_card_register() returned %d\n", ret);
+		snd_card_free(dev->chip.card);
+	}
+
+	ret = snd_usb_caiaq_control_init(dev);
+	if (ret < 0)
+		log("Unable to set up control system (ret=%d)\n", ret);
+}
+
+static int create_card(struct usb_device *usb_dev,
+		       struct usb_interface *intf,
+		       struct snd_card **cardp)
+{
+	int devnum;
+	int err;
+	struct snd_card *card;
+	struct snd_usb_caiaqdev *dev;
+
+	for (devnum = 0; devnum < SNDRV_CARDS; devnum++)
+		if (enable[devnum] && !snd_card_used[devnum])
+			break;
+
+	if (devnum >= SNDRV_CARDS)
+		return -ENODEV;
+
+	err = snd_card_create(index[devnum], id[devnum], THIS_MODULE,
+			      sizeof(struct snd_usb_caiaqdev), &card);
+	if (err < 0)
+		return err;
+
+	dev = caiaqdev(card);
+	dev->chip.dev = usb_dev;
+	dev->chip.card = card;
+	dev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor),
+				  le16_to_cpu(usb_dev->descriptor.idProduct));
+	spin_lock_init(&dev->spinlock);
+	snd_card_set_dev(card, &intf->dev);
+
+	*cardp = card;
+	return 0;
+}
+
+static int __devinit init_card(struct snd_usb_caiaqdev *dev)
+{
+	char *c, usbpath[32];
+	struct usb_device *usb_dev = dev->chip.dev;
+	struct snd_card *card = dev->chip.card;
+	int err, len;
+
+	if (usb_set_interface(usb_dev, 0, 1) != 0) {
+		log("can't set alt interface.\n");
+		return -EIO;
+	}
+
+	usb_init_urb(&dev->ep1_in_urb);
+	usb_init_urb(&dev->midi_out_urb);
+
+	usb_fill_bulk_urb(&dev->ep1_in_urb, usb_dev,
+			  usb_rcvbulkpipe(usb_dev, 0x1),
+			  dev->ep1_in_buf, EP1_BUFSIZE,
+			  usb_ep1_command_reply_dispatch, dev);
+
+	usb_fill_bulk_urb(&dev->midi_out_urb, usb_dev,
+			  usb_sndbulkpipe(usb_dev, 0x1),
+			  dev->midi_out_buf, EP1_BUFSIZE,
+			  snd_usb_caiaq_midi_output_done, dev);
+
+	init_waitqueue_head(&dev->ep1_wait_queue);
+	init_waitqueue_head(&dev->prepare_wait_queue);
+
+	if (usb_submit_urb(&dev->ep1_in_urb, GFP_KERNEL) != 0)
+		return -EIO;
+
+	err = snd_usb_caiaq_send_command(dev, EP1_CMD_GET_DEVICE_INFO, NULL, 0);
+	if (err)
+		return err;
+
+	if (!wait_event_timeout(dev->ep1_wait_queue, dev->spec_received, HZ))
+		return -ENODEV;
+
+	usb_string(usb_dev, usb_dev->descriptor.iManufacturer,
+		   dev->vendor_name, CAIAQ_USB_STR_LEN);
+
+	usb_string(usb_dev, usb_dev->descriptor.iProduct,
+		   dev->product_name, CAIAQ_USB_STR_LEN);
+
+	strlcpy(card->driver, MODNAME, sizeof(card->driver));
+	strlcpy(card->shortname, dev->product_name, sizeof(card->shortname));
+	strlcpy(card->mixername, dev->product_name, sizeof(card->mixername));
+
+	/* if the id was not passed as module option, fill it with a shortened
+	 * version of the product string which does not contain any
+	 * whitespaces */
+
+	if (*card->id == '\0') {
+		char id[sizeof(card->id)];
+
+		memset(id, 0, sizeof(id));
+
+		for (c = card->shortname, len = 0;
+			*c && len < sizeof(card->id); c++)
+			if (*c != ' ')
+				id[len++] = *c;
+
+		snd_card_set_id(card, id);
+	}
+
+	usb_make_path(usb_dev, usbpath, sizeof(usbpath));
+	snprintf(card->longname, sizeof(card->longname),
+		       "%s %s (%s)",
+		       dev->vendor_name, dev->product_name, usbpath);
+
+	setup_card(dev);
+	return 0;
+}
+
+static int __devinit snd_probe(struct usb_interface *intf,
+		     const struct usb_device_id *id)
+{
+	int ret;
+	struct snd_card *card;
+	struct usb_device *device = interface_to_usbdev(intf);
+
+	ret = create_card(device, intf, &card);
+
+	if (ret < 0)
+		return ret;
+
+	usb_set_intfdata(intf, card);
+	ret = init_card(caiaqdev(card));
+	if (ret < 0) {
+		log("unable to init card! (ret=%d)\n", ret);
+		snd_card_free(card);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void snd_disconnect(struct usb_interface *intf)
+{
+	struct snd_usb_caiaqdev *dev;
+	struct snd_card *card = usb_get_intfdata(intf);
+
+	debug("%s(%p)\n", __func__, intf);
+
+	if (!card)
+		return;
+
+	dev = caiaqdev(card);
+	snd_card_disconnect(card);
+
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	snd_usb_caiaq_input_free(dev);
+#endif
+	snd_usb_caiaq_audio_free(dev);
+
+	usb_kill_urb(&dev->ep1_in_urb);
+	usb_kill_urb(&dev->midi_out_urb);
+
+	snd_card_free(card);
+	usb_reset_device(interface_to_usbdev(intf));
+}
+
+
+MODULE_DEVICE_TABLE(usb, snd_usb_id_table);
+static struct usb_driver snd_usb_driver = {
+	.name 		= MODNAME,
+	.probe 		= snd_probe,
+	.disconnect	= snd_disconnect,
+	.id_table 	= snd_usb_id_table,
+};
+
+static int __init snd_module_init(void)
+{
+	return usb_register(&snd_usb_driver);
+}
+
+static void __exit snd_module_exit(void)
+{
+	usb_deregister(&snd_usb_driver);
+}
+
+module_init(snd_module_init)
+module_exit(snd_module_exit)
+
diff --git a/linux/sound/usb/caiaq/device.h b/linux/sound/usb/caiaq/device.h
new file mode 100644
index 0000000..e3d8a3e
--- /dev/null
+++ b/linux/sound/usb/caiaq/device.h
@@ -0,0 +1,137 @@
+#ifndef CAIAQ_DEVICE_H
+#define CAIAQ_DEVICE_H
+
+#include "../usbaudio.h"
+
+#define USB_VID_NATIVEINSTRUMENTS 0x17cc
+
+#define USB_PID_RIGKONTROL2		0x1969
+#define USB_PID_RIGKONTROL3		0x1940
+#define USB_PID_KORECONTROLLER		0x4711
+#define USB_PID_KORECONTROLLER2		0x4712
+#define USB_PID_AK1			0x0815
+#define USB_PID_AUDIO2DJ		0x041c
+#define USB_PID_AUDIO4DJ		0x0839
+#define USB_PID_AUDIO8DJ		0x1978
+#define USB_PID_SESSIONIO		0x1915
+#define USB_PID_GUITARRIGMOBILE		0x0d8d
+#define USB_PID_TRAKTORKONTROLX1	0x2305
+#define USB_PID_TRAKTORKONTROLS4	0xbaff
+
+#define EP1_BUFSIZE 64
+#define EP4_BUFSIZE 512
+#define CAIAQ_USB_STR_LEN 0xff
+#define MAX_STREAMS 32
+
+//#define	SND_USB_CAIAQ_DEBUG
+
+#define MODNAME "snd-usb-caiaq"
+#define log(x...) snd_printk(KERN_WARNING MODNAME" log: " x)
+
+#ifdef SND_USB_CAIAQ_DEBUG
+#define debug(x...) snd_printk(KERN_WARNING MODNAME " debug: " x)
+#else
+#define debug(x...) do { } while(0)
+#endif
+
+#define EP1_CMD_GET_DEVICE_INFO	0x1
+#define EP1_CMD_READ_ERP	0x2
+#define EP1_CMD_READ_ANALOG	0x3
+#define EP1_CMD_READ_IO		0x4
+#define EP1_CMD_WRITE_IO	0x5
+#define EP1_CMD_MIDI_READ	0x6
+#define EP1_CMD_MIDI_WRITE	0x7
+#define EP1_CMD_AUDIO_PARAMS	0x9
+#define EP1_CMD_AUTO_MSG	0xb
+#define EP1_CMD_DIMM_LEDS       0xc
+
+struct caiaq_device_spec {
+	unsigned short fw_version;
+	unsigned char hw_subtype;
+	unsigned char num_erp;
+	unsigned char num_analog_in;
+	unsigned char num_digital_in;
+	unsigned char num_digital_out;
+	unsigned char num_analog_audio_out;
+	unsigned char num_analog_audio_in;
+	unsigned char num_digital_audio_out;
+	unsigned char num_digital_audio_in;
+	unsigned char num_midi_out;
+	unsigned char num_midi_in;
+	unsigned char data_alignment;
+} __attribute__ ((packed));
+
+struct snd_usb_caiaq_cb_info;
+
+struct snd_usb_caiaqdev {
+	struct snd_usb_audio chip;
+
+	struct urb ep1_in_urb;
+	struct urb midi_out_urb;
+	struct urb **data_urbs_in;
+	struct urb **data_urbs_out;
+	struct snd_usb_caiaq_cb_info *data_cb_info;
+
+	unsigned char ep1_in_buf[EP1_BUFSIZE];
+	unsigned char ep1_out_buf[EP1_BUFSIZE];
+	unsigned char midi_out_buf[EP1_BUFSIZE];
+
+	struct caiaq_device_spec spec;
+	spinlock_t spinlock;
+	wait_queue_head_t ep1_wait_queue;
+	wait_queue_head_t prepare_wait_queue;
+	int spec_received, audio_parm_answer;
+	int midi_out_active;
+
+	char vendor_name[CAIAQ_USB_STR_LEN];
+	char product_name[CAIAQ_USB_STR_LEN];
+
+	int n_streams, n_audio_in, n_audio_out;
+	int streaming, first_packet, output_running;
+	int audio_in_buf_pos[MAX_STREAMS];
+	int audio_out_buf_pos[MAX_STREAMS];
+	int period_in_count[MAX_STREAMS];
+	int period_out_count[MAX_STREAMS];
+	int input_panic, output_panic, warned;
+	char *audio_in_buf, *audio_out_buf;
+	unsigned int samplerates, bpp;
+
+	struct snd_pcm_substream *sub_playback[MAX_STREAMS];
+	struct snd_pcm_substream *sub_capture[MAX_STREAMS];
+
+	/* Controls */
+	unsigned char control_state[256];
+	unsigned char ep8_out_buf[2];
+
+	/* Linux input */
+#ifdef CONFIG_SND_USB_CAIAQ_INPUT
+	struct input_dev *input_dev;
+	char phys[64];			/* physical device path */
+	unsigned short keycode[128];
+	struct urb *ep4_in_urb;
+	unsigned char ep4_in_buf[EP4_BUFSIZE];
+#endif
+
+	/* ALSA */
+	struct snd_pcm *pcm;
+	struct snd_pcm_hardware pcm_info;
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_substream *midi_receive_substream;
+	struct snd_rawmidi_substream *midi_out_substream;
+};
+
+struct snd_usb_caiaq_cb_info {
+	struct snd_usb_caiaqdev *dev;
+	int index;
+};
+
+#define caiaqdev(c) ((struct snd_usb_caiaqdev*)(c)->private_data)
+
+int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *dev, int rate, int depth, int bbp);
+int snd_usb_caiaq_set_auto_msg (struct snd_usb_caiaqdev *dev, int digital, int analog, int erp);
+int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *dev,
+			       unsigned char command,
+			       const unsigned char *buffer,
+			       int len);
+
+#endif /* CAIAQ_DEVICE_H */
diff --git a/linux/sound/usb/caiaq/input.c b/linux/sound/usb/caiaq/input.c
new file mode 100644
index 0000000..4432ef7
--- /dev/null
+++ b/linux/sound/usb/caiaq/input.c
@@ -0,0 +1,691 @@
+/*
+ *   Copyright (c) 2006,2007 Daniel Mack, Tim Ruetz
+ *
+ *   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/gfp.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <linux/usb/input.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "input.h"
+
+static unsigned short keycode_ak1[] =  { KEY_C, KEY_B, KEY_A };
+static unsigned short keycode_rk2[] =  { KEY_1, KEY_2, KEY_3, KEY_4,
+					 KEY_5, KEY_6, KEY_7 };
+static unsigned short keycode_rk3[] =  { KEY_1, KEY_2, KEY_3, KEY_4,
+					 KEY_5, KEY_6, KEY_7, KEY_5, KEY_6 };
+
+static unsigned short keycode_kore[] = {
+	KEY_FN_F1,      /* "menu"               */
+	KEY_FN_F7,      /* "lcd backlight       */
+	KEY_FN_F2,      /* "control"            */
+	KEY_FN_F3,      /* "enter"              */
+	KEY_FN_F4,      /* "view"               */
+	KEY_FN_F5,      /* "esc"                */
+	KEY_FN_F6,      /* "sound"              */
+	KEY_FN_F8,      /* array spacer, never triggered. */
+	KEY_RIGHT,
+	KEY_DOWN,
+	KEY_UP,
+	KEY_LEFT,
+	KEY_SOUND,      /* "listen"             */
+	KEY_RECORD,
+	KEY_PLAYPAUSE,
+	KEY_STOP,
+	BTN_4,          /* 8 softkeys */
+	BTN_3,
+	BTN_2,
+	BTN_1,
+	BTN_8,
+	BTN_7,
+	BTN_6,
+	BTN_5,
+	KEY_BRL_DOT4,   /* touch sensitive knobs */
+	KEY_BRL_DOT3,
+	KEY_BRL_DOT2,
+	KEY_BRL_DOT1,
+	KEY_BRL_DOT8,
+	KEY_BRL_DOT7,
+	KEY_BRL_DOT6,
+	KEY_BRL_DOT5
+};
+
+#define KONTROLX1_INPUTS	(40)
+#define KONTROLS4_BUTTONS	(12 * 8)
+#define KONTROLS4_AXIS		(46)
+
+#define KONTROLS4_BUTTON(X)	((X) + BTN_MISC)
+#define KONTROLS4_ABS(X)	((X) + ABS_HAT0X)
+
+#define DEG90		(range / 2)
+#define DEG180		(range)
+#define DEG270		(DEG90 + DEG180)
+#define DEG360		(DEG180 * 2)
+#define HIGH_PEAK	(268)
+#define LOW_PEAK	(-7)
+
+/* some of these devices have endless rotation potentiometers
+ * built in which use two tapers, 90 degrees phase shifted.
+ * this algorithm decodes them to one single value, ranging
+ * from 0 to 999 */
+static unsigned int decode_erp(unsigned char a, unsigned char b)
+{
+	int weight_a, weight_b;
+	int pos_a, pos_b;
+	int ret;
+	int range = HIGH_PEAK - LOW_PEAK;
+	int mid_value = (HIGH_PEAK + LOW_PEAK) / 2;
+
+	weight_b = abs(mid_value - a) - (range / 2 - 100) / 2;
+
+	if (weight_b < 0)
+		weight_b = 0;
+
+	if (weight_b > 100)
+		weight_b = 100;
+
+	weight_a = 100 - weight_b;
+
+	if (a < mid_value) {
+		/* 0..90 and 270..360 degrees */
+		pos_b = b - LOW_PEAK + DEG270;
+		if (pos_b >= DEG360)
+			pos_b -= DEG360;
+	} else
+		/* 90..270 degrees */
+		pos_b = HIGH_PEAK - b + DEG90;
+
+
+	if (b > mid_value)
+		/* 0..180 degrees */
+		pos_a = a - LOW_PEAK;
+	else
+		/* 180..360 degrees */
+		pos_a = HIGH_PEAK - a + DEG180;
+
+	/* interpolate both slider values, depending on weight factors */
+	/* 0..99 x DEG360 */
+	ret = pos_a * weight_a + pos_b * weight_b;
+
+	/* normalize to 0..999 */
+	ret *= 10;
+	ret /= DEG360;
+
+	if (ret < 0)
+		ret += 1000;
+
+	if (ret >= 1000)
+		ret -= 1000;
+
+	return ret;
+}
+
+#undef DEG90
+#undef DEG180
+#undef DEG270
+#undef DEG360
+#undef HIGH_PEAK
+#undef LOW_PEAK
+
+static inline void snd_caiaq_input_report_abs(struct snd_usb_caiaqdev *dev,
+					      int axis, const unsigned char *buf,
+					      int offset)
+{
+	input_report_abs(dev->input_dev, axis,
+			 (buf[offset * 2] << 8) | buf[offset * 2 + 1]);
+}
+
+static void snd_caiaq_input_read_analog(struct snd_usb_caiaqdev *dev,
+					const unsigned char *buf,
+					unsigned int len)
+{
+	struct input_dev *input_dev = dev->input_dev;
+
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+		snd_caiaq_input_report_abs(dev, ABS_X, buf, 2);
+		snd_caiaq_input_report_abs(dev, ABS_Y, buf, 0);
+		snd_caiaq_input_report_abs(dev, ABS_Z, buf, 1);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+		snd_caiaq_input_report_abs(dev, ABS_X, buf, 0);
+		snd_caiaq_input_report_abs(dev, ABS_Y, buf, 1);
+		snd_caiaq_input_report_abs(dev, ABS_Z, buf, 2);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		snd_caiaq_input_report_abs(dev, ABS_HAT0X, buf, 4);
+		snd_caiaq_input_report_abs(dev, ABS_HAT0Y, buf, 2);
+		snd_caiaq_input_report_abs(dev, ABS_HAT1X, buf, 6);
+		snd_caiaq_input_report_abs(dev, ABS_HAT1Y, buf, 1);
+		snd_caiaq_input_report_abs(dev, ABS_HAT2X, buf, 7);
+		snd_caiaq_input_report_abs(dev, ABS_HAT2Y, buf, 0);
+		snd_caiaq_input_report_abs(dev, ABS_HAT3X, buf, 5);
+		snd_caiaq_input_report_abs(dev, ABS_HAT3Y, buf, 3);
+		break;
+	}
+
+	input_sync(input_dev);
+}
+
+static void snd_caiaq_input_read_erp(struct snd_usb_caiaqdev *dev,
+				     const char *buf, unsigned int len)
+{
+	struct input_dev *input_dev = dev->input_dev;
+	int i;
+
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+		i = decode_erp(buf[0], buf[1]);
+		input_report_abs(input_dev, ABS_X, i);
+		input_sync(input_dev);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+		i = decode_erp(buf[7], buf[5]);
+		input_report_abs(input_dev, ABS_HAT0X, i);
+		i = decode_erp(buf[12], buf[14]);
+		input_report_abs(input_dev, ABS_HAT0Y, i);
+		i = decode_erp(buf[15], buf[13]);
+		input_report_abs(input_dev, ABS_HAT1X, i);
+		i = decode_erp(buf[0], buf[2]);
+		input_report_abs(input_dev, ABS_HAT1Y, i);
+		i = decode_erp(buf[3], buf[1]);
+		input_report_abs(input_dev, ABS_HAT2X, i);
+		i = decode_erp(buf[8], buf[10]);
+		input_report_abs(input_dev, ABS_HAT2Y, i);
+		i = decode_erp(buf[11], buf[9]);
+		input_report_abs(input_dev, ABS_HAT3X, i);
+		i = decode_erp(buf[4], buf[6]);
+		input_report_abs(input_dev, ABS_HAT3Y, i);
+		input_sync(input_dev);
+		break;
+	}
+}
+
+static void snd_caiaq_input_read_io(struct snd_usb_caiaqdev *dev,
+				    unsigned char *buf, unsigned int len)
+{
+	struct input_dev *input_dev = dev->input_dev;
+	unsigned short *keycode = input_dev->keycode;
+	int i;
+
+	if (!keycode)
+		return;
+
+	if (input_dev->id.product == USB_PID_RIGKONTROL2)
+		for (i = 0; i < len; i++)
+			buf[i] = ~buf[i];
+
+	for (i = 0; i < input_dev->keycodemax && i < len * 8; i++)
+		input_report_key(input_dev, keycode[i],
+				 buf[i / 8] & (1 << (i % 8)));
+
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+		input_report_abs(dev->input_dev, ABS_MISC, 255 - buf[4]);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		/* rotary encoders */
+		input_report_abs(dev->input_dev, ABS_X, buf[5] & 0xf);
+		input_report_abs(dev->input_dev, ABS_Y, buf[5] >> 4);
+		input_report_abs(dev->input_dev, ABS_Z, buf[6] & 0xf);
+		input_report_abs(dev->input_dev, ABS_MISC, buf[6] >> 4);
+		break;
+	}
+
+	input_sync(input_dev);
+}
+
+#define TKS4_MSGBLOCK_SIZE	16
+
+static void snd_usb_caiaq_tks4_dispatch(struct snd_usb_caiaqdev *dev,
+					const unsigned char *buf,
+					unsigned int len)
+{
+	while (len) {
+		unsigned int i, block_id = (buf[0] << 8) | buf[1];
+
+		switch (block_id) {
+		case 0:
+			/* buttons */
+			for (i = 0; i < KONTROLS4_BUTTONS; i++)
+				input_report_key(dev->input_dev, KONTROLS4_BUTTON(i),
+						 (buf[4 + (i / 8)] >> (i % 8)) & 1);
+			break;
+
+		case 1:
+			/* left wheel */
+			input_report_abs(dev->input_dev, KONTROLS4_ABS(36), buf[9] | ((buf[8] & 0x3) << 8));
+			/* right wheel */
+			input_report_abs(dev->input_dev, KONTROLS4_ABS(37), buf[13] | ((buf[12] & 0x3) << 8));
+
+			/* rotary encoders */
+			input_report_abs(dev->input_dev, KONTROLS4_ABS(38), buf[3] & 0xf);
+			input_report_abs(dev->input_dev, KONTROLS4_ABS(39), buf[4] >> 4);
+			input_report_abs(dev->input_dev, KONTROLS4_ABS(40), buf[4] & 0xf);
+			input_report_abs(dev->input_dev, KONTROLS4_ABS(41), buf[5] >> 4);
+			input_report_abs(dev->input_dev, KONTROLS4_ABS(42), buf[5] & 0xf);
+			input_report_abs(dev->input_dev, KONTROLS4_ABS(43), buf[6] >> 4);
+			input_report_abs(dev->input_dev, KONTROLS4_ABS(44), buf[6] & 0xf);
+			input_report_abs(dev->input_dev, KONTROLS4_ABS(45), buf[7] >> 4);
+			input_report_abs(dev->input_dev, KONTROLS4_ABS(46), buf[7] & 0xf);
+
+			break;
+		case 2:
+			/* Volume Fader Channel D */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(0), buf, 1);
+			/* Volume Fader Channel B */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(1), buf, 2);
+			/* Volume Fader Channel A */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(2), buf, 3);
+			/* Volume Fader Channel C */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(3), buf, 4);
+			/* Loop Volume */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(4), buf, 6);
+			/* Crossfader */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(7), buf, 7);
+
+			break;
+
+		case 3:
+			/* Tempo Fader R */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(6), buf, 3);
+			/* Tempo Fader L */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(5), buf, 4);
+			/* Mic Volume */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(8), buf, 6);
+			/* Cue Mix */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(9), buf, 7);
+
+			break;
+
+		case 4:
+			/* Wheel distance sensor L */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(10), buf, 1);
+			/* Wheel distance sensor R */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(11), buf, 2);
+			/* Channel D EQ - Filter */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(12), buf, 3);
+			/* Channel D EQ - Low */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(13), buf, 4);
+			/* Channel D EQ - Mid */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(14), buf, 5);
+			/* Channel D EQ - Hi */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(15), buf, 6);
+			/* FX2 - dry/wet */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(16), buf, 7);
+
+			break;
+
+		case 5:
+			/* FX2 - 1 */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(17), buf, 1);
+			/* FX2 - 2 */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(18), buf, 2);
+			/* FX2 - 3 */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(19), buf, 3);
+			/* Channel B EQ - Filter */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(20), buf, 4);
+			/* Channel B EQ - Low */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(21), buf, 5);
+			/* Channel B EQ - Mid */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(22), buf, 6);
+			/* Channel B EQ - Hi */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(23), buf, 7);
+
+			break;
+
+		case 6:
+			/* Channel A EQ - Filter */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(24), buf, 1);
+			/* Channel A EQ - Low */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(25), buf, 2);
+			/* Channel A EQ - Mid */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(26), buf, 3);
+			/* Channel A EQ - Hi */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(27), buf, 4);
+			/* Channel C EQ - Filter */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(28), buf, 5);
+			/* Channel C EQ - Low */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(29), buf, 6);
+			/* Channel C EQ - Mid */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(30), buf, 7);
+
+			break;
+
+		case 7:
+			/* Channel C EQ - Hi */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(31), buf, 1);
+			/* FX1 - wet/dry */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(32), buf, 2);
+			/* FX1 - 1 */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(33), buf, 3);
+			/* FX1 - 2 */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(34), buf, 4);
+			/* FX1 - 3 */
+			snd_caiaq_input_report_abs(dev, KONTROLS4_ABS(35), buf, 5);
+
+			break;
+
+		default:
+			debug("%s(): bogus block (id %d)\n",
+				__func__, block_id);
+			return;
+		}
+
+		len -= TKS4_MSGBLOCK_SIZE;
+		buf += TKS4_MSGBLOCK_SIZE;
+	}
+
+	input_sync(dev->input_dev);
+}
+
+static void snd_usb_caiaq_ep4_reply_dispatch(struct urb *urb)
+{
+	struct snd_usb_caiaqdev *dev = urb->context;
+	unsigned char *buf = urb->transfer_buffer;
+	int ret;
+
+	if (urb->status || !dev || urb != dev->ep4_in_urb)
+		return;
+
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		if (urb->actual_length < 24)
+			goto requeue;
+
+		if (buf[0] & 0x3)
+			snd_caiaq_input_read_io(dev, buf + 1, 7);
+
+		if (buf[0] & 0x4)
+			snd_caiaq_input_read_analog(dev, buf + 8, 16);
+
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+		snd_usb_caiaq_tks4_dispatch(dev, buf, urb->actual_length);
+		break;
+	}
+
+requeue:
+	dev->ep4_in_urb->actual_length = 0;
+	ret = usb_submit_urb(dev->ep4_in_urb, GFP_ATOMIC);
+	if (ret < 0)
+		log("unable to submit urb. OOM!?\n");
+}
+
+static int snd_usb_caiaq_input_open(struct input_dev *idev)
+{
+	struct snd_usb_caiaqdev *dev = input_get_drvdata(idev);
+
+	if (!dev)
+		return -EINVAL;
+
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+		if (usb_submit_urb(dev->ep4_in_urb, GFP_KERNEL) != 0)
+			return -EIO;
+		break;
+	}
+
+	return 0;
+}
+
+static void snd_usb_caiaq_input_close(struct input_dev *idev)
+{
+	struct snd_usb_caiaqdev *dev = input_get_drvdata(idev);
+
+	if (!dev)
+		return;
+
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+		usb_kill_urb(dev->ep4_in_urb);
+		break;
+	}
+}
+
+void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *dev,
+				  char *buf,
+				  unsigned int len)
+{
+	if (!dev->input_dev || len < 1)
+		return;
+
+	switch (buf[0]) {
+	case EP1_CMD_READ_ANALOG:
+		snd_caiaq_input_read_analog(dev, buf + 1, len - 1);
+		break;
+	case EP1_CMD_READ_ERP:
+		snd_caiaq_input_read_erp(dev, buf + 1, len - 1);
+		break;
+	case EP1_CMD_READ_IO:
+		snd_caiaq_input_read_io(dev, buf + 1, len - 1);
+		break;
+	}
+}
+
+int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *dev)
+{
+	struct usb_device *usb_dev = dev->chip.dev;
+	struct input_dev *input;
+	int i, ret = 0;
+
+	input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+
+	usb_make_path(usb_dev, dev->phys, sizeof(dev->phys));
+	strlcat(dev->phys, "/input0", sizeof(dev->phys));
+
+	input->name = dev->product_name;
+	input->phys = dev->phys;
+	usb_to_input_id(usb_dev, &input->id);
+	input->dev.parent = &usb_dev->dev;
+
+	input_set_drvdata(input, dev);
+
+	switch (dev->chip.usb_id) {
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+			BIT_MASK(ABS_Z);
+		BUILD_BUG_ON(sizeof(dev->keycode) < sizeof(keycode_rk2));
+		memcpy(dev->keycode, keycode_rk2, sizeof(keycode_rk2));
+		input->keycodemax = ARRAY_SIZE(keycode_rk2);
+		input_set_abs_params(input, ABS_X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_Y, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10);
+		snd_usb_caiaq_set_auto_msg(dev, 1, 10, 0);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+			BIT_MASK(ABS_Z);
+		BUILD_BUG_ON(sizeof(dev->keycode) < sizeof(keycode_rk3));
+		memcpy(dev->keycode, keycode_rk3, sizeof(keycode_rk3));
+		input->keycodemax = ARRAY_SIZE(keycode_rk3);
+		input_set_abs_params(input, ABS_X, 0, 1024, 0, 10);
+		input_set_abs_params(input, ABS_Y, 0, 1024, 0, 10);
+		input_set_abs_params(input, ABS_Z, 0, 1024, 0, 10);
+		snd_usb_caiaq_set_auto_msg(dev, 1, 10, 0);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		input->absbit[0] = BIT_MASK(ABS_X);
+		BUILD_BUG_ON(sizeof(dev->keycode) < sizeof(keycode_ak1));
+		memcpy(dev->keycode, keycode_ak1, sizeof(keycode_ak1));
+		input->keycodemax = ARRAY_SIZE(keycode_ak1);
+		input_set_abs_params(input, ABS_X, 0, 999, 0, 10);
+		snd_usb_caiaq_set_auto_msg(dev, 1, 0, 5);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) |
+				   BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) |
+				   BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) |
+				   BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) |
+				   BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+				   BIT_MASK(ABS_Z);
+		input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC);
+		BUILD_BUG_ON(sizeof(dev->keycode) < sizeof(keycode_kore));
+		memcpy(dev->keycode, keycode_kore, sizeof(keycode_kore));
+		input->keycodemax = ARRAY_SIZE(keycode_kore);
+		input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT1X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT1Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT2X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT2Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT3X, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_HAT3Y, 0, 999, 0, 10);
+		input_set_abs_params(input, ABS_X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_Y, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_MISC, 0, 255, 0, 1);
+		snd_usb_caiaq_set_auto_msg(dev, 1, 10, 5);
+		break;
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) |
+				   BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) |
+				   BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) |
+				   BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) |
+				   BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
+				   BIT_MASK(ABS_Z);
+		input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC);
+		BUILD_BUG_ON(sizeof(dev->keycode) < KONTROLX1_INPUTS);
+		for (i = 0; i < KONTROLX1_INPUTS; i++)
+			dev->keycode[i] = BTN_MISC + i;
+		input->keycodemax = KONTROLX1_INPUTS;
+
+		/* analog potentiometers */
+		input_set_abs_params(input, ABS_HAT0X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT0Y, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT1X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT1Y, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT2X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT2Y, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT3X, 0, 4096, 0, 10);
+		input_set_abs_params(input, ABS_HAT3Y, 0, 4096, 0, 10);
+
+		/* rotary encoders */
+		input_set_abs_params(input, ABS_X, 0, 0xf, 0, 1);
+		input_set_abs_params(input, ABS_Y, 0, 0xf, 0, 1);
+		input_set_abs_params(input, ABS_Z, 0, 0xf, 0, 1);
+		input_set_abs_params(input, ABS_MISC, 0, 0xf, 0, 1);
+
+		dev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!dev->ep4_in_urb) {
+			ret = -ENOMEM;
+			goto exit_free_idev;
+		}
+
+		usb_fill_bulk_urb(dev->ep4_in_urb, usb_dev,
+				  usb_rcvbulkpipe(usb_dev, 0x4),
+				  dev->ep4_in_buf, EP4_BUFSIZE,
+				  snd_usb_caiaq_ep4_reply_dispatch, dev);
+
+		snd_usb_caiaq_set_auto_msg(dev, 1, 10, 5);
+
+		break;
+
+	case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4):
+		input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+		BUILD_BUG_ON(sizeof(dev->keycode) < KONTROLS4_BUTTONS);
+		for (i = 0; i < KONTROLS4_BUTTONS; i++)
+			dev->keycode[i] = KONTROLS4_BUTTON(i);
+		input->keycodemax = KONTROLS4_BUTTONS;
+
+		for (i = 0; i < KONTROLS4_AXIS; i++) {
+			int axis = KONTROLS4_ABS(i);
+			input->absbit[BIT_WORD(axis)] |= BIT_MASK(axis);
+		}
+
+		/* 36 analog potentiometers and faders */
+		for (i = 0; i < 36; i++)
+			input_set_abs_params(input, KONTROLS4_ABS(i), 0, 0xfff, 0, 10);
+
+		/* 2 encoder wheels */
+		input_set_abs_params(input, KONTROLS4_ABS(36), 0, 0x3ff, 0, 1);
+		input_set_abs_params(input, KONTROLS4_ABS(37), 0, 0x3ff, 0, 1);
+
+		/* 9 rotary encoders */
+		for (i = 0; i < 9; i++)
+			input_set_abs_params(input, KONTROLS4_ABS(38+i), 0, 0xf, 0, 1);
+
+		dev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!dev->ep4_in_urb) {
+			ret = -ENOMEM;
+			goto exit_free_idev;
+		}
+
+		usb_fill_bulk_urb(dev->ep4_in_urb, usb_dev,
+				  usb_rcvbulkpipe(usb_dev, 0x4),
+				  dev->ep4_in_buf, EP4_BUFSIZE,
+				  snd_usb_caiaq_ep4_reply_dispatch, dev);
+
+		snd_usb_caiaq_set_auto_msg(dev, 1, 10, 5);
+
+		break;
+
+	default:
+		/* no input methods supported on this device */
+		goto exit_free_idev;
+	}
+
+	input->open = snd_usb_caiaq_input_open;
+	input->close = snd_usb_caiaq_input_close;
+	input->keycode = dev->keycode;
+	input->keycodesize = sizeof(unsigned short);
+	for (i = 0; i < input->keycodemax; i++)
+		__set_bit(dev->keycode[i], input->keybit);
+
+	ret = input_register_device(input);
+	if (ret < 0)
+		goto exit_free_idev;
+
+	dev->input_dev = input;
+	return 0;
+
+exit_free_idev:
+	input_free_device(input);
+	return ret;
+}
+
+void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *dev)
+{
+	if (!dev || !dev->input_dev)
+		return;
+
+	usb_kill_urb(dev->ep4_in_urb);
+	usb_free_urb(dev->ep4_in_urb);
+	dev->ep4_in_urb = NULL;
+
+	input_unregister_device(dev->input_dev);
+	dev->input_dev = NULL;
+}
+
diff --git a/linux/sound/usb/caiaq/input.h b/linux/sound/usb/caiaq/input.h
new file mode 100644
index 0000000..ced5355
--- /dev/null
+++ b/linux/sound/usb/caiaq/input.h
@@ -0,0 +1,8 @@
+#ifndef CAIAQ_INPUT_H
+#define CAIAQ_INPUT_H
+
+void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *dev, char *buf, unsigned int len);
+int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *dev);
+void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *dev);
+
+#endif
diff --git a/linux/sound/usb/caiaq/midi.c b/linux/sound/usb/caiaq/midi.c
new file mode 100644
index 0000000..2f218c7
--- /dev/null
+++ b/linux/sound/usb/caiaq/midi.c
@@ -0,0 +1,174 @@
+/*
+ *   Copyright (c) 2006,2007 Daniel Mack
+ *
+ *   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/usb.h>
+#include <linux/gfp.h>
+#include <sound/rawmidi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "midi.h"
+
+static int snd_usb_caiaq_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int snd_usb_caiaq_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static void snd_usb_caiaq_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_usb_caiaqdev *dev = substream->rmidi->private_data;
+
+	if (!dev)
+		return;
+
+	dev->midi_receive_substream = up ? substream : NULL;
+}
+
+
+static int snd_usb_caiaq_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int snd_usb_caiaq_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_usb_caiaqdev *dev = substream->rmidi->private_data;
+	if (dev->midi_out_active) {
+		usb_kill_urb(&dev->midi_out_urb);
+		dev->midi_out_active = 0;
+	}
+	return 0;
+}
+
+static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *dev,
+				    struct snd_rawmidi_substream *substream)
+{
+	int len, ret;
+
+	dev->midi_out_buf[0] = EP1_CMD_MIDI_WRITE;
+	dev->midi_out_buf[1] = 0; /* port */
+	len = snd_rawmidi_transmit(substream, dev->midi_out_buf + 3,
+				   EP1_BUFSIZE - 3);
+
+	if (len <= 0)
+		return;
+
+	dev->midi_out_buf[2] = len;
+	dev->midi_out_urb.transfer_buffer_length = len+3;
+
+	ret = usb_submit_urb(&dev->midi_out_urb, GFP_ATOMIC);
+	if (ret < 0)
+		log("snd_usb_caiaq_midi_send(%p): usb_submit_urb() failed,"
+		    "ret=%d, len=%d\n",
+		    substream, ret, len);
+	else
+		dev->midi_out_active = 1;
+}
+
+static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_usb_caiaqdev *dev = substream->rmidi->private_data;
+
+	if (up) {
+		dev->midi_out_substream = substream;
+		if (!dev->midi_out_active)
+			snd_usb_caiaq_midi_send(dev, substream);
+	} else {
+		dev->midi_out_substream = NULL;
+	}
+}
+
+
+static struct snd_rawmidi_ops snd_usb_caiaq_midi_output =
+{
+	.open =		snd_usb_caiaq_midi_output_open,
+	.close =	snd_usb_caiaq_midi_output_close,
+	.trigger =      snd_usb_caiaq_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops snd_usb_caiaq_midi_input =
+{
+	.open =		snd_usb_caiaq_midi_input_open,
+	.close =	snd_usb_caiaq_midi_input_close,
+	.trigger =      snd_usb_caiaq_midi_input_trigger,
+};
+
+void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *dev,
+				     int port, const char *buf, int len)
+{
+	if (!dev->midi_receive_substream)
+		return;
+
+	snd_rawmidi_receive(dev->midi_receive_substream, buf, len);
+}
+
+int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device)
+{
+	int ret;
+	struct snd_rawmidi *rmidi;
+
+	ret = snd_rawmidi_new(device->chip.card, device->product_name, 0,
+					device->spec.num_midi_out,
+					device->spec.num_midi_in,
+					&rmidi);
+
+	if (ret < 0)
+		return ret;
+
+	strcpy(rmidi->name, device->product_name);
+
+	rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->private_data = device;
+
+	if (device->spec.num_midi_out > 0) {
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+				    &snd_usb_caiaq_midi_output);
+	}
+
+	if (device->spec.num_midi_in > 0) {
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+				    &snd_usb_caiaq_midi_input);
+	}
+
+	device->rmidi = rmidi;
+
+	return 0;
+}
+
+void snd_usb_caiaq_midi_output_done(struct urb* urb)
+{
+	struct snd_usb_caiaqdev *dev = urb->context;
+
+	dev->midi_out_active = 0;
+	if (urb->status != 0)
+		return;
+
+	if (!dev->midi_out_substream)
+		return;
+
+	snd_usb_caiaq_midi_send(dev, dev->midi_out_substream);
+}
+
diff --git a/linux/sound/usb/caiaq/midi.h b/linux/sound/usb/caiaq/midi.h
new file mode 100644
index 0000000..380f984
--- /dev/null
+++ b/linux/sound/usb/caiaq/midi.h
@@ -0,0 +1,8 @@
+#ifndef CAIAQ_MIDI_H
+#define CAIAQ_MIDI_H
+
+int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *dev);
+void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *dev, int port, const char *buf, int len);
+void snd_usb_caiaq_midi_output_done(struct urb *urb);
+
+#endif /* CAIAQ_MIDI_H */
diff --git a/linux/sound/usb/card.c b/linux/sound/usb/card.c
new file mode 100644
index 0000000..800f7cb
--- /dev/null
+++ b/linux/sound/usb/card.c
@@ -0,0 +1,660 @@
+/*
+ *   (Tentative) USB Audio Driver for ALSA
+ *
+ *   Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   Many codes borrowed from audio.c by
+ *	    Alan Cox (alan@lxorguk.ukuu.org.uk)
+ *	    Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *
+ *  NOTES:
+ *
+ *   - async unlink should be used for avoiding the sleep inside lock.
+ *     2.4.22 usb-uhci seems buggy for async unlinking and results in
+ *     oops.  in such a cse, pass async_unlink=0 option.
+ *   - the linked URBs would be preferred but not used so far because of
+ *     the instability of unlinking.
+ *   - type II is not supported properly.  there is no device which supports
+ *     this type *correctly*.  SB extigy looks as if it supports, but it's
+ *     indeed an AC3 stream packed in SPDIF frames (i.e. no real AC3 stream).
+ */
+
+
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "midi.h"
+#include "mixer.h"
+#include "proc.h"
+#include "quirks.h"
+#include "endpoint.h"
+#include "helper.h"
+#include "debug.h"
+#include "pcm.h"
+#include "urb.h"
+#include "format.h"
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("USB Audio");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Generic,USB Audio}}");
+
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */
+/* Vendor/product IDs for this card */
+static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 };
+static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 };
+static int nrpacks = 8;		/* max. number of packets per urb */
+static int async_unlink = 1;
+static int device_setup[SNDRV_CARDS]; /* device parameter for this card */
+static int ignore_ctl_error;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the USB audio adapter.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the USB audio adapter.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable USB audio adapter.");
+module_param_array(vid, int, NULL, 0444);
+MODULE_PARM_DESC(vid, "Vendor ID for the USB audio device.");
+module_param_array(pid, int, NULL, 0444);
+MODULE_PARM_DESC(pid, "Product ID for the USB audio device.");
+module_param(nrpacks, int, 0644);
+MODULE_PARM_DESC(nrpacks, "Max. number of packets per URB.");
+module_param(async_unlink, bool, 0444);
+MODULE_PARM_DESC(async_unlink, "Use async unlink mode.");
+module_param_array(device_setup, int, NULL, 0444);
+MODULE_PARM_DESC(device_setup, "Specific device setup (if needed).");
+module_param(ignore_ctl_error, bool, 0444);
+MODULE_PARM_DESC(ignore_ctl_error,
+		 "Ignore errors from USB controller for mixer interfaces.");
+
+/*
+ * we keep the snd_usb_audio_t instances by ourselves for merging
+ * the all interfaces on the same card as one sound device.
+ */
+
+static DEFINE_MUTEX(register_mutex);
+static struct snd_usb_audio *usb_chip[SNDRV_CARDS];
+static struct usb_driver usb_audio_driver;
+
+/*
+ * disconnect streams
+ * called from snd_usb_audio_disconnect()
+ */
+static void snd_usb_stream_disconnect(struct list_head *head)
+{
+	int idx;
+	struct snd_usb_stream *as;
+	struct snd_usb_substream *subs;
+
+	as = list_entry(head, struct snd_usb_stream, list);
+	for (idx = 0; idx < 2; idx++) {
+		subs = &as->substream[idx];
+		if (!subs->num_formats)
+			continue;
+		snd_usb_release_substream_urbs(subs, 1);
+		subs->interface = -1;
+	}
+}
+
+static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int interface)
+{
+	struct usb_device *dev = chip->dev;
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	struct usb_interface *iface = usb_ifnum_to_if(dev, interface);
+
+	if (!iface) {
+		snd_printk(KERN_ERR "%d:%u:%d : does not exist\n",
+			   dev->devnum, ctrlif, interface);
+		return -EINVAL;
+	}
+
+	if (usb_interface_claimed(iface)) {
+		snd_printdd(KERN_INFO "%d:%d:%d: skipping, already claimed\n",
+						dev->devnum, ctrlif, interface);
+		return -EINVAL;
+	}
+
+	alts = &iface->altsetting[0];
+	altsd = get_iface_desc(alts);
+	if ((altsd->bInterfaceClass == USB_CLASS_AUDIO ||
+	     altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) &&
+	    altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) {
+		int err = snd_usbmidi_create(chip->card, iface,
+					     &chip->midi_list, NULL);
+		if (err < 0) {
+			snd_printk(KERN_ERR "%d:%u:%d: cannot create sequencer device\n",
+						dev->devnum, ctrlif, interface);
+			return -EINVAL;
+		}
+		usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
+
+		return 0;
+	}
+
+	if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
+	     altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
+	    altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING) {
+		snd_printdd(KERN_ERR "%d:%u:%d: skipping non-supported interface %d\n",
+					dev->devnum, ctrlif, interface, altsd->bInterfaceClass);
+		/* skip non-supported classes */
+		return -EINVAL;
+	}
+
+	if (snd_usb_get_speed(dev) == USB_SPEED_LOW) {
+		snd_printk(KERN_ERR "low speed audio streaming not supported\n");
+		return -EINVAL;
+	}
+
+	if (! snd_usb_parse_audio_endpoints(chip, interface)) {
+		usb_set_interface(dev, interface, 0); /* reset the current interface */
+		usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * parse audio control descriptor and create pcm/midi streams
+ */
+static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
+{
+	struct usb_device *dev = chip->dev;
+	struct usb_host_interface *host_iface;
+	struct usb_interface_descriptor *altsd;
+	void *control_header;
+	int i, protocol;
+
+	/* find audiocontrol interface */
+	host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0];
+	control_header = snd_usb_find_csint_desc(host_iface->extra,
+						 host_iface->extralen,
+						 NULL, UAC_HEADER);
+	altsd = get_iface_desc(host_iface);
+	protocol = altsd->bInterfaceProtocol;
+
+	if (!control_header) {
+		snd_printk(KERN_ERR "cannot find UAC_HEADER\n");
+		return -EINVAL;
+	}
+
+	switch (protocol) {
+	default:
+		snd_printdd(KERN_WARNING "unknown interface protocol %#02x, assuming v1\n",
+			    protocol);
+		/* fall through */
+
+	case UAC_VERSION_1: {
+		struct uac1_ac_header_descriptor *h1 = control_header;
+
+		if (!h1->bInCollection) {
+			snd_printk(KERN_INFO "skipping empty audio interface (v1)\n");
+			return -EINVAL;
+		}
+
+		if (h1->bLength < sizeof(*h1) + h1->bInCollection) {
+			snd_printk(KERN_ERR "invalid UAC_HEADER (v1)\n");
+			return -EINVAL;
+		}
+
+		for (i = 0; i < h1->bInCollection; i++)
+			snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]);
+
+		break;
+	}
+
+	case UAC_VERSION_2: {
+		struct usb_interface_assoc_descriptor *assoc =
+			usb_ifnum_to_if(dev, ctrlif)->intf_assoc;
+
+		if (!assoc) {
+			snd_printk(KERN_ERR "Audio class v2 interfaces need an interface association\n");
+			return -EINVAL;
+		}
+
+		for (i = 0; i < assoc->bInterfaceCount; i++) {
+			int intf = assoc->bFirstInterface + i;
+
+			if (intf != ctrlif)
+				snd_usb_create_stream(chip, ctrlif, intf);
+		}
+
+		break;
+	}
+	}
+
+	return 0;
+}
+
+/*
+ * free the chip instance
+ *
+ * here we have to do not much, since pcm and controls are already freed
+ *
+ */
+
+static int snd_usb_audio_free(struct snd_usb_audio *chip)
+{
+	kfree(chip);
+	return 0;
+}
+
+static int snd_usb_audio_dev_free(struct snd_device *device)
+{
+	struct snd_usb_audio *chip = device->device_data;
+	return snd_usb_audio_free(chip);
+}
+
+
+/*
+ * create a chip instance and set its names.
+ */
+static int snd_usb_audio_create(struct usb_device *dev, int idx,
+				const struct snd_usb_audio_quirk *quirk,
+				struct snd_usb_audio **rchip)
+{
+	struct snd_card *card;
+	struct snd_usb_audio *chip;
+	int err, len;
+	char component[14];
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_usb_audio_dev_free,
+	};
+
+	*rchip = NULL;
+
+	switch (snd_usb_get_speed(dev)) {
+	case USB_SPEED_LOW:
+	case USB_SPEED_FULL:
+	case USB_SPEED_HIGH:
+	case USB_SPEED_SUPER:
+		break;
+	default:
+		snd_printk(KERN_ERR "unknown device speed %d\n", snd_usb_get_speed(dev));
+		return -ENXIO;
+	}
+
+	err = snd_card_create(index[idx], id[idx], THIS_MODULE, 0, &card);
+	if (err < 0) {
+		snd_printk(KERN_ERR "cannot create card instance %d\n", idx);
+		return err;
+	}
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (! chip) {
+		snd_card_free(card);
+		return -ENOMEM;
+	}
+
+	chip->index = idx;
+	chip->dev = dev;
+	chip->card = card;
+	chip->setup = device_setup[idx];
+	chip->nrpacks = nrpacks;
+	chip->async_unlink = async_unlink;
+
+	chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
+			      le16_to_cpu(dev->descriptor.idProduct));
+	INIT_LIST_HEAD(&chip->pcm_list);
+	INIT_LIST_HEAD(&chip->midi_list);
+	INIT_LIST_HEAD(&chip->mixer_list);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
+		snd_usb_audio_free(chip);
+		snd_card_free(card);
+		return err;
+	}
+
+	strcpy(card->driver, "USB-Audio");
+	sprintf(component, "USB%04x:%04x",
+		USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id));
+	snd_component_add(card, component);
+
+	/* retrieve the device string as shortname */
+	if (quirk && quirk->product_name) {
+		strlcpy(card->shortname, quirk->product_name, sizeof(card->shortname));
+	} else {
+		if (!dev->descriptor.iProduct ||
+		    usb_string(dev, dev->descriptor.iProduct,
+		    card->shortname, sizeof(card->shortname)) <= 0) {
+			/* no name available from anywhere, so use ID */
+			sprintf(card->shortname, "USB Device %#04x:%#04x",
+				USB_ID_VENDOR(chip->usb_id),
+				USB_ID_PRODUCT(chip->usb_id));
+		}
+	}
+
+	/* retrieve the vendor and device strings as longname */
+	if (quirk && quirk->vendor_name) {
+		len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname));
+	} else {
+		if (dev->descriptor.iManufacturer)
+			len = usb_string(dev, dev->descriptor.iManufacturer,
+					 card->longname, sizeof(card->longname));
+		else
+			len = 0;
+		/* we don't really care if there isn't any vendor string */
+	}
+	if (len > 0)
+		strlcat(card->longname, " ", sizeof(card->longname));
+
+	strlcat(card->longname, card->shortname, sizeof(card->longname));
+
+	len = strlcat(card->longname, " at ", sizeof(card->longname));
+
+	if (len < sizeof(card->longname))
+		usb_make_path(dev, card->longname + len, sizeof(card->longname) - len);
+
+	switch (snd_usb_get_speed(dev)) {
+	case USB_SPEED_LOW:
+		strlcat(card->longname, ", low speed", sizeof(card->longname));
+		break;
+	case USB_SPEED_FULL:
+		strlcat(card->longname, ", full speed", sizeof(card->longname));
+		break;
+	case USB_SPEED_HIGH:
+		strlcat(card->longname, ", high speed", sizeof(card->longname));
+		break;
+	case USB_SPEED_SUPER:
+		strlcat(card->longname, ", super speed", sizeof(card->longname));
+		break;
+	default:
+		break;
+	}
+
+	snd_usb_audio_create_proc(chip);
+
+	*rchip = chip;
+	return 0;
+}
+
+/*
+ * probe the active usb device
+ *
+ * note that this can be called multiple times per a device, when it
+ * includes multiple audio control interfaces.
+ *
+ * thus we check the usb device pointer and creates the card instance
+ * only at the first time.  the successive calls of this function will
+ * append the pcm interface to the corresponding card.
+ */
+static void *snd_usb_audio_probe(struct usb_device *dev,
+				 struct usb_interface *intf,
+				 const struct usb_device_id *usb_id)
+{
+	const struct snd_usb_audio_quirk *quirk = (const struct snd_usb_audio_quirk *)usb_id->driver_info;
+	int i, err;
+	struct snd_usb_audio *chip;
+	struct usb_host_interface *alts;
+	int ifnum;
+	u32 id;
+
+	alts = &intf->altsetting[0];
+	ifnum = get_iface_desc(alts)->bInterfaceNumber;
+	id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
+		    le16_to_cpu(dev->descriptor.idProduct));
+	if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum)
+		goto __err_val;
+
+	if (snd_usb_apply_boot_quirk(dev, intf, quirk) < 0)
+		goto __err_val;
+
+	/*
+	 * found a config.  now register to ALSA
+	 */
+
+	/* check whether it's already registered */
+	chip = NULL;
+	mutex_lock(&register_mutex);
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (usb_chip[i] && usb_chip[i]->dev == dev) {
+			if (usb_chip[i]->shutdown) {
+				snd_printk(KERN_ERR "USB device is in the shutdown state, cannot create a card instance\n");
+				goto __error;
+			}
+			chip = usb_chip[i];
+			break;
+		}
+	}
+	if (! chip) {
+		/* it's a fresh one.
+		 * now look for an empty slot and create a new card instance
+		 */
+		for (i = 0; i < SNDRV_CARDS; i++)
+			if (enable[i] && ! usb_chip[i] &&
+			    (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) &&
+			    (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) {
+				if (snd_usb_audio_create(dev, i, quirk, &chip) < 0) {
+					goto __error;
+				}
+				snd_card_set_dev(chip->card, &intf->dev);
+				break;
+			}
+		if (!chip) {
+			printk(KERN_ERR "no available usb audio device\n");
+			goto __error;
+		}
+	}
+
+	chip->txfr_quirk = 0;
+	err = 1; /* continue */
+	if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) {
+		/* need some special handlings */
+		if ((err = snd_usb_create_quirk(chip, intf, &usb_audio_driver, quirk)) < 0)
+			goto __error;
+	}
+
+	/*
+	 * For devices with more than one control interface, we assume the
+	 * first contains the audio controls. We might need a more specific
+	 * check here in the future.
+	 */
+	if (!chip->ctrl_intf)
+		chip->ctrl_intf = alts;
+
+	if (err > 0) {
+		/* create normal USB audio interfaces */
+		if (snd_usb_create_streams(chip, ifnum) < 0 ||
+		    snd_usb_create_mixer(chip, ifnum, ignore_ctl_error) < 0) {
+			goto __error;
+		}
+	}
+
+	/* we are allowed to call snd_card_register() many times */
+	if (snd_card_register(chip->card) < 0) {
+		goto __error;
+	}
+
+	usb_chip[chip->index] = chip;
+	chip->num_interfaces++;
+	mutex_unlock(&register_mutex);
+	return chip;
+
+ __error:
+	if (chip && !chip->num_interfaces)
+		snd_card_free(chip->card);
+	mutex_unlock(&register_mutex);
+ __err_val:
+	return NULL;
+}
+
+/*
+ * we need to take care of counter, since disconnection can be called also
+ * many times as well as usb_audio_probe().
+ */
+static void snd_usb_audio_disconnect(struct usb_device *dev, void *ptr)
+{
+	struct snd_usb_audio *chip;
+	struct snd_card *card;
+	struct list_head *p;
+
+	if (ptr == (void *)-1L)
+		return;
+
+	chip = ptr;
+	card = chip->card;
+	mutex_lock(&register_mutex);
+	chip->shutdown = 1;
+	chip->num_interfaces--;
+	if (chip->num_interfaces <= 0) {
+		snd_card_disconnect(card);
+		/* release the pcm resources */
+		list_for_each(p, &chip->pcm_list) {
+			snd_usb_stream_disconnect(p);
+		}
+		/* release the midi resources */
+		list_for_each(p, &chip->midi_list) {
+			snd_usbmidi_disconnect(p);
+		}
+		/* release mixer resources */
+		list_for_each(p, &chip->mixer_list) {
+			snd_usb_mixer_disconnect(p);
+		}
+		usb_chip[chip->index] = NULL;
+		mutex_unlock(&register_mutex);
+		snd_card_free_when_closed(card);
+	} else {
+		mutex_unlock(&register_mutex);
+	}
+}
+
+/*
+ * new 2.5 USB kernel API
+ */
+static int usb_audio_probe(struct usb_interface *intf,
+			   const struct usb_device_id *id)
+{
+	void *chip;
+	chip = snd_usb_audio_probe(interface_to_usbdev(intf), intf, id);
+	if (chip) {
+		usb_set_intfdata(intf, chip);
+		return 0;
+	} else
+		return -EIO;
+}
+
+static void usb_audio_disconnect(struct usb_interface *intf)
+{
+	snd_usb_audio_disconnect(interface_to_usbdev(intf),
+				 usb_get_intfdata(intf));
+}
+
+#ifdef CONFIG_PM
+static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct snd_usb_audio *chip = usb_get_intfdata(intf);
+	struct list_head *p;
+	struct snd_usb_stream *as;
+
+	if (chip == (void *)-1L)
+		return 0;
+
+	snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
+	if (!chip->num_suspended_intf++) {
+		list_for_each(p, &chip->pcm_list) {
+			as = list_entry(p, struct snd_usb_stream, list);
+			snd_pcm_suspend_all(as->pcm);
+		}
+	}
+
+	return 0;
+}
+
+static int usb_audio_resume(struct usb_interface *intf)
+{
+	struct snd_usb_audio *chip = usb_get_intfdata(intf);
+
+	if (chip == (void *)-1L)
+		return 0;
+	if (--chip->num_suspended_intf)
+		return 0;
+	/*
+	 * ALSA leaves material resumption to user space
+	 * we just notify
+	 */
+
+	snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
+
+	return 0;
+}
+#else
+#define usb_audio_suspend	NULL
+#define usb_audio_resume	NULL
+#endif		/* CONFIG_PM */
+
+static struct usb_device_id usb_audio_ids [] = {
+#include "quirks-table.h"
+    { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
+      .bInterfaceClass = USB_CLASS_AUDIO,
+      .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL },
+    { }						/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, usb_audio_ids);
+
+/*
+ * entry point for linux usb interface
+ */
+
+static struct usb_driver usb_audio_driver = {
+	.name =		"snd-usb-audio",
+	.probe =	usb_audio_probe,
+	.disconnect =	usb_audio_disconnect,
+	.suspend =	usb_audio_suspend,
+	.resume =	usb_audio_resume,
+	.id_table =	usb_audio_ids,
+};
+
+static int __init snd_usb_audio_init(void)
+{
+	if (nrpacks < 1 || nrpacks > MAX_PACKS) {
+		printk(KERN_WARNING "invalid nrpacks value.\n");
+		return -EINVAL;
+	}
+	return usb_register(&usb_audio_driver);
+}
+
+static void __exit snd_usb_audio_cleanup(void)
+{
+	usb_deregister(&usb_audio_driver);
+}
+
+module_init(snd_usb_audio_init);
+module_exit(snd_usb_audio_cleanup);
diff --git a/linux/sound/usb/card.h b/linux/sound/usb/card.h
new file mode 100644
index 0000000..ae4251d
--- /dev/null
+++ b/linux/sound/usb/card.h
@@ -0,0 +1,108 @@
+#ifndef __USBAUDIO_CARD_H
+#define __USBAUDIO_CARD_H
+
+#define MAX_PACKS	20
+#define MAX_PACKS_HS	(MAX_PACKS * 8)	/* in high speed mode */
+#define MAX_URBS	8
+#define SYNC_URBS	4	/* always four urbs for sync */
+#define MAX_QUEUE	24	/* try not to exceed this queue length, in ms */
+
+struct audioformat {
+	struct list_head list;
+	u64 formats;			/* ALSA format bits */
+	unsigned int channels;		/* # channels */
+	unsigned int fmt_type;		/* USB audio format type (1-3) */
+	unsigned int frame_size;	/* samples per frame for non-audio */
+	int iface;			/* interface number */
+	unsigned char altsetting;	/* corresponding alternate setting */
+	unsigned char altset_idx;	/* array index of altenate setting */
+	unsigned char attributes;	/* corresponding attributes of cs endpoint */
+	unsigned char endpoint;		/* endpoint */
+	unsigned char ep_attr;		/* endpoint attributes */
+	unsigned char datainterval;	/* log_2 of data packet interval */
+	unsigned int maxpacksize;	/* max. packet size */
+	unsigned int rates;		/* rate bitmasks */
+	unsigned int rate_min, rate_max;	/* min/max rates */
+	unsigned int nr_rates;		/* number of rate table entries */
+	unsigned int *rate_table;	/* rate table */
+	unsigned char clock;		/* associated clock */
+};
+
+struct snd_usb_substream;
+
+struct snd_urb_ctx {
+	struct urb *urb;
+	unsigned int buffer_size;	/* size of data buffer, if data URB */
+	struct snd_usb_substream *subs;
+	int index;	/* index for urb array */
+	int packets;	/* number of packets per urb */
+};
+
+struct snd_urb_ops {
+	int (*prepare)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u);
+	int (*retire)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u);
+	int (*prepare_sync)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u);
+	int (*retire_sync)(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime, struct urb *u);
+};
+
+struct snd_usb_substream {
+	struct snd_usb_stream *stream;
+	struct usb_device *dev;
+	struct snd_pcm_substream *pcm_substream;
+	int direction;	/* playback or capture */
+	int interface;	/* current interface */
+	int endpoint;	/* assigned endpoint */
+	struct audioformat *cur_audiofmt;	/* current audioformat pointer (for hw_params callback) */
+	unsigned int cur_rate;		/* current rate (for hw_params callback) */
+	unsigned int period_bytes;	/* current period bytes (for hw_params callback) */
+	unsigned int altset_idx;     /* USB data format: index of alternate setting */
+	unsigned int datapipe;   /* the data i/o pipe */
+	unsigned int syncpipe;   /* 1 - async out or adaptive in */
+	unsigned int datainterval;	/* log_2 of data packet interval */
+	unsigned int syncinterval;  /* P for adaptive mode, 0 otherwise */
+	unsigned int freqn;      /* nominal sampling rate in fs/fps in Q16.16 format */
+	unsigned int freqm;      /* momentary sampling rate in fs/fps in Q16.16 format */
+	int          freqshift;  /* how much to shift the feedback value to get Q16.16 */
+	unsigned int freqmax;    /* maximum sampling rate, used for buffer management */
+	unsigned int phase;      /* phase accumulator */
+	unsigned int maxpacksize;	/* max packet size in bytes */
+	unsigned int maxframesize;	/* max packet size in frames */
+	unsigned int curpacksize;	/* current packet size in bytes (for capture) */
+	unsigned int curframesize;	/* current packet size in frames (for capture) */
+	unsigned int syncmaxsize;	/* sync endpoint packet size */
+	unsigned int fill_max: 1;	/* fill max packet size always */
+	unsigned int txfr_quirk:1;	/* allow sub-frame alignment */
+	unsigned int fmt_type;		/* USB audio format type (1-3) */
+
+	unsigned int running: 1;	/* running status */
+
+	unsigned int hwptr_done;	/* processed byte position in the buffer */
+	unsigned int transfer_done;		/* processed frames since last period update */
+	unsigned long active_mask;	/* bitmask of active urbs */
+	unsigned long unlink_mask;	/* bitmask of unlinked urbs */
+
+	unsigned int nurbs;			/* # urbs */
+	struct snd_urb_ctx dataurb[MAX_URBS];	/* data urb table */
+	struct snd_urb_ctx syncurb[SYNC_URBS];	/* sync urb table */
+	char *syncbuf;				/* sync buffer for all sync URBs */
+	dma_addr_t sync_dma;			/* DMA address of syncbuf */
+
+	u64 formats;			/* format bitmasks (all or'ed) */
+	unsigned int num_formats;		/* number of supported audio formats (list) */
+	struct list_head fmt_list;	/* format list */
+	struct snd_pcm_hw_constraint_list rate_list;	/* limited rates */
+	spinlock_t lock;
+
+	struct snd_urb_ops ops;		/* callbacks (must be filled at init) */
+};
+
+struct snd_usb_stream {
+	struct snd_usb_audio *chip;
+	struct snd_pcm *pcm;
+	int pcm_index;
+	unsigned int fmt_type;		/* USB audio format type (1-3) */
+	struct snd_usb_substream substream[2];
+	struct list_head list;
+};
+
+#endif /* __USBAUDIO_CARD_H */
diff --git a/linux/sound/usb/clock.c b/linux/sound/usb/clock.c
new file mode 100644
index 0000000..7754a10
--- /dev/null
+++ b/linux/sound/usb/clock.c
@@ -0,0 +1,305 @@
+/*
+ *   Clock domain and sample rate management functions
+ *
+ *   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/bitops.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "helper.h"
+#include "clock.h"
+
+static struct uac_clock_source_descriptor *
+	snd_usb_find_clock_source(struct usb_host_interface *ctrl_iface,
+				  int clock_id)
+{
+	struct uac_clock_source_descriptor *cs = NULL;
+
+	while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra,
+					     ctrl_iface->extralen,
+					     cs, UAC2_CLOCK_SOURCE))) {
+		if (cs->bClockID == clock_id)
+			return cs;
+	}
+
+	return NULL;
+}
+
+static struct uac_clock_selector_descriptor *
+	snd_usb_find_clock_selector(struct usb_host_interface *ctrl_iface,
+				    int clock_id)
+{
+	struct uac_clock_selector_descriptor *cs = NULL;
+
+	while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra,
+					     ctrl_iface->extralen,
+					     cs, UAC2_CLOCK_SELECTOR))) {
+		if (cs->bClockID == clock_id)
+			return cs;
+	}
+
+	return NULL;
+}
+
+static struct uac_clock_multiplier_descriptor *
+	snd_usb_find_clock_multiplier(struct usb_host_interface *ctrl_iface,
+				      int clock_id)
+{
+	struct uac_clock_multiplier_descriptor *cs = NULL;
+
+	while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra,
+					     ctrl_iface->extralen,
+					     cs, UAC2_CLOCK_MULTIPLIER))) {
+		if (cs->bClockID == clock_id)
+			return cs;
+	}
+
+	return NULL;
+}
+
+static int uac_clock_selector_get_val(struct snd_usb_audio *chip, int selector_id)
+{
+	unsigned char buf;
+	int ret;
+
+	ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0),
+			      UAC2_CS_CUR,
+			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+			      UAC2_CX_CLOCK_SELECTOR << 8,
+			      snd_usb_ctrl_intf(chip) | (selector_id << 8),
+			      &buf, sizeof(buf), 1000);
+
+	if (ret < 0)
+		return ret;
+
+	return buf;
+}
+
+static bool uac_clock_source_is_valid(struct snd_usb_audio *chip, int source_id)
+{
+	int err;
+	unsigned char data;
+	struct usb_device *dev = chip->dev;
+
+	err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR,
+			      USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+			      UAC2_CS_CONTROL_CLOCK_VALID << 8,
+			      snd_usb_ctrl_intf(chip) | (source_id << 8),
+			      &data, sizeof(data), 1000);
+
+	if (err < 0) {
+		snd_printk(KERN_WARNING "%s(): cannot get clock validity for id %d\n",
+			   __func__, source_id);
+		return err;
+	}
+
+	return !!data;
+}
+
+static int __uac_clock_find_source(struct snd_usb_audio *chip,
+				   int entity_id, unsigned long *visited)
+{
+	struct uac_clock_source_descriptor *source;
+	struct uac_clock_selector_descriptor *selector;
+	struct uac_clock_multiplier_descriptor *multiplier;
+
+	entity_id &= 0xff;
+
+	if (test_and_set_bit(entity_id, visited)) {
+		snd_printk(KERN_WARNING
+			"%s(): recursive clock topology detected, id %d.\n",
+			__func__, entity_id);
+		return -EINVAL;
+	}
+
+	/* first, see if the ID we're looking for is a clock source already */
+	source = snd_usb_find_clock_source(chip->ctrl_intf, entity_id);
+	if (source)
+		return source->bClockID;
+
+	selector = snd_usb_find_clock_selector(chip->ctrl_intf, entity_id);
+	if (selector) {
+		int ret;
+
+		/* the entity ID we are looking for is a selector.
+		 * find out what it currently selects */
+		ret = uac_clock_selector_get_val(chip, selector->bClockID);
+		if (ret < 0)
+			return ret;
+
+		/* Selector values are one-based */
+
+		if (ret > selector->bNrInPins || ret < 1) {
+			printk(KERN_ERR
+				"%s(): selector reported illegal value, id %d, ret %d\n",
+				__func__, selector->bClockID, ret);
+
+			return -EINVAL;
+		}
+
+		return __uac_clock_find_source(chip, selector->baCSourceID[ret-1],
+					       visited);
+	}
+
+	/* FIXME: multipliers only act as pass-thru element for now */
+	multiplier = snd_usb_find_clock_multiplier(chip->ctrl_intf, entity_id);
+	if (multiplier)
+		return __uac_clock_find_source(chip, multiplier->bCSourceID,
+						visited);
+
+	return -EINVAL;
+}
+
+/*
+ * For all kinds of sample rate settings and other device queries,
+ * the clock source (end-leaf) must be used. However, clock selectors,
+ * clock multipliers and sample rate converters may be specified as
+ * clock source input to terminal. This functions walks the clock path
+ * to its end and tries to find the source.
+ *
+ * The 'visited' bitfield is used internally to detect recursive loops.
+ *
+ * Returns the clock source UnitID (>=0) on success, or an error.
+ */
+int snd_usb_clock_find_source(struct snd_usb_audio *chip, int entity_id)
+{
+	DECLARE_BITMAP(visited, 256);
+	memset(visited, 0, sizeof(visited));
+	return __uac_clock_find_source(chip, entity_id, visited);
+}
+
+static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface,
+			      struct usb_host_interface *alts,
+			      struct audioformat *fmt, int rate)
+{
+	struct usb_device *dev = chip->dev;
+	unsigned int ep;
+	unsigned char data[3];
+	int err, crate;
+
+	ep = get_endpoint(alts, 0)->bEndpointAddress;
+
+	/* if endpoint doesn't have sampling rate control, bail out */
+	if (!(fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE))
+		return 0;
+
+	data[0] = rate;
+	data[1] = rate >> 8;
+	data[2] = rate >> 16;
+	if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
+				   USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT,
+				   UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep,
+				   data, sizeof(data), 1000)) < 0) {
+		snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep %#x\n",
+			   dev->devnum, iface, fmt->altsetting, rate, ep);
+		return err;
+	}
+
+	if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR,
+				   USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN,
+				   UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep,
+				   data, sizeof(data), 1000)) < 0) {
+		snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq at ep %#x\n",
+			   dev->devnum, iface, fmt->altsetting, ep);
+		return 0; /* some devices don't support reading */
+	}
+
+	crate = data[0] | (data[1] << 8) | (data[2] << 16);
+	if (crate != rate) {
+		snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate);
+		// runtime->rate = crate;
+	}
+
+	return 0;
+}
+
+static int set_sample_rate_v2(struct snd_usb_audio *chip, int iface,
+			      struct usb_host_interface *alts,
+			      struct audioformat *fmt, int rate)
+{
+	struct usb_device *dev = chip->dev;
+	unsigned char data[4];
+	int err, crate;
+	int clock = snd_usb_clock_find_source(chip, fmt->clock);
+
+	if (clock < 0)
+		return clock;
+
+	if (!uac_clock_source_is_valid(chip, clock)) {
+		/* TODO: should we try to find valid clock setups by ourself? */
+		snd_printk(KERN_ERR "%d:%d:%d: clock source %d is not valid, cannot use\n",
+			   dev->devnum, iface, fmt->altsetting, clock);
+		return -ENXIO;
+	}
+
+	data[0] = rate;
+	data[1] = rate >> 8;
+	data[2] = rate >> 16;
+	data[3] = rate >> 24;
+	if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR,
+				   USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
+				   UAC2_CS_CONTROL_SAM_FREQ << 8,
+				   snd_usb_ctrl_intf(chip) | (clock << 8),
+				   data, sizeof(data), 1000)) < 0) {
+		snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d (v2)\n",
+			   dev->devnum, iface, fmt->altsetting, rate);
+		return err;
+	}
+
+	if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR,
+				   USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+				   UAC2_CS_CONTROL_SAM_FREQ << 8,
+				   snd_usb_ctrl_intf(chip) | (clock << 8),
+				   data, sizeof(data), 1000)) < 0) {
+		snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq (v2)\n",
+			   dev->devnum, iface, fmt->altsetting);
+		return err;
+	}
+
+	crate = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
+	if (crate != rate)
+		snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate);
+
+	return 0;
+}
+
+int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface,
+			     struct usb_host_interface *alts,
+			     struct audioformat *fmt, int rate)
+{
+	struct usb_interface_descriptor *altsd = get_iface_desc(alts);
+
+	switch (altsd->bInterfaceProtocol) {
+	case UAC_VERSION_1:
+	default:
+		return set_sample_rate_v1(chip, iface, alts, fmt, rate);
+
+	case UAC_VERSION_2:
+		return set_sample_rate_v2(chip, iface, alts, fmt, rate);
+	}
+}
+
diff --git a/linux/sound/usb/clock.h b/linux/sound/usb/clock.h
new file mode 100644
index 0000000..4663093
--- /dev/null
+++ b/linux/sound/usb/clock.h
@@ -0,0 +1,10 @@
+#ifndef __USBAUDIO_CLOCK_H
+#define __USBAUDIO_CLOCK_H
+
+int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface,
+			     struct usb_host_interface *alts,
+			     struct audioformat *fmt, int rate);
+
+int snd_usb_clock_find_source(struct snd_usb_audio *chip, int entity_id);
+
+#endif /* __USBAUDIO_CLOCK_H */
diff --git a/linux/sound/usb/debug.h b/linux/sound/usb/debug.h
new file mode 100644
index 0000000..343ec2d
--- /dev/null
+++ b/linux/sound/usb/debug.h
@@ -0,0 +1,15 @@
+#ifndef __USBAUDIO_DEBUG_H
+#define __USBAUDIO_DEBUG_H
+
+/*
+ * h/w constraints
+ */
+
+#ifdef HW_CONST_DEBUG
+#define hwc_debug(fmt, args...) printk(KERN_DEBUG fmt, ##args)
+#else
+#define hwc_debug(fmt, args...) /**/
+#endif
+
+#endif /* __USBAUDIO_DEBUG_H */
+
diff --git a/linux/sound/usb/endpoint.c b/linux/sound/usb/endpoint.c
new file mode 100644
index 0000000..b0ef9f5
--- /dev/null
+++ b/linux/sound/usb/endpoint.c
@@ -0,0 +1,448 @@
+/*
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "proc.h"
+#include "quirks.h"
+#include "endpoint.h"
+#include "urb.h"
+#include "pcm.h"
+#include "helper.h"
+#include "format.h"
+#include "clock.h"
+
+/*
+ * free a substream
+ */
+static void free_substream(struct snd_usb_substream *subs)
+{
+	struct list_head *p, *n;
+
+	if (!subs->num_formats)
+		return; /* not initialized */
+	list_for_each_safe(p, n, &subs->fmt_list) {
+		struct audioformat *fp = list_entry(p, struct audioformat, list);
+		kfree(fp->rate_table);
+		kfree(fp);
+	}
+	kfree(subs->rate_list.list);
+}
+
+
+/*
+ * free a usb stream instance
+ */
+static void snd_usb_audio_stream_free(struct snd_usb_stream *stream)
+{
+	free_substream(&stream->substream[0]);
+	free_substream(&stream->substream[1]);
+	list_del(&stream->list);
+	kfree(stream);
+}
+
+static void snd_usb_audio_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_usb_stream *stream = pcm->private_data;
+	if (stream) {
+		stream->pcm = NULL;
+		snd_usb_audio_stream_free(stream);
+	}
+}
+
+
+/*
+ * add this endpoint to the chip instance.
+ * if a stream with the same endpoint already exists, append to it.
+ * if not, create a new pcm stream.
+ */
+int snd_usb_add_audio_endpoint(struct snd_usb_audio *chip, int stream, struct audioformat *fp)
+{
+	struct list_head *p;
+	struct snd_usb_stream *as;
+	struct snd_usb_substream *subs;
+	struct snd_pcm *pcm;
+	int err;
+
+	list_for_each(p, &chip->pcm_list) {
+		as = list_entry(p, struct snd_usb_stream, list);
+		if (as->fmt_type != fp->fmt_type)
+			continue;
+		subs = &as->substream[stream];
+		if (!subs->endpoint)
+			continue;
+		if (subs->endpoint == fp->endpoint) {
+			list_add_tail(&fp->list, &subs->fmt_list);
+			subs->num_formats++;
+			subs->formats |= fp->formats;
+			return 0;
+		}
+	}
+	/* look for an empty stream */
+	list_for_each(p, &chip->pcm_list) {
+		as = list_entry(p, struct snd_usb_stream, list);
+		if (as->fmt_type != fp->fmt_type)
+			continue;
+		subs = &as->substream[stream];
+		if (subs->endpoint)
+			continue;
+		err = snd_pcm_new_stream(as->pcm, stream, 1);
+		if (err < 0)
+			return err;
+		snd_usb_init_substream(as, stream, fp);
+		return 0;
+	}
+
+	/* create a new pcm */
+	as = kzalloc(sizeof(*as), GFP_KERNEL);
+	if (!as)
+		return -ENOMEM;
+	as->pcm_index = chip->pcm_devs;
+	as->chip = chip;
+	as->fmt_type = fp->fmt_type;
+	err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs,
+			  stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0,
+			  stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1,
+			  &pcm);
+	if (err < 0) {
+		kfree(as);
+		return err;
+	}
+	as->pcm = pcm;
+	pcm->private_data = as;
+	pcm->private_free = snd_usb_audio_pcm_free;
+	pcm->info_flags = 0;
+	if (chip->pcm_devs > 0)
+		sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs);
+	else
+		strcpy(pcm->name, "USB Audio");
+
+	snd_usb_init_substream(as, stream, fp);
+
+	list_add(&as->list, &chip->pcm_list);
+	chip->pcm_devs++;
+
+	snd_usb_proc_pcm_format_add(as);
+
+	return 0;
+}
+
+static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip,
+					 struct usb_host_interface *alts,
+					 int protocol, int iface_no)
+{
+	/* parsed with a v1 header here. that's ok as we only look at the
+	 * header first which is the same for both versions */
+	struct uac_iso_endpoint_descriptor *csep;
+	struct usb_interface_descriptor *altsd = get_iface_desc(alts);
+	int attributes = 0;
+
+	csep = snd_usb_find_desc(alts->endpoint[0].extra, alts->endpoint[0].extralen, NULL, USB_DT_CS_ENDPOINT);
+
+	/* Creamware Noah has this descriptor after the 2nd endpoint */
+	if (!csep && altsd->bNumEndpoints >= 2)
+		csep = snd_usb_find_desc(alts->endpoint[1].extra, alts->endpoint[1].extralen, NULL, USB_DT_CS_ENDPOINT);
+
+	if (!csep || csep->bLength < 7 ||
+	    csep->bDescriptorSubtype != UAC_EP_GENERAL) {
+		snd_printk(KERN_WARNING "%d:%u:%d : no or invalid"
+			   " class specific endpoint descriptor\n",
+			   chip->dev->devnum, iface_no,
+			   altsd->bAlternateSetting);
+		return 0;
+	}
+
+	if (protocol == UAC_VERSION_1) {
+		attributes = csep->bmAttributes;
+	} else {
+		struct uac2_iso_endpoint_descriptor *csep2 =
+			(struct uac2_iso_endpoint_descriptor *) csep;
+
+		attributes = csep->bmAttributes & UAC_EP_CS_ATTR_FILL_MAX;
+
+		/* emulate the endpoint attributes of a v1 device */
+		if (csep2->bmControls & UAC2_CONTROL_PITCH)
+			attributes |= UAC_EP_CS_ATTR_PITCH_CONTROL;
+	}
+
+	return attributes;
+}
+
+static struct uac2_input_terminal_descriptor *
+	snd_usb_find_input_terminal_descriptor(struct usb_host_interface *ctrl_iface,
+					       int terminal_id)
+{
+	struct uac2_input_terminal_descriptor *term = NULL;
+
+	while ((term = snd_usb_find_csint_desc(ctrl_iface->extra,
+					       ctrl_iface->extralen,
+					       term, UAC_INPUT_TERMINAL))) {
+		if (term->bTerminalID == terminal_id)
+			return term;
+	}
+
+	return NULL;
+}
+
+static struct uac2_output_terminal_descriptor *
+	snd_usb_find_output_terminal_descriptor(struct usb_host_interface *ctrl_iface,
+						int terminal_id)
+{
+	struct uac2_output_terminal_descriptor *term = NULL;
+
+	while ((term = snd_usb_find_csint_desc(ctrl_iface->extra,
+					       ctrl_iface->extralen,
+					       term, UAC_OUTPUT_TERMINAL))) {
+		if (term->bTerminalID == terminal_id)
+			return term;
+	}
+
+	return NULL;
+}
+
+int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
+{
+	struct usb_device *dev;
+	struct usb_interface *iface;
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	int i, altno, err, stream;
+	int format = 0, num_channels = 0;
+	struct audioformat *fp = NULL;
+	int num, protocol, clock = 0;
+	struct uac_format_type_i_continuous_descriptor *fmt;
+
+	dev = chip->dev;
+
+	/* parse the interface's altsettings */
+	iface = usb_ifnum_to_if(dev, iface_no);
+
+	num = iface->num_altsetting;
+
+	/*
+	 * Dallas DS4201 workaround: It presents 5 altsettings, but the last
+	 * one misses syncpipe, and does not produce any sound.
+	 */
+	if (chip->usb_id == USB_ID(0x04fa, 0x4201))
+		num = 4;
+
+	for (i = 0; i < num; i++) {
+		alts = &iface->altsetting[i];
+		altsd = get_iface_desc(alts);
+		protocol = altsd->bInterfaceProtocol;
+		/* skip invalid one */
+		if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
+		     altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
+		    (altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING &&
+		     altsd->bInterfaceSubClass != USB_SUBCLASS_VENDOR_SPEC) ||
+		    altsd->bNumEndpoints < 1 ||
+		    le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == 0)
+			continue;
+		/* must be isochronous */
+		if ((get_endpoint(alts, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
+		    USB_ENDPOINT_XFER_ISOC)
+			continue;
+		/* check direction */
+		stream = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN) ?
+			SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+		altno = altsd->bAlternateSetting;
+
+		if (snd_usb_apply_interface_quirk(chip, iface_no, altno))
+			continue;
+
+		/* get audio formats */
+		switch (protocol) {
+		default:
+			snd_printdd(KERN_WARNING "%d:%u:%d: unknown interface protocol %#02x, assuming v1\n",
+				    dev->devnum, iface_no, altno, protocol);
+			protocol = UAC_VERSION_1;
+			/* fall through */
+
+		case UAC_VERSION_1: {
+			struct uac1_as_header_descriptor *as =
+				snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
+
+			if (!as) {
+				snd_printk(KERN_ERR "%d:%u:%d : UAC_AS_GENERAL descriptor not found\n",
+					   dev->devnum, iface_no, altno);
+				continue;
+			}
+
+			if (as->bLength < sizeof(*as)) {
+				snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_AS_GENERAL desc\n",
+					   dev->devnum, iface_no, altno);
+				continue;
+			}
+
+			format = le16_to_cpu(as->wFormatTag); /* remember the format value */
+			break;
+		}
+
+		case UAC_VERSION_2: {
+			struct uac2_input_terminal_descriptor *input_term;
+			struct uac2_output_terminal_descriptor *output_term;
+			struct uac2_as_header_descriptor *as =
+				snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
+
+			if (!as) {
+				snd_printk(KERN_ERR "%d:%u:%d : UAC_AS_GENERAL descriptor not found\n",
+					   dev->devnum, iface_no, altno);
+				continue;
+			}
+
+			if (as->bLength < sizeof(*as)) {
+				snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_AS_GENERAL desc\n",
+					   dev->devnum, iface_no, altno);
+				continue;
+			}
+
+			num_channels = as->bNrChannels;
+			format = le32_to_cpu(as->bmFormats);
+
+			/* lookup the terminal associated to this interface
+			 * to extract the clock */
+			input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf,
+									    as->bTerminalLink);
+			if (input_term) {
+				clock = input_term->bCSourceID;
+				break;
+			}
+
+			output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf,
+									      as->bTerminalLink);
+			if (output_term) {
+				clock = output_term->bCSourceID;
+				break;
+			}
+
+			snd_printk(KERN_ERR "%d:%u:%d : bogus bTerminalLink %d\n",
+				   dev->devnum, iface_no, altno, as->bTerminalLink);
+			continue;
+		}
+		}
+
+		/* get format type */
+		fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_FORMAT_TYPE);
+		if (!fmt) {
+			snd_printk(KERN_ERR "%d:%u:%d : no UAC_FORMAT_TYPE desc\n",
+				   dev->devnum, iface_no, altno);
+			continue;
+		}
+		if (((protocol == UAC_VERSION_1) && (fmt->bLength < 8)) ||
+		    ((protocol == UAC_VERSION_2) && (fmt->bLength != 6))) {
+			snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_FORMAT_TYPE desc\n",
+				   dev->devnum, iface_no, altno);
+			continue;
+		}
+
+		/*
+		 * Blue Microphones workaround: The last altsetting is identical
+		 * with the previous one, except for a larger packet size, but
+		 * is actually a mislabeled two-channel setting; ignore it.
+		 */
+		if (fmt->bNrChannels == 1 &&
+		    fmt->bSubframeSize == 2 &&
+		    altno == 2 && num == 3 &&
+		    fp && fp->altsetting == 1 && fp->channels == 1 &&
+		    fp->formats == SNDRV_PCM_FMTBIT_S16_LE &&
+		    protocol == UAC_VERSION_1 &&
+		    le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) ==
+							fp->maxpacksize * 2)
+			continue;
+
+		fp = kzalloc(sizeof(*fp), GFP_KERNEL);
+		if (! fp) {
+			snd_printk(KERN_ERR "cannot malloc\n");
+			return -ENOMEM;
+		}
+
+		fp->iface = iface_no;
+		fp->altsetting = altno;
+		fp->altset_idx = i;
+		fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
+		fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
+		fp->datainterval = snd_usb_parse_datainterval(chip, alts);
+		fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+		/* num_channels is only set for v2 interfaces */
+		fp->channels = num_channels;
+		if (snd_usb_get_speed(dev) == USB_SPEED_HIGH)
+			fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1)
+					* (fp->maxpacksize & 0x7ff);
+		fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, iface_no);
+		fp->clock = clock;
+
+		/* some quirks for attributes here */
+
+		switch (chip->usb_id) {
+		case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */
+			/* Optoplay sets the sample rate attribute although
+			 * it seems not supporting it in fact.
+			 */
+			fp->attributes &= ~UAC_EP_CS_ATTR_SAMPLE_RATE;
+			break;
+		case USB_ID(0x041e, 0x3020): /* Creative SB Audigy 2 NX */
+		case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
+			/* doesn't set the sample rate attribute, but supports it */
+			fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE;
+			break;
+		case USB_ID(0x047f, 0x0ca1): /* plantronics headset */
+		case USB_ID(0x077d, 0x07af): /* Griffin iMic (note that there is
+						an older model 77d:223) */
+		/*
+		 * plantronics headset and Griffin iMic have set adaptive-in
+		 * although it's really not...
+		 */
+			fp->ep_attr &= ~USB_ENDPOINT_SYNCTYPE;
+			if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+				fp->ep_attr |= USB_ENDPOINT_SYNC_ADAPTIVE;
+			else
+				fp->ep_attr |= USB_ENDPOINT_SYNC_SYNC;
+			break;
+		}
+
+		/* ok, let's parse further... */
+		if (snd_usb_parse_audio_format(chip, fp, format, fmt, stream, alts) < 0) {
+			kfree(fp->rate_table);
+			kfree(fp);
+			fp = NULL;
+			continue;
+		}
+
+		snd_printdd(KERN_INFO "%d:%u:%d: add audio endpoint %#x\n", dev->devnum, iface_no, altno, fp->endpoint);
+		err = snd_usb_add_audio_endpoint(chip, stream, fp);
+		if (err < 0) {
+			kfree(fp->rate_table);
+			kfree(fp);
+			return err;
+		}
+		/* try to set the interface... */
+		usb_set_interface(chip->dev, iface_no, altno);
+		snd_usb_init_pitch(chip, iface_no, alts, fp);
+		snd_usb_init_sample_rate(chip, iface_no, alts, fp, fp->rate_max);
+	}
+	return 0;
+}
+
diff --git a/linux/sound/usb/endpoint.h b/linux/sound/usb/endpoint.h
new file mode 100644
index 0000000..64dd0db
--- /dev/null
+++ b/linux/sound/usb/endpoint.h
@@ -0,0 +1,11 @@
+#ifndef __USBAUDIO_ENDPOINT_H
+#define __USBAUDIO_ENDPOINT_H
+
+int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip,
+				  int iface_no);
+
+int snd_usb_add_audio_endpoint(struct snd_usb_audio *chip,
+			       int stream,
+			       struct audioformat *fp);
+
+#endif /* __USBAUDIO_ENDPOINT_H */
diff --git a/linux/sound/usb/format.c b/linux/sound/usb/format.c
new file mode 100644
index 0000000..6914821
--- /dev/null
+++ b/linux/sound/usb/format.c
@@ -0,0 +1,505 @@
+/*
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "quirks.h"
+#include "helper.h"
+#include "debug.h"
+#include "clock.h"
+
+/*
+ * parse the audio format type I descriptor
+ * and returns the corresponding pcm format
+ *
+ * @dev: usb device
+ * @fp: audioformat record
+ * @format: the format tag (wFormatTag)
+ * @fmt: the format type descriptor
+ */
+static u64 parse_audio_format_i_type(struct snd_usb_audio *chip,
+				     struct audioformat *fp,
+				     int format, void *_fmt,
+				     int protocol)
+{
+	int sample_width, sample_bytes;
+	u64 pcm_formats;
+
+	switch (protocol) {
+	case UAC_VERSION_1:
+	default: {
+		struct uac_format_type_i_discrete_descriptor *fmt = _fmt;
+		sample_width = fmt->bBitResolution;
+		sample_bytes = fmt->bSubframeSize;
+		format = 1 << format;
+		break;
+	}
+
+	case UAC_VERSION_2: {
+		struct uac_format_type_i_ext_descriptor *fmt = _fmt;
+		sample_width = fmt->bBitResolution;
+		sample_bytes = fmt->bSubslotSize;
+		format <<= 1;
+		break;
+	}
+	}
+
+	pcm_formats = 0;
+
+	if (format == 0 || format == (1 << UAC_FORMAT_TYPE_I_UNDEFINED)) {
+		/* some devices don't define this correctly... */
+		snd_printdd(KERN_INFO "%d:%u:%d : format type 0 is detected, processed as PCM\n",
+			    chip->dev->devnum, fp->iface, fp->altsetting);
+		format = 1 << UAC_FORMAT_TYPE_I_PCM;
+	}
+	if (format & (1 << UAC_FORMAT_TYPE_I_PCM)) {
+		if (sample_width > sample_bytes * 8) {
+			snd_printk(KERN_INFO "%d:%u:%d : sample bitwidth %d in over sample bytes %d\n",
+				   chip->dev->devnum, fp->iface, fp->altsetting,
+				   sample_width, sample_bytes);
+		}
+		/* check the format byte size */
+		switch (sample_bytes) {
+		case 1:
+			pcm_formats |= SNDRV_PCM_FMTBIT_S8;
+			break;
+		case 2:
+			if (snd_usb_is_big_endian_format(chip, fp))
+				pcm_formats |= SNDRV_PCM_FMTBIT_S16_BE; /* grrr, big endian!! */
+			else
+				pcm_formats |= SNDRV_PCM_FMTBIT_S16_LE;
+			break;
+		case 3:
+			if (snd_usb_is_big_endian_format(chip, fp))
+				pcm_formats |= SNDRV_PCM_FMTBIT_S24_3BE; /* grrr, big endian!! */
+			else
+				pcm_formats |= SNDRV_PCM_FMTBIT_S24_3LE;
+			break;
+		case 4:
+			pcm_formats |= SNDRV_PCM_FMTBIT_S32_LE;
+			break;
+		default:
+			snd_printk(KERN_INFO "%d:%u:%d : unsupported sample bitwidth %d in %d bytes\n",
+				   chip->dev->devnum, fp->iface, fp->altsetting,
+				   sample_width, sample_bytes);
+			break;
+		}
+	}
+	if (format & (1 << UAC_FORMAT_TYPE_I_PCM8)) {
+		/* Dallas DS4201 workaround: it advertises U8 format, but really
+		   supports S8. */
+		if (chip->usb_id == USB_ID(0x04fa, 0x4201))
+			pcm_formats |= SNDRV_PCM_FMTBIT_S8;
+		else
+			pcm_formats |= SNDRV_PCM_FMTBIT_U8;
+	}
+	if (format & (1 << UAC_FORMAT_TYPE_I_IEEE_FLOAT)) {
+		pcm_formats |= SNDRV_PCM_FMTBIT_FLOAT_LE;
+	}
+	if (format & (1 << UAC_FORMAT_TYPE_I_ALAW)) {
+		pcm_formats |= SNDRV_PCM_FMTBIT_A_LAW;
+	}
+	if (format & (1 << UAC_FORMAT_TYPE_I_MULAW)) {
+		pcm_formats |= SNDRV_PCM_FMTBIT_MU_LAW;
+	}
+	if (format & ~0x3f) {
+		snd_printk(KERN_INFO "%d:%u:%d : unsupported format bits %#x\n",
+			   chip->dev->devnum, fp->iface, fp->altsetting, format);
+	}
+	return pcm_formats;
+}
+
+
+/*
+ * parse the format descriptor and stores the possible sample rates
+ * on the audioformat table (audio class v1).
+ *
+ * @dev: usb device
+ * @fp: audioformat record
+ * @fmt: the format descriptor
+ * @offset: the start offset of descriptor pointing the rate type
+ *          (7 for type I and II, 8 for type II)
+ */
+static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audioformat *fp,
+				       unsigned char *fmt, int offset)
+{
+	int nr_rates = fmt[offset];
+
+	if (fmt[0] < offset + 1 + 3 * (nr_rates ? nr_rates : 2)) {
+		snd_printk(KERN_ERR "%d:%u:%d : invalid UAC_FORMAT_TYPE desc\n",
+				   chip->dev->devnum, fp->iface, fp->altsetting);
+		return -1;
+	}
+
+	if (nr_rates) {
+		/*
+		 * build the rate table and bitmap flags
+		 */
+		int r, idx;
+
+		fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL);
+		if (fp->rate_table == NULL) {
+			snd_printk(KERN_ERR "cannot malloc\n");
+			return -1;
+		}
+
+		fp->nr_rates = 0;
+		fp->rate_min = fp->rate_max = 0;
+		for (r = 0, idx = offset + 1; r < nr_rates; r++, idx += 3) {
+			unsigned int rate = combine_triple(&fmt[idx]);
+			if (!rate)
+				continue;
+			/* C-Media CM6501 mislabels its 96 kHz altsetting */
+			if (rate == 48000 && nr_rates == 1 &&
+			    (chip->usb_id == USB_ID(0x0d8c, 0x0201) ||
+			     chip->usb_id == USB_ID(0x0d8c, 0x0102)) &&
+			    fp->altsetting == 5 && fp->maxpacksize == 392)
+				rate = 96000;
+			/* Creative VF0470 Live Cam reports 16 kHz instead of 8kHz */
+			if (rate == 16000 && chip->usb_id == USB_ID(0x041e, 0x4068))
+				rate = 8000;
+
+			fp->rate_table[fp->nr_rates] = rate;
+			if (!fp->rate_min || rate < fp->rate_min)
+				fp->rate_min = rate;
+			if (!fp->rate_max || rate > fp->rate_max)
+				fp->rate_max = rate;
+			fp->rates |= snd_pcm_rate_to_rate_bit(rate);
+			fp->nr_rates++;
+		}
+		if (!fp->nr_rates) {
+			hwc_debug("All rates were zero. Skipping format!\n");
+			return -1;
+		}
+	} else {
+		/* continuous rates */
+		fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
+		fp->rate_min = combine_triple(&fmt[offset + 1]);
+		fp->rate_max = combine_triple(&fmt[offset + 4]);
+	}
+	return 0;
+}
+
+/*
+ * Helper function to walk the array of sample rate triplets reported by
+ * the device. The problem is that we need to parse whole array first to
+ * get to know how many sample rates we have to expect.
+ * Then fp->rate_table can be allocated and filled.
+ */
+static int parse_uac2_sample_rate_range(struct audioformat *fp, int nr_triplets,
+					const unsigned char *data)
+{
+	int i, nr_rates = 0;
+
+	fp->rates = fp->rate_min = fp->rate_max = 0;
+
+	for (i = 0; i < nr_triplets; i++) {
+		int min = combine_quad(&data[2 + 12 * i]);
+		int max = combine_quad(&data[6 + 12 * i]);
+		int res = combine_quad(&data[10 + 12 * i]);
+		int rate;
+
+		if ((max < 0) || (min < 0) || (res < 0) || (max < min))
+			continue;
+
+		/*
+		 * for ranges with res == 1, we announce a continuous sample
+		 * rate range, and this function should return 0 for no further
+		 * parsing.
+		 */
+		if (res == 1) {
+			fp->rate_min = min;
+			fp->rate_max = max;
+			fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
+			return 0;
+		}
+
+		for (rate = min; rate <= max; rate += res) {
+			if (fp->rate_table)
+				fp->rate_table[nr_rates] = rate;
+			if (!fp->rate_min || rate < fp->rate_min)
+				fp->rate_min = rate;
+			if (!fp->rate_max || rate > fp->rate_max)
+				fp->rate_max = rate;
+			fp->rates |= snd_pcm_rate_to_rate_bit(rate);
+
+			nr_rates++;
+
+			/* avoid endless loop */
+			if (res == 0)
+				break;
+		}
+	}
+
+	return nr_rates;
+}
+
+/*
+ * parse the format descriptor and stores the possible sample rates
+ * on the audioformat table (audio class v2).
+ */
+static int parse_audio_format_rates_v2(struct snd_usb_audio *chip,
+				       struct audioformat *fp)
+{
+	struct usb_device *dev = chip->dev;
+	unsigned char tmp[2], *data;
+	int nr_triplets, data_size, ret = 0;
+	int clock = snd_usb_clock_find_source(chip, fp->clock);
+
+	if (clock < 0) {
+		snd_printk(KERN_ERR "%s(): unable to find clock source (clock %d)\n",
+				__func__, clock);
+		goto err;
+	}
+
+	/* get the number of sample rates first by only fetching 2 bytes */
+	ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE,
+			      USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+			      UAC2_CS_CONTROL_SAM_FREQ << 8,
+			      snd_usb_ctrl_intf(chip) | (clock << 8),
+			      tmp, sizeof(tmp), 1000);
+
+	if (ret < 0) {
+		snd_printk(KERN_ERR "%s(): unable to retrieve number of sample rates (clock %d)\n",
+				__func__, clock);
+		goto err;
+	}
+
+	nr_triplets = (tmp[1] << 8) | tmp[0];
+	data_size = 2 + 12 * nr_triplets;
+	data = kzalloc(data_size, GFP_KERNEL);
+	if (!data) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	/* now get the full information */
+	ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE,
+			      USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+			      UAC2_CS_CONTROL_SAM_FREQ << 8,
+			      snd_usb_ctrl_intf(chip) | (clock << 8),
+			      data, data_size, 1000);
+
+	if (ret < 0) {
+		snd_printk(KERN_ERR "%s(): unable to retrieve sample rate range (clock %d)\n",
+				__func__, clock);
+		ret = -EINVAL;
+		goto err_free;
+	}
+
+	/* Call the triplet parser, and make sure fp->rate_table is NULL.
+	 * We just use the return value to know how many sample rates we
+	 * will have to deal with. */
+	kfree(fp->rate_table);
+	fp->rate_table = NULL;
+	fp->nr_rates = parse_uac2_sample_rate_range(fp, nr_triplets, data);
+
+	if (fp->nr_rates == 0) {
+		/* SNDRV_PCM_RATE_CONTINUOUS */
+		ret = 0;
+		goto err_free;
+	}
+
+	fp->rate_table = kmalloc(sizeof(int) * fp->nr_rates, GFP_KERNEL);
+	if (!fp->rate_table) {
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	/* Call the triplet parser again, but this time, fp->rate_table is
+	 * allocated, so the rates will be stored */
+	parse_uac2_sample_rate_range(fp, nr_triplets, data);
+
+err_free:
+	kfree(data);
+err:
+	return ret;
+}
+
+/*
+ * parse the format type I and III descriptors
+ */
+static int parse_audio_format_i(struct snd_usb_audio *chip,
+				struct audioformat *fp, int format,
+				struct uac_format_type_i_continuous_descriptor *fmt,
+				struct usb_host_interface *iface)
+{
+	struct usb_interface_descriptor *altsd = get_iface_desc(iface);
+	int protocol = altsd->bInterfaceProtocol;
+	int pcm_format, ret;
+
+	if (fmt->bFormatType == UAC_FORMAT_TYPE_III) {
+		/* FIXME: the format type is really IECxxx
+		 *        but we give normal PCM format to get the existing
+		 *        apps working...
+		 */
+		switch (chip->usb_id) {
+
+		case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
+			if (chip->setup == 0x00 && 
+			    fp->altsetting == 6)
+				pcm_format = SNDRV_PCM_FORMAT_S16_BE;
+			else
+				pcm_format = SNDRV_PCM_FORMAT_S16_LE;
+			break;
+		default:
+			pcm_format = SNDRV_PCM_FORMAT_S16_LE;
+		}
+		fp->formats = 1uLL << pcm_format;
+	} else {
+		fp->formats = parse_audio_format_i_type(chip, fp, format,
+							fmt, protocol);
+		if (!fp->formats)
+			return -1;
+	}
+
+	/* gather possible sample rates */
+	/* audio class v1 reports possible sample rates as part of the
+	 * proprietary class specific descriptor.
+	 * audio class v2 uses class specific EP0 range requests for that.
+	 */
+	switch (protocol) {
+	default:
+		snd_printdd(KERN_WARNING "%d:%u:%d : invalid protocol version %d, assuming v1\n",
+			   chip->dev->devnum, fp->iface, fp->altsetting, protocol);
+		/* fall through */
+	case UAC_VERSION_1:
+		fp->channels = fmt->bNrChannels;
+		ret = parse_audio_format_rates_v1(chip, fp, (unsigned char *) fmt, 7);
+		break;
+	case UAC_VERSION_2:
+		/* fp->channels is already set in this case */
+		ret = parse_audio_format_rates_v2(chip, fp);
+		break;
+	}
+
+	if (fp->channels < 1) {
+		snd_printk(KERN_ERR "%d:%u:%d : invalid channels %d\n",
+			   chip->dev->devnum, fp->iface, fp->altsetting, fp->channels);
+		return -1;
+	}
+
+	return ret;
+}
+
+/*
+ * parse the format type II descriptor
+ */
+static int parse_audio_format_ii(struct snd_usb_audio *chip,
+				 struct audioformat *fp,
+				 int format, void *_fmt,
+				 struct usb_host_interface *iface)
+{
+	int brate, framesize, ret;
+	struct usb_interface_descriptor *altsd = get_iface_desc(iface);
+	int protocol = altsd->bInterfaceProtocol;
+
+	switch (format) {
+	case UAC_FORMAT_TYPE_II_AC3:
+		/* FIXME: there is no AC3 format defined yet */
+		// fp->formats = SNDRV_PCM_FMTBIT_AC3;
+		fp->formats = SNDRV_PCM_FMTBIT_U8; /* temporary hack to receive byte streams */
+		break;
+	case UAC_FORMAT_TYPE_II_MPEG:
+		fp->formats = SNDRV_PCM_FMTBIT_MPEG;
+		break;
+	default:
+		snd_printd(KERN_INFO "%d:%u:%d : unknown format tag %#x is detected.  processed as MPEG.\n",
+			   chip->dev->devnum, fp->iface, fp->altsetting, format);
+		fp->formats = SNDRV_PCM_FMTBIT_MPEG;
+		break;
+	}
+
+	fp->channels = 1;
+
+	switch (protocol) {
+	default:
+		snd_printdd(KERN_WARNING "%d:%u:%d : invalid protocol version %d, assuming v1\n",
+			   chip->dev->devnum, fp->iface, fp->altsetting, protocol);
+		/* fall through */
+	case UAC_VERSION_1: {
+		struct uac_format_type_ii_discrete_descriptor *fmt = _fmt;
+		brate = le16_to_cpu(fmt->wMaxBitRate);
+		framesize = le16_to_cpu(fmt->wSamplesPerFrame);
+		snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
+		fp->frame_size = framesize;
+		ret = parse_audio_format_rates_v1(chip, fp, _fmt, 8); /* fmt[8..] sample rates */
+		break;
+	}
+	case UAC_VERSION_2: {
+		struct uac_format_type_ii_ext_descriptor *fmt = _fmt;
+		brate = le16_to_cpu(fmt->wMaxBitRate);
+		framesize = le16_to_cpu(fmt->wSamplesPerFrame);
+		snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
+		fp->frame_size = framesize;
+		ret = parse_audio_format_rates_v2(chip, fp);
+		break;
+	}
+	}
+
+	return ret;
+}
+
+int snd_usb_parse_audio_format(struct snd_usb_audio *chip, struct audioformat *fp,
+			       int format, struct uac_format_type_i_continuous_descriptor *fmt,
+			       int stream, struct usb_host_interface *iface)
+{
+	int err;
+
+	switch (fmt->bFormatType) {
+	case UAC_FORMAT_TYPE_I:
+	case UAC_FORMAT_TYPE_III:
+		err = parse_audio_format_i(chip, fp, format, fmt, iface);
+		break;
+	case UAC_FORMAT_TYPE_II:
+		err = parse_audio_format_ii(chip, fp, format, fmt, iface);
+		break;
+	default:
+		snd_printd(KERN_INFO "%d:%u:%d : format type %d is not supported yet\n",
+			   chip->dev->devnum, fp->iface, fp->altsetting,
+			   fmt->bFormatType);
+		return -ENOTSUPP;
+	}
+	fp->fmt_type = fmt->bFormatType;
+	if (err < 0)
+		return err;
+#if 1
+	/* FIXME: temporary hack for extigy/audigy 2 nx/zs */
+	/* extigy apparently supports sample rates other than 48k
+	 * but not in ordinary way.  so we enable only 48k atm.
+	 */
+	if (chip->usb_id == USB_ID(0x041e, 0x3000) ||
+	    chip->usb_id == USB_ID(0x041e, 0x3020) ||
+	    chip->usb_id == USB_ID(0x041e, 0x3061)) {
+		if (fmt->bFormatType == UAC_FORMAT_TYPE_I &&
+		    fp->rates != SNDRV_PCM_RATE_48000 &&
+		    fp->rates != SNDRV_PCM_RATE_96000)
+			return -ENOTSUPP;
+	}
+#endif
+	return 0;
+}
+
diff --git a/linux/sound/usb/format.h b/linux/sound/usb/format.h
new file mode 100644
index 0000000..387924f
--- /dev/null
+++ b/linux/sound/usb/format.h
@@ -0,0 +1,9 @@
+#ifndef __USBAUDIO_FORMAT_H
+#define __USBAUDIO_FORMAT_H
+
+int snd_usb_parse_audio_format(struct snd_usb_audio *chip,
+			       struct audioformat *fp, int format,
+			       struct uac_format_type_i_continuous_descriptor *fmt,
+			       int stream, struct usb_host_interface *iface);
+
+#endif /*  __USBAUDIO_FORMAT_H */
diff --git a/linux/sound/usb/helper.c b/linux/sound/usb/helper.c
new file mode 100644
index 0000000..f280c19
--- /dev/null
+++ b/linux/sound/usb/helper.c
@@ -0,0 +1,118 @@
+/*
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include "usbaudio.h"
+#include "helper.h"
+
+/*
+ * combine bytes and get an integer value
+ */
+unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size)
+{
+	switch (size) {
+	case 1:  return *bytes;
+	case 2:  return combine_word(bytes);
+	case 3:  return combine_triple(bytes);
+	case 4:  return combine_quad(bytes);
+	default: return 0;
+	}
+}
+
+/*
+ * parse descriptor buffer and return the pointer starting the given
+ * descriptor type.
+ */
+void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype)
+{
+	u8 *p, *end, *next;
+
+	p = descstart;
+	end = p + desclen;
+	for (; p < end;) {
+		if (p[0] < 2)
+			return NULL;
+		next = p + p[0];
+		if (next > end)
+			return NULL;
+		if (p[1] == dtype && (!after || (void *)p > after)) {
+			return p;
+		}
+		p = next;
+	}
+	return NULL;
+}
+
+/*
+ * find a class-specified interface descriptor with the given subtype.
+ */
+void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype)
+{
+	unsigned char *p = after;
+
+	while ((p = snd_usb_find_desc(buffer, buflen, p,
+				      USB_DT_CS_INTERFACE)) != NULL) {
+		if (p[0] >= 3 && p[2] == dsubtype)
+			return p;
+	}
+	return NULL;
+}
+
+/*
+ * Wrapper for usb_control_msg().
+ * Allocates a temp buffer to prevent dmaing from/to the stack.
+ */
+int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
+		    __u8 requesttype, __u16 value, __u16 index, void *data,
+		    __u16 size, int timeout)
+{
+	int err;
+	void *buf = NULL;
+
+	if (size > 0) {
+		buf = kmemdup(data, size, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+	}
+	err = usb_control_msg(dev, pipe, request, requesttype,
+			      value, index, buf, size, timeout);
+	if (size > 0) {
+		memcpy(data, buf, size);
+		kfree(buf);
+	}
+	return err;
+}
+
+unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip,
+					 struct usb_host_interface *alts)
+{
+	switch (snd_usb_get_speed(chip->dev)) {
+	case USB_SPEED_HIGH:
+	case USB_SPEED_SUPER:
+		if (get_endpoint(alts, 0)->bInterval >= 1 &&
+		    get_endpoint(alts, 0)->bInterval <= 4)
+			return get_endpoint(alts, 0)->bInterval - 1;
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
diff --git a/linux/sound/usb/helper.h b/linux/sound/usb/helper.h
new file mode 100644
index 0000000..09bd943
--- /dev/null
+++ b/linux/sound/usb/helper.h
@@ -0,0 +1,36 @@
+#ifndef __USBAUDIO_HELPER_H
+#define __USBAUDIO_HELPER_H
+
+unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size);
+
+void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype);
+void *snd_usb_find_csint_desc(void *descstart, int desclen, void *after, u8 dsubtype);
+
+int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe,
+		    __u8 request, __u8 requesttype, __u16 value, __u16 index,
+		    void *data, __u16 size, int timeout);
+
+unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip,
+					 struct usb_host_interface *alts);
+
+/*
+ * retrieve usb_interface descriptor from the host interface
+ * (conditional for compatibility with the older API)
+ */
+#ifndef get_iface_desc
+#define get_iface_desc(iface)	(&(iface)->desc)
+#define get_endpoint(alt,ep)	(&(alt)->endpoint[ep].desc)
+#define get_ep_desc(ep)		(&(ep)->desc)
+#define get_cfg_desc(cfg)	(&(cfg)->desc)
+#endif
+
+#ifndef snd_usb_get_speed
+#define snd_usb_get_speed(dev) ((dev)->speed)
+#endif
+
+static inline int snd_usb_ctrl_intf(struct snd_usb_audio *chip)
+{
+	return get_iface_desc(chip->ctrl_intf)->bInterfaceNumber;
+}
+
+#endif /* __USBAUDIO_HELPER_H */
diff --git a/linux/sound/usb/midi.c b/linux/sound/usb/midi.c
new file mode 100644
index 0000000..25bce7e
--- /dev/null
+++ b/linux/sound/usb/midi.c
@@ -0,0 +1,2199 @@
+/*
+ * usbmidi.c - ALSA USB MIDI driver
+ *
+ * Copyright (c) 2002-2009 Clemens Ladisch
+ * All rights reserved.
+ *
+ * Based on the OSS usb-midi driver by NAGANO Daisuke,
+ *          NetBSD's umidi driver by Takuya SHIOZAKI,
+ *          the "USB Device Class Definition for MIDI Devices" by Roland
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed and/or modified under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/usb/audio.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/rawmidi.h>
+#include <sound/asequencer.h>
+#include "usbaudio.h"
+#include "midi.h"
+#include "helper.h"
+
+/*
+ * define this to log all USB packets
+ */
+/* #define DUMP_PACKETS */
+
+/*
+ * how long to wait after some USB errors, so that khubd can disconnect() us
+ * without too many spurious errors
+ */
+#define ERROR_DELAY_JIFFIES (HZ / 10)
+
+#define OUTPUT_URBS 7
+#define INPUT_URBS 7
+
+
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_DESCRIPTION("USB Audio/MIDI helper module");
+MODULE_LICENSE("Dual BSD/GPL");
+
+
+struct usb_ms_header_descriptor {
+	__u8  bLength;
+	__u8  bDescriptorType;
+	__u8  bDescriptorSubtype;
+	__u8  bcdMSC[2];
+	__le16 wTotalLength;
+} __attribute__ ((packed));
+
+struct usb_ms_endpoint_descriptor {
+	__u8  bLength;
+	__u8  bDescriptorType;
+	__u8  bDescriptorSubtype;
+	__u8  bNumEmbMIDIJack;
+	__u8  baAssocJackID[0];
+} __attribute__ ((packed));
+
+struct snd_usb_midi_in_endpoint;
+struct snd_usb_midi_out_endpoint;
+struct snd_usb_midi_endpoint;
+
+struct usb_protocol_ops {
+	void (*input)(struct snd_usb_midi_in_endpoint*, uint8_t*, int);
+	void (*output)(struct snd_usb_midi_out_endpoint *ep, struct urb *urb);
+	void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t);
+	void (*init_out_endpoint)(struct snd_usb_midi_out_endpoint*);
+	void (*finish_out_endpoint)(struct snd_usb_midi_out_endpoint*);
+};
+
+struct snd_usb_midi {
+	struct usb_device *dev;
+	struct snd_card *card;
+	struct usb_interface *iface;
+	const struct snd_usb_audio_quirk *quirk;
+	struct snd_rawmidi *rmidi;
+	struct usb_protocol_ops* usb_protocol_ops;
+	struct list_head list;
+	struct timer_list error_timer;
+	spinlock_t disc_lock;
+	struct mutex mutex;
+	u32 usb_id;
+	int next_midi_device;
+
+	struct snd_usb_midi_endpoint {
+		struct snd_usb_midi_out_endpoint *out;
+		struct snd_usb_midi_in_endpoint *in;
+	} endpoints[MIDI_MAX_ENDPOINTS];
+	unsigned long input_triggered;
+	unsigned int opened;
+	unsigned char disconnected;
+
+	struct snd_kcontrol *roland_load_ctl;
+};
+
+struct snd_usb_midi_out_endpoint {
+	struct snd_usb_midi* umidi;
+	struct out_urb_context {
+		struct urb *urb;
+		struct snd_usb_midi_out_endpoint *ep;
+	} urbs[OUTPUT_URBS];
+	unsigned int active_urbs;
+	unsigned int drain_urbs;
+	int max_transfer;		/* size of urb buffer */
+	struct tasklet_struct tasklet;
+	unsigned int next_urb;
+	spinlock_t buffer_lock;
+
+	struct usbmidi_out_port {
+		struct snd_usb_midi_out_endpoint* ep;
+		struct snd_rawmidi_substream *substream;
+		int active;
+		uint8_t cable;		/* cable number << 4 */
+		uint8_t state;
+#define STATE_UNKNOWN	0
+#define STATE_1PARAM	1
+#define STATE_2PARAM_1	2
+#define STATE_2PARAM_2	3
+#define STATE_SYSEX_0	4
+#define STATE_SYSEX_1	5
+#define STATE_SYSEX_2	6
+		uint8_t data[2];
+	} ports[0x10];
+	int current_port;
+
+	wait_queue_head_t drain_wait;
+};
+
+struct snd_usb_midi_in_endpoint {
+	struct snd_usb_midi* umidi;
+	struct urb* urbs[INPUT_URBS];
+	struct usbmidi_in_port {
+		struct snd_rawmidi_substream *substream;
+		u8 running_status_length;
+	} ports[0x10];
+	u8 seen_f5;
+	u8 error_resubmit;
+	int current_port;
+};
+
+static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint* ep);
+
+static const uint8_t snd_usbmidi_cin_length[] = {
+	0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1
+};
+
+/*
+ * Submits the URB, with error handling.
+ */
+static int snd_usbmidi_submit_urb(struct urb* urb, gfp_t flags)
+{
+	int err = usb_submit_urb(urb, flags);
+	if (err < 0 && err != -ENODEV)
+		snd_printk(KERN_ERR "usb_submit_urb: %d\n", err);
+	return err;
+}
+
+/*
+ * Error handling for URB completion functions.
+ */
+static int snd_usbmidi_urb_error(int status)
+{
+	switch (status) {
+	/* manually unlinked, or device gone */
+	case -ENOENT:
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+	case -ENODEV:
+		return -ENODEV;
+	/* errors that might occur during unplugging */
+	case -EPROTO:
+	case -ETIME:
+	case -EILSEQ:
+		return -EIO;
+	default:
+		snd_printk(KERN_ERR "urb status %d\n", status);
+		return 0; /* continue */
+	}
+}
+
+/*
+ * Receives a chunk of MIDI data.
+ */
+static void snd_usbmidi_input_data(struct snd_usb_midi_in_endpoint* ep, int portidx,
+				   uint8_t* data, int length)
+{
+	struct usbmidi_in_port* port = &ep->ports[portidx];
+
+	if (!port->substream) {
+		snd_printd("unexpected port %d!\n", portidx);
+		return;
+	}
+	if (!test_bit(port->substream->number, &ep->umidi->input_triggered))
+		return;
+	snd_rawmidi_receive(port->substream, data, length);
+}
+
+#ifdef DUMP_PACKETS
+static void dump_urb(const char *type, const u8 *data, int length)
+{
+	snd_printk(KERN_DEBUG "%s packet: [", type);
+	for (; length > 0; ++data, --length)
+		printk(" %02x", *data);
+	printk(" ]\n");
+}
+#else
+#define dump_urb(type, data, length) /* nothing */
+#endif
+
+/*
+ * Processes the data read from the device.
+ */
+static void snd_usbmidi_in_urb_complete(struct urb* urb)
+{
+	struct snd_usb_midi_in_endpoint* ep = urb->context;
+
+	if (urb->status == 0) {
+		dump_urb("received", urb->transfer_buffer, urb->actual_length);
+		ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer,
+						   urb->actual_length);
+	} else {
+		int err = snd_usbmidi_urb_error(urb->status);
+		if (err < 0) {
+			if (err != -ENODEV) {
+				ep->error_resubmit = 1;
+				mod_timer(&ep->umidi->error_timer,
+					  jiffies + ERROR_DELAY_JIFFIES);
+			}
+			return;
+		}
+	}
+
+	urb->dev = ep->umidi->dev;
+	snd_usbmidi_submit_urb(urb, GFP_ATOMIC);
+}
+
+static void snd_usbmidi_out_urb_complete(struct urb* urb)
+{
+	struct out_urb_context *context = urb->context;
+	struct snd_usb_midi_out_endpoint* ep = context->ep;
+	unsigned int urb_index;
+
+	spin_lock(&ep->buffer_lock);
+	urb_index = context - ep->urbs;
+	ep->active_urbs &= ~(1 << urb_index);
+	if (unlikely(ep->drain_urbs)) {
+		ep->drain_urbs &= ~(1 << urb_index);
+		wake_up(&ep->drain_wait);
+	}
+	spin_unlock(&ep->buffer_lock);
+	if (urb->status < 0) {
+		int err = snd_usbmidi_urb_error(urb->status);
+		if (err < 0) {
+			if (err != -ENODEV)
+				mod_timer(&ep->umidi->error_timer,
+					  jiffies + ERROR_DELAY_JIFFIES);
+			return;
+		}
+	}
+	snd_usbmidi_do_output(ep);
+}
+
+/*
+ * This is called when some data should be transferred to the device
+ * (from one or more substreams).
+ */
+static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint* ep)
+{
+	unsigned int urb_index;
+	struct urb* urb;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ep->buffer_lock, flags);
+	if (ep->umidi->disconnected) {
+		spin_unlock_irqrestore(&ep->buffer_lock, flags);
+		return;
+	}
+
+	urb_index = ep->next_urb;
+	for (;;) {
+		if (!(ep->active_urbs & (1 << urb_index))) {
+			urb = ep->urbs[urb_index].urb;
+			urb->transfer_buffer_length = 0;
+			ep->umidi->usb_protocol_ops->output(ep, urb);
+			if (urb->transfer_buffer_length == 0)
+				break;
+
+			dump_urb("sending", urb->transfer_buffer,
+				 urb->transfer_buffer_length);
+			urb->dev = ep->umidi->dev;
+			if (snd_usbmidi_submit_urb(urb, GFP_ATOMIC) < 0)
+				break;
+			ep->active_urbs |= 1 << urb_index;
+		}
+		if (++urb_index >= OUTPUT_URBS)
+			urb_index = 0;
+		if (urb_index == ep->next_urb)
+			break;
+	}
+	ep->next_urb = urb_index;
+	spin_unlock_irqrestore(&ep->buffer_lock, flags);
+}
+
+static void snd_usbmidi_out_tasklet(unsigned long data)
+{
+	struct snd_usb_midi_out_endpoint* ep = (struct snd_usb_midi_out_endpoint *) data;
+
+	snd_usbmidi_do_output(ep);
+}
+
+/* called after transfers had been interrupted due to some USB error */
+static void snd_usbmidi_error_timer(unsigned long data)
+{
+	struct snd_usb_midi *umidi = (struct snd_usb_midi *)data;
+	unsigned int i, j;
+
+	spin_lock(&umidi->disc_lock);
+	if (umidi->disconnected) {
+		spin_unlock(&umidi->disc_lock);
+		return;
+	}
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		struct snd_usb_midi_in_endpoint *in = umidi->endpoints[i].in;
+		if (in && in->error_resubmit) {
+			in->error_resubmit = 0;
+			for (j = 0; j < INPUT_URBS; ++j) {
+				in->urbs[j]->dev = umidi->dev;
+				snd_usbmidi_submit_urb(in->urbs[j], GFP_ATOMIC);
+			}
+		}
+		if (umidi->endpoints[i].out)
+			snd_usbmidi_do_output(umidi->endpoints[i].out);
+	}
+	spin_unlock(&umidi->disc_lock);
+}
+
+/* helper function to send static data that may not DMA-able */
+static int send_bulk_static_data(struct snd_usb_midi_out_endpoint* ep,
+				 const void *data, int len)
+{
+	int err = 0;
+	void *buf = kmemdup(data, len, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	dump_urb("sending", buf, len);
+	if (ep->urbs[0].urb)
+		err = usb_bulk_msg(ep->umidi->dev, ep->urbs[0].urb->pipe,
+				   buf, len, NULL, 250);
+	kfree(buf);
+	return err;
+}
+
+/*
+ * Standard USB MIDI protocol: see the spec.
+ * Midiman protocol: like the standard protocol, but the control byte is the
+ * fourth byte in each packet, and uses length instead of CIN.
+ */
+
+static void snd_usbmidi_standard_input(struct snd_usb_midi_in_endpoint* ep,
+				       uint8_t* buffer, int buffer_length)
+{
+	int i;
+
+	for (i = 0; i + 3 < buffer_length; i += 4)
+		if (buffer[i] != 0) {
+			int cable = buffer[i] >> 4;
+			int length = snd_usbmidi_cin_length[buffer[i] & 0x0f];
+			snd_usbmidi_input_data(ep, cable, &buffer[i + 1], length);
+		}
+}
+
+static void snd_usbmidi_midiman_input(struct snd_usb_midi_in_endpoint* ep,
+				      uint8_t* buffer, int buffer_length)
+{
+	int i;
+
+	for (i = 0; i + 3 < buffer_length; i += 4)
+		if (buffer[i + 3] != 0) {
+			int port = buffer[i + 3] >> 4;
+			int length = buffer[i + 3] & 3;
+			snd_usbmidi_input_data(ep, port, &buffer[i], length);
+		}
+}
+
+/*
+ * Buggy M-Audio device: running status on input results in a packet that has
+ * the data bytes but not the status byte and that is marked with CIN 4.
+ */
+static void snd_usbmidi_maudio_broken_running_status_input(
+					struct snd_usb_midi_in_endpoint* ep,
+					uint8_t* buffer, int buffer_length)
+{
+	int i;
+
+	for (i = 0; i + 3 < buffer_length; i += 4)
+		if (buffer[i] != 0) {
+			int cable = buffer[i] >> 4;
+			u8 cin = buffer[i] & 0x0f;
+			struct usbmidi_in_port *port = &ep->ports[cable];
+			int length;
+
+			length = snd_usbmidi_cin_length[cin];
+			if (cin == 0xf && buffer[i + 1] >= 0xf8)
+				; /* realtime msg: no running status change */
+			else if (cin >= 0x8 && cin <= 0xe)
+				/* channel msg */
+				port->running_status_length = length - 1;
+			else if (cin == 0x4 &&
+				 port->running_status_length != 0 &&
+				 buffer[i + 1] < 0x80)
+				/* CIN 4 that is not a SysEx */
+				length = port->running_status_length;
+			else
+				/*
+				 * All other msgs cannot begin running status.
+				 * (A channel msg sent as two or three CIN 0xF
+				 * packets could in theory, but this device
+				 * doesn't use this format.)
+				 */
+				port->running_status_length = 0;
+			snd_usbmidi_input_data(ep, cable, &buffer[i + 1], length);
+		}
+}
+
+/*
+ * CME protocol: like the standard protocol, but SysEx commands are sent as a
+ * single USB packet preceded by a 0x0F byte.
+ */
+static void snd_usbmidi_cme_input(struct snd_usb_midi_in_endpoint *ep,
+				  uint8_t *buffer, int buffer_length)
+{
+	if (buffer_length < 2 || (buffer[0] & 0x0f) != 0x0f)
+		snd_usbmidi_standard_input(ep, buffer, buffer_length);
+	else
+		snd_usbmidi_input_data(ep, buffer[0] >> 4,
+				       &buffer[1], buffer_length - 1);
+}
+
+/*
+ * Adds one USB MIDI packet to the output buffer.
+ */
+static void snd_usbmidi_output_standard_packet(struct urb* urb, uint8_t p0,
+					       uint8_t p1, uint8_t p2, uint8_t p3)
+{
+
+	uint8_t* buf = (uint8_t*)urb->transfer_buffer + urb->transfer_buffer_length;
+	buf[0] = p0;
+	buf[1] = p1;
+	buf[2] = p2;
+	buf[3] = p3;
+	urb->transfer_buffer_length += 4;
+}
+
+/*
+ * Adds one Midiman packet to the output buffer.
+ */
+static void snd_usbmidi_output_midiman_packet(struct urb* urb, uint8_t p0,
+					      uint8_t p1, uint8_t p2, uint8_t p3)
+{
+
+	uint8_t* buf = (uint8_t*)urb->transfer_buffer + urb->transfer_buffer_length;
+	buf[0] = p1;
+	buf[1] = p2;
+	buf[2] = p3;
+	buf[3] = (p0 & 0xf0) | snd_usbmidi_cin_length[p0 & 0x0f];
+	urb->transfer_buffer_length += 4;
+}
+
+/*
+ * Converts MIDI commands to USB MIDI packets.
+ */
+static void snd_usbmidi_transmit_byte(struct usbmidi_out_port* port,
+				      uint8_t b, struct urb* urb)
+{
+	uint8_t p0 = port->cable;
+	void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t) =
+		port->ep->umidi->usb_protocol_ops->output_packet;
+
+	if (b >= 0xf8) {
+		output_packet(urb, p0 | 0x0f, b, 0, 0);
+	} else if (b >= 0xf0) {
+		switch (b) {
+		case 0xf0:
+			port->data[0] = b;
+			port->state = STATE_SYSEX_1;
+			break;
+		case 0xf1:
+		case 0xf3:
+			port->data[0] = b;
+			port->state = STATE_1PARAM;
+			break;
+		case 0xf2:
+			port->data[0] = b;
+			port->state = STATE_2PARAM_1;
+			break;
+		case 0xf4:
+		case 0xf5:
+			port->state = STATE_UNKNOWN;
+			break;
+		case 0xf6:
+			output_packet(urb, p0 | 0x05, 0xf6, 0, 0);
+			port->state = STATE_UNKNOWN;
+			break;
+		case 0xf7:
+			switch (port->state) {
+			case STATE_SYSEX_0:
+				output_packet(urb, p0 | 0x05, 0xf7, 0, 0);
+				break;
+			case STATE_SYSEX_1:
+				output_packet(urb, p0 | 0x06, port->data[0], 0xf7, 0);
+				break;
+			case STATE_SYSEX_2:
+				output_packet(urb, p0 | 0x07, port->data[0], port->data[1], 0xf7);
+				break;
+			}
+			port->state = STATE_UNKNOWN;
+			break;
+		}
+	} else if (b >= 0x80) {
+		port->data[0] = b;
+		if (b >= 0xc0 && b <= 0xdf)
+			port->state = STATE_1PARAM;
+		else
+			port->state = STATE_2PARAM_1;
+	} else { /* b < 0x80 */
+		switch (port->state) {
+		case STATE_1PARAM:
+			if (port->data[0] < 0xf0) {
+				p0 |= port->data[0] >> 4;
+			} else {
+				p0 |= 0x02;
+				port->state = STATE_UNKNOWN;
+			}
+			output_packet(urb, p0, port->data[0], b, 0);
+			break;
+		case STATE_2PARAM_1:
+			port->data[1] = b;
+			port->state = STATE_2PARAM_2;
+			break;
+		case STATE_2PARAM_2:
+			if (port->data[0] < 0xf0) {
+				p0 |= port->data[0] >> 4;
+				port->state = STATE_2PARAM_1;
+			} else {
+				p0 |= 0x03;
+				port->state = STATE_UNKNOWN;
+			}
+			output_packet(urb, p0, port->data[0], port->data[1], b);
+			break;
+		case STATE_SYSEX_0:
+			port->data[0] = b;
+			port->state = STATE_SYSEX_1;
+			break;
+		case STATE_SYSEX_1:
+			port->data[1] = b;
+			port->state = STATE_SYSEX_2;
+			break;
+		case STATE_SYSEX_2:
+			output_packet(urb, p0 | 0x04, port->data[0], port->data[1], b);
+			port->state = STATE_SYSEX_0;
+			break;
+		}
+	}
+}
+
+static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint* ep,
+					struct urb *urb)
+{
+	int p;
+
+	/* FIXME: lower-numbered ports can starve higher-numbered ports */
+	for (p = 0; p < 0x10; ++p) {
+		struct usbmidi_out_port* port = &ep->ports[p];
+		if (!port->active)
+			continue;
+		while (urb->transfer_buffer_length + 3 < ep->max_transfer) {
+			uint8_t b;
+			if (snd_rawmidi_transmit(port->substream, &b, 1) != 1) {
+				port->active = 0;
+				break;
+			}
+			snd_usbmidi_transmit_byte(port, b, urb);
+		}
+	}
+}
+
+static struct usb_protocol_ops snd_usbmidi_standard_ops = {
+	.input = snd_usbmidi_standard_input,
+	.output = snd_usbmidi_standard_output,
+	.output_packet = snd_usbmidi_output_standard_packet,
+};
+
+static struct usb_protocol_ops snd_usbmidi_midiman_ops = {
+	.input = snd_usbmidi_midiman_input,
+	.output = snd_usbmidi_standard_output,
+	.output_packet = snd_usbmidi_output_midiman_packet,
+};
+
+static struct usb_protocol_ops snd_usbmidi_maudio_broken_running_status_ops = {
+	.input = snd_usbmidi_maudio_broken_running_status_input,
+	.output = snd_usbmidi_standard_output,
+	.output_packet = snd_usbmidi_output_standard_packet,
+};
+
+static struct usb_protocol_ops snd_usbmidi_cme_ops = {
+	.input = snd_usbmidi_cme_input,
+	.output = snd_usbmidi_standard_output,
+	.output_packet = snd_usbmidi_output_standard_packet,
+};
+
+/*
+ * AKAI MPD16 protocol:
+ *
+ * For control port (endpoint 1):
+ * ==============================
+ * One or more chunks consisting of first byte of (0x10 | msg_len) and then a
+ * SysEx message (msg_len=9 bytes long).
+ *
+ * For data port (endpoint 2):
+ * ===========================
+ * One or more chunks consisting of first byte of (0x20 | msg_len) and then a
+ * MIDI message (msg_len bytes long)
+ *
+ * Messages sent: Active Sense, Note On, Poly Pressure, Control Change.
+ */
+static void snd_usbmidi_akai_input(struct snd_usb_midi_in_endpoint *ep,
+				   uint8_t *buffer, int buffer_length)
+{
+	unsigned int pos = 0;
+	unsigned int len = (unsigned int)buffer_length;
+	while (pos < len) {
+		unsigned int port = (buffer[pos] >> 4) - 1;
+		unsigned int msg_len = buffer[pos] & 0x0f;
+		pos++;
+		if (pos + msg_len <= len && port < 2)
+			snd_usbmidi_input_data(ep, 0, &buffer[pos], msg_len);
+		pos += msg_len;
+	}
+}
+
+#define MAX_AKAI_SYSEX_LEN 9
+
+static void snd_usbmidi_akai_output(struct snd_usb_midi_out_endpoint *ep,
+				    struct urb *urb)
+{
+	uint8_t *msg;
+	int pos, end, count, buf_end;
+	uint8_t tmp[MAX_AKAI_SYSEX_LEN];
+	struct snd_rawmidi_substream *substream = ep->ports[0].substream;
+
+	if (!ep->ports[0].active)
+		return;
+
+	msg = urb->transfer_buffer + urb->transfer_buffer_length;
+	buf_end = ep->max_transfer - MAX_AKAI_SYSEX_LEN - 1;
+
+	/* only try adding more data when there's space for at least 1 SysEx */
+	while (urb->transfer_buffer_length < buf_end) {
+		count = snd_rawmidi_transmit_peek(substream,
+						  tmp, MAX_AKAI_SYSEX_LEN);
+		if (!count) {
+			ep->ports[0].active = 0;
+			return;
+		}
+		/* try to skip non-SysEx data */
+		for (pos = 0; pos < count && tmp[pos] != 0xF0; pos++)
+			;
+
+		if (pos > 0) {
+			snd_rawmidi_transmit_ack(substream, pos);
+			continue;
+		}
+
+		/* look for the start or end marker */
+		for (end = 1; end < count && tmp[end] < 0xF0; end++)
+			;
+
+		/* next SysEx started before the end of current one */
+		if (end < count && tmp[end] == 0xF0) {
+			/* it's incomplete - drop it */
+			snd_rawmidi_transmit_ack(substream, end);
+			continue;
+		}
+		/* SysEx complete */
+		if (end < count && tmp[end] == 0xF7) {
+			/* queue it, ack it, and get the next one */
+			count = end + 1;
+			msg[0] = 0x10 | count;
+			memcpy(&msg[1], tmp, count);
+			snd_rawmidi_transmit_ack(substream, count);
+			urb->transfer_buffer_length += count + 1;
+			msg += count + 1;
+			continue;
+		}
+		/* less than 9 bytes and no end byte - wait for more */
+		if (count < MAX_AKAI_SYSEX_LEN) {
+			ep->ports[0].active = 0;
+			return;
+		}
+		/* 9 bytes and no end marker in sight - malformed, skip it */
+		snd_rawmidi_transmit_ack(substream, count);
+	}
+}
+
+static struct usb_protocol_ops snd_usbmidi_akai_ops = {
+	.input = snd_usbmidi_akai_input,
+	.output = snd_usbmidi_akai_output,
+};
+
+/*
+ * Novation USB MIDI protocol: number of data bytes is in the first byte
+ * (when receiving) (+1!) or in the second byte (when sending); data begins
+ * at the third byte.
+ */
+
+static void snd_usbmidi_novation_input(struct snd_usb_midi_in_endpoint* ep,
+				       uint8_t* buffer, int buffer_length)
+{
+	if (buffer_length < 2 || !buffer[0] || buffer_length < buffer[0] + 1)
+		return;
+	snd_usbmidi_input_data(ep, 0, &buffer[2], buffer[0] - 1);
+}
+
+static void snd_usbmidi_novation_output(struct snd_usb_midi_out_endpoint* ep,
+					struct urb *urb)
+{
+	uint8_t* transfer_buffer;
+	int count;
+
+	if (!ep->ports[0].active)
+		return;
+	transfer_buffer = urb->transfer_buffer;
+	count = snd_rawmidi_transmit(ep->ports[0].substream,
+				     &transfer_buffer[2],
+				     ep->max_transfer - 2);
+	if (count < 1) {
+		ep->ports[0].active = 0;
+		return;
+	}
+	transfer_buffer[0] = 0;
+	transfer_buffer[1] = count;
+	urb->transfer_buffer_length = 2 + count;
+}
+
+static struct usb_protocol_ops snd_usbmidi_novation_ops = {
+	.input = snd_usbmidi_novation_input,
+	.output = snd_usbmidi_novation_output,
+};
+
+/*
+ * "raw" protocol: just move raw MIDI bytes from/to the endpoint
+ */
+
+static void snd_usbmidi_raw_input(struct snd_usb_midi_in_endpoint* ep,
+				  uint8_t* buffer, int buffer_length)
+{
+	snd_usbmidi_input_data(ep, 0, buffer, buffer_length);
+}
+
+static void snd_usbmidi_raw_output(struct snd_usb_midi_out_endpoint* ep,
+				   struct urb *urb)
+{
+	int count;
+
+	if (!ep->ports[0].active)
+		return;
+	count = snd_rawmidi_transmit(ep->ports[0].substream,
+				     urb->transfer_buffer,
+				     ep->max_transfer);
+	if (count < 1) {
+		ep->ports[0].active = 0;
+		return;
+	}
+	urb->transfer_buffer_length = count;
+}
+
+static struct usb_protocol_ops snd_usbmidi_raw_ops = {
+	.input = snd_usbmidi_raw_input,
+	.output = snd_usbmidi_raw_output,
+};
+
+static void snd_usbmidi_us122l_input(struct snd_usb_midi_in_endpoint *ep,
+				     uint8_t *buffer, int buffer_length)
+{
+	if (buffer_length != 9)
+		return;
+	buffer_length = 8;
+	while (buffer_length && buffer[buffer_length - 1] == 0xFD)
+		buffer_length--;
+	if (buffer_length)
+		snd_usbmidi_input_data(ep, 0, buffer, buffer_length);
+}
+
+static void snd_usbmidi_us122l_output(struct snd_usb_midi_out_endpoint *ep,
+				      struct urb *urb)
+{
+	int count;
+
+	if (!ep->ports[0].active)
+		return;
+	switch (snd_usb_get_speed(ep->umidi->dev)) {
+	case USB_SPEED_HIGH:
+	case USB_SPEED_SUPER:
+		count = 1;
+		break;
+	default:
+		count = 2;
+	}
+	count = snd_rawmidi_transmit(ep->ports[0].substream,
+				     urb->transfer_buffer,
+				     count);
+	if (count < 1) {
+		ep->ports[0].active = 0;
+		return;
+	}
+
+	memset(urb->transfer_buffer + count, 0xFD, 9 - count);
+	urb->transfer_buffer_length = count;
+}
+
+static struct usb_protocol_ops snd_usbmidi_122l_ops = {
+	.input = snd_usbmidi_us122l_input,
+	.output = snd_usbmidi_us122l_output,
+};
+
+/*
+ * Emagic USB MIDI protocol: raw MIDI with "F5 xx" port switching.
+ */
+
+static void snd_usbmidi_emagic_init_out(struct snd_usb_midi_out_endpoint* ep)
+{
+	static const u8 init_data[] = {
+		/* initialization magic: "get version" */
+		0xf0,
+		0x00, 0x20, 0x31,	/* Emagic */
+		0x64,			/* Unitor8 */
+		0x0b,			/* version number request */
+		0x00,			/* command version */
+		0x00,			/* EEPROM, box 0 */
+		0xf7
+	};
+	send_bulk_static_data(ep, init_data, sizeof(init_data));
+	/* while we're at it, pour on more magic */
+	send_bulk_static_data(ep, init_data, sizeof(init_data));
+}
+
+static void snd_usbmidi_emagic_finish_out(struct snd_usb_midi_out_endpoint* ep)
+{
+	static const u8 finish_data[] = {
+		/* switch to patch mode with last preset */
+		0xf0,
+		0x00, 0x20, 0x31,	/* Emagic */
+		0x64,			/* Unitor8 */
+		0x10,			/* patch switch command */
+		0x00,			/* command version */
+		0x7f,			/* to all boxes */
+		0x40,			/* last preset in EEPROM */
+		0xf7
+	};
+	send_bulk_static_data(ep, finish_data, sizeof(finish_data));
+}
+
+static void snd_usbmidi_emagic_input(struct snd_usb_midi_in_endpoint* ep,
+				     uint8_t* buffer, int buffer_length)
+{
+	int i;
+
+	/* FF indicates end of valid data */
+	for (i = 0; i < buffer_length; ++i)
+		if (buffer[i] == 0xff) {
+			buffer_length = i;
+			break;
+		}
+
+	/* handle F5 at end of last buffer */
+	if (ep->seen_f5)
+		goto switch_port;
+
+	while (buffer_length > 0) {
+		/* determine size of data until next F5 */
+		for (i = 0; i < buffer_length; ++i)
+			if (buffer[i] == 0xf5)
+				break;
+		snd_usbmidi_input_data(ep, ep->current_port, buffer, i);
+		buffer += i;
+		buffer_length -= i;
+
+		if (buffer_length <= 0)
+			break;
+		/* assert(buffer[0] == 0xf5); */
+		ep->seen_f5 = 1;
+		++buffer;
+		--buffer_length;
+
+	switch_port:
+		if (buffer_length <= 0)
+			break;
+		if (buffer[0] < 0x80) {
+			ep->current_port = (buffer[0] - 1) & 15;
+			++buffer;
+			--buffer_length;
+		}
+		ep->seen_f5 = 0;
+	}
+}
+
+static void snd_usbmidi_emagic_output(struct snd_usb_midi_out_endpoint* ep,
+				      struct urb *urb)
+{
+	int port0 = ep->current_port;
+	uint8_t* buf = urb->transfer_buffer;
+	int buf_free = ep->max_transfer;
+	int length, i;
+
+	for (i = 0; i < 0x10; ++i) {
+		/* round-robin, starting at the last current port */
+		int portnum = (port0 + i) & 15;
+		struct usbmidi_out_port* port = &ep->ports[portnum];
+
+		if (!port->active)
+			continue;
+		if (snd_rawmidi_transmit_peek(port->substream, buf, 1) != 1) {
+			port->active = 0;
+			continue;
+		}
+
+		if (portnum != ep->current_port) {
+			if (buf_free < 2)
+				break;
+			ep->current_port = portnum;
+			buf[0] = 0xf5;
+			buf[1] = (portnum + 1) & 15;
+			buf += 2;
+			buf_free -= 2;
+		}
+
+		if (buf_free < 1)
+			break;
+		length = snd_rawmidi_transmit(port->substream, buf, buf_free);
+		if (length > 0) {
+			buf += length;
+			buf_free -= length;
+			if (buf_free < 1)
+				break;
+		}
+	}
+	if (buf_free < ep->max_transfer && buf_free > 0) {
+		*buf = 0xff;
+		--buf_free;
+	}
+	urb->transfer_buffer_length = ep->max_transfer - buf_free;
+}
+
+static struct usb_protocol_ops snd_usbmidi_emagic_ops = {
+	.input = snd_usbmidi_emagic_input,
+	.output = snd_usbmidi_emagic_output,
+	.init_out_endpoint = snd_usbmidi_emagic_init_out,
+	.finish_out_endpoint = snd_usbmidi_emagic_finish_out,
+};
+
+
+static void update_roland_altsetting(struct snd_usb_midi* umidi)
+{
+	struct usb_interface *intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor *intfd;
+	int is_light_load;
+
+	intf = umidi->iface;
+	is_light_load = intf->cur_altsetting != intf->altsetting;
+	if (umidi->roland_load_ctl->private_value == is_light_load)
+		return;
+	hostif = &intf->altsetting[umidi->roland_load_ctl->private_value];
+	intfd = get_iface_desc(hostif);
+	snd_usbmidi_input_stop(&umidi->list);
+	usb_set_interface(umidi->dev, intfd->bInterfaceNumber,
+			  intfd->bAlternateSetting);
+	snd_usbmidi_input_start(&umidi->list);
+}
+
+static void substream_open(struct snd_rawmidi_substream *substream, int open)
+{
+	struct snd_usb_midi* umidi = substream->rmidi->private_data;
+	struct snd_kcontrol *ctl;
+
+	mutex_lock(&umidi->mutex);
+	if (open) {
+		if (umidi->opened++ == 0 && umidi->roland_load_ctl) {
+			ctl = umidi->roland_load_ctl;
+			ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+			snd_ctl_notify(umidi->card,
+				       SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+			update_roland_altsetting(umidi);
+		}
+	} else {
+		if (--umidi->opened == 0 && umidi->roland_load_ctl) {
+			ctl = umidi->roland_load_ctl;
+			ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+			snd_ctl_notify(umidi->card,
+				       SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+		}
+	}
+	mutex_unlock(&umidi->mutex);
+}
+
+static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_usb_midi* umidi = substream->rmidi->private_data;
+	struct usbmidi_out_port* port = NULL;
+	int i, j;
+
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i)
+		if (umidi->endpoints[i].out)
+			for (j = 0; j < 0x10; ++j)
+				if (umidi->endpoints[i].out->ports[j].substream == substream) {
+					port = &umidi->endpoints[i].out->ports[j];
+					break;
+				}
+	if (!port) {
+		snd_BUG();
+		return -ENXIO;
+	}
+	substream->runtime->private_data = port;
+	port->state = STATE_UNKNOWN;
+	substream_open(substream, 1);
+	return 0;
+}
+
+static int snd_usbmidi_output_close(struct snd_rawmidi_substream *substream)
+{
+	substream_open(substream, 0);
+	return 0;
+}
+
+static void snd_usbmidi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct usbmidi_out_port* port = (struct usbmidi_out_port*)substream->runtime->private_data;
+
+	port->active = up;
+	if (up) {
+		if (port->ep->umidi->disconnected) {
+			/* gobble up remaining bytes to prevent wait in
+			 * snd_rawmidi_drain_output */
+			while (!snd_rawmidi_transmit_empty(substream))
+				snd_rawmidi_transmit_ack(substream, 1);
+			return;
+		}
+		tasklet_schedule(&port->ep->tasklet);
+	}
+}
+
+static void snd_usbmidi_output_drain(struct snd_rawmidi_substream *substream)
+{
+	struct usbmidi_out_port* port = substream->runtime->private_data;
+	struct snd_usb_midi_out_endpoint *ep = port->ep;
+	unsigned int drain_urbs;
+	DEFINE_WAIT(wait);
+	long timeout = msecs_to_jiffies(50);
+
+	if (ep->umidi->disconnected)
+		return;
+	/*
+	 * The substream buffer is empty, but some data might still be in the
+	 * currently active URBs, so we have to wait for those to complete.
+	 */
+	spin_lock_irq(&ep->buffer_lock);
+	drain_urbs = ep->active_urbs;
+	if (drain_urbs) {
+		ep->drain_urbs |= drain_urbs;
+		do {
+			prepare_to_wait(&ep->drain_wait, &wait,
+					TASK_UNINTERRUPTIBLE);
+			spin_unlock_irq(&ep->buffer_lock);
+			timeout = schedule_timeout(timeout);
+			spin_lock_irq(&ep->buffer_lock);
+			drain_urbs &= ep->drain_urbs;
+		} while (drain_urbs && timeout);
+		finish_wait(&ep->drain_wait, &wait);
+	}
+	spin_unlock_irq(&ep->buffer_lock);
+}
+
+static int snd_usbmidi_input_open(struct snd_rawmidi_substream *substream)
+{
+	substream_open(substream, 1);
+	return 0;
+}
+
+static int snd_usbmidi_input_close(struct snd_rawmidi_substream *substream)
+{
+	substream_open(substream, 0);
+	return 0;
+}
+
+static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct snd_usb_midi* umidi = substream->rmidi->private_data;
+
+	if (up)
+		set_bit(substream->number, &umidi->input_triggered);
+	else
+		clear_bit(substream->number, &umidi->input_triggered);
+}
+
+static struct snd_rawmidi_ops snd_usbmidi_output_ops = {
+	.open = snd_usbmidi_output_open,
+	.close = snd_usbmidi_output_close,
+	.trigger = snd_usbmidi_output_trigger,
+	.drain = snd_usbmidi_output_drain,
+};
+
+static struct snd_rawmidi_ops snd_usbmidi_input_ops = {
+	.open = snd_usbmidi_input_open,
+	.close = snd_usbmidi_input_close,
+	.trigger = snd_usbmidi_input_trigger
+};
+
+static void free_urb_and_buffer(struct snd_usb_midi *umidi, struct urb *urb,
+				unsigned int buffer_length)
+{
+	usb_free_coherent(umidi->dev, buffer_length,
+			  urb->transfer_buffer, urb->transfer_dma);
+	usb_free_urb(urb);
+}
+
+/*
+ * Frees an input endpoint.
+ * May be called when ep hasn't been initialized completely.
+ */
+static void snd_usbmidi_in_endpoint_delete(struct snd_usb_midi_in_endpoint* ep)
+{
+	unsigned int i;
+
+	for (i = 0; i < INPUT_URBS; ++i)
+		if (ep->urbs[i])
+			free_urb_and_buffer(ep->umidi, ep->urbs[i],
+					    ep->urbs[i]->transfer_buffer_length);
+	kfree(ep);
+}
+
+/*
+ * Creates an input endpoint.
+ */
+static int snd_usbmidi_in_endpoint_create(struct snd_usb_midi* umidi,
+					  struct snd_usb_midi_endpoint_info* ep_info,
+					  struct snd_usb_midi_endpoint* rep)
+{
+	struct snd_usb_midi_in_endpoint* ep;
+	void* buffer;
+	unsigned int pipe;
+	int length;
+	unsigned int i;
+
+	rep->in = NULL;
+	ep = kzalloc(sizeof(*ep), GFP_KERNEL);
+	if (!ep)
+		return -ENOMEM;
+	ep->umidi = umidi;
+
+	for (i = 0; i < INPUT_URBS; ++i) {
+		ep->urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+		if (!ep->urbs[i]) {
+			snd_usbmidi_in_endpoint_delete(ep);
+			return -ENOMEM;
+		}
+	}
+	if (ep_info->in_interval)
+		pipe = usb_rcvintpipe(umidi->dev, ep_info->in_ep);
+	else
+		pipe = usb_rcvbulkpipe(umidi->dev, ep_info->in_ep);
+	length = usb_maxpacket(umidi->dev, pipe, 0);
+	for (i = 0; i < INPUT_URBS; ++i) {
+		buffer = usb_alloc_coherent(umidi->dev, length, GFP_KERNEL,
+					    &ep->urbs[i]->transfer_dma);
+		if (!buffer) {
+			snd_usbmidi_in_endpoint_delete(ep);
+			return -ENOMEM;
+		}
+		if (ep_info->in_interval)
+			usb_fill_int_urb(ep->urbs[i], umidi->dev,
+					 pipe, buffer, length,
+					 snd_usbmidi_in_urb_complete,
+					 ep, ep_info->in_interval);
+		else
+			usb_fill_bulk_urb(ep->urbs[i], umidi->dev,
+					  pipe, buffer, length,
+					  snd_usbmidi_in_urb_complete, ep);
+		ep->urbs[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+	}
+
+	rep->in = ep;
+	return 0;
+}
+
+/*
+ * Frees an output endpoint.
+ * May be called when ep hasn't been initialized completely.
+ */
+static void snd_usbmidi_out_endpoint_clear(struct snd_usb_midi_out_endpoint *ep)
+{
+	unsigned int i;
+
+	for (i = 0; i < OUTPUT_URBS; ++i)
+		if (ep->urbs[i].urb) {
+			free_urb_and_buffer(ep->umidi, ep->urbs[i].urb,
+					    ep->max_transfer);
+			ep->urbs[i].urb = NULL;
+		}
+}
+
+static void snd_usbmidi_out_endpoint_delete(struct snd_usb_midi_out_endpoint *ep)
+{
+	snd_usbmidi_out_endpoint_clear(ep);
+	kfree(ep);
+}
+
+/*
+ * Creates an output endpoint, and initializes output ports.
+ */
+static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi* umidi,
+					   struct snd_usb_midi_endpoint_info* ep_info,
+					   struct snd_usb_midi_endpoint* rep)
+{
+	struct snd_usb_midi_out_endpoint* ep;
+	unsigned int i;
+	unsigned int pipe;
+	void* buffer;
+
+	rep->out = NULL;
+	ep = kzalloc(sizeof(*ep), GFP_KERNEL);
+	if (!ep)
+		return -ENOMEM;
+	ep->umidi = umidi;
+
+	for (i = 0; i < OUTPUT_URBS; ++i) {
+		ep->urbs[i].urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!ep->urbs[i].urb) {
+			snd_usbmidi_out_endpoint_delete(ep);
+			return -ENOMEM;
+		}
+		ep->urbs[i].ep = ep;
+	}
+	if (ep_info->out_interval)
+		pipe = usb_sndintpipe(umidi->dev, ep_info->out_ep);
+	else
+		pipe = usb_sndbulkpipe(umidi->dev, ep_info->out_ep);
+	switch (umidi->usb_id) {
+	default:
+		ep->max_transfer = usb_maxpacket(umidi->dev, pipe, 1);
+		break;
+		/*
+		 * Various chips declare a packet size larger than 4 bytes, but
+		 * do not actually work with larger packets:
+		 */
+	case USB_ID(0x0a92, 0x1020): /* ESI M4U */
+	case USB_ID(0x1430, 0x474b): /* RedOctane GH MIDI INTERFACE */
+	case USB_ID(0x15ca, 0x0101): /* Textech USB Midi Cable */
+	case USB_ID(0x15ca, 0x1806): /* Textech USB Midi Cable */
+	case USB_ID(0x1a86, 0x752d): /* QinHeng CH345 "USB2.0-MIDI" */
+		ep->max_transfer = 4;
+		break;
+	}
+	for (i = 0; i < OUTPUT_URBS; ++i) {
+		buffer = usb_alloc_coherent(umidi->dev,
+					    ep->max_transfer, GFP_KERNEL,
+					    &ep->urbs[i].urb->transfer_dma);
+		if (!buffer) {
+			snd_usbmidi_out_endpoint_delete(ep);
+			return -ENOMEM;
+		}
+		if (ep_info->out_interval)
+			usb_fill_int_urb(ep->urbs[i].urb, umidi->dev,
+					 pipe, buffer, ep->max_transfer,
+					 snd_usbmidi_out_urb_complete,
+					 &ep->urbs[i], ep_info->out_interval);
+		else
+			usb_fill_bulk_urb(ep->urbs[i].urb, umidi->dev,
+					  pipe, buffer, ep->max_transfer,
+					  snd_usbmidi_out_urb_complete,
+					  &ep->urbs[i]);
+		ep->urbs[i].urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+	}
+
+	spin_lock_init(&ep->buffer_lock);
+	tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep);
+	init_waitqueue_head(&ep->drain_wait);
+
+	for (i = 0; i < 0x10; ++i)
+		if (ep_info->out_cables & (1 << i)) {
+			ep->ports[i].ep = ep;
+			ep->ports[i].cable = i << 4;
+		}
+
+	if (umidi->usb_protocol_ops->init_out_endpoint)
+		umidi->usb_protocol_ops->init_out_endpoint(ep);
+
+	rep->out = ep;
+	return 0;
+}
+
+/*
+ * Frees everything.
+ */
+static void snd_usbmidi_free(struct snd_usb_midi* umidi)
+{
+	int i;
+
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		struct snd_usb_midi_endpoint* ep = &umidi->endpoints[i];
+		if (ep->out)
+			snd_usbmidi_out_endpoint_delete(ep->out);
+		if (ep->in)
+			snd_usbmidi_in_endpoint_delete(ep->in);
+	}
+	mutex_destroy(&umidi->mutex);
+	kfree(umidi);
+}
+
+/*
+ * Unlinks all URBs (must be done before the usb_device is deleted).
+ */
+void snd_usbmidi_disconnect(struct list_head* p)
+{
+	struct snd_usb_midi* umidi;
+	unsigned int i, j;
+
+	umidi = list_entry(p, struct snd_usb_midi, list);
+	/*
+	 * an URB's completion handler may start the timer and
+	 * a timer may submit an URB. To reliably break the cycle
+	 * a flag under lock must be used
+	 */
+	spin_lock_irq(&umidi->disc_lock);
+	umidi->disconnected = 1;
+	spin_unlock_irq(&umidi->disc_lock);
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		struct snd_usb_midi_endpoint* ep = &umidi->endpoints[i];
+		if (ep->out)
+			tasklet_kill(&ep->out->tasklet);
+		if (ep->out) {
+			for (j = 0; j < OUTPUT_URBS; ++j)
+				usb_kill_urb(ep->out->urbs[j].urb);
+			if (umidi->usb_protocol_ops->finish_out_endpoint)
+				umidi->usb_protocol_ops->finish_out_endpoint(ep->out);
+			ep->out->active_urbs = 0;
+			if (ep->out->drain_urbs) {
+				ep->out->drain_urbs = 0;
+				wake_up(&ep->out->drain_wait);
+			}
+		}
+		if (ep->in)
+			for (j = 0; j < INPUT_URBS; ++j)
+				usb_kill_urb(ep->in->urbs[j]);
+		/* free endpoints here; later call can result in Oops */
+		if (ep->out)
+			snd_usbmidi_out_endpoint_clear(ep->out);
+		if (ep->in) {
+			snd_usbmidi_in_endpoint_delete(ep->in);
+			ep->in = NULL;
+		}
+	}
+	del_timer_sync(&umidi->error_timer);
+}
+
+static void snd_usbmidi_rawmidi_free(struct snd_rawmidi *rmidi)
+{
+	struct snd_usb_midi* umidi = rmidi->private_data;
+	snd_usbmidi_free(umidi);
+}
+
+static struct snd_rawmidi_substream *snd_usbmidi_find_substream(struct snd_usb_midi* umidi,
+								int stream, int number)
+{
+	struct list_head* list;
+
+	list_for_each(list, &umidi->rmidi->streams[stream].substreams) {
+		struct snd_rawmidi_substream *substream = list_entry(list, struct snd_rawmidi_substream, list);
+		if (substream->number == number)
+			return substream;
+	}
+	return NULL;
+}
+
+/*
+ * This list specifies names for ports that do not fit into the standard
+ * "(product) MIDI (n)" schema because they aren't external MIDI ports,
+ * such as internal control or synthesizer ports.
+ */
+static struct port_info {
+	u32 id;
+	short int port;
+	short int voices;
+	const char *name;
+	unsigned int seq_flags;
+} snd_usbmidi_port_info[] = {
+#define PORT_INFO(vendor, product, num, name_, voices_, flags) \
+	{ .id = USB_ID(vendor, product), \
+	  .port = num, .voices = voices_, \
+	  .name = name_, .seq_flags = flags }
+#define EXTERNAL_PORT(vendor, product, num, name) \
+	PORT_INFO(vendor, product, num, name, 0, \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+		  SNDRV_SEQ_PORT_TYPE_HARDWARE | \
+		  SNDRV_SEQ_PORT_TYPE_PORT)
+#define CONTROL_PORT(vendor, product, num, name) \
+	PORT_INFO(vendor, product, num, name, 0, \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+		  SNDRV_SEQ_PORT_TYPE_HARDWARE)
+#define ROLAND_SYNTH_PORT(vendor, product, num, name, voices) \
+	PORT_INFO(vendor, product, num, name, voices, \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GM | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GS | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_XG | \
+		  SNDRV_SEQ_PORT_TYPE_HARDWARE | \
+		  SNDRV_SEQ_PORT_TYPE_SYNTHESIZER)
+#define SOUNDCANVAS_PORT(vendor, product, num, name, voices) \
+	PORT_INFO(vendor, product, num, name, voices, \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GM | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_GS | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_XG | \
+		  SNDRV_SEQ_PORT_TYPE_MIDI_MT32 | \
+		  SNDRV_SEQ_PORT_TYPE_HARDWARE | \
+		  SNDRV_SEQ_PORT_TYPE_SYNTHESIZER)
+	/* Roland UA-100 */
+	CONTROL_PORT(0x0582, 0x0000, 2, "%s Control"),
+	/* Roland SC-8850 */
+	SOUNDCANVAS_PORT(0x0582, 0x0003, 0, "%s Part A", 128),
+	SOUNDCANVAS_PORT(0x0582, 0x0003, 1, "%s Part B", 128),
+	SOUNDCANVAS_PORT(0x0582, 0x0003, 2, "%s Part C", 128),
+	SOUNDCANVAS_PORT(0x0582, 0x0003, 3, "%s Part D", 128),
+	EXTERNAL_PORT(0x0582, 0x0003, 4, "%s MIDI 1"),
+	EXTERNAL_PORT(0x0582, 0x0003, 5, "%s MIDI 2"),
+	/* Roland U-8 */
+	EXTERNAL_PORT(0x0582, 0x0004, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x0004, 1, "%s Control"),
+	/* Roland SC-8820 */
+	SOUNDCANVAS_PORT(0x0582, 0x0007, 0, "%s Part A", 64),
+	SOUNDCANVAS_PORT(0x0582, 0x0007, 1, "%s Part B", 64),
+	EXTERNAL_PORT(0x0582, 0x0007, 2, "%s MIDI"),
+	/* Roland SK-500 */
+	SOUNDCANVAS_PORT(0x0582, 0x000b, 0, "%s Part A", 64),
+	SOUNDCANVAS_PORT(0x0582, 0x000b, 1, "%s Part B", 64),
+	EXTERNAL_PORT(0x0582, 0x000b, 2, "%s MIDI"),
+	/* Roland SC-D70 */
+	SOUNDCANVAS_PORT(0x0582, 0x000c, 0, "%s Part A", 64),
+	SOUNDCANVAS_PORT(0x0582, 0x000c, 1, "%s Part B", 64),
+	EXTERNAL_PORT(0x0582, 0x000c, 2, "%s MIDI"),
+	/* Edirol UM-880 */
+	CONTROL_PORT(0x0582, 0x0014, 8, "%s Control"),
+	/* Edirol SD-90 */
+	ROLAND_SYNTH_PORT(0x0582, 0x0016, 0, "%s Part A", 128),
+	ROLAND_SYNTH_PORT(0x0582, 0x0016, 1, "%s Part B", 128),
+	EXTERNAL_PORT(0x0582, 0x0016, 2, "%s MIDI 1"),
+	EXTERNAL_PORT(0x0582, 0x0016, 3, "%s MIDI 2"),
+	/* Edirol UM-550 */
+	CONTROL_PORT(0x0582, 0x0023, 5, "%s Control"),
+	/* Edirol SD-20 */
+	ROLAND_SYNTH_PORT(0x0582, 0x0027, 0, "%s Part A", 64),
+	ROLAND_SYNTH_PORT(0x0582, 0x0027, 1, "%s Part B", 64),
+	EXTERNAL_PORT(0x0582, 0x0027, 2, "%s MIDI"),
+	/* Edirol SD-80 */
+	ROLAND_SYNTH_PORT(0x0582, 0x0029, 0, "%s Part A", 128),
+	ROLAND_SYNTH_PORT(0x0582, 0x0029, 1, "%s Part B", 128),
+	EXTERNAL_PORT(0x0582, 0x0029, 2, "%s MIDI 1"),
+	EXTERNAL_PORT(0x0582, 0x0029, 3, "%s MIDI 2"),
+	/* Edirol UA-700 */
+	EXTERNAL_PORT(0x0582, 0x002b, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x002b, 1, "%s Control"),
+	/* Roland VariOS */
+	EXTERNAL_PORT(0x0582, 0x002f, 0, "%s MIDI"),
+	EXTERNAL_PORT(0x0582, 0x002f, 1, "%s External MIDI"),
+	EXTERNAL_PORT(0x0582, 0x002f, 2, "%s Sync"),
+	/* Edirol PCR */
+	EXTERNAL_PORT(0x0582, 0x0033, 0, "%s MIDI"),
+	EXTERNAL_PORT(0x0582, 0x0033, 1, "%s 1"),
+	EXTERNAL_PORT(0x0582, 0x0033, 2, "%s 2"),
+	/* BOSS GS-10 */
+	EXTERNAL_PORT(0x0582, 0x003b, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x003b, 1, "%s Control"),
+	/* Edirol UA-1000 */
+	EXTERNAL_PORT(0x0582, 0x0044, 0, "%s MIDI"),
+	CONTROL_PORT(0x0582, 0x0044, 1, "%s Control"),
+	/* Edirol UR-80 */
+	EXTERNAL_PORT(0x0582, 0x0048, 0, "%s MIDI"),
+	EXTERNAL_PORT(0x0582, 0x0048, 1, "%s 1"),
+	EXTERNAL_PORT(0x0582, 0x0048, 2, "%s 2"),
+	/* Edirol PCR-A */
+	EXTERNAL_PORT(0x0582, 0x004d, 0, "%s MIDI"),
+	EXTERNAL_PORT(0x0582, 0x004d, 1, "%s 1"),
+	EXTERNAL_PORT(0x0582, 0x004d, 2, "%s 2"),
+	/* Edirol UM-3EX */
+	CONTROL_PORT(0x0582, 0x009a, 3, "%s Control"),
+	/* M-Audio MidiSport 8x8 */
+	CONTROL_PORT(0x0763, 0x1031, 8, "%s Control"),
+	CONTROL_PORT(0x0763, 0x1033, 8, "%s Control"),
+	/* MOTU Fastlane */
+	EXTERNAL_PORT(0x07fd, 0x0001, 0, "%s MIDI A"),
+	EXTERNAL_PORT(0x07fd, 0x0001, 1, "%s MIDI B"),
+	/* Emagic Unitor8/AMT8/MT4 */
+	EXTERNAL_PORT(0x086a, 0x0001, 8, "%s Broadcast"),
+	EXTERNAL_PORT(0x086a, 0x0002, 8, "%s Broadcast"),
+	EXTERNAL_PORT(0x086a, 0x0003, 4, "%s Broadcast"),
+	/* Akai MPD16 */
+	CONTROL_PORT(0x09e8, 0x0062, 0, "%s Control"),
+	PORT_INFO(0x09e8, 0x0062, 1, "%s MIDI", 0,
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE),
+	/* Access Music Virus TI */
+	EXTERNAL_PORT(0x133e, 0x0815, 0, "%s MIDI"),
+	PORT_INFO(0x133e, 0x0815, 1, "%s Synth", 0,
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE |
+		SNDRV_SEQ_PORT_TYPE_SYNTHESIZER),
+};
+
+static struct port_info *find_port_info(struct snd_usb_midi* umidi, int number)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(snd_usbmidi_port_info); ++i) {
+		if (snd_usbmidi_port_info[i].id == umidi->usb_id &&
+		    snd_usbmidi_port_info[i].port == number)
+			return &snd_usbmidi_port_info[i];
+	}
+	return NULL;
+}
+
+static void snd_usbmidi_get_port_info(struct snd_rawmidi *rmidi, int number,
+				      struct snd_seq_port_info *seq_port_info)
+{
+	struct snd_usb_midi *umidi = rmidi->private_data;
+	struct port_info *port_info;
+
+	/* TODO: read port flags from descriptors */
+	port_info = find_port_info(umidi, number);
+	if (port_info) {
+		seq_port_info->type = port_info->seq_flags;
+		seq_port_info->midi_voices = port_info->voices;
+	}
+}
+
+static void snd_usbmidi_init_substream(struct snd_usb_midi* umidi,
+				       int stream, int number,
+				       struct snd_rawmidi_substream ** rsubstream)
+{
+	struct port_info *port_info;
+	const char *name_format;
+
+	struct snd_rawmidi_substream *substream = snd_usbmidi_find_substream(umidi, stream, number);
+	if (!substream) {
+		snd_printd(KERN_ERR "substream %d:%d not found\n", stream, number);
+		return;
+	}
+
+	/* TODO: read port name from jack descriptor */
+	port_info = find_port_info(umidi, number);
+	name_format = port_info ? port_info->name : "%s MIDI %d";
+	snprintf(substream->name, sizeof(substream->name),
+		 name_format, umidi->card->shortname, number + 1);
+
+	*rsubstream = substream;
+}
+
+/*
+ * Creates the endpoints and their ports.
+ */
+static int snd_usbmidi_create_endpoints(struct snd_usb_midi* umidi,
+					struct snd_usb_midi_endpoint_info* endpoints)
+{
+	int i, j, err;
+	int out_ports = 0, in_ports = 0;
+
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		if (endpoints[i].out_cables) {
+			err = snd_usbmidi_out_endpoint_create(umidi, &endpoints[i],
+							      &umidi->endpoints[i]);
+			if (err < 0)
+				return err;
+		}
+		if (endpoints[i].in_cables) {
+			err = snd_usbmidi_in_endpoint_create(umidi, &endpoints[i],
+							     &umidi->endpoints[i]);
+			if (err < 0)
+				return err;
+		}
+
+		for (j = 0; j < 0x10; ++j) {
+			if (endpoints[i].out_cables & (1 << j)) {
+				snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_OUTPUT, out_ports,
+							   &umidi->endpoints[i].out->ports[j].substream);
+				++out_ports;
+			}
+			if (endpoints[i].in_cables & (1 << j)) {
+				snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_INPUT, in_ports,
+							   &umidi->endpoints[i].in->ports[j].substream);
+				++in_ports;
+			}
+		}
+	}
+	snd_printdd(KERN_INFO "created %d output and %d input ports\n",
+		    out_ports, in_ports);
+	return 0;
+}
+
+/*
+ * Returns MIDIStreaming device capabilities.
+ */
+static int snd_usbmidi_get_ms_info(struct snd_usb_midi* umidi,
+			   	   struct snd_usb_midi_endpoint_info* endpoints)
+{
+	struct usb_interface* intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor* intfd;
+	struct usb_ms_header_descriptor* ms_header;
+	struct usb_host_endpoint *hostep;
+	struct usb_endpoint_descriptor* ep;
+	struct usb_ms_endpoint_descriptor* ms_ep;
+	int i, epidx;
+
+	intf = umidi->iface;
+	if (!intf)
+		return -ENXIO;
+	hostif = &intf->altsetting[0];
+	intfd = get_iface_desc(hostif);
+	ms_header = (struct usb_ms_header_descriptor*)hostif->extra;
+	if (hostif->extralen >= 7 &&
+	    ms_header->bLength >= 7 &&
+	    ms_header->bDescriptorType == USB_DT_CS_INTERFACE &&
+	    ms_header->bDescriptorSubtype == UAC_HEADER)
+		snd_printdd(KERN_INFO "MIDIStreaming version %02x.%02x\n",
+			    ms_header->bcdMSC[1], ms_header->bcdMSC[0]);
+	else
+		snd_printk(KERN_WARNING "MIDIStreaming interface descriptor not found\n");
+
+	epidx = 0;
+	for (i = 0; i < intfd->bNumEndpoints; ++i) {
+		hostep = &hostif->endpoint[i];
+		ep = get_ep_desc(hostep);
+		if (!usb_endpoint_xfer_bulk(ep) && !usb_endpoint_xfer_int(ep))
+			continue;
+		ms_ep = (struct usb_ms_endpoint_descriptor*)hostep->extra;
+		if (hostep->extralen < 4 ||
+		    ms_ep->bLength < 4 ||
+		    ms_ep->bDescriptorType != USB_DT_CS_ENDPOINT ||
+		    ms_ep->bDescriptorSubtype != UAC_MS_GENERAL)
+			continue;
+		if (usb_endpoint_dir_out(ep)) {
+			if (endpoints[epidx].out_ep) {
+				if (++epidx >= MIDI_MAX_ENDPOINTS) {
+					snd_printk(KERN_WARNING "too many endpoints\n");
+					break;
+				}
+			}
+			endpoints[epidx].out_ep = usb_endpoint_num(ep);
+			if (usb_endpoint_xfer_int(ep))
+				endpoints[epidx].out_interval = ep->bInterval;
+			else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW)
+				/*
+				 * Low speed bulk transfers don't exist, so
+				 * force interrupt transfers for devices like
+				 * ESI MIDI Mate that try to use them anyway.
+				 */
+				endpoints[epidx].out_interval = 1;
+			endpoints[epidx].out_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1;
+			snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n",
+				    ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack);
+		} else {
+			if (endpoints[epidx].in_ep) {
+				if (++epidx >= MIDI_MAX_ENDPOINTS) {
+					snd_printk(KERN_WARNING "too many endpoints\n");
+					break;
+				}
+			}
+			endpoints[epidx].in_ep = usb_endpoint_num(ep);
+			if (usb_endpoint_xfer_int(ep))
+				endpoints[epidx].in_interval = ep->bInterval;
+			else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW)
+				endpoints[epidx].in_interval = 1;
+			endpoints[epidx].in_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1;
+			snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n",
+				    ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack);
+		}
+	}
+	return 0;
+}
+
+static int roland_load_info(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *info)
+{
+	static const char *const names[] = { "High Load", "Light Load" };
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 2;
+	if (info->value.enumerated.item > 1)
+		info->value.enumerated.item = 1;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int roland_load_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *value)
+{
+	value->value.enumerated.item[0] = kcontrol->private_value;
+	return 0;
+}
+
+static int roland_load_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *value)
+{
+	struct snd_usb_midi* umidi = kcontrol->private_data;
+	int changed;
+
+	if (value->value.enumerated.item[0] > 1)
+		return -EINVAL;
+	mutex_lock(&umidi->mutex);
+	changed = value->value.enumerated.item[0] != kcontrol->private_value;
+	if (changed)
+		kcontrol->private_value = value->value.enumerated.item[0];
+	mutex_unlock(&umidi->mutex);
+	return changed;
+}
+
+static struct snd_kcontrol_new roland_load_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "MIDI Input Mode",
+	.info = roland_load_info,
+	.get = roland_load_get,
+	.put = roland_load_put,
+	.private_value = 1,
+};
+
+/*
+ * On Roland devices, use the second alternate setting to be able to use
+ * the interrupt input endpoint.
+ */
+static void snd_usbmidi_switch_roland_altsetting(struct snd_usb_midi* umidi)
+{
+	struct usb_interface* intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor* intfd;
+
+	intf = umidi->iface;
+	if (!intf || intf->num_altsetting != 2)
+		return;
+
+	hostif = &intf->altsetting[1];
+	intfd = get_iface_desc(hostif);
+	if (intfd->bNumEndpoints != 2 ||
+	    (get_endpoint(hostif, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK ||
+	    (get_endpoint(hostif, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT)
+		return;
+
+	snd_printdd(KERN_INFO "switching to altsetting %d with int ep\n",
+		    intfd->bAlternateSetting);
+	usb_set_interface(umidi->dev, intfd->bInterfaceNumber,
+			  intfd->bAlternateSetting);
+
+	umidi->roland_load_ctl = snd_ctl_new1(&roland_load_ctl, umidi);
+	if (snd_ctl_add(umidi->card, umidi->roland_load_ctl) < 0)
+		umidi->roland_load_ctl = NULL;
+}
+
+/*
+ * Try to find any usable endpoints in the interface.
+ */
+static int snd_usbmidi_detect_endpoints(struct snd_usb_midi* umidi,
+					struct snd_usb_midi_endpoint_info* endpoint,
+					int max_endpoints)
+{
+	struct usb_interface* intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor* intfd;
+	struct usb_endpoint_descriptor* epd;
+	int i, out_eps = 0, in_eps = 0;
+
+	if (USB_ID_VENDOR(umidi->usb_id) == 0x0582)
+		snd_usbmidi_switch_roland_altsetting(umidi);
+
+	if (endpoint[0].out_ep || endpoint[0].in_ep)
+		return 0;
+
+	intf = umidi->iface;
+	if (!intf || intf->num_altsetting < 1)
+		return -ENOENT;
+	hostif = intf->cur_altsetting;
+	intfd = get_iface_desc(hostif);
+
+	for (i = 0; i < intfd->bNumEndpoints; ++i) {
+		epd = get_endpoint(hostif, i);
+		if (!usb_endpoint_xfer_bulk(epd) &&
+		    !usb_endpoint_xfer_int(epd))
+			continue;
+		if (out_eps < max_endpoints &&
+		    usb_endpoint_dir_out(epd)) {
+			endpoint[out_eps].out_ep = usb_endpoint_num(epd);
+			if (usb_endpoint_xfer_int(epd))
+				endpoint[out_eps].out_interval = epd->bInterval;
+			++out_eps;
+		}
+		if (in_eps < max_endpoints &&
+		    usb_endpoint_dir_in(epd)) {
+			endpoint[in_eps].in_ep = usb_endpoint_num(epd);
+			if (usb_endpoint_xfer_int(epd))
+				endpoint[in_eps].in_interval = epd->bInterval;
+			++in_eps;
+		}
+	}
+	return (out_eps || in_eps) ? 0 : -ENOENT;
+}
+
+/*
+ * Detects the endpoints for one-port-per-endpoint protocols.
+ */
+static int snd_usbmidi_detect_per_port_endpoints(struct snd_usb_midi* umidi,
+						 struct snd_usb_midi_endpoint_info* endpoints)
+{
+	int err, i;
+
+	err = snd_usbmidi_detect_endpoints(umidi, endpoints, MIDI_MAX_ENDPOINTS);
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		if (endpoints[i].out_ep)
+			endpoints[i].out_cables = 0x0001;
+		if (endpoints[i].in_ep)
+			endpoints[i].in_cables = 0x0001;
+	}
+	return err;
+}
+
+/*
+ * Detects the endpoints and ports of Yamaha devices.
+ */
+static int snd_usbmidi_detect_yamaha(struct snd_usb_midi* umidi,
+				     struct snd_usb_midi_endpoint_info* endpoint)
+{
+	struct usb_interface* intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor* intfd;
+	uint8_t* cs_desc;
+
+	intf = umidi->iface;
+	if (!intf)
+		return -ENOENT;
+	hostif = intf->altsetting;
+	intfd = get_iface_desc(hostif);
+	if (intfd->bNumEndpoints < 1)
+		return -ENOENT;
+
+	/*
+	 * For each port there is one MIDI_IN/OUT_JACK descriptor, not
+	 * necessarily with any useful contents.  So simply count 'em.
+	 */
+	for (cs_desc = hostif->extra;
+	     cs_desc < hostif->extra + hostif->extralen && cs_desc[0] >= 2;
+	     cs_desc += cs_desc[0]) {
+		if (cs_desc[1] == USB_DT_CS_INTERFACE) {
+			if (cs_desc[2] == UAC_MIDI_IN_JACK)
+				endpoint->in_cables = (endpoint->in_cables << 1) | 1;
+			else if (cs_desc[2] == UAC_MIDI_OUT_JACK)
+				endpoint->out_cables = (endpoint->out_cables << 1) | 1;
+		}
+	}
+	if (!endpoint->in_cables && !endpoint->out_cables)
+		return -ENOENT;
+
+	return snd_usbmidi_detect_endpoints(umidi, endpoint, 1);
+}
+
+/*
+ * Creates the endpoints and their ports for Midiman devices.
+ */
+static int snd_usbmidi_create_endpoints_midiman(struct snd_usb_midi* umidi,
+						struct snd_usb_midi_endpoint_info* endpoint)
+{
+	struct snd_usb_midi_endpoint_info ep_info;
+	struct usb_interface* intf;
+	struct usb_host_interface *hostif;
+	struct usb_interface_descriptor* intfd;
+	struct usb_endpoint_descriptor* epd;
+	int cable, err;
+
+	intf = umidi->iface;
+	if (!intf)
+		return -ENOENT;
+	hostif = intf->altsetting;
+	intfd = get_iface_desc(hostif);
+	/*
+	 * The various MidiSport devices have more or less random endpoint
+	 * numbers, so we have to identify the endpoints by their index in
+	 * the descriptor array, like the driver for that other OS does.
+	 *
+	 * There is one interrupt input endpoint for all input ports, one
+	 * bulk output endpoint for even-numbered ports, and one for odd-
+	 * numbered ports.  Both bulk output endpoints have corresponding
+	 * input bulk endpoints (at indices 1 and 3) which aren't used.
+	 */
+	if (intfd->bNumEndpoints < (endpoint->out_cables > 0x0001 ? 5 : 3)) {
+		snd_printdd(KERN_ERR "not enough endpoints\n");
+		return -ENOENT;
+	}
+
+	epd = get_endpoint(hostif, 0);
+	if (!usb_endpoint_dir_in(epd) || !usb_endpoint_xfer_int(epd)) {
+		snd_printdd(KERN_ERR "endpoint[0] isn't interrupt\n");
+		return -ENXIO;
+	}
+	epd = get_endpoint(hostif, 2);
+	if (!usb_endpoint_dir_out(epd) || !usb_endpoint_xfer_bulk(epd)) {
+		snd_printdd(KERN_ERR "endpoint[2] isn't bulk output\n");
+		return -ENXIO;
+	}
+	if (endpoint->out_cables > 0x0001) {
+		epd = get_endpoint(hostif, 4);
+		if (!usb_endpoint_dir_out(epd) ||
+		    !usb_endpoint_xfer_bulk(epd)) {
+			snd_printdd(KERN_ERR "endpoint[4] isn't bulk output\n");
+			return -ENXIO;
+		}
+	}
+
+	ep_info.out_ep = get_endpoint(hostif, 2)->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
+	ep_info.out_interval = 0;
+	ep_info.out_cables = endpoint->out_cables & 0x5555;
+	err = snd_usbmidi_out_endpoint_create(umidi, &ep_info, &umidi->endpoints[0]);
+	if (err < 0)
+		return err;
+
+	ep_info.in_ep = get_endpoint(hostif, 0)->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
+	ep_info.in_interval = get_endpoint(hostif, 0)->bInterval;
+	ep_info.in_cables = endpoint->in_cables;
+	err = snd_usbmidi_in_endpoint_create(umidi, &ep_info, &umidi->endpoints[0]);
+	if (err < 0)
+		return err;
+
+	if (endpoint->out_cables > 0x0001) {
+		ep_info.out_ep = get_endpoint(hostif, 4)->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
+		ep_info.out_cables = endpoint->out_cables & 0xaaaa;
+		err = snd_usbmidi_out_endpoint_create(umidi, &ep_info, &umidi->endpoints[1]);
+		if (err < 0)
+			return err;
+	}
+
+	for (cable = 0; cable < 0x10; ++cable) {
+		if (endpoint->out_cables & (1 << cable))
+			snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_OUTPUT, cable,
+						   &umidi->endpoints[cable & 1].out->ports[cable].substream);
+		if (endpoint->in_cables & (1 << cable))
+			snd_usbmidi_init_substream(umidi, SNDRV_RAWMIDI_STREAM_INPUT, cable,
+						   &umidi->endpoints[0].in->ports[cable].substream);
+	}
+	return 0;
+}
+
+static struct snd_rawmidi_global_ops snd_usbmidi_ops = {
+	.get_port_info = snd_usbmidi_get_port_info,
+};
+
+static int snd_usbmidi_create_rawmidi(struct snd_usb_midi* umidi,
+				      int out_ports, int in_ports)
+{
+	struct snd_rawmidi *rmidi;
+	int err;
+
+	err = snd_rawmidi_new(umidi->card, "USB MIDI",
+			      umidi->next_midi_device++,
+			      out_ports, in_ports, &rmidi);
+	if (err < 0)
+		return err;
+	strcpy(rmidi->name, umidi->card->shortname);
+	rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+			    SNDRV_RAWMIDI_INFO_INPUT |
+			    SNDRV_RAWMIDI_INFO_DUPLEX;
+	rmidi->ops = &snd_usbmidi_ops;
+	rmidi->private_data = umidi;
+	rmidi->private_free = snd_usbmidi_rawmidi_free;
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_usbmidi_output_ops);
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_usbmidi_input_ops);
+
+	umidi->rmidi = rmidi;
+	return 0;
+}
+
+/*
+ * Temporarily stop input.
+ */
+void snd_usbmidi_input_stop(struct list_head* p)
+{
+	struct snd_usb_midi* umidi;
+	unsigned int i, j;
+
+	umidi = list_entry(p, struct snd_usb_midi, list);
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		struct snd_usb_midi_endpoint* ep = &umidi->endpoints[i];
+		if (ep->in)
+			for (j = 0; j < INPUT_URBS; ++j)
+				usb_kill_urb(ep->in->urbs[j]);
+	}
+}
+
+static void snd_usbmidi_input_start_ep(struct snd_usb_midi_in_endpoint* ep)
+{
+	unsigned int i;
+
+	if (!ep)
+		return;
+	for (i = 0; i < INPUT_URBS; ++i) {
+		struct urb* urb = ep->urbs[i];
+		urb->dev = ep->umidi->dev;
+		snd_usbmidi_submit_urb(urb, GFP_KERNEL);
+	}
+}
+
+/*
+ * Resume input after a call to snd_usbmidi_input_stop().
+ */
+void snd_usbmidi_input_start(struct list_head* p)
+{
+	struct snd_usb_midi* umidi;
+	int i;
+
+	umidi = list_entry(p, struct snd_usb_midi, list);
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i)
+		snd_usbmidi_input_start_ep(umidi->endpoints[i].in);
+}
+
+/*
+ * Creates and registers everything needed for a MIDI streaming interface.
+ */
+int snd_usbmidi_create(struct snd_card *card,
+		       struct usb_interface* iface,
+		       struct list_head *midi_list,
+		       const struct snd_usb_audio_quirk* quirk)
+{
+	struct snd_usb_midi* umidi;
+	struct snd_usb_midi_endpoint_info endpoints[MIDI_MAX_ENDPOINTS];
+	int out_ports, in_ports;
+	int i, err;
+
+	umidi = kzalloc(sizeof(*umidi), GFP_KERNEL);
+	if (!umidi)
+		return -ENOMEM;
+	umidi->dev = interface_to_usbdev(iface);
+	umidi->card = card;
+	umidi->iface = iface;
+	umidi->quirk = quirk;
+	umidi->usb_protocol_ops = &snd_usbmidi_standard_ops;
+	init_timer(&umidi->error_timer);
+	spin_lock_init(&umidi->disc_lock);
+	mutex_init(&umidi->mutex);
+	umidi->usb_id = USB_ID(le16_to_cpu(umidi->dev->descriptor.idVendor),
+			       le16_to_cpu(umidi->dev->descriptor.idProduct));
+	umidi->error_timer.function = snd_usbmidi_error_timer;
+	umidi->error_timer.data = (unsigned long)umidi;
+
+	/* detect the endpoint(s) to use */
+	memset(endpoints, 0, sizeof(endpoints));
+	switch (quirk ? quirk->type : QUIRK_MIDI_STANDARD_INTERFACE) {
+	case QUIRK_MIDI_STANDARD_INTERFACE:
+		err = snd_usbmidi_get_ms_info(umidi, endpoints);
+		if (umidi->usb_id == USB_ID(0x0763, 0x0150)) /* M-Audio Uno */
+			umidi->usb_protocol_ops =
+				&snd_usbmidi_maudio_broken_running_status_ops;
+		break;
+	case QUIRK_MIDI_US122L:
+		umidi->usb_protocol_ops = &snd_usbmidi_122l_ops;
+		/* fall through */
+	case QUIRK_MIDI_FIXED_ENDPOINT:
+		memcpy(&endpoints[0], quirk->data,
+		       sizeof(struct snd_usb_midi_endpoint_info));
+		err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1);
+		break;
+	case QUIRK_MIDI_YAMAHA:
+		err = snd_usbmidi_detect_yamaha(umidi, &endpoints[0]);
+		break;
+	case QUIRK_MIDI_MIDIMAN:
+		umidi->usb_protocol_ops = &snd_usbmidi_midiman_ops;
+		memcpy(&endpoints[0], quirk->data,
+		       sizeof(struct snd_usb_midi_endpoint_info));
+		err = 0;
+		break;
+	case QUIRK_MIDI_NOVATION:
+		umidi->usb_protocol_ops = &snd_usbmidi_novation_ops;
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		break;
+	case QUIRK_MIDI_RAW_BYTES:
+		umidi->usb_protocol_ops = &snd_usbmidi_raw_ops;
+		/*
+		 * Interface 1 contains isochronous endpoints, but with the same
+		 * numbers as in interface 0.  Since it is interface 1 that the
+		 * USB core has most recently seen, these descriptors are now
+		 * associated with the endpoint numbers.  This will foul up our
+		 * attempts to submit bulk/interrupt URBs to the endpoints in
+		 * interface 0, so we have to make sure that the USB core looks
+		 * again at interface 0 by calling usb_set_interface() on it.
+		 */
+		if (umidi->usb_id == USB_ID(0x07fd, 0x0001)) /* MOTU Fastlane */
+			usb_set_interface(umidi->dev, 0, 0);
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		break;
+	case QUIRK_MIDI_EMAGIC:
+		umidi->usb_protocol_ops = &snd_usbmidi_emagic_ops;
+		memcpy(&endpoints[0], quirk->data,
+		       sizeof(struct snd_usb_midi_endpoint_info));
+		err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1);
+		break;
+	case QUIRK_MIDI_CME:
+		umidi->usb_protocol_ops = &snd_usbmidi_cme_ops;
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		break;
+	case QUIRK_MIDI_AKAI:
+		umidi->usb_protocol_ops = &snd_usbmidi_akai_ops;
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		/* endpoint 1 is input-only */
+		endpoints[1].out_cables = 0;
+		break;
+	default:
+		snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type);
+		err = -ENXIO;
+		break;
+	}
+	if (err < 0) {
+		kfree(umidi);
+		return err;
+	}
+
+	/* create rawmidi device */
+	out_ports = 0;
+	in_ports = 0;
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
+		out_ports += hweight16(endpoints[i].out_cables);
+		in_ports += hweight16(endpoints[i].in_cables);
+	}
+	err = snd_usbmidi_create_rawmidi(umidi, out_ports, in_ports);
+	if (err < 0) {
+		kfree(umidi);
+		return err;
+	}
+
+	/* create endpoint/port structures */
+	if (quirk && quirk->type == QUIRK_MIDI_MIDIMAN)
+		err = snd_usbmidi_create_endpoints_midiman(umidi, &endpoints[0]);
+	else
+		err = snd_usbmidi_create_endpoints(umidi, endpoints);
+	if (err < 0) {
+		snd_usbmidi_free(umidi);
+		return err;
+	}
+
+	list_add_tail(&umidi->list, midi_list);
+
+	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i)
+		snd_usbmidi_input_start_ep(umidi->endpoints[i].in);
+	return 0;
+}
+
+EXPORT_SYMBOL(snd_usbmidi_create);
+EXPORT_SYMBOL(snd_usbmidi_input_stop);
+EXPORT_SYMBOL(snd_usbmidi_input_start);
+EXPORT_SYMBOL(snd_usbmidi_disconnect);
diff --git a/linux/sound/usb/midi.h b/linux/sound/usb/midi.h
new file mode 100644
index 0000000..2fca80b
--- /dev/null
+++ b/linux/sound/usb/midi.h
@@ -0,0 +1,50 @@
+#ifndef __USBMIDI_H
+#define __USBMIDI_H
+
+/* maximum number of endpoints per interface */
+#define MIDI_MAX_ENDPOINTS 2
+
+/* data for QUIRK_MIDI_FIXED_ENDPOINT */
+struct snd_usb_midi_endpoint_info {
+	int8_t   out_ep;	/* ep number, 0 autodetect */
+	uint8_t  out_interval;	/* interval for interrupt endpoints */
+	int8_t   in_ep;
+	uint8_t  in_interval;
+	uint16_t out_cables;	/* bitmask */
+	uint16_t in_cables;	/* bitmask */
+};
+
+/* for QUIRK_MIDI_YAMAHA, data is NULL */
+
+/* for QUIRK_MIDI_MIDIMAN, data points to a snd_usb_midi_endpoint_info
+ * structure (out_cables and in_cables only) */
+
+/* for QUIRK_COMPOSITE, data points to an array of snd_usb_audio_quirk
+ * structures, terminated with .ifnum = -1 */
+
+/* for QUIRK_AUDIO_FIXED_ENDPOINT, data points to an audioformat structure */
+
+/* for QUIRK_AUDIO/MIDI_STANDARD_INTERFACE, data is NULL */
+
+/* for QUIRK_AUDIO_EDIROL_UA700_UA25/UA1000, data is NULL */
+
+/* for QUIRK_IGNORE_INTERFACE, data is NULL */
+
+/* for QUIRK_MIDI_NOVATION and _RAW, data is NULL */
+
+/* for QUIRK_MIDI_EMAGIC, data points to a snd_usb_midi_endpoint_info
+ * structure (out_cables and in_cables only) */
+
+/* for QUIRK_MIDI_CME, data is NULL */
+
+/* for QUIRK_MIDI_AKAI, data is NULL */
+
+int snd_usbmidi_create(struct snd_card *card,
+		       struct usb_interface *iface,
+		       struct list_head *midi_list,
+		       const struct snd_usb_audio_quirk *quirk);
+void snd_usbmidi_input_stop(struct list_head* p);
+void snd_usbmidi_input_start(struct list_head* p);
+void snd_usbmidi_disconnect(struct list_head *p);
+
+#endif /* __USBMIDI_H */
diff --git a/linux/sound/usb/misc/Makefile b/linux/sound/usb/misc/Makefile
new file mode 100644
index 0000000..ccefd81
--- /dev/null
+++ b/linux/sound/usb/misc/Makefile
@@ -0,0 +1,2 @@
+snd-ua101-objs := ua101.o
+obj-$(CONFIG_SND_USB_UA101) += snd-ua101.o
diff --git a/linux/sound/usb/misc/ua101.c b/linux/sound/usb/misc/ua101.c
new file mode 100644
index 0000000..fb5d68f
--- /dev/null
+++ b/linux/sound/usb/misc/ua101.c
@@ -0,0 +1,1388 @@
+/*
+ * Edirol UA-101/UA-1000 driver
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * This driver is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver 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 driver.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "../usbaudio.h"
+#include "../midi.h"
+
+MODULE_DESCRIPTION("Edirol UA-101/1000 driver");
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{Edirol,UA-101},{Edirol,UA-1000}}");
+
+/*
+ * Should not be lower than the minimum scheduling delay of the host
+ * controller.  Some Intel controllers need more than one frame; as long as
+ * that driver doesn't tell us about this, use 1.5 frames just to be sure.
+ */
+#define MIN_QUEUE_LENGTH	12
+/* Somewhat random. */
+#define MAX_QUEUE_LENGTH	30
+/*
+ * This magic value optimizes memory usage efficiency for the UA-101's packet
+ * sizes at all sample rates, taking into account the stupid cache pool sizes
+ * that usb_alloc_coherent() uses.
+ */
+#define DEFAULT_QUEUE_LENGTH	21
+
+#define MAX_PACKET_SIZE		672 /* hardware specific */
+#define MAX_MEMORY_BUFFERS	DIV_ROUND_UP(MAX_QUEUE_LENGTH, \
+					     PAGE_SIZE / MAX_PACKET_SIZE)
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+static unsigned int queue_length = 21;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "card index");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "enable card");
+module_param(queue_length, uint, 0644);
+MODULE_PARM_DESC(queue_length, "USB queue length in microframes, "
+		 __stringify(MIN_QUEUE_LENGTH)"-"__stringify(MAX_QUEUE_LENGTH));
+
+enum {
+	INTF_PLAYBACK,
+	INTF_CAPTURE,
+	INTF_MIDI,
+
+	INTF_COUNT
+};
+
+/* bits in struct ua101::states */
+enum {
+	USB_CAPTURE_RUNNING,
+	USB_PLAYBACK_RUNNING,
+	ALSA_CAPTURE_OPEN,
+	ALSA_PLAYBACK_OPEN,
+	ALSA_CAPTURE_RUNNING,
+	ALSA_PLAYBACK_RUNNING,
+	CAPTURE_URB_COMPLETED,
+	PLAYBACK_URB_COMPLETED,
+	DISCONNECTED,
+};
+
+struct ua101 {
+	struct usb_device *dev;
+	struct snd_card *card;
+	struct usb_interface *intf[INTF_COUNT];
+	int card_index;
+	struct snd_pcm *pcm;
+	struct list_head midi_list;
+	u64 format_bit;
+	unsigned int rate;
+	unsigned int packets_per_second;
+	spinlock_t lock;
+	struct mutex mutex;
+	unsigned long states;
+
+	/* FIFO to synchronize playback rate to capture rate */
+	unsigned int rate_feedback_start;
+	unsigned int rate_feedback_count;
+	u8 rate_feedback[MAX_QUEUE_LENGTH];
+
+	struct list_head ready_playback_urbs;
+	struct tasklet_struct playback_tasklet;
+	wait_queue_head_t alsa_capture_wait;
+	wait_queue_head_t rate_feedback_wait;
+	wait_queue_head_t alsa_playback_wait;
+	struct ua101_stream {
+		struct snd_pcm_substream *substream;
+		unsigned int usb_pipe;
+		unsigned int channels;
+		unsigned int frame_bytes;
+		unsigned int max_packet_bytes;
+		unsigned int period_pos;
+		unsigned int buffer_pos;
+		unsigned int queue_length;
+		struct ua101_urb {
+			struct urb urb;
+			struct usb_iso_packet_descriptor iso_frame_desc[1];
+			struct list_head ready_list;
+		} *urbs[MAX_QUEUE_LENGTH];
+		struct {
+			unsigned int size;
+			void *addr;
+			dma_addr_t dma;
+		} buffers[MAX_MEMORY_BUFFERS];
+	} capture, playback;
+};
+
+static DEFINE_MUTEX(devices_mutex);
+static unsigned int devices_used;
+static struct usb_driver ua101_driver;
+
+static void abort_alsa_playback(struct ua101 *ua);
+static void abort_alsa_capture(struct ua101 *ua);
+
+static const char *usb_error_string(int err)
+{
+	switch (err) {
+	case -ENODEV:
+		return "no device";
+	case -ENOENT:
+		return "endpoint not enabled";
+	case -EPIPE:
+		return "endpoint stalled";
+	case -ENOSPC:
+		return "not enough bandwidth";
+	case -ESHUTDOWN:
+		return "device disabled";
+	case -EHOSTUNREACH:
+		return "device suspended";
+	case -EINVAL:
+	case -EAGAIN:
+	case -EFBIG:
+	case -EMSGSIZE:
+		return "internal error";
+	default:
+		return "unknown error";
+	}
+}
+
+static void abort_usb_capture(struct ua101 *ua)
+{
+	if (test_and_clear_bit(USB_CAPTURE_RUNNING, &ua->states)) {
+		wake_up(&ua->alsa_capture_wait);
+		wake_up(&ua->rate_feedback_wait);
+	}
+}
+
+static void abort_usb_playback(struct ua101 *ua)
+{
+	if (test_and_clear_bit(USB_PLAYBACK_RUNNING, &ua->states))
+		wake_up(&ua->alsa_playback_wait);
+}
+
+static void playback_urb_complete(struct urb *usb_urb)
+{
+	struct ua101_urb *urb = (struct ua101_urb *)usb_urb;
+	struct ua101 *ua = urb->urb.context;
+	unsigned long flags;
+
+	if (unlikely(urb->urb.status == -ENOENT ||	/* unlinked */
+		     urb->urb.status == -ENODEV ||	/* device removed */
+		     urb->urb.status == -ECONNRESET ||	/* unlinked */
+		     urb->urb.status == -ESHUTDOWN)) {	/* device disabled */
+		abort_usb_playback(ua);
+		abort_alsa_playback(ua);
+		return;
+	}
+
+	if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) {
+		/* append URB to FIFO */
+		spin_lock_irqsave(&ua->lock, flags);
+		list_add_tail(&urb->ready_list, &ua->ready_playback_urbs);
+		if (ua->rate_feedback_count > 0)
+			tasklet_schedule(&ua->playback_tasklet);
+		ua->playback.substream->runtime->delay -=
+				urb->urb.iso_frame_desc[0].length /
+						ua->playback.frame_bytes;
+		spin_unlock_irqrestore(&ua->lock, flags);
+	}
+}
+
+static void first_playback_urb_complete(struct urb *urb)
+{
+	struct ua101 *ua = urb->context;
+
+	urb->complete = playback_urb_complete;
+	playback_urb_complete(urb);
+
+	set_bit(PLAYBACK_URB_COMPLETED, &ua->states);
+	wake_up(&ua->alsa_playback_wait);
+}
+
+/* copy data from the ALSA ring buffer into the URB buffer */
+static bool copy_playback_data(struct ua101_stream *stream, struct urb *urb,
+			       unsigned int frames)
+{
+	struct snd_pcm_runtime *runtime;
+	unsigned int frame_bytes, frames1;
+	const u8 *source;
+
+	runtime = stream->substream->runtime;
+	frame_bytes = stream->frame_bytes;
+	source = runtime->dma_area + stream->buffer_pos * frame_bytes;
+	if (stream->buffer_pos + frames <= runtime->buffer_size) {
+		memcpy(urb->transfer_buffer, source, frames * frame_bytes);
+	} else {
+		/* wrap around at end of ring buffer */
+		frames1 = runtime->buffer_size - stream->buffer_pos;
+		memcpy(urb->transfer_buffer, source, frames1 * frame_bytes);
+		memcpy(urb->transfer_buffer + frames1 * frame_bytes,
+		       runtime->dma_area, (frames - frames1) * frame_bytes);
+	}
+
+	stream->buffer_pos += frames;
+	if (stream->buffer_pos >= runtime->buffer_size)
+		stream->buffer_pos -= runtime->buffer_size;
+	stream->period_pos += frames;
+	if (stream->period_pos >= runtime->period_size) {
+		stream->period_pos -= runtime->period_size;
+		return true;
+	}
+	return false;
+}
+
+static inline void add_with_wraparound(struct ua101 *ua,
+				       unsigned int *value, unsigned int add)
+{
+	*value += add;
+	if (*value >= ua->playback.queue_length)
+		*value -= ua->playback.queue_length;
+}
+
+static void playback_tasklet(unsigned long data)
+{
+	struct ua101 *ua = (void *)data;
+	unsigned long flags;
+	unsigned int frames;
+	struct ua101_urb *urb;
+	bool do_period_elapsed = false;
+	int err;
+
+	if (unlikely(!test_bit(USB_PLAYBACK_RUNNING, &ua->states)))
+		return;
+
+	/*
+	 * Synchronizing the playback rate to the capture rate is done by using
+	 * the same sequence of packet sizes for both streams.
+	 * Submitting a playback URB therefore requires both a ready URB and
+	 * the size of the corresponding capture packet, i.e., both playback
+	 * and capture URBs must have been completed.  Since the USB core does
+	 * not guarantee that playback and capture complete callbacks are
+	 * called alternately, we use two FIFOs for packet sizes and read URBs;
+	 * submitting playback URBs is possible as long as both FIFOs are
+	 * nonempty.
+	 */
+	spin_lock_irqsave(&ua->lock, flags);
+	while (ua->rate_feedback_count > 0 &&
+	       !list_empty(&ua->ready_playback_urbs)) {
+		/* take packet size out of FIFO */
+		frames = ua->rate_feedback[ua->rate_feedback_start];
+		add_with_wraparound(ua, &ua->rate_feedback_start, 1);
+		ua->rate_feedback_count--;
+
+		/* take URB out of FIFO */
+		urb = list_first_entry(&ua->ready_playback_urbs,
+				       struct ua101_urb, ready_list);
+		list_del(&urb->ready_list);
+
+		/* fill packet with data or silence */
+		urb->urb.iso_frame_desc[0].length =
+			frames * ua->playback.frame_bytes;
+		if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states))
+			do_period_elapsed |= copy_playback_data(&ua->playback,
+								&urb->urb,
+								frames);
+		else
+			memset(urb->urb.transfer_buffer, 0,
+			       urb->urb.iso_frame_desc[0].length);
+
+		/* and off you go ... */
+		err = usb_submit_urb(&urb->urb, GFP_ATOMIC);
+		if (unlikely(err < 0)) {
+			spin_unlock_irqrestore(&ua->lock, flags);
+			abort_usb_playback(ua);
+			abort_alsa_playback(ua);
+			dev_err(&ua->dev->dev, "USB request error %d: %s\n",
+				err, usb_error_string(err));
+			return;
+		}
+		ua->playback.substream->runtime->delay += frames;
+	}
+	spin_unlock_irqrestore(&ua->lock, flags);
+	if (do_period_elapsed)
+		snd_pcm_period_elapsed(ua->playback.substream);
+}
+
+/* copy data from the URB buffer into the ALSA ring buffer */
+static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb,
+			      unsigned int frames)
+{
+	struct snd_pcm_runtime *runtime;
+	unsigned int frame_bytes, frames1;
+	u8 *dest;
+
+	runtime = stream->substream->runtime;
+	frame_bytes = stream->frame_bytes;
+	dest = runtime->dma_area + stream->buffer_pos * frame_bytes;
+	if (stream->buffer_pos + frames <= runtime->buffer_size) {
+		memcpy(dest, urb->transfer_buffer, frames * frame_bytes);
+	} else {
+		/* wrap around at end of ring buffer */
+		frames1 = runtime->buffer_size - stream->buffer_pos;
+		memcpy(dest, urb->transfer_buffer, frames1 * frame_bytes);
+		memcpy(runtime->dma_area,
+		       urb->transfer_buffer + frames1 * frame_bytes,
+		       (frames - frames1) * frame_bytes);
+	}
+
+	stream->buffer_pos += frames;
+	if (stream->buffer_pos >= runtime->buffer_size)
+		stream->buffer_pos -= runtime->buffer_size;
+	stream->period_pos += frames;
+	if (stream->period_pos >= runtime->period_size) {
+		stream->period_pos -= runtime->period_size;
+		return true;
+	}
+	return false;
+}
+
+static void capture_urb_complete(struct urb *urb)
+{
+	struct ua101 *ua = urb->context;
+	struct ua101_stream *stream = &ua->capture;
+	unsigned long flags;
+	unsigned int frames, write_ptr;
+	bool do_period_elapsed;
+	int err;
+
+	if (unlikely(urb->status == -ENOENT ||		/* unlinked */
+		     urb->status == -ENODEV ||		/* device removed */
+		     urb->status == -ECONNRESET ||	/* unlinked */
+		     urb->status == -ESHUTDOWN))	/* device disabled */
+		goto stream_stopped;
+
+	if (urb->status >= 0 && urb->iso_frame_desc[0].status >= 0)
+		frames = urb->iso_frame_desc[0].actual_length /
+			stream->frame_bytes;
+	else
+		frames = 0;
+
+	spin_lock_irqsave(&ua->lock, flags);
+
+	if (frames > 0 && test_bit(ALSA_CAPTURE_RUNNING, &ua->states))
+		do_period_elapsed = copy_capture_data(stream, urb, frames);
+	else
+		do_period_elapsed = false;
+
+	if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) {
+		err = usb_submit_urb(urb, GFP_ATOMIC);
+		if (unlikely(err < 0)) {
+			spin_unlock_irqrestore(&ua->lock, flags);
+			dev_err(&ua->dev->dev, "USB request error %d: %s\n",
+				err, usb_error_string(err));
+			goto stream_stopped;
+		}
+
+		/* append packet size to FIFO */
+		write_ptr = ua->rate_feedback_start;
+		add_with_wraparound(ua, &write_ptr, ua->rate_feedback_count);
+		ua->rate_feedback[write_ptr] = frames;
+		if (ua->rate_feedback_count < ua->playback.queue_length) {
+			ua->rate_feedback_count++;
+			if (ua->rate_feedback_count ==
+						ua->playback.queue_length)
+				wake_up(&ua->rate_feedback_wait);
+		} else {
+			/*
+			 * Ring buffer overflow; this happens when the playback
+			 * stream is not running.  Throw away the oldest entry,
+			 * so that the playback stream, when it starts, sees
+			 * the most recent packet sizes.
+			 */
+			add_with_wraparound(ua, &ua->rate_feedback_start, 1);
+		}
+		if (test_bit(USB_PLAYBACK_RUNNING, &ua->states) &&
+		    !list_empty(&ua->ready_playback_urbs))
+			tasklet_schedule(&ua->playback_tasklet);
+	}
+
+	spin_unlock_irqrestore(&ua->lock, flags);
+
+	if (do_period_elapsed)
+		snd_pcm_period_elapsed(stream->substream);
+
+	return;
+
+stream_stopped:
+	abort_usb_playback(ua);
+	abort_usb_capture(ua);
+	abort_alsa_playback(ua);
+	abort_alsa_capture(ua);
+}
+
+static void first_capture_urb_complete(struct urb *urb)
+{
+	struct ua101 *ua = urb->context;
+
+	urb->complete = capture_urb_complete;
+	capture_urb_complete(urb);
+
+	set_bit(CAPTURE_URB_COMPLETED, &ua->states);
+	wake_up(&ua->alsa_capture_wait);
+}
+
+static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream)
+{
+	unsigned int i;
+
+	for (i = 0; i < stream->queue_length; ++i) {
+		int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL);
+		if (err < 0) {
+			dev_err(&ua->dev->dev, "USB request error %d: %s\n",
+				err, usb_error_string(err));
+			return err;
+		}
+	}
+	return 0;
+}
+
+static void kill_stream_urbs(struct ua101_stream *stream)
+{
+	unsigned int i;
+
+	for (i = 0; i < stream->queue_length; ++i)
+		usb_kill_urb(&stream->urbs[i]->urb);
+}
+
+static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index)
+{
+	struct usb_host_interface *alts;
+
+	alts = ua->intf[intf_index]->cur_altsetting;
+	if (alts->desc.bAlternateSetting != 1) {
+		int err = usb_set_interface(ua->dev,
+					    alts->desc.bInterfaceNumber, 1);
+		if (err < 0) {
+			dev_err(&ua->dev->dev,
+				"cannot initialize interface; error %d: %s\n",
+				err, usb_error_string(err));
+			return err;
+		}
+	}
+	return 0;
+}
+
+static void disable_iso_interface(struct ua101 *ua, unsigned int intf_index)
+{
+	struct usb_host_interface *alts;
+
+	alts = ua->intf[intf_index]->cur_altsetting;
+	if (alts->desc.bAlternateSetting != 0) {
+		int err = usb_set_interface(ua->dev,
+					    alts->desc.bInterfaceNumber, 0);
+		if (err < 0 && !test_bit(DISCONNECTED, &ua->states))
+			dev_warn(&ua->dev->dev,
+				 "interface reset failed; error %d: %s\n",
+				 err, usb_error_string(err));
+	}
+}
+
+static void stop_usb_capture(struct ua101 *ua)
+{
+	clear_bit(USB_CAPTURE_RUNNING, &ua->states);
+
+	kill_stream_urbs(&ua->capture);
+
+	disable_iso_interface(ua, INTF_CAPTURE);
+}
+
+static int start_usb_capture(struct ua101 *ua)
+{
+	int err;
+
+	if (test_bit(DISCONNECTED, &ua->states))
+		return -ENODEV;
+
+	if (test_bit(USB_CAPTURE_RUNNING, &ua->states))
+		return 0;
+
+	kill_stream_urbs(&ua->capture);
+
+	err = enable_iso_interface(ua, INTF_CAPTURE);
+	if (err < 0)
+		return err;
+
+	clear_bit(CAPTURE_URB_COMPLETED, &ua->states);
+	ua->capture.urbs[0]->urb.complete = first_capture_urb_complete;
+	ua->rate_feedback_start = 0;
+	ua->rate_feedback_count = 0;
+
+	set_bit(USB_CAPTURE_RUNNING, &ua->states);
+	err = submit_stream_urbs(ua, &ua->capture);
+	if (err < 0)
+		stop_usb_capture(ua);
+	return err;
+}
+
+static void stop_usb_playback(struct ua101 *ua)
+{
+	clear_bit(USB_PLAYBACK_RUNNING, &ua->states);
+
+	kill_stream_urbs(&ua->playback);
+
+	tasklet_kill(&ua->playback_tasklet);
+
+	disable_iso_interface(ua, INTF_PLAYBACK);
+}
+
+static int start_usb_playback(struct ua101 *ua)
+{
+	unsigned int i, frames;
+	struct urb *urb;
+	int err = 0;
+
+	if (test_bit(DISCONNECTED, &ua->states))
+		return -ENODEV;
+
+	if (test_bit(USB_PLAYBACK_RUNNING, &ua->states))
+		return 0;
+
+	kill_stream_urbs(&ua->playback);
+	tasklet_kill(&ua->playback_tasklet);
+
+	err = enable_iso_interface(ua, INTF_PLAYBACK);
+	if (err < 0)
+		return err;
+
+	clear_bit(PLAYBACK_URB_COMPLETED, &ua->states);
+	ua->playback.urbs[0]->urb.complete =
+		first_playback_urb_complete;
+	spin_lock_irq(&ua->lock);
+	INIT_LIST_HEAD(&ua->ready_playback_urbs);
+	spin_unlock_irq(&ua->lock);
+
+	/*
+	 * We submit the initial URBs all at once, so we have to wait for the
+	 * packet size FIFO to be full.
+	 */
+	wait_event(ua->rate_feedback_wait,
+		   ua->rate_feedback_count >= ua->playback.queue_length ||
+		   !test_bit(USB_CAPTURE_RUNNING, &ua->states) ||
+		   test_bit(DISCONNECTED, &ua->states));
+	if (test_bit(DISCONNECTED, &ua->states)) {
+		stop_usb_playback(ua);
+		return -ENODEV;
+	}
+	if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) {
+		stop_usb_playback(ua);
+		return -EIO;
+	}
+
+	for (i = 0; i < ua->playback.queue_length; ++i) {
+		/* all initial URBs contain silence */
+		spin_lock_irq(&ua->lock);
+		frames = ua->rate_feedback[ua->rate_feedback_start];
+		add_with_wraparound(ua, &ua->rate_feedback_start, 1);
+		ua->rate_feedback_count--;
+		spin_unlock_irq(&ua->lock);
+		urb = &ua->playback.urbs[i]->urb;
+		urb->iso_frame_desc[0].length =
+			frames * ua->playback.frame_bytes;
+		memset(urb->transfer_buffer, 0,
+		       urb->iso_frame_desc[0].length);
+	}
+
+	set_bit(USB_PLAYBACK_RUNNING, &ua->states);
+	err = submit_stream_urbs(ua, &ua->playback);
+	if (err < 0)
+		stop_usb_playback(ua);
+	return err;
+}
+
+static void abort_alsa_capture(struct ua101 *ua)
+{
+	if (test_bit(ALSA_CAPTURE_RUNNING, &ua->states))
+		snd_pcm_stop(ua->capture.substream, SNDRV_PCM_STATE_XRUN);
+}
+
+static void abort_alsa_playback(struct ua101 *ua)
+{
+	if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states))
+		snd_pcm_stop(ua->playback.substream, SNDRV_PCM_STATE_XRUN);
+}
+
+static int set_stream_hw(struct ua101 *ua, struct snd_pcm_substream *substream,
+			 unsigned int channels)
+{
+	int err;
+
+	substream->runtime->hw.info =
+		SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BATCH |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_FIFO_IN_FRAMES;
+	substream->runtime->hw.formats = ua->format_bit;
+	substream->runtime->hw.rates = snd_pcm_rate_to_rate_bit(ua->rate);
+	substream->runtime->hw.rate_min = ua->rate;
+	substream->runtime->hw.rate_max = ua->rate;
+	substream->runtime->hw.channels_min = channels;
+	substream->runtime->hw.channels_max = channels;
+	substream->runtime->hw.buffer_bytes_max = 45000 * 1024;
+	substream->runtime->hw.period_bytes_min = 1;
+	substream->runtime->hw.period_bytes_max = UINT_MAX;
+	substream->runtime->hw.periods_min = 2;
+	substream->runtime->hw.periods_max = UINT_MAX;
+	err = snd_pcm_hw_constraint_minmax(substream->runtime,
+					   SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+					   1500000 / ua->packets_per_second,
+					   8192000);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24);
+	return err;
+}
+
+static int capture_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	ua->capture.substream = substream;
+	err = set_stream_hw(ua, substream, ua->capture.channels);
+	if (err < 0)
+		return err;
+	substream->runtime->hw.fifo_size =
+		DIV_ROUND_CLOSEST(ua->rate, ua->packets_per_second);
+	substream->runtime->delay = substream->runtime->hw.fifo_size;
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	if (err >= 0)
+		set_bit(ALSA_CAPTURE_OPEN, &ua->states);
+	mutex_unlock(&ua->mutex);
+	return err;
+}
+
+static int playback_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	ua->playback.substream = substream;
+	err = set_stream_hw(ua, substream, ua->playback.channels);
+	if (err < 0)
+		return err;
+	substream->runtime->hw.fifo_size =
+		DIV_ROUND_CLOSEST(ua->rate * ua->playback.queue_length,
+				  ua->packets_per_second);
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	if (err < 0)
+		goto error;
+	err = start_usb_playback(ua);
+	if (err < 0) {
+		if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states))
+			stop_usb_capture(ua);
+		goto error;
+	}
+	set_bit(ALSA_PLAYBACK_OPEN, &ua->states);
+error:
+	mutex_unlock(&ua->mutex);
+	return err;
+}
+
+static int capture_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+
+	mutex_lock(&ua->mutex);
+	clear_bit(ALSA_CAPTURE_OPEN, &ua->states);
+	if (!test_bit(ALSA_PLAYBACK_OPEN, &ua->states))
+		stop_usb_capture(ua);
+	mutex_unlock(&ua->mutex);
+	return 0;
+}
+
+static int playback_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+
+	mutex_lock(&ua->mutex);
+	stop_usb_playback(ua);
+	clear_bit(ALSA_PLAYBACK_OPEN, &ua->states);
+	if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states))
+		stop_usb_capture(ua);
+	mutex_unlock(&ua->mutex);
+	return 0;
+}
+
+static int capture_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	mutex_unlock(&ua->mutex);
+	if (err < 0)
+		return err;
+
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						params_buffer_bytes(hw_params));
+}
+
+static int playback_pcm_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	if (err >= 0)
+		err = start_usb_playback(ua);
+	mutex_unlock(&ua->mutex);
+	if (err < 0)
+		return err;
+
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						params_buffer_bytes(hw_params));
+}
+
+static int ua101_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int capture_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	mutex_unlock(&ua->mutex);
+	if (err < 0)
+		return err;
+
+	/*
+	 * The EHCI driver schedules the first packet of an iso stream at 10 ms
+	 * in the future, i.e., no data is actually captured for that long.
+	 * Take the wait here so that the stream is known to be actually
+	 * running when the start trigger has been called.
+	 */
+	wait_event(ua->alsa_capture_wait,
+		   test_bit(CAPTURE_URB_COMPLETED, &ua->states) ||
+		   !test_bit(USB_CAPTURE_RUNNING, &ua->states));
+	if (test_bit(DISCONNECTED, &ua->states))
+		return -ENODEV;
+	if (!test_bit(USB_CAPTURE_RUNNING, &ua->states))
+		return -EIO;
+
+	ua->capture.period_pos = 0;
+	ua->capture.buffer_pos = 0;
+	return 0;
+}
+
+static int playback_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct ua101 *ua = substream->private_data;
+	int err;
+
+	mutex_lock(&ua->mutex);
+	err = start_usb_capture(ua);
+	if (err >= 0)
+		err = start_usb_playback(ua);
+	mutex_unlock(&ua->mutex);
+	if (err < 0)
+		return err;
+
+	/* see the comment in capture_pcm_prepare() */
+	wait_event(ua->alsa_playback_wait,
+		   test_bit(PLAYBACK_URB_COMPLETED, &ua->states) ||
+		   !test_bit(USB_PLAYBACK_RUNNING, &ua->states));
+	if (test_bit(DISCONNECTED, &ua->states))
+		return -ENODEV;
+	if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states))
+		return -EIO;
+
+	substream->runtime->delay = 0;
+	ua->playback.period_pos = 0;
+	ua->playback.buffer_pos = 0;
+	return 0;
+}
+
+static int capture_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct ua101 *ua = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (!test_bit(USB_CAPTURE_RUNNING, &ua->states))
+			return -EIO;
+		set_bit(ALSA_CAPTURE_RUNNING, &ua->states);
+		return 0;
+	case SNDRV_PCM_TRIGGER_STOP:
+		clear_bit(ALSA_CAPTURE_RUNNING, &ua->states);
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int playback_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct ua101 *ua = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states))
+			return -EIO;
+		set_bit(ALSA_PLAYBACK_RUNNING, &ua->states);
+		return 0;
+	case SNDRV_PCM_TRIGGER_STOP:
+		clear_bit(ALSA_PLAYBACK_RUNNING, &ua->states);
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static inline snd_pcm_uframes_t ua101_pcm_pointer(struct ua101 *ua,
+						  struct ua101_stream *stream)
+{
+	unsigned long flags;
+	unsigned int pos;
+
+	spin_lock_irqsave(&ua->lock, flags);
+	pos = stream->buffer_pos;
+	spin_unlock_irqrestore(&ua->lock, flags);
+	return pos;
+}
+
+static snd_pcm_uframes_t capture_pcm_pointer(struct snd_pcm_substream *subs)
+{
+	struct ua101 *ua = subs->private_data;
+
+	return ua101_pcm_pointer(ua, &ua->capture);
+}
+
+static snd_pcm_uframes_t playback_pcm_pointer(struct snd_pcm_substream *subs)
+{
+	struct ua101 *ua = subs->private_data;
+
+	return ua101_pcm_pointer(ua, &ua->playback);
+}
+
+static struct snd_pcm_ops capture_pcm_ops = {
+	.open = capture_pcm_open,
+	.close = capture_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = capture_pcm_hw_params,
+	.hw_free = ua101_pcm_hw_free,
+	.prepare = capture_pcm_prepare,
+	.trigger = capture_pcm_trigger,
+	.pointer = capture_pcm_pointer,
+	.page = snd_pcm_lib_get_vmalloc_page,
+	.mmap = snd_pcm_lib_mmap_vmalloc,
+};
+
+static struct snd_pcm_ops playback_pcm_ops = {
+	.open = playback_pcm_open,
+	.close = playback_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = playback_pcm_hw_params,
+	.hw_free = ua101_pcm_hw_free,
+	.prepare = playback_pcm_prepare,
+	.trigger = playback_pcm_trigger,
+	.pointer = playback_pcm_pointer,
+	.page = snd_pcm_lib_get_vmalloc_page,
+	.mmap = snd_pcm_lib_mmap_vmalloc,
+};
+
+static const struct uac_format_type_i_discrete_descriptor *
+find_format_descriptor(struct usb_interface *interface)
+{
+	struct usb_host_interface *alt;
+	u8 *extra;
+	int extralen;
+
+	if (interface->num_altsetting != 2) {
+		dev_err(&interface->dev, "invalid num_altsetting\n");
+		return NULL;
+	}
+
+	alt = &interface->altsetting[0];
+	if (alt->desc.bNumEndpoints != 0) {
+		dev_err(&interface->dev, "invalid bNumEndpoints\n");
+		return NULL;
+	}
+
+	alt = &interface->altsetting[1];
+	if (alt->desc.bNumEndpoints != 1) {
+		dev_err(&interface->dev, "invalid bNumEndpoints\n");
+		return NULL;
+	}
+
+	extra = alt->extra;
+	extralen = alt->extralen;
+	while (extralen >= sizeof(struct usb_descriptor_header)) {
+		struct uac_format_type_i_discrete_descriptor *desc;
+
+		desc = (struct uac_format_type_i_discrete_descriptor *)extra;
+		if (desc->bLength > extralen) {
+			dev_err(&interface->dev, "descriptor overflow\n");
+			return NULL;
+		}
+		if (desc->bLength == UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1) &&
+		    desc->bDescriptorType == USB_DT_CS_INTERFACE &&
+		    desc->bDescriptorSubtype == UAC_FORMAT_TYPE) {
+			if (desc->bFormatType != UAC_FORMAT_TYPE_I_PCM ||
+			    desc->bSamFreqType != 1) {
+				dev_err(&interface->dev,
+					"invalid format type\n");
+				return NULL;
+			}
+			return desc;
+		}
+		extralen -= desc->bLength;
+		extra += desc->bLength;
+	}
+	dev_err(&interface->dev, "sample format descriptor not found\n");
+	return NULL;
+}
+
+static int detect_usb_format(struct ua101 *ua)
+{
+	const struct uac_format_type_i_discrete_descriptor *fmt_capture;
+	const struct uac_format_type_i_discrete_descriptor *fmt_playback;
+	const struct usb_endpoint_descriptor *epd;
+	unsigned int rate2;
+
+	fmt_capture = find_format_descriptor(ua->intf[INTF_CAPTURE]);
+	fmt_playback = find_format_descriptor(ua->intf[INTF_PLAYBACK]);
+	if (!fmt_capture || !fmt_playback)
+		return -ENXIO;
+
+	switch (fmt_capture->bSubframeSize) {
+	case 3:
+		ua->format_bit = SNDRV_PCM_FMTBIT_S24_3LE;
+		break;
+	case 4:
+		ua->format_bit = SNDRV_PCM_FMTBIT_S32_LE;
+		break;
+	default:
+		dev_err(&ua->dev->dev, "sample width is not 24 or 32 bits\n");
+		return -ENXIO;
+	}
+	if (fmt_capture->bSubframeSize != fmt_playback->bSubframeSize) {
+		dev_err(&ua->dev->dev,
+			"playback/capture sample widths do not match\n");
+		return -ENXIO;
+	}
+
+	if (fmt_capture->bBitResolution != 24 ||
+	    fmt_playback->bBitResolution != 24) {
+		dev_err(&ua->dev->dev, "sample width is not 24 bits\n");
+		return -ENXIO;
+	}
+
+	ua->rate = combine_triple(fmt_capture->tSamFreq[0]);
+	rate2 = combine_triple(fmt_playback->tSamFreq[0]);
+	if (ua->rate != rate2) {
+		dev_err(&ua->dev->dev,
+			"playback/capture rates do not match: %u/%u\n",
+			rate2, ua->rate);
+		return -ENXIO;
+	}
+
+	switch (ua->dev->speed) {
+	case USB_SPEED_FULL:
+		ua->packets_per_second = 1000;
+		break;
+	case USB_SPEED_HIGH:
+		ua->packets_per_second = 8000;
+		break;
+	default:
+		dev_err(&ua->dev->dev, "unknown device speed\n");
+		return -ENXIO;
+	}
+
+	ua->capture.channels = fmt_capture->bNrChannels;
+	ua->playback.channels = fmt_playback->bNrChannels;
+	ua->capture.frame_bytes =
+		fmt_capture->bSubframeSize * ua->capture.channels;
+	ua->playback.frame_bytes =
+		fmt_playback->bSubframeSize * ua->playback.channels;
+
+	epd = &ua->intf[INTF_CAPTURE]->altsetting[1].endpoint[0].desc;
+	if (!usb_endpoint_is_isoc_in(epd)) {
+		dev_err(&ua->dev->dev, "invalid capture endpoint\n");
+		return -ENXIO;
+	}
+	ua->capture.usb_pipe = usb_rcvisocpipe(ua->dev, usb_endpoint_num(epd));
+	ua->capture.max_packet_bytes = le16_to_cpu(epd->wMaxPacketSize);
+
+	epd = &ua->intf[INTF_PLAYBACK]->altsetting[1].endpoint[0].desc;
+	if (!usb_endpoint_is_isoc_out(epd)) {
+		dev_err(&ua->dev->dev, "invalid playback endpoint\n");
+		return -ENXIO;
+	}
+	ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd));
+	ua->playback.max_packet_bytes = le16_to_cpu(epd->wMaxPacketSize);
+	return 0;
+}
+
+static int alloc_stream_buffers(struct ua101 *ua, struct ua101_stream *stream)
+{
+	unsigned int remaining_packets, packets, packets_per_page, i;
+	size_t size;
+
+	stream->queue_length = queue_length;
+	stream->queue_length = max(stream->queue_length,
+				   (unsigned int)MIN_QUEUE_LENGTH);
+	stream->queue_length = min(stream->queue_length,
+				   (unsigned int)MAX_QUEUE_LENGTH);
+
+	/*
+	 * The cache pool sizes used by usb_alloc_coherent() (128, 512, 2048) are
+	 * quite bad when used with the packet sizes of this device (e.g. 280,
+	 * 520, 624).  Therefore, we allocate and subdivide entire pages, using
+	 * a smaller buffer only for the last chunk.
+	 */
+	remaining_packets = stream->queue_length;
+	packets_per_page = PAGE_SIZE / stream->max_packet_bytes;
+	for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i) {
+		packets = min(remaining_packets, packets_per_page);
+		size = packets * stream->max_packet_bytes;
+		stream->buffers[i].addr =
+			usb_alloc_coherent(ua->dev, size, GFP_KERNEL,
+					   &stream->buffers[i].dma);
+		if (!stream->buffers[i].addr)
+			return -ENOMEM;
+		stream->buffers[i].size = size;
+		remaining_packets -= packets;
+		if (!remaining_packets)
+			break;
+	}
+	if (remaining_packets) {
+		dev_err(&ua->dev->dev, "too many packets\n");
+		return -ENXIO;
+	}
+	return 0;
+}
+
+static void free_stream_buffers(struct ua101 *ua, struct ua101_stream *stream)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i)
+		usb_free_coherent(ua->dev,
+				  stream->buffers[i].size,
+				  stream->buffers[i].addr,
+				  stream->buffers[i].dma);
+}
+
+static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream,
+			     void (*urb_complete)(struct urb *))
+{
+	unsigned max_packet_size = stream->max_packet_bytes;
+	struct ua101_urb *urb;
+	unsigned int b, u = 0;
+
+	for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) {
+		unsigned int size = stream->buffers[b].size;
+		u8 *addr = stream->buffers[b].addr;
+		dma_addr_t dma = stream->buffers[b].dma;
+
+		while (size >= max_packet_size) {
+			if (u >= stream->queue_length)
+				goto bufsize_error;
+			urb = kmalloc(sizeof(*urb), GFP_KERNEL);
+			if (!urb)
+				return -ENOMEM;
+			usb_init_urb(&urb->urb);
+			urb->urb.dev = ua->dev;
+			urb->urb.pipe = stream->usb_pipe;
+			urb->urb.transfer_flags = URB_ISO_ASAP |
+					URB_NO_TRANSFER_DMA_MAP;
+			urb->urb.transfer_buffer = addr;
+			urb->urb.transfer_dma = dma;
+			urb->urb.transfer_buffer_length = max_packet_size;
+			urb->urb.number_of_packets = 1;
+			urb->urb.interval = 1;
+			urb->urb.context = ua;
+			urb->urb.complete = urb_complete;
+			urb->urb.iso_frame_desc[0].offset = 0;
+			urb->urb.iso_frame_desc[0].length = max_packet_size;
+			stream->urbs[u++] = urb;
+			size -= max_packet_size;
+			addr += max_packet_size;
+			dma += max_packet_size;
+		}
+	}
+	if (u == stream->queue_length)
+		return 0;
+bufsize_error:
+	dev_err(&ua->dev->dev, "internal buffer size error\n");
+	return -ENXIO;
+}
+
+static void free_stream_urbs(struct ua101_stream *stream)
+{
+	unsigned int i;
+
+	for (i = 0; i < stream->queue_length; ++i)
+		kfree(stream->urbs[i]);
+}
+
+static void free_usb_related_resources(struct ua101 *ua,
+				       struct usb_interface *interface)
+{
+	unsigned int i;
+
+	free_stream_urbs(&ua->capture);
+	free_stream_urbs(&ua->playback);
+	free_stream_buffers(ua, &ua->capture);
+	free_stream_buffers(ua, &ua->playback);
+
+	for (i = 0; i < ARRAY_SIZE(ua->intf); ++i)
+		if (ua->intf[i]) {
+			usb_set_intfdata(ua->intf[i], NULL);
+			if (ua->intf[i] != interface)
+				usb_driver_release_interface(&ua101_driver,
+							     ua->intf[i]);
+		}
+}
+
+static void ua101_card_free(struct snd_card *card)
+{
+	struct ua101 *ua = card->private_data;
+
+	mutex_destroy(&ua->mutex);
+}
+
+static int ua101_probe(struct usb_interface *interface,
+		       const struct usb_device_id *usb_id)
+{
+	static const struct snd_usb_midi_endpoint_info midi_ep = {
+		.out_cables = 0x0001,
+		.in_cables = 0x0001
+	};
+	static const struct snd_usb_audio_quirk midi_quirk = {
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = &midi_ep
+	};
+	static const int intf_numbers[2][3] = {
+		{	/* UA-101 */
+			[INTF_PLAYBACK] = 0,
+			[INTF_CAPTURE] = 1,
+			[INTF_MIDI] = 2,
+		},
+		{	/* UA-1000 */
+			[INTF_CAPTURE] = 1,
+			[INTF_PLAYBACK] = 2,
+			[INTF_MIDI] = 3,
+		},
+	};
+	struct snd_card *card;
+	struct ua101 *ua;
+	unsigned int card_index, i;
+	int is_ua1000;
+	const char *name;
+	char usb_path[32];
+	int err;
+
+	is_ua1000 = usb_id->idProduct == 0x0044;
+
+	if (interface->altsetting->desc.bInterfaceNumber !=
+	    intf_numbers[is_ua1000][0])
+		return -ENODEV;
+
+	mutex_lock(&devices_mutex);
+
+	for (card_index = 0; card_index < SNDRV_CARDS; ++card_index)
+		if (enable[card_index] && !(devices_used & (1 << card_index)))
+			break;
+	if (card_index >= SNDRV_CARDS) {
+		mutex_unlock(&devices_mutex);
+		return -ENOENT;
+	}
+	err = snd_card_create(index[card_index], id[card_index], THIS_MODULE,
+			      sizeof(*ua), &card);
+	if (err < 0) {
+		mutex_unlock(&devices_mutex);
+		return err;
+	}
+	card->private_free = ua101_card_free;
+	ua = card->private_data;
+	ua->dev = interface_to_usbdev(interface);
+	ua->card = card;
+	ua->card_index = card_index;
+	INIT_LIST_HEAD(&ua->midi_list);
+	spin_lock_init(&ua->lock);
+	mutex_init(&ua->mutex);
+	INIT_LIST_HEAD(&ua->ready_playback_urbs);
+	tasklet_init(&ua->playback_tasklet,
+		     playback_tasklet, (unsigned long)ua);
+	init_waitqueue_head(&ua->alsa_capture_wait);
+	init_waitqueue_head(&ua->rate_feedback_wait);
+	init_waitqueue_head(&ua->alsa_playback_wait);
+
+	ua->intf[0] = interface;
+	for (i = 1; i < ARRAY_SIZE(ua->intf); ++i) {
+		ua->intf[i] = usb_ifnum_to_if(ua->dev,
+					      intf_numbers[is_ua1000][i]);
+		if (!ua->intf[i]) {
+			dev_err(&ua->dev->dev, "interface %u not found\n",
+				intf_numbers[is_ua1000][i]);
+			err = -ENXIO;
+			goto probe_error;
+		}
+		err = usb_driver_claim_interface(&ua101_driver,
+						 ua->intf[i], ua);
+		if (err < 0) {
+			ua->intf[i] = NULL;
+			err = -EBUSY;
+			goto probe_error;
+		}
+	}
+
+	snd_card_set_dev(card, &interface->dev);
+
+	err = detect_usb_format(ua);
+	if (err < 0)
+		goto probe_error;
+
+	name = usb_id->idProduct == 0x0044 ? "UA-1000" : "UA-101";
+	strcpy(card->driver, "UA-101");
+	strcpy(card->shortname, name);
+	usb_make_path(ua->dev, usb_path, sizeof(usb_path));
+	snprintf(ua->card->longname, sizeof(ua->card->longname),
+		 "EDIROL %s (serial %s), %u Hz at %s, %s speed", name,
+		 ua->dev->serial ? ua->dev->serial : "?", ua->rate, usb_path,
+		 ua->dev->speed == USB_SPEED_HIGH ? "high" : "full");
+
+	err = alloc_stream_buffers(ua, &ua->capture);
+	if (err < 0)
+		goto probe_error;
+	err = alloc_stream_buffers(ua, &ua->playback);
+	if (err < 0)
+		goto probe_error;
+
+	err = alloc_stream_urbs(ua, &ua->capture, capture_urb_complete);
+	if (err < 0)
+		goto probe_error;
+	err = alloc_stream_urbs(ua, &ua->playback, playback_urb_complete);
+	if (err < 0)
+		goto probe_error;
+
+	err = snd_pcm_new(card, name, 0, 1, 1, &ua->pcm);
+	if (err < 0)
+		goto probe_error;
+	ua->pcm->private_data = ua;
+	strcpy(ua->pcm->name, name);
+	snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_pcm_ops);
+	snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_pcm_ops);
+
+	err = snd_usbmidi_create(card, ua->intf[INTF_MIDI],
+				 &ua->midi_list, &midi_quirk);
+	if (err < 0)
+		goto probe_error;
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto probe_error;
+
+	usb_set_intfdata(interface, ua);
+	devices_used |= 1 << card_index;
+
+	mutex_unlock(&devices_mutex);
+	return 0;
+
+probe_error:
+	free_usb_related_resources(ua, interface);
+	snd_card_free(card);
+	mutex_unlock(&devices_mutex);
+	return err;
+}
+
+static void ua101_disconnect(struct usb_interface *interface)
+{
+	struct ua101 *ua = usb_get_intfdata(interface);
+	struct list_head *midi;
+
+	if (!ua)
+		return;
+
+	mutex_lock(&devices_mutex);
+
+	set_bit(DISCONNECTED, &ua->states);
+	wake_up(&ua->rate_feedback_wait);
+
+	/* make sure that userspace cannot create new requests */
+	snd_card_disconnect(ua->card);
+
+	/* make sure that there are no pending USB requests */
+	__list_for_each(midi, &ua->midi_list)
+		snd_usbmidi_disconnect(midi);
+	abort_alsa_playback(ua);
+	abort_alsa_capture(ua);
+	mutex_lock(&ua->mutex);
+	stop_usb_playback(ua);
+	stop_usb_capture(ua);
+	mutex_unlock(&ua->mutex);
+
+	free_usb_related_resources(ua, interface);
+
+	devices_used &= ~(1 << ua->card_index);
+
+	snd_card_free_when_closed(ua->card);
+
+	mutex_unlock(&devices_mutex);
+}
+
+static struct usb_device_id ua101_ids[] = {
+	{ USB_DEVICE(0x0582, 0x0044) }, /* UA-1000 high speed */
+	{ USB_DEVICE(0x0582, 0x007d) }, /* UA-101 high speed */
+	{ USB_DEVICE(0x0582, 0x008d) }, /* UA-101 full speed */
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, ua101_ids);
+
+static struct usb_driver ua101_driver = {
+	.name = "snd-ua101",
+	.id_table = ua101_ids,
+	.probe = ua101_probe,
+	.disconnect = ua101_disconnect,
+#if 0
+	.suspend = ua101_suspend,
+	.resume = ua101_resume,
+#endif
+};
+
+static int __init alsa_card_ua101_init(void)
+{
+	return usb_register(&ua101_driver);
+}
+
+static void __exit alsa_card_ua101_exit(void)
+{
+	usb_deregister(&ua101_driver);
+	mutex_destroy(&devices_mutex);
+}
+
+module_init(alsa_card_ua101_init);
+module_exit(alsa_card_ua101_exit);
diff --git a/linux/sound/usb/mixer.c b/linux/sound/usb/mixer.c
new file mode 100644
index 0000000..f2d74d6
--- /dev/null
+++ b/linux/sound/usb/mixer.c
@@ -0,0 +1,2216 @@
+/*
+ *   (Tentative) USB Audio Driver for ALSA
+ *
+ *   Mixer control part
+ *
+ *   Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   Many codes borrowed from audio.c by
+ *	    Alan Cox (alan@lxorguk.ukuu.org.uk)
+ *	    Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ *
+ *   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
+ *
+ */
+
+/*
+ * TODOs, for both the mixer and the streaming interfaces:
+ *
+ *  - support for UAC2 effect units
+ *  - support for graphical equalizers
+ *  - RANGE and MEM set commands (UAC2)
+ *  - RANGE and MEM interrupt dispatchers (UAC2)
+ *  - audio channel clustering (UAC2)
+ *  - audio sample rate converter units (UAC2)
+ *  - proper handling of clock multipliers (UAC2)
+ *  - dispatch clock change notifications (UAC2)
+ *  	- stop PCM streams which use a clock that became invalid
+ *  	- stop PCM streams which use a clock selector that has changed
+ *  	- parse available sample rates again when clock sources changed
+ */
+
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/hwdep.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "helper.h"
+#include "mixer_quirks.h"
+
+#define MAX_ID_ELEMS	256
+
+struct usb_audio_term {
+	int id;
+	int type;
+	int channels;
+	unsigned int chconfig;
+	int name;
+};
+
+struct usbmix_name_map;
+
+struct mixer_build {
+	struct snd_usb_audio *chip;
+	struct usb_mixer_interface *mixer;
+	unsigned char *buffer;
+	unsigned int buflen;
+	DECLARE_BITMAP(unitbitmap, MAX_ID_ELEMS);
+	struct usb_audio_term oterm;
+	const struct usbmix_name_map *map;
+	const struct usbmix_selector_map *selector_map;
+};
+
+enum {
+	USB_MIXER_BOOLEAN,
+	USB_MIXER_INV_BOOLEAN,
+	USB_MIXER_S8,
+	USB_MIXER_U8,
+	USB_MIXER_S16,
+	USB_MIXER_U16,
+};
+
+
+/*E-mu 0202(0404) eXtension Unit(XU) control*/
+enum {
+	USB_XU_CLOCK_RATE 		= 0xe301,
+	USB_XU_CLOCK_SOURCE		= 0xe302,
+	USB_XU_DIGITAL_IO_STATUS	= 0xe303,
+	USB_XU_DEVICE_OPTIONS		= 0xe304,
+	USB_XU_DIRECT_MONITORING	= 0xe305,
+	USB_XU_METERING			= 0xe306
+};
+enum {
+	USB_XU_CLOCK_SOURCE_SELECTOR = 0x02,	/* clock source*/
+	USB_XU_CLOCK_RATE_SELECTOR = 0x03,	/* clock rate */
+	USB_XU_DIGITAL_FORMAT_SELECTOR = 0x01,	/* the spdif format */
+	USB_XU_SOFT_LIMIT_SELECTOR = 0x03	/* soft limiter */
+};
+
+/*
+ * manual mapping of mixer names
+ * if the mixer topology is too complicated and the parsed names are
+ * ambiguous, add the entries in usbmixer_maps.c.
+ */
+#include "mixer_maps.c"
+
+static const struct usbmix_name_map *
+find_map(struct mixer_build *state, int unitid, int control)
+{
+	const struct usbmix_name_map *p = state->map;
+
+	if (!p)
+		return NULL;
+
+	for (p = state->map; p->id; p++) {
+		if (p->id == unitid &&
+		    (!control || !p->control || control == p->control))
+			return p;
+	}
+	return NULL;
+}
+
+/* get the mapped name if the unit matches */
+static int
+check_mapped_name(const struct usbmix_name_map *p, char *buf, int buflen)
+{
+	if (!p || !p->name)
+		return 0;
+
+	buflen--;
+	return strlcpy(buf, p->name, buflen);
+}
+
+/* check whether the control should be ignored */
+static inline int
+check_ignored_ctl(const struct usbmix_name_map *p)
+{
+	if (!p || p->name || p->dB)
+		return 0;
+	return 1;
+}
+
+/* dB mapping */
+static inline void check_mapped_dB(const struct usbmix_name_map *p,
+				   struct usb_mixer_elem_info *cval)
+{
+	if (p && p->dB) {
+		cval->dBmin = p->dB->min;
+		cval->dBmax = p->dB->max;
+	}
+}
+
+/* get the mapped selector source name */
+static int check_mapped_selector_name(struct mixer_build *state, int unitid,
+				      int index, char *buf, int buflen)
+{
+	const struct usbmix_selector_map *p;
+
+	if (! state->selector_map)
+		return 0;
+	for (p = state->selector_map; p->id; p++) {
+		if (p->id == unitid && index < p->count)
+			return strlcpy(buf, p->names[index], buflen);
+	}
+	return 0;
+}
+
+/*
+ * find an audio control unit with the given unit id
+ */
+static void *find_audio_control_unit(struct mixer_build *state, unsigned char unit)
+{
+	/* we just parse the header */
+	struct uac_feature_unit_descriptor *hdr = NULL;
+
+	while ((hdr = snd_usb_find_desc(state->buffer, state->buflen, hdr,
+					USB_DT_CS_INTERFACE)) != NULL) {
+		if (hdr->bLength >= 4 &&
+		    hdr->bDescriptorSubtype >= UAC_INPUT_TERMINAL &&
+		    hdr->bDescriptorSubtype <= UAC2_SAMPLE_RATE_CONVERTER &&
+		    hdr->bUnitID == unit)
+			return hdr;
+	}
+
+	return NULL;
+}
+
+/*
+ * copy a string with the given id
+ */
+static int snd_usb_copy_string_desc(struct mixer_build *state, int index, char *buf, int maxlen)
+{
+	int len = usb_string(state->chip->dev, index, buf, maxlen - 1);
+	buf[len] = 0;
+	return len;
+}
+
+/*
+ * convert from the byte/word on usb descriptor to the zero-based integer
+ */
+static int convert_signed_value(struct usb_mixer_elem_info *cval, int val)
+{
+	switch (cval->val_type) {
+	case USB_MIXER_BOOLEAN:
+		return !!val;
+	case USB_MIXER_INV_BOOLEAN:
+		return !val;
+	case USB_MIXER_U8:
+		val &= 0xff;
+		break;
+	case USB_MIXER_S8:
+		val &= 0xff;
+		if (val >= 0x80)
+			val -= 0x100;
+		break;
+	case USB_MIXER_U16:
+		val &= 0xffff;
+		break;
+	case USB_MIXER_S16:
+		val &= 0xffff;
+		if (val >= 0x8000)
+			val -= 0x10000;
+		break;
+	}
+	return val;
+}
+
+/*
+ * convert from the zero-based int to the byte/word for usb descriptor
+ */
+static int convert_bytes_value(struct usb_mixer_elem_info *cval, int val)
+{
+	switch (cval->val_type) {
+	case USB_MIXER_BOOLEAN:
+		return !!val;
+	case USB_MIXER_INV_BOOLEAN:
+		return !val;
+	case USB_MIXER_S8:
+	case USB_MIXER_U8:
+		return val & 0xff;
+	case USB_MIXER_S16:
+	case USB_MIXER_U16:
+		return val & 0xffff;
+	}
+	return 0; /* not reached */
+}
+
+static int get_relative_value(struct usb_mixer_elem_info *cval, int val)
+{
+	if (! cval->res)
+		cval->res = 1;
+	if (val < cval->min)
+		return 0;
+	else if (val >= cval->max)
+		return (cval->max - cval->min + cval->res - 1) / cval->res;
+	else
+		return (val - cval->min) / cval->res;
+}
+
+static int get_abs_value(struct usb_mixer_elem_info *cval, int val)
+{
+	if (val < 0)
+		return cval->min;
+	if (! cval->res)
+		cval->res = 1;
+	val *= cval->res;
+	val += cval->min;
+	if (val > cval->max)
+		return cval->max;
+	return val;
+}
+
+
+/*
+ * retrieve a mixer value
+ */
+
+static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
+{
+	struct snd_usb_audio *chip = cval->mixer->chip;
+	unsigned char buf[2];
+	int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
+	int timeout = 10;
+
+	while (timeout-- > 0) {
+		if (snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request,
+				    USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+				    validx, snd_usb_ctrl_intf(chip) | (cval->id << 8),
+				    buf, val_len, 100) >= val_len) {
+			*value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len));
+			return 0;
+		}
+	}
+	snd_printdd(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
+		    request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type);
+	return -EINVAL;
+}
+
+static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
+{
+	struct snd_usb_audio *chip = cval->mixer->chip;
+	unsigned char buf[2 + 3*sizeof(__u16)]; /* enough space for one range */
+	unsigned char *val;
+	int ret, size;
+	__u8 bRequest;
+
+	if (request == UAC_GET_CUR) {
+		bRequest = UAC2_CS_CUR;
+		size = sizeof(__u16);
+	} else {
+		bRequest = UAC2_CS_RANGE;
+		size = sizeof(buf);
+	}
+
+	memset(buf, 0, sizeof(buf));
+
+	ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
+			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+			      validx, snd_usb_ctrl_intf(chip) | (cval->id << 8),
+			      buf, size, 1000);
+
+	if (ret < 0) {
+		snd_printk(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
+			   request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type);
+		return ret;
+	}
+
+	/* FIXME: how should we handle multiple triplets here? */
+
+	switch (request) {
+	case UAC_GET_CUR:
+		val = buf;
+		break;
+	case UAC_GET_MIN:
+		val = buf + sizeof(__u16);
+		break;
+	case UAC_GET_MAX:
+		val = buf + sizeof(__u16) * 2;
+		break;
+	case UAC_GET_RES:
+		val = buf + sizeof(__u16) * 3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	*value_ret = convert_signed_value(cval, snd_usb_combine_bytes(val, sizeof(__u16)));
+
+	return 0;
+}
+
+static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret)
+{
+	return (cval->mixer->protocol == UAC_VERSION_1) ?
+		get_ctl_value_v1(cval, request, validx, value_ret) :
+		get_ctl_value_v2(cval, request, validx, value_ret);
+}
+
+static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int *value)
+{
+	return get_ctl_value(cval, UAC_GET_CUR, validx, value);
+}
+
+/* channel = 0: master, 1 = first channel */
+static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval,
+				  int channel, int *value)
+{
+	return get_ctl_value(cval, UAC_GET_CUR, (cval->control << 8) | channel, value);
+}
+
+static int get_cur_mix_value(struct usb_mixer_elem_info *cval,
+			     int channel, int index, int *value)
+{
+	int err;
+
+	if (cval->cached & (1 << channel)) {
+		*value = cval->cache_val[index];
+		return 0;
+	}
+	err = get_cur_mix_raw(cval, channel, value);
+	if (err < 0) {
+		if (!cval->mixer->ignore_ctl_error)
+			snd_printd(KERN_ERR "cannot get current value for control %d ch %d: err = %d\n",
+				   cval->control, channel, err);
+		return err;
+	}
+	cval->cached |= 1 << channel;
+	cval->cache_val[index] = *value;
+	return 0;
+}
+
+
+/*
+ * set a mixer value
+ */
+
+int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
+				int request, int validx, int value_set)
+{
+	struct snd_usb_audio *chip = cval->mixer->chip;
+	unsigned char buf[2];
+	int val_len, timeout = 10;
+
+	if (cval->mixer->protocol == UAC_VERSION_1) {
+		val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
+	} else { /* UAC_VERSION_2 */
+		/* audio class v2 controls are always 2 bytes in size */
+		val_len = sizeof(__u16);
+
+		/* FIXME */
+		if (request != UAC_SET_CUR) {
+			snd_printdd(KERN_WARNING "RANGE setting not yet supported\n");
+			return -EINVAL;
+		}
+
+		request = UAC2_CS_CUR;
+	}
+
+	value_set = convert_bytes_value(cval, value_set);
+	buf[0] = value_set & 0xff;
+	buf[1] = (value_set >> 8) & 0xff;
+	while (timeout-- > 0)
+		if (snd_usb_ctl_msg(chip->dev,
+				    usb_sndctrlpipe(chip->dev, 0), request,
+				    USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+				    validx, snd_usb_ctrl_intf(chip) | (cval->id << 8),
+				    buf, val_len, 100) >= 0)
+			return 0;
+	snd_printdd(KERN_ERR "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n",
+		    request, validx, snd_usb_ctrl_intf(chip) | (cval->id << 8), cval->val_type, buf[0], buf[1]);
+	return -EINVAL;
+}
+
+static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int value)
+{
+	return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value);
+}
+
+static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel,
+			     int index, int value)
+{
+	int err;
+	unsigned int read_only = (channel == 0) ?
+		cval->master_readonly :
+		cval->ch_readonly & (1 << (channel - 1));
+
+	if (read_only) {
+		snd_printdd(KERN_INFO "%s(): channel %d of control %d is read_only\n",
+			    __func__, channel, cval->control);
+		return 0;
+	}
+
+	err = snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel,
+			    value);
+	if (err < 0)
+		return err;
+	cval->cached |= 1 << channel;
+	cval->cache_val[index] = value;
+	return 0;
+}
+
+/*
+ * TLV callback for mixer volume controls
+ */
+static int mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+			 unsigned int size, unsigned int __user *_tlv)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	DECLARE_TLV_DB_MINMAX(scale, 0, 0);
+
+	if (size < sizeof(scale))
+		return -ENOMEM;
+	scale[2] = cval->dBmin;
+	scale[3] = cval->dBmax;
+	if (copy_to_user(_tlv, scale, sizeof(scale)))
+		return -EFAULT;
+	return 0;
+}
+
+/*
+ * parser routines begin here...
+ */
+
+static int parse_audio_unit(struct mixer_build *state, int unitid);
+
+
+/*
+ * check if the input/output channel routing is enabled on the given bitmap.
+ * used for mixer unit parser
+ */
+static int check_matrix_bitmap(unsigned char *bmap, int ich, int och, int num_outs)
+{
+	int idx = ich * num_outs + och;
+	return bmap[idx >> 3] & (0x80 >> (idx & 7));
+}
+
+
+/*
+ * add an alsa control element
+ * search and increment the index until an empty slot is found.
+ *
+ * if failed, give up and free the control instance.
+ */
+
+static int add_control_to_empty(struct mixer_build *state, struct snd_kcontrol *kctl)
+{
+	struct usb_mixer_elem_info *cval = kctl->private_data;
+	int err;
+
+	while (snd_ctl_find_id(state->chip->card, &kctl->id))
+		kctl->id.index++;
+	if ((err = snd_ctl_add(state->chip->card, kctl)) < 0) {
+		snd_printd(KERN_ERR "cannot add control (err = %d)\n", err);
+		return err;
+	}
+	cval->elem_id = &kctl->id;
+	cval->next_id_elem = state->mixer->id_elems[cval->id];
+	state->mixer->id_elems[cval->id] = cval;
+	return 0;
+}
+
+
+/*
+ * get a terminal name string
+ */
+
+static struct iterm_name_combo {
+	int type;
+	char *name;
+} iterm_names[] = {
+	{ 0x0300, "Output" },
+	{ 0x0301, "Speaker" },
+	{ 0x0302, "Headphone" },
+	{ 0x0303, "HMD Audio" },
+	{ 0x0304, "Desktop Speaker" },
+	{ 0x0305, "Room Speaker" },
+	{ 0x0306, "Com Speaker" },
+	{ 0x0307, "LFE" },
+	{ 0x0600, "External In" },
+	{ 0x0601, "Analog In" },
+	{ 0x0602, "Digital In" },
+	{ 0x0603, "Line" },
+	{ 0x0604, "Legacy In" },
+	{ 0x0605, "IEC958 In" },
+	{ 0x0606, "1394 DA Stream" },
+	{ 0x0607, "1394 DV Stream" },
+	{ 0x0700, "Embedded" },
+	{ 0x0701, "Noise Source" },
+	{ 0x0702, "Equalization Noise" },
+	{ 0x0703, "CD" },
+	{ 0x0704, "DAT" },
+	{ 0x0705, "DCC" },
+	{ 0x0706, "MiniDisk" },
+	{ 0x0707, "Analog Tape" },
+	{ 0x0708, "Phonograph" },
+	{ 0x0709, "VCR Audio" },
+	{ 0x070a, "Video Disk Audio" },
+	{ 0x070b, "DVD Audio" },
+	{ 0x070c, "TV Tuner Audio" },
+	{ 0x070d, "Satellite Rec Audio" },
+	{ 0x070e, "Cable Tuner Audio" },
+	{ 0x070f, "DSS Audio" },
+	{ 0x0710, "Radio Receiver" },
+	{ 0x0711, "Radio Transmitter" },
+	{ 0x0712, "Multi-Track Recorder" },
+	{ 0x0713, "Synthesizer" },
+	{ 0 },
+};
+
+static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm,
+			 unsigned char *name, int maxlen, int term_only)
+{
+	struct iterm_name_combo *names;
+
+	if (iterm->name)
+		return snd_usb_copy_string_desc(state, iterm->name, name, maxlen);
+
+	/* virtual type - not a real terminal */
+	if (iterm->type >> 16) {
+		if (term_only)
+			return 0;
+		switch (iterm->type >> 16) {
+		case UAC_SELECTOR_UNIT:
+			strcpy(name, "Selector"); return 8;
+		case UAC1_PROCESSING_UNIT:
+			strcpy(name, "Process Unit"); return 12;
+		case UAC1_EXTENSION_UNIT:
+			strcpy(name, "Ext Unit"); return 8;
+		case UAC_MIXER_UNIT:
+			strcpy(name, "Mixer"); return 5;
+		default:
+			return sprintf(name, "Unit %d", iterm->id);
+		}
+	}
+
+	switch (iterm->type & 0xff00) {
+	case 0x0100:
+		strcpy(name, "PCM"); return 3;
+	case 0x0200:
+		strcpy(name, "Mic"); return 3;
+	case 0x0400:
+		strcpy(name, "Headset"); return 7;
+	case 0x0500:
+		strcpy(name, "Phone"); return 5;
+	}
+
+	for (names = iterm_names; names->type; names++)
+		if (names->type == iterm->type) {
+			strcpy(name, names->name);
+			return strlen(names->name);
+		}
+	return 0;
+}
+
+
+/*
+ * parse the source unit recursively until it reaches to a terminal
+ * or a branched unit.
+ */
+static int check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term)
+{
+	int err;
+	void *p1;
+
+	memset(term, 0, sizeof(*term));
+	while ((p1 = find_audio_control_unit(state, id)) != NULL) {
+		unsigned char *hdr = p1;
+		term->id = id;
+		switch (hdr[2]) {
+		case UAC_INPUT_TERMINAL:
+			if (state->mixer->protocol == UAC_VERSION_1) {
+				struct uac_input_terminal_descriptor *d = p1;
+				term->type = le16_to_cpu(d->wTerminalType);
+				term->channels = d->bNrChannels;
+				term->chconfig = le16_to_cpu(d->wChannelConfig);
+				term->name = d->iTerminal;
+			} else { /* UAC_VERSION_2 */
+				struct uac2_input_terminal_descriptor *d = p1;
+				term->type = le16_to_cpu(d->wTerminalType);
+				term->channels = d->bNrChannels;
+				term->chconfig = le32_to_cpu(d->bmChannelConfig);
+				term->name = d->iTerminal;
+
+				/* call recursively to get the clock selectors */
+				err = check_input_term(state, d->bCSourceID, term);
+				if (err < 0)
+					return err;
+			}
+			return 0;
+		case UAC_FEATURE_UNIT: {
+			/* the header is the same for v1 and v2 */
+			struct uac_feature_unit_descriptor *d = p1;
+			id = d->bSourceID;
+			break; /* continue to parse */
+		}
+		case UAC_MIXER_UNIT: {
+			struct uac_mixer_unit_descriptor *d = p1;
+			term->type = d->bDescriptorSubtype << 16; /* virtual type */
+			term->channels = uac_mixer_unit_bNrChannels(d);
+			term->chconfig = uac_mixer_unit_wChannelConfig(d, state->mixer->protocol);
+			term->name = uac_mixer_unit_iMixer(d);
+			return 0;
+		}
+		case UAC_SELECTOR_UNIT:
+		case UAC2_CLOCK_SELECTOR: {
+			struct uac_selector_unit_descriptor *d = p1;
+			/* call recursively to retrieve the channel info */
+			if (check_input_term(state, d->baSourceID[0], term) < 0)
+				return -ENODEV;
+			term->type = d->bDescriptorSubtype << 16; /* virtual type */
+			term->id = id;
+			term->name = uac_selector_unit_iSelector(d);
+			return 0;
+		}
+		case UAC1_PROCESSING_UNIT:
+		case UAC1_EXTENSION_UNIT: {
+			struct uac_processing_unit_descriptor *d = p1;
+			if (d->bNrInPins) {
+				id = d->baSourceID[0];
+				break; /* continue to parse */
+			}
+			term->type = d->bDescriptorSubtype << 16; /* virtual type */
+			term->channels = uac_processing_unit_bNrChannels(d);
+			term->chconfig = uac_processing_unit_wChannelConfig(d, state->mixer->protocol);
+			term->name = uac_processing_unit_iProcessing(d, state->mixer->protocol);
+			return 0;
+		}
+		case UAC2_CLOCK_SOURCE: {
+			struct uac_clock_source_descriptor *d = p1;
+			term->type = d->bDescriptorSubtype << 16; /* virtual type */
+			term->id = id;
+			term->name = d->iClockSource;
+			return 0;
+		}
+		default:
+			return -ENODEV;
+		}
+	}
+	return -ENODEV;
+}
+
+
+/*
+ * Feature Unit
+ */
+
+/* feature unit control information */
+struct usb_feature_control_info {
+	const char *name;
+	unsigned int type;	/* control type (mute, volume, etc.) */
+};
+
+static struct usb_feature_control_info audio_feature_info[] = {
+	{ "Mute",			USB_MIXER_INV_BOOLEAN },
+	{ "Volume",			USB_MIXER_S16 },
+	{ "Tone Control - Bass",	USB_MIXER_S8 },
+	{ "Tone Control - Mid",		USB_MIXER_S8 },
+	{ "Tone Control - Treble",	USB_MIXER_S8 },
+	{ "Graphic Equalizer",		USB_MIXER_S8 }, /* FIXME: not implemeted yet */
+	{ "Auto Gain Control",		USB_MIXER_BOOLEAN },
+	{ "Delay Control",		USB_MIXER_U16 },
+	{ "Bass Boost",			USB_MIXER_BOOLEAN },
+	{ "Loudness",			USB_MIXER_BOOLEAN },
+	/* UAC2 specific */
+	{ "Input Gain Control",		USB_MIXER_U16 },
+	{ "Input Gain Pad Control",	USB_MIXER_BOOLEAN },
+	{ "Phase Inverter Control",	USB_MIXER_BOOLEAN },
+};
+
+
+/* private_free callback */
+static void usb_mixer_elem_free(struct snd_kcontrol *kctl)
+{
+	kfree(kctl->private_data);
+	kctl->private_data = NULL;
+}
+
+
+/*
+ * interface to ALSA control for feature/mixer units
+ */
+
+/*
+ * retrieve the minimum and maximum values for the specified control
+ */
+static int get_min_max(struct usb_mixer_elem_info *cval, int default_min)
+{
+	/* for failsafe */
+	cval->min = default_min;
+	cval->max = cval->min + 1;
+	cval->res = 1;
+	cval->dBmin = cval->dBmax = 0;
+
+	if (cval->val_type == USB_MIXER_BOOLEAN ||
+	    cval->val_type == USB_MIXER_INV_BOOLEAN) {
+		cval->initialized = 1;
+	} else {
+		int minchn = 0;
+		if (cval->cmask) {
+			int i;
+			for (i = 0; i < MAX_CHANNELS; i++)
+				if (cval->cmask & (1 << i)) {
+					minchn = i + 1;
+					break;
+				}
+		}
+		if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 ||
+		    get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) {
+			snd_printd(KERN_ERR "%d:%d: cannot get min/max values for control %d (id %d)\n",
+				   cval->id, snd_usb_ctrl_intf(cval->mixer->chip), cval->control, cval->id);
+			return -EINVAL;
+		}
+		if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, &cval->res) < 0) {
+			cval->res = 1;
+		} else {
+			int last_valid_res = cval->res;
+
+			while (cval->res > 1) {
+				if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES,
+								(cval->control << 8) | minchn, cval->res / 2) < 0)
+					break;
+				cval->res /= 2;
+			}
+			if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, &cval->res) < 0)
+				cval->res = last_valid_res;
+		}
+		if (cval->res == 0)
+			cval->res = 1;
+
+		/* Additional checks for the proper resolution
+		 *
+		 * Some devices report smaller resolutions than actually
+		 * reacting.  They don't return errors but simply clip
+		 * to the lower aligned value.
+		 */
+		if (cval->min + cval->res < cval->max) {
+			int last_valid_res = cval->res;
+			int saved, test, check;
+			get_cur_mix_raw(cval, minchn, &saved);
+			for (;;) {
+				test = saved;
+				if (test < cval->max)
+					test += cval->res;
+				else
+					test -= cval->res;
+				if (test < cval->min || test > cval->max ||
+				    set_cur_mix_value(cval, minchn, 0, test) ||
+				    get_cur_mix_raw(cval, minchn, &check)) {
+					cval->res = last_valid_res;
+					break;
+				}
+				if (test == check)
+					break;
+				cval->res *= 2;
+			}
+			set_cur_mix_value(cval, minchn, 0, saved);
+		}
+
+		cval->initialized = 1;
+	}
+
+	/* USB descriptions contain the dB scale in 1/256 dB unit
+	 * while ALSA TLV contains in 1/100 dB unit
+	 */
+	cval->dBmin = (convert_signed_value(cval, cval->min) * 100) / 256;
+	cval->dBmax = (convert_signed_value(cval, cval->max) * 100) / 256;
+	if (cval->dBmin > cval->dBmax) {
+		/* something is wrong; assume it's either from/to 0dB */
+		if (cval->dBmin < 0)
+			cval->dBmax = 0;
+		else if (cval->dBmin > 0)
+			cval->dBmin = 0;
+		if (cval->dBmin > cval->dBmax) {
+			/* totally crap, return an error */
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+
+/* get a feature/mixer unit info */
+static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+
+	if (cval->val_type == USB_MIXER_BOOLEAN ||
+	    cval->val_type == USB_MIXER_INV_BOOLEAN)
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	else
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = cval->channels;
+	if (cval->val_type == USB_MIXER_BOOLEAN ||
+	    cval->val_type == USB_MIXER_INV_BOOLEAN) {
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = 1;
+	} else {
+		if (! cval->initialized)
+			get_min_max(cval,  0);
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max =
+			(cval->max - cval->min + cval->res - 1) / cval->res;
+	}
+	return 0;
+}
+
+/* get the current value from feature/mixer unit */
+static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int c, cnt, val, err;
+
+	ucontrol->value.integer.value[0] = cval->min;
+	if (cval->cmask) {
+		cnt = 0;
+		for (c = 0; c < MAX_CHANNELS; c++) {
+			if (!(cval->cmask & (1 << c)))
+				continue;
+			err = get_cur_mix_value(cval, c + 1, cnt, &val);
+			if (err < 0)
+				return cval->mixer->ignore_ctl_error ? 0 : err;
+			val = get_relative_value(cval, val);
+			ucontrol->value.integer.value[cnt] = val;
+			cnt++;
+		}
+		return 0;
+	} else {
+		/* master channel */
+		err = get_cur_mix_value(cval, 0, 0, &val);
+		if (err < 0)
+			return cval->mixer->ignore_ctl_error ? 0 : err;
+		val = get_relative_value(cval, val);
+		ucontrol->value.integer.value[0] = val;
+	}
+	return 0;
+}
+
+/* put the current value to feature/mixer unit */
+static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int c, cnt, val, oval, err;
+	int changed = 0;
+
+	if (cval->cmask) {
+		cnt = 0;
+		for (c = 0; c < MAX_CHANNELS; c++) {
+			if (!(cval->cmask & (1 << c)))
+				continue;
+			err = get_cur_mix_value(cval, c + 1, cnt, &oval);
+			if (err < 0)
+				return cval->mixer->ignore_ctl_error ? 0 : err;
+			val = ucontrol->value.integer.value[cnt];
+			val = get_abs_value(cval, val);
+			if (oval != val) {
+				set_cur_mix_value(cval, c + 1, cnt, val);
+				changed = 1;
+			}
+			cnt++;
+		}
+	} else {
+		/* master channel */
+		err = get_cur_mix_value(cval, 0, 0, &oval);
+		if (err < 0)
+			return cval->mixer->ignore_ctl_error ? 0 : err;
+		val = ucontrol->value.integer.value[0];
+		val = get_abs_value(cval, val);
+		if (val != oval) {
+			set_cur_mix_value(cval, 0, 0, val);
+			changed = 1;
+		}
+	}
+	return changed;
+}
+
+static struct snd_kcontrol_new usb_feature_unit_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "", /* will be filled later manually */
+	.info = mixer_ctl_feature_info,
+	.get = mixer_ctl_feature_get,
+	.put = mixer_ctl_feature_put,
+};
+
+/* the read-only variant */
+static struct snd_kcontrol_new usb_feature_unit_ctl_ro = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "", /* will be filled later manually */
+	.info = mixer_ctl_feature_info,
+	.get = mixer_ctl_feature_get,
+	.put = NULL,
+};
+
+
+/*
+ * build a feature control
+ */
+
+static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str)
+{
+	return strlcat(kctl->id.name, str, sizeof(kctl->id.name));
+}
+
+static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
+			      unsigned int ctl_mask, int control,
+			      struct usb_audio_term *iterm, int unitid,
+			      int readonly_mask)
+{
+	struct uac_feature_unit_descriptor *desc = raw_desc;
+	unsigned int len = 0;
+	int mapped_name = 0;
+	int nameid = uac_feature_unit_iFeature(desc);
+	struct snd_kcontrol *kctl;
+	struct usb_mixer_elem_info *cval;
+	const struct usbmix_name_map *map;
+
+	control++; /* change from zero-based to 1-based value */
+
+	if (control == UAC_FU_GRAPHIC_EQUALIZER) {
+		/* FIXME: not supported yet */
+		return;
+	}
+
+	map = find_map(state, unitid, control);
+	if (check_ignored_ctl(map))
+		return;
+
+	cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+	if (! cval) {
+		snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+		return;
+	}
+	cval->mixer = state->mixer;
+	cval->id = unitid;
+	cval->control = control;
+	cval->cmask = ctl_mask;
+	cval->val_type = audio_feature_info[control-1].type;
+	if (ctl_mask == 0) {
+		cval->channels = 1;	/* master channel */
+		cval->master_readonly = readonly_mask;
+	} else {
+		int i, c = 0;
+		for (i = 0; i < 16; i++)
+			if (ctl_mask & (1 << i))
+				c++;
+		cval->channels = c;
+		cval->ch_readonly = readonly_mask;
+	}
+
+	/* get min/max values */
+	get_min_max(cval, 0);
+
+	/* if all channels in the mask are marked read-only, make the control
+	 * read-only. set_cur_mix_value() will check the mask again and won't
+	 * issue write commands to read-only channels. */
+	if (cval->channels == readonly_mask)
+		kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval);
+	else
+		kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
+
+	if (! kctl) {
+		snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+		kfree(cval);
+		return;
+	}
+	kctl->private_free = usb_mixer_elem_free;
+
+	len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
+	mapped_name = len != 0;
+	if (! len && nameid)
+		len = snd_usb_copy_string_desc(state, nameid,
+				kctl->id.name, sizeof(kctl->id.name));
+
+	switch (control) {
+	case UAC_FU_MUTE:
+	case UAC_FU_VOLUME:
+		/* determine the control name.  the rule is:
+		 * - if a name id is given in descriptor, use it.
+		 * - if the connected input can be determined, then use the name
+		 *   of terminal type.
+		 * - if the connected output can be determined, use it.
+		 * - otherwise, anonymous name.
+		 */
+		if (! len) {
+			len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 1);
+			if (! len)
+				len = get_term_name(state, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 1);
+			if (! len)
+				len = snprintf(kctl->id.name, sizeof(kctl->id.name),
+					       "Feature %d", unitid);
+		}
+		/* determine the stream direction:
+		 * if the connected output is USB stream, then it's likely a
+		 * capture stream.  otherwise it should be playback (hopefully :)
+		 */
+		if (! mapped_name && ! (state->oterm.type >> 16)) {
+			if ((state->oterm.type & 0xff00) == 0x0100) {
+				len = append_ctl_name(kctl, " Capture");
+			} else {
+				len = append_ctl_name(kctl, " Playback");
+			}
+		}
+		append_ctl_name(kctl, control == UAC_FU_MUTE ?
+				" Switch" : " Volume");
+		if (control == UAC_FU_VOLUME) {
+			kctl->tlv.c = mixer_vol_tlv;
+			kctl->vd[0].access |= 
+				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+				SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+			check_mapped_dB(map, cval);
+		}
+		break;
+
+	default:
+		if (! len)
+			strlcpy(kctl->id.name, audio_feature_info[control-1].name,
+				sizeof(kctl->id.name));
+		break;
+	}
+
+	/* volume control quirks */
+	switch (state->chip->usb_id) {
+	case USB_ID(0x0471, 0x0101):
+	case USB_ID(0x0471, 0x0104):
+	case USB_ID(0x0471, 0x0105):
+	case USB_ID(0x0672, 0x1041):
+	/* quirk for UDA1321/N101.
+	 * note that detection between firmware 2.1.1.7 (N101)
+	 * and later 2.1.1.21 is not very clear from datasheets.
+	 * I hope that the min value is -15360 for newer firmware --jk
+	 */
+		if (!strcmp(kctl->id.name, "PCM Playback Volume") &&
+		    cval->min == -15616) {
+			snd_printk(KERN_INFO
+				 "set volume quirk for UDA1321/N101 chip\n");
+			cval->max = -256;
+		}
+		break;
+
+	case USB_ID(0x046d, 0x09a4):
+		if (!strcmp(kctl->id.name, "Mic Capture Volume")) {
+			snd_printk(KERN_INFO
+				"set volume quirk for QuickCam E3500\n");
+			cval->min = 6080;
+			cval->max = 8768;
+			cval->res = 192;
+		}
+		break;
+
+	case USB_ID(0x046d, 0x0809):
+	case USB_ID(0x046d, 0x0991):
+	/* Most audio usb devices lie about volume resolution.
+	 * Most Logitech webcams have res = 384.
+	 * Proboly there is some logitech magic behind this number --fishor
+	 */
+		if (!strcmp(kctl->id.name, "Mic Capture Volume")) {
+			snd_printk(KERN_INFO
+				"set resolution quirk: cval->res = 384\n");
+			cval->res = 384;
+		}
+		break;
+
+	}
+
+	snd_printdd(KERN_INFO "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
+		    cval->id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res);
+	add_control_to_empty(state, kctl);
+}
+
+
+
+/*
+ * parse a feature unit
+ *
+ * most of controlls are defined here.
+ */
+static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void *_ftr)
+{
+	int channels, i, j;
+	struct usb_audio_term iterm;
+	unsigned int master_bits, first_ch_bits;
+	int err, csize;
+	struct uac_feature_unit_descriptor *hdr = _ftr;
+	__u8 *bmaControls;
+
+	if (state->mixer->protocol == UAC_VERSION_1) {
+		csize = hdr->bControlSize;
+		channels = (hdr->bLength - 7) / csize - 1;
+		bmaControls = hdr->bmaControls;
+	} else {
+		struct uac2_feature_unit_descriptor *ftr = _ftr;
+		csize = 4;
+		channels = (hdr->bLength - 6) / 4 - 1;
+		bmaControls = ftr->bmaControls;
+	}
+
+	if (hdr->bLength < 7 || !csize || hdr->bLength < 7 + csize) {
+		snd_printk(KERN_ERR "usbaudio: unit %u: invalid UAC_FEATURE_UNIT descriptor\n", unitid);
+		return -EINVAL;
+	}
+
+	/* parse the source unit */
+	if ((err = parse_audio_unit(state, hdr->bSourceID)) < 0)
+		return err;
+
+	/* determine the input source type and name */
+	if (check_input_term(state, hdr->bSourceID, &iterm) < 0)
+		return -EINVAL;
+
+	master_bits = snd_usb_combine_bytes(bmaControls, csize);
+	/* master configuration quirks */
+	switch (state->chip->usb_id) {
+	case USB_ID(0x08bb, 0x2702):
+		snd_printk(KERN_INFO
+			   "usbmixer: master volume quirk for PCM2702 chip\n");
+		/* disable non-functional volume control */
+		master_bits &= ~UAC_CONTROL_BIT(UAC_FU_VOLUME);
+		break;
+	}
+	if (channels > 0)
+		first_ch_bits = snd_usb_combine_bytes(bmaControls + csize, csize);
+	else
+		first_ch_bits = 0;
+
+	if (state->mixer->protocol == UAC_VERSION_1) {
+		/* check all control types */
+		for (i = 0; i < 10; i++) {
+			unsigned int ch_bits = 0;
+			for (j = 0; j < channels; j++) {
+				unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize);
+				if (mask & (1 << i))
+					ch_bits |= (1 << j);
+			}
+			/* audio class v1 controls are never read-only */
+			if (ch_bits & 1) /* the first channel must be set (for ease of programming) */
+				build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, 0);
+			if (master_bits & (1 << i))
+				build_feature_ctl(state, _ftr, 0, i, &iterm, unitid, 0);
+		}
+	} else { /* UAC_VERSION_2 */
+		for (i = 0; i < 30/2; i++) {
+			unsigned int ch_bits = 0;
+			unsigned int ch_read_only = 0;
+
+			for (j = 0; j < channels; j++) {
+				unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize);
+				if (uac2_control_is_readable(mask, i)) {
+					ch_bits |= (1 << j);
+					if (!uac2_control_is_writeable(mask, i))
+						ch_read_only |= (1 << j);
+				}
+			}
+
+			/* NOTE: build_feature_ctl() will mark the control read-only if all channels
+			 * are marked read-only in the descriptors. Otherwise, the control will be
+			 * reported as writeable, but the driver will not actually issue a write
+			 * command for read-only channels */
+			if (ch_bits & 1) /* the first channel must be set (for ease of programming) */
+				build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, ch_read_only);
+			if (uac2_control_is_readable(master_bits, i))
+				build_feature_ctl(state, _ftr, 0, i, &iterm, unitid,
+						  !uac2_control_is_writeable(master_bits, i));
+		}
+	}
+
+	return 0;
+}
+
+
+/*
+ * Mixer Unit
+ */
+
+/*
+ * build a mixer unit control
+ *
+ * the callbacks are identical with feature unit.
+ * input channel number (zero based) is given in control field instead.
+ */
+
+static void build_mixer_unit_ctl(struct mixer_build *state,
+				 struct uac_mixer_unit_descriptor *desc,
+				 int in_pin, int in_ch, int unitid,
+				 struct usb_audio_term *iterm)
+{
+	struct usb_mixer_elem_info *cval;
+	unsigned int num_outs = uac_mixer_unit_bNrChannels(desc);
+	unsigned int i, len;
+	struct snd_kcontrol *kctl;
+	const struct usbmix_name_map *map;
+
+	map = find_map(state, unitid, 0);
+	if (check_ignored_ctl(map))
+		return;
+
+	cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+	if (! cval)
+		return;
+
+	cval->mixer = state->mixer;
+	cval->id = unitid;
+	cval->control = in_ch + 1; /* based on 1 */
+	cval->val_type = USB_MIXER_S16;
+	for (i = 0; i < num_outs; i++) {
+		if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc, state->mixer->protocol), in_ch, i, num_outs)) {
+			cval->cmask |= (1 << i);
+			cval->channels++;
+		}
+	}
+
+	/* get min/max values */
+	get_min_max(cval, 0);
+
+	kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
+	if (! kctl) {
+		snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+		kfree(cval);
+		return;
+	}
+	kctl->private_free = usb_mixer_elem_free;
+
+	len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
+	if (! len)
+		len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 0);
+	if (! len)
+		len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1);
+	append_ctl_name(kctl, " Volume");
+
+	snd_printdd(KERN_INFO "[%d] MU [%s] ch = %d, val = %d/%d\n",
+		    cval->id, kctl->id.name, cval->channels, cval->min, cval->max);
+	add_control_to_empty(state, kctl);
+}
+
+
+/*
+ * parse a mixer unit
+ */
+static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, void *raw_desc)
+{
+	struct uac_mixer_unit_descriptor *desc = raw_desc;
+	struct usb_audio_term iterm;
+	int input_pins, num_ins, num_outs;
+	int pin, ich, err;
+
+	if (desc->bLength < 11 || ! (input_pins = desc->bNrInPins) || ! (num_outs = uac_mixer_unit_bNrChannels(desc))) {
+		snd_printk(KERN_ERR "invalid MIXER UNIT descriptor %d\n", unitid);
+		return -EINVAL;
+	}
+	/* no bmControls field (e.g. Maya44) -> ignore */
+	if (desc->bLength <= 10 + input_pins) {
+		snd_printdd(KERN_INFO "MU %d has no bmControls field\n", unitid);
+		return 0;
+	}
+
+	num_ins = 0;
+	ich = 0;
+	for (pin = 0; pin < input_pins; pin++) {
+		err = parse_audio_unit(state, desc->baSourceID[pin]);
+		if (err < 0)
+			return err;
+		err = check_input_term(state, desc->baSourceID[pin], &iterm);
+		if (err < 0)
+			return err;
+		num_ins += iterm.channels;
+		for (; ich < num_ins; ++ich) {
+			int och, ich_has_controls = 0;
+
+			for (och = 0; och < num_outs; ++och) {
+				if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc, state->mixer->protocol),
+							ich, och, num_outs)) {
+					ich_has_controls = 1;
+					break;
+				}
+			}
+			if (ich_has_controls)
+				build_mixer_unit_ctl(state, desc, pin, ich,
+						     unitid, &iterm);
+		}
+	}
+	return 0;
+}
+
+
+/*
+ * Processing Unit / Extension Unit
+ */
+
+/* get callback for processing/extension unit */
+static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int err, val;
+
+	err = get_cur_ctl_value(cval, cval->control << 8, &val);
+	if (err < 0 && cval->mixer->ignore_ctl_error) {
+		ucontrol->value.integer.value[0] = cval->min;
+		return 0;
+	}
+	if (err < 0)
+		return err;
+	val = get_relative_value(cval, val);
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+/* put callback for processing/extension unit */
+static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int val, oval, err;
+
+	err = get_cur_ctl_value(cval, cval->control << 8, &oval);
+	if (err < 0) {
+		if (cval->mixer->ignore_ctl_error)
+			return 0;
+		return err;
+	}
+	val = ucontrol->value.integer.value[0];
+	val = get_abs_value(cval, val);
+	if (val != oval) {
+		set_cur_ctl_value(cval, cval->control << 8, val);
+		return 1;
+	}
+	return 0;
+}
+
+/* alsa control interface for processing/extension unit */
+static struct snd_kcontrol_new mixer_procunit_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "", /* will be filled later */
+	.info = mixer_ctl_feature_info,
+	.get = mixer_ctl_procunit_get,
+	.put = mixer_ctl_procunit_put,
+};
+
+
+/*
+ * predefined data for processing units
+ */
+struct procunit_value_info {
+	int control;
+	char *suffix;
+	int val_type;
+	int min_value;
+};
+
+struct procunit_info {
+	int type;
+	char *name;
+	struct procunit_value_info *values;
+};
+
+static struct procunit_value_info updown_proc_info[] = {
+	{ UAC_UD_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 },
+	{ 0 }
+};
+static struct procunit_value_info prologic_proc_info[] = {
+	{ UAC_DP_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_DP_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 },
+	{ 0 }
+};
+static struct procunit_value_info threed_enh_proc_info[] = {
+	{ UAC_3D_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_3D_SPACE, "Spaciousness", USB_MIXER_U8 },
+	{ 0 }
+};
+static struct procunit_value_info reverb_proc_info[] = {
+	{ UAC_REVERB_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_REVERB_LEVEL, "Level", USB_MIXER_U8 },
+	{ UAC_REVERB_TIME, "Time", USB_MIXER_U16 },
+	{ UAC_REVERB_FEEDBACK, "Feedback", USB_MIXER_U8 },
+	{ 0 }
+};
+static struct procunit_value_info chorus_proc_info[] = {
+	{ UAC_CHORUS_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_CHORUS_LEVEL, "Level", USB_MIXER_U8 },
+	{ UAC_CHORUS_RATE, "Rate", USB_MIXER_U16 },
+	{ UAC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 },
+	{ 0 }
+};
+static struct procunit_value_info dcr_proc_info[] = {
+	{ UAC_DCR_ENABLE, "Switch", USB_MIXER_BOOLEAN },
+	{ UAC_DCR_RATE, "Ratio", USB_MIXER_U16 },
+	{ UAC_DCR_MAXAMPL, "Max Amp", USB_MIXER_S16 },
+	{ UAC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 },
+	{ UAC_DCR_ATTACK_TIME, "Attack Time", USB_MIXER_U16 },
+	{ UAC_DCR_RELEASE_TIME, "Release Time", USB_MIXER_U16 },
+	{ 0 }
+};
+
+static struct procunit_info procunits[] = {
+	{ UAC_PROCESS_UP_DOWNMIX, "Up Down", updown_proc_info },
+	{ UAC_PROCESS_DOLBY_PROLOGIC, "Dolby Prologic", prologic_proc_info },
+	{ UAC_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", threed_enh_proc_info },
+	{ UAC_PROCESS_REVERB, "Reverb", reverb_proc_info },
+	{ UAC_PROCESS_CHORUS, "Chorus", chorus_proc_info },
+	{ UAC_PROCESS_DYN_RANGE_COMP, "DCR", dcr_proc_info },
+	{ 0 },
+};
+/*
+ * predefined data for extension units
+ */
+static struct procunit_value_info clock_rate_xu_info[] = {
+	{ USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 },
+	{ 0 }
+};
+static struct procunit_value_info clock_source_xu_info[] = {
+	{ USB_XU_CLOCK_SOURCE_SELECTOR, "External", USB_MIXER_BOOLEAN },
+	{ 0 }
+};
+static struct procunit_value_info spdif_format_xu_info[] = {
+	{ USB_XU_DIGITAL_FORMAT_SELECTOR, "SPDIF/AC3", USB_MIXER_BOOLEAN },
+	{ 0 }
+};
+static struct procunit_value_info soft_limit_xu_info[] = {
+	{ USB_XU_SOFT_LIMIT_SELECTOR, " ", USB_MIXER_BOOLEAN },
+	{ 0 }
+};
+static struct procunit_info extunits[] = {
+	{ USB_XU_CLOCK_RATE, "Clock rate", clock_rate_xu_info },
+	{ USB_XU_CLOCK_SOURCE, "DigitalIn CLK source", clock_source_xu_info },
+	{ USB_XU_DIGITAL_IO_STATUS, "DigitalOut format:", spdif_format_xu_info },
+	{ USB_XU_DEVICE_OPTIONS, "AnalogueIn Soft Limit", soft_limit_xu_info },
+	{ 0 }
+};
+/*
+ * build a processing/extension unit
+ */
+static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw_desc, struct procunit_info *list, char *name)
+{
+	struct uac_processing_unit_descriptor *desc = raw_desc;
+	int num_ins = desc->bNrInPins;
+	struct usb_mixer_elem_info *cval;
+	struct snd_kcontrol *kctl;
+	int i, err, nameid, type, len;
+	struct procunit_info *info;
+	struct procunit_value_info *valinfo;
+	const struct usbmix_name_map *map;
+	static struct procunit_value_info default_value_info[] = {
+		{ 0x01, "Switch", USB_MIXER_BOOLEAN },
+		{ 0 }
+	};
+	static struct procunit_info default_info = {
+		0, NULL, default_value_info
+	};
+
+	if (desc->bLength < 13 || desc->bLength < 13 + num_ins ||
+	    desc->bLength < num_ins + uac_processing_unit_bControlSize(desc, state->mixer->protocol)) {
+		snd_printk(KERN_ERR "invalid %s descriptor (id %d)\n", name, unitid);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < num_ins; i++) {
+		if ((err = parse_audio_unit(state, desc->baSourceID[i])) < 0)
+			return err;
+	}
+
+	type = le16_to_cpu(desc->wProcessType);
+	for (info = list; info && info->type; info++)
+		if (info->type == type)
+			break;
+	if (! info || ! info->type)
+		info = &default_info;
+
+	for (valinfo = info->values; valinfo->control; valinfo++) {
+		__u8 *controls = uac_processing_unit_bmControls(desc, state->mixer->protocol);
+
+		if (! (controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1))))
+			continue;
+		map = find_map(state, unitid, valinfo->control);
+		if (check_ignored_ctl(map))
+			continue;
+		cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+		if (! cval) {
+			snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+			return -ENOMEM;
+		}
+		cval->mixer = state->mixer;
+		cval->id = unitid;
+		cval->control = valinfo->control;
+		cval->val_type = valinfo->val_type;
+		cval->channels = 1;
+
+		/* get min/max values */
+		if (type == UAC_PROCESS_UP_DOWNMIX && cval->control == UAC_UD_MODE_SELECT) {
+			__u8 *control_spec = uac_processing_unit_specific(desc, state->mixer->protocol);
+			/* FIXME: hard-coded */
+			cval->min = 1;
+			cval->max = control_spec[0];
+			cval->res = 1;
+			cval->initialized = 1;
+		} else {
+			if (type == USB_XU_CLOCK_RATE) {
+				/* E-Mu USB 0404/0202/TrackerPre
+				 * samplerate control quirk
+				 */
+				cval->min = 0;
+				cval->max = 5;
+				cval->res = 1;
+				cval->initialized = 1;
+			} else
+				get_min_max(cval, valinfo->min_value);
+		}
+
+		kctl = snd_ctl_new1(&mixer_procunit_ctl, cval);
+		if (! kctl) {
+			snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+			kfree(cval);
+			return -ENOMEM;
+		}
+		kctl->private_free = usb_mixer_elem_free;
+
+		if (check_mapped_name(map, kctl->id.name,
+						sizeof(kctl->id.name)))
+			/* nothing */ ;
+		else if (info->name)
+			strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name));
+		else {
+			nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol);
+			len = 0;
+			if (nameid)
+				len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
+			if (! len)
+				strlcpy(kctl->id.name, name, sizeof(kctl->id.name));
+		}
+		append_ctl_name(kctl, " ");
+		append_ctl_name(kctl, valinfo->suffix);
+
+		snd_printdd(KERN_INFO "[%d] PU [%s] ch = %d, val = %d/%d\n",
+			    cval->id, kctl->id.name, cval->channels, cval->min, cval->max);
+		if ((err = add_control_to_empty(state, kctl)) < 0)
+			return err;
+	}
+	return 0;
+}
+
+
+static int parse_audio_processing_unit(struct mixer_build *state, int unitid, void *raw_desc)
+{
+	return build_audio_procunit(state, unitid, raw_desc, procunits, "Processing Unit");
+}
+
+static int parse_audio_extension_unit(struct mixer_build *state, int unitid, void *raw_desc)
+{
+	/* Note that we parse extension units with processing unit descriptors.
+	 * That's ok as the layout is the same */
+	return build_audio_procunit(state, unitid, raw_desc, extunits, "Extension Unit");
+}
+
+
+/*
+ * Selector Unit
+ */
+
+/* info callback for selector unit
+ * use an enumerator type for routing
+ */
+static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	char **itemlist = (char **)kcontrol->private_value;
+
+	if (snd_BUG_ON(!itemlist))
+		return -EINVAL;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = cval->max;
+	if (uinfo->value.enumerated.item >= cval->max)
+		uinfo->value.enumerated.item = cval->max - 1;
+	strlcpy(uinfo->value.enumerated.name, itemlist[uinfo->value.enumerated.item],
+		sizeof(uinfo->value.enumerated.name));
+	return 0;
+}
+
+/* get callback for selector unit */
+static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int val, err;
+
+	err = get_cur_ctl_value(cval, cval->control << 8, &val);
+	if (err < 0) {
+		if (cval->mixer->ignore_ctl_error) {
+			ucontrol->value.enumerated.item[0] = 0;
+			return 0;
+		}
+		return err;
+	}
+	val = get_relative_value(cval, val);
+	ucontrol->value.enumerated.item[0] = val;
+	return 0;
+}
+
+/* put callback for selector unit */
+static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_elem_info *cval = kcontrol->private_data;
+	int val, oval, err;
+
+	err = get_cur_ctl_value(cval, cval->control << 8, &oval);
+	if (err < 0) {
+		if (cval->mixer->ignore_ctl_error)
+			return 0;
+		return err;
+	}
+	val = ucontrol->value.enumerated.item[0];
+	val = get_abs_value(cval, val);
+	if (val != oval) {
+		set_cur_ctl_value(cval, cval->control << 8, val);
+		return 1;
+	}
+	return 0;
+}
+
+/* alsa control interface for selector unit */
+static struct snd_kcontrol_new mixer_selectunit_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "", /* will be filled later */
+	.info = mixer_ctl_selector_info,
+	.get = mixer_ctl_selector_get,
+	.put = mixer_ctl_selector_put,
+};
+
+
+/* private free callback.
+ * free both private_data and private_value
+ */
+static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl)
+{
+	int i, num_ins = 0;
+
+	if (kctl->private_data) {
+		struct usb_mixer_elem_info *cval = kctl->private_data;
+		num_ins = cval->max;
+		kfree(cval);
+		kctl->private_data = NULL;
+	}
+	if (kctl->private_value) {
+		char **itemlist = (char **)kctl->private_value;
+		for (i = 0; i < num_ins; i++)
+			kfree(itemlist[i]);
+		kfree(itemlist);
+		kctl->private_value = 0;
+	}
+}
+
+/*
+ * parse a selector unit
+ */
+static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void *raw_desc)
+{
+	struct uac_selector_unit_descriptor *desc = raw_desc;
+	unsigned int i, nameid, len;
+	int err;
+	struct usb_mixer_elem_info *cval;
+	struct snd_kcontrol *kctl;
+	const struct usbmix_name_map *map;
+	char **namelist;
+
+	if (!desc->bNrInPins || desc->bLength < 5 + desc->bNrInPins) {
+		snd_printk(KERN_ERR "invalid SELECTOR UNIT descriptor %d\n", unitid);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < desc->bNrInPins; i++) {
+		if ((err = parse_audio_unit(state, desc->baSourceID[i])) < 0)
+			return err;
+	}
+
+	if (desc->bNrInPins == 1) /* only one ? nonsense! */
+		return 0;
+
+	map = find_map(state, unitid, 0);
+	if (check_ignored_ctl(map))
+		return 0;
+
+	cval = kzalloc(sizeof(*cval), GFP_KERNEL);
+	if (! cval) {
+		snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+		return -ENOMEM;
+	}
+	cval->mixer = state->mixer;
+	cval->id = unitid;
+	cval->val_type = USB_MIXER_U8;
+	cval->channels = 1;
+	cval->min = 1;
+	cval->max = desc->bNrInPins;
+	cval->res = 1;
+	cval->initialized = 1;
+
+	if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR)
+		cval->control = UAC2_CX_CLOCK_SELECTOR;
+	else
+		cval->control = 0;
+
+	namelist = kmalloc(sizeof(char *) * desc->bNrInPins, GFP_KERNEL);
+	if (! namelist) {
+		snd_printk(KERN_ERR "cannot malloc\n");
+		kfree(cval);
+		return -ENOMEM;
+	}
+#define MAX_ITEM_NAME_LEN	64
+	for (i = 0; i < desc->bNrInPins; i++) {
+		struct usb_audio_term iterm;
+		len = 0;
+		namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL);
+		if (! namelist[i]) {
+			snd_printk(KERN_ERR "cannot malloc\n");
+			while (i--)
+				kfree(namelist[i]);
+			kfree(namelist);
+			kfree(cval);
+			return -ENOMEM;
+		}
+		len = check_mapped_selector_name(state, unitid, i, namelist[i],
+						 MAX_ITEM_NAME_LEN);
+		if (! len && check_input_term(state, desc->baSourceID[i], &iterm) >= 0)
+			len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0);
+		if (! len)
+			sprintf(namelist[i], "Input %d", i);
+	}
+
+	kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval);
+	if (! kctl) {
+		snd_printk(KERN_ERR "cannot malloc kcontrol\n");
+		kfree(namelist);
+		kfree(cval);
+		return -ENOMEM;
+	}
+	kctl->private_value = (unsigned long)namelist;
+	kctl->private_free = usb_mixer_selector_elem_free;
+
+	nameid = uac_selector_unit_iSelector(desc);
+	len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
+	if (len)
+		;
+	else if (nameid)
+		snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
+	else {
+		len = get_term_name(state, &state->oterm,
+				    kctl->id.name, sizeof(kctl->id.name), 0);
+		if (! len)
+			strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name));
+
+		if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR)
+			append_ctl_name(kctl, " Clock Source");
+		else if ((state->oterm.type & 0xff00) == 0x0100)
+			append_ctl_name(kctl, " Capture Source");
+		else
+			append_ctl_name(kctl, " Playback Source");
+	}
+
+	snd_printdd(KERN_INFO "[%d] SU [%s] items = %d\n",
+		    cval->id, kctl->id.name, desc->bNrInPins);
+	if ((err = add_control_to_empty(state, kctl)) < 0)
+		return err;
+
+	return 0;
+}
+
+
+/*
+ * parse an audio unit recursively
+ */
+
+static int parse_audio_unit(struct mixer_build *state, int unitid)
+{
+	unsigned char *p1;
+
+	if (test_and_set_bit(unitid, state->unitbitmap))
+		return 0; /* the unit already visited */
+
+	p1 = find_audio_control_unit(state, unitid);
+	if (!p1) {
+		snd_printk(KERN_ERR "usbaudio: unit %d not found!\n", unitid);
+		return -EINVAL;
+	}
+
+	switch (p1[2]) {
+	case UAC_INPUT_TERMINAL:
+	case UAC2_CLOCK_SOURCE:
+		return 0; /* NOP */
+	case UAC_MIXER_UNIT:
+		return parse_audio_mixer_unit(state, unitid, p1);
+	case UAC_SELECTOR_UNIT:
+	case UAC2_CLOCK_SELECTOR:
+		return parse_audio_selector_unit(state, unitid, p1);
+	case UAC_FEATURE_UNIT:
+		return parse_audio_feature_unit(state, unitid, p1);
+	case UAC1_PROCESSING_UNIT:
+	/*   UAC2_EFFECT_UNIT has the same value */
+		if (state->mixer->protocol == UAC_VERSION_1)
+			return parse_audio_processing_unit(state, unitid, p1);
+		else
+			return 0; /* FIXME - effect units not implemented yet */
+	case UAC1_EXTENSION_UNIT:
+	/*   UAC2_PROCESSING_UNIT_V2 has the same value */
+		if (state->mixer->protocol == UAC_VERSION_1)
+			return parse_audio_extension_unit(state, unitid, p1);
+		else /* UAC_VERSION_2 */
+			return parse_audio_processing_unit(state, unitid, p1);
+	default:
+		snd_printk(KERN_ERR "usbaudio: unit %u: unexpected type 0x%02x\n", unitid, p1[2]);
+		return -EINVAL;
+	}
+}
+
+static void snd_usb_mixer_free(struct usb_mixer_interface *mixer)
+{
+	kfree(mixer->id_elems);
+	if (mixer->urb) {
+		kfree(mixer->urb->transfer_buffer);
+		usb_free_urb(mixer->urb);
+	}
+	usb_free_urb(mixer->rc_urb);
+	kfree(mixer->rc_setup_packet);
+	kfree(mixer);
+}
+
+static int snd_usb_mixer_dev_free(struct snd_device *device)
+{
+	struct usb_mixer_interface *mixer = device->device_data;
+	snd_usb_mixer_free(mixer);
+	return 0;
+}
+
+/*
+ * create mixer controls
+ *
+ * walk through all UAC_OUTPUT_TERMINAL descriptors to search for mixers
+ */
+static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
+{
+	struct mixer_build state;
+	int err;
+	const struct usbmix_ctl_map *map;
+	struct usb_host_interface *hostif;
+	void *p;
+
+	hostif = mixer->chip->ctrl_intf;
+	memset(&state, 0, sizeof(state));
+	state.chip = mixer->chip;
+	state.mixer = mixer;
+	state.buffer = hostif->extra;
+	state.buflen = hostif->extralen;
+
+	/* check the mapping table */
+	for (map = usbmix_ctl_maps; map->id; map++) {
+		if (map->id == state.chip->usb_id) {
+			state.map = map->map;
+			state.selector_map = map->selector_map;
+			mixer->ignore_ctl_error = map->ignore_ctl_error;
+			break;
+		}
+	}
+
+	p = NULL;
+	while ((p = snd_usb_find_csint_desc(hostif->extra, hostif->extralen, p, UAC_OUTPUT_TERMINAL)) != NULL) {
+		if (mixer->protocol == UAC_VERSION_1) {
+			struct uac1_output_terminal_descriptor *desc = p;
+
+			if (desc->bLength < sizeof(*desc))
+				continue; /* invalid descriptor? */
+			set_bit(desc->bTerminalID, state.unitbitmap);  /* mark terminal ID as visited */
+			state.oterm.id = desc->bTerminalID;
+			state.oterm.type = le16_to_cpu(desc->wTerminalType);
+			state.oterm.name = desc->iTerminal;
+			err = parse_audio_unit(&state, desc->bSourceID);
+			if (err < 0)
+				return err;
+		} else { /* UAC_VERSION_2 */
+			struct uac2_output_terminal_descriptor *desc = p;
+
+			if (desc->bLength < sizeof(*desc))
+				continue; /* invalid descriptor? */
+			set_bit(desc->bTerminalID, state.unitbitmap);  /* mark terminal ID as visited */
+			state.oterm.id = desc->bTerminalID;
+			state.oterm.type = le16_to_cpu(desc->wTerminalType);
+			state.oterm.name = desc->iTerminal;
+			err = parse_audio_unit(&state, desc->bSourceID);
+			if (err < 0)
+				return err;
+
+			/* for UAC2, use the same approach to also add the clock selectors */
+			err = parse_audio_unit(&state, desc->bCSourceID);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid)
+{
+	struct usb_mixer_elem_info *info;
+
+	for (info = mixer->id_elems[unitid]; info; info = info->next_id_elem)
+		snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       info->elem_id);
+}
+
+static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer,
+				    int unitid,
+				    struct usb_mixer_elem_info *cval)
+{
+	static char *val_types[] = {"BOOLEAN", "INV_BOOLEAN",
+				    "S8", "U8", "S16", "U16"};
+	snd_iprintf(buffer, "  Unit: %i\n", unitid);
+	if (cval->elem_id)
+		snd_iprintf(buffer, "    Control: name=\"%s\", index=%i\n",
+				cval->elem_id->name, cval->elem_id->index);
+	snd_iprintf(buffer, "    Info: id=%i, control=%i, cmask=0x%x, "
+			    "channels=%i, type=\"%s\"\n", cval->id,
+			    cval->control, cval->cmask, cval->channels,
+			    val_types[cval->val_type]);
+	snd_iprintf(buffer, "    Volume: min=%i, max=%i, dBmin=%i, dBmax=%i\n",
+			    cval->min, cval->max, cval->dBmin, cval->dBmax);
+}
+
+static void snd_usb_mixer_proc_read(struct snd_info_entry *entry,
+				    struct snd_info_buffer *buffer)
+{
+	struct snd_usb_audio *chip = entry->private_data;
+	struct usb_mixer_interface *mixer;
+	struct usb_mixer_elem_info *cval;
+	int unitid;
+
+	list_for_each_entry(mixer, &chip->mixer_list, list) {
+		snd_iprintf(buffer,
+			"USB Mixer: usb_id=0x%08x, ctrlif=%i, ctlerr=%i\n",
+				chip->usb_id, snd_usb_ctrl_intf(chip),
+				mixer->ignore_ctl_error);
+		snd_iprintf(buffer, "Card: %s\n", chip->card->longname);
+		for (unitid = 0; unitid < MAX_ID_ELEMS; unitid++) {
+			for (cval = mixer->id_elems[unitid]; cval;
+						cval = cval->next_id_elem)
+				snd_usb_mixer_dump_cval(buffer, unitid, cval);
+		}
+	}
+}
+
+static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
+				       int attribute, int value, int index)
+{
+	struct usb_mixer_elem_info *info;
+	__u8 unitid = (index >> 8) & 0xff;
+	__u8 control = (value >> 8) & 0xff;
+	__u8 channel = value & 0xff;
+
+	if (channel >= MAX_CHANNELS) {
+		snd_printk(KERN_DEBUG "%s(): bogus channel number %d\n",
+				__func__, channel);
+		return;
+	}
+
+	for (info = mixer->id_elems[unitid]; info; info = info->next_id_elem) {
+		if (info->control != control)
+			continue;
+
+		switch (attribute) {
+		case UAC2_CS_CUR:
+			/* invalidate cache, so the value is read from the device */
+			if (channel)
+				info->cached &= ~(1 << channel);
+			else /* master channel */
+				info->cached = 0;
+
+			snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
+					info->elem_id);
+			break;
+
+		case UAC2_CS_RANGE:
+			/* TODO */
+			break;
+
+		case UAC2_CS_MEM:
+			/* TODO */
+			break;
+
+		default:
+			snd_printk(KERN_DEBUG "unknown attribute %d in interrupt\n",
+						attribute);
+			break;
+		} /* switch */
+	}
+}
+
+static void snd_usb_mixer_interrupt(struct urb *urb)
+{
+	struct usb_mixer_interface *mixer = urb->context;
+	int len = urb->actual_length;
+
+	if (urb->status != 0)
+		goto requeue;
+
+	if (mixer->protocol == UAC_VERSION_1) {
+		struct uac1_status_word *status;
+
+		for (status = urb->transfer_buffer;
+		     len >= sizeof(*status);
+		     len -= sizeof(*status), status++) {
+			snd_printd(KERN_DEBUG "status interrupt: %02x %02x\n",
+						status->bStatusType,
+						status->bOriginator);
+
+			/* ignore any notifications not from the control interface */
+			if ((status->bStatusType & UAC1_STATUS_TYPE_ORIG_MASK) !=
+				UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF)
+				continue;
+
+			if (status->bStatusType & UAC1_STATUS_TYPE_MEM_CHANGED)
+				snd_usb_mixer_rc_memory_change(mixer, status->bOriginator);
+			else
+				snd_usb_mixer_notify_id(mixer, status->bOriginator);
+		}
+	} else { /* UAC_VERSION_2 */
+		struct uac2_interrupt_data_msg *msg;
+
+		for (msg = urb->transfer_buffer;
+		     len >= sizeof(*msg);
+		     len -= sizeof(*msg), msg++) {
+			/* drop vendor specific and endpoint requests */
+			if ((msg->bInfo & UAC2_INTERRUPT_DATA_MSG_VENDOR) ||
+			    (msg->bInfo & UAC2_INTERRUPT_DATA_MSG_EP))
+				continue;
+
+			snd_usb_mixer_interrupt_v2(mixer, msg->bAttribute,
+						   le16_to_cpu(msg->wValue),
+						   le16_to_cpu(msg->wIndex));
+		}
+	}
+
+requeue:
+	if (urb->status != -ENOENT && urb->status != -ECONNRESET) {
+		urb->dev = mixer->chip->dev;
+		usb_submit_urb(urb, GFP_ATOMIC);
+	}
+}
+
+/* create the handler for the optional status interrupt endpoint */
+static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer)
+{
+	struct usb_host_interface *hostif;
+	struct usb_endpoint_descriptor *ep;
+	void *transfer_buffer;
+	int buffer_length;
+	unsigned int epnum;
+
+	hostif = mixer->chip->ctrl_intf;
+	/* we need one interrupt input endpoint */
+	if (get_iface_desc(hostif)->bNumEndpoints < 1)
+		return 0;
+	ep = get_endpoint(hostif, 0);
+	if (!usb_endpoint_dir_in(ep) || !usb_endpoint_xfer_int(ep))
+		return 0;
+
+	epnum = usb_endpoint_num(ep);
+	buffer_length = le16_to_cpu(ep->wMaxPacketSize);
+	transfer_buffer = kmalloc(buffer_length, GFP_KERNEL);
+	if (!transfer_buffer)
+		return -ENOMEM;
+	mixer->urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!mixer->urb) {
+		kfree(transfer_buffer);
+		return -ENOMEM;
+	}
+	usb_fill_int_urb(mixer->urb, mixer->chip->dev,
+			 usb_rcvintpipe(mixer->chip->dev, epnum),
+			 transfer_buffer, buffer_length,
+			 snd_usb_mixer_interrupt, mixer, ep->bInterval);
+	usb_submit_urb(mixer->urb, GFP_KERNEL);
+	return 0;
+}
+
+int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
+			 int ignore_error)
+{
+	static struct snd_device_ops dev_ops = {
+		.dev_free = snd_usb_mixer_dev_free
+	};
+	struct usb_mixer_interface *mixer;
+	struct snd_info_entry *entry;
+	struct usb_host_interface *host_iface;
+	int err;
+
+	strcpy(chip->card->mixername, "USB Mixer");
+
+	mixer = kzalloc(sizeof(*mixer), GFP_KERNEL);
+	if (!mixer)
+		return -ENOMEM;
+	mixer->chip = chip;
+	mixer->ignore_ctl_error = ignore_error;
+	mixer->id_elems = kcalloc(MAX_ID_ELEMS, sizeof(*mixer->id_elems),
+				  GFP_KERNEL);
+	if (!mixer->id_elems) {
+		kfree(mixer);
+		return -ENOMEM;
+	}
+
+	host_iface = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0];
+	switch (get_iface_desc(host_iface)->bInterfaceProtocol) {
+	case UAC_VERSION_1:
+	default:
+		mixer->protocol = UAC_VERSION_1;
+		break;
+	case UAC_VERSION_2:
+		mixer->protocol = UAC_VERSION_2;
+		break;
+	}
+
+	if ((err = snd_usb_mixer_controls(mixer)) < 0 ||
+	    (err = snd_usb_mixer_status_create(mixer)) < 0)
+		goto _error;
+
+	snd_usb_mixer_apply_create_quirk(mixer);
+
+	err = snd_device_new(chip->card, SNDRV_DEV_LOWLEVEL, mixer, &dev_ops);
+	if (err < 0)
+		goto _error;
+
+	if (list_empty(&chip->mixer_list) &&
+	    !snd_card_proc_new(chip->card, "usbmixer", &entry))
+		snd_info_set_text_ops(entry, chip, snd_usb_mixer_proc_read);
+
+	list_add(&mixer->list, &chip->mixer_list);
+	return 0;
+
+_error:
+	snd_usb_mixer_free(mixer);
+	return err;
+}
+
+void snd_usb_mixer_disconnect(struct list_head *p)
+{
+	struct usb_mixer_interface *mixer;
+
+	mixer = list_entry(p, struct usb_mixer_interface, list);
+	usb_kill_urb(mixer->urb);
+	usb_kill_urb(mixer->rc_urb);
+}
diff --git a/linux/sound/usb/mixer.h b/linux/sound/usb/mixer.h
new file mode 100644
index 0000000..26c636c
--- /dev/null
+++ b/linux/sound/usb/mixer.h
@@ -0,0 +1,56 @@
+#ifndef __USBMIXER_H
+#define __USBMIXER_H
+
+struct usb_mixer_interface {
+	struct snd_usb_audio *chip;
+	struct list_head list;
+	unsigned int ignore_ctl_error;
+	struct urb *urb;
+	/* array[MAX_ID_ELEMS], indexed by unit id */
+	struct usb_mixer_elem_info **id_elems;
+
+	/* the usb audio specification version this interface complies to */
+	int protocol;
+
+	/* Sound Blaster remote control stuff */
+	const struct rc_config *rc_cfg;
+	u32 rc_code;
+	wait_queue_head_t rc_waitq;
+	struct urb *rc_urb;
+	struct usb_ctrlrequest *rc_setup_packet;
+	u8 rc_buffer[6];
+
+	u8 audigy2nx_leds[3];
+	u8 xonar_u1_status;
+};
+
+#define MAX_CHANNELS	10	/* max logical channels */
+
+struct usb_mixer_elem_info {
+	struct usb_mixer_interface *mixer;
+	struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */
+	struct snd_ctl_elem_id *elem_id;
+	unsigned int id;
+	unsigned int control;	/* CS or ICN (high byte) */
+	unsigned int cmask; /* channel mask bitmap: 0 = master */
+	unsigned int ch_readonly;
+	unsigned int master_readonly;
+	int channels;
+	int val_type;
+	int min, max, res;
+	int dBmin, dBmax;
+	int cached;
+	int cache_val[MAX_CHANNELS];
+	u8 initialized;
+};
+
+int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
+			 int ignore_error);
+void snd_usb_mixer_disconnect(struct list_head *p);
+
+void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid);
+
+int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
+				int request, int validx, int value_set);
+
+#endif /* __USBMIXER_H */
diff --git a/linux/sound/usb/mixer_maps.c b/linux/sound/usb/mixer_maps.c
new file mode 100644
index 0000000..f1324c4
--- /dev/null
+++ b/linux/sound/usb/mixer_maps.c
@@ -0,0 +1,376 @@
+/*
+ *   Additional mixer mapping
+ *
+ *   Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+struct usbmix_dB_map {
+	u32 min;
+	u32 max;
+};
+
+struct usbmix_name_map {
+	int id;
+	const char *name;
+	int control;
+	struct usbmix_dB_map *dB;
+};
+
+struct usbmix_selector_map {
+	int id;
+	int count;
+	const char **names;
+};
+
+struct usbmix_ctl_map {
+	u32 id;
+	const struct usbmix_name_map *map;
+	const struct usbmix_selector_map *selector_map;
+	int ignore_ctl_error;
+};
+
+/*
+ * USB control mappers for SB Exitigy
+ */
+
+/*
+ * Topology of SB Extigy (see on the wide screen :)
+
+USB_IN[1] --->FU[2]------------------------------+->MU[16]-->PU[17]-+->FU[18]--+->EU[27]--+->EU[21]-->FU[22]--+->FU[23] > Dig_OUT[24]
+                                                 ^                  |          |          |                   |
+USB_IN[3] -+->SU[5]-->FU[6]--+->MU[14] ->PU[15]->+                  |          |          |                   +->FU[25] > Dig_OUT[26]
+           ^                 ^                   |                  |          |          |
+Dig_IN[4] -+                 |                   |                  |          |          +->FU[28]---------------------> Spk_OUT[19]
+                             |                   |                  |          |
+Lin-IN[7] -+-->FU[8]---------+                   |                  |          +----------------------------------------> Hph_OUT[20]
+           |                                     |                  |
+Mic-IN[9] --+->FU[10]----------------------------+                  |
+           ||                                                       |
+           ||  +----------------------------------------------------+
+           VV  V
+           ++--+->SU[11]-->FU[12] --------------------------------------------------------------------------------------> USB_OUT[13]
+*/
+
+static struct usbmix_name_map extigy_map[] = {
+	/* 1: IT pcm */
+	{ 2, "PCM Playback" }, /* FU */
+	/* 3: IT pcm */
+	/* 4: IT digital in */
+	{ 5, NULL }, /* DISABLED: this seems to be bogus on some firmware */
+	{ 6, "Digital In" }, /* FU */
+	/* 7: IT line */
+	{ 8, "Line Playback" }, /* FU */
+	/* 9: IT mic */
+	{ 10, "Mic Playback" }, /* FU */
+	{ 11, "Capture Source" }, /* SU */
+	{ 12, "Capture" }, /* FU */
+	/* 13: OT pcm capture */
+	/* 14: MU (w/o controls) */
+	/* 15: PU (3D enh) */
+	/* 16: MU (w/o controls) */
+	{ 17, NULL, 1 }, /* DISABLED: PU-switch (any effect?) */
+	{ 17, "Channel Routing", 2 },	/* PU: mode select */
+	{ 18, "Tone Control - Bass", UAC_FU_BASS }, /* FU */
+	{ 18, "Tone Control - Treble", UAC_FU_TREBLE }, /* FU */
+	{ 18, "Master Playback" }, /* FU; others */
+	/* 19: OT speaker */
+	/* 20: OT headphone */
+	{ 21, NULL }, /* DISABLED: EU (for what?) */
+	{ 22, "Digital Out Playback" }, /* FU */
+	{ 23, "Digital Out1 Playback" }, /* FU */  /* FIXME: corresponds to 24 */
+	/* 24: OT digital out */
+	{ 25, "IEC958 Optical Playback" }, /* FU */
+	{ 26, "IEC958 Optical Playback" }, /* OT */
+	{ 27, NULL }, /* DISABLED: EU (for what?) */
+	/* 28: FU speaker (mute) */
+	{ 29, NULL }, /* Digital Input Playback Source? */
+	{ 0 } /* terminator */
+};
+
+/* Sound Blaster MP3+ controls mapping
+ * The default mixer channels have totally misleading names,
+ * e.g. no Master and fake PCM volume
+ *			Pavel Mihaylov <bin@bash.info>
+ */
+static struct usbmix_dB_map mp3plus_dB_1 = {-4781, 0};	/* just guess */
+static struct usbmix_dB_map mp3plus_dB_2 = {-1781, 618}; /* just guess */
+
+static struct usbmix_name_map mp3plus_map[] = {
+	/* 1: IT pcm */
+	/* 2: IT mic */
+	/* 3: IT line */
+	/* 4: IT digital in */
+	/* 5: OT digital out */
+	/* 6: OT speaker */
+	/* 7: OT pcm capture */
+	{ 8, "Capture Source" }, /* FU, default PCM Capture Source */
+		/* (Mic, Input 1 = Line input, Input 2 = Optical input) */
+	{ 9, "Master Playback" }, /* FU, default Speaker 1 */
+	/* { 10, "Mic Capture", 1 }, */ /* FU, Mic Capture */
+	{ 10, /* "Mic Capture", */ NULL, 2, .dB = &mp3plus_dB_2 },
+		/* FU, Mic Capture */
+	{ 10, "Mic Boost", 7 }, /* FU, default Auto Gain Input */
+	{ 11, "Line Capture", .dB = &mp3plus_dB_2 },
+		/* FU, default PCM Capture */
+	{ 12, "Digital In Playback" }, /* FU, default PCM 1 */
+	{ 13, /* "Mic Playback", */ .dB = &mp3plus_dB_1 },
+		/* FU, default Mic Playback */
+	{ 14, "Line Playback", .dB = &mp3plus_dB_1 }, /* FU, default Speaker */
+	/* 15: MU */
+	{ 0 } /* terminator */
+};
+
+/* Topology of SB Audigy 2 NX
+
+          +----------------------------->EU[27]--+
+          |                                      v
+          | +----------------------------------->SU[29]---->FU[22]-->Dig_OUT[24]
+          | |                                    ^
+USB_IN[1]-+------------+              +->EU[17]->+->FU[11]-+
+            |          v              |          v         |
+Dig_IN[4]---+->FU[6]-->MU[16]->FU[18]-+->EU[21]->SU[31]----->FU[30]->Hph_OUT[20]
+            |          ^              |                    |
+Lin_IN[7]-+--->FU[8]---+              +->EU[23]->FU[28]------------->Spk_OUT[19]
+          | |                                              v
+          +--->FU[12]------------------------------------->SU[14]--->USB_OUT[15]
+            |                                              ^
+            +->FU[13]--------------------------------------+
+*/
+static struct usbmix_name_map audigy2nx_map[] = {
+	/* 1: IT pcm playback */
+	/* 4: IT digital in */
+	{ 6, "Digital In Playback" }, /* FU */
+	/* 7: IT line in */
+	{ 8, "Line Playback" }, /* FU */
+	{ 11, "What-U-Hear Capture" }, /* FU */
+	{ 12, "Line Capture" }, /* FU */
+	{ 13, "Digital In Capture" }, /* FU */
+	{ 14, "Capture Source" }, /* SU */
+	/* 15: OT pcm capture */
+	/* 16: MU w/o controls */
+	{ 17, NULL }, /* DISABLED: EU (for what?) */
+	{ 18, "Master Playback" }, /* FU */
+	/* 19: OT speaker */
+	/* 20: OT headphone */
+	{ 21, NULL }, /* DISABLED: EU (for what?) */
+	{ 22, "Digital Out Playback" }, /* FU */
+	{ 23, NULL }, /* DISABLED: EU (for what?) */
+	/* 24: OT digital out */
+	{ 27, NULL }, /* DISABLED: EU (for what?) */
+	{ 28, "Speaker Playback" }, /* FU */
+	{ 29, "Digital Out Source" }, /* SU */
+	{ 30, "Headphone Playback" }, /* FU */
+	{ 31, "Headphone Source" }, /* SU */
+	{ 0 } /* terminator */
+};
+
+static struct usbmix_selector_map audigy2nx_selectors[] = {
+	{
+		.id = 14, /* Capture Source */
+		.count = 3,
+		.names = (const char*[]) {"Line", "Digital In", "What-U-Hear"}
+	},
+	{
+		.id = 29, /* Digital Out Source */
+		.count = 3,
+		.names = (const char*[]) {"Front", "PCM", "Digital In"}
+	},
+	{
+		.id = 31, /* Headphone Source */
+		.count = 2,
+		.names = (const char*[]) {"Front", "Side"}
+	},
+	{ 0 } /* terminator */
+};
+
+/* Creative SoundBlaster Live! 24-bit External */
+static struct usbmix_name_map live24ext_map[] = {
+	/* 2: PCM Playback Volume */
+	{ 5, "Mic Capture" }, /* FU, default PCM Capture Volume */
+	{ 0 } /* terminator */
+};
+
+/* LineX FM Transmitter entry - needed to bypass controls bug */
+static struct usbmix_name_map linex_map[] = {
+	/* 1: IT pcm */
+	/* 2: OT Speaker */ 
+	{ 3, "Master" }, /* FU: master volume - left / right / mute */
+	{ 0 } /* terminator */
+};
+
+static struct usbmix_name_map maya44_map[] = {
+	/* 1: IT line */
+	{ 2, "Line Playback" }, /* FU */
+	/* 3: IT line */
+	{ 4, "Line Playback" }, /* FU */
+	/* 5: IT pcm playback */
+	/* 6: MU */
+	{ 7, "Master Playback" }, /* FU */
+	/* 8: OT speaker */
+	/* 9: IT line */
+	{ 10, "Line Capture" }, /* FU */
+	/* 11: MU */
+	/* 12: OT pcm capture */
+	{ }
+};
+
+/* Section "justlink_map" below added by James Courtier-Dutton <James@superbug.demon.co.uk>
+ * sourced from Maplin Electronics (http://www.maplin.co.uk), part number A56AK
+ * Part has 2 connectors that act as a single output. (TOSLINK Optical for digital out, and 3.5mm Jack for Analogue out.)
+ * The USB Mixer publishes a Microphone and extra Volume controls for it, but none exist on the device,
+ * so this map removes all unwanted sliders from alsamixer
+ */
+
+static struct usbmix_name_map justlink_map[] = {
+	/* 1: IT pcm playback */
+	/* 2: Not present */
+	{ 3, NULL}, /* IT mic (No mic input on device) */
+	/* 4: Not present */
+	/* 5: OT speacker */
+	/* 6: OT pcm capture */
+	{ 7, "Master Playback" }, /* Mute/volume for speaker */
+	{ 8, NULL }, /* Capture Switch (No capture inputs on device) */
+	{ 9, NULL }, /* Capture Mute/volume (No capture inputs on device */
+	/* 0xa: Not present */
+	/* 0xb: MU (w/o controls) */
+	{ 0xc, NULL }, /* Mic feedback Mute/volume (No capture inputs on device) */
+	{ 0 } /* terminator */
+};
+
+/* TerraTec Aureon 5.1 MkII USB */
+static struct usbmix_name_map aureon_51_2_map[] = {
+	/* 1: IT USB */
+	/* 2: IT Mic */
+	/* 3: IT Line */
+	/* 4: IT SPDIF */
+	/* 5: OT SPDIF */
+	/* 6: OT Speaker */
+	/* 7: OT USB */
+	{ 8, "Capture Source" }, /* SU */
+	{ 9, "Master Playback" }, /* FU */
+	{ 10, "Mic Capture" }, /* FU */
+	{ 11, "Line Capture" }, /* FU */
+	{ 12, "IEC958 In Capture" }, /* FU */
+	{ 13, "Mic Playback" }, /* FU */
+	{ 14, "Line Playback" }, /* FU */
+	/* 15: MU */
+	{} /* terminator */
+};
+
+static struct usbmix_name_map scratch_live_map[] = {
+	/* 1: IT Line 1 (USB streaming) */
+	/* 2: OT Line 1 (Speaker) */
+	/* 3: IT Line 1 (Line connector) */
+	{ 4, "Line 1 In" }, /* FU */
+	/* 5: OT Line 1 (USB streaming) */
+	/* 6: IT Line 2 (USB streaming) */
+	/* 7: OT Line 2 (Speaker) */
+	/* 8: IT Line 2 (Line connector) */
+	{ 9, "Line 2 In" }, /* FU */
+	/* 10: OT Line 2 (USB streaming) */
+	/* 11: IT Mic (Line connector) */
+	/* 12: OT Mic (USB streaming) */
+	{ 0 } /* terminator */
+};
+
+/* "Gamesurround Muse Pocket LT" looks same like "Sound Blaster MP3+"
+ *  most importand difference is SU[8], it should be set to "Capture Source"
+ *  to make alsamixer and PA working properly.
+ *  FIXME: or mp3plus_map should use "Capture Source" too,
+ *  so this maps can be merget
+ */
+static struct usbmix_name_map hercules_usb51_map[] = {
+	{ 8, "Capture Source" },	/* SU, default "PCM Capture Source" */
+	{ 9, "Master Playback" },	/* FU, default "Speaker Playback" */
+	{ 10, "Mic Boost", 7 },		/* FU, default "Auto Gain Input" */
+	{ 11, "Line Capture" },		/* FU, default "PCM Capture" */
+	{ 13, "Mic Bypass Playback" },	/* FU, default "Mic Playback" */
+	{ 14, "Line Bypass Playback" },	/* FU, default "Line Playback" */
+	{ 0 }				/* terminator */
+};
+
+/*
+ * Control map entries
+ */
+
+static struct usbmix_ctl_map usbmix_ctl_maps[] = {
+	{
+		.id = USB_ID(0x041e, 0x3000),
+		.map = extigy_map,
+		.ignore_ctl_error = 1,
+	},
+	{
+		.id = USB_ID(0x041e, 0x3010),
+		.map = mp3plus_map,
+	},
+	{
+		.id = USB_ID(0x041e, 0x3020),
+		.map = audigy2nx_map,
+		.selector_map = audigy2nx_selectors,
+	},
+ 	{
+		.id = USB_ID(0x041e, 0x3040),
+		.map = live24ext_map,
+	},
+	{
+		.id = USB_ID(0x041e, 0x3048),
+		.map = audigy2nx_map,
+		.selector_map = audigy2nx_selectors,
+	},
+	{
+		/* Hercules DJ Console (Windows Edition) */
+		.id = USB_ID(0x06f8, 0xb000),
+		.ignore_ctl_error = 1,
+	},
+	{
+		/* Hercules DJ Console (Macintosh Edition) */
+		.id = USB_ID(0x06f8, 0xd002),
+		.ignore_ctl_error = 1,
+	},
+	{
+		/* Hercules Gamesurround Muse Pocket LT
+		 * (USB 5.1 Channel Audio Adapter)
+		 */
+		.id = USB_ID(0x06f8, 0xc000),
+		.map = hercules_usb51_map,
+	},
+	{
+		.id = USB_ID(0x08bb, 0x2702),
+		.map = linex_map,
+		.ignore_ctl_error = 1,
+	},
+	{
+		.id = USB_ID(0x0a92, 0x0091),
+		.map = maya44_map,
+	},
+	{
+		.id = USB_ID(0x0c45, 0x1158),
+		.map = justlink_map,
+	},
+	{
+		.id = USB_ID(0x0ccd, 0x0028),
+		.map = aureon_51_2_map,
+	},
+	{
+		.id = USB_ID(0x13e5, 0x0001),
+		.map = scratch_live_map,
+		.ignore_ctl_error = 1,
+	},
+	{ 0 } /* terminator */
+};
+
diff --git a/linux/sound/usb/mixer_quirks.c b/linux/sound/usb/mixer_quirks.c
new file mode 100644
index 0000000..782f741
--- /dev/null
+++ b/linux/sound/usb/mixer_quirks.c
@@ -0,0 +1,424 @@
+/*
+ *   USB Audio Driver for ALSA
+ *
+ *   Quirks and vendor-specific extensions for mixer interfaces
+ *
+ *   Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   Many codes borrowed from audio.c by
+ *	    Alan Cox (alan@lxorguk.ukuu.org.uk)
+ *	    Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/hwdep.h>
+#include <sound/info.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "mixer_quirks.h"
+#include "helper.h"
+
+/*
+ * Sound Blaster remote control configuration
+ *
+ * format of remote control data:
+ * Extigy:       xx 00
+ * Audigy 2 NX:  06 80 xx 00 00 00
+ * Live! 24-bit: 06 80 xx yy 22 83
+ */
+static const struct rc_config {
+	u32 usb_id;
+	u8  offset;
+	u8  length;
+	u8  packet_length;
+	u8  min_packet_length; /* minimum accepted length of the URB result */
+	u8  mute_mixer_id;
+	u32 mute_code;
+} rc_configs[] = {
+	{ USB_ID(0x041e, 0x3000), 0, 1, 2, 1,  18, 0x0013 }, /* Extigy       */
+	{ USB_ID(0x041e, 0x3020), 2, 1, 6, 6,  18, 0x0013 }, /* Audigy 2 NX  */
+	{ USB_ID(0x041e, 0x3040), 2, 2, 6, 6,  2,  0x6e91 }, /* Live! 24-bit */
+	{ USB_ID(0x041e, 0x3042), 0, 1, 1, 1,  1,  0x000d }, /* Usb X-Fi S51 */
+	{ USB_ID(0x041e, 0x3048), 2, 2, 6, 6,  2,  0x6e91 }, /* Toshiba SB0500 */
+};
+
+static void snd_usb_soundblaster_remote_complete(struct urb *urb)
+{
+	struct usb_mixer_interface *mixer = urb->context;
+	const struct rc_config *rc = mixer->rc_cfg;
+	u32 code;
+
+	if (urb->status < 0 || urb->actual_length < rc->min_packet_length)
+		return;
+
+	code = mixer->rc_buffer[rc->offset];
+	if (rc->length == 2)
+		code |= mixer->rc_buffer[rc->offset + 1] << 8;
+
+	/* the Mute button actually changes the mixer control */
+	if (code == rc->mute_code)
+		snd_usb_mixer_notify_id(mixer, rc->mute_mixer_id);
+	mixer->rc_code = code;
+	wmb();
+	wake_up(&mixer->rc_waitq);
+}
+
+static long snd_usb_sbrc_hwdep_read(struct snd_hwdep *hw, char __user *buf,
+				     long count, loff_t *offset)
+{
+	struct usb_mixer_interface *mixer = hw->private_data;
+	int err;
+	u32 rc_code;
+
+	if (count != 1 && count != 4)
+		return -EINVAL;
+	err = wait_event_interruptible(mixer->rc_waitq,
+				       (rc_code = xchg(&mixer->rc_code, 0)) != 0);
+	if (err == 0) {
+		if (count == 1)
+			err = put_user(rc_code, buf);
+		else
+			err = put_user(rc_code, (u32 __user *)buf);
+	}
+	return err < 0 ? err : count;
+}
+
+static unsigned int snd_usb_sbrc_hwdep_poll(struct snd_hwdep *hw, struct file *file,
+					    poll_table *wait)
+{
+	struct usb_mixer_interface *mixer = hw->private_data;
+
+	poll_wait(file, &mixer->rc_waitq, wait);
+	return mixer->rc_code ? POLLIN | POLLRDNORM : 0;
+}
+
+static int snd_usb_soundblaster_remote_init(struct usb_mixer_interface *mixer)
+{
+	struct snd_hwdep *hwdep;
+	int err, len, i;
+
+	for (i = 0; i < ARRAY_SIZE(rc_configs); ++i)
+		if (rc_configs[i].usb_id == mixer->chip->usb_id)
+			break;
+	if (i >= ARRAY_SIZE(rc_configs))
+		return 0;
+	mixer->rc_cfg = &rc_configs[i];
+
+	len = mixer->rc_cfg->packet_length;
+
+	init_waitqueue_head(&mixer->rc_waitq);
+	err = snd_hwdep_new(mixer->chip->card, "SB remote control", 0, &hwdep);
+	if (err < 0)
+		return err;
+	snprintf(hwdep->name, sizeof(hwdep->name),
+		 "%s remote control", mixer->chip->card->shortname);
+	hwdep->iface = SNDRV_HWDEP_IFACE_SB_RC;
+	hwdep->private_data = mixer;
+	hwdep->ops.read = snd_usb_sbrc_hwdep_read;
+	hwdep->ops.poll = snd_usb_sbrc_hwdep_poll;
+	hwdep->exclusive = 1;
+
+	mixer->rc_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!mixer->rc_urb)
+		return -ENOMEM;
+	mixer->rc_setup_packet = kmalloc(sizeof(*mixer->rc_setup_packet), GFP_KERNEL);
+	if (!mixer->rc_setup_packet) {
+		usb_free_urb(mixer->rc_urb);
+		mixer->rc_urb = NULL;
+		return -ENOMEM;
+	}
+	mixer->rc_setup_packet->bRequestType =
+		USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
+	mixer->rc_setup_packet->bRequest = UAC_GET_MEM;
+	mixer->rc_setup_packet->wValue = cpu_to_le16(0);
+	mixer->rc_setup_packet->wIndex = cpu_to_le16(0);
+	mixer->rc_setup_packet->wLength = cpu_to_le16(len);
+	usb_fill_control_urb(mixer->rc_urb, mixer->chip->dev,
+			     usb_rcvctrlpipe(mixer->chip->dev, 0),
+			     (u8*)mixer->rc_setup_packet, mixer->rc_buffer, len,
+			     snd_usb_soundblaster_remote_complete, mixer);
+	return 0;
+}
+
+#define snd_audigy2nx_led_info		snd_ctl_boolean_mono_info
+
+static int snd_audigy2nx_led_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+
+	ucontrol->value.integer.value[0] = mixer->audigy2nx_leds[index];
+	return 0;
+}
+
+static int snd_audigy2nx_led_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol);
+	int index = kcontrol->private_value;
+	int value = ucontrol->value.integer.value[0];
+	int err, changed;
+
+	if (value > 1)
+		return -EINVAL;
+	changed = value != mixer->audigy2nx_leds[index];
+	if (mixer->chip->usb_id == USB_ID(0x041e, 0x3042))
+		err = snd_usb_ctl_msg(mixer->chip->dev,
+			      usb_sndctrlpipe(mixer->chip->dev, 0), 0x24,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+			      !value, 0, NULL, 0, 100);
+	else
+		err = snd_usb_ctl_msg(mixer->chip->dev,
+			      usb_sndctrlpipe(mixer->chip->dev, 0), 0x24,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+			      value, index + 2, NULL, 0, 100);
+	if (err < 0)
+		return err;
+	mixer->audigy2nx_leds[index] = value;
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_audigy2nx_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "CMSS LED Switch",
+		.info = snd_audigy2nx_led_info,
+		.get = snd_audigy2nx_led_get,
+		.put = snd_audigy2nx_led_put,
+		.private_value = 0,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Power LED Switch",
+		.info = snd_audigy2nx_led_info,
+		.get = snd_audigy2nx_led_get,
+		.put = snd_audigy2nx_led_put,
+		.private_value = 1,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Dolby Digital LED Switch",
+		.info = snd_audigy2nx_led_info,
+		.get = snd_audigy2nx_led_get,
+		.put = snd_audigy2nx_led_put,
+		.private_value = 2,
+	},
+};
+
+static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer)
+{
+	int i, err;
+
+	for (i = 0; i < ARRAY_SIZE(snd_audigy2nx_controls); ++i) {
+		/* USB X-Fi S51 doesn't have a CMSS LED */
+		if ((mixer->chip->usb_id == USB_ID(0x041e, 0x3042)) && i == 0)
+			continue;
+		if (i > 1 && /* Live24ext has 2 LEDs only */
+			(mixer->chip->usb_id == USB_ID(0x041e, 0x3040) ||
+			 mixer->chip->usb_id == USB_ID(0x041e, 0x3042) ||
+			 mixer->chip->usb_id == USB_ID(0x041e, 0x3048)))
+			break; 
+		err = snd_ctl_add(mixer->chip->card,
+				  snd_ctl_new1(&snd_audigy2nx_controls[i], mixer));
+		if (err < 0)
+			return err;
+	}
+	mixer->audigy2nx_leds[1] = 1; /* Power LED is on by default */
+	return 0;
+}
+
+static void snd_audigy2nx_proc_read(struct snd_info_entry *entry,
+				    struct snd_info_buffer *buffer)
+{
+	static const struct sb_jack {
+		int unitid;
+		const char *name;
+	}  jacks_audigy2nx[] = {
+		{4,  "dig in "},
+		{7,  "line in"},
+		{19, "spk out"},
+		{20, "hph out"},
+		{-1, NULL}
+	}, jacks_live24ext[] = {
+		{4,  "line in"}, /* &1=Line, &2=Mic*/
+		{3,  "hph out"}, /* headphones */
+		{0,  "RC     "}, /* last command, 6 bytes see rc_config above */
+		{-1, NULL}
+	};
+	const struct sb_jack *jacks;
+	struct usb_mixer_interface *mixer = entry->private_data;
+	int i, err;
+	u8 buf[3];
+
+	snd_iprintf(buffer, "%s jacks\n\n", mixer->chip->card->shortname);
+	if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020))
+		jacks = jacks_audigy2nx;
+	else if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) ||
+		 mixer->chip->usb_id == USB_ID(0x041e, 0x3048))
+		jacks = jacks_live24ext;
+	else
+		return;
+
+	for (i = 0; jacks[i].name; ++i) {
+		snd_iprintf(buffer, "%s: ", jacks[i].name);
+		err = snd_usb_ctl_msg(mixer->chip->dev,
+				      usb_rcvctrlpipe(mixer->chip->dev, 0),
+				      UAC_GET_MEM, USB_DIR_IN | USB_TYPE_CLASS |
+				      USB_RECIP_INTERFACE, 0,
+				      jacks[i].unitid << 8, buf, 3, 100);
+		if (err == 3 && (buf[0] == 3 || buf[0] == 6))
+			snd_iprintf(buffer, "%02x %02x\n", buf[1], buf[2]);
+		else
+			snd_iprintf(buffer, "?\n");
+	}
+}
+
+static int snd_xonar_u1_switch_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.integer.value[0] = !!(mixer->xonar_u1_status & 0x02);
+	return 0;
+}
+
+static int snd_xonar_u1_switch_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol);
+	u8 old_status, new_status;
+	int err, changed;
+
+	old_status = mixer->xonar_u1_status;
+	if (ucontrol->value.integer.value[0])
+		new_status = old_status | 0x02;
+	else
+		new_status = old_status & ~0x02;
+	changed = new_status != old_status;
+	err = snd_usb_ctl_msg(mixer->chip->dev,
+			      usb_sndctrlpipe(mixer->chip->dev, 0), 0x08,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+			      50, 0, &new_status, 1, 100);
+	if (err < 0)
+		return err;
+	mixer->xonar_u1_status = new_status;
+	return changed;
+}
+
+static struct snd_kcontrol_new snd_xonar_u1_output_switch = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Digital Playback Switch",
+	.info = snd_ctl_boolean_mono_info,
+	.get = snd_xonar_u1_switch_get,
+	.put = snd_xonar_u1_switch_put,
+};
+
+static int snd_xonar_u1_controls_create(struct usb_mixer_interface *mixer)
+{
+	int err;
+
+	err = snd_ctl_add(mixer->chip->card,
+			  snd_ctl_new1(&snd_xonar_u1_output_switch, mixer));
+	if (err < 0)
+		return err;
+	mixer->xonar_u1_status = 0x05;
+	return 0;
+}
+
+void snd_emuusb_set_samplerate(struct snd_usb_audio *chip,
+			       unsigned char samplerate_id)
+{
+	struct usb_mixer_interface *mixer;
+	struct usb_mixer_elem_info *cval;
+	int unitid = 12; /* SamleRate ExtensionUnit ID */
+
+	list_for_each_entry(mixer, &chip->mixer_list, list) {
+		cval = mixer->id_elems[unitid];
+		if (cval) {
+			snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR,
+						    cval->control << 8,
+						    samplerate_id);
+			snd_usb_mixer_notify_id(mixer, unitid);
+		}
+		break;
+	}
+}
+
+int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
+{
+	int err;
+	struct snd_info_entry *entry;
+
+	if ((err = snd_usb_soundblaster_remote_init(mixer)) < 0)
+		return err;
+
+	if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020) ||
+	    mixer->chip->usb_id == USB_ID(0x041e, 0x3040) ||
+	    mixer->chip->usb_id == USB_ID(0x041e, 0x3042) ||
+	    mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) {
+		if ((err = snd_audigy2nx_controls_create(mixer)) < 0)
+			return err;
+		if (!snd_card_proc_new(mixer->chip->card, "audigy2nx", &entry))
+			snd_info_set_text_ops(entry, mixer,
+					      snd_audigy2nx_proc_read);
+	}
+
+	if (mixer->chip->usb_id == USB_ID(0x0b05, 0x1739) ||
+	    mixer->chip->usb_id == USB_ID(0x0b05, 0x1743)) {
+		err = snd_xonar_u1_controls_create(mixer);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer,
+				    int unitid)
+{
+	if (!mixer->rc_cfg)
+		return;
+	/* unit ids specific to Extigy/Audigy 2 NX: */
+	switch (unitid) {
+	case 0: /* remote control */
+		mixer->rc_urb->dev = mixer->chip->dev;
+		usb_submit_urb(mixer->rc_urb, GFP_ATOMIC);
+		break;
+	case 4: /* digital in jack */
+	case 7: /* line in jacks */
+	case 19: /* speaker out jacks */
+	case 20: /* headphones out jack */
+		break;
+	/* live24ext: 4 = line-in jack */
+	case 3:	/* hp-out jack (may actuate Mute) */
+		if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) ||
+		    mixer->chip->usb_id == USB_ID(0x041e, 0x3048))
+			snd_usb_mixer_notify_id(mixer, mixer->rc_cfg->mute_mixer_id);
+		break;
+	default:
+		snd_printd(KERN_DEBUG "memory change in unknown unit %d\n", unitid);
+		break;
+	}
+}
+
diff --git a/linux/sound/usb/mixer_quirks.h b/linux/sound/usb/mixer_quirks.h
new file mode 100644
index 0000000..bdbfab0
--- /dev/null
+++ b/linux/sound/usb/mixer_quirks.h
@@ -0,0 +1,13 @@
+#ifndef SND_USB_MIXER_QUIRKS_H
+#define SND_USB_MIXER_QUIRKS_H
+
+int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer);
+
+void snd_emuusb_set_samplerate(struct snd_usb_audio *chip,
+			       unsigned char samplerate_id);
+
+void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer,
+				    int unitid);
+
+#endif /* SND_USB_MIXER_QUIRKS_H */
+
diff --git a/linux/sound/usb/pcm.c b/linux/sound/usb/pcm.c
new file mode 100644
index 0000000..4132522
--- /dev/null
+++ b/linux/sound/usb/pcm.c
@@ -0,0 +1,865 @@
+/*
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "quirks.h"
+#include "debug.h"
+#include "urb.h"
+#include "helper.h"
+#include "pcm.h"
+#include "clock.h"
+
+/*
+ * return the current pcm pointer.  just based on the hwptr_done value.
+ */
+static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_usb_substream *subs;
+	unsigned int hwptr_done;
+
+	subs = (struct snd_usb_substream *)substream->runtime->private_data;
+	spin_lock(&subs->lock);
+	hwptr_done = subs->hwptr_done;
+	spin_unlock(&subs->lock);
+	return hwptr_done / (substream->runtime->frame_bits >> 3);
+}
+
+/*
+ * find a matching audio format
+ */
+static struct audioformat *find_format(struct snd_usb_substream *subs, unsigned int format,
+				       unsigned int rate, unsigned int channels)
+{
+	struct list_head *p;
+	struct audioformat *found = NULL;
+	int cur_attr = 0, attr;
+
+	list_for_each(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		fp = list_entry(p, struct audioformat, list);
+		if (!(fp->formats & (1uLL << format)))
+			continue;
+		if (fp->channels != channels)
+			continue;
+		if (rate < fp->rate_min || rate > fp->rate_max)
+			continue;
+		if (! (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) {
+			unsigned int i;
+			for (i = 0; i < fp->nr_rates; i++)
+				if (fp->rate_table[i] == rate)
+					break;
+			if (i >= fp->nr_rates)
+				continue;
+		}
+		attr = fp->ep_attr & USB_ENDPOINT_SYNCTYPE;
+		if (! found) {
+			found = fp;
+			cur_attr = attr;
+			continue;
+		}
+		/* avoid async out and adaptive in if the other method
+		 * supports the same format.
+		 * this is a workaround for the case like
+		 * M-audio audiophile USB.
+		 */
+		if (attr != cur_attr) {
+			if ((attr == USB_ENDPOINT_SYNC_ASYNC &&
+			     subs->direction == SNDRV_PCM_STREAM_PLAYBACK) ||
+			    (attr == USB_ENDPOINT_SYNC_ADAPTIVE &&
+			     subs->direction == SNDRV_PCM_STREAM_CAPTURE))
+				continue;
+			if ((cur_attr == USB_ENDPOINT_SYNC_ASYNC &&
+			     subs->direction == SNDRV_PCM_STREAM_PLAYBACK) ||
+			    (cur_attr == USB_ENDPOINT_SYNC_ADAPTIVE &&
+			     subs->direction == SNDRV_PCM_STREAM_CAPTURE)) {
+				found = fp;
+				cur_attr = attr;
+				continue;
+			}
+		}
+		/* find the format with the largest max. packet size */
+		if (fp->maxpacksize > found->maxpacksize) {
+			found = fp;
+			cur_attr = attr;
+		}
+	}
+	return found;
+}
+
+static int init_pitch_v1(struct snd_usb_audio *chip, int iface,
+			 struct usb_host_interface *alts,
+			 struct audioformat *fmt)
+{
+	struct usb_device *dev = chip->dev;
+	unsigned int ep;
+	unsigned char data[1];
+	int err;
+
+	ep = get_endpoint(alts, 0)->bEndpointAddress;
+
+	data[0] = 1;
+	if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
+				   USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
+				   UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep,
+				   data, sizeof(data), 1000)) < 0) {
+		snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n",
+			   dev->devnum, iface, ep);
+		return err;
+	}
+
+	return 0;
+}
+
+static int init_pitch_v2(struct snd_usb_audio *chip, int iface,
+			 struct usb_host_interface *alts,
+			 struct audioformat *fmt)
+{
+	struct usb_device *dev = chip->dev;
+	unsigned char data[1];
+	unsigned int ep;
+	int err;
+
+	ep = get_endpoint(alts, 0)->bEndpointAddress;
+
+	data[0] = 1;
+	if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR,
+				   USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT,
+				   UAC2_EP_CS_PITCH << 8, 0,
+				   data, sizeof(data), 1000)) < 0) {
+		snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH (v2)\n",
+			   dev->devnum, iface, fmt->altsetting);
+		return err;
+	}
+
+	return 0;
+}
+
+/*
+ * initialize the pitch control and sample rate
+ */
+int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
+		       struct usb_host_interface *alts,
+		       struct audioformat *fmt)
+{
+	struct usb_interface_descriptor *altsd = get_iface_desc(alts);
+
+	/* if endpoint doesn't have pitch control, bail out */
+	if (!(fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL))
+		return 0;
+
+	switch (altsd->bInterfaceProtocol) {
+	case UAC_VERSION_1:
+	default:
+		return init_pitch_v1(chip, iface, alts, fmt);
+
+	case UAC_VERSION_2:
+		return init_pitch_v2(chip, iface, alts, fmt);
+	}
+}
+
+/*
+ * find a matching format and set up the interface
+ */
+static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
+{
+	struct usb_device *dev = subs->dev;
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	struct usb_interface *iface;
+	unsigned int ep, attr;
+	int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK;
+	int err;
+
+	iface = usb_ifnum_to_if(dev, fmt->iface);
+	if (WARN_ON(!iface))
+		return -EINVAL;
+	alts = &iface->altsetting[fmt->altset_idx];
+	altsd = get_iface_desc(alts);
+	if (WARN_ON(altsd->bAlternateSetting != fmt->altsetting))
+		return -EINVAL;
+
+	if (fmt == subs->cur_audiofmt)
+		return 0;
+
+	/* close the old interface */
+	if (subs->interface >= 0 && subs->interface != fmt->iface) {
+		if (usb_set_interface(subs->dev, subs->interface, 0) < 0) {
+			snd_printk(KERN_ERR "%d:%d:%d: return to setting 0 failed\n",
+				dev->devnum, fmt->iface, fmt->altsetting);
+			return -EIO;
+		}
+		subs->interface = -1;
+		subs->altset_idx = 0;
+	}
+
+	/* set interface */
+	if (subs->interface != fmt->iface || subs->altset_idx != fmt->altset_idx) {
+		if (usb_set_interface(dev, fmt->iface, fmt->altsetting) < 0) {
+			snd_printk(KERN_ERR "%d:%d:%d: usb_set_interface failed\n",
+				   dev->devnum, fmt->iface, fmt->altsetting);
+			return -EIO;
+		}
+		snd_printdd(KERN_INFO "setting usb interface %d:%d\n", fmt->iface, fmt->altsetting);
+		subs->interface = fmt->iface;
+		subs->altset_idx = fmt->altset_idx;
+	}
+
+	/* create a data pipe */
+	ep = fmt->endpoint & USB_ENDPOINT_NUMBER_MASK;
+	if (is_playback)
+		subs->datapipe = usb_sndisocpipe(dev, ep);
+	else
+		subs->datapipe = usb_rcvisocpipe(dev, ep);
+	subs->datainterval = fmt->datainterval;
+	subs->syncpipe = subs->syncinterval = 0;
+	subs->maxpacksize = fmt->maxpacksize;
+	subs->syncmaxsize = 0;
+	subs->fill_max = 0;
+
+	/* we need a sync pipe in async OUT or adaptive IN mode */
+	/* check the number of EP, since some devices have broken
+	 * descriptors which fool us.  if it has only one EP,
+	 * assume it as adaptive-out or sync-in.
+	 */
+	attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE;
+	if (((is_playback && attr == USB_ENDPOINT_SYNC_ASYNC) ||
+	     (! is_playback && attr == USB_ENDPOINT_SYNC_ADAPTIVE)) &&
+	    altsd->bNumEndpoints >= 2) {
+		/* check sync-pipe endpoint */
+		/* ... and check descriptor size before accessing bSynchAddress
+		   because there is a version of the SB Audigy 2 NX firmware lacking
+		   the audio fields in the endpoint descriptors */
+		if ((get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != 0x01 ||
+		    (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
+		     get_endpoint(alts, 1)->bSynchAddress != 0)) {
+			snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n",
+				   dev->devnum, fmt->iface, fmt->altsetting);
+			return -EINVAL;
+		}
+		ep = get_endpoint(alts, 1)->bEndpointAddress;
+		if (get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
+		    (( is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) ||
+		     (!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) {
+			snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n",
+				   dev->devnum, fmt->iface, fmt->altsetting);
+			return -EINVAL;
+		}
+		ep &= USB_ENDPOINT_NUMBER_MASK;
+		if (is_playback)
+			subs->syncpipe = usb_rcvisocpipe(dev, ep);
+		else
+			subs->syncpipe = usb_sndisocpipe(dev, ep);
+		if (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE &&
+		    get_endpoint(alts, 1)->bRefresh >= 1 &&
+		    get_endpoint(alts, 1)->bRefresh <= 9)
+			subs->syncinterval = get_endpoint(alts, 1)->bRefresh;
+		else if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL)
+			subs->syncinterval = 1;
+		else if (get_endpoint(alts, 1)->bInterval >= 1 &&
+			 get_endpoint(alts, 1)->bInterval <= 16)
+			subs->syncinterval = get_endpoint(alts, 1)->bInterval - 1;
+		else
+			subs->syncinterval = 3;
+		subs->syncmaxsize = le16_to_cpu(get_endpoint(alts, 1)->wMaxPacketSize);
+	}
+
+	/* always fill max packet size */
+	if (fmt->attributes & UAC_EP_CS_ATTR_FILL_MAX)
+		subs->fill_max = 1;
+
+	if ((err = snd_usb_init_pitch(subs->stream->chip, subs->interface, alts, fmt)) < 0)
+		return err;
+
+	subs->cur_audiofmt = fmt;
+
+	snd_usb_set_format_quirk(subs, fmt);
+
+#if 0
+	printk(KERN_DEBUG
+	       "setting done: format = %d, rate = %d..%d, channels = %d\n",
+	       fmt->format, fmt->rate_min, fmt->rate_max, fmt->channels);
+	printk(KERN_DEBUG
+	       "  datapipe = 0x%0x, syncpipe = 0x%0x\n",
+	       subs->datapipe, subs->syncpipe);
+#endif
+
+	return 0;
+}
+
+/*
+ * hw_params callback
+ *
+ * allocate a buffer and set the given audio format.
+ *
+ * so far we use a physically linear buffer although packetize transfer
+ * doesn't need a continuous area.
+ * if sg buffer is supported on the later version of alsa, we'll follow
+ * that.
+ */
+static int snd_usb_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_usb_substream *subs = substream->runtime->private_data;
+	struct audioformat *fmt;
+	unsigned int channels, rate, format;
+	int ret, changed;
+
+	ret = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+					       params_buffer_bytes(hw_params));
+	if (ret < 0)
+		return ret;
+
+	format = params_format(hw_params);
+	rate = params_rate(hw_params);
+	channels = params_channels(hw_params);
+	fmt = find_format(subs, format, rate, channels);
+	if (!fmt) {
+		snd_printd(KERN_DEBUG "cannot set format: format = %#x, rate = %d, channels = %d\n",
+			   format, rate, channels);
+		return -EINVAL;
+	}
+
+	changed = subs->cur_audiofmt != fmt ||
+		subs->period_bytes != params_period_bytes(hw_params) ||
+		subs->cur_rate != rate;
+	if ((ret = set_format(subs, fmt)) < 0)
+		return ret;
+
+	if (subs->cur_rate != rate) {
+		struct usb_host_interface *alts;
+		struct usb_interface *iface;
+		iface = usb_ifnum_to_if(subs->dev, fmt->iface);
+		alts = &iface->altsetting[fmt->altset_idx];
+		ret = snd_usb_init_sample_rate(subs->stream->chip, subs->interface, alts, fmt, rate);
+		if (ret < 0)
+			return ret;
+		subs->cur_rate = rate;
+	}
+
+	if (changed) {
+		/* format changed */
+		snd_usb_release_substream_urbs(subs, 0);
+		/* influenced: period_bytes, channels, rate, format, */
+		ret = snd_usb_init_substream_urbs(subs, params_period_bytes(hw_params),
+						  params_rate(hw_params),
+						  snd_pcm_format_physical_width(params_format(hw_params)) *
+							params_channels(hw_params));
+	}
+
+	return ret;
+}
+
+/*
+ * hw_free callback
+ *
+ * reset the audio format and release the buffer
+ */
+static int snd_usb_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_usb_substream *subs = substream->runtime->private_data;
+
+	subs->cur_audiofmt = NULL;
+	subs->cur_rate = 0;
+	subs->period_bytes = 0;
+	if (!subs->stream->chip->shutdown)
+		snd_usb_release_substream_urbs(subs, 0);
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+/*
+ * prepare callback
+ *
+ * only a few subtle things...
+ */
+static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usb_substream *subs = runtime->private_data;
+
+	if (! subs->cur_audiofmt) {
+		snd_printk(KERN_ERR "usbaudio: no format is specified!\n");
+		return -ENXIO;
+	}
+
+	/* some unit conversions in runtime */
+	subs->maxframesize = bytes_to_frames(runtime, subs->maxpacksize);
+	subs->curframesize = bytes_to_frames(runtime, subs->curpacksize);
+
+	/* reset the pointer */
+	subs->hwptr_done = 0;
+	subs->transfer_done = 0;
+	subs->phase = 0;
+	runtime->delay = 0;
+
+	return snd_usb_substream_prepare(subs, runtime);
+}
+
+static struct snd_pcm_hardware snd_usb_hardware =
+{
+	.info =			SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_MMAP_VALID |
+				SNDRV_PCM_INFO_BATCH |
+				SNDRV_PCM_INFO_INTERLEAVED |
+				SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				SNDRV_PCM_INFO_PAUSE,
+	.buffer_bytes_max =	1024 * 1024,
+	.period_bytes_min =	64,
+	.period_bytes_max =	512 * 1024,
+	.periods_min =		2,
+	.periods_max =		1024,
+};
+
+static int hw_check_valid_format(struct snd_usb_substream *subs,
+				 struct snd_pcm_hw_params *params,
+				 struct audioformat *fp)
+{
+	struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	struct snd_interval *ct = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_mask *fmts = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	struct snd_interval *pt = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME);
+	struct snd_mask check_fmts;
+	unsigned int ptime;
+
+	/* check the format */
+	snd_mask_none(&check_fmts);
+	check_fmts.bits[0] = (u32)fp->formats;
+	check_fmts.bits[1] = (u32)(fp->formats >> 32);
+	snd_mask_intersect(&check_fmts, fmts);
+	if (snd_mask_empty(&check_fmts)) {
+		hwc_debug("   > check: no supported format %d\n", fp->format);
+		return 0;
+	}
+	/* check the channels */
+	if (fp->channels < ct->min || fp->channels > ct->max) {
+		hwc_debug("   > check: no valid channels %d (%d/%d)\n", fp->channels, ct->min, ct->max);
+		return 0;
+	}
+	/* check the rate is within the range */
+	if (fp->rate_min > it->max || (fp->rate_min == it->max && it->openmax)) {
+		hwc_debug("   > check: rate_min %d > max %d\n", fp->rate_min, it->max);
+		return 0;
+	}
+	if (fp->rate_max < it->min || (fp->rate_max == it->min && it->openmin)) {
+		hwc_debug("   > check: rate_max %d < min %d\n", fp->rate_max, it->min);
+		return 0;
+	}
+	/* check whether the period time is >= the data packet interval */
+	if (snd_usb_get_speed(subs->dev) != USB_SPEED_FULL) {
+		ptime = 125 * (1 << fp->datainterval);
+		if (ptime > pt->max || (ptime == pt->max && pt->openmax)) {
+			hwc_debug("   > check: ptime %u > max %u\n", ptime, pt->max);
+			return 0;
+		}
+	}
+	return 1;
+}
+
+static int hw_rule_rate(struct snd_pcm_hw_params *params,
+			struct snd_pcm_hw_rule *rule)
+{
+	struct snd_usb_substream *subs = rule->private;
+	struct list_head *p;
+	struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	unsigned int rmin, rmax;
+	int changed;
+
+	hwc_debug("hw_rule_rate: (%d,%d)\n", it->min, it->max);
+	changed = 0;
+	rmin = rmax = 0;
+	list_for_each(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		fp = list_entry(p, struct audioformat, list);
+		if (!hw_check_valid_format(subs, params, fp))
+			continue;
+		if (changed++) {
+			if (rmin > fp->rate_min)
+				rmin = fp->rate_min;
+			if (rmax < fp->rate_max)
+				rmax = fp->rate_max;
+		} else {
+			rmin = fp->rate_min;
+			rmax = fp->rate_max;
+		}
+	}
+
+	if (!changed) {
+		hwc_debug("  --> get empty\n");
+		it->empty = 1;
+		return -EINVAL;
+	}
+
+	changed = 0;
+	if (it->min < rmin) {
+		it->min = rmin;
+		it->openmin = 0;
+		changed = 1;
+	}
+	if (it->max > rmax) {
+		it->max = rmax;
+		it->openmax = 0;
+		changed = 1;
+	}
+	if (snd_interval_checkempty(it)) {
+		it->empty = 1;
+		return -EINVAL;
+	}
+	hwc_debug("  --> (%d, %d) (changed = %d)\n", it->min, it->max, changed);
+	return changed;
+}
+
+
+static int hw_rule_channels(struct snd_pcm_hw_params *params,
+			    struct snd_pcm_hw_rule *rule)
+{
+	struct snd_usb_substream *subs = rule->private;
+	struct list_head *p;
+	struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	unsigned int rmin, rmax;
+	int changed;
+
+	hwc_debug("hw_rule_channels: (%d,%d)\n", it->min, it->max);
+	changed = 0;
+	rmin = rmax = 0;
+	list_for_each(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		fp = list_entry(p, struct audioformat, list);
+		if (!hw_check_valid_format(subs, params, fp))
+			continue;
+		if (changed++) {
+			if (rmin > fp->channels)
+				rmin = fp->channels;
+			if (rmax < fp->channels)
+				rmax = fp->channels;
+		} else {
+			rmin = fp->channels;
+			rmax = fp->channels;
+		}
+	}
+
+	if (!changed) {
+		hwc_debug("  --> get empty\n");
+		it->empty = 1;
+		return -EINVAL;
+	}
+
+	changed = 0;
+	if (it->min < rmin) {
+		it->min = rmin;
+		it->openmin = 0;
+		changed = 1;
+	}
+	if (it->max > rmax) {
+		it->max = rmax;
+		it->openmax = 0;
+		changed = 1;
+	}
+	if (snd_interval_checkempty(it)) {
+		it->empty = 1;
+		return -EINVAL;
+	}
+	hwc_debug("  --> (%d, %d) (changed = %d)\n", it->min, it->max, changed);
+	return changed;
+}
+
+static int hw_rule_format(struct snd_pcm_hw_params *params,
+			  struct snd_pcm_hw_rule *rule)
+{
+	struct snd_usb_substream *subs = rule->private;
+	struct list_head *p;
+	struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	u64 fbits;
+	u32 oldbits[2];
+	int changed;
+
+	hwc_debug("hw_rule_format: %x:%x\n", fmt->bits[0], fmt->bits[1]);
+	fbits = 0;
+	list_for_each(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		fp = list_entry(p, struct audioformat, list);
+		if (!hw_check_valid_format(subs, params, fp))
+			continue;
+		fbits |= fp->formats;
+	}
+
+	oldbits[0] = fmt->bits[0];
+	oldbits[1] = fmt->bits[1];
+	fmt->bits[0] &= (u32)fbits;
+	fmt->bits[1] &= (u32)(fbits >> 32);
+	if (!fmt->bits[0] && !fmt->bits[1]) {
+		hwc_debug("  --> get empty\n");
+		return -EINVAL;
+	}
+	changed = (oldbits[0] != fmt->bits[0] || oldbits[1] != fmt->bits[1]);
+	hwc_debug("  --> %x:%x (changed = %d)\n", fmt->bits[0], fmt->bits[1], changed);
+	return changed;
+}
+
+static int hw_rule_period_time(struct snd_pcm_hw_params *params,
+			       struct snd_pcm_hw_rule *rule)
+{
+	struct snd_usb_substream *subs = rule->private;
+	struct audioformat *fp;
+	struct snd_interval *it;
+	unsigned char min_datainterval;
+	unsigned int pmin;
+	int changed;
+
+	it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME);
+	hwc_debug("hw_rule_period_time: (%u,%u)\n", it->min, it->max);
+	min_datainterval = 0xff;
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		if (!hw_check_valid_format(subs, params, fp))
+			continue;
+		min_datainterval = min(min_datainterval, fp->datainterval);
+	}
+	if (min_datainterval == 0xff) {
+		hwc_debug("  --> get empty\n");
+		it->empty = 1;
+		return -EINVAL;
+	}
+	pmin = 125 * (1 << min_datainterval);
+	changed = 0;
+	if (it->min < pmin) {
+		it->min = pmin;
+		it->openmin = 0;
+		changed = 1;
+	}
+	if (snd_interval_checkempty(it)) {
+		it->empty = 1;
+		return -EINVAL;
+	}
+	hwc_debug("  --> (%u,%u) (changed = %d)\n", it->min, it->max, changed);
+	return changed;
+}
+
+/*
+ *  If the device supports unusual bit rates, does the request meet these?
+ */
+static int snd_usb_pcm_check_knot(struct snd_pcm_runtime *runtime,
+				  struct snd_usb_substream *subs)
+{
+	struct audioformat *fp;
+	int count = 0, needs_knot = 0;
+	int err;
+
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)
+			return 0;
+		count += fp->nr_rates;
+		if (fp->rates & SNDRV_PCM_RATE_KNOT)
+			needs_knot = 1;
+	}
+	if (!needs_knot)
+		return 0;
+
+	subs->rate_list.list = kmalloc(sizeof(int) * count, GFP_KERNEL);
+	if (!subs->rate_list.list)
+		return -ENOMEM;
+	subs->rate_list.count = count;
+	subs->rate_list.mask = 0;
+	count = 0;
+	list_for_each_entry(fp, &subs->fmt_list, list) {
+		int i;
+		for (i = 0; i < fp->nr_rates; i++)
+			subs->rate_list.list[count++] = fp->rate_table[i];
+	}
+	err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+					 &subs->rate_list);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+
+/*
+ * set up the runtime hardware information.
+ */
+
+static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substream *subs)
+{
+	struct list_head *p;
+	unsigned int pt, ptmin;
+	int param_period_time_if_needed;
+	int err;
+
+	runtime->hw.formats = subs->formats;
+
+	runtime->hw.rate_min = 0x7fffffff;
+	runtime->hw.rate_max = 0;
+	runtime->hw.channels_min = 256;
+	runtime->hw.channels_max = 0;
+	runtime->hw.rates = 0;
+	ptmin = UINT_MAX;
+	/* check min/max rates and channels */
+	list_for_each(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		fp = list_entry(p, struct audioformat, list);
+		runtime->hw.rates |= fp->rates;
+		if (runtime->hw.rate_min > fp->rate_min)
+			runtime->hw.rate_min = fp->rate_min;
+		if (runtime->hw.rate_max < fp->rate_max)
+			runtime->hw.rate_max = fp->rate_max;
+		if (runtime->hw.channels_min > fp->channels)
+			runtime->hw.channels_min = fp->channels;
+		if (runtime->hw.channels_max < fp->channels)
+			runtime->hw.channels_max = fp->channels;
+		if (fp->fmt_type == UAC_FORMAT_TYPE_II && fp->frame_size > 0) {
+			/* FIXME: there might be more than one audio formats... */
+			runtime->hw.period_bytes_min = runtime->hw.period_bytes_max =
+				fp->frame_size;
+		}
+		pt = 125 * (1 << fp->datainterval);
+		ptmin = min(ptmin, pt);
+	}
+
+	param_period_time_if_needed = SNDRV_PCM_HW_PARAM_PERIOD_TIME;
+	if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL)
+		/* full speed devices have fixed data packet interval */
+		ptmin = 1000;
+	if (ptmin == 1000)
+		/* if period time doesn't go below 1 ms, no rules needed */
+		param_period_time_if_needed = -1;
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+				     ptmin, UINT_MAX);
+
+	if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				       hw_rule_rate, subs,
+				       SNDRV_PCM_HW_PARAM_FORMAT,
+				       SNDRV_PCM_HW_PARAM_CHANNELS,
+				       param_period_time_if_needed,
+				       -1)) < 0)
+		return err;
+	if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				       hw_rule_channels, subs,
+				       SNDRV_PCM_HW_PARAM_FORMAT,
+				       SNDRV_PCM_HW_PARAM_RATE,
+				       param_period_time_if_needed,
+				       -1)) < 0)
+		return err;
+	if ((err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
+				       hw_rule_format, subs,
+				       SNDRV_PCM_HW_PARAM_RATE,
+				       SNDRV_PCM_HW_PARAM_CHANNELS,
+				       param_period_time_if_needed,
+				       -1)) < 0)
+		return err;
+	if (param_period_time_if_needed >= 0) {
+		err = snd_pcm_hw_rule_add(runtime, 0,
+					  SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+					  hw_rule_period_time, subs,
+					  SNDRV_PCM_HW_PARAM_FORMAT,
+					  SNDRV_PCM_HW_PARAM_CHANNELS,
+					  SNDRV_PCM_HW_PARAM_RATE,
+					  -1);
+		if (err < 0)
+			return err;
+	}
+	if ((err = snd_usb_pcm_check_knot(runtime, subs)) < 0)
+		return err;
+	return 0;
+}
+
+static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction)
+{
+	struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usb_substream *subs = &as->substream[direction];
+
+	subs->interface = -1;
+	subs->altset_idx = 0;
+	runtime->hw = snd_usb_hardware;
+	runtime->private_data = subs;
+	subs->pcm_substream = substream;
+	return setup_hw_info(runtime, subs);
+}
+
+static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction)
+{
+	struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
+	struct snd_usb_substream *subs = &as->substream[direction];
+
+	if (!as->chip->shutdown && subs->interface >= 0) {
+		usb_set_interface(subs->dev, subs->interface, 0);
+		subs->interface = -1;
+	}
+	subs->pcm_substream = NULL;
+	return 0;
+}
+
+static int snd_usb_playback_open(struct snd_pcm_substream *substream)
+{
+	return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+static int snd_usb_playback_close(struct snd_pcm_substream *substream)
+{
+	return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+static int snd_usb_capture_open(struct snd_pcm_substream *substream)
+{
+	return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static int snd_usb_capture_close(struct snd_pcm_substream *substream)
+{
+	return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static struct snd_pcm_ops snd_usb_playback_ops = {
+	.open =		snd_usb_playback_open,
+	.close =	snd_usb_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usb_hw_params,
+	.hw_free =	snd_usb_hw_free,
+	.prepare =	snd_usb_pcm_prepare,
+	.trigger =	snd_usb_substream_playback_trigger,
+	.pointer =	snd_usb_pcm_pointer,
+	.page =		snd_pcm_lib_get_vmalloc_page,
+	.mmap =		snd_pcm_lib_mmap_vmalloc,
+};
+
+static struct snd_pcm_ops snd_usb_capture_ops = {
+	.open =		snd_usb_capture_open,
+	.close =	snd_usb_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usb_hw_params,
+	.hw_free =	snd_usb_hw_free,
+	.prepare =	snd_usb_pcm_prepare,
+	.trigger =	snd_usb_substream_capture_trigger,
+	.pointer =	snd_usb_pcm_pointer,
+	.page =		snd_pcm_lib_get_vmalloc_page,
+	.mmap =		snd_pcm_lib_mmap_vmalloc,
+};
+
+void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream)
+{
+	snd_pcm_set_ops(pcm, stream,
+			stream == SNDRV_PCM_STREAM_PLAYBACK ?
+			&snd_usb_playback_ops : &snd_usb_capture_ops);
+}
diff --git a/linux/sound/usb/pcm.h b/linux/sound/usb/pcm.h
new file mode 100644
index 0000000..ed3e283
--- /dev/null
+++ b/linux/sound/usb/pcm.h
@@ -0,0 +1,11 @@
+#ifndef __USBAUDIO_PCM_H
+#define __USBAUDIO_PCM_H
+
+void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream);
+
+int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
+		       struct usb_host_interface *alts,
+		       struct audioformat *fmt);
+
+
+#endif /* __USBAUDIO_PCM_H */
diff --git a/linux/sound/usb/proc.c b/linux/sound/usb/proc.c
new file mode 100644
index 0000000..961c9a2
--- /dev/null
+++ b/linux/sound/usb/proc.c
@@ -0,0 +1,173 @@
+/*
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/usb.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "helper.h"
+#include "card.h"
+#include "proc.h"
+
+/* convert our full speed USB rate into sampling rate in Hz */
+static inline unsigned get_full_speed_hz(unsigned int usb_rate)
+{
+	return (usb_rate * 125 + (1 << 12)) >> 13;
+}
+
+/* convert our high speed USB rate into sampling rate in Hz */
+static inline unsigned get_high_speed_hz(unsigned int usb_rate)
+{
+	return (usb_rate * 125 + (1 << 9)) >> 10;
+}
+
+/*
+ * common proc files to show the usb device info
+ */
+static void proc_audio_usbbus_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_usb_audio *chip = entry->private_data;
+	if (!chip->shutdown)
+		snd_iprintf(buffer, "%03d/%03d\n", chip->dev->bus->busnum, chip->dev->devnum);
+}
+
+static void proc_audio_usbid_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_usb_audio *chip = entry->private_data;
+	if (!chip->shutdown)
+		snd_iprintf(buffer, "%04x:%04x\n", 
+			    USB_ID_VENDOR(chip->usb_id),
+			    USB_ID_PRODUCT(chip->usb_id));
+}
+
+void snd_usb_audio_create_proc(struct snd_usb_audio *chip)
+{
+	struct snd_info_entry *entry;
+	if (!snd_card_proc_new(chip->card, "usbbus", &entry))
+		snd_info_set_text_ops(entry, chip, proc_audio_usbbus_read);
+	if (!snd_card_proc_new(chip->card, "usbid", &entry))
+		snd_info_set_text_ops(entry, chip, proc_audio_usbid_read);
+}
+
+/*
+ * proc interface for list the supported pcm formats
+ */
+static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct snd_info_buffer *buffer)
+{
+	struct list_head *p;
+	static char *sync_types[4] = {
+		"NONE", "ASYNC", "ADAPTIVE", "SYNC"
+	};
+
+	list_for_each(p, &subs->fmt_list) {
+		struct audioformat *fp;
+		snd_pcm_format_t fmt;
+		fp = list_entry(p, struct audioformat, list);
+		snd_iprintf(buffer, "  Interface %d\n", fp->iface);
+		snd_iprintf(buffer, "    Altset %d\n", fp->altsetting);
+		snd_iprintf(buffer, "    Format:");
+		for (fmt = 0; fmt <= SNDRV_PCM_FORMAT_LAST; ++fmt)
+			if (fp->formats & (1uLL << fmt))
+				snd_iprintf(buffer, " %s",
+					    snd_pcm_format_name(fmt));
+		snd_iprintf(buffer, "\n");
+		snd_iprintf(buffer, "    Channels: %d\n", fp->channels);
+		snd_iprintf(buffer, "    Endpoint: %d %s (%s)\n",
+			    fp->endpoint & USB_ENDPOINT_NUMBER_MASK,
+			    fp->endpoint & USB_DIR_IN ? "IN" : "OUT",
+			    sync_types[(fp->ep_attr & USB_ENDPOINT_SYNCTYPE) >> 2]);
+		if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) {
+			snd_iprintf(buffer, "    Rates: %d - %d (continuous)\n",
+				    fp->rate_min, fp->rate_max);
+		} else {
+			unsigned int i;
+			snd_iprintf(buffer, "    Rates: ");
+			for (i = 0; i < fp->nr_rates; i++) {
+				if (i > 0)
+					snd_iprintf(buffer, ", ");
+				snd_iprintf(buffer, "%d", fp->rate_table[i]);
+			}
+			snd_iprintf(buffer, "\n");
+		}
+		if (snd_usb_get_speed(subs->dev) != USB_SPEED_FULL)
+			snd_iprintf(buffer, "    Data packet interval: %d us\n",
+				    125 * (1 << fp->datainterval));
+		// snd_iprintf(buffer, "    Max Packet Size = %d\n", fp->maxpacksize);
+		// snd_iprintf(buffer, "    EP Attribute = %#x\n", fp->attributes);
+	}
+}
+
+static void proc_dump_substream_status(struct snd_usb_substream *subs, struct snd_info_buffer *buffer)
+{
+	if (subs->running) {
+		unsigned int i;
+		snd_iprintf(buffer, "  Status: Running\n");
+		snd_iprintf(buffer, "    Interface = %d\n", subs->interface);
+		snd_iprintf(buffer, "    Altset = %d\n", subs->altset_idx);
+		snd_iprintf(buffer, "    URBs = %d [ ", subs->nurbs);
+		for (i = 0; i < subs->nurbs; i++)
+			snd_iprintf(buffer, "%d ", subs->dataurb[i].packets);
+		snd_iprintf(buffer, "]\n");
+		snd_iprintf(buffer, "    Packet Size = %d\n", subs->curpacksize);
+		snd_iprintf(buffer, "    Momentary freq = %u Hz (%#x.%04x)\n",
+			    snd_usb_get_speed(subs->dev) == USB_SPEED_FULL
+			    ? get_full_speed_hz(subs->freqm)
+			    : get_high_speed_hz(subs->freqm),
+			    subs->freqm >> 16, subs->freqm & 0xffff);
+		if (subs->freqshift != INT_MIN)
+			snd_iprintf(buffer, "    Feedback Format = %d.%d\n",
+				    (subs->syncmaxsize > 3 ? 32 : 24)
+						- (16 - subs->freqshift),
+				    16 - subs->freqshift);
+	} else {
+		snd_iprintf(buffer, "  Status: Stop\n");
+	}
+}
+
+static void proc_pcm_format_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_usb_stream *stream = entry->private_data;
+
+	snd_iprintf(buffer, "%s : %s\n", stream->chip->card->longname, stream->pcm->name);
+
+	if (stream->substream[SNDRV_PCM_STREAM_PLAYBACK].num_formats) {
+		snd_iprintf(buffer, "\nPlayback:\n");
+		proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer);
+		proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer);
+	}
+	if (stream->substream[SNDRV_PCM_STREAM_CAPTURE].num_formats) {
+		snd_iprintf(buffer, "\nCapture:\n");
+		proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer);
+		proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer);
+	}
+}
+
+void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream)
+{
+	struct snd_info_entry *entry;
+	char name[32];
+	struct snd_card *card = stream->chip->card;
+
+	sprintf(name, "stream%d", stream->pcm_index);
+	if (!snd_card_proc_new(card, name, &entry))
+		snd_info_set_text_ops(entry, stream, proc_pcm_format_read);
+}
+
diff --git a/linux/sound/usb/proc.h b/linux/sound/usb/proc.h
new file mode 100644
index 0000000..a45b765
--- /dev/null
+++ b/linux/sound/usb/proc.h
@@ -0,0 +1,8 @@
+#ifndef __USBAUDIO_PROC_H
+#define __USBAUDIO_PROC_H
+
+void snd_usb_audio_create_proc(struct snd_usb_audio *chip);
+void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream);
+
+#endif /* __USBAUDIO_PROC_H */
+
diff --git a/linux/sound/usb/quirks-table.h b/linux/sound/usb/quirks-table.h
new file mode 100644
index 0000000..ad7079d
--- /dev/null
+++ b/linux/sound/usb/quirks-table.h
@@ -0,0 +1,2495 @@
+/*
+ * ALSA USB Audio Driver
+ *
+ * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>,
+ *                       Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+/*
+ * The contents of this file are part of the driver's id_table.
+ *
+ * In a perfect world, this file would be empty.
+ */
+
+/*
+ * Use this for devices where other interfaces are standard compliant,
+ * to prevent the quirk being applied to those interfaces. (To work with
+ * hotplugging, bDeviceClass must be set to USB_CLASS_PER_INTERFACE.)
+ */
+#define USB_DEVICE_VENDOR_SPEC(vend, prod) \
+	.match_flags = USB_DEVICE_ID_MATCH_VENDOR | \
+		       USB_DEVICE_ID_MATCH_PRODUCT | \
+		       USB_DEVICE_ID_MATCH_INT_CLASS, \
+	.idVendor = vend, \
+	.idProduct = prod, \
+	.bInterfaceClass = USB_CLASS_VENDOR_SPEC
+
+/* Creative/Toshiba Multimedia Center SB-0500 */
+{
+	USB_DEVICE(0x041e, 0x3048),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Toshiba",
+		.product_name = "SB-0500",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+
+/* Creative/E-Mu devices */
+{
+	USB_DEVICE(0x041e, 0x3010),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Creative Labs",
+		.product_name = "Sound Blaster MP3+",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+{
+	/* E-Mu 0202 USB */
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor = 0x041e,
+	.idProduct = 0x3f02,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+},
+{
+	/* E-Mu 0404 USB */
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor = 0x041e,
+	.idProduct = 0x3f04,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+},
+{
+	/* E-Mu Tracker Pre */
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor = 0x041e,
+	.idProduct = 0x3f0a,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+},
+
+/*
+ * Logitech QuickCam: bDeviceClass is vendor-specific, so generic interface
+ * class matches do not take effect without an explicit ID match.
+ */
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x0850,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x08ae,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x08c6,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x08f0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x08f5,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.idVendor = 0x046d,
+	.idProduct = 0x08f6,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+},
+{
+	USB_DEVICE(0x046d, 0x0990),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Logitech, Inc.",
+		.product_name = "QuickCam Pro 9000",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+
+/*
+ * Yamaha devices
+ */
+
+#define YAMAHA_DEVICE(id, name) { \
+	USB_DEVICE(0x0499, id), \
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \
+		.vendor_name = "Yamaha", \
+		.product_name = name, \
+		.ifnum = QUIRK_ANY_INTERFACE, \
+		.type = QUIRK_MIDI_YAMAHA \
+	} \
+}
+#define YAMAHA_INTERFACE(id, intf, name) { \
+	USB_DEVICE_VENDOR_SPEC(0x0499, id), \
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \
+		.vendor_name = "Yamaha", \
+		.product_name = name, \
+		.ifnum = intf, \
+		.type = QUIRK_MIDI_YAMAHA \
+	} \
+}
+YAMAHA_DEVICE(0x1000, "UX256"),
+YAMAHA_DEVICE(0x1001, "MU1000"),
+YAMAHA_DEVICE(0x1002, "MU2000"),
+YAMAHA_DEVICE(0x1003, "MU500"),
+YAMAHA_INTERFACE(0x1004, 3, "UW500"),
+YAMAHA_DEVICE(0x1005, "MOTIF6"),
+YAMAHA_DEVICE(0x1006, "MOTIF7"),
+YAMAHA_DEVICE(0x1007, "MOTIF8"),
+YAMAHA_DEVICE(0x1008, "UX96"),
+YAMAHA_DEVICE(0x1009, "UX16"),
+YAMAHA_INTERFACE(0x100a, 3, "EOS BX"),
+YAMAHA_DEVICE(0x100c, "UC-MX"),
+YAMAHA_DEVICE(0x100d, "UC-KX"),
+YAMAHA_DEVICE(0x100e, "S08"),
+YAMAHA_DEVICE(0x100f, "CLP-150"),
+YAMAHA_DEVICE(0x1010, "CLP-170"),
+YAMAHA_DEVICE(0x1011, "P-250"),
+YAMAHA_DEVICE(0x1012, "TYROS"),
+YAMAHA_DEVICE(0x1013, "PF-500"),
+YAMAHA_DEVICE(0x1014, "S90"),
+YAMAHA_DEVICE(0x1015, "MOTIF-R"),
+YAMAHA_DEVICE(0x1016, "MDP-5"),
+YAMAHA_DEVICE(0x1017, "CVP-204"),
+YAMAHA_DEVICE(0x1018, "CVP-206"),
+YAMAHA_DEVICE(0x1019, "CVP-208"),
+YAMAHA_DEVICE(0x101a, "CVP-210"),
+YAMAHA_DEVICE(0x101b, "PSR-1100"),
+YAMAHA_DEVICE(0x101c, "PSR-2100"),
+YAMAHA_DEVICE(0x101d, "CLP-175"),
+YAMAHA_DEVICE(0x101e, "PSR-K1"),
+YAMAHA_DEVICE(0x101f, "EZ-J24"),
+YAMAHA_DEVICE(0x1020, "EZ-250i"),
+YAMAHA_DEVICE(0x1021, "MOTIF ES 6"),
+YAMAHA_DEVICE(0x1022, "MOTIF ES 7"),
+YAMAHA_DEVICE(0x1023, "MOTIF ES 8"),
+YAMAHA_DEVICE(0x1024, "CVP-301"),
+YAMAHA_DEVICE(0x1025, "CVP-303"),
+YAMAHA_DEVICE(0x1026, "CVP-305"),
+YAMAHA_DEVICE(0x1027, "CVP-307"),
+YAMAHA_DEVICE(0x1028, "CVP-309"),
+YAMAHA_DEVICE(0x1029, "CVP-309GP"),
+YAMAHA_DEVICE(0x102a, "PSR-1500"),
+YAMAHA_DEVICE(0x102b, "PSR-3000"),
+YAMAHA_DEVICE(0x102e, "ELS-01/01C"),
+YAMAHA_DEVICE(0x1030, "PSR-295/293"),
+YAMAHA_DEVICE(0x1031, "DGX-205/203"),
+YAMAHA_DEVICE(0x1032, "DGX-305"),
+YAMAHA_DEVICE(0x1033, "DGX-505"),
+YAMAHA_DEVICE(0x1034, NULL),
+YAMAHA_DEVICE(0x1035, NULL),
+YAMAHA_DEVICE(0x1036, NULL),
+YAMAHA_DEVICE(0x1037, NULL),
+YAMAHA_DEVICE(0x1038, NULL),
+YAMAHA_DEVICE(0x1039, NULL),
+YAMAHA_DEVICE(0x103a, NULL),
+YAMAHA_DEVICE(0x103b, NULL),
+YAMAHA_DEVICE(0x103c, NULL),
+YAMAHA_DEVICE(0x103d, NULL),
+YAMAHA_DEVICE(0x103e, NULL),
+YAMAHA_DEVICE(0x103f, NULL),
+YAMAHA_DEVICE(0x1040, NULL),
+YAMAHA_DEVICE(0x1041, NULL),
+YAMAHA_DEVICE(0x1042, NULL),
+YAMAHA_DEVICE(0x1043, NULL),
+YAMAHA_DEVICE(0x1044, NULL),
+YAMAHA_DEVICE(0x1045, NULL),
+YAMAHA_INTERFACE(0x104e, 0, NULL),
+YAMAHA_DEVICE(0x104f, NULL),
+YAMAHA_DEVICE(0x1050, NULL),
+YAMAHA_DEVICE(0x1051, NULL),
+YAMAHA_DEVICE(0x1052, NULL),
+YAMAHA_INTERFACE(0x1053, 0, NULL),
+YAMAHA_INTERFACE(0x1054, 0, NULL),
+YAMAHA_DEVICE(0x1055, NULL),
+YAMAHA_DEVICE(0x1056, NULL),
+YAMAHA_DEVICE(0x1057, NULL),
+YAMAHA_DEVICE(0x1058, NULL),
+YAMAHA_DEVICE(0x1059, NULL),
+YAMAHA_DEVICE(0x105a, NULL),
+YAMAHA_DEVICE(0x105b, NULL),
+YAMAHA_DEVICE(0x105c, NULL),
+YAMAHA_DEVICE(0x105d, NULL),
+YAMAHA_DEVICE(0x2000, "DGP-7"),
+YAMAHA_DEVICE(0x2001, "DGP-5"),
+YAMAHA_DEVICE(0x2002, NULL),
+YAMAHA_DEVICE(0x2003, NULL),
+YAMAHA_DEVICE(0x5000, "CS1D"),
+YAMAHA_DEVICE(0x5001, "DSP1D"),
+YAMAHA_DEVICE(0x5002, "DME32"),
+YAMAHA_DEVICE(0x5003, "DM2000"),
+YAMAHA_DEVICE(0x5004, "02R96"),
+YAMAHA_DEVICE(0x5005, "ACU16-C"),
+YAMAHA_DEVICE(0x5006, "NHB32-C"),
+YAMAHA_DEVICE(0x5007, "DM1000"),
+YAMAHA_DEVICE(0x5008, "01V96"),
+YAMAHA_DEVICE(0x5009, "SPX2000"),
+YAMAHA_DEVICE(0x500a, "PM5D"),
+YAMAHA_DEVICE(0x500b, "DME64N"),
+YAMAHA_DEVICE(0x500c, "DME24N"),
+YAMAHA_DEVICE(0x500d, NULL),
+YAMAHA_DEVICE(0x500e, NULL),
+YAMAHA_DEVICE(0x500f, NULL),
+YAMAHA_DEVICE(0x7000, "DTX"),
+YAMAHA_DEVICE(0x7010, "UB99"),
+#undef YAMAHA_DEVICE
+#undef YAMAHA_INTERFACE
+
+/*
+ * Roland/RolandED/Edirol/BOSS devices
+ */
+{
+	USB_DEVICE(0x0582, 0x0000),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "UA-100",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S16_LE,
+					.channels = 4,
+					.iface = 0,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0,
+					.endpoint = 0x01,
+					.ep_attr = 0x09,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S16_LE,
+					.channels = 2,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_FILL_MAX,
+					.endpoint = 0x81,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0007,
+					.in_cables  = 0x0007
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0002),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-4",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x000f,
+					.in_cables  = 0x000f
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0003),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "SC-8850",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x003f,
+					.in_cables  = 0x003f
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0004),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "U-8",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0005,
+					.in_cables  = 0x0005
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* Has ID 0x0099 when not in "Advanced Driver" mode.
+	 * The UM-2EX has only one input, but we cannot detect this. */
+	USB_DEVICE(0x0582, 0x0005),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-2",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0003,
+					.in_cables  = 0x0003
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0007),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "SC-8820",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0013,
+					.in_cables  = 0x0013
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0008),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "PC-300",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x009d when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0009),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-1",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x000b),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "SK-500",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0013,
+					.in_cables  = 0x0013
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* thanks to Emiliano Grilli <emillo@libero.it>
+	 * for helping researching this data */
+	USB_DEVICE(0x0582, 0x000c),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "SC-D70",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 2,
+					.iface = 0,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0,
+					.endpoint = 0x01,
+					.ep_attr = 0x01,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 2,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0,
+					.endpoint = 0x81,
+					.ep_attr = 0x01,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0007,
+					.in_cables  = 0x0007
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{	/*
+	 * This quirk is for the "Advanced Driver" mode of the Edirol UA-5.
+	 * If the advanced mode switch at the back of the unit is off, the
+	 * UA-5 has ID 0x0582/0x0011 and is standard compliant (no quirks),
+	 * but offers only 16-bit PCM.
+	 * In advanced mode, the UA-5 will output S24_3LE samples (two
+	 * channels) at the rate indicated on the front switch, including
+	 * the 96kHz sample rate.
+	 */
+	USB_DEVICE(0x0582, 0x0010),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-5",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x0013 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0012),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "XV-5050",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x0015 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0014),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-880",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x01ff,
+			.in_cables  = 0x01ff
+		}
+	}
+},
+{
+	/* has ID 0x0017 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0016),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "SD-90",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x000f,
+					.in_cables  = 0x000f
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x001c when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x001b),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "MMP-2",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x001e when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x001d),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "V-SYNTH",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x0024 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0023),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-550",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x003f,
+			.in_cables  = 0x003f
+		}
+	}
+},
+{
+	/*
+	 * This quirk is for the "Advanced Driver" mode. If off, the UA-20
+	 * has ID 0x0026 and is standard compliant, but has only 16-bit PCM
+	 * and no MIDI.
+	 */
+	USB_DEVICE(0x0582, 0x0025),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-20",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 2,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0,
+					.endpoint = 0x01,
+					.ep_attr = 0x01,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 2,
+					.iface = 2,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = 0,
+					.endpoint = 0x82,
+					.ep_attr = 0x01,
+					.rates = SNDRV_PCM_RATE_CONTINUOUS,
+					.rate_min = 44100,
+					.rate_max = 44100,
+				}
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x0028 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0027),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "SD-20",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	/* has ID 0x002a when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0029),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "SD-80",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x000f,
+			.in_cables  = 0x000f
+		}
+	}
+},
+{	/*
+	 * This quirk is for the "Advanced" modes of the Edirol UA-700.
+	 * If the sample format switch is not in an advanced setting, the
+	 * UA-700 has ID 0x0582/0x002c and is standard compliant (no quirks),
+	 * but offers only 16-bit PCM and no MIDI.
+	 */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x002b),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-700",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x002e when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x002d),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "XV-2020",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x0030 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x002f),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "VariOS",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0007,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	/* has ID 0x0034 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0033),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "PCR",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+	/* TODO: add Roland M-1000 support */
+{
+	/*
+	 * Has ID 0x0038 when not in "Advanced Driver" mode;
+	 * later revisions use IDs 0x0054 and 0x00a2.
+	 */
+	USB_DEVICE(0x0582, 0x0037),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "Digital Piano",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/*
+	 * This quirk is for the "Advanced Driver" mode.  If off, the GS-10
+	 * has ID 0x003c and is standard compliant, but has only 16-bit PCM
+	 * and no MIDI.
+	 */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x003b),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "BOSS",
+		.product_name = "GS-10",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x0041 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0040),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "GI-20",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x0043 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0042),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "RS-70",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x0049 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0047),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "EDIROL", */
+		/* .product_name = "UR-80", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			/* in the 96 kHz modes, only interface 1 is there */
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x004a when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0048),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "EDIROL", */
+		/* .product_name = "UR-80", */
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+	/* TODO: add Edirol M-100FX support */
+{
+	/* has ID 0x004e when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x004c),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "PCR-A",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x004f when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x004d),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "PCR-A",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	/*
+	 * This quirk is for the "Advanced Driver" mode. If off, the UA-3FX
+	 * is standard compliant, but has only 16-bit PCM.
+	 */
+	USB_DEVICE(0x0582, 0x0050),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-3FX",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0052),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-1SX",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0060),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "EXR Series",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+{
+	/* has ID 0x0066 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0064),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "EDIROL", */
+		/* .product_name = "PCR-1", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x0067 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0065),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "EDIROL", */
+		/* .product_name = "PCR-1", */
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	/* has ID 0x006b when not in "Advanced Driver" mode */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x006a),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "SP-606",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x006e when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x006d),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "FANTOM-X",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{	/*
+	 * This quirk is for the "Advanced" modes of the Edirol UA-25.
+	 * If the switch is not in an advanced setting, the UA-25 has
+	 * ID 0x0582/0x0073 and is standard compliant (no quirks), but
+	 * offers only 16-bit PCM at 44.1 kHz and no MIDI.
+	 */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x0074),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-25",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x0076 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0075),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "BOSS",
+		.product_name = "DR-880",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* has ID 0x007b when not in "Advanced Driver" mode */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x007a),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		/* "RD" or "RD-700SX"? */
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	/* has ID 0x0081 when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x0080),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "G-70",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+	/* TODO: add Roland V-SYNTH XT support */
+	/* TODO: add BOSS GT-PRO support */
+{
+	/* has ID 0x008c when not in "Advanced Driver" mode */
+	USB_DEVICE(0x0582, 0x008b),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "PC-50",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+	/* TODO: add Edirol PC-80 support */
+{
+	USB_DEVICE(0x0582, 0x0096),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-1EX",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x009a),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UM-3EX",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x000f,
+			.in_cables  = 0x000f
+		}
+	}
+},
+{
+	/*
+	 * This quirk is for the "Advanced Driver" mode. If off, the UA-4FX
+	 * is standard compliant, but has only 16-bit PCM and no MIDI.
+	 */
+	USB_DEVICE(0x0582, 0x00a3),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-4FX",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+	/* TODO: add Edirol MD-P1 support */
+{
+	USB_DEVICE(0x582, 0x00a6),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "Juno-G",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	/* Roland SH-201 */
+	USB_DEVICE(0x0582, 0x00ad),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "SH-201",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* Roland SonicCell */
+	USB_DEVICE(0x0582, 0x00c2),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Roland",
+		.product_name = "SonicCell",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* Edirol M-16DX */
+	/* FIXME: This quirk gives a good-working capture stream but the
+	 *        playback seems problematic because of lacking of sync
+	 *        with capture stream.  It needs to sync with the capture
+	 *        clock.  As now, you'll get frequent sound distortions
+	 *        via the playback.
+	 */
+	USB_DEVICE(0x0582, 0x00c4),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* BOSS GT-10 */
+	USB_DEVICE(0x0582, 0x00da),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* Advanced modes of the Edirol UA-25EX.
+	 * For the standard mode, UA-25EX has ID 0582:00e7, which
+	 * offers only 16-bit PCM at 44.1 kHz and no MIDI.
+	 */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x00e6),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "EDIROL",
+		.product_name = "UA-25EX",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_EDIROL_UAXX
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x00ea when not in Advanced Driver mode */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x00e9),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Roland", */
+		/* .product_name = "UA-1G", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	/* has ID 0x0110 when not in Advanced Driver mode */
+	USB_DEVICE_VENDOR_SPEC(0x0582, 0x010f),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Roland", */
+		/* .product_name = "A-PRO", */
+		.ifnum = 1,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0007
+		}
+	}
+},
+{
+	USB_DEVICE(0x0582, 0x0113),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "BOSS", */
+		/* .product_name = "ME-25", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* Guillemot devices */
+{
+	/*
+	 * This is for the "Windows Edition" where the external MIDI ports are
+	 * the only MIDI ports; the control data is reported through HID
+	 * interfaces.  The "Macintosh Edition" has ID 0xd002 and uses standard
+	 * compliant USB MIDI ports for external MIDI and controls.
+	 */
+	USB_DEVICE_VENDOR_SPEC(0x06f8, 0xb000),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Hercules",
+		.product_name = "DJ Console (WE)",
+		.ifnum = 4,
+		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables = 0x0001
+		}
+	}
+},
+
+/* Midiman/M-Audio devices */
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1002),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 2x2",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1011),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 1x1",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1015),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "Keystation",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1021),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 4x4",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x000f,
+			.in_cables  = 0x000f
+		}
+	}
+},
+{
+	/*
+	 * For hardware revision 1.05; in the later revisions (1.10 and
+	 * 1.21), 0x1031 is the ID for the device without firmware.
+	 * Thanks to Olaf Giesbrecht <Olaf_Giesbrecht@yahoo.de>
+	 */
+	USB_DEVICE_VER(0x0763, 0x1031, 0x0100, 0x0109),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 8x8",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x01ff,
+			.in_cables  = 0x01ff
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1033),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 8x8",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x01ff,
+			.in_cables  = 0x01ff
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x1041),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "MidiSport 2x4",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x000f,
+			.in_cables  = 0x0003
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2001),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "Quattro",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			/*
+			 * Interfaces 0-2 are "Windows-compatible", 16-bit only,
+			 * and share endpoints with the other interfaces.
+			 * Ignore them.  The other interfaces can do 24 bits,
+			 * but captured samples are big-endian (see usbaudio.c).
+			 */
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 4,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 5,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 6,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 7,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 8,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 9,
+				.type = QUIRK_MIDI_MIDIMAN,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2003),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "AudioPhile",
+		.ifnum = 6,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x2008),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "Ozone",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_MIDIMAN,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		}
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0763, 0x200d),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "M-Audio",
+		.product_name = "OmniStudio",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 4,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 5,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 6,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 7,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 8,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 9,
+				.type = QUIRK_MIDI_MIDIMAN,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0763, 0x2019),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "M-Audio", */
+		/* .product_name = "Ozone Academic", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_STANDARD_INTERFACE
+			},
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_MIDIMAN,
+				.data = & (const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0001,
+					.in_cables  = 0x0001
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0763, 0x2080),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "M-Audio", */
+		/* .product_name = "Fast Track Ultra", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 8,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x01,
+					.ep_attr = 0x09,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+						44100, 48000, 88200, 96000
+					}
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 8,
+					.iface = 2,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x81,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+						44100, 48000, 88200, 96000
+					}
+				}
+			},
+			/* interface 3 (MIDI) is standard compliant */
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+{
+	USB_DEVICE(0x0763, 0x2081),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "M-Audio", */
+		/* .product_name = "Fast Track Ultra 8R", */
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 8,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x01,
+					.ep_attr = 0x09,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+							44100, 48000, 88200, 96000
+					}
+				}
+			},
+			{
+				.ifnum = 2,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = & (const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+					.channels = 8,
+					.iface = 2,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x81,
+					.ep_attr = 0x05,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000 |
+						 SNDRV_PCM_RATE_88200 |
+						 SNDRV_PCM_RATE_96000,
+					.rate_min = 44100,
+					.rate_max = 96000,
+					.nr_rates = 4,
+					.rate_table = (unsigned int[]) {
+						44100, 48000, 88200, 96000
+					}
+				}
+			},
+			/* interface 3 (MIDI) is standard compliant */
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* Casio devices */
+{
+	USB_DEVICE(0x07cf, 0x6801),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Casio",
+		.product_name = "PL-40R",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_YAMAHA
+	}
+},
+{
+	/* this ID is used by several devices without a product ID */
+	USB_DEVICE(0x07cf, 0x6802),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Casio",
+		.product_name = "Keyboard",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_YAMAHA
+	}
+},
+
+/* Mark of the Unicorn devices */
+{
+	/* thanks to Robert A. Lerche <ral 'at' msbit.com> */
+	.match_flags = USB_DEVICE_ID_MATCH_VENDOR |
+		       USB_DEVICE_ID_MATCH_PRODUCT |
+		       USB_DEVICE_ID_MATCH_DEV_SUBCLASS,
+	.idVendor = 0x07fd,
+	.idProduct = 0x0001,
+	.bDeviceSubClass = 2,
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "MOTU",
+		.product_name = "Fastlane",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = & (const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 0,
+				.type = QUIRK_MIDI_RAW_BYTES
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* Emagic devices */
+{
+	USB_DEVICE(0x086a, 0x0001),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Emagic",
+		/* .product_name = "Unitor8", */
+		.ifnum = 2,
+		.type = QUIRK_MIDI_EMAGIC,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x80ff,
+			.in_cables  = 0x80ff
+		}
+	}
+},
+{
+	USB_DEVICE(0x086a, 0x0002),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Emagic",
+		/* .product_name = "AMT8", */
+		.ifnum = 2,
+		.type = QUIRK_MIDI_EMAGIC,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x80ff,
+			.in_cables  = 0x80ff
+		}
+	}
+},
+{
+	USB_DEVICE(0x086a, 0x0003),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Emagic",
+		/* .product_name = "MT4", */
+		.ifnum = 2,
+		.type = QUIRK_MIDI_EMAGIC,
+		.data = & (const struct snd_usb_midi_endpoint_info) {
+			.out_cables = 0x800f,
+			.in_cables  = 0x8003
+		}
+	}
+},
+
+/* AKAI devices */
+{
+	USB_DEVICE(0x09e8, 0x0062),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "AKAI",
+		.product_name = "MPD16",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_AKAI,
+	}
+},
+
+/* TerraTec devices */
+{
+	USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "TerraTec",
+		.product_name = "PHASE 26",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0013),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "TerraTec",
+		.product_name = "PHASE 26",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0014),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "TerraTec",
+		.product_name = "PHASE 26",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+{
+	USB_DEVICE(0x0ccd, 0x0028),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "TerraTec",
+		.product_name = "Aureon5.1MkII",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+{
+	USB_DEVICE(0x0ccd, 0x0035),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Miditech",
+		.product_name = "Play'n Roll",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_CME
+	}
+},
+
+/* Stanton/N2IT Final Scratch v1 device ('Scratchamp') */
+{
+	USB_DEVICE(0x103d, 0x0100),
+		.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Stanton",
+		.product_name = "ScratchAmp",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+{
+	USB_DEVICE(0x103d, 0x0101),
+		.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Stanton",
+		.product_name = "ScratchAmp",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+
+/* Novation EMS devices */
+{
+	USB_DEVICE_VENDOR_SPEC(0x1235, 0x0001),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Novation",
+		.product_name = "ReMOTE Audio/XStation",
+		.ifnum = 4,
+		.type = QUIRK_MIDI_NOVATION
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x1235, 0x0002),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Novation",
+		.product_name = "Speedio",
+		.ifnum = 3,
+		.type = QUIRK_MIDI_NOVATION
+	}
+},
+{
+	USB_DEVICE(0x1235, 0x000e),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		/* .vendor_name = "Novation", */
+		/* .product_name = "Launchpad", */
+		.ifnum = 0,
+		.type = QUIRK_MIDI_RAW_BYTES
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x1235, 0x4661),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Novation",
+		.product_name = "ReMOTE25",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_NOVATION
+	}
+},
+
+/* Access Music devices */
+{
+	/* VirusTI Desktop */
+	USB_DEVICE_VENDOR_SPEC(0x133e, 0x0815),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = &(const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = &(const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0003,
+					.in_cables  = 0x0003
+				}
+			},
+			{
+				.ifnum = 4,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
+/* */
+{
+	/* aka. Serato Scratch Live DJ Box */
+	USB_DEVICE(0x13e5, 0x0001),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Rane",
+		.product_name = "SL-1",
+		.ifnum = QUIRK_NO_INTERFACE
+	}
+},
+
+/* Miditech devices */
+{
+	USB_DEVICE(0x4752, 0x0011),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "Miditech",
+		.product_name = "Midistart-2",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_CME
+	}
+},
+
+/* Central Music devices */
+{
+	/* this ID used by both Miditech MidiStudio-2 and CME UF-x */
+	USB_DEVICE(0x7104, 0x2202),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = 0,
+		.type = QUIRK_MIDI_CME
+	}
+},
+
+/* Hauppauge HVR-950Q and HVR-850 */
+{
+	USB_DEVICE_VENDOR_SPEC(0x2040, 0x7200),
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Hauppauge",
+		.product_name = "HVR-950Q",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUDIO_ALIGN_TRANSFER,
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x2040, 0x7240),
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Hauppauge",
+		.product_name = "HVR-850",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUDIO_ALIGN_TRANSFER,
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x2040, 0x7210),
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Hauppauge",
+		.product_name = "HVR-950Q",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUDIO_ALIGN_TRANSFER,
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x2040, 0x7217),
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Hauppauge",
+		.product_name = "HVR-950Q",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUDIO_ALIGN_TRANSFER,
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x2040, 0x721b),
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Hauppauge",
+		.product_name = "HVR-950Q",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUDIO_ALIGN_TRANSFER,
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x2040, 0x721e),
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Hauppauge",
+		.product_name = "HVR-950Q",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUDIO_ALIGN_TRANSFER,
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x2040, 0x721f),
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Hauppauge",
+		.product_name = "HVR-950Q",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUDIO_ALIGN_TRANSFER,
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x2040, 0x7280),
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Hauppauge",
+		.product_name = "HVR-950Q",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUDIO_ALIGN_TRANSFER,
+	}
+},
+{
+	USB_DEVICE_VENDOR_SPEC(0x0fd9, 0x0008),
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
+		       USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Hauppauge",
+		.product_name = "HVR-950Q",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_AUDIO_ALIGN_TRANSFER,
+	}
+},
+
+/* Digidesign Mbox */
+{
+	/* Thanks to Clemens Ladisch <clemens@ladisch.de> */
+	USB_DEVICE(0x0dba, 0x1000),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "Digidesign",
+		.product_name = "MBox",
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = (const struct snd_usb_audio_quirk[]){
+			{
+				.ifnum = 0,
+				.type = QUIRK_IGNORE_INTERFACE,
+			},
+			{
+				.ifnum = 1,
+				.type = QUIRK_AUDIO_FIXED_ENDPOINT,
+				.data = &(const struct audioformat) {
+					.formats = SNDRV_PCM_FMTBIT_S24_3BE,
+					.channels = 2,
+					.iface = 1,
+					.altsetting = 1,
+					.altset_idx = 1,
+					.attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+					.endpoint = 0x02,
+					.ep_attr = 0x01,
+					.maxpacksize = 0x130,
+					.rates = SNDRV_PCM_RATE_44100 |
+						 SNDRV_PCM_RATE_48000,
+					.rate_min = 44100,
+					.rate_max = 48000,
+					.nr_rates = 2,
+					.rate_table = (unsigned int[]) {
+						44100, 48000
+					}
+				}
+			},
+			{
+				.ifnum = -1
+			}
+		}
+
+	}
+},
+
+{
+	/*
+	 * Some USB MIDI devices don't have an audio control interface,
+	 * so we have to grab MIDI streaming interfaces here.
+	 */
+	.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
+		       USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING,
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_MIDI_STANDARD_INTERFACE
+	}
+},
+
+#undef USB_DEVICE_VENDOR_SPEC
diff --git a/linux/sound/usb/quirks.c b/linux/sound/usb/quirks.c
new file mode 100644
index 0000000..cf8bf08
--- /dev/null
+++ b/linux/sound/usb/quirks.c
@@ -0,0 +1,596 @@
+/*
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "card.h"
+#include "mixer.h"
+#include "mixer_quirks.h"
+#include "midi.h"
+#include "quirks.h"
+#include "helper.h"
+#include "endpoint.h"
+#include "pcm.h"
+#include "clock.h"
+
+/*
+ * handle the quirks for the contained interfaces
+ */
+static int create_composite_quirk(struct snd_usb_audio *chip,
+				  struct usb_interface *iface,
+				  struct usb_driver *driver,
+				  const struct snd_usb_audio_quirk *quirk)
+{
+	int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber;
+	int err;
+
+	for (quirk = quirk->data; quirk->ifnum >= 0; ++quirk) {
+		iface = usb_ifnum_to_if(chip->dev, quirk->ifnum);
+		if (!iface)
+			continue;
+		if (quirk->ifnum != probed_ifnum &&
+		    usb_interface_claimed(iface))
+			continue;
+		err = snd_usb_create_quirk(chip, iface, driver, quirk);
+		if (err < 0)
+			return err;
+		if (quirk->ifnum != probed_ifnum)
+			usb_driver_claim_interface(driver, iface, (void *)-1L);
+	}
+	return 0;
+}
+
+static int ignore_interface_quirk(struct snd_usb_audio *chip,
+				  struct usb_interface *iface,
+				  struct usb_driver *driver,
+				  const struct snd_usb_audio_quirk *quirk)
+{
+	return 0;
+}
+
+
+/*
+ * Allow alignment on audio sub-slot (channel samples) rather than
+ * on audio slots (audio frames)
+ */
+static int create_align_transfer_quirk(struct snd_usb_audio *chip,
+				       struct usb_interface *iface,
+				       struct usb_driver *driver,
+				       const struct snd_usb_audio_quirk *quirk)
+{
+	chip->txfr_quirk = 1;
+	return 1;	/* Continue with creating streams and mixer */
+}
+
+static int create_any_midi_quirk(struct snd_usb_audio *chip,
+				 struct usb_interface *intf,
+				 struct usb_driver *driver,
+				 const struct snd_usb_audio_quirk *quirk)
+{
+	return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk);
+}
+
+/*
+ * create a stream for an interface with proper descriptors
+ */
+static int create_standard_audio_quirk(struct snd_usb_audio *chip,
+				       struct usb_interface *iface,
+				       struct usb_driver *driver,
+				       const struct snd_usb_audio_quirk *quirk)
+{
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	int err;
+
+	alts = &iface->altsetting[0];
+	altsd = get_iface_desc(alts);
+	err = snd_usb_parse_audio_endpoints(chip, altsd->bInterfaceNumber);
+	if (err < 0) {
+		snd_printk(KERN_ERR "cannot setup if %d: error %d\n",
+			   altsd->bInterfaceNumber, err);
+		return err;
+	}
+	/* reset the current interface */
+	usb_set_interface(chip->dev, altsd->bInterfaceNumber, 0);
+	return 0;
+}
+
+/*
+ * create a stream for an endpoint/altsetting without proper descriptors
+ */
+static int create_fixed_stream_quirk(struct snd_usb_audio *chip,
+				     struct usb_interface *iface,
+				     struct usb_driver *driver,
+				     const struct snd_usb_audio_quirk *quirk)
+{
+	struct audioformat *fp;
+	struct usb_host_interface *alts;
+	int stream, err;
+	unsigned *rate_table = NULL;
+
+	fp = kmemdup(quirk->data, sizeof(*fp), GFP_KERNEL);
+	if (! fp) {
+		snd_printk(KERN_ERR "cannot memdup\n");
+		return -ENOMEM;
+	}
+	if (fp->nr_rates > 0) {
+		rate_table = kmalloc(sizeof(int) * fp->nr_rates, GFP_KERNEL);
+		if (!rate_table) {
+			kfree(fp);
+			return -ENOMEM;
+		}
+		memcpy(rate_table, fp->rate_table, sizeof(int) * fp->nr_rates);
+		fp->rate_table = rate_table;
+	}
+
+	stream = (fp->endpoint & USB_DIR_IN)
+		? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+	err = snd_usb_add_audio_endpoint(chip, stream, fp);
+	if (err < 0) {
+		kfree(fp);
+		kfree(rate_table);
+		return err;
+	}
+	if (fp->iface != get_iface_desc(&iface->altsetting[0])->bInterfaceNumber ||
+	    fp->altset_idx >= iface->num_altsetting) {
+		kfree(fp);
+		kfree(rate_table);
+		return -EINVAL;
+	}
+	alts = &iface->altsetting[fp->altset_idx];
+	fp->datainterval = snd_usb_parse_datainterval(chip, alts);
+	fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+	usb_set_interface(chip->dev, fp->iface, 0);
+	snd_usb_init_pitch(chip, fp->iface, alts, fp);
+	snd_usb_init_sample_rate(chip, fp->iface, alts, fp, fp->rate_max);
+	return 0;
+}
+
+/*
+ * Create a stream for an Edirol UA-700/UA-25/UA-4FX interface.  
+ * The only way to detect the sample rate is by looking at wMaxPacketSize.
+ */
+static int create_uaxx_quirk(struct snd_usb_audio *chip,
+			     struct usb_interface *iface,
+			     struct usb_driver *driver,
+			     const struct snd_usb_audio_quirk *quirk)
+{
+	static const struct audioformat ua_format = {
+		.formats = SNDRV_PCM_FMTBIT_S24_3LE,
+		.channels = 2,
+		.fmt_type = UAC_FORMAT_TYPE_I,
+		.altsetting = 1,
+		.altset_idx = 1,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+	};
+	struct usb_host_interface *alts;
+	struct usb_interface_descriptor *altsd;
+	struct audioformat *fp;
+	int stream, err;
+
+	/* both PCM and MIDI interfaces have 2 or more altsettings */
+	if (iface->num_altsetting < 2)
+		return -ENXIO;
+	alts = &iface->altsetting[1];
+	altsd = get_iface_desc(alts);
+
+	if (altsd->bNumEndpoints == 2) {
+		static const struct snd_usb_midi_endpoint_info ua700_ep = {
+			.out_cables = 0x0003,
+			.in_cables  = 0x0003
+		};
+		static const struct snd_usb_audio_quirk ua700_quirk = {
+			.type = QUIRK_MIDI_FIXED_ENDPOINT,
+			.data = &ua700_ep
+		};
+		static const struct snd_usb_midi_endpoint_info uaxx_ep = {
+			.out_cables = 0x0001,
+			.in_cables  = 0x0001
+		};
+		static const struct snd_usb_audio_quirk uaxx_quirk = {
+			.type = QUIRK_MIDI_FIXED_ENDPOINT,
+			.data = &uaxx_ep
+		};
+		const struct snd_usb_audio_quirk *quirk =
+			chip->usb_id == USB_ID(0x0582, 0x002b)
+			? &ua700_quirk : &uaxx_quirk;
+		return snd_usbmidi_create(chip->card, iface,
+					  &chip->midi_list, quirk);
+	}
+
+	if (altsd->bNumEndpoints != 1)
+		return -ENXIO;
+
+	fp = kmalloc(sizeof(*fp), GFP_KERNEL);
+	if (!fp)
+		return -ENOMEM;
+	memcpy(fp, &ua_format, sizeof(*fp));
+
+	fp->iface = altsd->bInterfaceNumber;
+	fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
+	fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
+	fp->datainterval = 0;
+	fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+
+	switch (fp->maxpacksize) {
+	case 0x120:
+		fp->rate_max = fp->rate_min = 44100;
+		break;
+	case 0x138:
+	case 0x140:
+		fp->rate_max = fp->rate_min = 48000;
+		break;
+	case 0x258:
+	case 0x260:
+		fp->rate_max = fp->rate_min = 96000;
+		break;
+	default:
+		snd_printk(KERN_ERR "unknown sample rate\n");
+		kfree(fp);
+		return -ENXIO;
+	}
+
+	stream = (fp->endpoint & USB_DIR_IN)
+		? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
+	err = snd_usb_add_audio_endpoint(chip, stream, fp);
+	if (err < 0) {
+		kfree(fp);
+		return err;
+	}
+	usb_set_interface(chip->dev, fp->iface, 0);
+	return 0;
+}
+
+/*
+ * audio-interface quirks
+ *
+ * returns zero if no standard audio/MIDI parsing is needed.
+ * returns a postive value if standard audio/midi interfaces are parsed
+ * after this.
+ * returns a negative value at error.
+ */
+int snd_usb_create_quirk(struct snd_usb_audio *chip,
+			 struct usb_interface *iface,
+			 struct usb_driver *driver,
+			 const struct snd_usb_audio_quirk *quirk)
+{
+	typedef int (*quirk_func_t)(struct snd_usb_audio *,
+				    struct usb_interface *,
+				    struct usb_driver *,
+				    const struct snd_usb_audio_quirk *);
+	static const quirk_func_t quirk_funcs[] = {
+		[QUIRK_IGNORE_INTERFACE] = ignore_interface_quirk,
+		[QUIRK_COMPOSITE] = create_composite_quirk,
+		[QUIRK_MIDI_STANDARD_INTERFACE] = create_any_midi_quirk,
+		[QUIRK_MIDI_FIXED_ENDPOINT] = create_any_midi_quirk,
+		[QUIRK_MIDI_YAMAHA] = create_any_midi_quirk,
+		[QUIRK_MIDI_MIDIMAN] = create_any_midi_quirk,
+		[QUIRK_MIDI_NOVATION] = create_any_midi_quirk,
+		[QUIRK_MIDI_RAW_BYTES] = create_any_midi_quirk,
+		[QUIRK_MIDI_EMAGIC] = create_any_midi_quirk,
+		[QUIRK_MIDI_CME] = create_any_midi_quirk,
+		[QUIRK_MIDI_AKAI] = create_any_midi_quirk,
+		[QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk,
+		[QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk,
+		[QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk,
+		[QUIRK_AUDIO_ALIGN_TRANSFER] = create_align_transfer_quirk
+	};
+
+	if (quirk->type < QUIRK_TYPE_COUNT) {
+		return quirk_funcs[quirk->type](chip, iface, driver, quirk);
+	} else {
+		snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type);
+		return -ENXIO;
+	}
+}
+
+/*
+ * boot quirks
+ */
+
+#define EXTIGY_FIRMWARE_SIZE_OLD 794
+#define EXTIGY_FIRMWARE_SIZE_NEW 483
+
+static int snd_usb_extigy_boot_quirk(struct usb_device *dev, struct usb_interface *intf)
+{
+	struct usb_host_config *config = dev->actconfig;
+	int err;
+
+	if (le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_OLD ||
+	    le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_NEW) {
+		snd_printdd("sending Extigy boot sequence...\n");
+		/* Send message to force it to reconnect with full interface. */
+		err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev,0),
+				      0x10, 0x43, 0x0001, 0x000a, NULL, 0, 1000);
+		if (err < 0) snd_printdd("error sending boot message: %d\n", err);
+		err = usb_get_descriptor(dev, USB_DT_DEVICE, 0,
+				&dev->descriptor, sizeof(dev->descriptor));
+		config = dev->actconfig;
+		if (err < 0) snd_printdd("error usb_get_descriptor: %d\n", err);
+		err = usb_reset_configuration(dev);
+		if (err < 0) snd_printdd("error usb_reset_configuration: %d\n", err);
+		snd_printdd("extigy_boot: new boot length = %d\n",
+			    le16_to_cpu(get_cfg_desc(config)->wTotalLength));
+		return -ENODEV; /* quit this anyway */
+	}
+	return 0;
+}
+
+static int snd_usb_audigy2nx_boot_quirk(struct usb_device *dev)
+{
+	u8 buf = 1;
+
+	snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0x2a,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+			0, 0, &buf, 1, 1000);
+	if (buf == 0) {
+		snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 0x29,
+				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+				1, 2000, NULL, 0, 1000);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/*
+ * C-Media CM106/CM106+ have four 16-bit internal registers that are nicely
+ * documented in the device's data sheet.
+ */
+static int snd_usb_cm106_write_int_reg(struct usb_device *dev, int reg, u16 value)
+{
+	u8 buf[4];
+	buf[0] = 0x20;
+	buf[1] = value & 0xff;
+	buf[2] = (value >> 8) & 0xff;
+	buf[3] = reg;
+	return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION,
+			       USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT,
+			       0, 0, &buf, 4, 1000);
+}
+
+static int snd_usb_cm106_boot_quirk(struct usb_device *dev)
+{
+	/*
+	 * Enable line-out driver mode, set headphone source to front
+	 * channels, enable stereo mic.
+	 */
+	return snd_usb_cm106_write_int_reg(dev, 2, 0x8004);
+}
+
+/*
+ * C-Media CM6206 is based on CM106 with two additional
+ * registers that are not documented in the data sheet.
+ * Values here are chosen based on sniffing USB traffic
+ * under Windows.
+ */
+static int snd_usb_cm6206_boot_quirk(struct usb_device *dev)
+{
+	int err, reg;
+	int val[] = {0x200c, 0x3000, 0xf800, 0x143f, 0x0000, 0x3000};
+
+	for (reg = 0; reg < ARRAY_SIZE(val); reg++) {
+		err = snd_usb_cm106_write_int_reg(dev, reg, val[reg]);
+		if (err < 0)
+			return err;
+	}
+
+	return err;
+}
+
+/*
+ * This call will put the synth in "USB send" mode, i.e it will send MIDI
+ * messages through USB (this is disabled at startup). The synth will
+ * acknowledge by sending a sysex on endpoint 0x85 and by displaying a USB
+ * sign on its LCD. Values here are chosen based on sniffing USB traffic
+ * under Windows.
+ */
+static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev)
+{
+	int err, actual_length;
+
+	/* "midi send" enable */
+	static const u8 seq[] = { 0x4e, 0x73, 0x52, 0x01 };
+
+	void *buf = kmemdup(seq, ARRAY_SIZE(seq), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	err = usb_interrupt_msg(dev, usb_sndintpipe(dev, 0x05), buf,
+			ARRAY_SIZE(seq), &actual_length, 1000);
+	kfree(buf);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/*
+ * Setup quirks
+ */
+#define AUDIOPHILE_SET			0x01 /* if set, parse device_setup */
+#define AUDIOPHILE_SET_DTS              0x02 /* if set, enable DTS Digital Output */
+#define AUDIOPHILE_SET_96K              0x04 /* 48-96KHz rate if set, 8-48KHz otherwise */
+#define AUDIOPHILE_SET_24B		0x08 /* 24bits sample if set, 16bits otherwise */
+#define AUDIOPHILE_SET_DI		0x10 /* if set, enable Digital Input */
+#define AUDIOPHILE_SET_MASK		0x1F /* bit mask for setup value */
+#define AUDIOPHILE_SET_24B_48K_DI	0x19 /* value for 24bits+48KHz+Digital Input */
+#define AUDIOPHILE_SET_24B_48K_NOTDI	0x09 /* value for 24bits+48KHz+No Digital Input */
+#define AUDIOPHILE_SET_16B_48K_DI	0x11 /* value for 16bits+48KHz+Digital Input */
+#define AUDIOPHILE_SET_16B_48K_NOTDI	0x01 /* value for 16bits+48KHz+No Digital Input */
+
+static int audiophile_skip_setting_quirk(struct snd_usb_audio *chip,
+					 int iface,
+					 int altno)
+{
+	/* Reset ALL ifaces to 0 altsetting.
+	 * Call it for every possible altsetting of every interface.
+	 */
+	usb_set_interface(chip->dev, iface, 0);
+
+	if (chip->setup & AUDIOPHILE_SET) {
+		if ((chip->setup & AUDIOPHILE_SET_DTS)
+		    && altno != 6)
+			return 1; /* skip this altsetting */
+		if ((chip->setup & AUDIOPHILE_SET_96K)
+		    && altno != 1)
+			return 1; /* skip this altsetting */
+		if ((chip->setup & AUDIOPHILE_SET_MASK) ==
+		    AUDIOPHILE_SET_24B_48K_DI && altno != 2)
+			return 1; /* skip this altsetting */
+		if ((chip->setup & AUDIOPHILE_SET_MASK) ==
+		    AUDIOPHILE_SET_24B_48K_NOTDI && altno != 3)
+			return 1; /* skip this altsetting */
+		if ((chip->setup & AUDIOPHILE_SET_MASK) ==
+		    AUDIOPHILE_SET_16B_48K_DI && altno != 4)
+			return 1; /* skip this altsetting */
+		if ((chip->setup & AUDIOPHILE_SET_MASK) ==
+		    AUDIOPHILE_SET_16B_48K_NOTDI && altno != 5)
+			return 1; /* skip this altsetting */
+	}
+
+	return 0; /* keep this altsetting */
+}
+
+int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip,
+				  int iface,
+				  int altno)
+{
+	/* audiophile usb: skip altsets incompatible with device_setup */
+	if (chip->usb_id == USB_ID(0x0763, 0x2003))
+		return audiophile_skip_setting_quirk(chip, iface, altno);
+
+	return 0;
+}
+
+int snd_usb_apply_boot_quirk(struct usb_device *dev,
+			     struct usb_interface *intf,
+			     const struct snd_usb_audio_quirk *quirk)
+{
+	u32 id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
+			le16_to_cpu(dev->descriptor.idProduct));
+
+	/* SB Extigy needs special boot-up sequence */
+	/* if more models come, this will go to the quirk list. */
+	if (id == USB_ID(0x041e, 0x3000))
+		return snd_usb_extigy_boot_quirk(dev, intf);
+
+	/* SB Audigy 2 NX needs its own boot-up magic, too */
+	if (id == USB_ID(0x041e, 0x3020))
+		return snd_usb_audigy2nx_boot_quirk(dev);
+
+	/* C-Media CM106 / Turtle Beach Audio Advantage Roadie */
+	if (id == USB_ID(0x10f5, 0x0200))
+		return snd_usb_cm106_boot_quirk(dev);
+
+	/* C-Media CM6206 / CM106-Like Sound Device */
+	if (id == USB_ID(0x0d8c, 0x0102))
+		return snd_usb_cm6206_boot_quirk(dev);
+
+	/* Access Music VirusTI Desktop */
+	if (id == USB_ID(0x133e, 0x0815))
+		return snd_usb_accessmusic_boot_quirk(dev);
+
+	return 0;
+}
+
+/*
+ * check if the device uses big-endian samples
+ */
+int snd_usb_is_big_endian_format(struct snd_usb_audio *chip, struct audioformat *fp)
+{
+	switch (chip->usb_id) {
+	case USB_ID(0x0763, 0x2001): /* M-Audio Quattro: captured data only */
+		if (fp->endpoint & USB_DIR_IN)
+			return 1;
+		break;
+	case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */
+		if (chip->setup == 0x00 ||
+		    fp->altsetting==1 || fp->altsetting==2 || fp->altsetting==3)
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ * For E-Mu 0404USB/0202USB/TrackerPre sample rate should be set for device,
+ * not for interface.
+ */
+
+enum {
+	EMU_QUIRK_SR_44100HZ = 0,
+	EMU_QUIRK_SR_48000HZ,
+	EMU_QUIRK_SR_88200HZ,
+	EMU_QUIRK_SR_96000HZ,
+	EMU_QUIRK_SR_176400HZ,
+	EMU_QUIRK_SR_192000HZ
+};
+
+static void set_format_emu_quirk(struct snd_usb_substream *subs,
+				 struct audioformat *fmt)
+{
+	unsigned char emu_samplerate_id = 0;
+
+	/* When capture is active
+	 * sample rate shouldn't be changed
+	 * by playback substream
+	 */
+	if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (subs->stream->substream[SNDRV_PCM_STREAM_CAPTURE].interface != -1)
+			return;
+	}
+
+	switch (fmt->rate_min) {
+	case 48000:
+		emu_samplerate_id = EMU_QUIRK_SR_48000HZ;
+		break;
+	case 88200:
+		emu_samplerate_id = EMU_QUIRK_SR_88200HZ;
+		break;
+	case 96000:
+		emu_samplerate_id = EMU_QUIRK_SR_96000HZ;
+		break;
+	case 176400:
+		emu_samplerate_id = EMU_QUIRK_SR_176400HZ;
+		break;
+	case 192000:
+		emu_samplerate_id = EMU_QUIRK_SR_192000HZ;
+		break;
+	default:
+		emu_samplerate_id = EMU_QUIRK_SR_44100HZ;
+		break;
+	}
+	snd_emuusb_set_samplerate(subs->stream->chip, emu_samplerate_id);
+}
+
+void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
+			      struct audioformat *fmt)
+{
+	switch (subs->stream->chip->usb_id) {
+	case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */
+	case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */
+	case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */
+		set_format_emu_quirk(subs, fmt);
+		break;
+	}
+}
+
diff --git a/linux/sound/usb/quirks.h b/linux/sound/usb/quirks.h
new file mode 100644
index 0000000..03e5e94
--- /dev/null
+++ b/linux/sound/usb/quirks.h
@@ -0,0 +1,23 @@
+#ifndef __USBAUDIO_QUIRKS_H
+#define __USBAUDIO_QUIRKS_H
+
+int snd_usb_create_quirk(struct snd_usb_audio *chip,
+			 struct usb_interface *iface,
+			 struct usb_driver *driver,
+			 const struct snd_usb_audio_quirk *quirk);
+
+int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip,
+				  int iface,
+				  int altno);
+
+int snd_usb_apply_boot_quirk(struct usb_device *dev,
+			     struct usb_interface *intf,
+			     const struct snd_usb_audio_quirk *quirk);
+
+void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
+			      struct audioformat *fmt);
+
+int snd_usb_is_big_endian_format(struct snd_usb_audio *chip,
+				 struct audioformat *fp);
+
+#endif /* __USBAUDIO_QUIRKS_H */
diff --git a/linux/sound/usb/urb.c b/linux/sound/usb/urb.c
new file mode 100644
index 0000000..e184349
--- /dev/null
+++ b/linux/sound/usb/urb.c
@@ -0,0 +1,941 @@
+/*
+ *   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/gfp.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+
+#include "usbaudio.h"
+#include "helper.h"
+#include "card.h"
+#include "urb.h"
+#include "pcm.h"
+
+/*
+ * convert a sampling rate into our full speed format (fs/1000 in Q16.16)
+ * this will overflow at approx 524 kHz
+ */
+static inline unsigned get_usb_full_speed_rate(unsigned int rate)
+{
+	return ((rate << 13) + 62) / 125;
+}
+
+/*
+ * convert a sampling rate into USB high speed format (fs/8000 in Q16.16)
+ * this will overflow at approx 4 MHz
+ */
+static inline unsigned get_usb_high_speed_rate(unsigned int rate)
+{
+	return ((rate << 10) + 62) / 125;
+}
+
+/*
+ * unlink active urbs.
+ */
+static int deactivate_urbs(struct snd_usb_substream *subs, int force, int can_sleep)
+{
+	struct snd_usb_audio *chip = subs->stream->chip;
+	unsigned int i;
+	int async;
+
+	subs->running = 0;
+
+	if (!force && subs->stream->chip->shutdown) /* to be sure... */
+		return -EBADFD;
+
+	async = !can_sleep && chip->async_unlink;
+
+	if (!async && in_interrupt())
+		return 0;
+
+	for (i = 0; i < subs->nurbs; i++) {
+		if (test_bit(i, &subs->active_mask)) {
+			if (!test_and_set_bit(i, &subs->unlink_mask)) {
+				struct urb *u = subs->dataurb[i].urb;
+				if (async)
+					usb_unlink_urb(u);
+				else
+					usb_kill_urb(u);
+			}
+		}
+	}
+	if (subs->syncpipe) {
+		for (i = 0; i < SYNC_URBS; i++) {
+			if (test_bit(i+16, &subs->active_mask)) {
+				if (!test_and_set_bit(i+16, &subs->unlink_mask)) {
+					struct urb *u = subs->syncurb[i].urb;
+					if (async)
+						usb_unlink_urb(u);
+					else
+						usb_kill_urb(u);
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+
+/*
+ * release a urb data
+ */
+static void release_urb_ctx(struct snd_urb_ctx *u)
+{
+	if (u->urb) {
+		if (u->buffer_size)
+			usb_free_coherent(u->subs->dev, u->buffer_size,
+					u->urb->transfer_buffer,
+					u->urb->transfer_dma);
+		usb_free_urb(u->urb);
+		u->urb = NULL;
+	}
+}
+
+/*
+ *  wait until all urbs are processed.
+ */
+static int wait_clear_urbs(struct snd_usb_substream *subs)
+{
+	unsigned long end_time = jiffies + msecs_to_jiffies(1000);
+	unsigned int i;
+	int alive;
+
+	do {
+		alive = 0;
+		for (i = 0; i < subs->nurbs; i++) {
+			if (test_bit(i, &subs->active_mask))
+				alive++;
+		}
+		if (subs->syncpipe) {
+			for (i = 0; i < SYNC_URBS; i++) {
+				if (test_bit(i + 16, &subs->active_mask))
+					alive++;
+			}
+		}
+		if (! alive)
+			break;
+		schedule_timeout_uninterruptible(1);
+	} while (time_before(jiffies, end_time));
+	if (alive)
+		snd_printk(KERN_ERR "timeout: still %d active urbs..\n", alive);
+	return 0;
+}
+
+/*
+ * release a substream
+ */
+void snd_usb_release_substream_urbs(struct snd_usb_substream *subs, int force)
+{
+	int i;
+
+	/* stop urbs (to be sure) */
+	deactivate_urbs(subs, force, 1);
+	wait_clear_urbs(subs);
+
+	for (i = 0; i < MAX_URBS; i++)
+		release_urb_ctx(&subs->dataurb[i]);
+	for (i = 0; i < SYNC_URBS; i++)
+		release_urb_ctx(&subs->syncurb[i]);
+	usb_free_coherent(subs->dev, SYNC_URBS * 4,
+			subs->syncbuf, subs->sync_dma);
+	subs->syncbuf = NULL;
+	subs->nurbs = 0;
+}
+
+/*
+ * complete callback from data urb
+ */
+static void snd_complete_urb(struct urb *urb)
+{
+	struct snd_urb_ctx *ctx = urb->context;
+	struct snd_usb_substream *subs = ctx->subs;
+	struct snd_pcm_substream *substream = ctx->subs->pcm_substream;
+	int err = 0;
+
+	if ((subs->running && subs->ops.retire(subs, substream->runtime, urb)) ||
+	    !subs->running || /* can be stopped during retire callback */
+	    (err = subs->ops.prepare(subs, substream->runtime, urb)) < 0 ||
+	    (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+		clear_bit(ctx->index, &subs->active_mask);
+		if (err < 0) {
+			snd_printd(KERN_ERR "cannot submit urb (err = %d)\n", err);
+			snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+		}
+	}
+}
+
+
+/*
+ * complete callback from sync urb
+ */
+static void snd_complete_sync_urb(struct urb *urb)
+{
+	struct snd_urb_ctx *ctx = urb->context;
+	struct snd_usb_substream *subs = ctx->subs;
+	struct snd_pcm_substream *substream = ctx->subs->pcm_substream;
+	int err = 0;
+
+	if ((subs->running && subs->ops.retire_sync(subs, substream->runtime, urb)) ||
+	    !subs->running || /* can be stopped during retire callback */
+	    (err = subs->ops.prepare_sync(subs, substream->runtime, urb)) < 0 ||
+	    (err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+		clear_bit(ctx->index + 16, &subs->active_mask);
+		if (err < 0) {
+			snd_printd(KERN_ERR "cannot submit sync urb (err = %d)\n", err);
+			snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+		}
+	}
+}
+
+
+/*
+ * initialize a substream for plaback/capture
+ */
+int snd_usb_init_substream_urbs(struct snd_usb_substream *subs,
+				unsigned int period_bytes,
+				unsigned int rate,
+				unsigned int frame_bits)
+{
+	unsigned int maxsize, i;
+	int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK;
+	unsigned int urb_packs, total_packs, packs_per_ms;
+	struct snd_usb_audio *chip = subs->stream->chip;
+
+	/* calculate the frequency in 16.16 format */
+	if (snd_usb_get_speed(subs->dev) == USB_SPEED_FULL)
+		subs->freqn = get_usb_full_speed_rate(rate);
+	else
+		subs->freqn = get_usb_high_speed_rate(rate);
+	subs->freqm = subs->freqn;
+	subs->freqshift = INT_MIN;
+	/* calculate max. frequency */
+	if (subs->maxpacksize) {
+		/* whatever fits into a max. size packet */
+		maxsize = subs->maxpacksize;
+		subs->freqmax = (maxsize / (frame_bits >> 3))
+				<< (16 - subs->datainterval);
+	} else {
+		/* no max. packet size: just take 25% higher than nominal */
+		subs->freqmax = subs->freqn + (subs->freqn >> 2);
+		maxsize = ((subs->freqmax + 0xffff) * (frame_bits >> 3))
+				>> (16 - subs->datainterval);
+	}
+	subs->phase = 0;
+
+	if (subs->fill_max)
+		subs->curpacksize = subs->maxpacksize;
+	else
+		subs->curpacksize = maxsize;
+
+	if (snd_usb_get_speed(subs->dev) != USB_SPEED_FULL)
+		packs_per_ms = 8 >> subs->datainterval;
+	else
+		packs_per_ms = 1;
+
+	if (is_playback) {
+		urb_packs = max(chip->nrpacks, 1);
+		urb_packs = min(urb_packs, (unsigned int)MAX_PACKS);
+	} else
+		urb_packs = 1;
+	urb_packs *= packs_per_ms;
+	if (subs->syncpipe)
+		urb_packs = min(urb_packs, 1U << subs->syncinterval);
+
+	/* decide how many packets to be used */
+	if (is_playback) {
+		unsigned int minsize, maxpacks;
+		/* determine how small a packet can be */
+		minsize = (subs->freqn >> (16 - subs->datainterval))
+			  * (frame_bits >> 3);
+		/* with sync from device, assume it can be 12% lower */
+		if (subs->syncpipe)
+			minsize -= minsize >> 3;
+		minsize = max(minsize, 1u);
+		total_packs = (period_bytes + minsize - 1) / minsize;
+		/* we need at least two URBs for queueing */
+		if (total_packs < 2) {
+			total_packs = 2;
+		} else {
+			/* and we don't want too long a queue either */
+			maxpacks = max(MAX_QUEUE * packs_per_ms, urb_packs * 2);
+			total_packs = min(total_packs, maxpacks);
+		}
+	} else {
+		while (urb_packs > 1 && urb_packs * maxsize >= period_bytes)
+			urb_packs >>= 1;
+		total_packs = MAX_URBS * urb_packs;
+	}
+	subs->nurbs = (total_packs + urb_packs - 1) / urb_packs;
+	if (subs->nurbs > MAX_URBS) {
+		/* too much... */
+		subs->nurbs = MAX_URBS;
+		total_packs = MAX_URBS * urb_packs;
+	} else if (subs->nurbs < 2) {
+		/* too little - we need at least two packets
+		 * to ensure contiguous playback/capture
+		 */
+		subs->nurbs = 2;
+	}
+
+	/* allocate and initialize data urbs */
+	for (i = 0; i < subs->nurbs; i++) {
+		struct snd_urb_ctx *u = &subs->dataurb[i];
+		u->index = i;
+		u->subs = subs;
+		u->packets = (i + 1) * total_packs / subs->nurbs
+			- i * total_packs / subs->nurbs;
+		u->buffer_size = maxsize * u->packets;
+		if (subs->fmt_type == UAC_FORMAT_TYPE_II)
+			u->packets++; /* for transfer delimiter */
+		u->urb = usb_alloc_urb(u->packets, GFP_KERNEL);
+		if (!u->urb)
+			goto out_of_memory;
+		u->urb->transfer_buffer =
+			usb_alloc_coherent(subs->dev, u->buffer_size,
+					   GFP_KERNEL, &u->urb->transfer_dma);
+		if (!u->urb->transfer_buffer)
+			goto out_of_memory;
+		u->urb->pipe = subs->datapipe;
+		u->urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+		u->urb->interval = 1 << subs->datainterval;
+		u->urb->context = u;
+		u->urb->complete = snd_complete_urb;
+	}
+
+	if (subs->syncpipe) {
+		/* allocate and initialize sync urbs */
+		subs->syncbuf = usb_alloc_coherent(subs->dev, SYNC_URBS * 4,
+						 GFP_KERNEL, &subs->sync_dma);
+		if (!subs->syncbuf)
+			goto out_of_memory;
+		for (i = 0; i < SYNC_URBS; i++) {
+			struct snd_urb_ctx *u = &subs->syncurb[i];
+			u->index = i;
+			u->subs = subs;
+			u->packets = 1;
+			u->urb = usb_alloc_urb(1, GFP_KERNEL);
+			if (!u->urb)
+				goto out_of_memory;
+			u->urb->transfer_buffer = subs->syncbuf + i * 4;
+			u->urb->transfer_dma = subs->sync_dma + i * 4;
+			u->urb->transfer_buffer_length = 4;
+			u->urb->pipe = subs->syncpipe;
+			u->urb->transfer_flags = URB_ISO_ASAP |
+						 URB_NO_TRANSFER_DMA_MAP;
+			u->urb->number_of_packets = 1;
+			u->urb->interval = 1 << subs->syncinterval;
+			u->urb->context = u;
+			u->urb->complete = snd_complete_sync_urb;
+		}
+	}
+	return 0;
+
+out_of_memory:
+	snd_usb_release_substream_urbs(subs, 0);
+	return -ENOMEM;
+}
+
+/*
+ * prepare urb for full speed capture sync pipe
+ *
+ * fill the length and offset of each urb descriptor.
+ * the fixed 10.14 frequency is passed through the pipe.
+ */
+static int prepare_capture_sync_urb(struct snd_usb_substream *subs,
+				    struct snd_pcm_runtime *runtime,
+				    struct urb *urb)
+{
+	unsigned char *cp = urb->transfer_buffer;
+	struct snd_urb_ctx *ctx = urb->context;
+
+	urb->dev = ctx->subs->dev; /* we need to set this at each time */
+	urb->iso_frame_desc[0].length = 3;
+	urb->iso_frame_desc[0].offset = 0;
+	cp[0] = subs->freqn >> 2;
+	cp[1] = subs->freqn >> 10;
+	cp[2] = subs->freqn >> 18;
+	return 0;
+}
+
+/*
+ * prepare urb for high speed capture sync pipe
+ *
+ * fill the length and offset of each urb descriptor.
+ * the fixed 12.13 frequency is passed as 16.16 through the pipe.
+ */
+static int prepare_capture_sync_urb_hs(struct snd_usb_substream *subs,
+				       struct snd_pcm_runtime *runtime,
+				       struct urb *urb)
+{
+	unsigned char *cp = urb->transfer_buffer;
+	struct snd_urb_ctx *ctx = urb->context;
+
+	urb->dev = ctx->subs->dev; /* we need to set this at each time */
+	urb->iso_frame_desc[0].length = 4;
+	urb->iso_frame_desc[0].offset = 0;
+	cp[0] = subs->freqn;
+	cp[1] = subs->freqn >> 8;
+	cp[2] = subs->freqn >> 16;
+	cp[3] = subs->freqn >> 24;
+	return 0;
+}
+
+/*
+ * process after capture sync complete
+ * - nothing to do
+ */
+static int retire_capture_sync_urb(struct snd_usb_substream *subs,
+				   struct snd_pcm_runtime *runtime,
+				   struct urb *urb)
+{
+	return 0;
+}
+
+/*
+ * prepare urb for capture data pipe
+ *
+ * fill the offset and length of each descriptor.
+ *
+ * we use a temporary buffer to write the captured data.
+ * since the length of written data is determined by host, we cannot
+ * write onto the pcm buffer directly...  the data is thus copied
+ * later at complete callback to the global buffer.
+ */
+static int prepare_capture_urb(struct snd_usb_substream *subs,
+			       struct snd_pcm_runtime *runtime,
+			       struct urb *urb)
+{
+	int i, offs;
+	struct snd_urb_ctx *ctx = urb->context;
+
+	offs = 0;
+	urb->dev = ctx->subs->dev; /* we need to set this at each time */
+	for (i = 0; i < ctx->packets; i++) {
+		urb->iso_frame_desc[i].offset = offs;
+		urb->iso_frame_desc[i].length = subs->curpacksize;
+		offs += subs->curpacksize;
+	}
+	urb->transfer_buffer_length = offs;
+	urb->number_of_packets = ctx->packets;
+	return 0;
+}
+
+/*
+ * process after capture complete
+ *
+ * copy the data from each desctiptor to the pcm buffer, and
+ * update the current position.
+ */
+static int retire_capture_urb(struct snd_usb_substream *subs,
+			      struct snd_pcm_runtime *runtime,
+			      struct urb *urb)
+{
+	unsigned long flags;
+	unsigned char *cp;
+	int i;
+	unsigned int stride, frames, bytes, oldptr;
+	int period_elapsed = 0;
+
+	stride = runtime->frame_bits >> 3;
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+		if (urb->iso_frame_desc[i].status) {
+			snd_printd(KERN_ERR "frame %d active: %d\n", i, urb->iso_frame_desc[i].status);
+			// continue;
+		}
+		bytes = urb->iso_frame_desc[i].actual_length;
+		frames = bytes / stride;
+		if (!subs->txfr_quirk)
+			bytes = frames * stride;
+		if (bytes % (runtime->sample_bits >> 3) != 0) {
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+			int oldbytes = bytes;
+#endif
+			bytes = frames * stride;
+			snd_printdd(KERN_ERR "Corrected urb data len. %d->%d\n",
+							oldbytes, bytes);
+		}
+		/* update the current pointer */
+		spin_lock_irqsave(&subs->lock, flags);
+		oldptr = subs->hwptr_done;
+		subs->hwptr_done += bytes;
+		if (subs->hwptr_done >= runtime->buffer_size * stride)
+			subs->hwptr_done -= runtime->buffer_size * stride;
+		frames = (bytes + (oldptr % stride)) / stride;
+		subs->transfer_done += frames;
+		if (subs->transfer_done >= runtime->period_size) {
+			subs->transfer_done -= runtime->period_size;
+			period_elapsed = 1;
+		}
+		spin_unlock_irqrestore(&subs->lock, flags);
+		/* copy a data chunk */
+		if (oldptr + bytes > runtime->buffer_size * stride) {
+			unsigned int bytes1 =
+					runtime->buffer_size * stride - oldptr;
+			memcpy(runtime->dma_area + oldptr, cp, bytes1);
+			memcpy(runtime->dma_area, cp + bytes1, bytes - bytes1);
+		} else {
+			memcpy(runtime->dma_area + oldptr, cp, bytes);
+		}
+	}
+	if (period_elapsed)
+		snd_pcm_period_elapsed(subs->pcm_substream);
+	return 0;
+}
+
+/*
+ * Process after capture complete when paused.  Nothing to do.
+ */
+static int retire_paused_capture_urb(struct snd_usb_substream *subs,
+				     struct snd_pcm_runtime *runtime,
+				     struct urb *urb)
+{
+	return 0;
+}
+
+
+/*
+ * prepare urb for playback sync pipe
+ *
+ * set up the offset and length to receive the current frequency.
+ */
+static int prepare_playback_sync_urb(struct snd_usb_substream *subs,
+				     struct snd_pcm_runtime *runtime,
+				     struct urb *urb)
+{
+	struct snd_urb_ctx *ctx = urb->context;
+
+	urb->dev = ctx->subs->dev; /* we need to set this at each time */
+	urb->iso_frame_desc[0].length = min(4u, ctx->subs->syncmaxsize);
+	urb->iso_frame_desc[0].offset = 0;
+	return 0;
+}
+
+/*
+ * process after playback sync complete
+ *
+ * Full speed devices report feedback values in 10.14 format as samples per
+ * frame, high speed devices in 16.16 format as samples per microframe.
+ * Because the Audio Class 1 spec was written before USB 2.0, many high speed
+ * devices use a wrong interpretation, some others use an entirely different
+ * format.  Therefore, we cannot predict what format any particular device uses
+ * and must detect it automatically.
+ */
+static int retire_playback_sync_urb(struct snd_usb_substream *subs,
+				    struct snd_pcm_runtime *runtime,
+				    struct urb *urb)
+{
+	unsigned int f;
+	int shift;
+	unsigned long flags;
+
+	if (urb->iso_frame_desc[0].status != 0 ||
+	    urb->iso_frame_desc[0].actual_length < 3)
+		return 0;
+
+	f = le32_to_cpup(urb->transfer_buffer);
+	if (urb->iso_frame_desc[0].actual_length == 3)
+		f &= 0x00ffffff;
+	else
+		f &= 0x0fffffff;
+	if (f == 0)
+		return 0;
+
+	if (unlikely(subs->freqshift == INT_MIN)) {
+		/*
+		 * The first time we see a feedback value, determine its format
+		 * by shifting it left or right until it matches the nominal
+		 * frequency value.  This assumes that the feedback does not
+		 * differ from the nominal value more than +50% or -25%.
+		 */
+		shift = 0;
+		while (f < subs->freqn - subs->freqn / 4) {
+			f <<= 1;
+			shift++;
+		}
+		while (f > subs->freqn + subs->freqn / 2) {
+			f >>= 1;
+			shift--;
+		}
+		subs->freqshift = shift;
+	}
+	else if (subs->freqshift >= 0)
+		f <<= subs->freqshift;
+	else
+		f >>= -subs->freqshift;
+
+	if (likely(f >= subs->freqn - subs->freqn / 8 && f <= subs->freqmax)) {
+		/*
+		 * If the frequency looks valid, set it.
+		 * This value is referred to in prepare_playback_urb().
+		 */
+		spin_lock_irqsave(&subs->lock, flags);
+		subs->freqm = f;
+		spin_unlock_irqrestore(&subs->lock, flags);
+	} else {
+		/*
+		 * Out of range; maybe the shift value is wrong.
+		 * Reset it so that we autodetect again the next time.
+		 */
+		subs->freqshift = INT_MIN;
+	}
+
+	return 0;
+}
+
+/* determine the number of frames in the next packet */
+static int snd_usb_audio_next_packet_size(struct snd_usb_substream *subs)
+{
+	if (subs->fill_max)
+		return subs->maxframesize;
+	else {
+		subs->phase = (subs->phase & 0xffff)
+			+ (subs->freqm << subs->datainterval);
+		return min(subs->phase >> 16, subs->maxframesize);
+	}
+}
+
+/*
+ * Prepare urb for streaming before playback starts or when paused.
+ *
+ * We don't have any data, so we send silence.
+ */
+static int prepare_nodata_playback_urb(struct snd_usb_substream *subs,
+				       struct snd_pcm_runtime *runtime,
+				       struct urb *urb)
+{
+	unsigned int i, offs, counts;
+	struct snd_urb_ctx *ctx = urb->context;
+	int stride = runtime->frame_bits >> 3;
+
+	offs = 0;
+	urb->dev = ctx->subs->dev;
+	for (i = 0; i < ctx->packets; ++i) {
+		counts = snd_usb_audio_next_packet_size(subs);
+		urb->iso_frame_desc[i].offset = offs * stride;
+		urb->iso_frame_desc[i].length = counts * stride;
+		offs += counts;
+	}
+	urb->number_of_packets = ctx->packets;
+	urb->transfer_buffer_length = offs * stride;
+	memset(urb->transfer_buffer,
+	       runtime->format == SNDRV_PCM_FORMAT_U8 ? 0x80 : 0,
+	       offs * stride);
+	return 0;
+}
+
+/*
+ * prepare urb for playback data pipe
+ *
+ * Since a URB can handle only a single linear buffer, we must use double
+ * buffering when the data to be transferred overflows the buffer boundary.
+ * To avoid inconsistencies when updating hwptr_done, we use double buffering
+ * for all URBs.
+ */
+static int prepare_playback_urb(struct snd_usb_substream *subs,
+				struct snd_pcm_runtime *runtime,
+				struct urb *urb)
+{
+	int i, stride;
+	unsigned int counts, frames, bytes;
+	unsigned long flags;
+	int period_elapsed = 0;
+	struct snd_urb_ctx *ctx = urb->context;
+
+	stride = runtime->frame_bits >> 3;
+
+	frames = 0;
+	urb->dev = ctx->subs->dev; /* we need to set this at each time */
+	urb->number_of_packets = 0;
+	spin_lock_irqsave(&subs->lock, flags);
+	for (i = 0; i < ctx->packets; i++) {
+		counts = snd_usb_audio_next_packet_size(subs);
+		/* set up descriptor */
+		urb->iso_frame_desc[i].offset = frames * stride;
+		urb->iso_frame_desc[i].length = counts * stride;
+		frames += counts;
+		urb->number_of_packets++;
+		subs->transfer_done += counts;
+		if (subs->transfer_done >= runtime->period_size) {
+			subs->transfer_done -= runtime->period_size;
+			period_elapsed = 1;
+			if (subs->fmt_type == UAC_FORMAT_TYPE_II) {
+				if (subs->transfer_done > 0) {
+					/* FIXME: fill-max mode is not
+					 * supported yet */
+					frames -= subs->transfer_done;
+					counts -= subs->transfer_done;
+					urb->iso_frame_desc[i].length =
+						counts * stride;
+					subs->transfer_done = 0;
+				}
+				i++;
+				if (i < ctx->packets) {
+					/* add a transfer delimiter */
+					urb->iso_frame_desc[i].offset =
+						frames * stride;
+					urb->iso_frame_desc[i].length = 0;
+					urb->number_of_packets++;
+				}
+				break;
+			}
+		}
+		if (period_elapsed) /* finish at the period boundary */
+			break;
+	}
+	bytes = frames * stride;
+	if (subs->hwptr_done + bytes > runtime->buffer_size * stride) {
+		/* err, the transferred area goes over buffer boundary. */
+		unsigned int bytes1 =
+			runtime->buffer_size * stride - subs->hwptr_done;
+		memcpy(urb->transfer_buffer,
+		       runtime->dma_area + subs->hwptr_done, bytes1);
+		memcpy(urb->transfer_buffer + bytes1,
+		       runtime->dma_area, bytes - bytes1);
+	} else {
+		memcpy(urb->transfer_buffer,
+		       runtime->dma_area + subs->hwptr_done, bytes);
+	}
+	subs->hwptr_done += bytes;
+	if (subs->hwptr_done >= runtime->buffer_size * stride)
+		subs->hwptr_done -= runtime->buffer_size * stride;
+	runtime->delay += frames;
+	spin_unlock_irqrestore(&subs->lock, flags);
+	urb->transfer_buffer_length = bytes;
+	if (period_elapsed)
+		snd_pcm_period_elapsed(subs->pcm_substream);
+	return 0;
+}
+
+/*
+ * process after playback data complete
+ * - decrease the delay count again
+ */
+static int retire_playback_urb(struct snd_usb_substream *subs,
+			       struct snd_pcm_runtime *runtime,
+			       struct urb *urb)
+{
+	unsigned long flags;
+	int stride = runtime->frame_bits >> 3;
+	int processed = urb->transfer_buffer_length / stride;
+
+	spin_lock_irqsave(&subs->lock, flags);
+	if (processed > runtime->delay)
+		runtime->delay = 0;
+	else
+		runtime->delay -= processed;
+	spin_unlock_irqrestore(&subs->lock, flags);
+	return 0;
+}
+
+static const char *usb_error_string(int err)
+{
+	switch (err) {
+	case -ENODEV:
+		return "no device";
+	case -ENOENT:
+		return "endpoint not enabled";
+	case -EPIPE:
+		return "endpoint stalled";
+	case -ENOSPC:
+		return "not enough bandwidth";
+	case -ESHUTDOWN:
+		return "device disabled";
+	case -EHOSTUNREACH:
+		return "device suspended";
+	case -EINVAL:
+	case -EAGAIN:
+	case -EFBIG:
+	case -EMSGSIZE:
+		return "internal error";
+	default:
+		return "unknown error";
+	}
+}
+
+/*
+ * set up and start data/sync urbs
+ */
+static int start_urbs(struct snd_usb_substream *subs, struct snd_pcm_runtime *runtime)
+{
+	unsigned int i;
+	int err;
+
+	if (subs->stream->chip->shutdown)
+		return -EBADFD;
+
+	for (i = 0; i < subs->nurbs; i++) {
+		if (snd_BUG_ON(!subs->dataurb[i].urb))
+			return -EINVAL;
+		if (subs->ops.prepare(subs, runtime, subs->dataurb[i].urb) < 0) {
+			snd_printk(KERN_ERR "cannot prepare datapipe for urb %d\n", i);
+			goto __error;
+		}
+	}
+	if (subs->syncpipe) {
+		for (i = 0; i < SYNC_URBS; i++) {
+			if (snd_BUG_ON(!subs->syncurb[i].urb))
+				return -EINVAL;
+			if (subs->ops.prepare_sync(subs, runtime, subs->syncurb[i].urb) < 0) {
+				snd_printk(KERN_ERR "cannot prepare syncpipe for urb %d\n", i);
+				goto __error;
+			}
+		}
+	}
+
+	subs->active_mask = 0;
+	subs->unlink_mask = 0;
+	subs->running = 1;
+	for (i = 0; i < subs->nurbs; i++) {
+		err = usb_submit_urb(subs->dataurb[i].urb, GFP_ATOMIC);
+		if (err < 0) {
+			snd_printk(KERN_ERR "cannot submit datapipe "
+				   "for urb %d, error %d: %s\n",
+				   i, err, usb_error_string(err));
+			goto __error;
+		}
+		set_bit(i, &subs->active_mask);
+	}
+	if (subs->syncpipe) {
+		for (i = 0; i < SYNC_URBS; i++) {
+			err = usb_submit_urb(subs->syncurb[i].urb, GFP_ATOMIC);
+			if (err < 0) {
+				snd_printk(KERN_ERR "cannot submit syncpipe "
+					   "for urb %d, error %d: %s\n",
+					   i, err, usb_error_string(err));
+				goto __error;
+			}
+			set_bit(i + 16, &subs->active_mask);
+		}
+	}
+	return 0;
+
+ __error:
+	// snd_pcm_stop(subs->pcm_substream, SNDRV_PCM_STATE_XRUN);
+	deactivate_urbs(subs, 0, 0);
+	return -EPIPE;
+}
+
+
+/*
+ */
+static struct snd_urb_ops audio_urb_ops[2] = {
+	{
+		.prepare =	prepare_nodata_playback_urb,
+		.retire =	retire_playback_urb,
+		.prepare_sync =	prepare_playback_sync_urb,
+		.retire_sync =	retire_playback_sync_urb,
+	},
+	{
+		.prepare =	prepare_capture_urb,
+		.retire =	retire_capture_urb,
+		.prepare_sync =	prepare_capture_sync_urb,
+		.retire_sync =	retire_capture_sync_urb,
+	},
+};
+
+/*
+ * initialize the substream instance.
+ */
+
+void snd_usb_init_substream(struct snd_usb_stream *as,
+			    int stream, struct audioformat *fp)
+{
+	struct snd_usb_substream *subs = &as->substream[stream];
+
+	INIT_LIST_HEAD(&subs->fmt_list);
+	spin_lock_init(&subs->lock);
+
+	subs->stream = as;
+	subs->direction = stream;
+	subs->dev = as->chip->dev;
+	subs->txfr_quirk = as->chip->txfr_quirk;
+	subs->ops = audio_urb_ops[stream];
+	if (snd_usb_get_speed(subs->dev) >= USB_SPEED_HIGH)
+		subs->ops.prepare_sync = prepare_capture_sync_urb_hs;
+
+	snd_usb_set_pcm_ops(as->pcm, stream);
+
+	list_add_tail(&fp->list, &subs->fmt_list);
+	subs->formats |= fp->formats;
+	subs->endpoint = fp->endpoint;
+	subs->num_formats++;
+	subs->fmt_type = fp->fmt_type;
+}
+
+int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_usb_substream *subs = substream->runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		subs->ops.prepare = prepare_playback_urb;
+		return 0;
+	case SNDRV_PCM_TRIGGER_STOP:
+		return deactivate_urbs(subs, 0, 0);
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		subs->ops.prepare = prepare_nodata_playback_urb;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_usb_substream *subs = substream->runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		subs->ops.retire = retire_capture_urb;
+		return start_urbs(subs, substream->runtime);
+	case SNDRV_PCM_TRIGGER_STOP:
+		return deactivate_urbs(subs, 0, 0);
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		subs->ops.retire = retire_paused_capture_urb;
+		return 0;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		subs->ops.retire = retire_capture_urb;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+int snd_usb_substream_prepare(struct snd_usb_substream *subs,
+			      struct snd_pcm_runtime *runtime)
+{
+	/* clear urbs (to be sure) */
+	deactivate_urbs(subs, 0, 1);
+	wait_clear_urbs(subs);
+
+	/* for playback, submit the URBs now; otherwise, the first hwptr_done
+	 * updates for all URBs would happen at the same time when starting */
+	if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) {
+		subs->ops.prepare = prepare_nodata_playback_urb;
+		return start_urbs(subs, runtime);
+	}
+
+	return 0;
+}
+
diff --git a/linux/sound/usb/urb.h b/linux/sound/usb/urb.h
new file mode 100644
index 0000000..888da38
--- /dev/null
+++ b/linux/sound/usb/urb.h
@@ -0,0 +1,21 @@
+#ifndef __USBAUDIO_URB_H
+#define __USBAUDIO_URB_H
+
+void snd_usb_init_substream(struct snd_usb_stream *as,
+			    int stream,
+			    struct audioformat *fp);
+
+int snd_usb_init_substream_urbs(struct snd_usb_substream *subs,
+				unsigned int period_bytes,
+				unsigned int rate,
+				unsigned int frame_bits);
+
+void snd_usb_release_substream_urbs(struct snd_usb_substream *subs, int force);
+
+int snd_usb_substream_prepare(struct snd_usb_substream *subs,
+			      struct snd_pcm_runtime *runtime);
+
+int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream, int cmd);
+int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream, int cmd);
+
+#endif /* __USBAUDIO_URB_H */
diff --git a/linux/sound/usb/usbaudio.h b/linux/sound/usb/usbaudio.h
new file mode 100644
index 0000000..db3eb21
--- /dev/null
+++ b/linux/sound/usb/usbaudio.h
@@ -0,0 +1,98 @@
+#ifndef __USBAUDIO_H
+#define __USBAUDIO_H
+/*
+ *   (Tentative) USB Audio Driver for ALSA
+ *
+ *   Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+/* handling of USB vendor/product ID pairs as 32-bit numbers */
+#define USB_ID(vendor, product) (((vendor) << 16) | (product))
+#define USB_ID_VENDOR(id) ((id) >> 16)
+#define USB_ID_PRODUCT(id) ((u16)(id))
+
+/*
+ *
+ */
+
+struct snd_usb_audio {
+	int index;
+	struct usb_device *dev;
+	struct snd_card *card;
+	u32 usb_id;
+	int shutdown;
+	unsigned int txfr_quirk:1; /* Subframe boundaries on transfers */
+	int num_interfaces;
+	int num_suspended_intf;
+
+	struct list_head pcm_list;	/* list of pcm streams */
+	int pcm_devs;
+
+	struct list_head midi_list;	/* list of midi interfaces */
+
+	struct list_head mixer_list;	/* list of mixer interfaces */
+
+	int setup;			/* from the 'device_setup' module param */
+	int nrpacks;			/* from the 'nrpacks' module param */
+	int async_unlink;		/* from the 'async_unlink' module param */
+
+	struct usb_host_interface *ctrl_intf;	/* the audio control interface */
+};
+
+/*
+ * Information about devices with broken descriptors
+ */
+
+/* special values for .ifnum */
+#define QUIRK_NO_INTERFACE		-2
+#define QUIRK_ANY_INTERFACE		-1
+
+enum quirk_type {
+	QUIRK_IGNORE_INTERFACE,
+	QUIRK_COMPOSITE,
+	QUIRK_MIDI_STANDARD_INTERFACE,
+	QUIRK_MIDI_FIXED_ENDPOINT,
+	QUIRK_MIDI_YAMAHA,
+	QUIRK_MIDI_MIDIMAN,
+	QUIRK_MIDI_NOVATION,
+	QUIRK_MIDI_RAW_BYTES,
+	QUIRK_MIDI_EMAGIC,
+	QUIRK_MIDI_CME,
+	QUIRK_MIDI_AKAI,
+	QUIRK_MIDI_US122L,
+	QUIRK_AUDIO_STANDARD_INTERFACE,
+	QUIRK_AUDIO_FIXED_ENDPOINT,
+	QUIRK_AUDIO_EDIROL_UAXX,
+	QUIRK_AUDIO_ALIGN_TRANSFER,
+
+	QUIRK_TYPE_COUNT
+};
+
+struct snd_usb_audio_quirk {
+	const char *vendor_name;
+	const char *product_name;
+	int16_t ifnum;
+	uint16_t type;
+	const void *data;
+};
+
+#define combine_word(s)    ((*(s)) | ((unsigned int)(s)[1] << 8))
+#define combine_triple(s)  (combine_word(s) | ((unsigned int)(s)[2] << 16))
+#define combine_quad(s)    (combine_triple(s) | ((unsigned int)(s)[3] << 24))
+
+#endif /* __USBAUDIO_H */
diff --git a/linux/sound/usb/usx2y/Makefile b/linux/sound/usb/usx2y/Makefile
new file mode 100644
index 0000000..7489330
--- /dev/null
+++ b/linux/sound/usb/usx2y/Makefile
@@ -0,0 +1,5 @@
+snd-usb-usx2y-objs := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o
+snd-usb-us122l-objs := us122l.o
+
+obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o
+obj-$(CONFIG_SND_USB_US122L) += snd-usb-us122l.o
diff --git a/linux/sound/usb/usx2y/us122l.c b/linux/sound/usb/usx2y/us122l.c
new file mode 100644
index 0000000..6ef68e4
--- /dev/null
+++ b/linux/sound/usb/usx2y/us122l.c
@@ -0,0 +1,787 @@
+/*
+ * Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <sound/core.h>
+#include <sound/hwdep.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#define MODNAME "US122L"
+#include "usb_stream.c"
+#include "../usbaudio.h"
+#include "../midi.h"
+#include "us122l.h"
+
+MODULE_AUTHOR("Karsten Wiese <fzu@wemgehoertderstaat.de>");
+MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.5");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* Id for this card */
+							/* Enable this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS".");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS".");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS".");
+
+static int snd_us122l_card_used[SNDRV_CARDS];
+
+
+static int us122l_create_usbmidi(struct snd_card *card)
+{
+	static struct snd_usb_midi_endpoint_info quirk_data = {
+		.out_ep = 4,
+		.in_ep = 3,
+		.out_cables =	0x001,
+		.in_cables =	0x001
+	};
+	static struct snd_usb_audio_quirk quirk = {
+		.vendor_name =	"US122L",
+		.product_name =	NAME_ALLCAPS,
+		.ifnum = 	1,
+		.type = QUIRK_MIDI_US122L,
+		.data = &quirk_data
+	};
+	struct usb_device *dev = US122L(card)->dev;
+	struct usb_interface *iface = usb_ifnum_to_if(dev, 1);
+
+	return snd_usbmidi_create(card, iface,
+				  &US122L(card)->midi_list, &quirk);
+}
+
+static int us144_create_usbmidi(struct snd_card *card)
+{
+	static struct snd_usb_midi_endpoint_info quirk_data = {
+		.out_ep = 4,
+		.in_ep = 3,
+		.out_cables =	0x001,
+		.in_cables =	0x001
+	};
+	static struct snd_usb_audio_quirk quirk = {
+		.vendor_name =	"US144",
+		.product_name =	NAME_ALLCAPS,
+		.ifnum = 	0,
+		.type = QUIRK_MIDI_US122L,
+		.data = &quirk_data
+	};
+	struct usb_device *dev = US122L(card)->dev;
+	struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
+
+	return snd_usbmidi_create(card, iface,
+				  &US122L(card)->midi_list, &quirk);
+}
+
+/*
+ * Wrapper for usb_control_msg().
+ * Allocates a temp buffer to prevent dmaing from/to the stack.
+ */
+static int us122l_ctl_msg(struct usb_device *dev, unsigned int pipe,
+			  __u8 request, __u8 requesttype,
+			  __u16 value, __u16 index, void *data,
+			  __u16 size, int timeout)
+{
+	int err;
+	void *buf = NULL;
+
+	if (size > 0) {
+		buf = kmemdup(data, size, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+	}
+	err = usb_control_msg(dev, pipe, request, requesttype,
+			      value, index, buf, size, timeout);
+	if (size > 0) {
+		memcpy(data, buf, size);
+		kfree(buf);
+	}
+	return err;
+}
+
+static void pt_info_set(struct usb_device *dev, u8 v)
+{
+	int ret;
+
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			      'I',
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      v, 0, NULL, 0, 1000);
+	snd_printdd(KERN_DEBUG "%i\n", ret);
+}
+
+static void usb_stream_hwdep_vm_open(struct vm_area_struct *area)
+{
+	struct us122l *us122l = area->vm_private_data;
+	atomic_inc(&us122l->mmap_count);
+	snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count));
+}
+
+static int usb_stream_hwdep_vm_fault(struct vm_area_struct *area,
+				     struct vm_fault *vmf)
+{
+	unsigned long offset;
+	struct page *page;
+	void *vaddr;
+	struct us122l *us122l = area->vm_private_data;
+	struct usb_stream *s;
+
+	mutex_lock(&us122l->mutex);
+	s = us122l->sk.s;
+	if (!s)
+		goto unlock;
+
+	offset = vmf->pgoff << PAGE_SHIFT;
+	if (offset < PAGE_ALIGN(s->read_size))
+		vaddr = (char *)s + offset;
+	else {
+		offset -= PAGE_ALIGN(s->read_size);
+		if (offset >= PAGE_ALIGN(s->write_size))
+			goto unlock;
+
+		vaddr = us122l->sk.write_page + offset;
+	}
+	page = virt_to_page(vaddr);
+
+	get_page(page);
+	mutex_unlock(&us122l->mutex);
+
+	vmf->page = page;
+
+	return 0;
+unlock:
+	mutex_unlock(&us122l->mutex);
+	return VM_FAULT_SIGBUS;
+}
+
+static void usb_stream_hwdep_vm_close(struct vm_area_struct *area)
+{
+	struct us122l *us122l = area->vm_private_data;
+	atomic_dec(&us122l->mmap_count);
+	snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count));
+}
+
+static const struct vm_operations_struct usb_stream_hwdep_vm_ops = {
+	.open = usb_stream_hwdep_vm_open,
+	.fault = usb_stream_hwdep_vm_fault,
+	.close = usb_stream_hwdep_vm_close,
+};
+
+
+static int usb_stream_hwdep_open(struct snd_hwdep *hw, struct file *file)
+{
+	struct us122l	*us122l = hw->private_data;
+	struct usb_interface *iface;
+	snd_printdd(KERN_DEBUG "%p %p\n", hw, file);
+	if (hw->used >= 2)
+		return -EBUSY;
+
+	if (!us122l->first)
+		us122l->first = file;
+
+	if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
+	    us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
+		iface = usb_ifnum_to_if(us122l->dev, 0);
+		usb_autopm_get_interface(iface);
+	}
+	iface = usb_ifnum_to_if(us122l->dev, 1);
+	usb_autopm_get_interface(iface);
+	return 0;
+}
+
+static int usb_stream_hwdep_release(struct snd_hwdep *hw, struct file *file)
+{
+	struct us122l	*us122l = hw->private_data;
+	struct usb_interface *iface;
+	snd_printdd(KERN_DEBUG "%p %p\n", hw, file);
+
+	if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
+	    us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
+		iface = usb_ifnum_to_if(us122l->dev, 0);
+		usb_autopm_put_interface(iface);
+	}
+	iface = usb_ifnum_to_if(us122l->dev, 1);
+	usb_autopm_put_interface(iface);
+	if (us122l->first == file)
+		us122l->first = NULL;
+	mutex_lock(&us122l->mutex);
+	if (us122l->master == file)
+		us122l->master = us122l->slave;
+
+	us122l->slave = NULL;
+	mutex_unlock(&us122l->mutex);
+	return 0;
+}
+
+static int usb_stream_hwdep_mmap(struct snd_hwdep *hw,
+				 struct file *filp, struct vm_area_struct *area)
+{
+	unsigned long	size = area->vm_end - area->vm_start;
+	struct us122l	*us122l = hw->private_data;
+	unsigned long offset;
+	struct usb_stream *s;
+	int err = 0;
+	bool read;
+
+	offset = area->vm_pgoff << PAGE_SHIFT;
+	mutex_lock(&us122l->mutex);
+	s = us122l->sk.s;
+	read = offset < s->read_size;
+	if (read && area->vm_flags & VM_WRITE) {
+		err = -EPERM;
+		goto out;
+	}
+	snd_printdd(KERN_DEBUG "%lu %u\n", size,
+		    read ? s->read_size : s->write_size);
+	/* if userspace tries to mmap beyond end of our buffer, fail */
+	if (size > PAGE_ALIGN(read ? s->read_size : s->write_size)) {
+		snd_printk(KERN_WARNING "%lu > %u\n", size,
+			   read ? s->read_size : s->write_size);
+		err = -EINVAL;
+		goto out;
+	}
+
+	area->vm_ops = &usb_stream_hwdep_vm_ops;
+	area->vm_flags |= VM_RESERVED;
+	area->vm_private_data = us122l;
+	atomic_inc(&us122l->mmap_count);
+out:
+	mutex_unlock(&us122l->mutex);
+	return err;
+}
+
+static unsigned int usb_stream_hwdep_poll(struct snd_hwdep *hw,
+					  struct file *file, poll_table *wait)
+{
+	struct us122l	*us122l = hw->private_data;
+	struct usb_stream *s = us122l->sk.s;
+	unsigned	*polled;
+	unsigned int	mask;
+
+	poll_wait(file, &us122l->sk.sleep, wait);
+
+	switch (s->state) {
+	case usb_stream_ready:
+		if (us122l->first == file)
+			polled = &s->periods_polled;
+		else
+			polled = &us122l->second_periods_polled;
+		if (*polled != s->periods_done) {
+			*polled = s->periods_done;
+			mask = POLLIN | POLLOUT | POLLWRNORM;
+			break;
+		}
+		/* Fall through */
+		mask = 0;
+		break;
+	default:
+		mask = POLLIN | POLLOUT | POLLWRNORM | POLLERR;
+		break;
+	}
+	return mask;
+}
+
+static void us122l_stop(struct us122l *us122l)
+{
+	struct list_head *p;
+	list_for_each(p, &us122l->midi_list)
+		snd_usbmidi_input_stop(p);
+
+	usb_stream_stop(&us122l->sk);
+	usb_stream_free(&us122l->sk);
+}
+
+static int us122l_set_sample_rate(struct usb_device *dev, int rate)
+{
+	unsigned int ep = 0x81;
+	unsigned char data[3];
+	int err;
+
+	data[0] = rate;
+	data[1] = rate >> 8;
+	data[2] = rate >> 16;
+	err = us122l_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
+			     USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
+			     UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000);
+	if (err < 0)
+		snd_printk(KERN_ERR "%d: cannot set freq %d to ep 0x%x\n",
+			   dev->devnum, rate, ep);
+	return err;
+}
+
+static bool us122l_start(struct us122l *us122l,
+			 unsigned rate, unsigned period_frames)
+{
+	struct list_head *p;
+	int err;
+	unsigned use_packsize = 0;
+	bool success = false;
+
+	if (us122l->dev->speed == USB_SPEED_HIGH) {
+		/* The us-122l's descriptor defaults to iso max_packsize 78,
+		   which isn't needed for samplerates <= 48000.
+		   Lets save some memory:
+		*/
+		switch (rate) {
+		case 44100:
+			use_packsize = 36;
+			break;
+		case 48000:
+			use_packsize = 42;
+			break;
+		case 88200:
+			use_packsize = 72;
+			break;
+		}
+	}
+	if (!usb_stream_new(&us122l->sk, us122l->dev, 1, 2,
+			    rate, use_packsize, period_frames, 6))
+		goto out;
+
+	err = us122l_set_sample_rate(us122l->dev, rate);
+	if (err < 0) {
+		us122l_stop(us122l);
+		snd_printk(KERN_ERR "us122l_set_sample_rate error \n");
+		goto out;
+	}
+	err = usb_stream_start(&us122l->sk);
+	if (err < 0) {
+		us122l_stop(us122l);
+		snd_printk(KERN_ERR "us122l_start error %i \n", err);
+		goto out;
+	}
+	list_for_each(p, &us122l->midi_list)
+		snd_usbmidi_input_start(p);
+	success = true;
+out:
+	return success;
+}
+
+static int usb_stream_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
+				  unsigned cmd, unsigned long arg)
+{
+	struct usb_stream_config *cfg;
+	struct us122l *us122l = hw->private_data;
+	unsigned min_period_frames;
+	int err = 0;
+	bool high_speed;
+
+	if (cmd != SNDRV_USB_STREAM_IOCTL_SET_PARAMS)
+		return -ENOTTY;
+
+	cfg = memdup_user((void *)arg, sizeof(*cfg));
+	if (IS_ERR(cfg))
+		return PTR_ERR(cfg);
+
+	if (cfg->version != USB_STREAM_INTERFACE_VERSION) {
+		err = -ENXIO;
+		goto free;
+	}
+	high_speed = us122l->dev->speed == USB_SPEED_HIGH;
+	if ((cfg->sample_rate != 44100 && cfg->sample_rate != 48000  &&
+	     (!high_speed ||
+	      (cfg->sample_rate != 88200 && cfg->sample_rate != 96000))) ||
+	    cfg->frame_size != 6 ||
+	    cfg->period_frames > 0x3000) {
+		err = -EINVAL;
+		goto free;
+	}
+	switch (cfg->sample_rate) {
+	case 44100:
+		min_period_frames = 48;
+		break;
+	case 48000:
+		min_period_frames = 52;
+		break;
+	default:
+		min_period_frames = 104;
+		break;
+	}
+	if (!high_speed)
+		min_period_frames <<= 1;
+	if (cfg->period_frames < min_period_frames) {
+		err = -EINVAL;
+		goto free;
+	}
+
+	snd_power_wait(hw->card, SNDRV_CTL_POWER_D0);
+
+	mutex_lock(&us122l->mutex);
+	if (!us122l->master)
+		us122l->master = file;
+	else if (us122l->master != file) {
+		if (memcmp(cfg, &us122l->sk.s->cfg, sizeof(*cfg))) {
+			err = -EIO;
+			goto unlock;
+		}
+		us122l->slave = file;
+	}
+	if (!us122l->sk.s ||
+	    memcmp(cfg, &us122l->sk.s->cfg, sizeof(*cfg)) ||
+	    us122l->sk.s->state == usb_stream_xrun) {
+		us122l_stop(us122l);
+		if (!us122l_start(us122l, cfg->sample_rate, cfg->period_frames))
+			err = -EIO;
+		else
+			err = 1;
+	}
+unlock:
+	mutex_unlock(&us122l->mutex);
+free:
+	kfree(cfg);
+	return err;
+}
+
+#define SND_USB_STREAM_ID "USB STREAM"
+static int usb_stream_hwdep_new(struct snd_card *card)
+{
+	int err;
+	struct snd_hwdep *hw;
+	struct usb_device *dev = US122L(card)->dev;
+
+	err = snd_hwdep_new(card, SND_USB_STREAM_ID, 0, &hw);
+	if (err < 0)
+		return err;
+
+	hw->iface = SNDRV_HWDEP_IFACE_USB_STREAM;
+	hw->private_data = US122L(card);
+	hw->ops.open = usb_stream_hwdep_open;
+	hw->ops.release = usb_stream_hwdep_release;
+	hw->ops.ioctl = usb_stream_hwdep_ioctl;
+	hw->ops.ioctl_compat = usb_stream_hwdep_ioctl;
+	hw->ops.mmap = usb_stream_hwdep_mmap;
+	hw->ops.poll = usb_stream_hwdep_poll;
+
+	sprintf(hw->name, "/proc/bus/usb/%03d/%03d/hwdeppcm",
+		dev->bus->busnum, dev->devnum);
+	return 0;
+}
+
+
+static bool us122l_create_card(struct snd_card *card)
+{
+	int err;
+	struct us122l *us122l = US122L(card);
+
+	if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
+	    us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
+		err = usb_set_interface(us122l->dev, 0, 1);
+		if (err) {
+			snd_printk(KERN_ERR "usb_set_interface error \n");
+			return false;
+		}
+	}
+	err = usb_set_interface(us122l->dev, 1, 1);
+	if (err) {
+		snd_printk(KERN_ERR "usb_set_interface error \n");
+		return false;
+	}
+
+	pt_info_set(us122l->dev, 0x11);
+	pt_info_set(us122l->dev, 0x10);
+
+	if (!us122l_start(us122l, 44100, 256))
+		return false;
+
+	if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
+	    us122l->dev->descriptor.idProduct == USB_ID_US144MKII)
+		err = us144_create_usbmidi(card);
+	else
+		err = us122l_create_usbmidi(card);
+	if (err < 0) {
+		snd_printk(KERN_ERR "us122l_create_usbmidi error %i \n", err);
+		us122l_stop(us122l);
+		return false;
+	}
+	err = usb_stream_hwdep_new(card);
+	if (err < 0) {
+/* release the midi resources */
+		struct list_head *p;
+		list_for_each(p, &us122l->midi_list)
+			snd_usbmidi_disconnect(p);
+
+		us122l_stop(us122l);
+		return false;
+	}
+	return true;
+}
+
+static void snd_us122l_free(struct snd_card *card)
+{
+	struct us122l	*us122l = US122L(card);
+	int		index = us122l->card_index;
+	if (index >= 0  &&  index < SNDRV_CARDS)
+		snd_us122l_card_used[index] = 0;
+}
+
+static int usx2y_create_card(struct usb_device *device, struct snd_card **cardp)
+{
+	int		dev;
+	struct snd_card *card;
+	int err;
+
+	for (dev = 0; dev < SNDRV_CARDS; ++dev)
+		if (enable[dev] && !snd_us122l_card_used[dev])
+			break;
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct us122l), &card);
+	if (err < 0)
+		return err;
+	snd_us122l_card_used[US122L(card)->card_index = dev] = 1;
+	card->private_free = snd_us122l_free;
+	US122L(card)->dev = device;
+	mutex_init(&US122L(card)->mutex);
+	init_waitqueue_head(&US122L(card)->sk.sleep);
+	INIT_LIST_HEAD(&US122L(card)->midi_list);
+	strcpy(card->driver, "USB "NAME_ALLCAPS"");
+	sprintf(card->shortname, "TASCAM "NAME_ALLCAPS"");
+	sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)",
+		card->shortname,
+		le16_to_cpu(device->descriptor.idVendor),
+		le16_to_cpu(device->descriptor.idProduct),
+		0,
+		US122L(card)->dev->bus->busnum,
+		US122L(card)->dev->devnum
+		);
+	*cardp = card;
+	return 0;
+}
+
+static int us122l_usb_probe(struct usb_interface *intf,
+			    const struct usb_device_id *device_id,
+			    struct snd_card **cardp)
+{
+	struct usb_device *device = interface_to_usbdev(intf);
+	struct snd_card *card;
+	int err;
+
+	err = usx2y_create_card(device, &card);
+	if (err < 0)
+		return err;
+
+	snd_card_set_dev(card, &intf->dev);
+	if (!us122l_create_card(card)) {
+		snd_card_free(card);
+		return -EINVAL;
+	}
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	usb_get_intf(usb_ifnum_to_if(device, 0));
+	usb_get_dev(device);
+	*cardp = card;
+	return 0;
+}
+
+static int snd_us122l_probe(struct usb_interface *intf,
+			    const struct usb_device_id *id)
+{
+	struct usb_device *device = interface_to_usbdev(intf);
+	struct snd_card *card;
+	int err;
+
+	if ((device->descriptor.idProduct == USB_ID_US144 ||
+	     device->descriptor.idProduct == USB_ID_US144MKII)
+		&& device->speed == USB_SPEED_HIGH) {
+		snd_printk(KERN_ERR "disable ehci-hcd to run US-144 \n");
+		return -ENODEV;
+	}
+
+	snd_printdd(KERN_DEBUG"%p:%i\n",
+		    intf, intf->cur_altsetting->desc.bInterfaceNumber);
+	if (intf->cur_altsetting->desc.bInterfaceNumber != 1)
+		return 0;
+
+	err = us122l_usb_probe(usb_get_intf(intf), id, &card);
+	if (err < 0) {
+		usb_put_intf(intf);
+		return err;
+	}
+
+	usb_set_intfdata(intf, card);
+	return 0;
+}
+
+static void snd_us122l_disconnect(struct usb_interface *intf)
+{
+	struct snd_card *card;
+	struct us122l *us122l;
+	struct list_head *p;
+
+	card = usb_get_intfdata(intf);
+	if (!card)
+		return;
+
+	snd_card_disconnect(card);
+
+	us122l = US122L(card);
+	mutex_lock(&us122l->mutex);
+	us122l_stop(us122l);
+	mutex_unlock(&us122l->mutex);
+
+/* release the midi resources */
+	list_for_each(p, &us122l->midi_list) {
+		snd_usbmidi_disconnect(p);
+	}
+
+	usb_put_intf(usb_ifnum_to_if(us122l->dev, 0));
+	usb_put_intf(usb_ifnum_to_if(us122l->dev, 1));
+	usb_put_dev(us122l->dev);
+
+	while (atomic_read(&us122l->mmap_count))
+		msleep(500);
+
+	snd_card_free(card);
+}
+
+static int snd_us122l_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct snd_card *card;
+	struct us122l *us122l;
+	struct list_head *p;
+
+	card = usb_get_intfdata(intf);
+	if (!card)
+		return 0;
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+	us122l = US122L(card);
+	if (!us122l)
+		return 0;
+
+	list_for_each(p, &us122l->midi_list)
+		snd_usbmidi_input_stop(p);
+
+	mutex_lock(&us122l->mutex);
+	usb_stream_stop(&us122l->sk);
+	mutex_unlock(&us122l->mutex);
+
+	return 0;
+}
+
+static int snd_us122l_resume(struct usb_interface *intf)
+{
+	struct snd_card *card;
+	struct us122l *us122l;
+	struct list_head *p;
+	int err;
+
+	card = usb_get_intfdata(intf);
+	if (!card)
+		return 0;
+
+	us122l = US122L(card);
+	if (!us122l)
+		return 0;
+
+	mutex_lock(&us122l->mutex);
+	/* needed, doesn't restart without: */
+	if (us122l->dev->descriptor.idProduct == USB_ID_US144 ||
+	    us122l->dev->descriptor.idProduct == USB_ID_US144MKII) {
+		err = usb_set_interface(us122l->dev, 0, 1);
+		if (err) {
+			snd_printk(KERN_ERR "usb_set_interface error \n");
+			goto unlock;
+		}
+	}
+	err = usb_set_interface(us122l->dev, 1, 1);
+	if (err) {
+		snd_printk(KERN_ERR "usb_set_interface error \n");
+		goto unlock;
+	}
+
+	pt_info_set(us122l->dev, 0x11);
+	pt_info_set(us122l->dev, 0x10);
+
+	err = us122l_set_sample_rate(us122l->dev,
+				     us122l->sk.s->cfg.sample_rate);
+	if (err < 0) {
+		snd_printk(KERN_ERR "us122l_set_sample_rate error \n");
+		goto unlock;
+	}
+	err = usb_stream_start(&us122l->sk);
+	if (err)
+		goto unlock;
+
+	list_for_each(p, &us122l->midi_list)
+		snd_usbmidi_input_start(p);
+unlock:
+	mutex_unlock(&us122l->mutex);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+	return err;
+}
+
+static struct usb_device_id snd_us122l_usb_id_table[] = {
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x0644,
+		.idProduct =	USB_ID_US122L
+	},
+	{	/* US-144 only works at USB1.1! Disable module ehci-hcd. */
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x0644,
+		.idProduct =	USB_ID_US144
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x0644,
+		.idProduct =	USB_ID_US122MKII
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x0644,
+		.idProduct =	USB_ID_US144MKII
+	},
+	{ /* terminator */ }
+};
+
+MODULE_DEVICE_TABLE(usb, snd_us122l_usb_id_table);
+static struct usb_driver snd_us122l_usb_driver = {
+	.name =		"snd-usb-us122l",
+	.probe =	snd_us122l_probe,
+	.disconnect =	snd_us122l_disconnect,
+	.suspend =	snd_us122l_suspend,
+	.resume =	snd_us122l_resume,
+	.reset_resume =	snd_us122l_resume,
+	.id_table =	snd_us122l_usb_id_table,
+	.supports_autosuspend = 1
+};
+
+
+static int __init snd_us122l_module_init(void)
+{
+	return usb_register(&snd_us122l_usb_driver);
+}
+
+static void __exit snd_us122l_module_exit(void)
+{
+	usb_deregister(&snd_us122l_usb_driver);
+}
+
+module_init(snd_us122l_module_init)
+module_exit(snd_us122l_module_exit)
diff --git a/linux/sound/usb/usx2y/us122l.h b/linux/sound/usb/usx2y/us122l.h
new file mode 100644
index 0000000..f263b3f
--- /dev/null
+++ b/linux/sound/usb/usx2y/us122l.h
@@ -0,0 +1,31 @@
+#ifndef US122L_H
+#define US122L_H
+
+
+struct us122l {
+	struct usb_device	*dev;
+	int			card_index;
+	int			stride;
+	struct usb_stream_kernel sk;
+
+	struct mutex		mutex;
+	struct file		*first;
+	unsigned		second_periods_polled;
+	struct file		*master;
+	struct file		*slave;
+	struct list_head	midi_list;
+
+	atomic_t		mmap_count;
+};
+
+
+#define US122L(c) ((struct us122l *)(c)->private_data)
+
+#define NAME_ALLCAPS "US-122L"
+
+#define USB_ID_US122L 0x800E
+#define USB_ID_US144 0x800F
+#define USB_ID_US122MKII 0x8021
+#define USB_ID_US144MKII 0x8020
+
+#endif
diff --git a/linux/sound/usb/usx2y/usX2Yhwdep.c b/linux/sound/usb/usx2y/usX2Yhwdep.c
new file mode 100644
index 0000000..04aafb4
--- /dev/null
+++ b/linux/sound/usb/usx2y/usX2Yhwdep.c
@@ -0,0 +1,265 @@
+/*
+ * Driver for Tascam US-X2Y USB soundcards
+ *
+ * FPGA Loader + ALSA Startup
+ *
+ * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   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/interrupt.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+#include <sound/memalloc.h>
+#include <sound/pcm.h>
+#include <sound/hwdep.h>
+#include "usx2y.h"
+#include "usbusx2y.h"
+#include "usX2Yhwdep.h"
+
+static int snd_us428ctls_vm_fault(struct vm_area_struct *area,
+				  struct vm_fault *vmf)
+{
+	unsigned long offset;
+	struct page * page;
+	void *vaddr;
+
+	snd_printdd("ENTER, start %lXh, pgoff %ld\n",
+		   area->vm_start,
+		   vmf->pgoff);
+	
+	offset = vmf->pgoff << PAGE_SHIFT;
+	vaddr = (char*)((struct usX2Ydev *)area->vm_private_data)->us428ctls_sharedmem + offset;
+	page = virt_to_page(vaddr);
+	get_page(page);
+	vmf->page = page;
+
+	snd_printdd("vaddr=%p made us428ctls_vm_fault() page %p\n",
+		    vaddr, page);
+
+	return 0;
+}
+
+static const struct vm_operations_struct us428ctls_vm_ops = {
+	.fault = snd_us428ctls_vm_fault,
+};
+
+static int snd_us428ctls_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area)
+{
+	unsigned long	size = (unsigned long)(area->vm_end - area->vm_start);
+	struct usX2Ydev	*us428 = hw->private_data;
+
+	// FIXME this hwdep interface is used twice: fpga download and mmap for controlling Lights etc. Maybe better using 2 hwdep devs?
+	// so as long as the device isn't fully initialised yet we return -EBUSY here.
+ 	if (!(us428->chip_status & USX2Y_STAT_CHIP_INIT))
+		return -EBUSY;
+
+	/* if userspace tries to mmap beyond end of our buffer, fail */ 
+        if (size > PAGE_ALIGN(sizeof(struct us428ctls_sharedmem))) {
+		snd_printd( "%lu > %lu\n", size, (unsigned long)sizeof(struct us428ctls_sharedmem)); 
+                return -EINVAL;
+	}
+
+	if (!us428->us428ctls_sharedmem) {
+		init_waitqueue_head(&us428->us428ctls_wait_queue_head);
+		if(!(us428->us428ctls_sharedmem = snd_malloc_pages(sizeof(struct us428ctls_sharedmem), GFP_KERNEL)))
+			return -ENOMEM;
+		memset(us428->us428ctls_sharedmem, -1, sizeof(struct us428ctls_sharedmem));
+		us428->us428ctls_sharedmem->CtlSnapShotLast = -2;
+	}
+	area->vm_ops = &us428ctls_vm_ops;
+	area->vm_flags |= VM_RESERVED | VM_DONTEXPAND;
+	area->vm_private_data = hw->private_data;
+	return 0;
+}
+
+static unsigned int snd_us428ctls_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait)
+{
+	unsigned int	mask = 0;
+	struct usX2Ydev	*us428 = hw->private_data;
+	struct us428ctls_sharedmem *shm = us428->us428ctls_sharedmem;
+	if (us428->chip_status & USX2Y_STAT_CHIP_HUP)
+		return POLLHUP;
+
+	poll_wait(file, &us428->us428ctls_wait_queue_head, wait);
+
+	if (shm != NULL && shm->CtlSnapShotLast != shm->CtlSnapShotRed)
+		mask |= POLLIN;
+
+	return mask;
+}
+
+
+static int snd_usX2Y_hwdep_dsp_status(struct snd_hwdep *hw,
+				      struct snd_hwdep_dsp_status *info)
+{
+	static char *type_ids[USX2Y_TYPE_NUMS] = {
+		[USX2Y_TYPE_122] = "us122",
+		[USX2Y_TYPE_224] = "us224",
+		[USX2Y_TYPE_428] = "us428",
+	};
+	struct usX2Ydev	*us428 = hw->private_data;
+	int id = -1;
+
+	switch (le16_to_cpu(us428->dev->descriptor.idProduct)) {
+	case USB_ID_US122:
+		id = USX2Y_TYPE_122;
+		break;
+	case USB_ID_US224:
+		id = USX2Y_TYPE_224;
+		break;
+	case USB_ID_US428:
+		id = USX2Y_TYPE_428;
+		break;
+	}
+	if (0 > id)
+		return -ENODEV;
+	strcpy(info->id, type_ids[id]);
+	info->num_dsps = 2;		// 0: Prepad Data, 1: FPGA Code
+	if (us428->chip_status & USX2Y_STAT_CHIP_INIT)
+		info->chip_ready = 1;
+ 	info->version = USX2Y_DRIVER_VERSION; 
+	return 0;
+}
+
+
+static int usX2Y_create_usbmidi(struct snd_card *card)
+{
+	static struct snd_usb_midi_endpoint_info quirk_data_1 = {
+		.out_ep = 0x06,
+		.in_ep = 0x06,
+		.out_cables =	0x001,
+		.in_cables =	0x001
+	};
+	static struct snd_usb_audio_quirk quirk_1 = {
+		.vendor_name =	"TASCAM",
+		.product_name =	NAME_ALLCAPS,
+		.ifnum = 	0,
+       		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = &quirk_data_1
+	};
+	static struct snd_usb_midi_endpoint_info quirk_data_2 = {
+		.out_ep = 0x06,
+		.in_ep = 0x06,
+		.out_cables =	0x003,
+		.in_cables =	0x003
+	};
+	static struct snd_usb_audio_quirk quirk_2 = {
+		.vendor_name =	"TASCAM",
+		.product_name =	"US428",
+		.ifnum = 	0,
+       		.type = QUIRK_MIDI_FIXED_ENDPOINT,
+		.data = &quirk_data_2
+	};
+	struct usb_device *dev = usX2Y(card)->dev;
+	struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
+	struct snd_usb_audio_quirk *quirk =
+		le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ?
+		&quirk_2 : &quirk_1;
+
+	snd_printdd("usX2Y_create_usbmidi \n");
+	return snd_usbmidi_create(card, iface, &usX2Y(card)->midi_list, quirk);
+}
+
+static int usX2Y_create_alsa_devices(struct snd_card *card)
+{
+	int err;
+
+	do {
+		if ((err = usX2Y_create_usbmidi(card)) < 0) {
+			snd_printk(KERN_ERR "usX2Y_create_alsa_devices: usX2Y_create_usbmidi error %i \n", err);
+			break;
+		}
+		if ((err = usX2Y_audio_create(card)) < 0) 
+			break;
+		if ((err = usX2Y_hwdep_pcm_new(card)) < 0)
+			break;
+		if ((err = snd_card_register(card)) < 0)
+			break;
+	} while (0);
+
+	return err;
+} 
+
+static int snd_usX2Y_hwdep_dsp_load(struct snd_hwdep *hw,
+				    struct snd_hwdep_dsp_image *dsp)
+{
+	struct usX2Ydev *priv = hw->private_data;
+	int	lret, err = -EINVAL;
+	snd_printdd( "dsp_load %s\n", dsp->name);
+
+	if (access_ok(VERIFY_READ, dsp->image, dsp->length)) {
+		struct usb_device* dev = priv->dev;
+		char *buf;
+
+		buf = memdup_user(dsp->image, dsp->length);
+		if (IS_ERR(buf))
+			return PTR_ERR(buf);
+
+		err = usb_set_interface(dev, 0, 1);
+		if (err)
+			snd_printk(KERN_ERR "usb_set_interface error \n");
+		else
+			err = usb_bulk_msg(dev, usb_sndbulkpipe(dev, 2), buf, dsp->length, &lret, 6000);
+		kfree(buf);
+	}
+	if (err)
+		return err;
+	if (dsp->index == 1) {
+		msleep(250);				// give the device some time
+		err = usX2Y_AsyncSeq04_init(priv);
+		if (err) {
+			snd_printk(KERN_ERR "usX2Y_AsyncSeq04_init error \n");
+			return err;
+		}
+		err = usX2Y_In04_init(priv);
+		if (err) {
+			snd_printk(KERN_ERR "usX2Y_In04_init error \n");
+			return err;
+		}
+		err = usX2Y_create_alsa_devices(hw->card);
+		if (err) {
+			snd_printk(KERN_ERR "usX2Y_create_alsa_devices error %i \n", err);
+			snd_card_free(hw->card);
+			return err;
+		}
+		priv->chip_status |= USX2Y_STAT_CHIP_INIT; 
+		snd_printdd("%s: alsa all started\n", hw->name);
+	}
+	return err;
+}
+
+
+int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device)
+{
+	int err;
+	struct snd_hwdep *hw;
+
+	if ((err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw)) < 0)
+		return err;
+
+	hw->iface = SNDRV_HWDEP_IFACE_USX2Y;
+	hw->private_data = usX2Y(card);
+	hw->ops.dsp_status = snd_usX2Y_hwdep_dsp_status;
+	hw->ops.dsp_load = snd_usX2Y_hwdep_dsp_load;
+	hw->ops.mmap = snd_us428ctls_mmap;
+	hw->ops.poll = snd_us428ctls_poll;
+	hw->exclusive = 1;
+	sprintf(hw->name, "/proc/bus/usb/%03d/%03d", device->bus->busnum, device->devnum);
+	return 0;
+}
+
diff --git a/linux/sound/usb/usx2y/usX2Yhwdep.h b/linux/sound/usb/usx2y/usX2Yhwdep.h
new file mode 100644
index 0000000..c095d5b
--- /dev/null
+++ b/linux/sound/usb/usx2y/usX2Yhwdep.h
@@ -0,0 +1,6 @@
+#ifndef USX2YHWDEP_H
+#define USX2YHWDEP_H
+
+int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device);
+
+#endif
diff --git a/linux/sound/usb/usx2y/usb_stream.c b/linux/sound/usb/usx2y/usb_stream.c
new file mode 100644
index 0000000..c400ade
--- /dev/null
+++ b/linux/sound/usb/usx2y/usb_stream.c
@@ -0,0 +1,756 @@
+/*
+ * Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/usb.h>
+#include <linux/gfp.h>
+
+#include "usb_stream.h"
+
+
+/*                             setup                                  */
+
+static unsigned usb_stream_next_packet_size(struct usb_stream_kernel *sk)
+{
+	struct usb_stream *s = sk->s;
+	sk->out_phase_peeked = (sk->out_phase & 0xffff) + sk->freqn;
+	return (sk->out_phase_peeked >> 16) * s->cfg.frame_size;
+}
+
+static void playback_prep_freqn(struct usb_stream_kernel *sk, struct urb *urb)
+{
+	struct usb_stream *s = sk->s;
+	int pack, lb = 0;
+
+	for (pack = 0; pack < sk->n_o_ps; pack++) {
+		int l = usb_stream_next_packet_size(sk);
+		if (s->idle_outsize + lb + l > s->period_size)
+			goto check;
+
+		sk->out_phase = sk->out_phase_peeked;
+		urb->iso_frame_desc[pack].offset = lb;
+		urb->iso_frame_desc[pack].length = l;
+		lb += l;
+	}
+	snd_printdd(KERN_DEBUG "%i\n", lb);
+
+check:
+	urb->number_of_packets = pack;
+	urb->transfer_buffer_length = lb;
+	s->idle_outsize += lb - s->period_size;
+	snd_printdd(KERN_DEBUG "idle=%i ul=%i ps=%i\n", s->idle_outsize,
+		    lb, s->period_size);
+}
+
+static void init_pipe_urbs(struct usb_stream_kernel *sk, unsigned use_packsize,
+			   struct urb **urbs, char *transfer,
+			   struct usb_device *dev, int pipe)
+{
+	int u, p;
+	int maxpacket = use_packsize ?
+		use_packsize : usb_maxpacket(dev, pipe, usb_pipeout(pipe));
+	int transfer_length = maxpacket * sk->n_o_ps;
+
+	for (u = 0; u < USB_STREAM_NURBS;
+	     ++u, transfer += transfer_length) {
+		struct urb *urb = urbs[u];
+		struct usb_iso_packet_descriptor *desc;
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->transfer_buffer = transfer;
+		urb->dev = dev;
+		urb->pipe = pipe;
+		urb->number_of_packets = sk->n_o_ps;
+		urb->context = sk;
+		urb->interval = 1;
+		if (usb_pipeout(pipe))
+			continue;
+
+		urb->transfer_buffer_length = transfer_length;
+		desc = urb->iso_frame_desc;
+		desc->offset = 0;
+		desc->length = maxpacket;
+		for (p = 1; p < sk->n_o_ps; ++p) {
+			desc[p].offset = desc[p - 1].offset + maxpacket;
+			desc[p].length = maxpacket;
+		}
+	}
+}
+
+static void init_urbs(struct usb_stream_kernel *sk, unsigned use_packsize,
+		      struct usb_device *dev, int in_pipe, int out_pipe)
+{
+	struct usb_stream	*s = sk->s;
+	char			*indata = (char *)s + sizeof(*s) +
+					sizeof(struct usb_stream_packet) *
+					s->inpackets;
+	int			u;
+
+	for (u = 0; u < USB_STREAM_NURBS; ++u) {
+		sk->inurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL);
+		sk->outurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL);
+	}
+
+	init_pipe_urbs(sk, use_packsize, sk->inurb, indata, dev, in_pipe);
+	init_pipe_urbs(sk, use_packsize, sk->outurb, sk->write_page, dev,
+		       out_pipe);
+}
+
+
+/*
+ * convert a sampling rate into our full speed format (fs/1000 in Q16.16)
+ * this will overflow at approx 524 kHz
+ */
+static inline unsigned get_usb_full_speed_rate(unsigned rate)
+{
+	return ((rate << 13) + 62) / 125;
+}
+
+/*
+ * convert a sampling rate into USB high speed format (fs/8000 in Q16.16)
+ * this will overflow at approx 4 MHz
+ */
+static inline unsigned get_usb_high_speed_rate(unsigned rate)
+{
+	return ((rate << 10) + 62) / 125;
+}
+
+void usb_stream_free(struct usb_stream_kernel *sk)
+{
+	struct usb_stream *s;
+	unsigned u;
+
+	for (u = 0; u < USB_STREAM_NURBS; ++u) {
+		usb_free_urb(sk->inurb[u]);
+		sk->inurb[u] = NULL;
+		usb_free_urb(sk->outurb[u]);
+		sk->outurb[u] = NULL;
+	}
+
+	s = sk->s;
+	if (!s)
+		return;
+
+	free_pages((unsigned long)sk->write_page, get_order(s->write_size));
+	sk->write_page = NULL;
+	free_pages((unsigned long)s, get_order(s->read_size));
+	sk->s = NULL;
+}
+
+struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
+				  struct usb_device *dev,
+				  unsigned in_endpoint, unsigned out_endpoint,
+				  unsigned sample_rate, unsigned use_packsize,
+				  unsigned period_frames, unsigned frame_size)
+{
+	int packets, max_packsize;
+	int in_pipe, out_pipe;
+	int read_size = sizeof(struct usb_stream);
+	int write_size;
+	int usb_frames = dev->speed == USB_SPEED_HIGH ? 8000 : 1000;
+	int pg;
+
+	in_pipe = usb_rcvisocpipe(dev, in_endpoint);
+	out_pipe = usb_sndisocpipe(dev, out_endpoint);
+
+	max_packsize = use_packsize ?
+		use_packsize : usb_maxpacket(dev, in_pipe, 0);
+
+	/*
+		t_period = period_frames / sample_rate
+		iso_packs = t_period / t_iso_frame
+			= (period_frames / sample_rate) * (1 / t_iso_frame)
+	*/
+
+	packets = period_frames * usb_frames / sample_rate + 1;
+
+	if (dev->speed == USB_SPEED_HIGH)
+		packets = (packets + 7) & ~7;
+
+	read_size += packets * USB_STREAM_URBDEPTH *
+		(max_packsize + sizeof(struct usb_stream_packet));
+
+	max_packsize = usb_maxpacket(dev, out_pipe, 1);
+	write_size = max_packsize * packets * USB_STREAM_URBDEPTH;
+
+	if (read_size >= 256*PAGE_SIZE || write_size >= 256*PAGE_SIZE) {
+		snd_printk(KERN_WARNING "a size exceeds 128*PAGE_SIZE\n");
+		goto out;
+	}
+
+	pg = get_order(read_size);
+	sk->s = (void *) __get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO, pg);
+	if (!sk->s) {
+		snd_printk(KERN_WARNING "couldn't __get_free_pages()\n");
+		goto out;
+	}
+	sk->s->cfg.version = USB_STREAM_INTERFACE_VERSION;
+
+	sk->s->read_size = read_size;
+
+	sk->s->cfg.sample_rate = sample_rate;
+	sk->s->cfg.frame_size = frame_size;
+	sk->n_o_ps = packets;
+	sk->s->inpackets = packets * USB_STREAM_URBDEPTH;
+	sk->s->cfg.period_frames = period_frames;
+	sk->s->period_size = frame_size * period_frames;
+
+	sk->s->write_size = write_size;
+	pg = get_order(write_size);
+
+	sk->write_page =
+		(void *)__get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO, pg);
+	if (!sk->write_page) {
+		snd_printk(KERN_WARNING "couldn't __get_free_pages()\n");
+		usb_stream_free(sk);
+		return NULL;
+	}
+
+	/* calculate the frequency in 16.16 format */
+	if (dev->speed == USB_SPEED_FULL)
+		sk->freqn = get_usb_full_speed_rate(sample_rate);
+	else
+		sk->freqn = get_usb_high_speed_rate(sample_rate);
+
+	init_urbs(sk, use_packsize, dev, in_pipe, out_pipe);
+	sk->s->state = usb_stream_stopped;
+out:
+	return sk->s;
+}
+
+
+/*                             start                                  */
+
+static bool balance_check(struct usb_stream_kernel *sk, struct urb *urb)
+{
+	bool r;
+	if (unlikely(urb->status)) {
+		if (urb->status != -ESHUTDOWN && urb->status != -ENOENT)
+			snd_printk(KERN_WARNING "status=%i\n", urb->status);
+		sk->iso_frame_balance = 0x7FFFFFFF;
+		return false;
+	}
+	r = sk->iso_frame_balance == 0;
+	if (!r)
+		sk->i_urb = urb;
+	return r;
+}
+
+static bool balance_playback(struct usb_stream_kernel *sk, struct urb *urb)
+{
+	sk->iso_frame_balance += urb->number_of_packets;
+	return balance_check(sk, urb);
+}
+
+static bool balance_capture(struct usb_stream_kernel *sk, struct urb *urb)
+{
+	sk->iso_frame_balance -= urb->number_of_packets;
+	return balance_check(sk, urb);
+}
+
+static void subs_set_complete(struct urb **urbs, void (*complete)(struct urb *))
+{
+	int u;
+
+	for (u = 0; u < USB_STREAM_NURBS; u++) {
+		struct urb *urb = urbs[u];
+		urb->complete = complete;
+	}
+}
+
+static int usb_stream_prepare_playback(struct usb_stream_kernel *sk,
+		struct urb *inurb)
+{
+	struct usb_stream *s = sk->s;
+	struct urb *io;
+	struct usb_iso_packet_descriptor *id, *od;
+	int p = 0, lb = 0, l = 0;
+
+	io = sk->idle_outurb;
+	od = io->iso_frame_desc;
+
+	for (; s->sync_packet < 0; ++p, ++s->sync_packet) {
+		struct urb *ii = sk->completed_inurb;
+		id = ii->iso_frame_desc +
+			ii->number_of_packets + s->sync_packet;
+		l = id->actual_length;
+
+		od[p].length = l;
+		od[p].offset = lb;
+		lb += l;
+	}
+
+	for (;
+	     s->sync_packet < inurb->number_of_packets && p < sk->n_o_ps;
+	     ++p, ++s->sync_packet) {
+		l = inurb->iso_frame_desc[s->sync_packet].actual_length;
+
+		if (s->idle_outsize + lb + l > s->period_size)
+			goto check_ok;
+
+		od[p].length = l;
+		od[p].offset = lb;
+		lb += l;
+	}
+
+check_ok:
+	s->sync_packet -= inurb->number_of_packets;
+	if (unlikely(s->sync_packet < -2 || s->sync_packet > 0)) {
+		snd_printk(KERN_WARNING "invalid sync_packet = %i;"
+			   " p=%i nop=%i %i %x %x %x > %x\n",
+			   s->sync_packet, p, inurb->number_of_packets,
+			   s->idle_outsize + lb + l,
+			   s->idle_outsize, lb,  l,
+			   s->period_size);
+		return -1;
+	}
+	if (unlikely(lb % s->cfg.frame_size)) {
+		snd_printk(KERN_WARNING"invalid outsize = %i\n",
+			   lb);
+		return -1;
+	}
+	s->idle_outsize += lb - s->period_size;
+	io->number_of_packets = p;
+	io->transfer_buffer_length = lb;
+	if (s->idle_outsize <= 0)
+		return 0;
+
+	snd_printk(KERN_WARNING "idle=%i\n", s->idle_outsize);
+	return -1;
+}
+
+static void prepare_inurb(int number_of_packets, struct urb *iu)
+{
+	struct usb_iso_packet_descriptor *id;
+	int p;
+
+	iu->number_of_packets = number_of_packets;
+	id = iu->iso_frame_desc;
+	id->offset = 0;
+	for (p = 0; p < iu->number_of_packets - 1; ++p)
+		id[p + 1].offset = id[p].offset + id[p].length;
+
+	iu->transfer_buffer_length =
+		id[0].length * iu->number_of_packets;
+}
+
+static int submit_urbs(struct usb_stream_kernel *sk,
+		       struct urb *inurb, struct urb *outurb)
+{
+	int err;
+	prepare_inurb(sk->idle_outurb->number_of_packets, sk->idle_inurb);
+	err = usb_submit_urb(sk->idle_inurb, GFP_ATOMIC);
+	if (err < 0) {
+		snd_printk(KERN_ERR "%i\n", err);
+		return err;
+	}
+	sk->idle_inurb = sk->completed_inurb;
+	sk->completed_inurb = inurb;
+	err = usb_submit_urb(sk->idle_outurb, GFP_ATOMIC);
+	if (err < 0) {
+		snd_printk(KERN_ERR "%i\n", err);
+		return err;
+	}
+	sk->idle_outurb = sk->completed_outurb;
+	sk->completed_outurb = outurb;
+	return 0;
+}
+
+#ifdef DEBUG_LOOP_BACK
+/*
+  This loop_back() shows how to read/write the period data.
+ */
+static void loop_back(struct usb_stream *s)
+{
+	char *i, *o;
+	int il, ol, l, p;
+	struct urb *iu;
+	struct usb_iso_packet_descriptor *id;
+
+	o = s->playback1st_to;
+	ol = s->playback1st_size;
+	l = 0;
+
+	if (s->insplit_pack >= 0) {
+		iu = sk->idle_inurb;
+		id = iu->iso_frame_desc;
+		p = s->insplit_pack;
+	} else
+		goto second;
+loop:
+	for (; p < iu->number_of_packets && l < s->period_size; ++p) {
+		i = iu->transfer_buffer + id[p].offset;
+		il = id[p].actual_length;
+		if (l + il > s->period_size)
+			il = s->period_size - l;
+		if (il <= ol) {
+			memcpy(o, i, il);
+			o += il;
+			ol -= il;
+		} else {
+			memcpy(o, i, ol);
+			singen_6pack(o, ol);
+			o = s->playback_to;
+			memcpy(o, i + ol, il - ol);
+			o += il - ol;
+			ol = s->period_size - s->playback1st_size;
+		}
+		l += il;
+	}
+	if (iu == sk->completed_inurb) {
+		if (l != s->period_size)
+			printk(KERN_DEBUG"%s:%i %i\n", __func__, __LINE__,
+			       l/(int)s->cfg.frame_size);
+
+		return;
+	}
+second:
+	iu = sk->completed_inurb;
+	id = iu->iso_frame_desc;
+	p = 0;
+	goto loop;
+
+}
+#else
+static void loop_back(struct usb_stream *s)
+{
+}
+#endif
+
+static void stream_idle(struct usb_stream_kernel *sk,
+			struct urb *inurb, struct urb *outurb)
+{
+	struct usb_stream *s = sk->s;
+	int l, p;
+	int insize = s->idle_insize;
+	int urb_size = 0;
+
+	s->inpacket_split = s->next_inpacket_split;
+	s->inpacket_split_at = s->next_inpacket_split_at;
+	s->next_inpacket_split = -1;
+	s->next_inpacket_split_at = 0;
+
+	for (p = 0; p < inurb->number_of_packets; ++p) {
+		struct usb_iso_packet_descriptor *id = inurb->iso_frame_desc;
+		l = id[p].actual_length;
+		if (unlikely(l == 0 || id[p].status)) {
+			snd_printk(KERN_WARNING "underrun, status=%u\n",
+				   id[p].status);
+			goto err_out;
+		}
+		s->inpacket_head++;
+		s->inpacket_head %= s->inpackets;
+		if (s->inpacket_split == -1)
+			s->inpacket_split = s->inpacket_head;
+
+		s->inpacket[s->inpacket_head].offset =
+			id[p].offset + (inurb->transfer_buffer - (void *)s);
+		s->inpacket[s->inpacket_head].length = l;
+		if (insize + l > s->period_size &&
+		    s->next_inpacket_split == -1) {
+			s->next_inpacket_split = s->inpacket_head;
+			s->next_inpacket_split_at = s->period_size - insize;
+		}
+		insize += l;
+		urb_size += l;
+	}
+	s->idle_insize += urb_size - s->period_size;
+	if (s->idle_insize < 0) {
+		snd_printk(KERN_WARNING "%i\n",
+			   (s->idle_insize)/(int)s->cfg.frame_size);
+		goto err_out;
+	}
+	s->insize_done += urb_size;
+
+	l = s->idle_outsize;
+	s->outpacket[0].offset = (sk->idle_outurb->transfer_buffer -
+				  sk->write_page) - l;
+
+	if (usb_stream_prepare_playback(sk, inurb) < 0)
+		goto err_out;
+
+	s->outpacket[0].length = sk->idle_outurb->transfer_buffer_length + l;
+	s->outpacket[1].offset = sk->completed_outurb->transfer_buffer -
+		sk->write_page;
+
+	if (submit_urbs(sk, inurb, outurb) < 0)
+		goto err_out;
+
+	loop_back(s);
+	s->periods_done++;
+	wake_up_all(&sk->sleep);
+	return;
+err_out:
+	s->state = usb_stream_xrun;
+	wake_up_all(&sk->sleep);
+}
+
+static void i_capture_idle(struct urb *urb)
+{
+	struct usb_stream_kernel *sk = urb->context;
+	if (balance_capture(sk, urb))
+		stream_idle(sk, urb, sk->i_urb);
+}
+
+static void i_playback_idle(struct urb *urb)
+{
+	struct usb_stream_kernel *sk = urb->context;
+	if (balance_playback(sk, urb))
+		stream_idle(sk, sk->i_urb, urb);
+}
+
+static void stream_start(struct usb_stream_kernel *sk,
+			 struct urb *inurb, struct urb *outurb)
+{
+	struct usb_stream *s = sk->s;
+	if (s->state >= usb_stream_sync1) {
+		int l, p, max_diff, max_diff_0;
+		int urb_size = 0;
+		unsigned frames_per_packet, min_frames = 0;
+		frames_per_packet = (s->period_size - s->idle_insize);
+		frames_per_packet <<= 8;
+		frames_per_packet /=
+			s->cfg.frame_size * inurb->number_of_packets;
+		frames_per_packet++;
+
+		max_diff_0 = s->cfg.frame_size;
+		if (s->cfg.period_frames >= 256)
+			max_diff_0 <<= 1;
+		if (s->cfg.period_frames >= 1024)
+			max_diff_0 <<= 1;
+		max_diff = max_diff_0;
+		for (p = 0; p < inurb->number_of_packets; ++p) {
+			int diff;
+			l = inurb->iso_frame_desc[p].actual_length;
+			urb_size += l;
+
+			min_frames += frames_per_packet;
+			diff = urb_size -
+				(min_frames >> 8) * s->cfg.frame_size;
+			if (diff < max_diff) {
+				snd_printdd(KERN_DEBUG "%i %i %i %i\n",
+					    s->insize_done,
+					    urb_size / (int)s->cfg.frame_size,
+					    inurb->number_of_packets, diff);
+				max_diff = diff;
+			}
+		}
+		s->idle_insize -= max_diff - max_diff_0;
+		s->idle_insize += urb_size - s->period_size;
+		if (s->idle_insize < 0) {
+			snd_printk(KERN_WARNING "%i %i %i\n",
+				   s->idle_insize, urb_size, s->period_size);
+			return;
+		} else if (s->idle_insize == 0) {
+			s->next_inpacket_split =
+				(s->inpacket_head + 1) % s->inpackets;
+			s->next_inpacket_split_at = 0;
+		} else {
+			unsigned split = s->inpacket_head;
+			l = s->idle_insize;
+			while (l > s->inpacket[split].length) {
+				l -= s->inpacket[split].length;
+				if (split == 0)
+					split = s->inpackets - 1;
+				else
+					split--;
+			}
+			s->next_inpacket_split = split;
+			s->next_inpacket_split_at =
+				s->inpacket[split].length - l;
+		}
+
+		s->insize_done += urb_size;
+
+		if (usb_stream_prepare_playback(sk, inurb) < 0)
+			return;
+
+	} else
+		playback_prep_freqn(sk, sk->idle_outurb);
+
+	if (submit_urbs(sk, inurb, outurb) < 0)
+		return;
+
+	if (s->state == usb_stream_sync1 && s->insize_done > 360000) {
+		/* just guesswork                            ^^^^^^ */
+		s->state = usb_stream_ready;
+		subs_set_complete(sk->inurb, i_capture_idle);
+		subs_set_complete(sk->outurb, i_playback_idle);
+	}
+}
+
+static void i_capture_start(struct urb *urb)
+{
+	struct usb_iso_packet_descriptor *id = urb->iso_frame_desc;
+	struct usb_stream_kernel *sk = urb->context;
+	struct usb_stream *s = sk->s;
+	int p;
+	int empty = 0;
+
+	if (urb->status) {
+		snd_printk(KERN_WARNING "status=%i\n", urb->status);
+		return;
+	}
+
+	for (p = 0; p < urb->number_of_packets; ++p) {
+		int l = id[p].actual_length;
+		if (l < s->cfg.frame_size) {
+			++empty;
+			if (s->state >= usb_stream_sync0) {
+				snd_printk(KERN_WARNING "%i\n", l);
+				return;
+			}
+		}
+		s->inpacket_head++;
+		s->inpacket_head %= s->inpackets;
+		s->inpacket[s->inpacket_head].offset =
+			id[p].offset + (urb->transfer_buffer - (void *)s);
+		s->inpacket[s->inpacket_head].length = l;
+	}
+#ifdef SHOW_EMPTY
+	if (empty) {
+		printk(KERN_DEBUG"%s:%i: %i", __func__, __LINE__,
+		       urb->iso_frame_desc[0].actual_length);
+		for (pack = 1; pack < urb->number_of_packets; ++pack) {
+			int l = urb->iso_frame_desc[pack].actual_length;
+			printk(" %i", l);
+		}
+		printk("\n");
+	}
+#endif
+	if (!empty && s->state < usb_stream_sync1)
+		++s->state;
+
+	if (balance_capture(sk, urb))
+		stream_start(sk, urb, sk->i_urb);
+}
+
+static void i_playback_start(struct urb *urb)
+{
+	struct usb_stream_kernel *sk = urb->context;
+	if (balance_playback(sk, urb))
+		stream_start(sk, sk->i_urb, urb);
+}
+
+int usb_stream_start(struct usb_stream_kernel *sk)
+{
+	struct usb_stream *s = sk->s;
+	int frame = 0, iters = 0;
+	int u, err;
+	int try = 0;
+
+	if (s->state != usb_stream_stopped)
+		return -EAGAIN;
+
+	subs_set_complete(sk->inurb, i_capture_start);
+	subs_set_complete(sk->outurb, i_playback_start);
+	memset(sk->write_page, 0, s->write_size);
+dotry:
+	s->insize_done = 0;
+	s->idle_insize = 0;
+	s->idle_outsize = 0;
+	s->sync_packet = -1;
+	s->inpacket_head = -1;
+	sk->iso_frame_balance = 0;
+	++try;
+	for (u = 0; u < 2; u++) {
+		struct urb *inurb = sk->inurb[u];
+		struct urb *outurb = sk->outurb[u];
+		playback_prep_freqn(sk, outurb);
+		inurb->number_of_packets = outurb->number_of_packets;
+		inurb->transfer_buffer_length =
+			inurb->number_of_packets *
+			inurb->iso_frame_desc[0].length;
+		preempt_disable();
+		if (u == 0) {
+			int now;
+			struct usb_device *dev = inurb->dev;
+			frame = usb_get_current_frame_number(dev);
+			do {
+				now = usb_get_current_frame_number(dev);
+				++iters;
+			} while (now > -1 && now == frame);
+		}
+		err = usb_submit_urb(inurb, GFP_ATOMIC);
+		if (err < 0) {
+			preempt_enable();
+			snd_printk(KERN_ERR"usb_submit_urb(sk->inurb[%i])"
+				   " returned %i\n", u, err);
+			return err;
+		}
+		err = usb_submit_urb(outurb, GFP_ATOMIC);
+		if (err < 0) {
+			preempt_enable();
+			snd_printk(KERN_ERR"usb_submit_urb(sk->outurb[%i])"
+				   " returned %i\n", u, err);
+			return err;
+		}
+		preempt_enable();
+		if (inurb->start_frame != outurb->start_frame) {
+			snd_printd(KERN_DEBUG
+				   "u[%i] start_frames differ in:%u out:%u\n",
+				   u, inurb->start_frame, outurb->start_frame);
+			goto check_retry;
+		}
+	}
+	snd_printdd(KERN_DEBUG "%i %i\n", frame, iters);
+	try = 0;
+check_retry:
+	if (try) {
+		usb_stream_stop(sk);
+		if (try < 5) {
+			msleep(1500);
+			snd_printd(KERN_DEBUG "goto dotry;\n");
+			goto dotry;
+		}
+		snd_printk(KERN_WARNING"couldn't start"
+			   " all urbs on the same start_frame.\n");
+		return -EFAULT;
+	}
+
+	sk->idle_inurb = sk->inurb[USB_STREAM_NURBS - 2];
+	sk->idle_outurb = sk->outurb[USB_STREAM_NURBS - 2];
+	sk->completed_inurb = sk->inurb[USB_STREAM_NURBS - 1];
+	sk->completed_outurb = sk->outurb[USB_STREAM_NURBS - 1];
+
+/* wait, check */
+	{
+		int wait_ms = 3000;
+		while (s->state != usb_stream_ready && wait_ms > 0) {
+			snd_printdd(KERN_DEBUG "%i\n", s->state);
+			msleep(200);
+			wait_ms -= 200;
+		}
+	}
+
+	return s->state == usb_stream_ready ? 0 : -EFAULT;
+}
+
+
+/*                             stop                                   */
+
+void usb_stream_stop(struct usb_stream_kernel *sk)
+{
+	int u;
+	if (!sk->s)
+		return;
+	for (u = 0; u < USB_STREAM_NURBS; ++u) {
+		usb_kill_urb(sk->inurb[u]);
+		usb_kill_urb(sk->outurb[u]);
+	}
+	sk->s->state = usb_stream_stopped;
+	msleep(400);
+}
diff --git a/linux/sound/usb/usx2y/usb_stream.h b/linux/sound/usb/usx2y/usb_stream.h
new file mode 100644
index 0000000..4dd74ab
--- /dev/null
+++ b/linux/sound/usb/usx2y/usb_stream.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define USB_STREAM_INTERFACE_VERSION 2
+
+#define SNDRV_USB_STREAM_IOCTL_SET_PARAMS \
+	_IOW('H', 0x90, struct usb_stream_config)
+
+struct usb_stream_packet {
+	unsigned offset;
+	unsigned length;
+};
+
+
+struct usb_stream_config {
+	unsigned version;
+	unsigned sample_rate;
+	unsigned period_frames;
+	unsigned frame_size;
+};
+
+struct usb_stream {
+	struct usb_stream_config cfg;
+	unsigned read_size;
+	unsigned write_size;
+
+	int period_size;
+
+	unsigned state;
+
+	int idle_insize;
+	int idle_outsize;
+	int sync_packet;
+	unsigned insize_done;
+	unsigned periods_done;
+	unsigned periods_polled;
+
+	struct usb_stream_packet outpacket[2];
+	unsigned		 inpackets;
+	unsigned		 inpacket_head;
+	unsigned		 inpacket_split;
+	unsigned		 inpacket_split_at;
+	unsigned		 next_inpacket_split;
+	unsigned		 next_inpacket_split_at;
+	struct usb_stream_packet inpacket[0];
+};
+
+enum usb_stream_state {
+	usb_stream_invalid,
+	usb_stream_stopped,
+	usb_stream_sync0,
+	usb_stream_sync1,
+	usb_stream_ready,
+	usb_stream_running,
+	usb_stream_xrun,
+};
+
+#if __KERNEL__
+
+#define USB_STREAM_NURBS 4
+#define USB_STREAM_URBDEPTH 4
+
+struct usb_stream_kernel {
+	struct usb_stream *s;
+
+	void *write_page;
+
+	unsigned n_o_ps;
+
+	struct urb *inurb[USB_STREAM_NURBS];
+	struct urb *idle_inurb;
+	struct urb *completed_inurb;
+	struct urb *outurb[USB_STREAM_NURBS];
+	struct urb *idle_outurb;
+	struct urb *completed_outurb;
+	struct urb *i_urb;
+
+	int iso_frame_balance;
+
+	wait_queue_head_t sleep;
+
+	unsigned out_phase;
+	unsigned out_phase_peeked;
+	unsigned freqn;
+};
+
+struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
+				  struct usb_device *dev,
+				  unsigned in_endpoint, unsigned out_endpoint,
+				  unsigned sample_rate, unsigned use_packsize,
+				  unsigned period_frames, unsigned frame_size);
+void usb_stream_free(struct usb_stream_kernel *);
+int usb_stream_start(struct usb_stream_kernel *);
+void usb_stream_stop(struct usb_stream_kernel *);
+
+
+#endif
diff --git a/linux/sound/usb/usx2y/usbus428ctldefs.h b/linux/sound/usb/usx2y/usbus428ctldefs.h
new file mode 100644
index 0000000..b864e7e
--- /dev/null
+++ b/linux/sound/usb/usx2y/usbus428ctldefs.h
@@ -0,0 +1,104 @@
+/*
+ *
+ * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   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
+ */
+
+enum E_In84{
+	eFader0 = 0,
+	eFader1,
+	eFader2,
+	eFader3,
+	eFader4,
+	eFader5,
+	eFader6,
+	eFader7,
+	eFaderM,
+	eTransport,
+	eModifier = 10,
+	eFilterSelect,
+	eSelect,
+	eMute,
+
+	eSwitch   = 15,
+	eWheelGain,
+	eWheelFreq,
+	eWheelQ,
+	eWheelPan,
+	eWheel    = 20
+};
+
+#define T_RECORD   1
+#define T_PLAY     2
+#define T_STOP     4
+#define T_F_FWD    8
+#define T_REW   0x10
+#define T_SOLO  0x20
+#define T_REC   0x40
+#define T_NULL  0x80
+
+
+struct us428_ctls {
+	unsigned char   Fader[9];
+	unsigned char 	Transport;
+	unsigned char 	Modifier;
+	unsigned char 	FilterSelect;
+	unsigned char 	Select;
+	unsigned char   Mute;
+	unsigned char   UNKNOWN;
+	unsigned char   Switch;	     
+	unsigned char   Wheel[5];
+};
+
+struct us428_setByte {
+	unsigned char Offset,
+		Value;
+};
+
+enum {
+	eLT_Volume = 0,
+	eLT_Light
+};
+
+struct usX2Y_volume {
+	unsigned char Channel,
+		LH,
+		LL,
+		RH,
+		RL;
+};
+
+struct us428_lights {
+	struct us428_setByte Light[7];
+};
+
+struct us428_p4out {
+	char type;
+	union {
+		struct usX2Y_volume vol;
+		struct us428_lights lights;
+	} val;
+};
+
+#define N_us428_ctl_BUFS 16
+#define N_us428_p4out_BUFS 16
+struct us428ctls_sharedmem{
+	struct us428_ctls	CtlSnapShot[N_us428_ctl_BUFS];
+	int			CtlSnapShotDiffersAt[N_us428_ctl_BUFS];
+	int			CtlSnapShotLast, CtlSnapShotRed;
+	struct us428_p4out	p4out[N_us428_p4out_BUFS];
+	int			p4outLast, p4outSent;
+};
diff --git a/linux/sound/usb/usx2y/usbusx2y.c b/linux/sound/usb/usx2y/usbusx2y.c
new file mode 100644
index 0000000..cbd37f2
--- /dev/null
+++ b/linux/sound/usb/usx2y/usbusx2y.c
@@ -0,0 +1,473 @@
+/*
+ * usbusy2y.c - ALSA USB US-428 Driver
+ *
+2005-04-14 Karsten Wiese
+	Version 0.8.7.2:
+	Call snd_card_free() instead of snd_card_free_in_thread() to prevent oops with dead keyboard symptom.
+	Tested ok with kernel 2.6.12-rc2.
+
+2004-12-14 Karsten Wiese
+	Version 0.8.7.1:
+	snd_pcm_open for rawusb pcm-devices now returns -EBUSY if called without rawusb's hwdep device being open.
+
+2004-12-02 Karsten Wiese
+	Version 0.8.7:
+	Use macro usb_maxpacket() for portability.
+
+2004-10-26 Karsten Wiese
+	Version 0.8.6:
+	wake_up() process waiting in usX2Y_urbs_start() on error.
+
+2004-10-21 Karsten Wiese
+	Version 0.8.5:
+	nrpacks is runtime or compiletime configurable now with tested values from 1 to 4.
+
+2004-10-03 Karsten Wiese
+	Version 0.8.2:
+	Avoid any possible racing while in prepare callback.
+
+2004-09-30 Karsten Wiese
+	Version 0.8.0:
+	Simplified things and made ohci work again.
+
+2004-09-20 Karsten Wiese
+	Version 0.7.3:
+	Use usb_kill_urb() instead of deprecated (kernel 2.6.9) usb_unlink_urb().
+
+2004-07-13 Karsten Wiese
+	Version 0.7.1:
+	Don't sleep in START/STOP callbacks anymore.
+	us428 channels C/D not handled just for this version, sorry.
+
+2004-06-21 Karsten Wiese
+	Version 0.6.4:
+	Temporarely suspend midi input
+	to sanely call usb_set_interface() when setting format.
+
+2004-06-12 Karsten Wiese
+	Version 0.6.3:
+	Made it thus the following rule is enforced:
+	"All pcm substreams of one usX2Y have to operate at the same rate & format."
+
+2004-04-06 Karsten Wiese
+	Version 0.6.0:
+	Runs on 2.6.5 kernel without any "--with-debug=" things.
+	us224 reported running.
+
+2004-01-14 Karsten Wiese
+	Version 0.5.1:
+	Runs with 2.6.1 kernel.
+
+2003-12-30 Karsten Wiese
+	Version 0.4.1:
+	Fix 24Bit 4Channel capturing for the us428.
+
+2003-11-27 Karsten Wiese, Martin Langer
+	Version 0.4:
+	us122 support.
+	us224 could be tested by uncommenting the sections containing USB_ID_US224
+
+2003-11-03 Karsten Wiese
+	Version 0.3:
+	24Bit support. 
+	"arecord -D hw:1 -c 2 -r 48000 -M -f S24_3LE|aplay -D hw:1 -c 2 -r 48000 -M -f S24_3LE" works.
+
+2003-08-22 Karsten Wiese
+	Version 0.0.8:
+	Removed EZUSB Firmware. First Stage Firmwaredownload is now done by tascam-firmware downloader.
+	See:
+	http://usb-midi-fw.sourceforge.net/tascam-firmware.tar.gz
+
+2003-06-18 Karsten Wiese
+	Version 0.0.5:
+	changed to compile with kernel 2.4.21 and alsa 0.9.4
+
+2002-10-16 Karsten Wiese
+	Version 0.0.4:
+	compiles again with alsa-current.
+	USB_ISO_ASAP not used anymore (most of the time), instead
+	urb->start_frame is calculated here now, some calls inside usb-driver don't need to happen anymore.
+
+	To get the best out of this:
+	Disable APM-support in the kernel as APM-BIOS calls (once each second) hard disable interrupt for many precious milliseconds.
+	This helped me much on my slowish PII 400 & PIII 500.
+	ACPI yet untested but might cause the same bad behaviour.
+	Use a kernel with lowlatency and preemptiv patches applied.
+	To autoload snd-usb-midi append a line 
+		post-install snd-usb-us428 modprobe snd-usb-midi
+	to /etc/modules.conf.
+
+	known problems:
+	sliders, knobs, lights not yet handled except MASTER Volume slider.
+       	"pcm -c 2" doesn't work. "pcm -c 2 -m direct_interleaved" does.
+	KDE3: "Enable full duplex operation" deadlocks.
+
+	
+2002-08-31 Karsten Wiese
+	Version 0.0.3: audio also simplex;
+	simplifying: iso urbs only 1 packet, melted structs.
+	ASYNC_UNLINK not used anymore: no more crashes so far.....
+	for alsa 0.9 rc3.
+
+2002-08-09 Karsten Wiese
+	Version 0.0.2: midi works with snd-usb-midi, audio (only fullduplex now) with i.e. bristol.
+	The firmware has been sniffed from win2k us-428 driver 3.09.
+
+ *   Copyright (c) 2002 - 2004 Karsten Wiese
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+*/
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+#include <sound/rawmidi.h>
+#include "usx2y.h"
+#include "usbusx2y.h"
+#include "usX2Yhwdep.h"
+
+
+
+MODULE_AUTHOR("Karsten Wiese <annabellesgarden@yahoo.de>");
+MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.8.7.2");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{TASCAM(0x1604), "NAME_ALLCAPS"(0x8001)(0x8005)(0x8007) }}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS".");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS".");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS".");
+
+
+static int snd_usX2Y_card_used[SNDRV_CARDS];
+
+static void usX2Y_usb_disconnect(struct usb_device* usb_device, void* ptr);
+static void snd_usX2Y_card_private_free(struct snd_card *card);
+
+/* 
+ * pipe 4 is used for switching the lamps, setting samplerate, volumes ....   
+ */
+static void i_usX2Y_Out04Int(struct urb *urb)
+{
+#ifdef CONFIG_SND_DEBUG
+	if (urb->status) {
+		int 		i;
+		struct usX2Ydev *usX2Y = urb->context;
+		for (i = 0; i < 10 && usX2Y->AS04.urb[i] != urb; i++);
+		snd_printdd("i_usX2Y_Out04Int() urb %i status=%i\n", i, urb->status);
+	}
+#endif
+}
+
+static void i_usX2Y_In04Int(struct urb *urb)
+{
+	int			err = 0;
+	struct usX2Ydev		*usX2Y = urb->context;
+	struct us428ctls_sharedmem	*us428ctls = usX2Y->us428ctls_sharedmem;
+
+	usX2Y->In04IntCalls++;
+
+	if (urb->status) {
+		snd_printdd("Interrupt Pipe 4 came back with status=%i\n", urb->status);
+		return;
+	}
+
+	//	printk("%i:0x%02X ", 8, (int)((unsigned char*)usX2Y->In04Buf)[8]); Master volume shows 0 here if fader is at max during boot ?!?
+	if (us428ctls) {
+		int diff = -1;
+		if (-2 == us428ctls->CtlSnapShotLast) {
+			diff = 0;
+			memcpy(usX2Y->In04Last, usX2Y->In04Buf, sizeof(usX2Y->In04Last));
+			us428ctls->CtlSnapShotLast = -1;
+		} else {
+			int i;
+			for (i = 0; i < 21; i++) {
+				if (usX2Y->In04Last[i] != ((char*)usX2Y->In04Buf)[i]) {
+					if (diff < 0)
+						diff = i;
+					usX2Y->In04Last[i] = ((char*)usX2Y->In04Buf)[i];
+				}
+			}
+		}
+		if (0 <= diff) {
+			int n = us428ctls->CtlSnapShotLast + 1;
+			if (n >= N_us428_ctl_BUFS  ||  n < 0)
+				n = 0;
+			memcpy(us428ctls->CtlSnapShot + n, usX2Y->In04Buf, sizeof(us428ctls->CtlSnapShot[0]));
+			us428ctls->CtlSnapShotDiffersAt[n] = diff;
+			us428ctls->CtlSnapShotLast = n;
+			wake_up(&usX2Y->us428ctls_wait_queue_head);
+		}
+	}
+	
+	
+	if (usX2Y->US04) {
+		if (0 == usX2Y->US04->submitted)
+			do {
+				err = usb_submit_urb(usX2Y->US04->urb[usX2Y->US04->submitted++], GFP_ATOMIC);
+			} while (!err && usX2Y->US04->submitted < usX2Y->US04->len);
+	} else
+		if (us428ctls && us428ctls->p4outLast >= 0 && us428ctls->p4outLast < N_us428_p4out_BUFS) {
+			if (us428ctls->p4outLast != us428ctls->p4outSent) {
+				int j, send = us428ctls->p4outSent + 1;
+				if (send >= N_us428_p4out_BUFS)
+					send = 0;
+				for (j = 0; j < URBS_AsyncSeq  &&  !err; ++j)
+					if (0 == usX2Y->AS04.urb[j]->status) {
+						struct us428_p4out *p4out = us428ctls->p4out + send;	// FIXME if more than 1 p4out is new, 1 gets lost.
+						usb_fill_bulk_urb(usX2Y->AS04.urb[j], usX2Y->dev,
+								  usb_sndbulkpipe(usX2Y->dev, 0x04), &p4out->val.vol,
+								  p4out->type == eLT_Light ? sizeof(struct us428_lights) : 5,
+								  i_usX2Y_Out04Int, usX2Y);
+						err = usb_submit_urb(usX2Y->AS04.urb[j], GFP_ATOMIC);
+						us428ctls->p4outSent = send;
+						break;
+					}
+			}
+		}
+
+	if (err)
+		snd_printk(KERN_ERR "In04Int() usb_submit_urb err=%i\n", err);
+
+	urb->dev = usX2Y->dev;
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+/*
+ * Prepare some urbs
+ */
+int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y)
+{
+	int	err = 0,
+		i;
+
+	if (NULL == (usX2Y->AS04.buffer = kmalloc(URB_DataLen_AsyncSeq*URBS_AsyncSeq, GFP_KERNEL))) {
+		err = -ENOMEM;
+	} else
+		for (i = 0; i < URBS_AsyncSeq; ++i) {
+			if (NULL == (usX2Y->AS04.urb[i] = usb_alloc_urb(0, GFP_KERNEL))) {
+				err = -ENOMEM;
+				break;
+			}
+			usb_fill_bulk_urb(	usX2Y->AS04.urb[i], usX2Y->dev,
+						usb_sndbulkpipe(usX2Y->dev, 0x04),
+						usX2Y->AS04.buffer + URB_DataLen_AsyncSeq*i, 0,
+						i_usX2Y_Out04Int, usX2Y
+				);
+		}
+	return err;
+}
+
+int usX2Y_In04_init(struct usX2Ydev *usX2Y)
+{
+	if (! (usX2Y->In04urb = usb_alloc_urb(0, GFP_KERNEL)))
+		return -ENOMEM;
+
+	if (! (usX2Y->In04Buf = kmalloc(21, GFP_KERNEL))) {
+		usb_free_urb(usX2Y->In04urb);
+		return -ENOMEM;
+	}
+	 
+	init_waitqueue_head(&usX2Y->In04WaitQueue);
+	usb_fill_int_urb(usX2Y->In04urb, usX2Y->dev, usb_rcvintpipe(usX2Y->dev, 0x4),
+			 usX2Y->In04Buf, 21,
+			 i_usX2Y_In04Int, usX2Y,
+			 10);
+	return usb_submit_urb(usX2Y->In04urb, GFP_KERNEL);
+}
+
+static void usX2Y_unlinkSeq(struct snd_usX2Y_AsyncSeq *S)
+{
+	int	i;
+	for (i = 0; i < URBS_AsyncSeq; ++i) {
+		if (S[i].urb) {
+			usb_kill_urb(S->urb[i]);
+			usb_free_urb(S->urb[i]);
+			S->urb[i] = NULL;
+		}
+	}
+	kfree(S->buffer);
+}
+
+
+static struct usb_device_id snd_usX2Y_usb_id_table[] = {
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x1604,
+		.idProduct =	USB_ID_US428 
+	},
+	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x1604,
+		.idProduct =	USB_ID_US122 
+	},
+ 	{
+		.match_flags =	USB_DEVICE_ID_MATCH_DEVICE,
+		.idVendor =	0x1604,
+		.idProduct =	USB_ID_US224
+	},
+	{ /* terminator */ }
+};
+
+static int usX2Y_create_card(struct usb_device *device, struct snd_card **cardp)
+{
+	int		dev;
+	struct snd_card *	card;
+	int err;
+
+	for (dev = 0; dev < SNDRV_CARDS; ++dev)
+		if (enable[dev] && !snd_usX2Y_card_used[dev])
+			break;
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE,
+			      sizeof(struct usX2Ydev), &card);
+	if (err < 0)
+		return err;
+	snd_usX2Y_card_used[usX2Y(card)->card_index = dev] = 1;
+	card->private_free = snd_usX2Y_card_private_free;
+	usX2Y(card)->dev = device;
+	init_waitqueue_head(&usX2Y(card)->prepare_wait_queue);
+	mutex_init(&usX2Y(card)->prepare_mutex);
+	INIT_LIST_HEAD(&usX2Y(card)->midi_list);
+	strcpy(card->driver, "USB "NAME_ALLCAPS"");
+	sprintf(card->shortname, "TASCAM "NAME_ALLCAPS"");
+	sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)",
+		card->shortname, 
+		le16_to_cpu(device->descriptor.idVendor),
+		le16_to_cpu(device->descriptor.idProduct),
+		0,//us428(card)->usbmidi.ifnum,
+		usX2Y(card)->dev->bus->busnum, usX2Y(card)->dev->devnum
+		);
+	*cardp = card;
+	return 0;
+}
+
+
+static int usX2Y_usb_probe(struct usb_device *device,
+			   struct usb_interface *intf,
+			   const struct usb_device_id *device_id,
+			   struct snd_card **cardp)
+{
+	int		err;
+	struct snd_card *	card;
+
+	*cardp = NULL;
+	if (le16_to_cpu(device->descriptor.idVendor) != 0x1604 ||
+	    (le16_to_cpu(device->descriptor.idProduct) != USB_ID_US122 &&
+	     le16_to_cpu(device->descriptor.idProduct) != USB_ID_US224 &&
+	     le16_to_cpu(device->descriptor.idProduct) != USB_ID_US428))
+		return -EINVAL;
+
+	err = usX2Y_create_card(device, &card);
+	if (err < 0)
+		return err;
+	snd_card_set_dev(card, &intf->dev);
+	if ((err = usX2Y_hwdep_new(card, device)) < 0  ||
+	    (err = snd_card_register(card)) < 0) {
+		snd_card_free(card);
+		return err;
+	}
+	*cardp = card;
+	return 0;
+}
+
+/*
+ * new 2.5 USB kernel API
+ */
+static int snd_usX2Y_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct snd_card *card;
+	int err;
+
+	err = usX2Y_usb_probe(interface_to_usbdev(intf), intf, id, &card);
+	if (err < 0)
+		return err;
+	dev_set_drvdata(&intf->dev, card);
+	return 0;
+}
+
+static void snd_usX2Y_disconnect(struct usb_interface *intf)
+{
+	usX2Y_usb_disconnect(interface_to_usbdev(intf),
+				 usb_get_intfdata(intf));
+}
+
+MODULE_DEVICE_TABLE(usb, snd_usX2Y_usb_id_table);
+static struct usb_driver snd_usX2Y_usb_driver = {
+	.name =		"snd-usb-usx2y",
+	.probe =	snd_usX2Y_probe,
+	.disconnect =	snd_usX2Y_disconnect,
+	.id_table =	snd_usX2Y_usb_id_table,
+};
+
+static void snd_usX2Y_card_private_free(struct snd_card *card)
+{
+	kfree(usX2Y(card)->In04Buf);
+	usb_free_urb(usX2Y(card)->In04urb);
+	if (usX2Y(card)->us428ctls_sharedmem)
+		snd_free_pages(usX2Y(card)->us428ctls_sharedmem, sizeof(*usX2Y(card)->us428ctls_sharedmem));
+	if (usX2Y(card)->card_index >= 0  &&  usX2Y(card)->card_index < SNDRV_CARDS)
+		snd_usX2Y_card_used[usX2Y(card)->card_index] = 0;
+}
+
+/*
+ * Frees the device.
+ */
+static void usX2Y_usb_disconnect(struct usb_device *device, void* ptr)
+{
+	if (ptr) {
+		struct snd_card *card = ptr;
+		struct usX2Ydev *usX2Y = usX2Y(card);
+		struct list_head *p;
+		usX2Y->chip_status = USX2Y_STAT_CHIP_HUP;
+		usX2Y_unlinkSeq(&usX2Y->AS04);
+		usb_kill_urb(usX2Y->In04urb);
+		snd_card_disconnect(card);
+		/* release the midi resources */
+		list_for_each(p, &usX2Y->midi_list) {
+			snd_usbmidi_disconnect(p);
+		}
+		if (usX2Y->us428ctls_sharedmem) 
+			wake_up(&usX2Y->us428ctls_wait_queue_head);
+		snd_card_free(card);
+	}
+}
+
+static int __init snd_usX2Y_module_init(void)
+{
+	return usb_register(&snd_usX2Y_usb_driver);
+}
+
+static void __exit snd_usX2Y_module_exit(void)
+{
+	usb_deregister(&snd_usX2Y_usb_driver);
+}
+
+module_init(snd_usX2Y_module_init)
+module_exit(snd_usX2Y_module_exit)
diff --git a/linux/sound/usb/usx2y/usbusx2y.h b/linux/sound/usb/usx2y/usbusx2y.h
new file mode 100644
index 0000000..e43c0a8
--- /dev/null
+++ b/linux/sound/usb/usx2y/usbusx2y.h
@@ -0,0 +1,88 @@
+#ifndef USBUSX2Y_H
+#define USBUSX2Y_H
+#include "../usbaudio.h"
+#include "../midi.h"
+#include "usbus428ctldefs.h" 
+
+#define NRURBS	        2	
+
+
+#define URBS_AsyncSeq 10
+#define URB_DataLen_AsyncSeq 32
+struct snd_usX2Y_AsyncSeq {
+	struct urb	*urb[URBS_AsyncSeq];
+	char		*buffer;
+};
+
+struct snd_usX2Y_urbSeq {
+	int	submitted;
+	int	len;
+	struct urb	*urb[0];
+};
+
+#include "usx2yhwdeppcm.h"
+
+struct usX2Ydev {
+	struct usb_device	*dev;
+	int			card_index;
+	int			stride;
+	struct urb		*In04urb;
+	void			*In04Buf;
+	char			In04Last[24];
+	unsigned		In04IntCalls;
+	struct snd_usX2Y_urbSeq	*US04;
+	wait_queue_head_t	In04WaitQueue;
+	struct snd_usX2Y_AsyncSeq	AS04;
+	unsigned int		rate,
+				format;
+	int			chip_status;
+	struct mutex		prepare_mutex;
+	struct us428ctls_sharedmem	*us428ctls_sharedmem;
+	int			wait_iso_frame;
+	wait_queue_head_t	us428ctls_wait_queue_head;
+	struct snd_usX2Y_hwdep_pcm_shm	*hwdep_pcm_shm;
+	struct snd_usX2Y_substream	*subs[4];
+	struct snd_usX2Y_substream	* volatile  prepare_subs;
+	wait_queue_head_t	prepare_wait_queue;
+	struct list_head	midi_list;
+	struct list_head	pcm_list;
+	int			pcm_devs;
+};
+
+
+struct snd_usX2Y_substream {
+	struct usX2Ydev	*usX2Y;
+	struct snd_pcm_substream *pcm_substream;
+
+	int			endpoint;		
+	unsigned int		maxpacksize;		/* max packet size in bytes */
+
+	atomic_t		state;
+#define state_STOPPED	0
+#define state_STARTING1 1
+#define state_STARTING2 2
+#define state_STARTING3 3
+#define state_PREPARED	4
+#define state_PRERUNNING  6
+#define state_RUNNING	8
+
+	int			hwptr;			/* free frame position in the buffer (only for playback) */
+	int			hwptr_done;		/* processed frame position in the buffer */
+	int			transfer_done;		/* processed frames since last period update */
+
+	struct urb		*urb[NRURBS];	/* data urb table */
+	struct urb		*completed_urb;
+	char			*tmpbuf;			/* temporary buffer for playback */
+};
+
+
+#define usX2Y(c) ((struct usX2Ydev *)(c)->private_data)
+
+int usX2Y_audio_create(struct snd_card *card);
+
+int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y);
+int usX2Y_In04_init(struct usX2Ydev *usX2Y);
+
+#define NAME_ALLCAPS "US-X2Y"
+
+#endif
diff --git a/linux/sound/usb/usx2y/usbusx2yaudio.c b/linux/sound/usb/usx2y/usbusx2yaudio.c
new file mode 100644
index 0000000..5d37d1c
--- /dev/null
+++ b/linux/sound/usb/usx2y/usbusx2yaudio.c
@@ -0,0 +1,1027 @@
+/*
+ *   US-X2Y AUDIO
+ *   Copyright (c) 2002-2004 by Karsten Wiese
+ *
+ *   based on
+ *
+ *   (Tentative) USB Audio Driver for ALSA
+ *
+ *   Main and PCM part
+ *
+ *   Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   Many codes borrowed from audio.c by 
+ *	    Alan Cox (alan@lxorguk.ukuu.org.uk)
+ *	    Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ *
+ *   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/interrupt.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "usx2y.h"
+#include "usbusx2y.h"
+
+#define USX2Y_NRPACKS 4			/* Default value used for nr of packs per urb.
+					  1 to 4 have been tested ok on uhci.
+					  To use 3 on ohci, you'd need a patch:
+					  look for "0000425-linux-2.6.9-rc4-mm1_ohci-hcd.patch.gz" on
+					  "https://bugtrack.alsa-project.org/alsa-bug/bug_view_page.php?bug_id=0000425"
+					  .
+					  1, 2 and 4 work out of the box on ohci, if I recall correctly.
+					  Bigger is safer operation,
+					  smaller gives lower latencies.
+					*/
+#define USX2Y_NRPACKS_VARIABLE y	/* If your system works ok with this module's parameter
+					   nrpacks set to 1, you might as well comment 
+					   this #define out, and thereby produce smaller, faster code.
+					   You'd also set USX2Y_NRPACKS to 1 then.
+					*/
+
+#ifdef USX2Y_NRPACKS_VARIABLE
+ static int nrpacks = USX2Y_NRPACKS; /* number of packets per urb */
+ #define  nr_of_packs() nrpacks
+ module_param(nrpacks, int, 0444);
+ MODULE_PARM_DESC(nrpacks, "Number of packets per URB.");
+#else
+ #define nr_of_packs() USX2Y_NRPACKS
+#endif
+
+
+static int usX2Y_urb_capt_retire(struct snd_usX2Y_substream *subs)
+{
+	struct urb	*urb = subs->completed_urb;
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+	unsigned char	*cp;
+	int 		i, len, lens = 0, hwptr_done = subs->hwptr_done;
+	struct usX2Ydev	*usX2Y = subs->usX2Y;
+
+	for (i = 0; i < nr_of_packs(); i++) {
+		cp = (unsigned char*)urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+		if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */
+			snd_printk(KERN_ERR "active frame status %i. "
+				   "Most propably some hardware problem.\n",
+				   urb->iso_frame_desc[i].status);
+			return urb->iso_frame_desc[i].status;
+		}
+		len = urb->iso_frame_desc[i].actual_length / usX2Y->stride;
+		if (! len) {
+			snd_printd("0 == len ERROR!\n");
+			continue;
+		}
+
+		/* copy a data chunk */
+		if ((hwptr_done + len) > runtime->buffer_size) {
+			int cnt = runtime->buffer_size - hwptr_done;
+			int blen = cnt * usX2Y->stride;
+			memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp, blen);
+			memcpy(runtime->dma_area, cp + blen, len * usX2Y->stride - blen);
+		} else {
+			memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp,
+			       len * usX2Y->stride);
+		}
+		lens += len;
+		if ((hwptr_done += len) >= runtime->buffer_size)
+			hwptr_done -= runtime->buffer_size;
+	}
+
+	subs->hwptr_done = hwptr_done;
+	subs->transfer_done += lens;
+	/* update the pointer, call callback if necessary */
+	if (subs->transfer_done >= runtime->period_size) {
+		subs->transfer_done -= runtime->period_size;
+		snd_pcm_period_elapsed(subs->pcm_substream);
+	}
+	return 0;
+}
+/*
+ * prepare urb for playback data pipe
+ *
+ * we copy the data directly from the pcm buffer.
+ * the current position to be copied is held in hwptr field.
+ * since a urb can handle only a single linear buffer, if the total
+ * transferred area overflows the buffer boundary, we cannot send
+ * it directly from the buffer.  thus the data is once copied to
+ * a temporary buffer and urb points to that.
+ */
+static int usX2Y_urb_play_prepare(struct snd_usX2Y_substream *subs,
+				  struct urb *cap_urb,
+				  struct urb *urb)
+{
+	int count, counts, pack;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+
+	count = 0;
+	for (pack = 0; pack <  nr_of_packs(); pack++) {
+		/* calculate the size of a packet */
+		counts = cap_urb->iso_frame_desc[pack].actual_length / usX2Y->stride;
+		count += counts;
+		if (counts < 43 || counts > 50) {
+			snd_printk(KERN_ERR "should not be here with counts=%i\n", counts);
+			return -EPIPE;
+		}
+		/* set up descriptor */
+		urb->iso_frame_desc[pack].offset = pack ?
+			urb->iso_frame_desc[pack - 1].offset +
+			urb->iso_frame_desc[pack - 1].length :
+			0;
+		urb->iso_frame_desc[pack].length = cap_urb->iso_frame_desc[pack].actual_length;
+	}
+	if (atomic_read(&subs->state) >= state_PRERUNNING)
+		if (subs->hwptr + count > runtime->buffer_size) {
+			/* err, the transferred area goes over buffer boundary.
+			 * copy the data to the temp buffer.
+			 */
+			int len;
+			len = runtime->buffer_size - subs->hwptr;
+			urb->transfer_buffer = subs->tmpbuf;
+			memcpy(subs->tmpbuf, runtime->dma_area +
+			       subs->hwptr * usX2Y->stride, len * usX2Y->stride);
+			memcpy(subs->tmpbuf + len * usX2Y->stride,
+			       runtime->dma_area, (count - len) * usX2Y->stride);
+			subs->hwptr += count;
+			subs->hwptr -= runtime->buffer_size;
+		} else {
+			/* set the buffer pointer */
+			urb->transfer_buffer = runtime->dma_area + subs->hwptr * usX2Y->stride;
+			if ((subs->hwptr += count) >= runtime->buffer_size)
+			subs->hwptr -= runtime->buffer_size;			
+		}
+	else
+		urb->transfer_buffer = subs->tmpbuf;
+	urb->transfer_buffer_length = count * usX2Y->stride;
+	return 0;
+}
+
+/*
+ * process after playback data complete
+ *
+ * update the current position and call callback if a period is processed.
+ */
+static void usX2Y_urb_play_retire(struct snd_usX2Y_substream *subs, struct urb *urb)
+{
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+	int		len = urb->actual_length / subs->usX2Y->stride;
+
+	subs->transfer_done += len;
+	subs->hwptr_done +=  len;
+	if (subs->hwptr_done >= runtime->buffer_size)
+		subs->hwptr_done -= runtime->buffer_size;
+	if (subs->transfer_done >= runtime->period_size) {
+		subs->transfer_done -= runtime->period_size;
+		snd_pcm_period_elapsed(subs->pcm_substream);
+	}
+}
+
+static int usX2Y_urb_submit(struct snd_usX2Y_substream *subs, struct urb *urb, int frame)
+{
+	int err;
+	if (!urb)
+		return -ENODEV;
+	urb->start_frame = (frame + NRURBS * nr_of_packs());  // let hcd do rollover sanity checks
+	urb->hcpriv = NULL;
+	urb->dev = subs->usX2Y->dev; /* we need to set this at each time */
+	if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+		snd_printk(KERN_ERR "usb_submit_urb() returned %i\n", err);
+		return err;
+	}
+	return 0;
+}
+
+static inline int usX2Y_usbframe_complete(struct snd_usX2Y_substream *capsubs,
+					  struct snd_usX2Y_substream *playbacksubs,
+					  int frame)
+{
+	int err, state;
+	struct urb *urb = playbacksubs->completed_urb;
+
+	state = atomic_read(&playbacksubs->state);
+	if (NULL != urb) {
+		if (state == state_RUNNING)
+			usX2Y_urb_play_retire(playbacksubs, urb);
+		else if (state >= state_PRERUNNING)
+			atomic_inc(&playbacksubs->state);
+	} else {
+		switch (state) {
+		case state_STARTING1:
+			urb = playbacksubs->urb[0];
+			atomic_inc(&playbacksubs->state);
+			break;
+		case state_STARTING2:
+			urb = playbacksubs->urb[1];
+			atomic_inc(&playbacksubs->state);
+			break;
+		}
+	}
+	if (urb) {
+		if ((err = usX2Y_urb_play_prepare(playbacksubs, capsubs->completed_urb, urb)) ||
+		    (err = usX2Y_urb_submit(playbacksubs, urb, frame))) {
+			return err;
+		}
+	}
+
+	playbacksubs->completed_urb = NULL;
+
+	state = atomic_read(&capsubs->state);
+	if (state >= state_PREPARED) {
+		if (state == state_RUNNING) {
+			if ((err = usX2Y_urb_capt_retire(capsubs)))
+				return err;
+		} else if (state >= state_PRERUNNING)
+			atomic_inc(&capsubs->state);
+		if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame)))
+			return err;
+	}
+	capsubs->completed_urb = NULL;
+	return 0;
+}
+
+
+static void usX2Y_clients_stop(struct usX2Ydev *usX2Y)
+{
+	int s, u;
+
+	for (s = 0; s < 4; s++) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[s];
+		if (subs) {
+			snd_printdd("%i %p state=%i\n", s, subs, atomic_read(&subs->state));
+			atomic_set(&subs->state, state_STOPPED);
+		}
+	}
+	for (s = 0; s < 4; s++) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[s];
+		if (subs) {
+			if (atomic_read(&subs->state) >= state_PRERUNNING) {
+				snd_pcm_stop(subs->pcm_substream, SNDRV_PCM_STATE_XRUN);
+			}
+			for (u = 0; u < NRURBS; u++) {
+				struct urb *urb = subs->urb[u];
+				if (NULL != urb)
+					snd_printdd("%i status=%i start_frame=%i\n",
+						    u, urb->status, urb->start_frame);
+			}
+		}
+	}
+	usX2Y->prepare_subs = NULL;
+	wake_up(&usX2Y->prepare_wait_queue);
+}
+
+static void usX2Y_error_urb_status(struct usX2Ydev *usX2Y,
+				   struct snd_usX2Y_substream *subs, struct urb *urb)
+{
+	snd_printk(KERN_ERR "ep=%i stalled with status=%i\n", subs->endpoint, urb->status);
+	urb->status = 0;
+	usX2Y_clients_stop(usX2Y);
+}
+
+static void usX2Y_error_sequence(struct usX2Ydev *usX2Y,
+				 struct snd_usX2Y_substream *subs, struct urb *urb)
+{
+	snd_printk(KERN_ERR
+"Sequence Error!(hcd_frame=%i ep=%i%s;wait=%i,frame=%i).\n"
+"Most propably some urb of usb-frame %i is still missing.\n"
+"Cause could be too long delays in usb-hcd interrupt handling.\n",
+		   usb_get_current_frame_number(usX2Y->dev),
+		   subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out",
+		   usX2Y->wait_iso_frame, urb->start_frame, usX2Y->wait_iso_frame);
+	usX2Y_clients_stop(usX2Y);
+}
+
+static void i_usX2Y_urb_complete(struct urb *urb)
+{
+	struct snd_usX2Y_substream *subs = urb->context;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+
+	if (unlikely(atomic_read(&subs->state) < state_PREPARED)) {
+		snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n",
+			    usb_get_current_frame_number(usX2Y->dev),
+			    subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out",
+			    urb->status, urb->start_frame);
+		return;
+	}
+	if (unlikely(urb->status)) {
+		usX2Y_error_urb_status(usX2Y, subs, urb);
+		return;
+	}
+	if (likely((urb->start_frame & 0xFFFF) == (usX2Y->wait_iso_frame & 0xFFFF)))
+		subs->completed_urb = urb;
+	else {
+		usX2Y_error_sequence(usX2Y, subs, urb);
+		return;
+	}
+	{
+		struct snd_usX2Y_substream *capsubs = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE],
+			*playbacksubs = usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+		if (capsubs->completed_urb &&
+		    atomic_read(&capsubs->state) >= state_PREPARED &&
+		    (playbacksubs->completed_urb ||
+		     atomic_read(&playbacksubs->state) < state_PREPARED)) {
+			if (!usX2Y_usbframe_complete(capsubs, playbacksubs, urb->start_frame))
+				usX2Y->wait_iso_frame += nr_of_packs();
+			else {
+				snd_printdd("\n");
+				usX2Y_clients_stop(usX2Y);
+			}
+		}
+	}
+}
+
+static void usX2Y_urbs_set_complete(struct usX2Ydev * usX2Y,
+				    void (*complete)(struct urb *))
+{
+	int s, u;
+	for (s = 0; s < 4; s++) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[s];
+		if (NULL != subs)
+			for (u = 0; u < NRURBS; u++) {
+				struct urb * urb = subs->urb[u];
+				if (NULL != urb)
+					urb->complete = complete;
+			}
+	}
+}
+
+static void usX2Y_subs_startup_finish(struct usX2Ydev * usX2Y)
+{
+	usX2Y_urbs_set_complete(usX2Y, i_usX2Y_urb_complete);
+	usX2Y->prepare_subs = NULL;
+}
+
+static void i_usX2Y_subs_startup(struct urb *urb)
+{
+	struct snd_usX2Y_substream *subs = urb->context;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_substream *prepare_subs = usX2Y->prepare_subs;
+	if (NULL != prepare_subs)
+		if (urb->start_frame == prepare_subs->urb[0]->start_frame) {
+			usX2Y_subs_startup_finish(usX2Y);
+			atomic_inc(&prepare_subs->state);
+			wake_up(&usX2Y->prepare_wait_queue);
+		}
+
+	i_usX2Y_urb_complete(urb);
+}
+
+static void usX2Y_subs_prepare(struct snd_usX2Y_substream *subs)
+{
+	snd_printdd("usX2Y_substream_prepare(%p) ep=%i urb0=%p urb1=%p\n",
+		    subs, subs->endpoint, subs->urb[0], subs->urb[1]);
+	/* reset the pointer */
+	subs->hwptr = 0;
+	subs->hwptr_done = 0;
+	subs->transfer_done = 0;
+}
+
+
+static void usX2Y_urb_release(struct urb **urb, int free_tb)
+{
+	if (*urb) {
+		usb_kill_urb(*urb);
+		if (free_tb)
+			kfree((*urb)->transfer_buffer);
+		usb_free_urb(*urb);
+		*urb = NULL;
+	}
+}
+/*
+ * release a substreams urbs
+ */
+static void usX2Y_urbs_release(struct snd_usX2Y_substream *subs)
+{
+	int i;
+	snd_printdd("usX2Y_urbs_release() %i\n", subs->endpoint);
+	for (i = 0; i < NRURBS; i++)
+		usX2Y_urb_release(subs->urb + i,
+				  subs != subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]);
+
+	kfree(subs->tmpbuf);
+	subs->tmpbuf = NULL;
+}
+/*
+ * initialize a substream's urbs
+ */
+static int usX2Y_urbs_allocate(struct snd_usX2Y_substream *subs)
+{
+	int i;
+	unsigned int pipe;
+	int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+	struct usb_device *dev = subs->usX2Y->dev;
+
+	pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) :
+			usb_rcvisocpipe(dev, subs->endpoint);
+	subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback);
+	if (!subs->maxpacksize)
+		return -EINVAL;
+
+	if (is_playback && NULL == subs->tmpbuf) {	/* allocate a temporary buffer for playback */
+		subs->tmpbuf = kcalloc(nr_of_packs(), subs->maxpacksize, GFP_KERNEL);
+		if (NULL == subs->tmpbuf) {
+			snd_printk(KERN_ERR "cannot malloc tmpbuf\n");
+			return -ENOMEM;
+		}
+	}
+	/* allocate and initialize data urbs */
+	for (i = 0; i < NRURBS; i++) {
+		struct urb **purb = subs->urb + i;
+		if (*purb) {
+			usb_kill_urb(*purb);
+			continue;
+		}
+		*purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL);
+		if (NULL == *purb) {
+			usX2Y_urbs_release(subs);
+			return -ENOMEM;
+		}
+		if (!is_playback && !(*purb)->transfer_buffer) {
+			/* allocate a capture buffer per urb */
+			(*purb)->transfer_buffer = kmalloc(subs->maxpacksize * nr_of_packs(), GFP_KERNEL);
+			if (NULL == (*purb)->transfer_buffer) {
+				usX2Y_urbs_release(subs);
+				return -ENOMEM;
+			}
+		}
+		(*purb)->dev = dev;
+		(*purb)->pipe = pipe;
+		(*purb)->number_of_packets = nr_of_packs();
+		(*purb)->context = subs;
+		(*purb)->interval = 1;
+		(*purb)->complete = i_usX2Y_subs_startup;
+	}
+	return 0;
+}
+
+static void usX2Y_subs_startup(struct snd_usX2Y_substream *subs)
+{
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	usX2Y->prepare_subs = subs;
+	subs->urb[0]->start_frame = -1;
+	wmb();
+	usX2Y_urbs_set_complete(usX2Y, i_usX2Y_subs_startup);
+}
+
+static int usX2Y_urbs_start(struct snd_usX2Y_substream *subs)
+{
+	int i, err;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+
+	if ((err = usX2Y_urbs_allocate(subs)) < 0)
+		return err;
+	subs->completed_urb = NULL;
+	for (i = 0; i < 4; i++) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[i];
+		if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED)
+			goto start;
+	}
+
+ start:
+	usX2Y_subs_startup(subs);
+	for (i = 0; i < NRURBS; i++) {
+		struct urb *urb = subs->urb[i];
+		if (usb_pipein(urb->pipe)) {
+			unsigned long pack;
+			if (0 == i)
+				atomic_set(&subs->state, state_STARTING3);
+			urb->dev = usX2Y->dev;
+			urb->transfer_flags = URB_ISO_ASAP;
+			for (pack = 0; pack < nr_of_packs(); pack++) {
+				urb->iso_frame_desc[pack].offset = subs->maxpacksize * pack;
+				urb->iso_frame_desc[pack].length = subs->maxpacksize;
+			}
+			urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); 
+			if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+				snd_printk (KERN_ERR "cannot submit datapipe for urb %d, err = %d\n", i, err);
+				err = -EPIPE;
+				goto cleanup;
+			} else
+				if (i == 0)
+					usX2Y->wait_iso_frame = urb->start_frame;
+			urb->transfer_flags = 0;
+		} else {
+			atomic_set(&subs->state, state_STARTING1);
+			break;
+		}
+	}
+	err = 0;
+	wait_event(usX2Y->prepare_wait_queue, NULL == usX2Y->prepare_subs);
+	if (atomic_read(&subs->state) != state_PREPARED)
+		err = -EPIPE;
+
+ cleanup:
+	if (err) {
+		usX2Y_subs_startup_finish(usX2Y);
+		usX2Y_clients_stop(usX2Y);		// something is completely wroong > stop evrything
+	}
+	return err;
+}
+
+/*
+ * return the current pcm pointer.  just return the hwptr_done value.
+ */
+static snd_pcm_uframes_t snd_usX2Y_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_usX2Y_substream *subs = substream->runtime->private_data;
+	return subs->hwptr_done;
+}
+/*
+ * start/stop substream
+ */
+static int snd_usX2Y_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_usX2Y_substream *subs = substream->runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		snd_printdd("snd_usX2Y_pcm_trigger(START)\n");
+		if (atomic_read(&subs->state) == state_PREPARED &&
+		    atomic_read(&subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]->state) >= state_PREPARED) {
+			atomic_set(&subs->state, state_PRERUNNING);
+		} else {
+			snd_printdd("\n");
+			return -EPIPE;
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		snd_printdd("snd_usX2Y_pcm_trigger(STOP)\n");
+		if (atomic_read(&subs->state) >= state_PRERUNNING)
+			atomic_set(&subs->state, state_PREPARED);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/*
+ * allocate a buffer, setup samplerate
+ *
+ * so far we use a physically linear buffer although packetize transfer
+ * doesn't need a continuous area.
+ * if sg buffer is supported on the later version of alsa, we'll follow
+ * that.
+ */
+static struct s_c2
+{
+	char c1, c2;
+}
+	SetRate44100[] =
+{
+	{ 0x14, 0x08},	// this line sets 44100, well actually a little less
+	{ 0x18, 0x40},	// only tascam / frontier design knows the further lines .......
+	{ 0x18, 0x42},
+	{ 0x18, 0x45},
+	{ 0x18, 0x46},
+	{ 0x18, 0x48},
+	{ 0x18, 0x4A},
+	{ 0x18, 0x4C},
+	{ 0x18, 0x4E},
+	{ 0x18, 0x50},
+	{ 0x18, 0x52},
+	{ 0x18, 0x54},
+	{ 0x18, 0x56},
+	{ 0x18, 0x58},
+	{ 0x18, 0x5A},
+	{ 0x18, 0x5C},
+	{ 0x18, 0x5E},
+	{ 0x18, 0x60},
+	{ 0x18, 0x62},
+	{ 0x18, 0x64},
+	{ 0x18, 0x66},
+	{ 0x18, 0x68},
+	{ 0x18, 0x6A},
+	{ 0x18, 0x6C},
+	{ 0x18, 0x6E},
+	{ 0x18, 0x70},
+	{ 0x18, 0x72},
+	{ 0x18, 0x74},
+	{ 0x18, 0x76},
+	{ 0x18, 0x78},
+	{ 0x18, 0x7A},
+	{ 0x18, 0x7C},
+	{ 0x18, 0x7E}
+};
+static struct s_c2 SetRate48000[] =
+{
+	{ 0x14, 0x09},	// this line sets 48000, well actually a little less
+	{ 0x18, 0x40},	// only tascam / frontier design knows the further lines .......
+	{ 0x18, 0x42},
+	{ 0x18, 0x45},
+	{ 0x18, 0x46},
+	{ 0x18, 0x48},
+	{ 0x18, 0x4A},
+	{ 0x18, 0x4C},
+	{ 0x18, 0x4E},
+	{ 0x18, 0x50},
+	{ 0x18, 0x52},
+	{ 0x18, 0x54},
+	{ 0x18, 0x56},
+	{ 0x18, 0x58},
+	{ 0x18, 0x5A},
+	{ 0x18, 0x5C},
+	{ 0x18, 0x5E},
+	{ 0x18, 0x60},
+	{ 0x18, 0x62},
+	{ 0x18, 0x64},
+	{ 0x18, 0x66},
+	{ 0x18, 0x68},
+	{ 0x18, 0x6A},
+	{ 0x18, 0x6C},
+	{ 0x18, 0x6E},
+	{ 0x18, 0x70},
+	{ 0x18, 0x73},
+	{ 0x18, 0x74},
+	{ 0x18, 0x76},
+	{ 0x18, 0x78},
+	{ 0x18, 0x7A},
+	{ 0x18, 0x7C},
+	{ 0x18, 0x7E}
+};
+#define NOOF_SETRATE_URBS ARRAY_SIZE(SetRate48000)
+
+static void i_usX2Y_04Int(struct urb *urb)
+{
+	struct usX2Ydev *usX2Y = urb->context;
+	
+	if (urb->status)
+		snd_printk(KERN_ERR "snd_usX2Y_04Int() urb->status=%i\n", urb->status);
+	if (0 == --usX2Y->US04->len)
+		wake_up(&usX2Y->In04WaitQueue);
+}
+
+static int usX2Y_rate_set(struct usX2Ydev *usX2Y, int rate)
+{
+	int			err = 0, i;
+	struct snd_usX2Y_urbSeq	*us = NULL;
+	int			*usbdata = NULL;
+	struct s_c2		*ra = rate == 48000 ? SetRate48000 : SetRate44100;
+
+	if (usX2Y->rate != rate) {
+		us = kzalloc(sizeof(*us) + sizeof(struct urb*) * NOOF_SETRATE_URBS, GFP_KERNEL);
+		if (NULL == us) {
+			err = -ENOMEM;
+			goto cleanup;
+		}
+		usbdata = kmalloc(sizeof(int) * NOOF_SETRATE_URBS, GFP_KERNEL);
+		if (NULL == usbdata) {
+			err = -ENOMEM;
+			goto cleanup;
+		}
+		for (i = 0; i < NOOF_SETRATE_URBS; ++i) {
+			if (NULL == (us->urb[i] = usb_alloc_urb(0, GFP_KERNEL))) {
+				err = -ENOMEM;
+				goto cleanup;
+			}
+			((char*)(usbdata + i))[0] = ra[i].c1;
+			((char*)(usbdata + i))[1] = ra[i].c2;
+			usb_fill_bulk_urb(us->urb[i], usX2Y->dev, usb_sndbulkpipe(usX2Y->dev, 4),
+					  usbdata + i, 2, i_usX2Y_04Int, usX2Y);
+#ifdef OLD_USB
+			us->urb[i]->transfer_flags = USB_QUEUE_BULK;
+#endif
+		}
+		us->submitted =	0;
+		us->len =	NOOF_SETRATE_URBS;
+		usX2Y->US04 =	us;
+		wait_event_timeout(usX2Y->In04WaitQueue, 0 == us->len, HZ);
+		usX2Y->US04 =	NULL;
+		if (us->len)
+			err = -ENODEV;
+	cleanup:
+		if (us) {
+			us->submitted =	2*NOOF_SETRATE_URBS;
+			for (i = 0; i < NOOF_SETRATE_URBS; ++i) {
+				struct urb *urb = us->urb[i];
+				if (urb->status) {
+					if (!err)
+						err = -ENODEV;
+					usb_kill_urb(urb);
+				}
+				usb_free_urb(urb);
+			}
+			usX2Y->US04 = NULL;
+			kfree(usbdata);
+			kfree(us);
+			if (!err)
+				usX2Y->rate = rate;
+		}
+	}
+
+	return err;
+}
+
+
+static int usX2Y_format_set(struct usX2Ydev *usX2Y, snd_pcm_format_t format)
+{
+	int alternate, err;
+	struct list_head* p;
+	if (format == SNDRV_PCM_FORMAT_S24_3LE) {
+		alternate = 2;
+		usX2Y->stride = 6;
+	} else {
+		alternate = 1;
+		usX2Y->stride = 4;
+	}
+	list_for_each(p, &usX2Y->midi_list) {
+		snd_usbmidi_input_stop(p);
+	}
+	usb_kill_urb(usX2Y->In04urb);
+	if ((err = usb_set_interface(usX2Y->dev, 0, alternate))) {
+		snd_printk(KERN_ERR "usb_set_interface error \n");
+		return err;
+	}
+	usX2Y->In04urb->dev = usX2Y->dev;
+	err = usb_submit_urb(usX2Y->In04urb, GFP_KERNEL);
+	list_for_each(p, &usX2Y->midi_list) {
+		snd_usbmidi_input_start(p);
+	}
+	usX2Y->format = format;
+	usX2Y->rate = 0;
+	return err;
+}
+
+
+static int snd_usX2Y_pcm_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *hw_params)
+{
+	int			err = 0;
+	unsigned int		rate = params_rate(hw_params);
+	snd_pcm_format_t	format = params_format(hw_params);
+	struct snd_card *card = substream->pstr->pcm->card;
+	struct list_head *list;
+
+	snd_printdd("snd_usX2Y_hw_params(%p, %p)\n", substream, hw_params);
+	// all pcm substreams off one usX2Y have to operate at the same rate & format
+	list_for_each(list, &card->devices) {
+		struct snd_device *dev;
+		struct snd_pcm *pcm;
+		int s;
+		dev = snd_device(list);
+		if (dev->type != SNDRV_DEV_PCM)
+			continue;
+		pcm = dev->device_data;
+		for (s = 0; s < 2; ++s) {
+			struct snd_pcm_substream *test_substream;
+			test_substream = pcm->streams[s].substream;
+			if (test_substream && test_substream != substream  &&
+			    test_substream->runtime &&
+			    ((test_substream->runtime->format &&
+			      test_substream->runtime->format != format) ||
+			     (test_substream->runtime->rate &&
+			      test_substream->runtime->rate != rate)))
+				return -EINVAL;
+		}
+	}
+	if (0 > (err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)))) {
+		snd_printk(KERN_ERR "snd_pcm_lib_malloc_pages(%p, %i) returned %i\n",
+			   substream, params_buffer_bytes(hw_params), err);
+		return err;
+	}
+	return 0;
+}
+
+/*
+ * free the buffer
+ */
+static int snd_usX2Y_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data;
+	mutex_lock(&subs->usX2Y->prepare_mutex);
+	snd_printdd("snd_usX2Y_hw_free(%p)\n", substream);
+
+	if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
+		struct snd_usX2Y_substream *cap_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+		atomic_set(&subs->state, state_STOPPED);
+		usX2Y_urbs_release(subs);
+		if (!cap_subs->pcm_substream ||
+		    !cap_subs->pcm_substream->runtime ||
+		    !cap_subs->pcm_substream->runtime->status ||
+		    cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) {
+			atomic_set(&cap_subs->state, state_STOPPED);
+			usX2Y_urbs_release(cap_subs);
+		}
+	} else {
+		struct snd_usX2Y_substream *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+		if (atomic_read(&playback_subs->state) < state_PREPARED) {
+			atomic_set(&subs->state, state_STOPPED);
+			usX2Y_urbs_release(subs);
+		}
+	}
+	mutex_unlock(&subs->usX2Y->prepare_mutex);
+	return snd_pcm_lib_free_pages(substream);
+}
+/*
+ * prepare callback
+ *
+ * set format and initialize urbs
+ */
+static int snd_usX2Y_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_substream *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+	int err = 0;
+	snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream);
+
+	mutex_lock(&usX2Y->prepare_mutex);
+	usX2Y_subs_prepare(subs);
+// Start hardware streams
+// SyncStream first....
+	if (atomic_read(&capsubs->state) < state_PREPARED) {
+		if (usX2Y->format != runtime->format)
+			if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0)
+				goto up_prepare_mutex;
+		if (usX2Y->rate != runtime->rate)
+			if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0)
+				goto up_prepare_mutex;
+		snd_printdd("starting capture pipe for %s\n", subs == capsubs ? "self" : "playpipe");
+		if (0 > (err = usX2Y_urbs_start(capsubs)))
+			goto up_prepare_mutex;
+	}
+
+	if (subs != capsubs && atomic_read(&subs->state) < state_PREPARED)
+		err = usX2Y_urbs_start(subs);
+
+ up_prepare_mutex:
+	mutex_unlock(&usX2Y->prepare_mutex);
+	return err;
+}
+
+static struct snd_pcm_hardware snd_usX2Y_2c =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_BATCH),
+	.formats =                 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE,
+	.rates =                   SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+	.rate_min =                44100,
+	.rate_max =                48000,
+	.channels_min =            2,
+	.channels_max =            2,
+	.buffer_bytes_max =	(2*128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =              0
+};
+
+
+
+static int snd_usX2Y_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_usX2Y_substream	*subs = ((struct snd_usX2Y_substream **)
+					 snd_pcm_substream_chip(substream))[substream->stream];
+	struct snd_pcm_runtime	*runtime = substream->runtime;
+
+	if (subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS)
+		return -EBUSY;
+
+	runtime->hw = snd_usX2Y_2c;
+	runtime->private_data = subs;
+	subs->pcm_substream = substream;
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000);
+	return 0;
+}
+
+
+
+static int snd_usX2Y_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data;
+
+	subs->pcm_substream = NULL;
+
+	return 0;
+}
+
+
+static struct snd_pcm_ops snd_usX2Y_pcm_ops = 
+{
+	.open =		snd_usX2Y_pcm_open,
+	.close =	snd_usX2Y_pcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usX2Y_pcm_hw_params,
+	.hw_free =	snd_usX2Y_pcm_hw_free,
+	.prepare =	snd_usX2Y_pcm_prepare,
+	.trigger =	snd_usX2Y_pcm_trigger,
+	.pointer =	snd_usX2Y_pcm_pointer,
+};
+
+
+/*
+ * free a usb stream instance
+ */
+static void usX2Y_audio_stream_free(struct snd_usX2Y_substream **usX2Y_substream)
+{
+	kfree(usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]);
+	usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK] = NULL;
+
+	kfree(usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE]);
+	usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE] = NULL;
+}
+
+static void snd_usX2Y_pcm_private_free(struct snd_pcm *pcm)
+{
+	struct snd_usX2Y_substream **usX2Y_stream = pcm->private_data;
+	if (usX2Y_stream)
+		usX2Y_audio_stream_free(usX2Y_stream);
+}
+
+static int usX2Y_audio_stream_new(struct snd_card *card, int playback_endpoint, int capture_endpoint)
+{
+	struct snd_pcm *pcm;
+	int err, i;
+	struct snd_usX2Y_substream **usX2Y_substream =
+		usX2Y(card)->subs + 2 * usX2Y(card)->pcm_devs;
+
+	for (i = playback_endpoint ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE;
+	     i <= SNDRV_PCM_STREAM_CAPTURE; ++i) {
+		usX2Y_substream[i] = kzalloc(sizeof(struct snd_usX2Y_substream), GFP_KERNEL);
+		if (NULL == usX2Y_substream[i]) {
+			snd_printk(KERN_ERR "cannot malloc\n");
+			return -ENOMEM;
+		}
+		usX2Y_substream[i]->usX2Y = usX2Y(card);
+	}
+
+	if (playback_endpoint)
+		usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]->endpoint = playback_endpoint;
+	usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE]->endpoint = capture_endpoint;
+
+	err = snd_pcm_new(card, NAME_ALLCAPS" Audio", usX2Y(card)->pcm_devs,
+			  playback_endpoint ? 1 : 0, 1,
+			  &pcm);
+	if (err < 0) {
+		usX2Y_audio_stream_free(usX2Y_substream);
+		return err;
+	}
+
+	if (playback_endpoint)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usX2Y_pcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usX2Y_pcm_ops);
+
+	pcm->private_data = usX2Y_substream;
+	pcm->private_free = snd_usX2Y_pcm_private_free;
+	pcm->info_flags = 0;
+
+	sprintf(pcm->name, NAME_ALLCAPS" Audio #%d", usX2Y(card)->pcm_devs);
+
+	if ((playback_endpoint &&
+	     0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
+						     SNDRV_DMA_TYPE_CONTINUOUS,
+						     snd_dma_continuous_data(GFP_KERNEL),
+						     64*1024, 128*1024))) ||
+	    0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+	    					     SNDRV_DMA_TYPE_CONTINUOUS,
+	    					     snd_dma_continuous_data(GFP_KERNEL),
+						     64*1024, 128*1024))) {
+		snd_usX2Y_pcm_private_free(pcm);
+		return err;
+	}
+	usX2Y(card)->pcm_devs++;
+
+	return 0;
+}
+
+/*
+ * create a chip instance and set its names.
+ */
+int usX2Y_audio_create(struct snd_card *card)
+{
+	int err = 0;
+	
+	INIT_LIST_HEAD(&usX2Y(card)->pcm_list);
+
+	if (0 > (err = usX2Y_audio_stream_new(card, 0xA, 0x8)))
+		return err;
+	if (le16_to_cpu(usX2Y(card)->dev->descriptor.idProduct) == USB_ID_US428)
+	     if (0 > (err = usX2Y_audio_stream_new(card, 0, 0xA)))
+		     return err;
+	if (le16_to_cpu(usX2Y(card)->dev->descriptor.idProduct) != USB_ID_US122)
+		err = usX2Y_rate_set(usX2Y(card), 44100);	// Lets us428 recognize output-volume settings, disturbs us122.
+	return err;
+}
diff --git a/linux/sound/usb/usx2y/usx2y.h b/linux/sound/usb/usx2y/usx2y.h
new file mode 100644
index 0000000..7e59263
--- /dev/null
+++ b/linux/sound/usb/usx2y/usx2y.h
@@ -0,0 +1,51 @@
+/*
+ * Driver for Tascam US-X2Y USB soundcards
+ *
+ * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   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 __SOUND_USX2Y_COMMON_H
+#define __SOUND_USX2Y_COMMON_H
+
+
+#define USX2Y_DRIVER_VERSION	0x0100	/* 0.1.0 */
+
+
+/* hwdep id string */
+#define SND_USX2Y_LOADER_ID		"USX2Y Loader"
+#define SND_USX2Y_USBPCM_ID		"USX2Y USBPCM"
+
+/* hardware type */
+enum {
+	USX2Y_TYPE_122,
+	USX2Y_TYPE_224,
+	USX2Y_TYPE_428,
+	USX2Y_TYPE_NUMS
+};
+
+#define USB_ID_US122 0x8007
+#define USB_ID_US224 0x8005
+#define USB_ID_US428 0x8001
+
+/* chip status */
+enum {
+	USX2Y_STAT_CHIP_INIT	=	(1 << 0),	/* all operational */
+	USX2Y_STAT_CHIP_MMAP_PCM_URBS = (1 << 1),	/* pcm transport over mmaped urbs */
+	USX2Y_STAT_CHIP_HUP	=	(1 << 31),	/* all operational */
+};
+
+#endif /* __SOUND_USX2Y_COMMON_H */
diff --git a/linux/sound/usb/usx2y/usx2yhwdeppcm.c b/linux/sound/usb/usx2y/usx2yhwdeppcm.c
new file mode 100644
index 0000000..287ef73
--- /dev/null
+++ b/linux/sound/usb/usx2y/usx2yhwdeppcm.c
@@ -0,0 +1,794 @@
+/*
+ *   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
+ */
+
+/* USX2Y "rawusb" aka hwdep_pcm implementation
+
+ Its usb's unableness to atomically handle power of 2 period sized data chuncs
+ at standard samplerates,
+ what led to this part of the usx2y module: 
+ It provides the alsa kernel half of the usx2y-alsa-jack driver pair.
+ The pair uses a hardware dependant alsa-device for mmaped pcm transport.
+ Advantage achieved:
+         The usb_hc moves pcm data from/into memory via DMA.
+         That memory is mmaped by jack's usx2y driver.
+         Jack's usx2y driver is the first/last to read/write pcm data.
+         Read/write is a combination of power of 2 period shaping and
+         float/int conversation.
+         Compared to mainline alsa/jack we leave out power of 2 period shaping inside
+         snd-usb-usx2y which needs memcpy() and additional buffers.
+         As a side effect possible unwanted pcm-data coruption resulting of
+         standard alsa's snd-usb-usx2y period shaping scheme falls away.
+         Result is sane jack operation at buffering schemes down to 128frames,
+         2 periods.
+         plain usx2y alsa mode is able to achieve 64frames, 4periods, but only at the
+         cost of easier triggered i.e. aeolus xruns (128 or 256frames,
+         2periods works but is useless cause of crackling).
+
+ This is a first "proof of concept" implementation.
+ Later, functionalities should migrate to more apropriate places:
+ Userland:
+ - The jackd could mmap its float-pcm buffers directly from alsa-lib.
+ - alsa-lib could provide power of 2 period sized shaping combined with int/float
+   conversation.
+   Currently the usx2y jack driver provides above 2 services.
+ Kernel:
+ - rawusb dma pcm buffer transport should go to snd-usb-lib, so also snd-usb-audio
+   devices can use it.
+   Currently rawusb dma pcm buffer transport (this file) is only available to snd-usb-usx2y. 
+*/
+
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include "usbusx2yaudio.c"
+
+#if defined(USX2Y_NRPACKS_VARIABLE) || USX2Y_NRPACKS == 1
+
+#include <sound/hwdep.h>
+
+
+static int usX2Y_usbpcm_urb_capt_retire(struct snd_usX2Y_substream *subs)
+{
+	struct urb	*urb = subs->completed_urb;
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+	int 		i, lens = 0, hwptr_done = subs->hwptr_done;
+	struct usX2Ydev	*usX2Y = subs->usX2Y;
+	if (0 > usX2Y->hwdep_pcm_shm->capture_iso_start) { //FIXME
+		int head = usX2Y->hwdep_pcm_shm->captured_iso_head + 1;
+		if (head >= ARRAY_SIZE(usX2Y->hwdep_pcm_shm->captured_iso))
+			head = 0;
+		usX2Y->hwdep_pcm_shm->capture_iso_start = head;
+		snd_printdd("cap start %i\n", head);
+	}
+	for (i = 0; i < nr_of_packs(); i++) {
+		if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */
+			snd_printk(KERN_ERR "activ frame status %i. Most propably some hardware problem.\n", urb->iso_frame_desc[i].status);
+			return urb->iso_frame_desc[i].status;
+		}
+		lens += urb->iso_frame_desc[i].actual_length / usX2Y->stride;
+	}
+	if ((hwptr_done += lens) >= runtime->buffer_size)
+		hwptr_done -= runtime->buffer_size;
+	subs->hwptr_done = hwptr_done;
+	subs->transfer_done += lens;
+	/* update the pointer, call callback if necessary */
+	if (subs->transfer_done >= runtime->period_size) {
+		subs->transfer_done -= runtime->period_size;
+		snd_pcm_period_elapsed(subs->pcm_substream);
+	}
+	return 0;
+}
+
+static inline int usX2Y_iso_frames_per_buffer(struct snd_pcm_runtime *runtime,
+					      struct usX2Ydev * usX2Y)
+{
+	return (runtime->buffer_size * 1000) / usX2Y->rate + 1;	//FIXME: so far only correct period_size == 2^x ?
+}
+
+/*
+ * prepare urb for playback data pipe
+ *
+ * we copy the data directly from the pcm buffer.
+ * the current position to be copied is held in hwptr field.
+ * since a urb can handle only a single linear buffer, if the total
+ * transferred area overflows the buffer boundary, we cannot send
+ * it directly from the buffer.  thus the data is once copied to
+ * a temporary buffer and urb points to that.
+ */
+static int usX2Y_hwdep_urb_play_prepare(struct snd_usX2Y_substream *subs,
+					struct urb *urb)
+{
+	int count, counts, pack;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_hwdep_pcm_shm *shm = usX2Y->hwdep_pcm_shm;
+	struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
+
+	if (0 > shm->playback_iso_start) {
+		shm->playback_iso_start = shm->captured_iso_head -
+			usX2Y_iso_frames_per_buffer(runtime, usX2Y);
+		if (0 > shm->playback_iso_start)
+			shm->playback_iso_start += ARRAY_SIZE(shm->captured_iso);
+		shm->playback_iso_head = shm->playback_iso_start;
+	}
+
+	count = 0;
+	for (pack = 0; pack < nr_of_packs(); pack++) {
+		/* calculate the size of a packet */
+		counts = shm->captured_iso[shm->playback_iso_head].length / usX2Y->stride;
+		if (counts < 43 || counts > 50) {
+			snd_printk(KERN_ERR "should not be here with counts=%i\n", counts);
+			return -EPIPE;
+		}
+		/* set up descriptor */
+		urb->iso_frame_desc[pack].offset = shm->captured_iso[shm->playback_iso_head].offset;
+		urb->iso_frame_desc[pack].length = shm->captured_iso[shm->playback_iso_head].length;
+		if (atomic_read(&subs->state) != state_RUNNING)
+			memset((char *)urb->transfer_buffer + urb->iso_frame_desc[pack].offset, 0,
+			       urb->iso_frame_desc[pack].length);
+		if (++shm->playback_iso_head >= ARRAY_SIZE(shm->captured_iso))
+			shm->playback_iso_head = 0;
+		count += counts;
+	}
+	urb->transfer_buffer_length = count * usX2Y->stride;
+	return 0;
+}
+
+
+static inline void usX2Y_usbpcm_urb_capt_iso_advance(struct snd_usX2Y_substream *subs,
+						     struct urb *urb)
+{
+	int pack;
+	for (pack = 0; pack < nr_of_packs(); ++pack) {
+		struct usb_iso_packet_descriptor *desc = urb->iso_frame_desc + pack;
+		if (NULL != subs) {
+			struct snd_usX2Y_hwdep_pcm_shm *shm = subs->usX2Y->hwdep_pcm_shm;
+			int head = shm->captured_iso_head + 1;
+			if (head >= ARRAY_SIZE(shm->captured_iso))
+				head = 0;
+			shm->captured_iso[head].frame = urb->start_frame + pack;
+			shm->captured_iso[head].offset = desc->offset;
+			shm->captured_iso[head].length = desc->actual_length;
+			shm->captured_iso_head = head;
+			shm->captured_iso_frames++;
+		}
+		if ((desc->offset += desc->length * NRURBS*nr_of_packs()) +
+		    desc->length >= SSS)
+			desc->offset -= (SSS - desc->length);
+	}
+}
+
+static inline int usX2Y_usbpcm_usbframe_complete(struct snd_usX2Y_substream *capsubs,
+						 struct snd_usX2Y_substream *capsubs2,
+						 struct snd_usX2Y_substream *playbacksubs,
+						 int frame)
+{
+	int err, state;
+	struct urb *urb = playbacksubs->completed_urb;
+
+	state = atomic_read(&playbacksubs->state);
+	if (NULL != urb) {
+		if (state == state_RUNNING)
+			usX2Y_urb_play_retire(playbacksubs, urb);
+		else if (state >= state_PRERUNNING)
+			atomic_inc(&playbacksubs->state);
+	} else {
+		switch (state) {
+		case state_STARTING1:
+			urb = playbacksubs->urb[0];
+			atomic_inc(&playbacksubs->state);
+			break;
+		case state_STARTING2:
+			urb = playbacksubs->urb[1];
+			atomic_inc(&playbacksubs->state);
+			break;
+		}
+	}
+	if (urb) {
+		if ((err = usX2Y_hwdep_urb_play_prepare(playbacksubs, urb)) ||
+		    (err = usX2Y_urb_submit(playbacksubs, urb, frame))) {
+			return err;
+		}
+	}
+	
+	playbacksubs->completed_urb = NULL;
+
+	state = atomic_read(&capsubs->state);
+	if (state >= state_PREPARED) {
+		if (state == state_RUNNING) {
+			if ((err = usX2Y_usbpcm_urb_capt_retire(capsubs)))
+				return err;
+		} else if (state >= state_PRERUNNING)
+			atomic_inc(&capsubs->state);
+		usX2Y_usbpcm_urb_capt_iso_advance(capsubs, capsubs->completed_urb);
+		if (NULL != capsubs2)
+			usX2Y_usbpcm_urb_capt_iso_advance(NULL, capsubs2->completed_urb);
+		if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame)))
+			return err;
+		if (NULL != capsubs2)
+			if ((err = usX2Y_urb_submit(capsubs2, capsubs2->completed_urb, frame)))
+				return err;
+	}
+	capsubs->completed_urb = NULL;
+	if (NULL != capsubs2)
+		capsubs2->completed_urb = NULL;
+	return 0;
+}
+
+
+static void i_usX2Y_usbpcm_urb_complete(struct urb *urb)
+{
+	struct snd_usX2Y_substream *subs = urb->context;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_substream *capsubs, *capsubs2, *playbacksubs;
+
+	if (unlikely(atomic_read(&subs->state) < state_PREPARED)) {
+		snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n",
+			    usb_get_current_frame_number(usX2Y->dev),
+			    subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out",
+			    urb->status, urb->start_frame);
+		return;
+	}
+	if (unlikely(urb->status)) {
+		usX2Y_error_urb_status(usX2Y, subs, urb);
+		return;
+	}
+	if (likely((urb->start_frame & 0xFFFF) == (usX2Y->wait_iso_frame & 0xFFFF)))
+		subs->completed_urb = urb;
+	else {
+		usX2Y_error_sequence(usX2Y, subs, urb);
+		return;
+	}
+
+	capsubs = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+	capsubs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
+	playbacksubs = usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+	if (capsubs->completed_urb && atomic_read(&capsubs->state) >= state_PREPARED &&
+	    (NULL == capsubs2 || capsubs2->completed_urb) &&
+	    (playbacksubs->completed_urb || atomic_read(&playbacksubs->state) < state_PREPARED)) {
+		if (!usX2Y_usbpcm_usbframe_complete(capsubs, capsubs2, playbacksubs, urb->start_frame))
+			usX2Y->wait_iso_frame += nr_of_packs();
+		else {
+			snd_printdd("\n");
+			usX2Y_clients_stop(usX2Y);
+		}
+	}
+}
+
+
+static void usX2Y_hwdep_urb_release(struct urb **urb)
+{
+	usb_kill_urb(*urb);
+	usb_free_urb(*urb);
+	*urb = NULL;
+}
+
+/*
+ * release a substream
+ */
+static void usX2Y_usbpcm_urbs_release(struct snd_usX2Y_substream *subs)
+{
+	int i;
+	snd_printdd("snd_usX2Y_urbs_release() %i\n", subs->endpoint);
+	for (i = 0; i < NRURBS; i++)
+		usX2Y_hwdep_urb_release(subs->urb + i);
+}
+
+static void usX2Y_usbpcm_subs_startup_finish(struct usX2Ydev * usX2Y)
+{
+	usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_urb_complete);
+	usX2Y->prepare_subs = NULL;
+}
+
+static void i_usX2Y_usbpcm_subs_startup(struct urb *urb)
+{
+	struct snd_usX2Y_substream *subs = urb->context;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_substream *prepare_subs = usX2Y->prepare_subs;
+	if (NULL != prepare_subs &&
+	    urb->start_frame == prepare_subs->urb[0]->start_frame) {
+		atomic_inc(&prepare_subs->state);
+		if (prepare_subs == usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]) {
+			struct snd_usX2Y_substream *cap_subs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
+			if (cap_subs2 != NULL)
+				atomic_inc(&cap_subs2->state);
+		}
+		usX2Y_usbpcm_subs_startup_finish(usX2Y);
+		wake_up(&usX2Y->prepare_wait_queue);
+	}
+
+	i_usX2Y_usbpcm_urb_complete(urb);
+}
+
+/*
+ * initialize a substream's urbs
+ */
+static int usX2Y_usbpcm_urbs_allocate(struct snd_usX2Y_substream *subs)
+{
+	int i;
+	unsigned int pipe;
+	int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+	struct usb_device *dev = subs->usX2Y->dev;
+
+	pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) :
+			usb_rcvisocpipe(dev, subs->endpoint);
+	subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback);
+	if (!subs->maxpacksize)
+		return -EINVAL;
+
+	/* allocate and initialize data urbs */
+	for (i = 0; i < NRURBS; i++) {
+		struct urb **purb = subs->urb + i;
+		if (*purb) {
+			usb_kill_urb(*purb);
+			continue;
+		}
+		*purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL);
+		if (NULL == *purb) {
+			usX2Y_usbpcm_urbs_release(subs);
+			return -ENOMEM;
+		}
+		(*purb)->transfer_buffer = is_playback ?
+			subs->usX2Y->hwdep_pcm_shm->playback : (
+				subs->endpoint == 0x8 ?
+				subs->usX2Y->hwdep_pcm_shm->capture0x8 :
+				subs->usX2Y->hwdep_pcm_shm->capture0xA);
+
+		(*purb)->dev = dev;
+		(*purb)->pipe = pipe;
+		(*purb)->number_of_packets = nr_of_packs();
+		(*purb)->context = subs;
+		(*purb)->interval = 1;
+		(*purb)->complete = i_usX2Y_usbpcm_subs_startup;
+	}
+	return 0;
+}
+
+/*
+ * free the buffer
+ */
+static int snd_usX2Y_usbpcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data,
+		*cap_subs2 = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
+	mutex_lock(&subs->usX2Y->prepare_mutex);
+	snd_printdd("snd_usX2Y_usbpcm_hw_free(%p)\n", substream);
+
+	if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
+		struct snd_usX2Y_substream *cap_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+		atomic_set(&subs->state, state_STOPPED);
+		usX2Y_usbpcm_urbs_release(subs);
+		if (!cap_subs->pcm_substream ||
+		    !cap_subs->pcm_substream->runtime ||
+		    !cap_subs->pcm_substream->runtime->status ||
+		    cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) {
+			atomic_set(&cap_subs->state, state_STOPPED);
+			if (NULL != cap_subs2)
+				atomic_set(&cap_subs2->state, state_STOPPED);
+			usX2Y_usbpcm_urbs_release(cap_subs);
+			if (NULL != cap_subs2)
+				usX2Y_usbpcm_urbs_release(cap_subs2);
+		}
+	} else {
+		struct snd_usX2Y_substream *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
+		if (atomic_read(&playback_subs->state) < state_PREPARED) {
+			atomic_set(&subs->state, state_STOPPED);
+			if (NULL != cap_subs2)
+				atomic_set(&cap_subs2->state, state_STOPPED);
+			usX2Y_usbpcm_urbs_release(subs);
+			if (NULL != cap_subs2)
+				usX2Y_usbpcm_urbs_release(cap_subs2);
+		}
+	}
+	mutex_unlock(&subs->usX2Y->prepare_mutex);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static void usX2Y_usbpcm_subs_startup(struct snd_usX2Y_substream *subs)
+{
+	struct usX2Ydev * usX2Y = subs->usX2Y;
+	usX2Y->prepare_subs = subs;
+	subs->urb[0]->start_frame = -1;
+	smp_wmb();	// Make sure above modifications are seen by i_usX2Y_subs_startup()
+	usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_subs_startup);
+}
+
+static int usX2Y_usbpcm_urbs_start(struct snd_usX2Y_substream *subs)
+{
+	int	p, u, err,
+		stream = subs->pcm_substream->stream;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+
+	if (SNDRV_PCM_STREAM_CAPTURE == stream) {
+		usX2Y->hwdep_pcm_shm->captured_iso_head = -1;
+		usX2Y->hwdep_pcm_shm->captured_iso_frames = 0;
+	}
+
+	for (p = 0; 3 >= (stream + p); p += 2) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p];
+		if (subs != NULL) {
+			if ((err = usX2Y_usbpcm_urbs_allocate(subs)) < 0)
+				return err;
+			subs->completed_urb = NULL;
+		}
+	}
+
+	for (p = 0; p < 4; p++) {
+		struct snd_usX2Y_substream *subs = usX2Y->subs[p];
+		if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED)
+			goto start;
+	}
+
+ start:
+	usX2Y_usbpcm_subs_startup(subs);
+	for (u = 0; u < NRURBS; u++) {
+		for (p = 0; 3 >= (stream + p); p += 2) {
+			struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p];
+			if (subs != NULL) {
+				struct urb *urb = subs->urb[u];
+				if (usb_pipein(urb->pipe)) {
+					unsigned long pack;
+					if (0 == u)
+						atomic_set(&subs->state, state_STARTING3);
+					urb->dev = usX2Y->dev;
+					urb->transfer_flags = URB_ISO_ASAP;
+					for (pack = 0; pack < nr_of_packs(); pack++) {
+						urb->iso_frame_desc[pack].offset = subs->maxpacksize * (pack + u * nr_of_packs());
+						urb->iso_frame_desc[pack].length = subs->maxpacksize;
+					}
+					urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); 
+					if ((err = usb_submit_urb(urb, GFP_KERNEL)) < 0) {
+						snd_printk (KERN_ERR "cannot usb_submit_urb() for urb %d, err = %d\n", u, err);
+						err = -EPIPE;
+						goto cleanup;
+					}  else {
+						snd_printdd("%i\n", urb->start_frame);
+						if (u == 0)
+							usX2Y->wait_iso_frame = urb->start_frame;
+					}
+					urb->transfer_flags = 0;
+				} else {
+					atomic_set(&subs->state, state_STARTING1);
+					break;
+				}			
+			}
+		}
+	}
+	err = 0;
+	wait_event(usX2Y->prepare_wait_queue, NULL == usX2Y->prepare_subs);
+	if (atomic_read(&subs->state) != state_PREPARED)
+		err = -EPIPE;
+		
+ cleanup:
+	if (err) {
+		usX2Y_subs_startup_finish(usX2Y);	// Call it now
+		usX2Y_clients_stop(usX2Y);		// something is completely wroong > stop evrything			
+	}
+	return err;
+}
+
+/*
+ * prepare callback
+ *
+ * set format and initialize urbs
+ */
+static int snd_usX2Y_usbpcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data;
+	struct usX2Ydev *usX2Y = subs->usX2Y;
+	struct snd_usX2Y_substream *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
+	int err = 0;
+	snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream);
+
+	if (NULL == usX2Y->hwdep_pcm_shm) {
+		if (NULL == (usX2Y->hwdep_pcm_shm = snd_malloc_pages(sizeof(struct snd_usX2Y_hwdep_pcm_shm), GFP_KERNEL)))
+			return -ENOMEM;
+		memset(usX2Y->hwdep_pcm_shm, 0, sizeof(struct snd_usX2Y_hwdep_pcm_shm));
+	}
+
+	mutex_lock(&usX2Y->prepare_mutex);
+	usX2Y_subs_prepare(subs);
+// Start hardware streams
+// SyncStream first....
+	if (atomic_read(&capsubs->state) < state_PREPARED) {
+		if (usX2Y->format != runtime->format)
+			if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0)
+				goto up_prepare_mutex;
+		if (usX2Y->rate != runtime->rate)
+			if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0)
+				goto up_prepare_mutex;
+		snd_printdd("starting capture pipe for %s\n", subs == capsubs ?
+			    "self" : "playpipe");
+		if (0 > (err = usX2Y_usbpcm_urbs_start(capsubs)))
+			goto up_prepare_mutex;
+	}
+
+	if (subs != capsubs) {
+		usX2Y->hwdep_pcm_shm->playback_iso_start = -1;
+		if (atomic_read(&subs->state) < state_PREPARED) {
+			while (usX2Y_iso_frames_per_buffer(runtime, usX2Y) >
+			       usX2Y->hwdep_pcm_shm->captured_iso_frames) {
+				snd_printdd("Wait: iso_frames_per_buffer=%i,"
+					    "captured_iso_frames=%i\n",
+					    usX2Y_iso_frames_per_buffer(runtime, usX2Y),
+					    usX2Y->hwdep_pcm_shm->captured_iso_frames);
+				if (msleep_interruptible(10)) {
+					err = -ERESTARTSYS;
+					goto up_prepare_mutex;
+				}
+			} 
+			if (0 > (err = usX2Y_usbpcm_urbs_start(subs)))
+				goto up_prepare_mutex;
+		}
+		snd_printdd("Ready: iso_frames_per_buffer=%i,captured_iso_frames=%i\n",
+			    usX2Y_iso_frames_per_buffer(runtime, usX2Y),
+			    usX2Y->hwdep_pcm_shm->captured_iso_frames);
+	} else
+		usX2Y->hwdep_pcm_shm->capture_iso_start = -1;
+
+ up_prepare_mutex:
+	mutex_unlock(&usX2Y->prepare_mutex);
+	return err;
+}
+
+static struct snd_pcm_hardware snd_usX2Y_4c =
+{
+	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =                 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE,
+	.rates =                   SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+	.rate_min =                44100,
+	.rate_max =                48000,
+	.channels_min =            2,
+	.channels_max =            4,
+	.buffer_bytes_max =	(2*128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(128*1024),
+	.periods_min =		2,
+	.periods_max =		1024,
+	.fifo_size =              0
+};
+
+
+
+static int snd_usX2Y_usbpcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_usX2Y_substream	*subs = ((struct snd_usX2Y_substream **)
+					 snd_pcm_substream_chip(substream))[substream->stream];
+	struct snd_pcm_runtime	*runtime = substream->runtime;
+
+	if (!(subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS))
+		return -EBUSY;
+
+	runtime->hw = SNDRV_PCM_STREAM_PLAYBACK == substream->stream ? snd_usX2Y_2c :
+		(subs->usX2Y->subs[3] ? snd_usX2Y_4c : snd_usX2Y_2c);
+	runtime->private_data = subs;
+	subs->pcm_substream = substream;
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000);
+	return 0;
+}
+
+
+static int snd_usX2Y_usbpcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_usX2Y_substream *subs = runtime->private_data;
+
+	subs->pcm_substream = NULL;
+	return 0;
+}
+
+
+static struct snd_pcm_ops snd_usX2Y_usbpcm_ops = 
+{
+	.open =		snd_usX2Y_usbpcm_open,
+	.close =	snd_usX2Y_usbpcm_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_usX2Y_pcm_hw_params,
+	.hw_free =	snd_usX2Y_usbpcm_hw_free,
+	.prepare =	snd_usX2Y_usbpcm_prepare,
+	.trigger =	snd_usX2Y_pcm_trigger,
+	.pointer =	snd_usX2Y_pcm_pointer,
+};
+
+
+static int usX2Y_pcms_lock_check(struct snd_card *card)
+{
+	struct list_head *list;
+	struct snd_device *dev;
+	struct snd_pcm *pcm;
+	int err = 0;
+	list_for_each(list, &card->devices) {
+		dev = snd_device(list);
+		if (dev->type != SNDRV_DEV_PCM)
+			continue;
+		pcm = dev->device_data;
+		mutex_lock(&pcm->open_mutex);
+	}
+	list_for_each(list, &card->devices) {
+		int s;
+		dev = snd_device(list);
+		if (dev->type != SNDRV_DEV_PCM)
+			continue;
+		pcm = dev->device_data;
+		for (s = 0; s < 2; ++s) {
+			struct snd_pcm_substream *substream;
+			substream = pcm->streams[s].substream;
+			if (substream && SUBSTREAM_BUSY(substream))
+				err = -EBUSY;
+		}
+	}
+	return err;
+}
+
+
+static void usX2Y_pcms_unlock(struct snd_card *card)
+{
+	struct list_head *list;
+	struct snd_device *dev;
+	struct snd_pcm *pcm;
+	list_for_each(list, &card->devices) {
+		dev = snd_device(list);
+		if (dev->type != SNDRV_DEV_PCM)
+			continue;
+		pcm = dev->device_data;
+		mutex_unlock(&pcm->open_mutex);
+	}
+}
+
+
+static int snd_usX2Y_hwdep_pcm_open(struct snd_hwdep *hw, struct file *file)
+{
+	// we need to be the first 
+	struct snd_card *card = hw->card;
+	int err = usX2Y_pcms_lock_check(card);
+	if (0 == err)
+		usX2Y(card)->chip_status |= USX2Y_STAT_CHIP_MMAP_PCM_URBS;
+	usX2Y_pcms_unlock(card);
+	return err;
+}
+
+
+static int snd_usX2Y_hwdep_pcm_release(struct snd_hwdep *hw, struct file *file)
+{
+	struct snd_card *card = hw->card;
+	int err = usX2Y_pcms_lock_check(card);
+	if (0 == err)
+		usX2Y(hw->card)->chip_status &= ~USX2Y_STAT_CHIP_MMAP_PCM_URBS;
+	usX2Y_pcms_unlock(card);
+	return err;
+}
+
+
+static void snd_usX2Y_hwdep_pcm_vm_open(struct vm_area_struct *area)
+{
+}
+
+
+static void snd_usX2Y_hwdep_pcm_vm_close(struct vm_area_struct *area)
+{
+}
+
+
+static int snd_usX2Y_hwdep_pcm_vm_fault(struct vm_area_struct *area,
+					struct vm_fault *vmf)
+{
+	unsigned long offset;
+	void *vaddr;
+
+	offset = vmf->pgoff << PAGE_SHIFT;
+	vaddr = (char*)((struct usX2Ydev *)area->vm_private_data)->hwdep_pcm_shm + offset;
+	vmf->page = virt_to_page(vaddr);
+	get_page(vmf->page);
+	return 0;
+}
+
+
+static const struct vm_operations_struct snd_usX2Y_hwdep_pcm_vm_ops = {
+	.open = snd_usX2Y_hwdep_pcm_vm_open,
+	.close = snd_usX2Y_hwdep_pcm_vm_close,
+	.fault = snd_usX2Y_hwdep_pcm_vm_fault,
+};
+
+
+static int snd_usX2Y_hwdep_pcm_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area)
+{
+	unsigned long	size = (unsigned long)(area->vm_end - area->vm_start);
+	struct usX2Ydev	*usX2Y = hw->private_data;
+
+	if (!(usX2Y->chip_status & USX2Y_STAT_CHIP_INIT))
+		return -EBUSY;
+
+	/* if userspace tries to mmap beyond end of our buffer, fail */ 
+	if (size > PAGE_ALIGN(sizeof(struct snd_usX2Y_hwdep_pcm_shm))) {
+		snd_printd("%lu > %lu\n", size, (unsigned long)sizeof(struct snd_usX2Y_hwdep_pcm_shm)); 
+		return -EINVAL;
+	}
+
+	if (!usX2Y->hwdep_pcm_shm) {
+		return -ENODEV;
+	}
+	area->vm_ops = &snd_usX2Y_hwdep_pcm_vm_ops;
+	area->vm_flags |= VM_RESERVED | VM_DONTEXPAND;
+	area->vm_private_data = hw->private_data;
+	return 0;
+}
+
+
+static void snd_usX2Y_hwdep_pcm_private_free(struct snd_hwdep *hwdep)
+{
+	struct usX2Ydev *usX2Y = hwdep->private_data;
+	if (NULL != usX2Y->hwdep_pcm_shm)
+		snd_free_pages(usX2Y->hwdep_pcm_shm, sizeof(struct snd_usX2Y_hwdep_pcm_shm));
+}
+
+
+int usX2Y_hwdep_pcm_new(struct snd_card *card)
+{
+	int err;
+	struct snd_hwdep *hw;
+	struct snd_pcm *pcm;
+	struct usb_device *dev = usX2Y(card)->dev;
+	if (1 != nr_of_packs())
+		return 0;
+
+	if ((err = snd_hwdep_new(card, SND_USX2Y_USBPCM_ID, 1, &hw)) < 0)
+		return err;
+
+	hw->iface = SNDRV_HWDEP_IFACE_USX2Y_PCM;
+	hw->private_data = usX2Y(card);
+	hw->private_free = snd_usX2Y_hwdep_pcm_private_free;
+	hw->ops.open = snd_usX2Y_hwdep_pcm_open;
+	hw->ops.release = snd_usX2Y_hwdep_pcm_release;
+	hw->ops.mmap = snd_usX2Y_hwdep_pcm_mmap;
+	hw->exclusive = 1;
+	sprintf(hw->name, "/proc/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum);
+
+	err = snd_pcm_new(card, NAME_ALLCAPS" hwdep Audio", 2, 1, 1, &pcm);
+	if (err < 0) {
+		return err;
+	}
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usX2Y_usbpcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usX2Y_usbpcm_ops);
+
+	pcm->private_data = usX2Y(card)->subs;
+	pcm->info_flags = 0;
+
+	sprintf(pcm->name, NAME_ALLCAPS" hwdep Audio");
+	if (0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
+						     SNDRV_DMA_TYPE_CONTINUOUS,
+						     snd_dma_continuous_data(GFP_KERNEL),
+						     64*1024, 128*1024)) ||
+	    0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+	    					     SNDRV_DMA_TYPE_CONTINUOUS,
+	    					     snd_dma_continuous_data(GFP_KERNEL),
+						     64*1024, 128*1024))) {
+		return err;
+	}
+
+
+	return 0;
+}
+
+#else
+
+int usX2Y_hwdep_pcm_new(struct snd_card *card)
+{
+	return 0;
+}
+
+#endif
diff --git a/linux/sound/usb/usx2y/usx2yhwdeppcm.h b/linux/sound/usb/usx2y/usx2yhwdeppcm.h
new file mode 100644
index 0000000..9c4fb84
--- /dev/null
+++ b/linux/sound/usb/usx2y/usx2yhwdeppcm.h
@@ -0,0 +1,22 @@
+#define MAXPACK 50
+#define MAXBUFFERMS 100
+#define MAXSTRIDE 3
+
+#define SSS (((MAXPACK*MAXBUFFERMS*MAXSTRIDE + 4096) / 4096) * 4096)
+struct snd_usX2Y_hwdep_pcm_shm {
+	char playback[SSS];
+	char capture0x8[SSS];
+	char capture0xA[SSS];
+	volatile int playback_iso_head;
+	int playback_iso_start;
+	struct {
+		int	frame,
+			offset,
+			length;
+	} captured_iso[128];
+	volatile int captured_iso_head;
+	volatile unsigned captured_iso_frames;
+	int capture_iso_start;
+};
+
+int usX2Y_hwdep_pcm_new(struct snd_card *card);